From 52fe4b56fae033d34620f382ee8b581fa3216288 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Wed, 13 Mar 2024 17:07:34 -0500 Subject: [PATCH] Vendored dependencies (no more submodules) --- .gitmodules | 18 - Makefile | 11 +- quickjs | 1 - source/engine/thirdparty/TinySoundFont | 1 - .../engine/thirdparty/TinySoundFont/LICENSE | 19 + .../engine/thirdparty/TinySoundFont/README.md | 37 + .../TinySoundFont/examples/.gitignore | 15 + .../examples/build-linux-clang.sh | 7 + .../TinySoundFont/examples/build-linux-gcc.sh | 7 + .../TinySoundFont/examples/build-osx.sh | 7 + .../TinySoundFont/examples/example1.c | 86 + .../TinySoundFont/examples/example1.dsp | 77 + .../TinySoundFont/examples/example1.vcxproj | 107 + .../TinySoundFont/examples/example2.c | 81 + .../TinySoundFont/examples/example2.dsp | 77 + .../TinySoundFont/examples/example2.vcxproj | 107 + .../TinySoundFont/examples/example3.c | 125 + .../TinySoundFont/examples/example3.dsp | 77 + .../TinySoundFont/examples/example3.vcxproj | 108 + .../TinySoundFont/examples/examples.sln | 48 + .../examples/florestan-subset.sf2 | Bin 0 -> 531786 bytes .../TinySoundFont/examples/minisdl_audio.c | 19233 ++++++ .../TinySoundFont/examples/minisdl_audio.h | 2824 + .../TinySoundFont/examples/venture.mid | Bin 0 -> 7637 bytes .../TinySoundFont/sfotool/.gitignore | 15 + .../thirdparty/TinySoundFont/sfotool/Makefile | 2 + .../TinySoundFont/sfotool/README.md | 29 + .../thirdparty/TinySoundFont/sfotool/main.c | 200 + .../TinySoundFont/sfotool/sfotool.sln | 28 + .../TinySoundFont/sfotool/sfotool.vcxproj | 100 + source/engine/thirdparty/TinySoundFont/tml.h | 531 + source/engine/thirdparty/TinySoundFont/tsf.h | 2045 + source/engine/thirdparty/cgltf | 1 - .../cgltf/.github/workflows/build.yml | 15 + source/engine/thirdparty/cgltf/LICENSE | 7 + source/engine/thirdparty/cgltf/README.md | 161 + source/engine/thirdparty/cgltf/cgltf.h | 6739 ++ source/engine/thirdparty/cgltf/cgltf_write.h | 1403 + .../thirdparty/cgltf/fuzz/data/BadBasisU.gltf | 1 + .../engine/thirdparty/cgltf/fuzz/data/Box.glb | Bin 0 -> 1664 bytes .../fuzz/data/TriangleWithoutIndices.gltf | 53 + .../cgltf/fuzz/data/WaterBottle.gltf | 172 + source/engine/thirdparty/cgltf/fuzz/gltf.dict | 224 + source/engine/thirdparty/cgltf/fuzz/main.c | 22 + .../thirdparty/cgltf/test/CMakeLists.txt | 68 + source/engine/thirdparty/cgltf/test/main.c | 38 + .../engine/thirdparty/cgltf/test/test_all.py | 76 + .../thirdparty/cgltf/test/test_conversion.cpp | 92 + .../thirdparty/cgltf/test/test_math.cpp | 52 + .../thirdparty/cgltf/test/test_strings.cpp | 57 + .../thirdparty/cgltf/test/test_write.cpp | 45 + source/engine/thirdparty/dr_libs | 1 - .../thirdparty/dr_libs/.github/FUNDING.yml | 1 + source/engine/thirdparty/dr_libs/.gitignore | 12 + source/engine/thirdparty/dr_libs/.gitmodules | 3 + source/engine/thirdparty/dr_libs/LICENSE | 47 + source/engine/thirdparty/dr_libs/README.md | 21 + source/engine/thirdparty/dr_libs/dr_flac.h | 12536 ++++ source/engine/thirdparty/dr_libs/dr_mp3.h | 4837 ++ source/engine/thirdparty/dr_libs/dr_wav.h | 8815 +++ source/engine/thirdparty/dr_libs/old/dr.h | 4755 ++ source/engine/thirdparty/dr_libs/old/dr_2d.h | 3417 + .../engine/thirdparty/dr_libs/old/dr_audio.h | 5307 ++ .../thirdparty/dr_libs/old/dr_audio_ancient.h | 4310 ++ source/engine/thirdparty/dr_libs/old/dr_fs.h | 8051 +++ source/engine/thirdparty/dr_libs/old/dr_fsw.h | 1625 + source/engine/thirdparty/dr_libs/old/dr_gui.h | 17167 +++++ .../engine/thirdparty/dr_libs/old/dr_math.h | 676 + source/engine/thirdparty/dr_libs/old/dr_mtl.h | 4512 ++ source/engine/thirdparty/dr_libs/old/dr_pcx.h | 793 + .../engine/thirdparty/dr_libs/tests/README.md | 49 + .../dr_libs/tests/bin/DO_NOT_DELETE.md | 0 .../dr_libs/tests/build_flac_linux.sh | 4 + .../dr_libs/tests/build_flac_win32.bat | 37 + .../dr_libs/tests/build_mp3_win32.bat | 2 + .../dr_libs/tests/build_opus_win32.bat | 1 + .../dr_libs/tests/build_wav_win32.bat | 38 + .../dr_libs/tests/common/dr_common.c | 957 + .../dr_libs/tests/external/miniaudio | 1 + .../dr_libs/tests/flac/dr_flac_common.c | 332 + .../dr_libs/tests/flac/dr_flac_decoding.c | 825 + .../dr_libs/tests/flac/dr_flac_decoding.cpp | 1 + .../dr_libs/tests/flac/dr_flac_seeking.c | 483 + .../dr_libs/tests/flac/dr_flac_test_0.c | 11 + .../dr_libs/tests/flac/dr_flac_test_0.cpp | 1 + .../dr_libs/tests/flac/fuzz_dr_flac.c | 76 + .../dr_libs/tests/mp3/dr_mp3_common.c | 4 + .../dr_libs/tests/mp3/dr_mp3_playback.c | 161 + .../dr_libs/tests/mp3/dr_mp3_test_0.c | 12 + .../dr_libs/tests/opus/dr_opus_decoding.c | 205 + .../dr_libs/tests/opus/dr_opus_test_0.c | 9 + .../dr_libs/tests/opus/dr_opus_test_0.cpp | 1 + .../tests/testvectors/DO_NOT_DELETE.md | 0 .../tests/testvectors/flac/DO_NOT_DELETE.md | 0 .../tests/testvectors/mp3/DO_NOT_DELETE.md | 0 .../tests/testvectors/opus/DO_NOT_DELETE.md | 0 .../tests/testvectors/wav/DO_NOT_DELETE.md | 0 .../dr_libs/tests/wav/dr_wav_common.c | 326 + .../dr_libs/tests/wav/dr_wav_decoding.c | 446 + .../dr_libs/tests/wav/dr_wav_decoding.cpp | 1 + .../dr_libs/tests/wav/dr_wav_encoding.c | 68 + .../dr_libs/tests/wav/dr_wav_playback.c | 70 + .../dr_libs/tests/wav/dr_wav_playback.cpp | 1 + .../dr_libs/tests/wav/dr_wav_test_0.c | 11 + .../dr_libs/tests/wav/dr_wav_test_0.cpp | 1 + .../engine/thirdparty/dr_libs/wip/dr_opus.h | 2565 + .../thirdparty/dr_libs/wip/dr_opus_log.txt | 318 + .../quickjs/.github/workflows/ci.yml | 166 + source/engine/thirdparty/quickjs/.gitignore | 24 + source/engine/thirdparty/quickjs/Changelog | 175 + source/engine/thirdparty/quickjs/LICENSE | 22 + source/engine/thirdparty/quickjs/Makefile | 564 + source/engine/thirdparty/quickjs/TODO | 67 + source/engine/thirdparty/quickjs/VERSION | 1 + source/engine/thirdparty/quickjs/cutils.c | 631 + source/engine/thirdparty/quickjs/cutils.h | 341 + .../thirdparty/quickjs/doc/jsbignum.texi | 589 + .../thirdparty/quickjs/doc/quickjs.texi | 1122 + .../engine/thirdparty/quickjs/examples/fib.c | 72 + .../thirdparty/quickjs/examples/fib_module.js | 10 + .../thirdparty/quickjs/examples/hello.js | 1 + .../quickjs/examples/hello_module.js | 6 + .../quickjs/examples/pi_bigdecimal.js | 68 + .../quickjs/examples/pi_bigfloat.js | 66 + .../thirdparty/quickjs/examples/pi_bigint.js | 118 + .../thirdparty/quickjs/examples/point.c | 151 + .../thirdparty/quickjs/examples/test_fib.js | 6 + .../thirdparty/quickjs/examples/test_point.js | 40 + source/engine/thirdparty/quickjs/libbf.c | 8473 +++ source/engine/thirdparty/quickjs/libbf.h | 535 + .../thirdparty/quickjs/libregexp-opcode.h | 57 + source/engine/thirdparty/quickjs/libregexp.c | 2528 + source/engine/thirdparty/quickjs/libregexp.h | 92 + .../thirdparty/quickjs/libunicode-table.h | 4486 ++ source/engine/thirdparty/quickjs/libunicode.c | 1788 + source/engine/thirdparty/quickjs/libunicode.h | 127 + source/engine/thirdparty/quickjs/list.h | 99 + source/engine/thirdparty/quickjs/qjs.c | 564 + source/engine/thirdparty/quickjs/qjsc.c | 761 + source/engine/thirdparty/quickjs/qjscalc.js | 2657 + .../engine/thirdparty/quickjs/quickjs-atom.h | 273 + .../engine/thirdparty/quickjs/quickjs-libc.c | 4034 ++ .../engine/thirdparty/quickjs/quickjs-libc.h | 60 + .../thirdparty/quickjs/quickjs-opcode.h | 372 + source/engine/thirdparty/quickjs/quickjs.c | 55869 ++++++++++++++++ source/engine/thirdparty/quickjs/quickjs.h | 1062 + source/engine/thirdparty/quickjs/readme.txt | 1 + source/engine/thirdparty/quickjs/release.sh | 158 + source/engine/thirdparty/quickjs/repl.js | 1591 + .../engine/thirdparty/quickjs/run-test262.c | 2216 + source/engine/thirdparty/quickjs/test262.conf | 227 + .../thirdparty/quickjs/test262_errors.txt | 8 + .../engine/thirdparty/quickjs/test262o.conf | 413 + .../thirdparty/quickjs/test262o_errors.txt | 0 .../engine/thirdparty/quickjs/tests/bjson.c | 96 + .../thirdparty/quickjs/tests/microbench.js | 1322 + .../thirdparty/quickjs/tests/test262.patch | 71 + .../thirdparty/quickjs/tests/test_bigfloat.js | 279 + .../thirdparty/quickjs/tests/test_bignum.js | 114 + .../thirdparty/quickjs/tests/test_bjson.js | 191 + .../thirdparty/quickjs/tests/test_builtin.js | 856 + .../thirdparty/quickjs/tests/test_closure.js | 221 + .../thirdparty/quickjs/tests/test_language.js | 620 + .../thirdparty/quickjs/tests/test_loop.js | 392 + .../quickjs/tests/test_op_overloading.js | 207 + .../thirdparty/quickjs/tests/test_qjscalc.js | 256 + .../thirdparty/quickjs/tests/test_std.js | 307 + .../thirdparty/quickjs/tests/test_worker.js | 62 + .../quickjs/tests/test_worker_module.js | 32 + .../thirdparty/quickjs/unicode_download.sh | 19 + .../engine/thirdparty/quickjs/unicode_gen.c | 3169 + .../thirdparty/quickjs/unicode_gen_def.h | 291 + source/engine/thirdparty/sokol | 1 - source/engine/thirdparty/sokol/.editorconfig | 9 + .../sokol/.github/workflows/gen_bindings.yml | 331 + .../sokol/.github/workflows/main.yml | 65 + source/engine/thirdparty/sokol/.gitignore | 7 + source/engine/thirdparty/sokol/CHANGELOG.md | 2692 + source/engine/thirdparty/sokol/LICENSE | 22 + source/engine/thirdparty/sokol/README.md | 406 + .../thirdparty/sokol/bindgen/.gitignore | 8 + .../engine/thirdparty/sokol/bindgen/README.md | 40 + .../thirdparty/sokol/bindgen/gen_all.py | 37 + .../engine/thirdparty/sokol/bindgen/gen_ir.py | 124 + .../thirdparty/sokol/bindgen/gen_nim.py | 613 + .../thirdparty/sokol/bindgen/gen_odin.py | 500 + .../thirdparty/sokol/bindgen/gen_rust.py | 893 + .../thirdparty/sokol/bindgen/gen_util.py | 57 + .../thirdparty/sokol/bindgen/gen_zig.py | 552 + source/engine/thirdparty/sokol/fips.yml | 2 + source/engine/thirdparty/sokol/sokol_app.h | 11770 ++++ source/engine/thirdparty/sokol/sokol_args.h | 867 + source/engine/thirdparty/sokol/sokol_audio.h | 2594 + source/engine/thirdparty/sokol/sokol_fetch.h | 2812 + source/engine/thirdparty/sokol/sokol_gfx.h | 18866 ++++++ source/engine/thirdparty/sokol/sokol_glue.h | 162 + source/engine/thirdparty/sokol/sokol_log.h | 343 + source/engine/thirdparty/sokol/sokol_time.h | 319 + .../engine/thirdparty/sokol/tests/.gitignore | 1 + .../thirdparty/sokol/tests/CMakeLists.txt | 164 + .../thirdparty/sokol/tests/CMakePresets.json | 720 + .../thirdparty/sokol/tests/analyze_ios.sh | 6 + .../thirdparty/sokol/tests/analyze_linux.sh | 6 + .../thirdparty/sokol/tests/analyze_macos.sh | 8 + .../thirdparty/sokol/tests/analyze_win.cmd | 4 + .../sokol/tests/compile/CMakeLists.txt | 58 + .../sokol/tests/compile/sokol_app.c | 6 + .../sokol/tests/compile/sokol_app.cc | 6 + .../sokol/tests/compile/sokol_args.c | 6 + .../sokol/tests/compile/sokol_args.cc | 6 + .../sokol/tests/compile/sokol_audio.c | 6 + .../sokol/tests/compile/sokol_audio.cc | 6 + .../sokol/tests/compile/sokol_color.c | 8 + .../sokol/tests/compile/sokol_color.cc | 9 + .../sokol/tests/compile/sokol_debugtext.c | 7 + .../sokol/tests/compile/sokol_debugtext.cc | 7 + .../sokol/tests/compile/sokol_fetch.c | 6 + .../sokol/tests/compile/sokol_fetch.cc | 6 + .../sokol/tests/compile/sokol_fontstash.c | 21 + .../sokol/tests/compile/sokol_fontstash.cc | 23 + .../sokol/tests/compile/sokol_gfx.c | 6 + .../sokol/tests/compile/sokol_gfx.cc | 6 + .../sokol/tests/compile/sokol_gfx_imgui.c | 17 + .../sokol/tests/compile/sokol_gfx_imgui.cc | 12 + .../thirdparty/sokol/tests/compile/sokol_gl.c | 7 + .../sokol/tests/compile/sokol_gl.cc | 7 + .../sokol/tests/compile/sokol_glue.c | 9 + .../sokol/tests/compile/sokol_glue.cc | 9 + .../sokol/tests/compile/sokol_imgui.c | 18 + .../sokol/tests/compile/sokol_imgui.cc | 12 + .../sokol/tests/compile/sokol_log.c | 6 + .../sokol/tests/compile/sokol_log.cc | 6 + .../sokol/tests/compile/sokol_main.c | 13 + .../sokol/tests/compile/sokol_main.cc | 7 + .../sokol/tests/compile/sokol_nuklear.c | 22 + .../sokol/tests/compile/sokol_shape.c | 7 + .../sokol/tests/compile/sokol_shape.cc | 7 + .../sokol/tests/compile/sokol_spine.c | 8 + .../sokol/tests/compile/sokol_spine.cc | 9 + .../sokol/tests/compile/sokol_time.c | 6 + .../sokol/tests/compile/sokol_time.cc | 7 + .../thirdparty/sokol/tests/ext/CMakeLists.txt | 97 + .../thirdparty/sokol/tests/ext/fontstash.h | 1714 + .../thirdparty/sokol/tests/ext/nuklear.c | 33 + .../thirdparty/sokol/tests/ext/nuklear.h | 29499 ++++++++ .../thirdparty/sokol/tests/ext/stb_truetype.h | 4548 ++ .../sokol/tests/functional/CMakeLists.txt | 23 + .../sokol/tests/functional/README.txt | 1 + .../sokol/tests/functional/assets/comsi.s3m | Bin 0 -> 409482 bytes .../sokol/tests/functional/assets/readme.txt | 3 + .../tests/functional/force_dummy_backend.h | 18 + .../sokol/tests/functional/sokol_args_test.c | 302 + .../sokol/tests/functional/sokol_audio_test.c | 99 + .../sokol/tests/functional/sokol_color_test.c | 70 + .../tests/functional/sokol_debugtext_test.c | 520 + .../sokol/tests/functional/sokol_fetch_test.c | 862 + .../sokol/tests/functional/sokol_gfx_test.c | 2975 + .../sokol/tests/functional/sokol_gl_test.c | 323 + .../sokol/tests/functional/sokol_log_test.c | 2 + .../sokol/tests/functional/sokol_shape_test.c | 421 + .../sokol/tests/functional/sokol_spine_test.c | 1197 + .../sokol/tests/functional/sokol_test.c | 7 + .../thirdparty/sokol/tests/functional/utest.h | 859 + .../thirdparty/sokol/tests/test_android.sh | 8 + .../thirdparty/sokol/tests/test_common.sh | 50 + .../thirdparty/sokol/tests/test_emscripten.sh | 8 + .../engine/thirdparty/sokol/tests/test_ios.sh | 10 + .../thirdparty/sokol/tests/test_linux.sh | 10 + .../thirdparty/sokol/tests/test_macos.sh | 11 + .../thirdparty/sokol/tests/test_win.cmd | 11 + .../thirdparty/sokol/util/gen_sokol_color.py | 500 + .../thirdparty/sokol/util/sokol_color.h | 1148 + .../thirdparty/sokol/util/sokol_debugtext.h | 4561 ++ .../thirdparty/sokol/util/sokol_fontstash.h | 1784 + .../thirdparty/sokol/util/sokol_gfx_imgui.h | 4720 ++ .../engine/thirdparty/sokol/util/sokol_gl.h | 4254 ++ .../thirdparty/sokol/util/sokol_imgui.h | 3141 + .../thirdparty/sokol/util/sokol_memtrack.h | 167 + .../thirdparty/sokol/util/sokol_nuklear.h | 2667 + .../thirdparty/sokol/util/sokol_shape.h | 1431 + .../thirdparty/sokol/util/sokol_spine.h | 5941 ++ source/engine/thirdparty/stb | 1 - .../thirdparty/stb/.github/CONTRIBUTING.md | 32 + .../stb/.github/ISSUE_TEMPLATE/bug_report.md | 24 + .../stb/.github/ISSUE_TEMPLATE/config.yml | 4 + .../.github/ISSUE_TEMPLATE/feature_request.md | 20 + .../stb/.github/PULL_REQUEST_TEMPLATE.md | 6 + .../stb/.github/workflows/ci-fuzz.yml | 23 + source/engine/thirdparty/stb/.gitignore | 3 + source/engine/thirdparty/stb/.travis.yml | 8 + source/engine/thirdparty/stb/LICENSE | 37 + source/engine/thirdparty/stb/README.md | 184 + .../stb/data/atari_8bit_font_revised.png | Bin 0 -> 1769 bytes .../thirdparty/stb/data/easy_font_raw.png | Bin 0 -> 645 bytes .../stb/data/herringbone/license.txt | 4 + .../template_caves_limit_connectivity.png | Bin 0 -> 10818 bytes .../template_caves_tiny_corridors.png | Bin 0 -> 4904 bytes .../herringbone/template_corner_caves.png | Bin 0 -> 9337 bytes .../template_horizontal_corridors_v1.png | Bin 0 -> 5491 bytes .../template_horizontal_corridors_v2.png | Bin 0 -> 5759 bytes .../template_horizontal_corridors_v3.png | Bin 0 -> 6766 bytes .../template_limit_connectivity_fat.png | Bin 0 -> 6655 bytes .../template_limited_connectivity.png | Bin 0 -> 6728 bytes .../data/herringbone/template_maze_2_wide.png | Bin 0 -> 12776 bytes .../herringbone/template_maze_plus_2_wide.png | Bin 0 -> 13141 bytes .../data/herringbone/template_open_areas.png | Bin 0 -> 7764 bytes .../template_ref2_corner_caves.png | Bin 0 -> 9729 bytes .../template_rooms_and_corridors.png | Bin 0 -> 4736 bytes ...oms_and_corridors_2_wide_diagonal_bias.png | Bin 0 -> 4193 bytes .../template_rooms_limit_connectivity.png | Bin 0 -> 7664 bytes ...emplate_round_rooms_diagonal_corridors.png | Bin 0 -> 8172 bytes .../herringbone/template_sean_dungeon.png | Bin 0 -> 10261 bytes .../template_simple_caves_2_wide.png | Bin 0 -> 15712 bytes ...emplate_square_rooms_with_random_rects.png | Bin 0 -> 4772 bytes source/engine/thirdparty/stb/data/map_01.png | Bin 0 -> 30625 bytes source/engine/thirdparty/stb/data/map_02.png | Bin 0 -> 3243 bytes source/engine/thirdparty/stb/data/map_03.png | Bin 0 -> 9620 bytes .../thirdparty/stb/deprecated/rrsprintf.h | 1055 + source/engine/thirdparty/stb/deprecated/stb.h | 13111 ++++ .../thirdparty/stb/deprecated/stb_image.c | 4678 ++ .../stb/deprecated/stb_image_resize.h | 2634 + .../thirdparty/stb/deprecated/stretch_test.c | 29 + .../stb/deprecated/stretchy_buffer.h | 263 + .../stb/deprecated/stretchy_buffer.txt | 28 + .../engine/thirdparty/stb/docs/other_libs.md | 1 + .../engine/thirdparty/stb/docs/stb_howto.txt | 185 + .../stb/docs/stb_voxel_render_interview.md | 173 + .../thirdparty/stb/docs/why_public_domain.md | 117 + source/engine/thirdparty/stb/stb_c_lexer.h | 940 + .../thirdparty/stb/stb_connected_components.h | 1049 + source/engine/thirdparty/stb/stb_divide.h | 433 + source/engine/thirdparty/stb/stb_ds.h | 1895 + source/engine/thirdparty/stb/stb_dxt.h | 719 + source/engine/thirdparty/stb/stb_easy_font.h | 305 + .../stb/stb_herringbone_wang_tile.h | 1221 + source/engine/thirdparty/stb/stb_hexwave.h | 680 + source/engine/thirdparty/stb/stb_image.h | 7985 +++ .../engine/thirdparty/stb/stb_image_resize2.h | 10365 +++ .../stb/stb_image_resize_test/dotimings.c | 224 + .../stb_image_resize_test/old_image_resize.h | 2738 + .../stb/stb_image_resize_test/oldir.c | 56 + .../stb/stb_image_resize_test/stbirtest.c | 992 + .../stb/stb_image_resize_test/vf_train.c | 999 + .../engine/thirdparty/stb/stb_image_write.h | 1724 + source/engine/thirdparty/stb/stb_include.h | 295 + source/engine/thirdparty/stb/stb_leakcheck.h | 194 + source/engine/thirdparty/stb/stb_perlin.h | 428 + source/engine/thirdparty/stb/stb_rect_pack.h | 623 + source/engine/thirdparty/stb/stb_sprintf.h | 1906 + source/engine/thirdparty/stb/stb_textedit.h | 1429 + .../thirdparty/stb/stb_tilemap_editor.h | 4187 ++ source/engine/thirdparty/stb/stb_truetype.h | 5077 ++ source/engine/thirdparty/stb/stb_vorbis.c | 5584 ++ .../engine/thirdparty/stb/stb_voxel_render.h | 3807 ++ source/engine/thirdparty/stb/tests/Makefile | 12 + .../thirdparty/stb/tests/c_lexer_test.c | 50 + .../thirdparty/stb/tests/c_lexer_test.dsp | 89 + .../thirdparty/stb/tests/caveview/README.md | 85 + .../thirdparty/stb/tests/caveview/cave_main.c | 598 + .../stb/tests/caveview/cave_mesher.c | 933 + .../stb/tests/caveview/cave_parse.c | 632 + .../stb/tests/caveview/cave_parse.h | 41 + .../stb/tests/caveview/cave_render.c | 951 + .../stb/tests/caveview/caveview.dsp | 157 + .../stb/tests/caveview/caveview.dsw | 29 + .../thirdparty/stb/tests/caveview/caveview.h | 50 + .../thirdparty/stb/tests/caveview/glext.h | 11124 +++ .../stb/tests/caveview/glext_list.h | 34 + .../thirdparty/stb/tests/caveview/main.c | 0 .../thirdparty/stb/tests/caveview/stb_gl.h | 1103 + .../stb/tests/caveview/stb_glprog.h | 504 + .../tests/caveview/win32/SDL_windows_main.c | 224 + .../engine/thirdparty/stb/tests/fuzz_main.c | 54 + .../thirdparty/stb/tests/grid_reachability.c | 363 + .../thirdparty/stb/tests/herringbone.dsp | 95 + .../stb/tests/herringbone_generator.c | 87 + .../thirdparty/stb/tests/herringbone_map.c | 83 + .../thirdparty/stb/tests/herringbone_map.dsp | 94 + .../engine/thirdparty/stb/tests/image_test.c | 173 + .../thirdparty/stb/tests/image_test.dsp | 103 + .../thirdparty/stb/tests/image_write_test.c | 60 + source/engine/thirdparty/stb/tests/ossfuzz.sh | 29 + .../thirdparty/stb/tests/oversample/README.md | 94 + .../thirdparty/stb/tests/oversample/main.c | 332 + .../stb/tests/oversample/oversample.dsp | 97 + .../stb/tests/oversample/oversample.dsw | 29 + .../stb/tests/oversample/oversample.exe | Bin 0 -> 54272 bytes .../stb/tests/oversample/stb_wingraph.h | 829 + .../thirdparty/stb/tests/pbm/basi0g16.pgm | Bin 0 -> 2064 bytes .../thirdparty/stb/tests/pbm/basi2c16.ppm | Bin 0 -> 6160 bytes .../thirdparty/stb/tests/pbm/cdfn2c08.ppm | Bin 0 -> 826 bytes .../thirdparty/stb/tests/pbm/cdun2c08.ppm | Bin 0 -> 3086 bytes .../thirdparty/stb/tests/pbm/comment.pgm | Bin 0 -> 1083 bytes .../thirdparty/stb/tests/pbm/ctfn0g04.pgm | Bin 0 -> 1038 bytes .../thirdparty/stb/tests/pg_test/pg_test.c | 124 + .../stb/tests/pngsuite/16bit/basi0g16.png | Bin 0 -> 299 bytes .../stb/tests/pngsuite/16bit/basi2c16.png | Bin 0 -> 595 bytes .../stb/tests/pngsuite/16bit/basi4a16.png | Bin 0 -> 2855 bytes .../stb/tests/pngsuite/16bit/basi6a16.png | Bin 0 -> 4180 bytes .../stb/tests/pngsuite/16bit/basn0g16.png | Bin 0 -> 167 bytes .../stb/tests/pngsuite/16bit/basn2c16.png | Bin 0 -> 302 bytes .../stb/tests/pngsuite/16bit/basn4a16.png | Bin 0 -> 2206 bytes .../stb/tests/pngsuite/16bit/basn6a16.png | Bin 0 -> 3435 bytes .../stb/tests/pngsuite/16bit/bgai4a16.png | Bin 0 -> 2855 bytes .../stb/tests/pngsuite/16bit/bgan6a16.png | Bin 0 -> 3435 bytes .../stb/tests/pngsuite/16bit/bggn4a16.png | Bin 0 -> 2220 bytes .../stb/tests/pngsuite/16bit/bgyn6a16.png | Bin 0 -> 3453 bytes .../stb/tests/pngsuite/16bit/oi1n0g16.png | Bin 0 -> 167 bytes .../stb/tests/pngsuite/16bit/oi1n2c16.png | Bin 0 -> 302 bytes .../stb/tests/pngsuite/16bit/oi2n0g16.png | Bin 0 -> 179 bytes .../stb/tests/pngsuite/16bit/oi2n2c16.png | Bin 0 -> 314 bytes .../stb/tests/pngsuite/16bit/oi4n0g16.png | Bin 0 -> 203 bytes .../stb/tests/pngsuite/16bit/oi4n2c16.png | Bin 0 -> 338 bytes .../stb/tests/pngsuite/16bit/oi9n0g16.png | Bin 0 -> 1283 bytes .../stb/tests/pngsuite/16bit/oi9n2c16.png | Bin 0 -> 3038 bytes .../stb/tests/pngsuite/16bit/tbbn2c16.png | Bin 0 -> 2041 bytes .../stb/tests/pngsuite/16bit/tbgn2c16.png | Bin 0 -> 2041 bytes .../stb/tests/pngsuite/16bit/tbwn0g16.png | Bin 0 -> 1313 bytes .../stb/tests/pngsuite/PngSuite.LICENSE | 9 + .../stb/tests/pngsuite/corrupt/xc1n0g08.png | Bin 0 -> 138 bytes .../stb/tests/pngsuite/corrupt/xc9n2c08.png | Bin 0 -> 145 bytes .../stb/tests/pngsuite/corrupt/xcrn0g04.png | Bin 0 -> 145 bytes .../stb/tests/pngsuite/corrupt/xcsn0g01.png | Bin 0 -> 164 bytes .../stb/tests/pngsuite/corrupt/xd0n2c08.png | Bin 0 -> 145 bytes .../stb/tests/pngsuite/corrupt/xd3n2c08.png | Bin 0 -> 145 bytes .../stb/tests/pngsuite/corrupt/xd9n2c08.png | Bin 0 -> 145 bytes .../stb/tests/pngsuite/corrupt/xdtn0g01.png | Bin 0 -> 61 bytes .../stb/tests/pngsuite/corrupt/xhdn0g08.png | Bin 0 -> 138 bytes .../stb/tests/pngsuite/corrupt/xlfn0g04.png | Bin 0 -> 145 bytes .../stb/tests/pngsuite/corrupt/xs1n0g01.png | Bin 0 -> 164 bytes .../stb/tests/pngsuite/corrupt/xs2n0g01.png | Bin 0 -> 164 bytes .../stb/tests/pngsuite/corrupt/xs4n0g01.png | Bin 0 -> 164 bytes .../stb/tests/pngsuite/corrupt/xs7n0g01.png | Bin 0 -> 164 bytes .../tests/pngsuite/iphone/iphone_basi0g01.png | Bin 0 -> 384 bytes .../tests/pngsuite/iphone/iphone_basi0g02.png | Bin 0 -> 302 bytes .../tests/pngsuite/iphone/iphone_basi3p02.png | Bin 0 -> 303 bytes .../tests/pngsuite/iphone/iphone_bgwn6a08.png | Bin 0 -> 2573 bytes .../tests/pngsuite/iphone/iphone_bgyn6a16.png | Bin 0 -> 2267 bytes .../tests/pngsuite/iphone/iphone_tbyn3p08.png | Bin 0 -> 1558 bytes .../tests/pngsuite/iphone/iphone_z06n2c08.png | Bin 0 -> 2393 bytes .../stb/tests/pngsuite/primary/basi0g01.png | Bin 0 -> 217 bytes .../stb/tests/pngsuite/primary/basi0g02.png | Bin 0 -> 154 bytes .../stb/tests/pngsuite/primary/basi0g04.png | Bin 0 -> 247 bytes .../stb/tests/pngsuite/primary/basi0g08.png | Bin 0 -> 254 bytes .../stb/tests/pngsuite/primary/basi2c08.png | Bin 0 -> 315 bytes .../stb/tests/pngsuite/primary/basi3p01.png | Bin 0 -> 132 bytes .../stb/tests/pngsuite/primary/basi3p02.png | Bin 0 -> 193 bytes .../stb/tests/pngsuite/primary/basi3p04.png | Bin 0 -> 327 bytes .../stb/tests/pngsuite/primary/basi3p08.png | Bin 0 -> 1527 bytes .../stb/tests/pngsuite/primary/basi4a08.png | Bin 0 -> 214 bytes .../stb/tests/pngsuite/primary/basi6a08.png | Bin 0 -> 361 bytes .../stb/tests/pngsuite/primary/basn0g01.png | Bin 0 -> 164 bytes .../stb/tests/pngsuite/primary/basn0g02.png | Bin 0 -> 104 bytes .../stb/tests/pngsuite/primary/basn0g04.png | Bin 0 -> 145 bytes .../stb/tests/pngsuite/primary/basn0g08.png | Bin 0 -> 138 bytes .../stb/tests/pngsuite/primary/basn2c08.png | Bin 0 -> 145 bytes .../stb/tests/pngsuite/primary/basn3p01.png | Bin 0 -> 112 bytes .../stb/tests/pngsuite/primary/basn3p02.png | Bin 0 -> 146 bytes .../stb/tests/pngsuite/primary/basn3p04.png | Bin 0 -> 216 bytes .../stb/tests/pngsuite/primary/basn3p08.png | Bin 0 -> 1286 bytes .../stb/tests/pngsuite/primary/basn4a08.png | Bin 0 -> 126 bytes .../stb/tests/pngsuite/primary/basn6a08.png | Bin 0 -> 184 bytes .../stb/tests/pngsuite/primary/bgai4a08.png | Bin 0 -> 214 bytes .../stb/tests/pngsuite/primary/bgan6a08.png | Bin 0 -> 184 bytes .../stb/tests/pngsuite/primary/bgbn4a08.png | Bin 0 -> 140 bytes .../stb/tests/pngsuite/primary/bgwn6a08.png | Bin 0 -> 202 bytes .../stb/tests/pngsuite/primary/s01i3p01.png | Bin 0 -> 113 bytes .../stb/tests/pngsuite/primary/s01n3p01.png | Bin 0 -> 113 bytes .../stb/tests/pngsuite/primary/s02i3p01.png | Bin 0 -> 114 bytes .../stb/tests/pngsuite/primary/s02n3p01.png | Bin 0 -> 115 bytes .../stb/tests/pngsuite/primary/s03i3p01.png | Bin 0 -> 118 bytes .../stb/tests/pngsuite/primary/s03n3p01.png | Bin 0 -> 120 bytes .../stb/tests/pngsuite/primary/s04i3p01.png | Bin 0 -> 126 bytes .../stb/tests/pngsuite/primary/s04n3p01.png | Bin 0 -> 121 bytes .../stb/tests/pngsuite/primary/s05i3p02.png | Bin 0 -> 134 bytes .../stb/tests/pngsuite/primary/s05n3p02.png | Bin 0 -> 129 bytes .../stb/tests/pngsuite/primary/s06i3p02.png | Bin 0 -> 143 bytes .../stb/tests/pngsuite/primary/s06n3p02.png | Bin 0 -> 131 bytes .../stb/tests/pngsuite/primary/s07i3p02.png | Bin 0 -> 149 bytes .../stb/tests/pngsuite/primary/s07n3p02.png | Bin 0 -> 138 bytes .../stb/tests/pngsuite/primary/s08i3p02.png | Bin 0 -> 149 bytes .../stb/tests/pngsuite/primary/s08n3p02.png | Bin 0 -> 139 bytes .../stb/tests/pngsuite/primary/s09i3p02.png | Bin 0 -> 147 bytes .../stb/tests/pngsuite/primary/s09n3p02.png | Bin 0 -> 143 bytes .../stb/tests/pngsuite/primary/s32i3p04.png | Bin 0 -> 355 bytes .../stb/tests/pngsuite/primary/s32n3p04.png | Bin 0 -> 263 bytes .../stb/tests/pngsuite/primary/s33i3p04.png | Bin 0 -> 385 bytes .../stb/tests/pngsuite/primary/s33n3p04.png | Bin 0 -> 329 bytes .../stb/tests/pngsuite/primary/s34i3p04.png | Bin 0 -> 349 bytes .../stb/tests/pngsuite/primary/s34n3p04.png | Bin 0 -> 248 bytes .../stb/tests/pngsuite/primary/s35i3p04.png | Bin 0 -> 399 bytes .../stb/tests/pngsuite/primary/s35n3p04.png | Bin 0 -> 338 bytes .../stb/tests/pngsuite/primary/s36i3p04.png | Bin 0 -> 356 bytes .../stb/tests/pngsuite/primary/s36n3p04.png | Bin 0 -> 258 bytes .../stb/tests/pngsuite/primary/s37i3p04.png | Bin 0 -> 393 bytes .../stb/tests/pngsuite/primary/s37n3p04.png | Bin 0 -> 336 bytes .../stb/tests/pngsuite/primary/s38i3p04.png | Bin 0 -> 357 bytes .../stb/tests/pngsuite/primary/s38n3p04.png | Bin 0 -> 245 bytes .../stb/tests/pngsuite/primary/s39i3p04.png | Bin 0 -> 420 bytes .../stb/tests/pngsuite/primary/s39n3p04.png | Bin 0 -> 352 bytes .../stb/tests/pngsuite/primary/s40i3p04.png | Bin 0 -> 357 bytes .../stb/tests/pngsuite/primary/s40n3p04.png | Bin 0 -> 256 bytes .../stb/tests/pngsuite/primary/tbbn0g04.png | Bin 0 -> 429 bytes .../stb/tests/pngsuite/primary/tbbn3p08.png | Bin 0 -> 1499 bytes .../stb/tests/pngsuite/primary/tbgn3p08.png | Bin 0 -> 1499 bytes .../stb/tests/pngsuite/primary/tbrn2c08.png | Bin 0 -> 1633 bytes .../stb/tests/pngsuite/primary/tbwn3p08.png | Bin 0 -> 1496 bytes .../stb/tests/pngsuite/primary/tbyn3p08.png | Bin 0 -> 1499 bytes .../stb/tests/pngsuite/primary/tm3n3p02.png | Bin 0 -> 116 bytes .../stb/tests/pngsuite/primary/tp0n0g08.png | Bin 0 -> 719 bytes .../stb/tests/pngsuite/primary/tp0n2c08.png | Bin 0 -> 1594 bytes .../stb/tests/pngsuite/primary/tp0n3p08.png | Bin 0 -> 1476 bytes .../stb/tests/pngsuite/primary/tp1n3p08.png | Bin 0 -> 1483 bytes .../stb/tests/pngsuite/primary/z00n2c08.png | Bin 0 -> 3172 bytes .../stb/tests/pngsuite/primary/z03n2c08.png | Bin 0 -> 232 bytes .../stb/tests/pngsuite/primary/z06n2c08.png | Bin 0 -> 224 bytes .../stb/tests/pngsuite/primary/z09n2c08.png | Bin 0 -> 224 bytes .../tests/pngsuite/primary_check/basi0g01.png | Bin 0 -> 391 bytes .../tests/pngsuite/primary_check/basi0g02.png | Bin 0 -> 283 bytes .../tests/pngsuite/primary_check/basi0g04.png | Bin 0 -> 252 bytes .../tests/pngsuite/primary_check/basi0g08.png | Bin 0 -> 293 bytes .../tests/pngsuite/primary_check/basi2c08.png | Bin 0 -> 274 bytes .../tests/pngsuite/primary_check/basi3p01.png | Bin 0 -> 273 bytes .../tests/pngsuite/primary_check/basi3p02.png | Bin 0 -> 286 bytes .../tests/pngsuite/primary_check/basi3p04.png | Bin 0 -> 331 bytes .../tests/pngsuite/primary_check/basi3p08.png | Bin 0 -> 387 bytes .../tests/pngsuite/primary_check/basi4a08.png | Bin 0 -> 263 bytes .../tests/pngsuite/primary_check/basi6a08.png | Bin 0 -> 298 bytes .../tests/pngsuite/primary_check/basn0g01.png | Bin 0 -> 391 bytes .../tests/pngsuite/primary_check/basn0g02.png | Bin 0 -> 283 bytes .../tests/pngsuite/primary_check/basn0g04.png | Bin 0 -> 252 bytes .../tests/pngsuite/primary_check/basn0g08.png | Bin 0 -> 293 bytes .../tests/pngsuite/primary_check/basn2c08.png | Bin 0 -> 274 bytes .../tests/pngsuite/primary_check/basn3p01.png | Bin 0 -> 273 bytes .../tests/pngsuite/primary_check/basn3p02.png | Bin 0 -> 286 bytes .../tests/pngsuite/primary_check/basn3p04.png | Bin 0 -> 331 bytes .../tests/pngsuite/primary_check/basn3p08.png | Bin 0 -> 387 bytes .../tests/pngsuite/primary_check/basn4a08.png | Bin 0 -> 263 bytes .../tests/pngsuite/primary_check/basn6a08.png | Bin 0 -> 298 bytes .../tests/pngsuite/primary_check/bgai4a08.png | Bin 0 -> 263 bytes .../tests/pngsuite/primary_check/bgan6a08.png | Bin 0 -> 298 bytes .../tests/pngsuite/primary_check/bgbn4a08.png | Bin 0 -> 263 bytes .../tests/pngsuite/primary_check/bgwn6a08.png | Bin 0 -> 298 bytes .../tests/pngsuite/primary_check/s01i3p01.png | Bin 0 -> 202 bytes .../tests/pngsuite/primary_check/s01n3p01.png | Bin 0 -> 202 bytes .../tests/pngsuite/primary_check/s02i3p01.png | Bin 0 -> 210 bytes .../tests/pngsuite/primary_check/s02n3p01.png | Bin 0 -> 210 bytes .../tests/pngsuite/primary_check/s03i3p01.png | Bin 0 -> 216 bytes .../tests/pngsuite/primary_check/s03n3p01.png | Bin 0 -> 216 bytes .../tests/pngsuite/primary_check/s04i3p01.png | Bin 0 -> 221 bytes .../tests/pngsuite/primary_check/s04n3p01.png | Bin 0 -> 221 bytes .../tests/pngsuite/primary_check/s05i3p02.png | Bin 0 -> 232 bytes .../tests/pngsuite/primary_check/s05n3p02.png | Bin 0 -> 232 bytes .../tests/pngsuite/primary_check/s06i3p02.png | Bin 0 -> 239 bytes .../tests/pngsuite/primary_check/s06n3p02.png | Bin 0 -> 239 bytes .../tests/pngsuite/primary_check/s07i3p02.png | Bin 0 -> 249 bytes .../tests/pngsuite/primary_check/s07n3p02.png | Bin 0 -> 249 bytes .../tests/pngsuite/primary_check/s08i3p02.png | Bin 0 -> 255 bytes .../tests/pngsuite/primary_check/s08n3p02.png | Bin 0 -> 255 bytes .../tests/pngsuite/primary_check/s09i3p02.png | Bin 0 -> 263 bytes .../tests/pngsuite/primary_check/s09n3p02.png | Bin 0 -> 263 bytes .../tests/pngsuite/primary_check/s32i3p04.png | Bin 0 -> 441 bytes .../tests/pngsuite/primary_check/s32n3p04.png | Bin 0 -> 441 bytes .../tests/pngsuite/primary_check/s33i3p04.png | Bin 0 -> 470 bytes .../tests/pngsuite/primary_check/s33n3p04.png | Bin 0 -> 470 bytes .../tests/pngsuite/primary_check/s34i3p04.png | Bin 0 -> 431 bytes .../tests/pngsuite/primary_check/s34n3p04.png | Bin 0 -> 431 bytes .../tests/pngsuite/primary_check/s35i3p04.png | Bin 0 -> 477 bytes .../tests/pngsuite/primary_check/s35n3p04.png | Bin 0 -> 477 bytes .../tests/pngsuite/primary_check/s36i3p04.png | Bin 0 -> 448 bytes .../tests/pngsuite/primary_check/s36n3p04.png | Bin 0 -> 448 bytes .../tests/pngsuite/primary_check/s37i3p04.png | Bin 0 -> 478 bytes .../tests/pngsuite/primary_check/s37n3p04.png | Bin 0 -> 478 bytes .../tests/pngsuite/primary_check/s38i3p04.png | Bin 0 -> 439 bytes .../tests/pngsuite/primary_check/s38n3p04.png | Bin 0 -> 439 bytes .../tests/pngsuite/primary_check/s39i3p04.png | Bin 0 -> 499 bytes .../tests/pngsuite/primary_check/s39n3p04.png | Bin 0 -> 499 bytes .../tests/pngsuite/primary_check/s40i3p04.png | Bin 0 -> 463 bytes .../tests/pngsuite/primary_check/s40n3p04.png | Bin 0 -> 463 bytes .../tests/pngsuite/primary_check/tbbn0g04.png | Bin 0 -> 762 bytes .../tests/pngsuite/primary_check/tbbn3p08.png | Bin 0 -> 1911 bytes .../tests/pngsuite/primary_check/tbgn3p08.png | Bin 0 -> 1911 bytes .../tests/pngsuite/primary_check/tbrn2c08.png | Bin 0 -> 1901 bytes .../tests/pngsuite/primary_check/tbwn3p08.png | Bin 0 -> 1911 bytes .../tests/pngsuite/primary_check/tbyn3p08.png | Bin 0 -> 1911 bytes .../tests/pngsuite/primary_check/tm3n3p02.png | Bin 0 -> 306 bytes .../tests/pngsuite/primary_check/tp0n0g08.png | Bin 0 -> 1802 bytes .../tests/pngsuite/primary_check/tp0n2c08.png | Bin 0 -> 1955 bytes .../tests/pngsuite/primary_check/tp0n3p08.png | Bin 0 -> 1959 bytes .../tests/pngsuite/primary_check/tp1n3p08.png | Bin 0 -> 1911 bytes .../tests/pngsuite/primary_check/z00n2c08.png | Bin 0 -> 422 bytes .../tests/pngsuite/primary_check/z03n2c08.png | Bin 0 -> 422 bytes .../tests/pngsuite/primary_check/z06n2c08.png | Bin 0 -> 422 bytes .../tests/pngsuite/primary_check/z09n2c08.png | Bin 0 -> 422 bytes .../stb/tests/pngsuite/ref_results.csv | 259 + .../stb/tests/pngsuite/unused/ccwn2c08.png | Bin 0 -> 1514 bytes .../stb/tests/pngsuite/unused/ccwn3p08.png | Bin 0 -> 1554 bytes .../stb/tests/pngsuite/unused/cdfn2c08.png | Bin 0 -> 404 bytes .../stb/tests/pngsuite/unused/cdhn2c08.png | Bin 0 -> 344 bytes .../stb/tests/pngsuite/unused/cdsn2c08.png | Bin 0 -> 232 bytes .../stb/tests/pngsuite/unused/cdun2c08.png | Bin 0 -> 724 bytes .../stb/tests/pngsuite/unused/ch1n3p04.png | Bin 0 -> 258 bytes .../stb/tests/pngsuite/unused/ch2n3p08.png | Bin 0 -> 1810 bytes .../stb/tests/pngsuite/unused/cm0n0g04.png | Bin 0 -> 292 bytes .../stb/tests/pngsuite/unused/cm7n0g04.png | Bin 0 -> 292 bytes .../stb/tests/pngsuite/unused/cm9n0g04.png | Bin 0 -> 292 bytes .../stb/tests/pngsuite/unused/cs3n2c16.png | Bin 0 -> 214 bytes .../stb/tests/pngsuite/unused/cs3n3p08.png | Bin 0 -> 259 bytes .../stb/tests/pngsuite/unused/cs5n2c08.png | Bin 0 -> 186 bytes .../stb/tests/pngsuite/unused/cs5n3p08.png | Bin 0 -> 271 bytes .../stb/tests/pngsuite/unused/cs8n2c08.png | Bin 0 -> 149 bytes .../stb/tests/pngsuite/unused/cs8n3p08.png | Bin 0 -> 256 bytes .../stb/tests/pngsuite/unused/ct0n0g04.png | Bin 0 -> 273 bytes .../stb/tests/pngsuite/unused/ct1n0g04.png | Bin 0 -> 792 bytes .../stb/tests/pngsuite/unused/cten0g04.png | Bin 0 -> 742 bytes .../stb/tests/pngsuite/unused/ctfn0g04.png | Bin 0 -> 716 bytes .../stb/tests/pngsuite/unused/ctgn0g04.png | Bin 0 -> 1182 bytes .../stb/tests/pngsuite/unused/cthn0g04.png | Bin 0 -> 1269 bytes .../stb/tests/pngsuite/unused/ctjn0g04.png | Bin 0 -> 941 bytes .../stb/tests/pngsuite/unused/ctzn0g04.png | Bin 0 -> 753 bytes .../stb/tests/pngsuite/unused/f00n0g08.png | Bin 0 -> 319 bytes .../stb/tests/pngsuite/unused/f00n2c08.png | Bin 0 -> 2475 bytes .../stb/tests/pngsuite/unused/f01n0g08.png | Bin 0 -> 321 bytes .../stb/tests/pngsuite/unused/f01n2c08.png | Bin 0 -> 1180 bytes .../stb/tests/pngsuite/unused/f02n0g08.png | Bin 0 -> 355 bytes .../stb/tests/pngsuite/unused/f02n2c08.png | Bin 0 -> 1729 bytes .../stb/tests/pngsuite/unused/f03n0g08.png | Bin 0 -> 389 bytes .../stb/tests/pngsuite/unused/f03n2c08.png | Bin 0 -> 1291 bytes .../stb/tests/pngsuite/unused/f04n0g08.png | Bin 0 -> 269 bytes .../stb/tests/pngsuite/unused/f04n2c08.png | Bin 0 -> 985 bytes .../stb/tests/pngsuite/unused/f99n0g04.png | Bin 0 -> 426 bytes .../stb/tests/pngsuite/unused/g03n0g16.png | Bin 0 -> 345 bytes .../stb/tests/pngsuite/unused/g03n2c08.png | Bin 0 -> 370 bytes .../stb/tests/pngsuite/unused/g03n3p04.png | Bin 0 -> 214 bytes .../stb/tests/pngsuite/unused/g04n0g16.png | Bin 0 -> 363 bytes .../stb/tests/pngsuite/unused/g04n2c08.png | Bin 0 -> 377 bytes .../stb/tests/pngsuite/unused/g04n3p04.png | Bin 0 -> 219 bytes .../stb/tests/pngsuite/unused/g05n0g16.png | Bin 0 -> 339 bytes .../stb/tests/pngsuite/unused/g05n2c08.png | Bin 0 -> 350 bytes .../stb/tests/pngsuite/unused/g05n3p04.png | Bin 0 -> 206 bytes .../stb/tests/pngsuite/unused/g07n0g16.png | Bin 0 -> 321 bytes .../stb/tests/pngsuite/unused/g07n2c08.png | Bin 0 -> 340 bytes .../stb/tests/pngsuite/unused/g07n3p04.png | Bin 0 -> 207 bytes .../stb/tests/pngsuite/unused/g10n0g16.png | Bin 0 -> 262 bytes .../stb/tests/pngsuite/unused/g10n2c08.png | Bin 0 -> 285 bytes .../stb/tests/pngsuite/unused/g10n3p04.png | Bin 0 -> 214 bytes .../stb/tests/pngsuite/unused/g25n0g16.png | Bin 0 -> 383 bytes .../stb/tests/pngsuite/unused/g25n2c08.png | Bin 0 -> 405 bytes .../stb/tests/pngsuite/unused/g25n3p04.png | Bin 0 -> 215 bytes .../stb/tests/pngsuite/unused/pp0n2c16.png | Bin 0 -> 962 bytes .../stb/tests/pngsuite/unused/pp0n6a08.png | Bin 0 -> 818 bytes .../stb/tests/pngsuite/unused/ps1n0g08.png | Bin 0 -> 1456 bytes .../stb/tests/pngsuite/unused/ps1n2c16.png | Bin 0 -> 1620 bytes .../stb/tests/pngsuite/unused/ps2n0g08.png | Bin 0 -> 2320 bytes .../stb/tests/pngsuite/unused/ps2n2c16.png | Bin 0 -> 2484 bytes .../thirdparty/stb/tests/prerelease/stb_lib.h | 3305 + .../thirdparty/stb/tests/resample_test.cpp | 1127 + .../thirdparty/stb/tests/resample_test_c.c | 8 + source/engine/thirdparty/stb/tests/resize.dsp | 94 + .../thirdparty/stb/tests/sdf/sdf_test.c | 152 + .../stb/tests/sdf/sdf_test_arial_16.png | Bin 0 -> 121269 bytes .../stb/tests/sdf/sdf_test_times_16.png | Bin 0 -> 108371 bytes .../stb/tests/sdf/sdf_test_times_50.png | Bin 0 -> 104962 bytes source/engine/thirdparty/stb/tests/stb.c | 3330 + source/engine/thirdparty/stb/tests/stb.dsp | 240 + source/engine/thirdparty/stb/tests/stb.dsw | 158 + .../stb/tests/stb_c_lexer_fuzzer.cpp | 74 + .../engine/thirdparty/stb/tests/stb_cpp.cpp | 85 + .../engine/thirdparty/stb/tests/stb_cpp.dsp | 98 + .../engine/thirdparty/stb/tests/stb_png.dict | 8 + .../engine/thirdparty/stb/tests/stb_static.c | 12 + .../thirdparty/stb/tests/stbi_read_fuzzer.c | 28 + source/engine/thirdparty/stb/tests/stblib.dsp | 102 + .../engine/thirdparty/stb/tests/stblib_test.c | 11 + .../stb/tests/stblib_test_companion.c | 4 + .../thirdparty/stb/tests/stretch_test.dsp | 89 + source/engine/thirdparty/stb/tests/test.sbm | 60 + .../thirdparty/stb/tests/test_c_compilation.c | 50 + .../thirdparty/stb/tests/test_c_lexer.c | 50 + .../stb/tests/test_cpp_compilation.cpp | 186 + source/engine/thirdparty/stb/tests/test_ds.c | 1034 + .../thirdparty/stb/tests/test_ds_cpp.cpp | 418 + source/engine/thirdparty/stb/tests/test_dxt.c | 1 + .../thirdparty/stb/tests/test_easyfont.c | 10 + .../engine/thirdparty/stb/tests/test_image.c | 7 + .../thirdparty/stb/tests/test_image_write.c | 7 + .../engine/thirdparty/stb/tests/test_perlin.c | 1 + .../thirdparty/stb/tests/test_png_paeth.c | 47 + .../thirdparty/stb/tests/test_png_regress.c | 75 + .../thirdparty/stb/tests/test_siphash.c | 21 + .../thirdparty/stb/tests/test_sprintf.c | 202 + .../thirdparty/stb/tests/test_truetype.c | 154 + .../engine/thirdparty/stb/tests/test_vorbis.c | 18 + .../engine/thirdparty/stb/tests/test_voxel.c | 1 + .../thirdparty/stb/tests/textedit_sample.c | 94 + .../tilemap_editor_integration_example.c | 193 + .../stb/tests/truetype_test_win32.c | 184 + .../thirdparty/stb/tests/vorbseek/vorbseek.c | 125 + .../stb/tests/vorbseek/vorbseek.dsp | 96 + .../thirdparty/stb/tools/README.footer.md | 135 + .../thirdparty/stb/tools/README.header.md | 22 + .../engine/thirdparty/stb/tools/README.list | 21 + .../thirdparty/stb/tools/build_matrix.c | 137 + .../thirdparty/stb/tools/easy_font_maker.c | 211 + .../engine/thirdparty/stb/tools/make_readme.c | 65 + .../thirdparty/stb/tools/make_readme.dsp | 97 + source/engine/thirdparty/stb/tools/mr.bat | 1 + .../stb/tools/trailing_whitespace.c | 32 + source/engine/thirdparty/stb/tools/unicode.c | 749 + .../thirdparty/stb/tools/unicode/unicode.dsp | 88 + 710 files changed, 451219 insertions(+), 29 deletions(-) delete mode 100644 .gitmodules delete mode 160000 quickjs delete mode 160000 source/engine/thirdparty/TinySoundFont create mode 100644 source/engine/thirdparty/TinySoundFont/LICENSE create mode 100644 source/engine/thirdparty/TinySoundFont/README.md create mode 100644 source/engine/thirdparty/TinySoundFont/examples/.gitignore create mode 100644 source/engine/thirdparty/TinySoundFont/examples/build-linux-clang.sh create mode 100644 source/engine/thirdparty/TinySoundFont/examples/build-linux-gcc.sh create mode 100644 source/engine/thirdparty/TinySoundFont/examples/build-osx.sh create mode 100644 source/engine/thirdparty/TinySoundFont/examples/example1.c create mode 100644 source/engine/thirdparty/TinySoundFont/examples/example1.dsp create mode 100644 source/engine/thirdparty/TinySoundFont/examples/example1.vcxproj create mode 100644 source/engine/thirdparty/TinySoundFont/examples/example2.c create mode 100644 source/engine/thirdparty/TinySoundFont/examples/example2.dsp create mode 100644 source/engine/thirdparty/TinySoundFont/examples/example2.vcxproj create mode 100644 source/engine/thirdparty/TinySoundFont/examples/example3.c create mode 100644 source/engine/thirdparty/TinySoundFont/examples/example3.dsp create mode 100644 source/engine/thirdparty/TinySoundFont/examples/example3.vcxproj create mode 100644 source/engine/thirdparty/TinySoundFont/examples/examples.sln create mode 100644 source/engine/thirdparty/TinySoundFont/examples/florestan-subset.sf2 create mode 100644 source/engine/thirdparty/TinySoundFont/examples/minisdl_audio.c create mode 100644 source/engine/thirdparty/TinySoundFont/examples/minisdl_audio.h create mode 100644 source/engine/thirdparty/TinySoundFont/examples/venture.mid create mode 100644 source/engine/thirdparty/TinySoundFont/sfotool/.gitignore create mode 100644 source/engine/thirdparty/TinySoundFont/sfotool/Makefile create mode 100644 source/engine/thirdparty/TinySoundFont/sfotool/README.md create mode 100644 source/engine/thirdparty/TinySoundFont/sfotool/main.c create mode 100644 source/engine/thirdparty/TinySoundFont/sfotool/sfotool.sln create mode 100644 source/engine/thirdparty/TinySoundFont/sfotool/sfotool.vcxproj create mode 100644 source/engine/thirdparty/TinySoundFont/tml.h create mode 100644 source/engine/thirdparty/TinySoundFont/tsf.h delete mode 160000 source/engine/thirdparty/cgltf create mode 100644 source/engine/thirdparty/cgltf/.github/workflows/build.yml create mode 100644 source/engine/thirdparty/cgltf/LICENSE create mode 100644 source/engine/thirdparty/cgltf/README.md create mode 100644 source/engine/thirdparty/cgltf/cgltf.h create mode 100644 source/engine/thirdparty/cgltf/cgltf_write.h create mode 100644 source/engine/thirdparty/cgltf/fuzz/data/BadBasisU.gltf create mode 100644 source/engine/thirdparty/cgltf/fuzz/data/Box.glb create mode 100644 source/engine/thirdparty/cgltf/fuzz/data/TriangleWithoutIndices.gltf create mode 100644 source/engine/thirdparty/cgltf/fuzz/data/WaterBottle.gltf create mode 100644 source/engine/thirdparty/cgltf/fuzz/gltf.dict create mode 100644 source/engine/thirdparty/cgltf/fuzz/main.c create mode 100644 source/engine/thirdparty/cgltf/test/CMakeLists.txt create mode 100644 source/engine/thirdparty/cgltf/test/main.c create mode 100755 source/engine/thirdparty/cgltf/test/test_all.py create mode 100644 source/engine/thirdparty/cgltf/test/test_conversion.cpp create mode 100644 source/engine/thirdparty/cgltf/test/test_math.cpp create mode 100644 source/engine/thirdparty/cgltf/test/test_strings.cpp create mode 100644 source/engine/thirdparty/cgltf/test/test_write.cpp delete mode 160000 source/engine/thirdparty/dr_libs create mode 100644 source/engine/thirdparty/dr_libs/.github/FUNDING.yml create mode 100644 source/engine/thirdparty/dr_libs/.gitignore create mode 100644 source/engine/thirdparty/dr_libs/.gitmodules create mode 100644 source/engine/thirdparty/dr_libs/LICENSE create mode 100644 source/engine/thirdparty/dr_libs/README.md create mode 100644 source/engine/thirdparty/dr_libs/dr_flac.h create mode 100644 source/engine/thirdparty/dr_libs/dr_mp3.h create mode 100644 source/engine/thirdparty/dr_libs/dr_wav.h create mode 100644 source/engine/thirdparty/dr_libs/old/dr.h create mode 100644 source/engine/thirdparty/dr_libs/old/dr_2d.h create mode 100644 source/engine/thirdparty/dr_libs/old/dr_audio.h create mode 100644 source/engine/thirdparty/dr_libs/old/dr_audio_ancient.h create mode 100644 source/engine/thirdparty/dr_libs/old/dr_fs.h create mode 100644 source/engine/thirdparty/dr_libs/old/dr_fsw.h create mode 100644 source/engine/thirdparty/dr_libs/old/dr_gui.h create mode 100644 source/engine/thirdparty/dr_libs/old/dr_math.h create mode 100644 source/engine/thirdparty/dr_libs/old/dr_mtl.h create mode 100644 source/engine/thirdparty/dr_libs/old/dr_pcx.h create mode 100644 source/engine/thirdparty/dr_libs/tests/README.md create mode 100644 source/engine/thirdparty/dr_libs/tests/bin/DO_NOT_DELETE.md create mode 100755 source/engine/thirdparty/dr_libs/tests/build_flac_linux.sh create mode 100644 source/engine/thirdparty/dr_libs/tests/build_flac_win32.bat create mode 100644 source/engine/thirdparty/dr_libs/tests/build_mp3_win32.bat create mode 100644 source/engine/thirdparty/dr_libs/tests/build_opus_win32.bat create mode 100644 source/engine/thirdparty/dr_libs/tests/build_wav_win32.bat create mode 100644 source/engine/thirdparty/dr_libs/tests/common/dr_common.c create mode 160000 source/engine/thirdparty/dr_libs/tests/external/miniaudio create mode 100644 source/engine/thirdparty/dr_libs/tests/flac/dr_flac_common.c create mode 100644 source/engine/thirdparty/dr_libs/tests/flac/dr_flac_decoding.c create mode 100644 source/engine/thirdparty/dr_libs/tests/flac/dr_flac_decoding.cpp create mode 100644 source/engine/thirdparty/dr_libs/tests/flac/dr_flac_seeking.c create mode 100644 source/engine/thirdparty/dr_libs/tests/flac/dr_flac_test_0.c create mode 100644 source/engine/thirdparty/dr_libs/tests/flac/dr_flac_test_0.cpp create mode 100644 source/engine/thirdparty/dr_libs/tests/flac/fuzz_dr_flac.c create mode 100644 source/engine/thirdparty/dr_libs/tests/mp3/dr_mp3_common.c create mode 100644 source/engine/thirdparty/dr_libs/tests/mp3/dr_mp3_playback.c create mode 100644 source/engine/thirdparty/dr_libs/tests/mp3/dr_mp3_test_0.c create mode 100644 source/engine/thirdparty/dr_libs/tests/opus/dr_opus_decoding.c create mode 100644 source/engine/thirdparty/dr_libs/tests/opus/dr_opus_test_0.c create mode 100644 source/engine/thirdparty/dr_libs/tests/opus/dr_opus_test_0.cpp create mode 100644 source/engine/thirdparty/dr_libs/tests/testvectors/DO_NOT_DELETE.md create mode 100644 source/engine/thirdparty/dr_libs/tests/testvectors/flac/DO_NOT_DELETE.md create mode 100644 source/engine/thirdparty/dr_libs/tests/testvectors/mp3/DO_NOT_DELETE.md create mode 100644 source/engine/thirdparty/dr_libs/tests/testvectors/opus/DO_NOT_DELETE.md create mode 100644 source/engine/thirdparty/dr_libs/tests/testvectors/wav/DO_NOT_DELETE.md create mode 100644 source/engine/thirdparty/dr_libs/tests/wav/dr_wav_common.c create mode 100644 source/engine/thirdparty/dr_libs/tests/wav/dr_wav_decoding.c create mode 100644 source/engine/thirdparty/dr_libs/tests/wav/dr_wav_decoding.cpp create mode 100644 source/engine/thirdparty/dr_libs/tests/wav/dr_wav_encoding.c create mode 100644 source/engine/thirdparty/dr_libs/tests/wav/dr_wav_playback.c create mode 100644 source/engine/thirdparty/dr_libs/tests/wav/dr_wav_playback.cpp create mode 100644 source/engine/thirdparty/dr_libs/tests/wav/dr_wav_test_0.c create mode 100644 source/engine/thirdparty/dr_libs/tests/wav/dr_wav_test_0.cpp create mode 100644 source/engine/thirdparty/dr_libs/wip/dr_opus.h create mode 100644 source/engine/thirdparty/dr_libs/wip/dr_opus_log.txt create mode 100644 source/engine/thirdparty/quickjs/.github/workflows/ci.yml create mode 100644 source/engine/thirdparty/quickjs/.gitignore create mode 100644 source/engine/thirdparty/quickjs/Changelog create mode 100644 source/engine/thirdparty/quickjs/LICENSE create mode 100644 source/engine/thirdparty/quickjs/Makefile create mode 100644 source/engine/thirdparty/quickjs/TODO create mode 100644 source/engine/thirdparty/quickjs/VERSION create mode 100644 source/engine/thirdparty/quickjs/cutils.c create mode 100644 source/engine/thirdparty/quickjs/cutils.h create mode 100644 source/engine/thirdparty/quickjs/doc/jsbignum.texi create mode 100644 source/engine/thirdparty/quickjs/doc/quickjs.texi create mode 100644 source/engine/thirdparty/quickjs/examples/fib.c create mode 100644 source/engine/thirdparty/quickjs/examples/fib_module.js create mode 100644 source/engine/thirdparty/quickjs/examples/hello.js create mode 100644 source/engine/thirdparty/quickjs/examples/hello_module.js create mode 100644 source/engine/thirdparty/quickjs/examples/pi_bigdecimal.js create mode 100644 source/engine/thirdparty/quickjs/examples/pi_bigfloat.js create mode 100644 source/engine/thirdparty/quickjs/examples/pi_bigint.js create mode 100644 source/engine/thirdparty/quickjs/examples/point.c create mode 100644 source/engine/thirdparty/quickjs/examples/test_fib.js create mode 100644 source/engine/thirdparty/quickjs/examples/test_point.js create mode 100644 source/engine/thirdparty/quickjs/libbf.c create mode 100644 source/engine/thirdparty/quickjs/libbf.h create mode 100644 source/engine/thirdparty/quickjs/libregexp-opcode.h create mode 100644 source/engine/thirdparty/quickjs/libregexp.c create mode 100644 source/engine/thirdparty/quickjs/libregexp.h create mode 100644 source/engine/thirdparty/quickjs/libunicode-table.h create mode 100644 source/engine/thirdparty/quickjs/libunicode.c create mode 100644 source/engine/thirdparty/quickjs/libunicode.h create mode 100644 source/engine/thirdparty/quickjs/list.h create mode 100644 source/engine/thirdparty/quickjs/qjs.c create mode 100644 source/engine/thirdparty/quickjs/qjsc.c create mode 100644 source/engine/thirdparty/quickjs/qjscalc.js create mode 100644 source/engine/thirdparty/quickjs/quickjs-atom.h create mode 100644 source/engine/thirdparty/quickjs/quickjs-libc.c create mode 100644 source/engine/thirdparty/quickjs/quickjs-libc.h create mode 100644 source/engine/thirdparty/quickjs/quickjs-opcode.h create mode 100644 source/engine/thirdparty/quickjs/quickjs.c create mode 100644 source/engine/thirdparty/quickjs/quickjs.h create mode 100644 source/engine/thirdparty/quickjs/readme.txt create mode 100755 source/engine/thirdparty/quickjs/release.sh create mode 100644 source/engine/thirdparty/quickjs/repl.js create mode 100644 source/engine/thirdparty/quickjs/run-test262.c create mode 100644 source/engine/thirdparty/quickjs/test262.conf create mode 100644 source/engine/thirdparty/quickjs/test262_errors.txt create mode 100644 source/engine/thirdparty/quickjs/test262o.conf create mode 100644 source/engine/thirdparty/quickjs/test262o_errors.txt create mode 100644 source/engine/thirdparty/quickjs/tests/bjson.c create mode 100644 source/engine/thirdparty/quickjs/tests/microbench.js create mode 100644 source/engine/thirdparty/quickjs/tests/test262.patch create mode 100644 source/engine/thirdparty/quickjs/tests/test_bigfloat.js create mode 100644 source/engine/thirdparty/quickjs/tests/test_bignum.js create mode 100644 source/engine/thirdparty/quickjs/tests/test_bjson.js create mode 100644 source/engine/thirdparty/quickjs/tests/test_builtin.js create mode 100644 source/engine/thirdparty/quickjs/tests/test_closure.js create mode 100644 source/engine/thirdparty/quickjs/tests/test_language.js create mode 100644 source/engine/thirdparty/quickjs/tests/test_loop.js create mode 100644 source/engine/thirdparty/quickjs/tests/test_op_overloading.js create mode 100644 source/engine/thirdparty/quickjs/tests/test_qjscalc.js create mode 100644 source/engine/thirdparty/quickjs/tests/test_std.js create mode 100644 source/engine/thirdparty/quickjs/tests/test_worker.js create mode 100644 source/engine/thirdparty/quickjs/tests/test_worker_module.js create mode 100755 source/engine/thirdparty/quickjs/unicode_download.sh create mode 100644 source/engine/thirdparty/quickjs/unicode_gen.c create mode 100644 source/engine/thirdparty/quickjs/unicode_gen_def.h delete mode 160000 source/engine/thirdparty/sokol create mode 100644 source/engine/thirdparty/sokol/.editorconfig create mode 100644 source/engine/thirdparty/sokol/.github/workflows/gen_bindings.yml create mode 100644 source/engine/thirdparty/sokol/.github/workflows/main.yml create mode 100644 source/engine/thirdparty/sokol/.gitignore create mode 100644 source/engine/thirdparty/sokol/CHANGELOG.md create mode 100644 source/engine/thirdparty/sokol/LICENSE create mode 100644 source/engine/thirdparty/sokol/README.md create mode 100644 source/engine/thirdparty/sokol/bindgen/.gitignore create mode 100644 source/engine/thirdparty/sokol/bindgen/README.md create mode 100644 source/engine/thirdparty/sokol/bindgen/gen_all.py create mode 100644 source/engine/thirdparty/sokol/bindgen/gen_ir.py create mode 100644 source/engine/thirdparty/sokol/bindgen/gen_nim.py create mode 100644 source/engine/thirdparty/sokol/bindgen/gen_odin.py create mode 100644 source/engine/thirdparty/sokol/bindgen/gen_rust.py create mode 100644 source/engine/thirdparty/sokol/bindgen/gen_util.py create mode 100644 source/engine/thirdparty/sokol/bindgen/gen_zig.py create mode 100644 source/engine/thirdparty/sokol/fips.yml create mode 100644 source/engine/thirdparty/sokol/sokol_app.h create mode 100644 source/engine/thirdparty/sokol/sokol_args.h create mode 100644 source/engine/thirdparty/sokol/sokol_audio.h create mode 100644 source/engine/thirdparty/sokol/sokol_fetch.h create mode 100644 source/engine/thirdparty/sokol/sokol_gfx.h create mode 100644 source/engine/thirdparty/sokol/sokol_glue.h create mode 100644 source/engine/thirdparty/sokol/sokol_log.h create mode 100644 source/engine/thirdparty/sokol/sokol_time.h create mode 100644 source/engine/thirdparty/sokol/tests/.gitignore create mode 100644 source/engine/thirdparty/sokol/tests/CMakeLists.txt create mode 100644 source/engine/thirdparty/sokol/tests/CMakePresets.json create mode 100755 source/engine/thirdparty/sokol/tests/analyze_ios.sh create mode 100755 source/engine/thirdparty/sokol/tests/analyze_linux.sh create mode 100755 source/engine/thirdparty/sokol/tests/analyze_macos.sh create mode 100644 source/engine/thirdparty/sokol/tests/analyze_win.cmd create mode 100644 source/engine/thirdparty/sokol/tests/compile/CMakeLists.txt create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_app.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_app.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_args.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_args.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_audio.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_audio.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_color.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_color.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_debugtext.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_debugtext.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_fetch.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_fetch.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_fontstash.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_fontstash.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_gfx.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_gfx.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_gfx_imgui.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_gfx_imgui.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_gl.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_gl.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_glue.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_glue.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_imgui.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_imgui.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_log.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_log.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_main.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_main.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_nuklear.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_shape.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_shape.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_spine.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_spine.cc create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_time.c create mode 100644 source/engine/thirdparty/sokol/tests/compile/sokol_time.cc create mode 100644 source/engine/thirdparty/sokol/tests/ext/CMakeLists.txt create mode 100644 source/engine/thirdparty/sokol/tests/ext/fontstash.h create mode 100644 source/engine/thirdparty/sokol/tests/ext/nuklear.c create mode 100644 source/engine/thirdparty/sokol/tests/ext/nuklear.h create mode 100644 source/engine/thirdparty/sokol/tests/ext/stb_truetype.h create mode 100644 source/engine/thirdparty/sokol/tests/functional/CMakeLists.txt create mode 100644 source/engine/thirdparty/sokol/tests/functional/README.txt create mode 100644 source/engine/thirdparty/sokol/tests/functional/assets/comsi.s3m create mode 100644 source/engine/thirdparty/sokol/tests/functional/assets/readme.txt create mode 100644 source/engine/thirdparty/sokol/tests/functional/force_dummy_backend.h create mode 100644 source/engine/thirdparty/sokol/tests/functional/sokol_args_test.c create mode 100644 source/engine/thirdparty/sokol/tests/functional/sokol_audio_test.c create mode 100644 source/engine/thirdparty/sokol/tests/functional/sokol_color_test.c create mode 100644 source/engine/thirdparty/sokol/tests/functional/sokol_debugtext_test.c create mode 100644 source/engine/thirdparty/sokol/tests/functional/sokol_fetch_test.c create mode 100644 source/engine/thirdparty/sokol/tests/functional/sokol_gfx_test.c create mode 100644 source/engine/thirdparty/sokol/tests/functional/sokol_gl_test.c create mode 100644 source/engine/thirdparty/sokol/tests/functional/sokol_log_test.c create mode 100644 source/engine/thirdparty/sokol/tests/functional/sokol_shape_test.c create mode 100644 source/engine/thirdparty/sokol/tests/functional/sokol_spine_test.c create mode 100644 source/engine/thirdparty/sokol/tests/functional/sokol_test.c create mode 100644 source/engine/thirdparty/sokol/tests/functional/utest.h create mode 100755 source/engine/thirdparty/sokol/tests/test_android.sh create mode 100644 source/engine/thirdparty/sokol/tests/test_common.sh create mode 100755 source/engine/thirdparty/sokol/tests/test_emscripten.sh create mode 100755 source/engine/thirdparty/sokol/tests/test_ios.sh create mode 100755 source/engine/thirdparty/sokol/tests/test_linux.sh create mode 100755 source/engine/thirdparty/sokol/tests/test_macos.sh create mode 100644 source/engine/thirdparty/sokol/tests/test_win.cmd create mode 100644 source/engine/thirdparty/sokol/util/gen_sokol_color.py create mode 100644 source/engine/thirdparty/sokol/util/sokol_color.h create mode 100644 source/engine/thirdparty/sokol/util/sokol_debugtext.h create mode 100644 source/engine/thirdparty/sokol/util/sokol_fontstash.h create mode 100644 source/engine/thirdparty/sokol/util/sokol_gfx_imgui.h create mode 100644 source/engine/thirdparty/sokol/util/sokol_gl.h create mode 100644 source/engine/thirdparty/sokol/util/sokol_imgui.h create mode 100644 source/engine/thirdparty/sokol/util/sokol_memtrack.h create mode 100644 source/engine/thirdparty/sokol/util/sokol_nuklear.h create mode 100644 source/engine/thirdparty/sokol/util/sokol_shape.h create mode 100644 source/engine/thirdparty/sokol/util/sokol_spine.h delete mode 160000 source/engine/thirdparty/stb create mode 100644 source/engine/thirdparty/stb/.github/CONTRIBUTING.md create mode 100644 source/engine/thirdparty/stb/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 source/engine/thirdparty/stb/.github/ISSUE_TEMPLATE/config.yml create mode 100644 source/engine/thirdparty/stb/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 source/engine/thirdparty/stb/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 source/engine/thirdparty/stb/.github/workflows/ci-fuzz.yml create mode 100644 source/engine/thirdparty/stb/.gitignore create mode 100644 source/engine/thirdparty/stb/.travis.yml create mode 100644 source/engine/thirdparty/stb/LICENSE create mode 100644 source/engine/thirdparty/stb/README.md create mode 100644 source/engine/thirdparty/stb/data/atari_8bit_font_revised.png create mode 100644 source/engine/thirdparty/stb/data/easy_font_raw.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/license.txt create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_caves_limit_connectivity.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_caves_tiny_corridors.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_corner_caves.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_horizontal_corridors_v1.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_horizontal_corridors_v2.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_horizontal_corridors_v3.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_limit_connectivity_fat.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_limited_connectivity.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_maze_2_wide.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_maze_plus_2_wide.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_open_areas.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_ref2_corner_caves.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_rooms_and_corridors.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_rooms_and_corridors_2_wide_diagonal_bias.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_rooms_limit_connectivity.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_round_rooms_diagonal_corridors.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_sean_dungeon.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_simple_caves_2_wide.png create mode 100644 source/engine/thirdparty/stb/data/herringbone/template_square_rooms_with_random_rects.png create mode 100644 source/engine/thirdparty/stb/data/map_01.png create mode 100644 source/engine/thirdparty/stb/data/map_02.png create mode 100644 source/engine/thirdparty/stb/data/map_03.png create mode 100644 source/engine/thirdparty/stb/deprecated/rrsprintf.h create mode 100644 source/engine/thirdparty/stb/deprecated/stb.h create mode 100644 source/engine/thirdparty/stb/deprecated/stb_image.c create mode 100644 source/engine/thirdparty/stb/deprecated/stb_image_resize.h create mode 100644 source/engine/thirdparty/stb/deprecated/stretch_test.c create mode 100644 source/engine/thirdparty/stb/deprecated/stretchy_buffer.h create mode 100644 source/engine/thirdparty/stb/deprecated/stretchy_buffer.txt create mode 100644 source/engine/thirdparty/stb/docs/other_libs.md create mode 100644 source/engine/thirdparty/stb/docs/stb_howto.txt create mode 100644 source/engine/thirdparty/stb/docs/stb_voxel_render_interview.md create mode 100644 source/engine/thirdparty/stb/docs/why_public_domain.md create mode 100644 source/engine/thirdparty/stb/stb_c_lexer.h create mode 100644 source/engine/thirdparty/stb/stb_connected_components.h create mode 100644 source/engine/thirdparty/stb/stb_divide.h create mode 100644 source/engine/thirdparty/stb/stb_ds.h create mode 100644 source/engine/thirdparty/stb/stb_dxt.h create mode 100644 source/engine/thirdparty/stb/stb_easy_font.h create mode 100644 source/engine/thirdparty/stb/stb_herringbone_wang_tile.h create mode 100644 source/engine/thirdparty/stb/stb_hexwave.h create mode 100644 source/engine/thirdparty/stb/stb_image.h create mode 100644 source/engine/thirdparty/stb/stb_image_resize2.h create mode 100644 source/engine/thirdparty/stb/stb_image_resize_test/dotimings.c create mode 100644 source/engine/thirdparty/stb/stb_image_resize_test/old_image_resize.h create mode 100644 source/engine/thirdparty/stb/stb_image_resize_test/oldir.c create mode 100644 source/engine/thirdparty/stb/stb_image_resize_test/stbirtest.c create mode 100644 source/engine/thirdparty/stb/stb_image_resize_test/vf_train.c create mode 100644 source/engine/thirdparty/stb/stb_image_write.h create mode 100644 source/engine/thirdparty/stb/stb_include.h create mode 100644 source/engine/thirdparty/stb/stb_leakcheck.h create mode 100644 source/engine/thirdparty/stb/stb_perlin.h create mode 100644 source/engine/thirdparty/stb/stb_rect_pack.h create mode 100644 source/engine/thirdparty/stb/stb_sprintf.h create mode 100644 source/engine/thirdparty/stb/stb_textedit.h create mode 100644 source/engine/thirdparty/stb/stb_tilemap_editor.h create mode 100644 source/engine/thirdparty/stb/stb_truetype.h create mode 100644 source/engine/thirdparty/stb/stb_vorbis.c create mode 100644 source/engine/thirdparty/stb/stb_voxel_render.h create mode 100644 source/engine/thirdparty/stb/tests/Makefile create mode 100644 source/engine/thirdparty/stb/tests/c_lexer_test.c create mode 100644 source/engine/thirdparty/stb/tests/c_lexer_test.dsp create mode 100644 source/engine/thirdparty/stb/tests/caveview/README.md create mode 100644 source/engine/thirdparty/stb/tests/caveview/cave_main.c create mode 100644 source/engine/thirdparty/stb/tests/caveview/cave_mesher.c create mode 100644 source/engine/thirdparty/stb/tests/caveview/cave_parse.c create mode 100644 source/engine/thirdparty/stb/tests/caveview/cave_parse.h create mode 100644 source/engine/thirdparty/stb/tests/caveview/cave_render.c create mode 100644 source/engine/thirdparty/stb/tests/caveview/caveview.dsp create mode 100644 source/engine/thirdparty/stb/tests/caveview/caveview.dsw create mode 100644 source/engine/thirdparty/stb/tests/caveview/caveview.h create mode 100644 source/engine/thirdparty/stb/tests/caveview/glext.h create mode 100644 source/engine/thirdparty/stb/tests/caveview/glext_list.h create mode 100644 source/engine/thirdparty/stb/tests/caveview/main.c create mode 100644 source/engine/thirdparty/stb/tests/caveview/stb_gl.h create mode 100644 source/engine/thirdparty/stb/tests/caveview/stb_glprog.h create mode 100644 source/engine/thirdparty/stb/tests/caveview/win32/SDL_windows_main.c create mode 100644 source/engine/thirdparty/stb/tests/fuzz_main.c create mode 100644 source/engine/thirdparty/stb/tests/grid_reachability.c create mode 100644 source/engine/thirdparty/stb/tests/herringbone.dsp create mode 100644 source/engine/thirdparty/stb/tests/herringbone_generator.c create mode 100644 source/engine/thirdparty/stb/tests/herringbone_map.c create mode 100644 source/engine/thirdparty/stb/tests/herringbone_map.dsp create mode 100644 source/engine/thirdparty/stb/tests/image_test.c create mode 100644 source/engine/thirdparty/stb/tests/image_test.dsp create mode 100644 source/engine/thirdparty/stb/tests/image_write_test.c create mode 100755 source/engine/thirdparty/stb/tests/ossfuzz.sh create mode 100644 source/engine/thirdparty/stb/tests/oversample/README.md create mode 100644 source/engine/thirdparty/stb/tests/oversample/main.c create mode 100644 source/engine/thirdparty/stb/tests/oversample/oversample.dsp create mode 100644 source/engine/thirdparty/stb/tests/oversample/oversample.dsw create mode 100644 source/engine/thirdparty/stb/tests/oversample/oversample.exe create mode 100644 source/engine/thirdparty/stb/tests/oversample/stb_wingraph.h create mode 100644 source/engine/thirdparty/stb/tests/pbm/basi0g16.pgm create mode 100644 source/engine/thirdparty/stb/tests/pbm/basi2c16.ppm create mode 100644 source/engine/thirdparty/stb/tests/pbm/cdfn2c08.ppm create mode 100644 source/engine/thirdparty/stb/tests/pbm/cdun2c08.ppm create mode 100644 source/engine/thirdparty/stb/tests/pbm/comment.pgm create mode 100644 source/engine/thirdparty/stb/tests/pbm/ctfn0g04.pgm create mode 100644 source/engine/thirdparty/stb/tests/pg_test/pg_test.c create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/basi0g16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/basi2c16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/basi4a16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/basi6a16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/basn0g16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/basn2c16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/basn4a16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/basn6a16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/bgai4a16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/bgan6a16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/bggn4a16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/bgyn6a16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/oi1n0g16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/oi1n2c16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/oi2n0g16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/oi2n2c16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/oi4n0g16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/oi4n2c16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/oi9n0g16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/oi9n2c16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/tbbn2c16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/tbgn2c16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/16bit/tbwn0g16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/PngSuite.LICENSE create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/corrupt/xc1n0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/corrupt/xc9n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/corrupt/xcrn0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/corrupt/xcsn0g01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/corrupt/xd0n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/corrupt/xd3n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/corrupt/xd9n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/corrupt/xdtn0g01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/corrupt/xhdn0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/corrupt/xlfn0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/corrupt/xs1n0g01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/corrupt/xs2n0g01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/corrupt/xs4n0g01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/corrupt/xs7n0g01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_basi0g01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_basi0g02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_basi3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_bgwn6a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_bgyn6a16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_tbyn3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_z06n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basi0g01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basi0g02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basi0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basi0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basi2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basi3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basi3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basi3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basi3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basi4a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basi6a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basn0g01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basn0g02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basn0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basn0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basn2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basn3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basn3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basn3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basn3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basn4a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/basn6a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/bgai4a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/bgan6a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/bgbn4a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/bgwn6a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s01i3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s01n3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s02i3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s02n3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s03i3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s03n3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s04i3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s04n3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s05i3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s05n3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s06i3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s06n3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s07i3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s07n3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s08i3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s08n3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s09i3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s09n3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s32i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s32n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s33i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s33n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s34i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s34n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s35i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s35n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s36i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s36n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s37i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s37n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s38i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s38n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s39i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s39n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s40i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/s40n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/tbbn0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/tbbn3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/tbgn3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/tbrn2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/tbwn3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/tbyn3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/tm3n3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/tp0n0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/tp0n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/tp0n3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/tp1n3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/z00n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/z03n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/z06n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary/z09n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi0g01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi0g02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi4a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi6a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn0g01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn0g02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn4a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn6a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/bgai4a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/bgan6a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/bgbn4a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/bgwn6a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s01i3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s01n3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s02i3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s02n3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s03i3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s03n3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s04i3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s04n3p01.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s05i3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s05n3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s06i3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s06n3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s07i3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s07n3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s08i3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s08n3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s09i3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s09n3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s32i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s32n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s33i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s33n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s34i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s34n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s35i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s35n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s36i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s36n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s37i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s37n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s38i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s38n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s39i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s39n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s40i3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/s40n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/tbbn0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/tbbn3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/tbgn3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/tbrn2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/tbwn3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/tbyn3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/tm3n3p02.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/tp0n0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/tp0n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/tp0n3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/tp1n3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/z00n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/z03n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/z06n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/primary_check/z09n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/ref_results.csv create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/ccwn2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/ccwn3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cdfn2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cdhn2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cdsn2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cdun2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/ch1n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/ch2n3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cm0n0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cm7n0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cm9n0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cs3n2c16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cs3n3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cs5n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cs5n3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cs8n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cs8n3p08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/ct0n0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/ct1n0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cten0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/ctfn0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/ctgn0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/cthn0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/ctjn0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/ctzn0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/f00n0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/f00n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/f01n0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/f01n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/f02n0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/f02n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/f03n0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/f03n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/f04n0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/f04n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/f99n0g04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g03n0g16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g03n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g03n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g04n0g16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g04n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g04n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g05n0g16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g05n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g05n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g07n0g16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g07n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g07n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g10n0g16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g10n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g10n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g25n0g16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g25n2c08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/g25n3p04.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/pp0n2c16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/pp0n6a08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/ps1n0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/ps1n2c16.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/ps2n0g08.png create mode 100644 source/engine/thirdparty/stb/tests/pngsuite/unused/ps2n2c16.png create mode 100644 source/engine/thirdparty/stb/tests/prerelease/stb_lib.h create mode 100644 source/engine/thirdparty/stb/tests/resample_test.cpp create mode 100644 source/engine/thirdparty/stb/tests/resample_test_c.c create mode 100644 source/engine/thirdparty/stb/tests/resize.dsp create mode 100644 source/engine/thirdparty/stb/tests/sdf/sdf_test.c create mode 100644 source/engine/thirdparty/stb/tests/sdf/sdf_test_arial_16.png create mode 100644 source/engine/thirdparty/stb/tests/sdf/sdf_test_times_16.png create mode 100644 source/engine/thirdparty/stb/tests/sdf/sdf_test_times_50.png create mode 100644 source/engine/thirdparty/stb/tests/stb.c create mode 100644 source/engine/thirdparty/stb/tests/stb.dsp create mode 100644 source/engine/thirdparty/stb/tests/stb.dsw create mode 100644 source/engine/thirdparty/stb/tests/stb_c_lexer_fuzzer.cpp create mode 100644 source/engine/thirdparty/stb/tests/stb_cpp.cpp create mode 100644 source/engine/thirdparty/stb/tests/stb_cpp.dsp create mode 100644 source/engine/thirdparty/stb/tests/stb_png.dict create mode 100644 source/engine/thirdparty/stb/tests/stb_static.c create mode 100644 source/engine/thirdparty/stb/tests/stbi_read_fuzzer.c create mode 100644 source/engine/thirdparty/stb/tests/stblib.dsp create mode 100644 source/engine/thirdparty/stb/tests/stblib_test.c create mode 100644 source/engine/thirdparty/stb/tests/stblib_test_companion.c create mode 100644 source/engine/thirdparty/stb/tests/stretch_test.dsp create mode 100644 source/engine/thirdparty/stb/tests/test.sbm create mode 100644 source/engine/thirdparty/stb/tests/test_c_compilation.c create mode 100644 source/engine/thirdparty/stb/tests/test_c_lexer.c create mode 100644 source/engine/thirdparty/stb/tests/test_cpp_compilation.cpp create mode 100644 source/engine/thirdparty/stb/tests/test_ds.c create mode 100644 source/engine/thirdparty/stb/tests/test_ds_cpp.cpp create mode 100644 source/engine/thirdparty/stb/tests/test_dxt.c create mode 100644 source/engine/thirdparty/stb/tests/test_easyfont.c create mode 100644 source/engine/thirdparty/stb/tests/test_image.c create mode 100644 source/engine/thirdparty/stb/tests/test_image_write.c create mode 100644 source/engine/thirdparty/stb/tests/test_perlin.c create mode 100644 source/engine/thirdparty/stb/tests/test_png_paeth.c create mode 100644 source/engine/thirdparty/stb/tests/test_png_regress.c create mode 100644 source/engine/thirdparty/stb/tests/test_siphash.c create mode 100644 source/engine/thirdparty/stb/tests/test_sprintf.c create mode 100644 source/engine/thirdparty/stb/tests/test_truetype.c create mode 100644 source/engine/thirdparty/stb/tests/test_vorbis.c create mode 100644 source/engine/thirdparty/stb/tests/test_voxel.c create mode 100644 source/engine/thirdparty/stb/tests/textedit_sample.c create mode 100644 source/engine/thirdparty/stb/tests/tilemap_editor_integration_example.c create mode 100644 source/engine/thirdparty/stb/tests/truetype_test_win32.c create mode 100644 source/engine/thirdparty/stb/tests/vorbseek/vorbseek.c create mode 100644 source/engine/thirdparty/stb/tests/vorbseek/vorbseek.dsp create mode 100644 source/engine/thirdparty/stb/tools/README.footer.md create mode 100644 source/engine/thirdparty/stb/tools/README.header.md create mode 100644 source/engine/thirdparty/stb/tools/README.list create mode 100644 source/engine/thirdparty/stb/tools/build_matrix.c create mode 100644 source/engine/thirdparty/stb/tools/easy_font_maker.c create mode 100644 source/engine/thirdparty/stb/tools/make_readme.c create mode 100644 source/engine/thirdparty/stb/tools/make_readme.dsp create mode 100644 source/engine/thirdparty/stb/tools/mr.bat create mode 100644 source/engine/thirdparty/stb/tools/trailing_whitespace.c create mode 100644 source/engine/thirdparty/stb/tools/unicode.c create mode 100644 source/engine/thirdparty/stb/tools/unicode/unicode.dsp diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 1cbc994..0000000 --- a/.gitmodules +++ /dev/null @@ -1,18 +0,0 @@ -[submodule "source/engine/thirdparty/sokol"] - path = source/engine/thirdparty/sokol - url = https://github.com/floooh/sokol.git -[submodule "source/engine/thirdparty/stb"] - path = source/engine/thirdparty/stb - url = https://github.com/nothings/stb.git -[submodule "source/engine/thirdparty/cgltf"] - path = source/engine/thirdparty/cgltf - url = https://github.com/jkuhlmann/cgltf.git -[submodule "quickjs"] - path = quickjs - url = https://github.com/bellard/quickjs.git -[submodule "source/engine/thirdparty/TinySoundFont"] - path = source/engine/thirdparty/TinySoundFont - url = https://github.com/schellingb/TinySoundFont.git -[submodule "source/engine/thirdparty/dr_libs"] - path = source/engine/thirdparty/dr_libs - url = https://github.com/mackron/dr_libs.git diff --git a/Makefile b/Makefile index 5226010..dcc3ea6 100755 --- a/Makefile +++ b/Makefile @@ -159,7 +159,7 @@ endif OBJDIR = $(BIN)/obj # All other sources -OBJS != find source/engine -type f -name '*.c' | grep -vE 'test|tool|example|fuzz|main' +OBJS != find source/engine -type f -name '*.c' | grep -vE 'test|tool|example|fuzz|main' | grep -vE 'quickjs' CPPOBJS != find source/engine -type f -name '*.cpp' | grep -vE 'test|tool|example|fuzz|main' OBJS += $(CPPOBJS) OBJS += $(shell find source/engine -type f -name '*.m') @@ -256,11 +256,12 @@ input.md: $(INPUTMD) @echo Printing api for $* @./primum -d $* > $@ +QUICKJS := source/engine/thirdparty/quickjs $(BIN)/libquickjs.a: - make -C quickjs clean - make -C quickjs SYSRT=$(SYSRT) TTARGET=$(TTARGET) ARCH=$(ARCH) DBG=$(DBG) OPT=$(OPT) AR=$(AR) OS=$(OS) libquickjs.a HOST_CC=$(CC) LEAK=$(LEAK) + make -C $(QUICKJS) clean + make -C $(QUICKJS) SYSRT=$(SYSRT) TTARGET=$(TTARGET) ARCH=$(ARCH) DBG=$(DBG) OPT=$(OPT) AR=$(AR) OS=$(OS) libquickjs.a HOST_CC=$(CC) LEAK=$(LEAK) @mkdir -p $(BIN) - cp -rf quickjs/libquickjs.* $(BIN) + cp -rf $(QUICKJS)/libquickjs.* $(BIN) $(OBJDIR)/%.o: %.c source/engine/core.cdb.h $(SHADERS) @mkdir -p $(@D) @@ -332,7 +333,7 @@ clean: rm -rf bin dist rm -f source/shaders/*.h core.cdb jso cdb packer TAGS source/engine/core.cdb.h tools/libcdb.a $(CDB)/libcdb.a rm -f $(CDB)/*.o - @make -C quickjs clean + @make -C $(QUICKJS) clean docs: doc/prosperon.org make -C doc diff --git a/quickjs b/quickjs deleted file mode 160000 index 6a89d7c..0000000 --- a/quickjs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6a89d7c27099be84e5312a7ec73205d6a7abe1b4 diff --git a/source/engine/thirdparty/TinySoundFont b/source/engine/thirdparty/TinySoundFont deleted file mode 160000 index 92a8f0e..0000000 --- a/source/engine/thirdparty/TinySoundFont +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 92a8f0e9fe3c98358be7d8564db21fc4b1142d04 diff --git a/source/engine/thirdparty/TinySoundFont/LICENSE b/source/engine/thirdparty/TinySoundFont/LICENSE new file mode 100644 index 0000000..533da73 --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2017-2023 Bernhard Schelling (Based on SFZero, Copyright (C) 2012 Steve Folta, https://github.com/stevefolta/SFZero) + +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/source/engine/thirdparty/TinySoundFont/README.md b/source/engine/thirdparty/TinySoundFont/README.md new file mode 100644 index 0000000..28d3995 --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/README.md @@ -0,0 +1,37 @@ +# TinySoundFont +SoundFont2 synthesizer library in a single C/C++ file + +## Overview + +TinySoundFont is a software synthesizer using SoundFont2 sound bank files. + +The library is a single C header file so it is extremely simple to integrate in your C/C++ projects. + +```c++ +#define TSF_IMPLEMENTATION +#include "tsf.h" + +... + +tsf* TinySoundFont = tsf_load_filename("soundfont.sf2"); +tsf_set_output(TinySoundFont, TSF_MONO, 44100, 0); //sample rate +tsf_note_on(TinySoundFont, 0, 60, 1.0f); //preset 0, middle C +short HalfSecond[22050]; //synthesize 0.5 seconds +tsf_render_short(TinySoundFont, HalfSecond, 22050, 0); +``` + +The library code is based on [SFZero by Steve Folta](https://github.com/stevefolta/SFZero). + +## Documentation + +The API documentation can be found on [top of the library source code](https://github.com/schellingb/TinySoundFont/blob/master/tsf.h). + +There are also [examples available](https://github.com/schellingb/TinySoundFont/tree/master/examples) which come with a sample SoundFont file and build and play sound on Win32, Win64, Linux and MacOSX with no further dependencies. + +## Dependencies + +C standard libraries for fopen, math and malloc (can be removed by providing custom functions with #defines). + +## License + +TinySoundFont is available under the [MIT license](https://choosealicense.com/licenses/mit/). diff --git a/source/engine/thirdparty/TinySoundFont/examples/.gitignore b/source/engine/thirdparty/TinySoundFont/examples/.gitignore new file mode 100644 index 0000000..e80d29b --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/.gitignore @@ -0,0 +1,15 @@ +*.ncb +*.opt +*.plg +*.aps +*.ipch +*.suo +*.user +*.sdf +*.opensdf +*.dsw +*-i686 +*-x86_64 +Debug/ +Release/ +.vs/ diff --git a/source/engine/thirdparty/TinySoundFont/examples/build-linux-clang.sh b/source/engine/thirdparty/TinySoundFont/examples/build-linux-clang.sh new file mode 100644 index 0000000..deafc0d --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/build-linux-clang.sh @@ -0,0 +1,7 @@ +echo Building \'example1-linux-`uname -m`\' ... +clang -std=c99 -Wall example1.c minisdl_audio.c -lm -ldl -lpthread -o example1-linux-`uname -m` +echo Building \'example2-linux-`uname -m`\' ... +clang -std=c99 -Wall example2.c minisdl_audio.c -lm -ldl -lpthread -o example2-linux-`uname -m` +echo Building \'example3-linux-`uname -m`\' ... +clang -std=c99 -Wall example3.c minisdl_audio.c -lm -ldl -lpthread -o example3-linux-`uname -m` +echo Done! diff --git a/source/engine/thirdparty/TinySoundFont/examples/build-linux-gcc.sh b/source/engine/thirdparty/TinySoundFont/examples/build-linux-gcc.sh new file mode 100644 index 0000000..35a12cc --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/build-linux-gcc.sh @@ -0,0 +1,7 @@ +echo Building \'example1-linux-`uname -m`\' ... +gcc -Wall example1.c minisdl_audio.c -lm -ldl -lpthread -o example1-linux-`uname -m` +echo Building \'example2-linux-`uname -m`\' ... +gcc -Wall example2.c minisdl_audio.c -lm -ldl -lpthread -o example2-linux-`uname -m` +echo Building \'example3-linux-`uname -m`\' ... +gcc -Wall example3.c minisdl_audio.c -lm -ldl -lpthread -o example3-linux-`uname -m` +echo Done! diff --git a/source/engine/thirdparty/TinySoundFont/examples/build-osx.sh b/source/engine/thirdparty/TinySoundFont/examples/build-osx.sh new file mode 100644 index 0000000..e41ef05 --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/build-osx.sh @@ -0,0 +1,7 @@ +echo Building \'example1-osx-`uname -m`\' ... +clang -std=c99 -Wall example1.c minisdl_audio.c -lm -ldl -lpthread -framework CoreServices -framework CoreAudio -framework AudioUnit -o example1-osx-`uname -m` +echo Building \'example2-osx-`uname -m`\' ... +clang -std=c99 -Wall example2.c minisdl_audio.c -lm -ldl -lpthread -framework CoreServices -framework CoreAudio -framework AudioUnit -o example2-osx-`uname -m` +echo Building \'example3-osx-`uname -m`\' ... +clang -std=c99 -Wall example3.c minisdl_audio.c -lm -ldl -lpthread -framework CoreServices -framework CoreAudio -framework AudioUnit -o example3-osx-`uname -m` +echo Done! diff --git a/source/engine/thirdparty/TinySoundFont/examples/example1.c b/source/engine/thirdparty/TinySoundFont/examples/example1.c new file mode 100644 index 0000000..4761ac4 --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/example1.c @@ -0,0 +1,86 @@ +#define TSF_IMPLEMENTATION +#include "../tsf.h" + +#include "minisdl_audio.h" + +//This is a minimal SoundFont with a single loopin saw-wave sample/instrument/preset (484 bytes) +const static unsigned char MinimalSoundFont[] = +{ + #define TEN0 0,0,0,0,0,0,0,0,0,0 + 'R','I','F','F',220,1,0,0,'s','f','b','k', + 'L','I','S','T',88,1,0,0,'p','d','t','a', + 'p','h','d','r',76,TEN0,TEN0,TEN0,TEN0,0,0,0,0,TEN0,0,0,0,0,0,0,0,255,0,255,0,1,TEN0,0,0,0, + 'p','b','a','g',8,0,0,0,0,0,0,0,1,0,0,0,'p','m','o','d',10,TEN0,0,0,0,'p','g','e','n',8,0,0,0,41,0,0,0,0,0,0,0, + 'i','n','s','t',44,TEN0,TEN0,0,0,0,0,0,0,0,0,TEN0,0,0,0,0,0,0,0,1,0, + 'i','b','a','g',8,0,0,0,0,0,0,0,2,0,0,0,'i','m','o','d',10,TEN0,0,0,0, + 'i','g','e','n',12,0,0,0,54,0,1,0,53,0,0,0,0,0,0,0, + 's','h','d','r',92,TEN0,TEN0,0,0,0,0,0,0,0,50,0,0,0,0,0,0,0,49,0,0,0,34,86,0,0,60,0,0,0,1,TEN0,TEN0,TEN0,TEN0,0,0,0,0,0,0,0, + 'L','I','S','T',112,0,0,0,'s','d','t','a','s','m','p','l',100,0,0,0,86,0,119,3,31,7,147,10,43,14,169,17,58,21,189,24,73,28,204,31,73,35,249,38,46,42,71,46,250,48,150,53,242,55,126,60,151,63,108,66,126,72,207, + 70,86,83,100,72,74,100,163,39,241,163,59,175,59,179,9,179,134,187,6,186,2,194,5,194,15,200,6,202,96,206,159,209,35,213,213,216,45,220,221,223,76,227,221,230,91,234,242,237,105,241,8,245,118,248,32,252 +}; + +// Holds the global instance pointer +static tsf* g_TinySoundFont; + +// Callback function called by the audio thread +static void AudioCallback(void* data, Uint8 *stream, int len) +{ + // Note we don't do any thread concurrency control here because in this + // example all notes are started before the audio playback begins. + // If you do play notes while the audio thread renders output you + // will need a mutex of some sort. + int SampleCount = (len / (2 * sizeof(short))); //2 output channels + tsf_render_short(g_TinySoundFont, (short*)stream, SampleCount, 0); +} + +int main(int argc, char *argv[]) +{ + // Define the desired audio output format we request + SDL_AudioSpec OutputAudioSpec; + OutputAudioSpec.freq = 44100; + OutputAudioSpec.format = AUDIO_S16; + OutputAudioSpec.channels = 2; + OutputAudioSpec.samples = 4096; + OutputAudioSpec.callback = AudioCallback; + + // Initialize the audio system + if (SDL_AudioInit(NULL) < 0) + { + fprintf(stderr, "Could not initialize audio hardware or driver\n"); + return 1; + } + + // Load the SoundFont from the memory block + g_TinySoundFont = tsf_load_memory(MinimalSoundFont, sizeof(MinimalSoundFont)); + if (!g_TinySoundFont) + { + fprintf(stderr, "Could not load soundfont\n"); + return 1; + } + + // Set the rendering output mode to 44.1khz and -10 decibel gain + tsf_set_output(g_TinySoundFont, TSF_STEREO_INTERLEAVED, OutputAudioSpec.freq, -10); + + // Start two notes before starting the audio playback + tsf_note_on(g_TinySoundFont, 0, 48, 1.0f); //C2 + tsf_note_on(g_TinySoundFont, 0, 52, 1.0f); //E2 + + // Request the desired audio output format + if (SDL_OpenAudio(&OutputAudioSpec, NULL) < 0) + { + fprintf(stderr, "Could not open the audio hardware or the desired audio output format\n"); + return 1; + } + + // Start the actual audio playback here + // The audio thread will begin to call our AudioCallback function + SDL_PauseAudio(0); + + // Let the audio callback play some sound for 3 seconds + SDL_Delay(3000); + + // We could call tsf_close(g_TinySoundFont) and SDL_DestroyMutex(g_Mutex) + // here to free the memory and resources but we just let the OS clean up + // because the process ends here. + return 0; +} diff --git a/source/engine/thirdparty/TinySoundFont/examples/example1.dsp b/source/engine/thirdparty/TinySoundFont/examples/example1.dsp new file mode 100644 index 0000000..594e654 --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/example1.dsp @@ -0,0 +1,77 @@ +# Microsoft Developer Studio Project File - Name="example1" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=example1 - Win32 Debug +!MESSAGE "example1 - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "example1 - Win32 Debug" (based on "Win32 (x86) Console Application") + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "example1 - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x807 /d "NDEBUG" +# ADD RSC /l 0x807 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "example1 - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x807 /d "_DEBUG" +# ADD RSC /l 0x807 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target +# Name "example1 - Win32 Release" +# Name "example1 - Win32 Debug" +# Begin Source File +SOURCE=example1.c +# End Source File +# Begin Source File +SOURCE=minisdl_audio.c +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/TinySoundFont/examples/example1.vcxproj b/source/engine/thirdparty/TinySoundFont/examples/example1.vcxproj new file mode 100644 index 0000000..10ae8eb --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/example1.vcxproj @@ -0,0 +1,107 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {A47C788B-1BDA-4057-87A9-FC35ED711B44} + Win32Proj + example1 + example1 + + + + Application + v110 + v120 + v140 + v141 + v142 + MultiByte + $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ + $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ + + + true + + + false + true + + + + + + + + + + true + + + false + + + + Level3 + true + false + false + false + + + Console + true + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDebug + + + true + + + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreaded + /Gw %(AdditionalOptions) + + + true + true + false + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/engine/thirdparty/TinySoundFont/examples/example2.c b/source/engine/thirdparty/TinySoundFont/examples/example2.c new file mode 100644 index 0000000..a9f1b10 --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/example2.c @@ -0,0 +1,81 @@ +#include "minisdl_audio.h" + +#define TSF_IMPLEMENTATION +#include "../tsf.h" + +// Holds the global instance pointer +static tsf* g_TinySoundFont; + +// A Mutex so we don't call note_on/note_off while rendering audio samples +static SDL_mutex* g_Mutex; + +static void AudioCallback(void* data, Uint8 *stream, int len) +{ + // Render the audio samples in float format + int SampleCount = (len / (2 * sizeof(float))); //2 output channels + SDL_LockMutex(g_Mutex); //get exclusive lock + tsf_render_float(g_TinySoundFont, (float*)stream, SampleCount, 0); + SDL_UnlockMutex(g_Mutex); +} + +int main(int argc, char *argv[]) +{ + int i, Notes[7] = { 48, 50, 52, 53, 55, 57, 59 }; + + // Define the desired audio output format we request + SDL_AudioSpec OutputAudioSpec; + OutputAudioSpec.freq = 44100; + OutputAudioSpec.format = AUDIO_F32; + OutputAudioSpec.channels = 2; + OutputAudioSpec.samples = 4096; + OutputAudioSpec.callback = AudioCallback; + + // Initialize the audio system + if (SDL_AudioInit(TSF_NULL) < 0) + { + fprintf(stderr, "Could not initialize audio hardware or driver\n"); + return 1; + } + + // Load the SoundFont from a file + g_TinySoundFont = tsf_load_filename("florestan-subset.sf2"); + if (!g_TinySoundFont) + { + fprintf(stderr, "Could not load SoundFont\n"); + return 1; + } + + // Set the SoundFont rendering output mode + tsf_set_output(g_TinySoundFont, TSF_STEREO_INTERLEAVED, OutputAudioSpec.freq, 0); + + // Create the mutex + g_Mutex = SDL_CreateMutex(); + + // Request the desired audio output format + if (SDL_OpenAudio(&OutputAudioSpec, TSF_NULL) < 0) + { + fprintf(stderr, "Could not open the audio hardware or the desired audio output format\n"); + return 1; + } + + // Start the actual audio playback here + // The audio thread will begin to call our AudioCallback function + SDL_PauseAudio(0); + + // Loop through all the presets in the loaded SoundFont + for (i = 0; i < tsf_get_presetcount(g_TinySoundFont); i++) + { + //Get exclusive mutex lock, end the previous note and play a new note + printf("Play note %d with preset #%d '%s'\n", Notes[i % 7], i, tsf_get_presetname(g_TinySoundFont, i)); + SDL_LockMutex(g_Mutex); + tsf_note_off(g_TinySoundFont, i - 1, Notes[(i - 1) % 7]); + tsf_note_on(g_TinySoundFont, i, Notes[i % 7], 1.0f); + SDL_UnlockMutex(g_Mutex); + SDL_Delay(1000); + } + + // We could call tsf_close(g_TinySoundFont) and SDL_DestroyMutex(g_Mutex) + // here to free the memory and resources but we just let the OS clean up + // because the process ends here. + return 0; +} diff --git a/source/engine/thirdparty/TinySoundFont/examples/example2.dsp b/source/engine/thirdparty/TinySoundFont/examples/example2.dsp new file mode 100644 index 0000000..2e89683 --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/example2.dsp @@ -0,0 +1,77 @@ +# Microsoft Developer Studio Project File - Name="example2" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=example2 - Win32 Debug +!MESSAGE "example2 - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "example2 - Win32 Debug" (based on "Win32 (x86) Console Application") + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "example2 - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x807 /d "NDEBUG" +# ADD RSC /l 0x807 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "example2 - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x807 /d "_DEBUG" +# ADD RSC /l 0x807 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target +# Name "example2 - Win32 Release" +# Name "example2 - Win32 Debug" +# Begin Source File +SOURCE=example2.c +# End Source File +# Begin Source File +SOURCE=minisdl_audio.c +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/TinySoundFont/examples/example2.vcxproj b/source/engine/thirdparty/TinySoundFont/examples/example2.vcxproj new file mode 100644 index 0000000..6d6476a --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/example2.vcxproj @@ -0,0 +1,107 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {78C3A807-00CB-410B-8F75-04910BB3975F} + Win32Proj + example2 + example2 + + + + Application + v110 + v120 + v140 + v141 + v142 + MultiByte + $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ + $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ + + + true + + + false + true + + + + + + + + + + true + + + false + + + + Level3 + true + false + false + false + + + Console + true + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDebug + + + true + + + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreaded + /Gw %(AdditionalOptions) + + + true + true + false + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/engine/thirdparty/TinySoundFont/examples/example3.c b/source/engine/thirdparty/TinySoundFont/examples/example3.c new file mode 100644 index 0000000..3045eec --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/example3.c @@ -0,0 +1,125 @@ +#include "minisdl_audio.h" + +#define TSF_IMPLEMENTATION +#include "../tsf.h" + +#define TML_IMPLEMENTATION +#include "../tml.h" + +// Holds the global instance pointer +static tsf* g_TinySoundFont; + +// Holds global MIDI playback state +static double g_Msec; //current playback time +static tml_message* g_MidiMessage; //next message to be played + +// Callback function called by the audio thread +static void AudioCallback(void* data, Uint8 *stream, int len) +{ + //Number of samples to process + int SampleBlock, SampleCount = (len / (2 * sizeof(float))); //2 output channels + for (SampleBlock = TSF_RENDER_EFFECTSAMPLEBLOCK; SampleCount; SampleCount -= SampleBlock, stream += (SampleBlock * (2 * sizeof(float)))) + { + //We progress the MIDI playback and then process TSF_RENDER_EFFECTSAMPLEBLOCK samples at once + if (SampleBlock > SampleCount) SampleBlock = SampleCount; + + //Loop through all MIDI messages which need to be played up until the current playback time + for (g_Msec += SampleBlock * (1000.0 / 44100.0); g_MidiMessage && g_Msec >= g_MidiMessage->time; g_MidiMessage = g_MidiMessage->next) + { + switch (g_MidiMessage->type) + { + case TML_PROGRAM_CHANGE: //channel program (preset) change (special handling for 10th MIDI channel with drums) + tsf_channel_set_presetnumber(g_TinySoundFont, g_MidiMessage->channel, g_MidiMessage->program, (g_MidiMessage->channel == 9)); + break; + case TML_NOTE_ON: //play a note + tsf_channel_note_on(g_TinySoundFont, g_MidiMessage->channel, g_MidiMessage->key, g_MidiMessage->velocity / 127.0f); + break; + case TML_NOTE_OFF: //stop a note + tsf_channel_note_off(g_TinySoundFont, g_MidiMessage->channel, g_MidiMessage->key); + break; + case TML_PITCH_BEND: //pitch wheel modification + tsf_channel_set_pitchwheel(g_TinySoundFont, g_MidiMessage->channel, g_MidiMessage->pitch_bend); + break; + case TML_CONTROL_CHANGE: //MIDI controller messages + tsf_channel_midi_control(g_TinySoundFont, g_MidiMessage->channel, g_MidiMessage->control, g_MidiMessage->control_value); + break; + } + } + + // Render the block of audio samples in float format + tsf_render_float(g_TinySoundFont, (float*)stream, SampleBlock, 0); + } +} + +int main(int argc, char *argv[]) +{ + // This implements a small program that you can launch without + // parameters for a default file & soundfont, or with these arguments: + // + // ./example3-... .mid .sf2 + + tml_message* TinyMidiLoader = NULL; + + // Define the desired audio output format we request + SDL_AudioSpec OutputAudioSpec; + OutputAudioSpec.freq = 44100; + OutputAudioSpec.format = AUDIO_F32; + OutputAudioSpec.channels = 2; + OutputAudioSpec.samples = 4096; + OutputAudioSpec.callback = AudioCallback; + + // Initialize the audio system + if (SDL_AudioInit(TSF_NULL) < 0) + { + fprintf(stderr, "Could not initialize audio hardware or driver\n"); + return 1; + } + + //Venture (Original WIP) by Ximon + //https://musescore.com/user/2391686/scores/841451 + //License: Creative Commons copyright waiver (CC0) + TinyMidiLoader = tml_load_filename((argc >= 2 ? argv[1] : "venture.mid")); + if (!TinyMidiLoader) + { + fprintf(stderr, "Could not load MIDI file\n"); + return 1; + } + + //Set up the global MidiMessage pointer to the first MIDI message + g_MidiMessage = TinyMidiLoader; + + // Load the SoundFont from a file + g_TinySoundFont = tsf_load_filename( + (argc >= 3 ? argv[2] : "florestan-subset.sf2") + ); + if (!g_TinySoundFont) + { + fprintf(stderr, "Could not load SoundFont\n"); + return 1; + } + + //Initialize preset on special 10th MIDI channel to use percussion sound bank (128) if available + tsf_channel_set_bank_preset(g_TinySoundFont, 9, 128, 0); + + // Set the SoundFont rendering output mode + tsf_set_output(g_TinySoundFont, TSF_STEREO_INTERLEAVED, OutputAudioSpec.freq, 0.0f); + + // Request the desired audio output format + if (SDL_OpenAudio(&OutputAudioSpec, TSF_NULL) < 0) + { + fprintf(stderr, "Could not open the audio hardware or the desired audio output format\n"); + return 1; + } + + // Start the actual audio playback here + // The audio thread will begin to call our AudioCallback function + SDL_PauseAudio(0); + + //Wait until the entire MIDI file has been played back (until the end of the linked message list is reached) + while (g_MidiMessage != NULL) SDL_Delay(100); + + // We could call tsf_close(g_TinySoundFont) and tml_free(TinyMidiLoader) + // here to free the memory and resources but we just let the OS clean up + // because the process ends here. + return 0; +} diff --git a/source/engine/thirdparty/TinySoundFont/examples/example3.dsp b/source/engine/thirdparty/TinySoundFont/examples/example3.dsp new file mode 100644 index 0000000..645395c --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/example3.dsp @@ -0,0 +1,77 @@ +# Microsoft Developer Studio Project File - Name="example3" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=example3 - Win32 Debug +!MESSAGE "example3 - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "example3 - Win32 Debug" (based on "Win32 (x86) Console Application") + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "example3 - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x807 /d "NDEBUG" +# ADD RSC /l 0x807 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "example3 - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x807 /d "_DEBUG" +# ADD RSC /l 0x807 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target +# Name "example3 - Win32 Release" +# Name "example3 - Win32 Debug" +# Begin Source File +SOURCE=example3.c +# End Source File +# Begin Source File +SOURCE=minisdl_audio.c +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/TinySoundFont/examples/example3.vcxproj b/source/engine/thirdparty/TinySoundFont/examples/example3.vcxproj new file mode 100644 index 0000000..4ba6003 --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/example3.vcxproj @@ -0,0 +1,108 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {78C3A807-00CB-410B-8F75-04910BB39760} + Win32Proj + example3 + example3 + + + + Application + v110 + v120 + v140 + v141 + v142 + MultiByte + $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ + $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ + + + true + + + false + true + + + + + + + + + + true + + + false + + + + Level3 + true + false + false + false + + + Console + true + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDebug + + + true + + + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreaded + /Gw %(AdditionalOptions) + + + true + true + false + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/engine/thirdparty/TinySoundFont/examples/examples.sln b/source/engine/thirdparty/TinySoundFont/examples/examples.sln new file mode 100644 index 0000000..565bea9 --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/examples.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example1", "example1.vcxproj", "{A47C788B-1BDA-4057-87A9-FC35ED711B44}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example2", "example2.vcxproj", "{78C3A807-00CB-410B-8F75-04910BB3975F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example3", "example3.vcxproj", "{78C3A807-00CB-410B-8F75-04910BB39760}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Debug|Win32.ActiveCfg = Debug|Win32 + {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Debug|Win32.Build.0 = Debug|Win32 + {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Debug|x64.ActiveCfg = Debug|x64 + {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Debug|x64.Build.0 = Debug|x64 + {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Release|Win32.ActiveCfg = Release|Win32 + {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Release|Win32.Build.0 = Release|Win32 + {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Release|x64.ActiveCfg = Release|x64 + {A47C788B-1BDA-4057-87A9-FC35ED711B44}.Release|x64.Build.0 = Release|x64 + {78C3A807-00CB-410B-8F75-04910BB3975F}.Debug|Win32.ActiveCfg = Debug|Win32 + {78C3A807-00CB-410B-8F75-04910BB3975F}.Debug|Win32.Build.0 = Debug|Win32 + {78C3A807-00CB-410B-8F75-04910BB3975F}.Debug|x64.ActiveCfg = Debug|x64 + {78C3A807-00CB-410B-8F75-04910BB3975F}.Debug|x64.Build.0 = Debug|x64 + {78C3A807-00CB-410B-8F75-04910BB3975F}.Release|Win32.ActiveCfg = Release|Win32 + {78C3A807-00CB-410B-8F75-04910BB3975F}.Release|Win32.Build.0 = Release|Win32 + {78C3A807-00CB-410B-8F75-04910BB3975F}.Release|x64.ActiveCfg = Release|x64 + {78C3A807-00CB-410B-8F75-04910BB3975F}.Release|x64.Build.0 = Release|x64 + {78C3A807-00CB-410B-8F75-04910BB39760}.Debug|Win32.ActiveCfg = Debug|Win32 + {78C3A807-00CB-410B-8F75-04910BB39760}.Debug|Win32.Build.0 = Debug|Win32 + {78C3A807-00CB-410B-8F75-04910BB39760}.Debug|x64.ActiveCfg = Debug|x64 + {78C3A807-00CB-410B-8F75-04910BB39760}.Debug|x64.Build.0 = Debug|x64 + {78C3A807-00CB-410B-8F75-04910BB39760}.Release|Win32.ActiveCfg = Release|Win32 + {78C3A807-00CB-410B-8F75-04910BB39760}.Release|Win32.Build.0 = Release|Win32 + {78C3A807-00CB-410B-8F75-04910BB39760}.Release|x64.ActiveCfg = Release|x64 + {78C3A807-00CB-410B-8F75-04910BB39760}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = TRUE + EndGlobalSection +EndGlobal diff --git a/source/engine/thirdparty/TinySoundFont/examples/florestan-subset.sf2 b/source/engine/thirdparty/TinySoundFont/examples/florestan-subset.sf2 new file mode 100644 index 0000000000000000000000000000000000000000..56a930aca967777ede5084d7fe117493d57c2237 GIT binary patch literal 531786 zcmbq)_ghm>uy=X_X@r&#AoSjQuTll+RmFm0S1gGAYwx|Fq9BNhfHbA|CLIJq?+`)? zCDf3RkmTh)&wbv1;OjTz+cXFO+NYIE&jj!GY8Kcj{W~^!$T*c4bQ~H0saSl zJr@qRa5(PZh0|wG6>rl3K&j}C$XT>Q^jdTQKmncwk3hK4yD)he5o!jx1^xwU0zL%t zfGO^uTPMJmP(o>x{4nW&QmFiQX+3Nc@+aan^cREz z%M{yz`igEuIU{-EpOLZR?a&Ni%EsMA1#0&gV_>Q~rsHD!LMOeqVf_6ZdxHZ&iNC{r zk#>{%g?%J;5V)6T$~nAoZT zlu-Vth{kuK8b#D~`traEe_3V`wvezKxp@ZRppF=lLK9==K{3{!yY z#L?vE)bzEY)uxG3GS{UmBpb!rc=zX0ht*r2mX8;B<#DnGGM%$e{5e-W-Xq00%43P$ zlLM&VS5H(V<7~ms8zIZDR^-+?me*KkSDZKvKwaq=9ZTCL_cou$o+g%as`m&^(tcPV zssndRQCVw5_o1eW!XfF)Ql64DgeTXF_OS0&J-=`~-zz^p*DLpZeq+&GEv6r`ND!r< z`sB%)IvPoG|A|d-#}{7B^Vat8q`6<#w>Inq28fdiBc=f^E?zwEMCS(0G0cAP0}@zV z2Zn=>*D*5(n%QX4WrEN>I3MX-aF1o@k+o)giRf2#u1?Y3B4NRn)aoil} zl6brV%>b&kA2Yy7otI#^Ee`;b#nJ$(T-&Yd5GGnt`GR?=(+k(A9nWl^${_{o;5fW1 zK@BrSsIeMzN7+8o#7jn@zsVWP4}zwrS6Y$flwVz`f~@?i{k7N142zF0Ee$ zCBoa~znRwSSE5x{DRd_N_NpK37d8!H29kv3AboLIQoVtNt-e#c^MZAY%zqmsQH_ij zz5(@I?zZzrV2QJ`o;}`0s+%+^?ld575`raHSp%v{D^QUarfh2k2e&J(NERV=g|^c~|Z8ItrB5-(O# zQ}zt{sJxf=#i(l@+Q!R@{c_%_lEf36I)mmF>p#3drat#esc$gveo`k_^di5d$glIx zTm&x~3{t#fzi4GDwoZjnAJ3O^tWe%Wkph6YB;zQ3T4q5yk@(02XaCVI%v>Z{ux!N6 z#MH_jl!#JzyKDXMy`5!d=4#=Jk}3(ZFFDA;-jdc-{{-1*-d~oQRfkA_n+rU1ZT|SQ zY*Kr<(GXLGMSDlPKZuu<2eWZ3_012+eF__DPD)RtgK*Q5e^3+&bK_!rJKGax$+C@W zf)zW|Rk>p_e-sz|BadNx`puIy`$!Ushj9)Y9z*+!ij$=hCZ2DmEOjJLW;SmAMrE7j zu^Kcc?{R`*9Qif-+YUPLhf^=;_f};2QpiiBy?US3b)-5l`4Y=uFSwe~1N-mRQzrX~ zzd7Gn-eM{WMMPJnnE=NVCwz@9(Ykw-EeTxYOE!2!zo723U&4=9CTZF|k7$|A#raxU z3E5FKa>K_qk`dwZ6?OxTnJ|T^^Gt_zxBx1?s3c+9tSyccB3WoFK0)-^fMSZF=M_I|LSp?IZ_X@lOQ&R#vU$d!_3WVfBT$trF7}3i5 zfaQ5zsLJg)8jgAvJvsx3s*TpQ!Wp09H=hdLxqlI6_mBfP zDHC~xfbYgXq;q&xw`sS~(((rkZrc5knP<`%k^C(v7_LA%H8}1x{0+Of}+I z=KCz)XfNw1$~!L4FEz-c%paT6+~%U*?T5J%tcLV&z{6J_t?ZkvE{*-{`7Srr;&(MU zi6b?8xRjSR`zw{lCHmx&_PQdRhTxCINK^2`z%WqGlr4?26 zKHI39yIGZ)){vHve*Mp(5od7|(vsGS$$jEYw&9%I)*f+Vu^5GLw{*{bB~{SP&5<$J z!My-A-47O38rO94H7o?$b1X?~>sFU}kI_JbgC#y%cI_rA2yI65T>q?Cb<&sBx250A z%d7gIt!phEXdrxR_zL)*mvgwUANE8kO;b|;2T^a!asj)=M#>>w6cpXkcXp}21~;&0 zhfMk+xZSGW<^+uw+AQs9$f3DXw39W$i@5WkukQZ50JcMzS%TOswUYLedc79=)$?7( z_np-lL+I7Mm8sVK-;+}-zQ$&r`S%HQM%hl&Thm{z5%hX#k#CB&MSoX-?MU#g*P%gK zD`S0wJ%p7hqKTFoDbgK_5ju><=G7mw&pSGPSnf(6u z+|SYX7t`$YAt0{3cIVXFGhtiyN=dr7o{U)=^U%K->c8YpF%59IAHDbkB;c3mvq4B~e2? z(D$7DyHWg)O-f7_rCqo%xgb5qq3|kHQl%1iB@s${W@*x39Vy96U}tFx<|x<(wI&;< zbL<1OwwSWmSe!t^N8+tTGu*z4Udtt%zu z=oIq4&d|RyMXKL!WS{ChuzZEZqWzocuP;hV`yl<fx9%XaHkK!pXd=pB zqLB{>lLjXp=}r>1wi-KGz{-zJ!U5&kO~7-7NS#BPe>7K=PhiW5l;Fds8Ush|TYSj} z?mAjZ2yE7$GsemXHs*CE*R z;KA3fyChZ@zxKVa)~N_BkIXMAx;=n9B2VYJMzF>!y|cUKC*WfhQttz&5tXphk56uZ-r~N`hM*XW{gbq;A$D`}S zz5Rb(uRBifJ-+jm~%k6tGbgA)EQp)@29Jki8 z`6i?guDos~+^r;Tbd|V32(dZuWo|ug=w%=)())X}y0G25pFi|-!&OC0Q(M*A1Zxwo zW9gI`J$%^4_pV)g(BiIWLcxmPL_%?W#!$hT>b)(O$9*@S@Nd%%dglj1+mC&nevios zX>O+NgH(d)>vCIh3Q}fm%DadPwhB9UT3hSK>#+d;T8K5g7PnqnZ#0V|57ksw{b^$8 z#5Iw2r0?~JSq!*sue7Vhw@JQy)pp9UWbcpq{GwV$=jb$m7tMdcyg+`}_o++oC;tOE zyS(uhrECi?OlGh0#O3Z;1Z$NlB-;A>{IMqLPHArer#lyGuQj22CcCm}XJk%loL8MN zncZP(m*JQmcKYzXP>fTuC&PP}($)2w(@mxAX$CpGI`!U9G&XOH7dW3r?(K;qlXGyN zva@skew#37*K!uvZM-}gOUq${=gQ-@5Z_3f8`}3ZdV#vVB@Nbpv;N)aNS_=*H>=62 zb($!6?sdE6vL`?@8WK6`D!JpByQk{>h83l%-0JIr%%k-hLksg(TT;B_h1OoXEl-Iv+jbSgDHCgBCN!mC1EZc@@Ig%=bhT~ctrFCkpYR%n! z%qjU_F4IeWDpY=U=LTIk3A-DDg1OXjmOk&l?K`}F0wd-kqDH=6rW~f^!paPEO zV(PF?JM-W9M)%ybTxwlk@2wHq5O*1t!eGVVVt){LFbk<8Z zIMp|F`Y-6?IyIurBYf-+R_tZFob;nc*@qu?s?g=Ce1Qj3o$I21*d=FHUY?Bus$U7G4qessge=>fjutFFMuQHIqd?cW|!|f@LsES@j8>zPuD%^X=5sl zr)o=U{M%QlJH9CmDaw(QB+qv*Pzeob)zX+(q76EmWdxMiPcx8cfS zZJb;4KI5l42V3s7RaRXrMAdb55g1dAZpXh~SG>AB9@=&4QX~&9S1@!b z)h({oid83DqbI5W&8h}g!kuYHmrvz;c)GernC?$=&rzPi%V9?u$LqCzeNJ*MDW+jj zK=ftqo4Lq|ysnt0+O{K&mSxDggsztMpoZr0G!Y#uRC#FM>F49ea=&RS)H{wlyx_o8 zp-XfwR0fpSGz*4A95tms*6Kd4$1^WLe9Il;g6|&u;>}R>!PQE*ER5Hab41DDA|qNk zmMT%cna=E*Fd!HHebQ>xVO(>pF5=(s=FQ62gXw~wIFxdg&BtI$$QySh>r}lI>|VxO z#^~Hz^5v=zC2txv`}Qx55HH#N+QmB2c^MY6WKZ@P-3#}gC8ohoB1aZZG@dSEWuPmP z>0=W5QXlvUjM(wdeM-%_?RN)A?HUdHTbMQRC5=6Ixn!v;>KME1FxKuH?(W9l)jY(P zsrIz2`76TVq>t;!IqVIm?GyKWts9`A z2K`21bojluIP$XXM*s(#8keG5j6Gl6ubv^9Jd(R5{1MX)X)Craxb2fEW&^g(B zrE#dMW+4#sUTO}2Vd@QA4gBb8C9li`Pgb>^tMw=t&J$~a&0mr9G)#6vhlw3zdgHV* zWu`>@DM?Bf^ElO})3*42`K{K7Nmub2d;du6xxQ=ar(67otkZ11SXL2Y*H}#XsYJ4J z_o*KA2yaOa|Q&jFC(qB1@L0mt1Q08()VcTVL9(dVzQK!MVk7MQcAJd82)DdaIUG62q?t z<_1rUewg{URw_1#{R_Rb(A)o`@52yt%!1j(?C964b^g_rq2uR2&xK4qZ3boJrF&Fv_C5f9vVi*R z-xU%L579RY5`)dxjDMoyW^I^TV|(hHiu)S|hk;y2je{Z9mnfG%#2q-+>EfpHA8Cbf zexr+gqlGY5M^j^-TU8b5!WZGM;JenO2i1B@CvVUJ%nD`%b-qI?llM(f>e6E+^fW2) zjPOkkw+?$}{sdh&cYS;ZO`n#}SRM;+K$R*rbPe>b=8*CNG%nd*c@@`rJi%REV@#=5 zZj-CqAJS?&_H_0Z?bC9CXc2xE{vJ+b`w!3ehE1NNRnh1SeFmdPE$h-(X3^L`IsPO4 zFt=|$lyI$xMw=1bx4F=9C_|aPLT??r*{EE8p+2v-eDNEh+4pU{)0L38!4p5dPwGgk zfQVsy+F)qgmiIPRoe5k>?!^WchRdjSn&>=nPu;m}E(2^XC{w-E@QV>x{R^;M{ia{W7d4}dp+9<7iPh)CLc9Z>>447qiD0VUa&-wjl z_>tlEJ{r~dO-L_Yl{_+3H7YsgK1T*bh~dEFkOqMglQ&X5QaRB+fu*Z%?54dd&`HY3 zI?<>y_Y2E3I_Yf^S+NJO>$+C+%F^_SrD4YG^xe_9-q;pF^^=A#Lp4HIE7YmS7o*Qh z#V$sfyPVRjmUV`IT}bas88jazkNnxTsdNMZqzMTUwJeN}9UWdB15Fsw?{T0^hvKm$ z+sxvI2ef#xL_HPnxX8dg$NanXknF`7i{Vr?){i`q69_i2EC7T{Rws^tk z{7CGFgDm$%tyY-_VqaFy4EhaB4Qvg^F~I;lLK*l4OaokA&Y6G?DUV(mKT1EsCC($u za9_X~pXy2}W?)CHcCX`+wY%>IS{r}n+SBTmC+RJdzecr20{^|QdQcTc9@{7|5FK(q zfAxINS(ljlouk@787A`ghSSJE-?2WwVFxBk#D*CF6u^5x{I#U1gn>iDHsfXVf84JN zvX!eTy!6{O)CqzBs&>vRB62oTKlq9{WowuLS&^sHC)P&A#$|h!DksYSbe&vF)7IFp zdN%cZ&bi2Acz+w+0BIa1l`k_M*(*+l4_h)hg0IkMQ8Oq99LIS=@$au4bQo7>)N{KQ z6Kj&w_N4Eq;*IEV21rAmz{r}2DMcr{NJIj}r}#ZJU1LKw%=oQkaEmYrM1g$f-J%BcMGm|e|HAO) zo??YJh^H&0%T$;SCam-lmp!5)PKUdNrr3Xn4YA%W%g*XdxQv@o7W+dh{}q2~KeXsg zY6>Qv+dz~+Yy*HSV3X=pF2kIysA zIPX~4=Hkq}AHJm$z1j;Kvj5!AhmVH>!V;bNNXY8!Qt6E91bpHkwP#SP(!U7T_HIr@ z__gcsF@?B;ahb7acc&S7N}rT=5?h&tcB^)z4BugPa-o1t0R+4PQv8`Ri(~&#O*o_InoZvpV5Pk$?J-3^T>3uP= ziz!?qu^xBT{-OOS{^Qa9oDzvexIPLg2)Yx-@O*+d*i>QpPuoqpPJN(Hj!-JQ3gVmf zXUXVu&WEEfTr`SLKY#i_nR$(@CE=Sy$vmd(TY>G(y*EnG=^yjFnnNa9ksD6X;5UKJ;gvqW6deRl7LQN)P3@kJWjr1wRB07- zHHxMq5p6cH`wQYzFYStZc4*$}JAp2@Ps)Qu`G;>w94TOUt|)AF3XVe*5Qm|Qi?Y4m zV$@TcrF~`Dq#RmDw&*%k9w^#t#;4!&7((8jJV}t6gIB z29p0yt}}`jzsy^XIn}%U%*-690S{6k7i^fjZu=RAp9(y#V*vd=e{zCH5zL%sQpSdB z2t~brH^$-xLwd{Mi}9wHBjY|C;WrW(iPDU|e=KJREO^q~1W>C^&w=Mkt&BkwsX#bC;SNI>F#48qj0(}FAfukTISgcqI zv~1%#Gj+)nAS-qdPJt^U-{DXaW?MhTe+^BqI--7HZipM9&1L))9MM5DZS~`WQ{Z3t zNGt>+D|0|iR|Ua4Q@f-5#G)7BDE=PuCgue(Qqx-M`PhF27kl~0A<`-IAp99tQpHsD z+!nJoxU!yoMQK4+4{{e~B~!1FF7s+)xL{%ExOA3^2jYcjw^#{DMjZfXuAi~XkKSg*&*!E@RU^I{sx?teWB+` zm>DY1eowVfPgHW_j&Hn1AJO`#9nHD?C%s-p)>HQz*kG{@qNsS%C|Ihj^GVh#PMATV z^y!6z+|yE)x=*y?m#4GOxARrnHKO>3=5`~jNt(tck~iBUv%dm-4Mgat3}_EnI1Ve#j@)cLrmjA~N<)&>6`#0Oau~)KCXH>^=8T5N)!cS+n zboVN8b{6(t{;KIoNv94{dB3EE${7%OMr93)yP%Vz7q+5TJVKk-gkkosc1)E(P7_ni z%%r=zvg&kXo)KpOy31x%OJvd&j&Rh(?ug|5i6t`3M zC2(O%V&R3ve(f}41CDi_0b^M~Ol)Y~Z3GRjmP@tZ%B^hs3E1L3l7k?~^dPewN7f25 zi|2iBqEcVU4#HbD;6pfYuiSr@s|t1F9j#K3#~2(WW;&YAlvL8{wD>KE?eLzql~{z8 zaft&7AOw+RNmUxASa-bKQbA5Z1k-A>^-||G3D&28m&i}Y)5IiTdwFmB=s-`Rsg;)6 zZN}`shIIz~BQ$zOigHalN+Zg)7RnylH@pV2fM#qZ_P!D|5RX`1*9v9z_2?~>LqM>~ z*-sM%(t7HC_J!hqCo}r5@QolDLX+P4EnoR4i$uM%>pA_@84aK!Oo`z$`bFA6t<&Ka z_6Ti`Jg_DM_5|GO9o8d%7X3~a>xSqUuGVnIg zx^HP?OaW|GYC0e)o;cT+#hrsLEF=$@NrkKGI4u#X*NEM5Ghu*ZpoadNYblCPP3kRt zpg8LL&dX~Lpg~K@{i(PxRi%tEzU6PO3|Q(4Khm-L0Kq?(powQBsAWpY3yr{3ZYTZn_ub=-P5G9?e(9Ib>?8 z!`*tpZ7`lQ`0^N=xu5jaV8AXwww=9Lb!PfBn7@9c&rkfbT814@_Xs+tf3?n(0|W<8 zs?qt1wfbZBt@5S(^zzFiM?vlz`}#Ss8|qkFoY8S9`C*Il_iKkh1Jlz~c|}M4D2I1cM%E=cd++@3V6sbJyh z`90IK(!Kg8ZC$L4<$6lb6xMSUIM=6V*_)cpCeaQMlS4}nzJ!j*3id8qP?)&$27}gC z_FIZM<@<8_*f|?~s@PhR<{#rh$8gKKwdBvaz4?5NrR14ZjIW`(6~%c()wn7$-FVe` zV>e@bnWiygH0?yU)8i&3f;vgO#O0(}8zkIlpM{V6VJ-byw^T#cTQ+?jyOg3HW3HA*sb_wLnu)bRCFD6@Z^xWrprQZ+QX=6u5=5m5WFuo|`T zZFOVL3*utDXj0=FVZ6I}DZ!A+T(ez0yqKeGY=m+?;ByC3nwVJfea&@EhcyB?WPH)M z#{adsY`f$et%=1|1pCt>o^;IUl#^M&ZN!hy=kw1mm#*E~_Ux$|YZ~E$1lEtb4!ooe zcC6gt*swN?YhW++Zf;n#;?fBhRU=Q>*~~@0Cp{C*yv*z`fm4G3h#A zoT$&%I{C32qCUvreCQ?j{4wZ@uzK!7>z3VmoJ5dup>4qa(|GY;`x3h*V%ElmsT&*ttdV#eDS82%v|+_P&cS**#>hVTmE?r?z`&j(%Bl3r$8FM zV))P+7o(&SSN7pmXvf9XTo{rYr0ijc3CRx9UMT%2mwuEI2zUYb3HfGl%{n_qMcuag z`AdA`;>vAA-e!&h&Co0u9!gsxCDT$8CmH}1;BnBLUYYe+jI)+Y!}*ttD#8j(yoEnV zcxDtFgbUxb*8KJOrxPP^;6K<4V1|yr&7l}eU1-atS0{?cmRylng(R6}qf>#$A~+kw zpEU`G`c;5$#1;W4EtKtr!$bpqNAw%Zym*!}Di|1nlQ+H>us8BN?`SsqjbXP8PzyB& z=u(rg10KpTitkZN*!p>6p%HZ(0>vyEQT^XViUg=X+Aq!9*xUZW>%cf=Gy9N(a;DY; z7eDHJb)7RoSHeZ&myBNcpNu>Oj4CmFuKo8Tz)_+Sl&z5FaAm)~`L&Udq|{Fl^haoM z9faBN&j?N}Xrtsj6*v?7qwuB8R07%rHxG%|(+O}5tw)A8uRoK`w-f3} zqs4aOlw8h4Zdi5DZ)Al(T^K!!o|8gB_UQ}+;DV*FsU7cMJkG4&VoI+fGte_G&%<4; zfeSbDV2{iO4bTT<2f$^TKLVozv?b$vq+gT1Rq;;C6k_g+Yq*YtN?G4oo-O=xzoDl9 zb(>%V;i?@AdcKP!^K@X}JD22B+(MaD+-Eq%)hi^%s&*Yz?tIU-LuETB#TeS7Y#w}h zXFA?^l=!jkLm+!x=Dzd==x!Hyu!<#k)3s*!u5Ob)vR?iftVwYp__*h+9Dh>WkFY7uAbnGDC9tAc13G z;8oKoAiDMQ{eiLs#D?Mr@oD^c$ORXIqAAns$FrwC%xoDW1$U6X!|wo+@iFM<&c=tE z1x>K$Bn31{rah#>5kg8{6l9t{#!+|6Sd(sn#%-+vZWzdmIgneQKysrXSf$t4B}tDE z8~ckSz18X5e-Cpf=A`?Sg25-PYy9(c^H2!`jxQQAz5@3vxl1B(VZmVAAkwvsX9b}T z2Zx-c?No*!ITjcE2ecZne@5Ki)P2(yK2XY*W?)5u#?~E*v%J94nukf`C@F8%1{l(8 z-VdPhLJ~Y#|Ndc0Ebpe0Hr@uS5RhQmrXUbDR1zQG?(mcpsOpRP8oT)IRBe#vPy#=1 zex6~+DEZ3mz&7u4wm2!z1_jsoKUHl?k+`PTgd8)l-c_&kP8LgdN%c=OT+vXnmT$pI z`C!c{a%9-KM$!wj+73**S{3GuUZ>Af#RWWX&i}`q4@L6@B)S3$*X}84MkEA@Gh0_) zdzJ4*6{vl{9ne|xelOohkYDE+hcZ0ar13irZQT^*2G+|3!RUrP$Yi;{vN&-uDnO3 zSaZO9Nk&`-4|QnG{RXVhl^Rw)q;6)2(CI)K6Sio7izmL-EQTv-s}Z$zjT2>VZ^zyn zIv0N~R%S~4Ql8R!s28cJjnXAd&uLY%zctViq;_?p?pwV;8F5Jy@k`{~Y{$|P)EQ-@ z-dUYF`)y!LyK#)NhO1ErE_ZVgmcBRYIXzwsb6N!@g=x}U0 z^;KPRP761hM7MCzoRm6+dMt?(cnoajJ!pF=>8Z16d0h6P#2M6OtPOLb$uft&dR1}3 zs$J6?JBrGe*c2&^+86$AoWqpq{I)TZx`n}@Fxaw1<&KP;>kE4o?%I6R8WzW+=P)-x z#3@#3XkCf8tgf>CR|z7jA8CToSl!(-o$Eov%1_!(YDdDX&;;~bNCUN|YNW~)=A|3y zn2WIyqa#;Q`!@UrP`~FV-Q}2e0lMxW5c)Av6_(88){`oLz&*Ow&R%Fem^A7VGM;y2 zlvA)Y?2a$8AJ%ghK1WH5`@yYPk}Y9nKlskN<1PMZ`F~Z*@;+cHmi*erg{5n>v#27v3rYvD%#tD|jYpcyx9C-)(A(E4TcKhQC8uhqep zin>VHOGiC3$<=g-CL99)0LhvfsYo1yYwdDx!>F!UpyPp;QSO}2{}gHr0n-k3W`ipU zfF{^~P-F3{jIla+-<*n{n1BAfsMVH0(*pg2RaCplzzgE?XGS_%4WJ0N?&Ep zfhvT4m@sr9Bf2hq=9JYFQ%%nO6*A-;zzv@)OdZgzdms_x%#j&qJQRHvE}?xSM_Jbz z(#JF`CQNXfrmS`FSuNlsyllOie z1uhVF5ToIo$@oepJjn4NevSd+B@4SzEd=&PV{>BfO_Ky;*{v0(11L)PPToSCOG&FB zL7zL+;KP{q+$>=v>JkyZ8QFTZC(IaREGA$xUIJ}|?edS1G1MCsufR4AL-+*df1E!; zQ`8_ab#th7qnm34H&s#`@cm_kb?jIR9@*|BtSf?dDq|p@=Wy zh&DtEqBRjs^h9(?R3>T_g@^`)al%K!Xdzbs5F&(O!c<|rs1g7Lh5^Mv*&sS-5<~>= z2d{ygK+AwRQM$-j^h|h5_)_?v@PaT_C?+ZpRf#%9Z??1Z?M0Eo5W#-ICqb=%BKRdd zD$)nQfpNe@;3?2NXg{b2_z0*EWC09-7C<&28+add9i#}d0UZOC0doQABBYSEmAd7# z^`8HMFTWMHbwt1wx&i>eBfwc;G0+j{38Vvl0&W9lL=Qy?qN9KuKs3M|I1X$ERs$75 zZ$QzYLm)8_1F$A)6do0XY~}My_`&=G{AvE9twMp1s1a})C=KcXd4a5ecL2u$UV!hS ze$ifl7Vrp287u}-g}#U8LnWaENIR$xpe*_-Slp`T2k}nuF#G}j;MNy`qVSCHoQS@y zJ%+GB_;FiXr-YqCJyFf}NHu{@ARyQgOar5!fzZDYZ-@q@2F0)|jYga(*{c0=|+r=WXbcVRKGL(oZZ zFNh6%1ptfA31x-yg3zrLz9yf}%i~q^oOn-p1N_`AXMw*kMWnYqre%QX_FV`eungD^ z%m5t$mw`d*kF3Um~51o9Z{00IF?fIT9D$XR$!5G>f-;__eebNR`9H$IR*&pXa{ z-3k!|2wg-Wfa5?%P!(tivgTc9Snj5Ll}@ zF+2hSn+MMUdqfO@{?<9(0QWOziyg`S%}!!lZs>6{c~OEKks|0V*az|rYz*2B+!DD6 zm4))6CV)3sA9h~s7V0u482b|Q991f&4Lb?6+B(n4TXk9TU;Mg&W`S22>vo$Lg=$~{ zlnuurzQH0PFkqvA!n51_Z_|P=CmaL0!L^Wyn9mYoxSbLrj5YeMcq<$SJqgkfjqoPe zlx33zAR}wmj%La9Va2l>1QF2RNMGzFY(I(*e+b0zr8bc3>+ANL{i0v+a0v~9tl}2w zl_E-R3a2an2Dpdov3!}?H1l`r%+zJdI&BB5VB?|479o$_EBRMaTVg<54lK=&UvFDd zWW8K=+4v$1Lx|&6@jVI@k_V|-zLwA~Qz*s2xnQ&rX~N8<_OVB;$(3!T6_tz4^TRt< zcA{>mpEX~wHMT0zc`IWj{7k>y&+L%s>KP4MNfA$1$u=#pTeCZDzEdkpmH{o@D4kcI z?U?qN(wPpURx$T23D<#qENB8TgHFSq#_FMeARIy7e9QHwrJIY+tg2NQ&mMx2Aj-R{ zf7EL=b+utT7})O8nt^Z9>Ft5};V)_*&>wYvnd-4#Pr&`OzkhiAxZ%Ev9j;1;HU(WH zrF;IoEt61<(8xmigQHS>oyC# zSreLQ{?dv$$40b*D8%l@BE`L-8Nm6iI5uYK^{Oo3uymu|PiLVo(<{^rD-}N<+x|Fl z`0o41<_~Uskn1d8H&*&D-0fo2h18=XE-ge&dU-`!u1RifUG*rOcV7Lm%h`ZCyE<)E zm10G(1v~2OOd*Z3W-Au2%rRcJJZzG##lp>Pl4s`nx4NBr^7`LRwJse1jbbdM%cN;i zf6)Iyk~RVtAI#ZvtguQ3vi5)MB+Tmd(=lxerg_Qt8Xg^f==Fq@;o4^j`?s?*wk_`D zxv||L2ATYl*2Be}S+d2&ZSU#72+Q`h{=)EDuMZ{$5>@O0M%Zl6bSGoC;H>Ni?Q|0# z(|bngqzmv%D>2hiBhUKdd$FSg+Kx3{zBS+m{5K{T1BK;s{$usbX|g57bkuV#5teTa zZyHn*Pi>mFOr${488a$0LaH4?Hf_*m&rO^jZW-*JfG+Ii z2t_?WPq+gr4)wT;C|AEO-aq#0%*#vfS=pe$ ze0d$ePlv@X79R)fb2B<3_}H6SUS7PvI=(HO(Ius6=-}?PYuMI8;}_It_2hglErP1c zItp?noYL+v{m=Zp-eK7kQRuv2ET`AM$7u*on_vAR@Pinkon+Rf$1&;v|E0(oZ;JFv z5iC>rx^|Afz6C~~h{;-}RR4Qc_1ZBZ=fm@#{FdXQZpY2=1E&SYHun!ZsUR;;7uGqJ zf2;{;<4nNC2)Zbzc)u*?B3&x__Ue9m#I)7~aONoc4SHGSh;g3fkm<521o?z5Gm|wy z@3}d!Ii1Rq;rNSk;P-J|QX@DYxCIA3H$Z*6v;u6EJFW9a=eVh*AzLh;QuCBMa| zFn>%9k6swBS;&AS5tnpNSb#0N^t`0MZs8cnVW*y{-cRGL^8xEZK_}E4J0(>sc~X3H zGoN{W8o;#J@|B`#H|eUG+M1=PD1o0){$!QCmPoqrIW`qskT*0VE4~x3ukz@*eeoe0 zIvQJ-`af56mG;zz{%c-TCZw7Kdz|rEwhB;v2)@5qM;REa9v!6_33<{xwYX**miop& ziEbdP#j(*JJx6*JhL`E)tB?4PAs;Yo8Eu(EnA^f{^G7CsP<+>SiVv#DY0Vq%vK-ct zK$91~mQ}wCO)ftp$RLizk)668_8>+b~ z`r6xhXm%iSmzF3u$Cx8U6kqOstfHEsX_wU(Glr5i9IzTV89~0)wbIX=K`yy-sbF91 zRXkf3f+K*+mvEH9>7He62w6T=-C0jy{#4HgcVQXQfJh`KD||hWb~pP&>tW$#+mYa# z2i*2Hh1%PMBc16tn{_J-s^!}bO-G7L>qOa$yrmqhwG&XftG8yoCOSuRC(&y=#6_fP zBZg&>xwghd^hs_b^+>;<>wDkC)SHE_brwJwZAfU7yDI%2s<;+QwWF|E3{kF(rOG3% zeP$8*;WESQ*PVS|w7%GUJM!)HFZl-dwGSr80%G#%97?vo$6fU`}%l7JnwVg+3%iTxZhuX@5rK7 z`OF{D{j%%)UgF;R(5@X>vchFl*FbGiZCq>4s17hl?YZT=M}Y%Qy9c|@MlsDPuO^!( z-!5}topK6#%Vu+Cx!Pph8^PvW#^{s2;DN&NW||4Bk$+DtUS>ir3|}w)ZnK#&N-yhYjz$H!dy zp7a%s_RKRuEVFX*{C$SZ+|zJY<+Pmzwlb zknW#-s=9UVp%!~PC*)&fKyaU{qTC1@(08j*xiPPkI#~eTt=47!%T3yDNFxxVzdkjK zoL-r{KIOgA163xh>X@1anzm@wNScaV7ru{`^ zXpYkbbA{Xm>?=ivx}AxkZaY3{3p#izb5|NA<9f#ZoWYv5#Yvs2ou?x)5yyh`ofYtH zYu@DQ#)D1Nu0o0={GNJ`MWI`sb-r2~a&k3w_TIGKwM;P5}ILIYQX;P!gC%I&?=nX1$fqHO}F36BMtq9ctnuKYl zNM2pP-&T{}kfD}+F#F^mzGs~rjyqAthtL!p9e_Q>2U3@ zyJ?uEDLF%TENW9!CnLvlClO1TKs~8st!%SQ%i~5rgFd-eWt!eW{@Pf=$uqj(dmzFyWW#^It-a)-#rc2cP248; zo@ho47N|XDi*}zgf1*NzZ?S492PWT)O;7A!R0TZ2u`~?Ko?0dupO=3G{k76FsnECD z&Fj~s$S%NnLtq3w_khIvTp@Avh zOrsSKhV@L4uZ~}27_Ub{MkLKhw^eQvb|Z?`t*9So&$3iZwbPht)>ggd zkGw2W_T?OpqHE;;kQ;V|f#?WM;8!1aJp|mj54VSNRrI--Ne| zTDrSyX(^FVxJ@AEp!tiq`oe4S)WUCJ1m>l&DdB`qwok5|xkj&a zKgDNsfB%UA+2j=EIPVBNQESR{*fPm568l_`wBWs1#MlnT>BXDsIu*K?+o14`?6u8* z-Zd6~D-o6KsJhd81Zx$rGI>wN)|5kW5w5S1LnL8)OG|ruBoQlGZG4X)4s{9H>e8Zr zRi;86CizWcCt33e!WU>4V2wL$Tzk!d0|NOrch9 z4QsJ@Vd{eFqF|Y+py{fcRyjU#XWM+j|K$jp7v6m>Mtx~739gFjd5B5&cTLvIIG^$& zPJaA*u*Arh@GSIj$Ss#7g9%w6RYXddHlKVrZz!}uGmLy)Py6im z$+Yj#_##75{~bNkZ$3~q8AB0rt6&qFyG#|9jfQ0GX5q#K^~GRdM|YI% zJU*6tw8ilK)#8*dBuTi{$b=9X zdNt%9m!k%NEQ1Q0&7O{%yfyDAR71}hDO~b>e0|dGYc(olhSZADH~k(1zb7*(7+wji zS2N1A&+?h!Uu=Q!;DYPo0?SvQ|3^g;8D z0GB8qn2){vzZwr@+SK~d#{QIn$;rbMJ6=6(NOOhhXUihPSJ*t^wT0kC45LNyQP<7X z&2hW?FPpXaSx$XR{`Lta z%`yk8hsHqL!9;}};3Gc(@Dv@8N3eK!Kl}__0{sMO19vDUfO|j(5TKY>1VGL}Ps4by zTT1boQ(&|5%0Ivj;1h5{frs3Jk(F~z9Q-BZJrE?9$+YDMW$khjXa&EIOhau$F2Y-( zo{;;B>vE2Ao*4-^DJQi3&{wbJ$iZqxv@&alCIRY<&+JUcSn&Q{OZqW|;9k>89j=PFIgKB~~f!1<==}O5f zX)mw{Nrg8eG>`=dbyyP^3Y?Xx$%~cG^htVG8lxb=6459u7PB9ThwDR5%f1Q#P6h9> zlnlR&d#c)wD@C1wkpUe^ui%ToT7p&N!MCG3u^Vw|7(DzR#YOo=`MmO1D7_;3EOM9U z!WPjhv3Jo6NOi45?9YPs7mko)HFbjqU+j7-z>@`wuMK#_2nWJa;-=(EEV;D2-!Z zUd*N*lm6E5(Cft>6L)ju!cYYRcr3dQE7JeuD6!K*KN!)hI`oY`eiXCc+R1KE?-g8w zUpH;@*Q=$4ZRYVX1s8LTa8GX**2JtJx^_cn3$XDv77q3LHOLw%N_vl*yVS({Xj&IF z7rs*6zHjm)>D{@yRb$zVPtLDsm`e+co#5BNhV~pkI=;%Kdv-A}mA6nT< z?-ft+G~?aYH!Nxm9w{m&FZEY5qm5)Mw}tsS99Xofepq&`@<83q_K&>I6=AWqmKlF9 zJx}azMCRf)iPId;91bcnj%q<*=k2@HNvBv1{F~^tHu0`&>=Lm}^UEFFu_OV@AUa@O z@TlRf!JRchWrw~gnwirQ)3foHSL0zX%edudr&oy{^6C-HO#2h7x7~9rkvB~r>olLh z&68wdCI>u*2`fyx`RN1Uy|0!m1k;)!ehCpJNW>5FSH>^&$i#50@QzPsQjPbf#ex!1 z%i+a70YTtyvnp?U9x@@=Fr1HUJ=L{hHj4HEnPt1z^N8~`&35Xiu8EOb%v;hVvxLy= zE3Z+%mJOCiwmu>njS;b{R_{ub_XUssEkvS|ZD`4lMP1&J}%C-~4f5xe5cfwWUvy9zZ(@PtM zZZblI{tD(ANWsUAS$^G^1EkD9ougmpN&I-7H-x9|4OUka6{CJ#jSD<39~T0+q>eN=Hhh?|#Z@C|G!V(bmrggh%@g?){v8vSEywZ~lb)LVEA(pb?2wYGmp&tI z*u;1iIN#H3q>gv(CUTkivd`wJVJZGu^EXO^E3dbICSIHH)(nr7$9CzdHGyU4i51LL z;SQ*0?DUS8IS?-|WZFdcpEDEr3wyVd{S5 zm)iY4IwBo*5MTAZo0RMU%s7->X}?Q3C_E%=aIM+)aw|6SiPlPnQPX&T%-juTq-wO& zZ;yxe%jo%;>`vKqEaxX|$WbGbXD{zI|C;wbzwhn{Y;nS>E#7hU3ecpgs`mMe0}m_E z*9uu*xvf7f)_SvSx%*11Drth^56!o<_iS)ErTcI>zlSt5%-AeJn!rP%gNkYVvb7cG zTStkLW9wCUYqaBwjgwlBm1YlDGj<9IU{K`pj_VuBeSV>;rjGnMGp@7XDFXBw+(z6C zER3b!hP%33DZ%_9^*$d=WFfAi*0Xf_m&=I$NQkIy#nq&ho7r68~>!P;f9259jV0@`z2GI7VUYYaq1XM(^}6{<}B6< zrS0!lA^yj7l1-UShHmv&Ch)TLm80!%iKxj*%~O$UWB=93{}WnqX7mh;B=`Y$Oeok9 zw_e$i!~I6}o32iJFYcC(81=b}2q#U#1rG;S^zEUM1T5`k|KK$RK>WA!Wu=WGS&Z;kscqr_m!KX>f%NbK%W1NbFx>o|4vg;%8zw{jgTY<%oVea@T{eVKq6DZ6bO=4Hl`oj9+^q8&rCyjo&4-nRyXb2t;y?)`QHT$Ss&GOuC1KF!pT3?iQ~iTgGj)LfsIz1viF;tL)0i$L=ADHvgykeV9w zOB-}c=jwxp9Y>0|v7XSRdY?z+^I!a1-;vh|X9WvRYxC!~I!4B8uV<$34y!~=l@Hm+3OXuQxM zVDTNTP`STy@5OD?59Qu%Ioyk#lPy!R)egTrG8~xLH?t2qw@rs|*1<9yA4R0vU+#YQ z^-Rs}{`Qe8loUI^1ZprrA&m-?c+} z);or(om#+j(I+;sTftG=8{uBAb%XS;qHl}6)DfcvdrPho$afrkT4hl0O5*UU_&K_D zIYIgJDXQi_#IHM)BUsY(Qa_^4M&i-sB++o7T<#K#EwMU*ZPrd}xE@zV3reNc8+(or zpUmztZHNsIZ$g-TKly_?J;O~D^y9U%XY(i5CR#m|I(5_A%1Ogi0^HGB#k0uSLd$X~ zw)^quDORccq@~a525-vbmD0>=LicH+3dzu@K4vOn2gd3LzGnAS5qFHgQ++V=;5J2? zpN)~cxW~K=M@ps&py^f&54_8E?QOI}-M5JC%-=G(d3V^zimaK@(qC0>U8jhjr(+Bv zVw|I@R2FN8zI~n~aR&Jws`Ke9x6{(E*ujB4eaKeP%sWaiB;Qi%anI$E&RhCScj<^8 zGeGvq?0eV|ztTBG*_En2oe{*RQ%v30(caM#wcL8lw^I}I98*3{<=a~R_MI874giqX z|Kac2nWL0)u-c-<1M7NLm$9tU<36&7Q7m0!x;acIfVhxQwxVjWV+--?WR{L?v~5fp zzUXIi^}_fq4w`=*Ym>Tb$6Ur=$JdIl1CYP&Gj^0|h0Oev$9mUPJu+icPtP!UIY!Df z_6s8g=unQ9$tq8GL=ep;k7+%Ls*5qu`u1y}%6z<3Gt4jNgsG%)^rPOa_Un+x?c72#g)I>zBh<&Gh|93{! zwXqIP0S`b65}gX{GYwn@!JwhamJ8GRl%qh1In<-yHCX=`>p{=qVVh-z9bdUbk1sc?eAj-DxNc%z{d2^BvA6Zg|BO|7kD72z zc}8fv_^N{7%wE@d$kpLevvm5TK2~Yzhr@6;#IE}6zu>4H|JXwpqWaaj5bRF_equ^`VAhrwYO|j}x{*tw+wa9G*&|ypk1~#kubw zL>V-4Bt6(+F%2)J=`97P20dhkRnRIl+Fub@PUx%c3eS$cX;9WQSh<2|%XL)V>;Jpk z|KGtHL+ZhC&;`5(8Ae=Fc>>?ZZ{Q!3UgA9GNP$F5w#q?O16ZM&rDcLz4Q1O>f}}`< z=6{n{Xln#LAn1|)wDm5nnX>4*LU+gSGd^h64Y*a}>$)3oJI_#IafRudn2d z)6;Vm1I-AuwdGHjtF}nYSYSZT9u()h-msk8PaUDb5lvzCC+=9S|FBexnKk)-`j~Y9vE)S?gr)|gPyB1=_0hG?h{X7w{<{?tX~}n{+up!FU17H28J1cz zH3;Qhr{>Zl5TDoVIR4&*@Yb}!e&$wHcJnX-Vd+F@w|a*45Aw@i1G)S?Z4}S@kJ7S? z#N3H{di1RNsh5XaGN%g4pSP26)|N~{g2e#VFT5e$fKU5&eCh*thq2b>=oHc8bU*FV+kj5AeRK zO^$0bY%|?7b$xWcrnQ6}A}-WDSc+qvBY)@TnDrFeZ7A&vd_$h#b{s6-OucHj){ad$ zqjAV2DaXwtzkPG#2=mi&4JB3b-Ff~{Zq#gT=$B_R;eTAdo!}RnZgo22cwL3&cx5BQ zsQ>4REq*qKd-@V}1|19<+F zUL{p=w`rw`0f8l_&RTs<&Ai67Ct7s6CFAC0t)@sQEab&j}-&aC7wj-MV4B z18MF>)~I=J>({k&8T!7qpWM_ma}9N~=Y7(;oc^>hoEd2pq~xYk(xGRO(Y0A$*)!*V z1K;^VvFR>ntb-Ab=5}&}j^^pw1N{};oz(U8?+ByFdq<_(!dKDF#gkzbAsr`GORb)` zZZy5A3W;5p_a_I2aLmq*P@;yjytg66 z?pLFtRh4s#=DU=vsPYdw*5`ENBUtA%^RoI+Q#(?_Vw#X=VT5a5)4TQ{>(=)i5{VN> zw&L{Zu(zmf51!ujN_Q-;OICK4yxIWg@XU3K+8fLJIa};=^c$NJR`2%mprJQp{jn+k zmiej&oR7vsPF0R$*jxg1!)pBV+TQ~_?ldKTxeYy+u=_9!w);g}_lc3UWu-(Nt#X@M zw}aXu-Q|q+{*QWE8a^`SmOoI!rMumVis}Mxm%Xk=l7ux&zXze7<_LnjRXkQJ43nP9 zef)iRW|VW7?8P~xTfa3t|K)h->l@=cIwH$kY3mGC9MoO^Q!%ikZ1`?+{THLloeNp& zp&7&1hT9#T)$(~|)jB`^w`@(Hfrgt^iJPxMC?22cv>HvM)o-BXFzMv=q7(Lq4%9{U z{y6t_E6Lzz|BofPr&+RdwB0StqW{G#E#Sv@-gp7`7deqNtTCT`Y}X`p>x=M_?p~GB z|K>vtp4-m2>S|P&rRT)hOB)N?gls?Bo25FiFlg`5B+J-$84V3nm`cNTi0W6%2G>Xv zCpAp`jTncSleJEi^Q`0KNP&mtwcT%0zBUzo(websoUX1HdYavGG{n>-({Z$-7% zER8(p`cRq~gDQq;#rp>NMlW9UFArWR{V};u@4T&*Yn^7A(V;AgTW>3)`7SGj-ayR- z!hA0tVL8h`<<}KY@Benbc?;UY%EYzJ^nj{G6e`h^?EB--!gh9?@>Hix(1p*}C3nI; zV5dS_2CFJKzNSe|eh!CmVQyMEm1+||@I$Y;<&@^-aI9VO>VtXmt1nD?x z)=yMv#DYwu6nd{=AMK~gL#2%ctXDmjr+ zNgdSUn%4Ptb|l$|x78R~@Fv@!`{Mg;Q|c|hs>V10#sN+fjxkt(us3I=dPwb^!CG!5 zrE7T}LrmIoaJ^jf#ou1l;kZ)hM1!`kjj5YZQ`=x}9nlloakfd$tfp_Frpj%6Opkma zTrIAuO`K_|zSi73FSX_?#xAo_+TYkJ9KF7_V++nDxET(ST*C{yoVsHQGU|lINxkK-RZKI2$ zX1sB3);-t#f3^N1*^4y9Qn-TW?{Q?8ZR>}Vzb;QXR0~^AVtcGUyTXi})UDzSV=l}* zs@+f7#=1i;5+qyww}+PK^GCO&YI=V|PmPV3ZK~q3-qr&9)i*UO8)8)h8{f$tAk&z6 z8lKs=c5*0BUY;I#*3(lqz7VYMZok)cS+(6#FK369%b%wm_c_;=VrU)EePI`m+|+e{ zEpCyF`1DQN>|54nZ|)qjwyy18I@;f?78w*6DZ7$e#w+abeon~I?` zJV&aTT(@GNh)bgEU-rW&68a9TQkcK|+_PqwRW>v>qCI1qq1Hp^au#E77FEAC-*=giOAvy=_2dh%sKht=~v z!uZ(V>R%dWoPL-7FqYYy`Z)L5Zovfm=d8oZb!x6mIC4wKW31bnMjP6;|Cr^xJTW@n zb))PH)!XQr-8<(is;g~tbB-Cc{yfyf-~LVttY6ytYD19j-- zug)jLwz8g)YufK^e!DnopVm*$xZ?G^W29+<`HFsr3Ipm_q!*<+5a@f-Xq`YPsb zkX`yR-?OH3x`y+Vyp99te%ktAv*XCzo05sRwyzcGtY+gn`?ro4apzoKtuI!~t_>Sn z%e_sBVf=^Noa(vvIN#?b*#GYUu`GLLQE$v{)_F;NpSgSXMca>!!ghB~(^4R<9$FWc zQ52><|9V5~^)Zc#j{}+-1=a>`7j$4c_tI|rKkJ43ImCFdtU}e5ZSZV7EN~Al$*;xC zsC;|Tl8V`80e3~3nyHq?=*5)InARPq=&_o~WPz!T-rg(GSL&O;=FJrT%C6G^2F(vU z|FYD^S*;?ko#*D&tRk)C93mHU91V8lU&^WId;9*?CPHql{Jsaw5x5OxtzfF8YQfAcRl?V*P6ioLknGkfIEVEivBgm zzFXb=V&ZkhgWf{5Vygi{zwU3%->H5<&I6c+(q(-{2j#m&=H`1??DhQ%P;+sn|J%0a zSoEX?;PSvENEH?{8DmW{tt+Oaux7~51x7aS_r8m&sk>78VCLS>rut?e!MxsuVQ~vP z8_Y_zVV|mbGP{z~PS)kVF+96HDr;5O&_Od?X$8%O}tC+EVy=Sba6Iqc)FElE2$Z_n%DI7D`Z`3re`!M*J8%T9#T)=jx z9NBBh&w3d-s6Mb*#+%jA=O+Uyje9Sst+&P# zn7S*pUZ$K~ncM%c!IQC)QA&L&HSrie^xeI&q@*@$rtq6*s}tt85&{@#45)66y&m~= z(xe`?1TxzwaUyH`ntj*8^1t_%dysC{MgBevoicB9`EK5Tn+{u@bapA~N5H~uc0Oer zKgsN6Atm`@Ggu6pLH+6Zp(!mel{zDA;;@*2{B&4cJ~_&PL&1E4(P73+vhT>E;WzK zl3P0b!FRcF2A3x2)`)WvTMnAzj9>rT5i*)yzI9YjS#_nl9@R23#AUd&A`o-Ax@ZS@-pQcMGOn{<1zy6IIMK+Q*m^%$v&z%P`7_@goC7>W)$Nz zQ(x{5=O@1E9xxtoD<7N-*K4rLc3!D2xBQZI*Ze?3Lbn&kV(BuSix5ZE9vDTfer?(n zMf5H=82_ThvT1fH(yTO+W#C;kThFz=XFa41D3ze@uRND-`*j&q0*L{Z1G>qbN z)~JlbeoZgQ8NZbt{r`OW|M%`9C0mK7Lat3idXIoH#CvGnaA(@Y0+YEF8crsguK$Y6fOtk8K#RJm&ZTxTg%>!<@e{(1(> z?F)2SY4>Sem+810^N)drT70jaJoZxNKc21dSBzr_i7tD#@K(qp&TNUsBip2OmAu-^3K?UB$7nk#&#kaw(hg{ z1>|#vJ={!BbK%MDbq@n(PioZIUW;kYeB-55jbQj9T6G&|BX8~e_t1POe#A~1OUP{T z5X0ISr;)34Ev723lb+JZA2ioEh~lTxb=*!uT$n%6IGqq8{_2~T&*&Sp8=UqfVbUg? z_5$t9)42aM!JY?~iawZdVsv4I!c@bQ2euQEVD>9DgnCEw-V4u5Lq&D^t2~dTuS{T= z+bOjzDEK>=`Wnj%hib=xGe+lqNts8Zei-cGMhMy3Hz0pLufF8^*Aa2S^gxh)*0<0E z&0DNR>6q>o!Hwsw*G~;@#=W)txMpeH1^;}kkf8!q)w@Std$;@HiP^szqjmvth8dl1 zix2~5A39V!VQ9;hdv6z+^Ew@_k*USW^ER52DfUryrpor;TP{phN620qnygrt=@@Nq zc!axN2x|8!96mk0{HzWRgKf9x6Q zR$V&(*$dU1Lj&E|^;T8kJ2N-eVy*hli0H= zB=yj?b(ed;cnaM0ExZcS@Np2+Hh#3=kya;ctnA8Vuf|rW)FdM4Q09hEEFQsrC#UOb za+;q#xV?GAMdh<~YGi)KoVO2Rh?$AZ(LqnVys7izCpA@z;jEpsCS|StCaE8%LTyQP zw9)ypdWDrZ+u)&(EUhB?k)b_rTqx286?LDzt`h$mA?}+Qhqz?MtSVBCWA!NF^>)!~ z|24etJ%Ll>+B(Nrq+>nCAl9tEXk%^OV9xc>x2cR_Z6RSz3OA8xbxHJuE6_;AZm!#S zc|(nseAzI=-!{WNqDm*7<1N{!%M#Pxn%tP~c0|9mP+7ywsP}(`{>dDLg1YYW@9w>L zoH3W7G3)Rte$(2A&d=mjmKe7NpVy(dqFd6>8`Ev_>|6UZj%t$1ujJp>ItzbQzWGYy zpL}S5NoA0C=K9c1^?j_J^6h$V?7U}fcPfdIDu1jCqAW5hy@Fv|n9ax?I)zJ1 z&>v`_T{k2jP9C?h6i0Cy)uU8a{0hB%?OU2M&kg&cGwLIedTyLwBE0UH)c*bU>o=4Z z|9>|B|6d|oB?1bmcmhlSR*FU7IbaPW$QPw#Nw(BQz7Z%>@(IQT-`BHAU1l34>pAXB-&%3QG$n3rn;nu-uWDBlC16v+xr&|HbI3Id7X1qceN z4ZRAz2)zZhh8~8rC{aoZ<>o72uu5@LVWb$7i)62)J(6MxR>`;gAlfDz6EJvdc!k`9 zd>4U-k|$|`#H$bt6W#K%ge$({RTS#WXBcSi2H{#!w4x9a0{BWJC6`49#TNk*L>syc zr$ZAJabP>}5r_j6z;Un%G=pw|g&`Ud9GD?&9x@3Dg0$#aRTG)<}{eW&Dz zrpP1Zb+Vr_BUy#ysW6CtpL3D>oiiiYE-6&>!TwXN)=MSyIAZ3~DiJazQCU90I4CZa6msf$Xjz(q0wgGCN(`F_asibE?}7Uw z&MO(q>)@GiV^|vW0dy_oG3X73fj7XrfV*r;)GGW(s4dbK@VWl{SE8puzF!Kk;=8{<-8OeBdhZT zqC)OUrcf{knM81)ub_qCY3NBb7xxjj5w{Nc2D}1l%U{YyB$m<`8K?+_WW)A?*A$MD zErMF^QT`78ej%F=5e16Jfn|9#=mS&(`4UIa5e$SB11rFM#iYVaq9^EPtY)VzPjK#X z;nIzup$1?ZKycG(-{c%N485dsmfH`Z$yPF!7U;`oh2aoBY!CJv3<6(5?ZN%4+;nCH zxgq~8%a=Jxu80f;u9B-#8t4i&g9d>|fvwVD@pfUeC`r0o)-PF#2r9_po;vkz*aEIJ0e^V`O6^+2-F|i0l5Nf zl%d3L#gD`@k{CfdFOSa?b&3KcA|XvUz-y80kS)pW0VVbZtOL7cIwBA86^RnJh%nZ+ zFn{Q{$+mm-ODzMJ(?K+Hy1-%yD?f=06Xny*S!%pciCEkOw2AY?lX3+5G~zC@4RH)Y zfX#yMfR9R4U6(9aMu5CV?0|2E2LMLWO#%|fiQ~iD!8^~LXOZ}hqSqpbAYRDfRr2@n zRUz9HBvd#8sphQCz_+Q4cVNs}ROfJp`c}*o8_ZH;?EmDMoO~5o53-tgBG_3R*H0H3| zF@A_+z+sU{cubhf-zBaT+ljj*XGPWg(`Gg4L7l7!x(A$*RtnkdUQROS z9=}hpMZ6$)h8$CTh6rWFaz{~;SlJ76RQN7H23kYY5Er6lU~HJYd2Ky*1=o!>GU`h{ zw&bR9N~_1>*Xn;34^gNy0kpqzdq^x~J6@{#4s8X#0lgtBCG}i)afS3XA_BJ?Sqb?L zBOz`eje&OQQ9hl6;QtXyMEhhPezTp>txQ;@(lKK9G@daE3^Ex;+E3m zi_(Y6Xix=Op-JGPG(GjPBWjq@hMI?wgbt6jxxflb3cz#On1xYOtv#AB#X z9ssxs=$vPiMJkHU<7>)ek!G-RL_YWn@=kVJepKQlfh%*!L(wY!8}ZHWszK6tNKfxjJXK*b=Eul740 z7NeMx!S0lxBviqE-ZB1PfiBn~zpH9$X@7#6xdvdsR!@yj&W*=SohEG-o|OMYU6Q`D z-emeX3SD&IZD4IR$+ za*-u-5pyPs#S4F@;%5WcSz@9B4%dY7tZ*i8gKO>Qrw>ggb&V~wGQW_8BuD;F)Ioi? z$s?;s=P9pw*J^`Zs6tqsv|GHNhgz4zUULJsP^+;yEsv*Soa50HfTanXwu;8Dbfbqr?c@1$V#)pqnB3(rHnF0K(zZ z{FWul8wI;Xh01TtU*iU^x-EagSJE!CyoYTT8x}e!*T*fTi)^vzGW(!3n=>KI2esUS z2ufg?Hj=8`sl+&o@`Mg^m$@7+MeHLz42~mLV)Jy4TQ#}f4%un7$g`)7kg^xzIC$0~ zYlWa*ejKs~e1JU$*{z&#{DjQ%F0leBE*@1CI5G!3H<8bX*&O!%HjI0FKqbYVM3_0le znv3TdaDSs7X#T?8<80#v3Dzq&!FAy~G{PFbs|fnOCc z>3IPK_8GHT@ta=)8-QIvoPyaR_Cw?3BP@$0a}t%I#kj?5hSeDVu~v7#W@dzN7v2?k zvnNDvMfM`Pm?;ToS27;ZuF$j`LQThI1r%cpf4IT?d3p4=OA8K%s)A zSTI-w=eI3+OHz>__O|wElW7-Ss}MCOSt6^3H=ExFx0kx_ z4%`rNN#-k3zJH1-=LrLi3=W-U2_OZIk#GzE^mTU)cOqbt^XlhGF*mD z@a9>+X;+qql~oprb5a}ue7CaFS|65U`+Z@VTs%=Z8^82~9ze^M7b$gY-inmFZ+V~C zXxUZ89zYXCLeQ|T1UL61$?FKt%H7CL>}HuNLGxI= zKo(J>*_pyI@j2N?g%iRX;;Mba$i`mJI1e?+23W_bP$q_TOIR*{ZHF<_ix_jATO6C3 z>5HBmnSVMrP5Ugag@<5X!R{&o#SWZsE>T97$H0=|Zdez!LAU6*qgfSt$E1avZ|s8z zK4>7g%csZ^fwPd!V5Uri_k(+hy|}Dco@458=A~k2Cgh`Ji!59;EgzC+!lzYY49m@1 zwH;-%{I}HSY?*kSiAeWaP`rBmuCe)nxU*Rc{zU+m} z34&3~L#D8$R;&<2vS-RcOfAn~3CqPmc7iSJD2|W7O9sfIz<&j)>_!G!aE`Csnj>Zl z(Rx8 zRxN!KYbDd0{~M%2u1lZut06{c9C#F(EIr4CpY$`I>?iUwxk zQm9;9FCy{RGm9B$CXQLa#PMuEK9+9sJ2WZohvyW&!WHD{4w{cy5RDfR952yY;G*1H zexJX{J}o)VFB4!zZA$g5JE+5O9wG!&YFuQ_@ZGZdQ1S)$Upy3ZZhn-xTYQF{JNJNc zdLdx$+bohCL3863LjK^tb>KIV2;u_o55j-R#IxGb*7$<`_H_egHa~f*3w(3gIJX0c;g88hi(F<5$Rk^KwCb+^9~0QIutl&73dABP>eGdWI{bEzYCp7)Idi*WoL} z&&U5IuUb5=?4{p}AHiI)o#+LWCHxYSqn56D5k4cq^NSds%hvP@%mchLLMeQ^!HF=3 zjV>E&e0DAF8W`>F?kCS-7rK@&D1PA%V&i~ZF^{`Xz89f@?@%~Pqa=QOB)ArHN&BS! zgoS|x%QG*iEZJ2vhY>gvKl*&3pL%_=bBsK5g~XpNB46h2kU9W8$gQYV$Pb7l1PP1R zNL3$4oRAc9!SiURyNngU~^F<#rYlJzy zeeNB3#p1!`E1bvj%Mf?uf6({1&FDO29C|@*0Q(uXO0>-RKwD6vF%L=#fHM3OmwS0n z!l}_a@M=Vrk?EE^q9)miYR)0ZKOrR8Txhh6Ar6$G)FJ9HY#YQ_d{w5Z=t9roR^s;< z%<9$HHoM*M-R!!+Nh5a-sm|<}Jxy{PNtw2t@*`I>Jb0?YO8IJtHO>;_j{SxNApyvT zDsbFpz(&NTyRsgNnq+m*-x^Vl>$k{5uLQO#A&k8fz3t-epvhFaHFroh0bW9ADqm>_{8gY8I-n>aoDX7L^A?_kAn z{|JBfUxp69NWlQpU|uS#G`8V$5uIQxdQhbf0g z;BVA#8y`V6(`CSyPL7|Q%^<5yo$uK)uz`4)a+FDv`U1-^HU`jot|3%k0o#onLxv!= zrH@#@mqNIfve(!M9pt)#Z4>QC^#kzd5LF0G;Uv7o_`!{mRA4t4{|X5AKC)u7&4v{lEh*HK zGg&=F#QWoC=Dsce2f8D3U=r{iZce#pCK5IYYM^(h4`N5**CFYMtB_y?6M*moqy^9) zx*whAgE#tq;YX5;x{r_VUgXZ14n_{2pABCkvETA7B~Ku4P>z}+O?9m<^)y%u#0X@` zp7W-8`m$KeC2O9KoB0!$9t(_Dy@N4#=bXmy7SfT$ym{3nWBC=tT6h*ff*T{BLedZ% zOgy|-trK?*_FCvDd#dE{wC7L}ut@knkQU`EH4ikyIXG&%?$9bXGd9?0=<5}UNMF*Y zCeG{833Ld}T(}?Jhg1QJ0V7ZyN<+NG!qH2xPiUBIly^~?t>)nV5Q4IP@dmG9BpPM5 zbocEcX$aGa=AP89(eae&$HNOFH|Zj}lh9f+Bc~~!*jfD(zDwhZ+6$PPQfqKO@I#?M z@74hbV2lz`$7x><0!0*)F_5oufuD5Xy}QLu%J?T+}1bAMs3H3NeDT>R{}z zhS?jn%=V5XHOc!ICl{v{hzH3gG#&>lHHGNFw`=xmpU3B6qp|KNI1nNK1AnCd-1oL! zD5e}JL!_7+7|#(pG`wjqmsc%$GdFSbIW!>&uAHA~+*kRH5`hJPshlfHV_&3=&rVa2 z!gl1aT8Ubb%4$S1>;m*UBpfV36HG=|`2?^T3)6R+<*iS~_f3_JBu*AmW9Uo#XvlxK z^Y|X^C+hkt<49dx1Hv154!7Dt?pC6a40|ij0JY4@EkG9|a|!P+^E~+>U5l;9QI~GT zoX1^KIf%7UsfB%%u$Ya^)r|G5OOydhKPL~-tYLz0Q4Pfi0lHEZGa8l&s^~v*nOu{M zpP!!UH~b|TxHcI+xo1RUVdK&buB$v>HB9HAwz)>EDgl;`)xqj%v>151`kNV=vK6iyb{h%#%sPI z^o_7VdwK{q}eiJN{;luY$f_ws_}2XOj2 z!T4+(Q}q+V4Wp`g%h1+onoz1HfKrvSH6E}@f60L8I^*#_($0dRjji3naU%(k1WSUu zLvcz=-QC^2Jw0`IcXxMpms103P_(!Ph`Wp=qc`_AeE(ozviI!0*7L4~G5-S!wWD-O zV2Eoj*^lewVJxc?a6GmRx7XOan39YJTZ&Wa@NzD||K)Y{+|2pSd`n%AZNVbZx1*wwk=iMY&f7h=*V^NuMNT!VZFx*_ZOprwcV#@=((Ipky$jU zYbacRR*}VC7i8t(r}OMy)4)$=wjl!gm#m?QSheg)6qV(tcC`78#RGV3on-oM8|hL& zU7^`Hn>E>UG=DU6Cu5g8U-AJo$Neo?=rJ+E*0(dZtnFUa)&JUy^o{eB>}H$h2v|p^ zGp6v~dJOPb%Om*39u@wL@nyML(iOC$j`iSG=R*1}?)%`Dz&?40LfK%uZI!tP9L=1} zxJsKqRJxv;!ZgD)>28-}Avn=G!MYeI1pCAF1cy0*ukq4y1k4@84D6!o1Tv7`jdw)4 zrB7+%IZabl=-1BrL3J^vE<_8i9( zu!weq7Zg?*(Iqn?8AAu#&sna(F#QTW554Kw>lka@qKnh|>Qv@0c8gn2T53P;$N~?O zA3Ti0Jpyl!jr2*x7Mw?nKu%I&&L-at$@4OHfk8FHesmN)t&3M>cebg0or5U=#YsKE z>&Lq)xFE^!1tS*c#>gG)7Z?nbJ1p>jWF-GiL|)?7%p2*?sUv}R)*i4N-->j%&bQjE z(cn^58qS`|-yqoGFOviDD^vFR1kwZu3~X{X!mo)rys?oR zx{$d!*>Y9~SYkIgzBt~SR%&Nk=zt1JvOO`s(1mMghFVLZ<6qFj@es(x3WO*8hWaex z6;ejIG_GCD&(x=s3uHLEzu)on^nm}`Zq()!m6pppi;R7=OQ0F#b>af0l;w^I_KcCv z3E$AADmK;g2K5|hg@(Fk;4OGht~_{rhA!i2kG|sb5XHX1ATT`8hpKuRX|@-xbFj;X zXv>w~G)s)HY|CJQb31ej8^@FQhxxzr`bBAhouHEDLr2}&vhCDq;_X?zBMhp-#`8rh ziVNGmTcoB%fSrgWQg9LLgvWcI!9mT*6`6UG80ItLG~fr#K)RE!X$u9%0}rPAr)7@j=MgLq`9*}WH~8KQ$P^11;KNb5LLNUFFjb4|n2d zt8JZA{dT? z0r=M`#z|KFBr1@eSB-~EP~gSRC0=~9&UD{O;fR7ZFn zw>4PTZfvu8x>69UtFJz$V~65`o23qQjEB1uJ7_+PB>Dnjj-;3OT<&9{*q&(rgDt~@ zvFnsO9Do0uE_>u6rAPh2UuUYrER{q7d;(s9>_Va`r-Z$v{o;=150-8u2Eo_h2hK8N zB3{C75C?^Ph**#mAHOT5TVR}HngXp`(Ku8$%lZj8gD*h30wwAb?U9}T=s()Eb{nL@ zqzp6b8{-S7+$&NzfD=fJ1h!f}I|mTmDDm{}oLRoVvUh~MQD!wxE{>=PwkBg% z+513~@C=vO-l;USS9J!NN`V}BI?N)k(}puCtX+)Nf;fH;Ry95X{A!Jb&f-yYh@QvE z^*x^YIqIuX*7BfSP=}g!IOhQmoNw`Wl>YQ|(YCOJoC&exIi0{aV<(8Z_K`CngfvavG$3)(vKcs#3UKKMcB>;mIX${xvgA7c#Cm084O_6_Qf>zdFpVkIgW=HycgE?Q4u|(=kn$dZ6Kw;}hCU^Pzas54*kpn?yCfj2^;W z#6_v$klbN%>~iIz>xhk1fByB5=q!_{$?eE2Y24TO1-yswz+B=o`-gCqe@SLY=nuLl zcF^9_d>K@b7CK!}?BgT9C>s|b4<0LP3=8sq1kKP>+b?(C)+bpG8LpbRx(>q(!w}ta z^B~Y0+sByCuu_cFK;k{V4P8Qw;@Ul?GO}S$+d|trc$y1Ftb~x)E_G)139B=|)mxgQ z+F<)Hw>Q3xGMYP9Yz>@j)qn{TCIbe!NhnLSD=$XzIlG};$!mFp{}?&K9`)I zba>lt_bL#YHyIQ?5>(l!Z7AKZi*vb)2324Av&-x~nM~pUdy(Jbj8%yu*E^%R6>jcko8zV<9#Y=(Klv_8ij7Hg>s&^g1t@}V$--Y7dp5fxLf$L);5-Nnhp!>RW9rILsv`(GGutQs}xu9ENU1U3Id**yi#xcV< zE9hGMzN;L0i%cP}utEMaPP~(9UTFGky9)L|P7xOvFD2D!AJe{(BKxF{g4W6A8vF+K zg}&ZX7;-kgn-5M~<<`#UL8sl`_$Rj>OZ9pr0z8hhbs}om;KXZ5V!>Ge)-aX6>b08D zY7bqy?w8KnFvjlAmjcGQM3kKj5&IJT7w1Ybl?QMep`es}I6SCx6Fai0aU8=Uhg zK{S(ymvpb2zvmnK6jfO3CH-=2K2c69_N2clAor-5qRVHZGB%5mCYFLhjapZ?N6fI$1c<-WQJ`AA($?8J=2nS5yP1b?l9rgf3`WLnuTC4-X=@v-Oi}OYWOR<##n_J7 z7lC!I2he4tmHL^P&KN;C0DpGmf|Z1l@tN!6@rH2~Ma@%nRoY{2kM#y!!#z+Oz6dMr z{cOLTf9a#VM@28p?%g#j=|A=pLrt@_eq?L6_A1qV^K!=zs1mx4uEeQC3Ti?RyE_74 zxR>p+tsZG)_VJMLG@MEJ7wDD2OP!{c*m@yrndO2q*_^ZuLpR29AWsdWAzl+lRRr9Q zVEdlpWDtX0sdyaaA$|$_2lIjdv9+5;R)wpO8|ohze=O%)?zHG-G>djf^NWVy*8A=A z)qAWLz+J8@a3;Ex=uRF*LlFl0$oUAGXM5&&>2}N?c&y??G5cfl9F@kU8gC89eh_PB z9rLCHzf4OScsu?ov`<&keAB4qWJezIpThpeNOQl5{dl@tLpnlsCq_G-=;(&|=Bel; zAuOjSEz4e&Gd8lC@{cC9v8*AmZC~dh?RT5Xy&6U#3Ftwhj0i&;+>LUhp+g`CxC_2Q z%b8xhS{6hrMN{lMOk91t)?#~zS8?|GybUQynbKFB^x5TONNg{%^zlxJ3KTw~y{1GV ztK2(jUowv9AReQAO*GXuE!Vo6=pybH`Ym~4W=%%l&7)Ge!XKvG%vjN7 zSirx?U{&|V(x%%Tmvn0_7u|YqHTnYYPgD~MjR6bTaa|AB~hKG2@EzMJ>#1fk=dkja>6#;8Riz#nh_ld_ue{450fy|*C z#|islb&rl;+ILVi=aTeYlp%RRmrn_P5*Xg3Mw%SW`JL7J2Ak5oD*wUUjv2XucZo7_(NJSok@levysby)b`xoX7A}*#r)y*-2bFJAS%4ak7OM-*4-*_ zgq7lzz@efC;H4y=7LAQUep3>tvz;oVurs{lzCIRx!XGA2irJieF_{s^^R5G{)UTRn zwE)T#!+u8!l8MISo%nGg5(|L>0i|tLX1x8nI+>pv*+ zaPN%?6c&qBOEg;gfC}^;^auOOY^1KmPY`=3iC}-jM^(?x`#K9Wnsq|_N(xgw_hx#q5Uw(}=tsGFlH$ELtP9Da7IwT~^|b`O%F^N34$v75QfK))is z*im$WYZtT-l~8YRiaj4mHc3_nv?uuY?-R}Aj^p*PztldmmpOaF*%Y^OR!w!GvYP2uT!h39-~)WyNIrga$bVhYRM0u)q$<)j(&#V6TCz8 zEaPlVt@Q~M=?tKw%tYFF%0I+T_^)ZJ?vAQj>$2OhO!h0WpKMLEB@U0Q_Muaz*l=~F z@>6F|-E*rD-9T@qv50z@VIO2kw8(5z92Y>+b;#`^vXDCGP1i1Dpu3I5M#K{qbOc4o zBzO+bSK^^QOM>NDz52}wn;=|=R&?g8f~}cuhjI>cqWfm#&h0~~M1}T#G+EiF7yPiR3bKlrRpAxa>IsNjF7lHmTRDdKs32|4_zr ztn^yf2rFr{88oJ^wg#X99*CaC#v!xeA+Qj6gPkSLlN73jd_Y;i%;rAyNb%|`F7eNg zUfmb#R~BeN0^1{+&FU!Ffqo>%GE-P>%s8UIZJLp9nrb4AcI$QL1u}-aLwrEak1C5r zL-+AcK^qK%H7wl_?O9V1j8GSH0_a(=hgoXqs|Stq>=&Tnu1V-yjO9L6b%mE9z3^_7 zRO%LLzMJ?x$WnN$60p4EB+q3@DdfKfKHw`-0&i%pLW z`KGDnPnIhHfs|6e@(X=~LtaK_$FKvwQ?hM^TARkGjnoV6vDhl+8g?m>?^x+3j&A9X znJ+jN! zZkp88tb5?B#cC;CS*KYqnFCS8e8ylkW|{k0207lj)TDs7NAfi=E-F8k5!{zu0o>QG z)!4Lgx4%_ zy--h`>e>qpcEz9~~*|v90UC(u^HXq z`s$>+5?#xkE_f}p7CZ+og~Uz>*+$Hx_vSqbEKD9ETg41UV?n8_1@(k?I*i~2w2hoV zF_A7}621iMiLb&@d^tIY=E>?1w1~TUN*Vhw5MBo@g__~9&|<*s_VLdGW1;)bcX%Pi z#w-)prCpAeaUSD1poP5ght3|h{k8^ZDL$9H<>sLm;5zgW))(JTB$J;gBWN#~ExZ{2 ziBd04C2|1z2)qZsKn|d{eU@#gV;r=?IUCzg6|*19PURmCji&8!9kCxlo)Im;6Z3XU zclSx^A$AylaaP% z7^gQrlhiYN_)z;_N@TmgVS4)(EQx4wC`DMIA|-%KjtuOFrrQAEgQz>L&Di!)*8! zM0c3&7aW9rx#JTQf{&-&W|+dijg1QFL6^Dxvojqh#3^&$ayJ#<0X93zt=aYq)&l!!+cji6A!3xV4rU~e zcqU#(>kGPjgs9!Y_3DDoXzgu>2c(DhL#I$4xrBC=I-7o%N#&)nR&!VLuSMTX8pNFq z^#)+b4zxKw+Ll`Tng^I-tY>YT9ieD{>LlinP;mIbu!*d8v<14tWYRxakLpb7BHJLS z9e(Y^;YuQoS;17&$1^fGzMNQ^i@8n^pU^LYO^&qPwY>pO121j8t!d_k`gFrgi{4>} z29YzFRUv~$Er_tOhf?lAdl-K?R>)u;ZQN-Mb-L?r;X;UqL@_pd=y?G54L5^56R*Rx zBq9xun8q;J%1v$7TCg811*h1kra1E+bB28&EF})IYXZfi6|tLH%c$p}SgMuVij1<1 zuu2@YFxPp?wZ?T5^`S5FZ1a-voitxe>qv$dx*C1b!fw;&0v0RRngOkKPKRdz1Fe9K zVvn%B1sk3BSoxx7gXVQ@?Z3{djr(f zIS-x(_J)_al0h@{3*JWuxcw7`j@unNO)!VWz}L|&w02;!AzwS!DsrrYInF#_kbS7D zj@ysJPMzzL%Lcl42kemZxqBCDCSJ1c2ScMv zWAi*{Tn(iU`#kF*EHv1aI{h$<-SNix7Iq#t7Rl;JA-Ccn><= z`v1RzTxX7Ji0c=88k#`#L3bd1T)Fs0R%vMWQQg7=S!?Odlq|NM6$XDc!pcedLQ4{m z={yUO?rDHSjOZ}$sPUBTrd|3g*0YX*-u4hF{k-EcFbCR$#G%ifW5Ez`I=&t&b$PnB zpo^FzqBBN*kd0zk$#IO`9*>x{;Bw1db(3CW++}+RB4DvK0_eh67j-l`lUQeHG7NEi z0B49_1>UEOhA2=69EMFHyj@crYi$?NF6brXkZT8)!Yqk14{Z$yXHcj&S#17miq@WI zovMR$o~CH)WU$q}BL6u45D^kQWujlZO>G$LsD_V9Zc3*hFYO?JW1SQg)el_(Y&P-W zMyCzAg?OWbk~@dW2v>mBGjc)(SMgyPk-`sB#&dBUM|7j}tz z->;V0+jiL8-7rUIQ4(s<=w&@+iZ?rK9rzXR#mVzxulvsNddRH~c{v&Gc$P8l=TcPCE<->|tGiwuSBzm+?TNtO$i zP#aKuRsk^eyVpj^qw+qs)@w$Z@>4e6|bG5)Ul$RuIHI#pcSz!qcKp z;ut5zxZK3Hrdz8tKh@(+!RFo&hiYXXk4nqobAO;?u&uN<5hchfd+)DiVUS9#Y=#@A zt3nL#tP|XS*=74Z_#3`SIz8Sd5sScpS=@pk5C3>utoo+1hkAyoOxIPTHJ6$z9D4j4 zPmuN`c|EqwX@({Q(Ku{ZlCSw0;HiFtktt;>Y>a9 ziA^I}UFO)e?b!``%d9mCiYQI0;huFVIKZA~3PJjaesoWaor71yqrrQWe8D)*H&%h4 zHTInBAAFDYQFBv0uO(JD+wN<9;cjzpbM|43i&hN&vrIly#XP7U)Ht9@UirKJLN(eN zX}k$su}v@>)|~*yhd6qV41lTA&@0$RR=J>6=!`K&@0N_A_XB@wHnkfRgF8PdKX-p#+YS0 zsok$H(?_d4I!5Yr=1JB}do#RA*emzuAZ2<7UuLhP#&)?v-Pv}o?8LW!io=zCHRtui zp*5k*v_PiAUSpb}&aftfGoWp}j&M=Jt?WhF<bPQ9XzBr8#gnnw zzANLWCaW__eKH_jkTbm0c(?7@Pv+a%Uv}5Ybc10Pe`aoEU@UMRitM**m z;i~kX)USo5j|?+ttx-jZ7`j4rzU7K4%2DKMaE1}hf}is5vMOnL#9=n6Brln)La)^3DZ6mwfJCa?#+;|Cva1%C;H;NbKj;g`g{WqYDazVz&h8+uFg~|!78Z1abA!g@EuH1r?z)(ZBsScKiZnCeVlS+FBIS^6U~ok z_Soh=t9y7olN^$t^|{SjiMYRh_yZ3khS|P3i^)T4fs$hwbs2 z9Q}YfSA(@4RH)i-X?L4vn9c$RX%7jh6QuVGD&TQ&JN{S`Yj@ zSJA0RMHDg8ZaX-w9dD~Y*On>PTjCsgdp}@09)_#|Kij>$9?Sc%7h(m(AoqD8T9g-? z7_rG`kAFhYXV!bW(7xQ+OO%!0%}-!Y1huqxhO~yE4FTGnB5BWsfs2gHx|3Do>RLMs z&3}LaYalzZ@!}rsKdbUjw~5H)f%h?97D;+<_Z+0_#F!J-}zzNWBucX$vx+`c|X0j;qd@ zjKtiK2sspLyldEqL}QJrCAyd55qW!}yVHI)Fp8cRZL7J{vD(}WU1F@EjU=C8EJ6 zd#Us_{W;bfKPR(ferILsHmSzynw_@94G4{rA|4kPbg!FL{%F zi@pq5L@WqP3JS%xSk@WlYf4o->kQW@#xI|?oXLGEq95|^FzBqS84t1!BO2A@=CZ~k zn(NGq!e^wve|GlA;Aqw@P2!KdkG|hyDqGc;pkd5>UKVQ$mM7jElEobeLDpvDHq{q> zBJ`6$cuym8^KYaM_qxG!Fz3mmd++i&r_X3T*Lq$B*}eFiyl#70QjHPA1c69lQ(Dp8 z&who=Yu9UkVad$h+-%NynZ&;fdk1n7tTRp4HX6@Ei;)Gi-o86CiS(zw-zX!fNz5sc zJ#s2JX&Pc1+bFg8Un8tKzfTPY|C`n2cQlD1<&JpCo-H*1-1S>$d{ z5nc@ba>ST^80Wh0AmdRbml8EFR}%Ds$|P>GHU^Y+0|RdXfgM#XQ&l7F9|ZZ~_tV4D zM~gmSMeTj7dVe4Ftydwd&eIy_zCX9Jn#d6WSa`F?Msx=B)8MB%WXOfXh&`;M@}TV8 zsAH@WXBB0UeCMF;k#nHBjs*?w(rDuo*HmdlR&dX&Arf}EZtvf&Kf4tuzU?Y2&D%C50WzzW{Vo= zk@jZu82b6Fn=$TLI<2Oi-qGK7jy@_-&b%`s3Qasioh30O`%gV3zGE~QY6_b=&;WLaIgU+x8AuHoX#J?fln6D^TDKZV8 z$wNUKM@|~{S{e_nYc`h0RHT;Fe2*(1u7sc##uoN>TD6FsusiIn;4PUC^;M^<_u6hc zSyY95eBv3uUd%}phiko~-0Nm~kzlZWf$^ZWmkx8z_3F~gZ_M?SrL+hIS;8rs@yGYK z^iO2VRr6qy&6&((3d5saA!g5o?i-fgeXhT3$agyNsSJ07#5H{f;fJIb7h`GF>+o1?>QGW(pZXren|Ark%t zj)!~skul*c9cZ&iOeSbr5IyxvTvN&fzqPtkCD`}cB2__dsigg+-9|sbe?<)iRGd3e zErA1tGZ@E7DfA5Ng+@XX9`_D*Whm=O- z$=jU$YmWW+Q`Git#jk>v1Lj85ha1iFM1|O%KqS1~`xtX5dE0dZy+I~ZGd(}}vCvVt=@tXBGu$1kcEC7T#dYG}W`_e*?vOQ;J`3!DU8wGOgt$<_o{tejbh4-c1ZS*_ZOKTi2Z9 zvt4$TFQVRob&g}eBS*QL=s3<=oxC@8CRa`$#J?A2m3MfY_dh88&jr=T{Wx6E`h7=9 z*M@bbS4bxH1APZ21FPig!U_XZg_Ed@$m^7zj6&}v0iA-0lxMEtP8_-fCfFg<4RDsf zDWiX&ntNLKBdRP=$NG;K8<5I=tGrv>{w1ZTwj|-Nr>fJrhVqq`MoN&iRHYb+%niEB zMX2lP_vvC`ML@H+nDQ82>Li>vxCF46%1lSFmKbH1sR4Q7Z&9YOkE~$UI&rwy67!p? zF@<9aQ+|)CXlyLDbYMdIKI%oZ0ei;g1TKi2Df!K=VVq=L=dYG-mn@_ggB*|vvVi;c zXxkUd9q=XJkj_dI1&1VYqYu-HQ423qOrqNx@$a2Q1B>~UoVpnGJ@<@W3;7Yl(O~L( z!SJv}!GtHsZsy+d94V{um2l>v&uwz+U|X{_*7(Jw1|q4Ua#sHGjH>j^q#X8Udw(KG zoX@UMH~(?|SoHg0<=EO)t*6Wu*CH$u*I-@920AI)8`&?k+AEhoPyAh;DNd!oB@Tnt zrdw`4Daz=jCoDsWr@}s&?+1SBwJbB-W0J+k(udt$xDfnRJO9t0lHuh?s|MF^(dyuT z-ECHz5F>I9711RD&*QH~l=)PORlye|^GP2>>GT6~t#s2aQ=5K~eim54@Cz##`p@XO zy=MFU0ff3c*jLX>*x;7c*`5;J==-rR&IIQ)gyHctTAA=OsKhTO ze2$b$MgUvkvG6ko$2{4v#(2uO&+&^=AUc{AHDmSIn~`5ohpNRifEUjmqS^g7pkiI$^FzFDkkgSIfzXmsQh{06E)FZpMs#Kz1Fo)>*H>71yW#v*Z&gjI0@FMvm-Wf-RNu_?#v#*0>EL|j5KA(1 z4ESHm()v5i(RG{u=F}^jrz=a~>2xEdJGuvcVfh2T@JG{CUH+sVik~H}cKVwD>mFo2 zs&X9De^NH9QTtr7(0g3E@?UgTpU|V&H2o&c3`~Oq9qRg5Z9iMDR#()7HdeKTns$?G zsTMTEb>F_lvW2xKKBv2;E1bH)zdK%L_^ET*%t#Gvb#wK*)YZm1w85iW{FiZ(Ss!yl zcvI|l1IN{s{Gwac_`F5kezS36HBvL9DN$JpMp7E_3iK;D#4-#0DQ(MR530`lDvzS( zm@jJ>)>-IwSGjwxa-eRWt`T_0*b$yMIAh}%B56tvg0ey#0W z_3baEWr1lq){T6GWw>;@HN*ai^<+3KTGezzyo-jPcli64K zpxQCao%VCio^MqXi(`tjN5mWneV^D6aX^qwiz0*&pdPM&sSH;bTPM`r`tzm&Y&xJ~ z+Ge6z=xww&fzyWjP%~NvrFWM~lkvaifyR1kE#M2B1@=2h__&>qh{LFvVUdBd8zI9I zwg&jH?hp<_ZTzYgX`?%iDwIuGm4;GFZLor_Pk}348(k+z1=A#AC;8;>&R-pQl6Brq z{Mf9!fHlBeU@vTTCV(Fx#4k8)SJ*9oyU*v?-LZ!}i)p@$x3(L~X5CFSrQ>X~{_p9s z>}qk#XytXQm$Mn!LAk}==9Ln?IsI7H+=Sm=-O%k;v5jkcW{(3xoc@>+ZE&f@Uz2=; zdb>S4Qo1>Lj6BYLW&VikG?&zqwP!k;T7&C-?{IV|{-0uD@?<6I!ZNKa8J2lsL?K=Y1l4diRt z+Rb{Y;T;r87joFFaIetlIX-`>o^B3oyilAJ)+O7EWG(mPV2#T54y^NPv%Vp!v7>HO zU21cW4wa^leG_?5TX_=5?OwIiN6;{Hitf5~DEXE>mOY2>8MREFK;s}) zh=Tb(q$BG>$V}chQBTfooxG!}{dMD~+HsAy>p#?;ZVKs`tv+N5MTc>B$?hlChu3?a z=DqX(*Z(GWIj@?~*zon|Vq_I!a)n=qlT|KK|e*1V`nXL|M5@be=!?)g({7F{Dt zEv@QN`q@}FuR%UTLTkb#44;8f z=TbL|dEggykv7uB<^J}aozaptJNmznIn0yF#`+6&j~a?w+M74m_i9+*P}JI8UkQw* zEb~7W zE+guNr&T|+0k2J{ztn7OZm6%UAJACX(o@@EUx7~*uZ}DU>f<$wF_+xHO!ECE;jy=3 z(;Ydc!QdB9%?*RS+B1Gk<()QS2u_n^jx?q?%DDY+2oc)NiUg zQ?G66quy$phGD{paB0XxZynvCKQ`M)X9=Cw)%)Iz8#S!Mf=mldH>2jy@H!o7>cxRHLrx z*A&srZ0uS4PxbtU4&`lg1$2Td4c!sM7ApPd)_0;vEuUYHz$H%0G zAC!Ewj&ImkP1bRmIvX$5f3FFt9oXh$xC%~S6v-Sx-riF=RQgQHWzH9W()%*~lshMl z4u3_j@dv~wWfptiw*d;L;-uL|LY~jlxAZnATnOLpiv!H&9@QnaOBxq9I^EOD*1y^& zg*FdlGA>9jh1~Z(z)GjJQz^Wq0VBl-V-U_qeVw`3cwTl4oV8JsV85=|tnfBXzzrTf zl7sr@b`eFH1CkNAExh(s?aK!5hN9X(f4^5=s^@BwfI{jjzg=MuC4X29YIkZ8=ehrF zNpF_B)fM}SDDYbz`(kKW_r*+m|F)eiLHg+^$Q_W(=(jKBdDt?UduCNt=$=3kH^tV^ zulrqnq%yX4n=;H^N15%b46T*CVHFck$z1kPf3BZ`_0ipqi(o5>yS$FblbJf-89=Z0 z_btD)#YhFWGX8h3L8;5b`^$6buho|uE9!eTZ?7Lyd#-wY)ykT|9f;*Rx!U(s$bX_= zOd&CbNar-l`UhB8^YAfPH|!N=V=s!hoP9z*9-iOc)O1zWhQ9b(nld&?YCU`7Y zsvFZZxnXKc|N3XuiB(`la&@Mnr>PgNlB@^=#j98+i8**Di!bjlYvS}Hg0T#A2G+tz z4|$(`H3UO`ckXVwuY3gbW=s#lyNBoa#-0pY?U`vB*mS93Q)^TM@i((Fwj%BC*49;q zF$m_}JG{TIf;E5)#U|6P1*8S5_?O8LY$Un|i)BOv(sC6Mo|v=KvoW>fn7xW74-V_u zknI`c3fm{*SRXfCuYcM0xv5JHRQ{`UO{J~5mtF(=2?N6k|AU-v)O=zJ<-3?0wAQng z`U=m+yAeO>y`^Zj8~4Y_&hq-n3c3AX`hnmBJtDI>OL|?k!$L z2@o?w_lalIZ{ZSbAij|P%;!M*sKj5)I_>y|Q7y|&tEoQ0{d?r6-wsIF)6R z#RPithmtGtG-|k4zHFEG7sd^DQ!|eo%!m-b>OxPe
  • 7)t_yAs2PmR@XzdF$@voT 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

    Z%vqwk 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 z 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⋘4Hxd&|hK9M^R|>;hkNLtw#u9*ui%G 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/TinySoundFont/examples/minisdl_audio.c b/source/engine/thirdparty/TinySoundFont/examples/minisdl_audio.c new file mode 100644 index 0000000..e2e2d3f --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/minisdl_audio.c @@ -0,0 +1,19233 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2014 Sam Lantinga + + 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. +*/ + +// (Partial copyright from inline included files dsound.h, dinput.h) +/**************************************************************************** + * + * Copyright (C) 1996-2000 Microsoft Corporation. All Rights Reserved. + * + ****************************************************************************/ + +#if defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__) +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NONSTDC_NO_WARNINGS +#endif + +#include "minisdl_audio.h" +#ifndef _SDL_H +#define _SDL_H + +#ifndef _SDL_main_h +#define _SDL_main_h + +#ifndef SDL_MAIN_HANDLED +#if defined(__WIN32__) + +#define SDL_MAIN_AVAILABLE + +#elif defined(__WINRT__) + +#define SDL_MAIN_NEEDED + +#elif defined(__IPHONEOS__) + +#define SDL_MAIN_NEEDED + +#elif defined(__ANDROID__) + +#define SDL_MAIN_NEEDED + +#endif +#endif + +#ifdef __cplusplus +#define C_LINKAGE "C" +#else +#define C_LINKAGE +#endif + +#if defined(SDL_MAIN_NEEDED) || defined(SDL_MAIN_AVAILABLE) +#define main SDL_main +#endif + +extern C_LINKAGE int SDL_main(int argc, char *argv[]); + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif +#ifdef __cplusplus +extern "C" { +#endif + +extern DECLSPEC void SDLCALL SDL_SetMainReady(void); + +#ifdef __WIN32__ + +extern DECLSPEC int SDLCALL SDL_RegisterApp(char *name, Uint32 style, + void *hInst); +extern DECLSPEC void SDLCALL SDL_UnregisterApp(void); + +#endif + +#ifdef __WINRT__ + +extern DECLSPEC int SDLCALL SDL_WinRTRunApp(int (*mainFunction)(int, char **), void * xamlBackgroundPanel); + +#endif + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_assert_h +#define _SDL_assert_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef SDL_ASSERT_LEVEL +#ifdef SDL_DEFAULT_ASSERT_LEVEL +#define SDL_ASSERT_LEVEL SDL_DEFAULT_ASSERT_LEVEL +#elif defined(_DEBUG) || defined(DEBUG) || \ + (defined(__GNUC__) && !defined(__OPTIMIZE__)) +#define SDL_ASSERT_LEVEL 2 +#else +#define SDL_ASSERT_LEVEL 1 +#endif +#endif + +#if defined(_MSC_VER) + + extern void __cdecl __debugbreak(void); + #define SDL_TriggerBreakpoint() __debugbreak() +#elif (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) + #define SDL_TriggerBreakpoint() __asm__ __volatile__ ( "int $3\n\t" ) +#elif defined(HAVE_SIGNAL_H) + #include + #define SDL_TriggerBreakpoint() raise(SIGTRAP) +#else + + #define SDL_TriggerBreakpoint() +#endif + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define SDL_FUNCTION __func__ +#elif ((__GNUC__ >= 2) || defined(_MSC_VER)) +# define SDL_FUNCTION __FUNCTION__ +#else +# define SDL_FUNCTION "???" +#endif +#define SDL_FILE __FILE__ +#define SDL_LINE __LINE__ + +#ifdef _MSC_VER +#define SDL_NULL_WHILE_LOOP_CONDITION (-1 == __LINE__) +#else +#define SDL_NULL_WHILE_LOOP_CONDITION (0) +#endif + +#define SDL_disabled_assert(condition) \ + do { (void) sizeof ((condition)); } while (SDL_NULL_WHILE_LOOP_CONDITION) + +typedef enum +{ + SDL_ASSERTION_RETRY, + SDL_ASSERTION_BREAK, + SDL_ASSERTION_ABORT, + SDL_ASSERTION_IGNORE, + SDL_ASSERTION_ALWAYS_IGNORE +} SDL_assert_state; + +typedef struct SDL_assert_data +{ + int always_ignore; + unsigned int trigger_count; + const char *condition; + const char *filename; + int linenum; + const char *function; + const struct SDL_assert_data *next; +} SDL_assert_data; + +#if (SDL_ASSERT_LEVEL > 0) + +extern DECLSPEC SDL_assert_state SDLCALL SDL_ReportAssertion(SDL_assert_data *, + const char *, + const char *, int) +#if defined(__clang__) +#if __has_feature(attribute_analyzer_noreturn) + + __attribute__((analyzer_noreturn)) +#endif +#endif +; + +#define SDL_enabled_assert(condition) \ + do { \ + while ( !(condition) ) { \ + static struct SDL_assert_data assert_data = { \ + 0, 0, #condition, 0, 0, 0, 0 \ + }; \ + const SDL_assert_state state = SDL_ReportAssertion(&assert_data, \ + SDL_FUNCTION, \ + SDL_FILE, \ + SDL_LINE); \ + if (state == SDL_ASSERTION_RETRY) { \ + continue; \ + } else if (state == SDL_ASSERTION_BREAK) { \ + SDL_TriggerBreakpoint(); \ + } \ + break; \ + } \ + } while (SDL_NULL_WHILE_LOOP_CONDITION) + +#endif + +#if SDL_ASSERT_LEVEL == 0 +# define SDL_assert(condition) SDL_disabled_assert(condition) +# define SDL_assert_release(condition) SDL_disabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition) +#elif SDL_ASSERT_LEVEL == 1 +# define SDL_assert(condition) SDL_disabled_assert(condition) +# define SDL_assert_release(condition) SDL_enabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition) +#elif SDL_ASSERT_LEVEL == 2 +# define SDL_assert(condition) SDL_enabled_assert(condition) +# define SDL_assert_release(condition) SDL_enabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_disabled_assert(condition) +#elif SDL_ASSERT_LEVEL == 3 +# define SDL_assert(condition) SDL_enabled_assert(condition) +# define SDL_assert_release(condition) SDL_enabled_assert(condition) +# define SDL_assert_paranoid(condition) SDL_enabled_assert(condition) +#else +# error Unknown assertion level. +#endif + +#define SDL_assert_always(condition) SDL_enabled_assert(condition) + +typedef SDL_assert_state (SDLCALL *SDL_AssertionHandler)( + const SDL_assert_data* data, void* userdata); + +extern DECLSPEC void SDLCALL SDL_SetAssertionHandler( + SDL_AssertionHandler handler, + void *userdata); + +extern DECLSPEC SDL_AssertionHandler SDLCALL SDL_GetDefaultAssertionHandler(void); + +extern DECLSPEC SDL_AssertionHandler SDLCALL SDL_GetAssertionHandler(void **puserdata); + +extern DECLSPEC const SDL_assert_data * SDLCALL SDL_GetAssertionReport(void); + +extern DECLSPEC void SDLCALL SDL_ResetAssertionReport(void); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_clipboard_h +#define _SDL_clipboard_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern DECLSPEC int SDLCALL SDL_SetClipboardText(const char *text); + +extern DECLSPEC char * SDLCALL SDL_GetClipboardText(void); + +extern DECLSPEC SDL_bool SDLCALL SDL_HasClipboardText(void); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_cpuinfo_h +#define _SDL_cpuinfo_h + +#if defined(_MSC_VER) && (_MSC_VER >= 1500) && (defined(_M_IX86) || defined(_M_X64)) +#include +#ifndef _WIN64 +#define __MMX__ +#define __3dNOW__ +#endif +#define __SSE__ +#define __SSE2__ +#elif defined(__MINGW64_VERSION_MAJOR) +#include +#else +#ifdef __ALTIVEC__ +#if HAVE_ALTIVEC_H && !defined(__APPLE_ALTIVEC__) +#include +#undef pixel +#endif +#endif +#ifdef __MMX__ +#include +#endif +#ifdef __3dNOW__ +#include +#endif +#ifdef __SSE__ +#include +#endif +#ifdef __SSE2__ +#include +#endif +#endif + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define SDL_CACHELINE_SIZE 128 + +extern DECLSPEC int SDLCALL SDL_GetCPUCount(void); + +extern DECLSPEC int SDLCALL SDL_GetCPUCacheLineSize(void); + +extern DECLSPEC SDL_bool SDLCALL SDL_HasRDTSC(void); + +extern DECLSPEC SDL_bool SDLCALL SDL_HasAltiVec(void); + +extern DECLSPEC SDL_bool SDLCALL SDL_HasMMX(void); + +extern DECLSPEC SDL_bool SDLCALL SDL_Has3DNow(void); + +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE(void); + +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE2(void); + +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE3(void); + +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE41(void); + +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE42(void); + +extern DECLSPEC SDL_bool SDLCALL SDL_HasAVX(void); + +extern DECLSPEC int SDLCALL SDL_GetSystemRAM(void); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_events_h +#define _SDL_events_h + +#ifndef _SDL_video_h +#define _SDL_video_h + +#ifndef _SDL_pixels_h +#define _SDL_pixels_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define SDL_ALPHA_OPAQUE 255 +#define SDL_ALPHA_TRANSPARENT 0 + +enum +{ + SDL_PIXELTYPE_UNKNOWN, + SDL_PIXELTYPE_INDEX1, + SDL_PIXELTYPE_INDEX4, + SDL_PIXELTYPE_INDEX8, + SDL_PIXELTYPE_PACKED8, + SDL_PIXELTYPE_PACKED16, + SDL_PIXELTYPE_PACKED32, + SDL_PIXELTYPE_ARRAYU8, + SDL_PIXELTYPE_ARRAYU16, + SDL_PIXELTYPE_ARRAYU32, + SDL_PIXELTYPE_ARRAYF16, + SDL_PIXELTYPE_ARRAYF32 +}; + +enum +{ + SDL_BITMAPORDER_NONE, + SDL_BITMAPORDER_4321, + SDL_BITMAPORDER_1234 +}; + +enum +{ + SDL_PACKEDORDER_NONE, + SDL_PACKEDORDER_XRGB, + SDL_PACKEDORDER_RGBX, + SDL_PACKEDORDER_ARGB, + SDL_PACKEDORDER_RGBA, + SDL_PACKEDORDER_XBGR, + SDL_PACKEDORDER_BGRX, + SDL_PACKEDORDER_ABGR, + SDL_PACKEDORDER_BGRA +}; + +enum +{ + SDL_ARRAYORDER_NONE, + SDL_ARRAYORDER_RGB, + SDL_ARRAYORDER_RGBA, + SDL_ARRAYORDER_ARGB, + SDL_ARRAYORDER_BGR, + SDL_ARRAYORDER_BGRA, + SDL_ARRAYORDER_ABGR +}; + +enum +{ + SDL_PACKEDLAYOUT_NONE, + SDL_PACKEDLAYOUT_332, + SDL_PACKEDLAYOUT_4444, + SDL_PACKEDLAYOUT_1555, + SDL_PACKEDLAYOUT_5551, + SDL_PACKEDLAYOUT_565, + SDL_PACKEDLAYOUT_8888, + SDL_PACKEDLAYOUT_2101010, + SDL_PACKEDLAYOUT_1010102 +}; + +#define SDL_DEFINE_PIXELFOURCC(A, B, C, D) SDL_FOURCC(A, B, C, D) + +#define SDL_DEFINE_PIXELFORMAT(type, order, layout, bits, bytes) \ + ((1 << 28) | ((type) << 24) | ((order) << 20) | ((layout) << 16) | \ + ((bits) << 8) | ((bytes) << 0)) + +#define SDL_PIXELFLAG(X) (((X) >> 28) & 0x0F) +#define SDL_PIXELTYPE(X) (((X) >> 24) & 0x0F) +#define SDL_PIXELORDER(X) (((X) >> 20) & 0x0F) +#define SDL_PIXELLAYOUT(X) (((X) >> 16) & 0x0F) +#define SDL_BITSPERPIXEL(X) (((X) >> 8) & 0xFF) +#define SDL_BYTESPERPIXEL(X) \ + (SDL_ISPIXELFORMAT_FOURCC(X) ? \ + ((((X) == SDL_PIXELFORMAT_YUY2) || \ + ((X) == SDL_PIXELFORMAT_UYVY) || \ + ((X) == SDL_PIXELFORMAT_YVYU)) ? 2 : 1) : (((X) >> 0) & 0xFF)) + +#define SDL_ISPIXELFORMAT_INDEXED(format) \ + (!SDL_ISPIXELFORMAT_FOURCC(format) && \ + ((SDL_PIXELTYPE(format) == SDL_PIXELTYPE_INDEX1) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_INDEX4) || \ + (SDL_PIXELTYPE(format) == SDL_PIXELTYPE_INDEX8))) + +#define SDL_ISPIXELFORMAT_ALPHA(format) \ + (!SDL_ISPIXELFORMAT_FOURCC(format) && \ + ((SDL_PIXELORDER(format) == SDL_PACKEDORDER_ARGB) || \ + (SDL_PIXELORDER(format) == SDL_PACKEDORDER_RGBA) || \ + (SDL_PIXELORDER(format) == SDL_PACKEDORDER_ABGR) || \ + (SDL_PIXELORDER(format) == SDL_PACKEDORDER_BGRA))) + +#define SDL_ISPIXELFORMAT_FOURCC(format) \ + ((format) && (SDL_PIXELFLAG(format) != 1)) + +enum +{ + SDL_PIXELFORMAT_UNKNOWN, + SDL_PIXELFORMAT_INDEX1LSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX1, SDL_BITMAPORDER_4321, 0, + 1, 0), + SDL_PIXELFORMAT_INDEX1MSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX1, SDL_BITMAPORDER_1234, 0, + 1, 0), + SDL_PIXELFORMAT_INDEX4LSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX4, SDL_BITMAPORDER_4321, 0, + 4, 0), + SDL_PIXELFORMAT_INDEX4MSB = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX4, SDL_BITMAPORDER_1234, 0, + 4, 0), + SDL_PIXELFORMAT_INDEX8 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_INDEX8, 0, 0, 8, 1), + SDL_PIXELFORMAT_RGB332 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED8, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_332, 8, 1), + SDL_PIXELFORMAT_RGB444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_4444, 12, 2), + SDL_PIXELFORMAT_RGB555 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_1555, 15, 2), + SDL_PIXELFORMAT_BGR555 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XBGR, + SDL_PACKEDLAYOUT_1555, 15, 2), + SDL_PIXELFORMAT_ARGB4444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ARGB, + SDL_PACKEDLAYOUT_4444, 16, 2), + SDL_PIXELFORMAT_RGBA4444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_RGBA, + SDL_PACKEDLAYOUT_4444, 16, 2), + SDL_PIXELFORMAT_ABGR4444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ABGR, + SDL_PACKEDLAYOUT_4444, 16, 2), + SDL_PIXELFORMAT_BGRA4444 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_BGRA, + SDL_PACKEDLAYOUT_4444, 16, 2), + SDL_PIXELFORMAT_ARGB1555 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ARGB, + SDL_PACKEDLAYOUT_1555, 16, 2), + SDL_PIXELFORMAT_RGBA5551 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_RGBA, + SDL_PACKEDLAYOUT_5551, 16, 2), + SDL_PIXELFORMAT_ABGR1555 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_ABGR, + SDL_PACKEDLAYOUT_1555, 16, 2), + SDL_PIXELFORMAT_BGRA5551 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_BGRA, + SDL_PACKEDLAYOUT_5551, 16, 2), + SDL_PIXELFORMAT_RGB565 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_565, 16, 2), + SDL_PIXELFORMAT_BGR565 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED16, SDL_PACKEDORDER_XBGR, + SDL_PACKEDLAYOUT_565, 16, 2), + SDL_PIXELFORMAT_RGB24 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_ARRAYU8, SDL_ARRAYORDER_RGB, 0, + 24, 3), + SDL_PIXELFORMAT_BGR24 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_ARRAYU8, SDL_ARRAYORDER_BGR, 0, + 24, 3), + SDL_PIXELFORMAT_RGB888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_XRGB, + SDL_PACKEDLAYOUT_8888, 24, 4), + SDL_PIXELFORMAT_RGBX8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_RGBX, + SDL_PACKEDLAYOUT_8888, 24, 4), + SDL_PIXELFORMAT_BGR888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_XBGR, + SDL_PACKEDLAYOUT_8888, 24, 4), + SDL_PIXELFORMAT_BGRX8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_BGRX, + SDL_PACKEDLAYOUT_8888, 24, 4), + SDL_PIXELFORMAT_ARGB8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ARGB, + SDL_PACKEDLAYOUT_8888, 32, 4), + SDL_PIXELFORMAT_RGBA8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_RGBA, + SDL_PACKEDLAYOUT_8888, 32, 4), + SDL_PIXELFORMAT_ABGR8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ABGR, + SDL_PACKEDLAYOUT_8888, 32, 4), + SDL_PIXELFORMAT_BGRA8888 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_BGRA, + SDL_PACKEDLAYOUT_8888, 32, 4), + SDL_PIXELFORMAT_ARGB2101010 = + SDL_DEFINE_PIXELFORMAT(SDL_PIXELTYPE_PACKED32, SDL_PACKEDORDER_ARGB, + SDL_PACKEDLAYOUT_2101010, 32, 4), + + SDL_PIXELFORMAT_YV12 = + SDL_DEFINE_PIXELFOURCC('Y', 'V', '1', '2'), + SDL_PIXELFORMAT_IYUV = + SDL_DEFINE_PIXELFOURCC('I', 'Y', 'U', 'V'), + SDL_PIXELFORMAT_YUY2 = + SDL_DEFINE_PIXELFOURCC('Y', 'U', 'Y', '2'), + SDL_PIXELFORMAT_UYVY = + SDL_DEFINE_PIXELFOURCC('U', 'Y', 'V', 'Y'), + SDL_PIXELFORMAT_YVYU = + SDL_DEFINE_PIXELFOURCC('Y', 'V', 'Y', 'U') +}; + +typedef struct SDL_Color +{ + Uint8 r; + Uint8 g; + Uint8 b; + Uint8 a; +} SDL_Color; +#define SDL_Colour SDL_Color + +typedef struct SDL_Palette +{ + int ncolors; + SDL_Color *colors; + Uint32 version; + int refcount; +} SDL_Palette; + +typedef struct SDL_PixelFormat +{ + Uint32 format; + SDL_Palette *palette; + Uint8 BitsPerPixel; + Uint8 BytesPerPixel; + Uint8 padding[2]; + Uint32 Rmask; + Uint32 Gmask; + Uint32 Bmask; + Uint32 Amask; + Uint8 Rloss; + Uint8 Gloss; + Uint8 Bloss; + Uint8 Aloss; + Uint8 Rshift; + Uint8 Gshift; + Uint8 Bshift; + Uint8 Ashift; + int refcount; + struct SDL_PixelFormat *next; +} SDL_PixelFormat; + +extern DECLSPEC const char* SDLCALL SDL_GetPixelFormatName(Uint32 format); + +extern DECLSPEC SDL_bool SDLCALL SDL_PixelFormatEnumToMasks(Uint32 format, + int *bpp, + Uint32 * Rmask, + Uint32 * Gmask, + Uint32 * Bmask, + Uint32 * Amask); + +extern DECLSPEC Uint32 SDLCALL SDL_MasksToPixelFormatEnum(int bpp, + Uint32 Rmask, + Uint32 Gmask, + Uint32 Bmask, + Uint32 Amask); + +extern DECLSPEC SDL_PixelFormat * SDLCALL SDL_AllocFormat(Uint32 pixel_format); + +extern DECLSPEC void SDLCALL SDL_FreeFormat(SDL_PixelFormat *format); + +extern DECLSPEC SDL_Palette *SDLCALL SDL_AllocPalette(int ncolors); + +extern DECLSPEC int SDLCALL SDL_SetPixelFormatPalette(SDL_PixelFormat * format, + SDL_Palette *palette); + +extern DECLSPEC int SDLCALL SDL_SetPaletteColors(SDL_Palette * palette, + const SDL_Color * colors, + int firstcolor, int ncolors); + +extern DECLSPEC void SDLCALL SDL_FreePalette(SDL_Palette * palette); + +extern DECLSPEC Uint32 SDLCALL SDL_MapRGB(const SDL_PixelFormat * format, + Uint8 r, Uint8 g, Uint8 b); + +extern DECLSPEC Uint32 SDLCALL SDL_MapRGBA(const SDL_PixelFormat * format, + Uint8 r, Uint8 g, Uint8 b, + Uint8 a); + +extern DECLSPEC void SDLCALL SDL_GetRGB(Uint32 pixel, + const SDL_PixelFormat * format, + Uint8 * r, Uint8 * g, Uint8 * b); + +extern DECLSPEC void SDLCALL SDL_GetRGBA(Uint32 pixel, + const SDL_PixelFormat * format, + Uint8 * r, Uint8 * g, Uint8 * b, + Uint8 * a); + +extern DECLSPEC void SDLCALL SDL_CalculateGammaRamp(float gamma, Uint16 * ramp); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_rect_h +#define _SDL_rect_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SDL_Point +{ + int x; + int y; +} SDL_Point; + +typedef struct SDL_Rect +{ + int x, y; + int w, h; +} SDL_Rect; + +SDL_FORCE_INLINE SDL_bool SDL_RectEmpty(const SDL_Rect *r) +{ + return ((!r) || (r->w <= 0) || (r->h <= 0)) ? SDL_TRUE : SDL_FALSE; +} + +SDL_FORCE_INLINE SDL_bool SDL_RectEquals(const SDL_Rect *a, const SDL_Rect *b) +{ + return (a && b && (a->x == b->x) && (a->y == b->y) && + (a->w == b->w) && (a->h == b->h)) ? SDL_TRUE : SDL_FALSE; +} + +extern DECLSPEC SDL_bool SDLCALL SDL_HasIntersection(const SDL_Rect * A, + const SDL_Rect * B); + +extern DECLSPEC SDL_bool SDLCALL SDL_IntersectRect(const SDL_Rect * A, + const SDL_Rect * B, + SDL_Rect * result); + +extern DECLSPEC void SDLCALL SDL_UnionRect(const SDL_Rect * A, + const SDL_Rect * B, + SDL_Rect * result); + +extern DECLSPEC SDL_bool SDLCALL SDL_EnclosePoints(const SDL_Point * points, + int count, + const SDL_Rect * clip, + SDL_Rect * result); + +extern DECLSPEC SDL_bool SDLCALL SDL_IntersectRectAndLine(const SDL_Rect * + rect, int *X1, + int *Y1, int *X2, + int *Y2); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_surface_h +#define _SDL_surface_h + +#ifndef _SDL_blendmode_h +#define _SDL_blendmode_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum +{ + SDL_BLENDMODE_NONE = 0x00000000, + SDL_BLENDMODE_BLEND = 0x00000001, + SDL_BLENDMODE_ADD = 0x00000002, + SDL_BLENDMODE_MOD = 0x00000004 +} SDL_BlendMode; + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define SDL_SWSURFACE 0 +#define SDL_PREALLOC 0x00000001 +#define SDL_RLEACCEL 0x00000002 +#define SDL_DONTFREE 0x00000004 + +#define SDL_MUSTLOCK(S) (((S)->flags & SDL_RLEACCEL) != 0) + +typedef struct SDL_Surface +{ + Uint32 flags; + SDL_PixelFormat *format; + int w, h; + int pitch; + void *pixels; + + void *userdata; + + int locked; + void *lock_data; + + SDL_Rect clip_rect; + + struct SDL_BlitMap *map; + + int refcount; +} SDL_Surface; + +typedef int (*SDL_blit) (struct SDL_Surface * src, SDL_Rect * srcrect, + struct SDL_Surface * dst, SDL_Rect * dstrect); + +extern DECLSPEC SDL_Surface *SDLCALL SDL_CreateRGBSurface + (Uint32 flags, int width, int height, int depth, + Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); +extern DECLSPEC SDL_Surface *SDLCALL SDL_CreateRGBSurfaceFrom(void *pixels, + int width, + int height, + int depth, + int pitch, + Uint32 Rmask, + Uint32 Gmask, + Uint32 Bmask, + Uint32 Amask); +extern DECLSPEC void SDLCALL SDL_FreeSurface(SDL_Surface * surface); + +extern DECLSPEC int SDLCALL SDL_SetSurfacePalette(SDL_Surface * surface, + SDL_Palette * palette); + +extern DECLSPEC int SDLCALL SDL_LockSurface(SDL_Surface * surface); + +extern DECLSPEC void SDLCALL SDL_UnlockSurface(SDL_Surface * surface); + +extern DECLSPEC SDL_Surface *SDLCALL SDL_LoadBMP_RW(SDL_RWops * src, + int freesrc); + +#define SDL_LoadBMP(file) SDL_LoadBMP_RW(SDL_RWFromFile(file, "rb"), 1) + +extern DECLSPEC int SDLCALL SDL_SaveBMP_RW + (SDL_Surface * surface, SDL_RWops * dst, int freedst); + +#define SDL_SaveBMP(surface, file) \ + SDL_SaveBMP_RW(surface, SDL_RWFromFile(file, "wb"), 1) + +extern DECLSPEC int SDLCALL SDL_SetSurfaceRLE(SDL_Surface * surface, + int flag); + +extern DECLSPEC int SDLCALL SDL_SetColorKey(SDL_Surface * surface, + int flag, Uint32 key); + +extern DECLSPEC int SDLCALL SDL_GetColorKey(SDL_Surface * surface, + Uint32 * key); + +extern DECLSPEC int SDLCALL SDL_SetSurfaceColorMod(SDL_Surface * surface, + Uint8 r, Uint8 g, Uint8 b); + +extern DECLSPEC int SDLCALL SDL_GetSurfaceColorMod(SDL_Surface * surface, + Uint8 * r, Uint8 * g, + Uint8 * b); + +extern DECLSPEC int SDLCALL SDL_SetSurfaceAlphaMod(SDL_Surface * surface, + Uint8 alpha); + +extern DECLSPEC int SDLCALL SDL_GetSurfaceAlphaMod(SDL_Surface * surface, + Uint8 * alpha); + +extern DECLSPEC int SDLCALL SDL_SetSurfaceBlendMode(SDL_Surface * surface, + SDL_BlendMode blendMode); + +extern DECLSPEC int SDLCALL SDL_GetSurfaceBlendMode(SDL_Surface * surface, + SDL_BlendMode *blendMode); + +extern DECLSPEC SDL_bool SDLCALL SDL_SetClipRect(SDL_Surface * surface, + const SDL_Rect * rect); + +extern DECLSPEC void SDLCALL SDL_GetClipRect(SDL_Surface * surface, + SDL_Rect * rect); + +extern DECLSPEC SDL_Surface *SDLCALL SDL_ConvertSurface + (SDL_Surface * src, const SDL_PixelFormat * fmt, Uint32 flags); +extern DECLSPEC SDL_Surface *SDLCALL SDL_ConvertSurfaceFormat + (SDL_Surface * src, Uint32 pixel_format, Uint32 flags); + +extern DECLSPEC int SDLCALL SDL_ConvertPixels(int width, int height, + Uint32 src_format, + const void * src, int src_pitch, + Uint32 dst_format, + void * dst, int dst_pitch); + +extern DECLSPEC int SDLCALL SDL_FillRect + (SDL_Surface * dst, const SDL_Rect * rect, Uint32 color); +extern DECLSPEC int SDLCALL SDL_FillRects + (SDL_Surface * dst, const SDL_Rect * rects, int count, Uint32 color); + +#define SDL_BlitSurface SDL_UpperBlit + +extern DECLSPEC int SDLCALL SDL_UpperBlit + (SDL_Surface * src, const SDL_Rect * srcrect, + SDL_Surface * dst, SDL_Rect * dstrect); + +extern DECLSPEC int SDLCALL SDL_LowerBlit + (SDL_Surface * src, SDL_Rect * srcrect, + SDL_Surface * dst, SDL_Rect * dstrect); + +extern DECLSPEC int SDLCALL SDL_SoftStretch(SDL_Surface * src, + const SDL_Rect * srcrect, + SDL_Surface * dst, + const SDL_Rect * dstrect); + +#define SDL_BlitScaled SDL_UpperBlitScaled + +extern DECLSPEC int SDLCALL SDL_UpperBlitScaled + (SDL_Surface * src, const SDL_Rect * srcrect, + SDL_Surface * dst, SDL_Rect * dstrect); + +extern DECLSPEC int SDLCALL SDL_LowerBlitScaled + (SDL_Surface * src, SDL_Rect * srcrect, + SDL_Surface * dst, SDL_Rect * dstrect); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct +{ + Uint32 format; + int w; + int h; + int refresh_rate; + void *driverdata; +} SDL_DisplayMode; + +typedef struct SDL_Window SDL_Window; + +typedef enum +{ + SDL_WINDOW_FULLSCREEN = 0x00000001, + SDL_WINDOW_OPENGL = 0x00000002, + SDL_WINDOW_SHOWN = 0x00000004, + SDL_WINDOW_HIDDEN = 0x00000008, + SDL_WINDOW_BORDERLESS = 0x00000010, + SDL_WINDOW_RESIZABLE = 0x00000020, + SDL_WINDOW_MINIMIZED = 0x00000040, + SDL_WINDOW_MAXIMIZED = 0x00000080, + SDL_WINDOW_INPUT_GRABBED = 0x00000100, + SDL_WINDOW_INPUT_FOCUS = 0x00000200, + SDL_WINDOW_MOUSE_FOCUS = 0x00000400, + SDL_WINDOW_FULLSCREEN_DESKTOP = ( SDL_WINDOW_FULLSCREEN | 0x00001000 ), + SDL_WINDOW_FOREIGN = 0x00000800, + SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000 +} SDL_WindowFlags; + +#define SDL_WINDOWPOS_UNDEFINED_MASK 0x1FFF0000 +#define SDL_WINDOWPOS_UNDEFINED_DISPLAY(X) (SDL_WINDOWPOS_UNDEFINED_MASK|(X)) +#define SDL_WINDOWPOS_UNDEFINED SDL_WINDOWPOS_UNDEFINED_DISPLAY(0) +#define SDL_WINDOWPOS_ISUNDEFINED(X) \ + (((X)&0xFFFF0000) == SDL_WINDOWPOS_UNDEFINED_MASK) + +#define SDL_WINDOWPOS_CENTERED_MASK 0x2FFF0000 +#define SDL_WINDOWPOS_CENTERED_DISPLAY(X) (SDL_WINDOWPOS_CENTERED_MASK|(X)) +#define SDL_WINDOWPOS_CENTERED SDL_WINDOWPOS_CENTERED_DISPLAY(0) +#define SDL_WINDOWPOS_ISCENTERED(X) \ + (((X)&0xFFFF0000) == SDL_WINDOWPOS_CENTERED_MASK) + +typedef enum +{ + SDL_WINDOWEVENT_NONE, + SDL_WINDOWEVENT_SHOWN, + SDL_WINDOWEVENT_HIDDEN, + SDL_WINDOWEVENT_EXPOSED, + SDL_WINDOWEVENT_MOVED, + SDL_WINDOWEVENT_RESIZED, + SDL_WINDOWEVENT_SIZE_CHANGED, + SDL_WINDOWEVENT_MINIMIZED, + SDL_WINDOWEVENT_MAXIMIZED, + SDL_WINDOWEVENT_RESTORED, + SDL_WINDOWEVENT_ENTER, + SDL_WINDOWEVENT_LEAVE, + SDL_WINDOWEVENT_FOCUS_GAINED, + SDL_WINDOWEVENT_FOCUS_LOST, + SDL_WINDOWEVENT_CLOSE +} SDL_WindowEventID; + +typedef void *SDL_GLContext; + +typedef enum +{ + SDL_GL_RED_SIZE, + SDL_GL_GREEN_SIZE, + SDL_GL_BLUE_SIZE, + SDL_GL_ALPHA_SIZE, + SDL_GL_BUFFER_SIZE, + SDL_GL_DOUBLEBUFFER, + SDL_GL_DEPTH_SIZE, + SDL_GL_STENCIL_SIZE, + SDL_GL_ACCUM_RED_SIZE, + SDL_GL_ACCUM_GREEN_SIZE, + SDL_GL_ACCUM_BLUE_SIZE, + SDL_GL_ACCUM_ALPHA_SIZE, + SDL_GL_STEREO, + SDL_GL_MULTISAMPLEBUFFERS, + SDL_GL_MULTISAMPLESAMPLES, + SDL_GL_ACCELERATED_VISUAL, + SDL_GL_RETAINED_BACKING, + SDL_GL_CONTEXT_MAJOR_VERSION, + SDL_GL_CONTEXT_MINOR_VERSION, + SDL_GL_CONTEXT_EGL, + SDL_GL_CONTEXT_FLAGS, + SDL_GL_CONTEXT_PROFILE_MASK, + SDL_GL_SHARE_WITH_CURRENT_CONTEXT, + SDL_GL_FRAMEBUFFER_SRGB_CAPABLE +} SDL_GLattr; + +typedef enum +{ + SDL_GL_CONTEXT_PROFILE_CORE = 0x0001, + SDL_GL_CONTEXT_PROFILE_COMPATIBILITY = 0x0002, + SDL_GL_CONTEXT_PROFILE_ES = 0x0004 +} SDL_GLprofile; + +typedef enum +{ + SDL_GL_CONTEXT_DEBUG_FLAG = 0x0001, + SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG = 0x0002, + SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG = 0x0004, + SDL_GL_CONTEXT_RESET_ISOLATION_FLAG = 0x0008 +} SDL_GLcontextFlag; + +extern DECLSPEC int SDLCALL SDL_GetNumVideoDrivers(void); + +extern DECLSPEC const char *SDLCALL SDL_GetVideoDriver(int index); + +extern DECLSPEC int SDLCALL SDL_VideoInit(const char *driver_name); + +extern DECLSPEC void SDLCALL SDL_VideoQuit(void); + +extern DECLSPEC const char *SDLCALL SDL_GetCurrentVideoDriver(void); + +extern DECLSPEC int SDLCALL SDL_GetNumVideoDisplays(void); + +extern DECLSPEC const char * SDLCALL SDL_GetDisplayName(int displayIndex); + +extern DECLSPEC int SDLCALL SDL_GetDisplayBounds(int displayIndex, SDL_Rect * rect); + +extern DECLSPEC int SDLCALL SDL_GetNumDisplayModes(int displayIndex); + +extern DECLSPEC int SDLCALL SDL_GetDisplayMode(int displayIndex, int modeIndex, + SDL_DisplayMode * mode); + +extern DECLSPEC int SDLCALL SDL_GetDesktopDisplayMode(int displayIndex, SDL_DisplayMode * mode); + +extern DECLSPEC int SDLCALL SDL_GetCurrentDisplayMode(int displayIndex, SDL_DisplayMode * mode); + +extern DECLSPEC SDL_DisplayMode * SDLCALL SDL_GetClosestDisplayMode(int displayIndex, const SDL_DisplayMode * mode, SDL_DisplayMode * closest); + +extern DECLSPEC int SDLCALL SDL_GetWindowDisplayIndex(SDL_Window * window); + +extern DECLSPEC int SDLCALL SDL_SetWindowDisplayMode(SDL_Window * window, + const SDL_DisplayMode + * mode); + +extern DECLSPEC int SDLCALL SDL_GetWindowDisplayMode(SDL_Window * window, + SDL_DisplayMode * mode); + +extern DECLSPEC Uint32 SDLCALL SDL_GetWindowPixelFormat(SDL_Window * window); + +extern DECLSPEC SDL_Window * SDLCALL SDL_CreateWindow(const char *title, + int x, int y, int w, + int h, Uint32 flags); + +extern DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowFrom(const void *data); + +extern DECLSPEC Uint32 SDLCALL SDL_GetWindowID(SDL_Window * window); + +extern DECLSPEC SDL_Window * SDLCALL SDL_GetWindowFromID(Uint32 id); + +extern DECLSPEC Uint32 SDLCALL SDL_GetWindowFlags(SDL_Window * window); + +extern DECLSPEC void SDLCALL SDL_SetWindowTitle(SDL_Window * window, + const char *title); + +extern DECLSPEC const char *SDLCALL SDL_GetWindowTitle(SDL_Window * window); + +extern DECLSPEC void SDLCALL SDL_SetWindowIcon(SDL_Window * window, + SDL_Surface * icon); + +extern DECLSPEC void* SDLCALL SDL_SetWindowData(SDL_Window * window, + const char *name, + void *userdata); + +extern DECLSPEC void *SDLCALL SDL_GetWindowData(SDL_Window * window, + const char *name); + +extern DECLSPEC void SDLCALL SDL_SetWindowPosition(SDL_Window * window, + int x, int y); + +extern DECLSPEC void SDLCALL SDL_GetWindowPosition(SDL_Window * window, + int *x, int *y); + +extern DECLSPEC void SDLCALL SDL_SetWindowSize(SDL_Window * window, int w, + int h); + +extern DECLSPEC void SDLCALL SDL_GetWindowSize(SDL_Window * window, int *w, + int *h); + +extern DECLSPEC void SDLCALL SDL_SetWindowMinimumSize(SDL_Window * window, + int min_w, int min_h); + +extern DECLSPEC void SDLCALL SDL_GetWindowMinimumSize(SDL_Window * window, + int *w, int *h); + +extern DECLSPEC void SDLCALL SDL_SetWindowMaximumSize(SDL_Window * window, + int max_w, int max_h); + +extern DECLSPEC void SDLCALL SDL_GetWindowMaximumSize(SDL_Window * window, + int *w, int *h); + +extern DECLSPEC void SDLCALL SDL_SetWindowBordered(SDL_Window * window, + SDL_bool bordered); + +extern DECLSPEC void SDLCALL SDL_ShowWindow(SDL_Window * window); + +extern DECLSPEC void SDLCALL SDL_HideWindow(SDL_Window * window); + +extern DECLSPEC void SDLCALL SDL_RaiseWindow(SDL_Window * window); + +extern DECLSPEC void SDLCALL SDL_MaximizeWindow(SDL_Window * window); + +extern DECLSPEC void SDLCALL SDL_MinimizeWindow(SDL_Window * window); + +extern DECLSPEC void SDLCALL SDL_RestoreWindow(SDL_Window * window); + +extern DECLSPEC int SDLCALL SDL_SetWindowFullscreen(SDL_Window * window, + Uint32 flags); + +extern DECLSPEC SDL_Surface * SDLCALL SDL_GetWindowSurface(SDL_Window * window); + +extern DECLSPEC int SDLCALL SDL_UpdateWindowSurface(SDL_Window * window); + +extern DECLSPEC int SDLCALL SDL_UpdateWindowSurfaceRects(SDL_Window * window, + const SDL_Rect * rects, + int numrects); + +extern DECLSPEC void SDLCALL SDL_SetWindowGrab(SDL_Window * window, + SDL_bool grabbed); + +extern DECLSPEC SDL_bool SDLCALL SDL_GetWindowGrab(SDL_Window * window); + +extern DECLSPEC int SDLCALL SDL_SetWindowBrightness(SDL_Window * window, float brightness); + +extern DECLSPEC float SDLCALL SDL_GetWindowBrightness(SDL_Window * window); + +extern DECLSPEC int SDLCALL SDL_SetWindowGammaRamp(SDL_Window * window, + const Uint16 * red, + const Uint16 * green, + const Uint16 * blue); + +extern DECLSPEC int SDLCALL SDL_GetWindowGammaRamp(SDL_Window * window, + Uint16 * red, + Uint16 * green, + Uint16 * blue); + +extern DECLSPEC void SDLCALL SDL_DestroyWindow(SDL_Window * window); + +extern DECLSPEC SDL_bool SDLCALL SDL_IsScreenSaverEnabled(void); + +extern DECLSPEC void SDLCALL SDL_EnableScreenSaver(void); + +extern DECLSPEC void SDLCALL SDL_DisableScreenSaver(void); + +extern DECLSPEC int SDLCALL SDL_GL_LoadLibrary(const char *path); + +extern DECLSPEC void *SDLCALL SDL_GL_GetProcAddress(const char *proc); + +extern DECLSPEC void SDLCALL SDL_GL_UnloadLibrary(void); + +extern DECLSPEC SDL_bool SDLCALL SDL_GL_ExtensionSupported(const char + *extension); + +extern DECLSPEC void SDLCALL SDL_GL_ResetAttributes(void); + +extern DECLSPEC int SDLCALL SDL_GL_SetAttribute(SDL_GLattr attr, int value); + +extern DECLSPEC int SDLCALL SDL_GL_GetAttribute(SDL_GLattr attr, int *value); + +extern DECLSPEC SDL_GLContext SDLCALL SDL_GL_CreateContext(SDL_Window * + window); + +extern DECLSPEC int SDLCALL SDL_GL_MakeCurrent(SDL_Window * window, + SDL_GLContext context); + +extern DECLSPEC SDL_Window* SDLCALL SDL_GL_GetCurrentWindow(void); + +extern DECLSPEC SDL_GLContext SDLCALL SDL_GL_GetCurrentContext(void); + +extern DECLSPEC void SDLCALL SDL_GL_GetDrawableSize(SDL_Window * window, int *w, + int *h); + +extern DECLSPEC int SDLCALL SDL_GL_SetSwapInterval(int interval); + +extern DECLSPEC int SDLCALL SDL_GL_GetSwapInterval(void); + +extern DECLSPEC void SDLCALL SDL_GL_SwapWindow(SDL_Window * window); + +extern DECLSPEC void SDLCALL SDL_GL_DeleteContext(SDL_GLContext context); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_keyboard_h +#define _SDL_keyboard_h + +#ifndef _SDL_keycode_h +#define _SDL_keycode_h + +#ifndef _SDL_scancode_h +#define _SDL_scancode_h + +typedef enum +{ + SDL_SCANCODE_UNKNOWN = 0, + + SDL_SCANCODE_A = 4, + SDL_SCANCODE_B = 5, + SDL_SCANCODE_C = 6, + SDL_SCANCODE_D = 7, + SDL_SCANCODE_E = 8, + SDL_SCANCODE_F = 9, + SDL_SCANCODE_G = 10, + SDL_SCANCODE_H = 11, + SDL_SCANCODE_I = 12, + SDL_SCANCODE_J = 13, + SDL_SCANCODE_K = 14, + SDL_SCANCODE_L = 15, + SDL_SCANCODE_M = 16, + SDL_SCANCODE_N = 17, + SDL_SCANCODE_O = 18, + SDL_SCANCODE_P = 19, + SDL_SCANCODE_Q = 20, + SDL_SCANCODE_R = 21, + SDL_SCANCODE_S = 22, + SDL_SCANCODE_T = 23, + SDL_SCANCODE_U = 24, + SDL_SCANCODE_V = 25, + SDL_SCANCODE_W = 26, + SDL_SCANCODE_X = 27, + SDL_SCANCODE_Y = 28, + SDL_SCANCODE_Z = 29, + + SDL_SCANCODE_1 = 30, + SDL_SCANCODE_2 = 31, + SDL_SCANCODE_3 = 32, + SDL_SCANCODE_4 = 33, + SDL_SCANCODE_5 = 34, + SDL_SCANCODE_6 = 35, + SDL_SCANCODE_7 = 36, + SDL_SCANCODE_8 = 37, + SDL_SCANCODE_9 = 38, + SDL_SCANCODE_0 = 39, + + SDL_SCANCODE_RETURN = 40, + SDL_SCANCODE_ESCAPE = 41, + SDL_SCANCODE_BACKSPACE = 42, + SDL_SCANCODE_TAB = 43, + SDL_SCANCODE_SPACE = 44, + + SDL_SCANCODE_MINUS = 45, + SDL_SCANCODE_EQUALS = 46, + SDL_SCANCODE_LEFTBRACKET = 47, + SDL_SCANCODE_RIGHTBRACKET = 48, + SDL_SCANCODE_BACKSLASH = 49, + SDL_SCANCODE_NONUSHASH = 50, + SDL_SCANCODE_SEMICOLON = 51, + SDL_SCANCODE_APOSTROPHE = 52, + SDL_SCANCODE_GRAVE = 53, + SDL_SCANCODE_COMMA = 54, + SDL_SCANCODE_PERIOD = 55, + SDL_SCANCODE_SLASH = 56, + + SDL_SCANCODE_CAPSLOCK = 57, + + SDL_SCANCODE_F1 = 58, + SDL_SCANCODE_F2 = 59, + SDL_SCANCODE_F3 = 60, + SDL_SCANCODE_F4 = 61, + SDL_SCANCODE_F5 = 62, + SDL_SCANCODE_F6 = 63, + SDL_SCANCODE_F7 = 64, + SDL_SCANCODE_F8 = 65, + SDL_SCANCODE_F9 = 66, + SDL_SCANCODE_F10 = 67, + SDL_SCANCODE_F11 = 68, + SDL_SCANCODE_F12 = 69, + + SDL_SCANCODE_PRINTSCREEN = 70, + SDL_SCANCODE_SCROLLLOCK = 71, + SDL_SCANCODE_PAUSE = 72, + SDL_SCANCODE_INSERT = 73, + SDL_SCANCODE_HOME = 74, + SDL_SCANCODE_PAGEUP = 75, + SDL_SCANCODE_DELETE = 76, + SDL_SCANCODE_END = 77, + SDL_SCANCODE_PAGEDOWN = 78, + SDL_SCANCODE_RIGHT = 79, + SDL_SCANCODE_LEFT = 80, + SDL_SCANCODE_DOWN = 81, + SDL_SCANCODE_UP = 82, + + SDL_SCANCODE_NUMLOCKCLEAR = 83, + SDL_SCANCODE_KP_DIVIDE = 84, + SDL_SCANCODE_KP_MULTIPLY = 85, + SDL_SCANCODE_KP_MINUS = 86, + SDL_SCANCODE_KP_PLUS = 87, + SDL_SCANCODE_KP_ENTER = 88, + SDL_SCANCODE_KP_1 = 89, + SDL_SCANCODE_KP_2 = 90, + SDL_SCANCODE_KP_3 = 91, + SDL_SCANCODE_KP_4 = 92, + SDL_SCANCODE_KP_5 = 93, + SDL_SCANCODE_KP_6 = 94, + SDL_SCANCODE_KP_7 = 95, + SDL_SCANCODE_KP_8 = 96, + SDL_SCANCODE_KP_9 = 97, + SDL_SCANCODE_KP_0 = 98, + SDL_SCANCODE_KP_PERIOD = 99, + + SDL_SCANCODE_NONUSBACKSLASH = 100, + SDL_SCANCODE_APPLICATION = 101, + SDL_SCANCODE_POWER = 102, + SDL_SCANCODE_KP_EQUALS = 103, + SDL_SCANCODE_F13 = 104, + SDL_SCANCODE_F14 = 105, + SDL_SCANCODE_F15 = 106, + SDL_SCANCODE_F16 = 107, + SDL_SCANCODE_F17 = 108, + SDL_SCANCODE_F18 = 109, + SDL_SCANCODE_F19 = 110, + SDL_SCANCODE_F20 = 111, + SDL_SCANCODE_F21 = 112, + SDL_SCANCODE_F22 = 113, + SDL_SCANCODE_F23 = 114, + SDL_SCANCODE_F24 = 115, + SDL_SCANCODE_EXECUTE = 116, + SDL_SCANCODE_HELP = 117, + SDL_SCANCODE_MENU = 118, + SDL_SCANCODE_SELECT = 119, + SDL_SCANCODE_STOP = 120, + SDL_SCANCODE_AGAIN = 121, + SDL_SCANCODE_UNDO = 122, + SDL_SCANCODE_CUT = 123, + SDL_SCANCODE_COPY = 124, + SDL_SCANCODE_PASTE = 125, + SDL_SCANCODE_FIND = 126, + SDL_SCANCODE_MUTE = 127, + SDL_SCANCODE_VOLUMEUP = 128, + SDL_SCANCODE_VOLUMEDOWN = 129, + + SDL_SCANCODE_KP_COMMA = 133, + SDL_SCANCODE_KP_EQUALSAS400 = 134, + + SDL_SCANCODE_INTERNATIONAL1 = 135, + SDL_SCANCODE_INTERNATIONAL2 = 136, + SDL_SCANCODE_INTERNATIONAL3 = 137, + SDL_SCANCODE_INTERNATIONAL4 = 138, + SDL_SCANCODE_INTERNATIONAL5 = 139, + SDL_SCANCODE_INTERNATIONAL6 = 140, + SDL_SCANCODE_INTERNATIONAL7 = 141, + SDL_SCANCODE_INTERNATIONAL8 = 142, + SDL_SCANCODE_INTERNATIONAL9 = 143, + SDL_SCANCODE_LANG1 = 144, + SDL_SCANCODE_LANG2 = 145, + SDL_SCANCODE_LANG3 = 146, + SDL_SCANCODE_LANG4 = 147, + SDL_SCANCODE_LANG5 = 148, + SDL_SCANCODE_LANG6 = 149, + SDL_SCANCODE_LANG7 = 150, + SDL_SCANCODE_LANG8 = 151, + SDL_SCANCODE_LANG9 = 152, + + SDL_SCANCODE_ALTERASE = 153, + SDL_SCANCODE_SYSREQ = 154, + SDL_SCANCODE_CANCEL = 155, + SDL_SCANCODE_CLEAR = 156, + SDL_SCANCODE_PRIOR = 157, + SDL_SCANCODE_RETURN2 = 158, + SDL_SCANCODE_SEPARATOR = 159, + SDL_SCANCODE_OUT = 160, + SDL_SCANCODE_OPER = 161, + SDL_SCANCODE_CLEARAGAIN = 162, + SDL_SCANCODE_CRSEL = 163, + SDL_SCANCODE_EXSEL = 164, + + SDL_SCANCODE_KP_00 = 176, + SDL_SCANCODE_KP_000 = 177, + SDL_SCANCODE_THOUSANDSSEPARATOR = 178, + SDL_SCANCODE_DECIMALSEPARATOR = 179, + SDL_SCANCODE_CURRENCYUNIT = 180, + SDL_SCANCODE_CURRENCYSUBUNIT = 181, + SDL_SCANCODE_KP_LEFTPAREN = 182, + SDL_SCANCODE_KP_RIGHTPAREN = 183, + SDL_SCANCODE_KP_LEFTBRACE = 184, + SDL_SCANCODE_KP_RIGHTBRACE = 185, + SDL_SCANCODE_KP_TAB = 186, + SDL_SCANCODE_KP_BACKSPACE = 187, + SDL_SCANCODE_KP_A = 188, + SDL_SCANCODE_KP_B = 189, + SDL_SCANCODE_KP_C = 190, + SDL_SCANCODE_KP_D = 191, + SDL_SCANCODE_KP_E = 192, + SDL_SCANCODE_KP_F = 193, + SDL_SCANCODE_KP_XOR = 194, + SDL_SCANCODE_KP_POWER = 195, + SDL_SCANCODE_KP_PERCENT = 196, + SDL_SCANCODE_KP_LESS = 197, + SDL_SCANCODE_KP_GREATER = 198, + SDL_SCANCODE_KP_AMPERSAND = 199, + SDL_SCANCODE_KP_DBLAMPERSAND = 200, + SDL_SCANCODE_KP_VERTICALBAR = 201, + SDL_SCANCODE_KP_DBLVERTICALBAR = 202, + SDL_SCANCODE_KP_COLON = 203, + SDL_SCANCODE_KP_HASH = 204, + SDL_SCANCODE_KP_SPACE = 205, + SDL_SCANCODE_KP_AT = 206, + SDL_SCANCODE_KP_EXCLAM = 207, + SDL_SCANCODE_KP_MEMSTORE = 208, + SDL_SCANCODE_KP_MEMRECALL = 209, + SDL_SCANCODE_KP_MEMCLEAR = 210, + SDL_SCANCODE_KP_MEMADD = 211, + SDL_SCANCODE_KP_MEMSUBTRACT = 212, + SDL_SCANCODE_KP_MEMMULTIPLY = 213, + SDL_SCANCODE_KP_MEMDIVIDE = 214, + SDL_SCANCODE_KP_PLUSMINUS = 215, + SDL_SCANCODE_KP_CLEAR = 216, + SDL_SCANCODE_KP_CLEARENTRY = 217, + SDL_SCANCODE_KP_BINARY = 218, + SDL_SCANCODE_KP_OCTAL = 219, + SDL_SCANCODE_KP_DECIMAL = 220, + SDL_SCANCODE_KP_HEXADECIMAL = 221, + + SDL_SCANCODE_LCTRL = 224, + SDL_SCANCODE_LSHIFT = 225, + SDL_SCANCODE_LALT = 226, + SDL_SCANCODE_LGUI = 227, + SDL_SCANCODE_RCTRL = 228, + SDL_SCANCODE_RSHIFT = 229, + SDL_SCANCODE_RALT = 230, + SDL_SCANCODE_RGUI = 231, + + SDL_SCANCODE_MODE = 257, + + SDL_SCANCODE_AUDIONEXT = 258, + SDL_SCANCODE_AUDIOPREV = 259, + SDL_SCANCODE_AUDIOSTOP = 260, + SDL_SCANCODE_AUDIOPLAY = 261, + SDL_SCANCODE_AUDIOMUTE = 262, + SDL_SCANCODE_MEDIASELECT = 263, + SDL_SCANCODE_WWW = 264, + SDL_SCANCODE_MAIL = 265, + SDL_SCANCODE_CALCULATOR = 266, + SDL_SCANCODE_COMPUTER = 267, + SDL_SCANCODE_AC_SEARCH = 268, + SDL_SCANCODE_AC_HOME = 269, + SDL_SCANCODE_AC_BACK = 270, + SDL_SCANCODE_AC_FORWARD = 271, + SDL_SCANCODE_AC_STOP = 272, + SDL_SCANCODE_AC_REFRESH = 273, + SDL_SCANCODE_AC_BOOKMARKS = 274, + + SDL_SCANCODE_BRIGHTNESSDOWN = 275, + SDL_SCANCODE_BRIGHTNESSUP = 276, + SDL_SCANCODE_DISPLAYSWITCH = 277, + SDL_SCANCODE_KBDILLUMTOGGLE = 278, + SDL_SCANCODE_KBDILLUMDOWN = 279, + SDL_SCANCODE_KBDILLUMUP = 280, + SDL_SCANCODE_EJECT = 281, + SDL_SCANCODE_SLEEP = 282, + + SDL_SCANCODE_APP1 = 283, + SDL_SCANCODE_APP2 = 284, + + SDL_NUM_SCANCODES = 512 +} SDL_Scancode; + +#endif + +typedef Sint32 SDL_Keycode; + +#define SDLK_SCANCODE_MASK (1<<30) +#define SDL_SCANCODE_TO_KEYCODE(X) (X | SDLK_SCANCODE_MASK) + +enum +{ + SDLK_UNKNOWN = 0, + + SDLK_RETURN = '\r', + SDLK_ESCAPE = '\033', + SDLK_BACKSPACE = '\b', + SDLK_TAB = '\t', + SDLK_SPACE = ' ', + SDLK_EXCLAIM = '!', + SDLK_QUOTEDBL = '"', + SDLK_HASH = '#', + SDLK_PERCENT = '%', + SDLK_DOLLAR = '$', + SDLK_AMPERSAND = '&', + SDLK_QUOTE = '\'', + SDLK_LEFTPAREN = '(', + SDLK_RIGHTPAREN = ')', + SDLK_ASTERISK = '*', + SDLK_PLUS = '+', + SDLK_COMMA = ',', + SDLK_MINUS = '-', + SDLK_PERIOD = '.', + SDLK_SLASH = '/', + SDLK_0 = '0', + SDLK_1 = '1', + SDLK_2 = '2', + SDLK_3 = '3', + SDLK_4 = '4', + SDLK_5 = '5', + SDLK_6 = '6', + SDLK_7 = '7', + SDLK_8 = '8', + SDLK_9 = '9', + SDLK_COLON = ':', + SDLK_SEMICOLON = ';', + SDLK_LESS = '<', + SDLK_EQUALS = '=', + SDLK_GREATER = '>', + SDLK_QUESTION = '?', + SDLK_AT = '@', + + SDLK_LEFTBRACKET = '[', + SDLK_BACKSLASH = '\\', + SDLK_RIGHTBRACKET = ']', + SDLK_CARET = '^', + SDLK_UNDERSCORE = '_', + SDLK_BACKQUOTE = '`', + SDLK_a = 'a', + SDLK_b = 'b', + SDLK_c = 'c', + SDLK_d = 'd', + SDLK_e = 'e', + SDLK_f = 'f', + SDLK_g = 'g', + SDLK_h = 'h', + SDLK_i = 'i', + SDLK_j = 'j', + SDLK_k = 'k', + SDLK_l = 'l', + SDLK_m = 'm', + SDLK_n = 'n', + SDLK_o = 'o', + SDLK_p = 'p', + SDLK_q = 'q', + SDLK_r = 'r', + SDLK_s = 's', + SDLK_t = 't', + SDLK_u = 'u', + SDLK_v = 'v', + SDLK_w = 'w', + SDLK_x = 'x', + SDLK_y = 'y', + SDLK_z = 'z', + + SDLK_CAPSLOCK = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CAPSLOCK), + + SDLK_F1 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F1), + SDLK_F2 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F2), + SDLK_F3 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F3), + SDLK_F4 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F4), + SDLK_F5 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F5), + SDLK_F6 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F6), + SDLK_F7 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F7), + SDLK_F8 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F8), + SDLK_F9 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F9), + SDLK_F10 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F10), + SDLK_F11 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F11), + SDLK_F12 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F12), + + SDLK_PRINTSCREEN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PRINTSCREEN), + SDLK_SCROLLLOCK = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SCROLLLOCK), + SDLK_PAUSE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PAUSE), + SDLK_INSERT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_INSERT), + SDLK_HOME = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_HOME), + SDLK_PAGEUP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PAGEUP), + SDLK_DELETE = '\177', + SDLK_END = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_END), + SDLK_PAGEDOWN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PAGEDOWN), + SDLK_RIGHT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RIGHT), + SDLK_LEFT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LEFT), + SDLK_DOWN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_DOWN), + SDLK_UP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_UP), + + SDLK_NUMLOCKCLEAR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_NUMLOCKCLEAR), + SDLK_KP_DIVIDE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_DIVIDE), + SDLK_KP_MULTIPLY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MULTIPLY), + SDLK_KP_MINUS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MINUS), + SDLK_KP_PLUS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_PLUS), + SDLK_KP_ENTER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_ENTER), + SDLK_KP_1 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_1), + SDLK_KP_2 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_2), + SDLK_KP_3 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_3), + SDLK_KP_4 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_4), + SDLK_KP_5 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_5), + SDLK_KP_6 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_6), + SDLK_KP_7 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_7), + SDLK_KP_8 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_8), + SDLK_KP_9 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_9), + SDLK_KP_0 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_0), + SDLK_KP_PERIOD = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_PERIOD), + + SDLK_APPLICATION = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_APPLICATION), + SDLK_POWER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_POWER), + SDLK_KP_EQUALS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_EQUALS), + SDLK_F13 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F13), + SDLK_F14 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F14), + SDLK_F15 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F15), + SDLK_F16 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F16), + SDLK_F17 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F17), + SDLK_F18 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F18), + SDLK_F19 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F19), + SDLK_F20 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F20), + SDLK_F21 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F21), + SDLK_F22 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F22), + SDLK_F23 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F23), + SDLK_F24 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_F24), + SDLK_EXECUTE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_EXECUTE), + SDLK_HELP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_HELP), + SDLK_MENU = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MENU), + SDLK_SELECT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SELECT), + SDLK_STOP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_STOP), + SDLK_AGAIN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AGAIN), + SDLK_UNDO = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_UNDO), + SDLK_CUT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CUT), + SDLK_COPY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_COPY), + SDLK_PASTE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PASTE), + SDLK_FIND = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_FIND), + SDLK_MUTE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MUTE), + SDLK_VOLUMEUP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_VOLUMEUP), + SDLK_VOLUMEDOWN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_VOLUMEDOWN), + SDLK_KP_COMMA = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_COMMA), + SDLK_KP_EQUALSAS400 = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_EQUALSAS400), + + SDLK_ALTERASE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_ALTERASE), + SDLK_SYSREQ = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SYSREQ), + SDLK_CANCEL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CANCEL), + SDLK_CLEAR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CLEAR), + SDLK_PRIOR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_PRIOR), + SDLK_RETURN2 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RETURN2), + SDLK_SEPARATOR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SEPARATOR), + SDLK_OUT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_OUT), + SDLK_OPER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_OPER), + SDLK_CLEARAGAIN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CLEARAGAIN), + SDLK_CRSEL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CRSEL), + SDLK_EXSEL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_EXSEL), + + SDLK_KP_00 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_00), + SDLK_KP_000 = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_000), + SDLK_THOUSANDSSEPARATOR = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_THOUSANDSSEPARATOR), + SDLK_DECIMALSEPARATOR = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_DECIMALSEPARATOR), + SDLK_CURRENCYUNIT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CURRENCYUNIT), + SDLK_CURRENCYSUBUNIT = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CURRENCYSUBUNIT), + SDLK_KP_LEFTPAREN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_LEFTPAREN), + SDLK_KP_RIGHTPAREN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_RIGHTPAREN), + SDLK_KP_LEFTBRACE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_LEFTBRACE), + SDLK_KP_RIGHTBRACE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_RIGHTBRACE), + SDLK_KP_TAB = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_TAB), + SDLK_KP_BACKSPACE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_BACKSPACE), + SDLK_KP_A = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_A), + SDLK_KP_B = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_B), + SDLK_KP_C = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_C), + SDLK_KP_D = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_D), + SDLK_KP_E = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_E), + SDLK_KP_F = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_F), + SDLK_KP_XOR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_XOR), + SDLK_KP_POWER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_POWER), + SDLK_KP_PERCENT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_PERCENT), + SDLK_KP_LESS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_LESS), + SDLK_KP_GREATER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_GREATER), + SDLK_KP_AMPERSAND = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_AMPERSAND), + SDLK_KP_DBLAMPERSAND = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_DBLAMPERSAND), + SDLK_KP_VERTICALBAR = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_VERTICALBAR), + SDLK_KP_DBLVERTICALBAR = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_DBLVERTICALBAR), + SDLK_KP_COLON = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_COLON), + SDLK_KP_HASH = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_HASH), + SDLK_KP_SPACE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_SPACE), + SDLK_KP_AT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_AT), + SDLK_KP_EXCLAM = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_EXCLAM), + SDLK_KP_MEMSTORE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMSTORE), + SDLK_KP_MEMRECALL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMRECALL), + SDLK_KP_MEMCLEAR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMCLEAR), + SDLK_KP_MEMADD = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMADD), + SDLK_KP_MEMSUBTRACT = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMSUBTRACT), + SDLK_KP_MEMMULTIPLY = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMMULTIPLY), + SDLK_KP_MEMDIVIDE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_MEMDIVIDE), + SDLK_KP_PLUSMINUS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_PLUSMINUS), + SDLK_KP_CLEAR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_CLEAR), + SDLK_KP_CLEARENTRY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_CLEARENTRY), + SDLK_KP_BINARY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_BINARY), + SDLK_KP_OCTAL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_OCTAL), + SDLK_KP_DECIMAL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_DECIMAL), + SDLK_KP_HEXADECIMAL = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KP_HEXADECIMAL), + + SDLK_LCTRL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LCTRL), + SDLK_LSHIFT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LSHIFT), + SDLK_LALT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LALT), + SDLK_LGUI = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_LGUI), + SDLK_RCTRL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RCTRL), + SDLK_RSHIFT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RSHIFT), + SDLK_RALT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RALT), + SDLK_RGUI = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_RGUI), + + SDLK_MODE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MODE), + + SDLK_AUDIONEXT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIONEXT), + SDLK_AUDIOPREV = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIOPREV), + SDLK_AUDIOSTOP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIOSTOP), + SDLK_AUDIOPLAY = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIOPLAY), + SDLK_AUDIOMUTE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AUDIOMUTE), + SDLK_MEDIASELECT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MEDIASELECT), + SDLK_WWW = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_WWW), + SDLK_MAIL = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MAIL), + SDLK_CALCULATOR = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_CALCULATOR), + SDLK_COMPUTER = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_COMPUTER), + SDLK_AC_SEARCH = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_SEARCH), + SDLK_AC_HOME = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_HOME), + SDLK_AC_BACK = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_BACK), + SDLK_AC_FORWARD = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_FORWARD), + SDLK_AC_STOP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_STOP), + SDLK_AC_REFRESH = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_REFRESH), + SDLK_AC_BOOKMARKS = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_AC_BOOKMARKS), + + SDLK_BRIGHTNESSDOWN = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_BRIGHTNESSDOWN), + SDLK_BRIGHTNESSUP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_BRIGHTNESSUP), + SDLK_DISPLAYSWITCH = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_DISPLAYSWITCH), + SDLK_KBDILLUMTOGGLE = + SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KBDILLUMTOGGLE), + SDLK_KBDILLUMDOWN = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KBDILLUMDOWN), + SDLK_KBDILLUMUP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_KBDILLUMUP), + SDLK_EJECT = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_EJECT), + SDLK_SLEEP = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_SLEEP) +}; + +typedef enum +{ + KMOD_NONE = 0x0000, + KMOD_LSHIFT = 0x0001, + KMOD_RSHIFT = 0x0002, + KMOD_LCTRL = 0x0040, + KMOD_RCTRL = 0x0080, + KMOD_LALT = 0x0100, + KMOD_RALT = 0x0200, + KMOD_LGUI = 0x0400, + KMOD_RGUI = 0x0800, + KMOD_NUM = 0x1000, + KMOD_CAPS = 0x2000, + KMOD_MODE = 0x4000, + KMOD_RESERVED = 0x8000 +} SDL_Keymod; + +#define KMOD_CTRL (KMOD_LCTRL|KMOD_RCTRL) +#define KMOD_SHIFT (KMOD_LSHIFT|KMOD_RSHIFT) +#define KMOD_ALT (KMOD_LALT|KMOD_RALT) +#define KMOD_GUI (KMOD_LGUI|KMOD_RGUI) + +#endif + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SDL_Keysym +{ + SDL_Scancode scancode; + SDL_Keycode sym; + Uint16 mod; + Uint32 unused; +} SDL_Keysym; + +extern DECLSPEC SDL_Window * SDLCALL SDL_GetKeyboardFocus(void); + +extern DECLSPEC const Uint8 *SDLCALL SDL_GetKeyboardState(int *numkeys); + +extern DECLSPEC SDL_Keymod SDLCALL SDL_GetModState(void); + +extern DECLSPEC void SDLCALL SDL_SetModState(SDL_Keymod modstate); + +extern DECLSPEC SDL_Keycode SDLCALL SDL_GetKeyFromScancode(SDL_Scancode scancode); + +extern DECLSPEC SDL_Scancode SDLCALL SDL_GetScancodeFromKey(SDL_Keycode key); + +extern DECLSPEC const char *SDLCALL SDL_GetScancodeName(SDL_Scancode scancode); + +extern DECLSPEC SDL_Scancode SDLCALL SDL_GetScancodeFromName(const char *name); + +extern DECLSPEC const char *SDLCALL SDL_GetKeyName(SDL_Keycode key); + +extern DECLSPEC SDL_Keycode SDLCALL SDL_GetKeyFromName(const char *name); + +extern DECLSPEC void SDLCALL SDL_StartTextInput(void); + +extern DECLSPEC SDL_bool SDLCALL SDL_IsTextInputActive(void); + +extern DECLSPEC void SDLCALL SDL_StopTextInput(void); + +extern DECLSPEC void SDLCALL SDL_SetTextInputRect(SDL_Rect *rect); + +extern DECLSPEC SDL_bool SDLCALL SDL_HasScreenKeyboardSupport(void); + +extern DECLSPEC SDL_bool SDLCALL SDL_IsScreenKeyboardShown(SDL_Window *window); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_mouse_h +#define _SDL_mouse_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SDL_Cursor SDL_Cursor; + +typedef enum +{ + SDL_SYSTEM_CURSOR_ARROW, + SDL_SYSTEM_CURSOR_IBEAM, + SDL_SYSTEM_CURSOR_WAIT, + SDL_SYSTEM_CURSOR_CROSSHAIR, + SDL_SYSTEM_CURSOR_WAITARROW, + SDL_SYSTEM_CURSOR_SIZENWSE, + SDL_SYSTEM_CURSOR_SIZENESW, + SDL_SYSTEM_CURSOR_SIZEWE, + SDL_SYSTEM_CURSOR_SIZENS, + SDL_SYSTEM_CURSOR_SIZEALL, + SDL_SYSTEM_CURSOR_NO, + SDL_SYSTEM_CURSOR_HAND, + SDL_NUM_SYSTEM_CURSORS +} SDL_SystemCursor; + +extern DECLSPEC SDL_Window * SDLCALL SDL_GetMouseFocus(void); + +extern DECLSPEC Uint32 SDLCALL SDL_GetMouseState(int *x, int *y); + +extern DECLSPEC Uint32 SDLCALL SDL_GetRelativeMouseState(int *x, int *y); + +extern DECLSPEC void SDLCALL SDL_WarpMouseInWindow(SDL_Window * window, + int x, int y); + +extern DECLSPEC int SDLCALL SDL_SetRelativeMouseMode(SDL_bool enabled); + +extern DECLSPEC SDL_bool SDLCALL SDL_GetRelativeMouseMode(void); + +extern DECLSPEC SDL_Cursor *SDLCALL SDL_CreateCursor(const Uint8 * data, + const Uint8 * mask, + int w, int h, int hot_x, + int hot_y); + +extern DECLSPEC SDL_Cursor *SDLCALL SDL_CreateColorCursor(SDL_Surface *surface, + int hot_x, + int hot_y); + +extern DECLSPEC SDL_Cursor *SDLCALL SDL_CreateSystemCursor(SDL_SystemCursor id); + +extern DECLSPEC void SDLCALL SDL_SetCursor(SDL_Cursor * cursor); + +extern DECLSPEC SDL_Cursor *SDLCALL SDL_GetCursor(void); + +extern DECLSPEC SDL_Cursor *SDLCALL SDL_GetDefaultCursor(void); + +extern DECLSPEC void SDLCALL SDL_FreeCursor(SDL_Cursor * cursor); + +extern DECLSPEC int SDLCALL SDL_ShowCursor(int toggle); + +#define SDL_BUTTON(X) (1 << ((X)-1)) +#define SDL_BUTTON_LEFT 1 +#define SDL_BUTTON_MIDDLE 2 +#define SDL_BUTTON_RIGHT 3 +#define SDL_BUTTON_X1 4 +#define SDL_BUTTON_X2 5 +#define SDL_BUTTON_LMASK SDL_BUTTON(SDL_BUTTON_LEFT) +#define SDL_BUTTON_MMASK SDL_BUTTON(SDL_BUTTON_MIDDLE) +#define SDL_BUTTON_RMASK SDL_BUTTON(SDL_BUTTON_RIGHT) +#define SDL_BUTTON_X1MASK SDL_BUTTON(SDL_BUTTON_X1) +#define SDL_BUTTON_X2MASK SDL_BUTTON(SDL_BUTTON_X2) + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_joystick_h +#define _SDL_joystick_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct _SDL_Joystick; +typedef struct _SDL_Joystick SDL_Joystick; + +typedef struct { + Uint8 data[16]; +} SDL_JoystickGUID; + +typedef Sint32 SDL_JoystickID; + +extern DECLSPEC int SDLCALL SDL_NumJoysticks(void); + +extern DECLSPEC const char *SDLCALL SDL_JoystickNameForIndex(int device_index); + +extern DECLSPEC SDL_Joystick *SDLCALL SDL_JoystickOpen(int device_index); + +extern DECLSPEC const char *SDLCALL SDL_JoystickName(SDL_Joystick * joystick); + +extern DECLSPEC SDL_JoystickGUID SDLCALL SDL_JoystickGetDeviceGUID(int device_index); + +extern DECLSPEC SDL_JoystickGUID SDLCALL SDL_JoystickGetGUID(SDL_Joystick * joystick); + +extern DECLSPEC void SDLCALL SDL_JoystickGetGUIDString(SDL_JoystickGUID guid, char *pszGUID, int cbGUID); + +extern DECLSPEC SDL_JoystickGUID SDLCALL SDL_JoystickGetGUIDFromString(const char *pchGUID); + +extern DECLSPEC SDL_bool SDLCALL SDL_JoystickGetAttached(SDL_Joystick * joystick); + +extern DECLSPEC SDL_JoystickID SDLCALL SDL_JoystickInstanceID(SDL_Joystick * joystick); + +extern DECLSPEC int SDLCALL SDL_JoystickNumAxes(SDL_Joystick * joystick); + +extern DECLSPEC int SDLCALL SDL_JoystickNumBalls(SDL_Joystick * joystick); + +extern DECLSPEC int SDLCALL SDL_JoystickNumHats(SDL_Joystick * joystick); + +extern DECLSPEC int SDLCALL SDL_JoystickNumButtons(SDL_Joystick * joystick); + +extern DECLSPEC void SDLCALL SDL_JoystickUpdate(void); + +extern DECLSPEC int SDLCALL SDL_JoystickEventState(int state); + +extern DECLSPEC Sint16 SDLCALL SDL_JoystickGetAxis(SDL_Joystick * joystick, + int axis); + +#define SDL_HAT_CENTERED 0x00 +#define SDL_HAT_UP 0x01 +#define SDL_HAT_RIGHT 0x02 +#define SDL_HAT_DOWN 0x04 +#define SDL_HAT_LEFT 0x08 +#define SDL_HAT_RIGHTUP (SDL_HAT_RIGHT|SDL_HAT_UP) +#define SDL_HAT_RIGHTDOWN (SDL_HAT_RIGHT|SDL_HAT_DOWN) +#define SDL_HAT_LEFTUP (SDL_HAT_LEFT|SDL_HAT_UP) +#define SDL_HAT_LEFTDOWN (SDL_HAT_LEFT|SDL_HAT_DOWN) + +extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetHat(SDL_Joystick * joystick, + int hat); + +extern DECLSPEC int SDLCALL SDL_JoystickGetBall(SDL_Joystick * joystick, + int ball, int *dx, int *dy); + +extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetButton(SDL_Joystick * joystick, + int button); + +extern DECLSPEC void SDLCALL SDL_JoystickClose(SDL_Joystick * joystick); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_gamecontroller_h +#define _SDL_gamecontroller_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct _SDL_GameController; +typedef struct _SDL_GameController SDL_GameController; + +typedef enum +{ + SDL_CONTROLLER_BINDTYPE_NONE = 0, + SDL_CONTROLLER_BINDTYPE_BUTTON, + SDL_CONTROLLER_BINDTYPE_AXIS, + SDL_CONTROLLER_BINDTYPE_HAT +} SDL_GameControllerBindType; + +typedef struct SDL_GameControllerButtonBind +{ + SDL_GameControllerBindType bindType; + union + { + int button; + int axis; + struct { + int hat; + int hat_mask; + } hat; + } value; + +} SDL_GameControllerButtonBind; + +extern DECLSPEC int SDLCALL SDL_GameControllerAddMappingsFromRW( SDL_RWops * rw, int freerw ); + +#define SDL_GameControllerAddMappingsFromFile(file) SDL_GameControllerAddMappingsFromRW(SDL_RWFromFile(file, "rb"), 1) + +extern DECLSPEC int SDLCALL SDL_GameControllerAddMapping( const char* mappingString ); + +extern DECLSPEC char * SDLCALL SDL_GameControllerMappingForGUID( SDL_JoystickGUID guid ); + +extern DECLSPEC char * SDLCALL SDL_GameControllerMapping( SDL_GameController * gamecontroller ); + +extern DECLSPEC SDL_bool SDLCALL SDL_IsGameController(int joystick_index); + +extern DECLSPEC const char *SDLCALL SDL_GameControllerNameForIndex(int joystick_index); + +extern DECLSPEC SDL_GameController *SDLCALL SDL_GameControllerOpen(int joystick_index); + +extern DECLSPEC const char *SDLCALL SDL_GameControllerName(SDL_GameController *gamecontroller); + +extern DECLSPEC SDL_bool SDLCALL SDL_GameControllerGetAttached(SDL_GameController *gamecontroller); + +extern DECLSPEC SDL_Joystick *SDLCALL SDL_GameControllerGetJoystick(SDL_GameController *gamecontroller); + +extern DECLSPEC int SDLCALL SDL_GameControllerEventState(int state); + +extern DECLSPEC void SDLCALL SDL_GameControllerUpdate(void); + +typedef enum +{ + SDL_CONTROLLER_AXIS_INVALID = -1, + SDL_CONTROLLER_AXIS_LEFTX, + SDL_CONTROLLER_AXIS_LEFTY, + SDL_CONTROLLER_AXIS_RIGHTX, + SDL_CONTROLLER_AXIS_RIGHTY, + SDL_CONTROLLER_AXIS_TRIGGERLEFT, + SDL_CONTROLLER_AXIS_TRIGGERRIGHT, + SDL_CONTROLLER_AXIS_MAX +} SDL_GameControllerAxis; + +extern DECLSPEC SDL_GameControllerAxis SDLCALL SDL_GameControllerGetAxisFromString(const char *pchString); + +extern DECLSPEC const char* SDLCALL SDL_GameControllerGetStringForAxis(SDL_GameControllerAxis axis); + +extern DECLSPEC SDL_GameControllerButtonBind SDLCALL +SDL_GameControllerGetBindForAxis(SDL_GameController *gamecontroller, + SDL_GameControllerAxis axis); + +extern DECLSPEC Sint16 SDLCALL +SDL_GameControllerGetAxis(SDL_GameController *gamecontroller, + SDL_GameControllerAxis axis); + +typedef enum +{ + SDL_CONTROLLER_BUTTON_INVALID = -1, + SDL_CONTROLLER_BUTTON_A, + SDL_CONTROLLER_BUTTON_B, + SDL_CONTROLLER_BUTTON_X, + SDL_CONTROLLER_BUTTON_Y, + SDL_CONTROLLER_BUTTON_BACK, + SDL_CONTROLLER_BUTTON_GUIDE, + SDL_CONTROLLER_BUTTON_START, + SDL_CONTROLLER_BUTTON_LEFTSTICK, + SDL_CONTROLLER_BUTTON_RIGHTSTICK, + SDL_CONTROLLER_BUTTON_LEFTSHOULDER, + SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, + SDL_CONTROLLER_BUTTON_DPAD_UP, + SDL_CONTROLLER_BUTTON_DPAD_DOWN, + SDL_CONTROLLER_BUTTON_DPAD_LEFT, + SDL_CONTROLLER_BUTTON_DPAD_RIGHT, + SDL_CONTROLLER_BUTTON_MAX +} SDL_GameControllerButton; + +extern DECLSPEC SDL_GameControllerButton SDLCALL SDL_GameControllerGetButtonFromString(const char *pchString); + +extern DECLSPEC const char* SDLCALL SDL_GameControllerGetStringForButton(SDL_GameControllerButton button); + +extern DECLSPEC SDL_GameControllerButtonBind SDLCALL +SDL_GameControllerGetBindForButton(SDL_GameController *gamecontroller, + SDL_GameControllerButton button); + +extern DECLSPEC Uint8 SDLCALL SDL_GameControllerGetButton(SDL_GameController *gamecontroller, + SDL_GameControllerButton button); + +extern DECLSPEC void SDLCALL SDL_GameControllerClose(SDL_GameController *gamecontroller); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_quit_h +#define _SDL_quit_h + +#define SDL_QuitRequested() \ + (SDL_PumpEvents(), (SDL_PeepEvents(NULL,0,SDL_PEEKEVENT,SDL_QUIT,SDL_QUIT) > 0)) + +#endif +#ifndef _SDL_gesture_h +#define _SDL_gesture_h + +#ifndef _SDL_touch_h +#define _SDL_touch_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef Sint64 SDL_TouchID; +typedef Sint64 SDL_FingerID; + +typedef struct SDL_Finger +{ + SDL_FingerID id; + float x; + float y; + float pressure; +} SDL_Finger; + +#define SDL_TOUCH_MOUSEID ((Uint32)-1) + +extern DECLSPEC int SDLCALL SDL_GetNumTouchDevices(void); + +extern DECLSPEC SDL_TouchID SDLCALL SDL_GetTouchDevice(int index); + +extern DECLSPEC int SDLCALL SDL_GetNumTouchFingers(SDL_TouchID touchID); + +extern DECLSPEC SDL_Finger * SDLCALL SDL_GetTouchFinger(SDL_TouchID touchID, int index); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef Sint64 SDL_GestureID; + +extern DECLSPEC int SDLCALL SDL_RecordGesture(SDL_TouchID touchId); + +extern DECLSPEC int SDLCALL SDL_SaveAllDollarTemplates(SDL_RWops *dst); + +extern DECLSPEC int SDLCALL SDL_SaveDollarTemplate(SDL_GestureID gestureId,SDL_RWops *dst); + +extern DECLSPEC int SDLCALL SDL_LoadDollarTemplates(SDL_TouchID touchId, SDL_RWops *src); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define SDL_RELEASED 0 +#define SDL_PRESSED 1 + +typedef enum +{ + SDL_FIRSTEVENT = 0, + + SDL_QUIT = 0x100, + + SDL_APP_TERMINATING, + SDL_APP_LOWMEMORY, + SDL_APP_WILLENTERBACKGROUND, + SDL_APP_DIDENTERBACKGROUND, + SDL_APP_WILLENTERFOREGROUND, + SDL_APP_DIDENTERFOREGROUND, + + SDL_WINDOWEVENT = 0x200, + SDL_SYSWMEVENT, + + SDL_KEYDOWN = 0x300, + SDL_KEYUP, + SDL_TEXTEDITING, + SDL_TEXTINPUT, + + SDL_MOUSEMOTION = 0x400, + SDL_MOUSEBUTTONDOWN, + SDL_MOUSEBUTTONUP, + SDL_MOUSEWHEEL, + + SDL_JOYAXISMOTION = 0x600, + SDL_JOYBALLMOTION, + SDL_JOYHATMOTION, + SDL_JOYBUTTONDOWN, + SDL_JOYBUTTONUP, + SDL_JOYDEVICEADDED, + SDL_JOYDEVICEREMOVED, + + SDL_CONTROLLERAXISMOTION = 0x650, + SDL_CONTROLLERBUTTONDOWN, + SDL_CONTROLLERBUTTONUP, + SDL_CONTROLLERDEVICEADDED, + SDL_CONTROLLERDEVICEREMOVED, + SDL_CONTROLLERDEVICEREMAPPED, + + SDL_FINGERDOWN = 0x700, + SDL_FINGERUP, + SDL_FINGERMOTION, + + SDL_DOLLARGESTURE = 0x800, + SDL_DOLLARRECORD, + SDL_MULTIGESTURE, + + SDL_CLIPBOARDUPDATE = 0x900, + + SDL_DROPFILE = 0x1000, + + SDL_RENDER_TARGETS_RESET = 0x2000, + + SDL_USEREVENT = 0x8000, + + SDL_LASTEVENT = 0xFFFF +} SDL_EventType; + +typedef struct SDL_CommonEvent +{ + Uint32 type; + Uint32 timestamp; +} SDL_CommonEvent; + +typedef struct SDL_WindowEvent +{ + Uint32 type; + Uint32 timestamp; + Uint32 windowID; + Uint8 event; + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + Sint32 data1; + Sint32 data2; +} SDL_WindowEvent; + +typedef struct SDL_KeyboardEvent +{ + Uint32 type; + Uint32 timestamp; + Uint32 windowID; + Uint8 state; + Uint8 repeat; + Uint8 padding2; + Uint8 padding3; + SDL_Keysym keysym; +} SDL_KeyboardEvent; + +#define SDL_TEXTEDITINGEVENT_TEXT_SIZE (32) + +typedef struct SDL_TextEditingEvent +{ + Uint32 type; + Uint32 timestamp; + Uint32 windowID; + char text[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; + Sint32 start; + Sint32 length; +} SDL_TextEditingEvent; + +#define SDL_TEXTINPUTEVENT_TEXT_SIZE (32) + +typedef struct SDL_TextInputEvent +{ + Uint32 type; + Uint32 timestamp; + Uint32 windowID; + char text[SDL_TEXTINPUTEVENT_TEXT_SIZE]; +} SDL_TextInputEvent; + +typedef struct SDL_MouseMotionEvent +{ + Uint32 type; + Uint32 timestamp; + Uint32 windowID; + Uint32 which; + Uint32 state; + Sint32 x; + Sint32 y; + Sint32 xrel; + Sint32 yrel; +} SDL_MouseMotionEvent; + +typedef struct SDL_MouseButtonEvent +{ + Uint32 type; + Uint32 timestamp; + Uint32 windowID; + Uint32 which; + Uint8 button; + Uint8 state; + Uint8 clicks; + Uint8 padding1; + Sint32 x; + Sint32 y; +} SDL_MouseButtonEvent; + +typedef struct SDL_MouseWheelEvent +{ + Uint32 type; + Uint32 timestamp; + Uint32 windowID; + Uint32 which; + Sint32 x; + Sint32 y; +} SDL_MouseWheelEvent; + +typedef struct SDL_JoyAxisEvent +{ + Uint32 type; + Uint32 timestamp; + SDL_JoystickID which; + Uint8 axis; + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + Sint16 value; + Uint16 padding4; +} SDL_JoyAxisEvent; + +typedef struct SDL_JoyBallEvent +{ + Uint32 type; + Uint32 timestamp; + SDL_JoystickID which; + Uint8 ball; + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + Sint16 xrel; + Sint16 yrel; +} SDL_JoyBallEvent; + +typedef struct SDL_JoyHatEvent +{ + Uint32 type; + Uint32 timestamp; + SDL_JoystickID which; + Uint8 hat; + Uint8 value; + Uint8 padding1; + Uint8 padding2; +} SDL_JoyHatEvent; + +typedef struct SDL_JoyButtonEvent +{ + Uint32 type; + Uint32 timestamp; + SDL_JoystickID which; + Uint8 button; + Uint8 state; + Uint8 padding1; + Uint8 padding2; +} SDL_JoyButtonEvent; + +typedef struct SDL_JoyDeviceEvent +{ + Uint32 type; + Uint32 timestamp; + Sint32 which; +} SDL_JoyDeviceEvent; + +typedef struct SDL_ControllerAxisEvent +{ + Uint32 type; + Uint32 timestamp; + SDL_JoystickID which; + Uint8 axis; + Uint8 padding1; + Uint8 padding2; + Uint8 padding3; + Sint16 value; + Uint16 padding4; +} SDL_ControllerAxisEvent; + +typedef struct SDL_ControllerButtonEvent +{ + Uint32 type; + Uint32 timestamp; + SDL_JoystickID which; + Uint8 button; + Uint8 state; + Uint8 padding1; + Uint8 padding2; +} SDL_ControllerButtonEvent; + +typedef struct SDL_ControllerDeviceEvent +{ + Uint32 type; + Uint32 timestamp; + Sint32 which; +} SDL_ControllerDeviceEvent; + +typedef struct SDL_TouchFingerEvent +{ + Uint32 type; + Uint32 timestamp; + SDL_TouchID touchId; + SDL_FingerID fingerId; + float x; + float y; + float dx; + float dy; + float pressure; +} SDL_TouchFingerEvent; + +typedef struct SDL_MultiGestureEvent +{ + Uint32 type; + Uint32 timestamp; + SDL_TouchID touchId; + float dTheta; + float dDist; + float x; + float y; + Uint16 numFingers; + Uint16 padding; +} SDL_MultiGestureEvent; + +typedef struct SDL_DollarGestureEvent +{ + Uint32 type; + Uint32 timestamp; + SDL_TouchID touchId; + SDL_GestureID gestureId; + Uint32 numFingers; + float error; + float x; + float y; +} SDL_DollarGestureEvent; + +typedef struct SDL_DropEvent +{ + Uint32 type; + Uint32 timestamp; + char *file; +} SDL_DropEvent; + +typedef struct SDL_QuitEvent +{ + Uint32 type; + Uint32 timestamp; +} SDL_QuitEvent; + +typedef struct SDL_OSEvent +{ + Uint32 type; + Uint32 timestamp; +} SDL_OSEvent; + +typedef struct SDL_UserEvent +{ + Uint32 type; + Uint32 timestamp; + Uint32 windowID; + Sint32 code; + void *data1; + void *data2; +} SDL_UserEvent; + +struct SDL_SysWMmsg; +typedef struct SDL_SysWMmsg SDL_SysWMmsg; + +typedef struct SDL_SysWMEvent +{ + Uint32 type; + Uint32 timestamp; + SDL_SysWMmsg *msg; +} SDL_SysWMEvent; + +typedef union SDL_Event +{ + Uint32 type; + SDL_CommonEvent common; + SDL_WindowEvent window; + SDL_KeyboardEvent key; + SDL_TextEditingEvent edit; + SDL_TextInputEvent text; + SDL_MouseMotionEvent motion; + SDL_MouseButtonEvent button; + SDL_MouseWheelEvent wheel; + SDL_JoyAxisEvent jaxis; + SDL_JoyBallEvent jball; + SDL_JoyHatEvent jhat; + SDL_JoyButtonEvent jbutton; + SDL_JoyDeviceEvent jdevice; + SDL_ControllerAxisEvent caxis; + SDL_ControllerButtonEvent cbutton; + SDL_ControllerDeviceEvent cdevice; + SDL_QuitEvent quit; + SDL_UserEvent user; + SDL_SysWMEvent syswm; + SDL_TouchFingerEvent tfinger; + SDL_MultiGestureEvent mgesture; + SDL_DollarGestureEvent dgesture; + SDL_DropEvent drop; + + Uint8 padding[56]; +} SDL_Event; + +extern DECLSPEC void SDLCALL SDL_PumpEvents(void); + +typedef enum +{ + SDL_ADDEVENT, + SDL_PEEKEVENT, + SDL_GETEVENT +} SDL_eventaction; + +extern DECLSPEC int SDLCALL SDL_PeepEvents(SDL_Event * events, int numevents, + SDL_eventaction action, + Uint32 minType, Uint32 maxType); + +extern DECLSPEC SDL_bool SDLCALL SDL_HasEvent(Uint32 type); +extern DECLSPEC SDL_bool SDLCALL SDL_HasEvents(Uint32 minType, Uint32 maxType); + +extern DECLSPEC void SDLCALL SDL_FlushEvent(Uint32 type); +extern DECLSPEC void SDLCALL SDL_FlushEvents(Uint32 minType, Uint32 maxType); + +extern DECLSPEC int SDLCALL SDL_PollEvent(SDL_Event * event); + +extern DECLSPEC int SDLCALL SDL_WaitEvent(SDL_Event * event); + +extern DECLSPEC int SDLCALL SDL_WaitEventTimeout(SDL_Event * event, + int timeout); + +extern DECLSPEC int SDLCALL SDL_PushEvent(SDL_Event * event); + +typedef int (SDLCALL * SDL_EventFilter) (void *userdata, SDL_Event * event); + +extern DECLSPEC void SDLCALL SDL_SetEventFilter(SDL_EventFilter filter, + void *userdata); + +extern DECLSPEC SDL_bool SDLCALL SDL_GetEventFilter(SDL_EventFilter * filter, + void **userdata); + +extern DECLSPEC void SDLCALL SDL_AddEventWatch(SDL_EventFilter filter, + void *userdata); + +extern DECLSPEC void SDLCALL SDL_DelEventWatch(SDL_EventFilter filter, + void *userdata); + +extern DECLSPEC void SDLCALL SDL_FilterEvents(SDL_EventFilter filter, + void *userdata); + +#define SDL_QUERY -1 +#define SDL_IGNORE 0 +#define SDL_DISABLE 0 +#define SDL_ENABLE 1 + +extern DECLSPEC Uint8 SDLCALL SDL_EventState(Uint32 type, int state); + +#define SDL_GetEventState(type) SDL_EventState(type, SDL_QUERY) + +extern DECLSPEC Uint32 SDLCALL SDL_RegisterEvents(int numevents); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#include "SDL_filesystem.h" +#include "SDL_haptic.h" +#ifndef _SDL_hints_h +#define _SDL_hints_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define SDL_HINT_FRAMEBUFFER_ACCELERATION "SDL_FRAMEBUFFER_ACCELERATION" + +#define SDL_HINT_RENDER_DRIVER "SDL_RENDER_DRIVER" + +#define SDL_HINT_RENDER_OPENGL_SHADERS "SDL_RENDER_OPENGL_SHADERS" + +#define SDL_HINT_RENDER_DIRECT3D_THREADSAFE "SDL_RENDER_DIRECT3D_THREADSAFE" + +#define SDL_HINT_RENDER_DIRECT3D11_DEBUG "SDL_HINT_RENDER_DIRECT3D11_DEBUG" + +#define SDL_HINT_RENDER_SCALE_QUALITY "SDL_RENDER_SCALE_QUALITY" + +#define SDL_HINT_RENDER_VSYNC "SDL_RENDER_VSYNC" + +#define SDL_HINT_VIDEO_ALLOW_SCREENSAVER "SDL_VIDEO_ALLOW_SCREENSAVER" + +#define SDL_HINT_VIDEO_X11_XVIDMODE "SDL_VIDEO_X11_XVIDMODE" + +#define SDL_HINT_VIDEO_X11_XINERAMA "SDL_VIDEO_X11_XINERAMA" + +#define SDL_HINT_VIDEO_X11_XRANDR "SDL_VIDEO_X11_XRANDR" + +#define SDL_HINT_GRAB_KEYBOARD "SDL_GRAB_KEYBOARD" + +#define SDL_HINT_MOUSE_RELATIVE_MODE_WARP "SDL_MOUSE_RELATIVE_MODE_WARP" + +#define SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS "SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS" + +#define SDL_HINT_IDLE_TIMER_DISABLED "SDL_IOS_IDLE_TIMER_DISABLED" + +#define SDL_HINT_ORIENTATIONS "SDL_IOS_ORIENTATIONS" + +#define SDL_HINT_ACCELEROMETER_AS_JOYSTICK "SDL_ACCELEROMETER_AS_JOYSTICK" + +#define SDL_HINT_XINPUT_ENABLED "SDL_XINPUT_ENABLED" + +#define SDL_HINT_GAMECONTROLLERCONFIG "SDL_GAMECONTROLLERCONFIG" + +#define SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS "SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS" + +#define SDL_HINT_ALLOW_TOPMOST "SDL_ALLOW_TOPMOST" + +#define SDL_HINT_TIMER_RESOLUTION "SDL_TIMER_RESOLUTION" + +#define SDL_HINT_VIDEO_HIGHDPI_DISABLED "SDL_VIDEO_HIGHDPI_DISABLED" + +#define SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK "SDL_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK" + +#define SDL_HINT_VIDEO_WIN_D3DCOMPILER "SDL_VIDEO_WIN_D3DCOMPILER" + +#define SDL_HINT_VIDEO_WINDOW_SHARE_PIXEL_FORMAT "SDL_VIDEO_WINDOW_SHARE_PIXEL_FORMAT" + +#define SDL_HINT_WINRT_PRIVACY_POLICY_URL "SDL_HINT_WINRT_PRIVACY_POLICY_URL" + +#define SDL_HINT_WINRT_PRIVACY_POLICY_LABEL "SDL_HINT_WINRT_PRIVACY_POLICY_LABEL" + +#define SDL_HINT_WINRT_HANDLE_BACK_BUTTON "SDL_HINT_WINRT_HANDLE_BACK_BUTTON" + +#define SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES "SDL_VIDEO_MAC_FULLSCREEN_SPACES" + +typedef enum +{ + SDL_HINT_DEFAULT, + SDL_HINT_NORMAL, + SDL_HINT_OVERRIDE +} SDL_HintPriority; + +extern DECLSPEC SDL_bool SDLCALL SDL_SetHintWithPriority(const char *name, + const char *value, + SDL_HintPriority priority); + +extern DECLSPEC SDL_bool SDLCALL SDL_SetHint(const char *name, + const char *value); + +extern DECLSPEC const char * SDLCALL SDL_GetHint(const char *name); + +typedef void (*SDL_HintCallback)(void *userdata, const char *name, const char *oldValue, const char *newValue); +extern DECLSPEC void SDLCALL SDL_AddHintCallback(const char *name, + SDL_HintCallback callback, + void *userdata); + +extern DECLSPEC void SDLCALL SDL_DelHintCallback(const char *name, + SDL_HintCallback callback, + void *userdata); + +extern DECLSPEC void SDLCALL SDL_ClearHints(void); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_loadso_h +#define _SDL_loadso_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern DECLSPEC void *SDLCALL SDL_LoadObject(const char *sofile); + +extern DECLSPEC void *SDLCALL SDL_LoadFunction(void *handle, + const char *name); + +extern DECLSPEC void SDLCALL SDL_UnloadObject(void *handle); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#include "SDL_log.h" +#ifndef _SDL_messagebox_h +#define _SDL_messagebox_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum +{ + SDL_MESSAGEBOX_ERROR = 0x00000010, + SDL_MESSAGEBOX_WARNING = 0x00000020, + SDL_MESSAGEBOX_INFORMATION = 0x00000040 +} SDL_MessageBoxFlags; + +typedef enum +{ + SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT = 0x00000001, + SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT = 0x00000002 +} SDL_MessageBoxButtonFlags; + +typedef struct +{ + Uint32 flags; + int buttonid; + const char * text; +} SDL_MessageBoxButtonData; + +typedef struct +{ + Uint8 r, g, b; +} SDL_MessageBoxColor; + +typedef enum +{ + SDL_MESSAGEBOX_COLOR_BACKGROUND, + SDL_MESSAGEBOX_COLOR_TEXT, + SDL_MESSAGEBOX_COLOR_BUTTON_BORDER, + SDL_MESSAGEBOX_COLOR_BUTTON_BACKGROUND, + SDL_MESSAGEBOX_COLOR_BUTTON_SELECTED, + SDL_MESSAGEBOX_COLOR_MAX +} SDL_MessageBoxColorType; + +typedef struct +{ + SDL_MessageBoxColor colors[SDL_MESSAGEBOX_COLOR_MAX]; +} SDL_MessageBoxColorScheme; + +typedef struct +{ + Uint32 flags; + SDL_Window *window; + const char *title; + const char *message; + + int numbuttons; + const SDL_MessageBoxButtonData *buttons; + + const SDL_MessageBoxColorScheme *colorScheme; +} SDL_MessageBoxData; + +extern DECLSPEC int SDLCALL SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid); + +extern DECLSPEC int SDLCALL SDL_ShowSimpleMessageBox(Uint32 flags, const char *title, const char *message, SDL_Window *window); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#include "SDL_power.h" +#ifndef _SDL_render_h +#define _SDL_render_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum +{ + SDL_RENDERER_SOFTWARE = 0x00000001, + SDL_RENDERER_ACCELERATED = 0x00000002, + SDL_RENDERER_PRESENTVSYNC = 0x00000004, + SDL_RENDERER_TARGETTEXTURE = 0x00000008 +} SDL_RendererFlags; + +typedef struct SDL_RendererInfo +{ + const char *name; + Uint32 flags; + Uint32 num_texture_formats; + Uint32 texture_formats[16]; + int max_texture_width; + int max_texture_height; +} SDL_RendererInfo; + +typedef enum +{ + SDL_TEXTUREACCESS_STATIC, + SDL_TEXTUREACCESS_STREAMING, + SDL_TEXTUREACCESS_TARGET +} SDL_TextureAccess; + +typedef enum +{ + SDL_TEXTUREMODULATE_NONE = 0x00000000, + SDL_TEXTUREMODULATE_COLOR = 0x00000001, + SDL_TEXTUREMODULATE_ALPHA = 0x00000002 +} SDL_TextureModulate; + +typedef enum +{ + SDL_FLIP_NONE = 0x00000000, + SDL_FLIP_HORIZONTAL = 0x00000001, + SDL_FLIP_VERTICAL = 0x00000002 +} SDL_RendererFlip; + +struct SDL_Renderer; +typedef struct SDL_Renderer SDL_Renderer; + +struct SDL_Texture; +typedef struct SDL_Texture SDL_Texture; + +extern DECLSPEC int SDLCALL SDL_GetNumRenderDrivers(void); + +extern DECLSPEC int SDLCALL SDL_GetRenderDriverInfo(int index, + SDL_RendererInfo * info); + +extern DECLSPEC int SDLCALL SDL_CreateWindowAndRenderer( + int width, int height, Uint32 window_flags, + SDL_Window **window, SDL_Renderer **renderer); + +extern DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window * window, + int index, Uint32 flags); + +extern DECLSPEC SDL_Renderer * SDLCALL SDL_CreateSoftwareRenderer(SDL_Surface * surface); + +extern DECLSPEC SDL_Renderer * SDLCALL SDL_GetRenderer(SDL_Window * window); + +extern DECLSPEC int SDLCALL SDL_GetRendererInfo(SDL_Renderer * renderer, + SDL_RendererInfo * info); + +extern DECLSPEC int SDLCALL SDL_GetRendererOutputSize(SDL_Renderer * renderer, + int *w, int *h); + +extern DECLSPEC SDL_Texture * SDLCALL SDL_CreateTexture(SDL_Renderer * renderer, + Uint32 format, + int access, int w, + int h); + +extern DECLSPEC SDL_Texture * SDLCALL SDL_CreateTextureFromSurface(SDL_Renderer * renderer, SDL_Surface * surface); + +extern DECLSPEC int SDLCALL SDL_QueryTexture(SDL_Texture * texture, + Uint32 * format, int *access, + int *w, int *h); + +extern DECLSPEC int SDLCALL SDL_SetTextureColorMod(SDL_Texture * texture, + Uint8 r, Uint8 g, Uint8 b); + +extern DECLSPEC int SDLCALL SDL_GetTextureColorMod(SDL_Texture * texture, + Uint8 * r, Uint8 * g, + Uint8 * b); + +extern DECLSPEC int SDLCALL SDL_SetTextureAlphaMod(SDL_Texture * texture, + Uint8 alpha); + +extern DECLSPEC int SDLCALL SDL_GetTextureAlphaMod(SDL_Texture * texture, + Uint8 * alpha); + +extern DECLSPEC int SDLCALL SDL_SetTextureBlendMode(SDL_Texture * texture, + SDL_BlendMode blendMode); + +extern DECLSPEC int SDLCALL SDL_GetTextureBlendMode(SDL_Texture * texture, + SDL_BlendMode *blendMode); + +extern DECLSPEC int SDLCALL SDL_UpdateTexture(SDL_Texture * texture, + const SDL_Rect * rect, + const void *pixels, int pitch); + +extern DECLSPEC int SDLCALL SDL_UpdateYUVTexture(SDL_Texture * texture, + const SDL_Rect * rect, + const Uint8 *Yplane, int Ypitch, + const Uint8 *Uplane, int Upitch, + const Uint8 *Vplane, int Vpitch); + +extern DECLSPEC int SDLCALL SDL_LockTexture(SDL_Texture * texture, + const SDL_Rect * rect, + void **pixels, int *pitch); + +extern DECLSPEC void SDLCALL SDL_UnlockTexture(SDL_Texture * texture); + +extern DECLSPEC SDL_bool SDLCALL SDL_RenderTargetSupported(SDL_Renderer *renderer); + +extern DECLSPEC int SDLCALL SDL_SetRenderTarget(SDL_Renderer *renderer, + SDL_Texture *texture); + +extern DECLSPEC SDL_Texture * SDLCALL SDL_GetRenderTarget(SDL_Renderer *renderer); + +extern DECLSPEC int SDLCALL SDL_RenderSetLogicalSize(SDL_Renderer * renderer, int w, int h); + +extern DECLSPEC void SDLCALL SDL_RenderGetLogicalSize(SDL_Renderer * renderer, int *w, int *h); + +extern DECLSPEC int SDLCALL SDL_RenderSetViewport(SDL_Renderer * renderer, + const SDL_Rect * rect); + +extern DECLSPEC void SDLCALL SDL_RenderGetViewport(SDL_Renderer * renderer, + SDL_Rect * rect); + +extern DECLSPEC int SDLCALL SDL_RenderSetClipRect(SDL_Renderer * renderer, + const SDL_Rect * rect); + +extern DECLSPEC void SDLCALL SDL_RenderGetClipRect(SDL_Renderer * renderer, + SDL_Rect * rect); + +extern DECLSPEC int SDLCALL SDL_RenderSetScale(SDL_Renderer * renderer, + float scaleX, float scaleY); + +extern DECLSPEC void SDLCALL SDL_RenderGetScale(SDL_Renderer * renderer, + float *scaleX, float *scaleY); + +extern DECLSPEC int SDLCALL SDL_SetRenderDrawColor(SDL_Renderer * renderer, + Uint8 r, Uint8 g, Uint8 b, + Uint8 a); + +extern DECLSPEC int SDLCALL SDL_GetRenderDrawColor(SDL_Renderer * renderer, + Uint8 * r, Uint8 * g, Uint8 * b, + Uint8 * a); + +extern DECLSPEC int SDLCALL SDL_SetRenderDrawBlendMode(SDL_Renderer * renderer, + SDL_BlendMode blendMode); + +extern DECLSPEC int SDLCALL SDL_GetRenderDrawBlendMode(SDL_Renderer * renderer, + SDL_BlendMode *blendMode); + +extern DECLSPEC int SDLCALL SDL_RenderClear(SDL_Renderer * renderer); + +extern DECLSPEC int SDLCALL SDL_RenderDrawPoint(SDL_Renderer * renderer, + int x, int y); + +extern DECLSPEC int SDLCALL SDL_RenderDrawPoints(SDL_Renderer * renderer, + const SDL_Point * points, + int count); + +extern DECLSPEC int SDLCALL SDL_RenderDrawLine(SDL_Renderer * renderer, + int x1, int y1, int x2, int y2); + +extern DECLSPEC int SDLCALL SDL_RenderDrawLines(SDL_Renderer * renderer, + const SDL_Point * points, + int count); + +extern DECLSPEC int SDLCALL SDL_RenderDrawRect(SDL_Renderer * renderer, + const SDL_Rect * rect); + +extern DECLSPEC int SDLCALL SDL_RenderDrawRects(SDL_Renderer * renderer, + const SDL_Rect * rects, + int count); + +extern DECLSPEC int SDLCALL SDL_RenderFillRect(SDL_Renderer * renderer, + const SDL_Rect * rect); + +extern DECLSPEC int SDLCALL SDL_RenderFillRects(SDL_Renderer * renderer, + const SDL_Rect * rects, + int count); + +extern DECLSPEC int SDLCALL SDL_RenderCopy(SDL_Renderer * renderer, + SDL_Texture * texture, + const SDL_Rect * srcrect, + const SDL_Rect * dstrect); + +extern DECLSPEC int SDLCALL SDL_RenderCopyEx(SDL_Renderer * renderer, + SDL_Texture * texture, + const SDL_Rect * srcrect, + const SDL_Rect * dstrect, + const double angle, + const SDL_Point *center, + const SDL_RendererFlip flip); + +extern DECLSPEC int SDLCALL SDL_RenderReadPixels(SDL_Renderer * renderer, + const SDL_Rect * rect, + Uint32 format, + void *pixels, int pitch); + +extern DECLSPEC void SDLCALL SDL_RenderPresent(SDL_Renderer * renderer); + +extern DECLSPEC void SDLCALL SDL_DestroyTexture(SDL_Texture * texture); + +extern DECLSPEC void SDLCALL SDL_DestroyRenderer(SDL_Renderer * renderer); + +extern DECLSPEC int SDLCALL SDL_GL_BindTexture(SDL_Texture *texture, float *texw, float *texh); + +extern DECLSPEC int SDLCALL SDL_GL_UnbindTexture(SDL_Texture *texture); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_system_h +#define _SDL_system_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __WIN32__ + +extern DECLSPEC int SDLCALL SDL_Direct3D9GetAdapterIndex( int displayIndex ); + +typedef struct IDirect3DDevice9 IDirect3DDevice9; +extern DECLSPEC IDirect3DDevice9* SDLCALL SDL_RenderGetD3D9Device(SDL_Renderer * renderer); + +extern DECLSPEC void SDLCALL SDL_DXGIGetOutputInfo( int displayIndex, int *adapterIndex, int *outputIndex ); + +#endif + +#if defined(__IPHONEOS__) && __IPHONEOS__ + +extern DECLSPEC int SDLCALL SDL_iPhoneSetAnimationCallback(SDL_Window * window, int interval, void (*callback)(void*), void *callbackParam); +extern DECLSPEC void SDLCALL SDL_iPhoneSetEventPump(SDL_bool enabled); + +#endif + +#if defined(__ANDROID__) && __ANDROID__ + +extern DECLSPEC void * SDLCALL SDL_AndroidGetJNIEnv(); + +extern DECLSPEC void * SDLCALL SDL_AndroidGetActivity(); + +#define SDL_ANDROID_EXTERNAL_STORAGE_READ 0x01 +#define SDL_ANDROID_EXTERNAL_STORAGE_WRITE 0x02 + +extern DECLSPEC const char * SDLCALL SDL_AndroidGetInternalStoragePath(); + +extern DECLSPEC int SDLCALL SDL_AndroidGetExternalStorageState(); + +extern DECLSPEC const char * SDLCALL SDL_AndroidGetExternalStoragePath(); + +#endif + +#if defined(__WINRT__) && __WINRT__ + +typedef enum +{ + + SDL_WINRT_PATH_INSTALLED_LOCATION, + + SDL_WINRT_PATH_LOCAL_FOLDER, + + SDL_WINRT_PATH_ROAMING_FOLDER, + + SDL_WINRT_PATH_TEMP_FOLDER +} SDL_WinRT_Path; + +extern DECLSPEC const wchar_t * SDLCALL SDL_WinRTGetFSPathUNICODE(SDL_WinRT_Path pathType); + +extern DECLSPEC const char * SDLCALL SDL_WinRTGetFSPathUTF8(SDL_WinRT_Path pathType); + +#endif + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_timer_h +#define _SDL_timer_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern DECLSPEC Uint32 SDLCALL SDL_GetTicks(void); + +#define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0) + +extern DECLSPEC Uint64 SDLCALL SDL_GetPerformanceCounter(void); + +extern DECLSPEC Uint64 SDLCALL SDL_GetPerformanceFrequency(void); + +extern DECLSPEC void SDLCALL SDL_Delay(Uint32 ms); + +typedef Uint32 (SDLCALL * SDL_TimerCallback) (Uint32 interval, void *param); + +typedef int SDL_TimerID; + +extern DECLSPEC SDL_TimerID SDLCALL SDL_AddTimer(Uint32 interval, + SDL_TimerCallback callback, + void *param); + +extern DECLSPEC SDL_bool SDLCALL SDL_RemoveTimer(SDL_TimerID id); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_version_h +#define _SDL_version_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SDL_version +{ + Uint8 major; + Uint8 minor; + Uint8 patch; +} SDL_version; + +#define SDL_MAJOR_VERSION 2 +#define SDL_MINOR_VERSION 0 +#define SDL_PATCHLEVEL 3 + +#define SDL_VERSION(x) \ +{ \ + (x)->major = SDL_MAJOR_VERSION; \ + (x)->minor = SDL_MINOR_VERSION; \ + (x)->patch = SDL_PATCHLEVEL; \ +} + +#define SDL_VERSIONNUM(X, Y, Z) \ + ((X)*1000 + (Y)*100 + (Z)) + +#define SDL_COMPILEDVERSION \ + SDL_VERSIONNUM(SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL) + +#define SDL_VERSION_ATLEAST(X, Y, Z) \ + (SDL_COMPILEDVERSION >= SDL_VERSIONNUM(X, Y, Z)) + +extern DECLSPEC void SDLCALL SDL_GetVersion(SDL_version * ver); + +extern DECLSPEC const char *SDLCALL SDL_GetRevision(void); + +extern DECLSPEC int SDLCALL SDL_GetRevisionNumber(void); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define SDL_INIT_TIMER 0x00000001 +#define SDL_INIT_AUDIO 0x00000010 +#define SDL_INIT_VIDEO 0x00000020 +#define SDL_INIT_JOYSTICK 0x00000200 +#define SDL_INIT_HAPTIC 0x00001000 +#define SDL_INIT_GAMECONTROLLER 0x00002000 +#define SDL_INIT_EVENTS 0x00004000 +#define SDL_INIT_NOPARACHUTE 0x00100000 +#define SDL_INIT_EVERYTHING ( \ + SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_EVENTS | \ + SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER \ + ) + +extern DECLSPEC int SDLCALL SDL_Init(Uint32 flags); + +extern DECLSPEC int SDLCALL SDL_InitSubSystem(Uint32 flags); + +extern DECLSPEC void SDLCALL SDL_QuitSubSystem(Uint32 flags); + +extern DECLSPEC Uint32 SDLCALL SDL_WasInit(Uint32 flags); + +extern DECLSPEC void SDLCALL SDL_Quit(void); + +#ifdef __cplusplus +} +#endif +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif +#ifndef _SDL_internal_h +#define _SDL_internal_h + +#endif +#ifndef _SDL_internal_h +#define _SDL_internal_h + +#endif + +#ifndef _SDL_error_c_h +#define _SDL_error_c_h + +#define ERR_MAX_STRLEN 128 +#define ERR_MAX_ARGS 5 + +typedef struct SDL_error +{ + + int error; + + char key[ERR_MAX_STRLEN]; + + int argc; + union + { + void *value_ptr; +#if 0 + unsigned char value_c; +#endif + int value_i; + double value_f; + char buf[ERR_MAX_STRLEN]; + } args[ERR_MAX_ARGS]; +} SDL_error; + +extern SDL_error *SDL_GetErrBuf(void); + +#endif +extern SDL_AudioFormat SDL_FirstAudioFormat(SDL_AudioFormat format); +extern SDL_AudioFormat SDL_NextAudioFormat(void); + +extern void SDL_CalculateAudioSpec(SDL_AudioSpec * spec); + +extern int SDLCALL SDL_RunAudio(void *audiop); + +typedef struct +{ + SDL_AudioFormat src_fmt; + SDL_AudioFormat dst_fmt; + SDL_AudioFilter filter; +} SDL_AudioTypeFilters; +extern const SDL_AudioTypeFilters sdl_audio_type_filters[]; + +typedef struct +{ + SDL_AudioFormat fmt; + int channels; + int upsample; + int multiple; + SDL_AudioFilter filter; +} SDL_AudioRateFilters; +extern const SDL_AudioRateFilters sdl_audio_rate_filters[]; +#ifndef _SDL_sysaudio_h +#define _SDL_sysaudio_h + +typedef struct SDL_AudioDevice SDL_AudioDevice; +#define _THIS SDL_AudioDevice *_this + +typedef void (*SDL_AddAudioDevice)(const char *name); + +typedef struct SDL_AudioDriverImpl +{ + void (*DetectDevices) (int iscapture, SDL_AddAudioDevice addfn); + int (*OpenDevice) (_THIS, const char *devname, int iscapture); + void (*ThreadInit) (_THIS); + void (*WaitDevice) (_THIS); + void (*PlayDevice) (_THIS); + Uint8 *(*GetDeviceBuf) (_THIS); + void (*WaitDone) (_THIS); + void (*CloseDevice) (_THIS); + void (*LockDevice) (_THIS); + void (*UnlockDevice) (_THIS); + void (*Deinitialize) (void); + + int ProvidesOwnCallbackThread; + int SkipMixerLock; + int HasCaptureSupport; + int OnlyHasDefaultOutputDevice; + int OnlyHasDefaultInputDevice; +} SDL_AudioDriverImpl; + +typedef struct SDL_AudioDriver +{ + + const char *name; + + const char *desc; + + SDL_AudioDriverImpl impl; + + char **outputDevices; + int outputDeviceCount; + + char **inputDevices; + int inputDeviceCount; +} SDL_AudioDriver; + +typedef struct +{ + Uint8 *buffer; + int max_len; + int read_pos, write_pos; +} SDL_AudioStreamer; + +struct SDL_AudioDevice +{ + + SDL_AudioSpec spec; + + SDL_AudioCVT convert; + + int use_streamer; + SDL_AudioStreamer streamer; + + int iscapture; + int enabled; + int paused; + int opened; + + Uint8 *fake_stream; + + SDL_mutex *mixer_lock; + + SDL_Thread *thread; + SDL_threadID threadid; + + struct SDL_PrivateAudioData *hidden; +}; +#undef _THIS + +typedef struct AudioBootStrap +{ + const char *name; + const char *desc; + int (*init) (SDL_AudioDriverImpl * impl); + int demand_only; +} AudioBootStrap; + +#endif +#ifndef _SDL_thread_c_h +#define _SDL_thread_c_h + +#if SDL_THREADS_DISABLED +typedef int SYS_ThreadHandle; +#elif SDL_THREAD_BEOS +#include "beos/SDL_systhread_c.h" +#elif SDL_THREAD_EPOC +#include "epoc/SDL_systhread_c.h" +#elif SDL_THREAD_PTHREAD +#include + +typedef pthread_t SYS_ThreadHandle; +#elif SDL_THREAD_WINDOWS +#ifndef _SDL_systhread_c_h +#define _SDL_systhread_c_h + +#ifndef _INCLUDED_WINDOWS_H +#define _INCLUDED_WINDOWS_H + +#if defined(__WIN32__) +#define WIN32_LEAN_AND_MEAN +#define STRICT +#ifndef UNICODE +#define UNICODE 1 +#endif +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x501 +#endif + +#include + +#if UNICODE +#define WIN_StringToUTF8(S) SDL_iconv_string("UTF-8", "UTF-16LE", (char *)(S), (SDL_wcslen(S)+1)*sizeof(WCHAR)) +#define WIN_UTF8ToString(S) (WCHAR *)SDL_iconv_string("UTF-16LE", "UTF-8", (char *)(S), SDL_strlen(S)+1) +#else + +#define WIN_StringToUTF8(S) SDL_iconv_string("UTF-8", "ASCII", (char *)(S), (SDL_strlen(S)+1)) +#define WIN_UTF8ToString(S) SDL_iconv_string("ASCII", "UTF-8", (char *)(S), SDL_strlen(S)+1) +#endif + +extern int WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr); + +extern int WIN_SetError(const char *prefix); + +extern HRESULT WIN_CoInitialize(void); +extern void WIN_CoUninitialize(void); + +#endif + +typedef HANDLE SYS_ThreadHandle; + +#endif +#elif SDL_THREAD_PSP +#include "psp/SDL_systhread_c.h" +#else +#error Need thread implementation for this platform +typedef int SYS_ThreadHandle; +#endif + +struct SDL_Thread +{ + SDL_threadID threadid; + SYS_ThreadHandle handle; + int status; + SDL_error errbuf; + char *name; + void *data; +}; + +extern void SDL_RunThread(void *data); + +#endif +#ifndef _SDL_systhread_h +#define _SDL_systhread_h + +#ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD +extern int SDL_SYS_CreateThread(SDL_Thread * thread, void *args, + pfnSDL_CurrentBeginThread pfnBeginThread, + pfnSDL_CurrentEndThread pfnEndThread); +#else +extern int SDL_SYS_CreateThread(SDL_Thread * thread, void *args); +#endif + +extern void SDL_SYS_SetupThread(const char *name); + +extern int SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority); + +extern void SDL_SYS_WaitThread(SDL_Thread * thread); + +#endif +#define ROUND_RESOLUTION(X) \ + (((X+TIMER_RESOLUTION-1)/TIMER_RESOLUTION)*TIMER_RESOLUTION) + +extern void SDL_TicksInit(void); +extern void SDL_TicksQuit(void); +extern int SDL_TimerInit(void); +extern void SDL_TimerQuit(void); +#ifndef _SDL_udev_h +#define _SDL_udev_h + +#if HAVE_LIBUDEV_H + +#ifndef SDL_USE_LIBUDEV +#define SDL_USE_LIBUDEV 1 +#endif + +#include +#include +#include + +typedef enum +{ + SDL_UDEV_DEVICEADDED = 0x0001, + SDL_UDEV_DEVICEREMOVED +} SDL_UDEV_deviceevent; + +typedef enum +{ + SDL_UDEV_DEVICE_MOUSE = 0x0001, + SDL_UDEV_DEVICE_KEYBOARD = 0x0002, + SDL_UDEV_DEVICE_JOYSTICK = 0x0004, + SDL_UDEV_DEVICE_SOUND = 0x0008 +} SDL_UDEV_deviceclass; + +typedef void (*SDL_UDEV_Callback)(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath); + +typedef struct SDL_UDEV_CallbackList { + SDL_UDEV_Callback callback; + struct SDL_UDEV_CallbackList *next; +} SDL_UDEV_CallbackList; + +typedef struct SDL_UDEV_PrivateData +{ + const char *udev_library; + void *udev_handle; + struct udev *udev; + struct udev_monitor *udev_mon; + int ref_count; + SDL_UDEV_CallbackList *first, *last; + + const char *(*udev_device_get_action)(struct udev_device *); + const char *(*udev_device_get_devnode)(struct udev_device *); + const char *(*udev_device_get_subsystem)(struct udev_device *); + const char *(*udev_device_get_property_value)(struct udev_device *, const char *); + struct udev_device *(*udev_device_new_from_syspath)(struct udev *, const char *); + void (*udev_device_unref)(struct udev_device *); + int (*udev_enumerate_add_match_property)(struct udev_enumerate *, const char *, const char *); + int (*udev_enumerate_add_match_subsystem)(struct udev_enumerate *, const char *); + struct udev_list_entry *(*udev_enumerate_get_list_entry)(struct udev_enumerate *); + struct udev_enumerate *(*udev_enumerate_new)(struct udev *); + int (*udev_enumerate_scan_devices)(struct udev_enumerate *); + void (*udev_enumerate_unref)(struct udev_enumerate *); + const char *(*udev_list_entry_get_name)(struct udev_list_entry *); + struct udev_list_entry *(*udev_list_entry_get_next)(struct udev_list_entry *); + int (*udev_monitor_enable_receiving)(struct udev_monitor *); + int (*udev_monitor_filter_add_match_subsystem_devtype)(struct udev_monitor *, const char *, const char *); + int (*udev_monitor_get_fd)(struct udev_monitor *); + struct udev_monitor *(*udev_monitor_new_from_netlink)(struct udev *, const char *); + struct udev_device *(*udev_monitor_receive_device)(struct udev_monitor *); + void (*udev_monitor_unref)(struct udev_monitor *); + struct udev *(*udev_new)(void); + void (*udev_unref)(struct udev *); + struct udev_device * (*udev_device_new_from_devnum)(struct udev *udev, char type, dev_t devnum); + dev_t (*udev_device_get_devnum) (struct udev_device *udev_device); +} SDL_UDEV_PrivateData; + +extern int SDL_UDEV_Init(void); +extern void SDL_UDEV_Quit(void); +extern void SDL_UDEV_UnloadLibrary(void); +extern int SDL_UDEV_LoadLibrary(void); +extern void SDL_UDEV_Poll(void); +extern void SDL_UDEV_Scan(void); +extern int SDL_UDEV_AddCallback(SDL_UDEV_Callback cb); +extern void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb); + +#endif + +#endif + +#ifdef SDL_USE_LIBUDEV + +static char* SDL_UDEV_LIBS[] = { "libudev.so.1", "libudev.so.0" }; + +#undef _THIS +#define _THIS SDL_UDEV_PrivateData *_this +static _THIS = NULL; + +static SDL_bool SDL_UDEV_load_sym(const char *fn, void **addr); +static int SDL_UDEV_load_syms(void); +static SDL_bool SDL_UDEV_hotplug_update_available(void); +static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev); + +static SDL_bool +SDL_UDEV_load_sym(const char *fn, void **addr) +{ + *addr = SDL_LoadFunction(_this->udev_handle, fn); + if (*addr == NULL) { + + return SDL_FALSE; + } + + return SDL_TRUE; +} + +static int +SDL_UDEV_load_syms(void) +{ + + #define SDL_UDEV_SYM(x) \ + if (!SDL_UDEV_load_sym(#x, (void **) (char *) & _this->x)) return -1 + + SDL_UDEV_SYM(udev_device_get_action); + SDL_UDEV_SYM(udev_device_get_devnode); + SDL_UDEV_SYM(udev_device_get_subsystem); + SDL_UDEV_SYM(udev_device_get_property_value); + SDL_UDEV_SYM(udev_device_new_from_syspath); + SDL_UDEV_SYM(udev_device_unref); + SDL_UDEV_SYM(udev_enumerate_add_match_property); + SDL_UDEV_SYM(udev_enumerate_add_match_subsystem); + SDL_UDEV_SYM(udev_enumerate_get_list_entry); + SDL_UDEV_SYM(udev_enumerate_new); + SDL_UDEV_SYM(udev_enumerate_scan_devices); + SDL_UDEV_SYM(udev_enumerate_unref); + SDL_UDEV_SYM(udev_list_entry_get_name); + SDL_UDEV_SYM(udev_list_entry_get_next); + SDL_UDEV_SYM(udev_monitor_enable_receiving); + SDL_UDEV_SYM(udev_monitor_filter_add_match_subsystem_devtype); + SDL_UDEV_SYM(udev_monitor_get_fd); + SDL_UDEV_SYM(udev_monitor_new_from_netlink); + SDL_UDEV_SYM(udev_monitor_receive_device); + SDL_UDEV_SYM(udev_monitor_unref); + SDL_UDEV_SYM(udev_new); + SDL_UDEV_SYM(udev_unref); + SDL_UDEV_SYM(udev_device_new_from_devnum); + SDL_UDEV_SYM(udev_device_get_devnum); + #undef SDL_UDEV_SYM + + return 0; +} + +static SDL_bool +SDL_UDEV_hotplug_update_available(void) +{ + if (_this->udev_mon != NULL) { + const int fd = _this->udev_monitor_get_fd(_this->udev_mon); + fd_set fds; + struct timeval tv; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + tv.tv_sec = 0; + tv.tv_usec = 0; + if ((select(fd+1, &fds, NULL, NULL, &tv) > 0) && (FD_ISSET(fd, &fds))) { + return SDL_TRUE; + } + } + return SDL_FALSE; +} + +int +SDL_UDEV_Init(void) +{ + int retval = 0; + + if (_this == NULL) { + _this = (SDL_UDEV_PrivateData *) SDL_calloc(1, sizeof(*_this)); + if(_this == NULL) { + return SDL_OutOfMemory(); + } + + retval = SDL_UDEV_LoadLibrary(); + if (retval < 0) { + SDL_UDEV_Quit(); + return retval; + } + + _this->udev = _this->udev_new(); + if (_this->udev == NULL) { + SDL_UDEV_Quit(); + return SDL_SetError("udev_new() failed"); + } + + _this->udev_mon = _this->udev_monitor_new_from_netlink(_this->udev, "udev"); + if (_this->udev_mon == NULL) { + SDL_UDEV_Quit(); + return SDL_SetError("udev_monitor_new_from_netlink() failed"); + } + + _this->udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "input", NULL); + _this->udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "sound", NULL); + _this->udev_monitor_enable_receiving(_this->udev_mon); + + SDL_UDEV_Scan(); + + } + + _this->ref_count += 1; + + return retval; +} + +void +SDL_UDEV_Quit(void) +{ + SDL_UDEV_CallbackList *item; + + if (_this == NULL) { + return; + } + + _this->ref_count -= 1; + + if (_this->ref_count < 1) { + + if (_this->udev_mon != NULL) { + _this->udev_monitor_unref(_this->udev_mon); + _this->udev_mon = NULL; + } + if (_this->udev != NULL) { + _this->udev_unref(_this->udev); + _this->udev = NULL; + } + + while (_this->first != NULL) { + item = _this->first; + _this->first = _this->first->next; + SDL_free(item); + } + + SDL_UDEV_UnloadLibrary(); + SDL_free(_this); + _this = NULL; + } +} + +void +SDL_UDEV_Scan(void) +{ + struct udev_enumerate *enumerate = NULL; + struct udev_list_entry *devs = NULL; + struct udev_list_entry *item = NULL; + + if (_this == NULL) { + return; + } + + enumerate = _this->udev_enumerate_new(_this->udev); + if (enumerate == NULL) { + SDL_UDEV_Quit(); + SDL_SetError("udev_monitor_new_from_netlink() failed"); + return; + } + + _this->udev_enumerate_add_match_subsystem(enumerate, "input"); + _this->udev_enumerate_add_match_subsystem(enumerate, "sound"); + + _this->udev_enumerate_scan_devices(enumerate); + devs = _this->udev_enumerate_get_list_entry(enumerate); + for (item = devs; item; item = _this->udev_list_entry_get_next(item)) { + const char *path = _this->udev_list_entry_get_name(item); + struct udev_device *dev = _this->udev_device_new_from_syspath(_this->udev, path); + if (dev != NULL) { + device_event(SDL_UDEV_DEVICEADDED, dev); + _this->udev_device_unref(dev); + } + } + + _this->udev_enumerate_unref(enumerate); +} + +void +SDL_UDEV_UnloadLibrary(void) +{ + if (_this == NULL) { + return; + } + + if (_this->udev_handle != NULL) { + SDL_UnloadObject(_this->udev_handle); + _this->udev_handle = NULL; + } +} + +int +SDL_UDEV_LoadLibrary(void) +{ + int retval = 0, i; + + if (_this == NULL) { + return SDL_SetError("UDEV not initialized"); + } + + if (_this->udev_handle == NULL) { + for( i = 0 ; i < SDL_arraysize(SDL_UDEV_LIBS); i++) { + _this->udev_handle = SDL_LoadObject(SDL_UDEV_LIBS[i]); + if (_this->udev_handle != NULL) { + retval = SDL_UDEV_load_syms(); + if (retval < 0) { + SDL_UDEV_UnloadLibrary(); + } + else { + break; + } + } + } + + if (_this->udev_handle == NULL) { + retval = -1; + + } + } + + return retval; +} + +static void +device_event(SDL_UDEV_deviceevent type, struct udev_device *dev) +{ + const char *subsystem; + const char *val = NULL; + int devclass = 0; + const char *path; + SDL_UDEV_CallbackList *item; + + path = _this->udev_device_get_devnode(dev); + if (path == NULL) { + return; + } + + subsystem = _this->udev_device_get_subsystem(dev); + if (SDL_strcmp(subsystem, "sound") == 0) { + devclass = SDL_UDEV_DEVICE_SOUND; + } else if (SDL_strcmp(subsystem, "input") == 0) { + val = _this->udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK"); + if (val != NULL && SDL_strcmp(val, "1") == 0 ) { + devclass |= SDL_UDEV_DEVICE_JOYSTICK; + } + + val = _this->udev_device_get_property_value(dev, "ID_INPUT_MOUSE"); + if (val != NULL && SDL_strcmp(val, "1") == 0 ) { + devclass |= SDL_UDEV_DEVICE_MOUSE; + } + + val = _this->udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD"); + if (val != NULL && SDL_strcmp(val, "1") == 0 ) { + devclass |= SDL_UDEV_DEVICE_KEYBOARD; + } + + if (devclass == 0) { + return; + } + } else { + return; + } + + for (item = _this->first; item != NULL; item = item->next) { + item->callback(type, devclass, path); + } +} + +void +SDL_UDEV_Poll(void) +{ + struct udev_device *dev = NULL; + const char *action = NULL; + + if (_this == NULL) { + return; + } + + while (SDL_UDEV_hotplug_update_available()) { + dev = _this->udev_monitor_receive_device(_this->udev_mon); + if (dev == NULL) { + break; + } + action = _this->udev_device_get_action(dev); + + if (SDL_strcmp(action, "add") == 0) { + device_event(SDL_UDEV_DEVICEADDED, dev); + } else if (SDL_strcmp(action, "remove") == 0) { + device_event(SDL_UDEV_DEVICEREMOVED, dev); + } + + _this->udev_device_unref(dev); + } +} + +int +SDL_UDEV_AddCallback(SDL_UDEV_Callback cb) +{ + SDL_UDEV_CallbackList *item; + item = (SDL_UDEV_CallbackList *) SDL_calloc(1, sizeof (SDL_UDEV_CallbackList)); + if (item == NULL) { + return SDL_OutOfMemory(); + } + + item->callback = cb; + + if (_this->last == NULL) { + _this->first = _this->last = item; + } else { + _this->last->next = item; + _this->last = item; + } + + return 1; +} + +void +SDL_UDEV_DelCallback(SDL_UDEV_Callback cb) +{ + SDL_UDEV_CallbackList *item; + SDL_UDEV_CallbackList *prev = NULL; + + for (item = _this->first; item != NULL; item = item->next) { + + if (item->callback == cb) { + if (prev != NULL) { + prev->next = item->next; + } else { + SDL_assert(_this->first == item); + _this->first = item->next; + } + if (item == _this->last) { + _this->last = prev; + } + SDL_free(item); + return; + } + prev = item; + } + +} + +#endif +#ifdef SDL_LOADSO_DLOPEN + +#include +#include + +void * +SDL_LoadObject(const char *sofile) +{ + void *handle = dlopen(sofile, RTLD_NOW|RTLD_LOCAL); + const char *loaderror = (char *) dlerror(); + if (handle == NULL) { + SDL_SetError("Failed loading %s: %s", sofile, loaderror); + } + return (handle); +} + +void * +SDL_LoadFunction(void *handle, const char *name) +{ + void *symbol = dlsym(handle, name); + if (symbol == NULL) { + + size_t len = 1 + SDL_strlen(name) + 1; + char *_name = SDL_stack_alloc(char, len); + _name[0] = '_'; + SDL_strlcpy(&_name[1], name, len); + symbol = dlsym(handle, _name); + SDL_stack_free(_name); + if (symbol == NULL) { + SDL_SetError("Failed loading %s: %s", name, + (const char *) dlerror()); + } + } + return (symbol); +} + +void +SDL_UnloadObject(void *handle) +{ + if (handle != NULL) { + dlclose(handle); + } +} + +#endif +#if defined(SDL_LOADSO_DUMMY) || defined(SDL_LOADSO_DISABLED) + +void * +SDL_LoadObject(const char *sofile) +{ + const char *loaderror = "SDL_LoadObject() not implemented"; + SDL_SetError("Failed loading %s: %s", sofile, loaderror); + return (NULL); +} + +void * +SDL_LoadFunction(void *handle, const char *name) +{ + const char *loaderror = "SDL_LoadFunction() not implemented"; + SDL_SetError("Failed loading %s: %s", name, loaderror); + return (NULL); +} + +void +SDL_UnloadObject(void *handle) +{ + +} + +#endif +#ifdef SDL_LOADSO_WINDOWS + +void * +SDL_LoadObject(const char *sofile) +{ + LPTSTR tstr = WIN_UTF8ToString(sofile); +#ifdef __WINRT__ + + void *handle = (void *) LoadPackagedLibrary(tstr, 0); +#else + void *handle = (void *) LoadLibrary(tstr); +#endif + SDL_free(tstr); + + if (handle == NULL) { + char errbuf[512]; + SDL_strlcpy(errbuf, "Failed loading ", SDL_arraysize(errbuf)); + SDL_strlcat(errbuf, sofile, SDL_arraysize(errbuf)); + WIN_SetError(errbuf); + } + return handle; +} + +void * +SDL_LoadFunction(void *handle, const char *name) +{ + void *symbol = (void *) GetProcAddress((HMODULE) handle, name); + if (symbol == NULL) { + char errbuf[512]; + SDL_strlcpy(errbuf, "Failed loading ", SDL_arraysize(errbuf)); + SDL_strlcat(errbuf, name, SDL_arraysize(errbuf)); + WIN_SetError(errbuf); + } + return symbol; +} + +void +SDL_UnloadObject(void *handle) +{ + if (handle != NULL) { + FreeLibrary((HMODULE) handle); + } +} + +#endif +#if SDL_AUDIO_DRIVER_ALSA + +#include +#include +#include +#include + +#define SDL_AllocAudioMem SDL_malloc +#define SDL_FreeAudioMem SDL_free +#ifndef _SDL_ALSA_audio_h +#define _SDL_ALSA_audio_h + +#include + +#undef _THIS +#define _THIS SDL_AudioDevice *this + +#undef SDLAUDIOHIDDEN +#define SDLAUDIOHIDDEN ((struct SDL_PrivateAudioDataALSA*)this->hidden) +struct SDL_PrivateAudioDataALSA +{ + + snd_pcm_t *pcm_handle; + + Uint8 *mixbuf; + int mixlen; +}; + +#endif + +#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC +#endif + +static int (*ALSA_snd_pcm_open) + (snd_pcm_t **, const char *, snd_pcm_stream_t, int); +static int (*ALSA_snd_pcm_close) (snd_pcm_t * pcm); +static snd_pcm_sframes_t(*ALSA_snd_pcm_writei) + (snd_pcm_t *, const void *, snd_pcm_uframes_t); +static int (*ALSA_snd_pcm_recover) (snd_pcm_t *, int, int); +static int (*ALSA_snd_pcm_prepare) (snd_pcm_t *); +static int (*ALSA_snd_pcm_drain) (snd_pcm_t *); +static const char *(*ALSA_snd_strerror) (int); +static size_t(*ALSA_snd_pcm_hw_params_sizeof) (void); +static size_t(*ALSA_snd_pcm_sw_params_sizeof) (void); +static void (*ALSA_snd_pcm_hw_params_copy) + (snd_pcm_hw_params_t *, const snd_pcm_hw_params_t *); +static int (*ALSA_snd_pcm_hw_params_any) (snd_pcm_t *, snd_pcm_hw_params_t *); +static int (*ALSA_snd_pcm_hw_params_set_access) + (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t); +static int (*ALSA_snd_pcm_hw_params_set_format) + (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t); +static int (*ALSA_snd_pcm_hw_params_set_channels) + (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int); +static int (*ALSA_snd_pcm_hw_params_get_channels) + (const snd_pcm_hw_params_t *, unsigned int *); +static int (*ALSA_snd_pcm_hw_params_set_rate_near) + (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *); +static int (*ALSA_snd_pcm_hw_params_set_period_size_near) + (snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *); +static int (*ALSA_snd_pcm_hw_params_get_period_size) + (const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *); +static int (*ALSA_snd_pcm_hw_params_set_periods_near) + (snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *); +static int (*ALSA_snd_pcm_hw_params_get_periods) + (const snd_pcm_hw_params_t *, unsigned int *, int *); +static int (*ALSA_snd_pcm_hw_params_set_buffer_size_near) + (snd_pcm_t *pcm, snd_pcm_hw_params_t *, snd_pcm_uframes_t *); +static int (*ALSA_snd_pcm_hw_params_get_buffer_size) + (const snd_pcm_hw_params_t *, snd_pcm_uframes_t *); +static int (*ALSA_snd_pcm_hw_params) (snd_pcm_t *, snd_pcm_hw_params_t *); +static int (*ALSA_snd_pcm_sw_params_current) (snd_pcm_t *, + snd_pcm_sw_params_t *); +static int (*ALSA_snd_pcm_sw_params_set_start_threshold) + (snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t); +static int (*ALSA_snd_pcm_sw_params) (snd_pcm_t *, snd_pcm_sw_params_t *); +static int (*ALSA_snd_pcm_nonblock) (snd_pcm_t *, int); +static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int); +static int (*ALSA_snd_pcm_sw_params_set_avail_min) + (snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t); + +#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC +#define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof +#define snd_pcm_sw_params_sizeof ALSA_snd_pcm_sw_params_sizeof + +static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC; +static void *alsa_handle = NULL; + +static int +load_alsa_sym(const char *fn, void **addr) +{ + *addr = SDL_LoadFunction(alsa_handle, fn); + if (*addr == NULL) { + + return 0; + } + + return 1; +} + +#define SDL_ALSA_SYM(x) \ + if (!load_alsa_sym(#x, (void **) (char *) &ALSA_##x)) return -1 +#else +#define SDL_ALSA_SYM(x) ALSA_##x = x +#endif + +static int +load_alsa_syms(void) +{ + SDL_ALSA_SYM(snd_pcm_open); + SDL_ALSA_SYM(snd_pcm_close); + SDL_ALSA_SYM(snd_pcm_writei); + SDL_ALSA_SYM(snd_pcm_recover); + SDL_ALSA_SYM(snd_pcm_prepare); + SDL_ALSA_SYM(snd_pcm_drain); + SDL_ALSA_SYM(snd_strerror); + SDL_ALSA_SYM(snd_pcm_hw_params_sizeof); + SDL_ALSA_SYM(snd_pcm_sw_params_sizeof); + SDL_ALSA_SYM(snd_pcm_hw_params_copy); + SDL_ALSA_SYM(snd_pcm_hw_params_any); + SDL_ALSA_SYM(snd_pcm_hw_params_set_access); + SDL_ALSA_SYM(snd_pcm_hw_params_set_format); + SDL_ALSA_SYM(snd_pcm_hw_params_set_channels); + SDL_ALSA_SYM(snd_pcm_hw_params_get_channels); + SDL_ALSA_SYM(snd_pcm_hw_params_set_rate_near); + SDL_ALSA_SYM(snd_pcm_hw_params_set_period_size_near); + SDL_ALSA_SYM(snd_pcm_hw_params_get_period_size); + SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_near); + SDL_ALSA_SYM(snd_pcm_hw_params_get_periods); + SDL_ALSA_SYM(snd_pcm_hw_params_set_buffer_size_near); + SDL_ALSA_SYM(snd_pcm_hw_params_get_buffer_size); + SDL_ALSA_SYM(snd_pcm_hw_params); + SDL_ALSA_SYM(snd_pcm_sw_params_current); + SDL_ALSA_SYM(snd_pcm_sw_params_set_start_threshold); + SDL_ALSA_SYM(snd_pcm_sw_params); + SDL_ALSA_SYM(snd_pcm_nonblock); + SDL_ALSA_SYM(snd_pcm_wait); + SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min); + return 0; +} + +#undef SDL_ALSA_SYM + +#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC + +static void +UnloadALSALibrary(void) +{ + if (alsa_handle != NULL) { + SDL_UnloadObject(alsa_handle); + alsa_handle = NULL; + } +} + +static int +LoadALSALibrary(void) +{ + int retval = 0; + if (alsa_handle == NULL) { + alsa_handle = SDL_LoadObject(alsa_library); + if (alsa_handle == NULL) { + retval = -1; + + } else { + retval = load_alsa_syms(); + if (retval < 0) { + UnloadALSALibrary(); + } + } + } + return retval; +} + +#else + +static void +UnloadALSALibrary(void) +{ +} + +static int +LoadALSALibrary(void) +{ + load_alsa_syms(); + return 0; +} + +#endif + +static const char * +ALSA_get_audio_device(int channels) +{ + const char *device; + + device = SDL_getenv("AUDIODEV"); + if (device == NULL) { + switch (channels) { + case 6: + device = "plug:surround51"; + break; + case 4: + device = "plug:surround40"; + break; + default: + device = "default"; + break; + } + } + return device; +} + +static void +ALSA_WaitDevice(_THIS) +{ + +} + +#define SWIZ6(T) \ + T *ptr = (T *) SDLAUDIOHIDDEN->mixbuf; \ + Uint32 i; \ + for (i = 0; i < this->spec.samples; i++, ptr += 6) { \ + T tmp; \ + tmp = ptr[2]; ptr[2] = ptr[4]; ptr[4] = tmp; \ + tmp = ptr[3]; ptr[3] = ptr[5]; ptr[5] = tmp; \ + } + +static SDL_INLINE void +swizzle_alsa_channels_6_64bit(_THIS) +{ + SWIZ6(Uint64); +} + +static SDL_INLINE void +swizzle_alsa_channels_6_32bit(_THIS) +{ + SWIZ6(Uint32); +} + +static SDL_INLINE void +swizzle_alsa_channels_6_16bit(_THIS) +{ + SWIZ6(Uint16); +} + +static SDL_INLINE void +swizzle_alsa_channels_6_8bit(_THIS) +{ + SWIZ6(Uint8); +} + +#undef SWIZ6 + +static SDL_INLINE void +swizzle_alsa_channels(_THIS) +{ + if (this->spec.channels == 6) { + const Uint16 fmtsize = (this->spec.format & 0xFF); + if (fmtsize == 16) + swizzle_alsa_channels_6_16bit(this); + else if (fmtsize == 8) + swizzle_alsa_channels_6_8bit(this); + else if (fmtsize == 32) + swizzle_alsa_channels_6_32bit(this); + else if (fmtsize == 64) + swizzle_alsa_channels_6_64bit(this); + } + +} + +static void +ALSA_PlayDevice(_THIS) +{ + int status; + const Uint8 *sample_buf = (const Uint8 *) SDLAUDIOHIDDEN->mixbuf; + const int frame_size = (((int) (this->spec.format & 0xFF)) / 8) * + this->spec.channels; + snd_pcm_uframes_t frames_left = ((snd_pcm_uframes_t) this->spec.samples); + + swizzle_alsa_channels(this); + + while ( frames_left > 0 && this->enabled ) { + + status = ALSA_snd_pcm_writei(SDLAUDIOHIDDEN->pcm_handle, + sample_buf, frames_left); + + if (status < 0) { + if (status == -EAGAIN) { + + SDL_Delay(1); + continue; + } + status = ALSA_snd_pcm_recover(SDLAUDIOHIDDEN->pcm_handle, status, 0); + if (status < 0) { + + fprintf(stderr, "ALSA write failed (unrecoverable): %s\n", + ALSA_snd_strerror(status)); + this->enabled = 0; + return; + } + continue; + } + sample_buf += status * frame_size; + frames_left -= status; + } +} + +static Uint8 * +ALSA_GetDeviceBuf(_THIS) +{ + return (SDLAUDIOHIDDEN->mixbuf); +} + +static void +ALSA_CloseDevice(_THIS) +{ + if (SDLAUDIOHIDDEN != NULL) { + SDL_FreeAudioMem(SDLAUDIOHIDDEN->mixbuf); + SDLAUDIOHIDDEN->mixbuf = NULL; + if (SDLAUDIOHIDDEN->pcm_handle) { + ALSA_snd_pcm_drain(SDLAUDIOHIDDEN->pcm_handle); + ALSA_snd_pcm_close(SDLAUDIOHIDDEN->pcm_handle); + SDLAUDIOHIDDEN->pcm_handle = NULL; + } + SDL_free(SDLAUDIOHIDDEN); + this->hidden = NULL; + } +} + +static int +ALSA_finalize_hardware(_THIS, snd_pcm_hw_params_t *hwparams, int override) +{ + int status; + snd_pcm_uframes_t bufsize; + + status = ALSA_snd_pcm_hw_params(SDLAUDIOHIDDEN->pcm_handle, hwparams); + if ( status < 0 ) { + return(-1); + } + + status = ALSA_snd_pcm_hw_params_get_buffer_size(hwparams, &bufsize); + if ( status < 0 ) { + return(-1); + } + if ( !override && bufsize != this->spec.samples * 2 ) { + return(-1); + } + + this->spec.samples = bufsize / 2; + + if ( SDL_getenv("SDL_AUDIO_ALSA_DEBUG") ) { + snd_pcm_uframes_t persize = 0; + unsigned int periods = 0; + + ALSA_snd_pcm_hw_params_get_period_size(hwparams, &persize, NULL); + ALSA_snd_pcm_hw_params_get_periods(hwparams, &periods, NULL); + + fprintf(stderr, + "ALSA: period size = %ld, periods = %u, buffer size = %lu\n", + persize, periods, bufsize); + } + + return(0); +} + +static int +ALSA_set_period_size(_THIS, snd_pcm_hw_params_t *params, int override) +{ + const char *env; + int status; + snd_pcm_hw_params_t *hwparams; + snd_pcm_uframes_t frames; + unsigned int periods; + + snd_pcm_hw_params_alloca(&hwparams); + ALSA_snd_pcm_hw_params_copy(hwparams, params); + + if ( !override ) { + env = SDL_getenv("SDL_AUDIO_ALSA_SET_PERIOD_SIZE"); + if ( env ) { + override = SDL_atoi(env); + if ( override == 0 ) { + return(-1); + } + } + } + + frames = this->spec.samples; + status = ALSA_snd_pcm_hw_params_set_period_size_near( + SDLAUDIOHIDDEN->pcm_handle, hwparams, &frames, NULL); + if ( status < 0 ) { + return(-1); + } + + periods = 2; + status = ALSA_snd_pcm_hw_params_set_periods_near( + SDLAUDIOHIDDEN->pcm_handle, hwparams, &periods, NULL); + if ( status < 0 ) { + return(-1); + } + + return ALSA_finalize_hardware(this, hwparams, override); +} + +static int +ALSA_set_buffer_size(_THIS, snd_pcm_hw_params_t *params, int override) +{ + const char *env; + int status; + snd_pcm_hw_params_t *hwparams; + snd_pcm_uframes_t frames; + + snd_pcm_hw_params_alloca(&hwparams); + ALSA_snd_pcm_hw_params_copy(hwparams, params); + + if ( !override ) { + env = SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE"); + if ( env ) { + override = SDL_atoi(env); + if ( override == 0 ) { + return(-1); + } + } + } + + frames = this->spec.samples * 2; + status = ALSA_snd_pcm_hw_params_set_buffer_size_near( + SDLAUDIOHIDDEN->pcm_handle, hwparams, &frames); + if ( status < 0 ) { + return(-1); + } + + return ALSA_finalize_hardware(this, hwparams, override); +} + +static int +ALSA_OpenDevice(_THIS, const char *devname, int iscapture) +{ + int status = 0; + snd_pcm_t *pcm_handle = NULL; + snd_pcm_hw_params_t *hwparams = NULL; + snd_pcm_sw_params_t *swparams = NULL; + snd_pcm_format_t format = 0; + SDL_AudioFormat test_format = 0; + unsigned int rate = 0; + unsigned int channels = 0; + + this->hidden = (struct SDL_PrivateAudioData *) + SDL_malloc((sizeof *SDLAUDIOHIDDEN)); + if (SDLAUDIOHIDDEN == NULL) { + return SDL_OutOfMemory(); + } + SDL_memset(SDLAUDIOHIDDEN, 0, (sizeof *SDLAUDIOHIDDEN)); + + status = ALSA_snd_pcm_open(&pcm_handle, + ALSA_get_audio_device(this->spec.channels), + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + + if (status < 0) { + ALSA_CloseDevice(this); + return SDL_SetError("ALSA: Couldn't open audio device: %s", + ALSA_snd_strerror(status)); + } + + SDLAUDIOHIDDEN->pcm_handle = pcm_handle; + + snd_pcm_hw_params_alloca(&hwparams); + status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams); + if (status < 0) { + ALSA_CloseDevice(this); + return SDL_SetError("ALSA: Couldn't get hardware config: %s", + ALSA_snd_strerror(status)); + } + + status = ALSA_snd_pcm_hw_params_set_access(pcm_handle, hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (status < 0) { + ALSA_CloseDevice(this); + return SDL_SetError("ALSA: Couldn't set interleaved access: %s", + ALSA_snd_strerror(status)); + } + + status = -1; + for (test_format = SDL_FirstAudioFormat(this->spec.format); + test_format && (status < 0);) { + status = 0; + switch (test_format) { + case AUDIO_U8: + format = SND_PCM_FORMAT_U8; + break; + case AUDIO_S8: + format = SND_PCM_FORMAT_S8; + break; + case AUDIO_S16LSB: + format = SND_PCM_FORMAT_S16_LE; + break; + case AUDIO_S16MSB: + format = SND_PCM_FORMAT_S16_BE; + break; + case AUDIO_U16LSB: + format = SND_PCM_FORMAT_U16_LE; + break; + case AUDIO_U16MSB: + format = SND_PCM_FORMAT_U16_BE; + break; + case AUDIO_S32LSB: + format = SND_PCM_FORMAT_S32_LE; + break; + case AUDIO_S32MSB: + format = SND_PCM_FORMAT_S32_BE; + break; + case AUDIO_F32LSB: + format = SND_PCM_FORMAT_FLOAT_LE; + break; + case AUDIO_F32MSB: + format = SND_PCM_FORMAT_FLOAT_BE; + break; + default: + status = -1; + break; + } + if (status >= 0) { + status = ALSA_snd_pcm_hw_params_set_format(pcm_handle, + hwparams, format); + } + if (status < 0) { + test_format = SDL_NextAudioFormat(); + } + } + if (status < 0) { + ALSA_CloseDevice(this); + return SDL_SetError("ALSA: Couldn't find any hardware audio formats"); + } + this->spec.format = test_format; + + status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams, + this->spec.channels); + channels = this->spec.channels; + if (status < 0) { + status = ALSA_snd_pcm_hw_params_get_channels(hwparams, &channels); + if (status < 0) { + ALSA_CloseDevice(this); + return SDL_SetError("ALSA: Couldn't set audio channels"); + } + this->spec.channels = channels; + } + + rate = this->spec.freq; + status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, + &rate, NULL); + if (status < 0) { + ALSA_CloseDevice(this); + return SDL_SetError("ALSA: Couldn't set audio frequency: %s", + ALSA_snd_strerror(status)); + } + this->spec.freq = rate; + + if ( ALSA_set_period_size(this, hwparams, 0) < 0 && + ALSA_set_buffer_size(this, hwparams, 0) < 0 ) { + + if ( ALSA_set_period_size(this, hwparams, 1) < 0 ) { + ALSA_CloseDevice(this); + return SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status)); + } + } + + snd_pcm_sw_params_alloca(&swparams); + status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams); + if (status < 0) { + ALSA_CloseDevice(this); + return SDL_SetError("ALSA: Couldn't get software config: %s", + ALSA_snd_strerror(status)); + } + status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, this->spec.samples); + if (status < 0) { + ALSA_CloseDevice(this); + return SDL_SetError("Couldn't set minimum available samples: %s", + ALSA_snd_strerror(status)); + } + status = + ALSA_snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 1); + if (status < 0) { + ALSA_CloseDevice(this); + return SDL_SetError("ALSA: Couldn't set start threshold: %s", + ALSA_snd_strerror(status)); + } + status = ALSA_snd_pcm_sw_params(pcm_handle, swparams); + if (status < 0) { + ALSA_CloseDevice(this); + return SDL_SetError("Couldn't set software audio parameters: %s", + ALSA_snd_strerror(status)); + } + + SDL_CalculateAudioSpec(&this->spec); + + SDLAUDIOHIDDEN->mixlen = this->spec.size; + SDLAUDIOHIDDEN->mixbuf = (Uint8 *) SDL_AllocAudioMem(SDLAUDIOHIDDEN->mixlen); + if (SDLAUDIOHIDDEN->mixbuf == NULL) { + ALSA_CloseDevice(this); + return SDL_OutOfMemory(); + } + SDL_memset(SDLAUDIOHIDDEN->mixbuf, this->spec.silence, SDLAUDIOHIDDEN->mixlen); + + ALSA_snd_pcm_nonblock(pcm_handle, 0); + + return 0; +} + +static void +ALSA_Deinitialize(void) +{ + UnloadALSALibrary(); +} + +static int +ALSA_Init(SDL_AudioDriverImpl * impl) +{ + if (LoadALSALibrary() < 0) { + return 0; + } + + impl->OpenDevice = ALSA_OpenDevice; + impl->WaitDevice = ALSA_WaitDevice; + impl->GetDeviceBuf = ALSA_GetDeviceBuf; + impl->PlayDevice = ALSA_PlayDevice; + impl->CloseDevice = ALSA_CloseDevice; + impl->Deinitialize = ALSA_Deinitialize; + impl->OnlyHasDefaultOutputDevice = 1; + + return 1; +} + +AudioBootStrap ALSA_bootstrap = { + "alsa", "ALSA PCM audio", ALSA_Init, 0 +}; + +#endif +#if SDL_AUDIO_DRIVER_ARTS + +#ifdef HAVE_SIGNAL_H +#include +#endif +#include +#include + +#define SDL_AllocAudioMem SDL_malloc +#define SDL_FreeAudioMem SDL_free +#ifndef _SDL_artscaudio_h +#define _SDL_artscaudio_h + +#include + +#undef _THIS +#define _THIS SDL_AudioDevice *this + +#undef SDLAUDIOHIDDEN +#define SDLAUDIOHIDDEN ((struct SDL_PrivateAudioDataARTS*)this->hidden) +struct SDL_PrivateAudioDataARTS +{ + + arts_stream_t stream; + + pid_t parent; + + Uint8 *mixbuf; + int mixlen; + + float frame_ticks; + float next_frame; +}; +#define FUDGE_TICKS 10 + +#endif + +#ifdef SDL_AUDIO_DRIVER_ARTS_DYNAMIC +#ifndef _SDLname_h_ +#define _SDLname_h_ + +#if defined(__STDC__) || defined(__cplusplus) +#define NeedFunctionPrototypes 1 +#endif + +#define SDL_NAME(X) SDL_##X + +#endif +#else +#define SDL_NAME(X) X +#endif + +#ifdef SDL_AUDIO_DRIVER_ARTS_DYNAMIC + +static const char *arts_library = SDL_AUDIO_DRIVER_ARTS_DYNAMIC; +static void *arts_handle = NULL; + +static int (*SDL_NAME(arts_init)) (void); +static void (*SDL_NAME(arts_free)) (void); +static arts_stream_t(*SDL_NAME(arts_play_stream)) (int rate, int bits, + int channels, + const char *name); +static int (*SDL_NAME(arts_stream_set)) (arts_stream_t s, + arts_parameter_t param, int value); +static int (*SDL_NAME(arts_stream_get)) (arts_stream_t s, + arts_parameter_t param); +static int (*SDL_NAME(arts_write)) (arts_stream_t s, const void *buffer, + int count); +static void (*SDL_NAME(arts_close_stream)) (arts_stream_t s); +static int (*SDL_NAME(arts_suspend))(void); +static int (*SDL_NAME(arts_suspended)) (void); +static const char *(*SDL_NAME(arts_error_text)) (int errorcode); + +#define SDL_ARTS_SYM(x) { #x, (void **) (char *) &SDL_NAME(x) } +static struct +{ + const char *name; + void **func; +} arts_functions[] = { + + SDL_ARTS_SYM(arts_init), + SDL_ARTS_SYM(arts_free), + SDL_ARTS_SYM(arts_play_stream), + SDL_ARTS_SYM(arts_stream_set), + SDL_ARTS_SYM(arts_stream_get), + SDL_ARTS_SYM(arts_write), + SDL_ARTS_SYM(arts_close_stream), + SDL_ARTS_SYM(arts_suspend), + SDL_ARTS_SYM(arts_suspended), + SDL_ARTS_SYM(arts_error_text), + +}; + +#undef SDL_ARTS_SYM + +static void +UnloadARTSLibrary() +{ + if (arts_handle != NULL) { + SDL_UnloadObject(arts_handle); + arts_handle = NULL; + } +} + +static int +LoadARTSLibrary(void) +{ + int i, retval = -1; + + if (arts_handle == NULL) { + arts_handle = SDL_LoadObject(arts_library); + if (arts_handle != NULL) { + retval = 0; + for (i = 0; i < SDL_arraysize(arts_functions); ++i) { + *arts_functions[i].func = + SDL_LoadFunction(arts_handle, arts_functions[i].name); + if (!*arts_functions[i].func) { + retval = -1; + UnloadARTSLibrary(); + break; + } + } + } + } + + return retval; +} + +#else + +static void +UnloadARTSLibrary() +{ + return; +} + +static int +LoadARTSLibrary(void) +{ + return 0; +} + +#endif + +static void +ARTS_WaitDevice(_THIS) +{ + Sint32 ticks; + + { + static int cnt = 0; + + if (SDLAUDIOHIDDEN->parent && (((++cnt) % 10) == 0)) { + if (kill(SDLAUDIOHIDDEN->parent, 0) < 0 && errno == ESRCH) { + this->enabled = 0; + } + } + } + + ticks = + ((Sint32) (SDLAUDIOHIDDEN->next_frame - SDL_GetTicks())) - FUDGE_TICKS; + if (ticks > 0) { + SDL_Delay(ticks); + } +} + +static void +ARTS_PlayDevice(_THIS) +{ + + int written = SDL_NAME(arts_write) (SDLAUDIOHIDDEN->stream, + SDLAUDIOHIDDEN->mixbuf, + SDLAUDIOHIDDEN->mixlen); + + if (SDLAUDIOHIDDEN->frame_ticks) { + SDLAUDIOHIDDEN->next_frame += SDLAUDIOHIDDEN->frame_ticks; + } + + if (written < 0) { + this->enabled = 0; + } +#ifdef DEBUG_AUDIO + fprintf(stderr, "Wrote %d bytes of audio data\n", written); +#endif +} + +static void +ARTS_WaitDone(_THIS) +{ + +} + +static Uint8 * +ARTS_GetDeviceBuf(_THIS) +{ + return (SDLAUDIOHIDDEN->mixbuf); +} + +static void +ARTS_CloseDevice(_THIS) +{ + if (SDLAUDIOHIDDEN != NULL) { + SDL_FreeAudioMem(SDLAUDIOHIDDEN->mixbuf); + SDLAUDIOHIDDEN->mixbuf = NULL; + if (SDLAUDIOHIDDEN->stream) { + SDL_NAME(arts_close_stream) (SDLAUDIOHIDDEN->stream); + SDLAUDIOHIDDEN->stream = 0; + } + SDL_NAME(arts_free) (); + SDL_free(SDLAUDIOHIDDEN); + this->hidden = NULL; + } +} + +static int +ARTS_Suspend(void) +{ + const Uint32 abortms = SDL_GetTicks() + 3000; + while ( (!SDL_NAME(arts_suspended)()) && !SDL_TICKS_PASSED(SDL_GetTicks(), abortms) ) { + if ( SDL_NAME(arts_suspend)() ) { + break; + } + } + return SDL_NAME(arts_suspended)(); +} + +static int +ARTS_OpenDevice(_THIS, const char *devname, int iscapture) +{ + int rc = 0; + int bits = 0, frag_spec = 0; + SDL_AudioFormat test_format = 0, format = 0; + + this->hidden = (struct SDL_PrivateAudioData *) + SDL_malloc((sizeof *SDLAUDIOHIDDEN)); + if (SDLAUDIOHIDDEN == NULL) { + return SDL_OutOfMemory(); + } + SDL_memset(SDLAUDIOHIDDEN, 0, (sizeof *SDLAUDIOHIDDEN)); + + for (test_format = SDL_FirstAudioFormat(this->spec.format); + !format && test_format;) { +#ifdef DEBUG_AUDIO + fprintf(stderr, "Trying format 0x%4.4x\n", test_format); +#endif + switch (test_format) { + case AUDIO_U8: + bits = 8; + format = 1; + break; + case AUDIO_S16LSB: + bits = 16; + format = 1; + break; + default: + format = 0; + break; + } + if (!format) { + test_format = SDL_NextAudioFormat(); + } + } + if (format == 0) { + ARTS_CloseDevice(this); + return SDL_SetError("Couldn't find any hardware audio formats"); + } + this->spec.format = test_format; + + if ((rc = SDL_NAME(arts_init) ()) != 0) { + ARTS_CloseDevice(this); + return SDL_SetError("Unable to initialize ARTS: %s", + SDL_NAME(arts_error_text) (rc)); + } + + if (!ARTS_Suspend()) { + ARTS_CloseDevice(this); + return SDL_SetError("ARTS can not open audio device"); + } + + SDLAUDIOHIDDEN->stream = SDL_NAME(arts_play_stream) (this->spec.freq, + bits, + this->spec.channels, + "SDL"); + + SDL_NAME(arts_write) (SDLAUDIOHIDDEN->stream, "", 0); + + SDL_CalculateAudioSpec(&this->spec); + + for (frag_spec = 0; (0x01 << frag_spec) < this->spec.size; ++frag_spec); + if ((0x01 << frag_spec) != this->spec.size) { + ARTS_CloseDevice(this); + return SDL_SetError("Fragment size must be a power of two"); + } + frag_spec |= 0x00020000; + +#ifdef ARTS_P_PACKET_SETTINGS + SDL_NAME(arts_stream_set) (SDLAUDIOHIDDEN->stream, + ARTS_P_PACKET_SETTINGS, frag_spec); +#else + SDL_NAME(arts_stream_set) (SDLAUDIOHIDDEN->stream, ARTS_P_PACKET_SIZE, + frag_spec & 0xffff); + SDL_NAME(arts_stream_set) (SDLAUDIOHIDDEN->stream, ARTS_P_PACKET_COUNT, + frag_spec >> 16); +#endif + this->spec.size = SDL_NAME(arts_stream_get) (SDLAUDIOHIDDEN->stream, + ARTS_P_PACKET_SIZE); + + SDLAUDIOHIDDEN->mixlen = this->spec.size; + SDLAUDIOHIDDEN->mixbuf = (Uint8 *) SDL_AllocAudioMem(SDLAUDIOHIDDEN->mixlen); + if (SDLAUDIOHIDDEN->mixbuf == NULL) { + ARTS_CloseDevice(this); + return SDL_OutOfMemory(); + } + SDL_memset(SDLAUDIOHIDDEN->mixbuf, this->spec.silence, this->spec.size); + + SDLAUDIOHIDDEN->parent = getpid(); + + return 0; +} + +static void +ARTS_Deinitialize(void) +{ + UnloadARTSLibrary(); +} + +static int +ARTS_Init(SDL_AudioDriverImpl * impl) +{ + if (LoadARTSLibrary() < 0) { + return 0; + } else { + if (SDL_NAME(arts_init) () != 0) { + UnloadARTSLibrary(); + SDL_SetError("ARTS: arts_init failed (no audio server?)"); + return 0; + } + + if (ARTS_Suspend()) { + arts_stream_t stream; + stream = SDL_NAME(arts_play_stream) (44100, 16, 2, "SDL"); + SDL_NAME(arts_write) (stream, "", 0); + SDL_NAME(arts_close_stream) (stream); + } + + SDL_NAME(arts_free) (); + } + + impl->OpenDevice = ARTS_OpenDevice; + impl->PlayDevice = ARTS_PlayDevice; + impl->WaitDevice = ARTS_WaitDevice; + impl->GetDeviceBuf = ARTS_GetDeviceBuf; + impl->CloseDevice = ARTS_CloseDevice; + impl->WaitDone = ARTS_WaitDone; + impl->Deinitialize = ARTS_Deinitialize; + impl->OnlyHasDefaultOutputDevice = 1; + + return 1; +} + +AudioBootStrap ARTS_bootstrap = { + "arts", "Analog RealTime Synthesizer", ARTS_Init, 0 +}; + +#endif +#if SDL_AUDIO_DRIVER_BSD + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SDL_AllocAudioMem SDL_malloc +#define SDL_FreeAudioMem SDL_free +#ifdef USE_BLOCKING_WRITES +#define OPEN_FLAGS_OUTPUT O_WRONLY +#define OPEN_FLAGS_INPUT O_RDONLY +#else +#define OPEN_FLAGS_OUTPUT (O_WRONLY|O_NONBLOCK) +#define OPEN_FLAGS_INPUT (O_RDONLY|O_NONBLOCK) +#endif + +void SDL_EnumUnixAudioDevices(int iscapture, int classic, + int (*test) (int fd), SDL_AddAudioDevice addfn); +#ifndef _SDL_bsdaudio_h +#define _SDL_bsdaudio_h + +#undef _THIS +#define _THIS SDL_AudioDevice *this + +#undef SDLAUDIOHIDDEN +#define SDLAUDIOHIDDEN ((struct SDL_PrivateAudioDataBSDAUDIO*)this->hidden) +struct SDL_PrivateAudioDataBSDAUDIO +{ + + int audio_fd; + + pid_t parent; + + Uint8 *mixbuf; + int mixlen; + + float frame_ticks; + float next_frame; +}; + +#define FUDGE_TICKS 10 + +#endif + +static void +BSDAUDIO_DetectDevices(int iscapture, SDL_AddAudioDevice addfn) +{ + SDL_EnumUnixAudioDevices(iscapture, 0, NULL, addfn); +} + +static void +BSDAUDIO_Status(_THIS) +{ +#ifdef DEBUG_AUDIO + + audio_info_t info; + + if (ioctl(SDLAUDIOHIDDEN->audio_fd, AUDIO_GETINFO, &info) < 0) { + fprintf(stderr, "AUDIO_GETINFO failed.\n"); + return; + } + fprintf(stderr, "\n" + "[play/record info]\n" + "buffer size : %d bytes\n" + "sample rate : %i Hz\n" + "channels : %i\n" + "precision : %i-bit\n" + "encoding : 0x%x\n" + "seek : %i\n" + "sample count : %i\n" + "EOF count : %i\n" + "paused : %s\n" + "error occured : %s\n" + "waiting : %s\n" + "active : %s\n" + "", + info.play.buffer_size, + info.play.sample_rate, + info.play.channels, + info.play.precision, + info.play.encoding, + info.play.seek, + info.play.samples, + info.play.eof, + info.play.pause ? "yes" : "no", + info.play.error ? "yes" : "no", + info.play.waiting ? "yes" : "no", + info.play.active ? "yes" : "no"); + + fprintf(stderr, "\n" + "[audio info]\n" + "monitor_gain : %i\n" + "hw block size : %d bytes\n" + "hi watermark : %i\n" + "lo watermark : %i\n" + "audio mode : %s\n" + "", + info.monitor_gain, + info.blocksize, + info.hiwat, info.lowat, + (info.mode == AUMODE_PLAY) ? "PLAY" + : (info.mode = AUMODE_RECORD) ? "RECORD" + : (info.mode == AUMODE_PLAY_ALL ? "PLAY_ALL" : "?")); + +#endif +} + +static void +BSDAUDIO_WaitDevice(_THIS) +{ +#ifndef USE_BLOCKING_WRITES + + if (SDLAUDIOHIDDEN->frame_ticks) { + + Sint32 ticks; + + ticks = ((Sint32) (SDLAUDIOHIDDEN->next_frame - SDL_GetTicks())) - FUDGE_TICKS; + if (ticks > 0) { + SDL_Delay(ticks); + } + } else { + + fd_set fdset; + struct timeval timeout; + + FD_ZERO(&fdset); + FD_SET(SDLAUDIOHIDDEN->audio_fd, &fdset); + timeout.tv_sec = 10; + timeout.tv_usec = 0; +#ifdef DEBUG_AUDIO + fprintf(stderr, "Waiting for audio to get ready\n"); +#endif + if (select(SDLAUDIOHIDDEN->audio_fd + 1, NULL, &fdset, NULL, &timeout) + <= 0) { + const char *message = + "Audio timeout - buggy audio driver? (disabled)"; + + fprintf(stderr, "SDL: %s\n", message); + this->enabled = 0; + + SDLAUDIOHIDDEN->audio_fd = -1; +#ifdef DEBUG_AUDIO + fprintf(stderr, "Done disabling audio\n"); +#endif + } +#ifdef DEBUG_AUDIO + fprintf(stderr, "Ready!\n"); +#endif + } +#endif +} + +static void +BSDAUDIO_PlayDevice(_THIS) +{ + int written, p = 0; + + do { + written = write(SDLAUDIOHIDDEN->audio_fd, + &SDLAUDIOHIDDEN->mixbuf[p], SDLAUDIOHIDDEN->mixlen - p); + + if (written > 0) + p += written; + if (written == -1 && errno != 0 && errno != EAGAIN && errno != EINTR) { + + perror("audio"); + break; + } + + if (p < written + || ((written < 0) && ((errno == 0) || (errno == EAGAIN)))) { + SDL_Delay(1); + } + } while (p < written); + + if (SDLAUDIOHIDDEN->frame_ticks) { + SDLAUDIOHIDDEN->next_frame += SDLAUDIOHIDDEN->frame_ticks; + } + + if (written < 0) { + this->enabled = 0; + } +#ifdef DEBUG_AUDIO + fprintf(stderr, "Wrote %d bytes of audio data\n", written); +#endif +} + +static Uint8 * +BSDAUDIO_GetDeviceBuf(_THIS) +{ + return (SDLAUDIOHIDDEN->mixbuf); +} + +static void +BSDAUDIO_CloseDevice(_THIS) +{ + if (SDLAUDIOHIDDEN != NULL) { + SDL_FreeAudioMem(SDLAUDIOHIDDEN->mixbuf); + SDLAUDIOHIDDEN->mixbuf = NULL; + if (SDLAUDIOHIDDEN->audio_fd >= 0) { + close(SDLAUDIOHIDDEN->audio_fd); + SDLAUDIOHIDDEN->audio_fd = -1; + } + SDL_free(SDLAUDIOHIDDEN); + this->hidden = NULL; + } +} + +static int +BSDAUDIO_OpenDevice(_THIS, const char *devname, int iscapture) +{ + const int flags = ((iscapture) ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT); + SDL_AudioFormat format = 0; + audio_info_t info; + + if (devname == NULL) { + devname = SDL_GetAudioDeviceName(0, iscapture); + if (devname == NULL) { + return SDL_SetError("No such audio device"); + } + } + + this->hidden = (struct SDL_PrivateAudioData *) + SDL_malloc((sizeof *SDLAUDIOHIDDEN)); + if (SDLAUDIOHIDDEN == NULL) { + return SDL_OutOfMemory(); + } + SDL_memset(SDLAUDIOHIDDEN, 0, (sizeof *SDLAUDIOHIDDEN)); + + SDLAUDIOHIDDEN->audio_fd = open(devname, flags, 0); + if (SDLAUDIOHIDDEN->audio_fd < 0) { + return SDL_SetError("Couldn't open %s: %s", devname, strerror(errno)); + } + + AUDIO_INITINFO(&info); + + SDL_CalculateAudioSpec(&this->spec); + + info.mode = AUMODE_PLAY; + if (ioctl(SDLAUDIOHIDDEN->audio_fd, AUDIO_SETINFO, &info) < 0) { + BSDAUDIO_CloseDevice(this); + return SDL_SetError("Couldn't put device into play mode"); + } + + AUDIO_INITINFO(&info); + for (format = SDL_FirstAudioFormat(this->spec.format); + format; format = SDL_NextAudioFormat()) { + switch (format) { + case AUDIO_U8: + info.play.encoding = AUDIO_ENCODING_ULINEAR; + info.play.precision = 8; + break; + case AUDIO_S8: + info.play.encoding = AUDIO_ENCODING_SLINEAR; + info.play.precision = 8; + break; + case AUDIO_S16LSB: + info.play.encoding = AUDIO_ENCODING_SLINEAR_LE; + info.play.precision = 16; + break; + case AUDIO_S16MSB: + info.play.encoding = AUDIO_ENCODING_SLINEAR_BE; + info.play.precision = 16; + break; + case AUDIO_U16LSB: + info.play.encoding = AUDIO_ENCODING_ULINEAR_LE; + info.play.precision = 16; + break; + case AUDIO_U16MSB: + info.play.encoding = AUDIO_ENCODING_ULINEAR_BE; + info.play.precision = 16; + break; + default: + continue; + } + + if (ioctl(SDLAUDIOHIDDEN->audio_fd, AUDIO_SETINFO, &info) == 0) { + break; + } + } + + if (!format) { + BSDAUDIO_CloseDevice(this); + return SDL_SetError("No supported encoding for 0x%x", this->spec.format); + } + + this->spec.format = format; + + AUDIO_INITINFO(&info); + info.play.channels = this->spec.channels; + if (ioctl(SDLAUDIOHIDDEN->audio_fd, AUDIO_SETINFO, &info) == -1) { + this->spec.channels = 1; + } + AUDIO_INITINFO(&info); + info.play.sample_rate = this->spec.freq; + info.blocksize = this->spec.size; + info.hiwat = 5; + info.lowat = 3; + (void) ioctl(SDLAUDIOHIDDEN->audio_fd, AUDIO_SETINFO, &info); + (void) ioctl(SDLAUDIOHIDDEN->audio_fd, AUDIO_GETINFO, &info); + this->spec.freq = info.play.sample_rate; + + SDLAUDIOHIDDEN->mixlen = this->spec.size; + SDLAUDIOHIDDEN->mixbuf = (Uint8 *) SDL_AllocAudioMem(SDLAUDIOHIDDEN->mixlen); + if (SDLAUDIOHIDDEN->mixbuf == NULL) { + BSDAUDIO_CloseDevice(this); + return SDL_OutOfMemory(); + } + SDL_memset(SDLAUDIOHIDDEN->mixbuf, this->spec.silence, this->spec.size); + + BSDAUDIO_Status(this); + + return 0; +} + +static int +BSDAUDIO_Init(SDL_AudioDriverImpl * impl) +{ + + impl->DetectDevices = BSDAUDIO_DetectDevices; + impl->OpenDevice = BSDAUDIO_OpenDevice; + impl->PlayDevice = BSDAUDIO_PlayDevice; + impl->WaitDevice = BSDAUDIO_WaitDevice; + impl->GetDeviceBuf = BSDAUDIO_GetDeviceBuf; + impl->CloseDevice = BSDAUDIO_CloseDevice; + + return 1; +} + +AudioBootStrap BSD_AUDIO_bootstrap = { + "bsd", "BSD audio", BSDAUDIO_Init, 0 +}; + +#endif +#if SDL_AUDIO_DRIVER_COREAUDIO + +#ifndef _SDL_coreaudio_h +#define _SDL_coreaudio_h + +#if !defined(__IPHONEOS__) +#define MACOSX_COREAUDIO 1 +#endif + +#if MACOSX_COREAUDIO +#include +#include +#else +#include +#endif + +#include + +#undef _THIS +#define _THIS SDL_AudioDevice *this + +#undef SDLAUDIOHIDDEN +#define SDLAUDIOHIDDEN ((struct SDL_PrivateAudioDataCOREAUDIO*)this->hidden) +struct SDL_PrivateAudioDataCOREAUDIO +{ + AudioUnit audioUnit; + int audioUnitOpened; + void *buffer; + UInt32 bufferOffset; + UInt32 bufferSize; +#if MACOSX_COREAUDIO + AudioDeviceID deviceID; +#endif +}; + +#endif + +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +#define DEBUG_COREAUDIO 0 + +static void COREAUDIO_CloseDevice(_THIS); + +#define CHECK_RESULT(msg) \ + if (result != noErr) { \ + COREAUDIO_CloseDevice(this); \ + SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \ + return 0; \ + } + +#if MACOSX_COREAUDIO +typedef void (*addDevFn)(const char *name, AudioDeviceID devId, void *data); + +static void +addToDevList(const char *name, AudioDeviceID devId, void *data) +{ + SDL_AddAudioDevice addfn = (SDL_AddAudioDevice) data; + addfn(name); +} + +typedef struct +{ + const char *findname; + AudioDeviceID devId; + int found; +} FindDevIdData; + +static void +findDevId(const char *name, AudioDeviceID devId, void *_data) +{ + FindDevIdData *data = (FindDevIdData *) _data; + if (!data->found) { + if (SDL_strcmp(name, data->findname) == 0) { + data->found = 1; + data->devId = devId; + } + } +} + +static void +build_device_list(int iscapture, addDevFn addfn, void *addfndata) +{ + OSStatus result = noErr; + UInt32 size = 0; + AudioDeviceID *devs = NULL; + UInt32 i = 0; + UInt32 max = 0; + + AudioObjectPropertyAddress addr = { + kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, + 0, NULL, &size); + if (result != kAudioHardwareNoError) + return; + + devs = (AudioDeviceID *) alloca(size); + if (devs == NULL) + return; + + result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, + 0, NULL, &size, devs); + if (result != kAudioHardwareNoError) + return; + + max = size / sizeof (AudioDeviceID); + for (i = 0; i < max; i++) { + CFStringRef cfstr = NULL; + char *ptr = NULL; + AudioDeviceID dev = devs[i]; + AudioBufferList *buflist = NULL; + int usable = 0; + CFIndex len = 0; + + addr.mScope = iscapture ? kAudioDevicePropertyScopeInput : + kAudioDevicePropertyScopeOutput; + addr.mSelector = kAudioDevicePropertyStreamConfiguration; + + result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size); + if (result != noErr) + continue; + + buflist = (AudioBufferList *) SDL_malloc(size); + if (buflist == NULL) + continue; + + result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, + &size, buflist); + + if (result == noErr) { + UInt32 j; + for (j = 0; j < buflist->mNumberBuffers; j++) { + if (buflist->mBuffers[j].mNumberChannels > 0) { + usable = 1; + break; + } + } + } + + SDL_free(buflist); + + if (!usable) + continue; + + addr.mSelector = kAudioObjectPropertyName; + size = sizeof (CFStringRef); + result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, &cfstr); + if (result != kAudioHardwareNoError) + continue; + + len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), + kCFStringEncodingUTF8); + + ptr = (char *) SDL_malloc(len + 1); + usable = ((ptr != NULL) && + (CFStringGetCString + (cfstr, ptr, len + 1, kCFStringEncodingUTF8))); + + CFRelease(cfstr); + + if (usable) { + len = strlen(ptr); + + while ((len > 0) && (ptr[len - 1] == ' ')) { + len--; + } + usable = (len > 0); + } + + if (usable) { + ptr[len] = '\0'; + +#if DEBUG_COREAUDIO + printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n", + ((iscapture) ? "capture" : "output"), + (int) *devCount, ptr, (int) dev); +#endif + addfn(ptr, dev, addfndata); + } + SDL_free(ptr); + } +} + +static void +COREAUDIO_DetectDevices(int iscapture, SDL_AddAudioDevice addfn) +{ + build_device_list(iscapture, addToDevList, addfn); +} + +static int +find_device_by_name(_THIS, const char *devname, int iscapture) +{ + AudioDeviceID devid = 0; + OSStatus result = noErr; + UInt32 size = 0; + UInt32 alive = 0; + pid_t pid = 0; + + AudioObjectPropertyAddress addr = { + 0, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + if (devname == NULL) { + size = sizeof (AudioDeviceID); + addr.mSelector = + ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice : + kAudioHardwarePropertyDefaultOutputDevice); + result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, + 0, NULL, &size, &devid); + CHECK_RESULT("AudioHardwareGetProperty (default device)"); + } else { + FindDevIdData data; + SDL_zero(data); + data.findname = devname; + build_device_list(iscapture, findDevId, &data); + if (!data.found) { + SDL_SetError("CoreAudio: No such audio device."); + return 0; + } + devid = data.devId; + } + + addr.mSelector = kAudioDevicePropertyDeviceIsAlive; + addr.mScope = iscapture ? kAudioDevicePropertyScopeInput : + kAudioDevicePropertyScopeOutput; + + size = sizeof (alive); + result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive); + CHECK_RESULT + ("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)"); + + if (!alive) { + SDL_SetError("CoreAudio: requested device exists, but isn't alive."); + return 0; + } + + addr.mSelector = kAudioDevicePropertyHogMode; + size = sizeof (pid); + result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid); + + if ((result == noErr) && (pid != -1)) { + SDL_SetError("CoreAudio: requested device is being hogged."); + return 0; + } + + SDLAUDIOHIDDEN->deviceID = devid; + return 1; +} +#endif + +static OSStatus +outputCallback(void *inRefCon, + AudioUnitRenderActionFlags * ioActionFlags, + const AudioTimeStamp * inTimeStamp, + UInt32 inBusNumber, UInt32 inNumberFrames, + AudioBufferList * ioData) +{ + SDL_AudioDevice *this = (SDL_AudioDevice *) inRefCon; + AudioBuffer *abuf; + UInt32 remaining, len; + void *ptr; + UInt32 i; + + if (!this->enabled || this->paused) { + for (i = 0; i < ioData->mNumberBuffers; i++) { + abuf = &ioData->mBuffers[i]; + SDL_memset(abuf->mData, this->spec.silence, abuf->mDataByteSize); + } + return 0; + } + + for (i = 0; i < ioData->mNumberBuffers; i++) { + abuf = &ioData->mBuffers[i]; + remaining = abuf->mDataByteSize; + ptr = abuf->mData; + while (remaining > 0) { + if (SDLAUDIOHIDDEN->bufferOffset >= SDLAUDIOHIDDEN->bufferSize) { + + SDL_LockMutex(this->mixer_lock); + (*this->spec.callback)(this->spec.userdata, + SDLAUDIOHIDDEN->buffer, SDLAUDIOHIDDEN->bufferSize); + SDL_UnlockMutex(this->mixer_lock); + SDLAUDIOHIDDEN->bufferOffset = 0; + } + + len = SDLAUDIOHIDDEN->bufferSize - SDLAUDIOHIDDEN->bufferOffset; + if (len > remaining) + len = remaining; + SDL_memcpy(ptr, (char *)SDLAUDIOHIDDEN->buffer + + SDLAUDIOHIDDEN->bufferOffset, len); + ptr = (char *)ptr + len; + remaining -= len; + SDLAUDIOHIDDEN->bufferOffset += len; + } + } + + return 0; +} + +static OSStatus +inputCallback(void *inRefCon, + AudioUnitRenderActionFlags * ioActionFlags, + const AudioTimeStamp * inTimeStamp, + UInt32 inBusNumber, UInt32 inNumberFrames, + AudioBufferList * ioData) +{ + + return noErr; +} + +static void +COREAUDIO_CloseDevice(_THIS) +{ + if (SDLAUDIOHIDDEN != NULL) { + if (SDLAUDIOHIDDEN->audioUnitOpened) { + OSStatus result = noErr; + AURenderCallbackStruct callback; + const AudioUnitElement output_bus = 0; + const AudioUnitElement input_bus = 1; + const int iscapture = this->iscapture; + const AudioUnitElement bus = + ((iscapture) ? input_bus : output_bus); + const AudioUnitScope scope = + ((iscapture) ? kAudioUnitScope_Output : + kAudioUnitScope_Input); + + result = AudioOutputUnitStop(SDLAUDIOHIDDEN->audioUnit); + + SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct)); + result = AudioUnitSetProperty(SDLAUDIOHIDDEN->audioUnit, + kAudioUnitProperty_SetRenderCallback, + scope, bus, &callback, + sizeof(callback)); + + #if MACOSX_COREAUDIO + CloseComponent(SDLAUDIOHIDDEN->audioUnit); + #else + AudioComponentInstanceDispose(SDLAUDIOHIDDEN->audioUnit); + #endif + + SDLAUDIOHIDDEN->audioUnitOpened = 0; + } + SDL_free(SDLAUDIOHIDDEN->buffer); + SDL_free(SDLAUDIOHIDDEN); + this->hidden = NULL; + } +} + +static int +prepare_audiounit(_THIS, const char *devname, int iscapture, + const AudioStreamBasicDescription * strdesc) +{ + OSStatus result = noErr; + AURenderCallbackStruct callback; +#if MACOSX_COREAUDIO + ComponentDescription desc; + Component comp = NULL; +#else + AudioComponentDescription desc; + AudioComponent comp = NULL; +#endif + const AudioUnitElement output_bus = 0; + const AudioUnitElement input_bus = 1; + const AudioUnitElement bus = ((iscapture) ? input_bus : output_bus); + const AudioUnitScope scope = ((iscapture) ? kAudioUnitScope_Output : + kAudioUnitScope_Input); + +#if MACOSX_COREAUDIO + if (!find_device_by_name(this, devname, iscapture)) { + SDL_SetError("Couldn't find requested CoreAudio device"); + return 0; + } +#endif + + SDL_zero(desc); + desc.componentType = kAudioUnitType_Output; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + +#if MACOSX_COREAUDIO + desc.componentSubType = kAudioUnitSubType_DefaultOutput; + comp = FindNextComponent(NULL, &desc); +#else + desc.componentSubType = kAudioUnitSubType_RemoteIO; + comp = AudioComponentFindNext(NULL, &desc); +#endif + + if (comp == NULL) { + SDL_SetError("Couldn't find requested CoreAudio component"); + return 0; + } + +#if MACOSX_COREAUDIO + result = OpenAComponent(comp, &SDLAUDIOHIDDEN->audioUnit); + CHECK_RESULT("OpenAComponent"); +#else + + result = AudioComponentInstanceNew(comp, &SDLAUDIOHIDDEN->audioUnit); + CHECK_RESULT("AudioComponentInstanceNew"); +#endif + + SDLAUDIOHIDDEN->audioUnitOpened = 1; + +#if MACOSX_COREAUDIO + result = AudioUnitSetProperty(SDLAUDIOHIDDEN->audioUnit, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, 0, + &SDLAUDIOHIDDEN->deviceID, + sizeof(AudioDeviceID)); + CHECK_RESULT + ("AudioUnitSetProperty (kAudioOutputUnitProperty_CurrentDevice)"); +#endif + + result = AudioUnitSetProperty(SDLAUDIOHIDDEN->audioUnit, + kAudioUnitProperty_StreamFormat, + scope, bus, strdesc, sizeof(*strdesc)); + CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_StreamFormat)"); + + SDL_memset(&callback, 0, sizeof(AURenderCallbackStruct)); + callback.inputProc = ((iscapture) ? inputCallback : outputCallback); + callback.inputProcRefCon = this; + result = AudioUnitSetProperty(SDLAUDIOHIDDEN->audioUnit, + kAudioUnitProperty_SetRenderCallback, + scope, bus, &callback, sizeof(callback)); + CHECK_RESULT + ("AudioUnitSetProperty (kAudioUnitProperty_SetRenderCallback)"); + + SDL_CalculateAudioSpec(&this->spec); + + SDLAUDIOHIDDEN->bufferOffset = SDLAUDIOHIDDEN->bufferSize = this->spec.size; + SDLAUDIOHIDDEN->buffer = SDL_malloc(SDLAUDIOHIDDEN->bufferSize); + + result = AudioUnitInitialize(SDLAUDIOHIDDEN->audioUnit); + CHECK_RESULT("AudioUnitInitialize"); + + result = AudioOutputUnitStart(SDLAUDIOHIDDEN->audioUnit); + CHECK_RESULT("AudioOutputUnitStart"); + + return 1; +} + +static int +COREAUDIO_OpenDevice(_THIS, const char *devname, int iscapture) +{ + AudioStreamBasicDescription strdesc; + SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); + int valid_datatype = 0; + + this->hidden = (struct SDL_PrivateAudioData *) + SDL_malloc((sizeof *SDLAUDIOHIDDEN)); + if (SDLAUDIOHIDDEN == NULL) { + return SDL_OutOfMemory(); + } + SDL_memset(SDLAUDIOHIDDEN, 0, (sizeof *SDLAUDIOHIDDEN)); + + SDL_memset(&strdesc, '\0', sizeof(AudioStreamBasicDescription)); + strdesc.mFormatID = kAudioFormatLinearPCM; + strdesc.mFormatFlags = kLinearPCMFormatFlagIsPacked; + strdesc.mChannelsPerFrame = this->spec.channels; + strdesc.mSampleRate = this->spec.freq; + strdesc.mFramesPerPacket = 1; + + while ((!valid_datatype) && (test_format)) { + this->spec.format = test_format; + + switch (test_format) { + case AUDIO_U8: + case AUDIO_S8: + case AUDIO_U16LSB: + case AUDIO_S16LSB: + case AUDIO_U16MSB: + case AUDIO_S16MSB: + case AUDIO_S32LSB: + case AUDIO_S32MSB: + case AUDIO_F32LSB: + case AUDIO_F32MSB: + valid_datatype = 1; + strdesc.mBitsPerChannel = SDL_AUDIO_BITSIZE(this->spec.format); + if (SDL_AUDIO_ISBIGENDIAN(this->spec.format)) + strdesc.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; + + if (SDL_AUDIO_ISFLOAT(this->spec.format)) + strdesc.mFormatFlags |= kLinearPCMFormatFlagIsFloat; + else if (SDL_AUDIO_ISSIGNED(this->spec.format)) + strdesc.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger; + break; + } + } + + if (!valid_datatype) { + COREAUDIO_CloseDevice(this); + return SDL_SetError("Unsupported audio format"); + } + + strdesc.mBytesPerFrame = + strdesc.mBitsPerChannel * strdesc.mChannelsPerFrame / 8; + strdesc.mBytesPerPacket = + strdesc.mBytesPerFrame * strdesc.mFramesPerPacket; + + if (!prepare_audiounit(this, devname, iscapture, &strdesc)) { + COREAUDIO_CloseDevice(this); + return -1; + } + + return 0; +} + +static int +COREAUDIO_Init(SDL_AudioDriverImpl * impl) +{ + + impl->OpenDevice = COREAUDIO_OpenDevice; + impl->CloseDevice = COREAUDIO_CloseDevice; + +#if MACOSX_COREAUDIO + impl->DetectDevices = COREAUDIO_DetectDevices; +#else + impl->OnlyHasDefaultOutputDevice = 1; + + AudioSessionInitialize(NULL, NULL, NULL, nil); + UInt32 category = kAudioSessionCategory_AmbientSound; + AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(UInt32), &category); +#endif + + impl->ProvidesOwnCallbackThread = 1; + + return 1; +} + +AudioBootStrap COREAUDIO_bootstrap = { + "coreaudio", "CoreAudio", COREAUDIO_Init, 0 +}; + +#endif +#if SDL_AUDIO_DRIVER_DSOUND + +#ifndef _SDL_directsound_h +#define _SDL_directsound_h + +#ifndef _directx_h +#define _directx_h + +#include +#ifndef WIN32 +#define WIN32 +#endif +#undef WINNT + +#ifndef FAR +#define FAR +#endif + +#ifndef MAKE_HRESULT +#define MAKE_HRESULT(sev,fac,code) \ + ((HRESULT)(((unsigned long)(sev)<<31) | ((unsigned long)(fac)<<16) | ((unsigned long)(code)))) +#endif + +#ifndef S_OK +#define S_OK (HRESULT)0x00000000L +#endif + +#ifndef SUCCEEDED +#define SUCCEEDED(x) ((HRESULT)(x) >= 0) +#endif +#ifndef FAILED +#define FAILED(x) ((HRESULT)(x)<0) +#endif + +#ifndef E_FAIL +#define E_FAIL (HRESULT)0x80000008L +#endif +#ifndef E_NOINTERFACE +#define E_NOINTERFACE (HRESULT)0x80004002L +#endif +#ifndef E_OUTOFMEMORY +#define E_OUTOFMEMORY (HRESULT)0x8007000EL +#endif +#ifndef E_INVALIDARG +#define E_INVALIDARG (HRESULT)0x80070057L +#endif +#ifndef E_NOTIMPL +#define E_NOTIMPL (HRESULT)0x80004001L +#endif +#ifndef REGDB_E_CLASSNOTREG +#define REGDB_E_CLASSNOTREG (HRESULT)0x80040154L +#endif + +#ifndef SEVERITY_ERROR +#define SEVERITY_ERROR 1 +#endif + +#ifndef FACILITY_WIN32 +#define FACILITY_WIN32 7 +#endif + +#ifndef FIELD_OFFSET +#define FIELD_OFFSET(type, field) ((LONG)&(((type *)0)->field)) +#endif + +#define DIRECTDRAW_VERSION 0x0700 +#define DIRECTSOUND_VERSION 0x0800 +#define DIRECTINPUT_VERSION 0x0500 + +#include +#define COM_NO_WINDOWS_H +#include +#include + +#ifndef DIRECTSOUND_VERSION +#define DIRECTSOUND_VERSION 0x0800 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __DSOUND_INCLUDED__ +#define __DSOUND_INCLUDED__ + +#ifndef DX_SHARED_DEFINES + +typedef float D3DVALUE, *LPD3DVALUE; + +#ifndef D3DCOLOR_DEFINED +typedef DWORD D3DCOLOR; +#define D3DCOLOR_DEFINED +#endif + +#ifndef LPD3DCOLOR_DEFINED +typedef DWORD *LPD3DCOLOR; +#define LPD3DCOLOR_DEFINED +#endif + +#ifndef D3DVECTOR_DEFINED +typedef struct _D3DVECTOR { + float x; + float y; + float z; +} D3DVECTOR; +#define D3DVECTOR_DEFINED +#endif + +#ifndef LPD3DVECTOR_DEFINED +typedef D3DVECTOR *LPD3DVECTOR; +#define LPD3DVECTOR_DEFINED +#endif + +#define DX_SHARED_DEFINES +#endif + +#define _FACDS 0x878 +#define MAKE_DSHRESULT(code) MAKE_HRESULT(1, _FACDS, code) + +DEFINE_GUID(CLSID_DirectSound, 0x47d4d946, 0x62e8, 0x11cf, 0x93, 0xbc, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0); + +DEFINE_GUID(CLSID_DirectSound8, 0x3901cc3f, 0x84b5, 0x4fa4, 0xba, 0x35, 0xaa, 0x81, 0x72, 0xb8, 0xa0, 0x9b); + +DEFINE_GUID(CLSID_DirectSoundCapture, 0xb0210780, 0x89cd, 0x11d0, 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16); + +DEFINE_GUID(CLSID_DirectSoundCapture8, 0xe4bcac13, 0x7f99, 0x4908, 0x9a, 0x8e, 0x74, 0xe3, 0xbf, 0x24, 0xb6, 0xe1); + +DEFINE_GUID(CLSID_DirectSoundFullDuplex, 0xfea4300c, 0x7959, 0x4147, 0xb2, 0x6a, 0x23, 0x77, 0xb9, 0xe7, 0xa9, 0x1d); + +DEFINE_GUID(DSDEVID_DefaultPlayback, 0xdef00000, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03); + +DEFINE_GUID(DSDEVID_DefaultCapture, 0xdef00001, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03); + +DEFINE_GUID(DSDEVID_DefaultVoicePlayback, 0xdef00002, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03); + +DEFINE_GUID(DSDEVID_DefaultVoiceCapture, 0xdef00003, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03); + +#ifdef __cplusplus +struct IDirectSound; +struct IDirectSoundBuffer; +struct IDirectSound3DListener; +struct IDirectSound3DBuffer; +struct IDirectSoundCapture; +struct IDirectSoundCaptureBuffer; +struct IDirectSoundNotify; +#endif + +#if DIRECTSOUND_VERSION >= 0x0800 + +#ifdef __cplusplus +struct IDirectSound8; +struct IDirectSoundBuffer8; +struct IDirectSoundCaptureBuffer8; +struct IDirectSoundFXGargle; +struct IDirectSoundFXChorus; +struct IDirectSoundFXFlanger; +struct IDirectSoundFXEcho; +struct IDirectSoundFXDistortion; +struct IDirectSoundFXCompressor; +struct IDirectSoundFXParamEq; +struct IDirectSoundFXWavesReverb; +struct IDirectSoundFXI3DL2Reverb; +struct IDirectSoundCaptureFXAec; +struct IDirectSoundCaptureFXNoiseSuppress; +struct IDirectSoundFullDuplex; +#endif + +#define IDirectSoundCapture8 IDirectSoundCapture +#define IDirectSound3DListener8 IDirectSound3DListener +#define IDirectSound3DBuffer8 IDirectSound3DBuffer +#define IDirectSoundNotify8 IDirectSoundNotify +#define IDirectSoundFXGargle8 IDirectSoundFXGargle +#define IDirectSoundFXChorus8 IDirectSoundFXChorus +#define IDirectSoundFXFlanger8 IDirectSoundFXFlanger +#define IDirectSoundFXEcho8 IDirectSoundFXEcho +#define IDirectSoundFXDistortion8 IDirectSoundFXDistortion +#define IDirectSoundFXCompressor8 IDirectSoundFXCompressor +#define IDirectSoundFXParamEq8 IDirectSoundFXParamEq +#define IDirectSoundFXWavesReverb8 IDirectSoundFXWavesReverb +#define IDirectSoundFXI3DL2Reverb8 IDirectSoundFXI3DL2Reverb +#define IDirectSoundCaptureFXAec8 IDirectSoundCaptureFXAec +#define IDirectSoundCaptureFXNoiseSuppress8 IDirectSoundCaptureFXNoiseSuppress +#define IDirectSoundFullDuplex8 IDirectSoundFullDuplex + +#endif + +typedef struct IDirectSound *LPDIRECTSOUND; +typedef struct IDirectSoundBuffer *LPDIRECTSOUNDBUFFER; +typedef struct IDirectSound3DListener *LPDIRECTSOUND3DLISTENER; +typedef struct IDirectSound3DBuffer *LPDIRECTSOUND3DBUFFER; +typedef struct IDirectSoundCapture *LPDIRECTSOUNDCAPTURE; +typedef struct IDirectSoundCaptureBuffer *LPDIRECTSOUNDCAPTUREBUFFER; +typedef struct IDirectSoundNotify *LPDIRECTSOUNDNOTIFY; + +#if DIRECTSOUND_VERSION >= 0x0800 +typedef struct IDirectSoundFXGargle *LPDIRECTSOUNDFXGARGLE; +typedef struct IDirectSoundFXChorus *LPDIRECTSOUNDFXCHORUS; +typedef struct IDirectSoundFXFlanger *LPDIRECTSOUNDFXFLANGER; +typedef struct IDirectSoundFXEcho *LPDIRECTSOUNDFXECHO; +typedef struct IDirectSoundFXDistortion *LPDIRECTSOUNDFXDISTORTION; +typedef struct IDirectSoundFXCompressor *LPDIRECTSOUNDFXCOMPRESSOR; +typedef struct IDirectSoundFXParamEq *LPDIRECTSOUNDFXPARAMEQ; +typedef struct IDirectSoundFXWavesReverb *LPDIRECTSOUNDFXWAVESREVERB; +typedef struct IDirectSoundFXI3DL2Reverb *LPDIRECTSOUNDFXI3DL2REVERB; +typedef struct IDirectSoundCaptureFXAec *LPDIRECTSOUNDCAPTUREFXAEC; +typedef struct IDirectSoundCaptureFXNoiseSuppress *LPDIRECTSOUNDCAPTUREFXNOISESUPPRESS; +typedef struct IDirectSoundFullDuplex *LPDIRECTSOUNDFULLDUPLEX; + +typedef struct IDirectSound8 *LPDIRECTSOUND8; +typedef struct IDirectSoundBuffer8 *LPDIRECTSOUNDBUFFER8; +typedef struct IDirectSound3DListener8 *LPDIRECTSOUND3DLISTENER8; +typedef struct IDirectSound3DBuffer8 *LPDIRECTSOUND3DBUFFER8; +typedef struct IDirectSoundCapture8 *LPDIRECTSOUNDCAPTURE8; +typedef struct IDirectSoundCaptureBuffer8 *LPDIRECTSOUNDCAPTUREBUFFER8; +typedef struct IDirectSoundNotify8 *LPDIRECTSOUNDNOTIFY8; +typedef struct IDirectSoundFXGargle8 *LPDIRECTSOUNDFXGARGLE8; +typedef struct IDirectSoundFXChorus8 *LPDIRECTSOUNDFXCHORUS8; +typedef struct IDirectSoundFXFlanger8 *LPDIRECTSOUNDFXFLANGER8; +typedef struct IDirectSoundFXEcho8 *LPDIRECTSOUNDFXECHO8; +typedef struct IDirectSoundFXDistortion8 *LPDIRECTSOUNDFXDISTORTION8; +typedef struct IDirectSoundFXCompressor8 *LPDIRECTSOUNDFXCOMPRESSOR8; +typedef struct IDirectSoundFXParamEq8 *LPDIRECTSOUNDFXPARAMEQ8; +typedef struct IDirectSoundFXWavesReverb8 *LPDIRECTSOUNDFXWAVESREVERB8; +typedef struct IDirectSoundFXI3DL2Reverb8 *LPDIRECTSOUNDFXI3DL2REVERB8; +typedef struct IDirectSoundCaptureFXAec8 *LPDIRECTSOUNDCAPTUREFXAEC8; +typedef struct IDirectSoundCaptureFXNoiseSuppress8 *LPDIRECTSOUNDCAPTUREFXNOISESUPPRESS8; +typedef struct IDirectSoundFullDuplex8 *LPDIRECTSOUNDFULLDUPLEX8; + +#endif + +#if DIRECTSOUND_VERSION >= 0x0800 +#define IID_IDirectSoundCapture8 IID_IDirectSoundCapture +#define IID_IDirectSound3DListener8 IID_IDirectSound3DListener +#define IID_IDirectSound3DBuffer8 IID_IDirectSound3DBuffer +#define IID_IDirectSoundNotify8 IID_IDirectSoundNotify +#define IID_IDirectSoundFXGargle8 IID_IDirectSoundFXGargle +#define IID_IDirectSoundFXChorus8 IID_IDirectSoundFXChorus +#define IID_IDirectSoundFXFlanger8 IID_IDirectSoundFXFlanger +#define IID_IDirectSoundFXEcho8 IID_IDirectSoundFXEcho +#define IID_IDirectSoundFXDistortion8 IID_IDirectSoundFXDistortion +#define IID_IDirectSoundFXCompressor8 IID_IDirectSoundFXCompressor +#define IID_IDirectSoundFXParamEq8 IID_IDirectSoundFXParamEq +#define IID_IDirectSoundFXWavesReverb8 IID_IDirectSoundFXWavesReverb +#define IID_IDirectSoundFXI3DL2Reverb8 IID_IDirectSoundFXI3DL2Reverb +#define IID_IDirectSoundCaptureFXAec8 IID_IDirectSoundCaptureFXAec +#define IID_IDirectSoundCaptureFXNoiseSuppress8 IID_IDirectSoundCaptureFXNoiseSuppress +#define IID_IDirectSoundFullDuplex8 IID_IDirectSoundFullDuplex +#endif + +#ifndef _LPCWAVEFORMATEX_DEFINED +#define _LPCWAVEFORMATEX_DEFINED +typedef const WAVEFORMATEX *LPCWAVEFORMATEX; +#endif + +#ifndef __LPCGUID_DEFINED__ +#define __LPCGUID_DEFINED__ +typedef const GUID *LPCGUID; +#endif + +typedef LPDIRECTSOUND *LPLPDIRECTSOUND; +typedef LPDIRECTSOUNDBUFFER *LPLPDIRECTSOUNDBUFFER; +typedef LPDIRECTSOUND3DLISTENER *LPLPDIRECTSOUND3DLISTENER; +typedef LPDIRECTSOUND3DBUFFER *LPLPDIRECTSOUND3DBUFFER; +typedef LPDIRECTSOUNDCAPTURE *LPLPDIRECTSOUNDCAPTURE; +typedef LPDIRECTSOUNDCAPTUREBUFFER *LPLPDIRECTSOUNDCAPTUREBUFFER; +typedef LPDIRECTSOUNDNOTIFY *LPLPDIRECTSOUNDNOTIFY; + +#if DIRECTSOUND_VERSION >= 0x0800 +typedef LPDIRECTSOUND8 *LPLPDIRECTSOUND8; +typedef LPDIRECTSOUNDBUFFER8 *LPLPDIRECTSOUNDBUFFER8; +typedef LPDIRECTSOUNDCAPTURE8 *LPLPDIRECTSOUNDCAPTURE8; +typedef LPDIRECTSOUNDCAPTUREBUFFER8 *LPLPDIRECTSOUNDCAPTUREBUFFER8; +#endif + +typedef struct _DSCAPS +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwMinSecondarySampleRate; + DWORD dwMaxSecondarySampleRate; + DWORD dwPrimaryBuffers; + DWORD dwMaxHwMixingAllBuffers; + DWORD dwMaxHwMixingStaticBuffers; + DWORD dwMaxHwMixingStreamingBuffers; + DWORD dwFreeHwMixingAllBuffers; + DWORD dwFreeHwMixingStaticBuffers; + DWORD dwFreeHwMixingStreamingBuffers; + DWORD dwMaxHw3DAllBuffers; + DWORD dwMaxHw3DStaticBuffers; + DWORD dwMaxHw3DStreamingBuffers; + DWORD dwFreeHw3DAllBuffers; + DWORD dwFreeHw3DStaticBuffers; + DWORD dwFreeHw3DStreamingBuffers; + DWORD dwTotalHwMemBytes; + DWORD dwFreeHwMemBytes; + DWORD dwMaxContigFreeHwMemBytes; + DWORD dwUnlockTransferRateHwBuffers; + DWORD dwPlayCpuOverheadSwBuffers; + DWORD dwReserved1; + DWORD dwReserved2; +} DSCAPS, *LPDSCAPS; + +typedef const DSCAPS *LPCDSCAPS; + +typedef struct _DSBCAPS +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwUnlockTransferRate; + DWORD dwPlayCpuOverhead; +} DSBCAPS, *LPDSBCAPS; + +typedef const DSBCAPS *LPCDSBCAPS; + +#if DIRECTSOUND_VERSION >= 0x0800 + + typedef struct _DSEFFECTDESC + { + DWORD dwSize; + DWORD dwFlags; + GUID guidDSFXClass; + DWORD dwReserved1; + DWORD dwReserved2; + } DSEFFECTDESC, *LPDSEFFECTDESC; + typedef const DSEFFECTDESC *LPCDSEFFECTDESC; + + #define DSFX_LOCHARDWARE 0x00000001 + #define DSFX_LOCSOFTWARE 0x00000002 + + enum + { + DSFXR_PRESENT, + DSFXR_LOCHARDWARE, + DSFXR_LOCSOFTWARE, + DSFXR_UNALLOCATED, + DSFXR_FAILED, + DSFXR_UNKNOWN, + DSFXR_SENDLOOP + }; + + typedef struct _DSCEFFECTDESC + { + DWORD dwSize; + DWORD dwFlags; + GUID guidDSCFXClass; + GUID guidDSCFXInstance; + DWORD dwReserved1; + DWORD dwReserved2; + } DSCEFFECTDESC, *LPDSCEFFECTDESC; + typedef const DSCEFFECTDESC *LPCDSCEFFECTDESC; + + #define DSCFX_LOCHARDWARE 0x00000001 + #define DSCFX_LOCSOFTWARE 0x00000002 + + #define DSCFXR_LOCHARDWARE 0x00000010 + #define DSCFXR_LOCSOFTWARE 0x00000020 + #define DSCFXR_UNALLOCATED 0x00000040 + #define DSCFXR_FAILED 0x00000080 + #define DSCFXR_UNKNOWN 0x00000100 + +#endif + +typedef struct _DSBUFFERDESC +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; + LPWAVEFORMATEX lpwfxFormat; +#if DIRECTSOUND_VERSION >= 0x0700 + GUID guid3DAlgorithm; +#endif +} DSBUFFERDESC, *LPDSBUFFERDESC; + +typedef const DSBUFFERDESC *LPCDSBUFFERDESC; + +typedef struct _DSBUFFERDESC1 +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; + LPWAVEFORMATEX lpwfxFormat; +} DSBUFFERDESC1, *LPDSBUFFERDESC1; + +typedef const DSBUFFERDESC1 *LPCDSBUFFERDESC1; + +typedef struct _DS3DBUFFER +{ + DWORD dwSize; + D3DVECTOR vPosition; + D3DVECTOR vVelocity; + DWORD dwInsideConeAngle; + DWORD dwOutsideConeAngle; + D3DVECTOR vConeOrientation; + LONG lConeOutsideVolume; + D3DVALUE flMinDistance; + D3DVALUE flMaxDistance; + DWORD dwMode; +} DS3DBUFFER, *LPDS3DBUFFER; + +typedef const DS3DBUFFER *LPCDS3DBUFFER; + +typedef struct _DS3DLISTENER +{ + DWORD dwSize; + D3DVECTOR vPosition; + D3DVECTOR vVelocity; + D3DVECTOR vOrientFront; + D3DVECTOR vOrientTop; + D3DVALUE flDistanceFactor; + D3DVALUE flRolloffFactor; + D3DVALUE flDopplerFactor; +} DS3DLISTENER, *LPDS3DLISTENER; + +typedef const DS3DLISTENER *LPCDS3DLISTENER; + +typedef struct _DSCCAPS +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwFormats; + DWORD dwChannels; +} DSCCAPS, *LPDSCCAPS; + +typedef const DSCCAPS *LPCDSCCAPS; + +typedef struct _DSCBUFFERDESC1 +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; + LPWAVEFORMATEX lpwfxFormat; +} DSCBUFFERDESC1, *LPDSCBUFFERDESC1; + +typedef struct _DSCBUFFERDESC +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; + LPWAVEFORMATEX lpwfxFormat; +#if DIRECTSOUND_VERSION >= 0x0800 + DWORD dwFXCount; + LPDSCEFFECTDESC lpDSCFXDesc; +#endif +} DSCBUFFERDESC, *LPDSCBUFFERDESC; + +typedef const DSCBUFFERDESC *LPCDSCBUFFERDESC; + +typedef struct _DSCBCAPS +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwBufferBytes; + DWORD dwReserved; +} DSCBCAPS, *LPDSCBCAPS; + +typedef const DSCBCAPS *LPCDSCBCAPS; + +typedef struct _DSBPOSITIONNOTIFY +{ + DWORD dwOffset; + HANDLE hEventNotify; +} DSBPOSITIONNOTIFY, *LPDSBPOSITIONNOTIFY; + +typedef const DSBPOSITIONNOTIFY *LPCDSBPOSITIONNOTIFY; + +typedef BOOL (CALLBACK *LPDSENUMCALLBACKA)(LPGUID, LPCSTR, LPCSTR, LPVOID); +typedef BOOL (CALLBACK *LPDSENUMCALLBACKW)(LPGUID, LPCWSTR, LPCWSTR, LPVOID); + +extern HRESULT WINAPI DirectSoundCreate(LPCGUID pcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter); +extern HRESULT WINAPI DirectSoundEnumerateA(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext); +extern HRESULT WINAPI DirectSoundEnumerateW(LPDSENUMCALLBACKW pDSEnumCallback, LPVOID pContext); + +extern HRESULT WINAPI DirectSoundCaptureCreate(LPCGUID pcGuidDevice, LPDIRECTSOUNDCAPTURE *ppDSC, LPUNKNOWN pUnkOuter); +extern HRESULT WINAPI DirectSoundCaptureEnumerateA(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext); +extern HRESULT WINAPI DirectSoundCaptureEnumerateW(LPDSENUMCALLBACKW pDSEnumCallback, LPVOID pContext); + +#if DIRECTSOUND_VERSION >= 0x0800 +extern HRESULT WINAPI DirectSoundCreate8(LPCGUID pcGuidDevice, LPDIRECTSOUND8 *ppDS8, LPUNKNOWN pUnkOuter); +extern HRESULT WINAPI DirectSoundCaptureCreate8(LPCGUID pcGuidDevice, LPDIRECTSOUNDCAPTURE8 *ppDSC8, LPUNKNOWN pUnkOuter); +extern HRESULT WINAPI DirectSoundFullDuplexCreate(LPCGUID pcGuidCaptureDevice, LPCGUID pcGuidRenderDevice, + LPCDSCBUFFERDESC pcDSCBufferDesc, LPCDSBUFFERDESC pcDSBufferDesc, HWND hWnd, + DWORD dwLevel, LPDIRECTSOUNDFULLDUPLEX* ppDSFD, LPDIRECTSOUNDCAPTUREBUFFER8 *ppDSCBuffer8, + LPDIRECTSOUNDBUFFER8 *ppDSBuffer8, LPUNKNOWN pUnkOuter); +#define DirectSoundFullDuplexCreate8 DirectSoundFullDuplexCreate + +extern HRESULT WINAPI GetDeviceID(LPCGUID pGuidSrc, LPGUID pGuidDest); +#endif + +#ifdef UNICODE +#define LPDSENUMCALLBACK LPDSENUMCALLBACKW +#define DirectSoundEnumerate DirectSoundEnumerateW +#define DirectSoundCaptureEnumerate DirectSoundCaptureEnumerateW +#else +#define LPDSENUMCALLBACK LPDSENUMCALLBACKA +#define DirectSoundEnumerate DirectSoundEnumerateA +#define DirectSoundCaptureEnumerate DirectSoundCaptureEnumerateA +#endif + +#if !defined(__cplusplus) || defined(CINTERFACE) +#ifndef IUnknown_QueryInterface +#define IUnknown_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#endif +#ifndef IUnknown_AddRef +#define IUnknown_AddRef(p) (p)->lpVtbl->AddRef(p) +#endif +#ifndef IUnknown_Release +#define IUnknown_Release(p) (p)->lpVtbl->Release(p) +#endif +#else +#ifndef IUnknown_QueryInterface +#define IUnknown_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#endif +#ifndef IUnknown_AddRef +#define IUnknown_AddRef(p) (p)->AddRef() +#endif +#ifndef IUnknown_Release +#define IUnknown_Release(p) (p)->Release() +#endif +#endif + +#ifndef __IReferenceClock_INTERFACE_DEFINED__ +#define __IReferenceClock_INTERFACE_DEFINED__ + +typedef LONGLONG REFERENCE_TIME; +typedef REFERENCE_TIME *LPREFERENCE_TIME; + +DEFINE_GUID(IID_IReferenceClock, 0x56a86897, 0x0ad4, 0x11ce, 0xb0, 0x3a, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70); + +#undef INTERFACE +#define INTERFACE IReferenceClock + +DECLARE_INTERFACE_(IReferenceClock, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(GetTime) (THIS_ REFERENCE_TIME *pTime) PURE; + STDMETHOD(AdviseTime) (THIS_ REFERENCE_TIME rtBaseTime, REFERENCE_TIME rtStreamTime, + HANDLE hEvent, LPDWORD pdwAdviseCookie) PURE; + STDMETHOD(AdvisePeriodic) (THIS_ REFERENCE_TIME rtStartTime, REFERENCE_TIME rtPeriodTime, + HANDLE hSemaphore, LPDWORD pdwAdviseCookie) PURE; + STDMETHOD(Unadvise) (THIS_ DWORD dwAdviseCookie) PURE; +}; + +#endif + +#ifndef IReferenceClock_QueryInterface + +#define IReferenceClock_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IReferenceClock_AddRef(p) IUnknown_AddRef(p) +#define IReferenceClock_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IReferenceClock_GetTime(p,a) (p)->lpVtbl->GetTime(p,a) +#define IReferenceClock_AdviseTime(p,a,b,c,d) (p)->lpVtbl->AdviseTime(p,a,b,c,d) +#define IReferenceClock_AdvisePeriodic(p,a,b,c,d) (p)->lpVtbl->AdvisePeriodic(p,a,b,c,d) +#define IReferenceClock_Unadvise(p,a) (p)->lpVtbl->Unadvise(p,a) +#else +#define IReferenceClock_GetTime(p,a) (p)->GetTime(a) +#define IReferenceClock_AdviseTime(p,a,b,c,d) (p)->AdviseTime(a,b,c,d) +#define IReferenceClock_AdvisePeriodic(p,a,b,c,d) (p)->AdvisePeriodic(a,b,c,d) +#define IReferenceClock_Unadvise(p,a) (p)->Unadvise(a) +#endif + +#endif + +DEFINE_GUID(IID_IDirectSound, 0x279AFA83, 0x4981, 0x11CE, 0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60); + +#undef INTERFACE +#define INTERFACE IDirectSound + +DECLARE_INTERFACE_(IDirectSound, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(CreateSoundBuffer) (THIS_ LPCDSBUFFERDESC pcDSBufferDesc, LPDIRECTSOUNDBUFFER *ppDSBuffer, LPUNKNOWN pUnkOuter) PURE; + STDMETHOD(GetCaps) (THIS_ LPDSCAPS pDSCaps) PURE; + STDMETHOD(DuplicateSoundBuffer) (THIS_ LPDIRECTSOUNDBUFFER pDSBufferOriginal, LPDIRECTSOUNDBUFFER *ppDSBufferDuplicate) PURE; + STDMETHOD(SetCooperativeLevel) (THIS_ HWND hwnd, DWORD dwLevel) PURE; + STDMETHOD(Compact) (THIS) PURE; + STDMETHOD(GetSpeakerConfig) (THIS_ LPDWORD pdwSpeakerConfig) PURE; + STDMETHOD(SetSpeakerConfig) (THIS_ DWORD dwSpeakerConfig) PURE; + STDMETHOD(Initialize) (THIS_ LPCGUID pcGuidDevice) PURE; +}; + +#define IDirectSound_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSound_AddRef(p) IUnknown_AddRef(p) +#define IDirectSound_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSound_CreateSoundBuffer(p,a,b,c) (p)->lpVtbl->CreateSoundBuffer(p,a,b,c) +#define IDirectSound_GetCaps(p,a) (p)->lpVtbl->GetCaps(p,a) +#define IDirectSound_DuplicateSoundBuffer(p,a,b) (p)->lpVtbl->DuplicateSoundBuffer(p,a,b) +#define IDirectSound_SetCooperativeLevel(p,a,b) (p)->lpVtbl->SetCooperativeLevel(p,a,b) +#define IDirectSound_Compact(p) (p)->lpVtbl->Compact(p) +#define IDirectSound_GetSpeakerConfig(p,a) (p)->lpVtbl->GetSpeakerConfig(p,a) +#define IDirectSound_SetSpeakerConfig(p,b) (p)->lpVtbl->SetSpeakerConfig(p,b) +#define IDirectSound_Initialize(p,a) (p)->lpVtbl->Initialize(p,a) +#else +#define IDirectSound_CreateSoundBuffer(p,a,b,c) (p)->CreateSoundBuffer(a,b,c) +#define IDirectSound_GetCaps(p,a) (p)->GetCaps(a) +#define IDirectSound_DuplicateSoundBuffer(p,a,b) (p)->DuplicateSoundBuffer(a,b) +#define IDirectSound_SetCooperativeLevel(p,a,b) (p)->SetCooperativeLevel(a,b) +#define IDirectSound_Compact(p) (p)->Compact() +#define IDirectSound_GetSpeakerConfig(p,a) (p)->GetSpeakerConfig(a) +#define IDirectSound_SetSpeakerConfig(p,b) (p)->SetSpeakerConfig(b) +#define IDirectSound_Initialize(p,a) (p)->Initialize(a) +#endif + +#if DIRECTSOUND_VERSION >= 0x0800 + +DEFINE_GUID(IID_IDirectSound8, 0xC50A7E93, 0xF395, 0x4834, 0x9E, 0xF6, 0x7F, 0xA9, 0x9D, 0xE5, 0x09, 0x66); + +#undef INTERFACE +#define INTERFACE IDirectSound8 + +DECLARE_INTERFACE_(IDirectSound8, IDirectSound) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(CreateSoundBuffer) (THIS_ LPCDSBUFFERDESC pcDSBufferDesc, LPDIRECTSOUNDBUFFER *ppDSBuffer, LPUNKNOWN pUnkOuter) PURE; + STDMETHOD(GetCaps) (THIS_ LPDSCAPS pDSCaps) PURE; + STDMETHOD(DuplicateSoundBuffer) (THIS_ LPDIRECTSOUNDBUFFER pDSBufferOriginal, LPDIRECTSOUNDBUFFER *ppDSBufferDuplicate) PURE; + STDMETHOD(SetCooperativeLevel) (THIS_ HWND hwnd, DWORD dwLevel) PURE; + STDMETHOD(Compact) (THIS) PURE; + STDMETHOD(GetSpeakerConfig) (THIS_ LPDWORD pdwSpeakerConfig) PURE; + STDMETHOD(SetSpeakerConfig) (THIS_ DWORD dwSpeakerConfig) PURE; + STDMETHOD(Initialize) (THIS_ LPCGUID pcGuidDevice) PURE; + + STDMETHOD(VerifyCertification) (THIS_ LPDWORD pdwCertified) PURE; +}; + +#define IDirectSound8_QueryInterface(p,a,b) IDirectSound_QueryInterface(p,a,b) +#define IDirectSound8_AddRef(p) IDirectSound_AddRef(p) +#define IDirectSound8_Release(p) IDirectSound_Release(p) +#define IDirectSound8_CreateSoundBuffer(p,a,b,c) IDirectSound_CreateSoundBuffer(p,a,b,c) +#define IDirectSound8_GetCaps(p,a) IDirectSound_GetCaps(p,a) +#define IDirectSound8_DuplicateSoundBuffer(p,a,b) IDirectSound_DuplicateSoundBuffer(p,a,b) +#define IDirectSound8_SetCooperativeLevel(p,a,b) IDirectSound_SetCooperativeLevel(p,a,b) +#define IDirectSound8_Compact(p) IDirectSound_Compact(p) +#define IDirectSound8_GetSpeakerConfig(p,a) IDirectSound_GetSpeakerConfig(p,a) +#define IDirectSound8_SetSpeakerConfig(p,a) IDirectSound_SetSpeakerConfig(p,a) +#define IDirectSound8_Initialize(p,a) IDirectSound_Initialize(p,a) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSound8_VerifyCertification(p,a) (p)->lpVtbl->VerifyCertification(p,a) +#else +#define IDirectSound8_VerifyCertification(p,a) (p)->VerifyCertification(a) +#endif + +#endif + +DEFINE_GUID(IID_IDirectSoundBuffer, 0x279AFA85, 0x4981, 0x11CE, 0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60); + +#undef INTERFACE +#define INTERFACE IDirectSoundBuffer + +DECLARE_INTERFACE_(IDirectSoundBuffer, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(GetCaps) (THIS_ LPDSBCAPS pDSBufferCaps) PURE; + STDMETHOD(GetCurrentPosition) (THIS_ LPDWORD pdwCurrentPlayCursor, LPDWORD pdwCurrentWriteCursor) PURE; + STDMETHOD(GetFormat) (THIS_ LPWAVEFORMATEX pwfxFormat, DWORD dwSizeAllocated, LPDWORD pdwSizeWritten) PURE; + STDMETHOD(GetVolume) (THIS_ LPLONG plVolume) PURE; + STDMETHOD(GetPan) (THIS_ LPLONG plPan) PURE; + STDMETHOD(GetFrequency) (THIS_ LPDWORD pdwFrequency) PURE; + STDMETHOD(GetStatus) (THIS_ LPDWORD pdwStatus) PURE; + STDMETHOD(Initialize) (THIS_ LPDIRECTSOUND pDirectSound, LPCDSBUFFERDESC pcDSBufferDesc) PURE; + STDMETHOD(Lock) (THIS_ DWORD dwOffset, DWORD dwBytes, LPVOID *ppvAudioPtr1, LPDWORD pdwAudioBytes1, + LPVOID *ppvAudioPtr2, LPDWORD pdwAudioBytes2, DWORD dwFlags) PURE; + STDMETHOD(Play) (THIS_ DWORD dwReserved1, DWORD dwPriority, DWORD dwFlags) PURE; + STDMETHOD(SetCurrentPosition) (THIS_ DWORD dwNewPosition) PURE; + STDMETHOD(SetFormat) (THIS_ LPCWAVEFORMATEX pcfxFormat) PURE; + STDMETHOD(SetVolume) (THIS_ LONG lVolume) PURE; + STDMETHOD(SetPan) (THIS_ LONG lPan) PURE; + STDMETHOD(SetFrequency) (THIS_ DWORD dwFrequency) PURE; + STDMETHOD(Stop) (THIS) PURE; + STDMETHOD(Unlock) (THIS_ LPVOID pvAudioPtr1, DWORD dwAudioBytes1, LPVOID pvAudioPtr2, DWORD dwAudioBytes2) PURE; + STDMETHOD(Restore) (THIS) PURE; +}; + +#define IDirectSoundBuffer_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundBuffer_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundBuffer_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundBuffer_GetCaps(p,a) (p)->lpVtbl->GetCaps(p,a) +#define IDirectSoundBuffer_GetCurrentPosition(p,a,b) (p)->lpVtbl->GetCurrentPosition(p,a,b) +#define IDirectSoundBuffer_GetFormat(p,a,b,c) (p)->lpVtbl->GetFormat(p,a,b,c) +#define IDirectSoundBuffer_GetVolume(p,a) (p)->lpVtbl->GetVolume(p,a) +#define IDirectSoundBuffer_GetPan(p,a) (p)->lpVtbl->GetPan(p,a) +#define IDirectSoundBuffer_GetFrequency(p,a) (p)->lpVtbl->GetFrequency(p,a) +#define IDirectSoundBuffer_GetStatus(p,a) (p)->lpVtbl->GetStatus(p,a) +#define IDirectSoundBuffer_Initialize(p,a,b) (p)->lpVtbl->Initialize(p,a,b) +#define IDirectSoundBuffer_Lock(p,a,b,c,d,e,f,g) (p)->lpVtbl->Lock(p,a,b,c,d,e,f,g) +#define IDirectSoundBuffer_Play(p,a,b,c) (p)->lpVtbl->Play(p,a,b,c) +#define IDirectSoundBuffer_SetCurrentPosition(p,a) (p)->lpVtbl->SetCurrentPosition(p,a) +#define IDirectSoundBuffer_SetFormat(p,a) (p)->lpVtbl->SetFormat(p,a) +#define IDirectSoundBuffer_SetVolume(p,a) (p)->lpVtbl->SetVolume(p,a) +#define IDirectSoundBuffer_SetPan(p,a) (p)->lpVtbl->SetPan(p,a) +#define IDirectSoundBuffer_SetFrequency(p,a) (p)->lpVtbl->SetFrequency(p,a) +#define IDirectSoundBuffer_Stop(p) (p)->lpVtbl->Stop(p) +#define IDirectSoundBuffer_Unlock(p,a,b,c,d) (p)->lpVtbl->Unlock(p,a,b,c,d) +#define IDirectSoundBuffer_Restore(p) (p)->lpVtbl->Restore(p) +#else +#define IDirectSoundBuffer_GetCaps(p,a) (p)->GetCaps(a) +#define IDirectSoundBuffer_GetCurrentPosition(p,a,b) (p)->GetCurrentPosition(a,b) +#define IDirectSoundBuffer_GetFormat(p,a,b,c) (p)->GetFormat(a,b,c) +#define IDirectSoundBuffer_GetVolume(p,a) (p)->GetVolume(a) +#define IDirectSoundBuffer_GetPan(p,a) (p)->GetPan(a) +#define IDirectSoundBuffer_GetFrequency(p,a) (p)->GetFrequency(a) +#define IDirectSoundBuffer_GetStatus(p,a) (p)->GetStatus(a) +#define IDirectSoundBuffer_Initialize(p,a,b) (p)->Initialize(a,b) +#define IDirectSoundBuffer_Lock(p,a,b,c,d,e,f,g) (p)->Lock(a,b,c,d,e,f,g) +#define IDirectSoundBuffer_Play(p,a,b,c) (p)->Play(a,b,c) +#define IDirectSoundBuffer_SetCurrentPosition(p,a) (p)->SetCurrentPosition(a) +#define IDirectSoundBuffer_SetFormat(p,a) (p)->SetFormat(a) +#define IDirectSoundBuffer_SetVolume(p,a) (p)->SetVolume(a) +#define IDirectSoundBuffer_SetPan(p,a) (p)->SetPan(a) +#define IDirectSoundBuffer_SetFrequency(p,a) (p)->SetFrequency(a) +#define IDirectSoundBuffer_Stop(p) (p)->Stop() +#define IDirectSoundBuffer_Unlock(p,a,b,c,d) (p)->Unlock(a,b,c,d) +#define IDirectSoundBuffer_Restore(p) (p)->Restore() +#endif + +#if DIRECTSOUND_VERSION >= 0x0800 + +DEFINE_GUID(IID_IDirectSoundBuffer8, 0x6825a449, 0x7524, 0x4d82, 0x92, 0x0f, 0x50, 0xe3, 0x6a, 0xb3, 0xab, 0x1e); + +#undef INTERFACE +#define INTERFACE IDirectSoundBuffer8 + +DECLARE_INTERFACE_(IDirectSoundBuffer8, IDirectSoundBuffer) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(GetCaps) (THIS_ LPDSBCAPS pDSBufferCaps) PURE; + STDMETHOD(GetCurrentPosition) (THIS_ LPDWORD pdwCurrentPlayCursor, LPDWORD pdwCurrentWriteCursor) PURE; + STDMETHOD(GetFormat) (THIS_ LPWAVEFORMATEX pwfxFormat, DWORD dwSizeAllocated, LPDWORD pdwSizeWritten) PURE; + STDMETHOD(GetVolume) (THIS_ LPLONG plVolume) PURE; + STDMETHOD(GetPan) (THIS_ LPLONG plPan) PURE; + STDMETHOD(GetFrequency) (THIS_ LPDWORD pdwFrequency) PURE; + STDMETHOD(GetStatus) (THIS_ LPDWORD pdwStatus) PURE; + STDMETHOD(Initialize) (THIS_ LPDIRECTSOUND pDirectSound, LPCDSBUFFERDESC pcDSBufferDesc) PURE; + STDMETHOD(Lock) (THIS_ DWORD dwOffset, DWORD dwBytes, LPVOID *ppvAudioPtr1, LPDWORD pdwAudioBytes1, + LPVOID *ppvAudioPtr2, LPDWORD pdwAudioBytes2, DWORD dwFlags) PURE; + STDMETHOD(Play) (THIS_ DWORD dwReserved1, DWORD dwPriority, DWORD dwFlags) PURE; + STDMETHOD(SetCurrentPosition) (THIS_ DWORD dwNewPosition) PURE; + STDMETHOD(SetFormat) (THIS_ LPCWAVEFORMATEX pcfxFormat) PURE; + STDMETHOD(SetVolume) (THIS_ LONG lVolume) PURE; + STDMETHOD(SetPan) (THIS_ LONG lPan) PURE; + STDMETHOD(SetFrequency) (THIS_ DWORD dwFrequency) PURE; + STDMETHOD(Stop) (THIS) PURE; + STDMETHOD(Unlock) (THIS_ LPVOID pvAudioPtr1, DWORD dwAudioBytes1, LPVOID pvAudioPtr2, DWORD dwAudioBytes2) PURE; + STDMETHOD(Restore) (THIS) PURE; + + STDMETHOD(SetFX) (THIS_ DWORD dwEffectsCount, LPDSEFFECTDESC pDSFXDesc, LPDWORD pdwResultCodes) PURE; + STDMETHOD(AcquireResources) (THIS_ DWORD dwFlags, DWORD dwEffectsCount, LPDWORD pdwResultCodes) PURE; + STDMETHOD(GetObjectInPath) (THIS_ REFGUID rguidObject, DWORD dwIndex, REFGUID rguidInterface, LPVOID *ppObject) PURE; +}; + +DEFINE_GUID(GUID_All_Objects, 0xaa114de5, 0xc262, 0x4169, 0xa1, 0xc8, 0x23, 0xd6, 0x98, 0xcc, 0x73, 0xb5); + +#define IDirectSoundBuffer8_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundBuffer8_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundBuffer8_Release(p) IUnknown_Release(p) + +#define IDirectSoundBuffer8_GetCaps(p,a) IDirectSoundBuffer_GetCaps(p,a) +#define IDirectSoundBuffer8_GetCurrentPosition(p,a,b) IDirectSoundBuffer_GetCurrentPosition(p,a,b) +#define IDirectSoundBuffer8_GetFormat(p,a,b,c) IDirectSoundBuffer_GetFormat(p,a,b,c) +#define IDirectSoundBuffer8_GetVolume(p,a) IDirectSoundBuffer_GetVolume(p,a) +#define IDirectSoundBuffer8_GetPan(p,a) IDirectSoundBuffer_GetPan(p,a) +#define IDirectSoundBuffer8_GetFrequency(p,a) IDirectSoundBuffer_GetFrequency(p,a) +#define IDirectSoundBuffer8_GetStatus(p,a) IDirectSoundBuffer_GetStatus(p,a) +#define IDirectSoundBuffer8_Initialize(p,a,b) IDirectSoundBuffer_Initialize(p,a,b) +#define IDirectSoundBuffer8_Lock(p,a,b,c,d,e,f,g) IDirectSoundBuffer_Lock(p,a,b,c,d,e,f,g) +#define IDirectSoundBuffer8_Play(p,a,b,c) IDirectSoundBuffer_Play(p,a,b,c) +#define IDirectSoundBuffer8_SetCurrentPosition(p,a) IDirectSoundBuffer_SetCurrentPosition(p,a) +#define IDirectSoundBuffer8_SetFormat(p,a) IDirectSoundBuffer_SetFormat(p,a) +#define IDirectSoundBuffer8_SetVolume(p,a) IDirectSoundBuffer_SetVolume(p,a) +#define IDirectSoundBuffer8_SetPan(p,a) IDirectSoundBuffer_SetPan(p,a) +#define IDirectSoundBuffer8_SetFrequency(p,a) IDirectSoundBuffer_SetFrequency(p,a) +#define IDirectSoundBuffer8_Stop(p) IDirectSoundBuffer_Stop(p) +#define IDirectSoundBuffer8_Unlock(p,a,b,c,d) IDirectSoundBuffer_Unlock(p,a,b,c,d) +#define IDirectSoundBuffer8_Restore(p) IDirectSoundBuffer_Restore(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundBuffer8_SetFX(p,a,b,c) (p)->lpVtbl->SetFX(p,a,b,c) +#define IDirectSoundBuffer8_AcquireResources(p,a,b,c) (p)->lpVtbl->AcquireResources(p,a,b,c) +#define IDirectSoundBuffer8_GetObjectInPath(p,a,b,c,d) (p)->lpVtbl->GetObjectInPath(p,a,b,c,d) +#else +#define IDirectSoundBuffer8_SetFX(p,a,b,c) (p)->SetFX(a,b,c) +#define IDirectSoundBuffer8_AcquireResources(p,a,b,c) (p)->AcquireResources(a,b,c) +#define IDirectSoundBuffer8_GetObjectInPath(p,a,b,c,d) (p)->GetObjectInPath(a,b,c,d) +#endif + +#endif + +DEFINE_GUID(IID_IDirectSound3DListener, 0x279AFA84, 0x4981, 0x11CE, 0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60); + +#undef INTERFACE +#define INTERFACE IDirectSound3DListener + +DECLARE_INTERFACE_(IDirectSound3DListener, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(GetAllParameters) (THIS_ LPDS3DLISTENER pListener) PURE; + STDMETHOD(GetDistanceFactor) (THIS_ D3DVALUE* pflDistanceFactor) PURE; + STDMETHOD(GetDopplerFactor) (THIS_ D3DVALUE* pflDopplerFactor) PURE; + STDMETHOD(GetOrientation) (THIS_ D3DVECTOR* pvOrientFront, D3DVECTOR* pvOrientTop) PURE; + STDMETHOD(GetPosition) (THIS_ D3DVECTOR* pvPosition) PURE; + STDMETHOD(GetRolloffFactor) (THIS_ D3DVALUE* pflRolloffFactor) PURE; + STDMETHOD(GetVelocity) (THIS_ D3DVECTOR* pvVelocity) PURE; + STDMETHOD(SetAllParameters) (THIS_ LPCDS3DLISTENER pcListener, DWORD dwApply) PURE; + STDMETHOD(SetDistanceFactor) (THIS_ D3DVALUE flDistanceFactor, DWORD dwApply) PURE; + STDMETHOD(SetDopplerFactor) (THIS_ D3DVALUE flDopplerFactor, DWORD dwApply) PURE; + STDMETHOD(SetOrientation) (THIS_ D3DVALUE xFront, D3DVALUE yFront, D3DVALUE zFront, + D3DVALUE xTop, D3DVALUE yTop, D3DVALUE zTop, DWORD dwApply) PURE; + STDMETHOD(SetPosition) (THIS_ D3DVALUE x, D3DVALUE y, D3DVALUE z, DWORD dwApply) PURE; + STDMETHOD(SetRolloffFactor) (THIS_ D3DVALUE flRolloffFactor, DWORD dwApply) PURE; + STDMETHOD(SetVelocity) (THIS_ D3DVALUE x, D3DVALUE y, D3DVALUE z, DWORD dwApply) PURE; + STDMETHOD(CommitDeferredSettings) (THIS) PURE; +}; + +#define IDirectSound3DListener_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSound3DListener_AddRef(p) IUnknown_AddRef(p) +#define IDirectSound3DListener_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSound3DListener_GetAllParameters(p,a) (p)->lpVtbl->GetAllParameters(p,a) +#define IDirectSound3DListener_GetDistanceFactor(p,a) (p)->lpVtbl->GetDistanceFactor(p,a) +#define IDirectSound3DListener_GetDopplerFactor(p,a) (p)->lpVtbl->GetDopplerFactor(p,a) +#define IDirectSound3DListener_GetOrientation(p,a,b) (p)->lpVtbl->GetOrientation(p,a,b) +#define IDirectSound3DListener_GetPosition(p,a) (p)->lpVtbl->GetPosition(p,a) +#define IDirectSound3DListener_GetRolloffFactor(p,a) (p)->lpVtbl->GetRolloffFactor(p,a) +#define IDirectSound3DListener_GetVelocity(p,a) (p)->lpVtbl->GetVelocity(p,a) +#define IDirectSound3DListener_SetAllParameters(p,a,b) (p)->lpVtbl->SetAllParameters(p,a,b) +#define IDirectSound3DListener_SetDistanceFactor(p,a,b) (p)->lpVtbl->SetDistanceFactor(p,a,b) +#define IDirectSound3DListener_SetDopplerFactor(p,a,b) (p)->lpVtbl->SetDopplerFactor(p,a,b) +#define IDirectSound3DListener_SetOrientation(p,a,b,c,d,e,f,g) (p)->lpVtbl->SetOrientation(p,a,b,c,d,e,f,g) +#define IDirectSound3DListener_SetPosition(p,a,b,c,d) (p)->lpVtbl->SetPosition(p,a,b,c,d) +#define IDirectSound3DListener_SetRolloffFactor(p,a,b) (p)->lpVtbl->SetRolloffFactor(p,a,b) +#define IDirectSound3DListener_SetVelocity(p,a,b,c,d) (p)->lpVtbl->SetVelocity(p,a,b,c,d) +#define IDirectSound3DListener_CommitDeferredSettings(p) (p)->lpVtbl->CommitDeferredSettings(p) +#else +#define IDirectSound3DListener_GetAllParameters(p,a) (p)->GetAllParameters(a) +#define IDirectSound3DListener_GetDistanceFactor(p,a) (p)->GetDistanceFactor(a) +#define IDirectSound3DListener_GetDopplerFactor(p,a) (p)->GetDopplerFactor(a) +#define IDirectSound3DListener_GetOrientation(p,a,b) (p)->GetOrientation(a,b) +#define IDirectSound3DListener_GetPosition(p,a) (p)->GetPosition(a) +#define IDirectSound3DListener_GetRolloffFactor(p,a) (p)->GetRolloffFactor(a) +#define IDirectSound3DListener_GetVelocity(p,a) (p)->GetVelocity(a) +#define IDirectSound3DListener_SetAllParameters(p,a,b) (p)->SetAllParameters(a,b) +#define IDirectSound3DListener_SetDistanceFactor(p,a,b) (p)->SetDistanceFactor(a,b) +#define IDirectSound3DListener_SetDopplerFactor(p,a,b) (p)->SetDopplerFactor(a,b) +#define IDirectSound3DListener_SetOrientation(p,a,b,c,d,e,f,g) (p)->SetOrientation(a,b,c,d,e,f,g) +#define IDirectSound3DListener_SetPosition(p,a,b,c,d) (p)->SetPosition(a,b,c,d) +#define IDirectSound3DListener_SetRolloffFactor(p,a,b) (p)->SetRolloffFactor(a,b) +#define IDirectSound3DListener_SetVelocity(p,a,b,c,d) (p)->SetVelocity(a,b,c,d) +#define IDirectSound3DListener_CommitDeferredSettings(p) (p)->CommitDeferredSettings() +#endif + +DEFINE_GUID(IID_IDirectSound3DBuffer, 0x279AFA86, 0x4981, 0x11CE, 0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60); + +#undef INTERFACE +#define INTERFACE IDirectSound3DBuffer + +DECLARE_INTERFACE_(IDirectSound3DBuffer, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(GetAllParameters) (THIS_ LPDS3DBUFFER pDs3dBuffer) PURE; + STDMETHOD(GetConeAngles) (THIS_ LPDWORD pdwInsideConeAngle, LPDWORD pdwOutsideConeAngle) PURE; + STDMETHOD(GetConeOrientation) (THIS_ D3DVECTOR* pvOrientation) PURE; + STDMETHOD(GetConeOutsideVolume) (THIS_ LPLONG plConeOutsideVolume) PURE; + STDMETHOD(GetMaxDistance) (THIS_ D3DVALUE* pflMaxDistance) PURE; + STDMETHOD(GetMinDistance) (THIS_ D3DVALUE* pflMinDistance) PURE; + STDMETHOD(GetMode) (THIS_ LPDWORD pdwMode) PURE; + STDMETHOD(GetPosition) (THIS_ D3DVECTOR* pvPosition) PURE; + STDMETHOD(GetVelocity) (THIS_ D3DVECTOR* pvVelocity) PURE; + STDMETHOD(SetAllParameters) (THIS_ LPCDS3DBUFFER pcDs3dBuffer, DWORD dwApply) PURE; + STDMETHOD(SetConeAngles) (THIS_ DWORD dwInsideConeAngle, DWORD dwOutsideConeAngle, DWORD dwApply) PURE; + STDMETHOD(SetConeOrientation) (THIS_ D3DVALUE x, D3DVALUE y, D3DVALUE z, DWORD dwApply) PURE; + STDMETHOD(SetConeOutsideVolume) (THIS_ LONG lConeOutsideVolume, DWORD dwApply) PURE; + STDMETHOD(SetMaxDistance) (THIS_ D3DVALUE flMaxDistance, DWORD dwApply) PURE; + STDMETHOD(SetMinDistance) (THIS_ D3DVALUE flMinDistance, DWORD dwApply) PURE; + STDMETHOD(SetMode) (THIS_ DWORD dwMode, DWORD dwApply) PURE; + STDMETHOD(SetPosition) (THIS_ D3DVALUE x, D3DVALUE y, D3DVALUE z, DWORD dwApply) PURE; + STDMETHOD(SetVelocity) (THIS_ D3DVALUE x, D3DVALUE y, D3DVALUE z, DWORD dwApply) PURE; +}; + +#define IDirectSound3DBuffer_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSound3DBuffer_AddRef(p) IUnknown_AddRef(p) +#define IDirectSound3DBuffer_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSound3DBuffer_GetAllParameters(p,a) (p)->lpVtbl->GetAllParameters(p,a) +#define IDirectSound3DBuffer_GetConeAngles(p,a,b) (p)->lpVtbl->GetConeAngles(p,a,b) +#define IDirectSound3DBuffer_GetConeOrientation(p,a) (p)->lpVtbl->GetConeOrientation(p,a) +#define IDirectSound3DBuffer_GetConeOutsideVolume(p,a) (p)->lpVtbl->GetConeOutsideVolume(p,a) +#define IDirectSound3DBuffer_GetPosition(p,a) (p)->lpVtbl->GetPosition(p,a) +#define IDirectSound3DBuffer_GetMinDistance(p,a) (p)->lpVtbl->GetMinDistance(p,a) +#define IDirectSound3DBuffer_GetMaxDistance(p,a) (p)->lpVtbl->GetMaxDistance(p,a) +#define IDirectSound3DBuffer_GetMode(p,a) (p)->lpVtbl->GetMode(p,a) +#define IDirectSound3DBuffer_GetVelocity(p,a) (p)->lpVtbl->GetVelocity(p,a) +#define IDirectSound3DBuffer_SetAllParameters(p,a,b) (p)->lpVtbl->SetAllParameters(p,a,b) +#define IDirectSound3DBuffer_SetConeAngles(p,a,b,c) (p)->lpVtbl->SetConeAngles(p,a,b,c) +#define IDirectSound3DBuffer_SetConeOrientation(p,a,b,c,d) (p)->lpVtbl->SetConeOrientation(p,a,b,c,d) +#define IDirectSound3DBuffer_SetConeOutsideVolume(p,a,b) (p)->lpVtbl->SetConeOutsideVolume(p,a,b) +#define IDirectSound3DBuffer_SetPosition(p,a,b,c,d) (p)->lpVtbl->SetPosition(p,a,b,c,d) +#define IDirectSound3DBuffer_SetMinDistance(p,a,b) (p)->lpVtbl->SetMinDistance(p,a,b) +#define IDirectSound3DBuffer_SetMaxDistance(p,a,b) (p)->lpVtbl->SetMaxDistance(p,a,b) +#define IDirectSound3DBuffer_SetMode(p,a,b) (p)->lpVtbl->SetMode(p,a,b) +#define IDirectSound3DBuffer_SetVelocity(p,a,b,c,d) (p)->lpVtbl->SetVelocity(p,a,b,c,d) +#else +#define IDirectSound3DBuffer_GetAllParameters(p,a) (p)->GetAllParameters(a) +#define IDirectSound3DBuffer_GetConeAngles(p,a,b) (p)->GetConeAngles(a,b) +#define IDirectSound3DBuffer_GetConeOrientation(p,a) (p)->GetConeOrientation(a) +#define IDirectSound3DBuffer_GetConeOutsideVolume(p,a) (p)->GetConeOutsideVolume(a) +#define IDirectSound3DBuffer_GetPosition(p,a) (p)->GetPosition(a) +#define IDirectSound3DBuffer_GetMinDistance(p,a) (p)->GetMinDistance(a) +#define IDirectSound3DBuffer_GetMaxDistance(p,a) (p)->GetMaxDistance(a) +#define IDirectSound3DBuffer_GetMode(p,a) (p)->GetMode(a) +#define IDirectSound3DBuffer_GetVelocity(p,a) (p)->GetVelocity(a) +#define IDirectSound3DBuffer_SetAllParameters(p,a,b) (p)->SetAllParameters(a,b) +#define IDirectSound3DBuffer_SetConeAngles(p,a,b,c) (p)->SetConeAngles(a,b,c) +#define IDirectSound3DBuffer_SetConeOrientation(p,a,b,c,d) (p)->SetConeOrientation(a,b,c,d) +#define IDirectSound3DBuffer_SetConeOutsideVolume(p,a,b) (p)->SetConeOutsideVolume(a,b) +#define IDirectSound3DBuffer_SetPosition(p,a,b,c,d) (p)->SetPosition(a,b,c,d) +#define IDirectSound3DBuffer_SetMinDistance(p,a,b) (p)->SetMinDistance(a,b) +#define IDirectSound3DBuffer_SetMaxDistance(p,a,b) (p)->SetMaxDistance(a,b) +#define IDirectSound3DBuffer_SetMode(p,a,b) (p)->SetMode(a,b) +#define IDirectSound3DBuffer_SetVelocity(p,a,b,c,d) (p)->SetVelocity(a,b,c,d) +#endif + +DEFINE_GUID(IID_IDirectSoundCapture, 0xb0210781, 0x89cd, 0x11d0, 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16); + +#undef INTERFACE +#define INTERFACE IDirectSoundCapture + +DECLARE_INTERFACE_(IDirectSoundCapture, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(CreateCaptureBuffer) (THIS_ LPCDSCBUFFERDESC pcDSCBufferDesc, LPDIRECTSOUNDCAPTUREBUFFER *ppDSCBuffer, LPUNKNOWN pUnkOuter) PURE; + STDMETHOD(GetCaps) (THIS_ LPDSCCAPS pDSCCaps) PURE; + STDMETHOD(Initialize) (THIS_ LPCGUID pcGuidDevice) PURE; +}; + +#define IDirectSoundCapture_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundCapture_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundCapture_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundCapture_CreateCaptureBuffer(p,a,b,c) (p)->lpVtbl->CreateCaptureBuffer(p,a,b,c) +#define IDirectSoundCapture_GetCaps(p,a) (p)->lpVtbl->GetCaps(p,a) +#define IDirectSoundCapture_Initialize(p,a) (p)->lpVtbl->Initialize(p,a) +#else +#define IDirectSoundCapture_CreateCaptureBuffer(p,a,b,c) (p)->CreateCaptureBuffer(a,b,c) +#define IDirectSoundCapture_GetCaps(p,a) (p)->GetCaps(a) +#define IDirectSoundCapture_Initialize(p,a) (p)->Initialize(a) +#endif + +DEFINE_GUID(IID_IDirectSoundCaptureBuffer, 0xb0210782, 0x89cd, 0x11d0, 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16); + +#undef INTERFACE +#define INTERFACE IDirectSoundCaptureBuffer + +DECLARE_INTERFACE_(IDirectSoundCaptureBuffer, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(GetCaps) (THIS_ LPDSCBCAPS pDSCBCaps) PURE; + STDMETHOD(GetCurrentPosition) (THIS_ LPDWORD pdwCapturePosition, LPDWORD pdwReadPosition) PURE; + STDMETHOD(GetFormat) (THIS_ LPWAVEFORMATEX pwfxFormat, DWORD dwSizeAllocated, LPDWORD pdwSizeWritten) PURE; + STDMETHOD(GetStatus) (THIS_ LPDWORD pdwStatus) PURE; + STDMETHOD(Initialize) (THIS_ LPDIRECTSOUNDCAPTURE pDirectSoundCapture, LPCDSCBUFFERDESC pcDSCBufferDesc) PURE; + STDMETHOD(Lock) (THIS_ DWORD dwOffset, DWORD dwBytes, LPVOID *ppvAudioPtr1, LPDWORD pdwAudioBytes1, + LPVOID *ppvAudioPtr2, LPDWORD pdwAudioBytes2, DWORD dwFlags) PURE; + STDMETHOD(Start) (THIS_ DWORD dwFlags) PURE; + STDMETHOD(Stop) (THIS) PURE; + STDMETHOD(Unlock) (THIS_ LPVOID pvAudioPtr1, DWORD dwAudioBytes1, LPVOID pvAudioPtr2, DWORD dwAudioBytes2) PURE; +}; + +#define IDirectSoundCaptureBuffer_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundCaptureBuffer_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundCaptureBuffer_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundCaptureBuffer_GetCaps(p,a) (p)->lpVtbl->GetCaps(p,a) +#define IDirectSoundCaptureBuffer_GetCurrentPosition(p,a,b) (p)->lpVtbl->GetCurrentPosition(p,a,b) +#define IDirectSoundCaptureBuffer_GetFormat(p,a,b,c) (p)->lpVtbl->GetFormat(p,a,b,c) +#define IDirectSoundCaptureBuffer_GetStatus(p,a) (p)->lpVtbl->GetStatus(p,a) +#define IDirectSoundCaptureBuffer_Initialize(p,a,b) (p)->lpVtbl->Initialize(p,a,b) +#define IDirectSoundCaptureBuffer_Lock(p,a,b,c,d,e,f,g) (p)->lpVtbl->Lock(p,a,b,c,d,e,f,g) +#define IDirectSoundCaptureBuffer_Start(p,a) (p)->lpVtbl->Start(p,a) +#define IDirectSoundCaptureBuffer_Stop(p) (p)->lpVtbl->Stop(p) +#define IDirectSoundCaptureBuffer_Unlock(p,a,b,c,d) (p)->lpVtbl->Unlock(p,a,b,c,d) +#else +#define IDirectSoundCaptureBuffer_GetCaps(p,a) (p)->GetCaps(a) +#define IDirectSoundCaptureBuffer_GetCurrentPosition(p,a,b) (p)->GetCurrentPosition(a,b) +#define IDirectSoundCaptureBuffer_GetFormat(p,a,b,c) (p)->GetFormat(a,b,c) +#define IDirectSoundCaptureBuffer_GetStatus(p,a) (p)->GetStatus(a) +#define IDirectSoundCaptureBuffer_Initialize(p,a,b) (p)->Initialize(a,b) +#define IDirectSoundCaptureBuffer_Lock(p,a,b,c,d,e,f,g) (p)->Lock(a,b,c,d,e,f,g) +#define IDirectSoundCaptureBuffer_Start(p,a) (p)->Start(a) +#define IDirectSoundCaptureBuffer_Stop(p) (p)->Stop() +#define IDirectSoundCaptureBuffer_Unlock(p,a,b,c,d) (p)->Unlock(a,b,c,d) +#endif + +#if DIRECTSOUND_VERSION >= 0x0800 + +DEFINE_GUID(IID_IDirectSoundCaptureBuffer8, 0x990df4, 0xdbb, 0x4872, 0x83, 0x3e, 0x6d, 0x30, 0x3e, 0x80, 0xae, 0xb6); + +#undef INTERFACE +#define INTERFACE IDirectSoundCaptureBuffer8 + +DECLARE_INTERFACE_(IDirectSoundCaptureBuffer8, IDirectSoundCaptureBuffer) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(GetCaps) (THIS_ LPDSCBCAPS pDSCBCaps) PURE; + STDMETHOD(GetCurrentPosition) (THIS_ LPDWORD pdwCapturePosition, LPDWORD pdwReadPosition) PURE; + STDMETHOD(GetFormat) (THIS_ LPWAVEFORMATEX pwfxFormat, DWORD dwSizeAllocated, LPDWORD pdwSizeWritten) PURE; + STDMETHOD(GetStatus) (THIS_ LPDWORD pdwStatus) PURE; + STDMETHOD(Initialize) (THIS_ LPDIRECTSOUNDCAPTURE pDirectSoundCapture, LPCDSCBUFFERDESC pcDSCBufferDesc) PURE; + STDMETHOD(Lock) (THIS_ DWORD dwOffset, DWORD dwBytes, LPVOID *ppvAudioPtr1, LPDWORD pdwAudioBytes1, + LPVOID *ppvAudioPtr2, LPDWORD pdwAudioBytes2, DWORD dwFlags) PURE; + STDMETHOD(Start) (THIS_ DWORD dwFlags) PURE; + STDMETHOD(Stop) (THIS) PURE; + STDMETHOD(Unlock) (THIS_ LPVOID pvAudioPtr1, DWORD dwAudioBytes1, LPVOID pvAudioPtr2, DWORD dwAudioBytes2) PURE; + + STDMETHOD(GetObjectInPath) (THIS_ REFGUID rguidObject, DWORD dwIndex, REFGUID rguidInterface, LPVOID *ppObject) PURE; + STDMETHOD(GetFXStatus) (DWORD dwFXCount, LPDWORD pdwFXStatus) PURE; +}; + +#define IDirectSoundCaptureBuffer8_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundCaptureBuffer8_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundCaptureBuffer8_Release(p) IUnknown_Release(p) + +#define IDirectSoundCaptureBuffer8_GetCaps(p,a) IDirectSoundCaptureBuffer_GetCaps(p,a) +#define IDirectSoundCaptureBuffer8_GetCurrentPosition(p,a,b) IDirectSoundCaptureBuffer_GetCurrentPosition(p,a,b) +#define IDirectSoundCaptureBuffer8_GetFormat(p,a,b,c) IDirectSoundCaptureBuffer_GetFormat(p,a,b,c) +#define IDirectSoundCaptureBuffer8_GetStatus(p,a) IDirectSoundCaptureBuffer_GetStatus(p,a) +#define IDirectSoundCaptureBuffer8_Initialize(p,a,b) IDirectSoundCaptureBuffer_Initialize(p,a,b) +#define IDirectSoundCaptureBuffer8_Lock(p,a,b,c,d,e,f,g) IDirectSoundCaptureBuffer_Lock(p,a,b,c,d,e,f,g) +#define IDirectSoundCaptureBuffer8_Start(p,a) IDirectSoundCaptureBuffer_Start(p,a) +#define IDirectSoundCaptureBuffer8_Stop(p) IDirectSoundCaptureBuffer_Stop(p)) +#define IDirectSoundCaptureBuffer8_Unlock(p,a,b,c,d) IDirectSoundCaptureBuffer_Unlock(p,a,b,c,d) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundCaptureBuffer8_GetObjectInPath(p,a,b,c,d) (p)->lpVtbl->GetObjectInPath(p,a,b,c,d) +#define IDirectSoundCaptureBuffer8_GetFXStatus(p,a,b) (p)->lpVtbl->GetFXStatus(p,a,b) +#else +#define IDirectSoundCaptureBuffer8_GetObjectInPath(p,a,b,c,d) (p)->GetObjectInPath(a,b,c,d) +#define IDirectSoundCaptureBuffer8_GetFXStatus(p,a,b) (p)->GetFXStatus(a,b) +#endif + +#endif + +DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16); + +#undef INTERFACE +#define INTERFACE IDirectSoundNotify + +DECLARE_INTERFACE_(IDirectSoundNotify, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(SetNotificationPositions) (THIS_ DWORD dwPositionNotifies, LPCDSBPOSITIONNOTIFY pcPositionNotifies) PURE; +}; + +#define IDirectSoundNotify_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundNotify_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundNotify_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundNotify_SetNotificationPositions(p,a,b) (p)->lpVtbl->SetNotificationPositions(p,a,b) +#else +#define IDirectSoundNotify_SetNotificationPositions(p,a,b) (p)->SetNotificationPositions(a,b) +#endif + +#ifndef _IKsPropertySet_ +#define _IKsPropertySet_ + +#ifdef __cplusplus + +struct IKsPropertySet; +#endif + +typedef struct IKsPropertySet *LPKSPROPERTYSET; + +#define KSPROPERTY_SUPPORT_GET 0x00000001 +#define KSPROPERTY_SUPPORT_SET 0x00000002 + +DEFINE_GUID(IID_IKsPropertySet, 0x31efac30, 0x515c, 0x11d0, 0xa9, 0xaa, 0x00, 0xaa, 0x00, 0x61, 0xbe, 0x93); + +#undef INTERFACE +#define INTERFACE IKsPropertySet + +DECLARE_INTERFACE_(IKsPropertySet, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(Get) (THIS_ REFGUID rguidPropSet, ULONG ulId, LPVOID pInstanceData, ULONG ulInstanceLength, + LPVOID pPropertyData, ULONG ulDataLength, PULONG pulBytesReturned) PURE; + STDMETHOD(Set) (THIS_ REFGUID rguidPropSet, ULONG ulId, LPVOID pInstanceData, ULONG ulInstanceLength, + LPVOID pPropertyData, ULONG ulDataLength) PURE; + STDMETHOD(QuerySupport) (THIS_ REFGUID rguidPropSet, ULONG ulId, PULONG pulTypeSupport) PURE; +}; + +#define IKsPropertySet_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IKsPropertySet_AddRef(p) IUnknown_AddRef(p) +#define IKsPropertySet_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IKsPropertySet_Get(p,a,b,c,d,e,f,g) (p)->lpVtbl->Get(p,a,b,c,d,e,f,g) +#define IKsPropertySet_Set(p,a,b,c,d,e,f) (p)->lpVtbl->Set(p,a,b,c,d,e,f) +#define IKsPropertySet_QuerySupport(p,a,b,c) (p)->lpVtbl->QuerySupport(p,a,b,c) +#else +#define IKsPropertySet_Get(p,a,b,c,d,e,f,g) (p)->Get(a,b,c,d,e,f,g) +#define IKsPropertySet_Set(p,a,b,c,d,e,f) (p)->Set(a,b,c,d,e,f) +#define IKsPropertySet_QuerySupport(p,a,b,c) (p)->QuerySupport(a,b,c) +#endif + +#endif + +#if DIRECTSOUND_VERSION >= 0x0800 + +DEFINE_GUID(IID_IDirectSoundFXGargle, 0xd616f352, 0xd622, 0x11ce, 0xaa, 0xc5, 0x00, 0x20, 0xaf, 0x0b, 0x99, 0xa3); + +typedef struct _DSFXGargle +{ + DWORD dwRateHz; + DWORD dwWaveShape; +} DSFXGargle, *LPDSFXGargle; + +#define DSFXGARGLE_WAVE_TRIANGLE 0 +#define DSFXGARGLE_WAVE_SQUARE 1 + +typedef const DSFXGargle *LPCDSFXGargle; + +#define DSFXGARGLE_RATEHZ_MIN 1 +#define DSFXGARGLE_RATEHZ_MAX 1000 + +#undef INTERFACE +#define INTERFACE IDirectSoundFXGargle + +DECLARE_INTERFACE_(IDirectSoundFXGargle, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(SetAllParameters) (THIS_ LPCDSFXGargle pcDsFxGargle) PURE; + STDMETHOD(GetAllParameters) (THIS_ LPDSFXGargle pDsFxGargle) PURE; +}; + +#define IDirectSoundFXGargle_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundFXGargle_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundFXGargle_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundFXGargle_SetAllParameters(p,a) (p)->lpVtbl->SetAllParameters(p,a) +#define IDirectSoundFXGargle_GetAllParameters(p,a) (p)->lpVtbl->GetAllParameters(p,a) +#else +#define IDirectSoundFXGargle_SetAllParameters(p,a) (p)->SetAllParameters(a) +#define IDirectSoundFXGargle_GetAllParameters(p,a) (p)->GetAllParameters(a) +#endif + +DEFINE_GUID(IID_IDirectSoundFXChorus, 0x880842e3, 0x145f, 0x43e6, 0xa9, 0x34, 0xa7, 0x18, 0x06, 0xe5, 0x05, 0x47); + +typedef struct _DSFXChorus +{ + FLOAT fWetDryMix; + FLOAT fDepth; + FLOAT fFeedback; + FLOAT fFrequency; + LONG lWaveform; + FLOAT fDelay; + LONG lPhase; +} DSFXChorus, *LPDSFXChorus; + +typedef const DSFXChorus *LPCDSFXChorus; + +#define DSFXCHORUS_WAVE_TRIANGLE 0 +#define DSFXCHORUS_WAVE_SIN 1 + +#define DSFXCHORUS_WETDRYMIX_MIN 0.0f +#define DSFXCHORUS_WETDRYMIX_MAX 100.0f +#define DSFXCHORUS_DEPTH_MIN 0.0f +#define DSFXCHORUS_DEPTH_MAX 100.0f +#define DSFXCHORUS_FEEDBACK_MIN -99.0f +#define DSFXCHORUS_FEEDBACK_MAX 99.0f +#define DSFXCHORUS_FREQUENCY_MIN 0.0f +#define DSFXCHORUS_FREQUENCY_MAX 10.0f +#define DSFXCHORUS_DELAY_MIN 0.0f +#define DSFXCHORUS_DELAY_MAX 20.0f +#define DSFXCHORUS_PHASE_MIN 0 +#define DSFXCHORUS_PHASE_MAX 4 + +#define DSFXCHORUS_PHASE_NEG_180 0 +#define DSFXCHORUS_PHASE_NEG_90 1 +#define DSFXCHORUS_PHASE_ZERO 2 +#define DSFXCHORUS_PHASE_90 3 +#define DSFXCHORUS_PHASE_180 4 + +#undef INTERFACE +#define INTERFACE IDirectSoundFXChorus + +DECLARE_INTERFACE_(IDirectSoundFXChorus, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(SetAllParameters) (THIS_ LPCDSFXChorus pcDsFxChorus) PURE; + STDMETHOD(GetAllParameters) (THIS_ LPDSFXChorus pDsFxChorus) PURE; +}; + +#define IDirectSoundFXChorus_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundFXChorus_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundFXChorus_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundFXChorus_SetAllParameters(p,a) (p)->lpVtbl->SetAllParameters(p,a) +#define IDirectSoundFXChorus_GetAllParameters(p,a) (p)->lpVtbl->GetAllParameters(p,a) +#else +#define IDirectSoundFXChorus_SetAllParameters(p,a) (p)->SetAllParameters(a) +#define IDirectSoundFXChorus_GetAllParameters(p,a) (p)->GetAllParameters(a) +#endif + +DEFINE_GUID(IID_IDirectSoundFXFlanger, 0x903e9878, 0x2c92, 0x4072, 0x9b, 0x2c, 0xea, 0x68, 0xf5, 0x39, 0x67, 0x83); + +typedef struct _DSFXFlanger +{ + FLOAT fWetDryMix; + FLOAT fDepth; + FLOAT fFeedback; + FLOAT fFrequency; + LONG lWaveform; + FLOAT fDelay; + LONG lPhase; +} DSFXFlanger, *LPDSFXFlanger; + +typedef const DSFXFlanger *LPCDSFXFlanger; + +#define DSFXFLANGER_WAVE_TRIANGLE 0 +#define DSFXFLANGER_WAVE_SIN 1 + +#define DSFXFLANGER_WETDRYMIX_MIN 0.0f +#define DSFXFLANGER_WETDRYMIX_MAX 100.0f +#define DSFXFLANGER_FREQUENCY_MIN 0.0f +#define DSFXFLANGER_FREQUENCY_MAX 10.0f +#define DSFXFLANGER_DEPTH_MIN 0.0f +#define DSFXFLANGER_DEPTH_MAX 100.0f +#define DSFXFLANGER_PHASE_MIN 0 +#define DSFXFLANGER_PHASE_MAX 4 +#define DSFXFLANGER_FEEDBACK_MIN -99.0f +#define DSFXFLANGER_FEEDBACK_MAX 99.0f +#define DSFXFLANGER_DELAY_MIN 0.0f +#define DSFXFLANGER_DELAY_MAX 4.0f + +#define DSFXFLANGER_PHASE_NEG_180 0 +#define DSFXFLANGER_PHASE_NEG_90 1 +#define DSFXFLANGER_PHASE_ZERO 2 +#define DSFXFLANGER_PHASE_90 3 +#define DSFXFLANGER_PHASE_180 4 + +#undef INTERFACE +#define INTERFACE IDirectSoundFXFlanger + +DECLARE_INTERFACE_(IDirectSoundFXFlanger, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(SetAllParameters) (THIS_ LPCDSFXFlanger pcDsFxFlanger) PURE; + STDMETHOD(GetAllParameters) (THIS_ LPDSFXFlanger pDsFxFlanger) PURE; +}; + +#define IDirectSoundFXFlanger_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundFXFlanger_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundFXFlanger_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundFXFlanger_SetAllParameters(p,a) (p)->lpVtbl->SetAllParameters(p,a) +#define IDirectSoundFXFlanger_GetAllParameters(p,a) (p)->lpVtbl->GetAllParameters(p,a) +#else +#define IDirectSoundFXFlanger_SetAllParameters(p,a) (p)->SetAllParameters(a) +#define IDirectSoundFXFlanger_GetAllParameters(p,a) (p)->GetAllParameters(a) +#endif + +DEFINE_GUID(IID_IDirectSoundFXEcho, 0x8bd28edf, 0x50db, 0x4e92, 0xa2, 0xbd, 0x44, 0x54, 0x88, 0xd1, 0xed, 0x42); + +typedef struct _DSFXEcho +{ + FLOAT fWetDryMix; + FLOAT fFeedback; + FLOAT fLeftDelay; + FLOAT fRightDelay; + LONG lPanDelay; +} DSFXEcho, *LPDSFXEcho; + +typedef const DSFXEcho *LPCDSFXEcho; + +#define DSFXECHO_WETDRYMIX_MIN 0.0f +#define DSFXECHO_WETDRYMIX_MAX 100.0f +#define DSFXECHO_FEEDBACK_MIN 0.0f +#define DSFXECHO_FEEDBACK_MAX 100.0f +#define DSFXECHO_LEFTDELAY_MIN 1.0f +#define DSFXECHO_LEFTDELAY_MAX 2000.0f +#define DSFXECHO_RIGHTDELAY_MIN 1.0f +#define DSFXECHO_RIGHTDELAY_MAX 2000.0f +#define DSFXECHO_PANDELAY_MIN 0 +#define DSFXECHO_PANDELAY_MAX 1 + +#undef INTERFACE +#define INTERFACE IDirectSoundFXEcho + +DECLARE_INTERFACE_(IDirectSoundFXEcho, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(SetAllParameters) (THIS_ LPCDSFXEcho pcDsFxEcho) PURE; + STDMETHOD(GetAllParameters) (THIS_ LPDSFXEcho pDsFxEcho) PURE; +}; + +#define IDirectSoundFXEcho_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundFXEcho_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundFXEcho_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundFXEcho_SetAllParameters(p,a) (p)->lpVtbl->SetAllParameters(p,a) +#define IDirectSoundFXEcho_GetAllParameters(p,a) (p)->lpVtbl->GetAllParameters(p,a) +#else +#define IDirectSoundFXEcho_SetAllParameters(p,a) (p)->SetAllParameters(a) +#define IDirectSoundFXEcho_GetAllParameters(p,a) (p)->GetAllParameters(a) +#endif + +DEFINE_GUID(IID_IDirectSoundFXDistortion, 0x8ecf4326, 0x455f, 0x4d8b, 0xbd, 0xa9, 0x8d, 0x5d, 0x3e, 0x9e, 0x3e, 0x0b); + +typedef struct _DSFXDistortion +{ + FLOAT fGain; + FLOAT fEdge; + FLOAT fPostEQCenterFrequency; + FLOAT fPostEQBandwidth; + FLOAT fPreLowpassCutoff; +} DSFXDistortion, *LPDSFXDistortion; + +typedef const DSFXDistortion *LPCDSFXDistortion; + +#define DSFXDISTORTION_GAIN_MIN -60.0f +#define DSFXDISTORTION_GAIN_MAX 0.0f +#define DSFXDISTORTION_EDGE_MIN 0.0f +#define DSFXDISTORTION_EDGE_MAX 100.0f +#define DSFXDISTORTION_POSTEQCENTERFREQUENCY_MIN 100.0f +#define DSFXDISTORTION_POSTEQCENTERFREQUENCY_MAX 8000.0f +#define DSFXDISTORTION_POSTEQBANDWIDTH_MIN 100.0f +#define DSFXDISTORTION_POSTEQBANDWIDTH_MAX 8000.0f +#define DSFXDISTORTION_PRELOWPASSCUTOFF_MIN 100.0f +#define DSFXDISTORTION_PRELOWPASSCUTOFF_MAX 8000.0f + +#undef INTERFACE +#define INTERFACE IDirectSoundFXDistortion + +DECLARE_INTERFACE_(IDirectSoundFXDistortion, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(SetAllParameters) (THIS_ LPCDSFXDistortion pcDsFxDistortion) PURE; + STDMETHOD(GetAllParameters) (THIS_ LPDSFXDistortion pDsFxDistortion) PURE; +}; + +#define IDirectSoundFXDistortion_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundFXDistortion_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundFXDistortion_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundFXDistortion_SetAllParameters(p,a) (p)->lpVtbl->SetAllParameters(p,a) +#define IDirectSoundFXDistortion_GetAllParameters(p,a) (p)->lpVtbl->GetAllParameters(p,a) +#else +#define IDirectSoundFXDistortion_SetAllParameters(p,a) (p)->SetAllParameters(a) +#define IDirectSoundFXDistortion_GetAllParameters(p,a) (p)->GetAllParameters(a) +#endif + +DEFINE_GUID(IID_IDirectSoundFXCompressor, 0x4bbd1154, 0x62f6, 0x4e2c, 0xa1, 0x5c, 0xd3, 0xb6, 0xc4, 0x17, 0xf7, 0xa0); + +typedef struct _DSFXCompressor +{ + FLOAT fGain; + FLOAT fAttack; + FLOAT fRelease; + FLOAT fThreshold; + FLOAT fRatio; + FLOAT fPredelay; +} DSFXCompressor, *LPDSFXCompressor; + +typedef const DSFXCompressor *LPCDSFXCompressor; + +#define DSFXCOMPRESSOR_GAIN_MIN -60.0f +#define DSFXCOMPRESSOR_GAIN_MAX 60.0f +#define DSFXCOMPRESSOR_ATTACK_MIN 0.01f +#define DSFXCOMPRESSOR_ATTACK_MAX 500.0f +#define DSFXCOMPRESSOR_RELEASE_MIN 50.0f +#define DSFXCOMPRESSOR_RELEASE_MAX 3000.0f +#define DSFXCOMPRESSOR_THRESHOLD_MIN -60.0f +#define DSFXCOMPRESSOR_THRESHOLD_MAX 0.0f +#define DSFXCOMPRESSOR_RATIO_MIN 1.0f +#define DSFXCOMPRESSOR_RATIO_MAX 100.0f +#define DSFXCOMPRESSOR_PREDELAY_MIN 0.0f +#define DSFXCOMPRESSOR_PREDELAY_MAX 4.0f + +#undef INTERFACE +#define INTERFACE IDirectSoundFXCompressor + +DECLARE_INTERFACE_(IDirectSoundFXCompressor, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(SetAllParameters) (THIS_ LPCDSFXCompressor pcDsFxCompressor) PURE; + STDMETHOD(GetAllParameters) (THIS_ LPDSFXCompressor pDsFxCompressor) PURE; +}; + +#define IDirectSoundFXCompressor_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundFXCompressor_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundFXCompressor_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundFXCompressor_SetAllParameters(p,a) (p)->lpVtbl->SetAllParameters(p,a) +#define IDirectSoundFXCompressor_GetAllParameters(p,a) (p)->lpVtbl->GetAllParameters(p,a) +#else +#define IDirectSoundFXCompressor_SetAllParameters(p,a) (p)->SetAllParameters(a) +#define IDirectSoundFXCompressor_GetAllParameters(p,a) (p)->GetAllParameters(a) +#endif + +DEFINE_GUID(IID_IDirectSoundFXParamEq, 0xc03ca9fe, 0xfe90, 0x4204, 0x80, 0x78, 0x82, 0x33, 0x4c, 0xd1, 0x77, 0xda); + +typedef struct _DSFXParamEq +{ + FLOAT fCenter; + FLOAT fBandwidth; + FLOAT fGain; +} DSFXParamEq, *LPDSFXParamEq; + +typedef const DSFXParamEq *LPCDSFXParamEq; + +#define DSFXPARAMEQ_CENTER_MIN 80.0f +#define DSFXPARAMEQ_CENTER_MAX 16000.0f +#define DSFXPARAMEQ_BANDWIDTH_MIN 1.0f +#define DSFXPARAMEQ_BANDWIDTH_MAX 36.0f +#define DSFXPARAMEQ_GAIN_MIN -15.0f +#define DSFXPARAMEQ_GAIN_MAX 15.0f + +#undef INTERFACE +#define INTERFACE IDirectSoundFXParamEq + +DECLARE_INTERFACE_(IDirectSoundFXParamEq, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(SetAllParameters) (THIS_ LPCDSFXParamEq pcDsFxParamEq) PURE; + STDMETHOD(GetAllParameters) (THIS_ LPDSFXParamEq pDsFxParamEq) PURE; +}; + +#define IDirectSoundFXParamEq_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundFXParamEq_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundFXParamEq_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundFXParamEq_SetAllParameters(p,a) (p)->lpVtbl->SetAllParameters(p,a) +#define IDirectSoundFXParamEq_GetAllParameters(p,a) (p)->lpVtbl->GetAllParameters(p,a) +#else +#define IDirectSoundFXParamEq_SetAllParameters(p,a) (p)->SetAllParameters(a) +#define IDirectSoundFXParamEq_GetAllParameters(p,a) (p)->GetAllParameters(a) +#endif + +DEFINE_GUID(IID_IDirectSoundFXI3DL2Reverb, 0x4b166a6a, 0x0d66, 0x43f3, 0x80, 0xe3, 0xee, 0x62, 0x80, 0xde, 0xe1, 0xa4); + +typedef struct _DSFXI3DL2Reverb +{ + LONG lRoom; + LONG lRoomHF; + FLOAT flRoomRolloffFactor; + FLOAT flDecayTime; + FLOAT flDecayHFRatio; + LONG lReflections; + FLOAT flReflectionsDelay; + LONG lReverb; + FLOAT flReverbDelay; + FLOAT flDiffusion; + FLOAT flDensity; + FLOAT flHFReference; +} DSFXI3DL2Reverb, *LPDSFXI3DL2Reverb; + +typedef const DSFXI3DL2Reverb *LPCDSFXI3DL2Reverb; + +#define DSFX_I3DL2REVERB_ROOM_MIN (-10000) +#define DSFX_I3DL2REVERB_ROOM_MAX 0 +#define DSFX_I3DL2REVERB_ROOM_DEFAULT (-1000) + +#define DSFX_I3DL2REVERB_ROOMHF_MIN (-10000) +#define DSFX_I3DL2REVERB_ROOMHF_MAX 0 +#define DSFX_I3DL2REVERB_ROOMHF_DEFAULT (-100) + +#define DSFX_I3DL2REVERB_ROOMROLLOFFFACTOR_MIN 0.0f +#define DSFX_I3DL2REVERB_ROOMROLLOFFFACTOR_MAX 10.0f +#define DSFX_I3DL2REVERB_ROOMROLLOFFFACTOR_DEFAULT 0.0f + +#define DSFX_I3DL2REVERB_DECAYTIME_MIN 0.1f +#define DSFX_I3DL2REVERB_DECAYTIME_MAX 20.0f +#define DSFX_I3DL2REVERB_DECAYTIME_DEFAULT 1.49f + +#define DSFX_I3DL2REVERB_DECAYHFRATIO_MIN 0.1f +#define DSFX_I3DL2REVERB_DECAYHFRATIO_MAX 2.0f +#define DSFX_I3DL2REVERB_DECAYHFRATIO_DEFAULT 0.83f + +#define DSFX_I3DL2REVERB_REFLECTIONS_MIN (-10000) +#define DSFX_I3DL2REVERB_REFLECTIONS_MAX 1000 +#define DSFX_I3DL2REVERB_REFLECTIONS_DEFAULT (-2602) + +#define DSFX_I3DL2REVERB_REFLECTIONSDELAY_MIN 0.0f +#define DSFX_I3DL2REVERB_REFLECTIONSDELAY_MAX 0.3f +#define DSFX_I3DL2REVERB_REFLECTIONSDELAY_DEFAULT 0.007f + +#define DSFX_I3DL2REVERB_REVERB_MIN (-10000) +#define DSFX_I3DL2REVERB_REVERB_MAX 2000 +#define DSFX_I3DL2REVERB_REVERB_DEFAULT (200) + +#define DSFX_I3DL2REVERB_REVERBDELAY_MIN 0.0f +#define DSFX_I3DL2REVERB_REVERBDELAY_MAX 0.1f +#define DSFX_I3DL2REVERB_REVERBDELAY_DEFAULT 0.011f + +#define DSFX_I3DL2REVERB_DIFFUSION_MIN 0.0f +#define DSFX_I3DL2REVERB_DIFFUSION_MAX 100.0f +#define DSFX_I3DL2REVERB_DIFFUSION_DEFAULT 100.0f + +#define DSFX_I3DL2REVERB_DENSITY_MIN 0.0f +#define DSFX_I3DL2REVERB_DENSITY_MAX 100.0f +#define DSFX_I3DL2REVERB_DENSITY_DEFAULT 100.0f + +#define DSFX_I3DL2REVERB_HFREFERENCE_MIN 20.0f +#define DSFX_I3DL2REVERB_HFREFERENCE_MAX 20000.0f +#define DSFX_I3DL2REVERB_HFREFERENCE_DEFAULT 5000.0f + +#define DSFX_I3DL2REVERB_QUALITY_MIN 0 +#define DSFX_I3DL2REVERB_QUALITY_MAX 3 +#define DSFX_I3DL2REVERB_QUALITY_DEFAULT 2 + +#undef INTERFACE +#define INTERFACE IDirectSoundFXI3DL2Reverb + +DECLARE_INTERFACE_(IDirectSoundFXI3DL2Reverb, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(SetAllParameters) (THIS_ LPCDSFXI3DL2Reverb pcDsFxI3DL2Reverb) PURE; + STDMETHOD(GetAllParameters) (THIS_ LPDSFXI3DL2Reverb pDsFxI3DL2Reverb) PURE; + STDMETHOD(SetPreset) (THIS_ DWORD dwPreset) PURE; + STDMETHOD(GetPreset) (THIS_ LPDWORD pdwPreset) PURE; + STDMETHOD(SetQuality) (THIS_ LONG lQuality) PURE; + STDMETHOD(GetQuality) (THIS_ LONG *plQuality) PURE; +}; + +#define IDirectSoundFXI3DL2Reverb_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundFXI3DL2Reverb_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundFXI3DL2Reverb_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundFXI3DL2Reverb_SetAllParameters(p,a) (p)->lpVtbl->SetAllParameters(p,a) +#define IDirectSoundFXI3DL2Reverb_GetAllParameters(p,a) (p)->lpVtbl->GetAllParameters(p,a) +#define IDirectSoundFXI3DL2Reverb_SetPreset(p,a) (p)->lpVtbl->SetPreset(p,a) +#define IDirectSoundFXI3DL2Reverb_GetPreset(p,a) (p)->lpVtbl->GetPreset(p,a) +#else +#define IDirectSoundFXI3DL2Reverb_SetAllParameters(p,a) (p)->SetAllParameters(a) +#define IDirectSoundFXI3DL2Reverb_GetAllParameters(p,a) (p)->GetAllParameters(a) +#define IDirectSoundFXI3DL2Reverb_SetPreset(p,a) (p)->SetPreset(a) +#define IDirectSoundFXI3DL2Reverb_GetPreset(p,a) (p)->GetPreset(a) +#endif + +DEFINE_GUID(IID_IDirectSoundFXWavesReverb,0x46858c3a,0x0dc6,0x45e3,0xb7,0x60,0xd4,0xee,0xf1,0x6c,0xb3,0x25); + +typedef struct _DSFXWavesReverb +{ + FLOAT fInGain; + FLOAT fReverbMix; + FLOAT fReverbTime; + FLOAT fHighFreqRTRatio; +} DSFXWavesReverb, *LPDSFXWavesReverb; + +typedef const DSFXWavesReverb *LPCDSFXWavesReverb; + +#define DSFX_WAVESREVERB_INGAIN_MIN -96.0f +#define DSFX_WAVESREVERB_INGAIN_MAX 0.0f +#define DSFX_WAVESREVERB_INGAIN_DEFAULT 0.0f +#define DSFX_WAVESREVERB_REVERBMIX_MIN -96.0f +#define DSFX_WAVESREVERB_REVERBMIX_MAX 0.0f +#define DSFX_WAVESREVERB_REVERBMIX_DEFAULT 0.0f +#define DSFX_WAVESREVERB_REVERBTIME_MIN 0.001f +#define DSFX_WAVESREVERB_REVERBTIME_MAX 3000.0f +#define DSFX_WAVESREVERB_REVERBTIME_DEFAULT 1000.0f +#define DSFX_WAVESREVERB_HIGHFREQRTRATIO_MIN 0.001f +#define DSFX_WAVESREVERB_HIGHFREQRTRATIO_MAX 0.999f +#define DSFX_WAVESREVERB_HIGHFREQRTRATIO_DEFAULT 0.001f + +#undef INTERFACE +#define INTERFACE IDirectSoundFXWavesReverb + +DECLARE_INTERFACE_(IDirectSoundFXWavesReverb, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(SetAllParameters) (THIS_ LPCDSFXWavesReverb pcDsFxWavesReverb) PURE; + STDMETHOD(GetAllParameters) (THIS_ LPDSFXWavesReverb pDsFxWavesReverb) PURE; +}; + +#define IDirectSoundFXWavesReverb_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundFXWavesReverb_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundFXWavesReverb_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundFXWavesReverb_SetAllParameters(p,a) (p)->lpVtbl->SetAllParameters(p,a) +#define IDirectSoundFXWavesReverb_GetAllParameters(p,a) (p)->lpVtbl->GetAllParameters(p,a) +#else +#define IDirectSoundFXWavesReverb_SetAllParameters(p,a) (p)->SetAllParameters(a) +#define IDirectSoundFXWavesReverb_GetAllParameters(p,a) (p)->GetAllParameters(a) +#endif + +DEFINE_GUID(IID_IDirectSoundCaptureFXAec, 0x174d3eb9, 0x6696, 0x4fac, 0xa4, 0x6c, 0xa0, 0xac, 0x7b, 0xc9, 0xe2, 0xf); + +typedef struct _DSCFXAec +{ + BOOL fEnable; + BOOL fReset; +} DSCFXAec, *LPDSCFXAec; + +typedef const DSCFXAec *LPCDSCFXAec; + +#undef INTERFACE +#define INTERFACE IDirectSoundCaptureFXAec + +DECLARE_INTERFACE_(IDirectSoundCaptureFXAec, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(SetAllParameters) (THIS_ LPCDSCFXAec pDscFxAec) PURE; + STDMETHOD(GetAllParameters) (THIS_ LPDSCFXAec pDscFxAec) PURE; +}; + +#define IDirectSoundCaptureFXAec_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundCaptureFXAec_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundCaptureFXAec_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundCaptureFXAec_SetAllParameters(p,a) (p)->lpVtbl->SetAllParameters(p,a) +#define IDirectSoundCaptureFXAec_GetAllParameters(p,a) (p)->lpVtbl->GetAllParameters(p,a) +#else +#define IDirectSoundCaptureFXAec_SetAllParameters(p,a) (p)->SetAllParameters(a) +#define IDirectSoundCaptureFXAec_GetAllParameters(p,a) (p)->GetAllParameters(a) +#endif + +DEFINE_GUID(IID_IDirectSoundCaptureFXNoiseSuppress, 0xed311e41, 0xfbae, 0x4175, 0x96, 0x25, 0xcd, 0x8, 0x54, 0xf6, 0x93, 0xca); + +typedef struct _DSCFXNoiseSuppress +{ + BOOL fEnable; + BOOL fReset; +} DSCFXNoiseSuppress, *LPDSCFXNoiseSuppress; + +typedef const DSCFXNoiseSuppress *LPCDSCFXNoiseSuppress; + +#undef INTERFACE +#define INTERFACE IDirectSoundCaptureFXNoiseSuppress + +DECLARE_INTERFACE_(IDirectSoundCaptureFXNoiseSuppress, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(SetAllParameters) (THIS_ LPCDSCFXNoiseSuppress pcDscFxNoiseSuppress) PURE; + STDMETHOD(GetAllParameters) (THIS_ LPDSCFXNoiseSuppress pDscFxNoiseSuppress) PURE; +}; + +#define IDirectSoundCaptureFXNoiseSuppress_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundCaptureFXNoiseSuppress_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundCaptureFXNoiseSuppress_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundCaptureFXNoiseSuppress_SetAllParameters(p,a) (p)->lpVtbl->SetAllParameters(p,a) +#define IDirectSoundCaptureFXNoiseSuppress_GetAllParameters(p,a) (p)->lpVtbl->GetAllParameters(p,a) +#else +#define IDirectSoundCaptureFXNoiseSuppress_SetAllParameters(p,a) (p)->SetAllParameters(a) +#define IDirectSoundCaptureFXNoiseSuppress_GetAllParameters(p,a) (p)->GetAllParameters(a) +#endif + +#ifndef _IDirectSoundFullDuplex_ +#define _IDirectSoundFullDuplex_ + +#ifdef __cplusplus + +struct IDirectSoundFullDuplex; +#endif + +typedef struct IDirectSoundFullDuplex *LPDIRECTSOUNDFULLDUPLEX; + +DEFINE_GUID(IID_IDirectSoundFullDuplex, 0xedcb4c7a, 0xdaab, 0x4216, 0xa4, 0x2e, 0x6c, 0x50, 0x59, 0x6d, 0xdc, 0x1d); + +#undef INTERFACE +#define INTERFACE IDirectSoundFullDuplex + +DECLARE_INTERFACE_(IDirectSoundFullDuplex, IUnknown) +{ + + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(Initialize) (THIS_ LPCGUID pCaptureGuid, LPCGUID pRenderGuid, LPCDSCBUFFERDESC lpDscBufferDesc, LPCDSBUFFERDESC lpDsBufferDesc, HWND hWnd, DWORD dwLevel, LPLPDIRECTSOUNDCAPTUREBUFFER8 lplpDirectSoundCaptureBuffer8, LPLPDIRECTSOUNDBUFFER8 lplpDirectSoundBuffer8) PURE; +}; + +#define IDirectSoundFullDuplex_QueryInterface(p,a,b) IUnknown_QueryInterface(p,a,b) +#define IDirectSoundFullDuplex_AddRef(p) IUnknown_AddRef(p) +#define IDirectSoundFullDuplex_Release(p) IUnknown_Release(p) + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectSoundFullDuplex_Initialize(p,a,b,c,d,e,f,g,h) (p)->lpVtbl->Initialize(p,a,b,c,d,e,f,g,h) +#else +#define IDirectSoundFullDuplex_Initialize(p,a,b,c,d,e,f,g,h) (p)->Initialize(a,b,c,d,e,f,g,h) +#endif + +#endif + +#endif + +#define DS_OK S_OK + +#define DS_NO_VIRTUALIZATION MAKE_HRESULT(0, _FACDS, 10) + +#define DS_INCOMPLETE MAKE_HRESULT(0, _FACDS, 20) + +#define DSERR_ALLOCATED MAKE_DSHRESULT(10) + +#define DSERR_CONTROLUNAVAIL MAKE_DSHRESULT(30) + +#define DSERR_INVALIDPARAM E_INVALIDARG + +#define DSERR_INVALIDCALL MAKE_DSHRESULT(50) + +#define DSERR_GENERIC E_FAIL + +#define DSERR_PRIOLEVELNEEDED MAKE_DSHRESULT(70) + +#define DSERR_OUTOFMEMORY E_OUTOFMEMORY + +#define DSERR_BADFORMAT MAKE_DSHRESULT(100) + +#define DSERR_UNSUPPORTED E_NOTIMPL + +#define DSERR_NODRIVER MAKE_DSHRESULT(120) + +#define DSERR_ALREADYINITIALIZED MAKE_DSHRESULT(130) + +#define DSERR_NOAGGREGATION CLASS_E_NOAGGREGATION + +#define DSERR_BUFFERLOST MAKE_DSHRESULT(150) + +#define DSERR_OTHERAPPHASPRIO MAKE_DSHRESULT(160) + +#define DSERR_UNINITIALIZED MAKE_DSHRESULT(170) + +#define DSERR_NOINTERFACE E_NOINTERFACE + +#define DSERR_ACCESSDENIED E_ACCESSDENIED + +#define DSERR_BUFFERTOOSMALL MAKE_DSHRESULT(180) + +#define DSERR_DS8_REQUIRED MAKE_DSHRESULT(190) + +#define DSERR_SENDLOOP MAKE_DSHRESULT(200) + +#define DSERR_BADSENDBUFFERGUID MAKE_DSHRESULT(210) + +#define DSERR_OBJECTNOTFOUND MAKE_DSHRESULT(4449) + +#define DSCAPS_PRIMARYMONO 0x00000001 +#define DSCAPS_PRIMARYSTEREO 0x00000002 +#define DSCAPS_PRIMARY8BIT 0x00000004 +#define DSCAPS_PRIMARY16BIT 0x00000008 +#define DSCAPS_CONTINUOUSRATE 0x00000010 +#define DSCAPS_EMULDRIVER 0x00000020 +#define DSCAPS_CERTIFIED 0x00000040 +#define DSCAPS_SECONDARYMONO 0x00000100 +#define DSCAPS_SECONDARYSTEREO 0x00000200 +#define DSCAPS_SECONDARY8BIT 0x00000400 +#define DSCAPS_SECONDARY16BIT 0x00000800 + +#define DSSCL_NORMAL 0x00000001 +#define DSSCL_PRIORITY 0x00000002 +#define DSSCL_EXCLUSIVE 0x00000003 +#define DSSCL_WRITEPRIMARY 0x00000004 + +#define DSSPEAKER_HEADPHONE 0x00000001 +#define DSSPEAKER_MONO 0x00000002 +#define DSSPEAKER_QUAD 0x00000003 +#define DSSPEAKER_STEREO 0x00000004 +#define DSSPEAKER_SURROUND 0x00000005 +#define DSSPEAKER_5POINT1 0x00000006 + +#define DSSPEAKER_GEOMETRY_MIN 0x00000005 +#define DSSPEAKER_GEOMETRY_NARROW 0x0000000A +#define DSSPEAKER_GEOMETRY_WIDE 0x00000014 +#define DSSPEAKER_GEOMETRY_MAX 0x000000B4 + +#define DSSPEAKER_COMBINED(c, g) ((DWORD)(((BYTE)(c)) | ((DWORD)((BYTE)(g))) << 16)) +#define DSSPEAKER_CONFIG(a) ((BYTE)(a)) +#define DSSPEAKER_GEOMETRY(a) ((BYTE)(((DWORD)(a) >> 16) & 0x00FF)) + +#define DSBCAPS_PRIMARYBUFFER 0x00000001 +#define DSBCAPS_STATIC 0x00000002 +#define DSBCAPS_LOCHARDWARE 0x00000004 +#define DSBCAPS_LOCSOFTWARE 0x00000008 +#define DSBCAPS_CTRL3D 0x00000010 +#define DSBCAPS_CTRLFREQUENCY 0x00000020 +#define DSBCAPS_CTRLPAN 0x00000040 +#define DSBCAPS_CTRLVOLUME 0x00000080 +#define DSBCAPS_CTRLPOSITIONNOTIFY 0x00000100 +#define DSBCAPS_CTRLFX 0x00000200 +#define DSBCAPS_STICKYFOCUS 0x00004000 +#define DSBCAPS_GLOBALFOCUS 0x00008000 +#define DSBCAPS_GETCURRENTPOSITION2 0x00010000 +#define DSBCAPS_MUTE3DATMAXDISTANCE 0x00020000 +#define DSBCAPS_LOCDEFER 0x00040000 + +#define DSBPLAY_LOOPING 0x00000001 +#define DSBPLAY_LOCHARDWARE 0x00000002 +#define DSBPLAY_LOCSOFTWARE 0x00000004 +#define DSBPLAY_TERMINATEBY_TIME 0x00000008 +#define DSBPLAY_TERMINATEBY_DISTANCE 0x000000010 +#define DSBPLAY_TERMINATEBY_PRIORITY 0x000000020 + +#define DSBSTATUS_PLAYING 0x00000001 +#define DSBSTATUS_BUFFERLOST 0x00000002 +#define DSBSTATUS_LOOPING 0x00000004 +#define DSBSTATUS_LOCHARDWARE 0x00000008 +#define DSBSTATUS_LOCSOFTWARE 0x00000010 +#define DSBSTATUS_TERMINATED 0x00000020 + +#define DSBLOCK_FROMWRITECURSOR 0x00000001 +#define DSBLOCK_ENTIREBUFFER 0x00000002 + +#define DSBFREQUENCY_MIN 100 +#define DSBFREQUENCY_MAX 100000 +#define DSBFREQUENCY_ORIGINAL 0 + +#define DSBPAN_LEFT -10000 +#define DSBPAN_CENTER 0 +#define DSBPAN_RIGHT 10000 + +#define DSBVOLUME_MIN -10000 +#define DSBVOLUME_MAX 0 + +#define DSBSIZE_MIN 4 +#define DSBSIZE_MAX 0x0FFFFFFF +#define DSBSIZE_FX_MIN 150 + +#define DS3DMODE_NORMAL 0x00000000 +#define DS3DMODE_HEADRELATIVE 0x00000001 +#define DS3DMODE_DISABLE 0x00000002 + +#define DS3D_IMMEDIATE 0x00000000 +#define DS3D_DEFERRED 0x00000001 + +#define DS3D_MINDISTANCEFACTOR FLT_MIN +#define DS3D_MAXDISTANCEFACTOR FLT_MAX +#define DS3D_DEFAULTDISTANCEFACTOR 1.0f + +#define DS3D_MINROLLOFFFACTOR 0.0f +#define DS3D_MAXROLLOFFFACTOR 10.0f +#define DS3D_DEFAULTROLLOFFFACTOR 1.0f + +#define DS3D_MINDOPPLERFACTOR 0.0f +#define DS3D_MAXDOPPLERFACTOR 10.0f +#define DS3D_DEFAULTDOPPLERFACTOR 1.0f + +#define DS3D_DEFAULTMINDISTANCE 1.0f +#define DS3D_DEFAULTMAXDISTANCE 1000000000.0f + +#define DS3D_MINCONEANGLE 0 +#define DS3D_MAXCONEANGLE 360 +#define DS3D_DEFAULTCONEANGLE 360 + +#define DS3D_DEFAULTCONEOUTSIDEVOLUME DSBVOLUME_MAX + +#define DSCCAPS_EMULDRIVER DSCAPS_EMULDRIVER +#define DSCCAPS_CERTIFIED DSCAPS_CERTIFIED + +#define DSCBCAPS_WAVEMAPPED 0x80000000 + +#if DIRECTSOUND_VERSION >= 0x0800 +#define DSCBCAPS_CTRLFX 0x00000200 +#endif + +#define DSCBLOCK_ENTIREBUFFER 0x00000001 + +#define DSCBSTATUS_CAPTURING 0x00000001 +#define DSCBSTATUS_LOOPING 0x00000002 + +#define DSCBSTART_LOOPING 0x00000001 + +#define DSBPN_OFFSETSTOP 0xFFFFFFFF + +#define DS_CERTIFIED 0x00000000 +#define DS_UNCERTIFIED 0x00000001 + +#define DS_SYSTEM_RESOURCES_NO_HOST_RESOURCES 0x00000000 +#define DS_SYSTEM_RESOURCES_ALL_HOST_RESOURCES 0x7FFFFFFF +#define DS_SYSTEM_RESOURCES_UNDEFINED 0x80000000 + +enum +{ + DSFX_I3DL2_MATERIAL_PRESET_SINGLEWINDOW, + DSFX_I3DL2_MATERIAL_PRESET_DOUBLEWINDOW, + DSFX_I3DL2_MATERIAL_PRESET_THINDOOR, + DSFX_I3DL2_MATERIAL_PRESET_THICKDOOR, + DSFX_I3DL2_MATERIAL_PRESET_WOODWALL, + DSFX_I3DL2_MATERIAL_PRESET_BRICKWALL, + DSFX_I3DL2_MATERIAL_PRESET_STONEWALL, + DSFX_I3DL2_MATERIAL_PRESET_CURTAIN +}; + +#define I3DL2_MATERIAL_PRESET_SINGLEWINDOW -2800,0.71f +#define I3DL2_MATERIAL_PRESET_DOUBLEWINDOW -5000,0.40f +#define I3DL2_MATERIAL_PRESET_THINDOOR -1800,0.66f +#define I3DL2_MATERIAL_PRESET_THICKDOOR -4400,0.64f +#define I3DL2_MATERIAL_PRESET_WOODWALL -4000,0.50f +#define I3DL2_MATERIAL_PRESET_BRICKWALL -5000,0.60f +#define I3DL2_MATERIAL_PRESET_STONEWALL -6000,0.68f +#define I3DL2_MATERIAL_PRESET_CURTAIN -1200,0.15f + +enum +{ + DSFX_I3DL2_ENVIRONMENT_PRESET_DEFAULT, + DSFX_I3DL2_ENVIRONMENT_PRESET_GENERIC, + DSFX_I3DL2_ENVIRONMENT_PRESET_PADDEDCELL, + DSFX_I3DL2_ENVIRONMENT_PRESET_ROOM, + DSFX_I3DL2_ENVIRONMENT_PRESET_BATHROOM, + DSFX_I3DL2_ENVIRONMENT_PRESET_LIVINGROOM, + DSFX_I3DL2_ENVIRONMENT_PRESET_STONEROOM, + DSFX_I3DL2_ENVIRONMENT_PRESET_AUDITORIUM, + DSFX_I3DL2_ENVIRONMENT_PRESET_CONCERTHALL, + DSFX_I3DL2_ENVIRONMENT_PRESET_CAVE, + DSFX_I3DL2_ENVIRONMENT_PRESET_ARENA, + DSFX_I3DL2_ENVIRONMENT_PRESET_HANGAR, + DSFX_I3DL2_ENVIRONMENT_PRESET_CARPETEDHALLWAY, + DSFX_I3DL2_ENVIRONMENT_PRESET_HALLWAY, + DSFX_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR, + DSFX_I3DL2_ENVIRONMENT_PRESET_ALLEY, + DSFX_I3DL2_ENVIRONMENT_PRESET_FOREST, + DSFX_I3DL2_ENVIRONMENT_PRESET_CITY, + DSFX_I3DL2_ENVIRONMENT_PRESET_MOUNTAINS, + DSFX_I3DL2_ENVIRONMENT_PRESET_QUARRY, + DSFX_I3DL2_ENVIRONMENT_PRESET_PLAIN, + DSFX_I3DL2_ENVIRONMENT_PRESET_PARKINGLOT, + DSFX_I3DL2_ENVIRONMENT_PRESET_SEWERPIPE, + DSFX_I3DL2_ENVIRONMENT_PRESET_UNDERWATER, + DSFX_I3DL2_ENVIRONMENT_PRESET_SMALLROOM, + DSFX_I3DL2_ENVIRONMENT_PRESET_MEDIUMROOM, + DSFX_I3DL2_ENVIRONMENT_PRESET_LARGEROOM, + DSFX_I3DL2_ENVIRONMENT_PRESET_MEDIUMHALL, + DSFX_I3DL2_ENVIRONMENT_PRESET_LARGEHALL, + DSFX_I3DL2_ENVIRONMENT_PRESET_PLATE +}; + +#define I3DL2_ENVIRONMENT_PRESET_DEFAULT -1000, -100, 0.0f, 1.49f, 0.83f, -2602, 0.007f, 200, 0.011f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_GENERIC -1000, -100, 0.0f, 1.49f, 0.83f, -2602, 0.007f, 200, 0.011f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_PADDEDCELL -1000,-6000, 0.0f, 0.17f, 0.10f, -1204, 0.001f, 207, 0.002f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_ROOM -1000, -454, 0.0f, 0.40f, 0.83f, -1646, 0.002f, 53, 0.003f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_BATHROOM -1000,-1200, 0.0f, 1.49f, 0.54f, -370, 0.007f, 1030, 0.011f, 100.0f, 60.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_LIVINGROOM -1000,-6000, 0.0f, 0.50f, 0.10f, -1376, 0.003f, -1104, 0.004f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_STONEROOM -1000, -300, 0.0f, 2.31f, 0.64f, -711, 0.012f, 83, 0.017f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_AUDITORIUM -1000, -476, 0.0f, 4.32f, 0.59f, -789, 0.020f, -289, 0.030f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_CONCERTHALL -1000, -500, 0.0f, 3.92f, 0.70f, -1230, 0.020f, -2, 0.029f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_CAVE -1000, 0, 0.0f, 2.91f, 1.30f, -602, 0.015f, -302, 0.022f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_ARENA -1000, -698, 0.0f, 7.24f, 0.33f, -1166, 0.020f, 16, 0.030f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_HANGAR -1000,-1000, 0.0f,10.05f, 0.23f, -602, 0.020f, 198, 0.030f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_CARPETEDHALLWAY -1000,-4000, 0.0f, 0.30f, 0.10f, -1831, 0.002f, -1630, 0.030f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_HALLWAY -1000, -300, 0.0f, 1.49f, 0.59f, -1219, 0.007f, 441, 0.011f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR -1000, -237, 0.0f, 2.70f, 0.79f, -1214, 0.013f, 395, 0.020f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_ALLEY -1000, -270, 0.0f, 1.49f, 0.86f, -1204, 0.007f, -4, 0.011f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_FOREST -1000,-3300, 0.0f, 1.49f, 0.54f, -2560, 0.162f, -613, 0.088f, 79.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_CITY -1000, -800, 0.0f, 1.49f, 0.67f, -2273, 0.007f, -2217, 0.011f, 50.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_MOUNTAINS -1000,-2500, 0.0f, 1.49f, 0.21f, -2780, 0.300f, -2014, 0.100f, 27.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_QUARRY -1000,-1000, 0.0f, 1.49f, 0.83f,-10000, 0.061f, 500, 0.025f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_PLAIN -1000,-2000, 0.0f, 1.49f, 0.50f, -2466, 0.179f, -2514, 0.100f, 21.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_PARKINGLOT -1000, 0, 0.0f, 1.65f, 1.50f, -1363, 0.008f, -1153, 0.012f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_SEWERPIPE -1000,-1000, 0.0f, 2.81f, 0.14f, 429, 0.014f, 648, 0.021f, 80.0f, 60.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_UNDERWATER -1000,-4000, 0.0f, 1.49f, 0.10f, -449, 0.007f, 1700, 0.011f, 100.0f, 100.0f, 5000.0f + +#define I3DL2_ENVIRONMENT_PRESET_SMALLROOM -1000, -600, 0.0f, 1.10f, 0.83f, -400, 0.005f, 500, 0.010f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_MEDIUMROOM -1000, -600, 0.0f, 1.30f, 0.83f, -1000, 0.010f, -200, 0.020f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_LARGEROOM -1000, -600, 0.0f, 1.50f, 0.83f, -1600, 0.020f, -1000, 0.040f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_MEDIUMHALL -1000, -600, 0.0f, 1.80f, 0.70f, -1300, 0.015f, -800, 0.030f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_LARGEHALL -1000, -600, 0.0f, 1.80f, 0.70f, -2000, 0.030f, -1400, 0.060f, 100.0f, 100.0f, 5000.0f +#define I3DL2_ENVIRONMENT_PRESET_PLATE -1000, -200, 0.0f, 1.30f, 0.90f, 0, 0.002f, 0, 0.010f, 100.0f, 75.0f, 5000.0f + +#define DS3DALG_DEFAULT GUID_NULL + +DEFINE_GUID(DS3DALG_NO_VIRTUALIZATION, 0xc241333f, 0x1c1b, 0x11d2, 0x94, 0xf5, 0x0, 0xc0, 0x4f, 0xc2, 0x8a, 0xca); + +DEFINE_GUID(DS3DALG_HRTF_FULL, 0xc2413340, 0x1c1b, 0x11d2, 0x94, 0xf5, 0x0, 0xc0, 0x4f, 0xc2, 0x8a, 0xca); + +DEFINE_GUID(DS3DALG_HRTF_LIGHT, 0xc2413342, 0x1c1b, 0x11d2, 0x94, 0xf5, 0x0, 0xc0, 0x4f, 0xc2, 0x8a, 0xca); + +#if DIRECTSOUND_VERSION >= 0x0800 + +DEFINE_GUID(GUID_DSFX_STANDARD_GARGLE, 0xdafd8210, 0x5711, 0x4b91, 0x9f, 0xe3, 0xf7, 0x5b, 0x7a, 0xe2, 0x79, 0xbf); + +DEFINE_GUID(GUID_DSFX_STANDARD_CHORUS, 0xefe6629c, 0x81f7, 0x4281, 0xbd, 0x91, 0xc9, 0xd6, 0x04, 0xa9, 0x5a, 0xf6); + +DEFINE_GUID(GUID_DSFX_STANDARD_FLANGER, 0xefca3d92, 0xdfd8, 0x4672, 0xa6, 0x03, 0x74, 0x20, 0x89, 0x4b, 0xad, 0x98); + +DEFINE_GUID(GUID_DSFX_STANDARD_ECHO, 0xef3e932c, 0xd40b, 0x4f51, 0x8c, 0xcf, 0x3f, 0x98, 0xf1, 0xb2, 0x9d, 0x5d); + +DEFINE_GUID(GUID_DSFX_STANDARD_DISTORTION, 0xef114c90, 0xcd1d, 0x484e, 0x96, 0xe5, 0x09, 0xcf, 0xaf, 0x91, 0x2a, 0x21); + +DEFINE_GUID(GUID_DSFX_STANDARD_COMPRESSOR, 0xef011f79, 0x4000, 0x406d, 0x87, 0xaf, 0xbf, 0xfb, 0x3f, 0xc3, 0x9d, 0x57); + +DEFINE_GUID(GUID_DSFX_STANDARD_PARAMEQ, 0x120ced89, 0x3bf4, 0x4173, 0xa1, 0x32, 0x3c, 0xb4, 0x06, 0xcf, 0x32, 0x31); + +DEFINE_GUID(GUID_DSFX_STANDARD_I3DL2REVERB, 0xef985e71, 0xd5c7, 0x42d4, 0xba, 0x4d, 0x2d, 0x07, 0x3e, 0x2e, 0x96, 0xf4); + +DEFINE_GUID(GUID_DSFX_WAVES_REVERB, 0x87fc0268, 0x9a55, 0x4360, 0x95, 0xaa, 0x00, 0x4a, 0x1d, 0x9d, 0xe2, 0x6c); + +DEFINE_GUID(GUID_DSCFX_CLASS_AEC, 0xBF963D80L, 0xC559, 0x11D0, 0x8A, 0x2B, 0x00, 0xA0, 0xC9, 0x25, 0x5A, 0xC1); + +DEFINE_GUID(GUID_DSCFX_MS_AEC, 0xcdebb919, 0x379a, 0x488a, 0x87, 0x65, 0xf5, 0x3c, 0xfd, 0x36, 0xde, 0x40); + +DEFINE_GUID(GUID_DSCFX_SYSTEM_AEC, 0x1c22c56d, 0x9879, 0x4f5b, 0xa3, 0x89, 0x27, 0x99, 0x6d, 0xdc, 0x28, 0x10); + +DEFINE_GUID(GUID_DSCFX_CLASS_NS, 0xe07f903f, 0x62fd, 0x4e60, 0x8c, 0xdd, 0xde, 0xa7, 0x23, 0x66, 0x65, 0xb5); + +DEFINE_GUID(GUID_DSCFX_MS_NS, 0x11c5c73b, 0x66e9, 0x4ba1, 0xa0, 0xba, 0xe8, 0x14, 0xc6, 0xee, 0xd9, 0x2d); + +DEFINE_GUID(GUID_DSCFX_SYSTEM_NS, 0x5ab0882e, 0x7274, 0x4516, 0x87, 0x7d, 0x4e, 0xee, 0x99, 0xba, 0x4f, 0xd0); + +#endif + +#endif + +#ifdef __cplusplus +}; +#endif +#ifndef __DINPUT_INCLUDED__ +#define __DINPUT_INCLUDED__ + +#ifndef DIJ_RINGZERO + +#ifdef _WIN32 +#define COM_NO_WINDOWS_H +#include +#endif + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define DIRECTINPUT_HEADER_VERSION 0x0800 +#ifndef DIRECTINPUT_VERSION +#define DIRECTINPUT_VERSION DIRECTINPUT_HEADER_VERSION +#endif + +#ifndef DIJ_RINGZERO + +DEFINE_GUID(CLSID_DirectInput, 0x25E609E0,0xB259,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(CLSID_DirectInputDevice, 0x25E609E1,0xB259,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); + +DEFINE_GUID(CLSID_DirectInput8, 0x25E609E4,0xB259,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(CLSID_DirectInputDevice8,0x25E609E5,0xB259,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); + +DEFINE_GUID(IID_IDirectInputA, 0x89521360,0xAA8A,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(IID_IDirectInputW, 0x89521361,0xAA8A,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(IID_IDirectInput2A, 0x5944E662,0xAA8A,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(IID_IDirectInput2W, 0x5944E663,0xAA8A,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(IID_IDirectInput7A, 0x9A4CB684,0x236D,0x11D3,0x8E,0x9D,0x00,0xC0,0x4F,0x68,0x44,0xAE); +DEFINE_GUID(IID_IDirectInput7W, 0x9A4CB685,0x236D,0x11D3,0x8E,0x9D,0x00,0xC0,0x4F,0x68,0x44,0xAE); +DEFINE_GUID(IID_IDirectInput8A, 0xBF798030,0x483A,0x4DA2,0xAA,0x99,0x5D,0x64,0xED,0x36,0x97,0x00); +DEFINE_GUID(IID_IDirectInput8W, 0xBF798031,0x483A,0x4DA2,0xAA,0x99,0x5D,0x64,0xED,0x36,0x97,0x00); +DEFINE_GUID(IID_IDirectInputDeviceA, 0x5944E680,0xC92E,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(IID_IDirectInputDeviceW, 0x5944E681,0xC92E,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(IID_IDirectInputDevice2A,0x5944E682,0xC92E,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(IID_IDirectInputDevice2W,0x5944E683,0xC92E,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(IID_IDirectInputDevice7A,0x57D7C6BC,0x2356,0x11D3,0x8E,0x9D,0x00,0xC0,0x4F,0x68,0x44,0xAE); +DEFINE_GUID(IID_IDirectInputDevice7W,0x57D7C6BD,0x2356,0x11D3,0x8E,0x9D,0x00,0xC0,0x4F,0x68,0x44,0xAE); +DEFINE_GUID(IID_IDirectInputDevice8A,0x54D41080,0xDC15,0x4833,0xA4,0x1B,0x74,0x8F,0x73,0xA3,0x81,0x79); +DEFINE_GUID(IID_IDirectInputDevice8W,0x54D41081,0xDC15,0x4833,0xA4,0x1B,0x74,0x8F,0x73,0xA3,0x81,0x79); +DEFINE_GUID(IID_IDirectInputEffect, 0xE7E1F7C0,0x88D2,0x11D0,0x9A,0xD0,0x00,0xA0,0xC9,0xA0,0x6E,0x35); + +DEFINE_GUID(GUID_XAxis, 0xA36D02E0,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(GUID_YAxis, 0xA36D02E1,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(GUID_ZAxis, 0xA36D02E2,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(GUID_RxAxis, 0xA36D02F4,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(GUID_RyAxis, 0xA36D02F5,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(GUID_RzAxis, 0xA36D02E3,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(GUID_Slider, 0xA36D02E4,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); + +DEFINE_GUID(GUID_Button, 0xA36D02F0,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(GUID_Key, 0x55728220,0xD33C,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); + +DEFINE_GUID(GUID_POV, 0xA36D02F2,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); + +DEFINE_GUID(GUID_Unknown, 0xA36D02F3,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); + +DEFINE_GUID(GUID_SysMouse, 0x6F1D2B60,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(GUID_SysKeyboard,0x6F1D2B61,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(GUID_Joystick ,0x6F1D2B70,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(GUID_SysMouseEm, 0x6F1D2B80,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(GUID_SysMouseEm2,0x6F1D2B81,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(GUID_SysKeyboardEm, 0x6F1D2B82,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(GUID_SysKeyboardEm2,0x6F1D2B83,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); + +DEFINE_GUID(GUID_ConstantForce, 0x13541C20,0x8E33,0x11D0,0x9A,0xD0,0x00,0xA0,0xC9,0xA0,0x6E,0x35); +DEFINE_GUID(GUID_RampForce, 0x13541C21,0x8E33,0x11D0,0x9A,0xD0,0x00,0xA0,0xC9,0xA0,0x6E,0x35); +DEFINE_GUID(GUID_Square, 0x13541C22,0x8E33,0x11D0,0x9A,0xD0,0x00,0xA0,0xC9,0xA0,0x6E,0x35); +DEFINE_GUID(GUID_Sine, 0x13541C23,0x8E33,0x11D0,0x9A,0xD0,0x00,0xA0,0xC9,0xA0,0x6E,0x35); +DEFINE_GUID(GUID_Triangle, 0x13541C24,0x8E33,0x11D0,0x9A,0xD0,0x00,0xA0,0xC9,0xA0,0x6E,0x35); +DEFINE_GUID(GUID_SawtoothUp, 0x13541C25,0x8E33,0x11D0,0x9A,0xD0,0x00,0xA0,0xC9,0xA0,0x6E,0x35); +DEFINE_GUID(GUID_SawtoothDown, 0x13541C26,0x8E33,0x11D0,0x9A,0xD0,0x00,0xA0,0xC9,0xA0,0x6E,0x35); +DEFINE_GUID(GUID_Spring, 0x13541C27,0x8E33,0x11D0,0x9A,0xD0,0x00,0xA0,0xC9,0xA0,0x6E,0x35); +DEFINE_GUID(GUID_Damper, 0x13541C28,0x8E33,0x11D0,0x9A,0xD0,0x00,0xA0,0xC9,0xA0,0x6E,0x35); +DEFINE_GUID(GUID_Inertia, 0x13541C29,0x8E33,0x11D0,0x9A,0xD0,0x00,0xA0,0xC9,0xA0,0x6E,0x35); +DEFINE_GUID(GUID_Friction, 0x13541C2A,0x8E33,0x11D0,0x9A,0xD0,0x00,0xA0,0xC9,0xA0,0x6E,0x35); +DEFINE_GUID(GUID_CustomForce, 0x13541C2B,0x8E33,0x11D0,0x9A,0xD0,0x00,0xA0,0xC9,0xA0,0x6E,0x35); + +#endif + +#if(DIRECTINPUT_VERSION >= 0x0500) + +#define DIEFT_ALL 0x00000000 + +#define DIEFT_CONSTANTFORCE 0x00000001 +#define DIEFT_RAMPFORCE 0x00000002 +#define DIEFT_PERIODIC 0x00000003 +#define DIEFT_CONDITION 0x00000004 +#define DIEFT_CUSTOMFORCE 0x00000005 +#define DIEFT_HARDWARE 0x000000FF +#define DIEFT_FFATTACK 0x00000200 +#define DIEFT_FFFADE 0x00000400 +#define DIEFT_SATURATION 0x00000800 +#define DIEFT_POSNEGCOEFFICIENTS 0x00001000 +#define DIEFT_POSNEGSATURATION 0x00002000 +#define DIEFT_DEADBAND 0x00004000 +#define DIEFT_STARTDELAY 0x00008000 +#define DIEFT_GETTYPE(n) LOBYTE(n) + +#define DI_DEGREES 100 +#define DI_FFNOMINALMAX 10000 +#define DI_SECONDS 1000000 + +typedef struct DICONSTANTFORCE { + LONG lMagnitude; +} DICONSTANTFORCE, *LPDICONSTANTFORCE; +typedef const DICONSTANTFORCE *LPCDICONSTANTFORCE; + +typedef struct DIRAMPFORCE { + LONG lStart; + LONG lEnd; +} DIRAMPFORCE, *LPDIRAMPFORCE; +typedef const DIRAMPFORCE *LPCDIRAMPFORCE; + +typedef struct DIPERIODIC { + DWORD dwMagnitude; + LONG lOffset; + DWORD dwPhase; + DWORD dwPeriod; +} DIPERIODIC, *LPDIPERIODIC; +typedef const DIPERIODIC *LPCDIPERIODIC; + +typedef struct DICONDITION { + LONG lOffset; + LONG lPositiveCoefficient; + LONG lNegativeCoefficient; + DWORD dwPositiveSaturation; + DWORD dwNegativeSaturation; + LONG lDeadBand; +} DICONDITION, *LPDICONDITION; +typedef const DICONDITION *LPCDICONDITION; + +typedef struct DICUSTOMFORCE { + DWORD cChannels; + DWORD dwSamplePeriod; + DWORD cSamples; + LPLONG rglForceData; +} DICUSTOMFORCE, *LPDICUSTOMFORCE; +typedef const DICUSTOMFORCE *LPCDICUSTOMFORCE; + +typedef struct DIENVELOPE { + DWORD dwSize; + DWORD dwAttackLevel; + DWORD dwAttackTime; + DWORD dwFadeLevel; + DWORD dwFadeTime; +} DIENVELOPE, *LPDIENVELOPE; +typedef const DIENVELOPE *LPCDIENVELOPE; + +typedef struct DIEFFECT_DX5 { + DWORD dwSize; + DWORD dwFlags; + DWORD dwDuration; + DWORD dwSamplePeriod; + DWORD dwGain; + DWORD dwTriggerButton; + DWORD dwTriggerRepeatInterval; + DWORD cAxes; + LPDWORD rgdwAxes; + LPLONG rglDirection; + LPDIENVELOPE lpEnvelope; + DWORD cbTypeSpecificParams; + LPVOID lpvTypeSpecificParams; +} DIEFFECT_DX5, *LPDIEFFECT_DX5; +typedef const DIEFFECT_DX5 *LPCDIEFFECT_DX5; + +typedef struct DIEFFECT { + DWORD dwSize; + DWORD dwFlags; + DWORD dwDuration; + DWORD dwSamplePeriod; + DWORD dwGain; + DWORD dwTriggerButton; + DWORD dwTriggerRepeatInterval; + DWORD cAxes; + LPDWORD rgdwAxes; + LPLONG rglDirection; + LPDIENVELOPE lpEnvelope; + DWORD cbTypeSpecificParams; + LPVOID lpvTypeSpecificParams; +#if(DIRECTINPUT_VERSION >= 0x0600) + DWORD dwStartDelay; +#endif +} DIEFFECT, *LPDIEFFECT; +typedef DIEFFECT DIEFFECT_DX6; +typedef LPDIEFFECT LPDIEFFECT_DX6; +typedef const DIEFFECT *LPCDIEFFECT; + +#if(DIRECTINPUT_VERSION >= 0x0700) +#ifndef DIJ_RINGZERO +typedef struct DIFILEEFFECT{ + DWORD dwSize; + GUID GuidEffect; + LPCDIEFFECT lpDiEffect; + CHAR szFriendlyName[MAX_PATH]; +}DIFILEEFFECT, *LPDIFILEEFFECT; +typedef const DIFILEEFFECT *LPCDIFILEEFFECT; +typedef BOOL (FAR PASCAL * LPDIENUMEFFECTSINFILECALLBACK)(LPCDIFILEEFFECT , LPVOID); +#endif +#endif + +#define DIEFF_OBJECTIDS 0x00000001 +#define DIEFF_OBJECTOFFSETS 0x00000002 +#define DIEFF_CARTESIAN 0x00000010 +#define DIEFF_POLAR 0x00000020 +#define DIEFF_SPHERICAL 0x00000040 + +#define DIEP_DURATION 0x00000001 +#define DIEP_SAMPLEPERIOD 0x00000002 +#define DIEP_GAIN 0x00000004 +#define DIEP_TRIGGERBUTTON 0x00000008 +#define DIEP_TRIGGERREPEATINTERVAL 0x00000010 +#define DIEP_AXES 0x00000020 +#define DIEP_DIRECTION 0x00000040 +#define DIEP_ENVELOPE 0x00000080 +#define DIEP_TYPESPECIFICPARAMS 0x00000100 +#if(DIRECTINPUT_VERSION >= 0x0600) +#define DIEP_STARTDELAY 0x00000200 +#define DIEP_ALLPARAMS_DX5 0x000001FF +#define DIEP_ALLPARAMS 0x000003FF +#else +#define DIEP_ALLPARAMS 0x000001FF +#endif +#define DIEP_START 0x20000000 +#define DIEP_NORESTART 0x40000000 +#define DIEP_NODOWNLOAD 0x80000000 +#define DIEB_NOTRIGGER 0xFFFFFFFF + +#define DIES_SOLO 0x00000001 +#define DIES_NODOWNLOAD 0x80000000 + +#define DIEGES_PLAYING 0x00000001 +#define DIEGES_EMULATED 0x00000002 + +typedef struct DIEFFESCAPE { + DWORD dwSize; + DWORD dwCommand; + LPVOID lpvInBuffer; + DWORD cbInBuffer; + LPVOID lpvOutBuffer; + DWORD cbOutBuffer; +} DIEFFESCAPE, *LPDIEFFESCAPE; + +#ifndef DIJ_RINGZERO + +#undef INTERFACE +#define INTERFACE IDirectInputEffect + +DECLARE_INTERFACE_(IDirectInputEffect, IUnknown) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD,REFGUID) PURE; + STDMETHOD(GetEffectGuid)(THIS_ LPGUID) PURE; + STDMETHOD(GetParameters)(THIS_ LPDIEFFECT,DWORD) PURE; + STDMETHOD(SetParameters)(THIS_ LPCDIEFFECT,DWORD) PURE; + STDMETHOD(Start)(THIS_ DWORD,DWORD) PURE; + STDMETHOD(Stop)(THIS) PURE; + STDMETHOD(GetEffectStatus)(THIS_ LPDWORD) PURE; + STDMETHOD(Download)(THIS) PURE; + STDMETHOD(Unload)(THIS) PURE; + STDMETHOD(Escape)(THIS_ LPDIEFFESCAPE) PURE; +}; + +typedef struct IDirectInputEffect *LPDIRECTINPUTEFFECT; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectInputEffect_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirectInputEffect_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirectInputEffect_Release(p) (p)->lpVtbl->Release(p) +#define IDirectInputEffect_Initialize(p,a,b,c) (p)->lpVtbl->Initialize(p,a,b,c) +#define IDirectInputEffect_GetEffectGuid(p,a) (p)->lpVtbl->GetEffectGuid(p,a) +#define IDirectInputEffect_GetParameters(p,a,b) (p)->lpVtbl->GetParameters(p,a,b) +#define IDirectInputEffect_SetParameters(p,a,b) (p)->lpVtbl->SetParameters(p,a,b) +#define IDirectInputEffect_Start(p,a,b) (p)->lpVtbl->Start(p,a,b) +#define IDirectInputEffect_Stop(p) (p)->lpVtbl->Stop(p) +#define IDirectInputEffect_GetEffectStatus(p,a) (p)->lpVtbl->GetEffectStatus(p,a) +#define IDirectInputEffect_Download(p) (p)->lpVtbl->Download(p) +#define IDirectInputEffect_Unload(p) (p)->lpVtbl->Unload(p) +#define IDirectInputEffect_Escape(p,a) (p)->lpVtbl->Escape(p,a) +#else +#define IDirectInputEffect_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirectInputEffect_AddRef(p) (p)->AddRef() +#define IDirectInputEffect_Release(p) (p)->Release() +#define IDirectInputEffect_Initialize(p,a,b,c) (p)->Initialize(a,b,c) +#define IDirectInputEffect_GetEffectGuid(p,a) (p)->GetEffectGuid(a) +#define IDirectInputEffect_GetParameters(p,a,b) (p)->GetParameters(a,b) +#define IDirectInputEffect_SetParameters(p,a,b) (p)->SetParameters(a,b) +#define IDirectInputEffect_Start(p,a,b) (p)->Start(a,b) +#define IDirectInputEffect_Stop(p) (p)->Stop() +#define IDirectInputEffect_GetEffectStatus(p,a) (p)->GetEffectStatus(a) +#define IDirectInputEffect_Download(p) (p)->Download() +#define IDirectInputEffect_Unload(p) (p)->Unload() +#define IDirectInputEffect_Escape(p,a) (p)->Escape(a) +#endif + +#endif + +#endif + +#if DIRECTINPUT_VERSION <= 0x700 +#define DIDEVTYPE_DEVICE 1 +#define DIDEVTYPE_MOUSE 2 +#define DIDEVTYPE_KEYBOARD 3 +#define DIDEVTYPE_JOYSTICK 4 + +#else +#define DI8DEVCLASS_ALL 0 +#define DI8DEVCLASS_DEVICE 1 +#define DI8DEVCLASS_POINTER 2 +#define DI8DEVCLASS_KEYBOARD 3 +#define DI8DEVCLASS_GAMECTRL 4 + +#define DI8DEVTYPE_DEVICE 0x11 +#define DI8DEVTYPE_MOUSE 0x12 +#define DI8DEVTYPE_KEYBOARD 0x13 +#define DI8DEVTYPE_JOYSTICK 0x14 +#define DI8DEVTYPE_GAMEPAD 0x15 +#define DI8DEVTYPE_DRIVING 0x16 +#define DI8DEVTYPE_FLIGHT 0x17 +#define DI8DEVTYPE_1STPERSON 0x18 +#define DI8DEVTYPE_DEVICECTRL 0x19 +#define DI8DEVTYPE_SCREENPOINTER 0x1A +#define DI8DEVTYPE_REMOTE 0x1B +#define DI8DEVTYPE_SUPPLEMENTAL 0x1C +#endif + +#define DIDEVTYPE_HID 0x00010000 + +#if DIRECTINPUT_VERSION <= 0x700 +#define DIDEVTYPEMOUSE_UNKNOWN 1 +#define DIDEVTYPEMOUSE_TRADITIONAL 2 +#define DIDEVTYPEMOUSE_FINGERSTICK 3 +#define DIDEVTYPEMOUSE_TOUCHPAD 4 +#define DIDEVTYPEMOUSE_TRACKBALL 5 + +#define DIDEVTYPEKEYBOARD_UNKNOWN 0 +#define DIDEVTYPEKEYBOARD_PCXT 1 +#define DIDEVTYPEKEYBOARD_OLIVETTI 2 +#define DIDEVTYPEKEYBOARD_PCAT 3 +#define DIDEVTYPEKEYBOARD_PCENH 4 +#define DIDEVTYPEKEYBOARD_NOKIA1050 5 +#define DIDEVTYPEKEYBOARD_NOKIA9140 6 +#define DIDEVTYPEKEYBOARD_NEC98 7 +#define DIDEVTYPEKEYBOARD_NEC98LAPTOP 8 +#define DIDEVTYPEKEYBOARD_NEC98106 9 +#define DIDEVTYPEKEYBOARD_JAPAN106 10 +#define DIDEVTYPEKEYBOARD_JAPANAX 11 +#define DIDEVTYPEKEYBOARD_J3100 12 + +#define DIDEVTYPEJOYSTICK_UNKNOWN 1 +#define DIDEVTYPEJOYSTICK_TRADITIONAL 2 +#define DIDEVTYPEJOYSTICK_FLIGHTSTICK 3 +#define DIDEVTYPEJOYSTICK_GAMEPAD 4 +#define DIDEVTYPEJOYSTICK_RUDDER 5 +#define DIDEVTYPEJOYSTICK_WHEEL 6 +#define DIDEVTYPEJOYSTICK_HEADTRACKER 7 + +#else +#define DI8DEVTYPEMOUSE_UNKNOWN 1 +#define DI8DEVTYPEMOUSE_TRADITIONAL 2 +#define DI8DEVTYPEMOUSE_FINGERSTICK 3 +#define DI8DEVTYPEMOUSE_TOUCHPAD 4 +#define DI8DEVTYPEMOUSE_TRACKBALL 5 +#define DI8DEVTYPEMOUSE_ABSOLUTE 6 + +#define DI8DEVTYPEKEYBOARD_UNKNOWN 0 +#define DI8DEVTYPEKEYBOARD_PCXT 1 +#define DI8DEVTYPEKEYBOARD_OLIVETTI 2 +#define DI8DEVTYPEKEYBOARD_PCAT 3 +#define DI8DEVTYPEKEYBOARD_PCENH 4 +#define DI8DEVTYPEKEYBOARD_NOKIA1050 5 +#define DI8DEVTYPEKEYBOARD_NOKIA9140 6 +#define DI8DEVTYPEKEYBOARD_NEC98 7 +#define DI8DEVTYPEKEYBOARD_NEC98LAPTOP 8 +#define DI8DEVTYPEKEYBOARD_NEC98106 9 +#define DI8DEVTYPEKEYBOARD_JAPAN106 10 +#define DI8DEVTYPEKEYBOARD_JAPANAX 11 +#define DI8DEVTYPEKEYBOARD_J3100 12 + +#define DI8DEVTYPE_LIMITEDGAMESUBTYPE 1 + +#define DI8DEVTYPEJOYSTICK_LIMITED DI8DEVTYPE_LIMITEDGAMESUBTYPE +#define DI8DEVTYPEJOYSTICK_STANDARD 2 + +#define DI8DEVTYPEGAMEPAD_LIMITED DI8DEVTYPE_LIMITEDGAMESUBTYPE +#define DI8DEVTYPEGAMEPAD_STANDARD 2 +#define DI8DEVTYPEGAMEPAD_TILT 3 + +#define DI8DEVTYPEDRIVING_LIMITED DI8DEVTYPE_LIMITEDGAMESUBTYPE +#define DI8DEVTYPEDRIVING_COMBINEDPEDALS 2 +#define DI8DEVTYPEDRIVING_DUALPEDALS 3 +#define DI8DEVTYPEDRIVING_THREEPEDALS 4 +#define DI8DEVTYPEDRIVING_HANDHELD 5 + +#define DI8DEVTYPEFLIGHT_LIMITED DI8DEVTYPE_LIMITEDGAMESUBTYPE +#define DI8DEVTYPEFLIGHT_STICK 2 +#define DI8DEVTYPEFLIGHT_YOKE 3 +#define DI8DEVTYPEFLIGHT_RC 4 + +#define DI8DEVTYPE1STPERSON_LIMITED DI8DEVTYPE_LIMITEDGAMESUBTYPE +#define DI8DEVTYPE1STPERSON_UNKNOWN 2 +#define DI8DEVTYPE1STPERSON_SIXDOF 3 +#define DI8DEVTYPE1STPERSON_SHOOTER 4 + +#define DI8DEVTYPESCREENPTR_UNKNOWN 2 +#define DI8DEVTYPESCREENPTR_LIGHTGUN 3 +#define DI8DEVTYPESCREENPTR_LIGHTPEN 4 +#define DI8DEVTYPESCREENPTR_TOUCH 5 + +#define DI8DEVTYPEREMOTE_UNKNOWN 2 + +#define DI8DEVTYPEDEVICECTRL_UNKNOWN 2 +#define DI8DEVTYPEDEVICECTRL_COMMSSELECTION 3 +#define DI8DEVTYPEDEVICECTRL_COMMSSELECTION_HARDWIRED 4 + +#define DI8DEVTYPESUPPLEMENTAL_UNKNOWN 2 +#define DI8DEVTYPESUPPLEMENTAL_2NDHANDCONTROLLER 3 +#define DI8DEVTYPESUPPLEMENTAL_HEADTRACKER 4 +#define DI8DEVTYPESUPPLEMENTAL_HANDTRACKER 5 +#define DI8DEVTYPESUPPLEMENTAL_SHIFTSTICKGATE 6 +#define DI8DEVTYPESUPPLEMENTAL_SHIFTER 7 +#define DI8DEVTYPESUPPLEMENTAL_THROTTLE 8 +#define DI8DEVTYPESUPPLEMENTAL_SPLITTHROTTLE 9 +#define DI8DEVTYPESUPPLEMENTAL_COMBINEDPEDALS 10 +#define DI8DEVTYPESUPPLEMENTAL_DUALPEDALS 11 +#define DI8DEVTYPESUPPLEMENTAL_THREEPEDALS 12 +#define DI8DEVTYPESUPPLEMENTAL_RUDDERPEDALS 13 +#endif + +#define GET_DIDEVICE_TYPE(dwDevType) LOBYTE(dwDevType) +#define GET_DIDEVICE_SUBTYPE(dwDevType) HIBYTE(dwDevType) + +#if(DIRECTINPUT_VERSION >= 0x0500) + +typedef struct DIDEVCAPS_DX3 { + DWORD dwSize; + DWORD dwFlags; + DWORD dwDevType; + DWORD dwAxes; + DWORD dwButtons; + DWORD dwPOVs; +} DIDEVCAPS_DX3, *LPDIDEVCAPS_DX3; +#endif + +typedef struct DIDEVCAPS { + DWORD dwSize; + DWORD dwFlags; + DWORD dwDevType; + DWORD dwAxes; + DWORD dwButtons; + DWORD dwPOVs; +#if(DIRECTINPUT_VERSION >= 0x0500) + DWORD dwFFSamplePeriod; + DWORD dwFFMinTimeResolution; + DWORD dwFirmwareRevision; + DWORD dwHardwareRevision; + DWORD dwFFDriverVersion; +#endif +} DIDEVCAPS, *LPDIDEVCAPS; + +#define DIDC_ATTACHED 0x00000001 +#define DIDC_POLLEDDEVICE 0x00000002 +#define DIDC_EMULATED 0x00000004 +#define DIDC_POLLEDDATAFORMAT 0x00000008 +#if(DIRECTINPUT_VERSION >= 0x0500) +#define DIDC_FORCEFEEDBACK 0x00000100 +#define DIDC_FFATTACK 0x00000200 +#define DIDC_FFFADE 0x00000400 +#define DIDC_SATURATION 0x00000800 +#define DIDC_POSNEGCOEFFICIENTS 0x00001000 +#define DIDC_POSNEGSATURATION 0x00002000 +#define DIDC_DEADBAND 0x00004000 +#endif +#define DIDC_STARTDELAY 0x00008000 +#if(DIRECTINPUT_VERSION >= 0x050a) +#define DIDC_ALIAS 0x00010000 +#define DIDC_PHANTOM 0x00020000 +#endif +#if(DIRECTINPUT_VERSION >= 0x0800) +#define DIDC_HIDDEN 0x00040000 +#endif + +#define DIDFT_ALL 0x00000000 + +#define DIDFT_RELAXIS 0x00000001 +#define DIDFT_ABSAXIS 0x00000002 +#define DIDFT_AXIS 0x00000003 + +#define DIDFT_PSHBUTTON 0x00000004 +#define DIDFT_TGLBUTTON 0x00000008 +#define DIDFT_BUTTON 0x0000000C + +#define DIDFT_POV 0x00000010 +#define DIDFT_COLLECTION 0x00000040 +#define DIDFT_NODATA 0x00000080 + +#define DIDFT_ANYINSTANCE 0x00FFFF00 +#define DIDFT_INSTANCEMASK DIDFT_ANYINSTANCE +#define DIDFT_MAKEINSTANCE(n) ((WORD)(n) << 8) +#define DIDFT_GETTYPE(n) LOBYTE(n) +#define DIDFT_GETINSTANCE(n) LOWORD((n) >> 8) +#define DIDFT_FFACTUATOR 0x01000000 +#define DIDFT_FFEFFECTTRIGGER 0x02000000 +#if(DIRECTINPUT_VERSION >= 0x050a) +#define DIDFT_OUTPUT 0x10000000 +#define DIDFT_VENDORDEFINED 0x04000000 +#define DIDFT_ALIAS 0x08000000 +#endif + +#define DIDFT_ENUMCOLLECTION(n) ((WORD)(n) << 8) +#define DIDFT_NOCOLLECTION 0x00FFFF00 + +#ifndef DIJ_RINGZERO + +typedef struct _DIOBJECTDATAFORMAT { + const GUID *pguid; + DWORD dwOfs; + DWORD dwType; + DWORD dwFlags; +} DIOBJECTDATAFORMAT, *LPDIOBJECTDATAFORMAT; +typedef const DIOBJECTDATAFORMAT *LPCDIOBJECTDATAFORMAT; + +typedef struct _DIDATAFORMAT { + DWORD dwSize; + DWORD dwObjSize; + DWORD dwFlags; + DWORD dwDataSize; + DWORD dwNumObjs; + LPDIOBJECTDATAFORMAT rgodf; +} DIDATAFORMAT, *LPDIDATAFORMAT; +typedef const DIDATAFORMAT *LPCDIDATAFORMAT; + +#define DIDF_ABSAXIS 0x00000001 +#define DIDF_RELAXIS 0x00000002 + +#ifdef __cplusplus +extern "C" { +#endif +extern const DIDATAFORMAT c_dfDIMouse; + +#if(DIRECTINPUT_VERSION >= 0x0700) +extern const DIDATAFORMAT c_dfDIMouse2; +#endif + +extern const DIDATAFORMAT c_dfDIKeyboard; + +#if(DIRECTINPUT_VERSION >= 0x0500) +extern const DIDATAFORMAT c_dfDIJoystick; +extern const DIDATAFORMAT c_dfDIJoystick2; +#endif + +#ifdef __cplusplus +}; +#endif + +#if DIRECTINPUT_VERSION > 0x0700 + +typedef struct _DIACTIONA { + UINT_PTR uAppData; + DWORD dwSemantic; + OPTIONAL DWORD dwFlags; + OPTIONAL union { + LPCSTR lptszActionName; + UINT uResIdString; + }; + OPTIONAL GUID guidInstance; + OPTIONAL DWORD dwObjID; + OPTIONAL DWORD dwHow; +} DIACTIONA, *LPDIACTIONA ; +typedef struct _DIACTIONW { + UINT_PTR uAppData; + DWORD dwSemantic; + OPTIONAL DWORD dwFlags; + OPTIONAL union { + LPCWSTR lptszActionName; + UINT uResIdString; + }; + OPTIONAL GUID guidInstance; + OPTIONAL DWORD dwObjID; + OPTIONAL DWORD dwHow; +} DIACTIONW, *LPDIACTIONW ; +#ifdef UNICODE +typedef DIACTIONW DIACTION; +typedef LPDIACTIONW LPDIACTION; +#else +typedef DIACTIONA DIACTION; +typedef LPDIACTIONA LPDIACTION; +#endif + +typedef const DIACTIONA *LPCDIACTIONA; +typedef const DIACTIONW *LPCDIACTIONW; +#ifdef UNICODE +typedef DIACTIONW DIACTION; +typedef LPCDIACTIONW LPCDIACTION; +#else +typedef DIACTIONA DIACTION; +typedef LPCDIACTIONA LPCDIACTION; +#endif +typedef const DIACTION *LPCDIACTION; + +#define DIA_FORCEFEEDBACK 0x00000001 +#define DIA_APPMAPPED 0x00000002 +#define DIA_APPNOMAP 0x00000004 +#define DIA_NORANGE 0x00000008 +#define DIA_APPFIXED 0x00000010 + +#define DIAH_UNMAPPED 0x00000000 +#define DIAH_USERCONFIG 0x00000001 +#define DIAH_APPREQUESTED 0x00000002 +#define DIAH_HWAPP 0x00000004 +#define DIAH_HWDEFAULT 0x00000008 +#define DIAH_DEFAULT 0x00000020 +#define DIAH_ERROR 0x80000000 + +typedef struct _DIACTIONFORMATA { + DWORD dwSize; + DWORD dwActionSize; + DWORD dwDataSize; + DWORD dwNumActions; + LPDIACTIONA rgoAction; + GUID guidActionMap; + DWORD dwGenre; + DWORD dwBufferSize; + OPTIONAL LONG lAxisMin; + OPTIONAL LONG lAxisMax; + OPTIONAL HINSTANCE hInstString; + FILETIME ftTimeStamp; + DWORD dwCRC; + CHAR tszActionMap[MAX_PATH]; +} DIACTIONFORMATA, *LPDIACTIONFORMATA; +typedef struct _DIACTIONFORMATW { + DWORD dwSize; + DWORD dwActionSize; + DWORD dwDataSize; + DWORD dwNumActions; + LPDIACTIONW rgoAction; + GUID guidActionMap; + DWORD dwGenre; + DWORD dwBufferSize; + OPTIONAL LONG lAxisMin; + OPTIONAL LONG lAxisMax; + OPTIONAL HINSTANCE hInstString; + FILETIME ftTimeStamp; + DWORD dwCRC; + WCHAR tszActionMap[MAX_PATH]; +} DIACTIONFORMATW, *LPDIACTIONFORMATW; +#ifdef UNICODE +typedef DIACTIONFORMATW DIACTIONFORMAT; +typedef LPDIACTIONFORMATW LPDIACTIONFORMAT; +#else +typedef DIACTIONFORMATA DIACTIONFORMAT; +typedef LPDIACTIONFORMATA LPDIACTIONFORMAT; +#endif +typedef const DIACTIONFORMATA *LPCDIACTIONFORMATA; +typedef const DIACTIONFORMATW *LPCDIACTIONFORMATW; +#ifdef UNICODE +typedef DIACTIONFORMATW DIACTIONFORMAT; +typedef LPCDIACTIONFORMATW LPCDIACTIONFORMAT; +#else +typedef DIACTIONFORMATA DIACTIONFORMAT; +typedef LPCDIACTIONFORMATA LPCDIACTIONFORMAT; +#endif +typedef const DIACTIONFORMAT *LPCDIACTIONFORMAT; + +#define DIAFTS_NEWDEVICELOW 0xFFFFFFFF +#define DIAFTS_NEWDEVICEHIGH 0xFFFFFFFF +#define DIAFTS_UNUSEDDEVICELOW 0x00000000 +#define DIAFTS_UNUSEDDEVICEHIGH 0x00000000 + +#define DIDBAM_DEFAULT 0x00000000 +#define DIDBAM_PRESERVE 0x00000001 +#define DIDBAM_INITIALIZE 0x00000002 +#define DIDBAM_HWDEFAULTS 0x00000004 + +#define DIDSAM_DEFAULT 0x00000000 +#define DIDSAM_NOUSER 0x00000001 +#define DIDSAM_FORCESAVE 0x00000002 + +#define DICD_DEFAULT 0x00000000 +#define DICD_EDIT 0x00000001 + +#ifndef D3DCOLOR_DEFINED +typedef DWORD D3DCOLOR; +#define D3DCOLOR_DEFINED +#endif + +typedef struct _DICOLORSET{ + DWORD dwSize; + D3DCOLOR cTextFore; + D3DCOLOR cTextHighlight; + D3DCOLOR cCalloutLine; + D3DCOLOR cCalloutHighlight; + D3DCOLOR cBorder; + D3DCOLOR cControlFill; + D3DCOLOR cHighlightFill; + D3DCOLOR cAreaFill; +} DICOLORSET, *LPDICOLORSET; +typedef const DICOLORSET *LPCDICOLORSET; + +typedef struct _DICONFIGUREDEVICESPARAMSA{ + DWORD dwSize; + DWORD dwcUsers; + LPSTR lptszUserNames; + DWORD dwcFormats; + LPDIACTIONFORMATA lprgFormats; + HWND hwnd; + DICOLORSET dics; + IUnknown FAR * lpUnkDDSTarget; +} DICONFIGUREDEVICESPARAMSA, *LPDICONFIGUREDEVICESPARAMSA; +typedef struct _DICONFIGUREDEVICESPARAMSW{ + DWORD dwSize; + DWORD dwcUsers; + LPWSTR lptszUserNames; + DWORD dwcFormats; + LPDIACTIONFORMATW lprgFormats; + HWND hwnd; + DICOLORSET dics; + IUnknown FAR * lpUnkDDSTarget; +} DICONFIGUREDEVICESPARAMSW, *LPDICONFIGUREDEVICESPARAMSW; +#ifdef UNICODE +typedef DICONFIGUREDEVICESPARAMSW DICONFIGUREDEVICESPARAMS; +typedef LPDICONFIGUREDEVICESPARAMSW LPDICONFIGUREDEVICESPARAMS; +#else +typedef DICONFIGUREDEVICESPARAMSA DICONFIGUREDEVICESPARAMS; +typedef LPDICONFIGUREDEVICESPARAMSA LPDICONFIGUREDEVICESPARAMS; +#endif +typedef const DICONFIGUREDEVICESPARAMSA *LPCDICONFIGUREDEVICESPARAMSA; +typedef const DICONFIGUREDEVICESPARAMSW *LPCDICONFIGUREDEVICESPARAMSW; +#ifdef UNICODE +typedef DICONFIGUREDEVICESPARAMSW DICONFIGUREDEVICESPARAMS; +typedef LPCDICONFIGUREDEVICESPARAMSW LPCDICONFIGUREDEVICESPARAMS; +#else +typedef DICONFIGUREDEVICESPARAMSA DICONFIGUREDEVICESPARAMS; +typedef LPCDICONFIGUREDEVICESPARAMSA LPCDICONFIGUREDEVICESPARAMS; +#endif +typedef const DICONFIGUREDEVICESPARAMS *LPCDICONFIGUREDEVICESPARAMS; + +#define DIDIFT_CONFIGURATION 0x00000001 +#define DIDIFT_OVERLAY 0x00000002 + +#define DIDAL_CENTERED 0x00000000 +#define DIDAL_LEFTALIGNED 0x00000001 +#define DIDAL_RIGHTALIGNED 0x00000002 +#define DIDAL_MIDDLE 0x00000000 +#define DIDAL_TOPALIGNED 0x00000004 +#define DIDAL_BOTTOMALIGNED 0x00000008 + +typedef struct _DIDEVICEIMAGEINFOA { + CHAR tszImagePath[MAX_PATH]; + DWORD dwFlags; + + DWORD dwViewID; + RECT rcOverlay; + DWORD dwObjID; + DWORD dwcValidPts; + POINT rgptCalloutLine[5]; + RECT rcCalloutRect; + DWORD dwTextAlign; +} DIDEVICEIMAGEINFOA, *LPDIDEVICEIMAGEINFOA; +typedef struct _DIDEVICEIMAGEINFOW { + WCHAR tszImagePath[MAX_PATH]; + DWORD dwFlags; + + DWORD dwViewID; + RECT rcOverlay; + DWORD dwObjID; + DWORD dwcValidPts; + POINT rgptCalloutLine[5]; + RECT rcCalloutRect; + DWORD dwTextAlign; +} DIDEVICEIMAGEINFOW, *LPDIDEVICEIMAGEINFOW; +#ifdef UNICODE +typedef DIDEVICEIMAGEINFOW DIDEVICEIMAGEINFO; +typedef LPDIDEVICEIMAGEINFOW LPDIDEVICEIMAGEINFO; +#else +typedef DIDEVICEIMAGEINFOA DIDEVICEIMAGEINFO; +typedef LPDIDEVICEIMAGEINFOA LPDIDEVICEIMAGEINFO; +#endif +typedef const DIDEVICEIMAGEINFOA *LPCDIDEVICEIMAGEINFOA; +typedef const DIDEVICEIMAGEINFOW *LPCDIDEVICEIMAGEINFOW; +#ifdef UNICODE +typedef DIDEVICEIMAGEINFOW DIDEVICEIMAGEINFO; +typedef LPCDIDEVICEIMAGEINFOW LPCDIDEVICEIMAGEINFO; +#else +typedef DIDEVICEIMAGEINFOA DIDEVICEIMAGEINFO; +typedef LPCDIDEVICEIMAGEINFOA LPCDIDEVICEIMAGEINFO; +#endif +typedef const DIDEVICEIMAGEINFO *LPCDIDEVICEIMAGEINFO; + +typedef struct _DIDEVICEIMAGEINFOHEADERA { + DWORD dwSize; + DWORD dwSizeImageInfo; + DWORD dwcViews; + DWORD dwcButtons; + DWORD dwcAxes; + DWORD dwcPOVs; + DWORD dwBufferSize; + DWORD dwBufferUsed; + LPDIDEVICEIMAGEINFOA lprgImageInfoArray; +} DIDEVICEIMAGEINFOHEADERA, *LPDIDEVICEIMAGEINFOHEADERA; +typedef struct _DIDEVICEIMAGEINFOHEADERW { + DWORD dwSize; + DWORD dwSizeImageInfo; + DWORD dwcViews; + DWORD dwcButtons; + DWORD dwcAxes; + DWORD dwcPOVs; + DWORD dwBufferSize; + DWORD dwBufferUsed; + LPDIDEVICEIMAGEINFOW lprgImageInfoArray; +} DIDEVICEIMAGEINFOHEADERW, *LPDIDEVICEIMAGEINFOHEADERW; +#ifdef UNICODE +typedef DIDEVICEIMAGEINFOHEADERW DIDEVICEIMAGEINFOHEADER; +typedef LPDIDEVICEIMAGEINFOHEADERW LPDIDEVICEIMAGEINFOHEADER; +#else +typedef DIDEVICEIMAGEINFOHEADERA DIDEVICEIMAGEINFOHEADER; +typedef LPDIDEVICEIMAGEINFOHEADERA LPDIDEVICEIMAGEINFOHEADER; +#endif +typedef const DIDEVICEIMAGEINFOHEADERA *LPCDIDEVICEIMAGEINFOHEADERA; +typedef const DIDEVICEIMAGEINFOHEADERW *LPCDIDEVICEIMAGEINFOHEADERW; +#ifdef UNICODE +typedef DIDEVICEIMAGEINFOHEADERW DIDEVICEIMAGEINFOHEADER; +typedef LPCDIDEVICEIMAGEINFOHEADERW LPCDIDEVICEIMAGEINFOHEADER; +#else +typedef DIDEVICEIMAGEINFOHEADERA DIDEVICEIMAGEINFOHEADER; +typedef LPCDIDEVICEIMAGEINFOHEADERA LPCDIDEVICEIMAGEINFOHEADER; +#endif +typedef const DIDEVICEIMAGEINFOHEADER *LPCDIDEVICEIMAGEINFOHEADER; + +#endif + +#if(DIRECTINPUT_VERSION >= 0x0500) + +typedef struct DIDEVICEOBJECTINSTANCE_DX3A { + DWORD dwSize; + GUID guidType; + DWORD dwOfs; + DWORD dwType; + DWORD dwFlags; + CHAR tszName[MAX_PATH]; +} DIDEVICEOBJECTINSTANCE_DX3A, *LPDIDEVICEOBJECTINSTANCE_DX3A; +typedef struct DIDEVICEOBJECTINSTANCE_DX3W { + DWORD dwSize; + GUID guidType; + DWORD dwOfs; + DWORD dwType; + DWORD dwFlags; + WCHAR tszName[MAX_PATH]; +} DIDEVICEOBJECTINSTANCE_DX3W, *LPDIDEVICEOBJECTINSTANCE_DX3W; +#ifdef UNICODE +typedef DIDEVICEOBJECTINSTANCE_DX3W DIDEVICEOBJECTINSTANCE_DX3; +typedef LPDIDEVICEOBJECTINSTANCE_DX3W LPDIDEVICEOBJECTINSTANCE_DX3; +#else +typedef DIDEVICEOBJECTINSTANCE_DX3A DIDEVICEOBJECTINSTANCE_DX3; +typedef LPDIDEVICEOBJECTINSTANCE_DX3A LPDIDEVICEOBJECTINSTANCE_DX3; +#endif +typedef const DIDEVICEOBJECTINSTANCE_DX3A *LPCDIDEVICEOBJECTINSTANCE_DX3A; +typedef const DIDEVICEOBJECTINSTANCE_DX3W *LPCDIDEVICEOBJECTINSTANCE_DX3W; +typedef const DIDEVICEOBJECTINSTANCE_DX3 *LPCDIDEVICEOBJECTINSTANCE_DX3; +#endif + +typedef struct DIDEVICEOBJECTINSTANCEA { + DWORD dwSize; + GUID guidType; + DWORD dwOfs; + DWORD dwType; + DWORD dwFlags; + CHAR tszName[MAX_PATH]; +#if(DIRECTINPUT_VERSION >= 0x0500) + DWORD dwFFMaxForce; + DWORD dwFFForceResolution; + WORD wCollectionNumber; + WORD wDesignatorIndex; + WORD wUsagePage; + WORD wUsage; + DWORD dwDimension; + WORD wExponent; + WORD wReportId; +#endif +} DIDEVICEOBJECTINSTANCEA, *LPDIDEVICEOBJECTINSTANCEA; +typedef struct DIDEVICEOBJECTINSTANCEW { + DWORD dwSize; + GUID guidType; + DWORD dwOfs; + DWORD dwType; + DWORD dwFlags; + WCHAR tszName[MAX_PATH]; +#if(DIRECTINPUT_VERSION >= 0x0500) + DWORD dwFFMaxForce; + DWORD dwFFForceResolution; + WORD wCollectionNumber; + WORD wDesignatorIndex; + WORD wUsagePage; + WORD wUsage; + DWORD dwDimension; + WORD wExponent; + WORD wReportId; +#endif +} DIDEVICEOBJECTINSTANCEW, *LPDIDEVICEOBJECTINSTANCEW; +#ifdef UNICODE +typedef DIDEVICEOBJECTINSTANCEW DIDEVICEOBJECTINSTANCE; +typedef LPDIDEVICEOBJECTINSTANCEW LPDIDEVICEOBJECTINSTANCE; +#else +typedef DIDEVICEOBJECTINSTANCEA DIDEVICEOBJECTINSTANCE; +typedef LPDIDEVICEOBJECTINSTANCEA LPDIDEVICEOBJECTINSTANCE; +#endif +typedef const DIDEVICEOBJECTINSTANCEA *LPCDIDEVICEOBJECTINSTANCEA; +typedef const DIDEVICEOBJECTINSTANCEW *LPCDIDEVICEOBJECTINSTANCEW; +typedef const DIDEVICEOBJECTINSTANCE *LPCDIDEVICEOBJECTINSTANCE; + +typedef BOOL (FAR PASCAL * LPDIENUMDEVICEOBJECTSCALLBACKA)(LPCDIDEVICEOBJECTINSTANCEA, LPVOID); +typedef BOOL (FAR PASCAL * LPDIENUMDEVICEOBJECTSCALLBACKW)(LPCDIDEVICEOBJECTINSTANCEW, LPVOID); +#ifdef UNICODE +#define LPDIENUMDEVICEOBJECTSCALLBACK LPDIENUMDEVICEOBJECTSCALLBACKW +#else +#define LPDIENUMDEVICEOBJECTSCALLBACK LPDIENUMDEVICEOBJECTSCALLBACKA +#endif + +#if(DIRECTINPUT_VERSION >= 0x0500) +#define DIDOI_FFACTUATOR 0x00000001 +#define DIDOI_FFEFFECTTRIGGER 0x00000002 +#define DIDOI_POLLED 0x00008000 +#define DIDOI_ASPECTPOSITION 0x00000100 +#define DIDOI_ASPECTVELOCITY 0x00000200 +#define DIDOI_ASPECTACCEL 0x00000300 +#define DIDOI_ASPECTFORCE 0x00000400 +#define DIDOI_ASPECTMASK 0x00000F00 +#endif +#if(DIRECTINPUT_VERSION >= 0x050a) +#define DIDOI_GUIDISUSAGE 0x00010000 +#endif + +typedef struct DIPROPHEADER { + DWORD dwSize; + DWORD dwHeaderSize; + DWORD dwObj; + DWORD dwHow; +} DIPROPHEADER, *LPDIPROPHEADER; +typedef const DIPROPHEADER *LPCDIPROPHEADER; + +#define DIPH_DEVICE 0 +#define DIPH_BYOFFSET 1 +#define DIPH_BYID 2 +#if(DIRECTINPUT_VERSION >= 0x050a) +#define DIPH_BYUSAGE 3 + +#define DIMAKEUSAGEDWORD(UsagePage, Usage) \ + (DWORD)MAKELONG(Usage, UsagePage) +#endif + +typedef struct DIPROPDWORD { + DIPROPHEADER diph; + DWORD dwData; +} DIPROPDWORD, *LPDIPROPDWORD; +typedef const DIPROPDWORD *LPCDIPROPDWORD; + +#if(DIRECTINPUT_VERSION >= 0x0800) +typedef struct DIPROPPOINTER { + DIPROPHEADER diph; + UINT_PTR uData; +} DIPROPPOINTER, *LPDIPROPPOINTER; +typedef const DIPROPPOINTER *LPCDIPROPPOINTER; +#endif + +typedef struct DIPROPRANGE { + DIPROPHEADER diph; + LONG lMin; + LONG lMax; +} DIPROPRANGE, *LPDIPROPRANGE; +typedef const DIPROPRANGE *LPCDIPROPRANGE; + +#define DIPROPRANGE_NOMIN ((LONG)0x80000000) +#define DIPROPRANGE_NOMAX ((LONG)0x7FFFFFFF) + +#if(DIRECTINPUT_VERSION >= 0x050a) +typedef struct DIPROPCAL { + DIPROPHEADER diph; + LONG lMin; + LONG lCenter; + LONG lMax; +} DIPROPCAL, *LPDIPROPCAL; +typedef const DIPROPCAL *LPCDIPROPCAL; + +typedef struct DIPROPGUIDANDPATH { + DIPROPHEADER diph; + GUID guidClass; + WCHAR wszPath[MAX_PATH]; +} DIPROPGUIDANDPATH, *LPDIPROPGUIDANDPATH; +typedef const DIPROPGUIDANDPATH *LPCDIPROPGUIDANDPATH; + +typedef struct DIPROPSTRING { + DIPROPHEADER diph; + WCHAR wsz[MAX_PATH]; +} DIPROPSTRING, *LPDIPROPSTRING; +typedef const DIPROPSTRING *LPCDIPROPSTRING; + +#endif + +#if(DIRECTINPUT_VERSION >= 0x0800) +#define MAXCPOINTSNUM 8 + +typedef struct _CPOINT +{ + LONG lP; + DWORD dwLog; +} CPOINT, *PCPOINT; + +typedef struct DIPROPCPOINTS { + DIPROPHEADER diph; + DWORD dwCPointsNum; + CPOINT cp[MAXCPOINTSNUM]; +} DIPROPCPOINTS, *LPDIPROPCPOINTS; +typedef const DIPROPCPOINTS *LPCDIPROPCPOINTS; +#endif + +#ifdef __cplusplus +#define MAKEDIPROP(prop) (*(const GUID *)(prop)) +#else +#define MAKEDIPROP(prop) ((REFGUID)(prop)) +#endif + +#define DIPROP_BUFFERSIZE MAKEDIPROP(1) + +#define DIPROP_AXISMODE MAKEDIPROP(2) + +#define DIPROPAXISMODE_ABS 0 +#define DIPROPAXISMODE_REL 1 + +#define DIPROP_GRANULARITY MAKEDIPROP(3) + +#define DIPROP_RANGE MAKEDIPROP(4) + +#define DIPROP_DEADZONE MAKEDIPROP(5) + +#define DIPROP_SATURATION MAKEDIPROP(6) + +#define DIPROP_FFGAIN MAKEDIPROP(7) + +#define DIPROP_FFLOAD MAKEDIPROP(8) + +#define DIPROP_AUTOCENTER MAKEDIPROP(9) + +#define DIPROPAUTOCENTER_OFF 0 +#define DIPROPAUTOCENTER_ON 1 + +#define DIPROP_CALIBRATIONMODE MAKEDIPROP(10) + +#define DIPROPCALIBRATIONMODE_COOKED 0 +#define DIPROPCALIBRATIONMODE_RAW 1 + +#if(DIRECTINPUT_VERSION >= 0x050a) +#define DIPROP_CALIBRATION MAKEDIPROP(11) + +#define DIPROP_GUIDANDPATH MAKEDIPROP(12) + +#define DIPROP_INSTANCENAME MAKEDIPROP(13) + +#define DIPROP_PRODUCTNAME MAKEDIPROP(14) +#endif + +#if(DIRECTINPUT_VERSION >= 0x05b2) +#define DIPROP_JOYSTICKID MAKEDIPROP(15) + +#define DIPROP_GETPORTDISPLAYNAME MAKEDIPROP(16) + +#endif + +#if(DIRECTINPUT_VERSION >= 0x0700) +#define DIPROP_PHYSICALRANGE MAKEDIPROP(18) + +#define DIPROP_LOGICALRANGE MAKEDIPROP(19) +#endif + +#if(DIRECTINPUT_VERSION >= 0x0800) +#define DIPROP_KEYNAME MAKEDIPROP(20) + +#define DIPROP_CPOINTS MAKEDIPROP(21) + +#define DIPROP_APPDATA MAKEDIPROP(22) + +#define DIPROP_SCANCODE MAKEDIPROP(23) + +#define DIPROP_VIDPID MAKEDIPROP(24) + +#define DIPROP_USERNAME MAKEDIPROP(25) + +#define DIPROP_TYPENAME MAKEDIPROP(26) +#endif + +typedef struct DIDEVICEOBJECTDATA_DX3 { + DWORD dwOfs; + DWORD dwData; + DWORD dwTimeStamp; + DWORD dwSequence; +} DIDEVICEOBJECTDATA_DX3, *LPDIDEVICEOBJECTDATA_DX3; +typedef const DIDEVICEOBJECTDATA_DX3 *LPCDIDEVICEOBJECTDATA_DX; + +typedef struct DIDEVICEOBJECTDATA { + DWORD dwOfs; + DWORD dwData; + DWORD dwTimeStamp; + DWORD dwSequence; +#if(DIRECTINPUT_VERSION >= 0x0800) + UINT_PTR uAppData; +#endif +} DIDEVICEOBJECTDATA, *LPDIDEVICEOBJECTDATA; +typedef const DIDEVICEOBJECTDATA *LPCDIDEVICEOBJECTDATA; + +#define DIGDD_PEEK 0x00000001 + +#define DISEQUENCE_COMPARE(dwSequence1, cmp, dwSequence2) \ + ((int)((dwSequence1) - (dwSequence2)) cmp 0) +#define DISCL_EXCLUSIVE 0x00000001 +#define DISCL_NONEXCLUSIVE 0x00000002 +#define DISCL_FOREGROUND 0x00000004 +#define DISCL_BACKGROUND 0x00000008 +#define DISCL_NOWINKEY 0x00000010 + +#if(DIRECTINPUT_VERSION >= 0x0500) + +typedef struct DIDEVICEINSTANCE_DX3A { + DWORD dwSize; + GUID guidInstance; + GUID guidProduct; + DWORD dwDevType; + CHAR tszInstanceName[MAX_PATH]; + CHAR tszProductName[MAX_PATH]; +} DIDEVICEINSTANCE_DX3A, *LPDIDEVICEINSTANCE_DX3A; +typedef struct DIDEVICEINSTANCE_DX3W { + DWORD dwSize; + GUID guidInstance; + GUID guidProduct; + DWORD dwDevType; + WCHAR tszInstanceName[MAX_PATH]; + WCHAR tszProductName[MAX_PATH]; +} DIDEVICEINSTANCE_DX3W, *LPDIDEVICEINSTANCE_DX3W; +#ifdef UNICODE +typedef DIDEVICEINSTANCE_DX3W DIDEVICEINSTANCE_DX3; +typedef LPDIDEVICEINSTANCE_DX3W LPDIDEVICEINSTANCE_DX3; +#else +typedef DIDEVICEINSTANCE_DX3A DIDEVICEINSTANCE_DX3; +typedef LPDIDEVICEINSTANCE_DX3A LPDIDEVICEINSTANCE_DX3; +#endif +typedef const DIDEVICEINSTANCE_DX3A *LPCDIDEVICEINSTANCE_DX3A; +typedef const DIDEVICEINSTANCE_DX3W *LPCDIDEVICEINSTANCE_DX3W; +typedef const DIDEVICEINSTANCE_DX3 *LPCDIDEVICEINSTANCE_DX3; +#endif + +typedef struct DIDEVICEINSTANCEA { + DWORD dwSize; + GUID guidInstance; + GUID guidProduct; + DWORD dwDevType; + CHAR tszInstanceName[MAX_PATH]; + CHAR tszProductName[MAX_PATH]; +#if(DIRECTINPUT_VERSION >= 0x0500) + GUID guidFFDriver; + WORD wUsagePage; + WORD wUsage; +#endif +} DIDEVICEINSTANCEA, *LPDIDEVICEINSTANCEA; +typedef struct DIDEVICEINSTANCEW { + DWORD dwSize; + GUID guidInstance; + GUID guidProduct; + DWORD dwDevType; + WCHAR tszInstanceName[MAX_PATH]; + WCHAR tszProductName[MAX_PATH]; +#if(DIRECTINPUT_VERSION >= 0x0500) + GUID guidFFDriver; + WORD wUsagePage; + WORD wUsage; +#endif +} DIDEVICEINSTANCEW, *LPDIDEVICEINSTANCEW; +#ifdef UNICODE +typedef DIDEVICEINSTANCEW DIDEVICEINSTANCE; +typedef LPDIDEVICEINSTANCEW LPDIDEVICEINSTANCE; +#else +typedef DIDEVICEINSTANCEA DIDEVICEINSTANCE; +typedef LPDIDEVICEINSTANCEA LPDIDEVICEINSTANCE; +#endif + +typedef const DIDEVICEINSTANCEA *LPCDIDEVICEINSTANCEA; +typedef const DIDEVICEINSTANCEW *LPCDIDEVICEINSTANCEW; +#ifdef UNICODE +typedef DIDEVICEINSTANCEW DIDEVICEINSTANCE; +typedef LPCDIDEVICEINSTANCEW LPCDIDEVICEINSTANCE; +#else +typedef DIDEVICEINSTANCEA DIDEVICEINSTANCE; +typedef LPCDIDEVICEINSTANCEA LPCDIDEVICEINSTANCE; +#endif +typedef const DIDEVICEINSTANCE *LPCDIDEVICEINSTANCE; + +#undef INTERFACE +#define INTERFACE IDirectInputDeviceW + +DECLARE_INTERFACE_(IDirectInputDeviceW, IUnknown) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(GetCapabilities)(THIS_ LPDIDEVCAPS) PURE; + STDMETHOD(EnumObjects)(THIS_ LPDIENUMDEVICEOBJECTSCALLBACKW,LPVOID,DWORD) PURE; + STDMETHOD(GetProperty)(THIS_ REFGUID,LPDIPROPHEADER) PURE; + STDMETHOD(SetProperty)(THIS_ REFGUID,LPCDIPROPHEADER) PURE; + STDMETHOD(Acquire)(THIS) PURE; + STDMETHOD(Unacquire)(THIS) PURE; + STDMETHOD(GetDeviceState)(THIS_ DWORD,LPVOID) PURE; + STDMETHOD(GetDeviceData)(THIS_ DWORD,LPDIDEVICEOBJECTDATA,LPDWORD,DWORD) PURE; + STDMETHOD(SetDataFormat)(THIS_ LPCDIDATAFORMAT) PURE; + STDMETHOD(SetEventNotification)(THIS_ HANDLE) PURE; + STDMETHOD(SetCooperativeLevel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(GetObjectInfo)(THIS_ LPDIDEVICEOBJECTINSTANCEW,DWORD,DWORD) PURE; + STDMETHOD(GetDeviceInfo)(THIS_ LPDIDEVICEINSTANCEW) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD,REFGUID) PURE; +}; + +typedef struct IDirectInputDeviceW *LPDIRECTINPUTDEVICEW; + +#undef INTERFACE +#define INTERFACE IDirectInputDeviceA + +DECLARE_INTERFACE_(IDirectInputDeviceA, IUnknown) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(GetCapabilities)(THIS_ LPDIDEVCAPS) PURE; + STDMETHOD(EnumObjects)(THIS_ LPDIENUMDEVICEOBJECTSCALLBACKA,LPVOID,DWORD) PURE; + STDMETHOD(GetProperty)(THIS_ REFGUID,LPDIPROPHEADER) PURE; + STDMETHOD(SetProperty)(THIS_ REFGUID,LPCDIPROPHEADER) PURE; + STDMETHOD(Acquire)(THIS) PURE; + STDMETHOD(Unacquire)(THIS) PURE; + STDMETHOD(GetDeviceState)(THIS_ DWORD,LPVOID) PURE; + STDMETHOD(GetDeviceData)(THIS_ DWORD,LPDIDEVICEOBJECTDATA,LPDWORD,DWORD) PURE; + STDMETHOD(SetDataFormat)(THIS_ LPCDIDATAFORMAT) PURE; + STDMETHOD(SetEventNotification)(THIS_ HANDLE) PURE; + STDMETHOD(SetCooperativeLevel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(GetObjectInfo)(THIS_ LPDIDEVICEOBJECTINSTANCEA,DWORD,DWORD) PURE; + STDMETHOD(GetDeviceInfo)(THIS_ LPDIDEVICEINSTANCEA) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD,REFGUID) PURE; +}; + +typedef struct IDirectInputDeviceA *LPDIRECTINPUTDEVICEA; + +#ifdef UNICODE +#define IID_IDirectInputDevice IID_IDirectInputDeviceW +#define IDirectInputDevice IDirectInputDeviceW +#define IDirectInputDeviceVtbl IDirectInputDeviceWVtbl +#else +#define IID_IDirectInputDevice IID_IDirectInputDeviceA +#define IDirectInputDevice IDirectInputDeviceA +#define IDirectInputDeviceVtbl IDirectInputDeviceAVtbl +#endif +typedef struct IDirectInputDevice *LPDIRECTINPUTDEVICE; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectInputDevice_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirectInputDevice_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirectInputDevice_Release(p) (p)->lpVtbl->Release(p) +#define IDirectInputDevice_GetCapabilities(p,a) (p)->lpVtbl->GetCapabilities(p,a) +#define IDirectInputDevice_EnumObjects(p,a,b,c) (p)->lpVtbl->EnumObjects(p,a,b,c) +#define IDirectInputDevice_GetProperty(p,a,b) (p)->lpVtbl->GetProperty(p,a,b) +#define IDirectInputDevice_SetProperty(p,a,b) (p)->lpVtbl->SetProperty(p,a,b) +#define IDirectInputDevice_Acquire(p) (p)->lpVtbl->Acquire(p) +#define IDirectInputDevice_Unacquire(p) (p)->lpVtbl->Unacquire(p) +#define IDirectInputDevice_GetDeviceState(p,a,b) (p)->lpVtbl->GetDeviceState(p,a,b) +#define IDirectInputDevice_GetDeviceData(p,a,b,c,d) (p)->lpVtbl->GetDeviceData(p,a,b,c,d) +#define IDirectInputDevice_SetDataFormat(p,a) (p)->lpVtbl->SetDataFormat(p,a) +#define IDirectInputDevice_SetEventNotification(p,a) (p)->lpVtbl->SetEventNotification(p,a) +#define IDirectInputDevice_SetCooperativeLevel(p,a,b) (p)->lpVtbl->SetCooperativeLevel(p,a,b) +#define IDirectInputDevice_GetObjectInfo(p,a,b,c) (p)->lpVtbl->GetObjectInfo(p,a,b,c) +#define IDirectInputDevice_GetDeviceInfo(p,a) (p)->lpVtbl->GetDeviceInfo(p,a) +#define IDirectInputDevice_RunControlPanel(p,a,b) (p)->lpVtbl->RunControlPanel(p,a,b) +#define IDirectInputDevice_Initialize(p,a,b,c) (p)->lpVtbl->Initialize(p,a,b,c) +#else +#define IDirectInputDevice_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirectInputDevice_AddRef(p) (p)->AddRef() +#define IDirectInputDevice_Release(p) (p)->Release() +#define IDirectInputDevice_GetCapabilities(p,a) (p)->GetCapabilities(a) +#define IDirectInputDevice_EnumObjects(p,a,b,c) (p)->EnumObjects(a,b,c) +#define IDirectInputDevice_GetProperty(p,a,b) (p)->GetProperty(a,b) +#define IDirectInputDevice_SetProperty(p,a,b) (p)->SetProperty(a,b) +#define IDirectInputDevice_Acquire(p) (p)->Acquire() +#define IDirectInputDevice_Unacquire(p) (p)->Unacquire() +#define IDirectInputDevice_GetDeviceState(p,a,b) (p)->GetDeviceState(a,b) +#define IDirectInputDevice_GetDeviceData(p,a,b,c,d) (p)->GetDeviceData(a,b,c,d) +#define IDirectInputDevice_SetDataFormat(p,a) (p)->SetDataFormat(a) +#define IDirectInputDevice_SetEventNotification(p,a) (p)->SetEventNotification(a) +#define IDirectInputDevice_SetCooperativeLevel(p,a,b) (p)->SetCooperativeLevel(a,b) +#define IDirectInputDevice_GetObjectInfo(p,a,b,c) (p)->GetObjectInfo(a,b,c) +#define IDirectInputDevice_GetDeviceInfo(p,a) (p)->GetDeviceInfo(a) +#define IDirectInputDevice_RunControlPanel(p,a,b) (p)->RunControlPanel(a,b) +#define IDirectInputDevice_Initialize(p,a,b,c) (p)->Initialize(a,b,c) +#endif + +#endif + +#if(DIRECTINPUT_VERSION >= 0x0500) + +#define DISFFC_RESET 0x00000001 +#define DISFFC_STOPALL 0x00000002 +#define DISFFC_PAUSE 0x00000004 +#define DISFFC_CONTINUE 0x00000008 +#define DISFFC_SETACTUATORSON 0x00000010 +#define DISFFC_SETACTUATORSOFF 0x00000020 + +#define DIGFFS_EMPTY 0x00000001 +#define DIGFFS_STOPPED 0x00000002 +#define DIGFFS_PAUSED 0x00000004 +#define DIGFFS_ACTUATORSON 0x00000010 +#define DIGFFS_ACTUATORSOFF 0x00000020 +#define DIGFFS_POWERON 0x00000040 +#define DIGFFS_POWEROFF 0x00000080 +#define DIGFFS_SAFETYSWITCHON 0x00000100 +#define DIGFFS_SAFETYSWITCHOFF 0x00000200 +#define DIGFFS_USERFFSWITCHON 0x00000400 +#define DIGFFS_USERFFSWITCHOFF 0x00000800 +#define DIGFFS_DEVICELOST 0x80000000 + +#ifndef DIJ_RINGZERO + +typedef struct DIEFFECTINFOA { + DWORD dwSize; + GUID guid; + DWORD dwEffType; + DWORD dwStaticParams; + DWORD dwDynamicParams; + CHAR tszName[MAX_PATH]; +} DIEFFECTINFOA, *LPDIEFFECTINFOA; +typedef struct DIEFFECTINFOW { + DWORD dwSize; + GUID guid; + DWORD dwEffType; + DWORD dwStaticParams; + DWORD dwDynamicParams; + WCHAR tszName[MAX_PATH]; +} DIEFFECTINFOW, *LPDIEFFECTINFOW; +#ifdef UNICODE +typedef DIEFFECTINFOW DIEFFECTINFO; +typedef LPDIEFFECTINFOW LPDIEFFECTINFO; +#else +typedef DIEFFECTINFOA DIEFFECTINFO; +typedef LPDIEFFECTINFOA LPDIEFFECTINFO; +#endif +typedef const DIEFFECTINFOA *LPCDIEFFECTINFOA; +typedef const DIEFFECTINFOW *LPCDIEFFECTINFOW; +typedef const DIEFFECTINFO *LPCDIEFFECTINFO; + +#define DISDD_CONTINUE 0x00000001 + +typedef BOOL (FAR PASCAL * LPDIENUMEFFECTSCALLBACKA)(LPCDIEFFECTINFOA, LPVOID); +typedef BOOL (FAR PASCAL * LPDIENUMEFFECTSCALLBACKW)(LPCDIEFFECTINFOW, LPVOID); +#ifdef UNICODE +#define LPDIENUMEFFECTSCALLBACK LPDIENUMEFFECTSCALLBACKW +#else +#define LPDIENUMEFFECTSCALLBACK LPDIENUMEFFECTSCALLBACKA +#endif +typedef BOOL (FAR PASCAL * LPDIENUMCREATEDEFFECTOBJECTSCALLBACK)(LPDIRECTINPUTEFFECT, LPVOID); + +#undef INTERFACE +#define INTERFACE IDirectInputDevice2W + +DECLARE_INTERFACE_(IDirectInputDevice2W, IDirectInputDeviceW) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(GetCapabilities)(THIS_ LPDIDEVCAPS) PURE; + STDMETHOD(EnumObjects)(THIS_ LPDIENUMDEVICEOBJECTSCALLBACKW,LPVOID,DWORD) PURE; + STDMETHOD(GetProperty)(THIS_ REFGUID,LPDIPROPHEADER) PURE; + STDMETHOD(SetProperty)(THIS_ REFGUID,LPCDIPROPHEADER) PURE; + STDMETHOD(Acquire)(THIS) PURE; + STDMETHOD(Unacquire)(THIS) PURE; + STDMETHOD(GetDeviceState)(THIS_ DWORD,LPVOID) PURE; + STDMETHOD(GetDeviceData)(THIS_ DWORD,LPDIDEVICEOBJECTDATA,LPDWORD,DWORD) PURE; + STDMETHOD(SetDataFormat)(THIS_ LPCDIDATAFORMAT) PURE; + STDMETHOD(SetEventNotification)(THIS_ HANDLE) PURE; + STDMETHOD(SetCooperativeLevel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(GetObjectInfo)(THIS_ LPDIDEVICEOBJECTINSTANCEW,DWORD,DWORD) PURE; + STDMETHOD(GetDeviceInfo)(THIS_ LPDIDEVICEINSTANCEW) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD,REFGUID) PURE; + + STDMETHOD(CreateEffect)(THIS_ REFGUID,LPCDIEFFECT,LPDIRECTINPUTEFFECT *,LPUNKNOWN) PURE; + STDMETHOD(EnumEffects)(THIS_ LPDIENUMEFFECTSCALLBACKW,LPVOID,DWORD) PURE; + STDMETHOD(GetEffectInfo)(THIS_ LPDIEFFECTINFOW,REFGUID) PURE; + STDMETHOD(GetForceFeedbackState)(THIS_ LPDWORD) PURE; + STDMETHOD(SendForceFeedbackCommand)(THIS_ DWORD) PURE; + STDMETHOD(EnumCreatedEffectObjects)(THIS_ LPDIENUMCREATEDEFFECTOBJECTSCALLBACK,LPVOID,DWORD) PURE; + STDMETHOD(Escape)(THIS_ LPDIEFFESCAPE) PURE; + STDMETHOD(Poll)(THIS) PURE; + STDMETHOD(SendDeviceData)(THIS_ DWORD,LPCDIDEVICEOBJECTDATA,LPDWORD,DWORD) PURE; +}; + +typedef struct IDirectInputDevice2W *LPDIRECTINPUTDEVICE2W; + +#undef INTERFACE +#define INTERFACE IDirectInputDevice2A + +DECLARE_INTERFACE_(IDirectInputDevice2A, IDirectInputDeviceA) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(GetCapabilities)(THIS_ LPDIDEVCAPS) PURE; + STDMETHOD(EnumObjects)(THIS_ LPDIENUMDEVICEOBJECTSCALLBACKA,LPVOID,DWORD) PURE; + STDMETHOD(GetProperty)(THIS_ REFGUID,LPDIPROPHEADER) PURE; + STDMETHOD(SetProperty)(THIS_ REFGUID,LPCDIPROPHEADER) PURE; + STDMETHOD(Acquire)(THIS) PURE; + STDMETHOD(Unacquire)(THIS) PURE; + STDMETHOD(GetDeviceState)(THIS_ DWORD,LPVOID) PURE; + STDMETHOD(GetDeviceData)(THIS_ DWORD,LPDIDEVICEOBJECTDATA,LPDWORD,DWORD) PURE; + STDMETHOD(SetDataFormat)(THIS_ LPCDIDATAFORMAT) PURE; + STDMETHOD(SetEventNotification)(THIS_ HANDLE) PURE; + STDMETHOD(SetCooperativeLevel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(GetObjectInfo)(THIS_ LPDIDEVICEOBJECTINSTANCEA,DWORD,DWORD) PURE; + STDMETHOD(GetDeviceInfo)(THIS_ LPDIDEVICEINSTANCEA) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD,REFGUID) PURE; + + STDMETHOD(CreateEffect)(THIS_ REFGUID,LPCDIEFFECT,LPDIRECTINPUTEFFECT *,LPUNKNOWN) PURE; + STDMETHOD(EnumEffects)(THIS_ LPDIENUMEFFECTSCALLBACKA,LPVOID,DWORD) PURE; + STDMETHOD(GetEffectInfo)(THIS_ LPDIEFFECTINFOA,REFGUID) PURE; + STDMETHOD(GetForceFeedbackState)(THIS_ LPDWORD) PURE; + STDMETHOD(SendForceFeedbackCommand)(THIS_ DWORD) PURE; + STDMETHOD(EnumCreatedEffectObjects)(THIS_ LPDIENUMCREATEDEFFECTOBJECTSCALLBACK,LPVOID,DWORD) PURE; + STDMETHOD(Escape)(THIS_ LPDIEFFESCAPE) PURE; + STDMETHOD(Poll)(THIS) PURE; + STDMETHOD(SendDeviceData)(THIS_ DWORD,LPCDIDEVICEOBJECTDATA,LPDWORD,DWORD) PURE; +}; + +typedef struct IDirectInputDevice2A *LPDIRECTINPUTDEVICE2A; + +#ifdef UNICODE +#define IID_IDirectInputDevice2 IID_IDirectInputDevice2W +#define IDirectInputDevice2 IDirectInputDevice2W +#define IDirectInputDevice2Vtbl IDirectInputDevice2WVtbl +#else +#define IID_IDirectInputDevice2 IID_IDirectInputDevice2A +#define IDirectInputDevice2 IDirectInputDevice2A +#define IDirectInputDevice2Vtbl IDirectInputDevice2AVtbl +#endif +typedef struct IDirectInputDevice2 *LPDIRECTINPUTDEVICE2; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectInputDevice2_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirectInputDevice2_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirectInputDevice2_Release(p) (p)->lpVtbl->Release(p) +#define IDirectInputDevice2_GetCapabilities(p,a) (p)->lpVtbl->GetCapabilities(p,a) +#define IDirectInputDevice2_EnumObjects(p,a,b,c) (p)->lpVtbl->EnumObjects(p,a,b,c) +#define IDirectInputDevice2_GetProperty(p,a,b) (p)->lpVtbl->GetProperty(p,a,b) +#define IDirectInputDevice2_SetProperty(p,a,b) (p)->lpVtbl->SetProperty(p,a,b) +#define IDirectInputDevice2_Acquire(p) (p)->lpVtbl->Acquire(p) +#define IDirectInputDevice2_Unacquire(p) (p)->lpVtbl->Unacquire(p) +#define IDirectInputDevice2_GetDeviceState(p,a,b) (p)->lpVtbl->GetDeviceState(p,a,b) +#define IDirectInputDevice2_GetDeviceData(p,a,b,c,d) (p)->lpVtbl->GetDeviceData(p,a,b,c,d) +#define IDirectInputDevice2_SetDataFormat(p,a) (p)->lpVtbl->SetDataFormat(p,a) +#define IDirectInputDevice2_SetEventNotification(p,a) (p)->lpVtbl->SetEventNotification(p,a) +#define IDirectInputDevice2_SetCooperativeLevel(p,a,b) (p)->lpVtbl->SetCooperativeLevel(p,a,b) +#define IDirectInputDevice2_GetObjectInfo(p,a,b,c) (p)->lpVtbl->GetObjectInfo(p,a,b,c) +#define IDirectInputDevice2_GetDeviceInfo(p,a) (p)->lpVtbl->GetDeviceInfo(p,a) +#define IDirectInputDevice2_RunControlPanel(p,a,b) (p)->lpVtbl->RunControlPanel(p,a,b) +#define IDirectInputDevice2_Initialize(p,a,b,c) (p)->lpVtbl->Initialize(p,a,b,c) +#define IDirectInputDevice2_CreateEffect(p,a,b,c,d) (p)->lpVtbl->CreateEffect(p,a,b,c,d) +#define IDirectInputDevice2_EnumEffects(p,a,b,c) (p)->lpVtbl->EnumEffects(p,a,b,c) +#define IDirectInputDevice2_GetEffectInfo(p,a,b) (p)->lpVtbl->GetEffectInfo(p,a,b) +#define IDirectInputDevice2_GetForceFeedbackState(p,a) (p)->lpVtbl->GetForceFeedbackState(p,a) +#define IDirectInputDevice2_SendForceFeedbackCommand(p,a) (p)->lpVtbl->SendForceFeedbackCommand(p,a) +#define IDirectInputDevice2_EnumCreatedEffectObjects(p,a,b,c) (p)->lpVtbl->EnumCreatedEffectObjects(p,a,b,c) +#define IDirectInputDevice2_Escape(p,a) (p)->lpVtbl->Escape(p,a) +#define IDirectInputDevice2_Poll(p) (p)->lpVtbl->Poll(p) +#define IDirectInputDevice2_SendDeviceData(p,a,b,c,d) (p)->lpVtbl->SendDeviceData(p,a,b,c,d) +#else +#define IDirectInputDevice2_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirectInputDevice2_AddRef(p) (p)->AddRef() +#define IDirectInputDevice2_Release(p) (p)->Release() +#define IDirectInputDevice2_GetCapabilities(p,a) (p)->GetCapabilities(a) +#define IDirectInputDevice2_EnumObjects(p,a,b,c) (p)->EnumObjects(a,b,c) +#define IDirectInputDevice2_GetProperty(p,a,b) (p)->GetProperty(a,b) +#define IDirectInputDevice2_SetProperty(p,a,b) (p)->SetProperty(a,b) +#define IDirectInputDevice2_Acquire(p) (p)->Acquire() +#define IDirectInputDevice2_Unacquire(p) (p)->Unacquire() +#define IDirectInputDevice2_GetDeviceState(p,a,b) (p)->GetDeviceState(a,b) +#define IDirectInputDevice2_GetDeviceData(p,a,b,c,d) (p)->GetDeviceData(a,b,c,d) +#define IDirectInputDevice2_SetDataFormat(p,a) (p)->SetDataFormat(a) +#define IDirectInputDevice2_SetEventNotification(p,a) (p)->SetEventNotification(a) +#define IDirectInputDevice2_SetCooperativeLevel(p,a,b) (p)->SetCooperativeLevel(a,b) +#define IDirectInputDevice2_GetObjectInfo(p,a,b,c) (p)->GetObjectInfo(a,b,c) +#define IDirectInputDevice2_GetDeviceInfo(p,a) (p)->GetDeviceInfo(a) +#define IDirectInputDevice2_RunControlPanel(p,a,b) (p)->RunControlPanel(a,b) +#define IDirectInputDevice2_Initialize(p,a,b,c) (p)->Initialize(a,b,c) +#define IDirectInputDevice2_CreateEffect(p,a,b,c,d) (p)->CreateEffect(a,b,c,d) +#define IDirectInputDevice2_EnumEffects(p,a,b,c) (p)->EnumEffects(a,b,c) +#define IDirectInputDevice2_GetEffectInfo(p,a,b) (p)->GetEffectInfo(a,b) +#define IDirectInputDevice2_GetForceFeedbackState(p,a) (p)->GetForceFeedbackState(a) +#define IDirectInputDevice2_SendForceFeedbackCommand(p,a) (p)->SendForceFeedbackCommand(a) +#define IDirectInputDevice2_EnumCreatedEffectObjects(p,a,b,c) (p)->EnumCreatedEffectObjects(a,b,c) +#define IDirectInputDevice2_Escape(p,a) (p)->Escape(a) +#define IDirectInputDevice2_Poll(p) (p)->Poll() +#define IDirectInputDevice2_SendDeviceData(p,a,b,c,d) (p)->SendDeviceData(a,b,c,d) +#endif + +#endif + +#endif + +#if(DIRECTINPUT_VERSION >= 0x0700) +#define DIFEF_DEFAULT 0x00000000 +#define DIFEF_INCLUDENONSTANDARD 0x00000001 +#define DIFEF_MODIFYIFNEEDED 0x00000010 + +#ifndef DIJ_RINGZERO + +#undef INTERFACE +#define INTERFACE IDirectInputDevice7W + +DECLARE_INTERFACE_(IDirectInputDevice7W, IDirectInputDevice2W) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(GetCapabilities)(THIS_ LPDIDEVCAPS) PURE; + STDMETHOD(EnumObjects)(THIS_ LPDIENUMDEVICEOBJECTSCALLBACKW,LPVOID,DWORD) PURE; + STDMETHOD(GetProperty)(THIS_ REFGUID,LPDIPROPHEADER) PURE; + STDMETHOD(SetProperty)(THIS_ REFGUID,LPCDIPROPHEADER) PURE; + STDMETHOD(Acquire)(THIS) PURE; + STDMETHOD(Unacquire)(THIS) PURE; + STDMETHOD(GetDeviceState)(THIS_ DWORD,LPVOID) PURE; + STDMETHOD(GetDeviceData)(THIS_ DWORD,LPDIDEVICEOBJECTDATA,LPDWORD,DWORD) PURE; + STDMETHOD(SetDataFormat)(THIS_ LPCDIDATAFORMAT) PURE; + STDMETHOD(SetEventNotification)(THIS_ HANDLE) PURE; + STDMETHOD(SetCooperativeLevel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(GetObjectInfo)(THIS_ LPDIDEVICEOBJECTINSTANCEW,DWORD,DWORD) PURE; + STDMETHOD(GetDeviceInfo)(THIS_ LPDIDEVICEINSTANCEW) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD,REFGUID) PURE; + STDMETHOD(CreateEffect)(THIS_ REFGUID,LPCDIEFFECT,LPDIRECTINPUTEFFECT *,LPUNKNOWN) PURE; + STDMETHOD(EnumEffects)(THIS_ LPDIENUMEFFECTSCALLBACKW,LPVOID,DWORD) PURE; + STDMETHOD(GetEffectInfo)(THIS_ LPDIEFFECTINFOW,REFGUID) PURE; + STDMETHOD(GetForceFeedbackState)(THIS_ LPDWORD) PURE; + STDMETHOD(SendForceFeedbackCommand)(THIS_ DWORD) PURE; + STDMETHOD(EnumCreatedEffectObjects)(THIS_ LPDIENUMCREATEDEFFECTOBJECTSCALLBACK,LPVOID,DWORD) PURE; + STDMETHOD(Escape)(THIS_ LPDIEFFESCAPE) PURE; + STDMETHOD(Poll)(THIS) PURE; + STDMETHOD(SendDeviceData)(THIS_ DWORD,LPCDIDEVICEOBJECTDATA,LPDWORD,DWORD) PURE; + + STDMETHOD(EnumEffectsInFile)(THIS_ LPCWSTR,LPDIENUMEFFECTSINFILECALLBACK,LPVOID,DWORD) PURE; + STDMETHOD(WriteEffectToFile)(THIS_ LPCWSTR,DWORD,LPDIFILEEFFECT,DWORD) PURE; +}; + +typedef struct IDirectInputDevice7W *LPDIRECTINPUTDEVICE7W; + +#undef INTERFACE +#define INTERFACE IDirectInputDevice7A + +DECLARE_INTERFACE_(IDirectInputDevice7A, IDirectInputDevice2A) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(GetCapabilities)(THIS_ LPDIDEVCAPS) PURE; + STDMETHOD(EnumObjects)(THIS_ LPDIENUMDEVICEOBJECTSCALLBACKA,LPVOID,DWORD) PURE; + STDMETHOD(GetProperty)(THIS_ REFGUID,LPDIPROPHEADER) PURE; + STDMETHOD(SetProperty)(THIS_ REFGUID,LPCDIPROPHEADER) PURE; + STDMETHOD(Acquire)(THIS) PURE; + STDMETHOD(Unacquire)(THIS) PURE; + STDMETHOD(GetDeviceState)(THIS_ DWORD,LPVOID) PURE; + STDMETHOD(GetDeviceData)(THIS_ DWORD,LPDIDEVICEOBJECTDATA,LPDWORD,DWORD) PURE; + STDMETHOD(SetDataFormat)(THIS_ LPCDIDATAFORMAT) PURE; + STDMETHOD(SetEventNotification)(THIS_ HANDLE) PURE; + STDMETHOD(SetCooperativeLevel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(GetObjectInfo)(THIS_ LPDIDEVICEOBJECTINSTANCEA,DWORD,DWORD) PURE; + STDMETHOD(GetDeviceInfo)(THIS_ LPDIDEVICEINSTANCEA) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD,REFGUID) PURE; + STDMETHOD(CreateEffect)(THIS_ REFGUID,LPCDIEFFECT,LPDIRECTINPUTEFFECT *,LPUNKNOWN) PURE; + STDMETHOD(EnumEffects)(THIS_ LPDIENUMEFFECTSCALLBACKA,LPVOID,DWORD) PURE; + STDMETHOD(GetEffectInfo)(THIS_ LPDIEFFECTINFOA,REFGUID) PURE; + STDMETHOD(GetForceFeedbackState)(THIS_ LPDWORD) PURE; + STDMETHOD(SendForceFeedbackCommand)(THIS_ DWORD) PURE; + STDMETHOD(EnumCreatedEffectObjects)(THIS_ LPDIENUMCREATEDEFFECTOBJECTSCALLBACK,LPVOID,DWORD) PURE; + STDMETHOD(Escape)(THIS_ LPDIEFFESCAPE) PURE; + STDMETHOD(Poll)(THIS) PURE; + STDMETHOD(SendDeviceData)(THIS_ DWORD,LPCDIDEVICEOBJECTDATA,LPDWORD,DWORD) PURE; + + STDMETHOD(EnumEffectsInFile)(THIS_ LPCSTR,LPDIENUMEFFECTSINFILECALLBACK,LPVOID,DWORD) PURE; + STDMETHOD(WriteEffectToFile)(THIS_ LPCSTR,DWORD,LPDIFILEEFFECT,DWORD) PURE; +}; + +typedef struct IDirectInputDevice7A *LPDIRECTINPUTDEVICE7A; + +#ifdef UNICODE +#define IID_IDirectInputDevice7 IID_IDirectInputDevice7W +#define IDirectInputDevice7 IDirectInputDevice7W +#define IDirectInputDevice7Vtbl IDirectInputDevice7WVtbl +#else +#define IID_IDirectInputDevice7 IID_IDirectInputDevice7A +#define IDirectInputDevice7 IDirectInputDevice7A +#define IDirectInputDevice7Vtbl IDirectInputDevice7AVtbl +#endif +typedef struct IDirectInputDevice7 *LPDIRECTINPUTDEVICE7; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectInputDevice7_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirectInputDevice7_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirectInputDevice7_Release(p) (p)->lpVtbl->Release(p) +#define IDirectInputDevice7_GetCapabilities(p,a) (p)->lpVtbl->GetCapabilities(p,a) +#define IDirectInputDevice7_EnumObjects(p,a,b,c) (p)->lpVtbl->EnumObjects(p,a,b,c) +#define IDirectInputDevice7_GetProperty(p,a,b) (p)->lpVtbl->GetProperty(p,a,b) +#define IDirectInputDevice7_SetProperty(p,a,b) (p)->lpVtbl->SetProperty(p,a,b) +#define IDirectInputDevice7_Acquire(p) (p)->lpVtbl->Acquire(p) +#define IDirectInputDevice7_Unacquire(p) (p)->lpVtbl->Unacquire(p) +#define IDirectInputDevice7_GetDeviceState(p,a,b) (p)->lpVtbl->GetDeviceState(p,a,b) +#define IDirectInputDevice7_GetDeviceData(p,a,b,c,d) (p)->lpVtbl->GetDeviceData(p,a,b,c,d) +#define IDirectInputDevice7_SetDataFormat(p,a) (p)->lpVtbl->SetDataFormat(p,a) +#define IDirectInputDevice7_SetEventNotification(p,a) (p)->lpVtbl->SetEventNotification(p,a) +#define IDirectInputDevice7_SetCooperativeLevel(p,a,b) (p)->lpVtbl->SetCooperativeLevel(p,a,b) +#define IDirectInputDevice7_GetObjectInfo(p,a,b,c) (p)->lpVtbl->GetObjectInfo(p,a,b,c) +#define IDirectInputDevice7_GetDeviceInfo(p,a) (p)->lpVtbl->GetDeviceInfo(p,a) +#define IDirectInputDevice7_RunControlPanel(p,a,b) (p)->lpVtbl->RunControlPanel(p,a,b) +#define IDirectInputDevice7_Initialize(p,a,b,c) (p)->lpVtbl->Initialize(p,a,b,c) +#define IDirectInputDevice7_CreateEffect(p,a,b,c,d) (p)->lpVtbl->CreateEffect(p,a,b,c,d) +#define IDirectInputDevice7_EnumEffects(p,a,b,c) (p)->lpVtbl->EnumEffects(p,a,b,c) +#define IDirectInputDevice7_GetEffectInfo(p,a,b) (p)->lpVtbl->GetEffectInfo(p,a,b) +#define IDirectInputDevice7_GetForceFeedbackState(p,a) (p)->lpVtbl->GetForceFeedbackState(p,a) +#define IDirectInputDevice7_SendForceFeedbackCommand(p,a) (p)->lpVtbl->SendForceFeedbackCommand(p,a) +#define IDirectInputDevice7_EnumCreatedEffectObjects(p,a,b,c) (p)->lpVtbl->EnumCreatedEffectObjects(p,a,b,c) +#define IDirectInputDevice7_Escape(p,a) (p)->lpVtbl->Escape(p,a) +#define IDirectInputDevice7_Poll(p) (p)->lpVtbl->Poll(p) +#define IDirectInputDevice7_SendDeviceData(p,a,b,c,d) (p)->lpVtbl->SendDeviceData(p,a,b,c,d) +#define IDirectInputDevice7_EnumEffectsInFile(p,a,b,c,d) (p)->lpVtbl->EnumEffectsInFile(p,a,b,c,d) +#define IDirectInputDevice7_WriteEffectToFile(p,a,b,c,d) (p)->lpVtbl->WriteEffectToFile(p,a,b,c,d) +#else +#define IDirectInputDevice7_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirectInputDevice7_AddRef(p) (p)->AddRef() +#define IDirectInputDevice7_Release(p) (p)->Release() +#define IDirectInputDevice7_GetCapabilities(p,a) (p)->GetCapabilities(a) +#define IDirectInputDevice7_EnumObjects(p,a,b,c) (p)->EnumObjects(a,b,c) +#define IDirectInputDevice7_GetProperty(p,a,b) (p)->GetProperty(a,b) +#define IDirectInputDevice7_SetProperty(p,a,b) (p)->SetProperty(a,b) +#define IDirectInputDevice7_Acquire(p) (p)->Acquire() +#define IDirectInputDevice7_Unacquire(p) (p)->Unacquire() +#define IDirectInputDevice7_GetDeviceState(p,a,b) (p)->GetDeviceState(a,b) +#define IDirectInputDevice7_GetDeviceData(p,a,b,c,d) (p)->GetDeviceData(a,b,c,d) +#define IDirectInputDevice7_SetDataFormat(p,a) (p)->SetDataFormat(a) +#define IDirectInputDevice7_SetEventNotification(p,a) (p)->SetEventNotification(a) +#define IDirectInputDevice7_SetCooperativeLevel(p,a,b) (p)->SetCooperativeLevel(a,b) +#define IDirectInputDevice7_GetObjectInfo(p,a,b,c) (p)->GetObjectInfo(a,b,c) +#define IDirectInputDevice7_GetDeviceInfo(p,a) (p)->GetDeviceInfo(a) +#define IDirectInputDevice7_RunControlPanel(p,a,b) (p)->RunControlPanel(a,b) +#define IDirectInputDevice7_Initialize(p,a,b,c) (p)->Initialize(a,b,c) +#define IDirectInputDevice7_CreateEffect(p,a,b,c,d) (p)->CreateEffect(a,b,c,d) +#define IDirectInputDevice7_EnumEffects(p,a,b,c) (p)->EnumEffects(a,b,c) +#define IDirectInputDevice7_GetEffectInfo(p,a,b) (p)->GetEffectInfo(a,b) +#define IDirectInputDevice7_GetForceFeedbackState(p,a) (p)->GetForceFeedbackState(a) +#define IDirectInputDevice7_SendForceFeedbackCommand(p,a) (p)->SendForceFeedbackCommand(a) +#define IDirectInputDevice7_EnumCreatedEffectObjects(p,a,b,c) (p)->EnumCreatedEffectObjects(a,b,c) +#define IDirectInputDevice7_Escape(p,a) (p)->Escape(a) +#define IDirectInputDevice7_Poll(p) (p)->Poll() +#define IDirectInputDevice7_SendDeviceData(p,a,b,c,d) (p)->SendDeviceData(a,b,c,d) +#define IDirectInputDevice7_EnumEffectsInFile(p,a,b,c,d) (p)->EnumEffectsInFile(a,b,c,d) +#define IDirectInputDevice7_WriteEffectToFile(p,a,b,c,d) (p)->WriteEffectToFile(a,b,c,d) +#endif + +#endif + +#endif + +#if(DIRECTINPUT_VERSION >= 0x0800) + +#ifndef DIJ_RINGZERO + +#undef INTERFACE +#define INTERFACE IDirectInputDevice8W + +DECLARE_INTERFACE_(IDirectInputDevice8W, IUnknown) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(GetCapabilities)(THIS_ LPDIDEVCAPS) PURE; + STDMETHOD(EnumObjects)(THIS_ LPDIENUMDEVICEOBJECTSCALLBACKW,LPVOID,DWORD) PURE; + STDMETHOD(GetProperty)(THIS_ REFGUID,LPDIPROPHEADER) PURE; + STDMETHOD(SetProperty)(THIS_ REFGUID,LPCDIPROPHEADER) PURE; + STDMETHOD(Acquire)(THIS) PURE; + STDMETHOD(Unacquire)(THIS) PURE; + STDMETHOD(GetDeviceState)(THIS_ DWORD,LPVOID) PURE; + STDMETHOD(GetDeviceData)(THIS_ DWORD,LPDIDEVICEOBJECTDATA,LPDWORD,DWORD) PURE; + STDMETHOD(SetDataFormat)(THIS_ LPCDIDATAFORMAT) PURE; + STDMETHOD(SetEventNotification)(THIS_ HANDLE) PURE; + STDMETHOD(SetCooperativeLevel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(GetObjectInfo)(THIS_ LPDIDEVICEOBJECTINSTANCEW,DWORD,DWORD) PURE; + STDMETHOD(GetDeviceInfo)(THIS_ LPDIDEVICEINSTANCEW) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD,REFGUID) PURE; + STDMETHOD(CreateEffect)(THIS_ REFGUID,LPCDIEFFECT,LPDIRECTINPUTEFFECT *,LPUNKNOWN) PURE; + STDMETHOD(EnumEffects)(THIS_ LPDIENUMEFFECTSCALLBACKW,LPVOID,DWORD) PURE; + STDMETHOD(GetEffectInfo)(THIS_ LPDIEFFECTINFOW,REFGUID) PURE; + STDMETHOD(GetForceFeedbackState)(THIS_ LPDWORD) PURE; + STDMETHOD(SendForceFeedbackCommand)(THIS_ DWORD) PURE; + STDMETHOD(EnumCreatedEffectObjects)(THIS_ LPDIENUMCREATEDEFFECTOBJECTSCALLBACK,LPVOID,DWORD) PURE; + STDMETHOD(Escape)(THIS_ LPDIEFFESCAPE) PURE; + STDMETHOD(Poll)(THIS) PURE; + STDMETHOD(SendDeviceData)(THIS_ DWORD,LPCDIDEVICEOBJECTDATA,LPDWORD,DWORD) PURE; + STDMETHOD(EnumEffectsInFile)(THIS_ LPCWSTR,LPDIENUMEFFECTSINFILECALLBACK,LPVOID,DWORD) PURE; + STDMETHOD(WriteEffectToFile)(THIS_ LPCWSTR,DWORD,LPDIFILEEFFECT,DWORD) PURE; + STDMETHOD(BuildActionMap)(THIS_ LPDIACTIONFORMATW,LPCWSTR,DWORD) PURE; + STDMETHOD(SetActionMap)(THIS_ LPDIACTIONFORMATW,LPCWSTR,DWORD) PURE; + STDMETHOD(GetImageInfo)(THIS_ LPDIDEVICEIMAGEINFOHEADERW) PURE; +}; + +typedef struct IDirectInputDevice8W *LPDIRECTINPUTDEVICE8W; + +#undef INTERFACE +#define INTERFACE IDirectInputDevice8A + +DECLARE_INTERFACE_(IDirectInputDevice8A, IUnknown) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(GetCapabilities)(THIS_ LPDIDEVCAPS) PURE; + STDMETHOD(EnumObjects)(THIS_ LPDIENUMDEVICEOBJECTSCALLBACKA,LPVOID,DWORD) PURE; + STDMETHOD(GetProperty)(THIS_ REFGUID,LPDIPROPHEADER) PURE; + STDMETHOD(SetProperty)(THIS_ REFGUID,LPCDIPROPHEADER) PURE; + STDMETHOD(Acquire)(THIS) PURE; + STDMETHOD(Unacquire)(THIS) PURE; + STDMETHOD(GetDeviceState)(THIS_ DWORD,LPVOID) PURE; + STDMETHOD(GetDeviceData)(THIS_ DWORD,LPDIDEVICEOBJECTDATA,LPDWORD,DWORD) PURE; + STDMETHOD(SetDataFormat)(THIS_ LPCDIDATAFORMAT) PURE; + STDMETHOD(SetEventNotification)(THIS_ HANDLE) PURE; + STDMETHOD(SetCooperativeLevel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(GetObjectInfo)(THIS_ LPDIDEVICEOBJECTINSTANCEA,DWORD,DWORD) PURE; + STDMETHOD(GetDeviceInfo)(THIS_ LPDIDEVICEINSTANCEA) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD,REFGUID) PURE; + STDMETHOD(CreateEffect)(THIS_ REFGUID,LPCDIEFFECT,LPDIRECTINPUTEFFECT *,LPUNKNOWN) PURE; + STDMETHOD(EnumEffects)(THIS_ LPDIENUMEFFECTSCALLBACKA,LPVOID,DWORD) PURE; + STDMETHOD(GetEffectInfo)(THIS_ LPDIEFFECTINFOA,REFGUID) PURE; + STDMETHOD(GetForceFeedbackState)(THIS_ LPDWORD) PURE; + STDMETHOD(SendForceFeedbackCommand)(THIS_ DWORD) PURE; + STDMETHOD(EnumCreatedEffectObjects)(THIS_ LPDIENUMCREATEDEFFECTOBJECTSCALLBACK,LPVOID,DWORD) PURE; + STDMETHOD(Escape)(THIS_ LPDIEFFESCAPE) PURE; + STDMETHOD(Poll)(THIS) PURE; + STDMETHOD(SendDeviceData)(THIS_ DWORD,LPCDIDEVICEOBJECTDATA,LPDWORD,DWORD) PURE; + STDMETHOD(EnumEffectsInFile)(THIS_ LPCSTR,LPDIENUMEFFECTSINFILECALLBACK,LPVOID,DWORD) PURE; + STDMETHOD(WriteEffectToFile)(THIS_ LPCSTR,DWORD,LPDIFILEEFFECT,DWORD) PURE; + STDMETHOD(BuildActionMap)(THIS_ LPDIACTIONFORMATA,LPCSTR,DWORD) PURE; + STDMETHOD(SetActionMap)(THIS_ LPDIACTIONFORMATA,LPCSTR,DWORD) PURE; + STDMETHOD(GetImageInfo)(THIS_ LPDIDEVICEIMAGEINFOHEADERA) PURE; +}; + +typedef struct IDirectInputDevice8A *LPDIRECTINPUTDEVICE8A; + +#ifdef UNICODE +#define IID_IDirectInputDevice8 IID_IDirectInputDevice8W +#define IDirectInputDevice8 IDirectInputDevice8W +#define IDirectInputDevice8Vtbl IDirectInputDevice8WVtbl +#else +#define IID_IDirectInputDevice8 IID_IDirectInputDevice8A +#define IDirectInputDevice8 IDirectInputDevice8A +#define IDirectInputDevice8Vtbl IDirectInputDevice8AVtbl +#endif +typedef struct IDirectInputDevice8 *LPDIRECTINPUTDEVICE8; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectInputDevice8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirectInputDevice8_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirectInputDevice8_Release(p) (p)->lpVtbl->Release(p) +#define IDirectInputDevice8_GetCapabilities(p,a) (p)->lpVtbl->GetCapabilities(p,a) +#define IDirectInputDevice8_EnumObjects(p,a,b,c) (p)->lpVtbl->EnumObjects(p,a,b,c) +#define IDirectInputDevice8_GetProperty(p,a,b) (p)->lpVtbl->GetProperty(p,a,b) +#define IDirectInputDevice8_SetProperty(p,a,b) (p)->lpVtbl->SetProperty(p,a,b) +#define IDirectInputDevice8_Acquire(p) (p)->lpVtbl->Acquire(p) +#define IDirectInputDevice8_Unacquire(p) (p)->lpVtbl->Unacquire(p) +#define IDirectInputDevice8_GetDeviceState(p,a,b) (p)->lpVtbl->GetDeviceState(p,a,b) +#define IDirectInputDevice8_GetDeviceData(p,a,b,c,d) (p)->lpVtbl->GetDeviceData(p,a,b,c,d) +#define IDirectInputDevice8_SetDataFormat(p,a) (p)->lpVtbl->SetDataFormat(p,a) +#define IDirectInputDevice8_SetEventNotification(p,a) (p)->lpVtbl->SetEventNotification(p,a) +#define IDirectInputDevice8_SetCooperativeLevel(p,a,b) (p)->lpVtbl->SetCooperativeLevel(p,a,b) +#define IDirectInputDevice8_GetObjectInfo(p,a,b,c) (p)->lpVtbl->GetObjectInfo(p,a,b,c) +#define IDirectInputDevice8_GetDeviceInfo(p,a) (p)->lpVtbl->GetDeviceInfo(p,a) +#define IDirectInputDevice8_RunControlPanel(p,a,b) (p)->lpVtbl->RunControlPanel(p,a,b) +#define IDirectInputDevice8_Initialize(p,a,b,c) (p)->lpVtbl->Initialize(p,a,b,c) +#define IDirectInputDevice8_CreateEffect(p,a,b,c,d) (p)->lpVtbl->CreateEffect(p,a,b,c,d) +#define IDirectInputDevice8_EnumEffects(p,a,b,c) (p)->lpVtbl->EnumEffects(p,a,b,c) +#define IDirectInputDevice8_GetEffectInfo(p,a,b) (p)->lpVtbl->GetEffectInfo(p,a,b) +#define IDirectInputDevice8_GetForceFeedbackState(p,a) (p)->lpVtbl->GetForceFeedbackState(p,a) +#define IDirectInputDevice8_SendForceFeedbackCommand(p,a) (p)->lpVtbl->SendForceFeedbackCommand(p,a) +#define IDirectInputDevice8_EnumCreatedEffectObjects(p,a,b,c) (p)->lpVtbl->EnumCreatedEffectObjects(p,a,b,c) +#define IDirectInputDevice8_Escape(p,a) (p)->lpVtbl->Escape(p,a) +#define IDirectInputDevice8_Poll(p) (p)->lpVtbl->Poll(p) +#define IDirectInputDevice8_SendDeviceData(p,a,b,c,d) (p)->lpVtbl->SendDeviceData(p,a,b,c,d) +#define IDirectInputDevice8_EnumEffectsInFile(p,a,b,c,d) (p)->lpVtbl->EnumEffectsInFile(p,a,b,c,d) +#define IDirectInputDevice8_WriteEffectToFile(p,a,b,c,d) (p)->lpVtbl->WriteEffectToFile(p,a,b,c,d) +#define IDirectInputDevice8_BuildActionMap(p,a,b,c) (p)->lpVtbl->BuildActionMap(p,a,b,c) +#define IDirectInputDevice8_SetActionMap(p,a,b,c) (p)->lpVtbl->SetActionMap(p,a,b,c) +#define IDirectInputDevice8_GetImageInfo(p,a) (p)->lpVtbl->GetImageInfo(p,a) +#else +#define IDirectInputDevice8_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirectInputDevice8_AddRef(p) (p)->AddRef() +#define IDirectInputDevice8_Release(p) (p)->Release() +#define IDirectInputDevice8_GetCapabilities(p,a) (p)->GetCapabilities(a) +#define IDirectInputDevice8_EnumObjects(p,a,b,c) (p)->EnumObjects(a,b,c) +#define IDirectInputDevice8_GetProperty(p,a,b) (p)->GetProperty(a,b) +#define IDirectInputDevice8_SetProperty(p,a,b) (p)->SetProperty(a,b) +#define IDirectInputDevice8_Acquire(p) (p)->Acquire() +#define IDirectInputDevice8_Unacquire(p) (p)->Unacquire() +#define IDirectInputDevice8_GetDeviceState(p,a,b) (p)->GetDeviceState(a,b) +#define IDirectInputDevice8_GetDeviceData(p,a,b,c,d) (p)->GetDeviceData(a,b,c,d) +#define IDirectInputDevice8_SetDataFormat(p,a) (p)->SetDataFormat(a) +#define IDirectInputDevice8_SetEventNotification(p,a) (p)->SetEventNotification(a) +#define IDirectInputDevice8_SetCooperativeLevel(p,a,b) (p)->SetCooperativeLevel(a,b) +#define IDirectInputDevice8_GetObjectInfo(p,a,b,c) (p)->GetObjectInfo(a,b,c) +#define IDirectInputDevice8_GetDeviceInfo(p,a) (p)->GetDeviceInfo(a) +#define IDirectInputDevice8_RunControlPanel(p,a,b) (p)->RunControlPanel(a,b) +#define IDirectInputDevice8_Initialize(p,a,b,c) (p)->Initialize(a,b,c) +#define IDirectInputDevice8_CreateEffect(p,a,b,c,d) (p)->CreateEffect(a,b,c,d) +#define IDirectInputDevice8_EnumEffects(p,a,b,c) (p)->EnumEffects(a,b,c) +#define IDirectInputDevice8_GetEffectInfo(p,a,b) (p)->GetEffectInfo(a,b) +#define IDirectInputDevice8_GetForceFeedbackState(p,a) (p)->GetForceFeedbackState(a) +#define IDirectInputDevice8_SendForceFeedbackCommand(p,a) (p)->SendForceFeedbackCommand(a) +#define IDirectInputDevice8_EnumCreatedEffectObjects(p,a,b,c) (p)->EnumCreatedEffectObjects(a,b,c) +#define IDirectInputDevice8_Escape(p,a) (p)->Escape(a) +#define IDirectInputDevice8_Poll(p) (p)->Poll() +#define IDirectInputDevice8_SendDeviceData(p,a,b,c,d) (p)->SendDeviceData(a,b,c,d) +#define IDirectInputDevice8_EnumEffectsInFile(p,a,b,c,d) (p)->EnumEffectsInFile(a,b,c,d) +#define IDirectInputDevice8_WriteEffectToFile(p,a,b,c,d) (p)->WriteEffectToFile(a,b,c,d) +#define IDirectInputDevice8_BuildActionMap(p,a,b,c) (p)->BuildActionMap(a,b,c) +#define IDirectInputDevice8_SetActionMap(p,a,b,c) (p)->SetActionMap(a,b,c) +#define IDirectInputDevice8_GetImageInfo(p,a) (p)->GetImageInfo(a) +#endif + +#endif + +#endif + +#ifndef DIJ_RINGZERO + +typedef struct _DIMOUSESTATE { + LONG lX; + LONG lY; + LONG lZ; + BYTE rgbButtons[4]; +} DIMOUSESTATE, *LPDIMOUSESTATE; + +#if DIRECTINPUT_VERSION >= 0x0700 +typedef struct _DIMOUSESTATE2 { + LONG lX; + LONG lY; + LONG lZ; + BYTE rgbButtons[8]; +} DIMOUSESTATE2, *LPDIMOUSESTATE2; +#endif + +#define DIMOFS_X FIELD_OFFSET(DIMOUSESTATE, lX) +#define DIMOFS_Y FIELD_OFFSET(DIMOUSESTATE, lY) +#define DIMOFS_Z FIELD_OFFSET(DIMOUSESTATE, lZ) +#define DIMOFS_BUTTON0 (FIELD_OFFSET(DIMOUSESTATE, rgbButtons) + 0) +#define DIMOFS_BUTTON1 (FIELD_OFFSET(DIMOUSESTATE, rgbButtons) + 1) +#define DIMOFS_BUTTON2 (FIELD_OFFSET(DIMOUSESTATE, rgbButtons) + 2) +#define DIMOFS_BUTTON3 (FIELD_OFFSET(DIMOUSESTATE, rgbButtons) + 3) +#if (DIRECTINPUT_VERSION >= 0x0700) +#define DIMOFS_BUTTON4 (FIELD_OFFSET(DIMOUSESTATE2, rgbButtons) + 4) +#define DIMOFS_BUTTON5 (FIELD_OFFSET(DIMOUSESTATE2, rgbButtons) + 5) +#define DIMOFS_BUTTON6 (FIELD_OFFSET(DIMOUSESTATE2, rgbButtons) + 6) +#define DIMOFS_BUTTON7 (FIELD_OFFSET(DIMOUSESTATE2, rgbButtons) + 7) +#endif +#endif + +#ifndef DIJ_RINGZERO + +#define DIK_ESCAPE 0x01 +#define DIK_1 0x02 +#define DIK_2 0x03 +#define DIK_3 0x04 +#define DIK_4 0x05 +#define DIK_5 0x06 +#define DIK_6 0x07 +#define DIK_7 0x08 +#define DIK_8 0x09 +#define DIK_9 0x0A +#define DIK_0 0x0B +#define DIK_MINUS 0x0C +#define DIK_EQUALS 0x0D +#define DIK_BACK 0x0E +#define DIK_TAB 0x0F +#define DIK_Q 0x10 +#define DIK_W 0x11 +#define DIK_E 0x12 +#define DIK_R 0x13 +#define DIK_T 0x14 +#define DIK_Y 0x15 +#define DIK_U 0x16 +#define DIK_I 0x17 +#define DIK_O 0x18 +#define DIK_P 0x19 +#define DIK_LBRACKET 0x1A +#define DIK_RBRACKET 0x1B +#define DIK_RETURN 0x1C +#define DIK_LCONTROL 0x1D +#define DIK_A 0x1E +#define DIK_S 0x1F +#define DIK_D 0x20 +#define DIK_F 0x21 +#define DIK_G 0x22 +#define DIK_H 0x23 +#define DIK_J 0x24 +#define DIK_K 0x25 +#define DIK_L 0x26 +#define DIK_SEMICOLON 0x27 +#define DIK_APOSTROPHE 0x28 +#define DIK_GRAVE 0x29 +#define DIK_LSHIFT 0x2A +#define DIK_BACKSLASH 0x2B +#define DIK_Z 0x2C +#define DIK_X 0x2D +#define DIK_C 0x2E +#define DIK_V 0x2F +#define DIK_B 0x30 +#define DIK_N 0x31 +#define DIK_M 0x32 +#define DIK_COMMA 0x33 +#define DIK_PERIOD 0x34 +#define DIK_SLASH 0x35 +#define DIK_RSHIFT 0x36 +#define DIK_MULTIPLY 0x37 +#define DIK_LMENU 0x38 +#define DIK_SPACE 0x39 +#define DIK_CAPITAL 0x3A +#define DIK_F1 0x3B +#define DIK_F2 0x3C +#define DIK_F3 0x3D +#define DIK_F4 0x3E +#define DIK_F5 0x3F +#define DIK_F6 0x40 +#define DIK_F7 0x41 +#define DIK_F8 0x42 +#define DIK_F9 0x43 +#define DIK_F10 0x44 +#define DIK_NUMLOCK 0x45 +#define DIK_SCROLL 0x46 +#define DIK_NUMPAD7 0x47 +#define DIK_NUMPAD8 0x48 +#define DIK_NUMPAD9 0x49 +#define DIK_SUBTRACT 0x4A +#define DIK_NUMPAD4 0x4B +#define DIK_NUMPAD5 0x4C +#define DIK_NUMPAD6 0x4D +#define DIK_ADD 0x4E +#define DIK_NUMPAD1 0x4F +#define DIK_NUMPAD2 0x50 +#define DIK_NUMPAD3 0x51 +#define DIK_NUMPAD0 0x52 +#define DIK_DECIMAL 0x53 +#define DIK_OEM_102 0x56 +#define DIK_F11 0x57 +#define DIK_F12 0x58 +#define DIK_F13 0x64 +#define DIK_F14 0x65 +#define DIK_F15 0x66 +#define DIK_KANA 0x70 +#define DIK_ABNT_C1 0x73 +#define DIK_CONVERT 0x79 +#define DIK_NOCONVERT 0x7B +#define DIK_YEN 0x7D +#define DIK_ABNT_C2 0x7E +#define DIK_NUMPADEQUALS 0x8D +#define DIK_PREVTRACK 0x90 +#define DIK_AT 0x91 +#define DIK_COLON 0x92 +#define DIK_UNDERLINE 0x93 +#define DIK_KANJI 0x94 +#define DIK_STOP 0x95 +#define DIK_AX 0x96 +#define DIK_UNLABELED 0x97 +#define DIK_NEXTTRACK 0x99 +#define DIK_NUMPADENTER 0x9C +#define DIK_RCONTROL 0x9D +#define DIK_MUTE 0xA0 +#define DIK_CALCULATOR 0xA1 +#define DIK_PLAYPAUSE 0xA2 +#define DIK_MEDIASTOP 0xA4 +#define DIK_VOLUMEDOWN 0xAE +#define DIK_VOLUMEUP 0xB0 +#define DIK_WEBHOME 0xB2 +#define DIK_NUMPADCOMMA 0xB3 +#define DIK_DIVIDE 0xB5 +#define DIK_SYSRQ 0xB7 +#define DIK_RMENU 0xB8 +#define DIK_PAUSE 0xC5 +#define DIK_HOME 0xC7 +#define DIK_UP 0xC8 +#define DIK_PRIOR 0xC9 +#define DIK_LEFT 0xCB +#define DIK_RIGHT 0xCD +#define DIK_END 0xCF +#define DIK_DOWN 0xD0 +#define DIK_NEXT 0xD1 +#define DIK_INSERT 0xD2 +#define DIK_DELETE 0xD3 +#define DIK_LWIN 0xDB +#define DIK_RWIN 0xDC +#define DIK_APPS 0xDD +#define DIK_POWER 0xDE +#define DIK_SLEEP 0xDF +#define DIK_WAKE 0xE3 +#define DIK_WEBSEARCH 0xE5 +#define DIK_WEBFAVORITES 0xE6 +#define DIK_WEBREFRESH 0xE7 +#define DIK_WEBSTOP 0xE8 +#define DIK_WEBFORWARD 0xE9 +#define DIK_WEBBACK 0xEA +#define DIK_MYCOMPUTER 0xEB +#define DIK_MAIL 0xEC +#define DIK_MEDIASELECT 0xED + +#define DIK_BACKSPACE DIK_BACK +#define DIK_NUMPADSTAR DIK_MULTIPLY +#define DIK_LALT DIK_LMENU +#define DIK_CAPSLOCK DIK_CAPITAL +#define DIK_NUMPADMINUS DIK_SUBTRACT +#define DIK_NUMPADPLUS DIK_ADD +#define DIK_NUMPADPERIOD DIK_DECIMAL +#define DIK_NUMPADSLASH DIK_DIVIDE +#define DIK_RALT DIK_RMENU +#define DIK_UPARROW DIK_UP +#define DIK_PGUP DIK_PRIOR +#define DIK_LEFTARROW DIK_LEFT +#define DIK_RIGHTARROW DIK_RIGHT +#define DIK_DOWNARROW DIK_DOWN +#define DIK_PGDN DIK_NEXT + +#define DIK_CIRCUMFLEX DIK_PREVTRACK + +#endif + +#ifndef DIJ_RINGZERO + +typedef struct DIJOYSTATE { + LONG lX; + LONG lY; + LONG lZ; + LONG lRx; + LONG lRy; + LONG lRz; + LONG rglSlider[2]; + DWORD rgdwPOV[4]; + BYTE rgbButtons[32]; +} DIJOYSTATE, *LPDIJOYSTATE; + +typedef struct DIJOYSTATE2 { + LONG lX; + LONG lY; + LONG lZ; + LONG lRx; + LONG lRy; + LONG lRz; + LONG rglSlider[2]; + DWORD rgdwPOV[4]; + BYTE rgbButtons[128]; + LONG lVX; + LONG lVY; + LONG lVZ; + LONG lVRx; + LONG lVRy; + LONG lVRz; + LONG rglVSlider[2]; + LONG lAX; + LONG lAY; + LONG lAZ; + LONG lARx; + LONG lARy; + LONG lARz; + LONG rglASlider[2]; + LONG lFX; + LONG lFY; + LONG lFZ; + LONG lFRx; + LONG lFRy; + LONG lFRz; + LONG rglFSlider[2]; +} DIJOYSTATE2, *LPDIJOYSTATE2; + +#define DIJOFS_X FIELD_OFFSET(DIJOYSTATE, lX) +#define DIJOFS_Y FIELD_OFFSET(DIJOYSTATE, lY) +#define DIJOFS_Z FIELD_OFFSET(DIJOYSTATE, lZ) +#define DIJOFS_RX FIELD_OFFSET(DIJOYSTATE, lRx) +#define DIJOFS_RY FIELD_OFFSET(DIJOYSTATE, lRy) +#define DIJOFS_RZ FIELD_OFFSET(DIJOYSTATE, lRz) +#define DIJOFS_SLIDER(n) (FIELD_OFFSET(DIJOYSTATE, rglSlider) + \ + (n) * sizeof(LONG)) +#define DIJOFS_POV(n) (FIELD_OFFSET(DIJOYSTATE, rgdwPOV) + \ + (n) * sizeof(DWORD)) +#define DIJOFS_BUTTON(n) (FIELD_OFFSET(DIJOYSTATE, rgbButtons) + (n)) +#define DIJOFS_BUTTON0 DIJOFS_BUTTON(0) +#define DIJOFS_BUTTON1 DIJOFS_BUTTON(1) +#define DIJOFS_BUTTON2 DIJOFS_BUTTON(2) +#define DIJOFS_BUTTON3 DIJOFS_BUTTON(3) +#define DIJOFS_BUTTON4 DIJOFS_BUTTON(4) +#define DIJOFS_BUTTON5 DIJOFS_BUTTON(5) +#define DIJOFS_BUTTON6 DIJOFS_BUTTON(6) +#define DIJOFS_BUTTON7 DIJOFS_BUTTON(7) +#define DIJOFS_BUTTON8 DIJOFS_BUTTON(8) +#define DIJOFS_BUTTON9 DIJOFS_BUTTON(9) +#define DIJOFS_BUTTON10 DIJOFS_BUTTON(10) +#define DIJOFS_BUTTON11 DIJOFS_BUTTON(11) +#define DIJOFS_BUTTON12 DIJOFS_BUTTON(12) +#define DIJOFS_BUTTON13 DIJOFS_BUTTON(13) +#define DIJOFS_BUTTON14 DIJOFS_BUTTON(14) +#define DIJOFS_BUTTON15 DIJOFS_BUTTON(15) +#define DIJOFS_BUTTON16 DIJOFS_BUTTON(16) +#define DIJOFS_BUTTON17 DIJOFS_BUTTON(17) +#define DIJOFS_BUTTON18 DIJOFS_BUTTON(18) +#define DIJOFS_BUTTON19 DIJOFS_BUTTON(19) +#define DIJOFS_BUTTON20 DIJOFS_BUTTON(20) +#define DIJOFS_BUTTON21 DIJOFS_BUTTON(21) +#define DIJOFS_BUTTON22 DIJOFS_BUTTON(22) +#define DIJOFS_BUTTON23 DIJOFS_BUTTON(23) +#define DIJOFS_BUTTON24 DIJOFS_BUTTON(24) +#define DIJOFS_BUTTON25 DIJOFS_BUTTON(25) +#define DIJOFS_BUTTON26 DIJOFS_BUTTON(26) +#define DIJOFS_BUTTON27 DIJOFS_BUTTON(27) +#define DIJOFS_BUTTON28 DIJOFS_BUTTON(28) +#define DIJOFS_BUTTON29 DIJOFS_BUTTON(29) +#define DIJOFS_BUTTON30 DIJOFS_BUTTON(30) +#define DIJOFS_BUTTON31 DIJOFS_BUTTON(31) + +#endif + +#ifndef DIJ_RINGZERO + +#define DIENUM_STOP 0 +#define DIENUM_CONTINUE 1 + +typedef BOOL (FAR PASCAL * LPDIENUMDEVICESCALLBACKA)(LPCDIDEVICEINSTANCEA, LPVOID); +typedef BOOL (FAR PASCAL * LPDIENUMDEVICESCALLBACKW)(LPCDIDEVICEINSTANCEW, LPVOID); +#ifdef UNICODE +#define LPDIENUMDEVICESCALLBACK LPDIENUMDEVICESCALLBACKW +#else +#define LPDIENUMDEVICESCALLBACK LPDIENUMDEVICESCALLBACKA +#endif +typedef BOOL (FAR PASCAL * LPDICONFIGUREDEVICESCALLBACK)(IUnknown FAR *, LPVOID); + +#define DIEDFL_ALLDEVICES 0x00000000 +#define DIEDFL_ATTACHEDONLY 0x00000001 +#if(DIRECTINPUT_VERSION >= 0x0500) +#define DIEDFL_FORCEFEEDBACK 0x00000100 +#endif +#if(DIRECTINPUT_VERSION >= 0x050a) +#define DIEDFL_INCLUDEALIASES 0x00010000 +#define DIEDFL_INCLUDEPHANTOMS 0x00020000 +#endif +#if(DIRECTINPUT_VERSION >= 0x0800) +#define DIEDFL_INCLUDEHIDDEN 0x00040000 +#endif + +#if(DIRECTINPUT_VERSION >= 0x0800) +typedef BOOL (FAR PASCAL * LPDIENUMDEVICESBYSEMANTICSCBA)(LPCDIDEVICEINSTANCEA, LPDIRECTINPUTDEVICE8A, DWORD, DWORD, LPVOID); +typedef BOOL (FAR PASCAL * LPDIENUMDEVICESBYSEMANTICSCBW)(LPCDIDEVICEINSTANCEW, LPDIRECTINPUTDEVICE8W, DWORD, DWORD, LPVOID); +#ifdef UNICODE +#define LPDIENUMDEVICESBYSEMANTICSCB LPDIENUMDEVICESBYSEMANTICSCBW +#else +#define LPDIENUMDEVICESBYSEMANTICSCB LPDIENUMDEVICESBYSEMANTICSCBA +#endif +#endif + +#if(DIRECTINPUT_VERSION >= 0x0800) +#define DIEDBS_MAPPEDPRI1 0x00000001 +#define DIEDBS_MAPPEDPRI2 0x00000002 +#define DIEDBS_RECENTDEVICE 0x00000010 +#define DIEDBS_NEWDEVICE 0x00000020 +#endif + +#if(DIRECTINPUT_VERSION >= 0x0800) +#define DIEDBSFL_ATTACHEDONLY 0x00000000 +#define DIEDBSFL_THISUSER 0x00000010 +#define DIEDBSFL_FORCEFEEDBACK DIEDFL_FORCEFEEDBACK +#define DIEDBSFL_AVAILABLEDEVICES 0x00001000 +#define DIEDBSFL_MULTIMICEKEYBOARDS 0x00002000 +#define DIEDBSFL_NONGAMINGDEVICES 0x00004000 +#define DIEDBSFL_VALID 0x00007110 +#endif + +#undef INTERFACE +#define INTERFACE IDirectInputW + +DECLARE_INTERFACE_(IDirectInputW, IUnknown) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(CreateDevice)(THIS_ REFGUID,LPDIRECTINPUTDEVICEW *,LPUNKNOWN) PURE; + STDMETHOD(EnumDevices)(THIS_ DWORD,LPDIENUMDEVICESCALLBACKW,LPVOID,DWORD) PURE; + STDMETHOD(GetDeviceStatus)(THIS_ REFGUID) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD) PURE; +}; + +typedef struct IDirectInputW *LPDIRECTINPUTW; + +#undef INTERFACE +#define INTERFACE IDirectInputA + +DECLARE_INTERFACE_(IDirectInputA, IUnknown) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(CreateDevice)(THIS_ REFGUID,LPDIRECTINPUTDEVICEA *,LPUNKNOWN) PURE; + STDMETHOD(EnumDevices)(THIS_ DWORD,LPDIENUMDEVICESCALLBACKA,LPVOID,DWORD) PURE; + STDMETHOD(GetDeviceStatus)(THIS_ REFGUID) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD) PURE; +}; + +typedef struct IDirectInputA *LPDIRECTINPUTA; + +#ifdef UNICODE +#define IID_IDirectInput IID_IDirectInputW +#define IDirectInput IDirectInputW +#define IDirectInputVtbl IDirectInputWVtbl +#else +#define IID_IDirectInput IID_IDirectInputA +#define IDirectInput IDirectInputA +#define IDirectInputVtbl IDirectInputAVtbl +#endif +typedef struct IDirectInput *LPDIRECTINPUT; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectInput_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirectInput_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirectInput_Release(p) (p)->lpVtbl->Release(p) +#define IDirectInput_CreateDevice(p,a,b,c) (p)->lpVtbl->CreateDevice(p,a,b,c) +#define IDirectInput_EnumDevices(p,a,b,c,d) (p)->lpVtbl->EnumDevices(p,a,b,c,d) +#define IDirectInput_GetDeviceStatus(p,a) (p)->lpVtbl->GetDeviceStatus(p,a) +#define IDirectInput_RunControlPanel(p,a,b) (p)->lpVtbl->RunControlPanel(p,a,b) +#define IDirectInput_Initialize(p,a,b) (p)->lpVtbl->Initialize(p,a,b) +#else +#define IDirectInput_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirectInput_AddRef(p) (p)->AddRef() +#define IDirectInput_Release(p) (p)->Release() +#define IDirectInput_CreateDevice(p,a,b,c) (p)->CreateDevice(a,b,c) +#define IDirectInput_EnumDevices(p,a,b,c,d) (p)->EnumDevices(a,b,c,d) +#define IDirectInput_GetDeviceStatus(p,a) (p)->GetDeviceStatus(a) +#define IDirectInput_RunControlPanel(p,a,b) (p)->RunControlPanel(a,b) +#define IDirectInput_Initialize(p,a,b) (p)->Initialize(a,b) +#endif + +#undef INTERFACE +#define INTERFACE IDirectInput2W + +DECLARE_INTERFACE_(IDirectInput2W, IDirectInputW) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(CreateDevice)(THIS_ REFGUID,LPDIRECTINPUTDEVICEW *,LPUNKNOWN) PURE; + STDMETHOD(EnumDevices)(THIS_ DWORD,LPDIENUMDEVICESCALLBACKW,LPVOID,DWORD) PURE; + STDMETHOD(GetDeviceStatus)(THIS_ REFGUID) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD) PURE; + + STDMETHOD(FindDevice)(THIS_ REFGUID,LPCWSTR,LPGUID) PURE; +}; + +typedef struct IDirectInput2W *LPDIRECTINPUT2W; + +#undef INTERFACE +#define INTERFACE IDirectInput2A + +DECLARE_INTERFACE_(IDirectInput2A, IDirectInputA) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(CreateDevice)(THIS_ REFGUID,LPDIRECTINPUTDEVICEA *,LPUNKNOWN) PURE; + STDMETHOD(EnumDevices)(THIS_ DWORD,LPDIENUMDEVICESCALLBACKA,LPVOID,DWORD) PURE; + STDMETHOD(GetDeviceStatus)(THIS_ REFGUID) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD) PURE; + + STDMETHOD(FindDevice)(THIS_ REFGUID,LPCSTR,LPGUID) PURE; +}; + +typedef struct IDirectInput2A *LPDIRECTINPUT2A; + +#ifdef UNICODE +#define IID_IDirectInput2 IID_IDirectInput2W +#define IDirectInput2 IDirectInput2W +#define IDirectInput2Vtbl IDirectInput2WVtbl +#else +#define IID_IDirectInput2 IID_IDirectInput2A +#define IDirectInput2 IDirectInput2A +#define IDirectInput2Vtbl IDirectInput2AVtbl +#endif +typedef struct IDirectInput2 *LPDIRECTINPUT2; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectInput2_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirectInput2_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirectInput2_Release(p) (p)->lpVtbl->Release(p) +#define IDirectInput2_CreateDevice(p,a,b,c) (p)->lpVtbl->CreateDevice(p,a,b,c) +#define IDirectInput2_EnumDevices(p,a,b,c,d) (p)->lpVtbl->EnumDevices(p,a,b,c,d) +#define IDirectInput2_GetDeviceStatus(p,a) (p)->lpVtbl->GetDeviceStatus(p,a) +#define IDirectInput2_RunControlPanel(p,a,b) (p)->lpVtbl->RunControlPanel(p,a,b) +#define IDirectInput2_Initialize(p,a,b) (p)->lpVtbl->Initialize(p,a,b) +#define IDirectInput2_FindDevice(p,a,b,c) (p)->lpVtbl->FindDevice(p,a,b,c) +#else +#define IDirectInput2_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirectInput2_AddRef(p) (p)->AddRef() +#define IDirectInput2_Release(p) (p)->Release() +#define IDirectInput2_CreateDevice(p,a,b,c) (p)->CreateDevice(a,b,c) +#define IDirectInput2_EnumDevices(p,a,b,c,d) (p)->EnumDevices(a,b,c,d) +#define IDirectInput2_GetDeviceStatus(p,a) (p)->GetDeviceStatus(a) +#define IDirectInput2_RunControlPanel(p,a,b) (p)->RunControlPanel(a,b) +#define IDirectInput2_Initialize(p,a,b) (p)->Initialize(a,b) +#define IDirectInput2_FindDevice(p,a,b,c) (p)->FindDevice(a,b,c) +#endif + +#undef INTERFACE +#define INTERFACE IDirectInput7W + +DECLARE_INTERFACE_(IDirectInput7W, IDirectInput2W) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(CreateDevice)(THIS_ REFGUID,LPDIRECTINPUTDEVICEW *,LPUNKNOWN) PURE; + STDMETHOD(EnumDevices)(THIS_ DWORD,LPDIENUMDEVICESCALLBACKW,LPVOID,DWORD) PURE; + STDMETHOD(GetDeviceStatus)(THIS_ REFGUID) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD) PURE; + STDMETHOD(FindDevice)(THIS_ REFGUID,LPCWSTR,LPGUID) PURE; + + STDMETHOD(CreateDeviceEx)(THIS_ REFGUID,REFIID,LPVOID *,LPUNKNOWN) PURE; +}; + +typedef struct IDirectInput7W *LPDIRECTINPUT7W; + +#undef INTERFACE +#define INTERFACE IDirectInput7A + +DECLARE_INTERFACE_(IDirectInput7A, IDirectInput2A) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(CreateDevice)(THIS_ REFGUID,LPDIRECTINPUTDEVICEA *,LPUNKNOWN) PURE; + STDMETHOD(EnumDevices)(THIS_ DWORD,LPDIENUMDEVICESCALLBACKA,LPVOID,DWORD) PURE; + STDMETHOD(GetDeviceStatus)(THIS_ REFGUID) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD) PURE; + STDMETHOD(FindDevice)(THIS_ REFGUID,LPCSTR,LPGUID) PURE; + + STDMETHOD(CreateDeviceEx)(THIS_ REFGUID,REFIID,LPVOID *,LPUNKNOWN) PURE; +}; + +typedef struct IDirectInput7A *LPDIRECTINPUT7A; + +#ifdef UNICODE +#define IID_IDirectInput7 IID_IDirectInput7W +#define IDirectInput7 IDirectInput7W +#define IDirectInput7Vtbl IDirectInput7WVtbl +#else +#define IID_IDirectInput7 IID_IDirectInput7A +#define IDirectInput7 IDirectInput7A +#define IDirectInput7Vtbl IDirectInput7AVtbl +#endif +typedef struct IDirectInput7 *LPDIRECTINPUT7; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectInput7_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirectInput7_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirectInput7_Release(p) (p)->lpVtbl->Release(p) +#define IDirectInput7_CreateDevice(p,a,b,c) (p)->lpVtbl->CreateDevice(p,a,b,c) +#define IDirectInput7_EnumDevices(p,a,b,c,d) (p)->lpVtbl->EnumDevices(p,a,b,c,d) +#define IDirectInput7_GetDeviceStatus(p,a) (p)->lpVtbl->GetDeviceStatus(p,a) +#define IDirectInput7_RunControlPanel(p,a,b) (p)->lpVtbl->RunControlPanel(p,a,b) +#define IDirectInput7_Initialize(p,a,b) (p)->lpVtbl->Initialize(p,a,b) +#define IDirectInput7_FindDevice(p,a,b,c) (p)->lpVtbl->FindDevice(p,a,b,c) +#define IDirectInput7_CreateDeviceEx(p,a,b,c,d) (p)->lpVtbl->CreateDeviceEx(p,a,b,c,d) +#else +#define IDirectInput7_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirectInput7_AddRef(p) (p)->AddRef() +#define IDirectInput7_Release(p) (p)->Release() +#define IDirectInput7_CreateDevice(p,a,b,c) (p)->CreateDevice(a,b,c) +#define IDirectInput7_EnumDevices(p,a,b,c,d) (p)->EnumDevices(a,b,c,d) +#define IDirectInput7_GetDeviceStatus(p,a) (p)->GetDeviceStatus(a) +#define IDirectInput7_RunControlPanel(p,a,b) (p)->RunControlPanel(a,b) +#define IDirectInput7_Initialize(p,a,b) (p)->Initialize(a,b) +#define IDirectInput7_FindDevice(p,a,b,c) (p)->FindDevice(a,b,c) +#define IDirectInput7_CreateDeviceEx(p,a,b,c,d) (p)->CreateDeviceEx(a,b,c,d) +#endif + +#if(DIRECTINPUT_VERSION >= 0x0800) +#undef INTERFACE +#define INTERFACE IDirectInput8W + +DECLARE_INTERFACE_(IDirectInput8W, IUnknown) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(CreateDevice)(THIS_ REFGUID,LPDIRECTINPUTDEVICE8W *,LPUNKNOWN) PURE; + STDMETHOD(EnumDevices)(THIS_ DWORD,LPDIENUMDEVICESCALLBACKW,LPVOID,DWORD) PURE; + STDMETHOD(GetDeviceStatus)(THIS_ REFGUID) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD) PURE; + STDMETHOD(FindDevice)(THIS_ REFGUID,LPCWSTR,LPGUID) PURE; + STDMETHOD(EnumDevicesBySemantics)(THIS_ LPCWSTR,LPDIACTIONFORMATW,LPDIENUMDEVICESBYSEMANTICSCBW,LPVOID,DWORD) PURE; + STDMETHOD(ConfigureDevices)(THIS_ LPDICONFIGUREDEVICESCALLBACK,LPDICONFIGUREDEVICESPARAMSW,DWORD,LPVOID) PURE; +}; + +typedef struct IDirectInput8W *LPDIRECTINPUT8W; + +#undef INTERFACE +#define INTERFACE IDirectInput8A + +DECLARE_INTERFACE_(IDirectInput8A, IUnknown) +{ + + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + STDMETHOD(CreateDevice)(THIS_ REFGUID,LPDIRECTINPUTDEVICE8A *,LPUNKNOWN) PURE; + STDMETHOD(EnumDevices)(THIS_ DWORD,LPDIENUMDEVICESCALLBACKA,LPVOID,DWORD) PURE; + STDMETHOD(GetDeviceStatus)(THIS_ REFGUID) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD) PURE; + STDMETHOD(FindDevice)(THIS_ REFGUID,LPCSTR,LPGUID) PURE; + STDMETHOD(EnumDevicesBySemantics)(THIS_ LPCSTR,LPDIACTIONFORMATA,LPDIENUMDEVICESBYSEMANTICSCBA,LPVOID,DWORD) PURE; + STDMETHOD(ConfigureDevices)(THIS_ LPDICONFIGUREDEVICESCALLBACK,LPDICONFIGUREDEVICESPARAMSA,DWORD,LPVOID) PURE; +}; + +typedef struct IDirectInput8A *LPDIRECTINPUT8A; + +#ifdef UNICODE +#define IID_IDirectInput8 IID_IDirectInput8W +#define IDirectInput8 IDirectInput8W +#define IDirectInput8Vtbl IDirectInput8WVtbl +#else +#define IID_IDirectInput8 IID_IDirectInput8A +#define IDirectInput8 IDirectInput8A +#define IDirectInput8Vtbl IDirectInput8AVtbl +#endif +typedef struct IDirectInput8 *LPDIRECTINPUT8; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirectInput8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirectInput8_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirectInput8_Release(p) (p)->lpVtbl->Release(p) +#define IDirectInput8_CreateDevice(p,a,b,c) (p)->lpVtbl->CreateDevice(p,a,b,c) +#define IDirectInput8_EnumDevices(p,a,b,c,d) (p)->lpVtbl->EnumDevices(p,a,b,c,d) +#define IDirectInput8_GetDeviceStatus(p,a) (p)->lpVtbl->GetDeviceStatus(p,a) +#define IDirectInput8_RunControlPanel(p,a,b) (p)->lpVtbl->RunControlPanel(p,a,b) +#define IDirectInput8_Initialize(p,a,b) (p)->lpVtbl->Initialize(p,a,b) +#define IDirectInput8_FindDevice(p,a,b,c) (p)->lpVtbl->FindDevice(p,a,b,c) +#define IDirectInput8_EnumDevicesBySemantics(p,a,b,c,d,e) (p)->lpVtbl->EnumDevicesBySemantics(p,a,b,c,d,e) +#define IDirectInput8_ConfigureDevices(p,a,b,c,d) (p)->lpVtbl->ConfigureDevices(p,a,b,c,d) +#else +#define IDirectInput8_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirectInput8_AddRef(p) (p)->AddRef() +#define IDirectInput8_Release(p) (p)->Release() +#define IDirectInput8_CreateDevice(p,a,b,c) (p)->CreateDevice(a,b,c) +#define IDirectInput8_EnumDevices(p,a,b,c,d) (p)->EnumDevices(a,b,c,d) +#define IDirectInput8_GetDeviceStatus(p,a) (p)->GetDeviceStatus(a) +#define IDirectInput8_RunControlPanel(p,a,b) (p)->RunControlPanel(a,b) +#define IDirectInput8_Initialize(p,a,b) (p)->Initialize(a,b) +#define IDirectInput8_FindDevice(p,a,b,c) (p)->FindDevice(a,b,c) +#define IDirectInput8_EnumDevicesBySemantics(p,a,b,c,d,e) (p)->EnumDevicesBySemantics(a,b,c,d,e) +#define IDirectInput8_ConfigureDevices(p,a,b,c,d) (p)->ConfigureDevices(a,b,c,d) +#endif +#endif + +#if DIRECTINPUT_VERSION > 0x0700 + +extern HRESULT WINAPI DirectInput8Create(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID *ppvOut, LPUNKNOWN punkOuter); + +#else +extern HRESULT WINAPI DirectInputCreateA(HINSTANCE hinst, DWORD dwVersion, LPDIRECTINPUTA *ppDI, LPUNKNOWN punkOuter); +extern HRESULT WINAPI DirectInputCreateW(HINSTANCE hinst, DWORD dwVersion, LPDIRECTINPUTW *ppDI, LPUNKNOWN punkOuter); +#ifdef UNICODE +#define DirectInputCreate DirectInputCreateW +#else +#define DirectInputCreate DirectInputCreateA +#endif + +extern HRESULT WINAPI DirectInputCreateEx(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID *ppvOut, LPUNKNOWN punkOuter); + +#endif + +#endif + +#define DI_OK S_OK + +#define DI_NOTATTACHED S_FALSE + +#define DI_BUFFEROVERFLOW S_FALSE + +#define DI_PROPNOEFFECT S_FALSE + +#define DI_NOEFFECT S_FALSE + +#define DI_POLLEDDEVICE ((HRESULT)0x00000002L) + +#define DI_DOWNLOADSKIPPED ((HRESULT)0x00000003L) + +#define DI_EFFECTRESTARTED ((HRESULT)0x00000004L) + +#define DI_TRUNCATED ((HRESULT)0x00000008L) + +#define DI_SETTINGSNOTSAVED ((HRESULT)0x0000000BL) + +#define DI_TRUNCATEDANDRESTARTED ((HRESULT)0x0000000CL) + +#define DI_WRITEPROTECT ((HRESULT)0x00000013L) + +#define DIERR_OLDDIRECTINPUTVERSION \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_OLD_WIN_VERSION) + +#define DIERR_BETADIRECTINPUTVERSION \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_RMODE_APP) + +#define DIERR_BADDRIVERVER \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_BAD_DRIVER_LEVEL) + +#define DIERR_DEVICENOTREG REGDB_E_CLASSNOTREG + +#define DIERR_NOTFOUND \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_FILE_NOT_FOUND) + +#define DIERR_OBJECTNOTFOUND \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_FILE_NOT_FOUND) + +#define DIERR_INVALIDPARAM E_INVALIDARG + +#define DIERR_NOINTERFACE E_NOINTERFACE + +#define DIERR_GENERIC E_FAIL + +#define DIERR_OUTOFMEMORY E_OUTOFMEMORY + +#define DIERR_UNSUPPORTED E_NOTIMPL + +#define DIERR_NOTINITIALIZED \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_NOT_READY) + +#define DIERR_ALREADYINITIALIZED \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_ALREADY_INITIALIZED) + +#define DIERR_NOAGGREGATION CLASS_E_NOAGGREGATION + +#define DIERR_OTHERAPPHASPRIO E_ACCESSDENIED + +#define DIERR_INPUTLOST \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_READ_FAULT) + +#define DIERR_ACQUIRED \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_BUSY) + +#define DIERR_NOTACQUIRED \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_INVALID_ACCESS) + +#define DIERR_READONLY E_ACCESSDENIED + +#define DIERR_HANDLEEXISTS E_ACCESSDENIED + +#ifndef E_PENDING +#define E_PENDING 0x8000000AL +#endif + +#define DIERR_INSUFFICIENTPRIVS 0x80040200L + +#define DIERR_DEVICEFULL 0x80040201L + +#define DIERR_MOREDATA 0x80040202L + +#define DIERR_NOTDOWNLOADED 0x80040203L + +#define DIERR_HASEFFECTS 0x80040204L + +#define DIERR_NOTEXCLUSIVEACQUIRED 0x80040205L + +#define DIERR_INCOMPLETEEFFECT 0x80040206L + +#define DIERR_NOTBUFFERED 0x80040207L + +#define DIERR_EFFECTPLAYING 0x80040208L + +#define DIERR_UNPLUGGED 0x80040209L + +#define DIERR_REPORTFULL 0x8004020AL + +#define DIERR_MAPFILEFAIL 0x8004020BL + +#define DIKEYBOARD_ESCAPE 0x81000401 +#define DIKEYBOARD_1 0x81000402 +#define DIKEYBOARD_2 0x81000403 +#define DIKEYBOARD_3 0x81000404 +#define DIKEYBOARD_4 0x81000405 +#define DIKEYBOARD_5 0x81000406 +#define DIKEYBOARD_6 0x81000407 +#define DIKEYBOARD_7 0x81000408 +#define DIKEYBOARD_8 0x81000409 +#define DIKEYBOARD_9 0x8100040A +#define DIKEYBOARD_0 0x8100040B +#define DIKEYBOARD_MINUS 0x8100040C +#define DIKEYBOARD_EQUALS 0x8100040D +#define DIKEYBOARD_BACK 0x8100040E +#define DIKEYBOARD_TAB 0x8100040F +#define DIKEYBOARD_Q 0x81000410 +#define DIKEYBOARD_W 0x81000411 +#define DIKEYBOARD_E 0x81000412 +#define DIKEYBOARD_R 0x81000413 +#define DIKEYBOARD_T 0x81000414 +#define DIKEYBOARD_Y 0x81000415 +#define DIKEYBOARD_U 0x81000416 +#define DIKEYBOARD_I 0x81000417 +#define DIKEYBOARD_O 0x81000418 +#define DIKEYBOARD_P 0x81000419 +#define DIKEYBOARD_LBRACKET 0x8100041A +#define DIKEYBOARD_RBRACKET 0x8100041B +#define DIKEYBOARD_RETURN 0x8100041C +#define DIKEYBOARD_LCONTROL 0x8100041D +#define DIKEYBOARD_A 0x8100041E +#define DIKEYBOARD_S 0x8100041F +#define DIKEYBOARD_D 0x81000420 +#define DIKEYBOARD_F 0x81000421 +#define DIKEYBOARD_G 0x81000422 +#define DIKEYBOARD_H 0x81000423 +#define DIKEYBOARD_J 0x81000424 +#define DIKEYBOARD_K 0x81000425 +#define DIKEYBOARD_L 0x81000426 +#define DIKEYBOARD_SEMICOLON 0x81000427 +#define DIKEYBOARD_APOSTROPHE 0x81000428 +#define DIKEYBOARD_GRAVE 0x81000429 +#define DIKEYBOARD_LSHIFT 0x8100042A +#define DIKEYBOARD_BACKSLASH 0x8100042B +#define DIKEYBOARD_Z 0x8100042C +#define DIKEYBOARD_X 0x8100042D +#define DIKEYBOARD_C 0x8100042E +#define DIKEYBOARD_V 0x8100042F +#define DIKEYBOARD_B 0x81000430 +#define DIKEYBOARD_N 0x81000431 +#define DIKEYBOARD_M 0x81000432 +#define DIKEYBOARD_COMMA 0x81000433 +#define DIKEYBOARD_PERIOD 0x81000434 +#define DIKEYBOARD_SLASH 0x81000435 +#define DIKEYBOARD_RSHIFT 0x81000436 +#define DIKEYBOARD_MULTIPLY 0x81000437 +#define DIKEYBOARD_LMENU 0x81000438 +#define DIKEYBOARD_SPACE 0x81000439 +#define DIKEYBOARD_CAPITAL 0x8100043A +#define DIKEYBOARD_F1 0x8100043B +#define DIKEYBOARD_F2 0x8100043C +#define DIKEYBOARD_F3 0x8100043D +#define DIKEYBOARD_F4 0x8100043E +#define DIKEYBOARD_F5 0x8100043F +#define DIKEYBOARD_F6 0x81000440 +#define DIKEYBOARD_F7 0x81000441 +#define DIKEYBOARD_F8 0x81000442 +#define DIKEYBOARD_F9 0x81000443 +#define DIKEYBOARD_F10 0x81000444 +#define DIKEYBOARD_NUMLOCK 0x81000445 +#define DIKEYBOARD_SCROLL 0x81000446 +#define DIKEYBOARD_NUMPAD7 0x81000447 +#define DIKEYBOARD_NUMPAD8 0x81000448 +#define DIKEYBOARD_NUMPAD9 0x81000449 +#define DIKEYBOARD_SUBTRACT 0x8100044A +#define DIKEYBOARD_NUMPAD4 0x8100044B +#define DIKEYBOARD_NUMPAD5 0x8100044C +#define DIKEYBOARD_NUMPAD6 0x8100044D +#define DIKEYBOARD_ADD 0x8100044E +#define DIKEYBOARD_NUMPAD1 0x8100044F +#define DIKEYBOARD_NUMPAD2 0x81000450 +#define DIKEYBOARD_NUMPAD3 0x81000451 +#define DIKEYBOARD_NUMPAD0 0x81000452 +#define DIKEYBOARD_DECIMAL 0x81000453 +#define DIKEYBOARD_OEM_102 0x81000456 +#define DIKEYBOARD_F11 0x81000457 +#define DIKEYBOARD_F12 0x81000458 +#define DIKEYBOARD_F13 0x81000464 +#define DIKEYBOARD_F14 0x81000465 +#define DIKEYBOARD_F15 0x81000466 +#define DIKEYBOARD_KANA 0x81000470 +#define DIKEYBOARD_ABNT_C1 0x81000473 +#define DIKEYBOARD_CONVERT 0x81000479 +#define DIKEYBOARD_NOCONVERT 0x8100047B +#define DIKEYBOARD_YEN 0x8100047D +#define DIKEYBOARD_ABNT_C2 0x8100047E +#define DIKEYBOARD_NUMPADEQUALS 0x8100048D +#define DIKEYBOARD_PREVTRACK 0x81000490 +#define DIKEYBOARD_AT 0x81000491 +#define DIKEYBOARD_COLON 0x81000492 +#define DIKEYBOARD_UNDERLINE 0x81000493 +#define DIKEYBOARD_KANJI 0x81000494 +#define DIKEYBOARD_STOP 0x81000495 +#define DIKEYBOARD_AX 0x81000496 +#define DIKEYBOARD_UNLABELED 0x81000497 +#define DIKEYBOARD_NEXTTRACK 0x81000499 +#define DIKEYBOARD_NUMPADENTER 0x8100049C +#define DIKEYBOARD_RCONTROL 0x8100049D +#define DIKEYBOARD_MUTE 0x810004A0 +#define DIKEYBOARD_CALCULATOR 0x810004A1 +#define DIKEYBOARD_PLAYPAUSE 0x810004A2 +#define DIKEYBOARD_MEDIASTOP 0x810004A4 +#define DIKEYBOARD_VOLUMEDOWN 0x810004AE +#define DIKEYBOARD_VOLUMEUP 0x810004B0 +#define DIKEYBOARD_WEBHOME 0x810004B2 +#define DIKEYBOARD_NUMPADCOMMA 0x810004B3 +#define DIKEYBOARD_DIVIDE 0x810004B5 +#define DIKEYBOARD_SYSRQ 0x810004B7 +#define DIKEYBOARD_RMENU 0x810004B8 +#define DIKEYBOARD_PAUSE 0x810004C5 +#define DIKEYBOARD_HOME 0x810004C7 +#define DIKEYBOARD_UP 0x810004C8 +#define DIKEYBOARD_PRIOR 0x810004C9 +#define DIKEYBOARD_LEFT 0x810004CB +#define DIKEYBOARD_RIGHT 0x810004CD +#define DIKEYBOARD_END 0x810004CF +#define DIKEYBOARD_DOWN 0x810004D0 +#define DIKEYBOARD_NEXT 0x810004D1 +#define DIKEYBOARD_INSERT 0x810004D2 +#define DIKEYBOARD_DELETE 0x810004D3 +#define DIKEYBOARD_LWIN 0x810004DB +#define DIKEYBOARD_RWIN 0x810004DC +#define DIKEYBOARD_APPS 0x810004DD +#define DIKEYBOARD_POWER 0x810004DE +#define DIKEYBOARD_SLEEP 0x810004DF +#define DIKEYBOARD_WAKE 0x810004E3 +#define DIKEYBOARD_WEBSEARCH 0x810004E5 +#define DIKEYBOARD_WEBFAVORITES 0x810004E6 +#define DIKEYBOARD_WEBREFRESH 0x810004E7 +#define DIKEYBOARD_WEBSTOP 0x810004E8 +#define DIKEYBOARD_WEBFORWARD 0x810004E9 +#define DIKEYBOARD_WEBBACK 0x810004EA +#define DIKEYBOARD_MYCOMPUTER 0x810004EB +#define DIKEYBOARD_MAIL 0x810004EC +#define DIKEYBOARD_MEDIASELECT 0x810004ED + +#define DIMOUSE_XAXISAB (0x82000200 |DIMOFS_X ) +#define DIMOUSE_YAXISAB (0x82000200 |DIMOFS_Y ) +#define DIMOUSE_XAXIS (0x82000300 |DIMOFS_X ) +#define DIMOUSE_YAXIS (0x82000300 |DIMOFS_Y ) +#define DIMOUSE_WHEEL (0x82000300 |DIMOFS_Z ) +#define DIMOUSE_BUTTON0 (0x82000400 |DIMOFS_BUTTON0) +#define DIMOUSE_BUTTON1 (0x82000400 |DIMOFS_BUTTON1) +#define DIMOUSE_BUTTON2 (0x82000400 |DIMOFS_BUTTON2) +#define DIMOUSE_BUTTON3 (0x82000400 |DIMOFS_BUTTON3) +#define DIMOUSE_BUTTON4 (0x82000400 |DIMOFS_BUTTON4) +#define DIMOUSE_BUTTON5 (0x82000400 |DIMOFS_BUTTON5) +#define DIMOUSE_BUTTON6 (0x82000400 |DIMOFS_BUTTON6) +#define DIMOUSE_BUTTON7 (0x82000400 |DIMOFS_BUTTON7) + +#define DIVOICE_CHANNEL1 0x83000401 +#define DIVOICE_CHANNEL2 0x83000402 +#define DIVOICE_CHANNEL3 0x83000403 +#define DIVOICE_CHANNEL4 0x83000404 +#define DIVOICE_CHANNEL5 0x83000405 +#define DIVOICE_CHANNEL6 0x83000406 +#define DIVOICE_CHANNEL7 0x83000407 +#define DIVOICE_CHANNEL8 0x83000408 +#define DIVOICE_TEAM 0x83000409 +#define DIVOICE_ALL 0x8300040A +#define DIVOICE_RECORDMUTE 0x8300040B +#define DIVOICE_PLAYBACKMUTE 0x8300040C +#define DIVOICE_TRANSMIT 0x8300040D + +#define DIVOICE_VOICECOMMAND 0x83000410 + +#define DIVIRTUAL_DRIVING_RACE 0x01000000 +#define DIAXIS_DRIVINGR_STEER 0x01008A01 +#define DIAXIS_DRIVINGR_ACCELERATE 0x01039202 +#define DIAXIS_DRIVINGR_BRAKE 0x01041203 +#define DIBUTTON_DRIVINGR_SHIFTUP 0x01000C01 +#define DIBUTTON_DRIVINGR_SHIFTDOWN 0x01000C02 +#define DIBUTTON_DRIVINGR_VIEW 0x01001C03 +#define DIBUTTON_DRIVINGR_MENU 0x010004FD + +#define DIAXIS_DRIVINGR_ACCEL_AND_BRAKE 0x01014A04 +#define DIHATSWITCH_DRIVINGR_GLANCE 0x01004601 +#define DIBUTTON_DRIVINGR_BRAKE 0x01004C04 +#define DIBUTTON_DRIVINGR_DASHBOARD 0x01004405 +#define DIBUTTON_DRIVINGR_AIDS 0x01004406 +#define DIBUTTON_DRIVINGR_MAP 0x01004407 +#define DIBUTTON_DRIVINGR_BOOST 0x01004408 +#define DIBUTTON_DRIVINGR_PIT 0x01004409 +#define DIBUTTON_DRIVINGR_ACCELERATE_LINK 0x0103D4E0 +#define DIBUTTON_DRIVINGR_STEER_LEFT_LINK 0x0100CCE4 +#define DIBUTTON_DRIVINGR_STEER_RIGHT_LINK 0x0100CCEC +#define DIBUTTON_DRIVINGR_GLANCE_LEFT_LINK 0x0107C4E4 +#define DIBUTTON_DRIVINGR_GLANCE_RIGHT_LINK 0x0107C4EC +#define DIBUTTON_DRIVINGR_DEVICE 0x010044FE +#define DIBUTTON_DRIVINGR_PAUSE 0x010044FC + +#define DIVIRTUAL_DRIVING_COMBAT 0x02000000 +#define DIAXIS_DRIVINGC_STEER 0x02008A01 +#define DIAXIS_DRIVINGC_ACCELERATE 0x02039202 +#define DIAXIS_DRIVINGC_BRAKE 0x02041203 +#define DIBUTTON_DRIVINGC_FIRE 0x02000C01 +#define DIBUTTON_DRIVINGC_WEAPONS 0x02000C02 +#define DIBUTTON_DRIVINGC_TARGET 0x02000C03 +#define DIBUTTON_DRIVINGC_MENU 0x020004FD + +#define DIAXIS_DRIVINGC_ACCEL_AND_BRAKE 0x02014A04 +#define DIHATSWITCH_DRIVINGC_GLANCE 0x02004601 +#define DIBUTTON_DRIVINGC_SHIFTUP 0x02004C04 +#define DIBUTTON_DRIVINGC_SHIFTDOWN 0x02004C05 +#define DIBUTTON_DRIVINGC_DASHBOARD 0x02004406 +#define DIBUTTON_DRIVINGC_AIDS 0x02004407 +#define DIBUTTON_DRIVINGC_BRAKE 0x02004C08 +#define DIBUTTON_DRIVINGC_FIRESECONDARY 0x02004C09 +#define DIBUTTON_DRIVINGC_ACCELERATE_LINK 0x0203D4E0 +#define DIBUTTON_DRIVINGC_STEER_LEFT_LINK 0x0200CCE4 +#define DIBUTTON_DRIVINGC_STEER_RIGHT_LINK 0x0200CCEC +#define DIBUTTON_DRIVINGC_GLANCE_LEFT_LINK 0x0207C4E4 +#define DIBUTTON_DRIVINGC_GLANCE_RIGHT_LINK 0x0207C4EC +#define DIBUTTON_DRIVINGC_DEVICE 0x020044FE +#define DIBUTTON_DRIVINGC_PAUSE 0x020044FC + +#define DIVIRTUAL_DRIVING_TANK 0x03000000 +#define DIAXIS_DRIVINGT_STEER 0x03008A01 +#define DIAXIS_DRIVINGT_BARREL 0x03010202 +#define DIAXIS_DRIVINGT_ACCELERATE 0x03039203 +#define DIAXIS_DRIVINGT_ROTATE 0x03020204 +#define DIBUTTON_DRIVINGT_FIRE 0x03000C01 +#define DIBUTTON_DRIVINGT_WEAPONS 0x03000C02 +#define DIBUTTON_DRIVINGT_TARGET 0x03000C03 +#define DIBUTTON_DRIVINGT_MENU 0x030004FD + +#define DIHATSWITCH_DRIVINGT_GLANCE 0x03004601 +#define DIAXIS_DRIVINGT_BRAKE 0x03045205 +#define DIAXIS_DRIVINGT_ACCEL_AND_BRAKE 0x03014A06 +#define DIBUTTON_DRIVINGT_VIEW 0x03005C04 +#define DIBUTTON_DRIVINGT_DASHBOARD 0x03005C05 +#define DIBUTTON_DRIVINGT_BRAKE 0x03004C06 +#define DIBUTTON_DRIVINGT_FIRESECONDARY 0x03004C07 +#define DIBUTTON_DRIVINGT_ACCELERATE_LINK 0x0303D4E0 +#define DIBUTTON_DRIVINGT_STEER_LEFT_LINK 0x0300CCE4 +#define DIBUTTON_DRIVINGT_STEER_RIGHT_LINK 0x0300CCEC +#define DIBUTTON_DRIVINGT_BARREL_UP_LINK 0x030144E0 +#define DIBUTTON_DRIVINGT_BARREL_DOWN_LINK 0x030144E8 +#define DIBUTTON_DRIVINGT_ROTATE_LEFT_LINK 0x030244E4 +#define DIBUTTON_DRIVINGT_ROTATE_RIGHT_LINK 0x030244EC +#define DIBUTTON_DRIVINGT_GLANCE_LEFT_LINK 0x0307C4E4 +#define DIBUTTON_DRIVINGT_GLANCE_RIGHT_LINK 0x0307C4EC +#define DIBUTTON_DRIVINGT_DEVICE 0x030044FE +#define DIBUTTON_DRIVINGT_PAUSE 0x030044FC + +#define DIVIRTUAL_FLYING_CIVILIAN 0x04000000 +#define DIAXIS_FLYINGC_BANK 0x04008A01 +#define DIAXIS_FLYINGC_PITCH 0x04010A02 +#define DIAXIS_FLYINGC_THROTTLE 0x04039203 +#define DIBUTTON_FLYINGC_VIEW 0x04002401 +#define DIBUTTON_FLYINGC_DISPLAY 0x04002402 +#define DIBUTTON_FLYINGC_GEAR 0x04002C03 +#define DIBUTTON_FLYINGC_MENU 0x040004FD + +#define DIHATSWITCH_FLYINGC_GLANCE 0x04004601 +#define DIAXIS_FLYINGC_BRAKE 0x04046A04 +#define DIAXIS_FLYINGC_RUDDER 0x04025205 +#define DIAXIS_FLYINGC_FLAPS 0x04055A06 +#define DIBUTTON_FLYINGC_FLAPSUP 0x04006404 +#define DIBUTTON_FLYINGC_FLAPSDOWN 0x04006405 +#define DIBUTTON_FLYINGC_BRAKE_LINK 0x04046CE0 +#define DIBUTTON_FLYINGC_FASTER_LINK 0x0403D4E0 +#define DIBUTTON_FLYINGC_SLOWER_LINK 0x0403D4E8 +#define DIBUTTON_FLYINGC_GLANCE_LEFT_LINK 0x0407C4E4 +#define DIBUTTON_FLYINGC_GLANCE_RIGHT_LINK 0x0407C4EC +#define DIBUTTON_FLYINGC_GLANCE_UP_LINK 0x0407C4E0 +#define DIBUTTON_FLYINGC_GLANCE_DOWN_LINK 0x0407C4E8 +#define DIBUTTON_FLYINGC_DEVICE 0x040044FE +#define DIBUTTON_FLYINGC_PAUSE 0x040044FC + +#define DIVIRTUAL_FLYING_MILITARY 0x05000000 +#define DIAXIS_FLYINGM_BANK 0x05008A01 +#define DIAXIS_FLYINGM_PITCH 0x05010A02 +#define DIAXIS_FLYINGM_THROTTLE 0x05039203 +#define DIBUTTON_FLYINGM_FIRE 0x05000C01 +#define DIBUTTON_FLYINGM_WEAPONS 0x05000C02 +#define DIBUTTON_FLYINGM_TARGET 0x05000C03 +#define DIBUTTON_FLYINGM_MENU 0x050004FD + +#define DIHATSWITCH_FLYINGM_GLANCE 0x05004601 +#define DIBUTTON_FLYINGM_COUNTER 0x05005C04 +#define DIAXIS_FLYINGM_RUDDER 0x05024A04 +#define DIAXIS_FLYINGM_BRAKE 0x05046205 +#define DIBUTTON_FLYINGM_VIEW 0x05006405 +#define DIBUTTON_FLYINGM_DISPLAY 0x05006406 +#define DIAXIS_FLYINGM_FLAPS 0x05055206 +#define DIBUTTON_FLYINGM_FLAPSUP 0x05005407 +#define DIBUTTON_FLYINGM_FLAPSDOWN 0x05005408 +#define DIBUTTON_FLYINGM_FIRESECONDARY 0x05004C09 +#define DIBUTTON_FLYINGM_GEAR 0x0500640A +#define DIBUTTON_FLYINGM_BRAKE_LINK 0x050464E0 +#define DIBUTTON_FLYINGM_FASTER_LINK 0x0503D4E0 +#define DIBUTTON_FLYINGM_SLOWER_LINK 0x0503D4E8 +#define DIBUTTON_FLYINGM_GLANCE_LEFT_LINK 0x0507C4E4 +#define DIBUTTON_FLYINGM_GLANCE_RIGHT_LINK 0x0507C4EC +#define DIBUTTON_FLYINGM_GLANCE_UP_LINK 0x0507C4E0 +#define DIBUTTON_FLYINGM_GLANCE_DOWN_LINK 0x0507C4E8 +#define DIBUTTON_FLYINGM_DEVICE 0x050044FE +#define DIBUTTON_FLYINGM_PAUSE 0x050044FC + +#define DIVIRTUAL_FLYING_HELICOPTER 0x06000000 +#define DIAXIS_FLYINGH_BANK 0x06008A01 +#define DIAXIS_FLYINGH_PITCH 0x06010A02 +#define DIAXIS_FLYINGH_COLLECTIVE 0x06018A03 +#define DIBUTTON_FLYINGH_FIRE 0x06001401 +#define DIBUTTON_FLYINGH_WEAPONS 0x06001402 +#define DIBUTTON_FLYINGH_TARGET 0x06001403 +#define DIBUTTON_FLYINGH_MENU 0x060004FD + +#define DIHATSWITCH_FLYINGH_GLANCE 0x06004601 +#define DIAXIS_FLYINGH_TORQUE 0x06025A04 +#define DIAXIS_FLYINGH_THROTTLE 0x0603DA05 +#define DIBUTTON_FLYINGH_COUNTER 0x06005404 +#define DIBUTTON_FLYINGH_VIEW 0x06006405 +#define DIBUTTON_FLYINGH_GEAR 0x06006406 +#define DIBUTTON_FLYINGH_FIRESECONDARY 0x06004C07 +#define DIBUTTON_FLYINGH_FASTER_LINK 0x0603DCE0 +#define DIBUTTON_FLYINGH_SLOWER_LINK 0x0603DCE8 +#define DIBUTTON_FLYINGH_GLANCE_LEFT_LINK 0x0607C4E4 +#define DIBUTTON_FLYINGH_GLANCE_RIGHT_LINK 0x0607C4EC +#define DIBUTTON_FLYINGH_GLANCE_UP_LINK 0x0607C4E0 +#define DIBUTTON_FLYINGH_GLANCE_DOWN_LINK 0x0607C4E8 +#define DIBUTTON_FLYINGH_DEVICE 0x060044FE +#define DIBUTTON_FLYINGH_PAUSE 0x060044FC + +#define DIVIRTUAL_SPACESIM 0x07000000 +#define DIAXIS_SPACESIM_LATERAL 0x07008201 +#define DIAXIS_SPACESIM_MOVE 0x07010202 +#define DIAXIS_SPACESIM_THROTTLE 0x07038203 +#define DIBUTTON_SPACESIM_FIRE 0x07000401 +#define DIBUTTON_SPACESIM_WEAPONS 0x07000402 +#define DIBUTTON_SPACESIM_TARGET 0x07000403 +#define DIBUTTON_SPACESIM_MENU 0x070004FD + +#define DIHATSWITCH_SPACESIM_GLANCE 0x07004601 +#define DIAXIS_SPACESIM_CLIMB 0x0701C204 +#define DIAXIS_SPACESIM_ROTATE 0x07024205 +#define DIBUTTON_SPACESIM_VIEW 0x07004404 +#define DIBUTTON_SPACESIM_DISPLAY 0x07004405 +#define DIBUTTON_SPACESIM_RAISE 0x07004406 +#define DIBUTTON_SPACESIM_LOWER 0x07004407 +#define DIBUTTON_SPACESIM_GEAR 0x07004408 +#define DIBUTTON_SPACESIM_FIRESECONDARY 0x07004409 +#define DIBUTTON_SPACESIM_LEFT_LINK 0x0700C4E4 +#define DIBUTTON_SPACESIM_RIGHT_LINK 0x0700C4EC +#define DIBUTTON_SPACESIM_FORWARD_LINK 0x070144E0 +#define DIBUTTON_SPACESIM_BACKWARD_LINK 0x070144E8 +#define DIBUTTON_SPACESIM_FASTER_LINK 0x0703C4E0 +#define DIBUTTON_SPACESIM_SLOWER_LINK 0x0703C4E8 +#define DIBUTTON_SPACESIM_TURN_LEFT_LINK 0x070244E4 +#define DIBUTTON_SPACESIM_TURN_RIGHT_LINK 0x070244EC +#define DIBUTTON_SPACESIM_GLANCE_LEFT_LINK 0x0707C4E4 +#define DIBUTTON_SPACESIM_GLANCE_RIGHT_LINK 0x0707C4EC +#define DIBUTTON_SPACESIM_GLANCE_UP_LINK 0x0707C4E0 +#define DIBUTTON_SPACESIM_GLANCE_DOWN_LINK 0x0707C4E8 +#define DIBUTTON_SPACESIM_DEVICE 0x070044FE +#define DIBUTTON_SPACESIM_PAUSE 0x070044FC + +#define DIVIRTUAL_FIGHTING_HAND2HAND 0x08000000 +#define DIAXIS_FIGHTINGH_LATERAL 0x08008201 +#define DIAXIS_FIGHTINGH_MOVE 0x08010202 +#define DIBUTTON_FIGHTINGH_PUNCH 0x08000401 +#define DIBUTTON_FIGHTINGH_KICK 0x08000402 +#define DIBUTTON_FIGHTINGH_BLOCK 0x08000403 +#define DIBUTTON_FIGHTINGH_CROUCH 0x08000404 +#define DIBUTTON_FIGHTINGH_JUMP 0x08000405 +#define DIBUTTON_FIGHTINGH_SPECIAL1 0x08000406 +#define DIBUTTON_FIGHTINGH_SPECIAL2 0x08000407 +#define DIBUTTON_FIGHTINGH_MENU 0x080004FD + +#define DIBUTTON_FIGHTINGH_SELECT 0x08004408 +#define DIHATSWITCH_FIGHTINGH_SLIDE 0x08004601 +#define DIBUTTON_FIGHTINGH_DISPLAY 0x08004409 +#define DIAXIS_FIGHTINGH_ROTATE 0x08024203 +#define DIBUTTON_FIGHTINGH_DODGE 0x0800440A +#define DIBUTTON_FIGHTINGH_LEFT_LINK 0x0800C4E4 +#define DIBUTTON_FIGHTINGH_RIGHT_LINK 0x0800C4EC +#define DIBUTTON_FIGHTINGH_FORWARD_LINK 0x080144E0 +#define DIBUTTON_FIGHTINGH_BACKWARD_LINK 0x080144E8 +#define DIBUTTON_FIGHTINGH_DEVICE 0x080044FE +#define DIBUTTON_FIGHTINGH_PAUSE 0x080044FC + +#define DIVIRTUAL_FIGHTING_FPS 0x09000000 +#define DIAXIS_FPS_ROTATE 0x09008201 +#define DIAXIS_FPS_MOVE 0x09010202 +#define DIBUTTON_FPS_FIRE 0x09000401 +#define DIBUTTON_FPS_WEAPONS 0x09000402 +#define DIBUTTON_FPS_APPLY 0x09000403 +#define DIBUTTON_FPS_SELECT 0x09000404 +#define DIBUTTON_FPS_CROUCH 0x09000405 +#define DIBUTTON_FPS_JUMP 0x09000406 +#define DIAXIS_FPS_LOOKUPDOWN 0x09018203 +#define DIBUTTON_FPS_STRAFE 0x09000407 +#define DIBUTTON_FPS_MENU 0x090004FD + +#define DIHATSWITCH_FPS_GLANCE 0x09004601 +#define DIBUTTON_FPS_DISPLAY 0x09004408 +#define DIAXIS_FPS_SIDESTEP 0x09024204 +#define DIBUTTON_FPS_DODGE 0x09004409 +#define DIBUTTON_FPS_GLANCEL 0x0900440A +#define DIBUTTON_FPS_GLANCER 0x0900440B +#define DIBUTTON_FPS_FIRESECONDARY 0x0900440C +#define DIBUTTON_FPS_ROTATE_LEFT_LINK 0x0900C4E4 +#define DIBUTTON_FPS_ROTATE_RIGHT_LINK 0x0900C4EC +#define DIBUTTON_FPS_FORWARD_LINK 0x090144E0 +#define DIBUTTON_FPS_BACKWARD_LINK 0x090144E8 +#define DIBUTTON_FPS_GLANCE_UP_LINK 0x0901C4E0 +#define DIBUTTON_FPS_GLANCE_DOWN_LINK 0x0901C4E8 +#define DIBUTTON_FPS_DEVICE 0x090044FE +#define DIBUTTON_FPS_PAUSE 0x090044FC + +#define DIVIRTUAL_FIGHTING_THIRDPERSON 0x0A000000 +#define DIAXIS_TPS_TURN 0x0A020201 +#define DIAXIS_TPS_MOVE 0x0A010202 +#define DIBUTTON_TPS_RUN 0x0A000401 +#define DIBUTTON_TPS_ACTION 0x0A000402 +#define DIBUTTON_TPS_SELECT 0x0A000403 +#define DIBUTTON_TPS_USE 0x0A000404 +#define DIBUTTON_TPS_JUMP 0x0A000405 +#define DIBUTTON_TPS_MENU 0x0A0004FD + +#define DIHATSWITCH_TPS_GLANCE 0x0A004601 +#define DIBUTTON_TPS_VIEW 0x0A004406 +#define DIBUTTON_TPS_STEPLEFT 0x0A004407 +#define DIBUTTON_TPS_STEPRIGHT 0x0A004408 +#define DIAXIS_TPS_STEP 0x0A00C203 +#define DIBUTTON_TPS_DODGE 0x0A004409 +#define DIBUTTON_TPS_INVENTORY 0x0A00440A +#define DIBUTTON_TPS_TURN_LEFT_LINK 0x0A0244E4 +#define DIBUTTON_TPS_TURN_RIGHT_LINK 0x0A0244EC +#define DIBUTTON_TPS_FORWARD_LINK 0x0A0144E0 +#define DIBUTTON_TPS_BACKWARD_LINK 0x0A0144E8 +#define DIBUTTON_TPS_GLANCE_UP_LINK 0x0A07C4E0 +#define DIBUTTON_TPS_GLANCE_DOWN_LINK 0x0A07C4E8 +#define DIBUTTON_TPS_GLANCE_LEFT_LINK 0x0A07C4E4 +#define DIBUTTON_TPS_GLANCE_RIGHT_LINK 0x0A07C4EC +#define DIBUTTON_TPS_DEVICE 0x0A0044FE +#define DIBUTTON_TPS_PAUSE 0x0A0044FC + +#define DIVIRTUAL_STRATEGY_ROLEPLAYING 0x0B000000 +#define DIAXIS_STRATEGYR_LATERAL 0x0B008201 +#define DIAXIS_STRATEGYR_MOVE 0x0B010202 +#define DIBUTTON_STRATEGYR_GET 0x0B000401 +#define DIBUTTON_STRATEGYR_APPLY 0x0B000402 +#define DIBUTTON_STRATEGYR_SELECT 0x0B000403 +#define DIBUTTON_STRATEGYR_ATTACK 0x0B000404 +#define DIBUTTON_STRATEGYR_CAST 0x0B000405 +#define DIBUTTON_STRATEGYR_CROUCH 0x0B000406 +#define DIBUTTON_STRATEGYR_JUMP 0x0B000407 +#define DIBUTTON_STRATEGYR_MENU 0x0B0004FD + +#define DIHATSWITCH_STRATEGYR_GLANCE 0x0B004601 +#define DIBUTTON_STRATEGYR_MAP 0x0B004408 +#define DIBUTTON_STRATEGYR_DISPLAY 0x0B004409 +#define DIAXIS_STRATEGYR_ROTATE 0x0B024203 +#define DIBUTTON_STRATEGYR_LEFT_LINK 0x0B00C4E4 +#define DIBUTTON_STRATEGYR_RIGHT_LINK 0x0B00C4EC +#define DIBUTTON_STRATEGYR_FORWARD_LINK 0x0B0144E0 +#define DIBUTTON_STRATEGYR_BACK_LINK 0x0B0144E8 +#define DIBUTTON_STRATEGYR_ROTATE_LEFT_LINK 0x0B0244E4 +#define DIBUTTON_STRATEGYR_ROTATE_RIGHT_LINK 0x0B0244EC +#define DIBUTTON_STRATEGYR_DEVICE 0x0B0044FE +#define DIBUTTON_STRATEGYR_PAUSE 0x0B0044FC + +#define DIVIRTUAL_STRATEGY_TURN 0x0C000000 +#define DIAXIS_STRATEGYT_LATERAL 0x0C008201 +#define DIAXIS_STRATEGYT_MOVE 0x0C010202 +#define DIBUTTON_STRATEGYT_SELECT 0x0C000401 +#define DIBUTTON_STRATEGYT_INSTRUCT 0x0C000402 +#define DIBUTTON_STRATEGYT_APPLY 0x0C000403 +#define DIBUTTON_STRATEGYT_TEAM 0x0C000404 +#define DIBUTTON_STRATEGYT_TURN 0x0C000405 +#define DIBUTTON_STRATEGYT_MENU 0x0C0004FD + +#define DIBUTTON_STRATEGYT_ZOOM 0x0C004406 +#define DIBUTTON_STRATEGYT_MAP 0x0C004407 +#define DIBUTTON_STRATEGYT_DISPLAY 0x0C004408 +#define DIBUTTON_STRATEGYT_LEFT_LINK 0x0C00C4E4 +#define DIBUTTON_STRATEGYT_RIGHT_LINK 0x0C00C4EC +#define DIBUTTON_STRATEGYT_FORWARD_LINK 0x0C0144E0 +#define DIBUTTON_STRATEGYT_BACK_LINK 0x0C0144E8 +#define DIBUTTON_STRATEGYT_DEVICE 0x0C0044FE +#define DIBUTTON_STRATEGYT_PAUSE 0x0C0044FC + +#define DIVIRTUAL_SPORTS_HUNTING 0x0D000000 +#define DIAXIS_HUNTING_LATERAL 0x0D008201 +#define DIAXIS_HUNTING_MOVE 0x0D010202 +#define DIBUTTON_HUNTING_FIRE 0x0D000401 +#define DIBUTTON_HUNTING_AIM 0x0D000402 +#define DIBUTTON_HUNTING_WEAPON 0x0D000403 +#define DIBUTTON_HUNTING_BINOCULAR 0x0D000404 +#define DIBUTTON_HUNTING_CALL 0x0D000405 +#define DIBUTTON_HUNTING_MAP 0x0D000406 +#define DIBUTTON_HUNTING_SPECIAL 0x0D000407 +#define DIBUTTON_HUNTING_MENU 0x0D0004FD + +#define DIHATSWITCH_HUNTING_GLANCE 0x0D004601 +#define DIBUTTON_HUNTING_DISPLAY 0x0D004408 +#define DIAXIS_HUNTING_ROTATE 0x0D024203 +#define DIBUTTON_HUNTING_CROUCH 0x0D004409 +#define DIBUTTON_HUNTING_JUMP 0x0D00440A +#define DIBUTTON_HUNTING_FIRESECONDARY 0x0D00440B +#define DIBUTTON_HUNTING_LEFT_LINK 0x0D00C4E4 +#define DIBUTTON_HUNTING_RIGHT_LINK 0x0D00C4EC +#define DIBUTTON_HUNTING_FORWARD_LINK 0x0D0144E0 +#define DIBUTTON_HUNTING_BACK_LINK 0x0D0144E8 +#define DIBUTTON_HUNTING_ROTATE_LEFT_LINK 0x0D0244E4 +#define DIBUTTON_HUNTING_ROTATE_RIGHT_LINK 0x0D0244EC +#define DIBUTTON_HUNTING_DEVICE 0x0D0044FE +#define DIBUTTON_HUNTING_PAUSE 0x0D0044FC + +#define DIVIRTUAL_SPORTS_FISHING 0x0E000000 +#define DIAXIS_FISHING_LATERAL 0x0E008201 +#define DIAXIS_FISHING_MOVE 0x0E010202 +#define DIBUTTON_FISHING_CAST 0x0E000401 +#define DIBUTTON_FISHING_TYPE 0x0E000402 +#define DIBUTTON_FISHING_BINOCULAR 0x0E000403 +#define DIBUTTON_FISHING_BAIT 0x0E000404 +#define DIBUTTON_FISHING_MAP 0x0E000405 +#define DIBUTTON_FISHING_MENU 0x0E0004FD + +#define DIHATSWITCH_FISHING_GLANCE 0x0E004601 +#define DIBUTTON_FISHING_DISPLAY 0x0E004406 +#define DIAXIS_FISHING_ROTATE 0x0E024203 +#define DIBUTTON_FISHING_CROUCH 0x0E004407 +#define DIBUTTON_FISHING_JUMP 0x0E004408 +#define DIBUTTON_FISHING_LEFT_LINK 0x0E00C4E4 +#define DIBUTTON_FISHING_RIGHT_LINK 0x0E00C4EC +#define DIBUTTON_FISHING_FORWARD_LINK 0x0E0144E0 +#define DIBUTTON_FISHING_BACK_LINK 0x0E0144E8 +#define DIBUTTON_FISHING_ROTATE_LEFT_LINK 0x0E0244E4 +#define DIBUTTON_FISHING_ROTATE_RIGHT_LINK 0x0E0244EC +#define DIBUTTON_FISHING_DEVICE 0x0E0044FE +#define DIBUTTON_FISHING_PAUSE 0x0E0044FC + +#define DIVIRTUAL_SPORTS_BASEBALL_BAT 0x0F000000 +#define DIAXIS_BASEBALLB_LATERAL 0x0F008201 +#define DIAXIS_BASEBALLB_MOVE 0x0F010202 +#define DIBUTTON_BASEBALLB_SELECT 0x0F000401 +#define DIBUTTON_BASEBALLB_NORMAL 0x0F000402 +#define DIBUTTON_BASEBALLB_POWER 0x0F000403 +#define DIBUTTON_BASEBALLB_BUNT 0x0F000404 +#define DIBUTTON_BASEBALLB_STEAL 0x0F000405 +#define DIBUTTON_BASEBALLB_BURST 0x0F000406 +#define DIBUTTON_BASEBALLB_SLIDE 0x0F000407 +#define DIBUTTON_BASEBALLB_CONTACT 0x0F000408 +#define DIBUTTON_BASEBALLB_MENU 0x0F0004FD + +#define DIBUTTON_BASEBALLB_NOSTEAL 0x0F004409 +#define DIBUTTON_BASEBALLB_BOX 0x0F00440A +#define DIBUTTON_BASEBALLB_LEFT_LINK 0x0F00C4E4 +#define DIBUTTON_BASEBALLB_RIGHT_LINK 0x0F00C4EC +#define DIBUTTON_BASEBALLB_FORWARD_LINK 0x0F0144E0 +#define DIBUTTON_BASEBALLB_BACK_LINK 0x0F0144E8 +#define DIBUTTON_BASEBALLB_DEVICE 0x0F0044FE +#define DIBUTTON_BASEBALLB_PAUSE 0x0F0044FC + +#define DIVIRTUAL_SPORTS_BASEBALL_PITCH 0x10000000 +#define DIAXIS_BASEBALLP_LATERAL 0x10008201 +#define DIAXIS_BASEBALLP_MOVE 0x10010202 +#define DIBUTTON_BASEBALLP_SELECT 0x10000401 +#define DIBUTTON_BASEBALLP_PITCH 0x10000402 +#define DIBUTTON_BASEBALLP_BASE 0x10000403 +#define DIBUTTON_BASEBALLP_THROW 0x10000404 +#define DIBUTTON_BASEBALLP_FAKE 0x10000405 +#define DIBUTTON_BASEBALLP_MENU 0x100004FD + +#define DIBUTTON_BASEBALLP_WALK 0x10004406 +#define DIBUTTON_BASEBALLP_LOOK 0x10004407 +#define DIBUTTON_BASEBALLP_LEFT_LINK 0x1000C4E4 +#define DIBUTTON_BASEBALLP_RIGHT_LINK 0x1000C4EC +#define DIBUTTON_BASEBALLP_FORWARD_LINK 0x100144E0 +#define DIBUTTON_BASEBALLP_BACK_LINK 0x100144E8 +#define DIBUTTON_BASEBALLP_DEVICE 0x100044FE +#define DIBUTTON_BASEBALLP_PAUSE 0x100044FC + +#define DIVIRTUAL_SPORTS_BASEBALL_FIELD 0x11000000 +#define DIAXIS_BASEBALLF_LATERAL 0x11008201 +#define DIAXIS_BASEBALLF_MOVE 0x11010202 +#define DIBUTTON_BASEBALLF_NEAREST 0x11000401 +#define DIBUTTON_BASEBALLF_THROW1 0x11000402 +#define DIBUTTON_BASEBALLF_THROW2 0x11000403 +#define DIBUTTON_BASEBALLF_BURST 0x11000404 +#define DIBUTTON_BASEBALLF_JUMP 0x11000405 +#define DIBUTTON_BASEBALLF_DIVE 0x11000406 +#define DIBUTTON_BASEBALLF_MENU 0x110004FD + +#define DIBUTTON_BASEBALLF_SHIFTIN 0x11004407 +#define DIBUTTON_BASEBALLF_SHIFTOUT 0x11004408 +#define DIBUTTON_BASEBALLF_AIM_LEFT_LINK 0x1100C4E4 +#define DIBUTTON_BASEBALLF_AIM_RIGHT_LINK 0x1100C4EC +#define DIBUTTON_BASEBALLF_FORWARD_LINK 0x110144E0 +#define DIBUTTON_BASEBALLF_BACK_LINK 0x110144E8 +#define DIBUTTON_BASEBALLF_DEVICE 0x110044FE +#define DIBUTTON_BASEBALLF_PAUSE 0x110044FC + +#define DIVIRTUAL_SPORTS_BASKETBALL_OFFENSE 0x12000000 +#define DIAXIS_BBALLO_LATERAL 0x12008201 +#define DIAXIS_BBALLO_MOVE 0x12010202 +#define DIBUTTON_BBALLO_SHOOT 0x12000401 +#define DIBUTTON_BBALLO_DUNK 0x12000402 +#define DIBUTTON_BBALLO_PASS 0x12000403 +#define DIBUTTON_BBALLO_FAKE 0x12000404 +#define DIBUTTON_BBALLO_SPECIAL 0x12000405 +#define DIBUTTON_BBALLO_PLAYER 0x12000406 +#define DIBUTTON_BBALLO_BURST 0x12000407 +#define DIBUTTON_BBALLO_CALL 0x12000408 +#define DIBUTTON_BBALLO_MENU 0x120004FD + +#define DIHATSWITCH_BBALLO_GLANCE 0x12004601 +#define DIBUTTON_BBALLO_SCREEN 0x12004409 +#define DIBUTTON_BBALLO_PLAY 0x1200440A +#define DIBUTTON_BBALLO_JAB 0x1200440B +#define DIBUTTON_BBALLO_POST 0x1200440C +#define DIBUTTON_BBALLO_TIMEOUT 0x1200440D +#define DIBUTTON_BBALLO_SUBSTITUTE 0x1200440E +#define DIBUTTON_BBALLO_LEFT_LINK 0x1200C4E4 +#define DIBUTTON_BBALLO_RIGHT_LINK 0x1200C4EC +#define DIBUTTON_BBALLO_FORWARD_LINK 0x120144E0 +#define DIBUTTON_BBALLO_BACK_LINK 0x120144E8 +#define DIBUTTON_BBALLO_DEVICE 0x120044FE +#define DIBUTTON_BBALLO_PAUSE 0x120044FC + +#define DIVIRTUAL_SPORTS_BASKETBALL_DEFENSE 0x13000000 +#define DIAXIS_BBALLD_LATERAL 0x13008201 +#define DIAXIS_BBALLD_MOVE 0x13010202 +#define DIBUTTON_BBALLD_JUMP 0x13000401 +#define DIBUTTON_BBALLD_STEAL 0x13000402 +#define DIBUTTON_BBALLD_FAKE 0x13000403 +#define DIBUTTON_BBALLD_SPECIAL 0x13000404 +#define DIBUTTON_BBALLD_PLAYER 0x13000405 +#define DIBUTTON_BBALLD_BURST 0x13000406 +#define DIBUTTON_BBALLD_PLAY 0x13000407 +#define DIBUTTON_BBALLD_MENU 0x130004FD + +#define DIHATSWITCH_BBALLD_GLANCE 0x13004601 +#define DIBUTTON_BBALLD_TIMEOUT 0x13004408 +#define DIBUTTON_BBALLD_SUBSTITUTE 0x13004409 +#define DIBUTTON_BBALLD_LEFT_LINK 0x1300C4E4 +#define DIBUTTON_BBALLD_RIGHT_LINK 0x1300C4EC +#define DIBUTTON_BBALLD_FORWARD_LINK 0x130144E0 +#define DIBUTTON_BBALLD_BACK_LINK 0x130144E8 +#define DIBUTTON_BBALLD_DEVICE 0x130044FE +#define DIBUTTON_BBALLD_PAUSE 0x130044FC + +#define DIVIRTUAL_SPORTS_FOOTBALL_FIELD 0x14000000 +#define DIBUTTON_FOOTBALLP_PLAY 0x14000401 +#define DIBUTTON_FOOTBALLP_SELECT 0x14000402 +#define DIBUTTON_FOOTBALLP_HELP 0x14000403 +#define DIBUTTON_FOOTBALLP_MENU 0x140004FD + +#define DIBUTTON_FOOTBALLP_DEVICE 0x140044FE +#define DIBUTTON_FOOTBALLP_PAUSE 0x140044FC + +#define DIVIRTUAL_SPORTS_FOOTBALL_QBCK 0x15000000 +#define DIAXIS_FOOTBALLQ_LATERAL 0x15008201 +#define DIAXIS_FOOTBALLQ_MOVE 0x15010202 +#define DIBUTTON_FOOTBALLQ_SELECT 0x15000401 +#define DIBUTTON_FOOTBALLQ_SNAP 0x15000402 +#define DIBUTTON_FOOTBALLQ_JUMP 0x15000403 +#define DIBUTTON_FOOTBALLQ_SLIDE 0x15000404 +#define DIBUTTON_FOOTBALLQ_PASS 0x15000405 +#define DIBUTTON_FOOTBALLQ_FAKE 0x15000406 +#define DIBUTTON_FOOTBALLQ_MENU 0x150004FD + +#define DIBUTTON_FOOTBALLQ_FAKESNAP 0x15004407 +#define DIBUTTON_FOOTBALLQ_MOTION 0x15004408 +#define DIBUTTON_FOOTBALLQ_AUDIBLE 0x15004409 +#define DIBUTTON_FOOTBALLQ_LEFT_LINK 0x1500C4E4 +#define DIBUTTON_FOOTBALLQ_RIGHT_LINK 0x1500C4EC +#define DIBUTTON_FOOTBALLQ_FORWARD_LINK 0x150144E0 +#define DIBUTTON_FOOTBALLQ_BACK_LINK 0x150144E8 +#define DIBUTTON_FOOTBALLQ_DEVICE 0x150044FE +#define DIBUTTON_FOOTBALLQ_PAUSE 0x150044FC + +#define DIVIRTUAL_SPORTS_FOOTBALL_OFFENSE 0x16000000 +#define DIAXIS_FOOTBALLO_LATERAL 0x16008201 +#define DIAXIS_FOOTBALLO_MOVE 0x16010202 +#define DIBUTTON_FOOTBALLO_JUMP 0x16000401 +#define DIBUTTON_FOOTBALLO_LEFTARM 0x16000402 +#define DIBUTTON_FOOTBALLO_RIGHTARM 0x16000403 +#define DIBUTTON_FOOTBALLO_THROW 0x16000404 +#define DIBUTTON_FOOTBALLO_SPIN 0x16000405 +#define DIBUTTON_FOOTBALLO_MENU 0x160004FD + +#define DIBUTTON_FOOTBALLO_JUKE 0x16004406 +#define DIBUTTON_FOOTBALLO_SHOULDER 0x16004407 +#define DIBUTTON_FOOTBALLO_TURBO 0x16004408 +#define DIBUTTON_FOOTBALLO_DIVE 0x16004409 +#define DIBUTTON_FOOTBALLO_ZOOM 0x1600440A +#define DIBUTTON_FOOTBALLO_SUBSTITUTE 0x1600440B +#define DIBUTTON_FOOTBALLO_LEFT_LINK 0x1600C4E4 +#define DIBUTTON_FOOTBALLO_RIGHT_LINK 0x1600C4EC +#define DIBUTTON_FOOTBALLO_FORWARD_LINK 0x160144E0 +#define DIBUTTON_FOOTBALLO_BACK_LINK 0x160144E8 +#define DIBUTTON_FOOTBALLO_DEVICE 0x160044FE +#define DIBUTTON_FOOTBALLO_PAUSE 0x160044FC + +#define DIVIRTUAL_SPORTS_FOOTBALL_DEFENSE 0x17000000 +#define DIAXIS_FOOTBALLD_LATERAL 0x17008201 +#define DIAXIS_FOOTBALLD_MOVE 0x17010202 +#define DIBUTTON_FOOTBALLD_PLAY 0x17000401 +#define DIBUTTON_FOOTBALLD_SELECT 0x17000402 +#define DIBUTTON_FOOTBALLD_JUMP 0x17000403 +#define DIBUTTON_FOOTBALLD_TACKLE 0x17000404 +#define DIBUTTON_FOOTBALLD_FAKE 0x17000405 +#define DIBUTTON_FOOTBALLD_SUPERTACKLE 0x17000406 +#define DIBUTTON_FOOTBALLD_MENU 0x170004FD + +#define DIBUTTON_FOOTBALLD_SPIN 0x17004407 +#define DIBUTTON_FOOTBALLD_SWIM 0x17004408 +#define DIBUTTON_FOOTBALLD_BULLRUSH 0x17004409 +#define DIBUTTON_FOOTBALLD_RIP 0x1700440A +#define DIBUTTON_FOOTBALLD_AUDIBLE 0x1700440B +#define DIBUTTON_FOOTBALLD_ZOOM 0x1700440C +#define DIBUTTON_FOOTBALLD_SUBSTITUTE 0x1700440D +#define DIBUTTON_FOOTBALLD_LEFT_LINK 0x1700C4E4 +#define DIBUTTON_FOOTBALLD_RIGHT_LINK 0x1700C4EC +#define DIBUTTON_FOOTBALLD_FORWARD_LINK 0x170144E0 +#define DIBUTTON_FOOTBALLD_BACK_LINK 0x170144E8 +#define DIBUTTON_FOOTBALLD_DEVICE 0x170044FE +#define DIBUTTON_FOOTBALLD_PAUSE 0x170044FC + +#define DIVIRTUAL_SPORTS_GOLF 0x18000000 +#define DIAXIS_GOLF_LATERAL 0x18008201 +#define DIAXIS_GOLF_MOVE 0x18010202 +#define DIBUTTON_GOLF_SWING 0x18000401 +#define DIBUTTON_GOLF_SELECT 0x18000402 +#define DIBUTTON_GOLF_UP 0x18000403 +#define DIBUTTON_GOLF_DOWN 0x18000404 +#define DIBUTTON_GOLF_TERRAIN 0x18000405 +#define DIBUTTON_GOLF_FLYBY 0x18000406 +#define DIBUTTON_GOLF_MENU 0x180004FD + +#define DIHATSWITCH_GOLF_SCROLL 0x18004601 +#define DIBUTTON_GOLF_ZOOM 0x18004407 +#define DIBUTTON_GOLF_TIMEOUT 0x18004408 +#define DIBUTTON_GOLF_SUBSTITUTE 0x18004409 +#define DIBUTTON_GOLF_LEFT_LINK 0x1800C4E4 +#define DIBUTTON_GOLF_RIGHT_LINK 0x1800C4EC +#define DIBUTTON_GOLF_FORWARD_LINK 0x180144E0 +#define DIBUTTON_GOLF_BACK_LINK 0x180144E8 +#define DIBUTTON_GOLF_DEVICE 0x180044FE +#define DIBUTTON_GOLF_PAUSE 0x180044FC + +#define DIVIRTUAL_SPORTS_HOCKEY_OFFENSE 0x19000000 +#define DIAXIS_HOCKEYO_LATERAL 0x19008201 +#define DIAXIS_HOCKEYO_MOVE 0x19010202 +#define DIBUTTON_HOCKEYO_SHOOT 0x19000401 +#define DIBUTTON_HOCKEYO_PASS 0x19000402 +#define DIBUTTON_HOCKEYO_BURST 0x19000403 +#define DIBUTTON_HOCKEYO_SPECIAL 0x19000404 +#define DIBUTTON_HOCKEYO_FAKE 0x19000405 +#define DIBUTTON_HOCKEYO_MENU 0x190004FD + +#define DIHATSWITCH_HOCKEYO_SCROLL 0x19004601 +#define DIBUTTON_HOCKEYO_ZOOM 0x19004406 +#define DIBUTTON_HOCKEYO_STRATEGY 0x19004407 +#define DIBUTTON_HOCKEYO_TIMEOUT 0x19004408 +#define DIBUTTON_HOCKEYO_SUBSTITUTE 0x19004409 +#define DIBUTTON_HOCKEYO_LEFT_LINK 0x1900C4E4 +#define DIBUTTON_HOCKEYO_RIGHT_LINK 0x1900C4EC +#define DIBUTTON_HOCKEYO_FORWARD_LINK 0x190144E0 +#define DIBUTTON_HOCKEYO_BACK_LINK 0x190144E8 +#define DIBUTTON_HOCKEYO_DEVICE 0x190044FE +#define DIBUTTON_HOCKEYO_PAUSE 0x190044FC + +#define DIVIRTUAL_SPORTS_HOCKEY_DEFENSE 0x1A000000 +#define DIAXIS_HOCKEYD_LATERAL 0x1A008201 +#define DIAXIS_HOCKEYD_MOVE 0x1A010202 +#define DIBUTTON_HOCKEYD_PLAYER 0x1A000401 +#define DIBUTTON_HOCKEYD_STEAL 0x1A000402 +#define DIBUTTON_HOCKEYD_BURST 0x1A000403 +#define DIBUTTON_HOCKEYD_BLOCK 0x1A000404 +#define DIBUTTON_HOCKEYD_FAKE 0x1A000405 +#define DIBUTTON_HOCKEYD_MENU 0x1A0004FD + +#define DIHATSWITCH_HOCKEYD_SCROLL 0x1A004601 +#define DIBUTTON_HOCKEYD_ZOOM 0x1A004406 +#define DIBUTTON_HOCKEYD_STRATEGY 0x1A004407 +#define DIBUTTON_HOCKEYD_TIMEOUT 0x1A004408 +#define DIBUTTON_HOCKEYD_SUBSTITUTE 0x1A004409 +#define DIBUTTON_HOCKEYD_LEFT_LINK 0x1A00C4E4 +#define DIBUTTON_HOCKEYD_RIGHT_LINK 0x1A00C4EC +#define DIBUTTON_HOCKEYD_FORWARD_LINK 0x1A0144E0 +#define DIBUTTON_HOCKEYD_BACK_LINK 0x1A0144E8 +#define DIBUTTON_HOCKEYD_DEVICE 0x1A0044FE +#define DIBUTTON_HOCKEYD_PAUSE 0x1A0044FC + +#define DIVIRTUAL_SPORTS_HOCKEY_GOALIE 0x1B000000 +#define DIAXIS_HOCKEYG_LATERAL 0x1B008201 +#define DIAXIS_HOCKEYG_MOVE 0x1B010202 +#define DIBUTTON_HOCKEYG_PASS 0x1B000401 +#define DIBUTTON_HOCKEYG_POKE 0x1B000402 +#define DIBUTTON_HOCKEYG_STEAL 0x1B000403 +#define DIBUTTON_HOCKEYG_BLOCK 0x1B000404 +#define DIBUTTON_HOCKEYG_MENU 0x1B0004FD + +#define DIHATSWITCH_HOCKEYG_SCROLL 0x1B004601 +#define DIBUTTON_HOCKEYG_ZOOM 0x1B004405 +#define DIBUTTON_HOCKEYG_STRATEGY 0x1B004406 +#define DIBUTTON_HOCKEYG_TIMEOUT 0x1B004407 +#define DIBUTTON_HOCKEYG_SUBSTITUTE 0x1B004408 +#define DIBUTTON_HOCKEYG_LEFT_LINK 0x1B00C4E4 +#define DIBUTTON_HOCKEYG_RIGHT_LINK 0x1B00C4EC +#define DIBUTTON_HOCKEYG_FORWARD_LINK 0x1B0144E0 +#define DIBUTTON_HOCKEYG_BACK_LINK 0x1B0144E8 +#define DIBUTTON_HOCKEYG_DEVICE 0x1B0044FE +#define DIBUTTON_HOCKEYG_PAUSE 0x1B0044FC + +#define DIVIRTUAL_SPORTS_BIKING_MOUNTAIN 0x1C000000 +#define DIAXIS_BIKINGM_TURN 0x1C008201 +#define DIAXIS_BIKINGM_PEDAL 0x1C010202 +#define DIBUTTON_BIKINGM_JUMP 0x1C000401 +#define DIBUTTON_BIKINGM_CAMERA 0x1C000402 +#define DIBUTTON_BIKINGM_SPECIAL1 0x1C000403 +#define DIBUTTON_BIKINGM_SELECT 0x1C000404 +#define DIBUTTON_BIKINGM_SPECIAL2 0x1C000405 +#define DIBUTTON_BIKINGM_MENU 0x1C0004FD + +#define DIHATSWITCH_BIKINGM_SCROLL 0x1C004601 +#define DIBUTTON_BIKINGM_ZOOM 0x1C004406 +#define DIAXIS_BIKINGM_BRAKE 0x1C044203 +#define DIBUTTON_BIKINGM_LEFT_LINK 0x1C00C4E4 +#define DIBUTTON_BIKINGM_RIGHT_LINK 0x1C00C4EC +#define DIBUTTON_BIKINGM_FASTER_LINK 0x1C0144E0 +#define DIBUTTON_BIKINGM_SLOWER_LINK 0x1C0144E8 +#define DIBUTTON_BIKINGM_BRAKE_BUTTON_LINK 0x1C0444E8 +#define DIBUTTON_BIKINGM_DEVICE 0x1C0044FE +#define DIBUTTON_BIKINGM_PAUSE 0x1C0044FC + +#define DIVIRTUAL_SPORTS_SKIING 0x1D000000 +#define DIAXIS_SKIING_TURN 0x1D008201 +#define DIAXIS_SKIING_SPEED 0x1D010202 +#define DIBUTTON_SKIING_JUMP 0x1D000401 +#define DIBUTTON_SKIING_CROUCH 0x1D000402 +#define DIBUTTON_SKIING_CAMERA 0x1D000403 +#define DIBUTTON_SKIING_SPECIAL1 0x1D000404 +#define DIBUTTON_SKIING_SELECT 0x1D000405 +#define DIBUTTON_SKIING_SPECIAL2 0x1D000406 +#define DIBUTTON_SKIING_MENU 0x1D0004FD + +#define DIHATSWITCH_SKIING_GLANCE 0x1D004601 +#define DIBUTTON_SKIING_ZOOM 0x1D004407 +#define DIBUTTON_SKIING_LEFT_LINK 0x1D00C4E4 +#define DIBUTTON_SKIING_RIGHT_LINK 0x1D00C4EC +#define DIBUTTON_SKIING_FASTER_LINK 0x1D0144E0 +#define DIBUTTON_SKIING_SLOWER_LINK 0x1D0144E8 +#define DIBUTTON_SKIING_DEVICE 0x1D0044FE +#define DIBUTTON_SKIING_PAUSE 0x1D0044FC + +#define DIVIRTUAL_SPORTS_SOCCER_OFFENSE 0x1E000000 +#define DIAXIS_SOCCERO_LATERAL 0x1E008201 +#define DIAXIS_SOCCERO_MOVE 0x1E010202 +#define DIAXIS_SOCCERO_BEND 0x1E018203 +#define DIBUTTON_SOCCERO_SHOOT 0x1E000401 +#define DIBUTTON_SOCCERO_PASS 0x1E000402 +#define DIBUTTON_SOCCERO_FAKE 0x1E000403 +#define DIBUTTON_SOCCERO_PLAYER 0x1E000404 +#define DIBUTTON_SOCCERO_SPECIAL1 0x1E000405 +#define DIBUTTON_SOCCERO_SELECT 0x1E000406 +#define DIBUTTON_SOCCERO_MENU 0x1E0004FD + +#define DIHATSWITCH_SOCCERO_GLANCE 0x1E004601 +#define DIBUTTON_SOCCERO_SUBSTITUTE 0x1E004407 +#define DIBUTTON_SOCCERO_SHOOTLOW 0x1E004408 +#define DIBUTTON_SOCCERO_SHOOTHIGH 0x1E004409 +#define DIBUTTON_SOCCERO_PASSTHRU 0x1E00440A +#define DIBUTTON_SOCCERO_SPRINT 0x1E00440B +#define DIBUTTON_SOCCERO_CONTROL 0x1E00440C +#define DIBUTTON_SOCCERO_HEAD 0x1E00440D +#define DIBUTTON_SOCCERO_LEFT_LINK 0x1E00C4E4 +#define DIBUTTON_SOCCERO_RIGHT_LINK 0x1E00C4EC +#define DIBUTTON_SOCCERO_FORWARD_LINK 0x1E0144E0 +#define DIBUTTON_SOCCERO_BACK_LINK 0x1E0144E8 +#define DIBUTTON_SOCCERO_DEVICE 0x1E0044FE +#define DIBUTTON_SOCCERO_PAUSE 0x1E0044FC + +#define DIVIRTUAL_SPORTS_SOCCER_DEFENSE 0x1F000000 +#define DIAXIS_SOCCERD_LATERAL 0x1F008201 +#define DIAXIS_SOCCERD_MOVE 0x1F010202 +#define DIBUTTON_SOCCERD_BLOCK 0x1F000401 +#define DIBUTTON_SOCCERD_STEAL 0x1F000402 +#define DIBUTTON_SOCCERD_FAKE 0x1F000403 +#define DIBUTTON_SOCCERD_PLAYER 0x1F000404 +#define DIBUTTON_SOCCERD_SPECIAL 0x1F000405 +#define DIBUTTON_SOCCERD_SELECT 0x1F000406 +#define DIBUTTON_SOCCERD_SLIDE 0x1F000407 +#define DIBUTTON_SOCCERD_MENU 0x1F0004FD + +#define DIHATSWITCH_SOCCERD_GLANCE 0x1F004601 +#define DIBUTTON_SOCCERD_FOUL 0x1F004408 +#define DIBUTTON_SOCCERD_HEAD 0x1F004409 +#define DIBUTTON_SOCCERD_CLEAR 0x1F00440A +#define DIBUTTON_SOCCERD_GOALIECHARGE 0x1F00440B +#define DIBUTTON_SOCCERD_SUBSTITUTE 0x1F00440C +#define DIBUTTON_SOCCERD_LEFT_LINK 0x1F00C4E4 +#define DIBUTTON_SOCCERD_RIGHT_LINK 0x1F00C4EC +#define DIBUTTON_SOCCERD_FORWARD_LINK 0x1F0144E0 +#define DIBUTTON_SOCCERD_BACK_LINK 0x1F0144E8 +#define DIBUTTON_SOCCERD_DEVICE 0x1F0044FE +#define DIBUTTON_SOCCERD_PAUSE 0x1F0044FC + +#define DIVIRTUAL_SPORTS_RACQUET 0x20000000 +#define DIAXIS_RACQUET_LATERAL 0x20008201 +#define DIAXIS_RACQUET_MOVE 0x20010202 +#define DIBUTTON_RACQUET_SWING 0x20000401 +#define DIBUTTON_RACQUET_BACKSWING 0x20000402 +#define DIBUTTON_RACQUET_SMASH 0x20000403 +#define DIBUTTON_RACQUET_SPECIAL 0x20000404 +#define DIBUTTON_RACQUET_SELECT 0x20000405 +#define DIBUTTON_RACQUET_MENU 0x200004FD + +#define DIHATSWITCH_RACQUET_GLANCE 0x20004601 +#define DIBUTTON_RACQUET_TIMEOUT 0x20004406 +#define DIBUTTON_RACQUET_SUBSTITUTE 0x20004407 +#define DIBUTTON_RACQUET_LEFT_LINK 0x2000C4E4 +#define DIBUTTON_RACQUET_RIGHT_LINK 0x2000C4EC +#define DIBUTTON_RACQUET_FORWARD_LINK 0x200144E0 +#define DIBUTTON_RACQUET_BACK_LINK 0x200144E8 +#define DIBUTTON_RACQUET_DEVICE 0x200044FE +#define DIBUTTON_RACQUET_PAUSE 0x200044FC + +#define DIVIRTUAL_ARCADE_SIDE2SIDE 0x21000000 +#define DIAXIS_ARCADES_LATERAL 0x21008201 +#define DIAXIS_ARCADES_MOVE 0x21010202 +#define DIBUTTON_ARCADES_THROW 0x21000401 +#define DIBUTTON_ARCADES_CARRY 0x21000402 +#define DIBUTTON_ARCADES_ATTACK 0x21000403 +#define DIBUTTON_ARCADES_SPECIAL 0x21000404 +#define DIBUTTON_ARCADES_SELECT 0x21000405 +#define DIBUTTON_ARCADES_MENU 0x210004FD + +#define DIHATSWITCH_ARCADES_VIEW 0x21004601 +#define DIBUTTON_ARCADES_LEFT_LINK 0x2100C4E4 +#define DIBUTTON_ARCADES_RIGHT_LINK 0x2100C4EC +#define DIBUTTON_ARCADES_FORWARD_LINK 0x210144E0 +#define DIBUTTON_ARCADES_BACK_LINK 0x210144E8 +#define DIBUTTON_ARCADES_VIEW_UP_LINK 0x2107C4E0 +#define DIBUTTON_ARCADES_VIEW_DOWN_LINK 0x2107C4E8 +#define DIBUTTON_ARCADES_VIEW_LEFT_LINK 0x2107C4E4 +#define DIBUTTON_ARCADES_VIEW_RIGHT_LINK 0x2107C4EC +#define DIBUTTON_ARCADES_DEVICE 0x210044FE +#define DIBUTTON_ARCADES_PAUSE 0x210044FC + +#define DIVIRTUAL_ARCADE_PLATFORM 0x22000000 +#define DIAXIS_ARCADEP_LATERAL 0x22008201 +#define DIAXIS_ARCADEP_MOVE 0x22010202 +#define DIBUTTON_ARCADEP_JUMP 0x22000401 +#define DIBUTTON_ARCADEP_FIRE 0x22000402 +#define DIBUTTON_ARCADEP_CROUCH 0x22000403 +#define DIBUTTON_ARCADEP_SPECIAL 0x22000404 +#define DIBUTTON_ARCADEP_SELECT 0x22000405 +#define DIBUTTON_ARCADEP_MENU 0x220004FD + +#define DIHATSWITCH_ARCADEP_VIEW 0x22004601 +#define DIBUTTON_ARCADEP_FIRESECONDARY 0x22004406 +#define DIBUTTON_ARCADEP_LEFT_LINK 0x2200C4E4 +#define DIBUTTON_ARCADEP_RIGHT_LINK 0x2200C4EC +#define DIBUTTON_ARCADEP_FORWARD_LINK 0x220144E0 +#define DIBUTTON_ARCADEP_BACK_LINK 0x220144E8 +#define DIBUTTON_ARCADEP_VIEW_UP_LINK 0x2207C4E0 +#define DIBUTTON_ARCADEP_VIEW_DOWN_LINK 0x2207C4E8 +#define DIBUTTON_ARCADEP_VIEW_LEFT_LINK 0x2207C4E4 +#define DIBUTTON_ARCADEP_VIEW_RIGHT_LINK 0x2207C4EC +#define DIBUTTON_ARCADEP_DEVICE 0x220044FE +#define DIBUTTON_ARCADEP_PAUSE 0x220044FC + +#define DIVIRTUAL_CAD_2DCONTROL 0x23000000 +#define DIAXIS_2DCONTROL_LATERAL 0x23008201 +#define DIAXIS_2DCONTROL_MOVE 0x23010202 +#define DIAXIS_2DCONTROL_INOUT 0x23018203 +#define DIBUTTON_2DCONTROL_SELECT 0x23000401 +#define DIBUTTON_2DCONTROL_SPECIAL1 0x23000402 +#define DIBUTTON_2DCONTROL_SPECIAL 0x23000403 +#define DIBUTTON_2DCONTROL_SPECIAL2 0x23000404 +#define DIBUTTON_2DCONTROL_MENU 0x230004FD + +#define DIHATSWITCH_2DCONTROL_HATSWITCH 0x23004601 +#define DIAXIS_2DCONTROL_ROTATEZ 0x23024204 +#define DIBUTTON_2DCONTROL_DISPLAY 0x23004405 +#define DIBUTTON_2DCONTROL_DEVICE 0x230044FE +#define DIBUTTON_2DCONTROL_PAUSE 0x230044FC + +#define DIVIRTUAL_CAD_3DCONTROL 0x24000000 +#define DIAXIS_3DCONTROL_LATERAL 0x24008201 +#define DIAXIS_3DCONTROL_MOVE 0x24010202 +#define DIAXIS_3DCONTROL_INOUT 0x24018203 +#define DIBUTTON_3DCONTROL_SELECT 0x24000401 +#define DIBUTTON_3DCONTROL_SPECIAL1 0x24000402 +#define DIBUTTON_3DCONTROL_SPECIAL 0x24000403 +#define DIBUTTON_3DCONTROL_SPECIAL2 0x24000404 +#define DIBUTTON_3DCONTROL_MENU 0x240004FD + +#define DIHATSWITCH_3DCONTROL_HATSWITCH 0x24004601 +#define DIAXIS_3DCONTROL_ROTATEX 0x24034204 +#define DIAXIS_3DCONTROL_ROTATEY 0x2402C205 +#define DIAXIS_3DCONTROL_ROTATEZ 0x24024206 +#define DIBUTTON_3DCONTROL_DISPLAY 0x24004405 +#define DIBUTTON_3DCONTROL_DEVICE 0x240044FE +#define DIBUTTON_3DCONTROL_PAUSE 0x240044FC + +#define DIVIRTUAL_CAD_FLYBY 0x25000000 +#define DIAXIS_CADF_LATERAL 0x25008201 +#define DIAXIS_CADF_MOVE 0x25010202 +#define DIAXIS_CADF_INOUT 0x25018203 +#define DIBUTTON_CADF_SELECT 0x25000401 +#define DIBUTTON_CADF_SPECIAL1 0x25000402 +#define DIBUTTON_CADF_SPECIAL 0x25000403 +#define DIBUTTON_CADF_SPECIAL2 0x25000404 +#define DIBUTTON_CADF_MENU 0x250004FD + +#define DIHATSWITCH_CADF_HATSWITCH 0x25004601 +#define DIAXIS_CADF_ROTATEX 0x25034204 +#define DIAXIS_CADF_ROTATEY 0x2502C205 +#define DIAXIS_CADF_ROTATEZ 0x25024206 +#define DIBUTTON_CADF_DISPLAY 0x25004405 +#define DIBUTTON_CADF_DEVICE 0x250044FE +#define DIBUTTON_CADF_PAUSE 0x250044FC + +#define DIVIRTUAL_CAD_MODEL 0x26000000 +#define DIAXIS_CADM_LATERAL 0x26008201 +#define DIAXIS_CADM_MOVE 0x26010202 +#define DIAXIS_CADM_INOUT 0x26018203 +#define DIBUTTON_CADM_SELECT 0x26000401 +#define DIBUTTON_CADM_SPECIAL1 0x26000402 +#define DIBUTTON_CADM_SPECIAL 0x26000403 +#define DIBUTTON_CADM_SPECIAL2 0x26000404 +#define DIBUTTON_CADM_MENU 0x260004FD + +#define DIHATSWITCH_CADM_HATSWITCH 0x26004601 +#define DIAXIS_CADM_ROTATEX 0x26034204 +#define DIAXIS_CADM_ROTATEY 0x2602C205 +#define DIAXIS_CADM_ROTATEZ 0x26024206 +#define DIBUTTON_CADM_DISPLAY 0x26004405 +#define DIBUTTON_CADM_DEVICE 0x260044FE +#define DIBUTTON_CADM_PAUSE 0x260044FC + +#define DIVIRTUAL_REMOTE_CONTROL 0x27000000 +#define DIAXIS_REMOTE_SLIDER 0x27050201 +#define DIBUTTON_REMOTE_MUTE 0x27000401 +#define DIBUTTON_REMOTE_SELECT 0x27000402 +#define DIBUTTON_REMOTE_PLAY 0x27002403 +#define DIBUTTON_REMOTE_CUE 0x27002404 +#define DIBUTTON_REMOTE_REVIEW 0x27002405 +#define DIBUTTON_REMOTE_CHANGE 0x27002406 +#define DIBUTTON_REMOTE_RECORD 0x27002407 +#define DIBUTTON_REMOTE_MENU 0x270004FD + +#define DIAXIS_REMOTE_SLIDER2 0x27054202 +#define DIBUTTON_REMOTE_TV 0x27005C08 +#define DIBUTTON_REMOTE_CABLE 0x27005C09 +#define DIBUTTON_REMOTE_CD 0x27005C0A +#define DIBUTTON_REMOTE_VCR 0x27005C0B +#define DIBUTTON_REMOTE_TUNER 0x27005C0C +#define DIBUTTON_REMOTE_DVD 0x27005C0D +#define DIBUTTON_REMOTE_ADJUST 0x27005C0E +#define DIBUTTON_REMOTE_DIGIT0 0x2700540F +#define DIBUTTON_REMOTE_DIGIT1 0x27005410 +#define DIBUTTON_REMOTE_DIGIT2 0x27005411 +#define DIBUTTON_REMOTE_DIGIT3 0x27005412 +#define DIBUTTON_REMOTE_DIGIT4 0x27005413 +#define DIBUTTON_REMOTE_DIGIT5 0x27005414 +#define DIBUTTON_REMOTE_DIGIT6 0x27005415 +#define DIBUTTON_REMOTE_DIGIT7 0x27005416 +#define DIBUTTON_REMOTE_DIGIT8 0x27005417 +#define DIBUTTON_REMOTE_DIGIT9 0x27005418 +#define DIBUTTON_REMOTE_DEVICE 0x270044FE +#define DIBUTTON_REMOTE_PAUSE 0x270044FC + +#define DIVIRTUAL_BROWSER_CONTROL 0x28000000 +#define DIAXIS_BROWSER_LATERAL 0x28008201 +#define DIAXIS_BROWSER_MOVE 0x28010202 +#define DIBUTTON_BROWSER_SELECT 0x28000401 +#define DIAXIS_BROWSER_VIEW 0x28018203 +#define DIBUTTON_BROWSER_REFRESH 0x28000402 +#define DIBUTTON_BROWSER_MENU 0x280004FD + +#define DIBUTTON_BROWSER_SEARCH 0x28004403 +#define DIBUTTON_BROWSER_STOP 0x28004404 +#define DIBUTTON_BROWSER_HOME 0x28004405 +#define DIBUTTON_BROWSER_FAVORITES 0x28004406 +#define DIBUTTON_BROWSER_NEXT 0x28004407 +#define DIBUTTON_BROWSER_PREVIOUS 0x28004408 +#define DIBUTTON_BROWSER_HISTORY 0x28004409 +#define DIBUTTON_BROWSER_PRINT 0x2800440A +#define DIBUTTON_BROWSER_DEVICE 0x280044FE +#define DIBUTTON_BROWSER_PAUSE 0x280044FC + +#define DIVIRTUAL_DRIVING_MECHA 0x29000000 +#define DIAXIS_MECHA_STEER 0x29008201 +#define DIAXIS_MECHA_TORSO 0x29010202 +#define DIAXIS_MECHA_ROTATE 0x29020203 +#define DIAXIS_MECHA_THROTTLE 0x29038204 +#define DIBUTTON_MECHA_FIRE 0x29000401 +#define DIBUTTON_MECHA_WEAPONS 0x29000402 +#define DIBUTTON_MECHA_TARGET 0x29000403 +#define DIBUTTON_MECHA_REVERSE 0x29000404 +#define DIBUTTON_MECHA_ZOOM 0x29000405 +#define DIBUTTON_MECHA_JUMP 0x29000406 +#define DIBUTTON_MECHA_MENU 0x290004FD + +#define DIBUTTON_MECHA_CENTER 0x29004407 +#define DIHATSWITCH_MECHA_GLANCE 0x29004601 +#define DIBUTTON_MECHA_VIEW 0x29004408 +#define DIBUTTON_MECHA_FIRESECONDARY 0x29004409 +#define DIBUTTON_MECHA_LEFT_LINK 0x2900C4E4 +#define DIBUTTON_MECHA_RIGHT_LINK 0x2900C4EC +#define DIBUTTON_MECHA_FORWARD_LINK 0x290144E0 +#define DIBUTTON_MECHA_BACK_LINK 0x290144E8 +#define DIBUTTON_MECHA_ROTATE_LEFT_LINK 0x290244E4 +#define DIBUTTON_MECHA_ROTATE_RIGHT_LINK 0x290244EC +#define DIBUTTON_MECHA_FASTER_LINK 0x2903C4E0 +#define DIBUTTON_MECHA_SLOWER_LINK 0x2903C4E8 +#define DIBUTTON_MECHA_DEVICE 0x290044FE +#define DIBUTTON_MECHA_PAUSE 0x290044FC + +#define DIAXIS_ANY_X_1 0xFF00C201 +#define DIAXIS_ANY_X_2 0xFF00C202 +#define DIAXIS_ANY_Y_1 0xFF014201 +#define DIAXIS_ANY_Y_2 0xFF014202 +#define DIAXIS_ANY_Z_1 0xFF01C201 +#define DIAXIS_ANY_Z_2 0xFF01C202 +#define DIAXIS_ANY_R_1 0xFF024201 +#define DIAXIS_ANY_R_2 0xFF024202 +#define DIAXIS_ANY_U_1 0xFF02C201 +#define DIAXIS_ANY_U_2 0xFF02C202 +#define DIAXIS_ANY_V_1 0xFF034201 +#define DIAXIS_ANY_V_2 0xFF034202 +#define DIAXIS_ANY_A_1 0xFF03C201 +#define DIAXIS_ANY_A_2 0xFF03C202 +#define DIAXIS_ANY_B_1 0xFF044201 +#define DIAXIS_ANY_B_2 0xFF044202 +#define DIAXIS_ANY_C_1 0xFF04C201 +#define DIAXIS_ANY_C_2 0xFF04C202 +#define DIAXIS_ANY_S_1 0xFF054201 +#define DIAXIS_ANY_S_2 0xFF054202 + +#define DIAXIS_ANY_1 0xFF004201 +#define DIAXIS_ANY_2 0xFF004202 +#define DIAXIS_ANY_3 0xFF004203 +#define DIAXIS_ANY_4 0xFF004204 + +#define DIPOV_ANY_1 0xFF004601 +#define DIPOV_ANY_2 0xFF004602 +#define DIPOV_ANY_3 0xFF004603 +#define DIPOV_ANY_4 0xFF004604 + +#define DIBUTTON_ANY(instance) ( 0xFF004400 | instance ) + +#ifdef __cplusplus +}; +#endif + +#endif + +#ifdef _INC_MMSYSTEM +#ifndef MMNOJOY + +#ifndef __VJOYDX_INCLUDED__ +#define __VJOYDX_INCLUDED__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define JOY_PASSDRIVERDATA 0x10000000l + +WINMMAPI MMRESULT WINAPI joyConfigChanged( DWORD dwFlags ); + +#define JOY_HWS_ISHEADTRACKER 0x02000000l + +#define JOY_HWS_ISGAMEPORTDRIVER 0x04000000l + +#define JOY_HWS_ISANALOGPORTDRIVER 0x08000000l + +#define JOY_HWS_AUTOLOAD 0x10000000l + +#define JOY_HWS_NODEVNODE 0x20000000l + +#define JOY_HWS_ISGAMEPORTBUS 0x80000000l +#define JOY_HWS_GAMEPORTBUSBUSY 0x00000001l + +#define JOY_US_VOLATILE 0x00000008L + +#ifdef __cplusplus +}; +#endif + +#endif + +#endif +#endif + +#ifndef DIJ_RINGZERO + +#ifdef _INC_MMDDK +#ifndef MMNOJOYDEV + +#ifndef __VJOYDXD_INCLUDED__ +#define __VJOYDXD_INCLUDED__ + +#define JOY_OEMPOLL_PASSDRIVERDATA 7 + +#endif + +#endif +#endif + +#endif + +#endif + +#undef _THIS +#define _THIS SDL_AudioDevice *this + +#undef SDLAUDIOHIDDEN +#define SDLAUDIOHIDDEN ((struct SDL_PrivateAudioDataDSOUND*)this->hidden) +struct SDL_PrivateAudioDataDSOUND +{ + LPDIRECTSOUND sound; + LPDIRECTSOUNDBUFFER mixbuf; + int num_buffers; + int mixlen; + DWORD lastchunk; + Uint8 *locked_buf; +}; + +#endif + +#ifndef WAVE_FORMAT_IEEE_FLOAT +#define WAVE_FORMAT_IEEE_FLOAT 0x0003 +#endif + +static void* DSoundDLL = NULL; +typedef HRESULT(WINAPI*fnDirectSoundCreate8)(LPGUID,LPDIRECTSOUND*,LPUNKNOWN); +typedef HRESULT(WINAPI*fnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID); +typedef HRESULT(WINAPI*fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW,LPVOID); +static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL; +static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL; +static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL; + +static void +DSOUND_Unload(void) +{ + pDirectSoundCreate8 = NULL; + pDirectSoundEnumerateW = NULL; + pDirectSoundCaptureEnumerateW = NULL; + + if (DSoundDLL != NULL) { + SDL_UnloadObject(DSoundDLL); + DSoundDLL = NULL; + } +} + +static int +DSOUND_Load(void) +{ + int loaded = 0; + + DSOUND_Unload(); + + DSoundDLL = SDL_LoadObject("DSOUND.DLL"); + if (DSoundDLL == NULL) { + SDL_SetError("DirectSound: failed to load DSOUND.DLL"); + } else { + + #define DSOUNDLOAD(f) { \ + p##f = (fn##f) SDL_LoadFunction(DSoundDLL, #f); \ + if (!p##f) loaded = 0; \ + } + loaded = 1; + DSOUNDLOAD(DirectSoundCreate8); + DSOUNDLOAD(DirectSoundEnumerateW); + DSOUNDLOAD(DirectSoundCaptureEnumerateW); + #undef DSOUNDLOAD + + if (!loaded) { + SDL_SetError("DirectSound: System doesn't appear to have DX8."); + } + } + + if (!loaded) { + DSOUND_Unload(); + } + + return loaded; +} + +static int +SetDSerror(const char *function, int code) +{ + static const char *error; + static char errbuf[1024]; + + errbuf[0] = 0; + switch (code) { + case E_NOINTERFACE: + error = "Unsupported interface -- Is DirectX 8.0 or later installed?"; + break; + case DSERR_ALLOCATED: + error = "Audio device in use"; + break; + case DSERR_BADFORMAT: + error = "Unsupported audio format"; + break; + case DSERR_BUFFERLOST: + error = "Mixing buffer was lost"; + break; + case DSERR_CONTROLUNAVAIL: + error = "Control requested is not available"; + break; + case DSERR_INVALIDCALL: + error = "Invalid call for the current state"; + break; + case DSERR_INVALIDPARAM: + error = "Invalid parameter"; + break; + case DSERR_NODRIVER: + error = "No audio device found"; + break; + case DSERR_OUTOFMEMORY: + error = "Out of memory"; + break; + case DSERR_PRIOLEVELNEEDED: + error = "Caller doesn't have priority"; + break; + case DSERR_UNSUPPORTED: + error = "Function not supported"; + break; + default: + SDL_snprintf(errbuf, SDL_arraysize(errbuf), + "%s: Unknown DirectSound error: 0x%x", function, code); + break; + } + if (!errbuf[0]) { + SDL_snprintf(errbuf, SDL_arraysize(errbuf), "%s: %s", function, + error); + } + return SDL_SetError("%s", errbuf); +} + +static BOOL CALLBACK +FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID data) +{ + SDL_AddAudioDevice addfn = (SDL_AddAudioDevice) data; + if (guid != NULL) { + char *str = WIN_StringToUTF8(desc); + if (str != NULL) { + addfn(str); + SDL_free(str); + } + } + return TRUE; +} + +static void +DSOUND_DetectDevices(int iscapture, SDL_AddAudioDevice addfn) +{ + if (iscapture) { + pDirectSoundCaptureEnumerateW(FindAllDevs, addfn); + } else { + pDirectSoundEnumerateW(FindAllDevs, addfn); + } +} + +static void +DSOUND_WaitDevice(_THIS) +{ + DWORD status = 0; + DWORD cursor = 0; + DWORD junk = 0; + HRESULT result = DS_OK; + + result = IDirectSoundBuffer_GetCurrentPosition(SDLAUDIOHIDDEN->mixbuf, + &junk, &cursor); + if (result != DS_OK) { + if (result == DSERR_BUFFERLOST) { + IDirectSoundBuffer_Restore(SDLAUDIOHIDDEN->mixbuf); + } +#ifdef DEBUG_SOUND + SetDSerror("DirectSound GetCurrentPosition", result); +#endif + return; + } + + while ((cursor / SDLAUDIOHIDDEN->mixlen) == SDLAUDIOHIDDEN->lastchunk) { + + SDL_Delay(1); + + IDirectSoundBuffer_GetStatus(SDLAUDIOHIDDEN->mixbuf, &status); + if ((status & DSBSTATUS_BUFFERLOST)) { + IDirectSoundBuffer_Restore(SDLAUDIOHIDDEN->mixbuf); + IDirectSoundBuffer_GetStatus(SDLAUDIOHIDDEN->mixbuf, &status); + if ((status & DSBSTATUS_BUFFERLOST)) { + break; + } + } + if (!(status & DSBSTATUS_PLAYING)) { + result = IDirectSoundBuffer_Play(SDLAUDIOHIDDEN->mixbuf, 0, 0, + DSBPLAY_LOOPING); + if (result == DS_OK) { + continue; + } +#ifdef DEBUG_SOUND + SetDSerror("DirectSound Play", result); +#endif + return; + } + + result = IDirectSoundBuffer_GetCurrentPosition(SDLAUDIOHIDDEN->mixbuf, + &junk, &cursor); + if (result != DS_OK) { + SetDSerror("DirectSound GetCurrentPosition", result); + return; + } + } +} + +static void +DSOUND_PlayDevice(_THIS) +{ + + if (SDLAUDIOHIDDEN->locked_buf) { + IDirectSoundBuffer_Unlock(SDLAUDIOHIDDEN->mixbuf, + SDLAUDIOHIDDEN->locked_buf, + SDLAUDIOHIDDEN->mixlen, NULL, 0); + } + +} + +static Uint8 * +DSOUND_GetDeviceBuf(_THIS) +{ + DWORD cursor = 0; + DWORD junk = 0; + HRESULT result = DS_OK; + DWORD rawlen = 0; + + SDLAUDIOHIDDEN->locked_buf = NULL; + result = IDirectSoundBuffer_GetCurrentPosition(SDLAUDIOHIDDEN->mixbuf, + &junk, &cursor); + if (result == DSERR_BUFFERLOST) { + IDirectSoundBuffer_Restore(SDLAUDIOHIDDEN->mixbuf); + result = IDirectSoundBuffer_GetCurrentPosition(SDLAUDIOHIDDEN->mixbuf, + &junk, &cursor); + } + if (result != DS_OK) { + SetDSerror("DirectSound GetCurrentPosition", result); + return (NULL); + } + cursor /= SDLAUDIOHIDDEN->mixlen; +#ifdef DEBUG_SOUND + + { + DWORD spot = cursor; + if (spot < SDLAUDIOHIDDEN->lastchunk) { + spot += SDLAUDIOHIDDEN->num_buffers; + } + if (spot > SDLAUDIOHIDDEN->lastchunk + 1) { + fprintf(stderr, "Audio dropout, missed %d fragments\n", + (spot - (SDLAUDIOHIDDEN->lastchunk + 1))); + } + } +#endif + SDLAUDIOHIDDEN->lastchunk = cursor; + cursor = (cursor + 1) % SDLAUDIOHIDDEN->num_buffers; + cursor *= SDLAUDIOHIDDEN->mixlen; + + result = IDirectSoundBuffer_Lock(SDLAUDIOHIDDEN->mixbuf, cursor, + SDLAUDIOHIDDEN->mixlen, + (LPVOID *) & SDLAUDIOHIDDEN->locked_buf, + &rawlen, NULL, &junk, 0); + if (result == DSERR_BUFFERLOST) { + IDirectSoundBuffer_Restore(SDLAUDIOHIDDEN->mixbuf); + result = IDirectSoundBuffer_Lock(SDLAUDIOHIDDEN->mixbuf, cursor, + SDLAUDIOHIDDEN->mixlen, + (LPVOID *) & + SDLAUDIOHIDDEN->locked_buf, &rawlen, NULL, + &junk, 0); + } + if (result != DS_OK) { + SetDSerror("DirectSound Lock", result); + return (NULL); + } + return (SDLAUDIOHIDDEN->locked_buf); +} + +static void +DSOUND_WaitDone(_THIS) +{ + Uint8 *stream = DSOUND_GetDeviceBuf(this); + + if (stream != NULL) { + SDL_memset(stream, this->spec.silence, SDLAUDIOHIDDEN->mixlen); + DSOUND_PlayDevice(this); + } + DSOUND_WaitDevice(this); + + IDirectSoundBuffer_Stop(SDLAUDIOHIDDEN->mixbuf); +} + +static void +DSOUND_CloseDevice(_THIS) +{ + if (SDLAUDIOHIDDEN != NULL) { + if (SDLAUDIOHIDDEN->sound != NULL) { + if (SDLAUDIOHIDDEN->mixbuf != NULL) { + + IDirectSoundBuffer_Release(SDLAUDIOHIDDEN->mixbuf); + SDLAUDIOHIDDEN->mixbuf = NULL; + } + IDirectSound_Release(SDLAUDIOHIDDEN->sound); + SDLAUDIOHIDDEN->sound = NULL; + } + + SDL_free(SDLAUDIOHIDDEN); + this->hidden = NULL; + } +} + +static int +CreateSecondary(_THIS, HWND focus) +{ + LPDIRECTSOUND sndObj = SDLAUDIOHIDDEN->sound; + LPDIRECTSOUNDBUFFER *sndbuf = &SDLAUDIOHIDDEN->mixbuf; + Uint32 chunksize = this->spec.size; + const int numchunks = 8; + HRESULT result = DS_OK; + DSBUFFERDESC format; + LPVOID pvAudioPtr1, pvAudioPtr2; + DWORD dwAudioBytes1, dwAudioBytes2; + WAVEFORMATEX wfmt; + + SDL_zero(wfmt); + + if (SDL_AUDIO_ISFLOAT(this->spec.format)) { + wfmt.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + } else { + wfmt.wFormatTag = WAVE_FORMAT_PCM; + } + + wfmt.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format); + wfmt.nChannels = this->spec.channels; + wfmt.nSamplesPerSec = this->spec.freq; + wfmt.nBlockAlign = wfmt.nChannels * (wfmt.wBitsPerSample / 8); + wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign; + + SDL_CalculateAudioSpec(&this->spec); + + if (focus) { + result = IDirectSound_SetCooperativeLevel(sndObj, + focus, DSSCL_PRIORITY); + } else { + result = IDirectSound_SetCooperativeLevel(sndObj, + GetDesktopWindow(), + DSSCL_NORMAL); + } + if (result != DS_OK) { + return SetDSerror("DirectSound SetCooperativeLevel", result); + } + + SDL_zero(format); + format.dwSize = sizeof(format); + format.dwFlags = DSBCAPS_GETCURRENTPOSITION2; + if (!focus) { + format.dwFlags |= DSBCAPS_GLOBALFOCUS; + } else { + format.dwFlags |= DSBCAPS_STICKYFOCUS; + } + format.dwBufferBytes = numchunks * chunksize; + if ((format.dwBufferBytes < DSBSIZE_MIN) || + (format.dwBufferBytes > DSBSIZE_MAX)) { + return SDL_SetError("Sound buffer size must be between %d and %d", + DSBSIZE_MIN / numchunks, DSBSIZE_MAX / numchunks); + } + format.dwReserved = 0; + format.lpwfxFormat = &wfmt; + result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL); + if (result != DS_OK) { + return SetDSerror("DirectSound CreateSoundBuffer", result); + } + IDirectSoundBuffer_SetFormat(*sndbuf, &wfmt); + + result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes, + (LPVOID *) & pvAudioPtr1, &dwAudioBytes1, + (LPVOID *) & pvAudioPtr2, &dwAudioBytes2, + DSBLOCK_ENTIREBUFFER); + if (result == DS_OK) { + SDL_memset(pvAudioPtr1, this->spec.silence, dwAudioBytes1); + IDirectSoundBuffer_Unlock(*sndbuf, + (LPVOID) pvAudioPtr1, dwAudioBytes1, + (LPVOID) pvAudioPtr2, dwAudioBytes2); + } + + return (numchunks); +} + +typedef struct FindDevGUIDData +{ + const char *devname; + GUID guid; + int found; +} FindDevGUIDData; + +static BOOL CALLBACK +FindDevGUID(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID _data) +{ + if (guid != NULL) { + FindDevGUIDData *data = (FindDevGUIDData *) _data; + char *str = WIN_StringToUTF8(desc); + const int match = (SDL_strcmp(str, data->devname) == 0); + SDL_free(str); + if (match) { + data->found = 1; + SDL_memcpy(&data->guid, guid, sizeof (data->guid)); + return FALSE; + } + } + return TRUE; +} + +static int +DSOUND_OpenDevice(_THIS, const char *devname, int iscapture) +{ + HRESULT result; + SDL_bool valid_format = SDL_FALSE; + SDL_bool tried_format = SDL_FALSE; + SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); + FindDevGUIDData devguid; + LPGUID guid = NULL; + + if (devname != NULL) { + devguid.found = 0; + devguid.devname = devname; + if (iscapture) + pDirectSoundCaptureEnumerateW(FindDevGUID, &devguid); + else + pDirectSoundEnumerateW(FindDevGUID, &devguid); + + if (!devguid.found) { + return SDL_SetError("DirectSound: Requested device not found"); + } + guid = &devguid.guid; + } + + this->hidden = (struct SDL_PrivateAudioData *) + SDL_malloc((sizeof *SDLAUDIOHIDDEN)); + if (SDLAUDIOHIDDEN == NULL) { + return SDL_OutOfMemory(); + } + SDL_memset(SDLAUDIOHIDDEN, 0, (sizeof *SDLAUDIOHIDDEN)); + + result = pDirectSoundCreate8(guid, &SDLAUDIOHIDDEN->sound, NULL); + if (result != DS_OK) { + DSOUND_CloseDevice(this); + return SetDSerror("DirectSoundCreate", result); + } + + while ((!valid_format) && (test_format)) { + switch (test_format) { + case AUDIO_U8: + case AUDIO_S16: + case AUDIO_S32: + case AUDIO_F32: + tried_format = SDL_TRUE; + this->spec.format = test_format; + SDLAUDIOHIDDEN->num_buffers = CreateSecondary(this, NULL); + if (SDLAUDIOHIDDEN->num_buffers > 0) { + valid_format = SDL_TRUE; + } + break; + } + test_format = SDL_NextAudioFormat(); + } + + if (!valid_format) { + DSOUND_CloseDevice(this); + if (tried_format) { + return -1; + } + return SDL_SetError("DirectSound: Unsupported audio format"); + } + + SDLAUDIOHIDDEN->mixlen = this->spec.size; + + return 0; +} + +static void +DSOUND_Deinitialize(void) +{ + DSOUND_Unload(); +} + +static int +DSOUND_Init(SDL_AudioDriverImpl * impl) +{ + if (!DSOUND_Load()) { + return 0; + } + + impl->DetectDevices = DSOUND_DetectDevices; + impl->OpenDevice = DSOUND_OpenDevice; + impl->PlayDevice = DSOUND_PlayDevice; + impl->WaitDevice = DSOUND_WaitDevice; + impl->WaitDone = DSOUND_WaitDone; + impl->GetDeviceBuf = DSOUND_GetDeviceBuf; + impl->CloseDevice = DSOUND_CloseDevice; + impl->Deinitialize = DSOUND_Deinitialize; + + return 1; +} + +AudioBootStrap DSOUND_bootstrap = { + "directsound", "DirectSound", DSOUND_Init, 0 +}; + +#endif +#if SDL_AUDIO_DRIVER_OSS + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if SDL_AUDIO_DRIVER_OSS_SOUNDCARD_H + +#include +#else + +#include +#endif + +#define SDL_AllocAudioMem SDL_malloc +#define SDL_FreeAudioMem SDL_free +#ifdef USE_BLOCKING_WRITES +#define OPEN_FLAGS_OUTPUT O_WRONLY +#define OPEN_FLAGS_INPUT O_RDONLY +#else +#define OPEN_FLAGS_OUTPUT (O_WRONLY|O_NONBLOCK) +#define OPEN_FLAGS_INPUT (O_RDONLY|O_NONBLOCK) +#endif + +void SDL_EnumUnixAudioDevices(int iscapture, int classic, + int (*test) (int fd), SDL_AddAudioDevice addfn); +#ifndef _SDL_dspaudio_h +#define _SDL_dspaudio_h + +#undef _THIS +#define _THIS SDL_AudioDevice *this + +#undef SDLAUDIOHIDDEN +#define SDLAUDIOHIDDEN ((struct SDL_PrivateAudioDataDSP*)this->hidden) +struct SDL_PrivateAudioDataDSP +{ + + int audio_fd; + + Uint8 *mixbuf; + int mixlen; +}; +#define FUDGE_TICKS 10 + +#endif + +static void +DSP_DetectDevices(int iscapture, SDL_AddAudioDevice addfn) +{ + SDL_EnumUnixAudioDevices(iscapture, 0, NULL, addfn); +} + +static void +DSP_CloseDevice(_THIS) +{ + if (SDLAUDIOHIDDEN != NULL) { + SDL_FreeAudioMem(SDLAUDIOHIDDEN->mixbuf); + SDLAUDIOHIDDEN->mixbuf = NULL; + if (SDLAUDIOHIDDEN->audio_fd >= 0) { + close(SDLAUDIOHIDDEN->audio_fd); + SDLAUDIOHIDDEN->audio_fd = -1; + } + SDL_free(SDLAUDIOHIDDEN); + this->hidden = NULL; + } +} + +static int +DSP_OpenDevice(_THIS, const char *devname, int iscapture) +{ + const int flags = ((iscapture) ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT); + int format; + int value; + int frag_spec; + SDL_AudioFormat test_format; + + if (devname == NULL) { + devname = SDL_GetAudioDeviceName(0, iscapture); + if (devname == NULL) { + return SDL_SetError("No such audio device"); + } + } + + if (this->spec.channels > 8) + this->spec.channels = 8; + else if (this->spec.channels > 4) + this->spec.channels = 4; + else if (this->spec.channels > 2) + this->spec.channels = 2; + + this->hidden = (struct SDL_PrivateAudioData *) + SDL_malloc((sizeof *SDLAUDIOHIDDEN)); + if (SDLAUDIOHIDDEN == NULL) { + return SDL_OutOfMemory(); + } + SDL_memset(SDLAUDIOHIDDEN, 0, (sizeof *SDLAUDIOHIDDEN)); + + SDLAUDIOHIDDEN->audio_fd = open(devname, flags, 0); + if (SDLAUDIOHIDDEN->audio_fd < 0) { + DSP_CloseDevice(this); + return SDL_SetError("Couldn't open %s: %s", devname, strerror(errno)); + } + SDLAUDIOHIDDEN->mixbuf = NULL; + + { + long ctlflags; + ctlflags = fcntl(SDLAUDIOHIDDEN->audio_fd, F_GETFL); + ctlflags &= ~O_NONBLOCK; + if (fcntl(SDLAUDIOHIDDEN->audio_fd, F_SETFL, ctlflags) < 0) { + DSP_CloseDevice(this); + return SDL_SetError("Couldn't set audio blocking mode"); + } + } + + if (ioctl(SDLAUDIOHIDDEN->audio_fd, SNDCTL_DSP_GETFMTS, &value) < 0) { + perror("SNDCTL_DSP_GETFMTS"); + DSP_CloseDevice(this); + return SDL_SetError("Couldn't get audio format list"); + } + + format = 0; + for (test_format = SDL_FirstAudioFormat(this->spec.format); + !format && test_format;) { +#ifdef DEBUG_AUDIO + fprintf(stderr, "Trying format 0x%4.4x\n", test_format); +#endif + switch (test_format) { + case AUDIO_U8: + if (value & AFMT_U8) { + format = AFMT_U8; + } + break; + case AUDIO_S16LSB: + if (value & AFMT_S16_LE) { + format = AFMT_S16_LE; + } + break; + case AUDIO_S16MSB: + if (value & AFMT_S16_BE) { + format = AFMT_S16_BE; + } + break; +#if 0 + + case AUDIO_S8: + if (value & AFMT_S8) { + format = AFMT_S8; + } + break; + case AUDIO_U16LSB: + if (value & AFMT_U16_LE) { + format = AFMT_U16_LE; + } + break; + case AUDIO_U16MSB: + if (value & AFMT_U16_BE) { + format = AFMT_U16_BE; + } + break; +#endif + default: + format = 0; + break; + } + if (!format) { + test_format = SDL_NextAudioFormat(); + } + } + if (format == 0) { + DSP_CloseDevice(this); + return SDL_SetError("Couldn't find any hardware audio formats"); + } + this->spec.format = test_format; + + value = format; + if ((ioctl(SDLAUDIOHIDDEN->audio_fd, SNDCTL_DSP_SETFMT, &value) < 0) || + (value != format)) { + perror("SNDCTL_DSP_SETFMT"); + DSP_CloseDevice(this); + return SDL_SetError("Couldn't set audio format"); + } + + value = this->spec.channels; + if (ioctl(SDLAUDIOHIDDEN->audio_fd, SNDCTL_DSP_CHANNELS, &value) < 0) { + perror("SNDCTL_DSP_CHANNELS"); + DSP_CloseDevice(this); + return SDL_SetError("Cannot set the number of channels"); + } + this->spec.channels = value; + + value = this->spec.freq; + if (ioctl(SDLAUDIOHIDDEN->audio_fd, SNDCTL_DSP_SPEED, &value) < 0) { + perror("SNDCTL_DSP_SPEED"); + DSP_CloseDevice(this); + return SDL_SetError("Couldn't set audio frequency"); + } + this->spec.freq = value; + + SDL_CalculateAudioSpec(&this->spec); + + for (frag_spec = 0; (0x01U << frag_spec) < this->spec.size; ++frag_spec); + if ((0x01U << frag_spec) != this->spec.size) { + DSP_CloseDevice(this); + return SDL_SetError("Fragment size must be a power of two"); + } + frag_spec |= 0x00020000; + +#ifdef DEBUG_AUDIO + fprintf(stderr, "Requesting %d fragments of size %d\n", + (frag_spec >> 16), 1 << (frag_spec & 0xFFFF)); +#endif + if (ioctl(SDLAUDIOHIDDEN->audio_fd, SNDCTL_DSP_SETFRAGMENT, &frag_spec) < 0) { + perror("SNDCTL_DSP_SETFRAGMENT"); + } +#ifdef DEBUG_AUDIO + { + audio_buf_info info; + ioctl(SDLAUDIOHIDDEN->audio_fd, SNDCTL_DSP_GETOSPACE, &info); + fprintf(stderr, "fragments = %d\n", info.fragments); + fprintf(stderr, "fragstotal = %d\n", info.fragstotal); + fprintf(stderr, "fragsize = %d\n", info.fragsize); + fprintf(stderr, "bytes = %d\n", info.bytes); + } +#endif + + SDLAUDIOHIDDEN->mixlen = this->spec.size; + SDLAUDIOHIDDEN->mixbuf = (Uint8 *) SDL_AllocAudioMem(SDLAUDIOHIDDEN->mixlen); + if (SDLAUDIOHIDDEN->mixbuf == NULL) { + DSP_CloseDevice(this); + return SDL_OutOfMemory(); + } + SDL_memset(SDLAUDIOHIDDEN->mixbuf, this->spec.silence, this->spec.size); + + return 0; +} + +static void +DSP_PlayDevice(_THIS) +{ + const Uint8 *mixbuf = SDLAUDIOHIDDEN->mixbuf; + const int mixlen = SDLAUDIOHIDDEN->mixlen; + if (write(SDLAUDIOHIDDEN->audio_fd, mixbuf, mixlen) == -1) { + perror("Audio write"); + this->enabled = 0; + } +#ifdef DEBUG_AUDIO + fprintf(stderr, "Wrote %d bytes of audio data\n", mixlen); +#endif +} + +static Uint8 * +DSP_GetDeviceBuf(_THIS) +{ + return (SDLAUDIOHIDDEN->mixbuf); +} + +static int +DSP_Init(SDL_AudioDriverImpl * impl) +{ + + impl->DetectDevices = DSP_DetectDevices; + impl->OpenDevice = DSP_OpenDevice; + impl->PlayDevice = DSP_PlayDevice; + impl->GetDeviceBuf = DSP_GetDeviceBuf; + impl->CloseDevice = DSP_CloseDevice; + + return 1; +} + +AudioBootStrap DSP_bootstrap = { + "dsp", "OSS /dev/dsp standard audio", DSP_Init, 0 +}; + +#endif +#define SDL_AllocAudioMem SDL_malloc +#define SDL_FreeAudioMem SDL_free + +#undef _THIS +#define _THIS SDL_AudioDevice *_this + +static SDL_AudioDriver current_audio; +static SDL_AudioDevice *open_devices[16]; + +#define DEFAULT_OUTPUT_DEVNAME "System audio output device" +#define DEFAULT_INPUT_DEVNAME "System audio capture device" + +extern AudioBootStrap BSD_AUDIO_bootstrap; +extern AudioBootStrap DSP_bootstrap; +extern AudioBootStrap ALSA_bootstrap; +extern AudioBootStrap PULSEAUDIO_bootstrap; +extern AudioBootStrap QSAAUDIO_bootstrap; +extern AudioBootStrap SUNAUDIO_bootstrap; +extern AudioBootStrap ARTS_bootstrap; +extern AudioBootStrap ESD_bootstrap; +extern AudioBootStrap NAS_bootstrap; +extern AudioBootStrap XAUDIO2_bootstrap; +extern AudioBootStrap DSOUND_bootstrap; +extern AudioBootStrap WINMM_bootstrap; +extern AudioBootStrap PAUDIO_bootstrap; +extern AudioBootStrap HAIKUAUDIO_bootstrap; +extern AudioBootStrap COREAUDIO_bootstrap; +extern AudioBootStrap SNDMGR_bootstrap; +extern AudioBootStrap DISKAUD_bootstrap; +extern AudioBootStrap DUMMYAUD_bootstrap; +extern AudioBootStrap DCAUD_bootstrap; +extern AudioBootStrap DART_bootstrap; +extern AudioBootStrap NDSAUD_bootstrap; +extern AudioBootStrap FUSIONSOUND_bootstrap; +extern AudioBootStrap ANDROIDAUD_bootstrap; +extern AudioBootStrap PSPAUD_bootstrap; +extern AudioBootStrap SNDIO_bootstrap; + +static const AudioBootStrap *const bootstrap[] = { +#if SDL_AUDIO_DRIVER_PULSEAUDIO + &PULSEAUDIO_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_ALSA + &ALSA_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_SNDIO + &SNDIO_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_BSD + &BSD_AUDIO_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_OSS + &DSP_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_QSA + &QSAAUDIO_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_SUNAUDIO + &SUNAUDIO_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_ARTS + &ARTS_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_ESD + &ESD_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_NAS + &NAS_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_XAUDIO2 + &XAUDIO2_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_DSOUND + &DSOUND_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_WINMM + &WINMM_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_PAUDIO + &PAUDIO_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_HAIKU + &HAIKUAUDIO_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_COREAUDIO + &COREAUDIO_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_DISK + &DISKAUD_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_DUMMY + &DUMMYAUD_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_FUSIONSOUND + &FUSIONSOUND_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_ANDROID + &ANDROIDAUD_bootstrap, +#endif +#if SDL_AUDIO_DRIVER_PSP + &PSPAUD_bootstrap, +#endif + NULL +}; + +static SDL_AudioDevice * +get_audio_device(SDL_AudioDeviceID id) +{ + id--; + if ((id >= SDL_arraysize(open_devices)) || (open_devices[id] == NULL)) { + SDL_SetError("Invalid audio device ID"); + return NULL; + } + + return open_devices[id]; +} + +static void +SDL_AudioDetectDevices_Default(int iscapture, SDL_AddAudioDevice addfn) +{ +} + +static void +SDL_AudioThreadInit_Default(_THIS) +{ +} + +static void +SDL_AudioWaitDevice_Default(_THIS) +{ +} + +static void +SDL_AudioPlayDevice_Default(_THIS) +{ +} + +static Uint8 * +SDL_AudioGetDeviceBuf_Default(_THIS) +{ + return NULL; +} + +static void +SDL_AudioWaitDone_Default(_THIS) +{ +} + +static void +SDL_AudioCloseDevice_Default(_THIS) +{ +} + +static void +SDL_AudioDeinitialize_Default(void) +{ +} + +static int +SDL_AudioOpenDevice_Default(_THIS, const char *devname, int iscapture) +{ + return -1; +} + +static void +SDL_AudioLockDevice_Default(SDL_AudioDevice * device) +{ + if (device->thread && (SDL_ThreadID() == device->threadid)) { + return; + } + SDL_LockMutex(device->mixer_lock); +} + +static void +SDL_AudioUnlockDevice_Default(SDL_AudioDevice * device) +{ + if (device->thread && (SDL_ThreadID() == device->threadid)) { + return; + } + SDL_UnlockMutex(device->mixer_lock); +} + +static void +finalize_audio_entry_points(void) +{ + +#define FILL_STUB(x) \ + if (current_audio.impl.x == NULL) { \ + current_audio.impl.x = SDL_Audio##x##_Default; \ + } + FILL_STUB(DetectDevices); + FILL_STUB(OpenDevice); + FILL_STUB(ThreadInit); + FILL_STUB(WaitDevice); + FILL_STUB(PlayDevice); + FILL_STUB(GetDeviceBuf); + FILL_STUB(WaitDone); + FILL_STUB(CloseDevice); + FILL_STUB(LockDevice); + FILL_STUB(UnlockDevice); + FILL_STUB(Deinitialize); +#undef FILL_STUB +} + +static void +SDL_StreamWrite(SDL_AudioStreamer * stream, Uint8 * buf, int length) +{ + int i; + + for (i = 0; i < length; ++i) { + stream->buffer[stream->write_pos] = buf[i]; + ++stream->write_pos; + } +} + +static void +SDL_StreamRead(SDL_AudioStreamer * stream, Uint8 * buf, int length) +{ + int i; + + for (i = 0; i < length; ++i) { + buf[i] = stream->buffer[stream->read_pos]; + ++stream->read_pos; + } +} + +static int +SDL_StreamLength(SDL_AudioStreamer * stream) +{ + return (stream->write_pos - stream->read_pos) % stream->max_len; +} + +#if 0 +static int +SDL_StreamInit(SDL_AudioStreamer * stream, int max_len, Uint8 silence) +{ + + stream->buffer = (Uint8 *) SDL_malloc(max_len); + if (stream->buffer == NULL) { + return -1; + } + + stream->max_len = max_len; + stream->read_pos = 0; + stream->write_pos = 0; + + SDL_memset(stream->buffer, silence, max_len); + + return 0; +} +#endif + +static void +SDL_StreamDeinit(SDL_AudioStreamer * stream) +{ + SDL_free(stream->buffer); +} + +#if defined(ANDROID) +#include +#endif + +int SDLCALL +SDL_RunAudio(void *devicep) +{ + SDL_AudioDevice *device = (SDL_AudioDevice *) devicep; + Uint8 *stream; + int stream_len; + void *udata; + void (SDLCALL * fill) (void *userdata, Uint8 * stream, int len); + Uint32 delay; + + Uint8 *istream; + int istream_len = 0; + + SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); + + device->threadid = SDL_ThreadID(); + current_audio.impl.ThreadInit(device); + + fill = device->spec.callback; + udata = device->spec.userdata; + + device->use_streamer = 0; + + if (device->convert.needed) { +#if 0 + + if (device->convert.len_mult != 1 || device->convert.len_div != 1) { + + stream_max_len = 2 * device->spec.size; + if (device->convert.len_mult > device->convert.len_div) { + stream_max_len *= device->convert.len_mult; + stream_max_len /= device->convert.len_div; + } + if (SDL_StreamInit(&device->streamer, stream_max_len, silence) < + 0) + return -1; + device->use_streamer = 1; + + istream_len = + device->spec.size * device->convert.len_div / + device->convert.len_mult; + } +#endif + stream_len = device->convert.len; + } else { + stream_len = device->spec.size; + } + + delay = ((device->spec.samples * 1000) / device->spec.freq); + + if (device->use_streamer == 1) { + + while (device->enabled) { + + if (device->paused) { + SDL_Delay(delay); + continue; + } + + if (SDL_StreamLength(&device->streamer) < stream_len) { + + if (device->convert.needed) { + if (device->convert.buf) { + istream = device->convert.buf; + } else { + continue; + } + } else { + + istream = current_audio.impl.GetDeviceBuf(device); + if (istream == NULL) { + istream = device->fake_stream; + } + } + + SDL_LockMutex(device->mixer_lock); + (*fill) (udata, istream, istream_len); + SDL_UnlockMutex(device->mixer_lock); + + if (device->convert.needed) { + SDL_ConvertAudio(&device->convert); + if (istream == NULL) { + istream = device->fake_stream; + } + + SDL_StreamWrite(&device->streamer, device->convert.buf, + device->convert.len_cvt); + } else { + SDL_StreamWrite(&device->streamer, istream, istream_len); + } + } + + if (SDL_StreamLength(&device->streamer) >= stream_len) { + + if (device->convert.needed) { + if (device->convert.buf) { + stream = device->convert.buf; + } else { + continue; + } + } else { + stream = current_audio.impl.GetDeviceBuf(device); + if (stream == NULL) { + stream = device->fake_stream; + } + } + + SDL_StreamRead(&device->streamer, stream, stream_len); + + if (stream != device->fake_stream) { + current_audio.impl.PlayDevice(device); + + current_audio.impl.WaitDevice(device); + } else { + SDL_Delay(delay); + } + } + + } + } else { + + const int silence = (int) device->spec.silence; + + while (device->enabled) { + + if (device->convert.needed) { + if (device->convert.buf) { + stream = device->convert.buf; + } else { + continue; + } + } else { + stream = current_audio.impl.GetDeviceBuf(device); + if (stream == NULL) { + stream = device->fake_stream; + } + } + + SDL_LockMutex(device->mixer_lock); + if (device->paused) { + SDL_memset(stream, silence, stream_len); + } else { + (*fill) (udata, stream, stream_len); + } + SDL_UnlockMutex(device->mixer_lock); + + if (device->convert.needed) { + SDL_ConvertAudio(&device->convert); + stream = current_audio.impl.GetDeviceBuf(device); + if (stream == NULL) { + stream = device->fake_stream; + } + SDL_memcpy(stream, device->convert.buf, + device->convert.len_cvt); + } + + if (stream != device->fake_stream) { + current_audio.impl.PlayDevice(device); + + current_audio.impl.WaitDevice(device); + } else { + SDL_Delay(delay); + } + } + } + + current_audio.impl.WaitDone(device); + + if (device->use_streamer == 1) + SDL_StreamDeinit(&device->streamer); + + return (0); +} + +static SDL_AudioFormat +SDL_ParseAudioFormat(const char *string) +{ +#define CHECK_FMT_STRING(x) if (SDL_strcmp(string, #x) == 0) return AUDIO_##x + CHECK_FMT_STRING(U8); + CHECK_FMT_STRING(S8); + CHECK_FMT_STRING(U16LSB); + CHECK_FMT_STRING(S16LSB); + CHECK_FMT_STRING(U16MSB); + CHECK_FMT_STRING(S16MSB); + CHECK_FMT_STRING(U16SYS); + CHECK_FMT_STRING(S16SYS); + CHECK_FMT_STRING(U16); + CHECK_FMT_STRING(S16); + CHECK_FMT_STRING(S32LSB); + CHECK_FMT_STRING(S32MSB); + CHECK_FMT_STRING(S32SYS); + CHECK_FMT_STRING(S32); + CHECK_FMT_STRING(F32LSB); + CHECK_FMT_STRING(F32MSB); + CHECK_FMT_STRING(F32SYS); + CHECK_FMT_STRING(F32); +#undef CHECK_FMT_STRING + return 0; +} + +int +SDL_GetNumAudioDrivers(void) +{ + return (SDL_arraysize(bootstrap) - 1); +} + +const char * +SDL_GetAudioDriver(int index) +{ + if (index >= 0 && index < SDL_GetNumAudioDrivers()) { + return (bootstrap[index]->name); + } + return (NULL); +} + +int +SDL_AudioInit(const char *driver_name) +{ + int i = 0; + int initialized = 0; + int tried_to_init = 0; + + if (SDL_WasInit(SDL_INIT_AUDIO)) { + SDL_AudioQuit(); + } + + SDL_memset(¤t_audio, '\0', sizeof(current_audio)); + SDL_memset(open_devices, '\0', sizeof(open_devices)); + + if (driver_name == NULL) { + driver_name = SDL_getenv("SDL_AUDIODRIVER"); + } + + for (i = 0; (!initialized) && (bootstrap[i]); ++i) { + + const AudioBootStrap *backend = bootstrap[i]; + if ((driver_name && (SDL_strncasecmp(backend->name, driver_name, SDL_strlen(driver_name)) != 0)) || + (!driver_name && backend->demand_only)) { + continue; + } + + tried_to_init = 1; + SDL_memset(¤t_audio, 0, sizeof(current_audio)); + current_audio.name = backend->name; + current_audio.desc = backend->desc; + initialized = backend->init(¤t_audio.impl); + } + + if (!initialized) { + + if (!tried_to_init) { + if (driver_name) { + SDL_SetError("Audio target '%s' not available", driver_name); + } else { + SDL_SetError("No available audio device"); + } + } + + SDL_memset(¤t_audio, 0, sizeof(current_audio)); + return (-1); + } + + finalize_audio_entry_points(); + + return (0); +} + +const char * +SDL_GetCurrentAudioDriver() +{ + return current_audio.name; +} + +static void +free_device_list(char ***devices, int *devCount) +{ + int i = *devCount; + if ((i > 0) && (*devices != NULL)) { + while (i--) { + SDL_free((*devices)[i]); + } + } + + SDL_free(*devices); + + *devices = NULL; + *devCount = 0; +} + +static +void SDL_AddCaptureAudioDevice(const char *_name) +{ + char *name = NULL; + void *ptr = SDL_realloc(current_audio.inputDevices, + (current_audio.inputDeviceCount+1) * sizeof(char*)); + if (ptr == NULL) { + return; + } + + current_audio.inputDevices = (char **) ptr; + name = SDL_strdup(_name); + current_audio.inputDevices[current_audio.inputDeviceCount++] = name; +} + +static +void SDL_AddOutputAudioDevice(const char *_name) +{ + char *name = NULL; + void *ptr = SDL_realloc(current_audio.outputDevices, + (current_audio.outputDeviceCount+1) * sizeof(char*)); + if (ptr == NULL) { + return; + } + + current_audio.outputDevices = (char **) ptr; + name = SDL_strdup(_name); + current_audio.outputDevices[current_audio.outputDeviceCount++] = name; +} + +int +SDL_GetNumAudioDevices(int iscapture) +{ + int retval = 0; + + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + return -1; + } + + if ((iscapture) && (!current_audio.impl.HasCaptureSupport)) { + return 0; + } + + if ((iscapture) && (current_audio.impl.OnlyHasDefaultInputDevice)) { + return 1; + } + + if ((!iscapture) && (current_audio.impl.OnlyHasDefaultOutputDevice)) { + return 1; + } + + if (iscapture) { + free_device_list(¤t_audio.inputDevices, + ¤t_audio.inputDeviceCount); + current_audio.impl.DetectDevices(iscapture, SDL_AddCaptureAudioDevice); + retval = current_audio.inputDeviceCount; + } else { + free_device_list(¤t_audio.outputDevices, + ¤t_audio.outputDeviceCount); + current_audio.impl.DetectDevices(iscapture, SDL_AddOutputAudioDevice); + retval = current_audio.outputDeviceCount; + } + + return retval; +} + +const char * +SDL_GetAudioDeviceName(int index, int iscapture) +{ + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + SDL_SetError("Audio subsystem is not initialized"); + return NULL; + } + + if ((iscapture) && (!current_audio.impl.HasCaptureSupport)) { + SDL_SetError("No capture support"); + return NULL; + } + + if (index < 0) { + goto no_such_device; + } + + if ((iscapture) && (current_audio.impl.OnlyHasDefaultInputDevice)) { + if (index > 0) { + goto no_such_device; + } + return DEFAULT_INPUT_DEVNAME; + } + + if ((!iscapture) && (current_audio.impl.OnlyHasDefaultOutputDevice)) { + if (index > 0) { + goto no_such_device; + } + return DEFAULT_OUTPUT_DEVNAME; + } + + if (iscapture) { + if (index >= current_audio.inputDeviceCount) { + goto no_such_device; + } + return current_audio.inputDevices[index]; + } else { + if (index >= current_audio.outputDeviceCount) { + goto no_such_device; + } + return current_audio.outputDevices[index]; + } + +no_such_device: + SDL_SetError("No such device"); + return NULL; +} + +static void +close_audio_device(SDL_AudioDevice * device) +{ + device->enabled = 0; + if (device->thread != NULL) { + SDL_WaitThread(device->thread, NULL); + } + if (device->mixer_lock != NULL) { + SDL_DestroyMutex(device->mixer_lock); + } + SDL_FreeAudioMem(device->fake_stream); + if (device->convert.needed) { + SDL_FreeAudioMem(device->convert.buf); + } + if (device->opened) { + current_audio.impl.CloseDevice(device); + device->opened = 0; + } + SDL_FreeAudioMem(device); +} + +static int +prepare_audiospec(const SDL_AudioSpec * orig, SDL_AudioSpec * prepared) +{ + SDL_memcpy(prepared, orig, sizeof(SDL_AudioSpec)); + + if (orig->callback == NULL) { + SDL_SetError("SDL_OpenAudio() passed a NULL callback"); + return 0; + } + + if (orig->freq == 0) { + const char *env = SDL_getenv("SDL_AUDIO_FREQUENCY"); + if ((!env) || ((prepared->freq = SDL_atoi(env)) == 0)) { + prepared->freq = 22050; + } + } + + if (orig->format == 0) { + const char *env = SDL_getenv("SDL_AUDIO_FORMAT"); + if ((!env) || ((prepared->format = SDL_ParseAudioFormat(env)) == 0)) { + prepared->format = AUDIO_S16; + } + } + + switch (orig->channels) { + case 0:{ + const char *env = SDL_getenv("SDL_AUDIO_CHANNELS"); + if ((!env) || ((prepared->channels = (Uint8) SDL_atoi(env)) == 0)) { + prepared->channels = 2; + } + break; + } + case 1: + case 2: + case 4: + case 6: + break; + default: + SDL_SetError("Unsupported number of audio channels."); + return 0; + } + + if (orig->samples == 0) { + const char *env = SDL_getenv("SDL_AUDIO_SAMPLES"); + if ((!env) || ((prepared->samples = (Uint16) SDL_atoi(env)) == 0)) { + + const int samples = (prepared->freq / 1000) * 46; + int power2 = 1; + while (power2 < samples) { + power2 *= 2; + } + prepared->samples = power2; + } + } + + SDL_CalculateAudioSpec(prepared); + + return 1; +} + +static SDL_AudioDeviceID +open_audio_device(const char *devname, int iscapture, + const SDL_AudioSpec * desired, SDL_AudioSpec * obtained, + int allowed_changes, int min_id) +{ + SDL_AudioDeviceID id = 0; + SDL_AudioSpec _obtained; + SDL_AudioDevice *device; + SDL_bool build_cvt; + int i = 0; + + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + SDL_SetError("Audio subsystem is not initialized"); + return 0; + } + + if ((iscapture) && (!current_audio.impl.HasCaptureSupport)) { + SDL_SetError("No capture support"); + return 0; + } + + if (!obtained) { + obtained = &_obtained; + } + if (!prepare_audiospec(desired, obtained)) { + return 0; + } + + if (devname == NULL) { + devname = SDL_getenv("SDL_AUDIO_DEVICE_NAME"); + } + + if ((iscapture) && (current_audio.impl.OnlyHasDefaultInputDevice)) { + if ((devname) && (SDL_strcmp(devname, DEFAULT_INPUT_DEVNAME) != 0)) { + SDL_SetError("No such device"); + return 0; + } + devname = NULL; + + for (i = 0; i < SDL_arraysize(open_devices); i++) { + if ((open_devices[i]) && (open_devices[i]->iscapture)) { + SDL_SetError("Audio device already open"); + return 0; + } + } + } + + if ((!iscapture) && (current_audio.impl.OnlyHasDefaultOutputDevice)) { + if ((devname) && (SDL_strcmp(devname, DEFAULT_OUTPUT_DEVNAME) != 0)) { + SDL_SetError("No such device"); + return 0; + } + devname = NULL; + + for (i = 0; i < SDL_arraysize(open_devices); i++) { + if ((open_devices[i]) && (!open_devices[i]->iscapture)) { + SDL_SetError("Audio device already open"); + return 0; + } + } + } + + device = (SDL_AudioDevice *) SDL_AllocAudioMem(sizeof(SDL_AudioDevice)); + if (device == NULL) { + SDL_OutOfMemory(); + return 0; + } + SDL_memset(device, '\0', sizeof(SDL_AudioDevice)); + device->spec = *obtained; + device->enabled = 1; + device->paused = 1; + device->iscapture = iscapture; + + if (!current_audio.impl.SkipMixerLock) { + device->mixer_lock = SDL_CreateMutex(); + if (device->mixer_lock == NULL) { + close_audio_device(device); + SDL_SetError("Couldn't create mixer lock"); + return 0; + } + } + + if ( ((iscapture) && (current_audio.inputDevices == NULL)) || + ((!iscapture) && (current_audio.outputDevices == NULL)) ) + SDL_GetNumAudioDevices(iscapture); + + if (current_audio.impl.OpenDevice(device, devname, iscapture) < 0) { + close_audio_device(device); + return 0; + } + device->opened = 1; + + device->fake_stream = (Uint8 *)SDL_AllocAudioMem(device->spec.size); + if (device->fake_stream == NULL) { + close_audio_device(device); + SDL_OutOfMemory(); + return 0; + } + + build_cvt = SDL_FALSE; + if (obtained->freq != device->spec.freq) { + if (allowed_changes & SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) { + obtained->freq = device->spec.freq; + } else { + build_cvt = SDL_TRUE; + } + } + if (obtained->format != device->spec.format) { + if (allowed_changes & SDL_AUDIO_ALLOW_FORMAT_CHANGE) { + obtained->format = device->spec.format; + } else { + build_cvt = SDL_TRUE; + } + } + if (obtained->channels != device->spec.channels) { + if (allowed_changes & SDL_AUDIO_ALLOW_CHANNELS_CHANGE) { + obtained->channels = device->spec.channels; + } else { + build_cvt = SDL_TRUE; + } + } + + if (device->spec.samples != obtained->samples) { + obtained->samples = device->spec.samples; + SDL_CalculateAudioSpec(obtained); + } + + if (build_cvt) { + + if (SDL_BuildAudioCVT(&device->convert, + obtained->format, obtained->channels, + obtained->freq, + device->spec.format, device->spec.channels, + device->spec.freq) < 0) { + close_audio_device(device); + return 0; + } + if (device->convert.needed) { + device->convert.len = (int) (((double) device->spec.size) / + device->convert.len_ratio); + + device->convert.buf = + (Uint8 *) SDL_AllocAudioMem(device->convert.len * + device->convert.len_mult); + if (device->convert.buf == NULL) { + close_audio_device(device); + SDL_OutOfMemory(); + return 0; + } + } + } + + for (id = min_id - 1; id < SDL_arraysize(open_devices); id++) { + if (open_devices[id] == NULL) { + open_devices[id] = device; + break; + } + } + + if (id == SDL_arraysize(open_devices)) { + SDL_SetError("Too many open audio devices"); + close_audio_device(device); + return 0; + } + + if (!current_audio.impl.ProvidesOwnCallbackThread) { + + char name[64]; + SDL_snprintf(name, sizeof (name), "SDLAudioDev%d", (int) (id + 1)); + +#if defined(__WIN32__) && !defined(HAVE_LIBC) +#undef SDL_CreateThread +#if SDL_DYNAMIC_API + device->thread = SDL_CreateThread_REAL(SDL_RunAudio, name, device, NULL, NULL); +#else + device->thread = SDL_CreateThread(SDL_RunAudio, name, device, NULL, NULL); +#endif +#else + device->thread = SDL_CreateThread(SDL_RunAudio, name, device); +#endif + if (device->thread == NULL) { + SDL_CloseAudioDevice(id + 1); + SDL_SetError("Couldn't create audio thread"); + return 0; + } + } + + return id + 1; +} + +int +SDL_OpenAudio(SDL_AudioSpec * desired, SDL_AudioSpec * obtained) +{ + SDL_AudioDeviceID id = 0; + + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + return (-1); + } + } + + if (open_devices[0] != NULL) { + SDL_SetError("Audio device is already opened"); + return (-1); + } + + if (obtained) { + id = open_audio_device(NULL, 0, desired, obtained, + SDL_AUDIO_ALLOW_ANY_CHANGE, 1); + } else { + id = open_audio_device(NULL, 0, desired, desired, 0, 1); + } + + SDL_assert((id == 0) || (id == 1)); + return ((id == 0) ? -1 : 0); +} + +SDL_AudioDeviceID +SDL_OpenAudioDevice(const char *device, int iscapture, + const SDL_AudioSpec * desired, SDL_AudioSpec * obtained, + int allowed_changes) +{ + return open_audio_device(device, iscapture, desired, obtained, + allowed_changes, 2); +} + +SDL_AudioStatus +SDL_GetAudioDeviceStatus(SDL_AudioDeviceID devid) +{ + SDL_AudioDevice *device = get_audio_device(devid); + SDL_AudioStatus status = SDL_AUDIO_STOPPED; + if (device && device->enabled) { + if (device->paused) { + status = SDL_AUDIO_PAUSED; + } else { + status = SDL_AUDIO_PLAYING; + } + } + return (status); +} + +SDL_AudioStatus +SDL_GetAudioStatus(void) +{ + return SDL_GetAudioDeviceStatus(1); +} + +void +SDL_PauseAudioDevice(SDL_AudioDeviceID devid, int pause_on) +{ + SDL_AudioDevice *device = get_audio_device(devid); + if (device) { + current_audio.impl.LockDevice(device); + device->paused = pause_on; + current_audio.impl.UnlockDevice(device); + } +} + +void +SDL_PauseAudio(int pause_on) +{ + SDL_PauseAudioDevice(1, pause_on); +} + +void +SDL_LockAudioDevice(SDL_AudioDeviceID devid) +{ + + SDL_AudioDevice *device = get_audio_device(devid); + if (device) { + current_audio.impl.LockDevice(device); + } +} + +void +SDL_LockAudio(void) +{ + SDL_LockAudioDevice(1); +} + +void +SDL_UnlockAudioDevice(SDL_AudioDeviceID devid) +{ + + SDL_AudioDevice *device = get_audio_device(devid); + if (device) { + current_audio.impl.UnlockDevice(device); + } +} + +void +SDL_UnlockAudio(void) +{ + SDL_UnlockAudioDevice(1); +} + +void +SDL_CloseAudioDevice(SDL_AudioDeviceID devid) +{ + SDL_AudioDevice *device = get_audio_device(devid); + if (device) { + close_audio_device(device); + open_devices[devid - 1] = NULL; + } +} + +void +SDL_CloseAudio(void) +{ + SDL_CloseAudioDevice(1); +} + +void +SDL_AudioQuit(void) +{ + SDL_AudioDeviceID i; + + if (!current_audio.name) { + return; + } + + for (i = 0; i < SDL_arraysize(open_devices); i++) { + if (open_devices[i] != NULL) { + SDL_CloseAudioDevice(i+1); + } + } + + current_audio.impl.Deinitialize(); + free_device_list(¤t_audio.outputDevices, + ¤t_audio.outputDeviceCount); + free_device_list(¤t_audio.inputDevices, + ¤t_audio.inputDeviceCount); + SDL_memset(¤t_audio, '\0', sizeof(current_audio)); + SDL_memset(open_devices, '\0', sizeof(open_devices)); +} + +#define NUM_FORMATS 10 +static int format_idx; +static int format_idx_sub; +static SDL_AudioFormat format_list[NUM_FORMATS][NUM_FORMATS] = { + {AUDIO_U8, AUDIO_S8, AUDIO_S16LSB, AUDIO_S16MSB, AUDIO_U16LSB, + AUDIO_U16MSB, AUDIO_S32LSB, AUDIO_S32MSB, AUDIO_F32LSB, AUDIO_F32MSB}, + {AUDIO_S8, AUDIO_U8, AUDIO_S16LSB, AUDIO_S16MSB, AUDIO_U16LSB, + AUDIO_U16MSB, AUDIO_S32LSB, AUDIO_S32MSB, AUDIO_F32LSB, AUDIO_F32MSB}, + {AUDIO_S16LSB, AUDIO_S16MSB, AUDIO_U16LSB, AUDIO_U16MSB, AUDIO_S32LSB, + AUDIO_S32MSB, AUDIO_F32LSB, AUDIO_F32MSB, AUDIO_U8, AUDIO_S8}, + {AUDIO_S16MSB, AUDIO_S16LSB, AUDIO_U16MSB, AUDIO_U16LSB, AUDIO_S32MSB, + AUDIO_S32LSB, AUDIO_F32MSB, AUDIO_F32LSB, AUDIO_U8, AUDIO_S8}, + {AUDIO_U16LSB, AUDIO_U16MSB, AUDIO_S16LSB, AUDIO_S16MSB, AUDIO_S32LSB, + AUDIO_S32MSB, AUDIO_F32LSB, AUDIO_F32MSB, AUDIO_U8, AUDIO_S8}, + {AUDIO_U16MSB, AUDIO_U16LSB, AUDIO_S16MSB, AUDIO_S16LSB, AUDIO_S32MSB, + AUDIO_S32LSB, AUDIO_F32MSB, AUDIO_F32LSB, AUDIO_U8, AUDIO_S8}, + {AUDIO_S32LSB, AUDIO_S32MSB, AUDIO_F32LSB, AUDIO_F32MSB, AUDIO_S16LSB, + AUDIO_S16MSB, AUDIO_U16LSB, AUDIO_U16MSB, AUDIO_U8, AUDIO_S8}, + {AUDIO_S32MSB, AUDIO_S32LSB, AUDIO_F32MSB, AUDIO_F32LSB, AUDIO_S16MSB, + AUDIO_S16LSB, AUDIO_U16MSB, AUDIO_U16LSB, AUDIO_U8, AUDIO_S8}, + {AUDIO_F32LSB, AUDIO_F32MSB, AUDIO_S32LSB, AUDIO_S32MSB, AUDIO_S16LSB, + AUDIO_S16MSB, AUDIO_U16LSB, AUDIO_U16MSB, AUDIO_U8, AUDIO_S8}, + {AUDIO_F32MSB, AUDIO_F32LSB, AUDIO_S32MSB, AUDIO_S32LSB, AUDIO_S16MSB, + AUDIO_S16LSB, AUDIO_U16MSB, AUDIO_U16LSB, AUDIO_U8, AUDIO_S8}, +}; + +SDL_AudioFormat +SDL_FirstAudioFormat(SDL_AudioFormat format) +{ + for (format_idx = 0; format_idx < NUM_FORMATS; ++format_idx) { + if (format_list[format_idx][0] == format) { + break; + } + } + format_idx_sub = 0; + return (SDL_NextAudioFormat()); +} + +SDL_AudioFormat +SDL_NextAudioFormat(void) +{ + if ((format_idx == NUM_FORMATS) || (format_idx_sub == NUM_FORMATS)) { + return (0); + } + return (format_list[format_idx][format_idx_sub++]); +} + +void +SDL_CalculateAudioSpec(SDL_AudioSpec * spec) +{ + switch (spec->format) { + case AUDIO_U8: + spec->silence = 0x80; + break; + default: + spec->silence = 0x00; + break; + } + spec->size = SDL_AUDIO_BITSIZE(spec->format) / 8; + spec->size *= spec->channels; + spec->size *= spec->samples; +} + +void +SDL_MixAudio(Uint8 * dst, const Uint8 * src, Uint32 len, int volume) +{ + + SDL_AudioDevice *device = get_audio_device(1); + if (device != NULL) { + SDL_AudioFormat format; + if (device->convert.needed) { + format = device->convert.src_format; + } else { + format = device->spec.format; + } + SDL_MixAudioFormat(dst, src, format, len, volume); + } +} +#if SDL_AUDIO_DRIVER_BSD || SDL_AUDIO_DRIVER_OSS || SDL_AUDIO_DRIVER_SUNAUDIO + +#include +#include +#include +#include + +#ifdef USE_BLOCKING_WRITES +#define OPEN_FLAGS_OUTPUT O_WRONLY +#define OPEN_FLAGS_INPUT O_RDONLY +#else +#define OPEN_FLAGS_OUTPUT (O_WRONLY|O_NONBLOCK) +#define OPEN_FLAGS_INPUT (O_RDONLY|O_NONBLOCK) +#endif + +void SDL_EnumUnixAudioDevices(int iscapture, int classic, + int (*test) (int fd), SDL_AddAudioDevice addfn); + +#ifndef _PATH_DEV_DSP +#if defined(__NETBSD__) || defined(__OPENBSD__) +#define _PATH_DEV_DSP "/dev/audio" +#else +#define _PATH_DEV_DSP "/dev/dsp" +#endif +#endif +#ifndef _PATH_DEV_DSP24 +#define _PATH_DEV_DSP24 "/dev/sound/dsp" +#endif +#ifndef _PATH_DEV_AUDIO +#define _PATH_DEV_AUDIO "/dev/audio" +#endif + +static SDL_INLINE void +test_device(const char *fname, int flags, int (*test) (int fd), + SDL_AddAudioDevice addfn) +{ + struct stat sb; + if ((stat(fname, &sb) == 0) && (S_ISCHR(sb.st_mode))) { + const int audio_fd = open(fname, flags, 0); + if (audio_fd >= 0) { + if (test(audio_fd)) { + addfn(fname); + } + close(audio_fd); + } + } +} + +static int +test_stub(int fd) +{ + return 1; +} + +void +SDL_EnumUnixAudioDevices(int iscapture, int classic, int (*test)(int fd), + SDL_AddAudioDevice addfn) +{ + const int flags = ((iscapture) ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT); + const char *audiodev; + char audiopath[1024]; + + if (test == NULL) + test = test_stub; + + if (((audiodev = SDL_getenv("SDL_PATH_DSP")) == NULL) && + ((audiodev = SDL_getenv("AUDIODEV")) == NULL)) { + if (classic) { + audiodev = _PATH_DEV_AUDIO; + } else { + struct stat sb; + + if (((stat("/dev/sound", &sb) == 0) && S_ISDIR(sb.st_mode)) + && ((stat(_PATH_DEV_DSP24, &sb) == 0) + && S_ISCHR(sb.st_mode))) { + audiodev = _PATH_DEV_DSP24; + } else { + audiodev = _PATH_DEV_DSP; + } + } + } + test_device(audiodev, flags, test, addfn); + + if (SDL_strlen(audiodev) < (sizeof(audiopath) - 3)) { + int instance = 0; + while (instance++ <= 64) { + SDL_snprintf(audiopath, SDL_arraysize(audiopath), + "%s%d", audiodev, instance); + test_device(audiopath, flags, test, addfn); + } + } +} + +#endif +#if SDL_AUDIO_DRIVER_WINMM + +#include + +#ifndef _SDL_winmm_h +#define _SDL_winmm_h + +#undef _THIS +#define _THIS SDL_AudioDevice *this + +#define NUM_BUFFERS 2 + +#undef SDLAUDIOHIDDEN +#define SDLAUDIOHIDDEN ((struct SDL_PrivateAudioDataWINMM*)this->hidden) +struct SDL_PrivateAudioDataWINMM +{ + HWAVEOUT hout; + HWAVEIN hin; + HANDLE audio_sem; + Uint8 *mixbuf; + WAVEHDR wavebuf[NUM_BUFFERS]; + int next_buffer; +}; + +#endif + +#ifndef WAVE_FORMAT_IEEE_FLOAT +#define WAVE_FORMAT_IEEE_FLOAT 0x0003 +#endif + +#define DETECT_DEV_IMPL(typ, capstyp) \ +static void DetectWave##typ##Devs(SDL_AddAudioDevice addfn) { \ + const UINT devcount = wave##typ##GetNumDevs(); \ + capstyp caps; \ + UINT i; \ + for (i = 0; i < devcount; i++) { \ + if (wave##typ##GetDevCaps(i,&caps,sizeof(caps))==MMSYSERR_NOERROR) { \ + char *name = WIN_StringToUTF8(caps.szPname); \ + if (name != NULL) { \ + addfn(name); \ + SDL_free(name); \ + } \ + } \ + } \ +} + +DETECT_DEV_IMPL(Out, WAVEOUTCAPS) +DETECT_DEV_IMPL(In, WAVEINCAPS) + +static void +WINMM_DetectDevices(int iscapture, SDL_AddAudioDevice addfn) +{ + if (iscapture) { + DetectWaveInDevs(addfn); + } else { + DetectWaveOutDevs(addfn); + } +} + +static void CALLBACK +CaptureSound(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, + DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + SDL_AudioDevice *this = (SDL_AudioDevice *) dwInstance; + + if (uMsg != WIM_DATA) + return; + + ReleaseSemaphore(SDLAUDIOHIDDEN->audio_sem, 1, NULL); +} + +static void CALLBACK +FillSound(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, + DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + SDL_AudioDevice *this = (SDL_AudioDevice *) dwInstance; + + if (uMsg != WOM_DONE) + return; + + ReleaseSemaphore(SDLAUDIOHIDDEN->audio_sem, 1, NULL); +} + +static int +SetMMerror(char *function, MMRESULT code) +{ + int len; + char errbuf[MAXERRORLENGTH]; + wchar_t werrbuf[MAXERRORLENGTH]; + + SDL_snprintf(errbuf, SDL_arraysize(errbuf), "%s: ", function); + len = SDL_static_cast(int, SDL_strlen(errbuf)); + + waveOutGetErrorText(code, werrbuf, MAXERRORLENGTH - len); + WideCharToMultiByte(CP_ACP, 0, werrbuf, -1, errbuf + len, + MAXERRORLENGTH - len, NULL, NULL); + + return SDL_SetError("%s", errbuf); +} + +static void +WINMM_WaitDevice(_THIS) +{ + + WaitForSingleObject(SDLAUDIOHIDDEN->audio_sem, INFINITE); +} + +static Uint8 * +WINMM_GetDeviceBuf(_THIS) +{ + return (Uint8 *) (SDLAUDIOHIDDEN-> + wavebuf[SDLAUDIOHIDDEN->next_buffer].lpData); +} + +static void +WINMM_PlayDevice(_THIS) +{ + + waveOutWrite(SDLAUDIOHIDDEN->hout, + &SDLAUDIOHIDDEN->wavebuf[SDLAUDIOHIDDEN->next_buffer], + sizeof(SDLAUDIOHIDDEN->wavebuf[0])); + SDLAUDIOHIDDEN->next_buffer = (SDLAUDIOHIDDEN->next_buffer + 1) % NUM_BUFFERS; +} + +static void +WINMM_WaitDone(_THIS) +{ + int i, left; + + do { + left = NUM_BUFFERS; + for (i = 0; i < NUM_BUFFERS; ++i) { + if (SDLAUDIOHIDDEN->wavebuf[i].dwFlags & WHDR_DONE) { + --left; + } + } + if (left > 0) { + SDL_Delay(100); + } + } while (left > 0); +} + +static void +WINMM_CloseDevice(_THIS) +{ + + if (SDLAUDIOHIDDEN != NULL) { + int i; + + if (SDLAUDIOHIDDEN->audio_sem) { + CloseHandle(SDLAUDIOHIDDEN->audio_sem); + SDLAUDIOHIDDEN->audio_sem = 0; + } + + for (i = 0; i < NUM_BUFFERS; ++i) { + if (SDLAUDIOHIDDEN->wavebuf[i].dwUser != 0xFFFF) { + waveOutUnprepareHeader(SDLAUDIOHIDDEN->hout, + &SDLAUDIOHIDDEN->wavebuf[i], + sizeof(SDLAUDIOHIDDEN->wavebuf[i])); + SDLAUDIOHIDDEN->wavebuf[i].dwUser = 0xFFFF; + } + } + + SDL_free(SDLAUDIOHIDDEN->mixbuf); + SDLAUDIOHIDDEN->mixbuf = NULL; + + if (SDLAUDIOHIDDEN->hin) { + waveInClose(SDLAUDIOHIDDEN->hin); + SDLAUDIOHIDDEN->hin = 0; + } + + if (SDLAUDIOHIDDEN->hout) { + waveOutClose(SDLAUDIOHIDDEN->hout); + SDLAUDIOHIDDEN->hout = 0; + } + + SDL_free(SDLAUDIOHIDDEN); + this->hidden = NULL; + } +} + +static SDL_bool +PrepWaveFormat(_THIS, UINT devId, WAVEFORMATEX *pfmt, const int iscapture) +{ + SDL_zerop(pfmt); + + if (SDL_AUDIO_ISFLOAT(this->spec.format)) { + pfmt->wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + } else { + pfmt->wFormatTag = WAVE_FORMAT_PCM; + } + pfmt->wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format); + + pfmt->nChannels = this->spec.channels; + pfmt->nSamplesPerSec = this->spec.freq; + pfmt->nBlockAlign = pfmt->nChannels * (pfmt->wBitsPerSample / 8); + pfmt->nAvgBytesPerSec = pfmt->nSamplesPerSec * pfmt->nBlockAlign; + + if (iscapture) { + return (waveInOpen(0, devId, pfmt, 0, 0, WAVE_FORMAT_QUERY) == 0); + } else { + return (waveOutOpen(0, devId, pfmt, 0, 0, WAVE_FORMAT_QUERY) == 0); + } +} + +static int +WINMM_OpenDevice(_THIS, const char *devname, int iscapture) +{ + SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); + int valid_datatype = 0; + MMRESULT result; + WAVEFORMATEX waveformat; + UINT devId = WAVE_MAPPER; + char *utf8 = NULL; + UINT i; + + if (devname != NULL) { + if (iscapture) { + const UINT devcount = waveInGetNumDevs(); + WAVEINCAPS caps; + for (i = 0; (i < devcount) && (devId == WAVE_MAPPER); i++) { + result = waveInGetDevCaps(i, &caps, sizeof (caps)); + if (result != MMSYSERR_NOERROR) + continue; + else if ((utf8 = WIN_StringToUTF8(caps.szPname)) == NULL) + continue; + else if (SDL_strcmp(devname, utf8) == 0) + devId = i; + SDL_free(utf8); + } + } else { + const UINT devcount = waveOutGetNumDevs(); + WAVEOUTCAPS caps; + for (i = 0; (i < devcount) && (devId == WAVE_MAPPER); i++) { + result = waveOutGetDevCaps(i, &caps, sizeof (caps)); + if (result != MMSYSERR_NOERROR) + continue; + else if ((utf8 = WIN_StringToUTF8(caps.szPname)) == NULL) + continue; + else if (SDL_strcmp(devname, utf8) == 0) + devId = i; + SDL_free(utf8); + } + } + + if (devId == WAVE_MAPPER) { + return SDL_SetError("Requested device not found"); + } + } + + this->hidden = (struct SDL_PrivateAudioData *) + SDL_malloc((sizeof *SDLAUDIOHIDDEN)); + if (SDLAUDIOHIDDEN == NULL) { + return SDL_OutOfMemory(); + } + SDL_memset(SDLAUDIOHIDDEN, 0, (sizeof *SDLAUDIOHIDDEN)); + + for (i = 0; i < NUM_BUFFERS; ++i) + SDLAUDIOHIDDEN->wavebuf[i].dwUser = 0xFFFF; + + if (this->spec.channels > 2) + this->spec.channels = 2; + + if (this->spec.samples < (this->spec.freq / 4)) + this->spec.samples = ((this->spec.freq / 4) + 3) & ~3; + + while ((!valid_datatype) && (test_format)) { + switch (test_format) { + case AUDIO_U8: + case AUDIO_S16: + case AUDIO_S32: + case AUDIO_F32: + this->spec.format = test_format; + if (PrepWaveFormat(this, devId, &waveformat, iscapture)) { + valid_datatype = 1; + } else { + test_format = SDL_NextAudioFormat(); + } + break; + + default: + test_format = SDL_NextAudioFormat(); + break; + } + } + + if (!valid_datatype) { + WINMM_CloseDevice(this); + return SDL_SetError("Unsupported audio format"); + } + + SDL_CalculateAudioSpec(&this->spec); + + if (iscapture) { + result = waveInOpen(&SDLAUDIOHIDDEN->hin, devId, &waveformat, + (DWORD_PTR) CaptureSound, (DWORD_PTR) this, + CALLBACK_FUNCTION); + } else { + result = waveOutOpen(&SDLAUDIOHIDDEN->hout, devId, &waveformat, + (DWORD_PTR) FillSound, (DWORD_PTR) this, + CALLBACK_FUNCTION); + } + + if (result != MMSYSERR_NOERROR) { + WINMM_CloseDevice(this); + return SetMMerror("waveOutOpen()", result); + } +#ifdef SOUND_DEBUG + + { + WAVEOUTCAPS caps; + + result = waveOutGetDevCaps((UINT) SDLAUDIOHIDDEN->hout, + &caps, sizeof(caps)); + if (result != MMSYSERR_NOERROR) { + WINMM_CloseDevice(this); + return SetMMerror("waveOutGetDevCaps()", result); + } + printf("Audio device: %s\n", caps.szPname); + } +#endif + + SDLAUDIOHIDDEN->audio_sem = + CreateSemaphore(NULL, NUM_BUFFERS - 1, NUM_BUFFERS, NULL); + if (SDLAUDIOHIDDEN->audio_sem == NULL) { + WINMM_CloseDevice(this); + return SDL_SetError("Couldn't create semaphore"); + } + + SDLAUDIOHIDDEN->mixbuf = + (Uint8 *) SDL_malloc(NUM_BUFFERS * this->spec.size); + if (SDLAUDIOHIDDEN->mixbuf == NULL) { + WINMM_CloseDevice(this); + return SDL_OutOfMemory(); + } + for (i = 0; i < NUM_BUFFERS; ++i) { + SDL_memset(&SDLAUDIOHIDDEN->wavebuf[i], 0, + sizeof(SDLAUDIOHIDDEN->wavebuf[i])); + SDLAUDIOHIDDEN->wavebuf[i].dwBufferLength = this->spec.size; + SDLAUDIOHIDDEN->wavebuf[i].dwFlags = WHDR_DONE; + SDLAUDIOHIDDEN->wavebuf[i].lpData = + (LPSTR) & SDLAUDIOHIDDEN->mixbuf[i * this->spec.size]; + result = waveOutPrepareHeader(SDLAUDIOHIDDEN->hout, + &SDLAUDIOHIDDEN->wavebuf[i], + sizeof(SDLAUDIOHIDDEN->wavebuf[i])); + if (result != MMSYSERR_NOERROR) { + WINMM_CloseDevice(this); + return SetMMerror("waveOutPrepareHeader()", result); + } + } + + return 0; +} + +static int +WINMM_Init(SDL_AudioDriverImpl * impl) +{ + + impl->DetectDevices = WINMM_DetectDevices; + impl->OpenDevice = WINMM_OpenDevice; + impl->PlayDevice = WINMM_PlayDevice; + impl->WaitDevice = WINMM_WaitDevice; + impl->WaitDone = WINMM_WaitDone; + impl->GetDeviceBuf = WINMM_GetDeviceBuf; + impl->CloseDevice = WINMM_CloseDevice; + + return 1; +} + +AudioBootStrap WINMM_bootstrap = { + "winmm", "Windows Waveform Audio", WINMM_Init, 0 +}; + +#endif +#if SDL_AUDIO_DRIVER_XAUDIO2 + +#ifdef __GNUC__ + +# define SDL_XAUDIO2_HAS_SDK 1 +#elif defined(__WINRT__) + +# define SDL_XAUDIO2_HAS_SDK +#else + +#include +#if (!defined(_DXSDK_BUILD_MAJOR) || (_DXSDK_BUILD_MAJOR < 1284)) +# pragma message("Your DirectX SDK is too old. Disabling XAudio2 support.") +#else +# define SDL_XAUDIO2_HAS_SDK 1 +#endif +#endif + +#ifdef SDL_XAUDIO2_HAS_SDK + +#ifdef WINVER +#if WINVER >= 0x0602 +#define SDL_XAUDIO2_WIN8 1 +#endif +#endif + +#ifdef __WINRT__ +#define uuid(x) +#define DX_BUILD +#endif + +#define INITGUID 1 +#include + +#undef _THIS +#define _THIS SDL_AudioDevice *this + +#ifdef __WINRT__ +#include "SDL_xaudio2_winrthelpers.h" +#endif + +#ifdef __GNUC__ +#ifdef THIS +#undef THIS +#endif +#define THIS INTERFACE *p +#ifdef THIS_ +#undef THIS_ +#endif +#define THIS_ INTERFACE *p, +#endif + +#undef SDLAUDIOHIDDEN +#define SDLAUDIOHIDDEN ((struct SDL_PrivateAudioDataXAUDIO2*)this->hidden) +struct SDL_PrivateAudioDataXAUDIO2 +{ + IXAudio2 *ixa2; + IXAudio2SourceVoice *source; + IXAudio2MasteringVoice *mastering; + SDL_sem * semaphore; + Uint8 *mixbuf; + int mixlen; + Uint8 *nextbuf; +}; + +static void +XAUDIO2_DetectDevices(int iscapture, SDL_AddAudioDevice addfn) +{ + IXAudio2 *ixa2 = NULL; + UINT32 devcount = 0; + UINT32 i = 0; + + if (iscapture) { + SDL_SetError("XAudio2: capture devices unsupported."); + return; + } else if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) { + SDL_SetError("XAudio2: XAudio2Create() failed at detection."); + return; + } else if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) { + SDL_SetError("XAudio2: IXAudio2::GetDeviceCount() failed."); + IXAudio2_Release(ixa2); + return; + } + + for (i = 0; i < devcount; i++) { + XAUDIO2_DEVICE_DETAILS details; + if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) { + char *str = WIN_StringToUTF8(details.DisplayName); + if (str != NULL) { + addfn(str); + SDL_free(str); + } + } + } + + IXAudio2_Release(ixa2); +} + +static void STDMETHODCALLTYPE +VoiceCBOnBufferEnd(THIS_ void *data) +{ + + SDL_AudioDevice *this = (SDL_AudioDevice *) data; + SDL_SemPost(SDLAUDIOHIDDEN->semaphore); +} + +static void STDMETHODCALLTYPE +VoiceCBOnVoiceError(THIS_ void *data, HRESULT Error) +{ + + SDL_assert(0 && "write me!"); +} + +static void STDMETHODCALLTYPE VoiceCBOnStreamEnd(THIS) {} +static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassStart(THIS_ UINT32 b) {} +static void STDMETHODCALLTYPE VoiceCBOnVoiceProcessPassEnd(THIS) {} +static void STDMETHODCALLTYPE VoiceCBOnBufferStart(THIS_ void *data) {} +static void STDMETHODCALLTYPE VoiceCBOnLoopEnd(THIS_ void *data) {} + +static Uint8 * +XAUDIO2_GetDeviceBuf(_THIS) +{ + return SDLAUDIOHIDDEN->nextbuf; +} + +static void +XAUDIO2_PlayDevice(_THIS) +{ + XAUDIO2_BUFFER buffer; + Uint8 *mixbuf = SDLAUDIOHIDDEN->mixbuf; + Uint8 *nextbuf = SDLAUDIOHIDDEN->nextbuf; + const int mixlen = SDLAUDIOHIDDEN->mixlen; + IXAudio2SourceVoice *source = SDLAUDIOHIDDEN->source; + HRESULT result = S_OK; + + if (!this->enabled) { + return; + } + + SDL_zero(buffer); + buffer.AudioBytes = mixlen; + buffer.pAudioData = nextbuf; + buffer.pContext = this; + + if (nextbuf == mixbuf) { + nextbuf += mixlen; + } else { + nextbuf = mixbuf; + } + SDLAUDIOHIDDEN->nextbuf = nextbuf; + + result = IXAudio2SourceVoice_SubmitSourceBuffer(source, &buffer, NULL); + if (result == XAUDIO2_E_DEVICE_INVALIDATED) { + + } + + if (result != S_OK) { + IXAudio2SourceVoice_FlushSourceBuffers(source); + this->enabled = 0; + } +} + +static void +XAUDIO2_WaitDevice(_THIS) +{ + if (this->enabled) { + SDL_SemWait(SDLAUDIOHIDDEN->semaphore); + } +} + +static void +XAUDIO2_WaitDone(_THIS) +{ + IXAudio2SourceVoice *source = SDLAUDIOHIDDEN->source; + XAUDIO2_VOICE_STATE state; + SDL_assert(!this->enabled); + IXAudio2SourceVoice_Discontinuity(source); +#if SDL_XAUDIO2_WIN8 + IXAudio2SourceVoice_GetState(source, &state, 0); +#else + IXAudio2SourceVoice_GetState(source, &state); +#endif + while (state.BuffersQueued > 0) { + SDL_SemWait(SDLAUDIOHIDDEN->semaphore); +#if SDL_XAUDIO2_WIN8 + IXAudio2SourceVoice_GetState(source, &state, 0); +#else + IXAudio2SourceVoice_GetState(source, &state); +#endif + } +} + +static void +XAUDIO2_CloseDevice(_THIS) +{ + if (SDLAUDIOHIDDEN != NULL) { + IXAudio2 *ixa2 = SDLAUDIOHIDDEN->ixa2; + IXAudio2SourceVoice *source = SDLAUDIOHIDDEN->source; + IXAudio2MasteringVoice *mastering = SDLAUDIOHIDDEN->mastering; + + if (source != NULL) { + IXAudio2SourceVoice_Stop(source, 0, XAUDIO2_COMMIT_NOW); + IXAudio2SourceVoice_FlushSourceBuffers(source); + IXAudio2SourceVoice_DestroyVoice(source); + } + if (ixa2 != NULL) { + IXAudio2_StopEngine(ixa2); + } + if (mastering != NULL) { + IXAudio2MasteringVoice_DestroyVoice(mastering); + } + if (ixa2 != NULL) { + IXAudio2_Release(ixa2); + } + SDL_free(SDLAUDIOHIDDEN->mixbuf); + if (SDLAUDIOHIDDEN->semaphore != NULL) { + SDL_DestroySemaphore(SDLAUDIOHIDDEN->semaphore); + } + + SDL_free(SDLAUDIOHIDDEN); + this->hidden = NULL; + } +} + +static int +XAUDIO2_OpenDevice(_THIS, const char *devname, int iscapture) +{ + HRESULT result = S_OK; + WAVEFORMATEX waveformat; + int valid_format = 0; + SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); + IXAudio2 *ixa2 = NULL; + IXAudio2SourceVoice *source = NULL; +#if defined(SDL_XAUDIO2_WIN8) + LPCWSTR devId = NULL; +#else + UINT32 devId = 0; +#endif + + static IXAudio2VoiceCallbackVtbl callbacks_vtable = { + VoiceCBOnVoiceProcessPassStart, + VoiceCBOnVoiceProcessPassEnd, + VoiceCBOnStreamEnd, + VoiceCBOnBufferStart, + VoiceCBOnBufferEnd, + VoiceCBOnLoopEnd, + VoiceCBOnVoiceError + }; + + static IXAudio2VoiceCallback callbacks = { &callbacks_vtable }; + + if (iscapture) { + return SDL_SetError("XAudio2: capture devices unsupported."); + } else if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) { + return SDL_SetError("XAudio2: XAudio2Create() failed at open."); + } + +#if ! defined(__WINRT__) + if (devname != NULL) { + UINT32 devcount = 0; + UINT32 i = 0; + + if (IXAudio2_GetDeviceCount(ixa2, &devcount) != S_OK) { + IXAudio2_Release(ixa2); + return SDL_SetError("XAudio2: IXAudio2_GetDeviceCount() failed."); + } + for (i = 0; i < devcount; i++) { + XAUDIO2_DEVICE_DETAILS details; + if (IXAudio2_GetDeviceDetails(ixa2, i, &details) == S_OK) { + char *str = WIN_StringToUTF8(details.DisplayName); + if (str != NULL) { + const int match = (SDL_strcmp(str, devname) == 0); + SDL_free(str); + if (match) { + devId = i; + break; + } + } + } + } + + if (i == devcount) { + IXAudio2_Release(ixa2); + return SDL_SetError("XAudio2: Requested device not found."); + } + } +#endif + + this->hidden = (struct SDL_PrivateAudioData *) + SDL_malloc((sizeof *SDLAUDIOHIDDEN)); + if (SDLAUDIOHIDDEN == NULL) { + IXAudio2_Release(ixa2); + return SDL_OutOfMemory(); + } + SDL_memset(SDLAUDIOHIDDEN, 0, (sizeof *SDLAUDIOHIDDEN)); + + SDLAUDIOHIDDEN->ixa2 = ixa2; + SDLAUDIOHIDDEN->semaphore = SDL_CreateSemaphore(1); + if (SDLAUDIOHIDDEN->semaphore == NULL) { + XAUDIO2_CloseDevice(this); + return SDL_SetError("XAudio2: CreateSemaphore() failed!"); + } + + while ((!valid_format) && (test_format)) { + switch (test_format) { + case AUDIO_U8: + case AUDIO_S16: + case AUDIO_S32: + case AUDIO_F32: + this->spec.format = test_format; + valid_format = 1; + break; + } + test_format = SDL_NextAudioFormat(); + } + + if (!valid_format) { + XAUDIO2_CloseDevice(this); + return SDL_SetError("XAudio2: Unsupported audio format"); + } + + SDL_CalculateAudioSpec(&this->spec); + + SDLAUDIOHIDDEN->mixlen = this->spec.size; + SDLAUDIOHIDDEN->mixbuf = (Uint8 *) SDL_malloc(2 * SDLAUDIOHIDDEN->mixlen); + if (SDLAUDIOHIDDEN->mixbuf == NULL) { + XAUDIO2_CloseDevice(this); + return SDL_OutOfMemory(); + } + SDLAUDIOHIDDEN->nextbuf = SDLAUDIOHIDDEN->mixbuf; + SDL_memset(SDLAUDIOHIDDEN->mixbuf, 0, 2 * SDLAUDIOHIDDEN->mixlen); + +#if SDL_XAUDIO2_WIN8 + result = IXAudio2_CreateMasteringVoice(ixa2, &SDLAUDIOHIDDEN->mastering, + XAUDIO2_DEFAULT_CHANNELS, + this->spec.freq, 0, devId, NULL, AudioCategory_GameEffects); +#else + result = IXAudio2_CreateMasteringVoice(ixa2, &SDLAUDIOHIDDEN->mastering, + XAUDIO2_DEFAULT_CHANNELS, + this->spec.freq, 0, devId, NULL); +#endif + if (result != S_OK) { + XAUDIO2_CloseDevice(this); + return SDL_SetError("XAudio2: Couldn't create mastering voice"); + } + + SDL_zero(waveformat); + if (SDL_AUDIO_ISFLOAT(this->spec.format)) { + waveformat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + } else { + waveformat.wFormatTag = WAVE_FORMAT_PCM; + } + waveformat.wBitsPerSample = SDL_AUDIO_BITSIZE(this->spec.format); + waveformat.nChannels = this->spec.channels; + waveformat.nSamplesPerSec = this->spec.freq; + waveformat.nBlockAlign = + waveformat.nChannels * (waveformat.wBitsPerSample / 8); + waveformat.nAvgBytesPerSec = + waveformat.nSamplesPerSec * waveformat.nBlockAlign; + waveformat.cbSize = sizeof(waveformat); + +#ifdef __WINRT__ + + result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat, + 0, + 1.0f, &callbacks, NULL, NULL); +#else + result = IXAudio2_CreateSourceVoice(ixa2, &source, &waveformat, + XAUDIO2_VOICE_NOSRC | + XAUDIO2_VOICE_NOPITCH, + 1.0f, &callbacks, NULL, NULL); + +#endif + if (result != S_OK) { + XAUDIO2_CloseDevice(this); + return SDL_SetError("XAudio2: Couldn't create source voice"); + } + SDLAUDIOHIDDEN->source = source; + + result = IXAudio2_StartEngine(ixa2); + if (result != S_OK) { + XAUDIO2_CloseDevice(this); + return SDL_SetError("XAudio2: Couldn't start engine"); + } + + result = IXAudio2SourceVoice_Start(source, 0, XAUDIO2_COMMIT_NOW); + if (result != S_OK) { + XAUDIO2_CloseDevice(this); + return SDL_SetError("XAudio2: Couldn't start source voice"); + } + + return 0; +} + +static void +XAUDIO2_Deinitialize(void) +{ +#if defined(__WIN32__) + WIN_CoUninitialize(); +#endif +} + +#endif + +static int +XAUDIO2_Init(SDL_AudioDriverImpl * impl) +{ +#ifndef SDL_XAUDIO2_HAS_SDK + SDL_SetError("XAudio2: SDL was built without XAudio2 support (old DirectX SDK)."); + return 0; +#else + + IXAudio2 *ixa2 = NULL; +#if defined(__WIN32__) + + if (FAILED(WIN_CoInitialize())) { + SDL_SetError("XAudio2: CoInitialize() failed"); + return 0; + } +#endif + + if (XAudio2Create(&ixa2, 0, XAUDIO2_DEFAULT_PROCESSOR) != S_OK) { +#if defined(__WIN32__) + WIN_CoUninitialize(); +#endif + SDL_SetError("XAudio2: XAudio2Create() failed at initialization"); + return 0; + } + IXAudio2_Release(ixa2); + + impl->DetectDevices = XAUDIO2_DetectDevices; + impl->OpenDevice = XAUDIO2_OpenDevice; + impl->PlayDevice = XAUDIO2_PlayDevice; + impl->WaitDevice = XAUDIO2_WaitDevice; + impl->WaitDone = XAUDIO2_WaitDone; + impl->GetDeviceBuf = XAUDIO2_GetDeviceBuf; + impl->CloseDevice = XAUDIO2_CloseDevice; + impl->Deinitialize = XAUDIO2_Deinitialize; + + return 1; +#endif +} + +AudioBootStrap XAUDIO2_bootstrap = { + "xaudio2", "XAudio2", XAUDIO2_Init, 0 +}; + +#endif +#if !SDL_THREAD_PTHREAD + +struct SDL_cond +{ + SDL_mutex *lock; + int waiting; + int signals; + SDL_sem *wait_sem; + SDL_sem *wait_done; +}; + +SDL_cond * +SDL_CreateCond(void) +{ + SDL_cond *cond; + + cond = (SDL_cond *) SDL_malloc(sizeof(SDL_cond)); + if (cond) { + cond->lock = SDL_CreateMutex(); + cond->wait_sem = SDL_CreateSemaphore(0); + cond->wait_done = SDL_CreateSemaphore(0); + cond->waiting = cond->signals = 0; + if (!cond->lock || !cond->wait_sem || !cond->wait_done) { + SDL_DestroyCond(cond); + cond = NULL; + } + } else { + SDL_OutOfMemory(); + } + return (cond); +} + +void +SDL_DestroyCond(SDL_cond * cond) +{ + if (cond) { + if (cond->wait_sem) { + SDL_DestroySemaphore(cond->wait_sem); + } + if (cond->wait_done) { + SDL_DestroySemaphore(cond->wait_done); + } + if (cond->lock) { + SDL_DestroyMutex(cond->lock); + } + SDL_free(cond); + } +} + +int +SDL_CondSignal(SDL_cond * cond) +{ + if (!cond) { + return SDL_SetError("Passed a NULL condition variable"); + } + + SDL_LockMutex(cond->lock); + if (cond->waiting > cond->signals) { + ++cond->signals; + SDL_SemPost(cond->wait_sem); + SDL_UnlockMutex(cond->lock); + SDL_SemWait(cond->wait_done); + } else { + SDL_UnlockMutex(cond->lock); + } + + return 0; +} + +int +SDL_CondBroadcast(SDL_cond * cond) +{ + if (!cond) { + return SDL_SetError("Passed a NULL condition variable"); + } + + SDL_LockMutex(cond->lock); + if (cond->waiting > cond->signals) { + int i, num_waiting; + + num_waiting = (cond->waiting - cond->signals); + cond->signals = cond->waiting; + for (i = 0; i < num_waiting; ++i) { + SDL_SemPost(cond->wait_sem); + } + + SDL_UnlockMutex(cond->lock); + for (i = 0; i < num_waiting; ++i) { + SDL_SemWait(cond->wait_done); + } + } else { + SDL_UnlockMutex(cond->lock); + } + + return 0; +} + +int +SDL_CondWaitTimeout(SDL_cond * cond, SDL_mutex * mutex, Uint32 ms) +{ + int retval; + + if (!cond) { + return SDL_SetError("Passed a NULL condition variable"); + } + + SDL_LockMutex(cond->lock); + ++cond->waiting; + SDL_UnlockMutex(cond->lock); + + SDL_UnlockMutex(mutex); + + if (ms == SDL_MUTEX_MAXWAIT) { + retval = SDL_SemWait(cond->wait_sem); + } else { + retval = SDL_SemWaitTimeout(cond->wait_sem, ms); + } + + SDL_LockMutex(cond->lock); + if (cond->signals > 0) { + + if (retval > 0) { + SDL_SemWait(cond->wait_sem); + } + + SDL_SemPost(cond->wait_done); + + --cond->signals; + } + --cond->waiting; + SDL_UnlockMutex(cond->lock); + + SDL_LockMutex(mutex); + + return retval; +} + +int +SDL_CondWait(SDL_cond * cond, SDL_mutex * mutex) +{ + return SDL_CondWaitTimeout(cond, mutex, SDL_MUTEX_MAXWAIT); +} + +#endif +#if !SDL_THREAD_PTHREAD || defined(__MACOSX__) || defined(__IPHONEOS__) +#if !SDL_THREAD_WINDOWS + +#if SDL_THREADS_DISABLED + +SDL_sem * +SDL_CreateSemaphore(Uint32 initial_value) +{ + SDL_SetError("SDL not built with thread support"); + return (SDL_sem *) 0; +} + +void +SDL_DestroySemaphore(SDL_sem * sem) +{ +} + +int +SDL_SemTryWait(SDL_sem * sem) +{ + return SDL_SetError("SDL not built with thread support"); +} + +int +SDL_SemWaitTimeout(SDL_sem * sem, Uint32 timeout) +{ + return SDL_SetError("SDL not built with thread support"); +} + +int +SDL_SemWait(SDL_sem * sem) +{ + return SDL_SetError("SDL not built with thread support"); +} + +Uint32 +SDL_SemValue(SDL_sem * sem) +{ + return 0; +} + +int +SDL_SemPost(SDL_sem * sem) +{ + return SDL_SetError("SDL not built with thread support"); +} + +#else + +struct SDL_semaphore +{ + Uint32 count; + Uint32 waiters_count; + SDL_mutex *count_lock; + SDL_cond *count_nonzero; +}; + +SDL_sem * +SDL_CreateSemaphore(Uint32 initial_value) +{ + SDL_sem *sem; + + sem = (SDL_sem *) SDL_malloc(sizeof(*sem)); + if (!sem) { + SDL_OutOfMemory(); + return NULL; + } + sem->count = initial_value; + sem->waiters_count = 0; + + sem->count_lock = SDL_CreateMutex(); + sem->count_nonzero = SDL_CreateCond(); + if (!sem->count_lock || !sem->count_nonzero) { + SDL_DestroySemaphore(sem); + return NULL; + } + + return sem; +} + +void +SDL_DestroySemaphore(SDL_sem * sem) +{ + if (sem) { + sem->count = 0xFFFFFFFF; + while (sem->waiters_count > 0) { + SDL_CondSignal(sem->count_nonzero); + SDL_Delay(10); + } + SDL_DestroyCond(sem->count_nonzero); + if (sem->count_lock) { + SDL_LockMutex(sem->count_lock); + SDL_UnlockMutex(sem->count_lock); + SDL_DestroyMutex(sem->count_lock); + } + SDL_free(sem); + } +} + +int +SDL_SemTryWait(SDL_sem * sem) +{ + int retval; + + if (!sem) { + return SDL_SetError("Passed a NULL semaphore"); + } + + retval = SDL_MUTEX_TIMEDOUT; + SDL_LockMutex(sem->count_lock); + if (sem->count > 0) { + --sem->count; + retval = 0; + } + SDL_UnlockMutex(sem->count_lock); + + return retval; +} + +int +SDL_SemWaitTimeout(SDL_sem * sem, Uint32 timeout) +{ + int retval; + + if (!sem) { + return SDL_SetError("Passed a NULL semaphore"); + } + + if (timeout == 0) { + return SDL_SemTryWait(sem); + } + + SDL_LockMutex(sem->count_lock); + ++sem->waiters_count; + retval = 0; + while ((sem->count == 0) && (retval != SDL_MUTEX_TIMEDOUT)) { + retval = SDL_CondWaitTimeout(sem->count_nonzero, + sem->count_lock, timeout); + } + --sem->waiters_count; + if (retval == 0) { + --sem->count; + } + SDL_UnlockMutex(sem->count_lock); + + return retval; +} + +int +SDL_SemWait(SDL_sem * sem) +{ + return SDL_SemWaitTimeout(sem, SDL_MUTEX_MAXWAIT); +} + +Uint32 +SDL_SemValue(SDL_sem * sem) +{ + Uint32 value; + + value = 0; + if (sem) { + SDL_LockMutex(sem->count_lock); + value = sem->count; + SDL_UnlockMutex(sem->count_lock); + } + return value; +} + +int +SDL_SemPost(SDL_sem * sem) +{ + if (!sem) { + return SDL_SetError("Passed a NULL semaphore"); + } + + SDL_LockMutex(sem->count_lock); + if (sem->waiters_count > 0) { + SDL_CondSignal(sem->count_nonzero); + } + ++sem->count; + SDL_UnlockMutex(sem->count_lock); + + return 0; +} + +#endif + +#endif +#endif +#if SDL_THREAD_PTHREAD + +#define _GNU_SOURCE +#include +#include + +#if !SDL_THREAD_PTHREAD_RECURSIVE_MUTEX && \ + !SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP +#define FAKE_RECURSIVE_MUTEX 1 +#endif + +struct SDL_mutex +{ + pthread_mutex_t id; +#if FAKE_RECURSIVE_MUTEX + int recursive; + pthread_t owner; +#endif +}; + +SDL_mutex * +SDL_CreateMutex(void) +{ + SDL_mutex *mutex; + pthread_mutexattr_t attr; + + mutex = (SDL_mutex *) SDL_calloc(1, sizeof(*mutex)); + if (mutex) { + pthread_mutexattr_init(&attr); +#if SDL_THREAD_PTHREAD_RECURSIVE_MUTEX + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); +#elif SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP + pthread_mutexattr_setkind_np(&attr, PTHREAD_MUTEX_RECURSIVE_NP); +#else + +#endif + if (pthread_mutex_init(&mutex->id, &attr) != 0) { + SDL_SetError("pthread_mutex_init() failed"); + SDL_free(mutex); + mutex = NULL; + } + } else { + SDL_OutOfMemory(); + } + return (mutex); +} + +void +SDL_DestroyMutex(SDL_mutex * mutex) +{ + if (mutex) { + pthread_mutex_destroy(&mutex->id); + SDL_free(mutex); + } +} + +int +SDL_LockMutex(SDL_mutex * mutex) +{ +#if FAKE_RECURSIVE_MUTEX + pthread_t this_thread; +#endif + + if (mutex == NULL) { + return SDL_SetError("Passed a NULL mutex"); + } + +#if FAKE_RECURSIVE_MUTEX + this_thread = pthread_self(); + if (mutex->owner == this_thread) { + ++mutex->recursive; + } else { + + if (pthread_mutex_lock(&mutex->id) == 0) { + mutex->owner = this_thread; + mutex->recursive = 0; + } else { + return SDL_SetError("pthread_mutex_lock() failed"); + } + } +#else + if (pthread_mutex_lock(&mutex->id) < 0) { + return SDL_SetError("pthread_mutex_lock() failed"); + } +#endif + return 0; +} + +int +SDL_TryLockMutex(SDL_mutex * mutex) +{ + int retval; +#if FAKE_RECURSIVE_MUTEX + pthread_t this_thread; +#endif + + if (mutex == NULL) { + return SDL_SetError("Passed a NULL mutex"); + } + + retval = 0; +#if FAKE_RECURSIVE_MUTEX + this_thread = pthread_self(); + if (mutex->owner == this_thread) { + ++mutex->recursive; + } else { + + if (pthread_mutex_lock(&mutex->id) == 0) { + mutex->owner = this_thread; + mutex->recursive = 0; + } else if (errno == EBUSY) { + retval = SDL_MUTEX_TIMEDOUT; + } else { + retval = SDL_SetError("pthread_mutex_trylock() failed"); + } + } +#else + if (pthread_mutex_trylock(&mutex->id) != 0) { + if (errno == EBUSY) { + retval = SDL_MUTEX_TIMEDOUT; + } else { + retval = SDL_SetError("pthread_mutex_trylock() failed"); + } + } +#endif + return retval; +} + +int +SDL_UnlockMutex(SDL_mutex * mutex) +{ + if (mutex == NULL) { + return SDL_SetError("Passed a NULL mutex"); + } + +#if FAKE_RECURSIVE_MUTEX + + if (pthread_self() == mutex->owner) { + if (mutex->recursive) { + --mutex->recursive; + } else { + + mutex->owner = 0; + pthread_mutex_unlock(&mutex->id); + } + } else { + return SDL_SetError("mutex not owned by this thread"); + } + +#else + if (pthread_mutex_unlock(&mutex->id) < 0) { + return SDL_SetError("pthread_mutex_unlock() failed"); + } +#endif + + return 0; +} + +#endif +#if SDL_THREAD_PTHREAD + +#include +#include +#include +#include + +#ifndef _SDL_mutex_c_h +#define _SDL_mutex_c_h + +//struct SDL_mutex +//{ +// pthread_mutex_t id; +//}; + +#endif + +struct SDL_cond +{ + pthread_cond_t cond; +}; + +SDL_cond * +SDL_CreateCond(void) +{ + SDL_cond *cond; + + cond = (SDL_cond *) SDL_malloc(sizeof(SDL_cond)); + if (cond) { + if (pthread_cond_init(&cond->cond, NULL) < 0) { + SDL_SetError("pthread_cond_init() failed"); + SDL_free(cond); + cond = NULL; + } + } + return (cond); +} + +void +SDL_DestroyCond(SDL_cond * cond) +{ + if (cond) { + pthread_cond_destroy(&cond->cond); + SDL_free(cond); + } +} + +int +SDL_CondSignal(SDL_cond * cond) +{ + int retval; + + if (!cond) { + return SDL_SetError("Passed a NULL condition variable"); + } + + retval = 0; + if (pthread_cond_signal(&cond->cond) != 0) { + return SDL_SetError("pthread_cond_signal() failed"); + } + return retval; +} + +int +SDL_CondBroadcast(SDL_cond * cond) +{ + int retval; + + if (!cond) { + return SDL_SetError("Passed a NULL condition variable"); + } + + retval = 0; + if (pthread_cond_broadcast(&cond->cond) != 0) { + return SDL_SetError("pthread_cond_broadcast() failed"); + } + return retval; +} + +int +SDL_CondWaitTimeout(SDL_cond * cond, SDL_mutex * mutex, Uint32 ms) +{ + int retval; + struct timeval delta; + struct timespec abstime; + + if (!cond) { + return SDL_SetError("Passed a NULL condition variable"); + } + + gettimeofday(&delta, NULL); + + abstime.tv_sec = delta.tv_sec + (ms / 1000); + abstime.tv_nsec = (delta.tv_usec + (ms % 1000) * 1000) * 1000; + if (abstime.tv_nsec > 1000000000) { + abstime.tv_sec += 1; + abstime.tv_nsec -= 1000000000; + } + + tryagain: + retval = pthread_cond_timedwait(&cond->cond, &mutex->id, &abstime); + switch (retval) { + case EINTR: + goto tryagain; + break; + case ETIMEDOUT: + retval = SDL_MUTEX_TIMEDOUT; + break; + case 0: + break; + default: + retval = SDL_SetError("pthread_cond_timedwait() failed"); + } + return retval; +} + +int +SDL_CondWait(SDL_cond * cond, SDL_mutex * mutex) +{ + if (!cond) { + return SDL_SetError("Passed a NULL condition variable"); + } else if (pthread_cond_wait(&cond->cond, &mutex->id) != 0) { + return SDL_SetError("pthread_cond_wait() failed"); + } + return 0; +} + +#endif +#if SDL_THREAD_PTHREAD + +#include +#include +#include +#include + +#if defined(__MACOSX__) || defined(__IPHONEOS__) +#else + +struct SDL_semaphore +{ + sem_t sem; +}; + +SDL_sem * +SDL_CreateSemaphore(Uint32 initial_value) +{ + SDL_sem *sem = (SDL_sem *) SDL_malloc(sizeof(SDL_sem)); + if (sem) { + if (sem_init(&sem->sem, 0, initial_value) < 0) { + SDL_SetError("sem_init() failed"); + SDL_free(sem); + sem = NULL; + } + } else { + SDL_OutOfMemory(); + } + return sem; +} + +void +SDL_DestroySemaphore(SDL_sem * sem) +{ + if (sem) { + sem_destroy(&sem->sem); + SDL_free(sem); + } +} + +int +SDL_SemTryWait(SDL_sem * sem) +{ + int retval; + + if (!sem) { + return SDL_SetError("Passed a NULL semaphore"); + } + retval = SDL_MUTEX_TIMEDOUT; + if (sem_trywait(&sem->sem) == 0) { + retval = 0; + } + return retval; +} + +int +SDL_SemWait(SDL_sem * sem) +{ + int retval; + + if (!sem) { + return SDL_SetError("Passed a NULL semaphore"); + } + + retval = sem_wait(&sem->sem); + if (retval < 0) { + retval = SDL_SetError("sem_wait() failed"); + } + return retval; +} + +int +SDL_SemWaitTimeout(SDL_sem * sem, Uint32 timeout) +{ + int retval; +#ifdef HAVE_SEM_TIMEDWAIT + struct timeval now; + struct timespec ts_timeout; +#else + Uint32 end; +#endif + + if (!sem) { + return SDL_SetError("Passed a NULL semaphore"); + } + + if (timeout == 0) { + return SDL_SemTryWait(sem); + } + if (timeout == SDL_MUTEX_MAXWAIT) { + return SDL_SemWait(sem); + } + +#ifdef HAVE_SEM_TIMEDWAIT + + gettimeofday(&now, NULL); + + now.tv_usec += (timeout % 1000) * 1000; + now.tv_sec += timeout / 1000; + + if ( now.tv_usec >= 1000000 ) { + now.tv_usec -= 1000000; + now.tv_sec ++; + } + + ts_timeout.tv_sec = now.tv_sec; + ts_timeout.tv_nsec = now.tv_usec * 1000; + + do { + retval = sem_timedwait(&sem->sem, &ts_timeout); + } while (retval < 0 && errno == EINTR); + + if (retval < 0) { + if (errno == ETIMEDOUT) { + retval = SDL_MUTEX_TIMEDOUT; + } else { + SDL_SetError(strerror(errno)); + } + } +#else + end = SDL_GetTicks() + timeout; + while ((retval = SDL_SemTryWait(sem)) == SDL_MUTEX_TIMEDOUT) { + if ((SDL_GetTicks() - end) >= 0) { + break; + } + SDL_Delay(0); + } +#endif + + return retval; +} + +Uint32 +SDL_SemValue(SDL_sem * sem) +{ + int ret = 0; + if (sem) { + sem_getvalue(&sem->sem, &ret); + if (ret < 0) { + ret = 0; + } + } + return (Uint32) ret; +} + +int +SDL_SemPost(SDL_sem * sem) +{ + int retval; + + if (!sem) { + return SDL_SetError("Passed a NULL semaphore"); + } + + retval = sem_post(&sem->sem); + if (retval < 0) { + SDL_SetError("sem_post() failed"); + } + return retval; +} + +#endif + +#endif +#if SDL_THREAD_PTHREAD + +#include + +#if HAVE_PTHREAD_NP_H +#include +#endif + +#include + +#ifdef __LINUX__ +#include +#include +#include +#include +#endif + +#if defined(__LINUX__) || defined(__MACOSX__) || defined(__IPHONEOS__) +#include +#ifndef RTLD_DEFAULT +#define RTLD_DEFAULT NULL +#endif +#endif + +#ifdef __ANDROID__ +#include "../../core/android/SDL_android.h" +#endif + +static const int sig_list[] = { + SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGWINCH, + SIGVTALRM, SIGPROF, 0 +}; + +static void * +RunThread(void *data) +{ +#ifdef __ANDROID__ + Android_JNI_SetupThread(); +#endif + SDL_RunThread(data); + return NULL; +} + +#if defined(__MACOSX__) || defined(__IPHONEOS__) +static SDL_bool checked_setname = SDL_FALSE; +static int (*ppthread_setname_np)(const char*) = NULL; +#elif defined(__LINUX__) +static SDL_bool checked_setname = SDL_FALSE; +static int (*ppthread_setname_np)(pthread_t, const char*) = NULL; +#endif +int +SDL_SYS_CreateThread(SDL_Thread * thread, void *args) +{ + pthread_attr_t type; + + #if defined(__MACOSX__) || defined(__IPHONEOS__) || defined(__LINUX__) + if (!checked_setname) { + void *fn = dlsym(RTLD_DEFAULT, "pthread_setname_np"); + #if defined(__MACOSX__) || defined(__IPHONEOS__) + ppthread_setname_np = (int(*)(const char*)) fn; + #elif defined(__LINUX__) + ppthread_setname_np = (int(*)(pthread_t, const char*)) fn; + #endif + checked_setname = SDL_TRUE; + } + #endif + + if (pthread_attr_init(&type) != 0) { + return SDL_SetError("Couldn't initialize pthread attributes"); + } + pthread_attr_setdetachstate(&type, PTHREAD_CREATE_JOINABLE); + + if (pthread_create(&thread->handle, &type, RunThread, args) != 0) { + return SDL_SetError("Not enough resources to create thread"); + } + + return 0; +} + +void +SDL_SYS_SetupThread(const char *name) +{ + int i; + sigset_t mask; + + if (name != NULL) { + #if defined(__MACOSX__) || defined(__IPHONEOS__) || defined(__LINUX__) + SDL_assert(checked_setname); + if (ppthread_setname_np != NULL) { + #if defined(__MACOSX__) || defined(__IPHONEOS__) + ppthread_setname_np(name); + #elif defined(__LINUX__) + ppthread_setname_np(pthread_self(), name); + #endif + } + #elif HAVE_PTHREAD_SETNAME_NP + pthread_setname_np(pthread_self(), name); + #elif HAVE_PTHREAD_SET_NAME_NP + pthread_set_name_np(pthread_self(), name); + #endif + } + + sigemptyset(&mask); + for (i = 0; sig_list[i]; ++i) { + sigaddset(&mask, sig_list[i]); + } + pthread_sigmask(SIG_BLOCK, &mask, 0); + +#ifdef PTHREAD_CANCEL_ASYNCHRONOUS + + { + int oldstate; + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldstate); + } +#endif +} + +SDL_threadID +SDL_ThreadID(void) +{ + return ((SDL_threadID) pthread_self()); +} + +int +SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority) +{ +#ifdef __LINUX__ + int value; + + if (priority == SDL_THREAD_PRIORITY_LOW) { + value = 19; + } else if (priority == SDL_THREAD_PRIORITY_HIGH) { + value = -20; + } else { + value = 0; + } + if (setpriority(PRIO_PROCESS, syscall(SYS_gettid), value) < 0) { + + return SDL_SetError("setpriority() failed"); + } + return 0; +#else + struct sched_param sched; + int policy; + pthread_t thread = pthread_self(); + + if (pthread_getschedparam(thread, &policy, &sched) < 0) { + return SDL_SetError("pthread_getschedparam() failed"); + } + if (priority == SDL_THREAD_PRIORITY_LOW) { + sched.sched_priority = sched_get_priority_min(policy); + } else if (priority == SDL_THREAD_PRIORITY_HIGH) { + sched.sched_priority = sched_get_priority_max(policy); + } else { + int min_priority = sched_get_priority_min(policy); + int max_priority = sched_get_priority_max(policy); + sched.sched_priority = (min_priority + (max_priority - min_priority) / 2); + } + if (pthread_setschedparam(thread, policy, &sched) < 0) { + return SDL_SetError("pthread_setschedparam() failed"); + } + return 0; +#endif +} + +void +SDL_SYS_WaitThread(SDL_Thread * thread) +{ + pthread_join(thread->handle, 0); +} + +#endif +#define ARRAY_CHUNKSIZE 32 + +static int SDL_maxthreads = 0; +static int SDL_numthreads = 0; +static SDL_Thread **SDL_Threads = NULL; +static SDL_mutex *thread_lock = NULL; + +static int +SDL_ThreadsInit(void) +{ + int retval; + + retval = 0; + thread_lock = SDL_CreateMutex(); + if (thread_lock == NULL) { + retval = -1; + } + return (retval); +} + +#if 0 +static void +SDL_ThreadsQuit(void) +{ + SDL_mutex *mutex; + + mutex = thread_lock; + thread_lock = NULL; + if (mutex != NULL) { + SDL_DestroyMutex(mutex); + } +} +#endif + +static void +SDL_AddThread(SDL_Thread * thread) +{ + + if (!thread_lock) { + if (SDL_ThreadsInit() < 0) { + return; + } + } + SDL_LockMutex(thread_lock); + +#ifdef DEBUG_THREADS + printf("Adding thread (%d already - %d max)\n", + SDL_numthreads, SDL_maxthreads); +#endif + if (SDL_numthreads == SDL_maxthreads) { + SDL_Thread **threads; + threads = (SDL_Thread **) SDL_realloc(SDL_Threads, + (SDL_maxthreads + + ARRAY_CHUNKSIZE) * + (sizeof *threads)); + if (threads == NULL) { + SDL_OutOfMemory(); + goto done; + } + SDL_maxthreads += ARRAY_CHUNKSIZE; + SDL_Threads = threads; + } + SDL_Threads[SDL_numthreads++] = thread; + done: + SDL_mutexV(thread_lock); +} + +static void +SDL_DelThread(SDL_Thread * thread) +{ + int i; + + if (!thread_lock) { + return; + } + SDL_LockMutex(thread_lock); + for (i = 0; i < SDL_numthreads; ++i) { + if (thread == SDL_Threads[i]) { + break; + } + } + if (i < SDL_numthreads) { + if (--SDL_numthreads > 0) { + while (i < SDL_numthreads) { + SDL_Threads[i] = SDL_Threads[i + 1]; + ++i; + } + } else { + SDL_maxthreads = 0; + SDL_free(SDL_Threads); + SDL_Threads = NULL; + } +#ifdef DEBUG_THREADS + printf("Deleting thread (%d left - %d max)\n", + SDL_numthreads, SDL_maxthreads); +#endif + } + SDL_mutexV(thread_lock); + +#if 0 + if (SDL_Threads == NULL) { + SDL_ThreadsQuit(); + } +#endif +} + +static SDL_error SDL_global_error; + +SDL_error * +SDL_GetErrBuf(void) +{ + SDL_error *errbuf; + + errbuf = &SDL_global_error; + if (SDL_Threads) { + int i; + SDL_threadID this_thread; + + this_thread = SDL_ThreadID(); + SDL_LockMutex(thread_lock); + for (i = 0; i < SDL_numthreads; ++i) { + if (this_thread == SDL_Threads[i]->threadid) { + errbuf = &SDL_Threads[i]->errbuf; + break; + } + } + SDL_mutexV(thread_lock); + } + return (errbuf); +} + +typedef struct +{ + int (SDLCALL * func) (void *); + void *data; + SDL_Thread *info; + SDL_sem *wait; +} thread_args; + +void +SDL_RunThread(void *data) +{ + thread_args *args = (thread_args *) data; + int (SDLCALL * userfunc) (void *) = args->func; + void *userdata = args->data; + int *statusloc = &args->info->status; + + SDL_SYS_SetupThread(args->info->name); + + args->info->threadid = SDL_ThreadID(); + + SDL_SemPost(args->wait); + + *statusloc = userfunc(userdata); +} + +#ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD +#undef SDL_CreateThread +DECLSPEC SDL_Thread *SDLCALL +SDL_CreateThread(int (SDLCALL * fn) (void *), + const char *name, void *data, + pfnSDL_CurrentBeginThread pfnBeginThread, + pfnSDL_CurrentEndThread pfnEndThread) +#else +DECLSPEC SDL_Thread *SDLCALL +SDL_CreateThread(int (SDLCALL * fn) (void *), + const char *name, void *data) +#endif +{ + SDL_Thread *thread; + thread_args *args; + int ret; + + thread = (SDL_Thread *) SDL_malloc(sizeof(*thread)); + if (thread == NULL) { + SDL_OutOfMemory(); + return (NULL); + } + SDL_memset(thread, 0, (sizeof *thread)); + thread->status = -1; + + if (name != NULL) { + thread->name = SDL_strdup(name); + if (thread->name == NULL) { + SDL_OutOfMemory(); + SDL_free(thread); + return (NULL); + } + } + + args = (thread_args *) SDL_malloc(sizeof(*args)); + if (args == NULL) { + SDL_OutOfMemory(); + SDL_free(thread->name); + SDL_free(thread); + return (NULL); + } + args->func = fn; + args->data = data; + args->info = thread; + args->wait = SDL_CreateSemaphore(0); + if (args->wait == NULL) { + SDL_free(thread->name); + SDL_free(thread); + SDL_free(args); + return (NULL); + } + + SDL_AddThread(thread); + +#ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD + ret = SDL_SYS_CreateThread(thread, args, pfnBeginThread, pfnEndThread); +#else + ret = SDL_SYS_CreateThread(thread, args); +#endif + if (ret >= 0) { + + SDL_SemWait(args->wait); + } else { + + SDL_DelThread(thread); + SDL_free(thread->name); + SDL_free(thread); + thread = NULL; + } + SDL_DestroySemaphore(args->wait); + SDL_free(args); + + return (thread); +} + +SDL_threadID +SDL_GetThreadID(SDL_Thread * thread) +{ + SDL_threadID id; + + if (thread) { + id = thread->threadid; + } else { + id = SDL_ThreadID(); + } + return id; +} + +const char * +SDL_GetThreadName(SDL_Thread * thread) +{ + return thread->name; +} + +int +SDL_SetThreadPriority(SDL_ThreadPriority priority) +{ + return SDL_SYS_SetThreadPriority(priority); +} + +void +SDL_WaitThread(SDL_Thread * thread, int *status) +{ + if (thread) { + SDL_SYS_WaitThread(thread); + if (status) { + *status = thread->status; + } + SDL_DelThread(thread); + SDL_free(thread->name); + SDL_free(thread); + } +} +#if SDL_THREAD_WINDOWS + +struct SDL_mutex +{ + CRITICAL_SECTION cs; +}; + +SDL_mutex * +SDL_CreateMutex(void) +{ + SDL_mutex *mutex; + + mutex = (SDL_mutex *) SDL_malloc(sizeof(*mutex)); + if (mutex) { + + InitializeCriticalSectionAndSpinCount(&mutex->cs, 2000); + } else { + SDL_OutOfMemory(); + } + return (mutex); +} + +void +SDL_DestroyMutex(SDL_mutex * mutex) +{ + if (mutex) { + DeleteCriticalSection(&mutex->cs); + SDL_free(mutex); + } +} + +int +SDL_LockMutex(SDL_mutex * mutex) +{ + if (mutex == NULL) { + return SDL_SetError("Passed a NULL mutex"); + } + + EnterCriticalSection(&mutex->cs); + return (0); +} + +int +SDL_TryLockMutex(SDL_mutex * mutex) +{ + int retval = 0; + if (mutex == NULL) { + return SDL_SetError("Passed a NULL mutex"); + } + + if (TryEnterCriticalSection(&mutex->cs) == 0) { + retval = SDL_MUTEX_TIMEDOUT; + } + return retval; +} + +int +SDL_UnlockMutex(SDL_mutex * mutex) +{ + if (mutex == NULL) { + return SDL_SetError("Passed a NULL mutex"); + } + + LeaveCriticalSection(&mutex->cs); + return (0); +} + +#endif +#if SDL_THREAD_WINDOWS + +struct SDL_semaphore +{ + HANDLE id; + LONG count; +}; + +SDL_sem * +SDL_CreateSemaphore(Uint32 initial_value) +{ + SDL_sem *sem; + + sem = (SDL_sem *) SDL_malloc(sizeof(*sem)); + if (sem) { + + sem->id = CreateSemaphore(NULL, initial_value, 32 * 1024, NULL); + sem->count = initial_value; + if (!sem->id) { + SDL_SetError("Couldn't create semaphore"); + SDL_free(sem); + sem = NULL; + } + } else { + SDL_OutOfMemory(); + } + return (sem); +} + +void +SDL_DestroySemaphore(SDL_sem * sem) +{ + if (sem) { + if (sem->id) { + CloseHandle(sem->id); + sem->id = 0; + } + SDL_free(sem); + } +} + +int +SDL_SemWaitTimeout(SDL_sem * sem, Uint32 timeout) +{ + int retval; + DWORD dwMilliseconds; + + if (!sem) { + return SDL_SetError("Passed a NULL sem"); + } + + if (timeout == SDL_MUTEX_MAXWAIT) { + dwMilliseconds = INFINITE; + } else { + dwMilliseconds = (DWORD) timeout; + } + switch (WaitForSingleObject(sem->id, dwMilliseconds)) { + case WAIT_OBJECT_0: + InterlockedDecrement(&sem->count); + retval = 0; + break; + case WAIT_TIMEOUT: + retval = SDL_MUTEX_TIMEDOUT; + break; + default: + retval = SDL_SetError("WaitForSingleObject() failed"); + break; + } + return retval; +} + +int +SDL_SemTryWait(SDL_sem * sem) +{ + return SDL_SemWaitTimeout(sem, 0); +} + +int +SDL_SemWait(SDL_sem * sem) +{ + return SDL_SemWaitTimeout(sem, SDL_MUTEX_MAXWAIT); +} + +Uint32 +SDL_SemValue(SDL_sem * sem) +{ + if (!sem) { + SDL_SetError("Passed a NULL sem"); + return 0; + } + return (Uint32)sem->count; +} + +int +SDL_SemPost(SDL_sem * sem) +{ + if (!sem) { + return SDL_SetError("Passed a NULL sem"); + } + + InterlockedIncrement(&sem->count); + if (ReleaseSemaphore(sem->id, 1, NULL) == FALSE) { + InterlockedDecrement(&sem->count); + return SDL_SetError("ReleaseSemaphore() failed"); + } + return 0; +} + +#endif +#if SDL_THREAD_WINDOWS + +#ifndef SDL_PASSED_BEGINTHREAD_ENDTHREAD + +#include + +#if (defined(__MINGW32__) && (__GNUC__ < 4)) +typedef unsigned long (__cdecl *pfnSDL_CurrentBeginThread) (void *, unsigned, + unsigned (__stdcall *func)(void *), void *arg, + unsigned, unsigned *threadID); +typedef void (__cdecl *pfnSDL_CurrentEndThread)(unsigned code); + +#elif defined(__WATCOMC__) + +#if __WATCOMC__ < 1240 +#define __watcall +#endif +typedef unsigned long (__watcall * pfnSDL_CurrentBeginThread) (void *, + unsigned, + unsigned + (__stdcall * + func) (void + *), + void *arg, + unsigned, + unsigned + *threadID); +typedef void (__watcall * pfnSDL_CurrentEndThread) (unsigned code); + +#else +typedef uintptr_t(__cdecl * pfnSDL_CurrentBeginThread) (void *, unsigned, + unsigned (__stdcall * + func) (void + *), + void *arg, unsigned, + unsigned *threadID); +typedef void (__cdecl * pfnSDL_CurrentEndThread) (unsigned code); +#endif +#endif + +typedef struct ThreadStartParms +{ + void *args; + pfnSDL_CurrentEndThread pfnCurrentEndThread; +} tThreadStartParms, *pThreadStartParms; + +static DWORD +RunThread(void *data) +{ + pThreadStartParms pThreadParms = (pThreadStartParms) data; + pfnSDL_CurrentEndThread pfnEndThread = pThreadParms->pfnCurrentEndThread; + void *args = pThreadParms->args; + SDL_free(pThreadParms); + SDL_RunThread(args); + if (pfnEndThread != NULL) + pfnEndThread(0); + return (0); +} + +static DWORD WINAPI +RunThreadViaCreateThread(LPVOID data) +{ + return RunThread(data); +} + +static unsigned __stdcall +RunThreadViaBeginThreadEx(void *data) +{ + return (unsigned) RunThread(data); +} + +#ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD +int +SDL_SYS_CreateThread(SDL_Thread * thread, void *args, + pfnSDL_CurrentBeginThread pfnBeginThread, + pfnSDL_CurrentEndThread pfnEndThread) +{ +#else +int +SDL_SYS_CreateThread(SDL_Thread * thread, void *args) +{ + pfnSDL_CurrentBeginThread pfnBeginThread = _beginthreadex; + pfnSDL_CurrentEndThread pfnEndThread = _endthreadex; +#endif + pThreadStartParms pThreadParms = + (pThreadStartParms) SDL_malloc(sizeof(tThreadStartParms)); + if (!pThreadParms) { + return SDL_OutOfMemory(); + } + + pThreadParms->pfnCurrentEndThread = pfnEndThread; + + pThreadParms->args = args; + + if (pfnBeginThread) { + unsigned threadid = 0; + thread->handle = (SYS_ThreadHandle) + ((size_t) pfnBeginThread(NULL, 0, RunThreadViaBeginThreadEx, + pThreadParms, 0, &threadid)); + } else { + DWORD threadid = 0; + thread->handle = CreateThread(NULL, 0, RunThreadViaCreateThread, + pThreadParms, 0, &threadid); + } + if (thread->handle == NULL) { + return SDL_SetError("Not enough resources to create thread"); + } + return 0; +} + +#ifdef _MSC_VER +#pragma pack(push,8) +typedef struct tagTHREADNAME_INFO +{ + DWORD dwType; + LPCSTR szName; + DWORD dwThreadID; + DWORD dwFlags; +} THREADNAME_INFO; +#pragma pack(pop) +#endif + +void +SDL_SYS_SetupThread(const char *name) +{ + if (name != NULL) { + #if 0 + #ifdef _MSC_VER + + THREADNAME_INFO inf; + inf.dwType = 0x1000; + inf.szName = name; + inf.dwThreadID = (DWORD) -1; + inf.dwFlags = 0; + + __try + { + RaiseException(0x406D1388, 0, sizeof(inf)/sizeof(DWORD), (DWORD*)&inf); + } + __except(EXCEPTION_CONTINUE_EXECUTION) + { + + } + #endif + #endif + } +} + +SDL_threadID +SDL_ThreadID(void) +{ + return ((SDL_threadID) GetCurrentThreadId()); +} + +int +SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority) +{ + int value; + + if (priority == SDL_THREAD_PRIORITY_LOW) { + value = THREAD_PRIORITY_LOWEST; + } else if (priority == SDL_THREAD_PRIORITY_HIGH) { + value = THREAD_PRIORITY_HIGHEST; + } else { + value = THREAD_PRIORITY_NORMAL; + } + if (!SetThreadPriority(GetCurrentThread(), value)) { + return WIN_SetError("SetThreadPriority()"); + } + return 0; +} + +void +SDL_SYS_WaitThread(SDL_Thread * thread) +{ + WaitForSingleObject(thread->handle, INFINITE); + CloseHandle(thread->handle); +} + +#endif +#ifdef SDL_TIMER_UNIX + +#include +#include +#include +#include + +#if HAVE_NANOSLEEP || HAVE_CLOCK_GETTIME +#include +#endif +#ifdef __APPLE__ +#include +#endif + +#if HAVE_CLOCK_GETTIME +static struct timespec start_ts; +#elif defined(__APPLE__) +static uint64_t start_mach; +mach_timebase_info_data_t mach_base_info; +#endif +static SDL_bool has_monotonic_time = SDL_FALSE; +static struct timeval start_tv; +static SDL_bool ticks_started = SDL_FALSE; + +void +SDL_TicksInit(void) +{ + if (ticks_started) { + return; + } + ticks_started = SDL_TRUE; + +#if HAVE_CLOCK_GETTIME + if (clock_gettime(CLOCK_MONOTONIC, &start_ts) == 0) { + has_monotonic_time = SDL_TRUE; + } else +#elif defined(__APPLE__) + kern_return_t ret = mach_timebase_info(&mach_base_info); + if (ret == 0) { + has_monotonic_time = SDL_TRUE; + start_mach = mach_absolute_time(); + } else +#endif + { + gettimeofday(&start_tv, NULL); + } +} + +void +SDL_TicksQuit(void) +{ + ticks_started = SDL_FALSE; +} + +Uint32 +SDL_GetTicks(void) +{ + Uint32 ticks; + if (!ticks_started) { + SDL_TicksInit(); + } + + if (has_monotonic_time) { +#if HAVE_CLOCK_GETTIME + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + ticks = (now.tv_sec - start_ts.tv_sec) * 1000 + (now.tv_nsec - + start_ts.tv_nsec) / 1000000; +#elif defined(__APPLE__) + uint64_t now = mach_absolute_time(); + ticks = (((now - start_mach) * mach_base_info.numer) / mach_base_info.denom) / 1000000; +#endif + } else { + struct timeval now; + + gettimeofday(&now, NULL); + ticks = + (now.tv_sec - start_tv.tv_sec) * 1000 + (now.tv_usec - + start_tv.tv_usec) / 1000; + } + return (ticks); +} + +Uint64 +SDL_GetPerformanceCounter(void) +{ + Uint64 ticks; + if (!ticks_started) { + SDL_TicksInit(); + } + + if (has_monotonic_time) { +#if HAVE_CLOCK_GETTIME + struct timespec now; + + clock_gettime(CLOCK_MONOTONIC, &now); + ticks = now.tv_sec; + ticks *= 1000000000; + ticks += now.tv_nsec; +#elif defined(__APPLE__) + ticks = mach_absolute_time(); +#endif + } else { + struct timeval now; + + gettimeofday(&now, NULL); + ticks = now.tv_sec; + ticks *= 1000000; + ticks += now.tv_usec; + } + return (ticks); +} + +Uint64 +SDL_GetPerformanceFrequency(void) +{ + if (!ticks_started) { + SDL_TicksInit(); + } + + if (has_monotonic_time) { +#if HAVE_CLOCK_GETTIME + return 1000000000; +#elif defined(__APPLE__) + Uint64 freq = mach_base_info.denom; + freq *= 1000000000; + freq /= mach_base_info.numer; + return freq; +#endif + } + + return 1000000; +} + +void +SDL_Delay(Uint32 ms) +{ + int was_error; + +#if HAVE_NANOSLEEP + struct timespec elapsed, tv; +#else + struct timeval tv; + Uint32 then, now, elapsed; +#endif + +#if HAVE_NANOSLEEP + elapsed.tv_sec = ms / 1000; + elapsed.tv_nsec = (ms % 1000) * 1000000; +#else + then = SDL_GetTicks(); +#endif + do { + errno = 0; + +#if HAVE_NANOSLEEP + tv.tv_sec = elapsed.tv_sec; + tv.tv_nsec = elapsed.tv_nsec; + was_error = nanosleep(&tv, &elapsed); +#else + + now = SDL_GetTicks(); + elapsed = (now - then); + then = now; + if (elapsed >= ms) { + break; + } + ms -= elapsed; + tv.tv_sec = ms / 1000; + tv.tv_usec = (ms % 1000) * 1000; + + was_error = select(0, NULL, NULL, NULL, &tv); +#endif + } while (was_error && (errno == EINTR)); +} + +#endif +#ifdef SDL_TIMER_WINDOWS + +#include + +static DWORD start; +static BOOL ticks_started = FALSE; + +#ifndef USE_GETTICKCOUNT + +static BOOL hires_timer_available; + +static LARGE_INTEGER hires_start_ticks; + +static LARGE_INTEGER hires_ticks_per_second; + +#ifndef __WINRT__ +static void +timeSetPeriod(UINT uPeriod) +{ + static UINT timer_period = 0; + + if (uPeriod != timer_period) { + if (timer_period) { + timeEndPeriod(timer_period); + } + + timer_period = uPeriod; + + if (timer_period) { + timeBeginPeriod(timer_period); + } + } +} + +static void +SDL_TimerResolutionChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + UINT uPeriod; + + if (hint && *hint) { + uPeriod = SDL_atoi(hint); + } else { + uPeriod = 1; + } + if (uPeriod || oldValue != hint) { + timeSetPeriod(uPeriod); + } +} +#endif + +#endif + +void +SDL_TicksInit(void) +{ + if (ticks_started) { + return; + } + ticks_started = SDL_TRUE; + +#ifdef USE_GETTICKCOUNT + start = GetTickCount(); +#else + + if (QueryPerformanceFrequency(&hires_ticks_per_second) == TRUE) { + hires_timer_available = TRUE; + QueryPerformanceCounter(&hires_start_ticks); + } else { + hires_timer_available = FALSE; +#ifdef __WINRT__ + start = 0; +#else + timeSetPeriod(1); + start = timeGetTime(); + + SDL_AddHintCallback(SDL_HINT_TIMER_RESOLUTION, + SDL_TimerResolutionChanged, NULL); +#endif + } +#endif +} + +void +SDL_TicksQuit(void) +{ +#ifndef USE_GETTICKCOUNT + if (!hires_timer_available) { +#ifndef __WINRT__ + SDL_DelHintCallback(SDL_HINT_TIMER_RESOLUTION, + SDL_TimerResolutionChanged, NULL); + + timeSetPeriod(0); +#endif + } +#endif + + ticks_started = SDL_FALSE; +} + +Uint32 +SDL_GetTicks(void) +{ + DWORD now; +#ifndef USE_GETTICKCOUNT + LARGE_INTEGER hires_now; +#endif + + if (!ticks_started) { + SDL_TicksInit(); + } + +#ifdef USE_GETTICKCOUNT + now = GetTickCount(); +#else + if (hires_timer_available) { + QueryPerformanceCounter(&hires_now); + + hires_now.QuadPart -= hires_start_ticks.QuadPart; + hires_now.QuadPart *= 1000; + hires_now.QuadPart /= hires_ticks_per_second.QuadPart; + + return (DWORD) hires_now.QuadPart; + } else { +#ifdef __WINRT__ + now = 0; +#else + now = timeGetTime(); +#endif + } +#endif + + return (now - start); +} + +Uint64 +SDL_GetPerformanceCounter(void) +{ + LARGE_INTEGER counter; + + if (!QueryPerformanceCounter(&counter)) { + return SDL_GetTicks(); + } + return counter.QuadPart; +} + +Uint64 +SDL_GetPerformanceFrequency(void) +{ + LARGE_INTEGER frequency; + + if (!QueryPerformanceFrequency(&frequency)) { + return 1000; + } + return frequency.QuadPart; +} + +#ifdef __WINRT__ +static void +Sleep(DWORD timeout) +{ + static HANDLE mutex = 0; + if ( ! mutex ) + { + mutex = CreateEventEx(0, 0, 0, EVENT_ALL_ACCESS); + } + WaitForSingleObjectEx(mutex, timeout, FALSE); +} +#endif + +void +SDL_Delay(Uint32 ms) +{ + Sleep(ms); +} + +#endif + +int SDL_ConvertAudio(SDL_AudioCVT* c) { return 0; } +int SDL_BuildAudioCVT(SDL_AudioCVT* c,SDL_AudioFormat sf, Uint8 sc, int sr, SDL_AudioFormat df, Uint8 dc, int dr) { return 0; } +void SDL_MixAudioFormat(Uint8 * dst, const Uint8 * src, SDL_AudioFormat format, Uint32 len, int volume) { } +static char SDL_Audio_Init_Done = 0; +Uint32 SDL_WasInit(Uint32 flags) { return (flags &16 ? SDL_Audio_Init_Done : 0); } +int SDL_InitSubSystem(Uint32 flags) { return (flags &16 ? (SDL_Audio_Init_Done = 1) : 0); } +#ifdef __WIN32__ +char* WIN_StringToUTF8(const unsigned short* s) { int l = WideCharToMultiByte(CP_UTF8,0,(LPCWSTR)s,-1,0,0,0,0); char* b = (char*)malloc(l); WideCharToMultiByte(CP_UTF8, 0, (LPWSTR)s, -1, b, l, NULL, NULL); return b; } +unsigned short* WIN_UTF8ToString(const char* s) { int l = MultiByteToWideChar(CP_UTF8,0,s,-1,NULL,0); unsigned short* b = (unsigned short*)malloc(l*sizeof(short)); MultiByteToWideChar(CP_UTF8,0,s,-1,(LPWSTR)b,l); return b; } +#if (defined(_MSC_VER) && (!defined(WINAPI_FAMILY) || WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) +#pragma comment (lib, "winmm.lib") +#endif +#endif diff --git a/source/engine/thirdparty/TinySoundFont/examples/minisdl_audio.h b/source/engine/thirdparty/TinySoundFont/examples/minisdl_audio.h new file mode 100644 index 0000000..e2bff7f --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/examples/minisdl_audio.h @@ -0,0 +1,2824 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2014 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_CONFIG_H +#define _SDL_CONFIG_H + +#ifndef _SDL_platform_h +#define _SDL_platform_h + +#if defined(_AIX) +#undef __AIX__ +#define __AIX__ 1 +#endif +#if defined(__HAIKU__) +#undef __HAIKU__ +#define __HAIKU__ 1 +#endif +#if defined(bsdi) || defined(__bsdi) || defined(__bsdi__) +#undef __BSDI__ +#define __BSDI__ 1 +#endif +#if defined(_arch_dreamcast) +#undef __DREAMCAST__ +#define __DREAMCAST__ 1 +#endif +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) +#undef __FREEBSD__ +#define __FREEBSD__ 1 +#endif +#if defined(hpux) || defined(__hpux) || defined(__hpux__) +#undef __HPUX__ +#define __HPUX__ 1 +#endif +#if defined(sgi) || defined(__sgi) || defined(__sgi__) || defined(_SGI_SOURCE) +#undef __IRIX__ +#define __IRIX__ 1 +#endif +#if defined(linux) || defined(__linux) || defined(__linux__) +#undef __LINUX__ +#define __LINUX__ 1 +#endif +#if defined(ANDROID) || defined(__ANDROID__) +#undef __ANDROID__ +#undef __LINUX__ +#define __ANDROID__ 1 +#endif + +#if defined(__APPLE__) + +#include "AvailabilityMacros.h" +#include "TargetConditionals.h" +#if TARGET_OS_IPHONE + +#undef __IPHONEOS__ +#define __IPHONEOS__ 1 +#undef __MACOSX__ +#else + +#undef __MACOSX__ +#define __MACOSX__ 1 +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +# error SDL for Mac OS X only supports deploying on 10.5 and above. +#endif +#endif +#endif + +#if defined(__NetBSD__) +#undef __NETBSD__ +#define __NETBSD__ 1 +#endif +#if defined(__OpenBSD__) +#undef __OPENBSD__ +#define __OPENBSD__ 1 +#endif +#if defined(__OS2__) +#undef __OS2__ +#define __OS2__ 1 +#endif +#if defined(osf) || defined(__osf) || defined(__osf__) || defined(_OSF_SOURCE) +#undef __OSF__ +#define __OSF__ 1 +#endif +#if defined(__QNXNTO__) +#undef __QNXNTO__ +#define __QNXNTO__ 1 +#endif +#if defined(riscos) || defined(__riscos) || defined(__riscos__) +#undef __RISCOS__ +#define __RISCOS__ 1 +#endif +#if defined(__SVR4) +#undef __SOLARIS__ +#define __SOLARIS__ 1 +#endif + +#if defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__) + +#if defined(__MINGW32__) || (defined(_MSC_VER) && (_MSC_VER >= 1700) && !_USING_V110_SDK71_) +#include +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#undef __WINDOWS__ +#define __WINDOWS__ 1 + +#elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) +#undef __WINRT__ +#define __WINRT__ 1 +#endif +#else +#undef __WINDOWS__ +#define __WINDOWS__ 1 +#endif +#endif + +#if defined(__WINDOWS__) +#undef __WIN32__ +#define __WIN32__ 1 +#endif +#if defined(__PSP__) +#undef __PSP__ +#define __PSP__ 1 +#endif + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern DECLSPEC const char * SDLCALL SDL_GetPlatform (void); + +#ifdef __cplusplus +} +#endif + +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif + +#if defined(__WIN32__) +#define HAVE_LIBC + +#ifndef _SDL_config_windows_h +#define _SDL_config_windows_h + +#if !defined(_STDINT_H_) && (!defined(HAVE_STDINT_H) || !_HAVE_STDINT_H) +#if defined(__GNUC__) || defined(__DMC__) || defined(__WATCOMC__) +#define HAVE_STDINT_H 1 +#elif defined(_MSC_VER) +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; +#ifndef _UINTPTR_T_DEFINED +#ifdef _WIN64 +typedef unsigned __int64 uintptr_t; +#else +typedef unsigned int uintptr_t; +#endif +#define _UINTPTR_T_DEFINED +#endif + +#if ((_MSC_VER <= 1200) && (!defined(DWORD_PTR))) +#define DWORD_PTR DWORD +#endif +#if ((_MSC_VER <= 1200) && (!defined(LONG_PTR))) +#define LONG_PTR LONG +#endif +#else +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef signed long long int64_t; +typedef unsigned long long uint64_t; +#ifndef _SIZE_T_DEFINED_ +#define _SIZE_T_DEFINED_ +typedef unsigned int size_t; +#endif +typedef unsigned int uintptr_t; +#endif +#endif + +#ifdef _WIN64 +# define SIZEOF_VOIDP 8 +#else +# define SIZEOF_VOIDP 4 +#endif + +#ifdef HAVE_LIBC + +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_SIGNAL_H 1 + +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE__STRREV 1 +#define HAVE__STRUPR 1 +#define HAVE__STRLWR 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE__LTOA 1 +#define HAVE__ULTOA 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE__STRICMP 1 +#define HAVE__STRNICMP 1 +#define HAVE_ATAN 1 +#define HAVE_ATAN2 1 +#define HAVE_ACOS 1 +#define HAVE_ASIN 1 +#define HAVE_CEIL 1 +#define HAVE_COS 1 +#define HAVE_COSF 1 +#define HAVE_FABS 1 +#define HAVE_FLOOR 1 +#define HAVE_LOG 1 +#define HAVE_POW 1 +#define HAVE_SIN 1 +#define HAVE_SINF 1 +#define HAVE_SQRT 1 +#if _MSC_VER >= 1800 +#define HAVE_STRTOLL 1 +#define HAVE_VSSCANF 1 +#define HAVE_COPYSIGN 1 +#define HAVE_SCALBN 1 +#endif +#if !defined(_MSC_VER) || defined(_USE_MATH_DEFINES) +#define HAVE_M_PI 1 +#endif +#else +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 +#endif + +#define SDL_AUDIO_DRIVER_DSOUND 1 +#define SDL_AUDIO_DRIVER_XAUDIO2 1 +#define SDL_AUDIO_DRIVER_WINMM 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +#define SDL_JOYSTICK_DINPUT 1 +#define SDL_HAPTIC_DINPUT 1 + +#define SDL_LOADSO_WINDOWS 1 + +#define SDL_THREAD_WINDOWS 1 + +#define SDL_TIMER_WINDOWS 1 + +#define SDL_VIDEO_DRIVER_DUMMY 1 +#define SDL_VIDEO_DRIVER_WINDOWS 1 + +#ifndef SDL_VIDEO_RENDER_D3D +#define SDL_VIDEO_RENDER_D3D 1 +#endif +#ifndef SDL_VIDEO_RENDER_D3D11 +#define SDL_VIDEO_RENDER_D3D11 0 +#endif + +#ifndef SDL_VIDEO_OPENGL +#define SDL_VIDEO_OPENGL 1 +#endif +#ifndef SDL_VIDEO_OPENGL_WGL +#define SDL_VIDEO_OPENGL_WGL 1 +#endif +#ifndef SDL_VIDEO_RENDER_OGL +#define SDL_VIDEO_RENDER_OGL 1 +#endif +#ifndef SDL_VIDEO_RENDER_OGL_ES2 +#define SDL_VIDEO_RENDER_OGL_ES2 1 +#endif +#ifndef SDL_VIDEO_OPENGL_ES2 +#define SDL_VIDEO_OPENGL_ES2 1 +#endif +#ifndef SDL_VIDEO_OPENGL_EGL +#define SDL_VIDEO_OPENGL_EGL 1 +#endif + +#define SDL_POWER_WINDOWS 1 + +#define SDL_FILESYSTEM_WINDOWS 1 + +#ifndef _WIN64 +#define SDL_ASSEMBLY_ROUTINES 1 +#endif + +#endif + +#elif defined(__MACOSX__) + +#ifndef _SDL_config_macosx_h +#define _SDL_config_macosx_h + +#include + +#ifdef __LP64__ + #define SIZEOF_VOIDP 8 +#else + #define SIZEOF_VOIDP 4 +#endif + +#define HAVE_ALLOCA_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_SIGNAL_H 1 + +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_SETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_UNSETENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE_STRLCPY 1 +#define HAVE_STRLCAT 1 +#define HAVE_STRDUP 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOULL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_STRNCASECMP 1 +#define HAVE_VSSCANF 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_CEIL 1 +#define HAVE_COPYSIGN 1 +#define HAVE_COS 1 +#define HAVE_COSF 1 +#define HAVE_FABS 1 +#define HAVE_FLOOR 1 +#define HAVE_LOG 1 +#define HAVE_POW 1 +#define HAVE_SCALBN 1 +#define HAVE_SIN 1 +#define HAVE_SINF 1 +#define HAVE_SQRT 1 +#define HAVE_SIGACTION 1 +#define HAVE_SETJMP 1 +#define HAVE_NANOSLEEP 1 +#define HAVE_SYSCONF 1 +#define HAVE_SYSCTLBYNAME 1 +#define HAVE_ATAN 1 +#define HAVE_ATAN2 1 +#define HAVE_ACOS 1 +#define HAVE_ASIN 1 + +#define SDL_AUDIO_DRIVER_COREAUDIO 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +#define SDL_JOYSTICK_IOKIT 1 +#define SDL_HAPTIC_IOKIT 1 + +#define SDL_LOADSO_DLOPEN 1 + +#define SDL_THREAD_PTHREAD 1 +#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX 1 + +#define SDL_TIMER_UNIX 1 + +#define SDL_VIDEO_DRIVER_COCOA 1 +#define SDL_VIDEO_DRIVER_DUMMY 1 +#undef SDL_VIDEO_DRIVER_X11 +#define SDL_VIDEO_DRIVER_X11_DYNAMIC "/usr/X11R6/lib/libX11.6.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT "/usr/X11R6/lib/libXext.6.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XINERAMA "/usr/X11R6/lib/libXinerama.1.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XINPUT2 "/usr/X11R6/lib/libXi.6.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR "/usr/X11R6/lib/libXrandr.2.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XSS "/usr/X11R6/lib/libXss.1.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XVIDMODE "/usr/X11R6/lib/libXxf86vm.1.dylib" +#define SDL_VIDEO_DRIVER_X11_XINERAMA 1 +#define SDL_VIDEO_DRIVER_X11_XRANDR 1 +#define SDL_VIDEO_DRIVER_X11_XSCRNSAVER 1 +#define SDL_VIDEO_DRIVER_X11_XSHAPE 1 +#define SDL_VIDEO_DRIVER_X11_XVIDMODE 1 +#define SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM 1 + +#ifdef MAC_OS_X_VERSION_10_8 + +#define SDL_VIDEO_DRIVER_X11_XINPUT2 1 +#define SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1 +#define SDL_VIDEO_DRIVER_X11_CONST_PARAM_XEXTADDDISPLAY 1 +#endif + +#ifndef SDL_VIDEO_RENDER_OGL +#define SDL_VIDEO_RENDER_OGL 1 +#endif + +#ifndef SDL_VIDEO_OPENGL +#define SDL_VIDEO_OPENGL 1 +#endif +#ifndef SDL_VIDEO_OPENGL_CGL +#define SDL_VIDEO_OPENGL_CGL 1 +#endif +#ifndef SDL_VIDEO_OPENGL_GLX +#define SDL_VIDEO_OPENGL_GLX 1 +#endif + +#define SDL_POWER_MACOSX 1 + +#define SDL_FILESYSTEM_COCOA 1 + +#define SDL_ASSEMBLY_ROUTINES 1 +#ifdef __ppc__ +#define SDL_ALTIVEC_BLITTERS 1 +#endif + +#endif + +#elif defined(__LINUX__) + +#ifndef _SDL_config_h +#define _SDL_config_h + +#ifdef _MSC_VER +#error You should run hg revert SDL_config.h +#endif + +#ifdef __LP64__ +#define SIZEOF_VOIDP 8 +#else +#define SIZEOF_VOIDP 4 +#endif +#define HAVE_GCC_ATOMICS 1 + +#define HAVE_PTHREAD_SPINLOCK 1 + +#define HAVE_LIBC 1 +#if HAVE_LIBC + +#define HAVE_ALLOCA_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STDARG_H 1 +#define HAVE_MALLOC_H 1 +#define HAVE_MEMORY_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_ICONV_H 1 +#define HAVE_SIGNAL_H 1 + +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#ifndef __WIN32__ +#define HAVE_GETENV 1 +#define HAVE_SETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_UNSETENV 1 +#endif +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 + +#define HAVE_STRDUP 1 + +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 + +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 + +#define HAVE_STRTOLL 1 +#define HAVE_STRTOULL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 + +#define HAVE_STRCASECMP 1 + +#define HAVE_STRNCASECMP 1 + +#define HAVE_VSSCANF 1 + +#define HAVE_VSNPRINTF 1 +#define HAVE_M_PI +#define HAVE_ATAN 1 +#define HAVE_ATAN2 1 +#define HAVE_ACOS 1 +#define HAVE_ASIN 1 +#define HAVE_CEIL 1 +#define HAVE_COPYSIGN 1 +#define HAVE_COS 1 +#define HAVE_COSF 1 +#define HAVE_FABS 1 +#define HAVE_FLOOR 1 +#define HAVE_LOG 1 +#define HAVE_POW 1 +#define HAVE_SCALBN 1 +#define HAVE_SIN 1 +#define HAVE_SINF 1 +#define HAVE_SQRT 1 +#define HAVE_FSEEKO 1 +#define HAVE_FSEEKO64 1 +#define HAVE_SIGACTION 1 +#define HAVE_SA_SIGACTION 1 +#define HAVE_SETJMP 1 +#define HAVE_NANOSLEEP 1 +#define HAVE_SYSCONF 1 + +#define HAVE_CLOCK_GETTIME 1 + +#define HAVE_MPROTECT 1 +#define HAVE_ICONV 1 +#define HAVE_PTHREAD_SETNAME_NP 1 + +#define HAVE_SEM_TIMEDWAIT 1 + +#else +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 +#define HAVE_STDINT_H 1 +#endif + +#define SDL_AUDIO_DRIVER_ALSA 1 +#if defined(__LP64__) || defined(_LP64) || defined(__LLP64__) || defined(__x86_64__) +#define SDL_AUDIO_DRIVER_ALSA_DYNAMIC "/usr/lib/x86_64-linux-gnu/libasound.so.2" +#else +#define SDL_AUDIO_DRIVER_ALSA_DYNAMIC "/usr/lib/i386-linux-gnu/libasound.so.2" +#endif + +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +#define SDL_AUDIO_DRIVER_OSS 1 + +#define SDL_INPUT_LINUXEV 1 +#define SDL_INPUT_LINUXKD 1 + +#define SDL_JOYSTICK_LINUX 1 + +#define SDL_HAPTIC_LINUX 1 + +#define SDL_LOADSO_DLOPEN 1 + +#define SDL_THREAD_PTHREAD 1 +#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX 1 + +#define SDL_TIMER_UNIX 1 + +#define SDL_VIDEO_DRIVER_DUMMY 1 + +#define SDL_VIDEO_DRIVER_X11 1 + +#define SDL_VIDEO_DRIVER_X11_DYNAMIC "libX11.so.6" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT "libXext.so.6" + +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XVIDMODE "libXxf86vm.so.1" + +#define SDL_VIDEO_DRIVER_X11_XSHAPE 1 +#define SDL_VIDEO_DRIVER_X11_XVIDMODE 1 +#define SDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS 1 +#define SDL_VIDEO_DRIVER_X11_CONST_PARAM_XDATA32 1 +#define SDL_VIDEO_DRIVER_X11_CONST_PARAM_XEXTADDDISPLAY 1 +#define SDL_VIDEO_DRIVER_X11_HAS_XKBKEYCODETOKEYSYM 1 + +#define SDL_VIDEO_RENDER_OGL 1 + +#define SDL_VIDEO_OPENGL 1 + +#define SDL_VIDEO_OPENGL_GLX 1 + +#define SDL_POWER_LINUX 1 + +#define SDL_FILESYSTEM_UNIX 1 + +#define SDL_ASSEMBLY_ROUTINES 1 + +#endif + +#else + +#ifndef _SDL_config_minimal_h +#define _SDL_config_minimal_h + +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 + +#if defined(_MSC_VER) && (_MSC_VER < 1600) + +typedef unsigned int size_t; +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef signed long long int64_t; +typedef unsigned long long uint64_t; +typedef unsigned long uintptr_t; +#else +#define HAVE_STDINT_H 1 +#endif + +#ifdef __GNUC__ +#define HAVE_GCC_SYNC_LOCK_TEST_AND_SET 1 +#endif + +#define SDL_AUDIO_DRIVER_DUMMY 1 + +#define SDL_JOYSTICK_DISABLED 1 + +#define SDL_HAPTIC_DISABLED 1 + +#define SDL_LOADSO_DISABLED 1 + +#define SDL_THREADS_DISABLED 1 + +#define SDL_TIMERS_DISABLED 1 + +#define SDL_VIDEO_DRIVER_DUMMY 1 + +#define SDL_FILESYSTEM_DUMMY 1 + +#endif + +#endif + +#ifdef DECLSPEC +#undef DECLSPEC +#endif +#define DECLSPEC + +#if (defined(_MSC_VER) && !defined(WINAPI_FAMILY)) +//#pragma warning(disable:4002) +//#pragma warning(disable:4003) +//#pragma warning(disable:4018) +//#pragma warning(disable:4244) +#pragma once +#endif + +#define SDL_INIT_AUDIO 0x00000010 +#define SDL_INIT_VIDEO 0x00000020 +#define SDL_INIT_EVENTS 0x00004000 +#define SDL_QuitSubSystem(s) NULL +#define HAVE_ITOA 1 +#undef SDL_AUDIO_DRIVER_XAUDIO2 +#undef SDL_AUDIO_DRIVER_WINMM +#undef SDL_AUDIO_DRIVER_DUMMY +#undef SDL_AUDIO_DRIVER_DISK +#undef SDL_VIDEO_DRIVER_DUMMY +#undef SDL_VIDEO_RENDER_OGL_ES2 +#undef SDL_VIDEO_OPENGL_ES2 +#undef SDL_VIDEO_OPENGL_EGL +#undef SDL_FILESYSTEM_WINDOWS +#undef SDL_FILESYSTEM_COCOA +#undef SDL_FILESYSTEM_DUMMY +#undef SDL_FILESYSTEM_UNIX +#undef SDL_HAPTIC_DINPUT +#undef SDL_JOYSTICK_WINMM +#undef SDL_HAPTIC_IOKIT +#define SDL_DISABLE_WINDOWS_IME 1 +#define SDL_DISABLE_SCANCODENAMES 1 + +#define _SDL_H +#define _SDL_error_h +#define _SDL_rwops_h +#define _SDL_hints_h +#define _SDL_assert_h +#define _SDL_main_h +#define _SDL_render_h +#define _SDL_gesture_c_h +#define _SDL_stdinc_h +static SDL_INLINE int SDL_SetError(const char *errstr, ...) { (void)errstr; return 1; } +#define SDL_assert(a) +#define SDL_Error(e) +#define SDL_ASSERT_LEVEL 0 +#define SDL_ClearError() +static SDL_INLINE int SDL_OutOfMemory() { return 1; } +#define SDL_Unsupported() 1 +#define SDL_InvalidParamError(p) 1 +#define SDL_arraysize(array) (sizeof(array)/sizeof(array[0])) +#define SDL_RWops void +#define SDL_RWFromMem(b,l) NULL +#define SDL_RWread(ctx, ptr, size, n) 0 +#define SDL_RWwrite(ctx, ptr, size, n) 0 +#define SDL_RWclose(ctx) 0 +#define SDL_WriteLE16(a, b) 0 +#define SDL_WriteLE32(a, b) 0 + +#define SDL_GestureAddTouch(t) NULL +#define SDL_GestureProcessEvent(e) NULL + +#define SDL_GetHint(n) NULL +#define SDL_AddHintCallback(n, f, x) NULL +#define SDL_DelHintCallback(n, f, x) NULL +#undef HAVE_M_PI +#include +#define HAVE_GETENV 1 +#undef getenv +#define getenv(env) NULL +#define HAVE_STRDUP 1 +#define HAVE_SNPRINTF 1 +#include +#ifndef snprintf +#define snprintf _snprintf +#endif + +#define SDL_Renderer void +#define SDL_Texture void +typedef struct SDL_RendererInfo { const char* name; int num_texture_formats; int *texture_formats; } SDL_RendererInfo; +#define SDL_GetNumRenderDrivers() 0 +#define SDL_GetRenderDriverInfo(i, info) { (info)->name = NULL; (info)->num_texture_formats = 0; } +#define SDL_CreateRenderer(a,b,c) NULL +#define SDL_DestroyRenderer(a) +#define SDL_DestroyTexture(a) +#define SDL_GetRendererInfo(a, b) NULL +#define SDL_TEXTUREACCESS_STREAMING 0 +#define SDL_RenderSetViewport(a,b) +#define SDL_UpdateTexture(a,b,c,d) 0 +#define SDL_RenderCopy(a,b,c,d) 0 +#define SDL_RenderPresent(a) +#define SDL_CreateTexture(a, b, c, d, e) NULL + +#define SDL_malloc(s) malloc(s) +#define SDL_calloc(n,s) calloc(n,s) +#define SDL_realloc(m,s) realloc(m,s) +#define SDL_free(m) free(m) +#define SDL_getenv(e) NULL +#define SDL_qsort(a,b,c,d) NULL +#define SDL_abs(v) abs(v) + +#define SDL_iconv_utf8_locale(S) strdup(S) +#define SDL_iconv_string(t,f,S,l) strdup(S) + +#ifdef __WIN32__ +#define _INCLUDED_WINDOWS_H +#define WIN32_LEAN_AND_MEAN +#define STRICT 1 +#define UNICODE 1 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x501 +#include +#undef CreateWindow +#ifdef __WINRT__ +#define WIN_CoInitialize() S_OK +#define WIN_CoUninitialize() NULL +#else +#define WIN_CoInitialize() S_OK;{HRESULT hr=CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);if(hr==RPC_E_CHANGED_MODE)hr=CoInitializeEx(NULL,COINIT_MULTITHREADED);} +#define WIN_CoUninitialize() {CoUninitialize();} +#endif + +#define WIN_SetError(e) -1 +#define CreateWindow(a, b, c, d, e, f, g, h, i, j, k) CreateWindowW(a, b, c, d, e, f, g, h, i, j, k) +#endif + +#ifndef _SDL_stdinc_h_ZL +#define _SDL_stdinc_h_ZL + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#if defined(STDC_HEADERS) +# include +# include +# include +#else +# if defined(HAVE_STDLIB_H) +# include +# elif defined(HAVE_MALLOC_H) +# include +# endif +# if defined(HAVE_STDDEF_H) +# include +# endif +# if defined(HAVE_STDARG_H) +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H) +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#if defined(HAVE_INTTYPES_H) +# include +#elif defined(HAVE_STDINT_H) +# include +#endif +#ifdef HAVE_CTYPE_H +# include +#endif +#ifdef HAVE_MATH_H +# if defined(__WINRT__) + +# define _USE_MATH_DEFINES +# endif +# include +#endif +#ifdef HAVE_FLOAT_H +# include +#endif +#if defined(HAVE_ICONV) && defined(HAVE_ICONV_H) +# include +#endif + +#define SDL_arraysize(array) (sizeof(array)/sizeof(array[0])) +#define SDL_TABLESIZE(table) SDL_arraysize(table) + +#ifdef __cplusplus +#define SDL_reinterpret_cast(type, expression) reinterpret_cast(expression) +#define SDL_static_cast(type, expression) static_cast(expression) +#define SDL_const_cast(type, expression) const_cast(expression) +#else +#define SDL_reinterpret_cast(type, expression) ((type)(expression)) +#define SDL_static_cast(type, expression) ((type)(expression)) +#define SDL_const_cast(type, expression) ((type)(expression)) +#endif + +#define SDL_FOURCC(A, B, C, D) \ + ((SDL_static_cast(Uint32, SDL_static_cast(Uint8, (A))) << 0) | \ + (SDL_static_cast(Uint32, SDL_static_cast(Uint8, (B))) << 8) | \ + (SDL_static_cast(Uint32, SDL_static_cast(Uint8, (C))) << 16) | \ + (SDL_static_cast(Uint32, SDL_static_cast(Uint8, (D))) << 24)) + +typedef enum +{ + SDL_FALSE = 0, + SDL_TRUE = 1 +} SDL_bool; + +typedef int8_t Sint8; + +typedef uint8_t Uint8; + +typedef int16_t Sint16; + +typedef uint16_t Uint16; + +typedef int32_t Sint32; + +typedef uint32_t Uint32; + +typedef int64_t Sint64; + +typedef uint64_t Uint64; + +#define SDL_COMPILE_TIME_ASSERT(name, x) \ + typedef int SDL_dummy_ ## name[(x) * 2 - 1] + +#ifndef DOXYGEN_SHOULD_IGNORE_THIS +SDL_COMPILE_TIME_ASSERT(uint8, sizeof(Uint8) == 1); +SDL_COMPILE_TIME_ASSERT(sint8, sizeof(Sint8) == 1); +SDL_COMPILE_TIME_ASSERT(uint16, sizeof(Uint16) == 2); +SDL_COMPILE_TIME_ASSERT(sint16, sizeof(Sint16) == 2); +SDL_COMPILE_TIME_ASSERT(uint32, sizeof(Uint32) == 4); +SDL_COMPILE_TIME_ASSERT(sint32, sizeof(Sint32) == 4); +SDL_COMPILE_TIME_ASSERT(uint64, sizeof(Uint64) == 8); +SDL_COMPILE_TIME_ASSERT(sint64, sizeof(Sint64) == 8); +#endif + +#ifndef DOXYGEN_SHOULD_IGNORE_THIS +#if !defined(__ANDROID__) + +typedef enum +{ + DUMMY_ENUM_VALUE +} SDL_DUMMY_ENUM; + +SDL_COMPILE_TIME_ASSERT(enum, sizeof(SDL_DUMMY_ENUM) == sizeof(int)); +#endif +#endif + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(HAVE_ALLOCA) && !defined(alloca) +# if defined(HAVE_ALLOCA_H) +# include +# elif defined(__GNUC__) +# define alloca __builtin_alloca +# elif defined(_MSC_VER) +# include +# define alloca _alloca +# elif defined(__WATCOMC__) +# include +# elif defined(__BORLANDC__) +# include +# elif defined(__DMC__) +# include +# elif defined(__AIX__) +#pragma alloca +# elif defined(__MRC__) +void *alloca(unsigned); +# else +char *alloca(); +# endif +#endif +#ifdef HAVE_ALLOCA +#define SDL_stack_alloc(type, count) (type*)alloca(sizeof(type)*(count)) +#define SDL_stack_free(data) +#else +#define SDL_stack_alloc(type, count) (type*)malloc(sizeof(type)*(count)) +#define SDL_stack_free(data) free(data) +#endif + +#define SDL_min(x, y) (((x) < (y)) ? (x) : (y)) +#define SDL_max(x, y) (((x) > (y)) ? (x) : (y)) + +#define SDL_isdigit isdigit +#define SDL_isspace isspace +#define SDL_toupper toupper +#define SDL_tolower tolower +#define SDL_memset memset +#define SDL_zero(x) memset(&(x), 0, sizeof((x))) +#define SDL_zerop(x) memset((x), 0, sizeof(*(x))) +#define SDL_memset4(dst, val, dwords) memset((dst), (val), (dwords)*4) +#define SDL_memcpy memcpy +#define SDL_memcpy4(dst, src, dwords) memcpy((dst), (src), (dwords)*4) +#define SDL_memmove memmove +#define SDL_memcmp memcmp +#define SDL_strlen strlen +#define SDL_strlcpy(dst,src,maxlen) strncpy(dst,src,maxlen-1) +#define SDL_utf8strlcpy(dst,src,dst_bytes) strncpy(dst,src,dst_bytes-1) +#define SDL_strlcat(dst,src,maxlen) strcat(dst,src) +#define SDL_strdup strdup +#define SDL_strchr strchr +#define SDL_strrchr strrchr +#define SDL_strstr strstr + +#define SDL_itoa(i, s, rdx) sprintf(s, "%i", (int)i); { typedef int _chk[(rdx==10)?1:-1]; } + +#define SDL_atoi atoi +#define SDL_atof atof +#define SDL_strtol strtol +#define SDL_strtoul strtoul +#define SDL_strtod strtod + +#define SDL_strcmp strcmp +#define SDL_strncmp strncmp +#define SDL_strcasecmp strcmp +#define SDL_strncasecmp strncmp +#if __STDC_WANT_SECURE_LIB__ +static SDL_INLINE int SDL_snprintf(char *text, size_t maxlen, const char *fmt, ...) { va_list ap; va_start(ap, fmt); return vsprintf_s(text, maxlen, fmt, ap); } +#else +static SDL_INLINE int SDL_snprintf(char *text, size_t maxlen, const char *fmt, ...) { va_list ap; (void)maxlen; va_start(ap, fmt); return vsprintf(text, fmt, ap); } +#endif +#define SDL_sscanf(t,f,v) NULL + +#ifndef HAVE_M_PI +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327950288 +#endif +#endif + +#define SDL_ceil ceil + +#ifdef __cplusplus +} +#endif + +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif + +#ifdef __cplusplus +extern "C" { +#endif +extern DECLSPEC void SDLCALL SDL_Delay(Uint32 ms); +extern DECLSPEC Uint32 SDLCALL SDL_WasInit(Uint32 flags); +extern DECLSPEC int SDLCALL SDL_InitSubSystem(Uint32 flags); +#ifdef __WIN32__ +extern DECLSPEC int SDLCALL SDL_RegisterApp(char *name, Uint32 style, void *hInst); +extern DECLSPEC void SDLCALL SDL_UnregisterApp(void); +char* WIN_StringToUTF8(const unsigned short* s); +unsigned short* WIN_UTF8ToString(const char* s); +#endif +#define SDL_TLSCreate() 0 +#if defined(__LP64__) || defined(_LP64) || defined(__LLP64__) || defined(__x86_64__) || defined(__ia64__) || defined(_WIN64) || defined(_M_X64) +unsigned int SDL_TLSSet64(void*); +void* SDL_TLSGet64(unsigned int); +#define SDL_TLSSet(tls, v, cb) tls = SDL_TLSSet64(v) +#define SDL_TLSGet(tls) SDL_TLSGet64(tls) +#else +#define SDL_TLSSet(tls, v, cb) tls = (SDL_TLSID)(v) +#define SDL_TLSGet(tls) (tls) +#endif +#ifdef __cplusplus +} +#endif + +#endif + +#ifndef _SDL_audio_h +#define _SDL_audio_h + +#ifndef _SDL_error_h +#define _SDL_error_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern DECLSPEC int SDLCALL SDL_SetError(const char *fmt, ...); +extern DECLSPEC const char *SDLCALL SDL_GetError(void); +extern DECLSPEC void SDLCALL SDL_ClearError(void); + +#define SDL_OutOfMemory() SDL_Error(SDL_ENOMEM) +#define SDL_Unsupported() SDL_Error(SDL_UNSUPPORTED) +#define SDL_InvalidParamError(param) SDL_SetError("Parameter '%s' is invalid", (param)) +typedef enum +{ + SDL_ENOMEM, + SDL_EFREAD, + SDL_EFWRITE, + SDL_EFSEEK, + SDL_UNSUPPORTED, + SDL_LASTERROR +} SDL_errorcode; + +extern DECLSPEC int SDLCALL SDL_Error(SDL_errorcode code); + +#ifdef __cplusplus +} +#endif + +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif + +#ifndef _SDL_endian_h +#define _SDL_endian_h + +#define SDL_LIL_ENDIAN 1234 +#define SDL_BIG_ENDIAN 4321 + +#ifndef SDL_BYTEORDER +#ifdef __linux__ +#include +#define SDL_BYTEORDER __BYTE_ORDER +#else +#if defined(__hppa__) || \ + defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ + (defined(__MIPS__) && defined(__MISPEB__)) || \ + defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ + defined(__sparc__) +#define SDL_BYTEORDER SDL_BIG_ENDIAN +#else +#define SDL_BYTEORDER SDL_LIL_ENDIAN +#endif +#endif +#endif + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__GNUC__) && defined(__i386__) && \ + !(__GNUC__ == 2 && __GNUC_MINOR__ == 95) +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + __asm__("xchgb %b0,%h0": "=q"(x):"0"(x)); + return x; +} +#elif defined(__GNUC__) && defined(__x86_64__) +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + __asm__("xchgb %b0,%h0": "=Q"(x):"0"(x)); + return x; +} +#elif defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__)) +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + int result; + + __asm__("rlwimi %0,%2,8,16,23": "=&r"(result):"0"(x >> 8), "r"(x)); + return (Uint16)result; +} +#elif defined(__GNUC__) && (defined(__M68000__) || defined(__M68020__)) && !defined(__mcoldfire__) +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + __asm__("rorw #8,%0": "=d"(x): "0"(x):"cc"); + return x; +} +#else +SDL_FORCE_INLINE Uint16 +SDL_Swap16(Uint16 x) +{ + return SDL_static_cast(Uint16, ((x << 8) | (x >> 8))); +} +#endif + +#if defined(__GNUC__) && defined(__i386__) +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + __asm__("bswap %0": "=r"(x):"0"(x)); + return x; +} +#elif defined(__GNUC__) && defined(__x86_64__) +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + __asm__("bswapl %0": "=r"(x):"0"(x)); + return x; +} +#elif defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__)) +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + Uint32 result; + + __asm__("rlwimi %0,%2,24,16,23": "=&r"(result):"0"(x >> 24), "r"(x)); + __asm__("rlwimi %0,%2,8,8,15": "=&r"(result):"0"(result), "r"(x)); + __asm__("rlwimi %0,%2,24,0,7": "=&r"(result):"0"(result), "r"(x)); + return result; +} +#elif defined(__GNUC__) && (defined(__M68000__) || defined(__M68020__)) && !defined(__mcoldfire__) +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + __asm__("rorw #8,%0\n\tswap %0\n\trorw #8,%0": "=d"(x): "0"(x):"cc"); + return x; +} +#else +SDL_FORCE_INLINE Uint32 +SDL_Swap32(Uint32 x) +{ + return SDL_static_cast(Uint32, ((x << 24) | ((x << 8) & 0x00FF0000) | + ((x >> 8) & 0x0000FF00) | (x >> 24))); +} +#endif + +#if defined(__GNUC__) && defined(__i386__) +SDL_FORCE_INLINE Uint64 +SDL_Swap64(Uint64 x) +{ + union + { + struct + { + Uint32 a, b; + } s; + Uint64 u; + } v; + v.u = x; + __asm__("bswapl %0 ; bswapl %1 ; xchgl %0,%1": "=r"(v.s.a), "=r"(v.s.b):"0"(v.s.a), + "1"(v.s. + b)); + return v.u; +} +#elif defined(__GNUC__) && defined(__x86_64__) +SDL_FORCE_INLINE Uint64 +SDL_Swap64(Uint64 x) +{ + __asm__("bswapq %0": "=r"(x):"0"(x)); + return x; +} +#else +SDL_FORCE_INLINE Uint64 +SDL_Swap64(Uint64 x) +{ + Uint32 hi, lo; + + lo = SDL_static_cast(Uint32, x & 0xFFFFFFFF); + x >>= 32; + hi = SDL_static_cast(Uint32, x & 0xFFFFFFFF); + x = SDL_Swap32(lo); + x <<= 32; + x |= SDL_Swap32(hi); + return (x); +} +#endif + +SDL_FORCE_INLINE float +SDL_SwapFloat(float x) +{ + union + { + float f; + Uint32 ui32; + } swapper; + swapper.f = x; + swapper.ui32 = SDL_Swap32(swapper.ui32); + return swapper.f; +} + +#if SDL_BYTEORDER == SDL_LIL_ENDIAN +#define SDL_SwapLE16(X) (X) +#define SDL_SwapLE32(X) (X) +#define SDL_SwapLE64(X) (X) +#define SDL_SwapFloatLE(X) (X) +#define SDL_SwapBE16(X) SDL_Swap16(X) +#define SDL_SwapBE32(X) SDL_Swap32(X) +#define SDL_SwapBE64(X) SDL_Swap64(X) +#define SDL_SwapFloatBE(X) SDL_SwapFloat(X) +#else +#define SDL_SwapLE16(X) SDL_Swap16(X) +#define SDL_SwapLE32(X) SDL_Swap32(X) +#define SDL_SwapLE64(X) SDL_Swap64(X) +#define SDL_SwapFloatLE(X) SDL_SwapFloat(X) +#define SDL_SwapBE16(X) (X) +#define SDL_SwapBE32(X) (X) +#define SDL_SwapBE64(X) (X) +#define SDL_SwapFloatBE(X) (X) +#endif + +#ifdef __cplusplus +} +#endif + +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif + +#ifndef _SDL_mutex_h +#define _SDL_mutex_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define SDL_MUTEX_TIMEDOUT 1 + +#define SDL_MUTEX_MAXWAIT (~(Uint32)0) + +struct SDL_mutex; +typedef struct SDL_mutex SDL_mutex; + +extern DECLSPEC SDL_mutex *SDLCALL SDL_CreateMutex(void); + +#define SDL_mutexP(m) SDL_LockMutex(m) +extern DECLSPEC int SDLCALL SDL_LockMutex(SDL_mutex * mutex); + +extern DECLSPEC int SDLCALL SDL_TryLockMutex(SDL_mutex * mutex); + +#define SDL_mutexV(m) SDL_UnlockMutex(m) +extern DECLSPEC int SDLCALL SDL_UnlockMutex(SDL_mutex * mutex); + +extern DECLSPEC void SDLCALL SDL_DestroyMutex(SDL_mutex * mutex); + +struct SDL_semaphore; +typedef struct SDL_semaphore SDL_sem; + +extern DECLSPEC SDL_sem *SDLCALL SDL_CreateSemaphore(Uint32 initial_value); + +extern DECLSPEC void SDLCALL SDL_DestroySemaphore(SDL_sem * sem); + +extern DECLSPEC int SDLCALL SDL_SemWait(SDL_sem * sem); + +extern DECLSPEC int SDLCALL SDL_SemTryWait(SDL_sem * sem); + +extern DECLSPEC int SDLCALL SDL_SemWaitTimeout(SDL_sem * sem, Uint32 ms); + +extern DECLSPEC int SDLCALL SDL_SemPost(SDL_sem * sem); + +extern DECLSPEC Uint32 SDLCALL SDL_SemValue(SDL_sem * sem); + +struct SDL_cond; +typedef struct SDL_cond SDL_cond; + +extern DECLSPEC SDL_cond *SDLCALL SDL_CreateCond(void); + +extern DECLSPEC void SDLCALL SDL_DestroyCond(SDL_cond * cond); + +extern DECLSPEC int SDLCALL SDL_CondSignal(SDL_cond * cond); + +extern DECLSPEC int SDLCALL SDL_CondBroadcast(SDL_cond * cond); + +extern DECLSPEC int SDLCALL SDL_CondWait(SDL_cond * cond, SDL_mutex * mutex); + +extern DECLSPEC int SDLCALL SDL_CondWaitTimeout(SDL_cond * cond, + SDL_mutex * mutex, Uint32 ms); + +#ifdef __cplusplus +} +#endif + +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif + +#ifndef _SDL_thread_h +#define _SDL_thread_h + +#ifndef _SDL_atomic_h_ +#define _SDL_atomic_h_ + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int SDL_SpinLock; + +extern DECLSPEC SDL_bool SDLCALL SDL_AtomicTryLock(SDL_SpinLock *lock); + +extern DECLSPEC void SDLCALL SDL_AtomicLock(SDL_SpinLock *lock); + +extern DECLSPEC void SDLCALL SDL_AtomicUnlock(SDL_SpinLock *lock); + +#if defined(_MSC_VER) && (_MSC_VER > 1200) +void _ReadWriteBarrier(void); +#pragma intrinsic(_ReadWriteBarrier) +#define SDL_CompilerBarrier() _ReadWriteBarrier() +#elif defined(__GNUC__) +#define SDL_CompilerBarrier() __asm__ __volatile__ ("" : : : "memory") +#else +#define SDL_CompilerBarrier() \ +{ SDL_SpinLock _tmp = 0; SDL_AtomicLock(&_tmp); SDL_AtomicUnlock(&_tmp); } +#endif + +#if defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__)) +#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("lwsync" : : : "memory") +#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("lwsync" : : : "memory") +#elif defined(__GNUC__) && defined(__arm__) +#if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) +#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("dmb ish" : : : "memory") +#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("dmb ish" : : : "memory") +#elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) +#ifdef __thumb__ + +extern DECLSPEC void SDLCALL SDL_MemoryBarrierRelease(); +extern DECLSPEC void SDLCALL SDL_MemoryBarrierAcquire(); +#else +#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" : : "r"(0) : "memory") +#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" : : "r"(0) : "memory") +#endif +#else +#define SDL_MemoryBarrierRelease() __asm__ __volatile__ ("" : : : "memory") +#define SDL_MemoryBarrierAcquire() __asm__ __volatile__ ("" : : : "memory") +#endif +#else + +#define SDL_MemoryBarrierRelease() SDL_CompilerBarrier() +#define SDL_MemoryBarrierAcquire() SDL_CompilerBarrier() +#endif + +typedef struct { int value; } SDL_atomic_t; + +extern DECLSPEC SDL_bool SDLCALL SDL_AtomicCAS(SDL_atomic_t *a, int oldval, int newval); + +extern DECLSPEC int SDLCALL SDL_AtomicSet(SDL_atomic_t *a, int v); + +extern DECLSPEC int SDLCALL SDL_AtomicGet(SDL_atomic_t *a); + +extern DECLSPEC int SDLCALL SDL_AtomicAdd(SDL_atomic_t *a, int v); + +#ifndef SDL_AtomicIncRef +#define SDL_AtomicIncRef(a) SDL_AtomicAdd(a, 1) +#endif + +#ifndef SDL_AtomicDecRef +#define SDL_AtomicDecRef(a) (SDL_AtomicAdd(a, -1) == 1) +#endif + +extern DECLSPEC SDL_bool SDLCALL SDL_AtomicCASPtr(void **a, void *oldval, void *newval); + +extern DECLSPEC void* SDLCALL SDL_AtomicSetPtr(void **a, void* v); + +extern DECLSPEC void* SDLCALL SDL_AtomicGetPtr(void **a); + +#ifdef __cplusplus +} +#endif + +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct SDL_Thread; +typedef struct SDL_Thread SDL_Thread; + +typedef unsigned long SDL_threadID; + +typedef unsigned int SDL_TLSID; + +typedef enum { + SDL_THREAD_PRIORITY_LOW, + SDL_THREAD_PRIORITY_NORMAL, + SDL_THREAD_PRIORITY_HIGH +} SDL_ThreadPriority; + +typedef int (SDLCALL * SDL_ThreadFunction) (void *data); + +#if defined(__WIN32__) && !defined(HAVE_LIBC) + +#define SDL_PASSED_BEGINTHREAD_ENDTHREAD +#include + +typedef uintptr_t(__cdecl * pfnSDL_CurrentBeginThread) (void *, unsigned, + unsigned (__stdcall * + func) (void + *), + void *arg, unsigned, + unsigned *threadID); +typedef void (__cdecl * pfnSDL_CurrentEndThread) (unsigned code); + +extern DECLSPEC SDL_Thread *SDLCALL +SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data, + pfnSDL_CurrentBeginThread pfnBeginThread, + pfnSDL_CurrentEndThread pfnEndThread); + +#if defined(SDL_CreateThread) && SDL_DYNAMIC_API +#undef SDL_CreateThread +#define SDL_CreateThread(fn, name, data) SDL_CreateThread_REAL(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex) +#else +#define SDL_CreateThread(fn, name, data) SDL_CreateThread(fn, name, data, (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex) +#endif + +#else + +extern DECLSPEC SDL_Thread *SDLCALL +SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data); + +#endif + +extern DECLSPEC const char *SDLCALL SDL_GetThreadName(SDL_Thread *thread); + +extern DECLSPEC SDL_threadID SDLCALL SDL_ThreadID(void); + +extern DECLSPEC SDL_threadID SDLCALL SDL_GetThreadID(SDL_Thread * thread); + +extern DECLSPEC int SDLCALL SDL_SetThreadPriority(SDL_ThreadPriority priority); + +extern DECLSPEC void SDLCALL SDL_WaitThread(SDL_Thread * thread, int *status); + +extern DECLSPEC void SDLCALL SDL_DetachThread(SDL_Thread * thread); + +#ifdef __cplusplus +} +#endif + +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif + +#ifndef _SDL_rwops_h +#define _SDL_rwops_h + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define SDL_RWOPS_UNKNOWN 0 +#define SDL_RWOPS_WINFILE 1 +#define SDL_RWOPS_STDFILE 2 +#define SDL_RWOPS_JNIFILE 3 +#define SDL_RWOPS_MEMORY 4 +#define SDL_RWOPS_MEMORY_RO 5 + +typedef struct SDL_RWops +{ + + Sint64 (SDLCALL * size) (struct SDL_RWops * context); + + Sint64 (SDLCALL * seek) (struct SDL_RWops * context, Sint64 offset, + int whence); + + size_t (SDLCALL * read) (struct SDL_RWops * context, void *ptr, + size_t size, size_t maxnum); + + size_t (SDLCALL * write) (struct SDL_RWops * context, const void *ptr, + size_t size, size_t num); + + int (SDLCALL * close) (struct SDL_RWops * context); + + Uint32 type; + union + { +#if defined(ANDROID) + struct + { + void *fileNameRef; + void *inputStreamRef; + void *readableByteChannelRef; + void *readMethod; + void *assetFileDescriptorRef; + long position; + long size; + long offset; + int fd; + } androidio; +#elif defined(__WIN32__) + struct + { + SDL_bool append; + void *h; + struct + { + void *data; + size_t size; + size_t left; + } buffer; + } windowsio; +#endif + +#ifdef HAVE_STDIO_H + struct + { + SDL_bool autoclose; + FILE *fp; + } stdio; +#endif + struct + { + Uint8 *base; + Uint8 *here; + Uint8 *stop; + } mem; + struct + { + void *data1; + void *data2; + } unknown; + } hidden; + +} SDL_RWops; + +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromFile(const char *file, + const char *mode); + +#ifdef HAVE_STDIO_H +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromFP(FILE * fp, + SDL_bool autoclose); +#else +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromFP(void * fp, + SDL_bool autoclose); +#endif + +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromMem(void *mem, int size); +extern DECLSPEC SDL_RWops *SDLCALL SDL_RWFromConstMem(const void *mem, + int size); + +extern DECLSPEC SDL_RWops *SDLCALL SDL_AllocRW(void); +extern DECLSPEC void SDLCALL SDL_FreeRW(SDL_RWops * area); + +#define RW_SEEK_SET 0 +#define RW_SEEK_CUR 1 +#define RW_SEEK_END 2 + +#define SDL_RWsize(ctx) (ctx)->size(ctx) +#define SDL_RWseek(ctx, offset, whence) (ctx)->seek(ctx, offset, whence) +#define SDL_RWtell(ctx) (ctx)->seek(ctx, 0, RW_SEEK_CUR) +#define SDL_RWread(ctx, ptr, size, n) (ctx)->read(ctx, ptr, size, n) +#define SDL_RWwrite(ctx, ptr, size, n) (ctx)->write(ctx, ptr, size, n) +#define SDL_RWclose(ctx) (ctx)->close(ctx) + +extern DECLSPEC Uint8 SDLCALL SDL_ReadU8(SDL_RWops * src); +extern DECLSPEC Uint16 SDLCALL SDL_ReadLE16(SDL_RWops * src); +extern DECLSPEC Uint16 SDLCALL SDL_ReadBE16(SDL_RWops * src); +extern DECLSPEC Uint32 SDLCALL SDL_ReadLE32(SDL_RWops * src); +extern DECLSPEC Uint32 SDLCALL SDL_ReadBE32(SDL_RWops * src); +extern DECLSPEC Uint64 SDLCALL SDL_ReadLE64(SDL_RWops * src); +extern DECLSPEC Uint64 SDLCALL SDL_ReadBE64(SDL_RWops * src); + +extern DECLSPEC size_t SDLCALL SDL_WriteU8(SDL_RWops * dst, Uint8 value); +extern DECLSPEC size_t SDLCALL SDL_WriteLE16(SDL_RWops * dst, Uint16 value); +extern DECLSPEC size_t SDLCALL SDL_WriteBE16(SDL_RWops * dst, Uint16 value); +extern DECLSPEC size_t SDLCALL SDL_WriteLE32(SDL_RWops * dst, Uint32 value); +extern DECLSPEC size_t SDLCALL SDL_WriteBE32(SDL_RWops * dst, Uint32 value); +extern DECLSPEC size_t SDLCALL SDL_WriteLE64(SDL_RWops * dst, Uint64 value); +extern DECLSPEC size_t SDLCALL SDL_WriteBE64(SDL_RWops * dst, Uint64 value); + +#ifdef __cplusplus +} +#endif + +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif + +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +#ifndef SDL_DEPRECATED +# if (__GNUC__ >= 4) +# define SDL_DEPRECATED __attribute__((deprecated)) +# else +# define SDL_DEPRECATED +# endif +#endif + +#ifndef DECLSPEC +# if defined(__WIN32__) || defined(__WINRT__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# elif defined(__GNUC__) && __GNUC__ >= 2 +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# endif +#endif + +#ifndef SDLCALL +#if (defined(__WIN32__) || defined(__WINRT__)) && !defined(__GNUC__) +#define SDLCALL __cdecl +#else +#define SDLCALL +#endif +#endif + +#ifdef __SYMBIAN32__ +#undef DECLSPEC +#define DECLSPEC +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__BORLANDC__) +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#ifdef _M_X64 + +#pragma pack(push,8) +#else +#pragma pack(push,4) +#endif +#endif + +#ifndef SDL_INLINE +#if defined(__GNUC__) +#define SDL_INLINE __inline__ +#elif defined(_MSC_VER) || defined(__BORLANDC__) || \ + defined(__DMC__) || defined(__SC__) || \ + defined(__WATCOMC__) || defined(__LCC__) || \ + defined(__DECC) +#define SDL_INLINE __inline +#ifndef __inline__ +#define __inline__ __inline +#endif +#else +#define SDL_INLINE inline +#ifndef __inline__ +#define __inline__ inline +#endif +#endif +#endif + +#ifndef SDL_FORCE_INLINE +#if defined(_MSC_VER) +#define SDL_FORCE_INLINE __forceinline +#elif ( (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) ) +#define SDL_FORCE_INLINE __attribute__((always_inline)) static __inline__ +#else +#define SDL_FORCE_INLINE static SDL_INLINE +#endif +#endif + +#if !defined(__MACH__) +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef Uint16 SDL_AudioFormat; + +#define SDL_AUDIO_MASK_BITSIZE (0xFF) +#define SDL_AUDIO_MASK_DATATYPE (1<<8) +#define SDL_AUDIO_MASK_ENDIAN (1<<12) +#define SDL_AUDIO_MASK_SIGNED (1<<15) +#define SDL_AUDIO_BITSIZE(x) (x & SDL_AUDIO_MASK_BITSIZE) +#define SDL_AUDIO_ISFLOAT(x) (x & SDL_AUDIO_MASK_DATATYPE) +#define SDL_AUDIO_ISBIGENDIAN(x) (x & SDL_AUDIO_MASK_ENDIAN) +#define SDL_AUDIO_ISSIGNED(x) (x & SDL_AUDIO_MASK_SIGNED) +#define SDL_AUDIO_ISINT(x) (!SDL_AUDIO_ISFLOAT(x)) +#define SDL_AUDIO_ISLITTLEENDIAN(x) (!SDL_AUDIO_ISBIGENDIAN(x)) +#define SDL_AUDIO_ISUNSIGNED(x) (!SDL_AUDIO_ISSIGNED(x)) + +#define AUDIO_U8 0x0008 +#define AUDIO_S8 0x8008 +#define AUDIO_U16LSB 0x0010 +#define AUDIO_S16LSB 0x8010 +#define AUDIO_U16MSB 0x1010 +#define AUDIO_S16MSB 0x9010 +#define AUDIO_U16 AUDIO_U16LSB +#define AUDIO_S16 AUDIO_S16LSB + +#define AUDIO_S32LSB 0x8020 +#define AUDIO_S32MSB 0x9020 +#define AUDIO_S32 AUDIO_S32LSB + +#define AUDIO_F32LSB 0x8120 +#define AUDIO_F32MSB 0x9120 +#define AUDIO_F32 AUDIO_F32LSB + +#if SDL_BYTEORDER == SDL_LIL_ENDIAN +#define AUDIO_U16SYS AUDIO_U16LSB +#define AUDIO_S16SYS AUDIO_S16LSB +#define AUDIO_S32SYS AUDIO_S32LSB +#define AUDIO_F32SYS AUDIO_F32LSB +#else +#define AUDIO_U16SYS AUDIO_U16MSB +#define AUDIO_S16SYS AUDIO_S16MSB +#define AUDIO_S32SYS AUDIO_S32MSB +#define AUDIO_F32SYS AUDIO_F32MSB +#endif + +#define SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001 +#define SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002 +#define SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004 +#define SDL_AUDIO_ALLOW_ANY_CHANGE (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_FORMAT_CHANGE|SDL_AUDIO_ALLOW_CHANNELS_CHANGE) + +typedef void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 * stream, + int len); + +typedef struct SDL_AudioSpec +{ + int freq; + SDL_AudioFormat format; + Uint8 channels; + Uint8 silence; + Uint16 samples; + Uint16 padding; + Uint32 size; + SDL_AudioCallback callback; + void *userdata; +} SDL_AudioSpec; + +struct SDL_AudioCVT; +typedef void (SDLCALL * SDL_AudioFilter) (struct SDL_AudioCVT * cvt, + SDL_AudioFormat format); + +#ifdef __GNUC__ + +#define SDL_AUDIOCVT_PACKED __attribute__((packed)) +#else +#define SDL_AUDIOCVT_PACKED +#endif + +typedef struct SDL_AudioCVT +{ + int needed; + SDL_AudioFormat src_format; + SDL_AudioFormat dst_format; + double rate_incr; + Uint8 *buf; + int len; + int len_cvt; + int len_mult; + double len_ratio; + SDL_AudioFilter filters[10]; + int filter_index; +} SDL_AUDIOCVT_PACKED SDL_AudioCVT; + +extern DECLSPEC int SDLCALL SDL_GetNumAudioDrivers(void); +extern DECLSPEC const char *SDLCALL SDL_GetAudioDriver(int index); + +extern DECLSPEC int SDLCALL SDL_AudioInit(const char *driver_name); +extern DECLSPEC void SDLCALL SDL_AudioQuit(void); + +extern DECLSPEC const char *SDLCALL SDL_GetCurrentAudioDriver(void); + +extern DECLSPEC int SDLCALL SDL_OpenAudio(SDL_AudioSpec * desired, + SDL_AudioSpec * obtained); + +typedef Uint32 SDL_AudioDeviceID; + +extern DECLSPEC int SDLCALL SDL_GetNumAudioDevices(int iscapture); + +extern DECLSPEC const char *SDLCALL SDL_GetAudioDeviceName(int index, + int iscapture); + +extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(const char + *device, + int iscapture, + const + SDL_AudioSpec * + desired, + SDL_AudioSpec * + obtained, + int + allowed_changes); + +typedef enum +{ + SDL_AUDIO_STOPPED = 0, + SDL_AUDIO_PLAYING, + SDL_AUDIO_PAUSED +} SDL_AudioStatus; +extern DECLSPEC SDL_AudioStatus SDLCALL SDL_GetAudioStatus(void); + +extern DECLSPEC SDL_AudioStatus SDLCALL +SDL_GetAudioDeviceStatus(SDL_AudioDeviceID dev); + +extern DECLSPEC void SDLCALL SDL_PauseAudio(int pause_on); +extern DECLSPEC void SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID dev, + int pause_on); + +extern DECLSPEC SDL_AudioSpec *SDLCALL SDL_LoadWAV_RW(SDL_RWops * src, + int freesrc, + SDL_AudioSpec * spec, + Uint8 ** audio_buf, + Uint32 * audio_len); + +#define SDL_LoadWAV(file, spec, audio_buf, audio_len) \ + SDL_LoadWAV_RW(SDL_RWFromFile(file, "rb"),1, spec,audio_buf,audio_len) + +extern DECLSPEC void SDLCALL SDL_FreeWAV(Uint8 * audio_buf); + +extern DECLSPEC int SDLCALL SDL_BuildAudioCVT(SDL_AudioCVT * cvt, + SDL_AudioFormat src_format, + Uint8 src_channels, + int src_rate, + SDL_AudioFormat dst_format, + Uint8 dst_channels, + int dst_rate); + +extern DECLSPEC int SDLCALL SDL_ConvertAudio(SDL_AudioCVT * cvt); + +#define SDL_MIX_MAXVOLUME 128 + +extern DECLSPEC void SDLCALL SDL_MixAudio(Uint8 * dst, const Uint8 * src, + Uint32 len, int volume); + +extern DECLSPEC void SDLCALL SDL_MixAudioFormat(Uint8 * dst, + const Uint8 * src, + SDL_AudioFormat format, + Uint32 len, int volume); + +extern DECLSPEC void SDLCALL SDL_LockAudio(void); +extern DECLSPEC void SDLCALL SDL_LockAudioDevice(SDL_AudioDeviceID dev); +extern DECLSPEC void SDLCALL SDL_UnlockAudio(void); +extern DECLSPEC void SDLCALL SDL_UnlockAudioDevice(SDL_AudioDeviceID dev); + +extern DECLSPEC void SDLCALL SDL_CloseAudio(void); +extern DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID dev); + +#ifdef __cplusplus +} +#endif + +#undef _begin_code_h + +#if defined(_MSC_VER) || defined(__MWERKS__) || defined(__WATCOMC__) || defined(__BORLANDC__) +#ifdef __BORLANDC__ +#pragma nopackwarning +#endif +#pragma pack(pop) +#endif + +#endif diff --git a/source/engine/thirdparty/TinySoundFont/examples/venture.mid b/source/engine/thirdparty/TinySoundFont/examples/venture.mid new file mode 100644 index 0000000000000000000000000000000000000000..e63e2853383591d9a0cebca22a2a7f565e860e4c GIT binary patch literal 7637 zcmeHL&ubGw6#llYR)sxy)0-s_lv-+wq=b+(T?ipdnogjgQlz8yAb2SD682PLED^z~ z(270!AN1#;P(f=w_+R*Ec#Cgl+9tDI&A=4fU@o(H?|tv>dmnG!W@d8w=^TKIaB$iA zIXS)X3~&a}xZl^;bL|ou4|;kaR>DNA<=*~JH?ST6U+!YPe-2l&m;oLVc)-D4IE;i> zxA88;>*Fan9!H7SW<8(;C(BVJAqy1YWH_b>lL4lrr9=W*2$qp*-IdT~awT;6FwXL# zievR6_?G+ih%x4NL5Oo~83} z3M^fKQ(`Y3m(WvY6{@(%Dnyyjs`Heo5(=zBs64BWlZ(eHo)9Y1oeY7;(xma(Js&gD zAtCuI9OA^zh)CODS?l9XpsyN%Bh``(ZYNIh$F zNzd9`(z7;~^sLP#^NMt&c||(XydoWGUXg}u?&bbhZT7PGTZWYwZGafuVTJ_#8U_+@ zZFGYaWf1NdgWzurpkZL6i&K<1+))O>&nQ5f-9|S=QHJ0q8N}940<`=8t`l_-j_PRQ znmRf6x-|ZQ#`>g9FGcy<^iq_sO)o|HHhbiB`YVb98(kC!Ho7PdY;;}z&ewdi>9r_d zn_i3Zwdu7e-&5|t=J&YwWt;Eu4*j?OejPjE|JEPxtjZ&NaCFwDgXHU5@?1^_Z*i)W zhPQ$I6d9sd3mtr5>FCo|H+gY!bv8FoN0)Wj7kd$r_n0mEUD}a9fZgzpY-HEsbmJ9A zed27M&8gxnSXJ^1YcaMo6@x>wdOn8QBlWC4*VOe1P5h5hEzEni%&UvisI@j*#D|F} zi@1U;(*11b+nY>Yn6YItF;bmRz)6f$TGoHBO3AmMTq&!do1!TLC;3yFE{m$W&HB2_ N;%SV6&Sp`J*xxMqzghqQ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/TinySoundFont/sfotool/.gitignore b/source/engine/thirdparty/TinySoundFont/sfotool/.gitignore new file mode 100644 index 0000000..f2af1ae --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/sfotool/.gitignore @@ -0,0 +1,15 @@ +*.ncb +*.opt +*.plg +*.aps +*.ipch +*.suo +*.user +*.sdf +*.opensdf +*.dsw +*-i686 +*-x86_64 +Debug +Release +.vs diff --git a/source/engine/thirdparty/TinySoundFont/sfotool/Makefile b/source/engine/thirdparty/TinySoundFont/sfotool/Makefile new file mode 100644 index 0000000..aef5ce0 --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/sfotool/Makefile @@ -0,0 +1,2 @@ +all: + gcc main.c -o sfotool diff --git a/source/engine/thirdparty/TinySoundFont/sfotool/README.md b/source/engine/thirdparty/TinySoundFont/sfotool/README.md new file mode 100644 index 0000000..89c757b --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/sfotool/README.md @@ -0,0 +1,29 @@ +# SFOTool for TinySoundFont +A tool to create heavily compressed .SFO files from .SF2 SoundFont files. + +## Purpose +SFO files are just regular SoundFont v2 files with the entire block of raw PCM samples replaced +with a single Ogg Vorbis compressed stream. Unlike .sf3 files, which can have every separate font +sample compressed individually, this will compress the entire sound data as if it were a single +sample. This results in much higher compression than processing samples individually but also +higher loss of quality. + +## Usage Help +```sh +sfotool : Show type of sample stream contained (PCM or OGG) +sfotool : Dump PCM sample stream to .WAV file +sfotool : Dump OGG sample stream to .OGG file +sfotool : Write new .SF2 soundfont file using PCM sample stream from .WAV file +sfotool : Write new .SFO soundfont file using OGG sample stream from .OGG file +``` + +## Making a .SFO file from a .SF2 file +1. Dump the PCM data of a .SF2 file to a .WAV file + `sfotool ` +2. Compress the .WAV file to .OGG (i.e. with [Audacity](https://www.audacityteam.org/download/legacy-windows/)) + Make sure to choose the desired compression quality level +3. Build the .SFO file from the .SF2 file and the new .OGG + `sfotool ` + +# License +SFOTool is available under the [Unlicense](http://unlicense.org/) (public domain). diff --git a/source/engine/thirdparty/TinySoundFont/sfotool/main.c b/source/engine/thirdparty/TinySoundFont/sfotool/main.c new file mode 100644 index 0000000..65a6652 --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/sfotool/main.c @@ -0,0 +1,200 @@ +//--------------------------------------------// +// SFOTool // +// License: Public Domain (www.unlicense.org) // +//--------------------------------------------// + +#include +#include + +typedef char sfo_fourcc[4]; +#define SFO_FourCCEquals(a, b) (a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]) +struct sfo_riffchunk { sfo_fourcc id; unsigned int size; }; +struct sfo_wavheader +{ + char RIFF[4]; unsigned int ChunkSize; char WAVE[4], fmt[4]; unsigned int Subchunk1Size; + unsigned short AudioFormat,NumOfChan; unsigned int SamplesPerSec, bytesPerSec; + unsigned short blockAlign, bitsPerSample; char Subchunk2ID[4]; unsigned int Subchunk2Size; +}; + +static void sfo_copy(FILE* src, FILE* trg, unsigned int size) +{ + unsigned int block; + unsigned char buf[512]; + for (; size; size -= block) + { + block = (size > sizeof(buf) ? sizeof(buf) : size); + fread(buf, 1, block, src); + fwrite(buf, 1, block, trg); + } +} + +static int sfo_riffchunk_read(struct sfo_riffchunk* parent, struct sfo_riffchunk* chunk, FILE* f) +{ + int is_riff, is_list; + if (parent && sizeof(sfo_fourcc) + sizeof(unsigned int) > parent->size) return 0; + if (!fread(&chunk->id, sizeof(sfo_fourcc), 1, f) || *chunk->id <= ' ' || *chunk->id >= 'z') return 0; + if (!fread(&chunk->size, sizeof(unsigned int), 1, f)) return 0; + if (parent && sizeof(sfo_fourcc) + sizeof(unsigned int) + chunk->size > parent->size) return 0; + if (parent) parent->size -= sizeof(sfo_fourcc) + sizeof(unsigned int) + chunk->size; + is_riff = SFO_FourCCEquals(chunk->id, "RIFF"), is_list = SFO_FourCCEquals(chunk->id, "LIST"); + if (is_riff && parent) return 0; /* not allowed */ + if (!is_riff && !is_list) return 1; /* custom type without sub type */ + if (!fread(&chunk->id, sizeof(sfo_fourcc), 1, f) || *chunk->id <= ' ' || *chunk->id >= 'z') return 0; + chunk->size -= sizeof(sfo_fourcc); + return 1; +} + +int main(int argc, const char** argv) +{ + const char* arg_sf_in = (argc > 1 ? argv[1] : NULL); + const char* arg_smpl = (argc > 2 ? argv[2] : NULL); + const char* arg_sf_out = (argc > 3 ? argv[3] : NULL); + char ext_sf_in = (arg_sf_in ? arg_sf_in [strlen(arg_sf_in)-1] | 0x20 : '\0'); + char ext_smpl = (arg_smpl ? arg_smpl [strlen(arg_smpl)-1] | 0x20 : '\0'); + char ext_sf_out = (arg_sf_out ? arg_sf_out[strlen(arg_sf_out)-1] | 0x20 : '\0'); + struct sfo_riffchunk chunkHead, chunkList, chunk; + FILE* f_sf_in = NULL, *f_smpl = NULL, *f_sf_out = NULL; + + if (argc < 2 || argc > 4) + { + print_usage: + fprintf(stderr, "Usage Help:\n"); + fprintf(stderr, "%s : Show type of sample stream contained (PCM or OGG)\n", argv[0]); + fprintf(stderr, "%s : Dump PCM sample stream to .WAV file\n", argv[0]); + fprintf(stderr, "%s : Dump OGG sample stream to .OGG file\n", argv[0]); + fprintf(stderr, "%s : Write new .SF2 soundfont file using PCM sample stream from .WAV file\n", argv[0]); + fprintf(stderr, "%s : Write new .SFO soundfont file using OGG sample stream from .OGG file\n", argv[0]); + if (f_sf_in) fclose(f_sf_in); + if (f_smpl) fclose(f_smpl); + if (f_sf_out) fclose(f_sf_out); + return 1; + } + + f_sf_in = fopen(arg_sf_in, "rb"); + if (!f_sf_in) { fprintf(stderr, "Error: Passed input file '%s' does not exist\n\n", arg_sf_in); goto print_usage; } + + if (!sfo_riffchunk_read(NULL, &chunkHead, f_sf_in) || !SFO_FourCCEquals(chunkHead.id, "sfbk")) + { + fprintf(stderr, "Error: Passed input file '%s' is not a valid soundfont file\n\n", arg_sf_in); + goto print_usage; + } + while (sfo_riffchunk_read(&chunkHead, &chunkList, f_sf_in)) + { + unsigned int pos_listsize = (unsigned int)ftell(f_sf_in) - 8; + if (!SFO_FourCCEquals(chunkList.id, "sdta")) + { + fseek(f_sf_in, chunkList.size, SEEK_CUR); + continue; + } + for (; sfo_riffchunk_read(&chunkList, &chunk, f_sf_in); fseek(f_sf_in, chunkList.size, SEEK_CUR)) + { + int is_pcm = SFO_FourCCEquals(chunk.id, "smpl"); + if (!is_pcm && !SFO_FourCCEquals(chunk.id, "smpo")) + continue; + + printf("Soundfont file '%s' contains a %s sample stream\n", arg_sf_in, (is_pcm ? "PCM" : "OGG")); + if (ext_sf_in != '2' && ext_sf_in != 'o') printf(" Warning: Soundfont file has unknown file extension (should be .SF2 or .SFO)\n"); + if (ext_sf_in == '2' && !is_pcm) printf(" Warning: Soundfont file has .SF%c extension but sample stream is %s\n", '2', "OGG (should be .SFO)"); + if (ext_sf_in == 'o' && is_pcm) printf(" Warning: Soundfont file has .SF%c extension but sample stream is %s\n", 'O', "PCM (should be .SF2)"); + if (arg_sf_out) + { + unsigned int pos_smpchunk, end_smpchunk, len_smpl, end_sf, len_list_in, len_list_out; + + printf("Writing file '%s' with samples from '%s'\n", arg_sf_out, arg_smpl); + if (ext_sf_out != '2' && ext_sf_out != 'o') printf(" Warning: Soundfont file has unknown file extension (should be .SF2 or .SFO)\n"); + if (ext_smpl != 'v' && ext_smpl != 'g') printf(" Warning: Sample file has unknown file extension (should be .WAV or .OGG)\n"); + if (ext_sf_out == '2' && ext_smpl != 'v') printf(" Warning: Soundfont file has .SF%c extension but sample file is .%s\n", '2', "OGG"); + if (ext_sf_out == 'o' && ext_smpl == 'v') printf(" Warning: Soundfont file has .SF%c extension but sample file is .%s\n", 'O', "WAV"); + + f_smpl = fopen(arg_smpl, "rb"); + if (!f_smpl) { fprintf(stderr, "Error: Unable to open input file '%s'\n\n", arg_smpl); goto print_usage; } + + if (ext_smpl == 'v') + { + struct sfo_wavheader wav_hdr; + fread(&wav_hdr, sizeof(wav_hdr), 1, f_smpl); + if (!SFO_FourCCEquals(wav_hdr.Subchunk2ID, "data") || !SFO_FourCCEquals(wav_hdr.RIFF, "RIFF") + || !SFO_FourCCEquals(wav_hdr.WAVE, "WAVE") || !SFO_FourCCEquals(wav_hdr.fmt, "fmt ") + || wav_hdr.Subchunk1Size != 16 || wav_hdr.AudioFormat != 1 || wav_hdr.NumOfChan != 1 + || wav_hdr.bytesPerSec != wav_hdr.SamplesPerSec * sizeof(short) || wav_hdr.bitsPerSample != sizeof(short) * 8) + { fprintf(stderr, "Input .WAV file is not a valid raw PCM encoded wave file\n\n"); goto print_usage; } + + len_smpl = wav_hdr.Subchunk2Size; + } + else + { + fseek(f_smpl, 0, SEEK_END); + len_smpl = (unsigned int)ftell(f_smpl); + fseek(f_smpl, 0, SEEK_SET); + } + + f_sf_out = fopen(arg_sf_out, "wb"); + if (!f_sf_out) { fprintf(stderr, "Error: Unable to open output file '%s'\n\n", arg_sf_out); goto print_usage; } + + pos_smpchunk = (unsigned int)(ftell(f_sf_in) - sizeof(struct sfo_riffchunk)); + end_smpchunk = pos_smpchunk + (unsigned int)sizeof(struct sfo_riffchunk) + chunk.size; + fseek(f_sf_in, 0, SEEK_END); + end_sf = (unsigned int)ftell(f_sf_in); + + /* Write data before list chunk size */ + fseek(f_sf_in, 0, SEEK_SET); + sfo_copy(f_sf_in, f_sf_out, pos_listsize); + + /* Write new list chunk size */ + fread(&len_list_in, 4, 1, f_sf_in); + len_list_out = len_list_in - chunk.size + len_smpl; + fwrite(&len_list_out, 4, 1, f_sf_out); + + /* Write data until sample chunk */ + sfo_copy(f_sf_in, f_sf_out, pos_smpchunk - pos_listsize - 4); + + /* Write sample chunk */ + fwrite((ext_smpl == 'v' ? "smpl" : "smpo"), 4, 1, f_sf_out); + fwrite(&len_smpl, 4, 1, f_sf_out); + sfo_copy(f_smpl, f_sf_out, len_smpl); + fclose(f_smpl); + + /* Write data after sample chunk */ + fseek(f_sf_in, end_smpchunk, SEEK_SET); + sfo_copy(f_sf_in, f_sf_out, end_sf - end_smpchunk); + fclose(f_sf_out); + } + else if (arg_smpl) + { + f_smpl = fopen(arg_smpl, "wb"); + printf("Writing file '%s' with %s sample stream\n", arg_smpl, (is_pcm ? "PCM" : "OGG")); + if (ext_smpl != 'v' && ext_smpl != 'g') printf(" Warning: Sample file has unknown file extension (should be .WAV or .OGG)\n"); + if (ext_smpl == 'v' && !is_pcm) printf(" Warning: Sample file has .%s extension but sample stream is %s\n", "WAV", "OGG (should be .OGG)"); + if (ext_smpl == 'g' && is_pcm) printf(" Warning: Sample file has .%s extension but sample stream is %s\n", "OGG", "PCM (should be .WAV)"); + if (!f_smpl) { fprintf(stderr, "Unable to open output file '%s'\n\n", arg_smpl); goto print_usage; } + + if (is_pcm) + { + struct sfo_wavheader wav_hdr; + memcpy(wav_hdr.Subchunk2ID, "data", 4); + memcpy(wav_hdr.RIFF, "RIFF", 4); + memcpy(wav_hdr.WAVE, "WAVE", 4); + memcpy(wav_hdr.fmt, "fmt ", 4); + wav_hdr.Subchunk1Size = 16; + wav_hdr.AudioFormat = 1; + wav_hdr.NumOfChan = 1; + wav_hdr.Subchunk2Size = (unsigned int)chunk.size; + wav_hdr.ChunkSize = sizeof(wav_hdr) - 4 - 4 + wav_hdr.Subchunk2Size; + wav_hdr.SamplesPerSec = 22050; + wav_hdr.bytesPerSec = 22050 * sizeof(short); + wav_hdr.blockAlign = 1 * sizeof(short); + wav_hdr.bitsPerSample = sizeof(short) * 8; + fwrite(&wav_hdr, sizeof(wav_hdr), 1, f_smpl); + } + sfo_copy(f_sf_in, f_smpl, chunk.size); + fclose(f_smpl); + printf("DONE\n"); + } + fclose(f_sf_in); + return 0; + } + } + + fprintf(stderr, "Passed input file is not a valid soundfont file\n\n"); + goto print_usage; +} diff --git a/source/engine/thirdparty/TinySoundFont/sfotool/sfotool.sln b/source/engine/thirdparty/TinySoundFont/sfotool/sfotool.sln new file mode 100644 index 0000000..dbcf421 --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/sfotool/sfotool.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sfotool", "sfotool.vcxproj", "{FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Debug|Win32.ActiveCfg = Debug|Win32 + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Debug|Win32.Build.0 = Debug|Win32 + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Debug|x64.ActiveCfg = Debug|x64 + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Debug|x64.Build.0 = Debug|x64 + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Release|Win32.ActiveCfg = Release|Win32 + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Release|Win32.Build.0 = Release|Win32 + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Release|x64.ActiveCfg = Release|x64 + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/source/engine/thirdparty/TinySoundFont/sfotool/sfotool.vcxproj b/source/engine/thirdparty/TinySoundFont/sfotool/sfotool.vcxproj new file mode 100644 index 0000000..d2f39bc --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/sfotool/sfotool.vcxproj @@ -0,0 +1,100 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {FFFFFFFF-FFFF-4FFF-FFFF-FFFFFFFFFFFF} + Win32Proj + sfotool + sfotool + + + + Application + v110_xp + v120 + v140 + v141 + v142 + v143 + $(DefaultPlatformToolset) + false + MultiByte + $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ + $(SolutionDir)$(Configuration)\$(ProjectName)_$(Platform)\ + + + true + + + false + true + + + + + + + true + + + false + + + + Level3 + true + false + false + false + + + Console + true + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDebug + + + true + + + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreaded + /Gw %(AdditionalOptions) + + + true + true + false + + + + + + + \ No newline at end of file diff --git a/source/engine/thirdparty/TinySoundFont/tml.h b/source/engine/thirdparty/TinySoundFont/tml.h new file mode 100644 index 0000000..3332873 --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/tml.h @@ -0,0 +1,531 @@ +/* TinyMidiLoader - v0.7 - Minimalistic midi parsing library - https://github.com/schellingb/TinySoundFont + no warranty implied; use at your own risk + Do this: + #define TML_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + // i.e. it should look like this: + #include ... + #include ... + #define TML_IMPLEMENTATION + #include "tml.h" + + [OPTIONAL] #define TML_NO_STDIO to remove stdio dependency + [OPTIONAL] #define TML_MALLOC, TML_REALLOC, and TML_FREE to avoid stdlib.h + [OPTIONAL] #define TML_MEMCPY to avoid string.h + + LICENSE (ZLIB) + + Copyright (C) 2017, 2018, 2020 Bernhard Schelling + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ + +#ifndef TML_INCLUDE_TML_INL +#define TML_INCLUDE_TML_INL + +#ifdef __cplusplus +extern "C" { +#endif + +// Define this if you want the API functions to be static +#ifdef TML_STATIC +#define TMLDEF static +#else +#define TMLDEF extern +#endif + +// Channel message type +enum TMLMessageType +{ + TML_NOTE_OFF = 0x80, TML_NOTE_ON = 0x90, TML_KEY_PRESSURE = 0xA0, TML_CONTROL_CHANGE = 0xB0, TML_PROGRAM_CHANGE = 0xC0, TML_CHANNEL_PRESSURE = 0xD0, TML_PITCH_BEND = 0xE0, TML_SET_TEMPO = 0x51 +}; + +// Midi controller numbers +enum TMLController +{ + TML_BANK_SELECT_MSB, TML_MODULATIONWHEEL_MSB, TML_BREATH_MSB, TML_FOOT_MSB = 4, TML_PORTAMENTO_TIME_MSB, TML_DATA_ENTRY_MSB, TML_VOLUME_MSB, + TML_BALANCE_MSB, TML_PAN_MSB = 10, TML_EXPRESSION_MSB, TML_EFFECTS1_MSB, TML_EFFECTS2_MSB, TML_GPC1_MSB = 16, TML_GPC2_MSB, TML_GPC3_MSB, TML_GPC4_MSB, + TML_BANK_SELECT_LSB = 32, TML_MODULATIONWHEEL_LSB, TML_BREATH_LSB, TML_FOOT_LSB = 36, TML_PORTAMENTO_TIME_LSB, TML_DATA_ENTRY_LSB, TML_VOLUME_LSB, + TML_BALANCE_LSB, TML_PAN_LSB = 42, TML_EXPRESSION_LSB, TML_EFFECTS1_LSB, TML_EFFECTS2_LSB, TML_GPC1_LSB = 48, TML_GPC2_LSB, TML_GPC3_LSB, TML_GPC4_LSB, + TML_SUSTAIN_SWITCH = 64, TML_PORTAMENTO_SWITCH, TML_SOSTENUTO_SWITCH, TML_SOFT_PEDAL_SWITCH, TML_LEGATO_SWITCH, TML_HOLD2_SWITCH, + TML_SOUND_CTRL1, TML_SOUND_CTRL2, TML_SOUND_CTRL3, TML_SOUND_CTRL4, TML_SOUND_CTRL5, TML_SOUND_CTRL6, + TML_SOUND_CTRL7, TML_SOUND_CTRL8, TML_SOUND_CTRL9, TML_SOUND_CTRL10, TML_GPC5, TML_GPC6, TML_GPC7, TML_GPC8, + TML_PORTAMENTO_CTRL, TML_FX_REVERB = 91, TML_FX_TREMOLO, TML_FX_CHORUS, TML_FX_CELESTE_DETUNE, TML_FX_PHASER, + TML_DATA_ENTRY_INCR, TML_DATA_ENTRY_DECR, TML_NRPN_LSB, TML_NRPN_MSB, TML_RPN_LSB, TML_RPN_MSB, + TML_ALL_SOUND_OFF = 120, TML_ALL_CTRL_OFF, TML_LOCAL_CONTROL, TML_ALL_NOTES_OFF, TML_OMNI_OFF, TML_OMNI_ON, TML_POLY_OFF, TML_POLY_ON +}; + +// A single MIDI message linked to the next message in time +typedef struct tml_message +{ + // Time of the message in milliseconds + unsigned int time; + + // Type (see TMLMessageType) and channel number + unsigned char type, channel; + + // 2 byte of parameter data based on the type: + // - key, velocity for TML_NOTE_ON and TML_NOTE_OFF messages + // - key, key_pressure for TML_KEY_PRESSURE messages + // - control, control_value for TML_CONTROL_CHANGE messages (see TMLController) + // - program for TML_PROGRAM_CHANGE messages + // - channel_pressure for TML_CHANNEL_PRESSURE messages + // - pitch_bend for TML_PITCH_BEND messages + union + { + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4201) //nonstandard extension used: nameless struct/union + #elif defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpedantic" //ISO C++ prohibits anonymous structs + #endif + + struct { union { char key, control, program, channel_pressure; }; union { char velocity, key_pressure, control_value; }; }; + struct { unsigned short pitch_bend; }; + + #ifdef _MSC_VER + #pragma warning( pop ) + #elif defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + }; + + // The pointer to the next message in time following this event + struct tml_message* next; +} tml_message; + +// The load functions will return a pointer to a struct tml_message. +// Normally the linked list gets traversed by following the next pointers. +// Make sure to keep the pointer to the first message to free the memory. +// On error the tml_load* functions will return NULL most likely due to an +// invalid MIDI stream (or if the file did not exist in tml_load_filename). + +#ifndef TML_NO_STDIO +// Directly load a MIDI file from a .mid file path +TMLDEF tml_message* tml_load_filename(const char* filename); +#endif + +// Load a MIDI file from a block of memory +TMLDEF tml_message* tml_load_memory(const void* buffer, int size); + +// Get infos about this loaded MIDI file, returns the note count +// NULL can be passed for any output value pointer if not needed. +// used_channels: Will be set to how many channels play notes +// (i.e. 1 if channel 15 is used but no other) +// used_programs: Will be set to how many different programs are used +// total_notes: Will be set to the total number of note on messages +// time_first_note: Will be set to the time of the first note on message +// time_length: Will be set to the total time in milliseconds +TMLDEF int tml_get_info(tml_message* first_message, int* used_channels, int* used_programs, int* total_notes, unsigned int* time_first_note, unsigned int* time_length); + +// Read the tempo (microseconds per quarter note) value from a message with the type TML_SET_TEMPO +TMLDEF int tml_get_tempo_value(tml_message* set_tempo_message); + +// Free all the memory of the linked message list (can also call free() manually) +TMLDEF void tml_free(tml_message* f); + +// Stream structure for the generic loading +struct tml_stream +{ + // Custom data given to the functions as the first parameter + void* data; + + // Function pointer will be called to read 'size' bytes into ptr (returns number of read bytes) + int (*read)(void* data, void* ptr, unsigned int size); +}; + +// Generic Midi loading method using the stream structure above +TMLDEF tml_message* tml_load(struct tml_stream* stream); + +// If this library is used together with TinySoundFont, tsf_stream (equivalent to tml_stream) can also be used +struct tsf_stream; +TMLDEF tml_message* tml_load_tsf_stream(struct tsf_stream* stream); + +#ifdef __cplusplus +} +#endif + +// end header +// --------------------------------------------------------------------------------------------------------- +#endif //TML_INCLUDE_TML_INL + +#ifdef TML_IMPLEMENTATION + +#if !defined(TML_MALLOC) || !defined(TML_FREE) || !defined(TML_REALLOC) +# include +# define TML_MALLOC malloc +# define TML_FREE free +# define TML_REALLOC realloc +#endif + +#if !defined(TML_MEMCPY) +# include +# define TML_MEMCPY memcpy +#endif + +#ifndef TML_NO_STDIO +# include +#endif + +#define TML_NULL 0 + +////crash on errors and warnings to find broken midi files while debugging +//#define TML_ERROR(msg) *(int*)0 = 0xbad; +//#define TML_WARN(msg) *(int*)0 = 0xf00d; + +////print errors and warnings +//#define TML_ERROR(msg) printf("ERROR: %s\n", msg); +//#define TML_WARN(msg) printf("WARNING: %s\n", msg); + +#ifndef TML_ERROR +#define TML_ERROR(msg) +#endif + +#ifndef TML_WARN +#define TML_WARN(msg) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef TML_NO_STDIO +static int tml_stream_stdio_read(FILE* f, void* ptr, unsigned int size) { return (int)fread(ptr, 1, size, f); } +TMLDEF tml_message* tml_load_filename(const char* filename) +{ + struct tml_message* res; + struct tml_stream stream = { TML_NULL, (int(*)(void*,void*,unsigned int))&tml_stream_stdio_read }; + #if __STDC_WANT_SECURE_LIB__ + FILE* f = TML_NULL; fopen_s(&f, filename, "rb"); + #else + FILE* f = fopen(filename, "rb"); + #endif + if (!f) { TML_ERROR("File not found"); return 0; } + stream.data = f; + res = tml_load(&stream); + fclose(f); + return res; +} +#endif + +struct tml_stream_memory { const char* buffer; unsigned int total, pos; }; +static int tml_stream_memory_read(struct tml_stream_memory* m, void* ptr, unsigned int size) { if (size > m->total - m->pos) size = m->total - m->pos; TML_MEMCPY(ptr, m->buffer+m->pos, size); m->pos += size; return size; } +TMLDEF struct tml_message* tml_load_memory(const void* buffer, int size) +{ + struct tml_stream stream = { TML_NULL, (int(*)(void*,void*,unsigned int))&tml_stream_memory_read }; + struct tml_stream_memory f = { 0, 0, 0 }; + f.buffer = (const char*)buffer; + f.total = size; + stream.data = &f; + return tml_load(&stream); +} + +struct tml_track +{ + unsigned int Idx, End, Ticks; +}; + +struct tml_tempomsg +{ + unsigned int time; + unsigned char type, Tempo[3]; + tml_message* next; +}; + +struct tml_parser +{ + unsigned char *buf, *buf_end; + int last_status, message_array_size, message_count; +}; + +enum TMLSystemType +{ + TML_TEXT = 0x01, TML_COPYRIGHT = 0x02, TML_TRACK_NAME = 0x03, TML_INST_NAME = 0x04, TML_LYRIC = 0x05, TML_MARKER = 0x06, TML_CUE_POINT = 0x07, + TML_EOT = 0x2f, TML_SMPTE_OFFSET = 0x54, TML_TIME_SIGNATURE = 0x58, TML_KEY_SIGNATURE = 0x59, TML_SEQUENCER_EVENT = 0x7f, + TML_SYSEX = 0xf0, TML_TIME_CODE = 0xf1, TML_SONG_POSITION = 0xf2, TML_SONG_SELECT = 0xf3, TML_TUNE_REQUEST = 0xf6, TML_EOX = 0xf7, TML_SYNC = 0xf8, + TML_TICK = 0xf9, TML_START = 0xfa, TML_CONTINUE = 0xfb, TML_STOP = 0xfc, TML_ACTIVE_SENSING = 0xfe, TML_SYSTEM_RESET = 0xff +}; + +static int tml_readbyte(struct tml_parser* p) +{ + return (p->buf == p->buf_end ? -1 : *(p->buf++)); +} + +static int tml_readvariablelength(struct tml_parser* p) +{ + unsigned int res = 0, i = 0; + unsigned char c; + for (; i != 4; i++) + { + if (p->buf == p->buf_end) { TML_WARN("Unexpected end of file"); return -1; } + c = *(p->buf++); + if (c & 0x80) res = ((res | (c & 0x7F)) << 7); + else return (int)(res | c); + } + TML_WARN("Invalid variable length byte count"); return -1; +} + +static int tml_parsemessage(tml_message** f, struct tml_parser* p) +{ + int deltatime = tml_readvariablelength(p), status = tml_readbyte(p); + tml_message* evt; + + if (deltatime & 0xFFF00000) deltatime = 0; //throw away delays that are insanely high for malformatted midis + if (status < 0) { TML_WARN("Unexpected end of file"); return -1; } + if ((status & 0x80) == 0) + { + // Invalid, use same status as before + if ((p->last_status & 0x80) == 0) { TML_WARN("Undefined status and invalid running status"); return -1; } + p->buf--; + status = p->last_status; + } + else p->last_status = status; + + if (p->message_array_size == p->message_count) + { + //start allocated memory size of message array at 64, double each time until 8192, then add 1024 entries until done + p->message_array_size += (!p->message_array_size ? 64 : (p->message_array_size > 4096 ? 1024 : p->message_array_size)); + *f = (tml_message*)TML_REALLOC(*f, p->message_array_size * sizeof(tml_message)); + if (!*f) { TML_ERROR("Out of memory"); return -1; } + } + evt = *f + p->message_count; + + //check what message we have + if ((status == TML_SYSEX) || (status == TML_EOX)) //sysex + { + //sysex messages are not handled + p->buf += tml_readvariablelength(p); + if (p->buf > p->buf_end) { TML_WARN("Unexpected end of file"); p->buf = p->buf_end; return -1; } + evt->type = 0; + } + else if (status == 0xFF) //meta events + { + int meta_type = tml_readbyte(p), buflen = tml_readvariablelength(p); + unsigned char* metadata = p->buf; + if (meta_type < 0) { TML_WARN("Unexpected end of file"); return -1; } + if (buflen > 0 && (p->buf += buflen) > p->buf_end) { TML_WARN("Unexpected end of file"); p->buf = p->buf_end; return -1; } + + switch (meta_type) + { + case TML_EOT: + if (buflen != 0) { TML_WARN("Invalid length for EndOfTrack event"); return -1; } + if (!deltatime) return TML_EOT; //no need to store this message + evt->type = TML_EOT; + break; + + case TML_SET_TEMPO: + if (buflen != 3) { TML_WARN("Invalid length for SetTempo meta event"); return -1; } + evt->type = TML_SET_TEMPO; + ((struct tml_tempomsg*)evt)->Tempo[0] = metadata[0]; + ((struct tml_tempomsg*)evt)->Tempo[1] = metadata[1]; + ((struct tml_tempomsg*)evt)->Tempo[2] = metadata[2]; + break; + + default: + evt->type = 0; + } + } + else //channel message + { + int param; + if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; } + evt->key = (param & 0x7f); + evt->channel = (status & 0x0f); + switch (evt->type = (status & 0xf0)) + { + case TML_NOTE_OFF: + case TML_NOTE_ON: + case TML_KEY_PRESSURE: + case TML_CONTROL_CHANGE: + if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; } + evt->velocity = (param & 0x7f); + break; + + case TML_PITCH_BEND: + if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; } + evt->pitch_bend = ((param & 0x7f) << 7) | evt->key; + break; + + case TML_PROGRAM_CHANGE: + case TML_CHANNEL_PRESSURE: + evt->velocity = 0; + break; + + default: //ignore system/manufacture messages + evt->type = 0; + break; + } + } + + if (deltatime || evt->type) + { + evt->time = deltatime; + p->message_count++; + } + return evt->type; +} + +TMLDEF tml_message* tml_load(struct tml_stream* stream) +{ + int num_tracks, division, trackbufsize = 0; + unsigned char midi_header[14], *trackbuf = TML_NULL; + struct tml_message* messages = TML_NULL; + struct tml_track *tracks, *t, *tracksEnd; + struct tml_parser p = { TML_NULL, TML_NULL, 0, 0, 0 }; + + // Parse MIDI header + if (stream->read(stream->data, midi_header, 14) != 14) { TML_ERROR("Unexpected end of file"); return messages; } + if (midi_header[0] != 'M' || midi_header[1] != 'T' || midi_header[2] != 'h' || midi_header[3] != 'd' || + midi_header[7] != 6 || midi_header[9] > 2) { TML_ERROR("Doesn't look like a MIDI file: invalid MThd header"); return messages; } + if (midi_header[12] & 0x80) { TML_ERROR("File uses unsupported SMPTE timing"); return messages; } + num_tracks = (int)(midi_header[10] << 8) | midi_header[11]; + division = (int)(midi_header[12] << 8) | midi_header[13]; //division is ticks per beat (quarter-note) + if (num_tracks <= 0 && division <= 0) { TML_ERROR("Doesn't look like a MIDI file: invalid track or division values"); return messages; } + + // Allocate temporary tracks array for parsing + tracks = (struct tml_track*)TML_MALLOC(sizeof(struct tml_track) * num_tracks); + tracksEnd = &tracks[num_tracks]; + for (t = tracks; t != tracksEnd; t++) t->Idx = t->End = t->Ticks = 0; + + // Read all messages for all tracks + for (t = tracks; t != tracksEnd; t++) + { + unsigned char track_header[8]; + int track_length; + if (stream->read(stream->data, track_header, 8) != 8) { TML_WARN("Unexpected end of file"); break; } + if (track_header[0] != 'M' || track_header[1] != 'T' || track_header[2] != 'r' || track_header[3] != 'k') + { TML_WARN("Invalid MTrk header"); break; } + + // Get size of track data and read into buffer (allocate bigger buffer if needed) + track_length = track_header[7] | (track_header[6] << 8) | (track_header[5] << 16) | (track_header[4] << 24); + if (track_length < 0) { TML_WARN("Invalid MTrk header"); break; } + if (trackbufsize < track_length) { TML_FREE(trackbuf); trackbuf = (unsigned char*)TML_MALLOC(trackbufsize = track_length); } + if (stream->read(stream->data, trackbuf, track_length) != track_length) { TML_WARN("Unexpected end of file"); break; } + + t->Idx = p.message_count; + for (p.buf_end = (p.buf = trackbuf) + track_length; p.buf != p.buf_end;) + { + int type = tml_parsemessage(&messages, &p); + if (type == TML_EOT || type < 0) break; //file end or illegal data encountered + } + if (p.buf != p.buf_end) { TML_WARN( "Track length did not match data length"); } + t->End = p.message_count; + } + TML_FREE(trackbuf); + + // Change message time signature from delta ticks to actual msec values and link messages ordered by time + if (p.message_count) + { + tml_message *PrevMessage = TML_NULL, *Msg, *MsgEnd, Swap; + unsigned int ticks = 0, tempo_ticks = 0; //tick counter and value at last tempo change + int step_smallest, msec, tempo_msec = 0; //msec value at last tempo change + double ticks2time = 500000 / (1000.0 * division); //milliseconds per tick + + // Loop through all messages over all tracks ordered by time + for (step_smallest = 0; step_smallest != 0x7fffffff; ticks += step_smallest) + { + step_smallest = 0x7fffffff; + msec = tempo_msec + (int)((ticks - tempo_ticks) * ticks2time); + for (t = tracks; t != tracksEnd; t++) + { + if (t->Idx == t->End) continue; + for (Msg = &messages[t->Idx], MsgEnd = &messages[t->End]; Msg != MsgEnd && t->Ticks + Msg->time == ticks; Msg++, t->Idx++) + { + t->Ticks += Msg->time; + if (Msg->type == TML_SET_TEMPO) + { + unsigned char* Tempo = ((struct tml_tempomsg*)Msg)->Tempo; + ticks2time = ((Tempo[0]<<16)|(Tempo[1]<<8)|Tempo[2])/(1000.0 * division); + tempo_msec = msec; + tempo_ticks = ticks; + } + if (Msg->type) + { + Msg->time = msec; + if (PrevMessage) { PrevMessage->next = Msg; PrevMessage = Msg; } + else { Swap = *Msg; *Msg = *messages; *messages = Swap; PrevMessage = messages; } + } + } + if (Msg != MsgEnd && t->Ticks + Msg->time > ticks) + { + int step = (int)(t->Ticks + Msg->time - ticks); + if (step < step_smallest) step_smallest = step; + } + } + } + if (PrevMessage) PrevMessage->next = TML_NULL; + else p.message_count = 0; + } + TML_FREE(tracks); + + if (p.message_count == 0) + { + TML_FREE(messages); + messages = TML_NULL; + } + + return messages; +} + +TMLDEF tml_message* tml_load_tsf_stream(struct tsf_stream* stream) +{ + return tml_load((struct tml_stream*)stream); +} + +TMLDEF int tml_get_info(tml_message* Msg, int* out_used_channels, int* out_used_programs, int* out_total_notes, unsigned int* out_time_first_note, unsigned int* out_time_length) +{ + int used_programs = 0, used_channels = 0, total_notes = 0; + unsigned int time_first_note = 0xffffffff, time_length = 0; + unsigned char channels[16] = { 0 }, programs[128] = { 0 }; + for (;Msg; Msg = Msg->next) + { + time_length = Msg->time; + if (Msg->type == TML_PROGRAM_CHANGE && !programs[(int)Msg->program]) { programs[(int)Msg->program] = 1; used_programs++; } + if (Msg->type != TML_NOTE_ON) continue; + if (time_first_note == 0xffffffff) time_first_note = time_length; + if (!channels[Msg->channel]) { channels[Msg->channel] = 1; used_channels++; } + total_notes++; + } + if (time_first_note == 0xffffffff) time_first_note = 0; + if (out_used_channels ) *out_used_channels = used_channels; + if (out_used_programs ) *out_used_programs = used_programs; + if (out_total_notes ) *out_total_notes = total_notes; + if (out_time_first_note) *out_time_first_note = time_first_note; + if (out_time_length ) *out_time_length = time_length; + return total_notes; +} + +TMLDEF int tml_get_tempo_value(tml_message* msg) +{ + unsigned char* Tempo; + if (!msg || msg->type != TML_SET_TEMPO) return 0; + Tempo = ((struct tml_tempomsg*)msg)->Tempo; + return ((Tempo[0]<<16)|(Tempo[1]<<8)|Tempo[2]); +} + +TMLDEF void tml_free(tml_message* f) +{ + TML_FREE(f); +} + +#ifdef __cplusplus +} +#endif + +#endif //TML_IMPLEMENTATION diff --git a/source/engine/thirdparty/TinySoundFont/tsf.h b/source/engine/thirdparty/TinySoundFont/tsf.h new file mode 100644 index 0000000..a2d59fc --- /dev/null +++ b/source/engine/thirdparty/TinySoundFont/tsf.h @@ -0,0 +1,2045 @@ +/* TinySoundFont - v0.9 - SoundFont2 synthesizer - https://github.com/schellingb/TinySoundFont + no warranty implied; use at your own risk + Do this: + #define TSF_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + // i.e. it should look like this: + #include ... + #include ... + #define TSF_IMPLEMENTATION + #include "tsf.h" + + [OPTIONAL] #define TSF_NO_STDIO to remove stdio dependency + [OPTIONAL] #define TSF_MALLOC, TSF_REALLOC, and TSF_FREE to avoid stdlib.h + [OPTIONAL] #define TSF_MEMCPY, TSF_MEMSET to avoid string.h + [OPTIONAL] #define TSF_POW, TSF_POWF, TSF_EXPF, TSF_LOG, TSF_TAN, TSF_LOG10, TSF_SQRT to avoid math.h + + NOT YET IMPLEMENTED + - Support for ChorusEffectsSend and ReverbEffectsSend generators + - Better low-pass filter without lowering performance too much + - Support for modulators + + LICENSE (MIT) + + Copyright (C) 2017-2023 Bernhard Schelling + Based on SFZero, Copyright (C) 2012 Steve Folta (https://github.com/stevefolta/SFZero) + + 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. + +*/ + +#ifndef TSF_INCLUDE_TSF_INL +#define TSF_INCLUDE_TSF_INL + +#ifdef __cplusplus +extern "C" { +# define CPP_DEFAULT0 = 0 +#else +# define CPP_DEFAULT0 +#endif + +//define this if you want the API functions to be static +#ifdef TSF_STATIC +#define TSFDEF static +#else +#define TSFDEF extern +#endif + +// The load functions will return a pointer to a struct tsf which all functions +// thereafter take as the first parameter. +// On error the tsf_load* functions will return NULL most likely due to invalid +// data (or if the file did not exist in tsf_load_filename). +typedef struct tsf tsf; + +#ifndef TSF_NO_STDIO +// Directly load a SoundFont from a .sf2 file path +TSFDEF tsf* tsf_load_filename(const char* filename); +#endif + +// Load a SoundFont from a block of memory +TSFDEF tsf* tsf_load_memory(const void* buffer, int size); + +// Stream structure for the generic loading +struct tsf_stream +{ + // Custom data given to the functions as the first parameter + void* data; + + // Function pointer will be called to read 'size' bytes into ptr (returns number of read bytes) + int (*read)(void* data, void* ptr, unsigned int size); + + // Function pointer will be called to skip ahead over 'count' bytes (returns 1 on success, 0 on error) + int (*skip)(void* data, unsigned int count); +}; + +// Generic SoundFont loading method using the stream structure above +TSFDEF tsf* tsf_load(struct tsf_stream* stream); + +// Copy a tsf instance from an existing one, use tsf_close to close it as well. +// All copied tsf instances and their original instance are linked, and share the underlying soundfont. +// This allows loading a soundfont only once, but using it for multiple independent playbacks. +// (This function isn't thread-safe without locking.) +TSFDEF tsf* tsf_copy(tsf* f); + +// Free the memory related to this tsf instance +TSFDEF void tsf_close(tsf* f); + +// Stop all playing notes immediately and reset all channel parameters +TSFDEF void tsf_reset(tsf* f); + +// Returns the preset index from a bank and preset number, or -1 if it does not exist in the loaded SoundFont +TSFDEF int tsf_get_presetindex(const tsf* f, int bank, int preset_number); + +// Returns the number of presets in the loaded SoundFont +TSFDEF int tsf_get_presetcount(const tsf* f); + +// Returns the name of a preset index >= 0 and < tsf_get_presetcount() +TSFDEF const char* tsf_get_presetname(const tsf* f, int preset_index); + +// Returns the name of a preset by bank and preset number +TSFDEF const char* tsf_bank_get_presetname(const tsf* f, int bank, int preset_number); + +// Supported output modes by the render methods +enum TSFOutputMode +{ + // Two channels with single left/right samples one after another + TSF_STEREO_INTERLEAVED, + // Two channels with all samples for the left channel first then right + TSF_STEREO_UNWEAVED, + // A single channel (stereo instruments are mixed into center) + TSF_MONO +}; + +// Thread safety: +// +// 1. Rendering / voices: +// +// Your audio output which calls the tsf_render* functions will most likely +// run on a different thread than where the playback tsf_note* functions +// are called. In which case some sort of concurrency control like a +// mutex needs to be used so they are not called at the same time. +// Alternatively, you can pre-allocate a maximum number of voices that can +// play simultaneously by calling tsf_set_max_voices after loading. +// That way memory re-allocation will not happen during tsf_note_on and +// TSF should become mostly thread safe. +// There is a theoretical chance that ending notes would negatively influence +// a voice that is rendering at the time but it is hard to say. +// Also be aware, this has not been tested much. +// +// 2. Channels: +// +// Calls to tsf_channel_set_... functions may allocate new channels +// if no channel with that number was previously used. Make sure to +// create all channels at the beginning as required if you call tsf_render* +// from a different thread. + +// Setup the parameters for the voice render methods +// outputmode: if mono or stereo and how stereo channel data is ordered +// samplerate: the number of samples per second (output frequency) +// global_gain_db: volume gain in decibels (>0 means higher, <0 means lower) +TSFDEF void tsf_set_output(tsf* f, enum TSFOutputMode outputmode, int samplerate, float global_gain_db CPP_DEFAULT0); + +// Set the global gain as a volume factor +// global_gain: the desired volume where 1.0 is 100% +TSFDEF void tsf_set_volume(tsf* f, float global_gain); + +// Set the maximum number of voices to play simultaneously +// Depending on the soundfond, one note can cause many new voices to be started, +// so don't keep this number too low or otherwise sounds may not play. +// max_voices: maximum number to pre-allocate and set the limit to +// (tsf_set_max_voices returns 0 if allocation failed, otherwise 1) +TSFDEF int tsf_set_max_voices(tsf* f, int max_voices); + +// Start playing a note +// preset_index: preset index >= 0 and < tsf_get_presetcount() +// key: note value between 0 and 127 (60 being middle C) +// vel: velocity as a float between 0.0 (equal to note off) and 1.0 (full) +// bank: instrument bank number (alternative to preset_index) +// preset_number: preset number (alternative to preset_index) +// (tsf_note_on returns 0 if the allocation of a new voice failed, otherwise 1) +// (tsf_bank_note_on returns 0 if preset does not exist or allocation failed, otherwise 1) +TSFDEF int tsf_note_on(tsf* f, int preset_index, int key, float vel); +TSFDEF int tsf_bank_note_on(tsf* f, int bank, int preset_number, int key, float vel); + +// Stop playing a note +// (bank_note_off returns 0 if preset does not exist, otherwise 1) +TSFDEF void tsf_note_off(tsf* f, int preset_index, int key); +TSFDEF int tsf_bank_note_off(tsf* f, int bank, int preset_number, int key); + +// Stop playing all notes (end with sustain and release) +TSFDEF void tsf_note_off_all(tsf* f); + +// Returns the number of active voices +TSFDEF int tsf_active_voice_count(tsf* f); + +// Render output samples into a buffer +// You can either render as signed 16-bit values (tsf_render_short) or +// as 32-bit float values (tsf_render_float) +// buffer: target buffer of size samples * output_channels * sizeof(type) +// samples: number of samples to render +// flag_mixing: if 0 clear the buffer first, otherwise mix into existing data +TSFDEF void tsf_render_short(tsf* f, short* buffer, int samples, int flag_mixing CPP_DEFAULT0); +TSFDEF void tsf_render_float(tsf* f, float* buffer, int samples, int flag_mixing CPP_DEFAULT0); + +// Higher level channel based functions, set up channel parameters +// channel: channel number +// preset_index: preset index >= 0 and < tsf_get_presetcount() +// preset_number: preset number (alternative to preset_index) +// flag_mididrums: 0 for normal channels, otherwise apply MIDI drum channel rules +// bank: instrument bank number (alternative to preset_index) +// pan: stereo panning value from 0.0 (left) to 1.0 (right) (default 0.5 center) +// volume: linear volume scale factor (default 1.0 full) +// pitch_wheel: pitch wheel position 0 to 16383 (default 8192 unpitched) +// pitch_range: range of the pitch wheel in semitones (default 2.0, total +/- 2 semitones) +// tuning: tuning of all playing voices in semitones (default 0.0, standard (A440) tuning) +// (tsf_set_preset_number and set_bank_preset return 0 if preset does not exist, otherwise 1) +// (tsf_channel_set_... return 0 if a new channel needed allocation and that failed, otherwise 1) +TSFDEF int tsf_channel_set_presetindex(tsf* f, int channel, int preset_index); +TSFDEF int tsf_channel_set_presetnumber(tsf* f, int channel, int preset_number, int flag_mididrums CPP_DEFAULT0); +TSFDEF int tsf_channel_set_bank(tsf* f, int channel, int bank); +TSFDEF int tsf_channel_set_bank_preset(tsf* f, int channel, int bank, int preset_number); +TSFDEF int tsf_channel_set_pan(tsf* f, int channel, float pan); +TSFDEF int tsf_channel_set_volume(tsf* f, int channel, float volume); +TSFDEF int tsf_channel_set_pitchwheel(tsf* f, int channel, int pitch_wheel); +TSFDEF int tsf_channel_set_pitchrange(tsf* f, int channel, float pitch_range); +TSFDEF int tsf_channel_set_tuning(tsf* f, int channel, float tuning); + +// Start or stop playing notes on a channel (needs channel preset to be set) +// channel: channel number +// key: note value between 0 and 127 (60 being middle C) +// vel: velocity as a float between 0.0 (equal to note off) and 1.0 (full) +// (tsf_channel_note_on returns 0 on allocation failure of new voice, otherwise 1) +TSFDEF int tsf_channel_note_on(tsf* f, int channel, int key, float vel); +TSFDEF void tsf_channel_note_off(tsf* f, int channel, int key); +TSFDEF void tsf_channel_note_off_all(tsf* f, int channel); //end with sustain and release +TSFDEF void tsf_channel_sounds_off_all(tsf* f, int channel); //end immediately + +// Apply a MIDI control change to the channel (not all controllers are supported!) +// (tsf_channel_midi_control returns 0 on allocation failure of new channel, otherwise 1) +TSFDEF int tsf_channel_midi_control(tsf* f, int channel, int controller, int control_value); + +// Get current values set on the channels +TSFDEF int tsf_channel_get_preset_index(tsf* f, int channel); +TSFDEF int tsf_channel_get_preset_bank(tsf* f, int channel); +TSFDEF int tsf_channel_get_preset_number(tsf* f, int channel); +TSFDEF float tsf_channel_get_pan(tsf* f, int channel); +TSFDEF float tsf_channel_get_volume(tsf* f, int channel); +TSFDEF int tsf_channel_get_pitchwheel(tsf* f, int channel); +TSFDEF float tsf_channel_get_pitchrange(tsf* f, int channel); +TSFDEF float tsf_channel_get_tuning(tsf* f, int channel); + +#ifdef __cplusplus +# undef CPP_DEFAULT0 +} +#endif + +// end header +// --------------------------------------------------------------------------------------------------------- +#endif //TSF_INCLUDE_TSF_INL + +#ifdef TSF_IMPLEMENTATION +#undef TSF_IMPLEMENTATION + +// The lower this block size is the more accurate the effects are. +// Increasing the value significantly lowers the CPU usage of the voice rendering. +// If LFO affects the low-pass filter it can be hearable even as low as 8. +#ifndef TSF_RENDER_EFFECTSAMPLEBLOCK +#define TSF_RENDER_EFFECTSAMPLEBLOCK 64 +#endif + +// When using tsf_render_short, to do the conversion a buffer of a fixed size is +// allocated on the stack. On low memory platforms this could be made smaller. +// Increasing this above 512 should not have a significant impact on performance. +// The value should be a multiple of TSF_RENDER_EFFECTSAMPLEBLOCK. +#ifndef TSF_RENDER_SHORTBUFFERBLOCK +#define TSF_RENDER_SHORTBUFFERBLOCK 512 +#endif + +// Grace release time for quick voice off (avoid clicking noise) +#define TSF_FASTRELEASETIME 0.01f + +#if !defined(TSF_MALLOC) || !defined(TSF_FREE) || !defined(TSF_REALLOC) +# include +# define TSF_MALLOC malloc +# define TSF_FREE free +# define TSF_REALLOC realloc +#endif + +#if !defined(TSF_MEMCPY) || !defined(TSF_MEMSET) +# include +# define TSF_MEMCPY memcpy +# define TSF_MEMSET memset +#endif + +#if !defined(TSF_POW) || !defined(TSF_POWF) || !defined(TSF_EXPF) || !defined(TSF_LOG) || !defined(TSF_TAN) || !defined(TSF_LOG10) || !defined(TSF_SQRT) +# include +# if !defined(__cplusplus) && !defined(NAN) && !defined(powf) && !defined(expf) && !defined(sqrtf) +# define powf (float)pow // deal with old math.h +# define expf (float)exp // files that come without +# define sqrtf (float)sqrt // powf, expf and sqrtf +# endif +# define TSF_POW pow +# define TSF_POWF powf +# define TSF_EXPF expf +# define TSF_LOG log +# define TSF_TAN tan +# define TSF_LOG10 log10 +# define TSF_SQRTF sqrtf +#endif + +#ifndef TSF_NO_STDIO +# include +#endif + +#define TSF_TRUE 1 +#define TSF_FALSE 0 +#define TSF_BOOL char +#define TSF_PI 3.14159265358979323846264338327950288 +#define TSF_NULL 0 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef char tsf_fourcc[4]; +typedef signed char tsf_s8; +typedef unsigned char tsf_u8; +typedef unsigned short tsf_u16; +typedef signed short tsf_s16; +typedef unsigned int tsf_u32; +typedef char tsf_char20[20]; + +#define TSF_FourCCEquals(value1, value2) (value1[0] == value2[0] && value1[1] == value2[1] && value1[2] == value2[2] && value1[3] == value2[3]) + +struct tsf +{ + struct tsf_preset* presets; + float* fontSamples; + struct tsf_voice* voices; + struct tsf_channels* channels; + + int presetNum; + int voiceNum; + int maxVoiceNum; + unsigned int voicePlayIndex; + + enum TSFOutputMode outputmode; + float outSampleRate; + float globalGainDB; + int* refCount; +}; + +#ifndef TSF_NO_STDIO +static int tsf_stream_stdio_read(FILE* f, void* ptr, unsigned int size) { return (int)fread(ptr, 1, size, f); } +static int tsf_stream_stdio_skip(FILE* f, unsigned int count) { return !fseek(f, count, SEEK_CUR); } +TSFDEF tsf* tsf_load_filename(const char* filename) +{ + tsf* res; + struct tsf_stream stream = { TSF_NULL, (int(*)(void*,void*,unsigned int))&tsf_stream_stdio_read, (int(*)(void*,unsigned int))&tsf_stream_stdio_skip }; + #if __STDC_WANT_SECURE_LIB__ + FILE* f = TSF_NULL; fopen_s(&f, filename, "rb"); + #else + FILE* f = fopen(filename, "rb"); + #endif + if (!f) + { + //if (e) *e = TSF_FILENOTFOUND; + return TSF_NULL; + } + stream.data = f; + res = tsf_load(&stream); + fclose(f); + return res; +} +#endif + +struct tsf_stream_memory { const char* buffer; unsigned int total, pos; }; +static int tsf_stream_memory_read(struct tsf_stream_memory* m, void* ptr, unsigned int size) { if (size > m->total - m->pos) size = m->total - m->pos; TSF_MEMCPY(ptr, m->buffer+m->pos, size); m->pos += size; return size; } +static int tsf_stream_memory_skip(struct tsf_stream_memory* m, unsigned int count) { if (m->pos + count > m->total) return 0; m->pos += count; return 1; } +TSFDEF tsf* tsf_load_memory(const void* buffer, int size) +{ + struct tsf_stream stream = { TSF_NULL, (int(*)(void*,void*,unsigned int))&tsf_stream_memory_read, (int(*)(void*,unsigned int))&tsf_stream_memory_skip }; + struct tsf_stream_memory f = { 0, 0, 0 }; + f.buffer = (const char*)buffer; + f.total = size; + stream.data = &f; + return tsf_load(&stream); +} + +enum { TSF_LOOPMODE_NONE, TSF_LOOPMODE_CONTINUOUS, TSF_LOOPMODE_SUSTAIN }; + +enum { TSF_SEGMENT_NONE, TSF_SEGMENT_DELAY, TSF_SEGMENT_ATTACK, TSF_SEGMENT_HOLD, TSF_SEGMENT_DECAY, TSF_SEGMENT_SUSTAIN, TSF_SEGMENT_RELEASE, TSF_SEGMENT_DONE }; + +struct tsf_hydra +{ + struct tsf_hydra_phdr *phdrs; struct tsf_hydra_pbag *pbags; struct tsf_hydra_pmod *pmods; + struct tsf_hydra_pgen *pgens; struct tsf_hydra_inst *insts; struct tsf_hydra_ibag *ibags; + struct tsf_hydra_imod *imods; struct tsf_hydra_igen *igens; struct tsf_hydra_shdr *shdrs; + int phdrNum, pbagNum, pmodNum, pgenNum, instNum, ibagNum, imodNum, igenNum, shdrNum; +}; + +union tsf_hydra_genamount { struct { tsf_u8 lo, hi; } range; tsf_s16 shortAmount; tsf_u16 wordAmount; }; +struct tsf_hydra_phdr { tsf_char20 presetName; tsf_u16 preset, bank, presetBagNdx; tsf_u32 library, genre, morphology; }; +struct tsf_hydra_pbag { tsf_u16 genNdx, modNdx; }; +struct tsf_hydra_pmod { tsf_u16 modSrcOper, modDestOper; tsf_s16 modAmount; tsf_u16 modAmtSrcOper, modTransOper; }; +struct tsf_hydra_pgen { tsf_u16 genOper; union tsf_hydra_genamount genAmount; }; +struct tsf_hydra_inst { tsf_char20 instName; tsf_u16 instBagNdx; }; +struct tsf_hydra_ibag { tsf_u16 instGenNdx, instModNdx; }; +struct tsf_hydra_imod { tsf_u16 modSrcOper, modDestOper; tsf_s16 modAmount; tsf_u16 modAmtSrcOper, modTransOper; }; +struct tsf_hydra_igen { tsf_u16 genOper; union tsf_hydra_genamount genAmount; }; +struct tsf_hydra_shdr { tsf_char20 sampleName; tsf_u32 start, end, startLoop, endLoop, sampleRate; tsf_u8 originalPitch; tsf_s8 pitchCorrection; tsf_u16 sampleLink, sampleType; }; + +#define TSFR(FIELD) stream->read(stream->data, &i->FIELD, sizeof(i->FIELD)); +static void tsf_hydra_read_phdr(struct tsf_hydra_phdr* i, struct tsf_stream* stream) { TSFR(presetName) TSFR(preset) TSFR(bank) TSFR(presetBagNdx) TSFR(library) TSFR(genre) TSFR(morphology) } +static void tsf_hydra_read_pbag(struct tsf_hydra_pbag* i, struct tsf_stream* stream) { TSFR(genNdx) TSFR(modNdx) } +static void tsf_hydra_read_pmod(struct tsf_hydra_pmod* i, struct tsf_stream* stream) { TSFR(modSrcOper) TSFR(modDestOper) TSFR(modAmount) TSFR(modAmtSrcOper) TSFR(modTransOper) } +static void tsf_hydra_read_pgen(struct tsf_hydra_pgen* i, struct tsf_stream* stream) { TSFR(genOper) TSFR(genAmount) } +static void tsf_hydra_read_inst(struct tsf_hydra_inst* i, struct tsf_stream* stream) { TSFR(instName) TSFR(instBagNdx) } +static void tsf_hydra_read_ibag(struct tsf_hydra_ibag* i, struct tsf_stream* stream) { TSFR(instGenNdx) TSFR(instModNdx) } +static void tsf_hydra_read_imod(struct tsf_hydra_imod* i, struct tsf_stream* stream) { TSFR(modSrcOper) TSFR(modDestOper) TSFR(modAmount) TSFR(modAmtSrcOper) TSFR(modTransOper) } +static void tsf_hydra_read_igen(struct tsf_hydra_igen* i, struct tsf_stream* stream) { TSFR(genOper) TSFR(genAmount) } +static void tsf_hydra_read_shdr(struct tsf_hydra_shdr* i, struct tsf_stream* stream) { TSFR(sampleName) TSFR(start) TSFR(end) TSFR(startLoop) TSFR(endLoop) TSFR(sampleRate) TSFR(originalPitch) TSFR(pitchCorrection) TSFR(sampleLink) TSFR(sampleType) } +#undef TSFR + +struct tsf_riffchunk { tsf_fourcc id; tsf_u32 size; }; +struct tsf_envelope { float delay, attack, hold, decay, sustain, release, keynumToHold, keynumToDecay; }; +struct tsf_voice_envelope { float level, slope; int samplesUntilNextSegment; short segment, midiVelocity; struct tsf_envelope parameters; TSF_BOOL segmentIsExponential, isAmpEnv; }; +struct tsf_voice_lowpass { double QInv, a0, a1, b1, b2, z1, z2; TSF_BOOL active; }; +struct tsf_voice_lfo { int samplesUntil; float level, delta; }; + +struct tsf_region +{ + int loop_mode; + unsigned int sample_rate; + unsigned char lokey, hikey, lovel, hivel; + unsigned int group, offset, end, loop_start, loop_end; + int transpose, tune, pitch_keycenter, pitch_keytrack; + float attenuation, pan; + struct tsf_envelope ampenv, modenv; + int initialFilterQ, initialFilterFc; + int modEnvToPitch, modEnvToFilterFc, modLfoToFilterFc, modLfoToVolume; + float delayModLFO; + int freqModLFO, modLfoToPitch; + float delayVibLFO; + int freqVibLFO, vibLfoToPitch; +}; + +struct tsf_preset +{ + tsf_char20 presetName; + tsf_u16 preset, bank; + struct tsf_region* regions; + int regionNum; +}; + +struct tsf_voice +{ + int playingPreset, playingKey, playingChannel; + struct tsf_region* region; + double pitchInputTimecents, pitchOutputFactor; + double sourceSamplePosition; + float noteGainDB, panFactorLeft, panFactorRight; + unsigned int playIndex, loopStart, loopEnd; + struct tsf_voice_envelope ampenv, modenv; + struct tsf_voice_lowpass lowpass; + struct tsf_voice_lfo modlfo, viblfo; +}; + +struct tsf_channel +{ + unsigned short presetIndex, bank, pitchWheel, midiPan, midiVolume, midiExpression, midiRPN, midiData; + float panOffset, gainDB, pitchRange, tuning; +}; + +struct tsf_channels +{ + void (*setupVoice)(tsf* f, struct tsf_voice* voice); + int channelNum, activeChannel; + struct tsf_channel channels[1]; +}; + +static double tsf_timecents2Secsd(double timecents) { return TSF_POW(2.0, timecents / 1200.0); } +static float tsf_timecents2Secsf(float timecents) { return TSF_POWF(2.0f, timecents / 1200.0f); } +static float tsf_cents2Hertz(float cents) { return 8.176f * TSF_POWF(2.0f, cents / 1200.0f); } +static float tsf_decibelsToGain(float db) { return (db > -100.f ? TSF_POWF(10.0f, db * 0.05f) : 0); } +static float tsf_gainToDecibels(float gain) { return (gain <= .00001f ? -100.f : (float)(20.0 * TSF_LOG10(gain))); } + +static TSF_BOOL tsf_riffchunk_read(struct tsf_riffchunk* parent, struct tsf_riffchunk* chunk, struct tsf_stream* stream) +{ + TSF_BOOL IsRiff, IsList; + if (parent && sizeof(tsf_fourcc) + sizeof(tsf_u32) > parent->size) return TSF_FALSE; + if (!stream->read(stream->data, &chunk->id, sizeof(tsf_fourcc)) || *chunk->id <= ' ' || *chunk->id >= 'z') return TSF_FALSE; + if (!stream->read(stream->data, &chunk->size, sizeof(tsf_u32))) return TSF_FALSE; + if (parent && sizeof(tsf_fourcc) + sizeof(tsf_u32) + chunk->size > parent->size) return TSF_FALSE; + if (parent) parent->size -= sizeof(tsf_fourcc) + sizeof(tsf_u32) + chunk->size; + IsRiff = TSF_FourCCEquals(chunk->id, "RIFF"), IsList = TSF_FourCCEquals(chunk->id, "LIST"); + if (IsRiff && parent) return TSF_FALSE; //not allowed + if (!IsRiff && !IsList) return TSF_TRUE; //custom type without sub type + if (!stream->read(stream->data, &chunk->id, sizeof(tsf_fourcc)) || *chunk->id <= ' ' || *chunk->id >= 'z') return TSF_FALSE; + chunk->size -= sizeof(tsf_fourcc); + return TSF_TRUE; +} + +static void tsf_region_clear(struct tsf_region* i, TSF_BOOL for_relative) +{ + TSF_MEMSET(i, 0, sizeof(struct tsf_region)); + i->hikey = i->hivel = 127; + i->pitch_keycenter = 60; // C4 + if (for_relative) return; + + i->pitch_keytrack = 100; + + i->pitch_keycenter = -1; + + // SF2 defaults in timecents. + i->ampenv.delay = i->ampenv.attack = i->ampenv.hold = i->ampenv.decay = i->ampenv.release = -12000.0f; + i->modenv.delay = i->modenv.attack = i->modenv.hold = i->modenv.decay = i->modenv.release = -12000.0f; + + i->initialFilterFc = 13500; + + i->delayModLFO = -12000.0f; + i->delayVibLFO = -12000.0f; +} + +static void tsf_region_operator(struct tsf_region* region, tsf_u16 genOper, union tsf_hydra_genamount* amount, struct tsf_region* merge_region) +{ + enum + { + _GEN_TYPE_MASK = 0x0F, + GEN_FLOAT = 0x01, + GEN_INT = 0x02, + GEN_UINT_ADD = 0x03, + GEN_UINT_ADD15 = 0x04, + GEN_KEYRANGE = 0x05, + GEN_VELRANGE = 0x06, + GEN_LOOPMODE = 0x07, + GEN_GROUP = 0x08, + GEN_KEYCENTER = 0x09, + + _GEN_LIMIT_MASK = 0xF0, + GEN_INT_LIMIT12K = 0x10, //min -12000, max 12000 + GEN_INT_LIMITFC = 0x20, //min 1500, max 13500 + GEN_INT_LIMITQ = 0x30, //min 0, max 960 + GEN_INT_LIMIT960 = 0x40, //min -960, max 960 + GEN_INT_LIMIT16K4500 = 0x50, //min -16000, max 4500 + GEN_FLOAT_LIMIT12K5K = 0x60, //min -12000, max 5000 + GEN_FLOAT_LIMIT12K8K = 0x70, //min -12000, max 8000 + GEN_FLOAT_LIMIT1200 = 0x80, //min -1200, max 1200 + GEN_FLOAT_LIMITPAN = 0x90, //* .001f, min -.5f, max .5f, + GEN_FLOAT_LIMITATTN = 0xA0, //* .1f, min 0, max 144.0 + GEN_FLOAT_MAX1000 = 0xB0, //min 0, max 1000 + GEN_FLOAT_MAX1440 = 0xC0, //min 0, max 1440 + + _GEN_MAX = 59 + }; + #define _TSFREGIONOFFSET(TYPE, FIELD) (unsigned char)(((TYPE*)&((struct tsf_region*)0)->FIELD) - (TYPE*)0) + #define _TSFREGIONENVOFFSET(TYPE, ENV, FIELD) (unsigned char)(((TYPE*)&((&(((struct tsf_region*)0)->ENV))->FIELD)) - (TYPE*)0) + static const struct { unsigned char mode, offset; } genMetas[_GEN_MAX] = + { + { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, offset ) }, // 0 StartAddrsOffset + { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, end ) }, // 1 EndAddrsOffset + { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, loop_start ) }, // 2 StartloopAddrsOffset + { GEN_UINT_ADD , _TSFREGIONOFFSET(unsigned int, loop_end ) }, // 3 EndloopAddrsOffset + { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, offset ) }, // 4 StartAddrsCoarseOffset + { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modLfoToPitch ) }, // 5 ModLfoToPitch + { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, vibLfoToPitch ) }, // 6 VibLfoToPitch + { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modEnvToPitch ) }, // 7 ModEnvToPitch + { GEN_INT | GEN_INT_LIMITFC , _TSFREGIONOFFSET( int, initialFilterFc ) }, // 8 InitialFilterFc + { GEN_INT | GEN_INT_LIMITQ , _TSFREGIONOFFSET( int, initialFilterQ ) }, // 9 InitialFilterQ + { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modLfoToFilterFc ) }, //10 ModLfoToFilterFc + { GEN_INT | GEN_INT_LIMIT12K , _TSFREGIONOFFSET( int, modEnvToFilterFc ) }, //11 ModEnvToFilterFc + { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, end ) }, //12 EndAddrsCoarseOffset + { GEN_INT | GEN_INT_LIMIT960 , _TSFREGIONOFFSET( int, modLfoToVolume ) }, //13 ModLfoToVolume + { 0 , (0 ) }, // Unused + { 0 , (0 ) }, //15 ChorusEffectsSend (unsupported) + { 0 , (0 ) }, //16 ReverbEffectsSend (unsupported) + { GEN_FLOAT | GEN_FLOAT_LIMITPAN , _TSFREGIONOFFSET( float, pan ) }, //17 Pan + { 0 , (0 ) }, // Unused + { 0 , (0 ) }, // Unused + { 0 , (0 ) }, // Unused + { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONOFFSET( float, delayModLFO ) }, //21 DelayModLFO + { GEN_INT | GEN_INT_LIMIT16K4500 , _TSFREGIONOFFSET( int, freqModLFO ) }, //22 FreqModLFO + { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONOFFSET( float, delayVibLFO ) }, //23 DelayVibLFO + { GEN_INT | GEN_INT_LIMIT16K4500 , _TSFREGIONOFFSET( int, freqVibLFO ) }, //24 FreqVibLFO + { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, modenv, delay ) }, //25 DelayModEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, modenv, attack ) }, //26 AttackModEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, modenv, hold ) }, //27 HoldModEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, modenv, decay ) }, //28 DecayModEnv + { GEN_FLOAT | GEN_FLOAT_MAX1000 , _TSFREGIONENVOFFSET( float, modenv, sustain ) }, //29 SustainModEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, modenv, release ) }, //30 ReleaseModEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, modenv, keynumToHold ) }, //31 KeynumToModEnvHold + { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, modenv, keynumToDecay) }, //32 KeynumToModEnvDecay + { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, ampenv, delay ) }, //33 DelayVolEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, ampenv, attack ) }, //34 AttackVolEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K5K , _TSFREGIONENVOFFSET( float, ampenv, hold ) }, //35 HoldVolEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, ampenv, decay ) }, //36 DecayVolEnv + { GEN_FLOAT | GEN_FLOAT_MAX1440 , _TSFREGIONENVOFFSET( float, ampenv, sustain ) }, //37 SustainVolEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT12K8K , _TSFREGIONENVOFFSET( float, ampenv, release ) }, //38 ReleaseVolEnv + { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, ampenv, keynumToHold ) }, //39 KeynumToVolEnvHold + { GEN_FLOAT | GEN_FLOAT_LIMIT1200 , _TSFREGIONENVOFFSET( float, ampenv, keynumToDecay) }, //40 KeynumToVolEnvDecay + { 0 , (0 ) }, // Instrument (special) + { 0 , (0 ) }, // Reserved + { GEN_KEYRANGE , (0 ) }, //43 KeyRange + { GEN_VELRANGE , (0 ) }, //44 VelRange + { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, loop_start ) }, //45 StartloopAddrsCoarseOffset + { 0 , (0 ) }, //46 Keynum (special) + { 0 , (0 ) }, //47 Velocity (special) + { GEN_FLOAT | GEN_FLOAT_LIMITATTN , _TSFREGIONOFFSET( float, attenuation ) }, //48 InitialAttenuation + { 0 , (0 ) }, // Reserved + { GEN_UINT_ADD15 , _TSFREGIONOFFSET(unsigned int, loop_end ) }, //50 EndloopAddrsCoarseOffset + { GEN_INT , _TSFREGIONOFFSET( int, transpose ) }, //51 CoarseTune + { GEN_INT , _TSFREGIONOFFSET( int, tune ) }, //52 FineTune + { 0 , (0 ) }, // SampleID (special) + { GEN_LOOPMODE , _TSFREGIONOFFSET( int, loop_mode ) }, //54 SampleModes + { 0 , (0 ) }, // Reserved + { GEN_INT , _TSFREGIONOFFSET( int, pitch_keytrack ) }, //56 ScaleTuning + { GEN_GROUP , _TSFREGIONOFFSET(unsigned int, group ) }, //57 ExclusiveClass + { GEN_KEYCENTER , _TSFREGIONOFFSET( int, pitch_keycenter ) }, //58 OverridingRootKey + }; + #undef _TSFREGIONOFFSET + #undef _TSFREGIONENVOFFSET + if (amount) + { + int offset; + if (genOper >= _GEN_MAX) return; + offset = genMetas[genOper].offset; + switch (genMetas[genOper].mode & _GEN_TYPE_MASK) + { + case GEN_FLOAT: (( float*)region)[offset] = amount->shortAmount; return; + case GEN_INT: (( int*)region)[offset] = amount->shortAmount; return; + case GEN_UINT_ADD: ((unsigned int*)region)[offset] += amount->shortAmount; return; + case GEN_UINT_ADD15: ((unsigned int*)region)[offset] += amount->shortAmount<<15; return; + case GEN_KEYRANGE: region->lokey = amount->range.lo; region->hikey = amount->range.hi; return; + case GEN_VELRANGE: region->lovel = amount->range.lo; region->hivel = amount->range.hi; return; + case GEN_LOOPMODE: region->loop_mode = ((amount->wordAmount&3) == 3 ? TSF_LOOPMODE_SUSTAIN : ((amount->wordAmount&3) == 1 ? TSF_LOOPMODE_CONTINUOUS : TSF_LOOPMODE_NONE)); return; + case GEN_GROUP: region->group = amount->wordAmount; return; + case GEN_KEYCENTER: region->pitch_keycenter = amount->shortAmount; return; + } + } + else //merge regions and clamp values + { + for (genOper = 0; genOper != _GEN_MAX; genOper++) + { + int offset = genMetas[genOper].offset; + switch (genMetas[genOper].mode & _GEN_TYPE_MASK) + { + case GEN_FLOAT: + { + float *val = &((float*)region)[offset], vfactor, vmin, vmax; + *val += ((float*)merge_region)[offset]; + switch (genMetas[genOper].mode & _GEN_LIMIT_MASK) + { + case GEN_FLOAT_LIMIT12K5K: vfactor = 1.0f; vmin = -12000.0f; vmax = 5000.0f; break; + case GEN_FLOAT_LIMIT12K8K: vfactor = 1.0f; vmin = -12000.0f; vmax = 8000.0f; break; + case GEN_FLOAT_LIMIT1200: vfactor = 1.0f; vmin = -1200.0f; vmax = 1200.0f; break; + case GEN_FLOAT_LIMITPAN: vfactor = 0.001f; vmin = -0.5f; vmax = 0.5f; break; + case GEN_FLOAT_LIMITATTN: vfactor = 0.1f; vmin = 0.0f; vmax = 144.0f; break; + case GEN_FLOAT_MAX1000: vfactor = 1.0f; vmin = 0.0f; vmax = 1000.0f; break; + case GEN_FLOAT_MAX1440: vfactor = 1.0f; vmin = 0.0f; vmax = 1440.0f; break; + default: continue; + } + *val *= vfactor; + if (*val < vmin) *val = vmin; + else if (*val > vmax) *val = vmax; + continue; + } + case GEN_INT: + { + int *val = &((int*)region)[offset], vmin, vmax; + *val += ((int*)merge_region)[offset]; + switch (genMetas[genOper].mode & _GEN_LIMIT_MASK) + { + case GEN_INT_LIMIT12K: vmin = -12000; vmax = 12000; break; + case GEN_INT_LIMITFC: vmin = 1500; vmax = 13500; break; + case GEN_INT_LIMITQ: vmin = 0; vmax = 960; break; + case GEN_INT_LIMIT960: vmin = -960; vmax = 960; break; + case GEN_INT_LIMIT16K4500: vmin = -16000; vmax = 4500; break; + default: continue; + } + if (*val < vmin) *val = vmin; + else if (*val > vmax) *val = vmax; + continue; + } + case GEN_UINT_ADD: + { + ((unsigned int*)region)[offset] += ((unsigned int*)merge_region)[offset]; + continue; + } + } + } + } +} + +static void tsf_region_envtosecs(struct tsf_envelope* p, TSF_BOOL sustainIsGain) +{ + // EG times need to be converted from timecents to seconds. + // Pin very short EG segments. Timecents don't get to zero, and our EG is + // happier with zero values. + p->delay = (p->delay < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->delay)); + p->attack = (p->attack < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->attack)); + p->release = (p->release < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->release)); + + // If we have dynamic hold or decay times depending on key number we need + // to keep the values in timecents so we can calculate it during startNote + if (!p->keynumToHold) p->hold = (p->hold < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->hold)); + if (!p->keynumToDecay) p->decay = (p->decay < -11950.0f ? 0.0f : tsf_timecents2Secsf(p->decay)); + + if (p->sustain < 0.0f) p->sustain = 0.0f; + else if (sustainIsGain) p->sustain = tsf_decibelsToGain(-p->sustain / 10.0f); + else p->sustain = 1.0f - (p->sustain / 1000.0f); +} + +static int tsf_load_presets(tsf* res, struct tsf_hydra *hydra, unsigned int fontSampleCount) +{ + enum { GenInstrument = 41, GenKeyRange = 43, GenVelRange = 44, GenSampleID = 53 }; + // Read each preset. + struct tsf_hydra_phdr *pphdr, *pphdrMax; + res->presetNum = hydra->phdrNum - 1; + res->presets = (struct tsf_preset*)TSF_MALLOC(res->presetNum * sizeof(struct tsf_preset)); + if (!res->presets) return 0; + else { int i; for (i = 0; i != res->presetNum; i++) res->presets[i].regions = TSF_NULL; } + for (pphdr = hydra->phdrs, pphdrMax = pphdr + hydra->phdrNum - 1; pphdr != pphdrMax; pphdr++) + { + int sortedIndex = 0, region_index = 0; + struct tsf_hydra_phdr *otherphdr; + struct tsf_preset* preset; + struct tsf_hydra_pbag *ppbag, *ppbagEnd; + struct tsf_region globalRegion; + for (otherphdr = hydra->phdrs; otherphdr != pphdrMax; otherphdr++) + { + if (otherphdr == pphdr || otherphdr->bank > pphdr->bank) continue; + else if (otherphdr->bank < pphdr->bank) sortedIndex++; + else if (otherphdr->preset > pphdr->preset) continue; + else if (otherphdr->preset < pphdr->preset) sortedIndex++; + else if (otherphdr < pphdr) sortedIndex++; + } + + preset = &res->presets[sortedIndex]; + TSF_MEMCPY(preset->presetName, pphdr->presetName, sizeof(preset->presetName)); + preset->presetName[sizeof(preset->presetName)-1] = '\0'; //should be zero terminated in source file but make sure + preset->bank = pphdr->bank; + preset->preset = pphdr->preset; + preset->regionNum = 0; + + //count regions covered by this preset + for (ppbag = hydra->pbags + pphdr->presetBagNdx, ppbagEnd = hydra->pbags + pphdr[1].presetBagNdx; ppbag != ppbagEnd; ppbag++) + { + unsigned char plokey = 0, phikey = 127, plovel = 0, phivel = 127; + struct tsf_hydra_pgen *ppgen, *ppgenEnd; struct tsf_hydra_inst *pinst; struct tsf_hydra_ibag *pibag, *pibagEnd; struct tsf_hydra_igen *pigen, *pigenEnd; + for (ppgen = hydra->pgens + ppbag->genNdx, ppgenEnd = hydra->pgens + ppbag[1].genNdx; ppgen != ppgenEnd; ppgen++) + { + if (ppgen->genOper == GenKeyRange) { plokey = ppgen->genAmount.range.lo; phikey = ppgen->genAmount.range.hi; continue; } + if (ppgen->genOper == GenVelRange) { plovel = ppgen->genAmount.range.lo; phivel = ppgen->genAmount.range.hi; continue; } + if (ppgen->genOper != GenInstrument) continue; + if (ppgen->genAmount.wordAmount >= hydra->instNum) continue; + pinst = hydra->insts + ppgen->genAmount.wordAmount; + for (pibag = hydra->ibags + pinst->instBagNdx, pibagEnd = hydra->ibags + pinst[1].instBagNdx; pibag != pibagEnd; pibag++) + { + unsigned char ilokey = 0, ihikey = 127, ilovel = 0, ihivel = 127; + for (pigen = hydra->igens + pibag->instGenNdx, pigenEnd = hydra->igens + pibag[1].instGenNdx; pigen != pigenEnd; pigen++) + { + if (pigen->genOper == GenKeyRange) { ilokey = pigen->genAmount.range.lo; ihikey = pigen->genAmount.range.hi; continue; } + if (pigen->genOper == GenVelRange) { ilovel = pigen->genAmount.range.lo; ihivel = pigen->genAmount.range.hi; continue; } + if (pigen->genOper == GenSampleID && ihikey >= plokey && ilokey <= phikey && ihivel >= plovel && ilovel <= phivel) preset->regionNum++; + } + } + } + } + + preset->regions = (struct tsf_region*)TSF_MALLOC(preset->regionNum * sizeof(struct tsf_region)); + if (!preset->regions) + { + int i; for (i = 0; i != res->presetNum; i++) TSF_FREE(res->presets[i].regions); + TSF_FREE(res->presets); + return 0; + } + tsf_region_clear(&globalRegion, TSF_TRUE); + + // Zones. + for (ppbag = hydra->pbags + pphdr->presetBagNdx, ppbagEnd = hydra->pbags + pphdr[1].presetBagNdx; ppbag != ppbagEnd; ppbag++) + { + struct tsf_hydra_pgen *ppgen, *ppgenEnd; struct tsf_hydra_inst *pinst; struct tsf_hydra_ibag *pibag, *pibagEnd; struct tsf_hydra_igen *pigen, *pigenEnd; + struct tsf_region presetRegion = globalRegion; + int hadGenInstrument = 0; + + // Generators. + for (ppgen = hydra->pgens + ppbag->genNdx, ppgenEnd = hydra->pgens + ppbag[1].genNdx; ppgen != ppgenEnd; ppgen++) + { + // Instrument. + if (ppgen->genOper == GenInstrument) + { + struct tsf_region instRegion; + tsf_u16 whichInst = ppgen->genAmount.wordAmount; + if (whichInst >= hydra->instNum) continue; + + tsf_region_clear(&instRegion, TSF_FALSE); + pinst = &hydra->insts[whichInst]; + for (pibag = hydra->ibags + pinst->instBagNdx, pibagEnd = hydra->ibags + pinst[1].instBagNdx; pibag != pibagEnd; pibag++) + { + // Generators. + struct tsf_region zoneRegion = instRegion; + int hadSampleID = 0; + for (pigen = hydra->igens + pibag->instGenNdx, pigenEnd = hydra->igens + pibag[1].instGenNdx; pigen != pigenEnd; pigen++) + { + if (pigen->genOper == GenSampleID) + { + struct tsf_hydra_shdr* pshdr; + + //preset region key and vel ranges are a filter for the zone regions + if (zoneRegion.hikey < presetRegion.lokey || zoneRegion.lokey > presetRegion.hikey) continue; + if (zoneRegion.hivel < presetRegion.lovel || zoneRegion.lovel > presetRegion.hivel) continue; + if (presetRegion.lokey > zoneRegion.lokey) zoneRegion.lokey = presetRegion.lokey; + if (presetRegion.hikey < zoneRegion.hikey) zoneRegion.hikey = presetRegion.hikey; + if (presetRegion.lovel > zoneRegion.lovel) zoneRegion.lovel = presetRegion.lovel; + if (presetRegion.hivel < zoneRegion.hivel) zoneRegion.hivel = presetRegion.hivel; + + //sum regions + tsf_region_operator(&zoneRegion, 0, TSF_NULL, &presetRegion); + + // EG times need to be converted from timecents to seconds. + tsf_region_envtosecs(&zoneRegion.ampenv, TSF_TRUE); + tsf_region_envtosecs(&zoneRegion.modenv, TSF_FALSE); + + // LFO times need to be converted from timecents to seconds. + zoneRegion.delayModLFO = (zoneRegion.delayModLFO < -11950.0f ? 0.0f : tsf_timecents2Secsf(zoneRegion.delayModLFO)); + zoneRegion.delayVibLFO = (zoneRegion.delayVibLFO < -11950.0f ? 0.0f : tsf_timecents2Secsf(zoneRegion.delayVibLFO)); + + // Fixup sample positions + pshdr = &hydra->shdrs[pigen->genAmount.wordAmount]; + zoneRegion.offset += pshdr->start; + zoneRegion.end += pshdr->end; + zoneRegion.loop_start += pshdr->startLoop; + zoneRegion.loop_end += pshdr->endLoop; + if (pshdr->endLoop > 0) zoneRegion.loop_end -= 1; + if (zoneRegion.loop_end > fontSampleCount) zoneRegion.loop_end = fontSampleCount; + if (zoneRegion.pitch_keycenter == -1) zoneRegion.pitch_keycenter = pshdr->originalPitch; + zoneRegion.tune += pshdr->pitchCorrection; + zoneRegion.sample_rate = pshdr->sampleRate; + if (zoneRegion.end && zoneRegion.end < fontSampleCount) zoneRegion.end++; + else zoneRegion.end = fontSampleCount; + + preset->regions[region_index] = zoneRegion; + region_index++; + hadSampleID = 1; + } + else tsf_region_operator(&zoneRegion, pigen->genOper, &pigen->genAmount, TSF_NULL); + } + + // Handle instrument's global zone. + if (pibag == hydra->ibags + pinst->instBagNdx && !hadSampleID) + instRegion = zoneRegion; + + // Modulators (TODO) + //if (ibag->instModNdx < ibag[1].instModNdx) addUnsupportedOpcode("any modulator"); + } + hadGenInstrument = 1; + } + else tsf_region_operator(&presetRegion, ppgen->genOper, &ppgen->genAmount, TSF_NULL); + } + + // Modulators (TODO) + //if (pbag->modNdx < pbag[1].modNdx) addUnsupportedOpcode("any modulator"); + + // Handle preset's global zone. + if (ppbag == hydra->pbags + pphdr->presetBagNdx && !hadGenInstrument) + globalRegion = presetRegion; + } + } + return 1; +} + +#ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H +static int tsf_decode_ogg(const tsf_u8 *pSmpl, const tsf_u8 *pSmplEnd, float** pRes, tsf_u32* pResNum, tsf_u32* pResMax, tsf_u32 resInitial) +{ + float *res = *pRes, *oldres; tsf_u32 resNum = *pResNum; tsf_u32 resMax = *pResMax; stb_vorbis *v; + + // Use whatever stb_vorbis API that is available (either pull or push) + #if !defined(STB_VORBIS_NO_PULLDATA_API) && !defined(STB_VORBIS_NO_FROMMEMORY) + v = stb_vorbis_open_memory(pSmpl, (int)(pSmplEnd - pSmpl), TSF_NULL, TSF_NULL); + #else + { int use, err; v = stb_vorbis_open_pushdata(pSmpl, (int)(pSmplEnd - pSmpl), &use, &err, TSF_NULL); pSmpl += use; } + #endif + if (v == TSF_NULL) return 0; + + for (;;) + { + float** outputs; int n_samples; + + // Decode one frame of vorbis samples with whatever stb_vorbis API that is available + #if !defined(STB_VORBIS_NO_PULLDATA_API) && !defined(STB_VORBIS_NO_FROMMEMORY) + n_samples = stb_vorbis_get_frame_float(v, TSF_NULL, &outputs); + if (!n_samples) break; + #else + if (pSmpl >= pSmplEnd) break; + { int use = stb_vorbis_decode_frame_pushdata(v, pSmpl, (int)(pSmplEnd - pSmpl), TSF_NULL, &outputs, &n_samples); pSmpl += use; } + if (!n_samples) continue; + #endif + + // Expand our output buffer if necessary then copy over the decoded frame samples + resNum += n_samples; + if (resNum > resMax) + { + do { resMax += (resMax ? (resMax < 1048576 ? resMax : 1048576) : resInitial); } while (resNum > resMax); + res = (float*)TSF_REALLOC((oldres = res), resMax * sizeof(float)); + if (!res) { TSF_FREE(oldres); stb_vorbis_close(v); return 0; } + } + TSF_MEMCPY(res + resNum - n_samples, outputs[0], n_samples * sizeof(float)); + } + stb_vorbis_close(v); + *pRes = res; *pResNum = resNum; *pResMax = resMax; + return 1; +} + +static int tsf_decode_sf3_samples(const void* rawBuffer, float** pFloatBuffer, unsigned int* pSmplCount, struct tsf_hydra *hydra) +{ + const tsf_u8* smplBuffer = (const tsf_u8*)rawBuffer; + tsf_u32 smplLength = *pSmplCount, resNum = 0, resMax = 0, resInitial = (smplLength > 0x100000 ? (smplLength & ~0xFFFFF) : 65536); + float *res = TSF_NULL, *oldres; + int i, shdrLast = hydra->shdrNum - 1, is_sf3 = 0; + for (i = 0; i <= shdrLast; i++) + { + struct tsf_hydra_shdr *shdr = &hydra->shdrs[i]; + if (shdr->sampleType & 0x30) // compression flags (sometimes Vorbis flag) + { + const tsf_u8 *pSmpl = smplBuffer + shdr->start, *pSmplEnd = smplBuffer + shdr->end; + if (pSmpl + 4 > pSmplEnd || !TSF_FourCCEquals(pSmpl, "OggS")) + { + shdr->start = shdr->end = shdr->startLoop = shdr->endLoop = 0; + continue; + } + + // Fix up sample indices in shdr (end index is set after decoding) + shdr->start = resNum; + shdr->startLoop += resNum; + shdr->endLoop += resNum; + if (!tsf_decode_ogg(pSmpl, pSmplEnd, &res, &resNum, &resMax, resInitial)) { TSF_FREE(res); return 0; } + shdr->end = resNum; + is_sf3 = 1; + } + else // raw PCM sample + { + float *out; short *in = (short*)smplBuffer + resNum, *inEnd; tsf_u32 oldResNum = resNum; + if (is_sf3) // Fix up sample indices in shdr + { + tsf_u32 fix_offset = resNum - shdr->start; + in -= fix_offset; + shdr->start = resNum; + shdr->end += fix_offset; + shdr->startLoop += fix_offset; + shdr->endLoop += fix_offset; + } + inEnd = in + ((shdr->end >= shdr->endLoop ? shdr->end : shdr->endLoop) - resNum); + if (i == shdrLast || (tsf_u8*)inEnd > (smplBuffer + smplLength)) inEnd = (short*)(smplBuffer + smplLength); + if (inEnd <= in) continue; + + // expand our output buffer if necessary then convert the PCM data from short to float + resNum += (tsf_u32)(inEnd - in); + if (resNum > resMax) + { + do { resMax += (resMax ? (resMax < 1048576 ? resMax : 1048576) : resInitial); } while (resNum > resMax); + res = (float*)TSF_REALLOC((oldres = res), resMax * sizeof(float)); + if (!res) { TSF_FREE(oldres); return 0; } + } + + // Convert the samples from short to float + for (out = res + oldResNum; in < inEnd;) + *(out++) = (float)(*(in++) / 32767.0); + } + } + + // Trim the sample buffer down then return success (unless out of memory) + if (!(*pFloatBuffer = (float*)TSF_REALLOC(res, resNum * sizeof(float)))) *pFloatBuffer = res; + *pFloatBuffer = res; + *pSmplCount = resNum; + return (res ? 1 : 0); +} +#endif + +static int tsf_load_samples(void** pRawBuffer, float** pFloatBuffer, unsigned int* pSmplCount, struct tsf_riffchunk *chunkSmpl, struct tsf_stream* stream) +{ + #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H + // With OGG Vorbis support we cannot pre-allocate the memory for tsf_decode_sf3_samples + tsf_u32 resNum, resMax; float* oldres; + *pSmplCount = chunkSmpl->size; + *pRawBuffer = (void*)TSF_MALLOC(*pSmplCount); + if (!*pRawBuffer || !stream->read(stream->data, *pRawBuffer, chunkSmpl->size)) return 0; + if (chunkSmpl->id[3] != 'o') return 1; + + // Decode custom .sfo 'smpo' format where all samples are in a single ogg stream + resNum = resMax = 0; + if (!tsf_decode_ogg((tsf_u8*)*pRawBuffer, (tsf_u8*)*pRawBuffer + chunkSmpl->size, pFloatBuffer, &resNum, &resMax, 65536)) return 0; + if (!(*pFloatBuffer = (float*)TSF_REALLOC((oldres = *pFloatBuffer), resNum * sizeof(float)))) *pFloatBuffer = oldres; + *pSmplCount = resNum; + return (*pFloatBuffer ? 1 : 0); + #else + // Inline convert the samples from short to float + float *res, *out; const short *in; + *pSmplCount = chunkSmpl->size / (unsigned int)sizeof(short); + *pFloatBuffer = (float*)TSF_MALLOC(*pSmplCount * sizeof(float)); + if (!*pFloatBuffer || !stream->read(stream->data, *pFloatBuffer, chunkSmpl->size)) return 0; + for (res = *pFloatBuffer, out = res + *pSmplCount, in = (short*)res + *pSmplCount; out != res;) + *(--out) = (float)(*(--in) / 32767.0); + return 1; + #endif +} + +static int tsf_voice_envelope_release_samples(struct tsf_voice_envelope* e, float outSampleRate) +{ + return (int)((e->parameters.release <= 0 ? TSF_FASTRELEASETIME : e->parameters.release) * outSampleRate); +} + +static void tsf_voice_envelope_nextsegment(struct tsf_voice_envelope* e, short active_segment, float outSampleRate) +{ + switch (active_segment) + { + case TSF_SEGMENT_NONE: + e->samplesUntilNextSegment = (int)(e->parameters.delay * outSampleRate); + if (e->samplesUntilNextSegment > 0) + { + e->segment = TSF_SEGMENT_DELAY; + e->segmentIsExponential = TSF_FALSE; + e->level = 0.0; + e->slope = 0.0; + return; + } + /* fall through */ + case TSF_SEGMENT_DELAY: + e->samplesUntilNextSegment = (int)(e->parameters.attack * outSampleRate); + if (e->samplesUntilNextSegment > 0) + { + if (!e->isAmpEnv) + { + //mod env attack duration scales with velocity (velocity of 1 is full duration, max velocity is 0.125 times duration) + e->samplesUntilNextSegment = (int)(e->parameters.attack * ((145 - e->midiVelocity) / 144.0f) * outSampleRate); + } + e->segment = TSF_SEGMENT_ATTACK; + e->segmentIsExponential = TSF_FALSE; + e->level = 0.0f; + e->slope = 1.0f / e->samplesUntilNextSegment; + return; + } + /* fall through */ + case TSF_SEGMENT_ATTACK: + e->samplesUntilNextSegment = (int)(e->parameters.hold * outSampleRate); + if (e->samplesUntilNextSegment > 0) + { + e->segment = TSF_SEGMENT_HOLD; + e->segmentIsExponential = TSF_FALSE; + e->level = 1.0f; + e->slope = 0.0f; + return; + } + /* fall through */ + case TSF_SEGMENT_HOLD: + e->samplesUntilNextSegment = (int)(e->parameters.decay * outSampleRate); + if (e->samplesUntilNextSegment > 0) + { + e->segment = TSF_SEGMENT_DECAY; + e->level = 1.0f; + if (e->isAmpEnv) + { + // I don't truly understand this; just following what LinuxSampler does. + float mysterySlope = -9.226f / e->samplesUntilNextSegment; + e->slope = TSF_EXPF(mysterySlope); + e->segmentIsExponential = TSF_TRUE; + if (e->parameters.sustain > 0.0f) + { + // Again, this is following LinuxSampler's example, which is similar to + // SF2-style decay, where "decay" specifies the time it would take to + // get to zero, not to the sustain level. The SFZ spec is not that + // specific about what "decay" means, so perhaps it's really supposed + // to specify the time to reach the sustain level. + e->samplesUntilNextSegment = (int)(TSF_LOG(e->parameters.sustain) / mysterySlope); + } + } + else + { + e->slope = -1.0f / e->samplesUntilNextSegment; + e->samplesUntilNextSegment = (int)(e->parameters.decay * (1.0f - e->parameters.sustain) * outSampleRate); + e->segmentIsExponential = TSF_FALSE; + } + return; + } + /* fall through */ + case TSF_SEGMENT_DECAY: + e->segment = TSF_SEGMENT_SUSTAIN; + e->level = e->parameters.sustain; + e->slope = 0.0f; + e->samplesUntilNextSegment = 0x7FFFFFFF; + e->segmentIsExponential = TSF_FALSE; + return; + case TSF_SEGMENT_SUSTAIN: + e->segment = TSF_SEGMENT_RELEASE; + e->samplesUntilNextSegment = tsf_voice_envelope_release_samples(e, outSampleRate); + if (e->isAmpEnv) + { + // I don't truly understand this; just following what LinuxSampler does. + float mysterySlope = -9.226f / e->samplesUntilNextSegment; + e->slope = TSF_EXPF(mysterySlope); + e->segmentIsExponential = TSF_TRUE; + } + else + { + e->slope = -e->level / e->samplesUntilNextSegment; + e->segmentIsExponential = TSF_FALSE; + } + return; + case TSF_SEGMENT_RELEASE: + default: + e->segment = TSF_SEGMENT_DONE; + e->segmentIsExponential = TSF_FALSE; + e->level = e->slope = 0.0f; + e->samplesUntilNextSegment = 0x7FFFFFF; + } +} + +static void tsf_voice_envelope_setup(struct tsf_voice_envelope* e, struct tsf_envelope* new_parameters, int midiNoteNumber, short midiVelocity, TSF_BOOL isAmpEnv, float outSampleRate) +{ + e->parameters = *new_parameters; + if (e->parameters.keynumToHold) + { + e->parameters.hold += e->parameters.keynumToHold * (60.0f - midiNoteNumber); + e->parameters.hold = (e->parameters.hold < -10000.0f ? 0.0f : tsf_timecents2Secsf(e->parameters.hold)); + } + if (e->parameters.keynumToDecay) + { + e->parameters.decay += e->parameters.keynumToDecay * (60.0f - midiNoteNumber); + e->parameters.decay = (e->parameters.decay < -10000.0f ? 0.0f : tsf_timecents2Secsf(e->parameters.decay)); + } + e->midiVelocity = midiVelocity; + e->isAmpEnv = isAmpEnv; + tsf_voice_envelope_nextsegment(e, TSF_SEGMENT_NONE, outSampleRate); +} + +static void tsf_voice_envelope_process(struct tsf_voice_envelope* e, int numSamples, float outSampleRate) +{ + if (e->slope) + { + if (e->segmentIsExponential) e->level *= TSF_POWF(e->slope, (float)numSamples); + else e->level += (e->slope * numSamples); + } + if ((e->samplesUntilNextSegment -= numSamples) <= 0) + tsf_voice_envelope_nextsegment(e, e->segment, outSampleRate); +} + +static void tsf_voice_lowpass_setup(struct tsf_voice_lowpass* e, float Fc) +{ + // Lowpass filter from http://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ + double K = TSF_TAN(TSF_PI * Fc), KK = K * K; + double norm = 1 / (1 + K * e->QInv + KK); + e->a0 = KK * norm; + e->a1 = 2 * e->a0; + e->b1 = 2 * (KK - 1) * norm; + e->b2 = (1 - K * e->QInv + KK) * norm; +} + +static float tsf_voice_lowpass_process(struct tsf_voice_lowpass* e, double In) +{ + double Out = In * e->a0 + e->z1; e->z1 = In * e->a1 + e->z2 - e->b1 * Out; e->z2 = In * e->a0 - e->b2 * Out; return (float)Out; +} + +static void tsf_voice_lfo_setup(struct tsf_voice_lfo* e, float delay, int freqCents, float outSampleRate) +{ + e->samplesUntil = (int)(delay * outSampleRate); + e->delta = (4.0f * tsf_cents2Hertz((float)freqCents) / outSampleRate); + e->level = 0; +} + +static void tsf_voice_lfo_process(struct tsf_voice_lfo* e, int blockSamples) +{ + if (e->samplesUntil > blockSamples) { e->samplesUntil -= blockSamples; return; } + e->level += e->delta * blockSamples; + if (e->level > 1.0f) { e->delta = -e->delta; e->level = 2.0f - e->level; } + else if (e->level < -1.0f) { e->delta = -e->delta; e->level = -2.0f - e->level; } +} + +static void tsf_voice_kill(struct tsf_voice* v) +{ + v->playingPreset = -1; +} + +static void tsf_voice_end(tsf* f, struct tsf_voice* v) +{ + // if maxVoiceNum is set, assume that voice rendering and note queuing are on separate threads + // so to minimize the chance that voice rendering would advance the segment at the same time + // we just do it twice here and hope that it sticks + int repeats = (f->maxVoiceNum ? 2 : 1); + while (repeats--) + { + tsf_voice_envelope_nextsegment(&v->ampenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate); + tsf_voice_envelope_nextsegment(&v->modenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate); + if (v->region->loop_mode == TSF_LOOPMODE_SUSTAIN) + { + // Continue playing, but stop looping. + v->loopEnd = v->loopStart; + } + } +} + +static void tsf_voice_endquick(tsf* f, struct tsf_voice* v) +{ + // if maxVoiceNum is set, assume that voice rendering and note queuing are on separate threads + // so to minimize the chance that voice rendering would advance the segment at the same time + // we just do it twice here and hope that it sticks + int repeats = (f->maxVoiceNum ? 2 : 1); + while (repeats--) + { + v->ampenv.parameters.release = 0.0f; tsf_voice_envelope_nextsegment(&v->ampenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate); + v->modenv.parameters.release = 0.0f; tsf_voice_envelope_nextsegment(&v->modenv, TSF_SEGMENT_SUSTAIN, f->outSampleRate); + } +} + +static void tsf_voice_calcpitchratio(struct tsf_voice* v, float pitchShift, float outSampleRate) +{ + double note = v->playingKey + v->region->transpose + v->region->tune / 100.0; + double adjustedPitch = v->region->pitch_keycenter + (note - v->region->pitch_keycenter) * (v->region->pitch_keytrack / 100.0); + if (pitchShift) adjustedPitch += pitchShift; + v->pitchInputTimecents = adjustedPitch * 100.0; + v->pitchOutputFactor = v->region->sample_rate / (tsf_timecents2Secsd(v->region->pitch_keycenter * 100.0) * outSampleRate); +} + +static void tsf_voice_render(tsf* f, struct tsf_voice* v, float* outputBuffer, int numSamples) +{ + struct tsf_region* region = v->region; + float* input = f->fontSamples; + float* outL = outputBuffer; + float* outR = (f->outputmode == TSF_STEREO_UNWEAVED ? outL + numSamples : TSF_NULL); + + // Cache some values, to give them at least some chance of ending up in registers. + TSF_BOOL updateModEnv = (region->modEnvToPitch || region->modEnvToFilterFc); + TSF_BOOL updateModLFO = (v->modlfo.delta && (region->modLfoToPitch || region->modLfoToFilterFc || region->modLfoToVolume)); + TSF_BOOL updateVibLFO = (v->viblfo.delta && (region->vibLfoToPitch)); + TSF_BOOL isLooping = (v->loopStart < v->loopEnd); + unsigned int tmpLoopStart = v->loopStart, tmpLoopEnd = v->loopEnd; + double tmpSampleEndDbl = (double)region->end, tmpLoopEndDbl = (double)tmpLoopEnd + 1.0; + double tmpSourceSamplePosition = v->sourceSamplePosition; + struct tsf_voice_lowpass tmpLowpass = v->lowpass; + + TSF_BOOL dynamicLowpass = (region->modLfoToFilterFc || region->modEnvToFilterFc); + float tmpSampleRate = f->outSampleRate, tmpInitialFilterFc, tmpModLfoToFilterFc, tmpModEnvToFilterFc; + + TSF_BOOL dynamicPitchRatio = (region->modLfoToPitch || region->modEnvToPitch || region->vibLfoToPitch); + double pitchRatio; + float tmpModLfoToPitch, tmpVibLfoToPitch, tmpModEnvToPitch; + + TSF_BOOL dynamicGain = (region->modLfoToVolume != 0); + float noteGain = 0, tmpModLfoToVolume; + + if (dynamicLowpass) tmpInitialFilterFc = (float)region->initialFilterFc, tmpModLfoToFilterFc = (float)region->modLfoToFilterFc, tmpModEnvToFilterFc = (float)region->modEnvToFilterFc; + else tmpInitialFilterFc = 0, tmpModLfoToFilterFc = 0, tmpModEnvToFilterFc = 0; + + if (dynamicPitchRatio) pitchRatio = 0, tmpModLfoToPitch = (float)region->modLfoToPitch, tmpVibLfoToPitch = (float)region->vibLfoToPitch, tmpModEnvToPitch = (float)region->modEnvToPitch; + else pitchRatio = tsf_timecents2Secsd(v->pitchInputTimecents) * v->pitchOutputFactor, tmpModLfoToPitch = 0, tmpVibLfoToPitch = 0, tmpModEnvToPitch = 0; + + if (dynamicGain) tmpModLfoToVolume = (float)region->modLfoToVolume * 0.1f; + else noteGain = tsf_decibelsToGain(v->noteGainDB), tmpModLfoToVolume = 0; + + while (numSamples) + { + float gainMono, gainLeft, gainRight; + int blockSamples = (numSamples > TSF_RENDER_EFFECTSAMPLEBLOCK ? TSF_RENDER_EFFECTSAMPLEBLOCK : numSamples); + numSamples -= blockSamples; + + if (dynamicLowpass) + { + float fres = tmpInitialFilterFc + v->modlfo.level * tmpModLfoToFilterFc + v->modenv.level * tmpModEnvToFilterFc; + float lowpassFc = (fres <= 13500 ? tsf_cents2Hertz(fres) / tmpSampleRate : 1.0f); + tmpLowpass.active = (lowpassFc < 0.499f); + if (tmpLowpass.active) tsf_voice_lowpass_setup(&tmpLowpass, lowpassFc); + } + + if (dynamicPitchRatio) + pitchRatio = tsf_timecents2Secsd(v->pitchInputTimecents + (v->modlfo.level * tmpModLfoToPitch + v->viblfo.level * tmpVibLfoToPitch + v->modenv.level * tmpModEnvToPitch)) * v->pitchOutputFactor; + + if (dynamicGain) + noteGain = tsf_decibelsToGain(v->noteGainDB + (v->modlfo.level * tmpModLfoToVolume)); + + gainMono = noteGain * v->ampenv.level; + + // Update EG. + tsf_voice_envelope_process(&v->ampenv, blockSamples, tmpSampleRate); + if (updateModEnv) tsf_voice_envelope_process(&v->modenv, blockSamples, tmpSampleRate); + + // Update LFOs. + if (updateModLFO) tsf_voice_lfo_process(&v->modlfo, blockSamples); + if (updateVibLFO) tsf_voice_lfo_process(&v->viblfo, blockSamples); + + switch (f->outputmode) + { + case TSF_STEREO_INTERLEAVED: + gainLeft = gainMono * v->panFactorLeft, gainRight = gainMono * v->panFactorRight; + while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl) + { + unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1); + + // Simple linear interpolation. + float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha); + + // Low-pass filter. + if (tmpLowpass.active) val = tsf_voice_lowpass_process(&tmpLowpass, val); + + *outL++ += val * gainLeft; + *outL++ += val * gainRight; + + // Next sample. + tmpSourceSamplePosition += pitchRatio; + if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0); + } + break; + + case TSF_STEREO_UNWEAVED: + gainLeft = gainMono * v->panFactorLeft, gainRight = gainMono * v->panFactorRight; + while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl) + { + unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1); + + // Simple linear interpolation. + float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha); + + // Low-pass filter. + if (tmpLowpass.active) val = tsf_voice_lowpass_process(&tmpLowpass, val); + + *outL++ += val * gainLeft; + *outR++ += val * gainRight; + + // Next sample. + tmpSourceSamplePosition += pitchRatio; + if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0); + } + break; + + case TSF_MONO: + while (blockSamples-- && tmpSourceSamplePosition < tmpSampleEndDbl) + { + unsigned int pos = (unsigned int)tmpSourceSamplePosition, nextPos = (pos >= tmpLoopEnd && isLooping ? tmpLoopStart : pos + 1); + + // Simple linear interpolation. + float alpha = (float)(tmpSourceSamplePosition - pos), val = (input[pos] * (1.0f - alpha) + input[nextPos] * alpha); + + // Low-pass filter. + if (tmpLowpass.active) val = tsf_voice_lowpass_process(&tmpLowpass, val); + + *outL++ += val * gainMono; + + // Next sample. + tmpSourceSamplePosition += pitchRatio; + if (tmpSourceSamplePosition >= tmpLoopEndDbl && isLooping) tmpSourceSamplePosition -= (tmpLoopEnd - tmpLoopStart + 1.0); + } + break; + } + + if (tmpSourceSamplePosition >= tmpSampleEndDbl || v->ampenv.segment == TSF_SEGMENT_DONE) + { + tsf_voice_kill(v); + return; + } + } + + v->sourceSamplePosition = tmpSourceSamplePosition; + if (tmpLowpass.active || dynamicLowpass) v->lowpass = tmpLowpass; +} + +TSFDEF tsf* tsf_load(struct tsf_stream* stream) +{ + tsf* res = TSF_NULL; + struct tsf_riffchunk chunkHead; + struct tsf_riffchunk chunkList; + struct tsf_hydra hydra; + void* rawBuffer = TSF_NULL; + float* floatBuffer = TSF_NULL; + tsf_u32 smplCount = 0; + + if (!tsf_riffchunk_read(TSF_NULL, &chunkHead, stream) || !TSF_FourCCEquals(chunkHead.id, "sfbk")) + { + //if (e) *e = TSF_INVALID_NOSF2HEADER; + return res; + } + + // Read hydra and locate sample data. + TSF_MEMSET(&hydra, 0, sizeof(hydra)); + while (tsf_riffchunk_read(&chunkHead, &chunkList, stream)) + { + struct tsf_riffchunk chunk; + if (TSF_FourCCEquals(chunkList.id, "pdta")) + { + while (tsf_riffchunk_read(&chunkList, &chunk, stream)) + { + #define HandleChunk(chunkName) (TSF_FourCCEquals(chunk.id, #chunkName) && !(chunk.size % chunkName##SizeInFile)) \ + { \ + int num = chunk.size / chunkName##SizeInFile, i; \ + hydra.chunkName##Num = num; \ + hydra.chunkName##s = (struct tsf_hydra_##chunkName*)TSF_MALLOC(num * sizeof(struct tsf_hydra_##chunkName)); \ + if (!hydra.chunkName##s) goto out_of_memory; \ + for (i = 0; i < num; ++i) tsf_hydra_read_##chunkName(&hydra.chunkName##s[i], stream); \ + } + enum + { + phdrSizeInFile = 38, pbagSizeInFile = 4, pmodSizeInFile = 10, + pgenSizeInFile = 4, instSizeInFile = 22, ibagSizeInFile = 4, + imodSizeInFile = 10, igenSizeInFile = 4, shdrSizeInFile = 46 + }; + if HandleChunk(phdr) else if HandleChunk(pbag) else if HandleChunk(pmod) + else if HandleChunk(pgen) else if HandleChunk(inst) else if HandleChunk(ibag) + else if HandleChunk(imod) else if HandleChunk(igen) else if HandleChunk(shdr) + else stream->skip(stream->data, chunk.size); + #undef HandleChunk + } + } + else if (TSF_FourCCEquals(chunkList.id, "sdta")) + { + while (tsf_riffchunk_read(&chunkList, &chunk, stream)) + { + if ((TSF_FourCCEquals(chunk.id, "smpl") + #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H + || TSF_FourCCEquals(chunk.id, "smpo") + #endif + ) && !rawBuffer && !floatBuffer && chunk.size >= sizeof(short)) + { + if (!tsf_load_samples(&rawBuffer, &floatBuffer, &smplCount, &chunk, stream)) goto out_of_memory; + } + else stream->skip(stream->data, chunk.size); + } + } + else stream->skip(stream->data, chunkList.size); + } + if (!hydra.phdrs || !hydra.pbags || !hydra.pmods || !hydra.pgens || !hydra.insts || !hydra.ibags || !hydra.imods || !hydra.igens || !hydra.shdrs) + { + //if (e) *e = TSF_INVALID_INCOMPLETE; + } + else if (!rawBuffer && !floatBuffer) + { + //if (e) *e = TSF_INVALID_NOSAMPLEDATA; + } + else + { + #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H + if (!floatBuffer && !tsf_decode_sf3_samples(rawBuffer, &floatBuffer, &smplCount, &hydra)) goto out_of_memory; + #endif + res = (tsf*)TSF_MALLOC(sizeof(tsf)); + if (res) TSF_MEMSET(res, 0, sizeof(tsf)); + if (!res || !tsf_load_presets(res, &hydra, smplCount)) goto out_of_memory; + res->outSampleRate = 44100.0f; + res->fontSamples = floatBuffer; + floatBuffer = TSF_NULL; // don't free below + } + if (0) + { + out_of_memory: + TSF_FREE(res); + res = TSF_NULL; + //if (e) *e = TSF_OUT_OF_MEMORY; + } + TSF_FREE(hydra.phdrs); TSF_FREE(hydra.pbags); TSF_FREE(hydra.pmods); + TSF_FREE(hydra.pgens); TSF_FREE(hydra.insts); TSF_FREE(hydra.ibags); + TSF_FREE(hydra.imods); TSF_FREE(hydra.igens); TSF_FREE(hydra.shdrs); + TSF_FREE(rawBuffer); TSF_FREE(floatBuffer); + return res; +} + +TSFDEF tsf* tsf_copy(tsf* f) +{ + tsf* res; + if (!f) return TSF_NULL; + if (!f->refCount) + { + f->refCount = (int*)TSF_MALLOC(sizeof(int)); + if (!f->refCount) return TSF_NULL; + *f->refCount = 1; + } + res = (tsf*)TSF_MALLOC(sizeof(tsf)); + if (!res) return TSF_NULL; + TSF_MEMCPY(res, f, sizeof(tsf)); + res->voices = TSF_NULL; + res->voiceNum = 0; + res->channels = TSF_NULL; + (*res->refCount)++; + return res; +} + +TSFDEF void tsf_close(tsf* f) +{ + if (!f) return; + if (!f->refCount || !--(*f->refCount)) + { + struct tsf_preset *preset = f->presets, *presetEnd = preset + f->presetNum; + for (; preset != presetEnd; preset++) TSF_FREE(preset->regions); + TSF_FREE(f->presets); + TSF_FREE(f->fontSamples); + TSF_FREE(f->refCount); + } + TSF_FREE(f->channels); + TSF_FREE(f->voices); + TSF_FREE(f); +} + +TSFDEF void tsf_reset(tsf* f) +{ + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; + for (; v != vEnd; v++) + if (v->playingPreset != -1 && (v->ampenv.segment < TSF_SEGMENT_RELEASE || v->ampenv.parameters.release)) + tsf_voice_endquick(f, v); + if (f->channels) { TSF_FREE(f->channels); f->channels = TSF_NULL; } +} + +TSFDEF int tsf_get_presetindex(const tsf* f, int bank, int preset_number) +{ + const struct tsf_preset *presets; + int i, iMax; + for (presets = f->presets, i = 0, iMax = f->presetNum; i < iMax; i++) + if (presets[i].preset == preset_number && presets[i].bank == bank) + return i; + return -1; +} + +TSFDEF int tsf_get_presetcount(const tsf* f) +{ + return f->presetNum; +} + +TSFDEF const char* tsf_get_presetname(const tsf* f, int preset) +{ + return (preset < 0 || preset >= f->presetNum ? TSF_NULL : f->presets[preset].presetName); +} + +TSFDEF const char* tsf_bank_get_presetname(const tsf* f, int bank, int preset_number) +{ + return tsf_get_presetname(f, tsf_get_presetindex(f, bank, preset_number)); +} + +TSFDEF void tsf_set_output(tsf* f, enum TSFOutputMode outputmode, int samplerate, float global_gain_db) +{ + f->outputmode = outputmode; + f->outSampleRate = (float)(samplerate >= 1 ? samplerate : 44100.0f); + f->globalGainDB = global_gain_db; +} + +TSFDEF void tsf_set_volume(tsf* f, float global_volume) +{ + f->globalGainDB = (global_volume == 1.0f ? 0 : -tsf_gainToDecibels(1.0f / global_volume)); +} + +TSFDEF int tsf_set_max_voices(tsf* f, int max_voices) +{ + int i = f->voiceNum; + int newVoiceNum = (f->voiceNum > max_voices ? f->voiceNum : max_voices); + struct tsf_voice *newVoices = (struct tsf_voice*)TSF_REALLOC(f->voices, newVoiceNum * sizeof(struct tsf_voice)); + if (!newVoices) return 0; + f->voices = newVoices; + f->voiceNum = f->maxVoiceNum = newVoiceNum; + for (; i < max_voices; i++) + f->voices[i].playingPreset = -1; + return 1; +} + +TSFDEF int tsf_note_on(tsf* f, int preset_index, int key, float vel) +{ + short midiVelocity = (short)(vel * 127); + int voicePlayIndex; + struct tsf_region *region, *regionEnd; + + if (preset_index < 0 || preset_index >= f->presetNum) return 1; + if (vel <= 0.0f) { tsf_note_off(f, preset_index, key); return 1; } + + // Play all matching regions. + voicePlayIndex = f->voicePlayIndex++; + for (region = f->presets[preset_index].regions, regionEnd = region + f->presets[preset_index].regionNum; region != regionEnd; region++) + { + struct tsf_voice *voice, *v, *vEnd; TSF_BOOL doLoop; float lowpassFilterQDB, lowpassFc; + if (key < region->lokey || key > region->hikey || midiVelocity < region->lovel || midiVelocity > region->hivel) continue; + + voice = TSF_NULL, v = f->voices, vEnd = v + f->voiceNum; + if (region->group) + { + for (; v != vEnd; v++) + if (v->playingPreset == preset_index && v->region->group == region->group) tsf_voice_endquick(f, v); + else if (v->playingPreset == -1 && !voice) voice = v; + } + else for (; v != vEnd; v++) if (v->playingPreset == -1) { voice = v; break; } + + if (!voice) + { + if (f->maxVoiceNum) + { + // Voices have been pre-allocated and limited to a maximum, try to kill a voice off in its release envelope + int bestKillReleaseSamplePos = -999999999; + for (v = f->voices; v != vEnd; v++) + { + if (v->ampenv.segment == TSF_SEGMENT_RELEASE) + { + // We're looking for the voice furthest into its release + int releaseSamplesDone = tsf_voice_envelope_release_samples(&v->ampenv, f->outSampleRate) - v->ampenv.samplesUntilNextSegment; + if (releaseSamplesDone > bestKillReleaseSamplePos) + { + bestKillReleaseSamplePos = releaseSamplesDone; + voice = v; + } + } + } + if (!voice) + continue; + tsf_voice_kill(voice); + } + else + { + // Allocate more voices so we don't need to kill one off. + struct tsf_voice* newVoices; + f->voiceNum += 4; + newVoices = (struct tsf_voice*)TSF_REALLOC(f->voices, f->voiceNum * sizeof(struct tsf_voice)); + if (!newVoices) return 0; + f->voices = newVoices; + voice = &f->voices[f->voiceNum - 4]; + voice[1].playingPreset = voice[2].playingPreset = voice[3].playingPreset = -1; + } + } + + voice->region = region; + voice->playingPreset = preset_index; + voice->playingKey = key; + voice->playIndex = voicePlayIndex; + voice->noteGainDB = f->globalGainDB - region->attenuation - tsf_gainToDecibels(1.0f / vel); + + if (f->channels) + { + f->channels->setupVoice(f, voice); + } + else + { + tsf_voice_calcpitchratio(voice, 0, f->outSampleRate); + // The SFZ spec is silent about the pan curve, but a 3dB pan law seems common. This sqrt() curve matches what Dimension LE does; Alchemy Free seems closer to sin(adjustedPan * pi/2). + voice->panFactorLeft = TSF_SQRTF(0.5f - region->pan); + voice->panFactorRight = TSF_SQRTF(0.5f + region->pan); + } + + // Offset/end. + voice->sourceSamplePosition = region->offset; + + // Loop. + doLoop = (region->loop_mode != TSF_LOOPMODE_NONE && region->loop_start < region->loop_end); + voice->loopStart = (doLoop ? region->loop_start : 0); + voice->loopEnd = (doLoop ? region->loop_end : 0); + + // Setup envelopes. + tsf_voice_envelope_setup(&voice->ampenv, ®ion->ampenv, key, midiVelocity, TSF_TRUE, f->outSampleRate); + tsf_voice_envelope_setup(&voice->modenv, ®ion->modenv, key, midiVelocity, TSF_FALSE, f->outSampleRate); + + // Setup lowpass filter. + lowpassFc = (region->initialFilterFc <= 13500 ? tsf_cents2Hertz((float)region->initialFilterFc) / f->outSampleRate : 1.0f); + lowpassFilterQDB = region->initialFilterQ / 10.0f; + voice->lowpass.QInv = 1.0 / TSF_POW(10.0, (lowpassFilterQDB / 20.0)); + voice->lowpass.z1 = voice->lowpass.z2 = 0; + voice->lowpass.active = (lowpassFc < 0.499f); + if (voice->lowpass.active) tsf_voice_lowpass_setup(&voice->lowpass, lowpassFc); + + // Setup LFO filters. + tsf_voice_lfo_setup(&voice->modlfo, region->delayModLFO, region->freqModLFO, f->outSampleRate); + tsf_voice_lfo_setup(&voice->viblfo, region->delayVibLFO, region->freqVibLFO, f->outSampleRate); + } + return 1; +} + +TSFDEF int tsf_bank_note_on(tsf* f, int bank, int preset_number, int key, float vel) +{ + int preset_index = tsf_get_presetindex(f, bank, preset_number); + if (preset_index == -1) return 0; + return tsf_note_on(f, preset_index, key, vel); +} + +TSFDEF void tsf_note_off(tsf* f, int preset_index, int key) +{ + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum, *vMatchFirst = TSF_NULL, *vMatchLast = TSF_NULL; + for (; v != vEnd; v++) + { + //Find the first and last entry in the voices list with matching preset, key and look up the smallest play index + if (v->playingPreset != preset_index || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE) continue; + else if (!vMatchFirst || v->playIndex < vMatchFirst->playIndex) vMatchFirst = vMatchLast = v; + else if (v->playIndex == vMatchFirst->playIndex) vMatchLast = v; + } + if (!vMatchFirst) return; + for (v = vMatchFirst; v <= vMatchLast; v++) + { + //Stop all voices with matching preset, key and the smallest play index which was enumerated above + if (v != vMatchFirst && v != vMatchLast && + (v->playIndex != vMatchFirst->playIndex || v->playingPreset != preset_index || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE)) continue; + tsf_voice_end(f, v); + } +} + +TSFDEF int tsf_bank_note_off(tsf* f, int bank, int preset_number, int key) +{ + int preset_index = tsf_get_presetindex(f, bank, preset_number); + if (preset_index == -1) return 0; + tsf_note_off(f, preset_index, key); + return 1; +} + +TSFDEF void tsf_note_off_all(tsf* f) +{ + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; + for (; v != vEnd; v++) if (v->playingPreset != -1 && v->ampenv.segment < TSF_SEGMENT_RELEASE) + tsf_voice_end(f, v); +} + +TSFDEF int tsf_active_voice_count(tsf* f) +{ + int count = 0; + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; + for (; v != vEnd; v++) if (v->playingPreset != -1) count++; + return count; +} + +TSFDEF void tsf_render_short(tsf* f, short* buffer, int samples, int flag_mixing) +{ + float outputSamples[TSF_RENDER_SHORTBUFFERBLOCK]; + int channels = (f->outputmode == TSF_MONO ? 1 : 2), maxChannelSamples = TSF_RENDER_SHORTBUFFERBLOCK / channels; + while (samples > 0) + { + int channelSamples = (samples > maxChannelSamples ? maxChannelSamples : samples); + short* bufferEnd = buffer + channelSamples * channels; + float *floatSamples = outputSamples; + tsf_render_float(f, floatSamples, channelSamples, TSF_FALSE); + samples -= channelSamples; + + if (flag_mixing) + while (buffer != bufferEnd) + { + float v = *floatSamples++; + int vi = *buffer + (v < -1.00004566f ? (int)-32768 : (v > 1.00001514f ? (int)32767 : (int)(v * 32767.5f))); + *buffer++ = (vi < -32768 ? (short)-32768 : (vi > 32767 ? (short)32767 : (short)vi)); + } + else + while (buffer != bufferEnd) + { + float v = *floatSamples++; + *buffer++ = (v < -1.00004566f ? (short)-32768 : (v > 1.00001514f ? (short)32767 : (short)(v * 32767.5f))); + } + } +} + +TSFDEF void tsf_render_float(tsf* f, float* buffer, int samples, int flag_mixing) +{ + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; + if (!flag_mixing) TSF_MEMSET(buffer, 0, (f->outputmode == TSF_MONO ? 1 : 2) * sizeof(float) * samples); + for (; v != vEnd; v++) + if (v->playingPreset != -1) + tsf_voice_render(f, v, buffer, samples); +} + +static void tsf_channel_setup_voice(tsf* f, struct tsf_voice* v) +{ + struct tsf_channel* c = &f->channels->channels[f->channels->activeChannel]; + float newpan = v->region->pan + c->panOffset; + v->playingChannel = f->channels->activeChannel; + v->noteGainDB += c->gainDB; + tsf_voice_calcpitchratio(v, (c->pitchWheel == 8192 ? c->tuning : ((c->pitchWheel / 16383.0f * c->pitchRange * 2.0f) - c->pitchRange + c->tuning)), f->outSampleRate); + if (newpan <= -0.5f) { v->panFactorLeft = 1.0f; v->panFactorRight = 0.0f; } + else if (newpan >= 0.5f) { v->panFactorLeft = 0.0f; v->panFactorRight = 1.0f; } + else { v->panFactorLeft = TSF_SQRTF(0.5f - newpan); v->panFactorRight = TSF_SQRTF(0.5f + newpan); } +} + +static struct tsf_channel* tsf_channel_init(tsf* f, int channel) +{ + int i; + if (f->channels && channel < f->channels->channelNum) return &f->channels->channels[channel]; + if (!f->channels) + { + f->channels = (struct tsf_channels*)TSF_MALLOC(sizeof(struct tsf_channels) + sizeof(struct tsf_channel) * channel); + if (!f->channels) return TSF_NULL; + f->channels->setupVoice = &tsf_channel_setup_voice; + f->channels->channelNum = 0; + f->channels->activeChannel = 0; + } + else + { + struct tsf_channels *newChannels = (struct tsf_channels*)TSF_REALLOC(f->channels, sizeof(struct tsf_channels) + sizeof(struct tsf_channel) * channel); + if (!newChannels) return TSF_NULL; + f->channels = newChannels; + } + i = f->channels->channelNum; + f->channels->channelNum = channel + 1; + for (; i <= channel; i++) + { + struct tsf_channel* c = &f->channels->channels[i]; + c->presetIndex = c->bank = 0; + c->pitchWheel = c->midiPan = 8192; + c->midiVolume = c->midiExpression = 16383; + c->midiRPN = 0xFFFF; + c->midiData = 0; + c->panOffset = 0.0f; + c->gainDB = 0.0f; + c->pitchRange = 2.0f; + c->tuning = 0.0f; + } + return &f->channels->channels[channel]; +} + +static void tsf_channel_applypitch(tsf* f, int channel, struct tsf_channel* c) +{ + struct tsf_voice *v, *vEnd; + float pitchShift = (c->pitchWheel == 8192 ? c->tuning : ((c->pitchWheel / 16383.0f * c->pitchRange * 2.0f) - c->pitchRange + c->tuning)); + for (v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++) + if (v->playingPreset != -1 && v->playingChannel == channel) + tsf_voice_calcpitchratio(v, pitchShift, f->outSampleRate); +} + +TSFDEF int tsf_channel_set_presetindex(tsf* f, int channel, int preset_index) +{ + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + c->presetIndex = (unsigned short)preset_index; + return 1; +} + +TSFDEF int tsf_channel_set_presetnumber(tsf* f, int channel, int preset_number, int flag_mididrums) +{ + int preset_index; + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + if (flag_mididrums) + { + preset_index = tsf_get_presetindex(f, 128 | (c->bank & 0x7FFF), preset_number); + if (preset_index == -1) preset_index = tsf_get_presetindex(f, 128, preset_number); + if (preset_index == -1) preset_index = tsf_get_presetindex(f, 128, 0); + if (preset_index == -1) preset_index = tsf_get_presetindex(f, (c->bank & 0x7FFF), preset_number); + } + else preset_index = tsf_get_presetindex(f, (c->bank & 0x7FFF), preset_number); + if (preset_index == -1) preset_index = tsf_get_presetindex(f, 0, preset_number); + if (preset_index != -1) + { + c->presetIndex = (unsigned short)preset_index; + return 1; + } + return 0; +} + +TSFDEF int tsf_channel_set_bank(tsf* f, int channel, int bank) +{ + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + c->bank = (unsigned short)bank; + return 1; +} + +TSFDEF int tsf_channel_set_bank_preset(tsf* f, int channel, int bank, int preset_number) +{ + int preset_index; + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + preset_index = tsf_get_presetindex(f, bank, preset_number); + if (preset_index == -1) return 0; + c->presetIndex = (unsigned short)preset_index; + c->bank = (unsigned short)bank; + return 1; +} + +TSFDEF int tsf_channel_set_pan(tsf* f, int channel, float pan) +{ + struct tsf_voice *v, *vEnd; + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + for (v = f->voices, vEnd = v + f->voiceNum; v != vEnd; v++) + if (v->playingChannel == channel && v->playingPreset != -1) + { + float newpan = v->region->pan + pan - 0.5f; + if (newpan <= -0.5f) { v->panFactorLeft = 1.0f; v->panFactorRight = 0.0f; } + else if (newpan >= 0.5f) { v->panFactorLeft = 0.0f; v->panFactorRight = 1.0f; } + else { v->panFactorLeft = TSF_SQRTF(0.5f - newpan); v->panFactorRight = TSF_SQRTF(0.5f + newpan); } + } + c->panOffset = pan - 0.5f; + return 1; +} + +TSFDEF int tsf_channel_set_volume(tsf* f, int channel, float volume) +{ + float gainDB = tsf_gainToDecibels(volume), gainDBChange; + struct tsf_voice *v, *vEnd; + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + if (gainDB == c->gainDB) return 1; + for (v = f->voices, vEnd = v + f->voiceNum, gainDBChange = gainDB - c->gainDB; v != vEnd; v++) + if (v->playingPreset != -1 && v->playingChannel == channel) + v->noteGainDB += gainDBChange; + c->gainDB = gainDB; + return 1; +} + +TSFDEF int tsf_channel_set_pitchwheel(tsf* f, int channel, int pitch_wheel) +{ + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + if (c->pitchWheel == pitch_wheel) return 1; + c->pitchWheel = (unsigned short)pitch_wheel; + tsf_channel_applypitch(f, channel, c); + return 1; +} + +TSFDEF int tsf_channel_set_pitchrange(tsf* f, int channel, float pitch_range) +{ + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + if (c->pitchRange == pitch_range) return 1; + c->pitchRange = pitch_range; + if (c->pitchWheel != 8192) tsf_channel_applypitch(f, channel, c); + return 1; +} + +TSFDEF int tsf_channel_set_tuning(tsf* f, int channel, float tuning) +{ + struct tsf_channel *c = tsf_channel_init(f, channel); + if (!c) return 0; + if (c->tuning == tuning) return 1; + c->tuning = tuning; + tsf_channel_applypitch(f, channel, c); + return 1; +} + +TSFDEF int tsf_channel_note_on(tsf* f, int channel, int key, float vel) +{ + if (!f->channels || channel >= f->channels->channelNum) return 1; + f->channels->activeChannel = channel; + return tsf_note_on(f, f->channels->channels[channel].presetIndex, key, vel); +} + +TSFDEF void tsf_channel_note_off(tsf* f, int channel, int key) +{ + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum, *vMatchFirst = TSF_NULL, *vMatchLast = TSF_NULL; + for (; v != vEnd; v++) + { + //Find the first and last entry in the voices list with matching channel, key and look up the smallest play index + if (v->playingPreset == -1 || v->playingChannel != channel || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE) continue; + else if (!vMatchFirst || v->playIndex < vMatchFirst->playIndex) vMatchFirst = vMatchLast = v; + else if (v->playIndex == vMatchFirst->playIndex) vMatchLast = v; + } + if (!vMatchFirst) return; + for (v = vMatchFirst; v <= vMatchLast; v++) + { + //Stop all voices with matching channel, key and the smallest play index which was enumerated above + if (v != vMatchFirst && v != vMatchLast && + (v->playIndex != vMatchFirst->playIndex || v->playingPreset == -1 || v->playingChannel != channel || v->playingKey != key || v->ampenv.segment >= TSF_SEGMENT_RELEASE)) continue; + tsf_voice_end(f, v); + } +} + +TSFDEF void tsf_channel_note_off_all(tsf* f, int channel) +{ + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; + for (; v != vEnd; v++) + if (v->playingPreset != -1 && v->playingChannel == channel && v->ampenv.segment < TSF_SEGMENT_RELEASE) + tsf_voice_end(f, v); +} + +TSFDEF void tsf_channel_sounds_off_all(tsf* f, int channel) +{ + struct tsf_voice *v = f->voices, *vEnd = v + f->voiceNum; + for (; v != vEnd; v++) + if (v->playingPreset != -1 && v->playingChannel == channel && (v->ampenv.segment < TSF_SEGMENT_RELEASE || v->ampenv.parameters.release)) + tsf_voice_endquick(f, v); +} + +TSFDEF int tsf_channel_midi_control(tsf* f, int channel, int controller, int control_value) +{ + struct tsf_channel* c = tsf_channel_init(f, channel); + if (!c) return 0; + switch (controller) + { + case 7 /*VOLUME_MSB*/ : c->midiVolume = (unsigned short)((c->midiVolume & 0x7F ) | (control_value << 7)); goto TCMC_SET_VOLUME; + case 39 /*VOLUME_LSB*/ : c->midiVolume = (unsigned short)((c->midiVolume & 0x3F80) | control_value); goto TCMC_SET_VOLUME; + case 11 /*EXPRESSION_MSB*/ : c->midiExpression = (unsigned short)((c->midiExpression & 0x7F ) | (control_value << 7)); goto TCMC_SET_VOLUME; + case 43 /*EXPRESSION_LSB*/ : c->midiExpression = (unsigned short)((c->midiExpression & 0x3F80) | control_value); goto TCMC_SET_VOLUME; + case 10 /*PAN_MSB*/ : c->midiPan = (unsigned short)((c->midiPan & 0x7F ) | (control_value << 7)); goto TCMC_SET_PAN; + case 42 /*PAN_LSB*/ : c->midiPan = (unsigned short)((c->midiPan & 0x3F80) | control_value); goto TCMC_SET_PAN; + case 6 /*DATA_ENTRY_MSB*/ : c->midiData = (unsigned short)((c->midiData & 0x7F) | (control_value << 7)); goto TCMC_SET_DATA; + case 38 /*DATA_ENTRY_LSB*/ : c->midiData = (unsigned short)((c->midiData & 0x3F80) | control_value); goto TCMC_SET_DATA; + case 0 /*BANK_SELECT_MSB*/ : c->bank = (unsigned short)(0x8000 | control_value); return 1; //bank select MSB alone acts like LSB + case 32 /*BANK_SELECT_LSB*/ : c->bank = (unsigned short)((c->bank & 0x8000 ? ((c->bank & 0x7F) << 7) : 0) | control_value); return 1; + case 101 /*RPN_MSB*/ : c->midiRPN = (unsigned short)(((c->midiRPN == 0xFFFF ? 0 : c->midiRPN) & 0x7F ) | (control_value << 7)); return 1; + case 100 /*RPN_LSB*/ : c->midiRPN = (unsigned short)(((c->midiRPN == 0xFFFF ? 0 : c->midiRPN) & 0x3F80) | control_value); return 1; + case 98 /*NRPN_LSB*/ : c->midiRPN = 0xFFFF; return 1; + case 99 /*NRPN_MSB*/ : c->midiRPN = 0xFFFF; return 1; + case 120 /*ALL_SOUND_OFF*/ : tsf_channel_sounds_off_all(f, channel); return 1; + case 123 /*ALL_NOTES_OFF*/ : tsf_channel_note_off_all(f, channel); return 1; + case 121 /*ALL_CTRL_OFF*/ : + c->midiVolume = c->midiExpression = 16383; + c->midiPan = 8192; + c->bank = 0; + c->midiRPN = 0xFFFF; + c->midiData = 0; + tsf_channel_set_volume(f, channel, 1.0f); + tsf_channel_set_pan(f, channel, 0.5f); + tsf_channel_set_pitchrange(f, channel, 2.0f); + tsf_channel_set_tuning(f, channel, 0); + return 1; + } + return 1; +TCMC_SET_VOLUME: + //Raising to the power of 3 seems to result in a decent sounding volume curve for MIDI + tsf_channel_set_volume(f, channel, TSF_POWF((c->midiVolume / 16383.0f) * (c->midiExpression / 16383.0f), 3.0f)); + return 1; +TCMC_SET_PAN: + tsf_channel_set_pan(f, channel, c->midiPan / 16383.0f); + return 1; +TCMC_SET_DATA: + if (c->midiRPN == 0) tsf_channel_set_pitchrange(f, channel, (c->midiData >> 7) + 0.01f * (c->midiData & 0x7F)); + else if (c->midiRPN == 1) tsf_channel_set_tuning(f, channel, (int)c->tuning + ((float)c->midiData - 8192.0f) / 8192.0f); //fine tune + else if (c->midiRPN == 2 && controller == 6) tsf_channel_set_tuning(f, channel, ((float)control_value - 64.0f) + (c->tuning - (int)c->tuning)); //coarse tune + return 1; +} + +TSFDEF int tsf_channel_get_preset_index(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].presetIndex : 0); +} + +TSFDEF int tsf_channel_get_preset_bank(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? (f->channels->channels[channel].bank & 0x7FFF) : 0); +} + +TSFDEF int tsf_channel_get_preset_number(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? f->presets[f->channels->channels[channel].presetIndex].preset : 0); +} + +TSFDEF float tsf_channel_get_pan(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].panOffset - 0.5f : 0.5f); +} + +TSFDEF float tsf_channel_get_volume(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? tsf_decibelsToGain(f->channels->channels[channel].gainDB) : 1.0f); +} + +TSFDEF int tsf_channel_get_pitchwheel(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].pitchWheel : 8192); +} + +TSFDEF float tsf_channel_get_pitchrange(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].pitchRange : 2.0f); +} + +TSFDEF float tsf_channel_get_tuning(tsf* f, int channel) +{ + return (f->channels && channel < f->channels->channelNum ? f->channels->channels[channel].tuning : 0.0f); +} + +#ifdef __cplusplus +} +#endif + +#endif //TSF_IMPLEMENTATION diff --git a/source/engine/thirdparty/cgltf b/source/engine/thirdparty/cgltf deleted file mode 160000 index de39988..0000000 --- a/source/engine/thirdparty/cgltf +++ /dev/null @@ -1 +0,0 @@ -Subproject commit de399881c65c438a635627c749440eeea7e05599 diff --git a/source/engine/thirdparty/cgltf/.github/workflows/build.yml b/source/engine/thirdparty/cgltf/.github/workflows/build.yml new file mode 100644 index 0000000..6e0bd7f --- /dev/null +++ b/source/engine/thirdparty/cgltf/.github/workflows/build.yml @@ -0,0 +1,15 @@ +name: build + +on: [push, pull_request] + +jobs: + generic: + strategy: + matrix: + os: [windows, ubuntu, macos] + name: ${{matrix.os}} + runs-on: ${{matrix.os}}-latest + steps: + - uses: actions/checkout@v1 + - name: test + run: cd test && python test_all.py diff --git a/source/engine/thirdparty/cgltf/LICENSE b/source/engine/thirdparty/cgltf/LICENSE new file mode 100644 index 0000000..599d934 --- /dev/null +++ b/source/engine/thirdparty/cgltf/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2018-2021 Johannes Kuhlmann + +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/source/engine/thirdparty/cgltf/README.md b/source/engine/thirdparty/cgltf/README.md new file mode 100644 index 0000000..ab7d439 --- /dev/null +++ b/source/engine/thirdparty/cgltf/README.md @@ -0,0 +1,161 @@ +# :diamond_shape_with_a_dot_inside: cgltf +**Single-file/stb-style C glTF loader and writer** + +[![Build Status](https://github.com/jkuhlmann/cgltf/workflows/build/badge.svg)](https://github.com/jkuhlmann/cgltf/actions) + +Used in: [bgfx](https://github.com/bkaradzic/bgfx), [Filament](https://github.com/google/filament), [gltfpack](https://github.com/zeux/meshoptimizer/tree/master/gltf), [raylib](https://github.com/raysan5/raylib), [Unigine](https://developer.unigine.com/en/docs/2.14.1/third_party?rlang=cpp#cgltf), and more! + +## Usage: Loading +Loading from file: +```c +#define CGLTF_IMPLEMENTATION +#include "cgltf.h" + +cgltf_options options = {0}; +cgltf_data* data = NULL; +cgltf_result result = cgltf_parse_file(&options, "scene.gltf", &data); +if (result == cgltf_result_success) +{ + /* TODO make awesome stuff */ + cgltf_free(data); +} +``` + +Loading from memory: +```c +#define CGLTF_IMPLEMENTATION +#include "cgltf.h" + +void* buf; /* Pointer to glb or gltf file data */ +size_t size; /* Size of the file data */ + +cgltf_options options = {0}; +cgltf_data* data = NULL; +cgltf_result result = cgltf_parse(&options, buf, size, &data); +if (result == cgltf_result_success) +{ + /* TODO make awesome stuff */ + cgltf_free(data); +} +``` + +Note that cgltf does not load the contents of extra files such as buffers or images into memory by default. You'll need to read these files yourself using URIs from `data.buffers[]` or `data.images[]` respectively. +For buffer data, you can alternatively call `cgltf_load_buffers`, which will use `FILE*` APIs to open and read buffer files. This automatically decodes base64 data URIs in buffers. For data URIs in images, you will need to use `cgltf_load_buffer_base64`. + +**For more in-depth documentation and a description of the public interface refer to the top of the `cgltf.h` file.** + +## Usage: Writing +When writing glTF data, you need a valid `cgltf_data` structure that represents a valid glTF document. You can construct such a structure yourself or load it using the loader functions described above. The writer functions do not deallocate any memory. So, you either have to do it manually or call `cgltf_free()` if you got the data by loading it from a glTF document. + +Writing to file: +```c +#define CGLTF_IMPLEMENTATION +#define CGLTF_WRITE_IMPLEMENTATION +#include "cgltf_write.h" + +cgltf_options options = {0}; +cgltf_data* data = /* TODO must be valid data */; +cgltf_result result = cgltf_write_file(&options, "out.gltf", data); +if (result != cgltf_result_success) +{ + /* TODO handle error */ +} +``` + +Writing to memory: +```c +#define CGLTF_IMPLEMENTATION +#define CGLTF_WRITE_IMPLEMENTATION +#include "cgltf_write.h" +cgltf_options options = {0}; +cgltf_data* data = /* TODO must be valid data */; + +cgltf_size size = cgltf_write(&options, NULL, 0, data); + +char* buf = malloc(size); + +cgltf_size written = cgltf_write(&options, buf, size, data); +if (written != size) +{ + /* TODO handle error */ +} +``` + +Note that cgltf does not write the contents of extra files such as buffers or images. You'll need to write this data yourself. + +**For more in-depth documentation and a description of the public interface refer to the top of the `cgltf_write.h` file.** + + +## Features +cgltf supports core glTF 2.0: +- glb (binary files) and gltf (JSON files) +- meshes (including accessors, buffer views, buffers) +- materials (including textures, samplers, images) +- scenes and nodes +- skins +- animations +- cameras +- morph targets +- extras data + +cgltf also supports some glTF extensions: +- EXT_mesh_gpu_instancing +- EXT_meshopt_compression +- KHR_draco_mesh_compression (requires a library like [Google's Draco](https://github.com/google/draco) for decompression though) +- KHR_lights_punctual +- KHR_materials_clearcoat +- KHR_materials_emissive_strength +- KHR_materials_ior +- KHR_materials_iridescence +- KHR_materials_pbrSpecularGlossiness +- KHR_materials_sheen +- KHR_materials_specular +- KHR_materials_transmission +- KHR_materials_unlit +- KHR_materials_variants +- KHR_materials_volume +- KHR_texture_basisu (requires a library like [Binomial Basisu](https://github.com/BinomialLLC/basis_universal) for transcoding to native compressed texture) +- KHR_texture_transform + +cgltf does **not** yet support unlisted extensions. However, unlisted extensions can be accessed via "extensions" member on objects. + +## Building +The easiest approach is to integrate the `cgltf.h` header file into your project. If you are unfamiliar with single-file C libraries (also known as stb-style libraries), this is how it goes: + +1. Include `cgltf.h` where you need the functionality. +1. Have exactly one source file that defines `CGLTF_IMPLEMENTATION` before including `cgltf.h`. +1. Use the cgltf functions as described above. + +Support for writing can be found in a separate file called `cgltf_write.h` (which includes `cgltf.h`). Building it works analogously using the `CGLTF_WRITE_IMPLEMENTATION` define. + +## Contributing +Everyone is welcome to contribute to the library. If you find any problems, you can submit them using [GitHub's issue system](https://github.com/jkuhlmann/cgltf/issues). If you want to contribute code, you should fork the project and then send a pull request. + + +## Dependencies +None. + +C headers being used by the implementation: +``` +#include +#include +#include +#include +#include +#include +#include // If asserts are enabled. +``` + +Note, this library has a copy of the [JSMN JSON parser](https://github.com/zserge/jsmn) embedded in its source. + +## Testing +There is a Python script in the `test/` folder that retrieves the glTF 2.0 sample files from the glTF-Sample-Models repository (https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0) and runs the library against all gltf and glb files. + +Here's one way to build and run the test: + + cd test ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Debug + make -j + cd .. + ./test_all.py + +There is also a llvm-fuzz test in `fuzz/`. See http://llvm.org/docs/LibFuzzer.html for more information. diff --git a/source/engine/thirdparty/cgltf/cgltf.h b/source/engine/thirdparty/cgltf/cgltf.h new file mode 100644 index 0000000..95b56ce --- /dev/null +++ b/source/engine/thirdparty/cgltf/cgltf.h @@ -0,0 +1,6739 @@ +/** + * cgltf - a single-file glTF 2.0 parser written in C99. + * + * Version: 1.13 + * + * Website: https://github.com/jkuhlmann/cgltf + * + * Distributed under the MIT License, see notice at the end of this file. + * + * Building: + * Include this file where you need the struct and function + * declarations. Have exactly one source file where you define + * `CGLTF_IMPLEMENTATION` before including this file to get the + * function definitions. + * + * Reference: + * `cgltf_result cgltf_parse(const cgltf_options*, const void*, + * cgltf_size, cgltf_data**)` parses both glTF and GLB data. If + * this function returns `cgltf_result_success`, you have to call + * `cgltf_free()` on the created `cgltf_data*` variable. + * Note that contents of external files for buffers and images are not + * automatically loaded. You'll need to read these files yourself using + * URIs in the `cgltf_data` structure. + * + * `cgltf_options` is the struct passed to `cgltf_parse()` to control + * parts of the parsing process. You can use it to force the file type + * and provide memory allocation as well as file operation callbacks. + * Should be zero-initialized to trigger default behavior. + * + * `cgltf_data` is the struct allocated and filled by `cgltf_parse()`. + * It generally mirrors the glTF format as described by the spec (see + * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0). + * + * `void cgltf_free(cgltf_data*)` frees the allocated `cgltf_data` + * variable. + * + * `cgltf_result cgltf_load_buffers(const cgltf_options*, cgltf_data*, + * const char* gltf_path)` can be optionally called to open and read buffer + * files using the `FILE*` APIs. The `gltf_path` argument is the path to + * the original glTF file, which allows the parser to resolve the path to + * buffer files. + * + * `cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, + * cgltf_size size, const char* base64, void** out_data)` decodes + * base64-encoded data content. Used internally by `cgltf_load_buffers()`. + * This is useful when decoding data URIs in images. + * + * `cgltf_result cgltf_parse_file(const cgltf_options* options, const + * char* path, cgltf_data** out_data)` can be used to open the given + * file using `FILE*` APIs and parse the data using `cgltf_parse()`. + * + * `cgltf_result cgltf_validate(cgltf_data*)` can be used to do additional + * checks to make sure the parsed glTF data is valid. + * + * `cgltf_node_transform_local` converts the translation / rotation / scale properties of a node + * into a mat4. + * + * `cgltf_node_transform_world` calls `cgltf_node_transform_local` on every ancestor in order + * to compute the root-to-node transformation. + * + * `cgltf_accessor_unpack_floats` reads in the data from an accessor, applies sparse data (if any), + * and converts them to floating point. Assumes that `cgltf_load_buffers` has already been called. + * By passing null for the output pointer, users can find out how many floats are required in the + * output buffer. + * + * `cgltf_num_components` is a tiny utility that tells you the dimensionality of + * a certain accessor type. This can be used before `cgltf_accessor_unpack_floats` to help allocate + * the necessary amount of memory. + * + * `cgltf_accessor_read_float` reads a certain element from a non-sparse accessor and converts it to + * floating point, assuming that `cgltf_load_buffers` has already been called. The passed-in element + * size is the number of floats in the output buffer, which should be in the range [1, 16]. Returns + * false if the passed-in element_size is too small, or if the accessor is sparse. + * + * `cgltf_accessor_read_uint` is similar to its floating-point counterpart, but limited to reading + * vector types and does not support matrix types. The passed-in element size is the number of uints + * in the output buffer, which should be in the range [1, 4]. Returns false if the passed-in + * element_size is too small, or if the accessor is sparse. + * + * `cgltf_accessor_read_index` is similar to its floating-point counterpart, but it returns size_t + * and only works with single-component data types. + * + * `cgltf_result cgltf_copy_extras_json(const cgltf_data*, const cgltf_extras*, + * char* dest, cgltf_size* dest_size)` allows users to retrieve the "extras" data that + * can be attached to many glTF objects (which can be arbitrary JSON data). The + * `cgltf_extras` struct stores the offsets of the start and end of the extras JSON data + * as it appears in the complete glTF JSON data. This function copies the extras data + * into the provided buffer. If `dest` is NULL, the length of the data is written into + * `dest_size`. You can then parse this data using your own JSON parser + * or, if you've included the cgltf implementation using the integrated JSMN JSON parser. + */ +#ifndef CGLTF_H_INCLUDED__ +#define CGLTF_H_INCLUDED__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef size_t cgltf_size; +typedef long long int cgltf_ssize; +typedef float cgltf_float; +typedef int cgltf_int; +typedef unsigned int cgltf_uint; +typedef int cgltf_bool; + +typedef enum cgltf_file_type +{ + cgltf_file_type_invalid, + cgltf_file_type_gltf, + cgltf_file_type_glb, + cgltf_file_type_max_enum +} cgltf_file_type; + +typedef enum cgltf_result +{ + cgltf_result_success, + cgltf_result_data_too_short, + cgltf_result_unknown_format, + cgltf_result_invalid_json, + cgltf_result_invalid_gltf, + cgltf_result_invalid_options, + cgltf_result_file_not_found, + cgltf_result_io_error, + cgltf_result_out_of_memory, + cgltf_result_legacy_gltf, + cgltf_result_max_enum +} cgltf_result; + +typedef struct cgltf_memory_options +{ + void* (*alloc_func)(void* user, cgltf_size size); + void (*free_func) (void* user, void* ptr); + void* user_data; +} cgltf_memory_options; + +typedef struct cgltf_file_options +{ + cgltf_result(*read)(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, const char* path, cgltf_size* size, void** data); + void (*release)(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data); + void* user_data; +} cgltf_file_options; + +typedef struct cgltf_options +{ + cgltf_file_type type; /* invalid == auto detect */ + cgltf_size json_token_count; /* 0 == auto */ + cgltf_memory_options memory; + cgltf_file_options file; +} cgltf_options; + +typedef enum cgltf_buffer_view_type +{ + cgltf_buffer_view_type_invalid, + cgltf_buffer_view_type_indices, + cgltf_buffer_view_type_vertices, + cgltf_buffer_view_type_max_enum +} cgltf_buffer_view_type; + +typedef enum cgltf_attribute_type +{ + cgltf_attribute_type_invalid, + cgltf_attribute_type_position, + cgltf_attribute_type_normal, + cgltf_attribute_type_tangent, + cgltf_attribute_type_texcoord, + cgltf_attribute_type_color, + cgltf_attribute_type_joints, + cgltf_attribute_type_weights, + cgltf_attribute_type_custom, + cgltf_attribute_type_max_enum +} cgltf_attribute_type; + +typedef enum cgltf_component_type +{ + cgltf_component_type_invalid, + cgltf_component_type_r_8, /* BYTE */ + cgltf_component_type_r_8u, /* UNSIGNED_BYTE */ + cgltf_component_type_r_16, /* SHORT */ + cgltf_component_type_r_16u, /* UNSIGNED_SHORT */ + cgltf_component_type_r_32u, /* UNSIGNED_INT */ + cgltf_component_type_r_32f, /* FLOAT */ + cgltf_component_type_max_enum +} cgltf_component_type; + +typedef enum cgltf_type +{ + cgltf_type_invalid, + cgltf_type_scalar, + cgltf_type_vec2, + cgltf_type_vec3, + cgltf_type_vec4, + cgltf_type_mat2, + cgltf_type_mat3, + cgltf_type_mat4, + cgltf_type_max_enum +} cgltf_type; + +typedef enum cgltf_primitive_type +{ + cgltf_primitive_type_points, + cgltf_primitive_type_lines, + cgltf_primitive_type_line_loop, + cgltf_primitive_type_line_strip, + cgltf_primitive_type_triangles, + cgltf_primitive_type_triangle_strip, + cgltf_primitive_type_triangle_fan, + cgltf_primitive_type_max_enum +} cgltf_primitive_type; + +typedef enum cgltf_alpha_mode +{ + cgltf_alpha_mode_opaque, + cgltf_alpha_mode_mask, + cgltf_alpha_mode_blend, + cgltf_alpha_mode_max_enum +} cgltf_alpha_mode; + +typedef enum cgltf_animation_path_type { + cgltf_animation_path_type_invalid, + cgltf_animation_path_type_translation, + cgltf_animation_path_type_rotation, + cgltf_animation_path_type_scale, + cgltf_animation_path_type_weights, + cgltf_animation_path_type_max_enum +} cgltf_animation_path_type; + +typedef enum cgltf_interpolation_type { + cgltf_interpolation_type_linear, + cgltf_interpolation_type_step, + cgltf_interpolation_type_cubic_spline, + cgltf_interpolation_type_max_enum +} cgltf_interpolation_type; + +typedef enum cgltf_camera_type { + cgltf_camera_type_invalid, + cgltf_camera_type_perspective, + cgltf_camera_type_orthographic, + cgltf_camera_type_max_enum +} cgltf_camera_type; + +typedef enum cgltf_light_type { + cgltf_light_type_invalid, + cgltf_light_type_directional, + cgltf_light_type_point, + cgltf_light_type_spot, + cgltf_light_type_max_enum +} cgltf_light_type; + +typedef enum cgltf_data_free_method { + cgltf_data_free_method_none, + cgltf_data_free_method_file_release, + cgltf_data_free_method_memory_free, + cgltf_data_free_method_max_enum +} cgltf_data_free_method; + +typedef struct cgltf_extras { + cgltf_size start_offset; + cgltf_size end_offset; +} cgltf_extras; + +typedef struct cgltf_extension { + char* name; + char* data; +} cgltf_extension; + +typedef struct cgltf_buffer +{ + char* name; + cgltf_size size; + char* uri; + void* data; /* loaded by cgltf_load_buffers */ + cgltf_data_free_method data_free_method; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_buffer; + +typedef enum cgltf_meshopt_compression_mode { + cgltf_meshopt_compression_mode_invalid, + cgltf_meshopt_compression_mode_attributes, + cgltf_meshopt_compression_mode_triangles, + cgltf_meshopt_compression_mode_indices, + cgltf_meshopt_compression_mode_max_enum +} cgltf_meshopt_compression_mode; + +typedef enum cgltf_meshopt_compression_filter { + cgltf_meshopt_compression_filter_none, + cgltf_meshopt_compression_filter_octahedral, + cgltf_meshopt_compression_filter_quaternion, + cgltf_meshopt_compression_filter_exponential, + cgltf_meshopt_compression_filter_max_enum +} cgltf_meshopt_compression_filter; + +typedef struct cgltf_meshopt_compression +{ + cgltf_buffer* buffer; + cgltf_size offset; + cgltf_size size; + cgltf_size stride; + cgltf_size count; + cgltf_meshopt_compression_mode mode; + cgltf_meshopt_compression_filter filter; +} cgltf_meshopt_compression; + +typedef struct cgltf_buffer_view +{ + char *name; + cgltf_buffer* buffer; + cgltf_size offset; + cgltf_size size; + cgltf_size stride; /* 0 == automatically determined by accessor */ + cgltf_buffer_view_type type; + void* data; /* overrides buffer->data if present, filled by extensions */ + cgltf_bool has_meshopt_compression; + cgltf_meshopt_compression meshopt_compression; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_buffer_view; + +typedef struct cgltf_accessor_sparse +{ + cgltf_size count; + cgltf_buffer_view* indices_buffer_view; + cgltf_size indices_byte_offset; + cgltf_component_type indices_component_type; + cgltf_buffer_view* values_buffer_view; + cgltf_size values_byte_offset; + cgltf_extras extras; + cgltf_extras indices_extras; + cgltf_extras values_extras; + cgltf_size extensions_count; + cgltf_extension* extensions; + cgltf_size indices_extensions_count; + cgltf_extension* indices_extensions; + cgltf_size values_extensions_count; + cgltf_extension* values_extensions; +} cgltf_accessor_sparse; + +typedef struct cgltf_accessor +{ + char* name; + cgltf_component_type component_type; + cgltf_bool normalized; + cgltf_type type; + cgltf_size offset; + cgltf_size count; + cgltf_size stride; + cgltf_buffer_view* buffer_view; + cgltf_bool has_min; + cgltf_float min[16]; + cgltf_bool has_max; + cgltf_float max[16]; + cgltf_bool is_sparse; + cgltf_accessor_sparse sparse; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_accessor; + +typedef struct cgltf_attribute +{ + char* name; + cgltf_attribute_type type; + cgltf_int index; + cgltf_accessor* data; +} cgltf_attribute; + +typedef struct cgltf_image +{ + char* name; + char* uri; + cgltf_buffer_view* buffer_view; + char* mime_type; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_image; + +typedef struct cgltf_sampler +{ + char* name; + cgltf_int mag_filter; + cgltf_int min_filter; + cgltf_int wrap_s; + cgltf_int wrap_t; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_sampler; + +typedef struct cgltf_texture +{ + char* name; + cgltf_image* image; + cgltf_sampler* sampler; + cgltf_bool has_basisu; + cgltf_image* basisu_image; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_texture; + +typedef struct cgltf_texture_transform +{ + cgltf_float offset[2]; + cgltf_float rotation; + cgltf_float scale[2]; + cgltf_bool has_texcoord; + cgltf_int texcoord; +} cgltf_texture_transform; + +typedef struct cgltf_texture_view +{ + cgltf_texture* texture; + cgltf_int texcoord; + cgltf_float scale; /* equivalent to strength for occlusion_texture */ + cgltf_bool has_transform; + cgltf_texture_transform transform; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_texture_view; + +typedef struct cgltf_pbr_metallic_roughness +{ + cgltf_texture_view base_color_texture; + cgltf_texture_view metallic_roughness_texture; + + cgltf_float base_color_factor[4]; + cgltf_float metallic_factor; + cgltf_float roughness_factor; + + cgltf_extras extras; +} cgltf_pbr_metallic_roughness; + +typedef struct cgltf_pbr_specular_glossiness +{ + cgltf_texture_view diffuse_texture; + cgltf_texture_view specular_glossiness_texture; + + cgltf_float diffuse_factor[4]; + cgltf_float specular_factor[3]; + cgltf_float glossiness_factor; +} cgltf_pbr_specular_glossiness; + +typedef struct cgltf_clearcoat +{ + cgltf_texture_view clearcoat_texture; + cgltf_texture_view clearcoat_roughness_texture; + cgltf_texture_view clearcoat_normal_texture; + + cgltf_float clearcoat_factor; + cgltf_float clearcoat_roughness_factor; +} cgltf_clearcoat; + +typedef struct cgltf_transmission +{ + cgltf_texture_view transmission_texture; + cgltf_float transmission_factor; +} cgltf_transmission; + +typedef struct cgltf_ior +{ + cgltf_float ior; +} cgltf_ior; + +typedef struct cgltf_specular +{ + cgltf_texture_view specular_texture; + cgltf_texture_view specular_color_texture; + cgltf_float specular_color_factor[3]; + cgltf_float specular_factor; +} cgltf_specular; + +typedef struct cgltf_volume +{ + cgltf_texture_view thickness_texture; + cgltf_float thickness_factor; + cgltf_float attenuation_color[3]; + cgltf_float attenuation_distance; +} cgltf_volume; + +typedef struct cgltf_sheen +{ + cgltf_texture_view sheen_color_texture; + cgltf_float sheen_color_factor[3]; + cgltf_texture_view sheen_roughness_texture; + cgltf_float sheen_roughness_factor; +} cgltf_sheen; + +typedef struct cgltf_emissive_strength +{ + cgltf_float emissive_strength; +} cgltf_emissive_strength; + +typedef struct cgltf_iridescence +{ + cgltf_float iridescence_factor; + cgltf_texture_view iridescence_texture; + cgltf_float iridescence_ior; + cgltf_float iridescence_thickness_min; + cgltf_float iridescence_thickness_max; + cgltf_texture_view iridescence_thickness_texture; +} cgltf_iridescence; + +typedef struct cgltf_material +{ + char* name; + cgltf_bool has_pbr_metallic_roughness; + cgltf_bool has_pbr_specular_glossiness; + cgltf_bool has_clearcoat; + cgltf_bool has_transmission; + cgltf_bool has_volume; + cgltf_bool has_ior; + cgltf_bool has_specular; + cgltf_bool has_sheen; + cgltf_bool has_emissive_strength; + cgltf_bool has_iridescence; + cgltf_pbr_metallic_roughness pbr_metallic_roughness; + cgltf_pbr_specular_glossiness pbr_specular_glossiness; + cgltf_clearcoat clearcoat; + cgltf_ior ior; + cgltf_specular specular; + cgltf_sheen sheen; + cgltf_transmission transmission; + cgltf_volume volume; + cgltf_emissive_strength emissive_strength; + cgltf_iridescence iridescence; + cgltf_texture_view normal_texture; + cgltf_texture_view occlusion_texture; + cgltf_texture_view emissive_texture; + cgltf_float emissive_factor[3]; + cgltf_alpha_mode alpha_mode; + cgltf_float alpha_cutoff; + cgltf_bool double_sided; + cgltf_bool unlit; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_material; + +typedef struct cgltf_material_mapping +{ + cgltf_size variant; + cgltf_material* material; + cgltf_extras extras; +} cgltf_material_mapping; + +typedef struct cgltf_morph_target { + cgltf_attribute* attributes; + cgltf_size attributes_count; +} cgltf_morph_target; + +typedef struct cgltf_draco_mesh_compression { + cgltf_buffer_view* buffer_view; + cgltf_attribute* attributes; + cgltf_size attributes_count; +} cgltf_draco_mesh_compression; + +typedef struct cgltf_mesh_gpu_instancing { + cgltf_buffer_view* buffer_view; + cgltf_attribute* attributes; + cgltf_size attributes_count; +} cgltf_mesh_gpu_instancing; + +typedef struct cgltf_primitive { + cgltf_primitive_type type; + cgltf_accessor* indices; + cgltf_material* material; + cgltf_attribute* attributes; + cgltf_size attributes_count; + cgltf_morph_target* targets; + cgltf_size targets_count; + cgltf_extras extras; + cgltf_bool has_draco_mesh_compression; + cgltf_draco_mesh_compression draco_mesh_compression; + cgltf_material_mapping* mappings; + cgltf_size mappings_count; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_primitive; + +typedef struct cgltf_mesh { + char* name; + cgltf_primitive* primitives; + cgltf_size primitives_count; + cgltf_float* weights; + cgltf_size weights_count; + char** target_names; + cgltf_size target_names_count; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_mesh; + +typedef struct cgltf_node cgltf_node; + +typedef struct cgltf_skin { + char* name; + cgltf_node** joints; + cgltf_size joints_count; + cgltf_node* skeleton; + cgltf_accessor* inverse_bind_matrices; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_skin; + +typedef struct cgltf_camera_perspective { + cgltf_bool has_aspect_ratio; + cgltf_float aspect_ratio; + cgltf_float yfov; + cgltf_bool has_zfar; + cgltf_float zfar; + cgltf_float znear; + cgltf_extras extras; +} cgltf_camera_perspective; + +typedef struct cgltf_camera_orthographic { + cgltf_float xmag; + cgltf_float ymag; + cgltf_float zfar; + cgltf_float znear; + cgltf_extras extras; +} cgltf_camera_orthographic; + +typedef struct cgltf_camera { + char* name; + cgltf_camera_type type; + union { + cgltf_camera_perspective perspective; + cgltf_camera_orthographic orthographic; + } data; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_camera; + +typedef struct cgltf_light { + char* name; + cgltf_float color[3]; + cgltf_float intensity; + cgltf_light_type type; + cgltf_float range; + cgltf_float spot_inner_cone_angle; + cgltf_float spot_outer_cone_angle; + cgltf_extras extras; +} cgltf_light; + +struct cgltf_node { + char* name; + cgltf_node* parent; + cgltf_node** children; + cgltf_size children_count; + cgltf_skin* skin; + cgltf_mesh* mesh; + cgltf_camera* camera; + cgltf_light* light; + cgltf_float* weights; + cgltf_size weights_count; + cgltf_bool has_translation; + cgltf_bool has_rotation; + cgltf_bool has_scale; + cgltf_bool has_matrix; + cgltf_float translation[3]; + cgltf_float rotation[4]; + cgltf_float scale[3]; + cgltf_float matrix[16]; + cgltf_extras extras; + cgltf_bool has_mesh_gpu_instancing; + cgltf_mesh_gpu_instancing mesh_gpu_instancing; + cgltf_size extensions_count; + cgltf_extension* extensions; +}; + +typedef struct cgltf_scene { + char* name; + cgltf_node** nodes; + cgltf_size nodes_count; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_scene; + +typedef struct cgltf_animation_sampler { + cgltf_accessor* input; + cgltf_accessor* output; + cgltf_interpolation_type interpolation; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_animation_sampler; + +typedef struct cgltf_animation_channel { + cgltf_animation_sampler* sampler; + cgltf_node* target_node; + cgltf_animation_path_type target_path; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_animation_channel; + +typedef struct cgltf_animation { + char* name; + cgltf_animation_sampler* samplers; + cgltf_size samplers_count; + cgltf_animation_channel* channels; + cgltf_size channels_count; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_animation; + +typedef struct cgltf_material_variant +{ + char* name; + cgltf_extras extras; +} cgltf_material_variant; + +typedef struct cgltf_asset { + char* copyright; + char* generator; + char* version; + char* min_version; + cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; +} cgltf_asset; + +typedef struct cgltf_data +{ + cgltf_file_type file_type; + void* file_data; + + cgltf_asset asset; + + cgltf_mesh* meshes; + cgltf_size meshes_count; + + cgltf_material* materials; + cgltf_size materials_count; + + cgltf_accessor* accessors; + cgltf_size accessors_count; + + cgltf_buffer_view* buffer_views; + cgltf_size buffer_views_count; + + cgltf_buffer* buffers; + cgltf_size buffers_count; + + cgltf_image* images; + cgltf_size images_count; + + cgltf_texture* textures; + cgltf_size textures_count; + + cgltf_sampler* samplers; + cgltf_size samplers_count; + + cgltf_skin* skins; + cgltf_size skins_count; + + cgltf_camera* cameras; + cgltf_size cameras_count; + + cgltf_light* lights; + cgltf_size lights_count; + + cgltf_node* nodes; + cgltf_size nodes_count; + + cgltf_scene* scenes; + cgltf_size scenes_count; + + cgltf_scene* scene; + + cgltf_animation* animations; + cgltf_size animations_count; + + cgltf_material_variant* variants; + cgltf_size variants_count; + + cgltf_extras extras; + + cgltf_size data_extensions_count; + cgltf_extension* data_extensions; + + char** extensions_used; + cgltf_size extensions_used_count; + + char** extensions_required; + cgltf_size extensions_required_count; + + const char* json; + cgltf_size json_size; + + const void* bin; + cgltf_size bin_size; + + cgltf_memory_options memory; + cgltf_file_options file; +} cgltf_data; + +cgltf_result cgltf_parse( + const cgltf_options* options, + const void* data, + cgltf_size size, + cgltf_data** out_data); + +cgltf_result cgltf_parse_file( + const cgltf_options* options, + const char* path, + cgltf_data** out_data); + +cgltf_result cgltf_load_buffers( + const cgltf_options* options, + cgltf_data* data, + const char* gltf_path); + +cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data); + +cgltf_size cgltf_decode_string(char* string); +cgltf_size cgltf_decode_uri(char* uri); + +cgltf_result cgltf_validate(cgltf_data* data); + +void cgltf_free(cgltf_data* data); + +void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix); +void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix); + +cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size); +cgltf_bool cgltf_accessor_read_uint(const cgltf_accessor* accessor, cgltf_size index, cgltf_uint* out, cgltf_size element_size); +cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index); + +cgltf_size cgltf_num_components(cgltf_type type); + +cgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_float* out, cgltf_size float_count); + +cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size); + +#ifdef __cplusplus +} +#endif + +#endif /* #ifndef CGLTF_H_INCLUDED__ */ + +/* + * + * Stop now, if you are only interested in the API. + * Below, you find the implementation. + * + */ + +#if defined(__INTELLISENSE__) || defined(__JETBRAINS_IDE__) +/* This makes MSVC/CLion intellisense work. */ +#define CGLTF_IMPLEMENTATION +#endif + +#ifdef CGLTF_IMPLEMENTATION + +#include /* For uint8_t, uint32_t */ +#include /* For strncpy */ +#include /* For fopen */ +#include /* For UINT_MAX etc */ +#include /* For FLT_MAX */ + +#if !defined(CGLTF_MALLOC) || !defined(CGLTF_FREE) || !defined(CGLTF_ATOI) || !defined(CGLTF_ATOF) || !defined(CGLTF_ATOLL) +#include /* For malloc, free, atoi, atof */ +#endif + +#if CGLTF_VALIDATE_ENABLE_ASSERTS +#include +#endif + +/* JSMN_PARENT_LINKS is necessary to make parsing large structures linear in input size */ +#define JSMN_PARENT_LINKS + +/* JSMN_STRICT is necessary to reject invalid JSON documents */ +#define JSMN_STRICT + +/* + * -- jsmn.h start -- + * Source: https://github.com/zserge/jsmn + * License: MIT + */ +typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 +} jsmntype_t; +enum jsmnerr { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 +}; +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; +typedef struct { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; +static void jsmn_init(jsmn_parser *parser); +static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens); +/* + * -- jsmn.h end -- + */ + + +static const cgltf_size GlbHeaderSize = 12; +static const cgltf_size GlbChunkHeaderSize = 8; +static const uint32_t GlbVersion = 2; +static const uint32_t GlbMagic = 0x46546C67; +static const uint32_t GlbMagicJsonChunk = 0x4E4F534A; +static const uint32_t GlbMagicBinChunk = 0x004E4942; + +#ifndef CGLTF_MALLOC +#define CGLTF_MALLOC(size) malloc(size) +#endif +#ifndef CGLTF_FREE +#define CGLTF_FREE(ptr) free(ptr) +#endif +#ifndef CGLTF_ATOI +#define CGLTF_ATOI(str) atoi(str) +#endif +#ifndef CGLTF_ATOF +#define CGLTF_ATOF(str) atof(str) +#endif +#ifndef CGLTF_ATOLL +#define CGLTF_ATOLL(str) atoll(str) +#endif +#ifndef CGLTF_VALIDATE_ENABLE_ASSERTS +#define CGLTF_VALIDATE_ENABLE_ASSERTS 0 +#endif + +static void* cgltf_default_alloc(void* user, cgltf_size size) +{ + (void)user; + return CGLTF_MALLOC(size); +} + +static void cgltf_default_free(void* user, void* ptr) +{ + (void)user; + CGLTF_FREE(ptr); +} + +static void* cgltf_calloc(cgltf_options* options, size_t element_size, cgltf_size count) +{ + if (SIZE_MAX / element_size < count) + { + return NULL; + } + void* result = options->memory.alloc_func(options->memory.user_data, element_size * count); + if (!result) + { + return NULL; + } + memset(result, 0, element_size * count); + return result; +} + +static cgltf_result cgltf_default_file_read(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, const char* path, cgltf_size* size, void** data) +{ + (void)file_options; + void* (*memory_alloc)(void*, cgltf_size) = memory_options->alloc_func ? memory_options->alloc_func : &cgltf_default_alloc; + void (*memory_free)(void*, void*) = memory_options->free_func ? memory_options->free_func : &cgltf_default_free; + + FILE* file = fopen(path, "rb"); + if (!file) + { + return cgltf_result_file_not_found; + } + + cgltf_size file_size = size ? *size : 0; + + if (file_size == 0) + { + fseek(file, 0, SEEK_END); + +#ifdef _WIN32 + __int64 length = _ftelli64(file); +#else + long length = ftell(file); +#endif + + if (length < 0) + { + fclose(file); + return cgltf_result_io_error; + } + + fseek(file, 0, SEEK_SET); + file_size = (cgltf_size)length; + } + + char* file_data = (char*)memory_alloc(memory_options->user_data, file_size); + if (!file_data) + { + fclose(file); + return cgltf_result_out_of_memory; + } + + cgltf_size read_size = fread(file_data, 1, file_size, file); + + fclose(file); + + if (read_size != file_size) + { + memory_free(memory_options->user_data, file_data); + return cgltf_result_io_error; + } + + if (size) + { + *size = file_size; + } + if (data) + { + *data = file_data; + } + + return cgltf_result_success; +} + +static void cgltf_default_file_release(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data) +{ + (void)file_options; + void (*memfree)(void*, void*) = memory_options->free_func ? memory_options->free_func : &cgltf_default_free; + memfree(memory_options->user_data, data); +} + +static cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data); + +cgltf_result cgltf_parse(const cgltf_options* options, const void* data, cgltf_size size, cgltf_data** out_data) +{ + if (size < GlbHeaderSize) + { + return cgltf_result_data_too_short; + } + + if (options == NULL) + { + return cgltf_result_invalid_options; + } + + cgltf_options fixed_options = *options; + if (fixed_options.memory.alloc_func == NULL) + { + fixed_options.memory.alloc_func = &cgltf_default_alloc; + } + if (fixed_options.memory.free_func == NULL) + { + fixed_options.memory.free_func = &cgltf_default_free; + } + + uint32_t tmp; + // Magic + memcpy(&tmp, data, 4); + if (tmp != GlbMagic) + { + if (fixed_options.type == cgltf_file_type_invalid) + { + fixed_options.type = cgltf_file_type_gltf; + } + else if (fixed_options.type == cgltf_file_type_glb) + { + return cgltf_result_unknown_format; + } + } + + if (fixed_options.type == cgltf_file_type_gltf) + { + cgltf_result json_result = cgltf_parse_json(&fixed_options, (const uint8_t*)data, size, out_data); + if (json_result != cgltf_result_success) + { + return json_result; + } + + (*out_data)->file_type = cgltf_file_type_gltf; + + return cgltf_result_success; + } + + const uint8_t* ptr = (const uint8_t*)data; + // Version + memcpy(&tmp, ptr + 4, 4); + uint32_t version = tmp; + if (version != GlbVersion) + { + return version < GlbVersion ? cgltf_result_legacy_gltf : cgltf_result_unknown_format; + } + + // Total length + memcpy(&tmp, ptr + 8, 4); + if (tmp > size) + { + return cgltf_result_data_too_short; + } + + const uint8_t* json_chunk = ptr + GlbHeaderSize; + + if (GlbHeaderSize + GlbChunkHeaderSize > size) + { + return cgltf_result_data_too_short; + } + + // JSON chunk: length + uint32_t json_length; + memcpy(&json_length, json_chunk, 4); + if (GlbHeaderSize + GlbChunkHeaderSize + json_length > size) + { + return cgltf_result_data_too_short; + } + + // JSON chunk: magic + memcpy(&tmp, json_chunk + 4, 4); + if (tmp != GlbMagicJsonChunk) + { + return cgltf_result_unknown_format; + } + + json_chunk += GlbChunkHeaderSize; + + const void* bin = 0; + cgltf_size bin_size = 0; + + if (GlbHeaderSize + GlbChunkHeaderSize + json_length + GlbChunkHeaderSize <= size) + { + // We can read another chunk + const uint8_t* bin_chunk = json_chunk + json_length; + + // Bin chunk: length + uint32_t bin_length; + memcpy(&bin_length, bin_chunk, 4); + if (GlbHeaderSize + GlbChunkHeaderSize + json_length + GlbChunkHeaderSize + bin_length > size) + { + return cgltf_result_data_too_short; + } + + // Bin chunk: magic + memcpy(&tmp, bin_chunk + 4, 4); + if (tmp != GlbMagicBinChunk) + { + return cgltf_result_unknown_format; + } + + bin_chunk += GlbChunkHeaderSize; + + bin = bin_chunk; + bin_size = bin_length; + } + + cgltf_result json_result = cgltf_parse_json(&fixed_options, json_chunk, json_length, out_data); + if (json_result != cgltf_result_success) + { + return json_result; + } + + (*out_data)->file_type = cgltf_file_type_glb; + (*out_data)->bin = bin; + (*out_data)->bin_size = bin_size; + + return cgltf_result_success; +} + +cgltf_result cgltf_parse_file(const cgltf_options* options, const char* path, cgltf_data** out_data) +{ + if (options == NULL) + { + return cgltf_result_invalid_options; + } + + cgltf_result (*file_read)(const struct cgltf_memory_options*, const struct cgltf_file_options*, const char*, cgltf_size*, void**) = options->file.read ? options->file.read : &cgltf_default_file_read; + void (*file_release)(const struct cgltf_memory_options*, const struct cgltf_file_options*, void* data) = options->file.release ? options->file.release : cgltf_default_file_release; + + void* file_data = NULL; + cgltf_size file_size = 0; + cgltf_result result = file_read(&options->memory, &options->file, path, &file_size, &file_data); + if (result != cgltf_result_success) + { + return result; + } + + result = cgltf_parse(options, file_data, file_size, out_data); + + if (result != cgltf_result_success) + { + file_release(&options->memory, &options->file, file_data); + return result; + } + + (*out_data)->file_data = file_data; + + return cgltf_result_success; +} + +static void cgltf_combine_paths(char* path, const char* base, const char* uri) +{ + const char* s0 = strrchr(base, '/'); + const char* s1 = strrchr(base, '\\'); + const char* slash = s0 ? (s1 && s1 > s0 ? s1 : s0) : s1; + + if (slash) + { + size_t prefix = slash - base + 1; + + strncpy(path, base, prefix); + strcpy(path + prefix, uri); + } + else + { + strcpy(path, uri); + } +} + +static cgltf_result cgltf_load_buffer_file(const cgltf_options* options, cgltf_size size, const char* uri, const char* gltf_path, void** out_data) +{ + void* (*memory_alloc)(void*, cgltf_size) = options->memory.alloc_func ? options->memory.alloc_func : &cgltf_default_alloc; + void (*memory_free)(void*, void*) = options->memory.free_func ? options->memory.free_func : &cgltf_default_free; + cgltf_result (*file_read)(const struct cgltf_memory_options*, const struct cgltf_file_options*, const char*, cgltf_size*, void**) = options->file.read ? options->file.read : &cgltf_default_file_read; + + char* path = (char*)memory_alloc(options->memory.user_data, strlen(uri) + strlen(gltf_path) + 1); + if (!path) + { + return cgltf_result_out_of_memory; + } + + cgltf_combine_paths(path, gltf_path, uri); + + // after combining, the tail of the resulting path is a uri; decode_uri converts it into path + cgltf_decode_uri(path + strlen(path) - strlen(uri)); + + void* file_data = NULL; + cgltf_result result = file_read(&options->memory, &options->file, path, &size, &file_data); + + memory_free(options->memory.user_data, path); + + *out_data = (result == cgltf_result_success) ? file_data : NULL; + + return result; +} + +cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data) +{ + void* (*memory_alloc)(void*, cgltf_size) = options->memory.alloc_func ? options->memory.alloc_func : &cgltf_default_alloc; + void (*memory_free)(void*, void*) = options->memory.free_func ? options->memory.free_func : &cgltf_default_free; + + unsigned char* data = (unsigned char*)memory_alloc(options->memory.user_data, size); + if (!data) + { + return cgltf_result_out_of_memory; + } + + unsigned int buffer = 0; + unsigned int buffer_bits = 0; + + for (cgltf_size i = 0; i < size; ++i) + { + while (buffer_bits < 8) + { + char ch = *base64++; + + int index = + (unsigned)(ch - 'A') < 26 ? (ch - 'A') : + (unsigned)(ch - 'a') < 26 ? (ch - 'a') + 26 : + (unsigned)(ch - '0') < 10 ? (ch - '0') + 52 : + ch == '+' ? 62 : + ch == '/' ? 63 : + -1; + + if (index < 0) + { + memory_free(options->memory.user_data, data); + return cgltf_result_io_error; + } + + buffer = (buffer << 6) | index; + buffer_bits += 6; + } + + data[i] = (unsigned char)(buffer >> (buffer_bits - 8)); + buffer_bits -= 8; + } + + *out_data = data; + + return cgltf_result_success; +} + +static int cgltf_unhex(char ch) +{ + return + (unsigned)(ch - '0') < 10 ? (ch - '0') : + (unsigned)(ch - 'A') < 6 ? (ch - 'A') + 10 : + (unsigned)(ch - 'a') < 6 ? (ch - 'a') + 10 : + -1; +} + +cgltf_size cgltf_decode_string(char* string) +{ + char* read = string + strcspn(string, "\\"); + if (*read == 0) + { + return read - string; + } + char* write = string; + char* last = string; + + for (;;) + { + // Copy characters since last escaped sequence + cgltf_size written = read - last; + memmove(write, last, written); + write += written; + + if (*read++ == 0) + { + break; + } + + // jsmn already checked that all escape sequences are valid + switch (*read++) + { + case '\"': *write++ = '\"'; break; + case '/': *write++ = '/'; break; + case '\\': *write++ = '\\'; break; + case 'b': *write++ = '\b'; break; + case 'f': *write++ = '\f'; break; + case 'r': *write++ = '\r'; break; + case 'n': *write++ = '\n'; break; + case 't': *write++ = '\t'; break; + case 'u': + { + // UCS-2 codepoint \uXXXX to UTF-8 + int character = 0; + for (cgltf_size i = 0; i < 4; ++i) + { + character = (character << 4) + cgltf_unhex(*read++); + } + + if (character <= 0x7F) + { + *write++ = character & 0xFF; + } + else if (character <= 0x7FF) + { + *write++ = 0xC0 | ((character >> 6) & 0xFF); + *write++ = 0x80 | (character & 0x3F); + } + else + { + *write++ = 0xE0 | ((character >> 12) & 0xFF); + *write++ = 0x80 | ((character >> 6) & 0x3F); + *write++ = 0x80 | (character & 0x3F); + } + break; + } + default: + break; + } + + last = read; + read += strcspn(read, "\\"); + } + + *write = 0; + return write - string; +} + +cgltf_size cgltf_decode_uri(char* uri) +{ + char* write = uri; + char* i = uri; + + while (*i) + { + if (*i == '%') + { + int ch1 = cgltf_unhex(i[1]); + + if (ch1 >= 0) + { + int ch2 = cgltf_unhex(i[2]); + + if (ch2 >= 0) + { + *write++ = (char)(ch1 * 16 + ch2); + i += 3; + continue; + } + } + } + + *write++ = *i++; + } + + *write = 0; + return write - uri; +} + +cgltf_result cgltf_load_buffers(const cgltf_options* options, cgltf_data* data, const char* gltf_path) +{ + if (options == NULL) + { + return cgltf_result_invalid_options; + } + + if (data->buffers_count && data->buffers[0].data == NULL && data->buffers[0].uri == NULL && data->bin) + { + if (data->bin_size < data->buffers[0].size) + { + return cgltf_result_data_too_short; + } + + data->buffers[0].data = (void*)data->bin; + data->buffers[0].data_free_method = cgltf_data_free_method_none; + } + + for (cgltf_size i = 0; i < data->buffers_count; ++i) + { + if (data->buffers[i].data) + { + continue; + } + + const char* uri = data->buffers[i].uri; + + if (uri == NULL) + { + continue; + } + + if (strncmp(uri, "data:", 5) == 0) + { + const char* comma = strchr(uri, ','); + + if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0) + { + cgltf_result res = cgltf_load_buffer_base64(options, data->buffers[i].size, comma + 1, &data->buffers[i].data); + data->buffers[i].data_free_method = cgltf_data_free_method_memory_free; + + if (res != cgltf_result_success) + { + return res; + } + } + else + { + return cgltf_result_unknown_format; + } + } + else if (strstr(uri, "://") == NULL && gltf_path) + { + cgltf_result res = cgltf_load_buffer_file(options, data->buffers[i].size, uri, gltf_path, &data->buffers[i].data); + data->buffers[i].data_free_method = cgltf_data_free_method_file_release; + + if (res != cgltf_result_success) + { + return res; + } + } + else + { + return cgltf_result_unknown_format; + } + } + + return cgltf_result_success; +} + +static cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type); + +static cgltf_size cgltf_calc_index_bound(cgltf_buffer_view* buffer_view, cgltf_size offset, cgltf_component_type component_type, cgltf_size count) +{ + char* data = (char*)buffer_view->buffer->data + offset + buffer_view->offset; + cgltf_size bound = 0; + + switch (component_type) + { + case cgltf_component_type_r_8u: + for (size_t i = 0; i < count; ++i) + { + cgltf_size v = ((unsigned char*)data)[i]; + bound = bound > v ? bound : v; + } + break; + + case cgltf_component_type_r_16u: + for (size_t i = 0; i < count; ++i) + { + cgltf_size v = ((unsigned short*)data)[i]; + bound = bound > v ? bound : v; + } + break; + + case cgltf_component_type_r_32u: + for (size_t i = 0; i < count; ++i) + { + cgltf_size v = ((unsigned int*)data)[i]; + bound = bound > v ? bound : v; + } + break; + + default: + ; + } + + return bound; +} + +#if CGLTF_VALIDATE_ENABLE_ASSERTS +#define CGLTF_ASSERT_IF(cond, result) assert(!(cond)); if (cond) return result; +#else +#define CGLTF_ASSERT_IF(cond, result) if (cond) return result; +#endif + +cgltf_result cgltf_validate(cgltf_data* data) +{ + for (cgltf_size i = 0; i < data->accessors_count; ++i) + { + cgltf_accessor* accessor = &data->accessors[i]; + + cgltf_size element_size = cgltf_calc_size(accessor->type, accessor->component_type); + + if (accessor->buffer_view) + { + cgltf_size req_size = accessor->offset + accessor->stride * (accessor->count - 1) + element_size; + + CGLTF_ASSERT_IF(accessor->buffer_view->size < req_size, cgltf_result_data_too_short); + } + + if (accessor->is_sparse) + { + cgltf_accessor_sparse* sparse = &accessor->sparse; + + cgltf_size indices_component_size = cgltf_calc_size(cgltf_type_scalar, sparse->indices_component_type); + cgltf_size indices_req_size = sparse->indices_byte_offset + indices_component_size * sparse->count; + cgltf_size values_req_size = sparse->values_byte_offset + element_size * sparse->count; + + CGLTF_ASSERT_IF(sparse->indices_buffer_view->size < indices_req_size || + sparse->values_buffer_view->size < values_req_size, cgltf_result_data_too_short); + + CGLTF_ASSERT_IF(sparse->indices_component_type != cgltf_component_type_r_8u && + sparse->indices_component_type != cgltf_component_type_r_16u && + sparse->indices_component_type != cgltf_component_type_r_32u, cgltf_result_invalid_gltf); + + if (sparse->indices_buffer_view->buffer->data) + { + cgltf_size index_bound = cgltf_calc_index_bound(sparse->indices_buffer_view, sparse->indices_byte_offset, sparse->indices_component_type, sparse->count); + + CGLTF_ASSERT_IF(index_bound >= accessor->count, cgltf_result_data_too_short); + } + } + } + + for (cgltf_size i = 0; i < data->buffer_views_count; ++i) + { + cgltf_size req_size = data->buffer_views[i].offset + data->buffer_views[i].size; + + CGLTF_ASSERT_IF(data->buffer_views[i].buffer && data->buffer_views[i].buffer->size < req_size, cgltf_result_data_too_short); + + if (data->buffer_views[i].has_meshopt_compression) + { + cgltf_meshopt_compression* mc = &data->buffer_views[i].meshopt_compression; + + CGLTF_ASSERT_IF(mc->buffer == NULL || mc->buffer->size < mc->offset + mc->size, cgltf_result_data_too_short); + + CGLTF_ASSERT_IF(data->buffer_views[i].stride && mc->stride != data->buffer_views[i].stride, cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF(data->buffer_views[i].size != mc->stride * mc->count, cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_invalid, cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_attributes && !(mc->stride % 4 == 0 && mc->stride <= 256), cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_triangles && mc->count % 3 != 0, cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF((mc->mode == cgltf_meshopt_compression_mode_triangles || mc->mode == cgltf_meshopt_compression_mode_indices) && mc->stride != 2 && mc->stride != 4, cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF((mc->mode == cgltf_meshopt_compression_mode_triangles || mc->mode == cgltf_meshopt_compression_mode_indices) && mc->filter != cgltf_meshopt_compression_filter_none, cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF(mc->filter == cgltf_meshopt_compression_filter_octahedral && mc->stride != 4 && mc->stride != 8, cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF(mc->filter == cgltf_meshopt_compression_filter_quaternion && mc->stride != 8, cgltf_result_invalid_gltf); + } + } + + for (cgltf_size i = 0; i < data->meshes_count; ++i) + { + if (data->meshes[i].weights) + { + CGLTF_ASSERT_IF(data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].weights_count, cgltf_result_invalid_gltf); + } + + if (data->meshes[i].target_names) + { + CGLTF_ASSERT_IF(data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].target_names_count, cgltf_result_invalid_gltf); + } + + for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) + { + CGLTF_ASSERT_IF(data->meshes[i].primitives[j].targets_count != data->meshes[i].primitives[0].targets_count, cgltf_result_invalid_gltf); + + if (data->meshes[i].primitives[j].attributes_count) + { + cgltf_accessor* first = data->meshes[i].primitives[j].attributes[0].data; + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) + { + CGLTF_ASSERT_IF(data->meshes[i].primitives[j].attributes[k].data->count != first->count, cgltf_result_invalid_gltf); + } + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) + { + for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) + { + CGLTF_ASSERT_IF(data->meshes[i].primitives[j].targets[k].attributes[m].data->count != first->count, cgltf_result_invalid_gltf); + } + } + + cgltf_accessor* indices = data->meshes[i].primitives[j].indices; + + CGLTF_ASSERT_IF(indices && + indices->component_type != cgltf_component_type_r_8u && + indices->component_type != cgltf_component_type_r_16u && + indices->component_type != cgltf_component_type_r_32u, cgltf_result_invalid_gltf); + + if (indices && indices->buffer_view && indices->buffer_view->buffer->data) + { + cgltf_size index_bound = cgltf_calc_index_bound(indices->buffer_view, indices->offset, indices->component_type, indices->count); + + CGLTF_ASSERT_IF(index_bound >= first->count, cgltf_result_data_too_short); + } + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k) + { + CGLTF_ASSERT_IF(data->meshes[i].primitives[j].mappings[k].variant >= data->variants_count, cgltf_result_invalid_gltf); + } + } + } + } + + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + if (data->nodes[i].weights && data->nodes[i].mesh) + { + CGLTF_ASSERT_IF (data->nodes[i].mesh->primitives_count && data->nodes[i].mesh->primitives[0].targets_count != data->nodes[i].weights_count, cgltf_result_invalid_gltf); + } + } + + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + cgltf_node* p1 = data->nodes[i].parent; + cgltf_node* p2 = p1 ? p1->parent : NULL; + + while (p1 && p2) + { + CGLTF_ASSERT_IF(p1 == p2, cgltf_result_invalid_gltf); + + p1 = p1->parent; + p2 = p2->parent ? p2->parent->parent : NULL; + } + } + + for (cgltf_size i = 0; i < data->scenes_count; ++i) + { + for (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j) + { + CGLTF_ASSERT_IF(data->scenes[i].nodes[j]->parent, cgltf_result_invalid_gltf); + } + } + + for (cgltf_size i = 0; i < data->animations_count; ++i) + { + for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) + { + cgltf_animation_channel* channel = &data->animations[i].channels[j]; + + if (!channel->target_node) + { + continue; + } + + cgltf_size components = 1; + + if (channel->target_path == cgltf_animation_path_type_weights) + { + CGLTF_ASSERT_IF(!channel->target_node->mesh || !channel->target_node->mesh->primitives_count, cgltf_result_invalid_gltf); + + components = channel->target_node->mesh->primitives[0].targets_count; + } + + cgltf_size values = channel->sampler->interpolation == cgltf_interpolation_type_cubic_spline ? 3 : 1; + + CGLTF_ASSERT_IF(channel->sampler->input->count * components * values != channel->sampler->output->count, cgltf_result_data_too_short); + } + } + + return cgltf_result_success; +} + +cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size) +{ + cgltf_size json_size = extras->end_offset - extras->start_offset; + + if (!dest) + { + if (dest_size) + { + *dest_size = json_size + 1; + return cgltf_result_success; + } + return cgltf_result_invalid_options; + } + + if (*dest_size + 1 < json_size) + { + strncpy(dest, data->json + extras->start_offset, *dest_size - 1); + dest[*dest_size - 1] = 0; + } + else + { + strncpy(dest, data->json + extras->start_offset, json_size); + dest[json_size] = 0; + } + + return cgltf_result_success; +} + +void cgltf_free_extensions(cgltf_data* data, cgltf_extension* extensions, cgltf_size extensions_count) +{ + for (cgltf_size i = 0; i < extensions_count; ++i) + { + data->memory.free_func(data->memory.user_data, extensions[i].name); + data->memory.free_func(data->memory.user_data, extensions[i].data); + } + data->memory.free_func(data->memory.user_data, extensions); +} + +void cgltf_free(cgltf_data* data) +{ + if (!data) + { + return; + } + + void (*file_release)(const struct cgltf_memory_options*, const struct cgltf_file_options*, void* data) = data->file.release ? data->file.release : cgltf_default_file_release; + + data->memory.free_func(data->memory.user_data, data->asset.copyright); + data->memory.free_func(data->memory.user_data, data->asset.generator); + data->memory.free_func(data->memory.user_data, data->asset.version); + data->memory.free_func(data->memory.user_data, data->asset.min_version); + + cgltf_free_extensions(data, data->asset.extensions, data->asset.extensions_count); + + for (cgltf_size i = 0; i < data->accessors_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->accessors[i].name); + + if(data->accessors[i].is_sparse) + { + cgltf_free_extensions(data, data->accessors[i].sparse.extensions, data->accessors[i].sparse.extensions_count); + cgltf_free_extensions(data, data->accessors[i].sparse.indices_extensions, data->accessors[i].sparse.indices_extensions_count); + cgltf_free_extensions(data, data->accessors[i].sparse.values_extensions, data->accessors[i].sparse.values_extensions_count); + } + cgltf_free_extensions(data, data->accessors[i].extensions, data->accessors[i].extensions_count); + } + data->memory.free_func(data->memory.user_data, data->accessors); + + for (cgltf_size i = 0; i < data->buffer_views_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->buffer_views[i].name); + data->memory.free_func(data->memory.user_data, data->buffer_views[i].data); + + cgltf_free_extensions(data, data->buffer_views[i].extensions, data->buffer_views[i].extensions_count); + } + data->memory.free_func(data->memory.user_data, data->buffer_views); + + for (cgltf_size i = 0; i < data->buffers_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->buffers[i].name); + + if (data->buffers[i].data_free_method == cgltf_data_free_method_file_release) + { + file_release(&data->memory, &data->file, data->buffers[i].data); + } + else if (data->buffers[i].data_free_method == cgltf_data_free_method_memory_free) + { + data->memory.free_func(data->memory.user_data, data->buffers[i].data); + } + + data->memory.free_func(data->memory.user_data, data->buffers[i].uri); + + cgltf_free_extensions(data, data->buffers[i].extensions, data->buffers[i].extensions_count); + } + + data->memory.free_func(data->memory.user_data, data->buffers); + + for (cgltf_size i = 0; i < data->meshes_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->meshes[i].name); + + for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) + { + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) + { + data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].attributes[k].name); + } + + data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].attributes); + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) + { + for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) + { + data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].targets[k].attributes[m].name); + } + + data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].targets[k].attributes); + } + + data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].targets); + + if (data->meshes[i].primitives[j].has_draco_mesh_compression) + { + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].draco_mesh_compression.attributes_count; ++k) + { + data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].draco_mesh_compression.attributes[k].name); + } + + data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].draco_mesh_compression.attributes); + } + + data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].mappings); + + cgltf_free_extensions(data, data->meshes[i].primitives[j].extensions, data->meshes[i].primitives[j].extensions_count); + } + + data->memory.free_func(data->memory.user_data, data->meshes[i].primitives); + data->memory.free_func(data->memory.user_data, data->meshes[i].weights); + + for (cgltf_size j = 0; j < data->meshes[i].target_names_count; ++j) + { + data->memory.free_func(data->memory.user_data, data->meshes[i].target_names[j]); + } + + cgltf_free_extensions(data, data->meshes[i].extensions, data->meshes[i].extensions_count); + + data->memory.free_func(data->memory.user_data, data->meshes[i].target_names); + } + + data->memory.free_func(data->memory.user_data, data->meshes); + + for (cgltf_size i = 0; i < data->materials_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->materials[i].name); + + if(data->materials[i].has_pbr_metallic_roughness) + { + cgltf_free_extensions(data, data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.extensions, data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].pbr_metallic_roughness.base_color_texture.extensions, data->materials[i].pbr_metallic_roughness.base_color_texture.extensions_count); + } + if(data->materials[i].has_pbr_specular_glossiness) + { + cgltf_free_extensions(data, data->materials[i].pbr_specular_glossiness.diffuse_texture.extensions, data->materials[i].pbr_specular_glossiness.diffuse_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.extensions, data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.extensions_count); + } + if(data->materials[i].has_clearcoat) + { + cgltf_free_extensions(data, data->materials[i].clearcoat.clearcoat_texture.extensions, data->materials[i].clearcoat.clearcoat_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].clearcoat.clearcoat_roughness_texture.extensions, data->materials[i].clearcoat.clearcoat_roughness_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].clearcoat.clearcoat_normal_texture.extensions, data->materials[i].clearcoat.clearcoat_normal_texture.extensions_count); + } + if(data->materials[i].has_specular) + { + cgltf_free_extensions(data, data->materials[i].specular.specular_texture.extensions, data->materials[i].specular.specular_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].specular.specular_color_texture.extensions, data->materials[i].specular.specular_color_texture.extensions_count); + } + if(data->materials[i].has_transmission) + { + cgltf_free_extensions(data, data->materials[i].transmission.transmission_texture.extensions, data->materials[i].transmission.transmission_texture.extensions_count); + } + if (data->materials[i].has_volume) + { + cgltf_free_extensions(data, data->materials[i].volume.thickness_texture.extensions, data->materials[i].volume.thickness_texture.extensions_count); + } + if(data->materials[i].has_sheen) + { + cgltf_free_extensions(data, data->materials[i].sheen.sheen_color_texture.extensions, data->materials[i].sheen.sheen_color_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].sheen.sheen_roughness_texture.extensions, data->materials[i].sheen.sheen_roughness_texture.extensions_count); + } + if(data->materials[i].has_iridescence) + { + cgltf_free_extensions(data, data->materials[i].iridescence.iridescence_texture.extensions, data->materials[i].iridescence.iridescence_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].iridescence.iridescence_thickness_texture.extensions, data->materials[i].iridescence.iridescence_thickness_texture.extensions_count); + } + + cgltf_free_extensions(data, data->materials[i].normal_texture.extensions, data->materials[i].normal_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].occlusion_texture.extensions, data->materials[i].occlusion_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].emissive_texture.extensions, data->materials[i].emissive_texture.extensions_count); + + cgltf_free_extensions(data, data->materials[i].extensions, data->materials[i].extensions_count); + } + + data->memory.free_func(data->memory.user_data, data->materials); + + for (cgltf_size i = 0; i < data->images_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->images[i].name); + data->memory.free_func(data->memory.user_data, data->images[i].uri); + data->memory.free_func(data->memory.user_data, data->images[i].mime_type); + + cgltf_free_extensions(data, data->images[i].extensions, data->images[i].extensions_count); + } + + data->memory.free_func(data->memory.user_data, data->images); + + for (cgltf_size i = 0; i < data->textures_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->textures[i].name); + cgltf_free_extensions(data, data->textures[i].extensions, data->textures[i].extensions_count); + } + + data->memory.free_func(data->memory.user_data, data->textures); + + for (cgltf_size i = 0; i < data->samplers_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->samplers[i].name); + cgltf_free_extensions(data, data->samplers[i].extensions, data->samplers[i].extensions_count); + } + + data->memory.free_func(data->memory.user_data, data->samplers); + + for (cgltf_size i = 0; i < data->skins_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->skins[i].name); + data->memory.free_func(data->memory.user_data, data->skins[i].joints); + + cgltf_free_extensions(data, data->skins[i].extensions, data->skins[i].extensions_count); + } + + data->memory.free_func(data->memory.user_data, data->skins); + + for (cgltf_size i = 0; i < data->cameras_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->cameras[i].name); + cgltf_free_extensions(data, data->cameras[i].extensions, data->cameras[i].extensions_count); + } + + data->memory.free_func(data->memory.user_data, data->cameras); + + for (cgltf_size i = 0; i < data->lights_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->lights[i].name); + } + + data->memory.free_func(data->memory.user_data, data->lights); + + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->nodes[i].name); + data->memory.free_func(data->memory.user_data, data->nodes[i].children); + data->memory.free_func(data->memory.user_data, data->nodes[i].weights); + cgltf_free_extensions(data, data->nodes[i].extensions, data->nodes[i].extensions_count); + } + + data->memory.free_func(data->memory.user_data, data->nodes); + + for (cgltf_size i = 0; i < data->scenes_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->scenes[i].name); + data->memory.free_func(data->memory.user_data, data->scenes[i].nodes); + + cgltf_free_extensions(data, data->scenes[i].extensions, data->scenes[i].extensions_count); + } + + data->memory.free_func(data->memory.user_data, data->scenes); + + for (cgltf_size i = 0; i < data->animations_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->animations[i].name); + for (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j) + { + cgltf_free_extensions(data, data->animations[i].samplers[j].extensions, data->animations[i].samplers[j].extensions_count); + } + data->memory.free_func(data->memory.user_data, data->animations[i].samplers); + + for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) + { + cgltf_free_extensions(data, data->animations[i].channels[j].extensions, data->animations[i].channels[j].extensions_count); + } + data->memory.free_func(data->memory.user_data, data->animations[i].channels); + + cgltf_free_extensions(data, data->animations[i].extensions, data->animations[i].extensions_count); + } + + data->memory.free_func(data->memory.user_data, data->animations); + + for (cgltf_size i = 0; i < data->variants_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->variants[i].name); + } + + data->memory.free_func(data->memory.user_data, data->variants); + + cgltf_free_extensions(data, data->data_extensions, data->data_extensions_count); + + for (cgltf_size i = 0; i < data->extensions_used_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->extensions_used[i]); + } + + data->memory.free_func(data->memory.user_data, data->extensions_used); + + for (cgltf_size i = 0; i < data->extensions_required_count; ++i) + { + data->memory.free_func(data->memory.user_data, data->extensions_required[i]); + } + + data->memory.free_func(data->memory.user_data, data->extensions_required); + + file_release(&data->memory, &data->file, data->file_data); + + data->memory.free_func(data->memory.user_data, data); +} + +void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix) +{ + cgltf_float* lm = out_matrix; + + if (node->has_matrix) + { + memcpy(lm, node->matrix, sizeof(float) * 16); + } + else + { + float tx = node->translation[0]; + float ty = node->translation[1]; + float tz = node->translation[2]; + + float qx = node->rotation[0]; + float qy = node->rotation[1]; + float qz = node->rotation[2]; + float qw = node->rotation[3]; + + float sx = node->scale[0]; + float sy = node->scale[1]; + float sz = node->scale[2]; + + lm[0] = (1 - 2 * qy*qy - 2 * qz*qz) * sx; + lm[1] = (2 * qx*qy + 2 * qz*qw) * sx; + lm[2] = (2 * qx*qz - 2 * qy*qw) * sx; + lm[3] = 0.f; + + lm[4] = (2 * qx*qy - 2 * qz*qw) * sy; + lm[5] = (1 - 2 * qx*qx - 2 * qz*qz) * sy; + lm[6] = (2 * qy*qz + 2 * qx*qw) * sy; + lm[7] = 0.f; + + lm[8] = (2 * qx*qz + 2 * qy*qw) * sz; + lm[9] = (2 * qy*qz - 2 * qx*qw) * sz; + lm[10] = (1 - 2 * qx*qx - 2 * qy*qy) * sz; + lm[11] = 0.f; + + lm[12] = tx; + lm[13] = ty; + lm[14] = tz; + lm[15] = 1.f; + } +} + +void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix) +{ + cgltf_float* lm = out_matrix; + cgltf_node_transform_local(node, lm); + + const cgltf_node* parent = node->parent; + + while (parent) + { + float pm[16]; + cgltf_node_transform_local(parent, pm); + + for (int i = 0; i < 4; ++i) + { + float l0 = lm[i * 4 + 0]; + float l1 = lm[i * 4 + 1]; + float l2 = lm[i * 4 + 2]; + + float r0 = l0 * pm[0] + l1 * pm[4] + l2 * pm[8]; + float r1 = l0 * pm[1] + l1 * pm[5] + l2 * pm[9]; + float r2 = l0 * pm[2] + l1 * pm[6] + l2 * pm[10]; + + lm[i * 4 + 0] = r0; + lm[i * 4 + 1] = r1; + lm[i * 4 + 2] = r2; + } + + lm[12] += pm[12]; + lm[13] += pm[13]; + lm[14] += pm[14]; + + parent = parent->parent; + } +} + +static cgltf_ssize cgltf_component_read_integer(const void* in, cgltf_component_type component_type) +{ + switch (component_type) + { + case cgltf_component_type_r_16: + return *((const int16_t*) in); + case cgltf_component_type_r_16u: + return *((const uint16_t*) in); + case cgltf_component_type_r_32u: + return *((const uint32_t*) in); + case cgltf_component_type_r_32f: + return (cgltf_ssize)*((const float*) in); + case cgltf_component_type_r_8: + return *((const int8_t*) in); + case cgltf_component_type_r_8u: + return *((const uint8_t*) in); + default: + return 0; + } +} + +static cgltf_size cgltf_component_read_index(const void* in, cgltf_component_type component_type) +{ + switch (component_type) + { + case cgltf_component_type_r_16u: + return *((const uint16_t*) in); + case cgltf_component_type_r_32u: + return *((const uint32_t*) in); + case cgltf_component_type_r_32f: + return (cgltf_size)*((const float*) in); + case cgltf_component_type_r_8u: + return *((const uint8_t*) in); + default: + return 0; + } +} + +static cgltf_float cgltf_component_read_float(const void* in, cgltf_component_type component_type, cgltf_bool normalized) +{ + if (component_type == cgltf_component_type_r_32f) + { + return *((const float*) in); + } + + if (normalized) + { + switch (component_type) + { + // note: glTF spec doesn't currently define normalized conversions for 32-bit integers + case cgltf_component_type_r_16: + return *((const int16_t*) in) / (cgltf_float)32767; + case cgltf_component_type_r_16u: + return *((const uint16_t*) in) / (cgltf_float)65535; + case cgltf_component_type_r_8: + return *((const int8_t*) in) / (cgltf_float)127; + case cgltf_component_type_r_8u: + return *((const uint8_t*) in) / (cgltf_float)255; + default: + return 0; + } + } + + return (cgltf_float)cgltf_component_read_integer(in, component_type); +} + +static cgltf_size cgltf_component_size(cgltf_component_type component_type); + +static cgltf_bool cgltf_element_read_float(const uint8_t* element, cgltf_type type, cgltf_component_type component_type, cgltf_bool normalized, cgltf_float* out, cgltf_size element_size) +{ + cgltf_size num_components = cgltf_num_components(type); + + if (element_size < num_components) { + return 0; + } + + // There are three special cases for component extraction, see #data-alignment in the 2.0 spec. + + cgltf_size component_size = cgltf_component_size(component_type); + + if (type == cgltf_type_mat2 && component_size == 1) + { + out[0] = cgltf_component_read_float(element, component_type, normalized); + out[1] = cgltf_component_read_float(element + 1, component_type, normalized); + out[2] = cgltf_component_read_float(element + 4, component_type, normalized); + out[3] = cgltf_component_read_float(element + 5, component_type, normalized); + return 1; + } + + if (type == cgltf_type_mat3 && component_size == 1) + { + out[0] = cgltf_component_read_float(element, component_type, normalized); + out[1] = cgltf_component_read_float(element + 1, component_type, normalized); + out[2] = cgltf_component_read_float(element + 2, component_type, normalized); + out[3] = cgltf_component_read_float(element + 4, component_type, normalized); + out[4] = cgltf_component_read_float(element + 5, component_type, normalized); + out[5] = cgltf_component_read_float(element + 6, component_type, normalized); + out[6] = cgltf_component_read_float(element + 8, component_type, normalized); + out[7] = cgltf_component_read_float(element + 9, component_type, normalized); + out[8] = cgltf_component_read_float(element + 10, component_type, normalized); + return 1; + } + + if (type == cgltf_type_mat3 && component_size == 2) + { + out[0] = cgltf_component_read_float(element, component_type, normalized); + out[1] = cgltf_component_read_float(element + 2, component_type, normalized); + out[2] = cgltf_component_read_float(element + 4, component_type, normalized); + out[3] = cgltf_component_read_float(element + 8, component_type, normalized); + out[4] = cgltf_component_read_float(element + 10, component_type, normalized); + out[5] = cgltf_component_read_float(element + 12, component_type, normalized); + out[6] = cgltf_component_read_float(element + 16, component_type, normalized); + out[7] = cgltf_component_read_float(element + 18, component_type, normalized); + out[8] = cgltf_component_read_float(element + 20, component_type, normalized); + return 1; + } + + for (cgltf_size i = 0; i < num_components; ++i) + { + out[i] = cgltf_component_read_float(element + component_size * i, component_type, normalized); + } + return 1; +} + +const uint8_t* cgltf_buffer_view_data(const cgltf_buffer_view* view) +{ + if (view->data) + return (const uint8_t*)view->data; + + if (!view->buffer->data) + return NULL; + + const uint8_t* result = (const uint8_t*)view->buffer->data; + result += view->offset; + return result; +} + +cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size) +{ + if (accessor->is_sparse) + { + return 0; + } + if (accessor->buffer_view == NULL) + { + memset(out, 0, element_size * sizeof(cgltf_float)); + return 1; + } + const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view); + if (element == NULL) + { + return 0; + } + element += accessor->offset + accessor->stride * index; + return cgltf_element_read_float(element, accessor->type, accessor->component_type, accessor->normalized, out, element_size); +} + +cgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_float* out, cgltf_size float_count) +{ + cgltf_size floats_per_element = cgltf_num_components(accessor->type); + cgltf_size available_floats = accessor->count * floats_per_element; + if (out == NULL) + { + return available_floats; + } + + float_count = available_floats < float_count ? available_floats : float_count; + cgltf_size element_count = float_count / floats_per_element; + + // First pass: convert each element in the base accessor. + cgltf_float* dest = out; + cgltf_accessor dense = *accessor; + dense.is_sparse = 0; + for (cgltf_size index = 0; index < element_count; index++, dest += floats_per_element) + { + if (!cgltf_accessor_read_float(&dense, index, dest, floats_per_element)) + { + return 0; + } + } + + // Second pass: write out each element in the sparse accessor. + if (accessor->is_sparse) + { + const cgltf_accessor_sparse* sparse = &dense.sparse; + + const uint8_t* index_data = cgltf_buffer_view_data(sparse->indices_buffer_view); + const uint8_t* reader_head = cgltf_buffer_view_data(sparse->values_buffer_view); + + if (index_data == NULL || reader_head == NULL) + { + return 0; + } + + index_data += sparse->indices_byte_offset; + reader_head += sparse->values_byte_offset; + + cgltf_size index_stride = cgltf_component_size(sparse->indices_component_type); + for (cgltf_size reader_index = 0; reader_index < sparse->count; reader_index++, index_data += index_stride) + { + size_t writer_index = cgltf_component_read_index(index_data, sparse->indices_component_type); + float* writer_head = out + writer_index * floats_per_element; + + if (!cgltf_element_read_float(reader_head, dense.type, dense.component_type, dense.normalized, writer_head, floats_per_element)) + { + return 0; + } + + reader_head += dense.stride; + } + } + + return element_count * floats_per_element; +} + +static cgltf_uint cgltf_component_read_uint(const void* in, cgltf_component_type component_type) +{ + switch (component_type) + { + case cgltf_component_type_r_8: + return *((const int8_t*) in); + + case cgltf_component_type_r_8u: + return *((const uint8_t*) in); + + case cgltf_component_type_r_16: + return *((const int16_t*) in); + + case cgltf_component_type_r_16u: + return *((const uint16_t*) in); + + case cgltf_component_type_r_32u: + return *((const uint32_t*) in); + + default: + return 0; + } +} + +static cgltf_bool cgltf_element_read_uint(const uint8_t* element, cgltf_type type, cgltf_component_type component_type, cgltf_uint* out, cgltf_size element_size) +{ + cgltf_size num_components = cgltf_num_components(type); + + if (element_size < num_components) + { + return 0; + } + + // Reading integer matrices is not a valid use case + if (type == cgltf_type_mat2 || type == cgltf_type_mat3 || type == cgltf_type_mat4) + { + return 0; + } + + cgltf_size component_size = cgltf_component_size(component_type); + + for (cgltf_size i = 0; i < num_components; ++i) + { + out[i] = cgltf_component_read_uint(element + component_size * i, component_type); + } + return 1; +} + +cgltf_bool cgltf_accessor_read_uint(const cgltf_accessor* accessor, cgltf_size index, cgltf_uint* out, cgltf_size element_size) +{ + if (accessor->is_sparse) + { + return 0; + } + if (accessor->buffer_view == NULL) + { + memset(out, 0, element_size * sizeof( cgltf_uint )); + return 1; + } + const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view); + if (element == NULL) + { + return 0; + } + element += accessor->offset + accessor->stride * index; + return cgltf_element_read_uint(element, accessor->type, accessor->component_type, out, element_size); +} + +cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index) +{ + if (accessor->is_sparse) + { + return 0; // This is an error case, but we can't communicate the error with existing interface. + } + if (accessor->buffer_view == NULL) + { + return 0; + } + const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view); + if (element == NULL) + { + return 0; // This is an error case, but we can't communicate the error with existing interface. + } + element += accessor->offset + accessor->stride * index; + return cgltf_component_read_index(element, accessor->component_type); +} + +#define CGLTF_ERROR_JSON -1 +#define CGLTF_ERROR_NOMEM -2 +#define CGLTF_ERROR_LEGACY -3 + +#define CGLTF_CHECK_TOKTYPE(tok_, type_) if ((tok_).type != (type_)) { return CGLTF_ERROR_JSON; } +#define CGLTF_CHECK_TOKTYPE_RETTYPE(tok_, type_, ret_) if ((tok_).type != (type_)) { return (ret_)CGLTF_ERROR_JSON; } +#define CGLTF_CHECK_KEY(tok_) if ((tok_).type != JSMN_STRING || (tok_).size == 0) { return CGLTF_ERROR_JSON; } /* checking size for 0 verifies that a value follows the key */ + +#define CGLTF_PTRINDEX(type, idx) (type*)((cgltf_size)idx + 1) +#define CGLTF_PTRFIXUP(var, data, size) if (var) { if ((cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1]; } +#define CGLTF_PTRFIXUP_REQ(var, data, size) if (!var || (cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1]; + +static int cgltf_json_strcmp(jsmntok_t const* tok, const uint8_t* json_chunk, const char* str) +{ + CGLTF_CHECK_TOKTYPE(*tok, JSMN_STRING); + size_t const str_len = strlen(str); + size_t const name_length = tok->end - tok->start; + return (str_len == name_length) ? strncmp((const char*)json_chunk + tok->start, str, str_len) : 128; +} + +static int cgltf_json_to_int(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); + char tmp[128]; + int size = (cgltf_size)(tok->end - tok->start) < sizeof(tmp) ? tok->end - tok->start : (int)(sizeof(tmp) - 1); + strncpy(tmp, (const char*)json_chunk + tok->start, size); + tmp[size] = 0; + return CGLTF_ATOI(tmp); +} + +static cgltf_size cgltf_json_to_size(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + CGLTF_CHECK_TOKTYPE_RETTYPE(*tok, JSMN_PRIMITIVE, cgltf_size); + char tmp[128]; + int size = (cgltf_size)(tok->end - tok->start) < sizeof(tmp) ? tok->end - tok->start : (int)(sizeof(tmp) - 1); + strncpy(tmp, (const char*)json_chunk + tok->start, size); + tmp[size] = 0; + return (cgltf_size)CGLTF_ATOLL(tmp); +} + +static cgltf_float cgltf_json_to_float(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); + char tmp[128]; + int size = (cgltf_size)(tok->end - tok->start) < sizeof(tmp) ? tok->end - tok->start : (int)(sizeof(tmp) - 1); + strncpy(tmp, (const char*)json_chunk + tok->start, size); + tmp[size] = 0; + return (cgltf_float)CGLTF_ATOF(tmp); +} + +static cgltf_bool cgltf_json_to_bool(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + int size = tok->end - tok->start; + return size == 4 && memcmp(json_chunk + tok->start, "true", 4) == 0; +} + +static int cgltf_skip_json(jsmntok_t const* tokens, int i) +{ + int end = i + 1; + + while (i < end) + { + switch (tokens[i].type) + { + case JSMN_OBJECT: + end += tokens[i].size * 2; + break; + + case JSMN_ARRAY: + end += tokens[i].size; + break; + + case JSMN_PRIMITIVE: + case JSMN_STRING: + break; + + default: + return -1; + } + + i++; + } + + return i; +} + +static void cgltf_fill_float_array(float* out_array, int size, float value) +{ + for (int j = 0; j < size; ++j) + { + out_array[j] = value; + } +} + +static int cgltf_parse_json_float_array(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, float* out_array, int size) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + if (tokens[i].size != size) + { + return CGLTF_ERROR_JSON; + } + ++i; + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_array[j] = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + return i; +} + +static int cgltf_parse_json_string(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char** out_string) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING); + if (*out_string) + { + return CGLTF_ERROR_JSON; + } + int size = tokens[i].end - tokens[i].start; + char* result = (char*)options->memory.alloc_func(options->memory.user_data, size + 1); + if (!result) + { + return CGLTF_ERROR_NOMEM; + } + strncpy(result, (const char*)json_chunk + tokens[i].start, size); + result[size] = 0; + *out_string = result; + return i + 1; +} + +static int cgltf_parse_json_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, size_t element_size, void** out_array, cgltf_size* out_size) +{ + (void)json_chunk; + if (tokens[i].type != JSMN_ARRAY) + { + return tokens[i].type == JSMN_OBJECT ? CGLTF_ERROR_LEGACY : CGLTF_ERROR_JSON; + } + if (*out_array) + { + return CGLTF_ERROR_JSON; + } + int size = tokens[i].size; + void* result = cgltf_calloc(options, element_size, size); + if (!result) + { + return CGLTF_ERROR_NOMEM; + } + *out_array = result; + *out_size = size; + return i + 1; +} + +static int cgltf_parse_json_string_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char*** out_array, cgltf_size* out_size) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(char*), (void**)out_array, out_size); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < *out_size; ++j) + { + i = cgltf_parse_json_string(options, tokens, i, json_chunk, j + (*out_array)); + if (i < 0) + { + return i; + } + } + return i; +} + +static void cgltf_parse_attribute_type(const char* name, cgltf_attribute_type* out_type, int* out_index) +{ + if (*name == '_') + { + *out_type = cgltf_attribute_type_custom; + return; + } + + const char* us = strchr(name, '_'); + size_t len = us ? (size_t)(us - name) : strlen(name); + + if (len == 8 && strncmp(name, "POSITION", 8) == 0) + { + *out_type = cgltf_attribute_type_position; + } + else if (len == 6 && strncmp(name, "NORMAL", 6) == 0) + { + *out_type = cgltf_attribute_type_normal; + } + else if (len == 7 && strncmp(name, "TANGENT", 7) == 0) + { + *out_type = cgltf_attribute_type_tangent; + } + else if (len == 8 && strncmp(name, "TEXCOORD", 8) == 0) + { + *out_type = cgltf_attribute_type_texcoord; + } + else if (len == 5 && strncmp(name, "COLOR", 5) == 0) + { + *out_type = cgltf_attribute_type_color; + } + else if (len == 6 && strncmp(name, "JOINTS", 6) == 0) + { + *out_type = cgltf_attribute_type_joints; + } + else if (len == 7 && strncmp(name, "WEIGHTS", 7) == 0) + { + *out_type = cgltf_attribute_type_weights; + } + else + { + *out_type = cgltf_attribute_type_invalid; + } + + if (us && *out_type != cgltf_attribute_type_invalid) + { + *out_index = CGLTF_ATOI(us + 1); + } +} + +static int cgltf_parse_json_attribute_list(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_attribute** out_attributes, cgltf_size* out_attributes_count) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + if (*out_attributes) + { + return CGLTF_ERROR_JSON; + } + + *out_attributes_count = tokens[i].size; + *out_attributes = (cgltf_attribute*)cgltf_calloc(options, sizeof(cgltf_attribute), *out_attributes_count); + ++i; + + if (!*out_attributes) + { + return CGLTF_ERROR_NOMEM; + } + + for (cgltf_size j = 0; j < *out_attributes_count; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + i = cgltf_parse_json_string(options, tokens, i, json_chunk, &(*out_attributes)[j].name); + if (i < 0) + { + return CGLTF_ERROR_JSON; + } + + cgltf_parse_attribute_type((*out_attributes)[j].name, &(*out_attributes)[j].type, &(*out_attributes)[j].index); + + (*out_attributes)[j].data = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + + return i; +} + +static int cgltf_parse_json_extras(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extras* out_extras) +{ + (void)json_chunk; + out_extras->start_offset = tokens[i].start; + out_extras->end_offset = tokens[i].end; + i = cgltf_skip_json(tokens, i); + return i; +} + +static int cgltf_parse_json_unprocessed_extension(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extension* out_extension) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING); + CGLTF_CHECK_TOKTYPE(tokens[i+1], JSMN_OBJECT); + if (out_extension->name) + { + return CGLTF_ERROR_JSON; + } + + cgltf_size name_length = tokens[i].end - tokens[i].start; + out_extension->name = (char*)options->memory.alloc_func(options->memory.user_data, name_length + 1); + if (!out_extension->name) + { + return CGLTF_ERROR_NOMEM; + } + strncpy(out_extension->name, (const char*)json_chunk + tokens[i].start, name_length); + out_extension->name[name_length] = 0; + i++; + + size_t start = tokens[i].start; + size_t size = tokens[i].end - start; + out_extension->data = (char*)options->memory.alloc_func(options->memory.user_data, size + 1); + if (!out_extension->data) + { + return CGLTF_ERROR_NOMEM; + } + strncpy(out_extension->data, (const char*)json_chunk + start, size); + out_extension->data[size] = '\0'; + + i = cgltf_skip_json(tokens, i); + + return i; +} + +static int cgltf_parse_json_unprocessed_extensions(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_size* out_extensions_count, cgltf_extension** out_extensions) +{ + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if(*out_extensions) + { + return CGLTF_ERROR_JSON; + } + + int extensions_size = tokens[i].size; + *out_extensions_count = 0; + *out_extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + + if (!*out_extensions) + { + return CGLTF_ERROR_NOMEM; + } + + ++i; + + for (int j = 0; j < extensions_size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + cgltf_size extension_index = (*out_extensions_count)++; + cgltf_extension* extension = &((*out_extensions)[extension_index]); + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, extension); + + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_draco_mesh_compression(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_draco_mesh_compression* out_draco_mesh_compression) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "attributes") == 0) + { + i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_draco_mesh_compression->attributes, &out_draco_mesh_compression->attributes_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferView") == 0) + { + ++i; + out_draco_mesh_compression->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_mesh_gpu_instancing(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_mesh_gpu_instancing* out_mesh_gpu_instancing) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "attributes") == 0) + { + i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_mesh_gpu_instancing->attributes, &out_mesh_gpu_instancing->attributes_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferView") == 0) + { + ++i; + out_mesh_gpu_instancing->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_material_mapping_data(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material_mapping* out_mappings, cgltf_size* offset) +{ + (void)options; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int obj_size = tokens[i].size; + ++i; + + int material = -1; + int variants_tok = -1; + cgltf_extras extras = {0, 0}; + + for (int k = 0; k < obj_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "material") == 0) + { + ++i; + material = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "variants") == 0) + { + variants_tok = i+1; + CGLTF_CHECK_TOKTYPE(tokens[variants_tok], JSMN_ARRAY); + + i = cgltf_skip_json(tokens, i+1); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + if (material < 0 || variants_tok < 0) + { + return CGLTF_ERROR_JSON; + } + + if (out_mappings) + { + for (int k = 0; k < tokens[variants_tok].size; ++k) + { + int variant = cgltf_json_to_int(&tokens[variants_tok + 1 + k], json_chunk); + if (variant < 0) + return variant; + + out_mappings[*offset].material = CGLTF_PTRINDEX(cgltf_material, material); + out_mappings[*offset].variant = variant; + out_mappings[*offset].extras = extras; + + (*offset)++; + } + } + else + { + (*offset) += tokens[variants_tok].size; + } + } + + return i; +} + +static int cgltf_parse_json_material_mappings(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_primitive* out_prim) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "mappings") == 0) + { + if (out_prim->mappings) + { + return CGLTF_ERROR_JSON; + } + + cgltf_size mappings_offset = 0; + int k = cgltf_parse_json_material_mapping_data(options, tokens, i + 1, json_chunk, NULL, &mappings_offset); + if (k < 0) + { + return k; + } + + out_prim->mappings_count = mappings_offset; + out_prim->mappings = (cgltf_material_mapping*)cgltf_calloc(options, sizeof(cgltf_material_mapping), out_prim->mappings_count); + + mappings_offset = 0; + i = cgltf_parse_json_material_mapping_data(options, tokens, i + 1, json_chunk, out_prim->mappings, &mappings_offset); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_primitive(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_primitive* out_prim) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_prim->type = cgltf_primitive_type_triangles; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "mode") == 0) + { + ++i; + out_prim->type + = (cgltf_primitive_type) + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "indices") == 0) + { + ++i; + out_prim->indices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "material") == 0) + { + ++i; + out_prim->material = CGLTF_PTRINDEX(cgltf_material, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "attributes") == 0) + { + i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_prim->attributes, &out_prim->attributes_count); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "targets") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_morph_target), (void**)&out_prim->targets, &out_prim->targets_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_prim->targets_count; ++k) + { + i = cgltf_parse_json_attribute_list(options, tokens, i, json_chunk, &out_prim->targets[k].attributes, &out_prim->targets[k].attributes_count); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_prim->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if(out_prim->extensions) + { + return CGLTF_ERROR_JSON; + } + + int extensions_size = tokens[i].size; + out_prim->extensions_count = 0; + out_prim->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + + if (!out_prim->extensions) + { + return CGLTF_ERROR_NOMEM; + } + + ++i; + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_draco_mesh_compression") == 0) + { + out_prim->has_draco_mesh_compression = 1; + i = cgltf_parse_json_draco_mesh_compression(options, tokens, i + 1, json_chunk, &out_prim->draco_mesh_compression); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_variants") == 0) + { + i = cgltf_parse_json_material_mappings(options, tokens, i + 1, json_chunk, out_prim); + } + else + { + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_prim->extensions[out_prim->extensions_count++])); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_mesh* out_mesh) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_mesh->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "primitives") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_primitive), (void**)&out_mesh->primitives, &out_mesh->primitives_count); + if (i < 0) + { + return i; + } + + for (cgltf_size prim_index = 0; prim_index < out_mesh->primitives_count; ++prim_index) + { + i = cgltf_parse_json_primitive(options, tokens, i, json_chunk, &out_mesh->primitives[prim_index]); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_mesh->weights, &out_mesh->weights_count); + if (i < 0) + { + return i; + } + + i = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_mesh->weights, (int)out_mesh->weights_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + ++i; + + out_mesh->extras.start_offset = tokens[i].start; + out_mesh->extras.end_offset = tokens[i].end; + + if (tokens[i].type == JSMN_OBJECT) + { + int extras_size = tokens[i].size; + ++i; + + for (int k = 0; k < extras_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "targetNames") == 0 && tokens[i+1].type == JSMN_ARRAY) + { + i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_mesh->target_names, &out_mesh->target_names_count); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i); + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_mesh->extensions_count, &out_mesh->extensions); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_meshes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_mesh), (void**)&out_data->meshes, &out_data->meshes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->meshes_count; ++j) + { + i = cgltf_parse_json_mesh(options, tokens, i, json_chunk, &out_data->meshes[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static cgltf_component_type cgltf_json_to_component_type(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + int type = cgltf_json_to_int(tok, json_chunk); + + switch (type) + { + case 5120: + return cgltf_component_type_r_8; + case 5121: + return cgltf_component_type_r_8u; + case 5122: + return cgltf_component_type_r_16; + case 5123: + return cgltf_component_type_r_16u; + case 5125: + return cgltf_component_type_r_32u; + case 5126: + return cgltf_component_type_r_32f; + default: + return cgltf_component_type_invalid; + } +} + +static int cgltf_parse_json_accessor_sparse(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor_sparse* out_sparse) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) + { + ++i; + out_sparse->count = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "indices") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int indices_size = tokens[i].size; + ++i; + + for (int k = 0; k < indices_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_sparse->indices_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_sparse->indices_byte_offset = cgltf_json_to_size(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "componentType") == 0) + { + ++i; + out_sparse->indices_component_type = cgltf_json_to_component_type(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->indices_extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sparse->indices_extensions_count, &out_sparse->indices_extensions); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "values") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int values_size = tokens[i].size; + ++i; + + for (int k = 0; k < values_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_sparse->values_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_sparse->values_byte_offset = cgltf_json_to_size(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->values_extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sparse->values_extensions_count, &out_sparse->values_extensions); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sparse->extensions_count, &out_sparse->extensions); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_accessor(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor* out_accessor) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_accessor->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_accessor->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_accessor->offset = + cgltf_json_to_size(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "componentType") == 0) + { + ++i; + out_accessor->component_type = cgltf_json_to_component_type(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "normalized") == 0) + { + ++i; + out_accessor->normalized = cgltf_json_to_bool(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) + { + ++i; + out_accessor->count = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens+i, json_chunk, "SCALAR") == 0) + { + out_accessor->type = cgltf_type_scalar; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC2") == 0) + { + out_accessor->type = cgltf_type_vec2; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC3") == 0) + { + out_accessor->type = cgltf_type_vec3; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC4") == 0) + { + out_accessor->type = cgltf_type_vec4; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT2") == 0) + { + out_accessor->type = cgltf_type_mat2; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT3") == 0) + { + out_accessor->type = cgltf_type_mat3; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT4") == 0) + { + out_accessor->type = cgltf_type_mat4; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "min") == 0) + { + ++i; + out_accessor->has_min = 1; + // note: we can't parse the precise number of elements since type may not have been computed yet + int min_size = tokens[i].size > 16 ? 16 : tokens[i].size; + i = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->min, min_size); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "max") == 0) + { + ++i; + out_accessor->has_max = 1; + // note: we can't parse the precise number of elements since type may not have been computed yet + int max_size = tokens[i].size > 16 ? 16 : tokens[i].size; + i = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->max, max_size); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "sparse") == 0) + { + out_accessor->is_sparse = 1; + i = cgltf_parse_json_accessor_sparse(options, tokens, i + 1, json_chunk, &out_accessor->sparse); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_accessor->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_accessor->extensions_count, &out_accessor->extensions); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_texture_transform(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_transform* out_texture_transform) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "offset") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->offset, 2); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "rotation") == 0) + { + ++i; + out_texture_transform->rotation = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->scale, 2); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0) + { + ++i; + out_texture_transform->has_texcoord = 1; + out_texture_transform->texcoord = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_texture_view(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_view* out_texture_view) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_texture_view->scale = 1.0f; + cgltf_fill_float_array(out_texture_view->transform.scale, 2, 1.0f); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "index") == 0) + { + ++i; + out_texture_view->texture = CGLTF_PTRINDEX(cgltf_texture, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0) + { + ++i; + out_texture_view->texcoord = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0) + { + ++i; + out_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "strength") == 0) + { + ++i; + out_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_texture_view->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if(out_texture_view->extensions) + { + return CGLTF_ERROR_JSON; + } + + int extensions_size = tokens[i].size; + out_texture_view->extensions_count = 0; + out_texture_view->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + + if (!out_texture_view->extensions) + { + return CGLTF_ERROR_NOMEM; + } + + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_texture_transform") == 0) + { + out_texture_view->has_transform = 1; + i = cgltf_parse_json_texture_transform(tokens, i + 1, json_chunk, &out_texture_view->transform); + } + else + { + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_texture_view->extensions[out_texture_view->extensions_count++])); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_pbr_metallic_roughness(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_metallic_roughness* out_pbr) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "metallicFactor") == 0) + { + ++i; + out_pbr->metallic_factor = + cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "roughnessFactor") == 0) + { + ++i; + out_pbr->roughness_factor = + cgltf_json_to_float(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->base_color_factor, 4); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, + &out_pbr->base_color_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "metallicRoughnessTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, + &out_pbr->metallic_roughness_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_pbr->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_pbr_specular_glossiness(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_specular_glossiness* out_pbr) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "diffuseFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->diffuse_factor, 4); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->specular_factor, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "glossinessFactor") == 0) + { + ++i; + out_pbr->glossiness_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "diffuseTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->diffuse_texture); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularGlossinessTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->specular_glossiness_texture); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_clearcoat(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_clearcoat* out_clearcoat) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatFactor") == 0) + { + ++i; + out_clearcoat->clearcoat_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatRoughnessFactor") == 0) + { + ++i; + out_clearcoat->clearcoat_roughness_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_texture); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatRoughnessTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_roughness_texture); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatNormalTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_normal_texture); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_ior(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_ior* out_ior) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + // Default values + out_ior->ior = 1.5f; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "ior") == 0) + { + ++i; + out_ior->ior = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_specular(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_specular* out_specular) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + // Default values + out_specular->specular_factor = 1.0f; + cgltf_fill_float_array(out_specular->specular_color_factor, 3, 1.0f); + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "specularFactor") == 0) + { + ++i; + out_specular->specular_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularColorFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_specular->specular_color_factor, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_specular->specular_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "specularColorTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_specular->specular_color_texture); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_transmission(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_transmission* out_transmission) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "transmissionFactor") == 0) + { + ++i; + out_transmission->transmission_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "transmissionTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_transmission->transmission_texture); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_volume(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_volume* out_volume) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "thicknessFactor") == 0) + { + ++i; + out_volume->thickness_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "thicknessTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_volume->thickness_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "attenuationColor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_volume->attenuation_color, 3); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "attenuationDistance") == 0) + { + ++i; + out_volume->attenuation_distance = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_sheen(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sheen* out_sheen) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenColorFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_sheen->sheen_color_factor, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenColorTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_sheen->sheen_color_texture); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenRoughnessFactor") == 0) + { + ++i; + out_sheen->sheen_roughness_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenRoughnessTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_sheen->sheen_roughness_texture); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_emissive_strength(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_emissive_strength* out_emissive_strength) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + // Default + out_emissive_strength->emissive_strength = 1.f; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "emissiveStrength") == 0) + { + ++i; + out_emissive_strength->emissive_strength = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_iridescence(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_iridescence* out_iridescence) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + // Default + out_iridescence->iridescence_ior = 1.3f; + out_iridescence->iridescence_thickness_min = 100.f; + out_iridescence->iridescence_thickness_max = 400.f; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceFactor") == 0) + { + ++i; + out_iridescence->iridescence_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_iridescence->iridescence_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceIor") == 0) + { + ++i; + out_iridescence->iridescence_ior = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceThicknessMinimum") == 0) + { + ++i; + out_iridescence->iridescence_thickness_min = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceThicknessMaximum") == 0) + { + ++i; + out_iridescence->iridescence_thickness_max = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "iridescenceThicknessTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_iridescence->iridescence_thickness_texture); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_image(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_image* out_image) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "uri") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->uri); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_image->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "mimeType") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->mime_type); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_image->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_image->extensions_count, &out_image->extensions); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sampler* out_sampler) +{ + (void)options; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_sampler->wrap_s = 10497; + out_sampler->wrap_t = 10497; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_sampler->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "magFilter") == 0) + { + ++i; + out_sampler->mag_filter + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "minFilter") == 0) + { + ++i; + out_sampler->min_filter + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapS") == 0) + { + ++i; + out_sampler->wrap_s + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapT") == 0) + { + ++i; + out_sampler->wrap_t + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sampler->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sampler->extensions_count, &out_sampler->extensions); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_texture(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture* out_texture) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_texture->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "sampler") == 0) + { + ++i; + out_texture->sampler = CGLTF_PTRINDEX(cgltf_sampler, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0) + { + ++i; + out_texture->image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_texture->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if (out_texture->extensions) + { + return CGLTF_ERROR_JSON; + } + + int extensions_size = tokens[i].size; + ++i; + out_texture->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + out_texture->extensions_count = 0; + + if (!out_texture->extensions) + { + return CGLTF_ERROR_NOMEM; + } + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_texture_basisu") == 0) + { + out_texture->has_basisu = 1; + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int num_properties = tokens[i].size; + ++i; + + for (int t = 0; t < num_properties; ++t) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0) + { + ++i; + out_texture->basisu_image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_texture->extensions[out_texture->extensions_count++])); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material* out_material) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + cgltf_fill_float_array(out_material->pbr_metallic_roughness.base_color_factor, 4, 1.0f); + out_material->pbr_metallic_roughness.metallic_factor = 1.0f; + out_material->pbr_metallic_roughness.roughness_factor = 1.0f; + + cgltf_fill_float_array(out_material->pbr_specular_glossiness.diffuse_factor, 4, 1.0f); + cgltf_fill_float_array(out_material->pbr_specular_glossiness.specular_factor, 3, 1.0f); + out_material->pbr_specular_glossiness.glossiness_factor = 1.0f; + + cgltf_fill_float_array(out_material->volume.attenuation_color, 3, 1.0f); + out_material->volume.attenuation_distance = FLT_MAX; + + out_material->alpha_cutoff = 0.5f; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_material->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "pbrMetallicRoughness") == 0) + { + out_material->has_pbr_metallic_roughness = 1; + i = cgltf_parse_json_pbr_metallic_roughness(options, tokens, i + 1, json_chunk, &out_material->pbr_metallic_roughness); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "emissiveFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_material->emissive_factor, 3); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "normalTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, + &out_material->normal_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "occlusionTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, + &out_material->occlusion_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "emissiveTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, + &out_material->emissive_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaMode") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "OPAQUE") == 0) + { + out_material->alpha_mode = cgltf_alpha_mode_opaque; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "MASK") == 0) + { + out_material->alpha_mode = cgltf_alpha_mode_mask; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "BLEND") == 0) + { + out_material->alpha_mode = cgltf_alpha_mode_blend; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaCutoff") == 0) + { + ++i; + out_material->alpha_cutoff = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "doubleSided") == 0) + { + ++i; + out_material->double_sided = + cgltf_json_to_bool(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_material->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if(out_material->extensions) + { + return CGLTF_ERROR_JSON; + } + + int extensions_size = tokens[i].size; + ++i; + out_material->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + out_material->extensions_count= 0; + + if (!out_material->extensions) + { + return CGLTF_ERROR_NOMEM; + } + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_pbrSpecularGlossiness") == 0) + { + out_material->has_pbr_specular_glossiness = 1; + i = cgltf_parse_json_pbr_specular_glossiness(options, tokens, i + 1, json_chunk, &out_material->pbr_specular_glossiness); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_unlit") == 0) + { + out_material->unlit = 1; + i = cgltf_skip_json(tokens, i+1); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_clearcoat") == 0) + { + out_material->has_clearcoat = 1; + i = cgltf_parse_json_clearcoat(options, tokens, i + 1, json_chunk, &out_material->clearcoat); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_ior") == 0) + { + out_material->has_ior = 1; + i = cgltf_parse_json_ior(tokens, i + 1, json_chunk, &out_material->ior); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_specular") == 0) + { + out_material->has_specular = 1; + i = cgltf_parse_json_specular(options, tokens, i + 1, json_chunk, &out_material->specular); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_transmission") == 0) + { + out_material->has_transmission = 1; + i = cgltf_parse_json_transmission(options, tokens, i + 1, json_chunk, &out_material->transmission); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_volume") == 0) + { + out_material->has_volume = 1; + i = cgltf_parse_json_volume(options, tokens, i + 1, json_chunk, &out_material->volume); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_sheen") == 0) + { + out_material->has_sheen = 1; + i = cgltf_parse_json_sheen(options, tokens, i + 1, json_chunk, &out_material->sheen); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_emissive_strength") == 0) + { + out_material->has_emissive_strength = 1; + i = cgltf_parse_json_emissive_strength(tokens, i + 1, json_chunk, &out_material->emissive_strength); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_iridescence") == 0) + { + out_material->has_iridescence = 1; + i = cgltf_parse_json_iridescence(options, tokens, i + 1, json_chunk, &out_material->iridescence); + } + else + { + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_material->extensions[out_material->extensions_count++])); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_accessors(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_accessor), (void**)&out_data->accessors, &out_data->accessors_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->accessors_count; ++j) + { + i = cgltf_parse_json_accessor(options, tokens, i, json_chunk, &out_data->accessors[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_materials(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_material), (void**)&out_data->materials, &out_data->materials_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->materials_count; ++j) + { + i = cgltf_parse_json_material(options, tokens, i, json_chunk, &out_data->materials[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_images(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_image), (void**)&out_data->images, &out_data->images_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->images_count; ++j) + { + i = cgltf_parse_json_image(options, tokens, i, json_chunk, &out_data->images[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_textures(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_texture), (void**)&out_data->textures, &out_data->textures_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->textures_count; ++j) + { + i = cgltf_parse_json_texture(options, tokens, i, json_chunk, &out_data->textures[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_samplers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_sampler), (void**)&out_data->samplers, &out_data->samplers_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->samplers_count; ++j) + { + i = cgltf_parse_json_sampler(options, tokens, i, json_chunk, &out_data->samplers[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_meshopt_compression(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_meshopt_compression* out_meshopt_compression) +{ + (void)options; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "buffer") == 0) + { + ++i; + out_meshopt_compression->buffer = CGLTF_PTRINDEX(cgltf_buffer, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_meshopt_compression->offset = cgltf_json_to_size(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) + { + ++i; + out_meshopt_compression->size = cgltf_json_to_size(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteStride") == 0) + { + ++i; + out_meshopt_compression->stride = cgltf_json_to_size(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) + { + ++i; + out_meshopt_compression->count = cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "mode") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens+i, json_chunk, "ATTRIBUTES") == 0) + { + out_meshopt_compression->mode = cgltf_meshopt_compression_mode_attributes; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "TRIANGLES") == 0) + { + out_meshopt_compression->mode = cgltf_meshopt_compression_mode_triangles; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "INDICES") == 0) + { + out_meshopt_compression->mode = cgltf_meshopt_compression_mode_indices; + } + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "filter") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens+i, json_chunk, "NONE") == 0) + { + out_meshopt_compression->filter = cgltf_meshopt_compression_filter_none; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "OCTAHEDRAL") == 0) + { + out_meshopt_compression->filter = cgltf_meshopt_compression_filter_octahedral; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "QUATERNION") == 0) + { + out_meshopt_compression->filter = cgltf_meshopt_compression_filter_quaternion; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "EXPONENTIAL") == 0) + { + out_meshopt_compression->filter = cgltf_meshopt_compression_filter_exponential; + } + ++i; + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_buffer_view(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_buffer_view* out_buffer_view) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer_view->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "buffer") == 0) + { + ++i; + out_buffer_view->buffer = CGLTF_PTRINDEX(cgltf_buffer, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_buffer_view->offset = + cgltf_json_to_size(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) + { + ++i; + out_buffer_view->size = + cgltf_json_to_size(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteStride") == 0) + { + ++i; + out_buffer_view->stride = + cgltf_json_to_size(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "target") == 0) + { + ++i; + int type = cgltf_json_to_int(tokens+i, json_chunk); + switch (type) + { + case 34962: + type = cgltf_buffer_view_type_vertices; + break; + case 34963: + type = cgltf_buffer_view_type_indices; + break; + default: + type = cgltf_buffer_view_type_invalid; + break; + } + out_buffer_view->type = (cgltf_buffer_view_type)type; + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_buffer_view->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if(out_buffer_view->extensions) + { + return CGLTF_ERROR_JSON; + } + + int extensions_size = tokens[i].size; + out_buffer_view->extensions_count = 0; + out_buffer_view->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + + if (!out_buffer_view->extensions) + { + return CGLTF_ERROR_NOMEM; + } + + ++i; + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "EXT_meshopt_compression") == 0) + { + out_buffer_view->has_meshopt_compression = 1; + i = cgltf_parse_json_meshopt_compression(options, tokens, i + 1, json_chunk, &out_buffer_view->meshopt_compression); + } + else + { + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_buffer_view->extensions[out_buffer_view->extensions_count++])); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_buffer_views(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer_view), (void**)&out_data->buffer_views, &out_data->buffer_views_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->buffer_views_count; ++j) + { + i = cgltf_parse_json_buffer_view(options, tokens, i, json_chunk, &out_data->buffer_views[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_buffer(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_buffer* out_buffer) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) + { + ++i; + out_buffer->size = + cgltf_json_to_size(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "uri") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer->uri); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_buffer->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_buffer->extensions_count, &out_buffer->extensions); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_buffers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer), (void**)&out_data->buffers, &out_data->buffers_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->buffers_count; ++j) + { + i = cgltf_parse_json_buffer(options, tokens, i, json_chunk, &out_data->buffers[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_skin(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_skin* out_skin) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_skin->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "joints") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_skin->joints, &out_skin->joints_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_skin->joints_count; ++k) + { + out_skin->joints[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "skeleton") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_skin->skeleton = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "inverseBindMatrices") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_skin->inverse_bind_matrices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_skin->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_skin->extensions_count, &out_skin->extensions); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_skins(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_skin), (void**)&out_data->skins, &out_data->skins_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->skins_count; ++j) + { + i = cgltf_parse_json_skin(options, tokens, i, json_chunk, &out_data->skins[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_camera* out_camera) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_camera->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "perspective") == 0) + { + out_camera->type = cgltf_camera_type_perspective; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "orthographic") == 0) + { + out_camera->type = cgltf_camera_type_orthographic; + } + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "perspective") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + out_camera->type = cgltf_camera_type_perspective; + + for (int k = 0; k < data_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "aspectRatio") == 0) + { + ++i; + out_camera->data.perspective.has_aspect_ratio = 1; + out_camera->data.perspective.aspect_ratio = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "yfov") == 0) + { + ++i; + out_camera->data.perspective.yfov = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "zfar") == 0) + { + ++i; + out_camera->data.perspective.has_zfar = 1; + out_camera->data.perspective.zfar = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "znear") == 0) + { + ++i; + out_camera->data.perspective.znear = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->data.perspective.extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "orthographic") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + out_camera->type = cgltf_camera_type_orthographic; + + for (int k = 0; k < data_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "xmag") == 0) + { + ++i; + out_camera->data.orthographic.xmag = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "ymag") == 0) + { + ++i; + out_camera->data.orthographic.ymag = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "zfar") == 0) + { + ++i; + out_camera->data.orthographic.zfar = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "znear") == 0) + { + ++i; + out_camera->data.orthographic.znear = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->data.orthographic.extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_camera->extensions_count, &out_camera->extensions); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_cameras(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_camera), (void**)&out_data->cameras, &out_data->cameras_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->cameras_count; ++j) + { + i = cgltf_parse_json_camera(options, tokens, i, json_chunk, &out_data->cameras[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_light(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_light* out_light) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_light->color[0] = 1.f; + out_light->color[1] = 1.f; + out_light->color[2] = 1.f; + out_light->intensity = 1.f; + + out_light->spot_inner_cone_angle = 0.f; + out_light->spot_outer_cone_angle = 3.1415926535f / 4.0f; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_light->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "color") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_light->color, 3); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "intensity") == 0) + { + ++i; + out_light->intensity = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "directional") == 0) + { + out_light->type = cgltf_light_type_directional; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "point") == 0) + { + out_light->type = cgltf_light_type_point; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "spot") == 0) + { + out_light->type = cgltf_light_type_spot; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "range") == 0) + { + ++i; + out_light->range = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "spot") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + for (int k = 0; k < data_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "innerConeAngle") == 0) + { + ++i; + out_light->spot_inner_cone_angle = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "outerConeAngle") == 0) + { + ++i; + out_light->spot_outer_cone_angle = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_light->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_lights(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_light), (void**)&out_data->lights, &out_data->lights_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->lights_count; ++j) + { + i = cgltf_parse_json_light(options, tokens, i, json_chunk, &out_data->lights[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_node(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_node* out_node) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_node->rotation[3] = 1.0f; + out_node->scale[0] = 1.0f; + out_node->scale[1] = 1.0f; + out_node->scale[2] = 1.0f; + out_node->matrix[0] = 1.0f; + out_node->matrix[5] = 1.0f; + out_node->matrix[10] = 1.0f; + out_node->matrix[15] = 1.0f; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_node->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "children") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_node->children, &out_node->children_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_node->children_count; ++k) + { + out_node->children[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "mesh") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->mesh = CGLTF_PTRINDEX(cgltf_mesh, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "skin") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->skin = CGLTF_PTRINDEX(cgltf_skin, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "camera") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->camera = CGLTF_PTRINDEX(cgltf_camera, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "translation") == 0) + { + out_node->has_translation = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->translation, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "rotation") == 0) + { + out_node->has_rotation = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->rotation, 4); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "scale") == 0) + { + out_node->has_scale = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->scale, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "matrix") == 0) + { + out_node->has_matrix = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->matrix, 16); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_node->weights, &out_node->weights_count); + if (i < 0) + { + return i; + } + + i = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_node->weights, (int)out_node->weights_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_node->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if(out_node->extensions) + { + return CGLTF_ERROR_JSON; + } + + int extensions_size = tokens[i].size; + out_node->extensions_count= 0; + out_node->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + + if (!out_node->extensions) + { + return CGLTF_ERROR_NOMEM; + } + + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_lights_punctual") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + for (int m = 0; m < data_size; ++m) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "light") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->light = CGLTF_PTRINDEX(cgltf_light, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "EXT_mesh_gpu_instancing") == 0) + { + out_node->has_mesh_gpu_instancing = 1; + i = cgltf_parse_json_mesh_gpu_instancing(options, tokens, i + 1, json_chunk, &out_node->mesh_gpu_instancing); + } + else + { + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_node->extensions[out_node->extensions_count++])); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_nodes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_node), (void**)&out_data->nodes, &out_data->nodes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->nodes_count; ++j) + { + i = cgltf_parse_json_node(options, tokens, i, json_chunk, &out_data->nodes[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_scene(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_scene* out_scene) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_scene->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "nodes") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_scene->nodes, &out_scene->nodes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_scene->nodes_count; ++k) + { + out_scene->nodes[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_scene->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_scene->extensions_count, &out_scene->extensions); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_scenes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_scene), (void**)&out_data->scenes, &out_data->scenes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->scenes_count; ++j) + { + i = cgltf_parse_json_scene(options, tokens, i, json_chunk, &out_data->scenes[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_animation_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_sampler* out_sampler) +{ + (void)options; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "input") == 0) + { + ++i; + out_sampler->input = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "output") == 0) + { + ++i; + out_sampler->output = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "interpolation") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "LINEAR") == 0) + { + out_sampler->interpolation = cgltf_interpolation_type_linear; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "STEP") == 0) + { + out_sampler->interpolation = cgltf_interpolation_type_step; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "CUBICSPLINE") == 0) + { + out_sampler->interpolation = cgltf_interpolation_type_cubic_spline; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sampler->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sampler->extensions_count, &out_sampler->extensions); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_animation_channel(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_channel* out_channel) +{ + (void)options; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "sampler") == 0) + { + ++i; + out_channel->sampler = CGLTF_PTRINDEX(cgltf_animation_sampler, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "target") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int target_size = tokens[i].size; + ++i; + + for (int k = 0; k < target_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "node") == 0) + { + ++i; + out_channel->target_node = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "path") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens+i, json_chunk, "translation") == 0) + { + out_channel->target_path = cgltf_animation_path_type_translation; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "rotation") == 0) + { + out_channel->target_path = cgltf_animation_path_type_rotation; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "scale") == 0) + { + out_channel->target_path = cgltf_animation_path_type_scale; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "weights") == 0) + { + out_channel->target_path = cgltf_animation_path_type_weights; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_channel->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_channel->extensions_count, &out_channel->extensions); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_animation(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation* out_animation) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_animation->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "samplers") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_sampler), (void**)&out_animation->samplers, &out_animation->samplers_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_animation->samplers_count; ++k) + { + i = cgltf_parse_json_animation_sampler(options, tokens, i, json_chunk, &out_animation->samplers[k]); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "channels") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_channel), (void**)&out_animation->channels, &out_animation->channels_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_animation->channels_count; ++k) + { + i = cgltf_parse_json_animation_channel(options, tokens, i, json_chunk, &out_animation->channels[k]); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_animation->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_animation->extensions_count, &out_animation->extensions); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_animations(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_animation), (void**)&out_data->animations, &out_data->animations_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->animations_count; ++j) + { + i = cgltf_parse_json_animation(options, tokens, i, json_chunk, &out_data->animations[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_variant(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material_variant* out_variant) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_variant->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_variant->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_variants(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_material_variant), (void**)&out_data->variants, &out_data->variants_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->variants_count; ++j) + { + i = cgltf_parse_json_variant(options, tokens, i, json_chunk, &out_data->variants[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_asset(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_asset* out_asset) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "copyright") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->copyright); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "generator") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->generator); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "version") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->version); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "minVersion") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->min_version); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_asset->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_asset->extensions_count, &out_asset->extensions); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + if (out_asset->version && CGLTF_ATOF(out_asset->version) < 2) + { + return CGLTF_ERROR_LEGACY; + } + + return i; +} + +cgltf_size cgltf_num_components(cgltf_type type) { + switch (type) + { + case cgltf_type_vec2: + return 2; + case cgltf_type_vec3: + return 3; + case cgltf_type_vec4: + return 4; + case cgltf_type_mat2: + return 4; + case cgltf_type_mat3: + return 9; + case cgltf_type_mat4: + return 16; + case cgltf_type_invalid: + case cgltf_type_scalar: + default: + return 1; + } +} + +static cgltf_size cgltf_component_size(cgltf_component_type component_type) { + switch (component_type) + { + case cgltf_component_type_r_8: + case cgltf_component_type_r_8u: + return 1; + case cgltf_component_type_r_16: + case cgltf_component_type_r_16u: + return 2; + case cgltf_component_type_r_32u: + case cgltf_component_type_r_32f: + return 4; + case cgltf_component_type_invalid: + default: + return 0; + } +} + +static cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type) +{ + cgltf_size component_size = cgltf_component_size(component_type); + if (type == cgltf_type_mat2 && component_size == 1) + { + return 8 * component_size; + } + else if (type == cgltf_type_mat3 && (component_size == 1 || component_size == 2)) + { + return 12 * component_size; + } + return component_size * cgltf_num_components(type); +} + +static int cgltf_fixup_pointers(cgltf_data* out_data); + +static int cgltf_parse_json_root(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "asset") == 0) + { + i = cgltf_parse_json_asset(options, tokens, i + 1, json_chunk, &out_data->asset); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "meshes") == 0) + { + i = cgltf_parse_json_meshes(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "accessors") == 0) + { + i = cgltf_parse_json_accessors(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferViews") == 0) + { + i = cgltf_parse_json_buffer_views(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "buffers") == 0) + { + i = cgltf_parse_json_buffers(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "materials") == 0) + { + i = cgltf_parse_json_materials(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "images") == 0) + { + i = cgltf_parse_json_images(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "textures") == 0) + { + i = cgltf_parse_json_textures(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "samplers") == 0) + { + i = cgltf_parse_json_samplers(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "skins") == 0) + { + i = cgltf_parse_json_skins(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "cameras") == 0) + { + i = cgltf_parse_json_cameras(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "nodes") == 0) + { + i = cgltf_parse_json_nodes(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scenes") == 0) + { + i = cgltf_parse_json_scenes(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scene") == 0) + { + ++i; + out_data->scene = CGLTF_PTRINDEX(cgltf_scene, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "animations") == 0) + { + i = cgltf_parse_json_animations(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_data->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if(out_data->data_extensions) + { + return CGLTF_ERROR_JSON; + } + + int extensions_size = tokens[i].size; + out_data->data_extensions_count = 0; + out_data->data_extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + + if (!out_data->data_extensions) + { + return CGLTF_ERROR_NOMEM; + } + + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_lights_punctual") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + for (int m = 0; m < data_size; ++m) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "lights") == 0) + { + i = cgltf_parse_json_lights(options, tokens, i + 1, json_chunk, out_data); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_variants") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + for (int m = 0; m < data_size; ++m) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "variants") == 0) + { + i = cgltf_parse_json_variants(options, tokens, i + 1, json_chunk, out_data); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_data->data_extensions[out_data->data_extensions_count++])); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensionsUsed") == 0) + { + i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_used, &out_data->extensions_used_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensionsRequired") == 0) + { + i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_required, &out_data->extensions_required_count); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data) +{ + jsmn_parser parser = { 0, 0, 0 }; + + if (options->json_token_count == 0) + { + int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, NULL, 0); + + if (token_count <= 0) + { + return cgltf_result_invalid_json; + } + + options->json_token_count = token_count; + } + + jsmntok_t* tokens = (jsmntok_t*)options->memory.alloc_func(options->memory.user_data, sizeof(jsmntok_t) * (options->json_token_count + 1)); + + if (!tokens) + { + return cgltf_result_out_of_memory; + } + + jsmn_init(&parser); + + int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, tokens, options->json_token_count); + + if (token_count <= 0) + { + options->memory.free_func(options->memory.user_data, tokens); + return cgltf_result_invalid_json; + } + + // this makes sure that we always have an UNDEFINED token at the end of the stream + // for invalid JSON inputs this makes sure we don't perform out of bound reads of token data + tokens[token_count].type = JSMN_UNDEFINED; + + cgltf_data* data = (cgltf_data*)options->memory.alloc_func(options->memory.user_data, sizeof(cgltf_data)); + + if (!data) + { + options->memory.free_func(options->memory.user_data, tokens); + return cgltf_result_out_of_memory; + } + + memset(data, 0, sizeof(cgltf_data)); + data->memory = options->memory; + data->file = options->file; + + int i = cgltf_parse_json_root(options, tokens, 0, json_chunk, data); + + options->memory.free_func(options->memory.user_data, tokens); + + if (i < 0) + { + cgltf_free(data); + + switch (i) + { + case CGLTF_ERROR_NOMEM: return cgltf_result_out_of_memory; + case CGLTF_ERROR_LEGACY: return cgltf_result_legacy_gltf; + default: return cgltf_result_invalid_gltf; + } + } + + if (cgltf_fixup_pointers(data) < 0) + { + cgltf_free(data); + return cgltf_result_invalid_gltf; + } + + data->json = (const char*)json_chunk; + data->json_size = size; + + *out_data = data; + + return cgltf_result_success; +} + +static int cgltf_fixup_pointers(cgltf_data* data) +{ + for (cgltf_size i = 0; i < data->meshes_count; ++i) + { + for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) + { + CGLTF_PTRFIXUP(data->meshes[i].primitives[j].indices, data->accessors, data->accessors_count); + CGLTF_PTRFIXUP(data->meshes[i].primitives[j].material, data->materials, data->materials_count); + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].attributes[k].data, data->accessors, data->accessors_count); + } + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) + { + for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].targets[k].attributes[m].data, data->accessors, data->accessors_count); + } + } + + if (data->meshes[i].primitives[j].has_draco_mesh_compression) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].draco_mesh_compression.buffer_view, data->buffer_views, data->buffer_views_count); + for (cgltf_size m = 0; m < data->meshes[i].primitives[j].draco_mesh_compression.attributes_count; ++m) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].draco_mesh_compression.attributes[m].data, data->accessors, data->accessors_count); + } + } + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].mappings[k].material, data->materials, data->materials_count); + } + } + } + + for (cgltf_size i = 0; i < data->accessors_count; ++i) + { + CGLTF_PTRFIXUP(data->accessors[i].buffer_view, data->buffer_views, data->buffer_views_count); + + if (data->accessors[i].is_sparse) + { + CGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.indices_buffer_view, data->buffer_views, data->buffer_views_count); + CGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.values_buffer_view, data->buffer_views, data->buffer_views_count); + } + + if (data->accessors[i].buffer_view) + { + data->accessors[i].stride = data->accessors[i].buffer_view->stride; + } + + if (data->accessors[i].stride == 0) + { + data->accessors[i].stride = cgltf_calc_size(data->accessors[i].type, data->accessors[i].component_type); + } + } + + for (cgltf_size i = 0; i < data->textures_count; ++i) + { + CGLTF_PTRFIXUP(data->textures[i].image, data->images, data->images_count); + CGLTF_PTRFIXUP(data->textures[i].basisu_image, data->images, data->images_count); + CGLTF_PTRFIXUP(data->textures[i].sampler, data->samplers, data->samplers_count); + } + + for (cgltf_size i = 0; i < data->images_count; ++i) + { + CGLTF_PTRFIXUP(data->images[i].buffer_view, data->buffer_views, data->buffer_views_count); + } + + for (cgltf_size i = 0; i < data->materials_count; ++i) + { + CGLTF_PTRFIXUP(data->materials[i].normal_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].emissive_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].occlusion_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.base_color_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.diffuse_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_roughness_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_normal_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].specular.specular_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].specular.specular_color_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].transmission.transmission_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].volume.thickness_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].sheen.sheen_color_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].sheen.sheen_roughness_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].iridescence.iridescence_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].iridescence.iridescence_thickness_texture.texture, data->textures, data->textures_count); + } + + for (cgltf_size i = 0; i < data->buffer_views_count; ++i) + { + CGLTF_PTRFIXUP_REQ(data->buffer_views[i].buffer, data->buffers, data->buffers_count); + + if (data->buffer_views[i].has_meshopt_compression) + { + CGLTF_PTRFIXUP_REQ(data->buffer_views[i].meshopt_compression.buffer, data->buffers, data->buffers_count); + } + } + + for (cgltf_size i = 0; i < data->skins_count; ++i) + { + for (cgltf_size j = 0; j < data->skins[i].joints_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->skins[i].joints[j], data->nodes, data->nodes_count); + } + + CGLTF_PTRFIXUP(data->skins[i].skeleton, data->nodes, data->nodes_count); + CGLTF_PTRFIXUP(data->skins[i].inverse_bind_matrices, data->accessors, data->accessors_count); + } + + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + for (cgltf_size j = 0; j < data->nodes[i].children_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->nodes[i].children[j], data->nodes, data->nodes_count); + + if (data->nodes[i].children[j]->parent) + { + return CGLTF_ERROR_JSON; + } + + data->nodes[i].children[j]->parent = &data->nodes[i]; + } + + CGLTF_PTRFIXUP(data->nodes[i].mesh, data->meshes, data->meshes_count); + CGLTF_PTRFIXUP(data->nodes[i].skin, data->skins, data->skins_count); + CGLTF_PTRFIXUP(data->nodes[i].camera, data->cameras, data->cameras_count); + CGLTF_PTRFIXUP(data->nodes[i].light, data->lights, data->lights_count); + + if (data->nodes[i].has_mesh_gpu_instancing) + { + CGLTF_PTRFIXUP_REQ(data->nodes[i].mesh_gpu_instancing.buffer_view, data->buffer_views, data->buffer_views_count); + for (cgltf_size m = 0; m < data->nodes[i].mesh_gpu_instancing.attributes_count; ++m) + { + CGLTF_PTRFIXUP_REQ(data->nodes[i].mesh_gpu_instancing.attributes[m].data, data->accessors, data->accessors_count); + } + } + } + + for (cgltf_size i = 0; i < data->scenes_count; ++i) + { + for (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->scenes[i].nodes[j], data->nodes, data->nodes_count); + + if (data->scenes[i].nodes[j]->parent) + { + return CGLTF_ERROR_JSON; + } + } + } + + CGLTF_PTRFIXUP(data->scene, data->scenes, data->scenes_count); + + for (cgltf_size i = 0; i < data->animations_count; ++i) + { + for (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].input, data->accessors, data->accessors_count); + CGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].output, data->accessors, data->accessors_count); + } + + for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->animations[i].channels[j].sampler, data->animations[i].samplers, data->animations[i].samplers_count); + CGLTF_PTRFIXUP(data->animations[i].channels[j].target_node, data->nodes, data->nodes_count); + } + } + + return 0; +} + +/* + * -- jsmn.c start -- + * Source: https://github.com/zserge/jsmn + * License: MIT + * + * Copyright (c) 2010 Serge A. Zaitsev + + * 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. + */ + +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, size_t num_tokens) { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + if (tokens == NULL) + break; + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + if(token->type != type || parser->toksuper == -1) { + return JSMN_ERROR_INVAL; + } + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +static void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} +/* + * -- jsmn.c end -- + */ + +#endif /* #ifdef CGLTF_IMPLEMENTATION */ + +/* cgltf is distributed under MIT license: + * + * Copyright (c) 2018-2021 Johannes Kuhlmann + + * 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/source/engine/thirdparty/cgltf/cgltf_write.h b/source/engine/thirdparty/cgltf/cgltf_write.h new file mode 100644 index 0000000..5c4edaf --- /dev/null +++ b/source/engine/thirdparty/cgltf/cgltf_write.h @@ -0,0 +1,1403 @@ +/** + * cgltf_write - a single-file glTF 2.0 writer written in C99. + * + * Version: 1.13 + * + * Website: https://github.com/jkuhlmann/cgltf + * + * Distributed under the MIT License, see notice at the end of this file. + * + * Building: + * Include this file where you need the struct and function + * declarations. Have exactly one source file where you define + * `CGLTF_WRITE_IMPLEMENTATION` before including this file to get the + * function definitions. + * + * Reference: + * `cgltf_result cgltf_write_file(const cgltf_options* options, const char* + * path, const cgltf_data* data)` writes JSON to the given file path. Buffer + * files and external images are not written out. `data` is not deallocated. + * + * `cgltf_size cgltf_write(const cgltf_options* options, char* buffer, + * cgltf_size size, const cgltf_data* data)` writes JSON into the given memory + * buffer. Returns the number of bytes written to `buffer`, including a null + * terminator. If buffer is null, returns the number of bytes that would have + * been written. `data` is not deallocated. + * + * To write custom JSON into the `extras` field, aggregate all the custom JSON + * into a single buffer, then set `file_data` to this buffer. By supplying + * start_offset and end_offset values for various objects, you can select a + * range of characters within the aggregated buffer. + */ +#ifndef CGLTF_WRITE_H_INCLUDED__ +#define CGLTF_WRITE_H_INCLUDED__ + +#include "cgltf.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +cgltf_result cgltf_write_file(const cgltf_options* options, const char* path, const cgltf_data* data); +cgltf_size cgltf_write(const cgltf_options* options, char* buffer, cgltf_size size, const cgltf_data* data); + +#ifdef __cplusplus +} +#endif + +#endif /* #ifndef CGLTF_WRITE_H_INCLUDED__ */ + +/* + * + * Stop now, if you are only interested in the API. + * Below, you find the implementation. + * + */ + +#if defined(__INTELLISENSE__) || defined(__JETBRAINS_IDE__) +/* This makes MSVC/CLion intellisense work. */ +#define CGLTF_WRITE_IMPLEMENTATION +#endif + +#ifdef CGLTF_WRITE_IMPLEMENTATION + +#include +#include +#include +#include +#include +#include + +#define CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM (1 << 0) +#define CGLTF_EXTENSION_FLAG_MATERIALS_UNLIT (1 << 1) +#define CGLTF_EXTENSION_FLAG_SPECULAR_GLOSSINESS (1 << 2) +#define CGLTF_EXTENSION_FLAG_LIGHTS_PUNCTUAL (1 << 3) +#define CGLTF_EXTENSION_FLAG_DRACO_MESH_COMPRESSION (1 << 4) +#define CGLTF_EXTENSION_FLAG_MATERIALS_CLEARCOAT (1 << 5) +#define CGLTF_EXTENSION_FLAG_MATERIALS_IOR (1 << 6) +#define CGLTF_EXTENSION_FLAG_MATERIALS_SPECULAR (1 << 7) +#define CGLTF_EXTENSION_FLAG_MATERIALS_TRANSMISSION (1 << 8) +#define CGLTF_EXTENSION_FLAG_MATERIALS_SHEEN (1 << 9) +#define CGLTF_EXTENSION_FLAG_MATERIALS_VARIANTS (1 << 10) +#define CGLTF_EXTENSION_FLAG_MATERIALS_VOLUME (1 << 11) +#define CGLTF_EXTENSION_FLAG_TEXTURE_BASISU (1 << 12) +#define CGLTF_EXTENSION_FLAG_MATERIALS_EMISSIVE_STRENGTH (1 << 13) +#define CGLTF_EXTENSION_FLAG_MESH_GPU_INSTANCING (1 << 14) +#define CGLTF_EXTENSION_FLAG_MATERIALS_IRIDESCENCE (1 << 15) + +typedef struct { + char* buffer; + cgltf_size buffer_size; + cgltf_size remaining; + char* cursor; + cgltf_size tmp; + cgltf_size chars_written; + const cgltf_data* data; + int depth; + const char* indent; + int needs_comma; + uint32_t extension_flags; + uint32_t required_extension_flags; +} cgltf_write_context; + +#define CGLTF_MIN(a, b) (a < b ? a : b) + +#ifdef FLT_DECIMAL_DIG + // FLT_DECIMAL_DIG is C11 + #define CGLTF_DECIMAL_DIG (FLT_DECIMAL_DIG) +#else + #define CGLTF_DECIMAL_DIG 9 +#endif + +#define CGLTF_SPRINTF(...) { \ + assert(context->cursor || (!context->cursor && context->remaining == 0)); \ + context->tmp = snprintf ( context->cursor, context->remaining, __VA_ARGS__ ); \ + context->chars_written += context->tmp; \ + if (context->cursor) { \ + context->cursor += context->tmp; \ + context->remaining -= context->tmp; \ + } } + +#define CGLTF_SNPRINTF(length, ...) { \ + assert(context->cursor || (!context->cursor && context->remaining == 0)); \ + context->tmp = snprintf ( context->cursor, CGLTF_MIN(length + 1, context->remaining), __VA_ARGS__ ); \ + context->chars_written += length; \ + if (context->cursor) { \ + context->cursor += length; \ + context->remaining -= length; \ + } } + +#define CGLTF_WRITE_IDXPROP(label, val, start) if (val) { \ + cgltf_write_indent(context); \ + CGLTF_SPRINTF("\"%s\": %d", label, (int) (val - start)); \ + context->needs_comma = 1; } + +#define CGLTF_WRITE_IDXARRPROP(label, dim, vals, start) if (vals) { \ + cgltf_write_indent(context); \ + CGLTF_SPRINTF("\"%s\": [", label); \ + for (int i = 0; i < (int)(dim); ++i) { \ + int idx = (int) (vals[i] - start); \ + if (i != 0) CGLTF_SPRINTF(","); \ + CGLTF_SPRINTF(" %d", idx); \ + } \ + CGLTF_SPRINTF(" ]"); \ + context->needs_comma = 1; } + +#define CGLTF_WRITE_TEXTURE_INFO(label, info) if (info.texture) { \ + cgltf_write_line(context, "\"" label "\": {"); \ + CGLTF_WRITE_IDXPROP("index", info.texture, context->data->textures); \ + cgltf_write_intprop(context, "texCoord", info.texcoord, 0); \ + cgltf_write_floatprop(context, "scale", info.scale, 1.0f); \ + if (info.has_transform) { \ + context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM; \ + cgltf_write_texture_transform(context, &info.transform); \ + } \ + cgltf_write_extras(context, &info.extras); \ + cgltf_write_line(context, "}"); } + +static void cgltf_write_indent(cgltf_write_context* context) +{ + if (context->needs_comma) + { + CGLTF_SPRINTF(",\n"); + context->needs_comma = 0; + } + else + { + CGLTF_SPRINTF("\n"); + } + for (int i = 0; i < context->depth; ++i) + { + CGLTF_SPRINTF("%s", context->indent); + } +} + +static void cgltf_write_line(cgltf_write_context* context, const char* line) +{ + if (line[0] == ']' || line[0] == '}') + { + --context->depth; + context->needs_comma = 0; + } + cgltf_write_indent(context); + CGLTF_SPRINTF("%s", line); + cgltf_size last = (cgltf_size)(strlen(line) - 1); + if (line[0] == ']' || line[0] == '}') + { + context->needs_comma = 1; + } + if (line[last] == '[' || line[last] == '{') + { + ++context->depth; + context->needs_comma = 0; + } +} + +static void cgltf_write_strprop(cgltf_write_context* context, const char* label, const char* val) +{ + if (val) + { + cgltf_write_indent(context); + CGLTF_SPRINTF("\"%s\": \"%s\"", label, val); + context->needs_comma = 1; + } +} + +static void cgltf_write_extras(cgltf_write_context* context, const cgltf_extras* extras) +{ + cgltf_size length = extras->end_offset - extras->start_offset; + if (length > 0 && context->data->file_data) + { + char* json_string = ((char*) context->data->file_data) + extras->start_offset; + cgltf_write_indent(context); + CGLTF_SPRINTF("%s", "\"extras\": "); + CGLTF_SNPRINTF(length, "%.*s", (int)(extras->end_offset - extras->start_offset), json_string); + context->needs_comma = 1; + } +} + +static void cgltf_write_stritem(cgltf_write_context* context, const char* item) +{ + cgltf_write_indent(context); + CGLTF_SPRINTF("\"%s\"", item); + context->needs_comma = 1; +} + +static void cgltf_write_intprop(cgltf_write_context* context, const char* label, int val, int def) +{ + if (val != def) + { + cgltf_write_indent(context); + CGLTF_SPRINTF("\"%s\": %d", label, val); + context->needs_comma = 1; + } +} + +static void cgltf_write_sizeprop(cgltf_write_context* context, const char* label, cgltf_size val, cgltf_size def) +{ + if (val != def) + { + cgltf_write_indent(context); + CGLTF_SPRINTF("\"%s\": %zu", label, val); + context->needs_comma = 1; + } +} + +static void cgltf_write_floatprop(cgltf_write_context* context, const char* label, float val, float def) +{ + if (val != def) + { + cgltf_write_indent(context); + CGLTF_SPRINTF("\"%s\": ", label); + CGLTF_SPRINTF("%.*g", CGLTF_DECIMAL_DIG, val); + context->needs_comma = 1; + + if (context->cursor) + { + char *decimal_comma = strchr(context->cursor - context->tmp, ','); + if (decimal_comma) + { + *decimal_comma = '.'; + } + } + } +} + +static void cgltf_write_boolprop_optional(cgltf_write_context* context, const char* label, bool val, bool def) +{ + if (val != def) + { + cgltf_write_indent(context); + CGLTF_SPRINTF("\"%s\": %s", label, val ? "true" : "false"); + context->needs_comma = 1; + } +} + +static void cgltf_write_floatarrayprop(cgltf_write_context* context, const char* label, const cgltf_float* vals, cgltf_size dim) +{ + cgltf_write_indent(context); + CGLTF_SPRINTF("\"%s\": [", label); + for (cgltf_size i = 0; i < dim; ++i) + { + if (i != 0) + { + CGLTF_SPRINTF(", %.*g", CGLTF_DECIMAL_DIG, vals[i]); + } + else + { + CGLTF_SPRINTF("%.*g", CGLTF_DECIMAL_DIG, vals[i]); + } + } + CGLTF_SPRINTF("]"); + context->needs_comma = 1; +} + +static bool cgltf_check_floatarray(const float* vals, int dim, float val) { + while (dim--) + { + if (vals[dim] != val) + { + return true; + } + } + return false; +} + +static int cgltf_int_from_component_type(cgltf_component_type ctype) +{ + switch (ctype) + { + case cgltf_component_type_r_8: return 5120; + case cgltf_component_type_r_8u: return 5121; + case cgltf_component_type_r_16: return 5122; + case cgltf_component_type_r_16u: return 5123; + case cgltf_component_type_r_32u: return 5125; + case cgltf_component_type_r_32f: return 5126; + default: return 0; + } +} + +static const char* cgltf_str_from_alpha_mode(cgltf_alpha_mode alpha_mode) +{ + switch (alpha_mode) + { + case cgltf_alpha_mode_mask: return "MASK"; + case cgltf_alpha_mode_blend: return "BLEND"; + default: return NULL; + } +} + +static const char* cgltf_str_from_type(cgltf_type type) +{ + switch (type) + { + case cgltf_type_scalar: return "SCALAR"; + case cgltf_type_vec2: return "VEC2"; + case cgltf_type_vec3: return "VEC3"; + case cgltf_type_vec4: return "VEC4"; + case cgltf_type_mat2: return "MAT2"; + case cgltf_type_mat3: return "MAT3"; + case cgltf_type_mat4: return "MAT4"; + default: return NULL; + } +} + +static cgltf_size cgltf_dim_from_type(cgltf_type type) +{ + switch (type) + { + case cgltf_type_scalar: return 1; + case cgltf_type_vec2: return 2; + case cgltf_type_vec3: return 3; + case cgltf_type_vec4: return 4; + case cgltf_type_mat2: return 4; + case cgltf_type_mat3: return 9; + case cgltf_type_mat4: return 16; + default: return 0; + } +} + +static const char* cgltf_str_from_camera_type(cgltf_camera_type camera_type) +{ + switch (camera_type) + { + case cgltf_camera_type_perspective: return "perspective"; + case cgltf_camera_type_orthographic: return "orthographic"; + default: return NULL; + } +} + +static const char* cgltf_str_from_light_type(cgltf_light_type light_type) +{ + switch (light_type) + { + case cgltf_light_type_directional: return "directional"; + case cgltf_light_type_point: return "point"; + case cgltf_light_type_spot: return "spot"; + default: return NULL; + } +} + +static void cgltf_write_texture_transform(cgltf_write_context* context, const cgltf_texture_transform* transform) +{ + cgltf_write_line(context, "\"extensions\": {"); + cgltf_write_line(context, "\"KHR_texture_transform\": {"); + if (cgltf_check_floatarray(transform->offset, 2, 0.0f)) + { + cgltf_write_floatarrayprop(context, "offset", transform->offset, 2); + } + cgltf_write_floatprop(context, "rotation", transform->rotation, 0.0f); + if (cgltf_check_floatarray(transform->scale, 2, 1.0f)) + { + cgltf_write_floatarrayprop(context, "scale", transform->scale, 2); + } + if (transform->has_texcoord) + { + cgltf_write_intprop(context, "texCoord", transform->texcoord, -1); + } + cgltf_write_line(context, "}"); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_asset(cgltf_write_context* context, const cgltf_asset* asset) +{ + cgltf_write_line(context, "\"asset\": {"); + cgltf_write_strprop(context, "copyright", asset->copyright); + cgltf_write_strprop(context, "generator", asset->generator); + cgltf_write_strprop(context, "version", asset->version); + cgltf_write_strprop(context, "min_version", asset->min_version); + cgltf_write_extras(context, &asset->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_primitive(cgltf_write_context* context, const cgltf_primitive* prim) +{ + cgltf_write_intprop(context, "mode", (int) prim->type, 4); + CGLTF_WRITE_IDXPROP("indices", prim->indices, context->data->accessors); + CGLTF_WRITE_IDXPROP("material", prim->material, context->data->materials); + cgltf_write_line(context, "\"attributes\": {"); + for (cgltf_size i = 0; i < prim->attributes_count; ++i) + { + const cgltf_attribute* attr = prim->attributes + i; + CGLTF_WRITE_IDXPROP(attr->name, attr->data, context->data->accessors); + } + cgltf_write_line(context, "}"); + + if (prim->targets_count) + { + cgltf_write_line(context, "\"targets\": ["); + for (cgltf_size i = 0; i < prim->targets_count; ++i) + { + cgltf_write_line(context, "{"); + for (cgltf_size j = 0; j < prim->targets[i].attributes_count; ++j) + { + const cgltf_attribute* attr = prim->targets[i].attributes + j; + CGLTF_WRITE_IDXPROP(attr->name, attr->data, context->data->accessors); + } + cgltf_write_line(context, "}"); + } + cgltf_write_line(context, "]"); + } + cgltf_write_extras(context, &prim->extras); + + if (prim->has_draco_mesh_compression || prim->mappings_count > 0) + { + cgltf_write_line(context, "\"extensions\": {"); + + if (prim->has_draco_mesh_compression) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_DRACO_MESH_COMPRESSION; + if (prim->attributes_count == 0 || prim->indices == 0) + { + context->required_extension_flags |= CGLTF_EXTENSION_FLAG_DRACO_MESH_COMPRESSION; + } + + cgltf_write_line(context, "\"KHR_draco_mesh_compression\": {"); + CGLTF_WRITE_IDXPROP("bufferView", prim->draco_mesh_compression.buffer_view, context->data->buffer_views); + cgltf_write_line(context, "\"attributes\": {"); + for (cgltf_size i = 0; i < prim->draco_mesh_compression.attributes_count; ++i) + { + const cgltf_attribute* attr = prim->draco_mesh_compression.attributes + i; + CGLTF_WRITE_IDXPROP(attr->name, attr->data, context->data->accessors); + } + cgltf_write_line(context, "}"); + cgltf_write_line(context, "}"); + } + + if (prim->mappings_count > 0) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_VARIANTS; + cgltf_write_line(context, "\"KHR_materials_variants\": {"); + cgltf_write_line(context, "\"mappings\": ["); + for (cgltf_size i = 0; i < prim->mappings_count; ++i) + { + const cgltf_material_mapping* map = prim->mappings + i; + cgltf_write_line(context, "{"); + CGLTF_WRITE_IDXPROP("material", map->material, context->data->materials); + + cgltf_write_indent(context); + CGLTF_SPRINTF("\"variants\": [%d]", (int)map->variant); + context->needs_comma = 1; + + cgltf_write_extras(context, &map->extras); + cgltf_write_line(context, "}"); + } + cgltf_write_line(context, "]"); + cgltf_write_line(context, "}"); + } + + cgltf_write_line(context, "}"); + } +} + +static void cgltf_write_mesh(cgltf_write_context* context, const cgltf_mesh* mesh) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", mesh->name); + + cgltf_write_line(context, "\"primitives\": ["); + for (cgltf_size i = 0; i < mesh->primitives_count; ++i) + { + cgltf_write_line(context, "{"); + cgltf_write_primitive(context, mesh->primitives + i); + cgltf_write_line(context, "}"); + } + cgltf_write_line(context, "]"); + + if (mesh->weights_count > 0) + { + cgltf_write_floatarrayprop(context, "weights", mesh->weights, mesh->weights_count); + } + + cgltf_write_extras(context, &mesh->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_buffer_view(cgltf_write_context* context, const cgltf_buffer_view* view) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", view->name); + CGLTF_WRITE_IDXPROP("buffer", view->buffer, context->data->buffers); + cgltf_write_sizeprop(context, "byteLength", view->size, (cgltf_size)-1); + cgltf_write_sizeprop(context, "byteOffset", view->offset, 0); + cgltf_write_sizeprop(context, "byteStride", view->stride, 0); + // NOTE: We skip writing "target" because the spec says its usage can be inferred. + cgltf_write_extras(context, &view->extras); + cgltf_write_line(context, "}"); +} + + +static void cgltf_write_buffer(cgltf_write_context* context, const cgltf_buffer* buffer) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", buffer->name); + cgltf_write_strprop(context, "uri", buffer->uri); + cgltf_write_sizeprop(context, "byteLength", buffer->size, (cgltf_size)-1); + cgltf_write_extras(context, &buffer->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_material(cgltf_write_context* context, const cgltf_material* material) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", material->name); + if (material->alpha_mode == cgltf_alpha_mode_mask) + { + cgltf_write_floatprop(context, "alphaCutoff", material->alpha_cutoff, 0.5f); + } + cgltf_write_boolprop_optional(context, "doubleSided", (bool)material->double_sided, false); + // cgltf_write_boolprop_optional(context, "unlit", material->unlit, false); + + if (material->unlit) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_UNLIT; + } + + if (material->has_pbr_specular_glossiness) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_SPECULAR_GLOSSINESS; + } + + if (material->has_clearcoat) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_CLEARCOAT; + } + + if (material->has_transmission) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_TRANSMISSION; + } + + if (material->has_volume) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_VOLUME; + } + + if (material->has_ior) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_IOR; + } + + if (material->has_specular) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_SPECULAR; + } + + if (material->has_sheen) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_SHEEN; + } + + if (material->has_emissive_strength) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_EMISSIVE_STRENGTH; + } + + if (material->has_iridescence) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_IRIDESCENCE; + } + + if (material->has_pbr_metallic_roughness) + { + const cgltf_pbr_metallic_roughness* params = &material->pbr_metallic_roughness; + cgltf_write_line(context, "\"pbrMetallicRoughness\": {"); + CGLTF_WRITE_TEXTURE_INFO("baseColorTexture", params->base_color_texture); + CGLTF_WRITE_TEXTURE_INFO("metallicRoughnessTexture", params->metallic_roughness_texture); + cgltf_write_floatprop(context, "metallicFactor", params->metallic_factor, 1.0f); + cgltf_write_floatprop(context, "roughnessFactor", params->roughness_factor, 1.0f); + if (cgltf_check_floatarray(params->base_color_factor, 4, 1.0f)) + { + cgltf_write_floatarrayprop(context, "baseColorFactor", params->base_color_factor, 4); + } + cgltf_write_extras(context, ¶ms->extras); + cgltf_write_line(context, "}"); + } + + if (material->unlit || material->has_pbr_specular_glossiness || material->has_clearcoat || material->has_ior || material->has_specular || material->has_transmission || material->has_sheen || material->has_volume || material->has_emissive_strength || material->has_iridescence) + { + cgltf_write_line(context, "\"extensions\": {"); + if (material->has_clearcoat) + { + const cgltf_clearcoat* params = &material->clearcoat; + cgltf_write_line(context, "\"KHR_materials_clearcoat\": {"); + CGLTF_WRITE_TEXTURE_INFO("clearcoatTexture", params->clearcoat_texture); + CGLTF_WRITE_TEXTURE_INFO("clearcoatRoughnessTexture", params->clearcoat_roughness_texture); + CGLTF_WRITE_TEXTURE_INFO("clearcoatNormalTexture", params->clearcoat_normal_texture); + cgltf_write_floatprop(context, "clearcoatFactor", params->clearcoat_factor, 0.0f); + cgltf_write_floatprop(context, "clearcoatRoughnessFactor", params->clearcoat_roughness_factor, 0.0f); + cgltf_write_line(context, "}"); + } + if (material->has_ior) + { + const cgltf_ior* params = &material->ior; + cgltf_write_line(context, "\"KHR_materials_ior\": {"); + cgltf_write_floatprop(context, "ior", params->ior, 1.5f); + cgltf_write_line(context, "}"); + } + if (material->has_specular) + { + const cgltf_specular* params = &material->specular; + cgltf_write_line(context, "\"KHR_materials_specular\": {"); + CGLTF_WRITE_TEXTURE_INFO("specularTexture", params->specular_texture); + CGLTF_WRITE_TEXTURE_INFO("specularColorTexture", params->specular_color_texture); + cgltf_write_floatprop(context, "specularFactor", params->specular_factor, 1.0f); + if (cgltf_check_floatarray(params->specular_color_factor, 3, 1.0f)) + { + cgltf_write_floatarrayprop(context, "specularColorFactor", params->specular_color_factor, 3); + } + cgltf_write_line(context, "}"); + } + if (material->has_transmission) + { + const cgltf_transmission* params = &material->transmission; + cgltf_write_line(context, "\"KHR_materials_transmission\": {"); + CGLTF_WRITE_TEXTURE_INFO("transmissionTexture", params->transmission_texture); + cgltf_write_floatprop(context, "transmissionFactor", params->transmission_factor, 0.0f); + cgltf_write_line(context, "}"); + } + if (material->has_volume) + { + const cgltf_volume* params = &material->volume; + cgltf_write_line(context, "\"KHR_materials_volume\": {"); + CGLTF_WRITE_TEXTURE_INFO("thicknessTexture", params->thickness_texture); + cgltf_write_floatprop(context, "thicknessFactor", params->thickness_factor, 0.0f); + if (cgltf_check_floatarray(params->attenuation_color, 3, 1.0f)) + { + cgltf_write_floatarrayprop(context, "attenuationColor", params->attenuation_color, 3); + } + if (params->attenuation_distance < FLT_MAX) + { + cgltf_write_floatprop(context, "attenuationDistance", params->attenuation_distance, FLT_MAX); + } + cgltf_write_line(context, "}"); + } + if (material->has_sheen) + { + const cgltf_sheen* params = &material->sheen; + cgltf_write_line(context, "\"KHR_materials_sheen\": {"); + CGLTF_WRITE_TEXTURE_INFO("sheenColorTexture", params->sheen_color_texture); + CGLTF_WRITE_TEXTURE_INFO("sheenRoughnessTexture", params->sheen_roughness_texture); + if (cgltf_check_floatarray(params->sheen_color_factor, 3, 0.0f)) + { + cgltf_write_floatarrayprop(context, "sheenColorFactor", params->sheen_color_factor, 3); + } + cgltf_write_floatprop(context, "sheenRoughnessFactor", params->sheen_roughness_factor, 0.0f); + cgltf_write_line(context, "}"); + } + if (material->has_pbr_specular_glossiness) + { + const cgltf_pbr_specular_glossiness* params = &material->pbr_specular_glossiness; + cgltf_write_line(context, "\"KHR_materials_pbrSpecularGlossiness\": {"); + CGLTF_WRITE_TEXTURE_INFO("diffuseTexture", params->diffuse_texture); + CGLTF_WRITE_TEXTURE_INFO("specularGlossinessTexture", params->specular_glossiness_texture); + if (cgltf_check_floatarray(params->diffuse_factor, 4, 1.0f)) + { + cgltf_write_floatarrayprop(context, "diffuseFactor", params->diffuse_factor, 4); + } + if (cgltf_check_floatarray(params->specular_factor, 3, 1.0f)) + { + cgltf_write_floatarrayprop(context, "specularFactor", params->specular_factor, 3); + } + cgltf_write_floatprop(context, "glossinessFactor", params->glossiness_factor, 1.0f); + cgltf_write_line(context, "}"); + } + if (material->unlit) + { + cgltf_write_line(context, "\"KHR_materials_unlit\": {}"); + } + if (material->has_emissive_strength) + { + cgltf_write_line(context, "\"KHR_materials_emissive_strength\": {"); + const cgltf_emissive_strength* params = &material->emissive_strength; + cgltf_write_floatprop(context, "emissiveStrength", params->emissive_strength, 1.f); + cgltf_write_line(context, "}"); + } + if (material->has_iridescence) + { + cgltf_write_line(context, "\"KHR_materials_iridescence\": {"); + const cgltf_iridescence* params = &material->iridescence; + cgltf_write_floatprop(context, "iridescenceFactor", params->iridescence_factor, 0.f); + CGLTF_WRITE_TEXTURE_INFO("iridescenceTexture", params->iridescence_texture); + cgltf_write_floatprop(context, "iridescenceIor", params->iridescence_ior, 1.3f); + cgltf_write_floatprop(context, "iridescenceThicknessMinimum", params->iridescence_thickness_min, 100.f); + cgltf_write_floatprop(context, "iridescenceThicknessMaximum", params->iridescence_thickness_max, 400.f); + CGLTF_WRITE_TEXTURE_INFO("iridescenceThicknessTexture", params->iridescence_thickness_texture); + cgltf_write_line(context, "}"); + } + cgltf_write_line(context, "}"); + } + + CGLTF_WRITE_TEXTURE_INFO("normalTexture", material->normal_texture); + CGLTF_WRITE_TEXTURE_INFO("occlusionTexture", material->occlusion_texture); + CGLTF_WRITE_TEXTURE_INFO("emissiveTexture", material->emissive_texture); + if (cgltf_check_floatarray(material->emissive_factor, 3, 0.0f)) + { + cgltf_write_floatarrayprop(context, "emissiveFactor", material->emissive_factor, 3); + } + cgltf_write_strprop(context, "alphaMode", cgltf_str_from_alpha_mode(material->alpha_mode)); + cgltf_write_extras(context, &material->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_image(cgltf_write_context* context, const cgltf_image* image) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", image->name); + cgltf_write_strprop(context, "uri", image->uri); + CGLTF_WRITE_IDXPROP("bufferView", image->buffer_view, context->data->buffer_views); + cgltf_write_strprop(context, "mimeType", image->mime_type); + cgltf_write_extras(context, &image->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_texture(cgltf_write_context* context, const cgltf_texture* texture) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", texture->name); + CGLTF_WRITE_IDXPROP("source", texture->image, context->data->images); + CGLTF_WRITE_IDXPROP("sampler", texture->sampler, context->data->samplers); + + if (texture->has_basisu) + { + cgltf_write_line(context, "\"extensions\": {"); + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_BASISU; + cgltf_write_line(context, "\"KHR_texture_basisu\": {"); + CGLTF_WRITE_IDXPROP("source", texture->basisu_image, context->data->images); + cgltf_write_line(context, "}"); + } + cgltf_write_line(context, "}"); + } + cgltf_write_extras(context, &texture->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_skin(cgltf_write_context* context, const cgltf_skin* skin) +{ + cgltf_write_line(context, "{"); + CGLTF_WRITE_IDXPROP("skeleton", skin->skeleton, context->data->nodes); + CGLTF_WRITE_IDXPROP("inverseBindMatrices", skin->inverse_bind_matrices, context->data->accessors); + CGLTF_WRITE_IDXARRPROP("joints", skin->joints_count, skin->joints, context->data->nodes); + cgltf_write_strprop(context, "name", skin->name); + cgltf_write_extras(context, &skin->extras); + cgltf_write_line(context, "}"); +} + +static const char* cgltf_write_str_path_type(cgltf_animation_path_type path_type) +{ + switch (path_type) + { + case cgltf_animation_path_type_translation: + return "translation"; + case cgltf_animation_path_type_rotation: + return "rotation"; + case cgltf_animation_path_type_scale: + return "scale"; + case cgltf_animation_path_type_weights: + return "weights"; + default: + break; + } + return "invalid"; +} + +static const char* cgltf_write_str_interpolation_type(cgltf_interpolation_type interpolation_type) +{ + switch (interpolation_type) + { + case cgltf_interpolation_type_linear: + return "LINEAR"; + case cgltf_interpolation_type_step: + return "STEP"; + case cgltf_interpolation_type_cubic_spline: + return "CUBICSPLINE"; + default: + break; + } + return "invalid"; +} + +static void cgltf_write_path_type(cgltf_write_context* context, const char *label, cgltf_animation_path_type path_type) +{ + cgltf_write_strprop(context, label, cgltf_write_str_path_type(path_type)); +} + +static void cgltf_write_interpolation_type(cgltf_write_context* context, const char *label, cgltf_interpolation_type interpolation_type) +{ + cgltf_write_strprop(context, label, cgltf_write_str_interpolation_type(interpolation_type)); +} + +static void cgltf_write_animation_sampler(cgltf_write_context* context, const cgltf_animation_sampler* animation_sampler) +{ + cgltf_write_line(context, "{"); + cgltf_write_interpolation_type(context, "interpolation", animation_sampler->interpolation); + CGLTF_WRITE_IDXPROP("input", animation_sampler->input, context->data->accessors); + CGLTF_WRITE_IDXPROP("output", animation_sampler->output, context->data->accessors); + cgltf_write_extras(context, &animation_sampler->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_animation_channel(cgltf_write_context* context, const cgltf_animation* animation, const cgltf_animation_channel* animation_channel) +{ + cgltf_write_line(context, "{"); + CGLTF_WRITE_IDXPROP("sampler", animation_channel->sampler, animation->samplers); + cgltf_write_line(context, "\"target\": {"); + CGLTF_WRITE_IDXPROP("node", animation_channel->target_node, context->data->nodes); + cgltf_write_path_type(context, "path", animation_channel->target_path); + cgltf_write_line(context, "}"); + cgltf_write_extras(context, &animation_channel->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_animation(cgltf_write_context* context, const cgltf_animation* animation) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", animation->name); + + if (animation->samplers_count > 0) + { + cgltf_write_line(context, "\"samplers\": ["); + for (cgltf_size i = 0; i < animation->samplers_count; ++i) + { + cgltf_write_animation_sampler(context, animation->samplers + i); + } + cgltf_write_line(context, "]"); + } + if (animation->channels_count > 0) + { + cgltf_write_line(context, "\"channels\": ["); + for (cgltf_size i = 0; i < animation->channels_count; ++i) + { + cgltf_write_animation_channel(context, animation, animation->channels + i); + } + cgltf_write_line(context, "]"); + } + cgltf_write_extras(context, &animation->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_sampler(cgltf_write_context* context, const cgltf_sampler* sampler) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", sampler->name); + cgltf_write_intprop(context, "magFilter", sampler->mag_filter, 0); + cgltf_write_intprop(context, "minFilter", sampler->min_filter, 0); + cgltf_write_intprop(context, "wrapS", sampler->wrap_s, 10497); + cgltf_write_intprop(context, "wrapT", sampler->wrap_t, 10497); + cgltf_write_extras(context, &sampler->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_node(cgltf_write_context* context, const cgltf_node* node) +{ + cgltf_write_line(context, "{"); + CGLTF_WRITE_IDXARRPROP("children", node->children_count, node->children, context->data->nodes); + CGLTF_WRITE_IDXPROP("mesh", node->mesh, context->data->meshes); + cgltf_write_strprop(context, "name", node->name); + if (node->has_matrix) + { + cgltf_write_floatarrayprop(context, "matrix", node->matrix, 16); + } + if (node->has_translation) + { + cgltf_write_floatarrayprop(context, "translation", node->translation, 3); + } + if (node->has_rotation) + { + cgltf_write_floatarrayprop(context, "rotation", node->rotation, 4); + } + if (node->has_scale) + { + cgltf_write_floatarrayprop(context, "scale", node->scale, 3); + } + if (node->skin) + { + CGLTF_WRITE_IDXPROP("skin", node->skin, context->data->skins); + } + + bool has_extension = node->light || (node->has_mesh_gpu_instancing && node->mesh_gpu_instancing.attributes_count > 0); + if(has_extension) + cgltf_write_line(context, "\"extensions\": {"); + + if (node->light) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_LIGHTS_PUNCTUAL; + cgltf_write_line(context, "\"KHR_lights_punctual\": {"); + CGLTF_WRITE_IDXPROP("light", node->light, context->data->lights); + cgltf_write_line(context, "}"); + } + + if (node->has_mesh_gpu_instancing && node->mesh_gpu_instancing.attributes_count > 0) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MESH_GPU_INSTANCING; + context->required_extension_flags |= CGLTF_EXTENSION_FLAG_MESH_GPU_INSTANCING; + + cgltf_write_line(context, "\"EXT_mesh_gpu_instancing\": {"); + { + CGLTF_WRITE_IDXPROP("bufferView", node->mesh_gpu_instancing.buffer_view, context->data->buffer_views); + cgltf_write_line(context, "\"attributes\": {"); + { + for (cgltf_size i = 0; i < node->mesh_gpu_instancing.attributes_count; ++i) + { + const cgltf_attribute* attr = node->mesh_gpu_instancing.attributes + i; + CGLTF_WRITE_IDXPROP(attr->name, attr->data, context->data->accessors); + } + } + cgltf_write_line(context, "}"); + } + cgltf_write_line(context, "}"); + } + + if (has_extension) + cgltf_write_line(context, "}"); + + if (node->weights_count > 0) + { + cgltf_write_floatarrayprop(context, "weights", node->weights, node->weights_count); + } + + if (node->camera) + { + CGLTF_WRITE_IDXPROP("camera", node->camera, context->data->cameras); + } + + cgltf_write_extras(context, &node->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_scene(cgltf_write_context* context, const cgltf_scene* scene) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", scene->name); + CGLTF_WRITE_IDXARRPROP("nodes", scene->nodes_count, scene->nodes, context->data->nodes); + cgltf_write_extras(context, &scene->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_accessor(cgltf_write_context* context, const cgltf_accessor* accessor) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", accessor->name); + CGLTF_WRITE_IDXPROP("bufferView", accessor->buffer_view, context->data->buffer_views); + cgltf_write_intprop(context, "componentType", cgltf_int_from_component_type(accessor->component_type), 0); + cgltf_write_strprop(context, "type", cgltf_str_from_type(accessor->type)); + cgltf_size dim = cgltf_dim_from_type(accessor->type); + cgltf_write_boolprop_optional(context, "normalized", (bool)accessor->normalized, false); + cgltf_write_sizeprop(context, "byteOffset", (int)accessor->offset, 0); + cgltf_write_intprop(context, "count", (int)accessor->count, -1); + if (accessor->has_min) + { + cgltf_write_floatarrayprop(context, "min", accessor->min, dim); + } + if (accessor->has_max) + { + cgltf_write_floatarrayprop(context, "max", accessor->max, dim); + } + if (accessor->is_sparse) + { + cgltf_write_line(context, "\"sparse\": {"); + cgltf_write_intprop(context, "count", (int)accessor->sparse.count, 0); + cgltf_write_line(context, "\"indices\": {"); + cgltf_write_sizeprop(context, "byteOffset", (int)accessor->sparse.indices_byte_offset, 0); + CGLTF_WRITE_IDXPROP("bufferView", accessor->sparse.indices_buffer_view, context->data->buffer_views); + cgltf_write_intprop(context, "componentType", cgltf_int_from_component_type(accessor->sparse.indices_component_type), 0); + cgltf_write_extras(context, &accessor->sparse.indices_extras); + cgltf_write_line(context, "}"); + cgltf_write_line(context, "\"values\": {"); + cgltf_write_sizeprop(context, "byteOffset", (int)accessor->sparse.values_byte_offset, 0); + CGLTF_WRITE_IDXPROP("bufferView", accessor->sparse.values_buffer_view, context->data->buffer_views); + cgltf_write_extras(context, &accessor->sparse.values_extras); + cgltf_write_line(context, "}"); + cgltf_write_extras(context, &accessor->sparse.extras); + cgltf_write_line(context, "}"); + } + cgltf_write_extras(context, &accessor->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_camera(cgltf_write_context* context, const cgltf_camera* camera) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "type", cgltf_str_from_camera_type(camera->type)); + if (camera->name) + { + cgltf_write_strprop(context, "name", camera->name); + } + + if (camera->type == cgltf_camera_type_orthographic) + { + cgltf_write_line(context, "\"orthographic\": {"); + cgltf_write_floatprop(context, "xmag", camera->data.orthographic.xmag, -1.0f); + cgltf_write_floatprop(context, "ymag", camera->data.orthographic.ymag, -1.0f); + cgltf_write_floatprop(context, "zfar", camera->data.orthographic.zfar, -1.0f); + cgltf_write_floatprop(context, "znear", camera->data.orthographic.znear, -1.0f); + cgltf_write_extras(context, &camera->data.orthographic.extras); + cgltf_write_line(context, "}"); + } + else if (camera->type == cgltf_camera_type_perspective) + { + cgltf_write_line(context, "\"perspective\": {"); + + if (camera->data.perspective.has_aspect_ratio) { + cgltf_write_floatprop(context, "aspectRatio", camera->data.perspective.aspect_ratio, -1.0f); + } + + cgltf_write_floatprop(context, "yfov", camera->data.perspective.yfov, -1.0f); + + if (camera->data.perspective.has_zfar) { + cgltf_write_floatprop(context, "zfar", camera->data.perspective.zfar, -1.0f); + } + + cgltf_write_floatprop(context, "znear", camera->data.perspective.znear, -1.0f); + cgltf_write_extras(context, &camera->data.perspective.extras); + cgltf_write_line(context, "}"); + } + cgltf_write_extras(context, &camera->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_light(cgltf_write_context* context, const cgltf_light* light) +{ + context->extension_flags |= CGLTF_EXTENSION_FLAG_LIGHTS_PUNCTUAL; + + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "type", cgltf_str_from_light_type(light->type)); + if (light->name) + { + cgltf_write_strprop(context, "name", light->name); + } + if (cgltf_check_floatarray(light->color, 3, 1.0f)) + { + cgltf_write_floatarrayprop(context, "color", light->color, 3); + } + cgltf_write_floatprop(context, "intensity", light->intensity, 1.0f); + cgltf_write_floatprop(context, "range", light->range, 0.0f); + + if (light->type == cgltf_light_type_spot) + { + cgltf_write_line(context, "\"spot\": {"); + cgltf_write_floatprop(context, "innerConeAngle", light->spot_inner_cone_angle, 0.0f); + cgltf_write_floatprop(context, "outerConeAngle", light->spot_outer_cone_angle, 3.14159265358979323846f/4.0f); + cgltf_write_line(context, "}"); + } + cgltf_write_line(context, "}"); +} + +static void cgltf_write_variant(cgltf_write_context* context, const cgltf_material_variant* variant) +{ + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_VARIANTS; + + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", variant->name); + cgltf_write_extras(context, &variant->extras); + cgltf_write_line(context, "}"); +} + +cgltf_result cgltf_write_file(const cgltf_options* options, const char* path, const cgltf_data* data) +{ + cgltf_size expected = cgltf_write(options, NULL, 0, data); + char* buffer = (char*) malloc(expected); + cgltf_size actual = cgltf_write(options, buffer, expected, data); + if (expected != actual) { + fprintf(stderr, "Error: expected %zu bytes but wrote %zu bytes.\n", expected, actual); + } + FILE* file = fopen(path, "wt"); + if (!file) + { + return cgltf_result_file_not_found; + } + // Note that cgltf_write() includes a null terminator, which we omit from the file content. + fwrite(buffer, actual - 1, 1, file); + fclose(file); + free(buffer); + return cgltf_result_success; +} + +static void cgltf_write_extensions(cgltf_write_context* context, uint32_t extension_flags) +{ + if (extension_flags & CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM) { + cgltf_write_stritem(context, "KHR_texture_transform"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_UNLIT) { + cgltf_write_stritem(context, "KHR_materials_unlit"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_SPECULAR_GLOSSINESS) { + cgltf_write_stritem(context, "KHR_materials_pbrSpecularGlossiness"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_LIGHTS_PUNCTUAL) { + cgltf_write_stritem(context, "KHR_lights_punctual"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_DRACO_MESH_COMPRESSION) { + cgltf_write_stritem(context, "KHR_draco_mesh_compression"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_CLEARCOAT) { + cgltf_write_stritem(context, "KHR_materials_clearcoat"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_IOR) { + cgltf_write_stritem(context, "KHR_materials_ior"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_SPECULAR) { + cgltf_write_stritem(context, "KHR_materials_specular"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_TRANSMISSION) { + cgltf_write_stritem(context, "KHR_materials_transmission"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_SHEEN) { + cgltf_write_stritem(context, "KHR_materials_sheen"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_VARIANTS) { + cgltf_write_stritem(context, "KHR_materials_variants"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_VOLUME) { + cgltf_write_stritem(context, "KHR_materials_volume"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_TEXTURE_BASISU) { + cgltf_write_stritem(context, "KHR_texture_basisu"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_EMISSIVE_STRENGTH) { + cgltf_write_stritem(context, "KHR_materials_emissive_strength"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_IRIDESCENCE) { + cgltf_write_stritem(context, "KHR_materials_iridescence"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MESH_GPU_INSTANCING) { + cgltf_write_stritem(context, "EXT_mesh_gpu_instancing"); + } +} + +cgltf_size cgltf_write(const cgltf_options* options, char* buffer, cgltf_size size, const cgltf_data* data) +{ + (void)options; + cgltf_write_context ctx; + ctx.buffer = buffer; + ctx.buffer_size = size; + ctx.remaining = size; + ctx.cursor = buffer; + ctx.chars_written = 0; + ctx.data = data; + ctx.depth = 1; + ctx.indent = " "; + ctx.needs_comma = 0; + ctx.extension_flags = 0; + ctx.required_extension_flags = 0; + + cgltf_write_context* context = &ctx; + + CGLTF_SPRINTF("{"); + + if (data->accessors_count > 0) + { + cgltf_write_line(context, "\"accessors\": ["); + for (cgltf_size i = 0; i < data->accessors_count; ++i) + { + cgltf_write_accessor(context, data->accessors + i); + } + cgltf_write_line(context, "]"); + } + + cgltf_write_asset(context, &data->asset); + + if (data->buffer_views_count > 0) + { + cgltf_write_line(context, "\"bufferViews\": ["); + for (cgltf_size i = 0; i < data->buffer_views_count; ++i) + { + cgltf_write_buffer_view(context, data->buffer_views + i); + } + cgltf_write_line(context, "]"); + } + + if (data->buffers_count > 0) + { + cgltf_write_line(context, "\"buffers\": ["); + for (cgltf_size i = 0; i < data->buffers_count; ++i) + { + cgltf_write_buffer(context, data->buffers + i); + } + cgltf_write_line(context, "]"); + } + + if (data->images_count > 0) + { + cgltf_write_line(context, "\"images\": ["); + for (cgltf_size i = 0; i < data->images_count; ++i) + { + cgltf_write_image(context, data->images + i); + } + cgltf_write_line(context, "]"); + } + + if (data->meshes_count > 0) + { + cgltf_write_line(context, "\"meshes\": ["); + for (cgltf_size i = 0; i < data->meshes_count; ++i) + { + cgltf_write_mesh(context, data->meshes + i); + } + cgltf_write_line(context, "]"); + } + + if (data->materials_count > 0) + { + cgltf_write_line(context, "\"materials\": ["); + for (cgltf_size i = 0; i < data->materials_count; ++i) + { + cgltf_write_material(context, data->materials + i); + } + cgltf_write_line(context, "]"); + } + + if (data->nodes_count > 0) + { + cgltf_write_line(context, "\"nodes\": ["); + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + cgltf_write_node(context, data->nodes + i); + } + cgltf_write_line(context, "]"); + } + + if (data->samplers_count > 0) + { + cgltf_write_line(context, "\"samplers\": ["); + for (cgltf_size i = 0; i < data->samplers_count; ++i) + { + cgltf_write_sampler(context, data->samplers + i); + } + cgltf_write_line(context, "]"); + } + + CGLTF_WRITE_IDXPROP("scene", data->scene, data->scenes); + + if (data->scenes_count > 0) + { + cgltf_write_line(context, "\"scenes\": ["); + for (cgltf_size i = 0; i < data->scenes_count; ++i) + { + cgltf_write_scene(context, data->scenes + i); + } + cgltf_write_line(context, "]"); + } + + if (data->textures_count > 0) + { + cgltf_write_line(context, "\"textures\": ["); + for (cgltf_size i = 0; i < data->textures_count; ++i) + { + cgltf_write_texture(context, data->textures + i); + } + cgltf_write_line(context, "]"); + } + + if (data->skins_count > 0) + { + cgltf_write_line(context, "\"skins\": ["); + for (cgltf_size i = 0; i < data->skins_count; ++i) + { + cgltf_write_skin(context, data->skins + i); + } + cgltf_write_line(context, "]"); + } + + if (data->animations_count > 0) + { + cgltf_write_line(context, "\"animations\": ["); + for (cgltf_size i = 0; i < data->animations_count; ++i) + { + cgltf_write_animation(context, data->animations + i); + } + cgltf_write_line(context, "]"); + } + + if (data->cameras_count > 0) + { + cgltf_write_line(context, "\"cameras\": ["); + for (cgltf_size i = 0; i < data->cameras_count; ++i) + { + cgltf_write_camera(context, data->cameras + i); + } + cgltf_write_line(context, "]"); + } + + if (data->lights_count > 0 || data->variants_count > 0) + { + cgltf_write_line(context, "\"extensions\": {"); + + if (data->lights_count > 0) + { + cgltf_write_line(context, "\"KHR_lights_punctual\": {"); + cgltf_write_line(context, "\"lights\": ["); + for (cgltf_size i = 0; i < data->lights_count; ++i) + { + cgltf_write_light(context, data->lights + i); + } + cgltf_write_line(context, "]"); + cgltf_write_line(context, "}"); + } + + if (data->variants_count) + { + cgltf_write_line(context, "\"KHR_materials_variants\": {"); + cgltf_write_line(context, "\"variants\": ["); + for (cgltf_size i = 0; i < data->variants_count; ++i) + { + cgltf_write_variant(context, data->variants + i); + } + cgltf_write_line(context, "]"); + cgltf_write_line(context, "}"); + } + + cgltf_write_line(context, "}"); + } + + if (context->extension_flags != 0) + { + cgltf_write_line(context, "\"extensionsUsed\": ["); + cgltf_write_extensions(context, context->extension_flags); + cgltf_write_line(context, "]"); + } + + if (context->required_extension_flags != 0) + { + cgltf_write_line(context, "\"extensionsRequired\": ["); + cgltf_write_extensions(context, context->required_extension_flags); + cgltf_write_line(context, "]"); + } + + cgltf_write_extras(context, &data->extras); + + CGLTF_SPRINTF("\n}\n"); + + // snprintf does not include the null terminator in its return value, so be sure to include it + // in the returned byte count. + return 1 + ctx.chars_written; +} + +#endif /* #ifdef CGLTF_WRITE_IMPLEMENTATION */ + +/* cgltf is distributed under MIT license: + * + * Copyright (c) 2019-2021 Philip Rideout + + * 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/source/engine/thirdparty/cgltf/fuzz/data/BadBasisU.gltf b/source/engine/thirdparty/cgltf/fuzz/data/BadBasisU.gltf new file mode 100644 index 0000000..e108e39 --- /dev/null +++ b/source/engine/thirdparty/cgltf/fuzz/data/BadBasisU.gltf @@ -0,0 +1 @@ +{"textures":[{"extensions":{"KHR_texture_basisu":{""}}:{""""},""}]} \ No newline at end of file diff --git a/source/engine/thirdparty/cgltf/fuzz/data/Box.glb b/source/engine/thirdparty/cgltf/fuzz/data/Box.glb new file mode 100644 index 0000000000000000000000000000000000000000..95ec886b6b92b134291fd41d34ac9d5349306e0a GIT binary patch literal 1664 zcmb7EOK;jh5S}DW+O$p6v`u^8GeNd_1bm4oEftl43Q#VHgE0$OGB&bJ+Q_oRvHz-n zq(7!Jix*Y|3Dweg=e6_A%bt4u#xVe_&H(fXY|f(@CPIkBiUbn22;I3GyAPRY#|SxE#v~@J z-RZV!7Blr6`_bt&`^`?9nFdzn`eWB2AFOMR#W1rd(&eFRdl`st&r#1>1WTZ{gEyie zT(@AfoJ@Fl@A97_$mlWVoykNr7-KrYd=dEEkNb}c3{ujK0x6e1_PSp7z%Cj)?0>TUa1Jlw h71BAph6{KDmq-`z7OvnOyhpl%4{!}1;S +# + +"0" +",0" +":0" +"0:" +"-1.2e+3" + +"true" +"false" +"null" + +"\"\"" +",\"\"" +":\"\"" +"\"\":" + +"{}" +",{}" +":{}" +"{\"\":0}" +"{{}}" + +"[]" +",[]" +":[]" +"[0]" +"[[]]" + +"''" +"\\" +"\\b" +"\\f" +"\\n" +"\\r" +"\\t" +"\\u0000" +"\\x00" +"\\0" +"\\uD800\\uDC00" +"\\uDBFF\\uDFFF" + +"\"\":0" +"//" +"/**/" + +# +# AFL dictionary for GLTF core +# ----------------------- + +"5120" +"5121" +"5122" +"5123" +"5125" +"5126" +"\"BLEND\"" +"\"CUBICSPLINE\"" +"\"LINEAR\"" +"\"MASK\"" +"\"MAT2\"" +"\"MAT3\"" +"\"MAT4\"" +"\"OPAQUE\"" +"\"SCALAR\"" +"\"STEP\"" +"\"VEC2\"" +"\"VEC3\"" +"\"VEC4\"" +"\"accessor\"" +"\"accessors\"" +"\"alphaCutoff\"" +"\"alphaMode\"" +"\"animations\"" +"\"aspectRatio\"" +"\"asset\"" +"\"attributes\"" +"\"baseColorFactor\"" +"\"baseColorTexture\"" +"\"bufferView\"" +"\"bufferViews\"" +"\"buffer\"" +"\"buffers\"" +"\"byteLength\"" +"\"byteOffset\"" +"\"byteStride\"" +"\"camera\"" +"\"cameras\"" +"\"channel\"" +"\"channels\"" +"\"children\"" +"\"componentType\"" +"\"copyright\"" +"\"count\"" +"\"doubleSided\"" +"\"emissiveFactor\"" +"\"emissiveTexture\"" +"\"extensionsRequired\"" +"\"extensionsUsed\"" +"\"extensions\"" +"\"extras\"" +"\"generator\"" +"\"image\"" +"\"images\"" +"\"index\"" +"\"indices\"" +"\"input\"" +"\"interpolation\"" +"\"inverseBindMatrices\"" +"\"joints\"" +"\"magFilter\"" +"\"material\"" +"\"materials\"" +"\"matrix\"" +"\"max\"" +"\"mesh\"" +"\"meshes\"" +"\"metallicFactor\"" +"\"metallicRoughnessTexture\"" +"\"mimeType\"" +"\"minFilter\"" +"\"minVersion\"" +"\"min\"" +"\"mode\"" +"\"name\"" +"\"node\"" +"\"nodes\"" +"\"normalTextureInfo\"" +"\"normalTexture\"" +"\"normalized\"" +"\"occlusionTextureInfo\"" +"\"occlusionTexture\"" +"\"orthographic\"" +"\"output\"" +"\"path\"" +"\"pbrMetallicRoughness\"" +"\"perspective\"" +"\"primitive\"" +"\"primitives\"" +"\"rotation\"" +"\"roughnessFactor\"" +"\"sampler\"" +"\"samplers\"" +"\"scale\"" +"\"scene\"" +"\"scenes\"" +"\"skeleton\"" +"\"skin\"" +"\"skins\"" +"\"source\"" +"\"sparse\"" +"\"strength\"" +"\"target\"" +"\"targets\"" +"\"texCoord\"" +"\"textureInfo\"" +"\"texture\"" +"\"textures\"" +"\"translation\"" +"\"type\"" +"\"uri\"" +"\"values\"" +"\"version\"" +"\"weights\"" +"\"wrapS\"" +"\"wrapT\"" +"\"xmag\"" +"\"yfov\"" +"\"ymag\"" +"\"zfar\"" +"\"znear\"" + +# +# AFL dictionary for GLTF extensions +# ----------------------- +"\"KHR_materials_unlit\"" +"\"KHR_texture_basisu\"" + +"\"KHR_materials_pbrSpecularGlossiness\"" +"\"diffuseFactor\"" +"\"diffuseTexture\"" +"\"specularFactor\"" +"\"glossinessFactor\"" +"\"specularGlossinessTexture\"" + +"\"KHR_texture_transform\"" +"\"offset\"" +"\"rotation\"" +"\"scale\"" +"\"texCoord\"" + +"\"KHR_lights_punctual\"" +"\"color\"" +"\"intensity\"" +"\"type\"" +"\"range\"" +"\"innerConeAngle\"" +"\"outerConeAngle\"" + +"\"KHR_materials_transmission\"" +"\"transmissionFactor\"" +"\"transmissionTexture\"" + +"\"KHR_materials_volume\"" +"\"thicknessFactor\"" +"\"thicknessTexture\"" +"\"attenuationColor\"" +"\"attenuationDistance\"" + +"\"KHR_materials_sheen\"" +"\"sheenColorFactor\"" +"\"sheenColorTexture\"" +"\"sheenRoughnessFactor\"" +"\"sheenRoughnessTexture\"" + +"\"KHR_materials_emissive_strength\"" +"\"emissiveStrength"\"" diff --git a/source/engine/thirdparty/cgltf/fuzz/main.c b/source/engine/thirdparty/cgltf/fuzz/main.c new file mode 100644 index 0000000..e3b75d1 --- /dev/null +++ b/source/engine/thirdparty/cgltf/fuzz/main.c @@ -0,0 +1,22 @@ +/* How to fuzz: + +clang main.c -O2 -g -fsanitize=address,fuzzer -o fuzz +cp -r data temp +./fuzz temp/ -dict=gltf.dict -jobs=12 -workers=12 + +*/ +#define CGLTF_IMPLEMENTATION +#include "../cgltf.h" + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) +{ + cgltf_options options = {cgltf_file_type_invalid}; + cgltf_data* data = NULL; + cgltf_result res = cgltf_parse(&options, Data, Size, &data); + if (res == cgltf_result_success) + { + cgltf_validate(data); + cgltf_free(data); + } + return 0; +} diff --git a/source/engine/thirdparty/cgltf/test/CMakeLists.txt b/source/engine/thirdparty/cgltf/test/CMakeLists.txt new file mode 100644 index 0000000..aa31730 --- /dev/null +++ b/source/engine/thirdparty/cgltf/test/CMakeLists.txt @@ -0,0 +1,68 @@ +cmake_minimum_required( VERSION 2.8 ) + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) + +set( EXE_NAME cgltf_test ) +add_executable( ${EXE_NAME} main.c ) +set_property( TARGET ${EXE_NAME} PROPERTY C_STANDARD 99 ) +if(MSVC) + target_compile_options(${EXE_NAME} PRIVATE /W4 /WX) + add_definitions( -D_CRT_SECURE_NO_WARNINGS) +else() + target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) + target_compile_options(${EXE_NAME} PUBLIC -fsanitize=address) + target_link_options(${EXE_NAME} PUBLIC -fsanitize=address) +endif() +install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin ) + +set( EXE_NAME test_conversion ) +add_executable( ${EXE_NAME} test_conversion.cpp ) +set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 ) +if(MSVC) + target_compile_options(${EXE_NAME} PRIVATE /W4 /WX) + add_definitions( -D_CRT_SECURE_NO_WARNINGS) +else() + target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) + target_compile_options(${EXE_NAME} PUBLIC -fsanitize=address) + target_link_options(${EXE_NAME} PUBLIC -fsanitize=address) +endif() +install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin ) + +set( EXE_NAME test_write ) +add_executable( ${EXE_NAME} test_write.cpp ) +set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 ) +if(MSVC) + target_compile_options(${EXE_NAME} PRIVATE /W4 /WX) + add_definitions( -D_CRT_SECURE_NO_WARNINGS) +else() + target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) + target_compile_options(${EXE_NAME} PUBLIC -fsanitize=address) + target_link_options(${EXE_NAME} PUBLIC -fsanitize=address) +endif() +install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin ) + +set( EXE_NAME test_math ) +add_executable( ${EXE_NAME} test_math.cpp ) +set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 ) +if(MSVC) + target_compile_options(${EXE_NAME} PRIVATE /W4 /WX) + add_definitions( -D_CRT_SECURE_NO_WARNINGS) +else() + target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) + target_compile_options(${EXE_NAME} PUBLIC -fsanitize=address) + target_link_options(${EXE_NAME} PUBLIC -fsanitize=address) +endif() +install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin ) + +set( EXE_NAME test_strings ) +add_executable( ${EXE_NAME} test_strings.cpp ) +set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 ) +if(MSVC) + target_compile_options(${EXE_NAME} PRIVATE /W4 /WX) + add_definitions( -D_CRT_SECURE_NO_WARNINGS) +else() + target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) + target_compile_options(${EXE_NAME} PUBLIC -fsanitize=address) + target_link_options(${EXE_NAME} PUBLIC -fsanitize=address) +endif() +install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin ) diff --git a/source/engine/thirdparty/cgltf/test/main.c b/source/engine/thirdparty/cgltf/test/main.c new file mode 100644 index 0000000..75543c1 --- /dev/null +++ b/source/engine/thirdparty/cgltf/test/main.c @@ -0,0 +1,38 @@ + + +#define CGLTF_IMPLEMENTATION +#include "../cgltf.h" + +#include + +int main(int argc, char** argv) +{ + if (argc < 2) + { + printf("err\n"); + return -1; + } + + cgltf_options options; + memset(&options, 0, sizeof(cgltf_options)); + cgltf_data* data = NULL; + cgltf_result result = cgltf_parse_file(&options, argv[1], &data); + + if (result == cgltf_result_success) + result = cgltf_load_buffers(&options, data, argv[1]); + + if (result == cgltf_result_success) + result = cgltf_validate(data); + + printf("Result: %d\n", result); + + if (result == cgltf_result_success) + { + printf("Type: %u\n", data->file_type); + printf("Meshes: %u\n", (unsigned)data->meshes_count); + } + + cgltf_free(data); + + return result; +} diff --git a/source/engine/thirdparty/cgltf/test/test_all.py b/source/engine/thirdparty/cgltf/test/test_all.py new file mode 100755 index 0000000..ec9b9f0 --- /dev/null +++ b/source/engine/thirdparty/cgltf/test/test_all.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python + +import os, sys +from sys import platform + +num_tested = 0 +num_errors = 0 + +def get_executable_path(name): + if platform == "win32": + return "build\\Debug\\" + name + else: + return "build/" + name + +def is_ascii(s): + return all(ord(c) < 128 for c in s) + +def collect_files(path, type, name): + global num_tested + global num_errors + exe = get_executable_path(name) + for the_file in os.listdir(path): + file_path = os.path.join(os.path.normpath(path), the_file) + if os.path.isfile(file_path): + if the_file.endswith(type): + num_tested = num_tested +1 + if is_ascii(file_path): + print("### " + name + " " + file_path) + result = os.system("{0} \"{1}\"".format(exe, file_path)) + print("### Result: " + str(result) + "\n") + if result != 0: + num_errors = num_errors + 1 + print("Error.") + sys.exit(1) + elif os.path.isdir(file_path): + collect_files(file_path, type, name) + +if __name__ == "__main__": + if not os.path.exists("build/"): + os.makedirs("build/") + os.chdir("build/") + os.system("cmake ..") + if os.system("cmake --build .") != 0: + print("Unable to build.") + exit(1) + os.chdir("..") + if not os.path.exists("glTF-Sample-Models/"): + os.system("git init glTF-Sample-Models") + os.chdir("glTF-Sample-Models") + os.system("git remote add origin https://github.com/KhronosGroup/glTF-Sample-Models.git") + os.system("git config core.sparsecheckout true") + f = open(".git/info/sparse-checkout", "w+") + f.write("2.0/*\n") + f.close() + os.system("git pull --depth=1 origin master") + os.chdir("..") + collect_files("glTF-Sample-Models/2.0/", ".glb", "cgltf_test") + collect_files("glTF-Sample-Models/2.0/", ".gltf", "cgltf_test") + collect_files("glTF-Sample-Models/2.0/", ".glb", "test_conversion") + collect_files("glTF-Sample-Models/2.0/", ".gltf", "test_conversion") + collect_files("glTF-Sample-Models/2.0/", ".gltf", "test_write") + + result = os.system(get_executable_path("test_math")) + if result != 0: + num_errors = num_errors + 1 + print("Error.") + sys.exit(1) + + result = os.system(get_executable_path("test_strings")) + if result != 0: + num_errors = num_errors + 1 + print("Error.") + sys.exit(1) + + print("Tested files: " + str(num_tested)) + print("Errors: " + str(num_errors)) diff --git a/source/engine/thirdparty/cgltf/test/test_conversion.cpp b/source/engine/thirdparty/cgltf/test/test_conversion.cpp new file mode 100644 index 0000000..e5217f3 --- /dev/null +++ b/source/engine/thirdparty/cgltf/test/test_conversion.cpp @@ -0,0 +1,92 @@ +#define CGLTF_IMPLEMENTATION +#include "../cgltf.h" + +#include +#include +#include +#include + +static bool is_near(cgltf_float a, cgltf_float b) +{ + return std::abs(a - b) < 10 * std::numeric_limits::min(); +} + +int main(int argc, char** argv) +{ + if (argc < 2) + { + printf("err\n"); + return -1; + } + + cgltf_options options = {}; + cgltf_data* data = NULL; + cgltf_result result = cgltf_parse_file(&options, argv[1], &data); + + if (result == cgltf_result_success) + result = cgltf_load_buffers(&options, data, argv[1]); + + if (strstr(argv[1], "Draco")) + { + cgltf_free(data); + return 0; + } + + if (result != cgltf_result_success) + return result; + + //const cgltf_accessor* blobs = data->accessors; + cgltf_float element_float[16]; + cgltf_uint element_uint[4]; + for (cgltf_size blob_index = 0; blob_index < data->accessors_count; ++blob_index) + { + const cgltf_accessor* blob = data->accessors + blob_index; + if (blob->is_sparse) + { + cgltf_size nfloats = cgltf_num_components(blob->type) * blob->count; + cgltf_float* dense = (cgltf_float*) malloc(nfloats * sizeof(cgltf_float)); + if (cgltf_accessor_unpack_floats(blob, dense, nfloats) < nfloats) { + printf("Unable to completely unpack a sparse accessor.\n"); + return -1; + } + free(dense); + continue; + } + if (blob->has_max && blob->has_min) + { + cgltf_float min0 = std::numeric_limits::max(); + cgltf_float max0 = std::numeric_limits::lowest(); + for (cgltf_size index = 0; index < blob->count; index++) + { + cgltf_accessor_read_float(blob, index, element_float, 16); + min0 = std::min(min0, element_float[0]); + max0 = std::max(max0, element_float[0]); + } + if (!is_near(min0, blob->min[0]) || !is_near(max0, blob->max[0])) + { + printf("Computed [%f, %f] but expected [%f, %f]\n", min0, max0, blob->min[0], blob->max[0]); + return -1; + } + } + if (blob->has_max && blob->has_min && blob->component_type != cgltf_component_type_r_32f && blob->component_type != cgltf_component_type_invalid) + { + cgltf_uint min0 = std::numeric_limits::max(); + cgltf_uint max0 = std::numeric_limits::lowest(); + for ( cgltf_size index = 0; index < blob->count; index++ ) + { + cgltf_accessor_read_uint( blob, index, element_uint, 4 ); + min0 = std::min( min0, element_uint[0] ); + max0 = std::max( max0, element_uint[0] ); + } + if ( min0 != (unsigned int) blob->min[0] || max0 != (unsigned int) blob->max[0] ) + { + printf( "Computed [%u, %u] but expected [%u, %u]\n", min0, max0, (unsigned int) blob->min[0], (unsigned int) blob->max[0] ); + return -1; + } + } + } + + cgltf_free(data); + + return result; +} diff --git a/source/engine/thirdparty/cgltf/test/test_math.cpp b/source/engine/thirdparty/cgltf/test/test_math.cpp new file mode 100644 index 0000000..84f9305 --- /dev/null +++ b/source/engine/thirdparty/cgltf/test/test_math.cpp @@ -0,0 +1,52 @@ +#define CGLTF_IMPLEMENTATION +#include "../cgltf.h" + +// Performs matrix-vector multiplication, as in (4x4) * (4x1) = (4x1) +static void transform(const cgltf_float matrix[16], const cgltf_float source[4], cgltf_float target[4]) { + target[0] = matrix[0] * source[0] + matrix[4] * source[1] + matrix[ 8] * source[2] + matrix[12] * source[3]; + target[1] = matrix[1] * source[0] + matrix[5] * source[1] + matrix[ 9] * source[2] + matrix[13] * source[3]; + target[2] = matrix[2] * source[0] + matrix[6] * source[1] + matrix[10] * source[2] + matrix[14] * source[3]; + target[3] = matrix[3] * source[0] + matrix[7] * source[1] + matrix[11] * source[2] + matrix[15] * source[3]; +} + +static void set(cgltf_float target[3], float x, float y, float z) { + target[0] = x; + target[1] = y; + target[2] = z; +} + +static void check(cgltf_float target[3], float x, float y, float z) { + if (target[0] != x || target[1] != y || target[2] != z) { + fprintf(stderr, "Mismatch detected.\n"); + exit(1); + } +} + +int main(int, char**) +{ + cgltf_node node = {}; + + cgltf_float matrix[16]; + cgltf_float source[4] = {1, 2, 3, 1}; + cgltf_float target[4]; + + set(node.scale, 1, 1, 1); + set(node.translation, 1, 0, 0); + cgltf_node_transform_local(&node, matrix); + transform(matrix, source, target); + check(target, 2, 2, 3); + + set(node.scale, 3, 1, 1); + set(node.translation, 0, 0, 0); + cgltf_node_transform_local(&node, matrix); + transform(matrix, source, target); + check(target, 3, 2, 3); + + set(node.scale, 1, 3, 1); + set(node.translation, 1, 0, 0); + cgltf_node_transform_local(&node, matrix); + transform(matrix, source, target); + check(target, 2, 6, 3); + + return 0; +} diff --git a/source/engine/thirdparty/cgltf/test/test_strings.cpp b/source/engine/thirdparty/cgltf/test/test_strings.cpp new file mode 100644 index 0000000..3d63efc --- /dev/null +++ b/source/engine/thirdparty/cgltf/test/test_strings.cpp @@ -0,0 +1,57 @@ +#define CGLTF_IMPLEMENTATION +#include "../cgltf.h" + +#include + +static void check(const char* a, const char* b, cgltf_size size) { + if (strcmp(a, b) != 0 || strlen(a) != size) { + fprintf(stderr, "Mismatch detected.\n"); + exit(1); + } +} + +int main(int, char**) +{ + char string[64]; + cgltf_size size; + + // cgltf_decode_string + strcpy(string, ""); + size = cgltf_decode_string(string); + check(string, "", size); + + strcpy(string, "nothing to replace"); + size = cgltf_decode_string(string); + check(string, "nothing to replace", size); + + strcpy(string, "\\\" \\/ \\\\ \\b \\f \\r \\n \\t \\u0030"); + size = cgltf_decode_string(string); + check(string, "\" / \\ \b \f \r \n \t 0", size); + + strcpy(string, "test \\u121b\\u130d\\u1294\\u1276\\u127d test"); + size = cgltf_decode_string(string); + check(string, "test ማግኔቶች test", size); + + // cgltf_decode_uri + strcpy(string, ""); + size = cgltf_decode_uri(string); + check(string, "", size); + + strcpy(string, "nothing to replace"); + size = cgltf_decode_uri(string); + check(string, "nothing to replace", size); + + strcpy(string, "%2F%D0%BA%D0%B8%D1%80%D0%B8%D0%BB%D0%BB%D0%B8%D1%86%D0%B0"); + size = cgltf_decode_uri(string); + check(string, "/кириллица", size); + + strcpy(string, "test%20%E1%88%9B%E1%8C%8D%E1%8A%94%E1%89%B6%E1%89%BD%20test"); + size = cgltf_decode_uri(string); + check(string, "test ማግኔቶች test", size); + + strcpy(string, "%%2F%X%AX%%2F%%"); + size = cgltf_decode_uri(string); + check(string, "%/%X%AX%/%%", size); + + return 0; +} diff --git a/source/engine/thirdparty/cgltf/test/test_write.cpp b/source/engine/thirdparty/cgltf/test/test_write.cpp new file mode 100644 index 0000000..b7ee25d --- /dev/null +++ b/source/engine/thirdparty/cgltf/test/test_write.cpp @@ -0,0 +1,45 @@ +#define CGLTF_IMPLEMENTATION +#define CGLTF_WRITE_IMPLEMENTATION +#include "../cgltf_write.h" + +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (argc < 2) + { + printf("err\n"); + return -1; + } + + cgltf_options options = {}; + cgltf_data* data0 = NULL; + cgltf_result result = cgltf_parse_file(&options, argv[1], &data0); + + // Silently skip over files that are unreadable since this is a writing test. + if (result != cgltf_result_success) + { + return cgltf_result_success; + } + + result = cgltf_write_file(&options, "out.gltf", data0); + if (result != cgltf_result_success) + { + return result; + } + cgltf_data* data1 = NULL; + result = cgltf_parse_file(&options, "out.gltf", &data1); + if (result != cgltf_result_success) + { + return result; + } + if (data0->meshes_count != data1->meshes_count) { + return -1; + } + cgltf_free(data1); + cgltf_free(data0); + return cgltf_result_success; +} diff --git a/source/engine/thirdparty/dr_libs b/source/engine/thirdparty/dr_libs deleted file mode 160000 index da35f9d..0000000 --- a/source/engine/thirdparty/dr_libs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit da35f9d6c7374a95353fd1df1d394d44ab66cf01 diff --git a/source/engine/thirdparty/dr_libs/.github/FUNDING.yml b/source/engine/thirdparty/dr_libs/.github/FUNDING.yml new file mode 100644 index 0000000..fb80301 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/.github/FUNDING.yml @@ -0,0 +1 @@ +github: mackron diff --git a/source/engine/thirdparty/dr_libs/.gitignore b/source/engine/thirdparty/dr_libs/.gitignore new file mode 100644 index 0000000..b31d733 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/.gitignore @@ -0,0 +1,12 @@ +docs +tests/testvectors/opus/* +!tests/testvectors/opus/DO_NOT_DELETE.md +tests/bin/* +!tests/bin/DO_NOT_DELETE.md +tests/build +tests/flac/include +tests/flac/lib +tests/testvectors/flac/tests/* +!tests/testvectors/flac/tests/DO_NOT_DELETE.md +todo +_private \ No newline at end of file diff --git a/source/engine/thirdparty/dr_libs/.gitmodules b/source/engine/thirdparty/dr_libs/.gitmodules new file mode 100644 index 0000000..c4d4547 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tests/external/miniaudio"] + path = tests/external/miniaudio + url = https://github.com/dr-soft/miniaudio.git diff --git a/source/engine/thirdparty/dr_libs/LICENSE b/source/engine/thirdparty/dr_libs/LICENSE new file mode 100644 index 0000000..16dfbdf --- /dev/null +++ b/source/engine/thirdparty/dr_libs/LICENSE @@ -0,0 +1,47 @@ +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - Public Domain (www.unlicense.org) +=============================================================================== +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright 2020 David Reid + +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. + +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/source/engine/thirdparty/dr_libs/README.md b/source/engine/thirdparty/dr_libs/README.md new file mode 100644 index 0000000..88aca82 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/README.md @@ -0,0 +1,21 @@ +

    Public domain, single file audio decoding libraries for C and C++.

    + +

    + discord + mastodon +

    + + +Library | Description +----------------------------------------------- | ----------- +[dr_flac](dr_flac.h) | FLAC audio decoder. +[dr_mp3](dr_mp3.h) | MP3 audio decoder. Based off [minimp3](https://github.com/lieff/minimp3). +[dr_wav](dr_wav.h) | WAV audio loader and writer. + + +# Other Libraries +Below are some of my other libraries you may be interested in. + +Library | Description +------------------------------------------------- | ----------- +[miniaudio](https://github.com/mackron/miniaudio) | A public domain, single file library for audio playback and recording. diff --git a/source/engine/thirdparty/dr_libs/dr_flac.h b/source/engine/thirdparty/dr_libs/dr_flac.h new file mode 100644 index 0000000..14324cf --- /dev/null +++ b/source/engine/thirdparty/dr_libs/dr_flac.h @@ -0,0 +1,12536 @@ +/* +FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. +dr_flac - v0.12.42 - 2023-11-02 + +David Reid - mackron@gmail.com + +GitHub: https://github.com/mackron/dr_libs +*/ + +/* +RELEASE NOTES - v0.12.0 +======================= +Version 0.12.0 has breaking API changes including changes to the existing API and the removal of deprecated APIs. + + +Improved Client-Defined Memory Allocation +----------------------------------------- +The main change with this release is the addition of a more flexible way of implementing custom memory allocation routines. The +existing system of DRFLAC_MALLOC, DRFLAC_REALLOC and DRFLAC_FREE are still in place and will be used by default when no custom +allocation callbacks are specified. + +To use the new system, you pass in a pointer to a drflac_allocation_callbacks object to drflac_open() and family, like this: + + void* my_malloc(size_t sz, void* pUserData) + { + return malloc(sz); + } + void* my_realloc(void* p, size_t sz, void* pUserData) + { + return realloc(p, sz); + } + void my_free(void* p, void* pUserData) + { + free(p); + } + + ... + + drflac_allocation_callbacks allocationCallbacks; + allocationCallbacks.pUserData = &myData; + allocationCallbacks.onMalloc = my_malloc; + allocationCallbacks.onRealloc = my_realloc; + allocationCallbacks.onFree = my_free; + drflac* pFlac = drflac_open_file("my_file.flac", &allocationCallbacks); + +The advantage of this new system is that it allows you to specify user data which will be passed in to the allocation routines. + +Passing in null for the allocation callbacks object will cause dr_flac to use defaults which is the same as DRFLAC_MALLOC, +DRFLAC_REALLOC and DRFLAC_FREE and the equivalent of how it worked in previous versions. + +Every API that opens a drflac object now takes this extra parameter. These include the following: + + drflac_open() + drflac_open_relaxed() + drflac_open_with_metadata() + drflac_open_with_metadata_relaxed() + drflac_open_file() + drflac_open_file_with_metadata() + drflac_open_memory() + drflac_open_memory_with_metadata() + drflac_open_and_read_pcm_frames_s32() + drflac_open_and_read_pcm_frames_s16() + drflac_open_and_read_pcm_frames_f32() + drflac_open_file_and_read_pcm_frames_s32() + drflac_open_file_and_read_pcm_frames_s16() + drflac_open_file_and_read_pcm_frames_f32() + drflac_open_memory_and_read_pcm_frames_s32() + drflac_open_memory_and_read_pcm_frames_s16() + drflac_open_memory_and_read_pcm_frames_f32() + + + +Optimizations +------------- +Seeking performance has been greatly improved. A new binary search based seeking algorithm has been introduced which significantly +improves performance over the brute force method which was used when no seek table was present. Seek table based seeking also takes +advantage of the new binary search seeking system to further improve performance there as well. Note that this depends on CRC which +means it will be disabled when DR_FLAC_NO_CRC is used. + +The SSE4.1 pipeline has been cleaned up and optimized. You should see some improvements with decoding speed of 24-bit files in +particular. 16-bit streams should also see some improvement. + +drflac_read_pcm_frames_s16() has been optimized. Previously this sat on top of drflac_read_pcm_frames_s32() and performed it's s32 +to s16 conversion in a second pass. This is now all done in a single pass. This includes SSE2 and ARM NEON optimized paths. + +A minor optimization has been implemented for drflac_read_pcm_frames_s32(). This will now use an SSE2 optimized pipeline for stereo +channel reconstruction which is the last part of the decoding process. + +The ARM build has seen a few improvements. The CLZ (count leading zeroes) and REV (byte swap) instructions are now used when +compiling with GCC and Clang which is achieved using inline assembly. The CLZ instruction requires ARM architecture version 5 at +compile time and the REV instruction requires ARM architecture version 6. + +An ARM NEON optimized pipeline has been implemented. To enable this you'll need to add -mfpu=neon to the command line when compiling. + + +Removed APIs +------------ +The following APIs were deprecated in version 0.11.0 and have been completely removed in version 0.12.0: + + drflac_read_s32() -> drflac_read_pcm_frames_s32() + drflac_read_s16() -> drflac_read_pcm_frames_s16() + drflac_read_f32() -> drflac_read_pcm_frames_f32() + drflac_seek_to_sample() -> drflac_seek_to_pcm_frame() + drflac_open_and_decode_s32() -> drflac_open_and_read_pcm_frames_s32() + drflac_open_and_decode_s16() -> drflac_open_and_read_pcm_frames_s16() + drflac_open_and_decode_f32() -> drflac_open_and_read_pcm_frames_f32() + drflac_open_and_decode_file_s32() -> drflac_open_file_and_read_pcm_frames_s32() + drflac_open_and_decode_file_s16() -> drflac_open_file_and_read_pcm_frames_s16() + drflac_open_and_decode_file_f32() -> drflac_open_file_and_read_pcm_frames_f32() + drflac_open_and_decode_memory_s32() -> drflac_open_memory_and_read_pcm_frames_s32() + drflac_open_and_decode_memory_s16() -> drflac_open_memory_and_read_pcm_frames_s16() + drflac_open_and_decode_memory_f32() -> drflac_open_memroy_and_read_pcm_frames_f32() + +Prior versions of dr_flac operated on a per-sample basis whereas now it operates on PCM frames. The removed APIs all relate +to the old per-sample APIs. You now need to use the "pcm_frame" versions. +*/ + + +/* +Introduction +============ +dr_flac is a single file library. To use it, do something like the following in one .c file. + + ```c + #define DR_FLAC_IMPLEMENTATION + #include "dr_flac.h" + ``` + +You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, do something like the following: + + ```c + drflac* pFlac = drflac_open_file("MySong.flac", NULL); + if (pFlac == NULL) { + // Failed to open FLAC file + } + + drflac_int32* pSamples = malloc(pFlac->totalPCMFrameCount * pFlac->channels * sizeof(drflac_int32)); + drflac_uint64 numberOfInterleavedSamplesActuallyRead = drflac_read_pcm_frames_s32(pFlac, pFlac->totalPCMFrameCount, pSamples); + ``` + +The drflac object represents the decoder. It is a transparent type so all the information you need, such as the number of channels and the bits per sample, +should be directly accessible - just make sure you don't change their values. Samples are always output as interleaved signed 32-bit PCM. In the example above +a native FLAC stream was opened, however dr_flac has seamless support for Ogg encapsulated FLAC streams as well. + +You do not need to decode the entire stream in one go - you just specify how many samples you'd like at any given time and the decoder will give you as many +samples as it can, up to the amount requested. Later on when you need the next batch of samples, just call it again. Example: + + ```c + while (drflac_read_pcm_frames_s32(pFlac, chunkSizeInPCMFrames, pChunkSamples) > 0) { + do_something(); + } + ``` + +You can seek to a specific PCM frame with `drflac_seek_to_pcm_frame()`. + +If you just want to quickly decode an entire FLAC file in one go you can do something like this: + + ```c + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + drflac_int32* pSampleData = drflac_open_file_and_read_pcm_frames_s32("MySong.flac", &channels, &sampleRate, &totalPCMFrameCount, NULL); + if (pSampleData == NULL) { + // Failed to open and decode FLAC file. + } + + ... + + drflac_free(pSampleData, NULL); + ``` + +You can read samples as signed 16-bit integer and 32-bit floating-point PCM with the *_s16() and *_f32() family of APIs respectively, but note that these +should be considered lossy. + + +If you need access to metadata (album art, etc.), use `drflac_open_with_metadata()`, `drflac_open_file_with_metdata()` or `drflac_open_memory_with_metadata()`. +The rationale for keeping these APIs separate is that they're slightly slower than the normal versions and also just a little bit harder to use. dr_flac +reports metadata to the application through the use of a callback, and every metadata block is reported before `drflac_open_with_metdata()` returns. + +The main opening APIs (`drflac_open()`, etc.) will fail if the header is not present. The presents a problem in certain scenarios such as broadcast style +streams or internet radio where the header may not be present because the user has started playback mid-stream. To handle this, use the relaxed APIs: + + `drflac_open_relaxed()` + `drflac_open_with_metadata_relaxed()` + +It is not recommended to use these APIs for file based streams because a missing header would usually indicate a corrupt or perverse file. In addition, these +APIs can take a long time to initialize because they may need to spend a lot of time finding the first frame. + + + +Build Options +============= +#define these options before including this file. + +#define DR_FLAC_NO_STDIO + Disable `drflac_open_file()` and family. + +#define DR_FLAC_NO_OGG + Disables support for Ogg/FLAC streams. + +#define DR_FLAC_BUFFER_SIZE + Defines the size of the internal buffer to store data from onRead(). This buffer is used to reduce the number of calls back to the client for more data. + Larger values means more memory, but better performance. My tests show diminishing returns after about 4KB (which is the default). Consider reducing this if + you have a very efficient implementation of onRead(), or increase it if it's very inefficient. Must be a multiple of 8. + +#define DR_FLAC_NO_CRC + Disables CRC checks. This will offer a performance boost when CRC is unnecessary. This will disable binary search seeking. When seeking, the seek table will + be used if available. Otherwise the seek will be performed using brute force. + +#define DR_FLAC_NO_SIMD + Disables SIMD optimizations (SSE on x86/x64 architectures, NEON on ARM architectures). Use this if you are having compatibility issues with your compiler. + +#define DR_FLAC_NO_WCHAR + Disables all functions ending with `_w`. Use this if your compiler does not provide wchar.h. Not required if DR_FLAC_NO_STDIO is also defined. + + + +Notes +===== +- dr_flac does not support changing the sample rate nor channel count mid stream. +- dr_flac is not thread-safe, but its APIs can be called from any thread so long as you do your own synchronization. +- When using Ogg encapsulation, a corrupted metadata block will result in `drflac_open_with_metadata()` and `drflac_open()` returning inconsistent samples due + to differences in corrupted stream recorvery logic between the two APIs. +*/ + +#ifndef dr_flac_h +#define dr_flac_h + +#ifdef __cplusplus +extern "C" { +#endif + +#define DRFLAC_STRINGIFY(x) #x +#define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x) + +#define DRFLAC_VERSION_MAJOR 0 +#define DRFLAC_VERSION_MINOR 12 +#define DRFLAC_VERSION_REVISION 42 +#define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) + +#include /* For size_t. */ + +/* Sized Types */ +typedef signed char drflac_int8; +typedef unsigned char drflac_uint8; +typedef signed short drflac_int16; +typedef unsigned short drflac_uint16; +typedef signed int drflac_int32; +typedef unsigned int drflac_uint32; +#if defined(_MSC_VER) && !defined(__clang__) + typedef signed __int64 drflac_int64; + typedef unsigned __int64 drflac_uint64; +#else + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wlong-long" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif + #endif + typedef signed long long drflac_int64; + typedef unsigned long long drflac_uint64; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop + #endif +#endif +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) + typedef drflac_uint64 drflac_uintptr; +#else + typedef drflac_uint32 drflac_uintptr; +#endif +typedef drflac_uint8 drflac_bool8; +typedef drflac_uint32 drflac_bool32; +#define DRFLAC_TRUE 1 +#define DRFLAC_FALSE 0 +/* End Sized Types */ + +/* Decorations */ +#if !defined(DRFLAC_API) + #if defined(DRFLAC_DLL) + #if defined(_WIN32) + #define DRFLAC_DLL_IMPORT __declspec(dllimport) + #define DRFLAC_DLL_EXPORT __declspec(dllexport) + #define DRFLAC_DLL_PRIVATE static + #else + #if defined(__GNUC__) && __GNUC__ >= 4 + #define DRFLAC_DLL_IMPORT __attribute__((visibility("default"))) + #define DRFLAC_DLL_EXPORT __attribute__((visibility("default"))) + #define DRFLAC_DLL_PRIVATE __attribute__((visibility("hidden"))) + #else + #define DRFLAC_DLL_IMPORT + #define DRFLAC_DLL_EXPORT + #define DRFLAC_DLL_PRIVATE static + #endif + #endif + + #if defined(DR_FLAC_IMPLEMENTATION) || defined(DRFLAC_IMPLEMENTATION) + #define DRFLAC_API DRFLAC_DLL_EXPORT + #else + #define DRFLAC_API DRFLAC_DLL_IMPORT + #endif + #define DRFLAC_PRIVATE DRFLAC_DLL_PRIVATE + #else + #define DRFLAC_API extern + #define DRFLAC_PRIVATE static + #endif +#endif +/* End Decorations */ + +#if defined(_MSC_VER) && _MSC_VER >= 1700 /* Visual Studio 2012 */ + #define DRFLAC_DEPRECATED __declspec(deprecated) +#elif (defined(__GNUC__) && __GNUC__ >= 4) /* GCC 4 */ + #define DRFLAC_DEPRECATED __attribute__((deprecated)) +#elif defined(__has_feature) /* Clang */ + #if __has_feature(attribute_deprecated) + #define DRFLAC_DEPRECATED __attribute__((deprecated)) + #else + #define DRFLAC_DEPRECATED + #endif +#else + #define DRFLAC_DEPRECATED +#endif + +DRFLAC_API void drflac_version(drflac_uint32* pMajor, drflac_uint32* pMinor, drflac_uint32* pRevision); +DRFLAC_API const char* drflac_version_string(void); + +/* Allocation Callbacks */ +typedef struct +{ + void* pUserData; + void* (* onMalloc)(size_t sz, void* pUserData); + void* (* onRealloc)(void* p, size_t sz, void* pUserData); + void (* onFree)(void* p, void* pUserData); +} drflac_allocation_callbacks; +/* End Allocation Callbacks */ + +/* +As data is read from the client it is placed into an internal buffer for fast access. This controls the size of that buffer. Larger values means more speed, +but also more memory. In my testing there is diminishing returns after about 4KB, but you can fiddle with this to suit your own needs. Must be a multiple of 8. +*/ +#ifndef DR_FLAC_BUFFER_SIZE +#define DR_FLAC_BUFFER_SIZE 4096 +#endif + + +/* Architecture Detection */ +#if defined(_WIN64) || defined(_LP64) || defined(__LP64__) +#define DRFLAC_64BIT +#endif + +#if defined(__x86_64__) || defined(_M_X64) + #define DRFLAC_X64 +#elif defined(__i386) || defined(_M_IX86) + #define DRFLAC_X86 +#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) + #define DRFLAC_ARM +#endif +/* End Architecture Detection */ + + +#ifdef DRFLAC_64BIT +typedef drflac_uint64 drflac_cache_t; +#else +typedef drflac_uint32 drflac_cache_t; +#endif + +/* The various metadata block types. */ +#define DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO 0 +#define DRFLAC_METADATA_BLOCK_TYPE_PADDING 1 +#define DRFLAC_METADATA_BLOCK_TYPE_APPLICATION 2 +#define DRFLAC_METADATA_BLOCK_TYPE_SEEKTABLE 3 +#define DRFLAC_METADATA_BLOCK_TYPE_VORBIS_COMMENT 4 +#define DRFLAC_METADATA_BLOCK_TYPE_CUESHEET 5 +#define DRFLAC_METADATA_BLOCK_TYPE_PICTURE 6 +#define DRFLAC_METADATA_BLOCK_TYPE_INVALID 127 + +/* The various picture types specified in the PICTURE block. */ +#define DRFLAC_PICTURE_TYPE_OTHER 0 +#define DRFLAC_PICTURE_TYPE_FILE_ICON 1 +#define DRFLAC_PICTURE_TYPE_OTHER_FILE_ICON 2 +#define DRFLAC_PICTURE_TYPE_COVER_FRONT 3 +#define DRFLAC_PICTURE_TYPE_COVER_BACK 4 +#define DRFLAC_PICTURE_TYPE_LEAFLET_PAGE 5 +#define DRFLAC_PICTURE_TYPE_MEDIA 6 +#define DRFLAC_PICTURE_TYPE_LEAD_ARTIST 7 +#define DRFLAC_PICTURE_TYPE_ARTIST 8 +#define DRFLAC_PICTURE_TYPE_CONDUCTOR 9 +#define DRFLAC_PICTURE_TYPE_BAND 10 +#define DRFLAC_PICTURE_TYPE_COMPOSER 11 +#define DRFLAC_PICTURE_TYPE_LYRICIST 12 +#define DRFLAC_PICTURE_TYPE_RECORDING_LOCATION 13 +#define DRFLAC_PICTURE_TYPE_DURING_RECORDING 14 +#define DRFLAC_PICTURE_TYPE_DURING_PERFORMANCE 15 +#define DRFLAC_PICTURE_TYPE_SCREEN_CAPTURE 16 +#define DRFLAC_PICTURE_TYPE_BRIGHT_COLORED_FISH 17 +#define DRFLAC_PICTURE_TYPE_ILLUSTRATION 18 +#define DRFLAC_PICTURE_TYPE_BAND_LOGOTYPE 19 +#define DRFLAC_PICTURE_TYPE_PUBLISHER_LOGOTYPE 20 + +typedef enum +{ + drflac_container_native, + drflac_container_ogg, + drflac_container_unknown +} drflac_container; + +typedef enum +{ + drflac_seek_origin_start, + drflac_seek_origin_current +} drflac_seek_origin; + +/* The order of members in this structure is important because we map this directly to the raw data within the SEEKTABLE metadata block. */ +typedef struct +{ + drflac_uint64 firstPCMFrame; + drflac_uint64 flacFrameOffset; /* The offset from the first byte of the header of the first frame. */ + drflac_uint16 pcmFrameCount; +} drflac_seekpoint; + +typedef struct +{ + drflac_uint16 minBlockSizeInPCMFrames; + drflac_uint16 maxBlockSizeInPCMFrames; + drflac_uint32 minFrameSizeInPCMFrames; + drflac_uint32 maxFrameSizeInPCMFrames; + drflac_uint32 sampleRate; + drflac_uint8 channels; + drflac_uint8 bitsPerSample; + drflac_uint64 totalPCMFrameCount; + drflac_uint8 md5[16]; +} drflac_streaminfo; + +typedef struct +{ + /* + The metadata type. Use this to know how to interpret the data below. Will be set to one of the + DRFLAC_METADATA_BLOCK_TYPE_* tokens. + */ + drflac_uint32 type; + + /* + A pointer to the raw data. This points to a temporary buffer so don't hold on to it. It's best to + not modify the contents of this buffer. Use the structures below for more meaningful and structured + information about the metadata. It's possible for this to be null. + */ + const void* pRawData; + + /* The size in bytes of the block and the buffer pointed to by pRawData if it's non-NULL. */ + drflac_uint32 rawDataSize; + + union + { + drflac_streaminfo streaminfo; + + struct + { + int unused; + } padding; + + struct + { + drflac_uint32 id; + const void* pData; + drflac_uint32 dataSize; + } application; + + struct + { + drflac_uint32 seekpointCount; + const drflac_seekpoint* pSeekpoints; + } seektable; + + struct + { + drflac_uint32 vendorLength; + const char* vendor; + drflac_uint32 commentCount; + const void* pComments; + } vorbis_comment; + + struct + { + char catalog[128]; + drflac_uint64 leadInSampleCount; + drflac_bool32 isCD; + drflac_uint8 trackCount; + const void* pTrackData; + } cuesheet; + + struct + { + drflac_uint32 type; + drflac_uint32 mimeLength; + const char* mime; + drflac_uint32 descriptionLength; + const char* description; + drflac_uint32 width; + drflac_uint32 height; + drflac_uint32 colorDepth; + drflac_uint32 indexColorCount; + drflac_uint32 pictureDataSize; + const drflac_uint8* pPictureData; + } picture; + } data; +} drflac_metadata; + + +/* +Callback for when data needs to be read from the client. + + +Parameters +---------- +pUserData (in) + The user data that was passed to drflac_open() and family. + +pBufferOut (out) + The output buffer. + +bytesToRead (in) + The number of bytes to read. + + +Return Value +------------ +The number of bytes actually read. + + +Remarks +------- +A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until either the entire bytesToRead is filled or +you have reached the end of the stream. +*/ +typedef size_t (* drflac_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); + +/* +Callback for when data needs to be seeked. + + +Parameters +---------- +pUserData (in) + The user data that was passed to drflac_open() and family. + +offset (in) + The number of bytes to move, relative to the origin. Will never be negative. + +origin (in) + The origin of the seek - the current position or the start of the stream. + + +Return Value +------------ +Whether or not the seek was successful. + + +Remarks +------- +The offset will never be negative. Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which will be +either drflac_seek_origin_start or drflac_seek_origin_current. + +When seeking to a PCM frame using drflac_seek_to_pcm_frame(), dr_flac may call this with an offset beyond the end of the FLAC stream. This needs to be detected +and handled by returning DRFLAC_FALSE. +*/ +typedef drflac_bool32 (* drflac_seek_proc)(void* pUserData, int offset, drflac_seek_origin origin); + +/* +Callback for when a metadata block is read. + + +Parameters +---------- +pUserData (in) + The user data that was passed to drflac_open() and family. + +pMetadata (in) + A pointer to a structure containing the data of the metadata block. + + +Remarks +------- +Use pMetadata->type to determine which metadata block is being handled and how to read the data. This +will be set to one of the DRFLAC_METADATA_BLOCK_TYPE_* tokens. +*/ +typedef void (* drflac_meta_proc)(void* pUserData, drflac_metadata* pMetadata); + + +/* Structure for internal use. Only used for decoders opened with drflac_open_memory. */ +typedef struct +{ + const drflac_uint8* data; + size_t dataSize; + size_t currentReadPos; +} drflac__memory_stream; + +/* Structure for internal use. Used for bit streaming. */ +typedef struct +{ + /* The function to call when more data needs to be read. */ + drflac_read_proc onRead; + + /* The function to call when the current read position needs to be moved. */ + drflac_seek_proc onSeek; + + /* The user data to pass around to onRead and onSeek. */ + void* pUserData; + + + /* + The number of unaligned bytes in the L2 cache. This will always be 0 until the end of the stream is hit. At the end of the + stream there will be a number of bytes that don't cleanly fit in an L1 cache line, so we use this variable to know whether + or not the bistreamer needs to run on a slower path to read those last bytes. This will never be more than sizeof(drflac_cache_t). + */ + size_t unalignedByteCount; + + /* The content of the unaligned bytes. */ + drflac_cache_t unalignedCache; + + /* The index of the next valid cache line in the "L2" cache. */ + drflac_uint32 nextL2Line; + + /* The number of bits that have been consumed by the cache. This is used to determine how many valid bits are remaining. */ + drflac_uint32 consumedBits; + + /* + The cached data which was most recently read from the client. There are two levels of cache. Data flows as such: + Client -> L2 -> L1. The L2 -> L1 movement is aligned and runs on a fast path in just a few instructions. + */ + drflac_cache_t cacheL2[DR_FLAC_BUFFER_SIZE/sizeof(drflac_cache_t)]; + drflac_cache_t cache; + + /* + CRC-16. This is updated whenever bits are read from the bit stream. Manually set this to 0 to reset the CRC. For FLAC, this + is reset to 0 at the beginning of each frame. + */ + drflac_uint16 crc16; + drflac_cache_t crc16Cache; /* A cache for optimizing CRC calculations. This is filled when when the L1 cache is reloaded. */ + drflac_uint32 crc16CacheIgnoredBytes; /* The number of bytes to ignore when updating the CRC-16 from the CRC-16 cache. */ +} drflac_bs; + +typedef struct +{ + /* The type of the subframe: SUBFRAME_CONSTANT, SUBFRAME_VERBATIM, SUBFRAME_FIXED or SUBFRAME_LPC. */ + drflac_uint8 subframeType; + + /* The number of wasted bits per sample as specified by the sub-frame header. */ + drflac_uint8 wastedBitsPerSample; + + /* The order to use for the prediction stage for SUBFRAME_FIXED and SUBFRAME_LPC. */ + drflac_uint8 lpcOrder; + + /* A pointer to the buffer containing the decoded samples in the subframe. This pointer is an offset from drflac::pExtraData. */ + drflac_int32* pSamplesS32; +} drflac_subframe; + +typedef struct +{ + /* + If the stream uses variable block sizes, this will be set to the index of the first PCM frame. If fixed block sizes are used, this will + always be set to 0. This is 64-bit because the decoded PCM frame number will be 36 bits. + */ + drflac_uint64 pcmFrameNumber; + + /* + If the stream uses fixed block sizes, this will be set to the frame number. If variable block sizes are used, this will always be 0. This + is 32-bit because in fixed block sizes, the maximum frame number will be 31 bits. + */ + drflac_uint32 flacFrameNumber; + + /* The sample rate of this frame. */ + drflac_uint32 sampleRate; + + /* The number of PCM frames in each sub-frame within this frame. */ + drflac_uint16 blockSizeInPCMFrames; + + /* + The channel assignment of this frame. This is not always set to the channel count. If interchannel decorrelation is being used this + will be set to DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE, DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE or DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE. + */ + drflac_uint8 channelAssignment; + + /* The number of bits per sample within this frame. */ + drflac_uint8 bitsPerSample; + + /* The frame's CRC. */ + drflac_uint8 crc8; +} drflac_frame_header; + +typedef struct +{ + /* The header. */ + drflac_frame_header header; + + /* + The number of PCM frames left to be read in this FLAC frame. This is initially set to the block size. As PCM frames are read, + this will be decremented. When it reaches 0, the decoder will see this frame as fully consumed and load the next frame. + */ + drflac_uint32 pcmFramesRemaining; + + /* The list of sub-frames within the frame. There is one sub-frame for each channel, and there's a maximum of 8 channels. */ + drflac_subframe subframes[8]; +} drflac_frame; + +typedef struct +{ + /* The function to call when a metadata block is read. */ + drflac_meta_proc onMeta; + + /* The user data posted to the metadata callback function. */ + void* pUserDataMD; + + /* Memory allocation callbacks. */ + drflac_allocation_callbacks allocationCallbacks; + + + /* The sample rate. Will be set to something like 44100. */ + drflac_uint32 sampleRate; + + /* + The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. Maximum 8. This is set based on the + value specified in the STREAMINFO block. + */ + drflac_uint8 channels; + + /* The bits per sample. Will be set to something like 16, 24, etc. */ + drflac_uint8 bitsPerSample; + + /* The maximum block size, in samples. This number represents the number of samples in each channel (not combined). */ + drflac_uint16 maxBlockSizeInPCMFrames; + + /* + The total number of PCM Frames making up the stream. Can be 0 in which case it's still a valid stream, but just means + the total PCM frame count is unknown. Likely the case with streams like internet radio. + */ + drflac_uint64 totalPCMFrameCount; + + + /* The container type. This is set based on whether or not the decoder was opened from a native or Ogg stream. */ + drflac_container container; + + /* The number of seekpoints in the seektable. */ + drflac_uint32 seekpointCount; + + + /* Information about the frame the decoder is currently sitting on. */ + drflac_frame currentFLACFrame; + + + /* The index of the PCM frame the decoder is currently sitting on. This is only used for seeking. */ + drflac_uint64 currentPCMFrame; + + /* The position of the first FLAC frame in the stream. This is only ever used for seeking. */ + drflac_uint64 firstFLACFramePosInBytes; + + + /* A hack to avoid a malloc() when opening a decoder with drflac_open_memory(). */ + drflac__memory_stream memoryStream; + + + /* A pointer to the decoded sample data. This is an offset of pExtraData. */ + drflac_int32* pDecodedSamples; + + /* A pointer to the seek table. This is an offset of pExtraData, or NULL if there is no seek table. */ + drflac_seekpoint* pSeekpoints; + + /* Internal use only. Only used with Ogg containers. Points to a drflac_oggbs object. This is an offset of pExtraData. */ + void* _oggbs; + + /* Internal use only. Used for profiling and testing different seeking modes. */ + drflac_bool32 _noSeekTableSeek : 1; + drflac_bool32 _noBinarySearchSeek : 1; + drflac_bool32 _noBruteForceSeek : 1; + + /* The bit streamer. The raw FLAC data is fed through this object. */ + drflac_bs bs; + + /* Variable length extra data. We attach this to the end of the object so we can avoid unnecessary mallocs. */ + drflac_uint8 pExtraData[1]; +} drflac; + + +/* +Opens a FLAC decoder. + + +Parameters +---------- +onRead (in) + The function to call when data needs to be read from the client. + +onSeek (in) + The function to call when the read position of the client data needs to move. + +pUserData (in, optional) + A pointer to application defined data that will be passed to onRead and onSeek. + +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. + + +Return Value +------------ +Returns a pointer to an object representing the decoder. + + +Remarks +------- +Close the decoder with `drflac_close()`. + +`pAllocationCallbacks` can be NULL in which case it will use `DRFLAC_MALLOC`, `DRFLAC_REALLOC` and `DRFLAC_FREE`. + +This function will automatically detect whether or not you are attempting to open a native or Ogg encapsulated FLAC, both of which should work seamlessly +without any manual intervention. Ogg encapsulation also works with multiplexed streams which basically means it can play FLAC encoded audio tracks in videos. + +This is the lowest level function for opening a FLAC stream. You can also use `drflac_open_file()` and `drflac_open_memory()` to open the stream from a file or +from a block of memory respectively. + +The STREAMINFO block must be present for this to succeed. Use `drflac_open_relaxed()` to open a FLAC stream where the header may not be present. + +Use `drflac_open_with_metadata()` if you need access to metadata. + + +Seek Also +--------- +drflac_open_file() +drflac_open_memory() +drflac_open_with_metadata() +drflac_close() +*/ +DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* +Opens a FLAC stream with relaxed validation of the header block. + + +Parameters +---------- +onRead (in) + The function to call when data needs to be read from the client. + +onSeek (in) + The function to call when the read position of the client data needs to move. + +container (in) + Whether or not the FLAC stream is encapsulated using standard FLAC encapsulation or Ogg encapsulation. + +pUserData (in, optional) + A pointer to application defined data that will be passed to onRead and onSeek. + +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. + + +Return Value +------------ +A pointer to an object representing the decoder. + + +Remarks +------- +The same as drflac_open(), except attempts to open the stream even when a header block is not present. + +Because the header is not necessarily available, the caller must explicitly define the container (Native or Ogg). Do not set this to `drflac_container_unknown` +as that is for internal use only. + +Opening in relaxed mode will continue reading data from onRead until it finds a valid frame. If a frame is never found it will continue forever. To abort, +force your `onRead` callback to return 0, which dr_flac will use as an indicator that the end of the stream was found. + +Use `drflac_open_with_metadata_relaxed()` if you need access to metadata. +*/ +DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* +Opens a FLAC decoder and notifies the caller of the metadata chunks (album art, etc.). + + +Parameters +---------- +onRead (in) + The function to call when data needs to be read from the client. + +onSeek (in) + The function to call when the read position of the client data needs to move. + +onMeta (in) + The function to call for every metadata block. + +pUserData (in, optional) + A pointer to application defined data that will be passed to onRead, onSeek and onMeta. + +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. + + +Return Value +------------ +A pointer to an object representing the decoder. + + +Remarks +------- +Close the decoder with `drflac_close()`. + +`pAllocationCallbacks` can be NULL in which case it will use `DRFLAC_MALLOC`, `DRFLAC_REALLOC` and `DRFLAC_FREE`. + +This is slower than `drflac_open()`, so avoid this one if you don't need metadata. Internally, this will allocate and free memory on the heap for every +metadata block except for STREAMINFO and PADDING blocks. + +The caller is notified of the metadata via the `onMeta` callback. All metadata blocks will be handled before the function returns. This callback takes a +pointer to a `drflac_metadata` object which is a union containing the data of all relevant metadata blocks. Use the `type` member to discriminate against +the different metadata types. + +The STREAMINFO block must be present for this to succeed. Use `drflac_open_with_metadata_relaxed()` to open a FLAC stream where the header may not be present. + +Note that this will behave inconsistently with `drflac_open()` if the stream is an Ogg encapsulated stream and a metadata block is corrupted. This is due to +the way the Ogg stream recovers from corrupted pages. When `drflac_open_with_metadata()` is being used, the open routine will try to read the contents of the +metadata block, whereas `drflac_open()` will simply seek past it (for the sake of efficiency). This inconsistency can result in different samples being +returned depending on whether or not the stream is being opened with metadata. + + +Seek Also +--------- +drflac_open_file_with_metadata() +drflac_open_memory_with_metadata() +drflac_open() +drflac_close() +*/ +DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* +The same as drflac_open_with_metadata(), except attempts to open the stream even when a header block is not present. + +See Also +-------- +drflac_open_with_metadata() +drflac_open_relaxed() +*/ +DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* +Closes the given FLAC decoder. + + +Parameters +---------- +pFlac (in) + The decoder to close. + + +Remarks +------- +This will destroy the decoder object. + + +See Also +-------- +drflac_open() +drflac_open_with_metadata() +drflac_open_file() +drflac_open_file_w() +drflac_open_file_with_metadata() +drflac_open_file_with_metadata_w() +drflac_open_memory() +drflac_open_memory_with_metadata() +*/ +DRFLAC_API void drflac_close(drflac* pFlac); + + +/* +Reads sample data from the given FLAC decoder, output as interleaved signed 32-bit PCM. + + +Parameters +---------- +pFlac (in) + The decoder. + +framesToRead (in) + The number of PCM frames to read. + +pBufferOut (out, optional) + A pointer to the buffer that will receive the decoded samples. + + +Return Value +------------ +Returns the number of PCM frames actually read. If the return value is less than `framesToRead` it has reached the end. + + +Remarks +------- +pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of frames seeked. +*/ +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s32(drflac* pFlac, drflac_uint64 framesToRead, drflac_int32* pBufferOut); + + +/* +Reads sample data from the given FLAC decoder, output as interleaved signed 16-bit PCM. + + +Parameters +---------- +pFlac (in) + The decoder. + +framesToRead (in) + The number of PCM frames to read. + +pBufferOut (out, optional) + A pointer to the buffer that will receive the decoded samples. + + +Return Value +------------ +Returns the number of PCM frames actually read. If the return value is less than `framesToRead` it has reached the end. + + +Remarks +------- +pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of frames seeked. + +Note that this is lossy for streams where the bits per sample is larger than 16. +*/ +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s16(drflac* pFlac, drflac_uint64 framesToRead, drflac_int16* pBufferOut); + +/* +Reads sample data from the given FLAC decoder, output as interleaved 32-bit floating point PCM. + + +Parameters +---------- +pFlac (in) + The decoder. + +framesToRead (in) + The number of PCM frames to read. + +pBufferOut (out, optional) + A pointer to the buffer that will receive the decoded samples. + + +Return Value +------------ +Returns the number of PCM frames actually read. If the return value is less than `framesToRead` it has reached the end. + + +Remarks +------- +pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of frames seeked. + +Note that this should be considered lossy due to the nature of floating point numbers not being able to exactly represent every possible number. +*/ +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRead, float* pBufferOut); + +/* +Seeks to the PCM frame at the given index. + + +Parameters +---------- +pFlac (in) + The decoder. + +pcmFrameIndex (in) + The index of the PCM frame to seek to. See notes below. + + +Return Value +------------- +`DRFLAC_TRUE` if successful; `DRFLAC_FALSE` otherwise. +*/ +DRFLAC_API drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex); + + + +#ifndef DR_FLAC_NO_STDIO +/* +Opens a FLAC decoder from the file at the given path. + + +Parameters +---------- +pFileName (in) + The path of the file to open, either absolute or relative to the current directory. + +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. + + +Return Value +------------ +A pointer to an object representing the decoder. + + +Remarks +------- +Close the decoder with drflac_close(). + + +Remarks +------- +This will hold a handle to the file until the decoder is closed with drflac_close(). Some platforms will restrict the number of files a process can have open +at any given time, so keep this mind if you have many decoders open at the same time. + + +See Also +-------- +drflac_open_file_with_metadata() +drflac_open() +drflac_close() +*/ +DRFLAC_API drflac* drflac_open_file(const char* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* +Opens a FLAC decoder from the file at the given path and notifies the caller of the metadata chunks (album art, etc.) + + +Parameters +---------- +pFileName (in) + The path of the file to open, either absolute or relative to the current directory. + +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. + +onMeta (in) + The callback to fire for each metadata block. + +pUserData (in) + A pointer to the user data to pass to the metadata callback. + +pAllocationCallbacks (in) + A pointer to application defined callbacks for managing memory allocations. + + +Remarks +------- +Look at the documentation for drflac_open_with_metadata() for more information on how metadata is handled. + + +See Also +-------- +drflac_open_with_metadata() +drflac_open() +drflac_close() +*/ +DRFLAC_API drflac* drflac_open_file_with_metadata(const char* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac* drflac_open_file_with_metadata_w(const wchar_t* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); +#endif + +/* +Opens a FLAC decoder from a pre-allocated block of memory + + +Parameters +---------- +pData (in) + A pointer to the raw encoded FLAC data. + +dataSize (in) + The size in bytes of `data`. + +pAllocationCallbacks (in) + A pointer to application defined callbacks for managing memory allocations. + + +Return Value +------------ +A pointer to an object representing the decoder. + + +Remarks +------- +This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for the lifetime of the decoder. + + +See Also +-------- +drflac_open() +drflac_close() +*/ +DRFLAC_API drflac* drflac_open_memory(const void* pData, size_t dataSize, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* +Opens a FLAC decoder from a pre-allocated block of memory and notifies the caller of the metadata chunks (album art, etc.) + + +Parameters +---------- +pData (in) + A pointer to the raw encoded FLAC data. + +dataSize (in) + The size in bytes of `data`. + +onMeta (in) + The callback to fire for each metadata block. + +pUserData (in) + A pointer to the user data to pass to the metadata callback. + +pAllocationCallbacks (in) + A pointer to application defined callbacks for managing memory allocations. + + +Remarks +------- +Look at the documentation for drflac_open_with_metadata() for more information on how metadata is handled. + + +See Also +------- +drflac_open_with_metadata() +drflac_open() +drflac_close() +*/ +DRFLAC_API drflac* drflac_open_memory_with_metadata(const void* pData, size_t dataSize, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); + + + +/* High Level APIs */ + +/* +Opens a FLAC stream from the given callbacks and fully decodes it in a single operation. The return value is a +pointer to the sample data as interleaved signed 32-bit PCM. The returned data must be freed with drflac_free(). + +You can pass in custom memory allocation callbacks via the pAllocationCallbacks parameter. This can be NULL in which +case it will use DRFLAC_MALLOC, DRFLAC_REALLOC and DRFLAC_FREE. + +Sometimes a FLAC file won't keep track of the total sample count. In this situation the function will continuously +read samples into a dynamically sized buffer on the heap until no samples are left. + +Do not call this function on a broadcast type of stream (like internet radio streams and whatnot). +*/ +DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* Same as drflac_open_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. */ +DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* Same as drflac_open_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. */ +DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +#ifndef DR_FLAC_NO_STDIO +/* Same as drflac_open_and_read_pcm_frames_s32() except opens the decoder from a file. */ +DRFLAC_API drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* Same as drflac_open_file_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. */ +DRFLAC_API drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* Same as drflac_open_file_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. */ +DRFLAC_API float* drflac_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); +#endif + +/* Same as drflac_open_and_read_pcm_frames_s32() except opens the decoder from a block of memory. */ +DRFLAC_API drflac_int32* drflac_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* Same as drflac_open_memory_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. */ +DRFLAC_API drflac_int16* drflac_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* Same as drflac_open_memory_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. */ +DRFLAC_API float* drflac_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); + +/* +Frees memory that was allocated internally by dr_flac. + +Set pAllocationCallbacks to the same object that was passed to drflac_open_*_and_read_pcm_frames_*(). If you originally passed in NULL, pass in NULL for this. +*/ +DRFLAC_API void drflac_free(void* p, const drflac_allocation_callbacks* pAllocationCallbacks); + + +/* Structure representing an iterator for vorbis comments in a VORBIS_COMMENT metadata block. */ +typedef struct +{ + drflac_uint32 countRemaining; + const char* pRunningData; +} drflac_vorbis_comment_iterator; + +/* +Initializes a vorbis comment iterator. This can be used for iterating over the vorbis comments in a VORBIS_COMMENT +metadata block. +*/ +DRFLAC_API void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, drflac_uint32 commentCount, const void* pComments); + +/* +Goes to the next vorbis comment in the given iterator. If null is returned it means there are no more comments. The +returned string is NOT null terminated. +*/ +DRFLAC_API const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, drflac_uint32* pCommentLengthOut); + + +/* Structure representing an iterator for cuesheet tracks in a CUESHEET metadata block. */ +typedef struct +{ + drflac_uint32 countRemaining; + const char* pRunningData; +} drflac_cuesheet_track_iterator; + +/* The order of members here is important because we map this directly to the raw data within the CUESHEET metadata block. */ +typedef struct +{ + drflac_uint64 offset; + drflac_uint8 index; + drflac_uint8 reserved[3]; +} drflac_cuesheet_track_index; + +typedef struct +{ + drflac_uint64 offset; + drflac_uint8 trackNumber; + char ISRC[12]; + drflac_bool8 isAudio; + drflac_bool8 preEmphasis; + drflac_uint8 indexCount; + const drflac_cuesheet_track_index* pIndexPoints; +} drflac_cuesheet_track; + +/* +Initializes a cuesheet track iterator. This can be used for iterating over the cuesheet tracks in a CUESHEET metadata +block. +*/ +DRFLAC_API void drflac_init_cuesheet_track_iterator(drflac_cuesheet_track_iterator* pIter, drflac_uint32 trackCount, const void* pTrackData); + +/* Goes to the next cuesheet track in the given iterator. If DRFLAC_FALSE is returned it means there are no more comments. */ +DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, drflac_cuesheet_track* pCuesheetTrack); + + +#ifdef __cplusplus +} +#endif +#endif /* dr_flac_h */ + + +/************************************************************************************************************************************************************ + ************************************************************************************************************************************************************ + + IMPLEMENTATION + + ************************************************************************************************************************************************************ + ************************************************************************************************************************************************************/ +#if defined(DR_FLAC_IMPLEMENTATION) || defined(DRFLAC_IMPLEMENTATION) +#ifndef dr_flac_c +#define dr_flac_c + +/* Disable some annoying warnings. */ +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #if __GNUC__ >= 7 + #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" + #endif +#endif + +#ifdef __linux__ + #ifndef _BSD_SOURCE + #define _BSD_SOURCE + #endif + #ifndef _DEFAULT_SOURCE + #define _DEFAULT_SOURCE + #endif + #ifndef __USE_BSD + #define __USE_BSD + #endif + #include +#endif + +#include +#include + +/* Inline */ +#ifdef _MSC_VER + #define DRFLAC_INLINE __forceinline +#elif defined(__GNUC__) + /* + I've had a bug report where GCC is emitting warnings about functions possibly not being inlineable. This warning happens when + the __attribute__((always_inline)) attribute is defined without an "inline" statement. I think therefore there must be some + case where "__inline__" is not always defined, thus the compiler emitting these warnings. When using -std=c89 or -ansi on the + command line, we cannot use the "inline" keyword and instead need to use "__inline__". In an attempt to work around this issue + I am using "__inline__" only when we're compiling in strict ANSI mode. + */ + #if defined(__STRICT_ANSI__) + #define DRFLAC_GNUC_INLINE_HINT __inline__ + #else + #define DRFLAC_GNUC_INLINE_HINT inline + #endif + + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT + #endif +#elif defined(__WATCOMC__) + #define DRFLAC_INLINE __inline +#else + #define DRFLAC_INLINE +#endif +/* End Inline */ + +/* +Intrinsics Support + +There's a bug in GCC 4.2.x which results in an incorrect compilation error when using _mm_slli_epi32() where it complains with + + "error: shift must be an immediate" + +Unfortuantely dr_flac depends on this for a few things so we're just going to disable SSE on GCC 4.2 and below. +*/ +#if !defined(DR_FLAC_NO_SIMD) + #if defined(DRFLAC_X64) || defined(DRFLAC_X86) + #if defined(_MSC_VER) && !defined(__clang__) + /* MSVC. */ + #if _MSC_VER >= 1400 && !defined(DRFLAC_NO_SSE2) /* 2005 */ + #define DRFLAC_SUPPORT_SSE2 + #endif + #if _MSC_VER >= 1600 && !defined(DRFLAC_NO_SSE41) /* 2010 */ + #define DRFLAC_SUPPORT_SSE41 + #endif + #elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))) + /* Assume GNUC-style. */ + #if defined(__SSE2__) && !defined(DRFLAC_NO_SSE2) + #define DRFLAC_SUPPORT_SSE2 + #endif + #if defined(__SSE4_1__) && !defined(DRFLAC_NO_SSE41) + #define DRFLAC_SUPPORT_SSE41 + #endif + #endif + + /* If at this point we still haven't determined compiler support for the intrinsics just fall back to __has_include. */ + #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include) + #if !defined(DRFLAC_SUPPORT_SSE2) && !defined(DRFLAC_NO_SSE2) && __has_include() + #define DRFLAC_SUPPORT_SSE2 + #endif + #if !defined(DRFLAC_SUPPORT_SSE41) && !defined(DRFLAC_NO_SSE41) && __has_include() + #define DRFLAC_SUPPORT_SSE41 + #endif + #endif + + #if defined(DRFLAC_SUPPORT_SSE41) + #include + #elif defined(DRFLAC_SUPPORT_SSE2) + #include + #endif + #endif + + #if defined(DRFLAC_ARM) + #if !defined(DRFLAC_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) + #define DRFLAC_SUPPORT_NEON + #include + #endif + #endif +#endif + +/* Compile-time CPU feature support. */ +#if !defined(DR_FLAC_NO_SIMD) && (defined(DRFLAC_X86) || defined(DRFLAC_X64)) + #if defined(_MSC_VER) && !defined(__clang__) + #if _MSC_VER >= 1400 + #include + static void drflac__cpuid(int info[4], int fid) + { + __cpuid(info, fid); + } + #else + #define DRFLAC_NO_CPUID + #endif + #else + #if defined(__GNUC__) || defined(__clang__) + static void drflac__cpuid(int info[4], int fid) + { + /* + It looks like the -fPIC option uses the ebx register which GCC complains about. We can work around this by just using a different register, the + specific register of which I'm letting the compiler decide on. The "k" prefix is used to specify a 32-bit register. The {...} syntax is for + supporting different assembly dialects. + + What's basically happening is that we're saving and restoring the ebx register manually. + */ + #if defined(DRFLAC_X86) && defined(__PIC__) + __asm__ __volatile__ ( + "xchg{l} {%%}ebx, %k1;" + "cpuid;" + "xchg{l} {%%}ebx, %k1;" + : "=a"(info[0]), "=&r"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) + ); + #else + __asm__ __volatile__ ( + "cpuid" : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) + ); + #endif + } + #else + #define DRFLAC_NO_CPUID + #endif + #endif +#else + #define DRFLAC_NO_CPUID +#endif + +static DRFLAC_INLINE drflac_bool32 drflac_has_sse2(void) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + #if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE2) + #if defined(DRFLAC_X64) + return DRFLAC_TRUE; /* 64-bit targets always support SSE2. */ + #elif (defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE2__) + return DRFLAC_TRUE; /* If the compiler is allowed to freely generate SSE2 code we can assume support. */ + #else + #if defined(DRFLAC_NO_CPUID) + return DRFLAC_FALSE; + #else + int info[4]; + drflac__cpuid(info, 1); + return (info[3] & (1 << 26)) != 0; + #endif + #endif + #else + return DRFLAC_FALSE; /* SSE2 is only supported on x86 and x64 architectures. */ + #endif +#else + return DRFLAC_FALSE; /* No compiler support. */ +#endif +} + +static DRFLAC_INLINE drflac_bool32 drflac_has_sse41(void) +{ +#if defined(DRFLAC_SUPPORT_SSE41) + #if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE41) + #if defined(__SSE4_1__) || defined(__AVX__) + return DRFLAC_TRUE; /* If the compiler is allowed to freely generate SSE41 code we can assume support. */ + #else + #if defined(DRFLAC_NO_CPUID) + return DRFLAC_FALSE; + #else + int info[4]; + drflac__cpuid(info, 1); + return (info[2] & (1 << 19)) != 0; + #endif + #endif + #else + return DRFLAC_FALSE; /* SSE41 is only supported on x86 and x64 architectures. */ + #endif +#else + return DRFLAC_FALSE; /* No compiler support. */ +#endif +} + + +#if defined(_MSC_VER) && _MSC_VER >= 1500 && (defined(DRFLAC_X86) || defined(DRFLAC_X64)) && !defined(__clang__) + #define DRFLAC_HAS_LZCNT_INTRINSIC +#elif (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) + #define DRFLAC_HAS_LZCNT_INTRINSIC +#elif defined(__clang__) + #if defined(__has_builtin) + #if __has_builtin(__builtin_clzll) || __has_builtin(__builtin_clzl) + #define DRFLAC_HAS_LZCNT_INTRINSIC + #endif + #endif +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1400 && !defined(__clang__) + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC +#elif defined(__clang__) + #if defined(__has_builtin) + #if __has_builtin(__builtin_bswap16) + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap32) + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap64) + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC + #endif + #endif +#elif defined(__GNUC__) + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC + #endif + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #endif +#elif defined(__WATCOMC__) && defined(__386__) + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC + extern __inline drflac_uint16 _watcom_bswap16(drflac_uint16); + extern __inline drflac_uint32 _watcom_bswap32(drflac_uint32); + extern __inline drflac_uint64 _watcom_bswap64(drflac_uint64); +#pragma aux _watcom_bswap16 = \ + "xchg al, ah" \ + parm [ax] \ + value [ax] \ + modify nomemory; +#pragma aux _watcom_bswap32 = \ + "bswap eax" \ + parm [eax] \ + value [eax] \ + modify nomemory; +#pragma aux _watcom_bswap64 = \ + "bswap eax" \ + "bswap edx" \ + "xchg eax,edx" \ + parm [eax edx] \ + value [eax edx] \ + modify nomemory; +#endif + + +/* Standard library stuff. */ +#ifndef DRFLAC_ASSERT +#include +#define DRFLAC_ASSERT(expression) assert(expression) +#endif +#ifndef DRFLAC_MALLOC +#define DRFLAC_MALLOC(sz) malloc((sz)) +#endif +#ifndef DRFLAC_REALLOC +#define DRFLAC_REALLOC(p, sz) realloc((p), (sz)) +#endif +#ifndef DRFLAC_FREE +#define DRFLAC_FREE(p) free((p)) +#endif +#ifndef DRFLAC_COPY_MEMORY +#define DRFLAC_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#ifndef DRFLAC_ZERO_MEMORY +#define DRFLAC_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif +#ifndef DRFLAC_ZERO_OBJECT +#define DRFLAC_ZERO_OBJECT(p) DRFLAC_ZERO_MEMORY((p), sizeof(*(p))) +#endif + +#define DRFLAC_MAX_SIMD_VECTOR_SIZE 64 /* 64 for AVX-512 in the future. */ + +/* Result Codes */ +typedef drflac_int32 drflac_result; +#define DRFLAC_SUCCESS 0 +#define DRFLAC_ERROR -1 /* A generic error. */ +#define DRFLAC_INVALID_ARGS -2 +#define DRFLAC_INVALID_OPERATION -3 +#define DRFLAC_OUT_OF_MEMORY -4 +#define DRFLAC_OUT_OF_RANGE -5 +#define DRFLAC_ACCESS_DENIED -6 +#define DRFLAC_DOES_NOT_EXIST -7 +#define DRFLAC_ALREADY_EXISTS -8 +#define DRFLAC_TOO_MANY_OPEN_FILES -9 +#define DRFLAC_INVALID_FILE -10 +#define DRFLAC_TOO_BIG -11 +#define DRFLAC_PATH_TOO_LONG -12 +#define DRFLAC_NAME_TOO_LONG -13 +#define DRFLAC_NOT_DIRECTORY -14 +#define DRFLAC_IS_DIRECTORY -15 +#define DRFLAC_DIRECTORY_NOT_EMPTY -16 +#define DRFLAC_END_OF_FILE -17 +#define DRFLAC_NO_SPACE -18 +#define DRFLAC_BUSY -19 +#define DRFLAC_IO_ERROR -20 +#define DRFLAC_INTERRUPT -21 +#define DRFLAC_UNAVAILABLE -22 +#define DRFLAC_ALREADY_IN_USE -23 +#define DRFLAC_BAD_ADDRESS -24 +#define DRFLAC_BAD_SEEK -25 +#define DRFLAC_BAD_PIPE -26 +#define DRFLAC_DEADLOCK -27 +#define DRFLAC_TOO_MANY_LINKS -28 +#define DRFLAC_NOT_IMPLEMENTED -29 +#define DRFLAC_NO_MESSAGE -30 +#define DRFLAC_BAD_MESSAGE -31 +#define DRFLAC_NO_DATA_AVAILABLE -32 +#define DRFLAC_INVALID_DATA -33 +#define DRFLAC_TIMEOUT -34 +#define DRFLAC_NO_NETWORK -35 +#define DRFLAC_NOT_UNIQUE -36 +#define DRFLAC_NOT_SOCKET -37 +#define DRFLAC_NO_ADDRESS -38 +#define DRFLAC_BAD_PROTOCOL -39 +#define DRFLAC_PROTOCOL_UNAVAILABLE -40 +#define DRFLAC_PROTOCOL_NOT_SUPPORTED -41 +#define DRFLAC_PROTOCOL_FAMILY_NOT_SUPPORTED -42 +#define DRFLAC_ADDRESS_FAMILY_NOT_SUPPORTED -43 +#define DRFLAC_SOCKET_NOT_SUPPORTED -44 +#define DRFLAC_CONNECTION_RESET -45 +#define DRFLAC_ALREADY_CONNECTED -46 +#define DRFLAC_NOT_CONNECTED -47 +#define DRFLAC_CONNECTION_REFUSED -48 +#define DRFLAC_NO_HOST -49 +#define DRFLAC_IN_PROGRESS -50 +#define DRFLAC_CANCELLED -51 +#define DRFLAC_MEMORY_ALREADY_MAPPED -52 +#define DRFLAC_AT_END -53 + +#define DRFLAC_CRC_MISMATCH -100 +/* End Result Codes */ + + +#define DRFLAC_SUBFRAME_CONSTANT 0 +#define DRFLAC_SUBFRAME_VERBATIM 1 +#define DRFLAC_SUBFRAME_FIXED 8 +#define DRFLAC_SUBFRAME_LPC 32 +#define DRFLAC_SUBFRAME_RESERVED 255 + +#define DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE 0 +#define DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2 1 + +#define DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT 0 +#define DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE 8 +#define DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE 9 +#define DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE 10 + +#define DRFLAC_SEEKPOINT_SIZE_IN_BYTES 18 +#define DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES 36 +#define DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES 12 + +#define drflac_align(x, a) ((((x) + (a) - 1) / (a)) * (a)) + + +DRFLAC_API void drflac_version(drflac_uint32* pMajor, drflac_uint32* pMinor, drflac_uint32* pRevision) +{ + if (pMajor) { + *pMajor = DRFLAC_VERSION_MAJOR; + } + + if (pMinor) { + *pMinor = DRFLAC_VERSION_MINOR; + } + + if (pRevision) { + *pRevision = DRFLAC_VERSION_REVISION; + } +} + +DRFLAC_API const char* drflac_version_string(void) +{ + return DRFLAC_VERSION_STRING; +} + + +/* CPU caps. */ +#if defined(__has_feature) + #if __has_feature(thread_sanitizer) + #define DRFLAC_NO_THREAD_SANITIZE __attribute__((no_sanitize("thread"))) + #else + #define DRFLAC_NO_THREAD_SANITIZE + #endif +#else + #define DRFLAC_NO_THREAD_SANITIZE +#endif + +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) +static drflac_bool32 drflac__gIsLZCNTSupported = DRFLAC_FALSE; +#endif + +#ifndef DRFLAC_NO_CPUID +static drflac_bool32 drflac__gIsSSE2Supported = DRFLAC_FALSE; +static drflac_bool32 drflac__gIsSSE41Supported = DRFLAC_FALSE; + +/* +I've had a bug report that Clang's ThreadSanitizer presents a warning in this function. Having reviewed this, this does +actually make sense. However, since CPU caps should never differ for a running process, I don't think the trade off of +complicating internal API's by passing around CPU caps versus just disabling the warnings is worthwhile. I'm therefore +just going to disable these warnings. This is disabled via the DRFLAC_NO_THREAD_SANITIZE attribute. +*/ +DRFLAC_NO_THREAD_SANITIZE static void drflac__init_cpu_caps(void) +{ + static drflac_bool32 isCPUCapsInitialized = DRFLAC_FALSE; + + if (!isCPUCapsInitialized) { + /* LZCNT */ +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) + int info[4] = {0}; + drflac__cpuid(info, 0x80000001); + drflac__gIsLZCNTSupported = (info[2] & (1 << 5)) != 0; +#endif + + /* SSE2 */ + drflac__gIsSSE2Supported = drflac_has_sse2(); + + /* SSE4.1 */ + drflac__gIsSSE41Supported = drflac_has_sse41(); + + /* Initialized. */ + isCPUCapsInitialized = DRFLAC_TRUE; + } +} +#else +static drflac_bool32 drflac__gIsNEONSupported = DRFLAC_FALSE; + +static DRFLAC_INLINE drflac_bool32 drflac__has_neon(void) +{ +#if defined(DRFLAC_SUPPORT_NEON) + #if defined(DRFLAC_ARM) && !defined(DRFLAC_NO_NEON) + #if (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) + return DRFLAC_TRUE; /* If the compiler is allowed to freely generate NEON code we can assume support. */ + #else + /* TODO: Runtime check. */ + return DRFLAC_FALSE; + #endif + #else + return DRFLAC_FALSE; /* NEON is only supported on ARM architectures. */ + #endif +#else + return DRFLAC_FALSE; /* No compiler support. */ +#endif +} + +DRFLAC_NO_THREAD_SANITIZE static void drflac__init_cpu_caps(void) +{ + drflac__gIsNEONSupported = drflac__has_neon(); + +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) && defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) + drflac__gIsLZCNTSupported = DRFLAC_TRUE; +#endif +} +#endif + + +/* Endian Management */ +static DRFLAC_INLINE drflac_bool32 drflac__is_little_endian(void) +{ +#if defined(DRFLAC_X86) || defined(DRFLAC_X64) + return DRFLAC_TRUE; +#elif defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN + return DRFLAC_TRUE; +#else + int n = 1; + return (*(char*)&n) == 1; +#endif +} + +static DRFLAC_INLINE drflac_uint16 drflac__swap_endian_uint16(drflac_uint16 n) +{ +#ifdef DRFLAC_HAS_BYTESWAP16_INTRINSIC + #if defined(_MSC_VER) && !defined(__clang__) + return _byteswap_ushort(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap16(n); + #elif defined(__WATCOMC__) && defined(__386__) + return _watcom_bswap16(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & 0xFF00) >> 8) | + ((n & 0x00FF) << 8); +#endif +} + +static DRFLAC_INLINE drflac_uint32 drflac__swap_endian_uint32(drflac_uint32 n) +{ +#ifdef DRFLAC_HAS_BYTESWAP32_INTRINSIC + #if defined(_MSC_VER) && !defined(__clang__) + return _byteswap_ulong(n); + #elif defined(__GNUC__) || defined(__clang__) + #if defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(__ARM_ARCH_6M__) && !defined(DRFLAC_64BIT) /* <-- 64-bit inline assembly has not been tested, so disabling for now. */ + /* Inline assembly optimized implementation for ARM. In my testing, GCC does not generate optimized code with __builtin_bswap32(). */ + drflac_uint32 r; + __asm__ __volatile__ ( + #if defined(DRFLAC_64BIT) + "rev %w[out], %w[in]" : [out]"=r"(r) : [in]"r"(n) /* <-- This is untested. If someone in the community could test this, that would be appreciated! */ + #else + "rev %[out], %[in]" : [out]"=r"(r) : [in]"r"(n) + #endif + ); + return r; + #else + return __builtin_bswap32(n); + #endif + #elif defined(__WATCOMC__) && defined(__386__) + return _watcom_bswap32(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & 0xFF000000) >> 24) | + ((n & 0x00FF0000) >> 8) | + ((n & 0x0000FF00) << 8) | + ((n & 0x000000FF) << 24); +#endif +} + +static DRFLAC_INLINE drflac_uint64 drflac__swap_endian_uint64(drflac_uint64 n) +{ +#ifdef DRFLAC_HAS_BYTESWAP64_INTRINSIC + #if defined(_MSC_VER) && !defined(__clang__) + return _byteswap_uint64(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap64(n); + #elif defined(__WATCOMC__) && defined(__386__) + return _watcom_bswap64(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ + return ((n & ((drflac_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drflac_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drflac_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drflac_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drflac_uint64)0xFF000000 )) << 8) | + ((n & ((drflac_uint64)0x00FF0000 )) << 24) | + ((n & ((drflac_uint64)0x0000FF00 )) << 40) | + ((n & ((drflac_uint64)0x000000FF )) << 56); +#endif +} + + +static DRFLAC_INLINE drflac_uint16 drflac__be2host_16(drflac_uint16 n) +{ + if (drflac__is_little_endian()) { + return drflac__swap_endian_uint16(n); + } + + return n; +} + +static DRFLAC_INLINE drflac_uint32 drflac__be2host_32(drflac_uint32 n) +{ + if (drflac__is_little_endian()) { + return drflac__swap_endian_uint32(n); + } + + return n; +} + +static DRFLAC_INLINE drflac_uint32 drflac__be2host_32_ptr_unaligned(const void* pData) +{ + const drflac_uint8* pNum = (drflac_uint8*)pData; + return *(pNum) << 24 | *(pNum+1) << 16 | *(pNum+2) << 8 | *(pNum+3); +} + +static DRFLAC_INLINE drflac_uint64 drflac__be2host_64(drflac_uint64 n) +{ + if (drflac__is_little_endian()) { + return drflac__swap_endian_uint64(n); + } + + return n; +} + + +static DRFLAC_INLINE drflac_uint32 drflac__le2host_32(drflac_uint32 n) +{ + if (!drflac__is_little_endian()) { + return drflac__swap_endian_uint32(n); + } + + return n; +} + +static DRFLAC_INLINE drflac_uint32 drflac__le2host_32_ptr_unaligned(const void* pData) +{ + const drflac_uint8* pNum = (drflac_uint8*)pData; + return *pNum | *(pNum+1) << 8 | *(pNum+2) << 16 | *(pNum+3) << 24; +} + + +static DRFLAC_INLINE drflac_uint32 drflac__unsynchsafe_32(drflac_uint32 n) +{ + drflac_uint32 result = 0; + result |= (n & 0x7F000000) >> 3; + result |= (n & 0x007F0000) >> 2; + result |= (n & 0x00007F00) >> 1; + result |= (n & 0x0000007F) >> 0; + + return result; +} + + + +/* The CRC code below is based on this document: http://zlib.net/crc_v3.txt */ +static drflac_uint8 drflac__crc8_table[] = { + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, + 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, + 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, + 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, + 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, + 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, + 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, + 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, + 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, + 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, + 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 +}; + +static drflac_uint16 drflac__crc16_table[] = { + 0x0000, 0x8005, 0x800F, 0x000A, 0x801B, 0x001E, 0x0014, 0x8011, + 0x8033, 0x0036, 0x003C, 0x8039, 0x0028, 0x802D, 0x8027, 0x0022, + 0x8063, 0x0066, 0x006C, 0x8069, 0x0078, 0x807D, 0x8077, 0x0072, + 0x0050, 0x8055, 0x805F, 0x005A, 0x804B, 0x004E, 0x0044, 0x8041, + 0x80C3, 0x00C6, 0x00CC, 0x80C9, 0x00D8, 0x80DD, 0x80D7, 0x00D2, + 0x00F0, 0x80F5, 0x80FF, 0x00FA, 0x80EB, 0x00EE, 0x00E4, 0x80E1, + 0x00A0, 0x80A5, 0x80AF, 0x00AA, 0x80BB, 0x00BE, 0x00B4, 0x80B1, + 0x8093, 0x0096, 0x009C, 0x8099, 0x0088, 0x808D, 0x8087, 0x0082, + 0x8183, 0x0186, 0x018C, 0x8189, 0x0198, 0x819D, 0x8197, 0x0192, + 0x01B0, 0x81B5, 0x81BF, 0x01BA, 0x81AB, 0x01AE, 0x01A4, 0x81A1, + 0x01E0, 0x81E5, 0x81EF, 0x01EA, 0x81FB, 0x01FE, 0x01F4, 0x81F1, + 0x81D3, 0x01D6, 0x01DC, 0x81D9, 0x01C8, 0x81CD, 0x81C7, 0x01C2, + 0x0140, 0x8145, 0x814F, 0x014A, 0x815B, 0x015E, 0x0154, 0x8151, + 0x8173, 0x0176, 0x017C, 0x8179, 0x0168, 0x816D, 0x8167, 0x0162, + 0x8123, 0x0126, 0x012C, 0x8129, 0x0138, 0x813D, 0x8137, 0x0132, + 0x0110, 0x8115, 0x811F, 0x011A, 0x810B, 0x010E, 0x0104, 0x8101, + 0x8303, 0x0306, 0x030C, 0x8309, 0x0318, 0x831D, 0x8317, 0x0312, + 0x0330, 0x8335, 0x833F, 0x033A, 0x832B, 0x032E, 0x0324, 0x8321, + 0x0360, 0x8365, 0x836F, 0x036A, 0x837B, 0x037E, 0x0374, 0x8371, + 0x8353, 0x0356, 0x035C, 0x8359, 0x0348, 0x834D, 0x8347, 0x0342, + 0x03C0, 0x83C5, 0x83CF, 0x03CA, 0x83DB, 0x03DE, 0x03D4, 0x83D1, + 0x83F3, 0x03F6, 0x03FC, 0x83F9, 0x03E8, 0x83ED, 0x83E7, 0x03E2, + 0x83A3, 0x03A6, 0x03AC, 0x83A9, 0x03B8, 0x83BD, 0x83B7, 0x03B2, + 0x0390, 0x8395, 0x839F, 0x039A, 0x838B, 0x038E, 0x0384, 0x8381, + 0x0280, 0x8285, 0x828F, 0x028A, 0x829B, 0x029E, 0x0294, 0x8291, + 0x82B3, 0x02B6, 0x02BC, 0x82B9, 0x02A8, 0x82AD, 0x82A7, 0x02A2, + 0x82E3, 0x02E6, 0x02EC, 0x82E9, 0x02F8, 0x82FD, 0x82F7, 0x02F2, + 0x02D0, 0x82D5, 0x82DF, 0x02DA, 0x82CB, 0x02CE, 0x02C4, 0x82C1, + 0x8243, 0x0246, 0x024C, 0x8249, 0x0258, 0x825D, 0x8257, 0x0252, + 0x0270, 0x8275, 0x827F, 0x027A, 0x826B, 0x026E, 0x0264, 0x8261, + 0x0220, 0x8225, 0x822F, 0x022A, 0x823B, 0x023E, 0x0234, 0x8231, + 0x8213, 0x0216, 0x021C, 0x8219, 0x0208, 0x820D, 0x8207, 0x0202 +}; + +static DRFLAC_INLINE drflac_uint8 drflac_crc8_byte(drflac_uint8 crc, drflac_uint8 data) +{ + return drflac__crc8_table[crc ^ data]; +} + +static DRFLAC_INLINE drflac_uint8 drflac_crc8(drflac_uint8 crc, drflac_uint32 data, drflac_uint32 count) +{ +#ifdef DR_FLAC_NO_CRC + (void)crc; + (void)data; + (void)count; + return 0; +#else +#if 0 + /* REFERENCE (use of this implementation requires an explicit flush by doing "drflac_crc8(crc, 0, 8);") */ + drflac_uint8 p = 0x07; + for (int i = count-1; i >= 0; --i) { + drflac_uint8 bit = (data & (1 << i)) >> i; + if (crc & 0x80) { + crc = ((crc << 1) | bit) ^ p; + } else { + crc = ((crc << 1) | bit); + } + } + return crc; +#else + drflac_uint32 wholeBytes; + drflac_uint32 leftoverBits; + drflac_uint64 leftoverDataMask; + + static drflac_uint64 leftoverDataMaskTable[8] = { + 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F + }; + + DRFLAC_ASSERT(count <= 32); + + wholeBytes = count >> 3; + leftoverBits = count - (wholeBytes*8); + leftoverDataMask = leftoverDataMaskTable[leftoverBits]; + + switch (wholeBytes) { + case 4: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0xFF000000UL << leftoverBits)) >> (24 + leftoverBits))); + case 3: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x00FF0000UL << leftoverBits)) >> (16 + leftoverBits))); + case 2: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x0000FF00UL << leftoverBits)) >> ( 8 + leftoverBits))); + case 1: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x000000FFUL << leftoverBits)) >> ( 0 + leftoverBits))); + case 0: if (leftoverBits > 0) crc = (drflac_uint8)((crc << leftoverBits) ^ drflac__crc8_table[(crc >> (8 - leftoverBits)) ^ (data & leftoverDataMask)]); + } + return crc; +#endif +#endif +} + +static DRFLAC_INLINE drflac_uint16 drflac_crc16_byte(drflac_uint16 crc, drflac_uint8 data) +{ + return (crc << 8) ^ drflac__crc16_table[(drflac_uint8)(crc >> 8) ^ data]; +} + +static DRFLAC_INLINE drflac_uint16 drflac_crc16_cache(drflac_uint16 crc, drflac_cache_t data) +{ +#ifdef DRFLAC_64BIT + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 56) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 48) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 40) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 32) & 0xFF)); +#endif + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 24) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 16) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 8) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 0) & 0xFF)); + + return crc; +} + +static DRFLAC_INLINE drflac_uint16 drflac_crc16_bytes(drflac_uint16 crc, drflac_cache_t data, drflac_uint32 byteCount) +{ + switch (byteCount) + { +#ifdef DRFLAC_64BIT + case 8: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 56) & 0xFF)); + case 7: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 48) & 0xFF)); + case 6: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 40) & 0xFF)); + case 5: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 32) & 0xFF)); +#endif + case 4: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 24) & 0xFF)); + case 3: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 16) & 0xFF)); + case 2: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 8) & 0xFF)); + case 1: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 0) & 0xFF)); + } + + return crc; +} + +#if 0 +static DRFLAC_INLINE drflac_uint16 drflac_crc16__32bit(drflac_uint16 crc, drflac_uint32 data, drflac_uint32 count) +{ +#ifdef DR_FLAC_NO_CRC + (void)crc; + (void)data; + (void)count; + return 0; +#else +#if 0 + /* REFERENCE (use of this implementation requires an explicit flush by doing "drflac_crc16(crc, 0, 16);") */ + drflac_uint16 p = 0x8005; + for (int i = count-1; i >= 0; --i) { + drflac_uint16 bit = (data & (1ULL << i)) >> i; + if (r & 0x8000) { + r = ((r << 1) | bit) ^ p; + } else { + r = ((r << 1) | bit); + } + } + + return crc; +#else + drflac_uint32 wholeBytes; + drflac_uint32 leftoverBits; + drflac_uint64 leftoverDataMask; + + static drflac_uint64 leftoverDataMaskTable[8] = { + 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F + }; + + DRFLAC_ASSERT(count <= 64); + + wholeBytes = count >> 3; + leftoverBits = count & 7; + leftoverDataMask = leftoverDataMaskTable[leftoverBits]; + + switch (wholeBytes) { + default: + case 4: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0xFF000000UL << leftoverBits)) >> (24 + leftoverBits))); + case 3: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0x00FF0000UL << leftoverBits)) >> (16 + leftoverBits))); + case 2: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0x0000FF00UL << leftoverBits)) >> ( 8 + leftoverBits))); + case 1: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0x000000FFUL << leftoverBits)) >> ( 0 + leftoverBits))); + case 0: if (leftoverBits > 0) crc = (crc << leftoverBits) ^ drflac__crc16_table[(crc >> (16 - leftoverBits)) ^ (data & leftoverDataMask)]; + } + return crc; +#endif +#endif +} + +static DRFLAC_INLINE drflac_uint16 drflac_crc16__64bit(drflac_uint16 crc, drflac_uint64 data, drflac_uint32 count) +{ +#ifdef DR_FLAC_NO_CRC + (void)crc; + (void)data; + (void)count; + return 0; +#else + drflac_uint32 wholeBytes; + drflac_uint32 leftoverBits; + drflac_uint64 leftoverDataMask; + + static drflac_uint64 leftoverDataMaskTable[8] = { + 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F + }; + + DRFLAC_ASSERT(count <= 64); + + wholeBytes = count >> 3; + leftoverBits = count & 7; + leftoverDataMask = leftoverDataMaskTable[leftoverBits]; + + switch (wholeBytes) { + default: + case 8: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0xFF000000 << 32) << leftoverBits)) >> (56 + leftoverBits))); /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ + case 7: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x00FF0000 << 32) << leftoverBits)) >> (48 + leftoverBits))); + case 6: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x0000FF00 << 32) << leftoverBits)) >> (40 + leftoverBits))); + case 5: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x000000FF << 32) << leftoverBits)) >> (32 + leftoverBits))); + case 4: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0xFF000000 ) << leftoverBits)) >> (24 + leftoverBits))); + case 3: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x00FF0000 ) << leftoverBits)) >> (16 + leftoverBits))); + case 2: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x0000FF00 ) << leftoverBits)) >> ( 8 + leftoverBits))); + case 1: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (((drflac_uint64)0x000000FF ) << leftoverBits)) >> ( 0 + leftoverBits))); + case 0: if (leftoverBits > 0) crc = (crc << leftoverBits) ^ drflac__crc16_table[(crc >> (16 - leftoverBits)) ^ (data & leftoverDataMask)]; + } + return crc; +#endif +} + + +static DRFLAC_INLINE drflac_uint16 drflac_crc16(drflac_uint16 crc, drflac_cache_t data, drflac_uint32 count) +{ +#ifdef DRFLAC_64BIT + return drflac_crc16__64bit(crc, data, count); +#else + return drflac_crc16__32bit(crc, data, count); +#endif +} +#endif + + +#ifdef DRFLAC_64BIT +#define drflac__be2host__cache_line drflac__be2host_64 +#else +#define drflac__be2host__cache_line drflac__be2host_32 +#endif + +/* +BIT READING ATTEMPT #2 + +This uses a 32- or 64-bit bit-shifted cache - as bits are read, the cache is shifted such that the first valid bit is sitting +on the most significant bit. It uses the notion of an L1 and L2 cache (borrowed from CPU architecture), where the L1 cache +is a 32- or 64-bit unsigned integer (depending on whether or not a 32- or 64-bit build is being compiled) and the L2 is an +array of "cache lines", with each cache line being the same size as the L1. The L2 is a buffer of about 4KB and is where data +from onRead() is read into. +*/ +#define DRFLAC_CACHE_L1_SIZE_BYTES(bs) (sizeof((bs)->cache)) +#define DRFLAC_CACHE_L1_SIZE_BITS(bs) (sizeof((bs)->cache)*8) +#define DRFLAC_CACHE_L1_BITS_REMAINING(bs) (DRFLAC_CACHE_L1_SIZE_BITS(bs) - (bs)->consumedBits) +#define DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount) (~((~(drflac_cache_t)0) >> (_bitCount))) +#define DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, _bitCount) (DRFLAC_CACHE_L1_SIZE_BITS(bs) - (_bitCount)) +#define DRFLAC_CACHE_L1_SELECT(bs, _bitCount) (((bs)->cache) & DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount)) +#define DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, _bitCount) (DRFLAC_CACHE_L1_SELECT((bs), (_bitCount)) >> DRFLAC_CACHE_L1_SELECTION_SHIFT((bs), (_bitCount))) +#define DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, _bitCount)(DRFLAC_CACHE_L1_SELECT((bs), (_bitCount)) >> (DRFLAC_CACHE_L1_SELECTION_SHIFT((bs), (_bitCount)) & (DRFLAC_CACHE_L1_SIZE_BITS(bs)-1))) +#define DRFLAC_CACHE_L2_SIZE_BYTES(bs) (sizeof((bs)->cacheL2)) +#define DRFLAC_CACHE_L2_LINE_COUNT(bs) (DRFLAC_CACHE_L2_SIZE_BYTES(bs) / sizeof((bs)->cacheL2[0])) +#define DRFLAC_CACHE_L2_LINES_REMAINING(bs) (DRFLAC_CACHE_L2_LINE_COUNT(bs) - (bs)->nextL2Line) + + +#ifndef DR_FLAC_NO_CRC +static DRFLAC_INLINE void drflac__reset_crc16(drflac_bs* bs) +{ + bs->crc16 = 0; + bs->crc16CacheIgnoredBytes = bs->consumedBits >> 3; +} + +static DRFLAC_INLINE void drflac__update_crc16(drflac_bs* bs) +{ + if (bs->crc16CacheIgnoredBytes == 0) { + bs->crc16 = drflac_crc16_cache(bs->crc16, bs->crc16Cache); + } else { + bs->crc16 = drflac_crc16_bytes(bs->crc16, bs->crc16Cache, DRFLAC_CACHE_L1_SIZE_BYTES(bs) - bs->crc16CacheIgnoredBytes); + bs->crc16CacheIgnoredBytes = 0; + } +} + +static DRFLAC_INLINE drflac_uint16 drflac__flush_crc16(drflac_bs* bs) +{ + /* We should never be flushing in a situation where we are not aligned on a byte boundary. */ + DRFLAC_ASSERT((DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7) == 0); + + /* + The bits that were read from the L1 cache need to be accumulated. The number of bytes needing to be accumulated is determined + by the number of bits that have been consumed. + */ + if (DRFLAC_CACHE_L1_BITS_REMAINING(bs) == 0) { + drflac__update_crc16(bs); + } else { + /* We only accumulate the consumed bits. */ + bs->crc16 = drflac_crc16_bytes(bs->crc16, bs->crc16Cache >> DRFLAC_CACHE_L1_BITS_REMAINING(bs), (bs->consumedBits >> 3) - bs->crc16CacheIgnoredBytes); + + /* + The bits that we just accumulated should never be accumulated again. We need to keep track of how many bytes were accumulated + so we can handle that later. + */ + bs->crc16CacheIgnoredBytes = bs->consumedBits >> 3; + } + + return bs->crc16; +} +#endif + +static DRFLAC_INLINE drflac_bool32 drflac__reload_l1_cache_from_l2(drflac_bs* bs) +{ + size_t bytesRead; + size_t alignedL1LineCount; + + /* Fast path. Try loading straight from L2. */ + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + bs->cache = bs->cacheL2[bs->nextL2Line++]; + return DRFLAC_TRUE; + } + + /* + If we get here it means we've run out of data in the L2 cache. We'll need to fetch more from the client, if there's + any left. + */ + if (bs->unalignedByteCount > 0) { + return DRFLAC_FALSE; /* If we have any unaligned bytes it means there's no more aligned bytes left in the client. */ + } + + bytesRead = bs->onRead(bs->pUserData, bs->cacheL2, DRFLAC_CACHE_L2_SIZE_BYTES(bs)); + + bs->nextL2Line = 0; + if (bytesRead == DRFLAC_CACHE_L2_SIZE_BYTES(bs)) { + bs->cache = bs->cacheL2[bs->nextL2Line++]; + return DRFLAC_TRUE; + } + + + /* + If we get here it means we were unable to retrieve enough data to fill the entire L2 cache. It probably + means we've just reached the end of the file. We need to move the valid data down to the end of the buffer + and adjust the index of the next line accordingly. Also keep in mind that the L2 cache must be aligned to + the size of the L1 so we'll need to seek backwards by any misaligned bytes. + */ + alignedL1LineCount = bytesRead / DRFLAC_CACHE_L1_SIZE_BYTES(bs); + + /* We need to keep track of any unaligned bytes for later use. */ + bs->unalignedByteCount = bytesRead - (alignedL1LineCount * DRFLAC_CACHE_L1_SIZE_BYTES(bs)); + if (bs->unalignedByteCount > 0) { + bs->unalignedCache = bs->cacheL2[alignedL1LineCount]; + } + + if (alignedL1LineCount > 0) { + size_t offset = DRFLAC_CACHE_L2_LINE_COUNT(bs) - alignedL1LineCount; + size_t i; + for (i = alignedL1LineCount; i > 0; --i) { + bs->cacheL2[i-1 + offset] = bs->cacheL2[i-1]; + } + + bs->nextL2Line = (drflac_uint32)offset; + bs->cache = bs->cacheL2[bs->nextL2Line++]; + return DRFLAC_TRUE; + } else { + /* If we get into this branch it means we weren't able to load any L1-aligned data. */ + bs->nextL2Line = DRFLAC_CACHE_L2_LINE_COUNT(bs); + return DRFLAC_FALSE; + } +} + +static drflac_bool32 drflac__reload_cache(drflac_bs* bs) +{ + size_t bytesRead; + +#ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); +#endif + + /* Fast path. Try just moving the next value in the L2 cache to the L1 cache. */ + if (drflac__reload_l1_cache_from_l2(bs)) { + bs->cache = drflac__be2host__cache_line(bs->cache); + bs->consumedBits = 0; +#ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs->cache; +#endif + return DRFLAC_TRUE; + } + + /* Slow path. */ + + /* + If we get here it means we have failed to load the L1 cache from the L2. Likely we've just reached the end of the stream and the last + few bytes did not meet the alignment requirements for the L2 cache. In this case we need to fall back to a slower path and read the + data from the unaligned cache. + */ + bytesRead = bs->unalignedByteCount; + if (bytesRead == 0) { + bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); /* <-- The stream has been exhausted, so marked the bits as consumed. */ + return DRFLAC_FALSE; + } + + DRFLAC_ASSERT(bytesRead < DRFLAC_CACHE_L1_SIZE_BYTES(bs)); + bs->consumedBits = (drflac_uint32)(DRFLAC_CACHE_L1_SIZE_BYTES(bs) - bytesRead) * 8; + + bs->cache = drflac__be2host__cache_line(bs->unalignedCache); + bs->cache &= DRFLAC_CACHE_L1_SELECTION_MASK(DRFLAC_CACHE_L1_BITS_REMAINING(bs)); /* <-- Make sure the consumed bits are always set to zero. Other parts of the library depend on this property. */ + bs->unalignedByteCount = 0; /* <-- At this point the unaligned bytes have been moved into the cache and we thus have no more unaligned bytes. */ + +#ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs->cache >> bs->consumedBits; + bs->crc16CacheIgnoredBytes = bs->consumedBits >> 3; +#endif + return DRFLAC_TRUE; +} + +static void drflac__reset_cache(drflac_bs* bs) +{ + bs->nextL2Line = DRFLAC_CACHE_L2_LINE_COUNT(bs); /* <-- This clears the L2 cache. */ + bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); /* <-- This clears the L1 cache. */ + bs->cache = 0; + bs->unalignedByteCount = 0; /* <-- This clears the trailing unaligned bytes. */ + bs->unalignedCache = 0; + +#ifndef DR_FLAC_NO_CRC + bs->crc16Cache = 0; + bs->crc16CacheIgnoredBytes = 0; +#endif +} + + +static DRFLAC_INLINE drflac_bool32 drflac__read_uint32(drflac_bs* bs, unsigned int bitCount, drflac_uint32* pResultOut) +{ + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResultOut != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 32); + + if (bs->consumedBits == DRFLAC_CACHE_L1_SIZE_BITS(bs)) { + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + } + + if (bitCount <= DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* + If we want to load all 32-bits from a 32-bit cache we need to do it slightly differently because we can't do + a 32-bit shift on a 32-bit integer. This will never be the case on 64-bit caches, so we can have a slightly + more optimal solution for this. + */ +#ifdef DRFLAC_64BIT + *pResultOut = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCount); + bs->consumedBits += bitCount; + bs->cache <<= bitCount; +#else + if (bitCount < DRFLAC_CACHE_L1_SIZE_BITS(bs)) { + *pResultOut = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCount); + bs->consumedBits += bitCount; + bs->cache <<= bitCount; + } else { + /* Cannot shift by 32-bits, so need to do it differently. */ + *pResultOut = (drflac_uint32)bs->cache; + bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); + bs->cache = 0; + } +#endif + + return DRFLAC_TRUE; + } else { + /* It straddles the cached data. It will never cover more than the next chunk. We just read the number in two parts and combine them. */ + drflac_uint32 bitCountHi = DRFLAC_CACHE_L1_BITS_REMAINING(bs); + drflac_uint32 bitCountLo = bitCount - bitCountHi; + drflac_uint32 resultHi; + + DRFLAC_ASSERT(bitCountHi > 0); + DRFLAC_ASSERT(bitCountHi < 32); + resultHi = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountHi); + + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; + } + + *pResultOut = (resultHi << bitCountLo) | (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountLo); + bs->consumedBits += bitCountLo; + bs->cache <<= bitCountLo; + return DRFLAC_TRUE; + } +} + +static drflac_bool32 drflac__read_int32(drflac_bs* bs, unsigned int bitCount, drflac_int32* pResult) +{ + drflac_uint32 result; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 32); + + if (!drflac__read_uint32(bs, bitCount, &result)) { + return DRFLAC_FALSE; + } + + /* Do not attempt to shift by 32 as it's undefined. */ + if (bitCount < 32) { + drflac_uint32 signbit; + signbit = ((result >> (bitCount-1)) & 0x01); + result |= (~signbit + 1) << bitCount; + } + + *pResult = (drflac_int32)result; + return DRFLAC_TRUE; +} + +#ifdef DRFLAC_64BIT +static drflac_bool32 drflac__read_uint64(drflac_bs* bs, unsigned int bitCount, drflac_uint64* pResultOut) +{ + drflac_uint32 resultHi; + drflac_uint32 resultLo; + + DRFLAC_ASSERT(bitCount <= 64); + DRFLAC_ASSERT(bitCount > 32); + + if (!drflac__read_uint32(bs, bitCount - 32, &resultHi)) { + return DRFLAC_FALSE; + } + + if (!drflac__read_uint32(bs, 32, &resultLo)) { + return DRFLAC_FALSE; + } + + *pResultOut = (((drflac_uint64)resultHi) << 32) | ((drflac_uint64)resultLo); + return DRFLAC_TRUE; +} +#endif + +/* Function below is unused, but leaving it here in case I need to quickly add it again. */ +#if 0 +static drflac_bool32 drflac__read_int64(drflac_bs* bs, unsigned int bitCount, drflac_int64* pResultOut) +{ + drflac_uint64 result; + drflac_uint64 signbit; + + DRFLAC_ASSERT(bitCount <= 64); + + if (!drflac__read_uint64(bs, bitCount, &result)) { + return DRFLAC_FALSE; + } + + signbit = ((result >> (bitCount-1)) & 0x01); + result |= (~signbit + 1) << bitCount; + + *pResultOut = (drflac_int64)result; + return DRFLAC_TRUE; +} +#endif + +static drflac_bool32 drflac__read_uint16(drflac_bs* bs, unsigned int bitCount, drflac_uint16* pResult) +{ + drflac_uint32 result; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 16); + + if (!drflac__read_uint32(bs, bitCount, &result)) { + return DRFLAC_FALSE; + } + + *pResult = (drflac_uint16)result; + return DRFLAC_TRUE; +} + +#if 0 +static drflac_bool32 drflac__read_int16(drflac_bs* bs, unsigned int bitCount, drflac_int16* pResult) +{ + drflac_int32 result; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 16); + + if (!drflac__read_int32(bs, bitCount, &result)) { + return DRFLAC_FALSE; + } + + *pResult = (drflac_int16)result; + return DRFLAC_TRUE; +} +#endif + +static drflac_bool32 drflac__read_uint8(drflac_bs* bs, unsigned int bitCount, drflac_uint8* pResult) +{ + drflac_uint32 result; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 8); + + if (!drflac__read_uint32(bs, bitCount, &result)) { + return DRFLAC_FALSE; + } + + *pResult = (drflac_uint8)result; + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__read_int8(drflac_bs* bs, unsigned int bitCount, drflac_int8* pResult) +{ + drflac_int32 result; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 8); + + if (!drflac__read_int32(bs, bitCount, &result)) { + return DRFLAC_FALSE; + } + + *pResult = (drflac_int8)result; + return DRFLAC_TRUE; +} + + +static drflac_bool32 drflac__seek_bits(drflac_bs* bs, size_t bitsToSeek) +{ + if (bitsToSeek <= DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + bs->consumedBits += (drflac_uint32)bitsToSeek; + bs->cache <<= bitsToSeek; + return DRFLAC_TRUE; + } else { + /* It straddles the cached data. This function isn't called too frequently so I'm favouring simplicity here. */ + bitsToSeek -= DRFLAC_CACHE_L1_BITS_REMAINING(bs); + bs->consumedBits += DRFLAC_CACHE_L1_BITS_REMAINING(bs); + bs->cache = 0; + + /* Simple case. Seek in groups of the same number as bits that fit within a cache line. */ +#ifdef DRFLAC_64BIT + while (bitsToSeek >= DRFLAC_CACHE_L1_SIZE_BITS(bs)) { + drflac_uint64 bin; + if (!drflac__read_uint64(bs, DRFLAC_CACHE_L1_SIZE_BITS(bs), &bin)) { + return DRFLAC_FALSE; + } + bitsToSeek -= DRFLAC_CACHE_L1_SIZE_BITS(bs); + } +#else + while (bitsToSeek >= DRFLAC_CACHE_L1_SIZE_BITS(bs)) { + drflac_uint32 bin; + if (!drflac__read_uint32(bs, DRFLAC_CACHE_L1_SIZE_BITS(bs), &bin)) { + return DRFLAC_FALSE; + } + bitsToSeek -= DRFLAC_CACHE_L1_SIZE_BITS(bs); + } +#endif + + /* Whole leftover bytes. */ + while (bitsToSeek >= 8) { + drflac_uint8 bin; + if (!drflac__read_uint8(bs, 8, &bin)) { + return DRFLAC_FALSE; + } + bitsToSeek -= 8; + } + + /* Leftover bits. */ + if (bitsToSeek > 0) { + drflac_uint8 bin; + if (!drflac__read_uint8(bs, (drflac_uint32)bitsToSeek, &bin)) { + return DRFLAC_FALSE; + } + bitsToSeek = 0; /* <-- Necessary for the assert below. */ + } + + DRFLAC_ASSERT(bitsToSeek == 0); + return DRFLAC_TRUE; + } +} + + +/* This function moves the bit streamer to the first bit after the sync code (bit 15 of the of the frame header). It will also update the CRC-16. */ +static drflac_bool32 drflac__find_and_seek_to_next_sync_code(drflac_bs* bs) +{ + DRFLAC_ASSERT(bs != NULL); + + /* + The sync code is always aligned to 8 bits. This is convenient for us because it means we can do byte-aligned movements. The first + thing to do is align to the next byte. + */ + if (!drflac__seek_bits(bs, DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7)) { + return DRFLAC_FALSE; + } + + for (;;) { + drflac_uint8 hi; + +#ifndef DR_FLAC_NO_CRC + drflac__reset_crc16(bs); +#endif + + if (!drflac__read_uint8(bs, 8, &hi)) { + return DRFLAC_FALSE; + } + + if (hi == 0xFF) { + drflac_uint8 lo; + if (!drflac__read_uint8(bs, 6, &lo)) { + return DRFLAC_FALSE; + } + + if (lo == 0x3E) { + return DRFLAC_TRUE; + } else { + if (!drflac__seek_bits(bs, DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7)) { + return DRFLAC_FALSE; + } + } + } + } + + /* Should never get here. */ + /*return DRFLAC_FALSE;*/ +} + + +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) +#define DRFLAC_IMPLEMENT_CLZ_LZCNT +#endif +#if defined(_MSC_VER) && _MSC_VER >= 1400 && (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(__clang__) +#define DRFLAC_IMPLEMENT_CLZ_MSVC +#endif +#if defined(__WATCOMC__) && defined(__386__) +#define DRFLAC_IMPLEMENT_CLZ_WATCOM +#endif +#ifdef __MRC__ +#include +#define DRFLAC_IMPLEMENT_CLZ_MRC +#endif + +static DRFLAC_INLINE drflac_uint32 drflac__clz_software(drflac_cache_t x) +{ + drflac_uint32 n; + static drflac_uint32 clz_table_4[] = { + 0, + 4, + 3, 3, + 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1 + }; + + if (x == 0) { + return sizeof(x)*8; + } + + n = clz_table_4[x >> (sizeof(x)*8 - 4)]; + if (n == 0) { +#ifdef DRFLAC_64BIT + if ((x & ((drflac_uint64)0xFFFFFFFF << 32)) == 0) { n = 32; x <<= 32; } + if ((x & ((drflac_uint64)0xFFFF0000 << 32)) == 0) { n += 16; x <<= 16; } + if ((x & ((drflac_uint64)0xFF000000 << 32)) == 0) { n += 8; x <<= 8; } + if ((x & ((drflac_uint64)0xF0000000 << 32)) == 0) { n += 4; x <<= 4; } +#else + if ((x & 0xFFFF0000) == 0) { n = 16; x <<= 16; } + if ((x & 0xFF000000) == 0) { n += 8; x <<= 8; } + if ((x & 0xF0000000) == 0) { n += 4; x <<= 4; } +#endif + n += clz_table_4[x >> (sizeof(x)*8 - 4)]; + } + + return n - 1; +} + +#ifdef DRFLAC_IMPLEMENT_CLZ_LZCNT +static DRFLAC_INLINE drflac_bool32 drflac__is_lzcnt_supported(void) +{ + /* Fast compile time check for ARM. */ +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) && defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) + return DRFLAC_TRUE; +#elif defined(__MRC__) + return DRFLAC_TRUE; +#else + /* If the compiler itself does not support the intrinsic then we'll need to return false. */ + #ifdef DRFLAC_HAS_LZCNT_INTRINSIC + return drflac__gIsLZCNTSupported; + #else + return DRFLAC_FALSE; + #endif +#endif +} + +static DRFLAC_INLINE drflac_uint32 drflac__clz_lzcnt(drflac_cache_t x) +{ + /* + It's critical for competitive decoding performance that this function be highly optimal. With MSVC we can use the __lzcnt64() and __lzcnt() intrinsics + to achieve good performance, however on GCC and Clang it's a little bit more annoying. The __builtin_clzl() and __builtin_clzll() intrinsics leave + it undefined as to the return value when `x` is 0. We need this to be well defined as returning 32 or 64, depending on whether or not it's a 32- or + 64-bit build. To work around this we would need to add a conditional to check for the x = 0 case, but this creates unnecessary inefficiency. To work + around this problem I have written some inline assembly to emit the LZCNT (x86) or CLZ (ARM) instruction directly which removes the need to include + the conditional. This has worked well in the past, but for some reason Clang's MSVC compatible driver, clang-cl, does not seem to be handling this + in the same way as the normal Clang driver. It seems that `clang-cl` is just outputting the wrong results sometimes, maybe due to some register + getting clobbered? + + I'm not sure if this is a bug with dr_flac's inlined assembly (most likely), a bug in `clang-cl` or just a misunderstanding on my part with inline + assembly rules for `clang-cl`. If somebody can identify an error in dr_flac's inlined assembly I'm happy to get that fixed. + + Fortunately there is an easy workaround for this. Clang implements MSVC-specific intrinsics for compatibility. It also defines _MSC_VER for extra + compatibility. We can therefore just check for _MSC_VER and use the MSVC intrinsic which, fortunately for us, Clang supports. It would still be nice + to know how to fix the inlined assembly for correctness sake, however. + */ + +#if defined(_MSC_VER) /*&& !defined(__clang__)*/ /* <-- Intentionally wanting Clang to use the MSVC __lzcnt64/__lzcnt intrinsics due to above ^. */ + #ifdef DRFLAC_64BIT + return (drflac_uint32)__lzcnt64(x); + #else + return (drflac_uint32)__lzcnt(x); + #endif +#else + #if defined(__GNUC__) || defined(__clang__) + #if defined(DRFLAC_X64) + { + drflac_uint64 r; + __asm__ __volatile__ ( + "lzcnt{ %1, %0| %0, %1}" : "=r"(r) : "r"(x) : "cc" + ); + + return (drflac_uint32)r; + } + #elif defined(DRFLAC_X86) + { + drflac_uint32 r; + __asm__ __volatile__ ( + "lzcnt{l %1, %0| %0, %1}" : "=r"(r) : "r"(x) : "cc" + ); + + return r; + } + #elif defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) && !defined(__ARM_ARCH_6M__) && !defined(DRFLAC_64BIT) /* <-- I haven't tested 64-bit inline assembly, so only enabling this for the 32-bit build for now. */ + { + unsigned int r; + __asm__ __volatile__ ( + #if defined(DRFLAC_64BIT) + "clz %w[out], %w[in]" : [out]"=r"(r) : [in]"r"(x) /* <-- This is untested. If someone in the community could test this, that would be appreciated! */ + #else + "clz %[out], %[in]" : [out]"=r"(r) : [in]"r"(x) + #endif + ); + + return r; + } + #else + if (x == 0) { + return sizeof(x)*8; + } + #ifdef DRFLAC_64BIT + return (drflac_uint32)__builtin_clzll((drflac_uint64)x); + #else + return (drflac_uint32)__builtin_clzl((drflac_uint32)x); + #endif + #endif + #else + /* Unsupported compiler. */ + #error "This compiler does not support the lzcnt intrinsic." + #endif +#endif +} +#endif + +#ifdef DRFLAC_IMPLEMENT_CLZ_MSVC +#include /* For BitScanReverse(). */ + +static DRFLAC_INLINE drflac_uint32 drflac__clz_msvc(drflac_cache_t x) +{ + drflac_uint32 n; + + if (x == 0) { + return sizeof(x)*8; + } + +#ifdef DRFLAC_64BIT + _BitScanReverse64((unsigned long*)&n, x); +#else + _BitScanReverse((unsigned long*)&n, x); +#endif + return sizeof(x)*8 - n - 1; +} +#endif + +#ifdef DRFLAC_IMPLEMENT_CLZ_WATCOM +static __inline drflac_uint32 drflac__clz_watcom (drflac_uint32); +#ifdef DRFLAC_IMPLEMENT_CLZ_WATCOM_LZCNT +/* Use the LZCNT instruction (only available on some processors since the 2010s). */ +#pragma aux drflac__clz_watcom_lzcnt = \ + "db 0F3h, 0Fh, 0BDh, 0C0h" /* lzcnt eax, eax */ \ + parm [eax] \ + value [eax] \ + modify nomemory; +#else +/* Use the 386+-compatible implementation. */ +#pragma aux drflac__clz_watcom = \ + "bsr eax, eax" \ + "xor eax, 31" \ + parm [eax] nomemory \ + value [eax] \ + modify exact [eax] nomemory; +#endif +#endif + +static DRFLAC_INLINE drflac_uint32 drflac__clz(drflac_cache_t x) +{ +#ifdef DRFLAC_IMPLEMENT_CLZ_LZCNT + if (drflac__is_lzcnt_supported()) { + return drflac__clz_lzcnt(x); + } else +#endif + { +#ifdef DRFLAC_IMPLEMENT_CLZ_MSVC + return drflac__clz_msvc(x); +#elif defined(DRFLAC_IMPLEMENT_CLZ_WATCOM_LZCNT) + return drflac__clz_watcom_lzcnt(x); +#elif defined(DRFLAC_IMPLEMENT_CLZ_WATCOM) + return (x == 0) ? sizeof(x)*8 : drflac__clz_watcom(x); +#elif defined(__MRC__) + return __cntlzw(x); +#else + return drflac__clz_software(x); +#endif + } +} + + +static DRFLAC_INLINE drflac_bool32 drflac__seek_past_next_set_bit(drflac_bs* bs, unsigned int* pOffsetOut) +{ + drflac_uint32 zeroCounter = 0; + drflac_uint32 setBitOffsetPlus1; + + while (bs->cache == 0) { + zeroCounter += (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs); + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + } + + if (bs->cache == 1) { + /* Not catching this would lead to undefined behaviour: a shift of a 32-bit number by 32 or more is undefined */ + *pOffsetOut = zeroCounter + (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs) - 1; + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + return DRFLAC_TRUE; + } + + setBitOffsetPlus1 = drflac__clz(bs->cache); + setBitOffsetPlus1 += 1; + + if (setBitOffsetPlus1 > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; + } + + bs->consumedBits += setBitOffsetPlus1; + bs->cache <<= setBitOffsetPlus1; + + *pOffsetOut = zeroCounter + setBitOffsetPlus1 - 1; + return DRFLAC_TRUE; +} + + + +static drflac_bool32 drflac__seek_to_byte(drflac_bs* bs, drflac_uint64 offsetFromStart) +{ + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(offsetFromStart > 0); + + /* + Seeking from the start is not quite as trivial as it sounds because the onSeek callback takes a signed 32-bit integer (which + is intentional because it simplifies the implementation of the onSeek callbacks), however offsetFromStart is unsigned 64-bit. + To resolve we just need to do an initial seek from the start, and then a series of offset seeks to make up the remainder. + */ + if (offsetFromStart > 0x7FFFFFFF) { + drflac_uint64 bytesRemaining = offsetFromStart; + if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, drflac_seek_origin_start)) { + return DRFLAC_FALSE; + } + bytesRemaining -= 0x7FFFFFFF; + + while (bytesRemaining > 0x7FFFFFFF) { + if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + bytesRemaining -= 0x7FFFFFFF; + } + + if (bytesRemaining > 0) { + if (!bs->onSeek(bs->pUserData, (int)bytesRemaining, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + } + } else { + if (!bs->onSeek(bs->pUserData, (int)offsetFromStart, drflac_seek_origin_start)) { + return DRFLAC_FALSE; + } + } + + /* The cache should be reset to force a reload of fresh data from the client. */ + drflac__reset_cache(bs); + return DRFLAC_TRUE; +} + + +static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64* pNumberOut, drflac_uint8* pCRCOut) +{ + drflac_uint8 crc; + drflac_uint64 result; + drflac_uint8 utf8[7] = {0}; + int byteCount; + int i; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pNumberOut != NULL); + DRFLAC_ASSERT(pCRCOut != NULL); + + crc = *pCRCOut; + + if (!drflac__read_uint8(bs, 8, utf8)) { + *pNumberOut = 0; + return DRFLAC_AT_END; + } + crc = drflac_crc8(crc, utf8[0], 8); + + if ((utf8[0] & 0x80) == 0) { + *pNumberOut = utf8[0]; + *pCRCOut = crc; + return DRFLAC_SUCCESS; + } + + /*byteCount = 1;*/ + if ((utf8[0] & 0xE0) == 0xC0) { + byteCount = 2; + } else if ((utf8[0] & 0xF0) == 0xE0) { + byteCount = 3; + } else if ((utf8[0] & 0xF8) == 0xF0) { + byteCount = 4; + } else if ((utf8[0] & 0xFC) == 0xF8) { + byteCount = 5; + } else if ((utf8[0] & 0xFE) == 0xFC) { + byteCount = 6; + } else if ((utf8[0] & 0xFF) == 0xFE) { + byteCount = 7; + } else { + *pNumberOut = 0; + return DRFLAC_CRC_MISMATCH; /* Bad UTF-8 encoding. */ + } + + /* Read extra bytes. */ + DRFLAC_ASSERT(byteCount > 1); + + result = (drflac_uint64)(utf8[0] & (0xFF >> (byteCount + 1))); + for (i = 1; i < byteCount; ++i) { + if (!drflac__read_uint8(bs, 8, utf8 + i)) { + *pNumberOut = 0; + return DRFLAC_AT_END; + } + crc = drflac_crc8(crc, utf8[i], 8); + + result = (result << 6) | (utf8[i] & 0x3F); + } + + *pNumberOut = result; + *pCRCOut = crc; + return DRFLAC_SUCCESS; +} + + +static DRFLAC_INLINE drflac_uint32 drflac__ilog2_u32(drflac_uint32 x) +{ +#if 1 /* Needs optimizing. */ + drflac_uint32 result = 0; + while (x > 0) { + result += 1; + x >>= 1; + } + + return result; +#endif +} + +static DRFLAC_INLINE drflac_bool32 drflac__use_64_bit_prediction(drflac_uint32 bitsPerSample, drflac_uint32 order, drflac_uint32 precision) +{ + /* https://web.archive.org/web/20220205005724/https://github.com/ietf-wg-cellar/flac-specification/blob/37a49aa48ba4ba12e8757badfc59c0df35435fec/rfc_backmatter.md */ + return bitsPerSample + precision + drflac__ilog2_u32(order) > 32; +} + + +/* +The next two functions are responsible for calculating the prediction. + +When the bits per sample is >16 we need to use 64-bit integer arithmetic because otherwise we'll run out of precision. It's +safe to assume this will be slower on 32-bit platforms so we use a more optimal solution when the bits per sample is <=16. +*/ +#if defined(__clang__) +__attribute__((no_sanitize("signed-integer-overflow"))) +#endif +static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_32(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) +{ + drflac_int32 prediction = 0; + + DRFLAC_ASSERT(order <= 32); + + /* 32-bit version. */ + + /* VC++ optimizes this to a single jmp. I've not yet verified this for other compilers. */ + switch (order) + { + case 32: prediction += coefficients[31] * pDecodedSamples[-32]; + case 31: prediction += coefficients[30] * pDecodedSamples[-31]; + case 30: prediction += coefficients[29] * pDecodedSamples[-30]; + case 29: prediction += coefficients[28] * pDecodedSamples[-29]; + case 28: prediction += coefficients[27] * pDecodedSamples[-28]; + case 27: prediction += coefficients[26] * pDecodedSamples[-27]; + case 26: prediction += coefficients[25] * pDecodedSamples[-26]; + case 25: prediction += coefficients[24] * pDecodedSamples[-25]; + case 24: prediction += coefficients[23] * pDecodedSamples[-24]; + case 23: prediction += coefficients[22] * pDecodedSamples[-23]; + case 22: prediction += coefficients[21] * pDecodedSamples[-22]; + case 21: prediction += coefficients[20] * pDecodedSamples[-21]; + case 20: prediction += coefficients[19] * pDecodedSamples[-20]; + case 19: prediction += coefficients[18] * pDecodedSamples[-19]; + case 18: prediction += coefficients[17] * pDecodedSamples[-18]; + case 17: prediction += coefficients[16] * pDecodedSamples[-17]; + case 16: prediction += coefficients[15] * pDecodedSamples[-16]; + case 15: prediction += coefficients[14] * pDecodedSamples[-15]; + case 14: prediction += coefficients[13] * pDecodedSamples[-14]; + case 13: prediction += coefficients[12] * pDecodedSamples[-13]; + case 12: prediction += coefficients[11] * pDecodedSamples[-12]; + case 11: prediction += coefficients[10] * pDecodedSamples[-11]; + case 10: prediction += coefficients[ 9] * pDecodedSamples[-10]; + case 9: prediction += coefficients[ 8] * pDecodedSamples[- 9]; + case 8: prediction += coefficients[ 7] * pDecodedSamples[- 8]; + case 7: prediction += coefficients[ 6] * pDecodedSamples[- 7]; + case 6: prediction += coefficients[ 5] * pDecodedSamples[- 6]; + case 5: prediction += coefficients[ 4] * pDecodedSamples[- 5]; + case 4: prediction += coefficients[ 3] * pDecodedSamples[- 4]; + case 3: prediction += coefficients[ 2] * pDecodedSamples[- 3]; + case 2: prediction += coefficients[ 1] * pDecodedSamples[- 2]; + case 1: prediction += coefficients[ 0] * pDecodedSamples[- 1]; + } + + return (drflac_int32)(prediction >> shift); +} + +static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) +{ + drflac_int64 prediction; + + DRFLAC_ASSERT(order <= 32); + + /* 64-bit version. */ + + /* This method is faster on the 32-bit build when compiling with VC++. See note below. */ +#ifndef DRFLAC_64BIT + if (order == 8) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; + } + else if (order == 7) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + } + else if (order == 3) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + } + else if (order == 6) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + } + else if (order == 5) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + } + else if (order == 4) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + } + else if (order == 12) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; + prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; + prediction += coefficients[9] * (drflac_int64)pDecodedSamples[-10]; + prediction += coefficients[10] * (drflac_int64)pDecodedSamples[-11]; + prediction += coefficients[11] * (drflac_int64)pDecodedSamples[-12]; + } + else if (order == 2) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + } + else if (order == 1) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + } + else if (order == 10) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; + prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; + prediction += coefficients[9] * (drflac_int64)pDecodedSamples[-10]; + } + else if (order == 9) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; + prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; + } + else if (order == 11) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; + prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; + prediction += coefficients[9] * (drflac_int64)pDecodedSamples[-10]; + prediction += coefficients[10] * (drflac_int64)pDecodedSamples[-11]; + } + else + { + int j; + + prediction = 0; + for (j = 0; j < (int)order; ++j) { + prediction += coefficients[j] * (drflac_int64)pDecodedSamples[-j-1]; + } + } +#endif + + /* + VC++ optimizes this to a single jmp instruction, but only the 64-bit build. The 32-bit build generates less efficient code for some + reason. The ugly version above is faster so we'll just switch between the two depending on the target platform. + */ +#ifdef DRFLAC_64BIT + prediction = 0; + switch (order) + { + case 32: prediction += coefficients[31] * (drflac_int64)pDecodedSamples[-32]; + case 31: prediction += coefficients[30] * (drflac_int64)pDecodedSamples[-31]; + case 30: prediction += coefficients[29] * (drflac_int64)pDecodedSamples[-30]; + case 29: prediction += coefficients[28] * (drflac_int64)pDecodedSamples[-29]; + case 28: prediction += coefficients[27] * (drflac_int64)pDecodedSamples[-28]; + case 27: prediction += coefficients[26] * (drflac_int64)pDecodedSamples[-27]; + case 26: prediction += coefficients[25] * (drflac_int64)pDecodedSamples[-26]; + case 25: prediction += coefficients[24] * (drflac_int64)pDecodedSamples[-25]; + case 24: prediction += coefficients[23] * (drflac_int64)pDecodedSamples[-24]; + case 23: prediction += coefficients[22] * (drflac_int64)pDecodedSamples[-23]; + case 22: prediction += coefficients[21] * (drflac_int64)pDecodedSamples[-22]; + case 21: prediction += coefficients[20] * (drflac_int64)pDecodedSamples[-21]; + case 20: prediction += coefficients[19] * (drflac_int64)pDecodedSamples[-20]; + case 19: prediction += coefficients[18] * (drflac_int64)pDecodedSamples[-19]; + case 18: prediction += coefficients[17] * (drflac_int64)pDecodedSamples[-18]; + case 17: prediction += coefficients[16] * (drflac_int64)pDecodedSamples[-17]; + case 16: prediction += coefficients[15] * (drflac_int64)pDecodedSamples[-16]; + case 15: prediction += coefficients[14] * (drflac_int64)pDecodedSamples[-15]; + case 14: prediction += coefficients[13] * (drflac_int64)pDecodedSamples[-14]; + case 13: prediction += coefficients[12] * (drflac_int64)pDecodedSamples[-13]; + case 12: prediction += coefficients[11] * (drflac_int64)pDecodedSamples[-12]; + case 11: prediction += coefficients[10] * (drflac_int64)pDecodedSamples[-11]; + case 10: prediction += coefficients[ 9] * (drflac_int64)pDecodedSamples[-10]; + case 9: prediction += coefficients[ 8] * (drflac_int64)pDecodedSamples[- 9]; + case 8: prediction += coefficients[ 7] * (drflac_int64)pDecodedSamples[- 8]; + case 7: prediction += coefficients[ 6] * (drflac_int64)pDecodedSamples[- 7]; + case 6: prediction += coefficients[ 5] * (drflac_int64)pDecodedSamples[- 6]; + case 5: prediction += coefficients[ 4] * (drflac_int64)pDecodedSamples[- 5]; + case 4: prediction += coefficients[ 3] * (drflac_int64)pDecodedSamples[- 4]; + case 3: prediction += coefficients[ 2] * (drflac_int64)pDecodedSamples[- 3]; + case 2: prediction += coefficients[ 1] * (drflac_int64)pDecodedSamples[- 2]; + case 1: prediction += coefficients[ 0] * (drflac_int64)pDecodedSamples[- 1]; + } +#endif + + return (drflac_int32)(prediction >> shift); +} + + +#if 0 +/* +Reference implementation for reading and decoding samples with residual. This is intentionally left unoptimized for the +sake of readability and should only be used as a reference. +*/ +static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + drflac_uint32 i; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); + + for (i = 0; i < count; ++i) { + drflac_uint32 zeroCounter = 0; + for (;;) { + drflac_uint8 bit; + if (!drflac__read_uint8(bs, 1, &bit)) { + return DRFLAC_FALSE; + } + + if (bit == 0) { + zeroCounter += 1; + } else { + break; + } + } + + drflac_uint32 decodedRice; + if (riceParam > 0) { + if (!drflac__read_uint32(bs, riceParam, &decodedRice)) { + return DRFLAC_FALSE; + } + } else { + decodedRice = 0; + } + + decodedRice |= (zeroCounter << riceParam); + if ((decodedRice & 0x01)) { + decodedRice = ~(decodedRice >> 1); + } else { + decodedRice = (decodedRice >> 1); + } + + + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i); + } else { + pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i); + } + } + + return DRFLAC_TRUE; +} +#endif + +#if 0 +static drflac_bool32 drflac__read_rice_parts__reference(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) +{ + drflac_uint32 zeroCounter = 0; + drflac_uint32 decodedRice; + + for (;;) { + drflac_uint8 bit; + if (!drflac__read_uint8(bs, 1, &bit)) { + return DRFLAC_FALSE; + } + + if (bit == 0) { + zeroCounter += 1; + } else { + break; + } + } + + if (riceParam > 0) { + if (!drflac__read_uint32(bs, riceParam, &decodedRice)) { + return DRFLAC_FALSE; + } + } else { + decodedRice = 0; + } + + *pZeroCounterOut = zeroCounter; + *pRiceParamPartOut = decodedRice; + return DRFLAC_TRUE; +} +#endif + +#if 0 +static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) +{ + drflac_cache_t riceParamMask; + drflac_uint32 zeroCounter; + drflac_uint32 setBitOffsetPlus1; + drflac_uint32 riceParamPart; + drflac_uint32 riceLength; + + DRFLAC_ASSERT(riceParam > 0); /* <-- riceParam should never be 0. drflac__read_rice_parts__param_equals_zero() should be used instead for this case. */ + + riceParamMask = DRFLAC_CACHE_L1_SELECTION_MASK(riceParam); + + zeroCounter = 0; + while (bs->cache == 0) { + zeroCounter += (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs); + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + } + + setBitOffsetPlus1 = drflac__clz(bs->cache); + zeroCounter += setBitOffsetPlus1; + setBitOffsetPlus1 += 1; + + riceLength = setBitOffsetPlus1 + riceParam; + if (riceLength < DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + riceParamPart = (drflac_uint32)((bs->cache & (riceParamMask >> setBitOffsetPlus1)) >> DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceLength)); + + bs->consumedBits += riceLength; + bs->cache <<= riceLength; + } else { + drflac_uint32 bitCountLo; + drflac_cache_t resultHi; + + bs->consumedBits += riceLength; + bs->cache <<= setBitOffsetPlus1 & (DRFLAC_CACHE_L1_SIZE_BITS(bs)-1); /* <-- Equivalent to "if (setBitOffsetPlus1 < DRFLAC_CACHE_L1_SIZE_BITS(bs)) { bs->cache <<= setBitOffsetPlus1; }" */ + + /* It straddles the cached data. It will never cover more than the next chunk. We just read the number in two parts and combine them. */ + bitCountLo = bs->consumedBits - DRFLAC_CACHE_L1_SIZE_BITS(bs); + resultHi = DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, riceParam); /* <-- Use DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE() if ever this function allows riceParam=0. */ + + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { +#ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); +#endif + bs->cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs->consumedBits = 0; +#ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs->cache; +#endif + } else { + /* Slow path. We need to fetch more data from the client. */ + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; + } + } + + riceParamPart = (drflac_uint32)(resultHi | DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, bitCountLo)); + + bs->consumedBits += bitCountLo; + bs->cache <<= bitCountLo; + } + + pZeroCounterOut[0] = zeroCounter; + pRiceParamPartOut[0] = riceParamPart; + + return DRFLAC_TRUE; +} +#endif + +static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts_x1(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) +{ + drflac_uint32 riceParamPlus1 = riceParam + 1; + /*drflac_cache_t riceParamPlus1Mask = DRFLAC_CACHE_L1_SELECTION_MASK(riceParamPlus1);*/ + drflac_uint32 riceParamPlus1Shift = DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceParamPlus1); + drflac_uint32 riceParamPlus1MaxConsumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs) - riceParamPlus1; + + /* + The idea here is to use local variables for the cache in an attempt to encourage the compiler to store them in registers. I have + no idea how this will work in practice... + */ + drflac_cache_t bs_cache = bs->cache; + drflac_uint32 bs_consumedBits = bs->consumedBits; + + /* The first thing to do is find the first unset bit. Most likely a bit will be set in the current cache line. */ + drflac_uint32 lzcount = drflac__clz(bs_cache); + if (lzcount < sizeof(bs_cache)*8) { + pZeroCounterOut[0] = lzcount; + + /* + It is most likely that the riceParam part (which comes after the zero counter) is also on this cache line. When extracting + this, we include the set bit from the unary coded part because it simplifies cache management. This bit will be handled + outside of this function at a higher level. + */ + extract_rice_param_part: + bs_cache <<= lzcount; + bs_consumedBits += lzcount; + + if (bs_consumedBits <= riceParamPlus1MaxConsumedBits) { + /* Getting here means the rice parameter part is wholly contained within the current cache line. */ + pRiceParamPartOut[0] = (drflac_uint32)(bs_cache >> riceParamPlus1Shift); + bs_cache <<= riceParamPlus1; + bs_consumedBits += riceParamPlus1; + } else { + drflac_uint32 riceParamPartHi; + drflac_uint32 riceParamPartLo; + drflac_uint32 riceParamPartLoBitCount; + + /* + Getting here means the rice parameter part straddles the cache line. We need to read from the tail of the current cache + line, reload the cache, and then combine it with the head of the next cache line. + */ + + /* Grab the high part of the rice parameter part. */ + riceParamPartHi = (drflac_uint32)(bs_cache >> riceParamPlus1Shift); + + /* Before reloading the cache we need to grab the size in bits of the low part. */ + riceParamPartLoBitCount = bs_consumedBits - riceParamPlus1MaxConsumedBits; + DRFLAC_ASSERT(riceParamPartLoBitCount > 0 && riceParamPartLoBitCount < 32); + + /* Now reload the cache. */ + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + #ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); + #endif + bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs_consumedBits = riceParamPartLoBitCount; + #ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs_cache; + #endif + } else { + /* Slow path. We need to fetch more data from the client. */ + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; + } + + bs_cache = bs->cache; + bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; + } + + /* We should now have enough information to construct the rice parameter part. */ + riceParamPartLo = (drflac_uint32)(bs_cache >> (DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceParamPartLoBitCount))); + pRiceParamPartOut[0] = riceParamPartHi | riceParamPartLo; + + bs_cache <<= riceParamPartLoBitCount; + } + } else { + /* + Getting here means there are no bits set on the cache line. This is a less optimal case because we just wasted a call + to drflac__clz() and we need to reload the cache. + */ + drflac_uint32 zeroCounter = (drflac_uint32)(DRFLAC_CACHE_L1_SIZE_BITS(bs) - bs_consumedBits); + for (;;) { + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + #ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); + #endif + bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs_consumedBits = 0; + #ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs_cache; + #endif + } else { + /* Slow path. We need to fetch more data from the client. */ + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + bs_cache = bs->cache; + bs_consumedBits = bs->consumedBits; + } + + lzcount = drflac__clz(bs_cache); + zeroCounter += lzcount; + + if (lzcount < sizeof(bs_cache)*8) { + break; + } + } + + pZeroCounterOut[0] = zeroCounter; + goto extract_rice_param_part; + } + + /* Make sure the cache is restored at the end of it all. */ + bs->cache = bs_cache; + bs->consumedBits = bs_consumedBits; + + return DRFLAC_TRUE; +} + +static DRFLAC_INLINE drflac_bool32 drflac__seek_rice_parts(drflac_bs* bs, drflac_uint8 riceParam) +{ + drflac_uint32 riceParamPlus1 = riceParam + 1; + drflac_uint32 riceParamPlus1MaxConsumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs) - riceParamPlus1; + + /* + The idea here is to use local variables for the cache in an attempt to encourage the compiler to store them in registers. I have + no idea how this will work in practice... + */ + drflac_cache_t bs_cache = bs->cache; + drflac_uint32 bs_consumedBits = bs->consumedBits; + + /* The first thing to do is find the first unset bit. Most likely a bit will be set in the current cache line. */ + drflac_uint32 lzcount = drflac__clz(bs_cache); + if (lzcount < sizeof(bs_cache)*8) { + /* + It is most likely that the riceParam part (which comes after the zero counter) is also on this cache line. When extracting + this, we include the set bit from the unary coded part because it simplifies cache management. This bit will be handled + outside of this function at a higher level. + */ + extract_rice_param_part: + bs_cache <<= lzcount; + bs_consumedBits += lzcount; + + if (bs_consumedBits <= riceParamPlus1MaxConsumedBits) { + /* Getting here means the rice parameter part is wholly contained within the current cache line. */ + bs_cache <<= riceParamPlus1; + bs_consumedBits += riceParamPlus1; + } else { + /* + Getting here means the rice parameter part straddles the cache line. We need to read from the tail of the current cache + line, reload the cache, and then combine it with the head of the next cache line. + */ + + /* Before reloading the cache we need to grab the size in bits of the low part. */ + drflac_uint32 riceParamPartLoBitCount = bs_consumedBits - riceParamPlus1MaxConsumedBits; + DRFLAC_ASSERT(riceParamPartLoBitCount > 0 && riceParamPartLoBitCount < 32); + + /* Now reload the cache. */ + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + #ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); + #endif + bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs_consumedBits = riceParamPartLoBitCount; + #ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs_cache; + #endif + } else { + /* Slow path. We need to fetch more data from the client. */ + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + /* This happens when we get to end of stream */ + return DRFLAC_FALSE; + } + + bs_cache = bs->cache; + bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; + } + + bs_cache <<= riceParamPartLoBitCount; + } + } else { + /* + Getting here means there are no bits set on the cache line. This is a less optimal case because we just wasted a call + to drflac__clz() and we need to reload the cache. + */ + for (;;) { + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + #ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); + #endif + bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs_consumedBits = 0; + #ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs_cache; + #endif + } else { + /* Slow path. We need to fetch more data from the client. */ + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + bs_cache = bs->cache; + bs_consumedBits = bs->consumedBits; + } + + lzcount = drflac__clz(bs_cache); + if (lzcount < sizeof(bs_cache)*8) { + break; + } + } + + goto extract_rice_param_part; + } + + /* Make sure the cache is restored at the end of it all. */ + bs->cache = bs_cache; + bs->consumedBits = bs_consumedBits; + + return DRFLAC_TRUE; +} + + +static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar_zeroorder(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + drflac_uint32 zeroCountPart0; + drflac_uint32 riceParamPart0; + drflac_uint32 riceParamMask; + drflac_uint32 i; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); + + (void)bitsPerSample; + (void)order; + (void)shift; + (void)coefficients; + + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + + i = 0; + while (i < count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0)) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamPart0 &= riceParamMask; + riceParamPart0 |= (zeroCountPart0 << riceParam); + riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; + + pSamplesOut[i] = riceParamPart0; + + i += 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + drflac_uint32 zeroCountPart0 = 0; + drflac_uint32 zeroCountPart1 = 0; + drflac_uint32 zeroCountPart2 = 0; + drflac_uint32 zeroCountPart3 = 0; + drflac_uint32 riceParamPart0 = 0; + drflac_uint32 riceParamPart1 = 0; + drflac_uint32 riceParamPart2 = 0; + drflac_uint32 riceParamPart3 = 0; + drflac_uint32 riceParamMask; + const drflac_int32* pSamplesOutEnd; + drflac_uint32 i; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); + + if (lpcOrder == 0) { + return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); + } + + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + pSamplesOutEnd = pSamplesOut + (count & ~3); + + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + while (pSamplesOut < pSamplesOutEnd) { + /* + Rice extraction. It's faster to do this one at a time against local variables than it is to use the x4 version + against an array. Not sure why, but perhaps it's making more efficient use of registers? + */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart1, &riceParamPart1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart2, &riceParamPart2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart3, &riceParamPart3)) { + return DRFLAC_FALSE; + } + + riceParamPart0 &= riceParamMask; + riceParamPart1 &= riceParamMask; + riceParamPart2 &= riceParamMask; + riceParamPart3 &= riceParamMask; + + riceParamPart0 |= (zeroCountPart0 << riceParam); + riceParamPart1 |= (zeroCountPart1 << riceParam); + riceParamPart2 |= (zeroCountPart2 << riceParam); + riceParamPart3 |= (zeroCountPart3 << riceParam); + + riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; + riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; + riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; + riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; + + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); + pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 1); + pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 2); + pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 3); + + pSamplesOut += 4; + } + } else { + while (pSamplesOut < pSamplesOutEnd) { + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart1, &riceParamPart1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart2, &riceParamPart2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart3, &riceParamPart3)) { + return DRFLAC_FALSE; + } + + riceParamPart0 &= riceParamMask; + riceParamPart1 &= riceParamMask; + riceParamPart2 &= riceParamMask; + riceParamPart3 &= riceParamMask; + + riceParamPart0 |= (zeroCountPart0 << riceParam); + riceParamPart1 |= (zeroCountPart1 << riceParam); + riceParamPart2 |= (zeroCountPart2 << riceParam); + riceParamPart3 |= (zeroCountPart3 << riceParam); + + riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; + riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; + riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; + riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; + + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); + pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 1); + pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 2); + pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 3); + + pSamplesOut += 4; + } + } + + i = (count & ~3); + while (i < count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0)) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamPart0 &= riceParamMask; + riceParamPart0 |= (zeroCountPart0 << riceParam); + riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; + /*riceParamPart0 = (riceParamPart0 >> 1) ^ (~(riceParamPart0 & 0x01) + 1);*/ + + /* Sample reconstruction. */ + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); + } else { + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); + } + + i += 1; + pSamplesOut += 1; + } + + return DRFLAC_TRUE; +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE __m128i drflac__mm_packs_interleaved_epi32(__m128i a, __m128i b) +{ + __m128i r; + + /* Pack. */ + r = _mm_packs_epi32(a, b); + + /* a3a2 a1a0 b3b2 b1b0 -> a3a2 b3b2 a1a0 b1b0 */ + r = _mm_shuffle_epi32(r, _MM_SHUFFLE(3, 1, 2, 0)); + + /* a3a2 b3b2 a1a0 b1b0 -> a3b3 a2b2 a1b1 a0b0 */ + r = _mm_shufflehi_epi16(r, _MM_SHUFFLE(3, 1, 2, 0)); + r = _mm_shufflelo_epi16(r, _MM_SHUFFLE(3, 1, 2, 0)); + + return r; +} +#endif + +#if defined(DRFLAC_SUPPORT_SSE41) +static DRFLAC_INLINE __m128i drflac__mm_not_si128(__m128i a) +{ + return _mm_xor_si128(a, _mm_cmpeq_epi32(_mm_setzero_si128(), _mm_setzero_si128())); +} + +static DRFLAC_INLINE __m128i drflac__mm_hadd_epi32(__m128i x) +{ + __m128i x64 = _mm_add_epi32(x, _mm_shuffle_epi32(x, _MM_SHUFFLE(1, 0, 3, 2))); + __m128i x32 = _mm_shufflelo_epi16(x64, _MM_SHUFFLE(1, 0, 3, 2)); + return _mm_add_epi32(x64, x32); +} + +static DRFLAC_INLINE __m128i drflac__mm_hadd_epi64(__m128i x) +{ + return _mm_add_epi64(x, _mm_shuffle_epi32(x, _MM_SHUFFLE(1, 0, 3, 2))); +} + +static DRFLAC_INLINE __m128i drflac__mm_srai_epi64(__m128i x, int count) +{ + /* + To simplify this we are assuming count < 32. This restriction allows us to work on a low side and a high side. The low side + is shifted with zero bits, whereas the right side is shifted with sign bits. + */ + __m128i lo = _mm_srli_epi64(x, count); + __m128i hi = _mm_srai_epi32(x, count); + + hi = _mm_and_si128(hi, _mm_set_epi32(0xFFFFFFFF, 0, 0xFFFFFFFF, 0)); /* The high part needs to have the low part cleared. */ + + return _mm_or_si128(lo, hi); +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41_32(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + int i; + drflac_uint32 riceParamMask; + drflac_int32* pDecodedSamples = pSamplesOut; + drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); + drflac_uint32 zeroCountParts0 = 0; + drflac_uint32 zeroCountParts1 = 0; + drflac_uint32 zeroCountParts2 = 0; + drflac_uint32 zeroCountParts3 = 0; + drflac_uint32 riceParamParts0 = 0; + drflac_uint32 riceParamParts1 = 0; + drflac_uint32 riceParamParts2 = 0; + drflac_uint32 riceParamParts3 = 0; + __m128i coefficients128_0; + __m128i coefficients128_4; + __m128i coefficients128_8; + __m128i samples128_0; + __m128i samples128_4; + __m128i samples128_8; + __m128i riceParamMask128; + + const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + riceParamMask128 = _mm_set1_epi32(riceParamMask); + + /* Pre-load. */ + coefficients128_0 = _mm_setzero_si128(); + coefficients128_4 = _mm_setzero_si128(); + coefficients128_8 = _mm_setzero_si128(); + + samples128_0 = _mm_setzero_si128(); + samples128_4 = _mm_setzero_si128(); + samples128_8 = _mm_setzero_si128(); + + /* + Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than + what's available in the input buffers. It would be convenient to use a fall-through switch to do this, but this results + in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted + so I think there's opportunity for this to be simplified. + */ +#if 1 + { + int runningOrder = order; + + /* 0 - 3. */ + if (runningOrder >= 4) { + coefficients128_0 = _mm_loadu_si128((const __m128i*)(coefficients + 0)); + samples128_0 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 4)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_0 = _mm_set_epi32(0, coefficients[2], coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], pSamplesOut[-3], 0); break; + case 2: coefficients128_0 = _mm_set_epi32(0, 0, coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], 0, 0); break; + case 1: coefficients128_0 = _mm_set_epi32(0, 0, 0, coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* 4 - 7 */ + if (runningOrder >= 4) { + coefficients128_4 = _mm_loadu_si128((const __m128i*)(coefficients + 4)); + samples128_4 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 8)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_4 = _mm_set_epi32(0, coefficients[6], coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], pSamplesOut[-7], 0); break; + case 2: coefficients128_4 = _mm_set_epi32(0, 0, coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], 0, 0); break; + case 1: coefficients128_4 = _mm_set_epi32(0, 0, 0, coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* 8 - 11 */ + if (runningOrder == 4) { + coefficients128_8 = _mm_loadu_si128((const __m128i*)(coefficients + 8)); + samples128_8 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 12)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_8 = _mm_set_epi32(0, coefficients[10], coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], pSamplesOut[-11], 0); break; + case 2: coefficients128_8 = _mm_set_epi32(0, 0, coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], 0, 0); break; + case 1: coefficients128_8 = _mm_set_epi32(0, 0, 0, coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ + coefficients128_0 = _mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(0, 1, 2, 3)); + coefficients128_4 = _mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(0, 1, 2, 3)); + coefficients128_8 = _mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(0, 1, 2, 3)); + } +#else + /* This causes strict-aliasing warnings with GCC. */ + switch (order) + { + case 12: ((drflac_int32*)&coefficients128_8)[0] = coefficients[11]; ((drflac_int32*)&samples128_8)[0] = pDecodedSamples[-12]; + case 11: ((drflac_int32*)&coefficients128_8)[1] = coefficients[10]; ((drflac_int32*)&samples128_8)[1] = pDecodedSamples[-11]; + case 10: ((drflac_int32*)&coefficients128_8)[2] = coefficients[ 9]; ((drflac_int32*)&samples128_8)[2] = pDecodedSamples[-10]; + case 9: ((drflac_int32*)&coefficients128_8)[3] = coefficients[ 8]; ((drflac_int32*)&samples128_8)[3] = pDecodedSamples[- 9]; + case 8: ((drflac_int32*)&coefficients128_4)[0] = coefficients[ 7]; ((drflac_int32*)&samples128_4)[0] = pDecodedSamples[- 8]; + case 7: ((drflac_int32*)&coefficients128_4)[1] = coefficients[ 6]; ((drflac_int32*)&samples128_4)[1] = pDecodedSamples[- 7]; + case 6: ((drflac_int32*)&coefficients128_4)[2] = coefficients[ 5]; ((drflac_int32*)&samples128_4)[2] = pDecodedSamples[- 6]; + case 5: ((drflac_int32*)&coefficients128_4)[3] = coefficients[ 4]; ((drflac_int32*)&samples128_4)[3] = pDecodedSamples[- 5]; + case 4: ((drflac_int32*)&coefficients128_0)[0] = coefficients[ 3]; ((drflac_int32*)&samples128_0)[0] = pDecodedSamples[- 4]; + case 3: ((drflac_int32*)&coefficients128_0)[1] = coefficients[ 2]; ((drflac_int32*)&samples128_0)[1] = pDecodedSamples[- 3]; + case 2: ((drflac_int32*)&coefficients128_0)[2] = coefficients[ 1]; ((drflac_int32*)&samples128_0)[2] = pDecodedSamples[- 2]; + case 1: ((drflac_int32*)&coefficients128_0)[3] = coefficients[ 0]; ((drflac_int32*)&samples128_0)[3] = pDecodedSamples[- 1]; + } +#endif + + /* For this version we are doing one sample at a time. */ + while (pDecodedSamples < pDecodedSamplesEnd) { + __m128i prediction128; + __m128i zeroCountPart128; + __m128i riceParamPart128; + + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts1, &riceParamParts1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts2, &riceParamParts2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts3, &riceParamParts3)) { + return DRFLAC_FALSE; + } + + zeroCountPart128 = _mm_set_epi32(zeroCountParts3, zeroCountParts2, zeroCountParts1, zeroCountParts0); + riceParamPart128 = _mm_set_epi32(riceParamParts3, riceParamParts2, riceParamParts1, riceParamParts0); + + riceParamPart128 = _mm_and_si128(riceParamPart128, riceParamMask128); + riceParamPart128 = _mm_or_si128(riceParamPart128, _mm_slli_epi32(zeroCountPart128, riceParam)); + riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_add_epi32(drflac__mm_not_si128(_mm_and_si128(riceParamPart128, _mm_set1_epi32(0x01))), _mm_set1_epi32(0x01))); /* <-- SSE2 compatible */ + /*riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_mullo_epi32(_mm_and_si128(riceParamPart128, _mm_set1_epi32(0x01)), _mm_set1_epi32(0xFFFFFFFF)));*/ /* <-- Only supported from SSE4.1 and is slower in my testing... */ + + if (order <= 4) { + for (i = 0; i < 4; i += 1) { + prediction128 = _mm_mullo_epi32(coefficients128_0, samples128_0); + + /* Horizontal add and shift. */ + prediction128 = drflac__mm_hadd_epi32(prediction128); + prediction128 = _mm_srai_epi32(prediction128, shift); + prediction128 = _mm_add_epi32(riceParamPart128, prediction128); + + samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); + riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); + } + } else if (order <= 8) { + for (i = 0; i < 4; i += 1) { + prediction128 = _mm_mullo_epi32(coefficients128_4, samples128_4); + prediction128 = _mm_add_epi32(prediction128, _mm_mullo_epi32(coefficients128_0, samples128_0)); + + /* Horizontal add and shift. */ + prediction128 = drflac__mm_hadd_epi32(prediction128); + prediction128 = _mm_srai_epi32(prediction128, shift); + prediction128 = _mm_add_epi32(riceParamPart128, prediction128); + + samples128_4 = _mm_alignr_epi8(samples128_0, samples128_4, 4); + samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); + riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); + } + } else { + for (i = 0; i < 4; i += 1) { + prediction128 = _mm_mullo_epi32(coefficients128_8, samples128_8); + prediction128 = _mm_add_epi32(prediction128, _mm_mullo_epi32(coefficients128_4, samples128_4)); + prediction128 = _mm_add_epi32(prediction128, _mm_mullo_epi32(coefficients128_0, samples128_0)); + + /* Horizontal add and shift. */ + prediction128 = drflac__mm_hadd_epi32(prediction128); + prediction128 = _mm_srai_epi32(prediction128, shift); + prediction128 = _mm_add_epi32(riceParamPart128, prediction128); + + samples128_8 = _mm_alignr_epi8(samples128_4, samples128_8, 4); + samples128_4 = _mm_alignr_epi8(samples128_0, samples128_4, 4); + samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); + riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); + } + } + + /* We store samples in groups of 4. */ + _mm_storeu_si128((__m128i*)pDecodedSamples, samples128_0); + pDecodedSamples += 4; + } + + /* Make sure we process the last few samples. */ + i = (count & ~3); + while (i < (int)count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0)) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamParts0 &= riceParamMask; + riceParamParts0 |= (zeroCountParts0 << riceParam); + riceParamParts0 = (riceParamParts0 >> 1) ^ t[riceParamParts0 & 0x01]; + + /* Sample reconstruction. */ + pDecodedSamples[0] = riceParamParts0 + drflac__calculate_prediction_32(order, shift, coefficients, pDecodedSamples); + + i += 1; + pDecodedSamples += 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41_64(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + int i; + drflac_uint32 riceParamMask; + drflac_int32* pDecodedSamples = pSamplesOut; + drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); + drflac_uint32 zeroCountParts0 = 0; + drflac_uint32 zeroCountParts1 = 0; + drflac_uint32 zeroCountParts2 = 0; + drflac_uint32 zeroCountParts3 = 0; + drflac_uint32 riceParamParts0 = 0; + drflac_uint32 riceParamParts1 = 0; + drflac_uint32 riceParamParts2 = 0; + drflac_uint32 riceParamParts3 = 0; + __m128i coefficients128_0; + __m128i coefficients128_4; + __m128i coefficients128_8; + __m128i samples128_0; + __m128i samples128_4; + __m128i samples128_8; + __m128i prediction128; + __m128i riceParamMask128; + + const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + + DRFLAC_ASSERT(order <= 12); + + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + riceParamMask128 = _mm_set1_epi32(riceParamMask); + + prediction128 = _mm_setzero_si128(); + + /* Pre-load. */ + coefficients128_0 = _mm_setzero_si128(); + coefficients128_4 = _mm_setzero_si128(); + coefficients128_8 = _mm_setzero_si128(); + + samples128_0 = _mm_setzero_si128(); + samples128_4 = _mm_setzero_si128(); + samples128_8 = _mm_setzero_si128(); + +#if 1 + { + int runningOrder = order; + + /* 0 - 3. */ + if (runningOrder >= 4) { + coefficients128_0 = _mm_loadu_si128((const __m128i*)(coefficients + 0)); + samples128_0 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 4)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_0 = _mm_set_epi32(0, coefficients[2], coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], pSamplesOut[-3], 0); break; + case 2: coefficients128_0 = _mm_set_epi32(0, 0, coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], 0, 0); break; + case 1: coefficients128_0 = _mm_set_epi32(0, 0, 0, coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* 4 - 7 */ + if (runningOrder >= 4) { + coefficients128_4 = _mm_loadu_si128((const __m128i*)(coefficients + 4)); + samples128_4 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 8)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_4 = _mm_set_epi32(0, coefficients[6], coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], pSamplesOut[-7], 0); break; + case 2: coefficients128_4 = _mm_set_epi32(0, 0, coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], 0, 0); break; + case 1: coefficients128_4 = _mm_set_epi32(0, 0, 0, coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* 8 - 11 */ + if (runningOrder == 4) { + coefficients128_8 = _mm_loadu_si128((const __m128i*)(coefficients + 8)); + samples128_8 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 12)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_8 = _mm_set_epi32(0, coefficients[10], coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], pSamplesOut[-11], 0); break; + case 2: coefficients128_8 = _mm_set_epi32(0, 0, coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], 0, 0); break; + case 1: coefficients128_8 = _mm_set_epi32(0, 0, 0, coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ + coefficients128_0 = _mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(0, 1, 2, 3)); + coefficients128_4 = _mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(0, 1, 2, 3)); + coefficients128_8 = _mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(0, 1, 2, 3)); + } +#else + switch (order) + { + case 12: ((drflac_int32*)&coefficients128_8)[0] = coefficients[11]; ((drflac_int32*)&samples128_8)[0] = pDecodedSamples[-12]; + case 11: ((drflac_int32*)&coefficients128_8)[1] = coefficients[10]; ((drflac_int32*)&samples128_8)[1] = pDecodedSamples[-11]; + case 10: ((drflac_int32*)&coefficients128_8)[2] = coefficients[ 9]; ((drflac_int32*)&samples128_8)[2] = pDecodedSamples[-10]; + case 9: ((drflac_int32*)&coefficients128_8)[3] = coefficients[ 8]; ((drflac_int32*)&samples128_8)[3] = pDecodedSamples[- 9]; + case 8: ((drflac_int32*)&coefficients128_4)[0] = coefficients[ 7]; ((drflac_int32*)&samples128_4)[0] = pDecodedSamples[- 8]; + case 7: ((drflac_int32*)&coefficients128_4)[1] = coefficients[ 6]; ((drflac_int32*)&samples128_4)[1] = pDecodedSamples[- 7]; + case 6: ((drflac_int32*)&coefficients128_4)[2] = coefficients[ 5]; ((drflac_int32*)&samples128_4)[2] = pDecodedSamples[- 6]; + case 5: ((drflac_int32*)&coefficients128_4)[3] = coefficients[ 4]; ((drflac_int32*)&samples128_4)[3] = pDecodedSamples[- 5]; + case 4: ((drflac_int32*)&coefficients128_0)[0] = coefficients[ 3]; ((drflac_int32*)&samples128_0)[0] = pDecodedSamples[- 4]; + case 3: ((drflac_int32*)&coefficients128_0)[1] = coefficients[ 2]; ((drflac_int32*)&samples128_0)[1] = pDecodedSamples[- 3]; + case 2: ((drflac_int32*)&coefficients128_0)[2] = coefficients[ 1]; ((drflac_int32*)&samples128_0)[2] = pDecodedSamples[- 2]; + case 1: ((drflac_int32*)&coefficients128_0)[3] = coefficients[ 0]; ((drflac_int32*)&samples128_0)[3] = pDecodedSamples[- 1]; + } +#endif + + /* For this version we are doing one sample at a time. */ + while (pDecodedSamples < pDecodedSamplesEnd) { + __m128i zeroCountPart128; + __m128i riceParamPart128; + + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts1, &riceParamParts1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts2, &riceParamParts2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts3, &riceParamParts3)) { + return DRFLAC_FALSE; + } + + zeroCountPart128 = _mm_set_epi32(zeroCountParts3, zeroCountParts2, zeroCountParts1, zeroCountParts0); + riceParamPart128 = _mm_set_epi32(riceParamParts3, riceParamParts2, riceParamParts1, riceParamParts0); + + riceParamPart128 = _mm_and_si128(riceParamPart128, riceParamMask128); + riceParamPart128 = _mm_or_si128(riceParamPart128, _mm_slli_epi32(zeroCountPart128, riceParam)); + riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_add_epi32(drflac__mm_not_si128(_mm_and_si128(riceParamPart128, _mm_set1_epi32(1))), _mm_set1_epi32(1))); + + for (i = 0; i < 4; i += 1) { + prediction128 = _mm_xor_si128(prediction128, prediction128); /* Reset to 0. */ + + switch (order) + { + case 12: + case 11: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(1, 1, 0, 0)), _mm_shuffle_epi32(samples128_8, _MM_SHUFFLE(1, 1, 0, 0)))); + case 10: + case 9: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(3, 3, 2, 2)), _mm_shuffle_epi32(samples128_8, _MM_SHUFFLE(3, 3, 2, 2)))); + case 8: + case 7: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(1, 1, 0, 0)), _mm_shuffle_epi32(samples128_4, _MM_SHUFFLE(1, 1, 0, 0)))); + case 6: + case 5: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(3, 3, 2, 2)), _mm_shuffle_epi32(samples128_4, _MM_SHUFFLE(3, 3, 2, 2)))); + case 4: + case 3: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(1, 1, 0, 0)), _mm_shuffle_epi32(samples128_0, _MM_SHUFFLE(1, 1, 0, 0)))); + case 2: + case 1: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(3, 3, 2, 2)), _mm_shuffle_epi32(samples128_0, _MM_SHUFFLE(3, 3, 2, 2)))); + } + + /* Horizontal add and shift. */ + prediction128 = drflac__mm_hadd_epi64(prediction128); + prediction128 = drflac__mm_srai_epi64(prediction128, shift); + prediction128 = _mm_add_epi32(riceParamPart128, prediction128); + + /* Our value should be sitting in prediction128[0]. We need to combine this with our SSE samples. */ + samples128_8 = _mm_alignr_epi8(samples128_4, samples128_8, 4); + samples128_4 = _mm_alignr_epi8(samples128_0, samples128_4, 4); + samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); + + /* Slide our rice parameter down so that the value in position 0 contains the next one to process. */ + riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); + } + + /* We store samples in groups of 4. */ + _mm_storeu_si128((__m128i*)pDecodedSamples, samples128_0); + pDecodedSamples += 4; + } + + /* Make sure we process the last few samples. */ + i = (count & ~3); + while (i < (int)count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0)) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamParts0 &= riceParamMask; + riceParamParts0 |= (zeroCountParts0 << riceParam); + riceParamParts0 = (riceParamParts0 >> 1) ^ t[riceParamParts0 & 0x01]; + + /* Sample reconstruction. */ + pDecodedSamples[0] = riceParamParts0 + drflac__calculate_prediction_64(order, shift, coefficients, pDecodedSamples); + + i += 1; + pDecodedSamples += 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); + + /* In my testing the order is rarely > 12, so in this case I'm going to simplify the SSE implementation by only handling order <= 12. */ + if (lpcOrder > 0 && lpcOrder <= 12) { + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + return drflac__decode_samples_with_residual__rice__sse41_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); + } else { + return drflac__decode_samples_with_residual__rice__sse41_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); + } + } else { + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac__vst2q_s32(drflac_int32* p, int32x4x2_t x) +{ + vst1q_s32(p+0, x.val[0]); + vst1q_s32(p+4, x.val[1]); +} + +static DRFLAC_INLINE void drflac__vst2q_u32(drflac_uint32* p, uint32x4x2_t x) +{ + vst1q_u32(p+0, x.val[0]); + vst1q_u32(p+4, x.val[1]); +} + +static DRFLAC_INLINE void drflac__vst2q_f32(float* p, float32x4x2_t x) +{ + vst1q_f32(p+0, x.val[0]); + vst1q_f32(p+4, x.val[1]); +} + +static DRFLAC_INLINE void drflac__vst2q_s16(drflac_int16* p, int16x4x2_t x) +{ + vst1q_s16(p, vcombine_s16(x.val[0], x.val[1])); +} + +static DRFLAC_INLINE void drflac__vst2q_u16(drflac_uint16* p, uint16x4x2_t x) +{ + vst1q_u16(p, vcombine_u16(x.val[0], x.val[1])); +} + +static DRFLAC_INLINE int32x4_t drflac__vdupq_n_s32x4(drflac_int32 x3, drflac_int32 x2, drflac_int32 x1, drflac_int32 x0) +{ + drflac_int32 x[4]; + x[3] = x3; + x[2] = x2; + x[1] = x1; + x[0] = x0; + return vld1q_s32(x); +} + +static DRFLAC_INLINE int32x4_t drflac__valignrq_s32_1(int32x4_t a, int32x4_t b) +{ + /* Equivalent to SSE's _mm_alignr_epi8(a, b, 4) */ + + /* Reference */ + /*return drflac__vdupq_n_s32x4( + vgetq_lane_s32(a, 0), + vgetq_lane_s32(b, 3), + vgetq_lane_s32(b, 2), + vgetq_lane_s32(b, 1) + );*/ + + return vextq_s32(b, a, 1); +} + +static DRFLAC_INLINE uint32x4_t drflac__valignrq_u32_1(uint32x4_t a, uint32x4_t b) +{ + /* Equivalent to SSE's _mm_alignr_epi8(a, b, 4) */ + + /* Reference */ + /*return drflac__vdupq_n_s32x4( + vgetq_lane_s32(a, 0), + vgetq_lane_s32(b, 3), + vgetq_lane_s32(b, 2), + vgetq_lane_s32(b, 1) + );*/ + + return vextq_u32(b, a, 1); +} + +static DRFLAC_INLINE int32x2_t drflac__vhaddq_s32(int32x4_t x) +{ + /* The sum must end up in position 0. */ + + /* Reference */ + /*return vdupq_n_s32( + vgetq_lane_s32(x, 3) + + vgetq_lane_s32(x, 2) + + vgetq_lane_s32(x, 1) + + vgetq_lane_s32(x, 0) + );*/ + + int32x2_t r = vadd_s32(vget_high_s32(x), vget_low_s32(x)); + return vpadd_s32(r, r); +} + +static DRFLAC_INLINE int64x1_t drflac__vhaddq_s64(int64x2_t x) +{ + return vadd_s64(vget_high_s64(x), vget_low_s64(x)); +} + +static DRFLAC_INLINE int32x4_t drflac__vrevq_s32(int32x4_t x) +{ + /* Reference */ + /*return drflac__vdupq_n_s32x4( + vgetq_lane_s32(x, 0), + vgetq_lane_s32(x, 1), + vgetq_lane_s32(x, 2), + vgetq_lane_s32(x, 3) + );*/ + + return vrev64q_s32(vcombine_s32(vget_high_s32(x), vget_low_s32(x))); +} + +static DRFLAC_INLINE int32x4_t drflac__vnotq_s32(int32x4_t x) +{ + return veorq_s32(x, vdupq_n_s32(0xFFFFFFFF)); +} + +static DRFLAC_INLINE uint32x4_t drflac__vnotq_u32(uint32x4_t x) +{ + return veorq_u32(x, vdupq_n_u32(0xFFFFFFFF)); +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_32(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + int i; + drflac_uint32 riceParamMask; + drflac_int32* pDecodedSamples = pSamplesOut; + drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); + drflac_uint32 zeroCountParts[4]; + drflac_uint32 riceParamParts[4]; + int32x4_t coefficients128_0; + int32x4_t coefficients128_4; + int32x4_t coefficients128_8; + int32x4_t samples128_0; + int32x4_t samples128_4; + int32x4_t samples128_8; + uint32x4_t riceParamMask128; + int32x4_t riceParam128; + int32x2_t shift64; + uint32x4_t one128; + + const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + riceParamMask128 = vdupq_n_u32(riceParamMask); + + riceParam128 = vdupq_n_s32(riceParam); + shift64 = vdup_n_s32(-shift); /* Negate the shift because we'll be doing a variable shift using vshlq_s32(). */ + one128 = vdupq_n_u32(1); + + /* + Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than + what's available in the input buffers. It would be conenient to use a fall-through switch to do this, but this results + in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted + so I think there's opportunity for this to be simplified. + */ + { + int runningOrder = order; + drflac_int32 tempC[4] = {0, 0, 0, 0}; + drflac_int32 tempS[4] = {0, 0, 0, 0}; + + /* 0 - 3. */ + if (runningOrder >= 4) { + coefficients128_0 = vld1q_s32(coefficients + 0); + samples128_0 = vld1q_s32(pSamplesOut - 4); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[2]; tempS[1] = pSamplesOut[-3]; /* fallthrough */ + case 2: tempC[1] = coefficients[1]; tempS[2] = pSamplesOut[-2]; /* fallthrough */ + case 1: tempC[0] = coefficients[0]; tempS[3] = pSamplesOut[-1]; /* fallthrough */ + } + + coefficients128_0 = vld1q_s32(tempC); + samples128_0 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* 4 - 7 */ + if (runningOrder >= 4) { + coefficients128_4 = vld1q_s32(coefficients + 4); + samples128_4 = vld1q_s32(pSamplesOut - 8); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[6]; tempS[1] = pSamplesOut[-7]; /* fallthrough */ + case 2: tempC[1] = coefficients[5]; tempS[2] = pSamplesOut[-6]; /* fallthrough */ + case 1: tempC[0] = coefficients[4]; tempS[3] = pSamplesOut[-5]; /* fallthrough */ + } + + coefficients128_4 = vld1q_s32(tempC); + samples128_4 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* 8 - 11 */ + if (runningOrder == 4) { + coefficients128_8 = vld1q_s32(coefficients + 8); + samples128_8 = vld1q_s32(pSamplesOut - 12); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[10]; tempS[1] = pSamplesOut[-11]; /* fallthrough */ + case 2: tempC[1] = coefficients[ 9]; tempS[2] = pSamplesOut[-10]; /* fallthrough */ + case 1: tempC[0] = coefficients[ 8]; tempS[3] = pSamplesOut[- 9]; /* fallthrough */ + } + + coefficients128_8 = vld1q_s32(tempC); + samples128_8 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ + coefficients128_0 = drflac__vrevq_s32(coefficients128_0); + coefficients128_4 = drflac__vrevq_s32(coefficients128_4); + coefficients128_8 = drflac__vrevq_s32(coefficients128_8); + } + + /* For this version we are doing one sample at a time. */ + while (pDecodedSamples < pDecodedSamplesEnd) { + int32x4_t prediction128; + int32x2_t prediction64; + uint32x4_t zeroCountPart128; + uint32x4_t riceParamPart128; + + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[1], &riceParamParts[1]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[2], &riceParamParts[2]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[3], &riceParamParts[3])) { + return DRFLAC_FALSE; + } + + zeroCountPart128 = vld1q_u32(zeroCountParts); + riceParamPart128 = vld1q_u32(riceParamParts); + + riceParamPart128 = vandq_u32(riceParamPart128, riceParamMask128); + riceParamPart128 = vorrq_u32(riceParamPart128, vshlq_u32(zeroCountPart128, riceParam128)); + riceParamPart128 = veorq_u32(vshrq_n_u32(riceParamPart128, 1), vaddq_u32(drflac__vnotq_u32(vandq_u32(riceParamPart128, one128)), one128)); + + if (order <= 4) { + for (i = 0; i < 4; i += 1) { + prediction128 = vmulq_s32(coefficients128_0, samples128_0); + + /* Horizontal add and shift. */ + prediction64 = drflac__vhaddq_s32(prediction128); + prediction64 = vshl_s32(prediction64, shift64); + prediction64 = vadd_s32(prediction64, vget_low_s32(vreinterpretq_s32_u32(riceParamPart128))); + + samples128_0 = drflac__valignrq_s32_1(vcombine_s32(prediction64, vdup_n_s32(0)), samples128_0); + riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); + } + } else if (order <= 8) { + for (i = 0; i < 4; i += 1) { + prediction128 = vmulq_s32(coefficients128_4, samples128_4); + prediction128 = vmlaq_s32(prediction128, coefficients128_0, samples128_0); + + /* Horizontal add and shift. */ + prediction64 = drflac__vhaddq_s32(prediction128); + prediction64 = vshl_s32(prediction64, shift64); + prediction64 = vadd_s32(prediction64, vget_low_s32(vreinterpretq_s32_u32(riceParamPart128))); + + samples128_4 = drflac__valignrq_s32_1(samples128_0, samples128_4); + samples128_0 = drflac__valignrq_s32_1(vcombine_s32(prediction64, vdup_n_s32(0)), samples128_0); + riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); + } + } else { + for (i = 0; i < 4; i += 1) { + prediction128 = vmulq_s32(coefficients128_8, samples128_8); + prediction128 = vmlaq_s32(prediction128, coefficients128_4, samples128_4); + prediction128 = vmlaq_s32(prediction128, coefficients128_0, samples128_0); + + /* Horizontal add and shift. */ + prediction64 = drflac__vhaddq_s32(prediction128); + prediction64 = vshl_s32(prediction64, shift64); + prediction64 = vadd_s32(prediction64, vget_low_s32(vreinterpretq_s32_u32(riceParamPart128))); + + samples128_8 = drflac__valignrq_s32_1(samples128_4, samples128_8); + samples128_4 = drflac__valignrq_s32_1(samples128_0, samples128_4); + samples128_0 = drflac__valignrq_s32_1(vcombine_s32(prediction64, vdup_n_s32(0)), samples128_0); + riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); + } + } + + /* We store samples in groups of 4. */ + vst1q_s32(pDecodedSamples, samples128_0); + pDecodedSamples += 4; + } + + /* Make sure we process the last few samples. */ + i = (count & ~3); + while (i < (int)count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0])) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamParts[0] &= riceParamMask; + riceParamParts[0] |= (zeroCountParts[0] << riceParam); + riceParamParts[0] = (riceParamParts[0] >> 1) ^ t[riceParamParts[0] & 0x01]; + + /* Sample reconstruction. */ + pDecodedSamples[0] = riceParamParts[0] + drflac__calculate_prediction_32(order, shift, coefficients, pDecodedSamples); + + i += 1; + pDecodedSamples += 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + int i; + drflac_uint32 riceParamMask; + drflac_int32* pDecodedSamples = pSamplesOut; + drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); + drflac_uint32 zeroCountParts[4]; + drflac_uint32 riceParamParts[4]; + int32x4_t coefficients128_0; + int32x4_t coefficients128_4; + int32x4_t coefficients128_8; + int32x4_t samples128_0; + int32x4_t samples128_4; + int32x4_t samples128_8; + uint32x4_t riceParamMask128; + int32x4_t riceParam128; + int64x1_t shift64; + uint32x4_t one128; + int64x2_t prediction128 = { 0 }; + uint32x4_t zeroCountPart128; + uint32x4_t riceParamPart128; + + const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + riceParamMask128 = vdupq_n_u32(riceParamMask); + + riceParam128 = vdupq_n_s32(riceParam); + shift64 = vdup_n_s64(-shift); /* Negate the shift because we'll be doing a variable shift using vshlq_s32(). */ + one128 = vdupq_n_u32(1); + + /* + Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than + what's available in the input buffers. It would be convenient to use a fall-through switch to do this, but this results + in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted + so I think there's opportunity for this to be simplified. + */ + { + int runningOrder = order; + drflac_int32 tempC[4] = {0, 0, 0, 0}; + drflac_int32 tempS[4] = {0, 0, 0, 0}; + + /* 0 - 3. */ + if (runningOrder >= 4) { + coefficients128_0 = vld1q_s32(coefficients + 0); + samples128_0 = vld1q_s32(pSamplesOut - 4); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[2]; tempS[1] = pSamplesOut[-3]; /* fallthrough */ + case 2: tempC[1] = coefficients[1]; tempS[2] = pSamplesOut[-2]; /* fallthrough */ + case 1: tempC[0] = coefficients[0]; tempS[3] = pSamplesOut[-1]; /* fallthrough */ + } + + coefficients128_0 = vld1q_s32(tempC); + samples128_0 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* 4 - 7 */ + if (runningOrder >= 4) { + coefficients128_4 = vld1q_s32(coefficients + 4); + samples128_4 = vld1q_s32(pSamplesOut - 8); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[6]; tempS[1] = pSamplesOut[-7]; /* fallthrough */ + case 2: tempC[1] = coefficients[5]; tempS[2] = pSamplesOut[-6]; /* fallthrough */ + case 1: tempC[0] = coefficients[4]; tempS[3] = pSamplesOut[-5]; /* fallthrough */ + } + + coefficients128_4 = vld1q_s32(tempC); + samples128_4 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* 8 - 11 */ + if (runningOrder == 4) { + coefficients128_8 = vld1q_s32(coefficients + 8); + samples128_8 = vld1q_s32(pSamplesOut - 12); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[10]; tempS[1] = pSamplesOut[-11]; /* fallthrough */ + case 2: tempC[1] = coefficients[ 9]; tempS[2] = pSamplesOut[-10]; /* fallthrough */ + case 1: tempC[0] = coefficients[ 8]; tempS[3] = pSamplesOut[- 9]; /* fallthrough */ + } + + coefficients128_8 = vld1q_s32(tempC); + samples128_8 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ + coefficients128_0 = drflac__vrevq_s32(coefficients128_0); + coefficients128_4 = drflac__vrevq_s32(coefficients128_4); + coefficients128_8 = drflac__vrevq_s32(coefficients128_8); + } + + /* For this version we are doing one sample at a time. */ + while (pDecodedSamples < pDecodedSamplesEnd) { + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[1], &riceParamParts[1]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[2], &riceParamParts[2]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[3], &riceParamParts[3])) { + return DRFLAC_FALSE; + } + + zeroCountPart128 = vld1q_u32(zeroCountParts); + riceParamPart128 = vld1q_u32(riceParamParts); + + riceParamPart128 = vandq_u32(riceParamPart128, riceParamMask128); + riceParamPart128 = vorrq_u32(riceParamPart128, vshlq_u32(zeroCountPart128, riceParam128)); + riceParamPart128 = veorq_u32(vshrq_n_u32(riceParamPart128, 1), vaddq_u32(drflac__vnotq_u32(vandq_u32(riceParamPart128, one128)), one128)); + + for (i = 0; i < 4; i += 1) { + int64x1_t prediction64; + + prediction128 = veorq_s64(prediction128, prediction128); /* Reset to 0. */ + switch (order) + { + case 12: + case 11: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_low_s32(coefficients128_8), vget_low_s32(samples128_8))); + case 10: + case 9: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_high_s32(coefficients128_8), vget_high_s32(samples128_8))); + case 8: + case 7: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_low_s32(coefficients128_4), vget_low_s32(samples128_4))); + case 6: + case 5: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_high_s32(coefficients128_4), vget_high_s32(samples128_4))); + case 4: + case 3: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_low_s32(coefficients128_0), vget_low_s32(samples128_0))); + case 2: + case 1: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_high_s32(coefficients128_0), vget_high_s32(samples128_0))); + } + + /* Horizontal add and shift. */ + prediction64 = drflac__vhaddq_s64(prediction128); + prediction64 = vshl_s64(prediction64, shift64); + prediction64 = vadd_s64(prediction64, vdup_n_s64(vgetq_lane_u32(riceParamPart128, 0))); + + /* Our value should be sitting in prediction64[0]. We need to combine this with our SSE samples. */ + samples128_8 = drflac__valignrq_s32_1(samples128_4, samples128_8); + samples128_4 = drflac__valignrq_s32_1(samples128_0, samples128_4); + samples128_0 = drflac__valignrq_s32_1(vcombine_s32(vreinterpret_s32_s64(prediction64), vdup_n_s32(0)), samples128_0); + + /* Slide our rice parameter down so that the value in position 0 contains the next one to process. */ + riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); + } + + /* We store samples in groups of 4. */ + vst1q_s32(pDecodedSamples, samples128_0); + pDecodedSamples += 4; + } + + /* Make sure we process the last few samples. */ + i = (count & ~3); + while (i < (int)count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0])) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamParts[0] &= riceParamMask; + riceParamParts[0] |= (zeroCountParts[0] << riceParam); + riceParamParts[0] = (riceParamParts[0] >> 1) ^ t[riceParamParts[0] & 0x01]; + + /* Sample reconstruction. */ + pDecodedSamples[0] = riceParamParts[0] + drflac__calculate_prediction_64(order, shift, coefficients, pDecodedSamples); + + i += 1; + pDecodedSamples += 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); + + /* In my testing the order is rarely > 12, so in this case I'm going to simplify the NEON implementation by only handling order <= 12. */ + if (lpcOrder > 0 && lpcOrder <= 12) { + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + return drflac__decode_samples_with_residual__rice__neon_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); + } else { + return drflac__decode_samples_with_residual__rice__neon_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); + } + } else { + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); + } +} +#endif + +static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ +#if defined(DRFLAC_SUPPORT_SSE41) + if (drflac__gIsSSE41Supported) { + return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported) { + return drflac__decode_samples_with_residual__rice__neon(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); + } else +#endif + { + /* Scalar fallback. */ + #if 0 + return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); + #else + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); + #endif + } +} + +/* Reads and seeks past a string of residual values as Rice codes. The decoder should be sitting on the first bit of the Rice codes. */ +static drflac_bool32 drflac__read_and_seek_residual__rice(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam) +{ + drflac_uint32 i; + + DRFLAC_ASSERT(bs != NULL); + + for (i = 0; i < count; ++i) { + if (!drflac__seek_rice_parts(bs, riceParam)) { + return DRFLAC_FALSE; + } + } + + return DRFLAC_TRUE; +} + +#if defined(__clang__) +__attribute__((no_sanitize("signed-integer-overflow"))) +#endif +static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + drflac_uint32 i; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(unencodedBitsPerSample <= 31); /* <-- unencodedBitsPerSample is a 5 bit number, so cannot exceed 31. */ + DRFLAC_ASSERT(pSamplesOut != NULL); + + for (i = 0; i < count; ++i) { + if (unencodedBitsPerSample > 0) { + if (!drflac__read_int32(bs, unencodedBitsPerSample, pSamplesOut + i)) { + return DRFLAC_FALSE; + } + } else { + pSamplesOut[i] = 0; + } + + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[i] += drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i); + } else { + pSamplesOut[i] += drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i); + } + } + + return DRFLAC_TRUE; +} + + +/* +Reads and decodes the residual for the sub-frame the decoder is currently sitting on. This function should be called +when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be ignored. The + and parameters are used to determine how many residual values need to be decoded. +*/ +static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) +{ + drflac_uint8 residualMethod; + drflac_uint8 partitionOrder; + drflac_uint32 samplesInPartition; + drflac_uint32 partitionsRemaining; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(blockSize != 0); + DRFLAC_ASSERT(pDecodedSamples != NULL); /* <-- Should we allow NULL, in which case we just seek past the residual rather than do a full decode? */ + + if (!drflac__read_uint8(bs, 2, &residualMethod)) { + return DRFLAC_FALSE; + } + + if (residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE && residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { + return DRFLAC_FALSE; /* Unknown or unsupported residual coding method. */ + } + + /* Ignore the first values. */ + pDecodedSamples += lpcOrder; + + if (!drflac__read_uint8(bs, 4, &partitionOrder)) { + return DRFLAC_FALSE; + } + + /* + From the FLAC spec: + The Rice partition order in a Rice-coded residual section must be less than or equal to 8. + */ + if (partitionOrder > 8) { + return DRFLAC_FALSE; + } + + /* Validation check. */ + if ((blockSize / (1 << partitionOrder)) < lpcOrder) { + return DRFLAC_FALSE; + } + + samplesInPartition = (blockSize / (1 << partitionOrder)) - lpcOrder; + partitionsRemaining = (1 << partitionOrder); + for (;;) { + drflac_uint8 riceParam = 0; + if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE) { + if (!drflac__read_uint8(bs, 4, &riceParam)) { + return DRFLAC_FALSE; + } + if (riceParam == 15) { + riceParam = 0xFF; + } + } else if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { + if (!drflac__read_uint8(bs, 5, &riceParam)) { + return DRFLAC_FALSE; + } + if (riceParam == 31) { + riceParam = 0xFF; + } + } + + if (riceParam != 0xFF) { + if (!drflac__decode_samples_with_residual__rice(bs, bitsPerSample, samplesInPartition, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { + return DRFLAC_FALSE; + } + } else { + drflac_uint8 unencodedBitsPerSample = 0; + if (!drflac__read_uint8(bs, 5, &unencodedBitsPerSample)) { + return DRFLAC_FALSE; + } + + if (!drflac__decode_samples_with_residual__unencoded(bs, bitsPerSample, samplesInPartition, unencodedBitsPerSample, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { + return DRFLAC_FALSE; + } + } + + pDecodedSamples += samplesInPartition; + + if (partitionsRemaining == 1) { + break; + } + + partitionsRemaining -= 1; + + if (partitionOrder != 0) { + samplesInPartition = blockSize / (1 << partitionOrder); + } + } + + return DRFLAC_TRUE; +} + +/* +Reads and seeks past the residual for the sub-frame the decoder is currently sitting on. This function should be called +when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be set to 0. The + and parameters are used to determine how many residual values need to be decoded. +*/ +static drflac_bool32 drflac__read_and_seek_residual(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 order) +{ + drflac_uint8 residualMethod; + drflac_uint8 partitionOrder; + drflac_uint32 samplesInPartition; + drflac_uint32 partitionsRemaining; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(blockSize != 0); + + if (!drflac__read_uint8(bs, 2, &residualMethod)) { + return DRFLAC_FALSE; + } + + if (residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE && residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { + return DRFLAC_FALSE; /* Unknown or unsupported residual coding method. */ + } + + if (!drflac__read_uint8(bs, 4, &partitionOrder)) { + return DRFLAC_FALSE; + } + + /* + From the FLAC spec: + The Rice partition order in a Rice-coded residual section must be less than or equal to 8. + */ + if (partitionOrder > 8) { + return DRFLAC_FALSE; + } + + /* Validation check. */ + if ((blockSize / (1 << partitionOrder)) <= order) { + return DRFLAC_FALSE; + } + + samplesInPartition = (blockSize / (1 << partitionOrder)) - order; + partitionsRemaining = (1 << partitionOrder); + for (;;) + { + drflac_uint8 riceParam = 0; + if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE) { + if (!drflac__read_uint8(bs, 4, &riceParam)) { + return DRFLAC_FALSE; + } + if (riceParam == 15) { + riceParam = 0xFF; + } + } else if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { + if (!drflac__read_uint8(bs, 5, &riceParam)) { + return DRFLAC_FALSE; + } + if (riceParam == 31) { + riceParam = 0xFF; + } + } + + if (riceParam != 0xFF) { + if (!drflac__read_and_seek_residual__rice(bs, samplesInPartition, riceParam)) { + return DRFLAC_FALSE; + } + } else { + drflac_uint8 unencodedBitsPerSample = 0; + if (!drflac__read_uint8(bs, 5, &unencodedBitsPerSample)) { + return DRFLAC_FALSE; + } + + if (!drflac__seek_bits(bs, unencodedBitsPerSample * samplesInPartition)) { + return DRFLAC_FALSE; + } + } + + + if (partitionsRemaining == 1) { + break; + } + + partitionsRemaining -= 1; + samplesInPartition = blockSize / (1 << partitionOrder); + } + + return DRFLAC_TRUE; +} + + +static drflac_bool32 drflac__decode_samples__constant(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 subframeBitsPerSample, drflac_int32* pDecodedSamples) +{ + drflac_uint32 i; + + /* Only a single sample needs to be decoded here. */ + drflac_int32 sample; + if (!drflac__read_int32(bs, subframeBitsPerSample, &sample)) { + return DRFLAC_FALSE; + } + + /* + We don't really need to expand this, but it does simplify the process of reading samples. If this becomes a performance issue (unlikely) + we'll want to look at a more efficient way. + */ + for (i = 0; i < blockSize; ++i) { + pDecodedSamples[i] = sample; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples__verbatim(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 subframeBitsPerSample, drflac_int32* pDecodedSamples) +{ + drflac_uint32 i; + + for (i = 0; i < blockSize; ++i) { + drflac_int32 sample; + if (!drflac__read_int32(bs, subframeBitsPerSample, &sample)) { + return DRFLAC_FALSE; + } + + pDecodedSamples[i] = sample; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 subframeBitsPerSample, drflac_uint8 lpcOrder, drflac_int32* pDecodedSamples) +{ + drflac_uint32 i; + + static drflac_int32 lpcCoefficientsTable[5][4] = { + {0, 0, 0, 0}, + {1, 0, 0, 0}, + {2, -1, 0, 0}, + {3, -3, 1, 0}, + {4, -6, 4, -1} + }; + + /* Warm up samples and coefficients. */ + for (i = 0; i < lpcOrder; ++i) { + drflac_int32 sample; + if (!drflac__read_int32(bs, subframeBitsPerSample, &sample)) { + return DRFLAC_FALSE; + } + + pDecodedSamples[i] = sample; + } + + if (!drflac__decode_samples_with_residual(bs, subframeBitsPerSample, blockSize, lpcOrder, 0, 4, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) { + return DRFLAC_FALSE; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples__lpc(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 bitsPerSample, drflac_uint8 lpcOrder, drflac_int32* pDecodedSamples) +{ + drflac_uint8 i; + drflac_uint8 lpcPrecision; + drflac_int8 lpcShift; + drflac_int32 coefficients[32]; + + /* Warm up samples. */ + for (i = 0; i < lpcOrder; ++i) { + drflac_int32 sample; + if (!drflac__read_int32(bs, bitsPerSample, &sample)) { + return DRFLAC_FALSE; + } + + pDecodedSamples[i] = sample; + } + + if (!drflac__read_uint8(bs, 4, &lpcPrecision)) { + return DRFLAC_FALSE; + } + if (lpcPrecision == 15) { + return DRFLAC_FALSE; /* Invalid. */ + } + lpcPrecision += 1; + + if (!drflac__read_int8(bs, 5, &lpcShift)) { + return DRFLAC_FALSE; + } + + /* + From the FLAC specification: + + Quantized linear predictor coefficient shift needed in bits (NOTE: this number is signed two's-complement) + + Emphasis on the "signed two's-complement". In practice there does not seem to be any encoders nor decoders supporting negative shifts. For now dr_flac is + not going to support negative shifts as I don't have any reference files. However, when a reference file comes through I will consider adding support. + */ + if (lpcShift < 0) { + return DRFLAC_FALSE; + } + + DRFLAC_ZERO_MEMORY(coefficients, sizeof(coefficients)); + for (i = 0; i < lpcOrder; ++i) { + if (!drflac__read_int32(bs, lpcPrecision, coefficients + i)) { + return DRFLAC_FALSE; + } + } + + if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { + return DRFLAC_FALSE; + } + + return DRFLAC_TRUE; +} + + +static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_uint8 streaminfoBitsPerSample, drflac_frame_header* header) +{ + const drflac_uint32 sampleRateTable[12] = {0, 88200, 176400, 192000, 8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000}; + const drflac_uint8 bitsPerSampleTable[8] = {0, 8, 12, (drflac_uint8)-1, 16, 20, 24, (drflac_uint8)-1}; /* -1 = reserved. */ + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(header != NULL); + + /* Keep looping until we find a valid sync code. */ + for (;;) { + drflac_uint8 crc8 = 0xCE; /* 0xCE = drflac_crc8(0, 0x3FFE, 14); */ + drflac_uint8 reserved = 0; + drflac_uint8 blockingStrategy = 0; + drflac_uint8 blockSize = 0; + drflac_uint8 sampleRate = 0; + drflac_uint8 channelAssignment = 0; + drflac_uint8 bitsPerSample = 0; + drflac_bool32 isVariableBlockSize; + + if (!drflac__find_and_seek_to_next_sync_code(bs)) { + return DRFLAC_FALSE; + } + + if (!drflac__read_uint8(bs, 1, &reserved)) { + return DRFLAC_FALSE; + } + if (reserved == 1) { + continue; + } + crc8 = drflac_crc8(crc8, reserved, 1); + + if (!drflac__read_uint8(bs, 1, &blockingStrategy)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, blockingStrategy, 1); + + if (!drflac__read_uint8(bs, 4, &blockSize)) { + return DRFLAC_FALSE; + } + if (blockSize == 0) { + continue; + } + crc8 = drflac_crc8(crc8, blockSize, 4); + + if (!drflac__read_uint8(bs, 4, &sampleRate)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, sampleRate, 4); + + if (!drflac__read_uint8(bs, 4, &channelAssignment)) { + return DRFLAC_FALSE; + } + if (channelAssignment > 10) { + continue; + } + crc8 = drflac_crc8(crc8, channelAssignment, 4); + + if (!drflac__read_uint8(bs, 3, &bitsPerSample)) { + return DRFLAC_FALSE; + } + if (bitsPerSample == 3 || bitsPerSample == 7) { + continue; + } + crc8 = drflac_crc8(crc8, bitsPerSample, 3); + + + if (!drflac__read_uint8(bs, 1, &reserved)) { + return DRFLAC_FALSE; + } + if (reserved == 1) { + continue; + } + crc8 = drflac_crc8(crc8, reserved, 1); + + + isVariableBlockSize = blockingStrategy == 1; + if (isVariableBlockSize) { + drflac_uint64 pcmFrameNumber; + drflac_result result = drflac__read_utf8_coded_number(bs, &pcmFrameNumber, &crc8); + if (result != DRFLAC_SUCCESS) { + if (result == DRFLAC_AT_END) { + return DRFLAC_FALSE; + } else { + continue; + } + } + header->flacFrameNumber = 0; + header->pcmFrameNumber = pcmFrameNumber; + } else { + drflac_uint64 flacFrameNumber = 0; + drflac_result result = drflac__read_utf8_coded_number(bs, &flacFrameNumber, &crc8); + if (result != DRFLAC_SUCCESS) { + if (result == DRFLAC_AT_END) { + return DRFLAC_FALSE; + } else { + continue; + } + } + header->flacFrameNumber = (drflac_uint32)flacFrameNumber; /* <-- Safe cast. */ + header->pcmFrameNumber = 0; + } + + + DRFLAC_ASSERT(blockSize > 0); + if (blockSize == 1) { + header->blockSizeInPCMFrames = 192; + } else if (blockSize <= 5) { + DRFLAC_ASSERT(blockSize >= 2); + header->blockSizeInPCMFrames = 576 * (1 << (blockSize - 2)); + } else if (blockSize == 6) { + if (!drflac__read_uint16(bs, 8, &header->blockSizeInPCMFrames)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->blockSizeInPCMFrames, 8); + header->blockSizeInPCMFrames += 1; + } else if (blockSize == 7) { + if (!drflac__read_uint16(bs, 16, &header->blockSizeInPCMFrames)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->blockSizeInPCMFrames, 16); + if (header->blockSizeInPCMFrames == 0xFFFF) { + return DRFLAC_FALSE; /* Frame is too big. This is the size of the frame minus 1. The STREAMINFO block defines the max block size which is 16-bits. Adding one will make it 17 bits and therefore too big. */ + } + header->blockSizeInPCMFrames += 1; + } else { + DRFLAC_ASSERT(blockSize >= 8); + header->blockSizeInPCMFrames = 256 * (1 << (blockSize - 8)); + } + + + if (sampleRate <= 11) { + header->sampleRate = sampleRateTable[sampleRate]; + } else if (sampleRate == 12) { + if (!drflac__read_uint32(bs, 8, &header->sampleRate)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->sampleRate, 8); + header->sampleRate *= 1000; + } else if (sampleRate == 13) { + if (!drflac__read_uint32(bs, 16, &header->sampleRate)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->sampleRate, 16); + } else if (sampleRate == 14) { + if (!drflac__read_uint32(bs, 16, &header->sampleRate)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->sampleRate, 16); + header->sampleRate *= 10; + } else { + continue; /* Invalid. Assume an invalid block. */ + } + + + header->channelAssignment = channelAssignment; + + header->bitsPerSample = bitsPerSampleTable[bitsPerSample]; + if (header->bitsPerSample == 0) { + header->bitsPerSample = streaminfoBitsPerSample; + } + + if (header->bitsPerSample != streaminfoBitsPerSample) { + /* If this subframe has a different bitsPerSample then streaminfo or the first frame, reject it */ + return DRFLAC_FALSE; + } + + if (!drflac__read_uint8(bs, 8, &header->crc8)) { + return DRFLAC_FALSE; + } + +#ifndef DR_FLAC_NO_CRC + if (header->crc8 != crc8) { + continue; /* CRC mismatch. Loop back to the top and find the next sync code. */ + } +#endif + return DRFLAC_TRUE; + } +} + +static drflac_bool32 drflac__read_subframe_header(drflac_bs* bs, drflac_subframe* pSubframe) +{ + drflac_uint8 header; + int type; + + if (!drflac__read_uint8(bs, 8, &header)) { + return DRFLAC_FALSE; + } + + /* First bit should always be 0. */ + if ((header & 0x80) != 0) { + return DRFLAC_FALSE; + } + + type = (header & 0x7E) >> 1; + if (type == 0) { + pSubframe->subframeType = DRFLAC_SUBFRAME_CONSTANT; + } else if (type == 1) { + pSubframe->subframeType = DRFLAC_SUBFRAME_VERBATIM; + } else { + if ((type & 0x20) != 0) { + pSubframe->subframeType = DRFLAC_SUBFRAME_LPC; + pSubframe->lpcOrder = (drflac_uint8)(type & 0x1F) + 1; + } else if ((type & 0x08) != 0) { + pSubframe->subframeType = DRFLAC_SUBFRAME_FIXED; + pSubframe->lpcOrder = (drflac_uint8)(type & 0x07); + if (pSubframe->lpcOrder > 4) { + pSubframe->subframeType = DRFLAC_SUBFRAME_RESERVED; + pSubframe->lpcOrder = 0; + } + } else { + pSubframe->subframeType = DRFLAC_SUBFRAME_RESERVED; + } + } + + if (pSubframe->subframeType == DRFLAC_SUBFRAME_RESERVED) { + return DRFLAC_FALSE; + } + + /* Wasted bits per sample. */ + pSubframe->wastedBitsPerSample = 0; + if ((header & 0x01) == 1) { + unsigned int wastedBitsPerSample; + if (!drflac__seek_past_next_set_bit(bs, &wastedBitsPerSample)) { + return DRFLAC_FALSE; + } + pSubframe->wastedBitsPerSample = (drflac_uint8)wastedBitsPerSample + 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_subframe(drflac_bs* bs, drflac_frame* frame, int subframeIndex, drflac_int32* pDecodedSamplesOut) +{ + drflac_subframe* pSubframe; + drflac_uint32 subframeBitsPerSample; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(frame != NULL); + + pSubframe = frame->subframes + subframeIndex; + if (!drflac__read_subframe_header(bs, pSubframe)) { + return DRFLAC_FALSE; + } + + /* Side channels require an extra bit per sample. Took a while to figure that one out... */ + subframeBitsPerSample = frame->header.bitsPerSample; + if ((frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE || frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE) && subframeIndex == 1) { + subframeBitsPerSample += 1; + } else if (frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE && subframeIndex == 0) { + subframeBitsPerSample += 1; + } + + if (subframeBitsPerSample > 32) { + /* libFLAC and ffmpeg reject 33-bit subframes as well */ + return DRFLAC_FALSE; + } + + /* Need to handle wasted bits per sample. */ + if (pSubframe->wastedBitsPerSample >= subframeBitsPerSample) { + return DRFLAC_FALSE; + } + subframeBitsPerSample -= pSubframe->wastedBitsPerSample; + + pSubframe->pSamplesS32 = pDecodedSamplesOut; + + switch (pSubframe->subframeType) + { + case DRFLAC_SUBFRAME_CONSTANT: + { + drflac__decode_samples__constant(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->pSamplesS32); + } break; + + case DRFLAC_SUBFRAME_VERBATIM: + { + drflac__decode_samples__verbatim(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->pSamplesS32); + } break; + + case DRFLAC_SUBFRAME_FIXED: + { + drflac__decode_samples__fixed(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->lpcOrder, pSubframe->pSamplesS32); + } break; + + case DRFLAC_SUBFRAME_LPC: + { + drflac__decode_samples__lpc(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->lpcOrder, pSubframe->pSamplesS32); + } break; + + default: return DRFLAC_FALSE; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__seek_subframe(drflac_bs* bs, drflac_frame* frame, int subframeIndex) +{ + drflac_subframe* pSubframe; + drflac_uint32 subframeBitsPerSample; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(frame != NULL); + + pSubframe = frame->subframes + subframeIndex; + if (!drflac__read_subframe_header(bs, pSubframe)) { + return DRFLAC_FALSE; + } + + /* Side channels require an extra bit per sample. Took a while to figure that one out... */ + subframeBitsPerSample = frame->header.bitsPerSample; + if ((frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE || frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE) && subframeIndex == 1) { + subframeBitsPerSample += 1; + } else if (frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE && subframeIndex == 0) { + subframeBitsPerSample += 1; + } + + /* Need to handle wasted bits per sample. */ + if (pSubframe->wastedBitsPerSample >= subframeBitsPerSample) { + return DRFLAC_FALSE; + } + subframeBitsPerSample -= pSubframe->wastedBitsPerSample; + + pSubframe->pSamplesS32 = NULL; + + switch (pSubframe->subframeType) + { + case DRFLAC_SUBFRAME_CONSTANT: + { + if (!drflac__seek_bits(bs, subframeBitsPerSample)) { + return DRFLAC_FALSE; + } + } break; + + case DRFLAC_SUBFRAME_VERBATIM: + { + unsigned int bitsToSeek = frame->header.blockSizeInPCMFrames * subframeBitsPerSample; + if (!drflac__seek_bits(bs, bitsToSeek)) { + return DRFLAC_FALSE; + } + } break; + + case DRFLAC_SUBFRAME_FIXED: + { + unsigned int bitsToSeek = pSubframe->lpcOrder * subframeBitsPerSample; + if (!drflac__seek_bits(bs, bitsToSeek)) { + return DRFLAC_FALSE; + } + + if (!drflac__read_and_seek_residual(bs, frame->header.blockSizeInPCMFrames, pSubframe->lpcOrder)) { + return DRFLAC_FALSE; + } + } break; + + case DRFLAC_SUBFRAME_LPC: + { + drflac_uint8 lpcPrecision; + + unsigned int bitsToSeek = pSubframe->lpcOrder * subframeBitsPerSample; + if (!drflac__seek_bits(bs, bitsToSeek)) { + return DRFLAC_FALSE; + } + + if (!drflac__read_uint8(bs, 4, &lpcPrecision)) { + return DRFLAC_FALSE; + } + if (lpcPrecision == 15) { + return DRFLAC_FALSE; /* Invalid. */ + } + lpcPrecision += 1; + + + bitsToSeek = (pSubframe->lpcOrder * lpcPrecision) + 5; /* +5 for shift. */ + if (!drflac__seek_bits(bs, bitsToSeek)) { + return DRFLAC_FALSE; + } + + if (!drflac__read_and_seek_residual(bs, frame->header.blockSizeInPCMFrames, pSubframe->lpcOrder)) { + return DRFLAC_FALSE; + } + } break; + + default: return DRFLAC_FALSE; + } + + return DRFLAC_TRUE; +} + + +static DRFLAC_INLINE drflac_uint8 drflac__get_channel_count_from_channel_assignment(drflac_int8 channelAssignment) +{ + drflac_uint8 lookup[] = {1, 2, 3, 4, 5, 6, 7, 8, 2, 2, 2}; + + DRFLAC_ASSERT(channelAssignment <= 10); + return lookup[channelAssignment]; +} + +static drflac_result drflac__decode_flac_frame(drflac* pFlac) +{ + int channelCount; + int i; + drflac_uint8 paddingSizeInBits; + drflac_uint16 desiredCRC16; +#ifndef DR_FLAC_NO_CRC + drflac_uint16 actualCRC16; +#endif + + /* This function should be called while the stream is sitting on the first byte after the frame header. */ + DRFLAC_ZERO_MEMORY(pFlac->currentFLACFrame.subframes, sizeof(pFlac->currentFLACFrame.subframes)); + + /* The frame block size must never be larger than the maximum block size defined by the FLAC stream. */ + if (pFlac->currentFLACFrame.header.blockSizeInPCMFrames > pFlac->maxBlockSizeInPCMFrames) { + return DRFLAC_ERROR; + } + + /* The number of channels in the frame must match the channel count from the STREAMINFO block. */ + channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); + if (channelCount != (int)pFlac->channels) { + return DRFLAC_ERROR; + } + + for (i = 0; i < channelCount; ++i) { + if (!drflac__decode_subframe(&pFlac->bs, &pFlac->currentFLACFrame, i, pFlac->pDecodedSamples + (pFlac->currentFLACFrame.header.blockSizeInPCMFrames * i))) { + return DRFLAC_ERROR; + } + } + + paddingSizeInBits = (drflac_uint8)(DRFLAC_CACHE_L1_BITS_REMAINING(&pFlac->bs) & 7); + if (paddingSizeInBits > 0) { + drflac_uint8 padding = 0; + if (!drflac__read_uint8(&pFlac->bs, paddingSizeInBits, &padding)) { + return DRFLAC_AT_END; + } + } + +#ifndef DR_FLAC_NO_CRC + actualCRC16 = drflac__flush_crc16(&pFlac->bs); +#endif + if (!drflac__read_uint16(&pFlac->bs, 16, &desiredCRC16)) { + return DRFLAC_AT_END; + } + +#ifndef DR_FLAC_NO_CRC + if (actualCRC16 != desiredCRC16) { + return DRFLAC_CRC_MISMATCH; /* CRC mismatch. */ + } +#endif + + pFlac->currentFLACFrame.pcmFramesRemaining = pFlac->currentFLACFrame.header.blockSizeInPCMFrames; + + return DRFLAC_SUCCESS; +} + +static drflac_result drflac__seek_flac_frame(drflac* pFlac) +{ + int channelCount; + int i; + drflac_uint16 desiredCRC16; +#ifndef DR_FLAC_NO_CRC + drflac_uint16 actualCRC16; +#endif + + channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); + for (i = 0; i < channelCount; ++i) { + if (!drflac__seek_subframe(&pFlac->bs, &pFlac->currentFLACFrame, i)) { + return DRFLAC_ERROR; + } + } + + /* Padding. */ + if (!drflac__seek_bits(&pFlac->bs, DRFLAC_CACHE_L1_BITS_REMAINING(&pFlac->bs) & 7)) { + return DRFLAC_ERROR; + } + + /* CRC. */ +#ifndef DR_FLAC_NO_CRC + actualCRC16 = drflac__flush_crc16(&pFlac->bs); +#endif + if (!drflac__read_uint16(&pFlac->bs, 16, &desiredCRC16)) { + return DRFLAC_AT_END; + } + +#ifndef DR_FLAC_NO_CRC + if (actualCRC16 != desiredCRC16) { + return DRFLAC_CRC_MISMATCH; /* CRC mismatch. */ + } +#endif + + return DRFLAC_SUCCESS; +} + +static drflac_bool32 drflac__read_and_decode_next_flac_frame(drflac* pFlac) +{ + DRFLAC_ASSERT(pFlac != NULL); + + for (;;) { + drflac_result result; + + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + + result = drflac__decode_flac_frame(pFlac); + if (result != DRFLAC_SUCCESS) { + if (result == DRFLAC_CRC_MISMATCH) { + continue; /* CRC mismatch. Skip to the next frame. */ + } else { + return DRFLAC_FALSE; + } + } + + return DRFLAC_TRUE; + } +} + +static void drflac__get_pcm_frame_range_of_current_flac_frame(drflac* pFlac, drflac_uint64* pFirstPCMFrame, drflac_uint64* pLastPCMFrame) +{ + drflac_uint64 firstPCMFrame; + drflac_uint64 lastPCMFrame; + + DRFLAC_ASSERT(pFlac != NULL); + + firstPCMFrame = pFlac->currentFLACFrame.header.pcmFrameNumber; + if (firstPCMFrame == 0) { + firstPCMFrame = ((drflac_uint64)pFlac->currentFLACFrame.header.flacFrameNumber) * pFlac->maxBlockSizeInPCMFrames; + } + + lastPCMFrame = firstPCMFrame + pFlac->currentFLACFrame.header.blockSizeInPCMFrames; + if (lastPCMFrame > 0) { + lastPCMFrame -= 1; /* Needs to be zero based. */ + } + + if (pFirstPCMFrame) { + *pFirstPCMFrame = firstPCMFrame; + } + if (pLastPCMFrame) { + *pLastPCMFrame = lastPCMFrame; + } +} + +static drflac_bool32 drflac__seek_to_first_frame(drflac* pFlac) +{ + drflac_bool32 result; + + DRFLAC_ASSERT(pFlac != NULL); + + result = drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes); + + DRFLAC_ZERO_MEMORY(&pFlac->currentFLACFrame, sizeof(pFlac->currentFLACFrame)); + pFlac->currentPCMFrame = 0; + + return result; +} + +static DRFLAC_INLINE drflac_result drflac__seek_to_next_flac_frame(drflac* pFlac) +{ + /* This function should only ever be called while the decoder is sitting on the first byte past the FRAME_HEADER section. */ + DRFLAC_ASSERT(pFlac != NULL); + return drflac__seek_flac_frame(pFlac); +} + + +static drflac_uint64 drflac__seek_forward_by_pcm_frames(drflac* pFlac, drflac_uint64 pcmFramesToSeek) +{ + drflac_uint64 pcmFramesRead = 0; + while (pcmFramesToSeek > 0) { + if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + break; /* Couldn't read the next frame, so just break from the loop and return. */ + } + } else { + if (pFlac->currentFLACFrame.pcmFramesRemaining > pcmFramesToSeek) { + pcmFramesRead += pcmFramesToSeek; + pFlac->currentFLACFrame.pcmFramesRemaining -= (drflac_uint32)pcmFramesToSeek; /* <-- Safe cast. Will always be < currentFrame.pcmFramesRemaining < 65536. */ + pcmFramesToSeek = 0; + } else { + pcmFramesRead += pFlac->currentFLACFrame.pcmFramesRemaining; + pcmFramesToSeek -= pFlac->currentFLACFrame.pcmFramesRemaining; + pFlac->currentFLACFrame.pcmFramesRemaining = 0; + } + } + } + + pFlac->currentPCMFrame += pcmFramesRead; + return pcmFramesRead; +} + + +static drflac_bool32 drflac__seek_to_pcm_frame__brute_force(drflac* pFlac, drflac_uint64 pcmFrameIndex) +{ + drflac_bool32 isMidFrame = DRFLAC_FALSE; + drflac_uint64 runningPCMFrameCount; + + DRFLAC_ASSERT(pFlac != NULL); + + /* If we are seeking forward we start from the current position. Otherwise we need to start all the way from the start of the file. */ + if (pcmFrameIndex >= pFlac->currentPCMFrame) { + /* Seeking forward. Need to seek from the current position. */ + runningPCMFrameCount = pFlac->currentPCMFrame; + + /* The frame header for the first frame may not yet have been read. We need to do that if necessary. */ + if (pFlac->currentPCMFrame == 0 && pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + } else { + isMidFrame = DRFLAC_TRUE; + } + } else { + /* Seeking backwards. Need to seek from the start of the file. */ + runningPCMFrameCount = 0; + + /* Move back to the start. */ + if (!drflac__seek_to_first_frame(pFlac)) { + return DRFLAC_FALSE; + } + + /* Decode the first frame in preparation for sample-exact seeking below. */ + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + } + + /* + We need to as quickly as possible find the frame that contains the target sample. To do this, we iterate over each frame and inspect its + header. If based on the header we can determine that the frame contains the sample, we do a full decode of that frame. + */ + for (;;) { + drflac_uint64 pcmFrameCountInThisFLACFrame; + drflac_uint64 firstPCMFrameInFLACFrame = 0; + drflac_uint64 lastPCMFrameInFLACFrame = 0; + + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &firstPCMFrameInFLACFrame, &lastPCMFrameInFLACFrame); + + pcmFrameCountInThisFLACFrame = (lastPCMFrameInFLACFrame - firstPCMFrameInFLACFrame) + 1; + if (pcmFrameIndex < (runningPCMFrameCount + pcmFrameCountInThisFLACFrame)) { + /* + The sample should be in this frame. We need to fully decode it, however if it's an invalid frame (a CRC mismatch), we need to pretend + it never existed and keep iterating. + */ + drflac_uint64 pcmFramesToDecode = pcmFrameIndex - runningPCMFrameCount; + + if (!isMidFrame) { + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + /* The frame is valid. We just need to skip over some samples to ensure it's sample-exact. */ + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ + } else { + if (result == DRFLAC_CRC_MISMATCH) { + goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ + } else { + return DRFLAC_FALSE; + } + } + } else { + /* We started seeking mid-frame which means we need to skip the frame decoding part. */ + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; + } + } else { + /* + It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this + frame never existed and leave the running sample count untouched. + */ + if (!isMidFrame) { + drflac_result result = drflac__seek_to_next_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + runningPCMFrameCount += pcmFrameCountInThisFLACFrame; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ + } else { + return DRFLAC_FALSE; + } + } + } else { + /* + We started seeking mid-frame which means we need to seek by reading to the end of the frame instead of with + drflac__seek_to_next_flac_frame() which only works if the decoder is sitting on the byte just after the frame header. + */ + runningPCMFrameCount += pFlac->currentFLACFrame.pcmFramesRemaining; + pFlac->currentFLACFrame.pcmFramesRemaining = 0; + isMidFrame = DRFLAC_FALSE; + } + + /* If we are seeking to the end of the file and we've just hit it, we're done. */ + if (pcmFrameIndex == pFlac->totalPCMFrameCount && runningPCMFrameCount == pFlac->totalPCMFrameCount) { + return DRFLAC_TRUE; + } + } + + next_iteration: + /* Grab the next frame in preparation for the next iteration. */ + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + } +} + + +#if !defined(DR_FLAC_NO_CRC) +/* +We use an average compression ratio to determine our approximate start location. FLAC files are generally about 50%-70% the size of their +uncompressed counterparts so we'll use this as a basis. I'm going to split the middle and use a factor of 0.6 to determine the starting +location. +*/ +#define DRFLAC_BINARY_SEARCH_APPROX_COMPRESSION_RATIO 0.6f + +static drflac_bool32 drflac__seek_to_approximate_flac_frame_to_byte(drflac* pFlac, drflac_uint64 targetByte, drflac_uint64 rangeLo, drflac_uint64 rangeHi, drflac_uint64* pLastSuccessfulSeekOffset) +{ + DRFLAC_ASSERT(pFlac != NULL); + DRFLAC_ASSERT(pLastSuccessfulSeekOffset != NULL); + DRFLAC_ASSERT(targetByte >= rangeLo); + DRFLAC_ASSERT(targetByte <= rangeHi); + + *pLastSuccessfulSeekOffset = pFlac->firstFLACFramePosInBytes; + + for (;;) { + /* After rangeLo == rangeHi == targetByte fails, we need to break out. */ + drflac_uint64 lastTargetByte = targetByte; + + /* When seeking to a byte, failure probably means we've attempted to seek beyond the end of the stream. To counter this we just halve it each attempt. */ + if (!drflac__seek_to_byte(&pFlac->bs, targetByte)) { + /* If we couldn't even seek to the first byte in the stream we have a problem. Just abandon the whole thing. */ + if (targetByte == 0) { + drflac__seek_to_first_frame(pFlac); /* Try to recover. */ + return DRFLAC_FALSE; + } + + /* Halve the byte location and continue. */ + targetByte = rangeLo + ((rangeHi - rangeLo)/2); + rangeHi = targetByte; + } else { + /* Getting here should mean that we have seeked to an appropriate byte. */ + + /* Clear the details of the FLAC frame so we don't misreport data. */ + DRFLAC_ZERO_MEMORY(&pFlac->currentFLACFrame, sizeof(pFlac->currentFLACFrame)); + + /* + Now seek to the next FLAC frame. We need to decode the entire frame (not just the header) because it's possible for the header to incorrectly pass the + CRC check and return bad data. We need to decode the entire frame to be more certain. Although this seems unlikely, this has happened to me in testing + so it needs to stay this way for now. + */ +#if 1 + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + /* Halve the byte location and continue. */ + targetByte = rangeLo + ((rangeHi - rangeLo)/2); + rangeHi = targetByte; + } else { + break; + } +#else + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + /* Halve the byte location and continue. */ + targetByte = rangeLo + ((rangeHi - rangeLo)/2); + rangeHi = targetByte; + } else { + break; + } +#endif + } + + /* We already tried this byte and there are no more to try, break out. */ + if(targetByte == lastTargetByte) { + return DRFLAC_FALSE; + } + } + + /* The current PCM frame needs to be updated based on the frame we just seeked to. */ + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &pFlac->currentPCMFrame, NULL); + + DRFLAC_ASSERT(targetByte <= rangeHi); + + *pLastSuccessfulSeekOffset = targetByte; + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(drflac* pFlac, drflac_uint64 offset) +{ + /* This section of code would be used if we were only decoding the FLAC frame header when calling drflac__seek_to_approximate_flac_frame_to_byte(). */ +#if 0 + if (drflac__decode_flac_frame(pFlac) != DRFLAC_SUCCESS) { + /* We failed to decode this frame which may be due to it being corrupt. We'll just use the next valid FLAC frame. */ + if (drflac__read_and_decode_next_flac_frame(pFlac) == DRFLAC_FALSE) { + return DRFLAC_FALSE; + } + } +#endif + + return drflac__seek_forward_by_pcm_frames(pFlac, offset) == offset; +} + + +static drflac_bool32 drflac__seek_to_pcm_frame__binary_search_internal(drflac* pFlac, drflac_uint64 pcmFrameIndex, drflac_uint64 byteRangeLo, drflac_uint64 byteRangeHi) +{ + /* This assumes pFlac->currentPCMFrame is sitting on byteRangeLo upon entry. */ + + drflac_uint64 targetByte; + drflac_uint64 pcmRangeLo = pFlac->totalPCMFrameCount; + drflac_uint64 pcmRangeHi = 0; + drflac_uint64 lastSuccessfulSeekOffset = (drflac_uint64)-1; + drflac_uint64 closestSeekOffsetBeforeTargetPCMFrame = byteRangeLo; + drflac_uint32 seekForwardThreshold = (pFlac->maxBlockSizeInPCMFrames != 0) ? pFlac->maxBlockSizeInPCMFrames*2 : 4096; + + targetByte = byteRangeLo + (drflac_uint64)(((drflac_int64)((pcmFrameIndex - pFlac->currentPCMFrame) * pFlac->channels * pFlac->bitsPerSample)/8.0f) * DRFLAC_BINARY_SEARCH_APPROX_COMPRESSION_RATIO); + if (targetByte > byteRangeHi) { + targetByte = byteRangeHi; + } + + for (;;) { + if (drflac__seek_to_approximate_flac_frame_to_byte(pFlac, targetByte, byteRangeLo, byteRangeHi, &lastSuccessfulSeekOffset)) { + /* We found a FLAC frame. We need to check if it contains the sample we're looking for. */ + drflac_uint64 newPCMRangeLo; + drflac_uint64 newPCMRangeHi; + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &newPCMRangeLo, &newPCMRangeHi); + + /* If we selected the same frame, it means we should be pretty close. Just decode the rest. */ + if (pcmRangeLo == newPCMRangeLo) { + if (!drflac__seek_to_approximate_flac_frame_to_byte(pFlac, closestSeekOffsetBeforeTargetPCMFrame, closestSeekOffsetBeforeTargetPCMFrame, byteRangeHi, &lastSuccessfulSeekOffset)) { + break; /* Failed to seek to closest frame. */ + } + + if (drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(pFlac, pcmFrameIndex - pFlac->currentPCMFrame)) { + return DRFLAC_TRUE; + } else { + break; /* Failed to seek forward. */ + } + } + + pcmRangeLo = newPCMRangeLo; + pcmRangeHi = newPCMRangeHi; + + if (pcmRangeLo <= pcmFrameIndex && pcmRangeHi >= pcmFrameIndex) { + /* The target PCM frame is in this FLAC frame. */ + if (drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(pFlac, pcmFrameIndex - pFlac->currentPCMFrame) ) { + return DRFLAC_TRUE; + } else { + break; /* Failed to seek to FLAC frame. */ + } + } else { + const float approxCompressionRatio = (drflac_int64)(lastSuccessfulSeekOffset - pFlac->firstFLACFramePosInBytes) / ((drflac_int64)(pcmRangeLo * pFlac->channels * pFlac->bitsPerSample)/8.0f); + + if (pcmRangeLo > pcmFrameIndex) { + /* We seeked too far forward. We need to move our target byte backward and try again. */ + byteRangeHi = lastSuccessfulSeekOffset; + if (byteRangeLo > byteRangeHi) { + byteRangeLo = byteRangeHi; + } + + targetByte = byteRangeLo + ((byteRangeHi - byteRangeLo) / 2); + if (targetByte < byteRangeLo) { + targetByte = byteRangeLo; + } + } else /*if (pcmRangeHi < pcmFrameIndex)*/ { + /* We didn't seek far enough. We need to move our target byte forward and try again. */ + + /* If we're close enough we can just seek forward. */ + if ((pcmFrameIndex - pcmRangeLo) < seekForwardThreshold) { + if (drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(pFlac, pcmFrameIndex - pFlac->currentPCMFrame)) { + return DRFLAC_TRUE; + } else { + break; /* Failed to seek to FLAC frame. */ + } + } else { + byteRangeLo = lastSuccessfulSeekOffset; + if (byteRangeHi < byteRangeLo) { + byteRangeHi = byteRangeLo; + } + + targetByte = lastSuccessfulSeekOffset + (drflac_uint64)(((drflac_int64)((pcmFrameIndex-pcmRangeLo) * pFlac->channels * pFlac->bitsPerSample)/8.0f) * approxCompressionRatio); + if (targetByte > byteRangeHi) { + targetByte = byteRangeHi; + } + + if (closestSeekOffsetBeforeTargetPCMFrame < lastSuccessfulSeekOffset) { + closestSeekOffsetBeforeTargetPCMFrame = lastSuccessfulSeekOffset; + } + } + } + } + } else { + /* Getting here is really bad. We just recover as best we can, but moving to the first frame in the stream, and then abort. */ + break; + } + } + + drflac__seek_to_first_frame(pFlac); /* <-- Try to recover. */ + return DRFLAC_FALSE; +} + +static drflac_bool32 drflac__seek_to_pcm_frame__binary_search(drflac* pFlac, drflac_uint64 pcmFrameIndex) +{ + drflac_uint64 byteRangeLo; + drflac_uint64 byteRangeHi; + drflac_uint32 seekForwardThreshold = (pFlac->maxBlockSizeInPCMFrames != 0) ? pFlac->maxBlockSizeInPCMFrames*2 : 4096; + + /* Our algorithm currently assumes the FLAC stream is currently sitting at the start. */ + if (drflac__seek_to_first_frame(pFlac) == DRFLAC_FALSE) { + return DRFLAC_FALSE; + } + + /* If we're close enough to the start, just move to the start and seek forward. */ + if (pcmFrameIndex < seekForwardThreshold) { + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFrameIndex) == pcmFrameIndex; + } + + /* + Our starting byte range is the byte position of the first FLAC frame and the approximate end of the file as if it were completely uncompressed. This ensures + the entire file is included, even though most of the time it'll exceed the end of the actual stream. This is OK as the frame searching logic will handle it. + */ + byteRangeLo = pFlac->firstFLACFramePosInBytes; + byteRangeHi = pFlac->firstFLACFramePosInBytes + (drflac_uint64)((drflac_int64)(pFlac->totalPCMFrameCount * pFlac->channels * pFlac->bitsPerSample)/8.0f); + + return drflac__seek_to_pcm_frame__binary_search_internal(pFlac, pcmFrameIndex, byteRangeLo, byteRangeHi); +} +#endif /* !DR_FLAC_NO_CRC */ + +static drflac_bool32 drflac__seek_to_pcm_frame__seek_table(drflac* pFlac, drflac_uint64 pcmFrameIndex) +{ + drflac_uint32 iClosestSeekpoint = 0; + drflac_bool32 isMidFrame = DRFLAC_FALSE; + drflac_uint64 runningPCMFrameCount; + drflac_uint32 iSeekpoint; + + + DRFLAC_ASSERT(pFlac != NULL); + + if (pFlac->pSeekpoints == NULL || pFlac->seekpointCount == 0) { + return DRFLAC_FALSE; + } + + /* Do not use the seektable if pcmFramIndex is not coverd by it. */ + if (pFlac->pSeekpoints[0].firstPCMFrame > pcmFrameIndex) { + return DRFLAC_FALSE; + } + + for (iSeekpoint = 0; iSeekpoint < pFlac->seekpointCount; ++iSeekpoint) { + if (pFlac->pSeekpoints[iSeekpoint].firstPCMFrame >= pcmFrameIndex) { + break; + } + + iClosestSeekpoint = iSeekpoint; + } + + /* There's been cases where the seek table contains only zeros. We need to do some basic validation on the closest seekpoint. */ + if (pFlac->pSeekpoints[iClosestSeekpoint].pcmFrameCount == 0 || pFlac->pSeekpoints[iClosestSeekpoint].pcmFrameCount > pFlac->maxBlockSizeInPCMFrames) { + return DRFLAC_FALSE; + } + if (pFlac->pSeekpoints[iClosestSeekpoint].firstPCMFrame > pFlac->totalPCMFrameCount && pFlac->totalPCMFrameCount > 0) { + return DRFLAC_FALSE; + } + +#if !defined(DR_FLAC_NO_CRC) + /* At this point we should know the closest seek point. We can use a binary search for this. We need to know the total sample count for this. */ + if (pFlac->totalPCMFrameCount > 0) { + drflac_uint64 byteRangeLo; + drflac_uint64 byteRangeHi; + + byteRangeHi = pFlac->firstFLACFramePosInBytes + (drflac_uint64)((drflac_int64)(pFlac->totalPCMFrameCount * pFlac->channels * pFlac->bitsPerSample)/8.0f); + byteRangeLo = pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset; + + /* + If our closest seek point is not the last one, we only need to search between it and the next one. The section below calculates an appropriate starting + value for byteRangeHi which will clamp it appropriately. + + Note that the next seekpoint must have an offset greater than the closest seekpoint because otherwise our binary search algorithm will break down. There + have been cases where a seektable consists of seek points where every byte offset is set to 0 which causes problems. If this happens we need to abort. + */ + if (iClosestSeekpoint < pFlac->seekpointCount-1) { + drflac_uint32 iNextSeekpoint = iClosestSeekpoint + 1; + + /* Basic validation on the seekpoints to ensure they're usable. */ + if (pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset >= pFlac->pSeekpoints[iNextSeekpoint].flacFrameOffset || pFlac->pSeekpoints[iNextSeekpoint].pcmFrameCount == 0) { + return DRFLAC_FALSE; /* The next seekpoint doesn't look right. The seek table cannot be trusted from here. Abort. */ + } + + if (pFlac->pSeekpoints[iNextSeekpoint].firstPCMFrame != (((drflac_uint64)0xFFFFFFFF << 32) | 0xFFFFFFFF)) { /* Make sure it's not a placeholder seekpoint. */ + byteRangeHi = pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iNextSeekpoint].flacFrameOffset - 1; /* byteRangeHi must be zero based. */ + } + } + + if (drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset)) { + if (drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &pFlac->currentPCMFrame, NULL); + + if (drflac__seek_to_pcm_frame__binary_search_internal(pFlac, pcmFrameIndex, byteRangeLo, byteRangeHi)) { + return DRFLAC_TRUE; + } + } + } + } +#endif /* !DR_FLAC_NO_CRC */ + + /* Getting here means we need to use a slower algorithm because the binary search method failed or cannot be used. */ + + /* + If we are seeking forward and the closest seekpoint is _before_ the current sample, we just seek forward from where we are. Otherwise we start seeking + from the seekpoint's first sample. + */ + if (pcmFrameIndex >= pFlac->currentPCMFrame && pFlac->pSeekpoints[iClosestSeekpoint].firstPCMFrame <= pFlac->currentPCMFrame) { + /* Optimized case. Just seek forward from where we are. */ + runningPCMFrameCount = pFlac->currentPCMFrame; + + /* The frame header for the first frame may not yet have been read. We need to do that if necessary. */ + if (pFlac->currentPCMFrame == 0 && pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + } else { + isMidFrame = DRFLAC_TRUE; + } + } else { + /* Slower case. Seek to the start of the seekpoint and then seek forward from there. */ + runningPCMFrameCount = pFlac->pSeekpoints[iClosestSeekpoint].firstPCMFrame; + + if (!drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset)) { + return DRFLAC_FALSE; + } + + /* Grab the frame the seekpoint is sitting on in preparation for the sample-exact seeking below. */ + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + } + + for (;;) { + drflac_uint64 pcmFrameCountInThisFLACFrame; + drflac_uint64 firstPCMFrameInFLACFrame = 0; + drflac_uint64 lastPCMFrameInFLACFrame = 0; + + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &firstPCMFrameInFLACFrame, &lastPCMFrameInFLACFrame); + + pcmFrameCountInThisFLACFrame = (lastPCMFrameInFLACFrame - firstPCMFrameInFLACFrame) + 1; + if (pcmFrameIndex < (runningPCMFrameCount + pcmFrameCountInThisFLACFrame)) { + /* + The sample should be in this frame. We need to fully decode it, but if it's an invalid frame (a CRC mismatch) we need to pretend + it never existed and keep iterating. + */ + drflac_uint64 pcmFramesToDecode = pcmFrameIndex - runningPCMFrameCount; + + if (!isMidFrame) { + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + /* The frame is valid. We just need to skip over some samples to ensure it's sample-exact. */ + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ + } else { + if (result == DRFLAC_CRC_MISMATCH) { + goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ + } else { + return DRFLAC_FALSE; + } + } + } else { + /* We started seeking mid-frame which means we need to skip the frame decoding part. */ + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; + } + } else { + /* + It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this + frame never existed and leave the running sample count untouched. + */ + if (!isMidFrame) { + drflac_result result = drflac__seek_to_next_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + runningPCMFrameCount += pcmFrameCountInThisFLACFrame; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ + } else { + return DRFLAC_FALSE; + } + } + } else { + /* + We started seeking mid-frame which means we need to seek by reading to the end of the frame instead of with + drflac__seek_to_next_flac_frame() which only works if the decoder is sitting on the byte just after the frame header. + */ + runningPCMFrameCount += pFlac->currentFLACFrame.pcmFramesRemaining; + pFlac->currentFLACFrame.pcmFramesRemaining = 0; + isMidFrame = DRFLAC_FALSE; + } + + /* If we are seeking to the end of the file and we've just hit it, we're done. */ + if (pcmFrameIndex == pFlac->totalPCMFrameCount && runningPCMFrameCount == pFlac->totalPCMFrameCount) { + return DRFLAC_TRUE; + } + } + + next_iteration: + /* Grab the next frame in preparation for the next iteration. */ + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + } +} + + +#ifndef DR_FLAC_NO_OGG +typedef struct +{ + drflac_uint8 capturePattern[4]; /* Should be "OggS" */ + drflac_uint8 structureVersion; /* Always 0. */ + drflac_uint8 headerType; + drflac_uint64 granulePosition; + drflac_uint32 serialNumber; + drflac_uint32 sequenceNumber; + drflac_uint32 checksum; + drflac_uint8 segmentCount; + drflac_uint8 segmentTable[255]; +} drflac_ogg_page_header; +#endif + +typedef struct +{ + drflac_read_proc onRead; + drflac_seek_proc onSeek; + drflac_meta_proc onMeta; + drflac_container container; + void* pUserData; + void* pUserDataMD; + drflac_uint32 sampleRate; + drflac_uint8 channels; + drflac_uint8 bitsPerSample; + drflac_uint64 totalPCMFrameCount; + drflac_uint16 maxBlockSizeInPCMFrames; + drflac_uint64 runningFilePos; + drflac_bool32 hasStreamInfoBlock; + drflac_bool32 hasMetadataBlocks; + drflac_bs bs; /* <-- A bit streamer is required for loading data during initialization. */ + drflac_frame_header firstFrameHeader; /* <-- The header of the first frame that was read during relaxed initalization. Only set if there is no STREAMINFO block. */ + +#ifndef DR_FLAC_NO_OGG + drflac_uint32 oggSerial; + drflac_uint64 oggFirstBytePos; + drflac_ogg_page_header oggBosHeader; +#endif +} drflac_init_info; + +static DRFLAC_INLINE void drflac__decode_block_header(drflac_uint32 blockHeader, drflac_uint8* isLastBlock, drflac_uint8* blockType, drflac_uint32* blockSize) +{ + blockHeader = drflac__be2host_32(blockHeader); + *isLastBlock = (drflac_uint8)((blockHeader & 0x80000000UL) >> 31); + *blockType = (drflac_uint8)((blockHeader & 0x7F000000UL) >> 24); + *blockSize = (blockHeader & 0x00FFFFFFUL); +} + +static DRFLAC_INLINE drflac_bool32 drflac__read_and_decode_block_header(drflac_read_proc onRead, void* pUserData, drflac_uint8* isLastBlock, drflac_uint8* blockType, drflac_uint32* blockSize) +{ + drflac_uint32 blockHeader; + + *blockSize = 0; + if (onRead(pUserData, &blockHeader, 4) != 4) { + return DRFLAC_FALSE; + } + + drflac__decode_block_header(blockHeader, isLastBlock, blockType, blockSize); + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__read_streaminfo(drflac_read_proc onRead, void* pUserData, drflac_streaminfo* pStreamInfo) +{ + drflac_uint32 blockSizes; + drflac_uint64 frameSizes = 0; + drflac_uint64 importantProps; + drflac_uint8 md5[16]; + + /* min/max block size. */ + if (onRead(pUserData, &blockSizes, 4) != 4) { + return DRFLAC_FALSE; + } + + /* min/max frame size. */ + if (onRead(pUserData, &frameSizes, 6) != 6) { + return DRFLAC_FALSE; + } + + /* Sample rate, channels, bits per sample and total sample count. */ + if (onRead(pUserData, &importantProps, 8) != 8) { + return DRFLAC_FALSE; + } + + /* MD5 */ + if (onRead(pUserData, md5, sizeof(md5)) != sizeof(md5)) { + return DRFLAC_FALSE; + } + + blockSizes = drflac__be2host_32(blockSizes); + frameSizes = drflac__be2host_64(frameSizes); + importantProps = drflac__be2host_64(importantProps); + + pStreamInfo->minBlockSizeInPCMFrames = (drflac_uint16)((blockSizes & 0xFFFF0000) >> 16); + pStreamInfo->maxBlockSizeInPCMFrames = (drflac_uint16) (blockSizes & 0x0000FFFF); + pStreamInfo->minFrameSizeInPCMFrames = (drflac_uint32)((frameSizes & (((drflac_uint64)0x00FFFFFF << 16) << 24)) >> 40); + pStreamInfo->maxFrameSizeInPCMFrames = (drflac_uint32)((frameSizes & (((drflac_uint64)0x00FFFFFF << 16) << 0)) >> 16); + pStreamInfo->sampleRate = (drflac_uint32)((importantProps & (((drflac_uint64)0x000FFFFF << 16) << 28)) >> 44); + pStreamInfo->channels = (drflac_uint8 )((importantProps & (((drflac_uint64)0x0000000E << 16) << 24)) >> 41) + 1; + pStreamInfo->bitsPerSample = (drflac_uint8 )((importantProps & (((drflac_uint64)0x0000001F << 16) << 20)) >> 36) + 1; + pStreamInfo->totalPCMFrameCount = ((importantProps & ((((drflac_uint64)0x0000000F << 16) << 16) | 0xFFFFFFFF))); + DRFLAC_COPY_MEMORY(pStreamInfo->md5, md5, sizeof(md5)); + + return DRFLAC_TRUE; +} + + +static void* drflac__malloc_default(size_t sz, void* pUserData) +{ + (void)pUserData; + return DRFLAC_MALLOC(sz); +} + +static void* drflac__realloc_default(void* p, size_t sz, void* pUserData) +{ + (void)pUserData; + return DRFLAC_REALLOC(p, sz); +} + +static void drflac__free_default(void* p, void* pUserData) +{ + (void)pUserData; + DRFLAC_FREE(p); +} + + +static void* drflac__malloc_from_callbacks(size_t sz, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } + + if (pAllocationCallbacks->onMalloc != NULL) { + return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); + } + + /* Try using realloc(). */ + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); + } + + return NULL; +} + +static void* drflac__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } + + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); + } + + /* Try emulating realloc() in terms of malloc()/free(). */ + if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { + void* p2; + + p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); + if (p2 == NULL) { + return NULL; + } + + if (p != NULL) { + DRFLAC_COPY_MEMORY(p2, p, szOld); + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } + + return p2; + } + + return NULL; +} + +static void drflac__free_from_callbacks(void* p, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + if (p == NULL || pAllocationCallbacks == NULL) { + return; + } + + if (pAllocationCallbacks->onFree != NULL) { + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } +} + + +static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_uint64* pFirstFramePos, drflac_uint64* pSeektablePos, drflac_uint32* pSeekpointCount, drflac_allocation_callbacks* pAllocationCallbacks) +{ + /* + We want to keep track of the byte position in the stream of the seektable. At the time of calling this function we know that + we'll be sitting on byte 42. + */ + drflac_uint64 runningFilePos = 42; + drflac_uint64 seektablePos = 0; + drflac_uint32 seektableSize = 0; + + for (;;) { + drflac_metadata metadata; + drflac_uint8 isLastBlock = 0; + drflac_uint8 blockType = 0; + drflac_uint32 blockSize; + if (drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize) == DRFLAC_FALSE) { + return DRFLAC_FALSE; + } + runningFilePos += 4; + + metadata.type = blockType; + metadata.pRawData = NULL; + metadata.rawDataSize = 0; + + switch (blockType) + { + case DRFLAC_METADATA_BLOCK_TYPE_APPLICATION: + { + if (blockSize < 4) { + return DRFLAC_FALSE; + } + + if (onMeta) { + void* pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + metadata.data.application.id = drflac__be2host_32(*(drflac_uint32*)pRawData); + metadata.data.application.pData = (const void*)((drflac_uint8*)pRawData + sizeof(drflac_uint32)); + metadata.data.application.dataSize = blockSize - sizeof(drflac_uint32); + onMeta(pUserDataMD, &metadata); + + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + } + } break; + + case DRFLAC_METADATA_BLOCK_TYPE_SEEKTABLE: + { + seektablePos = runningFilePos; + seektableSize = blockSize; + + if (onMeta) { + drflac_uint32 seekpointCount; + drflac_uint32 iSeekpoint; + void* pRawData; + + seekpointCount = blockSize/DRFLAC_SEEKPOINT_SIZE_IN_BYTES; + + pRawData = drflac__malloc_from_callbacks(seekpointCount * sizeof(drflac_seekpoint), pAllocationCallbacks); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + /* We need to read seekpoint by seekpoint and do some processing. */ + for (iSeekpoint = 0; iSeekpoint < seekpointCount; ++iSeekpoint) { + drflac_seekpoint* pSeekpoint = (drflac_seekpoint*)pRawData + iSeekpoint; + + if (onRead(pUserData, pSeekpoint, DRFLAC_SEEKPOINT_SIZE_IN_BYTES) != DRFLAC_SEEKPOINT_SIZE_IN_BYTES) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + /* Endian swap. */ + pSeekpoint->firstPCMFrame = drflac__be2host_64(pSeekpoint->firstPCMFrame); + pSeekpoint->flacFrameOffset = drflac__be2host_64(pSeekpoint->flacFrameOffset); + pSeekpoint->pcmFrameCount = drflac__be2host_16(pSeekpoint->pcmFrameCount); + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + metadata.data.seektable.seekpointCount = seekpointCount; + metadata.data.seektable.pSeekpoints = (const drflac_seekpoint*)pRawData; + + onMeta(pUserDataMD, &metadata); + + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + } + } break; + + case DRFLAC_METADATA_BLOCK_TYPE_VORBIS_COMMENT: + { + if (blockSize < 8) { + return DRFLAC_FALSE; + } + + if (onMeta) { + void* pRawData; + const char* pRunningData; + const char* pRunningDataEnd; + drflac_uint32 i; + + pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + + pRunningData = (const char*)pRawData; + pRunningDataEnd = (const char*)pRawData + blockSize; + + metadata.data.vorbis_comment.vendorLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + + /* Need space for the rest of the block */ + if ((pRunningDataEnd - pRunningData) - 4 < (drflac_int64)metadata.data.vorbis_comment.vendorLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + metadata.data.vorbis_comment.vendor = pRunningData; pRunningData += metadata.data.vorbis_comment.vendorLength; + metadata.data.vorbis_comment.commentCount = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + + /* Need space for 'commentCount' comments after the block, which at minimum is a drflac_uint32 per comment */ + if ((pRunningDataEnd - pRunningData) / sizeof(drflac_uint32) < metadata.data.vorbis_comment.commentCount) { /* <-- Note the order of operations to avoid overflow to a valid value */ + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + metadata.data.vorbis_comment.pComments = pRunningData; + + /* Check that the comments section is valid before passing it to the callback */ + for (i = 0; i < metadata.data.vorbis_comment.commentCount; ++i) { + drflac_uint32 commentLength; + + if (pRunningDataEnd - pRunningData < 4) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + commentLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + if (pRunningDataEnd - pRunningData < (drflac_int64)commentLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + pRunningData += commentLength; + } + + onMeta(pUserDataMD, &metadata); + + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + } + } break; + + case DRFLAC_METADATA_BLOCK_TYPE_CUESHEET: + { + if (blockSize < 396) { + return DRFLAC_FALSE; + } + + if (onMeta) { + void* pRawData; + const char* pRunningData; + const char* pRunningDataEnd; + size_t bufferSize; + drflac_uint8 iTrack; + drflac_uint8 iIndex; + void* pTrackData; + + /* + This needs to be loaded in two passes. The first pass is used to calculate the size of the memory allocation + we need for storing the necessary data. The second pass will fill that buffer with usable data. + */ + pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + + pRunningData = (const char*)pRawData; + pRunningDataEnd = (const char*)pRawData + blockSize; + + DRFLAC_COPY_MEMORY(metadata.data.cuesheet.catalog, pRunningData, 128); pRunningData += 128; + metadata.data.cuesheet.leadInSampleCount = drflac__be2host_64(*(const drflac_uint64*)pRunningData); pRunningData += 8; + metadata.data.cuesheet.isCD = (pRunningData[0] & 0x80) != 0; pRunningData += 259; + metadata.data.cuesheet.trackCount = pRunningData[0]; pRunningData += 1; + metadata.data.cuesheet.pTrackData = NULL; /* Will be filled later. */ + + /* Pass 1: Calculate the size of the buffer for the track data. */ + { + const char* pRunningDataSaved = pRunningData; /* Will be restored at the end in preparation for the second pass. */ + + bufferSize = metadata.data.cuesheet.trackCount * DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES; + + for (iTrack = 0; iTrack < metadata.data.cuesheet.trackCount; ++iTrack) { + drflac_uint8 indexCount; + drflac_uint32 indexPointSize; + + if (pRunningDataEnd - pRunningData < DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + /* Skip to the index point count */ + pRunningData += 35; + + indexCount = pRunningData[0]; + pRunningData += 1; + + bufferSize += indexCount * sizeof(drflac_cuesheet_track_index); + + /* Quick validation check. */ + indexPointSize = indexCount * DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES; + if (pRunningDataEnd - pRunningData < (drflac_int64)indexPointSize) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + pRunningData += indexPointSize; + } + + pRunningData = pRunningDataSaved; + } + + /* Pass 2: Allocate a buffer and fill the data. Validation was done in the step above so can be skipped. */ + { + char* pRunningTrackData; + + pTrackData = drflac__malloc_from_callbacks(bufferSize, pAllocationCallbacks); + if (pTrackData == NULL) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + pRunningTrackData = (char*)pTrackData; + + for (iTrack = 0; iTrack < metadata.data.cuesheet.trackCount; ++iTrack) { + drflac_uint8 indexCount; + + DRFLAC_COPY_MEMORY(pRunningTrackData, pRunningData, DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES); + pRunningData += DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES-1; /* Skip forward, but not beyond the last byte in the CUESHEET_TRACK block which is the index count. */ + pRunningTrackData += DRFLAC_CUESHEET_TRACK_SIZE_IN_BYTES-1; + + /* Grab the index count for the next part. */ + indexCount = pRunningData[0]; + pRunningData += 1; + pRunningTrackData += 1; + + /* Extract each track index. */ + for (iIndex = 0; iIndex < indexCount; ++iIndex) { + drflac_cuesheet_track_index* pTrackIndex = (drflac_cuesheet_track_index*)pRunningTrackData; + + DRFLAC_COPY_MEMORY(pRunningTrackData, pRunningData, DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES); + pRunningData += DRFLAC_CUESHEET_TRACK_INDEX_SIZE_IN_BYTES; + pRunningTrackData += sizeof(drflac_cuesheet_track_index); + + pTrackIndex->offset = drflac__be2host_64(pTrackIndex->offset); + } + } + + metadata.data.cuesheet.pTrackData = pTrackData; + } + + /* The original data is no longer needed. */ + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + pRawData = NULL; + + onMeta(pUserDataMD, &metadata); + + drflac__free_from_callbacks(pTrackData, pAllocationCallbacks); + pTrackData = NULL; + } + } break; + + case DRFLAC_METADATA_BLOCK_TYPE_PICTURE: + { + if (blockSize < 32) { + return DRFLAC_FALSE; + } + + if (onMeta) { + void* pRawData; + const char* pRunningData; + const char* pRunningDataEnd; + + pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + + pRunningData = (const char*)pRawData; + pRunningDataEnd = (const char*)pRawData + blockSize; + + metadata.data.picture.type = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.mimeLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + + /* Need space for the rest of the block */ + if ((pRunningDataEnd - pRunningData) - 24 < (drflac_int64)metadata.data.picture.mimeLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + metadata.data.picture.mime = pRunningData; pRunningData += metadata.data.picture.mimeLength; + metadata.data.picture.descriptionLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + + /* Need space for the rest of the block */ + if ((pRunningDataEnd - pRunningData) - 20 < (drflac_int64)metadata.data.picture.descriptionLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + metadata.data.picture.description = pRunningData; pRunningData += metadata.data.picture.descriptionLength; + metadata.data.picture.width = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.height = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.colorDepth = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.indexColorCount = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.pictureDataSize = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.pPictureData = (const drflac_uint8*)pRunningData; + + /* Need space for the picture after the block */ + if (pRunningDataEnd - pRunningData < (drflac_int64)metadata.data.picture.pictureDataSize) { /* <-- Note the order of operations to avoid overflow to a valid value */ + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + onMeta(pUserDataMD, &metadata); + + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + } + } break; + + case DRFLAC_METADATA_BLOCK_TYPE_PADDING: + { + if (onMeta) { + metadata.data.padding.unused = 0; + + /* Padding doesn't have anything meaningful in it, so just skip over it, but make sure the caller is aware of it by firing the callback. */ + if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { + isLastBlock = DRFLAC_TRUE; /* An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. */ + } else { + onMeta(pUserDataMD, &metadata); + } + } + } break; + + case DRFLAC_METADATA_BLOCK_TYPE_INVALID: + { + /* Invalid chunk. Just skip over this one. */ + if (onMeta) { + if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { + isLastBlock = DRFLAC_TRUE; /* An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. */ + } + } + } break; + + default: + { + /* + It's an unknown chunk, but not necessarily invalid. There's a chance more metadata blocks might be defined later on, so we + can at the very least report the chunk to the application and let it look at the raw data. + */ + if (onMeta) { + void* pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + return DRFLAC_FALSE; + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + onMeta(pUserDataMD, &metadata); + + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); + } + } break; + } + + /* If we're not handling metadata, just skip over the block. If we are, it will have been handled earlier in the switch statement above. */ + if (onMeta == NULL && blockSize > 0) { + if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { + isLastBlock = DRFLAC_TRUE; + } + } + + runningFilePos += blockSize; + if (isLastBlock) { + break; + } + } + + *pSeektablePos = seektablePos; + *pSeekpointCount = seektableSize / DRFLAC_SEEKPOINT_SIZE_IN_BYTES; + *pFirstFramePos = runningFilePos; + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__init_private__native(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_bool32 relaxed) +{ + /* Pre Condition: The bit stream should be sitting just past the 4-byte id header. */ + + drflac_uint8 isLastBlock; + drflac_uint8 blockType; + drflac_uint32 blockSize; + + (void)onSeek; + + pInit->container = drflac_container_native; + + /* The first metadata block should be the STREAMINFO block. */ + if (!drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize)) { + return DRFLAC_FALSE; + } + + if (blockType != DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO || blockSize != 34) { + if (!relaxed) { + /* We're opening in strict mode and the first block is not the STREAMINFO block. Error. */ + return DRFLAC_FALSE; + } else { + /* + Relaxed mode. To open from here we need to just find the first frame and set the sample rate, etc. to whatever is defined + for that frame. + */ + pInit->hasStreamInfoBlock = DRFLAC_FALSE; + pInit->hasMetadataBlocks = DRFLAC_FALSE; + + if (!drflac__read_next_flac_frame_header(&pInit->bs, 0, &pInit->firstFrameHeader)) { + return DRFLAC_FALSE; /* Couldn't find a frame. */ + } + + if (pInit->firstFrameHeader.bitsPerSample == 0) { + return DRFLAC_FALSE; /* Failed to initialize because the first frame depends on the STREAMINFO block, which does not exist. */ + } + + pInit->sampleRate = pInit->firstFrameHeader.sampleRate; + pInit->channels = drflac__get_channel_count_from_channel_assignment(pInit->firstFrameHeader.channelAssignment); + pInit->bitsPerSample = pInit->firstFrameHeader.bitsPerSample; + pInit->maxBlockSizeInPCMFrames = 65535; /* <-- See notes here: https://xiph.org/flac/format.html#metadata_block_streaminfo */ + return DRFLAC_TRUE; + } + } else { + drflac_streaminfo streaminfo; + if (!drflac__read_streaminfo(onRead, pUserData, &streaminfo)) { + return DRFLAC_FALSE; + } + + pInit->hasStreamInfoBlock = DRFLAC_TRUE; + pInit->sampleRate = streaminfo.sampleRate; + pInit->channels = streaminfo.channels; + pInit->bitsPerSample = streaminfo.bitsPerSample; + pInit->totalPCMFrameCount = streaminfo.totalPCMFrameCount; + pInit->maxBlockSizeInPCMFrames = streaminfo.maxBlockSizeInPCMFrames; /* Don't care about the min block size - only the max (used for determining the size of the memory allocation). */ + pInit->hasMetadataBlocks = !isLastBlock; + + if (onMeta) { + drflac_metadata metadata; + metadata.type = DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO; + metadata.pRawData = NULL; + metadata.rawDataSize = 0; + metadata.data.streaminfo = streaminfo; + onMeta(pUserDataMD, &metadata); + } + + return DRFLAC_TRUE; + } +} + +#ifndef DR_FLAC_NO_OGG +#define DRFLAC_OGG_MAX_PAGE_SIZE 65307 +#define DRFLAC_OGG_CAPTURE_PATTERN_CRC32 1605413199 /* CRC-32 of "OggS". */ + +typedef enum +{ + drflac_ogg_recover_on_crc_mismatch, + drflac_ogg_fail_on_crc_mismatch +} drflac_ogg_crc_mismatch_recovery; + +#ifndef DR_FLAC_NO_CRC +static drflac_uint32 drflac__crc32_table[] = { + 0x00000000L, 0x04C11DB7L, 0x09823B6EL, 0x0D4326D9L, + 0x130476DCL, 0x17C56B6BL, 0x1A864DB2L, 0x1E475005L, + 0x2608EDB8L, 0x22C9F00FL, 0x2F8AD6D6L, 0x2B4BCB61L, + 0x350C9B64L, 0x31CD86D3L, 0x3C8EA00AL, 0x384FBDBDL, + 0x4C11DB70L, 0x48D0C6C7L, 0x4593E01EL, 0x4152FDA9L, + 0x5F15ADACL, 0x5BD4B01BL, 0x569796C2L, 0x52568B75L, + 0x6A1936C8L, 0x6ED82B7FL, 0x639B0DA6L, 0x675A1011L, + 0x791D4014L, 0x7DDC5DA3L, 0x709F7B7AL, 0x745E66CDL, + 0x9823B6E0L, 0x9CE2AB57L, 0x91A18D8EL, 0x95609039L, + 0x8B27C03CL, 0x8FE6DD8BL, 0x82A5FB52L, 0x8664E6E5L, + 0xBE2B5B58L, 0xBAEA46EFL, 0xB7A96036L, 0xB3687D81L, + 0xAD2F2D84L, 0xA9EE3033L, 0xA4AD16EAL, 0xA06C0B5DL, + 0xD4326D90L, 0xD0F37027L, 0xDDB056FEL, 0xD9714B49L, + 0xC7361B4CL, 0xC3F706FBL, 0xCEB42022L, 0xCA753D95L, + 0xF23A8028L, 0xF6FB9D9FL, 0xFBB8BB46L, 0xFF79A6F1L, + 0xE13EF6F4L, 0xE5FFEB43L, 0xE8BCCD9AL, 0xEC7DD02DL, + 0x34867077L, 0x30476DC0L, 0x3D044B19L, 0x39C556AEL, + 0x278206ABL, 0x23431B1CL, 0x2E003DC5L, 0x2AC12072L, + 0x128E9DCFL, 0x164F8078L, 0x1B0CA6A1L, 0x1FCDBB16L, + 0x018AEB13L, 0x054BF6A4L, 0x0808D07DL, 0x0CC9CDCAL, + 0x7897AB07L, 0x7C56B6B0L, 0x71159069L, 0x75D48DDEL, + 0x6B93DDDBL, 0x6F52C06CL, 0x6211E6B5L, 0x66D0FB02L, + 0x5E9F46BFL, 0x5A5E5B08L, 0x571D7DD1L, 0x53DC6066L, + 0x4D9B3063L, 0x495A2DD4L, 0x44190B0DL, 0x40D816BAL, + 0xACA5C697L, 0xA864DB20L, 0xA527FDF9L, 0xA1E6E04EL, + 0xBFA1B04BL, 0xBB60ADFCL, 0xB6238B25L, 0xB2E29692L, + 0x8AAD2B2FL, 0x8E6C3698L, 0x832F1041L, 0x87EE0DF6L, + 0x99A95DF3L, 0x9D684044L, 0x902B669DL, 0x94EA7B2AL, + 0xE0B41DE7L, 0xE4750050L, 0xE9362689L, 0xEDF73B3EL, + 0xF3B06B3BL, 0xF771768CL, 0xFA325055L, 0xFEF34DE2L, + 0xC6BCF05FL, 0xC27DEDE8L, 0xCF3ECB31L, 0xCBFFD686L, + 0xD5B88683L, 0xD1799B34L, 0xDC3ABDEDL, 0xD8FBA05AL, + 0x690CE0EEL, 0x6DCDFD59L, 0x608EDB80L, 0x644FC637L, + 0x7A089632L, 0x7EC98B85L, 0x738AAD5CL, 0x774BB0EBL, + 0x4F040D56L, 0x4BC510E1L, 0x46863638L, 0x42472B8FL, + 0x5C007B8AL, 0x58C1663DL, 0x558240E4L, 0x51435D53L, + 0x251D3B9EL, 0x21DC2629L, 0x2C9F00F0L, 0x285E1D47L, + 0x36194D42L, 0x32D850F5L, 0x3F9B762CL, 0x3B5A6B9BL, + 0x0315D626L, 0x07D4CB91L, 0x0A97ED48L, 0x0E56F0FFL, + 0x1011A0FAL, 0x14D0BD4DL, 0x19939B94L, 0x1D528623L, + 0xF12F560EL, 0xF5EE4BB9L, 0xF8AD6D60L, 0xFC6C70D7L, + 0xE22B20D2L, 0xE6EA3D65L, 0xEBA91BBCL, 0xEF68060BL, + 0xD727BBB6L, 0xD3E6A601L, 0xDEA580D8L, 0xDA649D6FL, + 0xC423CD6AL, 0xC0E2D0DDL, 0xCDA1F604L, 0xC960EBB3L, + 0xBD3E8D7EL, 0xB9FF90C9L, 0xB4BCB610L, 0xB07DABA7L, + 0xAE3AFBA2L, 0xAAFBE615L, 0xA7B8C0CCL, 0xA379DD7BL, + 0x9B3660C6L, 0x9FF77D71L, 0x92B45BA8L, 0x9675461FL, + 0x8832161AL, 0x8CF30BADL, 0x81B02D74L, 0x857130C3L, + 0x5D8A9099L, 0x594B8D2EL, 0x5408ABF7L, 0x50C9B640L, + 0x4E8EE645L, 0x4A4FFBF2L, 0x470CDD2BL, 0x43CDC09CL, + 0x7B827D21L, 0x7F436096L, 0x7200464FL, 0x76C15BF8L, + 0x68860BFDL, 0x6C47164AL, 0x61043093L, 0x65C52D24L, + 0x119B4BE9L, 0x155A565EL, 0x18197087L, 0x1CD86D30L, + 0x029F3D35L, 0x065E2082L, 0x0B1D065BL, 0x0FDC1BECL, + 0x3793A651L, 0x3352BBE6L, 0x3E119D3FL, 0x3AD08088L, + 0x2497D08DL, 0x2056CD3AL, 0x2D15EBE3L, 0x29D4F654L, + 0xC5A92679L, 0xC1683BCEL, 0xCC2B1D17L, 0xC8EA00A0L, + 0xD6AD50A5L, 0xD26C4D12L, 0xDF2F6BCBL, 0xDBEE767CL, + 0xE3A1CBC1L, 0xE760D676L, 0xEA23F0AFL, 0xEEE2ED18L, + 0xF0A5BD1DL, 0xF464A0AAL, 0xF9278673L, 0xFDE69BC4L, + 0x89B8FD09L, 0x8D79E0BEL, 0x803AC667L, 0x84FBDBD0L, + 0x9ABC8BD5L, 0x9E7D9662L, 0x933EB0BBL, 0x97FFAD0CL, + 0xAFB010B1L, 0xAB710D06L, 0xA6322BDFL, 0xA2F33668L, + 0xBCB4666DL, 0xB8757BDAL, 0xB5365D03L, 0xB1F740B4L +}; +#endif + +static DRFLAC_INLINE drflac_uint32 drflac_crc32_byte(drflac_uint32 crc32, drflac_uint8 data) +{ +#ifndef DR_FLAC_NO_CRC + return (crc32 << 8) ^ drflac__crc32_table[(drflac_uint8)((crc32 >> 24) & 0xFF) ^ data]; +#else + (void)data; + return crc32; +#endif +} + +#if 0 +static DRFLAC_INLINE drflac_uint32 drflac_crc32_uint32(drflac_uint32 crc32, drflac_uint32 data) +{ + crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 24) & 0xFF)); + crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 16) & 0xFF)); + crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 8) & 0xFF)); + crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 0) & 0xFF)); + return crc32; +} + +static DRFLAC_INLINE drflac_uint32 drflac_crc32_uint64(drflac_uint32 crc32, drflac_uint64 data) +{ + crc32 = drflac_crc32_uint32(crc32, (drflac_uint32)((data >> 32) & 0xFFFFFFFF)); + crc32 = drflac_crc32_uint32(crc32, (drflac_uint32)((data >> 0) & 0xFFFFFFFF)); + return crc32; +} +#endif + +static DRFLAC_INLINE drflac_uint32 drflac_crc32_buffer(drflac_uint32 crc32, drflac_uint8* pData, drflac_uint32 dataSize) +{ + /* This can be optimized. */ + drflac_uint32 i; + for (i = 0; i < dataSize; ++i) { + crc32 = drflac_crc32_byte(crc32, pData[i]); + } + return crc32; +} + + +static DRFLAC_INLINE drflac_bool32 drflac_ogg__is_capture_pattern(drflac_uint8 pattern[4]) +{ + return pattern[0] == 'O' && pattern[1] == 'g' && pattern[2] == 'g' && pattern[3] == 'S'; +} + +static DRFLAC_INLINE drflac_uint32 drflac_ogg__get_page_header_size(drflac_ogg_page_header* pHeader) +{ + return 27 + pHeader->segmentCount; +} + +static DRFLAC_INLINE drflac_uint32 drflac_ogg__get_page_body_size(drflac_ogg_page_header* pHeader) +{ + drflac_uint32 pageBodySize = 0; + int i; + + for (i = 0; i < pHeader->segmentCount; ++i) { + pageBodySize += pHeader->segmentTable[i]; + } + + return pageBodySize; +} + +static drflac_result drflac_ogg__read_page_header_after_capture_pattern(drflac_read_proc onRead, void* pUserData, drflac_ogg_page_header* pHeader, drflac_uint32* pBytesRead, drflac_uint32* pCRC32) +{ + drflac_uint8 data[23]; + drflac_uint32 i; + + DRFLAC_ASSERT(*pCRC32 == DRFLAC_OGG_CAPTURE_PATTERN_CRC32); + + if (onRead(pUserData, data, 23) != 23) { + return DRFLAC_AT_END; + } + *pBytesRead += 23; + + /* + It's not actually used, but set the capture pattern to 'OggS' for completeness. Not doing this will cause static analysers to complain about + us trying to access uninitialized data. We could alternatively just comment out this member of the drflac_ogg_page_header structure, but I + like to have it map to the structure of the underlying data. + */ + pHeader->capturePattern[0] = 'O'; + pHeader->capturePattern[1] = 'g'; + pHeader->capturePattern[2] = 'g'; + pHeader->capturePattern[3] = 'S'; + + pHeader->structureVersion = data[0]; + pHeader->headerType = data[1]; + DRFLAC_COPY_MEMORY(&pHeader->granulePosition, &data[ 2], 8); + DRFLAC_COPY_MEMORY(&pHeader->serialNumber, &data[10], 4); + DRFLAC_COPY_MEMORY(&pHeader->sequenceNumber, &data[14], 4); + DRFLAC_COPY_MEMORY(&pHeader->checksum, &data[18], 4); + pHeader->segmentCount = data[22]; + + /* Calculate the CRC. Note that for the calculation the checksum part of the page needs to be set to 0. */ + data[18] = 0; + data[19] = 0; + data[20] = 0; + data[21] = 0; + + for (i = 0; i < 23; ++i) { + *pCRC32 = drflac_crc32_byte(*pCRC32, data[i]); + } + + + if (onRead(pUserData, pHeader->segmentTable, pHeader->segmentCount) != pHeader->segmentCount) { + return DRFLAC_AT_END; + } + *pBytesRead += pHeader->segmentCount; + + for (i = 0; i < pHeader->segmentCount; ++i) { + *pCRC32 = drflac_crc32_byte(*pCRC32, pHeader->segmentTable[i]); + } + + return DRFLAC_SUCCESS; +} + +static drflac_result drflac_ogg__read_page_header(drflac_read_proc onRead, void* pUserData, drflac_ogg_page_header* pHeader, drflac_uint32* pBytesRead, drflac_uint32* pCRC32) +{ + drflac_uint8 id[4]; + + *pBytesRead = 0; + + if (onRead(pUserData, id, 4) != 4) { + return DRFLAC_AT_END; + } + *pBytesRead += 4; + + /* We need to read byte-by-byte until we find the OggS capture pattern. */ + for (;;) { + if (drflac_ogg__is_capture_pattern(id)) { + drflac_result result; + + *pCRC32 = DRFLAC_OGG_CAPTURE_PATTERN_CRC32; + + result = drflac_ogg__read_page_header_after_capture_pattern(onRead, pUserData, pHeader, pBytesRead, pCRC32); + if (result == DRFLAC_SUCCESS) { + return DRFLAC_SUCCESS; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + continue; + } else { + return result; + } + } + } else { + /* The first 4 bytes did not equal the capture pattern. Read the next byte and try again. */ + id[0] = id[1]; + id[1] = id[2]; + id[2] = id[3]; + if (onRead(pUserData, &id[3], 1) != 1) { + return DRFLAC_AT_END; + } + *pBytesRead += 1; + } + } +} + + +/* +The main part of the Ogg encapsulation is the conversion from the physical Ogg bitstream to the native FLAC bitstream. It works +in three general stages: Ogg Physical Bitstream -> Ogg/FLAC Logical Bitstream -> FLAC Native Bitstream. dr_flac is designed +in such a way that the core sections assume everything is delivered in native format. Therefore, for each encapsulation type +dr_flac is supporting there needs to be a layer sitting on top of the onRead and onSeek callbacks that ensures the bits read from +the physical Ogg bitstream are converted and delivered in native FLAC format. +*/ +typedef struct +{ + drflac_read_proc onRead; /* The original onRead callback from drflac_open() and family. */ + drflac_seek_proc onSeek; /* The original onSeek callback from drflac_open() and family. */ + void* pUserData; /* The user data passed on onRead and onSeek. This is the user data that was passed on drflac_open() and family. */ + drflac_uint64 currentBytePos; /* The position of the byte we are sitting on in the physical byte stream. Used for efficient seeking. */ + drflac_uint64 firstBytePos; /* The position of the first byte in the physical bitstream. Points to the start of the "OggS" identifier of the FLAC bos page. */ + drflac_uint32 serialNumber; /* The serial number of the FLAC audio pages. This is determined by the initial header page that was read during initialization. */ + drflac_ogg_page_header bosPageHeader; /* Used for seeking. */ + drflac_ogg_page_header currentPageHeader; + drflac_uint32 bytesRemainingInPage; + drflac_uint32 pageDataSize; + drflac_uint8 pageData[DRFLAC_OGG_MAX_PAGE_SIZE]; +} drflac_oggbs; /* oggbs = Ogg Bitstream */ + +static size_t drflac_oggbs__read_physical(drflac_oggbs* oggbs, void* bufferOut, size_t bytesToRead) +{ + size_t bytesActuallyRead = oggbs->onRead(oggbs->pUserData, bufferOut, bytesToRead); + oggbs->currentBytePos += bytesActuallyRead; + + return bytesActuallyRead; +} + +static drflac_bool32 drflac_oggbs__seek_physical(drflac_oggbs* oggbs, drflac_uint64 offset, drflac_seek_origin origin) +{ + if (origin == drflac_seek_origin_start) { + if (offset <= 0x7FFFFFFF) { + if (!oggbs->onSeek(oggbs->pUserData, (int)offset, drflac_seek_origin_start)) { + return DRFLAC_FALSE; + } + oggbs->currentBytePos = offset; + + return DRFLAC_TRUE; + } else { + if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, drflac_seek_origin_start)) { + return DRFLAC_FALSE; + } + oggbs->currentBytePos = offset; + + return drflac_oggbs__seek_physical(oggbs, offset - 0x7FFFFFFF, drflac_seek_origin_current); + } + } else { + while (offset > 0x7FFFFFFF) { + if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + oggbs->currentBytePos += 0x7FFFFFFF; + offset -= 0x7FFFFFFF; + } + + if (!oggbs->onSeek(oggbs->pUserData, (int)offset, drflac_seek_origin_current)) { /* <-- Safe cast thanks to the loop above. */ + return DRFLAC_FALSE; + } + oggbs->currentBytePos += offset; + + return DRFLAC_TRUE; + } +} + +static drflac_bool32 drflac_oggbs__goto_next_page(drflac_oggbs* oggbs, drflac_ogg_crc_mismatch_recovery recoveryMethod) +{ + drflac_ogg_page_header header; + for (;;) { + drflac_uint32 crc32 = 0; + drflac_uint32 bytesRead; + drflac_uint32 pageBodySize; +#ifndef DR_FLAC_NO_CRC + drflac_uint32 actualCRC32; +#endif + + if (drflac_ogg__read_page_header(oggbs->onRead, oggbs->pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { + return DRFLAC_FALSE; + } + oggbs->currentBytePos += bytesRead; + + pageBodySize = drflac_ogg__get_page_body_size(&header); + if (pageBodySize > DRFLAC_OGG_MAX_PAGE_SIZE) { + continue; /* Invalid page size. Assume it's corrupted and just move to the next page. */ + } + + if (header.serialNumber != oggbs->serialNumber) { + /* It's not a FLAC page. Skip it. */ + if (pageBodySize > 0 && !drflac_oggbs__seek_physical(oggbs, pageBodySize, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + continue; + } + + + /* We need to read the entire page and then do a CRC check on it. If there's a CRC mismatch we need to skip this page. */ + if (drflac_oggbs__read_physical(oggbs, oggbs->pageData, pageBodySize) != pageBodySize) { + return DRFLAC_FALSE; + } + oggbs->pageDataSize = pageBodySize; + +#ifndef DR_FLAC_NO_CRC + actualCRC32 = drflac_crc32_buffer(crc32, oggbs->pageData, oggbs->pageDataSize); + if (actualCRC32 != header.checksum) { + if (recoveryMethod == drflac_ogg_recover_on_crc_mismatch) { + continue; /* CRC mismatch. Skip this page. */ + } else { + /* + Even though we are failing on a CRC mismatch, we still want our stream to be in a good state. Therefore we + go to the next valid page to ensure we're in a good state, but return false to let the caller know that the + seek did not fully complete. + */ + drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch); + return DRFLAC_FALSE; + } + } +#else + (void)recoveryMethod; /* <-- Silence a warning. */ +#endif + + oggbs->currentPageHeader = header; + oggbs->bytesRemainingInPage = pageBodySize; + return DRFLAC_TRUE; + } +} + +/* Function below is unused at the moment, but I might be re-adding it later. */ +#if 0 +static drflac_uint8 drflac_oggbs__get_current_segment_index(drflac_oggbs* oggbs, drflac_uint8* pBytesRemainingInSeg) +{ + drflac_uint32 bytesConsumedInPage = drflac_ogg__get_page_body_size(&oggbs->currentPageHeader) - oggbs->bytesRemainingInPage; + drflac_uint8 iSeg = 0; + drflac_uint32 iByte = 0; + while (iByte < bytesConsumedInPage) { + drflac_uint8 segmentSize = oggbs->currentPageHeader.segmentTable[iSeg]; + if (iByte + segmentSize > bytesConsumedInPage) { + break; + } else { + iSeg += 1; + iByte += segmentSize; + } + } + + *pBytesRemainingInSeg = oggbs->currentPageHeader.segmentTable[iSeg] - (drflac_uint8)(bytesConsumedInPage - iByte); + return iSeg; +} + +static drflac_bool32 drflac_oggbs__seek_to_next_packet(drflac_oggbs* oggbs) +{ + /* The current packet ends when we get to the segment with a lacing value of < 255 which is not at the end of a page. */ + for (;;) { + drflac_bool32 atEndOfPage = DRFLAC_FALSE; + + drflac_uint8 bytesRemainingInSeg; + drflac_uint8 iFirstSeg = drflac_oggbs__get_current_segment_index(oggbs, &bytesRemainingInSeg); + + drflac_uint32 bytesToEndOfPacketOrPage = bytesRemainingInSeg; + for (drflac_uint8 iSeg = iFirstSeg; iSeg < oggbs->currentPageHeader.segmentCount; ++iSeg) { + drflac_uint8 segmentSize = oggbs->currentPageHeader.segmentTable[iSeg]; + if (segmentSize < 255) { + if (iSeg == oggbs->currentPageHeader.segmentCount-1) { + atEndOfPage = DRFLAC_TRUE; + } + + break; + } + + bytesToEndOfPacketOrPage += segmentSize; + } + + /* + At this point we will have found either the packet or the end of the page. If were at the end of the page we'll + want to load the next page and keep searching for the end of the packet. + */ + drflac_oggbs__seek_physical(oggbs, bytesToEndOfPacketOrPage, drflac_seek_origin_current); + oggbs->bytesRemainingInPage -= bytesToEndOfPacketOrPage; + + if (atEndOfPage) { + /* + We're potentially at the next packet, but we need to check the next page first to be sure because the packet may + straddle pages. + */ + if (!drflac_oggbs__goto_next_page(oggbs)) { + return DRFLAC_FALSE; + } + + /* If it's a fresh packet it most likely means we're at the next packet. */ + if ((oggbs->currentPageHeader.headerType & 0x01) == 0) { + return DRFLAC_TRUE; + } + } else { + /* We're at the next packet. */ + return DRFLAC_TRUE; + } + } +} + +static drflac_bool32 drflac_oggbs__seek_to_next_frame(drflac_oggbs* oggbs) +{ + /* The bitstream should be sitting on the first byte just after the header of the frame. */ + + /* What we're actually doing here is seeking to the start of the next packet. */ + return drflac_oggbs__seek_to_next_packet(oggbs); +} +#endif + +static size_t drflac__on_read_ogg(void* pUserData, void* bufferOut, size_t bytesToRead) +{ + drflac_oggbs* oggbs = (drflac_oggbs*)pUserData; + drflac_uint8* pRunningBufferOut = (drflac_uint8*)bufferOut; + size_t bytesRead = 0; + + DRFLAC_ASSERT(oggbs != NULL); + DRFLAC_ASSERT(pRunningBufferOut != NULL); + + /* Reading is done page-by-page. If we've run out of bytes in the page we need to move to the next one. */ + while (bytesRead < bytesToRead) { + size_t bytesRemainingToRead = bytesToRead - bytesRead; + + if (oggbs->bytesRemainingInPage >= bytesRemainingToRead) { + DRFLAC_COPY_MEMORY(pRunningBufferOut, oggbs->pageData + (oggbs->pageDataSize - oggbs->bytesRemainingInPage), bytesRemainingToRead); + bytesRead += bytesRemainingToRead; + oggbs->bytesRemainingInPage -= (drflac_uint32)bytesRemainingToRead; + break; + } + + /* If we get here it means some of the requested data is contained in the next pages. */ + if (oggbs->bytesRemainingInPage > 0) { + DRFLAC_COPY_MEMORY(pRunningBufferOut, oggbs->pageData + (oggbs->pageDataSize - oggbs->bytesRemainingInPage), oggbs->bytesRemainingInPage); + bytesRead += oggbs->bytesRemainingInPage; + pRunningBufferOut += oggbs->bytesRemainingInPage; + oggbs->bytesRemainingInPage = 0; + } + + DRFLAC_ASSERT(bytesRemainingToRead > 0); + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { + break; /* Failed to go to the next page. Might have simply hit the end of the stream. */ + } + } + + return bytesRead; +} + +static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_seek_origin origin) +{ + drflac_oggbs* oggbs = (drflac_oggbs*)pUserData; + int bytesSeeked = 0; + + DRFLAC_ASSERT(oggbs != NULL); + DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ + + /* Seeking is always forward which makes things a lot simpler. */ + if (origin == drflac_seek_origin_start) { + if (!drflac_oggbs__seek_physical(oggbs, (int)oggbs->firstBytePos, drflac_seek_origin_start)) { + return DRFLAC_FALSE; + } + + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_fail_on_crc_mismatch)) { + return DRFLAC_FALSE; + } + + return drflac__on_seek_ogg(pUserData, offset, drflac_seek_origin_current); + } + + DRFLAC_ASSERT(origin == drflac_seek_origin_current); + + while (bytesSeeked < offset) { + int bytesRemainingToSeek = offset - bytesSeeked; + DRFLAC_ASSERT(bytesRemainingToSeek >= 0); + + if (oggbs->bytesRemainingInPage >= (size_t)bytesRemainingToSeek) { + bytesSeeked += bytesRemainingToSeek; + (void)bytesSeeked; /* <-- Silence a dead store warning emitted by Clang Static Analyzer. */ + oggbs->bytesRemainingInPage -= bytesRemainingToSeek; + break; + } + + /* If we get here it means some of the requested data is contained in the next pages. */ + if (oggbs->bytesRemainingInPage > 0) { + bytesSeeked += (int)oggbs->bytesRemainingInPage; + oggbs->bytesRemainingInPage = 0; + } + + DRFLAC_ASSERT(bytesRemainingToSeek > 0); + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_fail_on_crc_mismatch)) { + /* Failed to go to the next page. We either hit the end of the stream or had a CRC mismatch. */ + return DRFLAC_FALSE; + } + } + + return DRFLAC_TRUE; +} + + +static drflac_bool32 drflac_ogg__seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex) +{ + drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; + drflac_uint64 originalBytePos; + drflac_uint64 runningGranulePosition; + drflac_uint64 runningFrameBytePos; + drflac_uint64 runningPCMFrameCount; + + DRFLAC_ASSERT(oggbs != NULL); + + originalBytePos = oggbs->currentBytePos; /* For recovery. Points to the OggS identifier. */ + + /* First seek to the first frame. */ + if (!drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes)) { + return DRFLAC_FALSE; + } + oggbs->bytesRemainingInPage = 0; + + runningGranulePosition = 0; + for (;;) { + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { + drflac_oggbs__seek_physical(oggbs, originalBytePos, drflac_seek_origin_start); + return DRFLAC_FALSE; /* Never did find that sample... */ + } + + runningFrameBytePos = oggbs->currentBytePos - drflac_ogg__get_page_header_size(&oggbs->currentPageHeader) - oggbs->pageDataSize; + if (oggbs->currentPageHeader.granulePosition >= pcmFrameIndex) { + break; /* The sample is somewhere in the previous page. */ + } + + /* + At this point we know the sample is not in the previous page. It could possibly be in this page. For simplicity we + disregard any pages that do not begin a fresh packet. + */ + if ((oggbs->currentPageHeader.headerType & 0x01) == 0) { /* <-- Is it a fresh page? */ + if (oggbs->currentPageHeader.segmentTable[0] >= 2) { + drflac_uint8 firstBytesInPage[2]; + firstBytesInPage[0] = oggbs->pageData[0]; + firstBytesInPage[1] = oggbs->pageData[1]; + + if ((firstBytesInPage[0] == 0xFF) && (firstBytesInPage[1] & 0xFC) == 0xF8) { /* <-- Does the page begin with a frame's sync code? */ + runningGranulePosition = oggbs->currentPageHeader.granulePosition; + } + + continue; + } + } + } + + /* + We found the page that that is closest to the sample, so now we need to find it. The first thing to do is seek to the + start of that page. In the loop above we checked that it was a fresh page which means this page is also the start of + a new frame. This property means that after we've seeked to the page we can immediately start looping over frames until + we find the one containing the target sample. + */ + if (!drflac_oggbs__seek_physical(oggbs, runningFrameBytePos, drflac_seek_origin_start)) { + return DRFLAC_FALSE; + } + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { + return DRFLAC_FALSE; + } + + /* + At this point we'll be sitting on the first byte of the frame header of the first frame in the page. We just keep + looping over these frames until we find the one containing the sample we're after. + */ + runningPCMFrameCount = runningGranulePosition; + for (;;) { + /* + There are two ways to find the sample and seek past irrelevant frames: + 1) Use the native FLAC decoder. + 2) Use Ogg's framing system. + + Both of these options have their own pros and cons. Using the native FLAC decoder is slower because it needs to + do a full decode of the frame. Using Ogg's framing system is faster, but more complicated and involves some code + duplication for the decoding of frame headers. + + Another thing to consider is that using the Ogg framing system will perform direct seeking of the physical Ogg + bitstream. This is important to consider because it means we cannot read data from the drflac_bs object using the + standard drflac__*() APIs because that will read in extra data for its own internal caching which in turn breaks + the positioning of the read pointer of the physical Ogg bitstream. Therefore, anything that would normally be read + using the native FLAC decoding APIs, such as drflac__read_next_flac_frame_header(), need to be re-implemented so as to + avoid the use of the drflac_bs object. + + Considering these issues, I have decided to use the slower native FLAC decoding method for the following reasons: + 1) Seeking is already partially accelerated using Ogg's paging system in the code block above. + 2) Seeking in an Ogg encapsulated FLAC stream is probably quite uncommon. + 3) Simplicity. + */ + drflac_uint64 firstPCMFrameInFLACFrame = 0; + drflac_uint64 lastPCMFrameInFLACFrame = 0; + drflac_uint64 pcmFrameCountInThisFrame; + + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + return DRFLAC_FALSE; + } + + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &firstPCMFrameInFLACFrame, &lastPCMFrameInFLACFrame); + + pcmFrameCountInThisFrame = (lastPCMFrameInFLACFrame - firstPCMFrameInFLACFrame) + 1; + + /* If we are seeking to the end of the file and we've just hit it, we're done. */ + if (pcmFrameIndex == pFlac->totalPCMFrameCount && (runningPCMFrameCount + pcmFrameCountInThisFrame) == pFlac->totalPCMFrameCount) { + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + pFlac->currentPCMFrame = pcmFrameIndex; + pFlac->currentFLACFrame.pcmFramesRemaining = 0; + return DRFLAC_TRUE; + } else { + return DRFLAC_FALSE; + } + } + + if (pcmFrameIndex < (runningPCMFrameCount + pcmFrameCountInThisFrame)) { + /* + The sample should be in this FLAC frame. We need to fully decode it, however if it's an invalid frame (a CRC mismatch), we need to pretend + it never existed and keep iterating. + */ + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + /* The frame is valid. We just need to skip over some samples to ensure it's sample-exact. */ + drflac_uint64 pcmFramesToDecode = (size_t)(pcmFrameIndex - runningPCMFrameCount); /* <-- Safe cast because the maximum number of samples in a frame is 65535. */ + if (pcmFramesToDecode == 0) { + return DRFLAC_TRUE; + } + + pFlac->currentPCMFrame = runningPCMFrameCount; + + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ + } else { + if (result == DRFLAC_CRC_MISMATCH) { + continue; /* CRC mismatch. Pretend this frame never existed. */ + } else { + return DRFLAC_FALSE; + } + } + } else { + /* + It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this + frame never existed and leave the running sample count untouched. + */ + drflac_result result = drflac__seek_to_next_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + runningPCMFrameCount += pcmFrameCountInThisFrame; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + continue; /* CRC mismatch. Pretend this frame never existed. */ + } else { + return DRFLAC_FALSE; + } + } + } + } +} + + + +static drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_bool32 relaxed) +{ + drflac_ogg_page_header header; + drflac_uint32 crc32 = DRFLAC_OGG_CAPTURE_PATTERN_CRC32; + drflac_uint32 bytesRead = 0; + + /* Pre Condition: The bit stream should be sitting just past the 4-byte OggS capture pattern. */ + (void)relaxed; + + pInit->container = drflac_container_ogg; + pInit->oggFirstBytePos = 0; + + /* + We'll get here if the first 4 bytes of the stream were the OggS capture pattern, however it doesn't necessarily mean the + stream includes FLAC encoded audio. To check for this we need to scan the beginning-of-stream page markers and check if + any match the FLAC specification. Important to keep in mind that the stream may be multiplexed. + */ + if (drflac_ogg__read_page_header_after_capture_pattern(onRead, pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { + return DRFLAC_FALSE; + } + pInit->runningFilePos += bytesRead; + + for (;;) { + int pageBodySize; + + /* Break if we're past the beginning of stream page. */ + if ((header.headerType & 0x02) == 0) { + return DRFLAC_FALSE; + } + + /* Check if it's a FLAC header. */ + pageBodySize = drflac_ogg__get_page_body_size(&header); + if (pageBodySize == 51) { /* 51 = the lacing value of the FLAC header packet. */ + /* It could be a FLAC page... */ + drflac_uint32 bytesRemainingInPage = pageBodySize; + drflac_uint8 packetType; + + if (onRead(pUserData, &packetType, 1) != 1) { + return DRFLAC_FALSE; + } + + bytesRemainingInPage -= 1; + if (packetType == 0x7F) { + /* Increasingly more likely to be a FLAC page... */ + drflac_uint8 sig[4]; + if (onRead(pUserData, sig, 4) != 4) { + return DRFLAC_FALSE; + } + + bytesRemainingInPage -= 4; + if (sig[0] == 'F' && sig[1] == 'L' && sig[2] == 'A' && sig[3] == 'C') { + /* Almost certainly a FLAC page... */ + drflac_uint8 mappingVersion[2]; + if (onRead(pUserData, mappingVersion, 2) != 2) { + return DRFLAC_FALSE; + } + + if (mappingVersion[0] != 1) { + return DRFLAC_FALSE; /* Only supporting version 1.x of the Ogg mapping. */ + } + + /* + The next 2 bytes are the non-audio packets, not including this one. We don't care about this because we're going to + be handling it in a generic way based on the serial number and packet types. + */ + if (!onSeek(pUserData, 2, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + + /* Expecting the native FLAC signature "fLaC". */ + if (onRead(pUserData, sig, 4) != 4) { + return DRFLAC_FALSE; + } + + if (sig[0] == 'f' && sig[1] == 'L' && sig[2] == 'a' && sig[3] == 'C') { + /* The remaining data in the page should be the STREAMINFO block. */ + drflac_streaminfo streaminfo; + drflac_uint8 isLastBlock; + drflac_uint8 blockType; + drflac_uint32 blockSize; + if (!drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize)) { + return DRFLAC_FALSE; + } + + if (blockType != DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO || blockSize != 34) { + return DRFLAC_FALSE; /* Invalid block type. First block must be the STREAMINFO block. */ + } + + if (drflac__read_streaminfo(onRead, pUserData, &streaminfo)) { + /* Success! */ + pInit->hasStreamInfoBlock = DRFLAC_TRUE; + pInit->sampleRate = streaminfo.sampleRate; + pInit->channels = streaminfo.channels; + pInit->bitsPerSample = streaminfo.bitsPerSample; + pInit->totalPCMFrameCount = streaminfo.totalPCMFrameCount; + pInit->maxBlockSizeInPCMFrames = streaminfo.maxBlockSizeInPCMFrames; + pInit->hasMetadataBlocks = !isLastBlock; + + if (onMeta) { + drflac_metadata metadata; + metadata.type = DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO; + metadata.pRawData = NULL; + metadata.rawDataSize = 0; + metadata.data.streaminfo = streaminfo; + onMeta(pUserDataMD, &metadata); + } + + pInit->runningFilePos += pageBodySize; + pInit->oggFirstBytePos = pInit->runningFilePos - 79; /* Subtracting 79 will place us right on top of the "OggS" identifier of the FLAC bos page. */ + pInit->oggSerial = header.serialNumber; + pInit->oggBosHeader = header; + break; + } else { + /* Failed to read STREAMINFO block. Aww, so close... */ + return DRFLAC_FALSE; + } + } else { + /* Invalid file. */ + return DRFLAC_FALSE; + } + } else { + /* Not a FLAC header. Skip it. */ + if (!onSeek(pUserData, bytesRemainingInPage, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + } + } else { + /* Not a FLAC header. Seek past the entire page and move on to the next. */ + if (!onSeek(pUserData, bytesRemainingInPage, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + } + } else { + if (!onSeek(pUserData, pageBodySize, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + } + + pInit->runningFilePos += pageBodySize; + + + /* Read the header of the next page. */ + if (drflac_ogg__read_page_header(onRead, pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { + return DRFLAC_FALSE; + } + pInit->runningFilePos += bytesRead; + } + + /* + If we get here it means we found a FLAC audio stream. We should be sitting on the first byte of the header of the next page. The next + packets in the FLAC logical stream contain the metadata. The only thing left to do in the initialization phase for Ogg is to create the + Ogg bistream object. + */ + pInit->hasMetadataBlocks = DRFLAC_TRUE; /* <-- Always have at least VORBIS_COMMENT metadata block. */ + return DRFLAC_TRUE; +} +#endif + +static drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD) +{ + drflac_bool32 relaxed; + drflac_uint8 id[4]; + + if (pInit == NULL || onRead == NULL || onSeek == NULL) { + return DRFLAC_FALSE; + } + + DRFLAC_ZERO_MEMORY(pInit, sizeof(*pInit)); + pInit->onRead = onRead; + pInit->onSeek = onSeek; + pInit->onMeta = onMeta; + pInit->container = container; + pInit->pUserData = pUserData; + pInit->pUserDataMD = pUserDataMD; + + pInit->bs.onRead = onRead; + pInit->bs.onSeek = onSeek; + pInit->bs.pUserData = pUserData; + drflac__reset_cache(&pInit->bs); + + + /* If the container is explicitly defined then we can try opening in relaxed mode. */ + relaxed = container != drflac_container_unknown; + + /* Skip over any ID3 tags. */ + for (;;) { + if (onRead(pUserData, id, 4) != 4) { + return DRFLAC_FALSE; /* Ran out of data. */ + } + pInit->runningFilePos += 4; + + if (id[0] == 'I' && id[1] == 'D' && id[2] == '3') { + drflac_uint8 header[6]; + drflac_uint8 flags; + drflac_uint32 headerSize; + + if (onRead(pUserData, header, 6) != 6) { + return DRFLAC_FALSE; /* Ran out of data. */ + } + pInit->runningFilePos += 6; + + flags = header[1]; + + DRFLAC_COPY_MEMORY(&headerSize, header+2, 4); + headerSize = drflac__unsynchsafe_32(drflac__be2host_32(headerSize)); + if (flags & 0x10) { + headerSize += 10; + } + + if (!onSeek(pUserData, headerSize, drflac_seek_origin_current)) { + return DRFLAC_FALSE; /* Failed to seek past the tag. */ + } + pInit->runningFilePos += headerSize; + } else { + break; + } + } + + if (id[0] == 'f' && id[1] == 'L' && id[2] == 'a' && id[3] == 'C') { + return drflac__init_private__native(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); + } +#ifndef DR_FLAC_NO_OGG + if (id[0] == 'O' && id[1] == 'g' && id[2] == 'g' && id[3] == 'S') { + return drflac__init_private__ogg(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); + } +#endif + + /* If we get here it means we likely don't have a header. Try opening in relaxed mode, if applicable. */ + if (relaxed) { + if (container == drflac_container_native) { + return drflac__init_private__native(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); + } +#ifndef DR_FLAC_NO_OGG + if (container == drflac_container_ogg) { + return drflac__init_private__ogg(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); + } +#endif + } + + /* Unsupported container. */ + return DRFLAC_FALSE; +} + +static void drflac__init_from_info(drflac* pFlac, const drflac_init_info* pInit) +{ + DRFLAC_ASSERT(pFlac != NULL); + DRFLAC_ASSERT(pInit != NULL); + + DRFLAC_ZERO_MEMORY(pFlac, sizeof(*pFlac)); + pFlac->bs = pInit->bs; + pFlac->onMeta = pInit->onMeta; + pFlac->pUserDataMD = pInit->pUserDataMD; + pFlac->maxBlockSizeInPCMFrames = pInit->maxBlockSizeInPCMFrames; + pFlac->sampleRate = pInit->sampleRate; + pFlac->channels = (drflac_uint8)pInit->channels; + pFlac->bitsPerSample = (drflac_uint8)pInit->bitsPerSample; + pFlac->totalPCMFrameCount = pInit->totalPCMFrameCount; + pFlac->container = pInit->container; +} + + +static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac_init_info init; + drflac_uint32 allocationSize; + drflac_uint32 wholeSIMDVectorCountPerChannel; + drflac_uint32 decodedSamplesAllocationSize; +#ifndef DR_FLAC_NO_OGG + drflac_oggbs* pOggbs = NULL; +#endif + drflac_uint64 firstFramePos; + drflac_uint64 seektablePos; + drflac_uint32 seekpointCount; + drflac_allocation_callbacks allocationCallbacks; + drflac* pFlac; + + /* CPU support first. */ + drflac__init_cpu_caps(); + + if (!drflac__init_private(&init, onRead, onSeek, onMeta, container, pUserData, pUserDataMD)) { + return NULL; + } + + if (pAllocationCallbacks != NULL) { + allocationCallbacks = *pAllocationCallbacks; + if (allocationCallbacks.onFree == NULL || (allocationCallbacks.onMalloc == NULL && allocationCallbacks.onRealloc == NULL)) { + return NULL; /* Invalid allocation callbacks. */ + } + } else { + allocationCallbacks.pUserData = NULL; + allocationCallbacks.onMalloc = drflac__malloc_default; + allocationCallbacks.onRealloc = drflac__realloc_default; + allocationCallbacks.onFree = drflac__free_default; + } + + + /* + The size of the allocation for the drflac object needs to be large enough to fit the following: + 1) The main members of the drflac structure + 2) A block of memory large enough to store the decoded samples of the largest frame in the stream + 3) If the container is Ogg, a drflac_oggbs object + + The complicated part of the allocation is making sure there's enough room the decoded samples, taking into consideration + the different SIMD instruction sets. + */ + allocationSize = sizeof(drflac); + + /* + The allocation size for decoded frames depends on the number of 32-bit integers that fit inside the largest SIMD vector + we are supporting. + */ + if ((init.maxBlockSizeInPCMFrames % (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) == 0) { + wholeSIMDVectorCountPerChannel = (init.maxBlockSizeInPCMFrames / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))); + } else { + wholeSIMDVectorCountPerChannel = (init.maxBlockSizeInPCMFrames / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) + 1; + } + + decodedSamplesAllocationSize = wholeSIMDVectorCountPerChannel * DRFLAC_MAX_SIMD_VECTOR_SIZE * init.channels; + + allocationSize += decodedSamplesAllocationSize; + allocationSize += DRFLAC_MAX_SIMD_VECTOR_SIZE; /* Allocate extra bytes to ensure we have enough for alignment. */ + +#ifndef DR_FLAC_NO_OGG + /* There's additional data required for Ogg streams. */ + if (init.container == drflac_container_ogg) { + allocationSize += sizeof(drflac_oggbs); + + pOggbs = (drflac_oggbs*)drflac__malloc_from_callbacks(sizeof(*pOggbs), &allocationCallbacks); + if (pOggbs == NULL) { + return NULL; /*DRFLAC_OUT_OF_MEMORY;*/ + } + + DRFLAC_ZERO_MEMORY(pOggbs, sizeof(*pOggbs)); + pOggbs->onRead = onRead; + pOggbs->onSeek = onSeek; + pOggbs->pUserData = pUserData; + pOggbs->currentBytePos = init.oggFirstBytePos; + pOggbs->firstBytePos = init.oggFirstBytePos; + pOggbs->serialNumber = init.oggSerial; + pOggbs->bosPageHeader = init.oggBosHeader; + pOggbs->bytesRemainingInPage = 0; + } +#endif + + /* + This part is a bit awkward. We need to load the seektable so that it can be referenced in-memory, but I want the drflac object to + consist of only a single heap allocation. To this, the size of the seek table needs to be known, which we determine when reading + and decoding the metadata. + */ + firstFramePos = 42; /* <-- We know we are at byte 42 at this point. */ + seektablePos = 0; + seekpointCount = 0; + if (init.hasMetadataBlocks) { + drflac_read_proc onReadOverride = onRead; + drflac_seek_proc onSeekOverride = onSeek; + void* pUserDataOverride = pUserData; + +#ifndef DR_FLAC_NO_OGG + if (init.container == drflac_container_ogg) { + onReadOverride = drflac__on_read_ogg; + onSeekOverride = drflac__on_seek_ogg; + pUserDataOverride = (void*)pOggbs; + } +#endif + + if (!drflac__read_and_decode_metadata(onReadOverride, onSeekOverride, onMeta, pUserDataOverride, pUserDataMD, &firstFramePos, &seektablePos, &seekpointCount, &allocationCallbacks)) { + #ifndef DR_FLAC_NO_OGG + drflac__free_from_callbacks(pOggbs, &allocationCallbacks); + #endif + return NULL; + } + + allocationSize += seekpointCount * sizeof(drflac_seekpoint); + } + + + pFlac = (drflac*)drflac__malloc_from_callbacks(allocationSize, &allocationCallbacks); + if (pFlac == NULL) { + #ifndef DR_FLAC_NO_OGG + drflac__free_from_callbacks(pOggbs, &allocationCallbacks); + #endif + return NULL; + } + + drflac__init_from_info(pFlac, &init); + pFlac->allocationCallbacks = allocationCallbacks; + pFlac->pDecodedSamples = (drflac_int32*)drflac_align((size_t)pFlac->pExtraData, DRFLAC_MAX_SIMD_VECTOR_SIZE); + +#ifndef DR_FLAC_NO_OGG + if (init.container == drflac_container_ogg) { + drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + (seekpointCount * sizeof(drflac_seekpoint))); + DRFLAC_COPY_MEMORY(pInternalOggbs, pOggbs, sizeof(*pOggbs)); + + /* At this point the pOggbs object has been handed over to pInternalOggbs and can be freed. */ + drflac__free_from_callbacks(pOggbs, &allocationCallbacks); + pOggbs = NULL; + + /* The Ogg bistream needs to be layered on top of the original bitstream. */ + pFlac->bs.onRead = drflac__on_read_ogg; + pFlac->bs.onSeek = drflac__on_seek_ogg; + pFlac->bs.pUserData = (void*)pInternalOggbs; + pFlac->_oggbs = (void*)pInternalOggbs; + } +#endif + + pFlac->firstFLACFramePosInBytes = firstFramePos; + + /* NOTE: Seektables are not currently compatible with Ogg encapsulation (Ogg has its own accelerated seeking system). I may change this later, so I'm leaving this here for now. */ +#ifndef DR_FLAC_NO_OGG + if (init.container == drflac_container_ogg) + { + pFlac->pSeekpoints = NULL; + pFlac->seekpointCount = 0; + } + else +#endif + { + /* If we have a seektable we need to load it now, making sure we move back to where we were previously. */ + if (seektablePos != 0) { + pFlac->seekpointCount = seekpointCount; + pFlac->pSeekpoints = (drflac_seekpoint*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize); + + DRFLAC_ASSERT(pFlac->bs.onSeek != NULL); + DRFLAC_ASSERT(pFlac->bs.onRead != NULL); + + /* Seek to the seektable, then just read directly into our seektable buffer. */ + if (pFlac->bs.onSeek(pFlac->bs.pUserData, (int)seektablePos, drflac_seek_origin_start)) { + drflac_uint32 iSeekpoint; + + for (iSeekpoint = 0; iSeekpoint < seekpointCount; iSeekpoint += 1) { + if (pFlac->bs.onRead(pFlac->bs.pUserData, pFlac->pSeekpoints + iSeekpoint, DRFLAC_SEEKPOINT_SIZE_IN_BYTES) == DRFLAC_SEEKPOINT_SIZE_IN_BYTES) { + /* Endian swap. */ + pFlac->pSeekpoints[iSeekpoint].firstPCMFrame = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].firstPCMFrame); + pFlac->pSeekpoints[iSeekpoint].flacFrameOffset = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].flacFrameOffset); + pFlac->pSeekpoints[iSeekpoint].pcmFrameCount = drflac__be2host_16(pFlac->pSeekpoints[iSeekpoint].pcmFrameCount); + } else { + /* Failed to read the seektable. Pretend we don't have one. */ + pFlac->pSeekpoints = NULL; + pFlac->seekpointCount = 0; + break; + } + } + + /* We need to seek back to where we were. If this fails it's a critical error. */ + if (!pFlac->bs.onSeek(pFlac->bs.pUserData, (int)pFlac->firstFLACFramePosInBytes, drflac_seek_origin_start)) { + drflac__free_from_callbacks(pFlac, &allocationCallbacks); + return NULL; + } + } else { + /* Failed to seek to the seektable. Ominous sign, but for now we can just pretend we don't have one. */ + pFlac->pSeekpoints = NULL; + pFlac->seekpointCount = 0; + } + } + } + + + /* + If we get here, but don't have a STREAMINFO block, it means we've opened the stream in relaxed mode and need to decode + the first frame. + */ + if (!init.hasStreamInfoBlock) { + pFlac->currentFLACFrame.header = init.firstFrameHeader; + for (;;) { + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + break; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + drflac__free_from_callbacks(pFlac, &allocationCallbacks); + return NULL; + } + continue; + } else { + drflac__free_from_callbacks(pFlac, &allocationCallbacks); + return NULL; + } + } + } + } + + return pFlac; +} + + + +#ifndef DR_FLAC_NO_STDIO +#include +#ifndef DR_FLAC_NO_WCHAR +#include /* For wcslen(), wcsrtombs() */ +#endif + +/* Errno */ +/* drflac_result_from_errno() is only used for fopen() and wfopen() so putting it inside DR_WAV_NO_STDIO for now. If something else needs this later we can move it out. */ +#include +static drflac_result drflac_result_from_errno(int e) +{ + switch (e) + { + case 0: return DRFLAC_SUCCESS; + #ifdef EPERM + case EPERM: return DRFLAC_INVALID_OPERATION; + #endif + #ifdef ENOENT + case ENOENT: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef ESRCH + case ESRCH: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef EINTR + case EINTR: return DRFLAC_INTERRUPT; + #endif + #ifdef EIO + case EIO: return DRFLAC_IO_ERROR; + #endif + #ifdef ENXIO + case ENXIO: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef E2BIG + case E2BIG: return DRFLAC_INVALID_ARGS; + #endif + #ifdef ENOEXEC + case ENOEXEC: return DRFLAC_INVALID_FILE; + #endif + #ifdef EBADF + case EBADF: return DRFLAC_INVALID_FILE; + #endif + #ifdef ECHILD + case ECHILD: return DRFLAC_ERROR; + #endif + #ifdef EAGAIN + case EAGAIN: return DRFLAC_UNAVAILABLE; + #endif + #ifdef ENOMEM + case ENOMEM: return DRFLAC_OUT_OF_MEMORY; + #endif + #ifdef EACCES + case EACCES: return DRFLAC_ACCESS_DENIED; + #endif + #ifdef EFAULT + case EFAULT: return DRFLAC_BAD_ADDRESS; + #endif + #ifdef ENOTBLK + case ENOTBLK: return DRFLAC_ERROR; + #endif + #ifdef EBUSY + case EBUSY: return DRFLAC_BUSY; + #endif + #ifdef EEXIST + case EEXIST: return DRFLAC_ALREADY_EXISTS; + #endif + #ifdef EXDEV + case EXDEV: return DRFLAC_ERROR; + #endif + #ifdef ENODEV + case ENODEV: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef ENOTDIR + case ENOTDIR: return DRFLAC_NOT_DIRECTORY; + #endif + #ifdef EISDIR + case EISDIR: return DRFLAC_IS_DIRECTORY; + #endif + #ifdef EINVAL + case EINVAL: return DRFLAC_INVALID_ARGS; + #endif + #ifdef ENFILE + case ENFILE: return DRFLAC_TOO_MANY_OPEN_FILES; + #endif + #ifdef EMFILE + case EMFILE: return DRFLAC_TOO_MANY_OPEN_FILES; + #endif + #ifdef ENOTTY + case ENOTTY: return DRFLAC_INVALID_OPERATION; + #endif + #ifdef ETXTBSY + case ETXTBSY: return DRFLAC_BUSY; + #endif + #ifdef EFBIG + case EFBIG: return DRFLAC_TOO_BIG; + #endif + #ifdef ENOSPC + case ENOSPC: return DRFLAC_NO_SPACE; + #endif + #ifdef ESPIPE + case ESPIPE: return DRFLAC_BAD_SEEK; + #endif + #ifdef EROFS + case EROFS: return DRFLAC_ACCESS_DENIED; + #endif + #ifdef EMLINK + case EMLINK: return DRFLAC_TOO_MANY_LINKS; + #endif + #ifdef EPIPE + case EPIPE: return DRFLAC_BAD_PIPE; + #endif + #ifdef EDOM + case EDOM: return DRFLAC_OUT_OF_RANGE; + #endif + #ifdef ERANGE + case ERANGE: return DRFLAC_OUT_OF_RANGE; + #endif + #ifdef EDEADLK + case EDEADLK: return DRFLAC_DEADLOCK; + #endif + #ifdef ENAMETOOLONG + case ENAMETOOLONG: return DRFLAC_PATH_TOO_LONG; + #endif + #ifdef ENOLCK + case ENOLCK: return DRFLAC_ERROR; + #endif + #ifdef ENOSYS + case ENOSYS: return DRFLAC_NOT_IMPLEMENTED; + #endif + #ifdef ENOTEMPTY + case ENOTEMPTY: return DRFLAC_DIRECTORY_NOT_EMPTY; + #endif + #ifdef ELOOP + case ELOOP: return DRFLAC_TOO_MANY_LINKS; + #endif + #ifdef ENOMSG + case ENOMSG: return DRFLAC_NO_MESSAGE; + #endif + #ifdef EIDRM + case EIDRM: return DRFLAC_ERROR; + #endif + #ifdef ECHRNG + case ECHRNG: return DRFLAC_ERROR; + #endif + #ifdef EL2NSYNC + case EL2NSYNC: return DRFLAC_ERROR; + #endif + #ifdef EL3HLT + case EL3HLT: return DRFLAC_ERROR; + #endif + #ifdef EL3RST + case EL3RST: return DRFLAC_ERROR; + #endif + #ifdef ELNRNG + case ELNRNG: return DRFLAC_OUT_OF_RANGE; + #endif + #ifdef EUNATCH + case EUNATCH: return DRFLAC_ERROR; + #endif + #ifdef ENOCSI + case ENOCSI: return DRFLAC_ERROR; + #endif + #ifdef EL2HLT + case EL2HLT: return DRFLAC_ERROR; + #endif + #ifdef EBADE + case EBADE: return DRFLAC_ERROR; + #endif + #ifdef EBADR + case EBADR: return DRFLAC_ERROR; + #endif + #ifdef EXFULL + case EXFULL: return DRFLAC_ERROR; + #endif + #ifdef ENOANO + case ENOANO: return DRFLAC_ERROR; + #endif + #ifdef EBADRQC + case EBADRQC: return DRFLAC_ERROR; + #endif + #ifdef EBADSLT + case EBADSLT: return DRFLAC_ERROR; + #endif + #ifdef EBFONT + case EBFONT: return DRFLAC_INVALID_FILE; + #endif + #ifdef ENOSTR + case ENOSTR: return DRFLAC_ERROR; + #endif + #ifdef ENODATA + case ENODATA: return DRFLAC_NO_DATA_AVAILABLE; + #endif + #ifdef ETIME + case ETIME: return DRFLAC_TIMEOUT; + #endif + #ifdef ENOSR + case ENOSR: return DRFLAC_NO_DATA_AVAILABLE; + #endif + #ifdef ENONET + case ENONET: return DRFLAC_NO_NETWORK; + #endif + #ifdef ENOPKG + case ENOPKG: return DRFLAC_ERROR; + #endif + #ifdef EREMOTE + case EREMOTE: return DRFLAC_ERROR; + #endif + #ifdef ENOLINK + case ENOLINK: return DRFLAC_ERROR; + #endif + #ifdef EADV + case EADV: return DRFLAC_ERROR; + #endif + #ifdef ESRMNT + case ESRMNT: return DRFLAC_ERROR; + #endif + #ifdef ECOMM + case ECOMM: return DRFLAC_ERROR; + #endif + #ifdef EPROTO + case EPROTO: return DRFLAC_ERROR; + #endif + #ifdef EMULTIHOP + case EMULTIHOP: return DRFLAC_ERROR; + #endif + #ifdef EDOTDOT + case EDOTDOT: return DRFLAC_ERROR; + #endif + #ifdef EBADMSG + case EBADMSG: return DRFLAC_BAD_MESSAGE; + #endif + #ifdef EOVERFLOW + case EOVERFLOW: return DRFLAC_TOO_BIG; + #endif + #ifdef ENOTUNIQ + case ENOTUNIQ: return DRFLAC_NOT_UNIQUE; + #endif + #ifdef EBADFD + case EBADFD: return DRFLAC_ERROR; + #endif + #ifdef EREMCHG + case EREMCHG: return DRFLAC_ERROR; + #endif + #ifdef ELIBACC + case ELIBACC: return DRFLAC_ACCESS_DENIED; + #endif + #ifdef ELIBBAD + case ELIBBAD: return DRFLAC_INVALID_FILE; + #endif + #ifdef ELIBSCN + case ELIBSCN: return DRFLAC_INVALID_FILE; + #endif + #ifdef ELIBMAX + case ELIBMAX: return DRFLAC_ERROR; + #endif + #ifdef ELIBEXEC + case ELIBEXEC: return DRFLAC_ERROR; + #endif + #ifdef EILSEQ + case EILSEQ: return DRFLAC_INVALID_DATA; + #endif + #ifdef ERESTART + case ERESTART: return DRFLAC_ERROR; + #endif + #ifdef ESTRPIPE + case ESTRPIPE: return DRFLAC_ERROR; + #endif + #ifdef EUSERS + case EUSERS: return DRFLAC_ERROR; + #endif + #ifdef ENOTSOCK + case ENOTSOCK: return DRFLAC_NOT_SOCKET; + #endif + #ifdef EDESTADDRREQ + case EDESTADDRREQ: return DRFLAC_NO_ADDRESS; + #endif + #ifdef EMSGSIZE + case EMSGSIZE: return DRFLAC_TOO_BIG; + #endif + #ifdef EPROTOTYPE + case EPROTOTYPE: return DRFLAC_BAD_PROTOCOL; + #endif + #ifdef ENOPROTOOPT + case ENOPROTOOPT: return DRFLAC_PROTOCOL_UNAVAILABLE; + #endif + #ifdef EPROTONOSUPPORT + case EPROTONOSUPPORT: return DRFLAC_PROTOCOL_NOT_SUPPORTED; + #endif + #ifdef ESOCKTNOSUPPORT + case ESOCKTNOSUPPORT: return DRFLAC_SOCKET_NOT_SUPPORTED; + #endif + #ifdef EOPNOTSUPP + case EOPNOTSUPP: return DRFLAC_INVALID_OPERATION; + #endif + #ifdef EPFNOSUPPORT + case EPFNOSUPPORT: return DRFLAC_PROTOCOL_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EAFNOSUPPORT + case EAFNOSUPPORT: return DRFLAC_ADDRESS_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EADDRINUSE + case EADDRINUSE: return DRFLAC_ALREADY_IN_USE; + #endif + #ifdef EADDRNOTAVAIL + case EADDRNOTAVAIL: return DRFLAC_ERROR; + #endif + #ifdef ENETDOWN + case ENETDOWN: return DRFLAC_NO_NETWORK; + #endif + #ifdef ENETUNREACH + case ENETUNREACH: return DRFLAC_NO_NETWORK; + #endif + #ifdef ENETRESET + case ENETRESET: return DRFLAC_NO_NETWORK; + #endif + #ifdef ECONNABORTED + case ECONNABORTED: return DRFLAC_NO_NETWORK; + #endif + #ifdef ECONNRESET + case ECONNRESET: return DRFLAC_CONNECTION_RESET; + #endif + #ifdef ENOBUFS + case ENOBUFS: return DRFLAC_NO_SPACE; + #endif + #ifdef EISCONN + case EISCONN: return DRFLAC_ALREADY_CONNECTED; + #endif + #ifdef ENOTCONN + case ENOTCONN: return DRFLAC_NOT_CONNECTED; + #endif + #ifdef ESHUTDOWN + case ESHUTDOWN: return DRFLAC_ERROR; + #endif + #ifdef ETOOMANYREFS + case ETOOMANYREFS: return DRFLAC_ERROR; + #endif + #ifdef ETIMEDOUT + case ETIMEDOUT: return DRFLAC_TIMEOUT; + #endif + #ifdef ECONNREFUSED + case ECONNREFUSED: return DRFLAC_CONNECTION_REFUSED; + #endif + #ifdef EHOSTDOWN + case EHOSTDOWN: return DRFLAC_NO_HOST; + #endif + #ifdef EHOSTUNREACH + case EHOSTUNREACH: return DRFLAC_NO_HOST; + #endif + #ifdef EALREADY + case EALREADY: return DRFLAC_IN_PROGRESS; + #endif + #ifdef EINPROGRESS + case EINPROGRESS: return DRFLAC_IN_PROGRESS; + #endif + #ifdef ESTALE + case ESTALE: return DRFLAC_INVALID_FILE; + #endif + #ifdef EUCLEAN + case EUCLEAN: return DRFLAC_ERROR; + #endif + #ifdef ENOTNAM + case ENOTNAM: return DRFLAC_ERROR; + #endif + #ifdef ENAVAIL + case ENAVAIL: return DRFLAC_ERROR; + #endif + #ifdef EISNAM + case EISNAM: return DRFLAC_ERROR; + #endif + #ifdef EREMOTEIO + case EREMOTEIO: return DRFLAC_IO_ERROR; + #endif + #ifdef EDQUOT + case EDQUOT: return DRFLAC_NO_SPACE; + #endif + #ifdef ENOMEDIUM + case ENOMEDIUM: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef EMEDIUMTYPE + case EMEDIUMTYPE: return DRFLAC_ERROR; + #endif + #ifdef ECANCELED + case ECANCELED: return DRFLAC_CANCELLED; + #endif + #ifdef ENOKEY + case ENOKEY: return DRFLAC_ERROR; + #endif + #ifdef EKEYEXPIRED + case EKEYEXPIRED: return DRFLAC_ERROR; + #endif + #ifdef EKEYREVOKED + case EKEYREVOKED: return DRFLAC_ERROR; + #endif + #ifdef EKEYREJECTED + case EKEYREJECTED: return DRFLAC_ERROR; + #endif + #ifdef EOWNERDEAD + case EOWNERDEAD: return DRFLAC_ERROR; + #endif + #ifdef ENOTRECOVERABLE + case ENOTRECOVERABLE: return DRFLAC_ERROR; + #endif + #ifdef ERFKILL + case ERFKILL: return DRFLAC_ERROR; + #endif + #ifdef EHWPOISON + case EHWPOISON: return DRFLAC_ERROR; + #endif + default: return DRFLAC_ERROR; + } +} +/* End Errno */ + +/* fopen */ +static drflac_result drflac_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err; +#endif + + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRFLAC_INVALID_ARGS; + } + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + err = fopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drflac_result_from_errno(err); + } +#else +#if defined(_WIN32) || defined(__APPLE__) + *ppFile = fopen(pFilePath, pOpenMode); +#else + #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) + *ppFile = fopen64(pFilePath, pOpenMode); + #else + *ppFile = fopen(pFilePath, pOpenMode); + #endif +#endif + if (*ppFile == NULL) { + drflac_result result = drflac_result_from_errno(errno); + if (result == DRFLAC_SUCCESS) { + result = DRFLAC_ERROR; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ + } + + return result; + } +#endif + + return DRFLAC_SUCCESS; +} + +/* +_wfopen() isn't always available in all compilation environments. + + * Windows only. + * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). + * MinGW-64 (both 32- and 64-bit) seems to support it. + * MinGW wraps it in !defined(__STRICT_ANSI__). + * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). + +This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() +fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. +*/ +#if defined(_WIN32) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) + #define DRFLAC_HAS_WFOPEN + #endif +#endif + +#ifndef DR_FLAC_NO_WCHAR +static drflac_result drflac_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRFLAC_INVALID_ARGS; + } + +#if defined(DRFLAC_HAS_WFOPEN) + { + /* Use _wfopen() on Windows. */ + #if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drflac_result_from_errno(err); + } + #else + *ppFile = _wfopen(pFilePath, pOpenMode); + if (*ppFile == NULL) { + return drflac_result_from_errno(errno); + } + #endif + (void)pAllocationCallbacks; + } +#else + /* + Use fopen() on anything other than Windows. Requires a conversion. This is annoying because + fopen() is locale specific. The only real way I can think of to do this is with wcsrtombs(). Note + that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for + maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler + error I'll look into improving compatibility. + */ + + /* + Some compilers don't support wchar_t or wcsrtombs() which we're using below. In this case we just + need to abort with an error. If you encounter a compiler lacking such support, add it to this list + and submit a bug report and it'll be added to the library upstream. + */ + #if defined(__DJGPP__) + { + /* Nothing to do here. This will fall through to the error check below. */ + } + #else + { + mbstate_t mbs; + size_t lenMB; + const wchar_t* pFilePathTemp = pFilePath; + char* pFilePathMB = NULL; + char pOpenModeMB[32] = {0}; + + /* Get the length first. */ + DRFLAC_ZERO_OBJECT(&mbs); + lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); + if (lenMB == (size_t)-1) { + return drflac_result_from_errno(errno); + } + + pFilePathMB = (char*)drflac__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks); + if (pFilePathMB == NULL) { + return DRFLAC_OUT_OF_MEMORY; + } + + pFilePathTemp = pFilePath; + DRFLAC_ZERO_OBJECT(&mbs); + wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); + + /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ + { + size_t i = 0; + for (;;) { + if (pOpenMode[i] == 0) { + pOpenModeMB[i] = '\0'; + break; + } + + pOpenModeMB[i] = (char)pOpenMode[i]; + i += 1; + } + } + + *ppFile = fopen(pFilePathMB, pOpenModeMB); + + drflac__free_from_callbacks(pFilePathMB, pAllocationCallbacks); + } + #endif + + if (*ppFile == NULL) { + return DRFLAC_ERROR; + } +#endif + + return DRFLAC_SUCCESS; +} +#endif +/* End fopen */ + +static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t bytesToRead) +{ + return fread(bufferOut, 1, bytesToRead, (FILE*)pUserData); +} + +static drflac_bool32 drflac__on_seek_stdio(void* pUserData, int offset, drflac_seek_origin origin) +{ + DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ + + return fseek((FILE*)pUserData, offset, (origin == drflac_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; +} + + +DRFLAC_API drflac* drflac_open_file(const char* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + FILE* pFile; + + if (drflac_fopen(&pFile, pFileName, "rb") != DRFLAC_SUCCESS) { + return NULL; + } + + pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (pFlac == NULL) { + fclose(pFile); + return NULL; + } + + return pFlac; +} + +#ifndef DR_FLAC_NO_WCHAR +DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + FILE* pFile; + + if (drflac_wfopen(&pFile, pFileName, L"rb", pAllocationCallbacks) != DRFLAC_SUCCESS) { + return NULL; + } + + pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (pFlac == NULL) { + fclose(pFile); + return NULL; + } + + return pFlac; +} +#endif + +DRFLAC_API drflac* drflac_open_file_with_metadata(const char* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + FILE* pFile; + + if (drflac_fopen(&pFile, pFileName, "rb") != DRFLAC_SUCCESS) { + return NULL; + } + + pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); + if (pFlac == NULL) { + fclose(pFile); + return pFlac; + } + + return pFlac; +} + +#ifndef DR_FLAC_NO_WCHAR +DRFLAC_API drflac* drflac_open_file_with_metadata_w(const wchar_t* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + FILE* pFile; + + if (drflac_wfopen(&pFile, pFileName, L"rb", pAllocationCallbacks) != DRFLAC_SUCCESS) { + return NULL; + } + + pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); + if (pFlac == NULL) { + fclose(pFile); + return pFlac; + } + + return pFlac; +} +#endif +#endif /* DR_FLAC_NO_STDIO */ + +static size_t drflac__on_read_memory(void* pUserData, void* bufferOut, size_t bytesToRead) +{ + drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; + size_t bytesRemaining; + + DRFLAC_ASSERT(memoryStream != NULL); + DRFLAC_ASSERT(memoryStream->dataSize >= memoryStream->currentReadPos); + + bytesRemaining = memoryStream->dataSize - memoryStream->currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + DRFLAC_COPY_MEMORY(bufferOut, memoryStream->data + memoryStream->currentReadPos, bytesToRead); + memoryStream->currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +static drflac_bool32 drflac__on_seek_memory(void* pUserData, int offset, drflac_seek_origin origin) +{ + drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; + + DRFLAC_ASSERT(memoryStream != NULL); + DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ + + if (offset > (drflac_int64)memoryStream->dataSize) { + return DRFLAC_FALSE; + } + + if (origin == drflac_seek_origin_current) { + if (memoryStream->currentReadPos + offset <= memoryStream->dataSize) { + memoryStream->currentReadPos += offset; + } else { + return DRFLAC_FALSE; /* Trying to seek too far forward. */ + } + } else { + if ((drflac_uint32)offset <= memoryStream->dataSize) { + memoryStream->currentReadPos = offset; + } else { + return DRFLAC_FALSE; /* Trying to seek too far forward. */ + } + } + + return DRFLAC_TRUE; +} + +DRFLAC_API drflac* drflac_open_memory(const void* pData, size_t dataSize, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac__memory_stream memoryStream; + drflac* pFlac; + + memoryStream.data = (const drflac_uint8*)pData; + memoryStream.dataSize = dataSize; + memoryStream.currentReadPos = 0; + pFlac = drflac_open(drflac__on_read_memory, drflac__on_seek_memory, &memoryStream, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + pFlac->memoryStream = memoryStream; + + /* This is an awful hack... */ +#ifndef DR_FLAC_NO_OGG + if (pFlac->container == drflac_container_ogg) + { + drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; + oggbs->pUserData = &pFlac->memoryStream; + } + else +#endif + { + pFlac->bs.pUserData = &pFlac->memoryStream; + } + + return pFlac; +} + +DRFLAC_API drflac* drflac_open_memory_with_metadata(const void* pData, size_t dataSize, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac__memory_stream memoryStream; + drflac* pFlac; + + memoryStream.data = (const drflac_uint8*)pData; + memoryStream.dataSize = dataSize; + memoryStream.currentReadPos = 0; + pFlac = drflac_open_with_metadata_private(drflac__on_read_memory, drflac__on_seek_memory, onMeta, drflac_container_unknown, &memoryStream, pUserData, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + pFlac->memoryStream = memoryStream; + + /* This is an awful hack... */ +#ifndef DR_FLAC_NO_OGG + if (pFlac->container == drflac_container_ogg) + { + drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; + oggbs->pUserData = &pFlac->memoryStream; + } + else +#endif + { + pFlac->bs.pUserData = &pFlac->memoryStream; + } + + return pFlac; +} + + + +DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + return drflac_open_with_metadata_private(onRead, onSeek, NULL, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); +} +DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + return drflac_open_with_metadata_private(onRead, onSeek, NULL, container, pUserData, pUserData, pAllocationCallbacks); +} + +DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + return drflac_open_with_metadata_private(onRead, onSeek, onMeta, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); +} +DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + return drflac_open_with_metadata_private(onRead, onSeek, onMeta, container, pUserData, pUserData, pAllocationCallbacks); +} + +DRFLAC_API void drflac_close(drflac* pFlac) +{ + if (pFlac == NULL) { + return; + } + +#ifndef DR_FLAC_NO_STDIO + /* + If we opened the file with drflac_open_file() we will want to close the file handle. We can know whether or not drflac_open_file() + was used by looking at the callbacks. + */ + if (pFlac->bs.onRead == drflac__on_read_stdio) { + fclose((FILE*)pFlac->bs.pUserData); + } + +#ifndef DR_FLAC_NO_OGG + /* Need to clean up Ogg streams a bit differently due to the way the bit streaming is chained. */ + if (pFlac->container == drflac_container_ogg) { + drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; + DRFLAC_ASSERT(pFlac->bs.onRead == drflac__on_read_ogg); + + if (oggbs->onRead == drflac__on_read_stdio) { + fclose((FILE*)oggbs->pUserData); + } + } +#endif +#endif + + drflac__free_from_callbacks(pFlac, &pFlac->allocationCallbacks); +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 left = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 left0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 left1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 left2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 left3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 right0 = left0 - side0; + drflac_uint32 right1 = left1 - side1; + drflac_uint32 right2 = left2 - side2; + drflac_uint32 right3 = left3 - side3; + + pOutputSamples[i*8+0] = (drflac_int32)left0; + pOutputSamples[i*8+1] = (drflac_int32)right0; + pOutputSamples[i*8+2] = (drflac_int32)left1; + pOutputSamples[i*8+3] = (drflac_int32)right1; + pOutputSamples[i*8+4] = (drflac_int32)left2; + pOutputSamples[i*8+5] = (drflac_int32)right2; + pOutputSamples[i*8+6] = (drflac_int32)left3; + pOutputSamples[i*8+7] = (drflac_int32)right3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + for (i = 0; i < frameCount4; ++i) { + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i right = _mm_sub_epi32(left, side); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t left; + uint32x4_t side; + uint32x4_t right; + + left = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + right = vsubq_u32(left, side); + + drflac__vst2q_u32((drflac_uint32*)pOutputSamples + i*8, vzipq_u32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_left_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_left_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s32__decode_left_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s32__decode_left_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 side = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 right = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 side0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 side1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 side2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 side3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 right0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 right1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 right2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 right3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 left0 = right0 + side0; + drflac_uint32 left1 = right1 + side1; + drflac_uint32 left2 = right2 + side2; + drflac_uint32 left3 = right3 + side3; + + pOutputSamples[i*8+0] = (drflac_int32)left0; + pOutputSamples[i*8+1] = (drflac_int32)right0; + pOutputSamples[i*8+2] = (drflac_int32)left1; + pOutputSamples[i*8+3] = (drflac_int32)right1; + pOutputSamples[i*8+4] = (drflac_int32)left2; + pOutputSamples[i*8+5] = (drflac_int32)right2; + pOutputSamples[i*8+6] = (drflac_int32)left3; + pOutputSamples[i*8+7] = (drflac_int32)right3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + for (i = 0; i < frameCount4; ++i) { + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i left = _mm_add_epi32(right, side); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t side; + uint32x4_t right; + uint32x4_t left; + + side = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + left = vaddq_u32(right, side); + + drflac__vst2q_u32((drflac_uint32*)pOutputSamples + i*8, vzipq_u32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_right_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_right_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s32__decode_right_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s32__decode_right_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample); + pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_int32 shift = unusedBitsPerSample; + + if (shift > 0) { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = (mid0 + side0) << shift; + temp1L = (mid1 + side1) << shift; + temp2L = (mid2 + side2) << shift; + temp3L = (mid3 + side3) << shift; + + temp0R = (mid0 - side0) << shift; + temp1R = (mid1 - side1) << shift; + temp2R = (mid2 - side2) << shift; + temp3R = (mid3 - side3) << shift; + + pOutputSamples[i*8+0] = (drflac_int32)temp0L; + pOutputSamples[i*8+1] = (drflac_int32)temp0R; + pOutputSamples[i*8+2] = (drflac_int32)temp1L; + pOutputSamples[i*8+3] = (drflac_int32)temp1R; + pOutputSamples[i*8+4] = (drflac_int32)temp2L; + pOutputSamples[i*8+5] = (drflac_int32)temp2R; + pOutputSamples[i*8+6] = (drflac_int32)temp3L; + pOutputSamples[i*8+7] = (drflac_int32)temp3R; + } + } else { + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = (drflac_uint32)((drflac_int32)(mid0 + side0) >> 1); + temp1L = (drflac_uint32)((drflac_int32)(mid1 + side1) >> 1); + temp2L = (drflac_uint32)((drflac_int32)(mid2 + side2) >> 1); + temp3L = (drflac_uint32)((drflac_int32)(mid3 + side3) >> 1); + + temp0R = (drflac_uint32)((drflac_int32)(mid0 - side0) >> 1); + temp1R = (drflac_uint32)((drflac_int32)(mid1 - side1) >> 1); + temp2R = (drflac_uint32)((drflac_int32)(mid2 - side2) >> 1); + temp3R = (drflac_uint32)((drflac_int32)(mid3 - side3) >> 1); + + pOutputSamples[i*8+0] = (drflac_int32)temp0L; + pOutputSamples[i*8+1] = (drflac_int32)temp0R; + pOutputSamples[i*8+2] = (drflac_int32)temp1L; + pOutputSamples[i*8+3] = (drflac_int32)temp1R; + pOutputSamples[i*8+4] = (drflac_int32)temp2L; + pOutputSamples[i*8+5] = (drflac_int32)temp2R; + pOutputSamples[i*8+6] = (drflac_int32)temp3L; + pOutputSamples[i*8+7] = (drflac_int32)temp3R; + } + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample); + pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_int32 shift = unusedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i left; + __m128i right; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + left = _mm_srai_epi32(_mm_add_epi32(mid, side), 1); + right = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)(mid + side) >> 1; + pOutputSamples[i*2+1] = (drflac_int32)(mid - side) >> 1; + } + } else { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i left; + __m128i right; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + left = _mm_slli_epi32(_mm_add_epi32(mid, side), shift); + right = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift); + pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift); + } + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_int32 shift = unusedBitsPerSample; + int32x4_t wbpsShift0_4; /* wbps = Wasted Bits Per Sample */ + int32x4_t wbpsShift1_4; /* wbps = Wasted Bits Per Sample */ + uint32x4_t one4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + wbpsShift0_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + wbpsShift1_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + one4 = vdupq_n_u32(1); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t left; + int32x4_t right; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, one4)); + + left = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1); + right = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1); + + drflac__vst2q_s32(pOutputSamples + i*8, vzipq_s32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)(mid + side) >> 1; + pOutputSamples[i*2+1] = (drflac_int32)(mid - side) >> 1; + } + } else { + int32x4_t shift4; + + shift -= 1; + shift4 = vdupq_n_s32(shift); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t left; + int32x4_t right; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, one4)); + + left = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4)); + right = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4)); + + drflac__vst2q_s32(pOutputSamples + i*8, vzipq_s32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift); + pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift); + } + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_mid_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_mid_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s32__decode_mid_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s32__decode_mid_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample)); + pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample)); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 tempL0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 tempL1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 tempL2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 tempL3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 tempR0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 tempR1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 tempR2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 tempR3 = pInputSamples1U32[i*4+3] << shift1; + + pOutputSamples[i*8+0] = (drflac_int32)tempL0; + pOutputSamples[i*8+1] = (drflac_int32)tempR0; + pOutputSamples[i*8+2] = (drflac_int32)tempL1; + pOutputSamples[i*8+3] = (drflac_int32)tempR1; + pOutputSamples[i*8+4] = (drflac_int32)tempL2; + pOutputSamples[i*8+5] = (drflac_int32)tempR2; + pOutputSamples[i*8+6] = (drflac_int32)tempL3; + pOutputSamples[i*8+7] = (drflac_int32)tempR3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0); + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0); + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1); + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + int32x4_t shift4_0 = vdupq_n_s32(shift0); + int32x4_t shift4_1 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + int32x4_t left; + int32x4_t right; + + left = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift4_0)); + right = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift4_1)); + + drflac__vst2q_s32(pOutputSamples + i*8, vzipq_s32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0); + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_independent_stereo__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_independent_stereo__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s32__decode_independent_stereo__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s32__decode_independent_stereo__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s32(drflac* pFlac, drflac_uint64 framesToRead, drflac_int32* pBufferOut) +{ + drflac_uint64 framesRead; + drflac_uint32 unusedBitsPerSample; + + if (pFlac == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drflac__seek_forward_by_pcm_frames(pFlac, framesToRead); + } + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 32); + unusedBitsPerSample = 32 - pFlac->bitsPerSample; + + framesRead = 0; + while (framesToRead > 0) { + /* If we've run out of samples in this frame, go to the next. */ + if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + break; /* Couldn't read the next frame, so just break from the loop and return. */ + } + } else { + unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); + drflac_uint64 iFirstPCMFrame = pFlac->currentFLACFrame.header.blockSizeInPCMFrames - pFlac->currentFLACFrame.pcmFramesRemaining; + drflac_uint64 frameCountThisIteration = framesToRead; + + if (frameCountThisIteration > pFlac->currentFLACFrame.pcmFramesRemaining) { + frameCountThisIteration = pFlac->currentFLACFrame.pcmFramesRemaining; + } + + if (channelCount == 2) { + const drflac_int32* pDecodedSamples0 = pFlac->currentFLACFrame.subframes[0].pSamplesS32 + iFirstPCMFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFLACFrame.subframes[1].pSamplesS32 + iFirstPCMFrame; + + switch (pFlac->currentFLACFrame.header.channelAssignment) + { + case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: + { + drflac_read_pcm_frames_s32__decode_left_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: + { + drflac_read_pcm_frames_s32__decode_right_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: + { + drflac_read_pcm_frames_s32__decode_mid_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: + default: + { + drflac_read_pcm_frames_s32__decode_independent_stereo(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + } + } else { + /* Generic interleaving. */ + drflac_uint64 i; + for (i = 0; i < frameCountThisIteration; ++i) { + unsigned int j; + for (j = 0; j < channelCount; ++j) { + pBufferOut[(i*channelCount)+j] = (drflac_int32)((drflac_uint32)(pFlac->currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[j].wastedBitsPerSample)); + } + } + } + + framesRead += frameCountThisIteration; + pBufferOut += frameCountThisIteration * channelCount; + framesToRead -= frameCountThisIteration; + pFlac->currentPCMFrame += frameCountThisIteration; + pFlac->currentFLACFrame.pcmFramesRemaining -= (drflac_uint32)frameCountThisIteration; + } + } + + return framesRead; +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 left = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 right = left - side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 left0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 left1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 left2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 left3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 right0 = left0 - side0; + drflac_uint32 right1 = left1 - side1; + drflac_uint32 right2 = left2 - side2; + drflac_uint32 right3 = left3 - side3; + + left0 >>= 16; + left1 >>= 16; + left2 >>= 16; + left3 >>= 16; + + right0 >>= 16; + right1 >>= 16; + right2 >>= 16; + right3 >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)left0; + pOutputSamples[i*8+1] = (drflac_int16)right0; + pOutputSamples[i*8+2] = (drflac_int16)left1; + pOutputSamples[i*8+3] = (drflac_int16)right1; + pOutputSamples[i*8+4] = (drflac_int16)left2; + pOutputSamples[i*8+5] = (drflac_int16)right2; + pOutputSamples[i*8+6] = (drflac_int16)left3; + pOutputSamples[i*8+7] = (drflac_int16)right3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + for (i = 0; i < frameCount4; ++i) { + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i right = _mm_sub_epi32(left, side); + + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t left; + uint32x4_t side; + uint32x4_t right; + + left = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + right = vsubq_u32(left, side); + + left = vshrq_n_u32(left, 16); + right = vshrq_n_u32(right, 16); + + drflac__vst2q_u16((drflac_uint16*)pOutputSamples + i*8, vzip_u16(vmovn_u32(left), vmovn_u32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_left_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_left_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s16__decode_left_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s16__decode_left_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 side = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 right = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 left = right + side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 side0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 side1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 side2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 side3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 right0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 right1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 right2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 right3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 left0 = right0 + side0; + drflac_uint32 left1 = right1 + side1; + drflac_uint32 left2 = right2 + side2; + drflac_uint32 left3 = right3 + side3; + + left0 >>= 16; + left1 >>= 16; + left2 >>= 16; + left3 >>= 16; + + right0 >>= 16; + right1 >>= 16; + right2 >>= 16; + right3 >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)left0; + pOutputSamples[i*8+1] = (drflac_int16)right0; + pOutputSamples[i*8+2] = (drflac_int16)left1; + pOutputSamples[i*8+3] = (drflac_int16)right1; + pOutputSamples[i*8+4] = (drflac_int16)left2; + pOutputSamples[i*8+5] = (drflac_int16)right2; + pOutputSamples[i*8+6] = (drflac_int16)left3; + pOutputSamples[i*8+7] = (drflac_int16)right3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + for (i = 0; i < frameCount4; ++i) { + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i left = _mm_add_epi32(right, side); + + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t side; + uint32x4_t right; + uint32x4_t left; + + side = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + left = vaddq_u32(right, side); + + left = vshrq_n_u32(left, 16); + right = vshrq_n_u32(right, 16); + + drflac__vst2q_u16((drflac_uint16*)pOutputSamples + i*8, vzip_u16(vmovn_u32(left), vmovn_u32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_right_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_right_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s16__decode_right_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s16__decode_right_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + drflac_uint32 mid = (drflac_uint32)pInputSamples0[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample) >> 16); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample; + + if (shift > 0) { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = (mid0 + side0) << shift; + temp1L = (mid1 + side1) << shift; + temp2L = (mid2 + side2) << shift; + temp3L = (mid3 + side3) << shift; + + temp0R = (mid0 - side0) << shift; + temp1R = (mid1 - side1) << shift; + temp2R = (mid2 - side2) << shift; + temp3R = (mid3 - side3) << shift; + + temp0L >>= 16; + temp1L >>= 16; + temp2L >>= 16; + temp3L >>= 16; + + temp0R >>= 16; + temp1R >>= 16; + temp2R >>= 16; + temp3R >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)temp0L; + pOutputSamples[i*8+1] = (drflac_int16)temp0R; + pOutputSamples[i*8+2] = (drflac_int16)temp1L; + pOutputSamples[i*8+3] = (drflac_int16)temp1R; + pOutputSamples[i*8+4] = (drflac_int16)temp2L; + pOutputSamples[i*8+5] = (drflac_int16)temp2R; + pOutputSamples[i*8+6] = (drflac_int16)temp3L; + pOutputSamples[i*8+7] = (drflac_int16)temp3R; + } + } else { + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = ((drflac_int32)(mid0 + side0) >> 1); + temp1L = ((drflac_int32)(mid1 + side1) >> 1); + temp2L = ((drflac_int32)(mid2 + side2) >> 1); + temp3L = ((drflac_int32)(mid3 + side3) >> 1); + + temp0R = ((drflac_int32)(mid0 - side0) >> 1); + temp1R = ((drflac_int32)(mid1 - side1) >> 1); + temp2R = ((drflac_int32)(mid2 - side2) >> 1); + temp3R = ((drflac_int32)(mid3 - side3) >> 1); + + temp0L >>= 16; + temp1L >>= 16; + temp2L >>= 16; + temp3L >>= 16; + + temp0R >>= 16; + temp1R >>= 16; + temp2R >>= 16; + temp3R >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)temp0L; + pOutputSamples[i*8+1] = (drflac_int16)temp0R; + pOutputSamples[i*8+2] = (drflac_int16)temp1L; + pOutputSamples[i*8+3] = (drflac_int16)temp1R; + pOutputSamples[i*8+4] = (drflac_int16)temp2L; + pOutputSamples[i*8+5] = (drflac_int16)temp2R; + pOutputSamples[i*8+6] = (drflac_int16)temp3L; + pOutputSamples[i*8+7] = (drflac_int16)temp3R; + } + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample) >> 16); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i left; + __m128i right; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + left = _mm_srai_epi32(_mm_add_epi32(mid, side), 1); + right = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1); + + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((drflac_int32)(mid + side) >> 1) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((drflac_int32)(mid - side) >> 1) >> 16); + } + } else { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i left; + __m128i right; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + left = _mm_slli_epi32(_mm_add_epi32(mid, side), shift); + right = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift); + + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((mid + side) << shift) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((mid - side) << shift) >> 16); + } + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample; + int32x4_t wbpsShift0_4; /* wbps = Wasted Bits Per Sample */ + int32x4_t wbpsShift1_4; /* wbps = Wasted Bits Per Sample */ + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + wbpsShift0_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + wbpsShift1_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t left; + int32x4_t right; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); + + left = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1); + right = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1); + + left = vshrq_n_s32(left, 16); + right = vshrq_n_s32(right, 16); + + drflac__vst2q_s16(pOutputSamples + i*8, vzip_s16(vmovn_s32(left), vmovn_s32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((drflac_int32)(mid + side) >> 1) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((drflac_int32)(mid - side) >> 1) >> 16); + } + } else { + int32x4_t shift4; + + shift -= 1; + shift4 = vdupq_n_s32(shift); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t left; + int32x4_t right; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); + + left = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4)); + right = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4)); + + left = vshrq_n_s32(left, 16); + right = vshrq_n_s32(right, 16); + + drflac__vst2q_s16(pOutputSamples + i*8, vzip_s16(vmovn_s32(left), vmovn_s32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((mid + side) << shift) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((mid - side) << shift) >> 16); + } + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_mid_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_mid_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s16__decode_mid_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s16__decode_mid_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int16)((drflac_int32)((drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample)) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)((drflac_int32)((drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample)) >> 16); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 tempL0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 tempL1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 tempL2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 tempL3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 tempR0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 tempR1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 tempR2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 tempR3 = pInputSamples1U32[i*4+3] << shift1; + + tempL0 >>= 16; + tempL1 >>= 16; + tempL2 >>= 16; + tempL3 >>= 16; + + tempR0 >>= 16; + tempR1 >>= 16; + tempR2 >>= 16; + tempR3 >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)tempL0; + pOutputSamples[i*8+1] = (drflac_int16)tempR0; + pOutputSamples[i*8+2] = (drflac_int16)tempL1; + pOutputSamples[i*8+3] = (drflac_int16)tempR1; + pOutputSamples[i*8+4] = (drflac_int16)tempL2; + pOutputSamples[i*8+5] = (drflac_int16)tempR2; + pOutputSamples[i*8+6] = (drflac_int16)tempL3; + pOutputSamples[i*8+7] = (drflac_int16)tempR3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int16)((pInputSamples0U32[i] << shift0) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)((pInputSamples1U32[i] << shift1) >> 16); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + /* At this point we have results. We can now pack and interleave these into a single __m128i object and then store the in the output buffer. */ + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int16)((pInputSamples0U32[i] << shift0) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)((pInputSamples1U32[i] << shift1) >> 16); + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + int32x4_t shift0_4 = vdupq_n_s32(shift0); + int32x4_t shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + int32x4_t left; + int32x4_t right; + + left = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4)); + right = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4)); + + left = vshrq_n_s32(left, 16); + right = vshrq_n_s32(right, 16); + + drflac__vst2q_s16(pOutputSamples + i*8, vzip_s16(vmovn_s32(left), vmovn_s32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int16)((pInputSamples0U32[i] << shift0) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)((pInputSamples1U32[i] << shift1) >> 16); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_independent_stereo__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_independent_stereo__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s16__decode_independent_stereo__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s16__decode_independent_stereo__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s16(drflac* pFlac, drflac_uint64 framesToRead, drflac_int16* pBufferOut) +{ + drflac_uint64 framesRead; + drflac_uint32 unusedBitsPerSample; + + if (pFlac == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drflac__seek_forward_by_pcm_frames(pFlac, framesToRead); + } + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 32); + unusedBitsPerSample = 32 - pFlac->bitsPerSample; + + framesRead = 0; + while (framesToRead > 0) { + /* If we've run out of samples in this frame, go to the next. */ + if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + break; /* Couldn't read the next frame, so just break from the loop and return. */ + } + } else { + unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); + drflac_uint64 iFirstPCMFrame = pFlac->currentFLACFrame.header.blockSizeInPCMFrames - pFlac->currentFLACFrame.pcmFramesRemaining; + drflac_uint64 frameCountThisIteration = framesToRead; + + if (frameCountThisIteration > pFlac->currentFLACFrame.pcmFramesRemaining) { + frameCountThisIteration = pFlac->currentFLACFrame.pcmFramesRemaining; + } + + if (channelCount == 2) { + const drflac_int32* pDecodedSamples0 = pFlac->currentFLACFrame.subframes[0].pSamplesS32 + iFirstPCMFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFLACFrame.subframes[1].pSamplesS32 + iFirstPCMFrame; + + switch (pFlac->currentFLACFrame.header.channelAssignment) + { + case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: + { + drflac_read_pcm_frames_s16__decode_left_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: + { + drflac_read_pcm_frames_s16__decode_right_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: + { + drflac_read_pcm_frames_s16__decode_mid_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: + default: + { + drflac_read_pcm_frames_s16__decode_independent_stereo(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + } + } else { + /* Generic interleaving. */ + drflac_uint64 i; + for (i = 0; i < frameCountThisIteration; ++i) { + unsigned int j; + for (j = 0; j < channelCount; ++j) { + drflac_int32 sampleS32 = (drflac_int32)((drflac_uint32)(pFlac->currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[j].wastedBitsPerSample)); + pBufferOut[(i*channelCount)+j] = (drflac_int16)(sampleS32 >> 16); + } + } + } + + framesRead += frameCountThisIteration; + pBufferOut += frameCountThisIteration * channelCount; + framesToRead -= frameCountThisIteration; + pFlac->currentPCMFrame += frameCountThisIteration; + pFlac->currentFLACFrame.pcmFramesRemaining -= (drflac_uint32)frameCountThisIteration; + } + } + + return framesRead; +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 left = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (float)((drflac_int32)left / 2147483648.0); + pOutputSamples[i*2+1] = (float)((drflac_int32)right / 2147483648.0); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + float factor = 1 / 2147483648.0; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 left0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 left1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 left2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 left3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 right0 = left0 - side0; + drflac_uint32 right1 = left1 - side1; + drflac_uint32 right2 = left2 - side2; + drflac_uint32 right3 = left3 - side3; + + pOutputSamples[i*8+0] = (drflac_int32)left0 * factor; + pOutputSamples[i*8+1] = (drflac_int32)right0 * factor; + pOutputSamples[i*8+2] = (drflac_int32)left1 * factor; + pOutputSamples[i*8+3] = (drflac_int32)right1 * factor; + pOutputSamples[i*8+4] = (drflac_int32)left2 * factor; + pOutputSamples[i*8+5] = (drflac_int32)right2 * factor; + pOutputSamples[i*8+6] = (drflac_int32)left3 * factor; + pOutputSamples[i*8+7] = (drflac_int32)right3 * factor; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left * factor; + pOutputSamples[i*2+1] = (drflac_int32)right * factor; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + __m128 factor; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor = _mm_set1_ps(1.0f / 8388608.0f); + + for (i = 0; i < frameCount4; ++i) { + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i right = _mm_sub_epi32(left, side); + __m128 leftf = _mm_mul_ps(_mm_cvtepi32_ps(left), factor); + __m128 rightf = _mm_mul_ps(_mm_cvtepi32_ps(right), factor); + + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; + pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + float32x4_t factor4; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor4 = vdupq_n_f32(1.0f / 8388608.0f); + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t left; + uint32x4_t side; + uint32x4_t right; + float32x4_t leftf; + float32x4_t rightf; + + left = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + right = vsubq_u32(left, side); + leftf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(left)), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(right)), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; + pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_left_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_left_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_f32__decode_left_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_left_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 side = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 right = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (float)((drflac_int32)left / 2147483648.0); + pOutputSamples[i*2+1] = (float)((drflac_int32)right / 2147483648.0); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + float factor = 1 / 2147483648.0; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 side0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 side1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 side2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 side3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 right0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 right1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 right2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 right3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 left0 = right0 + side0; + drflac_uint32 left1 = right1 + side1; + drflac_uint32 left2 = right2 + side2; + drflac_uint32 left3 = right3 + side3; + + pOutputSamples[i*8+0] = (drflac_int32)left0 * factor; + pOutputSamples[i*8+1] = (drflac_int32)right0 * factor; + pOutputSamples[i*8+2] = (drflac_int32)left1 * factor; + pOutputSamples[i*8+3] = (drflac_int32)right1 * factor; + pOutputSamples[i*8+4] = (drflac_int32)left2 * factor; + pOutputSamples[i*8+5] = (drflac_int32)right2 * factor; + pOutputSamples[i*8+6] = (drflac_int32)left3 * factor; + pOutputSamples[i*8+7] = (drflac_int32)right3 * factor; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left * factor; + pOutputSamples[i*2+1] = (drflac_int32)right * factor; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + __m128 factor; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor = _mm_set1_ps(1.0f / 8388608.0f); + + for (i = 0; i < frameCount4; ++i) { + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i left = _mm_add_epi32(right, side); + __m128 leftf = _mm_mul_ps(_mm_cvtepi32_ps(left), factor); + __m128 rightf = _mm_mul_ps(_mm_cvtepi32_ps(right), factor); + + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; + pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + float32x4_t factor4; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor4 = vdupq_n_f32(1.0f / 8388608.0f); + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t side; + uint32x4_t right; + uint32x4_t left; + float32x4_t leftf; + float32x4_t rightf; + + side = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + left = vaddq_u32(right, side); + leftf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(left)), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(right)), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; + pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_right_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_right_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_f32__decode_right_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_right_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + drflac_uint32 mid = (drflac_uint32)pInputSamples0[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (float)((((drflac_int32)(mid + side) >> 1) << (unusedBitsPerSample)) / 2147483648.0); + pOutputSamples[i*2+1] = (float)((((drflac_int32)(mid - side) >> 1) << (unusedBitsPerSample)) / 2147483648.0); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample; + float factor = 1 / 2147483648.0; + + if (shift > 0) { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = (mid0 + side0) << shift; + temp1L = (mid1 + side1) << shift; + temp2L = (mid2 + side2) << shift; + temp3L = (mid3 + side3) << shift; + + temp0R = (mid0 - side0) << shift; + temp1R = (mid1 - side1) << shift; + temp2R = (mid2 - side2) << shift; + temp3R = (mid3 - side3) << shift; + + pOutputSamples[i*8+0] = (drflac_int32)temp0L * factor; + pOutputSamples[i*8+1] = (drflac_int32)temp0R * factor; + pOutputSamples[i*8+2] = (drflac_int32)temp1L * factor; + pOutputSamples[i*8+3] = (drflac_int32)temp1R * factor; + pOutputSamples[i*8+4] = (drflac_int32)temp2L * factor; + pOutputSamples[i*8+5] = (drflac_int32)temp2R * factor; + pOutputSamples[i*8+6] = (drflac_int32)temp3L * factor; + pOutputSamples[i*8+7] = (drflac_int32)temp3R * factor; + } + } else { + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = (drflac_uint32)((drflac_int32)(mid0 + side0) >> 1); + temp1L = (drflac_uint32)((drflac_int32)(mid1 + side1) >> 1); + temp2L = (drflac_uint32)((drflac_int32)(mid2 + side2) >> 1); + temp3L = (drflac_uint32)((drflac_int32)(mid3 + side3) >> 1); + + temp0R = (drflac_uint32)((drflac_int32)(mid0 - side0) >> 1); + temp1R = (drflac_uint32)((drflac_int32)(mid1 - side1) >> 1); + temp2R = (drflac_uint32)((drflac_int32)(mid2 - side2) >> 1); + temp3R = (drflac_uint32)((drflac_int32)(mid3 - side3) >> 1); + + pOutputSamples[i*8+0] = (drflac_int32)temp0L * factor; + pOutputSamples[i*8+1] = (drflac_int32)temp0R * factor; + pOutputSamples[i*8+2] = (drflac_int32)temp1L * factor; + pOutputSamples[i*8+3] = (drflac_int32)temp1R * factor; + pOutputSamples[i*8+4] = (drflac_int32)temp2L * factor; + pOutputSamples[i*8+5] = (drflac_int32)temp2R * factor; + pOutputSamples[i*8+6] = (drflac_int32)temp3L * factor; + pOutputSamples[i*8+7] = (drflac_int32)temp3R * factor; + } + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample) * factor; + pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample) * factor; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample - 8; + float factor; + __m128 factor128; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor = 1.0f / 8388608.0f; + factor128 = _mm_set1_ps(factor); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i tempL; + __m128i tempR; + __m128 leftf; + __m128 rightf; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + tempL = _mm_srai_epi32(_mm_add_epi32(mid, side), 1); + tempR = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1); + + leftf = _mm_mul_ps(_mm_cvtepi32_ps(tempL), factor128); + rightf = _mm_mul_ps(_mm_cvtepi32_ps(tempR), factor128); + + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = ((drflac_int32)(mid + side) >> 1) * factor; + pOutputSamples[i*2+1] = ((drflac_int32)(mid - side) >> 1) * factor; + } + } else { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i tempL; + __m128i tempR; + __m128 leftf; + __m128 rightf; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + tempL = _mm_slli_epi32(_mm_add_epi32(mid, side), shift); + tempR = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift); + + leftf = _mm_mul_ps(_mm_cvtepi32_ps(tempL), factor128); + rightf = _mm_mul_ps(_mm_cvtepi32_ps(tempR), factor128); + + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift) * factor; + pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift) * factor; + } + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample - 8; + float factor; + float32x4_t factor4; + int32x4_t shift4; + int32x4_t wbps0_4; /* Wasted Bits Per Sample */ + int32x4_t wbps1_4; /* Wasted Bits Per Sample */ + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor = 1.0f / 8388608.0f; + factor4 = vdupq_n_f32(factor); + wbps0_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + wbps1_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + int32x4_t lefti; + int32x4_t righti; + float32x4_t leftf; + float32x4_t rightf; + + uint32x4_t mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbps0_4); + uint32x4_t side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbps1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); + + lefti = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1); + righti = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1); + + leftf = vmulq_f32(vcvtq_f32_s32(lefti), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(righti), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = ((drflac_int32)(mid + side) >> 1) * factor; + pOutputSamples[i*2+1] = ((drflac_int32)(mid - side) >> 1) * factor; + } + } else { + shift -= 1; + shift4 = vdupq_n_s32(shift); + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t lefti; + int32x4_t righti; + float32x4_t leftf; + float32x4_t rightf; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbps0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbps1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); + + lefti = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4)); + righti = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4)); + + leftf = vmulq_f32(vcvtq_f32_s32(lefti), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(righti), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift) * factor; + pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift) * factor; + } + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_mid_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_mid_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_f32__decode_mid_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_mid_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + pOutputSamples[i*2+0] = (float)((drflac_int32)((drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample)) / 2147483648.0); + pOutputSamples[i*2+1] = (float)((drflac_int32)((drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample)) / 2147483648.0); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + float factor = 1 / 2147483648.0; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 tempL0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 tempL1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 tempL2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 tempL3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 tempR0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 tempR1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 tempR2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 tempR3 = pInputSamples1U32[i*4+3] << shift1; + + pOutputSamples[i*8+0] = (drflac_int32)tempL0 * factor; + pOutputSamples[i*8+1] = (drflac_int32)tempR0 * factor; + pOutputSamples[i*8+2] = (drflac_int32)tempL1 * factor; + pOutputSamples[i*8+3] = (drflac_int32)tempR1 * factor; + pOutputSamples[i*8+4] = (drflac_int32)tempL2 * factor; + pOutputSamples[i*8+5] = (drflac_int32)tempR2 * factor; + pOutputSamples[i*8+6] = (drflac_int32)tempL3 * factor; + pOutputSamples[i*8+7] = (drflac_int32)tempR3 * factor; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0) * factor; + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1) * factor; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + + float factor = 1.0f / 8388608.0f; + __m128 factor128 = _mm_set1_ps(factor); + + for (i = 0; i < frameCount4; ++i) { + __m128i lefti; + __m128i righti; + __m128 leftf; + __m128 rightf; + + lefti = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + righti = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + + leftf = _mm_mul_ps(_mm_cvtepi32_ps(lefti), factor128); + rightf = _mm_mul_ps(_mm_cvtepi32_ps(righti), factor128); + + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0) * factor; + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1) * factor; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + + float factor = 1.0f / 8388608.0f; + float32x4_t factor4 = vdupq_n_f32(factor); + int32x4_t shift0_4 = vdupq_n_s32(shift0); + int32x4_t shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + int32x4_t lefti; + int32x4_t righti; + float32x4_t leftf; + float32x4_t rightf; + + lefti = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4)); + righti = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4)); + + leftf = vmulq_f32(vcvtq_f32_s32(lefti), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(righti), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0) * factor; + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1) * factor; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_independent_stereo__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_independent_stereo__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_f32__decode_independent_stereo__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_independent_stereo__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRead, float* pBufferOut) +{ + drflac_uint64 framesRead; + drflac_uint32 unusedBitsPerSample; + + if (pFlac == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drflac__seek_forward_by_pcm_frames(pFlac, framesToRead); + } + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 32); + unusedBitsPerSample = 32 - pFlac->bitsPerSample; + + framesRead = 0; + while (framesToRead > 0) { + /* If we've run out of samples in this frame, go to the next. */ + if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + break; /* Couldn't read the next frame, so just break from the loop and return. */ + } + } else { + unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); + drflac_uint64 iFirstPCMFrame = pFlac->currentFLACFrame.header.blockSizeInPCMFrames - pFlac->currentFLACFrame.pcmFramesRemaining; + drflac_uint64 frameCountThisIteration = framesToRead; + + if (frameCountThisIteration > pFlac->currentFLACFrame.pcmFramesRemaining) { + frameCountThisIteration = pFlac->currentFLACFrame.pcmFramesRemaining; + } + + if (channelCount == 2) { + const drflac_int32* pDecodedSamples0 = pFlac->currentFLACFrame.subframes[0].pSamplesS32 + iFirstPCMFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFLACFrame.subframes[1].pSamplesS32 + iFirstPCMFrame; + + switch (pFlac->currentFLACFrame.header.channelAssignment) + { + case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: + { + drflac_read_pcm_frames_f32__decode_left_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: + { + drflac_read_pcm_frames_f32__decode_right_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: + { + drflac_read_pcm_frames_f32__decode_mid_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: + default: + { + drflac_read_pcm_frames_f32__decode_independent_stereo(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + } + } else { + /* Generic interleaving. */ + drflac_uint64 i; + for (i = 0; i < frameCountThisIteration; ++i) { + unsigned int j; + for (j = 0; j < channelCount; ++j) { + drflac_int32 sampleS32 = (drflac_int32)((drflac_uint32)(pFlac->currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[j].wastedBitsPerSample)); + pBufferOut[(i*channelCount)+j] = (float)(sampleS32 / 2147483648.0); + } + } + } + + framesRead += frameCountThisIteration; + pBufferOut += frameCountThisIteration * channelCount; + framesToRead -= frameCountThisIteration; + pFlac->currentPCMFrame += frameCountThisIteration; + pFlac->currentFLACFrame.pcmFramesRemaining -= (unsigned int)frameCountThisIteration; + } + } + + return framesRead; +} + + +DRFLAC_API drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex) +{ + if (pFlac == NULL) { + return DRFLAC_FALSE; + } + + /* Don't do anything if we're already on the seek point. */ + if (pFlac->currentPCMFrame == pcmFrameIndex) { + return DRFLAC_TRUE; + } + + /* + If we don't know where the first frame begins then we can't seek. This will happen when the STREAMINFO block was not present + when the decoder was opened. + */ + if (pFlac->firstFLACFramePosInBytes == 0) { + return DRFLAC_FALSE; + } + + if (pcmFrameIndex == 0) { + pFlac->currentPCMFrame = 0; + return drflac__seek_to_first_frame(pFlac); + } else { + drflac_bool32 wasSuccessful = DRFLAC_FALSE; + drflac_uint64 originalPCMFrame = pFlac->currentPCMFrame; + + /* Clamp the sample to the end. */ + if (pcmFrameIndex > pFlac->totalPCMFrameCount) { + pcmFrameIndex = pFlac->totalPCMFrameCount; + } + + /* If the target sample and the current sample are in the same frame we just move the position forward. */ + if (pcmFrameIndex > pFlac->currentPCMFrame) { + /* Forward. */ + drflac_uint32 offset = (drflac_uint32)(pcmFrameIndex - pFlac->currentPCMFrame); + if (pFlac->currentFLACFrame.pcmFramesRemaining > offset) { + pFlac->currentFLACFrame.pcmFramesRemaining -= offset; + pFlac->currentPCMFrame = pcmFrameIndex; + return DRFLAC_TRUE; + } + } else { + /* Backward. */ + drflac_uint32 offsetAbs = (drflac_uint32)(pFlac->currentPCMFrame - pcmFrameIndex); + drflac_uint32 currentFLACFramePCMFrameCount = pFlac->currentFLACFrame.header.blockSizeInPCMFrames; + drflac_uint32 currentFLACFramePCMFramesConsumed = currentFLACFramePCMFrameCount - pFlac->currentFLACFrame.pcmFramesRemaining; + if (currentFLACFramePCMFramesConsumed > offsetAbs) { + pFlac->currentFLACFrame.pcmFramesRemaining += offsetAbs; + pFlac->currentPCMFrame = pcmFrameIndex; + return DRFLAC_TRUE; + } + } + + /* + Different techniques depending on encapsulation. Using the native FLAC seektable with Ogg encapsulation is a bit awkward so + we'll instead use Ogg's natural seeking facility. + */ +#ifndef DR_FLAC_NO_OGG + if (pFlac->container == drflac_container_ogg) + { + wasSuccessful = drflac_ogg__seek_to_pcm_frame(pFlac, pcmFrameIndex); + } + else +#endif + { + /* First try seeking via the seek table. If this fails, fall back to a brute force seek which is much slower. */ + if (/*!wasSuccessful && */!pFlac->_noSeekTableSeek) { + wasSuccessful = drflac__seek_to_pcm_frame__seek_table(pFlac, pcmFrameIndex); + } + +#if !defined(DR_FLAC_NO_CRC) + /* Fall back to binary search if seek table seeking fails. This requires the length of the stream to be known. */ + if (!wasSuccessful && !pFlac->_noBinarySearchSeek && pFlac->totalPCMFrameCount > 0) { + wasSuccessful = drflac__seek_to_pcm_frame__binary_search(pFlac, pcmFrameIndex); + } +#endif + + /* Fall back to brute force if all else fails. */ + if (!wasSuccessful && !pFlac->_noBruteForceSeek) { + wasSuccessful = drflac__seek_to_pcm_frame__brute_force(pFlac, pcmFrameIndex); + } + } + + if (wasSuccessful) { + pFlac->currentPCMFrame = pcmFrameIndex; + } else { + /* Seek failed. Try putting the decoder back to it's original state. */ + if (drflac_seek_to_pcm_frame(pFlac, originalPCMFrame) == DRFLAC_FALSE) { + /* Failed to seek back to the original PCM frame. Fall back to 0. */ + drflac_seek_to_pcm_frame(pFlac, 0); + } + } + + return wasSuccessful; + } +} + + + +/* High Level APIs */ + +/* SIZE_MAX */ +#if defined(SIZE_MAX) + #define DRFLAC_SIZE_MAX SIZE_MAX +#else + #if defined(DRFLAC_64BIT) + #define DRFLAC_SIZE_MAX ((drflac_uint64)0xFFFFFFFFFFFFFFFF) + #else + #define DRFLAC_SIZE_MAX 0xFFFFFFFF + #endif +#endif +/* End SIZE_MAX */ + + +/* Using a macro as the definition of the drflac__full_decode_and_close_*() API family. Sue me. */ +#define DRFLAC_DEFINE_FULL_READ_AND_CLOSE(extension, type) \ +static type* drflac__full_read_and_close_ ## extension (drflac* pFlac, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut)\ +{ \ + type* pSampleData = NULL; \ + drflac_uint64 totalPCMFrameCount; \ + \ + DRFLAC_ASSERT(pFlac != NULL); \ + \ + totalPCMFrameCount = pFlac->totalPCMFrameCount; \ + \ + if (totalPCMFrameCount == 0) { \ + type buffer[4096]; \ + drflac_uint64 pcmFramesRead; \ + size_t sampleDataBufferSize = sizeof(buffer); \ + \ + pSampleData = (type*)drflac__malloc_from_callbacks(sampleDataBufferSize, &pFlac->allocationCallbacks); \ + if (pSampleData == NULL) { \ + goto on_error; \ + } \ + \ + while ((pcmFramesRead = (drflac_uint64)drflac_read_pcm_frames_##extension(pFlac, sizeof(buffer)/sizeof(buffer[0])/pFlac->channels, buffer)) > 0) { \ + if (((totalPCMFrameCount + pcmFramesRead) * pFlac->channels * sizeof(type)) > sampleDataBufferSize) { \ + type* pNewSampleData; \ + size_t newSampleDataBufferSize; \ + \ + newSampleDataBufferSize = sampleDataBufferSize * 2; \ + pNewSampleData = (type*)drflac__realloc_from_callbacks(pSampleData, newSampleDataBufferSize, sampleDataBufferSize, &pFlac->allocationCallbacks); \ + if (pNewSampleData == NULL) { \ + drflac__free_from_callbacks(pSampleData, &pFlac->allocationCallbacks); \ + goto on_error; \ + } \ + \ + sampleDataBufferSize = newSampleDataBufferSize; \ + pSampleData = pNewSampleData; \ + } \ + \ + DRFLAC_COPY_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), buffer, (size_t)(pcmFramesRead*pFlac->channels*sizeof(type))); \ + totalPCMFrameCount += pcmFramesRead; \ + } \ + \ + /* At this point everything should be decoded, but we just want to fill the unused part buffer with silence - need to \ + protect those ears from random noise! */ \ + DRFLAC_ZERO_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), (size_t)(sampleDataBufferSize - totalPCMFrameCount*pFlac->channels*sizeof(type))); \ + } else { \ + drflac_uint64 dataSize = totalPCMFrameCount*pFlac->channels*sizeof(type); \ + if (dataSize > (drflac_uint64)DRFLAC_SIZE_MAX) { \ + goto on_error; /* The decoded data is too big. */ \ + } \ + \ + pSampleData = (type*)drflac__malloc_from_callbacks((size_t)dataSize, &pFlac->allocationCallbacks); /* <-- Safe cast as per the check above. */ \ + if (pSampleData == NULL) { \ + goto on_error; \ + } \ + \ + totalPCMFrameCount = drflac_read_pcm_frames_##extension(pFlac, pFlac->totalPCMFrameCount, pSampleData); \ + } \ + \ + if (sampleRateOut) *sampleRateOut = pFlac->sampleRate; \ + if (channelsOut) *channelsOut = pFlac->channels; \ + if (totalPCMFrameCountOut) *totalPCMFrameCountOut = totalPCMFrameCount; \ + \ + drflac_close(pFlac); \ + return pSampleData; \ + \ +on_error: \ + drflac_close(pFlac); \ + return NULL; \ +} + +DRFLAC_DEFINE_FULL_READ_AND_CLOSE(s32, drflac_int32) +DRFLAC_DEFINE_FULL_READ_AND_CLOSE(s16, drflac_int16) +DRFLAC_DEFINE_FULL_READ_AND_CLOSE(f32, float) + +DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalPCMFrameCountOut) { + *totalPCMFrameCountOut = 0; + } + + pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s32(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); +} + +DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalPCMFrameCountOut) { + *totalPCMFrameCountOut = 0; + } + + pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s16(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); +} + +DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalPCMFrameCountOut) { + *totalPCMFrameCountOut = 0; + } + + pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_f32(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); +} + +#ifndef DR_FLAC_NO_STDIO +DRFLAC_API drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_file(filename, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s32(pFlac, channels, sampleRate, totalPCMFrameCount); +} + +DRFLAC_API drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_file(filename, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s16(pFlac, channels, sampleRate, totalPCMFrameCount); +} + +DRFLAC_API float* drflac_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_file(filename, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_f32(pFlac, channels, sampleRate, totalPCMFrameCount); +} +#endif + +DRFLAC_API drflac_int32* drflac_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_memory(data, dataSize, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s32(pFlac, channels, sampleRate, totalPCMFrameCount); +} + +DRFLAC_API drflac_int16* drflac_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_memory(data, dataSize, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s16(pFlac, channels, sampleRate, totalPCMFrameCount); +} + +DRFLAC_API float* drflac_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + + if (sampleRate) { + *sampleRate = 0; + } + if (channels) { + *channels = 0; + } + if (totalPCMFrameCount) { + *totalPCMFrameCount = 0; + } + + pFlac = drflac_open_memory(data, dataSize, pAllocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_f32(pFlac, channels, sampleRate, totalPCMFrameCount); +} + + +DRFLAC_API void drflac_free(void* p, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + drflac__free_from_callbacks(p, pAllocationCallbacks); + } else { + drflac__free_default(p, NULL); + } +} + + + + +DRFLAC_API void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, drflac_uint32 commentCount, const void* pComments) +{ + if (pIter == NULL) { + return; + } + + pIter->countRemaining = commentCount; + pIter->pRunningData = (const char*)pComments; +} + +DRFLAC_API const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, drflac_uint32* pCommentLengthOut) +{ + drflac_int32 length; + const char* pComment; + + /* Safety. */ + if (pCommentLengthOut) { + *pCommentLengthOut = 0; + } + + if (pIter == NULL || pIter->countRemaining == 0 || pIter->pRunningData == NULL) { + return NULL; + } + + length = drflac__le2host_32_ptr_unaligned(pIter->pRunningData); + pIter->pRunningData += 4; + + pComment = pIter->pRunningData; + pIter->pRunningData += length; + pIter->countRemaining -= 1; + + if (pCommentLengthOut) { + *pCommentLengthOut = length; + } + + return pComment; +} + + + + +DRFLAC_API void drflac_init_cuesheet_track_iterator(drflac_cuesheet_track_iterator* pIter, drflac_uint32 trackCount, const void* pTrackData) +{ + if (pIter == NULL) { + return; + } + + pIter->countRemaining = trackCount; + pIter->pRunningData = (const char*)pTrackData; +} + +DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, drflac_cuesheet_track* pCuesheetTrack) +{ + drflac_cuesheet_track cuesheetTrack; + const char* pRunningData; + drflac_uint64 offsetHi; + drflac_uint64 offsetLo; + + if (pIter == NULL || pIter->countRemaining == 0 || pIter->pRunningData == NULL) { + return DRFLAC_FALSE; + } + + pRunningData = pIter->pRunningData; + + offsetHi = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + offsetLo = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + cuesheetTrack.offset = offsetLo | (offsetHi << 32); + cuesheetTrack.trackNumber = pRunningData[0]; pRunningData += 1; + DRFLAC_COPY_MEMORY(cuesheetTrack.ISRC, pRunningData, sizeof(cuesheetTrack.ISRC)); pRunningData += 12; + cuesheetTrack.isAudio = (pRunningData[0] & 0x80) != 0; + cuesheetTrack.preEmphasis = (pRunningData[0] & 0x40) != 0; pRunningData += 14; + cuesheetTrack.indexCount = pRunningData[0]; pRunningData += 1; + cuesheetTrack.pIndexPoints = (const drflac_cuesheet_track_index*)pRunningData; pRunningData += cuesheetTrack.indexCount * sizeof(drflac_cuesheet_track_index); + + pIter->pRunningData = pRunningData; + pIter->countRemaining -= 1; + + if (pCuesheetTrack) { + *pCuesheetTrack = cuesheetTrack; + } + + return DRFLAC_TRUE; +} + +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop +#endif +#endif /* dr_flac_c */ +#endif /* DR_FLAC_IMPLEMENTATION */ + + +/* +REVISION HISTORY +================ +v0.12.42 - 2023-11-02 + - Fix build for ARMv6-M. + - Fix a compilation warning with GCC. + +v0.12.41 - 2023-06-17 + - Fix an incorrect date in revision history. No functional change. + +v0.12.40 - 2023-05-22 + - Minor code restructure. No functional change. + +v0.12.39 - 2022-09-17 + - Fix compilation with DJGPP. + - Fix compilation error with Visual Studio 2019 and the ARM build. + - Fix an error with SSE 4.1 detection. + - Add support for disabling wchar_t with DR_WAV_NO_WCHAR. + - Improve compatibility with compilers which lack support for explicit struct packing. + - Improve compatibility with low-end and embedded hardware by reducing the amount of stack + allocation when loading an Ogg encapsulated file. + +v0.12.38 - 2022-04-10 + - Fix compilation error on older versions of GCC. + +v0.12.37 - 2022-02-12 + - Improve ARM detection. + +v0.12.36 - 2022-02-07 + - Fix a compilation error with the ARM build. + +v0.12.35 - 2022-02-06 + - Fix a bug due to underestimating the amount of precision required for the prediction stage. + - Fix some bugs found from fuzz testing. + +v0.12.34 - 2022-01-07 + - Fix some misalignment bugs when reading metadata. + +v0.12.33 - 2021-12-22 + - Fix a bug with seeking when the seek table does not start at PCM frame 0. + +v0.12.32 - 2021-12-11 + - Fix a warning with Clang. + +v0.12.31 - 2021-08-16 + - Silence some warnings. + +v0.12.30 - 2021-07-31 + - Fix platform detection for ARM64. + +v0.12.29 - 2021-04-02 + - Fix a bug where the running PCM frame index is set to an invalid value when over-seeking. + - Fix a decoding error due to an incorrect validation check. + +v0.12.28 - 2021-02-21 + - Fix a warning due to referencing _MSC_VER when it is undefined. + +v0.12.27 - 2021-01-31 + - Fix a static analysis warning. + +v0.12.26 - 2021-01-17 + - Fix a compilation warning due to _BSD_SOURCE being deprecated. + +v0.12.25 - 2020-12-26 + - Update documentation. + +v0.12.24 - 2020-11-29 + - Fix ARM64/NEON detection when compiling with MSVC. + +v0.12.23 - 2020-11-21 + - Fix compilation with OpenWatcom. + +v0.12.22 - 2020-11-01 + - Fix an error with the previous release. + +v0.12.21 - 2020-11-01 + - Fix a possible deadlock when seeking. + - Improve compiler support for older versions of GCC. + +v0.12.20 - 2020-09-08 + - Fix a compilation error on older compilers. + +v0.12.19 - 2020-08-30 + - Fix a bug due to an undefined 32-bit shift. + +v0.12.18 - 2020-08-14 + - Fix a crash when compiling with clang-cl. + +v0.12.17 - 2020-08-02 + - Simplify sized types. + +v0.12.16 - 2020-07-25 + - Fix a compilation warning. + +v0.12.15 - 2020-07-06 + - Check for negative LPC shifts and return an error. + +v0.12.14 - 2020-06-23 + - Add include guard for the implementation section. + +v0.12.13 - 2020-05-16 + - Add compile-time and run-time version querying. + - DRFLAC_VERSION_MINOR + - DRFLAC_VERSION_MAJOR + - DRFLAC_VERSION_REVISION + - DRFLAC_VERSION_STRING + - drflac_version() + - drflac_version_string() + +v0.12.12 - 2020-04-30 + - Fix compilation errors with VC6. + +v0.12.11 - 2020-04-19 + - Fix some pedantic warnings. + - Fix some undefined behaviour warnings. + +v0.12.10 - 2020-04-10 + - Fix some bugs when trying to seek with an invalid seek table. + +v0.12.9 - 2020-04-05 + - Fix warnings. + +v0.12.8 - 2020-04-04 + - Add drflac_open_file_w() and drflac_open_file_with_metadata_w(). + - Fix some static analysis warnings. + - Minor documentation updates. + +v0.12.7 - 2020-03-14 + - Fix compilation errors with VC6. + +v0.12.6 - 2020-03-07 + - Fix compilation error with Visual Studio .NET 2003. + +v0.12.5 - 2020-01-30 + - Silence some static analysis warnings. + +v0.12.4 - 2020-01-29 + - Silence some static analysis warnings. + +v0.12.3 - 2019-12-02 + - Fix some warnings when compiling with GCC and the -Og flag. + - Fix a crash in out-of-memory situations. + - Fix potential integer overflow bug. + - Fix some static analysis warnings. + - Fix a possible crash when using custom memory allocators without a custom realloc() implementation. + - Fix a bug with binary search seeking where the bits per sample is not a multiple of 8. + +v0.12.2 - 2019-10-07 + - Internal code clean up. + +v0.12.1 - 2019-09-29 + - Fix some Clang Static Analyzer warnings. + - Fix an unused variable warning. + +v0.12.0 - 2019-09-23 + - API CHANGE: Add support for user defined memory allocation routines. This system allows the program to specify their own memory allocation + routines with a user data pointer for client-specific contextual data. This adds an extra parameter to the end of the following APIs: + - drflac_open() + - drflac_open_relaxed() + - drflac_open_with_metadata() + - drflac_open_with_metadata_relaxed() + - drflac_open_file() + - drflac_open_file_with_metadata() + - drflac_open_memory() + - drflac_open_memory_with_metadata() + - drflac_open_and_read_pcm_frames_s32() + - drflac_open_and_read_pcm_frames_s16() + - drflac_open_and_read_pcm_frames_f32() + - drflac_open_file_and_read_pcm_frames_s32() + - drflac_open_file_and_read_pcm_frames_s16() + - drflac_open_file_and_read_pcm_frames_f32() + - drflac_open_memory_and_read_pcm_frames_s32() + - drflac_open_memory_and_read_pcm_frames_s16() + - drflac_open_memory_and_read_pcm_frames_f32() + Set this extra parameter to NULL to use defaults which is the same as the previous behaviour. Setting this NULL will use + DRFLAC_MALLOC, DRFLAC_REALLOC and DRFLAC_FREE. + - Remove deprecated APIs: + - drflac_read_s32() + - drflac_read_s16() + - drflac_read_f32() + - drflac_seek_to_sample() + - drflac_open_and_decode_s32() + - drflac_open_and_decode_s16() + - drflac_open_and_decode_f32() + - drflac_open_and_decode_file_s32() + - drflac_open_and_decode_file_s16() + - drflac_open_and_decode_file_f32() + - drflac_open_and_decode_memory_s32() + - drflac_open_and_decode_memory_s16() + - drflac_open_and_decode_memory_f32() + - Remove drflac.totalSampleCount which is now replaced with drflac.totalPCMFrameCount. You can emulate drflac.totalSampleCount + by doing pFlac->totalPCMFrameCount*pFlac->channels. + - Rename drflac.currentFrame to drflac.currentFLACFrame to remove ambiguity with PCM frames. + - Fix errors when seeking to the end of a stream. + - Optimizations to seeking. + - SSE improvements and optimizations. + - ARM NEON optimizations. + - Optimizations to drflac_read_pcm_frames_s16(). + - Optimizations to drflac_read_pcm_frames_s32(). + +v0.11.10 - 2019-06-26 + - Fix a compiler error. + +v0.11.9 - 2019-06-16 + - Silence some ThreadSanitizer warnings. + +v0.11.8 - 2019-05-21 + - Fix warnings. + +v0.11.7 - 2019-05-06 + - C89 fixes. + +v0.11.6 - 2019-05-05 + - Add support for C89. + - Fix a compiler warning when CRC is disabled. + - Change license to choice of public domain or MIT-0. + +v0.11.5 - 2019-04-19 + - Fix a compiler error with GCC. + +v0.11.4 - 2019-04-17 + - Fix some warnings with GCC when compiling with -std=c99. + +v0.11.3 - 2019-04-07 + - Silence warnings with GCC. + +v0.11.2 - 2019-03-10 + - Fix a warning. + +v0.11.1 - 2019-02-17 + - Fix a potential bug with seeking. + +v0.11.0 - 2018-12-16 + - API CHANGE: Deprecated drflac_read_s32(), drflac_read_s16() and drflac_read_f32() and replaced them with + drflac_read_pcm_frames_s32(), drflac_read_pcm_frames_s16() and drflac_read_pcm_frames_f32(). The new APIs take + and return PCM frame counts instead of sample counts. To upgrade you will need to change the input count by + dividing it by the channel count, and then do the same with the return value. + - API_CHANGE: Deprecated drflac_seek_to_sample() and replaced with drflac_seek_to_pcm_frame(). Same rules as + the changes to drflac_read_*() apply. + - API CHANGE: Deprecated drflac_open_and_decode_*() and replaced with drflac_open_*_and_read_*(). Same rules as + the changes to drflac_read_*() apply. + - Optimizations. + +v0.10.0 - 2018-09-11 + - Remove the DR_FLAC_NO_WIN32_IO option and the Win32 file IO functionality. If you need to use Win32 file IO you + need to do it yourself via the callback API. + - Fix the clang build. + - Fix undefined behavior. + - Fix errors with CUESHEET metdata blocks. + - Add an API for iterating over each cuesheet track in the CUESHEET metadata block. This works the same way as the + Vorbis comment API. + - Other miscellaneous bug fixes, mostly relating to invalid FLAC streams. + - Minor optimizations. + +v0.9.11 - 2018-08-29 + - Fix a bug with sample reconstruction. + +v0.9.10 - 2018-08-07 + - Improve 64-bit detection. + +v0.9.9 - 2018-08-05 + - Fix C++ build on older versions of GCC. + +v0.9.8 - 2018-07-24 + - Fix compilation errors. + +v0.9.7 - 2018-07-05 + - Fix a warning. + +v0.9.6 - 2018-06-29 + - Fix some typos. + +v0.9.5 - 2018-06-23 + - Fix some warnings. + +v0.9.4 - 2018-06-14 + - Optimizations to seeking. + - Clean up. + +v0.9.3 - 2018-05-22 + - Bug fix. + +v0.9.2 - 2018-05-12 + - Fix a compilation error due to a missing break statement. + +v0.9.1 - 2018-04-29 + - Fix compilation error with Clang. + +v0.9 - 2018-04-24 + - Fix Clang build. + - Start using major.minor.revision versioning. + +v0.8g - 2018-04-19 + - Fix build on non-x86/x64 architectures. + +v0.8f - 2018-02-02 + - Stop pretending to support changing rate/channels mid stream. + +v0.8e - 2018-02-01 + - Fix a crash when the block size of a frame is larger than the maximum block size defined by the FLAC stream. + - Fix a crash the the Rice partition order is invalid. + +v0.8d - 2017-09-22 + - Add support for decoding streams with ID3 tags. ID3 tags are just skipped. + +v0.8c - 2017-09-07 + - Fix warning on non-x86/x64 architectures. + +v0.8b - 2017-08-19 + - Fix build on non-x86/x64 architectures. + +v0.8a - 2017-08-13 + - A small optimization for the Clang build. + +v0.8 - 2017-08-12 + - API CHANGE: Rename dr_* types to drflac_*. + - Optimizations. This brings dr_flac back to about the same class of efficiency as the reference implementation. + - Add support for custom implementations of malloc(), realloc(), etc. + - Add CRC checking to Ogg encapsulated streams. + - Fix VC++ 6 build. This is only for the C++ compiler. The C compiler is not currently supported. + - Bug fixes. + +v0.7 - 2017-07-23 + - Add support for opening a stream without a header block. To do this, use drflac_open_relaxed() / drflac_open_with_metadata_relaxed(). + +v0.6 - 2017-07-22 + - Add support for recovering from invalid frames. With this change, dr_flac will simply skip over invalid frames as if they + never existed. Frames are checked against their sync code, the CRC-8 of the frame header and the CRC-16 of the whole frame. + +v0.5 - 2017-07-16 + - Fix typos. + - Change drflac_bool* types to unsigned. + - Add CRC checking. This makes dr_flac slower, but can be disabled with #define DR_FLAC_NO_CRC. + +v0.4f - 2017-03-10 + - Fix a couple of bugs with the bitstreaming code. + +v0.4e - 2017-02-17 + - Fix some warnings. + +v0.4d - 2016-12-26 + - Add support for 32-bit floating-point PCM decoding. + - Use drflac_int* and drflac_uint* sized types to improve compiler support. + - Minor improvements to documentation. + +v0.4c - 2016-12-26 + - Add support for signed 16-bit integer PCM decoding. + +v0.4b - 2016-10-23 + - A minor change to drflac_bool8 and drflac_bool32 types. + +v0.4a - 2016-10-11 + - Rename drBool32 to drflac_bool32 for styling consistency. + +v0.4 - 2016-09-29 + - API/ABI CHANGE: Use fixed size 32-bit booleans instead of the built-in bool type. + - API CHANGE: Rename drflac_open_and_decode*() to drflac_open_and_decode*_s32(). + - API CHANGE: Swap the order of "channels" and "sampleRate" parameters in drflac_open_and_decode*(). Rationale for this is to + keep it consistent with drflac_audio. + +v0.3f - 2016-09-21 + - Fix a warning with GCC. + +v0.3e - 2016-09-18 + - Fixed a bug where GCC 4.3+ was not getting properly identified. + - Fixed a few typos. + - Changed date formats to ISO 8601 (YYYY-MM-DD). + +v0.3d - 2016-06-11 + - Minor clean up. + +v0.3c - 2016-05-28 + - Fixed compilation error. + +v0.3b - 2016-05-16 + - Fixed Linux/GCC build. + - Updated documentation. + +v0.3a - 2016-05-15 + - Minor fixes to documentation. + +v0.3 - 2016-05-11 + - Optimizations. Now at about parity with the reference implementation on 32-bit builds. + - Lots of clean up. + +v0.2b - 2016-05-10 + - Bug fixes. + +v0.2a - 2016-05-10 + - Made drflac_open_and_decode() more robust. + - Removed an unused debugging variable + +v0.2 - 2016-05-09 + - Added support for Ogg encapsulation. + - API CHANGE. Have the onSeek callback take a third argument which specifies whether or not the seek + should be relative to the start or the current position. Also changes the seeking rules such that + seeking offsets will never be negative. + - Have drflac_open_and_decode() fail gracefully if the stream has an unknown total sample count. + +v0.1b - 2016-05-07 + - Properly close the file handle in drflac_open_file() and family when the decoder fails to initialize. + - Removed a stale comment. + +v0.1a - 2016-05-05 + - Minor formatting changes. + - Fixed a warning on the GCC build. + +v0.1 - 2016-05-03 + - Initial versioned release. +*/ + +/* +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - Public Domain (www.unlicense.org) +=============================================================================== +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright 2023 David Reid + +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. + +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/source/engine/thirdparty/dr_libs/dr_mp3.h b/source/engine/thirdparty/dr_libs/dr_mp3.h new file mode 100644 index 0000000..e1a66d9 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/dr_mp3.h @@ -0,0 +1,4837 @@ +/* +MP3 audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. +dr_mp3 - v0.6.39 - 2024-02-27 + +David Reid - mackron@gmail.com + +GitHub: https://github.com/mackron/dr_libs + +Based on minimp3 (https://github.com/lieff/minimp3) which is where the real work was done. See the bottom of this file for differences between minimp3 and dr_mp3. +*/ + +/* +RELEASE NOTES - VERSION 0.6 +=========================== +Version 0.6 includes breaking changes with the configuration of decoders. The ability to customize the number of output channels and the sample rate has been +removed. You must now use the channel count and sample rate reported by the MP3 stream itself, and all channel and sample rate conversion must be done +yourself. + + +Changes to Initialization +------------------------- +Previously, `drmp3_init()`, etc. took a pointer to a `drmp3_config` object that allowed you to customize the output channels and sample rate. This has been +removed. If you need the old behaviour you will need to convert the data yourself or just not upgrade. The following APIs have changed. + + `drmp3_init()` + `drmp3_init_memory()` + `drmp3_init_file()` + + +Miscellaneous Changes +--------------------- +Support for loading a file from a `wchar_t` string has been added via the `drmp3_init_file_w()` API. +*/ + +/* +Introducation +============= +dr_mp3 is a single file library. To use it, do something like the following in one .c file. + + ```c + #define DR_MP3_IMPLEMENTATION + #include "dr_mp3.h" + ``` + +You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, do something like the following: + + ```c + drmp3 mp3; + if (!drmp3_init_file(&mp3, "MySong.mp3", NULL)) { + // Failed to open file + } + + ... + + drmp3_uint64 framesRead = drmp3_read_pcm_frames_f32(pMP3, framesToRead, pFrames); + ``` + +The drmp3 object is transparent so you can get access to the channel count and sample rate like so: + + ``` + drmp3_uint32 channels = mp3.channels; + drmp3_uint32 sampleRate = mp3.sampleRate; + ``` + +The example above initializes a decoder from a file, but you can also initialize it from a block of memory and read and seek callbacks with +`drmp3_init_memory()` and `drmp3_init()` respectively. + +You do not need to do any annoying memory management when reading PCM frames - this is all managed internally. You can request any number of PCM frames in each +call to `drmp3_read_pcm_frames_f32()` and it will return as many PCM frames as it can, up to the requested amount. + +You can also decode an entire file in one go with `drmp3_open_and_read_pcm_frames_f32()`, `drmp3_open_memory_and_read_pcm_frames_f32()` and +`drmp3_open_file_and_read_pcm_frames_f32()`. + + +Build Options +============= +#define these options before including this file. + +#define DR_MP3_NO_STDIO + Disable drmp3_init_file(), etc. + +#define DR_MP3_NO_SIMD + Disable SIMD optimizations. +*/ + +#ifndef dr_mp3_h +#define dr_mp3_h + +#ifdef __cplusplus +extern "C" { +#endif + +#define DRMP3_STRINGIFY(x) #x +#define DRMP3_XSTRINGIFY(x) DRMP3_STRINGIFY(x) + +#define DRMP3_VERSION_MAJOR 0 +#define DRMP3_VERSION_MINOR 6 +#define DRMP3_VERSION_REVISION 39 +#define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION) + +#include /* For size_t. */ + +/* Sized Types */ +typedef signed char drmp3_int8; +typedef unsigned char drmp3_uint8; +typedef signed short drmp3_int16; +typedef unsigned short drmp3_uint16; +typedef signed int drmp3_int32; +typedef unsigned int drmp3_uint32; +#if defined(_MSC_VER) && !defined(__clang__) + typedef signed __int64 drmp3_int64; + typedef unsigned __int64 drmp3_uint64; +#else + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wlong-long" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif + #endif + typedef signed long long drmp3_int64; + typedef unsigned long long drmp3_uint64; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop + #endif +#endif +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) + typedef drmp3_uint64 drmp3_uintptr; +#else + typedef drmp3_uint32 drmp3_uintptr; +#endif +typedef drmp3_uint8 drmp3_bool8; +typedef drmp3_uint32 drmp3_bool32; +#define DRMP3_TRUE 1 +#define DRMP3_FALSE 0 +/* End Sized Types */ + +/* Decorations */ +#if !defined(DRMP3_API) + #if defined(DRMP3_DLL) + #if defined(_WIN32) + #define DRMP3_DLL_IMPORT __declspec(dllimport) + #define DRMP3_DLL_EXPORT __declspec(dllexport) + #define DRMP3_DLL_PRIVATE static + #else + #if defined(__GNUC__) && __GNUC__ >= 4 + #define DRMP3_DLL_IMPORT __attribute__((visibility("default"))) + #define DRMP3_DLL_EXPORT __attribute__((visibility("default"))) + #define DRMP3_DLL_PRIVATE __attribute__((visibility("hidden"))) + #else + #define DRMP3_DLL_IMPORT + #define DRMP3_DLL_EXPORT + #define DRMP3_DLL_PRIVATE static + #endif + #endif + + #if defined(DR_MP3_IMPLEMENTATION) || defined(DRMP3_IMPLEMENTATION) + #define DRMP3_API DRMP3_DLL_EXPORT + #else + #define DRMP3_API DRMP3_DLL_IMPORT + #endif + #define DRMP3_PRIVATE DRMP3_DLL_PRIVATE + #else + #define DRMP3_API extern + #define DRMP3_PRIVATE static + #endif +#endif +/* End Decorations */ + +/* Result Codes */ +typedef drmp3_int32 drmp3_result; +#define DRMP3_SUCCESS 0 +#define DRMP3_ERROR -1 /* A generic error. */ +#define DRMP3_INVALID_ARGS -2 +#define DRMP3_INVALID_OPERATION -3 +#define DRMP3_OUT_OF_MEMORY -4 +#define DRMP3_OUT_OF_RANGE -5 +#define DRMP3_ACCESS_DENIED -6 +#define DRMP3_DOES_NOT_EXIST -7 +#define DRMP3_ALREADY_EXISTS -8 +#define DRMP3_TOO_MANY_OPEN_FILES -9 +#define DRMP3_INVALID_FILE -10 +#define DRMP3_TOO_BIG -11 +#define DRMP3_PATH_TOO_LONG -12 +#define DRMP3_NAME_TOO_LONG -13 +#define DRMP3_NOT_DIRECTORY -14 +#define DRMP3_IS_DIRECTORY -15 +#define DRMP3_DIRECTORY_NOT_EMPTY -16 +#define DRMP3_END_OF_FILE -17 +#define DRMP3_NO_SPACE -18 +#define DRMP3_BUSY -19 +#define DRMP3_IO_ERROR -20 +#define DRMP3_INTERRUPT -21 +#define DRMP3_UNAVAILABLE -22 +#define DRMP3_ALREADY_IN_USE -23 +#define DRMP3_BAD_ADDRESS -24 +#define DRMP3_BAD_SEEK -25 +#define DRMP3_BAD_PIPE -26 +#define DRMP3_DEADLOCK -27 +#define DRMP3_TOO_MANY_LINKS -28 +#define DRMP3_NOT_IMPLEMENTED -29 +#define DRMP3_NO_MESSAGE -30 +#define DRMP3_BAD_MESSAGE -31 +#define DRMP3_NO_DATA_AVAILABLE -32 +#define DRMP3_INVALID_DATA -33 +#define DRMP3_TIMEOUT -34 +#define DRMP3_NO_NETWORK -35 +#define DRMP3_NOT_UNIQUE -36 +#define DRMP3_NOT_SOCKET -37 +#define DRMP3_NO_ADDRESS -38 +#define DRMP3_BAD_PROTOCOL -39 +#define DRMP3_PROTOCOL_UNAVAILABLE -40 +#define DRMP3_PROTOCOL_NOT_SUPPORTED -41 +#define DRMP3_PROTOCOL_FAMILY_NOT_SUPPORTED -42 +#define DRMP3_ADDRESS_FAMILY_NOT_SUPPORTED -43 +#define DRMP3_SOCKET_NOT_SUPPORTED -44 +#define DRMP3_CONNECTION_RESET -45 +#define DRMP3_ALREADY_CONNECTED -46 +#define DRMP3_NOT_CONNECTED -47 +#define DRMP3_CONNECTION_REFUSED -48 +#define DRMP3_NO_HOST -49 +#define DRMP3_IN_PROGRESS -50 +#define DRMP3_CANCELLED -51 +#define DRMP3_MEMORY_ALREADY_MAPPED -52 +#define DRMP3_AT_END -53 +/* End Result Codes */ + +#define DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152 +#define DRMP3_MAX_SAMPLES_PER_FRAME (DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME*2) + +/* Inline */ +#ifdef _MSC_VER + #define DRMP3_INLINE __forceinline +#elif defined(__GNUC__) + /* + I've had a bug report where GCC is emitting warnings about functions possibly not being inlineable. This warning happens when + the __attribute__((always_inline)) attribute is defined without an "inline" statement. I think therefore there must be some + case where "__inline__" is not always defined, thus the compiler emitting these warnings. When using -std=c89 or -ansi on the + command line, we cannot use the "inline" keyword and instead need to use "__inline__". In an attempt to work around this issue + I am using "__inline__" only when we're compiling in strict ANSI mode. + */ + #if defined(__STRICT_ANSI__) + #define DRMP3_GNUC_INLINE_HINT __inline__ + #else + #define DRMP3_GNUC_INLINE_HINT inline + #endif + + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define DRMP3_INLINE DRMP3_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define DRMP3_INLINE DRMP3_GNUC_INLINE_HINT + #endif +#elif defined(__WATCOMC__) + #define DRMP3_INLINE __inline +#else + #define DRMP3_INLINE +#endif +/* End Inline */ + + +DRMP3_API void drmp3_version(drmp3_uint32* pMajor, drmp3_uint32* pMinor, drmp3_uint32* pRevision); +DRMP3_API const char* drmp3_version_string(void); + + +/* Allocation Callbacks */ +typedef struct +{ + void* pUserData; + void* (* onMalloc)(size_t sz, void* pUserData); + void* (* onRealloc)(void* p, size_t sz, void* pUserData); + void (* onFree)(void* p, void* pUserData); +} drmp3_allocation_callbacks; +/* End Allocation Callbacks */ + + +/* +Low Level Push API +================== +*/ +typedef struct +{ + int frame_bytes, channels, hz, layer, bitrate_kbps; +} drmp3dec_frame_info; + +typedef struct +{ + float mdct_overlap[2][9*32], qmf_state[15*2*32]; + int reserv, free_format_bytes; + drmp3_uint8 header[4], reserv_buf[511]; +} drmp3dec; + +/* Initializes a low level decoder. */ +DRMP3_API void drmp3dec_init(drmp3dec *dec); + +/* Reads a frame from a low level decoder. */ +DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info); + +/* Helper for converting between f32 and s16. */ +DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num_samples); + + + +/* +Main API (Pull API) +=================== +*/ +typedef enum +{ + drmp3_seek_origin_start, + drmp3_seek_origin_current +} drmp3_seek_origin; + +typedef struct +{ + drmp3_uint64 seekPosInBytes; /* Points to the first byte of an MP3 frame. */ + drmp3_uint64 pcmFrameIndex; /* The index of the PCM frame this seek point targets. */ + drmp3_uint16 mp3FramesToDiscard; /* The number of whole MP3 frames to be discarded before pcmFramesToDiscard. */ + drmp3_uint16 pcmFramesToDiscard; /* The number of leading samples to read and discard. These are discarded after mp3FramesToDiscard. */ +} drmp3_seek_point; + +/* +Callback for when data is read. Return value is the number of bytes actually read. + +pUserData [in] The user data that was passed to drmp3_init(), drmp3_open() and family. +pBufferOut [out] The output buffer. +bytesToRead [in] The number of bytes to read. + +Returns the number of bytes actually read. + +A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until +either the entire bytesToRead is filled or you have reached the end of the stream. +*/ +typedef size_t (* drmp3_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); + +/* +Callback for when data needs to be seeked. + +pUserData [in] The user data that was passed to drmp3_init(), drmp3_open() and family. +offset [in] The number of bytes to move, relative to the origin. Will never be negative. +origin [in] The origin of the seek - the current position or the start of the stream. + +Returns whether or not the seek was successful. + +Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which +will be either drmp3_seek_origin_start or drmp3_seek_origin_current. +*/ +typedef drmp3_bool32 (* drmp3_seek_proc)(void* pUserData, int offset, drmp3_seek_origin origin); + +typedef struct +{ + drmp3_uint32 channels; + drmp3_uint32 sampleRate; +} drmp3_config; + +typedef struct +{ + drmp3dec decoder; + drmp3_uint32 channels; + drmp3_uint32 sampleRate; + drmp3_read_proc onRead; + drmp3_seek_proc onSeek; + void* pUserData; + drmp3_allocation_callbacks allocationCallbacks; + drmp3_uint32 mp3FrameChannels; /* The number of channels in the currently loaded MP3 frame. Internal use only. */ + drmp3_uint32 mp3FrameSampleRate; /* The sample rate of the currently loaded MP3 frame. Internal use only. */ + drmp3_uint32 pcmFramesConsumedInMP3Frame; + drmp3_uint32 pcmFramesRemainingInMP3Frame; + drmp3_uint8 pcmFrames[sizeof(float)*DRMP3_MAX_SAMPLES_PER_FRAME]; /* <-- Multipled by sizeof(float) to ensure there's enough room for DR_MP3_FLOAT_OUTPUT. */ + drmp3_uint64 currentPCMFrame; /* The current PCM frame, globally, based on the output sample rate. Mainly used for seeking. */ + drmp3_uint64 streamCursor; /* The current byte the decoder is sitting on in the raw stream. */ + drmp3_seek_point* pSeekPoints; /* NULL by default. Set with drmp3_bind_seek_table(). Memory is owned by the client. dr_mp3 will never attempt to free this pointer. */ + drmp3_uint32 seekPointCount; /* The number of items in pSeekPoints. When set to 0 assumes to no seek table. Defaults to zero. */ + size_t dataSize; + size_t dataCapacity; + size_t dataConsumed; + drmp3_uint8* pData; + drmp3_bool32 atEnd : 1; + struct + { + const drmp3_uint8* pData; + size_t dataSize; + size_t currentReadPos; + } memory; /* Only used for decoders that were opened against a block of memory. */ +} drmp3; + +/* +Initializes an MP3 decoder. + +onRead [in] The function to call when data needs to be read from the client. +onSeek [in] The function to call when the read position of the client data needs to move. +pUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. + +Returns true if successful; false otherwise. + +Close the loader with drmp3_uninit(). + +See also: drmp3_init_file(), drmp3_init_memory(), drmp3_uninit() +*/ +DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks); + +/* +Initializes an MP3 decoder from a block of memory. + +This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for +the lifetime of the drmp3 object. + +The buffer should contain the contents of the entire MP3 file. +*/ +DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_allocation_callbacks* pAllocationCallbacks); + +#ifndef DR_MP3_NO_STDIO +/* +Initializes an MP3 decoder from a file. + +This holds the internal FILE object until drmp3_uninit() is called. Keep this in mind if you're caching drmp3 +objects because the operating system may restrict the number of file handles an application can have open at +any given time. +*/ +DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks); +#endif + +/* +Uninitializes an MP3 decoder. +*/ +DRMP3_API void drmp3_uninit(drmp3* pMP3); + +/* +Reads PCM frames as interleaved 32-bit IEEE floating point PCM. + +Note that framesToRead specifies the number of PCM frames to read, _not_ the number of MP3 frames. +*/ +DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut); + +/* +Reads PCM frames as interleaved signed 16-bit integer PCM. + +Note that framesToRead specifies the number of PCM frames to read, _not_ the number of MP3 frames. +*/ +DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_s16(drmp3* pMP3, drmp3_uint64 framesToRead, drmp3_int16* pBufferOut); + +/* +Seeks to a specific frame. + +Note that this is _not_ an MP3 frame, but rather a PCM frame. +*/ +DRMP3_API drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex); + +/* +Calculates the total number of PCM frames in the MP3 stream. Cannot be used for infinite streams such as internet +radio. Runs in linear time. Returns 0 on error. +*/ +DRMP3_API drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3); + +/* +Calculates the total number of MP3 frames in the MP3 stream. Cannot be used for infinite streams such as internet +radio. Runs in linear time. Returns 0 on error. +*/ +DRMP3_API drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3); + +/* +Calculates the total number of MP3 and PCM frames in the MP3 stream. Cannot be used for infinite streams such as internet +radio. Runs in linear time. Returns 0 on error. + +This is equivalent to calling drmp3_get_mp3_frame_count() and drmp3_get_pcm_frame_count() except that it's more efficient. +*/ +DRMP3_API drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3FrameCount, drmp3_uint64* pPCMFrameCount); + +/* +Calculates the seekpoints based on PCM frames. This is slow. + +pSeekpoint count is a pointer to a uint32 containing the seekpoint count. On input it contains the desired count. +On output it contains the actual count. The reason for this design is that the client may request too many +seekpoints, in which case dr_mp3 will return a corrected count. + +Note that seektable seeking is not quite sample exact when the MP3 stream contains inconsistent sample rates. +*/ +DRMP3_API drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints); + +/* +Binds a seek table to the decoder. + +This does _not_ make a copy of pSeekPoints - it only references it. It is up to the application to ensure this +remains valid while it is bound to the decoder. + +Use drmp3_calculate_seek_points() to calculate the seek points. +*/ +DRMP3_API drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints); + + +/* +Opens an decodes an entire MP3 stream as a single operation. + +On output pConfig will receive the channel count and sample rate of the stream. + +Free the returned pointer with drmp3_free(). +*/ +DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); + +DRMP3_API float* drmp3_open_memory_and_read_pcm_frames_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); + +#ifndef DR_MP3_NO_STDIO +DRMP3_API float* drmp3_open_file_and_read_pcm_frames_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_int16* drmp3_open_file_and_read_pcm_frames_s16(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +#endif + +/* +Allocates a block of memory on the heap. +*/ +DRMP3_API void* drmp3_malloc(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks); + +/* +Frees any memory that was allocated by a public drmp3 API. +*/ +DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks); + +#ifdef __cplusplus +} +#endif +#endif /* dr_mp3_h */ + + +/************************************************************************************************************************************************************ + ************************************************************************************************************************************************************ + + IMPLEMENTATION + + ************************************************************************************************************************************************************ + ************************************************************************************************************************************************************/ +#if defined(DR_MP3_IMPLEMENTATION) || defined(DRMP3_IMPLEMENTATION) +#ifndef dr_mp3_c +#define dr_mp3_c + +#include +#include +#include /* For INT_MAX */ + +DRMP3_API void drmp3_version(drmp3_uint32* pMajor, drmp3_uint32* pMinor, drmp3_uint32* pRevision) +{ + if (pMajor) { + *pMajor = DRMP3_VERSION_MAJOR; + } + + if (pMinor) { + *pMinor = DRMP3_VERSION_MINOR; + } + + if (pRevision) { + *pRevision = DRMP3_VERSION_REVISION; + } +} + +DRMP3_API const char* drmp3_version_string(void) +{ + return DRMP3_VERSION_STRING; +} + +/* Disable SIMD when compiling with TCC for now. */ +#if defined(__TINYC__) +#define DR_MP3_NO_SIMD +#endif + +#define DRMP3_OFFSET_PTR(p, offset) ((void*)((drmp3_uint8*)(p) + (offset))) + +#define DRMP3_MAX_FREE_FORMAT_FRAME_SIZE 2304 /* more than ISO spec's */ +#ifndef DRMP3_MAX_FRAME_SYNC_MATCHES +#define DRMP3_MAX_FRAME_SYNC_MATCHES 10 +#endif + +#define DRMP3_MAX_L3_FRAME_PAYLOAD_BYTES DRMP3_MAX_FREE_FORMAT_FRAME_SIZE /* MUST be >= 320000/8/32000*1152 = 1440 */ + +#define DRMP3_MAX_BITRESERVOIR_BYTES 511 +#define DRMP3_SHORT_BLOCK_TYPE 2 +#define DRMP3_STOP_BLOCK_TYPE 3 +#define DRMP3_MODE_MONO 3 +#define DRMP3_MODE_JOINT_STEREO 1 +#define DRMP3_HDR_SIZE 4 +#define DRMP3_HDR_IS_MONO(h) (((h[3]) & 0xC0) == 0xC0) +#define DRMP3_HDR_IS_MS_STEREO(h) (((h[3]) & 0xE0) == 0x60) +#define DRMP3_HDR_IS_FREE_FORMAT(h) (((h[2]) & 0xF0) == 0) +#define DRMP3_HDR_IS_CRC(h) (!((h[1]) & 1)) +#define DRMP3_HDR_TEST_PADDING(h) ((h[2]) & 0x2) +#define DRMP3_HDR_TEST_MPEG1(h) ((h[1]) & 0x8) +#define DRMP3_HDR_TEST_NOT_MPEG25(h) ((h[1]) & 0x10) +#define DRMP3_HDR_TEST_I_STEREO(h) ((h[3]) & 0x10) +#define DRMP3_HDR_TEST_MS_STEREO(h) ((h[3]) & 0x20) +#define DRMP3_HDR_GET_STEREO_MODE(h) (((h[3]) >> 6) & 3) +#define DRMP3_HDR_GET_STEREO_MODE_EXT(h) (((h[3]) >> 4) & 3) +#define DRMP3_HDR_GET_LAYER(h) (((h[1]) >> 1) & 3) +#define DRMP3_HDR_GET_BITRATE(h) ((h[2]) >> 4) +#define DRMP3_HDR_GET_SAMPLE_RATE(h) (((h[2]) >> 2) & 3) +#define DRMP3_HDR_GET_MY_SAMPLE_RATE(h) (DRMP3_HDR_GET_SAMPLE_RATE(h) + (((h[1] >> 3) & 1) + ((h[1] >> 4) & 1))*3) +#define DRMP3_HDR_IS_FRAME_576(h) ((h[1] & 14) == 2) +#define DRMP3_HDR_IS_LAYER_1(h) ((h[1] & 6) == 6) + +#define DRMP3_BITS_DEQUANTIZER_OUT -1 +#define DRMP3_MAX_SCF (255 + DRMP3_BITS_DEQUANTIZER_OUT*4 - 210) +#define DRMP3_MAX_SCFI ((DRMP3_MAX_SCF + 3) & ~3) + +#define DRMP3_MIN(a, b) ((a) > (b) ? (b) : (a)) +#define DRMP3_MAX(a, b) ((a) < (b) ? (b) : (a)) + +#if !defined(DR_MP3_NO_SIMD) + +#if !defined(DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64)) +/* x64 always have SSE2, arm64 always have neon, no need for generic code */ +#define DR_MP3_ONLY_SIMD +#endif + +#if ((defined(_MSC_VER) && _MSC_VER >= 1400) && defined(_M_X64)) || ((defined(__i386) || defined(_M_IX86) || defined(__i386__) || defined(__x86_64__)) && ((defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE2__))) +#if defined(_MSC_VER) +#include +#endif +#include +#define DRMP3_HAVE_SSE 1 +#define DRMP3_HAVE_SIMD 1 +#define DRMP3_VSTORE _mm_storeu_ps +#define DRMP3_VLD _mm_loadu_ps +#define DRMP3_VSET _mm_set1_ps +#define DRMP3_VADD _mm_add_ps +#define DRMP3_VSUB _mm_sub_ps +#define DRMP3_VMUL _mm_mul_ps +#define DRMP3_VMAC(a, x, y) _mm_add_ps(a, _mm_mul_ps(x, y)) +#define DRMP3_VMSB(a, x, y) _mm_sub_ps(a, _mm_mul_ps(x, y)) +#define DRMP3_VMUL_S(x, s) _mm_mul_ps(x, _mm_set1_ps(s)) +#define DRMP3_VREV(x) _mm_shuffle_ps(x, x, _MM_SHUFFLE(0, 1, 2, 3)) +typedef __m128 drmp3_f4; +#if defined(_MSC_VER) || defined(DR_MP3_ONLY_SIMD) +#define drmp3_cpuid __cpuid +#else +static __inline__ __attribute__((always_inline)) void drmp3_cpuid(int CPUInfo[], const int InfoType) +{ +#if defined(__PIC__) + __asm__ __volatile__( +#if defined(__x86_64__) + "push %%rbx\n" + "cpuid\n" + "xchgl %%ebx, %1\n" + "pop %%rbx\n" +#else + "xchgl %%ebx, %1\n" + "cpuid\n" + "xchgl %%ebx, %1\n" +#endif + : "=a" (CPUInfo[0]), "=r" (CPUInfo[1]), "=c" (CPUInfo[2]), "=d" (CPUInfo[3]) + : "a" (InfoType)); +#else + __asm__ __volatile__( + "cpuid" + : "=a" (CPUInfo[0]), "=b" (CPUInfo[1]), "=c" (CPUInfo[2]), "=d" (CPUInfo[3]) + : "a" (InfoType)); +#endif +} +#endif +static int drmp3_have_simd(void) +{ +#ifdef DR_MP3_ONLY_SIMD + return 1; +#else + static int g_have_simd; + int CPUInfo[4]; +#ifdef MINIMP3_TEST + static int g_counter; + if (g_counter++ > 100) + return 0; +#endif + if (g_have_simd) + goto end; + drmp3_cpuid(CPUInfo, 0); + if (CPUInfo[0] > 0) + { + drmp3_cpuid(CPUInfo, 1); + g_have_simd = (CPUInfo[3] & (1 << 26)) + 1; /* SSE2 */ + return g_have_simd - 1; + } + +end: + return g_have_simd - 1; +#endif +} +#elif defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) +#include +#define DRMP3_HAVE_SSE 0 +#define DRMP3_HAVE_SIMD 1 +#define DRMP3_VSTORE vst1q_f32 +#define DRMP3_VLD vld1q_f32 +#define DRMP3_VSET vmovq_n_f32 +#define DRMP3_VADD vaddq_f32 +#define DRMP3_VSUB vsubq_f32 +#define DRMP3_VMUL vmulq_f32 +#define DRMP3_VMAC(a, x, y) vmlaq_f32(a, x, y) +#define DRMP3_VMSB(a, x, y) vmlsq_f32(a, x, y) +#define DRMP3_VMUL_S(x, s) vmulq_f32(x, vmovq_n_f32(s)) +#define DRMP3_VREV(x) vcombine_f32(vget_high_f32(vrev64q_f32(x)), vget_low_f32(vrev64q_f32(x))) +typedef float32x4_t drmp3_f4; +static int drmp3_have_simd(void) +{ /* TODO: detect neon for !DR_MP3_ONLY_SIMD */ + return 1; +} +#else +#define DRMP3_HAVE_SSE 0 +#define DRMP3_HAVE_SIMD 0 +#ifdef DR_MP3_ONLY_SIMD +#error DR_MP3_ONLY_SIMD used, but SSE/NEON not enabled +#endif +#endif + +#else + +#define DRMP3_HAVE_SIMD 0 + +#endif + +#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) && !defined(__ARM_ARCH_6M__) +#define DRMP3_HAVE_ARMV6 1 +static __inline__ __attribute__((always_inline)) drmp3_int32 drmp3_clip_int16_arm(drmp3_int32 a) +{ + drmp3_int32 x = 0; + __asm__ ("ssat %0, #16, %1" : "=r"(x) : "r"(a)); + return x; +} +#else +#define DRMP3_HAVE_ARMV6 0 +#endif + + +/* Standard library stuff. */ +#ifndef DRMP3_ASSERT +#include +#define DRMP3_ASSERT(expression) assert(expression) +#endif +#ifndef DRMP3_COPY_MEMORY +#define DRMP3_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#ifndef DRMP3_MOVE_MEMORY +#define DRMP3_MOVE_MEMORY(dst, src, sz) memmove((dst), (src), (sz)) +#endif +#ifndef DRMP3_ZERO_MEMORY +#define DRMP3_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif +#define DRMP3_ZERO_OBJECT(p) DRMP3_ZERO_MEMORY((p), sizeof(*(p))) +#ifndef DRMP3_MALLOC +#define DRMP3_MALLOC(sz) malloc((sz)) +#endif +#ifndef DRMP3_REALLOC +#define DRMP3_REALLOC(p, sz) realloc((p), (sz)) +#endif +#ifndef DRMP3_FREE +#define DRMP3_FREE(p) free((p)) +#endif + +typedef struct +{ + const drmp3_uint8 *buf; + int pos, limit; +} drmp3_bs; + +typedef struct +{ + float scf[3*64]; + drmp3_uint8 total_bands, stereo_bands, bitalloc[64], scfcod[64]; +} drmp3_L12_scale_info; + +typedef struct +{ + drmp3_uint8 tab_offset, code_tab_width, band_count; +} drmp3_L12_subband_alloc; + +typedef struct +{ + const drmp3_uint8 *sfbtab; + drmp3_uint16 part_23_length, big_values, scalefac_compress; + drmp3_uint8 global_gain, block_type, mixed_block_flag, n_long_sfb, n_short_sfb; + drmp3_uint8 table_select[3], region_count[3], subblock_gain[3]; + drmp3_uint8 preflag, scalefac_scale, count1_table, scfsi; +} drmp3_L3_gr_info; + +typedef struct +{ + drmp3_bs bs; + drmp3_uint8 maindata[DRMP3_MAX_BITRESERVOIR_BYTES + DRMP3_MAX_L3_FRAME_PAYLOAD_BYTES]; + drmp3_L3_gr_info gr_info[4]; + float grbuf[2][576], scf[40], syn[18 + 15][2*32]; + drmp3_uint8 ist_pos[2][39]; +} drmp3dec_scratch; + +static void drmp3_bs_init(drmp3_bs *bs, const drmp3_uint8 *data, int bytes) +{ + bs->buf = data; + bs->pos = 0; + bs->limit = bytes*8; +} + +static drmp3_uint32 drmp3_bs_get_bits(drmp3_bs *bs, int n) +{ + drmp3_uint32 next, cache = 0, s = bs->pos & 7; + int shl = n + s; + const drmp3_uint8 *p = bs->buf + (bs->pos >> 3); + if ((bs->pos += n) > bs->limit) + return 0; + next = *p++ & (255 >> s); + while ((shl -= 8) > 0) + { + cache |= next << shl; + next = *p++; + } + return cache | (next >> -shl); +} + +static int drmp3_hdr_valid(const drmp3_uint8 *h) +{ + return h[0] == 0xff && + ((h[1] & 0xF0) == 0xf0 || (h[1] & 0xFE) == 0xe2) && + (DRMP3_HDR_GET_LAYER(h) != 0) && + (DRMP3_HDR_GET_BITRATE(h) != 15) && + (DRMP3_HDR_GET_SAMPLE_RATE(h) != 3); +} + +static int drmp3_hdr_compare(const drmp3_uint8 *h1, const drmp3_uint8 *h2) +{ + return drmp3_hdr_valid(h2) && + ((h1[1] ^ h2[1]) & 0xFE) == 0 && + ((h1[2] ^ h2[2]) & 0x0C) == 0 && + !(DRMP3_HDR_IS_FREE_FORMAT(h1) ^ DRMP3_HDR_IS_FREE_FORMAT(h2)); +} + +static unsigned drmp3_hdr_bitrate_kbps(const drmp3_uint8 *h) +{ + static const drmp3_uint8 halfrate[2][3][15] = { + { { 0,4,8,12,16,20,24,28,32,40,48,56,64,72,80 }, { 0,4,8,12,16,20,24,28,32,40,48,56,64,72,80 }, { 0,16,24,28,32,40,48,56,64,72,80,88,96,112,128 } }, + { { 0,16,20,24,28,32,40,48,56,64,80,96,112,128,160 }, { 0,16,24,28,32,40,48,56,64,80,96,112,128,160,192 }, { 0,16,32,48,64,80,96,112,128,144,160,176,192,208,224 } }, + }; + return 2*halfrate[!!DRMP3_HDR_TEST_MPEG1(h)][DRMP3_HDR_GET_LAYER(h) - 1][DRMP3_HDR_GET_BITRATE(h)]; +} + +static unsigned drmp3_hdr_sample_rate_hz(const drmp3_uint8 *h) +{ + static const unsigned g_hz[3] = { 44100, 48000, 32000 }; + return g_hz[DRMP3_HDR_GET_SAMPLE_RATE(h)] >> (int)!DRMP3_HDR_TEST_MPEG1(h) >> (int)!DRMP3_HDR_TEST_NOT_MPEG25(h); +} + +static unsigned drmp3_hdr_frame_samples(const drmp3_uint8 *h) +{ + return DRMP3_HDR_IS_LAYER_1(h) ? 384 : (1152 >> (int)DRMP3_HDR_IS_FRAME_576(h)); +} + +static int drmp3_hdr_frame_bytes(const drmp3_uint8 *h, int free_format_size) +{ + int frame_bytes = drmp3_hdr_frame_samples(h)*drmp3_hdr_bitrate_kbps(h)*125/drmp3_hdr_sample_rate_hz(h); + if (DRMP3_HDR_IS_LAYER_1(h)) + { + frame_bytes &= ~3; /* slot align */ + } + return frame_bytes ? frame_bytes : free_format_size; +} + +static int drmp3_hdr_padding(const drmp3_uint8 *h) +{ + return DRMP3_HDR_TEST_PADDING(h) ? (DRMP3_HDR_IS_LAYER_1(h) ? 4 : 1) : 0; +} + +#ifndef DR_MP3_ONLY_MP3 +static const drmp3_L12_subband_alloc *drmp3_L12_subband_alloc_table(const drmp3_uint8 *hdr, drmp3_L12_scale_info *sci) +{ + const drmp3_L12_subband_alloc *alloc; + int mode = DRMP3_HDR_GET_STEREO_MODE(hdr); + int nbands, stereo_bands = (mode == DRMP3_MODE_MONO) ? 0 : (mode == DRMP3_MODE_JOINT_STEREO) ? (DRMP3_HDR_GET_STEREO_MODE_EXT(hdr) << 2) + 4 : 32; + + if (DRMP3_HDR_IS_LAYER_1(hdr)) + { + static const drmp3_L12_subband_alloc g_alloc_L1[] = { { 76, 4, 32 } }; + alloc = g_alloc_L1; + nbands = 32; + } else if (!DRMP3_HDR_TEST_MPEG1(hdr)) + { + static const drmp3_L12_subband_alloc g_alloc_L2M2[] = { { 60, 4, 4 }, { 44, 3, 7 }, { 44, 2, 19 } }; + alloc = g_alloc_L2M2; + nbands = 30; + } else + { + static const drmp3_L12_subband_alloc g_alloc_L2M1[] = { { 0, 4, 3 }, { 16, 4, 8 }, { 32, 3, 12 }, { 40, 2, 7 } }; + int sample_rate_idx = DRMP3_HDR_GET_SAMPLE_RATE(hdr); + unsigned kbps = drmp3_hdr_bitrate_kbps(hdr) >> (int)(mode != DRMP3_MODE_MONO); + if (!kbps) /* free-format */ + { + kbps = 192; + } + + alloc = g_alloc_L2M1; + nbands = 27; + if (kbps < 56) + { + static const drmp3_L12_subband_alloc g_alloc_L2M1_lowrate[] = { { 44, 4, 2 }, { 44, 3, 10 } }; + alloc = g_alloc_L2M1_lowrate; + nbands = sample_rate_idx == 2 ? 12 : 8; + } else if (kbps >= 96 && sample_rate_idx != 1) + { + nbands = 30; + } + } + + sci->total_bands = (drmp3_uint8)nbands; + sci->stereo_bands = (drmp3_uint8)DRMP3_MIN(stereo_bands, nbands); + + return alloc; +} + +static void drmp3_L12_read_scalefactors(drmp3_bs *bs, drmp3_uint8 *pba, drmp3_uint8 *scfcod, int bands, float *scf) +{ + static const float g_deq_L12[18*3] = { +#define DRMP3_DQ(x) 9.53674316e-07f/x, 7.56931807e-07f/x, 6.00777173e-07f/x + DRMP3_DQ(3),DRMP3_DQ(7),DRMP3_DQ(15),DRMP3_DQ(31),DRMP3_DQ(63),DRMP3_DQ(127),DRMP3_DQ(255),DRMP3_DQ(511),DRMP3_DQ(1023),DRMP3_DQ(2047),DRMP3_DQ(4095),DRMP3_DQ(8191),DRMP3_DQ(16383),DRMP3_DQ(32767),DRMP3_DQ(65535),DRMP3_DQ(3),DRMP3_DQ(5),DRMP3_DQ(9) + }; + int i, m; + for (i = 0; i < bands; i++) + { + float s = 0; + int ba = *pba++; + int mask = ba ? 4 + ((19 >> scfcod[i]) & 3) : 0; + for (m = 4; m; m >>= 1) + { + if (mask & m) + { + int b = drmp3_bs_get_bits(bs, 6); + s = g_deq_L12[ba*3 - 6 + b % 3]*(int)(1 << 21 >> b/3); + } + *scf++ = s; + } + } +} + +static void drmp3_L12_read_scale_info(const drmp3_uint8 *hdr, drmp3_bs *bs, drmp3_L12_scale_info *sci) +{ + static const drmp3_uint8 g_bitalloc_code_tab[] = { + 0,17, 3, 4, 5,6,7, 8,9,10,11,12,13,14,15,16, + 0,17,18, 3,19,4,5, 6,7, 8, 9,10,11,12,13,16, + 0,17,18, 3,19,4,5,16, + 0,17,18,16, + 0,17,18,19, 4,5,6, 7,8, 9,10,11,12,13,14,15, + 0,17,18, 3,19,4,5, 6,7, 8, 9,10,11,12,13,14, + 0, 2, 3, 4, 5,6,7, 8,9,10,11,12,13,14,15,16 + }; + const drmp3_L12_subband_alloc *subband_alloc = drmp3_L12_subband_alloc_table(hdr, sci); + + int i, k = 0, ba_bits = 0; + const drmp3_uint8 *ba_code_tab = g_bitalloc_code_tab; + + for (i = 0; i < sci->total_bands; i++) + { + drmp3_uint8 ba; + if (i == k) + { + k += subband_alloc->band_count; + ba_bits = subband_alloc->code_tab_width; + ba_code_tab = g_bitalloc_code_tab + subband_alloc->tab_offset; + subband_alloc++; + } + ba = ba_code_tab[drmp3_bs_get_bits(bs, ba_bits)]; + sci->bitalloc[2*i] = ba; + if (i < sci->stereo_bands) + { + ba = ba_code_tab[drmp3_bs_get_bits(bs, ba_bits)]; + } + sci->bitalloc[2*i + 1] = sci->stereo_bands ? ba : 0; + } + + for (i = 0; i < 2*sci->total_bands; i++) + { + sci->scfcod[i] = (drmp3_uint8)(sci->bitalloc[i] ? DRMP3_HDR_IS_LAYER_1(hdr) ? 2 : drmp3_bs_get_bits(bs, 2) : 6); + } + + drmp3_L12_read_scalefactors(bs, sci->bitalloc, sci->scfcod, sci->total_bands*2, sci->scf); + + for (i = sci->stereo_bands; i < sci->total_bands; i++) + { + sci->bitalloc[2*i + 1] = 0; + } +} + +static int drmp3_L12_dequantize_granule(float *grbuf, drmp3_bs *bs, drmp3_L12_scale_info *sci, int group_size) +{ + int i, j, k, choff = 576; + for (j = 0; j < 4; j++) + { + float *dst = grbuf + group_size*j; + for (i = 0; i < 2*sci->total_bands; i++) + { + int ba = sci->bitalloc[i]; + if (ba != 0) + { + if (ba < 17) + { + int half = (1 << (ba - 1)) - 1; + for (k = 0; k < group_size; k++) + { + dst[k] = (float)((int)drmp3_bs_get_bits(bs, ba) - half); + } + } else + { + unsigned mod = (2 << (ba - 17)) + 1; /* 3, 5, 9 */ + unsigned code = drmp3_bs_get_bits(bs, mod + 2 - (mod >> 3)); /* 5, 7, 10 */ + for (k = 0; k < group_size; k++, code /= mod) + { + dst[k] = (float)((int)(code % mod - mod/2)); + } + } + } + dst += choff; + choff = 18 - choff; + } + } + return group_size*4; +} + +static void drmp3_L12_apply_scf_384(drmp3_L12_scale_info *sci, const float *scf, float *dst) +{ + int i, k; + DRMP3_COPY_MEMORY(dst + 576 + sci->stereo_bands*18, dst + sci->stereo_bands*18, (sci->total_bands - sci->stereo_bands)*18*sizeof(float)); + for (i = 0; i < sci->total_bands; i++, dst += 18, scf += 6) + { + for (k = 0; k < 12; k++) + { + dst[k + 0] *= scf[0]; + dst[k + 576] *= scf[3]; + } + } +} +#endif + +static int drmp3_L3_read_side_info(drmp3_bs *bs, drmp3_L3_gr_info *gr, const drmp3_uint8 *hdr) +{ + static const drmp3_uint8 g_scf_long[8][23] = { + { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, + { 12,12,12,12,12,12,16,20,24,28,32,40,48,56,64,76,90,2,2,2,2,2,0 }, + { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, + { 6,6,6,6,6,6,8,10,12,14,16,18,22,26,32,38,46,54,62,70,76,36,0 }, + { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, + { 4,4,4,4,4,4,6,6,8,8,10,12,16,20,24,28,34,42,50,54,76,158,0 }, + { 4,4,4,4,4,4,6,6,6,8,10,12,16,18,22,28,34,40,46,54,54,192,0 }, + { 4,4,4,4,4,4,6,6,8,10,12,16,20,24,30,38,46,56,68,84,102,26,0 } + }; + static const drmp3_uint8 g_scf_short[8][40] = { + { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 8,8,8,8,8,8,8,8,8,12,12,12,16,16,16,20,20,20,24,24,24,28,28,28,36,36,36,2,2,2,2,2,2,2,2,2,26,26,26,0 }, + { 4,4,4,4,4,4,4,4,4,6,6,6,6,6,6,8,8,8,10,10,10,14,14,14,18,18,18,26,26,26,32,32,32,42,42,42,18,18,18,0 }, + { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,32,32,32,44,44,44,12,12,12,0 }, + { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,22,22,22,30,30,30,56,56,56,0 }, + { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,6,6,6,10,10,10,12,12,12,14,14,14,16,16,16,20,20,20,26,26,26,66,66,66,0 }, + { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,12,12,12,16,16,16,20,20,20,26,26,26,34,34,34,42,42,42,12,12,12,0 } + }; + static const drmp3_uint8 g_scf_mixed[8][40] = { + { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 12,12,12,4,4,4,8,8,8,12,12,12,16,16,16,20,20,20,24,24,24,28,28,28,36,36,36,2,2,2,2,2,2,2,2,2,26,26,26,0 }, + { 6,6,6,6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,14,14,14,18,18,18,26,26,26,32,32,32,42,42,42,18,18,18,0 }, + { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,32,32,32,44,44,44,12,12,12,0 }, + { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,22,22,22,30,30,30,56,56,56,0 }, + { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,6,6,6,10,10,10,12,12,12,14,14,14,16,16,16,20,20,20,26,26,26,66,66,66,0 }, + { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,8,8,8,12,12,12,16,16,16,20,20,20,26,26,26,34,34,34,42,42,42,12,12,12,0 } + }; + + unsigned tables, scfsi = 0; + int main_data_begin, part_23_sum = 0; + int gr_count = DRMP3_HDR_IS_MONO(hdr) ? 1 : 2; + int sr_idx = DRMP3_HDR_GET_MY_SAMPLE_RATE(hdr); sr_idx -= (sr_idx != 0); + + if (DRMP3_HDR_TEST_MPEG1(hdr)) + { + gr_count *= 2; + main_data_begin = drmp3_bs_get_bits(bs, 9); + scfsi = drmp3_bs_get_bits(bs, 7 + gr_count); + } else + { + main_data_begin = drmp3_bs_get_bits(bs, 8 + gr_count) >> gr_count; + } + + do + { + if (DRMP3_HDR_IS_MONO(hdr)) + { + scfsi <<= 4; + } + gr->part_23_length = (drmp3_uint16)drmp3_bs_get_bits(bs, 12); + part_23_sum += gr->part_23_length; + gr->big_values = (drmp3_uint16)drmp3_bs_get_bits(bs, 9); + if (gr->big_values > 288) + { + return -1; + } + gr->global_gain = (drmp3_uint8)drmp3_bs_get_bits(bs, 8); + gr->scalefac_compress = (drmp3_uint16)drmp3_bs_get_bits(bs, DRMP3_HDR_TEST_MPEG1(hdr) ? 4 : 9); + gr->sfbtab = g_scf_long[sr_idx]; + gr->n_long_sfb = 22; + gr->n_short_sfb = 0; + if (drmp3_bs_get_bits(bs, 1)) + { + gr->block_type = (drmp3_uint8)drmp3_bs_get_bits(bs, 2); + if (!gr->block_type) + { + return -1; + } + gr->mixed_block_flag = (drmp3_uint8)drmp3_bs_get_bits(bs, 1); + gr->region_count[0] = 7; + gr->region_count[1] = 255; + if (gr->block_type == DRMP3_SHORT_BLOCK_TYPE) + { + scfsi &= 0x0F0F; + if (!gr->mixed_block_flag) + { + gr->region_count[0] = 8; + gr->sfbtab = g_scf_short[sr_idx]; + gr->n_long_sfb = 0; + gr->n_short_sfb = 39; + } else + { + gr->sfbtab = g_scf_mixed[sr_idx]; + gr->n_long_sfb = DRMP3_HDR_TEST_MPEG1(hdr) ? 8 : 6; + gr->n_short_sfb = 30; + } + } + tables = drmp3_bs_get_bits(bs, 10); + tables <<= 5; + gr->subblock_gain[0] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); + gr->subblock_gain[1] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); + gr->subblock_gain[2] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); + } else + { + gr->block_type = 0; + gr->mixed_block_flag = 0; + tables = drmp3_bs_get_bits(bs, 15); + gr->region_count[0] = (drmp3_uint8)drmp3_bs_get_bits(bs, 4); + gr->region_count[1] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); + gr->region_count[2] = 255; + } + gr->table_select[0] = (drmp3_uint8)(tables >> 10); + gr->table_select[1] = (drmp3_uint8)((tables >> 5) & 31); + gr->table_select[2] = (drmp3_uint8)((tables) & 31); + gr->preflag = (drmp3_uint8)(DRMP3_HDR_TEST_MPEG1(hdr) ? drmp3_bs_get_bits(bs, 1) : (gr->scalefac_compress >= 500)); + gr->scalefac_scale = (drmp3_uint8)drmp3_bs_get_bits(bs, 1); + gr->count1_table = (drmp3_uint8)drmp3_bs_get_bits(bs, 1); + gr->scfsi = (drmp3_uint8)((scfsi >> 12) & 15); + scfsi <<= 4; + gr++; + } while(--gr_count); + + if (part_23_sum + bs->pos > bs->limit + main_data_begin*8) + { + return -1; + } + + return main_data_begin; +} + +static void drmp3_L3_read_scalefactors(drmp3_uint8 *scf, drmp3_uint8 *ist_pos, const drmp3_uint8 *scf_size, const drmp3_uint8 *scf_count, drmp3_bs *bitbuf, int scfsi) +{ + int i, k; + for (i = 0; i < 4 && scf_count[i]; i++, scfsi *= 2) + { + int cnt = scf_count[i]; + if (scfsi & 8) + { + DRMP3_COPY_MEMORY(scf, ist_pos, cnt); + } else + { + int bits = scf_size[i]; + if (!bits) + { + DRMP3_ZERO_MEMORY(scf, cnt); + DRMP3_ZERO_MEMORY(ist_pos, cnt); + } else + { + int max_scf = (scfsi < 0) ? (1 << bits) - 1 : -1; + for (k = 0; k < cnt; k++) + { + int s = drmp3_bs_get_bits(bitbuf, bits); + ist_pos[k] = (drmp3_uint8)(s == max_scf ? -1 : s); + scf[k] = (drmp3_uint8)s; + } + } + } + ist_pos += cnt; + scf += cnt; + } + scf[0] = scf[1] = scf[2] = 0; +} + +static float drmp3_L3_ldexp_q2(float y, int exp_q2) +{ + static const float g_expfrac[4] = { 9.31322575e-10f,7.83145814e-10f,6.58544508e-10f,5.53767716e-10f }; + int e; + do + { + e = DRMP3_MIN(30*4, exp_q2); + y *= g_expfrac[e & 3]*(1 << 30 >> (e >> 2)); + } while ((exp_q2 -= e) > 0); + return y; +} + +static void drmp3_L3_decode_scalefactors(const drmp3_uint8 *hdr, drmp3_uint8 *ist_pos, drmp3_bs *bs, const drmp3_L3_gr_info *gr, float *scf, int ch) +{ + static const drmp3_uint8 g_scf_partitions[3][28] = { + { 6,5,5, 5,6,5,5,5,6,5, 7,3,11,10,0,0, 7, 7, 7,0, 6, 6,6,3, 8, 8,5,0 }, + { 8,9,6,12,6,9,9,9,6,9,12,6,15,18,0,0, 6,15,12,0, 6,12,9,6, 6,18,9,0 }, + { 9,9,6,12,9,9,9,9,9,9,12,6,18,18,0,0,12,12,12,0,12, 9,9,6,15,12,9,0 } + }; + const drmp3_uint8 *scf_partition = g_scf_partitions[!!gr->n_short_sfb + !gr->n_long_sfb]; + drmp3_uint8 scf_size[4], iscf[40]; + int i, scf_shift = gr->scalefac_scale + 1, gain_exp, scfsi = gr->scfsi; + float gain; + + if (DRMP3_HDR_TEST_MPEG1(hdr)) + { + static const drmp3_uint8 g_scfc_decode[16] = { 0,1,2,3, 12,5,6,7, 9,10,11,13, 14,15,18,19 }; + int part = g_scfc_decode[gr->scalefac_compress]; + scf_size[1] = scf_size[0] = (drmp3_uint8)(part >> 2); + scf_size[3] = scf_size[2] = (drmp3_uint8)(part & 3); + } else + { + static const drmp3_uint8 g_mod[6*4] = { 5,5,4,4,5,5,4,1,4,3,1,1,5,6,6,1,4,4,4,1,4,3,1,1 }; + int k, modprod, sfc, ist = DRMP3_HDR_TEST_I_STEREO(hdr) && ch; + sfc = gr->scalefac_compress >> ist; + for (k = ist*3*4; sfc >= 0; sfc -= modprod, k += 4) + { + for (modprod = 1, i = 3; i >= 0; i--) + { + scf_size[i] = (drmp3_uint8)(sfc / modprod % g_mod[k + i]); + modprod *= g_mod[k + i]; + } + } + scf_partition += k; + scfsi = -16; + } + drmp3_L3_read_scalefactors(iscf, ist_pos, scf_size, scf_partition, bs, scfsi); + + if (gr->n_short_sfb) + { + int sh = 3 - scf_shift; + for (i = 0; i < gr->n_short_sfb; i += 3) + { + iscf[gr->n_long_sfb + i + 0] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 0] + (gr->subblock_gain[0] << sh)); + iscf[gr->n_long_sfb + i + 1] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 1] + (gr->subblock_gain[1] << sh)); + iscf[gr->n_long_sfb + i + 2] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 2] + (gr->subblock_gain[2] << sh)); + } + } else if (gr->preflag) + { + static const drmp3_uint8 g_preamp[10] = { 1,1,1,1,2,2,3,3,3,2 }; + for (i = 0; i < 10; i++) + { + iscf[11 + i] = (drmp3_uint8)(iscf[11 + i] + g_preamp[i]); + } + } + + gain_exp = gr->global_gain + DRMP3_BITS_DEQUANTIZER_OUT*4 - 210 - (DRMP3_HDR_IS_MS_STEREO(hdr) ? 2 : 0); + gain = drmp3_L3_ldexp_q2(1 << (DRMP3_MAX_SCFI/4), DRMP3_MAX_SCFI - gain_exp); + for (i = 0; i < (int)(gr->n_long_sfb + gr->n_short_sfb); i++) + { + scf[i] = drmp3_L3_ldexp_q2(gain, iscf[i] << scf_shift); + } +} + +static const float g_drmp3_pow43[129 + 16] = { + 0,-1,-2.519842f,-4.326749f,-6.349604f,-8.549880f,-10.902724f,-13.390518f,-16.000000f,-18.720754f,-21.544347f,-24.463781f,-27.473142f,-30.567351f,-33.741992f,-36.993181f, + 0,1,2.519842f,4.326749f,6.349604f,8.549880f,10.902724f,13.390518f,16.000000f,18.720754f,21.544347f,24.463781f,27.473142f,30.567351f,33.741992f,36.993181f,40.317474f,43.711787f,47.173345f,50.699631f,54.288352f,57.937408f,61.644865f,65.408941f,69.227979f,73.100443f,77.024898f,81.000000f,85.024491f,89.097188f,93.216975f,97.382800f,101.593667f,105.848633f,110.146801f,114.487321f,118.869381f,123.292209f,127.755065f,132.257246f,136.798076f,141.376907f,145.993119f,150.646117f,155.335327f,160.060199f,164.820202f,169.614826f,174.443577f,179.305980f,184.201575f,189.129918f,194.090580f,199.083145f,204.107210f,209.162385f,214.248292f,219.364564f,224.510845f,229.686789f,234.892058f,240.126328f,245.389280f,250.680604f,256.000000f,261.347174f,266.721841f,272.123723f,277.552547f,283.008049f,288.489971f,293.998060f,299.532071f,305.091761f,310.676898f,316.287249f,321.922592f,327.582707f,333.267377f,338.976394f,344.709550f,350.466646f,356.247482f,362.051866f,367.879608f,373.730522f,379.604427f,385.501143f,391.420496f,397.362314f,403.326427f,409.312672f,415.320884f,421.350905f,427.402579f,433.475750f,439.570269f,445.685987f,451.822757f,457.980436f,464.158883f,470.357960f,476.577530f,482.817459f,489.077615f,495.357868f,501.658090f,507.978156f,514.317941f,520.677324f,527.056184f,533.454404f,539.871867f,546.308458f,552.764065f,559.238575f,565.731879f,572.243870f,578.774440f,585.323483f,591.890898f,598.476581f,605.080431f,611.702349f,618.342238f,625.000000f,631.675540f,638.368763f,645.079578f +}; + +static float drmp3_L3_pow_43(int x) +{ + float frac; + int sign, mult = 256; + + if (x < 129) + { + return g_drmp3_pow43[16 + x]; + } + + if (x < 1024) + { + mult = 16; + x <<= 3; + } + + sign = 2*x & 64; + frac = (float)((x & 63) - sign) / ((x & ~63) + sign); + return g_drmp3_pow43[16 + ((x + sign) >> 6)]*(1.f + frac*((4.f/3) + frac*(2.f/9)))*mult; +} + +static void drmp3_L3_huffman(float *dst, drmp3_bs *bs, const drmp3_L3_gr_info *gr_info, const float *scf, int layer3gr_limit) +{ + static const drmp3_int16 tabs[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 785,785,785,785,784,784,784,784,513,513,513,513,513,513,513,513,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256, + -255,1313,1298,1282,785,785,785,785,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,290,288, + -255,1313,1298,1282,769,769,769,769,529,529,529,529,529,529,529,529,528,528,528,528,528,528,528,528,512,512,512,512,512,512,512,512,290,288, + -253,-318,-351,-367,785,785,785,785,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,819,818,547,547,275,275,275,275,561,560,515,546,289,274,288,258, + -254,-287,1329,1299,1314,1312,1057,1057,1042,1042,1026,1026,784,784,784,784,529,529,529,529,529,529,529,529,769,769,769,769,768,768,768,768,563,560,306,306,291,259, + -252,-413,-477,-542,1298,-575,1041,1041,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-383,-399,1107,1092,1106,1061,849,849,789,789,1104,1091,773,773,1076,1075,341,340,325,309,834,804,577,577,532,532,516,516,832,818,803,816,561,561,531,531,515,546,289,289,288,258, + -252,-429,-493,-559,1057,1057,1042,1042,529,529,529,529,529,529,529,529,784,784,784,784,769,769,769,769,512,512,512,512,512,512,512,512,-382,1077,-415,1106,1061,1104,849,849,789,789,1091,1076,1029,1075,834,834,597,581,340,340,339,324,804,833,532,532,832,772,818,803,817,787,816,771,290,290,290,290,288,258, + -253,-349,-414,-447,-463,1329,1299,-479,1314,1312,1057,1057,1042,1042,1026,1026,785,785,785,785,784,784,784,784,769,769,769,769,768,768,768,768,-319,851,821,-335,836,850,805,849,341,340,325,336,533,533,579,579,564,564,773,832,578,548,563,516,321,276,306,291,304,259, + -251,-572,-733,-830,-863,-879,1041,1041,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-511,-527,-543,1396,1351,1381,1366,1395,1335,1380,-559,1334,1138,1138,1063,1063,1350,1392,1031,1031,1062,1062,1364,1363,1120,1120,1333,1348,881,881,881,881,375,374,359,373,343,358,341,325,791,791,1123,1122,-703,1105,1045,-719,865,865,790,790,774,774,1104,1029,338,293,323,308,-799,-815,833,788,772,818,803,816,322,292,307,320,561,531,515,546,289,274,288,258, + -251,-525,-605,-685,-765,-831,-846,1298,1057,1057,1312,1282,785,785,785,785,784,784,784,784,769,769,769,769,512,512,512,512,512,512,512,512,1399,1398,1383,1367,1382,1396,1351,-511,1381,1366,1139,1139,1079,1079,1124,1124,1364,1349,1363,1333,882,882,882,882,807,807,807,807,1094,1094,1136,1136,373,341,535,535,881,775,867,822,774,-591,324,338,-671,849,550,550,866,864,609,609,293,336,534,534,789,835,773,-751,834,804,308,307,833,788,832,772,562,562,547,547,305,275,560,515,290,290, + -252,-397,-477,-557,-622,-653,-719,-735,-750,1329,1299,1314,1057,1057,1042,1042,1312,1282,1024,1024,785,785,785,785,784,784,784,784,769,769,769,769,-383,1127,1141,1111,1126,1140,1095,1110,869,869,883,883,1079,1109,882,882,375,374,807,868,838,881,791,-463,867,822,368,263,852,837,836,-543,610,610,550,550,352,336,534,534,865,774,851,821,850,805,593,533,579,564,773,832,578,578,548,548,577,577,307,276,306,291,516,560,259,259, + -250,-2107,-2507,-2764,-2909,-2974,-3007,-3023,1041,1041,1040,1040,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-767,-1052,-1213,-1277,-1358,-1405,-1469,-1535,-1550,-1582,-1614,-1647,-1662,-1694,-1726,-1759,-1774,-1807,-1822,-1854,-1886,1565,-1919,-1935,-1951,-1967,1731,1730,1580,1717,-1983,1729,1564,-1999,1548,-2015,-2031,1715,1595,-2047,1714,-2063,1610,-2079,1609,-2095,1323,1323,1457,1457,1307,1307,1712,1547,1641,1700,1699,1594,1685,1625,1442,1442,1322,1322,-780,-973,-910,1279,1278,1277,1262,1276,1261,1275,1215,1260,1229,-959,974,974,989,989,-943,735,478,478,495,463,506,414,-1039,1003,958,1017,927,942,987,957,431,476,1272,1167,1228,-1183,1256,-1199,895,895,941,941,1242,1227,1212,1135,1014,1014,490,489,503,487,910,1013,985,925,863,894,970,955,1012,847,-1343,831,755,755,984,909,428,366,754,559,-1391,752,486,457,924,997,698,698,983,893,740,740,908,877,739,739,667,667,953,938,497,287,271,271,683,606,590,712,726,574,302,302,738,736,481,286,526,725,605,711,636,724,696,651,589,681,666,710,364,467,573,695,466,466,301,465,379,379,709,604,665,679,316,316,634,633,436,436,464,269,424,394,452,332,438,363,347,408,393,448,331,422,362,407,392,421,346,406,391,376,375,359,1441,1306,-2367,1290,-2383,1337,-2399,-2415,1426,1321,-2431,1411,1336,-2447,-2463,-2479,1169,1169,1049,1049,1424,1289,1412,1352,1319,-2495,1154,1154,1064,1064,1153,1153,416,390,360,404,403,389,344,374,373,343,358,372,327,357,342,311,356,326,1395,1394,1137,1137,1047,1047,1365,1392,1287,1379,1334,1364,1349,1378,1318,1363,792,792,792,792,1152,1152,1032,1032,1121,1121,1046,1046,1120,1120,1030,1030,-2895,1106,1061,1104,849,849,789,789,1091,1076,1029,1090,1060,1075,833,833,309,324,532,532,832,772,818,803,561,561,531,560,515,546,289,274,288,258, + -250,-1179,-1579,-1836,-1996,-2124,-2253,-2333,-2413,-2477,-2542,-2574,-2607,-2622,-2655,1314,1313,1298,1312,1282,785,785,785,785,1040,1040,1025,1025,768,768,768,768,-766,-798,-830,-862,-895,-911,-927,-943,-959,-975,-991,-1007,-1023,-1039,-1055,-1070,1724,1647,-1103,-1119,1631,1767,1662,1738,1708,1723,-1135,1780,1615,1779,1599,1677,1646,1778,1583,-1151,1777,1567,1737,1692,1765,1722,1707,1630,1751,1661,1764,1614,1736,1676,1763,1750,1645,1598,1721,1691,1762,1706,1582,1761,1566,-1167,1749,1629,767,766,751,765,494,494,735,764,719,749,734,763,447,447,748,718,477,506,431,491,446,476,461,505,415,430,475,445,504,399,460,489,414,503,383,474,429,459,502,502,746,752,488,398,501,473,413,472,486,271,480,270,-1439,-1455,1357,-1471,-1487,-1503,1341,1325,-1519,1489,1463,1403,1309,-1535,1372,1448,1418,1476,1356,1462,1387,-1551,1475,1340,1447,1402,1386,-1567,1068,1068,1474,1461,455,380,468,440,395,425,410,454,364,467,466,464,453,269,409,448,268,432,1371,1473,1432,1417,1308,1460,1355,1446,1459,1431,1083,1083,1401,1416,1458,1445,1067,1067,1370,1457,1051,1051,1291,1430,1385,1444,1354,1415,1400,1443,1082,1082,1173,1113,1186,1066,1185,1050,-1967,1158,1128,1172,1097,1171,1081,-1983,1157,1112,416,266,375,400,1170,1142,1127,1065,793,793,1169,1033,1156,1096,1141,1111,1155,1080,1126,1140,898,898,808,808,897,897,792,792,1095,1152,1032,1125,1110,1139,1079,1124,882,807,838,881,853,791,-2319,867,368,263,822,852,837,866,806,865,-2399,851,352,262,534,534,821,836,594,594,549,549,593,593,533,533,848,773,579,579,564,578,548,563,276,276,577,576,306,291,516,560,305,305,275,259, + -251,-892,-2058,-2620,-2828,-2957,-3023,-3039,1041,1041,1040,1040,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-511,-527,-543,-559,1530,-575,-591,1528,1527,1407,1526,1391,1023,1023,1023,1023,1525,1375,1268,1268,1103,1103,1087,1087,1039,1039,1523,-604,815,815,815,815,510,495,509,479,508,463,507,447,431,505,415,399,-734,-782,1262,-815,1259,1244,-831,1258,1228,-847,-863,1196,-879,1253,987,987,748,-767,493,493,462,477,414,414,686,669,478,446,461,445,474,429,487,458,412,471,1266,1264,1009,1009,799,799,-1019,-1276,-1452,-1581,-1677,-1757,-1821,-1886,-1933,-1997,1257,1257,1483,1468,1512,1422,1497,1406,1467,1496,1421,1510,1134,1134,1225,1225,1466,1451,1374,1405,1252,1252,1358,1480,1164,1164,1251,1251,1238,1238,1389,1465,-1407,1054,1101,-1423,1207,-1439,830,830,1248,1038,1237,1117,1223,1148,1236,1208,411,426,395,410,379,269,1193,1222,1132,1235,1221,1116,976,976,1192,1162,1177,1220,1131,1191,963,963,-1647,961,780,-1663,558,558,994,993,437,408,393,407,829,978,813,797,947,-1743,721,721,377,392,844,950,828,890,706,706,812,859,796,960,948,843,934,874,571,571,-1919,690,555,689,421,346,539,539,944,779,918,873,932,842,903,888,570,570,931,917,674,674,-2575,1562,-2591,1609,-2607,1654,1322,1322,1441,1441,1696,1546,1683,1593,1669,1624,1426,1426,1321,1321,1639,1680,1425,1425,1305,1305,1545,1668,1608,1623,1667,1592,1638,1666,1320,1320,1652,1607,1409,1409,1304,1304,1288,1288,1664,1637,1395,1395,1335,1335,1622,1636,1394,1394,1319,1319,1606,1621,1392,1392,1137,1137,1137,1137,345,390,360,375,404,373,1047,-2751,-2767,-2783,1062,1121,1046,-2799,1077,-2815,1106,1061,789,789,1105,1104,263,355,310,340,325,354,352,262,339,324,1091,1076,1029,1090,1060,1075,833,833,788,788,1088,1028,818,818,803,803,561,561,531,531,816,771,546,546,289,274,288,258, + -253,-317,-381,-446,-478,-509,1279,1279,-811,-1179,-1451,-1756,-1900,-2028,-2189,-2253,-2333,-2414,-2445,-2511,-2526,1313,1298,-2559,1041,1041,1040,1040,1025,1025,1024,1024,1022,1007,1021,991,1020,975,1019,959,687,687,1018,1017,671,671,655,655,1016,1015,639,639,758,758,623,623,757,607,756,591,755,575,754,559,543,543,1009,783,-575,-621,-685,-749,496,-590,750,749,734,748,974,989,1003,958,988,973,1002,942,987,957,972,1001,926,986,941,971,956,1000,910,985,925,999,894,970,-1071,-1087,-1102,1390,-1135,1436,1509,1451,1374,-1151,1405,1358,1480,1420,-1167,1507,1494,1389,1342,1465,1435,1450,1326,1505,1310,1493,1373,1479,1404,1492,1464,1419,428,443,472,397,736,526,464,464,486,457,442,471,484,482,1357,1449,1434,1478,1388,1491,1341,1490,1325,1489,1463,1403,1309,1477,1372,1448,1418,1433,1476,1356,1462,1387,-1439,1475,1340,1447,1402,1474,1324,1461,1371,1473,269,448,1432,1417,1308,1460,-1711,1459,-1727,1441,1099,1099,1446,1386,1431,1401,-1743,1289,1083,1083,1160,1160,1458,1445,1067,1067,1370,1457,1307,1430,1129,1129,1098,1098,268,432,267,416,266,400,-1887,1144,1187,1082,1173,1113,1186,1066,1050,1158,1128,1143,1172,1097,1171,1081,420,391,1157,1112,1170,1142,1127,1065,1169,1049,1156,1096,1141,1111,1155,1080,1126,1154,1064,1153,1140,1095,1048,-2159,1125,1110,1137,-2175,823,823,1139,1138,807,807,384,264,368,263,868,838,853,791,867,822,852,837,866,806,865,790,-2319,851,821,836,352,262,850,805,849,-2399,533,533,835,820,336,261,578,548,563,577,532,532,832,772,562,562,547,547,305,275,560,515,290,290,288,258 }; + static const drmp3_uint8 tab32[] = { 130,162,193,209,44,28,76,140,9,9,9,9,9,9,9,9,190,254,222,238,126,94,157,157,109,61,173,205}; + static const drmp3_uint8 tab33[] = { 252,236,220,204,188,172,156,140,124,108,92,76,60,44,28,12 }; + static const drmp3_int16 tabindex[2*16] = { 0,32,64,98,0,132,180,218,292,364,426,538,648,746,0,1126,1460,1460,1460,1460,1460,1460,1460,1460,1842,1842,1842,1842,1842,1842,1842,1842 }; + static const drmp3_uint8 g_linbits[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,6,8,10,13,4,5,6,7,8,9,11,13 }; + +#define DRMP3_PEEK_BITS(n) (bs_cache >> (32 - (n))) +#define DRMP3_FLUSH_BITS(n) { bs_cache <<= (n); bs_sh += (n); } +#define DRMP3_CHECK_BITS while (bs_sh >= 0) { bs_cache |= (drmp3_uint32)*bs_next_ptr++ << bs_sh; bs_sh -= 8; } +#define DRMP3_BSPOS ((bs_next_ptr - bs->buf)*8 - 24 + bs_sh) + + float one = 0.0f; + int ireg = 0, big_val_cnt = gr_info->big_values; + const drmp3_uint8 *sfb = gr_info->sfbtab; + const drmp3_uint8 *bs_next_ptr = bs->buf + bs->pos/8; + drmp3_uint32 bs_cache = (((bs_next_ptr[0]*256u + bs_next_ptr[1])*256u + bs_next_ptr[2])*256u + bs_next_ptr[3]) << (bs->pos & 7); + int pairs_to_decode, np, bs_sh = (bs->pos & 7) - 8; + bs_next_ptr += 4; + + while (big_val_cnt > 0) + { + int tab_num = gr_info->table_select[ireg]; + int sfb_cnt = gr_info->region_count[ireg++]; + const drmp3_int16 *codebook = tabs + tabindex[tab_num]; + int linbits = g_linbits[tab_num]; + if (linbits) + { + do + { + np = *sfb++ / 2; + pairs_to_decode = DRMP3_MIN(big_val_cnt, np); + one = *scf++; + do + { + int j, w = 5; + int leaf = codebook[DRMP3_PEEK_BITS(w)]; + while (leaf < 0) + { + DRMP3_FLUSH_BITS(w); + w = leaf & 7; + leaf = codebook[DRMP3_PEEK_BITS(w) - (leaf >> 3)]; + } + DRMP3_FLUSH_BITS(leaf >> 8); + + for (j = 0; j < 2; j++, dst++, leaf >>= 4) + { + int lsb = leaf & 0x0F; + if (lsb == 15) + { + lsb += DRMP3_PEEK_BITS(linbits); + DRMP3_FLUSH_BITS(linbits); + DRMP3_CHECK_BITS; + *dst = one*drmp3_L3_pow_43(lsb)*((drmp3_int32)bs_cache < 0 ? -1: 1); + } else + { + *dst = g_drmp3_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; + } + DRMP3_FLUSH_BITS(lsb ? 1 : 0); + } + DRMP3_CHECK_BITS; + } while (--pairs_to_decode); + } while ((big_val_cnt -= np) > 0 && --sfb_cnt >= 0); + } else + { + do + { + np = *sfb++ / 2; + pairs_to_decode = DRMP3_MIN(big_val_cnt, np); + one = *scf++; + do + { + int j, w = 5; + int leaf = codebook[DRMP3_PEEK_BITS(w)]; + while (leaf < 0) + { + DRMP3_FLUSH_BITS(w); + w = leaf & 7; + leaf = codebook[DRMP3_PEEK_BITS(w) - (leaf >> 3)]; + } + DRMP3_FLUSH_BITS(leaf >> 8); + + for (j = 0; j < 2; j++, dst++, leaf >>= 4) + { + int lsb = leaf & 0x0F; + *dst = g_drmp3_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; + DRMP3_FLUSH_BITS(lsb ? 1 : 0); + } + DRMP3_CHECK_BITS; + } while (--pairs_to_decode); + } while ((big_val_cnt -= np) > 0 && --sfb_cnt >= 0); + } + } + + for (np = 1 - big_val_cnt;; dst += 4) + { + const drmp3_uint8 *codebook_count1 = (gr_info->count1_table) ? tab33 : tab32; + int leaf = codebook_count1[DRMP3_PEEK_BITS(4)]; + if (!(leaf & 8)) + { + leaf = codebook_count1[(leaf >> 3) + (bs_cache << 4 >> (32 - (leaf & 3)))]; + } + DRMP3_FLUSH_BITS(leaf & 7); + if (DRMP3_BSPOS > layer3gr_limit) + { + break; + } +#define DRMP3_RELOAD_SCALEFACTOR if (!--np) { np = *sfb++/2; if (!np) break; one = *scf++; } +#define DRMP3_DEQ_COUNT1(s) if (leaf & (128 >> s)) { dst[s] = ((drmp3_int32)bs_cache < 0) ? -one : one; DRMP3_FLUSH_BITS(1) } + DRMP3_RELOAD_SCALEFACTOR; + DRMP3_DEQ_COUNT1(0); + DRMP3_DEQ_COUNT1(1); + DRMP3_RELOAD_SCALEFACTOR; + DRMP3_DEQ_COUNT1(2); + DRMP3_DEQ_COUNT1(3); + DRMP3_CHECK_BITS; + } + + bs->pos = layer3gr_limit; +} + +static void drmp3_L3_midside_stereo(float *left, int n) +{ + int i = 0; + float *right = left + 576; +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) + { + for (; i < n - 3; i += 4) + { + drmp3_f4 vl = DRMP3_VLD(left + i); + drmp3_f4 vr = DRMP3_VLD(right + i); + DRMP3_VSTORE(left + i, DRMP3_VADD(vl, vr)); + DRMP3_VSTORE(right + i, DRMP3_VSUB(vl, vr)); + } +#ifdef __GNUC__ + /* Workaround for spurious -Waggressive-loop-optimizations warning from gcc. + * For more info see: https://github.com/lieff/minimp3/issues/88 + */ + if (__builtin_constant_p(n % 4 == 0) && n % 4 == 0) + return; +#endif + } +#endif + for (; i < n; i++) + { + float a = left[i]; + float b = right[i]; + left[i] = a + b; + right[i] = a - b; + } +} + +static void drmp3_L3_intensity_stereo_band(float *left, int n, float kl, float kr) +{ + int i; + for (i = 0; i < n; i++) + { + left[i + 576] = left[i]*kr; + left[i] = left[i]*kl; + } +} + +static void drmp3_L3_stereo_top_band(const float *right, const drmp3_uint8 *sfb, int nbands, int max_band[3]) +{ + int i, k; + + max_band[0] = max_band[1] = max_band[2] = -1; + + for (i = 0; i < nbands; i++) + { + for (k = 0; k < sfb[i]; k += 2) + { + if (right[k] != 0 || right[k + 1] != 0) + { + max_band[i % 3] = i; + break; + } + } + right += sfb[i]; + } +} + +static void drmp3_L3_stereo_process(float *left, const drmp3_uint8 *ist_pos, const drmp3_uint8 *sfb, const drmp3_uint8 *hdr, int max_band[3], int mpeg2_sh) +{ + static const float g_pan[7*2] = { 0,1,0.21132487f,0.78867513f,0.36602540f,0.63397460f,0.5f,0.5f,0.63397460f,0.36602540f,0.78867513f,0.21132487f,1,0 }; + unsigned i, max_pos = DRMP3_HDR_TEST_MPEG1(hdr) ? 7 : 64; + + for (i = 0; sfb[i]; i++) + { + unsigned ipos = ist_pos[i]; + if ((int)i > max_band[i % 3] && ipos < max_pos) + { + float kl, kr, s = DRMP3_HDR_TEST_MS_STEREO(hdr) ? 1.41421356f : 1; + if (DRMP3_HDR_TEST_MPEG1(hdr)) + { + kl = g_pan[2*ipos]; + kr = g_pan[2*ipos + 1]; + } else + { + kl = 1; + kr = drmp3_L3_ldexp_q2(1, (ipos + 1) >> 1 << mpeg2_sh); + if (ipos & 1) + { + kl = kr; + kr = 1; + } + } + drmp3_L3_intensity_stereo_band(left, sfb[i], kl*s, kr*s); + } else if (DRMP3_HDR_TEST_MS_STEREO(hdr)) + { + drmp3_L3_midside_stereo(left, sfb[i]); + } + left += sfb[i]; + } +} + +static void drmp3_L3_intensity_stereo(float *left, drmp3_uint8 *ist_pos, const drmp3_L3_gr_info *gr, const drmp3_uint8 *hdr) +{ + int max_band[3], n_sfb = gr->n_long_sfb + gr->n_short_sfb; + int i, max_blocks = gr->n_short_sfb ? 3 : 1; + + drmp3_L3_stereo_top_band(left + 576, gr->sfbtab, n_sfb, max_band); + if (gr->n_long_sfb) + { + max_band[0] = max_band[1] = max_band[2] = DRMP3_MAX(DRMP3_MAX(max_band[0], max_band[1]), max_band[2]); + } + for (i = 0; i < max_blocks; i++) + { + int default_pos = DRMP3_HDR_TEST_MPEG1(hdr) ? 3 : 0; + int itop = n_sfb - max_blocks + i; + int prev = itop - max_blocks; + ist_pos[itop] = (drmp3_uint8)(max_band[i] >= prev ? default_pos : ist_pos[prev]); + } + drmp3_L3_stereo_process(left, ist_pos, gr->sfbtab, hdr, max_band, gr[1].scalefac_compress & 1); +} + +static void drmp3_L3_reorder(float *grbuf, float *scratch, const drmp3_uint8 *sfb) +{ + int i, len; + float *src = grbuf, *dst = scratch; + + for (;0 != (len = *sfb); sfb += 3, src += 2*len) + { + for (i = 0; i < len; i++, src++) + { + *dst++ = src[0*len]; + *dst++ = src[1*len]; + *dst++ = src[2*len]; + } + } + DRMP3_COPY_MEMORY(grbuf, scratch, (dst - scratch)*sizeof(float)); +} + +static void drmp3_L3_antialias(float *grbuf, int nbands) +{ + static const float g_aa[2][8] = { + {0.85749293f,0.88174200f,0.94962865f,0.98331459f,0.99551782f,0.99916056f,0.99989920f,0.99999316f}, + {0.51449576f,0.47173197f,0.31337745f,0.18191320f,0.09457419f,0.04096558f,0.01419856f,0.00369997f} + }; + + for (; nbands > 0; nbands--, grbuf += 18) + { + int i = 0; +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) for (; i < 8; i += 4) + { + drmp3_f4 vu = DRMP3_VLD(grbuf + 18 + i); + drmp3_f4 vd = DRMP3_VLD(grbuf + 14 - i); + drmp3_f4 vc0 = DRMP3_VLD(g_aa[0] + i); + drmp3_f4 vc1 = DRMP3_VLD(g_aa[1] + i); + vd = DRMP3_VREV(vd); + DRMP3_VSTORE(grbuf + 18 + i, DRMP3_VSUB(DRMP3_VMUL(vu, vc0), DRMP3_VMUL(vd, vc1))); + vd = DRMP3_VADD(DRMP3_VMUL(vu, vc1), DRMP3_VMUL(vd, vc0)); + DRMP3_VSTORE(grbuf + 14 - i, DRMP3_VREV(vd)); + } +#endif +#ifndef DR_MP3_ONLY_SIMD + for(; i < 8; i++) + { + float u = grbuf[18 + i]; + float d = grbuf[17 - i]; + grbuf[18 + i] = u*g_aa[0][i] - d*g_aa[1][i]; + grbuf[17 - i] = u*g_aa[1][i] + d*g_aa[0][i]; + } +#endif + } +} + +static void drmp3_L3_dct3_9(float *y) +{ + float s0, s1, s2, s3, s4, s5, s6, s7, s8, t0, t2, t4; + + s0 = y[0]; s2 = y[2]; s4 = y[4]; s6 = y[6]; s8 = y[8]; + t0 = s0 + s6*0.5f; + s0 -= s6; + t4 = (s4 + s2)*0.93969262f; + t2 = (s8 + s2)*0.76604444f; + s6 = (s4 - s8)*0.17364818f; + s4 += s8 - s2; + + s2 = s0 - s4*0.5f; + y[4] = s4 + s0; + s8 = t0 - t2 + s6; + s0 = t0 - t4 + t2; + s4 = t0 + t4 - s6; + + s1 = y[1]; s3 = y[3]; s5 = y[5]; s7 = y[7]; + + s3 *= 0.86602540f; + t0 = (s5 + s1)*0.98480775f; + t4 = (s5 - s7)*0.34202014f; + t2 = (s1 + s7)*0.64278761f; + s1 = (s1 - s5 - s7)*0.86602540f; + + s5 = t0 - s3 - t2; + s7 = t4 - s3 - t0; + s3 = t4 + s3 - t2; + + y[0] = s4 - s7; + y[1] = s2 + s1; + y[2] = s0 - s3; + y[3] = s8 + s5; + y[5] = s8 - s5; + y[6] = s0 + s3; + y[7] = s2 - s1; + y[8] = s4 + s7; +} + +static void drmp3_L3_imdct36(float *grbuf, float *overlap, const float *window, int nbands) +{ + int i, j; + static const float g_twid9[18] = { + 0.73727734f,0.79335334f,0.84339145f,0.88701083f,0.92387953f,0.95371695f,0.97629601f,0.99144486f,0.99904822f,0.67559021f,0.60876143f,0.53729961f,0.46174861f,0.38268343f,0.30070580f,0.21643961f,0.13052619f,0.04361938f + }; + + for (j = 0; j < nbands; j++, grbuf += 18, overlap += 9) + { + float co[9], si[9]; + co[0] = -grbuf[0]; + si[0] = grbuf[17]; + for (i = 0; i < 4; i++) + { + si[8 - 2*i] = grbuf[4*i + 1] - grbuf[4*i + 2]; + co[1 + 2*i] = grbuf[4*i + 1] + grbuf[4*i + 2]; + si[7 - 2*i] = grbuf[4*i + 4] - grbuf[4*i + 3]; + co[2 + 2*i] = -(grbuf[4*i + 3] + grbuf[4*i + 4]); + } + drmp3_L3_dct3_9(co); + drmp3_L3_dct3_9(si); + + si[1] = -si[1]; + si[3] = -si[3]; + si[5] = -si[5]; + si[7] = -si[7]; + + i = 0; + +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) for (; i < 8; i += 4) + { + drmp3_f4 vovl = DRMP3_VLD(overlap + i); + drmp3_f4 vc = DRMP3_VLD(co + i); + drmp3_f4 vs = DRMP3_VLD(si + i); + drmp3_f4 vr0 = DRMP3_VLD(g_twid9 + i); + drmp3_f4 vr1 = DRMP3_VLD(g_twid9 + 9 + i); + drmp3_f4 vw0 = DRMP3_VLD(window + i); + drmp3_f4 vw1 = DRMP3_VLD(window + 9 + i); + drmp3_f4 vsum = DRMP3_VADD(DRMP3_VMUL(vc, vr1), DRMP3_VMUL(vs, vr0)); + DRMP3_VSTORE(overlap + i, DRMP3_VSUB(DRMP3_VMUL(vc, vr0), DRMP3_VMUL(vs, vr1))); + DRMP3_VSTORE(grbuf + i, DRMP3_VSUB(DRMP3_VMUL(vovl, vw0), DRMP3_VMUL(vsum, vw1))); + vsum = DRMP3_VADD(DRMP3_VMUL(vovl, vw1), DRMP3_VMUL(vsum, vw0)); + DRMP3_VSTORE(grbuf + 14 - i, DRMP3_VREV(vsum)); + } +#endif + for (; i < 9; i++) + { + float ovl = overlap[i]; + float sum = co[i]*g_twid9[9 + i] + si[i]*g_twid9[0 + i]; + overlap[i] = co[i]*g_twid9[0 + i] - si[i]*g_twid9[9 + i]; + grbuf[i] = ovl*window[0 + i] - sum*window[9 + i]; + grbuf[17 - i] = ovl*window[9 + i] + sum*window[0 + i]; + } + } +} + +static void drmp3_L3_idct3(float x0, float x1, float x2, float *dst) +{ + float m1 = x1*0.86602540f; + float a1 = x0 - x2*0.5f; + dst[1] = x0 + x2; + dst[0] = a1 + m1; + dst[2] = a1 - m1; +} + +static void drmp3_L3_imdct12(float *x, float *dst, float *overlap) +{ + static const float g_twid3[6] = { 0.79335334f,0.92387953f,0.99144486f, 0.60876143f,0.38268343f,0.13052619f }; + float co[3], si[3]; + int i; + + drmp3_L3_idct3(-x[0], x[6] + x[3], x[12] + x[9], co); + drmp3_L3_idct3(x[15], x[12] - x[9], x[6] - x[3], si); + si[1] = -si[1]; + + for (i = 0; i < 3; i++) + { + float ovl = overlap[i]; + float sum = co[i]*g_twid3[3 + i] + si[i]*g_twid3[0 + i]; + overlap[i] = co[i]*g_twid3[0 + i] - si[i]*g_twid3[3 + i]; + dst[i] = ovl*g_twid3[2 - i] - sum*g_twid3[5 - i]; + dst[5 - i] = ovl*g_twid3[5 - i] + sum*g_twid3[2 - i]; + } +} + +static void drmp3_L3_imdct_short(float *grbuf, float *overlap, int nbands) +{ + for (;nbands > 0; nbands--, overlap += 9, grbuf += 18) + { + float tmp[18]; + DRMP3_COPY_MEMORY(tmp, grbuf, sizeof(tmp)); + DRMP3_COPY_MEMORY(grbuf, overlap, 6*sizeof(float)); + drmp3_L3_imdct12(tmp, grbuf + 6, overlap + 6); + drmp3_L3_imdct12(tmp + 1, grbuf + 12, overlap + 6); + drmp3_L3_imdct12(tmp + 2, overlap, overlap + 6); + } +} + +static void drmp3_L3_change_sign(float *grbuf) +{ + int b, i; + for (b = 0, grbuf += 18; b < 32; b += 2, grbuf += 36) + for (i = 1; i < 18; i += 2) + grbuf[i] = -grbuf[i]; +} + +static void drmp3_L3_imdct_gr(float *grbuf, float *overlap, unsigned block_type, unsigned n_long_bands) +{ + static const float g_mdct_window[2][18] = { + { 0.99904822f,0.99144486f,0.97629601f,0.95371695f,0.92387953f,0.88701083f,0.84339145f,0.79335334f,0.73727734f,0.04361938f,0.13052619f,0.21643961f,0.30070580f,0.38268343f,0.46174861f,0.53729961f,0.60876143f,0.67559021f }, + { 1,1,1,1,1,1,0.99144486f,0.92387953f,0.79335334f,0,0,0,0,0,0,0.13052619f,0.38268343f,0.60876143f } + }; + if (n_long_bands) + { + drmp3_L3_imdct36(grbuf, overlap, g_mdct_window[0], n_long_bands); + grbuf += 18*n_long_bands; + overlap += 9*n_long_bands; + } + if (block_type == DRMP3_SHORT_BLOCK_TYPE) + drmp3_L3_imdct_short(grbuf, overlap, 32 - n_long_bands); + else + drmp3_L3_imdct36(grbuf, overlap, g_mdct_window[block_type == DRMP3_STOP_BLOCK_TYPE], 32 - n_long_bands); +} + +static void drmp3_L3_save_reservoir(drmp3dec *h, drmp3dec_scratch *s) +{ + int pos = (s->bs.pos + 7)/8u; + int remains = s->bs.limit/8u - pos; + if (remains > DRMP3_MAX_BITRESERVOIR_BYTES) + { + pos += remains - DRMP3_MAX_BITRESERVOIR_BYTES; + remains = DRMP3_MAX_BITRESERVOIR_BYTES; + } + if (remains > 0) + { + DRMP3_MOVE_MEMORY(h->reserv_buf, s->maindata + pos, remains); + } + h->reserv = remains; +} + +static int drmp3_L3_restore_reservoir(drmp3dec *h, drmp3_bs *bs, drmp3dec_scratch *s, int main_data_begin) +{ + int frame_bytes = (bs->limit - bs->pos)/8; + int bytes_have = DRMP3_MIN(h->reserv, main_data_begin); + DRMP3_COPY_MEMORY(s->maindata, h->reserv_buf + DRMP3_MAX(0, h->reserv - main_data_begin), DRMP3_MIN(h->reserv, main_data_begin)); + DRMP3_COPY_MEMORY(s->maindata + bytes_have, bs->buf + bs->pos/8, frame_bytes); + drmp3_bs_init(&s->bs, s->maindata, bytes_have + frame_bytes); + return h->reserv >= main_data_begin; +} + +static void drmp3_L3_decode(drmp3dec *h, drmp3dec_scratch *s, drmp3_L3_gr_info *gr_info, int nch) +{ + int ch; + + for (ch = 0; ch < nch; ch++) + { + int layer3gr_limit = s->bs.pos + gr_info[ch].part_23_length; + drmp3_L3_decode_scalefactors(h->header, s->ist_pos[ch], &s->bs, gr_info + ch, s->scf, ch); + drmp3_L3_huffman(s->grbuf[ch], &s->bs, gr_info + ch, s->scf, layer3gr_limit); + } + + if (DRMP3_HDR_TEST_I_STEREO(h->header)) + { + drmp3_L3_intensity_stereo(s->grbuf[0], s->ist_pos[1], gr_info, h->header); + } else if (DRMP3_HDR_IS_MS_STEREO(h->header)) + { + drmp3_L3_midside_stereo(s->grbuf[0], 576); + } + + for (ch = 0; ch < nch; ch++, gr_info++) + { + int aa_bands = 31; + int n_long_bands = (gr_info->mixed_block_flag ? 2 : 0) << (int)(DRMP3_HDR_GET_MY_SAMPLE_RATE(h->header) == 2); + + if (gr_info->n_short_sfb) + { + aa_bands = n_long_bands - 1; + drmp3_L3_reorder(s->grbuf[ch] + n_long_bands*18, s->syn[0], gr_info->sfbtab + gr_info->n_long_sfb); + } + + drmp3_L3_antialias(s->grbuf[ch], aa_bands); + drmp3_L3_imdct_gr(s->grbuf[ch], h->mdct_overlap[ch], gr_info->block_type, n_long_bands); + drmp3_L3_change_sign(s->grbuf[ch]); + } +} + +static void drmp3d_DCT_II(float *grbuf, int n) +{ + static const float g_sec[24] = { + 10.19000816f,0.50060302f,0.50241929f,3.40760851f,0.50547093f,0.52249861f,2.05778098f,0.51544732f,0.56694406f,1.48416460f,0.53104258f,0.64682180f,1.16943991f,0.55310392f,0.78815460f,0.97256821f,0.58293498f,1.06067765f,0.83934963f,0.62250412f,1.72244716f,0.74453628f,0.67480832f,5.10114861f + }; + int i, k = 0; +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) for (; k < n; k += 4) + { + drmp3_f4 t[4][8], *x; + float *y = grbuf + k; + + for (x = t[0], i = 0; i < 8; i++, x++) + { + drmp3_f4 x0 = DRMP3_VLD(&y[i*18]); + drmp3_f4 x1 = DRMP3_VLD(&y[(15 - i)*18]); + drmp3_f4 x2 = DRMP3_VLD(&y[(16 + i)*18]); + drmp3_f4 x3 = DRMP3_VLD(&y[(31 - i)*18]); + drmp3_f4 t0 = DRMP3_VADD(x0, x3); + drmp3_f4 t1 = DRMP3_VADD(x1, x2); + drmp3_f4 t2 = DRMP3_VMUL_S(DRMP3_VSUB(x1, x2), g_sec[3*i + 0]); + drmp3_f4 t3 = DRMP3_VMUL_S(DRMP3_VSUB(x0, x3), g_sec[3*i + 1]); + x[0] = DRMP3_VADD(t0, t1); + x[8] = DRMP3_VMUL_S(DRMP3_VSUB(t0, t1), g_sec[3*i + 2]); + x[16] = DRMP3_VADD(t3, t2); + x[24] = DRMP3_VMUL_S(DRMP3_VSUB(t3, t2), g_sec[3*i + 2]); + } + for (x = t[0], i = 0; i < 4; i++, x += 8) + { + drmp3_f4 x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3], x4 = x[4], x5 = x[5], x6 = x[6], x7 = x[7], xt; + xt = DRMP3_VSUB(x0, x7); x0 = DRMP3_VADD(x0, x7); + x7 = DRMP3_VSUB(x1, x6); x1 = DRMP3_VADD(x1, x6); + x6 = DRMP3_VSUB(x2, x5); x2 = DRMP3_VADD(x2, x5); + x5 = DRMP3_VSUB(x3, x4); x3 = DRMP3_VADD(x3, x4); + x4 = DRMP3_VSUB(x0, x3); x0 = DRMP3_VADD(x0, x3); + x3 = DRMP3_VSUB(x1, x2); x1 = DRMP3_VADD(x1, x2); + x[0] = DRMP3_VADD(x0, x1); + x[4] = DRMP3_VMUL_S(DRMP3_VSUB(x0, x1), 0.70710677f); + x5 = DRMP3_VADD(x5, x6); + x6 = DRMP3_VMUL_S(DRMP3_VADD(x6, x7), 0.70710677f); + x7 = DRMP3_VADD(x7, xt); + x3 = DRMP3_VMUL_S(DRMP3_VADD(x3, x4), 0.70710677f); + x5 = DRMP3_VSUB(x5, DRMP3_VMUL_S(x7, 0.198912367f)); /* rotate by PI/8 */ + x7 = DRMP3_VADD(x7, DRMP3_VMUL_S(x5, 0.382683432f)); + x5 = DRMP3_VSUB(x5, DRMP3_VMUL_S(x7, 0.198912367f)); + x0 = DRMP3_VSUB(xt, x6); xt = DRMP3_VADD(xt, x6); + x[1] = DRMP3_VMUL_S(DRMP3_VADD(xt, x7), 0.50979561f); + x[2] = DRMP3_VMUL_S(DRMP3_VADD(x4, x3), 0.54119611f); + x[3] = DRMP3_VMUL_S(DRMP3_VSUB(x0, x5), 0.60134488f); + x[5] = DRMP3_VMUL_S(DRMP3_VADD(x0, x5), 0.89997619f); + x[6] = DRMP3_VMUL_S(DRMP3_VSUB(x4, x3), 1.30656302f); + x[7] = DRMP3_VMUL_S(DRMP3_VSUB(xt, x7), 2.56291556f); + } + + if (k > n - 3) + { +#if DRMP3_HAVE_SSE +#define DRMP3_VSAVE2(i, v) _mm_storel_pi((__m64 *)(void*)&y[i*18], v) +#else +#define DRMP3_VSAVE2(i, v) vst1_f32((float32_t *)&y[(i)*18], vget_low_f32(v)) +#endif + for (i = 0; i < 7; i++, y += 4*18) + { + drmp3_f4 s = DRMP3_VADD(t[3][i], t[3][i + 1]); + DRMP3_VSAVE2(0, t[0][i]); + DRMP3_VSAVE2(1, DRMP3_VADD(t[2][i], s)); + DRMP3_VSAVE2(2, DRMP3_VADD(t[1][i], t[1][i + 1])); + DRMP3_VSAVE2(3, DRMP3_VADD(t[2][1 + i], s)); + } + DRMP3_VSAVE2(0, t[0][7]); + DRMP3_VSAVE2(1, DRMP3_VADD(t[2][7], t[3][7])); + DRMP3_VSAVE2(2, t[1][7]); + DRMP3_VSAVE2(3, t[3][7]); + } else + { +#define DRMP3_VSAVE4(i, v) DRMP3_VSTORE(&y[(i)*18], v) + for (i = 0; i < 7; i++, y += 4*18) + { + drmp3_f4 s = DRMP3_VADD(t[3][i], t[3][i + 1]); + DRMP3_VSAVE4(0, t[0][i]); + DRMP3_VSAVE4(1, DRMP3_VADD(t[2][i], s)); + DRMP3_VSAVE4(2, DRMP3_VADD(t[1][i], t[1][i + 1])); + DRMP3_VSAVE4(3, DRMP3_VADD(t[2][1 + i], s)); + } + DRMP3_VSAVE4(0, t[0][7]); + DRMP3_VSAVE4(1, DRMP3_VADD(t[2][7], t[3][7])); + DRMP3_VSAVE4(2, t[1][7]); + DRMP3_VSAVE4(3, t[3][7]); + } + } else +#endif +#ifdef DR_MP3_ONLY_SIMD + {} /* for HAVE_SIMD=1, MINIMP3_ONLY_SIMD=1 case we do not need non-intrinsic "else" branch */ +#else + for (; k < n; k++) + { + float t[4][8], *x, *y = grbuf + k; + + for (x = t[0], i = 0; i < 8; i++, x++) + { + float x0 = y[i*18]; + float x1 = y[(15 - i)*18]; + float x2 = y[(16 + i)*18]; + float x3 = y[(31 - i)*18]; + float t0 = x0 + x3; + float t1 = x1 + x2; + float t2 = (x1 - x2)*g_sec[3*i + 0]; + float t3 = (x0 - x3)*g_sec[3*i + 1]; + x[0] = t0 + t1; + x[8] = (t0 - t1)*g_sec[3*i + 2]; + x[16] = t3 + t2; + x[24] = (t3 - t2)*g_sec[3*i + 2]; + } + for (x = t[0], i = 0; i < 4; i++, x += 8) + { + float x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3], x4 = x[4], x5 = x[5], x6 = x[6], x7 = x[7], xt; + xt = x0 - x7; x0 += x7; + x7 = x1 - x6; x1 += x6; + x6 = x2 - x5; x2 += x5; + x5 = x3 - x4; x3 += x4; + x4 = x0 - x3; x0 += x3; + x3 = x1 - x2; x1 += x2; + x[0] = x0 + x1; + x[4] = (x0 - x1)*0.70710677f; + x5 = x5 + x6; + x6 = (x6 + x7)*0.70710677f; + x7 = x7 + xt; + x3 = (x3 + x4)*0.70710677f; + x5 -= x7*0.198912367f; /* rotate by PI/8 */ + x7 += x5*0.382683432f; + x5 -= x7*0.198912367f; + x0 = xt - x6; xt += x6; + x[1] = (xt + x7)*0.50979561f; + x[2] = (x4 + x3)*0.54119611f; + x[3] = (x0 - x5)*0.60134488f; + x[5] = (x0 + x5)*0.89997619f; + x[6] = (x4 - x3)*1.30656302f; + x[7] = (xt - x7)*2.56291556f; + + } + for (i = 0; i < 7; i++, y += 4*18) + { + y[0*18] = t[0][i]; + y[1*18] = t[2][i] + t[3][i] + t[3][i + 1]; + y[2*18] = t[1][i] + t[1][i + 1]; + y[3*18] = t[2][i + 1] + t[3][i] + t[3][i + 1]; + } + y[0*18] = t[0][7]; + y[1*18] = t[2][7] + t[3][7]; + y[2*18] = t[1][7]; + y[3*18] = t[3][7]; + } +#endif +} + +#ifndef DR_MP3_FLOAT_OUTPUT +typedef drmp3_int16 drmp3d_sample_t; + +static drmp3_int16 drmp3d_scale_pcm(float sample) +{ + drmp3_int16 s; +#if DRMP3_HAVE_ARMV6 + drmp3_int32 s32 = (drmp3_int32)(sample + .5f); + s32 -= (s32 < 0); + s = (drmp3_int16)drmp3_clip_int16_arm(s32); +#else + if (sample >= 32766.5f) return (drmp3_int16) 32767; + if (sample <= -32767.5f) return (drmp3_int16)-32768; + s = (drmp3_int16)(sample + .5f); + s -= (s < 0); /* away from zero, to be compliant */ +#endif + return s; +} +#else +typedef float drmp3d_sample_t; + +static float drmp3d_scale_pcm(float sample) +{ + return sample*(1.f/32768.f); +} +#endif + +static void drmp3d_synth_pair(drmp3d_sample_t *pcm, int nch, const float *z) +{ + float a; + a = (z[14*64] - z[ 0]) * 29; + a += (z[ 1*64] + z[13*64]) * 213; + a += (z[12*64] - z[ 2*64]) * 459; + a += (z[ 3*64] + z[11*64]) * 2037; + a += (z[10*64] - z[ 4*64]) * 5153; + a += (z[ 5*64] + z[ 9*64]) * 6574; + a += (z[ 8*64] - z[ 6*64]) * 37489; + a += z[ 7*64] * 75038; + pcm[0] = drmp3d_scale_pcm(a); + + z += 2; + a = z[14*64] * 104; + a += z[12*64] * 1567; + a += z[10*64] * 9727; + a += z[ 8*64] * 64019; + a += z[ 6*64] * -9975; + a += z[ 4*64] * -45; + a += z[ 2*64] * 146; + a += z[ 0*64] * -5; + pcm[16*nch] = drmp3d_scale_pcm(a); +} + +static void drmp3d_synth(float *xl, drmp3d_sample_t *dstl, int nch, float *lins) +{ + int i; + float *xr = xl + 576*(nch - 1); + drmp3d_sample_t *dstr = dstl + (nch - 1); + + static const float g_win[] = { + -1,26,-31,208,218,401,-519,2063,2000,4788,-5517,7134,5959,35640,-39336,74992, + -1,24,-35,202,222,347,-581,2080,1952,4425,-5879,7640,5288,33791,-41176,74856, + -1,21,-38,196,225,294,-645,2087,1893,4063,-6237,8092,4561,31947,-43006,74630, + -1,19,-41,190,227,244,-711,2085,1822,3705,-6589,8492,3776,30112,-44821,74313, + -1,17,-45,183,228,197,-779,2075,1739,3351,-6935,8840,2935,28289,-46617,73908, + -1,16,-49,176,228,153,-848,2057,1644,3004,-7271,9139,2037,26482,-48390,73415, + -2,14,-53,169,227,111,-919,2032,1535,2663,-7597,9389,1082,24694,-50137,72835, + -2,13,-58,161,224,72,-991,2001,1414,2330,-7910,9592,70,22929,-51853,72169, + -2,11,-63,154,221,36,-1064,1962,1280,2006,-8209,9750,-998,21189,-53534,71420, + -2,10,-68,147,215,2,-1137,1919,1131,1692,-8491,9863,-2122,19478,-55178,70590, + -3,9,-73,139,208,-29,-1210,1870,970,1388,-8755,9935,-3300,17799,-56778,69679, + -3,8,-79,132,200,-57,-1283,1817,794,1095,-8998,9966,-4533,16155,-58333,68692, + -4,7,-85,125,189,-83,-1356,1759,605,814,-9219,9959,-5818,14548,-59838,67629, + -4,7,-91,117,177,-106,-1428,1698,402,545,-9416,9916,-7154,12980,-61289,66494, + -5,6,-97,111,163,-127,-1498,1634,185,288,-9585,9838,-8540,11455,-62684,65290 + }; + float *zlin = lins + 15*64; + const float *w = g_win; + + zlin[4*15] = xl[18*16]; + zlin[4*15 + 1] = xr[18*16]; + zlin[4*15 + 2] = xl[0]; + zlin[4*15 + 3] = xr[0]; + + zlin[4*31] = xl[1 + 18*16]; + zlin[4*31 + 1] = xr[1 + 18*16]; + zlin[4*31 + 2] = xl[1]; + zlin[4*31 + 3] = xr[1]; + + drmp3d_synth_pair(dstr, nch, lins + 4*15 + 1); + drmp3d_synth_pair(dstr + 32*nch, nch, lins + 4*15 + 64 + 1); + drmp3d_synth_pair(dstl, nch, lins + 4*15); + drmp3d_synth_pair(dstl + 32*nch, nch, lins + 4*15 + 64); + +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) for (i = 14; i >= 0; i--) + { +#define DRMP3_VLOAD(k) drmp3_f4 w0 = DRMP3_VSET(*w++); drmp3_f4 w1 = DRMP3_VSET(*w++); drmp3_f4 vz = DRMP3_VLD(&zlin[4*i - 64*k]); drmp3_f4 vy = DRMP3_VLD(&zlin[4*i - 64*(15 - k)]); +#define DRMP3_V0(k) { DRMP3_VLOAD(k) b = DRMP3_VADD(DRMP3_VMUL(vz, w1), DRMP3_VMUL(vy, w0)) ; a = DRMP3_VSUB(DRMP3_VMUL(vz, w0), DRMP3_VMUL(vy, w1)); } +#define DRMP3_V1(k) { DRMP3_VLOAD(k) b = DRMP3_VADD(b, DRMP3_VADD(DRMP3_VMUL(vz, w1), DRMP3_VMUL(vy, w0))); a = DRMP3_VADD(a, DRMP3_VSUB(DRMP3_VMUL(vz, w0), DRMP3_VMUL(vy, w1))); } +#define DRMP3_V2(k) { DRMP3_VLOAD(k) b = DRMP3_VADD(b, DRMP3_VADD(DRMP3_VMUL(vz, w1), DRMP3_VMUL(vy, w0))); a = DRMP3_VADD(a, DRMP3_VSUB(DRMP3_VMUL(vy, w1), DRMP3_VMUL(vz, w0))); } + drmp3_f4 a, b; + zlin[4*i] = xl[18*(31 - i)]; + zlin[4*i + 1] = xr[18*(31 - i)]; + zlin[4*i + 2] = xl[1 + 18*(31 - i)]; + zlin[4*i + 3] = xr[1 + 18*(31 - i)]; + zlin[4*i + 64] = xl[1 + 18*(1 + i)]; + zlin[4*i + 64 + 1] = xr[1 + 18*(1 + i)]; + zlin[4*i - 64 + 2] = xl[18*(1 + i)]; + zlin[4*i - 64 + 3] = xr[18*(1 + i)]; + + DRMP3_V0(0) DRMP3_V2(1) DRMP3_V1(2) DRMP3_V2(3) DRMP3_V1(4) DRMP3_V2(5) DRMP3_V1(6) DRMP3_V2(7) + + { +#ifndef DR_MP3_FLOAT_OUTPUT +#if DRMP3_HAVE_SSE + static const drmp3_f4 g_max = { 32767.0f, 32767.0f, 32767.0f, 32767.0f }; + static const drmp3_f4 g_min = { -32768.0f, -32768.0f, -32768.0f, -32768.0f }; + __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, g_max), g_min)), + _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, g_max), g_min))); + dstr[(15 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 1); + dstr[(17 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 5); + dstl[(15 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 0); + dstl[(17 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 4); + dstr[(47 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 3); + dstr[(49 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 7); + dstl[(47 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 2); + dstl[(49 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 6); +#else + int16x4_t pcma, pcmb; + a = DRMP3_VADD(a, DRMP3_VSET(0.5f)); + b = DRMP3_VADD(b, DRMP3_VSET(0.5f)); + pcma = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(a), vreinterpretq_s32_u32(vcltq_f32(a, DRMP3_VSET(0))))); + pcmb = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(b), vreinterpretq_s32_u32(vcltq_f32(b, DRMP3_VSET(0))))); + vst1_lane_s16(dstr + (15 - i)*nch, pcma, 1); + vst1_lane_s16(dstr + (17 + i)*nch, pcmb, 1); + vst1_lane_s16(dstl + (15 - i)*nch, pcma, 0); + vst1_lane_s16(dstl + (17 + i)*nch, pcmb, 0); + vst1_lane_s16(dstr + (47 - i)*nch, pcma, 3); + vst1_lane_s16(dstr + (49 + i)*nch, pcmb, 3); + vst1_lane_s16(dstl + (47 - i)*nch, pcma, 2); + vst1_lane_s16(dstl + (49 + i)*nch, pcmb, 2); +#endif +#else + #if DRMP3_HAVE_SSE + static const drmp3_f4 g_scale = { 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f }; + #else + const drmp3_f4 g_scale = vdupq_n_f32(1.0f/32768.0f); + #endif + a = DRMP3_VMUL(a, g_scale); + b = DRMP3_VMUL(b, g_scale); +#if DRMP3_HAVE_SSE + _mm_store_ss(dstr + (15 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(1, 1, 1, 1))); + _mm_store_ss(dstr + (17 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(1, 1, 1, 1))); + _mm_store_ss(dstl + (15 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(0, 0, 0, 0))); + _mm_store_ss(dstl + (17 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(0, 0, 0, 0))); + _mm_store_ss(dstr + (47 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 3, 3, 3))); + _mm_store_ss(dstr + (49 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(3, 3, 3, 3))); + _mm_store_ss(dstl + (47 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(2, 2, 2, 2))); + _mm_store_ss(dstl + (49 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(2, 2, 2, 2))); +#else + vst1q_lane_f32(dstr + (15 - i)*nch, a, 1); + vst1q_lane_f32(dstr + (17 + i)*nch, b, 1); + vst1q_lane_f32(dstl + (15 - i)*nch, a, 0); + vst1q_lane_f32(dstl + (17 + i)*nch, b, 0); + vst1q_lane_f32(dstr + (47 - i)*nch, a, 3); + vst1q_lane_f32(dstr + (49 + i)*nch, b, 3); + vst1q_lane_f32(dstl + (47 - i)*nch, a, 2); + vst1q_lane_f32(dstl + (49 + i)*nch, b, 2); +#endif +#endif /* DR_MP3_FLOAT_OUTPUT */ + } + } else +#endif +#ifdef DR_MP3_ONLY_SIMD + {} /* for HAVE_SIMD=1, MINIMP3_ONLY_SIMD=1 case we do not need non-intrinsic "else" branch */ +#else + for (i = 14; i >= 0; i--) + { +#define DRMP3_LOAD(k) float w0 = *w++; float w1 = *w++; float *vz = &zlin[4*i - k*64]; float *vy = &zlin[4*i - (15 - k)*64]; +#define DRMP3_S0(k) { int j; DRMP3_LOAD(k); for (j = 0; j < 4; j++) b[j] = vz[j]*w1 + vy[j]*w0, a[j] = vz[j]*w0 - vy[j]*w1; } +#define DRMP3_S1(k) { int j; DRMP3_LOAD(k); for (j = 0; j < 4; j++) b[j] += vz[j]*w1 + vy[j]*w0, a[j] += vz[j]*w0 - vy[j]*w1; } +#define DRMP3_S2(k) { int j; DRMP3_LOAD(k); for (j = 0; j < 4; j++) b[j] += vz[j]*w1 + vy[j]*w0, a[j] += vy[j]*w1 - vz[j]*w0; } + float a[4], b[4]; + + zlin[4*i] = xl[18*(31 - i)]; + zlin[4*i + 1] = xr[18*(31 - i)]; + zlin[4*i + 2] = xl[1 + 18*(31 - i)]; + zlin[4*i + 3] = xr[1 + 18*(31 - i)]; + zlin[4*(i + 16)] = xl[1 + 18*(1 + i)]; + zlin[4*(i + 16) + 1] = xr[1 + 18*(1 + i)]; + zlin[4*(i - 16) + 2] = xl[18*(1 + i)]; + zlin[4*(i - 16) + 3] = xr[18*(1 + i)]; + + DRMP3_S0(0) DRMP3_S2(1) DRMP3_S1(2) DRMP3_S2(3) DRMP3_S1(4) DRMP3_S2(5) DRMP3_S1(6) DRMP3_S2(7) + + dstr[(15 - i)*nch] = drmp3d_scale_pcm(a[1]); + dstr[(17 + i)*nch] = drmp3d_scale_pcm(b[1]); + dstl[(15 - i)*nch] = drmp3d_scale_pcm(a[0]); + dstl[(17 + i)*nch] = drmp3d_scale_pcm(b[0]); + dstr[(47 - i)*nch] = drmp3d_scale_pcm(a[3]); + dstr[(49 + i)*nch] = drmp3d_scale_pcm(b[3]); + dstl[(47 - i)*nch] = drmp3d_scale_pcm(a[2]); + dstl[(49 + i)*nch] = drmp3d_scale_pcm(b[2]); + } +#endif +} + +static void drmp3d_synth_granule(float *qmf_state, float *grbuf, int nbands, int nch, drmp3d_sample_t *pcm, float *lins) +{ + int i; + for (i = 0; i < nch; i++) + { + drmp3d_DCT_II(grbuf + 576*i, nbands); + } + + DRMP3_COPY_MEMORY(lins, qmf_state, sizeof(float)*15*64); + + for (i = 0; i < nbands; i += 2) + { + drmp3d_synth(grbuf + i, pcm + 32*nch*i, nch, lins + i*64); + } +#ifndef DR_MP3_NONSTANDARD_BUT_LOGICAL + if (nch == 1) + { + for (i = 0; i < 15*64; i += 2) + { + qmf_state[i] = lins[nbands*64 + i]; + } + } else +#endif + { + DRMP3_COPY_MEMORY(qmf_state, lins + nbands*64, sizeof(float)*15*64); + } +} + +static int drmp3d_match_frame(const drmp3_uint8 *hdr, int mp3_bytes, int frame_bytes) +{ + int i, nmatch; + for (i = 0, nmatch = 0; nmatch < DRMP3_MAX_FRAME_SYNC_MATCHES; nmatch++) + { + i += drmp3_hdr_frame_bytes(hdr + i, frame_bytes) + drmp3_hdr_padding(hdr + i); + if (i + DRMP3_HDR_SIZE > mp3_bytes) + return nmatch > 0; + if (!drmp3_hdr_compare(hdr, hdr + i)) + return 0; + } + return 1; +} + +static int drmp3d_find_frame(const drmp3_uint8 *mp3, int mp3_bytes, int *free_format_bytes, int *ptr_frame_bytes) +{ + int i, k; + for (i = 0; i < mp3_bytes - DRMP3_HDR_SIZE; i++, mp3++) + { + if (drmp3_hdr_valid(mp3)) + { + int frame_bytes = drmp3_hdr_frame_bytes(mp3, *free_format_bytes); + int frame_and_padding = frame_bytes + drmp3_hdr_padding(mp3); + + for (k = DRMP3_HDR_SIZE; !frame_bytes && k < DRMP3_MAX_FREE_FORMAT_FRAME_SIZE && i + 2*k < mp3_bytes - DRMP3_HDR_SIZE; k++) + { + if (drmp3_hdr_compare(mp3, mp3 + k)) + { + int fb = k - drmp3_hdr_padding(mp3); + int nextfb = fb + drmp3_hdr_padding(mp3 + k); + if (i + k + nextfb + DRMP3_HDR_SIZE > mp3_bytes || !drmp3_hdr_compare(mp3, mp3 + k + nextfb)) + continue; + frame_and_padding = k; + frame_bytes = fb; + *free_format_bytes = fb; + } + } + + if ((frame_bytes && i + frame_and_padding <= mp3_bytes && + drmp3d_match_frame(mp3, mp3_bytes - i, frame_bytes)) || + (!i && frame_and_padding == mp3_bytes)) + { + *ptr_frame_bytes = frame_and_padding; + return i; + } + *free_format_bytes = 0; + } + } + *ptr_frame_bytes = 0; + return mp3_bytes; +} + +DRMP3_API void drmp3dec_init(drmp3dec *dec) +{ + dec->header[0] = 0; +} + +DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info) +{ + int i = 0, igr, frame_size = 0, success = 1; + const drmp3_uint8 *hdr; + drmp3_bs bs_frame[1]; + drmp3dec_scratch scratch; + + if (mp3_bytes > 4 && dec->header[0] == 0xff && drmp3_hdr_compare(dec->header, mp3)) + { + frame_size = drmp3_hdr_frame_bytes(mp3, dec->free_format_bytes) + drmp3_hdr_padding(mp3); + if (frame_size != mp3_bytes && (frame_size + DRMP3_HDR_SIZE > mp3_bytes || !drmp3_hdr_compare(mp3, mp3 + frame_size))) + { + frame_size = 0; + } + } + if (!frame_size) + { + DRMP3_ZERO_MEMORY(dec, sizeof(drmp3dec)); + i = drmp3d_find_frame(mp3, mp3_bytes, &dec->free_format_bytes, &frame_size); + if (!frame_size || i + frame_size > mp3_bytes) + { + info->frame_bytes = i; + return 0; + } + } + + hdr = mp3 + i; + DRMP3_COPY_MEMORY(dec->header, hdr, DRMP3_HDR_SIZE); + info->frame_bytes = i + frame_size; + info->channels = DRMP3_HDR_IS_MONO(hdr) ? 1 : 2; + info->hz = drmp3_hdr_sample_rate_hz(hdr); + info->layer = 4 - DRMP3_HDR_GET_LAYER(hdr); + info->bitrate_kbps = drmp3_hdr_bitrate_kbps(hdr); + + drmp3_bs_init(bs_frame, hdr + DRMP3_HDR_SIZE, frame_size - DRMP3_HDR_SIZE); + if (DRMP3_HDR_IS_CRC(hdr)) + { + drmp3_bs_get_bits(bs_frame, 16); + } + + if (info->layer == 3) + { + int main_data_begin = drmp3_L3_read_side_info(bs_frame, scratch.gr_info, hdr); + if (main_data_begin < 0 || bs_frame->pos > bs_frame->limit) + { + drmp3dec_init(dec); + return 0; + } + success = drmp3_L3_restore_reservoir(dec, bs_frame, &scratch, main_data_begin); + if (success && pcm != NULL) + { + for (igr = 0; igr < (DRMP3_HDR_TEST_MPEG1(hdr) ? 2 : 1); igr++, pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*576*info->channels)) + { + DRMP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); + drmp3_L3_decode(dec, &scratch, scratch.gr_info + igr*info->channels, info->channels); + drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 18, info->channels, (drmp3d_sample_t*)pcm, scratch.syn[0]); + } + } + drmp3_L3_save_reservoir(dec, &scratch); + } else + { +#ifdef DR_MP3_ONLY_MP3 + return 0; +#else + drmp3_L12_scale_info sci[1]; + + if (pcm == NULL) { + return drmp3_hdr_frame_samples(hdr); + } + + drmp3_L12_read_scale_info(hdr, bs_frame, sci); + + DRMP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); + for (i = 0, igr = 0; igr < 3; igr++) + { + if (12 == (i += drmp3_L12_dequantize_granule(scratch.grbuf[0] + i, bs_frame, sci, info->layer | 1))) + { + i = 0; + drmp3_L12_apply_scf_384(sci, sci->scf + igr, scratch.grbuf[0]); + drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 12, info->channels, (drmp3d_sample_t*)pcm, scratch.syn[0]); + DRMP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); + pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*384*info->channels); + } + if (bs_frame->pos > bs_frame->limit) + { + drmp3dec_init(dec); + return 0; + } + } +#endif + } + + return success*drmp3_hdr_frame_samples(dec->header); +} + +DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num_samples) +{ + size_t i = 0; +#if DRMP3_HAVE_SIMD + size_t aligned_count = num_samples & ~7; + for(; i < aligned_count; i+=8) + { + drmp3_f4 scale = DRMP3_VSET(32768.0f); + drmp3_f4 a = DRMP3_VMUL(DRMP3_VLD(&in[i ]), scale); + drmp3_f4 b = DRMP3_VMUL(DRMP3_VLD(&in[i+4]), scale); +#if DRMP3_HAVE_SSE + drmp3_f4 s16max = DRMP3_VSET( 32767.0f); + drmp3_f4 s16min = DRMP3_VSET(-32768.0f); + __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, s16max), s16min)), + _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, s16max), s16min))); + out[i ] = (drmp3_int16)_mm_extract_epi16(pcm8, 0); + out[i+1] = (drmp3_int16)_mm_extract_epi16(pcm8, 1); + out[i+2] = (drmp3_int16)_mm_extract_epi16(pcm8, 2); + out[i+3] = (drmp3_int16)_mm_extract_epi16(pcm8, 3); + out[i+4] = (drmp3_int16)_mm_extract_epi16(pcm8, 4); + out[i+5] = (drmp3_int16)_mm_extract_epi16(pcm8, 5); + out[i+6] = (drmp3_int16)_mm_extract_epi16(pcm8, 6); + out[i+7] = (drmp3_int16)_mm_extract_epi16(pcm8, 7); +#else + int16x4_t pcma, pcmb; + a = DRMP3_VADD(a, DRMP3_VSET(0.5f)); + b = DRMP3_VADD(b, DRMP3_VSET(0.5f)); + pcma = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(a), vreinterpretq_s32_u32(vcltq_f32(a, DRMP3_VSET(0))))); + pcmb = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(b), vreinterpretq_s32_u32(vcltq_f32(b, DRMP3_VSET(0))))); + vst1_lane_s16(out+i , pcma, 0); + vst1_lane_s16(out+i+1, pcma, 1); + vst1_lane_s16(out+i+2, pcma, 2); + vst1_lane_s16(out+i+3, pcma, 3); + vst1_lane_s16(out+i+4, pcmb, 0); + vst1_lane_s16(out+i+5, pcmb, 1); + vst1_lane_s16(out+i+6, pcmb, 2); + vst1_lane_s16(out+i+7, pcmb, 3); +#endif + } +#endif + for(; i < num_samples; i++) + { + float sample = in[i] * 32768.0f; + if (sample >= 32766.5f) + out[i] = (drmp3_int16) 32767; + else if (sample <= -32767.5f) + out[i] = (drmp3_int16)-32768; + else + { + short s = (drmp3_int16)(sample + .5f); + s -= (s < 0); /* away from zero, to be compliant */ + out[i] = s; + } + } +} + + + +/************************************************************************************************************************************************************ + + Main Public API + + ************************************************************************************************************************************************************/ +/* SIZE_MAX */ +#if defined(SIZE_MAX) + #define DRMP3_SIZE_MAX SIZE_MAX +#else + #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) + #define DRMP3_SIZE_MAX ((drmp3_uint64)0xFFFFFFFFFFFFFFFF) + #else + #define DRMP3_SIZE_MAX 0xFFFFFFFF + #endif +#endif +/* End SIZE_MAX */ + +/* Options. */ +#ifndef DRMP3_SEEK_LEADING_MP3_FRAMES +#define DRMP3_SEEK_LEADING_MP3_FRAMES 2 +#endif + +#define DRMP3_MIN_DATA_CHUNK_SIZE 16384 + +/* The size in bytes of each chunk of data to read from the MP3 stream. minimp3 recommends at least 16K, but in an attempt to reduce data movement I'm making this slightly larger. */ +#ifndef DRMP3_DATA_CHUNK_SIZE +#define DRMP3_DATA_CHUNK_SIZE (DRMP3_MIN_DATA_CHUNK_SIZE*4) +#endif + + +#define DRMP3_COUNTOF(x) (sizeof(x) / sizeof(x[0])) +#define DRMP3_CLAMP(x, lo, hi) (DRMP3_MAX(lo, DRMP3_MIN(x, hi))) + +#ifndef DRMP3_PI_D +#define DRMP3_PI_D 3.14159265358979323846264 +#endif + +#define DRMP3_DEFAULT_RESAMPLER_LPF_ORDER 2 + +static DRMP3_INLINE float drmp3_mix_f32(float x, float y, float a) +{ + return x*(1-a) + y*a; +} +static DRMP3_INLINE float drmp3_mix_f32_fast(float x, float y, float a) +{ + float r0 = (y - x); + float r1 = r0*a; + return x + r1; + /*return x + (y - x)*a;*/ +} + + +/* +Greatest common factor using Euclid's algorithm iteratively. +*/ +static DRMP3_INLINE drmp3_uint32 drmp3_gcf_u32(drmp3_uint32 a, drmp3_uint32 b) +{ + for (;;) { + if (b == 0) { + break; + } else { + drmp3_uint32 t = a; + a = b; + b = t % a; + } + } + + return a; +} + + +static void* drmp3__malloc_default(size_t sz, void* pUserData) +{ + (void)pUserData; + return DRMP3_MALLOC(sz); +} + +static void* drmp3__realloc_default(void* p, size_t sz, void* pUserData) +{ + (void)pUserData; + return DRMP3_REALLOC(p, sz); +} + +static void drmp3__free_default(void* p, void* pUserData) +{ + (void)pUserData; + DRMP3_FREE(p); +} + + +static void* drmp3__malloc_from_callbacks(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } + + if (pAllocationCallbacks->onMalloc != NULL) { + return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); + } + + /* Try using realloc(). */ + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); + } + + return NULL; +} + +static void* drmp3__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } + + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); + } + + /* Try emulating realloc() in terms of malloc()/free(). */ + if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { + void* p2; + + p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); + if (p2 == NULL) { + return NULL; + } + + if (p != NULL) { + DRMP3_COPY_MEMORY(p2, p, szOld); + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } + + return p2; + } + + return NULL; +} + +static void drmp3__free_from_callbacks(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (p == NULL || pAllocationCallbacks == NULL) { + return; + } + + if (pAllocationCallbacks->onFree != NULL) { + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } +} + + +static drmp3_allocation_callbacks drmp3_copy_allocation_callbacks_or_defaults(const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + /* Copy. */ + return *pAllocationCallbacks; + } else { + /* Defaults. */ + drmp3_allocation_callbacks allocationCallbacks; + allocationCallbacks.pUserData = NULL; + allocationCallbacks.onMalloc = drmp3__malloc_default; + allocationCallbacks.onRealloc = drmp3__realloc_default; + allocationCallbacks.onFree = drmp3__free_default; + return allocationCallbacks; + } +} + + + +static size_t drmp3__on_read(drmp3* pMP3, void* pBufferOut, size_t bytesToRead) +{ + size_t bytesRead = pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead); + pMP3->streamCursor += bytesRead; + return bytesRead; +} + +static drmp3_bool32 drmp3__on_seek(drmp3* pMP3, int offset, drmp3_seek_origin origin) +{ + DRMP3_ASSERT(offset >= 0); + + if (!pMP3->onSeek(pMP3->pUserData, offset, origin)) { + return DRMP3_FALSE; + } + + if (origin == drmp3_seek_origin_start) { + pMP3->streamCursor = (drmp3_uint64)offset; + } else { + pMP3->streamCursor += offset; + } + + return DRMP3_TRUE; +} + +static drmp3_bool32 drmp3__on_seek_64(drmp3* pMP3, drmp3_uint64 offset, drmp3_seek_origin origin) +{ + if (offset <= 0x7FFFFFFF) { + return drmp3__on_seek(pMP3, (int)offset, origin); + } + + + /* Getting here "offset" is too large for a 32-bit integer. We just keep seeking forward until we hit the offset. */ + if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, drmp3_seek_origin_start)) { + return DRMP3_FALSE; + } + + offset -= 0x7FFFFFFF; + while (offset > 0) { + if (offset <= 0x7FFFFFFF) { + if (!drmp3__on_seek(pMP3, (int)offset, drmp3_seek_origin_current)) { + return DRMP3_FALSE; + } + offset = 0; + } else { + if (!drmp3__on_seek(pMP3, 0x7FFFFFFF, drmp3_seek_origin_current)) { + return DRMP3_FALSE; + } + offset -= 0x7FFFFFFF; + } + } + + return DRMP3_TRUE; +} + + +static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) +{ + drmp3_uint32 pcmFramesRead = 0; + + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->onRead != NULL); + + if (pMP3->atEnd) { + return 0; + } + + for (;;) { + drmp3dec_frame_info info; + + /* minimp3 recommends doing data submission in chunks of at least 16K. If we don't have at least 16K bytes available, get more. */ + if (pMP3->dataSize < DRMP3_MIN_DATA_CHUNK_SIZE) { + size_t bytesRead; + + /* First we need to move the data down. */ + if (pMP3->pData != NULL) { + DRMP3_MOVE_MEMORY(pMP3->pData, pMP3->pData + pMP3->dataConsumed, pMP3->dataSize); + } + + pMP3->dataConsumed = 0; + + if (pMP3->dataCapacity < DRMP3_DATA_CHUNK_SIZE) { + drmp3_uint8* pNewData; + size_t newDataCap; + + newDataCap = DRMP3_DATA_CHUNK_SIZE; + + pNewData = (drmp3_uint8*)drmp3__realloc_from_callbacks(pMP3->pData, newDataCap, pMP3->dataCapacity, &pMP3->allocationCallbacks); + if (pNewData == NULL) { + return 0; /* Out of memory. */ + } + + pMP3->pData = pNewData; + pMP3->dataCapacity = newDataCap; + } + + bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + if (bytesRead == 0) { + if (pMP3->dataSize == 0) { + pMP3->atEnd = DRMP3_TRUE; + return 0; /* No data. */ + } + } + + pMP3->dataSize += bytesRead; + } + + if (pMP3->dataSize > INT_MAX) { + pMP3->atEnd = DRMP3_TRUE; + return 0; /* File too big. */ + } + + DRMP3_ASSERT(pMP3->pData != NULL); + DRMP3_ASSERT(pMP3->dataCapacity > 0); + + /* Do a runtime check here to try silencing a false-positive from clang-analyzer. */ + if (pMP3->pData == NULL) { + return 0; + } + + pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData + pMP3->dataConsumed, (int)pMP3->dataSize, pPCMFrames, &info); /* <-- Safe size_t -> int conversion thanks to the check above. */ + + /* Consume the data. */ + if (info.frame_bytes > 0) { + pMP3->dataConsumed += (size_t)info.frame_bytes; + pMP3->dataSize -= (size_t)info.frame_bytes; + } + + /* pcmFramesRead will be equal to 0 if decoding failed. If it is zero and info.frame_bytes > 0 then we have successfully decoded the frame. */ + if (pcmFramesRead > 0) { + pcmFramesRead = drmp3_hdr_frame_samples(pMP3->decoder.header); + pMP3->pcmFramesConsumedInMP3Frame = 0; + pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; + pMP3->mp3FrameChannels = info.channels; + pMP3->mp3FrameSampleRate = info.hz; + break; + } else if (info.frame_bytes == 0) { + /* Need more data. minimp3 recommends doing data submission in 16K chunks. */ + size_t bytesRead; + + /* First we need to move the data down. */ + DRMP3_MOVE_MEMORY(pMP3->pData, pMP3->pData + pMP3->dataConsumed, pMP3->dataSize); + pMP3->dataConsumed = 0; + + if (pMP3->dataCapacity == pMP3->dataSize) { + /* No room. Expand. */ + drmp3_uint8* pNewData; + size_t newDataCap; + + newDataCap = pMP3->dataCapacity + DRMP3_DATA_CHUNK_SIZE; + + pNewData = (drmp3_uint8*)drmp3__realloc_from_callbacks(pMP3->pData, newDataCap, pMP3->dataCapacity, &pMP3->allocationCallbacks); + if (pNewData == NULL) { + return 0; /* Out of memory. */ + } + + pMP3->pData = pNewData; + pMP3->dataCapacity = newDataCap; + } + + /* Fill in a chunk. */ + bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + if (bytesRead == 0) { + pMP3->atEnd = DRMP3_TRUE; + return 0; /* Error reading more data. */ + } + + pMP3->dataSize += bytesRead; + } + }; + + return pcmFramesRead; +} + +static drmp3_uint32 drmp3_decode_next_frame_ex__memory(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) +{ + drmp3_uint32 pcmFramesRead = 0; + drmp3dec_frame_info info; + + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->memory.pData != NULL); + + if (pMP3->atEnd) { + return 0; + } + + for (;;) { + pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->memory.pData + pMP3->memory.currentReadPos, (int)(pMP3->memory.dataSize - pMP3->memory.currentReadPos), pPCMFrames, &info); + if (pcmFramesRead > 0) { + pcmFramesRead = drmp3_hdr_frame_samples(pMP3->decoder.header); + pMP3->pcmFramesConsumedInMP3Frame = 0; + pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; + pMP3->mp3FrameChannels = info.channels; + pMP3->mp3FrameSampleRate = info.hz; + break; + } else if (info.frame_bytes > 0) { + /* No frames were read, but it looks like we skipped past one. Read the next MP3 frame. */ + pMP3->memory.currentReadPos += (size_t)info.frame_bytes; + } else { + /* Nothing at all was read. Abort. */ + break; + } + } + + /* Consume the data. */ + pMP3->memory.currentReadPos += (size_t)info.frame_bytes; + + return pcmFramesRead; +} + +static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) +{ + if (pMP3->memory.pData != NULL && pMP3->memory.dataSize > 0) { + return drmp3_decode_next_frame_ex__memory(pMP3, pPCMFrames); + } else { + return drmp3_decode_next_frame_ex__callbacks(pMP3, pPCMFrames); + } +} + +static drmp3_uint32 drmp3_decode_next_frame(drmp3* pMP3) +{ + DRMP3_ASSERT(pMP3 != NULL); + return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames); +} + +#if 0 +static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3) +{ + drmp3_uint32 pcmFrameCount; + + DRMP3_ASSERT(pMP3 != NULL); + + pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, NULL); + if (pcmFrameCount == 0) { + return 0; + } + + /* We have essentially just skipped past the frame, so just set the remaining samples to 0. */ + pMP3->currentPCMFrame += pcmFrameCount; + pMP3->pcmFramesConsumedInMP3Frame = pcmFrameCount; + pMP3->pcmFramesRemainingInMP3Frame = 0; + + return pcmFrameCount; +} +#endif + +static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(onRead != NULL); + + /* This function assumes the output object has already been reset to 0. Do not do that here, otherwise things will break. */ + drmp3dec_init(&pMP3->decoder); + + pMP3->onRead = onRead; + pMP3->onSeek = onSeek; + pMP3->pUserData = pUserData; + pMP3->allocationCallbacks = drmp3_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); + + if (pMP3->allocationCallbacks.onFree == NULL || (pMP3->allocationCallbacks.onMalloc == NULL && pMP3->allocationCallbacks.onRealloc == NULL)) { + return DRMP3_FALSE; /* Invalid allocation callbacks. */ + } + + /* Decode the first frame to confirm that it is indeed a valid MP3 stream. */ + if (drmp3_decode_next_frame(pMP3) == 0) { + drmp3__free_from_callbacks(pMP3->pData, &pMP3->allocationCallbacks); /* The call above may have allocated memory. Need to make sure it's freed before aborting. */ + return DRMP3_FALSE; /* Not a valid MP3 stream. */ + } + + pMP3->channels = pMP3->mp3FrameChannels; + pMP3->sampleRate = pMP3->mp3FrameSampleRate; + + return DRMP3_TRUE; +} + +DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (pMP3 == NULL || onRead == NULL) { + return DRMP3_FALSE; + } + + DRMP3_ZERO_OBJECT(pMP3); + return drmp3_init_internal(pMP3, onRead, onSeek, pUserData, pAllocationCallbacks); +} + + +static size_t drmp3__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + drmp3* pMP3 = (drmp3*)pUserData; + size_t bytesRemaining; + + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->memory.dataSize >= pMP3->memory.currentReadPos); + + bytesRemaining = pMP3->memory.dataSize - pMP3->memory.currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + DRMP3_COPY_MEMORY(pBufferOut, pMP3->memory.pData + pMP3->memory.currentReadPos, bytesToRead); + pMP3->memory.currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +static drmp3_bool32 drmp3__on_seek_memory(void* pUserData, int byteOffset, drmp3_seek_origin origin) +{ + drmp3* pMP3 = (drmp3*)pUserData; + + DRMP3_ASSERT(pMP3 != NULL); + + if (origin == drmp3_seek_origin_current) { + if (byteOffset > 0) { + if (pMP3->memory.currentReadPos + byteOffset > pMP3->memory.dataSize) { + byteOffset = (int)(pMP3->memory.dataSize - pMP3->memory.currentReadPos); /* Trying to seek too far forward. */ + } + } else { + if (pMP3->memory.currentReadPos < (size_t)-byteOffset) { + byteOffset = -(int)pMP3->memory.currentReadPos; /* Trying to seek too far backwards. */ + } + } + + /* This will never underflow thanks to the clamps above. */ + pMP3->memory.currentReadPos += byteOffset; + } else { + if ((drmp3_uint32)byteOffset <= pMP3->memory.dataSize) { + pMP3->memory.currentReadPos = byteOffset; + } else { + pMP3->memory.currentReadPos = pMP3->memory.dataSize; /* Trying to seek too far forward. */ + } + } + + return DRMP3_TRUE; +} + +DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (pMP3 == NULL) { + return DRMP3_FALSE; + } + + DRMP3_ZERO_OBJECT(pMP3); + + if (pData == NULL || dataSize == 0) { + return DRMP3_FALSE; + } + + pMP3->memory.pData = (const drmp3_uint8*)pData; + pMP3->memory.dataSize = dataSize; + pMP3->memory.currentReadPos = 0; + + return drmp3_init_internal(pMP3, drmp3__on_read_memory, drmp3__on_seek_memory, pMP3, pAllocationCallbacks); +} + + +#ifndef DR_MP3_NO_STDIO +#include +#include /* For wcslen(), wcsrtombs() */ + +/* Errno */ +/* drmp3_result_from_errno() is only used inside DR_MP3_NO_STDIO for now. Move this out if it's ever used elsewhere. */ +#include +static drmp3_result drmp3_result_from_errno(int e) +{ + switch (e) + { + case 0: return DRMP3_SUCCESS; + #ifdef EPERM + case EPERM: return DRMP3_INVALID_OPERATION; + #endif + #ifdef ENOENT + case ENOENT: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef ESRCH + case ESRCH: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef EINTR + case EINTR: return DRMP3_INTERRUPT; + #endif + #ifdef EIO + case EIO: return DRMP3_IO_ERROR; + #endif + #ifdef ENXIO + case ENXIO: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef E2BIG + case E2BIG: return DRMP3_INVALID_ARGS; + #endif + #ifdef ENOEXEC + case ENOEXEC: return DRMP3_INVALID_FILE; + #endif + #ifdef EBADF + case EBADF: return DRMP3_INVALID_FILE; + #endif + #ifdef ECHILD + case ECHILD: return DRMP3_ERROR; + #endif + #ifdef EAGAIN + case EAGAIN: return DRMP3_UNAVAILABLE; + #endif + #ifdef ENOMEM + case ENOMEM: return DRMP3_OUT_OF_MEMORY; + #endif + #ifdef EACCES + case EACCES: return DRMP3_ACCESS_DENIED; + #endif + #ifdef EFAULT + case EFAULT: return DRMP3_BAD_ADDRESS; + #endif + #ifdef ENOTBLK + case ENOTBLK: return DRMP3_ERROR; + #endif + #ifdef EBUSY + case EBUSY: return DRMP3_BUSY; + #endif + #ifdef EEXIST + case EEXIST: return DRMP3_ALREADY_EXISTS; + #endif + #ifdef EXDEV + case EXDEV: return DRMP3_ERROR; + #endif + #ifdef ENODEV + case ENODEV: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef ENOTDIR + case ENOTDIR: return DRMP3_NOT_DIRECTORY; + #endif + #ifdef EISDIR + case EISDIR: return DRMP3_IS_DIRECTORY; + #endif + #ifdef EINVAL + case EINVAL: return DRMP3_INVALID_ARGS; + #endif + #ifdef ENFILE + case ENFILE: return DRMP3_TOO_MANY_OPEN_FILES; + #endif + #ifdef EMFILE + case EMFILE: return DRMP3_TOO_MANY_OPEN_FILES; + #endif + #ifdef ENOTTY + case ENOTTY: return DRMP3_INVALID_OPERATION; + #endif + #ifdef ETXTBSY + case ETXTBSY: return DRMP3_BUSY; + #endif + #ifdef EFBIG + case EFBIG: return DRMP3_TOO_BIG; + #endif + #ifdef ENOSPC + case ENOSPC: return DRMP3_NO_SPACE; + #endif + #ifdef ESPIPE + case ESPIPE: return DRMP3_BAD_SEEK; + #endif + #ifdef EROFS + case EROFS: return DRMP3_ACCESS_DENIED; + #endif + #ifdef EMLINK + case EMLINK: return DRMP3_TOO_MANY_LINKS; + #endif + #ifdef EPIPE + case EPIPE: return DRMP3_BAD_PIPE; + #endif + #ifdef EDOM + case EDOM: return DRMP3_OUT_OF_RANGE; + #endif + #ifdef ERANGE + case ERANGE: return DRMP3_OUT_OF_RANGE; + #endif + #ifdef EDEADLK + case EDEADLK: return DRMP3_DEADLOCK; + #endif + #ifdef ENAMETOOLONG + case ENAMETOOLONG: return DRMP3_PATH_TOO_LONG; + #endif + #ifdef ENOLCK + case ENOLCK: return DRMP3_ERROR; + #endif + #ifdef ENOSYS + case ENOSYS: return DRMP3_NOT_IMPLEMENTED; + #endif + #ifdef ENOTEMPTY + case ENOTEMPTY: return DRMP3_DIRECTORY_NOT_EMPTY; + #endif + #ifdef ELOOP + case ELOOP: return DRMP3_TOO_MANY_LINKS; + #endif + #ifdef ENOMSG + case ENOMSG: return DRMP3_NO_MESSAGE; + #endif + #ifdef EIDRM + case EIDRM: return DRMP3_ERROR; + #endif + #ifdef ECHRNG + case ECHRNG: return DRMP3_ERROR; + #endif + #ifdef EL2NSYNC + case EL2NSYNC: return DRMP3_ERROR; + #endif + #ifdef EL3HLT + case EL3HLT: return DRMP3_ERROR; + #endif + #ifdef EL3RST + case EL3RST: return DRMP3_ERROR; + #endif + #ifdef ELNRNG + case ELNRNG: return DRMP3_OUT_OF_RANGE; + #endif + #ifdef EUNATCH + case EUNATCH: return DRMP3_ERROR; + #endif + #ifdef ENOCSI + case ENOCSI: return DRMP3_ERROR; + #endif + #ifdef EL2HLT + case EL2HLT: return DRMP3_ERROR; + #endif + #ifdef EBADE + case EBADE: return DRMP3_ERROR; + #endif + #ifdef EBADR + case EBADR: return DRMP3_ERROR; + #endif + #ifdef EXFULL + case EXFULL: return DRMP3_ERROR; + #endif + #ifdef ENOANO + case ENOANO: return DRMP3_ERROR; + #endif + #ifdef EBADRQC + case EBADRQC: return DRMP3_ERROR; + #endif + #ifdef EBADSLT + case EBADSLT: return DRMP3_ERROR; + #endif + #ifdef EBFONT + case EBFONT: return DRMP3_INVALID_FILE; + #endif + #ifdef ENOSTR + case ENOSTR: return DRMP3_ERROR; + #endif + #ifdef ENODATA + case ENODATA: return DRMP3_NO_DATA_AVAILABLE; + #endif + #ifdef ETIME + case ETIME: return DRMP3_TIMEOUT; + #endif + #ifdef ENOSR + case ENOSR: return DRMP3_NO_DATA_AVAILABLE; + #endif + #ifdef ENONET + case ENONET: return DRMP3_NO_NETWORK; + #endif + #ifdef ENOPKG + case ENOPKG: return DRMP3_ERROR; + #endif + #ifdef EREMOTE + case EREMOTE: return DRMP3_ERROR; + #endif + #ifdef ENOLINK + case ENOLINK: return DRMP3_ERROR; + #endif + #ifdef EADV + case EADV: return DRMP3_ERROR; + #endif + #ifdef ESRMNT + case ESRMNT: return DRMP3_ERROR; + #endif + #ifdef ECOMM + case ECOMM: return DRMP3_ERROR; + #endif + #ifdef EPROTO + case EPROTO: return DRMP3_ERROR; + #endif + #ifdef EMULTIHOP + case EMULTIHOP: return DRMP3_ERROR; + #endif + #ifdef EDOTDOT + case EDOTDOT: return DRMP3_ERROR; + #endif + #ifdef EBADMSG + case EBADMSG: return DRMP3_BAD_MESSAGE; + #endif + #ifdef EOVERFLOW + case EOVERFLOW: return DRMP3_TOO_BIG; + #endif + #ifdef ENOTUNIQ + case ENOTUNIQ: return DRMP3_NOT_UNIQUE; + #endif + #ifdef EBADFD + case EBADFD: return DRMP3_ERROR; + #endif + #ifdef EREMCHG + case EREMCHG: return DRMP3_ERROR; + #endif + #ifdef ELIBACC + case ELIBACC: return DRMP3_ACCESS_DENIED; + #endif + #ifdef ELIBBAD + case ELIBBAD: return DRMP3_INVALID_FILE; + #endif + #ifdef ELIBSCN + case ELIBSCN: return DRMP3_INVALID_FILE; + #endif + #ifdef ELIBMAX + case ELIBMAX: return DRMP3_ERROR; + #endif + #ifdef ELIBEXEC + case ELIBEXEC: return DRMP3_ERROR; + #endif + #ifdef EILSEQ + case EILSEQ: return DRMP3_INVALID_DATA; + #endif + #ifdef ERESTART + case ERESTART: return DRMP3_ERROR; + #endif + #ifdef ESTRPIPE + case ESTRPIPE: return DRMP3_ERROR; + #endif + #ifdef EUSERS + case EUSERS: return DRMP3_ERROR; + #endif + #ifdef ENOTSOCK + case ENOTSOCK: return DRMP3_NOT_SOCKET; + #endif + #ifdef EDESTADDRREQ + case EDESTADDRREQ: return DRMP3_NO_ADDRESS; + #endif + #ifdef EMSGSIZE + case EMSGSIZE: return DRMP3_TOO_BIG; + #endif + #ifdef EPROTOTYPE + case EPROTOTYPE: return DRMP3_BAD_PROTOCOL; + #endif + #ifdef ENOPROTOOPT + case ENOPROTOOPT: return DRMP3_PROTOCOL_UNAVAILABLE; + #endif + #ifdef EPROTONOSUPPORT + case EPROTONOSUPPORT: return DRMP3_PROTOCOL_NOT_SUPPORTED; + #endif + #ifdef ESOCKTNOSUPPORT + case ESOCKTNOSUPPORT: return DRMP3_SOCKET_NOT_SUPPORTED; + #endif + #ifdef EOPNOTSUPP + case EOPNOTSUPP: return DRMP3_INVALID_OPERATION; + #endif + #ifdef EPFNOSUPPORT + case EPFNOSUPPORT: return DRMP3_PROTOCOL_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EAFNOSUPPORT + case EAFNOSUPPORT: return DRMP3_ADDRESS_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EADDRINUSE + case EADDRINUSE: return DRMP3_ALREADY_IN_USE; + #endif + #ifdef EADDRNOTAVAIL + case EADDRNOTAVAIL: return DRMP3_ERROR; + #endif + #ifdef ENETDOWN + case ENETDOWN: return DRMP3_NO_NETWORK; + #endif + #ifdef ENETUNREACH + case ENETUNREACH: return DRMP3_NO_NETWORK; + #endif + #ifdef ENETRESET + case ENETRESET: return DRMP3_NO_NETWORK; + #endif + #ifdef ECONNABORTED + case ECONNABORTED: return DRMP3_NO_NETWORK; + #endif + #ifdef ECONNRESET + case ECONNRESET: return DRMP3_CONNECTION_RESET; + #endif + #ifdef ENOBUFS + case ENOBUFS: return DRMP3_NO_SPACE; + #endif + #ifdef EISCONN + case EISCONN: return DRMP3_ALREADY_CONNECTED; + #endif + #ifdef ENOTCONN + case ENOTCONN: return DRMP3_NOT_CONNECTED; + #endif + #ifdef ESHUTDOWN + case ESHUTDOWN: return DRMP3_ERROR; + #endif + #ifdef ETOOMANYREFS + case ETOOMANYREFS: return DRMP3_ERROR; + #endif + #ifdef ETIMEDOUT + case ETIMEDOUT: return DRMP3_TIMEOUT; + #endif + #ifdef ECONNREFUSED + case ECONNREFUSED: return DRMP3_CONNECTION_REFUSED; + #endif + #ifdef EHOSTDOWN + case EHOSTDOWN: return DRMP3_NO_HOST; + #endif + #ifdef EHOSTUNREACH + case EHOSTUNREACH: return DRMP3_NO_HOST; + #endif + #ifdef EALREADY + case EALREADY: return DRMP3_IN_PROGRESS; + #endif + #ifdef EINPROGRESS + case EINPROGRESS: return DRMP3_IN_PROGRESS; + #endif + #ifdef ESTALE + case ESTALE: return DRMP3_INVALID_FILE; + #endif + #ifdef EUCLEAN + case EUCLEAN: return DRMP3_ERROR; + #endif + #ifdef ENOTNAM + case ENOTNAM: return DRMP3_ERROR; + #endif + #ifdef ENAVAIL + case ENAVAIL: return DRMP3_ERROR; + #endif + #ifdef EISNAM + case EISNAM: return DRMP3_ERROR; + #endif + #ifdef EREMOTEIO + case EREMOTEIO: return DRMP3_IO_ERROR; + #endif + #ifdef EDQUOT + case EDQUOT: return DRMP3_NO_SPACE; + #endif + #ifdef ENOMEDIUM + case ENOMEDIUM: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef EMEDIUMTYPE + case EMEDIUMTYPE: return DRMP3_ERROR; + #endif + #ifdef ECANCELED + case ECANCELED: return DRMP3_CANCELLED; + #endif + #ifdef ENOKEY + case ENOKEY: return DRMP3_ERROR; + #endif + #ifdef EKEYEXPIRED + case EKEYEXPIRED: return DRMP3_ERROR; + #endif + #ifdef EKEYREVOKED + case EKEYREVOKED: return DRMP3_ERROR; + #endif + #ifdef EKEYREJECTED + case EKEYREJECTED: return DRMP3_ERROR; + #endif + #ifdef EOWNERDEAD + case EOWNERDEAD: return DRMP3_ERROR; + #endif + #ifdef ENOTRECOVERABLE + case ENOTRECOVERABLE: return DRMP3_ERROR; + #endif + #ifdef ERFKILL + case ERFKILL: return DRMP3_ERROR; + #endif + #ifdef EHWPOISON + case EHWPOISON: return DRMP3_ERROR; + #endif + default: return DRMP3_ERROR; + } +} +/* End Errno */ + +/* fopen */ +static drmp3_result drmp3_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err; +#endif + + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRMP3_INVALID_ARGS; + } + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + err = fopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drmp3_result_from_errno(err); + } +#else +#if defined(_WIN32) || defined(__APPLE__) + *ppFile = fopen(pFilePath, pOpenMode); +#else + #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) + *ppFile = fopen64(pFilePath, pOpenMode); + #else + *ppFile = fopen(pFilePath, pOpenMode); + #endif +#endif + if (*ppFile == NULL) { + drmp3_result result = drmp3_result_from_errno(errno); + if (result == DRMP3_SUCCESS) { + result = DRMP3_ERROR; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ + } + + return result; + } +#endif + + return DRMP3_SUCCESS; +} + +/* +_wfopen() isn't always available in all compilation environments. + + * Windows only. + * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). + * MinGW-64 (both 32- and 64-bit) seems to support it. + * MinGW wraps it in !defined(__STRICT_ANSI__). + * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). + +This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() +fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. +*/ +#if defined(_WIN32) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) + #define DRMP3_HAS_WFOPEN + #endif +#endif + +static drmp3_result drmp3_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRMP3_INVALID_ARGS; + } + +#if defined(DRMP3_HAS_WFOPEN) + { + /* Use _wfopen() on Windows. */ + #if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drmp3_result_from_errno(err); + } + #else + *ppFile = _wfopen(pFilePath, pOpenMode); + if (*ppFile == NULL) { + return drmp3_result_from_errno(errno); + } + #endif + (void)pAllocationCallbacks; + } +#else + /* + Use fopen() on anything other than Windows. Requires a conversion. This is annoying because + fopen() is locale specific. The only real way I can think of to do this is with wcsrtombs(). Note + that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for + maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler + error I'll look into improving compatibility. + */ + + /* + Some compilers don't support wchar_t or wcsrtombs() which we're using below. In this case we just + need to abort with an error. If you encounter a compiler lacking such support, add it to this list + and submit a bug report and it'll be added to the library upstream. + */ + #if defined(__DJGPP__) + { + /* Nothing to do here. This will fall through to the error check below. */ + } + #else + { + mbstate_t mbs; + size_t lenMB; + const wchar_t* pFilePathTemp = pFilePath; + char* pFilePathMB = NULL; + char pOpenModeMB[32] = {0}; + + /* Get the length first. */ + DRMP3_ZERO_OBJECT(&mbs); + lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); + if (lenMB == (size_t)-1) { + return drmp3_result_from_errno(errno); + } + + pFilePathMB = (char*)drmp3__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks); + if (pFilePathMB == NULL) { + return DRMP3_OUT_OF_MEMORY; + } + + pFilePathTemp = pFilePath; + DRMP3_ZERO_OBJECT(&mbs); + wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); + + /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ + { + size_t i = 0; + for (;;) { + if (pOpenMode[i] == 0) { + pOpenModeMB[i] = '\0'; + break; + } + + pOpenModeMB[i] = (char)pOpenMode[i]; + i += 1; + } + } + + *ppFile = fopen(pFilePathMB, pOpenModeMB); + + drmp3__free_from_callbacks(pFilePathMB, pAllocationCallbacks); + } + #endif + + if (*ppFile == NULL) { + return DRMP3_ERROR; + } +#endif + + return DRMP3_SUCCESS; +} +/* End fopen */ + + +static size_t drmp3__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData); +} + +static drmp3_bool32 drmp3__on_seek_stdio(void* pUserData, int offset, drmp3_seek_origin origin) +{ + return fseek((FILE*)pUserData, offset, (origin == drmp3_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; +} + +DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + drmp3_bool32 result; + FILE* pFile; + + if (drmp3_fopen(&pFile, pFilePath, "rb") != DRMP3_SUCCESS) { + return DRMP3_FALSE; + } + + result = drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (result != DRMP3_TRUE) { + fclose(pFile); + return result; + } + + return DRMP3_TRUE; +} + +DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + drmp3_bool32 result; + FILE* pFile; + + if (drmp3_wfopen(&pFile, pFilePath, L"rb", pAllocationCallbacks) != DRMP3_SUCCESS) { + return DRMP3_FALSE; + } + + result = drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (result != DRMP3_TRUE) { + fclose(pFile); + return result; + } + + return DRMP3_TRUE; +} +#endif + +DRMP3_API void drmp3_uninit(drmp3* pMP3) +{ + if (pMP3 == NULL) { + return; + } + +#ifndef DR_MP3_NO_STDIO + if (pMP3->onRead == drmp3__on_read_stdio) { + FILE* pFile = (FILE*)pMP3->pUserData; + if (pFile != NULL) { + fclose(pFile); + pMP3->pUserData = NULL; /* Make sure the file handle is cleared to NULL to we don't attempt to close it a second time. */ + } + } +#endif + + drmp3__free_from_callbacks(pMP3->pData, &pMP3->allocationCallbacks); +} + +#if defined(DR_MP3_FLOAT_OUTPUT) +static void drmp3_f32_to_s16(drmp3_int16* dst, const float* src, drmp3_uint64 sampleCount) +{ + drmp3_uint64 i; + drmp3_uint64 i4; + drmp3_uint64 sampleCount4; + + /* Unrolled. */ + i = 0; + sampleCount4 = sampleCount >> 2; + for (i4 = 0; i4 < sampleCount4; i4 += 1) { + float x0 = src[i+0]; + float x1 = src[i+1]; + float x2 = src[i+2]; + float x3 = src[i+3]; + + x0 = ((x0 < -1) ? -1 : ((x0 > 1) ? 1 : x0)); + x1 = ((x1 < -1) ? -1 : ((x1 > 1) ? 1 : x1)); + x2 = ((x2 < -1) ? -1 : ((x2 > 1) ? 1 : x2)); + x3 = ((x3 < -1) ? -1 : ((x3 > 1) ? 1 : x3)); + + x0 = x0 * 32767.0f; + x1 = x1 * 32767.0f; + x2 = x2 * 32767.0f; + x3 = x3 * 32767.0f; + + dst[i+0] = (drmp3_int16)x0; + dst[i+1] = (drmp3_int16)x1; + dst[i+2] = (drmp3_int16)x2; + dst[i+3] = (drmp3_int16)x3; + + i += 4; + } + + /* Leftover. */ + for (; i < sampleCount; i += 1) { + float x = src[i]; + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); /* clip */ + x = x * 32767.0f; /* -1..1 to -32767..32767 */ + + dst[i] = (drmp3_int16)x; + } +} +#endif + +#if !defined(DR_MP3_FLOAT_OUTPUT) +static void drmp3_s16_to_f32(float* dst, const drmp3_int16* src, drmp3_uint64 sampleCount) +{ + drmp3_uint64 i; + for (i = 0; i < sampleCount; i += 1) { + float x = (float)src[i]; + x = x * 0.000030517578125f; /* -32768..32767 to -1..0.999969482421875 */ + dst[i] = x; + } +} +#endif + + +static drmp3_uint64 drmp3_read_pcm_frames_raw(drmp3* pMP3, drmp3_uint64 framesToRead, void* pBufferOut) +{ + drmp3_uint64 totalFramesRead = 0; + + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->onRead != NULL); + + while (framesToRead > 0) { + drmp3_uint32 framesToConsume = (drmp3_uint32)DRMP3_MIN(pMP3->pcmFramesRemainingInMP3Frame, framesToRead); + if (pBufferOut != NULL) { + #if defined(DR_MP3_FLOAT_OUTPUT) + /* f32 */ + float* pFramesOutF32 = (float*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(float) * totalFramesRead * pMP3->channels); + float* pFramesInF32 = (float*)DRMP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(float) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); + DRMP3_COPY_MEMORY(pFramesOutF32, pFramesInF32, sizeof(float) * framesToConsume * pMP3->channels); + #else + /* s16 */ + drmp3_int16* pFramesOutS16 = (drmp3_int16*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(drmp3_int16) * totalFramesRead * pMP3->channels); + drmp3_int16* pFramesInS16 = (drmp3_int16*)DRMP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(drmp3_int16) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); + DRMP3_COPY_MEMORY(pFramesOutS16, pFramesInS16, sizeof(drmp3_int16) * framesToConsume * pMP3->channels); + #endif + } + + pMP3->currentPCMFrame += framesToConsume; + pMP3->pcmFramesConsumedInMP3Frame += framesToConsume; + pMP3->pcmFramesRemainingInMP3Frame -= framesToConsume; + totalFramesRead += framesToConsume; + framesToRead -= framesToConsume; + + if (framesToRead == 0) { + break; + } + + DRMP3_ASSERT(pMP3->pcmFramesRemainingInMP3Frame == 0); + + /* + At this point we have exhausted our in-memory buffer so we need to re-fill. Note that the sample rate may have changed + at this point which means we'll also need to update our sample rate conversion pipeline. + */ + if (drmp3_decode_next_frame(pMP3) == 0) { + break; + } + } + + return totalFramesRead; +} + + +DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut) +{ + if (pMP3 == NULL || pMP3->onRead == NULL) { + return 0; + } + +#if defined(DR_MP3_FLOAT_OUTPUT) + /* Fast path. No conversion required. */ + return drmp3_read_pcm_frames_raw(pMP3, framesToRead, pBufferOut); +#else + /* Slow path. Convert from s16 to f32. */ + { + drmp3_int16 pTempS16[8192]; + drmp3_uint64 totalPCMFramesRead = 0; + + while (totalPCMFramesRead < framesToRead) { + drmp3_uint64 framesJustRead; + drmp3_uint64 framesRemaining = framesToRead - totalPCMFramesRead; + drmp3_uint64 framesToReadNow = DRMP3_COUNTOF(pTempS16) / pMP3->channels; + if (framesToReadNow > framesRemaining) { + framesToReadNow = framesRemaining; + } + + framesJustRead = drmp3_read_pcm_frames_raw(pMP3, framesToReadNow, pTempS16); + if (framesJustRead == 0) { + break; + } + + drmp3_s16_to_f32((float*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(float) * totalPCMFramesRead * pMP3->channels), pTempS16, framesJustRead * pMP3->channels); + totalPCMFramesRead += framesJustRead; + } + + return totalPCMFramesRead; + } +#endif +} + +DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_s16(drmp3* pMP3, drmp3_uint64 framesToRead, drmp3_int16* pBufferOut) +{ + if (pMP3 == NULL || pMP3->onRead == NULL) { + return 0; + } + +#if !defined(DR_MP3_FLOAT_OUTPUT) + /* Fast path. No conversion required. */ + return drmp3_read_pcm_frames_raw(pMP3, framesToRead, pBufferOut); +#else + /* Slow path. Convert from f32 to s16. */ + { + float pTempF32[4096]; + drmp3_uint64 totalPCMFramesRead = 0; + + while (totalPCMFramesRead < framesToRead) { + drmp3_uint64 framesJustRead; + drmp3_uint64 framesRemaining = framesToRead - totalPCMFramesRead; + drmp3_uint64 framesToReadNow = DRMP3_COUNTOF(pTempF32) / pMP3->channels; + if (framesToReadNow > framesRemaining) { + framesToReadNow = framesRemaining; + } + + framesJustRead = drmp3_read_pcm_frames_raw(pMP3, framesToReadNow, pTempF32); + if (framesJustRead == 0) { + break; + } + + drmp3_f32_to_s16((drmp3_int16*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(drmp3_int16) * totalPCMFramesRead * pMP3->channels), pTempF32, framesJustRead * pMP3->channels); + totalPCMFramesRead += framesJustRead; + } + + return totalPCMFramesRead; + } +#endif +} + +static void drmp3_reset(drmp3* pMP3) +{ + DRMP3_ASSERT(pMP3 != NULL); + + pMP3->pcmFramesConsumedInMP3Frame = 0; + pMP3->pcmFramesRemainingInMP3Frame = 0; + pMP3->currentPCMFrame = 0; + pMP3->dataSize = 0; + pMP3->atEnd = DRMP3_FALSE; + drmp3dec_init(&pMP3->decoder); +} + +static drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3) +{ + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->onSeek != NULL); + + /* Seek to the start of the stream to begin with. */ + if (!drmp3__on_seek(pMP3, 0, drmp3_seek_origin_start)) { + return DRMP3_FALSE; + } + + /* Clear any cached data. */ + drmp3_reset(pMP3); + return DRMP3_TRUE; +} + + +static drmp3_bool32 drmp3_seek_forward_by_pcm_frames__brute_force(drmp3* pMP3, drmp3_uint64 frameOffset) +{ + drmp3_uint64 framesRead; + + /* + Just using a dumb read-and-discard for now. What would be nice is to parse only the header of the MP3 frame, and then skip over leading + frames without spending the time doing a full decode. I cannot see an easy way to do this in minimp3, however, so it may involve some + kind of manual processing. + */ +#if defined(DR_MP3_FLOAT_OUTPUT) + framesRead = drmp3_read_pcm_frames_f32(pMP3, frameOffset, NULL); +#else + framesRead = drmp3_read_pcm_frames_s16(pMP3, frameOffset, NULL); +#endif + if (framesRead != frameOffset) { + return DRMP3_FALSE; + } + + return DRMP3_TRUE; +} + +static drmp3_bool32 drmp3_seek_to_pcm_frame__brute_force(drmp3* pMP3, drmp3_uint64 frameIndex) +{ + DRMP3_ASSERT(pMP3 != NULL); + + if (frameIndex == pMP3->currentPCMFrame) { + return DRMP3_TRUE; + } + + /* + If we're moving foward we just read from where we're at. Otherwise we need to move back to the start of + the stream and read from the beginning. + */ + if (frameIndex < pMP3->currentPCMFrame) { + /* Moving backward. Move to the start of the stream and then move forward. */ + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return DRMP3_FALSE; + } + } + + DRMP3_ASSERT(frameIndex >= pMP3->currentPCMFrame); + return drmp3_seek_forward_by_pcm_frames__brute_force(pMP3, (frameIndex - pMP3->currentPCMFrame)); +} + +static drmp3_bool32 drmp3_find_closest_seek_point(drmp3* pMP3, drmp3_uint64 frameIndex, drmp3_uint32* pSeekPointIndex) +{ + drmp3_uint32 iSeekPoint; + + DRMP3_ASSERT(pSeekPointIndex != NULL); + + *pSeekPointIndex = 0; + + if (frameIndex < pMP3->pSeekPoints[0].pcmFrameIndex) { + return DRMP3_FALSE; + } + + /* Linear search for simplicity to begin with while I'm getting this thing working. Once it's all working change this to a binary search. */ + for (iSeekPoint = 0; iSeekPoint < pMP3->seekPointCount; ++iSeekPoint) { + if (pMP3->pSeekPoints[iSeekPoint].pcmFrameIndex > frameIndex) { + break; /* Found it. */ + } + + *pSeekPointIndex = iSeekPoint; + } + + return DRMP3_TRUE; +} + +static drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frameIndex) +{ + drmp3_seek_point seekPoint; + drmp3_uint32 priorSeekPointIndex; + drmp3_uint16 iMP3Frame; + drmp3_uint64 leftoverFrames; + + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->pSeekPoints != NULL); + DRMP3_ASSERT(pMP3->seekPointCount > 0); + + /* If there is no prior seekpoint it means the target PCM frame comes before the first seek point. Just assume a seekpoint at the start of the file in this case. */ + if (drmp3_find_closest_seek_point(pMP3, frameIndex, &priorSeekPointIndex)) { + seekPoint = pMP3->pSeekPoints[priorSeekPointIndex]; + } else { + seekPoint.seekPosInBytes = 0; + seekPoint.pcmFrameIndex = 0; + seekPoint.mp3FramesToDiscard = 0; + seekPoint.pcmFramesToDiscard = 0; + } + + /* First thing to do is seek to the first byte of the relevant MP3 frame. */ + if (!drmp3__on_seek_64(pMP3, seekPoint.seekPosInBytes, drmp3_seek_origin_start)) { + return DRMP3_FALSE; /* Failed to seek. */ + } + + /* Clear any cached data. */ + drmp3_reset(pMP3); + + /* Whole MP3 frames need to be discarded first. */ + for (iMP3Frame = 0; iMP3Frame < seekPoint.mp3FramesToDiscard; ++iMP3Frame) { + drmp3_uint32 pcmFramesRead; + drmp3d_sample_t* pPCMFrames; + + /* Pass in non-null for the last frame because we want to ensure the sample rate converter is preloaded correctly. */ + pPCMFrames = NULL; + if (iMP3Frame == seekPoint.mp3FramesToDiscard-1) { + pPCMFrames = (drmp3d_sample_t*)pMP3->pcmFrames; + } + + /* We first need to decode the next frame. */ + pcmFramesRead = drmp3_decode_next_frame_ex(pMP3, pPCMFrames); + if (pcmFramesRead == 0) { + return DRMP3_FALSE; + } + } + + /* We seeked to an MP3 frame in the raw stream so we need to make sure the current PCM frame is set correctly. */ + pMP3->currentPCMFrame = seekPoint.pcmFrameIndex - seekPoint.pcmFramesToDiscard; + + /* + Now at this point we can follow the same process as the brute force technique where we just skip over unnecessary MP3 frames and then + read-and-discard at least 2 whole MP3 frames. + */ + leftoverFrames = frameIndex - pMP3->currentPCMFrame; + return drmp3_seek_forward_by_pcm_frames__brute_force(pMP3, leftoverFrames); +} + +DRMP3_API drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex) +{ + if (pMP3 == NULL || pMP3->onSeek == NULL) { + return DRMP3_FALSE; + } + + if (frameIndex == 0) { + return drmp3_seek_to_start_of_stream(pMP3); + } + + /* Use the seek table if we have one. */ + if (pMP3->pSeekPoints != NULL && pMP3->seekPointCount > 0) { + return drmp3_seek_to_pcm_frame__seek_table(pMP3, frameIndex); + } else { + return drmp3_seek_to_pcm_frame__brute_force(pMP3, frameIndex); + } +} + +DRMP3_API drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3FrameCount, drmp3_uint64* pPCMFrameCount) +{ + drmp3_uint64 currentPCMFrame; + drmp3_uint64 totalPCMFrameCount; + drmp3_uint64 totalMP3FrameCount; + + if (pMP3 == NULL) { + return DRMP3_FALSE; + } + + /* + The way this works is we move back to the start of the stream, iterate over each MP3 frame and calculate the frame count based + on our output sample rate, the seek back to the PCM frame we were sitting on before calling this function. + */ + + /* The stream must support seeking for this to work. */ + if (pMP3->onSeek == NULL) { + return DRMP3_FALSE; + } + + /* We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later. */ + currentPCMFrame = pMP3->currentPCMFrame; + + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return DRMP3_FALSE; + } + + totalPCMFrameCount = 0; + totalMP3FrameCount = 0; + + for (;;) { + drmp3_uint32 pcmFramesInCurrentMP3Frame; + + pcmFramesInCurrentMP3Frame = drmp3_decode_next_frame_ex(pMP3, NULL); + if (pcmFramesInCurrentMP3Frame == 0) { + break; + } + + totalPCMFrameCount += pcmFramesInCurrentMP3Frame; + totalMP3FrameCount += 1; + } + + /* Finally, we need to seek back to where we were. */ + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return DRMP3_FALSE; + } + + if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) { + return DRMP3_FALSE; + } + + if (pMP3FrameCount != NULL) { + *pMP3FrameCount = totalMP3FrameCount; + } + if (pPCMFrameCount != NULL) { + *pPCMFrameCount = totalPCMFrameCount; + } + + return DRMP3_TRUE; +} + +DRMP3_API drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) +{ + drmp3_uint64 totalPCMFrameCount; + if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, NULL, &totalPCMFrameCount)) { + return 0; + } + + return totalPCMFrameCount; +} + +DRMP3_API drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3) +{ + drmp3_uint64 totalMP3FrameCount; + if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, &totalMP3FrameCount, NULL)) { + return 0; + } + + return totalMP3FrameCount; +} + +static void drmp3__accumulate_running_pcm_frame_count(drmp3* pMP3, drmp3_uint32 pcmFrameCountIn, drmp3_uint64* pRunningPCMFrameCount, float* pRunningPCMFrameCountFractionalPart) +{ + float srcRatio; + float pcmFrameCountOutF; + drmp3_uint32 pcmFrameCountOut; + + srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate; + DRMP3_ASSERT(srcRatio > 0); + + pcmFrameCountOutF = *pRunningPCMFrameCountFractionalPart + (pcmFrameCountIn / srcRatio); + pcmFrameCountOut = (drmp3_uint32)pcmFrameCountOutF; + *pRunningPCMFrameCountFractionalPart = pcmFrameCountOutF - pcmFrameCountOut; + *pRunningPCMFrameCount += pcmFrameCountOut; +} + +typedef struct +{ + drmp3_uint64 bytePos; + drmp3_uint64 pcmFrameIndex; /* <-- After sample rate conversion. */ +} drmp3__seeking_mp3_frame_info; + +DRMP3_API drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints) +{ + drmp3_uint32 seekPointCount; + drmp3_uint64 currentPCMFrame; + drmp3_uint64 totalMP3FrameCount; + drmp3_uint64 totalPCMFrameCount; + + if (pMP3 == NULL || pSeekPointCount == NULL || pSeekPoints == NULL) { + return DRMP3_FALSE; /* Invalid args. */ + } + + seekPointCount = *pSeekPointCount; + if (seekPointCount == 0) { + return DRMP3_FALSE; /* The client has requested no seek points. Consider this to be invalid arguments since the client has probably not intended this. */ + } + + /* We'll need to seek back to the current sample after calculating the seekpoints so we need to go ahead and grab the current location at the top. */ + currentPCMFrame = pMP3->currentPCMFrame; + + /* We never do more than the total number of MP3 frames and we limit it to 32-bits. */ + if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, &totalMP3FrameCount, &totalPCMFrameCount)) { + return DRMP3_FALSE; + } + + /* If there's less than DRMP3_SEEK_LEADING_MP3_FRAMES+1 frames we just report 1 seek point which will be the very start of the stream. */ + if (totalMP3FrameCount < DRMP3_SEEK_LEADING_MP3_FRAMES+1) { + seekPointCount = 1; + pSeekPoints[0].seekPosInBytes = 0; + pSeekPoints[0].pcmFrameIndex = 0; + pSeekPoints[0].mp3FramesToDiscard = 0; + pSeekPoints[0].pcmFramesToDiscard = 0; + } else { + drmp3_uint64 pcmFramesBetweenSeekPoints; + drmp3__seeking_mp3_frame_info mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES+1]; + drmp3_uint64 runningPCMFrameCount = 0; + float runningPCMFrameCountFractionalPart = 0; + drmp3_uint64 nextTargetPCMFrame; + drmp3_uint32 iMP3Frame; + drmp3_uint32 iSeekPoint; + + if (seekPointCount > totalMP3FrameCount-1) { + seekPointCount = (drmp3_uint32)totalMP3FrameCount-1; + } + + pcmFramesBetweenSeekPoints = totalPCMFrameCount / (seekPointCount+1); + + /* + Here is where we actually calculate the seek points. We need to start by moving the start of the stream. We then enumerate over each + MP3 frame. + */ + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return DRMP3_FALSE; + } + + /* + We need to cache the byte positions of the previous MP3 frames. As a new MP3 frame is iterated, we cycle the byte positions in this + array. The value in the first item in this array is the byte position that will be reported in the next seek point. + */ + + /* We need to initialize the array of MP3 byte positions for the leading MP3 frames. */ + for (iMP3Frame = 0; iMP3Frame < DRMP3_SEEK_LEADING_MP3_FRAMES+1; ++iMP3Frame) { + drmp3_uint32 pcmFramesInCurrentMP3FrameIn; + + /* The byte position of the next frame will be the stream's cursor position, minus whatever is sitting in the buffer. */ + DRMP3_ASSERT(pMP3->streamCursor >= pMP3->dataSize); + mp3FrameInfo[iMP3Frame].bytePos = pMP3->streamCursor - pMP3->dataSize; + mp3FrameInfo[iMP3Frame].pcmFrameIndex = runningPCMFrameCount; + + /* We need to get information about this frame so we can know how many samples it contained. */ + pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); + if (pcmFramesInCurrentMP3FrameIn == 0) { + return DRMP3_FALSE; /* This should never happen. */ + } + + drmp3__accumulate_running_pcm_frame_count(pMP3, pcmFramesInCurrentMP3FrameIn, &runningPCMFrameCount, &runningPCMFrameCountFractionalPart); + } + + /* + At this point we will have extracted the byte positions of the leading MP3 frames. We can now start iterating over each seek point and + calculate them. + */ + nextTargetPCMFrame = 0; + for (iSeekPoint = 0; iSeekPoint < seekPointCount; ++iSeekPoint) { + nextTargetPCMFrame += pcmFramesBetweenSeekPoints; + + for (;;) { + if (nextTargetPCMFrame < runningPCMFrameCount) { + /* The next seek point is in the current MP3 frame. */ + pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos; + pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame; + pSeekPoints[iSeekPoint].mp3FramesToDiscard = DRMP3_SEEK_LEADING_MP3_FRAMES; + pSeekPoints[iSeekPoint].pcmFramesToDiscard = (drmp3_uint16)(nextTargetPCMFrame - mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES-1].pcmFrameIndex); + break; + } else { + size_t i; + drmp3_uint32 pcmFramesInCurrentMP3FrameIn; + + /* + The next seek point is not in the current MP3 frame, so continue on to the next one. The first thing to do is cycle the cached + MP3 frame info. + */ + for (i = 0; i < DRMP3_COUNTOF(mp3FrameInfo)-1; ++i) { + mp3FrameInfo[i] = mp3FrameInfo[i+1]; + } + + /* Cache previous MP3 frame info. */ + mp3FrameInfo[DRMP3_COUNTOF(mp3FrameInfo)-1].bytePos = pMP3->streamCursor - pMP3->dataSize; + mp3FrameInfo[DRMP3_COUNTOF(mp3FrameInfo)-1].pcmFrameIndex = runningPCMFrameCount; + + /* + Go to the next MP3 frame. This shouldn't ever fail, but just in case it does we just set the seek point and break. If it happens, it + should only ever do it for the last seek point. + */ + pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); + if (pcmFramesInCurrentMP3FrameIn == 0) { + pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos; + pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame; + pSeekPoints[iSeekPoint].mp3FramesToDiscard = DRMP3_SEEK_LEADING_MP3_FRAMES; + pSeekPoints[iSeekPoint].pcmFramesToDiscard = (drmp3_uint16)(nextTargetPCMFrame - mp3FrameInfo[DRMP3_SEEK_LEADING_MP3_FRAMES-1].pcmFrameIndex); + break; + } + + drmp3__accumulate_running_pcm_frame_count(pMP3, pcmFramesInCurrentMP3FrameIn, &runningPCMFrameCount, &runningPCMFrameCountFractionalPart); + } + } + } + + /* Finally, we need to seek back to where we were. */ + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return DRMP3_FALSE; + } + if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) { + return DRMP3_FALSE; + } + } + + *pSeekPointCount = seekPointCount; + return DRMP3_TRUE; +} + +DRMP3_API drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints) +{ + if (pMP3 == NULL) { + return DRMP3_FALSE; + } + + if (seekPointCount == 0 || pSeekPoints == NULL) { + /* Unbinding. */ + pMP3->seekPointCount = 0; + pMP3->pSeekPoints = NULL; + } else { + /* Binding. */ + pMP3->seekPointCount = seekPointCount; + pMP3->pSeekPoints = pSeekPoints; + } + + return DRMP3_TRUE; +} + + +static float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +{ + drmp3_uint64 totalFramesRead = 0; + drmp3_uint64 framesCapacity = 0; + float* pFrames = NULL; + float temp[4096]; + + DRMP3_ASSERT(pMP3 != NULL); + + for (;;) { + drmp3_uint64 framesToReadRightNow = DRMP3_COUNTOF(temp) / pMP3->channels; + drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); + if (framesJustRead == 0) { + break; + } + + /* Reallocate the output buffer if there's not enough room. */ + if (framesCapacity < totalFramesRead + framesJustRead) { + drmp3_uint64 oldFramesBufferSize; + drmp3_uint64 newFramesBufferSize; + drmp3_uint64 newFramesCap; + float* pNewFrames; + + newFramesCap = framesCapacity * 2; + if (newFramesCap < totalFramesRead + framesJustRead) { + newFramesCap = totalFramesRead + framesJustRead; + } + + oldFramesBufferSize = framesCapacity * pMP3->channels * sizeof(float); + newFramesBufferSize = newFramesCap * pMP3->channels * sizeof(float); + if (newFramesBufferSize > (drmp3_uint64)DRMP3_SIZE_MAX) { + break; + } + + pNewFrames = (float*)drmp3__realloc_from_callbacks(pFrames, (size_t)newFramesBufferSize, (size_t)oldFramesBufferSize, &pMP3->allocationCallbacks); + if (pNewFrames == NULL) { + drmp3__free_from_callbacks(pFrames, &pMP3->allocationCallbacks); + break; + } + + pFrames = pNewFrames; + framesCapacity = newFramesCap; + } + + DRMP3_COPY_MEMORY(pFrames + totalFramesRead*pMP3->channels, temp, (size_t)(framesJustRead*pMP3->channels*sizeof(float))); + totalFramesRead += framesJustRead; + + /* If the number of frames we asked for is less that what we actually read it means we've reached the end. */ + if (framesJustRead != framesToReadRightNow) { + break; + } + } + + if (pConfig != NULL) { + pConfig->channels = pMP3->channels; + pConfig->sampleRate = pMP3->sampleRate; + } + + drmp3_uninit(pMP3); + + if (pTotalFrameCount) { + *pTotalFrameCount = totalFramesRead; + } + + return pFrames; +} + +static drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +{ + drmp3_uint64 totalFramesRead = 0; + drmp3_uint64 framesCapacity = 0; + drmp3_int16* pFrames = NULL; + drmp3_int16 temp[4096]; + + DRMP3_ASSERT(pMP3 != NULL); + + for (;;) { + drmp3_uint64 framesToReadRightNow = DRMP3_COUNTOF(temp) / pMP3->channels; + drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_s16(pMP3, framesToReadRightNow, temp); + if (framesJustRead == 0) { + break; + } + + /* Reallocate the output buffer if there's not enough room. */ + if (framesCapacity < totalFramesRead + framesJustRead) { + drmp3_uint64 newFramesBufferSize; + drmp3_uint64 oldFramesBufferSize; + drmp3_uint64 newFramesCap; + drmp3_int16* pNewFrames; + + newFramesCap = framesCapacity * 2; + if (newFramesCap < totalFramesRead + framesJustRead) { + newFramesCap = totalFramesRead + framesJustRead; + } + + oldFramesBufferSize = framesCapacity * pMP3->channels * sizeof(drmp3_int16); + newFramesBufferSize = newFramesCap * pMP3->channels * sizeof(drmp3_int16); + if (newFramesBufferSize > (drmp3_uint64)DRMP3_SIZE_MAX) { + break; + } + + pNewFrames = (drmp3_int16*)drmp3__realloc_from_callbacks(pFrames, (size_t)newFramesBufferSize, (size_t)oldFramesBufferSize, &pMP3->allocationCallbacks); + if (pNewFrames == NULL) { + drmp3__free_from_callbacks(pFrames, &pMP3->allocationCallbacks); + break; + } + + pFrames = pNewFrames; + framesCapacity = newFramesCap; + } + + DRMP3_COPY_MEMORY(pFrames + totalFramesRead*pMP3->channels, temp, (size_t)(framesJustRead*pMP3->channels*sizeof(drmp3_int16))); + totalFramesRead += framesJustRead; + + /* If the number of frames we asked for is less that what we actually read it means we've reached the end. */ + if (framesJustRead != framesToReadRightNow) { + break; + } + } + + if (pConfig != NULL) { + pConfig->channels = pMP3->channels; + pConfig->sampleRate = pMP3->sampleRate; + } + + drmp3_uninit(pMP3); + + if (pTotalFrameCount) { + *pTotalFrameCount = totalFramesRead; + } + + return pFrames; +} + + +DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + drmp3 mp3; + if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pAllocationCallbacks)) { + return NULL; + } + + return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); +} + +DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + drmp3 mp3; + if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pAllocationCallbacks)) { + return NULL; + } + + return drmp3__full_read_and_close_s16(&mp3, pConfig, pTotalFrameCount); +} + + +DRMP3_API float* drmp3_open_memory_and_read_pcm_frames_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + drmp3 mp3; + if (!drmp3_init_memory(&mp3, pData, dataSize, pAllocationCallbacks)) { + return NULL; + } + + return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); +} + +DRMP3_API drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + drmp3 mp3; + if (!drmp3_init_memory(&mp3, pData, dataSize, pAllocationCallbacks)) { + return NULL; + } + + return drmp3__full_read_and_close_s16(&mp3, pConfig, pTotalFrameCount); +} + + +#ifndef DR_MP3_NO_STDIO +DRMP3_API float* drmp3_open_file_and_read_pcm_frames_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + drmp3 mp3; + if (!drmp3_init_file(&mp3, filePath, pAllocationCallbacks)) { + return NULL; + } + + return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); +} + +DRMP3_API drmp3_int16* drmp3_open_file_and_read_pcm_frames_s16(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + drmp3 mp3; + if (!drmp3_init_file(&mp3, filePath, pAllocationCallbacks)) { + return NULL; + } + + return drmp3__full_read_and_close_s16(&mp3, pConfig, pTotalFrameCount); +} +#endif + +DRMP3_API void* drmp3_malloc(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + return drmp3__malloc_from_callbacks(sz, pAllocationCallbacks); + } else { + return drmp3__malloc_default(sz, NULL); + } +} + +DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + drmp3__free_from_callbacks(p, pAllocationCallbacks); + } else { + drmp3__free_default(p, NULL); + } +} + +#endif /* dr_mp3_c */ +#endif /*DR_MP3_IMPLEMENTATION*/ + +/* +DIFFERENCES BETWEEN minimp3 AND dr_mp3 +====================================== +- First, keep in mind that minimp3 (https://github.com/lieff/minimp3) is where all the real work was done. All of the + code relating to the actual decoding remains mostly unmodified, apart from some namespacing changes. +- dr_mp3 adds a pulling style API which allows you to deliver raw data via callbacks. So, rather than pushing data + to the decoder, the decoder _pulls_ data from your callbacks. +- In addition to callbacks, a decoder can be initialized from a block of memory and a file. +- The dr_mp3 pull API reads PCM frames rather than whole MP3 frames. +- dr_mp3 adds convenience APIs for opening and decoding entire files in one go. +- dr_mp3 is fully namespaced, including the implementation section, which is more suitable when compiling projects + as a single translation unit (aka unity builds). At the time of writing this, a unity build is not possible when + using minimp3 in conjunction with stb_vorbis. dr_mp3 addresses this. +*/ + +/* +RELEASE NOTES - v0.5.0 +======================= +Version 0.5.0 has breaking API changes. + +Improved Client-Defined Memory Allocation +----------------------------------------- +The main change with this release is the addition of a more flexible way of implementing custom memory allocation routines. The +existing system of DRMP3_MALLOC, DRMP3_REALLOC and DRMP3_FREE are still in place and will be used by default when no custom +allocation callbacks are specified. + +To use the new system, you pass in a pointer to a drmp3_allocation_callbacks object to drmp3_init() and family, like this: + + void* my_malloc(size_t sz, void* pUserData) + { + return malloc(sz); + } + void* my_realloc(void* p, size_t sz, void* pUserData) + { + return realloc(p, sz); + } + void my_free(void* p, void* pUserData) + { + free(p); + } + + ... + + drmp3_allocation_callbacks allocationCallbacks; + allocationCallbacks.pUserData = &myData; + allocationCallbacks.onMalloc = my_malloc; + allocationCallbacks.onRealloc = my_realloc; + allocationCallbacks.onFree = my_free; + drmp3_init_file(&mp3, "my_file.mp3", NULL, &allocationCallbacks); + +The advantage of this new system is that it allows you to specify user data which will be passed in to the allocation routines. + +Passing in null for the allocation callbacks object will cause dr_mp3 to use defaults which is the same as DRMP3_MALLOC, +DRMP3_REALLOC and DRMP3_FREE and the equivalent of how it worked in previous versions. + +Every API that opens a drmp3 object now takes this extra parameter. These include the following: + + drmp3_init() + drmp3_init_file() + drmp3_init_memory() + drmp3_open_and_read_pcm_frames_f32() + drmp3_open_and_read_pcm_frames_s16() + drmp3_open_memory_and_read_pcm_frames_f32() + drmp3_open_memory_and_read_pcm_frames_s16() + drmp3_open_file_and_read_pcm_frames_f32() + drmp3_open_file_and_read_pcm_frames_s16() + +Renamed APIs +------------ +The following APIs have been renamed for consistency with other dr_* libraries and to make it clear that they return PCM frame +counts rather than sample counts. + + drmp3_open_and_read_f32() -> drmp3_open_and_read_pcm_frames_f32() + drmp3_open_and_read_s16() -> drmp3_open_and_read_pcm_frames_s16() + drmp3_open_memory_and_read_f32() -> drmp3_open_memory_and_read_pcm_frames_f32() + drmp3_open_memory_and_read_s16() -> drmp3_open_memory_and_read_pcm_frames_s16() + drmp3_open_file_and_read_f32() -> drmp3_open_file_and_read_pcm_frames_f32() + drmp3_open_file_and_read_s16() -> drmp3_open_file_and_read_pcm_frames_s16() +*/ + +/* +REVISION HISTORY +================ +v0.6.39 - 2024-02-27 + - Fix a Wdouble-promotion warning. + +v0.6.38 - 2023-11-02 + - Fix build for ARMv6-M. + +v0.6.37 - 2023-07-07 + - Silence a static analysis warning. + +v0.6.36 - 2023-06-17 + - Fix an incorrect date in revision history. No functional change. + +v0.6.35 - 2023-05-22 + - Minor code restructure. No functional change. + +v0.6.34 - 2022-09-17 + - Fix compilation with DJGPP. + - Fix compilation when compiling with x86 with no SSE2. + - Remove an unnecessary variable from the drmp3 structure. + +v0.6.33 - 2022-04-10 + - Fix compilation error with the MSVC ARM64 build. + - Fix compilation error on older versions of GCC. + - Remove some unused functions. + +v0.6.32 - 2021-12-11 + - Fix a warning with Clang. + +v0.6.31 - 2021-08-22 + - Fix a bug when loading from memory. + +v0.6.30 - 2021-08-16 + - Silence some warnings. + - Replace memory operations with DRMP3_* macros. + +v0.6.29 - 2021-08-08 + - Bring up to date with minimp3. + +v0.6.28 - 2021-07-31 + - Fix platform detection for ARM64. + - Fix a compilation error with C89. + +v0.6.27 - 2021-02-21 + - Fix a warning due to referencing _MSC_VER when it is undefined. + +v0.6.26 - 2021-01-31 + - Bring up to date with minimp3. + +v0.6.25 - 2020-12-26 + - Remove DRMP3_DEFAULT_CHANNELS and DRMP3_DEFAULT_SAMPLE_RATE which are leftovers from some removed APIs. + +v0.6.24 - 2020-12-07 + - Fix a typo in version date for 0.6.23. + +v0.6.23 - 2020-12-03 + - Fix an error where a file can be closed twice when initialization of the decoder fails. + +v0.6.22 - 2020-12-02 + - Fix an error where it's possible for a file handle to be left open when initialization of the decoder fails. + +v0.6.21 - 2020-11-28 + - Bring up to date with minimp3. + +v0.6.20 - 2020-11-21 + - Fix compilation with OpenWatcom. + +v0.6.19 - 2020-11-13 + - Minor code clean up. + +v0.6.18 - 2020-11-01 + - Improve compiler support for older versions of GCC. + +v0.6.17 - 2020-09-28 + - Bring up to date with minimp3. + +v0.6.16 - 2020-08-02 + - Simplify sized types. + +v0.6.15 - 2020-07-25 + - Fix a compilation warning. + +v0.6.14 - 2020-07-23 + - Fix undefined behaviour with memmove(). + +v0.6.13 - 2020-07-06 + - Fix a bug when converting from s16 to f32 in drmp3_read_pcm_frames_f32(). + +v0.6.12 - 2020-06-23 + - Add include guard for the implementation section. + +v0.6.11 - 2020-05-26 + - Fix use of uninitialized variable error. + +v0.6.10 - 2020-05-16 + - Add compile-time and run-time version querying. + - DRMP3_VERSION_MINOR + - DRMP3_VERSION_MAJOR + - DRMP3_VERSION_REVISION + - DRMP3_VERSION_STRING + - drmp3_version() + - drmp3_version_string() + +v0.6.9 - 2020-04-30 + - Change the `pcm` parameter of drmp3dec_decode_frame() to a `const drmp3_uint8*` for consistency with internal APIs. + +v0.6.8 - 2020-04-26 + - Optimizations to decoding when initializing from memory. + +v0.6.7 - 2020-04-25 + - Fix a compilation error with DR_MP3_NO_STDIO + - Optimization to decoding by reducing some data movement. + +v0.6.6 - 2020-04-23 + - Fix a minor bug with the running PCM frame counter. + +v0.6.5 - 2020-04-19 + - Fix compilation error on ARM builds. + +v0.6.4 - 2020-04-19 + - Bring up to date with changes to minimp3. + +v0.6.3 - 2020-04-13 + - Fix some pedantic warnings. + +v0.6.2 - 2020-04-10 + - Fix a crash in drmp3_open_*_and_read_pcm_frames_*() if the output config object is NULL. + +v0.6.1 - 2020-04-05 + - Fix warnings. + +v0.6.0 - 2020-04-04 + - API CHANGE: Remove the pConfig parameter from the following APIs: + - drmp3_init() + - drmp3_init_memory() + - drmp3_init_file() + - Add drmp3_init_file_w() for opening a file from a wchar_t encoded path. + +v0.5.6 - 2020-02-12 + - Bring up to date with minimp3. + +v0.5.5 - 2020-01-29 + - Fix a memory allocation bug in high level s16 decoding APIs. + +v0.5.4 - 2019-12-02 + - Fix a possible null pointer dereference when using custom memory allocators for realloc(). + +v0.5.3 - 2019-11-14 + - Fix typos in documentation. + +v0.5.2 - 2019-11-02 + - Bring up to date with minimp3. + +v0.5.1 - 2019-10-08 + - Fix a warning with GCC. + +v0.5.0 - 2019-10-07 + - API CHANGE: Add support for user defined memory allocation routines. This system allows the program to specify their own memory allocation + routines with a user data pointer for client-specific contextual data. This adds an extra parameter to the end of the following APIs: + - drmp3_init() + - drmp3_init_file() + - drmp3_init_memory() + - drmp3_open_and_read_pcm_frames_f32() + - drmp3_open_and_read_pcm_frames_s16() + - drmp3_open_memory_and_read_pcm_frames_f32() + - drmp3_open_memory_and_read_pcm_frames_s16() + - drmp3_open_file_and_read_pcm_frames_f32() + - drmp3_open_file_and_read_pcm_frames_s16() + - API CHANGE: Renamed the following APIs: + - drmp3_open_and_read_f32() -> drmp3_open_and_read_pcm_frames_f32() + - drmp3_open_and_read_s16() -> drmp3_open_and_read_pcm_frames_s16() + - drmp3_open_memory_and_read_f32() -> drmp3_open_memory_and_read_pcm_frames_f32() + - drmp3_open_memory_and_read_s16() -> drmp3_open_memory_and_read_pcm_frames_s16() + - drmp3_open_file_and_read_f32() -> drmp3_open_file_and_read_pcm_frames_f32() + - drmp3_open_file_and_read_s16() -> drmp3_open_file_and_read_pcm_frames_s16() + +v0.4.7 - 2019-07-28 + - Fix a compiler error. + +v0.4.6 - 2019-06-14 + - Fix a compiler error. + +v0.4.5 - 2019-06-06 + - Bring up to date with minimp3. + +v0.4.4 - 2019-05-06 + - Fixes to the VC6 build. + +v0.4.3 - 2019-05-05 + - Use the channel count and/or sample rate of the first MP3 frame instead of DRMP3_DEFAULT_CHANNELS and + DRMP3_DEFAULT_SAMPLE_RATE when they are set to 0. To use the old behaviour, just set the relevant property to + DRMP3_DEFAULT_CHANNELS or DRMP3_DEFAULT_SAMPLE_RATE. + - Add s16 reading APIs + - drmp3_read_pcm_frames_s16 + - drmp3_open_memory_and_read_pcm_frames_s16 + - drmp3_open_and_read_pcm_frames_s16 + - drmp3_open_file_and_read_pcm_frames_s16 + - Add drmp3_get_mp3_and_pcm_frame_count() to the public header section. + - Add support for C89. + - Change license to choice of public domain or MIT-0. + +v0.4.2 - 2019-02-21 + - Fix a warning. + +v0.4.1 - 2018-12-30 + - Fix a warning. + +v0.4.0 - 2018-12-16 + - API CHANGE: Rename some APIs: + - drmp3_read_f32 -> to drmp3_read_pcm_frames_f32 + - drmp3_seek_to_frame -> drmp3_seek_to_pcm_frame + - drmp3_open_and_decode_f32 -> drmp3_open_and_read_pcm_frames_f32 + - drmp3_open_and_decode_memory_f32 -> drmp3_open_memory_and_read_pcm_frames_f32 + - drmp3_open_and_decode_file_f32 -> drmp3_open_file_and_read_pcm_frames_f32 + - Add drmp3_get_pcm_frame_count(). + - Add drmp3_get_mp3_frame_count(). + - Improve seeking performance. + +v0.3.2 - 2018-09-11 + - Fix a couple of memory leaks. + - Bring up to date with minimp3. + +v0.3.1 - 2018-08-25 + - Fix C++ build. + +v0.3.0 - 2018-08-25 + - Bring up to date with minimp3. This has a minor API change: the "pcm" parameter of drmp3dec_decode_frame() has + been changed from short* to void* because it can now output both s16 and f32 samples, depending on whether or + not the DR_MP3_FLOAT_OUTPUT option is set. + +v0.2.11 - 2018-08-08 + - Fix a bug where the last part of a file is not read. + +v0.2.10 - 2018-08-07 + - Improve 64-bit detection. + +v0.2.9 - 2018-08-05 + - Fix C++ build on older versions of GCC. + - Bring up to date with minimp3. + +v0.2.8 - 2018-08-02 + - Fix compilation errors with older versions of GCC. + +v0.2.7 - 2018-07-13 + - Bring up to date with minimp3. + +v0.2.6 - 2018-07-12 + - Bring up to date with minimp3. + +v0.2.5 - 2018-06-22 + - Bring up to date with minimp3. + +v0.2.4 - 2018-05-12 + - Bring up to date with minimp3. + +v0.2.3 - 2018-04-29 + - Fix TCC build. + +v0.2.2 - 2018-04-28 + - Fix bug when opening a decoder from memory. + +v0.2.1 - 2018-04-27 + - Efficiency improvements when the decoder reaches the end of the stream. + +v0.2 - 2018-04-21 + - Bring up to date with minimp3. + - Start using major.minor.revision versioning. + +v0.1d - 2018-03-30 + - Bring up to date with minimp3. + +v0.1c - 2018-03-11 + - Fix C++ build error. + +v0.1b - 2018-03-07 + - Bring up to date with minimp3. + +v0.1a - 2018-02-28 + - Fix compilation error on GCC/Clang. + - Fix some warnings. + +v0.1 - 2018-02-xx + - Initial versioned release. +*/ + +/* +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - Public Domain (www.unlicense.org) +=============================================================================== +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright 2023 David Reid + +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. + +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. +*/ + +/* + https://github.com/lieff/minimp3 + To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. + This software is distributed without any warranty. + See . +*/ diff --git a/source/engine/thirdparty/dr_libs/dr_wav.h b/source/engine/thirdparty/dr_libs/dr_wav.h new file mode 100644 index 0000000..a8207ab --- /dev/null +++ b/source/engine/thirdparty/dr_libs/dr_wav.h @@ -0,0 +1,8815 @@ +/* +WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file. +dr_wav - v0.13.16 - 2024-02-27 + +David Reid - mackron@gmail.com + +GitHub: https://github.com/mackron/dr_libs +*/ + +/* +Introduction +============ +This is a single file library. To use it, do something like the following in one .c file. + + ```c + #define DR_WAV_IMPLEMENTATION + #include "dr_wav.h" + ``` + +You can then #include this file in other parts of the program as you would with any other header file. Do something like the following to read audio data: + + ```c + drwav wav; + if (!drwav_init_file(&wav, "my_song.wav", NULL)) { + // Error opening WAV file. + } + + drwav_int32* pDecodedInterleavedPCMFrames = malloc(wav.totalPCMFrameCount * wav.channels * sizeof(drwav_int32)); + size_t numberOfSamplesActuallyDecoded = drwav_read_pcm_frames_s32(&wav, wav.totalPCMFrameCount, pDecodedInterleavedPCMFrames); + + ... + + drwav_uninit(&wav); + ``` + +If you just want to quickly open and read the audio data in a single operation you can do something like this: + + ```c + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalPCMFrameCount; + float* pSampleData = drwav_open_file_and_read_pcm_frames_f32("my_song.wav", &channels, &sampleRate, &totalPCMFrameCount, NULL); + if (pSampleData == NULL) { + // Error opening and reading WAV file. + } + + ... + + drwav_free(pSampleData, NULL); + ``` + +The examples above use versions of the API that convert the audio data to a consistent format (32-bit signed PCM, in this case), but you can still output the +audio data in its internal format (see notes below for supported formats): + + ```c + size_t framesRead = drwav_read_pcm_frames(&wav, wav.totalPCMFrameCount, pDecodedInterleavedPCMFrames); + ``` + +You can also read the raw bytes of audio data, which could be useful if dr_wav does not have native support for a particular data format: + + ```c + size_t bytesRead = drwav_read_raw(&wav, bytesToRead, pRawDataBuffer); + ``` + +dr_wav can also be used to output WAV files. This does not currently support compressed formats. To use this, look at `drwav_init_write()`, +`drwav_init_file_write()`, etc. Use `drwav_write_pcm_frames()` to write samples, or `drwav_write_raw()` to write raw data in the "data" chunk. + + ```c + drwav_data_format format; + format.container = drwav_container_riff; // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64. + format.format = DR_WAVE_FORMAT_PCM; // <-- Any of the DR_WAVE_FORMAT_* codes. + format.channels = 2; + format.sampleRate = 44100; + format.bitsPerSample = 16; + drwav_init_file_write(&wav, "data/recording.wav", &format, NULL); + + ... + + drwav_uint64 framesWritten = drwav_write_pcm_frames(pWav, frameCount, pSamples); + ``` + +Note that writing to AIFF or RIFX is not supported. + +dr_wav has support for decoding from a number of different encapsulation formats. See below for details. + + +Build Options +============= +#define these options before including this file. + +#define DR_WAV_NO_CONVERSION_API + Disables conversion APIs such as `drwav_read_pcm_frames_f32()` and `drwav_s16_to_f32()`. + +#define DR_WAV_NO_STDIO + Disables APIs that initialize a decoder from a file such as `drwav_init_file()`, `drwav_init_file_write()`, etc. + +#define DR_WAV_NO_WCHAR + Disables all functions ending with `_w`. Use this if your compiler does not provide wchar.h. Not required if DR_WAV_NO_STDIO is also defined. + + +Supported Encapsulations +======================== +- RIFF (Regular WAV) +- RIFX (Big-Endian) +- AIFF (Does not currently support ADPCM) +- RF64 +- W64 + +Note that AIFF and RIFX do not support write mode, nor do they support reading of metadata. + + +Supported Encodings +=================== +- Unsigned 8-bit PCM +- Signed 12-bit PCM +- Signed 16-bit PCM +- Signed 24-bit PCM +- Signed 32-bit PCM +- IEEE 32-bit floating point +- IEEE 64-bit floating point +- A-law and u-law +- Microsoft ADPCM +- IMA ADPCM (DVI, format code 0x11) + +8-bit PCM encodings are always assumed to be unsigned. Signed 8-bit encoding can only be read with `drwav_read_raw()`. + +Note that ADPCM is not currently supported with AIFF. Contributions welcome. + + +Notes +===== +- Samples are always interleaved. +- The default read function does not do any data conversion. Use `drwav_read_pcm_frames_f32()`, `drwav_read_pcm_frames_s32()` and `drwav_read_pcm_frames_s16()` + to read and convert audio data to 32-bit floating point, signed 32-bit integer and signed 16-bit integer samples respectively. +- dr_wav will try to read the WAV file as best it can, even if it's not strictly conformant to the WAV format. +*/ + +#ifndef dr_wav_h +#define dr_wav_h + +#ifdef __cplusplus +extern "C" { +#endif + +#define DRWAV_STRINGIFY(x) #x +#define DRWAV_XSTRINGIFY(x) DRWAV_STRINGIFY(x) + +#define DRWAV_VERSION_MAJOR 0 +#define DRWAV_VERSION_MINOR 13 +#define DRWAV_VERSION_REVISION 16 +#define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) + +#include /* For size_t. */ + +/* Sized Types */ +typedef signed char drwav_int8; +typedef unsigned char drwav_uint8; +typedef signed short drwav_int16; +typedef unsigned short drwav_uint16; +typedef signed int drwav_int32; +typedef unsigned int drwav_uint32; +#if defined(_MSC_VER) && !defined(__clang__) + typedef signed __int64 drwav_int64; + typedef unsigned __int64 drwav_uint64; +#else + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wlong-long" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif + #endif + typedef signed long long drwav_int64; + typedef unsigned long long drwav_uint64; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop + #endif +#endif +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) + typedef drwav_uint64 drwav_uintptr; +#else + typedef drwav_uint32 drwav_uintptr; +#endif +typedef drwav_uint8 drwav_bool8; +typedef drwav_uint32 drwav_bool32; +#define DRWAV_TRUE 1 +#define DRWAV_FALSE 0 +/* End Sized Types */ + +/* Decorations */ +#if !defined(DRWAV_API) + #if defined(DRWAV_DLL) + #if defined(_WIN32) + #define DRWAV_DLL_IMPORT __declspec(dllimport) + #define DRWAV_DLL_EXPORT __declspec(dllexport) + #define DRWAV_DLL_PRIVATE static + #else + #if defined(__GNUC__) && __GNUC__ >= 4 + #define DRWAV_DLL_IMPORT __attribute__((visibility("default"))) + #define DRWAV_DLL_EXPORT __attribute__((visibility("default"))) + #define DRWAV_DLL_PRIVATE __attribute__((visibility("hidden"))) + #else + #define DRWAV_DLL_IMPORT + #define DRWAV_DLL_EXPORT + #define DRWAV_DLL_PRIVATE static + #endif + #endif + + #if defined(DR_WAV_IMPLEMENTATION) || defined(DRWAV_IMPLEMENTATION) + #define DRWAV_API DRWAV_DLL_EXPORT + #else + #define DRWAV_API DRWAV_DLL_IMPORT + #endif + #define DRWAV_PRIVATE DRWAV_DLL_PRIVATE + #else + #define DRWAV_API extern + #define DRWAV_PRIVATE static + #endif +#endif +/* End Decorations */ + +/* Result Codes */ +typedef drwav_int32 drwav_result; +#define DRWAV_SUCCESS 0 +#define DRWAV_ERROR -1 /* A generic error. */ +#define DRWAV_INVALID_ARGS -2 +#define DRWAV_INVALID_OPERATION -3 +#define DRWAV_OUT_OF_MEMORY -4 +#define DRWAV_OUT_OF_RANGE -5 +#define DRWAV_ACCESS_DENIED -6 +#define DRWAV_DOES_NOT_EXIST -7 +#define DRWAV_ALREADY_EXISTS -8 +#define DRWAV_TOO_MANY_OPEN_FILES -9 +#define DRWAV_INVALID_FILE -10 +#define DRWAV_TOO_BIG -11 +#define DRWAV_PATH_TOO_LONG -12 +#define DRWAV_NAME_TOO_LONG -13 +#define DRWAV_NOT_DIRECTORY -14 +#define DRWAV_IS_DIRECTORY -15 +#define DRWAV_DIRECTORY_NOT_EMPTY -16 +#define DRWAV_END_OF_FILE -17 +#define DRWAV_NO_SPACE -18 +#define DRWAV_BUSY -19 +#define DRWAV_IO_ERROR -20 +#define DRWAV_INTERRUPT -21 +#define DRWAV_UNAVAILABLE -22 +#define DRWAV_ALREADY_IN_USE -23 +#define DRWAV_BAD_ADDRESS -24 +#define DRWAV_BAD_SEEK -25 +#define DRWAV_BAD_PIPE -26 +#define DRWAV_DEADLOCK -27 +#define DRWAV_TOO_MANY_LINKS -28 +#define DRWAV_NOT_IMPLEMENTED -29 +#define DRWAV_NO_MESSAGE -30 +#define DRWAV_BAD_MESSAGE -31 +#define DRWAV_NO_DATA_AVAILABLE -32 +#define DRWAV_INVALID_DATA -33 +#define DRWAV_TIMEOUT -34 +#define DRWAV_NO_NETWORK -35 +#define DRWAV_NOT_UNIQUE -36 +#define DRWAV_NOT_SOCKET -37 +#define DRWAV_NO_ADDRESS -38 +#define DRWAV_BAD_PROTOCOL -39 +#define DRWAV_PROTOCOL_UNAVAILABLE -40 +#define DRWAV_PROTOCOL_NOT_SUPPORTED -41 +#define DRWAV_PROTOCOL_FAMILY_NOT_SUPPORTED -42 +#define DRWAV_ADDRESS_FAMILY_NOT_SUPPORTED -43 +#define DRWAV_SOCKET_NOT_SUPPORTED -44 +#define DRWAV_CONNECTION_RESET -45 +#define DRWAV_ALREADY_CONNECTED -46 +#define DRWAV_NOT_CONNECTED -47 +#define DRWAV_CONNECTION_REFUSED -48 +#define DRWAV_NO_HOST -49 +#define DRWAV_IN_PROGRESS -50 +#define DRWAV_CANCELLED -51 +#define DRWAV_MEMORY_ALREADY_MAPPED -52 +#define DRWAV_AT_END -53 +/* End Result Codes */ + +/* Common data formats. */ +#define DR_WAVE_FORMAT_PCM 0x1 +#define DR_WAVE_FORMAT_ADPCM 0x2 +#define DR_WAVE_FORMAT_IEEE_FLOAT 0x3 +#define DR_WAVE_FORMAT_ALAW 0x6 +#define DR_WAVE_FORMAT_MULAW 0x7 +#define DR_WAVE_FORMAT_DVI_ADPCM 0x11 +#define DR_WAVE_FORMAT_EXTENSIBLE 0xFFFE + +/* Flags to pass into drwav_init_ex(), etc. */ +#define DRWAV_SEQUENTIAL 0x00000001 +#define DRWAV_WITH_METADATA 0x00000002 + +DRWAV_API void drwav_version(drwav_uint32* pMajor, drwav_uint32* pMinor, drwav_uint32* pRevision); +DRWAV_API const char* drwav_version_string(void); + +/* Allocation Callbacks */ +typedef struct +{ + void* pUserData; + void* (* onMalloc)(size_t sz, void* pUserData); + void* (* onRealloc)(void* p, size_t sz, void* pUserData); + void (* onFree)(void* p, void* pUserData); +} drwav_allocation_callbacks; +/* End Allocation Callbacks */ + +typedef enum +{ + drwav_seek_origin_start, + drwav_seek_origin_current +} drwav_seek_origin; + +typedef enum +{ + drwav_container_riff, + drwav_container_rifx, + drwav_container_w64, + drwav_container_rf64, + drwav_container_aiff +} drwav_container; + +typedef struct +{ + union + { + drwav_uint8 fourcc[4]; + drwav_uint8 guid[16]; + } id; + + /* The size in bytes of the chunk. */ + drwav_uint64 sizeInBytes; + + /* + RIFF = 2 byte alignment. + W64 = 8 byte alignment. + */ + unsigned int paddingSize; +} drwav_chunk_header; + +typedef struct +{ + /* + The format tag exactly as specified in the wave file's "fmt" chunk. This can be used by applications + that require support for data formats not natively supported by dr_wav. + */ + drwav_uint16 formatTag; + + /* The number of channels making up the audio data. When this is set to 1 it is mono, 2 is stereo, etc. */ + drwav_uint16 channels; + + /* The sample rate. Usually set to something like 44100. */ + drwav_uint32 sampleRate; + + /* Average bytes per second. You probably don't need this, but it's left here for informational purposes. */ + drwav_uint32 avgBytesPerSec; + + /* Block align. This is equal to the number of channels * bytes per sample. */ + drwav_uint16 blockAlign; + + /* Bits per sample. */ + drwav_uint16 bitsPerSample; + + /* The size of the extended data. Only used internally for validation, but left here for informational purposes. */ + drwav_uint16 extendedSize; + + /* + The number of valid bits per sample. When is equal to WAVE_FORMAT_EXTENSIBLE, + is always rounded up to the nearest multiple of 8. This variable contains information about exactly how + many bits are valid per sample. Mainly used for informational purposes. + */ + drwav_uint16 validBitsPerSample; + + /* The channel mask. Not used at the moment. */ + drwav_uint32 channelMask; + + /* The sub-format, exactly as specified by the wave file. */ + drwav_uint8 subFormat[16]; +} drwav_fmt; + +DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT); + + +/* +Callback for when data is read. Return value is the number of bytes actually read. + +pUserData [in] The user data that was passed to drwav_init() and family. +pBufferOut [out] The output buffer. +bytesToRead [in] The number of bytes to read. + +Returns the number of bytes actually read. + +A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until +either the entire bytesToRead is filled or you have reached the end of the stream. +*/ +typedef size_t (* drwav_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); + +/* +Callback for when data is written. Returns value is the number of bytes actually written. + +pUserData [in] The user data that was passed to drwav_init_write() and family. +pData [out] A pointer to the data to write. +bytesToWrite [in] The number of bytes to write. + +Returns the number of bytes actually written. + +If the return value differs from bytesToWrite, it indicates an error. +*/ +typedef size_t (* drwav_write_proc)(void* pUserData, const void* pData, size_t bytesToWrite); + +/* +Callback for when data needs to be seeked. + +pUserData [in] The user data that was passed to drwav_init() and family. +offset [in] The number of bytes to move, relative to the origin. Will never be negative. +origin [in] The origin of the seek - the current position or the start of the stream. + +Returns whether or not the seek was successful. + +Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which will be either drwav_seek_origin_start or +drwav_seek_origin_current. +*/ +typedef drwav_bool32 (* drwav_seek_proc)(void* pUserData, int offset, drwav_seek_origin origin); + +/* +Callback for when drwav_init_ex() finds a chunk. + +pChunkUserData [in] The user data that was passed to the pChunkUserData parameter of drwav_init_ex() and family. +onRead [in] A pointer to the function to call when reading. +onSeek [in] A pointer to the function to call when seeking. +pReadSeekUserData [in] The user data that was passed to the pReadSeekUserData parameter of drwav_init_ex() and family. +pChunkHeader [in] A pointer to an object containing basic header information about the chunk. Use this to identify the chunk. +container [in] Whether or not the WAV file is a RIFF or Wave64 container. If you're unsure of the difference, assume RIFF. +pFMT [in] A pointer to the object containing the contents of the "fmt" chunk. + +Returns the number of bytes read + seeked. + +To read data from the chunk, call onRead(), passing in pReadSeekUserData as the first parameter. Do the same for seeking with onSeek(). The return value must +be the total number of bytes you have read _plus_ seeked. + +Use the `container` argument to discriminate the fields in `pChunkHeader->id`. If the container is `drwav_container_riff` or `drwav_container_rf64` you should +use `id.fourcc`, otherwise you should use `id.guid`. + +The `pFMT` parameter can be used to determine the data format of the wave file. Use `drwav_fmt_get_format()` to get the sample format, which will be one of the +`DR_WAVE_FORMAT_*` identifiers. + +The read pointer will be sitting on the first byte after the chunk's header. You must not attempt to read beyond the boundary of the chunk. +*/ +typedef drwav_uint64 (* drwav_chunk_proc)(void* pChunkUserData, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_chunk_header* pChunkHeader, drwav_container container, const drwav_fmt* pFMT); + + +/* Structure for internal use. Only used for loaders opened with drwav_init_memory(). */ +typedef struct +{ + const drwav_uint8* data; + size_t dataSize; + size_t currentReadPos; +} drwav__memory_stream; + +/* Structure for internal use. Only used for writers opened with drwav_init_memory_write(). */ +typedef struct +{ + void** ppData; + size_t* pDataSize; + size_t dataSize; + size_t dataCapacity; + size_t currentWritePos; +} drwav__memory_stream_write; + +typedef struct +{ + drwav_container container; /* RIFF, W64. */ + drwav_uint32 format; /* DR_WAVE_FORMAT_* */ + drwav_uint32 channels; + drwav_uint32 sampleRate; + drwav_uint32 bitsPerSample; +} drwav_data_format; + +typedef enum +{ + drwav_metadata_type_none = 0, + + /* + Unknown simply means a chunk that drwav does not handle specifically. You can still ask to + receive these chunks as metadata objects. It is then up to you to interpret the chunk's data. + You can also write unknown metadata to a wav file. Be careful writing unknown chunks if you + have also edited the audio data. The unknown chunks could represent offsets/sizes that no + longer correctly correspond to the audio data. + */ + drwav_metadata_type_unknown = 1 << 0, + + /* Only 1 of each of these metadata items are allowed in a wav file. */ + drwav_metadata_type_smpl = 1 << 1, + drwav_metadata_type_inst = 1 << 2, + drwav_metadata_type_cue = 1 << 3, + drwav_metadata_type_acid = 1 << 4, + drwav_metadata_type_bext = 1 << 5, + + /* + Wav files often have a LIST chunk. This is a chunk that contains a set of subchunks. For this + higher-level metadata API, we don't make a distinction between a regular chunk and a LIST + subchunk. Instead, they are all just 'metadata' items. + + There can be multiple of these metadata items in a wav file. + */ + drwav_metadata_type_list_label = 1 << 6, + drwav_metadata_type_list_note = 1 << 7, + drwav_metadata_type_list_labelled_cue_region = 1 << 8, + + drwav_metadata_type_list_info_software = 1 << 9, + drwav_metadata_type_list_info_copyright = 1 << 10, + drwav_metadata_type_list_info_title = 1 << 11, + drwav_metadata_type_list_info_artist = 1 << 12, + drwav_metadata_type_list_info_comment = 1 << 13, + drwav_metadata_type_list_info_date = 1 << 14, + drwav_metadata_type_list_info_genre = 1 << 15, + drwav_metadata_type_list_info_album = 1 << 16, + drwav_metadata_type_list_info_tracknumber = 1 << 17, + + /* Other type constants for convenience. */ + drwav_metadata_type_list_all_info_strings = drwav_metadata_type_list_info_software + | drwav_metadata_type_list_info_copyright + | drwav_metadata_type_list_info_title + | drwav_metadata_type_list_info_artist + | drwav_metadata_type_list_info_comment + | drwav_metadata_type_list_info_date + | drwav_metadata_type_list_info_genre + | drwav_metadata_type_list_info_album + | drwav_metadata_type_list_info_tracknumber, + + drwav_metadata_type_list_all_adtl = drwav_metadata_type_list_label + | drwav_metadata_type_list_note + | drwav_metadata_type_list_labelled_cue_region, + + drwav_metadata_type_all = -2, /*0xFFFFFFFF & ~drwav_metadata_type_unknown,*/ + drwav_metadata_type_all_including_unknown = -1 /*0xFFFFFFFF,*/ +} drwav_metadata_type; + +/* +Sampler Metadata + +The sampler chunk contains information about how a sound should be played in the context of a whole +audio production, and when used in a sampler. See https://en.wikipedia.org/wiki/Sample-based_synthesis. +*/ +typedef enum +{ + drwav_smpl_loop_type_forward = 0, + drwav_smpl_loop_type_pingpong = 1, + drwav_smpl_loop_type_backward = 2 +} drwav_smpl_loop_type; + +typedef struct +{ + /* The ID of the associated cue point, see drwav_cue and drwav_cue_point. As with all cue point IDs, this can correspond to a label chunk to give this loop a name, see drwav_list_label_or_note. */ + drwav_uint32 cuePointId; + + /* See drwav_smpl_loop_type. */ + drwav_uint32 type; + + /* The byte offset of the first sample to be played in the loop. */ + drwav_uint32 firstSampleByteOffset; + + /* The byte offset into the audio data of the last sample to be played in the loop. */ + drwav_uint32 lastSampleByteOffset; + + /* A value to represent that playback should occur at a point between samples. This value ranges from 0 to UINT32_MAX. Where a value of 0 means no fraction, and a value of (UINT32_MAX / 2) would mean half a sample. */ + drwav_uint32 sampleFraction; + + /* Number of times to play the loop. 0 means loop infinitely. */ + drwav_uint32 playCount; +} drwav_smpl_loop; + +typedef struct +{ + /* IDs for a particular MIDI manufacturer. 0 if not used. */ + drwav_uint32 manufacturerId; + drwav_uint32 productId; + + /* The period of 1 sample in nanoseconds. */ + drwav_uint32 samplePeriodNanoseconds; + + /* The MIDI root note of this file. 0 to 127. */ + drwav_uint32 midiUnityNote; + + /* The fraction of a semitone up from the given MIDI note. This is a value from 0 to UINT32_MAX, where 0 means no change and (UINT32_MAX / 2) is half a semitone (AKA 50 cents). */ + drwav_uint32 midiPitchFraction; + + /* Data relating to SMPTE standards which are used for syncing audio and video. 0 if not used. */ + drwav_uint32 smpteFormat; + drwav_uint32 smpteOffset; + + /* drwav_smpl_loop loops. */ + drwav_uint32 sampleLoopCount; + + /* Optional sampler-specific data. */ + drwav_uint32 samplerSpecificDataSizeInBytes; + + drwav_smpl_loop* pLoops; + drwav_uint8* pSamplerSpecificData; +} drwav_smpl; + +/* +Instrument Metadata + +The inst metadata contains data about how a sound should be played as part of an instrument. This +commonly read by samplers. See https://en.wikipedia.org/wiki/Sample-based_synthesis. +*/ +typedef struct +{ + drwav_int8 midiUnityNote; /* The root note of the audio as a MIDI note number. 0 to 127. */ + drwav_int8 fineTuneCents; /* -50 to +50 */ + drwav_int8 gainDecibels; /* -64 to +64 */ + drwav_int8 lowNote; /* 0 to 127 */ + drwav_int8 highNote; /* 0 to 127 */ + drwav_int8 lowVelocity; /* 1 to 127 */ + drwav_int8 highVelocity; /* 1 to 127 */ +} drwav_inst; + +/* +Cue Metadata + +Cue points are markers at specific points in the audio. They often come with an associated piece of +drwav_list_label_or_note metadata which contains the text for the marker. +*/ +typedef struct +{ + /* Unique identification value. */ + drwav_uint32 id; + + /* Set to 0. This is only relevant if there is a 'playlist' chunk - which is not supported by dr_wav. */ + drwav_uint32 playOrderPosition; + + /* Should always be "data". This represents the fourcc value of the chunk that this cue point corresponds to. dr_wav only supports a single data chunk so this should always be "data". */ + drwav_uint8 dataChunkId[4]; + + /* Set to 0. This is only relevant if there is a wave list chunk. dr_wav, like lots of readers/writers, do not support this. */ + drwav_uint32 chunkStart; + + /* Set to 0 for uncompressed formats. Else the last byte in compressed wave data where decompression can begin to find the value of the corresponding sample value. */ + drwav_uint32 blockStart; + + /* For uncompressed formats this is the byte offset of the cue point into the audio data. For compressed formats this is relative to the block specified with blockStart. */ + drwav_uint32 sampleByteOffset; +} drwav_cue_point; + +typedef struct +{ + drwav_uint32 cuePointCount; + drwav_cue_point *pCuePoints; +} drwav_cue; + +/* +Acid Metadata + +This chunk contains some information about the time signature and the tempo of the audio. +*/ +typedef enum +{ + drwav_acid_flag_one_shot = 1, /* If this is not set, then it is a loop instead of a one-shot. */ + drwav_acid_flag_root_note_set = 2, + drwav_acid_flag_stretch = 4, + drwav_acid_flag_disk_based = 8, + drwav_acid_flag_acidizer = 16 /* Not sure what this means. */ +} drwav_acid_flag; + +typedef struct +{ + /* A bit-field, see drwav_acid_flag. */ + drwav_uint32 flags; + + /* Valid if flags contains drwav_acid_flag_root_note_set. It represents the MIDI root note the file - a value from 0 to 127. */ + drwav_uint16 midiUnityNote; + + /* Reserved values that should probably be ignored. reserved1 seems to often be 128 and reserved2 is 0. */ + drwav_uint16 reserved1; + float reserved2; + + /* Number of beats. */ + drwav_uint32 numBeats; + + /* The time signature of the audio. */ + drwav_uint16 meterDenominator; + drwav_uint16 meterNumerator; + + /* Beats per minute of the track. Setting a value of 0 suggests that there is no tempo. */ + float tempo; +} drwav_acid; + +/* +Cue Label or Note metadata + +These are 2 different types of metadata, but they have the exact same format. Labels tend to be the +more common and represent a short name for a cue point. Notes might be used to represent a longer +comment. +*/ +typedef struct +{ + /* The ID of a cue point that this label or note corresponds to. */ + drwav_uint32 cuePointId; + + /* Size of the string not including any null terminator. */ + drwav_uint32 stringLength; + + /* The string. The *init_with_metadata functions null terminate this for convenience. */ + char* pString; +} drwav_list_label_or_note; + +/* +BEXT metadata, also known as Broadcast Wave Format (BWF) + +This metadata adds some extra description to an audio file. You must check the version field to +determine if the UMID or the loudness fields are valid. +*/ +typedef struct +{ + /* + These top 3 fields, and the umid field are actually defined in the standard as a statically + sized buffers. In order to reduce the size of this struct (and therefore the union in the + metadata struct), we instead store these as pointers. + */ + char* pDescription; /* Can be NULL or a null-terminated string, must be <= 256 characters. */ + char* pOriginatorName; /* Can be NULL or a null-terminated string, must be <= 32 characters. */ + char* pOriginatorReference; /* Can be NULL or a null-terminated string, must be <= 32 characters. */ + char pOriginationDate[10]; /* ASCII "yyyy:mm:dd". */ + char pOriginationTime[8]; /* ASCII "hh:mm:ss". */ + drwav_uint64 timeReference; /* First sample count since midnight. */ + drwav_uint16 version; /* Version of the BWF, check this to see if the fields below are valid. */ + + /* + Unrestricted ASCII characters containing a collection of strings terminated by CR/LF. Each + string shall contain a description of a coding process applied to the audio data. + */ + char* pCodingHistory; + drwav_uint32 codingHistorySize; + + /* Fields below this point are only valid if the version is 1 or above. */ + drwav_uint8* pUMID; /* Exactly 64 bytes of SMPTE UMID */ + + /* Fields below this point are only valid if the version is 2 or above. */ + drwav_uint16 loudnessValue; /* Integrated Loudness Value of the file in LUFS (multiplied by 100). */ + drwav_uint16 loudnessRange; /* Loudness Range of the file in LU (multiplied by 100). */ + drwav_uint16 maxTruePeakLevel; /* Maximum True Peak Level of the file expressed as dBTP (multiplied by 100). */ + drwav_uint16 maxMomentaryLoudness; /* Highest value of the Momentary Loudness Level of the file in LUFS (multiplied by 100). */ + drwav_uint16 maxShortTermLoudness; /* Highest value of the Short-Term Loudness Level of the file in LUFS (multiplied by 100). */ +} drwav_bext; + +/* +Info Text Metadata + +There a many different types of information text that can be saved in this format. This is where +things like the album name, the artists, the year it was produced, etc are saved. See +drwav_metadata_type for the full list of types that dr_wav supports. +*/ +typedef struct +{ + /* Size of the string not including any null terminator. */ + drwav_uint32 stringLength; + + /* The string. The *init_with_metadata functions null terminate this for convenience. */ + char* pString; +} drwav_list_info_text; + +/* +Labelled Cue Region Metadata + +The labelled cue region metadata is used to associate some region of audio with text. The region +starts at a cue point, and extends for the given number of samples. +*/ +typedef struct +{ + /* The ID of a cue point that this object corresponds to. */ + drwav_uint32 cuePointId; + + /* The number of samples from the cue point forwards that should be considered this region */ + drwav_uint32 sampleLength; + + /* Four characters used to say what the purpose of this region is. */ + drwav_uint8 purposeId[4]; + + /* Unsure of the exact meanings of these. It appears to be acceptable to set them all to 0. */ + drwav_uint16 country; + drwav_uint16 language; + drwav_uint16 dialect; + drwav_uint16 codePage; + + /* Size of the string not including any null terminator. */ + drwav_uint32 stringLength; + + /* The string. The *init_with_metadata functions null terminate this for convenience. */ + char* pString; +} drwav_list_labelled_cue_region; + +/* +Unknown Metadata + +This chunk just represents a type of chunk that dr_wav does not understand. + +Unknown metadata has a location attached to it. This is because wav files can have a LIST chunk +that contains subchunks. These LIST chunks can be one of two types. An adtl list, or an INFO +list. This enum is used to specify the location of a chunk that dr_wav currently doesn't support. +*/ +typedef enum +{ + drwav_metadata_location_invalid, + drwav_metadata_location_top_level, + drwav_metadata_location_inside_info_list, + drwav_metadata_location_inside_adtl_list +} drwav_metadata_location; + +typedef struct +{ + drwav_uint8 id[4]; + drwav_metadata_location chunkLocation; + drwav_uint32 dataSizeInBytes; + drwav_uint8* pData; +} drwav_unknown_metadata; + +/* +Metadata is saved as a union of all the supported types. +*/ +typedef struct +{ + /* Determines which item in the union is valid. */ + drwav_metadata_type type; + + union + { + drwav_cue cue; + drwav_smpl smpl; + drwav_acid acid; + drwav_inst inst; + drwav_bext bext; + drwav_list_label_or_note labelOrNote; /* List label or list note. */ + drwav_list_labelled_cue_region labelledCueRegion; + drwav_list_info_text infoText; /* Any of the list info types. */ + drwav_unknown_metadata unknown; + } data; +} drwav_metadata; + +typedef struct +{ + /* A pointer to the function to call when more data is needed. */ + drwav_read_proc onRead; + + /* A pointer to the function to call when data needs to be written. Only used when the drwav object is opened in write mode. */ + drwav_write_proc onWrite; + + /* A pointer to the function to call when the wav file needs to be seeked. */ + drwav_seek_proc onSeek; + + /* The user data to pass to callbacks. */ + void* pUserData; + + /* Allocation callbacks. */ + drwav_allocation_callbacks allocationCallbacks; + + + /* Whether or not the WAV file is formatted as a standard RIFF file or W64. */ + drwav_container container; + + + /* Structure containing format information exactly as specified by the wav file. */ + drwav_fmt fmt; + + /* The sample rate. Will be set to something like 44100. */ + drwav_uint32 sampleRate; + + /* The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. */ + drwav_uint16 channels; + + /* The bits per sample. Will be set to something like 16, 24, etc. */ + drwav_uint16 bitsPerSample; + + /* Equal to fmt.formatTag, or the value specified by fmt.subFormat if fmt.formatTag is equal to 65534 (WAVE_FORMAT_EXTENSIBLE). */ + drwav_uint16 translatedFormatTag; + + /* The total number of PCM frames making up the audio data. */ + drwav_uint64 totalPCMFrameCount; + + + /* The size in bytes of the data chunk. */ + drwav_uint64 dataChunkDataSize; + + /* The position in the stream of the first data byte of the data chunk. This is used for seeking. */ + drwav_uint64 dataChunkDataPos; + + /* The number of bytes remaining in the data chunk. */ + drwav_uint64 bytesRemaining; + + /* The current read position in PCM frames. */ + drwav_uint64 readCursorInPCMFrames; + + + /* + Only used in sequential write mode. Keeps track of the desired size of the "data" chunk at the point of initialization time. Always + set to 0 for non-sequential writes and when the drwav object is opened in read mode. Used for validation. + */ + drwav_uint64 dataChunkDataSizeTargetWrite; + + /* Keeps track of whether or not the wav writer was initialized in sequential mode. */ + drwav_bool32 isSequentialWrite; + + + /* A array of metadata. This is valid after the *init_with_metadata call returns. It will be valid until drwav_uninit() is called. You can take ownership of this data with drwav_take_ownership_of_metadata(). */ + drwav_metadata* pMetadata; + drwav_uint32 metadataCount; + + + /* A hack to avoid a DRWAV_MALLOC() when opening a decoder with drwav_init_memory(). */ + drwav__memory_stream memoryStream; + drwav__memory_stream_write memoryStreamWrite; + + + /* Microsoft ADPCM specific data. */ + struct + { + drwav_uint32 bytesRemainingInBlock; + drwav_uint16 predictor[2]; + drwav_int32 delta[2]; + drwav_int32 cachedFrames[4]; /* Samples are stored in this cache during decoding. */ + drwav_uint32 cachedFrameCount; + drwav_int32 prevFrames[2][2]; /* The previous 2 samples for each channel (2 channels at most). */ + } msadpcm; + + /* IMA ADPCM specific data. */ + struct + { + drwav_uint32 bytesRemainingInBlock; + drwav_int32 predictor[2]; + drwav_int32 stepIndex[2]; + drwav_int32 cachedFrames[16]; /* Samples are stored in this cache during decoding. */ + drwav_uint32 cachedFrameCount; + } ima; + + /* AIFF specific data. */ + struct + { + drwav_bool8 isLE; /* Will be set to true if the audio data is little-endian encoded. */ + drwav_bool8 isUnsigned; /* Only used for 8-bit samples. When set to true, will be treated as unsigned. */ + } aiff; +} drwav; + + +/* +Initializes a pre-allocated drwav object for reading. + +pWav [out] A pointer to the drwav object being initialized. +onRead [in] The function to call when data needs to be read from the client. +onSeek [in] The function to call when the read position of the client data needs to move. +onChunk [in, optional] The function to call when a chunk is enumerated at initialized time. +pUserData, pReadSeekUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. +pChunkUserData [in, optional] A pointer to application defined data that will be passed to onChunk. +flags [in, optional] A set of flags for controlling how things are loaded. + +Returns true if successful; false otherwise. + +Close the loader with drwav_uninit(). + +This is the lowest level function for initializing a WAV file. You can also use drwav_init_file() and drwav_init_memory() +to open the stream from a file or from a block of memory respectively. + +Possible values for flags: + DRWAV_SEQUENTIAL: Never perform a backwards seek while loading. This disables the chunk callback and will cause this function + to return as soon as the data chunk is found. Any chunks after the data chunk will be ignored. + +drwav_init() is equivalent to "drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0);". + +The onChunk callback is not called for the WAVE or FMT chunks. The contents of the FMT chunk can be read from pWav->fmt +after the function returns. + +See also: drwav_init_file(), drwav_init_memory(), drwav_uninit() +*/ +DRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_with_metadata(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); + +/* +Initializes a pre-allocated drwav object for writing. + +onWrite [in] The function to call when data needs to be written. +onSeek [in] The function to call when the write position needs to move. +pUserData [in, optional] A pointer to application defined data that will be passed to onWrite and onSeek. +metadata, numMetadata [in, optional] An array of metadata objects that should be written to the file. The array is not edited. You are responsible for this metadata memory and it must maintain valid until drwav_uninit() is called. + +Returns true if successful; false otherwise. + +Close the writer with drwav_uninit(). + +This is the lowest level function for initializing a WAV file. You can also use drwav_init_file_write() and drwav_init_memory_write() +to open the stream from a file or from a block of memory respectively. + +If the total sample count is known, you can use drwav_init_write_sequential(). This avoids the need for dr_wav to perform +a post-processing step for storing the total sample count and the size of the data chunk which requires a backwards seek. + +See also: drwav_init_file_write(), drwav_init_memory_write(), drwav_uninit() +*/ +DRWAV_API drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_write_sequential_pcm_frames(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_write_with_metadata(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks, drwav_metadata* pMetadata, drwav_uint32 metadataCount); + +/* +Utility function to determine the target size of the entire data to be written (including all headers and chunks). + +Returns the target size in bytes. + +The metadata argument can be NULL meaning no metadata exists. + +Useful if the application needs to know the size to allocate. + +Only writing to the RIFF chunk and one data chunk is currently supported. + +See also: drwav_init_write(), drwav_init_file_write(), drwav_init_memory_write() +*/ +DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pFormat, drwav_uint64 totalFrameCount, drwav_metadata* pMetadata, drwav_uint32 metadataCount); + +/* +Take ownership of the metadata objects that were allocated via one of the init_with_metadata() function calls. The init_with_metdata functions perform a single heap allocation for this metadata. + +Useful if you want the data to persist beyond the lifetime of the drwav object. + +You must free the data returned from this function using drwav_free(). +*/ +DRWAV_API drwav_metadata* drwav_take_ownership_of_metadata(drwav* pWav); + +/* +Uninitializes the given drwav object. + +Use this only for objects initialized with drwav_init*() functions (drwav_init(), drwav_init_ex(), drwav_init_write(), drwav_init_write_sequential()). +*/ +DRWAV_API drwav_result drwav_uninit(drwav* pWav); + + +/* +Reads raw audio data. + +This is the lowest level function for reading audio data. It simply reads the given number of +bytes of the raw internal sample data. + +Consider using drwav_read_pcm_frames_s16(), drwav_read_pcm_frames_s32() or drwav_read_pcm_frames_f32() for +reading sample data in a consistent format. + +pBufferOut can be NULL in which case a seek will be performed. + +Returns the number of bytes actually read. +*/ +DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut); + +/* +Reads up to the specified number of PCM frames from the WAV file. + +The output data will be in the file's internal format, converted to native-endian byte order. Use +drwav_read_pcm_frames_s16/f32/s32() to read data in a specific format. + +If the return value is less than it means the end of the file has been reached or +you have requested more PCM frames than can possibly fit in the output buffer. + +This function will only work when sample data is of a fixed size and uncompressed. If you are +using a compressed format consider using drwav_read_raw() or drwav_read_pcm_frames_s16/s32/f32(). + +pBufferOut can be NULL in which case a seek will be performed. +*/ +DRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); + +/* +Seeks to the given PCM frame. + +Returns true if successful; false otherwise. +*/ +DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex); + +/* +Retrieves the current read position in pcm frames. +*/ +DRWAV_API drwav_result drwav_get_cursor_in_pcm_frames(drwav* pWav, drwav_uint64* pCursor); + +/* +Retrieves the length of the file. +*/ +DRWAV_API drwav_result drwav_get_length_in_pcm_frames(drwav* pWav, drwav_uint64* pLength); + + +/* +Writes raw audio data. + +Returns the number of bytes actually written. If this differs from bytesToWrite, it indicates an error. +*/ +DRWAV_API size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData); + +/* +Writes PCM frames. + +Returns the number of PCM frames written. + +Input samples need to be in native-endian byte order. On big-endian architectures the input data will be converted to +little-endian. Use drwav_write_raw() to write raw audio data without performing any conversion. +*/ +DRWAV_API drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); +DRWAV_API drwav_uint64 drwav_write_pcm_frames_le(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); +DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); + +/* Conversion Utilities */ +#ifndef DR_WAV_NO_CONVERSION_API + +/* +Reads a chunk of audio data and converts it to signed 16-bit PCM samples. + +pBufferOut can be NULL in which case a seek will be performed. + +Returns the number of PCM frames actually read. + +If the return value is less than it means the end of the file has been reached. +*/ +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16le(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16be(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); + +/* Low-level function for converting unsigned 8-bit PCM samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 24-bit PCM samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 32-bit PCM samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 32-bit floating point samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 64-bit floating point samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount); + +/* Low-level function for converting A-law samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting u-law samples to signed 16-bit PCM samples. */ +DRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + + +/* +Reads a chunk of audio data and converts it to IEEE 32-bit floating point samples. + +pBufferOut can be NULL in which case a seek will be performed. + +Returns the number of PCM frames actually read. + +If the return value is less than it means the end of the file has been reached. +*/ +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32be(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); + +/* Low-level function for converting unsigned 8-bit PCM samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 16-bit PCM samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount); + +/* Low-level function for converting signed 24-bit PCM samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 32-bit PCM samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 64-bit floating point samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount); + +/* Low-level function for converting A-law samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting u-law samples to IEEE 32-bit floating point samples. */ +DRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + + +/* +Reads a chunk of audio data and converts it to signed 32-bit PCM samples. + +pBufferOut can be NULL in which case a seek will be performed. + +Returns the number of PCM frames actually read. + +If the return value is less than it means the end of the file has been reached. +*/ +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32be(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); + +/* Low-level function for converting unsigned 8-bit PCM samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting signed 16-bit PCM samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount); + +/* Low-level function for converting signed 24-bit PCM samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 32-bit floating point samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount); + +/* Low-level function for converting IEEE 64-bit floating point samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount); + +/* Low-level function for converting A-law samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +/* Low-level function for converting u-law samples to signed 32-bit PCM samples. */ +DRWAV_API void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +#endif /* DR_WAV_NO_CONVERSION_API */ + + +/* High-Level Convenience Helpers */ + +#ifndef DR_WAV_NO_STDIO +/* +Helper for initializing a wave file for reading using stdio. + +This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav +objects because the operating system may restrict the number of file handles an application can have open at +any given time. +*/ +DRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_w(drwav* pWav, const wchar_t* filename, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_ex_w(drwav* pWav, const wchar_t* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_with_metadata(drwav* pWav, const char* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_with_metadata_w(drwav* pWav, const wchar_t* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); + + +/* +Helper for initializing a wave file for writing using stdio. + +This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav +objects because the operating system may restrict the number of file handles an application can have open at +any given time. +*/ +DRWAV_API drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); +#endif /* DR_WAV_NO_STDIO */ + +/* +Helper for initializing a loader from a pre-allocated memory buffer. + +This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for +the lifetime of the drwav object. + +The buffer should contain the contents of the entire wave file, not just the sample data. +*/ +DRWAV_API drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_memory_with_metadata(drwav* pWav, const void* data, size_t dataSize, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); + +/* +Helper for initializing a writer which outputs data to a memory buffer. + +dr_wav will manage the memory allocations, however it is up to the caller to free the data with drwav_free(). + +The buffer will remain allocated even after drwav_uninit() is called. The buffer should not be considered valid +until after drwav_uninit() has been called. +*/ +DRWAV_API drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_bool32 drwav_init_memory_write_sequential_pcm_frames(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); + + +#ifndef DR_WAV_NO_CONVERSION_API +/* +Opens and reads an entire wav file in a single operation. + +The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. +*/ +DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +#ifndef DR_WAV_NO_STDIO +/* +Opens and decodes an entire wav file in a single operation. + +The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. +*/ +DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +#endif +/* +Opens and decodes an entire wav file from a block of memory in a single operation. + +The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. +*/ +DRWAV_API drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +DRWAV_API drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); +#endif + +/* Frees data that was allocated internally by dr_wav. */ +DRWAV_API void drwav_free(void* p, const drwav_allocation_callbacks* pAllocationCallbacks); + +/* Converts bytes from a wav stream to a sized type of native endian. */ +DRWAV_API drwav_uint16 drwav_bytes_to_u16(const drwav_uint8* data); +DRWAV_API drwav_int16 drwav_bytes_to_s16(const drwav_uint8* data); +DRWAV_API drwav_uint32 drwav_bytes_to_u32(const drwav_uint8* data); +DRWAV_API drwav_int32 drwav_bytes_to_s32(const drwav_uint8* data); +DRWAV_API drwav_uint64 drwav_bytes_to_u64(const drwav_uint8* data); +DRWAV_API drwav_int64 drwav_bytes_to_s64(const drwav_uint8* data); +DRWAV_API float drwav_bytes_to_f32(const drwav_uint8* data); + +/* Compares a GUID for the purpose of checking the type of a Wave64 chunk. */ +DRWAV_API drwav_bool32 drwav_guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]); + +/* Compares a four-character-code for the purpose of checking the type of a RIFF chunk. */ +DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b); + +#ifdef __cplusplus +} +#endif +#endif /* dr_wav_h */ + + +/************************************************************************************************************************************************************ + ************************************************************************************************************************************************************ + + IMPLEMENTATION + + ************************************************************************************************************************************************************ + ************************************************************************************************************************************************************/ +#if defined(DR_WAV_IMPLEMENTATION) || defined(DRWAV_IMPLEMENTATION) +#ifndef dr_wav_c +#define dr_wav_c + +#ifdef __MRC__ +/* MrC currently doesn't compile dr_wav correctly with any optimizations enabled. */ +#pragma options opt off +#endif + +#include +#include +#include /* For INT_MAX */ + +#ifndef DR_WAV_NO_STDIO +#include +#ifndef DR_WAV_NO_WCHAR +#include +#endif +#endif + +/* Standard library stuff. */ +#ifndef DRWAV_ASSERT +#include +#define DRWAV_ASSERT(expression) assert(expression) +#endif +#ifndef DRWAV_MALLOC +#define DRWAV_MALLOC(sz) malloc((sz)) +#endif +#ifndef DRWAV_REALLOC +#define DRWAV_REALLOC(p, sz) realloc((p), (sz)) +#endif +#ifndef DRWAV_FREE +#define DRWAV_FREE(p) free((p)) +#endif +#ifndef DRWAV_COPY_MEMORY +#define DRWAV_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#ifndef DRWAV_ZERO_MEMORY +#define DRWAV_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif +#ifndef DRWAV_ZERO_OBJECT +#define DRWAV_ZERO_OBJECT(p) DRWAV_ZERO_MEMORY((p), sizeof(*p)) +#endif + +#define drwav_countof(x) (sizeof(x) / sizeof(x[0])) +#define drwav_align(x, a) ((((x) + (a) - 1) / (a)) * (a)) +#define drwav_min(a, b) (((a) < (b)) ? (a) : (b)) +#define drwav_max(a, b) (((a) > (b)) ? (a) : (b)) +#define drwav_clamp(x, lo, hi) (drwav_max((lo), drwav_min((hi), (x)))) +#define drwav_offset_ptr(p, offset) (((drwav_uint8*)(p)) + (offset)) + +#define DRWAV_MAX_SIMD_VECTOR_SIZE 32 + +/* Architecture Detection */ +#if defined(__x86_64__) || defined(_M_X64) + #define DRWAV_X64 +#elif defined(__i386) || defined(_M_IX86) + #define DRWAV_X86 +#elif defined(__arm__) || defined(_M_ARM) + #define DRWAV_ARM +#endif +/* End Architecture Detection */ + +/* Inline */ +#ifdef _MSC_VER + #define DRWAV_INLINE __forceinline +#elif defined(__GNUC__) + /* + I've had a bug report where GCC is emitting warnings about functions possibly not being inlineable. This warning happens when + the __attribute__((always_inline)) attribute is defined without an "inline" statement. I think therefore there must be some + case where "__inline__" is not always defined, thus the compiler emitting these warnings. When using -std=c89 or -ansi on the + command line, we cannot use the "inline" keyword and instead need to use "__inline__". In an attempt to work around this issue + I am using "__inline__" only when we're compiling in strict ANSI mode. + */ + #if defined(__STRICT_ANSI__) + #define DRWAV_GNUC_INLINE_HINT __inline__ + #else + #define DRWAV_GNUC_INLINE_HINT inline + #endif + + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define DRWAV_INLINE DRWAV_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define DRWAV_INLINE DRWAV_GNUC_INLINE_HINT + #endif +#elif defined(__WATCOMC__) + #define DRWAV_INLINE __inline +#else + #define DRWAV_INLINE +#endif +/* End Inline */ + +/* SIZE_MAX */ +#if defined(SIZE_MAX) + #define DRWAV_SIZE_MAX SIZE_MAX +#else + #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) + #define DRWAV_SIZE_MAX ((drwav_uint64)0xFFFFFFFFFFFFFFFF) + #else + #define DRWAV_SIZE_MAX 0xFFFFFFFF + #endif +#endif +/* End SIZE_MAX */ + +/* Weird bit manipulation is for C89 compatibility (no direct support for 64-bit integers). */ +#define DRWAV_INT64_MIN ((drwav_int64) ((drwav_uint64)0x80000000 << 32)) +#define DRWAV_INT64_MAX ((drwav_int64)(((drwav_uint64)0x7FFFFFFF << 32) | 0xFFFFFFFF)) + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + #define DRWAV_HAS_BYTESWAP16_INTRINSIC + #define DRWAV_HAS_BYTESWAP32_INTRINSIC + #define DRWAV_HAS_BYTESWAP64_INTRINSIC +#elif defined(__clang__) + #if defined(__has_builtin) + #if __has_builtin(__builtin_bswap16) + #define DRWAV_HAS_BYTESWAP16_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap32) + #define DRWAV_HAS_BYTESWAP32_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap64) + #define DRWAV_HAS_BYTESWAP64_INTRINSIC + #endif + #endif +#elif defined(__GNUC__) + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) + #define DRWAV_HAS_BYTESWAP32_INTRINSIC + #define DRWAV_HAS_BYTESWAP64_INTRINSIC + #endif + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #define DRWAV_HAS_BYTESWAP16_INTRINSIC + #endif +#endif + +DRWAV_API void drwav_version(drwav_uint32* pMajor, drwav_uint32* pMinor, drwav_uint32* pRevision) +{ + if (pMajor) { + *pMajor = DRWAV_VERSION_MAJOR; + } + + if (pMinor) { + *pMinor = DRWAV_VERSION_MINOR; + } + + if (pRevision) { + *pRevision = DRWAV_VERSION_REVISION; + } +} + +DRWAV_API const char* drwav_version_string(void) +{ + return DRWAV_VERSION_STRING; +} + +/* +These limits are used for basic validation when initializing the decoder. If you exceed these limits, first of all: what on Earth are +you doing?! (Let me know, I'd be curious!) Second, you can adjust these by #define-ing them before the dr_wav implementation. +*/ +#ifndef DRWAV_MAX_SAMPLE_RATE +#define DRWAV_MAX_SAMPLE_RATE 384000 +#endif +#ifndef DRWAV_MAX_CHANNELS +#define DRWAV_MAX_CHANNELS 256 +#endif +#ifndef DRWAV_MAX_BITS_PER_SAMPLE +#define DRWAV_MAX_BITS_PER_SAMPLE 64 +#endif + +static const drwav_uint8 drwavGUID_W64_RIFF[16] = {0x72,0x69,0x66,0x66, 0x2E,0x91, 0xCF,0x11, 0xA5,0xD6, 0x28,0xDB,0x04,0xC1,0x00,0x00}; /* 66666972-912E-11CF-A5D6-28DB04C10000 */ +static const drwav_uint8 drwavGUID_W64_WAVE[16] = {0x77,0x61,0x76,0x65, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 65766177-ACF3-11D3-8CD1-00C04F8EDB8A */ +/*static const drwav_uint8 drwavGUID_W64_JUNK[16] = {0x6A,0x75,0x6E,0x6B, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A};*/ /* 6B6E756A-ACF3-11D3-8CD1-00C04F8EDB8A */ +static const drwav_uint8 drwavGUID_W64_FMT [16] = {0x66,0x6D,0x74,0x20, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 20746D66-ACF3-11D3-8CD1-00C04F8EDB8A */ +static const drwav_uint8 drwavGUID_W64_FACT[16] = {0x66,0x61,0x63,0x74, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 74636166-ACF3-11D3-8CD1-00C04F8EDB8A */ +static const drwav_uint8 drwavGUID_W64_DATA[16] = {0x64,0x61,0x74,0x61, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 61746164-ACF3-11D3-8CD1-00C04F8EDB8A */ +/*static const drwav_uint8 drwavGUID_W64_SMPL[16] = {0x73,0x6D,0x70,0x6C, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A};*/ /* 6C706D73-ACF3-11D3-8CD1-00C04F8EDB8A */ + + +static DRWAV_INLINE int drwav__is_little_endian(void) +{ +#if defined(DRWAV_X86) || defined(DRWAV_X64) + return DRWAV_TRUE; +#elif defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN + return DRWAV_TRUE; +#else + int n = 1; + return (*(char*)&n) == 1; +#endif +} + + +static DRWAV_INLINE void drwav_bytes_to_guid(const drwav_uint8* data, drwav_uint8* guid) +{ + int i; + for (i = 0; i < 16; ++i) { + guid[i] = data[i]; + } +} + + +static DRWAV_INLINE drwav_uint16 drwav__bswap16(drwav_uint16 n) +{ +#ifdef DRWAV_HAS_BYTESWAP16_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_ushort(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap16(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & 0xFF00) >> 8) | + ((n & 0x00FF) << 8); +#endif +} + +static DRWAV_INLINE drwav_uint32 drwav__bswap32(drwav_uint32 n) +{ +#ifdef DRWAV_HAS_BYTESWAP32_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_ulong(n); + #elif defined(__GNUC__) || defined(__clang__) + #if defined(DRWAV_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(DRWAV_64BIT) /* <-- 64-bit inline assembly has not been tested, so disabling for now. */ + /* Inline assembly optimized implementation for ARM. In my testing, GCC does not generate optimized code with __builtin_bswap32(). */ + drwav_uint32 r; + __asm__ __volatile__ ( + #if defined(DRWAV_64BIT) + "rev %w[out], %w[in]" : [out]"=r"(r) : [in]"r"(n) /* <-- This is untested. If someone in the community could test this, that would be appreciated! */ + #else + "rev %[out], %[in]" : [out]"=r"(r) : [in]"r"(n) + #endif + ); + return r; + #else + return __builtin_bswap32(n); + #endif + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & 0xFF000000) >> 24) | + ((n & 0x00FF0000) >> 8) | + ((n & 0x0000FF00) << 8) | + ((n & 0x000000FF) << 24); +#endif +} + +static DRWAV_INLINE drwav_uint64 drwav__bswap64(drwav_uint64 n) +{ +#ifdef DRWAV_HAS_BYTESWAP64_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_uint64(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap64(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ + return ((n & ((drwav_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drwav_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drwav_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drwav_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drwav_uint64)0xFF000000 )) << 8) | + ((n & ((drwav_uint64)0x00FF0000 )) << 24) | + ((n & ((drwav_uint64)0x0000FF00 )) << 40) | + ((n & ((drwav_uint64)0x000000FF )) << 56); +#endif +} + + +static DRWAV_INLINE drwav_int16 drwav__bswap_s16(drwav_int16 n) +{ + return (drwav_int16)drwav__bswap16((drwav_uint16)n); +} + +static DRWAV_INLINE void drwav__bswap_samples_s16(drwav_int16* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamples[iSample] = drwav__bswap_s16(pSamples[iSample]); + } +} + + +static DRWAV_INLINE void drwav__bswap_s24(drwav_uint8* p) +{ + drwav_uint8 t; + t = p[0]; + p[0] = p[2]; + p[2] = t; +} + +static DRWAV_INLINE void drwav__bswap_samples_s24(drwav_uint8* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + drwav_uint8* pSample = pSamples + (iSample*3); + drwav__bswap_s24(pSample); + } +} + + +static DRWAV_INLINE drwav_int32 drwav__bswap_s32(drwav_int32 n) +{ + return (drwav_int32)drwav__bswap32((drwav_uint32)n); +} + +static DRWAV_INLINE void drwav__bswap_samples_s32(drwav_int32* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamples[iSample] = drwav__bswap_s32(pSamples[iSample]); + } +} + + +static DRWAV_INLINE drwav_int64 drwav__bswap_s64(drwav_int64 n) +{ + return (drwav_int64)drwav__bswap64((drwav_uint64)n); +} + +static DRWAV_INLINE void drwav__bswap_samples_s64(drwav_int64* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamples[iSample] = drwav__bswap_s64(pSamples[iSample]); + } +} + + +static DRWAV_INLINE float drwav__bswap_f32(float n) +{ + union { + drwav_uint32 i; + float f; + } x; + x.f = n; + x.i = drwav__bswap32(x.i); + + return x.f; +} + +static DRWAV_INLINE void drwav__bswap_samples_f32(float* pSamples, drwav_uint64 sampleCount) +{ + drwav_uint64 iSample; + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamples[iSample] = drwav__bswap_f32(pSamples[iSample]); + } +} + + +static DRWAV_INLINE void drwav__bswap_samples(void* pSamples, drwav_uint64 sampleCount, drwav_uint32 bytesPerSample) +{ + switch (bytesPerSample) + { + case 1: + { + /* No-op. */ + } break; + case 2: + { + drwav__bswap_samples_s16((drwav_int16*)pSamples, sampleCount); + } break; + case 3: + { + drwav__bswap_samples_s24((drwav_uint8*)pSamples, sampleCount); + } break; + case 4: + { + drwav__bswap_samples_s32((drwav_int32*)pSamples, sampleCount); + } break; + case 8: + { + drwav__bswap_samples_s64((drwav_int64*)pSamples, sampleCount); + } break; + default: + { + /* Unsupported format. */ + DRWAV_ASSERT(DRWAV_FALSE); + } break; + } +} + + + +DRWAV_PRIVATE DRWAV_INLINE drwav_bool32 drwav_is_container_be(drwav_container container) +{ + if (container == drwav_container_rifx || container == drwav_container_aiff) { + return DRWAV_TRUE; + } else { + return DRWAV_FALSE; + } +} + + +DRWAV_PRIVATE DRWAV_INLINE drwav_uint16 drwav_bytes_to_u16_le(const drwav_uint8* data) +{ + return ((drwav_uint16)data[0] << 0) | ((drwav_uint16)data[1] << 8); +} + +DRWAV_PRIVATE DRWAV_INLINE drwav_uint16 drwav_bytes_to_u16_be(const drwav_uint8* data) +{ + return ((drwav_uint16)data[1] << 0) | ((drwav_uint16)data[0] << 8); +} + +DRWAV_PRIVATE DRWAV_INLINE drwav_uint16 drwav_bytes_to_u16_ex(const drwav_uint8* data, drwav_container container) +{ + if (drwav_is_container_be(container)) { + return drwav_bytes_to_u16_be(data); + } else { + return drwav_bytes_to_u16_le(data); + } +} + + +DRWAV_PRIVATE DRWAV_INLINE drwav_uint32 drwav_bytes_to_u32_le(const drwav_uint8* data) +{ + return ((drwav_uint32)data[0] << 0) | ((drwav_uint32)data[1] << 8) | ((drwav_uint32)data[2] << 16) | ((drwav_uint32)data[3] << 24); +} + +DRWAV_PRIVATE DRWAV_INLINE drwav_uint32 drwav_bytes_to_u32_be(const drwav_uint8* data) +{ + return ((drwav_uint32)data[3] << 0) | ((drwav_uint32)data[2] << 8) | ((drwav_uint32)data[1] << 16) | ((drwav_uint32)data[0] << 24); +} + +DRWAV_PRIVATE DRWAV_INLINE drwav_uint32 drwav_bytes_to_u32_ex(const drwav_uint8* data, drwav_container container) +{ + if (drwav_is_container_be(container)) { + return drwav_bytes_to_u32_be(data); + } else { + return drwav_bytes_to_u32_le(data); + } +} + + + +DRWAV_PRIVATE drwav_int64 drwav_aiff_extented_to_s64(const drwav_uint8* data) +{ + drwav_uint32 exponent = ((drwav_uint32)data[0] << 8) | data[1]; + drwav_uint64 hi = ((drwav_uint64)data[2] << 24) | ((drwav_uint64)data[3] << 16) | ((drwav_uint64)data[4] << 8) | ((drwav_uint64)data[5] << 0); + drwav_uint64 lo = ((drwav_uint64)data[6] << 24) | ((drwav_uint64)data[7] << 16) | ((drwav_uint64)data[8] << 8) | ((drwav_uint64)data[9] << 0); + drwav_uint64 significand = (hi << 32) | lo; + int sign = exponent >> 15; + + /* Remove sign bit. */ + exponent &= 0x7FFF; + + /* Special cases. */ + if (exponent == 0 && significand == 0) { + return 0; + } else if (exponent == 0x7FFF) { + return sign ? DRWAV_INT64_MIN : DRWAV_INT64_MAX; /* Infinite. */ + } + + exponent -= 16383; + + if (exponent > 63) { + return sign ? DRWAV_INT64_MIN : DRWAV_INT64_MAX; /* Too big for a 64-bit integer. */ + } else if (exponent < 1) { + return 0; /* Number is less than 1, so rounds down to 0. */ + } + + significand >>= (63 - exponent); + + if (sign) { + return -(drwav_int64)significand; + } else { + return (drwav_int64)significand; + } +} + + +DRWAV_PRIVATE void* drwav__malloc_default(size_t sz, void* pUserData) +{ + (void)pUserData; + return DRWAV_MALLOC(sz); +} + +DRWAV_PRIVATE void* drwav__realloc_default(void* p, size_t sz, void* pUserData) +{ + (void)pUserData; + return DRWAV_REALLOC(p, sz); +} + +DRWAV_PRIVATE void drwav__free_default(void* p, void* pUserData) +{ + (void)pUserData; + DRWAV_FREE(p); +} + + +DRWAV_PRIVATE void* drwav__malloc_from_callbacks(size_t sz, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } + + if (pAllocationCallbacks->onMalloc != NULL) { + return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); + } + + /* Try using realloc(). */ + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); + } + + return NULL; +} + +DRWAV_PRIVATE void* drwav__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } + + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); + } + + /* Try emulating realloc() in terms of malloc()/free(). */ + if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { + void* p2; + + p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); + if (p2 == NULL) { + return NULL; + } + + if (p != NULL) { + DRWAV_COPY_MEMORY(p2, p, szOld); + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } + + return p2; + } + + return NULL; +} + +DRWAV_PRIVATE void drwav__free_from_callbacks(void* p, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (p == NULL || pAllocationCallbacks == NULL) { + return; + } + + if (pAllocationCallbacks->onFree != NULL) { + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } +} + + +DRWAV_PRIVATE drwav_allocation_callbacks drwav_copy_allocation_callbacks_or_defaults(const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + /* Copy. */ + return *pAllocationCallbacks; + } else { + /* Defaults. */ + drwav_allocation_callbacks allocationCallbacks; + allocationCallbacks.pUserData = NULL; + allocationCallbacks.onMalloc = drwav__malloc_default; + allocationCallbacks.onRealloc = drwav__realloc_default; + allocationCallbacks.onFree = drwav__free_default; + return allocationCallbacks; + } +} + + +static DRWAV_INLINE drwav_bool32 drwav__is_compressed_format_tag(drwav_uint16 formatTag) +{ + return + formatTag == DR_WAVE_FORMAT_ADPCM || + formatTag == DR_WAVE_FORMAT_DVI_ADPCM; +} + +DRWAV_PRIVATE unsigned int drwav__chunk_padding_size_riff(drwav_uint64 chunkSize) +{ + return (unsigned int)(chunkSize % 2); +} + +DRWAV_PRIVATE unsigned int drwav__chunk_padding_size_w64(drwav_uint64 chunkSize) +{ + return (unsigned int)(chunkSize % 8); +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); + +DRWAV_PRIVATE drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut) +{ + if (container == drwav_container_riff || container == drwav_container_rifx || container == drwav_container_rf64 || container == drwav_container_aiff) { + drwav_uint8 sizeInBytes[4]; + + if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) { + return DRWAV_AT_END; + } + + if (onRead(pUserData, sizeInBytes, 4) != 4) { + return DRWAV_INVALID_FILE; + } + + pHeaderOut->sizeInBytes = drwav_bytes_to_u32_ex(sizeInBytes, container); + pHeaderOut->paddingSize = drwav__chunk_padding_size_riff(pHeaderOut->sizeInBytes); + + *pRunningBytesReadOut += 8; + } else if (container == drwav_container_w64) { + drwav_uint8 sizeInBytes[8]; + + if (onRead(pUserData, pHeaderOut->id.guid, 16) != 16) { + return DRWAV_AT_END; + } + + if (onRead(pUserData, sizeInBytes, 8) != 8) { + return DRWAV_INVALID_FILE; + } + + pHeaderOut->sizeInBytes = drwav_bytes_to_u64(sizeInBytes) - 24; /* <-- Subtract 24 because w64 includes the size of the header. */ + pHeaderOut->paddingSize = drwav__chunk_padding_size_w64(pHeaderOut->sizeInBytes); + *pRunningBytesReadOut += 24; + } else { + return DRWAV_INVALID_FILE; + } + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) +{ + drwav_uint64 bytesRemainingToSeek = offset; + while (bytesRemainingToSeek > 0) { + if (bytesRemainingToSeek > 0x7FFFFFFF) { + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + bytesRemainingToSeek -= 0x7FFFFFFF; + } else { + if (!onSeek(pUserData, (int)bytesRemainingToSeek, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + bytesRemainingToSeek = 0; + } + } + + return DRWAV_TRUE; +} + +DRWAV_PRIVATE drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) +{ + if (offset <= 0x7FFFFFFF) { + return onSeek(pUserData, (int)offset, drwav_seek_origin_start); + } + + /* Larger than 32-bit seek. */ + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_start)) { + return DRWAV_FALSE; + } + offset -= 0x7FFFFFFF; + + for (;;) { + if (offset <= 0x7FFFFFFF) { + return onSeek(pUserData, (int)offset, drwav_seek_origin_current); + } + + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + offset -= 0x7FFFFFFF; + } + + /* Should never get here. */ + /*return DRWAV_TRUE; */ +} + + + +DRWAV_PRIVATE size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) +{ + size_t bytesRead; + + DRWAV_ASSERT(onRead != NULL); + DRWAV_ASSERT(pCursor != NULL); + + bytesRead = onRead(pUserData, pBufferOut, bytesToRead); + *pCursor += bytesRead; + return bytesRead; +} + +#if 0 +DRWAV_PRIVATE drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int offset, drwav_seek_origin origin, drwav_uint64* pCursor) +{ + DRWAV_ASSERT(onSeek != NULL); + DRWAV_ASSERT(pCursor != NULL); + + if (!onSeek(pUserData, offset, origin)) { + return DRWAV_FALSE; + } + + if (origin == drwav_seek_origin_start) { + *pCursor = offset; + } else { + *pCursor += offset; + } + + return DRWAV_TRUE; +} +#endif + + +#define DRWAV_SMPL_BYTES 36 +#define DRWAV_SMPL_LOOP_BYTES 24 +#define DRWAV_INST_BYTES 7 +#define DRWAV_ACID_BYTES 24 +#define DRWAV_CUE_BYTES 4 +#define DRWAV_BEXT_BYTES 602 +#define DRWAV_BEXT_DESCRIPTION_BYTES 256 +#define DRWAV_BEXT_ORIGINATOR_NAME_BYTES 32 +#define DRWAV_BEXT_ORIGINATOR_REF_BYTES 32 +#define DRWAV_BEXT_RESERVED_BYTES 180 +#define DRWAV_BEXT_UMID_BYTES 64 +#define DRWAV_CUE_POINT_BYTES 24 +#define DRWAV_LIST_LABEL_OR_NOTE_BYTES 4 +#define DRWAV_LIST_LABELLED_TEXT_BYTES 20 + +#define DRWAV_METADATA_ALIGNMENT 8 + +typedef enum +{ + drwav__metadata_parser_stage_count, + drwav__metadata_parser_stage_read +} drwav__metadata_parser_stage; + +typedef struct +{ + drwav_read_proc onRead; + drwav_seek_proc onSeek; + void *pReadSeekUserData; + drwav__metadata_parser_stage stage; + drwav_metadata *pMetadata; + drwav_uint32 metadataCount; + drwav_uint8 *pData; + drwav_uint8 *pDataCursor; + drwav_uint64 metadataCursor; + drwav_uint64 extraCapacity; +} drwav__metadata_parser; + +DRWAV_PRIVATE size_t drwav__metadata_memory_capacity(drwav__metadata_parser* pParser) +{ + drwav_uint64 cap = sizeof(drwav_metadata) * (drwav_uint64)pParser->metadataCount + pParser->extraCapacity; + if (cap > DRWAV_SIZE_MAX) { + return 0; /* Too big. */ + } + + return (size_t)cap; /* Safe cast thanks to the check above. */ +} + +DRWAV_PRIVATE drwav_uint8* drwav__metadata_get_memory(drwav__metadata_parser* pParser, size_t size, size_t align) +{ + drwav_uint8* pResult; + + if (align) { + drwav_uintptr modulo = (drwav_uintptr)pParser->pDataCursor % align; + if (modulo != 0) { + pParser->pDataCursor += align - modulo; + } + } + + pResult = pParser->pDataCursor; + + /* + Getting to the point where this function is called means there should always be memory + available. Out of memory checks should have been done at an earlier stage. + */ + DRWAV_ASSERT((pResult + size) <= (pParser->pData + drwav__metadata_memory_capacity(pParser))); + + pParser->pDataCursor += size; + return pResult; +} + +DRWAV_PRIVATE void drwav__metadata_request_extra_memory_for_stage_2(drwav__metadata_parser* pParser, size_t bytes, size_t align) +{ + size_t extra = bytes + (align ? (align - 1) : 0); + pParser->extraCapacity += extra; +} + +DRWAV_PRIVATE drwav_result drwav__metadata_alloc(drwav__metadata_parser* pParser, drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pParser->extraCapacity != 0 || pParser->metadataCount != 0) { + pAllocationCallbacks->onFree(pParser->pData, pAllocationCallbacks->pUserData); + + pParser->pData = (drwav_uint8*)pAllocationCallbacks->onMalloc(drwav__metadata_memory_capacity(pParser), pAllocationCallbacks->pUserData); + pParser->pDataCursor = pParser->pData; + + if (pParser->pData == NULL) { + return DRWAV_OUT_OF_MEMORY; + } + + /* + We don't need to worry about specifying an alignment here because malloc always returns something + of suitable alignment. This also means pParser->pMetadata is all that we need to store in order + for us to free when we are done. + */ + pParser->pMetadata = (drwav_metadata*)drwav__metadata_get_memory(pParser, sizeof(drwav_metadata) * pParser->metadataCount, 1); + pParser->metadataCursor = 0; + } + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE size_t drwav__metadata_parser_read(drwav__metadata_parser* pParser, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) +{ + if (pCursor != NULL) { + return drwav__on_read(pParser->onRead, pParser->pReadSeekUserData, pBufferOut, bytesToRead, pCursor); + } else { + return pParser->onRead(pParser->pReadSeekUserData, pBufferOut, bytesToRead); + } +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata* pMetadata) +{ + drwav_uint8 smplHeaderData[DRWAV_SMPL_BYTES]; + drwav_uint64 totalBytesRead = 0; + size_t bytesJustRead; + + if (pMetadata == NULL) { + return 0; + } + + bytesJustRead = drwav__metadata_parser_read(pParser, smplHeaderData, sizeof(smplHeaderData), &totalBytesRead); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + DRWAV_ASSERT(pChunkHeader != NULL); + + if (pMetadata != NULL && bytesJustRead == sizeof(smplHeaderData)) { + drwav_uint32 iSampleLoop; + + pMetadata->type = drwav_metadata_type_smpl; + pMetadata->data.smpl.manufacturerId = drwav_bytes_to_u32(smplHeaderData + 0); + pMetadata->data.smpl.productId = drwav_bytes_to_u32(smplHeaderData + 4); + pMetadata->data.smpl.samplePeriodNanoseconds = drwav_bytes_to_u32(smplHeaderData + 8); + pMetadata->data.smpl.midiUnityNote = drwav_bytes_to_u32(smplHeaderData + 12); + pMetadata->data.smpl.midiPitchFraction = drwav_bytes_to_u32(smplHeaderData + 16); + pMetadata->data.smpl.smpteFormat = drwav_bytes_to_u32(smplHeaderData + 20); + pMetadata->data.smpl.smpteOffset = drwav_bytes_to_u32(smplHeaderData + 24); + pMetadata->data.smpl.sampleLoopCount = drwav_bytes_to_u32(smplHeaderData + 28); + pMetadata->data.smpl.samplerSpecificDataSizeInBytes = drwav_bytes_to_u32(smplHeaderData + 32); + + /* + The loop count needs to be validated against the size of the chunk for safety so we don't + attempt to read over the boundary of the chunk. + */ + if (pMetadata->data.smpl.sampleLoopCount == (pChunkHeader->sizeInBytes - DRWAV_SMPL_BYTES) / DRWAV_SMPL_LOOP_BYTES) { + pMetadata->data.smpl.pLoops = (drwav_smpl_loop*)drwav__metadata_get_memory(pParser, sizeof(drwav_smpl_loop) * pMetadata->data.smpl.sampleLoopCount, DRWAV_METADATA_ALIGNMENT); + + for (iSampleLoop = 0; iSampleLoop < pMetadata->data.smpl.sampleLoopCount; ++iSampleLoop) { + drwav_uint8 smplLoopData[DRWAV_SMPL_LOOP_BYTES]; + bytesJustRead = drwav__metadata_parser_read(pParser, smplLoopData, sizeof(smplLoopData), &totalBytesRead); + + if (bytesJustRead == sizeof(smplLoopData)) { + pMetadata->data.smpl.pLoops[iSampleLoop].cuePointId = drwav_bytes_to_u32(smplLoopData + 0); + pMetadata->data.smpl.pLoops[iSampleLoop].type = drwav_bytes_to_u32(smplLoopData + 4); + pMetadata->data.smpl.pLoops[iSampleLoop].firstSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 8); + pMetadata->data.smpl.pLoops[iSampleLoop].lastSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 12); + pMetadata->data.smpl.pLoops[iSampleLoop].sampleFraction = drwav_bytes_to_u32(smplLoopData + 16); + pMetadata->data.smpl.pLoops[iSampleLoop].playCount = drwav_bytes_to_u32(smplLoopData + 20); + } else { + break; + } + } + + if (pMetadata->data.smpl.samplerSpecificDataSizeInBytes > 0) { + pMetadata->data.smpl.pSamplerSpecificData = drwav__metadata_get_memory(pParser, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, 1); + DRWAV_ASSERT(pMetadata->data.smpl.pSamplerSpecificData != NULL); + + drwav__metadata_parser_read(pParser, pMetadata->data.smpl.pSamplerSpecificData, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, &totalBytesRead); + } + } + } + + return totalBytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_cue_to_metadata_obj(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata* pMetadata) +{ + drwav_uint8 cueHeaderSectionData[DRWAV_CUE_BYTES]; + drwav_uint64 totalBytesRead = 0; + size_t bytesJustRead; + + if (pMetadata == NULL) { + return 0; + } + + bytesJustRead = drwav__metadata_parser_read(pParser, cueHeaderSectionData, sizeof(cueHeaderSectionData), &totalBytesRead); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesJustRead == sizeof(cueHeaderSectionData)) { + pMetadata->type = drwav_metadata_type_cue; + pMetadata->data.cue.cuePointCount = drwav_bytes_to_u32(cueHeaderSectionData); + + /* + We need to validate the cue point count against the size of the chunk so we don't read + beyond the chunk. + */ + if (pMetadata->data.cue.cuePointCount == (pChunkHeader->sizeInBytes - DRWAV_CUE_BYTES) / DRWAV_CUE_POINT_BYTES) { + pMetadata->data.cue.pCuePoints = (drwav_cue_point*)drwav__metadata_get_memory(pParser, sizeof(drwav_cue_point) * pMetadata->data.cue.cuePointCount, DRWAV_METADATA_ALIGNMENT); + DRWAV_ASSERT(pMetadata->data.cue.pCuePoints != NULL); + + if (pMetadata->data.cue.cuePointCount > 0) { + drwav_uint32 iCuePoint; + + for (iCuePoint = 0; iCuePoint < pMetadata->data.cue.cuePointCount; ++iCuePoint) { + drwav_uint8 cuePointData[DRWAV_CUE_POINT_BYTES]; + bytesJustRead = drwav__metadata_parser_read(pParser, cuePointData, sizeof(cuePointData), &totalBytesRead); + + if (bytesJustRead == sizeof(cuePointData)) { + pMetadata->data.cue.pCuePoints[iCuePoint].id = drwav_bytes_to_u32(cuePointData + 0); + pMetadata->data.cue.pCuePoints[iCuePoint].playOrderPosition = drwav_bytes_to_u32(cuePointData + 4); + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[0] = cuePointData[8]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[1] = cuePointData[9]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[2] = cuePointData[10]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[3] = cuePointData[11]; + pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart = drwav_bytes_to_u32(cuePointData + 12); + pMetadata->data.cue.pCuePoints[iCuePoint].blockStart = drwav_bytes_to_u32(cuePointData + 16); + pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset = drwav_bytes_to_u32(cuePointData + 20); + } else { + break; + } + } + } + } + } + + return totalBytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_inst_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata) +{ + drwav_uint8 instData[DRWAV_INST_BYTES]; + drwav_uint64 bytesRead; + + if (pMetadata == NULL) { + return 0; + } + + bytesRead = drwav__metadata_parser_read(pParser, instData, sizeof(instData), NULL); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesRead == sizeof(instData)) { + pMetadata->type = drwav_metadata_type_inst; + pMetadata->data.inst.midiUnityNote = (drwav_int8)instData[0]; + pMetadata->data.inst.fineTuneCents = (drwav_int8)instData[1]; + pMetadata->data.inst.gainDecibels = (drwav_int8)instData[2]; + pMetadata->data.inst.lowNote = (drwav_int8)instData[3]; + pMetadata->data.inst.highNote = (drwav_int8)instData[4]; + pMetadata->data.inst.lowVelocity = (drwav_int8)instData[5]; + pMetadata->data.inst.highVelocity = (drwav_int8)instData[6]; + } + + return bytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_acid_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata) +{ + drwav_uint8 acidData[DRWAV_ACID_BYTES]; + drwav_uint64 bytesRead; + + if (pMetadata == NULL) { + return 0; + } + + bytesRead = drwav__metadata_parser_read(pParser, acidData, sizeof(acidData), NULL); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesRead == sizeof(acidData)) { + pMetadata->type = drwav_metadata_type_acid; + pMetadata->data.acid.flags = drwav_bytes_to_u32(acidData + 0); + pMetadata->data.acid.midiUnityNote = drwav_bytes_to_u16(acidData + 4); + pMetadata->data.acid.reserved1 = drwav_bytes_to_u16(acidData + 6); + pMetadata->data.acid.reserved2 = drwav_bytes_to_f32(acidData + 8); + pMetadata->data.acid.numBeats = drwav_bytes_to_u32(acidData + 12); + pMetadata->data.acid.meterDenominator = drwav_bytes_to_u16(acidData + 16); + pMetadata->data.acid.meterNumerator = drwav_bytes_to_u16(acidData + 18); + pMetadata->data.acid.tempo = drwav_bytes_to_f32(acidData + 20); + } + + return bytesRead; +} + +DRWAV_PRIVATE size_t drwav__strlen(const char* str) +{ + size_t result = 0; + + while (*str++) { + result += 1; + } + + return result; +} + +DRWAV_PRIVATE size_t drwav__strlen_clamped(const char* str, size_t maxToRead) +{ + size_t result = 0; + + while (*str++ && result < maxToRead) { + result += 1; + } + + return result; +} + +DRWAV_PRIVATE char* drwav__metadata_copy_string(drwav__metadata_parser* pParser, const char* str, size_t maxToRead) +{ + size_t len = drwav__strlen_clamped(str, maxToRead); + + if (len) { + char* result = (char*)drwav__metadata_get_memory(pParser, len + 1, 1); + DRWAV_ASSERT(result != NULL); + + DRWAV_COPY_MEMORY(result, str, len); + result[len] = '\0'; + + return result; + } else { + return NULL; + } +} + +typedef struct +{ + const void* pBuffer; + size_t sizeInBytes; + size_t cursor; +} drwav_buffer_reader; + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_init(const void* pBuffer, size_t sizeInBytes, drwav_buffer_reader* pReader) +{ + DRWAV_ASSERT(pBuffer != NULL); + DRWAV_ASSERT(pReader != NULL); + + DRWAV_ZERO_OBJECT(pReader); + + pReader->pBuffer = pBuffer; + pReader->sizeInBytes = sizeInBytes; + pReader->cursor = 0; + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE const void* drwav_buffer_reader_ptr(const drwav_buffer_reader* pReader) +{ + DRWAV_ASSERT(pReader != NULL); + + return drwav_offset_ptr(pReader->pBuffer, pReader->cursor); +} + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_seek(drwav_buffer_reader* pReader, size_t bytesToSeek) +{ + DRWAV_ASSERT(pReader != NULL); + + if (pReader->cursor + bytesToSeek > pReader->sizeInBytes) { + return DRWAV_BAD_SEEK; /* Seeking too far forward. */ + } + + pReader->cursor += bytesToSeek; + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read(drwav_buffer_reader* pReader, void* pDst, size_t bytesToRead, size_t* pBytesRead) +{ + drwav_result result = DRWAV_SUCCESS; + size_t bytesRemaining; + + DRWAV_ASSERT(pReader != NULL); + + if (pBytesRead != NULL) { + *pBytesRead = 0; + } + + bytesRemaining = (pReader->sizeInBytes - pReader->cursor); + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (pDst == NULL) { + /* Seek. */ + result = drwav_buffer_reader_seek(pReader, bytesToRead); + } else { + /* Read. */ + DRWAV_COPY_MEMORY(pDst, drwav_buffer_reader_ptr(pReader), bytesToRead); + pReader->cursor += bytesToRead; + } + + DRWAV_ASSERT(pReader->cursor <= pReader->sizeInBytes); + + if (result == DRWAV_SUCCESS) { + if (pBytesRead != NULL) { + *pBytesRead = bytesToRead; + } + } + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read_u16(drwav_buffer_reader* pReader, drwav_uint16* pDst) +{ + drwav_result result; + size_t bytesRead; + drwav_uint8 data[2]; + + DRWAV_ASSERT(pReader != NULL); + DRWAV_ASSERT(pDst != NULL); + + *pDst = 0; /* Safety. */ + + result = drwav_buffer_reader_read(pReader, data, sizeof(*pDst), &bytesRead); + if (result != DRWAV_SUCCESS || bytesRead != sizeof(*pDst)) { + return result; + } + + *pDst = drwav_bytes_to_u16(data); + + return DRWAV_SUCCESS; +} + +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read_u32(drwav_buffer_reader* pReader, drwav_uint32* pDst) +{ + drwav_result result; + size_t bytesRead; + drwav_uint8 data[4]; + + DRWAV_ASSERT(pReader != NULL); + DRWAV_ASSERT(pDst != NULL); + + *pDst = 0; /* Safety. */ + + result = drwav_buffer_reader_read(pReader, data, sizeof(*pDst), &bytesRead); + if (result != DRWAV_SUCCESS || bytesRead != sizeof(*pDst)) { + return result; + } + + *pDst = drwav_bytes_to_u32(data); + + return DRWAV_SUCCESS; +} + + + +DRWAV_PRIVATE drwav_uint64 drwav__read_bext_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata, drwav_uint64 chunkSize) +{ + drwav_uint8 bextData[DRWAV_BEXT_BYTES]; + size_t bytesRead = drwav__metadata_parser_read(pParser, bextData, sizeof(bextData), NULL); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesRead == sizeof(bextData)) { + drwav_buffer_reader reader; + drwav_uint32 timeReferenceLow; + drwav_uint32 timeReferenceHigh; + size_t extraBytes; + + pMetadata->type = drwav_metadata_type_bext; + + if (drwav_buffer_reader_init(bextData, bytesRead, &reader) == DRWAV_SUCCESS) { + pMetadata->data.bext.pDescription = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_DESCRIPTION_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_DESCRIPTION_BYTES); + + pMetadata->data.bext.pOriginatorName = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_ORIGINATOR_NAME_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_ORIGINATOR_NAME_BYTES); + + pMetadata->data.bext.pOriginatorReference = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_ORIGINATOR_REF_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_ORIGINATOR_REF_BYTES); + + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pOriginationDate, sizeof(pMetadata->data.bext.pOriginationDate), NULL); + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pOriginationTime, sizeof(pMetadata->data.bext.pOriginationTime), NULL); + + drwav_buffer_reader_read_u32(&reader, &timeReferenceLow); + drwav_buffer_reader_read_u32(&reader, &timeReferenceHigh); + pMetadata->data.bext.timeReference = ((drwav_uint64)timeReferenceHigh << 32) + timeReferenceLow; + + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.version); + + pMetadata->data.bext.pUMID = drwav__metadata_get_memory(pParser, DRWAV_BEXT_UMID_BYTES, 1); + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pUMID, DRWAV_BEXT_UMID_BYTES, NULL); + + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.loudnessValue); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.loudnessRange); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxTruePeakLevel); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxMomentaryLoudness); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxShortTermLoudness); + + DRWAV_ASSERT((drwav_offset_ptr(drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_RESERVED_BYTES)) == (bextData + DRWAV_BEXT_BYTES)); + + extraBytes = (size_t)(chunkSize - DRWAV_BEXT_BYTES); + if (extraBytes > 0) { + pMetadata->data.bext.pCodingHistory = (char*)drwav__metadata_get_memory(pParser, extraBytes + 1, 1); + DRWAV_ASSERT(pMetadata->data.bext.pCodingHistory != NULL); + + bytesRead += drwav__metadata_parser_read(pParser, pMetadata->data.bext.pCodingHistory, extraBytes, NULL); + pMetadata->data.bext.codingHistorySize = (drwav_uint32)drwav__strlen(pMetadata->data.bext.pCodingHistory); + } else { + pMetadata->data.bext.pCodingHistory = NULL; + pMetadata->data.bext.codingHistorySize = 0; + } + } + } + + return bytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_list_label_or_note_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata, drwav_uint64 chunkSize, drwav_metadata_type type) +{ + drwav_uint8 cueIDBuffer[DRWAV_LIST_LABEL_OR_NOTE_BYTES]; + drwav_uint64 totalBytesRead = 0; + size_t bytesJustRead = drwav__metadata_parser_read(pParser, cueIDBuffer, sizeof(cueIDBuffer), &totalBytesRead); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesJustRead == sizeof(cueIDBuffer)) { + drwav_uint32 sizeIncludingNullTerminator; + + pMetadata->type = type; + pMetadata->data.labelOrNote.cuePointId = drwav_bytes_to_u32(cueIDBuffer); + + sizeIncludingNullTerminator = (drwav_uint32)chunkSize - DRWAV_LIST_LABEL_OR_NOTE_BYTES; + if (sizeIncludingNullTerminator > 0) { + pMetadata->data.labelOrNote.stringLength = sizeIncludingNullTerminator - 1; + pMetadata->data.labelOrNote.pString = (char*)drwav__metadata_get_memory(pParser, sizeIncludingNullTerminator, 1); + DRWAV_ASSERT(pMetadata->data.labelOrNote.pString != NULL); + + drwav__metadata_parser_read(pParser, pMetadata->data.labelOrNote.pString, sizeIncludingNullTerminator, &totalBytesRead); + } else { + pMetadata->data.labelOrNote.stringLength = 0; + pMetadata->data.labelOrNote.pString = NULL; + } + } + + return totalBytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__read_list_labelled_cue_region_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata, drwav_uint64 chunkSize) +{ + drwav_uint8 buffer[DRWAV_LIST_LABELLED_TEXT_BYTES]; + drwav_uint64 totalBytesRead = 0; + size_t bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &totalBytesRead); + + DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + + if (bytesJustRead == sizeof(buffer)) { + drwav_uint32 sizeIncludingNullTerminator; + + pMetadata->type = drwav_metadata_type_list_labelled_cue_region; + pMetadata->data.labelledCueRegion.cuePointId = drwav_bytes_to_u32(buffer + 0); + pMetadata->data.labelledCueRegion.sampleLength = drwav_bytes_to_u32(buffer + 4); + pMetadata->data.labelledCueRegion.purposeId[0] = buffer[8]; + pMetadata->data.labelledCueRegion.purposeId[1] = buffer[9]; + pMetadata->data.labelledCueRegion.purposeId[2] = buffer[10]; + pMetadata->data.labelledCueRegion.purposeId[3] = buffer[11]; + pMetadata->data.labelledCueRegion.country = drwav_bytes_to_u16(buffer + 12); + pMetadata->data.labelledCueRegion.language = drwav_bytes_to_u16(buffer + 14); + pMetadata->data.labelledCueRegion.dialect = drwav_bytes_to_u16(buffer + 16); + pMetadata->data.labelledCueRegion.codePage = drwav_bytes_to_u16(buffer + 18); + + sizeIncludingNullTerminator = (drwav_uint32)chunkSize - DRWAV_LIST_LABELLED_TEXT_BYTES; + if (sizeIncludingNullTerminator > 0) { + pMetadata->data.labelledCueRegion.stringLength = sizeIncludingNullTerminator - 1; + pMetadata->data.labelledCueRegion.pString = (char*)drwav__metadata_get_memory(pParser, sizeIncludingNullTerminator, 1); + DRWAV_ASSERT(pMetadata->data.labelledCueRegion.pString != NULL); + + drwav__metadata_parser_read(pParser, pMetadata->data.labelledCueRegion.pString, sizeIncludingNullTerminator, &totalBytesRead); + } else { + pMetadata->data.labelledCueRegion.stringLength = 0; + pMetadata->data.labelledCueRegion.pString = NULL; + } + } + + return totalBytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_info_text_chunk(drwav__metadata_parser* pParser, drwav_uint64 chunkSize, drwav_metadata_type type) +{ + drwav_uint64 bytesRead = 0; + drwav_uint32 stringSizeWithNullTerminator = (drwav_uint32)chunkSize; + + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, stringSizeWithNullTerminator, 1); + } else { + drwav_metadata* pMetadata = &pParser->pMetadata[pParser->metadataCursor]; + pMetadata->type = type; + if (stringSizeWithNullTerminator > 0) { + pMetadata->data.infoText.stringLength = stringSizeWithNullTerminator - 1; + pMetadata->data.infoText.pString = (char*)drwav__metadata_get_memory(pParser, stringSizeWithNullTerminator, 1); + DRWAV_ASSERT(pMetadata->data.infoText.pString != NULL); + + bytesRead = drwav__metadata_parser_read(pParser, pMetadata->data.infoText.pString, (size_t)stringSizeWithNullTerminator, NULL); + if (bytesRead == chunkSize) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } else { + pMetadata->data.infoText.stringLength = 0; + pMetadata->data.infoText.pString = NULL; + pParser->metadataCursor += 1; + } + } + + return bytesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_unknown_chunk(drwav__metadata_parser* pParser, const drwav_uint8* pChunkId, drwav_uint64 chunkSize, drwav_metadata_location location) +{ + drwav_uint64 bytesRead = 0; + + if (location == drwav_metadata_location_invalid) { + return 0; + } + + if (drwav_fourcc_equal(pChunkId, "data") || drwav_fourcc_equal(pChunkId, "fmt ") || drwav_fourcc_equal(pChunkId, "fact")) { + return 0; + } + + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, (size_t)chunkSize, 1); + } else { + drwav_metadata* pMetadata = &pParser->pMetadata[pParser->metadataCursor]; + pMetadata->type = drwav_metadata_type_unknown; + pMetadata->data.unknown.chunkLocation = location; + pMetadata->data.unknown.id[0] = pChunkId[0]; + pMetadata->data.unknown.id[1] = pChunkId[1]; + pMetadata->data.unknown.id[2] = pChunkId[2]; + pMetadata->data.unknown.id[3] = pChunkId[3]; + pMetadata->data.unknown.dataSizeInBytes = (drwav_uint32)chunkSize; + pMetadata->data.unknown.pData = (drwav_uint8 *)drwav__metadata_get_memory(pParser, (size_t)chunkSize, 1); + DRWAV_ASSERT(pMetadata->data.unknown.pData != NULL); + + bytesRead = drwav__metadata_parser_read(pParser, pMetadata->data.unknown.pData, pMetadata->data.unknown.dataSizeInBytes, NULL); + if (bytesRead == pMetadata->data.unknown.dataSizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to read. */ + } + } + + return bytesRead; +} + +DRWAV_PRIVATE drwav_bool32 drwav__chunk_matches(drwav_metadata_type allowedMetadataTypes, const drwav_uint8* pChunkID, drwav_metadata_type type, const char* pID) +{ + return (allowedMetadataTypes & type) && drwav_fourcc_equal(pChunkID, pID); +} + +DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata_type allowedMetadataTypes) +{ + const drwav_uint8 *pChunkID = pChunkHeader->id.fourcc; + drwav_uint64 bytesRead = 0; + + if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_smpl, "smpl")) { + if (pChunkHeader->sizeInBytes >= DRWAV_SMPL_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + drwav_uint8 buffer[4]; + size_t bytesJustRead; + + if (!pParser->onSeek(pParser->pReadSeekUserData, 28, drwav_seek_origin_current)) { + return bytesRead; + } + bytesRead += 28; + + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); + if (bytesJustRead == sizeof(buffer)) { + drwav_uint32 loopCount = drwav_bytes_to_u32(buffer); + drwav_uint64 calculatedLoopCount; + + /* The loop count must be validated against the size of the chunk. */ + calculatedLoopCount = (pChunkHeader->sizeInBytes - DRWAV_SMPL_BYTES) / DRWAV_SMPL_LOOP_BYTES; + if (calculatedLoopCount == loopCount) { + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); + if (bytesJustRead == sizeof(buffer)) { + drwav_uint32 samplerSpecificDataSizeInBytes = drwav_bytes_to_u32(buffer); + + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, sizeof(drwav_smpl_loop) * loopCount, DRWAV_METADATA_ALIGNMENT); + drwav__metadata_request_extra_memory_for_stage_2(pParser, samplerSpecificDataSizeInBytes, 1); + } + } else { + /* Loop count in header does not match the size of the chunk. */ + } + } + } else { + bytesRead = drwav__read_smpl_to_metadata_obj(pParser, pChunkHeader, &pParser->pMetadata[pParser->metadataCursor]); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_inst, "inst")) { + if (pChunkHeader->sizeInBytes == DRWAV_INST_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + } else { + bytesRead = drwav__read_inst_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor]); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_acid, "acid")) { + if (pChunkHeader->sizeInBytes == DRWAV_ACID_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + } else { + bytesRead = drwav__read_acid_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor]); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_cue, "cue ")) { + if (pChunkHeader->sizeInBytes >= DRWAV_CUE_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + size_t cueCount; + + pParser->metadataCount += 1; + cueCount = (size_t)(pChunkHeader->sizeInBytes - DRWAV_CUE_BYTES) / DRWAV_CUE_POINT_BYTES; + drwav__metadata_request_extra_memory_for_stage_2(pParser, sizeof(drwav_cue_point) * cueCount, DRWAV_METADATA_ALIGNMENT); + } else { + bytesRead = drwav__read_cue_to_metadata_obj(pParser, pChunkHeader, &pParser->pMetadata[pParser->metadataCursor]); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, pChunkID, drwav_metadata_type_bext, "bext")) { + if (pChunkHeader->sizeInBytes >= DRWAV_BEXT_BYTES) { + if (pParser->stage == drwav__metadata_parser_stage_count) { + /* The description field is the largest one in a bext chunk, so that is the max size of this temporary buffer. */ + char buffer[DRWAV_BEXT_DESCRIPTION_BYTES + 1]; + size_t allocSizeNeeded = DRWAV_BEXT_UMID_BYTES; /* We know we will need SMPTE umid size. */ + size_t bytesJustRead; + + buffer[DRWAV_BEXT_DESCRIPTION_BYTES] = '\0'; + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_DESCRIPTION_BYTES, &bytesRead); + if (bytesJustRead != DRWAV_BEXT_DESCRIPTION_BYTES) { + return bytesRead; + } + allocSizeNeeded += drwav__strlen(buffer) + 1; + + buffer[DRWAV_BEXT_ORIGINATOR_NAME_BYTES] = '\0'; + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_ORIGINATOR_NAME_BYTES, &bytesRead); + if (bytesJustRead != DRWAV_BEXT_ORIGINATOR_NAME_BYTES) { + return bytesRead; + } + allocSizeNeeded += drwav__strlen(buffer) + 1; + + buffer[DRWAV_BEXT_ORIGINATOR_REF_BYTES] = '\0'; + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_ORIGINATOR_REF_BYTES, &bytesRead); + if (bytesJustRead != DRWAV_BEXT_ORIGINATOR_REF_BYTES) { + return bytesRead; + } + allocSizeNeeded += drwav__strlen(buffer) + 1; + allocSizeNeeded += (size_t)pChunkHeader->sizeInBytes - DRWAV_BEXT_BYTES; /* Coding history. */ + + drwav__metadata_request_extra_memory_for_stage_2(pParser, allocSizeNeeded, 1); + + pParser->metadataCount += 1; + } else { + bytesRead = drwav__read_bext_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor], pChunkHeader->sizeInBytes); + if (bytesRead == pChunkHeader->sizeInBytes) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav_fourcc_equal(pChunkID, "LIST") || drwav_fourcc_equal(pChunkID, "list")) { + drwav_metadata_location listType = drwav_metadata_location_invalid; + while (bytesRead < pChunkHeader->sizeInBytes) { + drwav_uint8 subchunkId[4]; + drwav_uint8 subchunkSizeBuffer[4]; + drwav_uint64 subchunkDataSize; + drwav_uint64 subchunkBytesRead = 0; + drwav_uint64 bytesJustRead = drwav__metadata_parser_read(pParser, subchunkId, sizeof(subchunkId), &bytesRead); + if (bytesJustRead != sizeof(subchunkId)) { + break; + } + + /* + The first thing in a list chunk should be "adtl" or "INFO". + + - adtl means this list is a Associated Data List Chunk and will contain labels, notes + or labelled cue regions. + - INFO means this list is an Info List Chunk containing info text chunks such as IPRD + which would specifies the album of this wav file. + + No data follows the adtl or INFO id so we just make note of what type this list is and + continue. + */ + if (drwav_fourcc_equal(subchunkId, "adtl")) { + listType = drwav_metadata_location_inside_adtl_list; + continue; + } else if (drwav_fourcc_equal(subchunkId, "INFO")) { + listType = drwav_metadata_location_inside_info_list; + continue; + } + + bytesJustRead = drwav__metadata_parser_read(pParser, subchunkSizeBuffer, sizeof(subchunkSizeBuffer), &bytesRead); + if (bytesJustRead != sizeof(subchunkSizeBuffer)) { + break; + } + subchunkDataSize = drwav_bytes_to_u32(subchunkSizeBuffer); + + if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_label, "labl") || drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_note, "note")) { + if (subchunkDataSize >= DRWAV_LIST_LABEL_OR_NOTE_BYTES) { + drwav_uint64 stringSizeWithNullTerm = subchunkDataSize - DRWAV_LIST_LABEL_OR_NOTE_BYTES; + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, (size_t)stringSizeWithNullTerm, 1); + } else { + subchunkBytesRead = drwav__read_list_label_or_note_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor], subchunkDataSize, drwav_fourcc_equal(subchunkId, "labl") ? drwav_metadata_type_list_label : drwav_metadata_type_list_note); + if (subchunkBytesRead == subchunkDataSize) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_labelled_cue_region, "ltxt")) { + if (subchunkDataSize >= DRWAV_LIST_LABELLED_TEXT_BYTES) { + drwav_uint64 stringSizeWithNullTerminator = subchunkDataSize - DRWAV_LIST_LABELLED_TEXT_BYTES; + if (pParser->stage == drwav__metadata_parser_stage_count) { + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, (size_t)stringSizeWithNullTerminator, 1); + } else { + subchunkBytesRead = drwav__read_list_labelled_cue_region_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor], subchunkDataSize); + if (subchunkBytesRead == subchunkDataSize) { + pParser->metadataCursor += 1; + } else { + /* Failed to parse. */ + } + } + } else { + /* Incorrectly formed chunk. */ + } + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_software, "ISFT")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_software); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_copyright, "ICOP")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_copyright); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_title, "INAM")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_title); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_artist, "IART")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_artist); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_comment, "ICMT")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_comment); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_date, "ICRD")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_date); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_genre, "IGNR")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_genre); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_album, "IPRD")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_album); + } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_tracknumber, "ITRK")) { + subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_tracknumber); + } else if ((allowedMetadataTypes & drwav_metadata_type_unknown) != 0) { + subchunkBytesRead = drwav__metadata_process_unknown_chunk(pParser, subchunkId, subchunkDataSize, listType); + } + + bytesRead += subchunkBytesRead; + DRWAV_ASSERT(subchunkBytesRead <= subchunkDataSize); + + if (subchunkBytesRead < subchunkDataSize) { + drwav_uint64 bytesToSeek = subchunkDataSize - subchunkBytesRead; + + if (!pParser->onSeek(pParser->pReadSeekUserData, (int)bytesToSeek, drwav_seek_origin_current)) { + break; + } + bytesRead += bytesToSeek; + } + + if ((subchunkDataSize % 2) == 1) { + if (!pParser->onSeek(pParser->pReadSeekUserData, 1, drwav_seek_origin_current)) { + break; + } + bytesRead += 1; + } + } + } else if ((allowedMetadataTypes & drwav_metadata_type_unknown) != 0) { + bytesRead = drwav__metadata_process_unknown_chunk(pParser, pChunkID, pChunkHeader->sizeInBytes, drwav_metadata_location_top_level); + } + + return bytesRead; +} + + +DRWAV_PRIVATE drwav_uint32 drwav_get_bytes_per_pcm_frame(drwav* pWav) +{ + drwav_uint32 bytesPerFrame; + + /* + The bytes per frame is a bit ambiguous. It can be either be based on the bits per sample, or the block align. The way I'm doing it here + is that if the bits per sample is a multiple of 8, use floor(bitsPerSample*channels/8), otherwise fall back to the block align. + */ + if ((pWav->bitsPerSample & 0x7) == 0) { + /* Bits per sample is a multiple of 8. */ + bytesPerFrame = (pWav->bitsPerSample * pWav->fmt.channels) >> 3; + } else { + bytesPerFrame = pWav->fmt.blockAlign; + } + + /* Validation for known formats. a-law and mu-law should be 1 byte per channel. If it's not, it's not decodable. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW || pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + if (bytesPerFrame != pWav->fmt.channels) { + return 0; /* Invalid file. */ + } + } + + return bytesPerFrame; +} + +DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT) +{ + if (pFMT == NULL) { + return 0; + } + + if (pFMT->formatTag != DR_WAVE_FORMAT_EXTENSIBLE) { + return pFMT->formatTag; + } else { + return drwav_bytes_to_u16(pFMT->subFormat); /* Only the first two bytes are required. */ + } +} + +DRWAV_PRIVATE drwav_bool32 drwav_preinit(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pWav == NULL || onRead == NULL || onSeek == NULL) { + return DRWAV_FALSE; + } + + DRWAV_ZERO_MEMORY(pWav, sizeof(*pWav)); + pWav->onRead = onRead; + pWav->onSeek = onSeek; + pWav->pUserData = pReadSeekUserData; + pWav->allocationCallbacks = drwav_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); + + if (pWav->allocationCallbacks.onFree == NULL || (pWav->allocationCallbacks.onMalloc == NULL && pWav->allocationCallbacks.onRealloc == NULL)) { + return DRWAV_FALSE; /* Invalid allocation callbacks. */ + } + + return DRWAV_TRUE; +} + +DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +{ + /* This function assumes drwav_preinit() has been called beforehand. */ + drwav_result result; + drwav_uint64 cursor; /* <-- Keeps track of the byte position so we can seek to specific locations. */ + drwav_bool32 sequential; + drwav_uint8 riff[4]; + drwav_fmt fmt; + unsigned short translatedFormatTag; + drwav_uint64 dataChunkSize = 0; /* <-- Important! Don't explicitly set this to 0 anywhere else. Calculation of the size of the data chunk is performed in different paths depending on the container. */ + drwav_uint64 sampleCountFromFactChunk = 0; /* Same as dataChunkSize - make sure this is the only place this is initialized to 0. */ + drwav_uint64 metadataStartPos; + drwav__metadata_parser metadataParser; + drwav_bool8 isProcessingMetadata = DRWAV_FALSE; + drwav_bool8 foundChunk_fmt = DRWAV_FALSE; + drwav_bool8 foundChunk_data = DRWAV_FALSE; + drwav_bool8 isAIFCFormType = DRWAV_FALSE; /* Only used with AIFF. */ + drwav_uint64 aiffFrameCount = 0; + + cursor = 0; + sequential = (flags & DRWAV_SEQUENTIAL) != 0; + DRWAV_ZERO_OBJECT(&fmt); + + /* The first 4 bytes should be the RIFF identifier. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, riff, sizeof(riff), &cursor) != sizeof(riff)) { + return DRWAV_FALSE; + } + + /* + The first 4 bytes can be used to identify the container. For RIFF files it will start with "RIFF" and for + w64 it will start with "riff". + */ + if (drwav_fourcc_equal(riff, "RIFF")) { + pWav->container = drwav_container_riff; + } else if (drwav_fourcc_equal(riff, "RIFX")) { + pWav->container = drwav_container_rifx; + } else if (drwav_fourcc_equal(riff, "riff")) { + int i; + drwav_uint8 riff2[12]; + + pWav->container = drwav_container_w64; + + /* Check the rest of the GUID for validity. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, riff2, sizeof(riff2), &cursor) != sizeof(riff2)) { + return DRWAV_FALSE; + } + + for (i = 0; i < 12; ++i) { + if (riff2[i] != drwavGUID_W64_RIFF[i+4]) { + return DRWAV_FALSE; + } + } + } else if (drwav_fourcc_equal(riff, "RF64")) { + pWav->container = drwav_container_rf64; + } else if (drwav_fourcc_equal(riff, "FORM")) { + pWav->container = drwav_container_aiff; + } else { + return DRWAV_FALSE; /* Unknown or unsupported container. */ + } + + + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx || pWav->container == drwav_container_rf64) { + drwav_uint8 chunkSizeBytes[4]; + drwav_uint8 wave[4]; + + if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { + return DRWAV_FALSE; + } + + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx) { + if (drwav_bytes_to_u32_ex(chunkSizeBytes, pWav->container) < 36) { + /* + I've had a report of a WAV file failing to load when the size of the WAVE chunk is not encoded + and is instead just set to 0. I'm going to relax the validation here to allow these files to + load. Considering the chunk size isn't actually used this should be safe. With this change my + test suite still passes. + */ + /*return DRWAV_FALSE;*/ /* Chunk size should always be at least 36 bytes. */ + } + } else if (pWav->container == drwav_container_rf64) { + if (drwav_bytes_to_u32_le(chunkSizeBytes) != 0xFFFFFFFF) { + return DRWAV_FALSE; /* Chunk size should always be set to -1/0xFFFFFFFF for RF64. The actual size is retrieved later. */ + } + } else { + return DRWAV_FALSE; /* Should never hit this. */ + } + + if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { + return DRWAV_FALSE; + } + + if (!drwav_fourcc_equal(wave, "WAVE")) { + return DRWAV_FALSE; /* Expecting "WAVE". */ + } + } else if (pWav->container == drwav_container_w64) { + drwav_uint8 chunkSizeBytes[8]; + drwav_uint8 wave[16]; + + if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { + return DRWAV_FALSE; + } + + if (drwav_bytes_to_u64(chunkSizeBytes) < 80) { + return DRWAV_FALSE; + } + + if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { + return DRWAV_FALSE; + } + + if (!drwav_guid_equal(wave, drwavGUID_W64_WAVE)) { + return DRWAV_FALSE; + } + } else if (pWav->container == drwav_container_aiff) { + drwav_uint8 chunkSizeBytes[4]; + drwav_uint8 aiff[4]; + + if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { + return DRWAV_FALSE; + } + + if (drwav_bytes_to_u32_be(chunkSizeBytes) < 18) { + return DRWAV_FALSE; + } + + if (drwav__on_read(pWav->onRead, pWav->pUserData, aiff, sizeof(aiff), &cursor) != sizeof(aiff)) { + return DRWAV_FALSE; + } + + if (drwav_fourcc_equal(aiff, "AIFF")) { + isAIFCFormType = DRWAV_FALSE; + } else if (drwav_fourcc_equal(aiff, "AIFC")) { + isAIFCFormType = DRWAV_TRUE; + } else { + return DRWAV_FALSE; /* Expecting "AIFF" or "AIFC". */ + } + } else { + return DRWAV_FALSE; + } + + + /* For RF64, the "ds64" chunk must come next, before the "fmt " chunk. */ + if (pWav->container == drwav_container_rf64) { + drwav_uint8 sizeBytes[8]; + drwav_uint64 bytesRemainingInChunk; + drwav_chunk_header header; + result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + if (!drwav_fourcc_equal(header.id.fourcc, "ds64")) { + return DRWAV_FALSE; /* Expecting "ds64". */ + } + + bytesRemainingInChunk = header.sizeInBytes + header.paddingSize; + + /* We don't care about the size of the RIFF chunk - skip it. */ + if (!drwav__seek_forward(pWav->onSeek, 8, pWav->pUserData)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + cursor += 8; + + + /* Next 8 bytes is the size of the "data" chunk. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + dataChunkSize = drwav_bytes_to_u64(sizeBytes); + + + /* Next 8 bytes is the same count which we would usually derived from the FACT chunk if it was available. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + sampleCountFromFactChunk = drwav_bytes_to_u64(sizeBytes); + + + /* Skip over everything else. */ + if (!drwav__seek_forward(pWav->onSeek, bytesRemainingInChunk, pWav->pUserData)) { + return DRWAV_FALSE; + } + cursor += bytesRemainingInChunk; + } + + + metadataStartPos = cursor; + + /* + Whether or not we are processing metadata controls how we load. We can load more efficiently when + metadata is not being processed, but we also cannot process metadata for Wave64 because I have not + been able to test it. If someone is able to test this and provide a patch I'm happy to enable it. + + Seqential mode cannot support metadata because it involves seeking backwards. + */ + isProcessingMetadata = !sequential && ((flags & DRWAV_WITH_METADATA) != 0); + + /* Don't allow processing of metadata with untested containers. */ + if (pWav->container != drwav_container_riff && pWav->container != drwav_container_rf64) { + isProcessingMetadata = DRWAV_FALSE; + } + + DRWAV_ZERO_MEMORY(&metadataParser, sizeof(metadataParser)); + if (isProcessingMetadata) { + metadataParser.onRead = pWav->onRead; + metadataParser.onSeek = pWav->onSeek; + metadataParser.pReadSeekUserData = pWav->pUserData; + metadataParser.stage = drwav__metadata_parser_stage_count; + } + + + /* + From here on out, chunks might be in any order. In order to robustly handle metadata we'll need + to loop through every chunk and handle them as we find them. In sequential mode we need to get + out of the loop as soon as we find the data chunk because we won't be able to seek back. + */ + for (;;) { /* For each chunk... */ + drwav_chunk_header header; + drwav_uint64 chunkSize; + + result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + break; + } + + chunkSize = header.sizeInBytes; + + + /* + Always tell the caller about this chunk. We cannot do this in sequential mode because the + callback is allowed to read from the file, in which case we'll need to rewind. + */ + if (!sequential && onChunk != NULL) { + drwav_uint64 callbackBytesRead = onChunk(pChunkUserData, pWav->onRead, pWav->onSeek, pWav->pUserData, &header, pWav->container, &fmt); + + /* + dr_wav may need to read the contents of the chunk, so we now need to seek back to the position before + we called the callback. + */ + if (callbackBytesRead > 0) { + if (drwav__seek_from_start(pWav->onSeek, cursor, pWav->pUserData) == DRWAV_FALSE) { + return DRWAV_FALSE; + } + } + } + + + /* Explicitly handle known chunks first. */ + + /* "fmt " */ + if (((pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx || pWav->container == drwav_container_rf64) && drwav_fourcc_equal(header.id.fourcc, "fmt ")) || + ((pWav->container == drwav_container_w64) && drwav_guid_equal(header.id.guid, drwavGUID_W64_FMT))) { + drwav_uint8 fmtData[16]; + + foundChunk_fmt = DRWAV_TRUE; + + if (pWav->onRead(pWav->pUserData, fmtData, sizeof(fmtData)) != sizeof(fmtData)) { + return DRWAV_FALSE; + } + cursor += sizeof(fmtData); + + fmt.formatTag = drwav_bytes_to_u16_ex(fmtData + 0, pWav->container); + fmt.channels = drwav_bytes_to_u16_ex(fmtData + 2, pWav->container); + fmt.sampleRate = drwav_bytes_to_u32_ex(fmtData + 4, pWav->container); + fmt.avgBytesPerSec = drwav_bytes_to_u32_ex(fmtData + 8, pWav->container); + fmt.blockAlign = drwav_bytes_to_u16_ex(fmtData + 12, pWav->container); + fmt.bitsPerSample = drwav_bytes_to_u16_ex(fmtData + 14, pWav->container); + + fmt.extendedSize = 0; + fmt.validBitsPerSample = 0; + fmt.channelMask = 0; + DRWAV_ZERO_MEMORY(fmt.subFormat, sizeof(fmt.subFormat)); + + if (header.sizeInBytes > 16) { + drwav_uint8 fmt_cbSize[2]; + int bytesReadSoFar = 0; + + if (pWav->onRead(pWav->pUserData, fmt_cbSize, sizeof(fmt_cbSize)) != sizeof(fmt_cbSize)) { + return DRWAV_FALSE; /* Expecting more data. */ + } + cursor += sizeof(fmt_cbSize); + + bytesReadSoFar = 18; + + fmt.extendedSize = drwav_bytes_to_u16_ex(fmt_cbSize, pWav->container); + if (fmt.extendedSize > 0) { + /* Simple validation. */ + if (fmt.formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + if (fmt.extendedSize != 22) { + return DRWAV_FALSE; + } + } + + if (fmt.formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + drwav_uint8 fmtext[22]; + + if (pWav->onRead(pWav->pUserData, fmtext, fmt.extendedSize) != fmt.extendedSize) { + return DRWAV_FALSE; /* Expecting more data. */ + } + + fmt.validBitsPerSample = drwav_bytes_to_u16_ex(fmtext + 0, pWav->container); + fmt.channelMask = drwav_bytes_to_u32_ex(fmtext + 2, pWav->container); + drwav_bytes_to_guid(fmtext + 6, fmt.subFormat); + } else { + if (pWav->onSeek(pWav->pUserData, fmt.extendedSize, drwav_seek_origin_current) == DRWAV_FALSE) { + return DRWAV_FALSE; + } + } + cursor += fmt.extendedSize; + + bytesReadSoFar += fmt.extendedSize; + } + + /* Seek past any leftover bytes. For w64 the leftover will be defined based on the chunk size. */ + if (pWav->onSeek(pWav->pUserData, (int)(header.sizeInBytes - bytesReadSoFar), drwav_seek_origin_current) == DRWAV_FALSE) { + return DRWAV_FALSE; + } + cursor += (header.sizeInBytes - bytesReadSoFar); + } + + if (header.paddingSize > 0) { + if (drwav__seek_forward(pWav->onSeek, header.paddingSize, pWav->pUserData) == DRWAV_FALSE) { + break; + } + cursor += header.paddingSize; + } + + /* Go to the next chunk. Don't include this chunk in metadata. */ + continue; + } + + /* "data" */ + if (((pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx || pWav->container == drwav_container_rf64) && drwav_fourcc_equal(header.id.fourcc, "data")) || + ((pWav->container == drwav_container_w64) && drwav_guid_equal(header.id.guid, drwavGUID_W64_DATA))) { + foundChunk_data = DRWAV_TRUE; + + pWav->dataChunkDataPos = cursor; + + if (pWav->container != drwav_container_rf64) { /* The data chunk size for RF64 will always be set to 0xFFFFFFFF here. It was set to it's true value earlier. */ + dataChunkSize = chunkSize; + } + + /* If we're running in sequential mode, or we're not reading metadata, we have enough now that we can get out of the loop. */ + if (sequential || !isProcessingMetadata) { + break; /* No need to keep reading beyond the data chunk. */ + } else { + chunkSize += header.paddingSize; /* <-- Make sure we seek past the padding. */ + if (drwav__seek_forward(pWav->onSeek, chunkSize, pWav->pUserData) == DRWAV_FALSE) { + break; + } + cursor += chunkSize; + + continue; /* There may be some more metadata to read. */ + } + } + + /* "fact". This is optional. Can use this to get the sample count which is useful for compressed formats. For RF64 we retrieved the sample count from the ds64 chunk earlier. */ + if (((pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx || pWav->container == drwav_container_rf64) && drwav_fourcc_equal(header.id.fourcc, "fact")) || + ((pWav->container == drwav_container_w64) && drwav_guid_equal(header.id.guid, drwavGUID_W64_FACT))) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx) { + drwav_uint8 sampleCount[4]; + if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCount, 4, &cursor) != 4) { + return DRWAV_FALSE; + } + + chunkSize -= 4; + + /* + The sample count in the "fact" chunk is either unreliable, or I'm not understanding it properly. For now I am only enabling this + for Microsoft ADPCM formats. + */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + sampleCountFromFactChunk = drwav_bytes_to_u32_ex(sampleCount, pWav->container); + } else { + sampleCountFromFactChunk = 0; + } + } else if (pWav->container == drwav_container_w64) { + if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) { + return DRWAV_FALSE; + } + + chunkSize -= 8; + } else if (pWav->container == drwav_container_rf64) { + /* We retrieved the sample count from the ds64 chunk earlier so no need to do that here. */ + } + + /* Seek to the next chunk in preparation for the next iteration. */ + chunkSize += header.paddingSize; /* <-- Make sure we seek past the padding. */ + if (drwav__seek_forward(pWav->onSeek, chunkSize, pWav->pUserData) == DRWAV_FALSE) { + break; + } + cursor += chunkSize; + + continue; + } + + + /* "COMM". AIFF/AIFC only. */ + if (pWav->container == drwav_container_aiff && drwav_fourcc_equal(header.id.fourcc, "COMM")) { + drwav_uint8 commData[24]; + drwav_uint32 commDataBytesToRead; + drwav_uint16 channels; + drwav_uint32 frameCount; + drwav_uint16 sampleSizeInBits; + drwav_int64 sampleRate; + drwav_uint16 compressionFormat; + + foundChunk_fmt = DRWAV_TRUE; + + if (isAIFCFormType) { + commDataBytesToRead = 24; + if (header.sizeInBytes < commDataBytesToRead) { + return DRWAV_FALSE; /* Invalid COMM chunk. */ + } + } else { + commDataBytesToRead = 18; + if (header.sizeInBytes != commDataBytesToRead) { + return DRWAV_FALSE; /* INVALID COMM chunk. */ + } + } + + if (drwav__on_read(pWav->onRead, pWav->pUserData, commData, commDataBytesToRead, &cursor) != commDataBytesToRead) { + return DRWAV_FALSE; + } + + + channels = drwav_bytes_to_u16_ex (commData + 0, pWav->container); + frameCount = drwav_bytes_to_u32_ex (commData + 2, pWav->container); + sampleSizeInBits = drwav_bytes_to_u16_ex (commData + 6, pWav->container); + sampleRate = drwav_aiff_extented_to_s64(commData + 8); + + if (sampleRate < 0 || sampleRate > 0xFFFFFFFF) { + return DRWAV_FALSE; /* Invalid sample rate. */ + } + + if (isAIFCFormType) { + const drwav_uint8* type = commData + 18; + + if (drwav_fourcc_equal(type, "NONE")) { + compressionFormat = DR_WAVE_FORMAT_PCM; /* PCM, big-endian. */ + } else if (drwav_fourcc_equal(type, "raw ")) { + compressionFormat = DR_WAVE_FORMAT_PCM; + + /* In my testing, it looks like when the "raw " compression type is used, 8-bit samples should be considered unsigned. */ + if (sampleSizeInBits == 8) { + pWav->aiff.isUnsigned = DRWAV_TRUE; + } + } else if (drwav_fourcc_equal(type, "sowt")) { + compressionFormat = DR_WAVE_FORMAT_PCM; /* PCM, little-endian. */ + pWav->aiff.isLE = DRWAV_TRUE; + } else if (drwav_fourcc_equal(type, "fl32") || drwav_fourcc_equal(type, "fl64") || drwav_fourcc_equal(type, "FL32") || drwav_fourcc_equal(type, "FL64")) { + compressionFormat = DR_WAVE_FORMAT_IEEE_FLOAT; + } else if (drwav_fourcc_equal(type, "alaw") || drwav_fourcc_equal(type, "ALAW")) { + compressionFormat = DR_WAVE_FORMAT_ALAW; + } else if (drwav_fourcc_equal(type, "ulaw") || drwav_fourcc_equal(type, "ULAW")) { + compressionFormat = DR_WAVE_FORMAT_MULAW; + } else if (drwav_fourcc_equal(type, "ima4")) { + compressionFormat = DR_WAVE_FORMAT_DVI_ADPCM; + sampleSizeInBits = 4; + + /* + I haven't been able to figure out how to get correct decoding for IMA ADPCM. Until this is figured out + we'll need to abort when we encounter such an encoding. Advice welcome! + */ + return DRWAV_FALSE; + } else { + return DRWAV_FALSE; /* Unknown or unsupported compression format. Need to abort. */ + } + } else { + compressionFormat = DR_WAVE_FORMAT_PCM; /* It's a standard AIFF form which is always compressed. */ + } + + /* With AIFF we want to use the explicitly defined frame count rather than deriving it from the size of the chunk. */ + aiffFrameCount = frameCount; + + /* We should now have enough information to fill out our fmt structure. */ + fmt.formatTag = compressionFormat; + fmt.channels = channels; + fmt.sampleRate = (drwav_uint32)sampleRate; + fmt.bitsPerSample = sampleSizeInBits; + fmt.blockAlign = (drwav_uint16)(fmt.channels * fmt.bitsPerSample / 8); + fmt.avgBytesPerSec = fmt.blockAlign * fmt.sampleRate; + + if (fmt.blockAlign == 0 && compressionFormat == DR_WAVE_FORMAT_DVI_ADPCM) { + fmt.blockAlign = 34 * fmt.channels; + } + + /* + Weird one. I've seen some alaw and ulaw encoded files that for some reason set the bits per sample to 16 when + it should be 8. To get this working I need to explicitly check for this and change it. + */ + if (compressionFormat == DR_WAVE_FORMAT_ALAW || compressionFormat == DR_WAVE_FORMAT_MULAW) { + if (fmt.bitsPerSample > 8) { + fmt.bitsPerSample = 8; + fmt.blockAlign = fmt.channels; + } + } + + /* In AIFF, samples are padded to 8 byte boundaries. We need to round up our bits per sample here. */ + fmt.bitsPerSample += (fmt.bitsPerSample & 7); + + + /* If the form type is AIFC there will be some additional data in the chunk. We need to seek past it. */ + if (isAIFCFormType) { + if (drwav__seek_forward(pWav->onSeek, (chunkSize - commDataBytesToRead), pWav->pUserData) == DRWAV_FALSE) { + return DRWAV_FALSE; + } + cursor += (chunkSize - commDataBytesToRead); + } + + /* Don't fall through or else we'll end up treating this chunk as metadata which is incorrect. */ + continue; + } + + + /* "SSND". AIFF/AIFC only. This is the AIFF equivalent of the "data" chunk. */ + if (pWav->container == drwav_container_aiff && drwav_fourcc_equal(header.id.fourcc, "SSND")) { + drwav_uint8 offsetAndBlockSizeData[8]; + drwav_uint32 offset; + + foundChunk_data = DRWAV_TRUE; + + if (drwav__on_read(pWav->onRead, pWav->pUserData, offsetAndBlockSizeData, sizeof(offsetAndBlockSizeData), &cursor) != sizeof(offsetAndBlockSizeData)) { + return DRWAV_FALSE; + } + + /* We need to seek forward by the offset. */ + offset = drwav_bytes_to_u32_ex(offsetAndBlockSizeData + 0, pWav->container); + if (drwav__seek_forward(pWav->onSeek, offset, pWav->pUserData) == DRWAV_FALSE) { + return DRWAV_FALSE; + } + cursor += offset; + + pWav->dataChunkDataPos = cursor; + dataChunkSize = chunkSize; + + /* If we're running in sequential mode, or we're not reading metadata, we have enough now that we can get out of the loop. */ + if (sequential || !isProcessingMetadata) { + break; /* No need to keep reading beyond the data chunk. */ + } else { + if (drwav__seek_forward(pWav->onSeek, chunkSize, pWav->pUserData) == DRWAV_FALSE) { + break; + } + cursor += chunkSize; + + continue; /* There may be some more metadata to read. */ + } + } + + + + /* Getting here means it's not a chunk that we care about internally, but might need to be handled as metadata by the caller. */ + if (isProcessingMetadata) { + drwav__metadata_process_chunk(&metadataParser, &header, drwav_metadata_type_all_including_unknown); + + /* Go back to the start of the chunk so we can normalize the position of the cursor. */ + if (drwav__seek_from_start(pWav->onSeek, cursor, pWav->pUserData) == DRWAV_FALSE) { + break; /* Failed to seek. Can't reliable read the remaining chunks. Get out. */ + } + } + + + /* Make sure we skip past the content of this chunk before we go to the next one. */ + chunkSize += header.paddingSize; /* <-- Make sure we seek past the padding. */ + if (drwav__seek_forward(pWav->onSeek, chunkSize, pWav->pUserData) == DRWAV_FALSE) { + break; + } + cursor += chunkSize; + } + + /* There's some mandatory chunks that must exist. If they were not found in the iteration above we must abort. */ + if (!foundChunk_fmt || !foundChunk_data) { + return DRWAV_FALSE; + } + + /* Basic validation. */ + if ((fmt.sampleRate == 0 || fmt.sampleRate > DRWAV_MAX_SAMPLE_RATE ) || + (fmt.channels == 0 || fmt.channels > DRWAV_MAX_CHANNELS ) || + (fmt.bitsPerSample == 0 || fmt.bitsPerSample > DRWAV_MAX_BITS_PER_SAMPLE) || + fmt.blockAlign == 0) { + return DRWAV_FALSE; /* Probably an invalid WAV file. */ + } + + /* Translate the internal format. */ + translatedFormatTag = fmt.formatTag; + if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + translatedFormatTag = drwav_bytes_to_u16_ex(fmt.subFormat + 0, pWav->container); + } + + /* We may have moved passed the data chunk. If so we need to move back. If running in sequential mode we can assume we are already sitting on the data chunk. */ + if (!sequential) { + if (!drwav__seek_from_start(pWav->onSeek, pWav->dataChunkDataPos, pWav->pUserData)) { + return DRWAV_FALSE; + } + cursor = pWav->dataChunkDataPos; + } + + + /* + At this point we should have done the initial parsing of each of our chunks, but we now need to + do a second pass to extract the actual contents of the metadata (the first pass just calculated + the length of the memory allocation). + + We only do this if we've actually got metadata to parse. + */ + if (isProcessingMetadata && metadataParser.metadataCount > 0) { + if (drwav__seek_from_start(pWav->onSeek, metadataStartPos, pWav->pUserData) == DRWAV_FALSE) { + return DRWAV_FALSE; + } + + result = drwav__metadata_alloc(&metadataParser, &pWav->allocationCallbacks); + if (result != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + metadataParser.stage = drwav__metadata_parser_stage_read; + + for (;;) { + drwav_chunk_header header; + drwav_uint64 metadataBytesRead; + + result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + break; + } + + metadataBytesRead = drwav__metadata_process_chunk(&metadataParser, &header, drwav_metadata_type_all_including_unknown); + + /* Move to the end of the chunk so we can keep iterating. */ + if (drwav__seek_forward(pWav->onSeek, (header.sizeInBytes + header.paddingSize) - metadataBytesRead, pWav->pUserData) == DRWAV_FALSE) { + drwav_free(metadataParser.pMetadata, &pWav->allocationCallbacks); + return DRWAV_FALSE; + } + } + + /* Getting here means we're finished parsing the metadata. */ + pWav->pMetadata = metadataParser.pMetadata; + pWav->metadataCount = metadataParser.metadataCount; + } + + + /* At this point we should be sitting on the first byte of the raw audio data. */ + + /* + I've seen a WAV file in the wild where a RIFF-ecapsulated file has the size of it's "RIFF" and + "data" chunks set to 0xFFFFFFFF when the file is definitely not that big. In this case we're + going to have to calculate the size by reading and discarding bytes, and then seeking back. We + cannot do this in sequential mode. We just assume that the rest of the file is audio data. + */ + if (dataChunkSize == 0xFFFFFFFF && (pWav->container == drwav_container_riff || pWav->container == drwav_container_rifx) && pWav->isSequentialWrite == DRWAV_FALSE) { + dataChunkSize = 0; + + for (;;) { + drwav_uint8 temp[4096]; + size_t bytesRead = pWav->onRead(pWav->pUserData, temp, sizeof(temp)); + dataChunkSize += bytesRead; + + if (bytesRead < sizeof(temp)) { + break; + } + } + } + + if (drwav__seek_from_start(pWav->onSeek, pWav->dataChunkDataPos, pWav->pUserData) == DRWAV_FALSE) { + drwav_free(pWav->pMetadata, &pWav->allocationCallbacks); + return DRWAV_FALSE; + } + + + pWav->fmt = fmt; + pWav->sampleRate = fmt.sampleRate; + pWav->channels = fmt.channels; + pWav->bitsPerSample = fmt.bitsPerSample; + pWav->bytesRemaining = dataChunkSize; + pWav->translatedFormatTag = translatedFormatTag; + pWav->dataChunkDataSize = dataChunkSize; + + if (sampleCountFromFactChunk != 0) { + pWav->totalPCMFrameCount = sampleCountFromFactChunk; + } else if (aiffFrameCount != 0) { + pWav->totalPCMFrameCount = aiffFrameCount; + } else { + drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + drwav_free(pWav->pMetadata, &pWav->allocationCallbacks); + return DRWAV_FALSE; /* Invalid file. */ + } + + pWav->totalPCMFrameCount = dataChunkSize / bytesPerFrame; + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + drwav_uint64 totalBlockHeaderSizeInBytes; + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + + /* Make sure any trailing partial block is accounted for. */ + if ((blockCount * fmt.blockAlign) < dataChunkSize) { + blockCount += 1; + } + + /* We decode two samples per byte. There will be blockCount headers in the data chunk. This is enough to know how to calculate the total PCM frame count. */ + totalBlockHeaderSizeInBytes = blockCount * (6*fmt.channels); + pWav->totalPCMFrameCount = ((dataChunkSize - totalBlockHeaderSizeInBytes) * 2) / fmt.channels; + } + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + drwav_uint64 totalBlockHeaderSizeInBytes; + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + + /* Make sure any trailing partial block is accounted for. */ + if ((blockCount * fmt.blockAlign) < dataChunkSize) { + blockCount += 1; + } + + /* We decode two samples per byte. There will be blockCount headers in the data chunk. This is enough to know how to calculate the total PCM frame count. */ + totalBlockHeaderSizeInBytes = blockCount * (4*fmt.channels); + pWav->totalPCMFrameCount = ((dataChunkSize - totalBlockHeaderSizeInBytes) * 2) / fmt.channels; + + /* The header includes a decoded sample for each channel which acts as the initial predictor sample. */ + pWav->totalPCMFrameCount += blockCount; + } + } + + /* Some formats only support a certain number of channels. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + if (pWav->channels > 2) { + drwav_free(pWav->pMetadata, &pWav->allocationCallbacks); + return DRWAV_FALSE; + } + } + + /* The number of bytes per frame must be known. If not, it's an invalid file and not decodable. */ + if (drwav_get_bytes_per_pcm_frame(pWav) == 0) { + drwav_free(pWav->pMetadata, &pWav->allocationCallbacks); + return DRWAV_FALSE; + } + +#ifdef DR_WAV_LIBSNDFILE_COMPAT + /* + I use libsndfile as a benchmark for testing, however in the version I'm using (from the Windows installer on the libsndfile website), + it appears the total sample count libsndfile uses for MS-ADPCM is incorrect. It would seem they are computing the total sample count + from the number of blocks, however this results in the inclusion of extra silent samples at the end of the last block. The correct + way to know the total sample count is to inspect the "fact" chunk, which should always be present for compressed formats, and should + always include the sample count. This little block of code below is only used to emulate the libsndfile logic so I can properly run my + correctness tests against libsndfile, and is disabled by default. + */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2)) / fmt.channels; /* x2 because two samples per byte. */ + } + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels)) / fmt.channels; + } +#endif + + return DRWAV_TRUE; +} + +DRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (!drwav_preinit(pWav, onRead, onSeek, pReadSeekUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + return drwav_init__internal(pWav, onChunk, pChunkUserData, flags); +} + +DRWAV_API drwav_bool32 drwav_init_with_metadata(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (!drwav_preinit(pWav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + return drwav_init__internal(pWav, NULL, NULL, flags | DRWAV_WITH_METADATA); +} + +DRWAV_API drwav_metadata* drwav_take_ownership_of_metadata(drwav* pWav) +{ + drwav_metadata *result = pWav->pMetadata; + + pWav->pMetadata = NULL; + pWav->metadataCount = 0; + + return result; +} + + +DRWAV_PRIVATE size_t drwav__write(drwav* pWav, const void* pData, size_t dataSize) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + /* Generic write. Assumes no byte reordering required. */ + return pWav->onWrite(pWav->pUserData, pData, dataSize); +} + +DRWAV_PRIVATE size_t drwav__write_byte(drwav* pWav, drwav_uint8 byte) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + return pWav->onWrite(pWav->pUserData, &byte, 1); +} + +DRWAV_PRIVATE size_t drwav__write_u16ne_to_le(drwav* pWav, drwav_uint16 value) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + if (!drwav__is_little_endian()) { + value = drwav__bswap16(value); + } + + return drwav__write(pWav, &value, 2); +} + +DRWAV_PRIVATE size_t drwav__write_u32ne_to_le(drwav* pWav, drwav_uint32 value) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + if (!drwav__is_little_endian()) { + value = drwav__bswap32(value); + } + + return drwav__write(pWav, &value, 4); +} + +DRWAV_PRIVATE size_t drwav__write_u64ne_to_le(drwav* pWav, drwav_uint64 value) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + if (!drwav__is_little_endian()) { + value = drwav__bswap64(value); + } + + return drwav__write(pWav, &value, 8); +} + +DRWAV_PRIVATE size_t drwav__write_f32ne_to_le(drwav* pWav, float value) +{ + union { + drwav_uint32 u32; + float f32; + } u; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + u.f32 = value; + + if (!drwav__is_little_endian()) { + u.u32 = drwav__bswap32(u.u32); + } + + return drwav__write(pWav, &u.u32, 4); +} + +DRWAV_PRIVATE size_t drwav__write_or_count(drwav* pWav, const void* pData, size_t dataSize) +{ + if (pWav == NULL) { + return dataSize; + } + + return drwav__write(pWav, pData, dataSize); +} + +DRWAV_PRIVATE size_t drwav__write_or_count_byte(drwav* pWav, drwav_uint8 byte) +{ + if (pWav == NULL) { + return 1; + } + + return drwav__write_byte(pWav, byte); +} + +DRWAV_PRIVATE size_t drwav__write_or_count_u16ne_to_le(drwav* pWav, drwav_uint16 value) +{ + if (pWav == NULL) { + return 2; + } + + return drwav__write_u16ne_to_le(pWav, value); +} + +DRWAV_PRIVATE size_t drwav__write_or_count_u32ne_to_le(drwav* pWav, drwav_uint32 value) +{ + if (pWav == NULL) { + return 4; + } + + return drwav__write_u32ne_to_le(pWav, value); +} + +#if 0 /* Unused for now. */ +DRWAV_PRIVATE size_t drwav__write_or_count_u64ne_to_le(drwav* pWav, drwav_uint64 value) +{ + if (pWav == NULL) { + return 8; + } + + return drwav__write_u64ne_to_le(pWav, value); +} +#endif + +DRWAV_PRIVATE size_t drwav__write_or_count_f32ne_to_le(drwav* pWav, float value) +{ + if (pWav == NULL) { + return 4; + } + + return drwav__write_f32ne_to_le(pWav, value); +} + +DRWAV_PRIVATE size_t drwav__write_or_count_string_to_fixed_size_buf(drwav* pWav, char* str, size_t bufFixedSize) +{ + size_t len; + + if (pWav == NULL) { + return bufFixedSize; + } + + len = drwav__strlen_clamped(str, bufFixedSize); + drwav__write_or_count(pWav, str, len); + + if (len < bufFixedSize) { + size_t i; + for (i = 0; i < bufFixedSize - len; ++i) { + drwav__write_byte(pWav, 0); + } + } + + return bufFixedSize; +} + + +/* pWav can be NULL meaning just count the bytes that would be written. */ +DRWAV_PRIVATE size_t drwav__write_or_count_metadata(drwav* pWav, drwav_metadata* pMetadatas, drwav_uint32 metadataCount) +{ + size_t bytesWritten = 0; + drwav_bool32 hasListAdtl = DRWAV_FALSE; + drwav_bool32 hasListInfo = DRWAV_FALSE; + drwav_uint32 iMetadata; + + if (pMetadatas == NULL || metadataCount == 0) { + return 0; + } + + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + drwav_uint32 chunkSize = 0; + + if ((pMetadata->type & drwav_metadata_type_list_all_info_strings) || (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_info_list)) { + hasListInfo = DRWAV_TRUE; + } + + if ((pMetadata->type & drwav_metadata_type_list_all_adtl) || (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_adtl_list)) { + hasListAdtl = DRWAV_TRUE; + } + + switch (pMetadata->type) { + case drwav_metadata_type_smpl: + { + drwav_uint32 iLoop; + + chunkSize = DRWAV_SMPL_BYTES + DRWAV_SMPL_LOOP_BYTES * pMetadata->data.smpl.sampleLoopCount + pMetadata->data.smpl.samplerSpecificDataSizeInBytes; + + bytesWritten += drwav__write_or_count(pWav, "smpl", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.manufacturerId); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.productId); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.samplePeriodNanoseconds); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.midiUnityNote); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.midiPitchFraction); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.smpteFormat); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.smpteOffset); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.sampleLoopCount); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.samplerSpecificDataSizeInBytes); + + for (iLoop = 0; iLoop < pMetadata->data.smpl.sampleLoopCount; ++iLoop) { + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].cuePointId); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].type); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].firstSampleByteOffset); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].lastSampleByteOffset); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].sampleFraction); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.smpl.pLoops[iLoop].playCount); + } + + if (pMetadata->data.smpl.samplerSpecificDataSizeInBytes > 0) { + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.smpl.pSamplerSpecificData, pMetadata->data.smpl.samplerSpecificDataSizeInBytes); + } + } break; + + case drwav_metadata_type_inst: + { + chunkSize = DRWAV_INST_BYTES; + + bytesWritten += drwav__write_or_count(pWav, "inst", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.midiUnityNote, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.fineTuneCents, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.gainDecibels, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.lowNote, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.highNote, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.lowVelocity, 1); + bytesWritten += drwav__write_or_count(pWav, &pMetadata->data.inst.highVelocity, 1); + } break; + + case drwav_metadata_type_cue: + { + drwav_uint32 iCuePoint; + + chunkSize = DRWAV_CUE_BYTES + DRWAV_CUE_POINT_BYTES * pMetadata->data.cue.cuePointCount; + + bytesWritten += drwav__write_or_count(pWav, "cue ", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.cuePointCount); + for (iCuePoint = 0; iCuePoint < pMetadata->data.cue.cuePointCount; ++iCuePoint) { + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].id); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].playOrderPosition); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].blockStart); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset); + } + } break; + + case drwav_metadata_type_acid: + { + chunkSize = DRWAV_ACID_BYTES; + + bytesWritten += drwav__write_or_count(pWav, "acid", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.acid.flags); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.midiUnityNote); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.reserved1); + bytesWritten += drwav__write_or_count_f32ne_to_le(pWav, pMetadata->data.acid.reserved2); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.acid.numBeats); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.meterDenominator); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.acid.meterNumerator); + bytesWritten += drwav__write_or_count_f32ne_to_le(pWav, pMetadata->data.acid.tempo); + } break; + + case drwav_metadata_type_bext: + { + char reservedBuf[DRWAV_BEXT_RESERVED_BYTES]; + drwav_uint32 timeReferenceLow; + drwav_uint32 timeReferenceHigh; + + chunkSize = DRWAV_BEXT_BYTES + pMetadata->data.bext.codingHistorySize; + + bytesWritten += drwav__write_or_count(pWav, "bext", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + + bytesWritten += drwav__write_or_count_string_to_fixed_size_buf(pWav, pMetadata->data.bext.pDescription, DRWAV_BEXT_DESCRIPTION_BYTES); + bytesWritten += drwav__write_or_count_string_to_fixed_size_buf(pWav, pMetadata->data.bext.pOriginatorName, DRWAV_BEXT_ORIGINATOR_NAME_BYTES); + bytesWritten += drwav__write_or_count_string_to_fixed_size_buf(pWav, pMetadata->data.bext.pOriginatorReference, DRWAV_BEXT_ORIGINATOR_REF_BYTES); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pOriginationDate, sizeof(pMetadata->data.bext.pOriginationDate)); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pOriginationTime, sizeof(pMetadata->data.bext.pOriginationTime)); + + timeReferenceLow = (drwav_uint32)(pMetadata->data.bext.timeReference & 0xFFFFFFFF); + timeReferenceHigh = (drwav_uint32)(pMetadata->data.bext.timeReference >> 32); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, timeReferenceLow); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, timeReferenceHigh); + + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.version); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pUMID, DRWAV_BEXT_UMID_BYTES); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.loudnessValue); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.loudnessRange); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxTruePeakLevel); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxMomentaryLoudness); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxShortTermLoudness); + + DRWAV_ZERO_MEMORY(reservedBuf, sizeof(reservedBuf)); + bytesWritten += drwav__write_or_count(pWav, reservedBuf, sizeof(reservedBuf)); + + if (pMetadata->data.bext.codingHistorySize > 0) { + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pCodingHistory, pMetadata->data.bext.codingHistorySize); + } + } break; + + case drwav_metadata_type_unknown: + { + if (pMetadata->data.unknown.chunkLocation == drwav_metadata_location_top_level) { + chunkSize = pMetadata->data.unknown.dataSizeInBytes; + + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.id, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.pData, pMetadata->data.unknown.dataSizeInBytes); + } + } break; + + default: break; + } + if ((chunkSize % 2) != 0) { + bytesWritten += drwav__write_or_count_byte(pWav, 0); + } + } + + if (hasListInfo) { + drwav_uint32 chunkSize = 4; /* Start with 4 bytes for "INFO". */ + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + + if ((pMetadata->type & drwav_metadata_type_list_all_info_strings)) { + chunkSize += 8; /* For id and string size. */ + chunkSize += pMetadata->data.infoText.stringLength + 1; /* Include null terminator. */ + } else if (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_info_list) { + chunkSize += 8; /* For id string size. */ + chunkSize += pMetadata->data.unknown.dataSizeInBytes; + } + + if ((chunkSize % 2) != 0) { + chunkSize += 1; + } + } + + bytesWritten += drwav__write_or_count(pWav, "LIST", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count(pWav, "INFO", 4); + + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + drwav_uint32 subchunkSize = 0; + + if (pMetadata->type & drwav_metadata_type_list_all_info_strings) { + const char* pID = NULL; + + switch (pMetadata->type) { + case drwav_metadata_type_list_info_software: pID = "ISFT"; break; + case drwav_metadata_type_list_info_copyright: pID = "ICOP"; break; + case drwav_metadata_type_list_info_title: pID = "INAM"; break; + case drwav_metadata_type_list_info_artist: pID = "IART"; break; + case drwav_metadata_type_list_info_comment: pID = "ICMT"; break; + case drwav_metadata_type_list_info_date: pID = "ICRD"; break; + case drwav_metadata_type_list_info_genre: pID = "IGNR"; break; + case drwav_metadata_type_list_info_album: pID = "IPRD"; break; + case drwav_metadata_type_list_info_tracknumber: pID = "ITRK"; break; + default: break; + } + + DRWAV_ASSERT(pID != NULL); + + if (pMetadata->data.infoText.stringLength) { + subchunkSize = pMetadata->data.infoText.stringLength + 1; + bytesWritten += drwav__write_or_count(pWav, pID, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.infoText.pString, pMetadata->data.infoText.stringLength); + bytesWritten += drwav__write_or_count_byte(pWav, '\0'); + } + } else if (pMetadata->type == drwav_metadata_type_unknown && pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_info_list) { + if (pMetadata->data.unknown.dataSizeInBytes) { + subchunkSize = pMetadata->data.unknown.dataSizeInBytes; + + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.id, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.unknown.dataSizeInBytes); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.pData, subchunkSize); + } + } + + if ((subchunkSize % 2) != 0) { + bytesWritten += drwav__write_or_count_byte(pWav, 0); + } + } + } + + if (hasListAdtl) { + drwav_uint32 chunkSize = 4; /* start with 4 bytes for "adtl" */ + + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + + switch (pMetadata->type) + { + case drwav_metadata_type_list_label: + case drwav_metadata_type_list_note: + { + chunkSize += 8; /* for id and chunk size */ + chunkSize += DRWAV_LIST_LABEL_OR_NOTE_BYTES; + + if (pMetadata->data.labelOrNote.stringLength > 0) { + chunkSize += pMetadata->data.labelOrNote.stringLength + 1; + } + } break; + + case drwav_metadata_type_list_labelled_cue_region: + { + chunkSize += 8; /* for id and chunk size */ + chunkSize += DRWAV_LIST_LABELLED_TEXT_BYTES; + + if (pMetadata->data.labelledCueRegion.stringLength > 0) { + chunkSize += pMetadata->data.labelledCueRegion.stringLength + 1; + } + } break; + + case drwav_metadata_type_unknown: + { + if (pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_adtl_list) { + chunkSize += 8; /* for id and chunk size */ + chunkSize += pMetadata->data.unknown.dataSizeInBytes; + } + } break; + + default: break; + } + + if ((chunkSize % 2) != 0) { + chunkSize += 1; + } + } + + bytesWritten += drwav__write_or_count(pWav, "LIST", 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, chunkSize); + bytesWritten += drwav__write_or_count(pWav, "adtl", 4); + + for (iMetadata = 0; iMetadata < metadataCount; ++iMetadata) { + drwav_metadata* pMetadata = &pMetadatas[iMetadata]; + drwav_uint32 subchunkSize = 0; + + switch (pMetadata->type) + { + case drwav_metadata_type_list_label: + case drwav_metadata_type_list_note: + { + if (pMetadata->data.labelOrNote.stringLength > 0) { + const char *pID = NULL; + + if (pMetadata->type == drwav_metadata_type_list_label) { + pID = "labl"; + } + else if (pMetadata->type == drwav_metadata_type_list_note) { + pID = "note"; + } + + DRWAV_ASSERT(pID != NULL); + DRWAV_ASSERT(pMetadata->data.labelOrNote.pString != NULL); + + subchunkSize = DRWAV_LIST_LABEL_OR_NOTE_BYTES; + + bytesWritten += drwav__write_or_count(pWav, pID, 4); + subchunkSize += pMetadata->data.labelOrNote.stringLength + 1; + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); + + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.labelOrNote.cuePointId); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.labelOrNote.pString, pMetadata->data.labelOrNote.stringLength); + bytesWritten += drwav__write_or_count_byte(pWav, '\0'); + } + } break; + + case drwav_metadata_type_list_labelled_cue_region: + { + subchunkSize = DRWAV_LIST_LABELLED_TEXT_BYTES; + + bytesWritten += drwav__write_or_count(pWav, "ltxt", 4); + if (pMetadata->data.labelledCueRegion.stringLength > 0) { + subchunkSize += pMetadata->data.labelledCueRegion.stringLength + 1; + } + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.labelledCueRegion.cuePointId); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, pMetadata->data.labelledCueRegion.sampleLength); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.labelledCueRegion.purposeId, 4); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.country); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.language); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.dialect); + bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.labelledCueRegion.codePage); + + if (pMetadata->data.labelledCueRegion.stringLength > 0) { + DRWAV_ASSERT(pMetadata->data.labelledCueRegion.pString != NULL); + + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.labelledCueRegion.pString, pMetadata->data.labelledCueRegion.stringLength); + bytesWritten += drwav__write_or_count_byte(pWav, '\0'); + } + } break; + + case drwav_metadata_type_unknown: + { + if (pMetadata->data.unknown.chunkLocation == drwav_metadata_location_inside_adtl_list) { + subchunkSize = pMetadata->data.unknown.dataSizeInBytes; + + DRWAV_ASSERT(pMetadata->data.unknown.pData != NULL); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.id, 4); + bytesWritten += drwav__write_or_count_u32ne_to_le(pWav, subchunkSize); + bytesWritten += drwav__write_or_count(pWav, pMetadata->data.unknown.pData, subchunkSize); + } + } break; + + default: break; + } + + if ((subchunkSize % 2) != 0) { + bytesWritten += drwav__write_or_count_byte(pWav, 0); + } + } + } + + DRWAV_ASSERT((bytesWritten % 2) == 0); + + return bytesWritten; +} + +DRWAV_PRIVATE drwav_uint32 drwav__riff_chunk_size_riff(drwav_uint64 dataChunkSize, drwav_metadata* pMetadata, drwav_uint32 metadataCount) +{ + drwav_uint64 chunkSize = 4 + 24 + (drwav_uint64)drwav__write_or_count_metadata(NULL, pMetadata, metadataCount) + 8 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = "WAVE". 24 = "fmt " chunk. 8 = "data" + u32 data size. */ + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; + } + + return (drwav_uint32)chunkSize; /* Safe cast due to the clamp above. */ +} + +DRWAV_PRIVATE drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize) +{ + if (dataChunkSize <= 0xFFFFFFFFUL) { + return (drwav_uint32)dataChunkSize; + } else { + return 0xFFFFFFFFUL; + } +} + +DRWAV_PRIVATE drwav_uint64 drwav__riff_chunk_size_w64(drwav_uint64 dataChunkSize) +{ + drwav_uint64 dataSubchunkPaddingSize = drwav__chunk_padding_size_w64(dataChunkSize); + + return 80 + 24 + dataChunkSize + dataSubchunkPaddingSize; /* +24 because W64 includes the size of the GUID and size fields. */ +} + +DRWAV_PRIVATE drwav_uint64 drwav__data_chunk_size_w64(drwav_uint64 dataChunkSize) +{ + return 24 + dataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ +} + +DRWAV_PRIVATE drwav_uint64 drwav__riff_chunk_size_rf64(drwav_uint64 dataChunkSize, drwav_metadata *metadata, drwav_uint32 numMetadata) +{ + drwav_uint64 chunkSize = 4 + 36 + 24 + (drwav_uint64)drwav__write_or_count_metadata(NULL, metadata, numMetadata) + 8 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = "WAVE". 36 = "ds64" chunk. 24 = "fmt " chunk. 8 = "data" + u32 data size. */ + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; + } + + return chunkSize; +} + +DRWAV_PRIVATE drwav_uint64 drwav__data_chunk_size_rf64(drwav_uint64 dataChunkSize) +{ + return dataChunkSize; +} + + + +DRWAV_PRIVATE drwav_bool32 drwav_preinit_write(drwav* pWav, const drwav_data_format* pFormat, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pWav == NULL || onWrite == NULL) { + return DRWAV_FALSE; + } + + if (!isSequential && onSeek == NULL) { + return DRWAV_FALSE; /* <-- onSeek is required when in non-sequential mode. */ + } + + /* Not currently supporting compressed formats. Will need to add support for the "fact" chunk before we enable this. */ + if (pFormat->format == DR_WAVE_FORMAT_EXTENSIBLE) { + return DRWAV_FALSE; + } + if (pFormat->format == DR_WAVE_FORMAT_ADPCM || pFormat->format == DR_WAVE_FORMAT_DVI_ADPCM) { + return DRWAV_FALSE; + } + + DRWAV_ZERO_MEMORY(pWav, sizeof(*pWav)); + pWav->onWrite = onWrite; + pWav->onSeek = onSeek; + pWav->pUserData = pUserData; + pWav->allocationCallbacks = drwav_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); + + if (pWav->allocationCallbacks.onFree == NULL || (pWav->allocationCallbacks.onMalloc == NULL && pWav->allocationCallbacks.onRealloc == NULL)) { + return DRWAV_FALSE; /* Invalid allocation callbacks. */ + } + + pWav->fmt.formatTag = (drwav_uint16)pFormat->format; + pWav->fmt.channels = (drwav_uint16)pFormat->channels; + pWav->fmt.sampleRate = pFormat->sampleRate; + pWav->fmt.avgBytesPerSec = (drwav_uint32)((pFormat->bitsPerSample * pFormat->sampleRate * pFormat->channels) / 8); + pWav->fmt.blockAlign = (drwav_uint16)((pFormat->channels * pFormat->bitsPerSample) / 8); + pWav->fmt.bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; + pWav->fmt.extendedSize = 0; + pWav->isSequentialWrite = isSequential; + + return DRWAV_TRUE; +} + + +DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) +{ + /* The function assumes drwav_preinit_write() was called beforehand. */ + + size_t runningPos = 0; + drwav_uint64 initialDataChunkSize = 0; + drwav_uint64 chunkSizeFMT; + + /* + The initial values for the "RIFF" and "data" chunks depends on whether or not we are initializing in sequential mode or not. In + sequential mode we set this to its final values straight away since they can be calculated from the total sample count. In non- + sequential mode we initialize it all to zero and fill it out in drwav_uninit() using a backwards seek. + */ + if (pWav->isSequentialWrite) { + initialDataChunkSize = (totalSampleCount * pWav->fmt.bitsPerSample) / 8; + + /* + The RIFF container has a limit on the number of samples. drwav is not allowing this. There's no practical limits for Wave64 + so for the sake of simplicity I'm not doing any validation for that. + */ + if (pFormat->container == drwav_container_riff) { + if (initialDataChunkSize > (0xFFFFFFFFUL - 36)) { + return DRWAV_FALSE; /* Not enough room to store every sample. */ + } + } + } + + pWav->dataChunkDataSizeTargetWrite = initialDataChunkSize; + + + /* "RIFF" chunk. */ + if (pFormat->container == drwav_container_riff) { + drwav_uint32 chunkSizeRIFF = 28 + (drwav_uint32)initialDataChunkSize; /* +28 = "WAVE" + [sizeof "fmt " chunk] */ + runningPos += drwav__write(pWav, "RIFF", 4); + runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeRIFF); + runningPos += drwav__write(pWav, "WAVE", 4); + } else if (pFormat->container == drwav_container_w64) { + drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ + runningPos += drwav__write(pWav, drwavGUID_W64_RIFF, 16); + runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeRIFF); + runningPos += drwav__write(pWav, drwavGUID_W64_WAVE, 16); + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "RF64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always 0xFFFFFFFF for RF64. Set to a proper value in the "ds64" chunk. */ + runningPos += drwav__write(pWav, "WAVE", 4); + } else { + return DRWAV_FALSE; /* Container not supported for writing. */ + } + + + /* "ds64" chunk (RF64 only). */ + if (pFormat->container == drwav_container_rf64) { + drwav_uint32 initialds64ChunkSize = 28; /* 28 = [Size of RIFF (8 bytes)] + [Size of DATA (8 bytes)] + [Sample Count (8 bytes)] + [Table Length (4 bytes)]. Table length always set to 0. */ + drwav_uint64 initialRiffChunkSize = 8 + initialds64ChunkSize + initialDataChunkSize; /* +8 for the ds64 header. */ + + runningPos += drwav__write(pWav, "ds64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, initialds64ChunkSize); /* Size of ds64. */ + runningPos += drwav__write_u64ne_to_le(pWav, initialRiffChunkSize); /* Size of RIFF. Set to true value at the end. */ + runningPos += drwav__write_u64ne_to_le(pWav, initialDataChunkSize); /* Size of DATA. Set to true value at the end. */ + runningPos += drwav__write_u64ne_to_le(pWav, totalSampleCount); /* Sample count. */ + runningPos += drwav__write_u32ne_to_le(pWav, 0); /* Table length. Always set to zero in our case since we're not doing any other chunks than "DATA". */ + } + + + /* "fmt " chunk. */ + if (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64) { + chunkSizeFMT = 16; + runningPos += drwav__write(pWav, "fmt ", 4); + runningPos += drwav__write_u32ne_to_le(pWav, (drwav_uint32)chunkSizeFMT); + } else if (pFormat->container == drwav_container_w64) { + chunkSizeFMT = 40; + runningPos += drwav__write(pWav, drwavGUID_W64_FMT, 16); + runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeFMT); + } + + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.formatTag); + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.channels); + runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.sampleRate); + runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.avgBytesPerSec); + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.blockAlign); + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.bitsPerSample); + + /* TODO: is a 'fact' chunk required for DR_WAVE_FORMAT_IEEE_FLOAT? */ + + if (!pWav->isSequentialWrite && pWav->pMetadata != NULL && pWav->metadataCount > 0 && (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64)) { + runningPos += drwav__write_or_count_metadata(pWav, pWav->pMetadata, pWav->metadataCount); + } + + pWav->dataChunkDataPos = runningPos; + + /* "data" chunk. */ + if (pFormat->container == drwav_container_riff) { + drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize; + runningPos += drwav__write(pWav, "data", 4); + runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeDATA); + } else if (pFormat->container == drwav_container_w64) { + drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ + runningPos += drwav__write(pWav, drwavGUID_W64_DATA, 16); + runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeDATA); + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "data", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always set to 0xFFFFFFFF for RF64. The true size of the data chunk is specified in the ds64 chunk. */ + } + + /* Set some properties for the client's convenience. */ + pWav->container = pFormat->container; + pWav->channels = (drwav_uint16)pFormat->channels; + pWav->sampleRate = pFormat->sampleRate; + pWav->bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; + pWav->translatedFormatTag = (drwav_uint16)pFormat->format; + pWav->dataChunkDataPos = runningPos; + + return DRWAV_TRUE; +} + + +DRWAV_API drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (!drwav_preinit_write(pWav, pFormat, DRWAV_FALSE, onWrite, onSeek, pUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + return drwav_init_write__internal(pWav, pFormat, 0); /* DRWAV_FALSE = Not Sequential */ +} + +DRWAV_API drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (!drwav_preinit_write(pWav, pFormat, DRWAV_TRUE, onWrite, NULL, pUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + return drwav_init_write__internal(pWav, pFormat, totalSampleCount); /* DRWAV_TRUE = Sequential */ +} + +DRWAV_API drwav_bool32 drwav_init_write_sequential_pcm_frames(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pFormat == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_write_sequential(pWav, pFormat, totalPCMFrameCount*pFormat->channels, onWrite, pUserData, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_write_with_metadata(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks, drwav_metadata* pMetadata, drwav_uint32 metadataCount) +{ + if (!drwav_preinit_write(pWav, pFormat, DRWAV_FALSE, onWrite, onSeek, pUserData, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + pWav->pMetadata = pMetadata; + pWav->metadataCount = metadataCount; + + return drwav_init_write__internal(pWav, pFormat, 0); +} + + +DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pFormat, drwav_uint64 totalFrameCount, drwav_metadata* pMetadata, drwav_uint32 metadataCount) +{ + /* Casting totalFrameCount to drwav_int64 for VC6 compatibility. No issues in practice because nobody is going to exhaust the whole 63 bits. */ + drwav_uint64 targetDataSizeBytes = (drwav_uint64)((drwav_int64)totalFrameCount * pFormat->channels * pFormat->bitsPerSample/8.0); + drwav_uint64 riffChunkSizeBytes; + drwav_uint64 fileSizeBytes = 0; + + if (pFormat->container == drwav_container_riff) { + riffChunkSizeBytes = drwav__riff_chunk_size_riff(targetDataSizeBytes, pMetadata, metadataCount); + fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ + } else if (pFormat->container == drwav_container_w64) { + riffChunkSizeBytes = drwav__riff_chunk_size_w64(targetDataSizeBytes); + fileSizeBytes = riffChunkSizeBytes; + } else if (pFormat->container == drwav_container_rf64) { + riffChunkSizeBytes = drwav__riff_chunk_size_rf64(targetDataSizeBytes, pMetadata, metadataCount); + fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ + } + + return fileSizeBytes; +} + + +#ifndef DR_WAV_NO_STDIO + +/* Errno */ +/* drwav_result_from_errno() is only used for fopen() and wfopen() so putting it inside DR_WAV_NO_STDIO for now. If something else needs this later we can move it out. */ +#include +DRWAV_PRIVATE drwav_result drwav_result_from_errno(int e) +{ + switch (e) + { + case 0: return DRWAV_SUCCESS; + #ifdef EPERM + case EPERM: return DRWAV_INVALID_OPERATION; + #endif + #ifdef ENOENT + case ENOENT: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef ESRCH + case ESRCH: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef EINTR + case EINTR: return DRWAV_INTERRUPT; + #endif + #ifdef EIO + case EIO: return DRWAV_IO_ERROR; + #endif + #ifdef ENXIO + case ENXIO: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef E2BIG + case E2BIG: return DRWAV_INVALID_ARGS; + #endif + #ifdef ENOEXEC + case ENOEXEC: return DRWAV_INVALID_FILE; + #endif + #ifdef EBADF + case EBADF: return DRWAV_INVALID_FILE; + #endif + #ifdef ECHILD + case ECHILD: return DRWAV_ERROR; + #endif + #ifdef EAGAIN + case EAGAIN: return DRWAV_UNAVAILABLE; + #endif + #ifdef ENOMEM + case ENOMEM: return DRWAV_OUT_OF_MEMORY; + #endif + #ifdef EACCES + case EACCES: return DRWAV_ACCESS_DENIED; + #endif + #ifdef EFAULT + case EFAULT: return DRWAV_BAD_ADDRESS; + #endif + #ifdef ENOTBLK + case ENOTBLK: return DRWAV_ERROR; + #endif + #ifdef EBUSY + case EBUSY: return DRWAV_BUSY; + #endif + #ifdef EEXIST + case EEXIST: return DRWAV_ALREADY_EXISTS; + #endif + #ifdef EXDEV + case EXDEV: return DRWAV_ERROR; + #endif + #ifdef ENODEV + case ENODEV: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef ENOTDIR + case ENOTDIR: return DRWAV_NOT_DIRECTORY; + #endif + #ifdef EISDIR + case EISDIR: return DRWAV_IS_DIRECTORY; + #endif + #ifdef EINVAL + case EINVAL: return DRWAV_INVALID_ARGS; + #endif + #ifdef ENFILE + case ENFILE: return DRWAV_TOO_MANY_OPEN_FILES; + #endif + #ifdef EMFILE + case EMFILE: return DRWAV_TOO_MANY_OPEN_FILES; + #endif + #ifdef ENOTTY + case ENOTTY: return DRWAV_INVALID_OPERATION; + #endif + #ifdef ETXTBSY + case ETXTBSY: return DRWAV_BUSY; + #endif + #ifdef EFBIG + case EFBIG: return DRWAV_TOO_BIG; + #endif + #ifdef ENOSPC + case ENOSPC: return DRWAV_NO_SPACE; + #endif + #ifdef ESPIPE + case ESPIPE: return DRWAV_BAD_SEEK; + #endif + #ifdef EROFS + case EROFS: return DRWAV_ACCESS_DENIED; + #endif + #ifdef EMLINK + case EMLINK: return DRWAV_TOO_MANY_LINKS; + #endif + #ifdef EPIPE + case EPIPE: return DRWAV_BAD_PIPE; + #endif + #ifdef EDOM + case EDOM: return DRWAV_OUT_OF_RANGE; + #endif + #ifdef ERANGE + case ERANGE: return DRWAV_OUT_OF_RANGE; + #endif + #ifdef EDEADLK + case EDEADLK: return DRWAV_DEADLOCK; + #endif + #ifdef ENAMETOOLONG + case ENAMETOOLONG: return DRWAV_PATH_TOO_LONG; + #endif + #ifdef ENOLCK + case ENOLCK: return DRWAV_ERROR; + #endif + #ifdef ENOSYS + case ENOSYS: return DRWAV_NOT_IMPLEMENTED; + #endif + #ifdef ENOTEMPTY + case ENOTEMPTY: return DRWAV_DIRECTORY_NOT_EMPTY; + #endif + #ifdef ELOOP + case ELOOP: return DRWAV_TOO_MANY_LINKS; + #endif + #ifdef ENOMSG + case ENOMSG: return DRWAV_NO_MESSAGE; + #endif + #ifdef EIDRM + case EIDRM: return DRWAV_ERROR; + #endif + #ifdef ECHRNG + case ECHRNG: return DRWAV_ERROR; + #endif + #ifdef EL2NSYNC + case EL2NSYNC: return DRWAV_ERROR; + #endif + #ifdef EL3HLT + case EL3HLT: return DRWAV_ERROR; + #endif + #ifdef EL3RST + case EL3RST: return DRWAV_ERROR; + #endif + #ifdef ELNRNG + case ELNRNG: return DRWAV_OUT_OF_RANGE; + #endif + #ifdef EUNATCH + case EUNATCH: return DRWAV_ERROR; + #endif + #ifdef ENOCSI + case ENOCSI: return DRWAV_ERROR; + #endif + #ifdef EL2HLT + case EL2HLT: return DRWAV_ERROR; + #endif + #ifdef EBADE + case EBADE: return DRWAV_ERROR; + #endif + #ifdef EBADR + case EBADR: return DRWAV_ERROR; + #endif + #ifdef EXFULL + case EXFULL: return DRWAV_ERROR; + #endif + #ifdef ENOANO + case ENOANO: return DRWAV_ERROR; + #endif + #ifdef EBADRQC + case EBADRQC: return DRWAV_ERROR; + #endif + #ifdef EBADSLT + case EBADSLT: return DRWAV_ERROR; + #endif + #ifdef EBFONT + case EBFONT: return DRWAV_INVALID_FILE; + #endif + #ifdef ENOSTR + case ENOSTR: return DRWAV_ERROR; + #endif + #ifdef ENODATA + case ENODATA: return DRWAV_NO_DATA_AVAILABLE; + #endif + #ifdef ETIME + case ETIME: return DRWAV_TIMEOUT; + #endif + #ifdef ENOSR + case ENOSR: return DRWAV_NO_DATA_AVAILABLE; + #endif + #ifdef ENONET + case ENONET: return DRWAV_NO_NETWORK; + #endif + #ifdef ENOPKG + case ENOPKG: return DRWAV_ERROR; + #endif + #ifdef EREMOTE + case EREMOTE: return DRWAV_ERROR; + #endif + #ifdef ENOLINK + case ENOLINK: return DRWAV_ERROR; + #endif + #ifdef EADV + case EADV: return DRWAV_ERROR; + #endif + #ifdef ESRMNT + case ESRMNT: return DRWAV_ERROR; + #endif + #ifdef ECOMM + case ECOMM: return DRWAV_ERROR; + #endif + #ifdef EPROTO + case EPROTO: return DRWAV_ERROR; + #endif + #ifdef EMULTIHOP + case EMULTIHOP: return DRWAV_ERROR; + #endif + #ifdef EDOTDOT + case EDOTDOT: return DRWAV_ERROR; + #endif + #ifdef EBADMSG + case EBADMSG: return DRWAV_BAD_MESSAGE; + #endif + #ifdef EOVERFLOW + case EOVERFLOW: return DRWAV_TOO_BIG; + #endif + #ifdef ENOTUNIQ + case ENOTUNIQ: return DRWAV_NOT_UNIQUE; + #endif + #ifdef EBADFD + case EBADFD: return DRWAV_ERROR; + #endif + #ifdef EREMCHG + case EREMCHG: return DRWAV_ERROR; + #endif + #ifdef ELIBACC + case ELIBACC: return DRWAV_ACCESS_DENIED; + #endif + #ifdef ELIBBAD + case ELIBBAD: return DRWAV_INVALID_FILE; + #endif + #ifdef ELIBSCN + case ELIBSCN: return DRWAV_INVALID_FILE; + #endif + #ifdef ELIBMAX + case ELIBMAX: return DRWAV_ERROR; + #endif + #ifdef ELIBEXEC + case ELIBEXEC: return DRWAV_ERROR; + #endif + #ifdef EILSEQ + case EILSEQ: return DRWAV_INVALID_DATA; + #endif + #ifdef ERESTART + case ERESTART: return DRWAV_ERROR; + #endif + #ifdef ESTRPIPE + case ESTRPIPE: return DRWAV_ERROR; + #endif + #ifdef EUSERS + case EUSERS: return DRWAV_ERROR; + #endif + #ifdef ENOTSOCK + case ENOTSOCK: return DRWAV_NOT_SOCKET; + #endif + #ifdef EDESTADDRREQ + case EDESTADDRREQ: return DRWAV_NO_ADDRESS; + #endif + #ifdef EMSGSIZE + case EMSGSIZE: return DRWAV_TOO_BIG; + #endif + #ifdef EPROTOTYPE + case EPROTOTYPE: return DRWAV_BAD_PROTOCOL; + #endif + #ifdef ENOPROTOOPT + case ENOPROTOOPT: return DRWAV_PROTOCOL_UNAVAILABLE; + #endif + #ifdef EPROTONOSUPPORT + case EPROTONOSUPPORT: return DRWAV_PROTOCOL_NOT_SUPPORTED; + #endif + #ifdef ESOCKTNOSUPPORT + case ESOCKTNOSUPPORT: return DRWAV_SOCKET_NOT_SUPPORTED; + #endif + #ifdef EOPNOTSUPP + case EOPNOTSUPP: return DRWAV_INVALID_OPERATION; + #endif + #ifdef EPFNOSUPPORT + case EPFNOSUPPORT: return DRWAV_PROTOCOL_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EAFNOSUPPORT + case EAFNOSUPPORT: return DRWAV_ADDRESS_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EADDRINUSE + case EADDRINUSE: return DRWAV_ALREADY_IN_USE; + #endif + #ifdef EADDRNOTAVAIL + case EADDRNOTAVAIL: return DRWAV_ERROR; + #endif + #ifdef ENETDOWN + case ENETDOWN: return DRWAV_NO_NETWORK; + #endif + #ifdef ENETUNREACH + case ENETUNREACH: return DRWAV_NO_NETWORK; + #endif + #ifdef ENETRESET + case ENETRESET: return DRWAV_NO_NETWORK; + #endif + #ifdef ECONNABORTED + case ECONNABORTED: return DRWAV_NO_NETWORK; + #endif + #ifdef ECONNRESET + case ECONNRESET: return DRWAV_CONNECTION_RESET; + #endif + #ifdef ENOBUFS + case ENOBUFS: return DRWAV_NO_SPACE; + #endif + #ifdef EISCONN + case EISCONN: return DRWAV_ALREADY_CONNECTED; + #endif + #ifdef ENOTCONN + case ENOTCONN: return DRWAV_NOT_CONNECTED; + #endif + #ifdef ESHUTDOWN + case ESHUTDOWN: return DRWAV_ERROR; + #endif + #ifdef ETOOMANYREFS + case ETOOMANYREFS: return DRWAV_ERROR; + #endif + #ifdef ETIMEDOUT + case ETIMEDOUT: return DRWAV_TIMEOUT; + #endif + #ifdef ECONNREFUSED + case ECONNREFUSED: return DRWAV_CONNECTION_REFUSED; + #endif + #ifdef EHOSTDOWN + case EHOSTDOWN: return DRWAV_NO_HOST; + #endif + #ifdef EHOSTUNREACH + case EHOSTUNREACH: return DRWAV_NO_HOST; + #endif + #ifdef EALREADY + case EALREADY: return DRWAV_IN_PROGRESS; + #endif + #ifdef EINPROGRESS + case EINPROGRESS: return DRWAV_IN_PROGRESS; + #endif + #ifdef ESTALE + case ESTALE: return DRWAV_INVALID_FILE; + #endif + #ifdef EUCLEAN + case EUCLEAN: return DRWAV_ERROR; + #endif + #ifdef ENOTNAM + case ENOTNAM: return DRWAV_ERROR; + #endif + #ifdef ENAVAIL + case ENAVAIL: return DRWAV_ERROR; + #endif + #ifdef EISNAM + case EISNAM: return DRWAV_ERROR; + #endif + #ifdef EREMOTEIO + case EREMOTEIO: return DRWAV_IO_ERROR; + #endif + #ifdef EDQUOT + case EDQUOT: return DRWAV_NO_SPACE; + #endif + #ifdef ENOMEDIUM + case ENOMEDIUM: return DRWAV_DOES_NOT_EXIST; + #endif + #ifdef EMEDIUMTYPE + case EMEDIUMTYPE: return DRWAV_ERROR; + #endif + #ifdef ECANCELED + case ECANCELED: return DRWAV_CANCELLED; + #endif + #ifdef ENOKEY + case ENOKEY: return DRWAV_ERROR; + #endif + #ifdef EKEYEXPIRED + case EKEYEXPIRED: return DRWAV_ERROR; + #endif + #ifdef EKEYREVOKED + case EKEYREVOKED: return DRWAV_ERROR; + #endif + #ifdef EKEYREJECTED + case EKEYREJECTED: return DRWAV_ERROR; + #endif + #ifdef EOWNERDEAD + case EOWNERDEAD: return DRWAV_ERROR; + #endif + #ifdef ENOTRECOVERABLE + case ENOTRECOVERABLE: return DRWAV_ERROR; + #endif + #ifdef ERFKILL + case ERFKILL: return DRWAV_ERROR; + #endif + #ifdef EHWPOISON + case EHWPOISON: return DRWAV_ERROR; + #endif + default: return DRWAV_ERROR; + } +} +/* End Errno */ + +/* fopen */ +DRWAV_PRIVATE drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err; +#endif + + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRWAV_INVALID_ARGS; + } + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + err = fopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drwav_result_from_errno(err); + } +#else +#if defined(_WIN32) || defined(__APPLE__) + *ppFile = fopen(pFilePath, pOpenMode); +#else + #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) + *ppFile = fopen64(pFilePath, pOpenMode); + #else + *ppFile = fopen(pFilePath, pOpenMode); + #endif +#endif + if (*ppFile == NULL) { + drwav_result result = drwav_result_from_errno(errno); + if (result == DRWAV_SUCCESS) { + result = DRWAV_ERROR; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ + } + + return result; + } +#endif + + return DRWAV_SUCCESS; +} + +/* +_wfopen() isn't always available in all compilation environments. + + * Windows only. + * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). + * MinGW-64 (both 32- and 64-bit) seems to support it. + * MinGW wraps it in !defined(__STRICT_ANSI__). + * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). + +This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() +fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. +*/ +#if defined(_WIN32) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) + #define DRWAV_HAS_WFOPEN + #endif +#endif + +#ifndef DR_WAV_NO_WCHAR +DRWAV_PRIVATE drwav_result drwav_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRWAV_INVALID_ARGS; + } + +#if defined(DRWAV_HAS_WFOPEN) + { + /* Use _wfopen() on Windows. */ + #if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drwav_result_from_errno(err); + } + #else + *ppFile = _wfopen(pFilePath, pOpenMode); + if (*ppFile == NULL) { + return drwav_result_from_errno(errno); + } + #endif + (void)pAllocationCallbacks; + } +#else + /* + Use fopen() on anything other than Windows. Requires a conversion. This is annoying because + fopen() is locale specific. The only real way I can think of to do this is with wcsrtombs(). Note + that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for + maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler + error I'll look into improving compatibility. + */ + + /* + Some compilers don't support wchar_t or wcsrtombs() which we're using below. In this case we just + need to abort with an error. If you encounter a compiler lacking such support, add it to this list + and submit a bug report and it'll be added to the library upstream. + */ + #if defined(__DJGPP__) + { + /* Nothing to do here. This will fall through to the error check below. */ + } + #else + { + mbstate_t mbs; + size_t lenMB; + const wchar_t* pFilePathTemp = pFilePath; + char* pFilePathMB = NULL; + char pOpenModeMB[32] = {0}; + + /* Get the length first. */ + DRWAV_ZERO_OBJECT(&mbs); + lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); + if (lenMB == (size_t)-1) { + return drwav_result_from_errno(errno); + } + + pFilePathMB = (char*)drwav__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks); + if (pFilePathMB == NULL) { + return DRWAV_OUT_OF_MEMORY; + } + + pFilePathTemp = pFilePath; + DRWAV_ZERO_OBJECT(&mbs); + wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); + + /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ + { + size_t i = 0; + for (;;) { + if (pOpenMode[i] == 0) { + pOpenModeMB[i] = '\0'; + break; + } + + pOpenModeMB[i] = (char)pOpenMode[i]; + i += 1; + } + } + + *ppFile = fopen(pFilePathMB, pOpenModeMB); + + drwav__free_from_callbacks(pFilePathMB, pAllocationCallbacks); + } + #endif + + if (*ppFile == NULL) { + return DRWAV_ERROR; + } +#endif + + return DRWAV_SUCCESS; +} +#endif +/* End fopen */ + + +DRWAV_PRIVATE size_t drwav__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData); +} + +DRWAV_PRIVATE size_t drwav__on_write_stdio(void* pUserData, const void* pData, size_t bytesToWrite) +{ + return fwrite(pData, 1, bytesToWrite, (FILE*)pUserData); +} + +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin) +{ + return fseek((FILE*)pUserData, offset, (origin == drwav_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; +} + +DRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_ex(pWav, filename, NULL, NULL, 0, pAllocationCallbacks); +} + + +DRWAV_PRIVATE drwav_bool32 drwav_init_file__internal_FILE(drwav* pWav, FILE* pFile, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav_bool32 result; + + result = drwav_preinit(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (result != DRWAV_TRUE) { + fclose(pFile); + return result; + } + + result = drwav_init__internal(pWav, onChunk, pChunkUserData, flags); + if (result != DRWAV_TRUE) { + fclose(pFile); + return result; + } + + return DRWAV_TRUE; +} + +DRWAV_API drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_fopen(&pFile, filename, "rb") != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file__internal_FILE(pWav, pFile, onChunk, pChunkUserData, flags, pAllocationCallbacks); +} + +#ifndef DR_WAV_NO_WCHAR +DRWAV_API drwav_bool32 drwav_init_file_w(drwav* pWav, const wchar_t* filename, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_ex_w(pWav, filename, NULL, NULL, 0, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_ex_w(drwav* pWav, const wchar_t* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_wfopen(&pFile, filename, L"rb", pAllocationCallbacks) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file__internal_FILE(pWav, pFile, onChunk, pChunkUserData, flags, pAllocationCallbacks); +} +#endif + +DRWAV_API drwav_bool32 drwav_init_file_with_metadata(drwav* pWav, const char* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_fopen(&pFile, filename, "rb") != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file__internal_FILE(pWav, pFile, NULL, NULL, flags | DRWAV_WITH_METADATA, pAllocationCallbacks); +} + +#ifndef DR_WAV_NO_WCHAR +DRWAV_API drwav_bool32 drwav_init_file_with_metadata_w(drwav* pWav, const wchar_t* filename, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_wfopen(&pFile, filename, L"rb", pAllocationCallbacks) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file__internal_FILE(pWav, pFile, NULL, NULL, flags | DRWAV_WITH_METADATA, pAllocationCallbacks); +} +#endif + + +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write__internal_FILE(drwav* pWav, FILE* pFile, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav_bool32 result; + + result = drwav_preinit_write(pWav, pFormat, isSequential, drwav__on_write_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (result != DRWAV_TRUE) { + fclose(pFile); + return result; + } + + result = drwav_init_write__internal(pWav, pFormat, totalSampleCount); + if (result != DRWAV_TRUE) { + fclose(pFile); + return result; + } + + return DRWAV_TRUE; +} + +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write__internal(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_fopen(&pFile, filename, "wb") != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks); +} + +#ifndef DR_WAV_NO_WCHAR +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write_w__internal(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + FILE* pFile; + if (drwav_wfopen(&pFile, filename, L"wb", pAllocationCallbacks) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + /* This takes ownership of the FILE* object. */ + return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks); +} +#endif + +DRWAV_API drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_write__internal(pWav, filename, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_write__internal(pWav, filename, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pFormat == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_file_write_sequential(pWav, filename, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); +} + +#ifndef DR_WAV_NO_WCHAR +DRWAV_API drwav_bool32 drwav_init_file_write_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_write_w__internal(pWav, filename, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_file_write_w__internal(pWav, filename, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pFormat == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_file_write_sequential_w(pWav, filename, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); +} +#endif +#endif /* DR_WAV_NO_STDIO */ + + +DRWAV_PRIVATE size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + drwav* pWav = (drwav*)pUserData; + size_t bytesRemaining; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->memoryStream.dataSize >= pWav->memoryStream.currentReadPos); + + bytesRemaining = pWav->memoryStream.dataSize - pWav->memoryStream.currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + DRWAV_COPY_MEMORY(pBufferOut, pWav->memoryStream.data + pWav->memoryStream.currentReadPos, bytesToRead); + pWav->memoryStream.currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_seek_origin origin) +{ + drwav* pWav = (drwav*)pUserData; + DRWAV_ASSERT(pWav != NULL); + + if (origin == drwav_seek_origin_current) { + if (offset > 0) { + if (pWav->memoryStream.currentReadPos + offset > pWav->memoryStream.dataSize) { + return DRWAV_FALSE; /* Trying to seek too far forward. */ + } + } else { + if (pWav->memoryStream.currentReadPos < (size_t)-offset) { + return DRWAV_FALSE; /* Trying to seek too far backwards. */ + } + } + + /* This will never underflow thanks to the clamps above. */ + pWav->memoryStream.currentReadPos += offset; + } else { + if ((drwav_uint32)offset <= pWav->memoryStream.dataSize) { + pWav->memoryStream.currentReadPos = offset; + } else { + return DRWAV_FALSE; /* Trying to seek too far forward. */ + } + } + + return DRWAV_TRUE; +} + +DRWAV_PRIVATE size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_t bytesToWrite) +{ + drwav* pWav = (drwav*)pUserData; + size_t bytesRemaining; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->memoryStreamWrite.dataCapacity >= pWav->memoryStreamWrite.currentWritePos); + + bytesRemaining = pWav->memoryStreamWrite.dataCapacity - pWav->memoryStreamWrite.currentWritePos; + if (bytesRemaining < bytesToWrite) { + /* Need to reallocate. */ + void* pNewData; + size_t newDataCapacity = (pWav->memoryStreamWrite.dataCapacity == 0) ? 256 : pWav->memoryStreamWrite.dataCapacity * 2; + + /* If doubling wasn't enough, just make it the minimum required size to write the data. */ + if ((newDataCapacity - pWav->memoryStreamWrite.currentWritePos) < bytesToWrite) { + newDataCapacity = pWav->memoryStreamWrite.currentWritePos + bytesToWrite; + } + + pNewData = drwav__realloc_from_callbacks(*pWav->memoryStreamWrite.ppData, newDataCapacity, pWav->memoryStreamWrite.dataCapacity, &pWav->allocationCallbacks); + if (pNewData == NULL) { + return 0; + } + + *pWav->memoryStreamWrite.ppData = pNewData; + pWav->memoryStreamWrite.dataCapacity = newDataCapacity; + } + + DRWAV_COPY_MEMORY(((drwav_uint8*)(*pWav->memoryStreamWrite.ppData)) + pWav->memoryStreamWrite.currentWritePos, pDataIn, bytesToWrite); + + pWav->memoryStreamWrite.currentWritePos += bytesToWrite; + if (pWav->memoryStreamWrite.dataSize < pWav->memoryStreamWrite.currentWritePos) { + pWav->memoryStreamWrite.dataSize = pWav->memoryStreamWrite.currentWritePos; + } + + *pWav->memoryStreamWrite.pDataSize = pWav->memoryStreamWrite.dataSize; + + return bytesToWrite; +} + +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drwav_seek_origin origin) +{ + drwav* pWav = (drwav*)pUserData; + DRWAV_ASSERT(pWav != NULL); + + if (origin == drwav_seek_origin_current) { + if (offset > 0) { + if (pWav->memoryStreamWrite.currentWritePos + offset > pWav->memoryStreamWrite.dataSize) { + offset = (int)(pWav->memoryStreamWrite.dataSize - pWav->memoryStreamWrite.currentWritePos); /* Trying to seek too far forward. */ + } + } else { + if (pWav->memoryStreamWrite.currentWritePos < (size_t)-offset) { + offset = -(int)pWav->memoryStreamWrite.currentWritePos; /* Trying to seek too far backwards. */ + } + } + + /* This will never underflow thanks to the clamps above. */ + pWav->memoryStreamWrite.currentWritePos += offset; + } else { + if ((drwav_uint32)offset <= pWav->memoryStreamWrite.dataSize) { + pWav->memoryStreamWrite.currentWritePos = offset; + } else { + pWav->memoryStreamWrite.currentWritePos = pWav->memoryStreamWrite.dataSize; /* Trying to seek too far forward. */ + } + } + + return DRWAV_TRUE; +} + +DRWAV_API drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_memory_ex(pWav, data, dataSize, NULL, NULL, 0, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (data == NULL || dataSize == 0) { + return DRWAV_FALSE; + } + + if (!drwav_preinit(pWav, drwav__on_read_memory, drwav__on_seek_memory, pWav, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + pWav->memoryStream.data = (const drwav_uint8*)data; + pWav->memoryStream.dataSize = dataSize; + pWav->memoryStream.currentReadPos = 0; + + return drwav_init__internal(pWav, onChunk, pChunkUserData, flags); +} + +DRWAV_API drwav_bool32 drwav_init_memory_with_metadata(drwav* pWav, const void* data, size_t dataSize, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (data == NULL || dataSize == 0) { + return DRWAV_FALSE; + } + + if (!drwav_preinit(pWav, drwav__on_read_memory, drwav__on_seek_memory, pWav, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + pWav->memoryStream.data = (const drwav_uint8*)data; + pWav->memoryStream.dataSize = dataSize; + pWav->memoryStream.currentReadPos = 0; + + return drwav_init__internal(pWav, NULL, NULL, flags | DRWAV_WITH_METADATA); +} + + +DRWAV_PRIVATE drwav_bool32 drwav_init_memory_write__internal(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (ppData == NULL || pDataSize == NULL) { + return DRWAV_FALSE; + } + + *ppData = NULL; /* Important because we're using realloc()! */ + *pDataSize = 0; + + if (!drwav_preinit_write(pWav, pFormat, isSequential, drwav__on_write_memory, drwav__on_seek_memory_write, pWav, pAllocationCallbacks)) { + return DRWAV_FALSE; + } + + pWav->memoryStreamWrite.ppData = ppData; + pWav->memoryStreamWrite.pDataSize = pDataSize; + pWav->memoryStreamWrite.dataSize = 0; + pWav->memoryStreamWrite.dataCapacity = 0; + pWav->memoryStreamWrite.currentWritePos = 0; + + return drwav_init_write__internal(pWav, pFormat, totalSampleCount); +} + +DRWAV_API drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); +} + +DRWAV_API drwav_bool32 drwav_init_memory_write_sequential_pcm_frames(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pFormat == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_memory_write_sequential(pWav, ppData, pDataSize, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); +} + + + +DRWAV_API drwav_result drwav_uninit(drwav* pWav) +{ + drwav_result result = DRWAV_SUCCESS; + + if (pWav == NULL) { + return DRWAV_INVALID_ARGS; + } + + /* + If the drwav object was opened in write mode we'll need to finalize a few things: + - Make sure the "data" chunk is aligned to 16-bits for RIFF containers, or 64 bits for W64 containers. + - Set the size of the "data" chunk. + */ + if (pWav->onWrite != NULL) { + drwav_uint32 paddingSize = 0; + + /* Padding. Do not adjust pWav->dataChunkDataSize - this should not include the padding. */ + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { + paddingSize = drwav__chunk_padding_size_riff(pWav->dataChunkDataSize); + } else { + paddingSize = drwav__chunk_padding_size_w64(pWav->dataChunkDataSize); + } + + if (paddingSize > 0) { + drwav_uint64 paddingData = 0; + drwav__write(pWav, &paddingData, paddingSize); /* Byte order does not matter for this. */ + } + + /* + Chunk sizes. When using sequential mode, these will have been filled in at initialization time. We only need + to do this when using non-sequential mode. + */ + if (pWav->onSeek && !pWav->isSequentialWrite) { + if (pWav->container == drwav_container_riff) { + /* The "RIFF" chunk size. */ + if (pWav->onSeek(pWav->pUserData, 4, drwav_seek_origin_start)) { + drwav_uint32 riffChunkSize = drwav__riff_chunk_size_riff(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); + drwav__write_u32ne_to_le(pWav, riffChunkSize); + } + + /* The "data" chunk size. */ + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 4, drwav_seek_origin_start)) { + drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize); + drwav__write_u32ne_to_le(pWav, dataChunkSize); + } + } else if (pWav->container == drwav_container_w64) { + /* The "RIFF" chunk size. */ + if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) { + drwav_uint64 riffChunkSize = drwav__riff_chunk_size_w64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, riffChunkSize); + } + + /* The "data" chunk size. */ + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 8, drwav_seek_origin_start)) { + drwav_uint64 dataChunkSize = drwav__data_chunk_size_w64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, dataChunkSize); + } + } else if (pWav->container == drwav_container_rf64) { + /* We only need to update the ds64 chunk. The "RIFF" and "data" chunks always have their sizes set to 0xFFFFFFFF for RF64. */ + int ds64BodyPos = 12 + 8; + + /* The "RIFF" chunk size. */ + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, drwav_seek_origin_start)) { + drwav_uint64 riffChunkSize = drwav__riff_chunk_size_rf64(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); + drwav__write_u64ne_to_le(pWav, riffChunkSize); + } + + /* The "data" chunk size. */ + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, drwav_seek_origin_start)) { + drwav_uint64 dataChunkSize = drwav__data_chunk_size_rf64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, dataChunkSize); + } + } + } + + /* Validation for sequential mode. */ + if (pWav->isSequentialWrite) { + if (pWav->dataChunkDataSize != pWav->dataChunkDataSizeTargetWrite) { + result = DRWAV_INVALID_FILE; + } + } + } else { + drwav_free(pWav->pMetadata, &pWav->allocationCallbacks); + } + +#ifndef DR_WAV_NO_STDIO + /* + If we opened the file with drwav_open_file() we will want to close the file handle. We can know whether or not drwav_open_file() + was used by looking at the onRead and onSeek callbacks. + */ + if (pWav->onRead == drwav__on_read_stdio || pWav->onWrite == drwav__on_write_stdio) { + fclose((FILE*)pWav->pUserData); + } +#endif + + return result; +} + + + +DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut) +{ + size_t bytesRead; + drwav_uint32 bytesPerFrame; + + if (pWav == NULL || bytesToRead == 0) { + return 0; /* Invalid args. */ + } + + if (bytesToRead > pWav->bytesRemaining) { + bytesToRead = (size_t)pWav->bytesRemaining; + } + + if (bytesToRead == 0) { + return 0; /* At end. */ + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; /* Could not determine the bytes per frame. */ + } + + if (pBufferOut != NULL) { + bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead); + } else { + /* We need to seek. If we fail, we need to read-and-discard to make sure we get a good byte count. */ + bytesRead = 0; + while (bytesRead < bytesToRead) { + size_t bytesToSeek = (bytesToRead - bytesRead); + if (bytesToSeek > 0x7FFFFFFF) { + bytesToSeek = 0x7FFFFFFF; + } + + if (pWav->onSeek(pWav->pUserData, (int)bytesToSeek, drwav_seek_origin_current) == DRWAV_FALSE) { + break; + } + + bytesRead += bytesToSeek; + } + + /* When we get here we may need to read-and-discard some data. */ + while (bytesRead < bytesToRead) { + drwav_uint8 buffer[4096]; + size_t bytesSeeked; + size_t bytesToSeek = (bytesToRead - bytesRead); + if (bytesToSeek > sizeof(buffer)) { + bytesToSeek = sizeof(buffer); + } + + bytesSeeked = pWav->onRead(pWav->pUserData, buffer, bytesToSeek); + bytesRead += bytesSeeked; + + if (bytesSeeked < bytesToSeek) { + break; /* Reached the end. */ + } + } + } + + pWav->readCursorInPCMFrames += bytesRead / bytesPerFrame; + + pWav->bytesRemaining -= bytesRead; + return bytesRead; +} + + + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) +{ + drwav_uint32 bytesPerFrame; + drwav_uint64 bytesToRead; /* Intentionally uint64 instead of size_t so we can do a check that we're not reading too much on 32-bit builds. */ + drwav_uint64 framesRemainingInFile; + + if (pWav == NULL || framesToRead == 0) { + return 0; + } + + /* Cannot use this function for compressed formats. */ + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + return 0; + } + + framesRemainingInFile = pWav->totalPCMFrameCount - pWav->readCursorInPCMFrames; + if (framesToRead > framesRemainingInFile) { + framesToRead = framesRemainingInFile; + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + /* Don't try to read more samples than can potentially fit in the output buffer. */ + bytesToRead = framesToRead * bytesPerFrame; + if (bytesToRead > DRWAV_SIZE_MAX) { + bytesToRead = (DRWAV_SIZE_MAX / bytesPerFrame) * bytesPerFrame; /* Round the number of bytes to read to a clean frame boundary. */ + } + + /* + Doing an explicit check here just to make it clear that we don't want to be attempt to read anything if there's no bytes to read. There + *could* be a time where it evaluates to 0 due to overflowing. + */ + if (bytesToRead == 0) { + return 0; + } + + return drwav_read_raw(pWav, (size_t)bytesToRead, pBufferOut) / bytesPerFrame; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); + + if (pBufferOut != NULL) { + drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; /* Could not get the bytes per frame which means bytes per sample cannot be determined and we don't know how to byte swap. */ + } + + drwav__bswap_samples(pBufferOut, framesRead*pWav->channels, bytesPerFrame/pWav->channels); + } + + return framesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) +{ + drwav_uint64 framesRead = 0; + + if (drwav_is_container_be(pWav->container)) { + /* + Special case for AIFF. AIFF is a big-endian encoded format, but it supports a format that is + PCM in little-endian encoding. In this case, we fall through this branch and treate it as + little-endian. + */ + if (pWav->container != drwav_container_aiff || pWav->aiff.isLE == DRWAV_FALSE) { + if (drwav__is_little_endian()) { + framesRead = drwav_read_pcm_frames_be(pWav, framesToRead, pBufferOut); + } else { + framesRead = drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); + } + + goto post_process; + } + } + + /* Getting here means the data should be considered little-endian. */ + if (drwav__is_little_endian()) { + framesRead = drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); + } else { + framesRead = drwav_read_pcm_frames_be(pWav, framesToRead, pBufferOut); + } + + /* + Here is where we check if we need to do a signed/unsigned conversion for AIFF. The reason we need to do this + is because dr_wav always assumes an 8-bit sample is unsigned, whereas AIFF can have signed 8-bit formats. + */ + post_process: + { + if (pWav->container == drwav_container_aiff && pWav->bitsPerSample == 8 && pWav->aiff.isUnsigned == DRWAV_FALSE) { + if (pBufferOut != NULL) { + drwav_uint64 iSample; + + for (iSample = 0; iSample < framesRead * pWav->channels; iSample += 1) { + ((drwav_uint8*)pBufferOut)[iSample] += 128; + } + } + } + } + + return framesRead; +} + + + +DRWAV_PRIVATE drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav) +{ + if (pWav->onWrite != NULL) { + return DRWAV_FALSE; /* No seeking in write mode. */ + } + + if (!pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos, drwav_seek_origin_start)) { + return DRWAV_FALSE; + } + + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + /* Cached data needs to be cleared for compressed formats. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + DRWAV_ZERO_OBJECT(&pWav->msadpcm); + } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + DRWAV_ZERO_OBJECT(&pWav->ima); + } else { + DRWAV_ASSERT(DRWAV_FALSE); /* If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. */ + } + } + + pWav->readCursorInPCMFrames = 0; + pWav->bytesRemaining = pWav->dataChunkDataSize; + + return DRWAV_TRUE; +} + +DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex) +{ + /* Seeking should be compatible with wave files > 2GB. */ + + if (pWav == NULL || pWav->onSeek == NULL) { + return DRWAV_FALSE; + } + + /* No seeking in write mode. */ + if (pWav->onWrite != NULL) { + return DRWAV_FALSE; + } + + /* If there are no samples, just return DRWAV_TRUE without doing anything. */ + if (pWav->totalPCMFrameCount == 0) { + return DRWAV_TRUE; + } + + /* Make sure the sample is clamped. */ + if (targetFrameIndex > pWav->totalPCMFrameCount) { + targetFrameIndex = pWav->totalPCMFrameCount; + } + + /* + For compressed formats we just use a slow generic seek. If we are seeking forward we just seek forward. If we are going backwards we need + to seek back to the start. + */ + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + /* TODO: This can be optimized. */ + + /* + If we're seeking forward it's simple - just keep reading samples until we hit the sample we're requesting. If we're seeking backwards, + we first need to seek back to the start and then just do the same thing as a forward seek. + */ + if (targetFrameIndex < pWav->readCursorInPCMFrames) { + if (!drwav_seek_to_first_pcm_frame(pWav)) { + return DRWAV_FALSE; + } + } + + if (targetFrameIndex > pWav->readCursorInPCMFrames) { + drwav_uint64 offsetInFrames = targetFrameIndex - pWav->readCursorInPCMFrames; + + drwav_int16 devnull[2048]; + while (offsetInFrames > 0) { + drwav_uint64 framesRead = 0; + drwav_uint64 framesToRead = offsetInFrames; + if (framesToRead > drwav_countof(devnull)/pWav->channels) { + framesToRead = drwav_countof(devnull)/pWav->channels; + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + framesRead = drwav_read_pcm_frames_s16__msadpcm(pWav, framesToRead, devnull); + } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + framesRead = drwav_read_pcm_frames_s16__ima(pWav, framesToRead, devnull); + } else { + DRWAV_ASSERT(DRWAV_FALSE); /* If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. */ + } + + if (framesRead != framesToRead) { + return DRWAV_FALSE; + } + + offsetInFrames -= framesRead; + } + } + } else { + drwav_uint64 totalSizeInBytes; + drwav_uint64 currentBytePos; + drwav_uint64 targetBytePos; + drwav_uint64 offset; + drwav_uint32 bytesPerFrame; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return DRWAV_FALSE; /* Not able to calculate offset. */ + } + + totalSizeInBytes = pWav->totalPCMFrameCount * bytesPerFrame; + /*DRWAV_ASSERT(totalSizeInBytes >= pWav->bytesRemaining);*/ + + currentBytePos = totalSizeInBytes - pWav->bytesRemaining; + targetBytePos = targetFrameIndex * bytesPerFrame; + + if (currentBytePos < targetBytePos) { + /* Offset forwards. */ + offset = (targetBytePos - currentBytePos); + } else { + /* Offset backwards. */ + if (!drwav_seek_to_first_pcm_frame(pWav)) { + return DRWAV_FALSE; + } + offset = targetBytePos; + } + + while (offset > 0) { + int offset32 = ((offset > INT_MAX) ? INT_MAX : (int)offset); + if (!pWav->onSeek(pWav->pUserData, offset32, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + + pWav->readCursorInPCMFrames += offset32 / bytesPerFrame; + pWav->bytesRemaining -= offset32; + offset -= offset32; + } + } + + return DRWAV_TRUE; +} + +DRWAV_API drwav_result drwav_get_cursor_in_pcm_frames(drwav* pWav, drwav_uint64* pCursor) +{ + if (pCursor == NULL) { + return DRWAV_INVALID_ARGS; + } + + *pCursor = 0; /* Safety. */ + + if (pWav == NULL) { + return DRWAV_INVALID_ARGS; + } + + *pCursor = pWav->readCursorInPCMFrames; + + return DRWAV_SUCCESS; +} + +DRWAV_API drwav_result drwav_get_length_in_pcm_frames(drwav* pWav, drwav_uint64* pLength) +{ + if (pLength == NULL) { + return DRWAV_INVALID_ARGS; + } + + *pLength = 0; /* Safety. */ + + if (pWav == NULL) { + return DRWAV_INVALID_ARGS; + } + + *pLength = pWav->totalPCMFrameCount; + + return DRWAV_SUCCESS; +} + + +DRWAV_API size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData) +{ + size_t bytesWritten; + + if (pWav == NULL || bytesToWrite == 0 || pData == NULL) { + return 0; + } + + bytesWritten = pWav->onWrite(pWav->pUserData, pData, bytesToWrite); + pWav->dataChunkDataSize += bytesWritten; + + return bytesWritten; +} + +DRWAV_API drwav_uint64 drwav_write_pcm_frames_le(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) +{ + drwav_uint64 bytesToWrite; + drwav_uint64 bytesWritten; + const drwav_uint8* pRunningData; + + if (pWav == NULL || framesToWrite == 0 || pData == NULL) { + return 0; + } + + bytesToWrite = ((framesToWrite * pWav->channels * pWav->bitsPerSample) / 8); + if (bytesToWrite > DRWAV_SIZE_MAX) { + return 0; + } + + bytesWritten = 0; + pRunningData = (const drwav_uint8*)pData; + + while (bytesToWrite > 0) { + size_t bytesJustWritten; + drwav_uint64 bytesToWriteThisIteration; + + bytesToWriteThisIteration = bytesToWrite; + DRWAV_ASSERT(bytesToWriteThisIteration <= DRWAV_SIZE_MAX); /* <-- This is checked above. */ + + bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, pRunningData); + if (bytesJustWritten == 0) { + break; + } + + bytesToWrite -= bytesJustWritten; + bytesWritten += bytesJustWritten; + pRunningData += bytesJustWritten; + } + + return (bytesWritten * 8) / pWav->bitsPerSample / pWav->channels; +} + +DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) +{ + drwav_uint64 bytesToWrite; + drwav_uint64 bytesWritten; + drwav_uint32 bytesPerSample; + const drwav_uint8* pRunningData; + + if (pWav == NULL || framesToWrite == 0 || pData == NULL) { + return 0; + } + + bytesToWrite = ((framesToWrite * pWav->channels * pWav->bitsPerSample) / 8); + if (bytesToWrite > DRWAV_SIZE_MAX) { + return 0; + } + + bytesWritten = 0; + pRunningData = (const drwav_uint8*)pData; + + bytesPerSample = drwav_get_bytes_per_pcm_frame(pWav) / pWav->channels; + if (bytesPerSample == 0) { + return 0; /* Cannot determine bytes per sample, or bytes per sample is less than one byte. */ + } + + while (bytesToWrite > 0) { + drwav_uint8 temp[4096]; + drwav_uint32 sampleCount; + size_t bytesJustWritten; + drwav_uint64 bytesToWriteThisIteration; + + bytesToWriteThisIteration = bytesToWrite; + DRWAV_ASSERT(bytesToWriteThisIteration <= DRWAV_SIZE_MAX); /* <-- This is checked above. */ + + /* + WAV files are always little-endian. We need to byte swap on big-endian architectures. Since our input buffer is read-only we need + to use an intermediary buffer for the conversion. + */ + sampleCount = sizeof(temp)/bytesPerSample; + + if (bytesToWriteThisIteration > ((drwav_uint64)sampleCount)*bytesPerSample) { + bytesToWriteThisIteration = ((drwav_uint64)sampleCount)*bytesPerSample; + } + + DRWAV_COPY_MEMORY(temp, pRunningData, (size_t)bytesToWriteThisIteration); + drwav__bswap_samples(temp, sampleCount, bytesPerSample); + + bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, temp); + if (bytesJustWritten == 0) { + break; + } + + bytesToWrite -= bytesJustWritten; + bytesWritten += bytesJustWritten; + pRunningData += bytesJustWritten; + } + + return (bytesWritten * 8) / pWav->bitsPerSample / pWav->channels; +} + +DRWAV_API drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) +{ + if (drwav__is_little_endian()) { + return drwav_write_pcm_frames_le(pWav, framesToWrite, pData); + } else { + return drwav_write_pcm_frames_be(pWav, framesToWrite, pData); + } +} + + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead = 0; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(framesToRead > 0); + + /* TODO: Lots of room for optimization here. */ + + while (pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { + DRWAV_ASSERT(framesToRead > 0); /* This loop iteration will never get hit with framesToRead == 0 because it's asserted at the top, and we check for 0 inside the loop just below. */ + + /* If there are no cached frames we need to load a new block. */ + if (pWav->msadpcm.cachedFrameCount == 0 && pWav->msadpcm.bytesRemainingInBlock == 0) { + if (pWav->channels == 1) { + /* Mono. */ + drwav_uint8 header[7]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalFramesRead; + } + pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + pWav->msadpcm.predictor[0] = header[0]; + pWav->msadpcm.delta[0] = drwav_bytes_to_s16(header + 1); + pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav_bytes_to_s16(header + 3); + pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav_bytes_to_s16(header + 5); + pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][0]; + pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.cachedFrameCount = 2; + } else { + /* Stereo. */ + drwav_uint8 header[14]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalFramesRead; + } + pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + pWav->msadpcm.predictor[0] = header[0]; + pWav->msadpcm.predictor[1] = header[1]; + pWav->msadpcm.delta[0] = drwav_bytes_to_s16(header + 2); + pWav->msadpcm.delta[1] = drwav_bytes_to_s16(header + 4); + pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav_bytes_to_s16(header + 6); + pWav->msadpcm.prevFrames[1][1] = (drwav_int32)drwav_bytes_to_s16(header + 8); + pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav_bytes_to_s16(header + 10); + pWav->msadpcm.prevFrames[1][0] = (drwav_int32)drwav_bytes_to_s16(header + 12); + + pWav->msadpcm.cachedFrames[0] = pWav->msadpcm.prevFrames[0][0]; + pWav->msadpcm.cachedFrames[1] = pWav->msadpcm.prevFrames[1][0]; + pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[1][1]; + pWav->msadpcm.cachedFrameCount = 2; + } + } + + /* Output anything that's cached. */ + while (framesToRead > 0 && pWav->msadpcm.cachedFrameCount > 0 && pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { + if (pBufferOut != NULL) { + drwav_uint32 iSample = 0; + for (iSample = 0; iSample < pWav->channels; iSample += 1) { + pBufferOut[iSample] = (drwav_int16)pWav->msadpcm.cachedFrames[(drwav_countof(pWav->msadpcm.cachedFrames) - (pWav->msadpcm.cachedFrameCount*pWav->channels)) + iSample]; + } + + pBufferOut += pWav->channels; + } + + framesToRead -= 1; + totalFramesRead += 1; + pWav->readCursorInPCMFrames += 1; + pWav->msadpcm.cachedFrameCount -= 1; + } + + if (framesToRead == 0) { + break; + } + + + /* + If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next + loop iteration which will trigger the loading of a new block. + */ + if (pWav->msadpcm.cachedFrameCount == 0) { + if (pWav->msadpcm.bytesRemainingInBlock == 0) { + continue; + } else { + static drwav_int32 adaptationTable[] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 + }; + static drwav_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; + static drwav_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; + + drwav_uint8 nibbles; + drwav_int32 nibble0; + drwav_int32 nibble1; + + if (pWav->onRead(pWav->pUserData, &nibbles, 1) != 1) { + return totalFramesRead; + } + pWav->msadpcm.bytesRemainingInBlock -= 1; + + /* TODO: Optimize away these if statements. */ + nibble0 = ((nibbles & 0xF0) >> 4); if ((nibbles & 0x80)) { nibble0 |= 0xFFFFFFF0UL; } + nibble1 = ((nibbles & 0x0F) >> 0); if ((nibbles & 0x08)) { nibble1 |= 0xFFFFFFF0UL; } + + if (pWav->channels == 1) { + /* Mono. */ + drwav_int32 newSample0; + drwav_int32 newSample1; + + newSample0 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample0 += nibble0 * pWav->msadpcm.delta[0]; + newSample0 = drwav_clamp(newSample0, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.prevFrames[0][1] = newSample0; + + + newSample1 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample1 += nibble1 * pWav->msadpcm.delta[0]; + newSample1 = drwav_clamp(newSample1, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.prevFrames[0][1] = newSample1; + + + pWav->msadpcm.cachedFrames[2] = newSample0; + pWav->msadpcm.cachedFrames[3] = newSample1; + pWav->msadpcm.cachedFrameCount = 2; + } else { + /* Stereo. */ + drwav_int32 newSample0; + drwav_int32 newSample1; + + /* Left. */ + newSample0 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample0 += nibble0 * pWav->msadpcm.delta[0]; + newSample0 = drwav_clamp(newSample0, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; + pWav->msadpcm.prevFrames[0][1] = newSample0; + + + /* Right. */ + newSample1 = ((pWav->msadpcm.prevFrames[1][1] * coeff1Table[pWav->msadpcm.predictor[1]]) + (pWav->msadpcm.prevFrames[1][0] * coeff2Table[pWav->msadpcm.predictor[1]])) >> 8; + newSample1 += nibble1 * pWav->msadpcm.delta[1]; + newSample1 = drwav_clamp(newSample1, -32768, 32767); + + pWav->msadpcm.delta[1] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[1]) >> 8; + if (pWav->msadpcm.delta[1] < 16) { + pWav->msadpcm.delta[1] = 16; + } + + pWav->msadpcm.prevFrames[1][0] = pWav->msadpcm.prevFrames[1][1]; + pWav->msadpcm.prevFrames[1][1] = newSample1; + + pWav->msadpcm.cachedFrames[2] = newSample0; + pWav->msadpcm.cachedFrames[3] = newSample1; + pWav->msadpcm.cachedFrameCount = 1; + } + } + } + } + + return totalFramesRead; +} + + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead = 0; + drwav_uint32 iChannel; + + static drwav_int32 indexTable[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + + static drwav_int32 stepTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 + }; + + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(framesToRead > 0); + + /* TODO: Lots of room for optimization here. */ + + while (pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { + DRWAV_ASSERT(framesToRead > 0); /* This loop iteration will never get hit with framesToRead == 0 because it's asserted at the top, and we check for 0 inside the loop just below. */ + + /* If there are no cached samples we need to load a new block. */ + if (pWav->ima.cachedFrameCount == 0 && pWav->ima.bytesRemainingInBlock == 0) { + if (pWav->channels == 1) { + /* Mono. */ + drwav_uint8 header[4]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalFramesRead; + } + pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + if (header[2] >= drwav_countof(stepTable)) { + pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current); + pWav->ima.bytesRemainingInBlock = 0; + return totalFramesRead; /* Invalid data. */ + } + + pWav->ima.predictor[0] = (drwav_int16)drwav_bytes_to_u16(header + 0); + pWav->ima.stepIndex[0] = drwav_clamp(header[2], 0, (drwav_int32)drwav_countof(stepTable)-1); /* Clamp not necessary because we checked above, but adding here to silence a static analysis warning. */ + pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[0]; + pWav->ima.cachedFrameCount = 1; + } else { + /* Stereo. */ + drwav_uint8 header[8]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalFramesRead; + } + pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + if (header[2] >= drwav_countof(stepTable) || header[6] >= drwav_countof(stepTable)) { + pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current); + pWav->ima.bytesRemainingInBlock = 0; + return totalFramesRead; /* Invalid data. */ + } + + pWav->ima.predictor[0] = drwav_bytes_to_s16(header + 0); + pWav->ima.stepIndex[0] = drwav_clamp(header[2], 0, (drwav_int32)drwav_countof(stepTable)-1); /* Clamp not necessary because we checked above, but adding here to silence a static analysis warning. */ + pWav->ima.predictor[1] = drwav_bytes_to_s16(header + 4); + pWav->ima.stepIndex[1] = drwav_clamp(header[6], 0, (drwav_int32)drwav_countof(stepTable)-1); /* Clamp not necessary because we checked above, but adding here to silence a static analysis warning. */ + + pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 2] = pWav->ima.predictor[0]; + pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[1]; + pWav->ima.cachedFrameCount = 1; + } + } + + /* Output anything that's cached. */ + while (framesToRead > 0 && pWav->ima.cachedFrameCount > 0 && pWav->readCursorInPCMFrames < pWav->totalPCMFrameCount) { + if (pBufferOut != NULL) { + drwav_uint32 iSample; + for (iSample = 0; iSample < pWav->channels; iSample += 1) { + pBufferOut[iSample] = (drwav_int16)pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + iSample]; + } + pBufferOut += pWav->channels; + } + + framesToRead -= 1; + totalFramesRead += 1; + pWav->readCursorInPCMFrames += 1; + pWav->ima.cachedFrameCount -= 1; + } + + if (framesToRead == 0) { + break; + } + + /* + If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next + loop iteration which will trigger the loading of a new block. + */ + if (pWav->ima.cachedFrameCount == 0) { + if (pWav->ima.bytesRemainingInBlock == 0) { + continue; + } else { + /* + From what I can tell with stereo streams, it looks like every 4 bytes (8 samples) is for one channel. So it goes 4 bytes for the + left channel, 4 bytes for the right channel. + */ + pWav->ima.cachedFrameCount = 8; + for (iChannel = 0; iChannel < pWav->channels; ++iChannel) { + drwav_uint32 iByte; + drwav_uint8 nibbles[4]; + if (pWav->onRead(pWav->pUserData, &nibbles, 4) != 4) { + pWav->ima.cachedFrameCount = 0; + return totalFramesRead; + } + pWav->ima.bytesRemainingInBlock -= 4; + + for (iByte = 0; iByte < 4; ++iByte) { + drwav_uint8 nibble0 = ((nibbles[iByte] & 0x0F) >> 0); + drwav_uint8 nibble1 = ((nibbles[iByte] & 0xF0) >> 4); + + drwav_int32 step = stepTable[pWav->ima.stepIndex[iChannel]]; + drwav_int32 predictor = pWav->ima.predictor[iChannel]; + + drwav_int32 diff = step >> 3; + if (nibble0 & 1) diff += step >> 2; + if (nibble0 & 2) diff += step >> 1; + if (nibble0 & 4) diff += step; + if (nibble0 & 8) diff = -diff; + + predictor = drwav_clamp(predictor + diff, -32768, 32767); + pWav->ima.predictor[iChannel] = predictor; + pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble0], 0, (drwav_int32)drwav_countof(stepTable)-1); + pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + (iByte*2+0)*pWav->channels + iChannel] = predictor; + + + step = stepTable[pWav->ima.stepIndex[iChannel]]; + predictor = pWav->ima.predictor[iChannel]; + + diff = step >> 3; + if (nibble1 & 1) diff += step >> 2; + if (nibble1 & 2) diff += step >> 1; + if (nibble1 & 4) diff += step; + if (nibble1 & 8) diff = -diff; + + predictor = drwav_clamp(predictor + diff, -32768, 32767); + pWav->ima.predictor[iChannel] = predictor; + pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble1], 0, (drwav_int32)drwav_countof(stepTable)-1); + pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + (iByte*2+1)*pWav->channels + iChannel] = predictor; + } + } + } + } + } + + return totalFramesRead; +} + + +#ifndef DR_WAV_NO_CONVERSION_API +static unsigned short g_drwavAlawTable[256] = { + 0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580, + 0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0, + 0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600, + 0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00, + 0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58, + 0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58, + 0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960, + 0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0, + 0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80, + 0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40, + 0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00, + 0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500, + 0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8, + 0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8, + 0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0, + 0x02B0, 0x0290, 0x02F0, 0x02D0, 0x0230, 0x0210, 0x0270, 0x0250, 0x03B0, 0x0390, 0x03F0, 0x03D0, 0x0330, 0x0310, 0x0370, 0x0350 +}; + +static unsigned short g_drwavMulawTable[256] = { + 0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84, + 0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84, + 0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004, + 0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844, + 0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64, + 0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74, + 0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C, + 0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000, + 0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C, + 0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C, + 0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC, + 0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC, + 0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C, + 0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C, + 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084, + 0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000 +}; + +static DRWAV_INLINE drwav_int16 drwav__alaw_to_s16(drwav_uint8 sampleIn) +{ + return (short)g_drwavAlawTable[sampleIn]; +} + +static DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn) +{ + return (short)g_drwavMulawTable[sampleIn]; +} + + + +DRWAV_PRIVATE void drwav__pcm_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + size_t i; + + /* Special case for 8-bit sample data because it's treated as unsigned. */ + if (bytesPerSample == 1) { + drwav_u8_to_s16(pOut, pIn, totalSampleCount); + return; + } + + + /* Slightly more optimal implementation for common formats. */ + if (bytesPerSample == 2) { + for (i = 0; i < totalSampleCount; ++i) { + *pOut++ = ((const drwav_int16*)pIn)[i]; + } + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_s16(pOut, pIn, totalSampleCount); + return; + } + if (bytesPerSample == 4) { + drwav_s32_to_s16(pOut, (const drwav_int32*)pIn, totalSampleCount); + return; + } + + + /* Anything more than 64 bits per sample is not supported. */ + if (bytesPerSample > 8) { + DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); + return; + } + + + /* Generic, slow converter. */ + for (i = 0; i < totalSampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample; j += 1) { + DRWAV_ASSERT(j < 8); + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (drwav_int16)((drwav_int64)sample >> 48); + } +} + +DRWAV_PRIVATE void drwav__ieee_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + if (bytesPerSample == 4) { + drwav_f32_to_s16(pOut, (const float*)pIn, totalSampleCount); + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_s16(pOut, (const double*)pIn, totalSampleCount); + return; + } else { + /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ + DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); + return; + } +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + /* Fast path. */ + if ((pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) || pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__ieee_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); /* Safe cast. */ + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_alaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + + /* + For some reason libsndfile seems to be returning samples of the opposite sign for a-law, but only + with AIFF files. For WAV files it seems to be the same as dr_wav. This is resulting in dr_wav's + automated tests failing. I'm not sure which is correct, but will assume dr_wav. If we're enforcing + libsndfile compatibility we'll swap the signs here. + */ + #ifdef DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == drwav_container_aiff) { + drwav_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_mulaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + + /* + Just like with alaw, for some reason the signs between libsndfile and dr_wav are opposite. We just need to + swap the sign if we're compiling with libsndfile compatiblity so our automated tests don't fail. + */ + #ifdef DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == drwav_container_aiff) { + drwav_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + if (pWav == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + /* Don't try to read more samples than can potentially fit in the output buffer. */ + if (framesToRead * pWav->channels * sizeof(drwav_int16) > DRWAV_SIZE_MAX) { + framesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int16) / pWav->channels; + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_pcm_frames_s16__pcm(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_pcm_frames_s16__ieee(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_pcm_frames_s16__alaw(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_pcm_frames_s16__mulaw(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + return drwav_read_pcm_frames_s16__msadpcm(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_pcm_frames_s16__ima(pWav, framesToRead, pBufferOut); + } + + return 0; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16le(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { + drwav__bswap_samples_s16(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16be(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { + drwav__bswap_samples_s16(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + + +DRWAV_API void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + int x = pIn[i]; + r = x << 8; + r = r - 32768; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + int x = ((int)(((unsigned int)(((const drwav_uint8*)pIn)[i*3+0]) << 8) | ((unsigned int)(((const drwav_uint8*)pIn)[i*3+1]) << 16) | ((unsigned int)(((const drwav_uint8*)pIn)[i*3+2])) << 24)) >> 8; + r = x >> 8; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + int x = pIn[i]; + r = x >> 16; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + float x = pIn[i]; + float c; + c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); + c = c + 1; + r = (int)(c * 32767.5f); + r = r - 32768; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount) +{ + int r; + size_t i; + for (i = 0; i < sampleCount; ++i) { + double x = pIn[i]; + double c; + c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); + c = c + 1; + r = (int)(c * 32767.5); + r = r - 32768; + pOut[i] = (short)r; + } +} + +DRWAV_API void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + for (i = 0; i < sampleCount; ++i) { + pOut[i] = drwav__alaw_to_s16(pIn[i]); + } +} + +DRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + for (i = 0; i < sampleCount; ++i) { + pOut[i] = drwav__mulaw_to_s16(pIn[i]); + } +} + + +DRWAV_PRIVATE void drwav__pcm_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) +{ + unsigned int i; + + /* Special case for 8-bit sample data because it's treated as unsigned. */ + if (bytesPerSample == 1) { + drwav_u8_to_f32(pOut, pIn, sampleCount); + return; + } + + /* Slightly more optimal implementation for common formats. */ + if (bytesPerSample == 2) { + drwav_s16_to_f32(pOut, (const drwav_int16*)pIn, sampleCount); + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_f32(pOut, pIn, sampleCount); + return; + } + if (bytesPerSample == 4) { + drwav_s32_to_f32(pOut, (const drwav_int32*)pIn, sampleCount); + return; + } + + + /* Anything more than 64 bits per sample is not supported. */ + if (bytesPerSample > 8) { + DRWAV_ZERO_MEMORY(pOut, sampleCount * sizeof(*pOut)); + return; + } + + + /* Generic, slow converter. */ + for (i = 0; i < sampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample; j += 1) { + DRWAV_ASSERT(j < 8); + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (float)((drwav_int64)sample / 9223372036854775807.0); + } +} + +DRWAV_PRIVATE void drwav__ieee_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) +{ + if (bytesPerSample == 4) { + unsigned int i; + for (i = 0; i < sampleCount; ++i) { + *pOut++ = ((const float*)pIn)[i]; + } + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_f32(pOut, (const double*)pIn, sampleCount); + return; + } else { + /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ + DRWAV_ZERO_MEMORY(pOut, sampleCount * sizeof(*pOut)); + return; + } +} + + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__pcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__msadpcm_ima(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + /* + We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't + want to duplicate that code. + */ + drwav_uint64 totalFramesRead; + drwav_int16 samples16[2048]; + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels); + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToReadThisIteration, samples16); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + drwav_s16_to_f32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); /* <-- Safe cast because we're clamping to 2048. */ + + pBufferOut += framesRead*pWav->channels; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + /* Fast path. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bitsPerSample == 32) { + return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_alaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + + #ifdef DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == drwav_container_aiff) { + drwav_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__mulaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_mulaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + + #ifdef DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == drwav_container_aiff) { + drwav_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + if (pWav == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + /* Don't try to read more samples than can potentially fit in the output buffer. */ + if (framesToRead * pWav->channels * sizeof(float) > DRWAV_SIZE_MAX) { + framesToRead = DRWAV_SIZE_MAX / sizeof(float) / pWav->channels; + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_pcm_frames_f32__pcm(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_pcm_frames_f32__msadpcm_ima(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_pcm_frames_f32__ieee(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_pcm_frames_f32__alaw(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_pcm_frames_f32__mulaw(pWav, framesToRead, pBufferOut); + } + + return 0; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_f32(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { + drwav__bswap_samples_f32(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32be(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_f32(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { + drwav__bswap_samples_f32(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + + +DRWAV_API void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + +#ifdef DR_WAV_LIBSNDFILE_COMPAT + /* + It appears libsndfile uses slightly different logic for the u8 -> f32 conversion to dr_wav, which in my opinion is incorrect. It appears + libsndfile performs the conversion something like "f32 = (u8 / 256) * 2 - 1", however I think it should be "f32 = (u8 / 255) * 2 - 1" (note + the divisor of 256 vs 255). I use libsndfile as a benchmark for testing, so I'm therefore leaving this block here just for my automated + correctness testing. This is disabled by default. + */ + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (pIn[i] / 256.0f) * 2 - 1; + } +#else + for (i = 0; i < sampleCount; ++i) { + float x = pIn[i]; + x = x * 0.00784313725490196078f; /* 0..255 to 0..2 */ + x = x - 1; /* 0..2 to -1..1 */ + + *pOut++ = x; + } +#endif +} + +DRWAV_API void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = pIn[i] * 0.000030517578125f; + } +} + +DRWAV_API void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + double x; + drwav_uint32 a = ((drwav_uint32)(pIn[i*3+0]) << 8); + drwav_uint32 b = ((drwav_uint32)(pIn[i*3+1]) << 16); + drwav_uint32 c = ((drwav_uint32)(pIn[i*3+2]) << 24); + + x = (double)((drwav_int32)(a | b | c) >> 8); + *pOut++ = (float)(x * 0.00000011920928955078125); + } +} + +DRWAV_API void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount) +{ + size_t i; + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (float)(pIn[i] / 2147483648.0); + } +} + +DRWAV_API void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (float)pIn[i]; + } +} + +DRWAV_API void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = drwav__alaw_to_s16(pIn[i]) / 32768.0f; + } +} + +DRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = drwav__mulaw_to_s16(pIn[i]) / 32768.0f; + } +} + + + +DRWAV_PRIVATE void drwav__pcm_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + unsigned int i; + + /* Special case for 8-bit sample data because it's treated as unsigned. */ + if (bytesPerSample == 1) { + drwav_u8_to_s32(pOut, pIn, totalSampleCount); + return; + } + + /* Slightly more optimal implementation for common formats. */ + if (bytesPerSample == 2) { + drwav_s16_to_s32(pOut, (const drwav_int16*)pIn, totalSampleCount); + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_s32(pOut, pIn, totalSampleCount); + return; + } + if (bytesPerSample == 4) { + for (i = 0; i < totalSampleCount; ++i) { + *pOut++ = ((const drwav_int32*)pIn)[i]; + } + return; + } + + + /* Anything more than 64 bits per sample is not supported. */ + if (bytesPerSample > 8) { + DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); + return; + } + + + /* Generic, slow converter. */ + for (i = 0; i < totalSampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample; j += 1) { + DRWAV_ASSERT(j < 8); + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (drwav_int32)((drwav_int64)sample >> 32); + } +} + +DRWAV_PRIVATE void drwav__ieee_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + if (bytesPerSample == 4) { + drwav_f32_to_s32(pOut, (const float*)pIn, totalSampleCount); + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_s32(pOut, (const double*)pIn, totalSampleCount); + return; + } else { + /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ + DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); + return; + } +} + + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + /* Fast path. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 32) { + return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__msadpcm_ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + /* + We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't + want to duplicate that code. + */ + drwav_uint64 totalFramesRead = 0; + drwav_int16 samples16[2048]; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels); + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToReadThisIteration, samples16); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + drwav_s16_to_s32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); /* <-- Safe cast because we're clamping to 2048. */ + + pBufferOut += framesRead*pWav->channels; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_alaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + + #ifdef DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == drwav_container_aiff) { + drwav_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 totalFramesRead; + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; /* Only byte-aligned formats are supported. */ + } + + totalFramesRead = 0; + + while (framesToRead > 0) { + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); + if (framesRead == 0) { + break; + } + + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); /* If this fails it means there's a bug in drwav_read_pcm_frames(). */ + + /* Validation to ensure we don't read too much from out intermediary buffer. This is to protect from invalid files. */ + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); /* This should never happen with a valid file. */ + break; + } + + drwav_mulaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + + #ifdef DR_WAV_LIBSNDFILE_COMPAT + { + if (pWav->container == drwav_container_aiff) { + drwav_uint64 iSample; + for (iSample = 0; iSample < samplesRead; iSample += 1) { + pBufferOut[iSample] = -pBufferOut[iSample]; + } + } + } + #endif + + pBufferOut += samplesRead; + framesToRead -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + if (pWav == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + /* Don't try to read more samples than can potentially fit in the output buffer. */ + if (framesToRead * pWav->channels * sizeof(drwav_int32) > DRWAV_SIZE_MAX) { + framesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int32) / pWav->channels; + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_pcm_frames_s32__pcm(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_pcm_frames_s32__msadpcm_ima(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_pcm_frames_s32__ieee(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_pcm_frames_s32__alaw(pWav, framesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_pcm_frames_s32__mulaw(pWav, framesToRead, pBufferOut); + } + + return 0; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_s32(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { + drwav__bswap_samples_s32(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + +DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32be(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + drwav_uint64 framesRead = drwav_read_pcm_frames_s32(pWav, framesToRead, pBufferOut); + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { + drwav__bswap_samples_s32(pBufferOut, framesRead*pWav->channels); + } + + return framesRead; +} + + +DRWAV_API void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = ((int)pIn[i] - 128) << 24; + } +} + +DRWAV_API void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = pIn[i] << 16; + } +} + +DRWAV_API void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + unsigned int s0 = pIn[i*3 + 0]; + unsigned int s1 = pIn[i*3 + 1]; + unsigned int s2 = pIn[i*3 + 2]; + + drwav_int32 sample32 = (drwav_int32)((s0 << 8) | (s1 << 16) | (s2 << 24)); + *pOut++ = sample32; + } +} + +DRWAV_API void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (drwav_int32)(2147483648.0f * pIn[i]); + } +} + +DRWAV_API void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]); + } +} + +DRWAV_API void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i = 0; i < sampleCount; ++i) { + *pOut++ = ((drwav_int32)drwav__alaw_to_s16(pIn[i])) << 16; + } +} + +DRWAV_API void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + size_t i; + + if (pOut == NULL || pIn == NULL) { + return; + } + + for (i= 0; i < sampleCount; ++i) { + *pOut++ = ((drwav_int32)drwav__mulaw_to_s16(pIn[i])) << 16; + } +} + + + +DRWAV_PRIVATE drwav_int16* drwav__read_pcm_frames_and_close_s16(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +{ + drwav_uint64 sampleDataSize; + drwav_int16* pSampleData; + drwav_uint64 framesRead; + + DRWAV_ASSERT(pWav != NULL); + + sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(drwav_int16); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; /* File's too big. */ + } + + pSampleData = (drwav_int16*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; /* Failed to allocate memory. */ + } + + framesRead = drwav_read_pcm_frames_s16(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); + if (framesRead != pWav->totalPCMFrameCount) { + drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); + drwav_uninit(pWav); + return NULL; /* There was an error reading the samples. */ + } + + drwav_uninit(pWav); + + if (sampleRate) { + *sampleRate = pWav->sampleRate; + } + if (channels) { + *channels = pWav->channels; + } + if (totalFrameCount) { + *totalFrameCount = pWav->totalPCMFrameCount; + } + + return pSampleData; +} + +DRWAV_PRIVATE float* drwav__read_pcm_frames_and_close_f32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +{ + drwav_uint64 sampleDataSize; + float* pSampleData; + drwav_uint64 framesRead; + + DRWAV_ASSERT(pWav != NULL); + + sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(float); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; /* File's too big. */ + } + + pSampleData = (float*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; /* Failed to allocate memory. */ + } + + framesRead = drwav_read_pcm_frames_f32(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); + if (framesRead != pWav->totalPCMFrameCount) { + drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); + drwav_uninit(pWav); + return NULL; /* There was an error reading the samples. */ + } + + drwav_uninit(pWav); + + if (sampleRate) { + *sampleRate = pWav->sampleRate; + } + if (channels) { + *channels = pWav->channels; + } + if (totalFrameCount) { + *totalFrameCount = pWav->totalPCMFrameCount; + } + + return pSampleData; +} + +DRWAV_PRIVATE drwav_int32* drwav__read_pcm_frames_and_close_s32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +{ + drwav_uint64 sampleDataSize; + drwav_int32* pSampleData; + drwav_uint64 framesRead; + + DRWAV_ASSERT(pWav != NULL); + + sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(drwav_int32); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; /* File's too big. */ + } + + pSampleData = (drwav_int32*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; /* Failed to allocate memory. */ + } + + framesRead = drwav_read_pcm_frames_s32(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); + if (framesRead != pWav->totalPCMFrameCount) { + drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); + drwav_uninit(pWav); + return NULL; /* There was an error reading the samples. */ + } + + drwav_uninit(pWav); + + if (sampleRate) { + *sampleRate = pWav->sampleRate; + } + if (channels) { + *channels = pWav->channels; + } + if (totalFrameCount) { + *totalFrameCount = pWav->totalPCMFrameCount; + } + + return pSampleData; +} + + + +DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +#ifndef DR_WAV_NO_STDIO +DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + + +#ifndef DR_WAV_NO_WCHAR +DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (channelsOut) { + *channelsOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (channelsOut) { + *channelsOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (channelsOut) { + *channelsOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} +#endif /* DR_WAV_NO_WCHAR */ +#endif /* DR_WAV_NO_STDIO */ + +DRWAV_API drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} + +DRWAV_API drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + drwav wav; + + if (channelsOut) { + *channelsOut = 0; + } + if (sampleRateOut) { + *sampleRateOut = 0; + } + if (totalFrameCountOut) { + *totalFrameCountOut = 0; + } + + if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { + return NULL; + } + + return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); +} +#endif /* DR_WAV_NO_CONVERSION_API */ + + +DRWAV_API void drwav_free(void* p, const drwav_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + drwav__free_from_callbacks(p, pAllocationCallbacks); + } else { + drwav__free_default(p, NULL); + } +} + +DRWAV_API drwav_uint16 drwav_bytes_to_u16(const drwav_uint8* data) +{ + return ((drwav_uint16)data[0] << 0) | ((drwav_uint16)data[1] << 8); +} + +DRWAV_API drwav_int16 drwav_bytes_to_s16(const drwav_uint8* data) +{ + return (drwav_int16)drwav_bytes_to_u16(data); +} + +DRWAV_API drwav_uint32 drwav_bytes_to_u32(const drwav_uint8* data) +{ + return drwav_bytes_to_u32_le(data); +} + +DRWAV_API float drwav_bytes_to_f32(const drwav_uint8* data) +{ + union { + drwav_uint32 u32; + float f32; + } value; + + value.u32 = drwav_bytes_to_u32(data); + return value.f32; +} + +DRWAV_API drwav_int32 drwav_bytes_to_s32(const drwav_uint8* data) +{ + return (drwav_int32)drwav_bytes_to_u32(data); +} + +DRWAV_API drwav_uint64 drwav_bytes_to_u64(const drwav_uint8* data) +{ + return + ((drwav_uint64)data[0] << 0) | ((drwav_uint64)data[1] << 8) | ((drwav_uint64)data[2] << 16) | ((drwav_uint64)data[3] << 24) | + ((drwav_uint64)data[4] << 32) | ((drwav_uint64)data[5] << 40) | ((drwav_uint64)data[6] << 48) | ((drwav_uint64)data[7] << 56); +} + +DRWAV_API drwav_int64 drwav_bytes_to_s64(const drwav_uint8* data) +{ + return (drwav_int64)drwav_bytes_to_u64(data); +} + + +DRWAV_API drwav_bool32 drwav_guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]) +{ + int i; + for (i = 0; i < 16; i += 1) { + if (a[i] != b[i]) { + return DRWAV_FALSE; + } + } + + return DRWAV_TRUE; +} + +DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) +{ + return + a[0] == b[0] && + a[1] == b[1] && + a[2] == b[2] && + a[3] == b[3]; +} + +#ifdef __MRC__ +/* Undo the pragma at the beginning of this file. */ +#pragma options opt reset +#endif + +#endif /* dr_wav_c */ +#endif /* DR_WAV_IMPLEMENTATION */ + +/* +REVISION HISTORY +================ +v0.13.16 - 2024-02-27 + - Fix a Wdouble-promotion warning. + +v0.13.15 - 2024-01-23 + - Relax some unnecessary validation that prevented some files from loading. + +v0.13.14 - 2023-12-02 + - Fix a warning about an unused variable. + +v0.13.13 - 2023-11-02 + - Fix a warning when compiling with Clang. + +v0.13.12 - 2023-08-07 + - Fix a possible crash in drwav_read_pcm_frames(). + +v0.13.11 - 2023-07-07 + - AIFF compatibility improvements. + +v0.13.10 - 2023-05-29 + - Fix a bug where drwav_init_with_metadata() does not decode any frames after initializtion. + +v0.13.9 - 2023-05-22 + - Add support for AIFF decoding (writing and metadata not supported). + - Add support for RIFX decoding (writing and metadata not supported). + - Fix a bug where metadata is not processed if it's located before the "fmt " chunk. + - Add a workaround for a type of malformed WAV file where the size of the "RIFF" and "data" chunks + are incorrectly set to 0xFFFFFFFF. + +v0.13.8 - 2023-03-25 + - Fix a possible null pointer dereference. + - Fix a crash when loading files with badly formed metadata. + +v0.13.7 - 2022-09-17 + - Fix compilation with DJGPP. + - Add support for disabling wchar_t with DR_WAV_NO_WCHAR. + +v0.13.6 - 2022-04-10 + - Fix compilation error on older versions of GCC. + - Remove some dependencies on the standard library. + +v0.13.5 - 2022-01-26 + - Fix an error when seeking to the end of the file. + +v0.13.4 - 2021-12-08 + - Fix some static analysis warnings. + +v0.13.3 - 2021-11-24 + - Fix an incorrect assertion when trying to endian swap 1-byte sample formats. This is now a no-op + rather than a failed assertion. + - Fix a bug with parsing of the bext chunk. + - Fix some static analysis warnings. + +v0.13.2 - 2021-10-02 + - Fix a possible buffer overflow when reading from compressed formats. + +v0.13.1 - 2021-07-31 + - Fix platform detection for ARM64. + +v0.13.0 - 2021-07-01 + - Improve support for reading and writing metadata. Use the `_with_metadata()` APIs to initialize + a WAV decoder and store the metadata within the `drwav` object. Use the `pMetadata` and + `metadataCount` members of the `drwav` object to read the data. The old way of handling metadata + via a callback is still usable and valid. + - API CHANGE: drwav_target_write_size_bytes() now takes extra parameters for calculating the + required write size when writing metadata. + - Add drwav_get_cursor_in_pcm_frames() + - Add drwav_get_length_in_pcm_frames() + - Fix a bug where drwav_read_raw() can call the read callback with a byte count of zero. + +v0.12.20 - 2021-06-11 + - Fix some undefined behavior. + +v0.12.19 - 2021-02-21 + - Fix a warning due to referencing _MSC_VER when it is undefined. + - Minor improvements to the management of some internal state concerning the data chunk cursor. + +v0.12.18 - 2021-01-31 + - Clean up some static analysis warnings. + +v0.12.17 - 2021-01-17 + - Minor fix to sample code in documentation. + - Correctly qualify a private API as private rather than public. + - Code cleanup. + +v0.12.16 - 2020-12-02 + - Fix a bug when trying to read more bytes than can fit in a size_t. + +v0.12.15 - 2020-11-21 + - Fix compilation with OpenWatcom. + +v0.12.14 - 2020-11-13 + - Minor code clean up. + +v0.12.13 - 2020-11-01 + - Improve compiler support for older versions of GCC. + +v0.12.12 - 2020-09-28 + - Add support for RF64. + - Fix a bug in writing mode where the size of the RIFF chunk incorrectly includes the header section. + +v0.12.11 - 2020-09-08 + - Fix a compilation error on older compilers. + +v0.12.10 - 2020-08-24 + - Fix a bug when seeking with ADPCM formats. + +v0.12.9 - 2020-08-02 + - Simplify sized types. + +v0.12.8 - 2020-07-25 + - Fix a compilation warning. + +v0.12.7 - 2020-07-15 + - Fix some bugs on big-endian architectures. + - Fix an error in s24 to f32 conversion. + +v0.12.6 - 2020-06-23 + - Change drwav_read_*() to allow NULL to be passed in as the output buffer which is equivalent to a forward seek. + - Fix a buffer overflow when trying to decode invalid IMA-ADPCM files. + - Add include guard for the implementation section. + +v0.12.5 - 2020-05-27 + - Minor documentation fix. + +v0.12.4 - 2020-05-16 + - Replace assert() with DRWAV_ASSERT(). + - Add compile-time and run-time version querying. + - DRWAV_VERSION_MINOR + - DRWAV_VERSION_MAJOR + - DRWAV_VERSION_REVISION + - DRWAV_VERSION_STRING + - drwav_version() + - drwav_version_string() + +v0.12.3 - 2020-04-30 + - Fix compilation errors with VC6. + +v0.12.2 - 2020-04-21 + - Fix a bug where drwav_init_file() does not close the file handle after attempting to load an erroneous file. + +v0.12.1 - 2020-04-13 + - Fix some pedantic warnings. + +v0.12.0 - 2020-04-04 + - API CHANGE: Add container and format parameters to the chunk callback. + - Minor documentation updates. + +v0.11.5 - 2020-03-07 + - Fix compilation error with Visual Studio .NET 2003. + +v0.11.4 - 2020-01-29 + - Fix some static analysis warnings. + - Fix a bug when reading f32 samples from an A-law encoded stream. + +v0.11.3 - 2020-01-12 + - Minor changes to some f32 format conversion routines. + - Minor bug fix for ADPCM conversion when end of file is reached. + +v0.11.2 - 2019-12-02 + - Fix a possible crash when using custom memory allocators without a custom realloc() implementation. + - Fix an integer overflow bug. + - Fix a null pointer dereference bug. + - Add limits to sample rate, channels and bits per sample to tighten up some validation. + +v0.11.1 - 2019-10-07 + - Internal code clean up. + +v0.11.0 - 2019-10-06 + - API CHANGE: Add support for user defined memory allocation routines. This system allows the program to specify their own memory allocation + routines with a user data pointer for client-specific contextual data. This adds an extra parameter to the end of the following APIs: + - drwav_init() + - drwav_init_ex() + - drwav_init_file() + - drwav_init_file_ex() + - drwav_init_file_w() + - drwav_init_file_w_ex() + - drwav_init_memory() + - drwav_init_memory_ex() + - drwav_init_write() + - drwav_init_write_sequential() + - drwav_init_write_sequential_pcm_frames() + - drwav_init_file_write() + - drwav_init_file_write_sequential() + - drwav_init_file_write_sequential_pcm_frames() + - drwav_init_file_write_w() + - drwav_init_file_write_sequential_w() + - drwav_init_file_write_sequential_pcm_frames_w() + - drwav_init_memory_write() + - drwav_init_memory_write_sequential() + - drwav_init_memory_write_sequential_pcm_frames() + - drwav_open_and_read_pcm_frames_s16() + - drwav_open_and_read_pcm_frames_f32() + - drwav_open_and_read_pcm_frames_s32() + - drwav_open_file_and_read_pcm_frames_s16() + - drwav_open_file_and_read_pcm_frames_f32() + - drwav_open_file_and_read_pcm_frames_s32() + - drwav_open_file_and_read_pcm_frames_s16_w() + - drwav_open_file_and_read_pcm_frames_f32_w() + - drwav_open_file_and_read_pcm_frames_s32_w() + - drwav_open_memory_and_read_pcm_frames_s16() + - drwav_open_memory_and_read_pcm_frames_f32() + - drwav_open_memory_and_read_pcm_frames_s32() + Set this extra parameter to NULL to use defaults which is the same as the previous behaviour. Setting this NULL will use + DRWAV_MALLOC, DRWAV_REALLOC and DRWAV_FREE. + - Add support for reading and writing PCM frames in an explicit endianness. New APIs: + - drwav_read_pcm_frames_le() + - drwav_read_pcm_frames_be() + - drwav_read_pcm_frames_s16le() + - drwav_read_pcm_frames_s16be() + - drwav_read_pcm_frames_f32le() + - drwav_read_pcm_frames_f32be() + - drwav_read_pcm_frames_s32le() + - drwav_read_pcm_frames_s32be() + - drwav_write_pcm_frames_le() + - drwav_write_pcm_frames_be() + - Remove deprecated APIs. + - API CHANGE: The following APIs now return native-endian data. Previously they returned little-endian data. + - drwav_read_pcm_frames() + - drwav_read_pcm_frames_s16() + - drwav_read_pcm_frames_s32() + - drwav_read_pcm_frames_f32() + - drwav_open_and_read_pcm_frames_s16() + - drwav_open_and_read_pcm_frames_s32() + - drwav_open_and_read_pcm_frames_f32() + - drwav_open_file_and_read_pcm_frames_s16() + - drwav_open_file_and_read_pcm_frames_s32() + - drwav_open_file_and_read_pcm_frames_f32() + - drwav_open_file_and_read_pcm_frames_s16_w() + - drwav_open_file_and_read_pcm_frames_s32_w() + - drwav_open_file_and_read_pcm_frames_f32_w() + - drwav_open_memory_and_read_pcm_frames_s16() + - drwav_open_memory_and_read_pcm_frames_s32() + - drwav_open_memory_and_read_pcm_frames_f32() + +v0.10.1 - 2019-08-31 + - Correctly handle partial trailing ADPCM blocks. + +v0.10.0 - 2019-08-04 + - Remove deprecated APIs. + - Add wchar_t variants for file loading APIs: + drwav_init_file_w() + drwav_init_file_ex_w() + drwav_init_file_write_w() + drwav_init_file_write_sequential_w() + - Add drwav_target_write_size_bytes() which calculates the total size in bytes of a WAV file given a format and sample count. + - Add APIs for specifying the PCM frame count instead of the sample count when opening in sequential write mode: + drwav_init_write_sequential_pcm_frames() + drwav_init_file_write_sequential_pcm_frames() + drwav_init_file_write_sequential_pcm_frames_w() + drwav_init_memory_write_sequential_pcm_frames() + - Deprecate drwav_open*() and drwav_close(): + drwav_open() + drwav_open_ex() + drwav_open_write() + drwav_open_write_sequential() + drwav_open_file() + drwav_open_file_ex() + drwav_open_file_write() + drwav_open_file_write_sequential() + drwav_open_memory() + drwav_open_memory_ex() + drwav_open_memory_write() + drwav_open_memory_write_sequential() + drwav_close() + - Minor documentation updates. + +v0.9.2 - 2019-05-21 + - Fix warnings. + +v0.9.1 - 2019-05-05 + - Add support for C89. + - Change license to choice of public domain or MIT-0. + +v0.9.0 - 2018-12-16 + - API CHANGE: Add new reading APIs for reading by PCM frames instead of samples. Old APIs have been deprecated and + will be removed in v0.10.0. Deprecated APIs and their replacements: + drwav_read() -> drwav_read_pcm_frames() + drwav_read_s16() -> drwav_read_pcm_frames_s16() + drwav_read_f32() -> drwav_read_pcm_frames_f32() + drwav_read_s32() -> drwav_read_pcm_frames_s32() + drwav_seek_to_sample() -> drwav_seek_to_pcm_frame() + drwav_write() -> drwav_write_pcm_frames() + drwav_open_and_read_s16() -> drwav_open_and_read_pcm_frames_s16() + drwav_open_and_read_f32() -> drwav_open_and_read_pcm_frames_f32() + drwav_open_and_read_s32() -> drwav_open_and_read_pcm_frames_s32() + drwav_open_file_and_read_s16() -> drwav_open_file_and_read_pcm_frames_s16() + drwav_open_file_and_read_f32() -> drwav_open_file_and_read_pcm_frames_f32() + drwav_open_file_and_read_s32() -> drwav_open_file_and_read_pcm_frames_s32() + drwav_open_memory_and_read_s16() -> drwav_open_memory_and_read_pcm_frames_s16() + drwav_open_memory_and_read_f32() -> drwav_open_memory_and_read_pcm_frames_f32() + drwav_open_memory_and_read_s32() -> drwav_open_memory_and_read_pcm_frames_s32() + drwav::totalSampleCount -> drwav::totalPCMFrameCount + - API CHANGE: Rename drwav_open_and_read_file_*() to drwav_open_file_and_read_*(). + - API CHANGE: Rename drwav_open_and_read_memory_*() to drwav_open_memory_and_read_*(). + - Add built-in support for smpl chunks. + - Add support for firing a callback for each chunk in the file at initialization time. + - This is enabled through the drwav_init_ex(), etc. family of APIs. + - Handle invalid FMT chunks more robustly. + +v0.8.5 - 2018-09-11 + - Const correctness. + - Fix a potential stack overflow. + +v0.8.4 - 2018-08-07 + - Improve 64-bit detection. + +v0.8.3 - 2018-08-05 + - Fix C++ build on older versions of GCC. + +v0.8.2 - 2018-08-02 + - Fix some big-endian bugs. + +v0.8.1 - 2018-06-29 + - Add support for sequential writing APIs. + - Disable seeking in write mode. + - Fix bugs with Wave64. + - Fix typos. + +v0.8 - 2018-04-27 + - Bug fix. + - Start using major.minor.revision versioning. + +v0.7f - 2018-02-05 + - Restrict ADPCM formats to a maximum of 2 channels. + +v0.7e - 2018-02-02 + - Fix a crash. + +v0.7d - 2018-02-01 + - Fix a crash. + +v0.7c - 2018-02-01 + - Set drwav.bytesPerSample to 0 for all compressed formats. + - Fix a crash when reading 16-bit floating point WAV files. In this case dr_wav will output silence for + all format conversion reading APIs (*_s16, *_s32, *_f32 APIs). + - Fix some divide-by-zero errors. + +v0.7b - 2018-01-22 + - Fix errors with seeking of compressed formats. + - Fix compilation error when DR_WAV_NO_CONVERSION_API + +v0.7a - 2017-11-17 + - Fix some GCC warnings. + +v0.7 - 2017-11-04 + - Add writing APIs. + +v0.6 - 2017-08-16 + - API CHANGE: Rename dr_* types to drwav_*. + - Add support for custom implementations of malloc(), realloc(), etc. + - Add support for Microsoft ADPCM. + - Add support for IMA ADPCM (DVI, format code 0x11). + - Optimizations to drwav_read_s16(). + - Bug fixes. + +v0.5g - 2017-07-16 + - Change underlying type for booleans to unsigned. + +v0.5f - 2017-04-04 + - Fix a minor bug with drwav_open_and_read_s16() and family. + +v0.5e - 2016-12-29 + - Added support for reading samples as signed 16-bit integers. Use the _s16() family of APIs for this. + - Minor fixes to documentation. + +v0.5d - 2016-12-28 + - Use drwav_int* and drwav_uint* sized types to improve compiler support. + +v0.5c - 2016-11-11 + - Properly handle JUNK chunks that come before the FMT chunk. + +v0.5b - 2016-10-23 + - A minor change to drwav_bool8 and drwav_bool32 types. + +v0.5a - 2016-10-11 + - Fixed a bug with drwav_open_and_read() and family due to incorrect argument ordering. + - Improve A-law and mu-law efficiency. + +v0.5 - 2016-09-29 + - API CHANGE. Swap the order of "channels" and "sampleRate" parameters in drwav_open_and_read*(). Rationale for this is to + keep it consistent with dr_audio and dr_flac. + +v0.4b - 2016-09-18 + - Fixed a typo in documentation. + +v0.4a - 2016-09-18 + - Fixed a typo. + - Change date format to ISO 8601 (YYYY-MM-DD) + +v0.4 - 2016-07-13 + - API CHANGE. Make onSeek consistent with dr_flac. + - API CHANGE. Rename drwav_seek() to drwav_seek_to_sample() for clarity and consistency with dr_flac. + - Added support for Sony Wave64. + +v0.3a - 2016-05-28 + - API CHANGE. Return drwav_bool32 instead of int in onSeek callback. + - Fixed a memory leak. + +v0.3 - 2016-05-22 + - Lots of API changes for consistency. + +v0.2a - 2016-05-16 + - Fixed Linux/GCC build. + +v0.2 - 2016-05-11 + - Added support for reading data as signed 32-bit PCM for consistency with dr_flac. + +v0.1a - 2016-05-07 + - Fixed a bug in drwav_open_file() where the file handle would not be closed if the loader failed to initialize. + +v0.1 - 2016-05-04 + - Initial versioned release. +*/ + +/* +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - Public Domain (www.unlicense.org) +=============================================================================== +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright 2023 David Reid + +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. + +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/source/engine/thirdparty/dr_libs/old/dr.h b/source/engine/thirdparty/dr_libs/old/dr.h new file mode 100644 index 0000000..fb13f95 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/old/dr.h @@ -0,0 +1,4755 @@ +// Public Domain. See "unlicense" statement at the end of this file. + +// USAGE +// +// This is a single-file library. To use it, do something like the following in one .c file. +// #define DR_IMPLEMENTATION +// #include "dr.h" +// +// You can then #include dr.h in other parts of the program as you would with any other header file. +// +// +// +// OPTIONS +// +// #define DR_UTIL_WIN32_USE_CRITICAL_SECTION_MUTEX +// - Use the Win32 CRITICAL_SECTION API for mutex objects. + +#ifndef dr_util_h +#define dr_util_h + +#ifdef __cplusplus +extern "C" { +#endif + + +// Disable MSVC compatibility if we're compiling with it. +#if defined(_MSC_VER) || defined(__MINGW32__) + #define DR_NO_MSVC_COMPAT +#endif + +#if defined(_MSC_VER) +#define DR_INLINE static __inline +#else +#define DR_INLINE static inline +#endif + + +#include +#include +#include +#include + +#ifndef DR_NO_MSVC_COMPAT +#include +#endif + +#ifndef DR_SIZED_TYPES_DEFINED +#define DR_SIZED_TYPES_DEFINED +#if defined(_MSC_VER) && _MSC_VER < 1600 +typedef signed char dr_int8; +typedef unsigned char dr_uint8; +typedef signed short dr_int16; +typedef unsigned short dr_uint16; +typedef signed int dr_int32; +typedef unsigned int dr_uint32; +typedef signed __int64 dr_int64; +typedef unsigned __int64 dr_uint64; +#else +#include +typedef int8_t dr_int8; +typedef uint8_t dr_uint8; +typedef int16_t dr_int16; +typedef uint16_t dr_uint16; +typedef int32_t dr_int32; +typedef uint32_t dr_uint32; +typedef int64_t dr_int64; +typedef uint64_t dr_uint64; +#endif +typedef dr_uint8 dr_bool8; +typedef dr_uint32 dr_bool32; +#define DR_TRUE 1 +#define DR_FALSE 0 +#endif + + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + + +///////////////////////////////////////////////////////// +// Annotations + +#ifndef IN +#define IN +#endif + +#ifndef OUT +#define OUT +#endif + +#ifndef UNUSED +#define UNUSED(x) ((void)(x)) +#endif + + +///////////////////////////////////////////////////////// +// min/max/clamp + +#ifndef dr_min +#define dr_min(x, y) (((x) < (y)) ? (x) : (y)) +#endif + +#ifndef dr_max +#define dr_max(x, y) (((x) > (y)) ? (x) : (y)) +#endif + +#ifndef dr_clamp +#define dr_clamp(x, low, high) (dr_max(low, dr_min(x, high))) +#endif + +#ifndef dr_round_up +#define dr_round_up(x, multiple) ((((x) + ((multiple) - 1)) / (multiple)) * (multiple)) +#endif + +#ifndef dr_round_up_signed +#define dr_round_up_signed(x, multiple) ((((x) + (((x) >= 0)*((multiple) - 1))) / (multiple)) * (multiple)) +#endif + +DR_INLINE dr_uint32 dr_next_power_of_2(dr_uint32 value) +{ + --value; + value = (value >> 1) | value; + value = (value >> 2) | value; + value = (value >> 4) | value; + value = (value >> 8) | value; + value = (value >> 16) | value; + return value + 1; +} + + +#define dr_abs(x) (((x) < 0) ? (-(x)) : (x)) + + +///////////////////////////////////////////////////////// +// MSVC Compatibility + +// A basic implementation of MSVC's strcpy_s(). +int dr_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src); + +// A basic implementation of MSVC's strncpy_s(). +int dr_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count); + +// A basic implementation of MSVC's strcat_s(). +int dr_strcat_s(char* dst, size_t dstSizeInBytes, const char* src); + +// A basic implementation of MSVC's strncat_s() +int dr_strncat_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count); + +// A basic implementation of MSVC's _atoi_s() +int dr_itoa_s(int value, char* dst, size_t dstSizeInBytes, int radix); + +#ifndef DR_NO_MSVC_COMPAT +#ifndef _TRUNCATE +#define _TRUNCATE ((size_t)-1) +#endif + +DR_INLINE int strcpy_s(char* dst, size_t dstSizeInBytes, const char* src) +{ + return dr_strcpy_s(dst, dstSizeInBytes, src); +} + +DR_INLINE int strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) +{ + return dr_strncpy_s(dst, dstSizeInBytes, src, count); +} + +DR_INLINE int strcat_s(char* dst, size_t dstSizeInBytes, const char* src) +{ + return dr_strcat_s(dst, dstSizeInBytes, src); +} + +DR_INLINE int strncat_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) +{ + return dr_strncat_s(dst, dstSizeInBytes, src, count); +} + +#ifndef __MINGW32__ +DR_INLINE int _stricmp(const char* string1, const char* string2) +{ + return strcasecmp(string1, string2); +} +#endif + +DR_INLINE int _itoa_s(int value, char* dst, size_t dstSizeInBytes, int radix) +{ + return dr_itoa_s(value, dst, dstSizeInBytes, radix); +} +#endif + + +///////////////////////////////////////////////////////// +// String Helpers + +// Determines if the given character is whitespace. +dr_bool32 dr_is_whitespace(dr_uint32 utf32); + +/// Removes every occurance of the given character from the given string. +void dr_strrmchar(char* str, char c); + +/// Finds the first non-whitespace character in the given string. +const char* dr_first_non_whitespace(const char* str); + +static inline const char* dr_ltrim(const char* str) { return dr_first_non_whitespace(str); } +static const char* dr_rtrim(const char* str); + +/// Trims both the leading and trailing whitespace from the given string. +void dr_trim(char* str); + +/// Finds the first occurance of a whitespace character in the given string. +const char* dr_first_whitespace(const char* str); + +/// Finds the beginning of the next line. +const char* dr_next_line(const char* str); + +/// Makes a copy of the first line of the given string. +size_t dr_copy_line(const char* str, char* lineOut, size_t lineOutSize); + +// A slow string replace function. Free the returned string with free(). +char* dr_string_replace(const char* src, const char* query, const char* replacement); + +// Replaces an ASCII character with another in the given string. +void dr_string_replace_ascii(char* src, char c, char replacement); + + +///////////////////////////////////////////////////////// +// Unicode Utilities + +/// Converts a UTF-32 character to UTF-16. +/// +/// @param utf16 [in] A pointer to an array of at least two 16-bit values that will receive the UTF-16 character. +/// +/// @return 2 if the returned character is a surrogate pair, 1 if it's a simple UTF-16 code point, or 0 if it's an invalid character. +/// +/// @remarks +/// It is assumed the is large enough to hold at least 2 unsigned shorts. will be padded with 0 for unused +/// components. +DR_INLINE int dr_utf32_to_utf16_ch(unsigned int utf32, unsigned short utf16[2]) +{ + if (utf16 == NULL) { + return 0; + } + + if (utf32 < 0xD800 || (utf32 >= 0xE000 && utf32 <= 0xFFFF)) + { + utf16[0] = (unsigned short)utf32; + utf16[1] = 0; + return 1; + } + else + { + if (utf32 >= 0x10000 && utf32 <= 0x10FFFF) + { + utf16[0] = (unsigned short)(0xD7C0 + (unsigned short)(utf32 >> 10)); + utf16[1] = (unsigned short)(0xDC00 + (unsigned short)(utf32 & 0x3FF)); + return 2; + } + else + { + // Invalid. + utf16[0] = 0; + utf16[1] = 0; + return 0; + } + } +} + +/// Converts a UTF-16 character to UTF-32. +DR_INLINE unsigned int dr_utf16_to_utf32_ch(unsigned short utf16[2]) +{ + if (utf16 == NULL) { + return 0; + } + + if (utf16[0] < 0xD800 || utf16[0] > 0xDFFF) + { + return utf16[0]; + } + else + { + if ((utf16[0] & 0xFC00) == 0xD800 && (utf16[1] & 0xFC00) == 0xDC00) + { + return ((unsigned int)utf16[0] << 10) + utf16[1] - 0x35FDC00; + } + else + { + // Invalid. + return 0; + } + } +} + +/// Converts a UTF-16 surrogate pair to UTF-32. +DR_INLINE unsigned int dr_utf16pair_to_utf32_ch(unsigned short utf160, unsigned short utf161) +{ + unsigned short utf16[2]; + utf16[0] = utf160; + utf16[1] = utf161; + return dr_utf16_to_utf32_ch(utf16); +} + + +///////////////////////////////////////////////////////// +// Aligned Allocations + +#ifndef DRUTIL_NO_ALIGNED_MALLOC +DR_INLINE void* dr_aligned_malloc(size_t alignment, size_t size) +{ +#if defined(_WIN32) || defined(_WIN64) + return _aligned_malloc(size, alignment); +#else + void* pResult; + if (posix_memalign(&pResult, alignment, size) == 0) { + return pResult; + } + + return 0; +#endif +} + +DR_INLINE void dr_aligned_free(void* ptr) +{ +#if defined(_WIN32) || defined(_WIN64) + _aligned_free(ptr); +#else + free(ptr); +#endif +} +#endif // !DRUTIL_NO_ALIGNED_MALLOC + + + +///////////////////////////////////////////////////////// +// Key/Value Pair Parsing + +typedef size_t (* dr_key_value_read_proc) (void* pUserData, void* pDataOut, size_t bytesToRead); +typedef void (* dr_key_value_pair_proc) (void* pUserData, const char* key, const char* value); +typedef void (* dr_key_value_error_proc)(void* pUserData, const char* message, unsigned int line); + +/// Parses a series of simple Key/Value pairs. +/// +/// @remarks +/// This function is suitable for parsing simple key/value config files. +/// @par +/// This function will never allocate memory on the heap. Because of this there is a minor restriction in the length of an individual +/// key/value pair which is 4KB. +/// @par +/// Formatting rules are as follows: +/// - The basic syntax for a key/value pair is [key][whitespace][value]. Example: MyProperty 1234 +/// - All key/value pairs must be declared on a single line, and a single line cannot contain more than a single key/value pair. +/// - Comments begin with the '#' character and continue until the end of the line. +/// - A key cannot contain spaces but are permitted in values. +/// - The value will have any leading and trailing whitespace trimmed. +/// @par +/// If an error occurs, that line will be skipped and processing will continue. +void dr_parse_key_value_pairs(dr_key_value_read_proc onRead, dr_key_value_pair_proc onPair, dr_key_value_error_proc onError, void* pUserData); + +// This will only return DR_FALSE if the file fails to open. It will still return DR_TRUE even if there are syntax error or whatnot. +dr_bool32 dr_parse_key_value_pairs_from_file(const char* filePath, dr_key_value_pair_proc onPair, dr_key_value_error_proc onError, void* pUserData); + + + +///////////////////////////////////////////////////////// +// Basic Tokenizer + +/// Retrieves the first token in the given string. +/// +/// @remarks +/// This function is suitable for doing a simple whitespace tokenization of a null-terminated string. +/// @par +/// The return value is a pointer to one character past the last character of the next token. You can use the return value to execute +/// this function in a loop to parse an entire string. +/// @par +/// can be null. If the buffer is too small to contain the entire token it will be set to an empty string. The original +/// input string combined with the return value can be used to reliably find the token. +/// @par +/// This will handle double-quoted strings, so a string such as "My \"Complex String\"" contains two tokens: "My" and "\"Complex String\"". +/// @par +/// This function has no dependencies. +const char* dr_next_token(const char* tokens, char* tokenOut, size_t tokenOutSize); + + + +///////////////////////////////////////////////////////// +// Known Folders + +/// Retrieves the path of the executable. +/// +/// @remarks +/// Currently only works on Windows and Linux. Other platforms will be added as they're needed. +dr_bool32 dr_get_executable_path(char* pathOut, size_t pathOutSize); + +/// Retrieves the path of the directory containing the executable. +/// +/// @remarks +/// Currently only works on Windows and Linux. Other platforms will be added as they're needed. +dr_bool32 dr_get_executable_directory_path(char* pathOut, size_t pathOutSize); + +/// Retrieves the path of the user's config directory. +/// +/// @remarks +/// On Windows this will typically be %APPDATA% and on Linux it will usually be ~/.config +dr_bool32 dr_get_config_folder_path(char* pathOut, size_t pathOutSize); + +/// Retrieves the path of the user's log directory. +/// +/// @remarks +/// On Windows this will typically be %APPDATA% and on Linux it will usually be var/log +dr_bool32 dr_get_log_folder_path(char* pathOut, size_t pathOutSize); + +/// Retrieves the current directory. +const char* dr_get_current_directory(char* pathOut, size_t pathOutSize); + +/// Sets the current directory. +dr_bool32 dr_set_current_directory(const char* path); + + + +///////////////////////////////////////////////////////// +// Basic File Management + +// Callback function for file iteration. +typedef dr_bool32 (* dr_iterate_files_proc)(const char* filePath, void* pUserData); + + +// Helper for opening a stdio FILE. +FILE* dr_fopen(const char* fileName, const char* openMode); + +// Helper for creating an empty file. +dr_bool32 dr_create_empty_file(const char* fileName, dr_bool32 failIfExists); + +// Retrieves the file data of the given file. Free the returned pointer with dr_free_file_data(). +void* dr_open_and_read_file(const char* filePath, size_t* pFileSizeOut); + +// Retrieves the file data of the given file as a null terminated string. Free the returned pointer with dr_free_file_data(). The +// returned file size is the length of the string not including the null terminator. +char* dr_open_and_read_text_file(const char* filePath, size_t* pFileSizeOut); + +// Creates a new file with the given data. +dr_bool32 dr_open_and_write_file(const char* filePath, const void* pData, size_t dataSize); + +// Creates a new file with the given string. +dr_bool32 dr_open_and_write_text_file(const char* filePath, const char* text); + +// Frees the file data returned by dr_open_and_read_file(). +void dr_free_file_data(void* valueReturnedByOpenAndReadFile); + +// Determines whether or not the given file path is to a file. +// +// This will return DR_FALSE if the path points to a directory. +dr_bool32 dr_file_exists(const char* filePath); + +// Determines whether or not the given file path points to a directory. +// +// This will return DR_FALSE if the path points to a file. +dr_bool32 dr_directory_exists(const char* directoryPath); +static inline dr_bool32 dr_is_directory(const char* directoryPath) { return dr_directory_exists(directoryPath); } + +// Moves a file. +// +// This uses rename() on POSIX platforms and MoveFileEx(oldPath, newPath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH) on windows platforms. +dr_bool32 dr_move_file(const char* oldPath, const char* newPath); + +// Copies a file. +dr_bool32 dr_copy_file(const char* srcPath, const char* dstPath, dr_bool32 failIfExists); + +// Determines if the given file is read only. +dr_bool32 dr_is_file_read_only(const char* filePath); + +// Retrieves the last modified time of the file at the given path. +dr_uint64 dr_get_file_modified_time(const char* filePath); + +// Deletes the file at the given path. +// +// This uses remove() on POSIX platforms and DeleteFile() on Windows platforms. +dr_bool32 dr_delete_file(const char* filePath); + +// Cross-platform wrapper for creating a directory. +dr_bool32 dr_mkdir(const char* directoryPath); + +// Recursively creates a directory. +dr_bool32 dr_mkdir_recursive(const char* directoryPath); + +// Iterates over every file and folder of the given directory. +dr_bool32 dr_iterate_files(const char* directory, dr_bool32 recursive, dr_iterate_files_proc proc, void* pUserData); + + +///////////////////////////////////////////////////////// +// DPI Awareness + +#if defined(_WIN32) +/// Win32 Only: Makes the application DPI aware. +void dr_win32_make_dpi_aware(); + +/// Win32 Only: Retrieves the base DPI to use as a reference when calculating DPI scaling. +void dr_win32_get_base_dpi(int* pDPIXOut, int* pDPIYOut); + +/// Win32 Only: Retrieves the system-wide DPI. +void dr_win32_get_system_dpi(int* pDPIXOut, int* pDPIYOut); + +/// Win32 Only: Retrieves the actual DPI of the monitor at the given index. +/// +/// @remarks +/// If per-monitor DPI is not supported, the system wide DPI settings will be used instead. +/// @par +/// This runs in linear time. +void dr_win32_get_monitor_dpi(int monitor, int* pDPIXOut, int* pDPIYOut); + +/// Win32 Only: Retrieves the number of monitors active at the time of calling. +/// +/// @remarks +/// This runs in linear time. +int dr_win32_get_monitor_count(); +#endif + + + +///////////////////////////////////////////////////////// +// Date / Time + +/// Retrieves a time_t as of the time the function was called. +time_t dr_now(); + +/// Formats a data/time string. +void dr_datetime_short(time_t t, char* strOut, unsigned int strOutSize); + +// Returns a date string in YYYYMMDD format. +void dr_date_YYYYMMDD(time_t t, char* strOut, unsigned int strOutSize); + + + +///////////////////////////////////////////////////////// +// Command Line +// +// The command line functions below are just simple iteration functions. This command line system is good for +// simple command lines, but probably not the best for programs requiring complex command line work. +// +// For argv style command lines, parse_cmdline() will run without any heap allocations. With a Win32 style +// command line there will be one malloc() per call fo parse_cmdline(). This is the only function that will do +// a malloc(). +// +// Below is an example: +// +// dr_cmdline cmdline; +// if (dr_init_cmdline(&cmdline, argc, argv)) { +// dr_parse_cmdline(&cmdline, my_cmdline_handler, pMyUserData); +// } +// +// void my_cmdline_handler(const char* key, const char* value, void* pUserData) +// { +// // Do something... +// } +// +// +// When parsing the command line, the first iteration will be the program path and the key will be "[path]". +// +// For segments such as "-abcd", the callback will be called for "a", "b", "c", "d" individually, with the +// value set to NULL. +// +// For segments such as "--server", the callback will be called for "server", with the value set to NULL. +// +// For segments such as "-f file.txt", the callback will be called with the key set to "f" and the value set +// to "file.txt". +// +// For segments such as "-f file1.txt file2.txt", the callback will be called twice, once for file1.txt and +// again for file2.txt, with with the key set to "f" in both cases. +// +// For segments where there is no leading key, the values will be posted as annonymous (key set to NULL). An example +// is "my_program.exe file1.txt file2.txt", in which case the first iteration will be the program path, the second iteration +// will be "file1.txt", with the key set to NULL. The third iteration will be "file2.txt" with the key set to NULL. +// +// For segments such as "-abcd file.txt", "a", "b", "c", "d" will be sent with NULL values, and "file.txt" will be +// posted with a NULL key. + +typedef struct dr_cmdline dr_cmdline; +struct dr_cmdline +{ + // argv style. + int argc; + char** argv; + + // Win32 style + const char* win32; +}; + +typedef dr_bool32 dr_cmdline_parse_proc(const char* key, const char* value, void* pUserData); + + +/// Initializes a command line object. +dr_bool32 dr_init_cmdline(dr_cmdline* pCmdLine, int argc, char** argv); + +/// Initializes a command line object using a Win32 style command line. +dr_bool32 dr_init_cmdline_win32(dr_cmdline* pCmdLine, const char* args); + +/// Parses the given command line. +void dr_parse_cmdline(dr_cmdline* pCmdLine, dr_cmdline_parse_proc callback, void* pUserData); + +/// Helper for determining whether or not the given key exists. +dr_bool32 dr_cmdline_key_exists(dr_cmdline* pCmdLine, const char* key); + +// Convers the given command line object to argc/argv style. +// +// Returns the argument count. Returns 0 if an error occurs. Free "argvOut" with dr_free_argv(). +int dr_cmdline_to_argv(dr_cmdline* pCmdLine, char*** argvOut); + +// Converts a WinMain style command line to argc/argv. +// +// Returns the argument count. Returns 0 if an error occurs. Free "argvOut" with dr_free_argv(). +int dr_winmain_to_argv(const char* cmdlineWinMain, char*** argvOut); + +// Frees the argc/argv command line that was generated by dr.h +void dr_free_argv(char** argv); + + + + +///////////////////////////////////////////////////////// +// Threading + +/// Puts the calling thread to sleep for approximately the given number of milliseconds. +/// +/// @remarks +/// This is not 100% accurate and should be considered an approximation. +void dr_sleep(unsigned int milliseconds); +void dr_yield(); + +/// Retrieves the number of logical cores on system. +unsigned int dr_get_logical_processor_count(); + + +/// Thread. +typedef void* dr_thread; +typedef int (* dr_thread_entry_proc)(void* pData); + +/// Creates and begins executing a new thread. +/// +/// @remarks +/// This will not return until the thread has entered into it's entry point. +/// @par +/// Creating a thread should be considered an expensive operation. For high performance, you should create threads +/// at load time and cache them. +dr_thread dr_create_thread(dr_thread_entry_proc entryProc, void* pData); + +/// Deletes the given thread. +/// +/// @remarks +/// This does not actually exit the thread, but rather deletes the memory that was allocated for the thread +/// object returned by dr_create_thread(). +/// @par +/// It is usually best to wait for the thread to terminate naturally with dr_wait_thread() before calling +/// this function, however it is still safe to do something like the following. +/// @code +/// dr_delete_thread(dr_create_thread(my_thread_proc, pData)) +/// @endcode +void dr_delete_thread(dr_thread thread); + +/// Waits for the given thread to terminate. +void dr_wait_thread(dr_thread thread); + +/// Helper function for waiting for a thread and then deleting the handle after it has terminated. +void dr_wait_and_delete_thread(dr_thread thread); + + + +/// Mutex +typedef void* dr_mutex; + +/// Creates a mutex object. +/// +/// @remarks +/// If an error occurs, 0 is returned. Otherwise a handle the size of a pointer is returned. +dr_mutex dr_create_mutex(); + +/// Deletes a mutex object. +void dr_delete_mutex(dr_mutex mutex); + +/// Locks the given mutex. +void dr_lock_mutex(dr_mutex mutex); + +/// Unlocks the given mutex. +void dr_unlock_mutex(dr_mutex mutex); + + + +/// Semaphore +typedef void* dr_semaphore; + +/// Creates a semaphore object. +/// +/// @remarks +/// If an error occurs, 0 is returned. Otherwise a handle the size of a pointer is returned. +dr_semaphore dr_create_semaphore(int initialValue); + +/// Deletes the given semaphore. +void dr_delete_semaphore(dr_semaphore semaphore); + +/// Waits on the given semaphore object and decrements it's counter by one upon returning. +dr_bool32 dr_wait_semaphore(dr_semaphore semaphore); + +/// Releases the given semaphore and increments it's counter by one upon returning. +dr_bool32 dr_release_semaphore(dr_semaphore semaphore); + + + +///////////////////////////////////////////////////////// +// Timing + +typedef struct +{ + dr_int64 counter; +} dr_timer; + +// Initializes a high-resolution timer. +void dr_timer_init(dr_timer* pTimer); + +// Ticks the timer and returns the number of seconds since the previous tick. +// +// The maximum return value is about 140 years or so. +double dr_timer_tick(dr_timer* pTimer); + + + + +///////////////////////////////////////////////////////// +// Random + +// Generates a random double between 0 and 1. This is bassed of C standard rand(). +double dr_randd(); + +// Generates a random float between 0 and 1. This is based off C standard rand(). +float dr_randf(); + + + +///////////////////////////////////////////////////////// +// User Accounts and Process Management + +// Retrieves the user name of the user running the application. +size_t dr_get_username(char* usernameOut, size_t usernameOutSize); + +// Retrieves the ID of the current process. +unsigned int dr_get_process_id(); + + + + +///////////////////////////////////////////////////////// +// Miscellaneous Stuff. + +// Helper for clearing the given object to 0. +#define dr_zero_object(pObject) memset(pObject, 0, sizeof(*pObject)); + +// Converts an ASCII hex character to it's integral equivalent. Returns DR_FALSE if it's not a valid hex character. +dr_bool32 dr_hex_char_to_uint(char ascii, unsigned int* out); + + +///////////////////////////////////////////////////////// +// C++ Specific + +#ifdef __cplusplus + +// Use this to prevent objects of the given class or struct from being copied. This is also useful for eliminating some +// compiler warnings. +// +// Note for structs - this sets the access mode to private, so place this at the end of the declaration. +#define NO_COPY(classname) \ + private: \ + classname(const classname &); \ + classname & operator=(const classname &); + + +#ifndef DR_NO_MSVC_COMPAT +extern "C++" +{ + +template +int strcpy_s(char (&dst)[dstSizeInBytes], const char* src) +{ + return strcpy_s(dst, dstSizeInBytes, src); +} + +template +int strncpy_s(char (&dst)[dstSizeInBytes], const char* src, size_t count) +{ + return strncpy_s(dst, dstSizeInBytes, src, count); +} + +template +int strcat_s(char (&dst)[dstSizeInBytes], const char* src) +{ + return strcat_s(dst, dstSizeInBytes, src); +} + +template +int strncat_s(char (&dst)[dstSizeInBytes], const char* src, size_t count) +{ + return strncat_s(dst, dstSizeInBytes, src, count); +} + +} +#endif + +#endif + + + +#ifdef __cplusplus +} +#endif + +#endif //dr_util_h + + + +/////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef DR_IMPLEMENTATION +#include +#include +#include +#include // For memmove() +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +int dr_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src) +{ + if (dst == 0) { + return EINVAL; + } + if (dstSizeInBytes == 0) { + return ERANGE; + } + if (src == 0) { + dst[0] = '\0'; + return EINVAL; + } + + size_t i; + for (i = 0; i < dstSizeInBytes && src[i] != '\0'; ++i) { + dst[i] = src[i]; + } + + if (i < dstSizeInBytes) { + dst[i] = '\0'; + return 0; + } + + dst[0] = '\0'; + return ERANGE; +} + +int dr_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) +{ + if (dst == 0) { + return EINVAL; + } + if (dstSizeInBytes == 0) { + return ERANGE; + } + if (src == 0) { + dst[0] = '\0'; + return EINVAL; + } + + size_t maxcount = count; + if (count == ((size_t)-1) || count >= dstSizeInBytes) { // -1 = _TRUNCATE + maxcount = dstSizeInBytes - 1; + } + + size_t i; + for (i = 0; i < maxcount && src[i] != '\0'; ++i) { + dst[i] = src[i]; + } + + if (src[i] == '\0' || i == count || count == ((size_t)-1)) { + dst[i] = '\0'; + return 0; + } + + dst[0] = '\0'; + return ERANGE; +} + +int dr_strcat_s(char* dst, size_t dstSizeInBytes, const char* src) +{ + if (dst == 0) { + return EINVAL; + } + if (dstSizeInBytes == 0) { + return ERANGE; + } + if (src == 0) { + dst[0] = '\0'; + return EINVAL; + } + + char* dstorig = dst; + + while (dstSizeInBytes > 0 && dst[0] != '\0') { + dst += 1; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes == 0) { + return EINVAL; // Unterminated. + } + + + while (dstSizeInBytes > 0 && src[0] != '\0') { + *dst++ = *src++; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes > 0) { + dst[0] = '\0'; + } else { + dstorig[0] = '\0'; + return ERANGE; + } + + return 0; +} + +int dr_strncat_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) +{ + if (dst == 0) { + return EINVAL; + } + if (dstSizeInBytes == 0) { + return ERANGE; + } + if (src == 0) { + return EINVAL; + } + + char* dstorig = dst; + + while (dstSizeInBytes > 0 && dst[0] != '\0') { + dst += 1; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes == 0) { + return EINVAL; // Unterminated. + } + + + if (count == ((size_t)-1)) { // _TRUNCATE + count = dstSizeInBytes - 1; + } + + while (dstSizeInBytes > 0 && src[0] != '\0' && count > 0) + { + *dst++ = *src++; + dstSizeInBytes -= 1; + count -= 1; + } + + if (dstSizeInBytes > 0) { + dst[0] = '\0'; + } else { + dstorig[0] = '\0'; + return ERANGE; + } + + return 0; +} + +int dr_itoa_s(int value, char* dst, size_t dstSizeInBytes, int radix) +{ + if (dst == NULL || dstSizeInBytes == 0) { + return EINVAL; + } + if (radix < 2 || radix > 36) { + dst[0] = '\0'; + return EINVAL; + } + + int sign = (value < 0 && radix == 10) ? -1 : 1; // The negative sign is only used when the base is 10. + + unsigned int valueU; + if (value < 0) { + valueU = -value; + } else { + valueU = value; + } + + char* dstEnd = dst; + do + { + int remainder = valueU % radix; + if (remainder > 9) { + *dstEnd = (char)((remainder - 10) + 'a'); + } else { + *dstEnd = (char)(remainder + '0'); + } + + dstEnd += 1; + dstSizeInBytes -= 1; + valueU /= radix; + } while (dstSizeInBytes > 0 && valueU > 0); + + if (dstSizeInBytes == 0) { + dst[0] = '\0'; + return EINVAL; // Ran out of room in the output buffer. + } + + if (sign < 0) { + *dstEnd++ = '-'; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes == 0) { + dst[0] = '\0'; + return EINVAL; // Ran out of room in the output buffer. + } + + *dstEnd = '\0'; + + + // At this point the string will be reversed. + dstEnd -= 1; + while (dst < dstEnd) { + char temp = *dst; + *dst = *dstEnd; + *dstEnd = temp; + + dst += 1; + dstEnd -= 1; + } + + return 0; +} + +///////////////////////////////////////////////////////// +// String Helpers + +dr_bool32 dr_is_whitespace(dr_uint32 utf32) +{ + return utf32 == ' ' || utf32 == '\t' || utf32 == '\n' || utf32 == '\v' || utf32 == '\f' || utf32 == '\r'; +} + +void dr_strrmchar(char* str, char c) +{ + char* src = str; + char* dst = str; + + while (src[0] != '\0') + { + dst[0] = src[0]; + + if (dst[0] != c) { + dst += 1; + } + + src += 1; + } + + dst[0] = '\0'; +} + +const char* dr_first_non_whitespace(const char* str) +{ + if (str == NULL) { + return NULL; + } + + while (str[0] != '\0' && !(str[0] != ' ' && str[0] != '\t' && str[0] != '\n' && str[0] != '\v' && str[0] != '\f' && str[0] != '\r')) { + str += 1; + } + + return str; +} + +const char* dr_first_whitespace(const char* str) +{ + if (str == NULL) { + return NULL; + } + + while (str[0] != '\0' && (str[0] != ' ' && str[0] != '\t' && str[0] != '\n' && str[0] != '\v' && str[0] != '\f' && str[0] != '\r')) { + str += 1; + } + + return str; +} + +const char* dr_rtrim(const char* str) +{ + if (str == NULL) { + return NULL; + } + + const char* rstr = str; + while (str[0] != '\0') { + if (dr_is_whitespace(str[0])) { + str += 1; + continue; + } + + str += 1; + rstr = str; + } + + return rstr; +} + +void dr_trim(char* str) +{ + if (str == NULL) { + return; + } + + const char* lstr = dr_ltrim(str); + const char* rstr = dr_rtrim(lstr); + + if (lstr > str) { + memmove(str, lstr, rstr-lstr); + } + + str[rstr-lstr] = '\0'; +} + +const char* dr_next_line(const char* str) +{ + if (str == NULL) { + return NULL; + } + + while (str[0] != '\0' && (str[0] != '\n' && !(str[0] == '\r' && str[1] == '\n'))) { + str += 1; + } + + if (str[0] == '\0') { + return NULL; + } + + if (str[0] == '\r') { + return str + 2; + } + + return str + 1; +} + +size_t dr_copy_line(const char* str, char* lineOut, size_t lineOutSize) +{ + if (str == NULL) { + return 0; + } + + if (str == NULL) { + return 0; + } + + size_t length = 0; + while (lineOutSize > 0 && str[0] != '\0' && (str[0] != '\n' && !(str[0] == '\r' && str[1] == '\n'))) { + *lineOut++ = *str++; + lineOutSize -= 1; + length += 1; + } + + if (lineOutSize == 0) { + return 0; + } + + *lineOut = '\0'; + return length; +} + +char* dr_string_replace(const char* src, const char* query, const char* replacement) +{ + // This function could be improved, but it's good enough for now. + + if (src == NULL || query == NULL) { + return NULL; + } + + if (replacement == NULL) { + replacement = ""; + } + + int queryLen = (int)strlen(query); + int replacementLen = (int)strlen(replacement); + + size_t replacementCount = 0; + + const char* temp = src; + for (;;) { + temp = strstr(temp, query); + if (temp == NULL) { + break; + } + + temp += queryLen; + replacementCount += 1; + } + + + char* result = (char*)malloc(strlen(src) + (replacementLen - queryLen)*replacementCount + 1); // +1 for null terminator. + if (result == NULL) { + return NULL; + } + + char* runningResult = result; + for (size_t i = 0; i < replacementCount; ++i) { + size_t len = strstr(src, query) - src; + for (size_t j = 0; j < len; ++j) { + runningResult[j] = src[j]; + } + + runningResult += len; + for (int j = 0; j < replacementLen; ++j) { + runningResult[j] = replacement[j]; + } + + runningResult += replacementLen; + src += len + queryLen; + } + + // The trailing part. + strcpy_s(runningResult, strlen(src)+1, src); + return result; +} + +void dr_string_replace_ascii(char* src, char c, char replacement) +{ + for (;;) { + if (*src == '\0') { + break; + } + + if (*src == c) { + *src = replacement; + } + + src += 1; + } +} + + +///////////////////////////////////////////////////////// +// Key/Value Pair Parsing + +void dr_parse_key_value_pairs(dr_key_value_read_proc onRead, dr_key_value_pair_proc onPair, dr_key_value_error_proc onError, void* pUserData) +{ + if (onRead == NULL) { + return; + } + + char pChunk[4096]; + size_t chunkSize = 0; + + unsigned int currentLine = 1; + + dr_bool32 moveToNextLineBeforeProcessing = DR_FALSE; + dr_bool32 skipWhitespaceBeforeProcessing = DR_FALSE; + + // Just keep looping. We'll break from this loop when we have run out of data. + for (;;) + { + // Start the iteration by reading as much data as we can. + chunkSize = onRead(pUserData, pChunk, sizeof(pChunk)); + if (chunkSize == 0) { + // No more data available. + return; + } + + char* pChunkEnd = pChunk + chunkSize; + char* pC = pChunk; // Chunk pointer. This is as the chunk is processed. + + if (moveToNextLineBeforeProcessing) + { + move_to_next_line: + while (pC < pChunkEnd && pC[0] != '\n') { + pC += 1; + } + + if (pC == pChunkEnd) { + // Ran out of data. Load the next chunk and keep going. + moveToNextLineBeforeProcessing = DR_TRUE; + continue; + } + + pC += 1; // pC[0] == '\n' - skip past the new line character. + currentLine += 1; + moveToNextLineBeforeProcessing = DR_FALSE; + } + + if (skipWhitespaceBeforeProcessing) + { + while (pC < pChunkEnd && (pC[0] == ' ' || pC[0] == '\t' || pC[0] == '\r')) { + pC += 1; + } + + if (pC == pChunkEnd) { + // Ran out of data. + skipWhitespaceBeforeProcessing = DR_TRUE; + continue; + } + + skipWhitespaceBeforeProcessing = DR_FALSE; + } + + + // We loop character by character. When we run out of data, we start again. + while (pC < pChunkEnd) + { + //// Key //// + + // Skip whitespace. + while (pC < pChunkEnd && (pC[0] == ' ' || pC[0] == '\t' || pC[0] == '\r')) { + pC += 1; + } + + if (pC == pChunkEnd) { + // Ran out of data. + skipWhitespaceBeforeProcessing = DR_TRUE; + continue; + } + + if (pC[0] == '\n') { + // Found the end of the line. + pC += 1; + currentLine += 1; + continue; + } + + if (pC[0] == '#') { + // Found a comment. Move to the end of the line and continue. + goto move_to_next_line; + } + + char* pK = pC; + while (pC < pChunkEnd && pC[0] != ' ' && pC[0] != '\t' && pC[0] != '\r' && pC[0] != '\n' && pC[0] != '#') { + pC += 1; + } + + if (pC == pChunkEnd) + { + // Ran out of data. We need to move what we have of the key to the start of the chunk buffer, and then read more data. + if (chunkSize == sizeof(pChunk)) + { + size_t lineSizeSoFar = pC - pK; + memmove(pChunk, pK, lineSizeSoFar); + + chunkSize = lineSizeSoFar + onRead(pUserData, pChunk + lineSizeSoFar, sizeof(pChunk) - lineSizeSoFar); + pChunkEnd = pChunk + chunkSize; + + pK = pChunk; + pC = pChunk + lineSizeSoFar; + while (pC < pChunkEnd && pC[0] != ' ' && pC[0] != '\t' && pC[0] != '\r' && pC[0] != '\n' && pC[0] != '#') { + pC += 1; + } + } + + if (pC == pChunkEnd) { + if (chunkSize == sizeof(pChunk)) { + if (onError) { + onError(pUserData, "Line is too long. A single line cannot exceed 4KB.", currentLine); + } + + goto move_to_next_line; + } else { + // No more data. Just treat this one as a value-less key and return. + if (onPair) { + pC[0] = '\0'; + onPair(pUserData, pK, NULL); + } + + return; + } + } + } + + char* pKEnd = pC; + + //// Value //// + + // Skip whitespace. + while (pC < pChunkEnd && (pC[0] == ' ' || pC[0] == '\t' || pC[0] == '\r')) { + pC += 1; + } + + if (pC == pChunkEnd) + { + // Ran out of data. We need to move what we have of the key to the start of the chunk buffer, and then read more data. + if (chunkSize == sizeof(pChunk)) + { + size_t lineSizeSoFar = pC - pK; + memmove(pChunk, pK, lineSizeSoFar); + + chunkSize = lineSizeSoFar + onRead(pUserData, pChunk + lineSizeSoFar, sizeof(pChunk) - lineSizeSoFar); + pChunkEnd = pChunk + chunkSize; + + pKEnd = pChunk + (pKEnd - pK); + pK = pChunk; + pC = pChunk + lineSizeSoFar; + while (pC < pChunkEnd && (pC[0] == ' ' || pC[0] == '\t' || pC[0] == '\r')) { + pC += 1; + } + } + + if (pC == pChunkEnd) { + if (chunkSize == sizeof(pChunk)) { + if (onError) { + onError(pUserData, "Line is too long. A single line cannot exceed 4KB.", currentLine); + } + + goto move_to_next_line; + } else { + // No more data. Just treat this one as a value-less key and return. + if (onPair) { + pKEnd[0] = '\0'; + onPair(pUserData, pK, NULL); + } + + return; + } + } + } + + if (pC[0] == '\n') { + // Found the end of the line. Treat it as a value-less key. + pKEnd[0] = '\0'; + if (onPair) { + onPair(pUserData, pK, NULL); + } + + pC += 1; + currentLine += 1; + continue; + } + + if (pC[0] == '#') { + // Found a comment. Treat is as a value-less key and move to the end of the line. + pKEnd[0] = '\0'; + if (onPair) { + onPair(pUserData, pK, NULL); + } + + goto move_to_next_line; + } + + char* pV = pC; + + // Find the last non-whitespace character. + char* pVEnd = pC; + while (pC < pChunkEnd && pC[0] != '\n' && pC[0] != '#') { + if (pC[0] != ' ' && pC[0] != '\t' && pC[0] != '\r') { + pVEnd = pC; + } + + pC += 1; + } + + if (pC == pChunkEnd) + { + // Ran out of data. We need to move what we have of the key to the start of the chunk buffer, and then read more data. + if (chunkSize == sizeof(pChunk)) + { + size_t lineSizeSoFar = pC - pK; + memmove(pChunk, pK, lineSizeSoFar); + + chunkSize = lineSizeSoFar + onRead(pUserData, pChunk + lineSizeSoFar, sizeof(pChunk) - lineSizeSoFar); + pChunkEnd = pChunk + chunkSize; + + pVEnd = pChunk + (pVEnd - pK); + pKEnd = pChunk + (pKEnd - pK); + pV = pChunk + (pV - pK); + pK = pChunk; + pC = pChunk + lineSizeSoFar; + while (pC < pChunkEnd && pC[0] != '\n' && pC[0] != '#') { + if (pC[0] != ' ' && pC[0] != '\t' && pC[0] != '\r') { + pVEnd = pC; + } + + pC += 1; + } + } + + if (pC == pChunkEnd) { + if (chunkSize == sizeof(pChunk)) { + if (onError) { + onError(pUserData, "Line is too long. A single line cannot exceed 4KB.", currentLine); + } + + goto move_to_next_line; + } + } + } + + + // Before null-terminating the value we first need to determine how we'll proceed after posting onPair. + dr_bool32 wasOnNL = pVEnd[1] == '\n'; + + pKEnd[0] = '\0'; + pVEnd[1] = '\0'; + if (onPair) { + onPair(pUserData, pK, pV); + } + + if (wasOnNL) + { + // Was sitting on a new-line character. + pC += 1; + currentLine += 1; + continue; + } + else + { + // Was sitting on a comment - just to the next line. + goto move_to_next_line; + } + } + } +} + + +typedef struct +{ + FILE* pFile; + dr_key_value_pair_proc onPair; + dr_key_value_error_proc onError; + void* pOriginalUserData; +} dr_parse_key_value_pairs_from_file_data; + +size_t dr_parse_key_value_pairs_from_file__on_read(void* pUserData, void* pDataOut, size_t bytesToRead) +{ + dr_parse_key_value_pairs_from_file_data* pData = (dr_parse_key_value_pairs_from_file_data*)pUserData; + assert(pData != NULL); + + return fread(pDataOut, 1, bytesToRead, pData->pFile); +} + +void dr_parse_key_value_pairs_from_file__on_pair(void* pUserData, const char* key, const char* value) +{ + dr_parse_key_value_pairs_from_file_data* pData = (dr_parse_key_value_pairs_from_file_data*)pUserData; + assert(pData != NULL); + + pData->onPair(pData->pOriginalUserData, key, value); +} + +void dr_parse_key_value_pairs_from_file__on_error(void* pUserData, const char* message, unsigned int line) +{ + dr_parse_key_value_pairs_from_file_data* pData = (dr_parse_key_value_pairs_from_file_data*)pUserData; + assert(pData != NULL); + + pData->onError(pData->pOriginalUserData, message, line); +} + +dr_bool32 dr_parse_key_value_pairs_from_file(const char* filePath, dr_key_value_pair_proc onPair, dr_key_value_error_proc onError, void* pUserData) +{ + dr_parse_key_value_pairs_from_file_data data; + data.pFile = dr_fopen(filePath, "rb"); + if (data.pFile == NULL) { + if (onError) onError(pUserData, "Could not open file.", 0); + return DR_FALSE; + } + + data.onPair = onPair; + data.onError = onError; + data.pOriginalUserData = pUserData; + dr_parse_key_value_pairs(dr_parse_key_value_pairs_from_file__on_read, dr_parse_key_value_pairs_from_file__on_pair, dr_parse_key_value_pairs_from_file__on_error, &data); + + fclose(data.pFile); + return DR_TRUE; +} + + +///////////////////////////////////////////////////////// +// Basic Tokenizer + +const char* dr_next_token(const char* tokens, char* tokenOut, size_t tokenOutSize) +{ + if (tokenOut) tokenOut[0] = '\0'; + + if (tokens == NULL) { + return NULL; + } + + // Skip past leading whitespace. + while (tokens[0] != '\0' && !(tokens[0] != ' ' && tokens[0] != '\t' && tokens[0] != '\n' && tokens[0] != '\v' && tokens[0] != '\f' && tokens[0] != '\r')) { + tokens += 1; + } + + if (tokens[0] == '\0') { + return NULL; + } + + + const char* strBeg = tokens; + const char* strEnd = strBeg; + + if (strEnd[0] == '\"') + { + // It's double-quoted - loop until the next unescaped quote character. + + // Skip past the first double-quote character. + strBeg += 1; + strEnd += 1; + + // Keep looping until the next unescaped double-quote character. + char prevChar = '\0'; + while (strEnd[0] != '\0' && (strEnd[0] != '\"' || prevChar == '\\')) + { + prevChar = strEnd[0]; + strEnd += 1; + } + } + else + { + // It's not double-quoted - just loop until the first whitespace. + while (strEnd[0] != '\0' && (strEnd[0] != ' ' && strEnd[0] != '\t' && strEnd[0] != '\n' && strEnd[0] != '\v' && strEnd[0] != '\f' && strEnd[0] != '\r')) { + strEnd += 1; + } + } + + + // If the output buffer is large enough to hold the token, copy the token into it. When we copy the token we need to + // ensure we don't include the escape character. + //assert(strEnd >= strBeg); + + while (tokenOutSize > 1 && strBeg < strEnd) + { + if (strBeg[0] == '\\' && strBeg[1] == '\"' && strBeg < strEnd) { + strBeg += 1; + } + + *tokenOut++ = *strBeg++; + tokenOutSize -= 1; + } + + // Null-terminate. + if (tokenOutSize > 0) { + *tokenOut = '\0'; + } + + + // Skip past the double-quote character before returning. + if (strEnd[0] == '\"') { + strEnd += 1; + } + + return strEnd; +} + + + + +///////////////////////////////////////////////////////// +// Known Folders + +#if defined(_WIN32) +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4091) // 'typedef ': ignored on left of 'tagGPFIDL_FLAGS' when no variable is declared +#endif +#include +#if defined(_MSC_VER) + #pragma warning(pop) +#endif + + +dr_bool32 dr_get_executable_path(char* pathOut, size_t pathOutSize) +{ + if (pathOut == NULL || pathOutSize == 0) { + return 0; + } + + DWORD length = GetModuleFileNameA(NULL, pathOut, (DWORD)pathOutSize); + if (length == 0) { + pathOut[0] = '\0'; + return DR_FALSE; + } + + // Force null termination. + if (length == pathOutSize) { + pathOut[length - 1] = '\0'; + } + + // Back slashes need to be normalized to forward. + while (pathOut[0] != '\0') { + if (pathOut[0] == '\\') { + pathOut[0] = '/'; + } + + pathOut += 1; + } + + return DR_TRUE; +} + +dr_bool32 dr_get_config_folder_path(char* pathOut, size_t pathOutSize) +{ + // The documentation for SHGetFolderPathA() says that the output path should be the size of MAX_PATH. We'll enforce + // that just to be safe. + if (pathOutSize >= MAX_PATH) + { + SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, pathOut); + } + else + { + char pathOutTemp[MAX_PATH]; + SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, pathOutTemp); + + if (strcpy_s(pathOut, pathOutSize, pathOutTemp) != 0) { + return 0; + } + } + + + // Back slashes need to be normalized to forward. + while (pathOut[0] != '\0') { + if (pathOut[0] == '\\') { + pathOut[0] = '/'; + } + + pathOut += 1; + } + + return 1; +} + +dr_bool32 dr_get_log_folder_path(char* pathOut, size_t pathOutSize) +{ + return dr_get_config_folder_path(pathOut, pathOutSize); +} + +const char* dr_get_current_directory(char* pathOut, size_t pathOutSize) +{ + DWORD result = GetCurrentDirectoryA((DWORD)pathOutSize, pathOut); + if (result == 0) { + return NULL; + } + + return pathOut; +} + +dr_bool32 dr_set_current_directory(const char* path) +{ + return SetCurrentDirectoryA(path) != 0; +} +#else +#include +#include +#include + +dr_bool32 dr_get_executable_path(char* pathOut, size_t pathOutSize) +{ + if (pathOut == NULL || pathOutSize == 0) { + return 0; + } + + ssize_t length = readlink("/proc/self/exe", pathOut, pathOutSize); + if (length == -1) { + pathOut[0] = '\0'; + return DR_FALSE; + } + + if ((size_t)length == pathOutSize) { + pathOut[length - 1] = '\0'; + } else { + pathOut[length] = '\0'; + } + + return DR_TRUE; +} + +dr_bool32 dr_get_config_folder_path(char* pathOut, size_t pathOutSize) +{ + const char* configdir = getenv("XDG_CONFIG_HOME"); + if (configdir != NULL) + { + return strcpy_s(pathOut, pathOutSize, configdir) == 0; + } + else + { + const char* homedir = getenv("HOME"); + if (homedir == NULL) { + homedir = getpwuid(getuid())->pw_dir; + } + + if (homedir != NULL) + { + if (strcpy_s(pathOut, pathOutSize, homedir) == 0) + { + size_t homedirLength = strlen(homedir); + pathOut += homedirLength; + pathOutSize -= homedirLength; + + if (pathOutSize > 0) + { + pathOut[0] = '/'; + pathOut += 1; + pathOutSize -= 1; + + return strcpy_s(pathOut, pathOutSize, ".config") == 0; + } + } + } + } + + return 0; +} + +dr_bool32 dr_get_log_folder_path(char* pathOut, size_t pathOutSize) +{ + return strcpy_s(pathOut, pathOutSize, "var/log") == 0; +} + +const char* dr_get_current_directory(char* pathOut, size_t pathOutSize) +{ + return getcwd(pathOut, pathOutSize); +} + +dr_bool32 dr_set_current_directory(const char* path) +{ + return chdir(path) == 0; +} +#endif + +dr_bool32 dr_get_executable_directory_path(char* pathOut, size_t pathOutSize) +{ + if (!dr_get_executable_path(pathOut, pathOutSize)) { + return DR_FALSE; + } + + // A null terminator needs to be placed at the last slash. + char* lastSlash = pathOut; + while (pathOut[0] != '\0') { + if (pathOut[0] == '/' || pathOut[0] == '\\') { + lastSlash = pathOut; + } + pathOut += 1; + } + + lastSlash[0] = '\0'; + return DR_TRUE; +} + + + +///////////////////////////////////////////////////////// +// Basic File Management + +#ifndef _WIN32 +#include +#include +#include +#endif + +FILE* dr_fopen(const char* filePath, const char* openMode) +{ + FILE* pFile; +#ifdef _MSC_VER + if (fopen_s(&pFile, filePath, openMode) != 0) { + return NULL; + } +#else + pFile = fopen(filePath, openMode); + if (pFile == NULL) { + return NULL; + } +#endif + + return pFile; +} + +dr_bool32 dr_create_empty_file(const char* fileName, dr_bool32 failIfExists) +{ + if (fileName == NULL) { + return DR_FALSE; + } +#ifdef _WIN32 + DWORD dwCreationDisposition; + if (failIfExists) { + dwCreationDisposition = CREATE_NEW; + } else { + dwCreationDisposition = CREATE_ALWAYS; + } + + HANDLE hFile = CreateFileA(fileName, FILE_GENERIC_WRITE, 0, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + return DR_FALSE; + } + + CloseHandle(hFile); + return DR_TRUE; +#else + int flags = O_WRONLY | O_CREAT; + if (failIfExists) { + flags |= O_EXCL; + } else { + flags |= O_TRUNC; + } + int fd = open(fileName, flags, 0666); + if (fd == -1) { + return DR_FALSE; + } + + close(fd); + return DR_TRUE; +#endif +} + +static void* dr_open_and_read_file_with_extra_data(const char* filePath, size_t* pFileSizeOut, size_t extraBytes) +{ + if (pFileSizeOut) *pFileSizeOut = 0; // For safety. + + if (filePath == NULL) { + return NULL; + } + + // TODO: Use 64-bit versions of the FILE APIs. + + FILE* pFile = dr_fopen(filePath, "rb"); + if (pFile == NULL) { + return NULL; + } + + fseek(pFile, 0, SEEK_END); + dr_uint64 fileSize = ftell(pFile); + fseek(pFile, 0, SEEK_SET); + + if (fileSize + extraBytes > SIZE_MAX) { + fclose(pFile); + return NULL; // File is too big. + } + + void* pFileData = malloc((size_t)fileSize + extraBytes); // <-- Safe cast due to the check above. + if (pFileData == NULL) { + fclose(pFile); + return NULL; // Failed to allocate memory for the file. Good chance the file is too big. + } + + size_t bytesRead = fread(pFileData, 1, (size_t)fileSize, pFile); + if (bytesRead != fileSize) { + free(pFileData); + fclose(pFile); + return NULL; // Failed to read every byte from the file. + } + + fclose(pFile); + + if (pFileSizeOut) *pFileSizeOut = (size_t)fileSize; + return pFileData; +} + +void* dr_open_and_read_file(const char* filePath, size_t* pFileSizeOut) +{ + return dr_open_and_read_file_with_extra_data(filePath, pFileSizeOut, 0); +} + +char* dr_open_and_read_text_file(const char* filePath, size_t* pFileSizeOut) +{ + if (pFileSizeOut) *pFileSizeOut = 0; // For safety. + + size_t fileSize; + char* pFileData = (char*)dr_open_and_read_file_with_extra_data(filePath, &fileSize, 1); // <-- 1 extra byte for the null terminator. + if (pFileData == NULL) { + return NULL; + } + + pFileData[fileSize] = '\0'; + + if (pFileSizeOut) *pFileSizeOut = fileSize; + return pFileData; +} + +dr_bool32 dr_open_and_write_file(const char* filePath, const void* pData, size_t dataSize) +{ + if (filePath == NULL) { + return DR_FALSE; + } + + // TODO: Use 64-bit versions of the FILE APIs. + + FILE* pFile = dr_fopen(filePath, "wb"); + if (pFile == NULL) { + return DR_FALSE; + } + + if (pData != NULL && dataSize > 0) { + fwrite(pData, 1, dataSize, pFile); + } + + fclose(pFile); + return DR_TRUE; +} + +dr_bool32 dr_open_and_write_text_file(const char* filePath, const char* text) +{ + if (text == NULL) { + text = ""; + } + + return dr_open_and_write_file(filePath, text, strlen(text)); +} + +void dr_free_file_data(void* valueReturnedByOpenAndReadFile) +{ + free(valueReturnedByOpenAndReadFile); +} + +dr_bool32 dr_file_exists(const char* filePath) +{ + if (filePath == NULL) { + return DR_FALSE; + } + +#if _WIN32 + DWORD attributes = GetFileAttributesA(filePath); + return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0; +#else + struct stat info; + if (stat(filePath, &info) != 0) { + return DR_FALSE; // Likely the folder doesn't exist. + } + + return (info.st_mode & S_IFDIR) == 0; +#endif +} + +dr_bool32 dr_directory_exists(const char* directoryPath) +{ + if (directoryPath == NULL) { + return DR_FALSE; + } + +#if _WIN32 + DWORD attributes = GetFileAttributesA(directoryPath); + return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; +#else + struct stat info; + if (stat(directoryPath, &info) != 0) { + return DR_FALSE; // Likely the folder doesn't exist. + } + + return (info.st_mode & S_IFDIR) != 0; +#endif +} + +dr_bool32 dr_move_file(const char* oldPath, const char* newPath) +{ + if (oldPath == NULL || newPath == NULL) { + return DR_FALSE; + } + +#if _WIN32 + return MoveFileExA(oldPath, newPath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH) != 0; +#else + return rename(oldPath, newPath) == 0; +#endif +} + +dr_bool32 dr_copy_file(const char* srcPath, const char* dstPath, dr_bool32 failIfExists) +{ + if (srcPath == NULL || dstPath == NULL) { + return DR_FALSE; + } + +#if _WIN32 + return CopyFileA(srcPath, dstPath, failIfExists) != 0; +#else + int fdSrc = open(srcPath, O_RDONLY, 0666); + if (fdSrc == -1) { + return DR_FALSE; + } + + int fdDst = open(dstPath, O_WRONLY | O_TRUNC | O_CREAT | ((failIfExists) ? O_EXCL : 0), 0666); + if (fdDst == -1) { + close(fdSrc); + return DR_FALSE; + } + + dr_bool32 result = DR_TRUE; + struct stat info; + if (fstat(fdSrc, &info) == 0) { + char buffer[BUFSIZ]; + int bytesRead; + while ((bytesRead = read(fdSrc, buffer, sizeof(buffer))) > 0) { + if (write(fdDst, buffer, bytesRead) != bytesRead) { + result = DR_FALSE; + break; + } + } + } else { + result = DR_FALSE; + } + + close(fdDst); + close(fdSrc); + + // Permissions. + chmod(dstPath, info.st_mode & 07777); + + return result; +#endif +} + +dr_bool32 dr_is_file_read_only(const char* filePath) +{ + if (filePath == NULL || filePath[0] == '\0') { + return DR_FALSE; + } + +#if _WIN32 + DWORD attributes = GetFileAttributesA(filePath); + return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_READONLY) != 0; +#else + return access(filePath, W_OK) == -1; +#endif +} + +dr_uint64 dr_get_file_modified_time(const char* filePath) +{ + if (filePath == NULL || filePath[0] == '\0') { + return 0; + } + +#if _WIN32 + HANDLE hFile = CreateFileA(filePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + return 0; + } + + FILETIME fileTime; + BOOL wasSuccessful = GetFileTime(hFile, NULL, NULL, &fileTime); + CloseHandle(hFile); + + if (!wasSuccessful) { + return 0; + } + + ULARGE_INTEGER result; + result.HighPart = fileTime.dwHighDateTime; + result.LowPart = fileTime.dwLowDateTime; + return result.QuadPart; +#else + struct stat info; + if (stat(filePath, &info) != 0) { + return 0; + } + + return info.st_mtime; +#endif +} + +dr_bool32 dr_delete_file(const char* filePath) +{ + if (filePath == NULL) { + return DR_FALSE; + } + +#if _WIN32 + return DeleteFileA(filePath) != 0; +#else + return remove(filePath) == 0; +#endif +} + +dr_bool32 dr_mkdir(const char* directoryPath) +{ + if (directoryPath == NULL) { + return DR_FALSE; + } + +#if _WIN32 + return CreateDirectoryA(directoryPath, NULL) != 0; +#else + return mkdir(directoryPath, 0777) == 0; +#endif +} + +dr_bool32 dr_mkdir_recursive(const char* directoryPath) +{ + if (directoryPath == NULL || directoryPath[0] == '\0') { + return DR_FALSE; + } + + // All we need to do is iterate over every segment in the path and try creating the directory. + char runningPath[4096]; + memset(runningPath, 0, sizeof(runningPath)); + + size_t i = 0; + for (;;) { + if (i >= sizeof(runningPath)-1) { + return DR_FALSE; // Path is too long. + } + + if (directoryPath[0] == '\0' || directoryPath[0] == '/' || directoryPath[0] == '\\') { + if (runningPath[0] != '\0' && !(runningPath[1] == ':' && runningPath[2] == '\0')) { // <-- If the running path is empty, it means we're trying to create the root directory. + if (!dr_directory_exists(runningPath)) { + if (!dr_mkdir(runningPath)) { + return DR_FALSE; + } + } + } + + //printf("%s\n", runningPath); + runningPath[i++] = '/'; + runningPath[i] = '\0'; + + if (directoryPath[0] == '\0') { + break; + } + } else { + runningPath[i++] = directoryPath[0]; + } + + directoryPath += 1; + } + + return DR_TRUE; +} + +dr_bool32 dr_iterate_files(const char* directory, dr_bool32 recursive, dr_iterate_files_proc proc, void* pUserData) +{ +#ifdef _WIN32 + char searchQuery[MAX_PATH]; + strcpy_s(searchQuery, sizeof(searchQuery), directory); + + unsigned int searchQueryLength = (unsigned int)strlen(searchQuery); + if (searchQueryLength >= MAX_PATH - 3) { + return DR_FALSE; // Path is too long. + } + + searchQuery[searchQueryLength + 0] = '\\'; + searchQuery[searchQueryLength + 1] = '*'; + searchQuery[searchQueryLength + 2] = '\0'; + + WIN32_FIND_DATAA ffd; + HANDLE hFind = FindFirstFileA(searchQuery, &ffd); + if (hFind == INVALID_HANDLE_VALUE) { + return DR_FALSE; // Failed to begin search. + } + + do + { + // Skip past "." and ".." directories. + if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) { + continue; + } + + char filePath[MAX_PATH]; + strcpy_s(filePath, sizeof(filePath), directory); + strcat_s(filePath, sizeof(filePath), "/"); + strcat_s(filePath, sizeof(filePath), ffd.cFileName); + + if (!proc(filePath, pUserData)) { + return DR_FALSE; + } + + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (recursive) { + if (!dr_iterate_files(filePath, recursive, proc, pUserData)) { + return DR_FALSE; + } + } + } + + } while (FindNextFileA(hFind, &ffd)); + + FindClose(hFind); +#else + DIR* dir = opendir(directory); + if (dir == NULL) { + return DR_FALSE; + } + + struct dirent* info = NULL; + while ((info = readdir(dir)) != NULL) + { + // Skip past "." and ".." directories. + if (strcmp(info->d_name, ".") == 0 || strcmp(info->d_name, "..") == 0) { + continue; + } + + char filePath[4096]; + strcpy_s(filePath, sizeof(filePath), directory); + strcat_s(filePath, sizeof(filePath), "/"); + strcat_s(filePath, sizeof(filePath), info->d_name); + + struct stat fileinfo; + if (stat(filePath, &fileinfo) != 0) { + continue; + } + + if (!proc(filePath, pUserData)) { + return DR_FALSE; + } + + if (fileinfo.st_mode & S_IFDIR) { + if (recursive) { + if (!dr_iterate_files(filePath, recursive, proc, pUserData)) { + return DR_FALSE; + } + } + } + } + + closedir(dir); +#endif + + return DR_TRUE; +} + + + +///////////////////////////////////////////////////////// +// DPI Awareness + +#if defined(_WIN32) || defined(_WIN64) + +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; + +typedef BOOL (__stdcall * PFN_SetProcessDPIAware) (void); +typedef HRESULT (__stdcall * PFN_SetProcessDpiAwareness) (PROCESS_DPI_AWARENESS); +typedef HRESULT (__stdcall * PFN_GetDpiForMonitor) (HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT *dpiX, UINT *dpiY); + +void dr_win32_make_dpi_aware() +{ + dr_bool32 fallBackToDiscouragedAPI = DR_FALSE; + + // We can't call SetProcessDpiAwareness() directly because otherwise on versions of Windows < 8.1 we'll get an error at load time about + // a missing DLL. + HMODULE hSHCoreDLL = LoadLibraryW(L"shcore.dll"); + if (hSHCoreDLL != NULL) + { + PFN_SetProcessDpiAwareness _SetProcessDpiAwareness = (PFN_SetProcessDpiAwareness)GetProcAddress(hSHCoreDLL, "SetProcessDpiAwareness"); + if (_SetProcessDpiAwareness != NULL) + { + if (_SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE) != S_OK) + { + fallBackToDiscouragedAPI = DR_FALSE; + } + } + else + { + fallBackToDiscouragedAPI = DR_FALSE; + } + + FreeLibrary(hSHCoreDLL); + } + else + { + fallBackToDiscouragedAPI = DR_FALSE; + } + + + if (fallBackToDiscouragedAPI) + { + HMODULE hUser32DLL = LoadLibraryW(L"user32.dll"); + if (hUser32DLL != NULL) + { + PFN_SetProcessDPIAware _SetProcessDPIAware = (PFN_SetProcessDPIAware)GetProcAddress(hUser32DLL, "SetProcessDPIAware"); + if (_SetProcessDPIAware != NULL) { + _SetProcessDPIAware(); + } + + FreeLibrary(hUser32DLL); + } + } +} + +void dr_win32_get_base_dpi(int* pDPIXOut, int* pDPIYOut) +{ + if (pDPIXOut != NULL) { + *pDPIXOut = 96; + } + + if (pDPIYOut != NULL) { + *pDPIYOut = 96; + } +} + +void dr_win32_get_system_dpi(int* pDPIXOut, int* pDPIYOut) +{ + if (pDPIXOut != NULL) { + *pDPIXOut = GetDeviceCaps(GetDC(NULL), LOGPIXELSX); + } + + if (pDPIYOut != NULL) { + *pDPIYOut = GetDeviceCaps(GetDC(NULL), LOGPIXELSY); + } +} + + +typedef struct +{ + int monitorIndex; + int i; + int dpiX; + int dpiY; + PFN_GetDpiForMonitor _GetDpiForMonitor; + +} win32_get_monitor_dpi_data; + +static BOOL CALLBACK win32_get_monitor_dpi_callback(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) +{ + (void)hdcMonitor; + (void)lprcMonitor; + + win32_get_monitor_dpi_data* pData = (win32_get_monitor_dpi_data*)dwData; + if (pData->monitorIndex == pData->i) + { + UINT dpiX; + UINT dpiY; + if (pData->_GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY) == S_OK) + { + pData->dpiX = (int)dpiX; + pData->dpiY = (int)dpiY; + } + else + { + dr_win32_get_system_dpi(&pData->dpiX, &pData->dpiY); + } + + return FALSE; // Return DR_FALSE to terminate the enumerator. + } + + pData->i += 1; + return TRUE; +} + +void dr_win32_get_monitor_dpi(int monitor, int* pDPIXOut, int* pDPIYOut) +{ + // If multi-monitor DPI awareness is not supported we will need to fall back to system DPI. + HMODULE hSHCoreDLL = LoadLibraryW(L"shcore.dll"); + if (hSHCoreDLL == NULL) { + dr_win32_get_system_dpi(pDPIXOut, pDPIYOut); + return; + } + + PFN_GetDpiForMonitor _GetDpiForMonitor = (PFN_GetDpiForMonitor)GetProcAddress(hSHCoreDLL, "GetDpiForMonitor"); + if (_GetDpiForMonitor == NULL) { + dr_win32_get_system_dpi(pDPIXOut, pDPIYOut); + FreeLibrary(hSHCoreDLL); + return; + } + + + win32_get_monitor_dpi_data data; + data.monitorIndex = monitor; + data.i = 0; + data.dpiX = 0; + data.dpiY = 0; + data._GetDpiForMonitor = _GetDpiForMonitor; + EnumDisplayMonitors(NULL, NULL, win32_get_monitor_dpi_callback, (LPARAM)&data); + + if (pDPIXOut) { + *pDPIXOut = data.dpiX; + } + + if (pDPIYOut) { + *pDPIYOut = data.dpiY; + } + + + FreeLibrary(hSHCoreDLL); +} + + +static BOOL CALLBACK win32_get_monitor_count_callback(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) +{ + (void)hMonitor; + (void)hdcMonitor; + (void)lprcMonitor; + + int *count = (int*)dwData; + (*count)++; + + return TRUE; +} + +int dr_win32_get_monitor_count() +{ + int count = 0; + if (EnumDisplayMonitors(NULL, NULL, win32_get_monitor_count_callback, (LPARAM)&count)) { + return count; + } + + return 0; +} +#endif + + +///////////////////////////////////////////////////////// +// Date / Time + +time_t dr_now() +{ + return time(NULL); +} + +void dr_datetime_short(time_t t, char* strOut, unsigned int strOutSize) +{ +#if defined(_MSC_VER) + struct tm local; + localtime_s(&local, &t); + strftime(strOut, strOutSize, "%x %H:%M:%S", &local); +#else + struct tm *local = localtime(&t); + strftime(strOut, strOutSize, "%x %H:%M:%S", local); +#endif +} + +void dr_date_YYYYMMDD(time_t t, char* strOut, unsigned int strOutSize) +{ +#if defined(_MSC_VER) + struct tm local; + localtime_s(&local, &t); + strftime(strOut, strOutSize, "%Y%m%d", &local); +#else + struct tm *local = localtime(&t); + strftime(strOut, strOutSize, "%Y%m%d", local); +#endif +} + + + +///////////////////////////////////////////////////////// +// Command Line + +typedef struct +{ + dr_cmdline* pCmdLine; + char* value; + + // Win32 style data. + char* win32_payload; + char* valueEnd; + + // argv style data. + int iarg; // <-- This starts at -1 so that the first call to next() increments it to 0. + +} dr_cmdline_iterator; + +dr_cmdline_iterator dr_cmdline_begin(dr_cmdline* pCmdLine) +{ + dr_cmdline_iterator i; + i.pCmdLine = pCmdLine; + i.value = NULL; + i.win32_payload = NULL; + i.valueEnd = NULL; + i.iarg = -1; + + if (pCmdLine != NULL && pCmdLine->win32 != NULL) { + // Win32 style + size_t length = strlen(pCmdLine->win32); + i.win32_payload = (char*)malloc(length + 2); // +2 for a double null terminator. + strcpy_s(i.win32_payload, length + 2, pCmdLine->win32); + i.win32_payload[length + 1] = '\0'; + + i.valueEnd = i.win32_payload; + } + + return i; +} + +dr_bool32 dr_cmdline_next(dr_cmdline_iterator* i) +{ + if (i != NULL && i->pCmdLine != NULL) + { + if (i->pCmdLine->win32 != NULL) + { + // Win32 style + if (i->value == NULL) { + i->value = i->win32_payload; + i->valueEnd = i->value; + } else { + i->value = i->valueEnd + 1; + } + + + // Move to the start of the next argument. + while (i->value[0] == ' ') { + i->value += 1; + } + + + // If at this point we are sitting on the null terminator it means we have finished iterating. + if (i->value[0] == '\0') + { + free(i->win32_payload); + i->win32_payload = NULL; + i->pCmdLine = NULL; + i->value = NULL; + i->valueEnd = NULL; + + return DR_FALSE; + } + + + // Move to the end of the token. If the argument begins with a double quote, we iterate until we find + // the next unescaped double-quote. + if (i->value[0] == '\"') + { + // Go to the last unescaped double-quote. + i->value += 1; + i->valueEnd = i->value + 1; + + while (i->valueEnd[0] != '\0' && i->valueEnd[0] != '\"') + { + if (i->valueEnd[0] == '\\') { + i->valueEnd += 1; + + if (i->valueEnd[0] == '\0') { + break; + } + } + + i->valueEnd += 1; + } + i->valueEnd[0] = '\0'; + } + else + { + // Go to the next space. + i->valueEnd = i->value + 1; + + while (i->valueEnd[0] != '\0' && i->valueEnd[0] != ' ') + { + i->valueEnd += 1; + } + i->valueEnd[0] = '\0'; + } + + return DR_TRUE; + } + else + { + // argv style + i->iarg += 1; + if (i->iarg < i->pCmdLine->argc) + { + i->value = i->pCmdLine->argv[i->iarg]; + return DR_TRUE; + } + else + { + i->value = NULL; + return DR_FALSE; + } + } + } + + return DR_FALSE; +} + + +dr_bool32 dr_init_cmdline(dr_cmdline* pCmdLine, int argc, char** argv) +{ + if (pCmdLine == NULL) { + return DR_FALSE; + } + + pCmdLine->argc = argc; + pCmdLine->argv = argv; + pCmdLine->win32 = NULL; + + return DR_TRUE; +} + +dr_bool32 dr_init_cmdline_win32(dr_cmdline* pCmdLine, const char* args) +{ + if (pCmdLine == NULL) { + return DR_FALSE; + } + + pCmdLine->argc = 0; + pCmdLine->argv = NULL; + pCmdLine->win32 = args; + + return DR_TRUE; +} + +void dr_parse_cmdline(dr_cmdline* pCmdLine, dr_cmdline_parse_proc callback, void* pUserData) +{ + if (pCmdLine == NULL || callback == NULL) { + return; + } + + + char pTemp[2] = {0}; + + char* pKey = NULL; + char* pVal = NULL; + + dr_cmdline_iterator arg = dr_cmdline_begin(pCmdLine); + if (dr_cmdline_next(&arg)) + { + if (!callback("[path]", arg.value, pUserData)) { + return; + } + } + + while (dr_cmdline_next(&arg)) + { + if (arg.value[0] == '-') + { + // key + + // If the key is non-null, but the value IS null, it means we hit a key with no value in which case it will not yet have been posted. + if (pKey != NULL && pVal == NULL) + { + if (!callback(pKey, pVal, pUserData)) { + return; + } + + pKey = NULL; + } + else + { + // Need to ensure the key and value are reset before doing any further processing. + pKey = NULL; + pVal = NULL; + } + + + + if (arg.value[1] == '-') + { + // --argument style + pKey = arg.value + 2; + } + else + { + // -a -b -c -d or -abcd style + if (arg.value[1] != '\0') + { + if (arg.value[2] == '\0') + { + // -a -b -c -d style + pTemp[0] = arg.value[1]; + pKey = pTemp; + pVal = NULL; + } + else + { + // -abcd style. + int i = 1; + while (arg.value[i] != '\0') + { + pTemp[0] = arg.value[i]; + + if (!callback(pTemp, NULL, pUserData)) { + return; + } + + pKey = NULL; + pVal = NULL; + + i += 1; + } + } + } + } + } + else + { + // value + + pVal = arg.value; + if (!callback(pKey, pVal, pUserData)) { + return; + } + } + } + + + // There may be a key without a value that needs posting. + if (pKey != NULL && pVal == NULL) { + callback(pKey, pVal, pUserData); + } +} + +typedef struct +{ + dr_bool32 exists; + const char* key; +} dr_cmdline_key_exists_data; + +dr_bool32 dr_cmdline_key_exists_callback(const char* key, const char* value, void* pUserData) +{ + (void)value; + + dr_cmdline_key_exists_data* pData = (dr_cmdline_key_exists_data*)pUserData; + assert(pData != NULL); + + if (key != NULL && strcmp(pData->key, key) == 0) { + pData->exists = DR_TRUE; + return DR_FALSE; + } + + return DR_TRUE; +} + +dr_bool32 dr_cmdline_key_exists(dr_cmdline* pCmdLine, const char* key) +{ + dr_cmdline_key_exists_data data; + data.exists = DR_FALSE; + data.key = key; + dr_parse_cmdline(pCmdLine, dr_cmdline_key_exists_callback, &data); + + return data.exists; +} + +int dr_cmdline_to_argv(dr_cmdline* pCmdLine, char*** argvOut) +{ + if (argvOut == NULL) return 0; + *argvOut = NULL; // Safety. + + int argc = 0; + char** argv = NULL; + size_t cmdlineLen = 0; + + // The command line is parsed in 2 passes. The first pass simple calculates the required sizes of each buffer. The second + // pass fills those buffers with actual data. + + // First pass. + dr_cmdline_iterator arg = dr_cmdline_begin(pCmdLine); + while (dr_cmdline_next(&arg)) { + cmdlineLen += strlen(arg.value) + 1; // +1 for null terminator. + argc += 1; + } + + if (argc == 0) { + return 0; + } + + + // The entire data for the command line is stored in a single buffer. + char* data = (char*)malloc((argc * sizeof(char**)) + (cmdlineLen * sizeof(char))); + if (data == NULL) { + return 0; // Ran out of memory. + } + + argv = (char**)data; + char* cmdlineStr = data + (argc * sizeof(char**)); + + + + // Second pass. + argc = 0; + cmdlineLen = 0; + + arg = dr_cmdline_begin(pCmdLine); + while (dr_cmdline_next(&arg)) { + argv[argc] = cmdlineStr + cmdlineLen; + + int i = 0; + while (arg.value[i] != '\0') { + argv[argc][i] = arg.value[i]; + i += 1; + } + argv[argc][i] = '\0'; + + + cmdlineLen += strlen(arg.value) + 1; // +1 for null terminator. + argc += 1; + } + + + *argvOut = argv; + return argc; +} + +int dr_winmain_to_argv(const char* cmdlineWinMain, char*** argvOut) +{ + dr_cmdline cmdline; + if (!dr_init_cmdline_win32(&cmdline, cmdlineWinMain)) { + return 0; + } + + return dr_cmdline_to_argv(&cmdline, argvOut); +} + +void dr_free_argv(char** argv) +{ + if (argv == NULL) { + return; + } + + free(argv); +} + + + +///////////////////////////////////////////////////////// +// Threading + +#if defined(_WIN32) +void dr_sleep(unsigned int milliseconds) +{ + Sleep((DWORD)milliseconds); +} + +void dr_yield() +{ + SwitchToThread(); +} + +unsigned int dr_get_logical_processor_count() +{ + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + + return (unsigned int)sysinfo.dwNumberOfProcessors; +} + + +typedef struct +{ + /// The Win32 thread handle. + HANDLE hThread; + + /// The entry point. + dr_thread_entry_proc entryProc; + + /// The user data to pass to the thread's entry point. + void* pData; + + /// Set to DR_TRUE by the entry function. We use this to wait for the entry function to start. + dr_bool32 isInEntryProc; + +} dr_thread_win32; + +static DWORD WINAPI dr_thread_entry_proc_win32(LPVOID pUserData) +{ + dr_thread_win32* pThreadWin32 = (dr_thread_win32*)pUserData; + assert(pThreadWin32 != NULL); + + void* pEntryProcData = pThreadWin32->pData; + dr_thread_entry_proc entryProc = pThreadWin32->entryProc; + assert(entryProc != NULL); + + pThreadWin32->isInEntryProc = DR_TRUE; + + return (DWORD)entryProc(pEntryProcData); +} + +dr_thread dr_create_thread(dr_thread_entry_proc entryProc, void* pData) +{ + if (entryProc == NULL) { + return NULL; + } + + dr_thread_win32* pThreadWin32 = (dr_thread_win32*)malloc(sizeof(*pThreadWin32)); + if (pThreadWin32 != NULL) + { + pThreadWin32->entryProc = entryProc; + pThreadWin32->pData = pData; + pThreadWin32->isInEntryProc = DR_FALSE; + + pThreadWin32->hThread = CreateThread(NULL, 0, dr_thread_entry_proc_win32, pThreadWin32, 0, NULL); + if (pThreadWin32 == NULL) { + free(pThreadWin32); + return NULL; + } + + // Wait for the new thread to enter into it's entry point before returning. We need to do this so we can safely + // support something like dr_delete_thread(dr_create_thread(my_thread_proc, pData)). + // + // On Win32 there are times when this can get stuck in an infinite loop - I expect it's something to do with some + // bad scheduling by the OS. This can be "fixed" by sleeping for a bit. + while (!pThreadWin32->isInEntryProc) { dr_sleep(0); } + } + + return (dr_thread)pThreadWin32; +} + +void dr_delete_thread(dr_thread thread) +{ + dr_thread_win32* pThreadWin32 = (dr_thread_win32*)thread; + if (pThreadWin32 != NULL) + { + CloseHandle(pThreadWin32->hThread); + } + + free(pThreadWin32); +} + +void dr_wait_thread(dr_thread thread) +{ + dr_thread_win32* pThreadWin32 = (dr_thread_win32*)thread; + if (pThreadWin32 != NULL) + { + WaitForSingleObject(pThreadWin32->hThread, INFINITE); + } +} + +void dr_wait_and_delete_thread(dr_thread thread) +{ + dr_wait_thread(thread); + dr_delete_thread(thread); +} + + +#ifdef DR_UTIL_WIN32_USE_CRITICAL_SECTION_MUTEX +dr_mutex dr_create_mutex() +{ + dr_mutex mutex = malloc(sizeof(CRITICAL_SECTION)); + if (mutex != NULL) + { + InitializeCriticalSection(mutex); + } + + return mutex; +} + +void dr_delete_mutex(dr_mutex mutex) +{ + DeleteCriticalSection(mutex); + free(mutex); +} + +void dr_lock_mutex(dr_mutex mutex) +{ + EnterCriticalSection(mutex); +} + +void dr_unlock_mutex(dr_mutex mutex) +{ + LeaveCriticalSection(mutex); +} +#else +dr_mutex dr_create_mutex() +{ + return CreateEventA(NULL, FALSE, TRUE, NULL); +} + +void dr_delete_mutex(dr_mutex mutex) +{ + CloseHandle((HANDLE)mutex); +} + +void dr_lock_mutex(dr_mutex mutex) +{ + WaitForSingleObject((HANDLE)mutex, INFINITE); +} + +void dr_unlock_mutex(dr_mutex mutex) +{ + SetEvent((HANDLE)mutex); +} +#endif + + +dr_semaphore dr_create_semaphore(int initialValue) +{ + return (void*)CreateSemaphoreA(NULL, initialValue, LONG_MAX, NULL); +} + +void dr_delete_semaphore(dr_semaphore semaphore) +{ + CloseHandle(semaphore); +} + +dr_bool32 dr_wait_semaphore(dr_semaphore semaphore) +{ + return WaitForSingleObject((HANDLE)semaphore, INFINITE) == WAIT_OBJECT_0; +} + +dr_bool32 dr_release_semaphore(dr_semaphore semaphore) +{ + return ReleaseSemaphore((HANDLE)semaphore, 1, NULL) != 0; +} +#else +void dr_sleep(unsigned int milliseconds) +{ + usleep(milliseconds * 1000); // <-- usleep is in microseconds. +} + +void dr_yield() +{ + sched_yield(); +} + +unsigned int dr_get_logical_processor_count() +{ + return (unsigned int)sysconf(_SC_NPROCESSORS_ONLN); +} + + +typedef struct +{ + /// The Win32 thread handle. + pthread_t pthread; + + /// The entry point. + dr_thread_entry_proc entryProc; + + /// The user data to pass to the thread's entry point. + void* pData; + + /// Set to DR_TRUE by the entry function. We use this to wait for the entry function to start. + dr_bool32 isInEntryProc; + +} dr_thread_posix; + +static void* dr_thread_entry_proc_posix(void* pDataIn) +{ + dr_thread_posix* pThreadPosix = (dr_thread_posix*)pDataIn; + assert(pThreadPosix != NULL); + + void* pEntryProcData = pThreadPosix->pData; + dr_thread_entry_proc entryProc = pThreadPosix->entryProc; + assert(entryProc != NULL); + + pThreadPosix->isInEntryProc = DR_TRUE; + + return (void*)(size_t)entryProc(pEntryProcData); +} + +dr_thread dr_create_thread(dr_thread_entry_proc entryProc, void* pData) +{ + if (entryProc == NULL) { + return NULL; + } + + dr_thread_posix* pThreadPosix = (dr_thread_posix*)malloc(sizeof(*pThreadPosix)); + if (pThreadPosix != NULL) + { + pThreadPosix->entryProc = entryProc; + pThreadPosix->pData = pData; + pThreadPosix->isInEntryProc = DR_FALSE; + + if (pthread_create(&pThreadPosix->pthread, NULL, dr_thread_entry_proc_posix, pThreadPosix) != 0) { + free(pThreadPosix); + return NULL; + } + + // Wait for the new thread to enter into it's entry point before returning. We need to do this so we can safely + // support something like dr_delete_thread(dr_create_thread(my_thread_proc, pData)). + while (!pThreadPosix->isInEntryProc) {} + } + + return (dr_thread)pThreadPosix; +} + +void dr_delete_thread(dr_thread thread) +{ + free(thread); +} + +void dr_wait_thread(dr_thread thread) +{ + dr_thread_posix* pThreadPosix = (dr_thread_posix*)thread; + if (pThreadPosix != NULL) + { + pthread_join(pThreadPosix->pthread, NULL); + } +} + + + +dr_mutex dr_create_mutex() +{ + pthread_mutex_t* mutex = (pthread_mutex_t*)malloc(sizeof(*mutex)); + if (pthread_mutex_init(mutex, NULL) != 0) { + free(mutex); + mutex = NULL; + } + + return mutex; +} + +void dr_delete_mutex(dr_mutex mutex) +{ + pthread_mutex_destroy((pthread_mutex_t*)mutex); +} + +void dr_lock_mutex(dr_mutex mutex) +{ + pthread_mutex_lock((pthread_mutex_t*)mutex); +} + +void dr_unlock_mutex(dr_mutex mutex) +{ + pthread_mutex_unlock((pthread_mutex_t*)mutex); +} + + + +dr_semaphore dr_create_semaphore(int initialValue) +{ + sem_t* semaphore = (sem_t*)malloc(sizeof(*semaphore)); + if (sem_init(semaphore, 0, (unsigned int)initialValue) == -1) { + free(semaphore); + semaphore = NULL; + } + + return semaphore; +} + +void dr_delete_semaphore(dr_semaphore semaphore) +{ + sem_close((sem_t*)semaphore); +} + +dr_bool32 dr_wait_semaphore(dr_semaphore semaphore) +{ + return sem_wait((sem_t*)semaphore) != -1; +} + +dr_bool32 dr_release_semaphore(dr_semaphore semaphore) +{ + return sem_post((sem_t*)semaphore) != -1; +} +#endif + + + +///////////////////////////////////////////////////////// +// Timing + +// macOS does not have clock_gettime on OS X < 10.12 +#ifdef __MACH__ +#include +#ifndef MAC_OS_X_VERSION_10_12 +#define MAC_OS_X_VERSION_10_12 101200 +#endif +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12 +#include +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 0 +int clock_gettime(int clk_id, struct timespec* t) +{ + mach_timebase_info_data_t timebase; + mach_timebase_info(&timebase); + dr_uint64 time; + time = mach_absolute_time(); + double nseconds = ((double)time * (double)timebase.numer) / ((double)timebase.denom); + double seconds = ((double)time * (double)timebase.numer) / ((double)timebase.denom * 1e9); + t->tv_sec = seconds; + t->tv_nsec = nseconds; + return 0; +} +#endif +#endif + +#ifdef _WIN32 +static LARGE_INTEGER g_DRTimerFrequency = {{0}}; +void dr_timer_init(dr_timer* pTimer) +{ + if (g_DRTimerFrequency.QuadPart == 0) { + QueryPerformanceFrequency(&g_DRTimerFrequency); + } + + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + pTimer->counter = (dr_uint64)counter.QuadPart; +} + +double dr_timer_tick(dr_timer* pTimer) +{ + LARGE_INTEGER counter; + if (!QueryPerformanceCounter(&counter)) { + return 0; + } + + dr_uint64 newTimeCounter = counter.QuadPart; + dr_uint64 oldTimeCounter = pTimer->counter; + + pTimer->counter = newTimeCounter; + + return (newTimeCounter - oldTimeCounter) / (double)g_DRTimerFrequency.QuadPart; +} +#else +void dr_timer_init(dr_timer* pTimer) +{ + struct timespec newTime; + clock_gettime(CLOCK_MONOTONIC, &newTime); + + pTimer->counter = (newTime.tv_sec * 1000000000LL) + newTime.tv_nsec; +} + +double dr_timer_tick(dr_timer* pTimer) +{ + struct timespec newTime; + clock_gettime(CLOCK_MONOTONIC, &newTime); + + dr_uint64 newTimeCounter = (newTime.tv_sec * 1000000000LL) + newTime.tv_nsec; + dr_uint64 oldTimeCounter = pTimer->counter; + + pTimer->counter = newTimeCounter; + + return (newTimeCounter - oldTimeCounter) / 1000000000.0; +} +#endif + + +///////////////////////////////////////////////////////// +// Random + +double dr_randd() +{ + return (double)rand() / (double)RAND_MAX; +} + +float dr_randf() +{ + return (float)dr_randd(); +} + + +///////////////////////////////////////////////////////// +// User Accounts and Process Management + +size_t dr_get_username(char* usernameOut, size_t usernameOutSize) +{ + if (usernameOut != NULL && usernameOutSize > 0) { + usernameOut[0] = '\0'; + } + +#ifdef _WIN32 + DWORD dwSize = (DWORD)usernameOutSize; + if (!GetUserNameA(usernameOut, &dwSize)) { + return 0; + } + + return dwSize; +#else + struct passwd *pw = getpwuid(geteuid()); + if (pw == NULL) { + return 0; + } + + if (usernameOut != NULL) { + strcpy_s(usernameOut, usernameOutSize, pw->pw_name); + } + + return strlen(pw->pw_name); +#endif +} + +unsigned int dr_get_process_id() +{ +#ifdef _WIN32 + return GetProcessId(GetCurrentProcess()); +#else + return (unsigned int)getpid(); +#endif +} + + + +///////////////////////////////////////////////////////// +// Miscellaneous Stuff. + +dr_bool32 dr_hex_char_to_uint(char ascii, unsigned int* out) +{ + if (ascii >= '0' && ascii <= '9') { + if (out) *out = ascii - '0'; + return DR_TRUE; + } + + if (ascii >= 'A' && ascii <= 'F') { + if (out) *out = 10 + (ascii - 'A'); + return DR_TRUE; + } + + if (ascii >= 'a' && ascii <= 'f') { + if (out) *out = 10 + (ascii - 'a'); + return DR_TRUE; + } + + if (out) *out = 0; + return DR_FALSE; +} + +#endif //DR_IMPLEMENTATION + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// dr_path +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef dr_path_h +#define dr_path_h + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Structure representing a section of a path. +typedef struct +{ + size_t offset; + size_t length; + +} drpath_segment; + +// Structure used for iterating over a path while at the same time providing useful and easy-to-use information about the iteration. +typedef struct drpath_iterator +{ + const char* path; + drpath_segment segment; + +} drpath_iterator; + + + +/// Compares a section of two strings for equality. +/// +/// @param s0Path [in] The first path. +/// @param s0 [in] The segment of the first path to compare. +/// @param s1Path [in] The second path. +/// @param s1 [in] The segment of the second path to compare. +/// +/// @return DR_TRUE if the strings are equal; DR_FALSE otherwise. +dr_bool32 drpath_segments_equal(const char* s0Path, const drpath_segment s0, const char* s1Path, const drpath_segment s1); + + +/// Creates an iterator for iterating over each segment in a path. +/// +/// @param path [in] The path whose segments are being iterated. +/// +/// @return True if at least one segment is found; DR_FALSE if it's an empty path. +dr_bool32 drpath_first(const char* path, drpath_iterator* i); + +/// Creates an iterator beginning at the last segment. +dr_bool32 drpath_last(const char* path, drpath_iterator* i); + +/// Goes to the next segment in the path as per the given iterator. +/// +/// @param i [in] A pointer to the iterator to increment. +/// +/// @return True if the iterator contains a valid value. Use this to determine when to terminate iteration. +dr_bool32 drpath_next(drpath_iterator* i); + +/// Goes to the previous segment in the path. +/// +/// @param i [in] A pointer to the iterator to decrement. +/// +/// @return DR_TRUE if the iterator contains a valid value. Use this to determine when to terminate iteration. +dr_bool32 drpath_prev(drpath_iterator* i); + +/// Determines if the given iterator is at the end. +/// +/// @param i [in] The iterator to check. +dr_bool32 drpath_at_end(drpath_iterator i); + +/// Determines if the given iterator is at the start. +/// +/// @param i [in] The iterator to check. +dr_bool32 drpath_at_start(drpath_iterator i); + +/// Compares the string values of two iterators for equality. +/// +/// @param i0 [in] The first iterator to compare. +/// @param i1 [in] The second iterator to compare. +/// +/// @return DR_TRUE if the strings are equal; DR_FALSE otherwise. +dr_bool32 drpath_iterators_equal(const drpath_iterator i0, const drpath_iterator i1); + + +/// Determines whether or not the given iterator refers to the root segment of a path. +dr_bool32 drpath_is_root_segment(const drpath_iterator i); + +/// Determines whether or not the given iterator refers to a Linux style root directory ("/") +dr_bool32 drpath_is_linux_style_root_segment(const drpath_iterator i); + +/// Determines whether or not the given iterator refers to a Windows style root directory. +dr_bool32 drpath_is_win32_style_root_segment(const drpath_iterator i); + + +/// Converts the slashes in the given path to forward slashes. +/// +/// @param path [in] The path whose slashes are being converted. +void drpath_to_forward_slashes(char* path); + +/// Converts the slashes in the given path to back slashes. +/// +/// @param path [in] The path whose slashes are being converted. +void drpath_to_backslashes(char* path); + + +/// Determines whether or not the given path is a decendant of another. +/// +/// @param descendantAbsolutePath [in] The absolute path of the descendant. +/// @param parentAbsolutePath [in] The absolute path of the parent. +/// +/// @remarks +/// As an example, "C:/My/Folder" is a descendant of "C:/". +/// @par +/// If either path contains "." or "..", clean it with drpath_clean() before calling this. +dr_bool32 drpath_is_descendant(const char* descendantAbsolutePath, const char* parentAbsolutePath); + +/// Determines whether or not the given path is a direct child of another. +/// +/// @param childAbsolutePath [in] The absolute of the child. +/// @param parentAbsolutePath [in] The absolute path of the parent. +/// +/// @remarks +/// As an example, "C:/My/Folder" is NOT a child of "C:/" - it is a descendant. Alternatively, "C:/My" IS a child of "C:/". +/// @par +/// If either path contains "." or "..", clean it with drpath_clean() before calling this. +dr_bool32 drpath_is_child(const char* childAbsolutePath, const char* parentAbsolutePath); + + +/// Modifies the given path by transforming it into it's base path. +/// +/// Returns , for convenience. +char* drpath_base_path(char* path); + +/// Retrieves the base path from the given path, not including the trailing slash. +/// +/// @param path [in] The full path. +/// @param baseOut [out] A pointer to the buffer that will receive the base path. +/// @param baseSizeInBytes [in] The size in bytes of the buffer that will receive the base directory. +/// +/// @remarks +/// As an example, when "path" is "C:/MyFolder/MyFile", the output will be "C:/MyFolder". Note that there is no trailing slash. +/// @par +/// If "path" is something like "/MyFolder", the return value will be an empty string. +void drpath_copy_base_path(const char* path, char* baseOut, size_t baseSizeInBytes); + +/// Finds the file name portion of the path. +/// +/// @param path [in] The path to search. +/// +/// @return A pointer to the beginning of the string containing the file name. If this is non-null, it will be an offset of "path". +/// +/// @remarks +/// A path with a trailing slash will return an empty string. +/// @par +/// The return value is just an offset of "path". +const char* drpath_file_name(const char* path); + +/// Copies the file name into the given buffer. +const char* drpath_copy_file_name(const char* path, char* fileNameOut, size_t fileNameSizeInBytes); + +/// Finds the file extension of the given file path. +/// +/// @param path [in] The path to search. +/// +/// @return A pointer to the beginning of the string containing the file's extension. +/// +/// @remarks +/// A path with a trailing slash will return an empty string. +/// @par +/// The return value is just an offset of "path". +/// @par +/// On a path such as "filename.ext1.ext2" the returned string will be "ext2". +const char* drpath_extension(const char* path); + + +/// Checks whether or not the two paths are equal. +/// +/// @param path1 [in] The first path. +/// @param path2 [in] The second path. +/// +/// @return DR_TRUE if the paths are equal, DR_FALSE otherwise. +/// +/// @remarks +/// This is case-sensitive. +/// @par +/// This is more than just a string comparison. Rather, this splits the path and compares each segment. The path "C:/My/Folder" is considered +/// equal to to "C:\\My\\Folder". +dr_bool32 drpath_equal(const char* path1, const char* path2); + +/// Checks if the extension of the given path is equal to the given extension. +/// +/// @remarks +/// By default this is NOT case-sensitive, however if the standard library is disable, it is case-sensitive. +dr_bool32 drpath_extension_equal(const char* path, const char* extension); + + +/// Determines whether or not the given path is relative. +/// +/// @param path [in] The path to check. +dr_bool32 drpath_is_relative(const char* path); + +/// Determines whether or not the given path is absolute. +/// +/// @param path [in] The path to check. +dr_bool32 drpath_is_absolute(const char* path); + + +/// Appends two paths together, ensuring there is not double up on the last slash. +/// +/// @param base [in, out] The base path that is being appended to. +/// @param baseBufferSizeInBytes [in] The size of the buffer pointed to by "base", in bytes. +/// @param other [in] The other path segment. +/// +/// @remarks +/// This assumes both paths are well formed and "other" is a relative path. +dr_bool32 drpath_append(char* base, size_t baseBufferSizeInBytes, const char* other); + +/// Appends an iterator object to the given base path. +dr_bool32 drpath_append_iterator(char* base, size_t baseBufferSizeInBytes, drpath_iterator i); + +/// Appends an extension to the given path. +dr_bool32 drpath_append_extension(char* base, size_t baseBufferSizeInBytes, const char* extension); + +/// Appends two paths together, and copyies them to a separate buffer. +/// +/// @param dst [out] The destination buffer. +/// @param dstSizeInBytes [in] The size of the buffer pointed to by "dst", in bytes. +/// @param base [in] The base directory. +/// @param other [in] The relative path to append to "base". +/// +/// @return DR_TRUE if the paths were appended successfully; DR_FALSE otherwise. +/// +/// @remarks +/// This assumes both paths are well formed and "other" is a relative path. +dr_bool32 drpath_copy_and_append(char* dst, size_t dstSizeInBytes, const char* base, const char* other); + +/// Appends a base path and an iterator together, and copyies them to a separate buffer. +/// +/// @param dst [out] The destination buffer. +/// @param dstSizeInBytes [in] The size of the buffer pointed to by "dst", in bytes. +/// @param base [in] The base directory. +/// @param i [in] The iterator to append. +/// +/// @return DR_TRUE if the paths were appended successfully; DR_FALSE otherwise. +/// +/// @remarks +/// This assumes both paths are well formed and "i" is a valid iterator. +dr_bool32 drpath_copy_and_append_iterator(char* dst, size_t dstSizeInBytes, const char* base, drpath_iterator i); + +/// Appends an extension to the given base path and copies them to a separate buffer. +/// @param dst [out] The destination buffer. +/// @param dstSizeInBytes [in] The size of the buffer pointed to by "dst", in bytes. +/// @param base [in] The base directory. +/// @param extension [in] The relative path to append to "base". +/// +/// @return DR_TRUE if the paths were appended successfully; DR_FALSE otherwise. +dr_bool32 drpath_copy_and_append_extension(char* dst, size_t dstSizeInBytes, const char* base, const char* extension); + + +/// Cleans the path and resolves the ".." and "." segments. +/// +/// @param path [in] The path to clean. +/// @param pathOut [out] A pointer to the buffer that will receive the path. +/// @param pathOutSizeInBytes [in] The size of the buffer pointed to by pathOut, in bytes. +/// +/// @return The number of bytes written to the output buffer, including the null terminator. +/// +/// @remarks +/// The output path will never be longer than the input path. +/// @par +/// The output buffer should never overlap with the input path. +/// @par +/// As an example, the path "my/messy/../path" will result in "my/path" +/// @par +/// The path "my/messy/../../../path" (note how there are too many ".." segments) will return "path" (the extra ".." segments will be dropped.) +/// @par +/// If an error occurs, such as an invalid input path, 0 will be returned. +size_t drpath_clean(const char* path, char* pathOut, size_t pathOutSizeInBytes); + +/// Appends one path to the other and then cleans it. +size_t drpath_append_and_clean(char* dst, size_t dstSizeInBytes, const char* base, const char* other); + + +/// Removes the extension from the given path. +/// +/// @remarks +/// If the given path does not have an extension, 1 will be returned, but the string will be left unmodified. +dr_bool32 drpath_remove_extension(char* path); + +/// Creates a copy of the given string and removes the extension. +dr_bool32 drpath_copy_and_remove_extension(char* dst, size_t dstSizeInBytes, const char* path); + + +/// Removes the last segment from the given path. +dr_bool32 drpath_remove_file_name(char* path); + +/// Creates a copy of the given string and removes the extension. +dr_bool32 drpath_copy_and_remove_file_name(char* dst, size_t dstSizeInBytes, const char* path); + + +/// Converts an absolute path to a relative path. +/// +/// @return DR_TRUE if the conversion was successful; DR_FALSE if there was an error. +/// +/// @remarks +/// This will normalize every slash to forward slashes. +dr_bool32 drpath_to_relative(const char* absolutePathToMakeRelative, const char* absolutePathToMakeRelativeTo, char* relativePathOut, size_t relativePathOutSizeInBytes); + +/// Converts a relative path to an absolute path based on a base path. +/// +/// @return DR_TRUE if the conversion was successful; DR_FALSE if there was an error. +/// +/// @remarks +/// This is equivalent to an append followed by a clean. Slashes will be normalized to forward slashes. +dr_bool32 drpath_to_absolute(const char* relativePathToMakeAbsolute, const char* basePath, char* absolutePathOut, size_t absolutePathOutSizeInBytes); + + +#ifdef __cplusplus +} +#endif +#endif //dr_path_h + + + +/////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef DR_IMPLEMENTATION +#include +#include +#include + +dr_bool32 drpath_first(const char* path, drpath_iterator* i) +{ + if (i == 0) return DR_FALSE; + i->path = path; + i->segment.offset = 0; + i->segment.length = 0; + + if (path == 0 || path[0] == '\0') { + return DR_FALSE; + } + + while (i->path[i->segment.length] != '\0' && (i->path[i->segment.length] != '/' && i->path[i->segment.length] != '\\')) { + i->segment.length += 1; + } + + return DR_TRUE; +} + +dr_bool32 drpath_last(const char* path, drpath_iterator* i) +{ + if (i == 0) return DR_FALSE; + i->path = path; + i->segment.offset = 0; + i->segment.length = 0; + + if (path == 0 || path[0] == '\0') { + return DR_FALSE; + } + + i->path = path; + i->segment.offset = strlen(path); + i->segment.length = 0; + + return drpath_prev(i); +} + +dr_bool32 drpath_next(drpath_iterator* i) +{ + if (i == NULL || i->path == NULL) { + return DR_FALSE; + } + + i->segment.offset = i->segment.offset + i->segment.length; + i->segment.length = 0; + + while (i->path[i->segment.offset] != '\0' && (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\')) { + i->segment.offset += 1; + } + + if (i->path[i->segment.offset] == '\0') { + return DR_FALSE; + } + + + while (i->path[i->segment.offset + i->segment.length] != '\0' && (i->path[i->segment.offset + i->segment.length] != '/' && i->path[i->segment.offset + i->segment.length] != '\\')) { + i->segment.length += 1; + } + + return DR_TRUE; +} + +dr_bool32 drpath_prev(drpath_iterator* i) +{ + if (i == NULL || i->path == NULL || i->segment.offset == 0) { + return DR_FALSE; + } + + i->segment.length = 0; + + do + { + i->segment.offset -= 1; + } while (i->segment.offset > 0 && (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\')); + + if (i->segment.offset == 0) { + if (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\') { + i->segment.length = 0; + return DR_TRUE; + } + + return DR_FALSE; + } + + + size_t offsetEnd = i->segment.offset + 1; + while (i->segment.offset > 0 && (i->path[i->segment.offset] != '/' && i->path[i->segment.offset] != '\\')) { + i->segment.offset -= 1; + } + + if (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\') { + i->segment.offset += 1; + } + + + i->segment.length = offsetEnd - i->segment.offset; + + return DR_TRUE; +} + +dr_bool32 drpath_at_end(drpath_iterator i) +{ + return i.path == 0 || i.path[i.segment.offset] == '\0'; +} + +dr_bool32 drpath_at_start(drpath_iterator i) +{ + return i.path != 0 && i.segment.offset == 0; +} + +dr_bool32 drpath_iterators_equal(const drpath_iterator i0, const drpath_iterator i1) +{ + return drpath_segments_equal(i0.path, i0.segment, i1.path, i1.segment); +} + +dr_bool32 drpath_segments_equal(const char* s0Path, const drpath_segment s0, const char* s1Path, const drpath_segment s1) +{ + if (s0Path == NULL || s1Path == NULL) { + return DR_FALSE; + } + + if (s0.length != s1.length) { + return DR_FALSE; + } + + return strncmp(s0Path + s0.offset, s1Path + s1.offset, s0.length) == 0; +} + + +dr_bool32 drpath_is_root_segment(const drpath_iterator i) +{ + return drpath_is_linux_style_root_segment(i) || drpath_is_win32_style_root_segment(i); +} + +dr_bool32 drpath_is_linux_style_root_segment(const drpath_iterator i) +{ + if (i.path == NULL) { + return DR_FALSE; + } + + if (i.segment.offset == 0 && i.segment.length == 0) { + return DR_TRUE; // "/" style root. + } + + return DR_FALSE; +} + +dr_bool32 drpath_is_win32_style_root_segment(const drpath_iterator i) +{ + if (i.path == NULL) { + return DR_FALSE; + } + + if (i.segment.offset == 0 && i.segment.length == 2) { + if (((i.path[0] >= 'a' && i.path[0] <= 'z') || (i.path[0] >= 'A' && i.path[0] <= 'Z')) && i.path[1] == ':') { + return DR_TRUE; + } + } + + return DR_FALSE; +} + + +void drpath_to_forward_slashes(char* path) +{ + if (path == NULL) { + return; + } + + while (path[0] != '\0') { + if (path[0] == '\\') { + path[0] = '/'; + } + + path += 1; + } +} + +void drpath_to_backslashes(char* path) +{ + if (path == NULL) { + return; + } + + while (path[0] != '\0') { + if (path[0] == '/') { + path[0] = '\\'; + } + + path += 1; + } +} + + +dr_bool32 drpath_is_descendant(const char* descendantAbsolutePath, const char* parentAbsolutePath) +{ + drpath_iterator iChild; + if (!drpath_first(descendantAbsolutePath, &iChild)) { + return DR_FALSE; // The descendant is an empty string which makes it impossible for it to be a descendant. + } + + drpath_iterator iParent; + if (drpath_first(parentAbsolutePath, &iParent)) + { + do + { + // If the segment is different, the paths are different and thus it is not a descendant. + if (!drpath_iterators_equal(iParent, iChild)) { + return DR_FALSE; + } + + if (!drpath_next(&iChild)) { + return DR_FALSE; // The descendant is shorter which means it's impossible for it to be a descendant. + } + + } while (drpath_next(&iParent)); + } + + return DR_TRUE; +} + +dr_bool32 drpath_is_child(const char* childAbsolutePath, const char* parentAbsolutePath) +{ + drpath_iterator iChild; + if (!drpath_first(childAbsolutePath, &iChild)) { + return DR_FALSE; // The descendant is an empty string which makes it impossible for it to be a descendant. + } + + drpath_iterator iParent; + if (drpath_first(parentAbsolutePath, &iParent)) + { + do + { + // If the segment is different, the paths are different and thus it is not a descendant. + if (!drpath_iterators_equal(iParent, iChild)) { + return DR_FALSE; + } + + if (!drpath_next(&iChild)) { + return DR_FALSE; // The descendant is shorter which means it's impossible for it to be a descendant. + } + + } while (drpath_next(&iParent)); + } + + // At this point we have finished iteration of the parent, which should be shorter one. We now do one more iterations of + // the child to ensure it is indeed a direct child and not a deeper descendant. + return !drpath_next(&iChild); +} + +char* drpath_base_path(char* path) +{ + if (path == NULL) { + return NULL; + } + + char* pathorig = path; + char* baseend = path; + + // We just loop through the path until we find the last slash. + while (path[0] != '\0') { + if (path[0] == '/' || path[0] == '\\') { + baseend = path; + } + + path += 1; + } + + + // Now we just loop backwards until we hit the first non-slash. + while (baseend > path) { + if (baseend[0] != '/' && baseend[0] != '\\') { + break; + } + + baseend -= 1; + } + + + // We just put a null terminator on the end. + baseend[0] = '\0'; + + return pathorig; +} + +void drpath_copy_base_path(const char* path, char* baseOut, size_t baseSizeInBytes) +{ + if (path == NULL || baseOut == NULL || baseSizeInBytes == 0) { + return; + } + + strcpy_s(baseOut, baseSizeInBytes, path); + drpath_base_path(baseOut); +} + +const char* drpath_file_name(const char* path) +{ + if (path == NULL) { + return NULL; + } + + const char* fileName = path; + + // We just loop through the path until we find the last slash. + while (path[0] != '\0') { + if (path[0] == '/' || path[0] == '\\') { + fileName = path; + } + + path += 1; + } + + // At this point the file name is sitting on a slash, so just move forward. + while (fileName[0] != '\0' && (fileName[0] == '/' || fileName[0] == '\\')) { + fileName += 1; + } + + return fileName; +} + +const char* drpath_copy_file_name(const char* path, char* fileNameOut, size_t fileNameSizeInBytes) +{ + const char* fileName = drpath_file_name(path); + if (fileName != NULL) { + strcpy_s(fileNameOut, fileNameSizeInBytes, fileName); + } + + return fileName; +} + +const char* drpath_extension(const char* path) +{ + if (path == NULL) { + return NULL; + } + + const char* extension = drpath_file_name(path); + const char* lastoccurance = 0; + + // Just find the last '.' and return. + while (extension[0] != '\0') + { + if (extension[0] == '.') { + extension += 1; + lastoccurance = extension; + } + + extension += 1; + } + + return (lastoccurance != 0) ? lastoccurance : extension; +} + + +dr_bool32 drpath_equal(const char* path1, const char* path2) +{ + if (path1 == NULL || path2 == NULL) { + return DR_FALSE; + } + + if (path1 == path2 || (path1[0] == '\0' && path2[0] == '\0')) { + return DR_TRUE; // Two empty paths are treated as the same. + } + + drpath_iterator iPath1; + drpath_iterator iPath2; + if (drpath_first(path1, &iPath1) && drpath_first(path2, &iPath2)) + { + dr_bool32 isPath1Valid; + dr_bool32 isPath2Valid; + + do + { + if (!drpath_iterators_equal(iPath1, iPath2)) { + return DR_FALSE; + } + + isPath1Valid = drpath_next(&iPath1); + isPath2Valid = drpath_next(&iPath2); + + } while (isPath1Valid && isPath2Valid); + + // At this point either iPath1 and/or iPath2 have finished iterating. If both of them are at the end, the two paths are equal. + return isPath1Valid == isPath2Valid && iPath1.path[iPath1.segment.offset] == '\0' && iPath2.path[iPath2.segment.offset] == '\0'; + } + + return DR_FALSE; +} + +dr_bool32 drpath_extension_equal(const char* path, const char* extension) +{ + if (path == NULL || extension == NULL) { + return DR_FALSE; + } + + const char* ext1 = extension; + const char* ext2 = drpath_extension(path); + +#ifdef _MSC_VER + return _stricmp(ext1, ext2) == 0; +#else + return strcasecmp(ext1, ext2) == 0; +#endif +} + + + +dr_bool32 drpath_is_relative(const char* path) +{ + if (path == NULL) { + return DR_FALSE; + } + + drpath_iterator seg; + if (drpath_first(path, &seg)) { + return !drpath_is_root_segment(seg); + } + + // We'll get here if the path is empty. We consider this to be a relative path. + return DR_TRUE; +} + +dr_bool32 drpath_is_absolute(const char* path) +{ + return !drpath_is_relative(path); +} + + +dr_bool32 drpath_append(char* base, size_t baseBufferSizeInBytes, const char* other) +{ + if (base == NULL || other == NULL) { + return DR_FALSE; + } + + size_t path1Length = strlen(base); + size_t path2Length = strlen(other); + + if (path1Length >= baseBufferSizeInBytes) { + return DR_FALSE; + } + + + // Slash. + if (path1Length > 0 && base[path1Length - 1] != '/' && base[path1Length - 1] != '\\') { + base[path1Length] = '/'; + path1Length += 1; + } + + // Other part. + if (path1Length + path2Length >= baseBufferSizeInBytes) { + path2Length = baseBufferSizeInBytes - path1Length - 1; // -1 for the null terminator. + } + + strncpy_s(base + path1Length, baseBufferSizeInBytes - path1Length, other, path2Length); + + return DR_TRUE; +} + +dr_bool32 drpath_append_iterator(char* base, size_t baseBufferSizeInBytes, drpath_iterator i) +{ + if (base == NULL) { + return DR_FALSE; + } + + size_t path1Length = strlen(base); + size_t path2Length = i.segment.length; + + if (path1Length >= baseBufferSizeInBytes) { + return DR_FALSE; + } + + if (drpath_is_linux_style_root_segment(i)) { + if (baseBufferSizeInBytes > 1) { + base[0] = '/'; + base[1] = '\0'; + return DR_TRUE; + } + } + + // Slash. + if (path1Length > 0 && base[path1Length - 1] != '/' && base[path1Length - 1] != '\\') { + base[path1Length] = '/'; + path1Length += 1; + } + + // Other part. + if (path1Length + path2Length >= baseBufferSizeInBytes) { + path2Length = baseBufferSizeInBytes - path1Length - 1; // -1 for the null terminator. + } + + strncpy_s(base + path1Length, baseBufferSizeInBytes - path1Length, i.path + i.segment.offset, path2Length); + + return DR_TRUE; +} + +dr_bool32 drpath_append_extension(char* base, size_t baseBufferSizeInBytes, const char* extension) +{ + if (base == NULL || extension == NULL) { + return DR_FALSE; + } + + size_t baseLength = strlen(base); + size_t extLength = strlen(extension); + + if (baseLength >= baseBufferSizeInBytes) { + return DR_FALSE; + } + + base[baseLength] = '.'; + baseLength += 1; + + if (baseLength + extLength >= baseBufferSizeInBytes) { + extLength = baseBufferSizeInBytes - baseLength - 1; // -1 for the null terminator. + } + + strncpy_s(base + baseLength, baseBufferSizeInBytes - baseLength, extension, extLength); + + return DR_TRUE; +} + +dr_bool32 drpath_copy_and_append(char* dst, size_t dstSizeInBytes, const char* base, const char* other) +{ + if (dst == NULL || dstSizeInBytes == 0) { + return DR_FALSE; + } + + strcpy_s(dst, dstSizeInBytes, base); + return drpath_append(dst, dstSizeInBytes, other); +} + +dr_bool32 drpath_copy_and_append_iterator(char* dst, size_t dstSizeInBytes, const char* base, drpath_iterator i) +{ + if (dst == NULL || dstSizeInBytes == 0) { + return DR_FALSE; + } + + strcpy_s(dst, dstSizeInBytes, base); + return drpath_append_iterator(dst, dstSizeInBytes, i); +} + +dr_bool32 drpath_copy_and_append_extension(char* dst, size_t dstSizeInBytes, const char* base, const char* extension) +{ + if (dst == NULL || dstSizeInBytes == 0) { + return DR_FALSE; + } + + strcpy_s(dst, dstSizeInBytes, base); + return drpath_append_extension(dst, dstSizeInBytes, extension); +} + + + +// This function recursively cleans a path which is defined as a chain of iterators. This function works backwards, which means at the time of calling this +// function, each iterator in the chain should be placed at the end of the path. +// +// This does not write the null terminator, nor a leading slash for absolute paths. +size_t _drpath_clean_trywrite(drpath_iterator* iterators, unsigned int iteratorCount, char* pathOut, size_t pathOutSizeInBytes, unsigned int ignoreCounter) +{ + if (iteratorCount == 0) { + return 0; + } + + drpath_iterator isegment = iterators[iteratorCount - 1]; + + + // If this segment is a ".", we ignore it. If it is a ".." we ignore it and increment "ignoreCount". + int ignoreThisSegment = ignoreCounter > 0 && isegment.segment.length > 0; + + if (isegment.segment.length == 1 && isegment.path[isegment.segment.offset] == '.') + { + // "." + ignoreThisSegment = 1; + } + else + { + if (isegment.segment.length == 2 && isegment.path[isegment.segment.offset] == '.' && isegment.path[isegment.segment.offset + 1] == '.') + { + // ".." + ignoreThisSegment = 1; + ignoreCounter += 1; + } + else + { + // It's a regular segment, so decrement the ignore counter. + if (ignoreCounter > 0) { + ignoreCounter -= 1; + } + } + } + + + // The previous segment needs to be written before we can write this one. + size_t bytesWritten = 0; + + drpath_iterator prev = isegment; + if (!drpath_prev(&prev)) + { + if (iteratorCount > 1) + { + iteratorCount -= 1; + prev = iterators[iteratorCount - 1]; + } + else + { + prev.path = NULL; + prev.segment.offset = 0; + prev.segment.length = 0; + } + } + + if (prev.segment.length > 0) + { + iterators[iteratorCount - 1] = prev; + bytesWritten = _drpath_clean_trywrite(iterators, iteratorCount, pathOut, pathOutSizeInBytes, ignoreCounter); + } + + + if (!ignoreThisSegment) + { + if (pathOutSizeInBytes > 0) + { + pathOut += bytesWritten; + pathOutSizeInBytes -= bytesWritten; + + if (bytesWritten > 0) + { + pathOut[0] = '/'; + bytesWritten += 1; + + pathOut += 1; + pathOutSizeInBytes -= 1; + } + + if (pathOutSizeInBytes >= isegment.segment.length) + { + strncpy_s(pathOut, pathOutSizeInBytes, isegment.path + isegment.segment.offset, isegment.segment.length); + bytesWritten += isegment.segment.length; + } + else + { + strncpy_s(pathOut, pathOutSizeInBytes, isegment.path + isegment.segment.offset, pathOutSizeInBytes); + bytesWritten += pathOutSizeInBytes; + } + } + } + + return bytesWritten; +} + +size_t drpath_clean(const char* path, char* pathOut, size_t pathOutSizeInBytes) +{ + if (path == NULL) { + return 0; + } + + drpath_iterator last; + if (drpath_last(path, &last)) + { + size_t bytesWritten = 0; + if (path[0] == '/') { + if (pathOut != NULL && pathOutSizeInBytes > 1) { + pathOut[0] = '/'; + bytesWritten = 1; + } + } + + bytesWritten += _drpath_clean_trywrite(&last, 1, pathOut + bytesWritten, pathOutSizeInBytes - bytesWritten - 1, 0); // -1 to ensure there is enough room for a null terminator later on. + if (pathOutSizeInBytes > bytesWritten) { + pathOut[bytesWritten] = '\0'; + } + + return bytesWritten + 1; + } + + return 0; +} + +size_t drpath_append_and_clean(char* dst, size_t dstSizeInBytes, const char* base, const char* other) +{ + if (base == NULL || other == NULL) { + return 0; + } + + drpath_iterator last[2] = { + {NULL, {0, 0}}, + {NULL, {0, 0}} + }; + + dr_bool32 isPathEmpty0 = !drpath_last(base, last + 0); + dr_bool32 isPathEmpty1 = !drpath_last(other, last + 1); + + int iteratorCount = !isPathEmpty0 + !isPathEmpty1; + if (iteratorCount == 0) { + return 0; // Both input strings are empty. + } + + size_t bytesWritten = 0; + if (base[0] == '/') { + if (dst != NULL && dstSizeInBytes > 1) { + dst[0] = '/'; + bytesWritten = 1; + } + } + + bytesWritten += _drpath_clean_trywrite(last, 2, dst + bytesWritten, dstSizeInBytes - bytesWritten - 1, 0); // -1 to ensure there is enough room for a null terminator later on. + if (dstSizeInBytes > bytesWritten) { + dst[bytesWritten] = '\0'; + } + + return bytesWritten + 1; +} + + +dr_bool32 drpath_remove_extension(char* path) +{ + if (path == NULL) { + return DR_FALSE; + } + + const char* extension = drpath_extension(path); + if (extension != NULL) { + extension -= 1; + path[(extension - path)] = '\0'; + } + + return DR_TRUE; +} + +dr_bool32 drpath_copy_and_remove_extension(char* dst, size_t dstSizeInBytes, const char* path) +{ + if (dst == NULL || dstSizeInBytes == 0 || path == NULL) { + return DR_FALSE; + } + + const char* extension = drpath_extension(path); + if (extension != NULL) { + extension -= 1; + } + + strncpy_s(dst, dstSizeInBytes, path, (size_t)(extension - path)); + return DR_TRUE; +} + + +dr_bool32 drpath_remove_file_name(char* path) +{ + if (path == NULL) { + return DR_FALSE; + } + + + // We just create an iterator that starts at the last segment. We then move back one and place a null terminator at the end of + // that segment. That will ensure the resulting path is not left with a slash. + drpath_iterator iLast; + if (!drpath_last(path, &iLast)) { + return DR_FALSE; // The path is empty. + } + + if (drpath_is_root_segment(iLast)) { + return DR_FALSE; + } + + + drpath_iterator iSecondLast = iLast; + if (drpath_prev(&iSecondLast)) + { + // There is a segment before it so we just place a null terminator at the end, but only if it's not the root of a Linux-style absolute path. + if (drpath_is_linux_style_root_segment(iSecondLast)) { + path[iLast.segment.offset] = '\0'; + } else { + path[iSecondLast.segment.offset + iSecondLast.segment.length] = '\0'; + } + } + else + { + // This is already the last segment, so just place a null terminator at the beginning of the string. + path[0] = '\0'; + } + + return DR_TRUE; +} + +dr_bool32 drpath_copy_and_remove_file_name(char* dst, size_t dstSizeInBytes, const char* path) +{ + if (dst == NULL) { + return DR_FALSE; + } + if (dstSizeInBytes == 0) { + return DR_FALSE; + } + if (path == NULL) { + dst[0] = '\0'; + return DR_FALSE; + } + + + // We just create an iterator that starts at the last segment. We then move back one and place a null terminator at the end of + // that segment. That will ensure the resulting path is not left with a slash. + drpath_iterator iLast; + if (!drpath_last(path, &iLast)) { + dst[0] = '\0'; + return DR_FALSE; // The path is empty. + } + + if (drpath_is_linux_style_root_segment(iLast)) { + if (dstSizeInBytes > 1) { + dst[0] = '/'; dst[1] = '\0'; + return DR_FALSE; + } else { + dst[0] = '\0'; + return DR_FALSE; + } + } + + if (drpath_is_win32_style_root_segment(iLast)) { + return strncpy_s(dst, dstSizeInBytes, path + iLast.segment.offset, iLast.segment.length) == 0; + } + + + drpath_iterator iSecondLast = iLast; + if (drpath_prev(&iSecondLast)) + { + // There is a segment before it so we just place a null terminator at the end, but only if it's not the root of a Linux-style absolute path. + if (drpath_is_linux_style_root_segment(iSecondLast)) { + return strcpy_s(dst, dstSizeInBytes, "/") == 0; + } else { + return strncpy_s(dst, dstSizeInBytes, path, iSecondLast.segment.offset + iSecondLast.segment.length) == 0; + } + } + else + { + // This is already the last segment, so just place a null terminator at the beginning of the string. + dst[0] = '\0'; + return DR_TRUE; + } +} + + +dr_bool32 drpath_to_relative(const char* absolutePathToMakeRelative, const char* absolutePathToMakeRelativeTo, char* relativePathOut, size_t relativePathOutSizeInBytes) +{ + // We do this in two phases. The first phase just iterates past each segment of both the path to convert and the + // base path until we find two that are not equal. The second phase just adds the appropriate ".." segments. + + if (relativePathOut == 0) { + return DR_FALSE; + } + if (relativePathOutSizeInBytes == 0) { + return DR_FALSE; + } + + if (!drpath_is_absolute(absolutePathToMakeRelative) || !drpath_is_absolute(absolutePathToMakeRelativeTo)) { + return DR_FALSE; + } + + + drpath_iterator iPath; + drpath_iterator iBase; + dr_bool32 isPathEmpty = !drpath_first(absolutePathToMakeRelative, &iPath); + dr_bool32 isBaseEmpty = !drpath_first(absolutePathToMakeRelativeTo, &iBase); + + if (isPathEmpty && isBaseEmpty) + { + // Looks like both paths are empty. + relativePathOut[0] = '\0'; + return DR_FALSE; + } + + + // Phase 1: Get past the common section. + int isPathAtEnd = 0; + int isBaseAtEnd = 0; + while (!isPathAtEnd && !isBaseAtEnd && drpath_iterators_equal(iPath, iBase)) + { + isPathAtEnd = !drpath_next(&iPath); + isBaseAtEnd = !drpath_next(&iBase); + } + + if (iPath.segment.offset == 0) + { + // The path is not relative to the base path. + relativePathOut[0] = '\0'; + return DR_FALSE; + } + + + // Phase 2: Append ".." segments - one for each remaining segment in the base path. + char* pDst = relativePathOut; + size_t bytesAvailable = relativePathOutSizeInBytes; + + if (!drpath_at_end(iBase)) + { + do + { + if (pDst != relativePathOut) + { + if (bytesAvailable <= 3) + { + // Ran out of room. + relativePathOut[0] = '\0'; + return DR_FALSE; + } + + pDst[0] = '/'; + pDst[1] = '.'; + pDst[2] = '.'; + + pDst += 3; + bytesAvailable -= 3; + } + else + { + // It's the first segment, so we need to ensure we don't lead with a slash. + if (bytesAvailable <= 2) + { + // Ran out of room. + relativePathOut[0] = '\0'; + return DR_FALSE; + } + + pDst[0] = '.'; + pDst[1] = '.'; + + pDst += 2; + bytesAvailable -= 2; + } + } while (drpath_next(&iBase)); + } + + + // Now we just append whatever is left of the main path. We want the path to be clean, so we append segment-by-segment. + if (!drpath_at_end(iPath)) + { + do + { + // Leading slash, if required. + if (pDst != relativePathOut) + { + if (bytesAvailable <= 1) + { + // Ran out of room. + relativePathOut[0] = '\0'; + return DR_FALSE; + } + + pDst[0] = '/'; + + pDst += 1; + bytesAvailable -= 1; + } + + + if (bytesAvailable <= iPath.segment.length) + { + // Ran out of room. + relativePathOut[0] = '\0'; + return DR_FALSE; + } + + if (strncpy_s(pDst, bytesAvailable, iPath.path + iPath.segment.offset, iPath.segment.length) != 0) + { + // Error copying the string. Probably ran out of room in the output buffer. + relativePathOut[0] = '\0'; + return DR_FALSE; + } + + pDst += iPath.segment.length; + bytesAvailable -= iPath.segment.length; + + + } while (drpath_next(&iPath)); + } + + + // Always null terminate. + //assert(bytesAvailable > 0); + pDst[0] = '\0'; + + return DR_TRUE; +} + +dr_bool32 drpath_to_absolute(const char* relativePathToMakeAbsolute, const char* basePath, char* absolutePathOut, size_t absolutePathOutSizeInBytes) +{ + return drpath_append_and_clean(absolutePathOut, absolutePathOutSizeInBytes, basePath, relativePathToMakeAbsolute) != 0; +} +#endif //DR_IMPLEMENTATION + + + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ diff --git a/source/engine/thirdparty/dr_libs/old/dr_2d.h b/source/engine/thirdparty/dr_libs/old/dr_2d.h new file mode 100644 index 0000000..36d1a40 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/old/dr_2d.h @@ -0,0 +1,3417 @@ +// Public Domain. See "unlicense" statement at the end of this file. + +// ABOUT +// +// dr_2d is a simple library for drawing simple 2D graphics. +// +// +// +// USAGE +// +// This is a single-file library. To use it, do something like the following in one .c file. +// #define DR_2D_IMPLEMENTATION +// #include "dr_2d.h" +// +// You can then #include dr_2d.h in other parts of the program as you would with any other header file. +// +// +// +// QUICK NOTES +// +// - Drawing must be done inside a dr2d_begin_draw() and dr2d_end_draw() pair. Rationale: 1) required for compatibility +// with GDI's BeginPaint() and EndPaint() APIs; 2) gives implementations opportunity to save and restore state, such as +// OpenGL state and whatnot. +// - This library is not thread safe. +// +// +// +// OPTIONS +// +// #define DR2D_NO_GDI +// Excludes the GDI back-end. +// +// #define DR2D_NO_CAIRO +// Excludes the Cairo back-end. +// +// +// +// TODO +// - Document resource management. + +#ifndef dr_2d_h +#define dr_2d_h + +#include +#include + +#if defined(__WIN32__) || defined(_WIN32) || defined(_WIN64) +#include + +// No Cairo on Win32 builds. +#ifndef DR2D_NO_CAIRO +#define DR2D_NO_CAIRO +#endif +#else +// No GDI on non-Win32 builds. +#ifndef DR2D_NO_GDI +#define DR2D_NO_GDI +#endif +#endif + +#ifndef DR2D_MAX_FONT_FAMILY_LENGTH +#define DR2D_MAX_FONT_FAMILY_LENGTH 128 +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + + +///////////////////////////////////////////////////////////////// +// +// CORE 2D API +// +///////////////////////////////////////////////////////////////// + +typedef unsigned char dr2d_byte; + +typedef struct dr2d_context dr2d_context; +typedef struct dr2d_surface dr2d_surface; +typedef struct dr2d_font dr2d_font; +typedef struct dr2d_image dr2d_image; +typedef struct dr2d_color dr2d_color; +typedef struct dr2d_font_metrics dr2d_font_metrics; +typedef struct dr2d_glyph_metrics dr2d_glyph_metrics; +typedef struct dr2d_drawing_callbacks dr2d_drawing_callbacks; + + +/// Structure representing an RGBA color. Color components are specified in the range of 0 - 255. +struct dr2d_color +{ + dr2d_byte r; + dr2d_byte g; + dr2d_byte b; + dr2d_byte a; +}; + +struct dr2d_font_metrics +{ + int ascent; + int descent; + int lineHeight; + int spaceWidth; +}; + +struct dr2d_glyph_metrics +{ + int width; + int height; + int originX; + int originY; + int advanceX; + int advanceY; +}; + +typedef enum +{ + dr2d_font_weight_medium = 0, + dr2d_font_weight_thin, + dr2d_font_weight_extra_light, + dr2d_font_weight_light, + dr2d_font_weight_semi_light, + dr2d_font_weight_book, + dr2d_font_weight_semi_bold, + dr2d_font_weight_bold, + dr2d_font_weight_extra_bold, + dr2d_font_weight_heavy, + dr2d_font_weight_extra_heavy, + + dr2d_font_weight_normal = dr2d_font_weight_medium, + dr2d_font_weight_default = dr2d_font_weight_medium + +} dr2d_font_weight; + +typedef enum +{ + dr2d_font_slant_none = 0, + dr2d_font_slant_italic, + dr2d_font_slant_oblique + +} dr2d_font_slant; + +typedef enum +{ + dr2d_image_format_rgba8, + dr2d_image_format_bgra8, + dr2d_image_format_argb8, +} dr2d_image_format; + + +#define DR2D_IMAGE_DRAW_BACKGROUND (1 << 0) +#define DR2D_IMAGE_HINT_NO_ALPHA (1 << 1) + +#define DR2D_READ (1 << 0) +#define DR2D_WRITE (1 << 1) + +#define DR2D_FONT_NO_CLEARTYPE (1 << 0) + +typedef struct +{ + /// The destination position on the x axis. This is ignored if the DR2D_IMAGE_ALIGN_CENTER option is set. + float dstX; + + /// The destination position on the y axis. This is ignored if the DR2D_IMAGE_ALIGN_CENTER option is set. + float dstY; + + /// The destination width. + float dstWidth; + + /// The destination height. + float dstHeight; + + + /// The source offset on the x axis. + float srcX; + + /// The source offset on the y axis. + float srcY; + + /// The source width. + float srcWidth; + + /// The source height. + float srcHeight; + + + /// The foreground tint color. This is not applied to the background color, and the alpha component is ignored. + dr2d_color foregroundTint; + + /// The background color. Only used if the DR2D_IMAGE_DRAW_BACKGROUND option is set. + dr2d_color backgroundColor; + + + /// Flags for controlling how the image should be drawn. + unsigned int options; + +} dr2d_draw_image_args; + + +typedef bool (* dr2d_on_create_context_proc) (dr2d_context* pContext, const void* pUserData); +typedef void (* dr2d_on_delete_context_proc) (dr2d_context* pContext); +typedef bool (* dr2d_on_create_surface_proc) (dr2d_surface* pSurface, float width, float height); +typedef void (* dr2d_on_delete_surface_proc) (dr2d_surface* pSurface); +typedef bool (* dr2d_on_create_font_proc) (dr2d_font* pFont); +typedef void (* dr2d_on_delete_font_proc) (dr2d_font* pFont); +typedef bool (* dr2d_on_create_image_proc) (dr2d_image* pImage, unsigned int stride, const void* pData); +typedef void (* dr2d_on_delete_image_proc) (dr2d_image* pImage); +typedef void (* dr2d_begin_draw_proc) (dr2d_surface* pSurface); +typedef void (* dr2d_end_draw_proc) (dr2d_surface* pSurface); +typedef void (* dr2d_clear_proc) (dr2d_surface* pSurface, dr2d_color color); +typedef void (* dr2d_draw_rect_proc) (dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color); +typedef void (* dr2d_draw_rect_outline_proc) (dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth); +typedef void (* dr2d_draw_rect_with_outline_proc) (dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth, dr2d_color outlineColor); +typedef void (* dr2d_draw_round_rect_proc) (dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float width); +typedef void (* dr2d_draw_round_rect_outline_proc) (dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float width, float outlineWidth); +typedef void (* dr2d_draw_round_rect_with_outline_proc) (dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float width, float outlineWidth, dr2d_color outlineColor); +typedef void (* dr2d_draw_text_proc) (dr2d_surface* pSurface, dr2d_font* pFont, const char* text, size_t textSizeInBytes, float posX, float posY, dr2d_color color, dr2d_color backgroundColor); +typedef void (* dr2d_draw_image_proc) (dr2d_surface* pSurface, dr2d_image* pImage, dr2d_draw_image_args* pArgs); +typedef void (* dr2d_set_clip_proc) (dr2d_surface* pSurface, float left, float top, float right, float bottom); +typedef void (* dr2d_get_clip_proc) (dr2d_surface* pSurface, float* pLeftOut, float* pTopOut, float* pRightOut, float* pBottomOut); +typedef dr2d_image_format (* dr2d_get_optimal_image_format_proc) (dr2d_context* pContext); +typedef void* (* dr2d_map_image_data_proc) (dr2d_image* pImage, unsigned int accessFlags); +typedef void (* dr2d_unmap_image_data_proc) (dr2d_image* pImage); +typedef bool (* dr2d_get_font_metrics_proc) (dr2d_font* pFont, dr2d_font_metrics* pMetricsOut); +typedef bool (* dr2d_get_glyph_metrics_proc) (dr2d_font* pFont, unsigned int utf32, dr2d_glyph_metrics* pMetricsOut); +typedef bool (* dr2d_measure_string_proc) (dr2d_font* pFont, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut); +typedef bool (* dr2d_get_text_cursor_position_from_point_proc)(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut); +typedef bool (* dr2d_get_text_cursor_position_from_char_proc) (dr2d_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut); + + +struct dr2d_drawing_callbacks +{ + dr2d_on_create_context_proc on_create_context; + dr2d_on_delete_context_proc on_delete_context; + dr2d_on_create_surface_proc on_create_surface; + dr2d_on_delete_surface_proc on_delete_surface; + dr2d_on_create_font_proc on_create_font; + dr2d_on_delete_font_proc on_delete_font; + dr2d_on_create_image_proc on_create_image; + dr2d_on_delete_image_proc on_delete_image; + + dr2d_begin_draw_proc begin_draw; + dr2d_end_draw_proc end_draw; + dr2d_clear_proc clear; + dr2d_draw_rect_proc draw_rect; + dr2d_draw_rect_outline_proc draw_rect_outline; + dr2d_draw_rect_with_outline_proc draw_rect_with_outline; + dr2d_draw_round_rect_proc draw_round_rect; + dr2d_draw_round_rect_outline_proc draw_round_rect_outline; + dr2d_draw_round_rect_with_outline_proc draw_round_rect_with_outline; + dr2d_draw_text_proc draw_text; + dr2d_draw_image_proc draw_image; + dr2d_set_clip_proc set_clip; + dr2d_get_clip_proc get_clip; + + dr2d_get_optimal_image_format_proc get_optimal_image_format; + dr2d_map_image_data_proc map_image_data; + dr2d_unmap_image_data_proc unmap_image_data; + + dr2d_get_font_metrics_proc get_font_metrics; + dr2d_get_glyph_metrics_proc get_glyph_metrics; + dr2d_measure_string_proc measure_string; + dr2d_get_text_cursor_position_from_point_proc get_text_cursor_position_from_point; + dr2d_get_text_cursor_position_from_char_proc get_text_cursor_position_from_char; +}; + +struct dr2d_image +{ + /// A pointer to the context that owns the image. + dr2d_context* pContext; + + /// The width of the image. + unsigned int width; + + /// The height of the image. + unsigned int height; + + /// The format of the image data. + dr2d_image_format format; + + /// Whether or not the image's data is already mapped. + bool isMapped; + + /// The extra bytes. The size of this buffer is equal to pContext->imageExtraBytes. + dr2d_byte pExtraData[1]; +}; + +struct dr2d_font +{ + /// A pointer to the context that owns the font. + dr2d_context* pContext; + + /// The font family. + char family[DR2D_MAX_FONT_FAMILY_LENGTH]; + + /// The size of the font. + unsigned int size; + + /// The font's weight. + dr2d_font_weight weight; + + /// The font's slant. + dr2d_font_slant slant; + + /// The font's rotation, in degrees. + float rotation; + + /// Flags. Can be a combination of the following. + /// DR2D_FONT_NO_CLEARTYPE + unsigned int flags; + + /// The extra bytes. The size of this buffer is equal to pContext->fontExtraBytes. + dr2d_byte pExtraData[1]; +}; + +struct dr2d_surface +{ + /// A pointer to the context that owns the surface. + dr2d_context* pContext; + + /// The width of the surface. + float width; + + /// The height of the surface. + float height; + + /// The extra bytes. The size of this buffer is equal to pContext->surfaceExtraBytes. + dr2d_byte pExtraData[1]; +}; + +struct dr2d_context +{ + /// The drawing callbacks. + dr2d_drawing_callbacks drawingCallbacks; + + /// The number of extra bytes to allocate for each image. + size_t imageExtraBytes; + + /// The number of extra bytes to allocate for each font. + size_t fontExtraBytes; + + /// The number of extra bytes to allocate for each surface. + size_t surfaceExtraBytes; + + /// The number of extra bytes to allocate for the context. + size_t contextExtraBytes; + + /// The extra bytes. + dr2d_byte pExtraData[1]; +}; + + + +/// Creats a context. +dr2d_context* dr2d_create_context(dr2d_drawing_callbacks drawingCallbacks, size_t contextExtraBytes, size_t surfaceExtraBytes, size_t fontExtraBytes, size_t imageExtraBytes, const void* pUserData); + +/// Deletes the given context. +void dr2d_delete_context(dr2d_context* pContext); + +/// Retrieves a pointer to the given context's extra data buffer. +void* dr2d_get_context_extra_data(dr2d_context* pContext); + + +/// Creates a surface. +dr2d_surface* dr2d_create_surface(dr2d_context* pContext, float width, float height); + +/// Deletes the given surface. +void dr2d_delete_surface(dr2d_surface* pSurface); + +/// Retrieves the width of the surface. +float dr2d_get_surface_width(const dr2d_surface* pSurface); + +/// Retrieves the height of the surface. +float dr2d_get_surface_height(const dr2d_surface* pSurface); + +/// Retrieves a pointer to the given surface's extra data buffer. +void* dr2d_get_surface_extra_data(dr2d_surface* pSurface); + + + +//// Drawing //// + +/// Marks the beginning of a paint operation. +void dr2d_begin_draw(dr2d_surface* pSurface); + +/// Marks the end of a paint operation. +void dr2d_end_draw(dr2d_surface* pSurface); + +/// Clears the given surface with the given color. +void dr2d_clear(dr2d_surface* pSurface, dr2d_color color); + +/// Draws a filled rectangle without an outline. +void dr2d_draw_rect(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color); + +/// Draws the outline of the given rectangle. +void dr2d_draw_rect_outline(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth); + +/// Draws a filled rectangle with an outline. +void dr2d_draw_rect_with_outline(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth, dr2d_color outlineColor); + +/// Draws a filled rectangle without an outline with rounded corners. +void dr2d_draw_round_rect(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius); + +/// Draws the outline of the given rectangle with rounded corners. +void dr2d_draw_round_rect_outline(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth); + +/// Draws a filled rectangle with an outline. +void dr2d_draw_round_rect_with_outline(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth, dr2d_color outlineColor); + +/// Draws a run of text. +void dr2d_draw_text(dr2d_surface* pSurface, dr2d_font* pFont, const char* text, size_t textSizeInBytes, float posX, float posY, dr2d_color color, dr2d_color backgroundColor); + +/// Draws an image. +void dr2d_draw_image(dr2d_surface* pSurface, dr2d_image* pImage, dr2d_draw_image_args* pArgs); + +/// Sets the clipping rectangle. +void dr2d_set_clip(dr2d_surface* pSurface, float left, float top, float right, float bottom); + +/// Retrieves the clipping rectangle. +void dr2d_get_clip(dr2d_surface* pSurface, float* pLeftOut, float* pTopOut, float* pRightOut, float* pBottomOut); + + +/// Creates a font that can be passed to dr2d_draw_text(). +dr2d_font* dr2d_create_font(dr2d_context* pContext, const char* family, unsigned int size, dr2d_font_weight weight, dr2d_font_slant slant, float rotation, unsigned int flags); + +/// Deletes a font that was previously created with dr2d_create_font() +void dr2d_delete_font(dr2d_font* pFont); + +/// Retrieves a pointer to the given font's extra data buffer. +void* dr2d_get_font_extra_data(dr2d_font* pFont); + +/// Retrieves the size of the given font. +unsigned int dr2d_get_font_size(dr2d_font* pFont); + +/// Retrieves the metrics of the given font. +bool dr2d_get_font_metrics(dr2d_font* pFont, dr2d_font_metrics* pMetricsOut); + +/// Retrieves the metrics of the glyph for the given character when rendered with the given font. +bool dr2d_get_glyph_metrics(dr2d_font* pFont, unsigned int utf32, dr2d_glyph_metrics* pMetricsOut); + +/// Retrieves the dimensions of the given string when drawn with the given font. +bool dr2d_measure_string(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut); + +/// Retrieves the position to place a text cursor based on the given point for the given string when drawn with the given font. +bool dr2d_get_text_cursor_position_from_point(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut); + +/// Retrieves the position to palce a text cursor based on the character at the given index for the given string when drawn with the given font. +bool dr2d_get_text_cursor_position_from_char(dr2d_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut); + + +/// Creates an image that can be passed to dr2d_draw_image(). +/// +/// @remarks +/// The dimensions and format of an image are immutable. If these need to change, then the image needs to be deleted and re-created. +/// @par +/// If pData is NULL, the default image data is undefined. +/// @par +/// Use dr2d_map_image_data() and dr2d_unmap_image_data() to update or retrieve image data. +dr2d_image* dr2d_create_image(dr2d_context* pContext, unsigned int width, unsigned int height, dr2d_image_format format, unsigned int stride, const void* pData); + +/// Deletes the given image. +void dr2d_delete_image(dr2d_image* pImage); + +/// Retrieves a pointer to the given image's extra data buffer. +void* dr2d_get_image_extra_data(dr2d_image* pImage); + +/// Retrieves the size of the given image. +void dr2d_get_image_size(dr2d_image* pImage, unsigned int* pWidthOut, unsigned int* pHeightOut); + +/// Retrieves a pointer to a buffer representing the given image's data. +/// +/// Call dr2d_unmap_image_data() when you are done with this function. +/// +/// Use this function to access an image's data. The returned pointer does not necessarilly point to the image's actual data, so when +/// writing to this pointer, nothing is guaranteed to be updated until dr2d_unmap_image_data() is called. +/// +/// The returned data will contain the image data at the time of the mapping. +/// +/// This will fail if the image's data is already mapped. +void* dr2d_map_image_data(dr2d_image* pImage, unsigned int accessFlags); + +/// Unmaps the given image's data. +/// +/// A flush is done at this point to ensure the actual underlying image data is updated. +void dr2d_unmap_image_data(dr2d_image* pImage); + +/// Retrieves the optimal image format for the given context. This depends on the backend. +dr2d_image_format dr2d_get_optimal_image_format(dr2d_context* pContext); + + +///////////////////////////////////////////////////////////////// +// +// UTILITY API +// +///////////////////////////////////////////////////////////////// + +/// Creates a color object from a set of RGBA color components. +dr2d_color dr2d_rgba(dr2d_byte r, dr2d_byte g, dr2d_byte b, dr2d_byte a); + +/// Creates a fully opaque color object from a set of RGB color components. +dr2d_color dr2d_rgb(dr2d_byte r, dr2d_byte g, dr2d_byte b); + + + + +///////////////////////////////////////////////////////////////// +// +// WINDOWS GDI 2D API +// +// When using GDI as the rendering back-end you will usually want to only call drawing functions in response to a WM_PAINT message. +// +///////////////////////////////////////////////////////////////// +#ifndef DR2D_NO_GDI + +/// Creates a 2D context with GDI as the backend and, optionally, a custom HDC. +/// +/// hDC [in, optional] The main device context. Can be null. +dr2d_context* dr2d_create_context_gdi(HDC hDC); + +/// Creates a surface that draws directly to the given window. +/// +/// @remarks +/// When using this kind of surface, the internal HBITMAP is not used. +dr2d_surface* dr2d_create_surface_gdi_HWND(dr2d_context* pContext, HWND hWnd); +dr2d_surface* dr2d_create_surface_gdi_HDC(dr2d_context* pContext, HDC hDC); + +/// Retrieves the internal HDC that we have been rendering to for the given surface. +/// +/// @remarks +/// This assumes the given surface was created from a context that was created from dr2d_create_context_gdi() +HDC dr2d_get_HDC(dr2d_surface* pSurface); + +/// Retrieves the internal HBITMAP object that we have been rendering to. +/// +/// @remarks +/// This assumes the given surface was created from a context that was created from dr2d_create_context_gdi(). +HBITMAP dr2d_get_HBITMAP(dr2d_surface* pSurface); + +/// Retrieves the internal HFONT object from the given dr2d_font object. +HFONT dr2d_get_HFONT(dr2d_font* pFont); + +#endif // GDI + + + +///////////////////////////////////////////////////////////////// +// +// CAIRO 2D API +// +///////////////////////////////////////////////////////////////// +#ifndef DR2D_NO_CAIRO +#include + +/// Creates a 2D context with Cairo as the backend. +dr2d_context* dr2d_create_context_cairo(); + +/// Creates a surface that draws directly to the given cairo context. +dr2d_surface* dr2d_create_surface_cairo(dr2d_context* pContext, cairo_t* cr); + +/// Retrieves the internal cairo_surface_t object from the given surface. +/// +/// @remarks +/// This assumes the given surface was created from a context that was created with dr2d_create_context_cairo(). +cairo_surface_t* dr2d_get_cairo_surface_t(dr2d_surface* pSurface); + +/// Retrieves the internal cairo_t object from the given surface. +cairo_t* dr2d_get_cairo_t(dr2d_surface* pSurface); + +#endif // Cairo + + +#ifdef __cplusplus +} +#endif + +#endif //dr_2d_h + + +/////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef DR_2D_IMPLEMENTATION +#include +#include +#include +#include +#include + +#ifndef DR2D_PRIVATE +#define DR2D_PRIVATE static +#endif + +static int dr2d_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src) +{ +#ifdef _WIN32 + return strcpy_s(dst, dstSizeInBytes, src); +#else + if (dst == 0) { + return EINVAL; + } + if (dstSizeInBytes == 0) { + return ERANGE; + } + if (src == 0) { + dst[0] = '\0'; + return EINVAL; + } + + size_t i; + for (i = 0; i < dstSizeInBytes && src[i] != '\0'; ++i) { + dst[i] = src[i]; + } + + if (i < dstSizeInBytes) { + dst[i] = '\0'; + return 0; + } + + dst[0] = '\0'; + return ERANGE; +#endif +} + + +dr2d_context* dr2d_create_context(dr2d_drawing_callbacks drawingCallbacks, size_t contextExtraBytes, size_t surfaceExtraBytes, size_t fontExtraBytes, size_t imageExtraBytes, const void* pUserData) +{ + dr2d_context* pContext = (dr2d_context*)malloc(sizeof(dr2d_context) + contextExtraBytes); + if (pContext != NULL) + { + pContext->drawingCallbacks = drawingCallbacks; + pContext->imageExtraBytes = imageExtraBytes; + pContext->fontExtraBytes = fontExtraBytes; + pContext->surfaceExtraBytes = surfaceExtraBytes; + pContext->contextExtraBytes = contextExtraBytes; + memset(pContext->pExtraData, 0, contextExtraBytes); + + // The create_context callback is optional. If it is set to NULL, we just finish up here and return successfully. Otherwise + // we want to call the create_context callback and check it's return value. If it's return value if false it means there + // was an error and we need to return null. + if (pContext->drawingCallbacks.on_create_context != NULL) + { + if (!pContext->drawingCallbacks.on_create_context(pContext, pUserData)) + { + // An error was thrown from the callback. + free(pContext); + pContext = NULL; + } + } + } + + return pContext; +} + +void dr2d_delete_context(dr2d_context* pContext) +{ + if (pContext != NULL) { + if (pContext->drawingCallbacks.on_delete_context != NULL) { + pContext->drawingCallbacks.on_delete_context(pContext); + } + + free(pContext); + } +} + +void* dr2d_get_context_extra_data(dr2d_context* pContext) +{ + return pContext->pExtraData; +} + + +dr2d_surface* dr2d_create_surface(dr2d_context* pContext, float width, float height) +{ + if (pContext != NULL) + { + dr2d_surface* pSurface = (dr2d_surface*)malloc(sizeof(dr2d_surface) + pContext->surfaceExtraBytes); + if (pSurface != NULL) + { + pSurface->pContext = pContext; + pSurface->width = width; + pSurface->height = height; + memset(pSurface->pExtraData, 0, pContext->surfaceExtraBytes); + + // The create_surface callback is optional. If it is set to NULL, we just finish up here and return successfully. Otherwise + // we want to call the create_surface callback and check it's return value. If it's return value if false it means there + // was an error and we need to return null. + if (pContext->drawingCallbacks.on_create_surface != NULL) + { + if (!pContext->drawingCallbacks.on_create_surface(pSurface, width, height)) + { + free(pSurface); + pSurface = NULL; + } + } + } + + return pSurface; + } + + return NULL; +} + +void dr2d_delete_surface(dr2d_surface* pSurface) +{ + if (pSurface != NULL) + { + assert(pSurface->pContext != NULL); + + if (pSurface->pContext->drawingCallbacks.on_delete_surface != NULL) { + pSurface->pContext->drawingCallbacks.on_delete_surface(pSurface); + } + + free(pSurface); + } +} + +float dr2d_get_surface_width(const dr2d_surface* pSurface) +{ + if (pSurface != NULL) { + return pSurface->width; + } + + return 0; +} + +float dr2d_get_surface_height(const dr2d_surface* pSurface) +{ + if (pSurface != NULL) { + return pSurface->height; + } + + return 0; +} + +void* dr2d_get_surface_extra_data(dr2d_surface* pSurface) +{ + if (pSurface != NULL) { + return pSurface->pExtraData; + } + + return NULL; +} + + +void dr2d_begin_draw(dr2d_surface* pSurface) +{ + if (pSurface != NULL) + { + assert(pSurface->pContext != NULL); + + if (pSurface->pContext->drawingCallbacks.begin_draw != NULL) { + pSurface->pContext->drawingCallbacks.begin_draw(pSurface); + } + } +} + +void dr2d_end_draw(dr2d_surface* pSurface) +{ + if (pSurface != NULL) + { + assert(pSurface->pContext != NULL); + + if (pSurface->pContext->drawingCallbacks.end_draw != NULL) { + pSurface->pContext->drawingCallbacks.end_draw(pSurface); + } + } +} + +void dr2d_clear(dr2d_surface * pSurface, dr2d_color color) +{ + if (pSurface != NULL) + { + assert(pSurface->pContext != NULL); + + if (pSurface->pContext->drawingCallbacks.clear != NULL) { + pSurface->pContext->drawingCallbacks.clear(pSurface, color); + } + } +} + +void dr2d_draw_rect(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color) +{ + if (pSurface != NULL) + { + assert(pSurface->pContext != NULL); + + if (pSurface->pContext->drawingCallbacks.draw_rect != NULL) { + pSurface->pContext->drawingCallbacks.draw_rect(pSurface, left, top, right, bottom, color); + } + } +} + +void dr2d_draw_rect_outline(dr2d_surface * pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth) +{ + if (pSurface != NULL) + { + assert(pSurface->pContext != NULL); + + if (pSurface->pContext->drawingCallbacks.draw_rect_outline != NULL) { + pSurface->pContext->drawingCallbacks.draw_rect_outline(pSurface, left, top, right, bottom, color, outlineWidth); + } + } +} + +void dr2d_draw_rect_with_outline(dr2d_surface * pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth, dr2d_color outlineColor) +{ + if (pSurface != NULL) + { + assert(pSurface->pContext != NULL); + + if (pSurface->pContext->drawingCallbacks.draw_rect_with_outline != NULL) { + pSurface->pContext->drawingCallbacks.draw_rect_with_outline(pSurface, left, top, right, bottom, color, outlineWidth, outlineColor); + } + } +} + +void dr2d_draw_round_rect(dr2d_surface * pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius) +{ + if (pSurface != NULL) + { + assert(pSurface->pContext != NULL); + + if (pSurface->pContext->drawingCallbacks.draw_round_rect != NULL) { + pSurface->pContext->drawingCallbacks.draw_round_rect(pSurface, left, top, right, bottom, color, radius); + } + } +} + +void dr2d_draw_round_rect_outline(dr2d_surface * pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth) +{ + if (pSurface != NULL) + { + assert(pSurface->pContext != NULL); + + if (pSurface->pContext->drawingCallbacks.draw_round_rect_outline != NULL) { + pSurface->pContext->drawingCallbacks.draw_round_rect_outline(pSurface, left, top, right, bottom, color, radius, outlineWidth); + } + } +} + +void dr2d_draw_round_rect_with_outline(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth, dr2d_color outlineColor) +{ + if (pSurface != NULL) + { + assert(pSurface->pContext != NULL); + + if (pSurface->pContext->drawingCallbacks.draw_round_rect_with_outline != NULL) { + pSurface->pContext->drawingCallbacks.draw_round_rect_with_outline(pSurface, left, top, right, bottom, color, radius, outlineWidth, outlineColor); + } + } +} + +void dr2d_draw_text(dr2d_surface* pSurface, dr2d_font* pFont, const char* text, size_t textSizeInBytes, float posX, float posY, dr2d_color color, dr2d_color backgroundColor) +{ + if (pSurface != NULL) + { + assert(pSurface->pContext != NULL); + + if (pSurface->pContext->drawingCallbacks.draw_text != NULL) { + pSurface->pContext->drawingCallbacks.draw_text(pSurface, pFont, text, textSizeInBytes, posX, posY, color, backgroundColor); + } + } +} + +void dr2d_draw_image(dr2d_surface* pSurface, dr2d_image* pImage, dr2d_draw_image_args* pArgs) +{ + if (pSurface == NULL || pImage == NULL || pArgs == NULL) { + return; + } + + assert(pSurface->pContext != NULL); + + if (pSurface->pContext->drawingCallbacks.draw_image) { + pSurface->pContext->drawingCallbacks.draw_image(pSurface, pImage, pArgs); + } +} + +void dr2d_set_clip(dr2d_surface* pSurface, float left, float top, float right, float bottom) +{ + if (pSurface != NULL) + { + assert(pSurface->pContext != NULL); + + if (pSurface->pContext->drawingCallbacks.set_clip != NULL) { + pSurface->pContext->drawingCallbacks.set_clip(pSurface, left, top, right, bottom); + } + } +} + +void dr2d_get_clip(dr2d_surface* pSurface, float* pLeftOut, float* pTopOut, float* pRightOut, float* pBottomOut) +{ + if (pSurface != NULL) + { + assert(pSurface->pContext != NULL); + + if (pSurface->pContext->drawingCallbacks.get_clip != NULL) { + pSurface->pContext->drawingCallbacks.get_clip(pSurface, pLeftOut, pTopOut, pRightOut, pBottomOut); + } + } +} + +dr2d_font* dr2d_create_font(dr2d_context* pContext, const char* family, unsigned int size, dr2d_font_weight weight, dr2d_font_slant slant, float rotation, unsigned int flags) +{ + if (pContext == NULL) { + return NULL; + } + + dr2d_font* pFont = (dr2d_font*)malloc(sizeof(dr2d_font) + pContext->fontExtraBytes); + if (pFont == NULL) { + return NULL; + } + + pFont->pContext = pContext; + pFont->family[0] = '\0'; + pFont->size = size; + pFont->weight = weight; + pFont->slant = slant; + pFont->rotation = rotation; + pFont->flags = flags; + + if (family != NULL) { + dr2d_strcpy_s(pFont->family, sizeof(pFont->family), family); + } + + if (pContext->drawingCallbacks.on_create_font != NULL) { + if (!pContext->drawingCallbacks.on_create_font(pFont)) { + free(pFont); + return NULL; + } + } + + return pFont; +} + +void dr2d_delete_font(dr2d_font* pFont) +{ + if (pFont == NULL) { + return; + } + + assert(pFont->pContext != NULL); + + if (pFont->pContext->drawingCallbacks.on_delete_font != NULL) { + pFont->pContext->drawingCallbacks.on_delete_font(pFont); + } + + free(pFont); +} + +void* dr2d_get_font_extra_data(dr2d_font* pFont) +{ + if (pFont == NULL) { + return NULL; + } + + return pFont->pExtraData; +} + +unsigned int dr2d_get_font_size(dr2d_font* pFont) +{ + if (pFont == NULL) { + return 0; + } + + return pFont->size; +} + +bool dr2d_get_font_metrics(dr2d_font* pFont, dr2d_font_metrics* pMetricsOut) +{ + if (pFont == NULL) { + return false; + } + + assert(pFont->pContext != NULL); + + if (pFont->pContext->drawingCallbacks.get_font_metrics != NULL) { + return pFont->pContext->drawingCallbacks.get_font_metrics(pFont, pMetricsOut); + } + + return false; +} + +bool dr2d_get_glyph_metrics(dr2d_font* pFont, unsigned int utf32, dr2d_glyph_metrics* pMetricsOut) +{ + if (pFont == NULL || pMetricsOut == NULL) { + return false; + } + + assert(pFont->pContext != NULL); + + if (pFont->pContext->drawingCallbacks.get_glyph_metrics != NULL) { + return pFont->pContext->drawingCallbacks.get_glyph_metrics(pFont, utf32, pMetricsOut); + } + + return false; +} + +bool dr2d_measure_string(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut) +{ + if (pFont == NULL) { + return false; + } + + assert(pFont->pContext != NULL); + + if (pFont->pContext->drawingCallbacks.measure_string != NULL) { + return pFont->pContext->drawingCallbacks.measure_string(pFont, text, textSizeInBytes, pWidthOut, pHeightOut); + } + + return false; +} + +bool dr2d_get_text_cursor_position_from_point(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut) +{ + if (pFont == NULL) { + return false; + } + + assert(pFont->pContext != NULL); + + if (pFont->pContext->drawingCallbacks.get_text_cursor_position_from_point) { + return pFont->pContext->drawingCallbacks.get_text_cursor_position_from_point(pFont, text, textSizeInBytes, maxWidth, inputPosX, pTextCursorPosXOut, pCharacterIndexOut); + } + + return false; +} + +bool dr2d_get_text_cursor_position_from_char(dr2d_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut) +{ + if (pFont == NULL) { + return false; + } + + assert(pFont->pContext != NULL); + + if (pFont->pContext->drawingCallbacks.get_text_cursor_position_from_char) { + return pFont->pContext->drawingCallbacks.get_text_cursor_position_from_char(pFont, text, characterIndex, pTextCursorPosXOut); + } + + return false; +} + + +dr2d_image* dr2d_create_image(dr2d_context* pContext, unsigned int width, unsigned int height, dr2d_image_format format, unsigned int stride, const void* pData) +{ + if (pContext == NULL || width == 0 || height == 0) { + return NULL; + } + + dr2d_image* pImage = (dr2d_image*)malloc(sizeof(dr2d_image) + pContext->imageExtraBytes); + if (pImage == NULL) { + return NULL; + } + + pImage->pContext = pContext; + pImage->width = width; + pImage->height = height; + pImage->format = format; + pImage->isMapped = false; + + if (pContext->drawingCallbacks.on_create_image != NULL) { + if (!pContext->drawingCallbacks.on_create_image(pImage, stride, pData)) { + free(pImage); + return NULL; + } + } + + return pImage; +} + +void dr2d_delete_image(dr2d_image* pImage) +{ + if (pImage == NULL) { + return; + } + + assert(pImage->pContext != NULL); + + if (pImage->pContext->drawingCallbacks.on_delete_image != NULL) { + pImage->pContext->drawingCallbacks.on_delete_image(pImage); + } + + free(pImage); +} + +void* dr2d_get_image_extra_data(dr2d_image* pImage) +{ + if (pImage == NULL) { + return NULL; + } + + return pImage->pExtraData; +} + +void dr2d_get_image_size(dr2d_image* pImage, unsigned int* pWidthOut, unsigned int* pHeightOut) +{ + if (pImage == NULL) { + return; + } + + if (pWidthOut) { + *pWidthOut = pImage->width; + } + if (pHeightOut) { + *pHeightOut = pImage->height; + } +} + +void* dr2d_map_image_data(dr2d_image* pImage, unsigned int accessFlags) +{ + if (pImage == NULL || pImage->pContext->drawingCallbacks.map_image_data == NULL || pImage->pContext->drawingCallbacks.unmap_image_data == NULL || pImage->isMapped) { + return NULL; + } + + void* pImageData = pImage->pContext->drawingCallbacks.map_image_data(pImage, accessFlags); + if (pImageData != NULL) { + pImage->isMapped = true; + } + + return pImageData; +} + +void dr2d_unmap_image_data(dr2d_image* pImage) +{ + if (pImage == NULL || pImage->pContext->drawingCallbacks.unmap_image_data == NULL || !pImage->isMapped) { + return; + } + + pImage->pContext->drawingCallbacks.unmap_image_data(pImage); + pImage->isMapped = false; +} + +dr2d_image_format dr2d_get_optimal_image_format(dr2d_context* pContext) +{ + if (pContext == NULL || pContext->drawingCallbacks.get_optimal_image_format == NULL) { + return dr2d_image_format_rgba8; + } + + return pContext->drawingCallbacks.get_optimal_image_format(pContext); +} + + + +///////////////////////////////////////////////////////////////// +// +// UTILITY API +// +///////////////////////////////////////////////////////////////// + +dr2d_color dr2d_rgba(dr2d_byte r, dr2d_byte g, dr2d_byte b, dr2d_byte a) +{ + dr2d_color color; + color.r = r; + color.g = g; + color.b = b; + color.a = a; + + return color; +} + +dr2d_color dr2d_rgb(dr2d_byte r, dr2d_byte g, dr2d_byte b) +{ + dr2d_color color; + color.r = r; + color.g = g; + color.b = b; + color.a = 255; + + return color; +} + + + +///////////////////////////////////////////////////////////////// +// +// PRIVATE UTILITY API +// +///////////////////////////////////////////////////////////////// + +// RGBA8 <-> BGRA8 swap with alpha pre-multiply. +void dr2d__rgba8_bgra8_swap__premul(const void* pSrc, void* pDst, unsigned int width, unsigned int height, unsigned int srcStride, unsigned int dstStride) +{ + assert(pSrc != NULL); + assert(pDst != NULL); + + const unsigned int srcStride32 = srcStride/4; + const unsigned int dstStride32 = dstStride/4; + + const unsigned int* pSrcRow = (const unsigned int*)pSrc; + unsigned int* pDstRow = (unsigned int*)pDst; + + for (unsigned int iRow = 0; iRow < height; ++iRow) + { + for (unsigned int iCol = 0; iCol < width; ++iCol) + { + unsigned int srcTexel = pSrcRow[iCol]; + unsigned int srcTexelA = (srcTexel & 0xFF000000) >> 24; + unsigned int srcTexelB = (srcTexel & 0x00FF0000) >> 16; + unsigned int srcTexelG = (srcTexel & 0x0000FF00) >> 8; + unsigned int srcTexelR = (srcTexel & 0x000000FF) >> 0; + + srcTexelB = (unsigned int)(srcTexelB * (srcTexelA / 255.0f)); + srcTexelG = (unsigned int)(srcTexelG * (srcTexelA / 255.0f)); + srcTexelR = (unsigned int)(srcTexelR * (srcTexelA / 255.0f)); + + pDstRow[iCol] = (srcTexelR << 16) | (srcTexelG << 8) | (srcTexelB << 0) | (srcTexelA << 24); + } + + pSrcRow += srcStride32; + pDstRow += dstStride32; + } +} + + +///////////////////////////////////////////////////////////////// +// +// WINDOWS GDI 2D API +// +///////////////////////////////////////////////////////////////// +#ifndef DR2D_NO_GDI + +typedef struct +{ + /// The device context that owns every surface HBITMAP object. All drawing is done through this DC. + HDC hDC; + + /// The buffer used to store wchar strings when converting from char* to wchar_t* strings. We just use a global buffer for + /// this so we can avoid unnecessary allocations. + wchar_t* wcharBuffer; + + /// The size of wcharBuffer (including the null terminator). + unsigned int wcharBufferLength; + + /// The cache of glyph character positions. + int* pGlyphCache; + size_t glyphCacheSize; + + /// Whether or not the context owns the device context. + bool ownsDC; + +} gdi_context_data; + +typedef struct +{ + /// The window to draw to. The can be null, which is the case when creating the surface with dr2d_create_surface(). When this + /// is non-null the size of the surface is always tied to the window. + HWND hWnd; + + /// The HDC to use when drawing to the surface. + HDC hDC; + + /// The intermediate DC to use when drawing bitmaps. + HDC hIntermediateDC; + + /// The PAINTSTRUCT object that is filled by BeginPaint(). Only used when hWnd is non-null. + PAINTSTRUCT ps; + + /// The bitmap to render to. This is created with GDI's CreateDIBSection() API, using the DC above. This is only used + /// when hDC is NULL, which is the default. + HBITMAP hBitmap; + + /// A pointer to the raw bitmap data. This is allocated CreateDIBSection(). + void* pBitmapData; + + + /// The stock DC brush. + HGDIOBJ hStockDCBrush; + + /// The stock null brush. + HGDIOBJ hStockNullBrush; + + /// The stock DC pen. + HGDIOBJ hStockDCPen; + + /// The stock null pen. + HGDIOBJ hStockNullPen; + + + /// The pen that was active at the start of drawing. This is restored at the end of drawing. + HGDIOBJ hPrevPen; + + /// The brush that was active at the start of drawing. + HGDIOBJ hPrevBrush; + + /// The brush color at the start of drawing. + COLORREF prevBrushColor; + + /// The previous font. + HGDIOBJ hPrevFont; + + /// The previous text background mode. + int prevBkMode; + + /// The previous text background color. + COLORREF prevBkColor; + + +} gdi_surface_data; + +typedef struct +{ + /// A handle to the Win32 font. + HFONT hFont; + + /// The font metrics for faster retrieval. We cache the metrics when the font is loaded. + dr2d_font_metrics metrics; + +} gdi_font_data; + +typedef struct +{ + /// A handle to the primary bitmap object. + HBITMAP hSrcBitmap; + + /// A pointer to the raw bitmap data. + unsigned int* pSrcBitmapData; + + /// A handle to the secondary bitmap object that we use when we need to change the color values of + /// the primary bitmap before drawing. + HBITMAP hIntermediateBitmap; + + /// A pointer to the raw data of the intermediate bitmap. + unsigned int* pIntermediateBitmapData; + + + /// A pointer to the mapped data. This is null when the image data is not mapped. + void* pMappedImageData; + + /// The mapping flags. + unsigned int mapAccessFlags; + +} gdi_image_data; + + +bool dr2d_on_create_context_gdi(dr2d_context* pContext, const void* pUserData); +void dr2d_on_delete_context_gdi(dr2d_context* pContext); +bool dr2d_on_create_surface_gdi(dr2d_surface* pSurface, float width, float height); +void dr2d_on_delete_surface_gdi(dr2d_surface* pSurface); +bool dr2d_on_create_font_gdi(dr2d_font* pFont); +void dr2d_on_delete_font_gdi(dr2d_font* pFont); +bool dr2d_on_create_image_gdi(dr2d_image* pImage, unsigned int stride, const void* pData); +void dr2d_on_delete_image_gdi(dr2d_image* pImage); + +void dr2d_begin_draw_gdi(dr2d_surface* pSurface); +void dr2d_end_draw_gdi(dr2d_surface* pSurface); +void dr2d_clear_gdi(dr2d_surface* pSurface, dr2d_color color); +void dr2d_draw_rect_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color); +void dr2d_draw_rect_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth); +void dr2d_draw_rect_with_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth, dr2d_color outlineColor); +void dr2d_draw_round_rect_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius); +void dr2d_draw_round_rect_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth); +void dr2d_draw_round_rect_with_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth, dr2d_color outlineColor); +void dr2d_draw_text_gdi(dr2d_surface* pSurface, dr2d_font* pFont, const char* text, size_t textSizeInBytes, float posX, float posY, dr2d_color color, dr2d_color backgroundColor); +void dr2d_draw_image_gdi(dr2d_surface* pSurface, dr2d_image* pImage, dr2d_draw_image_args* pArgs); +void dr2d_set_clip_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom); +void dr2d_get_clip_gdi(dr2d_surface* pSurface, float* pLeftOut, float* pTopOut, float* pRightOut, float* pBottomOut); + +dr2d_image_format dr2d_get_optimal_image_format_gdi(dr2d_context* pContext); +void* dr2d_map_image_data_gdi(dr2d_image* pImage, unsigned accessFlags); +void dr2d_unmap_image_data_gdi(dr2d_image* pImage); + +bool dr2d_get_font_metrics_gdi(dr2d_font* pFont, dr2d_font_metrics* pMetricsOut); +bool dr2d_get_glyph_metrics_gdi(dr2d_font* pFont, unsigned int utf32, dr2d_glyph_metrics* pGlyphMetrics); +bool dr2d_measure_string_gdi(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut); +bool dr2d_get_text_cursor_position_from_point_gdi(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut); +bool dr2d_get_text_cursor_position_from_char_gdi(dr2d_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut); + +/// Converts a char* to a wchar_t* string. +wchar_t* dr2d_to_wchar_gdi(dr2d_context* pContext, const char* text, size_t textSizeInBytes, unsigned int* characterCountOut); + +/// Converts a UTF-32 character to a UTF-16. +static int dr2d_utf32_to_utf16(unsigned int utf32, unsigned short utf16[2]) +{ + if (utf16 == NULL) { + return 0; + } + + if (utf32 < 0xD800 || (utf32 >= 0xE000 && utf32 <= 0xFFFF)) + { + utf16[0] = (unsigned short)utf32; + utf16[1] = 0; + return 1; + } + else + { + if (utf32 >= 0x10000 && utf32 <= 0x10FFFF) + { + utf16[0] = (unsigned short)(0xD7C0 + (unsigned short)(utf32 >> 10)); + utf16[1] = (unsigned short)(0xDC00 + (unsigned short)(utf32 & 0x3FF)); + return 2; + } + else + { + // Invalid. + utf16[0] = 0; + utf16[0] = 0; + return 0; + } + } +} + +dr2d_context* dr2d_create_context_gdi(HDC hDC) +{ + dr2d_drawing_callbacks callbacks; + callbacks.on_create_context = dr2d_on_create_context_gdi; + callbacks.on_delete_context = dr2d_on_delete_context_gdi; + callbacks.on_create_surface = dr2d_on_create_surface_gdi; + callbacks.on_delete_surface = dr2d_on_delete_surface_gdi; + callbacks.on_create_font = dr2d_on_create_font_gdi; + callbacks.on_delete_font = dr2d_on_delete_font_gdi; + callbacks.on_create_image = dr2d_on_create_image_gdi; + callbacks.on_delete_image = dr2d_on_delete_image_gdi; + + callbacks.begin_draw = dr2d_begin_draw_gdi; + callbacks.end_draw = dr2d_end_draw_gdi; + callbacks.clear = dr2d_clear_gdi; + callbacks.draw_rect = dr2d_draw_rect_gdi; + callbacks.draw_rect_outline = dr2d_draw_rect_outline_gdi; + callbacks.draw_rect_with_outline = dr2d_draw_rect_with_outline_gdi; + callbacks.draw_round_rect = dr2d_draw_round_rect_gdi; + callbacks.draw_round_rect_outline = dr2d_draw_round_rect_outline_gdi; + callbacks.draw_round_rect_with_outline = dr2d_draw_round_rect_with_outline_gdi; + callbacks.draw_text = dr2d_draw_text_gdi; + callbacks.draw_image = dr2d_draw_image_gdi; + callbacks.set_clip = dr2d_set_clip_gdi; + callbacks.get_clip = dr2d_get_clip_gdi; + + callbacks.get_optimal_image_format = dr2d_get_optimal_image_format_gdi; + callbacks.map_image_data = dr2d_map_image_data_gdi; + callbacks.unmap_image_data = dr2d_unmap_image_data_gdi; + + callbacks.get_font_metrics = dr2d_get_font_metrics_gdi; + callbacks.get_glyph_metrics = dr2d_get_glyph_metrics_gdi; + callbacks.measure_string = dr2d_measure_string_gdi; + callbacks.get_text_cursor_position_from_point = dr2d_get_text_cursor_position_from_point_gdi; + callbacks.get_text_cursor_position_from_char = dr2d_get_text_cursor_position_from_char_gdi; + + return dr2d_create_context(callbacks, sizeof(gdi_context_data), sizeof(gdi_surface_data), sizeof(gdi_font_data), sizeof(gdi_image_data), &hDC); +} + +dr2d_surface* dr2d_create_surface_gdi_HWND(dr2d_context* pContext, HWND hWnd) +{ + dr2d_surface* pSurface = dr2d_create_surface(pContext, 0, 0); + if (pSurface != NULL) { + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) { + pGDIData->hWnd = hWnd; + } + } + + return pSurface; +} + +dr2d_surface* dr2d_create_surface_gdi_HDC(dr2d_context* pContext, HDC hDC) +{ + dr2d_surface* pSurface = dr2d_create_surface(pContext, 0, 0); + if (pSurface != NULL) { + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) { + pGDIData->hDC = hDC; + } + } + + return pSurface; +} + +HDC dr2d_get_HDC(dr2d_surface* pSurface) +{ + if (pSurface != NULL) { + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) { + return pGDIData->hDC; + } + } + + return NULL; +} + +HBITMAP dr2d_get_HBITMAP(dr2d_surface* pSurface) +{ + if (pSurface != NULL) { + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) { + return pGDIData->hBitmap; + } + } + + return NULL; +} + +HFONT dr2d_get_HFONT(dr2d_font* pFont) +{ + gdi_font_data* pGDIData = (gdi_font_data*)dr2d_get_font_extra_data(pFont); + if (pGDIData == NULL) { + return NULL; + } + + return pGDIData->hFont; +} + + +bool dr2d_on_create_context_gdi(dr2d_context* pContext, const void* pUserData) +{ + assert(pContext != NULL); + + HDC hDC = NULL; + if (pUserData != NULL) { + hDC = *(HDC*)pUserData; + } + + bool ownsDC = false; + if (hDC == NULL) { + hDC = CreateCompatibleDC(GetDC(GetDesktopWindow())); + ownsDC = true; + } + + + // We need to create the DC that all of our rendering commands will be drawn to. + gdi_context_data* pGDIData = (gdi_context_data*)dr2d_get_context_extra_data(pContext); + if (pGDIData == NULL) { + return false; + } + + pGDIData->hDC = hDC; + if (pGDIData->hDC == NULL) { + return false; + } + + pGDIData->ownsDC = ownsDC; + + + // We want to use the advanced graphics mode so that GetTextExtentPoint32() performs the conversions for font rotation for us. + SetGraphicsMode(pGDIData->hDC, GM_ADVANCED); + + + pGDIData->wcharBuffer = NULL; + pGDIData->wcharBufferLength = 0; + + pGDIData->pGlyphCache = NULL; + pGDIData->glyphCacheSize = 0; + + return true; +} + +void dr2d_on_delete_context_gdi(dr2d_context* pContext) +{ + assert(pContext != NULL); + + gdi_context_data* pGDIData = (gdi_context_data*)dr2d_get_context_extra_data(pContext); + if (pGDIData != NULL) + { + free(pGDIData->pGlyphCache); + pGDIData->glyphCacheSize = 0; + + free(pGDIData->wcharBuffer); + pGDIData->wcharBuffer = 0; + pGDIData->wcharBufferLength = 0; + + if (pGDIData->ownsDC) { + DeleteDC(pGDIData->hDC); + } + + pGDIData->hDC = NULL; + } +} + +bool dr2d_on_create_surface_gdi(dr2d_surface* pSurface, float width, float height) +{ + assert(pSurface != NULL); + + gdi_context_data* pGDIContextData = (gdi_context_data*)dr2d_get_context_extra_data(pSurface->pContext); + if (pGDIContextData == NULL) { + return false; + } + + gdi_surface_data* pGDISurfaceData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDISurfaceData == NULL) { + return false; + } + + HDC hDC = pGDIContextData->hDC; + if (hDC == NULL) { + return false; + } + + HDC hIntermediateDC = CreateCompatibleDC(hDC); + if (hIntermediateDC == NULL) { + return false; + } + + pGDISurfaceData->hIntermediateDC = hIntermediateDC; + pGDISurfaceData->hWnd = NULL; + + + if (width != 0 && height != 0) + { + pGDISurfaceData->hDC = hDC; + + BITMAPINFO bmi; + ZeroMemory(&bmi, sizeof(bmi)); + bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); + bmi.bmiHeader.biWidth = (LONG)width; + bmi.bmiHeader.biHeight = (LONG)height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + pGDISurfaceData->hBitmap = CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, &pGDISurfaceData->pBitmapData, NULL, 0); + if (pGDISurfaceData->hBitmap == NULL) { + return false; + } + } + else + { + pGDISurfaceData->hBitmap = NULL; + pGDISurfaceData->hDC = NULL; + } + + + return true; +} + +void dr2d_on_delete_surface_gdi(dr2d_surface* pSurface) +{ + assert(pSurface != NULL); + + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) + { + DeleteObject(pGDIData->hBitmap); + pGDIData->hBitmap = NULL; + + DeleteDC(pGDIData->hIntermediateDC); + pGDIData->hIntermediateDC = NULL; + } +} + +bool dr2d_on_create_font_gdi(dr2d_font* pFont) +{ + assert(pFont != NULL); + + gdi_font_data* pGDIData = (gdi_font_data*)dr2d_get_font_extra_data(pFont); + if (pGDIData == NULL) { + return false; + } + + + LONG weightGDI = FW_REGULAR; + switch (pFont->weight) + { + case dr2d_font_weight_medium: weightGDI = FW_MEDIUM; break; + case dr2d_font_weight_thin: weightGDI = FW_THIN; break; + case dr2d_font_weight_extra_light: weightGDI = FW_EXTRALIGHT; break; + case dr2d_font_weight_light: weightGDI = FW_LIGHT; break; + case dr2d_font_weight_semi_bold: weightGDI = FW_SEMIBOLD; break; + case dr2d_font_weight_bold: weightGDI = FW_BOLD; break; + case dr2d_font_weight_extra_bold: weightGDI = FW_EXTRABOLD; break; + case dr2d_font_weight_heavy: weightGDI = FW_HEAVY; break; + default: break; + } + + BYTE slantGDI = FALSE; + if (pFont->slant == dr2d_font_slant_italic || pFont->slant == dr2d_font_slant_oblique) { + slantGDI = TRUE; + } + + + LOGFONTA logfont; + memset(&logfont, 0, sizeof(logfont)); + + + + logfont.lfHeight = -(LONG)pFont->size; + logfont.lfWeight = weightGDI; + logfont.lfItalic = slantGDI; + logfont.lfCharSet = DEFAULT_CHARSET; + //logfont.lfQuality = (pFont->size > 36) ? ANTIALIASED_QUALITY : CLEARTYPE_QUALITY; + logfont.lfQuality = (pFont->flags & DR2D_FONT_NO_CLEARTYPE) ? ANTIALIASED_QUALITY : CLEARTYPE_QUALITY; + logfont.lfEscapement = (LONG)pFont->rotation * 10; + logfont.lfOrientation = (LONG)pFont->rotation * 10; + + size_t familyLength = strlen(pFont->family); + memcpy(logfont.lfFaceName, pFont->family, (familyLength < 31) ? familyLength : 31); + + + pGDIData->hFont = CreateFontIndirectA(&logfont); + if (pGDIData->hFont == NULL) { + return false; + } + + + gdi_context_data* pGDIContextData = (gdi_context_data*)dr2d_get_context_extra_data(pFont->pContext); + if (pGDIContextData == NULL) { + return false; + } + + // Cache the font metrics. + HGDIOBJ hPrevFont = SelectObject(pGDIContextData->hDC, pGDIData->hFont); + { + TEXTMETRIC metrics; + GetTextMetrics(pGDIContextData->hDC, &metrics); + + pGDIData->metrics.ascent = metrics.tmAscent; + pGDIData->metrics.descent = metrics.tmDescent; + pGDIData->metrics.lineHeight = metrics.tmHeight; + + + const MAT2 transform = {{0, 1}, {0, 0}, {0, 0}, {0, 1}}; // <-- Identity matrix + + GLYPHMETRICS spaceMetrics; + DWORD bitmapBufferSize = GetGlyphOutlineW(pGDIContextData->hDC, ' ', GGO_NATIVE, &spaceMetrics, 0, NULL, &transform); + if (bitmapBufferSize == GDI_ERROR) { + pGDIData->metrics.spaceWidth = 4; + } else { + pGDIData->metrics.spaceWidth = spaceMetrics.gmCellIncX; + } + } + SelectObject(pGDIContextData->hDC, hPrevFont); + + + return true; +} + +void dr2d_on_delete_font_gdi(dr2d_font* pFont) +{ + assert(pFont != NULL); + + gdi_font_data* pGDIData = (gdi_font_data*)dr2d_get_font_extra_data(pFont); + if (pGDIData == NULL) { + return; + } + + DeleteObject(pGDIData->hFont); +} + +bool dr2d_on_create_image_gdi(dr2d_image* pImage, unsigned int stride, const void* pData) +{ + assert(pImage != NULL); + + gdi_image_data* pGDIData = (gdi_image_data*)dr2d_get_image_extra_data(pImage); + if (pGDIData == NULL) { + return false; + } + + gdi_context_data* pGDIContextData = (gdi_context_data*)dr2d_get_context_extra_data(pImage->pContext); + if (pGDIContextData == NULL) { + return false; + } + + + BITMAPINFO bmi; + ZeroMemory(&bmi, sizeof(bmi)); + bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); + bmi.bmiHeader.biWidth = pImage->width; + bmi.bmiHeader.biHeight = pImage->height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; // Only supporting 32-bit formats. + bmi.bmiHeader.biCompression = BI_RGB; + pGDIData->hSrcBitmap = CreateDIBSection(pGDIContextData->hDC, &bmi, DIB_RGB_COLORS, (void**)&pGDIData->pSrcBitmapData, NULL, 0); + if (pGDIData->hSrcBitmap == NULL) { + return false; + } + + pGDIData->hIntermediateBitmap = CreateDIBSection(pGDIContextData->hDC, &bmi, DIB_RGB_COLORS, (void**)&pGDIData->pIntermediateBitmapData, NULL, 0); + if (pGDIData->hIntermediateBitmap == NULL) { + DeleteObject(pGDIData->hSrcBitmap); + return false; + } + + + // We need to convert the data so it renders correctly with AlphaBlend(). + if (pData != NULL) { + dr2d__rgba8_bgra8_swap__premul(pData, pGDIData->pSrcBitmapData, pImage->width, pImage->height, stride, pImage->width*4); + } + + // Flush GDI to let it know we are finished with the bitmap object's data. + GdiFlush(); + + + pGDIData->pMappedImageData = NULL; + pGDIData->mapAccessFlags = 0; + + // At this point everything should be good. + return true; +} + +void dr2d_on_delete_image_gdi(dr2d_image* pImage) +{ + assert(pImage != NULL); + + gdi_image_data* pGDIData = (gdi_image_data*)dr2d_get_image_extra_data(pImage); + if (pGDIData == NULL) { + return; + } + + DeleteObject(pGDIData->hSrcBitmap); + pGDIData->hSrcBitmap = NULL; + + DeleteObject(pGDIData->hIntermediateBitmap); + pGDIData->hIntermediateBitmap = NULL; +} + + +void dr2d_begin_draw_gdi(dr2d_surface* pSurface) +{ + assert(pSurface != NULL); + + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) { + if (pGDIData->hWnd != NULL) { + pGDIData->hDC = BeginPaint(pGDIData->hWnd, &pGDIData->ps); + } else { + SelectObject(dr2d_get_HDC(pSurface), pGDIData->hBitmap); + } + + HDC hDC = dr2d_get_HDC(pSurface); + + pGDIData->hStockDCBrush = GetStockObject(DC_BRUSH); + pGDIData->hStockNullBrush = GetStockObject(NULL_BRUSH); + pGDIData->hStockDCPen = GetStockObject(DC_PEN); + pGDIData->hStockNullPen = GetStockObject(NULL_PEN); + + // Retrieve the defaults so they can be restored later. + pGDIData->hPrevPen = GetCurrentObject(hDC, OBJ_PEN); + pGDIData->hPrevBrush = GetCurrentObject(hDC, OBJ_BRUSH); + pGDIData->prevBrushColor = GetDCBrushColor(hDC); + pGDIData->hPrevFont = GetCurrentObject(hDC, OBJ_FONT); + pGDIData->prevBkMode = GetBkMode(hDC); + pGDIData->prevBkColor = GetBkColor(hDC); + } +} + +void dr2d_end_draw_gdi(dr2d_surface* pSurface) +{ + assert(pSurface != NULL); + + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) { + HDC hDC = dr2d_get_HDC(pSurface); + + SelectClipRgn(hDC, NULL); + + SelectObject(hDC, pGDIData->hPrevPen); + SelectObject(hDC, pGDIData->hPrevBrush); + SetDCBrushColor(hDC, pGDIData->prevBrushColor); + SelectObject(hDC, pGDIData->hPrevFont); + SetBkMode(hDC, pGDIData->prevBkMode); + SetBkColor(hDC, pGDIData->prevBkColor); + + if (pGDIData->hWnd != NULL) { + EndPaint(pGDIData->hWnd, &pGDIData->ps); + } + } +} + +void dr2d_clear_gdi(dr2d_surface* pSurface, dr2d_color color) +{ + assert(pSurface != NULL); + + dr2d_draw_rect_gdi(pSurface, 0, 0, pSurface->width, pSurface->height, color); +} + +void dr2d_draw_rect_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color) +{ + assert(pSurface != NULL); + + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) + { + HDC hDC = dr2d_get_HDC(pSurface); + + SelectObject(hDC, pGDIData->hStockNullPen); + SelectObject(hDC, pGDIData->hStockDCBrush); + SetDCBrushColor(hDC, RGB(color.r, color.g, color.b)); + + // Now draw the rectangle. The documentation for this says that the width and height is 1 pixel less when the pen is null. Therefore we will + // increase the width and height by 1 since we have got the pen set to null. + Rectangle(hDC, (int)left, (int)top, (int)right + 1, (int)bottom + 1); + } +} + +void dr2d_draw_rect_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth) +{ + assert(pSurface != NULL); + + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) + { + HDC hDC = dr2d_get_HDC(pSurface); + + SelectObject(hDC, pGDIData->hStockNullPen); + SelectObject(hDC, pGDIData->hStockDCBrush); + SetDCBrushColor(hDC, RGB(color.r, color.g, color.b)); + + // Now draw the rectangle. The documentation for this says that the width and height is 1 pixel less when the pen is null. Therefore we will + // increase the width and height by 1 since we have got the pen set to null. + + Rectangle(hDC, (int)left, (int)top, (int)(left + outlineWidth + 1), (int)(bottom + 1)); // Left. + Rectangle(hDC, (int)(right - outlineWidth), (int)top, (int)(right + 1), (int)(bottom + 1)); // Right. + Rectangle(hDC, (int)(left + outlineWidth), (int)top, (int)(right - outlineWidth + 1), (int)(top + outlineWidth + 1)); // Top + Rectangle(hDC, (int)(left + outlineWidth), (int)(bottom - outlineWidth), (int)(right - outlineWidth + 1), (int)(bottom + 1)); // Bottom + } +} + +void dr2d_draw_rect_with_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth, dr2d_color outlineColor) +{ + assert(pSurface != NULL); + + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) + { + HDC hDC = dr2d_get_HDC(pSurface); + + HPEN hPen = CreatePen(PS_SOLID | PS_INSIDEFRAME, (int)outlineWidth, RGB(outlineColor.r, outlineColor.g, outlineColor.b)); + if (hPen != NULL) + { + SelectObject(hDC, hPen); + SelectObject(hDC, pGDIData->hStockDCBrush); + SetDCBrushColor(hDC, RGB(color.r, color.g, color.b)); + + Rectangle(hDC, (int)left, (int)top, (int)right, (int)bottom); + + DeleteObject(hPen); + } + } +} + +void dr2d_draw_round_rect_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius) +{ + assert(pSurface != NULL); + + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) + { + HDC hDC = dr2d_get_HDC(pSurface); + + SelectObject(hDC, pGDIData->hStockNullPen); + SelectObject(hDC, pGDIData->hStockDCBrush); + SetDCBrushColor(hDC, RGB(color.r, color.g, color.b)); + + RoundRect(hDC, (int)left, (int)top, (int)right + 1, (int)bottom + 1, (int)(radius*2), (int)(radius*2)); + } +} + +void dr2d_draw_round_rect_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth) +{ + assert(pSurface != NULL); + + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) + { + HDC hDC = dr2d_get_HDC(pSurface); + + HPEN hPen = CreatePen(PS_SOLID | PS_INSIDEFRAME, (int)outlineWidth, RGB(color.r, color.g, color.b)); + if (hPen != NULL) + { + SelectObject(hDC, pGDIData->hStockNullBrush); + SelectObject(hDC, hPen); + + RoundRect(hDC, (int)left, (int)top, (int)right, (int)bottom, (int)(radius*2), (int)(radius*2)); + + DeleteObject(hPen); + } + } +} + +void dr2d_draw_round_rect_with_outline_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth, dr2d_color outlineColor) +{ + assert(pSurface != NULL); + + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) + { + HDC hDC = dr2d_get_HDC(pSurface); + + HPEN hPen = CreatePen(PS_SOLID | PS_INSIDEFRAME, (int)outlineWidth, RGB(outlineColor.r, outlineColor.g, outlineColor.b)); + if (hPen != NULL) + { + SelectObject(hDC, hPen); + SelectObject(hDC, pGDIData->hStockDCBrush); + SetDCBrushColor(hDC, RGB(color.r, color.g, color.b)); + + RoundRect(hDC, (int)left, (int)top, (int)right, (int)bottom, (int)(radius*2), (int)(radius*2)); + + DeleteObject(hPen); + } + } +} + +void dr2d_draw_text_gdi(dr2d_surface* pSurface, dr2d_font* pFont, const char* text, size_t textSizeInBytes, float posX, float posY, dr2d_color color, dr2d_color backgroundColor) +{ + gdi_font_data* pGDIFontData = (gdi_font_data*)dr2d_get_font_extra_data(pFont); + if (pGDIFontData == NULL) { + return; + } + + + HDC hDC = dr2d_get_HDC(pSurface); + + HFONT hFontGDI = pGDIFontData->hFont; + if (hFontGDI != NULL) + { + // We actually want to use the W version of TextOut because otherwise unicode doesn't work properly. + + unsigned int textWLength; + wchar_t* textW = dr2d_to_wchar_gdi(pSurface->pContext, text, textSizeInBytes, &textWLength); + if (textW != NULL) + { + SelectObject(hDC, hFontGDI); + + UINT options = 0; + RECT rect = {0, 0, 0, 0}; + + if (backgroundColor.a == 0) { + SetBkMode(hDC, TRANSPARENT); + } else { + SetBkMode(hDC, OPAQUE); + SetBkColor(hDC, RGB(backgroundColor.r, backgroundColor.g, backgroundColor.b)); + + // There is an issue with the way GDI draws the background of a string of text. When ClearType is enabled, the rectangle appears + // to be wider than it is supposed to be. As a result, drawing text right next to each other results in the most recent one being + // drawn slightly on top of the previous one. To fix this we need to use ExtTextOut() with the ETO_CLIPPED parameter enabled. + options |= ETO_CLIPPED; + + SIZE textSize = {0, 0}; + GetTextExtentPoint32W(hDC, textW, textWLength, &textSize); + rect.left = (LONG)posX; + rect.top = (LONG)posY; + rect.right = (LONG)(posX + textSize.cx); + rect.bottom = (LONG)(posY + textSize.cy); + } + + SetTextColor(hDC, RGB(color.r, color.g, color.b)); + + ExtTextOutW(hDC, (int)posX, (int)posY, options, &rect, textW, textWLength, NULL); + } + } +} + +void dr2d_draw_image_gdi(dr2d_surface* pSurface, dr2d_image* pImage, dr2d_draw_image_args* pArgs) +{ + gdi_image_data* pGDIImageData = (gdi_image_data*)dr2d_get_image_extra_data(pImage); + if (pGDIImageData == NULL) { + return; + } + + gdi_surface_data* pGDISurfaceData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDISurfaceData == NULL) { + return; + } + + bool drawFlipped = false; + HBITMAP hSrcBitmap = NULL; + + if ((pArgs->options & DR2D_IMAGE_DRAW_BACKGROUND) == 0 && (pArgs->options & DR2D_IMAGE_HINT_NO_ALPHA) != 0 && pArgs->foregroundTint.r == 255 && pArgs->foregroundTint.g == 255 && pArgs->foregroundTint.b == 255) + { + // Fast path. No tint, no background, no alpha. + hSrcBitmap = pGDIImageData->hSrcBitmap; + drawFlipped = true; + } + else + { + // Slow path. We need to manually change the color values of the intermediate bitmap and use that as the source when drawing it. This is also flipped. + unsigned int* pSrcBitmapData = pGDIImageData->pSrcBitmapData; + unsigned int* pDstBitmapData = pGDIImageData->pIntermediateBitmapData; + for (unsigned int iRow = 0; iRow < pImage->height; ++iRow) + { + for (unsigned int iCol = 0; iCol < pImage->width; ++iCol) + { + unsigned int srcTexel = *(pSrcBitmapData + (iRow * pImage->width) + iCol); + unsigned int* dstTexel = (pDstBitmapData + ((pImage->height - iRow - 1) * pImage->width) + iCol); + + unsigned int srcTexelA = (srcTexel & 0xFF000000) >> 24; + unsigned int srcTexelR = (unsigned int)(((srcTexel & 0x00FF0000) >> 16) * (pArgs->foregroundTint.r / 255.0f)); + unsigned int srcTexelG = (unsigned int)(((srcTexel & 0x0000FF00) >> 8) * (pArgs->foregroundTint.g / 255.0f)); + unsigned int srcTexelB = (unsigned int)(((srcTexel & 0x000000FF) >> 0) * (pArgs->foregroundTint.b / 255.0f)); + + if (srcTexelR > 255) srcTexelR = 255; + if (srcTexelG > 255) srcTexelG = 255; + if (srcTexelB > 255) srcTexelB = 255; + + if ((pArgs->options & DR2D_IMAGE_DRAW_BACKGROUND) != 0) + { + srcTexelB += (unsigned int)(pArgs->backgroundColor.b * ((255 - srcTexelA) / 255.0f)); + srcTexelG += (unsigned int)(pArgs->backgroundColor.g * ((255 - srcTexelA) / 255.0f)); + srcTexelR += (unsigned int)(pArgs->backgroundColor.r * ((255 - srcTexelA) / 255.0f)); + srcTexelA = 0xFF; + } + + *dstTexel = (srcTexelR << 16) | (srcTexelG << 8) | (srcTexelB << 0) | (srcTexelA << 24); + } + } + + // Flush GDI to let it know we are finished with the bitmap object's data. + GdiFlush(); + + // If we have drawn the background manually we don't need to do an alpha blend. + if ((pArgs->options & DR2D_IMAGE_DRAW_BACKGROUND) != 0) { + pArgs->options |= DR2D_IMAGE_HINT_NO_ALPHA; + } + + hSrcBitmap = pGDIImageData->hIntermediateBitmap; + } + + HGDIOBJ hPrevBitmap = SelectObject(pGDISurfaceData->hIntermediateDC, hSrcBitmap); + if ((pArgs->options & DR2D_IMAGE_HINT_NO_ALPHA) != 0) + { + if (drawFlipped) { + StretchBlt(pGDISurfaceData->hDC, (int)pArgs->dstX, (int)pArgs->dstY + (int)pArgs->dstHeight - 1, (int)pArgs->dstWidth, -(int)pArgs->dstHeight, pGDISurfaceData->hIntermediateDC, (int)pArgs->srcX, (int)pArgs->srcY, (int)pArgs->srcWidth, (int)pArgs->srcHeight, SRCCOPY); + } else { + StretchBlt(pGDISurfaceData->hDC, (int)pArgs->dstX, (int)pArgs->dstY, (int)pArgs->dstWidth, (int)pArgs->dstHeight, pGDISurfaceData->hIntermediateDC, (int)pArgs->srcX, (int)pArgs->srcY, (int)pArgs->srcWidth, (int)pArgs->srcHeight, SRCCOPY); + } + } + else + { + assert(drawFlipped == false); // <-- Error if this is hit. + BLENDFUNCTION blend = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; + AlphaBlend(pGDISurfaceData->hDC, (int)pArgs->dstX, (int)pArgs->dstY, (int)pArgs->dstWidth, (int)pArgs->dstHeight, pGDISurfaceData->hIntermediateDC, (int)pArgs->srcX, (int)pArgs->srcY, (int)pArgs->srcWidth, (int)pArgs->srcHeight, blend); + } + SelectObject(pGDISurfaceData->hIntermediateDC, hPrevBitmap); +} + +void dr2d_set_clip_gdi(dr2d_surface* pSurface, float left, float top, float right, float bottom) +{ + assert(pSurface != NULL); + + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) + { + HDC hDC = dr2d_get_HDC(pSurface); + + SelectClipRgn(hDC, NULL); + IntersectClipRect(hDC, (int)left, (int)top, (int)right, (int)bottom); + } +} + +void dr2d_get_clip_gdi(dr2d_surface* pSurface, float* pLeftOut, float* pTopOut, float* pRightOut, float* pBottomOut) +{ + assert(pSurface != NULL); + + gdi_surface_data* pGDIData = (gdi_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pGDIData != NULL) + { + RECT rect; + GetClipBox(dr2d_get_HDC(pSurface), &rect); + + if (pLeftOut != NULL) { + *pLeftOut = (float)rect.left; + } + if (pTopOut != NULL) { + *pTopOut = (float)rect.top; + } + if (pRightOut != NULL) { + *pRightOut = (float)rect.right; + } + if (pBottomOut != NULL) { + *pBottomOut = (float)rect.bottom; + } + } +} + + +dr2d_image_format dr2d_get_optimal_image_format_gdi(dr2d_context* pContext) +{ + (void)pContext; + return dr2d_image_format_bgra8; +} + +void* dr2d_map_image_data_gdi(dr2d_image* pImage, unsigned accessFlags) +{ + assert(pImage != NULL); + + gdi_image_data* pGDIImageData = (gdi_image_data*)dr2d_get_image_extra_data(pImage); + if (pGDIImageData == NULL) { + return NULL; + } + + assert(pGDIImageData->pMappedImageData == NULL); // This function should never be called while the image is already mapped. + + pGDIImageData->mapAccessFlags = accessFlags; + + if (pImage->format == dr2d_image_format_bgra8) + { + pGDIImageData->pMappedImageData = pGDIImageData->pSrcBitmapData; + } + else + { + pGDIImageData->pMappedImageData = malloc(pImage->width * pImage->height * 4); + if (pGDIImageData->pMappedImageData == NULL) { + return NULL; + } + + for (unsigned int iRow = 0; iRow < pImage->height; ++iRow) + { + const unsigned int iRowSrc = pImage->height - (iRow + 1); + const unsigned int iRowDst = iRow; + + for (unsigned int iCol = 0; iCol < pImage->width; ++iCol) + { + unsigned int srcTexel = ((const unsigned int*)(pGDIImageData->pSrcBitmapData))[ (iRowSrc * pImage->width) + iCol]; + unsigned int* dstTexel = (( unsigned int*)(pGDIImageData->pMappedImageData)) + (iRowDst * pImage->width) + iCol; + + unsigned int srcTexelA = (srcTexel & 0xFF000000) >> 24; + unsigned int srcTexelB = (srcTexel & 0x00FF0000) >> 16; + unsigned int srcTexelG = (srcTexel & 0x0000FF00) >> 8; + unsigned int srcTexelR = (srcTexel & 0x000000FF) >> 0; + + srcTexelB = (unsigned int)(srcTexelB * (srcTexelA / 255.0f)); + srcTexelG = (unsigned int)(srcTexelG * (srcTexelA / 255.0f)); + srcTexelR = (unsigned int)(srcTexelR * (srcTexelA / 255.0f)); + + *dstTexel = (srcTexelR << 16) | (srcTexelG << 8) | (srcTexelB << 0) | (srcTexelA << 24); + } + } + } + + return pGDIImageData->pMappedImageData; +} + +void dr2d_unmap_image_data_gdi(dr2d_image* pImage) +{ + assert(pImage != NULL); + + gdi_image_data* pGDIImageData = (gdi_image_data*)dr2d_get_image_extra_data(pImage); + if (pGDIImageData == NULL) { + return; + } + + assert(pGDIImageData->pMappedImageData != NULL); // This function should never be called while the image is not mapped. + + if (pImage->format == dr2d_image_format_bgra8) + { + // It's in the native format, so just do a flush. + GdiFlush(); + } + else + { + // Update the actual image data if applicable. + if (pGDIImageData->mapAccessFlags & DR2D_WRITE) { + dr2d__rgba8_bgra8_swap__premul(pGDIImageData->pMappedImageData, pGDIImageData->pSrcBitmapData, pImage->width, pImage->height, pImage->width*4, pImage->width*4); + } + + free(pGDIImageData->pMappedImageData); + } + + pGDIImageData->pMappedImageData = NULL; + pGDIImageData->mapAccessFlags = 0; +} + + +bool dr2d_get_font_metrics_gdi(dr2d_font* pFont, dr2d_font_metrics* pMetricsOut) +{ + assert(pFont != NULL); + assert(pMetricsOut != NULL); + + gdi_font_data* pGDIFontData = (gdi_font_data*)dr2d_get_font_extra_data(pFont); + if (pGDIFontData == NULL) { + return false; + } + + *pMetricsOut = pGDIFontData->metrics; + return true; +} + +bool dr2d_get_glyph_metrics_gdi(dr2d_font* pFont, unsigned int utf32, dr2d_glyph_metrics* pGlyphMetrics) +{ + assert(pFont != NULL); + assert(pGlyphMetrics != NULL); + + gdi_font_data* pGDIFontData = (gdi_font_data*)dr2d_get_font_extra_data(pFont); + if (pGDIFontData == NULL) { + return false; + } + + gdi_context_data* pGDIContextData = (gdi_context_data*)dr2d_get_context_extra_data(pFont->pContext); + if (pGDIContextData == NULL) { + return false; + } + + + SelectObject(pGDIContextData->hDC, pGDIFontData->hFont); + + + const MAT2 transform = {{0, 1}, {0, 0}, {0, 0}, {0, 1}}; // <-- Identity matrix + + unsigned short utf16[2]; + int utf16Len = dr2d_utf32_to_utf16(utf32, utf16); + + WCHAR glyphIndices[2]; + + GCP_RESULTSW glyphResults; + ZeroMemory(&glyphResults, sizeof(glyphResults)); + glyphResults.lStructSize = sizeof(glyphResults); + glyphResults.lpGlyphs = glyphIndices; + glyphResults.nGlyphs = 2; + if (GetCharacterPlacementW(pGDIContextData->hDC, (LPCWSTR)utf16, utf16Len, 0, &glyphResults, 0) != 0) + { + GLYPHMETRICS metrics; + DWORD bitmapBufferSize = GetGlyphOutlineW(pGDIContextData->hDC, glyphIndices[0], GGO_NATIVE | GGO_GLYPH_INDEX, &metrics, 0, NULL, &transform); + if (bitmapBufferSize != GDI_ERROR) + { + pGlyphMetrics->width = metrics.gmBlackBoxX; + pGlyphMetrics->height = metrics.gmBlackBoxY; + pGlyphMetrics->originX = metrics.gmptGlyphOrigin.x; + pGlyphMetrics->originY = metrics.gmptGlyphOrigin.y; + pGlyphMetrics->advanceX = metrics.gmCellIncX; + pGlyphMetrics->advanceY = metrics.gmCellIncY; + + return true; + } + } + + return false; +} + +bool dr2d_measure_string_gdi(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut) +{ + assert(pFont != NULL); + + gdi_font_data* pGDIFontData = (gdi_font_data*)dr2d_get_font_extra_data(pFont); + if (pGDIFontData == NULL) { + return false; + } + + gdi_context_data* pGDIContextData = (gdi_context_data*)dr2d_get_context_extra_data(pFont->pContext); + if (pGDIContextData == NULL) { + return false; + } + + + SelectObject(pGDIContextData->hDC, pGDIFontData->hFont); + + unsigned int textWLength; + wchar_t* textW = dr2d_to_wchar_gdi(pFont->pContext, text, textSizeInBytes, &textWLength); + if (textW != NULL) + { + SIZE sizeWin32; + if (GetTextExtentPoint32W(pGDIContextData->hDC, textW, textWLength, &sizeWin32)) + { + if (pWidthOut != NULL) { + *pWidthOut = (float)sizeWin32.cx; + } + if (pHeightOut != NULL) { + *pHeightOut = (float)sizeWin32.cy; + } + + return true; + } + } + + return false; +} + +bool dr2d_get_text_cursor_position_from_point_gdi(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut) +{ + bool successful = false; + + assert(pFont != NULL); + + gdi_font_data* pGDIFontData = (gdi_font_data*)dr2d_get_font_extra_data(pFont); + if (pGDIFontData == NULL) { + return false; + } + + gdi_context_data* pGDIContextData = (gdi_context_data*)dr2d_get_context_extra_data(pFont->pContext); + if (pGDIContextData == NULL) { + return false; + } + + + SelectObject(pGDIContextData->hDC, pGDIFontData->hFont); + + + GCP_RESULTSW results; + ZeroMemory(&results, sizeof(results)); + results.lStructSize = sizeof(results); + results.nGlyphs = (UINT)textSizeInBytes; + + unsigned int textWLength; + wchar_t* textW = dr2d_to_wchar_gdi(pFont->pContext, text, textSizeInBytes, &textWLength); + if (textW != NULL) + { + if (results.nGlyphs > pGDIContextData->glyphCacheSize) { + free(pGDIContextData->pGlyphCache); + pGDIContextData->pGlyphCache = (int*)malloc(sizeof(int) * results.nGlyphs); + pGDIContextData->glyphCacheSize = results.nGlyphs; + } + + results.lpCaretPos = pGDIContextData->pGlyphCache; + if (results.lpCaretPos != NULL) + { + if (GetCharacterPlacementW(pGDIContextData->hDC, textW, results.nGlyphs, (int)maxWidth, &results, GCP_MAXEXTENT | GCP_USEKERNING) != 0) + { + float textCursorPosX = 0; + unsigned int iChar; + for (iChar = 0; iChar < results.nGlyphs; ++iChar) + { + float charBoundsLeft = charBoundsLeft = (float)results.lpCaretPos[iChar]; + float charBoundsRight = 0; + if (iChar < results.nGlyphs - 1) { + charBoundsRight = (float)results.lpCaretPos[iChar + 1]; + } else { + charBoundsRight = maxWidth; + } + + if (inputPosX >= charBoundsLeft && inputPosX <= charBoundsRight) + { + // The input position is somewhere on top of this character. If it's positioned on the left side of the character, set the output + // value to the character at iChar. Otherwise it should be set to the character at iChar + 1. + float charBoundsRightHalf = charBoundsLeft + ceilf(((charBoundsRight - charBoundsLeft) / 2.0f)); + if (inputPosX <= charBoundsRightHalf) { + break; + } else { + textCursorPosX = charBoundsRight; + iChar += 1; + break; + } + } + + textCursorPosX = charBoundsRight; + } + + if (pTextCursorPosXOut) { + *pTextCursorPosXOut = textCursorPosX; + } + if (pCharacterIndexOut) { + *pCharacterIndexOut = iChar; + } + + successful = true; + } + } + } + + return successful; +} + +bool dr2d_get_text_cursor_position_from_char_gdi(dr2d_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut) +{ + bool successful = false; + + assert(pFont != NULL); + + gdi_font_data* pGDIFontData = (gdi_font_data*)dr2d_get_font_extra_data(pFont); + if (pGDIFontData == NULL) { + return false; + } + + gdi_context_data* pGDIContextData = (gdi_context_data*)dr2d_get_context_extra_data(pFont->pContext); + if (pGDIContextData == NULL) { + return false; + } + + + SelectObject(pGDIContextData->hDC, pGDIFontData->hFont); + + + GCP_RESULTSW results; + ZeroMemory(&results, sizeof(results)); + results.lStructSize = sizeof(results); + results.nGlyphs = (DWORD)(characterIndex + 1); + + unsigned int textWLength; + wchar_t* textW = dr2d_to_wchar_gdi(pFont->pContext, text, (int)results.nGlyphs, &textWLength); + if (textW != NULL) + { + if (results.nGlyphs > pGDIContextData->glyphCacheSize) { + free(pGDIContextData->pGlyphCache); + pGDIContextData->pGlyphCache = (int*)malloc(sizeof(int) * results.nGlyphs); + pGDIContextData->glyphCacheSize = results.nGlyphs; + } + + results.lpCaretPos = pGDIContextData->pGlyphCache; + if (results.lpCaretPos != NULL) + { + if (GetCharacterPlacementW(pGDIContextData->hDC, textW, results.nGlyphs, 0, &results, GCP_USEKERNING) != 0) + { + if (pTextCursorPosXOut) { + *pTextCursorPosXOut = (float)results.lpCaretPos[characterIndex]; + } + + successful = true; + } + } + } + + return successful; +} + + +wchar_t* dr2d_to_wchar_gdi(dr2d_context* pContext, const char* text, size_t textSizeInBytes, unsigned int* characterCountOut) +{ + if (pContext == NULL || text == NULL) { + return NULL; + } + + gdi_context_data* pGDIData = (gdi_context_data*)dr2d_get_context_extra_data(pContext); + if (pGDIData == NULL) { + return NULL; + } + + int wcharCount = 0; + + + // We first try to copy the string into the already-allocated buffer. If it fails we fall back to the slow path which requires + // two conversions. + if (pGDIData->wcharBuffer == NULL) { + goto fallback; + } + + wcharCount = MultiByteToWideChar(CP_UTF8, 0, text, (int)textSizeInBytes, pGDIData->wcharBuffer, pGDIData->wcharBufferLength); + if (wcharCount != 0) { + if (characterCountOut) *characterCountOut = wcharCount; + return pGDIData->wcharBuffer; + } + + + +fallback:; + wcharCount = MultiByteToWideChar(CP_UTF8, 0, text, (int)textSizeInBytes, NULL, 0); + if (wcharCount == 0) { + return NULL; + } + + if (pGDIData->wcharBufferLength < (unsigned int)wcharCount + 1) { + free(pGDIData->wcharBuffer); + pGDIData->wcharBuffer = (wchar_t*)malloc(sizeof(wchar_t) * (wcharCount + 1)); + pGDIData->wcharBufferLength = wcharCount + 1; + } + + wcharCount = MultiByteToWideChar(CP_UTF8, 0, text, (int)textSizeInBytes, pGDIData->wcharBuffer, pGDIData->wcharBufferLength); + if (wcharCount == 0) { + return NULL; + } + + + if (characterCountOut != NULL) { + *characterCountOut = wcharCount; + } + + return pGDIData->wcharBuffer; +} + +#endif // GDI + + +///////////////////////////////////////////////////////////////// +// +// CAIRO 2D API +// +///////////////////////////////////////////////////////////////// +#ifndef DR2D_NO_CAIRO + +typedef struct +{ + cairo_surface_t* pCairoSurface; + cairo_t* pCairoContext; + + float clipRectLeft; + float clipRectTop; + float clipRectRight; + float clipRectBottom; + +} cairo_surface_data; + +typedef struct +{ + cairo_font_face_t* pFace; + cairo_scaled_font_t* pFont; + + // The font metrics. This is initialized when the font is created. + dr2d_font_metrics metrics; + +} cairo_font_data; + +typedef struct +{ + /// Images in Cairo are implemented as surfaces. + cairo_surface_t* pCairoSurface; + + /// A pointer to the raw data. + unsigned char* pData; + +} cairo_image_data; + +bool dr2d_on_create_context_cairo(dr2d_context* pContext, const void* pUserData); +void dr2d_on_delete_context_cairo(dr2d_context* pContext); +bool dr2d_on_create_surface_cairo(dr2d_surface* pSurface, float width, float height); +void dr2d_on_delete_surface_cairo(dr2d_surface* pSurface); +bool dr2d_on_create_font_cairo(dr2d_font* pFont); +void dr2d_on_delete_font_cairo(dr2d_font* pFont); +bool dr2d_on_create_image_cairo(dr2d_image* pImage, unsigned int stride, const void* pData); +void dr2d_on_delete_image_cairo(dr2d_image* pImage); + +void dr2d_begin_draw_cairo(dr2d_surface* pSurface); +void dr2d_end_draw_cairo(dr2d_surface* pSurface); +void dr2d_clear_cairo(dr2d_surface* pSurface, dr2d_color color); +void dr2d_draw_rect_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color); +void dr2d_draw_rect_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth); +void dr2d_draw_rect_with_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth, dr2d_color outlineColor); +void dr2d_draw_round_rect_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius); +void dr2d_draw_round_rect_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth); +void dr2d_draw_round_rect_with_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth, dr2d_color outlineColor); +void dr2d_draw_text_cairo(dr2d_surface* pSurface, dr2d_font* pFont, const char* text, size_t textSizeInBytes, float posX, float posY, dr2d_color color, dr2d_color backgroundColor); +void dr2d_draw_image_cairo(dr2d_surface* pSurface, dr2d_image* pImage, dr2d_draw_image_args* pArgs); +void dr2d_set_clip_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom); +void dr2d_get_clip_cairo(dr2d_surface* pSurface, float* pLeftOut, float* pTopOut, float* pRightOut, float* pBottomOut); + +dr2d_image_format dr2d_get_optimal_image_format_cairo(dr2d_context* pContext); +void* dr2d_map_image_data_cairo(dr2d_image* pImage, unsigned accessFlags); +void dr2d_unmap_image_data_cairo(dr2d_image* pImage); + +bool dr2d_get_font_metrics_cairo(dr2d_font* pFont, dr2d_font_metrics* pMetricsOut); +bool dr2d_get_glyph_metrics_cairo(dr2d_font* pFont, unsigned int utf32, dr2d_glyph_metrics* pGlyphMetrics); +bool dr2d_measure_string_cairo(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut); +bool dr2d_get_text_cursor_position_from_point_cairo(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut); +bool dr2d_get_text_cursor_position_from_char_cairo(dr2d_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut); + + +dr2d_context* dr2d_create_context_cairo() +{ + dr2d_drawing_callbacks callbacks; + callbacks.on_create_context = dr2d_on_create_context_cairo; + callbacks.on_delete_context = dr2d_on_delete_context_cairo; + callbacks.on_create_surface = dr2d_on_create_surface_cairo; + callbacks.on_delete_surface = dr2d_on_delete_surface_cairo; + callbacks.on_create_font = dr2d_on_create_font_cairo; + callbacks.on_delete_font = dr2d_on_delete_font_cairo; + callbacks.on_create_image = dr2d_on_create_image_cairo; + callbacks.on_delete_image = dr2d_on_delete_image_cairo; + + callbacks.begin_draw = dr2d_begin_draw_cairo; + callbacks.end_draw = dr2d_end_draw_cairo; + callbacks.clear = dr2d_clear_cairo; + callbacks.draw_rect = dr2d_draw_rect_cairo; + callbacks.draw_rect_outline = dr2d_draw_rect_outline_cairo; + callbacks.draw_rect_with_outline = dr2d_draw_rect_with_outline_cairo; + callbacks.draw_round_rect = dr2d_draw_round_rect_cairo; + callbacks.draw_round_rect_outline = dr2d_draw_round_rect_outline_cairo; + callbacks.draw_round_rect_with_outline = dr2d_draw_round_rect_with_outline_cairo; + callbacks.draw_text = dr2d_draw_text_cairo; + callbacks.draw_image = dr2d_draw_image_cairo; + callbacks.set_clip = dr2d_set_clip_cairo; + callbacks.get_clip = dr2d_get_clip_cairo; + + callbacks.map_image_data = dr2d_map_image_data_cairo; + callbacks.unmap_image_data = dr2d_unmap_image_data_cairo; + + callbacks.get_font_metrics = dr2d_get_font_metrics_cairo; + callbacks.get_glyph_metrics = dr2d_get_glyph_metrics_cairo; + callbacks.measure_string = dr2d_measure_string_cairo; + callbacks.get_text_cursor_position_from_point = dr2d_get_text_cursor_position_from_point_cairo; + callbacks.get_text_cursor_position_from_char = dr2d_get_text_cursor_position_from_char_cairo; + + + return dr2d_create_context(callbacks, 0, sizeof(cairo_surface_data), sizeof(cairo_font_data), sizeof(cairo_image_data), NULL); +} + +dr2d_surface* dr2d_create_surface_cairo(dr2d_context* pContext, cairo_t* cr) +{ + if (cr == NULL) { + return NULL; + } + + dr2d_surface* pSurface = dr2d_create_surface(pContext, 0, 0); + if (pSurface != NULL) { + cairo_surface_data* pCairoData = (cairo_surface_data*)dr2d_get_surface_extra_data(pSurface); + if (pCairoData != NULL) { + pCairoData->pCairoContext = cairo_reference(cr); + pCairoData->pCairoSurface = cairo_surface_reference(cairo_get_target(cr)); + } + } + + return pSurface; +} + +cairo_surface_t* dr2d_get_cairo_surface_t(dr2d_surface* pSurface) +{ + cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface); + if (pCairoData != NULL) { + return pCairoData->pCairoSurface; + } + + return NULL; +} + +cairo_t* dr2d_get_cairo_t(dr2d_surface* pSurface) +{ + cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface); + if (pCairoData != NULL) { + return pCairoData->pCairoContext; + } + + return NULL; +} + + +bool dr2d_on_create_context_cairo(dr2d_context* pContext, const void* pUserData) +{ + assert(pContext != NULL); + (void)pContext; + (void)pUserData; + + return true; +} + +void dr2d_on_delete_context_cairo(dr2d_context* pContext) +{ + assert(pContext != NULL); + (void)pContext; +} + +bool dr2d_on_create_surface_cairo(dr2d_surface* pSurface, float width, float height) +{ + assert(pSurface != NULL); + + cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface); + if (pCairoData == NULL) { + return false; + } + + if (width != 0 && height != 0) { + pCairoData->pCairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, (int)width, (int)height); + if (pCairoData->pCairoSurface == NULL) { + return false; + } + + pCairoData->pCairoContext = cairo_create(pCairoData->pCairoSurface); + if (pCairoData->pCairoContext == NULL) { + cairo_surface_destroy(pCairoData->pCairoSurface); + return false; + } + } else { + pCairoData->pCairoSurface = NULL; + pCairoData->pCairoContext = NULL; + } + + + return true; +} + +void dr2d_on_delete_surface_cairo(dr2d_surface* pSurface) +{ + assert(pSurface != NULL); + + cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface); + if (pCairoData != NULL) + { + cairo_destroy(pCairoData->pCairoContext); + cairo_surface_destroy(pCairoData->pCairoSurface); + } +} + +bool dr2d_on_create_font_cairo(dr2d_font* pFont) +{ + cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont); + if (pCairoFont == NULL) { + return false; + } + + cairo_font_slant_t cairoSlant = CAIRO_FONT_SLANT_NORMAL; + if (pFont->slant == dr2d_font_slant_italic) { + cairoSlant = CAIRO_FONT_SLANT_ITALIC; + } else if (pFont->slant == dr2d_font_slant_oblique) { + cairoSlant = CAIRO_FONT_SLANT_OBLIQUE; + } + + cairo_font_weight_t cairoWeight = CAIRO_FONT_WEIGHT_NORMAL; + if (pFont->weight == dr2d_font_weight_bold || pFont->weight == dr2d_font_weight_semi_bold || pFont->weight == dr2d_font_weight_extra_bold || pFont->weight == dr2d_font_weight_heavy) { + cairoWeight = CAIRO_FONT_WEIGHT_BOLD; + } + + pCairoFont->pFace = cairo_toy_font_face_create(pFont->family, cairoSlant, cairoWeight); + if (pCairoFont->pFace == NULL) { + return false; + } + + cairo_matrix_t fontMatrix; + cairo_matrix_init_scale(&fontMatrix, (double)pFont->size, (double)pFont->size); + cairo_matrix_rotate(&fontMatrix, pFont->rotation * (3.14159265 / 180.0)); + + cairo_matrix_t ctm; + cairo_matrix_init_identity(&ctm); + + cairo_font_options_t* options = cairo_font_options_create(); + cairo_font_options_set_antialias(options, CAIRO_ANTIALIAS_SUBPIXEL); // TODO: Control this with option flags in pFont. + + pCairoFont->pFont = cairo_scaled_font_create(pCairoFont->pFace, &fontMatrix, &ctm, options); + if (pCairoFont->pFont == NULL) { + cairo_font_face_destroy(pCairoFont->pFace); + return false; + } + + + // Metrics. + cairo_font_extents_t fontMetrics; + cairo_scaled_font_extents(pCairoFont->pFont, &fontMetrics); + + pCairoFont->metrics.ascent = fontMetrics.ascent; + pCairoFont->metrics.descent = fontMetrics.descent; + //pCairoFont->metrics.lineHeight = fontMetrics.height; + pCairoFont->metrics.lineHeight = fontMetrics.ascent + fontMetrics.descent; + + // The width of a space needs to be retrieved via glyph metrics. + const char space[] = " "; + cairo_text_extents_t spaceMetrics; + cairo_scaled_font_text_extents(pCairoFont->pFont, space, &spaceMetrics); + pCairoFont->metrics.spaceWidth = spaceMetrics.x_advance; + + return true; +} + +void dr2d_on_delete_font_cairo(dr2d_font* pFont) +{ + cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont); + if (pCairoFont == NULL) { + return; + } + + cairo_scaled_font_destroy(pCairoFont->pFont); + cairo_font_face_destroy(pCairoFont->pFace); +} + +bool dr2d_on_create_image_cairo(dr2d_image* pImage, unsigned int stride, const void* pData) +{ + cairo_image_data* pCairoImage = dr2d_get_image_extra_data(pImage); + if (pCairoImage == NULL) { + return false; + } + + size_t dataSize = pImage->height * pImage->width * 4; + pCairoImage->pData = malloc(dataSize); + if (pCairoImage->pData == NULL) { + return false; + } + + if (pData != NULL) + { + for (unsigned int iRow = 0; iRow < pImage->height; ++iRow) + { + const unsigned int iRowSrc = iRow; //pImage->height - (iRow + 1); + const unsigned int iRowDst = iRow; + + for (unsigned int iCol = 0; iCol < pImage->width; ++iCol) + { + unsigned int srcTexel = ((const unsigned int*)(pData ))[ (iRowSrc * (stride/4)) + iCol]; + unsigned int* dstTexel = (( unsigned int*)(pCairoImage->pData)) + (iRowDst * pImage->width) + iCol; + + unsigned int srcTexelA = (srcTexel & 0xFF000000) >> 24; + unsigned int srcTexelB = (srcTexel & 0x00FF0000) >> 16; + unsigned int srcTexelG = (srcTexel & 0x0000FF00) >> 8; + unsigned int srcTexelR = (srcTexel & 0x000000FF) >> 0; + + srcTexelB = (unsigned int)(srcTexelB * (srcTexelA / 255.0f)); + srcTexelG = (unsigned int)(srcTexelG * (srcTexelA / 255.0f)); + srcTexelR = (unsigned int)(srcTexelR * (srcTexelA / 255.0f)); + + *dstTexel = (srcTexelR << 16) | (srcTexelG << 8) | (srcTexelB << 0) | (srcTexelA << 24); + } + } + } + + pCairoImage->pCairoSurface = cairo_image_surface_create_for_data(pCairoImage->pData, CAIRO_FORMAT_ARGB32, (int)pImage->width, (int)pImage->height, (int)pImage->width*4); + if (pCairoImage->pCairoSurface == NULL) { + free(pCairoImage->pData); + return false; + } + + return true; +} + +void dr2d_on_delete_image_cairo(dr2d_image* pImage) +{ + cairo_image_data* pCairoImage = dr2d_get_image_extra_data(pImage); + if (pCairoImage == NULL) { + return; + } + + cairo_surface_destroy(pCairoImage->pCairoSurface); + free(pCairoImage->pData); +} + + +void dr2d_begin_draw_cairo(dr2d_surface* pSurface) +{ + assert(pSurface != NULL); + + cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface); + if (pCairoData == NULL) { + return; + } + + cairo_set_antialias(pCairoData->pCairoContext, CAIRO_ANTIALIAS_NONE); +} + +void dr2d_end_draw_cairo(dr2d_surface* pSurface) +{ + assert(pSurface != NULL); + + cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface); + if (pCairoData == NULL) { + return; + } + + cairo_set_antialias(pCairoData->pCairoContext, CAIRO_ANTIALIAS_DEFAULT); +} + +void dr2d_clear_cairo(dr2d_surface* pSurface, dr2d_color color) +{ + // TODO: I forget... is this supposed to ignore the current clip? If so, this needs to be changed so that + // the clip is reset first then restored afterwards. + dr2d_draw_rect_cairo(pSurface, 0, 0, dr2d_get_surface_width(pSurface), dr2d_get_surface_height(pSurface), color); +} + +void dr2d_draw_rect_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color) +{ + assert(pSurface != NULL); + + cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface); + if (pCairoData != NULL) + { + cairo_set_source_rgba(pCairoData->pCairoContext, color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0); + cairo_rectangle(pCairoData->pCairoContext, left, top, (right - left), (bottom - top)); + cairo_fill(pCairoData->pCairoContext); + } +} + +void dr2d_draw_rect_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth) +{ + cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface); + if (pCairoData == NULL) { + return; + } + + cairo_t* cr = pCairoData->pCairoContext; + + cairo_set_source_rgba(cr, color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0); + + // We do this as 4 separate rectangles. + cairo_rectangle(cr, left, top, outlineWidth, bottom - top); // Left + cairo_fill(cr); + cairo_rectangle(cr, right - outlineWidth, top, outlineWidth, bottom - top); // Right + cairo_fill(cr); + cairo_rectangle(cr, left + outlineWidth, top, right - left - (outlineWidth*2), outlineWidth); // Top + cairo_fill(cr); + cairo_rectangle(cr, left + outlineWidth, bottom - outlineWidth, right - left - (outlineWidth*2), outlineWidth); // Bottom + cairo_fill(cr); +} + +void dr2d_draw_rect_with_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float outlineWidth, dr2d_color outlineColor) +{ + dr2d_draw_rect_cairo(pSurface, left + outlineWidth, top + outlineWidth, right - outlineWidth, bottom - outlineWidth, color); + dr2d_draw_rect_outline_cairo(pSurface, left, top, right, bottom, outlineColor, outlineWidth); +} + +void dr2d_draw_round_rect_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius) +{ + // FIXME: This does not draw rounded corners. + (void)radius; + + dr2d_draw_rect_cairo(pSurface, left, top, right, bottom, color); +} + +void dr2d_draw_round_rect_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth) +{ + // FIXME: This does not draw rounded corners. + (void)radius; + + dr2d_draw_rect_outline_cairo(pSurface, left, top, right, bottom, color, outlineWidth); +} + +void dr2d_draw_round_rect_with_outline_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom, dr2d_color color, float radius, float outlineWidth, dr2d_color outlineColor) +{ + // FIXME: This does not draw rounded corners. + (void)radius; + + dr2d_draw_rect_with_outline_cairo(pSurface, left, top, right, bottom, color, outlineWidth, outlineColor); +} + +void dr2d_draw_text_cairo(dr2d_surface* pSurface, dr2d_font* pFont, const char* text, size_t textSizeInBytes, float posX, float posY, dr2d_color color, dr2d_color backgroundColor) +{ + cairo_surface_data* pCairoSurface = dr2d_get_surface_extra_data(pSurface); + if (pCairoSurface == NULL) { + return; + } + + cairo_t* cr = pCairoSurface->pCairoContext; + + + cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont); + if (pCairoFont == NULL) { + return; + } + + // Cairo expends null terminated strings, however the input string is not guaranteed to be null terminated. + char* textNT; + if (textSizeInBytes != (size_t)-1) { + textNT = malloc(textSizeInBytes + 1); + memcpy(textNT, text, textSizeInBytes); + textNT[textSizeInBytes] = '\0'; + } else { + textNT = (char*)text; + } + + + cairo_set_scaled_font(cr, pCairoFont->pFont); + + + + // Background. + cairo_text_extents_t textMetrics; + cairo_text_extents(cr, textNT, &textMetrics); + cairo_set_source_rgba(cr, backgroundColor.r / 255.0, backgroundColor.g / 255.0, backgroundColor.b / 255.0, backgroundColor.a / 255.0); + cairo_rectangle(cr, posX, posY, textMetrics.x_advance, pCairoFont->metrics.lineHeight); + cairo_fill(cr); + + + // Text. + cairo_move_to(cr, posX, posY + pCairoFont->metrics.ascent); + cairo_set_source_rgba(cr, color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0); + cairo_show_text(cr, textNT); + + + if (textNT != text) { + free(textNT); + } +} + +void dr2d_draw_image_cairo(dr2d_surface* pSurface, dr2d_image* pImage, dr2d_draw_image_args* pArgs) +{ + cairo_surface_data* pCairoSurface = dr2d_get_surface_extra_data(pSurface); + if (pCairoSurface == NULL) { + return; + } + + cairo_image_data* pCairoImage = dr2d_get_image_extra_data(pImage); + if (pCairoImage == NULL) { + return; + } + + cairo_t* cr = pCairoSurface->pCairoContext; + + cairo_save(cr); + cairo_translate(cr, pArgs->dstX, pArgs->dstY); + + // Background. + if ((pArgs->options & DR2D_IMAGE_DRAW_BACKGROUND) != 0) + { + cairo_set_source_rgba(cr, pArgs->backgroundColor.r / 255.0, pArgs->backgroundColor.g / 255.0, pArgs->backgroundColor.b / 255.0, pArgs->backgroundColor.a / 255.0); + cairo_rectangle(cr, 0, 0, pArgs->dstWidth, pArgs->dstHeight); + cairo_fill(cr); + } + + if (pArgs->foregroundTint.r == 255 && pArgs->foregroundTint.g == 255 && pArgs->foregroundTint.b == 255 && pArgs->foregroundTint.a == 255) { + cairo_scale(cr, pArgs->dstWidth / pArgs->srcWidth, pArgs->dstHeight / pArgs->srcHeight); + cairo_set_source_surface(cr, pCairoImage->pCairoSurface, pArgs->srcX, pArgs->srcY); + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); + cairo_paint(cr); + } else { + // Slower path. The image needs to be tinted. We create a temporary image for this. + // NOTE: This is incorrect. It's just a temporary solution until I figure out a better way. + cairo_surface_t* pTempImageSurface = cairo_surface_create_similar_image(pCairoImage->pCairoSurface, CAIRO_FORMAT_ARGB32, + cairo_image_surface_get_width(pCairoImage->pCairoSurface), cairo_image_surface_get_height(pCairoImage->pCairoSurface)); + if (pTempImageSurface != NULL) { + cairo_t* cr2 = cairo_create(pTempImageSurface); + + cairo_set_operator(cr2, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface(cr2, pCairoImage->pCairoSurface, 0, 0); + cairo_pattern_set_filter(cairo_get_source(cr2), CAIRO_FILTER_NEAREST); + cairo_paint(cr2); + + // Tint. + cairo_set_operator(cr2, CAIRO_OPERATOR_ATOP); + cairo_set_source_rgba(cr2, pArgs->foregroundTint.r / 255.0, pArgs->foregroundTint.g / 255.0, pArgs->foregroundTint.b / 255.0, 1); + cairo_rectangle(cr2, 0, 0, pArgs->dstWidth, pArgs->dstHeight); + cairo_fill(cr2); + + /*cairo_set_operator(cr2, CAIRO_OPERATOR_MULTIPLY); + cairo_set_source_surface(cr2, pCairoImage->pCairoSurface, 0, 0); + cairo_pattern_set_filter(cairo_get_source(cr2), CAIRO_FILTER_NEAREST); + cairo_paint(cr2);*/ + + // Draw the temporary surface onto the main surface. + cairo_scale(cr, pArgs->dstWidth / pArgs->srcWidth, pArgs->dstHeight / pArgs->srcHeight); + cairo_set_source_surface(cr, pTempImageSurface, pArgs->srcX, pArgs->srcY); + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST); + //cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_paint(cr); + + cairo_destroy(cr2); + cairo_surface_destroy(pTempImageSurface); + } + } + + cairo_restore(cr); +} + +void dr2d_set_clip_cairo(dr2d_surface* pSurface, float left, float top, float right, float bottom) +{ + cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface); + if (pCairoData == NULL) { + return; + } + + pCairoData->clipRectLeft = left; + pCairoData->clipRectTop = top; + pCairoData->clipRectRight = right; + pCairoData->clipRectBottom = bottom; + + cairo_reset_clip(pCairoData->pCairoContext); + cairo_rectangle(pCairoData->pCairoContext, left, top, right - left, bottom - top); + cairo_clip(pCairoData->pCairoContext); +} + +void dr2d_get_clip_cairo(dr2d_surface* pSurface, float* pLeftOut, float* pTopOut, float* pRightOut, float* pBottomOut) +{ + (void)pSurface; + (void)pLeftOut; + (void)pTopOut; + (void)pRightOut; + (void)pBottomOut; + + cairo_surface_data* pCairoData = dr2d_get_surface_extra_data(pSurface); + if (pCairoData == NULL) { + return; + } + + if (pLeftOut) { *pLeftOut = pCairoData->clipRectLeft; } + if (pTopOut) { *pTopOut = pCairoData->clipRectTop; } + if (pRightOut) { *pRightOut = pCairoData->clipRectRight; } + if (pBottomOut) { *pBottomOut = pCairoData->clipRectBottom; } +} + + +dr2d_image_format dr2d_get_optimal_image_format_cairo(dr2d_context* pContext) +{ + (void)pContext; + return dr2d_image_format_argb8; +} + +void* dr2d_map_image_data_cairo(dr2d_image* pImage, unsigned accessFlags) +{ + (void)pImage; + (void)accessFlags; + return NULL; +} + +void dr2d_unmap_image_data_cairo(dr2d_image* pImage) +{ + (void)pImage; +} + + +bool dr2d_get_font_metrics_cairo(dr2d_font* pFont, dr2d_font_metrics* pMetricsOut) +{ + cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont); + if (pCairoFont == NULL) { + return false; + } + + if (pMetricsOut) { + *pMetricsOut = pCairoFont->metrics; + } + + return true; +} + +static size_t dr2d__utf32_to_utf8(unsigned int utf32, char* utf8, size_t utf8Size) +{ + // NOTE: This function is untested. + + size_t utf8ByteCount = 0; + if (utf32 < 0x80) { + utf8ByteCount = 1; + } else if (utf32 < 0x800) { + utf8ByteCount = 2; + } else if (utf32 < 0x10000) { + utf8ByteCount = 3; + } else if (utf32 < 0x110000) { + utf8ByteCount = 4; + } + + if (utf8ByteCount > utf8Size) { + if (utf8 != NULL && utf8Size > 0) { + utf8[0] = '\0'; + } + return 0; + } + + utf8 += utf8ByteCount; + if (utf8ByteCount < utf8Size) { + utf8[0] = '\0'; // Null terminate. + } + + const unsigned char firstByteMark[7] = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC}; + switch (utf8ByteCount) + { + case 4: *--utf8 = (char)((utf32 | 0x80) & 0xBF); utf32 >>= 6; + case 3: *--utf8 = (char)((utf32 | 0x80) & 0xBF); utf32 >>= 6; + case 2: *--utf8 = (char)((utf32 | 0x80) & 0xBF); utf32 >>= 6; + case 1: *--utf8 = (char)(utf32 | firstByteMark[utf8ByteCount]); + default: break; + } + + return utf8ByteCount; +} + +bool dr2d_get_glyph_metrics_cairo(dr2d_font* pFont, unsigned int utf32, dr2d_glyph_metrics* pGlyphMetrics) +{ + cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont); + if (pCairoFont == NULL) { + return false; + } + + // The UTF-32 code point needs to be converted to a UTF-8 character. + char utf8[16]; + size_t utf8len = dr2d__utf32_to_utf8(utf32, utf8, sizeof(utf8)); // This will null-terminate. + if (utf8len == 0) { + return false; // Error converting UTF-32 to UTF-8. + } + + + cairo_text_extents_t glyphExtents; + cairo_scaled_font_text_extents(pCairoFont->pFont, utf8, &glyphExtents); + + if (pGlyphMetrics) + { + pGlyphMetrics->width = glyphExtents.width; + pGlyphMetrics->height = glyphExtents.height; + pGlyphMetrics->originX = glyphExtents.x_bearing; + pGlyphMetrics->originY = glyphExtents.y_bearing; + pGlyphMetrics->advanceX = glyphExtents.x_advance; + pGlyphMetrics->advanceY = glyphExtents.y_advance; + } + + return true; +} + +bool dr2d_measure_string_cairo(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut) +{ + cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont); + if (pCairoFont == NULL) { + return false; + } + + + // Cairo expends null terminated strings, however the input string is not guaranteed to be null terminated. + char* textNT; + if (textSizeInBytes != (size_t)-1) { + textNT = malloc(textSizeInBytes + 1); + if (textNT == NULL) { + return false; + } + memcpy(textNT, text, textSizeInBytes); + textNT[textSizeInBytes] = '\0'; + } else { + textNT = (char*)text; + } + + + cairo_text_extents_t textMetrics; + cairo_scaled_font_text_extents(pCairoFont->pFont, textNT, &textMetrics); + + if (pWidthOut) { + *pWidthOut = textMetrics.x_advance; + } + if (pHeightOut) { + //*pHeightOut = textMetrics.height; + *pHeightOut = pCairoFont->metrics.ascent + pCairoFont->metrics.descent; + } + + + if (textNT != text) { + free(textNT); + } + + return true; +} + +bool dr2d_get_text_cursor_position_from_point_cairo(dr2d_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut) +{ + cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont); + if (pCairoFont == NULL) { + return false; + } + + cairo_glyph_t* pGlyphs = NULL; + int glyphCount = 0; + cairo_status_t result = cairo_scaled_font_text_to_glyphs(pCairoFont->pFont, 0, 0, text, textSizeInBytes, &pGlyphs, &glyphCount, NULL, NULL, NULL); + if (result != CAIRO_STATUS_SUCCESS) { + return false; + } + + float cursorPosX = 0; + int charIndex = 0; + + // We just iterate over each glyph until we find the one sitting under . + float runningPosX = 0; + for (int iGlyph = 0; iGlyph < glyphCount; ++iGlyph) + { + cairo_text_extents_t glyphMetrics; + cairo_scaled_font_glyph_extents(pCairoFont->pFont, pGlyphs + iGlyph, 1, &glyphMetrics); + + float glyphLeft = runningPosX; + float glyphRight = glyphLeft + glyphMetrics.x_advance; + + // Are we sitting on top of inputPosX? + if (inputPosX >= glyphLeft && inputPosX <= glyphRight) + { + float glyphHalf = glyphLeft + ceilf(((glyphRight - glyphLeft) / 2.0f)); + if (inputPosX <= glyphHalf) { + cursorPosX = glyphLeft; + charIndex = iGlyph; + } else { + cursorPosX = glyphRight; + charIndex = iGlyph + 1; + } + + break; + } + else + { + // Have we moved past maxWidth? + if (glyphRight > maxWidth) + { + cursorPosX = maxWidth; + charIndex = iGlyph; + break; + } + else + { + runningPosX = glyphRight; + + cursorPosX = runningPosX; + charIndex = iGlyph; + } + } + } + + cairo_glyph_free(pGlyphs); + + if (pTextCursorPosXOut) { + *pTextCursorPosXOut = cursorPosX; + } + if (pCharacterIndexOut) { + *pCharacterIndexOut = charIndex; + } + + return true; +} + +bool dr2d_get_text_cursor_position_from_char_cairo(dr2d_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut) +{ + cairo_font_data* pCairoFont = dr2d_get_font_extra_data(pFont); + if (pCairoFont == NULL) { + return false; + } + + cairo_glyph_t* pGlyphs = NULL; + int glyphCount = 0; + cairo_status_t result = cairo_scaled_font_text_to_glyphs(pCairoFont->pFont, 0, 0, text, -1, &pGlyphs, &glyphCount, NULL, NULL, NULL); + if (result != CAIRO_STATUS_SUCCESS) { + return false; + } + + float cursorPosX = 0; + + // We just iterate over each glyph until we find the one sitting under . + for (int iGlyph = 0; iGlyph < glyphCount; ++iGlyph) + { + if (iGlyph == (int)characterIndex) { + break; + } + + cairo_text_extents_t glyphMetrics; + cairo_scaled_font_glyph_extents(pCairoFont->pFont, pGlyphs + iGlyph, 1, &glyphMetrics); + + cursorPosX += glyphMetrics.x_advance; + } + + cairo_glyph_free(pGlyphs); + + if (pTextCursorPosXOut) { + *pTextCursorPosXOut = cursorPosX; + } + + return true; +} +#endif // Cairo +#endif + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ diff --git a/source/engine/thirdparty/dr_libs/old/dr_audio.h b/source/engine/thirdparty/dr_libs/old/dr_audio.h new file mode 100644 index 0000000..87abde1 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/old/dr_audio.h @@ -0,0 +1,5307 @@ +// Audio playback, recording and mixing. Public domain. See "unlicense" statement at the end of this file. +// dr_audio - v0.0 (unversioned) - Release Date TBD +// +// David Reid - mackron@gmail.com + +// !!!!! THIS IS WORK IN PROGRESS !!!!! + +// USAGE +// +// dr_audio is a single-file library. To use it, do something like the following in one .c file. +// #define DR_AUDIO_IMPLEMENTATION +// #include "dr_audio.h" +// +// You can then #include this file in other parts of the program as you would with any other header file. +// +// dr_audio supports loading and decoding of WAV, FLAC and Vorbis streams via dr_wav, dr_flac and stb_vorbis respectively. To enable +// these all you need to do is #include "dr_audio.h" _after_ #include "dr_wav.h", #include "dr_flac.h" and #include "stb_vorbis.c" in +// the implementation file, like so: +// +// #define DR_WAV_IMPLEMENTATION +// #include "dr_wav.h" +// +// #define DR_FLAC_IMPLEMENTATION +// #include "dr_flac.h" +// +// #define STB_VORBIS_IMPLEMENTATION +// #include "stb_vorbis.c" +// +// #define DR_AUDIO_IMPLEMENTATION +// #include "dr_audio.h" +// +// dr_wav, dr_flac and stb_vorbis are entirely optional, and dr_audio will automatically detect the ones that are available without +// any additional intervention on your part. +// +// +// dr_audio has a layered API with different levels of flexibility vs simplicity. An example of the high level API follows: +// +// dra_device* pDevice; +// dra_result result = dra_device_create(NULL, dra_device_type_playback, &pDevice); +// if (result != DRA_RESULT_SUCCESS) { +// return -1; +// } +// +// dra_voice* pVoice; +// result = dra_voice_create_from_file(pDevice, "my_song.flac", &pVoice); +// if (result != DRA_RESULT_SUCCESS) { +// return -1; +// } +// +// dra_voice_play(pVoice, DR_FALSE); +// +// ... +// +// dra_voice_delete(pVoice); +// dra_device_delete(pDevice); +// +// +// An example of the low level API: +// +// dra_context context; +// dra_result result = dra_context_init(&context); // Initializes the backend (DirectSound/ALSA) +// if (result != DRA_RESULT_SUCCESS) { +// return -1; +// } +// +// unsigned int deviceID = 0; // Default device +// unsigned int channels = 2; // Stereo +// unsigned int sampleRate = 48000; +// unsigned int latencyInMilliseconds = 0; // 0 will default to DR_AUDIO_DEFAULT_LATENCY. +// dra_device device; +// result = dra_device_init_ex(&context, dra_device_type_playback, deviceID, channels, sampleRate, latencyInMilliseconds, &device); +// if (result != DRA_RESULT_SUCCESS) { +// return -1; +// } +// +// dra_voice* pVoice; +// dra_result result = dra_voice_create(pDevice, dra_format_f32, channels, sampleRate, voiceBufferSizeInBytes, pVoiceSampleData, &pVoice); +// if (result != DRA_RESULT_SUCCESS) { +// return -1; +// } +// +// ... +// +// // Sometime later you may need to update the data inside the voice's internal buffer... It's your job to handle +// // synchronization - have fun! Hint: use playback events at fixed locations to know when a region of the buffer +// // is available for updating. +// float* pVoiceData = (float*)dra_voice_get_buffer_ptr_by_sample(pVoice, sampleOffset); +// if (pVoiceData == NULL) { +// return -1; +// } +// +// memcpy(pVoiceData, pNewVoiceData, sizeof(float) * samplesToCopy); +// +// ... +// +// dra_voice_delete(pVoice); +// dra_device_uninit(pDevice); +// dra_context_uninit(pContext); +// +// In the above example the voice and device are configured to use the same number of channels and sample rate, however they are +// allowed to differ, in which case dr_audio will automatically convert the data. Note that sample rate conversion is currently +// very low quality. +// +// To handle streaming buffers, you can attach a callback that's fired when a voice's playback position reaches a certain point. +// Usually you would set this to the middle and end of the buffer, filling the previous half with new data. Use the +// dra_voice_add_playback_event() API for this. +// +// The playback position of a voice can be retrieved and set with dra_voice_get_playback_position() and dra_voice_set_playback_position() +// respctively. The playback is specified in samples. dra_voice_get_playback_position() will always return a value which is a multiple +// of the channel count. dra_voice_set_playback_position() will round the specified sample index to a multiple of the channel count. +// +// +// dr_audio has support for submixing which basically allows you to control volume (and in the future, effects) for groups of sounds +// which would typically be organized into categories. An abvious example would be in games where you may want to have separate volume +// controls for music, voices, special effects, etc. To do submixing, all you need to do is create a mixer. There is a master mixer +// associated with every device, and all newly created mixers are a child of the master mixer, by default: +// +// dra_mixer* pMusicMixer; +// dra_result result = dra_mixer_create(pDevice, &pMusicMixer); +// if (result != DRA_RESULT_SUCCESS) { +// return -1; +// } +// +// // At this point pMusicMixer is a child of the device's master mixer. To change the hierarchy, just do something like this: +// dra_mixer_attach_submixer(pSomeOtherMixer, pMusicMixer); +// +// // A voice is attached to the master mixer by default, but you can attach it to a different mixer like this: +// dra_mixer_attach_voice(pMusicMixer, pMyMusicVoice); +// +// // Control the volume of the mixer... +// dra_mixer_set_volume(pMusicMixer, 0.5f); // <-- The volume is linear, so this is half volume. +// +// +// +// dr_audio includes an abstraction for audio decoding. Built-in support is included for WAV, FLAC and Vorbis streams: +// +// dra_decoder decoder; +// if (dra_decoder_open_file(&decoder, filePath) != DRA_RESULT_SUCCESS) { +// return -1; +// } +// +// dr_uint64 samplesRead = dra_decoder_read_f32(&decoder, samplesToRead, pSamples); +// update_my_voice_data(pVoice, pSamples, samplesRead); +// +// dra_decoder_close(&decoder); +// +// Decoders can be opened/initialized from files, a block of memory, or application-defined callbacks. +// +// +// +// OPTIONS +// #define these options before including this file. +// +// #define DR_AUDIO_NO_DSOUND +// Disables the DirectSound backend. Note that this is the only backend for the Windows platform. +// +// #define DR_AUDIO_NO_ALSA +// Disables the ALSA backend. Note that this is the only backend for the Linux platform. +// +// #define DR_AUDIO_NO_STDIO +// Disables any functions that use stdio, such as dra_sound_create_from_file(). + + +#ifndef dr_audio2_h +#define dr_audio2_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifndef DR_SIZED_TYPES_DEFINED +#define DR_SIZED_TYPES_DEFINED +#if defined(_MSC_VER) && _MSC_VER < 1600 +typedef signed char dr_int8; +typedef unsigned char dr_uint8; +typedef signed short dr_int16; +typedef unsigned short dr_uint16; +typedef signed int dr_int32; +typedef unsigned int dr_uint32; +typedef signed __int64 dr_int64; +typedef unsigned __int64 dr_uint64; +#else +#include +typedef int8_t dr_int8; +typedef uint8_t dr_uint8; +typedef int16_t dr_int16; +typedef uint16_t dr_uint16; +typedef int32_t dr_int32; +typedef uint32_t dr_uint32; +typedef int64_t dr_int64; +typedef uint64_t dr_uint64; +#endif +typedef dr_int8 dr_bool8; +typedef dr_int32 dr_bool32; +#define DR_TRUE 1 +#define DR_FALSE 0 +#endif + +#ifndef DR_AUDIO_MAX_CHANNEL_COUNT +#define DR_AUDIO_MAX_CHANNEL_COUNT 16 +#endif + +#ifndef DR_AUDIO_MAX_EVENT_COUNT +#define DR_AUDIO_MAX_EVENT_COUNT 16 +#endif + +#define DR_AUDIO_EVENT_ID_STOP 0xFFFFFFFFFFFFFFFFULL +#define DR_AUDIO_EVENT_ID_PLAY 0xFFFFFFFFFFFFFFFEULL + +typedef int dra_result; +#define DRA_RESULT_SUCCESS 0 +#define DRA_RESULT_UNKNOWN_ERROR -1 +#define DRA_RESULT_INVALID_ARGS -2 +#define DRA_RESULT_OUT_OF_MEMORY -3 +#define DRA_RESULT_FAILED_TO_OPEN_FILE -4 +#define DRA_RESULT_NO_BACKEND -1024 +#define DRA_RESULT_NO_BACKEND_DEVICE -1025 +#define DRA_RESULT_NO_DECODER -1026 +#define DRA_FAILED(result) ((result) != 0) +#define DRA_SUCCEEDED(result) ((result) == 0) + +#define DRA_MIXER_FLAG_PAUSED (1 << 0) + +typedef enum +{ + dra_device_type_playback = 0, + dra_device_type_capture +} dra_device_type; + +typedef enum +{ + dra_format_u8 = 0, + dra_format_s16, + dra_format_s24, + dra_format_s32, + dra_format_f32, + dra_format_default = dra_format_f32 +} dra_format; + +typedef enum +{ + dra_src_algorithm_none, + dra_src_algorithm_linear, +} dra_src_algorithm; + +// dra_thread_event_type is used internally for thread management. +typedef enum +{ + dra_thread_event_type_none, + dra_thread_event_type_terminate, + dra_thread_event_type_play +} dra_thread_event_type; + +typedef struct dra_backend dra_backend; +typedef struct dra_backend_device dra_backend_device; + +typedef struct dra_context dra_context; +typedef struct dra_device dra_device; +typedef struct dra_mixer dra_mixer; +typedef struct dra_voice dra_voice; + +typedef void* dra_thread; +typedef void* dra_mutex; +typedef void* dra_semaphore; + +typedef void (* dra_event_proc) (dr_uint64 eventID, void* pUserData); +typedef void (* dra_samples_processed_proc)(dra_device* pDevice, const size_t sampleCount, const float* pSamples, void* pUserData); + +typedef struct +{ + dr_uint64 id; + void* pUserData; + dr_uint64 sampleIndex; + dra_event_proc proc; + dra_voice* pVoice; + dr_bool32 hasBeenSignaled; +} dra__event; + +typedef struct +{ + size_t firstEvent; + size_t eventCount; + size_t eventBufferSize; + dra__event* pEvents; + dra_mutex lock; +} dra__event_queue; + +struct dra_context +{ + dra_backend* pBackend; +}; + +struct dra_device +{ + // The context that created and owns this device. + dra_context* pContext; + + // The backend device. This is used to connect the cross-platform front-end with the backend. + dra_backend_device* pBackendDevice; + + // The main mutex for handling thread-safety. + dra_mutex mutex; + + // The main thread. For playback devices, this is the thread that waits for fragments to finish processing an then + // mixes and pushes new audio data to the hardware buffer for playback. + dra_thread thread; + + // The semaphore used by the main thread to determine whether or not an event is waiting to be processed. + dra_semaphore threadEventSem; + + // The event type of the most recent event. + dra_thread_event_type nextThreadEventType; + + // TODO: Make these booleans flags. + + // Whether or not the device owns the context. This basically just means whether or not the device was created with a null + // context and needs to delete the context itself when the device is deleted. + dr_bool32 ownsContext; + + // Whether or not the device is being closed. This is used by the thread to determine if it needs to terminate. When + // dra_device_close() is called, this flag will be set and threadEventSem released. The thread will then see this as it's + // signal to terminate. + dr_bool32 isClosed; + + // Whether or not the device is currently playing. When at least one voice is playing, this will be DR_TRUE. When there + // are no voices playing, this will be set to DR_FALSE and the background thread will sit dormant until another voice + // starts playing or the device is closed. + dr_bool32 isPlaying; + + // Whether or not the device should stop on the next fragment. This is used for stopping playback of devices that + // have no voice's playing. + dr_bool32 stopOnNextFragment; + + + // The master mixer. This is the one and only top-level mixer. + dra_mixer* pMasterMixer; + + // The number of voices currently being played. This is used to determine whether or not the device should be placed + // into a dormant state when nothing is being played. + size_t playingVoicesCount; + + + // When a playback event is scheduled it is added to this queue. Events are not posted straight away, but are instead + // placed in a queue for processing later at specific times to ensure the event is posted AFTER the device has actually + // played the sample the event is set for. + dra__event_queue eventQueue; + + // The function to call when a segment of samples has been processed. This is only really needed for capture devices, + // but can also be used to keep track of all of the audio data that's passed through a playback device. This is called + // as the read pointer moves passed each segment and again for the leftover partial segment that'll occur when the + // device is stopped. + dra_samples_processed_proc onSamplesProcessed; + void* pUserDataForOnSamplesProcessed; + + + // The number of channels being used by the device. + unsigned int channels; + + // The sample rate in seconds. + unsigned int sampleRate; +}; + +struct dra_mixer +{ + // The device that created and owns this mixer. + dra_device* pDevice; + + // Whether or not the mixer is paused. + dr_uint32 flags; + + + // The parent mixer. + dra_mixer* pParentMixer; + + // The first child mixer. + dra_mixer* pFirstChildMixer; + + // The last child mixer. + dra_mixer* pLastChildMixer; + + // The next sibling mixer. + dra_mixer* pNextSiblingMixer; + + // The previous sibling mixer. + dra_mixer* pPrevSiblingMixer; + + + // The first voice attached to the mixer. + dra_voice* pFirstVoice; + + // The last voice attached to the mixer. + dra_voice* pLastVoice; + + + // The volume of the buffer as a linear scale. A value of 0.5 means the sound is at half volume. There is no hard + // limit on the volume, however going beyond 1 may introduce clipping. + float linearVolume; + + + // Mixers output the results of the final mix into a buffer referred to as the staging buffer. A parent mixer will + // use the staging buffer when it mixes the results of it's submixers. This is an offset of pData. + float* pStagingBuffer; + + // A pointer to the buffer containing the sample data of the buffer currently being mixed, as floating point values. + // This is an offset of pData. + float* pNextSamplesToMix; + + + // The sample data for pStagingBuffer and pNextSamplesToMix. + float pData[1]; +}; + +struct dra_voice +{ + // The device that created and owns this voice. + dra_device* pDevice; + + // The mixer the voice is attached to. Should never be null. The mixer doesn't "own" the voice - the voice + // is simply attached to it. + dra_mixer* pMixer; + + + // The next voice in the linked list of voices attached to the mixer. + dra_voice* pNextVoice; + + // The previous voice in the linked list of voices attached to the mixer. + dra_voice* pPrevVoice; + + + // The format of the audio data contained within this voice. + dra_format format; + + // The number of channels. + unsigned int channels; + + // The sample rate in seconds. + unsigned int sampleRate; + + + // The volume of the voice as a linear scale. A value of 0.5 means the sound is at half volume. There is no hard + // limit on the volume, however going beyond 1 may introduce clipping. + float linearVolume; + + + + // Whether or not the voice is currently playing. + dr_bool32 isPlaying; + + // Whether or not the voice is currently looping. Whether or not the voice is looping is determined by the last + // call to dra_voice_play(). + dr_bool32 isLooping; + + + // The total number of frames in the voice. + dr_uint64 frameCount; + + // The current read position, in frames. An important detail with this is that it's based on the sample rate of the + // device, not the voice. + dr_uint64 currentReadPos; + + + // A buffer for storing converted frames. This is used by dra_voice__next_frame(). As frames are converted to + // floats, that are placed into this buffer. + float convertedFrame[DR_AUDIO_MAX_CHANNEL_COUNT]; + + + // Data for sample rate conversion. Different SRC algorithms will use different data, which will be stored in their + // own structure. + struct + { + // The sample rate conversion algorithm to use. + dra_src_algorithm algorithm; + + union + { + struct + { + dr_uint64 prevFrameIndex; + float prevFrame[DR_AUDIO_MAX_CHANNEL_COUNT]; + float nextFrame[DR_AUDIO_MAX_CHANNEL_COUNT]; + } linear; + } data; + } src; + + + // The number of playback notification events. This does not include the stop and play events. + size_t playbackEventCount; + + // A pointer to the list of playback events. + dra__event playbackEvents[DR_AUDIO_MAX_EVENT_COUNT]; + + // The event to call when the voice has stopped playing, either naturally or explicitly with dra_voice_stop(). + dra__event stopEvent; + + // The event to call when the voice starts playing. + dra__event playEvent; + + + // Application defined user data. + void* pUserData; + + + // The size of the buffer in bytes. + size_t sizeInBytes; + + // The actual buffer containing the raw audio data in it's native format. At mix time the data will be converted + // to floats. + dr_uint8 pData[1]; +}; + + +// dra_context_init() +dra_result dra_context_init(dra_context* pContext); +void dra_context_uninit(dra_context* pContext); + +// Helper for allocating and initializing a context. If you want to do your own memory management or just want to put the context +// object on the stack, just use dra_context_init() instead. +dra_result dra_context_create(dra_context** ppContext); +void dra_context_delete(dra_context* pContext); + + +// dra_device_init_ex() +// +// If deviceID is 0 the default device for the given type is used. +// format can be dra_format_default which is dra_format_s32. +// If channels is set to 0, defaults 2 channels (stereo). +// If sampleRate is set to 0, defaults to 48000. +// If latency is 0, defaults to 50 milliseconds. See notes about latency above. +// +// Concerning the DirectSound backend (From MSDN): +// Note that if your application is playing sounds as well as capturing them, capture buffer creation can fail when +// the format of the capture buffer is not the same as that of the primary buffer. The reason is that some cards have +// only a single clock and cannot support capture and playback at two different frequencies. +// +// This means you will need to keep the channels and sample rate consistent across playback and capture devices when +// using the DirectSound backend. +dra_result dra_device_init_ex(dra_context* pContext, dra_device_type type, unsigned int deviceID, unsigned int channels, unsigned int sampleRate, unsigned int latencyInMilliseconds, dra_device* pDevice); +dra_result dra_device_init(dra_context* pContext, dra_device_type type, dra_device* pDevice); +void dra_device_uninit(dra_device* pDevice); + +// Helper for allocating and initializing a device object. +dra_result dra_device_create_ex(dra_context* pContext, dra_device_type type, unsigned int deviceID, unsigned int channels, unsigned int sampleRate, unsigned int latencyInMilliseconds, dra_device** ppDevice); +dra_result dra_device_create(dra_context* pContext, dra_device_type type, dra_device** ppDevice); +void dra_device_delete(dra_device* pDevice); + +// Starts a capture device. +// +// Do not call this on a playback device - this is managed by dr_audio. This will fail for playback devices. +dra_result dra_device_start(dra_device* pDevice); + +// Stops a capture device. +// +// Do not call this on a playback device - this is managed by dr_audio. This will fail for playback devices. +dra_result dra_device_stop(dra_device* pDevice); + +// Sets the function to call when a segment of samples have been processed by the device (either captured +// or played back). Use this to keep track of the audio data that's passed to a playback device or from a +// capture device. +void dra_device_set_samples_processed_callback(dra_device* pDevice, dra_samples_processed_proc proc, void* pUserData); + + + + +// dra_mixer_create() +dra_result dra_mixer_create(dra_device* pDevice, dra_mixer** ppMixer); + +// dra_mixer_delete() +// +// Deleting a mixer will detach any attached voices and sub-mixers and attach them to the master mixer. It is +// up to the application to manage the allocation of sub-mixers and voices. Typically you'll want to delete +// child mixers and voices before deleting a mixer. +void dra_mixer_delete(dra_mixer* pMixer); + +// dra_mixer_attach_submixer() +void dra_mixer_attach_submixer(dra_mixer* pMixer, dra_mixer* pSubmixer); + +// dra_mixer_detach_submixer() +void dra_mixer_detach_submixer(dra_mixer* pMixer, dra_mixer* pSubmixer); + +// dra_mixer_detach_all_submixers() +void dra_mixer_detach_all_submixers(dra_mixer* pMixer); + +// dra_mixer_attach_voice() +void dra_mixer_attach_voice(dra_mixer* pMixer, dra_voice* pVoice); + +// dra_mixer_detach_voice() +void dra_mixer_detach_voice(dra_mixer* pMixer, dra_voice* pVoice); + +// dra_mixer_detach_all_voices() +void dra_mixer_detach_all_voices(dra_mixer* pMixer); + +// dra_voice_set_volume() +void dra_mixer_set_volume(dra_mixer* pMixer, float linearVolume); + +// dra_voice_get_volume() +float dra_mixer_get_volume(dra_mixer* pMixer); + +// Mixes the next number of frameCount and moves the playback position appropriately. +// +// pMixer [in] The mixer. +// frameCount [in] The number of frames to mix. +// +// Returns the number of frames actually mixed. +// +// The return value is used to determine whether or not there's anything left to mix in the future. When there are +// no samples left to mix, the device can be put into a dormant state to prevent unnecessary processing. +// +// Mixed samples will be placed in pMixer->pStagingBuffer. +size_t dra_mixer_mix_next_frames(dra_mixer* pMixer, size_t frameCount); + + +// Non-recursively counts the number of voices that are attached to the given mixer. +size_t dra_mixer_count_attached_voices(dra_mixer* pMixer); + +// Recursively counts the number of voices that are attached to the given mixer. +size_t dra_mixer_count_attached_voices_recursive(dra_mixer* pMixer); + +// Non-recursively gathers all of the voices that are currently attached to the given mixer. +size_t dra_mixer_gather_attached_voices(dra_mixer* pMixer, dra_voice** ppVoicesOut); + +// Recursively gathers all of the voices that are currently attached to the given mixer. +size_t dra_mixer_gather_attached_voices_recursive(dra_mixer* pMixer, dra_voice** ppVoicesOut); + + +// Marks the given mixer as paused. +void dra_mixer_pause(dra_mixer* pMixer); + +// Unmarks the given mixer as paused. +void dra_mixer_resume(dra_mixer* pMixer); + +// Determines whether or not the given mixer is paused. +dr_bool32 dra_mixer_is_paused(dra_mixer* pMixer); + + +// dra_voice_create() +dra_result dra_voice_create(dra_device* pDevice, dra_format format, unsigned int channels, unsigned int sampleRate, size_t sizeInBytes, const void* pInitialData, dra_voice** ppVoice); +dra_result dra_voice_create_compatible(dra_device* pDevice, size_t sizeInBytes, const void* pInitialData, dra_voice** ppVoice); + +// dra_voice_delete() +void dra_voice_delete(dra_voice* pVoice); + +// dra_voice_play() +// +// If the mixer the voice is attached to is not playing, the voice will be marked as playing, but won't actually play anything until +// the mixer is started again. +void dra_voice_play(dra_voice* pVoice, dr_bool32 loop); + +// dra_voice_stop() +void dra_voice_stop(dra_voice* pVoice); + +// dra_voice_is_playing() +dr_bool32 dra_voice_is_playing(dra_voice* pVoice); + +// dra_voice_is_looping() +dr_bool32 dra_voice_is_looping(dra_voice* pVoice); + + +// dra_voice_set_volume() +void dra_voice_set_volume(dra_voice* pVoice, float linearVolume); + +// dra_voice_get_volume() +float dra_voice_get_volume(dra_voice* pVoice); + + +void dra_voice_set_on_stop(dra_voice* pVoice, dra_event_proc proc, void* pUserData); +void dra_voice_set_on_play(dra_voice* pVoice, dra_event_proc proc, void* pUserData); +dr_bool32 dra_voice_add_playback_event(dra_voice* pVoice, dr_uint64 sampleIndex, dr_uint64 eventID, dra_event_proc proc, void* pUserData); +void dra_voice_remove_playback_event(dra_voice* pVoice, dr_uint64 eventID); + +// dra_voice_get_playback_position() +dr_uint64 dra_voice_get_playback_position(dra_voice* pVoice); + +// dra_voice_set_playback_position() +void dra_voice_set_playback_position(dra_voice* pVoice, dr_uint64 sampleIndex); + + +// dra_voice_get_buffer_ptr_by_sample() +void* dra_voice_get_buffer_ptr_by_sample(dra_voice* pVoice, dr_uint64 sample); + +// dra_voice_write_silence() +void dra_voice_write_silence(dra_voice* pVoice, dr_uint64 sampleOffset, dr_uint64 sampleCount); + + +//// Other APIs //// + +// Frees memory that was allocated internally by dr_audio. +void dra_free(void* p); + +// Retrieves the number of bits per sample based on the given format. +unsigned int dra_get_bits_per_sample_by_format(dra_format format); + +// Retrieves the number of bytes per sample based on the given format. +unsigned int dra_get_bytes_per_sample_by_format(dra_format format); + + +//// Decoder APIs //// + +typedef enum +{ + dra_seek_origin_start, + dra_seek_origin_current +} dra_seek_origin; + +typedef size_t (* dra_decoder_on_read_proc) (void* pUserData, void* pDataOut, size_t bytesToRead); +typedef dr_bool32 (* dra_decoder_on_seek_proc) (void* pUserData, int offset, dra_seek_origin origin); + +typedef void (* dra_decoder_on_delete_proc) (void* pBackendDecoder); +typedef dr_uint64 (* dra_decoder_on_read_samples_proc) (void* pBackendDecoder, dr_uint64 samplesToRead, float* pSamplesOut); +typedef dr_bool32 (* dra_decoder_on_seek_samples_proc) (void* pBackendDecoder, dr_uint64 sample); + +typedef struct +{ + const dr_uint8* data; + size_t dataSize; + size_t currentReadPos; +} dra__memory_stream; + +typedef struct +{ + unsigned int channels; + unsigned int sampleRate; + dr_uint64 totalSampleCount; // <-- Can be 0. + + dra_decoder_on_read_proc onRead; + dra_decoder_on_seek_proc onSeek; + void* pUserData; + + void* pBackendDecoder; + dra_decoder_on_delete_proc onDelete; + dra_decoder_on_read_samples_proc onReadSamples; + dra_decoder_on_seek_samples_proc onSeekSamples; + + // A hack to enable decoding from memory without mallocing the user data. + dra__memory_stream memoryStream; +} dra_decoder; + +// dra_decoder_open() +dra_result dra_decoder_open(dra_decoder* pDecoder, dra_decoder_on_read_proc onRead, dra_decoder_on_seek_proc onSeek, void* pUserData); +float* dra_decoder_open_and_decode_f32(dra_decoder_on_read_proc onRead, dra_decoder_on_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount); + +dra_result dra_decoder_open_memory(dra_decoder* pDecoder, const void* pData, size_t dataSize); +float* dra_decoder_open_and_decode_memory_f32(const void* pData, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount); + +#ifndef DR_AUDIO_NO_STDIO +dra_result dra_decoder_open_file(dra_decoder* pDecoder, const char* filePath); +float* dra_decoder_open_and_decode_file_f32(const char* filePath, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount); +#endif + +// dra_decoder_close() +void dra_decoder_close(dra_decoder* pDecoder); + +// dra_decoder_read_f32() +dr_uint64 dra_decoder_read_f32(dra_decoder* pDecoder, dr_uint64 samplesToRead, float* pSamplesOut); + +// dra_decoder_seek_to_sample() +dr_bool32 dra_decoder_seek_to_sample(dra_decoder* pDecoder, dr_uint64 sample); + + +//// High Level Helper APIs //// + +#ifndef DR_AUDIO_NO_STDIO +// Creates a voice from a file. +dra_result dra_voice_create_from_file(dra_device* pDevice, const char* filePath, dra_voice** ppVoice); +#endif + + +//// High Level World API //// +// +// This section is for the sound world APIs. These are high-level APIs that sit directly on top of the main API. +typedef struct dra_sound_world dra_sound_world; +typedef struct dra_sound dra_sound; +typedef struct dra_sound_desc dra_sound_desc; + +typedef void (* dra_sound_on_delete_proc) (dra_sound* pSound); +typedef dr_uint64 (* dra_sound_on_read_proc) (dra_sound* pSound, dr_uint64 samplesToRead, void* pSamplesOut); +typedef dr_bool32 (* dra_sound_on_seek_proc) (dra_sound* pSound, dr_uint64 sample); + +struct dra_sound_desc +{ + // The format of the sound. + dra_format format; + + // The number of channels in the audio data. + unsigned int channels; + + // The sample rate of the audio data. + unsigned int sampleRate; + + + // The size of the audio data in bytes. If this is 0 it is assumed the data will be streamed. + size_t dataSize; + + // A pointer to the audio data. If this is null it is assumed the audio data is streamed. + void* pData; + + + // A pointer to the function to call when the sound object is deleted. This is used to give the application an + // opportunity to do any clean up, such as closing decoders or whatnot. + dra_sound_on_delete_proc onDelete; + + // A pointer to the function to call when dr_audio needs to request a chunk of audio data. This is only used when + // streaming data. + dra_sound_on_read_proc onRead; + + // A pointer to the function to call when dr_audio needs to seek the audio data. This is only used when streaming + // data. + dra_sound_on_seek_proc onSeek; + + // A pointer to some application defined user data that can be associated with the sound. + void* pUserData; +}; + +struct dra_sound_world +{ + // The playback device. + dra_device* pPlaybackDevice; + + // Whether or not the world owns the playback device. When this is set to DR_TRUE, it will be deleted when the world is deleted. + dr_bool32 ownsPlaybackDevice; +}; + +struct dra_sound +{ + // The world that owns this sound. + dra_sound_world* pWorld; + + // The voice object for emitting audio out of the device. + dra_voice* pVoice; + + // The descriptor of the sound that was used to initialize the sound. + dra_sound_desc desc; + + // Whether or not the sound is looping. + dr_bool32 isLooping; + + // Whether or not the sound should be stopped at the end of the chunk that's currently playing. + dr_bool32 stopOnNextChunk; + + // Application defined user data. + void* pUserData; +}; + +// dra_sound_world_create() +// +// The playback device can be null, in which case a default one will be created. +dra_sound_world* dra_sound_world_create(dra_device* pPlaybackDevice); + +// dra_sound_world_delete() +// +// This will delete every sound this world owns. +void dra_sound_world_delete(dra_sound_world* pWorld); + +// dra_sound_world_play_inline() +// +// pMixer [in, optional] The mixer to attach the sound to. Can null, in which case it's attached to the master mixer. +void dra_sound_world_play_inline(dra_sound_world* pWorld, dra_sound_desc* pDesc, dra_mixer* pMixer); + +// Plays an inlined sound in 3D space. +// +// This is a placeholder function. 3D position is not yet implemented. +void dra_sound_world_play_inline_3f(dra_sound_world* pWorld, dra_sound_desc* pDesc, dra_mixer* pMixer, float xPos, float yPos, float zPos); + +// Stops playing every sound. +// +// This will stop all voices that are attached to the world's playback deviecs, including those that are not attached to a dra_sound object. +void dra_sound_world_stop_all_sounds(dra_sound_world* pWorld); + + +// Sets the position of the listener for 3D effects. +// +// This is placeholder. +void dra_sound_world_set_listener_position(dra_sound_world* pWorld, float xPos, float yPos, float zPos); + +// Sets the orientation of the listener for 3D effects. +// +// This is placeholder. +void dra_sound_world_set_listener_orientation(dra_sound_world* pWorld, float xForward, float yForward, float zForward, float xUp, float yUp, float zUp); + + + + +// dra_sound_create() +// +// The datails in "desc" can be accessed from the returned object directly. +// +// This uses the pUserData member of the internal voice. Do not overwrite this. Instead, use the pUserData member of +// the returned dra_sound object. +dra_sound* dra_sound_create(dra_sound_world* pWorld, dra_sound_desc* pDesc); + +#ifndef DR_AUDIO_NO_STDIO +// dra_sound_create_from_file() +// +// This will hold a handle to the file for the life of the sound. +dra_sound* dra_sound_create_from_file(dra_sound_world* pWorld, const char* filePath); +#endif + +// dra_sound_delete() +void dra_sound_delete(dra_sound* pSound); + + +// dra_sound_play() +void dra_sound_play(dra_sound* pSound, dr_bool32 loop); + +// dra_sound_stop() +void dra_sound_stop(dra_sound* pSound); + + +// Attaches the given sound to the given mixer. +// +// Setting pMixer to null will detach the sound from the mixer it is currently attached to and attach it +// to the master mixer. +void dra_sound_attach_to_mixer(dra_sound* pSound, dra_mixer* pMixer); + + +// dra_sound_set_on_stop() +void dra_sound_set_on_stop(dra_sound* pSound, dra_event_proc proc, void* pUserData); + +// dra_sound_set_on_play() +void dra_sound_set_on_play(dra_sound* pSound, dra_event_proc proc, void* pUserData); + + +#ifdef __cplusplus +} +#endif +#endif //dr_audio2_h + + + +/////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef DR_AUDIO_IMPLEMENTATION +#include +#include +#include +#include // For good old printf debugging. Delete later. + +#ifdef _MSC_VER +#define DR_AUDIO_INLINE static __forceinline +#else +#define DR_AUDIO_INLINE static inline +#endif + +#define DR_AUDIO_DEFAULT_CHANNEL_COUNT 2 +#define DR_AUDIO_DEFAULT_SAMPLE_RATE 48000 +#define DR_AUDIO_DEFAULT_LATENCY 100 // Milliseconds. TODO: Test this with very low values. DirectSound appears to not signal the fragment events when it's too small. With values of about 20 it sounds crackly. +#define DR_AUDIO_DEFAULT_FRAGMENT_COUNT 3 // The hardware buffer is divided up into latency-sized blocks. This controls that number. Must be at least 2. + +#define DR_AUDIO_BACKEND_TYPE_NULL 0 +#define DR_AUDIO_BACKEND_TYPE_DSOUND 1 +#define DR_AUDIO_BACKEND_TYPE_ALSA 2 + +#ifdef dr_wav_h +#define DR_AUDIO_HAS_WAV + #ifndef DR_WAV_NO_STDIO + #define DR_AUDIO_HAS_WAV_STDIO + #endif +#endif +#ifdef dr_flac_h +#define DR_AUDIO_HAS_FLAC + #ifndef DR_FLAC_NO_STDIO + #define DR_AUDIO_HAS_FLAC_STDIO + #endif +#endif +#ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H +#define DR_AUDIO_HAS_VORBIS + #ifndef STB_VORBIS_NO_STDIO + #define DR_AUDIO_HAS_VORBIS_STDIO + #endif +#endif + +#if defined(DR_AUDIO_HAS_WAV) || \ + defined(DR_AUDIO_HAS_FLAC) || \ + defined(DR_AUDIO_HAS_VORBIS) +#define DR_AUDIO_HAS_EXTERNAL_DECODER +#endif + + +// Thanks to good old Bit Twiddling Hacks for this one: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 +DR_AUDIO_INLINE unsigned int dra_next_power_of_2(unsigned int x) +{ + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x++; + + return x; +} + +DR_AUDIO_INLINE unsigned int dra_prev_power_of_2(unsigned int x) +{ + return dra_next_power_of_2(x) >> 1; +} + + +DR_AUDIO_INLINE float dra_mixf(float x, float y, float a) +{ + return x*(1-a) + y*a; +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// Platform Specific +// +/////////////////////////////////////////////////////////////////////////////// + +// Every backend struct should begin with these properties. +struct dra_backend +{ +#define DR_AUDIO_BASE_BACKEND_ATTRIBS \ + unsigned int type; \ + + DR_AUDIO_BASE_BACKEND_ATTRIBS +}; + +// Every backend device struct should begin with these properties. +struct dra_backend_device +{ +#define DR_AUDIO_BASE_BACKEND_DEVICE_ATTRIBS \ + dra_backend* pBackend; \ + dra_device_type type; \ + unsigned int channels; \ + unsigned int sampleRate; \ + unsigned int fragmentCount; \ + unsigned int samplesPerFragment; \ + + DR_AUDIO_BASE_BACKEND_DEVICE_ATTRIBS +}; + + + + +// Compile-time platform detection and #includes. +#ifdef _WIN32 +#include + +//// Threading (Win32) //// +typedef DWORD (* dra_thread_entry_proc)(LPVOID pData); + +dra_thread dra_thread_create(dra_thread_entry_proc entryProc, void* pData) +{ + return (dra_thread)CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)entryProc, pData, 0, NULL); +} + +void dra_thread_delete(dra_thread thread) +{ + CloseHandle((HANDLE)thread); +} + +void dra_thread_wait(dra_thread thread) +{ + WaitForSingleObject((HANDLE)thread, INFINITE); +} + + +dra_mutex dra_mutex_create() +{ + return (dra_mutex)CreateEventA(NULL, FALSE, TRUE, NULL); +} + +void dra_mutex_delete(dra_mutex mutex) +{ + CloseHandle((HANDLE)mutex); +} + +void dra_mutex_lock(dra_mutex mutex) +{ + WaitForSingleObject((HANDLE)mutex, INFINITE); +} + +void dra_mutex_unlock(dra_mutex mutex) +{ + SetEvent((HANDLE)mutex); +} + + +dra_semaphore dra_semaphore_create(int initialValue) +{ + return (void*)CreateSemaphoreA(NULL, initialValue, LONG_MAX, NULL); +} + +void dra_semaphore_delete(dra_semaphore semaphore) +{ + CloseHandle((HANDLE)semaphore); +} + +dr_bool32 dra_semaphore_wait(dra_semaphore semaphore) +{ + return WaitForSingleObject((HANDLE)semaphore, INFINITE) == WAIT_OBJECT_0; +} + +dr_bool32 dra_semaphore_release(dra_semaphore semaphore) +{ + return ReleaseSemaphore((HANDLE)semaphore, 1, NULL) != 0; +} + + + +//// DirectSound //// +#ifndef DR_AUDIO_NO_DSOUND +#define DR_AUDIO_ENABLE_DSOUND +#include +#include // WAVEFORMATEX + +GUID DR_AUDIO_GUID_NULL = {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + +static GUID _g_draGUID_IID_DirectSoundNotify = {0xb0210783, 0x89cd, 0x11d0, {0xaf, 0x08, 0x00, 0xa0, 0xc9, 0x25, 0xcd, 0x16}}; +static GUID _g_draGUID_IID_IDirectSoundCaptureBuffer8 = {0x00990df4, 0x0dbb, 0x4872, {0x83, 0x3e, 0x6d, 0x30, 0x3e, 0x80, 0xae, 0xb6}}; +static GUID _g_draGUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; + +#ifdef __cplusplus +static GUID g_draGUID_IID_DirectSoundNotify = _g_draGUID_IID_DirectSoundNotify; +static GUID g_draGUID_IID_IDirectSoundCaptureBuffer8 = _g_draGUID_IID_IDirectSoundCaptureBuffer8; +//static GUID g_draGUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = _g_draGUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; +#else +static GUID* g_draGUID_IID_DirectSoundNotify = &_g_draGUID_IID_DirectSoundNotify; +static GUID* g_draGUID_IID_IDirectSoundCaptureBuffer8 = &_g_draGUID_IID_IDirectSoundCaptureBuffer8; +//static GUID* g_draGUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = &_g_draGUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; +#endif + +typedef HRESULT (WINAPI * pDirectSoundCreate8Proc)(LPCGUID pcGuidDevice, LPDIRECTSOUND8 *ppDS8, LPUNKNOWN pUnkOuter); +typedef HRESULT (WINAPI * pDirectSoundEnumerateAProc)(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext); +typedef HRESULT (WINAPI * pDirectSoundCaptureCreate8Proc)(LPCGUID pcGuidDevice, LPDIRECTSOUNDCAPTURE8 *ppDSC8, LPUNKNOWN pUnkOuter); +typedef HRESULT (WINAPI * pDirectSoundCaptureEnumerateAProc)(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext); + +typedef struct +{ + DR_AUDIO_BASE_BACKEND_ATTRIBS + + // A handle to the dsound DLL for doing run-time linking. + HMODULE hDSoundDLL; + + pDirectSoundCreate8Proc pDirectSoundCreate8; + pDirectSoundEnumerateAProc pDirectSoundEnumerateA; + pDirectSoundCaptureCreate8Proc pDirectSoundCaptureCreate8; + pDirectSoundCaptureEnumerateAProc pDirectSoundCaptureEnumerateA; +} dra_backend_dsound; + +typedef struct +{ + DR_AUDIO_BASE_BACKEND_DEVICE_ATTRIBS + + + // The main device object for use with DirectSound. + LPDIRECTSOUND8 pDS; + + // The DirectSound "primary buffer". It's basically just representing the connection between us and the hardware device. + LPDIRECTSOUNDBUFFER pDSPrimaryBuffer; + + // The DirectSound "secondary buffer". This is where the actual audio data will be written to by dr_audio when it's time + // to play back some audio through the speakers. This represents the hardware buffer. + LPDIRECTSOUNDBUFFER pDSSecondaryBuffer; + + + // The main capture device object for use with DirectSound. This is only used by capture devices and is created by DirectSoundCaptureCreate8(). + LPDIRECTSOUNDCAPTURE8 pDSCapture; + + // The capture buffer. This is where captured audio data will be placed. This is only used by capture devices. + LPDIRECTSOUNDCAPTUREBUFFER8 pDSCaptureBuffer; + + + // The notification object used by DirectSound to notify dr_audio that it's ready for the next fragment of audio data. + LPDIRECTSOUNDNOTIFY pDSNotify; + + // Notification events for each fragment. + HANDLE pNotifyEvents[DR_AUDIO_DEFAULT_FRAGMENT_COUNT]; + + // The event the main playback thread will wait on to determine whether or not the playback loop should terminate. + HANDLE hStopEvent; + + // The index of the fragment that is currently being played. + unsigned int currentFragmentIndex; + + // The address of the mapped fragment. This is set with IDirectSoundBuffer8::Lock() and passed to IDriectSoundBuffer8::Unlock(). + void* pLockPtr; + + // The size of the locked buffer. This is set with IDirectSoundBuffer8::Lock() and passed to IDriectSoundBuffer8::Unlock(). + DWORD lockSize; + +} dra_backend_device_dsound; + +typedef struct +{ + unsigned int deviceID; + unsigned int counter; + const GUID* pGuid; +} dra_dsound__device_enum_data; + +static BOOL CALLBACK dra_dsound__get_device_guid_by_id__callback(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext) +{ + (void)lpcstrDescription; + (void)lpcstrModule; + + dra_dsound__device_enum_data* pData = (dra_dsound__device_enum_data*)lpContext; + assert(pData != NULL); + + if (pData->counter == pData->deviceID) { + pData->pGuid = lpGuid; + return DR_FALSE; + } + + pData->counter += 1; + return DR_TRUE; +} + +const GUID* dra_dsound__get_playback_device_guid_by_id(dra_backend* pBackend, unsigned int deviceID) +{ + // From MSDN: + // + // The first device enumerated is always called the Primary Sound Driver, and the lpGUID parameter of the callback is + // NULL. This device represents the preferred output device set by the user in Control Panel. + if (deviceID == 0) { + return NULL; + } + + dra_backend_dsound* pBackendDS = (dra_backend_dsound*)pBackend; + if (pBackendDS == NULL) { + return NULL; + } + + // The device ID is treated as the device index. The actual ID for use by DirectSound is a GUID. We use DirectSoundEnumerateA() + // iterate over each device. This function is usually only going to be used during initialization time so it won't be a performance + // issue to not cache these. + dra_dsound__device_enum_data data = {0}; + data.deviceID = deviceID; + pBackendDS->pDirectSoundEnumerateA(dra_dsound__get_device_guid_by_id__callback, &data); + + return data.pGuid; +} + +const GUID* dra_dsound__get_capture_device_guid_by_id(dra_backend* pBackend, unsigned int deviceID) +{ + // From MSDN: + // + // The first device enumerated is always called the Primary Sound Driver, and the lpGUID parameter of the callback is + // NULL. This device represents the preferred output device set by the user in Control Panel. + if (deviceID == 0) { + return NULL; + } + + dra_backend_dsound* pBackendDS = (dra_backend_dsound*)pBackend; + if (pBackendDS == NULL) { + return NULL; + } + + // The device ID is treated as the device index. The actual ID for use by DirectSound is a GUID. We use DirectSoundEnumerateA() + // iterate over each device. This function is usually only going to be used during initialization time so it won't be a performance + // issue to not cache these. + dra_dsound__device_enum_data data = {0}; + data.deviceID = deviceID; + pBackendDS->pDirectSoundCaptureEnumerateA(dra_dsound__get_device_guid_by_id__callback, &data); + + return data.pGuid; +} + +dra_backend* dra_backend_create_dsound() +{ + dra_backend_dsound* pBackendDS = (dra_backend_dsound*)calloc(1, sizeof(*pBackendDS)); // <-- Note the calloc() - makes it easier to handle the on_error goto. + if (pBackendDS == NULL) { + return NULL; + } + + pBackendDS->type = DR_AUDIO_BACKEND_TYPE_DSOUND; + + pBackendDS->hDSoundDLL = LoadLibraryW(L"dsound.dll"); + if (pBackendDS->hDSoundDLL == NULL) { + goto on_error; + } + + pBackendDS->pDirectSoundCreate8 = (pDirectSoundCreate8Proc)GetProcAddress(pBackendDS->hDSoundDLL, "DirectSoundCreate8"); + if (pBackendDS->pDirectSoundCreate8 == NULL){ + goto on_error; + } + + pBackendDS->pDirectSoundEnumerateA = (pDirectSoundEnumerateAProc)GetProcAddress(pBackendDS->hDSoundDLL, "DirectSoundEnumerateA"); + if (pBackendDS->pDirectSoundEnumerateA == NULL){ + goto on_error; + } + + pBackendDS->pDirectSoundCaptureCreate8 = (pDirectSoundCaptureCreate8Proc)GetProcAddress(pBackendDS->hDSoundDLL, "DirectSoundCaptureCreate8"); + if (pBackendDS->pDirectSoundCaptureCreate8 == NULL){ + goto on_error; + } + + pBackendDS->pDirectSoundCaptureEnumerateA = (pDirectSoundCaptureEnumerateAProc)GetProcAddress(pBackendDS->hDSoundDLL, "DirectSoundCaptureEnumerateA"); + if (pBackendDS->pDirectSoundCaptureEnumerateA == NULL){ + goto on_error; + } + + + return (dra_backend*)pBackendDS; + +on_error: + if (pBackendDS != NULL) { + if (pBackendDS->hDSoundDLL != NULL) { + FreeLibrary(pBackendDS->hDSoundDLL); + } + + free(pBackendDS); + } + + return NULL; +} + +void dra_backend_delete_dsound(dra_backend* pBackend) +{ + dra_backend_dsound* pBackendDS = (dra_backend_dsound*)pBackend; + if (pBackendDS == NULL) { + return; + } + + if (pBackendDS->hDSoundDLL != NULL) { + FreeLibrary(pBackendDS->hDSoundDLL); + } + + free(pBackendDS); +} + +void dra_backend_device_close_dsound(dra_backend_device* pDevice) +{ + dra_backend_device_dsound* pDeviceDS = (dra_backend_device_dsound*)pDevice; + if (pDeviceDS == NULL) { + return; + } + + if (pDeviceDS->pDSNotify) IDirectSoundNotify_Release(pDeviceDS->pDSNotify); + + if (pDevice->type == dra_device_type_playback) { + if (pDeviceDS->pDSSecondaryBuffer) IDirectSoundBuffer_Release(pDeviceDS->pDSSecondaryBuffer); + if (pDeviceDS->pDSPrimaryBuffer) IDirectSoundBuffer_Release(pDeviceDS->pDSPrimaryBuffer); + if (pDeviceDS->pDS) IDirectSound_Release(pDeviceDS->pDS); + } else { + if (pDeviceDS->pDSCaptureBuffer) IDirectSoundCaptureBuffer_Release(pDeviceDS->pDSCaptureBuffer); + if (pDeviceDS->pDSCapture) IDirectSoundCapture_Release(pDeviceDS->pDSCapture); + } + + + for (int i = 0; i < DR_AUDIO_DEFAULT_FRAGMENT_COUNT; ++i) { + CloseHandle(pDeviceDS->pNotifyEvents[i]); + } + + if (pDeviceDS->hStopEvent != NULL) { + CloseHandle(pDeviceDS->hStopEvent); + } + + free(pDeviceDS); +} + +dra_backend_device* dra_backend_device_open_playback_dsound(dra_backend* pBackend, unsigned int deviceID, unsigned int channels, unsigned int sampleRate, unsigned int latencyInMilliseconds) +{ + // These are declared at the top to stop compilations errors on GCC about goto statements skipping over variable initialization. + HRESULT hr; + WAVEFORMATEXTENSIBLE* actualFormat; + unsigned int sampleRateInMilliseconds; + unsigned int proposedFramesPerFragment; + unsigned int framesPerFragment; + size_t fragmentSize; + size_t hardwareBufferSize; + + dra_backend_dsound* pBackendDS = (dra_backend_dsound*)pBackend; + if (pBackendDS == NULL) { + return NULL; + } + + dra_backend_device_dsound* pDeviceDS = (dra_backend_device_dsound*)calloc(1, sizeof(*pDeviceDS)); + if (pDeviceDS == NULL) { + goto on_error; + } + + if (channels == 0) { + channels = DR_AUDIO_DEFAULT_CHANNEL_COUNT; + } + + pDeviceDS->pBackend = pBackend; + pDeviceDS->type = dra_device_type_playback; + pDeviceDS->channels = channels; + pDeviceDS->sampleRate = sampleRate; + + hr = pBackendDS->pDirectSoundCreate8(dra_dsound__get_playback_device_guid_by_id(pBackend, deviceID), &pDeviceDS->pDS, NULL); + if (FAILED(hr)) { + goto on_error; + } + + // The cooperative level must be set before doing anything else. + hr = IDirectSound_SetCooperativeLevel(pDeviceDS->pDS, GetForegroundWindow(), DSSCL_PRIORITY); + if (FAILED(hr)) { + goto on_error; + } + + + // The primary buffer is basically just the connection to the hardware. + DSBUFFERDESC descDSPrimary; + memset(&descDSPrimary, 0, sizeof(DSBUFFERDESC)); + descDSPrimary.dwSize = sizeof(DSBUFFERDESC); + descDSPrimary.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME; + + hr = IDirectSound_CreateSoundBuffer(pDeviceDS->pDS, &descDSPrimary, &pDeviceDS->pDSPrimaryBuffer, NULL); + if (FAILED(hr)) { + goto on_error; + } + + + // If the channel count is 0 then we need to use the default. From MSDN: + // + // The method succeeds even if the hardware does not support the requested format; DirectSound sets the buffer to the closest + // supported format. To determine whether this has happened, an application can call the GetFormat method for the primary buffer + // and compare the result with the format that was requested with the SetFormat method. + WAVEFORMATEXTENSIBLE wf; + memset(&wf, 0, sizeof(wf)); + wf.Format.cbSize = sizeof(wf); + wf.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wf.Format.nChannels = (WORD)channels; + wf.Format.nSamplesPerSec = (DWORD)sampleRate; + wf.Format.wBitsPerSample = sizeof(float)*8; + wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8; + wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec; + wf.Samples.wValidBitsPerSample = wf.Format.wBitsPerSample; + wf.dwChannelMask = 0; + wf.SubFormat = _g_draGUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + if (channels > 2) { + wf.dwChannelMask = ~(((DWORD)-1) << channels); + } + + hr = IDirectSoundBuffer_SetFormat(pDeviceDS->pDSPrimaryBuffer, (WAVEFORMATEX*)&wf); + if (FAILED(hr)) { + goto on_error; + } + + + // Get the ACTUAL properties of the buffer. This is silly API design... + DWORD requiredSize; + hr = IDirectSoundBuffer_GetFormat(pDeviceDS->pDSPrimaryBuffer, NULL, 0, &requiredSize); + if (FAILED(hr)) { + goto on_error; + } + + char rawdata[1024]; + actualFormat = (WAVEFORMATEXTENSIBLE*)rawdata; + hr = IDirectSoundBuffer_GetFormat(pDeviceDS->pDSPrimaryBuffer, (WAVEFORMATEX*)actualFormat, requiredSize, NULL); + if (FAILED(hr)) { + goto on_error; + } + + pDeviceDS->channels = actualFormat->Format.nChannels; + pDeviceDS->sampleRate = actualFormat->Format.nSamplesPerSec; + + // DirectSound always has the same number of fragments. + pDeviceDS->fragmentCount = DR_AUDIO_DEFAULT_FRAGMENT_COUNT; + + + // The secondary buffer is the buffer where the real audio data will be written to and used by the hardware device. It's + // size is based on the latency, sample rate and channels. + // + // The format of the secondary buffer should exactly match the primary buffer as to avoid unnecessary data conversions. + sampleRateInMilliseconds = pDeviceDS->sampleRate / 1000; + if (sampleRateInMilliseconds == 0) { + sampleRateInMilliseconds = 1; + } + + // The size of a fragment is sized such that the number of frames contained within it is a multiple of 2. The reason for + // this is to keep it consistent with the ALSA backend. + proposedFramesPerFragment = sampleRateInMilliseconds * latencyInMilliseconds; + framesPerFragment = dra_prev_power_of_2(proposedFramesPerFragment); + if (framesPerFragment == 0) { + framesPerFragment = 2; + } + + pDeviceDS->samplesPerFragment = framesPerFragment * pDeviceDS->channels; + + fragmentSize = pDeviceDS->samplesPerFragment * sizeof(float); + hardwareBufferSize = fragmentSize * pDeviceDS->fragmentCount; + assert(hardwareBufferSize > 0); // <-- If you've triggered this is means you've got something set to 0. You haven't been setting that latency to 0 have you?! That's not allowed! + + // Meaning of dwFlags (from MSDN): + // + // DSBCAPS_CTRLPOSITIONNOTIFY + // The buffer has position notification capability. + // + // DSBCAPS_GLOBALFOCUS + // With this flag set, an application using DirectSound can continue to play its buffers if the user switches focus to + // another application, even if the new application uses DirectSound. + // + // DSBCAPS_GETCURRENTPOSITION2 + // In the first version of DirectSound, the play cursor was significantly ahead of the actual playing sound on emulated + // sound cards; it was directly behind the write cursor. Now, if the DSBCAPS_GETCURRENTPOSITION2 flag is specified, the + // application can get a more accurate play cursor. + DSBUFFERDESC descDS; + memset(&descDS, 0, sizeof(DSBUFFERDESC)); + descDS.dwSize = sizeof(DSBUFFERDESC); + descDS.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; + descDS.dwBufferBytes = (DWORD)hardwareBufferSize; + descDS.lpwfxFormat = (WAVEFORMATEX*)&wf; + hr = IDirectSound_CreateSoundBuffer(pDeviceDS->pDS, &descDS, &pDeviceDS->pDSSecondaryBuffer, NULL); + if (FAILED(hr)) { + goto on_error; + } + + + // As DirectSound is playing back the hardware buffer it needs to notify dr_audio when it's ready for new data. This is done + // through a notification object which we retrieve from the secondary buffer. + hr = IDirectSoundBuffer8_QueryInterface(pDeviceDS->pDSSecondaryBuffer, g_draGUID_IID_DirectSoundNotify, (void**)&pDeviceDS->pDSNotify); + if (FAILED(hr)) { + goto on_error; + } + + DSBPOSITIONNOTIFY notifyPoints[DR_AUDIO_DEFAULT_FRAGMENT_COUNT]; // One notification event for each fragment. + for (int i = 0; i < DR_AUDIO_DEFAULT_FRAGMENT_COUNT; ++i) + { + pDeviceDS->pNotifyEvents[i] = CreateEventA(NULL, FALSE, FALSE, NULL); + if (pDeviceDS->pNotifyEvents[i] == NULL) { + goto on_error; + } + + notifyPoints[i].dwOffset = (DWORD)(i * fragmentSize); // <-- This is in bytes. + notifyPoints[i].hEventNotify = pDeviceDS->pNotifyEvents[i]; + } + + hr = IDirectSoundNotify_SetNotificationPositions(pDeviceDS->pDSNotify, DR_AUDIO_DEFAULT_FRAGMENT_COUNT, notifyPoints); + if (FAILED(hr)) { + goto on_error; + } + + + + // The termination event is used to determine when the playback thread should be terminated. The playback thread + // will wait on this event in addition to the notification events in it's main loop. + pDeviceDS->hStopEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + if (pDeviceDS->hStopEvent == NULL) { + goto on_error; + } + + return (dra_backend_device*)pDeviceDS; + +on_error: + dra_backend_device_close_dsound((dra_backend_device*)pDeviceDS); + return NULL; +} + +dra_backend_device* dra_backend_device_open_capture_dsound(dra_backend* pBackend, unsigned int deviceID, unsigned int channels, unsigned int sampleRate, unsigned int latencyInMilliseconds) +{ + (void)latencyInMilliseconds; + + HRESULT hr; + unsigned int sampleRateInMilliseconds; + unsigned int proposedFramesPerFragment; + unsigned int framesPerFragment; + size_t fragmentSize; + size_t hardwareBufferSize; + + dra_backend_dsound* pBackendDS = (dra_backend_dsound*)pBackend; + if (pBackendDS == NULL) { + return NULL; + } + + dra_backend_device_dsound* pDeviceDS = (dra_backend_device_dsound*)calloc(1, sizeof(*pDeviceDS)); + if (pDeviceDS == NULL) { + goto on_error; + } + + if (channels == 0) { + channels = DR_AUDIO_DEFAULT_CHANNEL_COUNT; + } + + pDeviceDS->pBackend = pBackend; + pDeviceDS->type = dra_device_type_capture; + pDeviceDS->channels = channels; + pDeviceDS->sampleRate = sampleRate; + + hr = pBackendDS->pDirectSoundCaptureCreate8(dra_dsound__get_capture_device_guid_by_id(pBackend, deviceID), &pDeviceDS->pDSCapture, NULL); + if (FAILED(hr)) { + goto on_error; + } + + pDeviceDS->fragmentCount = DR_AUDIO_DEFAULT_FRAGMENT_COUNT; + + // The secondary buffer is the buffer where the real audio data will be written to and used by the hardware device. It's + // size is based on the latency, sample rate and channels. + // + // The format of the secondary buffer should exactly match the primary buffer as to avoid unnecessary data conversions. + sampleRateInMilliseconds = pDeviceDS->sampleRate / 1000; + if (sampleRateInMilliseconds == 0) { + sampleRateInMilliseconds = 1; + } + + // The size of a fragment is sized such that the number of frames contained within it is a multiple of 2. The reason for + // this is to keep it consistent with the ALSA backend. + proposedFramesPerFragment = sampleRateInMilliseconds * latencyInMilliseconds; + framesPerFragment = dra_prev_power_of_2(proposedFramesPerFragment); + if (framesPerFragment == 0) { + framesPerFragment = 2; + } + + pDeviceDS->samplesPerFragment = framesPerFragment * pDeviceDS->channels; + + fragmentSize = pDeviceDS->samplesPerFragment * sizeof(float); + hardwareBufferSize = fragmentSize * pDeviceDS->fragmentCount; + assert(hardwareBufferSize > 0); // <-- If you've triggered this is means you've got something set to 0. You haven't been setting that latency to 0 have you?! That's not allowed! + + + WAVEFORMATEXTENSIBLE wf; + memset(&wf, 0, sizeof(wf)); + wf.Format.cbSize = sizeof(wf); + wf.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wf.Format.nChannels = (WORD)channels; + wf.Format.nSamplesPerSec = (DWORD)sampleRate; + wf.Format.wBitsPerSample = sizeof(float)*8; + wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8; + wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec; + wf.Samples.wValidBitsPerSample = wf.Format.wBitsPerSample; + wf.dwChannelMask = 0; + wf.SubFormat = _g_draGUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + if (channels > 2) { + wf.dwChannelMask = ~(((DWORD)-1) << channels); + } + + DSCBUFFERDESC descDS; + memset(&descDS, 0, sizeof(descDS)); + descDS.dwSize = sizeof(descDS); + descDS.dwFlags = 0; + descDS.dwBufferBytes = (DWORD)hardwareBufferSize; + descDS.lpwfxFormat = (WAVEFORMATEX*)&wf; + + LPDIRECTSOUNDCAPTUREBUFFER pDSCB_Temp; + hr = IDirectSoundCapture_CreateCaptureBuffer(pDeviceDS->pDSCapture, &descDS, &pDSCB_Temp, NULL); + if (FAILED(hr)) { + goto on_error; + } + + hr = IDirectSoundCapture_QueryInterface(pDSCB_Temp, g_draGUID_IID_IDirectSoundCaptureBuffer8, (LPVOID*)&pDeviceDS->pDSCaptureBuffer); + IDirectSoundCaptureBuffer_Release(pDSCB_Temp); + if (FAILED(hr)) { + goto on_error; // Failed to retrieve the DirectSoundCaptureBuffer8 interface. + } + + + // As DirectSound is playing back the hardware buffer it needs to notify dr_audio when it's ready for new data. This is done + // through a notification object which we retrieve from the secondary buffer. + hr = IDirectSoundCaptureBuffer8_QueryInterface(pDeviceDS->pDSCaptureBuffer, g_draGUID_IID_DirectSoundNotify, (void**)&pDeviceDS->pDSNotify); + if (FAILED(hr)) { + goto on_error; + } + + DSBPOSITIONNOTIFY notifyPoints[DR_AUDIO_DEFAULT_FRAGMENT_COUNT]; // One notification event for each fragment. + for (int i = 0; i < DR_AUDIO_DEFAULT_FRAGMENT_COUNT; ++i) + { + pDeviceDS->pNotifyEvents[i] = CreateEventA(NULL, FALSE, FALSE, NULL); + if (pDeviceDS->pNotifyEvents[i] == NULL) { + goto on_error; + } + + notifyPoints[i].dwOffset = (DWORD)(((i+1) * fragmentSize) % hardwareBufferSize); // <-- This is in bytes. + notifyPoints[i].hEventNotify = pDeviceDS->pNotifyEvents[i]; + } + + hr = IDirectSoundNotify_SetNotificationPositions(pDeviceDS->pDSNotify, DR_AUDIO_DEFAULT_FRAGMENT_COUNT, notifyPoints); + if (FAILED(hr)) { + goto on_error; + } + + + // The termination event is used to determine when the capture thread should be terminated. This thread + // will wait on this event in addition to the notification events in it's main loop. + pDeviceDS->hStopEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + if (pDeviceDS->hStopEvent == NULL) { + goto on_error; + } + + return (dra_backend_device*)pDeviceDS; + +on_error: + dra_backend_device_close_dsound((dra_backend_device*)pDeviceDS); + return NULL; +} + +dra_backend_device* dra_backend_device_open_dsound(dra_backend* pBackend, dra_device_type type, unsigned int deviceID, unsigned int channels, unsigned int sampleRate, unsigned int latencyInMilliseconds) +{ + if (type == dra_device_type_playback) { + return dra_backend_device_open_playback_dsound(pBackend, deviceID, channels, sampleRate, latencyInMilliseconds); + } else { + return dra_backend_device_open_capture_dsound(pBackend, deviceID, channels, sampleRate, latencyInMilliseconds); + } +} + +void dra_backend_device_play(dra_backend_device* pDevice) +{ + dra_backend_device_dsound* pDeviceDS = (dra_backend_device_dsound*)pDevice; + if (pDeviceDS == NULL) { + return; + } + + if (pDevice->type == dra_device_type_playback) { + IDirectSoundBuffer_Play(pDeviceDS->pDSSecondaryBuffer, 0, 0, DSBPLAY_LOOPING); + } else { + IDirectSoundCaptureBuffer8_Start(pDeviceDS->pDSCaptureBuffer, DSCBSTART_LOOPING); + } +} + +void dra_backend_device_stop(dra_backend_device* pDevice) +{ + dra_backend_device_dsound* pDeviceDS = (dra_backend_device_dsound*)pDevice; + if (pDeviceDS == NULL) { + return; + } + + if (pDevice->type == dra_device_type_playback) { + // Don't do anything if the buffer is not already playing. + DWORD status; + IDirectSoundBuffer_GetStatus(pDeviceDS->pDSSecondaryBuffer, &status); + if ((status & DSBSTATUS_PLAYING) == 0) { + return; // The buffer is already stopped. + } + + // Stop the playback straight away to ensure output to the hardware device is stopped as soon as possible. + IDirectSoundBuffer_Stop(pDeviceDS->pDSSecondaryBuffer); + IDirectSoundBuffer_SetCurrentPosition(pDeviceDS->pDSSecondaryBuffer, 0); + } else { + // Don't do anything if the buffer is not already playing. + DWORD status; + IDirectSoundCaptureBuffer8_GetStatus(pDeviceDS->pDSCaptureBuffer, &status); + if ((status & DSBSTATUS_PLAYING) == 0) { + return; // The buffer is already stopped. + } + + // Stop capture straight away to ensure output to the hardware device is stopped as soon as possible. + //IDirectSoundCaptureBuffer8_Stop(pDeviceDS->pDSCaptureBuffer); // <-- There's actually a typo in my version of dsound.h which trigger's a compilation error here. The call below is safe, albeit slightly less intuitive. + IDirectSoundCaptureBuffer_Stop(pDeviceDS->pDSCaptureBuffer); + } + + // Now we just need to make dra_backend_device_play() return which in the case of DirectSound we do by + // simply signaling the stop event. + SetEvent(pDeviceDS->hStopEvent); +} + +dr_bool32 dra_backend_device_wait(dra_backend_device* pDevice) // <-- Returns DR_TRUE if the function has returned because it needs more data; DR_FALSE if the device has been stopped or an error has occured. +{ + dra_backend_device_dsound* pDeviceDS = (dra_backend_device_dsound*)pDevice; + if (pDeviceDS == NULL) { + return DR_FALSE; + } + + unsigned int eventCount = DR_AUDIO_DEFAULT_FRAGMENT_COUNT + 1; + HANDLE eventHandles[DR_AUDIO_DEFAULT_FRAGMENT_COUNT + 1]; // +1 for the stop event. + memcpy(eventHandles, pDeviceDS->pNotifyEvents, sizeof(HANDLE) * DR_AUDIO_DEFAULT_FRAGMENT_COUNT); + eventHandles[DR_AUDIO_DEFAULT_FRAGMENT_COUNT] = pDeviceDS->hStopEvent; + + DWORD rc = WaitForMultipleObjects(DR_AUDIO_DEFAULT_FRAGMENT_COUNT + 1, eventHandles, FALSE, INFINITE); + if (rc >= WAIT_OBJECT_0 && rc < eventCount) + { + unsigned int eventIndex = rc - WAIT_OBJECT_0; + HANDLE hEvent = eventHandles[eventIndex]; + + // Has the device been stopped? If so, need to return DR_FALSE. + if (hEvent == pDeviceDS->hStopEvent) { + return DR_FALSE; + } + + // If we get here it means the event that's been signaled represents a fragment. + pDeviceDS->currentFragmentIndex = eventIndex; + return DR_TRUE; + } + + return DR_FALSE; +} + +void* dra_backend_device_map_next_fragment(dra_backend_device* pDevice, size_t* pSamplesInFragmentOut) +{ + assert(pSamplesInFragmentOut != NULL); + + dra_backend_device_dsound* pDeviceDS = (dra_backend_device_dsound*)pDevice; + if (pDeviceDS == NULL) { + return NULL; + } + + if (pDeviceDS->pLockPtr != NULL) { + return NULL; // A fragment is already mapped. Can only have a single fragment mapped at a time. + } + + if (pDevice->type == dra_device_type_playback) { + // If the device is not currently playing, we just return the first fragment. Otherwise we return the fragment that's sitting just past the + // one that's currently playing. + DWORD dwOffset = 0; + DWORD dwBytes = pDeviceDS->samplesPerFragment * sizeof(float); + + DWORD status; + IDirectSoundBuffer_GetStatus(pDeviceDS->pDSSecondaryBuffer, &status); + if ((status & DSBSTATUS_PLAYING) != 0) { + dwOffset = (((pDeviceDS->currentFragmentIndex + 1) % pDeviceDS->fragmentCount) * pDeviceDS->samplesPerFragment) * sizeof(float); + } + + HRESULT hr = IDirectSoundBuffer_Lock(pDeviceDS->pDSSecondaryBuffer, dwOffset, dwBytes, &pDeviceDS->pLockPtr, &pDeviceDS->lockSize, NULL, NULL, 0); + if (FAILED(hr)) { + return NULL; + } + } else { + DWORD dwOffset = (pDeviceDS->currentFragmentIndex * pDeviceDS->samplesPerFragment) * sizeof(float); + DWORD dwBytes = pDeviceDS->samplesPerFragment * sizeof(float); + + HRESULT hr = IDirectSoundCaptureBuffer8_Lock(pDeviceDS->pDSCaptureBuffer, dwOffset, dwBytes, &pDeviceDS->pLockPtr, &pDeviceDS->lockSize, NULL, NULL, 0); + if (FAILED(hr)) { + return NULL; + } + } + + *pSamplesInFragmentOut = pDeviceDS->samplesPerFragment; + return pDeviceDS->pLockPtr; +} + +void dra_backend_device_unmap_next_fragment(dra_backend_device* pDevice) +{ + dra_backend_device_dsound* pDeviceDS = (dra_backend_device_dsound*)pDevice; + if (pDeviceDS == NULL) { + return; + } + + if (pDeviceDS->pLockPtr == NULL) { + return; // Nothing is mapped. + } + + if (pDevice->type == dra_device_type_playback) { + IDirectSoundBuffer_Unlock(pDeviceDS->pDSSecondaryBuffer, pDeviceDS->pLockPtr, pDeviceDS->lockSize, NULL, 0); + } else { + IDirectSoundCaptureBuffer8_Unlock(pDeviceDS->pDSCaptureBuffer, pDeviceDS->pLockPtr, pDeviceDS->lockSize, NULL, 0); + } + + pDeviceDS->pLockPtr = NULL; + pDeviceDS->lockSize = 0; +} +#endif // DR_AUDIO_NO_SOUND +#endif // _WIN32 + +#ifdef __linux__ +#include +#include +#include +#include +#include +#include + +//// Threading (POSIX) //// +typedef void* (* dra_thread_entry_proc)(void* pData); + +dra_thread dra_thread_create(dra_thread_entry_proc entryProc, void* pData) +{ + pthread_t thread; + if (pthread_create(&thread, NULL, entryProc, pData) != 0) { + return NULL; + } + + return (dra_thread)thread; +} + +void dra_thread_delete(dra_thread thread) +{ + (void)thread; +} + +void dra_thread_wait(dra_thread thread) +{ + pthread_join((pthread_t)thread, NULL); +} + + +dra_mutex dra_mutex_create() +{ + // The pthread_mutex_t object is not a void* castable handle type. Just create it on the heap and be done with it. + pthread_mutex_t* mutex = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t)); + if (pthread_mutex_init(mutex, NULL) != 0) { + free(mutex); + mutex = NULL; + } + + return mutex; +} + +void dra_mutex_delete(dra_mutex mutex) +{ + pthread_mutex_destroy((pthread_mutex_t*)mutex); +} + +void dra_mutex_lock(dra_mutex mutex) +{ + pthread_mutex_lock((pthread_mutex_t*)mutex); +} + +void dra_mutex_unlock(dra_mutex mutex) +{ + pthread_mutex_unlock((pthread_mutex_t*)mutex); +} + + +dra_semaphore dra_semaphore_create(int initialValue) +{ + sem_t* semaphore = (sem_t*)malloc(sizeof(sem_t)); + if (sem_init(semaphore, 0, (unsigned int)initialValue) == -1) { + free(semaphore); + semaphore = NULL; + } + + return (dra_semaphore)semaphore; +} + +void dra_semaphore_delete(dra_semaphore semaphore) +{ + sem_close((sem_t*)semaphore); +} + +dr_bool32 dra_semaphore_wait(dra_semaphore semaphore) +{ + return sem_wait((sem_t*)semaphore) != -1; +} + +dr_bool32 dra_semaphore_release(dra_semaphore semaphore) +{ + return sem_post((sem_t*)semaphore) != -1; +} + + +//// ALSA //// + +#ifndef DR_AUDIO_NO_ALSA +#define DR_AUDIO_ENABLE_ALSA +#include + +typedef struct +{ + DR_AUDIO_BASE_BACKEND_ATTRIBS + + int unused; +} dra_backend_alsa; + +typedef struct +{ + DR_AUDIO_BASE_BACKEND_DEVICE_ATTRIBS + + // The ALSA device handle. + snd_pcm_t* deviceALSA; + + // Whether or not the device is currently playing. + dr_bool32 isPlaying; + + // Whether or not the intermediary buffer is mapped. + dr_bool32 isBufferMapped; + + // The intermediary buffer where audio data is written before being submitted to the device. + float* pIntermediaryBuffer; +} dra_backend_device_alsa; + + +static dr_bool32 dra_alsa__get_device_name_by_id(dra_backend* pBackend, unsigned int deviceID, char* deviceNameOut) +{ + assert(pBackend != NULL); + assert(deviceNameOut != NULL); + + deviceNameOut[0] = '\0'; // Safety. + + if (deviceID == 0) { + strcpy(deviceNameOut, "default"); + return DR_TRUE; + } + + + unsigned int iDevice = 0; + + char** deviceHints; + if (snd_device_name_hint(-1, "pcm", (void***)&deviceHints) < 0) { + //printf("Failed to iterate devices."); + return -1; + } + + char** nextDeviceHint = deviceHints; + while (*nextDeviceHint != NULL && iDevice < deviceID) { + nextDeviceHint += 1; + iDevice += 1; + } + + dr_bool32 result = DR_FALSE; + if (iDevice == deviceID) { + strcpy(deviceNameOut, snd_device_name_get_hint(*nextDeviceHint, "NAME")); + result = DR_TRUE; + } + + snd_device_name_free_hint((void**)deviceHints); + + + return result; +} + + +dra_backend* dra_backend_create_alsa() +{ + dra_backend_alsa* pBackendALSA = (dra_backend_alsa*)calloc(1, sizeof(*pBackendALSA)); // <-- Note the calloc() - makes it easier to handle the on_error goto. + if (pBackendALSA == NULL) { + return NULL; + } + + pBackendALSA->type = DR_AUDIO_BACKEND_TYPE_ALSA; + + + return (dra_backend*)pBackendALSA; + +#if 0 +on_error: + if (pBackendALSA != NULL) { + free(pBackendALSA); + } + + return NULL; +#endif +} + +void dra_backend_delete_alsa(dra_backend* pBackend) +{ + dra_backend_alsa* pBackendALSA = (dra_backend_alsa*)pBackend; + if (pBackendALSA == NULL) { + return; + } + + free(pBackend); +} + + +void dra_backend_device_close_alsa(dra_backend_device* pDevice) +{ + dra_backend_device_alsa* pDeviceALSA = (dra_backend_device_alsa*)pDevice; + if (pDeviceALSA == NULL) { + return; + } + + if (pDeviceALSA->deviceALSA != NULL) { + snd_pcm_close(pDeviceALSA->deviceALSA); + } + + free(pDeviceALSA->pIntermediaryBuffer); + free(pDeviceALSA); +} + +dra_backend_device* dra_backend_device_open_playback_alsa(dra_backend* pBackend, unsigned int deviceID, unsigned int channels, unsigned int sampleRate, unsigned int latencyInMilliseconds) +{ + unsigned int periods; + int dir; + size_t sampleRateInMilliseconds; + unsigned int proposedFramesPerFragment; + unsigned int framesPerFragment; + snd_pcm_sw_params_t* pSWParams; + + dra_backend_alsa* pBackendALSA = (dra_backend_alsa*)pBackend; + if (pBackendALSA == NULL) { + return NULL; + } + + snd_pcm_hw_params_t* pHWParams = NULL; + + dra_backend_device_alsa* pDeviceALSA = (dra_backend_device_alsa*)calloc(1, sizeof(*pDeviceALSA)); + if (pDeviceALSA == NULL) { + goto on_error; + } + + pDeviceALSA->pBackend = pBackend; + pDeviceALSA->type = dra_device_type_playback; + pDeviceALSA->channels = channels; + pDeviceALSA->sampleRate = sampleRate; + + char deviceName[1024]; + if (!dra_alsa__get_device_name_by_id(pBackend, deviceID, deviceName)) { // <-- This will return "default" if deviceID is 0. + goto on_error; + } + + if (snd_pcm_open(&pDeviceALSA->deviceALSA, deviceName, SND_PCM_STREAM_PLAYBACK, 0) < 0) { + goto on_error; + } + + + if (snd_pcm_hw_params_malloc(&pHWParams) < 0) { + goto on_error; + } + + if (snd_pcm_hw_params_any(pDeviceALSA->deviceALSA, pHWParams) < 0) { + goto on_error; + } + + if (snd_pcm_hw_params_set_access(pDeviceALSA->deviceALSA, pHWParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { + goto on_error; + } + + if (snd_pcm_hw_params_set_format(pDeviceALSA->deviceALSA, pHWParams, SND_PCM_FORMAT_FLOAT_LE) < 0) { + goto on_error; + } + + + if (snd_pcm_hw_params_set_rate_near(pDeviceALSA->deviceALSA, pHWParams, &sampleRate, 0) < 0) { + goto on_error; + } + + if (snd_pcm_hw_params_set_channels_near(pDeviceALSA->deviceALSA, pHWParams, &channels) < 0) { + goto on_error; + } + + pDeviceALSA->sampleRate = sampleRate; + pDeviceALSA->channels = channels; + + periods = DR_AUDIO_DEFAULT_FRAGMENT_COUNT; + dir = 1; + if (snd_pcm_hw_params_set_periods_near(pDeviceALSA->deviceALSA, pHWParams, &periods, &dir) < 0) { + //printf("Failed to set periods.\n"); + goto on_error; + } + + pDeviceALSA->fragmentCount = periods; + + //printf("Periods: %d | Direction: %d\n", periods, dir); + + + sampleRateInMilliseconds = pDeviceALSA->sampleRate / 1000; + if (sampleRateInMilliseconds == 0) { + sampleRateInMilliseconds = 1; + } + + + // According to the ALSA documentation, the value passed to snd_pcm_sw_params_set_avail_min() must be a power + // of 2 on some hardware. The value passed to this function is the size in frames of a fragment. Thus, to be + // as robust as possible the size of the hardware buffer should be sized based on the size of a closest power- + // of-two fragment. + // + // To calculate the size of a fragment, the first step is to determine the initial proposed size. From that + // it is dropped to the previous power of two. The reason for this is that, based on admittedly very basic + // testing, ALSA seems to have good latency characteristics, and less latency is always preferable. + proposedFramesPerFragment = sampleRateInMilliseconds * latencyInMilliseconds; + framesPerFragment = dra_prev_power_of_2(proposedFramesPerFragment); + if (framesPerFragment == 0) { + framesPerFragment = 2; + } + + pDeviceALSA->samplesPerFragment = framesPerFragment * pDeviceALSA->channels; + + if (snd_pcm_hw_params_set_buffer_size(pDeviceALSA->deviceALSA, pHWParams, framesPerFragment * pDeviceALSA->fragmentCount) < 0) { + //printf("Failed to set buffer size.\n"); + goto on_error; + } + + + if (snd_pcm_hw_params(pDeviceALSA->deviceALSA, pHWParams) < 0) { + goto on_error; + } + + snd_pcm_hw_params_free(pHWParams); + + + + // Software params. There needs to be at least fragmentSize bytes in the hardware buffer before playing it, and there needs + // be fragmentSize bytes available after every wait. + pSWParams = NULL; + if (snd_pcm_sw_params_malloc(&pSWParams) < 0) { + goto on_error; + } + + if (snd_pcm_sw_params_current(pDeviceALSA->deviceALSA, pSWParams) != 0) { + goto on_error; + } + + if (snd_pcm_sw_params_set_start_threshold(pDeviceALSA->deviceALSA, pSWParams, framesPerFragment) != 0) { + goto on_error; + } + if (snd_pcm_sw_params_set_avail_min(pDeviceALSA->deviceALSA, pSWParams, framesPerFragment) != 0) { + goto on_error; + } + + if (snd_pcm_sw_params(pDeviceALSA->deviceALSA, pSWParams) != 0) { + goto on_error; + } + snd_pcm_sw_params_free(pSWParams); + + + // The intermediary buffer that will be used for mapping/unmapping. + pDeviceALSA->isBufferMapped = DR_FALSE; + pDeviceALSA->pIntermediaryBuffer = (float*)malloc(pDeviceALSA->samplesPerFragment * sizeof(float)); + if (pDeviceALSA->pIntermediaryBuffer == NULL) { + goto on_error; + } + + return (dra_backend_device*)pDeviceALSA; + +on_error: + if (pHWParams) { + snd_pcm_hw_params_free(pHWParams); + } + + if (pDeviceALSA != NULL) { + if (pDeviceALSA->deviceALSA != NULL) { + snd_pcm_close(pDeviceALSA->deviceALSA); + } + + free(pDeviceALSA->pIntermediaryBuffer); + free(pDeviceALSA); + } + + return NULL; +} + +dra_backend_device* dra_backend_device_open_capture_alsa(dra_backend* pBackend, unsigned int deviceID, unsigned int channels, unsigned int sampleRate, unsigned int latencyInMilliseconds) +{ + unsigned int periods; + int dir; + size_t sampleRateInMilliseconds; + unsigned int proposedFramesPerFragment; + unsigned int framesPerFragment; + snd_pcm_sw_params_t* pSWParams; + + dra_backend_alsa* pBackendALSA = (dra_backend_alsa*)pBackend; + if (pBackendALSA == NULL) { + return NULL; + } + + snd_pcm_hw_params_t* pHWParams = NULL; + + dra_backend_device_alsa* pDeviceALSA = (dra_backend_device_alsa*)calloc(1, sizeof(*pDeviceALSA)); + if (pDeviceALSA == NULL) { + goto on_error; + } + + pDeviceALSA->pBackend = pBackend; + pDeviceALSA->type = dra_device_type_capture; + pDeviceALSA->channels = channels; + pDeviceALSA->sampleRate = sampleRate; + + char deviceName[1024]; + if (!dra_alsa__get_device_name_by_id(pBackend, deviceID, deviceName)) { // <-- This will return "default" if deviceID is 0. + goto on_error; + } + + if (snd_pcm_open(&pDeviceALSA->deviceALSA, deviceName, SND_PCM_STREAM_CAPTURE, 0) < 0) { + goto on_error; + } + + + if (snd_pcm_hw_params_malloc(&pHWParams) < 0) { + goto on_error; + } + + if (snd_pcm_hw_params_any(pDeviceALSA->deviceALSA, pHWParams) < 0) { + goto on_error; + } + + if (snd_pcm_hw_params_set_access(pDeviceALSA->deviceALSA, pHWParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { + goto on_error; + } + + if (snd_pcm_hw_params_set_format(pDeviceALSA->deviceALSA, pHWParams, SND_PCM_FORMAT_FLOAT_LE) < 0) { + goto on_error; + } + + + if (snd_pcm_hw_params_set_rate_near(pDeviceALSA->deviceALSA, pHWParams, &sampleRate, 0) < 0) { + goto on_error; + } + + if (snd_pcm_hw_params_set_channels_near(pDeviceALSA->deviceALSA, pHWParams, &channels) < 0) { + goto on_error; + } + + pDeviceALSA->sampleRate = sampleRate; + pDeviceALSA->channels = channels; + + periods = DR_AUDIO_DEFAULT_FRAGMENT_COUNT; + dir = 1; + if (snd_pcm_hw_params_set_periods_near(pDeviceALSA->deviceALSA, pHWParams, &periods, &dir) < 0) { + //printf("Failed to set periods.\n"); + goto on_error; + } + + pDeviceALSA->fragmentCount = periods; + + //printf("Periods: %d | Direction: %d\n", periods, dir); + + + sampleRateInMilliseconds = pDeviceALSA->sampleRate / 1000; + if (sampleRateInMilliseconds == 0) { + sampleRateInMilliseconds = 1; + } + + + // According to the ALSA documentation, the value passed to snd_pcm_sw_params_set_avail_min() must be a power + // of 2 on some hardware. The value passed to this function is the size in frames of a fragment. Thus, to be + // as robust as possible the size of the hardware buffer should be sized based on the size of a closest power- + // of-two fragment. + // + // To calculate the size of a fragment, the first step is to determine the initial proposed size. From that + // it is dropped to the previous power of two. The reason for this is that, based on admittedly very basic + // testing, ALSA seems to have good latency characteristics, and less latency is always preferable. + proposedFramesPerFragment = sampleRateInMilliseconds * latencyInMilliseconds; + framesPerFragment = dra_prev_power_of_2(proposedFramesPerFragment); + if (framesPerFragment == 0) { + framesPerFragment = 2; + } + + pDeviceALSA->samplesPerFragment = framesPerFragment * pDeviceALSA->channels; + + if (snd_pcm_hw_params_set_buffer_size(pDeviceALSA->deviceALSA, pHWParams, framesPerFragment * pDeviceALSA->fragmentCount) < 0) { + //printf("Failed to set buffer size.\n"); + goto on_error; + } + + + if (snd_pcm_hw_params(pDeviceALSA->deviceALSA, pHWParams) < 0) { + goto on_error; + } + + snd_pcm_hw_params_free(pHWParams); + + + + // Software params. There needs to be at least fragmentSize bytes in the hardware buffer before playing it, and there needs + // be fragmentSize bytes available after every wait. + pSWParams = NULL; + if (snd_pcm_sw_params_malloc(&pSWParams) < 0) { + goto on_error; + } + + if (snd_pcm_sw_params_current(pDeviceALSA->deviceALSA, pSWParams) != 0) { + goto on_error; + } + + if (snd_pcm_sw_params_set_start_threshold(pDeviceALSA->deviceALSA, pSWParams, framesPerFragment) != 0) { + goto on_error; + } + if (snd_pcm_sw_params_set_avail_min(pDeviceALSA->deviceALSA, pSWParams, framesPerFragment) != 0) { + goto on_error; + } + + if (snd_pcm_sw_params(pDeviceALSA->deviceALSA, pSWParams) != 0) { + goto on_error; + } + snd_pcm_sw_params_free(pSWParams); + + // The intermediary buffer that will be used for mapping/unmapping. + pDeviceALSA->isBufferMapped = DR_FALSE; + pDeviceALSA->pIntermediaryBuffer = (float*)malloc(pDeviceALSA->samplesPerFragment * sizeof(float)); + if (pDeviceALSA->pIntermediaryBuffer == NULL) { + goto on_error; + } + + return (dra_backend_device*)pDeviceALSA; + +on_error: + dra_backend_device_close_alsa((dra_backend_device*)pDeviceALSA); + return NULL; +} + +dra_backend_device* dra_backend_device_open_alsa(dra_backend* pBackend, dra_device_type type, unsigned int deviceID, unsigned int channels, unsigned int sampleRate, unsigned int latencyInMilliseconds) +{ + if (type == dra_device_type_playback) { + return dra_backend_device_open_playback_alsa(pBackend, deviceID, channels, sampleRate, latencyInMilliseconds); + } else { + return dra_backend_device_open_capture_alsa(pBackend, deviceID, channels, sampleRate, latencyInMilliseconds); + } +} + + +void dra_backend_device_play(dra_backend_device* pDevice) +{ + dra_backend_device_alsa* pDeviceALSA = (dra_backend_device_alsa*)pDevice; + if (pDeviceALSA == NULL) { + return; + } + + snd_pcm_prepare(pDeviceALSA->deviceALSA); + pDeviceALSA->isPlaying = DR_TRUE; +} + +void dra_backend_device_stop(dra_backend_device* pDevice) +{ + dra_backend_device_alsa* pDeviceALSA = (dra_backend_device_alsa*)pDevice; + if (pDeviceALSA == NULL) { + return; + } + + snd_pcm_drop(pDeviceALSA->deviceALSA); + pDeviceALSA->isPlaying = DR_FALSE; +} + +dr_bool32 dra_backend_device_wait(dra_backend_device* pDevice) +{ + dra_backend_device_alsa* pDeviceALSA = (dra_backend_device_alsa*)pDevice; + if (pDeviceALSA == NULL) { + return DR_FALSE; + } + + if (!pDeviceALSA->isPlaying) { + return DR_FALSE; + } + + if (pDevice->type == dra_device_type_playback) { + int result = snd_pcm_wait(pDeviceALSA->deviceALSA, -1); + if (result > 0) { + return DR_TRUE; + } + + if (result == -EPIPE) { + // xrun. Prepare the device again and just return DR_TRUE. + snd_pcm_prepare(pDeviceALSA->deviceALSA); + return DR_TRUE; + } + } else { + snd_pcm_uframes_t frameCount = pDeviceALSA->samplesPerFragment / pDeviceALSA->channels; + snd_pcm_sframes_t framesRead = snd_pcm_readi(pDeviceALSA->deviceALSA, pDeviceALSA->pIntermediaryBuffer, frameCount); + if (framesRead > 0) { + return DR_TRUE; + } + + if (framesRead == -EPIPE) { + // xrun. Prepare the device again and just return DR_TRUE. + snd_pcm_prepare(pDeviceALSA->deviceALSA); + return DR_TRUE; + } + } + + return DR_FALSE; +} + +void* dra_backend_device_map_next_fragment(dra_backend_device* pDevice, size_t* pSamplesInFragmentOut) +{ + assert(pSamplesInFragmentOut != NULL); + + dra_backend_device_alsa* pDeviceALSA = (dra_backend_device_alsa*)pDevice; + if (pDeviceALSA == NULL) { + return NULL; + } + + if (pDeviceALSA->isBufferMapped) { + return NULL; // A fragment is already mapped. Can only have a single fragment mapped at a time. + } + + //if (pDeviceALSA->type == dra_device_type_capture) { + // snd_pcm_readi(pDeviceALSA->deviceALSA, pDeviceALSA->pIntermediaryBuffer, pDeviceALSA->samplesPerFragment / pDeviceALSA->channels); + //} + + *pSamplesInFragmentOut = pDeviceALSA->samplesPerFragment; + return pDeviceALSA->pIntermediaryBuffer; +} + +void dra_backend_device_unmap_next_fragment(dra_backend_device* pDevice) +{ + dra_backend_device_alsa* pDeviceALSA = (dra_backend_device_alsa*)pDevice; + if (pDeviceALSA == NULL) { + return; + } + + if (pDeviceALSA->isBufferMapped) { + return; // Nothing is mapped. + } + + // Unammping is when the data is written to the device. + if (pDeviceALSA->type == dra_device_type_playback) { + snd_pcm_writei(pDeviceALSA->deviceALSA, pDeviceALSA->pIntermediaryBuffer, pDeviceALSA->samplesPerFragment / pDeviceALSA->channels); + } +} +#endif // DR_AUDIO_NO_ALSA +#endif // __linux__ + + +void dra_thread_wait_and_delete(dra_thread thread) +{ + dra_thread_wait(thread); + dra_thread_delete(thread); +} + + +dra_backend* dra_backend_create() +{ + dra_backend* pBackend = NULL; + +#ifdef DR_AUDIO_ENABLE_DSOUND + pBackend = dra_backend_create_dsound(); + if (pBackend != NULL) { + return pBackend; + } +#endif + +#ifdef DR_AUDIO_ENABLE_ALSA + pBackend = dra_backend_create_alsa(); + if (pBackend != NULL) { + return pBackend; + } +#endif + + // If we get here it means we couldn't find a backend. Default to a NULL backend? Returning NULL makes it clearer that an error occured. + return NULL; +} + +void dra_backend_delete(dra_backend* pBackend) +{ + if (pBackend == NULL) { + return; + } + +#ifdef DR_AUDIO_ENABLE_DSOUND + if (pBackend->type == DR_AUDIO_BACKEND_TYPE_DSOUND) { + dra_backend_delete_dsound(pBackend); + return; + } +#endif + +#ifdef DR_AUDIO_ENABLE_ALSA + if (pBackend->type == DR_AUDIO_BACKEND_TYPE_ALSA) { + dra_backend_delete_alsa(pBackend); + return; + } +#endif + + // Should never get here. If this assert is triggered it means you haven't plugged in the API in the list above. + assert(DR_FALSE); +} + + +dra_backend_device* dra_backend_device_open(dra_backend* pBackend, dra_device_type type, unsigned int deviceID, unsigned int channels, unsigned int sampleRate, unsigned int latencyInMilliseconds) +{ + if (pBackend == NULL) { + return NULL; + } + +#ifdef DR_AUDIO_ENABLE_DSOUND + if (pBackend->type == DR_AUDIO_BACKEND_TYPE_DSOUND) { + return dra_backend_device_open_dsound(pBackend, type, deviceID, channels, sampleRate, latencyInMilliseconds); + } +#endif + +#ifdef DR_AUDIO_ENABLE_ALSA + if (pBackend->type == DR_AUDIO_BACKEND_TYPE_ALSA) { + return dra_backend_device_open_alsa(pBackend, type, deviceID, channels, sampleRate, latencyInMilliseconds); + } +#endif + + + // Should never get here. If this assert is triggered it means you haven't plugged in the API in the list above. + assert(DR_FALSE); + return NULL; +} + +void dra_backend_device_close(dra_backend_device* pDevice) +{ + if (pDevice == NULL) { + return; + } + + assert(pDevice->pBackend != NULL); + +#ifdef DR_AUDIO_ENABLE_DSOUND + if (pDevice->pBackend->type == DR_AUDIO_BACKEND_TYPE_DSOUND) { + dra_backend_device_close_dsound(pDevice); + return; + } +#endif + +#ifdef DR_AUDIO_ENABLE_ALSA + if (pDevice->pBackend->type == DR_AUDIO_BACKEND_TYPE_ALSA) { + dra_backend_device_close_alsa(pDevice); + return; + } +#endif +} + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Cross Platform +// +/////////////////////////////////////////////////////////////////////////////// + +// Reads the next frame. +// +// Frames are retrieved with respect to the device the voice is attached to. What this basically means is +// that any data conversions will be done within this function. +// +// The return value is a pointer to the voice containing the converted samples, always as floating point. +// +// If the voice is in the same format as the device (floating point, same sample rate and channels), then +// this function will be on a fast path and will return almost immediately with a pointer that points to +// the voice's actual data without any data conversion. +// +// If an error occurs, null is returned. Null will be returned if the end of the voice's buffer is reached +// and it's non-looping. This will not return NULL if the voice is looping - it will just loop back to the +// start as one would expect. +// +// This function is not thread safe, but can be called from multiple threads if you do your own +// synchronization. Just keep in mind that the return value may point to the voice's actual internal data. +float* dra_voice__next_frame(dra_voice* pVoice); + +// dra_voice__next_frames() +size_t dra_voice__next_frames(dra_voice* pVoice, size_t frameCount, float* pSamplesOut); + + +dra_result dra_context_init(dra_context* pContext) +{ + if (pContext == NULL) return DRA_RESULT_INVALID_ARGS; + memset(pContext, 0, sizeof(*pContext)); + + // We need a backend first. + pContext->pBackend = dra_backend_create(); + if (pContext->pBackend == NULL) { + return DRA_RESULT_NO_BACKEND; // Failed to create a backend. + } + + return DRA_RESULT_SUCCESS; +} + +void dra_context_uninit(dra_context* pContext) +{ + if (pContext == NULL || pContext->pBackend == NULL) return; + dra_backend_delete(pContext->pBackend); +} + +dra_result dra_context_create(dra_context** ppContext) +{ + if (ppContext == NULL) return DRA_RESULT_INVALID_ARGS; + *ppContext = NULL; + + dra_context* pContext = (dra_context*)malloc(sizeof(*pContext)); + if (pContext == NULL) { + return DRA_RESULT_OUT_OF_MEMORY; + } + + dra_result result = dra_context_init(pContext); + if (result != DRA_RESULT_SUCCESS) { + free(pContext); + return result; + } + + *ppContext = pContext; + return DRA_RESULT_SUCCESS; +} + +void dra_context_delete(dra_context* pContext) +{ + if (pContext == NULL) return; + + dra_context_uninit(pContext); + free(pContext); +} + + +void dra_event_queue__schedule_event(dra__event_queue* pQueue, dra__event* pEvent) +{ + if (pQueue == NULL || pEvent == NULL) { + return; + } + + dra_mutex_lock(pQueue->lock); + { + if (pQueue->eventCount == pQueue->eventBufferSize) + { + // Ran out of room. Resize. + size_t newEventBufferSize = (pQueue->eventBufferSize == 0) ? 16 : pQueue->eventBufferSize*2; + dra__event* pNewEvents = (dra__event*)malloc(newEventBufferSize * sizeof(*pNewEvents)); + if (pNewEvents == NULL) { + return; + } + + for (size_t i = 0; i < pQueue->eventCount; ++i) { + pQueue->pEvents[i] = pQueue->pEvents[(pQueue->firstEvent + i) % pQueue->eventBufferSize]; + } + + pQueue->firstEvent = 0; + pQueue->eventBufferSize = newEventBufferSize; + pQueue->pEvents = pNewEvents; + } + + assert(pQueue->eventCount < pQueue->eventBufferSize); + + pQueue->pEvents[(pQueue->firstEvent + pQueue->eventCount) % pQueue->eventBufferSize] = *pEvent; + pQueue->eventCount += 1; + } + dra_mutex_unlock(pQueue->lock); +} + +void dra_event_queue__cancel_events_of_voice(dra__event_queue* pQueue, dra_voice* pVoice) +{ + if (pQueue == NULL || pVoice == NULL) { + return; + } + + dra_mutex_lock(pQueue->lock); + { + // We don't actually remove anything from the queue, but instead zero out the event's data. + for (size_t i = 0; i < pQueue->eventCount; ++i) { + dra__event* pEvent = &pQueue->pEvents[(pQueue->firstEvent + i) % pQueue->eventBufferSize]; + if (pEvent->pVoice == pVoice) { + pEvent->pVoice = NULL; + pEvent->proc = NULL; + } + } + } + dra_mutex_unlock(pQueue->lock); +} + +dr_bool32 dra_event_queue__next_event(dra__event_queue* pQueue, dra__event* pEventOut) +{ + if (pQueue == NULL || pEventOut == NULL) { + return DR_FALSE; + } + + dr_bool32 result = DR_FALSE; + dra_mutex_lock(pQueue->lock); + { + if (pQueue->eventCount > 0) { + *pEventOut = pQueue->pEvents[pQueue->firstEvent]; + pQueue->firstEvent = (pQueue->firstEvent + 1) % pQueue->eventBufferSize; + pQueue->eventCount -= 1; + result = DR_TRUE; + } + } + dra_mutex_unlock(pQueue->lock); + + return result; +} + +void dra_event_queue__post_events(dra__event_queue* pQueue) +{ + if (pQueue == NULL) { + return; + } + + dra__event nextEvent; + while (dra_event_queue__next_event(pQueue, &nextEvent)) { + if (nextEvent.proc) { + nextEvent.proc(nextEvent.id, nextEvent.pUserData); + } + } +} + + +void dra_device__post_event(dra_device* pDevice, dra_thread_event_type type) +{ + assert(pDevice != NULL); + + pDevice->nextThreadEventType = type; + dra_semaphore_release(pDevice->threadEventSem); +} + +void dra_device__lock(dra_device* pDevice) +{ + assert(pDevice != NULL); + dra_mutex_lock(pDevice->mutex); +} + +void dra_device__unlock(dra_device* pDevice) +{ + assert(pDevice != NULL); + dra_mutex_unlock(pDevice->mutex); +} + +dr_bool32 dra_device__is_playing_nolock(dra_device* pDevice) +{ + assert(pDevice != NULL); + return pDevice->isPlaying; +} + +dr_bool32 dra_device__mix_next_fragment(dra_device* pDevice) +{ + assert(pDevice != NULL); + + size_t samplesInFragment; + void* pSampleData = dra_backend_device_map_next_fragment(pDevice->pBackendDevice, &samplesInFragment); + if (pSampleData == NULL) { + dra_backend_device_stop(pDevice->pBackendDevice); + return DR_FALSE; + } + + size_t framesInFragment = samplesInFragment / pDevice->channels; + size_t framesMixed = dra_mixer_mix_next_frames(pDevice->pMasterMixer, framesInFragment); + + memcpy(pSampleData, pDevice->pMasterMixer->pStagingBuffer, (size_t)samplesInFragment * sizeof(float)); + + if (pDevice->onSamplesProcessed) { + pDevice->onSamplesProcessed(pDevice, framesMixed * pDevice->channels, (const float*)pSampleData, pDevice->pUserDataForOnSamplesProcessed); + } + + dra_backend_device_unmap_next_fragment(pDevice->pBackendDevice); + + if (framesMixed == 0) { + pDevice->stopOnNextFragment = DR_TRUE; + } + + //printf("Mixed next fragment into %p\n", pSampleData); + return DR_TRUE; +} + +void dra_device__play(dra_device* pDevice) +{ + assert(pDevice != NULL); + + dra_device__lock(pDevice); + { + // Don't do anything if the device is already playing. + if (!dra_device__is_playing_nolock(pDevice)) + { + assert(pDevice->pBackendDevice->type == dra_device_type_capture || pDevice->playingVoicesCount > 0); + + dra_device__post_event(pDevice, dra_thread_event_type_play); + pDevice->isPlaying = DR_TRUE; + pDevice->stopOnNextFragment = DR_FALSE; + } + } + dra_device__unlock(pDevice); +} + +void dra_device__stop(dra_device* pDevice) +{ + assert(pDevice != NULL); + + dra_device__lock(pDevice); + { + // Don't do anything if the device is already stopped. + if (dra_device__is_playing_nolock(pDevice)) + { + //assert(pDevice->playingVoicesCount == 0); + + dra_backend_device_stop(pDevice->pBackendDevice); + pDevice->isPlaying = DR_FALSE; + } + } + dra_device__unlock(pDevice); +} + +void dra_device__voice_playback_count_inc(dra_device* pDevice) +{ + assert(pDevice != NULL); + + dra_device__lock(pDevice); + { + pDevice->playingVoicesCount += 1; + pDevice->stopOnNextFragment = DR_FALSE; + } + dra_device__unlock(pDevice); +} + +void dra_device__voice_playback_count_dec(dra_device* pDevice) +{ + dra_device__lock(pDevice); + { + pDevice->playingVoicesCount -= 1; + } + dra_device__unlock(pDevice); +} + +// The entry point signature is slightly different depending on whether or not we're using Win32 or POSIX threads. +#ifdef _WIN32 +DWORD dra_device__thread_proc(LPVOID pData) +#else +void* dra_device__thread_proc(void* pData) +#endif +{ + dra_device* pDevice = (dra_device*)pData; + assert(pDevice != NULL); + + // The thread is always open for the life of the device. The loop below will only terminate when a terminate message is received. + for (;;) + { + // Wait for an event... + dra_semaphore_wait(pDevice->threadEventSem); + + if (pDevice->nextThreadEventType == dra_thread_event_type_terminate) { + //printf("Terminated!\n"); + break; + } + + if (pDevice->nextThreadEventType == dra_thread_event_type_play) + { + if (pDevice->pBackendDevice->type == dra_device_type_playback) { + // The backend device needs to start playing, but we first need to ensure it has an initial chunk of data available. + dra_device__mix_next_fragment(pDevice); + } + + // Start playing the backend device only after the initial fragment has been mixed, and only if it's a playback device. + dra_backend_device_play(pDevice->pBackendDevice); + + // There could be "play" events needing to be posted. + dra_event_queue__post_events(&pDevice->eventQueue); + + + // Wait for the device to request more data... + while (dra_backend_device_wait(pDevice->pBackendDevice)) { + dra_event_queue__post_events(&pDevice->eventQueue); + + if (pDevice->stopOnNextFragment) { + dra_device__stop(pDevice); // <-- Don't break from the loop here. Instead have dra_backend_device_wait() return naturally from the stop notification. + } else { + if (pDevice->pBackendDevice->type == dra_device_type_playback) { + dra_device__mix_next_fragment(pDevice); + } else { + size_t sampleCount; + void* pSampleData = dra_backend_device_map_next_fragment(pDevice->pBackendDevice, &sampleCount); + if (pSampleData != NULL) { + if (pDevice->onSamplesProcessed) { + pDevice->onSamplesProcessed(pDevice, sampleCount, (const float*)pSampleData, pDevice->pUserDataForOnSamplesProcessed); + } + + dra_backend_device_unmap_next_fragment(pDevice->pBackendDevice); + } + } + } + } + + // There could be some events needing to be posted. + dra_event_queue__post_events(&pDevice->eventQueue); + //printf("Stopped!\n"); + + // Don't fall through. + continue; + } + } + + return 0; +} + +dra_result dra_device_init_ex(dra_context* pContext, dra_device_type type, unsigned int deviceID, unsigned int channels, unsigned int sampleRate, unsigned int latencyInMilliseconds, dra_device* pDevice) +{ + if (pDevice == NULL) return DRA_RESULT_INVALID_ARGS; + + dr_bool32 ownsContext = DR_FALSE; + if (pContext == NULL) { + pContext = (dra_context*)malloc(sizeof(*pContext)); + if (pContext == NULL) { + return DRA_RESULT_OUT_OF_MEMORY; + } + + dra_result result = dra_context_init(pContext); + if (result != DRA_RESULT_SUCCESS) { + return result; + } + + ownsContext = DR_TRUE; + } + + if (sampleRate == 0) sampleRate = DR_AUDIO_DEFAULT_SAMPLE_RATE; + if (latencyInMilliseconds == 0) latencyInMilliseconds = DR_AUDIO_DEFAULT_LATENCY; + + + dra_result result = DRA_RESULT_SUCCESS; + + memset(pDevice, 0, sizeof(*pDevice)); + pDevice->pContext = pContext; + pDevice->ownsContext = ownsContext; + + pDevice->pBackendDevice = dra_backend_device_open(pContext->pBackend, type, deviceID, channels, sampleRate, latencyInMilliseconds); + if (pDevice->pBackendDevice == NULL) { + result = DRA_RESULT_NO_BACKEND_DEVICE; + goto on_error; + } + + pDevice->channels = pDevice->pBackendDevice->channels; + pDevice->sampleRate = pDevice->pBackendDevice->sampleRate; + + + pDevice->mutex = dra_mutex_create(); + if (pDevice->mutex == NULL) { + result = DRA_RESULT_UNKNOWN_ERROR; // TODO: Change this to the return value of dra_mutex_create(). + goto on_error; + } + + pDevice->threadEventSem = dra_semaphore_create(0); + if (pDevice->threadEventSem == NULL) { + result = DRA_RESULT_UNKNOWN_ERROR; // TODO: Change this to the return value of dra_semaphore_create(). + goto on_error; + } + + result = dra_mixer_create(pDevice, &pDevice->pMasterMixer); + if (result != DRA_RESULT_SUCCESS) { + goto on_error; + } + + + pDevice->eventQueue.lock = dra_mutex_create(); + if (pDevice->eventQueue.lock == NULL) { + result = DRA_RESULT_UNKNOWN_ERROR; // TODO: Change this to the return value of dra_mutex_create(). + goto on_error; + } + + + // Create the thread last to ensure the device is in a valid state as soon as the entry procedure is run. + pDevice->thread = dra_thread_create(dra_device__thread_proc, pDevice); + if (pDevice->thread == NULL) { + result = DRA_RESULT_UNKNOWN_ERROR; // TODO: Change this to the return value of dra_thread_create(). + goto on_error; + } + + return result; + +on_error: + if (pDevice != NULL) { + if (pDevice->pMasterMixer != NULL) dra_mixer_delete(pDevice->pMasterMixer); + if (pDevice->pBackendDevice != NULL) dra_backend_device_close(pDevice->pBackendDevice); + if (pDevice->threadEventSem != NULL) dra_semaphore_delete(pDevice->threadEventSem); + if (pDevice->mutex != NULL) dra_mutex_delete(pDevice->mutex); + + if (pDevice->ownsContext) { + dra_context_uninit(pDevice->pContext); + free(pDevice->pContext); + } + } + + return result; +} + +dra_result dra_device_init(dra_context* pContext, dra_device_type type, dra_device* pDevice) +{ + return dra_device_init_ex(pContext, type, 0, 0, DR_AUDIO_DEFAULT_SAMPLE_RATE, DR_AUDIO_DEFAULT_LATENCY, pDevice); +} + +void dra_device_uninit(dra_device* pDevice) +{ + if (pDevice == NULL || pDevice->pContext == NULL) return; + + // Mark the device as closed in order to prevent other threads from doing work after closing. + dra_device__lock(pDevice); + { + pDevice->isClosed = DR_TRUE; + } + dra_device__unlock(pDevice); + + // Stop playback before doing anything else. + dra_device__stop(pDevice); + + // The background thread needs to be terminated at this point. + dra_device__post_event(pDevice, dra_thread_event_type_terminate); + dra_thread_wait_and_delete(pDevice->thread); + + + // At this point the device is marked as closed which should prevent voice's and mixers from being created and deleted. We now need + // to delete the master mixer which in turn will delete all of the attached voices and submixers. + if (pDevice->pMasterMixer != NULL) { + dra_mixer_delete(pDevice->pMasterMixer); + } + + + if (pDevice->pBackendDevice != NULL) { + dra_backend_device_close(pDevice->pBackendDevice); + } + + if (pDevice->threadEventSem != NULL) { + dra_semaphore_delete(pDevice->threadEventSem); + } + + if (pDevice->mutex != NULL) { + dra_mutex_delete(pDevice->mutex); + } + + + if (pDevice->eventQueue.pEvents) { + free(pDevice->eventQueue.pEvents); + } + + + if (pDevice->ownsContext) { + dra_context_uninit(pDevice->pContext); + free(pDevice->pContext); + } +} + + +dra_result dra_device_create_ex(dra_context* pContext, dra_device_type type, unsigned int deviceID, unsigned int channels, unsigned int sampleRate, unsigned int latencyInMilliseconds, dra_device** ppDevice) +{ + if (ppDevice == NULL) return DRA_RESULT_INVALID_ARGS; + *ppDevice = NULL; + + dra_device* pDevice = (dra_device*)malloc(sizeof(*pDevice)); + if (pDevice == NULL) { + return DRA_RESULT_OUT_OF_MEMORY; + } + + dra_result result = dra_device_init_ex(pContext, type, deviceID, channels, sampleRate, latencyInMilliseconds, pDevice); + if (result != DRA_RESULT_SUCCESS) { + free(pDevice); + return result; + } + + *ppDevice = pDevice; + return DRA_RESULT_SUCCESS; +} + +dra_result dra_device_create(dra_context* pContext, dra_device_type type, dra_device** ppDevice) +{ + return dra_device_create_ex(pContext, type, 0, 0, DR_AUDIO_DEFAULT_SAMPLE_RATE, DR_AUDIO_DEFAULT_LATENCY, ppDevice); +} + +void dra_device_delete(dra_device* pDevice) +{ + if (pDevice == NULL) return; + + dra_device_uninit(pDevice); + free(pDevice); +} + +dra_result dra_device_start(dra_device* pDevice) +{ + if (pDevice == NULL || pDevice->pBackendDevice->type == dra_device_type_playback) return DRA_RESULT_INVALID_ARGS; + + dra_device__play(pDevice); + return DRA_RESULT_SUCCESS; +} + +dra_result dra_device_stop(dra_device* pDevice) +{ + if (pDevice == NULL || pDevice->pBackendDevice->type == dra_device_type_playback) return DRA_RESULT_INVALID_ARGS; + + dra_device__stop(pDevice); + return DRA_RESULT_SUCCESS; +} + +void dra_device_set_samples_processed_callback(dra_device* pDevice, dra_samples_processed_proc proc, void* pUserData) +{ + if (pDevice == NULL) return; + pDevice->onSamplesProcessed = proc; + pDevice->pUserDataForOnSamplesProcessed = pUserData; +} + + +dra_result dra_mixer_create(dra_device* pDevice, dra_mixer** ppMixer) +{ + if (ppMixer == NULL) return DRA_RESULT_INVALID_ARGS; + *ppMixer = NULL; + + if (pDevice == NULL) return DRA_RESULT_INVALID_ARGS; + + + // There needs to be two blocks of memory at the end of the mixer - one for the staging buffer and another for the buffer that + // will store the float32 samples of the voice currently being mixed. + size_t extraDataSize = (size_t)pDevice->pBackendDevice->samplesPerFragment * sizeof(float) * 2; + dra_mixer* pMixer = (dra_mixer*)calloc(1, sizeof(*pMixer) + extraDataSize); + if (pMixer == NULL) { + return DRA_RESULT_OUT_OF_MEMORY; + } + + pMixer->pDevice = pDevice; + pMixer->linearVolume = 1; + + pMixer->pStagingBuffer = pMixer->pData; + pMixer->pNextSamplesToMix = pMixer->pStagingBuffer + pDevice->pBackendDevice->samplesPerFragment; + + // Attach the mixer to the master mixer by default. If the master mixer is null it means we're creating the master mixer itself. + if (pDevice->pMasterMixer != NULL) { + dra_mixer_attach_submixer(pDevice->pMasterMixer, pMixer); + } + + *ppMixer = pMixer; + return DRA_RESULT_SUCCESS; +} + +void dra_mixer_delete(dra_mixer* pMixer) +{ + if (pMixer == NULL) return; + + dra_mixer_detach_all_submixers(pMixer); + dra_mixer_detach_all_voices(pMixer); + + if (pMixer->pParentMixer != NULL) { + dra_mixer_detach_submixer(pMixer->pParentMixer, pMixer); + } + + free(pMixer); +} + +void dra_mixer_attach_submixer(dra_mixer* pMixer, dra_mixer* pSubmixer) +{ + if (pMixer == NULL || pSubmixer == NULL) { + return; + } + + if (pSubmixer->pParentMixer != NULL) { + dra_mixer_detach_submixer(pSubmixer->pParentMixer, pSubmixer); + } + + + pSubmixer->pParentMixer = pMixer; + + if (pMixer->pFirstChildMixer == NULL) { + pMixer->pFirstChildMixer = pSubmixer; + pMixer->pLastChildMixer = pSubmixer; + return; + } + + assert(pMixer->pLastChildMixer != NULL); + pMixer->pLastChildMixer->pNextSiblingMixer = pSubmixer; + pSubmixer->pPrevSiblingMixer = pMixer->pLastChildMixer; + pSubmixer->pNextSiblingMixer = NULL; + pMixer->pLastChildMixer = pSubmixer; +} + +void dra_mixer_detach_submixer(dra_mixer* pMixer, dra_mixer* pSubmixer) +{ + if (pMixer == NULL || pSubmixer == NULL) { + return; + } + + if (pSubmixer->pParentMixer != pMixer) { + return; // Doesn't have the same parent. + } + + + // Detach from parent. + if (pSubmixer->pParentMixer->pFirstChildMixer == pSubmixer) { + pSubmixer->pParentMixer->pFirstChildMixer = pSubmixer->pNextSiblingMixer; + } + if (pSubmixer->pParentMixer->pLastChildMixer == pSubmixer) { + pSubmixer->pParentMixer->pLastChildMixer = pSubmixer->pPrevSiblingMixer; + } + + pSubmixer->pParentMixer = NULL; + + + // Detach from siblings. + if (pSubmixer->pPrevSiblingMixer) { + pSubmixer->pPrevSiblingMixer->pNextSiblingMixer = pSubmixer->pNextSiblingMixer; + } + if (pSubmixer->pNextSiblingMixer) { + pSubmixer->pNextSiblingMixer->pPrevSiblingMixer = pSubmixer->pPrevSiblingMixer; + } + + pSubmixer->pNextSiblingMixer = NULL; + pSubmixer->pPrevSiblingMixer = NULL; +} + +void dra_mixer_detach_all_submixers(dra_mixer* pMixer) +{ + if (pMixer == NULL) { + return; + } + + while (pMixer->pFirstChildMixer != NULL) { + dra_mixer_detach_submixer(pMixer, pMixer->pFirstChildMixer); + } +} + +void dra_mixer_attach_voice(dra_mixer* pMixer, dra_voice* pVoice) +{ + if (pMixer == NULL || pVoice == NULL) { + return; + } + + if (pVoice->pMixer != NULL) { + dra_mixer_detach_voice(pVoice->pMixer, pVoice); + } + + pVoice->pMixer = pMixer; + + if (pMixer->pFirstVoice == NULL) { + pMixer->pFirstVoice = pVoice; + pMixer->pLastVoice = pVoice; + return; + } + + // Attach the voice to the end of the list. + pVoice->pPrevVoice = pMixer->pLastVoice; + pVoice->pNextVoice = NULL; + + pMixer->pLastVoice->pNextVoice = pVoice; + pMixer->pLastVoice = pVoice; +} + +void dra_mixer_detach_voice(dra_mixer* pMixer, dra_voice* pVoice) +{ + if (pMixer == NULL || pVoice == NULL) { + return; + } + + + // Detach from mixer. + if (pMixer->pFirstVoice == pVoice) { + pMixer->pFirstVoice = pMixer->pFirstVoice->pNextVoice; + } + if (pMixer->pLastVoice == pVoice) { + pMixer->pLastVoice = pMixer->pLastVoice->pPrevVoice; + } + + pVoice->pMixer = NULL; + + + // Remove from list. + if (pVoice->pNextVoice) { + pVoice->pNextVoice->pPrevVoice = pVoice->pPrevVoice; + } + if (pVoice->pPrevVoice) { + pVoice->pPrevVoice->pNextVoice = pVoice->pNextVoice; + } + + pVoice->pNextVoice = NULL; + pVoice->pPrevVoice = NULL; + +} + +void dra_mixer_detach_all_voices(dra_mixer* pMixer) +{ + if (pMixer == NULL) { + return; + } + + while (pMixer->pFirstVoice) { + dra_mixer_detach_voice(pMixer, pMixer->pFirstVoice); + } +} + +void dra_mixer_set_volume(dra_mixer* pMixer, float linearVolume) +{ + if (pMixer == NULL) { + return; + } + + pMixer->linearVolume = linearVolume; +} + +float dra_mixer_get_volume(dra_mixer* pMixer) +{ + if (pMixer == NULL) { + return 0; + } + + return pMixer->linearVolume; +} + +size_t dra_mixer_mix_next_frames(dra_mixer* pMixer, size_t frameCount) +{ + if (pMixer == NULL) { + return 0; + } + + if (pMixer->pFirstVoice == NULL && pMixer->pFirstChildMixer == NULL) { + return 0; + } + + if (dra_mixer_is_paused(pMixer)) { + return 0; + } + + + size_t framesMixed = 0; + + // Mixing works by simply adding together the sample data of each voice and submixer. We just start at 0 and then + // just accumulate each one. + memset(pMixer->pStagingBuffer, 0, frameCount * pMixer->pDevice->channels * sizeof(float)); + + // Voices first. Doesn't really matter if we do voices or submixers first. + for (dra_voice* pVoice = pMixer->pFirstVoice; pVoice != NULL; pVoice = pVoice->pNextVoice) + { + if (pVoice->isPlaying) { + size_t framesJustRead = dra_voice__next_frames(pVoice, frameCount, pMixer->pNextSamplesToMix); + for (size_t i = 0; i < framesJustRead * pMixer->pDevice->channels; ++i) { + pMixer->pStagingBuffer[i] += (pMixer->pNextSamplesToMix[i] * pVoice->linearVolume); + } + + // Has the end of the voice's buffer been reached? + if (framesJustRead < frameCount) + { + // We'll get here if the end of the voice's buffer has been reached. The voice needs to be forcefully stopped to + // ensure the device is aware of it and is able to put itself into a dormant state if necessary. Also note that + // the playback position is moved back to start. The rationale for this is that it's a little bit more useful than + // just leaving the playback position sitting on the end. Also it allows an application to restart playback with + // a single call to dra_voice_play() without having to explicitly set the playback position. + pVoice->currentReadPos = 0; + dra_voice_stop(pVoice); + } + + if (framesMixed < framesJustRead) { + framesMixed = framesJustRead; + } + } + } + + // Submixers. + for (dra_mixer* pSubmixer = pMixer->pFirstChildMixer; pSubmixer != NULL; pSubmixer = pSubmixer->pNextSiblingMixer) + { + size_t framesJustMixed = dra_mixer_mix_next_frames(pSubmixer, frameCount); + for (size_t i = 0; i < framesJustMixed * pMixer->pDevice->channels; ++i) { + pMixer->pStagingBuffer[i] += pSubmixer->pStagingBuffer[i]; + } + + if (framesMixed < framesJustMixed) { + framesMixed = framesJustMixed; + } + } + + + // At this point the mixer's effects and volume need to be applied to each sample. + size_t samplesMixed = framesMixed * pMixer->pDevice->channels; + for (size_t i = 0; i < samplesMixed; ++i) { + pMixer->pStagingBuffer[i] *= pMixer->linearVolume; + } + + + // Finally we need to ensure every samples is clamped to -1 to 1. There are two easy ways to do this: clamp or normalize. For now I'm just + // clamping to keep it simple, but it might be valuable to make this configurable. + for (size_t i = 0; i < framesMixed * pMixer->pDevice->channels; ++i) + { + // TODO: Investigate using SSE here (MINPS/MAXPS) + // TODO: Investigate if the backends clamp the samples themselves, thus making this redundant. + if (pMixer->pStagingBuffer[i] < -1) { + pMixer->pStagingBuffer[i] = -1; + } else if (pMixer->pStagingBuffer[i] > 1) { + pMixer->pStagingBuffer[i] = 1; + } + } + + return framesMixed; +} + +size_t dra_mixer_count_attached_voices(dra_mixer* pMixer) +{ + if (pMixer == NULL) { + return 0; + } + + size_t count = 0; + for (dra_voice* pVoice = pMixer->pFirstVoice; pVoice != NULL; pVoice = pVoice->pNextVoice) { + count += 1; + } + + return count; +} + +size_t dra_mixer_count_attached_voices_recursive(dra_mixer* pMixer) +{ + if (pMixer == NULL) { + return 0; + } + + size_t count = dra_mixer_count_attached_voices(pMixer); + + // Children. + for (dra_mixer* pChildMixer = pMixer->pFirstChildMixer; pChildMixer != NULL; pChildMixer = pChildMixer->pNextSiblingMixer) { + count += dra_mixer_count_attached_voices_recursive(pChildMixer); + } + + return count; +} + +size_t dra_mixer_gather_attached_voices(dra_mixer* pMixer, dra_voice** ppVoicesOut) +{ + if (pMixer == NULL) { + return 0; + } + + if (ppVoicesOut == NULL) { + return dra_mixer_count_attached_voices(pMixer); + } + + size_t count = 0; + for (dra_voice* pVoice = pMixer->pFirstVoice; pVoice != NULL; pVoice = pVoice->pNextVoice) { + ppVoicesOut[count] = pVoice; + count += 1; + } + + return count; +} + +size_t dra_mixer_gather_attached_voices_recursive(dra_mixer* pMixer, dra_voice** ppVoicesOut) +{ + if (pMixer == NULL) { + return 0; + } + + if (ppVoicesOut == NULL) { + return dra_mixer_count_attached_voices_recursive(pMixer); + } + + size_t count = dra_mixer_gather_attached_voices(pMixer, ppVoicesOut); + + // Children. + for (dra_mixer* pChildMixer = pMixer->pFirstChildMixer; pChildMixer != NULL; pChildMixer = pChildMixer->pNextSiblingMixer) { + count += dra_mixer_gather_attached_voices_recursive(pChildMixer, ppVoicesOut + count); + } + + return count; +} + + +void dra_mixer_pause(dra_mixer* pMixer) +{ + if (pMixer == NULL) return; + pMixer->flags |= DRA_MIXER_FLAG_PAUSED; +} + +void dra_mixer_resume(dra_mixer* pMixer) +{ + if (pMixer == NULL) return; + pMixer->flags &= ~DRA_MIXER_FLAG_PAUSED; + + // When the mixer was paused it may have resulted in no audio being played which means dr_audio will have stopped the device + // to save CPU usage. We need to make sure we wake up the device. + dra_device__play(pMixer->pDevice); +} + +dr_bool32 dra_mixer_is_paused(dra_mixer* pMixer) +{ + if (pMixer == NULL) return DR_FALSE; + return (pMixer->flags & DRA_MIXER_FLAG_PAUSED) != 0; +} + + + +dra_result dra_voice_create(dra_device* pDevice, dra_format format, unsigned int channels, unsigned int sampleRate, size_t sizeInBytes, const void* pInitialData, dra_voice** ppVoice) +{ + if (ppVoice == NULL) return DRA_RESULT_INVALID_ARGS; + *ppVoice = NULL; + + if (pDevice == NULL || sizeInBytes == 0) return DRA_RESULT_INVALID_ARGS; + + + // The number of bytes must be a multiple of the size of a frame. + size_t bytesPerSample = dra_get_bytes_per_sample_by_format(format); + if ((sizeInBytes % (bytesPerSample * channels)) != 0) { + return DRA_RESULT_INVALID_ARGS; + } + + + dra_voice* pVoice = (dra_voice*)calloc(1, sizeof(*pVoice) + sizeInBytes); + if (pVoice == NULL) { + return DRA_RESULT_OUT_OF_MEMORY; + } + + pVoice->pDevice = pDevice; + pVoice->pMixer = NULL; + pVoice->format = format; + pVoice->channels = channels; + pVoice->sampleRate = sampleRate; + pVoice->linearVolume = 1; + pVoice->isPlaying = DR_FALSE; + pVoice->isLooping = DR_FALSE; + pVoice->frameCount = sizeInBytes / (bytesPerSample * channels); + pVoice->currentReadPos = 0; + pVoice->sizeInBytes = sizeInBytes; + pVoice->pNextVoice = NULL; + pVoice->pPrevVoice = NULL; + + if (pInitialData != NULL) { + memcpy(pVoice->pData, pInitialData, sizeInBytes); + } else { + //memset(pVoice->pData, 0, sizeInBytes); // <-- This is already zeroed by the calloc() above, but leaving this comment here for emphasis. + } + + + // Sample rate conversion. + if (sampleRate == pDevice->sampleRate) { + pVoice->src.algorithm = dra_src_algorithm_none; + } else { + pVoice->src.algorithm = dra_src_algorithm_linear; + } + + + // Attach the voice to the master mixer by default. + if (pDevice->pMasterMixer != NULL) { + dra_mixer_attach_voice(pDevice->pMasterMixer, pVoice); + } + + *ppVoice = pVoice; + return DRA_RESULT_SUCCESS; +} + +dra_result dra_voice_create_compatible(dra_device* pDevice, size_t sizeInBytes, const void* pInitialData, dra_voice** ppVoice) +{ + return dra_voice_create(pDevice, dra_format_f32, pDevice->channels, pDevice->sampleRate, sizeInBytes, pInitialData, ppVoice); +} + +void dra_voice_delete(dra_voice* pVoice) +{ + if (pVoice == NULL) return; + + // The voice needs to be stopped... + dra_voice_stop(pVoice); + + // ... and all pending events need to be cancelled to ensure the application isn't notified of an event of a deleted voice. + dra_event_queue__cancel_events_of_voice(&pVoice->pDevice->eventQueue, pVoice); + + if (pVoice->pMixer != NULL) { + dra_mixer_detach_voice(pVoice->pMixer, pVoice); + } + + free(pVoice); +} + +void dra_voice_play(dra_voice* pVoice, dr_bool32 loop) +{ + if (pVoice == NULL) { + return; + } + + if (!dra_voice_is_playing(pVoice)) { + dra_device__voice_playback_count_inc(pVoice->pDevice); + } else { + if (dra_voice_is_looping(pVoice) == loop) { + return; // Nothing has changed - don't need to do anything. + } + } + + pVoice->isPlaying = DR_TRUE; + pVoice->isLooping = loop; + + dra_event_queue__schedule_event(&pVoice->pDevice->eventQueue, &pVoice->playEvent); + + // When playing a voice we need to ensure the backend device is playing. + dra_device__play(pVoice->pDevice); +} + +void dra_voice_stop(dra_voice* pVoice) +{ + if (pVoice == NULL) { + return; + } + + if (!dra_voice_is_playing(pVoice)) { + return; // The voice is already stopped. + } + + dra_device__voice_playback_count_dec(pVoice->pDevice); + + pVoice->isPlaying = DR_FALSE; + pVoice->isLooping = DR_FALSE; + + dra_event_queue__schedule_event(&pVoice->pDevice->eventQueue, &pVoice->stopEvent); +} + +dr_bool32 dra_voice_is_playing(dra_voice* pVoice) +{ + if (pVoice == NULL) { + return DR_FALSE; + } + + return pVoice->isPlaying; +} + +dr_bool32 dra_voice_is_looping(dra_voice* pVoice) +{ + if (pVoice == NULL) { + return DR_FALSE; + } + + return pVoice->isLooping; +} + + +void dra_voice_set_volume(dra_voice* pVoice, float linearVolume) +{ + if (pVoice == NULL) { + return; + } + + pVoice->linearVolume = linearVolume; +} + +float dra_voice_get_volume(dra_voice* pVoice) +{ + if (pVoice == NULL) { + return 0; + } + + return pVoice->linearVolume; +} + + +void dra_f32_to_f32(float* pOut, const float* pIn, size_t sampleCount) +{ + memcpy(pOut, pIn, sampleCount * sizeof(float)); +} + +void dra_s32_to_f32(float* pOut, const dr_int32* pIn, size_t sampleCount) +{ + // TODO: Try SSE-ifying this. + for (size_t i = 0; i < sampleCount; ++i) { + pOut[i] = pIn[i] / 2147483648.0f; + } +} + +void dra_s24_to_f32(float* pOut, const dr_uint8* pIn, size_t sampleCount) +{ + // TODO: Try SSE-ifying this. + for (size_t i = 0; i < sampleCount; ++i) { + dr_uint8 s0 = pIn[i*3 + 0]; + dr_uint8 s1 = pIn[i*3 + 1]; + dr_uint8 s2 = pIn[i*3 + 2]; + + dr_int32 sample32 = (dr_int32)((s0 << 8) | (s1 << 16) | (s2 << 24)); + pOut[i] = sample32 / 2147483648.0f; + } +} + +void dra_s16_to_f32(float* pOut, const dr_int16* pIn, size_t sampleCount) +{ + // TODO: Try SSE-ifying this. + for (size_t i = 0; i < sampleCount; ++i) { + pOut[i] = pIn[i] / 32768.0f; + } +} + +void dra_u8_to_f32(float* pOut, const dr_uint8* pIn, size_t sampleCount) +{ + // TODO: Try SSE-ifying this. + for (size_t i = 0; i < sampleCount; ++i) { + pOut[i] = (pIn[i] / 127.5f) - 1; + } +} + + +// Generic sample format conversion function. To add basic, unoptimized support for a new format, just add it to this function. +// Format-specific optimizations need to be implemented specifically for each format, at a higher level. +void dra_to_f32(float* pOut, const void* pIn, size_t sampleCount, dra_format format) +{ + switch (format) + { + case dra_format_f32: + { + dra_f32_to_f32(pOut, (float*)pIn, sampleCount); + } break; + + case dra_format_s32: + { + dra_s32_to_f32(pOut, (dr_int32*)pIn, sampleCount); + } break; + + case dra_format_s24: + { + dra_s24_to_f32(pOut, (dr_uint8*)pIn, sampleCount); + } break; + + case dra_format_s16: + { + dra_s16_to_f32(pOut, (dr_int16*)pIn, sampleCount); + } break; + + case dra_format_u8: + { + dra_u8_to_f32(pOut, (dr_uint8*)pIn, sampleCount); + } break; + + default: break; // Unknown or unsupported format. + } +} + + +// Notes on channel shuffling. +// +// Channels are shuffled frame-by-frame by first normalizing everything to floats. Then, a shuffling function is called to +// shuffle the channels in a particular way depending on the destination and source channel assignments. +void dra_shuffle_channels__generic_inc(float* pOut, const float* pIn, unsigned int channelsOut, unsigned int channelsIn) +{ + // This is the generic function for taking a frame with a smaller number of channels and expanding it to a frame with + // a greater number of channels. This just copies the first channelsIn samples to the output and silences the remaing + // channels. + assert(channelsOut > channelsIn); + + for (unsigned int i = 0; i < channelsIn; ++i) { + pOut[i] = pIn[i]; + } + + // Silence the left over. + for (unsigned int i = channelsIn; i < channelsOut; ++i) { + pOut[i] = 0; + } +} + +void dra_shuffle_channels__generic_dec(float* pOut, const float* pIn, unsigned int channelsOut, unsigned int channelsIn) +{ + // This is the opposite of dra_shuffle_channels__generic_inc() - it decreases the number of channels in the input stream + // by simply stripping the excess channels. + assert(channelsOut < channelsIn); + (void)channelsIn; + + // Just copy the first channelsOut. + for (unsigned int i = 0; i < channelsOut; ++i) { + pOut[i] = pIn[i]; + } +} + +void dra_shuffle_channels(float* pOut, const float* pIn, unsigned int channelsOut, unsigned int channelsIn) +{ + assert(channelsOut != 0); + assert(channelsIn != 0); + + if (channelsOut == channelsIn) { + for (unsigned int i = 0; i < channelsOut; ++i) { + pOut[i] = pIn[i]; + } + } else { + switch (channelsIn) + { + case 1: + { + // Mono input. This is a simple case - just copy the value of the mono channel to every output channel. + for (unsigned int i = 0; i < channelsOut; ++i) { + pOut[i] = pIn[0]; + } + } break; + + case 2: + { + // Stereo input. + if (channelsOut == 1) + { + // For mono output, just average. + pOut[0] = (pIn[0] + pIn[1]) * 0.5f; + } + else + { + // TODO: Do a specialized implementation for all major formats, in particluar 5.1. + dra_shuffle_channels__generic_inc(pOut, pIn, channelsOut, channelsIn); + } + } break; + + default: + { + if (channelsOut == 1) + { + // For mono output, just average each sample. + float total = 0; + for (unsigned int i = 0; i < channelsIn; ++i) { + total += pIn[i]; + } + + pOut[0] = total / channelsIn; + } + else + { + if (channelsOut > channelsIn) { + dra_shuffle_channels__generic_inc(pOut, pIn, channelsOut, channelsIn); + } else { + dra_shuffle_channels__generic_dec(pOut, pIn, channelsOut, channelsIn); + } + } + } break; + } + } +} + +float dra_voice__get_sample_rate_factor(dra_voice* pVoice) +{ + if (pVoice == NULL) { + return 1; + } + + return pVoice->pDevice->sampleRate / (float)pVoice->sampleRate; +} + +void dra_voice__unsignal_playback_events(dra_voice* pVoice) +{ + // This function will be called when the voice has looped back to the start. In this case the playback notification events need + // to be marked as unsignaled so that they're able to be fired again. + for (size_t i = 0; i < pVoice->playbackEventCount; ++i) { + pVoice->playbackEvents[i].hasBeenSignaled = DR_FALSE; + } +} + +float* dra_voice__next_frame(dra_voice* pVoice) +{ + if (pVoice == NULL) { + return NULL; + } + + + if (pVoice->format == dra_format_f32 && pVoice->sampleRate == pVoice->pDevice->sampleRate && pVoice->channels == pVoice->pDevice->channels) + { + // Fast path. + if (!pVoice->isLooping && pVoice->currentReadPos == pVoice->frameCount) { + return NULL; // At the end of a non-looping voice. + } + + float* pOut = (float*)pVoice->pData + (pVoice->currentReadPos * pVoice->channels); + + pVoice->currentReadPos += 1; + if (pVoice->currentReadPos == pVoice->frameCount && pVoice->isLooping) { + pVoice->currentReadPos = 0; + dra_voice__unsignal_playback_events(pVoice); + } + + return pOut; + } + else + { + size_t bytesPerSample = dra_get_bytes_per_sample_by_format(pVoice->format); + + if (pVoice->sampleRate == pVoice->pDevice->sampleRate) + { + // Same sample rate. This path isn't ideal, but it's not too bad since there is no need for sample rate conversion. + if (!pVoice->isLooping && pVoice->currentReadPos == pVoice->frameCount) { + return NULL; // At the end of a non-looping voice. + } + + float* pOut = pVoice->convertedFrame; + + unsigned int channelsIn = pVoice->channels; + unsigned int channelsOut = pVoice->pDevice->channels; + float tempFrame[DR_AUDIO_MAX_CHANNEL_COUNT]; + dr_uint64 sampleOffset = pVoice->currentReadPos * channelsIn; + + // The conversion is done differently depending on the format of the voice. + if (pVoice->format == dra_format_f32) { + dra_shuffle_channels(pOut, (float*)pVoice->pData + sampleOffset, channelsOut, channelsIn); + } else { + sampleOffset = pVoice->currentReadPos * (channelsIn * bytesPerSample); + dra_to_f32(tempFrame, (dr_uint8*)pVoice->pData + sampleOffset, channelsIn, pVoice->format); + dra_shuffle_channels(pOut, tempFrame, channelsOut, channelsIn); + } + + pVoice->currentReadPos += 1; + if (pVoice->currentReadPos == pVoice->frameCount && pVoice->isLooping) { + pVoice->currentReadPos = 0; + dra_voice__unsignal_playback_events(pVoice); + } + + return pOut; + } + else + { + // Different sample rate. This is the truly slow path. + unsigned int sampleRateIn = pVoice->sampleRate; + unsigned int sampleRateOut = pVoice->pDevice->sampleRate; + unsigned int channelsIn = pVoice->channels; + unsigned int channelsOut = pVoice->pDevice->channels; + + float factor = (float)sampleRateOut / (float)sampleRateIn; + float invfactor = 1 / factor; + + if (!pVoice->isLooping && pVoice->currentReadPos >= (pVoice->frameCount * factor)) { + return NULL; // At the end of a non-looping voice. + } + + float* pOut = pVoice->convertedFrame; + + if (pVoice->src.algorithm == dra_src_algorithm_linear) { + // Linear filtering. + float timeIn = pVoice->currentReadPos * invfactor; + dr_uint64 prevFrameIndexIn = (dr_uint64)(timeIn); + dr_uint64 nextFrameIndexIn = prevFrameIndexIn + 1; + if (nextFrameIndexIn >= pVoice->frameCount) { + nextFrameIndexIn = pVoice->frameCount-1; + } + + if (prevFrameIndexIn != pVoice->src.data.linear.prevFrameIndex) + { + dr_uint64 sampleOffset = prevFrameIndexIn * (channelsIn * bytesPerSample); + dra_to_f32(pVoice->src.data.linear.prevFrame, (dr_uint8*)pVoice->pData + sampleOffset, channelsIn, pVoice->format); + + sampleOffset = nextFrameIndexIn * (channelsIn * bytesPerSample); + dra_to_f32(pVoice->src.data.linear.nextFrame, (dr_uint8*)pVoice->pData + sampleOffset, channelsIn, pVoice->format); + + pVoice->src.data.linear.prevFrameIndex = prevFrameIndexIn; + } + + float alpha = timeIn - prevFrameIndexIn; + float frame[DR_AUDIO_MAX_CHANNEL_COUNT]; + for (unsigned int i = 0; i < pVoice->channels; ++i) { + frame[i] = dra_mixf(pVoice->src.data.linear.prevFrame[i], pVoice->src.data.linear.nextFrame[i], alpha); + } + + dra_shuffle_channels(pOut, frame, channelsOut, channelsIn); + } + + + pVoice->currentReadPos += 1; + if (pVoice->currentReadPos >= (pVoice->frameCount * factor) && pVoice->isLooping) { + pVoice->currentReadPos = 0; + dra_voice__unsignal_playback_events(pVoice); + } + + return pOut; + } + } +} + +size_t dra_voice__next_frames(dra_voice* pVoice, size_t frameCount, float* pSamplesOut) +{ + // TODO: Check for the fast path and do a bulk copy rather than frame-by-frame. Don't forget playback event handling. + + size_t framesRead = 0; + + dr_uint64 prevReadPosLocal = pVoice->currentReadPos * pVoice->channels; + + float* pNextFrame = NULL; + while ((framesRead < frameCount) && (pNextFrame = dra_voice__next_frame(pVoice)) != NULL) { + memcpy(pSamplesOut, pNextFrame, pVoice->pDevice->channels * sizeof(float)); + pSamplesOut += pVoice->pDevice->channels; + framesRead += 1; + } + + float sampleRateFactor = dra_voice__get_sample_rate_factor(pVoice); + dr_uint64 totalSampleCount = (dr_uint64)((pVoice->frameCount * pVoice->channels) * sampleRateFactor); + + // Now we need to check if we've got past any notification events and post events for them if so. + dr_uint64 currentReadPosLocal = (prevReadPosLocal + (framesRead * pVoice->channels)) % totalSampleCount; + for (size_t i = 0; i < pVoice->playbackEventCount; ++i) { + dra__event* pEvent = &pVoice->playbackEvents[i]; + if (!pEvent->hasBeenSignaled && pEvent->sampleIndex*sampleRateFactor <= currentReadPosLocal) { + dra_event_queue__schedule_event(&pVoice->pDevice->eventQueue, pEvent); // <-- TODO: Check that this really needs to be scheduled. Can probably call it directly and avoid a mutex lock/unlock. + pEvent->hasBeenSignaled = DR_TRUE; + } + } + + return framesRead; +} + + +void dra_voice_set_on_stop(dra_voice* pVoice, dra_event_proc proc, void* pUserData) +{ + if (pVoice == NULL) { + return; + } + + pVoice->stopEvent.id = DR_AUDIO_EVENT_ID_STOP; + pVoice->stopEvent.pUserData = pUserData; + pVoice->stopEvent.sampleIndex = 0; + pVoice->stopEvent.proc = proc; + pVoice->stopEvent.pVoice = pVoice; +} + +void dra_voice_set_on_play(dra_voice* pVoice, dra_event_proc proc, void* pUserData) +{ + if (pVoice == NULL) { + return; + } + + pVoice->playEvent.id = DR_AUDIO_EVENT_ID_PLAY; + pVoice->playEvent.pUserData = pUserData; + pVoice->playEvent.sampleIndex = 0; + pVoice->playEvent.proc = proc; + pVoice->playEvent.pVoice = pVoice; +} + +dr_bool32 dra_voice_add_playback_event(dra_voice* pVoice, dr_uint64 sampleIndex, dr_uint64 eventID, dra_event_proc proc, void* pUserData) +{ + if (pVoice == NULL) { + return DR_FALSE; + } + + if (pVoice->playbackEventCount >= DR_AUDIO_MAX_EVENT_COUNT) { + return DR_FALSE; + } + + pVoice->playbackEvents[pVoice->playbackEventCount].id = eventID; + pVoice->playbackEvents[pVoice->playbackEventCount].pUserData = pUserData; + pVoice->playbackEvents[pVoice->playbackEventCount].sampleIndex = sampleIndex; + pVoice->playbackEvents[pVoice->playbackEventCount].proc = proc; + pVoice->playbackEvents[pVoice->playbackEventCount].pVoice = pVoice; + + pVoice->playbackEventCount += 1; + return DR_TRUE; +} + +void dra_voice_remove_playback_event(dra_voice* pVoice, dr_uint64 eventID) +{ + if (pVoice == NULL) { + return; + } + + for (size_t i = 0; i < pVoice->playbackEventCount; /* DO NOTHING */) { + if (pVoice->playbackEvents[i].id == eventID) { + memmove(&pVoice->playbackEvents[i], &pVoice->playbackEvents[i + 1], (pVoice->playbackEventCount - (i+1)) * sizeof(dra__event)); + pVoice->playbackEventCount -= 1; + } else { + i += 1; + } + } +} + + +dr_uint64 dra_voice_get_playback_position(dra_voice* pVoice) +{ + if (pVoice == NULL) { + return 0; + } + + return (dr_uint64)((pVoice->currentReadPos * pVoice->channels) / dra_voice__get_sample_rate_factor(pVoice)); +} + +void dra_voice_set_playback_position(dra_voice* pVoice, dr_uint64 sampleIndex) +{ + if (pVoice == NULL) { + return; + } + + // When setting the playback position it's important to consider sample-rate conversion. Sample rate conversion will often depend on + // previous and next frames in order to calculate the next frame. Therefore, depending on the type of SRC we're using, we'll need to + // seek a few frames earlier and then re-fill the delay-line buffer used for a particular SRC algorithm. + dr_uint64 localFramePos = sampleIndex / pVoice->channels; + pVoice->currentReadPos = (dr_uint64)(localFramePos * dra_voice__get_sample_rate_factor(pVoice)); + + if (pVoice->sampleRate != pVoice->pDevice->sampleRate) { + if (pVoice->src.algorithm == dra_src_algorithm_linear) { + // Linear filtering just requires the previous frame. However, this is handled at mixing time for linear SRC so all we need to + // do is ensure the mixing function is aware that the previous frame need to be re-read. This is done by simply resetting the + // variable the mixer uses to determine whether or not the previous frame needs to be re-read. + pVoice->src.data.linear.prevFrameIndex = 0; + } + } + + // TODO: Normalize the hasBeenSignaled properties of events. +} + + +void* dra_voice_get_buffer_ptr_by_sample(dra_voice* pVoice, dr_uint64 sample) +{ + if (pVoice == NULL) { + return NULL; + } + + dr_uint64 totalSampleCount = pVoice->frameCount * pVoice->channels; + if (sample > totalSampleCount) { + return NULL; + } + + return pVoice->pData + (sample * dra_get_bytes_per_sample_by_format(pVoice->format)); +} + +void dra_voice_write_silence(dra_voice* pVoice, dr_uint64 sampleOffset, dr_uint64 sampleCount) +{ + void* pData = dra_voice_get_buffer_ptr_by_sample(pVoice, sampleOffset); + if (pData == NULL) { + return; + } + + dr_uint64 totalSamplesRemaining = (pVoice->frameCount * pVoice->channels) - sampleOffset; + if (sampleCount > totalSamplesRemaining) { + sampleCount = totalSamplesRemaining; + } + + memset(pData, 0, (size_t)(sampleCount * dra_get_bytes_per_sample_by_format(pVoice->format))); +} + + + + + + +//// Other APIs //// + +void dra_free(void* p) +{ + free(p); +} + +unsigned int dra_get_bits_per_sample_by_format(dra_format format) +{ + unsigned int lookup[] = { + 8, // dra_format_u8 + 16, // dra_format_s16 + 24, // dra_format_s24 + 32, // dra_format_s32 + 32 // dra_format_f32 + }; + + return lookup[format]; +} + +unsigned int dra_get_bytes_per_sample_by_format(dra_format format) +{ + return dra_get_bits_per_sample_by_format(format) / 8; +} + + +//// STDIO //// + +#ifndef DR_AUDIO_NO_STDIO +static FILE* dra__fopen(const char* filePath) +{ + FILE* pFile; +#ifdef _MSC_VER + if (fopen_s(&pFile, filePath, "rb") != 0) { + return NULL; + } +#else + pFile = fopen(filePath, "rb"); + if (pFile == NULL) { + return NULL; + } +#endif + + return (FILE*)pFile; +} +#endif //DR_AUDIO_NO_STDIO + + +//// Decoder APIs //// + +#ifdef DR_AUDIO_HAS_WAV +size_t dra_decoder_on_read__wav(void* pUserData, void* pDataOut, size_t bytesToRead) +{ + dra_decoder* pDecoder = (dra_decoder*)pUserData; + assert(pDecoder != NULL); + assert(pDecoder->onRead != NULL); + + return pDecoder->onRead(pDecoder->pUserData, pDataOut, bytesToRead); +} +drwav_bool32 dra_decoder_on_seek__wav(void* pUserData, int offset, drwav_seek_origin origin) +{ + dra_decoder* pDecoder = (dra_decoder*)pUserData; + assert(pDecoder != NULL); + assert(pDecoder->onSeek != NULL); + + return pDecoder->onSeek(pDecoder->pUserData, offset, (origin == drwav_seek_origin_start) ? dra_seek_origin_start : dra_seek_origin_current); +} + +void dra_decoder_on_delete__wav(void* pBackendDecoder) +{ + drwav* pWav = (drwav*)pBackendDecoder; + assert(pWav != NULL); + + drwav_close(pWav); +} + +dr_uint64 dra_decoder_on_read_samples__wav(void* pBackendDecoder, dr_uint64 samplesToRead, float* pSamplesOut) +{ + drwav* pWav = (drwav*)pBackendDecoder; + assert(pWav != NULL); + + return drwav_read_f32(pWav, samplesToRead, pSamplesOut); +} + +dr_bool32 dra_decoder_on_seek_samples__wav(void* pBackendDecoder, dr_uint64 sample) +{ + drwav* pWav = (drwav*)pBackendDecoder; + assert(pWav != NULL); + + return drwav_seek_to_sample(pWav, sample); +} + + +void dra_decoder_init__wav(dra_decoder* pDecoder, drwav* pWav) +{ + assert(pDecoder != NULL); + assert(pWav != NULL); + + pDecoder->channels = pWav->channels; + pDecoder->sampleRate = pWav->sampleRate; + pDecoder->totalSampleCount = pWav->totalSampleCount; + + pDecoder->pBackendDecoder = pWav; + pDecoder->onDelete = dra_decoder_on_delete__wav; + pDecoder->onReadSamples = dra_decoder_on_read_samples__wav; + pDecoder->onSeekSamples = dra_decoder_on_seek_samples__wav; +} + +dr_bool32 dra_decoder_open__wav(dra_decoder* pDecoder) +{ + drwav* pWav = drwav_open(dra_decoder_on_read__wav, dra_decoder_on_seek__wav, pDecoder); + if (pWav == NULL) { + return DR_FALSE; + } + + dra_decoder_init__wav(pDecoder, pWav); + return DR_TRUE; +} + +dr_bool32 dra_decoder_open_memory__wav(dra_decoder* pDecoder, const void* pData, size_t dataSize) +{ + drwav* pWav = drwav_open_memory(pData, dataSize); + if (pWav == NULL) { + return DR_FALSE; + } + + dra_decoder_init__wav(pDecoder, pWav); + return DR_TRUE; +} + +#ifdef DR_AUDIO_HAS_WAV_STDIO +dr_bool32 dra_decoder_open_file__wav(dra_decoder* pDecoder, const char* filePath) +{ + drwav* pWav = drwav_open_file(filePath); + if (pWav == NULL) { + return DR_FALSE; + } + + dra_decoder_init__wav(pDecoder, pWav); + return DR_TRUE; +} +#endif +#endif //WAV + +#ifdef DR_AUDIO_HAS_FLAC +size_t dra_decoder_on_read__flac(void* pUserData, void* pDataOut, size_t bytesToRead) +{ + dra_decoder* pDecoder = (dra_decoder*)pUserData; + assert(pDecoder != NULL); + assert(pDecoder->onRead != NULL); + + return pDecoder->onRead(pDecoder->pUserData, pDataOut, bytesToRead); +} +drflac_bool32 dra_decoder_on_seek__flac(void* pUserData, int offset, drflac_seek_origin origin) +{ + dra_decoder* pDecoder = (dra_decoder*)pUserData; + assert(pDecoder != NULL); + assert(pDecoder->onSeek != NULL); + + return pDecoder->onSeek(pDecoder->pUserData, offset, (origin == drflac_seek_origin_start) ? dra_seek_origin_start : dra_seek_origin_current); +} + +void dra_decoder_on_delete__flac(void* pBackendDecoder) +{ + drflac* pFlac = (drflac*)pBackendDecoder; + assert(pFlac != NULL); + + drflac_close(pFlac); +} + +dr_uint64 dra_decoder_on_read_samples__flac(void* pBackendDecoder, dr_uint64 samplesToRead, float* pSamplesOut) +{ + drflac* pFlac = (drflac*)pBackendDecoder; + assert(pFlac != NULL); + + dr_uint64 samplesRead = drflac_read_s32(pFlac, samplesToRead, (dr_int32*)pSamplesOut); + + dra_s32_to_f32(pSamplesOut, (dr_int32*)pSamplesOut, (size_t)samplesRead); + return samplesRead; +} + +dr_bool32 dra_decoder_on_seek_samples__flac(void* pBackendDecoder, dr_uint64 sample) +{ + drflac* pFlac = (drflac*)pBackendDecoder; + assert(pFlac != NULL); + + return drflac_seek_to_sample(pFlac, sample); +} + + +void dra_decoder_init__flac(dra_decoder* pDecoder, drflac* pFlac) +{ + assert(pDecoder != NULL); + assert(pFlac != NULL); + + pDecoder->channels = pFlac->channels; + pDecoder->sampleRate = pFlac->sampleRate; + pDecoder->totalSampleCount = pFlac->totalSampleCount; + + pDecoder->pBackendDecoder = pFlac; + pDecoder->onDelete = dra_decoder_on_delete__flac; + pDecoder->onReadSamples = dra_decoder_on_read_samples__flac; + pDecoder->onSeekSamples = dra_decoder_on_seek_samples__flac; +} + +dr_bool32 dra_decoder_open__flac(dra_decoder* pDecoder) +{ + drflac* pFlac = drflac_open(dra_decoder_on_read__flac, dra_decoder_on_seek__flac, pDecoder); + if (pFlac == NULL) { + return DR_FALSE; + } + + dra_decoder_init__flac(pDecoder, pFlac); + return DR_TRUE; +} + +dr_bool32 dra_decoder_open_memory__flac(dra_decoder* pDecoder, const void* pData, size_t dataSize) +{ + drflac* pFlac = drflac_open_memory(pData, dataSize); + if (pFlac == NULL) { + return DR_FALSE; + } + + dra_decoder_init__flac(pDecoder, pFlac); + return DR_TRUE; +} + +#ifdef DR_AUDIO_HAS_FLAC_STDIO +dr_bool32 dra_decoder_open_file__flac(dra_decoder* pDecoder, const char* filePath) +{ + drflac* pFlac = drflac_open_file(filePath); + if (pFlac == NULL) { + return DR_FALSE; + } + + dra_decoder_init__flac(pDecoder, pFlac); + return DR_TRUE; +} +#endif +#endif //FLAC + +#ifdef DR_AUDIO_HAS_VORBIS +void dra_decoder_on_delete__vorbis(void* pBackendDecoder) +{ + stb_vorbis* pVorbis = (stb_vorbis*)pBackendDecoder; + assert(pVorbis != NULL); + + stb_vorbis_close(pVorbis); +} + +dr_uint64 dra_decoder_on_read_samples__vorbis(void* pBackendDecoder, dr_uint64 samplesToRead, float* pSamplesOut) +{ + stb_vorbis* pVorbis = (stb_vorbis*)pBackendDecoder; + assert(pVorbis != NULL); + + stb_vorbis_info info = stb_vorbis_get_info(pVorbis); + return (dr_uint64)stb_vorbis_get_samples_float_interleaved(pVorbis, info.channels, pSamplesOut, (int)samplesToRead) * info.channels; +} + +dr_bool32 dra_decoder_on_seek_samples__vorbis(void* pBackendDecoder, dr_uint64 sample) +{ + stb_vorbis* pVorbis = (stb_vorbis*)pBackendDecoder; + assert(pVorbis != NULL); + + return stb_vorbis_seek(pVorbis, (unsigned int)sample) != 0; +} + + +void dra_decoder_init__vorbis(dra_decoder* pDecoder, stb_vorbis* pVorbis) +{ + assert(pDecoder != NULL); + assert(pVorbis != NULL); + + stb_vorbis_info info = stb_vorbis_get_info(pVorbis); + + pDecoder->channels = info.channels; + pDecoder->sampleRate = info.sample_rate; + pDecoder->totalSampleCount = stb_vorbis_stream_length_in_samples(pVorbis) * info.channels; + + pDecoder->pBackendDecoder = pVorbis; + pDecoder->onDelete = dra_decoder_on_delete__vorbis; + pDecoder->onReadSamples = dra_decoder_on_read_samples__vorbis; + pDecoder->onSeekSamples = dra_decoder_on_seek_samples__vorbis; +} + +dr_bool32 dra_decoder_open__vorbis(dra_decoder* pDecoder) +{ + // TODO: Add support for the push API. + + // Not currently supporting callback based decoding. + (void)pDecoder; + return DR_FALSE; +} + +dr_bool32 dra_decoder_open_memory__vorbis(dra_decoder* pDecoder, const void* pData, size_t dataSize) +{ + stb_vorbis* pVorbis = stb_vorbis_open_memory((const unsigned char*)pData, (int)dataSize, NULL, NULL); + if (pVorbis == NULL) { + return DR_FALSE; + } + + dra_decoder_init__vorbis(pDecoder, pVorbis); + return DR_TRUE; +} + +#ifdef DR_AUDIO_HAS_VORBIS_STDIO +dr_bool32 dra_decoder_open_file__vorbis(dra_decoder* pDecoder, const char* filePath) +{ + stb_vorbis* pVorbis = stb_vorbis_open_filename(filePath, NULL, NULL); + if (pVorbis == NULL) { + return DR_FALSE; + } + + dra_decoder_init__vorbis(pDecoder, pVorbis); + return DR_TRUE; +} +#endif +#endif //Vorbis + +dra_result dra_decoder_open(dra_decoder* pDecoder, dra_decoder_on_read_proc onRead, dra_decoder_on_seek_proc onSeek, void* pUserData) +{ + if (pDecoder == NULL) return DRA_RESULT_INVALID_ARGS; + memset(pDecoder, 0, sizeof(*pDecoder)); + + if (onRead == NULL || onSeek == NULL) return DRA_RESULT_INVALID_ARGS; + + pDecoder->onRead = onRead; + pDecoder->onSeek = onSeek; + pDecoder->pUserData = pUserData; + +#ifdef DR_AUDIO_HAS_WAV_STDIO + if (dra_decoder_open__wav(pDecoder)) { + return DRA_RESULT_SUCCESS; + } + onSeek(pUserData, 0, dra_seek_origin_start); +#endif +#ifdef DR_AUDIO_HAS_FLAC_STDIO + if (dra_decoder_open__flac(pDecoder)) { + return DRA_RESULT_SUCCESS; + } + onSeek(pUserData, 0, dra_seek_origin_start); +#endif +#ifdef DR_AUDIO_HAS_VORBIS_STDIO + if (dra_decoder_open__vorbis(pDecoder)) { + return DRA_RESULT_SUCCESS; + } + onSeek(pUserData, 0, dra_seek_origin_start); +#endif + + // If we get here it means we were unable to open a decoder. + return DRA_RESULT_NO_DECODER; +} + + +size_t dra_decoder__on_read_memory(void* pUserData, void* pDataOut, size_t bytesToRead) +{ + dra__memory_stream* memoryStream = (dra__memory_stream*)pUserData; + assert(memoryStream != NULL); + assert(memoryStream->dataSize >= memoryStream->currentReadPos); + + size_t bytesRemaining = memoryStream->dataSize - memoryStream->currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + memcpy(pDataOut, memoryStream->data + memoryStream->currentReadPos, bytesToRead); + memoryStream->currentReadPos += bytesToRead; + } + + return bytesToRead; +} +dr_bool32 dra_decoder__on_seek_memory(void* pUserData, int offset, dra_seek_origin origin) +{ + dra__memory_stream* memoryStream = (dra__memory_stream*)pUserData; + assert(memoryStream != NULL); + assert(offset > 0 || (offset == 0 && origin == dra_seek_origin_start)); + + if (origin == dra_seek_origin_current) { + if (memoryStream->currentReadPos + offset <= memoryStream->dataSize) { + memoryStream->currentReadPos += offset; + } else { + memoryStream->currentReadPos = memoryStream->dataSize; // Trying to seek too far forward. + } + } else { + if ((dr_uint32)offset <= memoryStream->dataSize) { + memoryStream->currentReadPos = offset; + } else { + memoryStream->currentReadPos = memoryStream->dataSize; // Trying to seek too far forward. + } + } + + return DR_TRUE; +} + +dra_result dra_decoder_open_memory(dra_decoder* pDecoder, const void* pData, size_t dataSize) +{ + if (pDecoder == NULL) return DRA_RESULT_INVALID_ARGS; + memset(pDecoder, 0, sizeof(*pDecoder)); + + if (pData == NULL || dataSize == 0) return DRA_RESULT_INVALID_ARGS; + + // Prefer the backend's native APIs. +#if defined(DR_AUDIO_HAS_WAV) + if (dra_decoder_open_memory__wav(pDecoder, pData, dataSize)) { + return DRA_RESULT_SUCCESS; + } +#endif +#if defined(DR_AUDIO_HAS_FLAC) + if (dra_decoder_open_memory__flac(pDecoder, pData, dataSize)) { + return DRA_RESULT_SUCCESS; + } +#endif +#if defined(DR_AUDIO_HAS_VORBIS) + if (dra_decoder_open_memory__vorbis(pDecoder, pData, dataSize)) { + return DRA_RESULT_SUCCESS; + } +#endif + + // If we get here it means the backend does not have a native memory loader so we'll need to it ourselves. + dra__memory_stream memoryStream; + memoryStream.data = (const unsigned char*)pData; + memoryStream.dataSize = dataSize; + memoryStream.currentReadPos = 0; + dra_result result = dra_decoder_open(pDecoder, dra_decoder__on_read_memory, dra_decoder__on_seek_memory, &memoryStream); + if (result != DRA_RESULT_SUCCESS) { + return result; + } + + pDecoder->memoryStream = memoryStream; + pDecoder->pUserData = &pDecoder->memoryStream; + + return DRA_RESULT_SUCCESS; +} + + +#ifndef DR_AUDIO_NO_STDIO +size_t dra_decoder__on_read_stdio(void* pUserData, void* pDataOut, size_t bytesToRead) +{ + return fread(pDataOut, 1, bytesToRead, (FILE*)pUserData); +} +dr_bool32 dra_decoder__on_seek_stdio(void* pUserData, int offset, dra_seek_origin origin) +{ + return fseek((FILE*)pUserData, offset, (origin == dra_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; +} + +dra_result dra_decoder_open_file(dra_decoder* pDecoder, const char* filePath) +{ + if (pDecoder == NULL) return DRA_RESULT_INVALID_ARGS; + memset(pDecoder, 0, sizeof(*pDecoder)); + + if (filePath == NULL) return DR_FALSE; + + // When opening a decoder from a file it's preferrable to use the backend's native file IO APIs if it has them. +#if defined(DR_AUDIO_HAS_WAV) && defined(DR_AUDIO_HAS_WAV_STDIO) + if (dra_decoder_open_file__wav(pDecoder, filePath)) { + return DRA_RESULT_SUCCESS; + } +#endif +#if defined(DR_AUDIO_HAS_FLAC) && defined(DR_AUDIO_HAS_FLAC_STDIO) + if (dra_decoder_open_file__flac(pDecoder, filePath)) { + return DRA_RESULT_SUCCESS; + } +#endif +#if defined(DR_AUDIO_HAS_VORBIS) && defined(DR_AUDIO_HAS_VORBIS_STDIO) + if (dra_decoder_open_file__vorbis(pDecoder, filePath)) { + return DRA_RESULT_SUCCESS; + } +#endif + + // If we get here it means we were unable to open the decoder using any of the backends' native file IO + // APIs. In this case we fall back to a generic method. + FILE* pFile = dra__fopen(filePath); + if (pFile == NULL) { + return DRA_RESULT_FAILED_TO_OPEN_FILE; + } + + dra_result result = dra_decoder_open(pDecoder, dra_decoder__on_read_stdio, dra_decoder__on_seek_stdio, pFile); + if (result != DRA_RESULT_SUCCESS) { + fclose(pFile); + return result; + } + + return DRA_RESULT_SUCCESS; +} +#endif + +void dra_decoder_close(dra_decoder* pDecoder) +{ + if (pDecoder == NULL) { + return; + } + + if (pDecoder->onDelete) { + pDecoder->onDelete(pDecoder->pBackendDecoder); + } + +#ifndef DR_AUDIO_NO_STDIO + if (pDecoder->onRead == dra_decoder__on_read_stdio) { + fclose((FILE*)pDecoder->pUserData); + } +#endif +} + +dr_uint64 dra_decoder_read_f32(dra_decoder* pDecoder, dr_uint64 samplesToRead, float* pSamplesOut) +{ + if (pDecoder == NULL || pSamplesOut == NULL) { + return 0; + } + + return pDecoder->onReadSamples(pDecoder->pBackendDecoder, samplesToRead, pSamplesOut); +} + +dr_bool32 dra_decoder_seek_to_sample(dra_decoder* pDecoder, dr_uint64 sample) +{ + if (pDecoder == NULL) { + return DR_FALSE; + } + + return pDecoder->onSeekSamples(pDecoder->pBackendDecoder, sample); +} + + +float* dra_decoder__full_decode_and_close(dra_decoder* pDecoder, unsigned int* channelsOut, unsigned int* sampleRateOut, dr_uint64* totalSampleCountOut) +{ + assert(pDecoder != NULL); + + float* pSampleData = NULL; + dr_uint64 totalSampleCount = pDecoder->totalSampleCount; + + if (totalSampleCount == 0) + { + float buffer[4096]; + + size_t sampleDataBufferSize = sizeof(buffer); + pSampleData = (float*)malloc(sampleDataBufferSize); + if (pSampleData == NULL) { + goto on_error; + } + + dr_uint64 samplesRead; + while ((samplesRead = (dr_uint64)dra_decoder_read_f32(pDecoder, sizeof(buffer)/sizeof(buffer[0]), buffer)) > 0) + { + if (((totalSampleCount + samplesRead) * sizeof(float)) > sampleDataBufferSize) { + sampleDataBufferSize *= 2; + float* pNewSampleData = (float*)realloc(pSampleData, sampleDataBufferSize); + if (pNewSampleData == NULL) { + free(pSampleData); + goto on_error; + } + + pSampleData = pNewSampleData; + } + + memcpy(pSampleData + totalSampleCount, buffer, (size_t)(samplesRead*sizeof(float))); + totalSampleCount += samplesRead; + } + + // At this point everything should be decoded, but we just want to fill the unused part buffer with silence - need to + // protect those ears from random noise! + memset(pSampleData + totalSampleCount, 0, (size_t)(sampleDataBufferSize - totalSampleCount*sizeof(float))); + } + else + { + dr_uint64 dataSize = totalSampleCount * sizeof(float); + if (dataSize > SIZE_MAX) { + goto on_error; // The decoded data is too big. + } + + pSampleData = (float*)malloc((size_t)dataSize); // <-- Safe cast as per the check above. + if (pSampleData == NULL) { + goto on_error; + } + + dr_uint64 samplesDecoded = dra_decoder_read_f32(pDecoder, pDecoder->totalSampleCount, pSampleData); + if (samplesDecoded != pDecoder->totalSampleCount) { + free(pSampleData); + goto on_error; // Something went wrong when decoding the FLAC stream. + } + } + + + if (channelsOut) *channelsOut = pDecoder->channels; + if (sampleRateOut) *sampleRateOut = pDecoder->sampleRate; + if (totalSampleCountOut) *totalSampleCountOut = totalSampleCount; + + dra_decoder_close(pDecoder); + return pSampleData; + +on_error: + dra_decoder_close(pDecoder); + return NULL; +} + +float* dra_decoder_open_and_decode_f32(dra_decoder_on_read_proc onRead, dra_decoder_on_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount) +{ + // Safety. + if (channels) *channels = 0; + if (sampleRate) *sampleRate = 0; + if (totalSampleCount) *totalSampleCount = 0; + + dra_decoder decoder; + dra_result result = dra_decoder_open(&decoder, onRead, onSeek, pUserData); + if (result != DRA_RESULT_SUCCESS) { + return NULL; + } + + return dra_decoder__full_decode_and_close(&decoder, channels, sampleRate, totalSampleCount); +} + +float* dra_decoder_open_and_decode_memory_f32(const void* pData, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount) +{ + // Safety. + if (channels) *channels = 0; + if (sampleRate) *sampleRate = 0; + if (totalSampleCount) *totalSampleCount = 0; + + dra_decoder decoder; + dra_result result = dra_decoder_open_memory(&decoder, pData, dataSize); + if (result != DRA_RESULT_SUCCESS) { + return NULL; + } + + return dra_decoder__full_decode_and_close(&decoder, channels, sampleRate, totalSampleCount); +} + +#ifndef DR_AUDIO_NO_STDIO +float* dra_decoder_open_and_decode_file_f32(const char* filePath, unsigned int* channels, unsigned int* sampleRate, dr_uint64* totalSampleCount) +{ + // Safety. + if (channels) *channels = 0; + if (sampleRate) *sampleRate = 0; + if (totalSampleCount) *totalSampleCount = 0; + + dra_decoder decoder; + dra_result result = dra_decoder_open_file(&decoder, filePath); + if (result != DRA_RESULT_SUCCESS) { + return NULL; + } + + return dra_decoder__full_decode_and_close(&decoder, channels, sampleRate, totalSampleCount); +} +#endif + + + +//// High Level Helper APIs //// + +#ifndef DR_AUDIO_NO_STDIO +dra_result dra_voice_create_from_file(dra_device* pDevice, const char* filePath, dra_voice** ppVoice) +{ + if (pDevice == NULL || filePath == NULL) return DRA_RESULT_INVALID_ARGS; + + unsigned int channels; + unsigned int sampleRate; + dr_uint64 totalSampleCount; + float* pSampleData = dra_decoder_open_and_decode_file_f32(filePath, &channels, &sampleRate, &totalSampleCount); + if (pSampleData == NULL) { + return DRA_RESULT_UNKNOWN_ERROR; + } + + dra_result result = dra_voice_create(pDevice, dra_format_f32, channels, sampleRate, (size_t)totalSampleCount * sizeof(float), pSampleData, ppVoice); + + free(pSampleData); + return result; +} +#endif + + +//// High Level World APIs //// + +dra_sound_world* dra_sound_world_create(dra_device* pPlaybackDevice) +{ + dra_sound_world* pWorld = (dra_sound_world*)calloc(1, sizeof(*pWorld)); + if (pWorld == NULL) { + goto on_error; + } + + pWorld->pPlaybackDevice = pPlaybackDevice; + if (pWorld->pPlaybackDevice == NULL) { + dra_result result = dra_device_create(NULL, dra_device_type_playback, &pWorld->pPlaybackDevice); + if (result != DRA_RESULT_SUCCESS) { + return NULL; + } + + pWorld->ownsPlaybackDevice = DR_TRUE; + } + + + + + return pWorld; + + +on_error: + dra_sound_world_delete(pWorld); + return NULL; +} + +void dra_sound_world_delete(dra_sound_world* pWorld) +{ + if (pWorld == NULL) { + return; + } + + if (pWorld->ownsPlaybackDevice) { + dra_device_delete(pWorld->pPlaybackDevice); + } + + free(pWorld); +} + + +void dra_sound_world__on_inline_sound_stop(dr_uint64 eventID, void* pUserData) +{ + (void)eventID; + + dra_sound* pSound = (dra_sound*)pUserData; + assert(pSound != NULL); + + dra_sound_delete(pSound); +} + +void dra_sound_world_play_inline(dra_sound_world* pWorld, dra_sound_desc* pDesc, dra_mixer* pMixer) +{ + if (pWorld == NULL || pDesc == NULL) { + return; + } + + // An inline sound is just like any other, except it never loops and it's deleted automatically when it stops playing. Therefore what + // we need to do is attach an event handler to the voice's stop callback which is where the sound will be deleted. + dra_sound* pSound = dra_sound_create(pWorld, pDesc); + if (pSound == NULL) { + return; + } + + if (pMixer != NULL) { + dra_sound_attach_to_mixer(pSound, pMixer); + } + + dra_voice_set_on_stop(pSound->pVoice, dra_sound_world__on_inline_sound_stop, pSound); + dra_sound_play(pSound, DR_FALSE); +} + +void dra_sound_world_play_inline_3f(dra_sound_world* pWorld, dra_sound_desc* pDesc, dra_mixer* pMixer, float xPos, float yPos, float zPos) +{ + if (pWorld == NULL || pDesc == NULL) { + return; + } + + // TODO: Implement 3D positioning once the effects framework is in. + (void)xPos; + (void)yPos; + (void)zPos; + dra_sound_world_play_inline(pWorld, pDesc, pMixer); +} + +void dra_sound_world_stop_all_sounds(dra_sound_world* pWorld) +{ + if (pWorld == NULL) { + return; + } + + // When sounds are stopped the on_stop event handler will be fired. It is possible for the implementation of this event handler to + // delete the sound, so we'll first need to gather the sounds into a separate list. + size_t voiceCount = dra_mixer_count_attached_voices_recursive(pWorld->pPlaybackDevice->pMasterMixer); + if (voiceCount == 0) { + return; + } + + dra_voice** ppVoices = (dra_voice**)malloc(voiceCount * sizeof(*ppVoices)); + if (ppVoices == NULL) { + return; + } + + voiceCount = dra_mixer_gather_attached_voices_recursive(pWorld->pPlaybackDevice->pMasterMixer, ppVoices); + assert(voiceCount != 0); + + for (size_t iVoice = 0; iVoice < voiceCount; ++iVoice) { + dra_voice_stop(ppVoices[iVoice]); + } + + free(ppVoices); +} + +void dra_sound_world_set_listener_position(dra_sound_world* pWorld, float xPos, float yPos, float zPos) +{ + if (pWorld == NULL) { + return; + } + + // TODO: Implement me. + (void)xPos; + (void)yPos; + (void)zPos; +} + +void dra_sound_world_set_listener_orientation(dra_sound_world* pWorld, float xForward, float yForward, float zForward, float xUp, float yUp, float zUp) +{ + if (pWorld == NULL) { + return; + } + + // TODO: Implement me. + (void)xForward; + (void)yForward; + (void)zForward; + (void)xUp; + (void)yUp; + (void)zUp; +} + + +dr_bool32 dra_sound__is_streaming(dra_sound* pSound) +{ + assert(pSound != NULL); + return pSound->desc.dataSize == 0 || pSound->desc.pData == NULL; +} + +dr_bool32 dra_sound__read_next_chunk(dra_sound* pSound, dr_uint64 outputSampleOffset) +{ + assert(pSound != NULL); + if (pSound->desc.onRead == NULL) { + return DR_FALSE; + } + + dr_uint64 chunkSizeInSamples = (pSound->pVoice->frameCount * pSound->pVoice->channels) / 2; + assert(chunkSizeInSamples > 0); + + dr_uint64 samplesRead = pSound->desc.onRead(pSound, chunkSizeInSamples, dra_voice_get_buffer_ptr_by_sample(pSound->pVoice, outputSampleOffset)); + if (samplesRead == 0 && !pSound->isLooping) { + dra_voice_write_silence(pSound->pVoice, outputSampleOffset, chunkSizeInSamples); + return DR_FALSE; // Ran out of samples in a non-looping buffer. + } + + if (samplesRead == chunkSizeInSamples) { + return DR_TRUE; + } + + assert(samplesRead > 0); + assert(samplesRead < chunkSizeInSamples); + + // Ran out of samples. If the sound is not looping it simply means the end of the data has been reached. The remaining samples need + // to be zeroed out to create silence. + if (!pSound->isLooping) { + dra_voice_write_silence(pSound->pVoice, outputSampleOffset + samplesRead, chunkSizeInSamples - samplesRead); + return DR_TRUE; + } + + // At this point the sound will not be looping. We want to continuously loop back to the start and keep reading samples until the + // chunk is filled. + while (samplesRead < chunkSizeInSamples) { + if (!pSound->desc.onSeek(pSound, 0)) { + return DR_FALSE; + } + + dr_uint64 samplesRemaining = chunkSizeInSamples - samplesRead; + dr_uint64 samplesJustRead = pSound->desc.onRead(pSound, samplesRemaining, dra_voice_get_buffer_ptr_by_sample(pSound->pVoice, outputSampleOffset + samplesRead)); + if (samplesJustRead == 0) { + return DR_FALSE; + } + + samplesRead += samplesJustRead; + } + + return DR_TRUE; +} + +void dra_sound__on_read_next_chunk(dr_uint64 eventID, void* pUserData) +{ + dra_sound* pSound = (dra_sound*)pUserData; + assert(pSound != NULL); + + if (pSound->stopOnNextChunk) { + pSound->stopOnNextChunk = DR_FALSE; + dra_sound_stop(pSound); + return; + } + + // The event ID is the index of the sample to write to. + dr_uint64 sampleOffset = eventID; + if (!dra_sound__read_next_chunk(pSound, sampleOffset)) { + pSound->stopOnNextChunk = DR_TRUE; + } +} + + +dra_sound* dra_sound_create(dra_sound_world* pWorld, dra_sound_desc* pDesc) +{ + if (pWorld == NULL) { + return NULL; + } + + dr_bool32 isStreaming = DR_FALSE; + dra_result result = DRA_RESULT_SUCCESS; + + dra_sound* pSound = (dra_sound*)calloc(1, sizeof(*pSound)); + if (pSound == NULL) { + result = DRA_RESULT_OUT_OF_MEMORY; + goto on_error; + } + + pSound->pWorld = pWorld; + pSound->desc = *pDesc; + + isStreaming = dra_sound__is_streaming(pSound); + if (!isStreaming) { + result = dra_voice_create(pWorld->pPlaybackDevice, pDesc->format, pDesc->channels, pDesc->sampleRate, pDesc->dataSize, pDesc->pData, &pSound->pVoice); + } else { + size_t streamingBufferSize = (pDesc->sampleRate * pDesc->channels) * 2; // 2 seconds total, 1 second chunks. Keep total an even number and a multiple of the channel count. + result = dra_voice_create(pWorld->pPlaybackDevice, pDesc->format, pDesc->channels, pDesc->sampleRate, streamingBufferSize * dra_get_bytes_per_sample_by_format(pDesc->format), NULL, &pSound->pVoice); + + // Streaming buffers require 2 playback events. As one is being played, the other is filled. The event ID is set to the sample + // index of the next chunk that needs updating and is used in determining where to place new data. + dra_voice_add_playback_event(pSound->pVoice, 0, streamingBufferSize/2, dra_sound__on_read_next_chunk, pSound); + dra_voice_add_playback_event(pSound->pVoice, streamingBufferSize/2, 0, dra_sound__on_read_next_chunk, pSound); + } + + if (result != DRA_RESULT_SUCCESS) { + goto on_error; + } + + pSound->pVoice->pUserData = pSound; + + + // Streaming buffers need to have an initial chunk of data loaded before returning. This ensures the internal buffer contains valid audio data in + // preparation for being played for the first time. + if (isStreaming) { + if (!dra_sound__read_next_chunk(pSound, 0)) { + goto on_error; + } + } + + return pSound; + +on_error: + dra_sound_delete(pSound); + return NULL; +} + + + +void dra_sound__on_delete_decoder(dra_sound* pSound) +{ + dra_decoder* pDecoder = (dra_decoder*)pSound->desc.pUserData; + assert(pDecoder != NULL); + + dra_decoder_close(pDecoder); + free(pDecoder); +} + +dr_uint64 dra_sound__on_read_decoder(dra_sound* pSound, dr_uint64 samplesToRead, void* pSamplesOut) +{ + dra_decoder* pDecoder = (dra_decoder*)pSound->desc.pUserData; + assert(pDecoder != NULL); + + return dra_decoder_read_f32(pDecoder, samplesToRead, (float*)pSamplesOut); +} + +dr_bool32 dra_sound__on_seek_decoder(dra_sound* pSound, dr_uint64 sample) +{ + dra_decoder* pDecoder = (dra_decoder*)pSound->desc.pUserData; + assert(pDecoder != NULL); + + return dra_decoder_seek_to_sample(pDecoder, sample); +} + +#ifndef DR_AUDIO_NO_STDIO +dra_sound* dra_sound_create_from_file(dra_sound_world* pWorld, const char* filePath) +{ + if (pWorld == NULL || filePath == NULL) { + return NULL; + } + + dra_decoder* pDecoder = (dra_decoder*)malloc(sizeof(*pDecoder)); + if (pDecoder == NULL) { + return NULL; + } + + dra_result result = dra_decoder_open_file(pDecoder, filePath); + if (result != DRA_RESULT_SUCCESS) { + free(pDecoder); + return NULL; + } + + dra_sound_desc desc; + desc.format = dra_format_f32; + desc.channels = pDecoder->channels; + desc.sampleRate = pDecoder->sampleRate; + desc.dataSize = 0; + desc.pData = NULL; + desc.onDelete = dra_sound__on_delete_decoder; + desc.onRead = dra_sound__on_read_decoder; + desc.onSeek = dra_sound__on_seek_decoder; + desc.pUserData = pDecoder; + + dra_sound* pSound = dra_sound_create(pWorld, &desc); + + // After creating the sound, the audio data of a non-streaming voice can be deleted. + if (desc.pData != NULL) { + free(desc.pData); + } + + return pSound; +} +#endif + +void dra_sound_delete(dra_sound* pSound) +{ + if (pSound == NULL) { + return; + } + + if (pSound->pVoice != NULL) { + dra_voice_delete(pSound->pVoice); + } + + if (pSound->desc.onDelete) { + pSound->desc.onDelete(pSound); + } + + free(pSound); +} + + +void dra_sound_play(dra_sound* pSound, dr_bool32 loop) +{ + if (pSound == NULL) { + return; + } + + // The voice is always set to loop for streaming sounds. + if (dra_sound__is_streaming(pSound)) { + dra_voice_play(pSound->pVoice, DR_TRUE); + } else { + dra_voice_play(pSound->pVoice, loop); + } + + pSound->isLooping = loop; +} + +void dra_sound_stop(dra_sound* pSound) +{ + if (pSound == NULL) { + return; + } + + dra_voice_stop(pSound->pVoice); +} + + +void dra_sound_attach_to_mixer(dra_sound* pSound, dra_mixer* pMixer) +{ + if (pSound == NULL) { + return; + } + + if (pMixer == NULL) { + pMixer = pSound->pWorld->pPlaybackDevice->pMasterMixer; + } + + dra_mixer_attach_voice(pMixer, pSound->pVoice); +} + + +void dra_sound_set_on_stop(dra_sound* pSound, dra_event_proc proc, void* pUserData) +{ + dra_voice_set_on_stop(pSound->pVoice, proc, pUserData); +} + +void dra_sound_set_on_play(dra_sound* pSound, dra_event_proc proc, void* pUserData) +{ + dra_voice_set_on_play(pSound->pVoice, proc, pUserData); +} + +#endif //DR_AUDIO_IMPLEMENTATION + + +// TODO +// +// - Forward declare every backend function and document them. +// - Add support for the push API in stb_vorbis. + +// DEVELOPMENT NOTES AND BRAINSTORMING +// +// This is just random brainstorming and is likely very out of date and often just outright incorrect. +// +// +// Latency +// +// When a device is created it'll create a "hardware buffer" which is basically the buffer that the hardware +// device will read from when it needs to play audio. The hardware buffer is divided into two halves. As the +// buffer plays, it moves the playback pointer forward through the buffer and loops. When it hits the half +// way point it notifies the application that it needs more data to continue playing. Once one half starts +// playing the data within it is committed and cannot be changed. The size of each half determines the latency +// of the device. +// +// It sounds tempting to set this to something small like 1ms, but making it too small will +// increase the chance that the CPU won't be able to keep filling it with fresh data. In addition it will +// incrase overall CPU usage because operating system's scheduler will need to wake up the thread more often. +// Increasing the latency will increase memory usage and make playback of new sound sources take longer to +// begin playing. For example, if the latency was set to something like 1 second, a sound effect in a game +// may take up to a whole second to start playing. A balance needs to be made when setting the latency, and +// it can be configured when the device is created. +// +// (mention the fragments system to help avoiding the CPU running out of time to fill new audio data) +// +// +// Mixing +// +// Mixing is done via dra_mixer objects. Buffers can be attached to a mixer, but not more than one at a time. +// By default buffers are attached to a master mixer. Effects like volume can be applied on a per-buffer and +// per-mixer basis. +// +// Mixers can be chained together in a hierarchial manner. Child mixers will be mixed first with the result +// then passed on to higher level mixing. The master mixer is always the top level mixer. +// +// An obvious use case for mixers in games is to have separate volume controls for different categories of +// sounds, such as music, voices and sounds effects. Another example may be in music production where you may +// want to have separate mixers for vocal tracks, percussion tracks, etc. +// +// A mixer can be thought of as a complex buffer - it can be played/stopped/paused and have effects such as +// volume apllied to it. All of this affects all attached buffers and sub-mixers. You can, for example, pause +// every buffer attached to the mixer by simply pausing the mixer. This is an efficient and easy way to pause +// a group of audio buffers at the same time, such as when the user hits the pause button in a game. +// +// Every device includes a master mixer which is the one that buffers are automatically attached to. This one +// is intentionally hidden from the public facing API in order to keep it simpler. For basic audio playback +// using the master mixer will work just fine, however for more complex sound interactions you'll want to use +// your own mixers. Mixers, like buffers, are attached to the master mixer by default +// +// +// Thread Safety +// +// Everything in dr_audio should be thread-safe. +// +// Backends are implemented as if they are always run from a single thread. It's up to the cross-platform +// section to ensure thread-safety. This is an important property because if each backend is responsible for +// their own thread safety it increases the likelyhood of subtle backend-specific bugs. +// +// Every device has their own thread for asynchronous playback. This thread is created when the device is +// created, and deleted when the device is deleted. +// +// (Note edge cases when thread-safety may be an issue) + + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ diff --git a/source/engine/thirdparty/dr_libs/old/dr_audio_ancient.h b/source/engine/thirdparty/dr_libs/old/dr_audio_ancient.h new file mode 100644 index 0000000..4cb62f3 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/old/dr_audio_ancient.h @@ -0,0 +1,4310 @@ +// Public domain. See "unlicense" statement at the end of this file. + +// +// QUICK NOTES +// +// If you've stumbled across this library, be aware that this is very, very early in development. A lot of APIs +// are very temporary, in particular the effects API which at the moment are tied very closely to DirectSound. +// +// Currently, the only backend available is DirectSound while I figure out the API. +// +// General +// - This library is NOT thread safe. Functions can be called from any thread, but it is up to the host application +// to do synchronization. +// - This library is only concerned with playback and recording of raw audio data. It does not load audio files +// such as WAV, OGG and MP3. +// - Before you can create an output (playback) or input (recording) device you need to first create a context. +// - Each backend (DirectSound, ALSA, etc.) has it's own context. Using draudio_create_context() will find +// a backend implementation based on the platform dr_audio has been compiled for. +// - A context for a specific backend can be created as well. For example, draudio_create_context_dsound() will +// create a context which uses DirectSound as it's backend. +// - Currently, devices are enumerated once when the context is created. Thus, when a device is plugged in or +// unplugged it will not be detected by dr_audio and the context will need to be deleted and re-created. +// - Currently, dr_audio will only consider the first DRAUDIO_MAX_DEVICE_COUNT output and input devices, which +// is currently set to 16 and should be plenty for the vast majority of cases. Feel free to increase (or decrease) +// this number to suit your own requirements. +// +// Events +// - Events are handled via callbacks. The different types of events include stop, pause, play and markers. +// - The Stop event is fired when an output buffer is stopped, either from finishing it's playback or if it was +// stopped manually. +// - The Pause event is fired when the output buffer is paused. +// - The Play event is fired when the output buffer begins being played from either a stopped or paused state. +// - A Marker event is fired when the playback position of an output buffer reaches a certain point within the +// buffer. This is useful for streaming audio data because it can tell you when a particular section of the +// buffer can be filled with new data. +// - Due to the inherent multi-threaded nature of audio playback, events can be fired from any thread. It is up +// to the application to ensure events are handled safely. +// - Currently, the maximum number of markers is set by DRAUDIO_MAX_MARKER_COUNT which is set to 4 by default. This +// can be increased, however doing so increases memory usage for each sound buffer. +// +// Performance Considerations +// - Creating and deleting buffers should be considered an expensive operation because there is quite a bit of thread +// management being performed under the hood. Prefer caching sound buffers. +// + +// +// OPTIONS +// +// #define DRAUDIO_NO_DIRECTSOUND +// Disables support for the DirectSound backend. Note that at the moment this is the only backend available for +// Windows platforms, so you will likely not want to set this. DirectSound will only be compiled on Win32 builds. +// + +// +// TODO +// +// - DirectSound: Consider using Win32 critical sections instead of events where possible. +// - DirectSound: Remove the semaphore and replace with an auto-reset event. +// - Implement a better error handling API. +// - Implement effects +// - Implement velocity +// - Implement cones +// - Implement attenuation min/max distances +// + +#ifndef dr_audio_h +#define dr_audio_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + + +#define DRAUDIO_MAX_DEVICE_COUNT 16 +#define DRAUDIO_MAX_MARKER_COUNT 4 +#define DRAUDIO_MAX_MESSAGE_QUEUE_SIZE 1024 // The maximum number of messages that can be cached in the internal message queues. + + +#if defined(_WIN32) && !defined(DRAUDIO_NO_DIRECTSOUND) +#define DRAUDIO_BUILD_DSOUND +#endif + + +#define DRAUDIO_EVENT_ID_STOP 0xFFFFFFFF +#define DRAUDIO_EVENT_ID_PAUSE 0xFFFFFFFE +#define DRAUDIO_EVENT_ID_PLAY 0xFFFFFFFD +#define DRAUDIO_EVENT_ID_MARKER 0 + +#define DRAUDIO_ENABLE_3D (1 << 0) +#define DRAUDIO_RELATIVE_3D (1 << 1) // <-- Uses relative 3D positioning by default instead of absolute. Only used if DRAUDIO_ENABLE_3D is also specified. + + +// Data formats. +typedef enum +{ + draudio_format_pcm, + draudio_format_float + +} draudio_format; + +// Playback states. +typedef enum +{ + draudio_stopped, + draudio_paused, + draudio_playing + +} draudio_playback_state; + +// Effect types. +typedef enum +{ + draudio_effect_type_none, + draudio_effect_type_i3dl2reverb, + draudio_effect_type_chorus, + draudio_effect_type_compressor, + draudio_effect_type_distortion, + draudio_effect_type_echo, + draudio_effect_type_flanger + +} draudio_effect_type; + +// 3D processing modes. +typedef enum +{ + draudio_3d_mode_absolute, + draudio_3d_mode_relative, + draudio_3d_mode_disabled + +} draudio_3d_mode; + + +typedef struct draudio_context draudio_context; +typedef struct draudio_device draudio_device; +typedef struct draudio_buffer draudio_buffer; + +typedef void (* draudio_event_callback_proc)(draudio_buffer* pBuffer, unsigned int eventID, void *pUserData); + +typedef struct +{ + /// The callback function. + draudio_event_callback_proc callback; + + /// The user data. + void* pUserData; + +} draudio_event_callback; + +typedef struct +{ + /// The description of the device. + char description[256]; + +} draudio_device_info; + +typedef struct +{ + /// Boolean flags. + /// DRAUDIO_ENABLE_3D: Enable 3D positioning + unsigned int flags; + + /// The data format. + draudio_format format; + + /// The number of channels. This should be 1 for mono, 2 for stereo. + unsigned int channels; + + /// The sample rate. + unsigned int sampleRate; + + /// The number of bits per sample. + unsigned int bitsPerSample; + + /// The size in bytes of the data. + size_t sizeInBytes; + + /// A pointer to the initial data. + void* pData; + +} draudio_buffer_desc; + +typedef struct +{ + /// The effect type. + draudio_effect_type type; + + struct + { + float room; + float roomHF; + float roomRolloffFactor; + float decayTime; + float reflections; + float reflectionsDelay; + float reverb; + float reverbDelay; + float diffusion; + float density; + float hfReference; + } i3dl2reverb; + + struct + { + int waveform; + int phase; + float depth; + float feedback; + float frequency; + float delay; + } chorus; + + struct + { + float gain; + float attack; + float release; + float threshold; + float ratio; + float predelay; + } compressor; + + struct + { + float gain; + float edge; + float postEQCenterFrequency; + float postEQBandwidth; + float preLowpassCutoff; + } distortion; + + struct + { + float wetDryMix; + float feedback; + float leftDelay; + float rightDelay; + float panDelay; + } echo; + + struct + { + float wetDryMix; + float depth; + float feedback; + float frequency; + float waveform; + float delay; + float phase; + } flanger; + +} draudio_effect; + + +/// Creates a context which chooses an appropriate backend based on the given platform. +draudio_context* draudio_create_context(); + +#ifdef DRAUDIO_BUILD_DSOUND +/// Creates a context that uses DirectSound for it's backend. +draudio_context* draudio_create_context_dsound(); +#endif + +/// Deletes the given context. +void draudio_delete_context(draudio_context* pContext); + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// OUTPUT / PLAYBACK +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +/// Retrieves the number of output devices that were enumerated when the context was created. +unsigned int draudio_get_output_device_count(draudio_context* pContext); + +/// Retrieves information about the device at the given index. +bool draudio_get_output_device_info(draudio_context* pContext, unsigned int deviceIndex, draudio_device_info* pInfoOut); + + +/// Creates a output device. +/// +/// @param pContext [in] A pointer to the main context. +/// @param deviceIndex [in] The index of the device. +/// +/// @remarks +/// Use a device index of 0 to use the default output device. +draudio_device* draudio_create_output_device(draudio_context* pContext, unsigned int deviceIndex); + +/// Deletes the given output device. +void draudio_delete_output_device(draudio_device* pDevice); + + +/// Create a buffer. +/// +/// @remarks +/// This will fail if 3D positioning is requested when the sound has more than 1 channel. +draudio_buffer* draudio_create_buffer(draudio_device* pDevice, draudio_buffer_desc* pBufferDesc, size_t extraDataSize); + +/// Deletes the given buffer. +void draudio_delete_buffer(draudio_buffer* pBuffer); + + +/// Retrieves the size in bytes of the given buffer's extra data. +unsigned int draudio_get_buffer_extra_data_size(draudio_buffer* pBuffer); + +/// Retrieves a pointer to the given buffer's extra data. +void* draudio_get_buffer_extra_data(draudio_buffer* pBuffer); + + +/// Sets the audio data of the given buffer. +void draudio_set_buffer_data(draudio_buffer* pBuffer, size_t offset, const void* pData, size_t dataSizeInBytes); + + +/// Begins or resumes playing the given buffer. +/// +/// @remarks +/// If the sound is already playing, it will continue to play, but the \c loop setting will be replaced with that specified +/// by the most recent call. +void draudio_play(draudio_buffer* pBuffer, bool loop); + +/// Pauses playback of the given buffer. +void draudio_pause(draudio_buffer* pBuffer); + +/// Stops playback of the given buffer. +void draudio_stop(draudio_buffer* pBuffer); + +/// Retrieves the playback state of the given buffer. +draudio_playback_state draudio_get_playback_state(draudio_buffer* pBuffer); + +/// Determines whether or not the given audio buffer is looping. +bool draudio_is_looping(draudio_buffer* pBuffer); + + +/// Sets the playback position for the given buffer. +void draudio_set_playback_position(draudio_buffer* pBuffer, unsigned int position); + +/// Retrieves hte playback position of the given buffer. +unsigned int draudio_get_playback_position(draudio_buffer* pBuffer); + + +/// Sets the pan of the given buffer. +/// +/// @remarks +/// This does nothing for 3D sounds. +void draudio_set_pan(draudio_buffer* pBuffer, float pan); + +/// Retrieves the pan of the given buffer. +float draudio_get_pan(draudio_buffer* pBuffer); + + +/// Sets the volume of the given buffer. +/// +/// @param volume [in] The new volume. +/// +/// @remarks +/// Amplificiation is not currently supported, so the maximum value is 1. A value of 1 represents the volume of the original +/// data. +void draudio_set_volume(draudio_buffer* pBuffer, float volume); + +/// Retrieves the volume of the sound. +float draudio_get_volume(draudio_buffer* pBuffer); + + +/// Removes every marker. +void draudio_remove_markers(draudio_buffer* pBuffer); + +/// Registers the callback to fire when the playback position hits a certain position in the given buffer. +/// +/// @param eventID [in] The event ID that will be passed to the callback and can be used to identify a specific marker. +/// +/// @remarks +/// This will fail if the buffer is not in a stopped state. +/// @par +/// Set the event ID to DRAUDIO_EVENT_ID_MARKER + n, where "n" is your own application-specific identifier. +bool draudio_register_marker_callback(draudio_buffer* pBuffer, size_t offsetInBytes, draudio_event_callback_proc callback, unsigned int eventID, void* pUserData); + +/// Registers the callback to fire when the buffer stops playing. +/// +/// @remarks +/// This will fail if the buffer is not in a stopped state and the callback is non-null. It is fine to call this +/// with a null callback while the buffer is in the middle of playback in which case the callback will be cleared. +/// @par +/// The will replace any previous callback. +bool draudio_register_stop_callback(draudio_buffer* pBuffer, draudio_event_callback_proc callback, void* pUserData); + +/// Registers the callback to fire when the buffer is paused. +/// +/// @remarks +/// This will fail if the buffer is not in a stopped state and the callback is non-null. It is fine to call this +/// with a null callback while the buffer is in the middle of playback in which case the callback will be cleared. +/// @par +/// The will replace any previous callback. +bool draudio_register_pause_callback(draudio_buffer* pBuffer, draudio_event_callback_proc callback, void* pUserData); + +/// Registers the callback to fire when the buffer begins playing from either a stopped or paused state. +/// +/// @remarks +/// This will fail if the buffer is not in a stopped state and the callback is non-null. It is fine to call this +/// with a null callback while the buffer is in the middle of playback in which case the callback will be cleared. +/// @par +/// The will replace any previous callback. +bool draudio_register_play_callback(draudio_buffer* pBuffer, draudio_event_callback_proc callback, void* pUserData); + + +/// Retrieves the callback that is currently set for the stop event. +draudio_event_callback draudio_get_stop_callback(draudio_buffer* pBuffer); + +/// Retrieves the callback that is currently set for the pause event. +draudio_event_callback draudio_get_pause_callback(draudio_buffer* pBuffer); + +/// Retrieves the callback that is currently set for the play event. +draudio_event_callback draudio_get_play_callback(draudio_buffer* pBuffer); + + +/// Sets the position of the given buffer in 3D space. +/// +/// @remarks +/// This does nothing for buffers that do not support 3D positioning. +void draudio_set_position(draudio_buffer* pBuffer, float x, float y, float z); + +/// Retrieves the position of the given buffer in 3D space. +void draudio_get_position(draudio_buffer* pBuffer, float* pPosOut); + +/// Sets the position of the listener for the given output device. +void draudio_set_listener_position(draudio_device* pDevice, float x, float y, float z); + +/// Retrieves the position of the listner for the given output device. +void draudio_get_listener_position(draudio_device* pDevice, float* pPosOut); + +/// Sets the orientation of the listener for the given output device. +void draudio_set_listener_orientation(draudio_device* pDevice, float forwardX, float forwardY, float forwardZ, float upX, float upY, float upZ); + +/// Retrieves the orientation of the listener for the given output device. +void draudio_get_listener_orientation(draudio_device* pDevice, float* pForwardOut, float* pUpOut); + + +/// Sets the 3D processing mode (absolute, relative or disabled). +/// +/// @remarks +/// This applies to position, orientation and velocity. +void draudio_set_3d_mode(draudio_buffer* pBuffer, draudio_3d_mode mode); + +/// Retrieves the 3D processing mode (absolute, relative or disabled). +draudio_3d_mode draudio_get_3d_mode(draudio_buffer* pBuffer); + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// INPUT / RECORDING +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// HIGH-LEVEL API +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +//// SYNCHRONIZATION //// + +typedef void* draudio_mutex; + +/// Creates a mutex object. +draudio_mutex draudio_create_mutex(); + +/// Deletes a mutex object. +void draudio_delete_mutex(draudio_mutex mutex); + +/// Locks the given mutex. +void draudio_lock_mutex(draudio_mutex mutex); + +/// Unlocks the given mutex. +void draudio_unlock_mutex(draudio_mutex mutex); + + + + +//// STREAMING //// +typedef int draudio_bool; + +typedef draudio_bool (* draudio_stream_read_proc)(void* pUserData, void* pDataOut, size_t bytesToRead, size_t* bytesReadOut); +typedef draudio_bool (* draudio_stream_seek_proc)(void* pUserData, size_t offsetInBytesFromStart); + +typedef struct +{ + /// A pointer to the user data to pass to each callback. + void* pUserData; + + /// A pointer to the function to call when more data needs to be read. + draudio_stream_read_proc read; + + /// Seeks source data from the beginning of the file. + draudio_stream_seek_proc seek; + +} draudio_streaming_callbacks; + + +/// Creates a buffer that's pre-configured for use for streaming audio data. +/// +/// @remarks +/// This function is just a high-level convenience wrapper. The returned buffer is just a regular buffer with pre-configured +/// markers attached to the buffer. This will attach 3 markers in total which means there is only DRAUDIO_MAX_MARKER_COUNT - 3 +/// marker slots available to the application. +/// @par +/// You must play the buffer with draudio_play_streaming_buffer() because the underlying buffer management is slightly different +/// to a regular buffer. +/// @par +/// Looping and stop callbacks may be inaccurate by up to half a second. +/// @par +/// Callback functions use bytes to determine how much data to process. This is always a multiple of samples * channels, however. +/// @par +/// The first chunk of data is not loaded until the buffer is played with draudio_play_streaming_buffer(). +draudio_buffer* draudio_create_streaming_buffer(draudio_device* pDevice, draudio_buffer_desc* pBufferDesc, draudio_streaming_callbacks callbacks, unsigned int extraDataSize); + +/// Retrieves the size of the extra data of the given streaming buffer.. +size_t draudio_get_streaming_buffer_extra_data_size(draudio_buffer* pBuffer); + +/// Retrieves a pointer to the extra data of the given streaming buffer. +void* draudio_get_streaming_buffer_extra_data(draudio_buffer* pBuffer); + +/// Begins playing the given streaming buffer. +bool draudio_play_streaming_buffer(draudio_buffer* pBuffer, bool loop); + +/// Determines whether or not the given streaming buffer is looping. +bool draudio_is_streaming_buffer_looping(draudio_buffer* pBuffer); + + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Sound World +// +// When a sound is created, whether or not it will be streamed is determined by +// whether or not a pointer to some initial data is specified when creating the +// sound. When initial data is specified, the sound data will be loaded into +// buffer once at creation time. If no data is specified, sound data will be +// loaded dynamically at playback time. +// +/////////////////////////////////////////////////////////////////////////////// + +typedef struct draudio_sound draudio_sound; +typedef struct draudio_world draudio_world; + +typedef void (* draudio_on_sound_delete_proc) (draudio_sound* pSound); +typedef bool (* draudio_on_sound_read_data_proc)(draudio_sound* pSound, void* pDataOut, size_t bytesToRead, size_t* bytesReadOut); +typedef bool (* draudio_on_sound_seek_data_proc)(draudio_sound* pSound, size_t offsetInBytesFromStart); + +/// The structure that is used for creating a sound object. +typedef struct +{ + /// Boolean flags. + /// DRAUDIO_ENABLE_3D: Enable 3D positioning + unsigned int flags; + + /// The data format. + draudio_format format; + + /// The number of channels. This should be 1 for mono, 2 for stereo. + unsigned int channels; + + /// The sample rate. + unsigned int sampleRate; + + /// The number of bits per sample. + unsigned int bitsPerSample; + + /// The size in bytes of the data. When this is non-zero, and pInitialData is non-null, the onRead and onSeek streaming + /// callbacks are not used, and instead the sound's audio data is made up exclusively with this data. + size_t sizeInBytes; + + /// A pointer to the initial data. Can be null, in which case the audio data is streamed with the onRead and onSeek + /// callbacks below. It is an error for this to be null in addition to onRead and onSeek. + void* pData; + + + /// A pointer to the function to call when the sound is being deleted. This gives the application the opportunity + /// to delete internal objects that are used for streaming or whatnot. + draudio_on_sound_delete_proc onDelete; + + /// A pointer to the function to call when dr_audio needs to request a chunk of audio data. This is only used when + /// streaming data. + draudio_on_sound_read_data_proc onRead; + + /// A pointer to the function to call when dr_audio needs to seek the audio data. This is only used when streaming + /// data. + draudio_on_sound_seek_data_proc onSeek; + + + /// The size of the extra data to associate with the sound. Extra data is how an application can link custom data to the + /// sound object. + unsigned int extraDataSize; + + /// A pointer to a buffer containing the initial extra data. This buffer is copied when the sound is initially created, + /// and can be null. + const void* pExtraData; + +} draudio_sound_desc; + +struct draudio_sound +{ + /// A pointer to the world that owns the sound. + draudio_world* pWorld; + + /// A pointer to the audio buffer for playback. + draudio_buffer* pBuffer; + + + /// [Internal Use Only] The state of the buffer's playback at the time the associated world overwrote it. + draudio_playback_state prevPlaybackState; + + /// [Internal Use Only] A pointer to the next sound in the local list. + draudio_sound* pNextSound; + + /// [Internal Use Only] A pointer ot the previous sound in the local list. + draudio_sound* pPrevSound; + + /// [Internal Use Only] Keeps track of whether or not a streaming buffer is being used. + bool isUsingStreamingBuffer; + + /// [Internal Use Only] Keeps track of whether or not the sound has been marked for deletion. This is used to + /// ensure onRead and onSeek are never called after the sound has been deleted. This scenario is possible because + /// these functions are called in response to the sound buffer hitting markers which can be slightly delayed + /// due to multi-threading synchronization. + bool markedForDeletion; + + /// [Internal Use Only] the onDelete function. Can be null. + draudio_on_sound_delete_proc onDelete; + + /// [Internal Use Only] The onRead streaming function. Can be null, in which case streaming will not be used. + draudio_on_sound_read_data_proc onRead; + + /// [Internal Use Only] The onSeek streaming function. Can be null, in which case streaming will not be used. + draudio_on_sound_seek_data_proc onSeek; +}; + +struct draudio_world +{ + /// A pointer to the dr_audio device to output audio to. + draudio_device* pDevice; + + /// The global playback state of the world. + draudio_playback_state playbackState; + + /// A pointer to the first sound in the local list of sounds. + draudio_sound* pFirstSound; + + /// Mutex for thread-safety. + draudio_mutex lock; +}; + + +/// Creates a new sound world which will output audio from the given device. +draudio_world* draudio_create_world(draudio_device* pDevice); + +/// Deletes a sound world that was previously created with draudio_create_world(). +void draudio_delete_world(draudio_world* pWorld); + + +/// Creates a sound in 3D space. +draudio_sound* draudio_create_sound(draudio_world* pWorld, draudio_sound_desc desc); + +/// Deletes a sound that was previously created with draudio_create_sound(). +void draudio_delete_sound(draudio_sound* pSound); + +/// Deletes every sound from the given world. +void draudio_delete_all_sounds(draudio_world* pWorld); + + +/// Retrieves the size in bytes of the given sound's extra data. +size_t draudio_get_sound_extra_data_size(draudio_sound* pSound); + +/// Retrieves a pointer to the buffer containing the given sound's extra data. +void* draudio_get_sound_extra_data(draudio_sound* pSound); + + +/// Plays or resumes the given sound. +void draudio_play_sound(draudio_sound* pSound, bool loop); + +/// Pauses playback the given sound. +void draudio_pause_sound(draudio_sound* pSound); + +/// Stops playback of the given sound. +void draudio_stop_sound(draudio_sound* pSound); + +/// Retrieves the playback state of the given sound. +draudio_playback_state draudio_get_sound_playback_state(draudio_sound* pSound); + +/// Determines if the given sound is looping. +bool draudio_is_sound_looping(draudio_sound* pSound); + + +/// Begins playing a sound using the given streaming callbacks. +void draudio_play_inline_sound(draudio_world* pWorld, draudio_sound_desc desc); + +/// Begins playing the given sound at the given position. +void draudio_play_inline_sound_3f(draudio_world* pWorld, draudio_sound_desc desc, float posX, float posY, float posZ); + + +/// Stops playback of all sounds in the given world. +void draudio_stop_all_sounds(draudio_world* pWorld); + +/// Pauses playback of all sounds in the given world. +void draudio_pause_all_sounds(draudio_world* pWorld); + +/// Resumes playback of all sounds in the given world. +void draudio_resume_all_sounds(draudio_world* pWorld); + + +/// Sets the callback for the stop event for the given sound. +void draudio_set_sound_stop_callback(draudio_sound* pSound, draudio_event_callback_proc callback, void* pUserData); + +/// Sets the callback for the pause event for the given sound. +void draudio_set_sound_pause_callback(draudio_sound* pSound, draudio_event_callback_proc callback, void* pUserData); + +/// Sets the callback for the play event for the given sound. +void draudio_set_sound_play_callback(draudio_sound* pSound, draudio_event_callback_proc callback, void* pUserData); + + +/// Sets the position of the given sound. +void draudio_set_sound_position(draudio_sound* pSound, float posX, float posY, float posZ); + + +/// Sets the 3D mode of the given sound (absolute positioning, relative positioning, no positioning). +void draudio_set_sound_3d_mode(draudio_sound* pSound, draudio_3d_mode mode); + +/// Retrieves the 3D mode of the given sound. +draudio_3d_mode draudio_get_sound_3d_mode(draudio_sound* pSound); + + + +#ifdef __cplusplus +} +#endif + +#endif //dr_audio_h + +/////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef DR_AUDIO_IMPLEMENTATION +#include +#include +#include +#include +#include + + +// Annotations +#ifndef DRAUDIO_PRIVATE +#define DRAUDIO_PRIVATE +#endif + + +//////////////////////////////////////////////////////// +// Utilities + +// strcpy() +int draudio_strcpy(char* dst, size_t dstSizeInBytes, const char* src) +{ +#if defined(_MSC_VER) + return strcpy_s(dst, dstSizeInBytes, src); +#else + if (dst == 0) { + return EINVAL; + } + if (dstSizeInBytes == 0) { + return ERANGE; + } + if (src == 0) { + dst[0] = '\0'; + return EINVAL; + } + + char* iDst = dst; + const char* iSrc = src; + size_t remainingSizeInBytes = dstSizeInBytes; + while (remainingSizeInBytes > 0 && iSrc[0] != '\0') + { + iDst[0] = iSrc[0]; + + iDst += 1; + iSrc += 1; + remainingSizeInBytes -= 1; + } + + if (remainingSizeInBytes > 0) { + iDst[0] = '\0'; + } else { + dst[0] = '\0'; + return ERANGE; + } + + return 0; +#endif +} + + +typedef void (* draudio_delete_context_proc)(draudio_context* pContext); +typedef draudio_device* (* draudio_create_output_device_proc)(draudio_context* pContext, unsigned int deviceIndex); +typedef void (* draudio_delete_output_device_proc)(draudio_device* pDevice); +typedef unsigned int (* draudio_get_output_device_count_proc)(draudio_context* pContext); +typedef bool (* draudio_get_output_device_info_proc)(draudio_context* pContext, unsigned int deviceIndex, draudio_device_info* pInfoOut); +typedef draudio_buffer* (* draudio_create_buffer_proc)(draudio_device* pDevice, draudio_buffer_desc* pBufferDesc, size_t extraDataSize); +typedef void (* draudio_delete_buffer_proc)(draudio_buffer* pBuffer); +typedef unsigned int (* draudio_get_buffer_extra_data_size_proc)(draudio_buffer* pBuffer); +typedef void* (* draudio_get_buffer_extra_data_proc)(draudio_buffer* pBuffer); +typedef void (* draudio_set_buffer_data_proc)(draudio_buffer* pBuffer, size_t offset, const void* pData, size_t dataSizeInBytes); +typedef void (* draudio_play_proc)(draudio_buffer* pBuffer, bool loop); +typedef void (* draudio_pause_proc)(draudio_buffer* pBuffer); +typedef void (* draudio_stop_proc)(draudio_buffer* pBuffer); +typedef draudio_playback_state (* draudio_get_playback_state_proc)(draudio_buffer* pBuffer); +typedef void (* draudio_set_playback_position_proc)(draudio_buffer* pBuffer, unsigned int position); +typedef unsigned int (* draudio_get_playback_position_proc)(draudio_buffer* pBuffer); +typedef void (* draudio_set_pan_proc)(draudio_buffer* pBuffer, float pan); +typedef float (* draudio_get_pan_proc)(draudio_buffer* pBuffer); +typedef void (* draudio_set_volume_proc)(draudio_buffer* pBuffer, float volume); +typedef float (* draudio_get_volume_proc)(draudio_buffer* pBuffer); +typedef void (* draudio_remove_markers_proc)(draudio_buffer* pBuffer); +typedef bool (* draudio_register_marker_callback_proc)(draudio_buffer* pBuffer, size_t offsetInBytes, draudio_event_callback_proc callback, unsigned int eventID, void* pUserData); +typedef bool (* draudio_register_stop_callback_proc)(draudio_buffer* pBuffer, draudio_event_callback_proc callback, void* pUserData); +typedef bool (* draudio_register_pause_callback_proc)(draudio_buffer* pBuffer, draudio_event_callback_proc callback, void* pUserData); +typedef bool (* draudio_register_play_callback_proc)(draudio_buffer* pBuffer, draudio_event_callback_proc callback, void* pUserData); +typedef void (* draudio_set_position_proc)(draudio_buffer* pBuffer, float x, float y, float z); +typedef void (* draudio_get_position_proc)(draudio_buffer* pBuffer, float* pPosOut); +typedef void (* draudio_set_listener_position_proc)(draudio_device* pDevice, float x, float y, float z); +typedef void (* draudio_get_listener_position_proc)(draudio_device* pDevice, float* pPosOut); +typedef void (* draudio_set_listener_orientation_proc)(draudio_device* pDevice, float forwardX, float forwardY, float forwardZ, float upX, float upY, float upZ); +typedef void (* draudio_get_listener_orientation_proc)(draudio_device* pDevice, float* pForwardOut, float* pUpOut); +typedef void (* draudio_set_3d_mode_proc)(draudio_buffer* pBuffer, draudio_3d_mode mode); +typedef draudio_3d_mode (* draudio_get_3d_mode_proc)(draudio_buffer* pBuffer); + +struct draudio_context +{ + // Callbacks. + draudio_delete_context_proc delete_context; + draudio_create_output_device_proc create_output_device; + draudio_delete_output_device_proc delete_output_device; + draudio_get_output_device_count_proc get_output_device_count; + draudio_get_output_device_info_proc get_output_device_info; + draudio_create_buffer_proc create_buffer; + draudio_delete_buffer_proc delete_buffer; + draudio_get_buffer_extra_data_size_proc get_buffer_extra_data_size; + draudio_get_buffer_extra_data_proc get_buffer_extra_data; + draudio_set_buffer_data_proc set_buffer_data; + draudio_play_proc play; + draudio_pause_proc pause; + draudio_stop_proc stop; + draudio_get_playback_state_proc get_playback_state; + draudio_set_playback_position_proc set_playback_position; + draudio_get_playback_position_proc get_playback_position; + draudio_set_pan_proc set_pan; + draudio_get_pan_proc get_pan; + draudio_set_volume_proc set_volume; + draudio_get_volume_proc get_volume; + draudio_remove_markers_proc remove_markers; + draudio_register_marker_callback_proc register_marker_callback; + draudio_register_stop_callback_proc register_stop_callback; + draudio_register_pause_callback_proc register_pause_callback; + draudio_register_play_callback_proc register_play_callback; + draudio_set_position_proc set_position; + draudio_get_position_proc get_position; + draudio_set_listener_position_proc set_listener_position; + draudio_get_listener_position_proc get_listener_position; + draudio_set_listener_orientation_proc set_listener_orientation; + draudio_get_listener_orientation_proc get_listener_orientation; + draudio_set_3d_mode_proc set_3d_mode; + draudio_get_3d_mode_proc get_3d_mode; +}; + +struct draudio_device +{ + /// The context that owns this device. + draudio_context* pContext; + + /// Whether or not the device is marked for deletion. A device is not always deleted straight away, so we use this + /// to determine whether or not it has been marked for deletion. + bool markedForDeletion; +}; + +struct draudio_buffer +{ + /// The device that owns this buffer. + draudio_device* pDevice; + + /// The stop callback. + draudio_event_callback stopCallback; + + /// The pause callback. + draudio_event_callback pauseCallback; + + /// The play callback. + draudio_event_callback playCallback; + + /// Whether or not playback is looping. + bool isLooping; + + /// Whether or not the buffer is marked for deletion. A buffer is not always deleted straight away, so we use this + /// to determine whether or not it has been marked for deletion. + bool markedForDeletion; +}; + + +draudio_context* draudio_create_context() +{ + draudio_context* pContext = NULL; + +#ifdef DRAUDIO_BUILD_DSOUND + pContext = draudio_create_context_dsound(); + if (pContext != NULL) { + return pContext; + } +#endif + + + // If we get here it means we weren't able to create a context, so return NULL. Later on we're going to have a null implementation so that + // we don't ever need to return NULL here. + assert(pContext == NULL); + return pContext; +} + +void draudio_delete_context(draudio_context* pContext) +{ + if (pContext == NULL) { + return; + } + + pContext->delete_context(pContext); +} + + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// OUTPUT +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +unsigned int draudio_get_output_device_count(draudio_context* pContext) +{ + if (pContext == NULL) { + return 0; + } + + return pContext->get_output_device_count(pContext); +} + +bool draudio_get_output_device_info(draudio_context* pContext, unsigned int deviceIndex, draudio_device_info* pInfoOut) +{ + if (pContext == NULL) { + return false; + } + + if (pInfoOut == NULL) { + return false; + } + + return pContext->get_output_device_info(pContext, deviceIndex, pInfoOut); +} + + +draudio_device* draudio_create_output_device(draudio_context* pContext, unsigned int deviceIndex) +{ + if (pContext == NULL) { + return NULL; + } + + draudio_device* pDevice = pContext->create_output_device(pContext, deviceIndex); + if (pDevice != NULL) + { + pDevice->markedForDeletion = false; + } + + return pDevice; +} + +void draudio_delete_output_device(draudio_device* pDevice) +{ + if (pDevice == NULL) { + return; + } + + // If the device is already marked for deletion we just return straight away. However, this is an erroneous case so we trigger a failed assertion in this case. + if (pDevice->markedForDeletion) { + assert(false); + return; + } + + pDevice->markedForDeletion = true; + + assert(pDevice->pContext != NULL); + pDevice->pContext->delete_output_device(pDevice); +} + + +draudio_buffer* draudio_create_buffer(draudio_device* pDevice, draudio_buffer_desc* pBufferDesc, size_t extraDataSize) +{ + if (pDevice == NULL) { + return NULL; + } + + if (pBufferDesc == NULL) { + return NULL; + } + + assert(pDevice->pContext != NULL); + + draudio_buffer* pBuffer = pDevice->pContext->create_buffer(pDevice, pBufferDesc, extraDataSize); + if (pBuffer != NULL) + { + draudio_event_callback nullcb = {NULL, NULL}; + + pBuffer->pDevice = pDevice; + pBuffer->stopCallback = nullcb; + pBuffer->pauseCallback = nullcb; + pBuffer->playCallback = nullcb; + pBuffer->isLooping = false; + pBuffer->markedForDeletion = false; + } + + return pBuffer; +} + +void draudio_delete_buffer(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return; + } + + // We don't want to do anything if the buffer is marked for deletion. + if (pBuffer->markedForDeletion) { + assert(false); + return; + } + + pBuffer->markedForDeletion = true; + + + // The sound needs to be stopped first. + draudio_stop(pBuffer); + + // Now we need to remove every event. + draudio_remove_markers(pBuffer); + draudio_register_stop_callback(pBuffer, NULL, NULL); + draudio_register_pause_callback(pBuffer, NULL, NULL); + draudio_register_play_callback(pBuffer, NULL, NULL); + + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + pBuffer->pDevice->pContext->delete_buffer(pBuffer); +} + + +unsigned int draudio_get_buffer_extra_data_size(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return 0; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + return pBuffer->pDevice->pContext->get_buffer_extra_data_size(pBuffer); +} + +void* draudio_get_buffer_extra_data(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return NULL; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + return pBuffer->pDevice->pContext->get_buffer_extra_data(pBuffer); +} + + +void draudio_set_buffer_data(draudio_buffer* pBuffer, size_t offset, const void* pData, size_t dataSizeInBytes) +{ + if (pBuffer == NULL) { + return; + } + + if (pData == NULL) { + return; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + pBuffer->pDevice->pContext->set_buffer_data(pBuffer, offset, pData, dataSizeInBytes); +} + +void draudio_play(draudio_buffer* pBuffer, bool loop) +{ + if (pBuffer == NULL) { + return; + } + + pBuffer->isLooping = loop; + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + pBuffer->pDevice->pContext->play(pBuffer, loop); +} + +void draudio_pause(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + pBuffer->pDevice->pContext->pause(pBuffer); +} + +void draudio_stop(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + pBuffer->pDevice->pContext->stop(pBuffer); +} + +draudio_playback_state draudio_get_playback_state(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return draudio_stopped; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + return pBuffer->pDevice->pContext->get_playback_state(pBuffer); +} + +bool draudio_is_looping(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return false; + } + + return pBuffer->isLooping; +} + + +void draudio_set_playback_position(draudio_buffer* pBuffer, unsigned int position) +{ + if (pBuffer == NULL) { + return; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + pBuffer->pDevice->pContext->set_playback_position(pBuffer, position); +} + +unsigned int draudio_get_playback_position(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return 0; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + return pBuffer->pDevice->pContext->get_playback_position(pBuffer); +} + + +void draudio_set_pan(draudio_buffer* pBuffer, float pan) +{ + if (pBuffer == NULL) { + return; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + pBuffer->pDevice->pContext->set_pan(pBuffer, pan); +} + +float draudio_get_pan(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return 0.0f; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + return pBuffer->pDevice->pContext->get_pan(pBuffer); +} + + +void draudio_set_volume(draudio_buffer* pBuffer, float volume) +{ + if (pBuffer == NULL) { + return; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + pBuffer->pDevice->pContext->set_volume(pBuffer, volume); +} + +float draudio_get_volume(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return 1.0f; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + return pBuffer->pDevice->pContext->get_volume(pBuffer); +} + + +void draudio_remove_markers(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + pBuffer->pDevice->pContext->remove_markers(pBuffer); +} + +bool draudio_register_marker_callback(draudio_buffer* pBuffer, size_t offsetInBytes, draudio_event_callback_proc callback, unsigned int eventID, void* pUserData) +{ + if (pBuffer == NULL) { + return false; + } + + if (eventID == DRAUDIO_EVENT_ID_STOP || + eventID == DRAUDIO_EVENT_ID_PAUSE || + eventID == DRAUDIO_EVENT_ID_PLAY) + { + return false; + } + + if (draudio_get_playback_state(pBuffer) != draudio_stopped) { + return false; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + return pBuffer->pDevice->pContext->register_marker_callback(pBuffer, offsetInBytes, callback, eventID, pUserData); +} + +bool draudio_register_stop_callback(draudio_buffer* pBuffer, draudio_event_callback_proc callback, void* pUserData) +{ + if (pBuffer == NULL) { + return false; + } + + if (callback != NULL && draudio_get_playback_state(pBuffer) != draudio_stopped) { + return false; + } + + pBuffer->stopCallback.callback = callback; + pBuffer->stopCallback.pUserData = pUserData; + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + return pBuffer->pDevice->pContext->register_stop_callback(pBuffer, callback, pUserData); +} + +bool draudio_register_pause_callback(draudio_buffer* pBuffer, draudio_event_callback_proc callback, void* pUserData) +{ + if (pBuffer == NULL) { + return false; + } + + if (callback != NULL && draudio_get_playback_state(pBuffer) != draudio_stopped) { + return false; + } + + pBuffer->pauseCallback.callback = callback; + pBuffer->pauseCallback.pUserData = pUserData; + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + return pBuffer->pDevice->pContext->register_pause_callback(pBuffer, callback, pUserData); +} + +bool draudio_register_play_callback(draudio_buffer* pBuffer, draudio_event_callback_proc callback, void* pUserData) +{ + if (pBuffer == NULL) { + return false; + } + + if (callback != NULL && draudio_get_playback_state(pBuffer) != draudio_stopped) { + return false; + } + + pBuffer->playCallback.callback = callback; + pBuffer->playCallback.pUserData = pUserData; + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + return pBuffer->pDevice->pContext->register_play_callback(pBuffer, callback, pUserData); +} + + +draudio_event_callback draudio_get_stop_callback(draudio_buffer* pBuffer) +{ + if (pBuffer != NULL) { + return pBuffer->stopCallback; + } else { + draudio_event_callback result = {NULL, NULL}; + return result; + } +} + +draudio_event_callback draudio_get_pause_callback(draudio_buffer* pBuffer) +{ + if (pBuffer != NULL) { + return pBuffer->pauseCallback; + } else { + draudio_event_callback result = {NULL, NULL}; + return result; + } +} + +draudio_event_callback draudio_get_play_callback(draudio_buffer* pBuffer) +{ + if (pBuffer != NULL) { + return pBuffer->playCallback; + } else { + draudio_event_callback result = {NULL, NULL}; + return result; + } +} + + +void draudio_set_position(draudio_buffer* pBuffer, float x, float y, float z) +{ + if (pBuffer == NULL) { + return; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + pBuffer->pDevice->pContext->set_position(pBuffer, x, y, z); +} + +void draudio_get_position(draudio_buffer* pBuffer, float* pPosOut) +{ + if (pBuffer == NULL) { + return; + } + + if (pPosOut == NULL) { + return; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + pBuffer->pDevice->pContext->get_position(pBuffer, pPosOut); +} + + +void draudio_set_listener_position(draudio_device* pDevice, float x, float y, float z) +{ + if (pDevice == NULL) { + return; + } + + pDevice->pContext->set_listener_position(pDevice, x, y, z); +} + +void draudio_get_listener_position(draudio_device* pDevice, float* pPosOut) +{ + if (pDevice == NULL || pPosOut == NULL) { + return; + } + + pDevice->pContext->get_listener_position(pDevice, pPosOut); +} + +void draudio_set_listener_orientation(draudio_device* pDevice, float forwardX, float forwardY, float forwardZ, float upX, float upY, float upZ) +{ + if (pDevice == NULL) { + return; + } + + pDevice->pContext->set_listener_orientation(pDevice, forwardX, forwardY, forwardZ, upX, upY, upZ); +} + +void draudio_get_listener_orientation(draudio_device* pDevice, float* pForwardOut, float* pUpOut) +{ + if (pDevice == NULL) { + return; + } + + pDevice->pContext->get_listener_orientation(pDevice, pForwardOut, pUpOut); +} + + +void draudio_set_3d_mode(draudio_buffer* pBuffer, draudio_3d_mode mode) +{ + if (pBuffer == NULL) { + return; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + pBuffer->pDevice->pContext->set_3d_mode(pBuffer, mode); +} + +draudio_3d_mode draudio_get_3d_mode(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return draudio_3d_mode_disabled; + } + + assert(pBuffer->pDevice != NULL); + assert(pBuffer->pDevice->pContext != NULL); + return pBuffer->pDevice->pContext->get_3d_mode(pBuffer); +} + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// INPUT +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + + + + + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// HIGH-LEVEL API +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +//// SYNCHRONIZATION //// + +#if defined(_WIN32) +#include + +draudio_mutex draudio_create_mutex() +{ + draudio_mutex mutex = malloc(sizeof(CRITICAL_SECTION)); + if (mutex != NULL) + { + InitializeCriticalSection((LPCRITICAL_SECTION)mutex); + } + + return mutex; +} + +void draudio_delete_mutex(draudio_mutex mutex) +{ + DeleteCriticalSection((LPCRITICAL_SECTION)mutex); + free(mutex); +} + +void draudio_lock_mutex(draudio_mutex mutex) +{ + EnterCriticalSection((LPCRITICAL_SECTION)mutex); +} + +void draudio_unlock_mutex(draudio_mutex mutex) +{ + LeaveCriticalSection((LPCRITICAL_SECTION)mutex); +} +#else +#include + +draudio_mutex draudio_create_mutex() +{ + pthread_mutex_t* mutex = malloc(sizeof(pthread_mutex_t)); + if (pthread_mutex_init(mutex, NULL) != 0) { + free(mutex); + mutex = NULL; + } + + return mutex; +} + +void draudio_delete_mutex(draudio_mutex mutex) +{ + pthread_mutex_destroy(mutex); +} + +void draudio_lock_mutex(draudio_mutex mutex) +{ + pthread_mutex_lock(mutex); +} + +void draudio_unlock_mutex(draudio_mutex mutex) +{ + pthread_mutex_unlock(mutex); +} +#endif + + + +//// STREAMING //// + +#define DRAUDIO_STREAMING_MARKER_0 DRAUDIO_EVENT_ID_MARKER + 0 +#define DRAUDIO_STREAMING_MARKER_1 DRAUDIO_EVENT_ID_MARKER + 1 + +#define DRAUDIO_STREAMING_CHUNK_INVALID 0 + +typedef struct +{ + /// The steaming buffer callbacks. + draudio_streaming_callbacks callbacks; + + /// Keeps track of whether or not we are at the start of the playback. + bool atStart; + + /// Keeps track of whether or not we should stop at the end of the next chunk. + bool stopAtEndOfCurrentChunk; + + /// Keeps track of whether or not the sound should loop. + bool isLoopingEnabled; + + /// The size of the extra data. + size_t extraDataSize; + + /// The size of an individual chunk. A chunk is half the size of the buffer. + size_t chunkSize; + + /// A pointer to the temporary buffer for loading chunk data. + unsigned char pTempChunkData[1]; + +} ea_streaming_buffer_data; + + +bool ea_streaming_buffer_load_next_chunk(draudio_buffer* pBuffer, ea_streaming_buffer_data* pStreamingData, size_t offset, size_t chunkSize) +{ + assert(pStreamingData != NULL); + assert(pStreamingData->callbacks.read != NULL); + assert(pStreamingData->callbacks.seek != NULL); + assert(pStreamingData->chunkSize >= chunkSize); + + // A chunk size of 0 is valid, but we just return immediately. + if (chunkSize == 0) { + return true; + } + + size_t bytesRead; + if (!pStreamingData->callbacks.read(pStreamingData->callbacks.pUserData, pStreamingData->pTempChunkData, chunkSize, &bytesRead)) + { + // There was an error reading the data. We might have run out of data. + return false; + } + + + pStreamingData->stopAtEndOfCurrentChunk = false; + + draudio_set_buffer_data(pBuffer, offset, pStreamingData->pTempChunkData, bytesRead); + + if (chunkSize > bytesRead) + { + // The number of bytes read is less than our chunk size. This is our cue that we've reached the end of the steam. If we're looping, we + // just seek back to the start and read more data. There is a chance the data total size of the streaming data is smaller than our + // chunk, so we actually want to do this recursively. + // + // If we're not looping, we fill the remaining data with silence. + if (pStreamingData->isLoopingEnabled) + { + pStreamingData->callbacks.seek(pStreamingData->callbacks.pUserData, 0); + return ea_streaming_buffer_load_next_chunk(pBuffer, pStreamingData, offset + bytesRead, chunkSize - bytesRead); + } + else + { + memset(pStreamingData->pTempChunkData + bytesRead, 0, chunkSize - bytesRead); + draudio_set_buffer_data(pBuffer, offset + bytesRead, pStreamingData->pTempChunkData + bytesRead, chunkSize - bytesRead); + + pStreamingData->stopAtEndOfCurrentChunk = true; + } + } + + return true; +} + +void ea_steaming_buffer_marker_callback(draudio_buffer* pBuffer, unsigned int eventID, void *pUserData) +{ + ea_streaming_buffer_data* pStreamingData = (ea_streaming_buffer_data*)pUserData; + assert(pStreamingData != NULL); + + size_t offset = 0; + if (eventID == DRAUDIO_STREAMING_MARKER_0) { + offset = pStreamingData->chunkSize; + } + + if (pStreamingData->stopAtEndOfCurrentChunk) + { + if (!pStreamingData->atStart) { + draudio_stop(pBuffer); + } + } + else + { + ea_streaming_buffer_load_next_chunk(pBuffer, pStreamingData, offset, pStreamingData->chunkSize); + } + + pStreamingData->atStart = false; +} + + +draudio_buffer* draudio_create_streaming_buffer(draudio_device* pDevice, draudio_buffer_desc* pBufferDesc, draudio_streaming_callbacks callbacks, unsigned int extraDataSize) +{ + if (callbacks.read == NULL) { + return NULL; + } + + if (pBufferDesc == NULL) { + return NULL; + } + + + // We are determining for ourselves what the size of the buffer should be. We need to create our own copy rather than modify the input descriptor. + draudio_buffer_desc bufferDesc = *pBufferDesc; + bufferDesc.sizeInBytes = pBufferDesc->sampleRate * pBufferDesc->channels * (pBufferDesc->bitsPerSample / 8); + bufferDesc.pData = NULL; + + size_t chunkSize = bufferDesc.sizeInBytes / 2; + + draudio_buffer* pBuffer = draudio_create_buffer(pDevice, &bufferDesc, sizeof(ea_streaming_buffer_data) - sizeof(unsigned char) + chunkSize + extraDataSize); + if (pBuffer == NULL) { + return NULL; + } + + + ea_streaming_buffer_data* pStreamingData = (ea_streaming_buffer_data*)draudio_get_buffer_extra_data(pBuffer); + assert(pStreamingData != NULL); + + pStreamingData->callbacks = callbacks; + pStreamingData->atStart = true; + pStreamingData->stopAtEndOfCurrentChunk = false; + pStreamingData->isLoopingEnabled = false; + pStreamingData->chunkSize = chunkSize; + + // Register two markers - one for the first half and another for the second half. When a half is finished playing we need to + // replace it with new data. + draudio_register_marker_callback(pBuffer, 0, ea_steaming_buffer_marker_callback, DRAUDIO_STREAMING_MARKER_0, pStreamingData); + draudio_register_marker_callback(pBuffer, chunkSize, ea_steaming_buffer_marker_callback, DRAUDIO_STREAMING_MARKER_1, pStreamingData); + + + return pBuffer; +} + + +size_t draudio_get_streaming_buffer_extra_data_size(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return 0; + } + + ea_streaming_buffer_data* pStreamingData = (ea_streaming_buffer_data*)draudio_get_buffer_extra_data(pBuffer); + assert(pStreamingData != NULL); + + return pStreamingData->extraDataSize; +} + +void* draudio_get_streaming_buffer_extra_data(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return NULL; + } + + ea_streaming_buffer_data* pStreamingData = (ea_streaming_buffer_data*)draudio_get_buffer_extra_data(pBuffer); + assert(pStreamingData != NULL); + + return ((char*)pStreamingData->pTempChunkData) + pStreamingData->chunkSize; +} + + +bool draudio_play_streaming_buffer(draudio_buffer* pBuffer, bool loop) +{ + if (pBuffer == NULL) { + return false; + } + + + ea_streaming_buffer_data* pStreamingData = (ea_streaming_buffer_data*)draudio_get_buffer_extra_data(pBuffer); + assert(pStreamingData != NULL); + + // If the buffer was previously in a paused state, we just play like normal. If it was in a stopped state we need to start from the beginning. + if (draudio_get_playback_state(pBuffer) == draudio_stopped) + { + // We need to load some initial data into the first chunk. + pStreamingData->atStart = true; + pStreamingData->callbacks.seek(pStreamingData->callbacks.pUserData, 0); + + if (!ea_streaming_buffer_load_next_chunk(pBuffer, pStreamingData, 0, pStreamingData->chunkSize)) + { + // There was an error loading the initial data. + return false; + } + } + + + pStreamingData->isLoopingEnabled = loop; + draudio_play(pBuffer, true); // <-- Always loop on a streaming buffer. Actual looping is done a bit differently for streaming buffers. + + return true; +} + +bool draudio_is_streaming_buffer_looping(draudio_buffer* pBuffer) +{ + if (pBuffer == NULL) { + return false; + } + + ea_streaming_buffer_data* pStreamingData = (ea_streaming_buffer_data*)draudio_get_buffer_extra_data(pBuffer); + assert(pStreamingData != NULL); + + return pStreamingData->isLoopingEnabled; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Sound World +// +/////////////////////////////////////////////////////////////////////////////// + +DRAUDIO_PRIVATE draudio_bool draudio_on_sound_read_callback(void* pUserData, void* pDataOut, size_t bytesToRead, size_t* bytesReadOut) +{ + draudio_sound* pSound = (draudio_sound*)pUserData; + assert(pSound != NULL); + assert(pSound->onRead != NULL); + + bool result = false; + draudio_lock_mutex(pSound->pWorld->lock); + { + if (!pSound->markedForDeletion) { + result = pSound->onRead(pSound, pDataOut, bytesToRead, bytesReadOut); + } + } + draudio_unlock_mutex(pSound->pWorld->lock); + + return result; +} + +DRAUDIO_PRIVATE static draudio_bool draudio_on_sound_seek_callback(void* pUserData, size_t offsetInBytesFromStart) +{ + draudio_sound* pSound = (draudio_sound*)pUserData; + assert(pSound != NULL); + assert(pSound->onRead != NULL); + + bool result = false; + draudio_lock_mutex(pSound->pWorld->lock); + { + if (!pSound->markedForDeletion) { + result = pSound->onSeek(pSound, offsetInBytesFromStart); + } + } + draudio_unlock_mutex(pSound->pWorld->lock); + + return result; +} + + +DRAUDIO_PRIVATE static void draudio_inline_sound_stop_callback(draudio_buffer* pBuffer, unsigned int eventID, void *pUserData) +{ + (void)pBuffer; + (void)eventID; + + assert(pBuffer != NULL); + assert(eventID == DRAUDIO_EVENT_ID_STOP); + assert(pUserData != NULL); + + draudio_sound* pSound = (draudio_sound*)pUserData; + draudio_delete_sound(pSound); +} + + +DRAUDIO_PRIVATE void draudio_prepend_sound(draudio_sound* pSound) +{ + assert(pSound != NULL); + assert(pSound->pWorld != NULL); + assert(pSound->pPrevSound == NULL); + + draudio_lock_mutex(pSound->pWorld->lock); + { + pSound->pNextSound = pSound->pWorld->pFirstSound; + + if (pSound->pNextSound != NULL) { + pSound->pNextSound->pPrevSound = pSound; + } + + pSound->pWorld->pFirstSound = pSound; + } + draudio_unlock_mutex(pSound->pWorld->lock); +} + +DRAUDIO_PRIVATE void draudio_remove_sound_nolock(draudio_sound* pSound) +{ + assert(pSound != NULL); + assert(pSound->pWorld != NULL); + + if (pSound == pSound->pWorld->pFirstSound) { + pSound->pWorld->pFirstSound = pSound->pNextSound; + } + + if (pSound->pNextSound != NULL) { + pSound->pNextSound->pPrevSound = pSound->pPrevSound; + } + + if (pSound->pPrevSound != NULL) { + pSound->pPrevSound->pNextSound = pSound->pNextSound; + } +} + + +DRAUDIO_PRIVATE bool draudio_is_inline_sound(draudio_sound* pSound) +{ + assert(pSound != NULL); + return draudio_get_stop_callback(pSound->pBuffer).callback == draudio_inline_sound_stop_callback; +} + + +draudio_world* draudio_create_world(draudio_device* pDevice) +{ + draudio_world* pWorld = (draudio_world*)malloc(sizeof(*pWorld)); + if (pWorld != NULL) + { + pWorld->pDevice = pDevice; + pWorld->playbackState = draudio_playing; + pWorld->pFirstSound = NULL; + pWorld->lock = draudio_create_mutex(); + } + + return pWorld; +} + +void draudio_delete_world(draudio_world* pWorld) +{ + if (pWorld == NULL) { + return; + } + + // Delete every sound first. + draudio_delete_all_sounds(pWorld); + + // Delete the lock after deleting every sound because we still need thread-safety at this point. + draudio_delete_mutex(pWorld->lock); + + // Free the world last. + free(pWorld); +} + + +draudio_sound* draudio_create_sound(draudio_world* pWorld, draudio_sound_desc desc) +{ + if (pWorld == NULL) { + return NULL; + } + + if ((desc.pData == NULL || desc.sizeInBytes == 0) && (desc.onRead == NULL || desc.onSeek == NULL)) { + // When streaming is not being used, the initial data must be valid at creation time. + return NULL; + } + + draudio_sound* pSound = (draudio_sound*)malloc(sizeof(*pSound)); + if (pSound == NULL) { + return NULL; + } + + pSound->pWorld = pWorld; + pSound->prevPlaybackState = draudio_stopped; + pSound->pNextSound = NULL; + pSound->pPrevSound = NULL; + pSound->isUsingStreamingBuffer = desc.sizeInBytes == 0 || desc.pData == NULL; + pSound->markedForDeletion = false; + pSound->onDelete = desc.onDelete; + pSound->onRead = desc.onRead; + pSound->onSeek = desc.onSeek; + + draudio_buffer_desc bufferDesc; + bufferDesc.flags = desc.flags; + bufferDesc.format = desc.format; + bufferDesc.channels = desc.channels; + bufferDesc.sampleRate = desc.sampleRate; + bufferDesc.bitsPerSample = desc.bitsPerSample; + bufferDesc.sizeInBytes = desc.sizeInBytes; + bufferDesc.pData = desc.pData; + + if (pSound->isUsingStreamingBuffer) + { + draudio_streaming_callbacks streamingCallbacks; + streamingCallbacks.pUserData = pSound; + streamingCallbacks.read = draudio_on_sound_read_callback; + streamingCallbacks.seek = draudio_on_sound_seek_callback; + + pSound->pBuffer = draudio_create_streaming_buffer(pWorld->pDevice, &bufferDesc, streamingCallbacks, desc.extraDataSize); + if (pSound->pBuffer != NULL && desc.pExtraData != NULL) + { + memcpy(draudio_get_streaming_buffer_extra_data(pSound->pBuffer), desc.pExtraData, desc.extraDataSize); + } + } + else + { + pSound->pBuffer = draudio_create_buffer(pWorld->pDevice, &bufferDesc, desc.extraDataSize); + if (pSound->pBuffer != NULL && desc.pExtraData != NULL) + { + memcpy(draudio_get_buffer_extra_data(pSound->pBuffer), desc.pExtraData, desc.extraDataSize); + } + } + + + // Return NULL if we failed to create the internal audio buffer. + if (pSound->pBuffer == NULL) { + free(pSound); + return NULL; + } + + + // Only attach the sound to the internal list at the end when we know everything has worked. + draudio_prepend_sound(pSound); + + return pSound; +} + +void draudio_delete_sound(draudio_sound* pSound) +{ + if (pSound == NULL) { + return; + } + + + draudio_lock_mutex(pSound->pWorld->lock); + { + if (pSound->markedForDeletion) { + assert(false); + return; + } + + pSound->markedForDeletion = true; + + + // Remove the sound from the internal list first. + draudio_remove_sound_nolock(pSound); + + + // If we're deleting an inline sound, we want to remove the stop event callback. If we don't do this, we'll end up trying to delete + // the sound twice. + if (draudio_is_inline_sound(pSound)) { + draudio_register_stop_callback(pSound->pBuffer, NULL, NULL); + } + + + // Let the application know that the sound is being deleted. We want to do this after removing the stop event just to be sure the + // application doesn't try to explicitly stop the sound in this callback - that would be a problem for inlined sounds because they + // are configured to delete themselves upon stopping which we are already in the process of doing. + if (pSound->onDelete != NULL) { + pSound->onDelete(pSound); + } + + + // Delete the internal audio buffer before letting the host application know about the deletion. + draudio_delete_buffer(pSound->pBuffer); + } + draudio_unlock_mutex(pSound->pWorld->lock); + + + // Only free the sound after the application has been made aware the sound is being deleted. + free(pSound); +} + +void draudio_delete_all_sounds(draudio_world* pWorld) +{ + if (pWorld == NULL) { + return; + } + + while (pWorld->pFirstSound != NULL) { + draudio_delete_sound(pWorld->pFirstSound); + } +} + + +size_t draudio_get_sound_extra_data_size(draudio_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + if (pSound->isUsingStreamingBuffer) { + return draudio_get_streaming_buffer_extra_data_size(pSound->pBuffer); + } else { + return draudio_get_buffer_extra_data_size(pSound->pBuffer); + } +} + +void* draudio_get_sound_extra_data(draudio_sound* pSound) +{ + if (pSound == NULL) { + return NULL; + } + + if (pSound->isUsingStreamingBuffer) { + return draudio_get_streaming_buffer_extra_data(pSound->pBuffer); + } else { + return draudio_get_buffer_extra_data(pSound->pBuffer); + } +} + + +void draudio_play_sound(draudio_sound* pSound, bool loop) +{ + if (pSound != NULL) { + if (pSound->isUsingStreamingBuffer) { + draudio_play_streaming_buffer(pSound->pBuffer, loop); + } else { + draudio_play(pSound->pBuffer, loop); + } + } +} + +void draudio_pause_sound(draudio_sound* pSound) +{ + if (pSound != NULL) { + draudio_pause(pSound->pBuffer); + } +} + +void draudio_stop_sound(draudio_sound* pSound) +{ + if (pSound != NULL) { + draudio_stop(pSound->pBuffer); + } +} + +draudio_playback_state draudio_get_sound_playback_state(draudio_sound* pSound) +{ + if (pSound == NULL) { + return draudio_stopped; + } + + return draudio_get_playback_state(pSound->pBuffer); +} + +bool draudio_is_sound_looping(draudio_sound* pSound) +{ + if (pSound == NULL) { + return false; + } + + if (pSound->isUsingStreamingBuffer) { + return draudio_is_streaming_buffer_looping(pSound->pBuffer); + } else { + return draudio_is_looping(pSound->pBuffer); + } +} + + + +void draudio_play_inline_sound(draudio_world* pWorld, draudio_sound_desc desc) +{ + if (pWorld == NULL) { + return; + } + + // We need to explicitly ensure 3D positioning is disabled. + desc.flags &= ~DRAUDIO_ENABLE_3D; + + draudio_sound* pSound = draudio_create_sound(pWorld, desc); + if (pSound != NULL) + { + // For inline sounds we set a callback for when the sound is stopped. When this callback is fired, the sound is deleted. + draudio_set_sound_stop_callback(pSound, draudio_inline_sound_stop_callback, pSound); + + // Start playing the sound once everything else has been set up. + draudio_play_sound(pSound, false); + } +} + +void draudio_play_inline_sound_3f(draudio_world* pWorld, draudio_sound_desc desc, float posX, float posY, float posZ) +{ + if (pWorld == NULL) { + return; + } + + draudio_sound* pSound = draudio_create_sound(pWorld, desc); + if (pSound != NULL) + { + // For inline sounds we set a callback for when the sound is stopped. When this callback is fired, the sound is deleted. + draudio_set_sound_stop_callback(pSound, draudio_inline_sound_stop_callback, pSound); + + // Set the position before playing anything. + draudio_set_sound_position(pSound, posX, posY, posZ); + + // Start playing the sound once everything else has been set up. + draudio_play_sound(pSound, false); + } +} + + +void draudio_stop_all_sounds(draudio_world* pWorld) +{ + if (pWorld == NULL) { + return; + } + + bool wasPlaying = pWorld->playbackState == draudio_playing; + if (pWorld->playbackState != draudio_stopped) + { + // We need to loop over every sound and stop them. We also need to keep track of their previous playback state + // so that when resume_all_sounds() is called, it can be restored correctly. + for (draudio_sound* pSound = pWorld->pFirstSound; pSound != NULL; pSound = pSound->pNextSound) + { + if (wasPlaying) { + pSound->prevPlaybackState = draudio_get_sound_playback_state(pSound); + } + + draudio_stop_sound(pSound); + } + } +} + +void draudio_pause_all_sounds(draudio_world* pWorld) +{ + if (pWorld == NULL) { + return; + } + + if (pWorld->playbackState == draudio_playing) + { + // We need to loop over every sound and stop them. We also need to keep track of their previous playback state + // so that when resume_all_sounds() is called, it can be restored correctly. + for (draudio_sound* pSound = pWorld->pFirstSound; pSound != NULL; pSound = pSound->pNextSound) + { + pSound->prevPlaybackState = draudio_get_sound_playback_state(pSound); + draudio_pause_sound(pSound); + } + } +} + +void draudio_resume_all_sounds(draudio_world* pWorld) +{ + if (pWorld == NULL) { + return; + } + + if (pWorld->playbackState != draudio_playing) + { + // When resuming playback, we use the previous playback state to determine how to resume. + for (draudio_sound* pSound = pWorld->pFirstSound; pSound != NULL; pSound = pSound->pNextSound) + { + if (pSound->prevPlaybackState == draudio_playing) { + draudio_play_sound(pSound, draudio_is_sound_looping(pSound)); + } + } + } +} + + +void draudio_set_sound_stop_callback(draudio_sound* pSound, draudio_event_callback_proc callback, void* pUserData) +{ + if (pSound != NULL) { + draudio_register_stop_callback(pSound->pBuffer, callback, pUserData); + } +} + +void draudio_set_sound_pause_callback(draudio_sound* pSound, draudio_event_callback_proc callback, void* pUserData) +{ + if (pSound != NULL) { + draudio_register_pause_callback(pSound->pBuffer, callback, pUserData); + } +} + +void draudio_set_sound_play_callback(draudio_sound* pSound, draudio_event_callback_proc callback, void* pUserData) +{ + if (pSound != NULL) { + draudio_register_play_callback(pSound->pBuffer, callback, pUserData); + } +} + + +void draudio_set_sound_position(draudio_sound* pSound, float posX, float posY, float posZ) +{ + if (pSound != NULL) { + draudio_set_position(pSound->pBuffer, posX, posY, posZ); + } +} + + +void draudio_set_sound_3d_mode(draudio_sound* pSound, draudio_3d_mode mode) +{ + if (pSound != NULL) { + draudio_set_3d_mode(pSound->pBuffer, mode); + } +} + +draudio_3d_mode draudio_get_sound_3d_mode(draudio_sound* pSound) +{ + if (pSound == NULL) { + return draudio_3d_mode_disabled; + } + + return draudio_get_3d_mode(pSound->pBuffer); +} + + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// BACKENDS +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// +// DirectSound +// +// The DirectSound backend is mostly simple, except for event handling. Events +// are achieved through the use of Win32 event objects and waiting on them to +// be put into a signaled state by DirectSound. Due to this mechanic we need to +// create a worker thread that waits on each event. +// +// The worker thread waits on three general types of events. The first is an +// event that is signaled when the thread needs to be terminated. The second +// is an event that is signaled when a new set of events need to be waited on. +// The third is a set of events that correspond to an output buffer event (such +// as stop, pause, play and marker events.) +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef DRAUDIO_BUILD_DSOUND +#include +#include +#include +#include // For testing and debugging with printf(). Delete this later. + +// Define a NULL GUID for use later on. If we don't do this and use GUID_NULL we'll end +// up with a link error. +GUID DRAUDIO_GUID_NULL = {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + + +//// Message Queue //// + +#define DRAUDIO_MESSAGE_ID_UNKNOWN 0 +#define DRAUDIO_MESSAGE_ID_EVENT 1 +#define DRAUDIO_MESSAGE_ID_DELETE_BUFFER 2 +#define DRAUDIO_MESSAGE_ID_DELETE_DEVICE 3 +#define DRAUDIO_MESSAGE_ID_TERMINATE_THREAD 4 + +/// Structure representing an individual message +typedef struct +{ + /// The message ID. + int id; + + /// A pointer to the relevant buffer. + draudio_buffer* pBuffer; + + + // The message-specific data. + union + { + struct + { + /// A pointer to the callback function. + draudio_event_callback_proc callback; + + /// The event ID. + unsigned int eventID; + + /// The callback user data. + void* pUserData; + + } callback_event; + + + struct + { + /// A pointer to the DirectSound buffer object. + LPDIRECTSOUNDBUFFER8 pDSBuffer; + + /// A pointer to the 3D DirectSound buffer object. This will be NULL if 3D positioning is disabled for the buffer. + LPDIRECTSOUND3DBUFFER pDSBuffer3D; + + /// A pointer to the object for handling notification events. + LPDIRECTSOUNDNOTIFY pDSNotify; + + } delete_buffer; + + + struct + { + /// A pointer to the DIRECTSOUND object that was created with DirectSoundCreate8(). + LPDIRECTSOUND8 pDS; + + /// A pointer to the DIRECTSOUNDBUFFER object for the primary buffer. + LPDIRECTSOUNDBUFFER pDSPrimaryBuffer; + + /// A pointer to the DIRECTSOUND3DLISTENER8 object associated with the device. + LPDIRECTSOUND3DLISTENER pDSListener; + + /// A pointer to the device object being deleted. + draudio_device* pDevice; + + } delete_device; + + } data; + +} draudio_message_dsound; + + +/// Structure representing the main message queue. +/// +/// The message queue is implemented as a fixed-sized cyclic array which means there should be no significant data +/// movement and fast pushing and popping. +typedef struct +{ + /// The buffer containing the list of events. + draudio_message_dsound messages[DRAUDIO_MAX_MESSAGE_QUEUE_SIZE]; + + /// The number of active messages in the queue. + unsigned int messageCount; + + /// The index of the first message in the queue. + unsigned int iFirstMessage; + + /// The mutex for synchronizing access to message pushing and popping. + draudio_mutex queueLock; + + /// The semaphore that's used for blocking draudio_next_message_dsound(). The maximum value is set to DRAUDIO_MAX_MESSAGE_QUEUE_SIZE. + HANDLE hMessageSemaphore; + + /// The message handling thread. + HANDLE hMessageHandlingThread; + + /// Keeps track of whether or not the queue is deleted. We use this to ensure a thread does not try to post an event. + bool isDeleted; + +} draudio_message_queue_dsound; + + +/// The function to run on the message handling thread. This is implemented down the bottom. +DWORD WINAPI MessageHandlingThread_DSound(draudio_message_queue_dsound* pQueue); + +/// Posts a new message. This is thread safe. +void draudio_post_message_dsound(draudio_message_queue_dsound* pQueue, draudio_message_dsound msg); + + + +/// Initializes the given mesasge queue. +bool draudio_init_message_queue_dsound(draudio_message_queue_dsound* pQueue) +{ + if (pQueue == NULL) { + return false; + } + + pQueue->messageCount = 0; + pQueue->iFirstMessage = 0; + + pQueue->queueLock = draudio_create_mutex(); + if (pQueue->queueLock == NULL) { + return false; + } + + pQueue->hMessageSemaphore = CreateSemaphoreA(NULL, 0, DRAUDIO_MAX_MESSAGE_QUEUE_SIZE, NULL); + if (pQueue->hMessageSemaphore == NULL) + { + draudio_delete_mutex(pQueue->queueLock); + return false; + } + + pQueue->hMessageHandlingThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MessageHandlingThread_DSound, pQueue, 0, NULL); + if (pQueue->hMessageHandlingThread == NULL) + { + CloseHandle(pQueue->hMessageSemaphore); + draudio_delete_mutex(pQueue->queueLock); + return false; + } + + pQueue->isDeleted = false; + + return true; +} + +/// Uninitializes the given message queue. +void draudio_uninit_message_queue_dsound(draudio_message_queue_dsound* pQueue) +{ + // We need to make sure the thread is closed properly before returning from here. To do this we just post an DRAUDIO_MESSAGE_ID_TERMINATE_THREAD + // event to the message queue and wait for the thread to finish. + draudio_message_dsound msg; + msg.id = DRAUDIO_MESSAGE_ID_TERMINATE_THREAD; + draudio_post_message_dsound(pQueue, msg); + + + // Once we posted the event we just wait for the thread to process it and terminate. + WaitForSingleObject(pQueue->hMessageHandlingThread, INFINITE); + + // At this point the thread has been terminated and we can clear everything. + CloseHandle(pQueue->hMessageHandlingThread); + pQueue->hMessageHandlingThread = NULL; + + CloseHandle(pQueue->hMessageSemaphore); + pQueue->hMessageSemaphore = NULL; + + + pQueue->isDeleted = true; + draudio_lock_mutex(pQueue->queueLock); + { + pQueue->messageCount = 0; + pQueue->iFirstMessage = 0; + } + draudio_unlock_mutex(pQueue->queueLock); + + draudio_delete_mutex(pQueue->queueLock); + pQueue->queueLock = NULL; +} + + +void draudio_post_message_dsound(draudio_message_queue_dsound* pQueue, draudio_message_dsound msg) +{ + assert(pQueue != NULL); + + if (pQueue->isDeleted) { + return; + } + + draudio_lock_mutex(pQueue->queueLock); + { + if (pQueue->messageCount < DRAUDIO_MAX_MESSAGE_QUEUE_SIZE) + { + pQueue->messages[(pQueue->iFirstMessage + pQueue->messageCount) % DRAUDIO_MAX_MESSAGE_QUEUE_SIZE] = msg; + pQueue->messageCount += 1; + + ReleaseSemaphore(pQueue->hMessageSemaphore, 1, NULL); + } + } + draudio_unlock_mutex(pQueue->queueLock); +} + + +/// Retrieves the next message in the queue. +/// +/// @remarks +/// This blocks until a message is available. This will return false when it receives the DRAUDIO_MESSAGE_ID_TERMINATE_THREAD message. +bool draudio_next_message_dsound(draudio_message_queue_dsound* pQueue, draudio_message_dsound* pMsgOut) +{ + if (WaitForSingleObject(pQueue->hMessageSemaphore, INFINITE) == WAIT_OBJECT_0) // Wait for a message to become available. + { + draudio_message_dsound msg; + msg.id = DRAUDIO_MESSAGE_ID_UNKNOWN; + + draudio_lock_mutex(pQueue->queueLock); + { + assert(pQueue->messageCount > 0); + + msg = pQueue->messages[pQueue->iFirstMessage]; + + pQueue->iFirstMessage = (pQueue->iFirstMessage + 1) % DRAUDIO_MAX_MESSAGE_QUEUE_SIZE; + pQueue->messageCount -= 1; + } + draudio_unlock_mutex(pQueue->queueLock); + + + if (pMsgOut != NULL) { + pMsgOut[0] = msg; + } + + return msg.id != DRAUDIO_MESSAGE_ID_TERMINATE_THREAD; + } + + return false; +} + + +DWORD WINAPI MessageHandlingThread_DSound(draudio_message_queue_dsound* pQueue) +{ + assert(pQueue != NULL); + + draudio_message_dsound msg; + while (draudio_next_message_dsound(pQueue, &msg)) + { + assert(msg.id != DRAUDIO_MESSAGE_ID_TERMINATE_THREAD); // <-- draudio_next_message_dsound() will return false when it receives DRAUDIO_MESSAGE_ID_TERMINATE_THREAD. + + switch (msg.id) + { + case DRAUDIO_MESSAGE_ID_EVENT: + { + assert(msg.data.callback_event.callback != NULL); + + msg.data.callback_event.callback(msg.pBuffer, msg.data.callback_event.eventID, msg.data.callback_event.pUserData); + break; + } + + case DRAUDIO_MESSAGE_ID_DELETE_BUFFER: + { + if (msg.data.delete_buffer.pDSNotify != NULL) { + IDirectSoundNotify_Release(msg.data.delete_buffer.pDSNotify); + } + + if (msg.data.delete_buffer.pDSBuffer3D != NULL) { + IDirectSound3DBuffer_Release(msg.data.delete_buffer.pDSBuffer3D); + } + + if (msg.data.delete_buffer.pDSBuffer != NULL) { + IDirectSoundBuffer8_Release(msg.data.delete_buffer.pDSBuffer); + } + + free(msg.pBuffer); + break; + } + + case DRAUDIO_MESSAGE_ID_DELETE_DEVICE: + { + if (msg.data.delete_device.pDSListener != NULL) { + IDirectSound3DListener_Release(msg.data.delete_device.pDSListener); + } + + if (msg.data.delete_device.pDSPrimaryBuffer != NULL) { + IDirectSoundBuffer_Release(msg.data.delete_device.pDSPrimaryBuffer); + } + + if (msg.data.delete_device.pDS != NULL) { + IDirectSound_Release(msg.data.delete_device.pDS); + } + + free(msg.data.delete_device.pDevice); + break; + } + + default: + { + // Should never hit this. + assert(false); + break; + } + } + } + + return 0; +} + + + + +/// Deactivates (but does not delete) every event associated with the given buffer. +void draudio_deactivate_buffer_events_dsound(draudio_buffer* pBuffer); + + +//// Event Management //// + +typedef struct draudio_event_manager_dsound draudio_event_manager_dsound; +typedef struct draudio_event_dsound draudio_event_dsound; + +struct draudio_event_dsound +{ + /// A pointer to the event manager that owns this event. + draudio_event_manager_dsound* pEventManager; + + /// The event. + HANDLE hEvent; + + /// The callback. + draudio_event_callback_proc callback; + + /// A pointer to the applicable buffer. + draudio_buffer* pBuffer; + + /// The event ID. For on_stop events, this will be set to DRAUDIO_EVENT_STOP + unsigned int eventID; + + /// A pointer to the user data. + void* pUserData; + + /// The marker offset. Only used for marker events. Should be set to 0 for non-markers. + DWORD markerOffset; + + /// Events are stored in a linked list. This is a pointer to the next event in the list. + draudio_event_dsound* pNextEvent; + + /// A pointer to the previous event. + draudio_event_dsound* pPrevEvent; +}; + +struct draudio_event_manager_dsound +{ + /// A pointer to the message queue where messages will be posted for event processing. + draudio_message_queue_dsound* pMessageQueue; + + + /// A handle to the event worker thread. + HANDLE hThread; + + /// A handle to the terminator event object. + HANDLE hTerminateEvent; + + /// A handle to the refresher event object. + HANDLE hRefreshEvent; + + /// The mutex to use when refreshing the worker thread. This is used to ensure only one refresh is done at a time. + draudio_mutex refreshMutex; + + /// The synchronization lock. + draudio_mutex mainLock; + + /// The event object for notifying dr_audio when an event has finished being handled by the event handling thread. + HANDLE hEventCompletionLock; + + + /// The first event in a list. + draudio_event_dsound* pFirstEvent; + + /// The last event in the list of events. + draudio_event_dsound* pLastEvent; +}; + + +/// Locks the event manager. +void draudio_lock_events_dsound(draudio_event_manager_dsound* pEventManager) +{ + draudio_lock_mutex(pEventManager->mainLock); +} + +/// Unlocks the event manager. +void draudio_unlock_events_dsound(draudio_event_manager_dsound* pEventManager) +{ + draudio_unlock_mutex(pEventManager->mainLock); +} + + +/// Removes the given event from the event lists. +/// +/// @remarks +/// This will be used when moving the event to a new location in the list or when it is being deleted. +void draudio_remove_event_dsound_nolock(draudio_event_dsound* pEvent) +{ + assert(pEvent != NULL); + + draudio_event_manager_dsound* pEventManager = pEvent->pEventManager; + assert(pEventManager != NULL); + + if (pEventManager->pFirstEvent == pEvent) { + pEventManager->pFirstEvent = pEvent->pNextEvent; + } + + if (pEventManager->pLastEvent == pEvent) { + pEventManager->pLastEvent = pEvent->pPrevEvent; + } + + + if (pEvent->pPrevEvent != NULL) { + pEvent->pPrevEvent->pNextEvent = pEvent->pNextEvent; + } + + if (pEvent->pNextEvent != NULL) { + pEvent->pNextEvent->pPrevEvent = pEvent->pPrevEvent; + } + + pEvent->pNextEvent = NULL; + pEvent->pPrevEvent = NULL; +} + +/// @copydoc draudio_remove_event_dsound_nolock() +void draudio_remove_event_dsound(draudio_event_dsound* pEvent) +{ + assert(pEvent != NULL); + + draudio_event_manager_dsound* pEventManager = pEvent->pEventManager; + draudio_lock_events_dsound(pEventManager); + { + draudio_remove_event_dsound_nolock(pEvent); + } + draudio_unlock_events_dsound(pEventManager); +} + +/// Adds the given event to the end of the internal list. +void draudio_append_event_dsound(draudio_event_dsound* pEvent) +{ + assert(pEvent != NULL); + + draudio_event_manager_dsound* pEventManager = pEvent->pEventManager; + draudio_lock_events_dsound(pEventManager); + { + draudio_remove_event_dsound_nolock(pEvent); + + assert(pEvent->pNextEvent == NULL); + + if (pEventManager->pLastEvent != NULL) { + pEvent->pPrevEvent = pEventManager->pLastEvent; + pEvent->pPrevEvent->pNextEvent = pEvent; + } + + if (pEventManager->pFirstEvent == NULL) { + pEventManager->pFirstEvent = pEvent; + } + + pEventManager->pLastEvent = pEvent; + } + draudio_unlock_events_dsound(pEventManager); +} + +void draudio_refresh_worker_thread_event_queue(draudio_event_manager_dsound* pEventManager) +{ + assert(pEventManager != NULL); + + // To refresh the worker thread we just need to signal the refresh event. We then just need to wait for + // processing to finish which we can do by waiting on another event to become signaled. + + draudio_lock_mutex(pEventManager->refreshMutex); + { + // Signal a refresh. + SetEvent(pEventManager->hRefreshEvent); + + // Wait for refreshing to complete. + WaitForSingleObject(pEventManager->hEventCompletionLock, INFINITE); + } + draudio_unlock_mutex(pEventManager->refreshMutex); +} + + +/// Closes the Win32 event handle of the given event. +void draudio_close_win32_event_handle_dsound(draudio_event_dsound* pEvent) +{ + assert(pEvent != NULL); + assert(pEvent->pEventManager != NULL); + + + // At the time of calling this function, pEvent should have been removed from the internal list. The issue is that + // the event notification thread is waiting on it. Thus, we need to refresh the worker thread to ensure the event + // have been flushed from that queue. To do this we just signal a special event that's used to trigger a refresh. + draudio_refresh_worker_thread_event_queue(pEvent->pEventManager); + + // The worker thread should not be waiting on the event so we can go ahead and close the handle now. + CloseHandle(pEvent->hEvent); + pEvent->hEvent = NULL; +} + + +/// Updates the given event to use the given callback and user data. +void draudio_update_event_dsound(draudio_event_dsound* pEvent, draudio_event_callback_proc callback, void* pUserData) +{ + assert(pEvent != NULL); + + pEvent->callback = callback; + pEvent->pUserData = pUserData; + + draudio_refresh_worker_thread_event_queue(pEvent->pEventManager); +} + +/// Creates a new event, but does not activate it. +/// +/// @remarks +/// When an event is created, it just sits dormant and will never be triggered until it has been +/// activated with draudio_activate_event_dsound(). +draudio_event_dsound* draudio_create_event_dsound(draudio_event_manager_dsound* pEventManager, draudio_event_callback_proc callback, draudio_buffer* pBuffer, unsigned int eventID, void* pUserData) +{ + draudio_event_dsound* pEvent = (draudio_event_dsound*)malloc(sizeof(draudio_event_dsound)); + if (pEvent != NULL) + { + pEvent->pEventManager = pEventManager; + pEvent->hEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + pEvent->callback = NULL; + pEvent->pBuffer = pBuffer; + pEvent->eventID = eventID; + pEvent->pUserData = NULL; + pEvent->markerOffset = 0; + pEvent->pNextEvent = NULL; + pEvent->pPrevEvent = NULL; + + // Append the event to the internal list. + draudio_append_event_dsound(pEvent); + + // This roundabout way of setting the callback and user data is to ensure the worker thread is made aware that it needs + // to refresh it's local event data. + draudio_update_event_dsound(pEvent, callback, pUserData); + } + + return pEvent; +} + +/// Deletes an event, and deactivates it. +/// +/// @remarks +/// This will not return until the event has been deleted completely. +void draudio_delete_event_dsound(draudio_event_dsound* pEvent) +{ + assert(pEvent != NULL); + + // Set everything to NULL so the worker thread is aware that the event is about to get deleted. + pEvent->pBuffer = NULL; + pEvent->callback = NULL; + pEvent->eventID = 0; + pEvent->pUserData = NULL; + pEvent->markerOffset = 0; + + // Remove the event from the list. + draudio_remove_event_dsound(pEvent); + + // Close the Win32 event handle. + if (pEvent->hEvent != NULL) { + draudio_close_win32_event_handle_dsound(pEvent); + } + + + // At this point everything has been closed so we can safely free the memory and return. + free(pEvent); +} + + +/// Gathers the event handles and callback data for all of the relevant buffer events. +unsigned int draudio_gather_events_dsound(draudio_event_manager_dsound *pEventManager, HANDLE* pHandlesOut, draudio_event_dsound** ppEventsOut, unsigned int outputBufferSize) +{ + assert(pEventManager != NULL); + assert(pHandlesOut != NULL); + assert(ppEventsOut != NULL); + assert(outputBufferSize >= 2); + + unsigned int i = 2; + draudio_lock_events_dsound(pEventManager); + { + pHandlesOut[0] = pEventManager->hTerminateEvent; + ppEventsOut[0] = NULL; + + pHandlesOut[1] = pEventManager->hRefreshEvent; + ppEventsOut[1] = NULL; + + + draudio_event_dsound* pEvent = pEventManager->pFirstEvent; + while (i < outputBufferSize && pEvent != NULL) + { + if (pEvent->hEvent != NULL) + { + pHandlesOut[i] = pEvent->hEvent; + ppEventsOut[i] = pEvent; + + i += 1; + } + + pEvent = pEvent->pNextEvent; + } + } + draudio_unlock_events_dsound(pEventManager); + + return i; +} + +/// The entry point to the event worker thread. +DWORD WINAPI DSound_EventWorkerThreadProc(draudio_event_manager_dsound *pEventManager) +{ + if (pEventManager != NULL) + { + HANDLE hTerminateEvent = pEventManager->hTerminateEvent; + HANDLE hRefreshEvent = pEventManager->hRefreshEvent; + + HANDLE eventHandles[1024]; + draudio_event_dsound* events[1024]; + unsigned int eventCount = draudio_gather_events_dsound(pEventManager, eventHandles, events, 1024); // <-- Initial gather. + + bool requestedRefresh = false; + for (;;) + { + if (requestedRefresh) + { + eventCount = draudio_gather_events_dsound(pEventManager, eventHandles, events, 1024); + + // Refreshing is done, so now we need to let other threads know about it. + SetEvent(pEventManager->hEventCompletionLock); + requestedRefresh = false; + } + + + + DWORD rc = WaitForMultipleObjects(eventCount, eventHandles, FALSE, INFINITE); + if (rc >= WAIT_OBJECT_0 && rc < eventCount) + { + const unsigned int eventIndex = rc - WAIT_OBJECT_0; + HANDLE hEvent = eventHandles[eventIndex]; + + if (hEvent == hTerminateEvent) + { + // The terminator event was signaled. We just return from the thread immediately. + return 0; + } + + if (hEvent == hRefreshEvent) + { + assert(hRefreshEvent == pEventManager->hRefreshEvent); + + // This event will get signaled when a new set of events need to be waited on, such as when a new event has been registered on a buffer. + requestedRefresh = true; + continue; + } + + + // If we get here if means we have hit a callback event. + draudio_event_dsound* pEvent = events[eventIndex]; + if (pEvent->callback != NULL) + { + assert(pEvent->hEvent == hEvent); + + // The stop event will be signaled by DirectSound when IDirectSoundBuffer::Stop() is called. The problem is that we need to call that when the + // sound is paused as well. Thus, we need to check if we got the stop event, and if so DON'T call the callback function if it is in a non-stopped + // state. + bool isStopEventButNotStopped = pEvent->eventID == DRAUDIO_EVENT_ID_STOP && draudio_get_playback_state(pEvent->pBuffer) != draudio_stopped; + if (!isStopEventButNotStopped) + { + // We don't call the callback directly. Instead we post a message to the message handling thread for processing later. + draudio_message_dsound msg; + msg.id = DRAUDIO_MESSAGE_ID_EVENT; + msg.pBuffer = pEvent->pBuffer; + msg.data.callback_event.callback = pEvent->callback; + msg.data.callback_event.eventID = pEvent->eventID; + msg.data.callback_event.pUserData = pEvent->pUserData; + draudio_post_message_dsound(pEventManager->pMessageQueue, msg); + } + } + } + } + } + + return 0; +} + + +/// Initializes the event manager by creating the thread and event objects. +bool draudio_init_event_manager_dsound(draudio_event_manager_dsound* pEventManager, draudio_message_queue_dsound* pMessageQueue) +{ + assert(pEventManager != NULL); + assert(pMessageQueue != NULL); + + pEventManager->pMessageQueue = pMessageQueue; + + HANDLE hTerminateEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + if (hTerminateEvent == NULL) { + return false; + } + + HANDLE hRefreshEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + if (hRefreshEvent == NULL) + { + CloseHandle(hTerminateEvent); + return false; + } + + draudio_mutex refreshMutex = draudio_create_mutex(); + if (refreshMutex == NULL) + { + CloseHandle(hTerminateEvent); + CloseHandle(hRefreshEvent); + return false; + } + + draudio_mutex mainLock = draudio_create_mutex(); + if (mainLock == NULL) + { + CloseHandle(hTerminateEvent); + CloseHandle(hRefreshEvent); + draudio_delete_mutex(refreshMutex); + return false; + } + + HANDLE hEventCompletionLock = CreateEventA(NULL, FALSE, FALSE, NULL); + if (hEventCompletionLock == NULL) + { + CloseHandle(hTerminateEvent); + CloseHandle(hRefreshEvent); + draudio_delete_mutex(refreshMutex); + draudio_delete_mutex(mainLock); + return false; + } + + + HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)DSound_EventWorkerThreadProc, pEventManager, 0, NULL); + if (hThread == NULL) + { + CloseHandle(hTerminateEvent); + CloseHandle(hRefreshEvent); + draudio_delete_mutex(refreshMutex); + draudio_delete_mutex(mainLock); + CloseHandle(hEventCompletionLock); + return false; + } + + + pEventManager->hTerminateEvent = hTerminateEvent; + pEventManager->hRefreshEvent = hRefreshEvent; + pEventManager->refreshMutex = refreshMutex; + pEventManager->mainLock = mainLock; + pEventManager->hEventCompletionLock = hEventCompletionLock; + pEventManager->hThread = hThread; + + pEventManager->pFirstEvent = NULL; + pEventManager->pLastEvent = NULL; + + return true; +} + +/// Shuts down the event manager by closing the thread and event objects. +/// +/// @remarks +/// This does not return until the worker thread has been terminated completely. +/// @par +/// This will delete every event, so any pointers to events will be made invalid upon calling this function. +void draudio_uninit_event_manager_dsound(draudio_event_manager_dsound* pEventManager) +{ + assert(pEventManager != NULL); + + + // Cleanly delete every event first. + while (pEventManager->pFirstEvent != NULL) { + draudio_delete_event_dsound(pEventManager->pFirstEvent); + } + + + + // Terminate the thread and wait for the thread to finish executing before freeing the context for real. + SignalObjectAndWait(pEventManager->hTerminateEvent, pEventManager->hThread, INFINITE, FALSE); + + // Only delete the thread after it has returned naturally. + CloseHandle(pEventManager->hThread); + pEventManager->hThread = NULL; + + + // Once the thread has been terminated we can delete the terminator and refresher events. + CloseHandle(pEventManager->hTerminateEvent); + pEventManager->hTerminateEvent = NULL; + + CloseHandle(pEventManager->hRefreshEvent); + pEventManager->hRefreshEvent = NULL; + + draudio_delete_mutex(pEventManager->refreshMutex); + pEventManager->refreshMutex = NULL; + + draudio_delete_mutex(pEventManager->mainLock); + pEventManager->mainLock = NULL; + + + CloseHandle(pEventManager->hEventCompletionLock); + pEventManager->hEventCompletionLock = NULL; +} + + +//// End Event Management //// + +static GUID _g_DSListenerGUID = {0x279AFA84, 0x4981, 0x11CE, {0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60}}; +static GUID _g_DirectSoundBuffer8GUID = {0x6825a449, 0x7524, 0x4d82, {0x92, 0x0f, 0x50, 0xe3, 0x6a, 0xb3, 0xab, 0x1e}}; +static GUID _g_DirectSound3DBuffer8GUID = {0x279AFA86, 0x4981, 0x11CE, {0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60}}; +static GUID _g_DirectSoundNotifyGUID = {0xb0210783, 0x89cd, 0x11d0, {0xaf, 0x08, 0x00, 0xa0, 0xc9, 0x25, 0xcd, 0x16}}; +static GUID _g_KSDATAFORMAT_SUBTYPE_PCM_GUID = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; +static GUID _g_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_GUID = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; + +#ifdef __cplusplus +static GUID g_DSListenerGUID = _g_DSListenerGUID; +static GUID g_DirectSoundBuffer8GUID = _g_DirectSoundBuffer8GUID; +static GUID g_DirectSound3DBuffer8GUID = _g_DirectSound3DBuffer8GUID; +static GUID g_DirectSoundNotifyGUID = _g_DirectSoundNotifyGUID; +static GUID g_KSDATAFORMAT_SUBTYPE_PCM_GUID = _g_KSDATAFORMAT_SUBTYPE_PCM_GUID; +static GUID g_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_GUID = _g_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_GUID; +#else +static GUID* g_DSListenerGUID = &_g_DSListenerGUID; +static GUID* g_DirectSoundBuffer8GUID = &_g_DirectSoundBuffer8GUID; +static GUID* g_DirectSound3DBuffer8GUID = &_g_DirectSound3DBuffer8GUID; +static GUID* g_DirectSoundNotifyGUID = &_g_DirectSoundNotifyGUID; +static GUID* g_KSDATAFORMAT_SUBTYPE_PCM_GUID = &_g_KSDATAFORMAT_SUBTYPE_PCM_GUID; +static GUID* g_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_GUID = &_g_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_GUID; +#endif + + +typedef HRESULT (WINAPI * pDirectSoundCreate8Proc)(LPCGUID pcGuidDevice, LPDIRECTSOUND8 *ppDS8, LPUNKNOWN pUnkOuter); +typedef HRESULT (WINAPI * pDirectSoundEnumerateAProc)(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext); +typedef HRESULT (WINAPI * pDirectSoundCaptureCreate8Proc)(LPCGUID pcGuidDevice, LPDIRECTSOUNDCAPTURE8 *ppDSC8, LPUNKNOWN pUnkOuter); +typedef HRESULT (WINAPI * pDirectSoundCaptureEnumerateAProc)(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext); + +typedef struct +{ + /// A pointer to the GUID of the device. This will be set to all zeros for the default device. + GUID guid; + + /// The description of the device. + char description[256]; + + /// The module name of the DirectSound driver corresponding to this device. + char moduleName[256]; + +} draudio_device_info_dsound; + +typedef struct +{ + /// The base context data. This must always be the first item in the struct. + draudio_context base; + + /// A handle to the dsound.dll file that was loaded by LoadLibrary(). + HMODULE hDSoundDLL; + + // DirectSound APIs. + pDirectSoundCreate8Proc pDirectSoundCreate8; + pDirectSoundEnumerateAProc pDirectSoundEnumerateA; + pDirectSoundCaptureCreate8Proc pDirectSoundCaptureCreate8; + pDirectSoundCaptureEnumerateAProc pDirectSoundCaptureEnumerateA; + + + /// The number of output devices that were iterated when the context was created. This is static, so if the user was to unplug + /// a device one would need to re-create the context. + unsigned int outputDeviceCount; + + /// The buffer containing the list of enumerated output devices. + draudio_device_info_dsound outputDeviceInfo[DRAUDIO_MAX_DEVICE_COUNT]; + + + /// The number of capture devices that were iterated when the context was created. This is static, so if the user was to unplug + /// a device one would need to re-create the context. + unsigned int inputDeviceCount; + + /// The buffer containing the list of enumerated input devices. + draudio_device_info_dsound inputDeviceInfo[DRAUDIO_MAX_DEVICE_COUNT]; + + + /// The event manager. + draudio_event_manager_dsound eventManager; + + + /// The message queue. + draudio_message_queue_dsound messageQueue; + +} draudio_context_dsound; + +typedef struct +{ + /// The base device data. This must always be the first item in the struct. + draudio_device base; + + /// A pointer to the DIRECTSOUND object that was created with DirectSoundCreate8(). + LPDIRECTSOUND8 pDS; + + /// A pointer to the DIRECTSOUNDBUFFER object for the primary buffer. + LPDIRECTSOUNDBUFFER pDSPrimaryBuffer; + + /// A pointer to the DIRECTSOUND3DLISTENER8 object associated with the device. + LPDIRECTSOUND3DLISTENER pDSListener; + +} draudio_device_dsound; + +typedef struct +{ + /// The base buffer data. This must always be the first item in the struct. + draudio_buffer base; + + /// A pointer to the DirectSound buffer object. + LPDIRECTSOUNDBUFFER8 pDSBuffer; + + /// A pointer to the 3D DirectSound buffer object. This will be NULL if 3D positioning is disabled for the buffer. + LPDIRECTSOUND3DBUFFER pDSBuffer3D; + + /// A pointer to the object for handling notification events. + LPDIRECTSOUNDNOTIFY pDSNotify; + + /// The current playback state. + draudio_playback_state playbackState; + + + /// The number of marker events that have been registered. This will never be more than DRAUDIO_MAX_MARKER_COUNT. + unsigned int markerEventCount; + + /// The marker events. + draudio_event_dsound* pMarkerEvents[DRAUDIO_MAX_MARKER_COUNT]; + + /// The event to trigger when the sound is stopped. + draudio_event_dsound* pStopEvent; + + /// The event to trigger when the sound is paused. + draudio_event_dsound* pPauseEvent; + + /// The event to trigger when the sound is played or resumed. + draudio_event_dsound* pPlayEvent; + + + /// The size in bytes of the buffer's extra data. + unsigned int extraDataSize; + + /// The buffer's extra data. + unsigned char pExtraData[1]; + +} draudio_buffer_dsound; + + +void draudio_activate_buffer_events_dsound(draudio_buffer* pBuffer) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + unsigned int dwPositionNotifies = 0; + DSBPOSITIONNOTIFY n[DRAUDIO_MAX_MARKER_COUNT + 1]; // +1 because we use this array for the markers + stop event. + + // Stop + if (pBufferDS->pStopEvent != NULL) + { + LPDSBPOSITIONNOTIFY pN = n + dwPositionNotifies; + pN->dwOffset = DSBPN_OFFSETSTOP; + pN->hEventNotify = pBufferDS->pStopEvent->hEvent; + + dwPositionNotifies += 1; + } + + // Markers + for (unsigned int iMarker = 0; iMarker < pBufferDS->markerEventCount; ++iMarker) + { + LPDSBPOSITIONNOTIFY pN = n + dwPositionNotifies; + pN->dwOffset = pBufferDS->pMarkerEvents[iMarker]->markerOffset; + pN->hEventNotify = pBufferDS->pMarkerEvents[iMarker]->hEvent; + + dwPositionNotifies += 1; + } + + + HRESULT hr = IDirectSoundNotify_SetNotificationPositions(pBufferDS->pDSNotify, dwPositionNotifies, n); +#if 0 + if (FAILED(hr)) { + printf("WARNING: FAILED TO CREATE DIRECTSOUND NOTIFIERS\n"); + } +#else + (void)hr; +#endif +} + +void draudio_deactivate_buffer_events_dsound(draudio_buffer* pBuffer) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + + HRESULT hr = IDirectSoundNotify_SetNotificationPositions(pBufferDS->pDSNotify, 0, NULL); +#if 0 + if (FAILED(hr)) { + printf("WARNING: FAILED TO CLEAR DIRECTSOUND NOTIFIERS\n"); + } +#else + (void)hr; +#endif +} + + +void draudio_delete_context_dsound(draudio_context* pContext) +{ + draudio_context_dsound* pContextDS = (draudio_context_dsound*)pContext; + assert(pContextDS != NULL); + + draudio_uninit_event_manager_dsound(&pContextDS->eventManager); + + // The message queue needs to uninitialized after the DirectSound marker notification thread. + draudio_uninit_message_queue_dsound(&pContextDS->messageQueue); + + FreeLibrary(pContextDS->hDSoundDLL); + free(pContextDS); +} + + +unsigned int draudio_get_output_device_count_dsound(draudio_context* pContext) +{ + draudio_context_dsound* pContextDS = (draudio_context_dsound*)pContext; + assert(pContextDS != NULL); + + return pContextDS->outputDeviceCount; +} + +bool draudio_get_output_device_info_dsound(draudio_context* pContext, unsigned int deviceIndex, draudio_device_info* pInfoOut) +{ + draudio_context_dsound* pContextDS = (draudio_context_dsound*)pContext; + assert(pContextDS != NULL); + assert(pInfoOut != NULL); + + if (deviceIndex >= pContextDS->outputDeviceCount) { + return false; + } + + + draudio_strcpy(pInfoOut->description, sizeof(pInfoOut->description), pContextDS->outputDeviceInfo[deviceIndex].description); + + return true; +} + + +draudio_device* draudio_create_output_device_dsound(draudio_context* pContext, unsigned int deviceIndex) +{ + draudio_context_dsound* pContextDS = (draudio_context_dsound*)pContext; + assert(pContextDS != NULL); + + if (deviceIndex >= pContextDS->outputDeviceCount) { + return NULL; + } + + + LPDIRECTSOUND8 pDS; + + // Create the device. + HRESULT hr; + if (deviceIndex == 0) { + hr = pContextDS->pDirectSoundCreate8(NULL, &pDS, NULL); + } else { + hr = pContextDS->pDirectSoundCreate8(&pContextDS->outputDeviceInfo[deviceIndex].guid, &pDS, NULL); + } + + if (FAILED(hr)) { + return NULL; + } + + + // Set the cooperative level. Must be done before anything else. + hr = IDirectSound_SetCooperativeLevel(pDS, GetForegroundWindow(), DSSCL_EXCLUSIVE); + if (FAILED(hr)) { + IDirectSound_Release(pDS); + return NULL; + } + + + // Primary buffer. + DSBUFFERDESC descDSPrimary; + memset(&descDSPrimary, 0, sizeof(DSBUFFERDESC)); + descDSPrimary.dwSize = sizeof(DSBUFFERDESC); + descDSPrimary.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRL3D; + descDSPrimary.guid3DAlgorithm = DRAUDIO_GUID_NULL; + + LPDIRECTSOUNDBUFFER pDSPrimaryBuffer; + hr = IDirectSound_CreateSoundBuffer(pDS, &descDSPrimary, &pDSPrimaryBuffer, NULL); + if (FAILED(hr)) { + IDirectSound_Release(pDS); + return NULL; + } + + + WAVEFORMATIEEEFLOATEX wf = {0}; + wf.Format.cbSize = sizeof(wf); + wf.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wf.Format.nChannels = 2; + wf.Format.nSamplesPerSec = 48000; + wf.Format.wBitsPerSample = 32; + wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8; + wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec; + wf.Samples.wValidBitsPerSample = wf.Format.wBitsPerSample; + wf.dwChannelMask = 0; + wf.SubFormat = _g_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_GUID; + hr = IDirectSoundBuffer_SetFormat(pDSPrimaryBuffer, (WAVEFORMATEX*)&wf); + if (FAILED(hr)) { + IDirectSoundBuffer_Release(pDSPrimaryBuffer); + IDirectSound_Release(pDS); + return NULL; + } + + + // Listener. + LPDIRECTSOUND3DLISTENER pDSListener = NULL; + hr = IDirectSound3DListener_QueryInterface(pDSPrimaryBuffer, g_DSListenerGUID, (LPVOID*)&pDSListener); + if (FAILED(hr)) { + IDirectSoundBuffer_Release(pDSPrimaryBuffer); + IDirectSound_Release(pDS); + return NULL; + } + + + draudio_device_dsound* pDeviceDS = (draudio_device_dsound*)malloc(sizeof(draudio_device_dsound)); + if (pDeviceDS != NULL) + { + pDeviceDS->base.pContext = pContext; + pDeviceDS->pDS = pDS; + pDeviceDS->pDSPrimaryBuffer = pDSPrimaryBuffer; + pDeviceDS->pDSListener = pDSListener; + + return (draudio_device*)pDeviceDS; + } + else + { + IDirectSound3DListener_Release(pDSListener); + IDirectSoundBuffer_Release(pDeviceDS->pDSPrimaryBuffer); + IDirectSound_Release(pDS); + return NULL; + } +} + +void draudio_delete_output_device_dsound(draudio_device* pDevice) +{ + draudio_device_dsound* pDeviceDS = (draudio_device_dsound*)pDevice; + assert(pDeviceDS != NULL); + + draudio_context_dsound* pContextDS = (draudio_context_dsound*)pDevice->pContext; + assert(pContextDS != NULL); + + // The device id not deleted straight away. Instead we post a message to the message for delayed processing. The reason for this is that buffer + // deletion is also delayed which means we want to ensure any delayed processing of buffers is handled before deleting the device. + draudio_message_dsound msg; + msg.id = DRAUDIO_MESSAGE_ID_DELETE_DEVICE; + msg.pBuffer = NULL; + msg.data.delete_device.pDSListener = pDeviceDS->pDSListener; + msg.data.delete_device.pDSPrimaryBuffer = pDeviceDS->pDSPrimaryBuffer; + msg.data.delete_device.pDS = pDeviceDS->pDS; + msg.data.delete_device.pDevice = pDevice; + draudio_post_message_dsound(&pContextDS->messageQueue, msg); + +#if 0 + IDirectSound3DListener_Release(pDeviceDS->pDSListener); + IDirectSoundBuffer_Release(pDeviceDS->pDSPrimaryBuffer); + IDirectSound_Release(pDeviceDS->pDS); + free(pDeviceDS); +#endif +} + + +draudio_buffer* draudio_create_buffer_dsound(draudio_device* pDevice, draudio_buffer_desc* pBufferDesc, size_t extraDataSize) +{ + draudio_device_dsound* pDeviceDS = (draudio_device_dsound*)pDevice; + assert(pDeviceDS != NULL); + assert(pBufferDesc != NULL); + + // 3D is only valid for mono sounds. + if (pBufferDesc->channels > 1 && (pBufferDesc->flags & DRAUDIO_ENABLE_3D) != 0) { + return NULL; + } + + WAVEFORMATIEEEFLOATEX wf = {0}; + wf.Format.cbSize = sizeof(wf); + wf.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wf.Format.nChannels = (WORD)pBufferDesc->channels; + wf.Format.nSamplesPerSec = pBufferDesc->sampleRate; + wf.Format.wBitsPerSample = (WORD)pBufferDesc->bitsPerSample; + wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8; + wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec; + wf.Samples.wValidBitsPerSample = wf.Format.wBitsPerSample; + wf.dwChannelMask = 0; + + if (pBufferDesc->format == draudio_format_pcm) { + wf.SubFormat = _g_KSDATAFORMAT_SUBTYPE_PCM_GUID; + } else if (pBufferDesc->format == draudio_format_float) { + wf.SubFormat = _g_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_GUID; + } else { + return NULL; + } + + + + // We want to try and create a 3D enabled buffer, however this will fail whenever the number of channels is > 1. In this case + // we do not want to attempt to create a 3D enabled buffer because it will just fail anyway. Instead we'll just create a normal + // buffer with panning enabled. + DSBUFFERDESC descDS; + memset(&descDS, 0, sizeof(DSBUFFERDESC)); + descDS.dwSize = sizeof(DSBUFFERDESC); + descDS.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS; + descDS.dwBufferBytes = (DWORD)pBufferDesc->sizeInBytes; + descDS.lpwfxFormat = (WAVEFORMATEX*)&wf; + + LPDIRECTSOUNDBUFFER8 pDSBuffer = NULL; + LPDIRECTSOUND3DBUFFER pDSBuffer3D = NULL; + if ((pBufferDesc->flags & DRAUDIO_ENABLE_3D) == 0) + { + // 3D Disabled. + descDS.dwFlags |= DSBCAPS_CTRLPAN; + + LPDIRECTSOUNDBUFFER pDSBufferTemp; + HRESULT hr = IDirectSound_CreateSoundBuffer(pDeviceDS->pDS, &descDS, &pDSBufferTemp, NULL); + if (FAILED(hr)) { + return NULL; + } + + hr = IDirectSoundBuffer_QueryInterface(pDSBufferTemp, g_DirectSoundBuffer8GUID, (void**)&pDSBuffer); + if (FAILED(hr)) { + IDirectSoundBuffer_Release(pDSBufferTemp); + return NULL; + } + IDirectSoundBuffer_Release(pDSBufferTemp); + } + else + { + // 3D Enabled. + descDS.dwFlags |= DSBCAPS_CTRL3D; + descDS.guid3DAlgorithm = DS3DALG_DEFAULT; + + LPDIRECTSOUNDBUFFER pDSBufferTemp; + HRESULT hr = IDirectSound_CreateSoundBuffer(pDeviceDS->pDS, &descDS, &pDSBufferTemp, NULL); + if (FAILED(hr)) { + return NULL; + } + + hr = IDirectSoundBuffer_QueryInterface(pDSBufferTemp, g_DirectSoundBuffer8GUID, (void**)&pDSBuffer); + if (FAILED(hr)) { + IDirectSoundBuffer_Release(pDSBufferTemp); + return NULL; + } + IDirectSoundBuffer_Release(pDSBufferTemp); + + + hr = IDirectSoundBuffer_QueryInterface(pDSBuffer, g_DirectSound3DBuffer8GUID, (void**)&pDSBuffer3D); + if (FAILED(hr)) { + return NULL; + } + + IDirectSound3DBuffer_SetPosition(pDSBuffer3D, 0, 0, 0, DS3D_IMMEDIATE); + + if ((pBufferDesc->flags & DRAUDIO_RELATIVE_3D) != 0) { + IDirectSound3DBuffer_SetMode(pDSBuffer3D, DS3DMODE_HEADRELATIVE, DS3D_IMMEDIATE); + } + } + + + + // We need to create a notification object so we can notify the host application when the playback buffer hits a certain point. + LPDIRECTSOUNDNOTIFY pDSNotify; + HRESULT hr = IDirectSoundBuffer8_QueryInterface(pDSBuffer, g_DirectSoundNotifyGUID, (void**)&pDSNotify); + if (FAILED(hr)) { + IDirectSound3DBuffer_Release(pDSBuffer3D); + IDirectSoundBuffer8_Release(pDSBuffer); + return NULL; + } + + + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)malloc(sizeof(draudio_buffer_dsound) - sizeof(pBufferDS->pExtraData) + extraDataSize); + if (pBufferDS == NULL) { + IDirectSound3DBuffer_Release(pDSBuffer3D); + IDirectSoundBuffer8_Release(pDSBuffer); + return NULL; + } + + pBufferDS->base.pDevice = pDevice; + pBufferDS->pDSBuffer = pDSBuffer; + pBufferDS->pDSBuffer3D = pDSBuffer3D; + pBufferDS->pDSNotify = pDSNotify; + pBufferDS->playbackState = draudio_stopped; + + pBufferDS->markerEventCount = 0; + memset(pBufferDS->pMarkerEvents, 0, sizeof(pBufferDS->pMarkerEvents)); + pBufferDS->pStopEvent = NULL; + pBufferDS->pPauseEvent = NULL; + pBufferDS->pPlayEvent = NULL; + + + + // Fill with initial data, if applicable. + if (pBufferDesc->pData != NULL) { + draudio_set_buffer_data((draudio_buffer*)pBufferDS, 0, pBufferDesc->pData, pBufferDesc->sizeInBytes); + } + + return (draudio_buffer*)pBufferDS; +} + +void draudio_delete_buffer_dsound(draudio_buffer* pBuffer) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + assert(pBuffer->pDevice != NULL); + + draudio_context_dsound* pContextDS = (draudio_context_dsound*)pBuffer->pDevice->pContext; + assert(pContextDS != NULL); + + + // Deactivate the DirectSound notify events for sanity. + draudio_deactivate_buffer_events_dsound(pBuffer); + + + draudio_message_dsound msg; + msg.id = DRAUDIO_MESSAGE_ID_DELETE_BUFFER; + msg.pBuffer = pBuffer; + msg.data.delete_buffer.pDSNotify = pBufferDS->pDSNotify; + msg.data.delete_buffer.pDSBuffer3D = pBufferDS->pDSBuffer3D; + msg.data.delete_buffer.pDSBuffer = pBufferDS->pDSBuffer; + draudio_post_message_dsound(&pContextDS->messageQueue, msg); + +#if 0 + if (pBufferDS->pDSNotify != NULL) { + IDirectSoundNotify_Release(pBufferDS->pDSNotify); + } + + if (pBufferDS->pDSBuffer3D != NULL) { + IDirectSound3DBuffer_Release(pBufferDS->pDSBuffer3D); + } + + if (pBufferDS->pDSBuffer != NULL) { + IDirectSoundBuffer8_Release(pBufferDS->pDSBuffer); + } + + free(pBufferDS); +#endif +} + + +unsigned int draudio_get_buffer_extra_data_size_dsound(draudio_buffer* pBuffer) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + return pBufferDS->extraDataSize; +} + +void* draudio_get_buffer_extra_data_dsound(draudio_buffer* pBuffer) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + return pBufferDS->pExtraData; +} + + +void draudio_set_buffer_data_dsound(draudio_buffer* pBuffer, size_t offset, const void* pData, size_t dataSizeInBytes) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + assert(pData != NULL); + + LPVOID lpvWrite; + DWORD dwLength; + HRESULT hr = IDirectSoundBuffer8_Lock(pBufferDS->pDSBuffer, (DWORD)offset, (DWORD)dataSizeInBytes, &lpvWrite, &dwLength, NULL, NULL, 0); + if (FAILED(hr)) { + return; + } + + assert(dataSizeInBytes <= dwLength); + memcpy(lpvWrite, pData, dataSizeInBytes); + + hr = IDirectSoundBuffer8_Unlock(pBufferDS->pDSBuffer, lpvWrite, dwLength, NULL, 0); + if (FAILED(hr)) { + return; + } +} + + +void draudio_play_dsound(draudio_buffer* pBuffer, bool loop) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + bool postEvent = true; + if (pBufferDS->playbackState == draudio_playing) { + postEvent = false; + } + + + // Events need to be activated. + if (pBufferDS->playbackState == draudio_stopped) { + draudio_activate_buffer_events_dsound(pBuffer); + } + + + DWORD dwFlags = 0; + if (loop) { + dwFlags |= DSBPLAY_LOOPING; + } + + pBufferDS->playbackState = draudio_playing; + IDirectSoundBuffer8_Play(pBufferDS->pDSBuffer, 0, 0, dwFlags); + + // If we have a play event we need to signal the event which will cause the worker thread to call the callback function. + if (pBufferDS->pPlayEvent != NULL && postEvent) { + SetEvent(pBufferDS->pPlayEvent->hEvent); + } +} + +void draudio_pause_dsound(draudio_buffer* pBuffer) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + if (pBufferDS->playbackState == draudio_playing) + { + pBufferDS->playbackState = draudio_paused; + IDirectSoundBuffer8_Stop(pBufferDS->pDSBuffer); + + // If we have a pause event we need to signal the event which will cause the worker thread to call the callback function. + if (pBufferDS->pPlayEvent != NULL) { + SetEvent(pBufferDS->pPauseEvent->hEvent); + } + } +} + +void draudio_stop_dsound(draudio_buffer* pBuffer) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + if (pBufferDS->playbackState == draudio_playing) + { + pBufferDS->playbackState = draudio_stopped; + IDirectSoundBuffer8_Stop(pBufferDS->pDSBuffer); + IDirectSoundBuffer8_SetCurrentPosition(pBufferDS->pDSBuffer, 0); + } + else if (pBufferDS->playbackState == draudio_paused) + { + pBufferDS->playbackState = draudio_stopped; + IDirectSoundBuffer8_SetCurrentPosition(pBufferDS->pDSBuffer, 0); + + if (pBufferDS->pStopEvent != NULL) { + SetEvent(pBufferDS->pStopEvent->hEvent); + } + } +} + +draudio_playback_state draudio_get_playback_state_dsound(draudio_buffer* pBuffer) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + return pBufferDS->playbackState; +} + + +void draudio_set_playback_position_dsound(draudio_buffer* pBuffer, unsigned int position) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + IDirectSoundBuffer8_SetCurrentPosition(pBufferDS->pDSBuffer, position); +} + +unsigned int draudio_get_playback_position_dsound(draudio_buffer* pBuffer) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + DWORD position; + HRESULT hr = IDirectSoundBuffer8_GetCurrentPosition(pBufferDS->pDSBuffer, &position, NULL); + if (FAILED(hr)) { + return 0; + } + + return position; +} + + +void draudio_set_pan_dsound(draudio_buffer* pBuffer, float pan) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + LONG panDB; + if (pan == 0) { + panDB = DSBPAN_CENTER; + } else { + if (pan > 1) { + panDB = DSBPAN_RIGHT; + } else if (pan < -1) { + panDB = DSBPAN_LEFT; + } else { + if (pan < 0) { + panDB = (LONG)((20*log10f(1 + pan)) * 100); + } else { + panDB = -(LONG)((20*log10f(1 - pan)) * 100); + } + } + } + + IDirectSoundBuffer_SetPan(pBufferDS->pDSBuffer, panDB); +} + +float draudio_get_pan_dsound(draudio_buffer* pBuffer) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + LONG panDB; + HRESULT hr = IDirectSoundBuffer_GetPan(pBufferDS->pDSBuffer, &panDB); + if (FAILED(hr)) { + return 0; + } + + + if (panDB < 0) { + return -(1 - (float)(1.0f / powf(10.0f, -panDB / (20.0f*100.0f)))); + } + + if (panDB > 0) { + return (1 - (float)(1.0f / powf(10.0f, panDB / (20.0f*100.0f)))); + } + + return 0; +} + + +void draudio_set_volume_dsound(draudio_buffer* pBuffer, float volume) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + LONG volumeDB; + if (volume > 0) { + if (volume < 1) { + volumeDB = (LONG)((20*log10f(volume)) * 100); + } else { + volumeDB = DSBVOLUME_MAX; + } + } else { + volumeDB = DSBVOLUME_MIN; + } + + IDirectSoundBuffer_SetVolume(pBufferDS->pDSBuffer, volumeDB); +} + +float draudio_get_volume_dsound(draudio_buffer* pBuffer) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + LONG volumeDB; + HRESULT hr = IDirectSoundBuffer_GetVolume(pBufferDS->pDSBuffer, &volumeDB); + if (FAILED(hr)) { + return 1; + } + + return (float)(1.0f / powf(10.0f, -volumeDB / (20.0f*100.0f))); +} + + +void draudio_remove_markers_dsound(draudio_buffer* pBuffer) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + for (unsigned int iMarker = 0; iMarker < pBufferDS->markerEventCount; ++iMarker) + { + if (pBufferDS->pMarkerEvents[iMarker] != NULL) { + draudio_delete_event_dsound(pBufferDS->pMarkerEvents[iMarker]); + pBufferDS->pMarkerEvents[iMarker] = NULL; + } + } + + pBufferDS->markerEventCount = 0; +} + +bool draudio_register_marker_callback_dsound(draudio_buffer* pBuffer, size_t offsetInBytes, draudio_event_callback_proc callback, unsigned int eventID, void* pUserData) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + assert(pBufferDS->markerEventCount <= DRAUDIO_MAX_MARKER_COUNT); + + if (pBufferDS->markerEventCount == DRAUDIO_MAX_MARKER_COUNT) { + // Too many markers. + return false; + } + + draudio_context_dsound* pContextDS = (draudio_context_dsound*)pBuffer->pDevice->pContext; + assert(pContextDS != NULL); + + draudio_event_dsound* pEvent = draudio_create_event_dsound(&pContextDS->eventManager, callback, pBuffer, eventID, pUserData); + if (pEvent == NULL) { + return false; + } + + // draudio_create_event_dsound() will initialize the marker offset to 0, so we'll need to set it manually here. + pEvent->markerOffset = (DWORD)offsetInBytes; + + pBufferDS->pMarkerEvents[pBufferDS->markerEventCount] = pEvent; + pBufferDS->markerEventCount += 1; + + return true; +} + +bool draudio_register_stop_callback_dsound(draudio_buffer* pBuffer, draudio_event_callback_proc callback, void* pUserData) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + if (callback == NULL) + { + if (pBufferDS->pStopEvent != NULL) { + draudio_delete_event_dsound(pBufferDS->pStopEvent); + pBufferDS->pStopEvent = NULL; + } + + return true; + } + else + { + draudio_context_dsound* pContextDS = (draudio_context_dsound*)pBuffer->pDevice->pContext; + + // If we already have a stop event, just replace the existing one. + if (pBufferDS->pStopEvent != NULL) { + draudio_update_event_dsound(pBufferDS->pStopEvent, callback, pUserData); + } else { + pBufferDS->pStopEvent = draudio_create_event_dsound(&pContextDS->eventManager, callback, pBuffer, DRAUDIO_EVENT_ID_STOP, pUserData); + } + + return pBufferDS->pStopEvent != NULL; + } +} + +bool draudio_register_pause_callback_dsound(draudio_buffer* pBuffer, draudio_event_callback_proc callback, void* pUserData) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + if (callback == NULL) + { + if (pBufferDS->pPauseEvent != NULL) { + draudio_delete_event_dsound(pBufferDS->pPauseEvent); + pBufferDS->pPauseEvent = NULL; + } + + return true; + } + else + { + draudio_context_dsound* pContextDS = (draudio_context_dsound*)pBuffer->pDevice->pContext; + + // If we already have a stop event, just replace the existing one. + if (pBufferDS->pPauseEvent != NULL) { + draudio_update_event_dsound(pBufferDS->pPauseEvent, callback, pUserData); + } else { + pBufferDS->pPauseEvent = draudio_create_event_dsound(&pContextDS->eventManager, callback, pBuffer, DRAUDIO_EVENT_ID_PAUSE, pUserData); + } + + return pBufferDS->pPauseEvent != NULL; + } +} + +bool draudio_register_play_callback_dsound(draudio_buffer* pBuffer, draudio_event_callback_proc callback, void* pUserData) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + if (callback == NULL) + { + if (pBufferDS->pPlayEvent != NULL) { + draudio_delete_event_dsound(pBufferDS->pPlayEvent); + pBufferDS->pPlayEvent = NULL; + } + + return true; + } + else + { + draudio_context_dsound* pContextDS = (draudio_context_dsound*)pBuffer->pDevice->pContext; + + // If we already have a stop event, just replace the existing one. + if (pBufferDS->pPlayEvent != NULL) { + draudio_update_event_dsound(pBufferDS->pPlayEvent, callback, pUserData); + } else { + pBufferDS->pPlayEvent = draudio_create_event_dsound(&pContextDS->eventManager, callback, pBuffer, DRAUDIO_EVENT_ID_PLAY, pUserData); + } + + return pBufferDS->pPlayEvent != NULL; + } +} + + + +void draudio_set_position_dsound(draudio_buffer* pBuffer, float x, float y, float z) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + if (pBufferDS->pDSBuffer3D != NULL) { + IDirectSound3DBuffer_SetPosition(pBufferDS->pDSBuffer3D, x, y, z, DS3D_IMMEDIATE); + } +} + +void draudio_get_position_dsound(draudio_buffer* pBuffer, float* pPosOut) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + assert(pPosOut != NULL); + + if (pBufferDS->pDSBuffer3D != NULL) + { + D3DVECTOR pos; + IDirectSound3DBuffer_GetPosition(pBufferDS->pDSBuffer3D, &pos); + + pPosOut[0] = pos.x; + pPosOut[1] = pos.y; + pPosOut[2] = pos.z; + } + else + { + pPosOut[0] = 0; + pPosOut[1] = 1; + pPosOut[2] = 2; + } +} + + +void draudio_set_listener_position_dsound(draudio_device* pDevice, float x, float y, float z) +{ + draudio_device_dsound* pDeviceDS = (draudio_device_dsound*)pDevice; + assert(pDeviceDS != NULL); + + IDirectSound3DListener_SetPosition(pDeviceDS->pDSListener, x, y, z, DS3D_IMMEDIATE); +} + +void draudio_get_listener_position_dsound(draudio_device* pDevice, float* pPosOut) +{ + draudio_device_dsound* pDeviceDS = (draudio_device_dsound*)pDevice; + assert(pDeviceDS != NULL); + assert(pPosOut != NULL); + + D3DVECTOR pos; + IDirectSound3DListener_GetPosition(pDeviceDS->pDSListener, &pos); + + pPosOut[0] = pos.x; + pPosOut[1] = pos.y; + pPosOut[2] = pos.z; +} + + +void draudio_set_listener_orientation_dsound(draudio_device* pDevice, float forwardX, float forwardY, float forwardZ, float upX, float upY, float upZ) +{ + draudio_device_dsound* pDeviceDS = (draudio_device_dsound*)pDevice; + assert(pDeviceDS != NULL); + + IDirectSound3DListener_SetOrientation(pDeviceDS->pDSListener, forwardX, forwardY, forwardZ, upX, upY, upZ, DS3D_IMMEDIATE); +} + +void draudio_get_listener_orientation_dsound(draudio_device* pDevice, float* pForwardOut, float* pUpOut) +{ + draudio_device_dsound* pDeviceDS = (draudio_device_dsound*)pDevice; + assert(pDeviceDS != NULL); + assert(pForwardOut != NULL); + assert(pUpOut != NULL); + + D3DVECTOR forward; + D3DVECTOR up; + IDirectSound3DListener_GetOrientation(pDeviceDS->pDSListener, &forward, &up); + + pForwardOut[0] = forward.x; + pForwardOut[1] = forward.y; + pForwardOut[2] = forward.z; + + pUpOut[0] = up.x; + pUpOut[1] = up.y; + pUpOut[2] = up.z; +} + +void draudio_set_3d_mode_dsound(draudio_buffer* pBuffer, draudio_3d_mode mode) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + if (pBufferDS->pDSBuffer3D == NULL) { + return; + } + + + DWORD dwMode = DS3DMODE_NORMAL; + if (mode == draudio_3d_mode_relative) { + dwMode = DS3DMODE_HEADRELATIVE; + } else if (mode == draudio_3d_mode_disabled) { + dwMode = DS3DMODE_DISABLE; + } + + IDirectSound3DBuffer_SetMode(pBufferDS->pDSBuffer3D, dwMode, DS3D_IMMEDIATE); +} + +draudio_3d_mode draudio_get_3d_mode_dsound(draudio_buffer* pBuffer) +{ + draudio_buffer_dsound* pBufferDS = (draudio_buffer_dsound*)pBuffer; + assert(pBufferDS != NULL); + + if (pBufferDS->pDSBuffer3D == NULL) { + return draudio_3d_mode_disabled; + } + + + DWORD dwMode; + if (FAILED(IDirectSound3DBuffer_GetMode(pBufferDS->pDSBuffer3D, &dwMode))) { + return draudio_3d_mode_disabled; + } + + + if (dwMode == DS3DMODE_NORMAL) { + return draudio_3d_mode_absolute; + } + + if (dwMode == DS3DMODE_HEADRELATIVE) { + return draudio_3d_mode_relative; + } + + return draudio_3d_mode_disabled; +} + + +static BOOL CALLBACK DSEnumCallback_OutputDevices(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext) +{ + // From MSDN: + // + // The first device enumerated is always called the Primary Sound Driver, and the lpGUID parameter of the callback is + // NULL. This device represents the preferred output device set by the user in Control Panel. + + draudio_context_dsound* pContextDS = (draudio_context_dsound*)lpContext; + assert(pContextDS != NULL); + + if (pContextDS->outputDeviceCount < DRAUDIO_MAX_DEVICE_COUNT) + { + if (lpGuid != NULL) { + memcpy(&pContextDS->outputDeviceInfo[pContextDS->outputDeviceCount].guid, lpGuid, sizeof(GUID)); + } else { + memset(&pContextDS->outputDeviceInfo[pContextDS->outputDeviceCount].guid, 0, sizeof(GUID)); + } + + draudio_strcpy(pContextDS->outputDeviceInfo[pContextDS->outputDeviceCount].description, 256, lpcstrDescription); + draudio_strcpy(pContextDS->outputDeviceInfo[pContextDS->outputDeviceCount].moduleName, 256, lpcstrModule); + + pContextDS->outputDeviceCount += 1; + return TRUE; + } + else + { + // Ran out of device slots. + return FALSE; + } +} + +static BOOL CALLBACK DSEnumCallback_InputDevices(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext) +{ + // From MSDN: + // + // The first device enumerated is always called the Primary Sound Driver, and the lpGUID parameter of the callback is + // NULL. This device represents the preferred output device set by the user in Control Panel. + + draudio_context_dsound* pContextDS = (draudio_context_dsound*)lpContext; + assert(pContextDS != NULL); + + if (pContextDS->inputDeviceCount < DRAUDIO_MAX_DEVICE_COUNT) + { + if (lpGuid != NULL) { + memcpy(&pContextDS->inputDeviceInfo[pContextDS->inputDeviceCount].guid, lpGuid, sizeof(GUID)); + } else { + memset(&pContextDS->inputDeviceInfo[pContextDS->inputDeviceCount].guid, 0, sizeof(GUID)); + } + + draudio_strcpy(pContextDS->inputDeviceInfo[pContextDS->inputDeviceCount].description, 256, lpcstrDescription); + draudio_strcpy(pContextDS->inputDeviceInfo[pContextDS->inputDeviceCount].moduleName, 256, lpcstrModule); + + pContextDS->inputDeviceCount += 1; + return TRUE; + } + else + { + // Ran out of device slots. + return FALSE; + } +} + +draudio_context* draudio_create_context_dsound() +{ + // Load the DLL. + HMODULE hDSoundDLL = LoadLibraryW(L"dsound.dll"); + if (hDSoundDLL == NULL) { + return NULL; + } + + + // Retrieve the APIs. + pDirectSoundCreate8Proc pDirectSoundCreate8 = (pDirectSoundCreate8Proc)GetProcAddress(hDSoundDLL, "DirectSoundCreate8"); + if (pDirectSoundCreate8 == NULL){ + FreeLibrary(hDSoundDLL); + return NULL; + } + + pDirectSoundEnumerateAProc pDirectSoundEnumerateA = (pDirectSoundEnumerateAProc)GetProcAddress(hDSoundDLL, "DirectSoundEnumerateA"); + if (pDirectSoundEnumerateA == NULL){ + FreeLibrary(hDSoundDLL); + return NULL; + } + + pDirectSoundCaptureCreate8Proc pDirectSoundCaptureCreate8 = (pDirectSoundCaptureCreate8Proc)GetProcAddress(hDSoundDLL, "DirectSoundCaptureCreate8"); + if (pDirectSoundCaptureCreate8 == NULL) { + FreeLibrary(hDSoundDLL); + return NULL; + } + + pDirectSoundCaptureEnumerateAProc pDirectSoundCaptureEnumerateA = (pDirectSoundCaptureEnumerateAProc)GetProcAddress(hDSoundDLL, "DirectSoundCaptureEnumerateA"); + if (pDirectSoundCaptureEnumerateA == NULL ){ + FreeLibrary(hDSoundDLL); + return NULL; + } + + + + // At this point we can almost certainly assume DirectSound is usable so we'll now go ahead and create the context. + draudio_context_dsound* pContext = (draudio_context_dsound*)malloc(sizeof(draudio_context_dsound)); + if (pContext != NULL) + { + pContext->base.delete_context = draudio_delete_context_dsound; + pContext->base.create_output_device = draudio_create_output_device_dsound; + pContext->base.delete_output_device = draudio_delete_output_device_dsound; + pContext->base.get_output_device_count = draudio_get_output_device_count_dsound; + pContext->base.get_output_device_info = draudio_get_output_device_info_dsound; + pContext->base.create_buffer = draudio_create_buffer_dsound; + pContext->base.delete_buffer = draudio_delete_buffer_dsound; + pContext->base.get_buffer_extra_data_size = draudio_get_buffer_extra_data_size_dsound; + pContext->base.get_buffer_extra_data = draudio_get_buffer_extra_data_dsound; + pContext->base.set_buffer_data = draudio_set_buffer_data_dsound; + pContext->base.play = draudio_play_dsound; + pContext->base.pause = draudio_pause_dsound; + pContext->base.stop = draudio_stop_dsound; + pContext->base.get_playback_state = draudio_get_playback_state_dsound; + pContext->base.set_playback_position = draudio_set_playback_position_dsound; + pContext->base.get_playback_position = draudio_get_playback_position_dsound; + pContext->base.set_pan = draudio_set_pan_dsound; + pContext->base.get_pan = draudio_get_pan_dsound; + pContext->base.set_volume = draudio_set_volume_dsound; + pContext->base.get_volume = draudio_get_volume_dsound; + pContext->base.remove_markers = draudio_remove_markers_dsound; + pContext->base.register_marker_callback = draudio_register_marker_callback_dsound; + pContext->base.register_stop_callback = draudio_register_stop_callback_dsound; + pContext->base.register_pause_callback = draudio_register_pause_callback_dsound; + pContext->base.register_play_callback = draudio_register_play_callback_dsound; + pContext->base.set_position = draudio_set_position_dsound; + pContext->base.get_position = draudio_get_position_dsound; + pContext->base.set_listener_position = draudio_set_listener_position_dsound; + pContext->base.get_listener_position = draudio_get_listener_position_dsound; + pContext->base.set_listener_orientation = draudio_set_listener_orientation_dsound; + pContext->base.get_listener_orientation = draudio_get_listener_orientation_dsound; + pContext->base.set_3d_mode = draudio_set_3d_mode_dsound; + pContext->base.get_3d_mode = draudio_get_3d_mode_dsound; + + pContext->hDSoundDLL = hDSoundDLL; + pContext->pDirectSoundCreate8 = pDirectSoundCreate8; + pContext->pDirectSoundEnumerateA = pDirectSoundEnumerateA; + pContext->pDirectSoundCaptureCreate8 = pDirectSoundCaptureCreate8; + pContext->pDirectSoundCaptureEnumerateA = pDirectSoundCaptureEnumerateA; + + // Enumerate output devices. + pContext->outputDeviceCount = 0; + pContext->pDirectSoundEnumerateA(DSEnumCallback_OutputDevices, pContext); + + // Enumerate input devices. + pContext->inputDeviceCount = 0; + pContext->pDirectSoundCaptureEnumerateA(DSEnumCallback_InputDevices, pContext); + + // The message queue and marker notification thread. + if (!draudio_init_message_queue_dsound(&pContext->messageQueue) || !draudio_init_event_manager_dsound(&pContext->eventManager, &pContext->messageQueue)) + { + // Failed to initialize the event manager. + FreeLibrary(hDSoundDLL); + free(pContext); + + return NULL; + } + } + + return (draudio_context*)pContext; +} +#endif // !DRAUDIO_BUILD_DSOUND + + +/////////////////////////////////////////////////////////////////////////////// +// +// XAudio2 +// +/////////////////////////////////////////////////////////////////////////////// + +#if 0 +#define uuid(x) +#define DX_BUILD +#define INITGUID 1 +#include +#endif +#endif + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ diff --git a/source/engine/thirdparty/dr_libs/old/dr_fs.h b/source/engine/thirdparty/dr_libs/old/dr_fs.h new file mode 100644 index 0000000..6589c14 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/old/dr_fs.h @@ -0,0 +1,8051 @@ +// Public Domain. See "unlicense" statement at the end of this file. +// +// Includes code from miniz.c which can be found here: https://github.com/richgel999/miniz + +// NOTE: dr_fs is very early in development and should be considered unstable. Expect many APIs to change. + +// ABOUT +// +// dr_fs is a simple library which abstracts file IO to allow one to open files from both the native file +// system and archive/package files such as Zip files using a common API. +// +// This file includes code from miniz.c which has been stripped down to include only what's needed to support +// Zip files at basic level. Every public API has been namespaced with "drfs_" to avoid naming conflicts. +// +// Some noteworthy features: +// - Supports verbose absolute paths to avoid ambiguity. For example you can specify a path +// such as "my/package.zip/file.txt" +// - Supports shortened, transparent paths by automatically scanning for supported archives. The +// path "my/package.zip/file.txt" can be shortened to "my/file.txt", for example. This does not +// work for absolute paths, however. See notes below. +// - Fully recursive. A path such as "pack1.zip/pack2.zip/file.txt" should work just fine. +// - Easily supports custom package formats without the need to modify the original source code. +// Look at drfs_register_archive_backend() and the implementation of Zip archives for an +// example. +// - No dependencies except for the C standard library. +// +// Limitations: +// - When a file contained within a Zip file is opened, the entire uncompressed data is loaded +// onto the heap. Keep this in mind when working with large files. +// - Zip, PAK and Wavefront MTL archives are read-only at the moment. +// - dr_fs is not fully thread-safe. See notes below. +// - Asynchronous IO is not supported. +// +// +// +// USAGE +// +// This is a single-file library. To use it, do something like the following in one .c file. +// #define DR_FS_IMPLEMENTATION +// #include "dr_fs.h" +// +// You can then #include dr_fs.h in other parts of the program as you would with any other header file. +// +// Example: +// // Create a context. +// drfs_context* pVFS = drfs_create_context(); +// if (pVFS == NULL) { +// // There was an error creating the context. +// } +// +// // Add your base directories for loading from relative paths. If you do not specify at +// // least one base directory you will need to load from absolute paths. +// drfs_add_base_directory(pVFS, "C:/Users/Admin"); +// drfs_add_base_directory(pVFS, "C:/My/Folder"); +// +// ... +// +// // Open a file. A relative path was specified which means it will first check it against +// // "C:/Users/Admin". If it can't be found it will then check against "C:/My/Folder". +// drfs_file* pFile; +// drfs_result result = drfs_open(pVFS, "my/file.txt", DRFS_READ, &pFile); +// if (result != drfs_success) { +// // There was an error loading the file. It probably doesn't exist. +// } +// +// result = drfs_read(pFile, buffer, bufferSize, NULL); +// if (result != drfs_success) { +// // There was an error reading the file. +// } +// +// drfs_close(pFile); +// +// ... +// +// // Shutdown. +// drfs_delete_context(pVFS); +// +// +// +// OPTIONS +// +// To use these options, just place the appropriate #define's in the .c file before including this file. +// +// #define DR_FS_NO_ZIP +// Disable built-in support for Zip files. +// +// #define DR_FS_NO_PAK +// Disable support for Quake 2 PAK files. +// +// #define DR_FS_NO_MTL +// Disable support for Wavefront MTL files. +// +// +// +// THREAD SAFETY +// +// dr_fs is not fully thread safe. Known unsafe functionality includes: +// - Opening a file while adding or removing base directories and backends +// - Closing a file while doing anything on that file object +// - drfs_open() will malloc() the drfs_file object, and drfs_close() will free() it with no garbage collection +// nor reference counting. +// +// The issues mentioned above should not be an issue for the vast majority of cases. Base directories and backends +// will typically be registered once during initialization when the context is created, and it's unlikely an +// application will want to close a file while simultaneously trying to use it on another thread without doing it's +// own synchronization anyway. +// +// Thread-safety has not been completely ignored either. It is possible to read, write and seek on multiple threads. +// In this case it is a simple matter of first-in first-served. Also, APIs are in place to allow an application to +// do it's own synchronization. An application can use drfs_lock() and drfs_unlock() to lock and unlock a file using +// simple mutal exclusion. Inside the lock/unlock pair the application can then use the "_nolock" variation of the +// relevant APIs: +// - drfs_read_nolock() +// - drfs_write_nolock() +// - drfs_seek_nolock() +// - drfs_tell_nolock() +// - drfs_size_nolock() +// +// Opening two files that share the same archive should work fine across multiple threads. For example, if you have +// an archive called MyArchive.zip and then open two files within that archive, you can do work with each of those +// files independently on separate threads. This functionality depends on the implementation of the relevant backend, +// however. +// +// When implementing a backend, it is important to keep synchronization in mind when reading data from the host +// archive file. To help with this, use the drfs_lock() and drfs_unlock() combined with the "_nolock" variations +// of the APIs listed above. +// +// +// +// QUICK NOTES +// +// - The library works by using the notion of an "archive" to create an abstraction around the file system. +// - Conceptually, and archive is just a grouping of files and folders. An archive can be a directory on the native +// file system or an actual archive file such as a .zip file. +// - When iterating over files and folder, the order is undefined. Do not assume alphabetical. +// - When a path includes the name of the package file, such as "my/package.zip/file.txt" (note how the .zip file is +// included in the path), it is referred to as a verbose path. +// - When specifying an absolute path, it is assumed to be verbose. When specifying a relative path, it does not need +// to be verbose, in which case the library will try to search for it. A path such as "my/package.zip/file.txt" is +// equivalent to "my/file.txt". +// - Archive backends are selected based on their extension. +// - Archive backends cannot currently share the same extension. For example, many package file formats use the .pak +// extension, however only one backend can use that .pak extension. +// - For safety, if you want to overwrite a file you must explicitly call drfs_open() with the DRFS_TRUNCATE flag. +// - Platforms other than Windows do not use buffering. That is, they use read() and write() instead of fread() and +// fwrite(). +// - On Linux platforms, if you are having issues with opening files larger than 2GB, make sure this file is the first +// file included in the .c file. This ensures the _LARGEFILE64_SOURCE macro is defined before any other header file +// as required for the use of 64-bit variants of the POSIX APIs. +// - Base paths must be absolute and verbose. +// +// +// +// TODO: +// +// - Test result code consistency. +// - Document performance issues. +// - Consider making it so persistent constant strings (such as base paths) use dynamically allocated strings rather +// than fixed sized arrays of DRFS_MAX_PATH. +// - Replace the miniz reading functionality with a custom one: +// - There is a sort-of-bug where miniz does not correctly enumerate directories in a zip file that was created with +// the "Send to -> Compressed (zipped) folder" functionality in Windows Explorer. This is more of a thing with +// Windows Explorer more than anything, but it'd be nice if it would Just Work. +// - miniz does not support streamed reading yet. Instead, one must decompress the entire file onto the heap which +// is a bit untidy and doesn't work well with very large files. +// - ZIP64 is not supported. + +#ifndef dr_fs_h +#define dr_fs_h + +// These need to be defined before including any headers, but we don't want to expose it to the public header. +#if defined(DR_FS_IMPLEMENTATION) && !defined(_WIN32) +#ifndef _LARGEFILE64_SOURCE +#define _LARGEFILE64_SOURCE +#endif +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 +#endif +#endif + +#include + +#ifndef DR_SIZED_TYPES_DEFINED +#define DR_SIZED_TYPES_DEFINED +#if defined(_MSC_VER) && _MSC_VER < 1600 +typedef signed char dr_int8; +typedef unsigned char dr_uint8; +typedef signed short dr_int16; +typedef unsigned short dr_uint16; +typedef signed int dr_int32; +typedef unsigned int dr_uint32; +typedef signed __int64 dr_int64; +typedef unsigned __int64 dr_uint64; +#else +#include +typedef int8_t dr_int8; +typedef uint8_t dr_uint8; +typedef int16_t dr_int16; +typedef uint16_t dr_uint16; +typedef int32_t dr_int32; +typedef uint32_t dr_uint32; +typedef int64_t dr_int64; +typedef uint64_t dr_uint64; +#endif +typedef dr_uint8 dr_bool8; +typedef dr_uint32 dr_bool32; +#define DR_TRUE 1 +#define DR_FALSE 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +// The maximum length of a path in bytes, including the null terminator. If a path exceeds this amount, it will be set to an empty +// string. When this is changed the source file will need to be recompiled. Most of the time leaving this at 256 is fine, but it's +// not a problem to increase the size if you are encountering issues. Note that increasing this value will increase memory usage +// on both the heap and the stack. +#ifndef DRFS_MAX_PATH +//#define DRFS_MAX_PATH 256 +#define DRFS_MAX_PATH 1024 +//#define DRFS_MAX_PATH 4096 +#endif + +#define DRFS_READ (1 << 0) +#define DRFS_WRITE (1 << 1) +#define DRFS_EXISTING (1 << 2) +#define DRFS_TRUNCATE (1 << 3) +#define DRFS_CREATE_DIRS (1 << 4) // Creates the directory structure if required. + +#define DRFS_FILE_ATTRIBUTE_DIRECTORY 0x00000001 +#define DRFS_FILE_ATTRIBUTE_READONLY 0x00000002 + +// Result codes. +typedef enum +{ + drfs_success = 0, + drfs_unknown_error = -1, + drfs_invalid_args = -2, // Bad input arguments like a file path string is equal to NULL or whatnot. + drfs_does_not_exist = -3, + drfs_already_exists = -4, + drfs_permission_denied = -5, + drfs_too_many_open_files = -6, + drfs_no_backend = -7, + drfs_out_of_memory = -8, + drfs_not_in_write_directory = -9, // A write operation is required, but the given path is not within the write directory or it's sub-directories. + drfs_path_too_long = -10, + drfs_no_space = -11, + drfs_not_directory = -12, + drfs_too_large = -13, + drfs_at_end_of_file = -14, + drfs_invalid_archive = -15, + drfs_negative_seek = -16 +} drfs_result; + +// The allowable seeking origins. +typedef enum +{ + drfs_origin_current, + drfs_origin_start, + drfs_origin_end +}drfs_seek_origin; + + +typedef void* drfs_handle; + +typedef struct drfs_context drfs_context; +typedef struct drfs_archive drfs_archive; +typedef struct drfs_file drfs_file; +typedef struct drfs_file_info drfs_file_info; +typedef struct drfs_iterator drfs_iterator; + +typedef dr_bool32 (* drfs_is_valid_extension_proc)(const char* extension); +typedef drfs_result (* drfs_open_archive_proc) (drfs_file* pArchiveFile, unsigned int accessMode, drfs_handle* pHandleOut); +typedef void (* drfs_close_archive_proc) (drfs_handle archive); +typedef drfs_result (* drfs_get_file_info_proc) (drfs_handle archive, const char* relativePath, drfs_file_info* fi); +typedef drfs_handle (* drfs_begin_iteration_proc) (drfs_handle archive, const char* relativePath); +typedef void (* drfs_end_iteration_proc) (drfs_handle archive, drfs_handle iterator); +typedef dr_bool32 (* drfs_next_iteration_proc) (drfs_handle archive, drfs_handle iterator, drfs_file_info* fi); +typedef drfs_result (* drfs_delete_file_proc) (drfs_handle archive, const char* relativePath); +typedef drfs_result (* drfs_create_directory_proc) (drfs_handle archive, const char* relativePath); +typedef drfs_result (* drfs_move_file_proc) (drfs_handle archive, const char* relativePathOld, const char* relativePathNew); +typedef drfs_result (* drfs_copy_file_proc) (drfs_handle archive, const char* relativePathSrc, const char* relativePathDst, dr_bool32 failIfExists); +typedef drfs_result (* drfs_open_file_proc) (drfs_handle archive, const char* relativePath, unsigned int accessMode, drfs_handle* pHandleOut); +typedef void (* drfs_close_file_proc) (drfs_handle archive, drfs_handle file); +typedef drfs_result (* drfs_read_file_proc) (drfs_handle archive, drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut); +typedef drfs_result (* drfs_write_file_proc) (drfs_handle archive, drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut); +typedef drfs_result (* drfs_seek_file_proc) (drfs_handle archive, drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin); +typedef dr_uint64 (* drfs_tell_file_proc) (drfs_handle archive, drfs_handle file); +typedef dr_uint64 (* drfs_file_size_proc) (drfs_handle archive, drfs_handle file); +typedef void (* drfs_flush_file_proc) (drfs_handle archive, drfs_handle file); + +typedef struct +{ + drfs_is_valid_extension_proc is_valid_extension; + drfs_open_archive_proc open_archive; + drfs_close_archive_proc close_archive; + drfs_get_file_info_proc get_file_info; + drfs_begin_iteration_proc begin_iteration; + drfs_end_iteration_proc end_iteration; + drfs_next_iteration_proc next_iteration; + drfs_delete_file_proc delete_file; + drfs_create_directory_proc create_directory; + drfs_move_file_proc move_file; + drfs_copy_file_proc copy_file; + drfs_open_file_proc open_file; + drfs_close_file_proc close_file; + drfs_read_file_proc read_file; + drfs_write_file_proc write_file; + drfs_seek_file_proc seek_file; + drfs_tell_file_proc tell_file; + drfs_file_size_proc file_size; + drfs_flush_file_proc flush_file; +} drfs_archive_callbacks; + +struct drfs_file_info +{ + // The absolute path of the file. + char absolutePath[DRFS_MAX_PATH]; + + // The size of the file, in bytes. + dr_uint64 sizeInBytes; + + // The time the file was last modified. + dr_uint64 lastModifiedTime; + + // File attributes. + unsigned int attributes; +}; + +struct drfs_iterator +{ + // A pointer to the archive that contains the folder being iterated. + drfs_archive* pArchive; + + // A pointer to the iterator's internal handle that was returned with begin_iteration(). + drfs_handle internalIteratorHandle; + + // The file info. + drfs_file_info info; +}; + + +typedef struct +{ + drfs_archive_callbacks* pBuffer; + unsigned int count; +} drfs_callbacklist; + +typedef struct +{ + char absolutePath[DRFS_MAX_PATH]; +} drfs_basepath; + +typedef struct +{ + drfs_basepath* pBuffer; + unsigned int capacity; + unsigned int count; +} drfs_basedirs; + +struct drfs_context +{ + // The list of archive callbacks which are used for loading non-native archives. This does not include the native callbacks. + drfs_callbacklist archiveCallbacks; + + // The list of base directories. + drfs_basedirs baseDirectories; + + // The write base directory. + char writeBaseDirectory[DRFS_MAX_PATH]; + + // Keeps track of whether or not write directory guard is enabled. + dr_bool32 isWriteGuardEnabled; +}; + + +// Initializes a pre-allocated context. +drfs_result drfs_init(drfs_context* pContext); + +// Uninitializes a context. +drfs_result drfs_uninit(drfs_context* pContext); + + +// Creates an empty context. +drfs_context* drfs_create_context(); + +// Deletes the given context. +// +// This does not close any files or archives - it is up to the application to ensure those are tidied up. +void drfs_delete_context(drfs_context* pContext); + + +// Registers an archive back-end. +void drfs_register_archive_backend(drfs_context* pContext, drfs_archive_callbacks callbacks); + + +// Inserts a base directory at a specific priority position. +// +// A lower value index means a higher priority. This must be in the range of [0, drfs_get_base_directory_count()]. +void drfs_insert_base_directory(drfs_context* pContext, const char* absolutePath, unsigned int index); + +// Adds a base directory to the end of the list. +// +// The further down the list the base directory, the lower priority is will receive. This adds it to the end which +// means it it given a lower priority to those that are already in the list. Use drfs_insert_base_directory() to +// insert the base directory at a specific position. +// +// Base directories must be an absolute path to a real directory. +void drfs_add_base_directory(drfs_context* pContext, const char* absolutePath); + +// Removes the given base directory. +void drfs_remove_base_directory(drfs_context* pContext, const char* absolutePath); + +// Removes the directory at the given index. +// +// If you need to remove every base directory, use drfs_remove_all_base_directories() since that is more efficient. +void drfs_remove_base_directory_by_index(drfs_context* pContext, unsigned int index); + +// Removes every base directory from the given context. +void drfs_remove_all_base_directories(drfs_context* pContext); + +// Retrieves the number of base directories attached to the given context. +unsigned int drfs_get_base_directory_count(drfs_context* pContext); + +// Retrieves the base directory at the given index. +const char* drfs_get_base_directory_by_index(drfs_context* pContext, unsigned int index); + + +// Sets the base directory for write operations (including delete). +// +// When doing a write operation using a relative path, the full path will be resolved using this directory as the base. +// +// If the base write directory is not set, and absolute path must be used for all write operations. +// +// If the write directory guard is enabled, all write operations that are attempted at a higher level than this directory +// will fail. +void drfs_set_base_write_directory(drfs_context* pContext, const char* absolutePath); + +// Retrieves the base write directory. +dr_bool32 drfs_get_base_write_directory(drfs_context* pContext, char* absolutePathOut, unsigned int absolutePathOutSize); + +// Enables the write directory guard. +void drfs_enable_write_directory_guard(drfs_context* pContext); + +// Disables the write directory guard. +void drfs_disable_write_directory_guard(drfs_context* pContext); + +// Determines whether or not the base directory guard is enabled. +dr_bool32 drfs_is_write_directory_guard_enabled(drfs_context* pContext); + + +// Opens an archive at the given path. +// +// If the given path points to a directory on the native file system an archive will be created at that +// directory. If the path points to an archive file such as a .zip file, dr_fs will hold a handle to +// that file until the archive is closed with drfs_close_archive(). Keep this in mind if you are keeping +// many archives open at a time on platforms that limit the number of files one can have open at any given +// time. +// +// The given path must be either absolute, or relative to one of the base directories. +// +// The path can be nested, such as "C:/my_zip_file.zip/my_inner_zip_file.zip". +drfs_result drfs_open_archive(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, drfs_archive** ppArchiveOut); + +// Opens the archive that owns the given file. +// +// This is different to drfs_open_archive() in that it can accept non-archive files. It will open the +// archive that directly owns the file. In addition, it will output the path of the file, relative to the +// archive. +// +// If the given file is an archive itself, the archive that owns that archive will be opened. If the file +// is a file on the native file system, the returned archive will represent the folder it's directly +// contained in. +drfs_result drfs_open_owner_archive(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, char* relativePathOut, size_t relativePathOutSize, drfs_archive** ppArchiveOut); + +// Closes the given archive. +void drfs_close_archive(drfs_archive* pArchive); + +// Opens a file relative to the given archive. +drfs_result drfs_open_file_from_archive(drfs_archive* pArchive, const char* relativePath, unsigned int accessMode, drfs_file** ppFileOut); + + + +// Opens a file. +// +// When opening the file in write mode, the write pointer will always be sitting at the start of the file. +drfs_result drfs_open(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, drfs_file** ppFileOut); + +// Closes the given file. +void drfs_close(drfs_file* pFile); + +// Reads data from the given file. +// +// Returns DR_TRUE if successful; DR_FALSE otherwise. If the value output to is less than it means the file is at the end. +// +// Do not use the return value to check if the end of the file has been reached. Instead, compare to the value returned to . +drfs_result drfs_read(drfs_file* pFile, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut); + +// Writes data to the given file. +drfs_result drfs_write(drfs_file* pFile, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut); + +// Seeks the file pointer by the given number of bytes, relative to the specified origin. +drfs_result drfs_seek(drfs_file* pFile, dr_int64 bytesToSeek, drfs_seek_origin origin); + +// Retrieves the current position of the file pointer. +dr_uint64 drfs_tell(drfs_file* pFile); + +// Retrieves the size of the given file. +dr_uint64 drfs_size(drfs_file* pFile); + +// Flushes the given file. +void drfs_flush(drfs_file* pFile); + + +// Locks the given file for simple mutal exclusion. +// +// If DR_FALSE is returned it means there was an error and the operation should be aborted. +dr_bool32 drfs_lock(drfs_file* pFile); + +// Unlocks the given file for simple mutal exclusion. +void drfs_unlock(drfs_file* pFile); + +// Unlocked drfs_read() - should only be called inside a drfs_lock()/drfs_unlock() pair. +drfs_result drfs_read_nolock(drfs_file* pFile, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut); + +// Unlocked drfs_write() - should only be called inside a drfs_lock()/drfs_unlock() pair. +drfs_result drfs_write_nolock(drfs_file* pFile, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut); + +// Unlocked drfs_seek() - should only be called inside a drfs_lock()/drfs_unlock() pair. +drfs_result drfs_seek_nolock(drfs_file* pFile, dr_int64 bytesToSeek, drfs_seek_origin origin); + +// Unlocked drfs_tell() - should only be called inside a drfs_lock()/drfs_unlock() pair. +dr_uint64 drfs_tell_nolock(drfs_file* pFile); + +// Unlocked drfs_size() - should only be called inside a drfs_lock()/drfs_unlock() pair. +dr_uint64 drfs_size_nolock(drfs_file* pFile); + + +// Retrieves information about the file at the given path. +// +// is allowed to be null, in which case the call is equivalent to simply checking if the file exists. +drfs_result drfs_get_file_info(drfs_context* pContext, const char* absoluteOrRelativePath, drfs_file_info* fi); + + +// Creates an iterator for iterating over the files and folders in the given directory. +dr_bool32 drfs_begin(drfs_context* pContext, const char* absoluteOrRelativePath, drfs_iterator* pIteratorOut); + +// Goes to the next file or folder based on the given iterator. +dr_bool32 drfs_next(drfs_context* pContext, drfs_iterator* pIterator); + +// Closes the given iterator. +// +// This is not needed if drfs_next_iteration() returns DR_FALSE naturally. If iteration is terminated early, however, this +// needs to be called on the iterator to ensure internal resources are freed. +void drfs_end(drfs_context* pContext, drfs_iterator* pIterator); + + +// Deletes the file at the given path. +// +// The path must be a absolute, or relative to the write directory. +drfs_result drfs_delete_file(drfs_context* pContext, const char* path); + +// Creates a directory. +// +// The path must be a absolute, or relative to the write directory. +drfs_result drfs_create_directory(drfs_context* pContext, const char* path); + +// Moves or renames the given file. +// +// The path must be a absolute, or relative to the write directory. This will fail if: +// - the file already exists +// - the old and new paths are across different archives +// - the archive containing both the old and new paths is read-only +// - the destinations directory structure does not exist +// +// Consider using drfs_copy_file() for more flexibility with moving files to a different location. +drfs_result drfs_move_file(drfs_context* pContext, const char* pathOld, const char* pathNew); + +// Copies a file. +// +// The destination path must be a absolute, or relative to the write directory. +drfs_result drfs_copy_file(drfs_context* pContext, const char* srcPath, const char* dstPath, dr_bool32 failIfExists); + + +// Determines whether or not the given path refers to an archive file. +// +// This does not validate that the archive file exists or is valid. This will also return DR_FALSE if the path refers +// to a folder on the normal file system. +// +// Use drfs_open_archive() to check that the archive file actually exists. +dr_bool32 drfs_is_archive_path(drfs_context* pContext, const char* path); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// High Level API +// +/////////////////////////////////////////////////////////////////////////////// + +// Free's memory that was allocated internally by dr_fs. This is used when dr_fs allocates memory via a high-level helper API +// and the application is done with that memory. +void drfs_free(void* p); + +// Finds the absolute, verbose path of the given path. +drfs_result drfs_find_absolute_path(drfs_context* pContext, const char* relativePath, char* absolutePathOut, size_t absolutePathOutSize); + +// Finds the absolute, verbose path of the given path, using the given path as the higest priority base path. +drfs_result drfs_find_absolute_path_explicit_base(drfs_context* pContext, const char* relativePath, const char* highestPriorityBasePath, char* absolutePathOut, size_t absolutePathOutSize); + +// Helper function for determining whether or not the given path refers to a base directory. +dr_bool32 drfs_is_base_directory(drfs_context* pContext, const char* baseDir); + +// Helper function for writing a string. +drfs_result drfs_write_string(drfs_file* pFile, const char* str); + +// Helper function for writing a string, and then inserting a new line right after it. +// +// The new line character is "\n" and NOT "\r\n". +drfs_result drfs_write_line(drfs_file* pFile, const char* str); + + +// Helper function for opening a binary file and retrieving it's data in one go. +// +// Free the returned pointer with drfs_free() +void* drfs_open_and_read_binary_file(drfs_context* pContext, const char* absoluteOrRelativePath, size_t* pSizeInBytesOut); + +// Helper function for opening a text file and retrieving it's data in one go. +// +// Free the returned pointer with drfs_free(). +// +// The returned string is null terminated. The size returned by pSizeInBytesOut does not include the null terminator. +char* drfs_open_and_read_text_file(drfs_context* pContext, const char* absoluteOrRelativePath, size_t* pSizeInBytesOut); + +// Helper function for opening a file, writing the given data, and then closing it. This deletes the contents of the existing file, if any. +drfs_result drfs_open_and_write_binary_file(drfs_context* pContext, const char* absoluteOrRelativePath, const void* pData, size_t dataSize); + +// Helper function for opening a file, writing the given textual data, and then closing it. This deletes the contents of the existing file, if any. +drfs_result drfs_open_and_write_text_file(drfs_context* pContext, const char* absoluteOrRelativePath, const char* pTextData); + + +// Helper function for determining whether or not the given path refers to an existing file or directory. +dr_bool32 drfs_exists(drfs_context* pContext, const char* absoluteOrRelativePath); + +// Determines if the given path refers to an existing file (not a directory). +// +// This will return DR_FALSE for directories. Use drfs_exists() to check for either a file or directory. +dr_bool32 drfs_is_existing_file(drfs_context* pContext, const char* absoluteOrRelativePath); + +// Determines if the given path refers to an existing directory. +dr_bool32 drfs_is_existing_directory(drfs_context* pContext, const char* absoluteOrRelativePath); + +// Same as drfs_create_directory(), except creates the entire directory structure recursively. +drfs_result drfs_create_directory_recursive(drfs_context* pContext, const char* path); + +// Determines whether or not the given file is at the end. +// +// This is just a high-level helper function equivalent to drfs_tell(pFile) == drfs_size(pFile). +dr_bool32 drfs_eof(drfs_file* pFile); + + + +#ifdef __cplusplus +} +#endif + +#endif //dr_fs_h + + + +/////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef DR_FS_IMPLEMENTATION +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +// Whether or not the file owns the archive object it's part of. +#define DR_FS_OWNS_PARENT_ARCHIVE 0x00000001 + + +static int drfs__strcpy_s(char* dst, size_t dstSizeInBytes, const char* src) +{ +#ifdef _MSC_VER + return strcpy_s(dst, dstSizeInBytes, src); +#else + if (dst == 0) { + return EINVAL; + } + if (dstSizeInBytes == 0) { + return ERANGE; + } + if (src == 0) { + dst[0] = '\0'; + return EINVAL; + } + + size_t i; + for (i = 0; i < dstSizeInBytes && src[i] != '\0'; ++i) { + dst[i] = src[i]; + } + + if (i < dstSizeInBytes) { + dst[i] = '\0'; + return 0; + } + + dst[0] = '\0'; + return ERANGE; +#endif +} + +static int drfs__strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) +{ +#ifdef _MSC_VER + return strncpy_s(dst, dstSizeInBytes, src, count); +#else + if (dst == 0) { + return EINVAL; + } + if (dstSizeInBytes == 0) { + return EINVAL; + } + if (src == 0) { + dst[0] = '\0'; + return EINVAL; + } + + size_t maxcount = count; + if (count == ((size_t)-1) || count >= dstSizeInBytes) { // -1 = _TRUNCATE + maxcount = dstSizeInBytes - 1; + } + + size_t i; + for (i = 0; i < maxcount && src[i] != '\0'; ++i) { + dst[i] = src[i]; + } + + if (src[i] == '\0' || i == count || count == ((size_t)-1)) { + dst[i] = '\0'; + return 0; + } + + dst[0] = '\0'; + return ERANGE; +#endif +} + +static int drfs__strcat_s(char* dst, size_t dstSizeInBytes, const char* src) +{ +#ifdef _MSC_VER + return strcat_s(dst, dstSizeInBytes, src); +#else + if (dst == 0) { + return EINVAL; + } + if (dstSizeInBytes == 0) { + return ERANGE; + } + if (src == 0) { + dst[0] = '\0'; + return EINVAL; + } + + char* dstorig = dst; + + while (dstSizeInBytes > 0 && dst[0] != '\0') { + dst += 1; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes == 0) { + return EINVAL; // Unterminated. + } + + + while (dstSizeInBytes > 0 && src[0] != '\0') { + *dst++ = *src++; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes > 0) { + dst[0] = '\0'; + } else { + dstorig[0] = '\0'; + return ERANGE; + } + + return 0; +#endif +} + +static int drfs__stricmp(const char* string1, const char* string2) +{ +#ifdef _MSC_VER + return _stricmp(string1, string2); +#else + return strcasecmp(string1, string2); +#endif +} + + +static dr_bool32 drfs_basedirs_init(drfs_basedirs* pBasePaths) +{ + if (pBasePaths == NULL) { + return DR_FALSE; + } + + pBasePaths->pBuffer = 0; + pBasePaths->capacity = 0; + pBasePaths->count = 0; + + return DR_TRUE; +} + +static void drfs_basedirs_uninit(drfs_basedirs* pBasePaths) +{ + if (pBasePaths == NULL) { + return; + } + + free(pBasePaths->pBuffer); +} + +static dr_bool32 drfs_basedirs_inflateandinsert(drfs_basedirs* pBasePaths, const char* absolutePath, unsigned int index) +{ + if (pBasePaths == NULL) { + return DR_FALSE; + } + + unsigned int newBufferSize = (pBasePaths->capacity == 0) ? 2 : pBasePaths->capacity*2; + + drfs_basepath* pOldBuffer = pBasePaths->pBuffer; + drfs_basepath* pNewBuffer = (drfs_basepath*)malloc(newBufferSize * sizeof(drfs_basepath)); + if (pNewBuffer == NULL) { + return DR_FALSE; + } + + for (unsigned int iDst = 0; iDst < index; ++iDst) { + memcpy(pNewBuffer + iDst, pOldBuffer + iDst, sizeof(drfs_basepath)); + } + + drfs__strcpy_s((pNewBuffer + index)->absolutePath, DRFS_MAX_PATH, absolutePath); + + for (unsigned int iDst = index; iDst < pBasePaths->count; ++iDst) { + memcpy(pNewBuffer + iDst + 1, pOldBuffer + iDst, sizeof(drfs_basepath)); + } + + + pBasePaths->pBuffer = pNewBuffer; + pBasePaths->capacity = newBufferSize; + pBasePaths->count += 1; + + free(pOldBuffer); + return DR_TRUE; +} + +static dr_bool32 drfs_basedirs_movedown1slot(drfs_basedirs* pBasePaths, unsigned int index) +{ + if (pBasePaths == NULL || pBasePaths->count >= pBasePaths->capacity) { + return DR_FALSE; + } + + for (unsigned int iDst = pBasePaths->count; iDst > index; --iDst) { + memcpy(pBasePaths->pBuffer + iDst, pBasePaths->pBuffer + iDst - 1, sizeof(drfs_basepath)); + } + + return DR_TRUE; +} + +static dr_bool32 drfs_basedirs_insert(drfs_basedirs* pBasePaths, const char* absolutePath, unsigned int index) +{ + if (pBasePaths == NULL || index > pBasePaths->count) { + return DR_FALSE; + } + + if (pBasePaths->count == pBasePaths->capacity) { + return drfs_basedirs_inflateandinsert(pBasePaths, absolutePath, index); + } else { + if (!drfs_basedirs_movedown1slot(pBasePaths, index)) { + return DR_FALSE; + } + + drfs__strcpy_s((pBasePaths->pBuffer + index)->absolutePath, DRFS_MAX_PATH, absolutePath); + pBasePaths->count += 1; + + return DR_TRUE; + } +} + +static dr_bool32 drfs_basedirs_remove(drfs_basedirs* pBasePaths, unsigned int index) +{ + if (pBasePaths == NULL || index >= pBasePaths->count) { + return DR_FALSE; + } + + assert(pBasePaths->count > 0); + + for (unsigned int iDst = index; iDst < pBasePaths->count - 1; ++iDst) { + memcpy(pBasePaths->pBuffer + iDst, pBasePaths->pBuffer + iDst + 1, sizeof(drfs_basepath)); + } + + pBasePaths->count -= 1; + + return DR_TRUE; +} + +static void drfs_basedirs_clear(drfs_basedirs* pBasePaths) +{ + if (pBasePaths == NULL) { + return; + } + + drfs_basedirs_uninit(pBasePaths); + drfs_basedirs_init(pBasePaths); +} + + + +static dr_bool32 drfs_callbacklist_init(drfs_callbacklist* pList) +{ + if (pList == NULL) { + return DR_FALSE; + } + + pList->pBuffer = 0; + pList->count = 0; + + return DR_TRUE; +} + +static void drfs_callbacklist_uninit(drfs_callbacklist* pList) +{ + if (pList == NULL) { + return; + } + + free(pList->pBuffer); +} + +static dr_bool32 drfs_callbacklist_inflate(drfs_callbacklist* pList) +{ + if (pList == NULL) { + return DR_FALSE; + } + + drfs_archive_callbacks* pOldBuffer = pList->pBuffer; + drfs_archive_callbacks* pNewBuffer = (drfs_archive_callbacks*)malloc((pList->count + 1) * sizeof(drfs_archive_callbacks)); + if (pNewBuffer == NULL) { + return DR_FALSE; + } + + for (unsigned int iDst = 0; iDst < pList->count; ++iDst) { + memcpy(pNewBuffer + iDst, pOldBuffer + iDst, sizeof(drfs_archive_callbacks)); + } + + pList->pBuffer = pNewBuffer; + + free(pOldBuffer); + return DR_TRUE; +} + +static dr_bool32 drfs_callbacklist_pushback(drfs_callbacklist* pList, drfs_archive_callbacks callbacks) +{ + if (pList == NULL) { + return DR_FALSE; + } + + if (!drfs_callbacklist_inflate(pList)) { + return DR_FALSE; + } + + pList->pBuffer[pList->count] = callbacks; + pList->count += 1; + + return DR_TRUE; +} + + +struct drfs_archive +{ + // A pointer to the context that owns this archive. + drfs_context* pContext; + + // A pointer to the archive that contains this archive. This can be null in which case it is the top level archive (which is always native). + drfs_archive* pParentArchive; + + // A pointer to the file containing the data of the archive file. + drfs_file* pFile; + + // The internal handle that was returned when the archive was opened by the archive definition. + drfs_handle internalArchiveHandle; + + // Flags. Can be a combination of the following: + // DR_FS_OWNS_PARENT_ARCHIVE + int flags; + + // The callbacks to use when working with on the archive. This contains all of the functions for opening files, reading + // files, etc. + drfs_archive_callbacks callbacks; + + // The absolute, verbose path of the archive. For native archives, this will be the name of the folder on the native file + // system. For non-native archives (zip, etc.) this is the the path of the archive file. + char absolutePath[DRFS_MAX_PATH]; // Change this to char absolutePath[1] and have it sized exactly as needed. +}; + +struct drfs_file +{ + // A pointer to the archive that contains the file. This should never be null. Retrieve a pointer to the contex from this + // by doing pArchive->pContext. The file containing the archive's raw data can be retrieved with pArchive->pFile. + drfs_archive* pArchive; + + // The internal file handle for use by the archive that owns it. + drfs_handle internalFileHandle; + + + // Flags. Can be a combination of the following: + // DR_FS_OWNS_PARENT_ARCHIVE + int flags; + + + // The critical section for locking and unlocking files. +#ifdef _WIN32 +#ifdef DR_FS_WIN32_USE_EVENT_MUTEX + HANDLE lock; +#else + CRITICAL_SECTION lock; +#endif +#else + pthread_mutex_t lock; +#endif +}; + + +//// Path Manipulation //// +// +// Functionality below is taken straight from dr_path, but namespaced as "drfs" to avoid naming conflicts. + +// Structure representing a section of a path. +typedef struct +{ + size_t offset; + size_t length; +} drfs_drpath_segment; + +// Structure used for iterating over a path while at the same time providing useful and easy-to-use information about the iteration. +typedef struct drfs_drpath_iterator +{ + const char* path; + drfs_drpath_segment segment; +} drfs_drpath_iterator; + +static dr_bool32 drfs_drpath_next(drfs_drpath_iterator* i) +{ + if (i == NULL || i->path == NULL) { + return DR_FALSE; + } + + i->segment.offset = i->segment.offset + i->segment.length; + i->segment.length = 0; + + while (i->path[i->segment.offset] != '\0' && (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\')) { + i->segment.offset += 1; + } + + if (i->path[i->segment.offset] == '\0') { + return DR_FALSE; + } + + + while (i->path[i->segment.offset + i->segment.length] != '\0' && (i->path[i->segment.offset + i->segment.length] != '/' && i->path[i->segment.offset + i->segment.length] != '\\')) { + i->segment.length += 1; + } + + return DR_TRUE; +} + +static dr_bool32 drfs_drpath_prev(drfs_drpath_iterator* i) +{ + if (i == NULL || i->path == NULL || i->segment.offset == 0) { + return DR_FALSE; + } + + i->segment.length = 0; + + do + { + i->segment.offset -= 1; + } while (i->segment.offset > 0 && (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\')); + + if (i->segment.offset == 0) { + if (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\') { + i->segment.length = 0; + return DR_TRUE; + } + + return DR_FALSE; + } + + + size_t offsetEnd = i->segment.offset + 1; + while (i->segment.offset > 0 && (i->path[i->segment.offset] != '/' && i->path[i->segment.offset] != '\\')) { + i->segment.offset -= 1; + } + + if (i->path[i->segment.offset] == '/' || i->path[i->segment.offset] == '\\') { + i->segment.offset += 1; + } + + + i->segment.length = offsetEnd - i->segment.offset; + + return DR_TRUE; +} + +static dr_bool32 drfs_drpath_first(const char* path, drfs_drpath_iterator* i) +{ + if (i == 0) return DR_FALSE; + i->path = path; + i->segment.offset = 0; + i->segment.length = 0; + + if (path == 0 || path[0] == '\0') { + return DR_FALSE; + } + + while (i->path[i->segment.length] != '\0' && (i->path[i->segment.length] != '/' && i->path[i->segment.length] != '\\')) { + i->segment.length += 1; + } + + return DR_TRUE; +} + +static dr_bool32 drfs_drpath_last(const char* path, drfs_drpath_iterator* i) +{ + if (i == 0) return DR_FALSE; + i->path = path; + i->segment.offset = 0; + i->segment.length = 0; + + if (path == 0 || path[0] == '\0') { + return DR_FALSE; + } + + i->path = path; + i->segment.offset = strlen(path); + i->segment.length = 0; + + return drfs_drpath_prev(i); +} + +static dr_bool32 drfs_drpath_segments_equal(const char* s0Path, const drfs_drpath_segment s0, const char* s1Path, const drfs_drpath_segment s1) +{ + if (s0Path == NULL || s1Path == NULL) { + return DR_FALSE; + } + + if (s0.length != s1.length) { + return DR_FALSE; + } + + return strncmp(s0Path + s0.offset, s1Path + s1.offset, s0.length) == 0; +} + +static dr_bool32 drfs_drpath_iterators_equal(const drfs_drpath_iterator i0, const drfs_drpath_iterator i1) +{ + return drfs_drpath_segments_equal(i0.path, i0.segment, i1.path, i1.segment); +} + +static dr_bool32 drfs_drpath_is_linux_style_root_segment(const drfs_drpath_iterator i) +{ + if (i.path == NULL) { + return DR_FALSE; + } + + if (i.segment.offset == 0 && i.segment.length == 0) { + return DR_TRUE; // "/" style root. + } + + return DR_FALSE; +} + +static dr_bool32 drfs_drpath_is_win32_style_root_segment(const drfs_drpath_iterator i) +{ + if (i.path == NULL) { + return DR_FALSE; + } + + if (i.segment.offset == 0 && i.segment.length == 2) { + if (((i.path[0] >= 'a' && i.path[0] <= 'z') || (i.path[0] >= 'A' && i.path[0] <= 'Z')) && i.path[1] == ':') { + return DR_TRUE; + } + } + + return DR_FALSE; +} + +static dr_bool32 drfs_drpath_is_root_segment(const drfs_drpath_iterator i) +{ + return drfs_drpath_is_linux_style_root_segment(i) || drfs_drpath_is_win32_style_root_segment(i); +} + +static dr_bool32 drfs_drpath_append_iterator(char* base, size_t baseBufferSizeInBytes, drfs_drpath_iterator i) +{ + if (base == NULL) { + return DR_FALSE; + } + + size_t path1Length = strlen(base); + size_t path2Length = i.segment.length; + + if (path1Length >= baseBufferSizeInBytes) { + return DR_FALSE; + } + + if (drfs_drpath_is_linux_style_root_segment(i)) { + if (baseBufferSizeInBytes > 1) { + base[0] = '/'; + base[1] = '\0'; + return DR_TRUE; + } + } + + + // Slash. + if (path1Length > 0 && base[path1Length - 1] != '/' && base[path1Length - 1] != '\\') { + base[path1Length] = '/'; + path1Length += 1; + } + + // Other part. + if (path1Length + path2Length >= baseBufferSizeInBytes) { + path2Length = baseBufferSizeInBytes - path1Length - 1; // -1 for the null terminator. + } + + drfs__strncpy_s(base + path1Length, baseBufferSizeInBytes - path1Length, i.path + i.segment.offset, path2Length); + + return DR_TRUE; +} + +static dr_bool32 drfs_drpath_is_descendant(const char* descendantAbsolutePath, const char* parentAbsolutePath) +{ + drfs_drpath_iterator iChild; + if (!drfs_drpath_first(descendantAbsolutePath, &iChild)) { + return DR_FALSE; // The descendant is an empty string which makes it impossible for it to be a descendant. + } + + drfs_drpath_iterator iParent; + if (drfs_drpath_first(parentAbsolutePath, &iParent)) + { + do + { + // If the segment is different, the paths are different and thus it is not a descendant. + if (!drfs_drpath_iterators_equal(iParent, iChild)) { + return DR_FALSE; + } + + if (!drfs_drpath_next(&iChild)) { + return DR_FALSE; // The descendant is shorter which means it's impossible for it to be a descendant. + } + + } while (drfs_drpath_next(&iParent)); + } + + return DR_TRUE; +} + +static dr_bool32 drfs_drpath_is_child(const char* childAbsolutePath, const char* parentAbsolutePath) +{ + drfs_drpath_iterator iChild; + if (!drfs_drpath_first(childAbsolutePath, &iChild)) { + return DR_FALSE; // The descendant is an empty string which makes it impossible for it to be a descendant. + } + + drfs_drpath_iterator iParent; + if (drfs_drpath_first(parentAbsolutePath, &iParent)) + { + do + { + // If the segment is different, the paths are different and thus it is not a descendant. + if (!drfs_drpath_iterators_equal(iParent, iChild)) { + return DR_FALSE; + } + + if (!drfs_drpath_next(&iChild)) { + return DR_FALSE; // The descendant is shorter which means it's impossible for it to be a descendant. + } + + } while (drfs_drpath_next(&iParent)); + } + + // At this point we have finished iteration of the parent, which should be shorter one. We now do one more iterations of + // the child to ensure it is indeed a direct child and not a deeper descendant. + return !drfs_drpath_next(&iChild); +} + +static void drfs_drpath_base_path(char* path) +{ + if (path == NULL) { + return; + } + + char* baseend = path; + + // We just loop through the path until we find the last slash. + while (path[0] != '\0') { + if (path[0] == '/' || path[0] == '\\') { + baseend = path; + } + + path += 1; + } + + + // Now we just loop backwards until we hit the first non-slash. + while (baseend > path) { + if (baseend[0] != '/' && baseend[0] != '\\') { + break; + } + + baseend -= 1; + } + + + // We just put a null terminator on the end. + baseend[0] = '\0'; +} + +static void drfs_drpath_copy_base_path(const char* path, char* baseOut, size_t baseSizeInBytes) +{ + if (path == NULL || baseOut == NULL || baseSizeInBytes == 0) { + return; + } + + drfs__strcpy_s(baseOut, baseSizeInBytes, path); + drfs_drpath_base_path(baseOut); +} + +static const char* drfs_drpath_file_name(const char* path) +{ + if (path == NULL) { + return NULL; + } + + const char* fileName = path; + + // We just loop through the path until we find the last slash. + while (path[0] != '\0') { + if (path[0] == '/' || path[0] == '\\') { + fileName = path; + } + + path += 1; + } + + // At this point the file name is sitting on a slash, so just move forward. + while (fileName[0] != '\0' && (fileName[0] == '/' || fileName[0] == '\\')) { + fileName += 1; + } + + return fileName; +} + +static const char* drfs_drpath_extension(const char* path) +{ + if (path == NULL) { + return NULL; + } + + const char* extension = drfs_drpath_file_name(path); + const char* lastoccurance = 0; + + // Just find the last '.' and return. + while (extension[0] != '\0') + { + extension += 1; + + if (extension[0] == '.') { + extension += 1; + lastoccurance = extension; + } + } + + return (lastoccurance != 0) ? lastoccurance : extension; +} + +static dr_bool32 drfs_drpath_equal(const char* path1, const char* path2) +{ + if (path1 == NULL || path2 == NULL) { + return DR_FALSE; + } + + if (path1 == path2 || (path1[0] == '\0' && path2[0] == '\0')) { + return DR_TRUE; // Two empty paths are treated as the same. + } + + drfs_drpath_iterator iPath1; + drfs_drpath_iterator iPath2; + if (drfs_drpath_first(path1, &iPath1) && drfs_drpath_first(path2, &iPath2)) + { + dr_bool32 isPath1Valid; + dr_bool32 isPath2Valid; + + do + { + if (!drfs_drpath_iterators_equal(iPath1, iPath2)) { + return DR_FALSE; + } + + isPath1Valid = drfs_drpath_next(&iPath1); + isPath2Valid = drfs_drpath_next(&iPath2); + + } while (isPath1Valid && isPath2Valid); + + // At this point either iPath1 and/or iPath2 have finished iterating. If both of them are at the end, the two paths are equal. + return isPath1Valid == isPath2Valid && iPath1.path[iPath1.segment.offset] == '\0' && iPath2.path[iPath2.segment.offset] == '\0'; + } + + return DR_FALSE; +} + +static dr_bool32 drfs_drpath_is_relative(const char* path) +{ + if (path == NULL) { + return DR_FALSE; + } + + drfs_drpath_iterator seg; + if (drfs_drpath_first(path, &seg)) { + return !drfs_drpath_is_root_segment(seg); + } + + // We'll get here if the path is empty. We consider this to be a relative path. + return DR_TRUE; +} + +static dr_bool32 drfs_drpath_is_absolute(const char* path) +{ + return !drfs_drpath_is_relative(path); +} + +static dr_bool32 drfs_drpath_append(char* base, size_t baseBufferSizeInBytes, const char* other) +{ + if (base == NULL || other == NULL) { + return DR_FALSE; + } + + size_t path1Length = strlen(base); + size_t path2Length = strlen(other); + + if (path1Length >= baseBufferSizeInBytes) { + return DR_FALSE; + } + + + // Slash. + if (path1Length > 0 && base[path1Length - 1] != '/' && base[path1Length - 1] != '\\') { + base[path1Length] = '/'; + path1Length += 1; + } + + // Other part. + if (path1Length + path2Length >= baseBufferSizeInBytes) { + path2Length = baseBufferSizeInBytes - path1Length - 1; // -1 for the null terminator. + } + + drfs__strncpy_s(base + path1Length, baseBufferSizeInBytes - path1Length, other, path2Length); + + return DR_TRUE; +} + +static dr_bool32 drfs_drpath_copy_and_append(char* dst, size_t dstSizeInBytes, const char* base, const char* other) +{ + if (dst == NULL || dstSizeInBytes == 0) { + return DR_FALSE; + } + + drfs__strcpy_s(dst, dstSizeInBytes, base); + return drfs_drpath_append(dst, dstSizeInBytes, other); +} + +// This function recursively cleans a path which is defined as a chain of iterators. This function works backwards, which means at the time of calling this +// function, each iterator in the chain should be placed at the end of the path. +// +// This does not write the null terminator, nor a leading slash for absolute paths. +static size_t _drfs_drpath_clean_trywrite(drfs_drpath_iterator* iterators, unsigned int iteratorCount, char* pathOut, size_t pathOutSizeInBytes, unsigned int ignoreCounter) +{ + if (iteratorCount == 0) { + return 0; + } + + drfs_drpath_iterator isegment = iterators[iteratorCount - 1]; + + + // If this segment is a ".", we ignore it. If it is a ".." we ignore it and increment "ignoreCount". + int ignoreThisSegment = ignoreCounter > 0 && isegment.segment.length > 0; + + if (isegment.segment.length == 1 && isegment.path[isegment.segment.offset] == '.') + { + // "." + ignoreThisSegment = 1; + } + else + { + if (isegment.segment.length == 2 && isegment.path[isegment.segment.offset] == '.' && isegment.path[isegment.segment.offset + 1] == '.') + { + // ".." + ignoreThisSegment = 1; + ignoreCounter += 1; + } + else + { + // It's a regular segment, so decrement the ignore counter. + if (ignoreCounter > 0) { + ignoreCounter -= 1; + } + } + } + + + // The previous segment needs to be written before we can write this one. + size_t bytesWritten = 0; + + drfs_drpath_iterator prev = isegment; + if (!drfs_drpath_prev(&prev)) + { + if (iteratorCount > 1) + { + iteratorCount -= 1; + prev = iterators[iteratorCount - 1]; + } + else + { + prev.path = NULL; + prev.segment.offset = 0; + prev.segment.length = 0; + } + } + + if (prev.segment.length > 0) + { + iterators[iteratorCount - 1] = prev; + bytesWritten = _drfs_drpath_clean_trywrite(iterators, iteratorCount, pathOut, pathOutSizeInBytes, ignoreCounter); + } + + + if (!ignoreThisSegment) + { + if (pathOutSizeInBytes > 0) + { + pathOut += bytesWritten; + pathOutSizeInBytes -= bytesWritten; + + if (bytesWritten > 0) + { + pathOut[0] = '/'; + bytesWritten += 1; + + pathOut += 1; + pathOutSizeInBytes -= 1; + } + + if (pathOutSizeInBytes >= isegment.segment.length) + { + drfs__strncpy_s(pathOut, pathOutSizeInBytes, isegment.path + isegment.segment.offset, isegment.segment.length); + bytesWritten += isegment.segment.length; + } + else + { + drfs__strncpy_s(pathOut, pathOutSizeInBytes, isegment.path + isegment.segment.offset, pathOutSizeInBytes); + bytesWritten += pathOutSizeInBytes; + } + } + } + + return bytesWritten; +} + +static size_t drfs_drpath_append_and_clean(char* dst, size_t dstSizeInBytes, const char* base, const char* other) +{ + if (base == NULL || other == NULL) { + return 0; + } + + drfs_drpath_iterator last[2]; + dr_bool32 isPathEmpty0 = !drfs_drpath_last(base, last + 0); + dr_bool32 isPathEmpty1 = !drfs_drpath_last(other, last + 1); + + if (isPathEmpty0 && isPathEmpty1) { + return 0; // Both input strings are empty. + } + + size_t bytesWritten = 0; + if (base[0] == '/') { + if (dst != NULL && dstSizeInBytes > 1) { + dst[0] = '/'; + bytesWritten = 1; + } + } + + bytesWritten += _drfs_drpath_clean_trywrite(last, 2, dst + bytesWritten, dstSizeInBytes - bytesWritten - 1, 0); // -1 to ensure there is enough room for a null terminator later on. + if (dstSizeInBytes > bytesWritten) { + dst[bytesWritten] = '\0'; + } + + return bytesWritten + 1; +} + + +//// Private Utiltities //// + +// Recursively creates the given directory structure on the native file system. +static dr_bool32 drfs_mkdir_recursive_native(const char* absolutePath); + +// Determines whether or not the given path is valid for writing based on the base write path and whether or not +// the write guard is enabled. +static dr_bool32 drfs_validate_write_path(drfs_context* pContext, const char* absoluteOrRelativePath, char* absolutePathOut, unsigned int absolutePathOutSize) +{ + // If the path is relative, we need to convert to absolute. Then, if the write directory guard is enabled, we need to check that it's a descendant of the base path. + if (drfs_drpath_is_relative(absoluteOrRelativePath)) { + if (drfs_drpath_append_and_clean(absolutePathOut, absolutePathOutSize, pContext->writeBaseDirectory, absoluteOrRelativePath)) { + absoluteOrRelativePath = absolutePathOut; + } else { + return DR_FALSE; + } + } else { + if (drfs__strcpy_s(absolutePathOut, absolutePathOutSize, absoluteOrRelativePath) != 0) { + return DR_FALSE; + } + } + + // If you trigger this assert it means you're trying to open a file for writing with a relative path but haven't yet + // set the write directory. Either set the write directory with drfs_set_base_write_directory(), or use an absolute + // path to open the file. + assert(drfs_drpath_is_absolute(absoluteOrRelativePath)); + if (!drfs_drpath_is_absolute(absoluteOrRelativePath)) { + return DR_FALSE; + } + + + if (drfs_is_write_directory_guard_enabled(pContext)) { + if (drfs_drpath_is_descendant(absoluteOrRelativePath, pContext->writeBaseDirectory)) { + return DR_TRUE; + } else { + return DR_FALSE; + } + } else { + return DR_TRUE; + } +} + +// A simple helper function for determining the access mode to use for an archive file based on the access mode +// of a file within that archive. +static unsigned int drfs_archive_access_mode(unsigned int fileAccessMode) +{ + return (fileAccessMode == DRFS_READ) ? DRFS_READ : DRFS_READ | DRFS_WRITE | DRFS_EXISTING; +} + + + +//// Platform-Specific Section //// + +// The functions in this section implement a common abstraction for working with files on the native file system. When adding +// support for a new platform you'll probably want to either implement a platform-specific back-end or force stdio. + +#if defined(_WIN32) +#define DR_FS_USE_WIN32 +#else +#define DR_FS_USE_STDIO +#endif + +// Low-level function for opening a file on the native file system. +// +// This will fail if attempting to open a file that's inside an archive such as a zip file. It can only open +// files that are sitting on the native file system. +// +// The given file path must be absolute. +static drfs_result drfs_open_native_file(const char* absolutePath, unsigned int accessMode, drfs_handle* pHandleOut); + +// Closes the given native file. +static void drfs_close_native_file(drfs_handle file); + +// Determines whether or not the given path refers to a directory on the native file system. +static dr_bool32 drfs_is_native_directory(const char* absolutePath); + +// Determines whether or not the given path refers to a file on the native file system. This will return DR_FALSE for directories. +static dr_bool32 drfs_is_native_file(const char* absolutePath); + +// Deletes a native file. +static drfs_result drfs_delete_native_file(const char* absolutePath); + +// Creates a directory on the native file system. +static drfs_result drfs_mkdir_native(const char* absolutePath); + +// Moves or renames a native file. Fails if the target already exists. +static drfs_result drfs_move_native_file(const char* absolutePathOld, const char* absolutePathNew); + +// Creates a copy of a native file. +static drfs_result drfs_copy_native_file(const char* absolutePathSrc, const char* absolutePathDst, dr_bool32 failIfExists); + +// Reads data from the given native file. +static drfs_result drfs_read_native_file(drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut); + +// Writes data to the given native file. +static drfs_result drfs_write_native_file(drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut); + +// Seeks the given native file. +static drfs_result drfs_seek_native_file(drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin); + +// Retrieves the read/write pointer of the given native file. +static dr_uint64 drfs_tell_native_file(drfs_handle file); + +// Retrieves the size of the given native file. +static dr_uint64 drfs_get_native_file_size(drfs_handle file); + +// Flushes the given native file. +static void drfs_flush_native_file(drfs_handle file); + +// Retrieves information about the file OR DIRECTORY at the given path on the native file system. +// +// is allowed to be null, in which case the call is equivalent to simply checking if the file or directory exists. +static drfs_result drfs_get_native_file_info(const char* absolutePath, drfs_file_info* fi); + +// Creates an iterator for iterating over the native files in the given directory. +// +// The iterator will be deleted with drfs_end_native_iteration(). +static drfs_handle drfs_begin_native_iteration(const char* absolutePath); + +// Deletes the iterator that was returned with drfs_end_native_iteration(). +static void drfs_end_native_iteration(drfs_handle iterator); + +// Retrieves information about the next native file based on the given iterator. +static dr_bool32 drfs_next_native_iteration(drfs_handle iterator, drfs_file_info* fi); + + + +#ifdef DR_FS_USE_WIN32 +static drfs_result drfs__GetLastError_to_result() +{ + switch (GetLastError()) + { + case ERROR_SUCCESS: return drfs_success; + case ERROR_FILE_NOT_FOUND: return drfs_does_not_exist; + case ERROR_PATH_NOT_FOUND: return drfs_does_not_exist; + case ERROR_TOO_MANY_OPEN_FILES: return drfs_too_many_open_files; + case ERROR_ACCESS_DENIED: return drfs_permission_denied; + case ERROR_NOT_ENOUGH_MEMORY: return drfs_out_of_memory; + case ERROR_DISK_FULL: return drfs_no_space; + case ERROR_HANDLE_EOF: return drfs_at_end_of_file; + case ERROR_NEGATIVE_SEEK: return drfs_negative_seek; + default: return drfs_unknown_error; + } +} + +static drfs_result drfs_open_native_file(const char* absolutePath, unsigned int accessMode, drfs_handle* pHandleOut) +{ + assert(absolutePath != NULL); + assert(pHandleOut != NULL); + + DWORD dwDesiredAccess = 0; + DWORD dwShareMode = 0; + DWORD dwCreationDisposition = OPEN_EXISTING; + + if ((accessMode & DRFS_READ) != 0) { + dwDesiredAccess |= FILE_GENERIC_READ; + dwShareMode |= FILE_SHARE_READ; + } + + if ((accessMode & DRFS_WRITE) != 0) { + dwDesiredAccess |= FILE_GENERIC_WRITE; + + if ((accessMode & DRFS_EXISTING) != 0) { + if ((accessMode & DRFS_TRUNCATE) != 0) { + dwCreationDisposition = TRUNCATE_EXISTING; + } else { + dwCreationDisposition = OPEN_EXISTING; + } + } else { + if ((accessMode & DRFS_TRUNCATE) != 0) { + dwCreationDisposition = CREATE_ALWAYS; + } else { + dwCreationDisposition = OPEN_ALWAYS; + } + } + } + + + HANDLE hFile = CreateFileA(absolutePath, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + // We failed to create the file, however it may be because we are trying to create the file and the directory structure does not exist yet. In this + // case we want to try creating the directory structure and try again. + if ((accessMode & DRFS_WRITE) != 0 && (accessMode & DRFS_CREATE_DIRS) != 0) + { + char dirAbsolutePath[DRFS_MAX_PATH]; + drfs_drpath_copy_base_path(absolutePath, dirAbsolutePath, sizeof(dirAbsolutePath)); + + if (!drfs_is_native_directory(dirAbsolutePath) && drfs_mkdir_recursive_native(dirAbsolutePath)) { + hFile = CreateFileA(absolutePath, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL); + } + } + } + + if (hFile != INVALID_HANDLE_VALUE) { + *pHandleOut = (drfs_handle)hFile; + return drfs_success; + } + + return drfs__GetLastError_to_result(); +} + +static void drfs_close_native_file(drfs_handle file) +{ + CloseHandle((HANDLE)file); +} + +static dr_bool32 drfs_is_native_directory(const char* absolutePath) +{ + DWORD attributes = GetFileAttributesA(absolutePath); + return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +static dr_bool32 drfs_is_native_file(const char* absolutePath) +{ + DWORD attributes = GetFileAttributesA(absolutePath); + return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0; +} + +static drfs_result drfs_delete_native_file(const char* absolutePath) +{ + BOOL wasSuccessful; + + DWORD attributes = GetFileAttributesA(absolutePath); + if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + wasSuccessful = DeleteFileA(absolutePath); + } else { + wasSuccessful = RemoveDirectoryA(absolutePath); + } + + if (wasSuccessful) { + return drfs_success; + } + + return drfs__GetLastError_to_result(); +} + +static drfs_result drfs_mkdir_native(const char* absolutePath) +{ + BOOL wasSuccessful = CreateDirectoryA(absolutePath, NULL); + if (wasSuccessful) { + return drfs_success; + } + + return drfs__GetLastError_to_result(); +} + +static drfs_result drfs_move_native_file(const char* absolutePathOld, const char* absolutePathNew) +{ + BOOL wasSuccessful = MoveFileExA(absolutePathOld, absolutePathNew, 0); + if (wasSuccessful) { + return drfs_success; + } + + return drfs__GetLastError_to_result(); +} + +static drfs_result drfs_copy_native_file(const char* absolutePathSrc, const char* absolutePathDst, dr_bool32 failIfExists) +{ + BOOL wasSuccessful = CopyFileA(absolutePathSrc, absolutePathDst, failIfExists); + if (wasSuccessful) { + return drfs_success; + } + + return drfs__GetLastError_to_result(); +} + +static drfs_result drfs_read_native_file(drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut) +{ + // Unfortunately Win32 expects a DWORD for the number of bytes to read, however we accept size_t. We need to loop to ensure + // we handle data > 4GB correctly. + + size_t totalBytesRead = 0; + + char* pDst = (char*)pDataOut; + dr_uint64 bytesRemaining = bytesToRead; + while (bytesRemaining > 0) + { + DWORD bytesToProcess; + if (bytesRemaining > UINT_MAX) { + bytesToProcess = UINT_MAX; + } else { + bytesToProcess = (DWORD)bytesRemaining; + } + + + DWORD bytesRead; + BOOL wasSuccessful = ReadFile((HANDLE)file, pDst, bytesToProcess, &bytesRead, NULL); + if (!wasSuccessful || bytesRead != bytesToProcess) + { + // We failed to read the chunk. + totalBytesRead += bytesRead; + + if (pBytesReadOut) { + *pBytesReadOut = totalBytesRead; + } + + return drfs__GetLastError_to_result(); + } + + pDst += bytesRead; + bytesRemaining -= bytesRead; + totalBytesRead += bytesRead; + } + + + if (pBytesReadOut != NULL) { + *pBytesReadOut = totalBytesRead; + } + + return drfs_success; +} + +static drfs_result drfs_write_native_file(drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut) +{ + // Unfortunately Win32 expects a DWORD for the number of bytes to write, however we accept size_t. We need to loop to ensure + // we handle data > 4GB correctly. + + size_t totalBytesWritten = 0; + const char* pSrc = (const char*)pData; + + size_t bytesRemaining = bytesToWrite; + while (bytesRemaining > 0) + { + DWORD bytesToProcess; + if (bytesRemaining > UINT_MAX) { + bytesToProcess = UINT_MAX; + } else { + bytesToProcess = (DWORD)bytesRemaining; + } + + DWORD bytesWritten; + BOOL wasSuccessful = WriteFile((HANDLE)file, pSrc, bytesToProcess, &bytesWritten, NULL); + if (!wasSuccessful || bytesWritten != bytesToProcess) + { + // We failed to write the chunk. + totalBytesWritten += bytesWritten; + + if (pBytesWrittenOut) { + *pBytesWrittenOut = totalBytesWritten; + } + + return drfs__GetLastError_to_result(); + } + + pSrc += bytesWritten; + bytesRemaining -= bytesWritten; + totalBytesWritten += bytesWritten; + } + + if (pBytesWrittenOut) { + *pBytesWrittenOut = totalBytesWritten; + } + + return drfs_success;; +} + +static drfs_result drfs_seek_native_file(drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin) +{ + LARGE_INTEGER lNewFilePointer; + LARGE_INTEGER lDistanceToMove; + lDistanceToMove.QuadPart = bytesToSeek; + + DWORD dwMoveMethod = FILE_CURRENT; + if (origin == drfs_origin_start) { + dwMoveMethod = FILE_BEGIN; + } else if (origin == drfs_origin_end) { + dwMoveMethod = FILE_END; + } + + BOOL wasSuccessful = SetFilePointerEx((HANDLE)file, lDistanceToMove, &lNewFilePointer, dwMoveMethod); + if (!wasSuccessful) { + return drfs__GetLastError_to_result(); + } + + return drfs_success; +} + +static dr_uint64 drfs_tell_native_file(drfs_handle file) +{ + LARGE_INTEGER lNewFilePointer; + LARGE_INTEGER lDistanceToMove; + lDistanceToMove.QuadPart = 0; + + if (SetFilePointerEx((HANDLE)file, lDistanceToMove, &lNewFilePointer, FILE_CURRENT)) { + return (dr_uint64)lNewFilePointer.QuadPart; + } + + return 0; +} + +static dr_uint64 drfs_get_native_file_size(drfs_handle file) +{ + LARGE_INTEGER fileSize; + if (GetFileSizeEx((HANDLE)file, &fileSize)) { + return (dr_uint64)fileSize.QuadPart; + } + + return 0; +} + +static void drfs_flush_native_file(drfs_handle file) +{ + FlushFileBuffers((HANDLE)file); +} + +static drfs_result drfs_get_native_file_info(const char* absolutePath, drfs_file_info* fi) +{ + assert(absolutePath != NULL); + + // is allowed to be null, in which case the call is equivalent to simply checking if the file exists. + if (fi == NULL) { + if (GetFileAttributesA(absolutePath) != INVALID_FILE_ATTRIBUTES) { + return drfs_success; + } + + return drfs_does_not_exist; + } + + WIN32_FILE_ATTRIBUTE_DATA fad; + if (GetFileAttributesExA(absolutePath, GetFileExInfoStandard, &fad)) + { + ULARGE_INTEGER liSize; + liSize.LowPart = fad.nFileSizeLow; + liSize.HighPart = fad.nFileSizeHigh; + + ULARGE_INTEGER liTime; + liTime.LowPart = fad.ftLastWriteTime.dwLowDateTime; + liTime.HighPart = fad.ftLastWriteTime.dwHighDateTime; + + strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), absolutePath); + fi->sizeInBytes = liSize.QuadPart; + fi->lastModifiedTime = liTime.QuadPart; + + fi->attributes = 0; + if ((fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { + fi->attributes |= DRFS_FILE_ATTRIBUTE_DIRECTORY; + } + if ((fad.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0) { + fi->attributes |= DRFS_FILE_ATTRIBUTE_READONLY; + } + + return drfs_success; + } + + return drfs__GetLastError_to_result(); +} + +typedef struct +{ + HANDLE hFind; + WIN32_FIND_DATAA ffd; + char directoryPath[1]; +} drfs_iterator_win32; + +static drfs_handle drfs_begin_native_iteration(const char* absolutePath) +{ + assert(drfs_drpath_is_absolute(absolutePath)); + + char searchQuery[DRFS_MAX_PATH]; + if (strcpy_s(searchQuery, sizeof(searchQuery), absolutePath) != 0) { + return NULL; + } + + unsigned int searchQueryLength = (unsigned int)strlen(searchQuery); + if (searchQueryLength >= DRFS_MAX_PATH - 3) { + return NULL; // Path is too long. + } + + searchQuery[searchQueryLength + 0] = '\\'; + searchQuery[searchQueryLength + 1] = '*'; + searchQuery[searchQueryLength + 2] = '\0'; + + drfs_iterator_win32* pIterator = (drfs_iterator_win32*)malloc(sizeof(*pIterator) + searchQueryLength); + if (pIterator == NULL) { + return NULL; + } + + ZeroMemory(pIterator, sizeof(*pIterator)); + + pIterator->hFind = FindFirstFileA(searchQuery, &pIterator->ffd); + if (pIterator->hFind == INVALID_HANDLE_VALUE) { + free(pIterator); + return NULL; // Failed to begin search. + } + + strcpy_s(pIterator->directoryPath, searchQueryLength + 1, absolutePath); // This won't fail in practice. + return (drfs_handle)pIterator; +} + +static void drfs_end_native_iteration(drfs_handle iterator) +{ + drfs_iterator_win32* pIterator = (drfs_iterator_win32*)iterator; + assert(pIterator != NULL); + + if (pIterator->hFind) { + FindClose(pIterator->hFind); + } + + free(pIterator); +} + +static dr_bool32 drfs_next_native_iteration(drfs_handle iterator, drfs_file_info* fi) +{ + drfs_iterator_win32* pIterator = (drfs_iterator_win32*)iterator; + assert(pIterator != NULL); + + if (pIterator->hFind != INVALID_HANDLE_VALUE && pIterator->hFind != NULL) + { + // Skip past "." and ".." directories. + while (strcmp(pIterator->ffd.cFileName, ".") == 0 || strcmp(pIterator->ffd.cFileName, "..") == 0) { + if (FindNextFileA(pIterator->hFind, &pIterator->ffd) == 0) { + return DR_FALSE; + } + } + + if (fi != NULL) + { + // The absolute path actually needs to be set to the relative path. The higher level APIs are the once responsible for translating + // it back to an absolute path. + drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), pIterator->ffd.cFileName); + + ULARGE_INTEGER liSize; + liSize.LowPart = pIterator->ffd.nFileSizeLow; + liSize.HighPart = pIterator->ffd.nFileSizeHigh; + fi->sizeInBytes = liSize.QuadPart; + + ULARGE_INTEGER liTime; + liTime.LowPart = pIterator->ffd.ftLastWriteTime.dwLowDateTime; + liTime.HighPart = pIterator->ffd.ftLastWriteTime.dwHighDateTime; + fi->lastModifiedTime = liTime.QuadPart; + + fi->attributes = 0; + if ((pIterator->ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { + fi->attributes |= DRFS_FILE_ATTRIBUTE_DIRECTORY; + } + if ((pIterator->ffd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0) { + fi->attributes |= DRFS_FILE_ATTRIBUTE_READONLY; + } + } + + if (!FindNextFileA(pIterator->hFind, &pIterator->ffd)) { + FindClose(pIterator->hFind); + pIterator->hFind = NULL; + } + + return DR_TRUE; + } + + return DR_FALSE; +} +#endif //DR_FS_USE_WIN32 + + +#ifdef DR_FS_USE_STDIO +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#endif + +#define DRFS_HANDLE_TO_FD(file) ((int)((size_t)file) - 1) +#define DRFS_FD_TO_HANDLE(fd) ((drfs_handle)(size_t)(fd + 1)) + +static drfs_result drfs__errno_to_result() +{ + switch (errno) + { + case EACCES: return drfs_permission_denied; + case EEXIST: return drfs_already_exists; + case EISDIR: return drfs_permission_denied; + case EMFILE: return drfs_too_many_open_files; + case ENFILE: return drfs_too_many_open_files; + case ENAMETOOLONG: return drfs_path_too_long; + case ENOENT: return drfs_does_not_exist; + case ENOMEM: return drfs_out_of_memory; + case ENOSPC: return drfs_no_space; + case ENOTDIR: return drfs_not_directory; + case EOVERFLOW: return drfs_too_large; + case EROFS: return drfs_permission_denied; + case ETXTBSY: return drfs_permission_denied; + case EBADF: return drfs_invalid_args; + case EINVAL: return drfs_invalid_args; + default: return drfs_unknown_error; + } +} + +static int drfs__open_fd(const char* absolutePath, int flags) +{ + return open64(absolutePath, flags, 0666); +} + +static int drfs__stat64(const char* filename, struct stat64* st) +{ + return stat64(filename, st); +} + +static int drfs__fstat64(int fd, struct stat64* st) +{ + return fstat64(fd, st); +} + +static int drfs__mode_is_dir(int mode) +{ + return S_ISDIR(mode); +} + +static int drfs__mode_has_write_permission(int mode) +{ + return (mode & S_IWUSR) != 0; +} + + +static drfs_result drfs_open_native_file(const char* absolutePath, unsigned int accessMode, drfs_handle* pHandleOut) +{ + assert(absolutePath != NULL); + assert(pHandleOut != NULL); + + *pHandleOut = NULL; + + int flags = 0; + if ((accessMode & DRFS_READ) != 0) { + if ((accessMode & DRFS_WRITE) != 0) { + flags = O_RDWR; + } else { + flags = O_RDONLY; + } + } else { + if ((accessMode & DRFS_WRITE) != 0) { + flags = O_WRONLY; + } else { + return drfs_permission_denied; // Neither read nor write mode was specified. + } + } + + if ((accessMode & DRFS_TRUNCATE) != 0) { + flags |= O_TRUNC; + } + + if ((accessMode & DRFS_EXISTING) == 0) { + flags |= O_CREAT; + } + + int fd = drfs__open_fd(absolutePath, flags); + if (fd == -1) + { + // We failed to open the file, however it could be because the directory structure is not in place. We need to check + // the access mode flags for DRFS_CREATE_DIRS and try creating the directory structure. + if ((accessMode & DRFS_WRITE) != 0 && (accessMode & DRFS_CREATE_DIRS) != 0) + { + char dirAbsolutePath[DRFS_MAX_PATH]; + drfs_drpath_copy_base_path(absolutePath, dirAbsolutePath, sizeof(dirAbsolutePath)); + + if (!drfs_is_native_directory(dirAbsolutePath) && drfs_mkdir_recursive_native(dirAbsolutePath)) { + fd = drfs__open_fd(absolutePath, flags); + } + } + } + + if (fd == -1) { + return drfs__errno_to_result(); + } + + *pHandleOut = DRFS_FD_TO_HANDLE(fd); + return drfs_success; +} + +static void drfs_close_native_file(drfs_handle file) +{ + close(DRFS_HANDLE_TO_FD(file)); +} + +static dr_bool32 drfs_is_native_directory(const char* absolutePath) +{ + struct stat64 info; + if (drfs__stat64(absolutePath, &info) != 0) { + return DR_FALSE; // Likely the folder doesn't exist. + } + + return drfs__mode_is_dir(info.st_mode); // Only return DR_TRUE if it's a directory. Return DR_FALSE if it's a file. +} + +static dr_bool32 drfs_is_native_file(const char* absolutePath) +{ + struct stat64 info; + if (drfs__stat64(absolutePath, &info) != 0) { + return DR_FALSE; // Likely the folder doesn't exist. + } + + return !drfs__mode_is_dir(info.st_mode); // Only return DR_TRUE if it's a file. Return DR_FALSE if it's a directory. +} + +static drfs_result drfs_delete_native_file(const char* absolutePath) +{ + if (remove(absolutePath) == 0) { + return drfs_success; + } + + return drfs__errno_to_result(); +} + +static drfs_result drfs_move_native_file(const char* absolutePathOld, const char* absolutePathNew) +{ + if (rename(absolutePathOld, absolutePathNew) == 0) { + return drfs_success; + } + + return drfs__errno_to_result(); +} + +static drfs_result drfs_mkdir_native(const char* absolutePath) +{ + if (mkdir(absolutePath, 0777) == 0) { + return drfs_success; + } + + return drfs__errno_to_result(); +} + +static drfs_result drfs_copy_native_file(const char* absolutePathSrc, const char* absolutePathDst, dr_bool32 failIfExists) +{ + if (drfs_drpath_equal(absolutePathSrc, absolutePathDst)) { + if (!failIfExists) { + return drfs_success; + } else { + return drfs_already_exists; + } + } + + int fdSrc = drfs__open_fd(absolutePathSrc, O_RDONLY); + if (fdSrc == -1) { + return drfs__errno_to_result(); + } + + int fdDst = drfs__open_fd(absolutePathDst, O_WRONLY | O_TRUNC | O_CREAT | ((failIfExists) ? O_EXCL : 0)); + if (fdDst == -1) { + close(fdSrc); + return drfs__errno_to_result(); + } + + drfs_result result = drfs_success; + + struct stat64 info; + if (drfs__fstat64(fdSrc, &info) == 0) + { +#ifdef __linux__ + ssize_t bytesRemaining = info.st_size; + while (bytesRemaining > 0) { + ssize_t bytesCopied = sendfile(fdDst, fdSrc, NULL, bytesRemaining); + if (bytesCopied == -1) { + result = drfs__errno_to_result(); + break; + } + + bytesRemaining -= bytesCopied; + } +#else + char buffer[BUFSIZ]; + int bytesRead; + while ((bytesRead = read(fdSrc, buffer, sizeof(buffer))) > 0) { + if (write(fdDst, buffer, bytesRead) != bytesRead) { + result = drfs__errno_to_result(); + break; + } + } +#endif + } + else + { + result = drfs__errno_to_result(); + } + + close(fdDst); + close(fdSrc); + + // Permissions. + chmod(absolutePathDst, info.st_mode & 07777); + + return result; +} + +static drfs_result drfs_read_native_file(drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut) +{ + // The documentation for read() states that if the number of bytes being read (bytesToRead) is larger than SSIZE_MAX, + // the result is unspecified. We'll make things a bit more robust by explicitly checking for this and handling it. + char* pDataOut8 = (char*)pDataOut; + drfs_result result = drfs_success; + + size_t totalBytesRead = 0; + while (bytesToRead > 0) + { + ssize_t bytesRead = read(DRFS_HANDLE_TO_FD(file), pDataOut8 + totalBytesRead, (bytesToRead > SSIZE_MAX) ? SSIZE_MAX : bytesToRead); + if (bytesRead == -1) { + result = drfs__errno_to_result(); + break; + } + + if (bytesRead == 0) { + break; // Reached the end. + } + + totalBytesRead += bytesRead; + bytesToRead -= bytesRead; + } + + if (pBytesReadOut) { + *pBytesReadOut = totalBytesRead; + } + + return result; +} + +static drfs_result drfs_write_native_file(drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut) +{ + // We want to handle writes in the same way as we do reads due to the return valid being signed. + const char* pDataIn8 = (const char*)pData; + drfs_result result = drfs_success; + + size_t totalBytesWritten = 0; + while (bytesToWrite > 0) + { + ssize_t bytesWritten = write(DRFS_HANDLE_TO_FD(file), pDataIn8 + totalBytesWritten, (bytesToWrite > SSIZE_MAX) ? SSIZE_MAX : bytesToWrite); + if (bytesWritten == -1) { + result = drfs__errno_to_result(); + break; + } + + if (bytesWritten == 0) { + result = drfs__errno_to_result(); + break; + } + + totalBytesWritten += bytesWritten; + bytesToWrite -= bytesWritten; + } + + if (pBytesWrittenOut != NULL) { + *pBytesWrittenOut = totalBytesWritten; + } + + return result; +} + +static drfs_result drfs_seek_native_file(drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin) +{ + int stdioOrigin = SEEK_CUR; + if (origin == drfs_origin_start) { + stdioOrigin = SEEK_SET; + } else if (origin == drfs_origin_end) { + stdioOrigin = SEEK_END; + } + + if (lseek64(DRFS_HANDLE_TO_FD(file), bytesToSeek, stdioOrigin) == -1) { + return drfs__errno_to_result(); + } + + return drfs_success; +} + +static dr_uint64 drfs_tell_native_file(drfs_handle file) +{ + return lseek64(DRFS_HANDLE_TO_FD(file), 0, SEEK_CUR); +} + +static dr_uint64 drfs_get_native_file_size(drfs_handle file) +{ + struct stat64 info; + if (drfs__fstat64(DRFS_HANDLE_TO_FD(file), &info) != 0) { + return 0; + } + + return info.st_size; +} + +static void drfs_flush_native_file(drfs_handle file) +{ + // The posix implementation does not require flushing. + (void)file; +} + +static drfs_result drfs_get_native_file_info(const char* absolutePath, drfs_file_info* fi) +{ + assert(absolutePath != NULL); + + struct stat64 info; + if (stat64(absolutePath, &info) != 0) { + return drfs__errno_to_result(); + } + + // is allowed to be null, in which case the call is equivalent to simply checking if the file exists. + if (fi != NULL) + { + drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), absolutePath); + fi->sizeInBytes = info.st_size; + fi->lastModifiedTime = info.st_mtime; + + fi->attributes = 0; + if (drfs__mode_is_dir(info.st_mode)) { + fi->attributes |= DRFS_FILE_ATTRIBUTE_DIRECTORY; + } + if (drfs__mode_has_write_permission(info.st_mode) == DR_FALSE) { + fi->attributes |= DRFS_FILE_ATTRIBUTE_READONLY; + } + } + + return drfs_success; +} + +typedef struct +{ + DIR* dir; + char directoryPath[1]; +} drfs_iterator_posix; + +static drfs_handle drfs_begin_native_iteration(const char* absolutePath) +{ + DIR* dir = opendir(absolutePath); + if (dir == NULL) { + return NULL; + } + + drfs_iterator_posix* pIterator = (drfs_iterator_posix*)malloc(sizeof(drfs_iterator_posix) + strlen(absolutePath)); + if (pIterator == NULL) { + return NULL; + } + + pIterator->dir = dir; + drfs__strcpy_s(pIterator->directoryPath, strlen(absolutePath) + 1, absolutePath); + + return pIterator; +} + +static void drfs_end_native_iteration(drfs_handle iterator) +{ + drfs_iterator_posix* pIterator = (drfs_iterator_posix*)iterator; + if (pIterator == NULL) { + return; + } + + closedir(pIterator->dir); + free(pIterator); +} + +static dr_bool32 drfs_next_native_iteration(drfs_handle iterator, drfs_file_info* fi) +{ + drfs_iterator_posix* pIterator = (drfs_iterator_posix*)iterator; + if (pIterator == NULL || pIterator->dir == NULL) { + return DR_FALSE; + } + + struct dirent* info = readdir(pIterator->dir); + if (info == NULL) { + return DR_FALSE; + } + + // Skip over "." and ".." folders. + while (strcmp(info->d_name, ".") == 0 || strcmp(info->d_name, "..") == 0) { + info = readdir(pIterator->dir); + if (info == NULL) { + return DR_FALSE; + } + } + + char fileAbsolutePath[DRFS_MAX_PATH]; + drfs_drpath_copy_and_append(fileAbsolutePath, sizeof(fileAbsolutePath), pIterator->directoryPath, info->d_name); + + if (drfs_get_native_file_info(fileAbsolutePath, fi)) { + drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), info->d_name); + } + + return DR_TRUE; +} +#endif //DR_FS_USE_STDIO + + +static dr_bool32 drfs_mkdir_recursive_native(const char* absolutePath) +{ + char runningPath[DRFS_MAX_PATH]; + runningPath[0] = '\0'; + + drfs_drpath_iterator iPathSeg; + if (!drfs_drpath_first(absolutePath, &iPathSeg)) { + return DR_FALSE; + } + + // Never check the first segment because we can assume it always exists - it will always be the drive root. + if (drfs_drpath_append_iterator(runningPath, sizeof(runningPath), iPathSeg)) + { + // Loop over every directory until we find one that does not exist. + while (drfs_drpath_next(&iPathSeg)) + { + if (!drfs_drpath_append_iterator(runningPath, sizeof(runningPath), iPathSeg)) { + return DR_FALSE; + } + + if (!drfs_is_native_directory(runningPath)) { + if (drfs_mkdir_native(runningPath) != drfs_success) { + return DR_FALSE; + } + + break; + } + } + + + // At this point all we need to do is create the remaining directories - we can assert that the directory does not exist + // rather than actually checking it which should be a bit more efficient. + while (drfs_drpath_next(&iPathSeg)) + { + if (!drfs_drpath_append_iterator(runningPath, sizeof(runningPath), iPathSeg)) { + return DR_FALSE; + } + + assert(!drfs_is_native_directory(runningPath)); + + if (drfs_mkdir_native(runningPath) != drfs_success) { + return DR_FALSE; + } + } + + return DR_TRUE; + } + + return DR_FALSE; +} + + + +//// Native Archive Implementation //// + +typedef struct +{ + // The access mode. + unsigned int accessMode; + + // The absolute path of the directory. + char absolutePath[1]; + +} drfs_archive_native; + +static drfs_result drfs_open_archive__native(drfs_file* pArchiveFile, unsigned int accessMode, drfs_handle* pHandleOut) +{ + (void)pArchiveFile; + (void)accessMode; + + // This function should never be called for native archives. Native archives are a special case because they are just directories + // on the file system rather than actual files, and as such they are handled just slightly differently. This function is only + // included here for this assert so we can ensure we don't incorrectly call this function. + assert(DR_FALSE); + + *pHandleOut = NULL; + return drfs_unknown_error; +} + +static drfs_result drfs_open_archive__native__special(const char* absolutePath, unsigned int accessMode, drfs_handle* pHandleOut) +{ + assert(absolutePath != NULL); // There is no notion of a file for native archives (which are just directories on the file system). + assert(pHandleOut != NULL); + + *pHandleOut = NULL; + + size_t absolutePathLen = strlen(absolutePath); + + drfs_archive_native* pNativeArchive = (drfs_archive_native*)malloc(sizeof(*pNativeArchive) + absolutePathLen + 1); + if (pNativeArchive == NULL) { + return drfs_out_of_memory; + } + + drfs__strcpy_s(pNativeArchive->absolutePath, absolutePathLen + 1, absolutePath); + pNativeArchive->accessMode = accessMode; + + *pHandleOut = (drfs_handle)pNativeArchive; + return drfs_success; +} + +static void drfs_close_archive__native(drfs_handle archive) +{ + // Nothing to do except free the object. + free(archive); +} + +static drfs_result drfs_get_file_info__native(drfs_handle archive, const char* relativePath, drfs_file_info* fi) +{ + drfs_archive_native* pNativeArchive = (drfs_archive_native*)archive; + assert(pNativeArchive != NULL); + + char absolutePath[DRFS_MAX_PATH]; + if (!drfs_drpath_copy_and_append(absolutePath, sizeof(absolutePath), pNativeArchive->absolutePath, relativePath)) { + return drfs_path_too_long; + } + + if (fi != NULL) { + memset(fi, 0, sizeof(*fi)); + } + + return drfs_get_native_file_info(absolutePath, fi); +} + +static drfs_handle drfs_begin_iteration__native(drfs_handle archive, const char* relativePath) +{ + drfs_archive_native* pNativeArchive = (drfs_archive_native*)archive; + assert(pNativeArchive != NULL); + + char absolutePath[DRFS_MAX_PATH]; + if (!drfs_drpath_copy_and_append(absolutePath, sizeof(absolutePath), pNativeArchive->absolutePath, relativePath)) { + return NULL; + } + + return drfs_begin_native_iteration(absolutePath); +} + +static void drfs_end_iteration__native(drfs_handle archive, drfs_handle iterator) +{ + (void)archive; + assert(archive != NULL); + assert(iterator != NULL); + + drfs_end_native_iteration(iterator); +} + +static dr_bool32 drfs_next_iteration__native(drfs_handle archive, drfs_handle iterator, drfs_file_info* fi) +{ + (void)archive; + assert(archive != NULL); + assert(iterator != NULL); + + return drfs_next_native_iteration(iterator, fi); +} + +static drfs_result drfs_delete_file__native(drfs_handle archive, const char* relativePath) +{ + assert(relativePath != NULL); + + drfs_archive_native* pNativeArchive = (drfs_archive_native*)archive; + assert(pNativeArchive != NULL); + + char absolutePath[DRFS_MAX_PATH]; + if (!drfs_drpath_copy_and_append(absolutePath, sizeof(absolutePath), pNativeArchive->absolutePath, relativePath)) { + return drfs_path_too_long; + } + + return drfs_delete_native_file(absolutePath); +} + +static drfs_result drfs_move_file__native(drfs_handle archive, const char* relativePathOld, const char* relativePathNew) +{ + assert(relativePathOld != NULL); + assert(relativePathNew != NULL); + + drfs_archive_native* pNativeArchive = (drfs_archive_native*)archive; + assert(pNativeArchive != NULL); + + char absolutePathOld[DRFS_MAX_PATH]; + if (!drfs_drpath_copy_and_append(absolutePathOld, sizeof(absolutePathOld), pNativeArchive->absolutePath, relativePathOld)) { + return drfs_path_too_long; + } + + char absolutePathNew[DRFS_MAX_PATH]; + if (!drfs_drpath_copy_and_append(absolutePathNew, sizeof(absolutePathNew), pNativeArchive->absolutePath, relativePathNew)) { + return drfs_path_too_long; + } + + return drfs_move_native_file(absolutePathOld, absolutePathNew); +} + +static drfs_result drfs_create_directory__native(drfs_handle archive, const char* relativePath) +{ + assert(relativePath != NULL); + + drfs_archive_native* pNativeArchive = (drfs_archive_native*)archive; + assert(pNativeArchive != NULL); + + char absolutePath[DRFS_MAX_PATH]; + if (!drfs_drpath_copy_and_append(absolutePath, sizeof(absolutePath), pNativeArchive->absolutePath, relativePath)) { + return drfs_path_too_long; + } + + return drfs_mkdir_native(absolutePath); +} + +static drfs_result drfs_copy_file__native(drfs_handle archive, const char* relativePathSrc, const char* relativePathDst, dr_bool32 failIfExists) +{ + assert(relativePathSrc != NULL); + assert(relativePathDst != NULL); + + drfs_archive_native* pNativeArchive = (drfs_archive_native*)archive; + assert(pNativeArchive != NULL); + + char absolutePathSrc[DRFS_MAX_PATH]; + if (!drfs_drpath_copy_and_append(absolutePathSrc, sizeof(absolutePathSrc), pNativeArchive->absolutePath, relativePathSrc)) { + return drfs_path_too_long; + } + + char absolutePathDst[DRFS_MAX_PATH]; + if (!drfs_drpath_copy_and_append(absolutePathDst, sizeof(absolutePathDst), pNativeArchive->absolutePath, relativePathDst)) { + return drfs_path_too_long; + } + + return drfs_copy_native_file(absolutePathSrc, absolutePathDst, failIfExists); +} + +static drfs_result drfs_open_file__native(drfs_handle archive, const char* relativePath, unsigned int accessMode, drfs_handle* pHandleOut) +{ + assert(archive != NULL); + assert(relativePath != NULL); + + drfs_archive_native* pNativeArchive = (drfs_archive_native*)archive; + assert(pNativeArchive != NULL); + + char absolutePath[DRFS_MAX_PATH]; + if (drfs_drpath_copy_and_append(absolutePath, sizeof(absolutePath), pNativeArchive->absolutePath, relativePath)) { + return drfs_open_native_file(absolutePath, accessMode, pHandleOut); + } + + return drfs_path_too_long; +} + +static void drfs_close_file__native(drfs_handle archive, drfs_handle file) +{ + (void)archive; + assert(archive != NULL); + assert(file != NULL); + + drfs_close_native_file(file); +} + +static drfs_result drfs_read_file__native(drfs_handle archive, drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut) +{ + (void)archive; + assert(archive != NULL); + assert(file != NULL); + + return drfs_read_native_file(file, pDataOut, bytesToRead, pBytesReadOut); +} + +static drfs_result drfs_write_file__native(drfs_handle archive, drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut) +{ + (void)archive; + assert(archive != NULL); + assert(file != NULL); + + return drfs_write_native_file(file, pData, bytesToWrite, pBytesWrittenOut); +} + +static drfs_result drfs_seek_file__native(drfs_handle archive, drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin) +{ + (void)archive; + assert(archive != NULL); + assert(file != NULL); + + return drfs_seek_native_file(file, bytesToSeek, origin); +} + +static dr_uint64 drfs_tell_file__native(drfs_handle archive, drfs_handle file) +{ + (void)archive; + assert(archive != NULL); + assert(file != NULL); + + return drfs_tell_native_file(file); +} + +static dr_uint64 drfs_file_size__native(drfs_handle archive, drfs_handle file) +{ + (void)archive; + assert(archive != NULL); + assert(file != NULL); + + return drfs_get_native_file_size(file); +} + +static void drfs_flush__native(drfs_handle archive, drfs_handle file) +{ + (void)archive; + assert(archive != NULL); + assert(file != NULL); + + drfs_flush_native_file(file); +} + + +// Finds the back-end callbacks by the given extension. +static dr_bool32 drfs_find_backend_by_extension(drfs_context* pContext, const char* extension, drfs_archive_callbacks* pCallbacksOut) +{ + if (pContext == NULL || extension == NULL || extension[0] == '\0') { + return DR_FALSE; + } + + for (unsigned int i = 0; i < pContext->archiveCallbacks.count; ++i) + { + if (pContext->archiveCallbacks.pBuffer[i].is_valid_extension) { + if (pContext->archiveCallbacks.pBuffer[i].is_valid_extension(extension)) { + if (pCallbacksOut) { + *pCallbacksOut = pContext->archiveCallbacks.pBuffer[i]; + } + return DR_TRUE; + } + } + } + + return DR_FALSE; +} + +// Recursively claims ownership of parent archives so that when the child archive is deleted, so are it's parents. +static void drfs_recursively_claim_ownership_or_parent_archive(drfs_archive* pArchive) +{ + if (pArchive == NULL) { + return; + } + + pArchive->flags |= DR_FS_OWNS_PARENT_ARCHIVE; + drfs_recursively_claim_ownership_or_parent_archive(pArchive->pParentArchive); +} + +// Opens a native archive. +static drfs_result drfs_open_native_archive(drfs_context* pContext, const char* absolutePath, unsigned int accessMode, drfs_archive** ppArchive) +{ + assert(pContext != NULL); + assert(ppArchive != NULL); + assert(absolutePath != NULL); + + *ppArchive = NULL; + + drfs_handle internalArchiveHandle; + drfs_result result = drfs_open_archive__native__special(absolutePath, accessMode, &internalArchiveHandle); + if (result != drfs_success) { + return result; + } + + drfs_archive* pArchive = (drfs_archive*)malloc(sizeof(*pArchive)); + if (pArchive == NULL) { + drfs_close_archive__native(internalArchiveHandle); + return drfs_out_of_memory; + } + + pArchive->pContext = pContext; + pArchive->pParentArchive = NULL; + pArchive->pFile = NULL; + pArchive->internalArchiveHandle = internalArchiveHandle; + pArchive->flags = 0; + pArchive->callbacks.is_valid_extension = NULL; + pArchive->callbacks.open_archive = drfs_open_archive__native; + pArchive->callbacks.close_archive = drfs_close_archive__native; + pArchive->callbacks.get_file_info = drfs_get_file_info__native; + pArchive->callbacks.begin_iteration = drfs_begin_iteration__native; + pArchive->callbacks.end_iteration = drfs_end_iteration__native; + pArchive->callbacks.next_iteration = drfs_next_iteration__native; + pArchive->callbacks.delete_file = drfs_delete_file__native; + pArchive->callbacks.create_directory = drfs_create_directory__native; + pArchive->callbacks.move_file = drfs_move_file__native; + pArchive->callbacks.copy_file = drfs_copy_file__native; + pArchive->callbacks.open_file = drfs_open_file__native; + pArchive->callbacks.close_file = drfs_close_file__native; + pArchive->callbacks.read_file = drfs_read_file__native; + pArchive->callbacks.write_file = drfs_write_file__native; + pArchive->callbacks.seek_file = drfs_seek_file__native; + pArchive->callbacks.tell_file = drfs_tell_file__native; + pArchive->callbacks.file_size = drfs_file_size__native; + pArchive->callbacks.flush_file = drfs_flush__native; + drfs__strcpy_s(pArchive->absolutePath, sizeof(pArchive->absolutePath), absolutePath); + + *ppArchive = pArchive; + return drfs_success; +} + +// Opens an archive from a file and callbacks. +static drfs_result drfs_open_non_native_archive(drfs_archive* pParentArchive, drfs_file* pArchiveFile, drfs_archive_callbacks* pBackEndCallbacks, const char* relativePath, unsigned int accessMode, drfs_archive** ppArchiveOut) +{ + assert(pParentArchive != NULL); + assert(pArchiveFile != NULL); + assert(pBackEndCallbacks != NULL); + assert(ppArchiveOut != NULL); + + *ppArchiveOut = NULL; + + if (pBackEndCallbacks->open_archive == NULL) { + return drfs_no_backend; + } + + drfs_handle internalArchiveHandle; + drfs_result result = pBackEndCallbacks->open_archive(pArchiveFile, accessMode, &internalArchiveHandle); + if (result != drfs_success) { + return result; + } + + drfs_archive* pArchive = (drfs_archive*)malloc(sizeof(*pArchive)); + if (pArchive == NULL) { + pBackEndCallbacks->close_archive(internalArchiveHandle); + return drfs_out_of_memory; + } + + pArchive->pContext = pParentArchive->pContext; + pArchive->pParentArchive = pParentArchive; + pArchive->pFile = pArchiveFile; + pArchive->internalArchiveHandle = internalArchiveHandle; + pArchive->flags = 0; + pArchive->callbacks = *pBackEndCallbacks; + drfs_drpath_copy_and_append(pArchive->absolutePath, sizeof(pArchive->absolutePath), pParentArchive->absolutePath, relativePath); + + *ppArchiveOut = pArchive; + return drfs_success; +} + +// Attempts to open an archive from another archive. +static drfs_result drfs_open_non_native_archive_from_path(drfs_archive* pParentArchive, const char* relativePath, unsigned int accessMode, drfs_archive** ppArchiveOut) +{ + assert(pParentArchive != NULL); + assert(ppArchiveOut != NULL); + assert(relativePath != NULL); + + *ppArchiveOut = NULL; + + drfs_archive_callbacks backendCallbacks; + if (!drfs_find_backend_by_extension(pParentArchive->pContext, drfs_drpath_extension(relativePath), &backendCallbacks)) { + return drfs_no_backend; + } + + drfs_file* pArchiveFile; + drfs_result result = drfs_open_file_from_archive(pParentArchive, relativePath, accessMode, &pArchiveFile); + if (result != drfs_success) { + return result; + } + + drfs_archive* pArchive; + result = drfs_open_non_native_archive(pParentArchive, pArchiveFile, &backendCallbacks, relativePath, accessMode, &pArchive); + if (pArchive == NULL) { + return result; + } + + *ppArchiveOut = pArchive; + return drfs_success; +} + + +// Recursively opens the archive that owns the file at the given verbose path. +static drfs_result drfs_open_owner_archive_recursively_from_verbose_path(drfs_archive* pParentArchive, const char* relativePath, unsigned int accessMode, char* relativePathOut, size_t relativePathOutSize, drfs_archive** ppArchiveOut) +{ + assert(pParentArchive != NULL); + assert(relativePath != NULL); + assert(ppArchiveOut != NULL); + + *ppArchiveOut = NULL; + + if (pParentArchive->callbacks.get_file_info == NULL) { + return drfs_no_backend; + } + + drfs_file_info fi; + if (pParentArchive->callbacks.get_file_info(pParentArchive->internalArchiveHandle, relativePath, &fi) == drfs_success) + { + // The file is in this archive. + drfs__strcpy_s(relativePathOut, relativePathOutSize, relativePath); + *ppArchiveOut = pParentArchive; + return drfs_success; + } + else + { + char runningPath[DRFS_MAX_PATH]; + runningPath[0] = '\0'; + + drfs_drpath_iterator segment; + if (drfs_drpath_first(relativePath, &segment)) + { + do + { + if (!drfs_drpath_append_iterator(runningPath, sizeof(runningPath), segment)) { + return drfs_path_too_long; + } + + if (pParentArchive->callbacks.get_file_info(pParentArchive->internalArchiveHandle, runningPath, &fi) == drfs_success) + { + if ((fi.attributes & DRFS_FILE_ATTRIBUTE_DIRECTORY) == 0) + { + // The running path points to an actual file. It could be a sub-archive. + drfs_archive_callbacks backendCallbacks; + if (drfs_find_backend_by_extension(pParentArchive->pContext, drfs_drpath_extension(runningPath), &backendCallbacks)) + { + drfs_file* pNextArchiveFile; + drfs_open_file_from_archive(pParentArchive, runningPath, accessMode, &pNextArchiveFile); + if (pNextArchiveFile == NULL) { + break; // Failed to open the archive file. + } + + drfs_archive* pNextArchive; + drfs_open_non_native_archive(pParentArchive, pNextArchiveFile, &backendCallbacks, runningPath, accessMode, &pNextArchive); + if (pNextArchive == NULL) { + drfs_close(pNextArchiveFile); + break; + } + + // At this point we should have an archive. We now need to call this function recursively if there are any segments left. + drfs_drpath_iterator nextsegment = segment; + if (drfs_drpath_next(&nextsegment)) + { + drfs_archive* pOwnerArchive; + drfs_open_owner_archive_recursively_from_verbose_path(pNextArchive, nextsegment.path + nextsegment.segment.offset, accessMode, relativePathOut, relativePathOutSize, &pOwnerArchive); + if (pOwnerArchive == NULL) { + drfs_close_archive(pNextArchive); + break; + } + + *ppArchiveOut = pOwnerArchive; + return drfs_success; + } + else + { + // We reached the end of the path. If we get here it means the file doesn't exist, because otherwise we would have caught it in the check right at the top. + drfs_close_archive(pNextArchive); + break; + } + } + } + } + } while (drfs_drpath_next(&segment)); + } + + // The running path is not part of this archive. + // NOTE: Is this the correct return value we should be using? Should we be returning an error and setting the output archive to NULL? + drfs__strcpy_s(relativePathOut, relativePathOutSize, relativePath); + *ppArchiveOut = pParentArchive; + return drfs_does_not_exist; + } +} + +// Opens the archive that owns the file at the given verbose path. +static drfs_result drfs_open_owner_archive_from_absolute_path(drfs_context* pContext, const char* absolutePath, unsigned int accessMode, char* relativePathOut, size_t relativePathOutSize, drfs_archive** ppArchiveOut) +{ + assert(pContext != NULL); + assert(absolutePath != NULL); + assert(ppArchiveOut != NULL); + + *ppArchiveOut = NULL; + + // We are currently assuming absolute path's are verbose. This means we don't need to do any searching on the file system. We just + // move through the path and look for a segment with an extension matching one of the registered back-ends. + + char runningPath[DRFS_MAX_PATH]; + runningPath[0] = '\0'; + + if (absolutePath[0] == '/') { + runningPath[0] = '/'; + runningPath[1] = '\0'; + } + + drfs_drpath_iterator segment; + if (drfs_drpath_first(absolutePath, &segment)) + { + do + { + if (!drfs_drpath_append_iterator(runningPath, sizeof(runningPath), segment)) { + return drfs_path_too_long; + } + + if (!drfs_is_native_directory(runningPath)) + { + char dirAbsolutePath[DRFS_MAX_PATH]; + drfs_drpath_copy_base_path(runningPath, dirAbsolutePath, sizeof(dirAbsolutePath)); + + drfs_archive* pNativeArchive; + drfs_result result = drfs_open_native_archive(pContext, dirAbsolutePath, accessMode, &pNativeArchive); + if (result != drfs_success) { + return result; // Failed to open the native archive. + } + + + // The running path is not a native directory. It could be an archive file. + drfs_archive_callbacks backendCallbacks; + if (drfs_find_backend_by_extension(pContext, drfs_drpath_extension(runningPath), &backendCallbacks)) + { + // The running path refers to an archive file. We need to try opening the archive here. If this fails, we + // need to return NULL. + drfs_archive* pArchive; + result = drfs_open_owner_archive_recursively_from_verbose_path(pNativeArchive, segment.path + segment.segment.offset, accessMode, relativePathOut, relativePathOutSize, &pArchive); + if (pArchive == NULL) { + drfs_close_archive(pNativeArchive); + return result; + } + + *ppArchiveOut = pArchive; + return drfs_success; + } + else + { + drfs__strcpy_s(relativePathOut, relativePathOutSize, segment.path + segment.segment.offset); + *ppArchiveOut = pNativeArchive; + return drfs_success; + } + } + + } while (drfs_drpath_next(&segment)); + } + + return drfs_does_not_exist; +} + +// Recursively opens the archive that owns the file specified by the given relative path. +static drfs_result drfs_open_owner_archive_recursively_from_relative_path(drfs_archive* pParentArchive, const char* rootSearchPath, const char* relativePath, unsigned int accessMode, char* relativePathOut, size_t relativePathOutSize, drfs_archive** ppArchiveOut) +{ + assert(pParentArchive != NULL); + assert(relativePath != NULL); + assert(ppArchiveOut != NULL); + + *ppArchiveOut = NULL; + + if (pParentArchive->callbacks.get_file_info == NULL) { + return drfs_no_backend; + } + + // Always try the direct route by checking if the file exists within the archive first. + drfs_file_info fi; + if (pParentArchive->callbacks.get_file_info(pParentArchive->internalArchiveHandle, relativePath, &fi) == drfs_success) + { + // The file is in this archive. + drfs__strcpy_s(relativePathOut, relativePathOutSize, relativePath); + *ppArchiveOut = pParentArchive; + return drfs_success; + } + else + { + // The file does not exist within this archive. We need to search the parent archive recursively. We never search above + // the path specified by rootSearchPath. + char runningPath[DRFS_MAX_PATH]; + drfs__strcpy_s(runningPath, sizeof(runningPath), rootSearchPath); + + // Part of rootSearchPath and relativePath will be overlapping. We want to begin searching at the non-overlapping section. + drfs_drpath_iterator pathSeg0; + drfs_drpath_iterator pathSeg1; + dr_bool32 isSeg0Valid = drfs_drpath_first(rootSearchPath, &pathSeg0); + dr_bool32 isSeg1Valid = drfs_drpath_first(relativePath, &pathSeg1); + while (isSeg0Valid && isSeg1Valid) { + isSeg0Valid = drfs_drpath_next(&pathSeg0); + isSeg1Valid = drfs_drpath_next(&pathSeg1); + } + + relativePath = pathSeg1.path + pathSeg1.segment.offset; + + // Searching works as such: + // For each segment in "relativePath" + // If segment is archive then + // Open and search archive + // Else + // Search each archive file in this directory + // Endif + drfs_drpath_iterator pathseg; + if (drfs_drpath_first(relativePath, &pathseg)) + { + do + { + char runningPathBase[DRFS_MAX_PATH]; + drfs__strcpy_s(runningPathBase, sizeof(runningPathBase), runningPath); + + if (!drfs_drpath_append_iterator(runningPath, sizeof(runningPath), pathseg)) { + return drfs_path_too_long; + } + + drfs_archive* pNextArchive; + drfs_open_non_native_archive_from_path(pParentArchive, runningPath, accessMode, &pNextArchive); + if (pNextArchive != NULL) + { + // It's an archive segment. We need to check this archive recursively, starting from the next segment. + drfs_drpath_iterator nextseg = pathseg; + if (!drfs_drpath_next(&nextseg)) { + // Should never actually get here, but if we do it means we've reached the end of the path. Assume the file could not be found. + drfs_close_archive(pNextArchive); + return drfs_unknown_error; + } + + drfs_archive* pOwnerArchive; + drfs_open_owner_archive_recursively_from_relative_path(pNextArchive, "", nextseg.path + nextseg.segment.offset, accessMode, relativePathOut, relativePathOutSize, &pOwnerArchive); + if (pOwnerArchive == NULL) { + drfs_close_archive(pNextArchive); + return drfs_does_not_exist; + } + + *ppArchiveOut = pOwnerArchive; + return drfs_success; + } + else + { + // It's not an archive segment. We need to search. + if (pParentArchive->callbacks.begin_iteration == NULL || pParentArchive->callbacks.next_iteration == NULL || pParentArchive->callbacks.end_iteration == NULL) { + return drfs_no_backend; + } + + drfs_handle iterator = pParentArchive->callbacks.begin_iteration(pParentArchive->internalArchiveHandle, runningPathBase); + if (iterator == NULL) { + return drfs_no_backend; + } + + while (pParentArchive->callbacks.next_iteration(pParentArchive->internalArchiveHandle, iterator, &fi)) + { + if ((fi.attributes & DRFS_FILE_ATTRIBUTE_DIRECTORY) == 0) + { + // It's a file which means it could be an archive. Note that fi.absolutePath is actually relative to the parent archive. + drfs_open_non_native_archive_from_path(pParentArchive, fi.absolutePath, accessMode, &pNextArchive); + if (pNextArchive != NULL) + { + // It's an archive, so check it. + drfs_archive* pOwnerArchive; + drfs_open_owner_archive_recursively_from_relative_path(pNextArchive, "", pathseg.path + pathseg.segment.offset, accessMode, relativePathOut, relativePathOutSize, &pOwnerArchive); + if (pOwnerArchive != NULL) { + pParentArchive->callbacks.end_iteration(pParentArchive->internalArchiveHandle, iterator); + *ppArchiveOut = pOwnerArchive; + return drfs_success; + } else { + drfs_close_archive(pNextArchive); + } + } + } + } + + pParentArchive->callbacks.end_iteration(pParentArchive->internalArchiveHandle, iterator); + } + + } while (drfs_drpath_next(&pathseg)); + } + } + + return drfs_does_not_exist; +} + +// Opens the archive that owns the file at the given path that's relative to the given base path. This supports non-verbose paths and will search +// the file system for the archive. +static drfs_result drfs_open_owner_archive_from_relative_path(drfs_context* pContext, const char* absoluteBasePath, const char* relativePath, unsigned int accessMode, char* relativePathOut, size_t relativePathOutSize, drfs_archive** ppArchiveOut) +{ + assert(pContext != NULL); + assert(absoluteBasePath != NULL); + assert(relativePath != NULL); + assert(drfs_drpath_is_absolute(absoluteBasePath)); + assert(ppArchiveOut != NULL); + + *ppArchiveOut = NULL; + + char adjustedRelativePath[DRFS_MAX_PATH]; + char relativeBasePath[DRFS_MAX_PATH]; + relativeBasePath[0] = '\0'; + + // The base path should be absolute and verbose. It does not need to be an actual directory. + drfs_archive* pBaseArchive = NULL; + if (drfs_is_native_directory(absoluteBasePath)) + { + drfs_result result = drfs_open_native_archive(pContext, absoluteBasePath, accessMode, &pBaseArchive); + if (result != drfs_success) { + return result; + } + + if (drfs__strcpy_s(adjustedRelativePath, sizeof(adjustedRelativePath), relativePath) != 0) { + drfs_close_archive(pBaseArchive); + return drfs_path_too_long; + } + } + else + { + drfs_result result = drfs_open_owner_archive(pContext, absoluteBasePath, accessMode, relativeBasePath, sizeof(relativeBasePath), &pBaseArchive); + if (result != drfs_success) { + return result; + } + + if (!drfs_drpath_copy_and_append(adjustedRelativePath, sizeof(adjustedRelativePath), relativeBasePath, relativePath)) { + drfs_close_archive(pBaseArchive); + return drfs_path_too_long; + } + } + + + // We couldn't open the archive file from here, so we'll need to search for the owner archive recursively. + drfs_archive* pOwnerArchive; + drfs_result result = drfs_open_owner_archive_recursively_from_relative_path(pBaseArchive, relativeBasePath, adjustedRelativePath, accessMode, relativePathOut, relativePathOutSize, &pOwnerArchive); + if (pOwnerArchive == NULL) { + return result; + } + + drfs_recursively_claim_ownership_or_parent_archive(pOwnerArchive); + + *ppArchiveOut = pOwnerArchive; + return drfs_success; +} + +// Opens an archive from a path that's relative to the given base path. This supports non-verbose paths and will search +// the file system for the archive. +static drfs_result drfs_open_archive_from_relative_path(drfs_context* pContext, const char* absoluteBasePath, const char* relativePath, unsigned int accessMode, drfs_archive** ppArchiveOut) +{ + assert(pContext != NULL); + assert(absoluteBasePath != NULL); + assert(relativePath != NULL); + assert(drfs_drpath_is_absolute(absoluteBasePath)); + assert(ppArchiveOut != NULL); + + *ppArchiveOut = NULL; + + char adjustedRelativePath[DRFS_MAX_PATH]; + char relativeBasePath[DRFS_MAX_PATH]; + relativeBasePath[0] = '\0'; + + // The base path should be absolute and verbose. It does not need to be an actual directory. + drfs_archive* pBaseArchive = NULL; + if (drfs_is_native_directory(absoluteBasePath)) + { + drfs_result result = drfs_open_native_archive(pContext, absoluteBasePath, accessMode, &pBaseArchive); + if (result != drfs_success) { + return result; + } + + if (drfs__strcpy_s(adjustedRelativePath, sizeof(adjustedRelativePath), relativePath) != 0) { + drfs_close_archive(pBaseArchive); + return drfs_path_too_long; + } + } + else + { + drfs_result result = drfs_open_owner_archive(pContext, absoluteBasePath, accessMode, relativeBasePath, sizeof(relativeBasePath), &pBaseArchive); + if (result != drfs_success) { + return result; + } + + if (!drfs_drpath_copy_and_append(adjustedRelativePath, sizeof(adjustedRelativePath), relativeBasePath, relativePath)) { + drfs_close_archive(pBaseArchive); + return drfs_path_too_long; + } + } + + + // We can try opening the file directly from the base path. If this doesn't work, we can try recursively opening the owner archive + // an open from there. + drfs_archive* pArchive; + drfs_result result = drfs_open_non_native_archive_from_path(pBaseArchive, adjustedRelativePath, accessMode, &pArchive); + if (pArchive == NULL) + { + // We couldn't open the archive file from here, so we'll need to search for the owner archive recursively. + char archiveRelativePath[DRFS_MAX_PATH]; + drfs_archive* pOwnerArchive; + result = drfs_open_owner_archive_recursively_from_relative_path(pBaseArchive, relativeBasePath, adjustedRelativePath, accessMode, archiveRelativePath, sizeof(archiveRelativePath), &pOwnerArchive); + if (pOwnerArchive != NULL) + { + drfs_recursively_claim_ownership_or_parent_archive(pOwnerArchive); + + result = drfs_open_non_native_archive_from_path(pOwnerArchive, archiveRelativePath, accessMode, &pArchive); + if (pArchive == NULL) { + drfs_close_archive(pOwnerArchive); + return result; + } + } + } + + if (pArchive == NULL) { + drfs_close_archive(pBaseArchive); + return drfs_does_not_exist; + } + + *ppArchiveOut = pArchive; + return drfs_success; +} + +//// Back-End Registration //// + +#ifndef DR_FS_NO_ZIP +// Registers the archive callbacks which enables support for Zip files. +static void drfs_register_zip_backend(drfs_context* pContext); +#endif + +#ifndef DR_FS_NO_PAK +// Registers the archive callbacks which enables support for Quake 2 pak files. +static void drfs_register_pak_backend(drfs_context* pContext); +#endif + +#ifndef DR_FS_NO_MTL +// Registers the archive callbacks which enables support for Quake 2 pak files. +static void drfs_register_mtl_backend(drfs_context* pContext); +#endif + + + +//// Public API Implementation //// + +drfs_result drfs_init(drfs_context* pContext) +{ + if (pContext == NULL) return drfs_invalid_args; + memset(pContext, 0, sizeof(*pContext)); + + if (!drfs_callbacklist_init(&pContext->archiveCallbacks) || !drfs_basedirs_init(&pContext->baseDirectories)) { + return drfs_unknown_error; + } + + pContext->isWriteGuardEnabled = 0; + +#ifndef DR_FS_NO_ZIP + drfs_register_zip_backend(pContext); +#endif + +#ifndef DR_FS_NO_PAK + drfs_register_pak_backend(pContext); +#endif + +#ifndef DR_FS_NO_MTL + drfs_register_mtl_backend(pContext); +#endif + + return drfs_success; +} + +drfs_result drfs_uninit(drfs_context* pContext) +{ + if (pContext == NULL) return drfs_invalid_args; + + drfs_basedirs_uninit(&pContext->baseDirectories); + drfs_callbacklist_uninit(&pContext->archiveCallbacks); + return drfs_success; +} + + +drfs_context* drfs_create_context() +{ + drfs_context* pContext = (drfs_context*)malloc(sizeof(*pContext)); + if (pContext == NULL) { + return NULL; + } + + if (drfs_init(pContext) != drfs_success) { + free(pContext); + return NULL; + } + + return pContext; +} + +void drfs_delete_context(drfs_context* pContext) +{ + if (pContext == NULL) { + return; + } + + drfs_uninit(pContext); + free(pContext); +} + + +void drfs_register_archive_backend(drfs_context* pContext, drfs_archive_callbacks callbacks) +{ + if (pContext == NULL) { + return; + } + + drfs_callbacklist_pushback(&pContext->archiveCallbacks, callbacks); +} + + +void drfs_insert_base_directory(drfs_context* pContext, const char* absolutePath, unsigned int index) +{ + if (pContext == NULL) { + return; + } + + drfs_basedirs_insert(&pContext->baseDirectories, absolutePath, index); +} + +void drfs_add_base_directory(drfs_context* pContext, const char* absolutePath) +{ + if (pContext == NULL) { + return; + } + + drfs_insert_base_directory(pContext, absolutePath, drfs_get_base_directory_count(pContext)); +} + +void drfs_remove_base_directory(drfs_context* pContext, const char* absolutePath) +{ + if (pContext == NULL) { + return; + } + + for (unsigned int iPath = 0; iPath < pContext->baseDirectories.count; /*DO NOTHING*/) { + if (drfs_drpath_equal(pContext->baseDirectories.pBuffer[iPath].absolutePath, absolutePath)) { + drfs_basedirs_remove(&pContext->baseDirectories, iPath); + } else { + ++iPath; + } + } +} + +void drfs_remove_base_directory_by_index(drfs_context* pContext, unsigned int index) +{ + if (pContext == NULL) { + return; + } + + drfs_basedirs_remove(&pContext->baseDirectories, index); +} + +void drfs_remove_all_base_directories(drfs_context* pContext) +{ + if (pContext == NULL) { + return; + } + + drfs_basedirs_clear(&pContext->baseDirectories); +} + +unsigned int drfs_get_base_directory_count(drfs_context* pContext) +{ + if (pContext == NULL) { + return 0; + } + + return pContext->baseDirectories.count; +} + +const char* drfs_get_base_directory_by_index(drfs_context* pContext, unsigned int index) +{ + if (pContext == NULL || index >= pContext->baseDirectories.count) { + return NULL; + } + + return pContext->baseDirectories.pBuffer[index].absolutePath; +} + + +void drfs_set_base_write_directory(drfs_context* pContext, const char* absolutePath) +{ + if (pContext == NULL) { + return; + } + + if (absolutePath == NULL) { + memset(pContext->writeBaseDirectory, 0, sizeof(pContext->writeBaseDirectory)); + } else { + drfs__strcpy_s(pContext->writeBaseDirectory, sizeof(pContext->writeBaseDirectory), absolutePath); + } +} + +dr_bool32 drfs_get_base_write_directory(drfs_context* pContext, char* absolutePathOut, unsigned int absolutePathOutSize) +{ + if (pContext == NULL || absolutePathOut == NULL || absolutePathOutSize == 0) { + return DR_FALSE; + } + + return drfs__strcpy_s(absolutePathOut, absolutePathOutSize, pContext->writeBaseDirectory) == 0; +} + +void drfs_enable_write_directory_guard(drfs_context* pContext) +{ + if (pContext == NULL) { + return; + } + + pContext->isWriteGuardEnabled = DR_TRUE; +} + +void drfs_disable_write_directory_guard(drfs_context* pContext) +{ + if (pContext == NULL) { + return; + } + + pContext->isWriteGuardEnabled = DR_FALSE; +} + +dr_bool32 drfs_is_write_directory_guard_enabled(drfs_context* pContext) +{ + if (pContext == NULL) { + return DR_FALSE; + } + + return pContext->isWriteGuardEnabled; +} + + + + +drfs_result drfs_open_archive(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, drfs_archive** ppArchiveOut) +{ + if (ppArchiveOut == NULL) { + return drfs_invalid_args; + } + + *ppArchiveOut = NULL; + + if (pContext == NULL || absoluteOrRelativePath == NULL) { + return drfs_invalid_args; + } + + if (drfs_drpath_is_absolute(absoluteOrRelativePath)) + { + if (drfs_is_native_directory(absoluteOrRelativePath)) + { + // It's a native directory. + return drfs_open_native_archive(pContext, absoluteOrRelativePath, accessMode, ppArchiveOut); + } + else + { + // It's not a native directory. We can just use drfs_open_owner_archive() in this case. + char relativePath[DRFS_MAX_PATH]; + drfs_archive* pOwnerArchive; + drfs_result result = drfs_open_owner_archive(pContext, absoluteOrRelativePath, accessMode, relativePath, sizeof(relativePath), &pOwnerArchive); + if (result != drfs_success) { + return result; + } + + drfs_archive* pArchive; + result = drfs_open_non_native_archive_from_path(pOwnerArchive, relativePath, accessMode, &pArchive); + if (result != drfs_success) { + drfs_close_archive(pOwnerArchive); + return result; + } + + drfs_recursively_claim_ownership_or_parent_archive(pArchive); + + *ppArchiveOut = pArchive; + return drfs_success; + } + } + else + { + // The input path is not absolute. We need to check each base directory. + + for (unsigned int iBaseDir = 0; iBaseDir < drfs_get_base_directory_count(pContext); ++iBaseDir) + { + drfs_archive* pArchive; + drfs_result result = drfs_open_archive_from_relative_path(pContext, drfs_get_base_directory_by_index(pContext, iBaseDir), absoluteOrRelativePath, accessMode, &pArchive); + if (result == drfs_success && pArchive != NULL) { + drfs_recursively_claim_ownership_or_parent_archive(pArchive); + + *ppArchiveOut = pArchive; + return drfs_success; + } + } + } + + return drfs_does_not_exist; +} + +drfs_result drfs_open_owner_archive(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, char* relativePathOut, size_t relativePathOutSize, drfs_archive** ppArchiveOut) +{ + if (ppArchiveOut == NULL) { + return drfs_invalid_args; + } + + *ppArchiveOut = NULL; + + if (pContext == NULL || absoluteOrRelativePath == NULL) { + return drfs_invalid_args; + } + + if (drfs_drpath_is_absolute(absoluteOrRelativePath)) + { + if (drfs_is_native_file(absoluteOrRelativePath) || drfs_is_native_directory(absoluteOrRelativePath)) + { + // It's a native file. The owner archive is the directory it's directly sitting in. + char dirAbsolutePath[DRFS_MAX_PATH]; + drfs_drpath_copy_base_path(absoluteOrRelativePath, dirAbsolutePath, sizeof(dirAbsolutePath)); + + drfs_archive* pArchive; + drfs_result result = drfs_open_archive(pContext, dirAbsolutePath, accessMode, &pArchive); + if (result != drfs_success) { + return result; + } + + if (relativePathOut) { + if (drfs__strcpy_s(relativePathOut, relativePathOutSize, drfs_drpath_file_name(absoluteOrRelativePath)) != 0) { + drfs_close_archive(pArchive); + return drfs_path_too_long; + } + } + + *ppArchiveOut = pArchive; + return drfs_success; + } + else + { + // It's not a native file or directory. + drfs_archive* pArchive; + drfs_result result = drfs_open_owner_archive_from_absolute_path(pContext, absoluteOrRelativePath, accessMode, relativePathOut, relativePathOutSize, &pArchive); + if (pArchive == NULL) { + return result; + } + + drfs_recursively_claim_ownership_or_parent_archive(pArchive); + + *ppArchiveOut = pArchive; + return drfs_success; + } + } + else + { + // The input path is not absolute. We need to loop through each base directory. + + for (unsigned int iBaseDir = 0; iBaseDir < drfs_get_base_directory_count(pContext); ++iBaseDir) + { + drfs_archive* pArchive; + drfs_result result = drfs_open_owner_archive_from_relative_path(pContext, drfs_get_base_directory_by_index(pContext, iBaseDir), absoluteOrRelativePath, accessMode, relativePathOut, relativePathOutSize, &pArchive); + if (result == drfs_success && pArchive != NULL) { + drfs_recursively_claim_ownership_or_parent_archive(pArchive); + + *ppArchiveOut = pArchive; + return drfs_success; + } + } + } + + return drfs_does_not_exist; +} + +void drfs_close_archive(drfs_archive* pArchive) +{ + if (pArchive == NULL) { + return; + } + + // The internal handle needs to be closed. + if (pArchive->callbacks.close_archive) { + pArchive->callbacks.close_archive(pArchive->internalArchiveHandle); + } + + drfs_close(pArchive->pFile); + + + if ((pArchive->flags & DR_FS_OWNS_PARENT_ARCHIVE) != 0) { + drfs_close_archive(pArchive->pParentArchive); + } + + free(pArchive); +} + +drfs_result drfs_open_file_from_archive(drfs_archive* pArchive, const char* relativePath, unsigned int accessMode, drfs_file** ppFileOut) +{ + if (ppFileOut == NULL) { + return drfs_invalid_args; + } + + *ppFileOut = NULL; + + if (pArchive == NULL || relativePath == NULL || pArchive->callbacks.open_file == NULL) { + return drfs_invalid_args; + } + + drfs_handle internalFileHandle; + drfs_result result = pArchive->callbacks.open_file(pArchive->internalArchiveHandle, relativePath, accessMode, &internalFileHandle); + if (result != drfs_success) { + return result; + } + + // At this point the file is opened and we can create the file object. + drfs_file* pFile = (drfs_file*)malloc(sizeof(*pFile)); + if (pFile == NULL) { + pArchive->callbacks.close_file(pArchive->internalArchiveHandle, internalFileHandle); + return drfs_out_of_memory; + } + + pFile->pArchive = pArchive; + pFile->internalFileHandle = internalFileHandle; + pFile->flags = 0; + + // The lock. +#ifdef _WIN32 +#ifdef DR_FS_WIN32_USE_EVENT_MUTEX + pFile->lock = CreateEvent(NULL, FALSE, TRUE, NULL); +#else + InitializeCriticalSection(&pFile->lock); +#endif +#else + if (pthread_mutex_init(&pFile->lock, NULL) != 0) { + drfs_close(pFile); + return drfs_unknown_error; + } +#endif + + *ppFileOut = pFile; + return drfs_success; +} + + +drfs_result drfs_open(drfs_context* pContext, const char* absoluteOrRelativePath, unsigned int accessMode, drfs_file** ppFile) +{ + if (ppFile == NULL) { + return drfs_invalid_args; + } + + // Always set the output file to null at the start for safety. + *ppFile = NULL; + + if (pContext == NULL || absoluteOrRelativePath == NULL) { + return drfs_invalid_args; + } + + char absolutePathForWriteMode[DRFS_MAX_PATH]; + if ((accessMode & DRFS_WRITE) != 0) { + if (drfs_validate_write_path(pContext, absoluteOrRelativePath, absolutePathForWriteMode, sizeof(absolutePathForWriteMode))) { + absoluteOrRelativePath = absolutePathForWriteMode; + } else { + return drfs_not_in_write_directory; + } + } + + char relativePath[DRFS_MAX_PATH]; + drfs_archive* pArchive; + drfs_result result = drfs_open_owner_archive(pContext, absoluteOrRelativePath, drfs_archive_access_mode(accessMode), relativePath, sizeof(relativePath), &pArchive); + if (result != drfs_success) { + return result; + } + + drfs_file* pFile; + result = drfs_open_file_from_archive(pArchive, relativePath, accessMode, &pFile); + if (result != drfs_success) { + drfs_close_archive(pArchive); + return result; + } + + // When using this API, we want to claim ownership of the archive so that it's closed when we close this file. + pFile->flags |= DR_FS_OWNS_PARENT_ARCHIVE; + + *ppFile = pFile; + return drfs_success; +} + +void drfs_close(drfs_file* pFile) +{ + if (!drfs_lock(pFile)) { + return; + } + + if (pFile->pArchive != NULL && pFile->pArchive->callbacks.close_file) { + pFile->pArchive->callbacks.close_file(pFile->pArchive->internalArchiveHandle, pFile->internalFileHandle); + pFile->internalFileHandle = NULL; + } + + if ((pFile->flags & DR_FS_OWNS_PARENT_ARCHIVE) != 0) { + drfs_close_archive(pFile->pArchive); + pFile->pArchive = NULL; + } + + drfs_unlock(pFile); + + + // The lock. +#ifdef _WIN32 +#ifdef DR_FS_WIN32_USE_EVENT_MUTEX + CloseHandle(pFile->lock); +#else + DeleteCriticalSection(&pFile->lock); +#endif +#else + pthread_mutex_destroy(&pFile->lock); +#endif + + free(pFile); +} + +drfs_result drfs_read_nolock(drfs_file* pFile, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut) +{ + if (pFile == NULL || pDataOut == NULL || pFile->pArchive == NULL || pFile->pArchive->callbacks.read_file == NULL) { + return drfs_invalid_args; + } + + return pFile->pArchive->callbacks.read_file(pFile->pArchive->internalArchiveHandle, pFile->internalFileHandle, pDataOut, bytesToRead, pBytesReadOut); +} + +drfs_result drfs_read(drfs_file* pFile, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut) +{ + if (!drfs_lock(pFile)) { + return drfs_unknown_error; + } + + drfs_result result = drfs_read_nolock(pFile, pDataOut, bytesToRead, pBytesReadOut); + + drfs_unlock(pFile); + return result; +} + +drfs_result drfs_write_nolock(drfs_file* pFile, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut) +{ + if (pFile == NULL || pData == NULL || pFile->pArchive == NULL || pFile->pArchive->callbacks.write_file == NULL) { + return drfs_invalid_args; + } + + return pFile->pArchive->callbacks.write_file(pFile->pArchive->internalArchiveHandle, pFile->internalFileHandle, pData, bytesToWrite, pBytesWrittenOut); +} + +drfs_result drfs_write(drfs_file* pFile, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut) +{ + if (!drfs_lock(pFile)) { + return drfs_unknown_error; + } + + drfs_result result = drfs_write_nolock(pFile, pData, bytesToWrite, pBytesWrittenOut); + + drfs_unlock(pFile); + return result; +} + +drfs_result drfs_seek_nolock(drfs_file* pFile, dr_int64 bytesToSeek, drfs_seek_origin origin) +{ + if (pFile == NULL || pFile->pArchive == NULL || pFile->pArchive->callbacks.seek_file == NULL) { + return drfs_invalid_args; + } + + return pFile->pArchive->callbacks.seek_file(pFile->pArchive->internalArchiveHandle, pFile->internalFileHandle, bytesToSeek, origin); +} + +drfs_result drfs_seek(drfs_file* pFile, dr_int64 bytesToSeek, drfs_seek_origin origin) +{ + if (!drfs_lock(pFile)) { + return drfs_unknown_error; + } + + drfs_result result = drfs_seek_nolock(pFile, bytesToSeek, origin); + + drfs_unlock(pFile); + return result; +} + +dr_uint64 drfs_tell_nolock(drfs_file* pFile) +{ + if (pFile == NULL || pFile->pArchive == NULL || pFile->pArchive->callbacks.tell_file == NULL) { + return DR_FALSE; + } + + return pFile->pArchive->callbacks.tell_file(pFile->pArchive->internalArchiveHandle, pFile->internalFileHandle); +} + +dr_uint64 drfs_tell(drfs_file* pFile) +{ + if (!drfs_lock(pFile)) { + return 0; + } + + dr_uint64 result = drfs_tell_nolock(pFile); + + drfs_unlock(pFile); + return result; +} + +dr_uint64 drfs_size_nolock(drfs_file* pFile) +{ + if (pFile == NULL || pFile->pArchive == NULL || pFile->pArchive->callbacks.file_size == NULL) { + return 0; + } + + return pFile->pArchive->callbacks.file_size(pFile->pArchive->internalArchiveHandle, pFile->internalFileHandle); +} + +dr_uint64 drfs_size(drfs_file* pFile) +{ + if (!drfs_lock(pFile)) { + return DR_FALSE; + } + + dr_uint64 result = drfs_size_nolock(pFile); + + drfs_unlock(pFile); + return result; +} + +void drfs_flush(drfs_file* pFile) +{ + if (pFile == NULL || pFile->pArchive == NULL || pFile->pArchive->callbacks.flush_file == NULL) { + return; + } + + pFile->pArchive->callbacks.flush_file(pFile->pArchive->internalArchiveHandle, pFile->internalFileHandle); +} + + +dr_bool32 drfs_lock(drfs_file* pFile) +{ + if (pFile == NULL || pFile->internalFileHandle == NULL) { + return DR_FALSE; + } + +#ifdef _WIN32 +#ifdef DR_FS_WIN32_USE_EVENT_MUTEX + WaitForSingleObject(pFile->lock, INFINITE); +#else + EnterCriticalSection(&pFile->lock); +#endif +#else + pthread_mutex_lock(&pFile->lock); +#endif + + return DR_TRUE; +} + +void drfs_unlock(drfs_file* pFile) +{ + if (pFile == NULL || pFile->internalFileHandle == NULL) { + return; + } + +#ifdef _WIN32 +#ifdef DR_FS_WIN32_USE_EVENT_MUTEX + SetEvent(pFile->lock); +#else + LeaveCriticalSection(&pFile->lock); +#endif +#else + pthread_mutex_unlock(&pFile->lock); +#endif +} + + +drfs_result drfs_get_file_info(drfs_context* pContext, const char* absoluteOrRelativePath, drfs_file_info* fi) +{ + if (pContext == NULL || absoluteOrRelativePath == NULL) { + return drfs_invalid_args; + } + + char relativePath[DRFS_MAX_PATH]; + drfs_archive* pOwnerArchive; + drfs_result result = drfs_open_owner_archive(pContext, absoluteOrRelativePath, DRFS_READ, relativePath, sizeof(relativePath), &pOwnerArchive); + if (result != drfs_success) { + return result; + } + + result = drfs_no_backend; + if (pOwnerArchive->callbacks.get_file_info) { + result = pOwnerArchive->callbacks.get_file_info(pOwnerArchive->internalArchiveHandle, relativePath, fi); + } + + if (result == drfs_success && fi != NULL) { + drfs_drpath_copy_and_append(fi->absolutePath, sizeof(fi->absolutePath), pOwnerArchive->absolutePath, relativePath); + } + + drfs_close_archive(pOwnerArchive); + return result; +} + + +dr_bool32 drfs_begin(drfs_context* pContext, const char* absoluteOrRelativePath, drfs_iterator* pIteratorOut) +{ + if (pIteratorOut == NULL) return DR_FALSE; + memset(pIteratorOut, 0, sizeof(*pIteratorOut)); + + if (pContext == NULL || absoluteOrRelativePath == NULL) { + return DR_FALSE; + } + + // We need to open the archive that the given folder is in. The path could, however, point to an actual archive which is allowed + // in which case we just iterate over the root directory within that archive. What we do is first try using the path as an actual + // archive. If it fails we assume the path is to a folder within an archive (such as a zip file) in which case we just try opening + // the owner archive. If both fail, we return DR_FALSE. + + char relativePath[DRFS_MAX_PATH]; + drfs_result result = drfs_open_archive(pContext, absoluteOrRelativePath, DRFS_READ, &pIteratorOut->pArchive); + if (result == drfs_success) { + relativePath[0] = '\0'; + } else { + result = drfs_open_owner_archive(pContext, absoluteOrRelativePath, DRFS_READ, relativePath, sizeof(relativePath), &pIteratorOut->pArchive); + if (result != drfs_success) { + return DR_FALSE; + } + } + + assert(pIteratorOut->pArchive != NULL); + + + if (pIteratorOut->pArchive->callbacks.begin_iteration) { + pIteratorOut->internalIteratorHandle = pIteratorOut->pArchive->callbacks.begin_iteration(pIteratorOut->pArchive->internalArchiveHandle, relativePath); + } + + if (pIteratorOut->internalIteratorHandle == NULL) { + drfs_close_archive(pIteratorOut->pArchive); + pIteratorOut->pArchive = NULL; + return DR_FALSE; + } + + + dr_bool32 foundFirst = drfs_next(pContext, pIteratorOut); + if (!foundFirst) { + drfs_end(pContext, pIteratorOut); + } + + return foundFirst; +} + +dr_bool32 drfs_next(drfs_context* pContext, drfs_iterator* pIterator) +{ + if (pIterator == NULL) { + return DR_FALSE; + } + + memset(&pIterator->info, 0, sizeof(pIterator->info)); + + if (pContext == NULL || pIterator->pArchive == NULL) { + return DR_FALSE; + } + + dr_bool32 result = DR_FALSE; + if (pIterator->pArchive->callbacks.next_iteration) { + result = pIterator->pArchive->callbacks.next_iteration(pIterator->pArchive->internalArchiveHandle, pIterator->internalIteratorHandle, &pIterator->info); + } + + // At this point the pIterator->info.absolutePath is actually referring to a relative path. We need to convert it to an absolute path. + if (result) { + char relativePath[DRFS_MAX_PATH]; + drfs__strcpy_s(relativePath, sizeof(relativePath), pIterator->info.absolutePath); + drfs_drpath_copy_and_append(pIterator->info.absolutePath, sizeof(pIterator->info.absolutePath), pIterator->pArchive->absolutePath, relativePath); + } + + return result; +} + +void drfs_end(drfs_context* pContext, drfs_iterator* pIterator) +{ + if (pContext == NULL || pIterator == NULL) { + return; + } + + if (pIterator->pArchive != NULL && pIterator->pArchive->callbacks.end_iteration) { + pIterator->pArchive->callbacks.end_iteration(pIterator->pArchive->internalArchiveHandle, pIterator->internalIteratorHandle); + } + + drfs_close_archive(pIterator->pArchive); + memset(pIterator, 0, sizeof(*pIterator)); +} + +drfs_result drfs_delete_file(drfs_context* pContext, const char* path) +{ + if (pContext == NULL || path == NULL) { + return drfs_invalid_args; + } + + char absolutePath[DRFS_MAX_PATH]; + if (!drfs_validate_write_path(pContext, path, absolutePath, sizeof(absolutePath))) { + return drfs_not_in_write_directory; + } + + + char relativePath[DRFS_MAX_PATH]; + drfs_archive* pArchive; + drfs_result result = drfs_open_owner_archive(pContext, absolutePath, drfs_archive_access_mode(DRFS_READ | DRFS_WRITE), relativePath, sizeof(relativePath), &pArchive); + if (result != drfs_success) { + return result; + } + + result = drfs_no_backend; + if (pArchive->callbacks.delete_file) { + result = pArchive->callbacks.delete_file(pArchive->internalArchiveHandle, relativePath); + } + + drfs_close_archive(pArchive); + return result; +} + +drfs_result drfs_create_directory(drfs_context* pContext, const char* path) +{ + if (pContext == NULL || path == NULL) { + return drfs_invalid_args; + } + + char absolutePath[DRFS_MAX_PATH]; + if (!drfs_validate_write_path(pContext, path, absolutePath, sizeof(absolutePath))) { + return drfs_not_in_write_directory; + } + + char relativePath[DRFS_MAX_PATH]; + drfs_archive* pArchive; + drfs_result result = drfs_open_owner_archive(pContext, absolutePath, drfs_archive_access_mode(DRFS_READ | DRFS_WRITE), relativePath, sizeof(relativePath), &pArchive); + if (result != drfs_success) { + return result; + } + + result = drfs_no_backend; + if (pArchive->callbacks.create_directory) { + result = pArchive->callbacks.create_directory(pArchive->internalArchiveHandle, relativePath); + } + + drfs_close_archive(pArchive); + return result; +} + +drfs_result drfs_move_file(drfs_context* pContext, const char* pathOld, const char* pathNew) +{ + // Renaming/moving is not supported across different archives. + + if (pContext == NULL || pathOld == NULL || pathNew == NULL) { + return drfs_invalid_args; + } + + + char absolutePathOld[DRFS_MAX_PATH]; + if (drfs_validate_write_path(pContext, pathOld, absolutePathOld, sizeof(absolutePathOld))) { + pathOld = absolutePathOld; + } else { + return drfs_not_in_write_directory; + } + + char absolutePathNew[DRFS_MAX_PATH]; + if (drfs_validate_write_path(pContext, pathNew, absolutePathNew, sizeof(absolutePathNew))) { + pathNew = absolutePathNew; + } else { + return drfs_not_in_write_directory; + } + + + + + + char relativePathOld[DRFS_MAX_PATH]; + drfs_archive* pArchiveOld; + drfs_result result = drfs_open_owner_archive(pContext, pathOld, drfs_archive_access_mode(DRFS_READ | DRFS_WRITE), relativePathOld, sizeof(relativePathOld), &pArchiveOld); + if (pArchiveOld != NULL) + { + char relativePathNew[DRFS_MAX_PATH]; + drfs_archive* pArchiveNew; + result = drfs_open_owner_archive(pContext, pathNew, drfs_archive_access_mode(DRFS_READ | DRFS_WRITE), relativePathNew, sizeof(relativePathNew), &pArchiveNew); + if (pArchiveNew != NULL) + { + dr_bool32 areBothArchivesNative = (pArchiveOld->callbacks.move_file == pArchiveNew->callbacks.move_file && pArchiveNew->callbacks.move_file == drfs_move_file__native); + if (areBothArchivesNative) + { + result = drfs_move_native_file(absolutePathOld, absolutePathNew); + } + else + { + if (drfs_drpath_equal(pArchiveOld->absolutePath, pArchiveNew->absolutePath) && pArchiveOld->callbacks.move_file) { + result = pArchiveOld->callbacks.move_file(pArchiveOld, relativePathOld, relativePathNew); + } else { + result = drfs_permission_denied; // Attempting to rename across different archives which is not supported. + } + } + + drfs_close_archive(pArchiveNew); + + } + + drfs_close_archive(pArchiveOld); + } + + return result; +} + +drfs_result drfs_copy_file(drfs_context* pContext, const char* srcPath, const char* dstPath, dr_bool32 failIfExists) +{ + if (pContext == NULL || srcPath == NULL || dstPath == NULL) { + return drfs_invalid_args; + } + + char dstPathAbsolute[DRFS_MAX_PATH]; + if (!drfs_validate_write_path(pContext, dstPath, dstPathAbsolute, sizeof(dstPathAbsolute))) { + return drfs_not_in_write_directory; + } + + + // We want to open the archive of both the source and destination. If they are the same archive we'll do an intra-archive copy. + char srcRelativePath[DRFS_MAX_PATH]; + drfs_archive* pSrcArchive; + drfs_result result = drfs_open_owner_archive(pContext, srcPath, drfs_archive_access_mode(DRFS_READ), srcRelativePath, sizeof(srcRelativePath), &pSrcArchive); + if (result != drfs_success) { + return result; + } + + char dstRelativePath[DRFS_MAX_PATH]; + drfs_archive* pDstArchive; + result = drfs_open_owner_archive(pContext, dstPathAbsolute, drfs_archive_access_mode(DRFS_READ | DRFS_WRITE), dstRelativePath, sizeof(dstRelativePath), &pDstArchive); + if (result != drfs_success) { + drfs_close_archive(pSrcArchive); + return result; + } + + + + result = drfs_success; + if (strcmp(pSrcArchive->absolutePath, pDstArchive->absolutePath) == 0 && pDstArchive->callbacks.copy_file) + { + // Intra-archive copy. + result = pDstArchive->callbacks.copy_file(pDstArchive->internalArchiveHandle, srcRelativePath, dstRelativePath, failIfExists); + } + else + { + dr_bool32 areBothArchivesNative = (pSrcArchive->callbacks.copy_file == pDstArchive->callbacks.copy_file && pDstArchive->callbacks.copy_file == drfs_copy_file__native); + if (areBothArchivesNative) + { + char srcPathAbsolute[DRFS_MAX_PATH]; + drfs_drpath_copy_and_append(srcPathAbsolute, sizeof(srcPathAbsolute), pSrcArchive->absolutePath, srcPath); + + result = drfs_copy_native_file(srcPathAbsolute, dstPathAbsolute, failIfExists); + } + else + { + // Inter-archive copy. This is a theoretically slower path because we do everything manually. Probably not much slower in practice, though. + if (failIfExists && pDstArchive->callbacks.get_file_info(pDstArchive, dstRelativePath, NULL) == drfs_success) + { + result = drfs_already_exists; + } + else + { + drfs_file* pSrcFile; + result = drfs_open_file_from_archive(pSrcArchive, srcRelativePath, DRFS_READ, &pSrcFile); + if (result != drfs_success) { + return result; + } + + drfs_file* pDstFile; + result = drfs_open_file_from_archive(pDstArchive, dstRelativePath, DRFS_WRITE | DRFS_TRUNCATE, &pDstFile); + if (result != drfs_success) { + drfs_close(pSrcFile); + return result; + } + + assert(pSrcFile != NULL); + assert(pDstFile != NULL); + + char chunk[4096]; + size_t bytesRead; + while (drfs_read(pSrcFile, chunk, sizeof(chunk), &bytesRead) == drfs_success && bytesRead > 0) + { + result = drfs_write(pDstFile, chunk, bytesRead, NULL); + if (result != drfs_success) { + break; + } + } + + drfs_close(pSrcFile); + drfs_close(pDstFile); + } + } + } + + + drfs_close_archive(pSrcArchive); + drfs_close_archive(pDstArchive); + + return result; +} + + +dr_bool32 drfs_is_archive_path(drfs_context* pContext, const char* path) +{ + return drfs_find_backend_by_extension(pContext, drfs_drpath_extension(path), NULL); +} + + + +/////////////////////////////////////////////////////////////////////////////// +// +// High Level API +// +/////////////////////////////////////////////////////////////////////////////// +void drfs_free(void* p) +{ + free(p); +} + +drfs_result drfs_find_absolute_path(drfs_context* pContext, const char* relativePath, char* absolutePathOut, size_t absolutePathOutSize) +{ + if (absolutePathOut == NULL) return drfs_invalid_args; + if (absolutePathOutSize > 0) absolutePathOut[0] = '\0'; + + if (pContext == NULL || relativePath == NULL || absolutePathOutSize == 0) { + return drfs_invalid_args; + } + + drfs_file_info fi; + if (drfs_get_file_info(pContext, relativePath, &fi) == drfs_success) { + if (drfs__strcpy_s(absolutePathOut, absolutePathOutSize, fi.absolutePath) == 0) { + return drfs_success; + } + } + + return drfs_does_not_exist; +} + +drfs_result drfs_find_absolute_path_explicit_base(drfs_context* pContext, const char* relativePath, const char* highestPriorityBasePath, char* absolutePathOut, size_t absolutePathOutSize) +{ + if (absolutePathOut == NULL) return drfs_invalid_args; + if (absolutePathOutSize > 0) absolutePathOut[0] = '\0'; + + if (pContext == NULL || relativePath == NULL || highestPriorityBasePath == NULL || absolutePathOutSize == 0) { + return drfs_invalid_args; + } + + drfs_result result = drfs_unknown_error; + drfs_insert_base_directory(pContext, highestPriorityBasePath, 0); + { + result = drfs_find_absolute_path(pContext, relativePath, absolutePathOut, absolutePathOutSize); + } + drfs_remove_base_directory_by_index(pContext, 0); + + return result; +} + +dr_bool32 drfs_is_base_directory(drfs_context* pContext, const char* baseDir) +{ + if (pContext == NULL) { + return DR_FALSE; + } + + for (unsigned int i = 0; i < drfs_get_base_directory_count(pContext); ++i) { + if (drfs_drpath_equal(drfs_get_base_directory_by_index(pContext, i), baseDir)) { + return DR_TRUE; + } + } + + return DR_FALSE; +} + +drfs_result drfs_write_string(drfs_file* pFile, const char* str) +{ + return drfs_write(pFile, str, (unsigned int)strlen(str), NULL); +} + +drfs_result drfs_write_line(drfs_file* pFile, const char* str) +{ + drfs_result result = drfs_write_string(pFile, str); + if (result != drfs_success) { + return result; + } + + return drfs_write_string(pFile, "\n"); +} + + +void* drfs_open_and_read_binary_file(drfs_context* pContext, const char* absoluteOrRelativePath, size_t* pSizeInBytesOut) +{ + drfs_file* pFile; + if (drfs_open(pContext, absoluteOrRelativePath, DRFS_READ, &pFile) != drfs_success) { + return NULL; + } + + dr_uint64 fileSize = drfs_size(pFile); + if (fileSize > SIZE_MAX) + { + // File's too big. + drfs_close(pFile); + return NULL; + } + + + void* pData = malloc((size_t)fileSize); + if (pData == NULL) + { + // Failed to allocate memory. + drfs_close(pFile); + return NULL; + } + + + if (drfs_read(pFile, pData, (size_t)fileSize, NULL) != drfs_success) + { + free(pData); + if (pSizeInBytesOut != NULL) { + *pSizeInBytesOut = 0; + } + + drfs_close(pFile); + return NULL; + } + + + if (pSizeInBytesOut != NULL) { + *pSizeInBytesOut = (size_t)fileSize; + } + + drfs_close(pFile); + return pData; +} + +char* drfs_open_and_read_text_file(drfs_context* pContext, const char* absoluteOrRelativePath, size_t* pSizeInBytesOut) +{ + drfs_file* pFile; + if (drfs_open(pContext, absoluteOrRelativePath, DRFS_READ, &pFile) != drfs_success) { + return NULL; + } + + dr_uint64 fileSize = drfs_size(pFile); + if (fileSize > SIZE_MAX) + { + // File's too big. + drfs_close(pFile); + return NULL; + } + + + void* pData = malloc((size_t)fileSize + 1); // +1 for null terminator. + if (pData == NULL) + { + // Failed to allocate memory. + drfs_close(pFile); + return NULL; + } + + + if (drfs_read(pFile, pData, (size_t)fileSize, NULL) != drfs_success) + { + free(pData); + if (pSizeInBytesOut != NULL) { + *pSizeInBytesOut = 0; + } + + drfs_close(pFile); + return NULL; + } + + // Null terminator. + ((char*)pData)[fileSize] = '\0'; + + + if (pSizeInBytesOut != NULL) { + *pSizeInBytesOut = (size_t)fileSize; + } + + drfs_close(pFile); + return (char*)pData; +} + +drfs_result drfs_open_and_write_binary_file(drfs_context* pContext, const char* absoluteOrRelativePath, const void* pData, size_t dataSize) +{ + drfs_result result = drfs_unknown_error; + + drfs_file* pFile; + result = drfs_open(pContext, absoluteOrRelativePath, DRFS_WRITE | DRFS_TRUNCATE, &pFile); + if (result != drfs_success) { + return result; + } + + result = drfs_write(pFile, pData, dataSize, NULL); + + drfs_close(pFile); + return result; +} + +drfs_result drfs_open_and_write_text_file(drfs_context* pContext, const char* absoluteOrRelativePath, const char* pTextData) +{ + return drfs_open_and_write_binary_file(pContext, absoluteOrRelativePath, pTextData, strlen(pTextData)); +} + + +dr_bool32 drfs_exists(drfs_context* pContext, const char* absoluteOrRelativePath) +{ + drfs_file_info fi; + return drfs_get_file_info(pContext, absoluteOrRelativePath, &fi) == drfs_success; +} + +dr_bool32 drfs_is_existing_file(drfs_context* pContext, const char* absoluteOrRelativePath) +{ + drfs_file_info fi; + if (drfs_get_file_info(pContext, absoluteOrRelativePath, &fi) == drfs_success) + { + return (fi.attributes & DRFS_FILE_ATTRIBUTE_DIRECTORY) == 0; + } + + return DR_FALSE; +} + +dr_bool32 drfs_is_existing_directory(drfs_context* pContext, const char* absoluteOrRelativePath) +{ + drfs_file_info fi; + if (drfs_get_file_info(pContext, absoluteOrRelativePath, &fi) == drfs_success) + { + return (fi.attributes & DRFS_FILE_ATTRIBUTE_DIRECTORY) != 0; + } + + return DR_FALSE; +} + +drfs_result drfs_create_directory_recursive(drfs_context* pContext, const char* path) +{ + if (pContext == NULL || path == NULL) { + return drfs_invalid_args; + } + + // We just iterate over each segment and try creating each directory if it doesn't exist. + char absolutePath[DRFS_MAX_PATH]; + if (drfs_validate_write_path(pContext, path, absolutePath, DRFS_MAX_PATH)) { + path = absolutePath; + } else { + return drfs_not_in_write_directory; + } + + + char runningPath[DRFS_MAX_PATH]; + runningPath[0] = '\0'; + + drfs_drpath_iterator iPathSeg; + if (!drfs_drpath_first(absolutePath, &iPathSeg)) { + return drfs_invalid_args; + } + + // Never check the first segment because we can assume it always exists - it will always be the drive root. + if (drfs_drpath_append_iterator(runningPath, sizeof(runningPath), iPathSeg)) + { + // Loop over every directory until we find one that does not exist. + while (drfs_drpath_next(&iPathSeg)) + { + if (!drfs_drpath_append_iterator(runningPath, sizeof(runningPath), iPathSeg)) { + return drfs_path_too_long; + } + + if (!drfs_is_existing_directory(pContext, runningPath)) { + drfs_result result = drfs_create_directory(pContext, runningPath); + if (result != drfs_success) { + return result; + } + + break; + } + } + + + // At this point all we need to do is create the remaining directories - we can assert that the directory does not exist + // rather than actually checking it which should be a bit more efficient. + while (drfs_drpath_next(&iPathSeg)) + { + if (!drfs_drpath_append_iterator(runningPath, sizeof(runningPath), iPathSeg)) { + return drfs_path_too_long; + } + + assert(!drfs_is_existing_directory(pContext, runningPath)); + + drfs_result result = drfs_create_directory(pContext, runningPath); + if (result != drfs_success) { + return result; + } + } + + + return drfs_success; + } + else + { + return drfs_invalid_args; + } +} + +dr_bool32 drfs_eof(drfs_file* pFile) +{ + return drfs_tell(pFile) == drfs_size(pFile); +} + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// ZIP +// +/////////////////////////////////////////////////////////////////////////////// +#ifndef DR_FS_NO_ZIP + +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4334) +#endif +#if defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-macros" + #pragma GCC diagnostic ignored "-Wcast-align" + #pragma GCC diagnostic ignored "-Wextra" + +#if __GNUC__ >= 6 && !defined __clang__ + #pragma GCC diagnostic ignored "-Wmisleading-indentation" +#endif +#endif + +#ifndef DRFS_MINIZ_HEADER_INCLUDED +#define DRFS_MINIZ_HEADER_INCLUDED + +// If DRFS_MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or +// get/set file times, and the C run-time funcs that get/set times won't be called. +// The current downside is the times written to your archives will be from 1979. +//#define DRFS_MINIZ_NO_TIME + +// Define DRFS_MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. +//#define DRFS_MINIZ_NO_ARCHIVE_APIS + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) + // TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux + #define DRFS_MINIZ_NO_TIME +#endif + +#if !defined(DRFS_MINIZ_NO_TIME) && !defined(DRFS_MINIZ_NO_ARCHIVE_APIS) + #include +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +// DRFS_MINIZ_X86_OR_X64_CPU is only used to help set the below macros. +#define DRFS_MINIZ_X86_OR_X64_CPU 1 +#endif + +#if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__) || DRFS_MINIZ_X86_OR_X64_CPU +// Set DRFS_MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. +#define DRFS_MINIZ_LITTLE_ENDIAN 1 +#endif + +#if DRFS_MINIZ_X86_OR_X64_CPU +// Set DRFS_MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. +#define DRFS_MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +// Set DRFS_MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). +#define DRFS_MINIZ_HAS_64BIT_REGISTERS 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------- zlib-style API Definitions. + +// For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: drfs_mz_ulong can be either 32 or 64-bits! +typedef unsigned long drfs_mz_ulong; + +// drfs_mz_free() internally uses the DRFS_MZ_FREE() macro (which by default calls free() unless you've modified the DRFS_MZ_MALLOC macro) to release a block allocated from the heap. +void drfs_mz_free(void *p); + +#define DRFS_MZ_ADLER32_INIT (1) +// drfs_mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. +drfs_mz_ulong drfs_mz_adler32(drfs_mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define DRFS_MZ_CRC32_INIT (0) +// drfs_mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. +drfs_mz_ulong drfs_mz_crc32(drfs_mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +// Compression strategies. +enum { DRFS_MZ_DEFAULT_STRATEGY = 0, DRFS_MZ_FILTERED = 1, DRFS_MZ_HUFFMAN_ONLY = 2, DRFS_MZ_RLE = 3, DRFS_MZ_FIXED = 4 }; + +// Method +#define DRFS_MZ_DEFLATED 8 + +// Heap allocation callbacks. +// Note that drfs_mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. +typedef void *(*drfs_mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*drfs_mz_free_func)(void *opaque, void *address); +typedef void *(*drfs_mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); + +// ------------------- Types and macros + +typedef unsigned char drfs_mz_uint8; +typedef signed short drfs_mz_int16; +typedef unsigned short drfs_drfs_mz_uint16; +typedef unsigned int drfs_drfs_mz_uint32; +typedef unsigned int drfs_mz_uint; +typedef long long drfs_mz_int64; +typedef unsigned long long drfs_mz_uint64; +typedef int drfs_mz_bool; + +#define DRFS_MZ_FALSE (0) +#define DRFS_MZ_TRUE (1) + +// An attempt to work around MSVC's spammy "warning C4127: conditional expression is constant" message. +#ifdef _MSC_VER + #define DRFS_MZ_MACRO_END while (0, 0) +#else + #define DRFS_MZ_MACRO_END while (0) +#endif + +// ------------------- ZIP archive reading/writing + +#ifndef DRFS_MINIZ_NO_ARCHIVE_APIS + +enum +{ + DRFS_MZ_ZIP_MAX_IO_BUF_SIZE = 64*1024, + DRFS_MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, + DRFS_MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 +}; + +typedef struct +{ + drfs_drfs_mz_uint32 m_file_index; + drfs_drfs_mz_uint32 m_central_dir_ofs; + drfs_drfs_mz_uint16 m_version_made_by; + drfs_drfs_mz_uint16 m_version_needed; + drfs_drfs_mz_uint16 m_bit_flag; + drfs_drfs_mz_uint16 m_method; +#ifndef DRFS_MINIZ_NO_TIME + time_t m_time; +#endif + drfs_drfs_mz_uint32 m_crc32; + drfs_mz_uint64 m_comp_size; + drfs_mz_uint64 m_uncomp_size; + drfs_drfs_mz_uint16 m_internal_attr; + drfs_drfs_mz_uint32 m_external_attr; + drfs_mz_uint64 m_local_header_ofs; + drfs_drfs_mz_uint32 m_comment_size; + char m_filename[DRFS_MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + char m_comment[DRFS_MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; +} drfs_drfs_mz_zip_archive_file_stat; + +typedef size_t (*drfs_mz_file_read_func)(void *pOpaque, drfs_mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t (*drfs_mz_file_write_func)(void *pOpaque, drfs_mz_uint64 file_ofs, const void *pBuf, size_t n); + +struct drfs_mz_zip_internal_state_tag; +typedef struct drfs_mz_zip_internal_state_tag drfs_mz_zip_internal_state; + +typedef enum +{ + DRFS_MZ_ZIP_MODE_INVALID = 0, + DRFS_MZ_ZIP_MODE_READING = 1, + DRFS_MZ_ZIP_MODE_WRITING = 2, + DRFS_MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} drfs_mz_zip_mode; + +typedef struct drfs_mz_zip_archive_tag +{ + drfs_mz_uint64 m_archive_size; + drfs_mz_uint64 m_central_directory_file_ofs; + drfs_mz_uint m_total_files; + drfs_mz_zip_mode m_zip_mode; + + drfs_mz_uint m_file_offset_alignment; + + drfs_mz_alloc_func m_pAlloc; + drfs_mz_free_func m_pFree; + drfs_mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + drfs_mz_file_read_func m_pRead; + drfs_mz_file_write_func m_pWrite; + void *m_pIO_opaque; + + drfs_mz_zip_internal_state *m_pState; + +} drfs_mz_zip_archive; + +typedef enum +{ + DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + DRFS_MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + DRFS_MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + DRFS_MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 +} drfs_mz_zip_flags; + +// ZIP archive reading + +// Inits a ZIP archive reader. +// These functions read and validate the archive's central directory. +drfs_mz_bool drfs_mz_zip_reader_init(drfs_mz_zip_archive *pZip, drfs_mz_uint64 size, drfs_drfs_mz_uint32 flags); + +// Returns the total number of files in the archive. +drfs_mz_uint drfs_mz_zip_reader_get_num_files(drfs_mz_zip_archive *pZip); + +// Returns detailed information about an archive file entry. +drfs_mz_bool drfs_mz_zip_reader_file_stat(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, drfs_drfs_mz_zip_archive_file_stat *pStat); + +// Determines if an archive file entry is a directory entry. +drfs_mz_bool drfs_mz_zip_reader_is_file_a_directory(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index); + +// Retrieves the filename of an archive file entry. +// Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. +drfs_mz_uint drfs_mz_zip_reader_get_filename(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, char *pFilename, drfs_mz_uint filename_buf_size); + +// Attempts to locates a file in the archive's central directory. +// Valid flags: DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE, DRFS_MZ_ZIP_FLAG_IGNORE_PATH +// Returns -1 if the file cannot be found. +int drfs_mz_zip_reader_locate_file(drfs_mz_zip_archive *pZip, const char *pName, const char *pComment, drfs_mz_uint flags); + +// Extracts a archive file to a memory buffer using no memory allocation. +drfs_mz_bool drfs_mz_zip_reader_extract_to_mem_no_alloc(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, void *pBuf, size_t buf_size, drfs_mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +drfs_mz_bool drfs_mz_zip_reader_extract_file_to_mem_no_alloc(drfs_mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, drfs_mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +// Extracts a archive file to a memory buffer. +drfs_mz_bool drfs_mz_zip_reader_extract_to_mem(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, void *pBuf, size_t buf_size, drfs_mz_uint flags); +drfs_mz_bool drfs_mz_zip_reader_extract_file_to_mem(drfs_mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, drfs_mz_uint flags); + +// Extracts a archive file to a dynamically allocated heap buffer. +void *drfs_mz_zip_reader_extract_to_heap(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, size_t *pSize, drfs_mz_uint flags); + +// Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. +drfs_mz_bool drfs_mz_zip_reader_end(drfs_mz_zip_archive *pZip); + +#endif // #ifndef DRFS_MINIZ_NO_ARCHIVE_APIS + +// ------------------- Low-level Decompression API Definitions + +// Decompression flags used by drfs_tinfl_decompress(). +// DRFS_TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. +// DRFS_TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. +// DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). +// DRFS_TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. +enum +{ + DRFS_TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + DRFS_TINFL_FLAG_HAS_MORE_INPUT = 2, + DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + DRFS_TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +// High level decompression functions: +// drfs_tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. +// On return: +// Function returns a pointer to the decompressed data, or NULL on failure. +// *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must call drfs_mz_free() on the returned block when it's no longer needed. +void *drfs_tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// drfs_tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. +// Returns DRFS_TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. +#define DRFS_TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +size_t drfs_tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// drfs_tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. +// Returns 1 on success or 0 on failure. +typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); +int drfs_tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +struct drfs_tinfl_decompressor_tag; typedef struct drfs_tinfl_decompressor_tag drfs_tinfl_decompressor; + +// Max size of LZ dictionary. +#define DRFS_TINFL_LZ_DICT_SIZE 32768 + +// Return status. +typedef enum +{ + DRFS_TINFL_STATUS_BAD_PARAM = -3, + DRFS_TINFL_STATUS_ADLER32_MISMATCH = -2, + DRFS_TINFL_STATUS_FAILED = -1, + DRFS_TINFL_STATUS_DONE = 0, + DRFS_TINFL_STATUS_NEEDS_MORE_INPUT = 1, + DRFS_TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} drfs_tinfl_status; + +// Initializes the decompressor to its initial state. +#define drfs_tinfl_init(r) do { (r)->m_state = 0; } DRFS_MZ_MACRO_END +#define drfs_tinfl_get_adler32(r) (r)->m_check_adler32 + +// Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. +// This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. +drfs_tinfl_status drfs_tinfl_decompress(drfs_tinfl_decompressor *r, const drfs_mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, drfs_mz_uint8 *pOut_buf_start, drfs_mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const drfs_drfs_mz_uint32 decomp_flags); + +// Internal/private bits follow. +enum +{ + DRFS_TINFL_MAX_HUFF_TABLES = 3, DRFS_TINFL_MAX_HUFF_SYMBOLS_0 = 288, DRFS_TINFL_MAX_HUFF_SYMBOLS_1 = 32, DRFS_TINFL_MAX_HUFF_SYMBOLS_2 = 19, + DRFS_TINFL_FAST_LOOKUP_BITS = 10, DRFS_TINFL_FAST_LOOKUP_SIZE = 1 << DRFS_TINFL_FAST_LOOKUP_BITS +}; + +typedef struct +{ + drfs_mz_uint8 m_code_size[DRFS_TINFL_MAX_HUFF_SYMBOLS_0]; + drfs_mz_int16 m_look_up[DRFS_TINFL_FAST_LOOKUP_SIZE], m_tree[DRFS_TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} drfs_tinfl_huff_table; + +#if DRFS_MINIZ_HAS_64BIT_REGISTERS + #define DRFS_TINFL_USE_64BIT_BITBUF 1 +#endif + +#if DRFS_TINFL_USE_64BIT_BITBUF + typedef drfs_mz_uint64 drfs_tinfl_bit_buf_t; + #define DRFS_TINFL_BITBUF_SIZE (64) +#else + typedef drfs_drfs_mz_uint32 drfs_tinfl_bit_buf_t; + #define DRFS_TINFL_BITBUF_SIZE (32) +#endif + +struct drfs_tinfl_decompressor_tag +{ + drfs_drfs_mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[DRFS_TINFL_MAX_HUFF_TABLES]; + drfs_tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + drfs_tinfl_huff_table m_tables[DRFS_TINFL_MAX_HUFF_TABLES]; + drfs_mz_uint8 m_raw_header[4], m_len_codes[DRFS_TINFL_MAX_HUFF_SYMBOLS_0 + DRFS_TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_INCLUDED + +// ------------------- End of Header: Implementation follows. (If you only want the header, define DRFS_MINIZ_HEADER_FILE_ONLY.) + +#ifndef DRFS_MINIZ_HEADER_FILE_ONLY + +typedef unsigned char mz_validate_uint16[sizeof(drfs_drfs_mz_uint16)==2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(drfs_drfs_mz_uint32)==4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(drfs_mz_uint64)==8 ? 1 : -1]; + +#include +#include + +#define DRFS_MZ_ASSERT(x) assert(x) + +#define DRFS_MZ_MALLOC(x) malloc(x) +#define DRFS_MZ_FREE(x) free(x) +#define DRFS_MZ_REALLOC(p, x) realloc(p, x) + +#define DRFS_MZ_MAX(a,b) (((a)>(b))?(a):(b)) +#define DRFS_MZ_MIN(a,b) (((a)<(b))?(a):(b)) +#define DRFS_MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if DRFS_MINIZ_USE_UNALIGNED_LOADS_AND_STORES && DRFS_MINIZ_LITTLE_ENDIAN + #define DRFS_MZ_READ_LE16(p) *((const drfs_drfs_mz_uint16 *)(p)) + #define DRFS_MZ_READ_LE32(p) *((const drfs_drfs_mz_uint32 *)(p)) +#else + #define DRFS_MZ_READ_LE16(p) ((drfs_drfs_mz_uint32)(((const drfs_mz_uint8 *)(p))[0]) | ((drfs_drfs_mz_uint32)(((const drfs_mz_uint8 *)(p))[1]) << 8U)) + #define DRFS_MZ_READ_LE32(p) ((drfs_drfs_mz_uint32)(((const drfs_mz_uint8 *)(p))[0]) | ((drfs_drfs_mz_uint32)(((const drfs_mz_uint8 *)(p))[1]) << 8U) | ((drfs_drfs_mz_uint32)(((const drfs_mz_uint8 *)(p))[2]) << 16U) | ((drfs_drfs_mz_uint32)(((const drfs_mz_uint8 *)(p))[3]) << 24U)) +#endif + +#ifdef _MSC_VER + #define DRFS_MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) + #define DRFS_MZ_FORCEINLINE inline __attribute__((__always_inline__)) +#else + #define DRFS_MZ_FORCEINLINE inline +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +static void *drfs__def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return DRFS_MZ_MALLOC(items * size); } +static void drfs__def_free_func(void *opaque, void *address) { (void)opaque, (void)address; DRFS_MZ_FREE(address); } +static void *drfs__def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return DRFS_MZ_REALLOC(address, items * size); } + +// ------------------- zlib-style API's + +drfs_mz_ulong drfs_mz_adler32(drfs_mz_ulong adler, const unsigned char *ptr, size_t buf_len) +{ + drfs_drfs_mz_uint32 i, s1 = (drfs_drfs_mz_uint32)(adler & 0xffff), s2 = (drfs_drfs_mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552; + if (!ptr) return DRFS_MZ_ADLER32_INIT; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + return (s2 << 16) + s1; +} + +// Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ +drfs_mz_ulong drfs_mz_crc32(drfs_mz_ulong crc, const drfs_mz_uint8 *ptr, size_t buf_len) +{ + static const drfs_drfs_mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + drfs_drfs_mz_uint32 crcu32 = (drfs_drfs_mz_uint32)crc; + if (!ptr) return DRFS_MZ_CRC32_INIT; + crcu32 = ~crcu32; while (buf_len--) { drfs_mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } + return ~crcu32; +} + +void drfs_mz_free(void *p) +{ + DRFS_MZ_FREE(p); +} + + +// ------------------- Low-level Decompression (completely independent from all compression API's) + +#define DRFS_TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define DRFS_TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define DRFS_TINFL_CR_BEGIN switch(r->m_state) { case 0: +#define DRFS_TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } DRFS_MZ_MACRO_END +#define DRFS_TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { DRFS_TINFL_CR_RETURN(state_index, result); } } DRFS_MZ_MACRO_END +#define DRFS_TINFL_CR_FINISH } + +// TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never +// reads ahead more than it needs to. Currently DRFS_TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. +#define DRFS_TINFL_GET_BYTE(state_index, c) do { \ + if (pIn_buf_cur >= pIn_buf_end) { \ + for ( ; ; ) { \ + if (decomp_flags & DRFS_TINFL_FLAG_HAS_MORE_INPUT) { \ + DRFS_TINFL_CR_RETURN(state_index, DRFS_TINFL_STATUS_NEEDS_MORE_INPUT); \ + if (pIn_buf_cur < pIn_buf_end) { \ + c = *pIn_buf_cur++; \ + break; \ + } \ + } else { \ + c = 0; \ + break; \ + } \ + } \ + } else c = *pIn_buf_cur++; } DRFS_MZ_MACRO_END + +#define DRFS_TINFL_NEED_BITS(state_index, n) do { drfs_mz_uint c; DRFS_TINFL_GET_BYTE(state_index, c); bit_buf |= (((drfs_tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (drfs_mz_uint)(n)) +#define DRFS_TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (drfs_mz_uint)(n)) { DRFS_TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } DRFS_MZ_MACRO_END +#define DRFS_TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (drfs_mz_uint)(n)) { DRFS_TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } DRFS_MZ_MACRO_END + +// DRFS_TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. +// It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a +// Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the +// bit buffer contains >=15 bits (deflate's max. Huffman code size). +#define DRFS_TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do { \ + temp = (pHuff)->m_look_up[bit_buf & (DRFS_TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } else if (num_bits > DRFS_TINFL_FAST_LOOKUP_BITS) { \ + code_len = DRFS_TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \ + } DRFS_TINFL_GET_BYTE(state_index, c); bit_buf |= (((drfs_tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \ + } while (num_bits < 15); + +// DRFS_TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read +// beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully +// decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. +// The slow path is only executed at the very end of the input buffer. +#define DRFS_TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \ + int temp; drfs_mz_uint code_len, c; \ + if (num_bits < 15) { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) { \ + DRFS_TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } else { \ + bit_buf |= (((drfs_tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((drfs_tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (DRFS_TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else { \ + code_len = DRFS_TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \ + } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } DRFS_MZ_MACRO_END + +drfs_tinfl_status drfs_tinfl_decompress(drfs_tinfl_decompressor *r, const drfs_mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, drfs_mz_uint8 *pOut_buf_start, drfs_mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const drfs_drfs_mz_uint32 decomp_flags) +{ + static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; + static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + static const drfs_mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + static const int s_min_table_sizes[3] = { 257, 1, 4 }; + + drfs_tinfl_status status = DRFS_TINFL_STATUS_FAILED; drfs_drfs_mz_uint32 num_bits, dist, counter, num_extra; drfs_tinfl_bit_buf_t bit_buf; + const drfs_mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + drfs_mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = (decomp_flags & DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; + + // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return DRFS_TINFL_STATUS_BAD_PARAM; } + + num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; + DRFS_TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & DRFS_TINFL_FLAG_PARSE_ZLIB_HEADER) + { + DRFS_TINFL_GET_BYTE(1, r->m_zhdr0); DRFS_TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) { DRFS_TINFL_CR_RETURN_FOREVER(36, DRFS_TINFL_STATUS_FAILED); } + } + + do + { + DRFS_TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + DRFS_TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { if (num_bits) DRFS_TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else DRFS_TINFL_GET_BYTE(7, r->m_raw_header[counter]); } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (drfs_mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { DRFS_TINFL_CR_RETURN_FOREVER(39, DRFS_TINFL_STATUS_FAILED); } + while ((counter) && (num_bits)) + { + DRFS_TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) { DRFS_TINFL_CR_RETURN(52, DRFS_TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (drfs_mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; while (pOut_buf_cur >= pOut_buf_end) { DRFS_TINFL_CR_RETURN(9, DRFS_TINFL_STATUS_HAS_MORE_OUTPUT); } + while (pIn_buf_cur >= pIn_buf_end) + { + if (decomp_flags & DRFS_TINFL_FLAG_HAS_MORE_INPUT) + { + DRFS_TINFL_CR_RETURN(38, DRFS_TINFL_STATUS_NEEDS_MORE_INPUT); + } + else + { + DRFS_TINFL_CR_RETURN_FOREVER(40, DRFS_TINFL_STATUS_FAILED); + } + } + n = DRFS_MZ_MIN(DRFS_MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); + DRFS_TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (drfs_mz_uint)n; + } + } + else if (r->m_type == 3) + { + DRFS_TINFL_CR_RETURN_FOREVER(10, DRFS_TINFL_STATUS_FAILED); + } + else + { + if (r->m_type == 1) + { + drfs_mz_uint8 *p = r->m_tables[0].m_code_size; drfs_mz_uint i; + r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; DRFS_TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for ( i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) { DRFS_TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } + DRFS_MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { drfs_mz_uint s; DRFS_TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (drfs_mz_uint8)s; } + r->m_table_sizes[2] = 19; + } + for ( ; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; drfs_tinfl_huff_table *pTable; + drfs_mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; DRFS_MZ_CLEAR_OBJ(total_syms); DRFS_MZ_CLEAR_OBJ(pTable->m_look_up); DRFS_MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } + if ((65536 != total) && (used_syms > 1)) + { + DRFS_TINFL_CR_RETURN_FOREVER(35, DRFS_TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + drfs_mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue; + cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= DRFS_TINFL_FAST_LOOKUP_BITS) { drfs_mz_int16 k = (drfs_mz_int16)((code_size << 9) | sym_index); while (rev_code < DRFS_TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; } + if (0 == (tree_cur = pTable->m_look_up[rev_code & (DRFS_TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (DRFS_TINFL_FAST_LOOKUP_SIZE - 1)] = (drfs_mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } + rev_code >>= (DRFS_TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (DRFS_TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (drfs_mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (drfs_mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); ) + { + drfs_mz_uint s; DRFS_TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (drfs_mz_uint8)dist; continue; } + if ((dist == 16) && (!counter)) + { + DRFS_TINFL_CR_RETURN_FOREVER(17, DRFS_TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; DRFS_TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; + DRFS_TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + DRFS_TINFL_CR_RETURN_FOREVER(21, DRFS_TINFL_STATUS_FAILED); + } + DRFS_TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); DRFS_TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for ( ; ; ) + { + drfs_mz_uint8 *pSrc; + for ( ; ; ) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + DRFS_TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) { DRFS_TINFL_CR_RETURN(24, DRFS_TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (drfs_mz_uint8)counter; + } + else + { + int sym2; drfs_mz_uint code_len; +#if DRFS_TINFL_USE_64BIT_BITBUF + if (num_bits < 30) { bit_buf |= (((drfs_tinfl_bit_buf_t)DRFS_MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } +#else + if (num_bits < 15) { bit_buf |= (((drfs_tinfl_bit_buf_t)DRFS_MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (DRFS_TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = DRFS_TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + counter = sym2; bit_buf >>= code_len; num_bits -= code_len; + if (counter & 256) + break; + +#if !DRFS_TINFL_USE_64BIT_BITBUF + if (num_bits < 15) { bit_buf |= (((drfs_tinfl_bit_buf_t)DRFS_MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (DRFS_TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = DRFS_TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + bit_buf >>= code_len; num_bits -= code_len; + + pOut_buf_cur[0] = (drfs_mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (drfs_mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) break; + + num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; + if (num_extra) { drfs_mz_uint extra_bits; DRFS_TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } + + DRFS_TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; + if (num_extra) { drfs_mz_uint extra_bits; DRFS_TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && (decomp_flags & DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + DRFS_TINFL_CR_RETURN_FOREVER(37, DRFS_TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((DRFS_MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) { DRFS_TINFL_CR_RETURN(53, DRFS_TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if DRFS_MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const drfs_mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { + ((drfs_drfs_mz_uint32 *)pOut_buf_cur)[0] = ((const drfs_drfs_mz_uint32 *)pSrc)[0]; + ((drfs_drfs_mz_uint32 *)pOut_buf_cur)[1] = ((const drfs_drfs_mz_uint32 *)pSrc)[1]; + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) + { + if (counter) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + do + { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; pSrc += 3; + } while ((int)(counter -= 3) > 2); + if ((int)counter > 0) + { + pOut_buf_cur[0] = pSrc[0]; + if ((int)counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + if (decomp_flags & DRFS_TINFL_FLAG_PARSE_ZLIB_HEADER) + { + DRFS_TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { drfs_mz_uint s; if (num_bits) DRFS_TINFL_GET_BITS(41, s, 8); else DRFS_TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } + } + DRFS_TINFL_CR_RETURN_FOREVER(34, DRFS_TINFL_STATUS_DONE); + DRFS_TINFL_CR_FINISH + +common_exit: + r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (DRFS_TINFL_FLAG_PARSE_ZLIB_HEADER | DRFS_TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const drfs_mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; + drfs_drfs_mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; if ((status == DRFS_TINFL_STATUS_DONE) && (decomp_flags & DRFS_TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = DRFS_TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +// Higher level helper functions. +void *drfs_tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + drfs_tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + drfs_tinfl_init(&decomp); + for ( ; ; ) + { + size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + drfs_tinfl_status status = drfs_tinfl_decompress(&decomp, (const drfs_mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (drfs_mz_uint8*)pBuf, pBuf ? (drfs_mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size, + (flags & ~DRFS_TINFL_FLAG_HAS_MORE_INPUT) | DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == DRFS_TINFL_STATUS_NEEDS_MORE_INPUT)) + { + DRFS_MZ_FREE(pBuf); *pOut_len = 0; return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == DRFS_TINFL_STATUS_DONE) break; + new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; + pNew_buf = DRFS_MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) + { + DRFS_MZ_FREE(pBuf); *pOut_len = 0; return NULL; + } + pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t drfs_tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + drfs_tinfl_decompressor decomp; drfs_tinfl_status status; drfs_tinfl_init(&decomp); + status = drfs_tinfl_decompress(&decomp, (const drfs_mz_uint8*)pSrc_buf, &src_buf_len, (drfs_mz_uint8*)pOut_buf, (drfs_mz_uint8*)pOut_buf, &out_buf_len, (flags & ~DRFS_TINFL_FLAG_HAS_MORE_INPUT) | DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != DRFS_TINFL_STATUS_DONE) ? DRFS_TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; +} + +int drfs_tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + int result = 0; + drfs_tinfl_decompressor decomp; + drfs_mz_uint8 *pDict = (drfs_mz_uint8*)DRFS_MZ_MALLOC(DRFS_TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return DRFS_TINFL_STATUS_FAILED; + drfs_tinfl_init(&decomp); + for ( ; ; ) + { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = DRFS_TINFL_LZ_DICT_SIZE - dict_ofs; + drfs_tinfl_status status = drfs_tinfl_decompress(&decomp, (const drfs_mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(DRFS_TINFL_FLAG_HAS_MORE_INPUT | DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != DRFS_TINFL_STATUS_HAS_MORE_OUTPUT) + { + result = (status == DRFS_TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (DRFS_TINFL_LZ_DICT_SIZE - 1); + } + DRFS_MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + + +// ------------------- .ZIP archive reading + +#ifndef DRFS_MINIZ_NO_ARCHIVE_APIS + +#define DRFS_MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +// Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. +enum +{ + // ZIP archive identifiers and record sizes + DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + // Central directory header record offsets + DRFS_MZ_ZIP_CDH_SIG_OFS = 0, DRFS_MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, DRFS_MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, DRFS_MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + DRFS_MZ_ZIP_CDH_METHOD_OFS = 10, DRFS_MZ_ZIP_CDH_FILE_TIME_OFS = 12, DRFS_MZ_ZIP_CDH_FILE_DATE_OFS = 14, DRFS_MZ_ZIP_CDH_CRC32_OFS = 16, + DRFS_MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, DRFS_MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, DRFS_MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + DRFS_MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, DRFS_MZ_ZIP_CDH_DISK_START_OFS = 34, DRFS_MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, DRFS_MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, DRFS_MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + // Local directory header offsets + DRFS_MZ_ZIP_LDH_SIG_OFS = 0, DRFS_MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, DRFS_MZ_ZIP_LDH_BIT_FLAG_OFS = 6, DRFS_MZ_ZIP_LDH_METHOD_OFS = 8, DRFS_MZ_ZIP_LDH_FILE_TIME_OFS = 10, + DRFS_MZ_ZIP_LDH_FILE_DATE_OFS = 12, DRFS_MZ_ZIP_LDH_CRC32_OFS = 14, DRFS_MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, DRFS_MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + DRFS_MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, DRFS_MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + // End of central directory offsets + DRFS_MZ_ZIP_ECDH_SIG_OFS = 0, DRFS_MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, DRFS_MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, DRFS_MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + DRFS_MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, DRFS_MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, DRFS_MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, DRFS_MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, +}; + +typedef struct +{ + void *m_p; + size_t m_size, m_capacity; + drfs_mz_uint m_element_size; +} drfs_mz_zip_array; + +struct drfs_mz_zip_internal_state_tag +{ + drfs_mz_zip_array m_central_dir; + drfs_mz_zip_array m_central_dir_offsets; + drfs_mz_zip_array m_sorted_central_dir_offsets; + void* *m_pFile; + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define DRFS_MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size +#define DRFS_MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] + +static DRFS_MZ_FORCEINLINE void drfs_mz_zip_array_clear(drfs_mz_zip_archive *pZip, drfs_mz_zip_array *pArray) +{ + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(drfs_mz_zip_array)); +} + +static drfs_mz_bool drfs_mz_zip_array_ensure_capacity(drfs_mz_zip_archive *pZip, drfs_mz_zip_array *pArray, size_t min_new_capacity, drfs_mz_uint growing) +{ + void *pNew_p; size_t new_capacity = min_new_capacity; DRFS_MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return DRFS_MZ_TRUE; + if (growing) { new_capacity = DRFS_MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return DRFS_MZ_FALSE; + pArray->m_p = pNew_p; pArray->m_capacity = new_capacity; + return DRFS_MZ_TRUE; +} + +static DRFS_MZ_FORCEINLINE drfs_mz_bool drfs_mz_zip_array_reserve(drfs_mz_zip_archive *pZip, drfs_mz_zip_array *pArray, size_t new_capacity, drfs_mz_uint growing) +{ + if (new_capacity > pArray->m_capacity) { if (!drfs_mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return DRFS_MZ_FALSE; } + return DRFS_MZ_TRUE; +} + +static DRFS_MZ_FORCEINLINE drfs_mz_bool drfs_mz_zip_array_resize(drfs_mz_zip_archive *pZip, drfs_mz_zip_array *pArray, size_t new_size, drfs_mz_uint growing) +{ + if (new_size > pArray->m_capacity) { if (!drfs_mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return DRFS_MZ_FALSE; } + pArray->m_size = new_size; + return DRFS_MZ_TRUE; +} + +static DRFS_MZ_FORCEINLINE drfs_mz_bool drfs_mz_zip_array_ensure_room(drfs_mz_zip_archive *pZip, drfs_mz_zip_array *pArray, size_t n) +{ + return drfs_mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, DRFS_MZ_TRUE); +} + +static DRFS_MZ_FORCEINLINE drfs_mz_bool drfs_mz_zip_array_push_back(drfs_mz_zip_archive *pZip, drfs_mz_zip_array *pArray, const void *pElements, size_t n) +{ + size_t orig_size = pArray->m_size; if (!drfs_mz_zip_array_resize(pZip, pArray, orig_size + n, DRFS_MZ_TRUE)) return DRFS_MZ_FALSE; + memcpy((drfs_mz_uint8*)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); + return DRFS_MZ_TRUE; +} + +#ifndef DRFS_MINIZ_NO_TIME +static time_t drfs_mz_zip_dos_to_time_t(int dos_time, int dos_date) +{ + struct tm tm; + memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} +#endif + +static drfs_mz_bool drfs_mz_zip_reader_init_internal(drfs_mz_zip_archive *pZip, drfs_drfs_mz_uint32 flags) +{ + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != DRFS_MZ_ZIP_MODE_INVALID)) + return DRFS_MZ_FALSE; + + if (!pZip->m_pAlloc) pZip->m_pAlloc = drfs__def_alloc_func; + if (!pZip->m_pFree) pZip->m_pFree = drfs__def_free_func; + if (!pZip->m_pRealloc) pZip->m_pRealloc = drfs__def_realloc_func; + + pZip->m_zip_mode = DRFS_MZ_ZIP_MODE_READING; + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (drfs_mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(drfs_mz_zip_internal_state)))) + return DRFS_MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(drfs_mz_zip_internal_state)); + DRFS_MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(drfs_mz_uint8)); + DRFS_MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(drfs_drfs_mz_uint32)); + DRFS_MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(drfs_drfs_mz_uint32)); + return DRFS_MZ_TRUE; +} + +static DRFS_MZ_FORCEINLINE drfs_mz_bool drfs_mz_zip_reader_filename_less(const drfs_mz_zip_array *pCentral_dir_array, const drfs_mz_zip_array *pCentral_dir_offsets, drfs_mz_uint l_index, drfs_mz_uint r_index) +{ + const drfs_mz_uint8 *pL = &DRFS_MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, drfs_mz_uint8, DRFS_MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, drfs_drfs_mz_uint32, l_index)), *pE; + const drfs_mz_uint8 *pR = &DRFS_MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, drfs_mz_uint8, DRFS_MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, drfs_drfs_mz_uint32, r_index)); + drfs_mz_uint l_len = DRFS_MZ_READ_LE16(pL + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = DRFS_MZ_READ_LE16(pR + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS); + drfs_mz_uint8 l = 0, r = 0; + pL += DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + DRFS_MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = DRFS_MZ_TOLOWER(*pL)) != (r = DRFS_MZ_TOLOWER(*pR))) + break; + pL++; pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define DRFS_MZ_SWAP_UINT32(a, b) do { drfs_drfs_mz_uint32 t = a; a = b; b = t; } DRFS_MZ_MACRO_END + +// Heap sort of lowercased filenames, used to help accelerate plain central directory searches by drfs_mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) +static void drfs_mz_zip_reader_sort_central_dir_offsets_by_filename(drfs_mz_zip_archive *pZip) +{ + drfs_mz_zip_internal_state *pState = pZip->m_pState; + const drfs_mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const drfs_mz_zip_array *pCentral_dir = &pState->m_central_dir; + drfs_drfs_mz_uint32 *pIndices = &DRFS_MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, drfs_drfs_mz_uint32, 0); + const int size = pZip->m_total_files; + int start = (size - 2) >> 1, end; + while (start >= 0) + { + int child, root = start; + for ( ; ; ) + { + if ((child = (root << 1) + 1) >= size) + break; + child += (((child + 1) < size) && (drfs_mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1]))); + if (!drfs_mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + DRFS_MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + } + start--; + } + + end = size - 1; + while (end > 0) + { + int child, root = 0; + DRFS_MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for ( ; ; ) + { + if ((child = (root << 1) + 1) >= end) + break; + child += (((child + 1) < end) && drfs_mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1])); + if (!drfs_mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + DRFS_MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + } + end--; + } +} + +static drfs_mz_bool drfs_mz_zip_reader_read_central_dir(drfs_mz_zip_archive *pZip, drfs_drfs_mz_uint32 flags) +{ + drfs_mz_uint cdir_size, num_this_disk, cdir_disk_index; + drfs_mz_uint64 cdir_ofs; + drfs_mz_int64 cur_file_ofs; + const drfs_mz_uint8 *p; + drfs_drfs_mz_uint32 buf_u32[4096 / sizeof(drfs_drfs_mz_uint32)]; drfs_mz_uint8 *pBuf = (drfs_mz_uint8 *)buf_u32; + drfs_mz_bool sort_central_dir = ((flags & DRFS_MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + // Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. + if (pZip->m_archive_size < DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return DRFS_MZ_FALSE; + // Find the end of central directory record by scanning the file from the end towards the beginning. + cur_file_ofs = DRFS_MZ_MAX((drfs_mz_int64)pZip->m_archive_size - (drfs_mz_int64)sizeof(buf_u32), 0); + for ( ; ; ) + { + int i, n = (int)DRFS_MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (drfs_mz_uint)n) + return DRFS_MZ_FALSE; + for (i = n - 4; i >= 0; --i) + if (DRFS_MZ_READ_LE32(pBuf + i) == DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + break; + if (i >= 0) + { + cur_file_ofs += i; + break; + } + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (0xFFFF + DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + return DRFS_MZ_FALSE; + cur_file_ofs = DRFS_MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + // Read and verify the end of central directory record. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return DRFS_MZ_FALSE; + if ((DRFS_MZ_READ_LE32(pBuf + DRFS_MZ_ZIP_ECDH_SIG_OFS) != DRFS_MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) || + ((pZip->m_total_files = DRFS_MZ_READ_LE16(pBuf + DRFS_MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != DRFS_MZ_READ_LE16(pBuf + DRFS_MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS))) + return DRFS_MZ_FALSE; + + num_this_disk = DRFS_MZ_READ_LE16(pBuf + DRFS_MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = DRFS_MZ_READ_LE16(pBuf + DRFS_MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) + return DRFS_MZ_FALSE; + + if ((cdir_size = DRFS_MZ_READ_LE32(pBuf + DRFS_MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < pZip->m_total_files * DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return DRFS_MZ_FALSE; + + cdir_ofs = DRFS_MZ_READ_LE32(pBuf + DRFS_MZ_ZIP_ECDH_CDIR_OFS_OFS); + if ((cdir_ofs + (drfs_mz_uint64)cdir_size) > pZip->m_archive_size) + return DRFS_MZ_FALSE; + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) + { + drfs_mz_uint i, n; + + // Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and another to hold the sorted indices. + if ((!drfs_mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, DRFS_MZ_FALSE)) || + (!drfs_mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, DRFS_MZ_FALSE))) + return DRFS_MZ_FALSE; + + if (sort_central_dir) + { + if (!drfs_mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, DRFS_MZ_FALSE)) + return DRFS_MZ_FALSE; + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) + return DRFS_MZ_FALSE; + + // Now create an index into the central directory file records, do some basic sanity checking on each record, and check for zip64 entries (which are not yet supported). + p = (const drfs_mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) + { + drfs_mz_uint total_header_size, comp_size, decomp_size, disk_index; + if ((n < DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (DRFS_MZ_READ_LE32(p) != DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return DRFS_MZ_FALSE; + DRFS_MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, drfs_drfs_mz_uint32, i) = (drfs_drfs_mz_uint32)(p - (const drfs_mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + if (sort_central_dir) + DRFS_MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, drfs_drfs_mz_uint32, i) = i; + comp_size = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + if (((!DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || (comp_size == 0xFFFFFFFF)) + return DRFS_MZ_FALSE; + disk_index = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index != num_this_disk) && (disk_index != 1)) + return DRFS_MZ_FALSE; + if (((drfs_mz_uint64)DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_LOCAL_HEADER_OFS) + DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return DRFS_MZ_FALSE; + if ((total_header_size = DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS) + DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_EXTRA_LEN_OFS) + DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + return DRFS_MZ_FALSE; + n -= total_header_size; p += total_header_size; + } + } + + if (sort_central_dir) + drfs_mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return DRFS_MZ_TRUE; +} + +drfs_mz_bool drfs_mz_zip_reader_init(drfs_mz_zip_archive *pZip, drfs_mz_uint64 size, drfs_drfs_mz_uint32 flags) +{ + if ((!pZip) || (!pZip->m_pRead)) + return DRFS_MZ_FALSE; + if (!drfs_mz_zip_reader_init_internal(pZip, flags)) + return DRFS_MZ_FALSE; + pZip->m_archive_size = size; + if (!drfs_mz_zip_reader_read_central_dir(pZip, flags)) + { + drfs_mz_zip_reader_end(pZip); + return DRFS_MZ_FALSE; + } + return DRFS_MZ_TRUE; +} + +drfs_mz_uint drfs_mz_zip_reader_get_num_files(drfs_mz_zip_archive *pZip) +{ + return pZip ? pZip->m_total_files : 0; +} + +static DRFS_MZ_FORCEINLINE const drfs_mz_uint8 *drfs_mz_zip_reader_get_cdh(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index) +{ + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || (pZip->m_zip_mode != DRFS_MZ_ZIP_MODE_READING)) + return NULL; + return &DRFS_MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, drfs_mz_uint8, DRFS_MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, drfs_drfs_mz_uint32, file_index)); +} + +drfs_mz_bool drfs_mz_zip_reader_is_file_a_directory(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index) +{ + drfs_mz_uint filename_len, external_attr; + const drfs_mz_uint8 *p = drfs_mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return DRFS_MZ_FALSE; + + // First see if the filename ends with a '/' character. + filename_len = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) + { + if (*(p + DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return DRFS_MZ_TRUE; + } + + // Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. + // Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. + // FIXME: Remove this check? Is it necessary - we already check the filename. + external_attr = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & 0x10) != 0) + return DRFS_MZ_TRUE; + + return DRFS_MZ_FALSE; +} + +drfs_mz_bool drfs_mz_zip_reader_file_stat(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, drfs_drfs_mz_zip_archive_file_stat *pStat) +{ + drfs_mz_uint n; + const drfs_mz_uint8 *p = drfs_mz_zip_reader_get_cdh(pZip, file_index); + if ((!p) || (!pStat)) + return DRFS_MZ_FALSE; + + // Unpack the central directory record. + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = DRFS_MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, drfs_drfs_mz_uint32, file_index); + pStat->m_version_made_by = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_METHOD_OFS); +#ifndef DRFS_MINIZ_NO_TIME + pStat->m_time = drfs_mz_zip_dos_to_time_t(DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_FILE_TIME_OFS), DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + // Copy as much of the filename and comment as possible. + n = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS); n = DRFS_MZ_MIN(n, DRFS_MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0'; + + n = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_COMMENT_LEN_OFS); n = DRFS_MZ_MIN(n, DRFS_MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, p + DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS) + DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0'; + + return DRFS_MZ_TRUE; +} + +drfs_mz_uint drfs_mz_zip_reader_get_filename(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, char *pFilename, drfs_mz_uint filename_buf_size) +{ + drfs_mz_uint n; + const drfs_mz_uint8 *p = drfs_mz_zip_reader_get_cdh(pZip, file_index); + if (!p) { if (filename_buf_size) pFilename[0] = '\0'; return 0; } + n = DRFS_MZ_READ_LE16(p + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) + { + n = DRFS_MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +static DRFS_MZ_FORCEINLINE drfs_mz_bool drfs_mz_zip_reader_string_equal(const char *pA, const char *pB, drfs_mz_uint len, drfs_mz_uint flags) +{ + drfs_mz_uint i; + if (flags & DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (DRFS_MZ_TOLOWER(pA[i]) != DRFS_MZ_TOLOWER(pB[i])) + return DRFS_MZ_FALSE; + return DRFS_MZ_TRUE; +} + +static DRFS_MZ_FORCEINLINE int drfs_mz_zip_reader_filename_compare(const drfs_mz_zip_array *pCentral_dir_array, const drfs_mz_zip_array *pCentral_dir_offsets, drfs_mz_uint l_index, const char *pR, drfs_mz_uint r_len) +{ + const drfs_mz_uint8 *pL = &DRFS_MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, drfs_mz_uint8, DRFS_MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, drfs_drfs_mz_uint32, l_index)), *pE; + drfs_mz_uint l_len = DRFS_MZ_READ_LE16(pL + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS); + drfs_mz_uint8 l = 0, r = 0; + pL += DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + DRFS_MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = DRFS_MZ_TOLOWER(*pL)) != (r = DRFS_MZ_TOLOWER(*pR))) + break; + pL++; pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static int drfs_mz_zip_reader_locate_file_binary_search(drfs_mz_zip_archive *pZip, const char *pFilename) +{ + drfs_mz_zip_internal_state *pState = pZip->m_pState; + const drfs_mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const drfs_mz_zip_array *pCentral_dir = &pState->m_central_dir; + drfs_drfs_mz_uint32 *pIndices = &DRFS_MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, drfs_drfs_mz_uint32, 0); + const int size = pZip->m_total_files; + const drfs_mz_uint filename_len = (drfs_mz_uint)strlen(pFilename); + int l = 0, h = size - 1; + while (l <= h) + { + int m = (l + h) >> 1, file_index = pIndices[m], comp = drfs_mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); + if (!comp) + return file_index; + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + return -1; +} + +int drfs_mz_zip_reader_locate_file(drfs_mz_zip_archive *pZip, const char *pName, const char *pComment, drfs_mz_uint flags) +{ + drfs_mz_uint file_index; size_t name_len, comment_len; + if ((!pZip) || (!pZip->m_pState) || (!pName) || (pZip->m_zip_mode != DRFS_MZ_ZIP_MODE_READING)) + return -1; + if (((flags & (DRFS_MZ_ZIP_FLAG_IGNORE_PATH | DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + return drfs_mz_zip_reader_locate_file_binary_search(pZip, pName); + name_len = strlen(pName); if (name_len > 0xFFFF) return -1; + comment_len = pComment ? strlen(pComment) : 0; if (comment_len > 0xFFFF) return -1; + for (file_index = 0; file_index < pZip->m_total_files; file_index++) + { + const drfs_mz_uint8 *pHeader = &DRFS_MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, drfs_mz_uint8, DRFS_MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, drfs_drfs_mz_uint32, file_index)); + drfs_mz_uint filename_len = DRFS_MZ_READ_LE16(pHeader + DRFS_MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + DRFS_MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) + { + drfs_mz_uint file_extra_len = DRFS_MZ_READ_LE16(pHeader + DRFS_MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = DRFS_MZ_READ_LE16(pHeader + DRFS_MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || (!drfs_mz_zip_reader_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + if ((flags & DRFS_MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) + { + int ofs = filename_len - 1; + do + { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; filename_len -= ofs; + } + if ((filename_len == name_len) && (drfs_mz_zip_reader_string_equal(pName, pFilename, filename_len, flags))) + return file_index; + } + return -1; +} + +drfs_mz_bool drfs_mz_zip_reader_extract_to_mem_no_alloc(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, void *pBuf, size_t buf_size, drfs_mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int status = DRFS_TINFL_STATUS_DONE; + drfs_mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + drfs_drfs_mz_zip_archive_file_stat file_stat; + void *pRead_buf; + drfs_drfs_mz_uint32 local_header_u32[(DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(drfs_drfs_mz_uint32) - 1) / sizeof(drfs_drfs_mz_uint32)]; drfs_mz_uint8 *pLocal_header = (drfs_mz_uint8 *)local_header_u32; + drfs_tinfl_decompressor inflator; + + if ((buf_size) && (!pBuf)) + return DRFS_MZ_FALSE; + + if (!drfs_mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return DRFS_MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return DRFS_MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (drfs_mz_zip_reader_is_file_a_directory(pZip, file_index)) + return DRFS_MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return DRFS_MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & DRFS_MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != DRFS_MZ_DEFLATED)) + return DRFS_MZ_FALSE; + + // Ensure supplied output buffer is large enough. + needed_size = (flags & DRFS_MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return DRFS_MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return DRFS_MZ_FALSE; + if (DRFS_MZ_READ_LE32(pLocal_header) != DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return DRFS_MZ_FALSE; + + cur_file_ofs += DRFS_MZ_ZIP_LOCAL_DIR_HEADER_SIZE + DRFS_MZ_READ_LE16(pLocal_header + DRFS_MZ_ZIP_LDH_FILENAME_LEN_OFS) + DRFS_MZ_READ_LE16(pLocal_header + DRFS_MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return DRFS_MZ_FALSE; + + if ((flags & DRFS_MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) + return DRFS_MZ_FALSE; + return ((flags & DRFS_MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || (drfs_mz_crc32(DRFS_MZ_CRC32_INIT, (const drfs_mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32); + } + + // Decompress the file either directly from memory or from a file input buffer. + drfs_tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) + { + // Read directly from the archive in memory. + pRead_buf = (drfs_mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else if (pUser_read_buf) + { + // Use a user provided read buffer. + if (!user_read_buf_size) + return DRFS_MZ_FALSE; + pRead_buf = (drfs_mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + else + { + // Temporarily allocate a read buffer. + read_buf_size = DRFS_MZ_MIN(file_stat.m_comp_size, DRFS_MZ_ZIP_MAX_IO_BUF_SIZE); +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(drfs_drfs_mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(drfs_drfs_mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#endif + return DRFS_MZ_FALSE; + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return DRFS_MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do + { + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = DRFS_MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = DRFS_TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = drfs_tinfl_decompress(&inflator, (drfs_mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (drfs_mz_uint8 *)pBuf, (drfs_mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, DRFS_TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? DRFS_TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == DRFS_TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == DRFS_TINFL_STATUS_DONE) + { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || (drfs_mz_crc32(DRFS_MZ_CRC32_INIT, (const drfs_mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)) + status = DRFS_TINFL_STATUS_FAILED; + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == DRFS_TINFL_STATUS_DONE; +} + +drfs_mz_bool drfs_mz_zip_reader_extract_file_to_mem_no_alloc(drfs_mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, drfs_mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int file_index = drfs_mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return DRFS_MZ_FALSE; + return drfs_mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); +} + +drfs_mz_bool drfs_mz_zip_reader_extract_to_mem(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, void *pBuf, size_t buf_size, drfs_mz_uint flags) +{ + return drfs_mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); +} + +drfs_mz_bool drfs_mz_zip_reader_extract_file_to_mem(drfs_mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, drfs_mz_uint flags) +{ + return drfs_mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); +} + +void *drfs_mz_zip_reader_extract_to_heap(drfs_mz_zip_archive *pZip, drfs_mz_uint file_index, size_t *pSize, drfs_mz_uint flags) +{ + drfs_mz_uint64 comp_size, uncomp_size, alloc_size; + const drfs_mz_uint8 *p = drfs_mz_zip_reader_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + if (!p) + return NULL; + + comp_size = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = DRFS_MZ_READ_LE32(p + DRFS_MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & DRFS_MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(drfs_drfs_mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(drfs_drfs_mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#endif + return NULL; + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + return NULL; + + if (!drfs_mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) *pSize = (size_t)alloc_size; + return pBuf; +} + +drfs_mz_bool drfs_mz_zip_reader_end(drfs_mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != DRFS_MZ_ZIP_MODE_READING)) + return DRFS_MZ_FALSE; + + if (pZip->m_pState) + { + drfs_mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL; + drfs_mz_zip_array_clear(pZip, &pState->m_central_dir); + drfs_mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + drfs_mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + pZip->m_zip_mode = DRFS_MZ_ZIP_MODE_INVALID; + + return DRFS_MZ_TRUE; +} + +#endif // #ifndef DRFS_MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +} +#endif + +#endif // DRFS_MINIZ_HEADER_FILE_ONLY + +#if defined(__GNUC__) + #pragma GCC diagnostic pop +#endif +#if defined(_MSC_VER) + #pragma warning(pop) +#endif + + + + +typedef struct +{ + // The current index of the iterator. When this hits the file count, the iteration is finished. + unsigned int index; + + // The directory being iterated. + char directoryPath[DRFS_MAX_PATH]; + +}drfs_iterator_zip; + +typedef struct +{ + // The file index within the archive. + drfs_mz_uint index; + + // A pointer to the buffer containing the entire uncompressed data of the file. Unfortunately this is the only way I'm aware of for + // reading file data from miniz.c so we'll just stick with it for now. We use a pointer to an 8-bit type so we can easily calculate + // offsets. + drfs_mz_uint8* pData; + + // The size of the file in bytes so we can guard against overflowing reads. + size_t sizeInBytes; + + // The current position of the file's read pointer. + size_t readPointer; + +}drfs_openedfile_zip; + +static size_t drfs_drfs_mz_file_read_func(void *pOpaque, drfs_mz_uint64 file_ofs, void *pBuf, size_t n) +{ + // The opaque type is a pointer to a drfs_file object which represents the file of the archive. + drfs_file* pZipFile = (drfs_file*)pOpaque; + assert(pZipFile != NULL); + + if (!drfs_lock(pZipFile)) { + return 0; + } + + drfs_seek_nolock(pZipFile, (dr_int64)file_ofs, drfs_origin_start); + + size_t bytesRead; + drfs_result result = drfs_read_nolock(pZipFile, pBuf, (unsigned int)n, &bytesRead); + if (result != drfs_success) { + // Failed to read the file. + bytesRead = 0; + } + + drfs_unlock(pZipFile); + return (size_t)bytesRead; +} + + +static dr_bool32 drfs_is_valid_extension__zip(const char* extension) +{ + return drfs__stricmp(extension, "zip") == 0; +} + +static drfs_result drfs_open_archive__zip(drfs_file* pArchiveFile, unsigned int accessMode, drfs_handle* pHandleOut) +{ + assert(pArchiveFile != NULL); + assert(pHandleOut != NULL); + assert(drfs_tell(pArchiveFile) == 0); + + *pHandleOut = NULL; + + // Only support read-only mode at the moment. + if ((accessMode & DRFS_WRITE) != 0) { + return drfs_permission_denied; + } + + + drfs_mz_zip_archive* pZip = (drfs_mz_zip_archive*)malloc(sizeof(drfs_mz_zip_archive)); + if (pZip == NULL) { + return drfs_out_of_memory; + } + + memset(pZip, 0, sizeof(drfs_mz_zip_archive)); + + pZip->m_pRead = drfs_drfs_mz_file_read_func; + pZip->m_pIO_opaque = pArchiveFile; + if (!drfs_mz_zip_reader_init(pZip, drfs_size(pArchiveFile), 0)) { + free(pZip); + return drfs_invalid_archive; + } + + *pHandleOut = pZip; + return drfs_success; +} + +static void drfs_close_archive__zip(drfs_handle archive) +{ + assert(archive != NULL); + + drfs_mz_zip_reader_end((drfs_mz_zip_archive*)archive); + free(archive); +} + +static drfs_result drfs_get_file_info__zip(drfs_handle archive, const char* relativePath, drfs_file_info* fi) +{ + assert(archive != NULL); + + drfs_mz_zip_archive* pZip = (drfs_mz_zip_archive*)archive; + int fileIndex = drfs_mz_zip_reader_locate_file(pZip, relativePath, NULL, DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE); + if (fileIndex == -1) + { + // We failed to locate the file, but there's a chance it could actually be a folder. Here's the problem - folders + // can be named such that they include a trailing slash. We'll want to check for that. Another problem is that + // sometimes the folders won't actually be included in the central directory at all which means we need to do a + // manual check across every file in the archive. + char relativePathWithSlash[DRFS_MAX_PATH]; + drfs__strcpy_s(relativePathWithSlash, sizeof(relativePathWithSlash), relativePath); + drfs__strcat_s(relativePathWithSlash, sizeof(relativePathWithSlash), "/"); + fileIndex = drfs_mz_zip_reader_locate_file(pZip, relativePath, NULL, DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE); + if (fileIndex == -1) + { + // We still couldn't find the directory even with the trailing slash. There's a chace it's a folder that's + // simply not included in the central directory. It's appears the "Send to -> Compressed (zipped) folder" + // functionality in Windows does this. + drfs_mz_uint numFiles = drfs_mz_zip_reader_get_num_files(pZip); + for (drfs_mz_uint iFile = 0; iFile < numFiles; ++iFile) + { + char filePath[DRFS_MAX_PATH]; + if (drfs_mz_zip_reader_get_filename(pZip, iFile, filePath, DRFS_MAX_PATH) > 0) + { + if (drfs_drpath_is_child(filePath, relativePath)) + { + // This file is within a folder with a path of relativePath which means we can imply that relativePath + // is a folder. + drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), relativePath); + fi->sizeInBytes = 0; + fi->lastModifiedTime = 0; + fi->attributes = DRFS_FILE_ATTRIBUTE_READONLY | DRFS_FILE_ATTRIBUTE_DIRECTORY; + + return drfs_success; + } + } + } + + return drfs_does_not_exist; + } + } + + assert(fileIndex != -1); + + if (fi != NULL) + { + drfs_drfs_mz_zip_archive_file_stat zipStat; + if (drfs_mz_zip_reader_file_stat(pZip, (drfs_mz_uint)fileIndex, &zipStat)) + { + drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), relativePath); + fi->sizeInBytes = zipStat.m_uncomp_size; + fi->lastModifiedTime = (dr_uint64)zipStat.m_time; + fi->attributes = DRFS_FILE_ATTRIBUTE_READONLY; + if (drfs_mz_zip_reader_is_file_a_directory(pZip, (drfs_mz_uint)fileIndex)) { + fi->attributes |= DRFS_FILE_ATTRIBUTE_DIRECTORY; + } + + return drfs_success; + } + } + + return drfs_success; +} + +static drfs_handle drfs_begin_iteration__zip(drfs_handle archive, const char* relativePath) +{ + assert(relativePath != NULL); + + drfs_mz_zip_archive* pZip = (drfs_mz_zip_archive*)archive; + assert(pZip != NULL); + + int directoryFileIndex = -1; + if (relativePath[0] == '\0') { + directoryFileIndex = 0; + } else { + directoryFileIndex = drfs_mz_zip_reader_locate_file(pZip, relativePath, NULL, DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE); + } + + if (directoryFileIndex == -1) + { + // The same issue applies here as documented in drfs_get_file_info__zip(). + char relativePathWithSlash[DRFS_MAX_PATH]; + drfs__strcpy_s(relativePathWithSlash, sizeof(relativePathWithSlash), relativePath); + drfs__strcat_s(relativePathWithSlash, sizeof(relativePathWithSlash), "/"); + directoryFileIndex = drfs_mz_zip_reader_locate_file(pZip, relativePath, NULL, DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE); + if (directoryFileIndex == -1) + { + // We still couldn't find the directory even with the trailing slash. There's a chace it's a folder that's + // simply not included in the central directory. It's appears the "Send to -> Compressed (zipped) folder" + // functionality in Windows does this. + drfs_mz_uint numFiles = drfs_mz_zip_reader_get_num_files(pZip); + for (drfs_mz_uint iFile = 0; iFile < numFiles; ++iFile) + { + char filePath[DRFS_MAX_PATH]; + if (drfs_mz_zip_reader_get_filename(pZip, iFile, filePath, DRFS_MAX_PATH) > 0) + { + if (drfs_drpath_is_child(filePath, relativePath)) + { + // This file is within a folder with a path of relativePath which means we can imply that relativePath + // is a folder. + goto on_success; + } + } + } + + return NULL; + } + } + + + +on_success:; + drfs_iterator_zip* pZipIterator = (drfs_iterator_zip*)malloc(sizeof(drfs_iterator_zip)); + if (pZipIterator != NULL) + { + pZipIterator->index = 0; + drfs__strcpy_s(pZipIterator->directoryPath, sizeof(pZipIterator->directoryPath), relativePath); + } + + return pZipIterator; +} + +static void drfs_end_iteration__zip(drfs_handle archive, drfs_handle iterator) +{ + (void)archive; + assert(archive != NULL); + assert(iterator != NULL); + + free(iterator); +} + +static dr_bool32 drfs_next_iteration__zip(drfs_handle archive, drfs_handle iterator, drfs_file_info* fi) +{ + (void)archive; + assert(archive != NULL); + assert(iterator != NULL); + + drfs_iterator_zip* pZipIterator = (drfs_iterator_zip*)iterator; + if (pZipIterator == NULL) { + return DR_FALSE; + } + + drfs_mz_zip_archive* pZip = (drfs_mz_zip_archive*)archive; + while (pZipIterator->index < drfs_mz_zip_reader_get_num_files(pZip)) + { + unsigned int iFile = pZipIterator->index++; + + char filePath[DRFS_MAX_PATH]; + if (drfs_mz_zip_reader_get_filename(pZip, iFile, filePath, DRFS_MAX_PATH) > 0) + { + if (drfs_drpath_is_child(filePath, pZipIterator->directoryPath)) + { + if (fi != NULL) + { + drfs_drfs_mz_zip_archive_file_stat zipStat; + if (drfs_mz_zip_reader_file_stat(pZip, iFile, &zipStat)) + { + drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), filePath); + fi->sizeInBytes = zipStat.m_uncomp_size; + fi->lastModifiedTime = (dr_uint64)zipStat.m_time; + fi->attributes = DRFS_FILE_ATTRIBUTE_READONLY; + if (drfs_mz_zip_reader_is_file_a_directory(pZip, iFile)) { + fi->attributes |= DRFS_FILE_ATTRIBUTE_DIRECTORY; + } + + // If we have a directory we need to ensure we don't have a trailing slash. + if ((fi->attributes & DRFS_FILE_ATTRIBUTE_DIRECTORY) != 0) { + size_t absolutePathLen = strlen(fi->absolutePath); + if (absolutePathLen > 0 && (fi->absolutePath[absolutePathLen - 1] == '/' || fi->absolutePath[absolutePathLen - 1] == '\\')) { + fi->absolutePath[absolutePathLen - 1] = '\0'; + } + } + } + } + + return DR_TRUE; + } + } + } + + return DR_FALSE; +} + +static drfs_result drfs_open_file__zip(drfs_handle archive, const char* relativePath, unsigned int accessMode, drfs_handle* pHandleOut) +{ + assert(archive != NULL); + assert(pHandleOut != NULL); + assert(relativePath != NULL); + + *pHandleOut = NULL; + + // Only supporting read-only for now. + if ((accessMode & DRFS_WRITE) != 0) { + return drfs_permission_denied; + } + + + drfs_mz_zip_archive* pZip = (drfs_mz_zip_archive*)archive; + int fileIndex = drfs_mz_zip_reader_locate_file(pZip, relativePath, NULL, DRFS_MZ_ZIP_FLAG_CASE_SENSITIVE); + if (fileIndex == -1) { + return drfs_does_not_exist; + } + + drfs_openedfile_zip* pOpenedFile = (drfs_openedfile_zip*)malloc(sizeof(*pOpenedFile)); + if (pOpenedFile == NULL) { + return drfs_out_of_memory; + } + + pOpenedFile->pData = (drfs_mz_uint8*)drfs_mz_zip_reader_extract_to_heap(pZip, (drfs_mz_uint)fileIndex, &pOpenedFile->sizeInBytes, 0); + if (pOpenedFile->pData == NULL) { + free(pOpenedFile); + return drfs_unknown_error; + } + + pOpenedFile->index = (drfs_mz_uint)fileIndex; + pOpenedFile->readPointer = 0; + + *pHandleOut = pOpenedFile; + return drfs_success; +} + +static void drfs_close_file__zip(drfs_handle archive, drfs_handle file) +{ + drfs_openedfile_zip* pOpenedFile = (drfs_openedfile_zip*)file; + assert(pOpenedFile != NULL); + + drfs_mz_zip_archive* pZip = (drfs_mz_zip_archive*)archive; + assert(pZip != NULL); + + pZip->m_pFree(pZip->m_pAlloc_opaque, pOpenedFile->pData); + free(pOpenedFile); +} + +static drfs_result drfs_read_file__zip(drfs_handle archive, drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut) +{ + (void)archive; + assert(archive != NULL); + assert(file != NULL); + assert(pDataOut != NULL); + assert(bytesToRead > 0); + + drfs_openedfile_zip* pOpenedFile = (drfs_openedfile_zip*)file; + if (pOpenedFile == NULL) { + return drfs_invalid_args; + } + + size_t bytesAvailable = pOpenedFile->sizeInBytes - pOpenedFile->readPointer; + if (bytesAvailable < bytesToRead) { + bytesToRead = bytesAvailable; + } + + if (bytesToRead == 0) { + return drfs_at_end_of_file; // Nothing left to read. + } + + + memcpy(pDataOut, pOpenedFile->pData + pOpenedFile->readPointer, bytesToRead); + pOpenedFile->readPointer += bytesToRead; + + if (pBytesReadOut) { + *pBytesReadOut = bytesToRead; + } + + return drfs_success; +} + +static drfs_result drfs_write_file__zip(drfs_handle archive, drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut) +{ + (void)archive; + (void)file; + (void)pData; + (void)bytesToWrite; + + assert(archive != NULL); + assert(file != NULL); + assert(pData != NULL); + assert(bytesToWrite > 0); + + // All files are read-only for now. + if (pBytesWrittenOut) { + *pBytesWrittenOut = 0; + } + + return drfs_permission_denied; +} + +static drfs_result drfs_seek_file__zip(drfs_handle archive, drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin) +{ + (void)archive; + + assert(archive != NULL); + assert(file != NULL); + + drfs_openedfile_zip* pOpenedFile = (drfs_openedfile_zip*)file; + if (pOpenedFile == NULL) { + return drfs_invalid_args; + } + + dr_uint64 newPos = pOpenedFile->readPointer; + if (origin == drfs_origin_current) + { + if ((dr_int64)newPos + bytesToSeek >= 0) + { + newPos = (dr_uint64)((dr_int64)newPos + bytesToSeek); + } + else + { + // Trying to seek to before the beginning of the file. + return drfs_invalid_args; + } + } + else if (origin == drfs_origin_start) + { + assert(bytesToSeek >= 0); + newPos = (dr_uint64)bytesToSeek; + } + else if (origin == drfs_origin_end) + { + assert(bytesToSeek >= 0); + if ((dr_uint64)bytesToSeek <= pOpenedFile->sizeInBytes) + { + newPos = pOpenedFile->sizeInBytes - (dr_uint64)bytesToSeek; + } + else + { + // Trying to seek to before the beginning of the file. + return drfs_invalid_args; + } + } + else + { + // Should never get here. + return drfs_unknown_error; + } + + + if (newPos > pOpenedFile->sizeInBytes) { + return drfs_invalid_args; + } + + pOpenedFile->readPointer = (size_t)newPos; + return drfs_success; +} + +static dr_uint64 drfs_tell_file__zip(drfs_handle archive, drfs_handle file) +{ + (void)archive; + + drfs_openedfile_zip* pOpenedFile = (drfs_openedfile_zip*)file; + assert(pOpenedFile != NULL); + + return pOpenedFile->readPointer; +} + +static dr_uint64 drfs_file_size__zip(drfs_handle archive, drfs_handle file) +{ + (void)archive; + + drfs_openedfile_zip* pOpenedFile = (drfs_openedfile_zip*)file; + assert(pOpenedFile != NULL); + + return pOpenedFile->sizeInBytes; +} + +static void drfs_flush__zip(drfs_handle archive, drfs_handle file) +{ + (void)archive; + (void)file; + + assert(archive != NULL); + assert(file != NULL); + + // All files are read-only for now. +} + + +static void drfs_register_zip_backend(drfs_context* pContext) +{ + if (pContext == NULL) { + return; + } + + drfs_archive_callbacks callbacks; + callbacks.is_valid_extension = drfs_is_valid_extension__zip; + callbacks.open_archive = drfs_open_archive__zip; + callbacks.close_archive = drfs_close_archive__zip; + callbacks.get_file_info = drfs_get_file_info__zip; + callbacks.begin_iteration = drfs_begin_iteration__zip; + callbacks.end_iteration = drfs_end_iteration__zip; + callbacks.next_iteration = drfs_next_iteration__zip; + callbacks.delete_file = NULL; + callbacks.move_file = NULL; + callbacks.create_directory = NULL; + callbacks.copy_file = NULL; + callbacks.open_file = drfs_open_file__zip; + callbacks.close_file = drfs_close_file__zip; + callbacks.read_file = drfs_read_file__zip; + callbacks.write_file = drfs_write_file__zip; + callbacks.seek_file = drfs_seek_file__zip; + callbacks.tell_file = drfs_tell_file__zip; + callbacks.file_size = drfs_file_size__zip; + callbacks.flush_file = drfs_flush__zip; + drfs_register_archive_backend(pContext, callbacks); +} +#endif //DR_FS_NO_ZIP + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Quake 2 PAK +// +/////////////////////////////////////////////////////////////////////////////// +#ifndef DR_FS_NO_PAK +typedef struct +{ + char path[64]; + +}drfs_path_pak; + +typedef struct +{ + // The file name. + char name[56]; + + // The position within the file of the first byte of the file. + unsigned int offset; + + // The size of the file, in bytes. + unsigned int sizeInBytes; + +}drfs_file_pak; + +typedef struct +{ + // A pointer to the archive file for reading data. + drfs_file* pArchiveFile; + + + // The 4-byte identifiers: "PACK" + char id[4]; + + // The offset of the directory. + unsigned int directoryOffset; + + // The size of the directory. This should a multiple of 64. + unsigned int directoryLength; + + + // The access mode. + unsigned int accessMode; + + // A pointer to the buffer containing the file information. The number of items in this array is equal to directoryLength / 64. + drfs_file_pak* pFiles; + +}drfs_archive_pak; + + +typedef struct +{ + // The current index of the iterator. When this hits childCount, the iteration is finished. + unsigned int index; + + // The directory being iterated. + char directoryPath[DRFS_MAX_PATH]; + + + // The number of directories that have previously been iterated. + unsigned int processedDirCount; + + // The directories that were previously iterated. + drfs_path_pak* pProcessedDirs; + +}drfs_iterator_pak; + +static dr_bool32 drfs_iterator_pak_append_processed_dir(drfs_iterator_pak* pIterator, const char* path) +{ + if (pIterator != NULL && path != NULL) + { + drfs_path_pak* pOldBuffer = pIterator->pProcessedDirs; + drfs_path_pak* pNewBuffer = (drfs_path_pak*)malloc(sizeof(drfs_path_pak) * (pIterator->processedDirCount + 1)); + + if (pNewBuffer != 0) + { + for (unsigned int iDst = 0; iDst < pIterator->processedDirCount; ++iDst) + { + pNewBuffer[iDst] = pOldBuffer[iDst]; + } + + drfs__strcpy_s(pNewBuffer[pIterator->processedDirCount].path, 64, path); + + + pIterator->pProcessedDirs = pNewBuffer; + pIterator->processedDirCount += 1; + + drfs_free(pOldBuffer); + + return 1; + } + } + + return 0; +} + +static dr_bool32 drfs_iterator_pak_has_dir_been_processed(drfs_iterator_pak* pIterator, const char* path) +{ + for (unsigned int i = 0; i < pIterator->processedDirCount; ++i) + { + if (strcmp(path, pIterator->pProcessedDirs[i].path) == 0) + { + return 1; + } + } + + return 0; +} + + +typedef struct +{ + // The offset of the first byte of the file's data. + size_t offsetInArchive; + + // The size of the file in bytes so we can guard against overflowing reads. + size_t sizeInBytes; + + // The current position of the file's read pointer. + size_t readPointer; + +}drfs_openedfile_pak; + + + +static drfs_archive_pak* drfs_pak_create(drfs_file* pArchiveFile, unsigned int accessMode) +{ + drfs_archive_pak* pak = (drfs_archive_pak*)malloc(sizeof(*pak)); + if (pak != NULL) + { + pak->pArchiveFile = pArchiveFile; + pak->directoryOffset = 0; + pak->directoryLength = 0; + pak->accessMode = accessMode; + pak->pFiles = NULL; + } + + return pak; +} + +static void drfs_pak_delete(drfs_archive_pak* pArchive) +{ + free(pArchive->pFiles); + free(pArchive); +} + + + + +static dr_bool32 drfs_is_valid_extension__pak(const char* extension) +{ + return drfs__stricmp(extension, "pak") == 0; +} + + +static drfs_result drfs_open_archive__pak(drfs_file* pArchiveFile, unsigned int accessMode, drfs_handle* pHandleOut) +{ + assert(pArchiveFile != NULL); + assert(pHandleOut != NULL); + assert(drfs_tell(pArchiveFile) == 0); + + *pHandleOut = NULL; + + if (!drfs_lock(pArchiveFile)) { + return drfs_unknown_error; + } + + drfs_result result = drfs_success; + drfs_archive_pak* pak = drfs_pak_create(pArchiveFile, accessMode); + if (pak != NULL) + { + // First 4 bytes should equal "PACK" + if (drfs_read_nolock(pArchiveFile, pak->id, 4, NULL) == drfs_success) + { + if (pak->id[0] == 'P' && pak->id[1] == 'A' && pak->id[2] == 'C' && pak->id[3] == 'K') + { + if (drfs_read_nolock(pArchiveFile, &pak->directoryOffset, 4, NULL) == drfs_success) + { + if (drfs_read_nolock(pArchiveFile, &pak->directoryLength, 4, NULL) == drfs_success) + { + // We loaded the header just fine so now we want to allocate space for each file in the directory and load them. Note that + // this does not load the file data itself, just information about the files like their name and size. + if (pak->directoryLength % 64 == 0) + { + unsigned int fileCount = pak->directoryLength / 64; + if (fileCount > 0) + { + assert((sizeof(drfs_file_pak) * fileCount) == pak->directoryLength); + + pak->pFiles = (drfs_file_pak*)malloc(pak->directoryLength); + if (pak->pFiles != NULL) + { + // Seek to the directory listing before trying to read it. + if (drfs_seek_nolock(pArchiveFile, pak->directoryOffset, drfs_origin_start) == drfs_success) + { + size_t bytesRead; + if (drfs_read_nolock(pArchiveFile, pak->pFiles, pak->directoryLength, &bytesRead) == drfs_success && bytesRead == pak->directoryLength) + { + // All good! + } + else + { + // Failed to read the directory listing. + drfs_pak_delete(pak); + pak = NULL; + result = drfs_invalid_archive; + } + } + else + { + // Failed to seek to the directory listing. + drfs_pak_delete(pak); + pak = NULL; + result = drfs_invalid_archive; + } + } + else + { + // Failed to allocate memory for the file info buffer. + drfs_pak_delete(pak); + pak = NULL; + result = drfs_out_of_memory; + } + } + } + else + { + // The directory length is not a multiple of 64 - something is wrong with the file. + drfs_pak_delete(pak); + pak = NULL; + result = drfs_invalid_archive; + } + } + else + { + // Failed to read the directory length. + drfs_pak_delete(pak); + pak = NULL; + result = drfs_invalid_archive; + } + } + else + { + // Failed to read the directory offset. + drfs_pak_delete(pak); + pak = NULL; + result = drfs_invalid_archive; + } + } + else + { + // Not a pak file. + drfs_pak_delete(pak); + pak = NULL; + result = drfs_invalid_archive; + } + } + else + { + // Failed to read the header. + drfs_pak_delete(pak); + pak = NULL; + result = drfs_invalid_archive; + } + } + + drfs_unlock(pArchiveFile); + *pHandleOut = pak; + return result; +} + +static void drfs_close_archive__pak(drfs_handle archive) +{ + drfs_archive_pak* pPak = (drfs_archive_pak*)archive; + assert(pPak != NULL); + + drfs_pak_delete(pPak); +} + +static drfs_result drfs_get_file_info__pak(drfs_handle archive, const char* relativePath, drfs_file_info* fi) +{ + // We can determine whether or not the path refers to a file or folder by checking it the path is parent of any + // files in the archive. If so, it's a folder, otherwise it's a file (so long as it exists). + drfs_archive_pak* pak = (drfs_archive_pak*)archive; + assert(pak != NULL); + + unsigned int fileCount = pak->directoryLength / 64; + for (unsigned int i = 0; i < fileCount; ++i) + { + drfs_file_pak* pFile = pak->pFiles + i; + if (strcmp(pFile->name, relativePath) == 0) + { + // It's a file. + drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), relativePath); + fi->sizeInBytes = (dr_uint64)pFile->sizeInBytes; + fi->lastModifiedTime = 0; + fi->attributes = DRFS_FILE_ATTRIBUTE_READONLY; + + return drfs_success; + } + else if (drfs_drpath_is_descendant(pFile->name, relativePath)) + { + // It's a directory. + drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), relativePath); + fi->sizeInBytes = 0; + fi->lastModifiedTime = 0; + fi->attributes = DRFS_FILE_ATTRIBUTE_READONLY | DRFS_FILE_ATTRIBUTE_DIRECTORY; + + return drfs_success; + } + } + + return drfs_does_not_exist; +} + +static drfs_handle drfs_begin_iteration__pak(drfs_handle archive, const char* relativePath) +{ + (void)archive; + assert(relativePath != NULL); + + drfs_iterator_pak* pIterator = (drfs_iterator_pak*)malloc(sizeof(drfs_iterator_pak)); + if (pIterator != NULL) + { + pIterator->index = 0; + drfs__strcpy_s(pIterator->directoryPath, sizeof(pIterator->directoryPath), relativePath); + pIterator->processedDirCount = 0; + pIterator->pProcessedDirs = NULL; + } + + return pIterator; +} + +static void drfs_end_iteration__pak(drfs_handle archive, drfs_handle iterator) +{ + (void)archive; + + drfs_iterator_pak* pIterator = (drfs_iterator_pak*)iterator; + assert(pIterator != NULL); + + free(pIterator); +} + +static dr_bool32 drfs_next_iteration__pak(drfs_handle archive, drfs_handle iterator, drfs_file_info* fi) +{ + drfs_iterator_pak* pIterator = (drfs_iterator_pak*)iterator; + assert(pIterator != NULL); + + drfs_archive_pak* pak = (drfs_archive_pak*)archive; + assert(pak != NULL); + + unsigned int fileCount = pak->directoryLength / 64; + while (pIterator->index < fileCount) + { + unsigned int iFile = pIterator->index++; + + drfs_file_pak* pFile = pak->pFiles + iFile; + if (drfs_drpath_is_child(pFile->name, pIterator->directoryPath)) + { + // It's a file. + drfs__strcpy_s(fi->absolutePath, DRFS_MAX_PATH, pFile->name); + fi->sizeInBytes = (dr_uint64)pFile->sizeInBytes; + fi->lastModifiedTime = 0; + fi->attributes = DRFS_FILE_ATTRIBUTE_READONLY; + + return DR_TRUE; + } + else if (drfs_drpath_is_descendant(pFile->name, pIterator->directoryPath)) + { + // It's a directory. This needs special handling because we don't want to iterate over the same directory multiple times. + const char* childDirEnd = pFile->name + strlen(pIterator->directoryPath) + 1; // +1 for the slash. + while (childDirEnd[0] != '\0' && childDirEnd[0] != '/' && childDirEnd[0] != '\\') + { + childDirEnd += 1; + } + + char childDir[64]; + memcpy(childDir, pFile->name, childDirEnd - pFile->name); + childDir[childDirEnd - pFile->name] = '\0'; + + if (!drfs_iterator_pak_has_dir_been_processed(pIterator, childDir)) + { + // It's a directory. + drfs__strcpy_s(fi->absolutePath, DRFS_MAX_PATH, childDir); + fi->sizeInBytes = 0; + fi->lastModifiedTime = 0; + fi->attributes = DRFS_FILE_ATTRIBUTE_READONLY | DRFS_FILE_ATTRIBUTE_DIRECTORY; + + drfs_iterator_pak_append_processed_dir(pIterator, childDir); + + return DR_TRUE; + } + } + } + + return DR_FALSE; +} + +static drfs_result drfs_open_file__pak(drfs_handle archive, const char* relativePath, unsigned int accessMode, drfs_handle* pHandleOut) +{ + assert(relativePath != NULL); + assert(pHandleOut != NULL); + + // Only supporting read-only for now. + if ((accessMode & DRFS_WRITE) != 0) { + return drfs_permission_denied; + } + + + drfs_archive_pak* pak = (drfs_archive_pak*)archive; + assert(pak != NULL); + + for (unsigned int iFile = 0; iFile < (pak->directoryLength / 64); ++iFile) + { + if (strcmp(relativePath, pak->pFiles[iFile].name) == 0) + { + // We found the file. + drfs_openedfile_pak* pOpenedFile = (drfs_openedfile_pak*)malloc(sizeof(*pOpenedFile)); + if (pOpenedFile != NULL) + { + pOpenedFile->offsetInArchive = pak->pFiles[iFile].offset; + pOpenedFile->sizeInBytes = pak->pFiles[iFile].sizeInBytes; + pOpenedFile->readPointer = 0; + + *pHandleOut = pOpenedFile; + return drfs_success; + } + } + } + + + return drfs_does_not_exist; +} + +static void drfs_close_file__pak(drfs_handle archive, drfs_handle file) +{ + (void)archive; + + drfs_openedfile_pak* pOpenedFile = (drfs_openedfile_pak*)file; + assert(pOpenedFile != NULL); + + free(pOpenedFile); +} + +static drfs_result drfs_read_file__pak(drfs_handle archive, drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut) +{ + assert(pDataOut != NULL); + assert(bytesToRead > 0); + + drfs_archive_pak* pak = (drfs_archive_pak*)archive; + assert(pak != NULL); + + drfs_openedfile_pak* pOpenedFile = (drfs_openedfile_pak*)file; + assert(pOpenedFile != NULL); + + // The read pointer should never go past the file size. + assert(pOpenedFile->sizeInBytes >= pOpenedFile->readPointer); + + + size_t bytesAvailable = pOpenedFile->sizeInBytes - pOpenedFile->readPointer; + if (bytesAvailable < bytesToRead) { + bytesToRead = bytesAvailable; // Safe cast, as per the check above. + } + + if (!drfs_lock(pak->pArchiveFile)) { + return drfs_unknown_error; + } + + drfs_seek_nolock(pak->pArchiveFile, (dr_int64)(pOpenedFile->offsetInArchive + pOpenedFile->readPointer), drfs_origin_start); + drfs_result result = drfs_read_nolock(pak->pArchiveFile, pDataOut, bytesToRead, pBytesReadOut); + if (result == drfs_success) { + pOpenedFile->readPointer += bytesToRead; + } + + drfs_unlock(pak->pArchiveFile); + return result; +} + +static drfs_result drfs_write_file__pak(drfs_handle archive, drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut) +{ + (void)archive; + (void)file; + (void)pData; + (void)bytesToWrite; + + assert(archive != NULL); + assert(file != NULL); + assert(pData != NULL); + assert(bytesToWrite > 0); + + // All files are read-only for now. + if (pBytesWrittenOut) { + *pBytesWrittenOut = 0; + } + + return drfs_permission_denied; +} + +static drfs_result drfs_seek_file__pak(drfs_handle archive, drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin) +{ + (void)archive; + + drfs_openedfile_pak* pOpenedFile = (drfs_openedfile_pak*)file; + assert(pOpenedFile != NULL); + + dr_uint64 newPos = pOpenedFile->readPointer; + if (origin == drfs_origin_current) { + if ((dr_int64)newPos + bytesToSeek >= 0) { + newPos = (dr_uint64)((dr_int64)newPos + bytesToSeek); + } else { + return drfs_invalid_args; // Trying to seek to before the beginning of the file. + } + } else if (origin == drfs_origin_start) { + assert(bytesToSeek >= 0); + newPos = (dr_uint64)bytesToSeek; + } else if (origin == drfs_origin_end) { + assert(bytesToSeek >= 0); + if ((dr_uint64)bytesToSeek <= pOpenedFile->sizeInBytes) { + newPos = pOpenedFile->sizeInBytes - (dr_uint64)bytesToSeek; + } else { + return drfs_invalid_args; // Trying to seek to before the beginning of the file. + } + } else { + return drfs_unknown_error; // Should never get here. + } + + + if (newPos > pOpenedFile->sizeInBytes) { + return drfs_invalid_args; + } + + pOpenedFile->readPointer = (size_t)newPos; + return drfs_success; +} + +static dr_uint64 drfs_tell_file__pak(drfs_handle archive, drfs_handle file) +{ + (void)archive; + + drfs_openedfile_pak* pOpenedFile = (drfs_openedfile_pak*)file; + assert(pOpenedFile != NULL); + + return pOpenedFile->readPointer; +} + +static dr_uint64 drfs_file_size__pak(drfs_handle archive, drfs_handle file) +{ + (void)archive; + + drfs_openedfile_pak* pOpenedFile = (drfs_openedfile_pak*)file; + assert(pOpenedFile != NULL); + + return pOpenedFile->sizeInBytes; +} + +static void drfs_flush__pak(drfs_handle archive, drfs_handle file) +{ + (void)archive; + (void)file; + + assert(archive != NULL); + assert(file != NULL); + + // All files are read-only for now. +} + +static void drfs_register_pak_backend(drfs_context* pContext) +{ + if (pContext == NULL) { + return; + } + + drfs_archive_callbacks callbacks; + callbacks.is_valid_extension = drfs_is_valid_extension__pak; + callbacks.open_archive = drfs_open_archive__pak; + callbacks.close_archive = drfs_close_archive__pak; + callbacks.get_file_info = drfs_get_file_info__pak; + callbacks.begin_iteration = drfs_begin_iteration__pak; + callbacks.end_iteration = drfs_end_iteration__pak; + callbacks.next_iteration = drfs_next_iteration__pak; + callbacks.delete_file = NULL; + callbacks.move_file = NULL; + callbacks.create_directory = NULL; + callbacks.copy_file = NULL; + callbacks.open_file = drfs_open_file__pak; + callbacks.close_file = drfs_close_file__pak; + callbacks.read_file = drfs_read_file__pak; + callbacks.write_file = drfs_write_file__pak; + callbacks.seek_file = drfs_seek_file__pak; + callbacks.tell_file = drfs_tell_file__pak; + callbacks.file_size = drfs_file_size__pak; + callbacks.flush_file = drfs_flush__pak; + drfs_register_archive_backend(pContext, callbacks); +} +#endif //DR_FS_NO_PAK + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Wavefront MTL +// +/////////////////////////////////////////////////////////////////////////////// +#ifndef DR_FS_NO_MTL +typedef struct +{ + // The byte offset within the archive + dr_uint64 offset; + + // The size of the file, in bytes. + dr_uint64 sizeInBytes; + + // The name of the material. The specification says this can be any length, but we're going to clamp it to 255 + null terminator which should be fine. + char name[256]; + +}drfs_file_mtl; + +typedef struct +{ + // A pointer to the archive's file so we can read data. + drfs_file* pArchiveFile; + + // The access mode. + unsigned int accessMode; + + // The buffer containing the list of files. + drfs_file_mtl* pFiles; + + // The number of files in the archive. + unsigned int fileCount; + +}drfs_archive_mtl; + +typedef struct +{ + // The current index of the iterator. When this hits the file count, the iteration is finished. + unsigned int index; + +}drfs_iterator_mtl; + +typedef struct +{ + // The offset within the archive file the first byte of the file is located. + dr_uint64 offsetInArchive; + + // The size of the file in bytes so we can guard against overflowing reads. + dr_uint64 sizeInBytes; + + // The current position of the file's read pointer. + dr_uint64 readPointer; + +}drfs_openedfile_mtl; + + +static drfs_archive_mtl* drfs_mtl_create(drfs_file* pArchiveFile, unsigned int accessMode) +{ + drfs_archive_mtl* mtl = (drfs_archive_mtl*)malloc(sizeof(drfs_archive_mtl)); + if (mtl != NULL) + { + mtl->pArchiveFile = pArchiveFile; + mtl->accessMode = accessMode; + mtl->pFiles = NULL; + mtl->fileCount = 0; + } + + return mtl; +} + +static void drfs_mtl_delete(drfs_archive_mtl* pArchive) +{ + free(pArchive->pFiles); + free(pArchive); +} + +static void drfs_mtl_addfile(drfs_archive_mtl* pArchive, drfs_file_mtl* pFile) +{ + if (pArchive != NULL && pFile != NULL) + { + drfs_file_mtl* pOldBuffer = pArchive->pFiles; + drfs_file_mtl* pNewBuffer = (drfs_file_mtl*)malloc(sizeof(drfs_file_mtl) * (pArchive->fileCount + 1)); + + if (pNewBuffer != 0) + { + for (unsigned int iDst = 0; iDst < pArchive->fileCount; ++iDst) { + pNewBuffer[iDst] = pOldBuffer[iDst]; + } + + pNewBuffer[pArchive->fileCount] = *pFile; + + pArchive->pFiles = pNewBuffer; + pArchive->fileCount += 1; + + free(pOldBuffer); + } + } +} + + +typedef struct +{ + dr_uint64 archiveSizeInBytes; + dr_uint64 bytesRemaining; + drfs_file* pFile; + char* chunkPointer; + char* chunkEnd; + char chunk[4096]; + unsigned int chunkSize; + +}drfs_openarchive_mtl_state; + +static dr_bool32 drfs_mtl_loadnextchunk(drfs_openarchive_mtl_state* pState) +{ + assert(pState != NULL); + + if (pState->bytesRemaining > 0) + { + pState->chunkSize = (pState->bytesRemaining > 4096) ? 4096 : (unsigned int)pState->bytesRemaining; + assert(pState->chunkSize); + + if (drfs_read_nolock(pState->pFile, pState->chunk, pState->chunkSize, NULL) == drfs_success) + { + pState->bytesRemaining -= pState->chunkSize; + pState->chunkPointer = pState->chunk; + pState->chunkEnd = pState->chunk + pState->chunkSize; + + return DR_TRUE; + } + else + { + // An error occured while reading. Just reset everything to make it look like an error occured. + pState->bytesRemaining = 0; + pState->chunkSize = 0; + pState->chunkPointer = pState->chunk; + pState->chunkEnd = pState->chunkPointer; + } + } + + return DR_FALSE; +} + +static dr_bool32 drfs_mtl_loadnewmtl(drfs_openarchive_mtl_state* pState) +{ + assert(pState != NULL); + + const char newmtl[7] = "newmtl"; + for (unsigned int i = 0; i < 6; ++i) + { + // Check if we need a new chunk. + if (pState->chunkPointer >= pState->chunkEnd) + { + if (!drfs_mtl_loadnextchunk(pState)) + { + return DR_FALSE; + } + } + + + if (pState->chunkPointer[0] != newmtl[i]) + { + return DR_FALSE; + } + + pState->chunkPointer += 1; + } + + // At this point the first 6 characters equal "newmtl". + return DR_TRUE; +} + +static dr_bool32 drfs_mtl_skipline(drfs_openarchive_mtl_state* pState) +{ + assert(pState != NULL); + + // Keep looping until we find a new line character. + while (pState->chunkPointer < pState->chunkEnd) + { + if (pState->chunkPointer[0] == '\n') + { + // Found the new line. Now move forward by one to get past the new line character. + pState->chunkPointer += 1; + if (pState->chunkPointer >= pState->chunkEnd) + { + return drfs_mtl_loadnextchunk(pState); + } + + return DR_TRUE; + } + + pState->chunkPointer += 1; + } + + // If we get here it means we got past the end of the chunk. We just read the next chunk and call this recursively. + if (drfs_mtl_loadnextchunk(pState)) + { + return drfs_mtl_skipline(pState); + } + + return DR_FALSE; +} + +static dr_bool32 drfs_mtl_skipwhitespace(drfs_openarchive_mtl_state* pState) +{ + assert(pState != NULL); + + while (pState->chunkPointer < pState->chunkEnd) + { + const char c = pState->chunkPointer[0]; + if (c != ' ' && c != '\t' && c != '\r' && c != '\n') + { + return DR_TRUE; + } + + pState->chunkPointer += 1; + } + + if (drfs_mtl_loadnextchunk(pState)) + { + return drfs_mtl_skipwhitespace(pState); + } + + return DR_FALSE; +} + +static dr_bool32 drfs_mtl_loadmtlname(drfs_openarchive_mtl_state* pState, void* dst, unsigned int dstSizeInBytes) +{ + assert(pState != NULL); + + // We loop over character by character until we find a whitespace, "#" character or the end of the file. + char* dst8 = (char*)dst; + while (dstSizeInBytes > 0 && pState->chunkPointer < pState->chunkEnd) + { + const char c = pState->chunkPointer[0]; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '#') + { + // We've found the end of the name. Null terminate and return. + *dst8 = '\0'; + return DR_TRUE; + } + else + { + *dst8++ = c; + dstSizeInBytes -= 1; + pState->chunkPointer += 1; + } + } + + // At this point we either ran out of space in the destination buffer or the chunk. + if (dstSizeInBytes > 0) + { + // We got to the end of the chunk, so we need to load the next chunk and call this recursively. + assert(pState->chunkPointer == pState->chunkEnd); + + if (drfs_mtl_loadnextchunk(pState)) + { + return drfs_mtl_loadmtlname(pState, dst8, dstSizeInBytes); + } + else + { + // We reached the end of the file, but the name may be valid. + return DR_TRUE; + } + } + else + { + // We ran out of room in the buffer. + return DR_FALSE; + } +} + + +static dr_bool32 drfs_is_valid_extension__mtl(const char* extension) +{ + return drfs__stricmp(extension, "mtl") == 0; +} + +static drfs_result drfs_open_archive__mtl(drfs_file* pArchiveFile, unsigned int accessMode, drfs_handle* pHandleOut) +{ + assert(pArchiveFile != NULL); + assert(pHandleOut != NULL); + assert(drfs_tell(pArchiveFile) == 0); + + *pHandleOut = NULL; + + if (!drfs_lock(pArchiveFile)) { + return drfs_unknown_error; + } + + drfs_archive_mtl* mtl = drfs_mtl_create(pArchiveFile, accessMode); + if (mtl == NULL) { + return drfs_invalid_archive; + } + + drfs_result result = drfs_success; + + // We create a state object that is used to help us with chunk management. + drfs_openarchive_mtl_state state; + state.pFile = pArchiveFile; + state.archiveSizeInBytes = drfs_size_nolock(pArchiveFile); + state.bytesRemaining = state.archiveSizeInBytes; + state.chunkSize = 0; + state.chunkPointer = state.chunk; + state.chunkEnd = state.chunk; + if (drfs_mtl_loadnextchunk(&state)) + { + while (state.bytesRemaining > 0 || state.chunkPointer < state.chunkEnd) + { + ptrdiff_t bytesRemainingInChunk = state.chunkEnd - state.chunkPointer; + assert(bytesRemainingInChunk > 0); + + dr_uint64 newmtlOffset = state.archiveSizeInBytes - state.bytesRemaining - ((dr_uint64)bytesRemainingInChunk); + + if (drfs_mtl_loadnewmtl(&state)) + { + if (state.chunkPointer[0] == ' ' || state.chunkPointer[0] == '\t') + { + // We found a new material. We need to iterate until we hit the first whitespace, "#", or the end of the file. + if (drfs_mtl_skipwhitespace(&state)) + { + drfs_file_mtl file; + if (drfs_mtl_loadmtlname(&state, file.name, 256)) + { + // Everything worked out. We now need to create the file and add it to our list. At this point we won't know the size. We determine + // the size in a post-processing step later. + file.offset = newmtlOffset; + drfs_mtl_addfile(mtl, &file); + } + } + } + } + + // Move to the next line. + drfs_mtl_skipline(&state); + } + + + // The files will have been read at this point, but we need to do a post-processing step to retrieve the size of each file. + for (unsigned int iFile = 0; iFile < mtl->fileCount; ++iFile) + { + if (iFile < mtl->fileCount - 1) + { + // It's not the last item. The size of this item is the offset of the next file minus the offset of this file. + mtl->pFiles[iFile].sizeInBytes = mtl->pFiles[iFile + 1].offset - mtl->pFiles[iFile].offset; + } + else + { + // It's the last item. The size of this item is the size of the archive file minus the file's offset. + mtl->pFiles[iFile].sizeInBytes = state.archiveSizeInBytes - mtl->pFiles[iFile].offset; + } + } + } + else + { + drfs_mtl_delete(mtl); + mtl = NULL; + result = drfs_invalid_archive; + } + + drfs_unlock(pArchiveFile); + *pHandleOut = mtl; + return result; +} + +static void drfs_close_archive__mtl(drfs_handle archive) +{ + drfs_archive_mtl* mtl = (drfs_archive_mtl*)archive; + assert(mtl != NULL); + + drfs_mtl_delete(mtl); +} + +static drfs_result drfs_get_file_info__mtl(drfs_handle archive, const char* relativePath, drfs_file_info* fi) +{ + drfs_archive_mtl* mtl = (drfs_archive_mtl*)archive; + assert(mtl != NULL); + + for (unsigned int iFile = 0; iFile < mtl->fileCount; ++iFile) + { + if (strcmp(relativePath, mtl->pFiles[iFile].name) == 0) + { + // We found the file. + if (fi != NULL) + { + drfs__strcpy_s(fi->absolutePath, sizeof(fi->absolutePath), relativePath); + fi->sizeInBytes = mtl->pFiles[iFile].sizeInBytes; + fi->lastModifiedTime = 0; + fi->attributes = DRFS_FILE_ATTRIBUTE_READONLY; + } + + return drfs_success; + } + } + + return drfs_does_not_exist; +} + +static drfs_handle drfs_begin_iteration__mtl(drfs_handle archive, const char* relativePath) +{ + assert(relativePath != NULL); + + drfs_archive_mtl* mtl = (drfs_archive_mtl*)archive; + assert(mtl != NULL); + + if (mtl->fileCount > 0) + { + if (relativePath[0] == '\0' || (relativePath[0] == '/' && relativePath[1] == '\0')) // This is a flat archive, so no sub-folders. + { + drfs_iterator_mtl* pIterator = (drfs_iterator_mtl*)malloc(sizeof(*pIterator)); + if (pIterator != NULL) + { + pIterator->index = 0; + return pIterator; + } + } + } + + return NULL; +} + +static void drfs_end_iteration__mtl(drfs_handle archive, drfs_handle iterator) +{ + (void)archive; + + drfs_iterator_mtl* pIterator = (drfs_iterator_mtl*)iterator; + free(pIterator); +} + +static dr_bool32 drfs_next_iteration__mtl(drfs_handle archive, drfs_handle iterator, drfs_file_info* fi) +{ + drfs_archive_mtl* mtl = (drfs_archive_mtl*)archive; + assert(mtl != NULL); + + drfs_iterator_mtl* pIterator = (drfs_iterator_mtl*)iterator; + assert(pIterator != NULL); + + if (pIterator->index < mtl->fileCount) + { + if (fi != NULL) + { + drfs__strcpy_s(fi->absolutePath, DRFS_MAX_PATH, mtl->pFiles[pIterator->index].name); + fi->sizeInBytes = mtl->pFiles[pIterator->index].sizeInBytes; + fi->lastModifiedTime = 0; + fi->attributes = DRFS_FILE_ATTRIBUTE_READONLY; + } + + pIterator->index += 1; + return DR_TRUE; + } + + return DR_FALSE; +} + +static drfs_result drfs_open_file__mtl(drfs_handle archive, const char* relativePath, unsigned int accessMode, drfs_handle* pHandleOut) +{ + assert(relativePath != NULL); + assert(pHandleOut != NULL); + + *pHandleOut = NULL; + + // Only supporting read-only for now. + if ((accessMode & DRFS_WRITE) != 0) { + return drfs_permission_denied; + } + + drfs_archive_mtl* mtl = (drfs_archive_mtl*)archive; + assert(mtl != NULL); + + for (unsigned int iFile = 0; iFile < mtl->fileCount; ++iFile) + { + if (strcmp(relativePath, mtl->pFiles[iFile].name) == 0) + { + // We found the file. + drfs_openedfile_mtl* pOpenedFile = (drfs_openedfile_mtl*)malloc(sizeof(drfs_openedfile_mtl)); + if (pOpenedFile != NULL) + { + pOpenedFile->offsetInArchive = mtl->pFiles[iFile].offset; + pOpenedFile->sizeInBytes = mtl->pFiles[iFile].sizeInBytes; + pOpenedFile->readPointer = 0; + + *pHandleOut = pOpenedFile; + return drfs_success; + } + } + } + + return drfs_does_not_exist; +} + +static void drfs_close_file__mtl(drfs_handle archive, drfs_handle file) +{ + (void)archive; + + drfs_openedfile_mtl* pOpenedFile = (drfs_openedfile_mtl*)file; + assert(pOpenedFile != NULL); + + free(pOpenedFile); +} + +static drfs_result drfs_read_file__mtl(drfs_handle archive, drfs_handle file, void* pDataOut, size_t bytesToRead, size_t* pBytesReadOut) +{ + assert(pDataOut != NULL); + assert(bytesToRead > 0); + + drfs_archive_mtl* mtl = (drfs_archive_mtl*)archive; + assert(mtl != NULL); + + drfs_openedfile_mtl* pOpenedFile = (drfs_openedfile_mtl*)file; + assert(pOpenedFile != NULL); + + // The read pointer should never go past the file size. + assert(pOpenedFile->sizeInBytes >= pOpenedFile->readPointer); + + dr_uint64 bytesAvailable = pOpenedFile->sizeInBytes - pOpenedFile->readPointer; + if (bytesAvailable < bytesToRead) { + bytesToRead = (size_t)bytesAvailable; // Safe cast, as per the check above. + } + + if (!drfs_lock(mtl->pArchiveFile)) { + return drfs_unknown_error; + } + + drfs_seek_nolock(mtl->pArchiveFile, (dr_int64)(pOpenedFile->offsetInArchive + pOpenedFile->readPointer), drfs_origin_start); + drfs_result result = drfs_read_nolock(mtl->pArchiveFile, pDataOut, bytesToRead, pBytesReadOut); + if (result == drfs_success) { + pOpenedFile->readPointer += bytesToRead; + } + + return result; +} + +static drfs_result drfs_write_file__mtl(drfs_handle archive, drfs_handle file, const void* pData, size_t bytesToWrite, size_t* pBytesWrittenOut) +{ + (void)archive; + (void)file; + (void)pData; + (void)bytesToWrite; + + assert(archive != NULL); + assert(file != NULL); + assert(pData != NULL); + assert(bytesToWrite > 0); + + // All files are read-only for now. + if (pBytesWrittenOut) { + *pBytesWrittenOut = 0; + } + + return drfs_success; +} + +static drfs_result drfs_seek_file__mtl(drfs_handle archive, drfs_handle file, dr_int64 bytesToSeek, drfs_seek_origin origin) +{ + (void)archive; + + drfs_openedfile_mtl* pOpenedFile = (drfs_openedfile_mtl*)file; + assert(pOpenedFile != NULL); + + dr_uint64 newPos = pOpenedFile->readPointer; + if (origin == drfs_origin_current) { + if ((dr_int64)newPos + bytesToSeek >= 0) { + newPos = (dr_uint64)((dr_int64)newPos + bytesToSeek); + } else { + return drfs_invalid_args; // Trying to seek to before the beginning of the file. + } + } else if (origin == drfs_origin_start) { + assert(bytesToSeek >= 0); + newPos = (dr_uint64)bytesToSeek; + } else if (origin == drfs_origin_end) { + assert(bytesToSeek >= 0); + if ((dr_uint64)bytesToSeek <= pOpenedFile->sizeInBytes) { + newPos = pOpenedFile->sizeInBytes - (dr_uint64)bytesToSeek; + } else { + return drfs_invalid_args; // Trying to seek to before the beginning of the file. + } + } else { + return drfs_unknown_error; // Should never get here. + } + + + if (newPos > pOpenedFile->sizeInBytes) { + return drfs_invalid_args; + } + + pOpenedFile->readPointer = newPos; + return drfs_success; +} + +static dr_uint64 drfs_tell_file__mtl(drfs_handle archive, drfs_handle file) +{ + (void)archive; + + drfs_openedfile_mtl* pOpenedFile = (drfs_openedfile_mtl*)file; + assert(pOpenedFile != NULL); + + return pOpenedFile->readPointer; +} + +static dr_uint64 drfs_file_size__mtl(drfs_handle archive, drfs_handle file) +{ + (void)archive; + + drfs_openedfile_mtl* pOpenedFile = (drfs_openedfile_mtl*)file; + assert(pOpenedFile != NULL); + + return pOpenedFile->sizeInBytes; +} + +static void drfs_flush__mtl(drfs_handle archive, drfs_handle file) +{ + (void)archive; + (void)file; + + assert(archive != NULL); + assert(file != NULL); + + // All files are read-only for now. +} + + +static void drfs_register_mtl_backend(drfs_context* pContext) +{ + if (pContext == NULL) { + return; + } + + drfs_archive_callbacks callbacks; + callbacks.is_valid_extension = drfs_is_valid_extension__mtl; + callbacks.open_archive = drfs_open_archive__mtl; + callbacks.close_archive = drfs_close_archive__mtl; + callbacks.get_file_info = drfs_get_file_info__mtl; + callbacks.begin_iteration = drfs_begin_iteration__mtl; + callbacks.end_iteration = drfs_end_iteration__mtl; + callbacks.next_iteration = drfs_next_iteration__mtl; + callbacks.delete_file = NULL; + callbacks.move_file = NULL; + callbacks.create_directory = NULL; + callbacks.copy_file = NULL; + callbacks.open_file = drfs_open_file__mtl; + callbacks.close_file = drfs_close_file__mtl; + callbacks.read_file = drfs_read_file__mtl; + callbacks.write_file = drfs_write_file__mtl; + callbacks.seek_file = drfs_seek_file__mtl; + callbacks.tell_file = drfs_tell_file__mtl; + callbacks.file_size = drfs_file_size__mtl; + callbacks.flush_file = drfs_flush__mtl; + drfs_register_archive_backend(pContext, callbacks); +} +#endif //DR_FS_NO_MTL + + + +#endif //DR_FS_IMPLEMENTATION + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ diff --git a/source/engine/thirdparty/dr_libs/old/dr_fsw.h b/source/engine/thirdparty/dr_libs/old/dr_fsw.h new file mode 100644 index 0000000..830e7fc --- /dev/null +++ b/source/engine/thirdparty/dr_libs/old/dr_fsw.h @@ -0,0 +1,1625 @@ +// Public Domain. See "unlicense" statement at the end of this file. + +// ABOUT +// +// dr_fsw is a simple library for watching for changes to the file system. This is not a full-featured library +// and is only intended for basic use cases. +// +// Limitations: +// - Only Windows is supported at the moment. +// +// +// +// USAGE +// +// This is a single-file library. To use it, do something like the following in one .c file. +// #define DR_FSW_IMPLEMENTATION +// #include "dr_fsw.h" +// +// You can then #include this file in other parts of the program as you would with any other header file. +// +// Example: +// // Startup. +// drfsw_context* context = drfsw_create_context(); +// if (context == NULL) +// { +// // There was an error creating the context... +// } +// +// drfsw_add_directory(context, "C:/My/Folder"); +// drfsw_add_directory(context, "C:/My/Other/Folder"); +// +// ... +// +// // Meanwhile, in another thread... +// void MyOtherThreadEntryProc() +// { +// drfsw_event e; +// while (isMyApplicationStillAlive && drfsw_next_event(context, &e)) +// { +// switch (e.type) +// { +// case drfsw_event_type_created: OnFileCreated(e.absolutePath); break; +// case drfsw_event_type_deleted: OnFileDeleted(e.absolutePath); break; +// case drfsw_event_type_renamed: OnFileRenamed(e.absolutePath, e.absolutePathNew); break; +// case drfsw_event_type_updated: OnFileUpdated(e.absolutePath); break; +// default: break; +// } +// } +// } +// +// ... +// +// // Shutdown +// drfsw_context* contextOld = context; +// context = NULL; +// drfsw_delete_context(contextOld); +// +// Directories are watched recursively, so try to avoid using this on high level directories +// like "C:\". Also avoid watching a directory that is a descendant of another directory that's +// already being watched. +// +// It does not matter whether or not paths are specified with forward or back slashes; the +// library will normalize all of that internally depending on the platform. Note, however, +// that events always report their paths with forward slashes. +// +// You don't need to wait for events on a separate thread, however drfsw_next_event() is +// a blocking call, so it's usually best to do so. Alternatively, you can use +// drfsw_peek_event() which is the same, except non-blocking. Avoid using both +// drfsw_next_event() and drfsw_peek_event() at the same time because both will remove +// the event from the internal queue so it likely won't work the way you expect. +// +// The shutdown sequence is a bit strange, but since another thread is accessing that pointer, +// you should set any shared pointers to null before calling drfsw_delete_context(). That way, +// the next call to drfsw_next_event() will pass in a null pointer which will cause it to +// immediately return with zero and thus break the loop and terminate the thread. It is up to +// the application to ensure the pointer passed to drfsw_next_event() is valid. Deleting a context +// will cause a waiting call to drfsw_next_event() to return 0, however there is a chance that the +// context is deleted before drfsw_next_event() has entered into it's wait state. It's up to the +// application to make sure the context remains valid. +// +// +// +// QUICK NOTES +// - Files that are not on the machine's local file system will not be detected (such as files on a network drive). +// - In some cases, renaming files won't be detected. Instead it may be implemented as a delete/create pair. +// +// - Win32: Every directory that is watched becomes "in use" by the operating system. It is still possible +// to modify the files and folders inside the watched directory, however. +// - Win32: There is a known issue with the ReadDirectoryChangesW() watch technique (which is used internally) +// where some events won't get processed if a large number of files change in a short period of time. + +#ifndef dr_fsw_h +#define dr_fsw_h + +#ifdef __cplusplus +extern "C" { +#endif + +// The maximum length of a path in bytes, including the null terminator. If a path exceeds this amount, it will be set to an empty +// string. When this is changed the source file will need to be recompiled. Most of the time leaving this at 256 is fine, but it's +// not a problem to increase the size if you are encountering issues. Note that increasing this value will increase memory usage +// on both the heap and the stack. +#ifndef DRFSW_MAX_PATH +//#define DRFSW_MAX_PATH 256U +#define DRFSW_MAX_PATH 1024U +//#define DRFSW_MAX_PATH 4096U +#endif + +// The maximum size of the event queue before it overflows. +#define DRFSW_EVENT_QUEUE_SIZE 1024U + + +// The different event types. +typedef enum +{ + drfsw_event_type_created, + drfsw_event_type_deleted, + drfsw_event_type_renamed, + drfsw_event_type_updated + +} drfsw_event_type; + + +// Structure containing information about an event. +typedef struct +{ + // The type of the event: created, deleted, renamed or updated. + drfsw_event_type type; + + // The absolute path of the file. For renamed events, this is the old name. + char absolutePath[DRFSW_MAX_PATH]; + + // The new file name. This is only used for renamed events. For other event types, this will be an empty string. + char absolutePathNew[DRFSW_MAX_PATH]; + + // The absolute base path. For renamed events, this is the old base path. + char absoluteBasePath[DRFSW_MAX_PATH]; + + // The absolute base path for the new file name. This is only used for renamed events. For other event types, this will be an empty string. + char absoluteBasePathNew[DRFSW_MAX_PATH]; + +} drfsw_event; + + +typedef void* drfsw_context; + + +// Creates a file system watcher. +// +// This will create a background thread that will do the actual checking. +drfsw_context* drfsw_create_context(void); + +// Deletes the given file system watcher. +// +// This will not return until the thread watching for changes has returned. +// +// You do not need to remove the watched directories beforehand - this function will make sure everything is cleaned up properly. +void drfsw_delete_context(drfsw_context* pContext); + + +// Adds a directory to watch. This will watch for files and folders recursively. +int drfsw_add_directory(drfsw_context* pContext, const char* absolutePath); + +// Removes a watched directory. +void drfsw_remove_directory(drfsw_context* pContext, const char* absolutePath); + +// Helper for removing every watched directory. +void drfsw_remove_all_directories(drfsw_context* pContext); + +// Determines whether or not the given directory is being watched. +int drfsw_is_watching_directory(drfsw_context* pContext, const char* absolutePath); + + +// Waits for an event from the file system. +// +// This is a blocking function. Call drfsw_peek_event() to do a non-blocking call. If an error occurs, or the context is deleted, 0 +// will be returned and the memory pointed to by pEventOut will be undefined. +// +// This can be called from any thread, however it should not be called from multiple threads simultaneously. +// +// Use caution when using this combined with drfsw_peek_event(). In almost all cases you should use just one or the other at any +// given time. +// +// It is up to the application to ensure the context is still valid before calling this function. +// +// Example Usage: +// +// void MyFSWatcher() { +// drfsw_event e; +// while (isMyContextStillAlive && drfsw_next_event(context, e)) { +// // Do something with the event... +// } +// } +int drfsw_next_event(drfsw_context* pContext, drfsw_event* pEventOut); + +// Checks to see if there is a pending event, and if so, returns non-zero and fills the given structure with the event details. This +// removes the event from the queue. +// +// This can be called from any thread, however it should not be called from multiple threads simultaneously. +// +// It is up to the application to ensure the context is still valid before calling this function. +int drfsw_peek_event(drfsw_context* pContext, drfsw_event* pEventOut); + + +#ifdef __cplusplus +} +#endif + +#endif //dr_fsw_h + + +/////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef DR_FSW_IMPLEMENTATION + +// NOTES: +// +// Win32 and ReadDirectoryChangesW +// +// Here is how watching for changes via the ReadDirectoryChangesW() works: +// 1) You create a handle to the directory with CreateFile() +// 2) You pass this handle to ReadDirectoryChangesW(), including a pointer to a function that is called when changes to the directory are made. +// 3) From the aforementioned callback, ReadDirectoryChangesW() needs to be called again +// +// There are, however, a lot of details that need to be handled correctly in order for this to work +// +// First of all, the callback passed to ReadDirectoryChangesW() will not be called unless the calling thread is in an alertable state. A thread +// is put into an alertable state with WaitForMultipleObjectsEx() (the Ex version is important since it has an extra parameter that lets you +// put the thread into an alertable state). Using this blocks the thread which means you need to create a worker thread in the background. + +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-align" +#endif + +#ifndef DRFSW_PRIVATE +#define DRFSW_PRIVATE static +#endif + +// The number of FILE_NOTIFY_INFORMATION structures in the buffer that's passed to ReadDirectoryChangesW() +#define WIN32_RDC_FNI_COUNT DRFSW_EVENT_QUEUE_SIZE + +#include + +#if defined(_WIN32) +#include + +DRFSW_PRIVATE void* drfsw_malloc(size_t sizeInBytes) +{ + return HeapAlloc(GetProcessHeap(), 0, sizeInBytes); +} + +DRFSW_PRIVATE void drfsw_free(void* p) +{ + HeapFree(GetProcessHeap(), 0, p); +} + +DRFSW_PRIVATE void drfsw_memcpy(void* dst, const void* src, size_t sizeInBytes) +{ + CopyMemory(dst, src, sizeInBytes); +} + +DRFSW_PRIVATE void drfsw_zeromemory(void* dst, size_t sizeInBytes) +{ + ZeroMemory(dst, sizeInBytes); +} +#else +#include +#include + +DRFSW_PRIVATE void* drfsw_malloc(size_t sizeInBytes) +{ + return malloc(sizeInBytes); +} + +DRFSW_PRIVATE void drfsw_free(void* p) +{ + free(p); +} + +DRFSW_PRIVATE void drfsw_memcpy(void* dst, const void* src, size_t sizeInBytes) +{ + memcpy(dst, src, sizeInBytes); +} + +DRFSW_PRIVATE void drfsw_zeromemory(void* dst, size_t sizeInBytes) +{ + memset(dst, 0, sizeInBytes); +} +#endif + + +DRFSW_PRIVATE int drfsw_strcpy(char* dst, unsigned int dstSizeInBytes, const char* src) +{ +#if defined(_MSC_VER) + return strcpy_s(dst, dstSizeInBytes, src); +#else + if (dst == 0) { + return EINVAL; + } + if (dstSizeInBytes == 0) { + return ERANGE; + } + if (src == 0) { + dst[0] = '\0'; + return EINVAL; + } + + char* iDst = dst; + const char* iSrc = src; + size_t remainingSizeInBytes = dstSizeInBytes; + while (remainingSizeInBytes > 0 && iSrc[0] != '\0') + { + iDst[0] = iSrc[0]; + + iDst += 1; + iSrc += 1; + remainingSizeInBytes -= 1; + } + + if (remainingSizeInBytes > 0) { + iDst[0] = '\0'; + } else { + dst[0] = '\0'; + return ERANGE; + } + + return 0; +#endif +} + + +DRFSW_PRIVATE int drfsw_event_init(drfsw_event* pEvent, drfsw_event_type type, const char* absolutePath, const char* absolutePathNew, const char* absoluteBasePath, const char* absoluteBasePathNew) +{ + if (pEvent != NULL) + { + pEvent->type = type; + + if (absolutePath != NULL) { + drfsw_strcpy(pEvent->absolutePath, DRFSW_MAX_PATH, absolutePath); + } else { + drfsw_zeromemory(pEvent->absolutePath, DRFSW_MAX_PATH); + } + + if (absolutePathNew != NULL) { + drfsw_strcpy(pEvent->absolutePathNew, DRFSW_MAX_PATH, absolutePathNew); + } else { + drfsw_zeromemory(pEvent->absolutePathNew, DRFSW_MAX_PATH); + } + + + if (absoluteBasePath != NULL) { + drfsw_strcpy(pEvent->absoluteBasePath, DRFSW_MAX_PATH, absoluteBasePath); + } else { + drfsw_zeromemory(pEvent->absoluteBasePath, DRFSW_MAX_PATH); + } + + if (absoluteBasePathNew != NULL) { + drfsw_strcpy(pEvent->absoluteBasePathNew, DRFSW_MAX_PATH, absoluteBasePathNew); + } else { + drfsw_zeromemory(pEvent->absoluteBasePathNew, DRFSW_MAX_PATH); + } + + return 1; + } + + return 0; +} + + +typedef struct +{ + // The buffer containing the events in the queue. + drfsw_event* pBuffer; + + // The size of the buffer, in drfsw_event's. + unsigned int bufferSize; + + // The number of items in the queue. + unsigned int count; + + // The index of the first item in the queue. + unsigned int indexFirst; + +#if defined(_WIN32) + // The semaphore for blocking in drfsw_next_event(). + HANDLE hSemaphore; + + // The mutex for synchronizing access to the buffer. This is needed because drfsw_next_event() will need to read the buffer while + // another thread is filling it with events. In addition, it will help to keep drfsw_next_event() and drfsw_peek_event() playing + // nicely with each other. + HANDLE hLock; +#endif + +} drfsw_event_queue; + +DRFSW_PRIVATE int drfsw_event_queue_init(drfsw_event_queue* pQueue) +{ + if (pQueue != NULL) + { + pQueue->pBuffer = NULL; + pQueue->bufferSize = 0; + pQueue->indexFirst = 0; + pQueue->count = 0; + +#if defined(_WIN32) + pQueue->hSemaphore = CreateSemaphoreW(NULL, 0, DRFSW_EVENT_QUEUE_SIZE, NULL); + if (pQueue->hSemaphore == NULL) + { + drfsw_free(pQueue->pBuffer); + return 0; + } + + pQueue->hLock = CreateEventW(NULL, FALSE, TRUE, NULL); + if (pQueue->hLock == NULL) + { + CloseHandle(pQueue->hSemaphore); + drfsw_free(pQueue->pBuffer); + return 0; + } +#endif + + return 1; + } + + return 0; +} + +DRFSW_PRIVATE void drfsw_event_queue_uninit(drfsw_event_queue* pQueue) +{ + if (pQueue != NULL) + { + drfsw_free(pQueue->pBuffer); + pQueue->pBuffer = NULL; + + pQueue->bufferSize = 0; + pQueue->indexFirst = 0; + pQueue->count = 0; + +#if defined(_WIN32) + CloseHandle(pQueue->hSemaphore); + pQueue->hSemaphore = NULL; + + CloseHandle(pQueue->hLock); + pQueue->hLock = NULL; +#endif + } +} + +DRFSW_PRIVATE unsigned int drfsw_event_queue_getcount(drfsw_event_queue* pQueue) +{ + if (pQueue != NULL) + { + return pQueue->count; + } + + return 0; +} + +DRFSW_PRIVATE void drfsw_event_queue_inflate(drfsw_event_queue* pQueue) +{ + if (pQueue != NULL) + { + unsigned int newBufferSize = pQueue->bufferSize + 1; + if (pQueue->bufferSize > 0) + { + newBufferSize = pQueue->bufferSize*2; + } + + drfsw_event* pOldBuffer = pQueue->pBuffer; + drfsw_event* pNewBuffer = (drfsw_event*)drfsw_malloc(newBufferSize * sizeof(drfsw_event)); + + for (unsigned int iDst = 0; iDst < pQueue->count; ++iDst) + { + unsigned int iSrc = (pQueue->indexFirst + iDst) % pQueue->bufferSize; + drfsw_memcpy(pNewBuffer + iDst, pOldBuffer + iSrc, sizeof(drfsw_event)); + } + + + pQueue->bufferSize = newBufferSize; + pQueue->pBuffer = pNewBuffer; + pQueue->indexFirst = 0; + + drfsw_free(pOldBuffer); + } +} + +DRFSW_PRIVATE int drfsw_event_queue_pushback(drfsw_event_queue* pQueue, drfsw_event* pEvent) +{ + if (pQueue != NULL) + { + if (pEvent != NULL) + { + unsigned int count = drfsw_event_queue_getcount(pQueue); + if (count == DRFSW_EVENT_QUEUE_SIZE) + { + // We've hit the limit. + return 0; + } + + if (count == pQueue->bufferSize) + { + drfsw_event_queue_inflate(pQueue); + assert(count < pQueue->bufferSize); + } + + + // Insert the value. + unsigned int iDst = (pQueue->indexFirst + pQueue->count) % pQueue->bufferSize; + drfsw_memcpy(pQueue->pBuffer + iDst, pEvent, sizeof(drfsw_event)); + + + // Increment the counter. + pQueue->count += 1; + + + return 1; + } + } + + return 0; +} + +DRFSW_PRIVATE int drfsw_event_queue_pop(drfsw_event_queue* pQueue, drfsw_event* pEventOut) +{ + if (pQueue != NULL && pQueue->count > 0) + { + if (pEventOut != NULL) + { + drfsw_memcpy(pEventOut, pQueue->pBuffer + pQueue->indexFirst, sizeof(drfsw_event)); + pQueue->indexFirst = (pQueue->indexFirst + 1) % pQueue->bufferSize; + } + + pQueue->count -= 1; + + + return 1; + } + + return 0; +} + + + +// A simple function for appending a relative path to an absolute path. This does not resolve "." and ".." components. +DRFSW_PRIVATE int drfsw_make_absolute_path(const char* absolutePart, const char* relativePart, char absolutePathOut[DRFSW_MAX_PATH]) +{ + size_t absolutePartLength = strlen(absolutePart); + size_t relativePartLength = strlen(relativePart); + + if (absolutePartLength > 0) + { + if (absolutePart[absolutePartLength - 1] == '/') + { + absolutePartLength -= 1; + } + + if (absolutePartLength > DRFSW_MAX_PATH) + { + absolutePartLength = DRFSW_MAX_PATH - 1; + } + } + + if (absolutePartLength + relativePartLength + 1 > DRFSW_MAX_PATH) + { + relativePartLength = DRFSW_MAX_PATH - 1 - absolutePartLength - 1; // -1 for the null terminate and -1 for the slash. + } + + + // Absolute part. + memcpy(absolutePathOut, absolutePart, absolutePartLength); + + // Slash. + absolutePathOut[absolutePartLength] = '/'; + + // Relative part. + memcpy(absolutePathOut + absolutePartLength + 1, relativePart, relativePartLength); + + // Null terminator. + absolutePathOut[absolutePartLength + 1 + relativePartLength] = '\0'; + + + return 1; +} + +// Replaces the back slashes with forward slashes in the given string. This operates on the string in place. +DRFSW_PRIVATE int drfsw_to_forward_slashes(char* path) +{ + if (path != NULL) + { + unsigned int counter = 0; + while (*path++ != '\0' && counter++ < DRFSW_MAX_PATH) + { + if (*path == '\\') + { + *path = '/'; + } + } + + return 1; + } + + return 0; +} + + +typedef struct +{ + // A pointer to the buffer containing pointers to the objects. + void** buffer; + + // The size of the buffer, in pointers. + unsigned int bufferSize; + + // The number of pointers in the list. + unsigned int count; + +} drfsw_list; + +DRFSW_PRIVATE int drfsw_list_init(drfsw_list* pList) +{ + if (pList != NULL) + { + pList->buffer = NULL; + pList->bufferSize = 0; + pList->count = 0; + + return 1; + } + + return 0; +} + +DRFSW_PRIVATE void drfsw_list_uninit(drfsw_list* pList) +{ + if (pList != NULL) + { + drfsw_free(pList->buffer); + } +} + +DRFSW_PRIVATE void drfsw_list_inflate(drfsw_list* pList) +{ + if (pList != NULL) + { + unsigned int newBufferSize = pList->bufferSize + 1; + if (pList->bufferSize > 0) + { + newBufferSize = pList->bufferSize*2; + } + + void** pOldBuffer = pList->buffer; + void** pNewBuffer = (void**)drfsw_malloc(newBufferSize*sizeof(void*)); + + // Move everything over to the new buffer. + for (unsigned int i = 0; i < pList->count; ++i) + { + pNewBuffer[i] = pOldBuffer[i]; + } + + + pList->bufferSize = newBufferSize; + pList->buffer = pNewBuffer; + } +} + +DRFSW_PRIVATE void drfsw_list_pushback(drfsw_list* pList, void* pItem) +{ + if (pList != NULL) + { + if (pList->count == pList->bufferSize) + { + drfsw_list_inflate(pList); + assert(pList->count < pList->bufferSize); + + pList->buffer[pList->count] = pItem; + pList->count += 1; + } + } +} + +DRFSW_PRIVATE void drfsw_list_removebyindex(drfsw_list* pList, unsigned int index) +{ + if (pList != NULL) + { + assert(index < pList->count); + assert(pList->count > 0); + + // Just move everything down one slot. + for (unsigned int i = index; index < pList->count - 1; ++i) + { + pList->buffer[i] = pList->buffer[i + 1]; + } + + pList->count -= 1; + } +} + + + +#if defined(_WIN32) +#define DRFSW_MAX_PATH_W (DRFSW_MAX_PATH / sizeof(wchar_t)) + +/////////////////////////////////////////////// +// ReadDirectoryChangesW + +static const int WIN32_RDC_PENDING_WATCH = (1 << 0); +static const int WIN32_RDC_PENDING_DELETE = (1 << 1); + + +// Replaces the forward slashes to back slashes for use with Win32. This operates on the string in place. +DRFSW_PRIVATE int drfsw_to_back_slashes_wchar(wchar_t* path) +{ + if (path != NULL) + { + unsigned int counter = 0; + while (*path++ != L'\0' && counter++ < DRFSW_MAX_PATH_W) + { + if (*path == L'/') + { + *path = L'\\'; + } + } + + return 1; + } + + return 0; +} + +// Converts a UTF-8 string to wchar_t for use with Win32. Free the returned pointer with drfsw_free(). +DRFSW_PRIVATE int drfsw_utf8_to_wchar(const char* str, wchar_t wstrOut[DRFSW_MAX_PATH_W]) +{ + int wcharsWritten = MultiByteToWideChar(CP_UTF8, 0, str, -1, wstrOut, DRFSW_MAX_PATH_W); + if (wcharsWritten > 0) + { + assert((unsigned int)wcharsWritten <= DRFSW_MAX_PATH_W); + return 1; + } + + return 0; +} + +DRFSW_PRIVATE int drfsw_wchar_to_utf8(const wchar_t* wstr, int wstrCC, char pathOut[DRFSW_MAX_PATH]) +{ + int bytesWritten = WideCharToMultiByte(CP_UTF8, 0, wstr, wstrCC, pathOut, DRFSW_MAX_PATH - 1, NULL, NULL); + if (bytesWritten > 0) + { + assert((unsigned int)bytesWritten < DRFSW_MAX_PATH); + pathOut[bytesWritten] = '\0'; + + return 1; + } + + return 0; +} + +// Converts a UTF-8 path to wchar_t and converts the slashes to backslashes for use with Win32. Free the returned pointer with drfsw_free(). +DRFSW_PRIVATE int drfsw_to_win32_path_wchar(const char* path, wchar_t wpathOut[DRFSW_MAX_PATH_W]) +{ + if (drfsw_utf8_to_wchar(path, wpathOut)) + { + return drfsw_to_back_slashes_wchar(wpathOut); + } + + return 0; +} + +// Converts a wchar_t Win32 path to a char unix style path (forward slashes instead of back). +DRFSW_PRIVATE int drfsw_from_win32_path(const wchar_t* wpath, int wpathCC, char pathOut[DRFSW_MAX_PATH]) +{ + if (drfsw_wchar_to_utf8(wpath, wpathCC, pathOut)) + { + return drfsw_to_forward_slashes(pathOut); + } + + return 0; +} + + +DRFSW_PRIVATE VOID CALLBACK drfsw_win32_completionroutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped); +DRFSW_PRIVATE VOID CALLBACK drfsw_win32_schedulewatchAPC(ULONG_PTR dwParam); +DRFSW_PRIVATE VOID CALLBACK drfsw_win32_cancelioAPC(ULONG_PTR dwParam); + +typedef struct +{ + // Thelist containing pointers to the watched directory objects. This is not thread safe. + drfsw_list list; + + // The lock for synchronizing access to the list. + HANDLE hLock; + +} drfsw_directory_list_win32; + +// Structure representing the watcher context for the Win32 RDC method. +typedef struct +{ + // The list of watched directories. + drfsw_directory_list_win32 watchedDirectories; + + // The event queue. + drfsw_event_queue eventQueue; + + + // A handle to the watcher thread. + HANDLE hThread; + + // The event that will become signaled when the watcher thread needs to be terminated. + HANDLE hTerminateEvent; + + // The semaphore which is used when deleting a watched folder. This starts off at 0, and the maximum count is 1. When a watched + // directory is removed, the calling thread will wait on this semaphore while the worker thread does the deletion. + HANDLE hDeleteDirSemaphore; + + // Whether or not the watch thread needs to be terminated. + BOOL terminateThread; + +} drfsw_context_win32; + +DRFSW_PRIVATE drfsw_context* drfsw_create_context_win32(void); +DRFSW_PRIVATE void drfsw_delete_context_win32(drfsw_context_win32* pContext); +DRFSW_PRIVATE int drfsw_add_directory_win32(drfsw_context_win32* pContext, const char* absolutePath); +DRFSW_PRIVATE void drfsw_remove_directory_win32(drfsw_context_win32* pContext, const char* absolutePath); +DRFSW_PRIVATE void drfsw_remove_all_directories_win32(drfsw_context_win32* pContext); +DRFSW_PRIVATE int drfsw_is_watching_directory_win32(drfsw_context_win32* pContext, const char* absolutePath); +DRFSW_PRIVATE int drfsw_next_event_win32(drfsw_context_win32* pContext, drfsw_event* pEventOut); +DRFSW_PRIVATE int drfsw_peek_event_win32(drfsw_context_win32* pContext, drfsw_event* pEventOut); +DRFSW_PRIVATE void drfsw_postevent_win32(drfsw_context_win32* pContext, drfsw_event* pEvent); + + +// Structure representing a directory that's being watched with the Win32 RDC method. +typedef struct +{ + // A pointer to the context that owns this directory. + drfsw_context_win32* pContext; + + // The absolute path of the directory being watched. + char absolutePath[DRFSW_MAX_PATH]; + + // The handle representing the directory. This is created with CreateFile() which means the directory itself will become locked + // because the operating system see's it as "in use". It is possible to modify the files and folder inside the directory, though. + HANDLE hDirectory; + + // This is required for for ReadDirectoryChangesW(). + OVERLAPPED overlapped; + + // A pointer to the buffer containing the notification objects that is passed to the notification callback specified with + // ReadDirectoryChangesW(). This must be aligned to a DWORD boundary, but drfsw_malloc() will do that for us, so that should not + // be an issue. + FILE_NOTIFY_INFORMATION* pFNIBuffer1; + FILE_NOTIFY_INFORMATION* pFNIBuffer2; + + // The size of the file notification information buffer, in bytes. + DWORD fniBufferSizeInBytes; + + // Flags describing the state of the directory. + int flags; + +} drfsw_directory_win32; + +DRFSW_PRIVATE int drfsw_directory_win32_beginwatch(drfsw_directory_win32* pDirectory); + + +DRFSW_PRIVATE void drfsw_directory_win32_uninit(drfsw_directory_win32* pDirectory) +{ + if (pDirectory != NULL) + { + if (pDirectory->hDirectory != NULL) + { + CloseHandle(pDirectory->hDirectory); + pDirectory->hDirectory = NULL; + } + + drfsw_free(pDirectory->pFNIBuffer1); + pDirectory->pFNIBuffer1 = NULL; + + drfsw_free(pDirectory->pFNIBuffer2); + pDirectory->pFNIBuffer2 = NULL; + } +} + +DRFSW_PRIVATE int drfsw_directory_win32_init(drfsw_directory_win32* pDirectory, drfsw_context_win32* pContext, const char* absolutePath) +{ + if (pDirectory != NULL) + { + pDirectory->pContext = pContext; + drfsw_zeromemory(pDirectory->absolutePath, DRFSW_MAX_PATH); + pDirectory->hDirectory = NULL; + pDirectory->pFNIBuffer1 = NULL; + pDirectory->pFNIBuffer2 = NULL; + pDirectory->fniBufferSizeInBytes = 0; + pDirectory->flags = 0; + + size_t length = strlen(absolutePath); + if (length > 0) + { + memcpy(pDirectory->absolutePath, absolutePath, length); + pDirectory->absolutePath[length] = '\0'; + + wchar_t absolutePathWithBackSlashes[DRFSW_MAX_PATH_W]; + if (drfsw_to_win32_path_wchar(absolutePath, absolutePathWithBackSlashes)) + { + pDirectory->hDirectory = CreateFileW( + absolutePathWithBackSlashes, + FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + NULL); + if (pDirectory->hDirectory != INVALID_HANDLE_VALUE) + { + // From MSDN: + // + // Using a completion routine. To receive notification through a completion routine, do not associate the directory with a + // completion port. Specify a completion routine in lpCompletionRoutine. This routine is called whenever the operation has + // been completed or canceled while the thread is in an alertable wait state. The hEvent member of the OVERLAPPED structure + // is not used by the system, so you can use it yourself. + ZeroMemory(&pDirectory->overlapped, sizeof(pDirectory->overlapped)); + pDirectory->overlapped.hEvent = pDirectory; + + pDirectory->fniBufferSizeInBytes = WIN32_RDC_FNI_COUNT * sizeof(FILE_NOTIFY_INFORMATION); + pDirectory->pFNIBuffer1 = (FILE_NOTIFY_INFORMATION*)drfsw_malloc(pDirectory->fniBufferSizeInBytes); + pDirectory->pFNIBuffer2 = (FILE_NOTIFY_INFORMATION*)drfsw_malloc(pDirectory->fniBufferSizeInBytes); + if (pDirectory->pFNIBuffer1 != NULL && pDirectory->pFNIBuffer2 != NULL) + { + // At this point the directory is initialized, however it is not yet being watched. The watch needs to be triggered from + // the worker thread. To do this, we need to signal hPendingWatchEvent, however that needs to be done after the context + // has added the directory to it's internal list. + return 1; + } + else + { + drfsw_directory_win32_uninit(pDirectory); + } + } + else + { + drfsw_directory_win32_uninit(pDirectory); + } + } + else + { + drfsw_directory_win32_uninit(pDirectory); + } + } + } + + return 0; +} + +DRFSW_PRIVATE int drfsw_directory_win32_schedulewatch(drfsw_directory_win32* pDirectory) +{ + if (pDirectory != NULL) + { + pDirectory->flags |= WIN32_RDC_PENDING_WATCH; + QueueUserAPC(drfsw_win32_schedulewatchAPC, pDirectory->pContext->hThread, (ULONG_PTR)pDirectory); + + return 1; + } + + return 0; +} + +DRFSW_PRIVATE int drfsw_directory_win32_scheduledelete(drfsw_directory_win32* pDirectory) +{ + if (pDirectory != NULL) + { + pDirectory->flags |= WIN32_RDC_PENDING_DELETE; + QueueUserAPC(drfsw_win32_cancelioAPC, pDirectory->pContext->hThread, (ULONG_PTR)pDirectory); + + return 1; + } + + return 0; +} + +DRFSW_PRIVATE int drfsw_directory_win32_beginwatch(drfsw_directory_win32* pDirectory) +{ + // This function should only be called from the worker thread. + + if (pDirectory != NULL) + { + DWORD dwNotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION; + DWORD dwBytes = 0; + if (ReadDirectoryChangesW(pDirectory->hDirectory, pDirectory->pFNIBuffer1, pDirectory->fniBufferSizeInBytes, TRUE, dwNotifyFilter, &dwBytes, &pDirectory->overlapped, drfsw_win32_completionroutine)) + { + pDirectory->flags &= ~WIN32_RDC_PENDING_WATCH; + return 1; + } + } + + return 0; +} + + + + +DRFSW_PRIVATE int drfsw_directory_list_win32_init(drfsw_directory_list_win32* pDirectories) +{ + if (pDirectories != NULL) + { + drfsw_list_init(&pDirectories->list); + + pDirectories->hLock = CreateEvent(NULL, FALSE, TRUE, NULL); + if (pDirectories->hLock != NULL) + { + return 1; + } + } + + return 0; +} + +DRFSW_PRIVATE void drfsw_directory_list_win32_uninit(drfsw_directory_list_win32* pDirectories) +{ + if (pDirectories != NULL) + { + drfsw_list_uninit(&pDirectories->list); + + CloseHandle(pDirectories->hLock); + pDirectories->hLock = NULL; + } +} + + + + + +DRFSW_PRIVATE VOID CALLBACK drfsw_win32_completionroutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) +{ + drfsw_directory_win32* pDirectory = (drfsw_directory_win32*)lpOverlapped->hEvent; + if (pDirectory != NULL) + { + if (dwErrorCode == ERROR_OPERATION_ABORTED) + { + // When we get here, CancelIo() was called on the directory. We treat this as a signal that the context has requested that the directory + // be deleted. At this point the directory has been removed from the context's internal list and we just need to uninitialize and free + // the directory object. + drfsw_context_win32* pContext = pDirectory->pContext; + drfsw_directory_win32_uninit(pDirectory); + drfsw_free(pDirectory); + + ReleaseSemaphore(pContext->hDeleteDirSemaphore, 1, NULL); + return; + } + + assert(dwNumberOfBytesTransfered >= sizeof(FILE_NOTIFY_INFORMATION)); + + // At this point we're not actually watching the directory - there is a chance that as we're executing this section there + // are changes to the file system whose events will go undetected. We need to call ReadDirectoryChangesW() again as soon as + // possible. This routine is always called from the worker thread, and only while it's in an alertable state. Therefore it + // is safe for us to use a simple front/back buffer type system to make it as quick as possible to resume watching operations. + FILE_NOTIFY_INFORMATION* temp = pDirectory->pFNIBuffer1; + pDirectory->pFNIBuffer1 = pDirectory->pFNIBuffer2; + pDirectory->pFNIBuffer2 = temp; + + + // Begin watching again (call ReadDirectoryChangesW() again) as soon as possible. At this point we are not currently watching + // for changes to the directory, so we need to start that before posting events. To start watching we need to send a signal to + // the worker thread which will do the actual call to ReadDirectoryChangesW(). + drfsw_directory_win32_schedulewatch(pDirectory); + + + // Now we loop through all of our notifications and post the event to the context for later processing by drfsw_next_event() + // and drfsw_peek_event(). + char absolutePathOld[DRFSW_MAX_PATH]; + char absoluteBasePathOld[DRFSW_MAX_PATH]; + drfsw_context_win32* pContext = pDirectory->pContext; // Just for convenience. + + + FILE_NOTIFY_INFORMATION* pFNI = pDirectory->pFNIBuffer2; + for (;;) + { + char relativePath[DRFSW_MAX_PATH]; + if (drfsw_from_win32_path(pFNI->FileName, pFNI->FileNameLength / sizeof(wchar_t), relativePath)) + { + char absolutePath[DRFSW_MAX_PATH]; + if (drfsw_make_absolute_path(pDirectory->absolutePath, relativePath, absolutePath)) + { + switch (pFNI->Action) + { + case FILE_ACTION_ADDED: + { + drfsw_event e; + if (drfsw_event_init(&e, drfsw_event_type_created, absolutePath, NULL, pDirectory->absolutePath, NULL)) + { + drfsw_postevent_win32(pContext, &e); + } + + break; + } + + case FILE_ACTION_REMOVED: + { + drfsw_event e; + if (drfsw_event_init(&e, drfsw_event_type_deleted, absolutePath, NULL, pDirectory->absolutePath, NULL)) + { + drfsw_postevent_win32(pContext, &e); + } + + break; + } + + case FILE_ACTION_RENAMED_OLD_NAME: + { + drfsw_strcpy(absolutePathOld, sizeof(absolutePathOld), absolutePath); + drfsw_strcpy(absoluteBasePathOld, sizeof(absoluteBasePathOld), pDirectory->absolutePath); + + break; + } + case FILE_ACTION_RENAMED_NEW_NAME: + { + drfsw_event e; + if (drfsw_event_init(&e, drfsw_event_type_renamed, absolutePathOld, absolutePath, absoluteBasePathOld, pDirectory->absolutePath)) + { + drfsw_postevent_win32(pContext, &e); + } + + break; + } + + case FILE_ACTION_MODIFIED: + { + drfsw_event e; + if (drfsw_event_init(&e, drfsw_event_type_updated, absolutePath, NULL, pDirectory->absolutePath, NULL)) + { + drfsw_postevent_win32(pContext, &e); + } + + break; + } + + default: break; + } + } + } + + + + if (pFNI->NextEntryOffset == 0) + { + break; + } + + pFNI = (FILE_NOTIFY_INFORMATION*)(((char*)pFNI) + pFNI->NextEntryOffset); + } + } +} + +DRFSW_PRIVATE VOID CALLBACK drfsw_win32_schedulewatchAPC(ULONG_PTR dwParam) +{ + drfsw_directory_win32* pDirectory = (drfsw_directory_win32*)dwParam; + if (pDirectory != NULL) + { + if ((pDirectory->flags & WIN32_RDC_PENDING_WATCH) != 0) + { + drfsw_directory_win32_beginwatch(pDirectory); + } + } +} + +DRFSW_PRIVATE VOID CALLBACK drfsw_win32_cancelioAPC(ULONG_PTR dwParam) +{ + drfsw_directory_win32* pDirectory = (drfsw_directory_win32*)dwParam; + if (pDirectory != NULL) + { + if ((pDirectory->flags & WIN32_RDC_PENDING_DELETE) != 0) + { + // We don't free the directory structure from here. Instead we just call CancelIo(). This will trigger + // the ERROR_OPERATION_ABORTED error in the notification callback which is where the real delete will + // occur. That is also where the synchronization lock is released that the thread that called + // drfsw_delete_directory() is waiting on. + CancelIo(pDirectory->hDirectory); + + // The directory needs to be removed from the context's list. The directory object will be freed in the + // notification callback in response to ERROR_OPERATION_ABORTED which will be triggered by the previous + // call to CancelIo(). + for (unsigned int i = 0; i < pDirectory->pContext->watchedDirectories.list.count; ++i) + { + if (pDirectory == pDirectory->pContext->watchedDirectories.list.buffer[i]) + { + drfsw_list_removebyindex(&pDirectory->pContext->watchedDirectories.list, i); + break; + } + } + } + } +} + + + + +DRFSW_PRIVATE DWORD WINAPI _WatcherThreadProc_RDC(drfsw_context_win32 *pContextRDC) +{ + while (!pContextRDC->terminateThread) + { + // Important that we use the Ex version here because we need to put the thread into an alertable state (last argument). If the thread is not put into + // an alertable state, ReadDirectoryChangesW() won't ever call the notification event. + DWORD rc = WaitForSingleObjectEx(pContextRDC->hTerminateEvent, INFINITE, TRUE); + switch (rc) + { + case WAIT_OBJECT_0 + 0: + { + // The context has signaled that it needs to be deleted. + pContextRDC->terminateThread = TRUE; + break; + } + + case WAIT_IO_COMPLETION: + default: + { + // Nothing to do. + break; + } + } + } + + return 0; +} + +DRFSW_PRIVATE drfsw_context* drfsw_create_context_win32() +{ + drfsw_context_win32* pContext = (drfsw_context_win32*)drfsw_malloc(sizeof(drfsw_context_win32)); + if (pContext != NULL) + { + if (drfsw_directory_list_win32_init(&pContext->watchedDirectories)) + { + if (drfsw_event_queue_init(&pContext->eventQueue)) + { + pContext->hTerminateEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + pContext->hDeleteDirSemaphore = CreateSemaphoreW(NULL, 0, 1, NULL); + pContext->terminateThread = FALSE; + + if (pContext->hTerminateEvent != NULL) + { + pContext->hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_WatcherThreadProc_RDC, pContext, 0, NULL); + if (pContext->hThread != NULL) + { + // Everything went well. + } + else + { + CloseHandle(pContext->hTerminateEvent); + + drfsw_free(pContext); + pContext = NULL; + } + } + else + { + drfsw_free(pContext); + pContext = NULL; + } + } + } + } + + return (drfsw_context*)pContext; +} + +DRFSW_PRIVATE void drfsw_delete_context_win32(drfsw_context_win32* pContext) +{ + if (pContext != NULL) + { + // Every watched directory needs to be removed. + drfsw_remove_all_directories_win32(pContext); + + + // Signal the close event, and wait for the thread to finish. + SignalObjectAndWait(pContext->hTerminateEvent, pContext->hThread, INFINITE, FALSE); + + // The thread has finished, so close the handle. + CloseHandle(pContext->hThread); + pContext->hThread = NULL; + + + // We need to wait for the event queue to finish up before deleting the context for real. If we don't do this nextevent() may try + // to access the context and then crash. + WaitForSingleObject(pContext->eventQueue.hLock, INFINITE); + drfsw_event_queue_uninit(&pContext->eventQueue); // <-- This will close pContext->eventQueue.hLock so no need to call SetEvent(). + + + // The worker thread events need to be closed. + CloseHandle(pContext->hTerminateEvent); + pContext->hTerminateEvent = NULL; + + + // The semaphore we use for deleting directories. + CloseHandle(pContext->hDeleteDirSemaphore); + pContext->hDeleteDirSemaphore = NULL; + + + drfsw_directory_list_win32_uninit(&pContext->watchedDirectories); + + // Free the memory. + drfsw_free(pContext); + } +} + +DRFSW_PRIVATE drfsw_directory_win32* drfsw_find_directory_win32(drfsw_context_win32* pContext, const char* absolutePath, unsigned int* pIndexOut) +{ + assert(pContext != NULL); + + for (unsigned int iDirectory = 0; iDirectory < pContext->watchedDirectories.list.count; ++iDirectory) + { + drfsw_directory_win32* pDirectory = (drfsw_directory_win32*)pContext->watchedDirectories.list.buffer[iDirectory]; + if (pDirectory != NULL) + { + if (strcmp(absolutePath, pDirectory->absolutePath) == 0) + { + if (pIndexOut != NULL) + { + *pIndexOut = iDirectory; + } + + return pDirectory; + } + } + } + + return NULL; +} + +DRFSW_PRIVATE int drfsw_add_directory_win32(drfsw_context_win32* pContext, const char* absolutePath) +{ + if (pContext != NULL) + { + drfsw_directory_win32* pDirectory = (drfsw_directory_win32*)drfsw_malloc(sizeof(drfsw_directory_win32)); + if (pDirectory != NULL) + { + if (!drfsw_is_watching_directory_win32(pContext, absolutePath)) + { + if (drfsw_directory_win32_init(pDirectory, pContext, absolutePath)) + { + // At this point the directory has been initialized, but it's not yet being watched. To do this, we need to call ReadDirectoryChangesW() from + // the worker thread which means we need to signal an event which the worker thread will be waiting on. When the worker thread receives the + // signal, it will iterate over each directory in the context and check for the ones that are pending watching. Therefore, before signaling + // the event, we need to make sure the directory is added to the context's list. + WaitForSingleObject(pContext->watchedDirectories.hLock, INFINITE); + { + drfsw_list_pushback(&pContext->watchedDirectories.list, pDirectory); + } + SetEvent(pContext->watchedDirectories.hLock); + + // The directory is now in the list and we can send the signal. + drfsw_directory_win32_schedulewatch(pDirectory); + + + return 1; + } + else + { + drfsw_free(pDirectory); + pDirectory = NULL; + } + } + else + { + drfsw_free(pDirectory); + pDirectory = NULL; + } + } + } + + + + // An error occured if we got here. + return 0; +} + +DRFSW_PRIVATE void drfsw_remove_directory_win32_no_lock(drfsw_context_win32* pContext, const char* absolutePath) +{ + assert(pContext != NULL); + + unsigned int index; + drfsw_directory_win32* pDirectory = drfsw_find_directory_win32(pContext, absolutePath, &index); + if (pDirectory != NULL) + { + // When removing a directory, we need to call CancelIo() on the file handle we created for the directory. The problem is that + // this needs to be called on the worker thread in order for watcher notification callback function to get the correct error + // code. To do this we need to signal an event which the worker thread is waiting on. The worker thread will then call CancelIo() + // which in turn will trigger the correct error code in the notification callback. The notification callback is where the + // the object will be deleted for real and will release the synchronization lock that this function is waiting on below. + drfsw_directory_win32_scheduledelete(pDirectory); + + // Now we need to wait for the relevant event to become signaled. This will become signaled when the worker thread has finished + // deleting the file handle and whatnot from it's end. + WaitForSingleObject(pContext->hDeleteDirSemaphore, INFINITE); + } +} + +DRFSW_PRIVATE void drfsw_remove_directory_win32(drfsw_context_win32* pContext, const char* absolutePath) +{ + if (pContext != NULL) + { + WaitForSingleObject(pContext->watchedDirectories.hLock, INFINITE); + { + drfsw_remove_directory_win32_no_lock(pContext, absolutePath); + } + SetEvent(pContext->watchedDirectories.hLock); + } +} + +DRFSW_PRIVATE void drfsw_remove_all_directories_win32(drfsw_context_win32* pContext) +{ + if (pContext != NULL) + { + WaitForSingleObject(pContext->watchedDirectories.hLock, INFINITE); + { + for (unsigned int i = pContext->watchedDirectories.list.count; i > 0; --i) + { + drfsw_remove_directory_win32_no_lock(pContext, ((drfsw_directory_win32*)pContext->watchedDirectories.list.buffer[i - 1])->absolutePath); + } + } + SetEvent(pContext->watchedDirectories.hLock); + } +} + +DRFSW_PRIVATE int drfsw_is_watching_directory_win32(drfsw_context_win32* pContext, const char* absolutePath) +{ + if (pContext != NULL) + { + return drfsw_find_directory_win32(pContext, absolutePath, NULL) != NULL; + } + + return 0; +} + + +DRFSW_PRIVATE int drfsw_next_event_win32(drfsw_context_win32* pContext, drfsw_event* pEvent) +{ + int result = 0; + if (pContext != NULL && !pContext->terminateThread) + { + // Wait for either the semaphore or the thread to terminate. + HANDLE hEvents[2]; + hEvents[0] = pContext->hThread; + hEvents[1] = pContext->eventQueue.hSemaphore; + + DWORD rc = WaitForMultipleObjects(sizeof(hEvents) / sizeof(hEvents[0]), hEvents, FALSE, INFINITE); + switch (rc) + { + case WAIT_OBJECT_0 + 0: + { + // The thread has been terminated. + result = 0; + break; + } + + case WAIT_OBJECT_0 + 1: + { + // We're past the semaphore block, so now we can copy of the event. We need to lock the queue before doing this in case another thread wants + // to try pushing another event onto the queue. + if (!pContext->terminateThread) + { + DWORD eventLockResult = WaitForSingleObject(pContext->eventQueue.hLock, INFINITE); + if (eventLockResult == WAIT_OBJECT_0) + { + drfsw_event_queue_pop(&pContext->eventQueue, pEvent); + } + SetEvent(pContext->eventQueue.hLock); + + if (eventLockResult == WAIT_OBJECT_0) + { + result = 1; + } + else + { + // The lock returned early for some reason which means there must have been some kind of error or the context has been destroyed. + result = 0; + } + } + + break; + } + + default: break; + } + } + + return result; +} + +DRFSW_PRIVATE int drfsw_peek_event_win32(drfsw_context_win32* pContext, drfsw_event* pEvent) +{ + int result = 0; + if (pContext != NULL) + { + DWORD eventLockResult = WaitForSingleObject(pContext->eventQueue.hLock, INFINITE); + { + // Make sure we decrement our semaphore counter. Don't block here. + WaitForSingleObject(pContext->eventQueue.hSemaphore, 0); + + // Now we just copy over the data by popping it from the queue. + if (eventLockResult == WAIT_OBJECT_0) + { + if (drfsw_event_queue_getcount(&pContext->eventQueue) > 0) + { + drfsw_event_queue_pop(&pContext->eventQueue, pEvent); + result = 1; + } + else + { + result = 0; + } + } + else + { + // Waiting on the event queue lock failed for some reason. It could mean that the context has been deleted. + result = 0; + } + } + SetEvent(pContext->eventQueue.hLock); + } + + return result; +} + +DRFSW_PRIVATE void drfsw_postevent_win32(drfsw_context_win32* pContext, drfsw_event* pEvent) +{ + if (pContext != NULL && pEvent != NULL) + { + // Add the event to the queue. + WaitForSingleObject(pContext->eventQueue.hLock, INFINITE); + { + drfsw_event_queue_pushback(&pContext->eventQueue, pEvent); + } + SetEvent(pContext->eventQueue.hLock); + + + // Release the semaphore so that drfsw_next_event() can handle it. + ReleaseSemaphore(pContext->eventQueue.hSemaphore, 1, NULL); + } +} + + + +//////////////////////////////////// +// Public API for Win32 + +drfsw_context* drfsw_create_context() +{ + return drfsw_create_context_win32(); +} + +void drfsw_delete_context(drfsw_context* pContext) +{ + drfsw_delete_context_win32((drfsw_context_win32*)pContext); +} + + +int drfsw_add_directory(drfsw_context* pContext, const char* absolutePath) +{ + return drfsw_add_directory_win32((drfsw_context_win32*)pContext, absolutePath); +} + +void drfsw_remove_directory(drfsw_context* pContext, const char* absolutePath) +{ + drfsw_remove_directory_win32((drfsw_context_win32*)pContext, absolutePath); +} + +void drfsw_remove_all_directories(drfsw_context* pContext) +{ + drfsw_remove_all_directories_win32((drfsw_context_win32*)pContext); +} + +int drfsw_is_watching_directory(drfsw_context* pContext, const char* absolutePath) +{ + return drfsw_is_watching_directory_win32((drfsw_context_win32*)pContext, absolutePath); +} + + +int drfsw_next_event(drfsw_context* pContext, drfsw_event* pEventOut) +{ + return drfsw_next_event_win32((drfsw_context_win32*)pContext, pEventOut); +} + +int drfsw_peek_event(drfsw_context* pContext, drfsw_event* pEventOut) +{ + return drfsw_peek_event_win32((drfsw_context_win32*)pContext, pEventOut); +} + + +#endif // Win32 + + +#if defined(__clang__) + #pragma GCC diagnostic pop +#endif + +#endif //DR_FSW_IMPLEMENTATION + + + + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ diff --git a/source/engine/thirdparty/dr_libs/old/dr_gui.h b/source/engine/thirdparty/dr_libs/old/dr_gui.h new file mode 100644 index 0000000..7283223 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/old/dr_gui.h @@ -0,0 +1,17167 @@ +// Public Domain. See "unlicense" statement at the end of this file. + +// QUICK NOTES +// +// General +// - dr_gui is a low-level GUI system that works on generic objects referred to as "elements". +// - An element is the most basic unit in dr_gui. It contains basic information about it's layout and hierarchy. +// - Elements can be used as the building blocks for more complex controls such as list boxes and scrollbars. +// - The layout of elements use floats instead of integers. The rationale for this is that it makes it easier to do certain +// layout arithmetic. For example, if you want to evenly distribute 3 elements across a fixed area, the integer based +// arithmetic can cause rounding errors which cause the elements to not sit flush against the area. By using float-based +// arithmetic we can avoid that particular issue. +// +// Hierarchy +// - An element can have a parent and any number of children. If an element does not have a parent, it is referred to as the +// top-level element. +// - When an element is deleted, it's children will be deleted as well. +// - Top-level elements do not have siblings. +// +// Event Handling +// - The application must notify dr_gui of application-generated events such as key strokes and mouse movements. These are +// referred to as inbound events. An event that is generated by dr_gui are referred to as outbound events. +// - Inbound events are used to generate outbound events. For example, a mouse-move inbound event will generate an outbound +// mouse-move event, and perhaps a mouse leave/enter pair. +// - Outbound events are posted and handled immediately. A call to drgui_post_inbound_event() will not return until all of +// the outbound events it generates have been handled. +// - Inbound events are not thread safe, however an application is free to post an inbound event from any thread so long as +// it does it's own synchronization. +// - Inbound events will typically specify the relevant top-level element and let dr_gui do the relevant processing required +// to generate the appropriate outbound events. For example, the mouse-move event will be specified with respect to the top- +// level element, but dr_gui will determine the exact child element that the mouse moved on and thus should receive the +// relevant outbound mouse-move event. +// - There are some special events that are handled differently to normal events. The best example is the paint events. The +// paint event is only called from drgui_draw(). +// - Key press/release events are only ever posted to the element that has the keyboard capture/focus which is set with +// drgui_capture_keyboard(). Thus, when posting an inbound key event, a top-level element is not required when posting +// those events. The relevant context is still required, however. +// +// Global Outbound Event Handling +// - At times dr_gui will need to notify the host application in order for certain functionality to work properly. For example. +// when the mouse is captured it won't work 100% correct unless the host application has a chance to capture the mouse against +// the container window. Because dr_gui has no notion of a window system it relies on the host application to handle this +// properly. +// - A global outbound event handler should be implemented for each of the following events: +// - on_dirty: Called when a region of an element is marked as dirty and needs to be redrawn. The application will want to +// invalidate the container window to trigger an operating system redraw. Set this with drgui_set_global_on_dirty(). +// - on_capture_mouse: Called when the mouse is captured and gives the application the opportunity to capture the mouse against +// the container window at the operating system level. Set with drgui_set_global_on_capture_mouse(). +// - on_release_mouse: Called when the mouse is released. The opposite of on_capture_mouse. +// - on_capture_keyboard: Called when an element is given the keyboard focus and gives the application the opportunity to +// apply the keyboard focus to the container window. Set with drgui_set_global_on_capture_keyboard(). +// - on_release_keyboard: Called when an element loses the keyboard focus. The opposite of on_capture_keyboard. +// - on_change_cursor: Called when the current cursor needs to be changed as a result of the mouse moving over a new element. +// +// Layout +// - An element's data structure does not store it's relative position but instead stores it's absolute position. The rationale +// for this is that storing it as relative complicates absolute positioning calculations because it would need to do a recursive +// traversal of the element's ancestors. +// - Child elements can be scaled by setting an element's inner scale. The inner scale does not scale the element itself - only +// it's children. +// - When an element is drawn, everything is scaled by it's inner scale. For example, if the inner scale is 2x and a 100x100 quad +// is drawn, the quad will be scaled to 200x200. An exception to this rule is fonts, which are never scaled. This 0s because +// text is always drawn based on the size of the font. +// - Applications should only need to work on unscaled coordinates. That is, an application should never need to worry about +// manual scaling, except for fonts. When positioning and sizing child elements, they should be done based on unscaled +// coordinates. +// - Use the inner scale system for DPI awareness. +// - The inner scale is applied recursively. That is, if a top level element has it's inner scale set to 2x and one of it's +// children has an inner scale of 2x, the actual inner scale of the child element will be 4x. +// +// +// Drawing/Painting +// - Drawing is one of the more complex parts of the GUI because it can be a bit unintuitive regarding exactly when an element +// is drawn and when a drawing function is allowed to be called. +// - To draw an element, call drgui_draw(). This takes a pointer to the element to draw and the rectangle region that should +// be redrawn. Any children that fall inside the specified rectangle region will be redrawn as well. You do not want to call +// drgui_draw() on a parent element and then again on it's children because dr_gui will do that automatically. +// - drgui_draw() does not draw anything directly, but rather calls painting callback routines which is where the actual +// drawing takes place. +// - Sometimes an application will need to be told when a region of an element is dirty and needs redrawing. An example is +// event-driven, non real-time applications such as normal desktop applications. To mark an element as dirty, you call the +// drgui_dirty() function which takes the element that is dirty, and the rectangle region that needs to be redrawn. This +// does not redraw the element immediately, but instead posts an on_dirty event for the application. Marking regions as dirty +// is not strictly required, but you should prefer it for event-driven applications that require painting operations to be +// performed at specific times (such as inside Win32's WM_PAINT messages). +// - Some operations will cause a region of an element to become dirty - such as when it is resized. dr_gui will +// automatically mark the relevant regions as dirty which in turn will cause a paint message to be posted. If this is not +// required, it can be disabled with drgui_disable_auto_dirty(). You may want to disable automatic dirtying if you are +// running a real-time application like a game which would redraw the entire GUI every frame anyway and thus not require +// handling of the paint message. +// - Real-time application guidelines (games, etc.): +// - drgui_disable_auto_dirty() +// - drgui_draw(pTopLevelElement, 0, 0, viewportWidth, viewportHeight) at the end of every frame after your main loop. +// + + +// +// OPTIONS +// +// #define DRGUI_NO_DR_2D +// Disable dr_2d integration. Disabling dr_2d will require you to implement your own drawing callbacks. +// +// #define DRGUI_NO_TEXT_EDITING +// Disables the text box control and text engine. + + +// +// EXAMPLES +// +// Basic Drawing: +// +// drgui_draw(pTopLevelElement, 0, 0, drgui_get_width(pTopLevelElement), drgui_get_height(pTopLevelElement)); +// +// ------------------------- +// +// Event-Driven Drawing (Win32): +// +// void my_global_on_dirty_win32(drgui_element* pElement, drgui_rect relativeRect) { +// drgui_rect absoluteRect = relativeRect; +// drgui_make_rect_absolute(pElement, &absoluteRect); +// +// RECT rect; +// rect.left = absoluteRect.left; +// rect.top = absoluteRect.top; +// rect.right = absoluteRect.right; +// rect.height = absoluteRect.bottom; +// InvalidateRect((HWND)drgui_get_user_data(drgui_find_top_level_element(pElement)), &rect, FALSE); +// } +// +// ... +// +// LRESULT CALLBACK MyWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { +// ... +// drgui_element* pTopLevelElement = (drgui_element*)GetWindowLongPtr(hWnd, 0); +// if (pTopLevelElement != NULL) { +// switch (msg) { +// ... +// case WM_PAINT: +// { +// RECT rect; +// if (GetUpdateRect(hWnd, &rect, FALSE)) { +// drgui_draw(pTopLevelElement, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); +// } +// +// break; +// } +// ... +// } +// } +// ... +// } +// + + + +#ifndef dr_gui_h +#define dr_gui_h + +#ifndef DRGUI_NO_DR_2D +// If you're using easy_draw integration, set the path below to the relative location of dr_2d. By default, the +// following structure is assumed: +// +// - dr_libs +// - dr_2d.h +// - dr_gui.h +#include "dr_2d.h" +#endif + +#include + +#ifndef DRGUI_MAX_FONT_FAMILY_LENGTH +#define DRGUI_MAX_FONT_FAMILY_LENGTH 128 +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct drgui_context drgui_context; +typedef struct drgui_element drgui_element; +typedef struct drgui_color drgui_color; +typedef struct drgui_rect drgui_rect; +typedef struct drgui_painting_callbacks drgui_painting_callbacks; +typedef struct drgui_font drgui_font; +typedef struct drgui_image drgui_image; +typedef struct drgui_font_metrics drgui_font_metrics; +typedef struct drgui_glyph_metrics drgui_glyph_metrics; + +typedef unsigned char drgui_byte; +typedef unsigned int drgui_key; + +typedef void* drgui_resource; + +/// Common system cursors. +typedef enum +{ + drgui_cursor_none, + drgui_cursor_default, + drgui_cursor_arrow = drgui_cursor_default, + drgui_cursor_text, + drgui_cursor_cross, + drgui_cursor_size_ns, // North/South resize arrows. + drgui_cursor_size_we, // West/East resize arrows. + drgui_cursor_size_nesw, // North/East, South/West resize arrows. + drgui_cursor_size_nwse // North/West, South/East resize arrows. +} drgui_cursor_type; + +/// Font weights. +typedef enum +{ + drgui_font_weight_medium, + drgui_font_weight_thin, + drgui_font_weight_extra_light, + drgui_font_weight_light, + drgui_font_weight_semi_light, + drgui_font_weight_book, + drgui_font_weight_semi_bold, + drgui_font_weight_bold, + drgui_font_weight_extra_bold, + drgui_font_weight_heavy, + drgui_font_weight_extra_heavy, + + drgui_font_weight_normal = drgui_font_weight_medium, + drgui_font_weight_default = drgui_font_weight_medium + +} drgui_font_weight; + +/// Font slants. +typedef enum +{ + drgui_font_slant_none, + drgui_font_slant_italic, + drgui_font_slant_oblique + +} drgui_font_slant; + +/// Image formats. +typedef enum +{ + drgui_image_format_rgba8, + drgui_image_format_bgra8, + drgui_image_format_argb8, +} drgui_image_format; + + +/// Font metrics. +struct drgui_font_metrics +{ + int ascent; + int descent; + int lineHeight; + int spaceWidth; +}; + +/// Glyph metrics. +struct drgui_glyph_metrics +{ + int width; + int height; + int originX; + int originY; + int advanceX; + int advanceY; +}; + + +/// Structure representing an RGBA color. Color components are specified in the range of 0 - 255. +struct drgui_color +{ + drgui_byte r; + drgui_byte g; + drgui_byte b; + drgui_byte a; +}; + +/// Structure representing a rectangle. +struct drgui_rect +{ + float left; + float top; + float right; + float bottom; +}; + + +#define DRGUI_IMAGE_DRAW_BACKGROUND (1 << 0) +#define DRGUI_IMAGE_HINT_NO_ALPHA (1 << 1) +#define DRGUI_IMAGE_DRAW_BOUNDS (1 << 2) +#define DRGUI_IMAGE_CLIP_BOUNDS (1 << 3) //< Clips the image to the bounds +#define DRGUI_IMAGE_ALIGN_CENTER (1 << 4) + +#define DRGUI_READ (1 << 0) +#define DRGUI_WRITE (1 << 1) + +#define DRGUI_FONT_NO_CLEARTYPE (1 << 0) + +typedef struct +{ + /// The destination position on the x axis. This is ignored if the DR2D_IMAGE_ALIGN_CENTER option is set. + float dstX; + + /// The destination position on the y axis. This is ignored if the DR2D_IMAGE_ALIGN_CENTER option is set. + float dstY; + + /// The destination width. + float dstWidth; + + /// The destination height. + float dstHeight; + + + /// The source offset on the x axis. + float srcX; + + /// The source offset on the y axis. + float srcY; + + /// The source width. + float srcWidth; + + /// The source height. + float srcHeight; + + + /// The position of the destination's bounds on the x axis. + float dstBoundsX; + + /// The position of the destination's bounds on the y axis. + float dstBoundsY; + + /// The width of the destination's bounds. + float dstBoundsWidth; + + /// The height of the destination's bounds. + float dstBoundsHeight; + + + /// The foreground tint color. This is not applied to the background color, and the alpha component is ignored. + drgui_color foregroundTint; + + /// The background color. Only used if the DR2D_IMAGE_DRAW_BACKGROUND option is set. + drgui_color backgroundColor; + + /// The bounds color. This color is used for the region of the bounds that sit on the outside of the destination rectangle. This will + /// usually be set to the same value as backgroundColor, but it could also be used to draw a border around the image. + drgui_color boundsColor; + + + /// Flags for controlling how the image should be drawn. + unsigned int options; + +} drgui_draw_image_args; + + +typedef void (* drgui_callback)(); + +typedef void (* drgui_on_move_proc) (drgui_element* pElement, float newRelativePosX, float newRelativePosY); +typedef void (* drgui_on_size_proc) (drgui_element* pElement, float newWidth, float newHeight); +typedef void (* drgui_on_mouse_enter_proc) (drgui_element* pElement); +typedef void (* drgui_on_mouse_leave_proc) (drgui_element* pElement); +typedef void (* drgui_on_mouse_move_proc) (drgui_element* pElement, int relativeMousePosX, int relativeMousePosY, int stateFlags); +typedef void (* drgui_on_mouse_button_down_proc) (drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); +typedef void (* drgui_on_mouse_button_up_proc) (drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); +typedef void (* drgui_on_mouse_button_dblclick_proc)(drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); +typedef void (* drgui_on_mouse_wheel_proc) (drgui_element* pElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags); +typedef void (* drgui_on_key_down_proc) (drgui_element* pElement, drgui_key key, int stateFlags); +typedef void (* drgui_on_key_up_proc) (drgui_element* pElement, drgui_key key, int stateFlags); +typedef void (* drgui_on_printable_key_down_proc) (drgui_element* pElement, unsigned int character, int stateFlags); +typedef void (* drgui_on_paint_proc) (drgui_element* pElement, drgui_rect relativeRect, void* pPaintData); +typedef void (* drgui_on_dirty_proc) (drgui_element* pElement, drgui_rect relativeRect); +typedef bool (* drgui_on_hittest_proc) (drgui_element* pElement, float relativePosX, float relativePosY); +typedef void (* drgui_on_capture_mouse_proc) (drgui_element* pElement); +typedef void (* drgui_on_release_mouse_proc) (drgui_element* pElement); +typedef void (* drgui_on_capture_keyboard_proc) (drgui_element* pElement, drgui_element* pPrevCapturedElement); +typedef void (* drgui_on_release_keyboard_proc) (drgui_element* pElement, drgui_element* pNewCapturedElement); +typedef void (* drgui_on_change_cursor_proc) (drgui_element* pElement, drgui_cursor_type cursor); +typedef void (* drgui_on_delete_element_proc) (drgui_element* pElement); +typedef void (* drgui_on_log) (drgui_context* pContext, const char* message); + +typedef void (* drgui_draw_begin_proc) (void* pPaintData); +typedef void (* drgui_draw_end_proc) (void* pPaintData); +typedef void (* drgui_set_clip_proc) (drgui_rect relativeRect, void* pPaintData); +typedef void (* drgui_get_clip_proc) (drgui_rect* pRectOut, void* pPaintData); +typedef void (* drgui_draw_line_proc) (float startX, float startY, float endX, float endY, float width, drgui_color color, void* pPaintData); +typedef void (* drgui_draw_rect_proc) (drgui_rect relativeRect, drgui_color color, void* pPaintData); +typedef void (* drgui_draw_rect_outline_proc) (drgui_rect relativeRect, drgui_color color, float outlineWidth, void* pPaintData); +typedef void (* drgui_draw_rect_with_outline_proc) (drgui_rect relativeRect, drgui_color color, float outlineWidth, drgui_color outlineColor, void* pPaintData); +typedef void (* drgui_draw_round_rect_proc) (drgui_rect relativeRect, drgui_color color, float radius, void* pPaintData); +typedef void (* drgui_draw_round_rect_outline_proc) (drgui_rect relativeRect, drgui_color color, float radius, float outlineWidth, void* pPaintData); +typedef void (* drgui_draw_round_rect_with_outline_proc) (drgui_rect relativeRect, drgui_color color, float radius, float outlineWidth, drgui_color outlineColor, void* pPaintData); +typedef void (* drgui_draw_text_proc) (drgui_resource font, const char* text, int textLengthInBytes, float posX, float posY, drgui_color color, drgui_color backgroundColor, void* pPaintData); +typedef void (* drgui_draw_image_proc) (drgui_resource image, drgui_draw_image_args* pArgs, void* pPaintData); + +typedef drgui_resource (* drgui_create_font_proc) (void* pPaintingContext, const char* family, unsigned int size, drgui_font_weight weight, drgui_font_slant slant, float rotation, unsigned int flags); +typedef void (* drgui_delete_font_proc) (drgui_resource font); +typedef unsigned int (* drgui_get_font_size_proc) (drgui_resource font); +typedef bool (* drgui_get_font_metrics_proc) (drgui_resource font, drgui_font_metrics* pMetricsOut); +typedef bool (* drgui_get_glyph_metrics_proc) (drgui_resource font, unsigned int utf32, drgui_glyph_metrics* pMetricsOut); +typedef bool (* drgui_measure_string_proc) (drgui_resource font, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut); +typedef bool (* drgui_get_text_cursor_position_from_point_proc)(drgui_resource font, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut); +typedef bool (* drgui_get_text_cursor_position_from_char_proc) (drgui_resource font, const char* text, size_t characterIndex, float* pTextCursorPosXOut); + +typedef drgui_resource (* drgui_create_image_proc) (void* pPaintingContext, unsigned int width, unsigned int height, drgui_image_format format, unsigned int stride, const void* pImageData); +typedef void (* drgui_delete_image_proc) (drgui_resource image); +typedef drgui_image_format (* drgui_get_optimal_image_format_proc)(void* pPaintingContext); +typedef void (* drgui_get_image_size_proc) (drgui_resource image, unsigned int* pWidthOut, unsigned int* pHeightOut); +typedef void* (* drgui_map_image_data_proc) (drgui_resource image, unsigned int accessFlags); +typedef void (* drgui_unmap_image_data_proc) (drgui_resource image); + +typedef bool (* drgui_visible_iteration_proc)(drgui_element* pElement, drgui_rect *pRelativeRect, void* pUserData); + + +// Key state flags. +#define DRGUI_MOUSE_BUTTON_LEFT_DOWN (1 << 0) +#define DRGUI_MOUSE_BUTTON_RIGHT_DOWN (1 << 1) +#define DRGUI_MOUSE_BUTTON_MIDDLE_DOWN (1 << 2) +#define DRGUI_MOUSE_BUTTON_4_DOWN (1 << 3) +#define DRGUI_MOUSE_BUTTON_5_DOWN (1 << 4) +#define DRGUI_KEY_STATE_SHIFT_DOWN (1 << 5) // Whether or not a shift key is down at the time the input event is handled. +#define DRGUI_KEY_STATE_CTRL_DOWN (1 << 6) // Whether or not a ctrl key is down at the time the input event is handled. +#define DRGUI_KEY_STATE_ALT_DOWN (1 << 7) // Whether or not an alt key is down at the time the input event is handled. +#define DRGUI_KEY_STATE_AUTO_REPEATED (1 << 31) // Whether or not the key press is generated due to auto-repeating. Only used with key down events. + +// Common mouse buttons. +#define DRGUI_MOUSE_BUTTON_LEFT 1 +#define DRGUI_MOUSE_BUTTON_RIGHT 2 +#define DRGUI_MOUSE_BUTTON_MIDDLE 3 + +// Common key codes. +#define DRGUI_BACKSPACE 0xff08 +#define DRGUI_SHIFT 0xff10 +#define DRGUI_ESCAPE 0xff1b +#define DRGUI_PAGE_UP 0xff55 +#define DRGUI_PAGE_DOWN 0xff56 +#define DRGUI_END 0xff57 +#define DRGUI_HOME 0xff50 +#define DRGUI_ARROW_LEFT 0x8fb +#define DRGUI_ARROW_UP 0x8fc +#define DRGUI_ARROW_RIGHT 0x8fd +#define DRGUI_ARROW_DOWN 0x8fe +#define DRGUI_DELETE 0xffff +#define DRGUI_F1 0xffbe +#define DRGUI_F2 0xffbf +#define DRGUI_F3 0xffc0 +#define DRGUI_F4 0xffc1 +#define DRGUI_F5 0xffc2 +#define DRGUI_F6 0xffc3 +#define DRGUI_F7 0xffc4 +#define DRGUI_F8 0xffc5 +#define DRGUI_F9 0xffc6 +#define DRGUI_F10 0xffc7 +#define DRGUI_F11 0xffc8 +#define DRGUI_F12 0xffc9 + +static size_t drgui_strcpy(char* dst, size_t dstSize, const char* src) +{ + if (strcpy_s(dst, dstSize, src) == 0) { + return strlen(dst); + } + + return 0; +} + +static inline size_t drgui_key_to_string(drgui_key key, char* strOut, size_t strOutSize) +{ + if (strOut == NULL || strOutSize == 0) { + return 0; + } + + if (strOutSize == 1) { + strOut[0] = '\0'; + return 0; + } + + + switch (key) + { + case DRGUI_BACKSPACE: return drgui_strcpy(strOut, strOutSize, "Backspace"); + case DRGUI_SHIFT: return drgui_strcpy(strOut, strOutSize, "Shift"); + case DRGUI_ESCAPE: return drgui_strcpy(strOut, strOutSize, "Escape"); + case DRGUI_PAGE_UP: return drgui_strcpy(strOut, strOutSize, "Page Up"); + case DRGUI_PAGE_DOWN: return drgui_strcpy(strOut, strOutSize, "Page Down"); + case DRGUI_END: return drgui_strcpy(strOut, strOutSize, "End"); + case DRGUI_HOME: return drgui_strcpy(strOut, strOutSize, "Home"); + case DRGUI_ARROW_LEFT: return drgui_strcpy(strOut, strOutSize, "Arrow Left"); + case DRGUI_ARROW_UP: return drgui_strcpy(strOut, strOutSize, "Arrow Up"); + case DRGUI_ARROW_RIGHT: return drgui_strcpy(strOut, strOutSize, "Arrow Right"); + case DRGUI_ARROW_DOWN: return drgui_strcpy(strOut, strOutSize, "Arrow Down"); + case DRGUI_DELETE: return drgui_strcpy(strOut, strOutSize, "Delete"); + case DRGUI_F1: return drgui_strcpy(strOut, strOutSize, "F1"); + case DRGUI_F2: return drgui_strcpy(strOut, strOutSize, "F2"); + case DRGUI_F3: return drgui_strcpy(strOut, strOutSize, "F3"); + case DRGUI_F4: return drgui_strcpy(strOut, strOutSize, "F4"); + case DRGUI_F5: return drgui_strcpy(strOut, strOutSize, "F5"); + case DRGUI_F6: return drgui_strcpy(strOut, strOutSize, "F6"); + case DRGUI_F7: return drgui_strcpy(strOut, strOutSize, "F7"); + case DRGUI_F8: return drgui_strcpy(strOut, strOutSize, "F8"); + case DRGUI_F9: return drgui_strcpy(strOut, strOutSize, "F9"); + case DRGUI_F10: return drgui_strcpy(strOut, strOutSize, "F10"); + case DRGUI_F11: return drgui_strcpy(strOut, strOutSize, "F11"); + case DRGUI_F12: return drgui_strcpy(strOut, strOutSize, "F12"); + } + + if (key >= 32 && key <= 126) { + strOut[0] = (char)key; + strOut[1] = '\0'; + return 1; + } + + // TODO: Non-ascii characters. + return 0; +} + +static inline drgui_key drgui_key_parse(const char* str) +{ + if (str == NULL || str[0] == '\0') { + return 0; + } + + if (_stricmp(str, "backspace") == 0) return DRGUI_BACKSPACE; + if (_stricmp(str, "shift") == 0) return DRGUI_SHIFT; + if (_stricmp(str, "escape") == 0) return DRGUI_ESCAPE; + if (_stricmp(str, "page up") == 0 || _stricmp(str, "pageup") == 0) return DRGUI_PAGE_UP; + if (_stricmp(str, "page down") == 0 || _stricmp(str, "pagedown") == 0) return DRGUI_PAGE_DOWN; + if (_stricmp(str, "end") == 0) return DRGUI_END; + if (_stricmp(str, "home") == 0) return DRGUI_HOME; + if (_stricmp(str, "arrow left") == 0 || _stricmp(str, "arrowleft") == 0) return DRGUI_ARROW_LEFT; + if (_stricmp(str, "arrow up") == 0 || _stricmp(str, "arrowup") == 0) return DRGUI_ARROW_UP; + if (_stricmp(str, "arrow right") == 0 || _stricmp(str, "arrowright") == 0) return DRGUI_ARROW_RIGHT; + if (_stricmp(str, "arrow down") == 0 || _stricmp(str, "arrowdown") == 0) return DRGUI_ARROW_DOWN; + if (_stricmp(str, "delete") == 0) return DRGUI_BACKSPACE; + + if (str[0] == 'F' || str[0] == 'f') { + if (str[1] == '1') { + if (str[2] == '\0') { + return DRGUI_F1; + } else { + if (str[2] == '0' && str[2] == '\0') return DRGUI_F10; + if (str[2] == '1' && str[2] == '\0') return DRGUI_F11; + if (str[2] == '2' && str[2] == '\0') return DRGUI_F12; + } + } + if (str[1] == '2' && str[2] == '\0') return DRGUI_F2; + if (str[1] == '3' && str[2] == '\0') return DRGUI_F3; + if (str[1] == '4' && str[2] == '\0') return DRGUI_F4; + if (str[1] == '5' && str[2] == '\0') return DRGUI_F5; + if (str[1] == '6' && str[2] == '\0') return DRGUI_F6; + if (str[1] == '7' && str[2] == '\0') return DRGUI_F7; + if (str[1] == '8' && str[2] == '\0') return DRGUI_F8; + if (str[1] == '9' && str[2] == '\0') return DRGUI_F9; + } + + + // ASCII characters. + if (str[0] >= 32 && str[0] <= 126 && str[1] == '\0') { + return str[0]; + } + + if (_stricmp(str, "tab") == 0) { + return '\t'; + } + + + // TODO: Non-ascii characters. + return 0; +} + + +/// Structure containing callbacks for painting routines. +struct drgui_painting_callbacks +{ + drgui_draw_begin_proc drawBegin; + drgui_draw_end_proc drawEnd; + + drgui_set_clip_proc setClip; + drgui_get_clip_proc getClip; + + drgui_draw_line_proc drawLine; + drgui_draw_rect_proc drawRect; + drgui_draw_rect_outline_proc drawRectOutline; + drgui_draw_rect_with_outline_proc drawRectWithOutline; + drgui_draw_round_rect_proc drawRoundRect; + drgui_draw_round_rect_outline_proc drawRoundRectOutline; + drgui_draw_round_rect_with_outline_proc drawRoundRectWithOutline; + drgui_draw_text_proc drawText; + drgui_draw_image_proc drawImage; + + drgui_create_font_proc createFont; + drgui_delete_font_proc deleteFont; + drgui_get_font_size_proc getFontSize; + drgui_get_font_metrics_proc getFontMetrics; + drgui_get_glyph_metrics_proc getGlyphMetrics; + drgui_measure_string_proc measureString; + drgui_get_text_cursor_position_from_point_proc getTextCursorPositionFromPoint; + drgui_get_text_cursor_position_from_char_proc getTextCursorPositionFromChar; + + drgui_create_image_proc createImage; + drgui_delete_image_proc deleteImage; + drgui_get_optimal_image_format_proc getOptimalImageFormat; + drgui_get_image_size_proc getImageSize; + drgui_map_image_data_proc mapImageData; + drgui_unmap_image_data_proc unmapImageData; +}; + +struct drgui_image +{ + /// A pointer to the context that owns this image. + drgui_context* pContext; + + /// The resource handle that is passed around to the callback functions. + drgui_resource hResource; +}; + +struct drgui_font +{ + /// A pointer to the context that owns this font. + drgui_context* pContext; + + /// The font family. + char family[DRGUI_MAX_FONT_FAMILY_LENGTH]; + + /// The base size of the font. This is set to the value that was used to create the font in the first place. + unsigned int size; + + /// The font's weight. + drgui_font_weight weight; + + /// The fon't slant. + drgui_font_slant slant; + + /// The fon't rotation. + float rotation; + + /// The font's flags. Can be a combination of the following: + /// DRGUI_FONT_NO_CLEARTYPE + unsigned int flags; + + /// The internal font. This is created by the rendering backend. + drgui_resource internalFont; +}; + + +struct drgui_element +{ + /// A pointer to the context that owns this element. This should never be null for valid elements. + drgui_context* pContext; + + + /// A pointer to the parent element. This can be null in which case this element is the parent. + drgui_element* pParent; + + /// A pointer to the first child element. + drgui_element* pFirstChild; + + /// A pointer to the last child element. + drgui_element* pLastChild; + + /// A pointer to the next sibling element. + drgui_element* pNextSibling; + + /// A pointer ot the previous sibing element. + drgui_element* pPrevSibling; + + + /// A pointer to the next dead element. When an element is deleted during an event handler it is not deleted straight away but + /// rather at the end of the current batch of event processing. Dead elements are stored in a linked list, with this pointer + /// acting as the link between items. This will be null if the element is the last in the list, or is not marked as dead. Note + /// that this should not be used to check if the element is marked as dead - use the IS_ELEMENT_DEAD flag instead. + drgui_element* pNextDeadElement; + + + /// The type of the element, as a string. This is only every used by the host application, and is intended to be used as way + /// to selectively perform certain operations on specific types of GUI elements. + char type[64]; + + + /// The absolute position of the element on the x axis. A position of 0 is the left side of the surface it is attached to. + float absolutePosX; + + /// The absolute position of the element on the y axis. A position of 0 is the top of the surface it is attached to. + float absolutePosY; + + /// The width of the element. + float width; + + /// The height of the element. + float height; + + + /// The cursor. Defaults to drge_cursor_default. + drgui_cursor_type cursor; + + /// Boolean flags. + unsigned int flags; + + // The region of the element that's dirty. + drgui_rect dirtyRect; + + + /// The function to call when the element's relative position moves. + drgui_on_move_proc onMove; + + /// The function to call when the element's size changes. + drgui_on_size_proc onSize; + + /// The function to call when the mouse enters the given element. + drgui_on_mouse_enter_proc onMouseEnter; + + /// The function to call when the mouse leaves the given element. + drgui_on_mouse_leave_proc onMouseLeave; + + /// The function to call when the mouse is moved while over the element. + drgui_on_mouse_move_proc onMouseMove; + + /// The function to call when a mouse buttonis pressed while over the element. + drgui_on_mouse_button_down_proc onMouseButtonDown; + + /// The function to call when a mouse button is released while over the element. + drgui_on_mouse_button_up_proc onMouseButtonUp; + + /// The function to call when a mouse button is double-clicked while over the element. + drgui_on_mouse_button_dblclick_proc onMouseButtonDblClick; + + /// The function to call when the mouse wheel it turned while over the element. + drgui_on_mouse_wheel_proc onMouseWheel; + + /// The function to call when a key on the keyboard is pressed or auto-repeated. + drgui_on_key_down_proc onKeyDown; + + /// The function to call when a key on the keyboard is released. + drgui_on_key_up_proc onKeyUp; + + /// The function to call when a printable character is pressed or auto-repeated. This would be used for text editing. + drgui_on_printable_key_down_proc onPrintableKeyDown; + + /// The function to call when the paint event is received. + drgui_on_paint_proc onPaint; + + /// The function to call when the element is marked as dirty. + drgui_on_dirty_proc onDirty; + + /// The function to call when a hit test needs to be performed. + drgui_on_hittest_proc onHitTest; + + /// The event handler to call when an element receives the mouse focus. + drgui_on_capture_mouse_proc onCaptureMouse; + + /// The event handler to call when an element loses the mouse focus. + drgui_on_release_mouse_proc onReleaseMouse; + + /// The event handler to call when an element receives the keyboard focus. + drgui_on_capture_keyboard_proc onCaptureKeyboard; + + /// The event handler to call when an element loses the keyboard focus. + drgui_on_release_keyboard_proc onReleaseKeyboard; + + + /// The size of the extra data. + size_t extraDataSize; + + /// A pointer to the extra data. + drgui_byte pExtraData[1]; +}; + +struct drgui_context +{ + /// The paiting context. + void* pPaintingContext; + + /// The painting callbacks. + drgui_painting_callbacks paintingCallbacks; + + + /// The inbound event counter. This is incremented with drgui_begin_inbound_event() and decremented with + /// drgui_end_inbound_event(). We use this to determine whether or not an inbound event is being processed. + int inboundEventCounter; + + /// The outbound event counter that we use as the "lock" for outbound events. All outbound events are posted from + /// inbound events, and all inbound events are already synchronized so we don't need to use a mutex. This is mainly + /// used as a way to check for erroneous outbound event generation. + int outboundEventLockCounter; + + /// A pointer to the first element that has been marked as dead. Elements marked as dead are stored as a linked list. + drgui_element* pFirstDeadElement; + + /// A pointer to the element that is sitting directly under the mouse. This is updated on every inbound mouse move event + /// and is used for determining when a mouse enter/leave event needs to be posted. + drgui_element* pElementUnderMouse; + + /// A pointer to the element with the mouse capture. + drgui_element* pElementWithMouseCapture; + + /// A pointer to the element with the keyboard focus. + drgui_element* pElementWithKeyboardCapture; + + /// A pointer to the element that wants the keyboard focus. If for some reason an element isn't able to immediately + /// capture the keyboard (such as while in the middle of a release_keyboard event handler) this will be set to that + /// particular element. This will then be used to capture the keyboard at a later time when it is able. + drgui_element* pElementWantingKeyboardCapture; + + /// The current cursor. + drgui_cursor_type currentCursor; + + /// Boolean flags. + unsigned int flags; + + + /// The global event callback to call when an element is marked as dirty. + drgui_on_dirty_proc onGlobalDirty; + + /// The global event handler to call when an element captures the mouse. + drgui_on_capture_mouse_proc onGlobalCaptureMouse; + + /// The global event handler to call when an element releases the mouse. + drgui_on_release_mouse_proc onGlobalReleaseMouse; + + /// The global event handler to call when an element captures the keyboard. + drgui_on_capture_keyboard_proc onGlobalCaptureKeyboard; + + /// The global event handler to call when an element releases the keyboard. + drgui_on_release_keyboard_proc onGlobalReleaseKeyboard; + + /// The global event handler to call when the system cursor needs to change. + drgui_on_change_cursor_proc onChangeCursor; + + /// The function to call when an element is deleted. + drgui_on_delete_element_proc onDeleteElement; + + + /// The function to call when a log message is posted. + drgui_on_log onLog; + + + + /// A pointer to the top level element that was passed in from the last inbound mouse move event. + drgui_element* pLastMouseMoveTopLevelElement; + + /// The position of the mouse that was passed in from the last inbound mouse move event. + float lastMouseMovePosX; + float lastMouseMovePosY; + + + // A pointer to the list of dirty elements. + drgui_element** ppDirtyElements; + + // The size of the buffer containing the dirty elements. + size_t dirtyElementBufferSize; + + // The number of dirty top-level elements. + size_t dirtyElementCount; + + /// The counter to use when determining whether or not an on_dirty event needs to be posted. This is incremented with + /// drgui_begin_auto_dirty() and decremented with drgui_end_auto_dirty(). When the counter is decremented and hits + /// zero, the on_dirty event will be posted. + unsigned int dirtyCounter; +}; + + + +///////////////////////////////////////////////////////////////// +// +// CORE API +// +///////////////////////////////////////////////////////////////// + +/// Creates a context. +drgui_context* drgui_create_context(); + +/// Deletes a context and everything that it created. +void drgui_delete_context(drgui_context* pContext); + + + +///////////////////////////////////////////////////////////////// +// Events + +/// Posts a mouse leave inbound event. +/// +/// @remarks +/// The intention behind this event is to allow the application to let dr_gui know that the mouse have left the window. Since dr_gui does +/// not have any notion of a window it must rely on the host application to notify it. +void drgui_post_inbound_event_mouse_leave(drgui_element* pTopLevelElement); + +/// Posts a mouse move inbound event. +void drgui_post_inbound_event_mouse_move(drgui_element* pTopLevelElement, int mousePosX, int mousePosY, int stateFlags); + +/// Posts a mouse button down inbound event. +void drgui_post_inbound_event_mouse_button_down(drgui_element* pTopLevelElement, int mouseButton, int mousePosX, int mousePosY, int stateFlags); + +/// Posts a mouse button up inbound event. +void drgui_post_inbound_event_mouse_button_up(drgui_element* pTopLevelElement, int mouseButton, int mousePosX, int mousePosY, int stateFlags); + +/// Posts a mouse button double-clicked inbound event. +void drgui_post_inbound_event_mouse_button_dblclick(drgui_element* pTopLevelElement, int mouseButton, int mousePosX, int mousePosY, int stateFlags); + +/// Posts a mouse wheel inbound event. +void drgui_post_inbound_event_mouse_wheel(drgui_element* pTopLevelElement, int mouseButton, int mousePosX, int mousePosY, int stateFlags); + +/// Posts a key down inbound event. +void drgui_post_inbound_event_key_down(drgui_context* pContext, drgui_key key, int stateFlags); + +/// Posts a key up inbound event. +void drgui_post_inbound_event_key_up(drgui_context* pContext, drgui_key key, int stateFlags); + +/// Posts a printable key down inbound event. +/// +/// @remarks +/// The \c character argument should be a UTF-32 code point. +void drgui_post_inbound_event_printable_key_down(drgui_context* pContext, unsigned int character, int stateFlags); + + +/// Registers the global on_dirty event callback. +/// +/// @remarks +/// This is called whenever a region of an element is marked as dirty and allows an application to mark the region of the +/// container window as dirty to trigger an operating system level repaint of the window. +void drgui_set_global_on_dirty(drgui_context* pContext, drgui_on_dirty_proc onDirty); + +/// Registers the global on_capture_mouse event callback. +/// +/// @remarks +/// This is called whenever an element receives an the mouse capture and allows an application to do operating system level +/// mouse captures against the container window or whatnot. +/// @par +/// The advantage of using a global event callback is that it can be set once at the context level rather than many times +/// at the element level. +void drgui_set_global_on_capture_mouse(drgui_context* pContext, drgui_on_capture_mouse_proc onCaptureMouse); + +/// Registers the global on_release_mouse event callback. +/// +/// @remarks +/// This is called whenever an element loses an the mouse capture and allows an application to do operating system level +/// mouse releases against the container window or whatnot. +/// @par +/// The advantage of using a global event callback is that it can be set once at the context level rather than many times +/// at the element level. +void drgui_set_global_on_release_mouse(drgui_context* pContext, drgui_on_release_mouse_proc onReleaseMouse); + +/// Registers the global on_capture_keyboard event callback. +/// +/// @remarks +/// This is called whenever an element receives an the keyboard capture and allows an application to do an operating system level +/// keyboard focus against the container window or whatnot. +/// @par +/// The advantage of using a global event callback is that it can be set once at the context level rather than many times +/// at the element level. +void drgui_set_global_on_capture_keyboard(drgui_context* pContext, drgui_on_capture_keyboard_proc onCaptureKeyboard); + +/// Registers the global on_release_keyboard event callback. +/// +/// @remarks +/// This is called whenever an element loses an the keyboard capture and allows an application to do an operating system level +/// keyboard release against the container window or whatnot. +/// @par +/// The advantage of using a global event callback is that it can be set once at the context level rather than many times +/// at the element level. +void drgui_set_global_on_release_keyboard(drgui_context* pContext, drgui_on_capture_keyboard_proc onReleaseKeyboard); + +/// Sets the global on_change_cursor event callback. +/// +/// @remarks +/// This is called whenever the operating system needs to change the cursor. +void drgui_set_global_on_change_cursor(drgui_context* pContext, drgui_on_change_cursor_proc onChangeCursor); + +/// Sets the function to call when an element is deleted. +void drgui_set_on_delete_element(drgui_context* pContext, drgui_on_delete_element_proc onDeleteElement); + + +/// Registers the callback to call when a log message is posted. +void drgui_set_on_log(drgui_context* pContext, drgui_on_log onLog); + + + + +///////////////////////////////////////////////////////////////// +// Elements + +/// Creates an element. +drgui_element* drgui_create_element(drgui_context* pContext, drgui_element* pParent, size_t extraDataSize, const void* pExtraData); + +/// Deletes and element. +void drgui_delete_element(drgui_element* pElement); + + +/// Retrieves the size of the extra data of the given element, in bytes. +size_t drgui_get_extra_data_size(drgui_element* pElement); + +/// Retrieves a pointer to the extra data of the given element. +void* drgui_get_extra_data(drgui_element* pElement); + + +/// Sets the type of the element. +/// +/// The type name cannot be more than 63 characters in length. +bool drgui_set_type(drgui_element* pElement, const char* type); + +/// Retrieves the type fo the element. +const char* drgui_get_type(drgui_element* pElement); + +/// Determines whether or not the given element is of the given type. +bool drgui_is_of_type(drgui_element* pElement, const char* type); + + +/// Hides the given element. +void drgui_hide(drgui_element *pElement); + +/// Shows the given element. +void drgui_show(drgui_element* pElement); + +/// Determines whether or not the element is marked as visible. +/// +/// @remarks +/// This is a direct accessor for the internal visible flag of the element and is not recursive. Thus, if this element is +/// marked as visible, but it's parent is invisible, it will still return true. Use drgui_is_visible_recursive() to do +/// a recursive visibility check. +bool drgui_is_visible(const drgui_element* pElement); + +/// Recursively determines whether or not the element is marked as visible. +bool drgui_is_visible_recursive(const drgui_element* pElement); + + +/// Disables clipping against the parent for the given element. +void drgui_disable_clipping(drgui_element* pElement); + +/// Enables clipping against the parent for the given element. +void drgui_enable_clipping(drgui_element* pElement); + +/// Determines whether or not clipping is enabled for the given element. +bool drgui_is_clipping_enabled(const drgui_element* pElement); + + +/// Sets the element that should receive all future mouse related events. +/// +/// @remarks +/// Release the mouse capture with drgui_release_mosue(). +void drgui_capture_mouse(drgui_element* pElement); + +/// Releases the mouse capture. +void drgui_release_mouse(drgui_context* pContext); + +/// Releases the mouse capture without posting the global-scoped event. Should only be used in very specific cases, usually in combination with awkward interop with the window system. +void drgui_release_mouse_no_global_notify(drgui_context* pContext); + +/// Retrieves a pointer to the element with the mouse capture. +drgui_element* drgui_get_element_with_mouse_capture(drgui_context* pContext); + +/// Determines whether or not the given element has the mouse capture. +bool drgui_has_mouse_capture(drgui_element* pElement); + + +/// Sets the element that should receive all future keyboard related events. +/// +/// @remarks +/// Releases the keyboard capture with drgui_release_keyboard(). +void drgui_capture_keyboard(drgui_element* pElement); + +/// Releases the keyboard capture. +void drgui_release_keyboard(drgui_context* pContext); + +/// Releases the keyboard capture without posting the global-scoped event. Should only be used in very specific cases, usually in combination with awkward interop with the window system. +void drgui_release_keyboard_no_global_notify(drgui_context* pContext); + +/// Retrieves a pointer to the element with the keyboard capture. +drgui_element* drgui_get_element_with_keyboard_capture(drgui_context* pContext); + +/// Determines whether or not the given element has the keyboard capture. +bool drgui_has_keyboard_capture(drgui_element* pElement); + + +/// Sets the cursor to use when the mouse enters the given GUI element. +void drgui_set_cursor(drgui_element* pElement, drgui_cursor_type cursor); + +/// Retrieves the cursor to use when the mouse enters the given GUI element. +drgui_cursor_type drgui_get_cursor(drgui_element* pElement); + + +//// Events //// + +/// Registers the on_move event callback. +void drgui_set_on_move(drgui_element* pElement, drgui_on_move_proc callback); + +/// Registers the on_size event callback. +void drgui_set_on_size(drgui_element* pElement, drgui_on_size_proc callback); + +/// Registers the on_mouse_enter event callback. +void drgui_set_on_mouse_enter(drgui_element* pElement, drgui_on_mouse_enter_proc callback); + +/// Registers the on_mouse_leave event callback. +void drgui_set_on_mouse_leave(drgui_element* pElement, drgui_on_mouse_leave_proc callback); + +/// Registers the on_mouse_move event callback. +void drgui_set_on_mouse_move(drgui_element* pElement, drgui_on_mouse_move_proc callback); + +/// Registers the on_mouse_button_down event callback. +void drgui_set_on_mouse_button_down(drgui_element* pElement, drgui_on_mouse_button_down_proc callback); + +/// Registers the on_mouse_button_up event callback. +void drgui_set_on_mouse_button_up(drgui_element* pElement, drgui_on_mouse_button_up_proc callback); + +/// Registers the on_mouse_button_down event callback. +void drgui_set_on_mouse_button_dblclick(drgui_element* pElement, drgui_on_mouse_button_dblclick_proc callback); + +/// Registers the on_mouse_wheel event callback. +void drgui_set_on_mouse_wheel(drgui_element* pElement, drgui_on_mouse_wheel_proc callback); + +/// Registers the on_key_down event callback. +void drgui_set_on_key_down(drgui_element* pElement, drgui_on_key_down_proc callback); + +/// Registers the on_key_up event callback. +void drgui_set_on_key_up(drgui_element* pElement, drgui_on_key_up_proc callback); + +/// Registers the on_printable_key_down event callback. +void drgui_set_on_printable_key_down(drgui_element* pElement, drgui_on_printable_key_down_proc callback); + +/// Registers the on_paint event callback. +void drgui_set_on_paint(drgui_element* pElement, drgui_on_paint_proc callback); + +/// Registers the on_dirty event callback. +void drgui_set_on_dirty(drgui_element* pElement, drgui_on_dirty_proc callback); + +/// Registers the on_hittest event callback. +void drgui_set_on_hittest(drgui_element* pElement, drgui_on_hittest_proc callback); + +/// Registers the on_capture_mouse event callback. +void drgui_set_on_capture_mouse(drgui_element* pElement, drgui_on_capture_mouse_proc callback); + +/// Registers the on_release_mouse event callback. +void drgui_set_on_release_mouse(drgui_element* pElement, drgui_on_release_mouse_proc callback); + +/// Registers the on_capture_keyboard event callback. +void drgui_set_on_capture_keyboard(drgui_element* pElement, drgui_on_capture_keyboard_proc callback); + +/// Registers the on_release_keyboard event callback. +void drgui_set_on_release_keyboard(drgui_element* pElement, drgui_on_release_keyboard_proc callback); + + + +//// Containment and Hit Detection //// + +/// Determines whether or not the given point is inside the bounds of the given element. +/// +/// @remarks +/// This only checks if the point is inside the bounds of the element and does not take hit testing into account. This difference +/// with this one and drgui_is_point_inside_element() is that the latter will use hit testing. +bool drgui_is_point_inside_element_bounds(const drgui_element* pElement, float absolutePosX, float absolutePosY); + +/// Determines whether or not the given point is inside the given element. +/// +/// @remarks +/// This will use hit testing to determine whether or not the point is inside the element. +bool drgui_is_point_inside_element(drgui_element* pElement, float absolutePosX, float absolutePosY); + +/// Finds the element under the given point taking mouse pass-through and hit testing into account. +drgui_element* drgui_find_element_under_point(drgui_element* pTopLevelElement, float absolutePosX, float absolutePosY); + +/// Determines whether or not the given element is currently sitting directly under the mouse. +bool drgui_is_element_under_mouse(drgui_element* pTopLevelElement); + + + +//// Hierarchy //// + +// Retrieves the parent of the given element. +drgui_element* drgui_get_parent(drgui_element* pChildElement); + +/// Detaches the given element from it's parent. +void drgui_detach(drgui_element* pChildElement); + +/// Attaches the given element as a child of the given parent element, and appends it to the end of the children list. +void drgui_append(drgui_element* pChildElement, drgui_element* pParentElement); + +/// Attaches the given element as a child of the given parent element, and prepends it to the end of the children list. +void drgui_prepend(drgui_element* pChildElement, drgui_element* pParentElement); + +/// Appends the given element to the given sibling. +void drgui_append_sibling(drgui_element* pElementToAppend, drgui_element* pElementToAppendTo); + +/// Prepends the given element to the given sibling. +void drgui_prepend_sibling(drgui_element* pElementToPrepend, drgui_element* pElementToPrependTo); + +/// Retrieves a pointer to the given element's top-level ancestor. +/// +/// @remarks +/// If pElement is the top level element, the return value will be pElement. +drgui_element* drgui_find_top_level_element(drgui_element* pElement); + +/// Determines whether or not the given element is the parent of the other. +/// +/// @remarks +/// This is not recursive. Use drgui_is_ancestor() to do a recursive traversal. +bool drgui_is_parent(drgui_element* pParentElement, drgui_element* pChildElement); + +/// Determines whether or not the given element is a child of the other. +/// +/// @remarks +/// This is not recursive. Use drgui_is_descendant() to do a recursive traversal. +bool drgui_is_child(drgui_element* pChildElement, drgui_element* pParentElement); + +/// Determines whether or not the given element is an ancestor of the other. +bool drgui_is_ancestor(drgui_element* pAncestorElement, drgui_element* pChildElement); + +/// Determines whether or not the given element is a descendant of the other. +bool drgui_is_descendant(drgui_element* pChildElement, drgui_element* pAncestorElement); + +/// Determines whether or not the given element is itself or a descendant. +bool drgui_is_self_or_ancestor(drgui_element* pAncestorElement, drgui_element* pChildElement); + +/// Determines whether or not the given element is itself or a descendant. +bool drgui_is_self_or_descendant(drgui_element* pChildElement, drgui_element* pAncestorElement); + + + +//// Layout //// + +/// Sets the absolute position of the given element. +void drgui_set_absolute_position(drgui_element* pElement, float positionX, float positionY); + +/// Retrieves the absolute position of the given element. +void drgui_get_absolute_position(const drgui_element* pElement, float* positionXOut, float* positionYOut); +float drgui_get_absolute_position_x(const drgui_element* pElement); +float drgui_get_absolute_position_y(const drgui_element* pElement); + + +/// Sets the relative position of the given element. +void drgui_set_relative_position(drgui_element* pElement, float relativePosX, float relativePosY); + +/// Retrieves the relative position of the given element. +void drgui_get_relative_position(const drgui_element* pElement, float* relativePosXOut, float* relativePosYOut); +float drgui_get_relative_position_x(const drgui_element* pElement); +float drgui_get_relative_position_y(const drgui_element* pElement); + + +/// Sets the size of the given element. +void drgui_set_size(drgui_element* pElement, float width, float height); + +/// Retrieves the size of the given element. +void drgui_get_size(const drgui_element* pElement, float* widthOut, float* heightOut); +float drgui_get_width(const drgui_element* pElement); +float drgui_get_height(const drgui_element* pElement); + + + +/// Retrieves the absolute rectangle for the given element. +drgui_rect drgui_get_absolute_rect(const drgui_element* pElement); + +/// Retrieves the relative rectangle for the given element. +drgui_rect drgui_get_relative_rect(const drgui_element* pElement); + +/// Retrieves the local rectangle for the given element. +/// +/// @remarks +/// The local rectangle is equivalent to drgui_make_rect(0, 0, drgui_get_width(pElement), drgui_get_height(pElement)); +drgui_rect drgui_get_local_rect(const drgui_element* pElement); + + + +//// Painting //// + +/// Registers the custom painting callbacks. +/// +/// @remarks +/// This can only be called once, so it should always be done after initialization. This will fail if called +/// more than once. +bool drgui_register_painting_callbacks(drgui_context* pContext, void* pPaintingContext, drgui_painting_callbacks callbacks); + + +/// Performs a recursive traversal of all visible elements in the given rectangle. +/// +/// @param pParentElement [in] A pointer to the element to iterate. +/// +/// @remarks +/// pParentElement will be included in the iteration is it is within the rectangle. +/// @par +/// The rectangle should be relative to pParentElement. +/// @par +/// The iteration callback function takes a pointer to a rectangle structure that represents the visible portion of the +/// element. This pointer can be modified by the callback to create an adjusted rectangle which can be used for clipping. +bool drgui_iterate_visible_elements(drgui_element* pParentElement, drgui_rect relativeRect, drgui_visible_iteration_proc callback, void* pUserData); + + +/// Disable's automatic dirtying of elements. +void drgui_disable_auto_dirty(drgui_context* pContext); + +/// Enable's automatic dirtying of elements. +void drgui_enable_auto_dirty(drgui_context* pContext); + +/// Determines whether or not automatic dirtying is enabled. +bool drgui_is_auto_dirty_enabled(drgui_context* pContext); + + +/// Begins accumulating a dirty rectangle. +/// +/// Returns a pointer to the top level element that was made dirty. +drgui_element* drgui_begin_dirty(drgui_element* pElement); + +/// Ends accumulating a dirty rectangle, and requests a redraw from the backend if the counter reaches zero. +void drgui_end_dirty(drgui_element* pElement); + +/// Marks a region of the given element as dirty. +/// +/// @remarks +/// This will not redraw the element immediately, but instead post a paint event. +void drgui_dirty(drgui_element* pElement, drgui_rect relativeRect); + + +/// Draws the given element. +/// +/// @remarks +/// Do not call this on one element, then again on it's children. Any children that fall inside the specified +/// rectangle will also be redrawn. +/// @par +/// This will call painting event handlers which will give the application time to do custom drawing. +/// @par +/// When using easy_draw to do drawing, pPaintData must be set to a pointer to the relevant easydraw_surface object. +void drgui_draw(drgui_element* pElement, drgui_rect relativeRect, void* pPaintData); + +/// Retrieves the current clipping rectangle. +void drgui_get_clip(drgui_element* pElement, drgui_rect* pRelativeRect, void* pPaintData); + +/// Sets the clipping rectangle to apply to all future draw operations on this element. +void drgui_set_clip(drgui_element* pElement, drgui_rect relativeRect, void* pPaintData); + +/// Draws a rectangle on the given element. +void drgui_draw_rect(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, void* pPaintData); + +/// Draws the outline of a rectangle on the given element. +void drgui_draw_rect_outline(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float outlineWidth, void* pPaintData); + +/// Draws a filled rectangle with an outline on the given element. +void drgui_draw_rect_with_outline(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float outlineWidth, drgui_color outlineColor, void* pPaintData); + +/// Draws a rectangle with rounded corners on the given element. +void drgui_draw_round_rect(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float radius, void* pPaintData); + +/// Draws the outline of a rectangle with rounded corners on the given element. +void drgui_draw_round_rect_outline(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float radius, float outlineWidth, void* pPaintData); + +/// Draws a filled rectangle and it's outline with rounded corners on the given element. +void drgui_draw_round_rect_with_outline(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float radius, float outlineWidth, drgui_color outlineColor, void* pPaintData); + +/// Draws a run of text on the given element. +/// +/// @remarks +/// This does not do any complex formatting like multiple lines and whatnot. Complex formatting can be achieved with multiple +/// calls to this function. +/// @par +/// \c textSizeInBytes can be -1 in which case the text string is treated as null terminated. +void drgui_draw_text(drgui_element* pElement, drgui_font* pFont, const char* text, int textLengthInBytes, float posX, float posY, drgui_color color, drgui_color backgroundColor, void* pPaintData); + +/// Draws an image. +void drgui_draw_image(drgui_element* pElement, drgui_image* pImage, drgui_draw_image_args* pArgs, void* pPaintData); + + +/// Creates a font resource. +drgui_font* drgui_create_font(drgui_context* pContext, const char* family, unsigned int size, drgui_font_weight weight, drgui_font_slant slant, float rotation, unsigned int flags); + +/// Deletes a font resource. +void drgui_delete_font(drgui_font* pFont); + +/// Retrieves the metrics of the given font. +bool drgui_get_font_metrics(drgui_font* pFont, drgui_font_metrics* pMetricsOut); + +/// Retrieves the metrics of the glyph for the given character when rendered with the given font. +bool drgui_get_glyph_metrics(drgui_font* pFont, unsigned int utf32, drgui_glyph_metrics* pMetricsOut); + +/// Retrieves the dimensions of the given string when drawn with the given font. +/// +/// @remarks +/// When the length of the text is 0, the width will be set to 0 and the height will be set to the line height. +bool drgui_measure_string(drgui_font* pFont, const char* text, size_t textLengthInBytes, float* pWidthOut, float* pHeightOut); + +/// Retrieves the position to place a text cursor based on the given point for the given string when drawn with the given font. +bool drgui_get_text_cursor_position_from_point(drgui_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut); + +/// Retrieves the position to palce a text cursor based on the character at the given index for the given string when drawn with the given font. +bool drgui_get_text_cursor_position_from_char(drgui_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut); + + + +/// Creates an image that can be passed to drgui_draw_image(). +/// +/// @remarks +/// The dimensions and format of an image are immutable. If these need to change, then the image needs to be deleted and re-created. +/// @par +/// If pData is NULL, the default image data is undefined. +/// @par +/// If stride is set to 0, it is assumed to be tightly packed. +/// @par +/// Use drgui_map_image_data() and drgui_unmap_image_data() to update or retrieve image data. +drgui_image* drgui_create_image(drgui_context* pContext, unsigned int width, unsigned int height, drgui_image_format format, unsigned int stride, const void* pData); + +/// Deletes the given image. +void drgui_delete_image(drgui_image* pImage); + +/// Retrieves the size of the given image. +void drgui_get_image_size(drgui_image* pImage, unsigned int* pWidthOut, unsigned int* pHeightOut); + +/// Retrieves the optimal image format for the given context. +drgui_image_format drgui_get_optimal_image_format(drgui_context* pContext); + +/// Retrieves a pointer to a buffer representing the given image's data. +/// +/// Call drgui_unmap_image_data() when you are done with this function. +/// +/// Use this function to access an image's data. The returned pointer does not necessarilly point to the image's actual data, so when +/// writing to this pointer, nothing is actually updated until drgui_unmap_image_data() is called. +/// +/// The returned data will contain the image data at the time of the mapping. +void* drgui_map_image_data(drgui_image* pImage, unsigned int accessFlags); + +/// Unmaps the given image data. +void drgui_unmap_image_data(drgui_image* pImage); + + + +///////////////////////////////////////////////////////////////// +// +// HIGH-LEVEL API +// +///////////////////////////////////////////////////////////////// + +//// Hit Testing and Layout //// + +/// An on_size event callback that resizes every child element to that of the parent. +void drgui_on_size_fit_children_to_parent(drgui_element* pElement, float newWidth, float newHeight); + +/// An on_hit_test event callback that can be used to always fail the mouse hit test. +bool drgui_pass_through_hit_test(drgui_element* pElement, float mousePosX, float mousePosY); + + +//// Painting //// + +/// Draws a border around the given element. +void drgui_draw_border(drgui_element* pElement, float borderWidth, drgui_color color, void* pUserData); + + + +///////////////////////////////////////////////////////////////// +// +// UTILITY API +// +///////////////////////////////////////////////////////////////// + +/// Creates a color object from a set of RGBA color components. +drgui_color drgui_rgba(drgui_byte r, drgui_byte g, drgui_byte b, drgui_byte a); + +/// Creates a color object from a set of RGB color components. +drgui_color drgui_rgb(drgui_byte r, drgui_byte g, drgui_byte b); + +/// Clamps the given rectangle to another. +drgui_rect drgui_clamp_rect(drgui_rect rect, drgui_rect other); + +/// Clamps the given rectangle to the given element and returns whether or not any of it is contained within the element's rectangle. +bool drgui_clamp_rect_to_element(const drgui_element* pElement, drgui_rect* pRelativeRect); + +/// Converts the given rectangle from absolute to relative to the given element. +drgui_rect drgui_make_rect_relative(const drgui_element* pElement, drgui_rect* pRect); + +/// Converts the given rectangle from relative to absolute based on the given element. +drgui_rect drgui_make_rect_absolute(const drgui_element* pElement, drgui_rect* pRect); + +/// Converts the given point from absolute to relative to the given element. +void drgui_make_point_relative(const drgui_element* pElement, float* positionX, float* positionY); + +/// Converts the given point from relative to absolute based on the given element. +void drgui_make_point_absolute(const drgui_element* pElement, float* positionX, float* positionY); + +/// Creates a drgui_rect object. +drgui_rect drgui_make_rect(float left, float top, float right, float bottom); + +/// Creates an inside-out rectangle. +/// +/// @remarks +/// An inside our rectangle is a negative-dimension rectangle with each edge at the extreme edges. The left edge will be at the +/// right-most side and the right edge will be at the left-most side. The same applies for the top and bottom edges. +drgui_rect drgui_make_inside_out_rect(); + +/// Expands the given rectangle on all sides by the given amount. +/// +/// @remarks +/// This will increase the width and height of the rectangle by x 2. +/// @par +/// The growth amount can be negative, in which case it will be shrunk. Note that this does not do any checking to ensure the rectangle +/// contains positive dimensions after a shrink. +drgui_rect drgui_grow_rect(drgui_rect rect, float amount); + +/// Scales the given rectangle. +/// +/// @param scaleX [in] The scale to apply to and +/// @param scaleY [in] The scale to apply to and +/// +/// @remarks +/// This will modify the and properties which means the rectangle will change position. To adjust only the size, scale the +/// rectangle manually. +drgui_rect drgui_scale_rect(drgui_rect rect, float scaleX, float scaleY); + +/// Offsets the given rectangle. +drgui_rect drgui_offset_rect(drgui_rect rect, float offsetX, float offsetY); + +/// Creates a rectangle that contains both of the given rectangles. +drgui_rect drgui_rect_union(drgui_rect rect0, drgui_rect rect1); + +/// Determines whether or not the given rectangle contains the given point. +/// +/// @remarks +/// An important not here is that if the position is sitting on the right or bottom border, false will be returned. If, however, the point +/// is sitting on the left or top border, true will be returned. The reason for this is that elements may sit exactly side-by-side with +/// each other, and if we use this function to determine if a point is contained within an element (which we do), we would end up having +/// this return true for both elements, which we don't want. +bool drgui_rect_contains_point(drgui_rect rect, float posX, float posY); + +/// Determines whether or not two rectangles are equal. +bool drgui_rect_equal(drgui_rect rect0, drgui_rect rect1); + +/// Determines whether or not the given rectangle has any volume (width and height > 0). +bool drgui_rect_has_volume(drgui_rect rect); + + + +///////////////////////////////////////////////////////////////// +// +// EASY_DRAW-SPECIFIC API +// +///////////////////////////////////////////////////////////////// +#ifndef DRGUI_NO_DR_2D + +/// A covenience function for creating a new context and registering the easy_draw painting callbacks. +/// +/// @remarks +/// This is equivalent to drgui_create_context() followed by drgui_register_dr_2d_callbacks(). +drgui_context* drgui_create_context_dr_2d(dr2d_context* pDrawingContext); + +/// Registers the drawing callbacks for use with easy_draw. +/// +/// @remarks +/// The user data of each callback is assumed to be a pointer to an easydraw_surface object. +void drgui_register_dr_2d_callbacks(drgui_context* pContext, dr2d_context* pDrawingContext); + +#endif + + +#ifdef __cplusplus +} +#endif + +#endif //dr_gui_h + + + +/////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef DR_GUI_IMPLEMENTATION +#include +#include +#include +#include +#include +#include + +#ifndef DRGUI_PRIVATE +#define DRGUI_PRIVATE static +#endif + +///////////////////////////////////////////////////////////////// +// +// PRIVATE CORE API +// +///////////////////////////////////////////////////////////////// + + +// Context Flags +#define IS_CONTEXT_DEAD (1U << 0) +#define IS_AUTO_DIRTY_DISABLED (1U << 1) +#define IS_RELEASING_KEYBOARD (1U << 2) + +// Element Flags +#define IS_ELEMENT_HIDDEN (1U << 0) +#define IS_ELEMENT_CLIPPING_DISABLED (1U << 1) +#define IS_ELEMENT_DEAD (1U << 31) + + +static int drgui__strcpy_s(char* dst, size_t dstSizeInBytes, const char* src) +{ +#ifdef _MSC_VER + return strcpy_s(dst, dstSizeInBytes, src); +#else + if (dst == 0) { + return EINVAL; + } + if (dstSizeInBytes == 0) { + return ERANGE; + } + if (src == 0) { + dst[0] = '\0'; + return EINVAL; + } + + size_t i; + for (i = 0; i < dstSizeInBytes && src[i] != '\0'; ++i) { + dst[i] = src[i]; + } + + if (i < dstSizeInBytes) { + dst[i] = '\0'; + return 0; + } + + dst[0] = '\0'; + return ERANGE; +#endif +} + +int drgui__strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) +{ +#ifdef _MSC_VER + return strncpy_s(dst, dstSizeInBytes, src, count); +#else + if (dst == 0) { + return EINVAL; + } + if (dstSizeInBytes == 0) { + return EINVAL; + } + if (src == 0) { + dst[0] = '\0'; + return EINVAL; + } + + size_t maxcount = count; + if (count == ((size_t)-1) || count >= dstSizeInBytes) { // -1 = _TRUNCATE + maxcount = dstSizeInBytes - 1; + } + + size_t i; + for (i = 0; i < maxcount && src[i] != '\0'; ++i) { + dst[i] = src[i]; + } + + if (src[i] == '\0' || i == count || count == ((size_t)-1)) { + dst[i] = '\0'; + return 0; + } + + dst[0] = '\0'; + return ERANGE; +#endif +} + + +/// Increments the inbound event counter +/// +/// @remarks +/// This is called from every drgui_post_inbound_event_*() function and is used to keep track of whether or +/// not an inbound event is being processed. We need to track this because if we are in the middle of event +/// processing and an element is deleted, we want to delay it's deletion until the end of the event processing. +/// @par +/// Use drgui_end_inbound_event() to decrement the counter. +void drgui_begin_inbound_event(drgui_context* pContext); + +/// Decrements the inbound event counter. +/// +/// @remarks +/// This is called from every drgui_post_inbound_event_*() function. +/// @par +/// When the internal counter reaches zero, deleted elements will be garbage collected. +void drgui_end_inbound_event(drgui_context* pContext); + +/// Determines whether or not inbound events are being processed. +/// +/// @remarks +/// This is used to determine whether or not an element can be deleted immediately or should be garbage collected +/// at the end of event processing. +bool drgui_is_handling_inbound_event(const drgui_context* pContext); + + +/// Increments the outbound event counter. +/// +/// @remarks +/// This will validate that the given element is allowed to have an event posted. When false is returned, nothing +/// will have been locked and the outbound event should be cancelled. +/// @par +/// This will return false if the given element has been marked as dead, or if there is some other reason it should +/// not be receiving events. +bool drgui_begin_outbound_event(drgui_element* pElement); + +/// Decrements the outbound event counter. +void drgui_end_outbound_event(drgui_element* pElement); + +/// Determines whether or not and outbound event is being processed. +bool drgui_is_handling_outbound_event(drgui_context* pContext); + + +/// Marks the given element as dead. +void drgui_mark_element_as_dead(drgui_element* pElement); + +/// Determines whether or not the given element is marked as dead. +bool drgui_is_element_marked_as_dead(const drgui_element* pElement); + +/// Deletes every element that has been marked as dead. +void drgui_delete_elements_marked_as_dead(drgui_context* pContext); + + +/// Marks the given context as deleted. +void drgui_mark_context_as_dead(drgui_context* pContext); + +/// Determines whether or not the given context is marked as dead. +bool drgui_is_context_marked_as_dead(const drgui_context* pContext); + + +/// Deletes the given context for real. +/// +/// If a context is deleted during the processing of an inbound event it will not be deleting immediately - this +/// will delete the context for real. +void drgui_delete_context_for_real(drgui_context* pContext); + +/// Deletes the given element for real. +/// +/// Sometimes an element will not be deleted straight away but instead just marked as dead. We use this to delete +/// the given element for real. +void drgui_delete_element_for_real(drgui_element* pElement); + + +/// Orphans the given element without triggering a redraw of the parent nor the child. +void drgui_detach_without_redraw(drgui_element* pChildElement); + +/// Appends the given element without first detaching it from the old parent, nor does it post a redraw. +void drgui_append_without_detach_or_redraw(drgui_element* pChildElement, drgui_element* pParentElement); + +/// Appends the given element without first detaching it from the old parent. +void drgui_append_without_detach(drgui_element* pChildElement, drgui_element* pParentElement); + +/// Prepends the given element without first detaching it from the old parent, nor does it post a redraw. +void drgui_prepend_without_detach_or_redraw(drgui_element* pChildElement, drgui_element* pParentElement); + +/// Prepends the given element without first detaching it from the old parent. +void drgui_prepend_without_detach(drgui_element* pChildElement, drgui_element* pParentElement); + +/// Appends an element to another as it's sibling, but does not detach it from the previous parent nor trigger a redraw. +void drgui_append_sibling_without_detach_or_redraw(drgui_element* pElementToAppend, drgui_element* pElementToAppendTo); + +/// Appends an element to another as it's sibling, but does not detach it from the previous parent. +void drgui_append_sibling_without_detach(drgui_element* pElementToAppend, drgui_element* pElementToAppendTo); + +/// Prepends an element to another as it's sibling, but does not detach it from the previous parent nor trigger a redraw. +void drgui_prepend_sibling_without_detach_or_redraw(drgui_element* pElementToPrepend, drgui_element* pElementToPrependTo); + +/// Prepends an element to another as it's sibling, but does not detach it from the previous parent. +void drgui_prepend_sibling_without_detach(drgui_element* pElementToPrepend, drgui_element* pElementToPrependTo); + + +/// Begins accumulating an invalidation rectangle. +void drgui_begin_auto_dirty(drgui_element* pElement); + +/// Ends accumulating the invalidation rectangle and posts on_dirty is auto-dirty is enabled. +void drgui_end_auto_dirty(drgui_element* pElement); + +/// Marks the given region of the given top level element as dirty, but only if automatic dirtying is enabled. +/// +/// @remarks +/// This is equivalent to drgui_begin_auto_dirty() immediately followed by drgui_end_auto_dirty(). +void drgui_auto_dirty(drgui_element* pTopLevelElement, drgui_rect rect); + + +/// Recursively applies the given offset to the absolute positions of the children of the given element. +/// +/// @remarks +/// This is called when the absolute position of an element is changed. +void drgui_apply_offset_to_children_recursive(drgui_element* pParentElement, float offsetX, float offsetY); + + +/// The function to call when the mouse may have entered into a new element. +void drgui_update_mouse_enter_and_leave_state(drgui_context* pContext, drgui_element* pNewElementUnderMouse); + + +/// Functions for posting outbound events. +void drgui_post_outbound_event_move(drgui_element* pElement, float newRelativePosX, float newRelativePosY); +void drgui_post_outbound_event_size(drgui_element* pElement, float newWidth, float newHeight); +void drgui_post_outbound_event_mouse_enter(drgui_element* pElement); +void drgui_post_outbound_event_mouse_leave(drgui_element* pElement); +void drgui_post_outbound_event_mouse_move(drgui_element* pElement, int relativeMousePosX, int relativeMousePosY, int stateFlags); +void drgui_post_outbound_event_mouse_button_down(drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); +void drgui_post_outbound_event_mouse_button_up(drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); +void drgui_post_outbound_event_mouse_button_dblclick(drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); +void drgui_post_outbound_event_mouse_wheel(drgui_element* pElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags); +void drgui_post_outbound_event_key_down(drgui_element* pElement, drgui_key key, int stateFlags); +void drgui_post_outbound_event_key_up(drgui_element* pElement, drgui_key key, int stateFlags); +void drgui_post_outbound_event_printable_key_down(drgui_element* pElement, unsigned int character, int stateFlags); +void drgui_post_outbound_event_dirty(drgui_element* pElement, drgui_rect relativeRect); +void drgui_post_outbound_event_dirty_global(drgui_element* pElement, drgui_rect relativeRect); +void drgui_post_outbound_event_capture_mouse(drgui_element* pElement); +void drgui_post_outbound_event_capture_mouse_global(drgui_element* pElement); +void drgui_post_outbound_event_release_mouse(drgui_element* pElement); +void drgui_post_outbound_event_release_mouse_global(drgui_element* pElement); +void drgui_post_outbound_event_capture_keyboard(drgui_element* pElement, drgui_element* pPrevCapturedElement); +void drgui_post_outbound_event_capture_keyboard_global(drgui_element* pElement, drgui_element* pPrevCapturedElement); +void drgui_post_outbound_event_release_keyboard(drgui_element* pElement, drgui_element* pNewCapturedElement); +void drgui_post_outbound_event_release_keyboard_global(drgui_element* pElement, drgui_element* pNewCapturedElement); + +/// Posts a log message. +void drgui_log(drgui_context* pContext, const char* message); + + +void drgui_begin_inbound_event(drgui_context* pContext) +{ + assert(pContext != NULL); + + pContext->inboundEventCounter += 1; +} + +void drgui_end_inbound_event(drgui_context* pContext) +{ + assert(pContext != NULL); + assert(pContext->inboundEventCounter > 0); + + pContext->inboundEventCounter -= 1; + + + // Here is where we want to clean up any elements that are marked as dead. When events are being handled elements are not deleted + // immediately but instead only marked for deletion. This function will be called at the end of event processing which makes it + // an appropriate place for cleaning up dead elements. + if (!drgui_is_handling_inbound_event(pContext)) + { + drgui_delete_elements_marked_as_dead(pContext); + + // If the context has been marked for deletion than we will need to delete that too. + if (drgui_is_context_marked_as_dead(pContext)) + { + drgui_delete_context_for_real(pContext); + } + } +} + +bool drgui_is_handling_inbound_event(const drgui_context* pContext) +{ + assert(pContext != NULL); + + return pContext->inboundEventCounter > 0; +} + + + +bool drgui_begin_outbound_event(drgui_element* pElement) +{ + assert(pElement != NULL); + assert(pElement->pContext != NULL); + + + // We want to cancel the outbound event if the element is marked as dead. + if (drgui_is_element_marked_as_dead(pElement)) { + drgui_log(pElement->pContext, "WARNING: Attemping to post an event to an element that is marked for deletion."); + return false; + } + + + // At this point everything should be fine so we just increment the count (which should never go above 1) and return true. + pElement->pContext->outboundEventLockCounter += 1; + + return true; +} + +void drgui_end_outbound_event(drgui_element* pElement) +{ + assert(pElement != NULL); + assert(pElement->pContext != NULL); + assert(pElement->pContext->outboundEventLockCounter > 0); + + pElement->pContext->outboundEventLockCounter -= 1; +} + +bool drgui_is_handling_outbound_event(drgui_context* pContext) +{ + assert(pContext != NULL); + return pContext->outboundEventLockCounter > 0; +} + + +void drgui_mark_element_as_dead(drgui_element* pElement) +{ + assert(pElement != NULL); + assert(pElement->pContext != NULL); + + pElement->flags |= IS_ELEMENT_DEAD; + + + if (pElement->pContext->pFirstDeadElement != NULL) { + pElement->pNextDeadElement = pElement->pContext->pFirstDeadElement; + } + + pElement->pContext->pFirstDeadElement = pElement; +} + +bool drgui_is_element_marked_as_dead(const drgui_element* pElement) +{ + if (pElement == NULL) { + return false; + } + + return (pElement->flags & IS_ELEMENT_DEAD) != 0; +} + +void drgui_delete_elements_marked_as_dead(drgui_context* pContext) +{ + assert(pContext != NULL); + + while (pContext->pFirstDeadElement != NULL) + { + drgui_element* pDeadElement = pContext->pFirstDeadElement; + pContext->pFirstDeadElement = pContext->pFirstDeadElement->pNextDeadElement; + + drgui_delete_element_for_real(pDeadElement); + } +} + + +void drgui_mark_context_as_dead(drgui_context* pContext) +{ + assert(pContext != NULL); + assert(!drgui_is_context_marked_as_dead(pContext)); + + pContext->flags |= IS_CONTEXT_DEAD; +} + +bool drgui_is_context_marked_as_dead(const drgui_context* pContext) +{ + assert(pContext != NULL); + + return (pContext->flags & IS_CONTEXT_DEAD) != 0; +} + + + +void drgui_delete_context_for_real(drgui_context* pContext) +{ + assert(pContext != NULL); + + // All elements marked as dead need to be deleted. + drgui_delete_elements_marked_as_dead(pContext); + + free(pContext); +} + +void drgui_delete_element_for_real(drgui_element* pElementToDelete) +{ + assert(pElementToDelete != NULL); + + drgui_context* pContext = pElementToDelete->pContext; + + // If the element is marked as dead + if (drgui_is_element_marked_as_dead(pElementToDelete)) { + if (pContext->pFirstDeadElement == pElementToDelete) { + pContext->pFirstDeadElement = pContext->pFirstDeadElement->pNextDeadElement; + } else { + drgui_element* pPrevDeadElement = pContext->pFirstDeadElement; + while (pPrevDeadElement != NULL) { + if (pPrevDeadElement->pNextDeadElement == pElementToDelete) { + break; + } + + pPrevDeadElement = pPrevDeadElement->pNextDeadElement; + } + + if (pPrevDeadElement != NULL) { + pElementToDelete->pNextDeadElement = pElementToDelete->pNextDeadElement; + } + } + } + + free(pElementToDelete); +} + + +void drgui_detach_without_redraw(drgui_element* pElement) +{ + if (pElement->pParent != NULL) { + if (pElement->pParent->pFirstChild == pElement) { + pElement->pParent->pFirstChild = pElement->pNextSibling; + } + + if (pElement->pParent->pLastChild == pElement) { + pElement->pParent->pLastChild = pElement->pPrevSibling; + } + + + if (pElement->pPrevSibling != NULL) { + pElement->pPrevSibling->pNextSibling = pElement->pNextSibling; + } + + if (pElement->pNextSibling != NULL) { + pElement->pNextSibling->pPrevSibling = pElement->pPrevSibling; + } + } + + pElement->pParent = NULL; + pElement->pPrevSibling = NULL; + pElement->pNextSibling = NULL; +} + +void drgui_append_without_detach_or_redraw(drgui_element* pChildElement, drgui_element* pParentElement) +{ + pChildElement->pParent = pParentElement; + if (pChildElement->pParent != NULL) { + if (pChildElement->pParent->pLastChild != NULL) { + pChildElement->pPrevSibling = pChildElement->pParent->pLastChild; + pChildElement->pPrevSibling->pNextSibling = pChildElement; + } + + if (pChildElement->pParent->pFirstChild == NULL) { + pChildElement->pParent->pFirstChild = pChildElement; + } + + pChildElement->pParent->pLastChild = pChildElement; + } +} + +void drgui_append_without_detach(drgui_element* pChildElement, drgui_element* pParentElement) +{ + drgui_append_without_detach_or_redraw(pChildElement, pParentElement); + drgui_auto_dirty(pChildElement, drgui_make_rect(0, 0, pChildElement->width, pChildElement->height)); +} + +void drgui_prepend_without_detach_or_redraw(drgui_element* pChildElement, drgui_element* pParentElement) +{ + pChildElement->pParent = pParentElement; + if (pChildElement->pParent != NULL) { + if (pChildElement->pParent->pFirstChild != NULL) { + pChildElement->pNextSibling = pChildElement->pParent->pFirstChild; + pChildElement->pNextSibling->pPrevSibling = pChildElement; + } + + if (pChildElement->pParent->pLastChild == NULL) { + pChildElement->pParent->pLastChild = pChildElement; + } + + pChildElement->pParent->pFirstChild = pChildElement; + } +} + +void drgui_prepend_without_detach(drgui_element* pChildElement, drgui_element* pParentElement) +{ + drgui_prepend_without_detach_or_redraw(pChildElement, pParentElement); + drgui_auto_dirty(pChildElement, drgui_make_rect(0, 0, pChildElement->width, pChildElement->height)); +} + +void drgui_append_sibling_without_detach_or_redraw(drgui_element* pElementToAppend, drgui_element* pElementToAppendTo) +{ + assert(pElementToAppend != NULL); + assert(pElementToAppendTo != NULL); + + pElementToAppend->pParent = pElementToAppendTo->pParent; + if (pElementToAppend->pParent != NULL) + { + pElementToAppend->pNextSibling = pElementToAppendTo->pNextSibling; + pElementToAppend->pPrevSibling = pElementToAppendTo; + + pElementToAppendTo->pNextSibling->pPrevSibling = pElementToAppend; + pElementToAppendTo->pNextSibling = pElementToAppend; + + if (pElementToAppend->pParent->pLastChild == pElementToAppendTo) { + pElementToAppend->pParent->pLastChild = pElementToAppend; + } + } +} + +void drgui_append_sibling_without_detach(drgui_element* pElementToAppend, drgui_element* pElementToAppendTo) +{ + drgui_append_sibling_without_detach_or_redraw(pElementToAppend, pElementToAppendTo); + drgui_auto_dirty(pElementToAppend, drgui_make_rect(0, 0, pElementToAppend->width, pElementToAppend->height)); +} + +void drgui_prepend_sibling_without_detach_or_redraw(drgui_element* pElementToPrepend, drgui_element* pElementToPrependTo) +{ + assert(pElementToPrepend != NULL); + assert(pElementToPrependTo != NULL); + + pElementToPrepend->pParent = pElementToPrependTo->pParent; + if (pElementToPrepend->pParent != NULL) + { + pElementToPrepend->pPrevSibling = pElementToPrependTo->pNextSibling; + pElementToPrepend->pNextSibling = pElementToPrependTo; + + pElementToPrependTo->pPrevSibling->pNextSibling = pElementToPrepend; + pElementToPrependTo->pNextSibling = pElementToPrepend; + + if (pElementToPrepend->pParent->pFirstChild == pElementToPrependTo) { + pElementToPrepend->pParent->pFirstChild = pElementToPrepend; + } + } +} + +void drgui_prepend_sibling_without_detach(drgui_element* pElementToPrepend, drgui_element* pElementToPrependTo) +{ + drgui_prepend_sibling_without_detach_or_redraw(pElementToPrepend, pElementToPrependTo); + drgui_auto_dirty(pElementToPrepend, drgui_make_rect(0, 0, pElementToPrepend->width, pElementToPrepend->height)); +} + + +void drgui_begin_auto_dirty(drgui_element* pElement) +{ + assert(pElement != NULL); + assert(pElement->pContext != NULL); + + if (drgui_is_auto_dirty_enabled(pElement->pContext)) { + drgui_begin_dirty(pElement); + } +} + +void drgui_end_auto_dirty(drgui_element* pElement) +{ + assert(pElement != NULL); + + drgui_context* pContext = pElement->pContext; + assert(pContext != NULL); + + if (drgui_is_auto_dirty_enabled(pContext)) { + drgui_end_dirty(pElement); + } +} + +void drgui_auto_dirty(drgui_element* pElement, drgui_rect relativeRect) +{ + assert(pElement != NULL); + assert(pElement->pContext != NULL); + + if (drgui_is_auto_dirty_enabled(pElement->pContext)) { + drgui_dirty(pElement, relativeRect); + } +} + + +void drgui__change_cursor(drgui_element* pElement, drgui_cursor_type cursor) +{ + if (pElement == NULL || pElement->pContext == NULL) { + return; + } + + pElement->pContext->currentCursor = cursor; + + if (pElement->pContext->onChangeCursor) { + pElement->pContext->onChangeCursor(pElement, cursor); + } +} + + + +void drgui_apply_offset_to_children_recursive(drgui_element* pParentElement, float offsetX, float offsetY) +{ + assert(pParentElement != NULL); + + for (drgui_element* pChild = pParentElement->pFirstChild; pChild != NULL; pChild = pChild->pNextSibling) + { + drgui_begin_auto_dirty(pParentElement); + { + drgui_auto_dirty(pParentElement, drgui_get_local_rect(pParentElement)); + pChild->absolutePosX += offsetX; + pChild->absolutePosY += offsetY; + + drgui_apply_offset_to_children_recursive(pChild, offsetX, offsetY); + } + drgui_end_auto_dirty(pParentElement); + } +} + +DRGUI_PRIVATE void drgui_post_on_mouse_leave_recursive(drgui_context* pContext, drgui_element* pNewElementUnderMouse, drgui_element* pOldElementUnderMouse) +{ + (void)pContext; + + drgui_element* pOldAncestor = pOldElementUnderMouse; + while (pOldAncestor != NULL) + { + bool isOldElementUnderMouse = pNewElementUnderMouse == pOldAncestor || drgui_is_ancestor(pOldAncestor, pNewElementUnderMouse); + if (!isOldElementUnderMouse) + { + drgui_post_outbound_event_mouse_leave(pOldAncestor); + } + + pOldAncestor = pOldAncestor->pParent; + } +} + +DRGUI_PRIVATE void drgui_post_on_mouse_enter_recursive(drgui_context* pContext, drgui_element* pNewElementUnderMouse, drgui_element* pOldElementUnderMouse) +{ + if (pNewElementUnderMouse == NULL) { + return; + } + + + if (pNewElementUnderMouse->pParent != NULL) { + drgui_post_on_mouse_enter_recursive(pContext, pNewElementUnderMouse->pParent, pOldElementUnderMouse); + } + + bool wasNewElementUnderMouse = pOldElementUnderMouse == pNewElementUnderMouse || drgui_is_ancestor(pNewElementUnderMouse, pOldElementUnderMouse); + if (!wasNewElementUnderMouse) + { + drgui_post_outbound_event_mouse_enter(pNewElementUnderMouse); + } +} + +void drgui_update_mouse_enter_and_leave_state(drgui_context* pContext, drgui_element* pNewElementUnderMouse) +{ + if (pContext == NULL) { + return; + } + + drgui_element* pOldElementUnderMouse = pContext->pElementUnderMouse; + if (pOldElementUnderMouse != pNewElementUnderMouse) + { + // We don't change the enter and leave state if an element is capturing the mouse. + if (pContext->pElementWithMouseCapture == NULL) + { + pContext->pElementUnderMouse = pNewElementUnderMouse; + + drgui_cursor_type newCursor = drgui_cursor_default; + if (pNewElementUnderMouse != NULL) { + newCursor = pNewElementUnderMouse->cursor; + } + + + // It's intuitive to check that the new cursor is different to the old one before trying to change it, but that is not actually + // what we want to do. We'll let the event handler manage it themselves because it's possible the window manager might do some + // window-specific cursor management and the old and new elements are on different windows. + drgui__change_cursor(pNewElementUnderMouse, newCursor); + + + + // The the event handlers below, remember that ancestors are considered hovered if a descendant is the element under the mouse. + + // on_mouse_leave + drgui_post_on_mouse_leave_recursive(pContext, pNewElementUnderMouse, pOldElementUnderMouse); + + // on_mouse_enter + drgui_post_on_mouse_enter_recursive(pContext, pNewElementUnderMouse, pOldElementUnderMouse); + } + } +} + + +void drgui_post_outbound_event_move(drgui_element* pElement, float newRelativePosX, float newRelativePosY) +{ + if (drgui_begin_outbound_event(pElement)) + { + if (pElement->onMove) { + pElement->onMove(pElement, newRelativePosX, newRelativePosY); + } + + drgui_end_outbound_event(pElement); + } +} + +void drgui_post_outbound_event_size(drgui_element* pElement, float newWidth, float newHeight) +{ + if (drgui_begin_outbound_event(pElement)) + { + if (pElement->onSize) { + pElement->onSize(pElement, newWidth, newHeight); + } + + drgui_end_outbound_event(pElement); + } +} + +void drgui_post_outbound_event_mouse_enter(drgui_element* pElement) +{ + if (drgui_begin_outbound_event(pElement)) + { + if (pElement->onMouseEnter) { + pElement->onMouseEnter(pElement); + } + + drgui_end_outbound_event(pElement); + } +} + +void drgui_post_outbound_event_mouse_leave(drgui_element* pElement) +{ + if (drgui_begin_outbound_event(pElement)) + { + if (pElement->onMouseLeave) { + pElement->onMouseLeave(pElement); + } + + drgui_end_outbound_event(pElement); + } +} + +void drgui_post_outbound_event_mouse_move(drgui_element* pElement, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + if (drgui_begin_outbound_event(pElement)) +{ + if (pElement->onMouseMove) { + pElement->onMouseMove(pElement, relativeMousePosX, relativeMousePosY, stateFlags); + } + + drgui_end_outbound_event(pElement); + } +} + +void drgui_post_outbound_event_mouse_button_down(drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + if (drgui_begin_outbound_event(pElement)) + { + if (pElement->onMouseButtonDown) { + pElement->onMouseButtonDown(pElement, mouseButton, relativeMousePosX, relativeMousePosY, stateFlags); + } + + drgui_end_outbound_event(pElement); + } +} + +void drgui_post_outbound_event_mouse_button_up(drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + if (drgui_begin_outbound_event(pElement)) + { + if (pElement->onMouseButtonUp) { + pElement->onMouseButtonUp(pElement, mouseButton, relativeMousePosX, relativeMousePosY, stateFlags); + } + + drgui_end_outbound_event(pElement); + } +} + +void drgui_post_outbound_event_mouse_button_dblclick(drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + if (drgui_begin_outbound_event(pElement)) + { + if (pElement->onMouseButtonDblClick) { + pElement->onMouseButtonDblClick(pElement, mouseButton, relativeMousePosX, relativeMousePosY, stateFlags); + } + + drgui_end_outbound_event(pElement); + } +} + +void drgui_post_outbound_event_mouse_wheel(drgui_element* pElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + if (drgui_begin_outbound_event(pElement)) + { + if (pElement->onMouseWheel) { + pElement->onMouseWheel(pElement, delta, relativeMousePosX, relativeMousePosY, stateFlags); + } + + drgui_end_outbound_event(pElement); + } +} + +void drgui_post_outbound_event_key_down(drgui_element* pElement, drgui_key key, int stateFlags) +{ + if (drgui_begin_outbound_event(pElement)) + { + if (pElement->onKeyDown) { + pElement->onKeyDown(pElement, key, stateFlags); + } + + drgui_end_outbound_event(pElement); + } +} + +void drgui_post_outbound_event_key_up(drgui_element* pElement, drgui_key key, int stateFlags) +{ + if (drgui_begin_outbound_event(pElement)) + { + if (pElement->onKeyUp) { + pElement->onKeyUp(pElement, key, stateFlags); + } + + drgui_end_outbound_event(pElement); + } +} + +void drgui_post_outbound_event_printable_key_down(drgui_element* pElement, unsigned int character, int stateFlags) +{ + if (drgui_begin_outbound_event(pElement)) + { + if (pElement->onPrintableKeyDown) { + pElement->onPrintableKeyDown(pElement, character, stateFlags); + } + + drgui_end_outbound_event(pElement); + } +} + + +void drgui_post_outbound_event_dirty(drgui_element* pElement, drgui_rect relativeRect) +{ + if (pElement != NULL) + { + if (pElement->onDirty) { + pElement->onDirty(pElement, relativeRect); + } + } +} + +void drgui_post_outbound_event_dirty_global(drgui_element* pElement, drgui_rect relativeRect) +{ + if (pElement != NULL && pElement->pContext != NULL) + { + if (pElement->pContext->onGlobalDirty) { + pElement->pContext->onGlobalDirty(pElement, relativeRect); + } + } +} + +void drgui_post_outbound_event_capture_mouse(drgui_element* pElement) +{ + if (pElement != NULL) + { + if (pElement->onCaptureMouse) { + pElement->onCaptureMouse(pElement); + } + } +} + +void drgui_post_outbound_event_capture_mouse_global(drgui_element* pElement) +{ + if (pElement != NULL && pElement->pContext != NULL) + { + if (pElement->pContext->onGlobalCaptureMouse) { + pElement->pContext->onGlobalCaptureMouse(pElement); + } + } +} + +void drgui_post_outbound_event_release_mouse(drgui_element* pElement) +{ + if (pElement != NULL) + { + if (pElement->onReleaseMouse) { + pElement->onReleaseMouse(pElement); + } + } +} + +void drgui_post_outbound_event_release_mouse_global(drgui_element* pElement) +{ + if (pElement != NULL && pElement->pContext != NULL) + { + if (pElement->pContext->onGlobalReleaseMouse) { + pElement->pContext->onGlobalReleaseMouse(pElement); + } + } +} + + +void drgui_post_outbound_event_capture_keyboard(drgui_element* pElement, drgui_element* pPrevCapturedElement) +{ + if (pElement != NULL) + { + if (pElement->onCaptureKeyboard) { + pElement->onCaptureKeyboard(pElement, pPrevCapturedElement); + } + } +} + +void drgui_post_outbound_event_capture_keyboard_global(drgui_element* pElement, drgui_element* pPrevCapturedElement) +{ + if (pElement != NULL && pElement->pContext != NULL) + { + if (pElement->pContext->onGlobalCaptureKeyboard) { + pElement->pContext->onGlobalCaptureKeyboard(pElement, pPrevCapturedElement); + } + } +} + +void drgui_post_outbound_event_release_keyboard(drgui_element* pElement, drgui_element* pNewCapturedElement) +{ + if (pElement != NULL) + { + if (pElement->onReleaseKeyboard) { + pElement->onReleaseKeyboard(pElement, pNewCapturedElement); + } + } +} + +void drgui_post_outbound_event_release_keyboard_global(drgui_element* pElement, drgui_element* pNewCapturedElement) +{ + if (pElement != NULL && pElement->pContext != NULL) + { + if (pElement->pContext->onGlobalReleaseKeyboard) { + pElement->pContext->onGlobalReleaseKeyboard(pElement, pNewCapturedElement); + } + } +} + + +void drgui_log(drgui_context* pContext, const char* message) +{ + if (pContext != NULL) + { + if (pContext->onLog) { + pContext->onLog(pContext, message); + } + } +} + + +///////////////////////////////////////////////////////////////// +// +// CORE API +// +///////////////////////////////////////////////////////////////// + +drgui_context* drgui_create_context() +{ + drgui_context* pContext = (drgui_context*)calloc(1, sizeof(drgui_context)); + if (pContext != NULL) { + pContext->currentCursor = drgui_cursor_default; + } + + return pContext; +} + +void drgui_delete_context(drgui_context* pContext) +{ + if (pContext == NULL) { + return; + } + + + // Make sure the mouse capture is released. + if (pContext->pElementWithMouseCapture != NULL) + { + drgui_log(pContext, "WARNING: Deleting the GUI context while an element still has the mouse capture."); + drgui_release_mouse(pContext); + } + + // Make sure the keyboard capture is released. + if (pContext->pElementWithKeyboardCapture != NULL) + { + drgui_log(pContext, "WARNING: Deleting the GUI context while an element still has the keyboard capture."); + drgui_release_keyboard(pContext); + } + + + if (drgui_is_handling_inbound_event(pContext)) + { + // An inbound event is still being processed - we don't want to delete the context straight away because we can't + // trust external event handlers to not try to access the context later on. To do this we just set the flag that + // the context is deleted. It will then be deleted for real at the end of the inbound event handler. + drgui_mark_context_as_dead(pContext); + } + else + { + // An inbound event is not being processed, so delete the context straight away. + drgui_delete_context_for_real(pContext); + } +} + + + +///////////////////////////////////////////////////////////////// +// Events + +void drgui_post_inbound_event_mouse_leave(drgui_element* pTopLevelElement) +{ + if (pTopLevelElement == NULL) { + return; + } + + drgui_context* pContext = pTopLevelElement->pContext; + if (pContext == NULL) { + return; + } + + drgui_begin_inbound_event(pContext); + { + // We assume that was previously under the mouse was either pTopLevelElement itself or one of it's descendants. + drgui_update_mouse_enter_and_leave_state(pContext, NULL); + } + drgui_end_inbound_event(pContext); +} + +void drgui_post_inbound_event_mouse_move(drgui_element* pTopLevelElement, int mousePosX, int mousePosY, int stateFlags) +{ + if (pTopLevelElement == NULL || pTopLevelElement->pContext == NULL) { + return; + } + + + drgui_begin_inbound_event(pTopLevelElement->pContext); + { + /// A pointer to the top level element that was passed in from the last inbound mouse move event. + pTopLevelElement->pContext->pLastMouseMoveTopLevelElement = pTopLevelElement; + + /// The position of the mouse that was passed in from the last inbound mouse move event. + pTopLevelElement->pContext->lastMouseMovePosX = (float)mousePosX; + pTopLevelElement->pContext->lastMouseMovePosY = (float)mousePosY; + + + + // The first thing we need to do is find the new element that's sitting under the mouse. + drgui_element* pNewElementUnderMouse = drgui_find_element_under_point(pTopLevelElement, (float)mousePosX, (float)mousePosY); + + // Now that we know which element is sitting under the mouse we need to check if the mouse has entered into a new element. + drgui_update_mouse_enter_and_leave_state(pTopLevelElement->pContext, pNewElementUnderMouse); + + + drgui_element* pEventReceiver = pTopLevelElement->pContext->pElementWithMouseCapture; + if (pEventReceiver == NULL) + { + pEventReceiver = pNewElementUnderMouse; + } + + if (pEventReceiver != NULL) + { + float relativeMousePosX = (float)mousePosX; + float relativeMousePosY = (float)mousePosY; + drgui_make_point_relative(pEventReceiver, &relativeMousePosX, &relativeMousePosY); + + drgui_post_outbound_event_mouse_move(pEventReceiver, (int)relativeMousePosX, (int)relativeMousePosY, stateFlags); + } + } + drgui_end_inbound_event(pTopLevelElement->pContext); +} + +void drgui_post_inbound_event_mouse_button_down(drgui_element* pTopLevelElement, int mouseButton, int mousePosX, int mousePosY, int stateFlags) +{ + if (pTopLevelElement == NULL || pTopLevelElement->pContext == NULL) { + return; + } + + drgui_context* pContext = pTopLevelElement->pContext; + drgui_begin_inbound_event(pContext); + { + drgui_element* pEventReceiver = pContext->pElementWithMouseCapture; + if (pEventReceiver == NULL) + { + pEventReceiver = pContext->pElementUnderMouse; + + if (pEventReceiver == NULL) + { + // We'll get here if this message is posted without a prior mouse move event. + pEventReceiver = drgui_find_element_under_point(pTopLevelElement, (float)mousePosX, (float)mousePosY); + } + } + + + if (pEventReceiver != NULL) + { + float relativeMousePosX = (float)mousePosX; + float relativeMousePosY = (float)mousePosY; + drgui_make_point_relative(pEventReceiver, &relativeMousePosX, &relativeMousePosY); + + drgui_post_outbound_event_mouse_button_down(pEventReceiver, mouseButton, (int)relativeMousePosX, (int)relativeMousePosY, stateFlags); + } + } + drgui_end_inbound_event(pContext); +} + +void drgui_post_inbound_event_mouse_button_up(drgui_element* pTopLevelElement, int mouseButton, int mousePosX, int mousePosY, int stateFlags) +{ + if (pTopLevelElement == NULL || pTopLevelElement->pContext == NULL) { + return; + } + + drgui_context* pContext = pTopLevelElement->pContext; + drgui_begin_inbound_event(pContext); + { + drgui_element* pEventReceiver = pContext->pElementWithMouseCapture; + if (pEventReceiver == NULL) + { + pEventReceiver = pContext->pElementUnderMouse; + + if (pEventReceiver == NULL) + { + // We'll get here if this message is posted without a prior mouse move event. + pEventReceiver = drgui_find_element_under_point(pTopLevelElement, (float)mousePosX, (float)mousePosY); + } + } + + + if (pEventReceiver != NULL) + { + float relativeMousePosX = (float)mousePosX; + float relativeMousePosY = (float)mousePosY; + drgui_make_point_relative(pEventReceiver, &relativeMousePosX, &relativeMousePosY); + + drgui_post_outbound_event_mouse_button_up(pEventReceiver, mouseButton, (int)relativeMousePosX, (int)relativeMousePosY, stateFlags); + } + } + drgui_end_inbound_event(pContext); +} + +void drgui_post_inbound_event_mouse_button_dblclick(drgui_element* pTopLevelElement, int mouseButton, int mousePosX, int mousePosY, int stateFlags) +{ + if (pTopLevelElement == NULL || pTopLevelElement->pContext == NULL) { + return; + } + + drgui_context* pContext = pTopLevelElement->pContext; + drgui_begin_inbound_event(pContext); + { + drgui_element* pEventReceiver = pContext->pElementWithMouseCapture; + if (pEventReceiver == NULL) + { + pEventReceiver = pContext->pElementUnderMouse; + + if (pEventReceiver == NULL) + { + // We'll get here if this message is posted without a prior mouse move event. + pEventReceiver = drgui_find_element_under_point(pTopLevelElement, (float)mousePosX, (float)mousePosY); + } + } + + + if (pEventReceiver != NULL) + { + float relativeMousePosX = (float)mousePosX; + float relativeMousePosY = (float)mousePosY; + drgui_make_point_relative(pEventReceiver, &relativeMousePosX, &relativeMousePosY); + + drgui_post_outbound_event_mouse_button_dblclick(pEventReceiver, mouseButton, (int)relativeMousePosX, (int)relativeMousePosY, stateFlags); + } + } + drgui_end_inbound_event(pContext); +} + +void drgui_post_inbound_event_mouse_wheel(drgui_element* pTopLevelElement, int delta, int mousePosX, int mousePosY, int stateFlags) +{ + if (pTopLevelElement == NULL || pTopLevelElement->pContext == NULL) { + return; + } + + drgui_context* pContext = pTopLevelElement->pContext; + drgui_begin_inbound_event(pContext); + { + drgui_element* pEventReceiver = pContext->pElementWithMouseCapture; + if (pEventReceiver == NULL) + { + pEventReceiver = pContext->pElementUnderMouse; + + if (pEventReceiver == NULL) + { + // We'll get here if this message is posted without a prior mouse move event. + pEventReceiver = drgui_find_element_under_point(pTopLevelElement, (float)mousePosX, (float)mousePosY); + } + } + + + if (pEventReceiver != NULL) + { + float relativeMousePosX = (float)mousePosX; + float relativeMousePosY = (float)mousePosY; + drgui_make_point_relative(pEventReceiver, &relativeMousePosX, &relativeMousePosY); + + drgui_post_outbound_event_mouse_wheel(pEventReceiver, delta, (int)relativeMousePosX, (int)relativeMousePosY, stateFlags); + } + } + drgui_end_inbound_event(pContext); +} + +void drgui_post_inbound_event_key_down(drgui_context* pContext, drgui_key key, int stateFlags) +{ + if (pContext == NULL) { + return; + } + + drgui_begin_inbound_event(pContext); + { + if (pContext->pElementWithKeyboardCapture != NULL) { + drgui_post_outbound_event_key_down(pContext->pElementWithKeyboardCapture, key, stateFlags); + } + } + drgui_end_inbound_event(pContext); +} + +void drgui_post_inbound_event_key_up(drgui_context* pContext, drgui_key key, int stateFlags) +{ + if (pContext == NULL) { + return; + } + + drgui_begin_inbound_event(pContext); + { + if (pContext->pElementWithKeyboardCapture != NULL) { + drgui_post_outbound_event_key_up(pContext->pElementWithKeyboardCapture, key, stateFlags); + } + } + drgui_end_inbound_event(pContext); +} + +void drgui_post_inbound_event_printable_key_down(drgui_context* pContext, unsigned int character, int stateFlags) +{ + if (pContext == NULL) { + return; + } + + drgui_begin_inbound_event(pContext); + { + if (pContext->pElementWithKeyboardCapture != NULL) { + drgui_post_outbound_event_printable_key_down(pContext->pElementWithKeyboardCapture, character, stateFlags); + } + } + drgui_end_inbound_event(pContext); +} + + + +void drgui_set_global_on_dirty(drgui_context * pContext, drgui_on_dirty_proc onDirty) +{ + if (pContext != NULL) { + pContext->onGlobalDirty = onDirty; + } +} + +void drgui_set_global_on_capture_mouse(drgui_context* pContext, drgui_on_capture_mouse_proc onCaptureMouse) +{ + if (pContext != NULL) { + pContext->onGlobalCaptureMouse = onCaptureMouse; + } +} + +void drgui_set_global_on_release_mouse(drgui_context* pContext, drgui_on_release_mouse_proc onReleaseMouse) +{ + if (pContext != NULL) { + pContext->onGlobalReleaseMouse = onReleaseMouse; + } +} + +void drgui_set_global_on_capture_keyboard(drgui_context* pContext, drgui_on_capture_keyboard_proc onCaptureKeyboard) +{ + if (pContext != NULL) { + pContext->onGlobalCaptureKeyboard = onCaptureKeyboard; + } +} + +void drgui_set_global_on_release_keyboard(drgui_context* pContext, drgui_on_capture_keyboard_proc onReleaseKeyboard) +{ + if (pContext != NULL) { + pContext->onGlobalReleaseKeyboard = onReleaseKeyboard; + } +} + +void drgui_set_global_on_change_cursor(drgui_context* pContext, drgui_on_change_cursor_proc onChangeCursor) +{ + if (pContext != NULL) { + pContext->onChangeCursor = onChangeCursor; + } +} + +void drgui_set_on_delete_element(drgui_context* pContext, drgui_on_delete_element_proc onDeleteElement) +{ + if (pContext != NULL) { + pContext->onDeleteElement = onDeleteElement; + } +} + +void drgui_set_on_log(drgui_context* pContext, drgui_on_log onLog) +{ + if (pContext != NULL) { + pContext->onLog = onLog; + } +} + + + +///////////////////////////////////////////////////////////////// +// Elements + +drgui_element* drgui_create_element(drgui_context* pContext, drgui_element* pParent, size_t extraDataSize, const void* pExtraData) +{ + if (pContext != NULL) + { + drgui_element* pElement = (drgui_element*)calloc(1, sizeof(drgui_element) + extraDataSize); + if (pElement != NULL) { + pElement->pContext = pContext; + pElement->pParent = pParent; + pElement->cursor = drgui_cursor_default; + pElement->dirtyRect = drgui_make_inside_out_rect(); + + pElement->extraDataSize = extraDataSize; + if (pExtraData != NULL) { + memcpy(pElement->pExtraData, pExtraData, extraDataSize); + } + + // Add to the the hierarchy. + drgui_append_without_detach_or_redraw(pElement, pElement->pParent); + + + // Have the element positioned at 0,0 relative to the parent by default. + if (pParent != NULL) { + pElement->absolutePosX = pParent->absolutePosX; + pElement->absolutePosY = pParent->absolutePosY; + } + + + return pElement; + } + } + + return NULL; +} + +void drgui_delete_element(drgui_element* pElement) +{ + if (pElement == NULL) { + return; + } + + drgui_context* pContext = pElement->pContext; + if (pContext == NULL) { + return; + } + + if (drgui_is_element_marked_as_dead(pElement)) { + drgui_log(pContext, "WARNING: Attempting to delete an element that is already marked for deletion."); + return; + } + + drgui_mark_element_as_dead(pElement); + + + // Notify the application that the element is being deleted. Do this at the top so the event handler can access things like the hierarchy and + // whatnot in case it needs it. + if (pContext->onDeleteElement) { + pContext->onDeleteElement(pElement); + } + + + // If this was element is marked as the one that was last under the mouse it needs to be unset. + bool needsMouseUpdate = false; + if (pContext->pElementUnderMouse == pElement) + { + pContext->pElementUnderMouse = NULL; + needsMouseUpdate = true; + } + + if (pContext->pLastMouseMoveTopLevelElement == pElement) + { + pContext->pLastMouseMoveTopLevelElement = NULL; + pContext->lastMouseMovePosX = 0; + pContext->lastMouseMovePosY = 0; + needsMouseUpdate = false; // It was a top-level element so the mouse enter/leave state doesn't need an update. + } + + + // If this element has the mouse capture it needs to be released. + if (pContext->pElementWithMouseCapture == pElement) + { + drgui_log(pContext, "WARNING: Deleting an element while it still has the mouse capture."); + drgui_release_mouse(pContext); + } + + // If this element has the keyboard capture it needs to be released. + if (pContext->pElementWithKeyboardCapture == pElement) + { + drgui_log(pContext, "WARNING: Deleting an element while it still has the keyboard capture."); + drgui_release_keyboard(pContext); + } + + // Is this element in the middle of being marked as dirty? + for (size_t iDirtyElement = 0; iDirtyElement < pContext->dirtyElementCount; ++iDirtyElement) { + if (pContext->ppDirtyElements[iDirtyElement] == pElement) { + drgui_log(pContext, "WARNING: Deleting an element while it is being marked as dirty."); + for (size_t iDirtyElement2 = iDirtyElement; iDirtyElement2+1 < pContext->dirtyElementCount; ++iDirtyElement2) { + pContext->ppDirtyElements[iDirtyElement2] = pContext->ppDirtyElements[iDirtyElement2+1]; + } + + pContext->dirtyElementCount -= 1; + break; + } + } + +#if 0 + if (pContext->pDirtyTopLevelElement == pElement) + { + drgui_log(pContext, "WARNING: Deleting an element while it is being marked as dirty."); + pContext->pDirtyTopLevelElement = NULL; + } +#endif + + + + // Deleting this element may have resulted in the mouse entering a new element. Here is where we do a mouse enter/leave update. + if (needsMouseUpdate) + { + pElement->onHitTest = drgui_pass_through_hit_test; // <-- This ensures we don't include this element when searching for the new element under the mouse. + drgui_update_mouse_enter_and_leave_state(pContext, drgui_find_element_under_point(pContext->pLastMouseMoveTopLevelElement, pContext->lastMouseMovePosX, pContext->lastMouseMovePosY)); + } + + + // Here is where we need to detach the element from the hierarchy. When doing this we want to ensure the element is not redrawn when + // it's children are detached. To do this we simply detach the event handler. + pElement->onPaint = NULL; + + // The parent needs to be redraw after detaching. + drgui_element* pParent = pElement->pParent; + drgui_rect relativeRect = drgui_get_relative_rect(pElement); + + + // Orphan the element first. + drgui_detach_without_redraw(pElement); + + // Children need to be deleted before deleting the element itself. + while (pElement->pLastChild != NULL) { + drgui_delete_element(pElement->pLastChild); + } + + + // The parent needs to be redrawn. + if (pParent) { + drgui_dirty(pParent, relativeRect); + } + + + // Finally, we to decided whether or not the element should be deleted for real straight away or not. If the element is being + // deleted within an event handler it should be delayed because the event handlers may try referencing it afterwards. + if (!drgui_is_handling_inbound_event(pContext)) { + drgui_delete_element_for_real(pElement); + } +} + + +size_t drgui_get_extra_data_size(drgui_element* pElement) +{ + if (pElement != NULL) { + return pElement->extraDataSize; + } + + return 0; +} + +void* drgui_get_extra_data(drgui_element* pElement) +{ + if (pElement != NULL) { + return pElement->pExtraData; + } + + return NULL; +} + + +bool drgui_set_type(drgui_element* pElement, const char* type) +{ + if (pElement == NULL) { + return false; + } + + return drgui__strcpy_s(pElement->type, sizeof(pElement->type), (type == NULL) ? "" : type) == 0; +} + +const char* drgui_get_type(drgui_element* pElement) +{ + if (pElement == NULL) { + return NULL; + } + + return pElement->type; +} + +bool drgui_is_of_type(drgui_element* pElement, const char* type) +{ + if (pElement == NULL || type == NULL) { + return false; + } + + return strncmp(pElement->type, type, strlen(type)) == 0; +} + + +void drgui_hide(drgui_element* pElement) +{ + if (pElement != NULL) { + pElement->flags |= IS_ELEMENT_HIDDEN; + drgui_auto_dirty(pElement, drgui_get_local_rect(pElement)); + } +} + +void drgui_show(drgui_element* pElement) +{ + if (pElement != NULL) { + pElement->flags &= ~IS_ELEMENT_HIDDEN; + drgui_auto_dirty(pElement, drgui_get_local_rect(pElement)); + } +} + +bool drgui_is_visible(const drgui_element* pElement) +{ + if (pElement != NULL) { + return (pElement->flags & IS_ELEMENT_HIDDEN) == 0; + } + + return false; +} + +bool drgui_is_visible_recursive(const drgui_element* pElement) +{ + if (drgui_is_visible(pElement)) + { + assert(pElement->pParent != NULL); + + if (pElement->pParent != NULL) { + return drgui_is_visible(pElement->pParent); + } + } + + return false; +} + + +void drgui_disable_clipping(drgui_element* pElement) +{ + if (pElement != NULL) { + pElement->flags |= IS_ELEMENT_CLIPPING_DISABLED; + } +} + +void drgui_enable_clipping(drgui_element* pElement) +{ + if (pElement != NULL) { + pElement->flags &= ~IS_ELEMENT_CLIPPING_DISABLED; + } +} + +bool drgui_is_clipping_enabled(const drgui_element* pElement) +{ + if (pElement != NULL) { + return (pElement->flags & IS_ELEMENT_CLIPPING_DISABLED) == 0; + } + + return true; +} + + + +void drgui_capture_mouse(drgui_element* pElement) +{ + if (pElement == NULL) { + return; + } + + if (pElement->pContext == NULL) { + return; + } + + + if (pElement->pContext->pElementWithMouseCapture != pElement) + { + // Release the previous capture first. + if (pElement->pContext->pElementWithMouseCapture != NULL) { + drgui_release_mouse(pElement->pContext); + } + + assert(pElement->pContext->pElementWithMouseCapture == NULL); + + pElement->pContext->pElementWithMouseCapture = pElement; + + // Two events need to be posted - the global on_capture_mouse event and the local on_capture_mouse event. + drgui_post_outbound_event_capture_mouse(pElement); + + if (pElement == pElement->pContext->pElementWithMouseCapture) { // <-- Only post the global event handler if the element still has the capture. + drgui_post_outbound_event_capture_mouse_global(pElement); + } + } +} + +void drgui_release_mouse(drgui_context* pContext) +{ + if (pContext == NULL) { + return; + } + + + // Events need to be posted before setting the internal pointer. + if (!drgui_is_element_marked_as_dead(pContext->pElementWithMouseCapture)) { // <-- There's a chace the element is releaseing the keyboard due to being deleted. Don't want to post an event in this case. + drgui_post_outbound_event_release_mouse(pContext->pElementWithMouseCapture); + drgui_post_outbound_event_release_mouse_global(pContext->pElementWithMouseCapture); + } + + // We want to set the internal pointer to NULL after posting the events since that is when it has truly released the mouse. + pContext->pElementWithMouseCapture = NULL; + + + // After releasing the mouse the cursor may be sitting on top of a different element - we want to recheck that. + drgui_update_mouse_enter_and_leave_state(pContext, drgui_find_element_under_point(pContext->pLastMouseMoveTopLevelElement, pContext->lastMouseMovePosX, pContext->lastMouseMovePosY)); +} + +void drgui_release_mouse_no_global_notify(drgui_context* pContext) +{ + if (pContext == NULL) { + return; + } + + drgui_on_release_mouse_proc prevProc = pContext->onGlobalReleaseMouse; + pContext->onGlobalReleaseMouse = NULL; + drgui_release_mouse(pContext); + pContext->onGlobalReleaseMouse = prevProc; +} + +drgui_element* drgui_get_element_with_mouse_capture(drgui_context* pContext) +{ + if (pContext == NULL) { + return NULL; + } + + return pContext->pElementWithMouseCapture; +} + +bool drgui_has_mouse_capture(drgui_element* pElement) +{ + if (pElement == NULL) { + return false; + } + + return drgui_get_element_with_mouse_capture(pElement->pContext) == pElement; +} + + +DRGUI_PRIVATE void drgui_release_keyboard_private(drgui_context* pContext, drgui_element* pNewCapturedElement) +{ + assert(pContext != NULL); + + // It is reasonable to expect that an application will want to change keyboard focus from within the release_keyboard + // event handler. The problem with this is that is can cause a infinite dependency chain. We need to handle that case + // by setting a flag that keeps track of whether or not we are in the middle of a release_keyboard event. At the end + // we look at the element that want's the keyboard focuse and explicitly capture it at the end. + + pContext->flags |= IS_RELEASING_KEYBOARD; + { + drgui_element* pPrevCapturedElement = pContext->pElementWithKeyboardCapture; + pContext->pElementWithKeyboardCapture = NULL; + + if (!drgui_is_element_marked_as_dead(pPrevCapturedElement)) { // <-- There's a chace the element is releaseing the keyboard due to being deleted. Don't want to post an event in this case. + drgui_post_outbound_event_release_keyboard(pPrevCapturedElement, pNewCapturedElement); + drgui_post_outbound_event_release_keyboard_global(pPrevCapturedElement, pNewCapturedElement); + } + } + pContext->flags &= ~IS_RELEASING_KEYBOARD; + + // Explicitly capture the keyboard. + drgui_capture_keyboard(pContext->pElementWantingKeyboardCapture); + pContext->pElementWantingKeyboardCapture = NULL; +} + +void drgui_capture_keyboard(drgui_element* pElement) +{ + if (pElement == NULL) { + return; + } + + if (pElement->pContext == NULL) { + return; + } + + + if ((pElement->pContext->flags & IS_RELEASING_KEYBOARD) != 0) { + pElement->pContext->pElementWantingKeyboardCapture = pElement; + return; + } + + + if (pElement->pContext->pElementWithKeyboardCapture != pElement) + { + // Release the previous capture first. + drgui_element* pPrevElementWithKeyboardCapture = pElement->pContext->pElementWithKeyboardCapture; + if (pPrevElementWithKeyboardCapture != NULL) { + drgui_release_keyboard_private(pElement->pContext, pElement); + } + + assert(pElement->pContext->pElementWithKeyboardCapture == NULL); + + pElement->pContext->pElementWithKeyboardCapture = pElement; + pElement->pContext->pElementWantingKeyboardCapture = NULL; + + // Two events need to be posted - the global on_capture event and the local on_capture event. The problem, however, is that the + // local event handler may change the keyboard capture internally, such as if it wants to pass it's focus onto an internal child + // element or whatnot. In this case we don't want to fire the global event handler because it will result in superfluous event + // posting, and could also be posted with an incorrect element. + drgui_post_outbound_event_capture_keyboard(pElement, pPrevElementWithKeyboardCapture); + + if (pElement == pElement->pContext->pElementWithKeyboardCapture) { // <-- Only post the global event handler if the element still has the capture. + drgui_post_outbound_event_capture_keyboard_global(pElement, pPrevElementWithKeyboardCapture); + } + } +} + +void drgui_release_keyboard(drgui_context* pContext) +{ + if (pContext == NULL) { + return; + } + + drgui_release_keyboard_private(pContext, NULL); +} + +void drgui_release_keyboard_no_global_notify(drgui_context* pContext) +{ + if (pContext == NULL) { + return; + } + + drgui_on_release_keyboard_proc prevProc = pContext->onGlobalReleaseKeyboard; + pContext->onGlobalReleaseKeyboard = NULL; + drgui_release_keyboard(pContext); + pContext->onGlobalReleaseKeyboard = prevProc; +} + +drgui_element* drgui_get_element_with_keyboard_capture(drgui_context* pContext) +{ + if (pContext == NULL) { + return NULL; + } + + return pContext->pElementWithKeyboardCapture; +} + +bool drgui_has_keyboard_capture(drgui_element* pElement) +{ + if (pElement == NULL) { + return false; + } + + return drgui_get_element_with_keyboard_capture(pElement->pContext) == pElement; +} + + +void drgui_set_cursor(drgui_element* pElement, drgui_cursor_type cursor) +{ + if (pElement == NULL) { + return; + } + + pElement->cursor = cursor; + + if (drgui_is_element_under_mouse(pElement) && pElement->pContext->currentCursor != cursor) { + drgui__change_cursor(pElement, cursor); + } +} + +drgui_cursor_type drgui_get_cursor(drgui_element* pElement) +{ + if (pElement == NULL) { + return drgui_cursor_none; + } + + return pElement->cursor; +} + + + +//// Events //// + +void drgui_set_on_move(drgui_element * pElement, drgui_on_move_proc callback) +{ + if (pElement != NULL) { + pElement->onMove = callback; + } +} + +void drgui_set_on_size(drgui_element * pElement, drgui_on_size_proc callback) +{ + if (pElement != NULL) { + pElement->onSize = callback; + } +} + +void drgui_set_on_mouse_enter(drgui_element* pElement, drgui_on_mouse_enter_proc callback) +{ + if (pElement != NULL) { + pElement->onMouseEnter = callback; + } +} + +void drgui_set_on_mouse_leave(drgui_element* pElement, drgui_on_mouse_leave_proc callback) +{ + if (pElement != NULL) { + pElement->onMouseLeave = callback; + } +} + +void drgui_set_on_mouse_move(drgui_element* pElement, drgui_on_mouse_move_proc callback) +{ + if (pElement != NULL) { + pElement->onMouseMove = callback; + } +} + +void drgui_set_on_mouse_button_down(drgui_element* pElement, drgui_on_mouse_button_down_proc callback) +{ + if (pElement != NULL) { + pElement->onMouseButtonDown = callback; + } +} + +void drgui_set_on_mouse_button_up(drgui_element* pElement, drgui_on_mouse_button_up_proc callback) +{ + if (pElement != NULL) { + pElement->onMouseButtonUp = callback; + } +} + +void drgui_set_on_mouse_button_dblclick(drgui_element* pElement, drgui_on_mouse_button_dblclick_proc callback) +{ + if (pElement != NULL) { + pElement->onMouseButtonDblClick = callback; + } +} + +void drgui_set_on_mouse_wheel(drgui_element* pElement, drgui_on_mouse_wheel_proc callback) +{ + if (pElement != NULL) { + pElement->onMouseWheel = callback; + } +} + +void drgui_set_on_key_down(drgui_element* pElement, drgui_on_key_down_proc callback) +{ + if (pElement != NULL) { + pElement->onKeyDown = callback; + } +} + +void drgui_set_on_key_up(drgui_element* pElement, drgui_on_key_up_proc callback) +{ + if (pElement != NULL) { + pElement->onKeyUp = callback; + } +} + +void drgui_set_on_printable_key_down(drgui_element* pElement, drgui_on_printable_key_down_proc callback) +{ + if (pElement != NULL) { + pElement->onPrintableKeyDown = callback; + } +} + +void drgui_set_on_paint(drgui_element* pElement, drgui_on_paint_proc callback) +{ + if (pElement != NULL) { + pElement->onPaint = callback; + } +} + +void drgui_set_on_dirty(drgui_element * pElement, drgui_on_dirty_proc callback) +{ + if (pElement != NULL) { + pElement->onDirty = callback; + } +} + +void drgui_set_on_hittest(drgui_element* pElement, drgui_on_hittest_proc callback) +{ + if (pElement != NULL) { + pElement->onHitTest = callback; + } +} + +void drgui_set_on_capture_mouse(drgui_element* pElement, drgui_on_capture_mouse_proc callback) +{ + if (pElement != NULL) { + pElement->onCaptureMouse = callback; + } +} + +void drgui_set_on_release_mouse(drgui_element* pElement, drgui_on_release_mouse_proc callback) +{ + if (pElement != NULL) { + pElement->onReleaseMouse = callback; + } +} + +void drgui_set_on_capture_keyboard(drgui_element* pElement, drgui_on_capture_keyboard_proc callback) +{ + if (pElement != NULL) { + pElement->onCaptureKeyboard = callback; + } +} + +void drgui_set_on_release_keyboard(drgui_element* pElement, drgui_on_release_keyboard_proc callback) +{ + if (pElement != NULL) { + pElement->onReleaseKeyboard = callback; + } +} + + + +bool drgui_is_point_inside_element_bounds(const drgui_element* pElement, float absolutePosX, float absolutePosY) +{ + if (absolutePosX < pElement->absolutePosX || + absolutePosX < pElement->absolutePosY) + { + return false; + } + + if (absolutePosX >= pElement->absolutePosX + pElement->width || + absolutePosY >= pElement->absolutePosY + pElement->height) + { + return false; + } + + return true; +} + +bool drgui_is_point_inside_element(drgui_element* pElement, float absolutePosX, float absolutePosY) +{ + if (drgui_is_point_inside_element_bounds(pElement, absolutePosX, absolutePosY)) + { + // It is valid for onHitTest to be null, in which case we use the default hit test which assumes the element is just a rectangle + // equal to the size of it's bounds. It's equivalent to onHitTest always returning true. + + if (pElement->onHitTest) { + return pElement->onHitTest(pElement, absolutePosX - pElement->absolutePosX, absolutePosY - pElement->absolutePosY); + } + + return true; + } + + return false; +} + + + +typedef struct +{ + drgui_element* pElementUnderPoint; + float absolutePosX; + float absolutePosY; +}drgui_find_element_under_point_data; + +bool drgui_find_element_under_point_iterator(drgui_element* pElement, drgui_rect* pRelativeVisibleRect, void* pUserData) +{ + assert(pElement != NULL); + assert(pRelativeVisibleRect != NULL); + + drgui_find_element_under_point_data* pData = (drgui_find_element_under_point_data*)pUserData; + assert(pData != NULL); + + float relativePosX = pData->absolutePosX; + float relativePosY = pData->absolutePosY; + drgui_make_point_relative(pElement, &relativePosX, &relativePosY); + + if (drgui_rect_contains_point(*pRelativeVisibleRect, relativePosX, relativePosY)) + { + if (pElement->onHitTest) { + if (pElement->onHitTest(pElement, relativePosX, relativePosY)) { + pData->pElementUnderPoint = pElement; + } + } else { + pData->pElementUnderPoint = pElement; + } + } + + + // Always return true to ensure the entire hierarchy is checked. + return true; +} + +drgui_element* drgui_find_element_under_point(drgui_element* pTopLevelElement, float absolutePosX, float absolutePosY) +{ + if (pTopLevelElement == NULL) { + return NULL; + } + + drgui_find_element_under_point_data data; + data.pElementUnderPoint = NULL; + data.absolutePosX = absolutePosX; + data.absolutePosY = absolutePosY; + drgui_iterate_visible_elements(pTopLevelElement, drgui_get_absolute_rect(pTopLevelElement), drgui_find_element_under_point_iterator, &data); + + return data.pElementUnderPoint; +} + +bool drgui_is_element_under_mouse(drgui_element* pElement) +{ + if (pElement == NULL) { + return false; + } + + return drgui_find_element_under_point(pElement->pContext->pLastMouseMoveTopLevelElement, pElement->pContext->lastMouseMovePosX, pElement->pContext->lastMouseMovePosY) == pElement; +} + + + +//// Hierarchy //// + +drgui_element* drgui_get_parent(drgui_element* pChildElement) +{ + if (pChildElement == NULL) { + return NULL; + } + + return pChildElement->pParent; +} + +void drgui_detach(drgui_element* pChildElement) +{ + if (pChildElement == NULL) { + return; + } + + drgui_element* pOldParent = pChildElement->pParent; + + + // We orphan the element using the private API. This will not mark the parent element as dirty so we need to do that afterwards. + drgui_detach_without_redraw(pChildElement); + + // The region of the old parent needs to be redrawn. + if (pOldParent != NULL) { + drgui_auto_dirty(pOldParent, drgui_get_relative_rect(pOldParent)); + } +} + +void drgui_append(drgui_element* pChildElement, drgui_element* pParentElement) +{ + if (pChildElement == NULL) { + return; + } + + // We first need to orphan the element. If the parent element is the new parent is the same as the old one, as in we + // are just moving the child element to the end of the children list, we want to delay the repaint until the end. To + // do this we use drgui_detach_without_redraw() because that will not trigger a redraw. + if (pChildElement->pParent != pParentElement) { + drgui_detach(pChildElement); + } else { + drgui_detach_without_redraw(pChildElement); + } + + + // Now we attach it to the end of the new parent. + if (pParentElement != NULL) { + drgui_append_without_detach(pChildElement, pParentElement); + } +} + +void drgui_prepend(drgui_element* pChildElement, drgui_element* pParentElement) +{ + if (pChildElement == NULL) { + return; + } + + // See comment in drgui_append() for explanation on this. + if (pChildElement->pParent != pParentElement) { + drgui_detach(pChildElement); + } else { + drgui_detach_without_redraw(pChildElement); + } + + + // Now we need to attach the element to the beginning of the parent. + if (pParentElement != NULL) { + drgui_prepend_without_detach(pChildElement, pParentElement); + } +} + +void drgui_append_sibling(drgui_element* pElementToAppend, drgui_element* pElementToAppendTo) +{ + if (pElementToAppend == NULL || pElementToAppendTo == NULL) { + return; + } + + // See comment in drgui_append() for explanation on this. + if (pElementToAppend->pParent != pElementToAppendTo->pParent) { + drgui_detach(pElementToAppend); + } else { + drgui_detach_without_redraw(pElementToAppend); + } + + + // Now we need to attach the element such that it comes just after pElementToAppendTo + drgui_append_sibling_without_detach(pElementToAppend, pElementToAppendTo); +} + +void drgui_prepend_sibling(drgui_element* pElementToPrepend, drgui_element* pElementToPrependTo) +{ + if (pElementToPrepend == NULL || pElementToPrependTo == NULL) { + return; + } + + // See comment in drgui_append() for explanation on this. + if (pElementToPrepend->pParent != pElementToPrependTo->pParent) { + drgui_detach(pElementToPrepend); + } else { + drgui_detach_without_redraw(pElementToPrepend); + } + + + // Now we need to attach the element such that it comes just after pElementToPrependTo + drgui_prepend_sibling_without_detach(pElementToPrepend, pElementToPrependTo); +} + +drgui_element* drgui_find_top_level_element(drgui_element* pElement) +{ + if (pElement == NULL) { + return NULL; + } + + if (pElement->pParent != NULL) { + return drgui_find_top_level_element(pElement->pParent); + } + + return pElement; +} + +bool drgui_is_parent(drgui_element* pParentElement, drgui_element* pChildElement) +{ + if (pParentElement == NULL || pChildElement == NULL) { + return false; + } + + return pParentElement == pChildElement->pParent; +} + +bool drgui_is_child(drgui_element* pChildElement, drgui_element* pParentElement) +{ + return drgui_is_parent(pParentElement, pChildElement); +} + +bool drgui_is_ancestor(drgui_element* pAncestorElement, drgui_element* pChildElement) +{ + if (pAncestorElement == NULL || pChildElement == NULL) { + return false; + } + + drgui_element* pParent = pChildElement->pParent; + while (pParent != NULL) + { + if (pParent == pAncestorElement) { + return true; + } + + pParent = pParent->pParent; + } + + + return false; +} + +bool drgui_is_descendant(drgui_element* pChildElement, drgui_element* pAncestorElement) +{ + return drgui_is_ancestor(pAncestorElement, pChildElement); +} + +bool drgui_is_self_or_ancestor(drgui_element* pAncestorElement, drgui_element* pChildElement) +{ + return pAncestorElement == pChildElement || drgui_is_ancestor(pAncestorElement, pChildElement); +} + +bool drgui_is_self_or_descendant(drgui_element* pChildElement, drgui_element* pAncestorElement) +{ + return pChildElement == pAncestorElement || drgui_is_descendant(pChildElement, pAncestorElement); +} + + + +//// Layout //// + +void drgui_set_absolute_position(drgui_element* pElement, float positionX, float positionY) +{ + if (pElement != NULL) + { + if (pElement->absolutePosX != positionX || pElement->absolutePosY != positionY) + { + float oldRelativePosX = drgui_get_relative_position_x(pElement); + float oldRelativePosY = drgui_get_relative_position_y(pElement); + + drgui_begin_auto_dirty(pElement); + { + drgui_auto_dirty(pElement, drgui_get_local_rect(pElement)); // <-- Previous rectangle. + + float offsetX = positionX - pElement->absolutePosX; + float offsetY = positionY - pElement->absolutePosY; + + pElement->absolutePosX = positionX; + pElement->absolutePosY = positionY; + drgui_auto_dirty(pElement, drgui_get_local_rect(pElement)); // <-- New rectangle. + + + float newRelativePosX = drgui_get_relative_position_x(pElement); + float newRelativePosY = drgui_get_relative_position_y(pElement); + + if (newRelativePosX != oldRelativePosX || newRelativePosY != oldRelativePosY) { + drgui_post_outbound_event_move(pElement, newRelativePosX, newRelativePosY); + } + + + drgui_apply_offset_to_children_recursive(pElement, offsetX, offsetY); + } + drgui_end_auto_dirty(pElement); + } + } +} + +void drgui_get_absolute_position(const drgui_element* pElement, float * positionXOut, float * positionYOut) +{ + if (pElement != NULL) + { + if (positionXOut != NULL) { + *positionXOut = pElement->absolutePosX; + } + + if (positionYOut != NULL) { + *positionYOut = pElement->absolutePosY; + } + } +} + +float drgui_get_absolute_position_x(const drgui_element* pElement) +{ + if (pElement != NULL) { + return pElement->absolutePosX; + } + + return 0.0f; +} + +float drgui_get_absolute_position_y(const drgui_element* pElement) +{ + if (pElement != NULL) { + return pElement->absolutePosY; + } + + return 0.0f; +} + + +void drgui_set_relative_position(drgui_element* pElement, float relativePosX, float relativePosY) +{ + if (pElement != NULL) { + if (pElement->pParent != NULL) { + drgui_set_absolute_position(pElement, pElement->pParent->absolutePosX + relativePosX, pElement->pParent->absolutePosY + relativePosY); + } else { + drgui_set_absolute_position(pElement, relativePosX, relativePosY); + } + } +} + +void drgui_get_relative_position(const drgui_element* pElement, float* positionXOut, float* positionYOut) +{ + if (pElement != NULL) + { + if (pElement->pParent != NULL) + { + if (positionXOut != NULL) { + *positionXOut = pElement->absolutePosX - pElement->pParent->absolutePosX; + } + + if (positionYOut != NULL) { + *positionYOut = pElement->absolutePosY - pElement->pParent->absolutePosY; + } + } + else + { + if (positionXOut != NULL) { + *positionXOut = pElement->absolutePosX; + } + + if (positionYOut != NULL) { + *positionYOut = pElement->absolutePosY; + } + } + } +} + +float drgui_get_relative_position_x(const drgui_element* pElement) +{ + if (pElement != NULL) { + if (pElement->pParent != NULL) { + return pElement->absolutePosX - pElement->pParent->absolutePosX; + } else { + return pElement->absolutePosX; + } + } + + return 0; +} + +float drgui_get_relative_position_y(const drgui_element* pElement) +{ + if (pElement != NULL) { + if (pElement->pParent != NULL) { + return pElement->absolutePosY - pElement->pParent->absolutePosY; + } else { + return pElement->absolutePosY; + } + } + + return 0; +} + + +void drgui_set_size(drgui_element* pElement, float width, float height) +{ + if (pElement != NULL) + { + if (pElement->width != width || pElement->height != height) + { + drgui_begin_auto_dirty(pElement); + { + drgui_auto_dirty(pElement, drgui_get_local_rect(pElement)); // <-- Previous rectangle. + + pElement->width = width; + pElement->height = height; + drgui_auto_dirty(pElement, drgui_get_local_rect(pElement)); // <-- New rectangle. + + drgui_post_outbound_event_size(pElement, width, height); + } + drgui_end_auto_dirty(pElement); + } + } +} + +void drgui_get_size(const drgui_element* pElement, float* widthOut, float* heightOut) +{ + if (pElement != NULL) { + if (widthOut) *widthOut = pElement->width; + if (heightOut) *heightOut = pElement->height; + } else { + if (widthOut) *widthOut = 0; + if (heightOut) *heightOut = 0; + } +} + +float drgui_get_width(const drgui_element * pElement) +{ + if (pElement != NULL) { + return pElement->width; + } + + return 0; +} + +float drgui_get_height(const drgui_element * pElement) +{ + if (pElement != NULL) { + return pElement->height; + } + + return 0; +} + + +drgui_rect drgui_get_absolute_rect(const drgui_element* pElement) +{ + drgui_rect rect; + if (pElement != NULL) + { + rect.left = pElement->absolutePosX; + rect.top = pElement->absolutePosY; + rect.right = rect.left + pElement->width; + rect.bottom = rect.top + pElement->height; + } + else + { + rect.left = 0; + rect.top = 0; + rect.right = 0; + rect.bottom = 0; + } + + return rect; +} + +drgui_rect drgui_get_relative_rect(const drgui_element* pElement) +{ + drgui_rect rect; + if (pElement != NULL) + { + rect.left = drgui_get_relative_position_x(pElement); + rect.top = drgui_get_relative_position_y(pElement); + rect.right = rect.left + pElement->width; + rect.bottom = rect.top + pElement->height; + } + else + { + rect.left = 0; + rect.top = 0; + rect.right = 0; + rect.bottom = 0; + } + + return rect; +} + +drgui_rect drgui_get_local_rect(const drgui_element* pElement) +{ + drgui_rect rect; + rect.left = 0; + rect.top = 0; + + if (pElement != NULL) + { + rect.right = pElement->width; + rect.bottom = pElement->height; + } + else + { + rect.right = 0; + rect.bottom = 0; + } + + return rect; +} + + + +//// Painting //// + +bool drgui_register_painting_callbacks(drgui_context* pContext, void* pPaintingContext, drgui_painting_callbacks callbacks) +{ + if (pContext == NULL) { + return false; + } + + // Fail if the painting callbacks have already been registered. + if (pContext->pPaintingContext != NULL) { + return false; + } + + + pContext->pPaintingContext = pPaintingContext; + pContext->paintingCallbacks = callbacks; + + return true; +} + + +bool drgui_iterate_visible_elements(drgui_element* pParentElement, drgui_rect relativeRect, drgui_visible_iteration_proc callback, void* pUserData) +{ + if (pParentElement == NULL) { + return false; + } + + if (callback == NULL) { + return false; + } + + + if (!drgui_is_visible(pParentElement)) { + return true; + } + + drgui_rect clampedRelativeRect = relativeRect; + if (drgui_clamp_rect_to_element(pParentElement, &clampedRelativeRect)) + { + // We'll only get here if some part of the rectangle was inside the element. + if (!callback(pParentElement, &clampedRelativeRect, pUserData)) { + return false; + } + } + + for (drgui_element* pChild = pParentElement->pFirstChild; pChild != NULL; pChild = pChild->pNextSibling) + { + float childRelativePosX = drgui_get_relative_position_x(pChild); + float childRelativePosY = drgui_get_relative_position_y(pChild); + + drgui_rect childRect; + if (drgui_is_clipping_enabled(pChild)) { + childRect = clampedRelativeRect; + } else { + childRect = relativeRect; + } + + + childRect.left -= childRelativePosX; + childRect.top -= childRelativePosY; + childRect.right -= childRelativePosX; + childRect.bottom -= childRelativePosY; + + if (!drgui_iterate_visible_elements(pChild, childRect, callback, pUserData)) { + return false; + } + } + + + return true; +} + +void drgui_disable_auto_dirty(drgui_context* pContext) +{ + if (pContext != NULL) { + pContext->flags |= IS_AUTO_DIRTY_DISABLED; + } +} + +void drgui_enable_auto_dirty(drgui_context* pContext) +{ + if (pContext != NULL) { + pContext->flags &= ~IS_AUTO_DIRTY_DISABLED; + } +} + +bool drgui_is_auto_dirty_enabled(drgui_context* pContext) +{ + if (pContext != NULL) { + return (pContext->flags & IS_AUTO_DIRTY_DISABLED) == 0; + } + + return false; +} + + +drgui_element* drgui_begin_dirty(drgui_element* pElement) +{ + if (pElement == NULL) { + return NULL; + } + + drgui_context* pContext = pElement->pContext; + assert(pContext != NULL); + + drgui_element* pTopLevelElement = drgui_find_top_level_element(pElement); + assert(pTopLevelElement != NULL); + + // The element needs to be added to the list of dirty elements if it doesn't exist already. + bool isAlreadyDirty = false; + for (size_t iDirtyElementCount = 0; iDirtyElementCount < pContext->dirtyElementCount; ++iDirtyElementCount) { + if (pContext->ppDirtyElements[iDirtyElementCount] == pTopLevelElement) { + isAlreadyDirty = true; + break; + } + } + + if (!isAlreadyDirty) { + if (pContext->dirtyElementCount == pContext->dirtyElementBufferSize) { + size_t newBufferSize = pContext->dirtyElementBufferSize == 0 ? 1 : pContext->dirtyElementBufferSize*2; + drgui_element** ppNewDirtyElements = (drgui_element**)realloc(pContext->ppDirtyElements, newBufferSize * sizeof(*ppNewDirtyElements)); + if (ppNewDirtyElements == NULL) { + return NULL; + } + + pContext->ppDirtyElements = ppNewDirtyElements; + pContext->dirtyElementBufferSize = newBufferSize; + } + + pContext->ppDirtyElements[pContext->dirtyCounter] = pTopLevelElement; + pContext->dirtyElementCount += 1; + } + + + pContext->dirtyCounter += 1; + return pTopLevelElement; +} + +void drgui_end_dirty(drgui_element* pElement) +{ + if (pElement == NULL) { + return; + } + + drgui_context* pContext = pElement->pContext; + assert(pContext != NULL); + + assert(pContext->dirtyElementCount > 0); + assert(pContext->dirtyCounter > 0); + + pContext->dirtyCounter -= 1; + if (pContext->dirtyCounter == 0) + { + for (size_t i = 0; i < pContext->dirtyElementCount; ++i) { + drgui_post_outbound_event_dirty_global(pContext->ppDirtyElements[i], pContext->ppDirtyElements[i]->dirtyRect); + pContext->ppDirtyElements[i]->dirtyRect = drgui_make_inside_out_rect(); + } + + pContext->dirtyElementCount = 0; + } +} + +void drgui_dirty(drgui_element* pElement, drgui_rect relativeRect) +{ + if (pElement == NULL) { + return; + } + + //drgui_context* pContext = pElement->pContext; + //assert(pContext != NULL); + + drgui_element* pTopLevelElement = drgui_begin_dirty(pElement); + if (pTopLevelElement == NULL) { + return; + } + + pTopLevelElement->dirtyRect = drgui_rect_union(pTopLevelElement->dirtyRect, drgui_make_rect_absolute(pElement, &relativeRect)); + drgui_end_dirty(pElement); +} + + +bool drgui_draw_iteration_callback(drgui_element* pElement, drgui_rect* pRelativeRect, void* pUserData) +{ + assert(pElement != NULL); + assert(pRelativeRect != NULL); + + if (pElement->onPaint != NULL) + { + // We want to set the initial clipping rectangle before drawing. + drgui_set_clip(pElement, *pRelativeRect, pUserData); + + // We now call the painting function, but only after setting the clipping rectangle. + pElement->onPaint(pElement, *pRelativeRect, pUserData); + + // The on_paint event handler may have adjusted the clipping rectangle so we need to ensure it's restored. + drgui_set_clip(pElement, *pRelativeRect, pUserData); + } + + return true; +} + +void drgui_draw(drgui_element* pElement, drgui_rect relativeRect, void* pPaintData) +{ + if (pElement == NULL) { + return; + } + + drgui_context* pContext = pElement->pContext; + if (pContext == NULL) { + return; + } + + assert(pContext->paintingCallbacks.drawBegin != NULL); + assert(pContext->paintingCallbacks.drawEnd != NULL); + + pContext->paintingCallbacks.drawBegin(pPaintData); + { + drgui_iterate_visible_elements(pElement, relativeRect, drgui_draw_iteration_callback, pPaintData); + } + pContext->paintingCallbacks.drawEnd(pPaintData); +} + +void drgui_get_clip(drgui_element* pElement, drgui_rect* pRelativeRect, void* pPaintData) +{ + if (pElement == NULL || pElement->pContext == NULL) { + return; + } + + pElement->pContext->paintingCallbacks.getClip(pRelativeRect, pPaintData); + + // The clip returned by the drawing callback will be absolute so we'll need to convert that to relative. + drgui_make_rect_relative(pElement, pRelativeRect); +} + +void drgui_set_clip(drgui_element* pElement, drgui_rect relativeRect, void* pPaintData) +{ + if (pElement == NULL || pElement->pContext == NULL) { + return; + } + + + // Make sure the rectangle is not negative. + if (relativeRect.right < relativeRect.left) { + relativeRect.right = relativeRect.left; + } + + if (relativeRect.bottom < relativeRect.top) { + relativeRect.bottom = relativeRect.top; + } + + drgui_rect absoluteRect = relativeRect; + drgui_make_rect_absolute(pElement, &absoluteRect); + + pElement->pContext->paintingCallbacks.setClip(absoluteRect, pPaintData); +} + +void drgui_draw_rect(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, void* pPaintData) +{ + if (pElement == NULL) { + return; + } + + assert(pElement->pContext != NULL); + + drgui_rect absoluteRect = relativeRect; + drgui_make_rect_absolute(pElement, &absoluteRect); + + pElement->pContext->paintingCallbacks.drawRect(absoluteRect, color, pPaintData); +} + +void drgui_draw_rect_outline(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float outlineWidth, void* pPaintData) +{ + if (pElement == NULL) { + return; + } + + assert(pElement->pContext != NULL); + + drgui_rect absoluteRect = relativeRect; + drgui_make_rect_absolute(pElement, &absoluteRect); + + pElement->pContext->paintingCallbacks.drawRectOutline(absoluteRect, color, outlineWidth, pPaintData); +} + +void drgui_draw_rect_with_outline(drgui_element * pElement, drgui_rect relativeRect, drgui_color color, float outlineWidth, drgui_color outlineColor, void * pPaintData) +{ + if (pElement == NULL) { + return; + } + + assert(pElement->pContext != NULL); + + drgui_rect absoluteRect = relativeRect; + drgui_make_rect_absolute(pElement, &absoluteRect); + + pElement->pContext->paintingCallbacks.drawRectWithOutline(absoluteRect, color, outlineWidth, outlineColor, pPaintData); +} + +void drgui_draw_round_rect(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float radius, void* pPaintData) +{ + if (pElement == NULL) { + return; + } + + assert(pElement->pContext != NULL); + + drgui_rect absoluteRect = relativeRect; + drgui_make_rect_absolute(pElement, &absoluteRect); + + pElement->pContext->paintingCallbacks.drawRoundRect(absoluteRect, color, radius, pPaintData); +} + +void drgui_draw_round_rect_outline(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float radius, float outlineWidth, void* pPaintData) +{ + if (pElement == NULL) { + return; + } + + assert(pElement->pContext != NULL); + + drgui_rect absoluteRect = relativeRect; + drgui_make_rect_absolute(pElement, &absoluteRect); + + pElement->pContext->paintingCallbacks.drawRoundRectOutline(absoluteRect, color, radius, outlineWidth, pPaintData); +} + +void drgui_draw_round_rect_with_outline(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float radius, float outlineWidth, drgui_color outlineColor, void* pPaintData) +{ + if (pElement == NULL) { + return; + } + + assert(pElement->pContext != NULL); + + drgui_rect absoluteRect = relativeRect; + drgui_make_rect_absolute(pElement, &absoluteRect); + + pElement->pContext->paintingCallbacks.drawRoundRectWithOutline(absoluteRect, color, radius, outlineWidth, outlineColor, pPaintData); +} + +void drgui_draw_text(drgui_element* pElement, drgui_font* pFont, const char* text, int textLengthInBytes, float posX, float posY, drgui_color color, drgui_color backgroundColor, void* pPaintData) +{ + if (pElement == NULL || pFont == NULL) { + return; + } + + assert(pElement->pContext != NULL); + + float absolutePosX = posX; + float absolutePosY = posY; + drgui_make_point_absolute(pElement, &absolutePosX, &absolutePosY); + + pElement->pContext->paintingCallbacks.drawText(pFont->internalFont, text, textLengthInBytes, absolutePosX, absolutePosY, color, backgroundColor, pPaintData); +} + +void drgui_draw_image(drgui_element* pElement, drgui_image* pImage, drgui_draw_image_args* pArgs, void* pPaintData) +{ + if (pElement == NULL || pImage == NULL || pArgs == NULL) { + return; + } + + assert(pElement->pContext != NULL); + + drgui_make_point_absolute(pElement, &pArgs->dstX, &pArgs->dstY); + drgui_make_point_absolute(pElement, &pArgs->dstBoundsX, &pArgs->dstBoundsY); + + if ((pArgs->options & DRGUI_IMAGE_ALIGN_CENTER) != 0) + { + pArgs->dstX = pArgs->dstBoundsX + (pArgs->dstBoundsWidth - pArgs->dstWidth) / 2; + pArgs->dstY = pArgs->dstBoundsY + (pArgs->dstBoundsHeight - pArgs->dstHeight) / 2; + } + + drgui_rect prevClip; + pElement->pContext->paintingCallbacks.getClip(&prevClip, pPaintData); + + bool restoreClip = false; + if ((pArgs->options & DRGUI_IMAGE_CLIP_BOUNDS) != 0) + { + // We only need to clip if part of the destination rectangle falls outside of the bounds. + if (pArgs->dstX < pArgs->dstBoundsX || pArgs->dstX + pArgs->dstWidth > pArgs->dstBoundsX + pArgs->dstBoundsWidth || + pArgs->dstY < pArgs->dstBoundsY || pArgs->dstY + pArgs->dstHeight > pArgs->dstBoundsY + pArgs->dstBoundsHeight) + { + restoreClip = true; + pElement->pContext->paintingCallbacks.setClip(drgui_make_rect(pArgs->dstBoundsX, pArgs->dstBoundsY, pArgs->dstBoundsX + pArgs->dstBoundsWidth, pArgs->dstBoundsY + pArgs->dstBoundsHeight), pPaintData); + } + } + + if ((pArgs->options & DRGUI_IMAGE_DRAW_BOUNDS) != 0) + { + // The bounds is the area sitting around the outside of the destination rectangle. + const float boundsLeft = pArgs->dstBoundsX; + const float boundsTop = pArgs->dstBoundsY; + const float boundsRight = boundsLeft + pArgs->dstBoundsWidth; + const float boundsBottom = boundsTop + pArgs->dstBoundsHeight; + + const float imageLeft = pArgs->dstX; + const float imageTop = pArgs->dstY; + const float imageRight = imageLeft + pArgs->dstWidth; + const float imageBottom = imageTop + pArgs->dstHeight; + + // Left. + if (boundsLeft < imageLeft) { + pElement->pContext->paintingCallbacks.drawRect(drgui_make_rect(boundsLeft, boundsTop, imageLeft, boundsBottom), pArgs->boundsColor, pPaintData); + } + + // Right. + if (boundsRight > imageRight) { + pElement->pContext->paintingCallbacks.drawRect(drgui_make_rect(imageRight, boundsTop, boundsRight, boundsBottom), pArgs->boundsColor, pPaintData); + } + + // Top. + if (boundsTop < imageTop) { + pElement->pContext->paintingCallbacks.drawRect(drgui_make_rect(imageLeft, boundsTop, imageRight, imageTop), pArgs->boundsColor, pPaintData); + } + + // Bottom. + if (boundsBottom > imageBottom) { + pElement->pContext->paintingCallbacks.drawRect(drgui_make_rect(imageLeft, imageBottom, imageRight, boundsBottom), pArgs->boundsColor, pPaintData); + } + } + + pElement->pContext->paintingCallbacks.drawImage(pImage->hResource, pArgs, pPaintData); + + if (restoreClip) { + pElement->pContext->paintingCallbacks.setClip(prevClip, pPaintData); + } +} + + +drgui_font* drgui_create_font(drgui_context* pContext, const char* family, unsigned int size, drgui_font_weight weight, drgui_font_slant slant, float rotation, unsigned int flags) +{ + if (pContext == NULL) { + return NULL; + } + + if (pContext->paintingCallbacks.createFont == NULL) { + return NULL; + } + + + drgui_resource internalFont = pContext->paintingCallbacks.createFont(pContext->pPaintingContext, family, size, weight, slant, rotation, flags); + if (internalFont == NULL) { + return NULL; + } + + drgui_font* pFont = (drgui_font*)malloc(sizeof(drgui_font)); + if (pFont == NULL) { + return NULL; + } + + pFont->pContext = pContext; + pFont->family[0] = '\0'; + pFont->size = size; + pFont->weight = weight; + pFont->slant = slant; + pFont->rotation = rotation; + pFont->flags = flags; + pFont->internalFont = internalFont; + + if (family != NULL) { + drgui__strcpy_s(pFont->family, sizeof(pFont->family), family); + } + + return pFont; +} + +void drgui_delete_font(drgui_font* pFont) +{ + if (pFont == NULL) { + return; + } + + assert(pFont->pContext != NULL); + + // Delete the internal font objects first. + if (pFont->pContext->paintingCallbacks.deleteFont) { + pFont->pContext->paintingCallbacks.deleteFont(pFont->internalFont); + } + + free(pFont); +} + +bool drgui_get_font_metrics(drgui_font* pFont, drgui_font_metrics* pMetricsOut) +{ + if (pFont == NULL || pMetricsOut == NULL) { + return false; + } + + assert(pFont->pContext != NULL); + + if (pFont->pContext->paintingCallbacks.getFontMetrics == NULL) { + return false; + } + + return pFont->pContext->paintingCallbacks.getFontMetrics(pFont->internalFont, pMetricsOut); +} + +bool drgui_get_glyph_metrics(drgui_font* pFont, unsigned int utf32, drgui_glyph_metrics* pMetricsOut) +{ + if (pFont == NULL || pMetricsOut == NULL) { + return false; + } + + assert(pFont->pContext != NULL); + + if (pFont->pContext->paintingCallbacks.getGlyphMetrics == NULL) { + return false; + } + + return pFont->pContext->paintingCallbacks.getGlyphMetrics(pFont->internalFont, utf32, pMetricsOut); +} + +bool drgui_measure_string(drgui_font* pFont, const char* text, size_t textLengthInBytes, float* pWidthOut, float* pHeightOut) +{ + if (pFont == NULL) { + return false; + } + + if (text == NULL || textLengthInBytes == 0) + { + drgui_font_metrics metrics; + if (!drgui_get_font_metrics(pFont, &metrics)) { + return false; + } + + if (pWidthOut) { + *pWidthOut = 0; + } + if (pHeightOut) { + *pHeightOut = (float)metrics.lineHeight; + } + + return true; + } + + + + assert(pFont->pContext != NULL); + + if (pFont->pContext->paintingCallbacks.measureString == NULL) { + return false; + } + + return pFont->pContext->paintingCallbacks.measureString(pFont->internalFont, text, textLengthInBytes, pWidthOut, pHeightOut); +} + +bool drgui_get_text_cursor_position_from_point(drgui_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut) +{ + if (pFont == NULL) { + return false; + } + + assert(pFont->pContext != NULL); + + if (pFont->pContext->paintingCallbacks.getTextCursorPositionFromPoint) { + return pFont->pContext->paintingCallbacks.getTextCursorPositionFromPoint(pFont->internalFont, text, textSizeInBytes, maxWidth, inputPosX, pTextCursorPosXOut, pCharacterIndexOut); + } + + return false; +} + +bool drgui_get_text_cursor_position_from_char(drgui_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut) +{ + if (pFont == NULL) { + return false; + } + + assert(pFont->pContext != NULL); + + if (pFont->pContext->paintingCallbacks.getTextCursorPositionFromChar) { + return pFont->pContext->paintingCallbacks.getTextCursorPositionFromChar(pFont->internalFont, text, characterIndex, pTextCursorPosXOut); + } + + return false; +} + + + +drgui_image* drgui_create_image(drgui_context* pContext, unsigned int width, unsigned int height, drgui_image_format format, unsigned int stride, const void* pData) +{ + if (pContext == NULL) { + return NULL; + } + + if (pContext->paintingCallbacks.createImage == NULL) { + return NULL; + } + + + // If the stride is 0, assume tightly packed. + if (stride == 0) { + stride = width * 4; + } + + + drgui_resource internalImage = pContext->paintingCallbacks.createImage(pContext->pPaintingContext, width, height, format, stride, pData); + if (internalImage == NULL) { + return NULL; + } + + drgui_image* pImage = (drgui_image*)malloc(sizeof(*pImage)); + if (pImage == NULL) { + return NULL; + } + + pImage->pContext = pContext; + pImage->hResource = internalImage; + + + return pImage; +} + +void drgui_delete_image(drgui_image* pImage) +{ + if (pImage == NULL) { + return; + } + + assert(pImage->pContext != NULL); + + // Delete the internal font object. + if (pImage->pContext->paintingCallbacks.deleteImage) { + pImage->pContext->paintingCallbacks.deleteImage(pImage->hResource); + } + + // Free the font object last. + free(pImage); +} + +void drgui_get_image_size(drgui_image* pImage, unsigned int* pWidthOut, unsigned int* pHeightOut) +{ + if (pWidthOut) *pWidthOut = 0; + if (pHeightOut) *pHeightOut = 0; + + if (pImage == NULL) { + return; + } + + assert(pImage->pContext != NULL); + + if (pImage->pContext->paintingCallbacks.getImageSize == NULL) { + return; + } + + pImage->pContext->paintingCallbacks.getImageSize(pImage->hResource, pWidthOut, pHeightOut); +} + +drgui_image_format drgui_get_optimal_image_format(drgui_context* pContext) +{ + if (pContext == NULL || pContext->paintingCallbacks.getOptimalImageFormat == NULL) { + return drgui_image_format_rgba8; + } + + return pContext->paintingCallbacks.getOptimalImageFormat(pContext->pPaintingContext); +} + +void* drgui_map_image_data(drgui_image* pImage, unsigned int accessFlags) +{ + if (pImage == NULL) { + return NULL; + } + + if (pImage->pContext->paintingCallbacks.mapImageData == NULL || pImage->pContext->paintingCallbacks.unmapImageData == NULL) { + return NULL; + } + + return pImage->pContext->paintingCallbacks.mapImageData(pImage->hResource, accessFlags); +} + +void drgui_unmap_image_data(drgui_image* pImage) +{ + if (pImage == NULL) { + return; + } + + if (pImage->pContext->paintingCallbacks.unmapImageData == NULL) { + return; + } + + pImage->pContext->paintingCallbacks.unmapImageData(pImage->hResource); +} + + + +///////////////////////////////////////////////////////////////// +// +// HIGH-LEVEL API +// +///////////////////////////////////////////////////////////////// + +//// Hit Testing and Layout //// + +void drgui_on_size_fit_children_to_parent(drgui_element* pElement, float newWidth, float newHeight) +{ + for (drgui_element* pChild = pElement->pFirstChild; pChild != NULL; pChild = pChild->pNextSibling) { + drgui_set_size(pChild, newWidth, newHeight); + } +} + +bool drgui_pass_through_hit_test(drgui_element* pElement, float mousePosX, float mousePosY) +{ + (void)pElement; + (void)mousePosX; + (void)mousePosY; + + return false; +} + + + +//// Painting //// + +void drgui_draw_border(drgui_element* pElement, float borderWidth, drgui_color color, void* pUserData) +{ + drgui_draw_rect_outline(pElement, drgui_get_local_rect(pElement), color, borderWidth, pUserData); +} + + + +///////////////////////////////////////////////////////////////// +// +// UTILITY API +// +///////////////////////////////////////////////////////////////// + +drgui_color drgui_rgba(drgui_byte r, drgui_byte g, drgui_byte b, drgui_byte a) +{ + drgui_color color; + color.r = r; + color.g = g; + color.b = b; + color.a = a; + + return color; +} + +drgui_color drgui_rgb(drgui_byte r, drgui_byte g, drgui_byte b) +{ + drgui_color color; + color.r = r; + color.g = g; + color.b = b; + color.a = 255; + + return color; +} + +drgui_rect drgui_clamp_rect(drgui_rect rect, drgui_rect other) +{ + drgui_rect result; + result.left = (rect.left >= other.left) ? rect.left : other.left; + result.top = (rect.top >= other.top) ? rect.top : other.top; + result.right = (rect.right <= other.right) ? rect.right : other.right; + result.bottom = (rect.bottom <= other.bottom) ? rect.bottom : other.bottom; + + return result; +} + +bool drgui_clamp_rect_to_element(const drgui_element* pElement, drgui_rect* pRelativeRect) +{ + if (pElement == NULL || pRelativeRect == NULL) { + return false; + } + + + if (pRelativeRect->left < 0) { + pRelativeRect->left = 0; + } + if (pRelativeRect->top < 0) { + pRelativeRect->top = 0; + } + + if (pRelativeRect->right > pElement->width) { + pRelativeRect->right = pElement->width; + } + if (pRelativeRect->bottom > pElement->height) { + pRelativeRect->bottom = pElement->height; + } + + + return (pRelativeRect->right - pRelativeRect->left > 0) && (pRelativeRect->bottom - pRelativeRect->top > 0); +} + +drgui_rect drgui_make_rect_relative(const drgui_element* pElement, drgui_rect* pRect) +{ + if (pElement == NULL || pRect == NULL) { + return drgui_make_rect(0, 0, 0, 0); + } + + pRect->left -= pElement->absolutePosX; + pRect->top -= pElement->absolutePosY; + pRect->right -= pElement->absolutePosX; + pRect->bottom -= pElement->absolutePosY; + + return *pRect; +} + +drgui_rect drgui_make_rect_absolute(const drgui_element * pElement, drgui_rect * pRect) +{ + if (pElement == NULL || pRect == NULL) { + return drgui_make_rect(0, 0, 0, 0); + } + + pRect->left += pElement->absolutePosX; + pRect->top += pElement->absolutePosY; + pRect->right += pElement->absolutePosX; + pRect->bottom += pElement->absolutePosY; + + return *pRect; +} + +void drgui_make_point_relative(const drgui_element* pElement, float* positionX, float* positionY) +{ + if (pElement != NULL) + { + if (positionX != NULL) { + *positionX -= pElement->absolutePosX; + } + + if (positionY != NULL) { + *positionY -= pElement->absolutePosY; + } + } +} + +void drgui_make_point_absolute(const drgui_element* pElement, float* positionX, float* positionY) +{ + if (pElement != NULL) + { + if (positionX != NULL) { + *positionX += pElement->absolutePosX; + } + + if (positionY != NULL) { + *positionY += pElement->absolutePosY; + } + } +} + +drgui_rect drgui_make_rect(float left, float top, float right, float bottom) +{ + drgui_rect rect; + rect.left = left; + rect.top = top; + rect.right = right; + rect.bottom = bottom; + + return rect; +} + +drgui_rect drgui_make_inside_out_rect() +{ + drgui_rect rect; + rect.left = FLT_MAX; + rect.top = FLT_MAX; + rect.right = -FLT_MAX; + rect.bottom = -FLT_MAX; + + return rect; +} + +drgui_rect drgui_grow_rect(drgui_rect rect, float amount) +{ + drgui_rect result = rect; + result.left -= amount; + result.top -= amount; + result.right += amount; + result.bottom += amount; + + return result; +} + +drgui_rect drgui_scale_rect(drgui_rect rect, float scaleX, float scaleY) +{ + drgui_rect result = rect; + result.left *= scaleX; + result.top *= scaleY; + result.right *= scaleX; + result.bottom *= scaleY; + + return result; +} + +drgui_rect drgui_offset_rect(drgui_rect rect, float offsetX, float offsetY) +{ + return drgui_make_rect(rect.left + offsetX, rect.top + offsetY, rect.right + offsetX, rect.bottom + offsetY); +} + +drgui_rect drgui_rect_union(drgui_rect rect0, drgui_rect rect1) +{ + drgui_rect result; + result.left = (rect0.left < rect1.left) ? rect0.left : rect1.left; + result.top = (rect0.top < rect1.top) ? rect0.top : rect1.top; + result.right = (rect0.right > rect1.right) ? rect0.right : rect1.right; + result.bottom = (rect0.bottom > rect1.bottom) ? rect0.bottom : rect1.bottom; + + return result; +} + +bool drgui_rect_contains_point(drgui_rect rect, float posX, float posY) +{ + if (posX < rect.left || posY < rect.top) { + return false; + } + + if (posX >= rect.right || posY >= rect.bottom) { + return false; + } + + return true; +} + +bool drgui_rect_equal(drgui_rect rect0, drgui_rect rect1) +{ + return + rect0.left == rect1.left && + rect0.top == rect1.top && + rect0.right == rect1.right && + rect0.bottom == rect1.bottom; +} + +bool drgui_rect_has_volume(drgui_rect rect) +{ + return rect.right > rect.left && rect.bottom > rect.top; +} + + + + +///////////////////////////////////////////////////////////////// +// +// EASY_DRAW-SPECIFIC API +// +///////////////////////////////////////////////////////////////// +#ifndef DRGUI_NO_DR_2D + +void drgui_draw_begin_dr_2d(void* pPaintData); +void drgui_draw_end_dr_2d(void* pPaintData); +void drgui_set_clip_dr_2d(drgui_rect rect, void* pPaintData); +void drgui_get_clip_dr_2d(drgui_rect* pRectOut, void* pPaintData); +void drgui_draw_rect_dr_2d(drgui_rect rect, drgui_color color, void* pPaintData); +void drgui_draw_rect_outline_dr_2d(drgui_rect, drgui_color, float, void*); +void drgui_draw_rect_with_outline_dr_2d(drgui_rect, drgui_color, float, drgui_color, void*); +void drgui_draw_round_rect_dr_2d(drgui_rect, drgui_color, float, void*); +void drgui_draw_round_rect_outline_dr_2d(drgui_rect, drgui_color, float, float, void*); +void drgui_draw_round_rect_with_outline_dr_2d(drgui_rect, drgui_color, float, float, drgui_color, void*); +void drgui_draw_text_dr_2d(drgui_resource, const char*, int, float, float, drgui_color, drgui_color, void*); +void drgui_draw_image_dr_2d(drgui_resource image, drgui_draw_image_args* pArgs, void* pPaintData); + +drgui_resource drgui_create_font_dr_2d(void*, const char*, unsigned int, drgui_font_weight, drgui_font_slant, float, unsigned int flags); +void drgui_delete_font_dr_2d(drgui_resource); +unsigned int drgui_get_font_size_dr_2d(drgui_resource hFont); +bool drgui_get_font_metrics_dr_2d(drgui_resource, drgui_font_metrics*); +bool drgui_get_glyph_metrics_dr_2d(drgui_resource, unsigned int, drgui_glyph_metrics*); +bool drgui_measure_string_dr_2d(drgui_resource, const char*, size_t, float*, float*); +bool drgui_get_text_cursor_position_from_point_dr_2d(drgui_resource font, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut); +bool drgui_get_text_cursor_position_from_char_dr_2d(drgui_resource font, const char* text, size_t characterIndex, float* pTextCursorPosXOut); + +drgui_resource drgui_create_image_dr_2d(void* pPaintingContext, unsigned int width, unsigned int height, drgui_image_format format, unsigned int stride, const void* pImageData); +void drgui_delete_image_dr_2d(drgui_resource image); +void drgui_get_image_size_dr_2d(drgui_resource image, unsigned int* pWidthOut, unsigned int* pHeightOut); +drgui_image_format drgui_get_optimal_image_format_dr_2d(void* pPaintingContext); +void* drgui_map_image_data_dr_2d(drgui_resource image, unsigned int accessFlags); +void drgui_unmap_image_data_dr_2d(drgui_resource image); + +drgui_context* drgui_create_context_dr_2d(dr2d_context* pDrawingContext) +{ + drgui_context* pContext = drgui_create_context(); + if (pContext != NULL) { + drgui_register_dr_2d_callbacks(pContext, pDrawingContext); + } + + return pContext; +} + +void drgui_register_dr_2d_callbacks(drgui_context* pContext, dr2d_context* pDrawingContext) +{ + drgui_painting_callbacks callbacks; + callbacks.drawBegin = drgui_draw_begin_dr_2d; + callbacks.drawEnd = drgui_draw_end_dr_2d; + callbacks.setClip = drgui_set_clip_dr_2d; + callbacks.getClip = drgui_get_clip_dr_2d; + callbacks.drawRect = drgui_draw_rect_dr_2d; + callbacks.drawRectOutline = drgui_draw_rect_outline_dr_2d; + callbacks.drawRectWithOutline = drgui_draw_rect_with_outline_dr_2d; + callbacks.drawRoundRect = drgui_draw_round_rect_dr_2d; + callbacks.drawRoundRectOutline = drgui_draw_round_rect_outline_dr_2d; + callbacks.drawRoundRectWithOutline = drgui_draw_round_rect_with_outline_dr_2d; + callbacks.drawText = drgui_draw_text_dr_2d; + callbacks.drawImage = drgui_draw_image_dr_2d; + + callbacks.createFont = drgui_create_font_dr_2d; + callbacks.deleteFont = drgui_delete_font_dr_2d; + callbacks.getFontSize = drgui_get_font_size_dr_2d; + callbacks.getFontMetrics = drgui_get_font_metrics_dr_2d; + callbacks.getGlyphMetrics = drgui_get_glyph_metrics_dr_2d; + callbacks.measureString = drgui_measure_string_dr_2d; + + callbacks.createImage = drgui_create_image_dr_2d; + callbacks.deleteImage = drgui_delete_image_dr_2d; + callbacks.getImageSize = drgui_get_image_size_dr_2d; + callbacks.getOptimalImageFormat = drgui_get_optimal_image_format_dr_2d; + callbacks.mapImageData = drgui_map_image_data_dr_2d; + callbacks.unmapImageData = drgui_unmap_image_data_dr_2d; + + callbacks.getTextCursorPositionFromPoint = drgui_get_text_cursor_position_from_point_dr_2d; + callbacks.getTextCursorPositionFromChar = drgui_get_text_cursor_position_from_char_dr_2d; + + drgui_register_painting_callbacks(pContext, pDrawingContext, callbacks); +} + + +void drgui_draw_begin_dr_2d(void* pPaintData) +{ + dr2d_surface* pSurface = (dr2d_surface*)pPaintData; + assert(pSurface != NULL); + + dr2d_begin_draw(pSurface); +} + +void drgui_draw_end_dr_2d(void* pPaintData) +{ + dr2d_surface* pSurface = (dr2d_surface*)pPaintData; + assert(pSurface != NULL); + + dr2d_end_draw(pSurface); +} + +void drgui_set_clip_dr_2d(drgui_rect rect, void* pPaintData) +{ + dr2d_surface* pSurface = (dr2d_surface*)pPaintData; + assert(pSurface != NULL); + + dr2d_set_clip(pSurface, rect.left, rect.top, rect.right, rect.bottom); +} + +void drgui_get_clip_dr_2d(drgui_rect* pRectOut, void* pPaintData) +{ + assert(pRectOut != NULL); + + dr2d_surface* pSurface = (dr2d_surface*)pPaintData; + assert(pSurface != NULL); + + dr2d_get_clip(pSurface, &pRectOut->left, &pRectOut->top, &pRectOut->right, &pRectOut->bottom); +} + +void drgui_draw_rect_dr_2d(drgui_rect rect, drgui_color color, void* pPaintData) +{ + dr2d_surface* pSurface = (dr2d_surface*)pPaintData; + assert(pSurface != NULL); + + dr2d_draw_rect(pSurface, rect.left, rect.top, rect.right, rect.bottom, dr2d_rgba(color.r, color.g, color.b, color.a)); +} + +void drgui_draw_rect_outline_dr_2d(drgui_rect rect, drgui_color color, float outlineWidth, void* pPaintData) +{ + dr2d_surface* pSurface = (dr2d_surface*)pPaintData; + assert(pSurface != NULL); + + dr2d_draw_rect_outline(pSurface, rect.left, rect.top, rect.right, rect.bottom, dr2d_rgba(color.r, color.g, color.b, color.a), outlineWidth); +} + +void drgui_draw_rect_with_outline_dr_2d(drgui_rect rect, drgui_color color, float outlineWidth, drgui_color outlineColor, void* pPaintData) +{ + dr2d_surface* pSurface = (dr2d_surface*)pPaintData; + assert(pSurface != NULL); + + dr2d_draw_rect_with_outline(pSurface, rect.left, rect.top, rect.right, rect.bottom, dr2d_rgba(color.r, color.g, color.b, color.a), outlineWidth, dr2d_rgba(outlineColor.r, outlineColor.g, outlineColor.b, outlineColor.a)); +} + +void drgui_draw_round_rect_dr_2d(drgui_rect rect, drgui_color color, float radius, void* pPaintData) +{ + dr2d_surface* pSurface = (dr2d_surface*)pPaintData; + assert(pSurface != NULL); + + dr2d_draw_round_rect(pSurface, rect.left, rect.top, rect.right, rect.bottom, dr2d_rgba(color.r, color.g, color.b, color.a), radius); +} + +void drgui_draw_round_rect_outline_dr_2d(drgui_rect rect, drgui_color color, float radius, float outlineWidth, void* pPaintData) +{ + dr2d_surface* pSurface = (dr2d_surface*)pPaintData; + assert(pSurface != NULL); + + dr2d_draw_round_rect_outline(pSurface, rect.left, rect.top, rect.right, rect.bottom, dr2d_rgba(color.r, color.g, color.b, color.a), radius, outlineWidth); +} + +void drgui_draw_round_rect_with_outline_dr_2d(drgui_rect rect, drgui_color color, float radius, float outlineWidth, drgui_color outlineColor, void* pPaintData) +{ + dr2d_surface* pSurface = (dr2d_surface*)pPaintData; + assert(pSurface != NULL); + + dr2d_draw_round_rect_with_outline(pSurface, rect.left, rect.top, rect.right, rect.bottom, dr2d_rgba(color.r, color.g, color.b, color.a), radius, outlineWidth, dr2d_rgba(outlineColor.r, outlineColor.g, outlineColor.b, outlineColor.a)); +} + +void drgui_draw_text_dr_2d(drgui_resource font, const char* text, int textSizeInBytes, float posX, float posY, drgui_color color, drgui_color backgroundColor, void* pPaintData) +{ + dr2d_surface* pSurface = (dr2d_surface*)pPaintData; + assert(pSurface != NULL); + + dr2d_draw_text(pSurface, (dr2d_font*)font, text, textSizeInBytes, posX, posY, dr2d_rgba(color.r, color.g, color.b, color.a), dr2d_rgba(backgroundColor.r, backgroundColor.g, backgroundColor.b, backgroundColor.a)); +} + +void drgui_draw_image_dr_2d(drgui_resource image, drgui_draw_image_args* pArgs, void* pPaintData) +{ + dr2d_surface* pSurface = (dr2d_surface*)pPaintData; + assert(pSurface != NULL); + + dr2d_draw_image_args args; + args.dstX = pArgs->dstX; + args.dstY = pArgs->dstY; + args.dstWidth = pArgs->dstWidth; + args.dstHeight = pArgs->dstHeight; + args.srcX = pArgs->srcX; + args.srcY = pArgs->srcY; + args.srcWidth = pArgs->srcWidth; + args.srcHeight = pArgs->srcHeight; + args.foregroundTint = dr2d_rgba(pArgs->foregroundTint.r, pArgs->foregroundTint.g, pArgs->foregroundTint.b, pArgs->foregroundTint.a); + args.backgroundColor = dr2d_rgba(pArgs->backgroundColor.r, pArgs->backgroundColor.g, pArgs->backgroundColor.b, pArgs->backgroundColor.a); + args.options = pArgs->options; + dr2d_draw_image(pSurface, (dr2d_image*)image, &args); +} + + +drgui_resource drgui_create_font_dr_2d(void* pPaintingContext, const char* family, unsigned int size, drgui_font_weight weight, drgui_font_slant slant, float rotation, unsigned int flags) +{ + return dr2d_create_font((dr2d_context*)pPaintingContext, family, size, (dr2d_font_weight)weight, (dr2d_font_slant)slant, rotation, flags); +} + +void drgui_delete_font_dr_2d(drgui_resource font) +{ + dr2d_delete_font((dr2d_font*)font); +} + +unsigned int drgui_get_font_size_dr_2d(drgui_resource font) +{ + return dr2d_get_font_size((dr2d_font*)font); +} + +bool drgui_get_font_metrics_dr_2d(drgui_resource font, drgui_font_metrics* pMetricsOut) +{ + assert(pMetricsOut != NULL); + + dr2d_font_metrics metrics; + if (!dr2d_get_font_metrics((dr2d_font*)font, &metrics)) { + return false; + } + + pMetricsOut->ascent = metrics.ascent; + pMetricsOut->descent = metrics.descent; + pMetricsOut->lineHeight = metrics.lineHeight; + pMetricsOut->spaceWidth = metrics.spaceWidth; + + return true; +} + +bool drgui_get_glyph_metrics_dr_2d(drgui_resource font, unsigned int utf32, drgui_glyph_metrics* pMetricsOut) +{ + assert(pMetricsOut != NULL); + + dr2d_glyph_metrics metrics; + if (!dr2d_get_glyph_metrics((dr2d_font*)font, utf32, &metrics)) { + return false; + } + + pMetricsOut->width = metrics.width; + pMetricsOut->height = metrics.height; + pMetricsOut->originX = metrics.originX; + pMetricsOut->originY = metrics.originY; + pMetricsOut->advanceX = metrics.advanceX; + pMetricsOut->advanceY = metrics.advanceY; + + return true; +} + +bool drgui_measure_string_dr_2d(drgui_resource font, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut) +{ + return dr2d_measure_string((dr2d_font*)font, text, textSizeInBytes, pWidthOut, pHeightOut); +} + +bool drgui_get_text_cursor_position_from_point_dr_2d(drgui_resource font, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut) +{ + return dr2d_get_text_cursor_position_from_point((dr2d_font*)font, text, textSizeInBytes, maxWidth, inputPosX, pTextCursorPosXOut, pCharacterIndexOut); +} + +bool drgui_get_text_cursor_position_from_char_dr_2d(drgui_resource font, const char* text, size_t characterIndex, float* pTextCursorPosXOut) +{ + return dr2d_get_text_cursor_position_from_char((dr2d_font*)font, text, characterIndex, pTextCursorPosXOut); +} + + +drgui_resource drgui_create_image_dr_2d(void* pPaintingContext, unsigned int width, unsigned int height, drgui_image_format format, unsigned int stride, const void* pImageData) +{ + dr2d_image_format dr2dFormat; + switch (format) + { + case drgui_image_format_bgra8: dr2dFormat = dr2d_image_format_bgra8; break; + case drgui_image_format_argb8: dr2dFormat = dr2d_image_format_argb8; break; + default: dr2dFormat = dr2d_image_format_rgba8; + } + + return dr2d_create_image((dr2d_context*)pPaintingContext, width, height, dr2dFormat, stride, pImageData); +} + +void drgui_delete_image_dr_2d(drgui_resource image) +{ + dr2d_delete_image((dr2d_image*)image); +} + +void drgui_get_image_size_dr_2d(drgui_resource image, unsigned int* pWidthOut, unsigned int* pHeightOut) +{ + dr2d_get_image_size((dr2d_image*)image, pWidthOut, pHeightOut); +} + +drgui_image_format drgui_get_optimal_image_format_dr_2d(void* pPaintingContext) +{ + return (drgui_image_format)dr2d_get_optimal_image_format((dr2d_context*)pPaintingContext); +} + +void* drgui_map_image_data_dr_2d(drgui_resource image, unsigned int accessFlags) +{ + return dr2d_map_image_data((dr2d_image*)image, accessFlags); +} + +void drgui_unmap_image_data_dr_2d(drgui_resource image) +{ + dr2d_unmap_image_data((dr2d_image*)image); +} + +#endif //DRGUI_NO_DR_2D +#endif //DR_GUI_IMPLEMENTATION + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// Text Engine +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef DRGUI_NO_TEXT_EDITING +// QUICK NOTES +// +// - Text engines are used to make it easier to manage the layout of a block of text. +// - Text engines support basic editing which requires inbound events to be posted from the higher level application. +// - Text engines are not GUI elements. They are lower level objects that are used by higher level GUI elements. +// - Text engines normalize line endings to \n format. Keep this in mind when retrieving the text of a layout. +// - Text engine use the notion of a container which is used for determining which text runs are visible. + +#ifndef drgui_text_engine_h +#define drgui_text_engine_h + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct drgui_text_engine drgui_text_engine; + +typedef enum +{ + drgui_text_engine_alignment_left, + drgui_text_engine_alignment_top, + drgui_text_engine_alignment_center, + drgui_text_engine_alignment_right, + drgui_text_engine_alignment_bottom, +} drgui_text_engine_alignment; + +typedef struct +{ + /// A pointer to the start of the string. This is NOT null terminated. + const char* text; + + /// The length of the string, in bytes. + size_t textLength; + + + /// The font. + drgui_font* pFont; + + /// The foreground color of the text. + drgui_color textColor; + + /// The backgorund color of the text. + drgui_color backgroundColor; + + + /// The position to draw the text on the x axis. + float posX; + + /// The position to draw the text on the y axis. + float posY; + + /// The width of the run. + float width; + + /// The height of the run. + float height; + + + // PROPERTIES BELOW ARE FOR INTERNAL USE ONLY + + /// Index of the line the run is placed on. For runs that are new line characters, this will represent the number of lines that came before it. For + /// example, if this run represents the new-line character for the first line, this will be 0 and so on. + size_t iLine; + + /// Index in the main text string of the first character of the run. + size_t iChar; + + /// Index in the main text string of the character just past the last character in the run. + size_t iCharEnd; + +} drgui_text_run; + +typedef void (* drgui_text_engine_on_paint_text_proc) (drgui_text_engine* pTL, drgui_text_run* pRun, drgui_element* pElement, void* pPaintData); +typedef void (* drgui_text_engine_on_paint_rect_proc) (drgui_text_engine* pTL, drgui_rect rect, drgui_color color, drgui_element* pElement, void* pPaintData); +typedef void (* drgui_text_engine_on_cursor_move_proc) (drgui_text_engine* pTL); +typedef void (* drgui_text_engine_on_dirty_proc) (drgui_text_engine* pTL, drgui_rect rect); +typedef void (* drgui_text_engine_on_text_changed_proc) (drgui_text_engine* pTL); +typedef void (* drgui_text_engine_on_undo_point_changed_proc)(drgui_text_engine* pTL, unsigned int iUndoPoint); + + +/// Creates a new text engine object. +drgui_text_engine* drgui_create_text_engine(drgui_context* pContext, size_t extraDataSize, void* pExtraData); + +/// Deletes the given text engine. +void drgui_delete_text_engine(drgui_text_engine* pTL); + + +/// Retrieves the size of the extra data associated with the given text engine. +size_t drgui_text_engine_get_extra_data_size(drgui_text_engine* pTL); + +/// Retrieves a pointer to the extra data associated with the given text engine. +void* drgui_text_engine_get_extra_data(drgui_text_engine* pTL); + + +/// Sets the given text engine's text. +void drgui_text_engine_set_text(drgui_text_engine* pTL, const char* text); + +/// Retrieves the given text engine's text. +/// +/// @return The length of the string, not including the null terminator. +/// +/// @remarks +/// Call this function with set to NULL to retieve the required size of . +size_t drgui_text_engine_get_text(drgui_text_engine* pTL, char* textOut, size_t textOutSize); + + +/// Sets the function to call when a region of the text engine needs to be redrawn. +void drgui_text_engine_set_on_dirty(drgui_text_engine* pTL, drgui_text_engine_on_dirty_proc proc); + +/// Sets the function to call when the content of the given text engine has changed. +void drgui_text_engine_set_on_text_changed(drgui_text_engine* pTL, drgui_text_engine_on_text_changed_proc proc); + +/// Sets the function to call when the content of the given text engine's current undo point has moved. +void drgui_text_engine_set_on_undo_point_changed(drgui_text_engine* pTL, drgui_text_engine_on_undo_point_changed_proc proc); + + +/// Sets the size of the container. +void drgui_text_engine_set_container_size(drgui_text_engine* pTL, float containerWidth, float containerHeight); + +/// Retrieves the size of the container. +void drgui_text_engine_get_container_size(drgui_text_engine* pTL, float* pContainerWidthOut, float* pContainerHeightOut); + +/// Retrieves the width of the container. +float drgui_text_engine_get_container_width(drgui_text_engine* pTL); + +/// Retrieves the height of the container. +float drgui_text_engine_get_container_height(drgui_text_engine* pTL); + + +/// Sets the inner offset of the given text engine. +void drgui_text_engine_set_inner_offset(drgui_text_engine* pTL, float innerOffsetX, float innerOffsetY); + +/// Sets the inner offset of the given text engine on the x axis. +void drgui_text_engine_set_inner_offset_x(drgui_text_engine* pTL, float innerOffsetX); + +/// Sets the inner offset of the given text engine on the y axis. +void drgui_text_engine_set_inner_offset_y(drgui_text_engine* pTL, float innerOffsetY); + +/// Retrieves the inner offset of the given text engine. +void drgui_text_engine_get_inner_offset(drgui_text_engine* pTL, float* pInnerOffsetX, float* pInnerOffsetY); + +/// Retrieves the inner offset of the given text engine on the x axis. +float drgui_text_engine_get_inner_offset_x(drgui_text_engine* pTL); + +/// Retrieves the inner offset of the given text engine on the x axis. +float drgui_text_engine_get_inner_offset_y(drgui_text_engine* pTL); + + +/// Sets the default font to use for text runs. +void drgui_text_engine_set_default_font(drgui_text_engine* pTL, drgui_font* pFont); + +/// Retrieves the default font to use for text runs. +drgui_font* drgui_text_engine_get_default_font(drgui_text_engine* pTL); + +/// Sets the default text color of the given text engine. +void drgui_text_engine_set_default_text_color(drgui_text_engine* pTL, drgui_color color); + +/// Retrieves the default text color of the given text engine. +drgui_color drgui_text_engine_get_default_text_color(drgui_text_engine* pTL); + +/// Sets the default background color of the given text engine. +void drgui_text_engine_set_default_bg_color(drgui_text_engine* pTL, drgui_color color); + +/// Retrieves the default background color of the given text engine. +drgui_color drgui_text_engine_get_default_bg_color(drgui_text_engine* pTL); + +/// Sets the background color of selected text. +void drgui_text_engine_set_selection_bg_color(drgui_text_engine* pTL, drgui_color color); + +/// Retrieves the background color of selected text. +drgui_color drgui_text_engine_get_selection_bg_color(drgui_text_engine* pTL); + +/// Sets the background color of the line the cursor is sitting on. +void drgui_text_engine_set_active_line_bg_color(drgui_text_engine* pTL, drgui_color color); + +/// Retrieves the background color of the line the cursor is sitting on. +drgui_color drgui_text_engine_get_active_line_bg_color(drgui_text_engine* pTL); + + +/// Sets the size of a tab in spaces. +void drgui_text_engine_set_tab_size(drgui_text_engine* pTL, unsigned int sizeInSpaces); + +/// Retrieves the size of a tab in spaces. +unsigned int drgui_text_engine_get_tab_size(drgui_text_engine* pTL); + + +/// Sets the horizontal alignment of the given text engine. +void drgui_text_engine_set_horizontal_align(drgui_text_engine* pTL, drgui_text_engine_alignment alignment); + +/// Retrieves the horizontal aligment of the given text engine. +drgui_text_engine_alignment drgui_text_engine_get_horizontal_align(drgui_text_engine* pTL); + +/// Sets the vertical alignment of the given text engine. +void drgui_text_engine_set_vertical_align(drgui_text_engine* pTL, drgui_text_engine_alignment alignment); + +/// Retrieves the vertical aligment of the given text engine. +drgui_text_engine_alignment drgui_text_engine_get_vertical_align(drgui_text_engine* pTL); + + +/// Retrieves the rectangle of the text relative to the bounds, taking alignment into account. +drgui_rect drgui_text_engine_get_text_rect_relative_to_bounds(drgui_text_engine* pTL); + + +/// Sets the width of the text cursor. +void drgui_text_engine_set_cursor_width(drgui_text_engine* pTL, float cursorWidth); + +/// Retrieves the width of the text cursor. +float drgui_text_engine_get_cursor_width(drgui_text_engine* pTL); + +/// Sets the color of the text cursor. +void drgui_text_engine_set_cursor_color(drgui_text_engine* pTL, drgui_color cursorColor); + +/// Retrieves the color of the text cursor. +drgui_color drgui_text_engine_get_cursor_color(drgui_text_engine* pTL); + +/// Sets the blink rate of the cursor in milliseconds. +void drgui_text_engine_set_cursor_blink_rate(drgui_text_engine* pTL, unsigned int blinkRateInMilliseconds); + +/// Retrieves the blink rate of the cursor in milliseconds. +unsigned int drgui_text_engine_get_cursor_blink_rate(drgui_text_engine* pTL); + +/// Shows the cursor. +void drgui_text_engine_show_cursor(drgui_text_engine* pTL); + +/// Hides the cursor. +void drgui_text_engine_hide_cursor(drgui_text_engine* pTL); + +/// Determines whether or not the cursor is visible. +bool drgui_text_engine_is_showing_cursor(drgui_text_engine* pTL); + +/// Retrieves the position of the cursor, relative to the container. +void drgui_text_engine_get_cursor_position(drgui_text_engine* pTL, float* pPosXOut, float* pPosYOut); + +/// Retrieves the rectangle of the cursor, relative to the container. +drgui_rect drgui_text_engine_get_cursor_rect(drgui_text_engine* pTL); + +/// Retrieves the index of the line the cursor is currently sitting on. +size_t drgui_text_engine_get_cursor_line(drgui_text_engine* pTL); + +/// Retrieves the index of the column the cursor is currently sitting on. +size_t drgui_text_engine_get_cursor_column(drgui_text_engine* pTL); + +/// Retrieves the index of the character the cursor is currently sitting on. +size_t drgui_text_engine_get_cursor_character(drgui_text_engine* pTL); + +/// Moves the cursor to the closest character based on the given input position. +void drgui_text_engine_move_cursor_to_point(drgui_text_engine* pTL, float posX, float posY); + +/// Moves the cursor of the given text engine to the left by one character. +bool drgui_text_engine_move_cursor_left(drgui_text_engine* pTL); + +/// Moves the cursor of the given text engine to the right by one character. +bool drgui_text_engine_move_cursor_right(drgui_text_engine* pTL); + +/// Moves the cursor of the given text engine up one line. +bool drgui_text_engine_move_cursor_up(drgui_text_engine* pTL); + +/// Moves the cursor of the given text engine down one line. +bool drgui_text_engine_move_cursor_down(drgui_text_engine* pTL); + +/// Moves the cursor up or down the given number of lines. +bool drgui_text_engine_move_cursor_y(drgui_text_engine* pTL, int amount); + +/// Moves the cursor of the given text engine to the end of the line. +bool drgui_text_engine_move_cursor_to_end_of_line(drgui_text_engine* pTL); + +/// Moves the cursor of the given text engine to the start of the line. +bool drgui_text_engine_move_cursor_to_start_of_line(drgui_text_engine* pTL); + +/// Moves the cursor of the given text engine to the end of the line at the given index. +bool drgui_text_engine_move_cursor_to_end_of_line_by_index(drgui_text_engine* pTL, size_t iLine); + +/// Moves the cursor of the given text engine to the start of the line at the given index. +bool drgui_text_engine_move_cursor_to_start_of_line_by_index(drgui_text_engine* pTL, size_t iLine); + +/// Moves the cursor of the given text engine to the end of the text. +bool drgui_text_engine_move_cursor_to_end_of_text(drgui_text_engine* pTL); + +/// Moves the cursor of the given text engine to the end of the text. +bool drgui_text_engine_move_cursor_to_start_of_text(drgui_text_engine* pTL); + +/// Moves the cursor to the start of the selected text. +void drgui_text_engine_move_cursor_to_start_of_selection(drgui_text_engine* pTL); + +/// Moves the cursor to the end of the selected text. +void drgui_text_engine_move_cursor_to_end_of_selection(drgui_text_engine* pTL); + +/// Moves the cursor to the given character index. +void drgui_text_engine_move_cursor_to_character(drgui_text_engine* pTL, size_t characterIndex); + +/// Determines whether or not the cursor is sitting at the start of the selection. +bool drgui_text_engine_is_cursor_at_start_of_selection(drgui_text_engine* pTL); + +/// Determines whether or not the cursor is sitting at the end fo the selection. +bool drgui_text_engine_is_cursor_at_end_of_selection(drgui_text_engine* pTL); + +/// Swaps the position of the cursor based on the current selection. +void drgui_text_engine_swap_selection_markers(drgui_text_engine* pTL); + +/// Sets the function to call when the cursor in the given text engine is mvoed. +void drgui_text_engine_set_on_cursor_move(drgui_text_engine* pTL, drgui_text_engine_on_cursor_move_proc proc); + +/// Refreshes the cursor and selection marker positions. +void drgui_text_engine_refresh_markers(drgui_text_engine* pTL); + + +/// Inserts a character into the given text engine. +/// +/// @return True if the text within the text engine has changed. +bool drgui_text_engine_insert_character(drgui_text_engine* pTL, unsigned int character, size_t insertIndex); + +/// Inserts the given string at the given character index. +/// +/// @return True if the text within the text engine has changed. +bool drgui_text_engine_insert_text(drgui_text_engine* pTL, const char* text, size_t insertIndex); + +/// Deletes a range of text in the given text engine. +/// +/// @return True if the text within the text engine has changed. +bool drgui_text_engine_delete_text_range(drgui_text_engine* pTL, size_t iFirstCh, size_t iLastChPlus1); + +/// Inserts a character at the position of the cursor. +/// +/// @return True if the text within the text engine has changed. +bool drgui_text_engine_insert_character_at_cursor(drgui_text_engine* pTL, unsigned int character); + +/// Inserts a character at the position of the cursor. +/// +/// @return True if the text within the text engine has changed. +bool drgui_text_engine_insert_text_at_cursor(drgui_text_engine* pTL, const char* text); + +/// Deletes the character to the left of the cursor. +/// +/// @return True if the text within the text engine has changed. +bool drgui_text_engine_delete_character_to_left_of_cursor(drgui_text_engine* pTL); + +/// Deletes the character to the right of the cursor. +/// +/// @return True if the text within the text engine has changed. +bool drgui_text_engine_delete_character_to_right_of_cursor(drgui_text_engine* pTL); + +/// Deletes the currently selected text. +/// +/// @return True if the text within the text engine has changed. +bool drgui_text_engine_delete_selected_text(drgui_text_engine* pTL); + + +/// Enter's into selection mode. +/// +/// @remarks +/// An application will typically enter selection mode when the Shift key is pressed, and then leave when the key is released. +/// @par +/// This will increment an internal counter, which is decremented with a corresponding call to drgui_text_engine_leave_selection_mode(). +/// Selection mode will be enabled so long as this counter is greater than 0. Thus, you must ensure you cleanly leave selection +/// mode. +void drgui_text_engine_enter_selection_mode(drgui_text_engine* pTL); + +/// Leaves selection mode. +/// +/// @remarks +/// This decrements the internal counter. Selection mode will not be disabled while this reference counter is greater than 0. Always +/// ensure a leave is correctly matched with an enter. +void drgui_text_engine_leave_selection_mode(drgui_text_engine* pTL); + +/// Determines whether or not the given text engine is in selection mode. +bool drgui_text_engine_is_in_selection_mode(drgui_text_engine* pTL); + +/// Determines whether or not anything is selected in the given text engine. +bool drgui_text_engine_is_anything_selected(drgui_text_engine* pTL); + +/// Deselects everything in the given text engine. +void drgui_text_engine_deselect_all(drgui_text_engine* pTL); + +/// Selects everything in the given text engine. +void drgui_text_engine_select_all(drgui_text_engine* pTL); + +/// Selects the given range of text. +void drgui_text_engine_select(drgui_text_engine* pTL, size_t firstCharacter, size_t lastCharacter); + +/// Retrieves a copy of the selected text. +/// +/// @remarks +/// This returns the length of the selected text. Call this once with set to NULL to calculate the required size of the +/// buffer. +/// @par +/// If the output buffer is not larger enough, the string will be truncated. +size_t drgui_text_engine_get_selected_text(drgui_text_engine* pTL, char* textOut, size_t textOutLength); + +/// Retrieves the index of the first line of the current selection. +size_t drgui_text_engine_get_selection_first_line(drgui_text_engine* pTL); + +/// Retrieves the index of the last line of the current selection. +size_t drgui_text_engine_get_selection_last_line(drgui_text_engine* pTL); + +/// Moves the selection anchor to the end of the given line. +void drgui_text_engine_move_selection_anchor_to_end_of_line(drgui_text_engine* pTL, size_t iLine); + +/// Moves the selection anchor to the start of the given line. +void drgui_text_engine_move_selection_anchor_to_start_of_line(drgui_text_engine* pTL, size_t iLine); + +/// Retrieves the line the selection anchor is sitting on. +size_t drgui_text_engine_get_selection_anchor_line(drgui_text_engine* pTL); + + +/// Prepares the next undo/redo point. +/// +/// @remarks +/// This captures the state that will be applied when the undo/redo point is undone. +bool drgui_text_engine_prepare_undo_point(drgui_text_engine* pTL); + +/// Creates a snapshot of the current state of the text engine and pushes it to the top of the undo/redo stack. +bool drgui_text_engine_commit_undo_point(drgui_text_engine* pTL); + +/// Performs an undo operation. +bool drgui_text_engine_undo(drgui_text_engine* pTL); + +/// Performs a redo operation. +bool drgui_text_engine_redo(drgui_text_engine* pTL); + +/// Retrieves the number of undo points remaining in the stack. +unsigned int drgui_text_engine_get_undo_points_remaining_count(drgui_text_engine* pTL); + +/// Retrieves the number of redo points remaining in the stack. +unsigned int drgui_text_engine_get_redo_points_remaining_count(drgui_text_engine* pTL); + +/// Clears the undo stack. +void drgui_text_engine_clear_undo_stack(drgui_text_engine* pTL); + + + +/// Retrieves the number of lines in the given text engine. +size_t drgui_text_engine_get_line_count(drgui_text_engine* pTL); + +/// Retrieves the number of lines that can fit on the visible portion of the layout, starting from the given line. +/// +/// @remarks +/// Use this for controlling the page size for scrollbars. +size_t drgui_text_engine_get_visible_line_count_starting_at(drgui_text_engine* pTL, size_t iFirstLine); + +/// Retrieves the position of the line at the given index on the y axis. +/// +/// @remarks +/// Use this for calculating the inner offset for scrolling on the y axis. +float drgui_text_engine_get_line_pos_y(drgui_text_engine* pTL, size_t iLine); + +/// Finds the line under the given point on the y axis relative to the container. +size_t drgui_text_engine_get_line_at_pos_y(drgui_text_engine* pTL, float posY); + +/// Retrieves the index of the first character of the line at the given index. +size_t drgui_text_engine_get_line_first_character(drgui_text_engine* pTL, size_t iLine); + +/// Retrieves the index of the last character of the line at the given index. +size_t drgui_text_engine_get_line_last_character(drgui_text_engine* pTL, size_t iLine); + +/// Retrieves teh index of the first and last character of the line at the given index. +void drgui_text_engine_get_line_character_range(drgui_text_engine* pTL, size_t iLine, size_t* pCharStartOut, size_t* pCharEndOut); + + +/// Sets the function to call when a run of text needs to be painted for the given text engine. +void drgui_text_engine_set_on_paint_text(drgui_text_engine* pTL, drgui_text_engine_on_paint_text_proc proc); + +/// Sets the function to call when a quad needs to the be painted for the given text engine. +void drgui_text_engine_set_on_paint_rect(drgui_text_engine* pTL, drgui_text_engine_on_paint_rect_proc proc); + +/// Paints the given text engine by calling the appropriate painting callbacks. +/// +/// @remarks +/// Typically a text engine will be painted to a GUI element. A pointer to an element can be passed to this function +/// which will be passed to the callback functions. This is purely for convenience and nothing is actually drawn to +/// the element outside of the callback functions. +void drgui_text_engine_paint(drgui_text_engine* pTL, drgui_rect rect, drgui_element* pElement, void* pPaintData); + + +/// Steps the given text engine by the given number of milliseconds. +/// +/// @remarks +/// This will trigger the on_dirty callback when the cursor switches it's blink states. +void drgui_text_engine_step(drgui_text_engine* pTL, unsigned int milliseconds); + + +/// Calls the given painting callbacks for the line numbers of the given text engine. +void drgui_text_engine_paint_line_numbers(drgui_text_engine* pTL, float lineNumbersWidth, float lineNumbersHeight, drgui_color textColor, drgui_color backgroundColor, drgui_text_engine_on_paint_text_proc onPaintText, drgui_text_engine_on_paint_rect_proc onPaintRect, drgui_element* pElement, void* pPaintData); + + +/// Finds the given string starting from the cursor and then looping back. +bool drgui_text_engine_find_next(drgui_text_engine* pTL, const char* text, size_t* pSelectionStartOut, size_t* pSelectionEndOut); + +/// Finds the given string starting from the cursor, but does not loop back. +bool drgui_text_engine_find_next_no_loop(drgui_text_engine* pTL, const char* text, size_t* pSelectionStartOut, size_t* pSelectionEndOut); + +#ifdef __cplusplus +} +#endif +#endif //drgui_text_engine_h + + +#ifdef DR_GUI_IMPLEMENTATION +typedef struct +{ + /// The index of the run within the line the marker is positioned on. + size_t iRun; + + /// The index of the character within the run the marker is positioned to the left of. + size_t iChar; + + /// The position on the x axis, relative to the x position of the run. + float relativePosX; + + /// The absolute position on the x axis to place the marker when moving up and down lines. Note that this is not relative + /// to the run, but rather the line. This will be updated when the marker is moved left and right. + float absoluteSickyPosX; + +} drgui_text_marker; + +/// Keeps track of the current state of the text engine. Used for calculating the difference between two states for undo/redo. +typedef struct +{ + /// The text. Can be null in some cases where it isn't used. + char* text; + + /// The index of the character the cursor is positioned at. + size_t cursorPos; + + /// The index of the character the selection anchor is positioned at. + size_t selectionAnchorPos; + + /// Whether or not anything is selected. + bool isAnythingSelected; + +} drgui_text_engine_state; + +typedef struct +{ + /// The position in the main string where the change is located. The length of the relevant string is used to determines how + /// large of a chunk of text needs to be replaced. + size_t diffPos; + + /// The string that was replaced. On undo, this will be inserted into the text engine. Can be empty, in which case this state + /// object was created in response to an insert operation. + char* oldText; + + /// The string that replaces the old text. On redo, this will be inserted into the text engine. This can be empty, in which case + /// this state object was created in response to a delete operation. + char* newText; + + /// The state of the text engine at the time the undo point was prepared, not including the text. The attribute + /// of this object is always null. + drgui_text_engine_state oldState; + + /// The state of the text engine at the time the undo point was committed, not including the text. The attribute + /// of this object is always null. + drgui_text_engine_state newState; + +} drgui_text_engine_undo_state; + +struct drgui_text_engine +{ + /// The main text of the layout. + char* text; + + /// The length of the text. + size_t textLength; + + + /// The function to call when the text engine needs to be redrawn. + drgui_text_engine_on_dirty_proc onDirty; + + /// The function to call when the content of the text engine changes. + drgui_text_engine_on_text_changed_proc onTextChanged; + + /// The function to call when the current undo point has changed. + drgui_text_engine_on_undo_point_changed_proc onUndoPointChanged; + + + /// The width of the container. + float containerWidth; + + /// The height of the container. + float containerHeight; + + /// The inner offset of the container. + float innerOffsetX; + + /// The inner offset of the container. + float innerOffsetY; + + + /// The default font. + drgui_font* pDefaultFont; + + /// The default text color. + drgui_color defaultTextColor; + + /// The default background color. + drgui_color defaultBackgroundColor; + + /// The background color to use for selected text. + drgui_color selectionBackgroundColor; + + /// The background color to use for the line the cursor is currently sitting on. + drgui_color lineBackgroundColor; + + /// The size of a tab in spaces. + unsigned int tabSizeInSpaces; + + /// The horizontal alignment. + drgui_text_engine_alignment horzAlign; + + /// The vertical alignment. + drgui_text_engine_alignment vertAlign; + + /// The width of the text cursor. + float cursorWidth; + + /// The color of the text cursor. + drgui_color cursorColor; + + /// The blink rate in milliseconds of the cursor. + unsigned int cursorBlinkRate; + + /// The amount of time in milliseconds to toggle the cursor's blink state. + unsigned int timeToNextCursorBlink; + + /// Whether or not the cursor is showing based on it's blinking state. + bool isCursorBlinkOn; + + /// Whether or not the cursor is being shown. False by default. + bool isShowingCursor; + + + /// The total width of the text. + float textBoundsWidth; + + /// The total height of the text. + float textBoundsHeight; + + + /// The cursor. + drgui_text_marker cursor; + + /// The selection anchor. + drgui_text_marker selectionAnchor; + + + /// The selection mode counter. When this is greater than 0 we are in selection mode, otherwise we are not. This + /// is incremented by enter_selection_mode() and decremented by leave_selection_mode(). + unsigned int selectionModeCounter; + + /// Whether or not anything is selected. + bool isAnythingSelected; + + + /// The function to call when a text run needs to be painted. + drgui_text_engine_on_paint_text_proc onPaintText; + + /// The function to call when a rectangle needs to be painted. + drgui_text_engine_on_paint_rect_proc onPaintRect; + + /// The function to call when the cursor moves. + drgui_text_engine_on_cursor_move_proc onCursorMove; + + + /// The prepared undo/redo state. This will be filled with some state by PrepareUndoRedoPoint() and again with CreateUndoRedoPoint(). + drgui_text_engine_state preparedState; + + /// The undo/redo stack. + drgui_text_engine_undo_state* pUndoStack; + + /// The number of items in the undo/redo stack. + unsigned int undoStackCount; + + /// The index of the undo/redo state item we are currently sitting on. + unsigned int iUndoState; + + + /// The counter used to determine when an onDirty event needs to be posted. + unsigned int dirtyCounter; + + /// The accumulated dirty rectangle. When dirtyCounter hits 0, this is the rectangle that's posted to the onDirty callback. + drgui_rect accumulatedDirtyRect; + + + /// A pointer to the buffer containing details about every run in the layout. + drgui_text_run* pRuns; + + /// The number of runs in . + size_t runCount; + + /// The size of the buffer in drgui_text_run's. This is used to determine whether or not the buffer + /// needs to be reallocated upon adding a new run. + size_t runBufferSize; + + + /// The size of the extra data. + size_t extraDataSize; + + /// A pointer to the extra data. + char pExtraData[1]; +}; + +/// Structure containing information about a line. This is used by first_line() and next_line(). +typedef struct +{ + /// The index of the line. + size_t index; + + /// The position of the line on the y axis. + float posY; + + /// The height of the line. + float height; + + /// The index of the first run on the line. + size_t iFirstRun; + + /// The index of the last run on the line. + size_t iLastRun; + +} drgui_text_engine_line; + + +/// Performs a complete refresh of the given text engine. +/// +/// @remarks +/// This will delete every run and re-create them. +DRGUI_PRIVATE void drgui_text_engine__refresh(drgui_text_engine* pTL); + +/// Refreshes the alignment of the given text engine. +DRGUI_PRIVATE void drgui_text_engine__refresh_alignment(drgui_text_engine* pTL); + +/// Appends a text run to the list of runs in the given text engine. +DRGUI_PRIVATE void drgui_text_engine__push_text_run(drgui_text_engine* pTL, drgui_text_run* pRun); + +/// Clears the internal list of text runs. +DRGUI_PRIVATE void drgui_text_engine__clear_text_runs(drgui_text_engine* pTL); + +/// Helper for calculating the offset to apply to each line based on the alignment of the given text engine. +DRGUI_PRIVATE void drgui_text_engine__calculate_line_alignment_offset(drgui_text_engine* pTL, float lineWidth, float* pOffsetXOut, float* pOffsetYOut); + +/// Helper for determine whether or not the given text run is whitespace. +DRGUI_PRIVATE bool drgui_text_engine__is_text_run_whitespace(drgui_text_engine* pTL, drgui_text_run* pRun); + +/// Helper for calculating the width of a tab. +DRGUI_PRIVATE float drgui_text_engine__get_tab_width(drgui_text_engine* pTL); + + +/// Finds the line that's closest to the given point relative to the text. +DRGUI_PRIVATE bool drgui_text_engine__find_closest_line_to_point(drgui_text_engine* pTL, float inputPosYRelativeToText, size_t* pFirstRunIndexOnLineOut, size_t* pLastRunIndexOnLinePlus1Out); + +/// Finds the run that's closest to the given point relative to the text. +DRGUI_PRIVATE bool drgui_text_engine__find_closest_run_to_point(drgui_text_engine* pTL, float inputPosXRelativeToText, float inputPosYRelativeToText, size_t* pRunIndexOut); + +/// Retrieves some basic information about a line, namely the index of the last run on the line, and the line's height. +DRGUI_PRIVATE bool drgui_text_engine__find_line_info(drgui_text_engine* pTL, size_t iFirstRunOnLine, size_t* pLastRunIndexOnLinePlus1Out, float* pLineHeightOut); + +/// Retrieves some basic information about a line by it's index. +DRGUI_PRIVATE bool drgui_text_engine__find_line_info_by_index(drgui_text_engine* pTL, size_t iLine, drgui_rect* pRectOut, size_t* pFirstRunIndexOut, size_t* pLastRunIndexPlus1Out); + +/// Finds the last run on the line that the given run is sitting on. +DRGUI_PRIVATE bool drgui_text_engine__find_last_run_on_line_starting_from_run(drgui_text_engine* pTL, size_t iRun, size_t* pLastRunIndexOnLineOut); + +/// Finds the first run on the line that the given run is sitting on. +DRGUI_PRIVATE bool drgui_text_engine__find_first_run_on_line_starting_from_run(drgui_text_engine* pTL, size_t iRun, size_t* pFirstRunIndexOnLineOut); + +/// Finds the run containing the character at the given index. +DRGUI_PRIVATE bool drgui_text_engine__find_run_at_character(drgui_text_engine* pTL, size_t iChar, size_t* pRunIndexOut); + + +/// Creates a blank text marker. +DRGUI_PRIVATE drgui_text_marker drgui_text_engine__new_marker(); + +/// Moves the given text marker to the given point, relative to the container. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_point_relative_to_container(drgui_text_engine* pTL, drgui_text_marker* pMarker, float inputPosX, float inputPosY); + +/// Retrieves the position of the given text marker relative to the container. +DRGUI_PRIVATE void drgui_text_engine__get_marker_position_relative_to_container(drgui_text_engine* pTL, drgui_text_marker* pMarker, float* pPosXOut, float* pPosYOut); + +/// Moves the marker to the given point, relative to the text rectangle. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_point(drgui_text_engine* pTL, drgui_text_marker* pMarker, float inputPosXRelativeToText, float inputPosYRelativeToText); + +/// Moves the given marker to the left by one character. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_left(drgui_text_engine* pTL, drgui_text_marker* pMarker); + +/// Moves the given marker to the right by one character. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_right(drgui_text_engine* pTL, drgui_text_marker* pMarker); + +/// Moves the given marker up one line. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_up(drgui_text_engine* pTL, drgui_text_marker* pMarker); + +/// Moves the given marker down one line. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_down(drgui_text_engine* pTL, drgui_text_marker* pMarker); + +/// Moves the given marker down one line. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_y(drgui_text_engine* pTL, drgui_text_marker* pMarker, int amount); + +/// Moves the given marker to the end of the line it's currently sitting on. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_end_of_line(drgui_text_engine* pTL, drgui_text_marker* pMarker); + +/// Moves the given marker to the start of the line it's currently sitting on. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_start_of_line(drgui_text_engine* pTL, drgui_text_marker* pMarker); + +/// Moves the given marker to the end of the line at the given index. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_end_of_line_by_index(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iLine); + +/// Moves the given marker to the start of the line at the given index. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_start_of_line_by_index(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iLine); + +/// Moves the given marker to the end of the text. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_end_of_text(drgui_text_engine* pTL, drgui_text_marker* pMarker); + +/// Moves the given marker to the start of the text. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_start_of_text(drgui_text_engine* pTL, drgui_text_marker* pMarker); + +/// Moves the given marker to the last character of the given run. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_last_character_of_run(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iRun); + +/// Moves the given marker to the first character of the given run. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_first_character_of_run(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iRun); + +/// Moves the given marker to the last character of the previous run. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_last_character_of_prev_run(drgui_text_engine* pTL, drgui_text_marker* pMarker); + +/// Moves the given marker to the first character of the next run. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_first_character_of_next_run(drgui_text_engine* pTL, drgui_text_marker* pMarker); + +/// Moves the given marker to the character at the given position. +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_character(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iChar); + + +/// Updates the relative position of the given marker. +/// +/// @remarks +/// This assumes the iRun and iChar properties are valid. +DRGUI_PRIVATE bool drgui_text_engine__update_marker_relative_position(drgui_text_engine* pTL, drgui_text_marker* pMarker); + +/// Updates the sticky position of the given marker. +DRGUI_PRIVATE void drgui_text_engine__update_marker_sticky_position(drgui_text_engine* pTL, drgui_text_marker* pMarker); + + +/// Retrieves the index of the character the given marker is located at. +DRGUI_PRIVATE size_t drgui_text_engine__get_marker_absolute_char_index(drgui_text_engine* pTL, drgui_text_marker* pMarker); + + +/// Helper function for determining whether or not there is any spacing between the selection markers. +DRGUI_PRIVATE bool drgui_text_engine__has_spacing_between_selection_markers(drgui_text_engine* pTL); + +/// Splits the given run into sub-runs based on the current selection rectangle. Returns the sub-run count. +DRGUI_PRIVATE size_t drgui_text_engine__split_text_run_by_selection(drgui_text_engine* pTL, drgui_text_run* pRunToSplit, drgui_text_run pSubRunsOut[3]); + + +/// Retrieves pointers to the selection markers in the correct order. +DRGUI_PRIVATE bool drgui_text_engine__get_selection_markers(drgui_text_engine* pTL, drgui_text_marker** ppSelectionMarker0Out, drgui_text_marker** ppSelectionMarker1Out); + + +/// Retrieves an iterator to the first line in the text engine. +DRGUI_PRIVATE bool drgui_text_engine__first_line(drgui_text_engine* pTL, drgui_text_engine_line* pLine); + +/// Retrieves an iterator to the next line in the text engine. +DRGUI_PRIVATE bool drgui_text_engine__next_line(drgui_text_engine* pTL, drgui_text_engine_line* pLine); + + +/// Removes the undo/redo state stack items after the current undo/redo point. +DRGUI_PRIVATE void drgui_text_engine__trim_undo_stack(drgui_text_engine* pTL); + +/// Initializes the given undo state object by diff-ing the given layout states. +DRGUI_PRIVATE bool drgui_text_engine__diff_states(drgui_text_engine_state* pPrevState, drgui_text_engine_state* pCurrentState, drgui_text_engine_undo_state* pUndoStateOut); + +/// Uninitializes the given undo state object. This basically just free's the internal string. +DRGUI_PRIVATE void drgui_text_engine__uninit_undo_state(drgui_text_engine_undo_state* pUndoState); + +/// Pushes an undo state onto the undo stack. +DRGUI_PRIVATE void drgui_text_engine__push_undo_state(drgui_text_engine* pTL, drgui_text_engine_undo_state* pUndoState); + +/// Applies the given undo state. +DRGUI_PRIVATE void drgui_text_engine__apply_undo_state(drgui_text_engine* pTL, drgui_text_engine_undo_state* pUndoState); + +/// Applies the given undo state as a redo operation. +DRGUI_PRIVATE void drgui_text_engine__apply_redo_state(drgui_text_engine* pTL, drgui_text_engine_undo_state* pUndoState); + + +/// Retrieves a rectangle relative to the given text engine that's equal to the size of the container. +DRGUI_PRIVATE drgui_rect drgui_text_engine__local_rect(drgui_text_engine* pTL); + + +/// Called when the cursor moves. +DRGUI_PRIVATE void drgui_text_engine__on_cursor_move(drgui_text_engine* pTL); + +/// Called when the text engine needs to be redrawn. +DRGUI_PRIVATE void drgui_text_engine__on_dirty(drgui_text_engine* pTL, drgui_rect rect); + +/// Increments the counter. The counter is decremented with drgui_text_engine__end_dirty(). Use this for batching redraws. +DRGUI_PRIVATE void drgui_text_engine__begin_dirty(drgui_text_engine* pTL); + +/// Decrements the dirty counter, and if it hits 0 posts the onDirty callback. +DRGUI_PRIVATE void drgui_text_engine__end_dirty(drgui_text_engine* pTL); + + + + +drgui_text_engine* drgui_create_text_engine(drgui_context* pContext, size_t extraDataSize, void* pExtraData) +{ + if (pContext == NULL) { + return NULL; + } + + drgui_text_engine* pTL = (drgui_text_engine*)malloc(sizeof(drgui_text_engine) + extraDataSize); + if (pTL == NULL) { + return NULL; + } + + pTL->text = NULL; + pTL->textLength = 0; + pTL->onDirty = NULL; + pTL->onTextChanged = NULL; + pTL->onUndoPointChanged = NULL; + pTL->containerWidth = 0; + pTL->containerHeight = 0; + pTL->innerOffsetX = 0; + pTL->innerOffsetY = 0; + pTL->pDefaultFont = NULL; + pTL->defaultTextColor = drgui_rgb(224, 224, 224); + pTL->defaultBackgroundColor = drgui_rgb(48, 48, 48); + pTL->selectionBackgroundColor = drgui_rgb(64, 128, 192); + pTL->lineBackgroundColor = drgui_rgb(40, 40, 40); + pTL->tabSizeInSpaces = 4; + pTL->horzAlign = drgui_text_engine_alignment_left; + pTL->vertAlign = drgui_text_engine_alignment_top; + pTL->cursorWidth = 1; + pTL->cursorColor = drgui_rgb(224, 224, 224); + pTL->cursorBlinkRate = 500; + pTL->timeToNextCursorBlink = pTL->cursorBlinkRate; + pTL->isCursorBlinkOn = true; + pTL->isShowingCursor = false; + pTL->textBoundsWidth = 0; + pTL->textBoundsHeight = 0; + pTL->cursor = drgui_text_engine__new_marker(); + pTL->selectionAnchor = drgui_text_engine__new_marker(); + pTL->selectionModeCounter = 0; + pTL->isAnythingSelected = false; + pTL->onPaintText = NULL; + pTL->onPaintRect = NULL; + pTL->onCursorMove = NULL; + pTL->preparedState.text = NULL; + pTL->pUndoStack = NULL; + pTL->undoStackCount = 0; + pTL->iUndoState = 0; + pTL->dirtyCounter = 0; + pTL->accumulatedDirtyRect = drgui_make_inside_out_rect(); + pTL->pRuns = NULL; + pTL->runCount = 0; + pTL->runBufferSize = 0; + + pTL->extraDataSize = extraDataSize; + if (pExtraData != NULL) { + memcpy(pTL->pExtraData, pExtraData, extraDataSize); + } + + return pTL; +} + +void drgui_delete_text_engine(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + drgui_text_engine_clear_undo_stack(pTL); + + free(pTL->pRuns); + free(pTL->preparedState.text); + free(pTL->text); + free(pTL); +} + + +size_t drgui_text_engine_get_extra_data_size(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return 0; + } + + return pTL->extraDataSize; +} + +void* drgui_text_engine_get_extra_data(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return NULL; + } + + return pTL->pExtraData; +} + + +void drgui_text_engine_set_text(drgui_text_engine* pTL, const char* text) +{ + if (pTL == NULL) { + return; + } + + size_t textLength = strlen(text); + + free(pTL->text); + pTL->text = (char*)malloc(textLength + 1); // +1 for null terminator. + + // We now need to copy over the text, however we need to skip past \r characters in order to normalize line endings + // and keep everything simple. + char* dst = pTL->text; + const char* src = text; + while (*src != '\0') + { + if (*src != '\r') { + *dst++ = *src; + } + + src++; + } + *dst = '\0'; + + pTL->textLength = dst - pTL->text; + + // A change in text means we need to refresh the layout. + drgui_text_engine__refresh(pTL); + + // If the position of the cursor is past the last character we'll need to move it. + if (drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->cursor) >= pTL->textLength) { + drgui_text_engine_move_cursor_to_end_of_text(pTL); + } + + if (pTL->onTextChanged) { + pTL->onTextChanged(pTL); + } + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); +} + +size_t drgui_text_engine_get_text(drgui_text_engine* pTL, char* textOut, size_t textOutSize) +{ + if (pTL == NULL) { + return 0; + } + + if (textOut == NULL) { + return pTL->textLength; + } + + + if (drgui__strcpy_s(textOut, textOutSize, (pTL->text != NULL) ? pTL->text : "") == 0) { + return pTL->textLength; + } + + return 0; // Error with strcpy_s(). +} + + +void drgui_text_engine_set_on_dirty(drgui_text_engine* pTL, drgui_text_engine_on_dirty_proc proc) +{ + if (pTL == NULL) { + return; + } + + pTL->onDirty = proc; +} + +void drgui_text_engine_set_on_text_changed(drgui_text_engine* pTL, drgui_text_engine_on_text_changed_proc proc) +{ + if (pTL == NULL) { + return; + } + + pTL->onTextChanged = proc; +} + +void drgui_text_engine_set_on_undo_point_changed(drgui_text_engine* pTL, drgui_text_engine_on_undo_point_changed_proc proc) +{ + if (pTL == NULL) { + return; + } + + pTL->onUndoPointChanged = proc; +} + + +void drgui_text_engine_set_container_size(drgui_text_engine* pTL, float containerWidth, float containerHeight) +{ + if (pTL == NULL) { + return; + } + + pTL->containerWidth = containerWidth; + pTL->containerHeight = containerHeight; + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); +} + +void drgui_text_engine_get_container_size(drgui_text_engine* pTL, float* pContainerWidthOut, float* pContainerHeightOut) +{ + float containerWidth = 0; + float containerHeight = 0; + + if (pTL != NULL) + { + containerWidth = pTL->containerWidth; + containerHeight = pTL->containerHeight; + } + + + if (pContainerWidthOut) { + *pContainerWidthOut = containerWidth; + } + if (pContainerHeightOut) { + *pContainerHeightOut = containerHeight; + } +} + +float drgui_text_engine_get_container_width(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return 0; + } + + return pTL->containerWidth; +} + +float drgui_text_engine_get_container_height(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return 0; + } + + return pTL->containerHeight; +} + + +void drgui_text_engine_set_inner_offset(drgui_text_engine* pTL, float innerOffsetX, float innerOffsetY) +{ + if (pTL == NULL) { + return; + } + + pTL->innerOffsetX = innerOffsetX; + pTL->innerOffsetY = innerOffsetY; + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); +} + +void drgui_text_engine_set_inner_offset_x(drgui_text_engine* pTL, float innerOffsetX) +{ + if (pTL == NULL) { + return; + } + + pTL->innerOffsetX = innerOffsetX; + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); +} + +void drgui_text_engine_set_inner_offset_y(drgui_text_engine* pTL, float innerOffsetY) +{ + if (pTL == NULL) { + return; + } + + pTL->innerOffsetY = innerOffsetY; + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); +} + +void drgui_text_engine_get_inner_offset(drgui_text_engine* pTL, float* pInnerOffsetX, float* pInnerOffsetY) +{ + float innerOffsetX = 0; + float innerOffsetY = 0; + + if (pTL != NULL) + { + innerOffsetX = pTL->innerOffsetX; + innerOffsetY = pTL->innerOffsetY; + } + + + if (pInnerOffsetX) { + *pInnerOffsetX = innerOffsetX; + } + if (pInnerOffsetY) { + *pInnerOffsetY = innerOffsetY; + } +} + +float drgui_text_engine_get_inner_offset_x(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return 0; + } + + return pTL->innerOffsetX; +} + +float drgui_text_engine_get_inner_offset_y(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return 0; + } + + return pTL->innerOffsetY; +} + + +void drgui_text_engine_set_default_font(drgui_text_engine* pTL, drgui_font* pFont) +{ + if (pTL == NULL) { + return; + } + + pTL->pDefaultFont = pFont; + + // A change in font requires a layout refresh. + drgui_text_engine__refresh(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); +} + +drgui_font* drgui_text_engine_get_default_font(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return NULL; + } + + return pTL->pDefaultFont; +} + +void drgui_text_engine_set_default_text_color(drgui_text_engine* pTL, drgui_color color) +{ + if (pTL == NULL) { + return; + } + + pTL->defaultTextColor = color; + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); +} + +drgui_color drgui_text_engine_get_default_text_color(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return drgui_rgb(0, 0, 0); + } + + return pTL->defaultTextColor; +} + +void drgui_text_engine_set_default_bg_color(drgui_text_engine* pTL, drgui_color color) +{ + if (pTL == NULL) { + return; + } + + pTL->defaultBackgroundColor = color; + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); +} + +drgui_color drgui_text_engine_get_default_bg_color(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return drgui_rgb(0, 0, 0); + } + + return pTL->defaultBackgroundColor; +} + +void drgui_text_engine_set_selection_bg_color(drgui_text_engine* pTL, drgui_color color) +{ + if (pTL == NULL) { + return; + } + + pTL->selectionBackgroundColor = color; + + if (drgui_text_engine_is_anything_selected(pTL)) { + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } +} + +drgui_color drgui_text_engine_get_selection_bg_color(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return drgui_rgb(0, 0, 0); + } + + return pTL->selectionBackgroundColor; +} + +void drgui_text_engine_set_active_line_bg_color(drgui_text_engine* pTL, drgui_color color) +{ + if (pTL == NULL) { + return; + } + + pTL->lineBackgroundColor = color; + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); +} + +drgui_color drgui_text_engine_get_active_line_bg_color(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return drgui_rgb(0, 0, 0); + } + + return pTL->lineBackgroundColor; +} + + +void drgui_text_engine_set_tab_size(drgui_text_engine* pTL, unsigned int sizeInSpaces) +{ + if (pTL == NULL) { + return; + } + + if (pTL->tabSizeInSpaces != sizeInSpaces) + { + pTL->tabSizeInSpaces = sizeInSpaces; + + drgui_text_engine__refresh(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } +} + +unsigned int drgui_text_engine_get_tab_size(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return 0; + } + + return pTL->tabSizeInSpaces; +} + + +void drgui_text_engine_set_horizontal_align(drgui_text_engine* pTL, drgui_text_engine_alignment alignment) +{ + if (pTL == NULL) { + return; + } + + if (pTL->horzAlign != alignment) + { + pTL->horzAlign = alignment; + + drgui_text_engine__refresh_alignment(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } +} + +drgui_text_engine_alignment drgui_text_engine_get_horizontal_align(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return drgui_text_engine_alignment_left; + } + + return pTL->horzAlign; +} + +void drgui_text_engine_set_vertical_align(drgui_text_engine* pTL, drgui_text_engine_alignment alignment) +{ + if (pTL == NULL) { + return; + } + + if (pTL->vertAlign != alignment) + { + pTL->vertAlign = alignment; + + drgui_text_engine__refresh_alignment(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } +} + +drgui_text_engine_alignment drgui_text_engine_get_vertical_align(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return drgui_text_engine_alignment_top; + } + + return pTL->vertAlign; +} + + +drgui_rect drgui_text_engine_get_text_rect_relative_to_bounds(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return drgui_make_rect(0, 0, 0, 0); + } + + drgui_rect rect; + rect.left = 0; + rect.top = 0; + + + switch (pTL->horzAlign) + { + case drgui_text_engine_alignment_right: + { + rect.left = pTL->containerWidth - pTL->textBoundsWidth; + break; + } + + case drgui_text_engine_alignment_center: + { + rect.left = (pTL->containerWidth - pTL->textBoundsWidth) / 2; + break; + } + + case drgui_text_engine_alignment_left: + case drgui_text_engine_alignment_top: // Invalid for horizontal align. + case drgui_text_engine_alignment_bottom: // Invalid for horizontal align. + default: + { + break; + } + } + + + switch (pTL->vertAlign) + { + case drgui_text_engine_alignment_bottom: + { + rect.top = pTL->containerHeight - pTL->textBoundsHeight; + break; + } + + case drgui_text_engine_alignment_center: + { + rect.top = (pTL->containerHeight - pTL->textBoundsHeight) / 2; + break; + } + + case drgui_text_engine_alignment_top: + case drgui_text_engine_alignment_left: // Invalid for vertical align. + case drgui_text_engine_alignment_right: // Invalid for vertical align. + default: + { + break; + } + } + + + rect.left += pTL->innerOffsetX; + rect.top += pTL->innerOffsetY; + rect.right = rect.left + pTL->textBoundsWidth; + rect.bottom = rect.top + pTL->textBoundsHeight; + + return rect; +} + + +void drgui_text_engine_set_cursor_width(drgui_text_engine* pTL, float cursorWidth) +{ + if (pTL == NULL) { + return; + } + + drgui_rect oldCursorRect = drgui_text_engine_get_cursor_rect(pTL); + pTL->cursorWidth = cursorWidth; + if (pTL->cursorWidth > 0 && pTL->cursorWidth < 1) { + pTL->cursorWidth = 1; + } + + drgui_text_engine__on_dirty(pTL, drgui_rect_union(oldCursorRect, drgui_text_engine_get_cursor_rect(pTL))); +} + +float drgui_text_engine_get_cursor_width(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return 0; + } + + return pTL->cursorWidth; +} + +void drgui_text_engine_set_cursor_color(drgui_text_engine* pTL, drgui_color cursorColor) +{ + if (pTL == NULL) { + return; + } + + pTL->cursorColor = cursorColor; + + drgui_text_engine__on_dirty(pTL, drgui_text_engine_get_cursor_rect(pTL)); +} + +drgui_color drgui_text_engine_get_cursor_color(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return drgui_rgb(0, 0, 0); + } + + return pTL->cursorColor; +} + +void drgui_text_engine_set_cursor_blink_rate(drgui_text_engine* pTL, unsigned int blinkRateInMilliseconds) +{ + if (pTL == NULL) { + return; + } + + pTL->cursorBlinkRate = blinkRateInMilliseconds; +} + +unsigned int drgui_text_engine_get_cursor_blink_rate(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return 0; + } + + return pTL->cursorBlinkRate; +} + +void drgui_text_engine_show_cursor(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + if (!pTL->isShowingCursor) + { + pTL->isShowingCursor = true; + + pTL->timeToNextCursorBlink = pTL->cursorBlinkRate; + pTL->isCursorBlinkOn = true; + + drgui_text_engine__on_dirty(pTL, drgui_text_engine_get_cursor_rect(pTL)); + } +} + +void drgui_text_engine_hide_cursor(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + if (pTL->isShowingCursor) + { + pTL->isShowingCursor = false; + + drgui_text_engine__on_dirty(pTL, drgui_text_engine_get_cursor_rect(pTL)); + } +} + +bool drgui_text_engine_is_showing_cursor(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + return pTL->isShowingCursor; +} + +void drgui_text_engine_get_cursor_position(drgui_text_engine* pTL, float* pPosXOut, float* pPosYOut) +{ + if (pTL == NULL) { + return; + } + + drgui_text_engine__get_marker_position_relative_to_container(pTL, &pTL->cursor, pPosXOut, pPosYOut); +} + +drgui_rect drgui_text_engine_get_cursor_rect(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return drgui_make_rect(0, 0, 0, 0); + } + + drgui_rect lineRect = drgui_make_rect(0, 0, 0, 0); + + if (pTL->runCount > 0) + { + drgui_text_engine__find_line_info_by_index(pTL, pTL->pRuns[pTL->cursor.iRun].iLine, &lineRect, NULL, NULL); + } + else if (pTL->pDefaultFont != NULL) + { + drgui_font_metrics defaultFontMetrics; + drgui_get_font_metrics(pTL->pDefaultFont, &defaultFontMetrics); + + lineRect.bottom = (float)defaultFontMetrics.lineHeight; + } + + + + float cursorPosX; + float cursorPosY; + drgui_text_engine_get_cursor_position(pTL, &cursorPosX, &cursorPosY); + + return drgui_make_rect(cursorPosX, cursorPosY, cursorPosX + pTL->cursorWidth, cursorPosY + (lineRect.bottom - lineRect.top)); +} + +size_t drgui_text_engine_get_cursor_line(drgui_text_engine* pTL) +{ + if (pTL == NULL || pTL->runCount == 0) { + return 0; + } + + return pTL->pRuns[pTL->cursor.iRun].iLine; +} + +size_t drgui_text_engine_get_cursor_column(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return 0; + } + + float posX; + float posY; + drgui_text_engine_get_cursor_position(pTL, &posX, &posY); + + drgui_font_metrics fontMetrics; + drgui_get_font_metrics(pTL->pDefaultFont, &fontMetrics); + + return (unsigned int)((int)posX / fontMetrics.spaceWidth); +} + +size_t drgui_text_engine_get_cursor_character(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return 0; + } + + return drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->cursor); +} + +void drgui_text_engine_move_cursor_to_point(drgui_text_engine* pTL, float posX, float posY) +{ + if (pTL == NULL) { + return; + } + + size_t iRunOld = pTL->cursor.iRun; + size_t iCharOld = pTL->cursor.iChar; + drgui_text_engine__move_marker_to_point_relative_to_container(pTL, &pTL->cursor, posX, posY); + + if (drgui_text_engine_is_in_selection_mode(pTL)) { + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + } + + if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) { + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } +} + +bool drgui_text_engine_move_cursor_left(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + size_t iRunOld = pTL->cursor.iRun; + size_t iCharOld = pTL->cursor.iChar; + if (drgui_text_engine__move_marker_left(pTL, &pTL->cursor)) { + if (drgui_text_engine_is_in_selection_mode(pTL)) { + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + } + + if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) { + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } + + return true; + } + + return false; +} + +bool drgui_text_engine_move_cursor_right(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + size_t iRunOld = pTL->cursor.iRun; + size_t iCharOld = pTL->cursor.iChar; + if (drgui_text_engine__move_marker_right(pTL, &pTL->cursor)) { + if (drgui_text_engine_is_in_selection_mode(pTL)) { + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + } + + if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) { + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } + + return true; + } + + return false; +} + +bool drgui_text_engine_move_cursor_up(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + size_t iRunOld = pTL->cursor.iRun; + size_t iCharOld = pTL->cursor.iChar; + if (drgui_text_engine__move_marker_up(pTL, &pTL->cursor)) { + if (drgui_text_engine_is_in_selection_mode(pTL)) { + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + } + + if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) { + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } + + return true; + } + + return false; +} + +bool drgui_text_engine_move_cursor_down(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + size_t iRunOld = pTL->cursor.iRun; + size_t iCharOld = pTL->cursor.iChar; + if (drgui_text_engine__move_marker_down(pTL, &pTL->cursor)) { + if (drgui_text_engine_is_in_selection_mode(pTL)) { + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + } + + if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) { + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } + + return true; + } + + return false; +} + +bool drgui_text_engine_move_cursor_y(drgui_text_engine* pTL, int amount) +{ + if (pTL == NULL) { + return false; + } + + size_t iRunOld = pTL->cursor.iRun; + size_t iCharOld = pTL->cursor.iChar; + if (drgui_text_engine__move_marker_y(pTL, &pTL->cursor, amount)) { + if (drgui_text_engine_is_in_selection_mode(pTL)) { + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + } + + if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) { + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } + + return true; + } + + return false; +} + +bool drgui_text_engine_move_cursor_to_end_of_line(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + size_t iRunOld = pTL->cursor.iRun; + size_t iCharOld = pTL->cursor.iChar; + if (drgui_text_engine__move_marker_to_end_of_line(pTL, &pTL->cursor)) { + if (drgui_text_engine_is_in_selection_mode(pTL)) { + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + } + + if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) { + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } + + return true; + } + + return false; +} + +bool drgui_text_engine_move_cursor_to_start_of_line(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + size_t iRunOld = pTL->cursor.iRun; + size_t iCharOld = pTL->cursor.iChar; + if (drgui_text_engine__move_marker_to_start_of_line(pTL, &pTL->cursor)) { + if (drgui_text_engine_is_in_selection_mode(pTL)) { + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + } + + if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) { + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } + + return true; + } + + return false; +} + +bool drgui_text_engine_move_cursor_to_end_of_line_by_index(drgui_text_engine* pTL, size_t iLine) +{ + if (pTL == NULL) { + return false; + } + + size_t iRunOld = pTL->cursor.iRun; + size_t iCharOld = pTL->cursor.iChar; + if (drgui_text_engine__move_marker_to_end_of_line_by_index(pTL, &pTL->cursor, iLine)) { + if (drgui_text_engine_is_in_selection_mode(pTL)) { + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + } + + if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) { + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } + + return true; + } + + return false; +} + +bool drgui_text_engine_move_cursor_to_start_of_line_by_index(drgui_text_engine* pTL, size_t iLine) +{ + if (pTL == NULL) { + return false; + } + + size_t iRunOld = pTL->cursor.iRun; + size_t iCharOld = pTL->cursor.iChar; + if (drgui_text_engine__move_marker_to_start_of_line_by_index(pTL, &pTL->cursor, iLine)) { + if (drgui_text_engine_is_in_selection_mode(pTL)) { + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + } + + if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) { + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } + + return true; + } + + return false; +} + +bool drgui_text_engine_move_cursor_to_end_of_text(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + size_t iRunOld = pTL->cursor.iRun; + size_t iCharOld = pTL->cursor.iChar; + if (drgui_text_engine__move_marker_to_end_of_text(pTL, &pTL->cursor)) { + if (drgui_text_engine_is_in_selection_mode(pTL)) { + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + } + + if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) { + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } + + return true; + } + + return false; +} + +bool drgui_text_engine_move_cursor_to_start_of_text(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + size_t iRunOld = pTL->cursor.iRun; + size_t iCharOld = pTL->cursor.iChar; + if (drgui_text_engine__move_marker_to_start_of_text(pTL, &pTL->cursor)) { + if (drgui_text_engine_is_in_selection_mode(pTL)) { + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + } + + if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) { + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } + + return true; + } + + return false; +} + +void drgui_text_engine_move_cursor_to_start_of_selection(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + drgui_text_marker* pSelectionMarker0; + drgui_text_marker* pSelectionMarker1; + if (drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) + { + pTL->cursor = *pSelectionMarker0; + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } +} + +void drgui_text_engine_move_cursor_to_end_of_selection(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + drgui_text_marker* pSelectionMarker0; + drgui_text_marker* pSelectionMarker1; + if (drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) + { + pTL->cursor = *pSelectionMarker1; + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } +} + +void drgui_text_engine_move_cursor_to_character(drgui_text_engine* pTL, size_t characterIndex) +{ + if (pTL == NULL) { + return; + } + + size_t iRunOld = pTL->cursor.iRun; + size_t iCharOld = pTL->cursor.iChar; + if (drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, characterIndex)) { + if (drgui_text_engine_is_in_selection_mode(pTL)) { + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + } + + if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) { + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } + } +} + +bool drgui_text_engine_is_cursor_at_start_of_selection(drgui_text_engine* pTL) +{ + drgui_text_marker* pSelectionMarker0; + drgui_text_marker* pSelectionMarker1; + if (drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) { + return &pTL->cursor == pSelectionMarker0; + } + + return false; +} + +bool drgui_text_engine_is_cursor_at_end_of_selection(drgui_text_engine* pTL) +{ + drgui_text_marker* pSelectionMarker0; + drgui_text_marker* pSelectionMarker1; + if (drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) { + return &pTL->cursor == pSelectionMarker1; + } + + return false; +} + +void drgui_text_engine_swap_selection_markers(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + drgui_text_marker* pSelectionMarker0; + drgui_text_marker* pSelectionMarker1; + if (drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) + { + size_t iRunOld = pTL->cursor.iRun; + size_t iCharOld = pTL->cursor.iChar; + + drgui_text_marker temp = *pSelectionMarker0; + *pSelectionMarker0 = *pSelectionMarker1; + *pSelectionMarker1 = temp; + + if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) { + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + } + } +} + +void drgui_text_engine_set_on_cursor_move(drgui_text_engine* pTL, drgui_text_engine_on_cursor_move_proc proc) +{ + if (pTL == NULL) { + return; + } + + pTL->onCursorMove = proc; +} + +void drgui_text_engine_refresh_markers(drgui_text_engine* pTL) +{ + if (pTL == NULL || pTL->pRuns == NULL) { + return; + } + + // Cursor. + drgui_text_run* pRun = pTL->pRuns + pTL->cursor.iRun; + drgui_get_text_cursor_position_from_char(pRun->pFont, pTL->text + pRun->iChar, pTL->cursor.iChar, OUT &pTL->cursor.relativePosX); + + pRun = pTL->pRuns + pTL->selectionAnchor.iRun; + drgui_get_text_cursor_position_from_char(pRun->pFont, pTL->text + pRun->iChar, pTL->selectionAnchor.iChar, OUT &pTL->selectionAnchor.relativePosX); +} + +bool drgui_text_engine_insert_character(drgui_text_engine* pTL, unsigned int character, size_t insertIndex) +{ + if (pTL == NULL) { + return false; + } + + // Transform '\r' to '\n'. + if (character == '\r') { + character = '\n'; + } + + + // TODO: Add proper support for UTF-8. + char* pOldText = pTL->text; + char* pNewText = (char*)malloc(pTL->textLength + 1 + 1); // +1 for the new character and +1 for the null terminator. + + if (insertIndex > 0) { + memcpy(pNewText, pOldText, insertIndex); + } + + pNewText[insertIndex] = (char)character; + + if (insertIndex < pTL->textLength) { + memcpy(pNewText + insertIndex + 1, pOldText + insertIndex, pTL->textLength - insertIndex); + } + + pTL->textLength += 1; + pTL->text = pNewText; + pNewText[pTL->textLength] = '\0'; + + free(pOldText); + + + + + // The layout will have changed so it needs to be refreshed. + drgui_text_engine__refresh(pTL); + + if (pTL->onTextChanged) { + pTL->onTextChanged(pTL); + } + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + + return true; +} + +bool drgui_text_engine_insert_text(drgui_text_engine* pTL, const char* text, size_t insertIndex) +{ + if (pTL == NULL || text == NULL) { + return false;; + } + + size_t newTextLength = strlen(text); + if (newTextLength == 0) { + return false; + } + + + // TODO: Add proper support for UTF-8. + char* pOldText = pTL->text; + char* pNewText = (char*)malloc(pTL->textLength + newTextLength + 1); // +1 for the new character and +1 for the null terminator. + + if (insertIndex > 0) { + memcpy(pNewText, pOldText, insertIndex); + } + + + // Replace \r\n with \n. + { + char* dst = pNewText + insertIndex; + const char* src = text; + size_t srcLen = newTextLength; + while (*src != '\0' && srcLen > 0) + { + if (*src != '\r') { + *dst++ = *src; + } + + src++; + srcLen -= 1; + } + + newTextLength = dst - (pNewText + insertIndex); + } + + if (insertIndex < pTL->textLength) { + memcpy(pNewText + insertIndex + newTextLength, pOldText + insertIndex, pTL->textLength - insertIndex); + } + + pTL->textLength += newTextLength; + pTL->text = pNewText; + pNewText[pTL->textLength] = '\0'; + + free(pOldText); + + + // The layout will have changed so it needs to be refreshed. + drgui_text_engine__refresh(pTL); + + if (pTL->onTextChanged) { + pTL->onTextChanged(pTL); + } + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + + return true; +} + +bool drgui_text_engine_delete_text_range(drgui_text_engine* pTL, size_t iFirstCh, size_t iLastChPlus1) +{ + if (pTL == NULL || iLastChPlus1 == iFirstCh) { + return false; + } + + if (iFirstCh > iLastChPlus1) { + size_t temp = iFirstCh; + iFirstCh = iLastChPlus1; + iLastChPlus1 = temp; + } + + + size_t bytesToRemove = iLastChPlus1 - iFirstCh; + if (bytesToRemove > 0) + { + memmove(pTL->text + iFirstCh, pTL->text + iLastChPlus1, pTL->textLength - iLastChPlus1); + pTL->textLength -= bytesToRemove; + pTL->text[pTL->textLength] = '\0'; + + // The layout will have changed. + drgui_text_engine__refresh(pTL); + + if (pTL->onTextChanged) { + pTL->onTextChanged(pTL); + } + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + + return true; + } + + return false; +} + +bool drgui_text_engine_insert_character_at_cursor(drgui_text_engine* pTL, unsigned int character) +{ + if (pTL == NULL) { + return false; + } + + size_t iAbsoluteMarkerChar = 0; + + drgui_text_run* pRun = pTL->pRuns + pTL->cursor.iRun; + if (pTL->runCount > 0 && pRun != NULL) { + iAbsoluteMarkerChar = pRun->iChar + pTL->cursor.iChar; + } + + drgui_text_engine__begin_dirty(pTL); + { + drgui_text_engine_insert_character(pTL, character, iAbsoluteMarkerChar); + drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, iAbsoluteMarkerChar + 1); + } + drgui_text_engine__end_dirty(pTL); + + + // The cursor's sticky position needs to be updated whenever the text is edited. + drgui_text_engine__update_marker_sticky_position(pTL, &pTL->cursor); + + + drgui_text_engine__on_cursor_move(pTL); + + return true; +} + +bool drgui_text_engine_insert_text_at_cursor(drgui_text_engine* pTL, const char* text) +{ + if (pTL == NULL || text == NULL) { + return false; + } + + drgui_text_engine__begin_dirty(pTL); + { + size_t cursorPos = drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->cursor); + drgui_text_engine_insert_text(pTL, text, cursorPos); + drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, cursorPos + strlen(text)); + } + drgui_text_engine__end_dirty(pTL); + + + // The cursor's sticky position needs to be updated whenever the text is edited. + drgui_text_engine__update_marker_sticky_position(pTL, &pTL->cursor); + + drgui_text_engine__on_cursor_move(pTL); + + return true; +} + +bool drgui_text_engine_delete_character_to_left_of_cursor(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + // We just move the cursor to the left, and then delete the character to the right. + if (drgui_text_engine_move_cursor_left(pTL)) { + drgui_text_engine_delete_character_to_right_of_cursor(pTL); + return true; + } + + return false; +} + +bool drgui_text_engine_delete_character_to_right_of_cursor(drgui_text_engine* pTL) +{ + if (pTL == NULL || pTL->runCount == 0) { + return false; + } + + drgui_text_run* pRun = pTL->pRuns + pTL->cursor.iRun; + size_t iAbsoluteMarkerChar = pRun->iChar + pTL->cursor.iChar; + + if (iAbsoluteMarkerChar < pTL->textLength) + { + // TODO: Add proper support for UTF-8. + memmove(pTL->text + iAbsoluteMarkerChar, pTL->text + iAbsoluteMarkerChar + 1, pTL->textLength - iAbsoluteMarkerChar); + pTL->textLength -= 1; + pTL->text[pTL->textLength] = '\0'; + + + + // The layout will have changed. + drgui_text_engine__refresh(pTL); + drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, iAbsoluteMarkerChar); + + if (pTL->onTextChanged) { + pTL->onTextChanged(pTL); + } + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); + + return true; + } + + return false; +} + +bool drgui_text_engine_delete_selected_text(drgui_text_engine* pTL) +{ + // Don't do anything if nothing is selected. + if (!drgui_text_engine_is_anything_selected(pTL)) { + return false; + } + + drgui_text_marker* pSelectionMarker0 = &pTL->selectionAnchor; + drgui_text_marker* pSelectionMarker1 = &pTL->cursor; + if (pTL->pRuns[pSelectionMarker0->iRun].iChar + pSelectionMarker0->iChar > pTL->pRuns[pSelectionMarker1->iRun].iChar + pSelectionMarker1->iChar) + { + drgui_text_marker* temp = pSelectionMarker0; + pSelectionMarker0 = pSelectionMarker1; + pSelectionMarker1 = temp; + } + + size_t iSelectionChar0 = pTL->pRuns[pSelectionMarker0->iRun].iChar + pSelectionMarker0->iChar; + size_t iSelectionChar1 = pTL->pRuns[pSelectionMarker1->iRun].iChar + pSelectionMarker1->iChar; + + drgui_text_engine__begin_dirty(pTL); + bool wasTextChanged = drgui_text_engine_delete_text_range(pTL, iSelectionChar0, iSelectionChar1); + if (wasTextChanged) + { + // The marker needs to be updated based on the new layout. + drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, iSelectionChar0); + + // The cursor's sticky position also needs to be updated. + drgui_text_engine__update_marker_sticky_position(pTL, &pTL->cursor); + + drgui_text_engine__on_cursor_move(pTL); + + + // Reset the selection marker. + pTL->selectionAnchor = pTL->cursor; + pTL->isAnythingSelected = false; + } + + drgui_text_engine__end_dirty(pTL); + return wasTextChanged; +} + + +void drgui_text_engine_enter_selection_mode(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + // If we've just entered selection mode and nothing is currently selected, we want to set the selection anchor to the current cursor position. + if (!drgui_text_engine_is_in_selection_mode(pTL) && !pTL->isAnythingSelected) { + pTL->selectionAnchor = pTL->cursor; + } + + pTL->selectionModeCounter += 1; +} + +void drgui_text_engine_leave_selection_mode(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + if (pTL->selectionModeCounter > 0) { + pTL->selectionModeCounter -= 1; + } +} + +bool drgui_text_engine_is_in_selection_mode(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + return pTL->selectionModeCounter > 0; +} + +bool drgui_text_engine_is_anything_selected(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + return pTL->isAnythingSelected; +} + +void drgui_text_engine_deselect_all(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + pTL->isAnythingSelected = false; + + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); +} + +void drgui_text_engine_select_all(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + drgui_text_engine__move_marker_to_start_of_text(pTL, &pTL->selectionAnchor); + drgui_text_engine__move_marker_to_end_of_text(pTL, &pTL->cursor); + + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); +} + +void drgui_text_engine_select(drgui_text_engine* pTL, size_t firstCharacter, size_t lastCharacter) +{ + if (pTL == NULL) { + return; + } + + drgui_text_engine__move_marker_to_character(pTL, &pTL->selectionAnchor, firstCharacter); + drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, lastCharacter); + + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); + + drgui_text_engine__on_cursor_move(pTL); + drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL)); +} + +size_t drgui_text_engine_get_selected_text(drgui_text_engine* pTL, char* textOut, size_t textOutSize) +{ + if (pTL == NULL || (textOut != NULL && textOutSize == 0)) { + return 0; + } + + if (!drgui_text_engine_is_anything_selected(pTL)) { + return 0; + } + + + drgui_text_marker* pSelectionMarker0; + drgui_text_marker* pSelectionMarker1; + if (!drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) { + return false; + } + + size_t iSelectionChar0 = pTL->pRuns[pSelectionMarker0->iRun].iChar + pSelectionMarker0->iChar; + size_t iSelectionChar1 = pTL->pRuns[pSelectionMarker1->iRun].iChar + pSelectionMarker1->iChar; + + size_t selectedTextLength = iSelectionChar1 - iSelectionChar0; + + if (textOut != NULL) { + drgui__strncpy_s(textOut, textOutSize, pTL->text + iSelectionChar0, selectedTextLength); + } + + return selectedTextLength; +} + +size_t drgui_text_engine_get_selection_first_line(drgui_text_engine* pTL) +{ + if (pTL == NULL || pTL->runCount == 0) { + return 0; + } + + drgui_text_marker* pSelectionMarker0; + drgui_text_marker* pSelectionMarker1; + if (!drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) { + return 0; + } + + return pTL->pRuns[pSelectionMarker0->iRun].iLine; +} + +size_t drgui_text_engine_get_selection_last_line(drgui_text_engine* pTL) +{ + if (pTL == NULL || pTL->runCount == 0) { + return 0; + } + + drgui_text_marker* pSelectionMarker0; + drgui_text_marker* pSelectionMarker1; + if (!drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) { + return 0; + } + + return pTL->pRuns[pSelectionMarker1->iRun].iLine; +} + +void drgui_text_engine_move_selection_anchor_to_end_of_line(drgui_text_engine* pTL, size_t iLine) +{ + if (pTL == NULL) { + return; + } + + drgui_text_engine__move_marker_to_end_of_line_by_index(pTL, &pTL->selectionAnchor, iLine); + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); +} + +void drgui_text_engine_move_selection_anchor_to_start_of_line(drgui_text_engine* pTL, size_t iLine) +{ + if (pTL == NULL) { + return; + } + + drgui_text_engine__move_marker_to_start_of_line_by_index(pTL, &pTL->selectionAnchor, iLine); + pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL); +} + +size_t drgui_text_engine_get_selection_anchor_line(drgui_text_engine* pTL) +{ + if (pTL == NULL || pTL->runCount == 0) { + return 0; + } + + return pTL->pRuns[pTL->selectionAnchor.iRun].iLine; +} + + + +bool drgui_text_engine_prepare_undo_point(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + // If we have a previously prepared state we'll need to clear it. + if (pTL->preparedState.text != NULL) { + free(pTL->preparedState.text); + } + + pTL->preparedState.text = (char*)malloc(pTL->textLength + 1); + drgui__strcpy_s(pTL->preparedState.text, pTL->textLength + 1, (pTL->text != NULL) ? pTL->text : ""); + + pTL->preparedState.cursorPos = drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->cursor); + pTL->preparedState.selectionAnchorPos = drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->selectionAnchor); + pTL->preparedState.isAnythingSelected = pTL->isAnythingSelected; + + return true; +} + +bool drgui_text_engine_commit_undo_point(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + // The undo point must have been prepared earlier. + if (pTL->preparedState.text == NULL) { + return false; + } + + + // The undo state is creating by diff-ing the prepared state and the current state. + drgui_text_engine_state currentState; + currentState.text = pTL->text; + currentState.cursorPos = drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->cursor); + currentState.selectionAnchorPos = drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->selectionAnchor); + currentState.isAnythingSelected = pTL->isAnythingSelected; + + drgui_text_engine_undo_state undoState; + if (!drgui_text_engine__diff_states(&pTL->preparedState, ¤tState, &undoState)) { + return false; + } + + + // At this point we have the undo state ready and we just need to add it the undo stack. Before doing so, however, + // we need to trim the end fo the stack. + drgui_text_engine__trim_undo_stack(pTL); + drgui_text_engine__push_undo_state(pTL, &undoState); + + return true; +} + +bool drgui_text_engine_undo(drgui_text_engine* pTL) +{ + if (pTL == NULL || pTL->pUndoStack == NULL) { + return false; + } + + if (drgui_text_engine_get_undo_points_remaining_count(pTL) > 0) + { + drgui_text_engine_undo_state* pUndoState = pTL->pUndoStack + (pTL->iUndoState - 1); + assert(pUndoState != NULL); + + drgui_text_engine__apply_undo_state(pTL, pUndoState); + pTL->iUndoState -= 1; + + if (pTL->onUndoPointChanged) { + pTL->onUndoPointChanged(pTL, pTL->iUndoState); + } + + return true; + } + + return false; +} + +bool drgui_text_engine_redo(drgui_text_engine* pTL) +{ + if (pTL == NULL || pTL->pUndoStack == NULL) { + return false; + } + + if (drgui_text_engine_get_redo_points_remaining_count(pTL) > 0) + { + drgui_text_engine_undo_state* pUndoState = pTL->pUndoStack + pTL->iUndoState; + assert(pUndoState != NULL); + + drgui_text_engine__apply_redo_state(pTL, pUndoState); + pTL->iUndoState += 1; + + if (pTL->onUndoPointChanged) { + pTL->onUndoPointChanged(pTL, pTL->iUndoState); + } + + return true; + } + + return false; +} + +unsigned int drgui_text_engine_get_undo_points_remaining_count(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return 0; + } + + return pTL->iUndoState; +} + +unsigned int drgui_text_engine_get_redo_points_remaining_count(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return 0; + } + + if (pTL->undoStackCount > 0) + { + assert(pTL->iUndoState <= pTL->undoStackCount); + return pTL->undoStackCount - pTL->iUndoState; + } + + return 0; +} + +void drgui_text_engine_clear_undo_stack(drgui_text_engine* pTL) +{ + if (pTL == NULL || pTL->pUndoStack == NULL) { + return; + } + + for (unsigned int i = 0; i < pTL->undoStackCount; ++i) { + drgui_text_engine__uninit_undo_state(pTL->pUndoStack + i); + } + + free(pTL->pUndoStack); + + pTL->pUndoStack = NULL; + pTL->undoStackCount = 0; + + if (pTL->iUndoState > 0) { + pTL->iUndoState = 0; + + if (pTL->onUndoPointChanged) { + pTL->onUndoPointChanged(pTL, pTL->iUndoState); + } + } +} + + + +size_t drgui_text_engine_get_line_count(drgui_text_engine* pTL) +{ + if (pTL == NULL || pTL->runCount == 0) { + return 0; + } + + return pTL->pRuns[pTL->runCount - 1].iLine + 1; +} + +size_t drgui_text_engine_get_visible_line_count_starting_at(drgui_text_engine* pTL, size_t iFirstLine) +{ + if (pTL == NULL || pTL->runCount == 0) { + return 0; + } + + unsigned int count = 0; + float lastLineBottom = 0; + + // First thing we do is find the first line. + unsigned int iLine = 0; + drgui_text_engine_line line; + if (drgui_text_engine__first_line(pTL, &line)) + { + do + { + if (iLine >= iFirstLine) { + break; + } + + iLine += 1; + } while (drgui_text_engine__next_line(pTL, &line)); + + + // At this point we are at the first line and we need to start counting. + do + { + if (line.posY + pTL->innerOffsetY >= pTL->containerHeight) { + break; + } + + count += 1; + lastLineBottom = line.posY + line.height; + + } while (drgui_text_engine__next_line(pTL, &line)); + } + + + // At this point there may be some empty space below the last line, in which case we use the line height of the default font to fill + // out the remaining space. + if (lastLineBottom + pTL->innerOffsetY < pTL->containerHeight) + { + drgui_font_metrics defaultFontMetrics; + if (drgui_get_font_metrics(pTL->pDefaultFont, &defaultFontMetrics)) { + count += (unsigned int)((pTL->containerHeight - (lastLineBottom + pTL->innerOffsetY)) / defaultFontMetrics.lineHeight); + } + } + + + + if (count == 0) { + return 1; + } + + return count; +} + +float drgui_text_engine_get_line_pos_y(drgui_text_engine* pTL, size_t iLine) +{ + drgui_rect lineRect; + if (!drgui_text_engine__find_line_info_by_index(pTL, iLine, &lineRect, NULL, NULL)) { + return 0; + } + + return lineRect.top; +} + +size_t drgui_text_engine_get_line_at_pos_y(drgui_text_engine* pTL, float posY) +{ + if (pTL == NULL || pTL->runCount == 0) { + return 0; + } + + drgui_rect textRect = drgui_text_engine_get_text_rect_relative_to_bounds(pTL); + + size_t iRun; + + float inputPosYRelativeToText = posY - textRect.top; + if (!drgui_text_engine__find_closest_run_to_point(pTL, 0, inputPosYRelativeToText, &iRun)) { + return 0; + } + + return pTL->pRuns[iRun].iLine; +} + +size_t drgui_text_engine_get_line_first_character(drgui_text_engine* pTL, size_t iLine) +{ + if (pTL == NULL || pTL->runCount == 0) { + return 0; + } + + size_t firstRunIndex0; + size_t lastRunIndexPlus1; + if (drgui_text_engine__find_line_info_by_index(pTL, iLine, NULL, &firstRunIndex0, &lastRunIndexPlus1)) { + return pTL->pRuns[firstRunIndex0].iChar; + } + + return 0; +} + +size_t drgui_text_engine_get_line_last_character(drgui_text_engine* pTL, size_t iLine) +{ + if (pTL == NULL || pTL->runCount == 0) { + return 0; + } + + size_t firstRunIndex0; + size_t lastRunIndexPlus1; + if (drgui_text_engine__find_line_info_by_index(pTL, iLine, NULL, &firstRunIndex0, &lastRunIndexPlus1)) { + size_t charEnd = pTL->pRuns[lastRunIndexPlus1 - 1].iCharEnd; + if (charEnd > 0) { + charEnd -= 1; + } + + return charEnd; + } + + return 0; +} + +void drgui_text_engine_get_line_character_range(drgui_text_engine* pTL, size_t iLine, size_t* pCharStartOut, size_t* pCharEndOut) +{ + if (pTL == NULL || pTL->runCount == 0) { + return; + } + + size_t charStart = 0; + size_t charEnd = 0; + + size_t firstRunIndex0; + size_t lastRunIndexPlus1; + if (drgui_text_engine__find_line_info_by_index(pTL, iLine, NULL, &firstRunIndex0, &lastRunIndexPlus1)) { + charStart = pTL->pRuns[firstRunIndex0].iChar; + charEnd = pTL->pRuns[lastRunIndexPlus1 - 1].iCharEnd; + if (charEnd > 0) { + charEnd -= 1; + } + } + + if (pCharStartOut) { + *pCharStartOut = charStart; + } + if (pCharEndOut) { + *pCharEndOut = charEnd; + } +} + + +void drgui_text_engine_set_on_paint_text(drgui_text_engine* pTL, drgui_text_engine_on_paint_text_proc proc) +{ + if (pTL == NULL) { + return; + } + + pTL->onPaintText = proc; +} + +void drgui_text_engine_set_on_paint_rect(drgui_text_engine* pTL, drgui_text_engine_on_paint_rect_proc proc) +{ + if (pTL == NULL) { + return; + } + + pTL->onPaintRect = proc; +} + +void drgui_text_engine_paint(drgui_text_engine* pTL, drgui_rect rect, drgui_element* pElement, void* pPaintData) +{ + if (pTL == NULL || pTL->onPaintText == NULL || pTL->onPaintRect == NULL) { + return; + } + + if (rect.left < 0) { + rect.left = 0; + } + if (rect.top < 0) { + rect.top = 0; + } + if (rect.right > pTL->containerWidth) { + rect.right = pTL->containerWidth; + } + if (rect.bottom > pTL->containerHeight) { + rect.bottom = pTL->containerHeight; + } + + if (rect.right <= rect.left || rect.bottom <= rect.top) { + return; + } + + + // The position of each run will be relative to the text bounds. We want to make it relative to the container bounds. + drgui_rect textRect = drgui_text_engine_get_text_rect_relative_to_bounds(pTL); + + // We draw a rectangle above and below the text rectangle. The main text rectangle will be drawn by iterating over each visible run. + drgui_rect rectTop = drgui_make_rect(0, 0, pTL->containerWidth, textRect.top); + drgui_rect rectBottom = drgui_make_rect(0, textRect.bottom, pTL->containerWidth, pTL->containerHeight); + + if (rectTop.bottom > rect.top) { + pTL->onPaintRect(pTL, rectTop, pTL->defaultBackgroundColor, pElement, pPaintData); + } + + if (rectBottom.top < rect.bottom) { + pTL->onPaintRect(pTL, rectBottom, pTL->defaultBackgroundColor, pElement, pPaintData); + } + + + // We draw line-by-line, starting from the first visible line. + drgui_text_engine_line line; + if (drgui_text_engine__first_line(pTL, &line)) + { + do + { + float lineTop = line.posY + textRect.top; + float lineBottom = lineTop + line.height; + + if (lineTop < rect.bottom) + { + if (lineBottom > rect.top) + { + // The line is visible. We draw in 3 main parts - 1) the blank space to the left of the first run; 2) the runs themselves; 3) the blank + // space to the right of the last run. + + drgui_color bgcolor = pTL->defaultBackgroundColor; + if (line.index == drgui_text_engine_get_cursor_line(pTL)) { + bgcolor = pTL->lineBackgroundColor; + } + + float lineSelectionOverhangLeft = 0; + float lineSelectionOverhangRight = 0; + + if (drgui_text_engine_is_anything_selected(pTL)) + { + drgui_text_marker* pSelectionMarker0 = &pTL->selectionAnchor; + drgui_text_marker* pSelectionMarker1 = &pTL->cursor; + if (pTL->pRuns[pSelectionMarker0->iRun].iChar + pSelectionMarker0->iChar > pTL->pRuns[pSelectionMarker1->iRun].iChar + pSelectionMarker1->iChar) + { + drgui_text_marker* temp = pSelectionMarker0; + pSelectionMarker0 = pSelectionMarker1; + pSelectionMarker1 = temp; + } + + size_t iSelectionLine0 = pTL->pRuns[pSelectionMarker0->iRun].iLine; + size_t iSelectionLine1 = pTL->pRuns[pSelectionMarker1->iRun].iLine; + + if (line.index >= iSelectionLine0 && line.index < iSelectionLine1) + { + drgui_font_metrics defaultFontMetrics; + drgui_get_font_metrics(pTL->pDefaultFont, &defaultFontMetrics); + + if (pTL->horzAlign == drgui_text_engine_alignment_right) + { + if (line.index > iSelectionLine0) { + lineSelectionOverhangLeft = (float)defaultFontMetrics.spaceWidth; + } + } + else if (pTL->horzAlign == drgui_text_engine_alignment_center) + { + lineSelectionOverhangRight = (float)defaultFontMetrics.spaceWidth; + + if (line.index > iSelectionLine0) { + lineSelectionOverhangLeft = (float)defaultFontMetrics.spaceWidth; + } + } + else + { + lineSelectionOverhangRight = (float)defaultFontMetrics.spaceWidth; + } + } + } + + + drgui_text_run* pFirstRun = pTL->pRuns + line.iFirstRun; + drgui_text_run* pLastRun = pTL->pRuns + line.iLastRun; + + float lineLeft = pFirstRun->posX + textRect.left; + float lineRight = pLastRun->posX + pLastRun->width + textRect.left; + + // 1) The blank space to the left of the first run. + if (lineLeft > 0) + { + if (lineSelectionOverhangLeft > 0) { + pTL->onPaintRect(pTL, drgui_make_rect(lineLeft - lineSelectionOverhangLeft, lineTop, lineLeft, lineBottom), pTL->selectionBackgroundColor, pElement, pPaintData); + } + + pTL->onPaintRect(pTL, drgui_make_rect(0, lineTop, lineLeft - lineSelectionOverhangLeft, lineBottom), bgcolor, pElement, pPaintData); + } + + + // 2) The runs themselves. + for (size_t iRun = line.iFirstRun; iRun <= line.iLastRun; ++iRun) + { + drgui_text_run* pRun = pTL->pRuns + iRun; + + float runLeft = pRun->posX + textRect.left; + float runRight = runLeft + pRun->width; + + if (runRight > 0 && runLeft < pTL->containerWidth) + { + // The run is visible. + if (!drgui_text_engine__is_text_run_whitespace(pTL, pRun) || pTL->text[pRun->iChar] == '\t') + { + drgui_text_run run = pTL->pRuns[iRun]; + run.pFont = pTL->pDefaultFont; + run.textColor = pTL->defaultTextColor; + run.backgroundColor = bgcolor; + run.text = pTL->text + run.iChar; + run.posX = runLeft; + run.posY = lineTop; + + // We paint the run differently depending on whether or not anything is selected. If something is selected + // we need to split the run into a maximum of 3 sub-runs so that the selection rectangle can be drawn correctly. + if (drgui_text_engine_is_anything_selected(pTL)) + { + drgui_text_run subruns[3]; + size_t subrunCount = drgui_text_engine__split_text_run_by_selection(pTL, &run, subruns); + for (size_t iSubRun = 0; iSubRun < subrunCount; ++iSubRun) + { + drgui_text_run* pSubRun = subruns + iSubRun; + + if (!drgui_text_engine__is_text_run_whitespace(pTL, pRun)) { + pTL->onPaintText(pTL, pSubRun, pElement, pPaintData); + } else { + pTL->onPaintRect(pTL, drgui_make_rect(pSubRun->posX, lineTop, pSubRun->posX + pSubRun->width, lineBottom), pSubRun->backgroundColor, pElement, pPaintData); + } + } + } + else + { + // Nothing is selected. + if (!drgui_text_engine__is_text_run_whitespace(pTL, &run)) { + pTL->onPaintText(pTL, &run, pElement, pPaintData); + } else { + pTL->onPaintRect(pTL, drgui_make_rect(run.posX, lineTop, run.posX + run.width, lineBottom), run.backgroundColor, pElement, pPaintData); + } + } + } + } + } + + + // 3) The blank space to the right of the last run. + if (lineRight < pTL->containerWidth) + { + if (lineSelectionOverhangRight > 0) { + pTL->onPaintRect(pTL, drgui_make_rect(lineRight, lineTop, lineRight + lineSelectionOverhangRight, lineBottom), pTL->selectionBackgroundColor, pElement, pPaintData); + } + + pTL->onPaintRect(pTL, drgui_make_rect(lineRight + lineSelectionOverhangRight, lineTop, pTL->containerWidth, lineBottom), bgcolor, pElement, pPaintData); + } + } + } + else + { + // The line is below the rectangle which means no other line will be visible and we can terminate early. + break; + } + + } while (drgui_text_engine__next_line(pTL, &line)); + } + else + { + // There are no lines so we do a simplified branch here. + float lineTop = textRect.top; + float lineBottom = textRect.bottom; + pTL->onPaintRect(pTL, drgui_make_rect(0, lineTop, pTL->containerWidth, lineBottom), pTL->lineBackgroundColor, pElement, pPaintData); + } + + // The cursor. + if (pTL->isShowingCursor && pTL->isCursorBlinkOn) { + pTL->onPaintRect(pTL, drgui_text_engine_get_cursor_rect(pTL), pTL->cursorColor, pElement, pPaintData); + } +} + + +void drgui_text_engine_step(drgui_text_engine* pTL, unsigned int milliseconds) +{ + if (pTL == NULL || milliseconds == 0) { + return; + } + + if (pTL->timeToNextCursorBlink < milliseconds) + { + pTL->isCursorBlinkOn = !pTL->isCursorBlinkOn; + pTL->timeToNextCursorBlink = pTL->cursorBlinkRate; + + drgui_text_engine__on_dirty(pTL, drgui_text_engine_get_cursor_rect(pTL)); + } + else + { + pTL->timeToNextCursorBlink -= milliseconds; + } +} + + + +void drgui_text_engine_paint_line_numbers(drgui_text_engine* pTL, float lineNumbersWidth, float lineNumbersHeight, drgui_color textColor, drgui_color backgroundColor, drgui_text_engine_on_paint_text_proc onPaintText, drgui_text_engine_on_paint_rect_proc onPaintRect, drgui_element* pElement, void* pPaintData) +{ + if (pTL == NULL || onPaintText == NULL || onPaintRect == NULL) { + return; + } + + + // The position of each run will be relative to the text bounds. We want to make it relative to the container bounds. + drgui_rect textRect = drgui_text_engine_get_text_rect_relative_to_bounds(pTL); + + // We draw a rectangle above and below the text rectangle. The main text rectangle will be drawn by iterating over each visible run. + drgui_rect rectTop = drgui_make_rect(0, 0, lineNumbersWidth, textRect.top); + drgui_rect rectBottom = drgui_make_rect(0, textRect.bottom, lineNumbersWidth, lineNumbersHeight); + + if (pTL->onPaintRect) + { + if (rectTop.bottom > 0) { + onPaintRect(pTL, rectTop, backgroundColor, pElement, pPaintData); + } + + if (rectBottom.top < lineNumbersHeight) { + onPaintRect(pTL, rectBottom, backgroundColor, pElement, pPaintData); + } + } + + + // Now we draw each line. + int iLine = 1; + drgui_text_engine_line line; + if (!drgui_text_engine__first_line(pTL, &line)) + { + // We failed to retrieve the first line which is probably due to the text engine being empty. We just fake the first line to + // ensure we get the number 1 to be drawn. + drgui_font_metrics fontMetrics; + drgui_get_font_metrics(pTL->pDefaultFont, &fontMetrics); + + line.height = (float)fontMetrics.lineHeight; + line.posY = 0; + } + + do + { + float lineTop = line.posY + textRect.top; + float lineBottom = lineTop + line.height; + + if (lineTop < lineNumbersHeight) + { + if (lineBottom > 0) + { + char iLineStr[64]; + #ifdef _MSC_VER + _itoa_s(iLine, iLineStr, sizeof(iLineStr), 10); + #else + snprintf(iLineStr, sizeof(iLineStr), "%d", iLine); + #endif + + drgui_font* pFont = pTL->pDefaultFont; + + float textWidth; + float textHeight; + drgui_measure_string(pFont, iLineStr, strlen(iLineStr), &textWidth, &textHeight); + + drgui_text_run run = {0}; + run.pFont = pFont; + run.textColor = textColor; + run.backgroundColor = backgroundColor; + run.text = iLineStr; + run.textLength = strlen(iLineStr); + run.posX = lineNumbersWidth - textWidth; + run.posY = lineTop; + onPaintText(pTL, &run, pElement, pPaintData); + onPaintRect(pTL, drgui_make_rect(0, lineTop, run.posX, lineBottom), run.backgroundColor, pElement, pPaintData); + } + } + else + { + // The line is below the rectangle which means no other line will be visible and we can terminate early. + break; + } + + iLine += 1; + } while (drgui_text_engine__next_line(pTL, &line)); +} + + +bool drgui_text_engine_find_next(drgui_text_engine* pTL, const char* text, size_t* pSelectionStartOut, size_t* pSelectionEndOut) +{ + if (pTL == NULL || pTL->text == NULL || text == NULL || text[0] == '\0') { + return false; + } + + size_t cursorPos = drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->cursor); + char* nextOccurance = strstr(pTL->text + cursorPos, text); + if (nextOccurance == NULL) { + nextOccurance = strstr(pTL->text, text); + } + + if (nextOccurance == NULL) { + return false; + } + + if (pSelectionStartOut) { + *pSelectionStartOut = nextOccurance - pTL->text; + } + if (pSelectionEndOut) { + *pSelectionEndOut = (nextOccurance - pTL->text) + strlen(text); + } + + return true; +} + +bool drgui_text_engine_find_next_no_loop(drgui_text_engine* pTL, const char* text, size_t* pSelectionStartOut, size_t* pSelectionEndOut) +{ + if (pTL == NULL || pTL->text == NULL || text == NULL || text[0] == '\0') { + return false; + } + + size_t cursorPos = drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->cursor); + + char* nextOccurance = strstr(pTL->text + cursorPos, text); + if (nextOccurance == NULL) { + return false; + } + + if (pSelectionStartOut) { + *pSelectionStartOut = nextOccurance - pTL->text; + } + if (pSelectionEndOut) { + *pSelectionEndOut = (nextOccurance - pTL->text) + strlen(text); + } + + return true; +} + + + + +DRGUI_PRIVATE bool drgui_next_run_string(const char* runStart, const char* textEndPastNullTerminator, const char** pRunEndOut) +{ + assert(runStart <= textEndPastNullTerminator); + + if (runStart == NULL || runStart == textEndPastNullTerminator) + { + // String is empty. + return false; + } + + + char firstChar = runStart[0]; + if (firstChar == '\t') + { + // We loop until we hit anything that is not a tab character (tabs will be grouped together into a single run). + do + { + runStart += 1; + *pRunEndOut = runStart; + } while (runStart[0] != '\0' && runStart[0] == '\t'); + } + else if (firstChar == '\n') + { + runStart += 1; + *pRunEndOut = runStart; + } + else if (firstChar == '\0') + { + assert(runStart + 1 == textEndPastNullTerminator); + *pRunEndOut = textEndPastNullTerminator; + } + else + { + do + { + runStart += 1; + *pRunEndOut = runStart; + } while (runStart[0] != '\0' && runStart[0] != '\t' && runStart[0] != '\n'); + } + + return true; +} + +DRGUI_PRIVATE void drgui_text_engine__refresh(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + // We split the runs based on tabs and new-lines. We want to create runs for tabs and new-line characters as well because we want + // to have the entire string covered by runs for the sake of simplicity when it comes to editing. + // + // The first pass positions the runs based on a top-to-bottom, left-to-right alignment. The second pass then repositions the runs + // based on alignment. + + // Runs need to be cleared first. + drgui_text_engine__clear_text_runs(pTL); + + // The text bounds also need to be reset at the top. + pTL->textBoundsWidth = 0; + pTL->textBoundsHeight = 0; + + drgui_font_metrics defaultFontMetrics; + drgui_get_font_metrics(pTL->pDefaultFont, &defaultFontMetrics); + + pTL->textBoundsHeight = (float)defaultFontMetrics.lineHeight; + + const float tabWidth = drgui_text_engine__get_tab_width(pTL); + + size_t iCurrentLine = 0; + float runningPosY = 0; + float runningLineHeight = 0; + + const char* nextRunStart = pTL->text; + const char* nextRunEnd; + while (drgui_next_run_string(nextRunStart, pTL->text + pTL->textLength + 1, OUT &nextRunEnd)) + { + drgui_text_run run; + run.iLine = iCurrentLine; + run.iChar = nextRunStart - pTL->text; + run.iCharEnd = nextRunEnd - pTL->text; + run.textLength = nextRunEnd - nextRunStart; + run.width = 0; + run.height = 0; + run.posX = 0; + run.posY = runningPosY; + run.pFont = pTL->pDefaultFont; + + // X position + // + // The x position depends on the previous run that's on the same line. + if (pTL->runCount > 0) + { + drgui_text_run* pPrevRun = pTL->pRuns + (pTL->runCount - 1); + if (pPrevRun->iLine == iCurrentLine) + { + run.posX = pPrevRun->posX + pPrevRun->width; + } + else + { + // It's the first run on the line. + run.posX = 0; + } + } + + + // Width and height. + assert(nextRunEnd > nextRunStart); + if (nextRunStart[0] == '\t') + { + // Tab. + size_t tabCount = run.iCharEnd - run.iChar; + run.width = (float)(((tabCount*(size_t)tabWidth) - ((size_t)run.posX % (size_t)tabWidth))); + run.height = (float)defaultFontMetrics.lineHeight; + } + else if (nextRunStart[0] == '\n') + { + // New line. + iCurrentLine += 1; + run.width = 0; + run.height = (float)defaultFontMetrics.lineHeight; + } + else if (nextRunStart[0] == '\0') + { + // Null terminator. + run.width = 0; + run.height = (float)defaultFontMetrics.lineHeight; + run.textLength = 0; + } + else + { + // Normal run. + drgui_measure_string(pTL->pDefaultFont, nextRunStart, run.textLength, &run.width, &run.height); + } + + + // The running line height needs to be updated by setting to the maximum size. + runningLineHeight = (run.height > runningLineHeight) ? run.height : runningLineHeight; + + + // Update the text bounds. + if (pTL->textBoundsWidth < run.posX + run.width) { + pTL->textBoundsWidth = run.posX + run.width; + } + pTL->textBoundsHeight = runningPosY + runningLineHeight; + + + // A new line means we need to increment the running y position by the running line height. + if (nextRunStart[0] == '\n') + { + runningPosY += runningLineHeight; + runningLineHeight = 0; + } + + // Add the run to the internal list. + drgui_text_engine__push_text_run(pTL, &run); + + // Go to the next run string. + nextRunStart = nextRunEnd; + } + + // If we were to return now the text would be alignment top/left. If the alignment is not top/left we need to refresh the layout. + if (pTL->horzAlign != drgui_text_engine_alignment_left || pTL->vertAlign != drgui_text_engine_alignment_top) + { + drgui_text_engine__refresh_alignment(pTL); + } +} + +DRGUI_PRIVATE void drgui_text_engine__refresh_alignment(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + float runningPosY = 0; + + unsigned int iCurrentLine = 0; + for (size_t iRun = 0; iRun < pTL->runCount; /* Do Nothing*/) // iRun is incremented from within the loop. + { + float lineWidth = 0; + float lineHeight = 0; + + // This loop does a few things. First, it defines the end point for the loop after this one (jRun). Second, it calculates + // the line width which is needed for center and right alignment. Third it resets the position of each run to their + // unaligned equivalents which will be offsetted in the second loop. + size_t jRun; + for (jRun = iRun; jRun < pTL->runCount && pTL->pRuns[jRun].iLine == iCurrentLine; ++jRun) + { + drgui_text_run* pRun = pTL->pRuns + jRun; + pRun->posX = lineWidth; + pRun->posY = runningPosY; + + lineWidth += pRun->width; + lineHeight = (lineHeight > pRun->height) ? lineHeight : pRun->height; + } + + + // The actual alignment is done here. + float lineOffsetX; + float lineOffsetY; + drgui_text_engine__calculate_line_alignment_offset(pTL, lineWidth, OUT &lineOffsetX, OUT &lineOffsetY); + + for (/* Do Nothing*/; iRun < jRun; ++iRun) + { + drgui_text_run* pRun = pTL->pRuns + iRun; + pRun->posX += lineOffsetX; + pRun->posY += lineOffsetY; + } + + + // Go to the next line. + iCurrentLine += 1; + runningPosY += lineHeight; + } +} + +void drgui_text_engine__calculate_line_alignment_offset(drgui_text_engine* pTL, float lineWidth, float* pOffsetXOut, float* pOffsetYOut) +{ + if (pTL == NULL) { + return; + } + + float offsetX = 0; + float offsetY = 0; + + switch (pTL->horzAlign) + { + case drgui_text_engine_alignment_right: + { + offsetX = pTL->textBoundsWidth - lineWidth; + break; + } + + case drgui_text_engine_alignment_center: + { + offsetX = (pTL->textBoundsWidth - lineWidth) / 2; + break; + } + + case drgui_text_engine_alignment_left: + case drgui_text_engine_alignment_top: // Invalid for horizontal alignment. + case drgui_text_engine_alignment_bottom: // Invalid for horizontal alignment. + default: + { + offsetX = 0; + break; + } + } + + + switch (pTL->vertAlign) + { + case drgui_text_engine_alignment_bottom: + { + offsetY = pTL->textBoundsHeight - pTL->textBoundsHeight; + break; + } + + case drgui_text_engine_alignment_center: + { + offsetY = (pTL->textBoundsHeight - pTL->textBoundsHeight) / 2; + break; + } + + case drgui_text_engine_alignment_top: + case drgui_text_engine_alignment_left: // Invalid for vertical alignment. + case drgui_text_engine_alignment_right: // Invalid for vertical alignment. + default: + { + offsetY = 0; + break; + } + } + + + if (pOffsetXOut) { + *pOffsetXOut = offsetX; + } + if (pOffsetYOut) { + *pOffsetYOut = offsetY; + } +} + + +DRGUI_PRIVATE void drgui_text_engine__push_text_run(drgui_text_engine* pTL, drgui_text_run* pRun) +{ + if (pTL == NULL) { + return; + } + + if (pTL->runBufferSize == pTL->runCount) + { + pTL->runBufferSize = pTL->runBufferSize*2; + if (pTL->runBufferSize == 0) { + pTL->runBufferSize = 1; + } + + pTL->pRuns = (drgui_text_run*)realloc(pTL->pRuns, sizeof(drgui_text_run) * pTL->runBufferSize); + if (pTL->pRuns == NULL) { + pTL->runCount = 0; + pTL->runBufferSize = 0; + return; + } + } + + pTL->pRuns[pTL->runCount] = *pRun; + pTL->runCount += 1; +} + +DRGUI_PRIVATE void drgui_text_engine__clear_text_runs(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + pTL->runCount = 0; +} + +DRGUI_PRIVATE bool drgui_text_engine__is_text_run_whitespace(drgui_text_engine* pTL, drgui_text_run* pRun) +{ + if (pRun == NULL) { + return false; + } + + if (pTL->text[pRun->iChar] != '\t' && pTL->text[pRun->iChar] != '\n') + { + return false; + } + + return true; +} + +DRGUI_PRIVATE float drgui_text_engine__get_tab_width(drgui_text_engine* pTL) +{ + drgui_font_metrics defaultFontMetrics; + drgui_get_font_metrics(pTL->pDefaultFont, &defaultFontMetrics); + + return (float)(defaultFontMetrics.spaceWidth * pTL->tabSizeInSpaces); +} + + +DRGUI_PRIVATE bool drgui_text_engine__find_closest_line_to_point(drgui_text_engine* pTL, float inputPosYRelativeToText, size_t* pFirstRunIndexOnLineOut, size_t* pLastRunIndexOnLinePlus1Out) +{ + size_t iFirstRunOnLine = 0; + size_t iLastRunOnLinePlus1 = 0; + + bool result = true; + if (pTL == NULL || pTL->runCount == 0) + { + result = false; + } + else + { + float runningLineTop = 0; + + float lineHeight; + while (drgui_text_engine__find_line_info(pTL, iFirstRunOnLine, OUT &iLastRunOnLinePlus1, OUT &lineHeight)) + { + const float lineTop = runningLineTop; + const float lineBottom = lineTop + lineHeight; + + if (inputPosYRelativeToText < lineBottom) + { + // It's on this line. + break; + } + else + { + // It's not on this line - go to the next one, unless we're already on the last line. + if (iLastRunOnLinePlus1 == pTL->runCount) { + break; + } + + iFirstRunOnLine = iLastRunOnLinePlus1; + runningLineTop = lineBottom; + } + } + } + + if (pFirstRunIndexOnLineOut) { + *pFirstRunIndexOnLineOut = iFirstRunOnLine; + } + if (pLastRunIndexOnLinePlus1Out) { + *pLastRunIndexOnLinePlus1Out = iLastRunOnLinePlus1; + } + + return result; +} + +DRGUI_PRIVATE bool drgui_text_engine__find_closest_run_to_point(drgui_text_engine* pTL, float inputPosXRelativeToText, float inputPosYRelativeToText, size_t* pRunIndexOut) +{ + if (pTL == NULL) { + return false; + } + + size_t iFirstRunOnLine; + size_t iLastRunOnLinePlus1; + if (drgui_text_engine__find_closest_line_to_point(pTL, inputPosYRelativeToText, OUT &iFirstRunOnLine, OUT &iLastRunOnLinePlus1)) + { + size_t iRunOut = 0; + + const drgui_text_run* pFirstRunOnLine = pTL->pRuns + iFirstRunOnLine; + const drgui_text_run* pLastRunOnLine = pTL->pRuns + (iLastRunOnLinePlus1 - 1); + + if (inputPosXRelativeToText < pFirstRunOnLine->posX) + { + // It's to the left of the first run. + iRunOut = iFirstRunOnLine; + } + else if (inputPosXRelativeToText > pLastRunOnLine->posX + pLastRunOnLine->width) + { + // It's to the right of the last run. + iRunOut = iLastRunOnLinePlus1 - 1; + } + else + { + // It's in the middle of the line. We just iterate over each run on the line and return the first one that contains the point. + for (size_t iRun = iFirstRunOnLine; iRun < iLastRunOnLinePlus1; ++iRun) + { + const drgui_text_run* pRun = pTL->pRuns + iRun; + iRunOut = iRun; + + if (inputPosXRelativeToText >= pRun->posX && inputPosXRelativeToText <= pRun->posX + pRun->width) { + break; + } + } + } + + if (pRunIndexOut) { + *pRunIndexOut = iRunOut; + } + + return true; + } + else + { + // There was an error finding the closest line. + return false; + } +} + +DRGUI_PRIVATE bool drgui_text_engine__find_line_info(drgui_text_engine* pTL, size_t iFirstRunOnLine, size_t* pLastRunIndexOnLinePlus1Out, float* pLineHeightOut) +{ + if (pTL == NULL) { + return false; + } + + if (iFirstRunOnLine < pTL->runCount) + { + const size_t iLine = pTL->pRuns[iFirstRunOnLine].iLine; + float lineHeight = 0; + + size_t iRun; + for (iRun = iFirstRunOnLine; iRun < pTL->runCount && pTL->pRuns[iRun].iLine == iLine; ++iRun) + { + if (lineHeight < pTL->pRuns[iRun].height) { + lineHeight = pTL->pRuns[iRun].height; + } + } + + assert(iRun > iFirstRunOnLine); + + if (pLastRunIndexOnLinePlus1Out) { + *pLastRunIndexOnLinePlus1Out = iRun; + } + if (pLineHeightOut) { + *pLineHeightOut = lineHeight; + } + + return true; + } + + return false; +} + +DRGUI_PRIVATE bool drgui_text_engine__find_line_info_by_index(drgui_text_engine* pTL, size_t iLine, drgui_rect* pRectOut, size_t* pFirstRunIndexOut, size_t* pLastRunIndexPlus1Out) +{ + if (pTL == NULL) { + return false; + } + + size_t iFirstRunOnLine = 0; + size_t iLastRunOnLinePlus1 = 0; + + float lineTop = 0; + float lineHeight = 0; + + for (size_t iCurrentLine = 0; iCurrentLine <= iLine; ++iCurrentLine) + { + iFirstRunOnLine = iLastRunOnLinePlus1; + lineTop += lineHeight; + + if (!drgui_text_engine__find_line_info(pTL, iFirstRunOnLine, &iLastRunOnLinePlus1, &lineHeight)) + { + // There was an error retrieving information about the line. + return false; + } + } + + + // At this point we have the first and last runs that make up the line and we can generate our output. + if (iLastRunOnLinePlus1 > iFirstRunOnLine) + { + if (pFirstRunIndexOut) { + *pFirstRunIndexOut = iFirstRunOnLine; + } + if (pLastRunIndexPlus1Out) { + *pLastRunIndexPlus1Out = iLastRunOnLinePlus1; + } + + if (pRectOut != NULL) + { + pRectOut->left = pTL->pRuns[iFirstRunOnLine].posX; + pRectOut->right = pTL->pRuns[iLastRunOnLinePlus1 - 1].posX + pTL->pRuns[iLastRunOnLinePlus1 - 1].width; + pRectOut->top = lineTop; + pRectOut->bottom = pRectOut->top + lineHeight; + } + + return true; + } + else + { + // We couldn't find any runs. + return false; + } +} + +DRGUI_PRIVATE bool drgui_text_engine__find_last_run_on_line_starting_from_run(drgui_text_engine* pTL, size_t iRun, size_t* pLastRunIndexOnLineOut) +{ + if (pTL == NULL || pTL->pRuns == NULL) { + return false; + } + + size_t result = iRun; + + size_t iLine = pTL->pRuns[iRun].iLine; + for (/* Do Nothing*/; iRun < pTL->runCount && pTL->pRuns[iRun].iLine == iLine; ++iRun) + { + result = iRun; + } + + if (pLastRunIndexOnLineOut) { + *pLastRunIndexOnLineOut = result; + } + + return true; +} + +DRGUI_PRIVATE bool drgui_text_engine__find_first_run_on_line_starting_from_run(drgui_text_engine* pTL, size_t iRun, size_t* pFirstRunIndexOnLineOut) +{ + if (pTL == NULL) { + return false; + } + + size_t result = iRun; + + size_t iLine = pTL->pRuns[iRun].iLine; + for (/* Do Nothing*/; iRun > 0 && pTL->pRuns[iRun - 1].iLine == iLine; --iRun) + { + result = iRun - 1; + } + + if (pFirstRunIndexOnLineOut) { + *pFirstRunIndexOnLineOut = result; + } + + return true; +} + +DRGUI_PRIVATE bool drgui_text_engine__find_run_at_character(drgui_text_engine* pTL, size_t iChar, size_t* pRunIndexOut) +{ + if (pTL == NULL || pTL->runCount == 0) { + return false; + } + + size_t result = 0; + if (iChar < pTL->textLength) + { + for (size_t iRun = 0; iRun < pTL->runCount; ++iRun) + { + const drgui_text_run* pRun = pTL->pRuns + iRun; + + if (iChar < pRun->iCharEnd) + { + result = iRun; + break; + } + } + } + else + { + // The character index is too high. Return the last run. + result = pTL->runCount - 1; + } + + if (pRunIndexOut) { + *pRunIndexOut = result; + } + + return true; +} + + +DRGUI_PRIVATE drgui_text_marker drgui_text_engine__new_marker() +{ + drgui_text_marker marker; + marker.iRun = 0; + marker.iChar = 0; + marker.relativePosX = 0; + marker.absoluteSickyPosX = 0; + + return marker; +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_point_relative_to_container(drgui_text_engine* pTL, drgui_text_marker* pMarker, float inputPosX, float inputPosY) +{ + if (pTL == NULL || pMarker == NULL) { + return false; + } + + pMarker->iRun = 0; + pMarker->iChar = 0; + pMarker->relativePosX = 0; + pMarker->absoluteSickyPosX = 0; + + drgui_rect textRect = drgui_text_engine_get_text_rect_relative_to_bounds(pTL); + + float inputPosXRelativeToText = inputPosX - textRect.left; + float inputPosYRelativeToText = inputPosY - textRect.top; + if (drgui_text_engine__move_marker_to_point(pTL, pMarker, inputPosXRelativeToText, inputPosYRelativeToText)) + { + drgui_text_engine__update_marker_sticky_position(pTL, pMarker); + return true; + } + + return false; +} + +DRGUI_PRIVATE void drgui_text_engine__get_marker_position_relative_to_container(drgui_text_engine* pTL, drgui_text_marker* pMarker, float* pPosXOut, float* pPosYOut) +{ + if (pTL == NULL || pMarker == NULL) { + return; + } + + float posX = 0; + float posY = 0; + + if (pMarker->iRun < pTL->runCount) + { + posX = pTL->pRuns[pMarker->iRun].posX + pMarker->relativePosX; + posY = pTL->pRuns[pMarker->iRun].posY; + } + + drgui_rect textRect = drgui_text_engine_get_text_rect_relative_to_bounds(pTL); + posX += textRect.left; + posY += textRect.top; + + + if (pPosXOut) { + *pPosXOut = posX; + } + if (pPosYOut) { + *pPosYOut = posY; + } +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_point(drgui_text_engine* pTL, drgui_text_marker* pMarker, float inputPosXRelativeToText, float inputPosYRelativeToText) +{ + if (pTL == NULL || pMarker == NULL) { + return false; + } + + size_t iClosestRunToPoint; + if (drgui_text_engine__find_closest_run_to_point(pTL, inputPosXRelativeToText, inputPosYRelativeToText, OUT &iClosestRunToPoint)) + { + const drgui_text_run* pRun = pTL->pRuns + iClosestRunToPoint; + + pMarker->iRun = iClosestRunToPoint; + + if (inputPosXRelativeToText < pRun->posX) + { + // It's to the left of the run. + pMarker->iChar = 0; + pMarker->relativePosX = 0; + } + else if (inputPosXRelativeToText > pRun->posX + pRun->width) + { + // It's to the right of the run. It may be a new-line run. If so, we need to move the marker to the front of it, not the back. + pMarker->iChar = pRun->textLength; + pMarker->relativePosX = pRun->width; + + if (pTL->text[pRun->iChar] == '\n') { + assert(pMarker->iChar == 1); + pMarker->iChar = 0; + pMarker->relativePosX = 0; + } + } + else + { + // It's somewhere in the middle of the run. We need to handle this a little different for tab runs since they are aligned differently. + if (pTL->text[pRun->iChar] == '\n') + { + // It's a new line character. It needs to be placed at the beginning of it. + pMarker->iChar = 0; + pMarker->relativePosX = 0; + } + else if (pTL->text[pRun->iChar] == '\t') + { + // It's a tab run. + pMarker->iChar = 0; + pMarker->relativePosX = 0; + + const float tabWidth = drgui_text_engine__get_tab_width(pTL); + + float tabLeft = pRun->posX + pMarker->relativePosX; + for (/* Do Nothing*/; pMarker->iChar < pRun->textLength; ++pMarker->iChar) + { + float tabRight = tabWidth * ((pRun->posX + (tabWidth*(pMarker->iChar + 1))) / tabWidth); + if (tabRight > pRun->posX + pRun->width) { + tabRight = pRun->posX + pRun->width; + } + + if (inputPosXRelativeToText >= tabLeft && inputPosXRelativeToText <= tabRight) + { + // The input position is somewhere on top of this character. If it's positioned on the left side of the character, set the output + // value to the character at iChar. Otherwise it should be set to the character at iChar + 1. + float charBoundsRightHalf = tabLeft + ceilf(((tabRight - tabLeft) / 2.0f)); + if (inputPosXRelativeToText <= charBoundsRightHalf) { + pMarker->relativePosX = tabLeft - pRun->posX; + } else { + pMarker->relativePosX = tabRight - pRun->posX; + pMarker->iChar += 1; + } + + break; + } + + tabLeft = tabRight; + } + + // If we're past the last character in the tab run, we want to move to the start of the next run. + if (pMarker->iChar == pRun->textLength) { + drgui_text_engine__move_marker_to_first_character_of_next_run(pTL, pMarker); + } + } + else + { + // It's a standard run. + float inputPosXRelativeToRun = inputPosXRelativeToText - pRun->posX; + if (drgui_get_text_cursor_position_from_point(pRun->pFont, pTL->text + pRun->iChar, pRun->textLength, pRun->width, inputPosXRelativeToRun, OUT &pMarker->relativePosX, OUT &pMarker->iChar)) + { + // If the marker is past the last character of the run it needs to be moved to the start of the next one. + if (pMarker->iChar == pRun->textLength) { + drgui_text_engine__move_marker_to_first_character_of_next_run(pTL, pMarker); + } + } + else + { + // An error occured somehow. + return false; + } + } + } + + return true; + } + else + { + // Couldn't find a run. + return false; + } +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_left(drgui_text_engine* pTL, drgui_text_marker* pMarker) +{ + if (pTL == NULL || pMarker == NULL) { + return false; + } + + if (pTL->runCount > 0) + { + if (pMarker->iChar > 0) + { + pMarker->iChar -= 1; + + const drgui_text_run* pRun = pTL->pRuns + pMarker->iRun; + if (pTL->text[pRun->iChar] == '\t') + { + const float tabWidth = drgui_text_engine__get_tab_width(pTL); + + if (pMarker->iChar == 0) + { + // Simple case - it's the first tab character which means the relative position is just 0. + pMarker->relativePosX = 0; + } + else + { + pMarker->relativePosX = tabWidth * ((pRun->posX + (tabWidth*(pMarker->iChar + 0))) / tabWidth); + pMarker->relativePosX -= pRun->posX; + } + } + else + { + if (!drgui_get_text_cursor_position_from_char(pRun->pFont, pTL->text + pTL->pRuns[pMarker->iRun].iChar, pMarker->iChar, OUT &pMarker->relativePosX)) { + return false; + } + } + } + else + { + // We're at the beginning of the run which means we need to transfer the cursor to the end of the previous run. + if (!drgui_text_engine__move_marker_to_last_character_of_prev_run(pTL, pMarker)) { + return false; + } + } + + drgui_text_engine__update_marker_sticky_position(pTL, pMarker); + return true; + } + + return false; +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_right(drgui_text_engine* pTL, drgui_text_marker* pMarker) +{ + if (pTL == NULL || pMarker == NULL) { + return false; + } + + if (pTL->runCount > 0) + { + if (pMarker->iChar + 1 < pTL->pRuns[pMarker->iRun].textLength) + { + pMarker->iChar += 1; + + const drgui_text_run* pRun = pTL->pRuns + pMarker->iRun; + if (pTL->text[pRun->iChar] == '\t') + { + const float tabWidth = drgui_text_engine__get_tab_width(pTL); + + pMarker->relativePosX = tabWidth * ((pRun->posX + (tabWidth*(pMarker->iChar + 0))) / tabWidth); + pMarker->relativePosX -= pRun->posX; + } + else + { + if (!drgui_get_text_cursor_position_from_char(pRun->pFont, pTL->text + pTL->pRuns[pMarker->iRun].iChar, pMarker->iChar, OUT &pMarker->relativePosX)) { + return false; + } + } + } + else + { + // We're at the end of the run which means we need to transfer the cursor to the beginning of the next run. + if (!drgui_text_engine__move_marker_to_first_character_of_next_run(pTL, pMarker)) { + return false; + } + } + + drgui_text_engine__update_marker_sticky_position(pTL, pMarker); + return true; + } + + return false; +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_up(drgui_text_engine* pTL, drgui_text_marker* pMarker) +{ + if (pTL == NULL || pMarker == NULL || pTL->runCount == 0) { + return false; + } + + return drgui_text_engine__move_marker_y(pTL, pMarker, -1); +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_down(drgui_text_engine* pTL, drgui_text_marker* pMarker) +{ + if (pTL == NULL || pMarker == NULL || pTL->runCount == 0) { + return false; + } + + return drgui_text_engine__move_marker_y(pTL, pMarker, 1); +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_y(drgui_text_engine* pTL, drgui_text_marker* pMarker, int amount) +{ + if (pTL == NULL || pMarker == NULL || pTL->runCount == 0) { + return false; + } + + const drgui_text_run* pOldRun = pTL->pRuns + pMarker->iRun; + + int iNewLine = (int)pOldRun->iLine + amount; + if (iNewLine >= (int)drgui_text_engine_get_line_count(pTL)) { + iNewLine = (int)drgui_text_engine_get_line_count(pTL) - 1; + } + if (iNewLine < 0) { + iNewLine = 0; + } + + if ((int)pOldRun->iLine == iNewLine) { + return false; // The lines are the same. + } + + drgui_rect lineRect; + size_t iFirstRunOnLine; + size_t iLastRunOnLinePlus1; + if (drgui_text_engine__find_line_info_by_index(pTL, iNewLine, OUT &lineRect, OUT &iFirstRunOnLine, OUT &iLastRunOnLinePlus1)) + { + float newMarkerPosX = pMarker->absoluteSickyPosX; + float newMarkerPosY = lineRect.top; + drgui_text_engine__move_marker_to_point(pTL, pMarker, newMarkerPosX, newMarkerPosY); + + return true; + } + else + { + // An error occured while finding information about the line above. + return false; + } +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_end_of_line(drgui_text_engine* pTL, drgui_text_marker* pMarker) +{ + if (pTL == NULL || pMarker == NULL) { + return false; + } + + size_t iLastRunOnLine; + if (drgui_text_engine__find_last_run_on_line_starting_from_run(pTL, pMarker->iRun, &iLastRunOnLine)) + { + return drgui_text_engine__move_marker_to_last_character_of_run(pTL, pMarker, iLastRunOnLine); + } + + return false; +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_start_of_line(drgui_text_engine* pTL, drgui_text_marker* pMarker) +{ + if (pTL == NULL || pMarker == NULL) { + return false; + } + + size_t iFirstRunOnLine; + if (drgui_text_engine__find_first_run_on_line_starting_from_run(pTL, pMarker->iRun, &iFirstRunOnLine)) + { + return drgui_text_engine__move_marker_to_first_character_of_run(pTL, pMarker, iFirstRunOnLine); + } + + return false; +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_end_of_line_by_index(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iLine) +{ + if (pTL == NULL || pMarker == NULL) { + return false; + } + + size_t iFirstRun; + size_t iLastRunPlus1; + if (drgui_text_engine__find_line_info_by_index(pTL, iLine, NULL, &iFirstRun, &iLastRunPlus1)) + { + return drgui_text_engine__move_marker_to_last_character_of_run(pTL, pMarker, iLastRunPlus1 - 1); + } + + return false; +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_start_of_line_by_index(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iLine) +{ + if (pTL == NULL || pMarker == NULL) { + return false; + } + + size_t iFirstRun; + size_t iLastRunPlus1; + if (drgui_text_engine__find_line_info_by_index(pTL, iLine, NULL, &iFirstRun, &iLastRunPlus1)) + { + return drgui_text_engine__move_marker_to_first_character_of_run(pTL, pMarker, iFirstRun); + } + + return false; +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_end_of_text(drgui_text_engine* pTL, drgui_text_marker* pMarker) +{ + if (pTL == NULL || pMarker == NULL) { + return false; + } + + if (pTL->runCount > 0) { + return drgui_text_engine__move_marker_to_last_character_of_run(pTL, pMarker, pTL->runCount - 1); + } + + return false; +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_start_of_text(drgui_text_engine* pTL, drgui_text_marker* pMarker) +{ + if (pTL == NULL || pMarker == NULL) { + return false; + } + + return drgui_text_engine__move_marker_to_first_character_of_run(pTL, pMarker, 0); +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_last_character_of_run(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iRun) +{ + if (pTL == NULL || pMarker == NULL) { + return false; + } + + if (iRun < pTL->runCount) + { + pMarker->iRun = iRun; + pMarker->iChar = pTL->pRuns[pMarker->iRun].textLength; + pMarker->relativePosX = pTL->pRuns[pMarker->iRun].width; + + if (pMarker->iChar > 0) + { + // At this point we are located one character past the last character - we need to move it left. + return drgui_text_engine__move_marker_left(pTL, pMarker); + } + + return true; + } + + return false; +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_first_character_of_run(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iRun) +{ + if (pTL == NULL || pMarker == NULL) { + return false; + } + + if (iRun < pTL->runCount) + { + pMarker->iRun = iRun; + pMarker->iChar = 0; + pMarker->relativePosX = 0; + + drgui_text_engine__update_marker_sticky_position(pTL, pMarker); + + return true; + } + + return false; +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_last_character_of_prev_run(drgui_text_engine* pTL, drgui_text_marker* pMarker) +{ + if (pTL == NULL || pMarker == NULL) { + return false; + } + + if (pMarker->iRun > 0) { + return drgui_text_engine__move_marker_to_last_character_of_run(pTL, pMarker, pMarker->iRun - 1); + } + + return false; +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_first_character_of_next_run(drgui_text_engine* pTL, drgui_text_marker* pMarker) +{ + if (pTL == NULL || pMarker == NULL) { + return false; + } + + if (pTL->runCount > 0 && pMarker->iRun < pTL->runCount - 1) { + return drgui_text_engine__move_marker_to_first_character_of_run(pTL, pMarker, pMarker->iRun + 1); + } + + return false; +} + +DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_character(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iChar) +{ + if (pTL == NULL || pMarker == NULL || pTL->runCount == 0) { + return false; + } + + // Clamp the character to the end of the string. + if (iChar > pTL->textLength) { + iChar = pTL->textLength; + } + + drgui_text_engine__find_run_at_character(pTL, iChar, &pMarker->iRun); + + assert(pMarker->iRun < pTL->runCount); + pMarker->iChar = iChar - pTL->pRuns[pMarker->iRun].iChar; + + + // The relative position depends on whether or not the run is a tab character. + return drgui_text_engine__update_marker_relative_position(pTL, pMarker); +} + + +DRGUI_PRIVATE bool drgui_text_engine__update_marker_relative_position(drgui_text_engine* pTL, drgui_text_marker* pMarker) +{ + if (pTL == NULL || pMarker == NULL || pTL->runCount == 0) { + return false; + } + + const drgui_text_run* pRun = pTL->pRuns + pMarker->iRun; + if (pTL->text[pRun->iChar] == '\t') + { + const float tabWidth = drgui_text_engine__get_tab_width(pTL); + + if (pMarker->iChar == 0) + { + // Simple case - it's the first tab character which means the relative position is just 0. + pMarker->relativePosX = 0; + } + else + { + pMarker->relativePosX = tabWidth * ((pRun->posX + (tabWidth*(pMarker->iChar + 0))) / tabWidth); + pMarker->relativePosX -= pRun->posX; + } + + return true; + } + else + { + return drgui_get_text_cursor_position_from_char(pRun->pFont, pTL->text + pTL->pRuns[pMarker->iRun].iChar, pMarker->iChar, OUT &pMarker->relativePosX); + } +} + +DRGUI_PRIVATE void drgui_text_engine__update_marker_sticky_position(drgui_text_engine* pTL, drgui_text_marker* pMarker) +{ + if (pTL == NULL || pMarker == NULL) { + return; + } + + pMarker->absoluteSickyPosX = pTL->pRuns[pMarker->iRun].posX + pMarker->relativePosX; +} + +DRGUI_PRIVATE size_t drgui_text_engine__get_marker_absolute_char_index(drgui_text_engine* pTL, drgui_text_marker* pMarker) +{ + if (pTL == NULL || pMarker == NULL || pTL->runCount == 0) { + return 0; + } + + return pTL->pRuns[pMarker->iRun].iChar + pMarker->iChar; +} + + +DRGUI_PRIVATE bool drgui_text_engine__has_spacing_between_selection_markers(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return false; + } + + return (pTL->cursor.iRun != pTL->selectionAnchor.iRun || pTL->cursor.iChar != pTL->selectionAnchor.iChar); +} + +DRGUI_PRIVATE size_t drgui_text_engine__split_text_run_by_selection(drgui_text_engine* pTL, drgui_text_run* pRunToSplit, drgui_text_run pSubRunsOut[3]) +{ + if (pTL == NULL || pRunToSplit == NULL || pSubRunsOut == NULL) { + return 0; + } + + drgui_text_marker* pSelectionMarker0 = &pTL->selectionAnchor; + drgui_text_marker* pSelectionMarker1 = &pTL->cursor; + if (pTL->pRuns[pSelectionMarker0->iRun].iChar + pSelectionMarker0->iChar > pTL->pRuns[pSelectionMarker1->iRun].iChar + pSelectionMarker1->iChar) + { + drgui_text_marker* temp = pSelectionMarker0; + pSelectionMarker0 = pSelectionMarker1; + pSelectionMarker1 = temp; + } + + drgui_text_run* pSelectionRun0 = pTL->pRuns + pSelectionMarker0->iRun; + drgui_text_run* pSelectionRun1 = pTL->pRuns + pSelectionMarker1->iRun; + + size_t iSelectionChar0 = pSelectionRun0->iChar + pSelectionMarker0->iChar; + size_t iSelectionChar1 = pSelectionRun1->iChar + pSelectionMarker1->iChar; + + if (drgui_text_engine_is_anything_selected(pTL)) + { + if (pRunToSplit->iChar < iSelectionChar1 && pRunToSplit->iCharEnd > iSelectionChar0) + { + // The run is somewhere inside the selection region. + for (int i = 0; i < 3; ++i) { + pSubRunsOut[i] = *pRunToSplit; + } + + if (pRunToSplit->iChar >= iSelectionChar0) + { + // The first part of the run is selected. + if (pRunToSplit->iCharEnd <= iSelectionChar1) + { + // The entire run is selected. + pSubRunsOut[0].backgroundColor = pTL->selectionBackgroundColor; + return 1; + } + else + { + // The head part of the run is selected, and the tail is deselected. + + // Head. + pSubRunsOut[0].backgroundColor = pTL->selectionBackgroundColor; + pSubRunsOut[0].iCharEnd = iSelectionChar1; + pSubRunsOut[0].width = pSelectionMarker1->relativePosX; + pSubRunsOut[0].text = pTL->text + pSubRunsOut[0].iChar; + pSubRunsOut[0].textLength = pSubRunsOut[0].iCharEnd - pSubRunsOut[0].iChar; + + // Tail. + pSubRunsOut[1].iChar = iSelectionChar1; + pSubRunsOut[1].width = pRunToSplit->width - pSelectionMarker1->relativePosX; + pSubRunsOut[1].posX = pSubRunsOut[0].posX + pSubRunsOut[0].width; + pSubRunsOut[1].text = pTL->text + pSubRunsOut[1].iChar; + pSubRunsOut[1].textLength = pSubRunsOut[1].iCharEnd - pSubRunsOut[1].iChar; + + return 2; + } + } + else + { + // The first part of the run is deselected. There will be at least 2, but possibly 3 sub-runs in this case. + if (pRunToSplit->iCharEnd <= iSelectionChar1) + { + // The head of the run is deselected and the tail is selected. + + // Head. + pSubRunsOut[0].iCharEnd = iSelectionChar0; + pSubRunsOut[0].width = pSelectionMarker0->relativePosX; + pSubRunsOut[0].text = pTL->text + pSubRunsOut[0].iChar; + pSubRunsOut[0].textLength = pSubRunsOut[0].iCharEnd - pSubRunsOut[0].iChar; + + // Tail. + pSubRunsOut[1].backgroundColor = pTL->selectionBackgroundColor; + pSubRunsOut[1].iChar = iSelectionChar0; + pSubRunsOut[1].width = pRunToSplit->width - pSubRunsOut[0].width; + pSubRunsOut[1].posX = pSubRunsOut[0].posX + pSubRunsOut[0].width; + pSubRunsOut[1].text = pTL->text + pSubRunsOut[1].iChar; + pSubRunsOut[1].textLength = pSubRunsOut[1].iCharEnd - pSubRunsOut[1].iChar; + + return 2; + } + else + { + // The head and tail are both deselected, and the middle section is selected. + + // Head. + pSubRunsOut[0].iCharEnd = iSelectionChar0; + pSubRunsOut[0].width = pSelectionMarker0->relativePosX; + pSubRunsOut[0].text = pTL->text + pSubRunsOut[0].iChar; + pSubRunsOut[0].textLength = pSubRunsOut[0].iCharEnd - pSubRunsOut[0].iChar; + + // Mid. + pSubRunsOut[1].iChar = iSelectionChar0; + pSubRunsOut[1].iCharEnd = iSelectionChar1; + pSubRunsOut[1].backgroundColor = pTL->selectionBackgroundColor; + pSubRunsOut[1].width = pSelectionMarker1->relativePosX - pSelectionMarker0->relativePosX; + pSubRunsOut[1].posX = pSubRunsOut[0].posX + pSubRunsOut[0].width; + pSubRunsOut[1].text = pTL->text + pSubRunsOut[1].iChar; + pSubRunsOut[1].textLength = pSubRunsOut[1].iCharEnd - pSubRunsOut[1].iChar; + + // Tail. + pSubRunsOut[2].iChar = iSelectionChar1; + pSubRunsOut[2].width = pRunToSplit->width - pSelectionMarker1->relativePosX; + pSubRunsOut[2].posX = pSubRunsOut[1].posX + pSubRunsOut[1].width; + pSubRunsOut[2].text = pTL->text + pSubRunsOut[2].iChar; + pSubRunsOut[2].textLength = pSubRunsOut[2].iCharEnd - pSubRunsOut[2].iChar; + + return 3; + } + } + } + } + + // If we get here it means the run is not within the selected region. + pSubRunsOut[0] = *pRunToSplit; + return 1; +} + +#if 0 +DRGUI_PRIVATE bool drgui_text_engine__is_run_selected(drgui_text_engine* pTL, unsigned int iRun) +{ + if (drgui_text_engine_is_anything_selected(pTL)) + { + drgui_text_marker* pSelectionMarker0; + drgui_text_marker* pSelectionMarker1; + if (!drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) { + return false; + } + + unsigned int iSelectionChar0 = pTL->pRuns[pSelectionMarker0->iRun].iChar + pSelectionMarker0->iChar; + unsigned int iSelectionChar1 = pTL->pRuns[pSelectionMarker1->iRun].iChar + pSelectionMarker1->iChar; + + return pTL->pRuns[iRun].iChar < iSelectionChar1 && pTL->pRuns[iRun].iCharEnd > iSelectionChar0; + } + + return false; +} +#endif + +DRGUI_PRIVATE bool drgui_text_engine__get_selection_markers(drgui_text_engine* pTL, drgui_text_marker** ppSelectionMarker0Out, drgui_text_marker** ppSelectionMarker1Out) +{ + bool result = false; + + drgui_text_marker* pSelectionMarker0 = NULL; + drgui_text_marker* pSelectionMarker1 = NULL; + if (pTL != NULL && drgui_text_engine_is_anything_selected(pTL)) + { + pSelectionMarker0 = &pTL->selectionAnchor; + pSelectionMarker1 = &pTL->cursor; + if (pTL->pRuns[pSelectionMarker0->iRun].iChar + pSelectionMarker0->iChar > pTL->pRuns[pSelectionMarker1->iRun].iChar + pSelectionMarker1->iChar) + { + drgui_text_marker* temp = pSelectionMarker0; + pSelectionMarker0 = pSelectionMarker1; + pSelectionMarker1 = temp; + } + + result = true; + } + + if (ppSelectionMarker0Out) { + *ppSelectionMarker0Out = pSelectionMarker0; + } + if (ppSelectionMarker1Out) { + *ppSelectionMarker1Out = pSelectionMarker1; + } + + return result; +} + + +DRGUI_PRIVATE bool drgui_text_engine__first_line(drgui_text_engine* pTL, drgui_text_engine_line* pLine) +{ + if (pTL == NULL || pLine == NULL || pTL->runCount == 0) { + return false; + } + + pLine->index = 0; + pLine->posY = 0; + pLine->height = 0; + pLine->iFirstRun = 0; + pLine->iLastRun = 0; + + // We need to find the last run in the line and the height. The height is determined by the run with the largest height. + while (pLine->iLastRun < pTL->runCount) + { + if (pLine->height < pTL->pRuns[pLine->iLastRun].height) { + pLine->height = pTL->pRuns[pLine->iLastRun].height; + } + + pLine->iLastRun += 1; + + if (pTL->pRuns[pLine->iLastRun].iLine != pLine->index) { + break; + } + } + + if (pLine->iLastRun > 0) { + pLine->iLastRun -= 1; + } + + return true; +} + +DRGUI_PRIVATE bool drgui_text_engine__next_line(drgui_text_engine* pTL, drgui_text_engine_line* pLine) +{ + if (pTL == NULL || pLine == NULL || pTL->runCount == 0) { + return false; + } + + // If there's no more runs, there is no next line. + if (pLine->iLastRun == pTL->runCount - 1) { + return false; + } + + pLine->index += 1; + pLine->posY += pLine->height; + pLine->height = 0; + pLine->iFirstRun = pLine->iLastRun + 1; + pLine->iLastRun = pLine->iFirstRun; + + while (pLine->iLastRun < pTL->runCount) + { + if (pLine->height < pTL->pRuns[pLine->iLastRun].height) { + pLine->height = pTL->pRuns[pLine->iLastRun].height; + } + + pLine->iLastRun += 1; + + if (pTL->pRuns[pLine->iLastRun].iLine != pLine->index) { + break; + } + } + + if (pLine->iLastRun > 0) { + pLine->iLastRun -= 1; + } + + return true; +} + +DRGUI_PRIVATE void drgui_text_engine__trim_undo_stack(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + while (pTL->undoStackCount > pTL->iUndoState) + { + unsigned int iLastItem = pTL->undoStackCount - 1; + + drgui_text_engine__uninit_undo_state(pTL->pUndoStack + iLastItem); + pTL->undoStackCount -= 1; + } +} + +DRGUI_PRIVATE bool drgui_text_engine__diff_states(drgui_text_engine_state* pPrevState, drgui_text_engine_state* pCurrentState, drgui_text_engine_undo_state* pUndoStateOut) +{ + if (pPrevState == NULL || pCurrentState == NULL || pUndoStateOut == NULL) { + return false; + } + + if (pPrevState->text == NULL || pCurrentState->text == NULL) { + return false; + } + + const char* prevText = pPrevState->text; + const char* currText = pCurrentState->text; + + const size_t prevLen = strlen(prevText); + const size_t currLen = strlen(currText); + + + // The first step is to find the position of the first differing character. + size_t sameChCountStart; + for (sameChCountStart = 0; sameChCountStart < prevLen && sameChCountStart < currLen; ++sameChCountStart) + { + char prevCh = prevText[sameChCountStart]; + char currCh = currText[sameChCountStart]; + + if (prevCh != currCh) { + break; + } + } + + // The next step is to find the position of the last differing character. + size_t sameChCountEnd; + for (sameChCountEnd = 0; sameChCountEnd < prevLen && sameChCountEnd < currLen; ++sameChCountEnd) + { + // Don't move beyond the first differing character. + if (prevLen - sameChCountEnd <= sameChCountStart || + currLen - sameChCountEnd <= sameChCountStart) + { + break; + } + + char prevCh = prevText[prevLen - sameChCountEnd - 1]; + char currCh = currText[currLen - sameChCountEnd - 1]; + + if (prevCh != currCh) { + break; + } + } + + + // At this point we know which section of the text is different. We now need to initialize the undo state object. + pUndoStateOut->diffPos = sameChCountStart; + pUndoStateOut->newState = *pCurrentState; + pUndoStateOut->newState.text = NULL; + pUndoStateOut->oldState = *pPrevState; + pUndoStateOut->oldState.text = NULL; + + size_t oldTextLen = prevLen - sameChCountStart - sameChCountEnd; + pUndoStateOut->oldText = (char*)malloc(oldTextLen + 1); + drgui__strncpy_s(pUndoStateOut->oldText, oldTextLen + 1, prevText + sameChCountStart, oldTextLen); + + size_t newTextLen = currLen - sameChCountStart - sameChCountEnd; + pUndoStateOut->newText = (char*)malloc(newTextLen + 1); + drgui__strncpy_s(pUndoStateOut->newText, newTextLen + 1, currText + sameChCountStart, newTextLen); + + return true; +} + +DRGUI_PRIVATE void drgui_text_engine__uninit_undo_state(drgui_text_engine_undo_state* pUndoState) +{ + if (pUndoState == NULL) { + return; + } + + free(pUndoState->oldText); + pUndoState->oldText = NULL; + + free(pUndoState->newText); + pUndoState->newText = NULL; +} + +DRGUI_PRIVATE void drgui_text_engine__push_undo_state(drgui_text_engine* pTL, drgui_text_engine_undo_state* pUndoState) +{ + if (pTL == NULL || pUndoState == NULL) { + return; + } + + assert(pTL->iUndoState == pTL->undoStackCount); + + + drgui_text_engine_undo_state* pOldStack = pTL->pUndoStack; + drgui_text_engine_undo_state* pNewStack = (drgui_text_engine_undo_state*)malloc(sizeof(*pNewStack) * (pTL->undoStackCount + 1)); + + if (pTL->undoStackCount > 0) { + memcpy(pNewStack, pOldStack, sizeof(*pNewStack) * (pTL->undoStackCount)); + } + + pNewStack[pTL->undoStackCount] = *pUndoState; + pTL->pUndoStack = pNewStack; + pTL->undoStackCount += 1; + pTL->iUndoState += 1; + + if (pTL->onUndoPointChanged) { + pTL->onUndoPointChanged(pTL, pTL->iUndoState); + } + + free(pOldStack); +} + +DRGUI_PRIVATE void drgui_text_engine__apply_undo_state(drgui_text_engine* pTL, drgui_text_engine_undo_state* pUndoState) +{ + if (pTL == NULL || pUndoState == NULL) { + return; + } + + // When undoing we want to remove the new text and replace it with the old text. + + size_t iFirstCh = pUndoState->diffPos; + size_t iLastChPlus1 = pUndoState->diffPos + strlen(pUndoState->newText); + size_t bytesToRemove = iLastChPlus1 - iFirstCh; + if (bytesToRemove > 0) + { + memmove(pTL->text + iFirstCh, pTL->text + iLastChPlus1, pTL->textLength - iLastChPlus1); + pTL->textLength -= bytesToRemove; + pTL->text[pTL->textLength] = '\0'; + } + + // TODO: This needs improving because it results in multiple onTextChanged and onDirty events being posted. + + // Insert the old text. + drgui_text_engine_insert_text(pTL, pUndoState->oldText, pUndoState->diffPos); + + + // The layout will have changed so it needs to be refreshed. + drgui_text_engine__refresh(pTL); + + + // Markers needs to be updated after refreshing the layout. + drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, pUndoState->oldState.cursorPos); + drgui_text_engine__move_marker_to_character(pTL, &pTL->selectionAnchor, pUndoState->oldState.selectionAnchorPos); + + // The cursor's sticky position needs to be updated whenever the text is edited. + drgui_text_engine__update_marker_sticky_position(pTL, &pTL->cursor); + + // Ensure we mark the text as selected if appropriate. + pTL->isAnythingSelected = pUndoState->oldState.isAnythingSelected; + + + if (pTL->onTextChanged) { + pTL->onTextChanged(pTL); + } + + drgui_text_engine__on_cursor_move(pTL); + + if (pTL->onDirty) { + pTL->onDirty(pTL, drgui_text_engine__local_rect(pTL)); + } +} + +DRGUI_PRIVATE void drgui_text_engine__apply_redo_state(drgui_text_engine* pTL, drgui_text_engine_undo_state* pUndoState) +{ + if (pTL == NULL || pUndoState == NULL) { + return; + } + + // An redo is just the opposite of an undo. We want to remove the old text and replace it with the new text. + + size_t iFirstCh = pUndoState->diffPos; + size_t iLastChPlus1 = pUndoState->diffPos + strlen(pUndoState->oldText); + size_t bytesToRemove = iLastChPlus1 - iFirstCh; + if (bytesToRemove > 0) + { + memmove(pTL->text + iFirstCh, pTL->text + iLastChPlus1, pTL->textLength - iLastChPlus1); + pTL->textLength -= bytesToRemove; + pTL->text[pTL->textLength] = '\0'; + } + + // TODO: This needs improving because it results in multiple onTextChanged and onDirty events being posted. + + // Insert the new text. + drgui_text_engine_insert_text(pTL, pUndoState->newText, pUndoState->diffPos); + + + // The layout will have changed so it needs to be refreshed. + drgui_text_engine__refresh(pTL); + + + // Markers needs to be updated after refreshing the layout. + drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, pUndoState->newState.cursorPos); + drgui_text_engine__move_marker_to_character(pTL, &pTL->selectionAnchor, pUndoState->newState.selectionAnchorPos); + + // The cursor's sticky position needs to be updated whenever the text is edited. + drgui_text_engine__update_marker_sticky_position(pTL, &pTL->cursor); + + // Ensure we mark the text as selected if appropriate. + pTL->isAnythingSelected = pUndoState->newState.isAnythingSelected; + + + if (pTL->onTextChanged) { + pTL->onTextChanged(pTL); + } + + drgui_text_engine__on_cursor_move(pTL); + + if (pTL->onDirty) { + pTL->onDirty(pTL, drgui_text_engine__local_rect(pTL)); + } +} + + +DRGUI_PRIVATE drgui_rect drgui_text_engine__local_rect(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return drgui_make_rect(0, 0, 0, 0); + } + + return drgui_make_rect(0, 0, pTL->containerWidth, pTL->containerHeight); +} + + +DRGUI_PRIVATE void drgui_text_engine__on_cursor_move(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + // When the cursor moves we want to reset the cursor's blink state. + pTL->timeToNextCursorBlink = pTL->cursorBlinkRate; + pTL->isCursorBlinkOn = true; + + if (pTL->onCursorMove) { + pTL->onCursorMove(pTL); + } + + drgui_text_engine__on_dirty(pTL, drgui_text_engine_get_cursor_rect(pTL)); +} + +DRGUI_PRIVATE void drgui_text_engine__on_dirty(drgui_text_engine* pTL, drgui_rect rect) +{ + drgui_text_engine__begin_dirty(pTL); + { + pTL->accumulatedDirtyRect = drgui_rect_union(pTL->accumulatedDirtyRect, rect); + } + drgui_text_engine__end_dirty(pTL); +} + +DRGUI_PRIVATE void drgui_text_engine__begin_dirty(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + pTL->dirtyCounter += 1; +} + +DRGUI_PRIVATE void drgui_text_engine__end_dirty(drgui_text_engine* pTL) +{ + if (pTL == NULL) { + return; + } + + assert(pTL->dirtyCounter > 0); + + pTL->dirtyCounter -= 1; + + if (pTL->dirtyCounter == 0) { + if (pTL->onDirty) { + pTL->onDirty(pTL, pTL->accumulatedDirtyRect); + } + + pTL->accumulatedDirtyRect = drgui_make_inside_out_rect(); + } +} +#endif //DR_GUI_IMPLEMENTATION +#endif + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// Scrollbar +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef drgui_scrollbar_h +#define drgui_scrollbar_h + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum +{ + drgui_sb_orientation_none, + drgui_sb_orientation_vertical, + drgui_sb_orientation_horizontal + +} drgui_sb_orientation; + +typedef void (* drgui_sb_on_scroll_proc)(drgui_element* pSBElement, int scrollPos); + + +/// Creates a scrollbar element. +drgui_element* drgui_create_scrollbar(drgui_context* pContext, drgui_element* pParent, drgui_sb_orientation orientation, size_t extraDataSize, const void* pExtraData); + +/// Deletes the given scrollbar element. +void drgui_delete_scrollbar(drgui_element* pSBElement); + + +/// Retrieves the size of the extra data associated with the scrollbar. +size_t drgui_sb_get_extra_data_size(drgui_element* pSBElement); + +/// Retrieves a pointer to the extra data associated with the scrollbar. +void* drgui_sb_get_extra_data(drgui_element* pSBElement); + + +/// Retrieves the orientation of the given scrollbar. +drgui_sb_orientation drgui_sb_get_orientation(drgui_element* pSBElement); + + +/// Sets the given scrollbar's range. +void drgui_sb_set_range(drgui_element* pSBElement, int rangeMin, int rangeMax); + +/// Retrieves the given scrollbar's range. +void drgui_sb_get_range(drgui_element* pSBElement, int* pRangeMinOut, int* pRangeMaxOut); + + +/// Sets the page size of the given scrollbar's page. +void drgui_sb_set_page_size(drgui_element* pSBElement, int pageSize); + +/// Retrieves the page size of the given scrollbar's page. +int drgui_sb_get_page_size(drgui_element* pSBElement); + + +/// Sets the range and page size. +/// +/// @remarks +/// Use this when both the range and page size need to be updated at the same time. +void drgui_sb_set_range_and_page_size(drgui_element* pSBElement, int rangeMin, int rangeMax, int pageSize); + + +/// Explicitly sets the scroll position. +/// +/// @remarks +/// This will move the thumb, but not post the on_scroll event. +/// @par +/// The scroll position will be clamped to the current range, minus the page size. +void drgui_sb_set_scroll_position(drgui_element* pSBElement, int position); + +/// Retrieves the scroll position. +int drgui_sb_get_scroll_position(drgui_element* pSBElement); + + +/// Scrolls by the given amount. +/// +/// @remarks +/// If the resulting scroll position differs from the old one, the on on_scroll event will be posted. +void drgui_sb_scroll(drgui_element* pSBElement, int offset); + +/// Scrolls to the given position. +/// +/// @remarks +/// This differs from drgui_sb_set_scroll_position in that it will post the on_scroll event. +/// @par +/// Note that the actual maximum scrollable position is equal to the maximum range value minus the page size. +void drgui_sb_scroll_to(drgui_element* pSBElement, int newScrollPos); + + +/// Enables auto-hiding of the thumb. +void drgui_sb_enable_thumb_auto_hide(drgui_element* pSBElement); + +/// Disables auto-hiding of the thumb. +void drgui_sb_disable_thumb_auto_hide(drgui_element* pSBElement); + +/// Determines whether or not thumb auto-hiding is enabled. +bool drgui_sb_is_thumb_auto_hide_enabled(drgui_element* pSBElement); + +/// Determines whether or not the thumb is visible. +/// +/// @remarks +/// This is determined by whether or not the thumb is set to auto-hide and the current range and page size. +bool drgui_sb_is_thumb_visible(drgui_element* pSBElement); + + +/// Sets the mouse wheel scale. +/// +/// @remarks +/// Set this to a negative value to reverse the direction. +void drgui_sb_set_mouse_wheel_scele(drgui_element* pSBElement, int scale); + +/// Retrieves the mouse wheel scale. +int drgui_sb_get_mouse_wheel_scale(drgui_element* pSBElement); + + +/// Sets the color of the track. +void drgui_sb_set_track_color(drgui_element* pSBElement, drgui_color color); + +/// Sets the default color of the thumb. +void drgui_sb_set_default_thumb_color(drgui_element* pSBElement, drgui_color color); + +/// Sets the hovered color of the thumb. +void drgui_sb_set_hovered_thumb_color(drgui_element* pSBElement, drgui_color color); + +/// Sets the pressed color of the thumb. +void drgui_sb_set_pressed_thumb_color(drgui_element* pSBElement, drgui_color color); + + +/// Sets the function to call when the given scrollbar is scrolled. +void drgui_sb_set_on_scroll(drgui_element* pSBElement, drgui_sb_on_scroll_proc onScroll); + +/// Retrieves the function call when the given scrollbar is scrolled. +drgui_sb_on_scroll_proc drgui_sb_get_on_scroll(drgui_element* pSBElement); + + +/// Calculates the relative rectangle of the given scrollbar's thumb. +drgui_rect drgui_sb_get_thumb_rect(drgui_element* pSBElement); + + +/// Called when the size event needs to be processed for the given scrollbar. +void drgui_sb_on_size(drgui_element* pSBElement, float newWidth, float newHeight); + +/// Called when the mouse leave event needs to be processed for the given scrollbar. +void drgui_sb_on_mouse_leave(drgui_element* pSBElement); + +/// Called when the mouse move event needs to be processed for the given scrollbar. +void drgui_sb_on_mouse_move(drgui_element* pSBElement, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when the mouse button down event needs to be processed for the given scrollbar. +void drgui_sb_on_mouse_button_down(drgui_element* pSBElement, int button, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when the mouse button up event needs to be processed for the given scrollbar. +void drgui_sb_on_mouse_button_up(drgui_element* pSBElement, int button, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when the mouse wheel event needs to be processed for the given scrollbar. +void drgui_sb_on_mouse_wheel(drgui_element* pSBElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when the paint event needs to be processed. +void drgui_sb_on_paint(drgui_element* pSBElement, drgui_rect relativeClippingRect, void* pPaintData); + + +#ifdef __cplusplus +} +#endif +#endif //drgui_scrollbar_h + + +#ifdef DR_GUI_IMPLEMENTATION + +#define DRGUI_MIN_SCROLLBAR_THUMB_SIZE 16 + +typedef struct +{ + /// The orientation. + drgui_sb_orientation orientation; + + /// The minimum scroll range. + int rangeMin; + + /// The maximum scroll range. + int rangeMax; + + /// The page size. + int pageSize; + + /// The current scroll position. + int scrollPos; + + /// Whether or not to auto-hide the thumb. + bool autoHideThumb; + + /// The mouse wheel scale. + int mouseWheelScale; + + /// The color of the track. + drgui_color trackColor; + + /// The color of the thumb while not hovered or pressed. + drgui_color thumbColor; + + /// The color of the thumb while hovered. + drgui_color thumbColorHovered; + + /// The color of the thumb while pressed. + drgui_color thumbColorPressed; + + /// The function to call when the scroll position changes. + drgui_sb_on_scroll_proc onScroll; + + + /// The current size of the thumb. + float thumbSize; + + /// The current position of the thumb. + float thumbPos; + + /// The amount of padding between the edge of the scrollbar and the thumb. + float thumbPadding; + + /// Whether or not we are hovered over the thumb. + bool thumbHovered; + + /// Whether or not the thumb is pressed. + bool thumbPressed; + + /// The relative position of the mouse on the x axis at the time the thumb was pressed with the mouse. + float thumbClickPosX; + + /// The relative position of the mouse on the y axis at the time the thumb was pressed with the mouse. + float thumbClickPosY; + + + /// The size of the extra data. + size_t extraDataSize; + + /// A pointer to the extra data. + char pExtraData[1]; + +} drgui_scrollbar; + + +/// Refreshes the given scrollbar's thumb layout and redraws it. +DRGUI_PRIVATE void drgui_sb_refresh_thumb(drgui_element* pSBElement); + +/// Calculates the size of the thumb. This does not change the state of the thumb. +DRGUI_PRIVATE float drgui_sb_calculate_thumb_size(drgui_element* pSBElement); + +/// Calculates the position of the thumb. This does not change the state of the thumb. +DRGUI_PRIVATE float drgui_sb_calculate_thumb_position(drgui_element* pSBElement); + +/// Retrieves the size of the given scrollbar's track. For vertical alignments, it's the height of the element, otherwise it's the width. +DRGUI_PRIVATE float drgui_sb_get_track_size(drgui_element* pSBElement); + +/// Makes the given point that's relative to the given scrollbar relative to it's thumb. +DRGUI_PRIVATE void drgui_sb_make_relative_to_thumb(drgui_element* pSBElement, float* pPosX, float* pPosY); + +/// Calculates the scroll position based on the current position of the thumb. This is used for scrolling while dragging the thumb. +DRGUI_PRIVATE int drgui_sb_calculate_scroll_pos_from_thumb_pos(drgui_element* pScrollba, float thumbPosr); + +/// Simple clamp function. +DRGUI_PRIVATE float drgui_sb_clampf(float n, float lower, float upper) +{ + return n <= lower ? lower : n >= upper ? upper : n; +} + +/// Simple clamp function. +DRGUI_PRIVATE int drgui_sb_clampi(int n, int lower, int upper) +{ + return n <= lower ? lower : n >= upper ? upper : n; +} + +/// Simple max function. +DRGUI_PRIVATE int drgui_sb_maxi(int x, int y) +{ + return (x > y) ? x : y; +} + + +drgui_element* drgui_create_scrollbar(drgui_context* pContext, drgui_element* pParent, drgui_sb_orientation orientation, size_t extraDataSize, const void* pExtraData) +{ + if (pContext == NULL || orientation == drgui_sb_orientation_none) { + return NULL; + } + + drgui_element* pSBElement = drgui_create_element(pContext, pParent, sizeof(drgui_scrollbar) + extraDataSize, NULL); + if (pSBElement == NULL) { + return NULL; + } + + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + assert(pSB != NULL); + + pSB->orientation = orientation; + pSB->rangeMin = 0; + pSB->rangeMax = 0; + pSB->pageSize = 0; + pSB->scrollPos = 0; + pSB->autoHideThumb = true; + pSB->mouseWheelScale = 1; + pSB->trackColor = drgui_rgb(80, 80, 80); + pSB->thumbColor = drgui_rgb(112, 112, 112); + pSB->thumbColorHovered = drgui_rgb(144, 144, 144); + pSB->thumbColorPressed = drgui_rgb(180, 180, 180); + pSB->onScroll = NULL; + + pSB->thumbSize = DRGUI_MIN_SCROLLBAR_THUMB_SIZE; + pSB->thumbPos = 0; + pSB->thumbPadding = 2; + pSB->thumbHovered = false; + pSB->thumbPressed = false; + pSB->thumbClickPosX = 0; + pSB->thumbClickPosY = 0; + + pSB->extraDataSize = extraDataSize; + if (pExtraData != NULL) { + memcpy(pSB->pExtraData, pExtraData, extraDataSize); + } + + + // Default event handlers. + drgui_set_on_size(pSBElement, drgui_sb_on_size); + drgui_set_on_mouse_leave(pSBElement, drgui_sb_on_mouse_leave); + drgui_set_on_mouse_move(pSBElement, drgui_sb_on_mouse_move); + drgui_set_on_mouse_button_down(pSBElement, drgui_sb_on_mouse_button_down); + drgui_set_on_mouse_button_up(pSBElement, drgui_sb_on_mouse_button_up); + drgui_set_on_mouse_wheel(pSBElement, drgui_sb_on_mouse_wheel); + drgui_set_on_paint(pSBElement, drgui_sb_on_paint); + + + return pSBElement; +} + +void drgui_delete_scrollbar(drgui_element* pSBElement) +{ + if (pSBElement == NULL) { + return; + } + + drgui_delete_element(pSBElement); +} + + +size_t drgui_sb_get_extra_data_size(drgui_element* pSBElement) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return 0; + } + + return pSB->extraDataSize; +} + +void* drgui_sb_get_extra_data(drgui_element* pSBElement) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return NULL; + } + + return pSB->pExtraData; +} + + +drgui_sb_orientation drgui_sb_get_orientation(drgui_element* pSBElement) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return drgui_sb_orientation_none; + } + + return pSB->orientation; +} + + +void drgui_sb_set_range(drgui_element* pSBElement, int rangeMin, int rangeMax) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + pSB->rangeMin = rangeMin; + pSB->rangeMax = rangeMax; + + + // Make sure the scroll position is still valid. + drgui_sb_scroll_to(pSBElement, drgui_sb_get_scroll_position(pSBElement)); + + // The thumb may have changed, so refresh it. + drgui_sb_refresh_thumb(pSBElement); +} + +void drgui_sb_get_range(drgui_element* pSBElement, int* pRangeMinOut, int* pRangeMaxOut) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + if (pRangeMinOut != NULL) { + *pRangeMinOut = pSB->rangeMin; + } + + if (pRangeMaxOut != NULL) { + *pRangeMaxOut = pSB->rangeMax; + } +} + + +void drgui_sb_set_page_size(drgui_element* pSBElement, int pageSize) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + pSB->pageSize = pageSize; + + + // Make sure the scroll position is still valid. + drgui_sb_scroll_to(pSBElement, drgui_sb_get_scroll_position(pSBElement)); + + // The thumb may have changed, so refresh it. + drgui_sb_refresh_thumb(pSBElement); +} + +int drgui_sb_get_page_size(drgui_element* pSBElement) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return 0; + } + + return pSB->pageSize; +} + + +void drgui_sb_set_range_and_page_size(drgui_element* pSBElement, int rangeMin, int rangeMax, int pageSize) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + pSB->rangeMin = rangeMin; + pSB->rangeMax = rangeMax; + pSB->pageSize = pageSize; + + + // Make sure the scroll position is still valid. + drgui_sb_scroll_to(pSBElement, drgui_sb_get_scroll_position(pSBElement)); + + // The thumb may have changed, so refresh it. + drgui_sb_refresh_thumb(pSBElement); +} + + +void drgui_sb_set_scroll_position(drgui_element* pSBElement, int position) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + int newScrollPos = drgui_sb_clampi(position, pSB->rangeMin, drgui_sb_maxi(0, pSB->rangeMax - pSB->pageSize + 1)); + if (newScrollPos != pSB->scrollPos) + { + pSB->scrollPos = newScrollPos; + + // The position of the thumb has changed, so refresh it. + drgui_sb_refresh_thumb(pSBElement); + } +} + +int drgui_sb_get_scroll_position(drgui_element* pSBElement) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return 0; + } + + return pSB->scrollPos; +} + + +void drgui_sb_scroll(drgui_element* pSBElement, int offset) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + drgui_sb_scroll_to(pSBElement, pSB->scrollPos + offset); +} + +void drgui_sb_scroll_to(drgui_element* pSBElement, int newScrollPos) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + int oldScrollPos = pSB->scrollPos; + drgui_sb_set_scroll_position(pSBElement, newScrollPos); + + if (oldScrollPos != pSB->scrollPos) + { + if (pSB->onScroll) { + pSB->onScroll(pSBElement, pSB->scrollPos); + } + } +} + + +void drgui_sb_enable_thumb_auto_hide(drgui_element* pSBElement) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + if (pSB->autoHideThumb != true) + { + pSB->autoHideThumb = true; + + // The thumb needs to be refreshed in order to show the correct state. + drgui_sb_refresh_thumb(pSBElement); + } +} + +void drgui_sb_disable_thumb_auto_hide(drgui_element* pSBElement) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + if (pSB->autoHideThumb != false) + { + pSB->autoHideThumb = false; + + // The thumb needs to be refreshed in order to show the correct state. + drgui_sb_refresh_thumb(pSBElement); + } +} + +bool drgui_sb_is_thumb_auto_hide_enabled(drgui_element* pSBElement) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return false; + } + + return pSB->autoHideThumb; +} + +bool drgui_sb_is_thumb_visible(drgui_element* pSBElement) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return false; + } + + // Always visible if auto-hiding is disabled. + if (!pSB->autoHideThumb) { + return true; + } + + return pSB->pageSize < (pSB->rangeMax - pSB->rangeMin + 1) && pSB->pageSize > 0; +} + + +void drgui_sb_set_mouse_wheel_scele(drgui_element* pSBElement, int scale) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + pSB->mouseWheelScale = scale; +} + +int drgui_sb_get_mouse_wheel_scale(drgui_element* pSBElement) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return 1; + } + + return pSB->mouseWheelScale; +} + + +void drgui_sb_set_track_color(drgui_element* pSBElement, drgui_color color) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + pSB->trackColor = color; +} + +void drgui_sb_set_default_thumb_color(drgui_element* pSBElement, drgui_color color) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + pSB->thumbColor = color; +} + +void drgui_sb_set_hovered_thumb_color(drgui_element* pSBElement, drgui_color color) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + pSB->thumbColorHovered = color; +} + +void drgui_sb_set_pressed_thumb_color(drgui_element* pSBElement, drgui_color color) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + pSB->thumbColorPressed = color; +} + + +void drgui_sb_set_on_scroll(drgui_element* pSBElement, drgui_sb_on_scroll_proc onScroll) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + pSB->onScroll = onScroll; +} + +drgui_sb_on_scroll_proc drgui_sb_get_on_scroll(drgui_element* pSBElement) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return NULL; + } + + return pSB->onScroll; +} + + +drgui_rect drgui_sb_get_thumb_rect(drgui_element* pSBElement) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return drgui_make_rect(0, 0, 0, 0); + } + + drgui_rect rect = {0, 0, 0, 0}; + rect.left = pSB->thumbPadding; + rect.top = pSB->thumbPadding; + + if (pSB->orientation == drgui_sb_orientation_vertical) + { + // Vertical. + rect.left = pSB->thumbPadding; + rect.right = drgui_get_width(pSBElement) - pSB->thumbPadding; + rect.top = pSB->thumbPadding + pSB->thumbPos; + rect.bottom = rect.top + pSB->thumbSize; + } + else + { + // Horizontal. + rect.left = pSB->thumbPadding + pSB->thumbPos; + rect.right = rect.left + pSB->thumbSize; + rect.top = pSB->thumbPadding; + rect.bottom = drgui_get_height(pSBElement) - pSB->thumbPadding; + } + + return rect; +} + + +void drgui_sb_on_size(drgui_element* pSBElement, float newWidth, float newHeight) +{ + (void)newWidth; + (void)newHeight; + + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + drgui_sb_refresh_thumb(pSBElement); +} + +void drgui_sb_on_mouse_leave(drgui_element* pSBElement) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + bool needsRedraw = false; + if (pSB->thumbHovered) + { + needsRedraw = true; + pSB->thumbHovered = false; + } + + if (pSB->thumbPressed) + { + needsRedraw = true; + pSB->thumbPressed = false; + } + + if (needsRedraw) { + drgui_dirty(pSBElement, drgui_sb_get_thumb_rect(pSBElement)); + } +} + +void drgui_sb_on_mouse_move(drgui_element* pSBElement, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)stateFlags; + + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + if (pSB->thumbPressed) + { + // The thumb is pressed. Drag it. + float thumbRelativeMousePosX = (float)relativeMousePosX; + float thumbRelativeMousePosY = (float)relativeMousePosY; + drgui_sb_make_relative_to_thumb(pSBElement, &thumbRelativeMousePosX, &thumbRelativeMousePosY); + + float dragOffsetX = thumbRelativeMousePosX - pSB->thumbClickPosX; + float dragOffsetY = thumbRelativeMousePosY - pSB->thumbClickPosY; + + float destTrackPos = pSB->thumbPos; + if (pSB->orientation == drgui_sb_orientation_vertical) { + destTrackPos += dragOffsetY; + } else { + destTrackPos += dragOffsetX; + } + + int destScrollPos = drgui_sb_calculate_scroll_pos_from_thumb_pos(pSBElement, destTrackPos); + if (destScrollPos != pSB->scrollPos) + { + drgui_sb_scroll_to(pSBElement, destScrollPos); + } + } + else + { + // The thumb is not pressed. We just need to check if the hovered state has change and redraw if required. + if (drgui_sb_is_thumb_visible(pSBElement)) + { + bool wasThumbHovered = pSB->thumbHovered; + + drgui_rect thumbRect = drgui_sb_get_thumb_rect(pSBElement); + pSB->thumbHovered = drgui_rect_contains_point(thumbRect, (float)relativeMousePosX, (float)relativeMousePosY); + + if (wasThumbHovered != pSB->thumbHovered) { + drgui_dirty(pSBElement, thumbRect); + } + } + } +} + +void drgui_sb_on_mouse_button_down(drgui_element* pSBElement, int button, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)stateFlags; + + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + if (button == DRGUI_MOUSE_BUTTON_LEFT) + { + if (drgui_sb_is_thumb_visible(pSBElement)) + { + drgui_rect thumbRect = drgui_sb_get_thumb_rect(pSBElement); + if (drgui_rect_contains_point(thumbRect, (float)relativeMousePosX, (float)relativeMousePosY)) + { + if (!pSB->thumbPressed) + { + drgui_capture_mouse(pSBElement); + pSB->thumbPressed = true; + + pSB->thumbClickPosX = (float)relativeMousePosX; + pSB->thumbClickPosY = (float)relativeMousePosY; + drgui_sb_make_relative_to_thumb(pSBElement, &pSB->thumbClickPosX, &pSB->thumbClickPosY); + + drgui_dirty(pSBElement, drgui_sb_get_thumb_rect(pSBElement)); + } + } + else + { + // If the click position is above the thumb we want to scroll up by a page. If it's below the thumb, we scroll down by a page. + if (relativeMousePosY < thumbRect.top) { + drgui_sb_scroll(pSBElement, -drgui_sb_get_page_size(pSBElement)); + } else if (relativeMousePosY >= thumbRect.bottom) { + drgui_sb_scroll(pSBElement, drgui_sb_get_page_size(pSBElement)); + } + } + } + } +} + +void drgui_sb_on_mouse_button_up(drgui_element* pSBElement, int button, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)relativeMousePosX; + (void)relativeMousePosY; + (void)stateFlags; + + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + if (button == DRGUI_MOUSE_BUTTON_LEFT) + { + if (pSB->thumbPressed && drgui_get_element_with_mouse_capture(pSBElement->pContext) == pSBElement) + { + drgui_release_mouse(pSBElement->pContext); + pSB->thumbPressed = false; + + drgui_dirty(pSBElement, drgui_sb_get_thumb_rect(pSBElement)); + } + } +} + +void drgui_sb_on_mouse_wheel(drgui_element* pSBElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)relativeMousePosX; + (void)relativeMousePosY; + (void)stateFlags; + + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + drgui_sb_scroll(pSBElement, -delta * drgui_sb_get_mouse_wheel_scale(pSBElement)); +} + +void drgui_sb_on_paint(drgui_element* pSBElement, drgui_rect relativeClippingRect, void* pPaintData) +{ + (void)relativeClippingRect; + + const drgui_scrollbar* pSB = (const drgui_scrollbar*)drgui_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + drgui_rect thumbRect = drgui_sb_get_thumb_rect(pSBElement); + + if (drgui_sb_is_thumb_visible(pSBElement)) + { + // The thumb is visible. + + // Track. We draw this in 4 seperate pieces so we can avoid overdraw with the thumb. + drgui_draw_rect(pSBElement, drgui_make_rect(0, 0, drgui_get_width(pSBElement), thumbRect.top), pSB->trackColor, pPaintData); // Top + drgui_draw_rect(pSBElement, drgui_make_rect(0, thumbRect.bottom, drgui_get_width(pSBElement), drgui_get_height(pSBElement)), pSB->trackColor, pPaintData); // Bottom + drgui_draw_rect(pSBElement, drgui_make_rect(0, thumbRect.top, thumbRect.left, thumbRect.bottom), pSB->trackColor, pPaintData); // Left + drgui_draw_rect(pSBElement, drgui_make_rect(thumbRect.right, thumbRect.top, drgui_get_width(pSBElement), thumbRect.bottom), pSB->trackColor, pPaintData); // Right + + // Thumb. + drgui_color thumbColor; + if (pSB->thumbPressed) { + thumbColor = pSB->thumbColorPressed; + } else if (pSB->thumbHovered) { + thumbColor = pSB->thumbColorHovered; + } else { + thumbColor = pSB->thumbColor; + } + + drgui_draw_rect(pSBElement, thumbRect, thumbColor, pPaintData); + } + else + { + // The thumb is not visible - just draw the track as one quad. + drgui_draw_rect(pSBElement, drgui_get_local_rect(pSBElement), pSB->trackColor, pPaintData); + } +} + + + +DRGUI_PRIVATE void drgui_sb_refresh_thumb(drgui_element* pSBElement) +{ + drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement); + assert(pSB != NULL); + + drgui_rect oldThumbRect = drgui_sb_get_thumb_rect(pSBElement); + + pSB->thumbSize = drgui_sb_calculate_thumb_size(pSBElement); + pSB->thumbPos = drgui_sb_calculate_thumb_position(pSBElement); + + drgui_rect newThumbRect = drgui_sb_get_thumb_rect(pSBElement); + if (!drgui_rect_equal(oldThumbRect, newThumbRect)) + { + drgui_dirty(pSBElement, drgui_rect_union(oldThumbRect, newThumbRect)); + } +} + +DRGUI_PRIVATE float drgui_sb_calculate_thumb_size(drgui_element* pSBElement) +{ + const drgui_scrollbar* pSB = (const drgui_scrollbar*)drgui_get_extra_data(pSBElement); + assert(pSB != NULL); + + float trackSize = drgui_sb_get_track_size(pSBElement); + float range = (float)(pSB->rangeMax - pSB->rangeMin + 1); + + float thumbSize = DRGUI_MIN_SCROLLBAR_THUMB_SIZE; + if (range > 0) + { + thumbSize = roundf((trackSize / range) * pSB->pageSize); + thumbSize = drgui_sb_clampf(thumbSize, DRGUI_MIN_SCROLLBAR_THUMB_SIZE, trackSize); + } + + return thumbSize; +} + +DRGUI_PRIVATE float drgui_sb_calculate_thumb_position(drgui_element* pSBElement) +{ + const drgui_scrollbar* pSB = (const drgui_scrollbar*)drgui_get_extra_data(pSBElement); + assert(pSB != NULL); + + float trackSize = drgui_sb_get_track_size(pSBElement); + float thumbSize = drgui_sb_calculate_thumb_size(pSBElement); + float range = (float)(pSB->rangeMax - pSB->rangeMin + 1); + + float thumbPos = 0; + if (range > pSB->pageSize) + { + thumbPos = roundf((trackSize / range) * pSB->scrollPos); + thumbPos = drgui_sb_clampf(thumbPos, 0, trackSize - thumbSize); + } + + return thumbPos; +} + +DRGUI_PRIVATE float drgui_sb_get_track_size(drgui_element* pSBElement) +{ + const drgui_scrollbar* pSB = (const drgui_scrollbar*)drgui_get_extra_data(pSBElement); + assert(pSB != NULL); + + if (pSB->orientation == drgui_sb_orientation_vertical) { + return drgui_get_height(pSBElement) - (pSB->thumbPadding*2); + } else { + return drgui_get_width(pSBElement) - (pSB->thumbPadding*2); + } +} + +DRGUI_PRIVATE void drgui_sb_make_relative_to_thumb(drgui_element* pSBElement, float* pPosX, float* pPosY) +{ + drgui_rect thumbRect = drgui_sb_get_thumb_rect(pSBElement); + + if (pPosX != NULL) { + *pPosX -= thumbRect.left; + } + + if (pPosY != NULL) { + *pPosY -= thumbRect.top; + } +} + +DRGUI_PRIVATE int drgui_sb_calculate_scroll_pos_from_thumb_pos(drgui_element* pSBElement, float thumbPos) +{ + const drgui_scrollbar* pSB = (const drgui_scrollbar*)drgui_get_extra_data(pSBElement); + assert(pSB != NULL); + + float trackSize = drgui_sb_get_track_size(pSBElement); + float range = (float)(pSB->rangeMax - pSB->rangeMin + 1); + + return (int)roundf(thumbPos / (trackSize / range)); +} +#endif //DR_GUI_IMPLEMENTATION + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// Tab Bar +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +// QUICK NOTES +// +// - This control is only the tab bar itself - this does not handle tab pages and content switching and whatnot. + +#ifndef drgui_tab_bar_h +#define drgui_tab_bar_h + +#ifdef __cplusplus +extern "C" { +#endif + +#define DRGUI_MAX_TAB_TEXT_LENGTH 256 + +typedef enum +{ + drgui_tabbar_orientation_top, + drgui_tabbar_orientation_bottom, + drgui_tabbar_orientation_left, + drgui_tabbar_orientation_right +} drgui_tabbar_orientation; + +typedef struct drgui_tab drgui_tab; + +typedef void (* drgui_tabbar_on_measure_tab_proc) (drgui_element* pTBElement, drgui_tab* pTab, float* pWidthOut, float* pHeightOut); +typedef void (* drgui_tabbar_on_paint_tab_proc) (drgui_element* pTBElement, drgui_tab* pTab, drgui_rect relativeClippingRect, float offsetX, float offsetY, float width, float height, void* pPaintData); +typedef void (* drgui_tabbar_on_tab_activated_proc) (drgui_element* pTBElement, drgui_tab* pTab, drgui_tab* pOldActiveTab); +typedef void (* drgui_tabbar_on_tab_deactivated_proc) (drgui_element* pTBElement, drgui_tab* pTab, drgui_tab* pNewActiveTab); +typedef void (* drgui_tabbar_on_tab_close_proc) (drgui_element* pTBElement, drgui_tab* pTab); +typedef void (* drgui_tabbar_on_tab_mouse_button_up_proc)(drgui_element* pTBElement, drgui_tab* pTab, int mouseButton, int mouseRelativePosX, int mouseRelativePosY, int stateFlags); + + +/////////////////////////////////////////////////////////////////////////////// +// +// Tab Bar +// +/////////////////////////////////////////////////////////////////////////////// + +/// Creates a new tab bar control. +drgui_element* drgui_create_tab_bar(drgui_context* pContext, drgui_element* pParent, drgui_tabbar_orientation orientation, size_t extraDataSize, const void* pExtraData); + +/// Deletes the given tab bar control. +void drgui_delete_tab_bar(drgui_element* pTBElement); + + +/// Retrieves the size of the extra data associated with the scrollbar. +size_t drgui_tabbar_get_extra_data_size(drgui_element* pTBElement); + +/// Retrieves a pointer to the extra data associated with the scrollbar. +void* drgui_tabbar_get_extra_data(drgui_element* pTBElement); + +/// Retrieves the orientation of the given scrollbar. +drgui_tabbar_orientation drgui_tabbar_get_orientation(drgui_element* pTBElement); + + +/// Sets the default font to use for tabs. +void drgui_tabbar_set_font(drgui_element* pTBElement, drgui_font* pFont); + +/// Retrieves the default font to use for tabs. +drgui_font* drgui_tabbar_get_font(drgui_element* pTBElement); + +// Sets the color of the text to use on tabs. +void drgui_tabbar_set_text_color(drgui_element* pTBElement, drgui_color color); + +// Retrieves the color of the text to use on tabs. +drgui_color drgui_tabbar_get_text_color(drgui_element* pTBElement); + +// Sets the color of the text to use on active tabs. +void drgui_tabbar_set_text_color_active(drgui_element* pTBElement, drgui_color color); + +// Sets the color of the text to use on hovered tabs. +void drgui_tabbar_set_text_color_hovered(drgui_element* pTBElement, drgui_color color); + +/// Sets the image to use for close buttons. +void drgui_tabbar_set_close_button_image(drgui_element* pTBElement, drgui_image* pImage); + +/// Retrieves the image being used for the close buttons. +drgui_image* drgui_tabbar_get_close_button_image(drgui_element* pTBElement); + +// Sets the default color of the close button. +void drgui_tabbar_set_close_button_color(drgui_element* pTBElement, drgui_color color); + +// Sets the padding to apply the the text of each tab. +void drgui_tabbar_set_tab_padding(drgui_element* pTBElement, float padding); + +// Retrieves the padding to apply to the text of each tab. +float drgui_tabbar_get_tab_padding(drgui_element* pTBElement); + +// Sets the padding to apply the the left of the close button. +void drgui_tabbar_set_close_button_left_padding(drgui_element* pTBElement, float padding); + +// Retrieves the padding to apply to the left of the close button. +float drgui_tabbar_get_close_button_left_padding(drgui_element* pTBElement); + +// Sets the default background color of tabs. This is the color of inactive tabs. +void drgui_tabbar_set_tab_background_color(drgui_element* pTBElement, drgui_color color); + +// Retrieves the default background color of tabs while inactive. +drgui_color drgui_tabbar_get_tab_background_color(drgui_element* pTBElement); + +// Sets the background color of tabs while hovered. +void drgui_tabbar_set_tab_background_color_hovered(drgui_element* pTBElement, drgui_color color); + +// Retrieves the background color of tabs while hovered. +drgui_color drgui_tabbar_get_tab_background_color_hovered(drgui_element* pTBElement); + +// Sets the background color of tabs while activated. +void drgui_tabbar_set_tab_background_color_active(drgui_element* pTBElement, drgui_color color); + +// Retrieves the background color of tabs while activated. +drgui_color drgui_tabbar_get_tab_background_color_actived(drgui_element* pTBElement); + + +/// Sets the function to call when a tab needs to be measured. +void drgui_tabbar_set_on_measure_tab(drgui_element* pTBElement, drgui_tabbar_on_measure_tab_proc proc); + +/// Sets the function to call when a tab needs to be painted. +void drgui_tabbar_set_on_paint_tab(drgui_element* pTBElement, drgui_tabbar_on_paint_tab_proc proc); + +/// Sets the function to call when a tab is activated. +void drgui_tabbar_set_on_tab_activated(drgui_element* pTBElement, drgui_tabbar_on_tab_activated_proc proc); + +/// Sets the function to call when a tab is deactivated. +void drgui_tabbar_set_on_tab_deactivated(drgui_element* pTBElement, drgui_tabbar_on_tab_deactivated_proc proc); + +/// Sets the function to call when a tab is closed with the close button. +void drgui_tabbar_set_on_tab_closed(drgui_element* pTBElement, drgui_tabbar_on_tab_close_proc proc); + +// Sets the function to call when a tab has a mouse button released on it. +void drgui_tabbar_set_on_tab_mouse_button_up(drgui_element* pTBElement, drgui_tabbar_on_tab_mouse_button_up_proc proc); + + +/// Measures the given tab. +void drgui_tabbar_measure_tab(drgui_element* pTBElement, drgui_tab* pTab, float* pWidthOut, float* pHeightOut); + +/// Paints the given tab. +void drgui_tabbar_paint_tab(drgui_element* pTBElement, drgui_tab* pTab, drgui_rect relativeClippingRect, float offsetX, float offsetY, float width, float height, void* pPaintData); + + +/// Sets the width or height of the tab bar to that of it's tabs based on it's orientation. +/// +/// @remarks +/// If the orientation is set to top or bottom, the height will be resized and the width will be left alone. If the orientation +/// is left or right, the width will be resized and the height will be left alone. +/// @par +/// If there is no tab measuring callback set, this will do nothing. +void drgui_tabbar_resize_by_tabs(drgui_element* pTBElement); + +/// Enables auto-resizing based on tabs. +/// +/// @remarks +/// This follows the same resizing rules as per drgui_tabbar_resize_by_tabs(). +/// +/// @see +/// drgui_tabbar_resize_by_tabs() +void drgui_tabbar_enable_auto_size(drgui_element* pTBElement); + +/// Disables auto-resizing based on tabs. +void drgui_tabbar_disable_auto_size(drgui_element* pTBElement); + +/// Determines whether or not auto-sizing is enabled. +bool drgui_tabbar_is_auto_size_enabled(drgui_element* pTBElement); + + +// Retrieves a pointer to the first tab in the given tab bar. +drgui_tab* drgui_tabbar_get_first_tab(drgui_element* pTBElement); + +// Retrieves a pointer to the last tab in the given tab bar. +drgui_tab* drgui_tabbar_get_last_tab(drgui_element* pTBElement); + +// Retrieves a pointer to the next tab in the given tab bar. +drgui_tab* drgui_tabbar_get_next_tab(drgui_element* pTBElement, drgui_tab* pTab); + +// Retrieves a pointer to the previous tab in the given tab bar. +drgui_tab* drgui_tabbar_get_prev_tab(drgui_element* pTBElement, drgui_tab* pTab); + + +/// Activates the given tab. +void drgui_tabbar_activate_tab(drgui_element* pTBElement, drgui_tab* pTab); + +// Activates the tab to the right of the currently active tab, looping back to the start if necessary. +void drgui_tabbar_activate_next_tab(drgui_element* pTBElement); + +// Activates the tab to the left of the currently active tab, looping back to the end if necessary. +void drgui_tabbar_activate_prev_tab(drgui_element* pTBElement); + +/// Retrieves a pointer to the currently active tab. +drgui_tab* drgui_tabbar_get_active_tab(drgui_element* pTBElement); + + +/// Determines whether or not the given tab is in view. +bool drgui_tabbar_is_tab_in_view(drgui_element* pTBElement, drgui_tab* pTab); + + +/// Shows the close buttons on each tab. +void drgui_tabbar_show_close_buttons(drgui_element* pTBElement); + +/// Hides the close buttons on each tab. +void drgui_tabbar_hide_close_buttons(drgui_element* pTBElement); + +/// Enables the on_close event on middle click. +void drgui_tabbar_enable_close_on_middle_click(drgui_element* pTBElement); + +/// Disables the on_close event on middle click. +void drgui_tabbar_disable_close_on_middle_click(drgui_element* pTBElement); + +/// Determines whether or not close-on-middle-click is enabled. +bool drgui_tabbar_is_close_on_middle_click_enabled(drgui_element* pTBElement); + + +/// Called when the mouse leave event needs to be processed for the given tab bar control. +void drgui_tabbar_on_mouse_leave(drgui_element* pTBElement); + +/// Called when the mouse move event needs to be processed for the given tab bar control. +void drgui_tabbar_on_mouse_move(drgui_element* pTBElement, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when the mouse button down event needs to be processed for the given tab bar control. +void drgui_tabbar_on_mouse_button_down(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when the mouse button up event needs to be processed for the given tab bar control. +void drgui_tabbar_on_mouse_button_up(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when the paint event needs to be processed for the given tab control. +void drgui_tabbar_on_paint(drgui_element* pTBElement, drgui_rect relativeClippingRect, void* pPaintData); + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Tab +// +/////////////////////////////////////////////////////////////////////////////// + +/// Creates and appends a tab +drgui_tab* drgui_tabbar_create_and_append_tab(drgui_element* pTBElement, const char* text, size_t extraDataSize, const void* pExtraData); + +/// Creates and prepends a tab. +drgui_tab* drgui_tabbar_create_and_prepend_tab(drgui_element* pTBElement, const char* text, size_t extraDataSize, const void* pExtraData); + +/// Recursively deletes a tree view item. +void drgui_tab_delete(drgui_tab* pTab); + +/// Retrieves the tab bar GUI element that owns the given item. +drgui_element* drgui_tab_get_tab_bar_element(drgui_tab* pTab); + +/// Retrieves the size of the extra data associated with the given tree-view item. +size_t drgui_tab_get_extra_data_size(drgui_tab* pTab); + +/// Retrieves a pointer to the extra data associated with the given tree-view item. +void* drgui_tab_get_extra_data(drgui_tab* pTab); + + +/// Sets the text of the given tab bar item. +void drgui_tab_set_text(drgui_tab* pTab, const char* text); + +/// Retrieves the text of the given tab bar item. +const char* drgui_tab_get_text(drgui_tab* pTab); + + +/// Retrieves a pointer to the next tab in the tab bar. +drgui_tab* drgui_tab_get_next_tab(drgui_tab* pTab); + +/// Retrieves a pointer to the previous tab in the tab bar. +drgui_tab* drgui_tab_get_prev_tab(drgui_tab* pTab); + + +/// Moves the given tab to the front of the tab bar that owns it. +void drgui_tab_move_to_front(drgui_tab* pTab); + +/// Determines whether or not the given tab is in view. +bool drgui_tab_is_in_view(drgui_tab* pTab); + +/// Moves the given tab into view, if it's not already. +/// +/// If the tab is out of view, it will be repositioned to the front of the tab bar. +void drgui_tab_move_into_view(drgui_tab* pTab); + + +#ifdef __cplusplus +} +#endif +#endif //drgui_tab_bar_h + + +#ifdef DR_GUI_IMPLEMENTATION +typedef struct drgui_tab_bar drgui_tab_bar; + +struct drgui_tab_bar +{ + /// The orientation. + drgui_tabbar_orientation orientation; + + + /// A pointer to the first tab. + drgui_tab* pFirstTab; + + /// A pointer to the last tab. + drgui_tab* pLastTab; + + + /// A pointer to the hovered tab. + drgui_tab* pHoveredTab; + + /// A pointer to the active tab. + drgui_tab* pActiveTab; + + /// The tab whose close button is currently pressed, if any. + drgui_tab* pTabWithCloseButtonPressed; + + + /// The default font to use for tab bar items. + drgui_font* pFont; + + /// The default color to use for tab bar item text. + drgui_color tabTextColor; + + /// The default color to use for tab bar item text while active. + drgui_color tabTextColorActivated; + + /// The default color to use for tab bar item text while hovered. + drgui_color tabTextColorHovered; + + /// The default background color of tab bar items. + drgui_color tabBackgroundColor; + + /// The background color of tab bar items while hovered. + drgui_color tabBackgroundColorHovered; + + /// The background color of tab bar items while selected. + drgui_color tabBackbroundColorActivated; + + /// The padding to apply to the text of tabs. + float tabPadding; + + /// The image to use for the close button. + drgui_image* pCloseButtonImage; + + /// The padding to the left of the close button. + float closeButtonPaddingLeft; + + /// The default color of the close button. + drgui_color closeButtonColorDefault; + + /// The color of the close button when the tab is hovered, but not the close button itself. + drgui_color closeButtonColorTabHovered; + + /// The color of the close button when it is hovered. + drgui_color closeButtonColorHovered; + + /// The color of the close button when it is pressed. + drgui_color closeButtonColorPressed; + + + /// Whether or not auto-sizing is enabled. Disabled by default. + bool isAutoSizeEnabled; + + /// Whether or not the close buttons are being shown. + bool isShowingCloseButton; + + /// Whether or not close-on-middle-click is enabled. + bool isCloseOnMiddleClickEnabled; + + /// Whether or not the close button is hovered. + bool isCloseButtonHovered; + + + /// The function to call when a tab needs to be measured. + drgui_tabbar_on_measure_tab_proc onMeasureTab; + + /// The function to call when a tab needs to be painted. + drgui_tabbar_on_paint_tab_proc onPaintTab; + + /// The function to call when a tab is activated. + drgui_tabbar_on_tab_activated_proc onTabActivated; + + /// The function to call when a tab is deactivated. + drgui_tabbar_on_tab_deactivated_proc onTabDeactivated; + + /// The function to call when a tab is closed via the close button. + drgui_tabbar_on_tab_close_proc onTabClose; + + // The function to call when a mouse button is released while over a tab. + drgui_tabbar_on_tab_mouse_button_up_proc onTabMouseButtonUp; + + + /// The size of the extra data. + size_t extraDataSize; + + /// A pointer to the extra data. + char pExtraData[1]; +}; + +struct drgui_tab +{ + /// The tab bar that owns the tab. + drgui_element* pTBElement; + + /// A pointer to the next tab in the tab bar. + drgui_tab* pNextTab; + + /// A pointer to the previous tab in the tab bar. + drgui_tab* pPrevTab; + + + /// The tab bar's text. + char text[DRGUI_MAX_TAB_TEXT_LENGTH]; + + + /// The size of the extra data. + size_t extraDataSize; + + /// A pointer to the extra data buffer. + char pExtraData[1]; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// +// Tab +// +/////////////////////////////////////////////////////////////////////////////// + +/// Default implementation of the item measure event. +DRGUI_PRIVATE void drgui_tabbar_on_measure_tab_default(drgui_element* pTBElement, drgui_tab* pTab, float* pWidthOut, float* pHeightOut); + +/// Paints the given menu item. +DRGUI_PRIVATE void drgui_tabbar_on_paint_tab_default(drgui_element* pTBElement, drgui_tab* pTab, drgui_rect relativeClippingRect, float offsetX, float offsetY, float width, float height, void* pPaintData); + +/// Finds the tab sitting under the given point, if any. +DRGUI_PRIVATE drgui_tab* drgui_tabbar_find_tab_under_point(drgui_element* pTBElement, float relativePosX, float relativePosY, bool* pIsOverCloseButtonOut); + +drgui_element* drgui_create_tab_bar(drgui_context* pContext, drgui_element* pParent, drgui_tabbar_orientation orientation, size_t extraDataSize, const void* pExtraData) +{ + if (pContext == NULL) { + return NULL; + } + + drgui_element* pTBElement = drgui_create_element(pContext, pParent, sizeof(drgui_tab_bar) + extraDataSize, NULL); + if (pTBElement == NULL) { + return NULL; + } + + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + pTB->orientation = orientation; + pTB->pFirstTab = NULL; + pTB->pLastTab = NULL; + pTB->pHoveredTab = NULL; + pTB->pActiveTab = NULL; + pTB->pTabWithCloseButtonPressed = NULL; + + pTB->pFont = NULL; + pTB->tabTextColor = drgui_rgb(224, 224, 224); + pTB->tabTextColorActivated = drgui_rgb(224, 224, 224); + pTB->tabTextColorHovered = drgui_rgb(224, 224, 224); + pTB->tabBackgroundColor = drgui_rgb(58, 58, 58); + pTB->tabBackgroundColorHovered = drgui_rgb(16, 92, 160); + pTB->tabBackbroundColorActivated = drgui_rgb(32, 128, 192); //drgui_rgb(80, 80, 80); + pTB->tabPadding = 4; + pTB->pCloseButtonImage = NULL; + pTB->closeButtonPaddingLeft = 6; + pTB->closeButtonColorDefault = pTB->tabBackgroundColor; + pTB->closeButtonColorTabHovered = drgui_rgb(192, 192, 192); + pTB->closeButtonColorHovered = drgui_rgb(255, 96, 96); + pTB->closeButtonColorPressed = drgui_rgb(192, 32, 32); + pTB->isAutoSizeEnabled = false; + pTB->isShowingCloseButton = false; + pTB->isCloseOnMiddleClickEnabled = false; + pTB->isCloseButtonHovered = false; + + pTB->onMeasureTab = drgui_tabbar_on_measure_tab_default; + pTB->onPaintTab = drgui_tabbar_on_paint_tab_default; + pTB->onTabActivated = NULL; + pTB->onTabDeactivated = NULL; + pTB->onTabClose = NULL; + + + pTB->extraDataSize = extraDataSize; + if (pExtraData != NULL) { + memcpy(pTB->pExtraData, pExtraData, extraDataSize); + } + + + // Event handlers. + drgui_set_on_mouse_leave(pTBElement, drgui_tabbar_on_mouse_leave); + drgui_set_on_mouse_move(pTBElement, drgui_tabbar_on_mouse_move); + drgui_set_on_mouse_button_down(pTBElement, drgui_tabbar_on_mouse_button_down); + drgui_set_on_mouse_button_up(pTBElement, drgui_tabbar_on_mouse_button_up); + drgui_set_on_paint(pTBElement, drgui_tabbar_on_paint); + + return pTBElement; +} + +void drgui_delete_tab_bar(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + while (pTB->pFirstTab != NULL) { + drgui_tab_delete(pTB->pFirstTab); + } + + + drgui_delete_element(pTBElement); +} + + +size_t drgui_tabbar_get_extra_data_size(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + return pTB->extraDataSize; +} + +void* drgui_tabbar_get_extra_data(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return NULL; + } + + return pTB->pExtraData; +} + +drgui_tabbar_orientation drgui_tabbar_get_orientation(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return drgui_tabbar_orientation_top; + } + + return pTB->orientation; +} + + +void drgui_tabbar_set_font(drgui_element* pTBElement, drgui_font* pFont) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->pFont = pFont; + + // A change in font may have changed the size of the tabbar. + if (pTB->isAutoSizeEnabled) { + drgui_tabbar_resize_by_tabs(pTBElement); + } + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + +drgui_font* drgui_tabbar_get_font(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return NULL; + } + + return pTB->pFont; +} + + +void drgui_tabbar_set_text_color(drgui_element* pTBElement, drgui_color color) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->tabTextColor = color; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + +drgui_color drgui_tabbar_get_text_color(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return drgui_rgb(0, 0, 0); + } + + return pTB->tabTextColor; +} + +void drgui_tabbar_set_text_color_active(drgui_element* pTBElement, drgui_color color) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->tabTextColorActivated = color; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + +void drgui_tabbar_set_text_color_hovered(drgui_element* pTBElement, drgui_color color) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->tabTextColorHovered = color; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + + +void drgui_tabbar_set_close_button_image(drgui_element* pTBElement, drgui_image* pImage) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->pCloseButtonImage = pImage; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + +drgui_image* drgui_tabbar_get_close_button_image(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return NULL; + } + + return pTB->pCloseButtonImage; +} + +void drgui_tabbar_set_close_button_color(drgui_element* pTBElement, drgui_color color) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->closeButtonColorDefault = color; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + + +void drgui_tabbar_set_tab_padding(drgui_element* pTBElement, float padding) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->tabPadding = padding; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + +float drgui_tabbar_get_tab_padding(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + return pTB->tabPadding; +} + +void drgui_tabbar_set_close_button_left_padding(drgui_element* pTBElement, float padding) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->closeButtonPaddingLeft = padding; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + +float drgui_tabbar_get_close_button_left_padding(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + return pTB->closeButtonPaddingLeft; +} + + +void drgui_tabbar_set_tab_background_color(drgui_element* pTBElement, drgui_color color) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->tabBackgroundColor = color; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + +drgui_color drgui_tabbar_get_tab_background_color(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return drgui_rgb(0, 0, 0); + } + + return pTB->tabBackgroundColor; +} + +void drgui_tabbar_set_tab_background_color_hovered(drgui_element* pTBElement, drgui_color color) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->tabBackgroundColorHovered = color; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + +drgui_color drgui_tabbar_get_tab_background_color_hovered(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return drgui_rgb(0, 0, 0); + } + + return pTB->tabBackgroundColorHovered; +} + +void drgui_tabbar_set_tab_background_color_active(drgui_element* pTBElement, drgui_color color) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->tabBackbroundColorActivated = color; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + +drgui_color drgui_tabbar_get_tab_background_color_actived(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return drgui_rgb(0, 0, 0); + } + + return pTB->tabBackbroundColorActivated; +} + + +void drgui_tabbar_set_on_measure_tab(drgui_element* pTBElement, drgui_tabbar_on_measure_tab_proc proc) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->onMeasureTab = proc; +} + +void drgui_tabbar_set_on_paint_tab(drgui_element* pTBElement, drgui_tabbar_on_paint_tab_proc proc) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->onPaintTab = proc; +} + +void drgui_tabbar_set_on_tab_activated(drgui_element* pTBElement, drgui_tabbar_on_tab_activated_proc proc) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->onTabActivated = proc; +} + +void drgui_tabbar_set_on_tab_deactivated(drgui_element* pTBElement, drgui_tabbar_on_tab_deactivated_proc proc) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->onTabDeactivated = proc; +} + +void drgui_tabbar_set_on_tab_closed(drgui_element* pTBElement, drgui_tabbar_on_tab_close_proc proc) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->onTabClose = proc; +} + +void drgui_tabbar_set_on_tab_mouse_button_up(drgui_element* pTBElement, drgui_tabbar_on_tab_mouse_button_up_proc proc) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->onTabMouseButtonUp = proc; +} + + +void drgui_tabbar_measure_tab(drgui_element* pTBElement, drgui_tab* pTab, float* pWidthOut, float* pHeightOut) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (pTB->onMeasureTab) { + pTB->onMeasureTab(pTBElement, pTab, pWidthOut, pHeightOut); + } +} + +void drgui_tabbar_paint_tab(drgui_element* pTBElement, drgui_tab* pTab, drgui_rect relativeClippingRect, float offsetX, float offsetY, float width, float height, void* pPaintData) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (pTB->onPaintTab) { + pTB->onPaintTab(pTBElement, pTab, relativeClippingRect, offsetX, offsetY, width, height, pPaintData); + } +} + + +void drgui_tabbar_resize_by_tabs(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (pTB->onMeasureTab == NULL) { + return; + } + + float maxWidth = 0; + float maxHeight = 0; + if (pTB->pFirstTab == NULL) { + // There are no tabs. Set initial size based on the line height of the font. + drgui_font_metrics fontMetrics; + if (drgui_get_font_metrics(pTB->pFont, &fontMetrics)) { + if (pTB->orientation == drgui_tabbar_orientation_top || pTB->orientation == drgui_tabbar_orientation_bottom) { + maxHeight = fontMetrics.lineHeight + (pTB->tabPadding*2); + } else { + maxWidth = fontMetrics.lineHeight + (pTB->tabPadding*2); + } + } + } else { + for (drgui_tab* pTab = pTB->pFirstTab; pTab != NULL; pTab = pTab->pNextTab) { + float tabWidth = 0; + float tabHeight = 0; + drgui_tabbar_measure_tab(pTBElement, pTab, &tabWidth, &tabHeight); + + maxWidth = (tabWidth > maxWidth) ? tabWidth : maxWidth; + maxHeight = (tabHeight > maxHeight) ? tabHeight : maxHeight; + } + } + + + + + if (pTB->orientation == drgui_tabbar_orientation_top || pTB->orientation == drgui_tabbar_orientation_bottom) { + drgui_set_size(pTBElement, drgui_get_width(pTBElement), maxHeight); + } else { + drgui_set_size(pTBElement, maxWidth, drgui_get_height(pTBElement)); + } +} + +void drgui_tabbar_enable_auto_size(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->isAutoSizeEnabled = true; +} + +void drgui_tabbar_disable_auto_size(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->isAutoSizeEnabled = false; +} + +bool drgui_tabbar_is_auto_size_enabled(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return false; + } + + return pTB->isAutoSizeEnabled; +} + + +drgui_tab* drgui_tabbar_get_first_tab(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return NULL; + } + + return pTB->pFirstTab; +} + +drgui_tab* drgui_tabbar_get_last_tab(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return NULL; + } + + return pTB->pLastTab; +} + +drgui_tab* drgui_tabbar_get_next_tab(drgui_element* pTBElement, drgui_tab* pTab) +{ + (void)pTBElement; + return drgui_tab_get_next_tab(pTab); +} + +drgui_tab* drgui_tabbar_get_prev_tab(drgui_element* pTBElement, drgui_tab* pTab) +{ + (void)pTBElement; + return drgui_tab_get_prev_tab(pTab); +} + + +void drgui_tabbar_activate_tab(drgui_element* pTBElement, drgui_tab* pTab) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_tab* pOldActiveTab = pTB->pActiveTab; + drgui_tab* pNewActiveTab = pTab; + + if (pOldActiveTab == pNewActiveTab) { + return; // The tab is already active - nothing to do. + } + + + pTB->pActiveTab = pNewActiveTab; + + if (pTB->onTabDeactivated && pOldActiveTab != NULL) { + pTB->onTabDeactivated(pTBElement, pOldActiveTab, pNewActiveTab); + } + + if (pTB->onTabActivated && pNewActiveTab != NULL) { + pTB->onTabActivated(pTBElement, pNewActiveTab, pOldActiveTab); + } + + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + +void drgui_tabbar_activate_next_tab(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (pTB->pActiveTab == NULL) { + drgui_tabbar_activate_tab(pTBElement, pTB->pFirstTab); + return; + } + + + drgui_tab* pNextTab = pTB->pActiveTab->pNextTab; + if (pNextTab == NULL) { + pNextTab = pTB->pFirstTab; + } + + drgui_tabbar_activate_tab(pTBElement, pNextTab); +} + +void drgui_tabbar_activate_prev_tab(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (pTB->pActiveTab == NULL) { + drgui_tabbar_activate_tab(pTBElement, pTB->pLastTab); + return; + } + + + drgui_tab* pPrevTab = pTB->pActiveTab->pPrevTab; + if (pPrevTab == NULL) { + pPrevTab = pTB->pLastTab; + } + + drgui_tabbar_activate_tab(pTBElement, pPrevTab); +} + +drgui_tab* drgui_tabbar_get_active_tab(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return NULL; + } + + return pTB->pActiveTab; +} + + +bool drgui_tabbar_is_tab_in_view(drgui_element* pTBElement, drgui_tab* pTabIn) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return false; + } + + float tabbarWidth = 0; + float tabbarHeight = 0; + drgui_get_size(pTBElement, &tabbarWidth, &tabbarHeight); + + + // Each tab. + float runningPosX = 0; + float runningPosY = 0; + for (drgui_tab* pTab = pTB->pFirstTab; pTab != NULL; pTab = pTab->pNextTab) + { + float tabWidth = 0; + float tabHeight = 0; + drgui_tabbar_measure_tab(pTBElement, pTab, &tabWidth, &tabHeight); + + if (pTab == pTabIn) { + return runningPosX + tabWidth <= tabbarWidth && runningPosY + tabHeight <= tabbarHeight; + } + + + if (pTB->orientation == drgui_tabbar_orientation_top || pTB->orientation == drgui_tabbar_orientation_bottom) { + runningPosX += tabWidth; + } else { + runningPosY += tabHeight; + } + } + + return false; +} + + +void drgui_tabbar_show_close_buttons(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->isShowingCloseButton = true; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + +void drgui_tabbar_hide_close_buttons(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->isShowingCloseButton = false; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + + +void drgui_tabbar_enable_close_on_middle_click(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->isCloseOnMiddleClickEnabled = true; +} + +void drgui_tabbar_disable_close_on_middle_click(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->isCloseOnMiddleClickEnabled = false; +} + +bool drgui_tabbar_is_close_on_middle_click_enabled(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return false; + } + + return pTB->isCloseOnMiddleClickEnabled; +} + + +void drgui_tabbar_on_mouse_leave(drgui_element* pTBElement) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (pTB->pHoveredTab != NULL) + { + pTB->pHoveredTab = NULL; + pTB->isCloseButtonHovered = false; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } + } +} + +void drgui_tabbar_on_mouse_move(drgui_element* pTBElement, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)stateFlags; + + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + bool isCloseButtonHovered = false; + + drgui_tab* pOldHoveredTab = pTB->pHoveredTab; + drgui_tab* pNewHoveredTab = drgui_tabbar_find_tab_under_point(pTBElement, (float)relativeMousePosX, (float)relativeMousePosY, &isCloseButtonHovered); + + if (pOldHoveredTab != pNewHoveredTab || pTB->isCloseButtonHovered != isCloseButtonHovered) + { + pTB->pHoveredTab = pNewHoveredTab; + pTB->isCloseButtonHovered = isCloseButtonHovered; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } + } +} + +void drgui_tabbar_on_mouse_button_down(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)stateFlags; + + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (mouseButton == DRGUI_MOUSE_BUTTON_LEFT || mouseButton == DRGUI_MOUSE_BUTTON_RIGHT) + { + bool isOverCloseButton = false; + + drgui_tab* pOldActiveTab = pTB->pActiveTab; + drgui_tab* pNewActiveTab = drgui_tabbar_find_tab_under_point(pTBElement, (float)relativeMousePosX, (float)relativeMousePosY, &isOverCloseButton); + + if (pNewActiveTab != NULL && pOldActiveTab != pNewActiveTab && !isOverCloseButton) { + drgui_tabbar_activate_tab(pTBElement, pNewActiveTab); + } + + if (isOverCloseButton && mouseButton == DRGUI_MOUSE_BUTTON_LEFT) { + pTB->pTabWithCloseButtonPressed = pNewActiveTab; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } + } + } + else if (mouseButton == DRGUI_MOUSE_BUTTON_MIDDLE) + { + if (pTB->isCloseOnMiddleClickEnabled) + { + drgui_tab* pHoveredTab = drgui_tabbar_find_tab_under_point(pTBElement, (float)relativeMousePosX, (float)relativeMousePosY, NULL); + if (pHoveredTab != NULL) { + if (pTB->onTabClose) { + pTB->onTabClose(pTBElement, pHoveredTab); + } + } + } + } +} + +void drgui_tabbar_on_mouse_button_up(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)stateFlags; + + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + + bool releasedOverCloseButton = false; + drgui_tab* pTabUnderMouse = drgui_tabbar_find_tab_under_point(pTBElement, (float)relativeMousePosX, (float)relativeMousePosY, &releasedOverCloseButton); + + if (pTB->pTabWithCloseButtonPressed != NULL && mouseButton == DRGUI_MOUSE_BUTTON_LEFT) + { + if (releasedOverCloseButton && pTabUnderMouse == pTB->pTabWithCloseButtonPressed) { + if (pTB->onTabClose) { + pTB->onTabClose(pTBElement, pTB->pTabWithCloseButtonPressed); + } + } + + + pTB->pTabWithCloseButtonPressed = NULL; + + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } + } + else + { + if (!releasedOverCloseButton && pTB->onTabMouseButtonUp) { + // TODO: Improve this by passing the mouse position relative to the tab. Currently it is relative to the tab BAR. Can have + // the drgui_tabbar_find_tab_under_point() function return the position relative to the tab. + pTB->onTabMouseButtonUp(pTBElement, pTabUnderMouse, mouseButton, relativeMousePosX, relativeMousePosY, stateFlags); + } + } +} + +void drgui_tabbar_on_paint(drgui_element* pTBElement, drgui_rect relativeClippingRect, void* pPaintData) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + + float tabbarWidth = 0; + float tabbarHeight = 0; + drgui_get_size(pTBElement, &tabbarWidth, &tabbarHeight); + + + // Each tab. + float runningPosX = 0; + float runningPosY = 0; + for (drgui_tab* pTab = pTB->pFirstTab; pTab != NULL; pTab = pTab->pNextTab) + { + float tabWidth = 0; + float tabHeight = 0; + drgui_tabbar_measure_tab(pTBElement, pTab, &tabWidth, &tabHeight); + + // If a part of the tab is out of bounds, stop drawing. + if (runningPosX > tabbarWidth || runningPosY > tabbarHeight) { + break; + } + + + drgui_tabbar_paint_tab(pTBElement, pTab, relativeClippingRect, runningPosX, runningPosY, tabWidth, tabHeight, pPaintData); + + // After painting the tab, there may be a region of the background that was not drawn by the tab painting callback. We'll need to + // draw that here. + if (pTB->orientation == drgui_tabbar_orientation_top || pTB->orientation == drgui_tabbar_orientation_bottom) { + drgui_draw_rect(pTBElement, drgui_make_rect(runningPosX, runningPosY + tabHeight, tabbarWidth, tabbarHeight), pTB->tabBackgroundColor, pPaintData); + } else { + drgui_draw_rect(pTBElement, drgui_make_rect(runningPosX + tabWidth, runningPosY, tabbarWidth, runningPosY + tabHeight), pTB->tabBackgroundColor, pPaintData); + } + + + + if (pTB->orientation == drgui_tabbar_orientation_top || pTB->orientation == drgui_tabbar_orientation_bottom) { + runningPosX += tabWidth; + } else { + runningPosY += tabHeight; + } + } + + + // Background. We just draw a quad around the region that is not covered by items. + drgui_draw_rect(pTBElement, drgui_make_rect(runningPosX, runningPosY, tabbarWidth, tabbarHeight), pTB->tabBackgroundColor, pPaintData); +} + + +DRGUI_PRIVATE void drgui_tabbar_on_measure_tab_default(drgui_element* pTBElement, drgui_tab* pTab, float* pWidthOut, float* pHeightOut) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + float textWidth = 0; + float textHeight = 0; + + if (pTab != NULL) { + drgui_measure_string(pTB->pFont, pTab->text, strlen(pTab->text), &textWidth, &textHeight); + } + + + float closeButtonWidth = 0; + if (pTB->isShowingCloseButton && pTB->pCloseButtonImage != NULL) { + unsigned int closeImageWidth; + drgui_get_image_size(pTB->pCloseButtonImage, &closeImageWidth, NULL); + + closeButtonWidth = closeImageWidth + pTB->closeButtonPaddingLeft; + } + + + if (pWidthOut) { + *pWidthOut = textWidth + closeButtonWidth + pTB->tabPadding*2; + } + if (pHeightOut) { + *pHeightOut = textHeight + pTB->tabPadding*2; + } +} + +DRGUI_PRIVATE void drgui_tabbar_on_paint_tab_default(drgui_element* pTBElement, drgui_tab* pTab, drgui_rect relativeClippingRect, float offsetX, float offsetY, float width, float height, void* pPaintData) +{ + (void)relativeClippingRect; + + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + // Background. + drgui_color bgcolor = pTB->tabBackgroundColor; + drgui_color closeButtonColor = pTB->closeButtonColorDefault; + drgui_color textColor = pTB->tabTextColor; + + if (pTB->pHoveredTab == pTab) { + bgcolor = pTB->tabBackgroundColorHovered; + closeButtonColor = pTB->closeButtonColorTabHovered; + textColor = pTB->tabTextColorHovered; + } + if (pTB->pActiveTab == pTab) { + bgcolor = pTB->tabBackbroundColorActivated; + closeButtonColor = pTB->closeButtonColorTabHovered; + textColor = pTB->tabTextColorActivated; + } + + if (pTB->pHoveredTab == pTab && pTB->isCloseButtonHovered) { + closeButtonColor = pTB->closeButtonColorHovered; + + if (pTB->pTabWithCloseButtonPressed == pTB->pHoveredTab) { + closeButtonColor = pTB->closeButtonColorPressed; + } + } + + drgui_draw_rect_outline(pTBElement, drgui_make_rect(offsetX, offsetY, offsetX + width, offsetY + height), bgcolor, pTB->tabPadding, pPaintData); + + + // Text. + float textPosX = offsetX + pTB->tabPadding; + float textPosY = offsetY + pTB->tabPadding; + if (pTab != NULL) { + drgui_draw_text(pTBElement, pTB->pFont, pTab->text, (int)strlen(pTab->text), textPosX, textPosY, textColor, bgcolor, pPaintData); + } + + + // Close button. + if (pTB->isShowingCloseButton && pTB->pCloseButtonImage != NULL) + { + float textWidth = 0; + float textHeight = 0; + if (pTab != NULL) { + drgui_measure_string(pTB->pFont, pTab->text, strlen(pTab->text), &textWidth, &textHeight); + } + + float closeButtonPosX = textPosX + textWidth + pTB->closeButtonPaddingLeft; + float closeButtonPosY = textPosY; + + unsigned int iconWidth; + unsigned int iconHeight; + drgui_get_image_size(pTB->pCloseButtonImage, &iconWidth, &iconHeight); + + drgui_draw_image_args args; + args.dstX = closeButtonPosX; + args.dstY = closeButtonPosY; + args.dstWidth = (float)iconWidth; + args.dstHeight = (float)iconHeight; + args.srcX = 0; + args.srcY = 0; + args.srcWidth = (float)iconWidth; + args.srcHeight = (float)iconHeight; + args.dstBoundsX = args.dstX; + args.dstBoundsY = args.dstY; + args.dstBoundsWidth = (float)iconWidth; + args.dstBoundsHeight = height - (pTB->tabPadding*2); + args.foregroundTint = closeButtonColor; + args.backgroundColor = bgcolor; + args.boundsColor = bgcolor; + args.options = DRGUI_IMAGE_DRAW_BACKGROUND | DRGUI_IMAGE_DRAW_BOUNDS | DRGUI_IMAGE_CLIP_BOUNDS | DRGUI_IMAGE_ALIGN_CENTER; + drgui_draw_image(pTBElement, pTB->pCloseButtonImage, &args, pPaintData); + + + /// Space between the text and the padding. + drgui_draw_rect(pTBElement, drgui_make_rect(textPosX + textWidth, textPosY, closeButtonPosX, textPosY + textHeight), bgcolor, pPaintData); + } +} + +DRGUI_PRIVATE drgui_tab* drgui_tabbar_find_tab_under_point(drgui_element* pTBElement, float relativePosX, float relativePosY, bool* pIsOverCloseButtonOut) +{ + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return NULL; + } + + unsigned int closeButtonWidth; + unsigned int closeButtonHeight; + drgui_get_image_size(pTB->pCloseButtonImage, &closeButtonWidth, &closeButtonHeight); + + float runningPosX = 0; + float runningPosY = 0; + for (drgui_tab* pTab = pTB->pFirstTab; pTab != NULL; pTab = pTab->pNextTab) + { + float tabWidth = 0; + float tabHeight = 0; + drgui_tabbar_measure_tab(pTBElement, pTab, &tabWidth, &tabHeight); + + if (relativePosX >= runningPosX && relativePosX < runningPosX + tabWidth && relativePosY >= runningPosY && relativePosY < runningPosY + tabHeight) + { + if (pIsOverCloseButtonOut) + { + // The close button is in the center, vertically. + drgui_rect closeButtonRect; + closeButtonRect.left = runningPosX + tabWidth - (pTB->tabPadding + closeButtonWidth); + closeButtonRect.right = closeButtonRect.left + closeButtonWidth; + closeButtonRect.top = runningPosY + (tabHeight - (pTB->tabPadding + closeButtonHeight))/2; + closeButtonRect.bottom = closeButtonRect.top + closeButtonHeight; + + if (pTB->isShowingCloseButton && drgui_rect_contains_point(closeButtonRect, relativePosX, relativePosY)) { + *pIsOverCloseButtonOut = true; + } else { + *pIsOverCloseButtonOut = false; + } + } + + return pTab; + } + + if (pTB->orientation == drgui_tabbar_orientation_top || pTB->orientation == drgui_tabbar_orientation_bottom) { + runningPosX += tabWidth; + } else { + runningPosY += tabHeight; + } + } + + + if (pIsOverCloseButtonOut) { + *pIsOverCloseButtonOut = false; + } + + return NULL; +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// Tab +// +/////////////////////////////////////////////////////////////////////////////// + +/// Appends the given tab to the given tab bar. +DRGUI_PRIVATE void tab_append(drgui_tab* pTab, drgui_element* pTBElement); + +/// Prepends the given tab to the given tab bar. +DRGUI_PRIVATE void tab_prepend(drgui_tab* pTab, drgui_element* pTBElement); + +/// Detaches the given tab bar from it's tab bar element's hierarchy. +/// +/// @remarks +/// This does not deactivate the tab or what - it only detaches the tab from the hierarchy. +DRGUI_PRIVATE void tab_detach_from_hierarchy(drgui_tab* pTab); + +/// Detaches the given tab bar from it's tab bar element. +DRGUI_PRIVATE void tab_detach(drgui_tab* pTab); + +DRGUI_PRIVATE drgui_tab* tb_create_tab(drgui_element* pTBElement, const char* text, size_t extraDataSize, const void* pExtraData) +{ + if (pTBElement == NULL) { + return NULL; + } + + drgui_tab* pTab = (drgui_tab*)malloc(sizeof(*pTab) + extraDataSize); + if (pTab == NULL) { + return NULL; + } + + pTab->pTBElement = NULL; + pTab->pNextTab = NULL; + pTab->pPrevTab = NULL; + pTab->text[0] = '\0'; + + pTab->extraDataSize = extraDataSize; + if (pExtraData) { + memcpy(pTab->pExtraData, pExtraData, extraDataSize); + } + + if (text != NULL) { + drgui__strncpy_s(pTab->text, sizeof(pTab->text), text, (size_t)-1); // -1 = _TRUNCATE + } + + return pTab; +} + +drgui_tab* drgui_tabbar_create_and_append_tab(drgui_element* pTBElement, const char* text, size_t extraDataSize, const void* pExtraData) +{ + drgui_tab* pTab = (drgui_tab*)tb_create_tab(pTBElement, text, extraDataSize, pExtraData); + if (pTab != NULL) + { + tab_append(pTab, pTBElement); + } + + return pTab; +} + +drgui_tab* drgui_tabbar_create_and_prepend_tab(drgui_element* pTBElement, const char* text, size_t extraDataSize, const void* pExtraData) +{ + drgui_tab* pTab = (drgui_tab*)tb_create_tab(pTBElement, text, extraDataSize, pExtraData); + if (pTab != NULL) + { + tab_prepend(pTab, pTBElement); + } + + return pTab; +} + +void drgui_tab_delete(drgui_tab* pTab) +{ + if (pTab == NULL) { + return; + } + + tab_detach(pTab); + free(pTab); +} + +drgui_element* drgui_tab_get_tab_bar_element(drgui_tab* pTab) +{ + if (pTab == NULL) { + return NULL; + } + + return pTab->pTBElement; +} + +size_t drgui_tab_get_extra_data_size(drgui_tab* pTab) +{ + if (pTab == NULL) { + return 0; + } + + return pTab->extraDataSize; +} + +void* drgui_tab_get_extra_data(drgui_tab* pTab) +{ + if (pTab == NULL) { + return NULL; + } + + return pTab->pExtraData; +} + + +void drgui_tab_set_text(drgui_tab* pTab, const char* text) +{ + if (pTab == NULL) { + return; + } + + if (text != NULL) { + drgui__strncpy_s(pTab->text, sizeof(pTab->text), text, (size_t)-1); // -1 = _TRUNCATE + } else { + pTab->text[0] = '\0'; + } + + // The content of the menu has changed so we'll need to schedule a redraw. + if (drgui_is_auto_dirty_enabled(pTab->pTBElement->pContext)) { + drgui_dirty(pTab->pTBElement, drgui_get_local_rect(pTab->pTBElement)); + } +} + +const char* drgui_tab_get_text(drgui_tab* pTab) +{ + if (pTab == NULL) { + return NULL; + } + + return pTab->text; +} + + +drgui_tab* drgui_tab_get_next_tab(drgui_tab* pTab) +{ + if (pTab == NULL) { + return NULL; + } + + return pTab->pNextTab; +} + +drgui_tab* drgui_tab_get_prev_tab(drgui_tab* pTab) +{ + if (pTab == NULL) { + return NULL; + } + + return pTab->pPrevTab; +} + + +void drgui_tab_move_to_front(drgui_tab* pTab) +{ + if (pTab == NULL) { + return; + } + + drgui_element* pTBElement = pTab->pTBElement; + + tab_detach_from_hierarchy(pTab); + tab_prepend(pTab, pTBElement); +} + +bool drgui_tab_is_in_view(drgui_tab* pTab) +{ + if (pTab == NULL) { + return false; + } + + return drgui_tabbar_is_tab_in_view(pTab->pTBElement, pTab); +} + +void drgui_tab_move_into_view(drgui_tab* pTab) +{ + if (!drgui_tab_is_in_view(pTab)) { + drgui_tab_move_to_front(pTab); + } +} + + + + +DRGUI_PRIVATE void tab_append(drgui_tab* pTab, drgui_element* pTBElement) +{ + if (pTab == NULL || pTBElement == NULL) { + return; + } + + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + pTab->pTBElement = pTBElement; + if (pTB->pFirstTab == NULL) + { + assert(pTB->pLastTab == NULL); + + pTB->pFirstTab = pTab; + pTB->pLastTab = pTab; + } + else + { + assert(pTB->pLastTab != NULL); + + pTab->pPrevTab = pTB->pLastTab; + + pTB->pLastTab->pNextTab = pTab; + pTB->pLastTab = pTab; + } + + + if (pTB->isAutoSizeEnabled) { + drgui_tabbar_resize_by_tabs(pTBElement); + } + + // The content of the menu has changed so we'll need to schedule a redraw. + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + +DRGUI_PRIVATE void tab_prepend(drgui_tab* pTab, drgui_element* pTBElement) +{ + if (pTab == NULL || pTBElement == NULL) { + return; + } + + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + pTab->pTBElement = pTBElement; + if (pTB->pFirstTab == NULL) + { + assert(pTB->pLastTab == NULL); + + pTB->pFirstTab = pTab; + pTB->pLastTab = pTab; + } + else + { + assert(pTB->pLastTab != NULL); + + pTab->pNextTab = pTB->pFirstTab; + + pTB->pFirstTab->pPrevTab = pTab; + pTB->pFirstTab = pTab; + } + + + if (pTB->isAutoSizeEnabled) { + drgui_tabbar_resize_by_tabs(pTBElement); + } + + // The content of the menu has changed so we'll need to schedule a redraw. + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} + +DRGUI_PRIVATE void tab_detach_from_hierarchy(drgui_tab* pTab) +{ + if (pTab == NULL) { + return; + } + + drgui_element* pTBElement = pTab->pTBElement; + if (pTBElement == NULL) { + return; + } + + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + + if (pTab->pNextTab != NULL) { + pTab->pNextTab->pPrevTab = pTab->pPrevTab; + } + + if (pTab->pPrevTab != NULL) { + pTab->pPrevTab->pNextTab = pTab->pNextTab; + } + + + if (pTab == pTB->pFirstTab) { + pTB->pFirstTab = pTab->pNextTab; + } + + if (pTab == pTB->pLastTab) { + pTB->pLastTab = pTab->pPrevTab; + } + + + pTab->pNextTab = NULL; + pTab->pPrevTab = NULL; + pTab->pTBElement = NULL; +} + +DRGUI_PRIVATE void tab_detach(drgui_tab* pTab) +{ + if (pTab == NULL) { + return; + } + + drgui_element* pTBElement = pTab->pTBElement; + if (pTBElement == NULL) { + return; + } + + drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + if (pTB->pHoveredTab == pTab) { + pTB->pHoveredTab = NULL; + pTB->isCloseButtonHovered = false; + } + + if (pTB->pActiveTab == pTab) { + pTB->pActiveTab = NULL; + } + + if (pTB->pTabWithCloseButtonPressed == pTab) { + pTB->pTabWithCloseButtonPressed = NULL; + } + + + tab_detach_from_hierarchy(pTab); + + + if (pTB->isAutoSizeEnabled) { + drgui_tabbar_resize_by_tabs(pTBElement); + } + + // The content of the menu has changed so we'll need to schedule a redraw. + if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) { + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); + } +} +#endif //DR_GUI_IMPLEMENTATION + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// Text Box +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef DRGUI_NO_TEXT_EDITING +// QUICK NOTES +// +// - By default the cursor/caret does not blink automatically. Instead, the application must "step" the text box by +// calling drgui_textbox_step(). + +#ifndef drgui_textbox_h +#define drgui_textbox_h + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (* drgui_textbox_on_cursor_move_proc)(drgui_element* pTBElement); +typedef void (* drgui_textbox_on_undo_point_changed_proc)(drgui_element* pTBElement, unsigned int iUndoPoint); + + +/// Creates a new text box control. +drgui_element* drgui_create_textbox(drgui_context* pContext, drgui_element* pParent, size_t extraDataSize, const void* pExtraData); + +/// Deletest the given text box control. +void drgui_delete_textbox(drgui_element* pTBElement); + + +/// Retrieves the size of the extra data associated with the given text box. +size_t drgui_textbox_get_extra_data_size(drgui_element* pTBElement); + +/// Retrieves a pointer to the extra data associated with the given text box. +void* drgui_textbox_get_extra_data(drgui_element* pTBElement); + + +/// Sets the font to use with the given text box. +void drgui_textbox_set_font(drgui_element* pTBElement, drgui_font* pFont); + +/// Retrieves the font being used with the given text box. +drgui_font* drgui_textbox_get_font(drgui_element* pTBElement); + +/// Sets the color of the text in teh given text box. +void drgui_textbox_set_text_color(drgui_element* pTBElement, drgui_color color); + +/// Sets the background color of the given text box. +void drgui_textbox_set_background_color(drgui_element* pTBElement, drgui_color color); + +/// Sets the background color of selected text. +void drgui_textbox_set_selection_background_color(drgui_element* pTBElement, drgui_color color); + +/// Retrieves the background color of selected text. +drgui_color drgui_textbox_get_selection_background_color(drgui_element* pTBElement); + +/// Sets the background color for the line the caret is currently sitting on. +void drgui_textbox_set_active_line_background_color(drgui_element* pTBElement, drgui_color color); + +/// Sets the width of the text cursor. +void drgui_textbox_set_cursor_width(drgui_element* pTBElement, float cursorWidth); + +/// Retrieves the width of the text cursor. +float drgui_textbox_get_cursor_width(drgui_element* pTBElement); + +/// Sets the color of the cursor of the given text box. +void drgui_textbox_set_cursor_color(drgui_element* pTBElement, drgui_color color); + +/// Sets the border color of the given text box. +void drgui_textbox_set_border_color(drgui_element* pTBElement, drgui_color color); + +/// Sets the border width of the given text box. +void drgui_textbox_set_border_width(drgui_element* pTBElement, float borderWidth); + +/// Sets the amount of padding to apply to given text box. +void drgui_textbox_set_padding(drgui_element* pTBElement, float padding); + +/// Retrieves the amound of vertical padding to apply to the given text box. +float drgui_textbox_get_padding_vert(drgui_element* pTBElement); + +/// Retrieves the amound of horizontal padding to apply to the given text box. +float drgui_textbox_get_padding_horz(drgui_element* pTBElement); + +/// Sets the vertical alignment of the given text box. +void drgui_textbox_set_vertical_align(drgui_element* pTBElement, drgui_text_engine_alignment align); + +/// Sets the horizontal alignment of the given text box. +void drgui_textbox_set_horizontal_align(drgui_element* pTBElement, drgui_text_engine_alignment align); + +// Sets the width of the line numbers. +void drgui_textbox_set_line_numbers_width(drgui_element* pTBElement, float lineNumbersWidth); + +// Retrieves the width of the line numbers. +float drgui_textbox_get_line_numbers_width(drgui_element* pTBElement); + +// Sets the padding to apply between the line numbers and the text. +void drgui_textbox_set_line_numbers_padding(drgui_element* pTBElement, float lineNumbersPadding); + +// Retrieves the padding to apply between the line numbers and the text. +float drgui_textbox_get_line_numbers_padding(drgui_element* pTBElement); + +// Sets the color of the text of the line numbers. +void drgui_textbox_set_line_numbers_color(drgui_element* pTBElement, drgui_color color); + +// Retrieves the color of the text of the line numbers. +drgui_color drgui_textbox_get_line_numbers_color(drgui_element* pTBElement); + +// Sets the color of the background of the line numbers. +void drgui_textbox_set_line_numbers_background_color(drgui_element* pTBElement, drgui_color color); + +// Retrieves the color of the background of the line numbers. +drgui_color drgui_textbox_get_line_numbers_background_color(drgui_element* pTBElement); + + +/// Sets the text of the given text box. +void drgui_textbox_set_text(drgui_element* pTBElement, const char* text); + +/// Retrieves the text of the given text box. +size_t drgui_textbox_get_text(drgui_element* pTBElement, char* pTextOut, size_t textOutSize); + +/// Steps the text box to allow it to blink the cursor. +void drgui_textbox_step(drgui_element* pTBElement, unsigned int milliseconds); + +/// Sets the blink rate of the cursor in milliseconds. +void drgui_textbox_set_cursor_blink_rate(drgui_element* pTBElement, unsigned int blinkRateInMilliseconds); + +/// Moves the caret to the end of the text. +void drgui_textbox_move_cursor_to_end_of_text(drgui_element* pTBElement); + +/// Moves the caret to the beginning of the line at the given index. +void drgui_textbox_move_cursor_to_start_of_line_by_index(drgui_element* pTBElement, size_t iLine); + +/// Determines whether or not anything is selected in the given text box. +bool drgui_textbox_is_anything_selected(drgui_element* pTBElement); + +/// Selects all of the text inside the text box. +void drgui_textbox_select_all(drgui_element* pTBElement); + +/// Deselect everything. +void drgui_textbox_deselect_all(drgui_element* pTBElement); + +/// Retrieves a copy of the selected text. +/// +/// @remarks +/// This returns the length of the selected text. Call this once with set to NULL to calculate the required size of the +/// buffer. +/// @par +/// If the output buffer is not larger enough, the string will be truncated. +size_t drgui_textbox_get_selected_text(drgui_element* pTBElement, char* textOut, size_t textOutLength); + +/// Deletes the character to the right of the cursor. +/// +/// @return True if the text within the text engine has changed. +bool drgui_textbox_delete_character_to_right_of_cursor(drgui_element* pTBElement); + +/// Deletes the currently selected text. +/// +/// @return True if the text within the text engine has changed. +bool drgui_textbox_delete_selected_text(drgui_element* pTBElement); + +/// Inserts a character at the position of the cursor. +/// +/// @return True if the text within the text engine has changed. +bool drgui_textbox_insert_text_at_cursor(drgui_element* pTBElement, const char* text); + +/// Performs an undo operation. +bool drgui_textbox_undo(drgui_element* pTBElement); + +/// Performs a redo operation. +bool drgui_textbox_redo(drgui_element* pTBElement); + +/// Retrieves the number of undo points remaining. +unsigned int drgui_textbox_get_undo_points_remaining_count(drgui_element* pTBElement); + +/// Retrieves the number of redo points remaining. +unsigned int drgui_textbox_get_redo_points_remaining_count(drgui_element* pTBElement); + +/// Clears the undo/redo stack. +void drgui_textbox_clear_undo_stack(drgui_element* pTBElement); + +/// Retrieves the index of the line the cursor is current sitting on. +size_t drgui_textbox_get_cursor_line(drgui_element* pTBElement); + +/// Retrieves the index of the column the cursor is current sitting on. +size_t drgui_textbox_get_cursor_column(drgui_element* pTBElement); + +/// Retrieves the number of lines in the given text box. +size_t drgui_textbox_get_line_count(drgui_element* pTBElement); + + +/// Finds and selects the next occurance of the given string, starting from the cursor and looping back to the start. +bool drgui_textbox_find_and_select_next(drgui_element* pTBElement, const char* text); + +/// Finds the next occurance of the given string and replaces it with another. +bool drgui_textbox_find_and_replace_next(drgui_element* pTBElement, const char* text, const char* replacement); + +/// Finds every occurance of the given string and replaces it with another. +bool drgui_textbox_find_and_replace_all(drgui_element* pTBElement, const char* text, const char* replacement); + + +/// Shows the line numbers. +void drgui_textbox_show_line_numbers(drgui_element* pTBElement); + +/// Hides the line numbers. +void drgui_textbox_hide_line_numbers(drgui_element* pTBElement); + + +/// Disables the vertical scrollbar. +void drgui_textbox_disable_vertical_scrollbar(drgui_element* pTBElement); + +/// Enables the vertical scrollbar. +void drgui_textbox_enable_vertical_scrollbar(drgui_element* pTBElement); + +/// Disables the horizontal scrollbar. +void drgui_textbox_disable_horizontal_scrollbar(drgui_element* pTBElement); + +/// Enables the horizontal scrollbar. +void drgui_textbox_enable_horizontal_scrollbar(drgui_element* pTBElement); + +// Retrieves the vertical scrollbar. +drgui_element* drgui_textbox_get_vertical_scrollbar(drgui_element* pTBElement); + +// Retrieves the horizontal scrollbar. +drgui_element* drgui_textbox_get_horizontal_scrollbar(drgui_element* pTBElement); + +// Sets the size of both the vertical and horizontal scrollbars. +void drgui_textbox_set_scrollbar_size(drgui_element* pTBElement, float size); + + +/// Sets the function to call when the cursor moves. +void drgui_textbox_set_on_cursor_move(drgui_element* pTBElement, drgui_textbox_on_cursor_move_proc proc); + +/// Sets the function to call when the undo point changes. +void drgui_textbox_set_on_undo_point_changed(drgui_element* pTBElement, drgui_textbox_on_undo_point_changed_proc proc); + + + +/// on_size. +void drgui_textbox_on_size(drgui_element* pTBElement, float newWidth, float newHeight); + +/// on_mouse_move. +void drgui_textbox_on_mouse_move(drgui_element* pTBElement, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// on_mouse_button_down. +void drgui_textbox_on_mouse_button_down(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// on_mouse_button_up. +void drgui_textbox_on_mouse_button_up(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// on_mouse_button_dblclick. +void drgui_textbox_on_mouse_button_dblclick(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// on_mouse_wheel +void drgui_textbox_on_mouse_wheel(drgui_element* pTBElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// on_key_down. +void drgui_textbox_on_key_down(drgui_element* pTBElement, drgui_key key, int stateFlags); + +/// on_key_up. +void drgui_textbox_on_key_up(drgui_element* pTBElement, drgui_key key, int stateFlags); + +/// on_printable_key_down. +void drgui_textbox_on_printable_key_down(drgui_element* pTBElement, unsigned int utf32, int stateFlags); + +/// on_paint. +void drgui_textbox_on_paint(drgui_element* pTBElement, drgui_rect relativeRect, void* pPaintData); + +/// on_capture_keyboard +void drgui_textbox_on_capture_keyboard(drgui_element* pTBElement, drgui_element* pPrevCapturedElement); + +/// on_release_keyboard +void drgui_textbox_on_release_keyboard(drgui_element* pTBElement, drgui_element* pNewCapturedElement); + +/// on_capture_mouse +void drgui_textbox_on_capture_mouse(drgui_element* pTBElement); + +/// on_release_mouse +void drgui_textbox_on_release_mouse(drgui_element* pTBElement); + + + +#ifdef __cplusplus +} +#endif +#endif //drgui_textbox_h + + +#ifdef DR_GUI_IMPLEMENTATION +typedef struct +{ + /// The text engine. + drgui_text_engine* pTL; + + /// The vertical scrollbar. + drgui_element* pVertScrollbar; + + /// The horizontal scrollbar. + drgui_element* pHorzScrollbar; + + /// The line numbers element. + drgui_element* pLineNumbers; + + + /// The color of the border. + drgui_color borderColor; + + /// The width of the border. + float borderWidth; + + /// The amount of padding to apply the left and right of the text. + float padding; + + // The width of the line numbers. + float lineNumbersWidth; + + /// The padding to the right of the line numbers. + float lineNumbersPaddingRight; + + // The color of the text of the line numbers. + drgui_color lineNumbersColor; + + // The color of the background of the line numbers. + drgui_color lineNumbersBackgroundColor; + + + /// The desired width of the vertical scrollbar. + float vertScrollbarSize; + + /// The desired height of the horizontal scrollbar. + float horzScrollbarSize; + + /// Whether or not the vertical scrollbar is enabled. + bool isVertScrollbarEnabled; + + /// Whether or not the horizontal scrollbar is enabled. + bool isHorzScrollbarEnabled; + + + /// When selecting lines by clicking and dragging on the line numbers, keeps track of the line to anchor the selection to. + size_t iLineSelectAnchor; + + + /// The function to call when the text cursor/caret moves. + drgui_textbox_on_cursor_move_proc onCursorMove; + + /// The function to call when the undo point changes. + drgui_textbox_on_undo_point_changed_proc onUndoPointChanged; + + + /// The size of the extra data. + size_t extraDataSize; + + /// A pointer to the extra data. + char pExtraData[1]; + +} drgui_textbox; + + +/// Retrieves the offset to draw the text in the text box. +DRGUI_PRIVATE void drgui_textbox__get_text_offset(drgui_element* pTBElement, float* pOffsetXOut, float* pOffsetYOut); + +/// Calculates the required size of the text engine. +DRGUI_PRIVATE void drgui_textbox__calculate_text_engine_container_size(drgui_element* pTBElement, float* pWidthOut, float* pHeightOut); + +/// Retrieves the rectangle of the text engine's container. +DRGUI_PRIVATE drgui_rect drgui_textbox__get_text_rect(drgui_element* pTBElement); + +/// Refreshes the range, page sizes and layouts of the scrollbars. +DRGUI_PRIVATE void drgui_textbox__refresh_scrollbars(drgui_element* pTBElement); + +/// Refreshes the range and page sizes of the scrollbars. +DRGUI_PRIVATE void drgui_textbox__refresh_scrollbar_ranges(drgui_element* pTBElement); + +/// Refreshes the size and position of the scrollbars. +DRGUI_PRIVATE void drgui_textbox__refresh_scrollbar_layouts(drgui_element* pTBElement); + +/// Retrieves a rectangle representing the space between the edges of the two scrollbars. +DRGUI_PRIVATE drgui_rect drgui_textbox__get_scrollbar_dead_space_rect(drgui_element* pTBElement); + + +/// Called when a mouse button is pressed on the line numbers element. +DRGUI_PRIVATE void drgui_textbox__on_mouse_move_line_numbers(drgui_element* pLineNumbers, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when a mouse button is pressed on the line numbers element. +DRGUI_PRIVATE void drgui_textbox__on_mouse_button_down_line_numbers(drgui_element* pLineNumbers, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when a mouse button is pressed on the line numbers element. +DRGUI_PRIVATE void drgui_textbox__on_mouse_button_up_line_numbers(drgui_element* pLineNumbers, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when the line numbers element needs to be drawn. +DRGUI_PRIVATE void drgui_textbox__on_paint_line_numbers(drgui_element* pLineNumbers, drgui_rect relativeRect, void* pPaintData); + +/// Refreshes the line number of the given text editor. +DRGUI_PRIVATE void drgui_textbox__refresh_line_numbers(drgui_element* pTBElement); + + +/// on_paint_rect() +DRGUI_PRIVATE void drgui_textbox__on_text_engine_paint_rect(drgui_text_engine* pLayout, drgui_rect rect, drgui_color color, drgui_element* pTBElement, void* pPaintData); + +/// on_paint_text() +DRGUI_PRIVATE void drgui_textbox__on_text_engine_paint_text(drgui_text_engine* pTL, drgui_text_run* pRun, drgui_element* pTBElement, void* pPaintData); + +/// on_dirty() +DRGUI_PRIVATE void drgui_textbox__on_text_engine_dirty(drgui_text_engine* pTL, drgui_rect rect); + +/// on_cursor_move() +DRGUI_PRIVATE void drgui_textbox__on_text_engine_cursor_move(drgui_text_engine* pTL); + +/// on_text_changed() +DRGUI_PRIVATE void drgui_textbox__on_text_engine_text_changed(drgui_text_engine* pTL); + +/// on_undo_point_changed() +DRGUI_PRIVATE void drgui_textbox__on_text_engine_undo_point_changed(drgui_text_engine* pTL, unsigned int iUndoPoint); + + +DRGUI_PRIVATE void drgui_textbox__on_vscroll(drgui_element* pSBElement, int scrollPos) +{ + drgui_element* pTBElement = *(drgui_element**)drgui_sb_get_extra_data(pSBElement); + assert(pTBElement != NULL); + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + drgui_text_engine_set_inner_offset_y(pTB->pTL, -drgui_text_engine_get_line_pos_y(pTB->pTL, scrollPos)); + + // The line numbers need to be redrawn. + drgui_dirty(pTB->pLineNumbers, drgui_get_local_rect(pTB->pLineNumbers)); +} + +DRGUI_PRIVATE void drgui_textbox__on_hscroll(drgui_element* pSBElement, int scrollPos) +{ + drgui_element* pTBElement = *(drgui_element**)drgui_sb_get_extra_data(pSBElement); + assert(pTBElement != NULL); + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + drgui_text_engine_set_inner_offset_x(pTB->pTL, (float)-scrollPos); +} + + + +drgui_element* drgui_create_textbox(drgui_context* pContext, drgui_element* pParent, size_t extraDataSize, const void* pExtraData) +{ + if (pContext == NULL) { + return NULL; + } + + drgui_element* pTBElement = drgui_create_element(pContext, pParent, sizeof(drgui_textbox) + extraDataSize, NULL); + if (pTBElement == NULL) { + return NULL; + } + + drgui_set_cursor(pTBElement, drgui_cursor_text); + drgui_set_on_size(pTBElement, drgui_textbox_on_size); + drgui_set_on_mouse_move(pTBElement, drgui_textbox_on_mouse_move); + drgui_set_on_mouse_button_down(pTBElement, drgui_textbox_on_mouse_button_down); + drgui_set_on_mouse_button_up(pTBElement, drgui_textbox_on_mouse_button_up); + drgui_set_on_mouse_button_dblclick(pTBElement, drgui_textbox_on_mouse_button_dblclick); + drgui_set_on_mouse_wheel(pTBElement, drgui_textbox_on_mouse_wheel); + drgui_set_on_key_down(pTBElement, drgui_textbox_on_key_down); + drgui_set_on_printable_key_down(pTBElement, drgui_textbox_on_printable_key_down); + drgui_set_on_paint(pTBElement, drgui_textbox_on_paint); + drgui_set_on_capture_keyboard(pTBElement, drgui_textbox_on_capture_keyboard); + drgui_set_on_release_keyboard(pTBElement, drgui_textbox_on_release_keyboard); + drgui_set_on_capture_mouse(pTBElement, drgui_textbox_on_capture_mouse); + drgui_set_on_release_mouse(pTBElement, drgui_textbox_on_release_mouse); + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + pTB->pVertScrollbar = drgui_create_scrollbar(pContext, pTBElement, drgui_sb_orientation_vertical, sizeof(pTBElement), &pTBElement); + drgui_sb_set_on_scroll(pTB->pVertScrollbar, drgui_textbox__on_vscroll); + drgui_sb_set_mouse_wheel_scele(pTB->pVertScrollbar, 3); + + pTB->pHorzScrollbar = drgui_create_scrollbar(pContext, pTBElement, drgui_sb_orientation_horizontal, sizeof(pTBElement), &pTBElement); + drgui_sb_set_on_scroll(pTB->pHorzScrollbar, drgui_textbox__on_hscroll); + + pTB->pLineNumbers = drgui_create_element(pContext, pTBElement, sizeof(pTBElement), &pTBElement); + drgui_hide(pTB->pLineNumbers); + drgui_set_on_mouse_move(pTB->pLineNumbers, drgui_textbox__on_mouse_move_line_numbers); + drgui_set_on_mouse_button_down(pTB->pLineNumbers, drgui_textbox__on_mouse_button_down_line_numbers); + drgui_set_on_mouse_button_up(pTB->pLineNumbers, drgui_textbox__on_mouse_button_up_line_numbers); + drgui_set_on_paint(pTB->pLineNumbers, drgui_textbox__on_paint_line_numbers); + + pTB->pTL = drgui_create_text_engine(pContext, sizeof(pTBElement), &pTBElement); + if (pTB->pTL == NULL) { + drgui_delete_element(pTBElement); + return NULL; + } + + drgui_text_engine_set_on_paint_rect(pTB->pTL, drgui_textbox__on_text_engine_paint_rect); + drgui_text_engine_set_on_paint_text(pTB->pTL, drgui_textbox__on_text_engine_paint_text); + drgui_text_engine_set_on_dirty(pTB->pTL, drgui_textbox__on_text_engine_dirty); + drgui_text_engine_set_on_cursor_move(pTB->pTL, drgui_textbox__on_text_engine_cursor_move); + drgui_text_engine_set_on_text_changed(pTB->pTL, drgui_textbox__on_text_engine_text_changed); + drgui_text_engine_set_on_undo_point_changed(pTB->pTL, drgui_textbox__on_text_engine_undo_point_changed); + drgui_text_engine_set_default_text_color(pTB->pTL, drgui_rgb(0, 0, 0)); + drgui_text_engine_set_cursor_color(pTB->pTL, drgui_rgb(0, 0, 0)); + drgui_text_engine_set_default_bg_color(pTB->pTL, drgui_rgb(255, 255, 255)); + drgui_text_engine_set_active_line_bg_color(pTB->pTL, drgui_rgb(255, 255, 255)); + drgui_text_engine_set_vertical_align(pTB->pTL, drgui_text_engine_alignment_center); + + pTB->borderColor = drgui_rgb(0, 0, 0); + pTB->borderWidth = 1; + pTB->padding = 2; + pTB->lineNumbersWidth = 64; + pTB->lineNumbersPaddingRight = 16; + pTB->lineNumbersColor = drgui_rgb(80, 160, 192); + pTB->lineNumbersBackgroundColor = drgui_text_engine_get_default_bg_color(pTB->pTL); + pTB->vertScrollbarSize = 16; + pTB->horzScrollbarSize = 16; + pTB->isVertScrollbarEnabled = true; + pTB->isHorzScrollbarEnabled = true; + pTB->iLineSelectAnchor = 0; + pTB->onCursorMove = NULL; + pTB->onUndoPointChanged = NULL; + + pTB->extraDataSize = extraDataSize; + if (pExtraData != NULL) { + memcpy(pTB->pExtraData, pExtraData, extraDataSize); + } + + return pTBElement; +} + +void drgui_delete_textbox(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (pTB->pTL) { + drgui_delete_text_engine(pTB->pTL); + pTB->pTL = NULL; + } + + if (pTB->pLineNumbers) { + drgui_delete_element(pTB->pLineNumbers); + pTB->pLineNumbers = NULL; + } + + if (pTB->pHorzScrollbar) { + drgui_delete_element(pTB->pHorzScrollbar); + pTB->pHorzScrollbar = NULL; + } + + if (pTB->pVertScrollbar) { + drgui_delete_element(pTB->pVertScrollbar); + pTB->pVertScrollbar = NULL; + } + + drgui_delete_element(pTBElement); +} + +size_t drgui_textbox_get_extra_data_size(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + return pTB->extraDataSize; +} + +void* drgui_textbox_get_extra_data(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return NULL; + } + + return pTB->pExtraData; +} + + +void drgui_textbox_set_font(drgui_element* pTBElement, drgui_font* pFont) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_begin_dirty(pTBElement); + { + drgui_text_engine_set_default_font(pTB->pTL, pFont); + + // The font used for line numbers are tied to the main font at the moment. + drgui_textbox__refresh_line_numbers(pTBElement); + + // Emulate a scroll to ensure the scroll position is pinned to a line. + drgui_textbox__on_vscroll(pTB->pVertScrollbar, drgui_sb_get_scroll_position(pTB->pVertScrollbar)); + drgui_textbox__refresh_scrollbars(pTBElement); + + // The caret position needs to be refreshes. We'll cheat here a little bit and just do a full refresh of the text engine. + //drgui_text_engine__refresh(pTB->pTL); + drgui_text_engine_refresh_markers(pTB->pTL); + } + drgui_end_dirty(pTBElement); +} + +drgui_font* drgui_textbox_get_font(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return NULL; + } + + return drgui_text_engine_get_default_font(pTB->pTL); +} + +void drgui_textbox_set_text_color(drgui_element* pTBElement, drgui_color color) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_set_default_text_color(pTB->pTL, color); +} + +void drgui_textbox_set_background_color(drgui_element* pTBElement, drgui_color color) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_set_default_bg_color(pTB->pTL, color); +} + +void drgui_textbox_set_selection_background_color(drgui_element* pTBElement, drgui_color color) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_set_selection_bg_color(pTB->pTL, color); +} + +drgui_color drgui_textbox_get_selection_background_color(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return drgui_rgb(0, 0, 0); + } + + return drgui_text_engine_get_selection_bg_color(pTB->pTL); +} + +void drgui_textbox_set_active_line_background_color(drgui_element* pTBElement, drgui_color color) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_set_active_line_bg_color(pTB->pTL, color); +} + +void drgui_textbox_set_cursor_width(drgui_element* pTBElement, float cursorWidth) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_set_cursor_width(pTB->pTL, cursorWidth); +} + +float drgui_textbox_get_cursor_width(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + return drgui_text_engine_get_cursor_width(pTB->pTL); +} + +void drgui_textbox_set_cursor_color(drgui_element* pTBElement, drgui_color color) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_set_cursor_color(pTB->pTL, color); +} + +void drgui_textbox_set_border_color(drgui_element* pTBElement, drgui_color color) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->borderColor = color; +} + +void drgui_textbox_set_border_width(drgui_element* pTBElement, float borderWidth) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->borderWidth = borderWidth; +} + +void drgui_textbox_set_padding(drgui_element* pTBElement, float padding) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->padding = padding; +} + +float drgui_textbox_get_padding_vert(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + return pTB->padding; +} + +float drgui_textbox_get_padding_horz(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + return pTB->padding; +} + +void drgui_textbox_set_vertical_align(drgui_element* pTBElement, drgui_text_engine_alignment align) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_set_vertical_align(pTB->pTL, align); +} + +void drgui_textbox_set_horizontal_align(drgui_element* pTBElement, drgui_text_engine_alignment align) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_set_horizontal_align(pTB->pTL, align); +} + +void drgui_textbox_set_line_numbers_width(drgui_element* pTBElement, float lineNumbersWidth) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->lineNumbersWidth = lineNumbersWidth; +} + +float drgui_textbox_get_line_numbers_width(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + return pTB->lineNumbersWidth; +} + +void drgui_textbox_set_line_numbers_padding(drgui_element* pTBElement, float lineNumbersPadding) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->lineNumbersPaddingRight = lineNumbersPadding; +} + +float drgui_textbox_get_line_numbers_padding(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + return pTB->lineNumbersPaddingRight; +} + +void drgui_textbox_set_line_numbers_color(drgui_element* pTBElement, drgui_color color) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->lineNumbersColor = color; + drgui_textbox__refresh_line_numbers(pTBElement); +} + +drgui_color drgui_textbox_get_line_numbers_color(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return drgui_rgb(0, 0, 0); + } + + return pTB->lineNumbersColor; +} + +void drgui_textbox_set_line_numbers_background_color(drgui_element* pTBElement, drgui_color color) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->lineNumbersBackgroundColor = color; + drgui_textbox__refresh_line_numbers(pTBElement); +} + +drgui_color drgui_textbox_get_line_numbers_background_color(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return drgui_rgb(0, 0, 0); + } + + return pTB->lineNumbersBackgroundColor; +} + + + +void drgui_textbox_set_text(drgui_element* pTBElement, const char* text) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_prepare_undo_point(pTB->pTL); + { + drgui_text_engine_set_text(pTB->pTL, text); + } + drgui_text_engine_commit_undo_point(pTB->pTL); +} + +size_t drgui_textbox_get_text(drgui_element* pTBElement, char* pTextOut, size_t textOutSize) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + return drgui_text_engine_get_text(pTB->pTL, pTextOut, textOutSize); +} + +void drgui_textbox_step(drgui_element* pTBElement, unsigned int milliseconds) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_step(pTB->pTL, milliseconds); +} + +void drgui_textbox_set_cursor_blink_rate(drgui_element* pTBElement, unsigned int blinkRateInMilliseconds) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_set_cursor_blink_rate(pTB->pTL, blinkRateInMilliseconds); +} + +void drgui_textbox_move_cursor_to_end_of_text(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_move_cursor_to_end_of_text(pTB->pTL); +} + +void drgui_textbox_move_cursor_to_start_of_line_by_index(drgui_element* pTBElement, size_t iLine) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_move_cursor_to_start_of_line_by_index(pTB->pTL, iLine); +} + + +bool drgui_textbox_is_anything_selected(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return false; + } + + return drgui_text_engine_is_anything_selected(pTB->pTL); +} + +void drgui_textbox_select_all(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_select_all(pTB->pTL); +} + +void drgui_textbox_deselect_all(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_deselect_all(pTB->pTL); +} + +size_t drgui_textbox_get_selected_text(drgui_element* pTBElement, char* textOut, size_t textOutLength) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + return drgui_text_engine_get_selected_text(pTB->pTL, textOut, textOutLength); +} + +bool drgui_textbox_delete_character_to_right_of_cursor(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return false; + } + + bool wasTextChanged = false; + drgui_text_engine_prepare_undo_point(pTB->pTL); + { + wasTextChanged = drgui_text_engine_delete_character_to_right_of_cursor(pTB->pTL); + } + if (wasTextChanged) { drgui_text_engine_commit_undo_point(pTB->pTL); } + + return wasTextChanged; +} + +bool drgui_textbox_delete_selected_text(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return false; + } + + bool wasTextChanged = false; + drgui_text_engine_prepare_undo_point(pTB->pTL); + { + wasTextChanged = drgui_text_engine_delete_selected_text(pTB->pTL); + } + if (wasTextChanged) { drgui_text_engine_commit_undo_point(pTB->pTL); } + + return wasTextChanged; +} + +bool drgui_textbox_insert_text_at_cursor(drgui_element* pTBElement, const char* text) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return false;; + } + + bool wasTextChanged = false; + drgui_text_engine_prepare_undo_point(pTB->pTL); + { + wasTextChanged = drgui_text_engine_insert_text_at_cursor(pTB->pTL, text); + } + if (wasTextChanged) { drgui_text_engine_commit_undo_point(pTB->pTL); } + + return wasTextChanged; +} + +bool drgui_textbox_undo(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return false; + } + + return drgui_text_engine_undo(pTB->pTL); +} + +bool drgui_textbox_redo(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return false; + } + + return drgui_text_engine_redo(pTB->pTL); +} + +unsigned int drgui_textbox_get_undo_points_remaining_count(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return false; + } + + return drgui_text_engine_get_undo_points_remaining_count(pTB->pTL); +} + +unsigned int drgui_textbox_get_redo_points_remaining_count(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return false; + } + + return drgui_text_engine_get_redo_points_remaining_count(pTB->pTL); +} + +void drgui_textbox_clear_undo_stack(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_clear_undo_stack(pTB->pTL); +} + + +size_t drgui_textbox_get_cursor_line(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + return drgui_text_engine_get_cursor_line(pTB->pTL); +} + +size_t drgui_textbox_get_cursor_column(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + return drgui_text_engine_get_cursor_column(pTB->pTL); +} + +size_t drgui_textbox_get_line_count(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + return drgui_text_engine_get_line_count(pTB->pTL); +} + + +bool drgui_textbox_find_and_select_next(drgui_element* pTBElement, const char* text) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + size_t selectionStart; + size_t selectionEnd; + if (drgui_text_engine_find_next(pTB->pTL, text, &selectionStart, &selectionEnd)) + { + drgui_text_engine_select(pTB->pTL, selectionStart, selectionEnd); + drgui_text_engine_move_cursor_to_end_of_selection(pTB->pTL); + + return true; + } + + return false; +} + +bool drgui_textbox_find_and_replace_next(drgui_element* pTBElement, const char* text, const char* replacement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + bool wasTextChanged = false; + drgui_text_engine_prepare_undo_point(pTB->pTL); + { + size_t selectionStart; + size_t selectionEnd; + if (drgui_text_engine_find_next(pTB->pTL, text, &selectionStart, &selectionEnd)) + { + drgui_text_engine_select(pTB->pTL, selectionStart, selectionEnd); + drgui_text_engine_move_cursor_to_end_of_selection(pTB->pTL); + + wasTextChanged = drgui_text_engine_delete_selected_text(pTB->pTL) || wasTextChanged; + wasTextChanged = drgui_text_engine_insert_text_at_cursor(pTB->pTL, replacement) || wasTextChanged; + } + } + if (wasTextChanged) { drgui_text_engine_commit_undo_point(pTB->pTL); } + + return wasTextChanged; +} + +bool drgui_textbox_find_and_replace_all(drgui_element* pTBElement, const char* text, const char* replacement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return 0; + } + + size_t originalCursorLine = drgui_text_engine_get_cursor_line(pTB->pTL); + size_t originalCursorPos = drgui_text_engine_get_cursor_character(pTB->pTL) - drgui_text_engine_get_line_first_character(pTB->pTL, originalCursorLine); + int originalScrollPosX = drgui_sb_get_scroll_position(pTB->pHorzScrollbar); + int originalScrollPosY = drgui_sb_get_scroll_position(pTB->pVertScrollbar); + + bool wasTextChanged = false; + drgui_text_engine_prepare_undo_point(pTB->pTL); + { + // It's important that we don't replace the replacement text. To handle this, we just move the cursor to the top of the text and find + // and replace every occurance without looping. + drgui_text_engine_move_cursor_to_start_of_text(pTB->pTL); + + size_t selectionStart; + size_t selectionEnd; + while (drgui_text_engine_find_next_no_loop(pTB->pTL, text, &selectionStart, &selectionEnd)) + { + drgui_text_engine_select(pTB->pTL, selectionStart, selectionEnd); + drgui_text_engine_move_cursor_to_end_of_selection(pTB->pTL); + + wasTextChanged = drgui_text_engine_delete_selected_text(pTB->pTL) || wasTextChanged; + wasTextChanged = drgui_text_engine_insert_text_at_cursor(pTB->pTL, replacement) || wasTextChanged; + } + + // The cursor may have moved so we'll need to restore it. + size_t lineCharStart; + size_t lineCharEnd; + drgui_text_engine_get_line_character_range(pTB->pTL, originalCursorLine, &lineCharStart, &lineCharEnd); + + size_t newCursorPos = lineCharStart + originalCursorPos; + if (newCursorPos > lineCharEnd) { + newCursorPos = lineCharEnd; + } + drgui_text_engine_move_cursor_to_character(pTB->pTL, newCursorPos); + } + if (wasTextChanged) { drgui_text_engine_commit_undo_point(pTB->pTL); } + + + // The scroll positions may have moved so we'll need to restore them. + drgui_sb_scroll_to(pTB->pHorzScrollbar, originalScrollPosX); + drgui_sb_scroll_to(pTB->pVertScrollbar, originalScrollPosY); + + return wasTextChanged; +} + + +void drgui_textbox_show_line_numbers(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_show(pTB->pLineNumbers); + drgui_textbox__refresh_line_numbers(pTBElement); +} + +void drgui_textbox_hide_line_numbers(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_hide(pTB->pLineNumbers); + drgui_textbox__refresh_line_numbers(pTBElement); +} + + +void drgui_textbox_disable_vertical_scrollbar(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (pTB->isVertScrollbarEnabled) { + pTB->isVertScrollbarEnabled = false; + drgui_textbox__refresh_scrollbars(pTBElement); + } +} + +void drgui_textbox_enable_vertical_scrollbar(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (!pTB->isVertScrollbarEnabled) { + pTB->isVertScrollbarEnabled = true; + drgui_textbox__refresh_scrollbars(pTBElement); + } +} + +void drgui_textbox_disable_horizontal_scrollbar(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (pTB->isHorzScrollbarEnabled) { + pTB->isHorzScrollbarEnabled = false; + drgui_textbox__refresh_scrollbars(pTBElement); + } +} + +void drgui_textbox_enable_horizontal_scrollbar(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (!pTB->isHorzScrollbarEnabled) { + pTB->isHorzScrollbarEnabled = true; + drgui_textbox__refresh_scrollbars(pTBElement); + } +} + +drgui_element* drgui_textbox_get_vertical_scrollbar(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return NULL; + } + + return pTB->pVertScrollbar; +} + +drgui_element* drgui_textbox_get_horizontal_scrollbar(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return NULL; + } + + return pTB->pHorzScrollbar; +} + +void drgui_textbox_set_scrollbar_size(drgui_element* pTBElement, float size) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->horzScrollbarSize = size; + pTB->vertScrollbarSize = size; + + drgui_textbox__refresh_scrollbars(pTBElement); +} + + +void drgui_textbox_set_on_cursor_move(drgui_element* pTBElement, drgui_textbox_on_cursor_move_proc proc) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->onCursorMove = proc; +} + +void drgui_textbox_set_on_undo_point_changed(drgui_element* pTBElement, drgui_textbox_on_undo_point_changed_proc proc) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + pTB->onUndoPointChanged = proc; +} + + +void drgui_textbox_on_size(drgui_element* pTBElement, float newWidth, float newHeight) +{ + (void)newWidth; + (void)newHeight; + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + // The text engine needs to be resized. + float containerWidth; + float containerHeight; + drgui_textbox__calculate_text_engine_container_size(pTBElement, &containerWidth, &containerHeight); + drgui_text_engine_set_container_size(pTB->pTL, containerWidth, containerHeight); + + // Scrollbars need to be refreshed first. + drgui_textbox__refresh_scrollbars(pTBElement); + + // Line numbers need to be refreshed. + drgui_textbox__refresh_line_numbers(pTBElement); +} + +void drgui_textbox_on_mouse_move(drgui_element* pTBElement, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)stateFlags; + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (drgui_get_element_with_mouse_capture(pTBElement->pContext) == pTBElement) + { + float offsetX; + float offsetY; + drgui_textbox__get_text_offset(pTBElement, &offsetX, &offsetY); + + drgui_text_engine_move_cursor_to_point(pTB->pTL, (float)relativeMousePosX - offsetX, (float)relativeMousePosY - offsetY); + } +} + +void drgui_textbox_on_mouse_button_down(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + // Focus the text editor. + drgui_capture_keyboard(pTBElement); + + if (mouseButton == DRGUI_MOUSE_BUTTON_LEFT) + { + // If we are not in selection mode, make sure everything is deselected. + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) == 0) { + drgui_text_engine_deselect_all(pTB->pTL); + drgui_text_engine_leave_selection_mode(pTB->pTL); + } else { + drgui_text_engine_enter_selection_mode(pTB->pTL); + } + + float offsetX; + float offsetY; + drgui_textbox__get_text_offset(pTBElement, &offsetX, &offsetY); + drgui_text_engine_move_cursor_to_point(pTB->pTL, (float)relativeMousePosX - offsetX, (float)relativeMousePosY - offsetY); + + // In order to support selection with the mouse we need to capture the mouse and enter selection mode. + drgui_capture_mouse(pTBElement); + + // If we didn't previously enter selection mode we'll need to do that now so we can drag select. + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) == 0) { + drgui_text_engine_enter_selection_mode(pTB->pTL); + } + } + + if (mouseButton == DRGUI_MOUSE_BUTTON_RIGHT) + { + drgui_text_engine_leave_selection_mode(pTB->pTL); + } +} + +void drgui_textbox_on_mouse_button_up(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)relativeMousePosX; + (void)relativeMousePosY; + (void)stateFlags; + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (mouseButton == DRGUI_MOUSE_BUTTON_LEFT) + { + if (drgui_get_element_with_mouse_capture(pTBElement->pContext) == pTBElement) + { + // Releasing the mouse will leave selectionmode. + drgui_release_mouse(pTBElement->pContext); + } + } +} + +void drgui_textbox_on_mouse_button_dblclick(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)mouseButton; + (void)relativeMousePosX; + (void)relativeMousePosY; + (void)stateFlags; + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } +} + +void drgui_textbox_on_mouse_wheel(drgui_element* pTBElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)relativeMousePosX; + (void)relativeMousePosY; + (void)stateFlags; + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_sb_scroll(pTB->pVertScrollbar, -delta * drgui_sb_get_mouse_wheel_scale(pTB->pVertScrollbar)); +} + +void drgui_textbox_on_key_down(drgui_element* pTBElement, drgui_key key, int stateFlags) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + switch (key) + { + case DRGUI_BACKSPACE: + { + bool wasTextChanged = false; + drgui_text_engine_prepare_undo_point(pTB->pTL); + { + if (drgui_text_engine_is_anything_selected(pTB->pTL)) { + wasTextChanged = drgui_text_engine_delete_selected_text(pTB->pTL); + } else { + wasTextChanged = drgui_text_engine_delete_character_to_left_of_cursor(pTB->pTL); + } + } + if (wasTextChanged) { drgui_text_engine_commit_undo_point(pTB->pTL); } + } break; + + case DRGUI_DELETE: + { + bool wasTextChanged = false; + drgui_text_engine_prepare_undo_point(pTB->pTL); + { + if (drgui_text_engine_is_anything_selected(pTB->pTL)) { + wasTextChanged = drgui_text_engine_delete_selected_text(pTB->pTL); + } else { + wasTextChanged = drgui_text_engine_delete_character_to_right_of_cursor(pTB->pTL); + } + } + if (wasTextChanged) { drgui_text_engine_commit_undo_point(pTB->pTL); } + } break; + + + case DRGUI_ARROW_LEFT: + { + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_enter_selection_mode(pTB->pTL); + } + + if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) { + drgui_text_engine_move_cursor_to_start_of_selection(pTB->pTL); + drgui_text_engine_deselect_all(pTB->pTL); + } else { + drgui_text_engine_move_cursor_left(pTB->pTL); + } + + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_leave_selection_mode(pTB->pTL); + } + } break; + + case DRGUI_ARROW_RIGHT: + { + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_enter_selection_mode(pTB->pTL); + } + + if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) { + drgui_text_engine_move_cursor_to_end_of_selection(pTB->pTL); + drgui_text_engine_deselect_all(pTB->pTL); + } else { + drgui_text_engine_move_cursor_right(pTB->pTL); + } + + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_leave_selection_mode(pTB->pTL); + } + } break; + + case DRGUI_ARROW_UP: + { + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_enter_selection_mode(pTB->pTL); + } + + if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) { + drgui_text_engine_deselect_all(pTB->pTL); + } + + drgui_text_engine_move_cursor_up(pTB->pTL); + + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_leave_selection_mode(pTB->pTL); + } + } break; + + case DRGUI_ARROW_DOWN: + { + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_enter_selection_mode(pTB->pTL); + } + + if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) { + drgui_text_engine_deselect_all(pTB->pTL); + } + + drgui_text_engine_move_cursor_down(pTB->pTL); + + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_leave_selection_mode(pTB->pTL); + } + } break; + + + case DRGUI_END: + { + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_enter_selection_mode(pTB->pTL); + } + + if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) { + drgui_text_engine_deselect_all(pTB->pTL); + } + + if ((stateFlags & DRGUI_KEY_STATE_CTRL_DOWN) != 0) { + drgui_text_engine_move_cursor_to_end_of_text(pTB->pTL); + } else { + drgui_text_engine_move_cursor_to_end_of_line(pTB->pTL); + } + + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_leave_selection_mode(pTB->pTL); + } + } break; + + case DRGUI_HOME: + { + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_enter_selection_mode(pTB->pTL); + } + + if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) { + drgui_text_engine_deselect_all(pTB->pTL); + } + + if ((stateFlags & DRGUI_KEY_STATE_CTRL_DOWN) != 0) { + drgui_text_engine_move_cursor_to_start_of_text(pTB->pTL); + } else { + drgui_text_engine_move_cursor_to_start_of_line(pTB->pTL); + } + + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_leave_selection_mode(pTB->pTL); + } + } break; + + case DRGUI_PAGE_UP: + { + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_enter_selection_mode(pTB->pTL); + } + + if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) { + drgui_text_engine_deselect_all(pTB->pTL); + } + + int scrollOffset = drgui_sb_get_page_size(pTB->pVertScrollbar); + if ((stateFlags & DRGUI_KEY_STATE_CTRL_DOWN) == 0) { + drgui_sb_scroll(pTB->pVertScrollbar, -scrollOffset); + } + + drgui_text_engine_move_cursor_y(pTB->pTL, -drgui_sb_get_page_size(pTB->pVertScrollbar)); + + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_leave_selection_mode(pTB->pTL); + } + } break; + + case DRGUI_PAGE_DOWN: + { + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_enter_selection_mode(pTB->pTL); + } + + if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) { + drgui_text_engine_deselect_all(pTB->pTL); + } + + int scrollOffset = drgui_sb_get_page_size(pTB->pVertScrollbar); + if (scrollOffset > (int)(drgui_text_engine_get_line_count(pTB->pTL) - drgui_text_engine_get_cursor_line(pTB->pTL))) { + scrollOffset = 0; + } + + if ((stateFlags & DRGUI_KEY_STATE_CTRL_DOWN) == 0) { + drgui_sb_scroll(pTB->pVertScrollbar, scrollOffset); + } + + drgui_text_engine_move_cursor_y(pTB->pTL, drgui_sb_get_page_size(pTB->pVertScrollbar)); + + if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) { + drgui_text_engine_leave_selection_mode(pTB->pTL); + } + } break; + + default: break; + } +} + +void drgui_textbox_on_key_up(drgui_element* pTBElement, drgui_key key, int stateFlags) +{ + (void)pTBElement; + (void)key; + (void)stateFlags; +} + +void drgui_textbox_on_printable_key_down(drgui_element* pTBElement, unsigned int utf32, int stateFlags) +{ + (void)stateFlags; + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_prepare_undo_point(pTB->pTL); + { + if (drgui_text_engine_is_anything_selected(pTB->pTL)) { + drgui_text_engine_delete_selected_text(pTB->pTL); + } + + drgui_text_engine_insert_character_at_cursor(pTB->pTL, utf32); + } + drgui_text_engine_commit_undo_point(pTB->pTL); +} + + +DRGUI_PRIVATE void drgui_textbox__on_text_engine_paint_rect(drgui_text_engine* pTL, drgui_rect rect, drgui_color color, drgui_element* pTBElement, void* pPaintData) +{ + (void)pTL; + + float offsetX; + float offsetY; + drgui_textbox__get_text_offset(pTBElement, &offsetX, &offsetY); + + drgui_draw_rect(pTBElement, drgui_offset_rect(rect, offsetX, offsetY), color, pPaintData); +} + +DRGUI_PRIVATE void drgui_textbox__on_text_engine_paint_text(drgui_text_engine* pTL, drgui_text_run* pRun, drgui_element* pTBElement, void* pPaintData) +{ + (void)pTL; + + float offsetX; + float offsetY; + drgui_textbox__get_text_offset(pTBElement, &offsetX, &offsetY); + + drgui_draw_text(pTBElement, pRun->pFont, pRun->text, (int)pRun->textLength, (float)pRun->posX + offsetX, (float)pRun->posY + offsetY, pRun->textColor, pRun->backgroundColor, pPaintData); +} + +DRGUI_PRIVATE void drgui_textbox__on_text_engine_dirty(drgui_text_engine* pTL, drgui_rect rect) +{ + drgui_element* pTBElement = *(drgui_element**)drgui_text_engine_get_extra_data(pTL); + if (pTBElement == NULL) { + return; + } + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + float offsetX; + float offsetY; + drgui_textbox__get_text_offset(pTBElement, &offsetX, &offsetY); + + drgui_dirty(pTBElement, drgui_offset_rect(rect, offsetX, offsetY)); +} + +DRGUI_PRIVATE void drgui_textbox__on_text_engine_cursor_move(drgui_text_engine* pTL) +{ + // If the cursor is off the edge of the container we want to scroll it into position. + drgui_element* pTBElement = *(drgui_element**)drgui_text_engine_get_extra_data(pTL); + if (pTBElement == NULL) { + return; + } + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + // If the cursor is above or below the container, we need to scroll vertically. + int iLine = (int)drgui_text_engine_get_cursor_line(pTB->pTL); + if (iLine < drgui_sb_get_scroll_position(pTB->pVertScrollbar)) { + drgui_sb_scroll_to(pTB->pVertScrollbar, iLine); + } + + int iBottomLine = drgui_sb_get_scroll_position(pTB->pVertScrollbar) + drgui_sb_get_page_size(pTB->pVertScrollbar) - 1; + if (iLine >= iBottomLine) { + drgui_sb_scroll_to(pTB->pVertScrollbar, iLine - (drgui_sb_get_page_size(pTB->pVertScrollbar) - 1) + 1); + } + + + // If the cursor is to the left or right of the container we need to scroll horizontally. + float cursorPosX; + float cursorPosY; + drgui_text_engine_get_cursor_position(pTB->pTL, &cursorPosX, &cursorPosY); + + if (cursorPosX < 0) { + drgui_sb_scroll_to(pTB->pHorzScrollbar, (int)(cursorPosX - drgui_text_engine_get_inner_offset_x(pTB->pTL)) - 100); + } + if (cursorPosX >= drgui_text_engine_get_container_width(pTB->pTL)) { + drgui_sb_scroll_to(pTB->pHorzScrollbar, (int)(cursorPosX - drgui_text_engine_get_inner_offset_x(pTB->pTL) - drgui_text_engine_get_container_width(pTB->pTL)) + 100); + } + + + if (pTB->onCursorMove) { + pTB->onCursorMove(pTBElement); + } +} + +DRGUI_PRIVATE void drgui_textbox__on_text_engine_text_changed(drgui_text_engine* pTL) +{ + drgui_element* pTBElement = *(drgui_element**)drgui_text_engine_get_extra_data(pTL); + if (pTBElement == NULL) { + return; + } + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + // Scrollbars need to be refreshed whenever text is changed. + drgui_textbox__refresh_scrollbars(pTBElement); + + // The line numbers need to be redrawn. + // TODO: This can probably be optimized a bit so that it is only redrawn if a line was inserted or deleted. + drgui_dirty(pTB->pLineNumbers, drgui_get_local_rect(pTB->pLineNumbers)); +} + +DRGUI_PRIVATE void drgui_textbox__on_text_engine_undo_point_changed(drgui_text_engine* pTL, unsigned int iUndoPoint) +{ + drgui_element* pTBElement = *(drgui_element**)drgui_text_engine_get_extra_data(pTL); + if (pTBElement == NULL) { + return; + } + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + if (pTB->onUndoPointChanged) { + pTB->onUndoPointChanged(pTBElement, iUndoPoint); + } +} + + +void drgui_textbox_on_paint(drgui_element* pTBElement, drgui_rect relativeRect, void* pPaintData) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_rect textRect = drgui_textbox__get_text_rect(pTBElement); + + // The dead space between the scrollbars should always be drawn with the default background color. + drgui_draw_rect(pTBElement, drgui_textbox__get_scrollbar_dead_space_rect(pTBElement), drgui_text_engine_get_default_bg_color(pTB->pTL), pPaintData); + + // Border. + drgui_rect borderRect = drgui_get_local_rect(pTBElement); + drgui_draw_rect_outline(pTBElement, borderRect, pTB->borderColor, pTB->borderWidth, pPaintData); + + // Padding. + drgui_rect paddingRect = drgui_grow_rect(textRect, pTB->padding); + drgui_draw_rect_outline(pTBElement, paddingRect, drgui_text_engine_get_default_bg_color(pTB->pTL), pTB->padding, pPaintData); + + // Text. + drgui_set_clip(pTBElement, drgui_clamp_rect(textRect, relativeRect), pPaintData); + drgui_text_engine_paint(pTB->pTL, drgui_offset_rect(drgui_clamp_rect(textRect, relativeRect), -textRect.left, -textRect.top), pTBElement, pPaintData); +} + +void drgui_textbox_on_capture_keyboard(drgui_element* pTBElement, drgui_element* pPrevCapturedElement) +{ + (void)pPrevCapturedElement; + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_show_cursor(pTB->pTL); +} + +void drgui_textbox_on_release_keyboard(drgui_element* pTBElement, drgui_element* pNewCapturedElement) +{ + (void)pNewCapturedElement; + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_hide_cursor(pTB->pTL); +} + +void drgui_textbox_on_capture_mouse(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + +} + +void drgui_textbox_on_release_mouse(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return; + } + + drgui_text_engine_leave_selection_mode(pTB->pTL); +} + + + +DRGUI_PRIVATE void drgui_textbox__get_text_offset(drgui_element* pTBElement, float* pOffsetXOut, float* pOffsetYOut) +{ + float offsetX = 0; + float offsetY = 0; + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB != NULL) + { + float lineNumbersWidth = 0; + if (drgui_is_visible(pTB->pLineNumbers)) { + lineNumbersWidth = drgui_get_width(pTB->pLineNumbers); + } + + offsetX = pTB->borderWidth + pTB->padding + lineNumbersWidth; + offsetY = pTB->borderWidth + pTB->padding; + } + + + if (pOffsetXOut != NULL) { + *pOffsetXOut = offsetX; + } + if (pOffsetYOut != NULL) { + *pOffsetYOut = offsetY; + } +} + +DRGUI_PRIVATE void drgui_textbox__calculate_text_engine_container_size(drgui_element* pTBElement, float* pWidthOut, float* pHeightOut) +{ + float width = 0; + float height = 0; + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB != NULL) + { + float horzScrollbarSize = 0; + if (drgui_is_visible(pTB->pHorzScrollbar)) { + horzScrollbarSize = drgui_get_height(pTB->pHorzScrollbar); + } + + float vertScrollbarSize = 0; + if (drgui_is_visible(pTB->pVertScrollbar)) { + vertScrollbarSize = drgui_get_width(pTB->pVertScrollbar); + } + + float lineNumbersWidth = 0; + if (drgui_is_visible(pTB->pLineNumbers)) { + lineNumbersWidth = drgui_get_width(pTB->pLineNumbers); + } + + width = drgui_get_width(pTBElement) - (pTB->borderWidth + pTB->padding)*2 - vertScrollbarSize - lineNumbersWidth; + height = drgui_get_height(pTBElement) - (pTB->borderWidth + pTB->padding)*2 - horzScrollbarSize; + } + + if (pWidthOut != NULL) { + *pWidthOut = width; + } + if (pHeightOut != NULL) { + *pHeightOut = height; + } +} + +DRGUI_PRIVATE drgui_rect drgui_textbox__get_text_rect(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + if (pTB == NULL) { + return drgui_make_rect(0, 0, 0, 0); + } + + float offsetX; + float offsetY; + drgui_textbox__get_text_offset(pTBElement, &offsetX, &offsetY); + + float width; + float height; + drgui_textbox__calculate_text_engine_container_size(pTBElement, &width, &height); + + return drgui_make_rect(offsetX, offsetY, offsetX + width, offsetY + height); +} + + +DRGUI_PRIVATE void drgui_textbox__refresh_scrollbars(drgui_element* pTBElement) +{ + // The layout depends on the range because we may be dynamically hiding and showing the scrollbars depending on the range. Thus, we + // refresh the range first. However, dynamically showing and hiding the scrollbars (which is done when the layout is refreshed) affects + // the size of the text box, which in turn affects the range. Thus, we need to refresh the ranges a second time after the layouts. + + drgui_textbox__refresh_scrollbar_ranges(pTBElement); + drgui_textbox__refresh_scrollbar_layouts(pTBElement); + drgui_textbox__refresh_scrollbar_ranges(pTBElement); +} + +DRGUI_PRIVATE void drgui_textbox__refresh_scrollbar_ranges(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + // The vertical scrollbar is based on the line count. + size_t lineCount = drgui_text_engine_get_line_count(pTB->pTL); + size_t pageSize = drgui_text_engine_get_visible_line_count_starting_at(pTB->pTL, drgui_sb_get_scroll_position(pTB->pVertScrollbar)); + drgui_sb_set_range_and_page_size(pTB->pVertScrollbar, 0, (int)(lineCount + pageSize - 1 - 1), (int)pageSize); // -1 to make the range 0 based. -1 to ensure at least one line is visible. + + if (drgui_sb_is_thumb_visible(pTB->pVertScrollbar)) { + if (!drgui_is_visible(pTB->pVertScrollbar)) { + drgui_show(pTB->pVertScrollbar); + } + } else { + if (drgui_is_visible(pTB->pVertScrollbar)) { + drgui_hide(pTB->pVertScrollbar); + } + } + + + // The horizontal scrollbar is a per-pixel scrollbar, and is based on the width of the text versus the width of the container. + drgui_rect textRect = drgui_text_engine_get_text_rect_relative_to_bounds(pTB->pTL); + float containerWidth; + drgui_text_engine_get_container_size(pTB->pTL, &containerWidth, NULL); + drgui_sb_set_range_and_page_size(pTB->pHorzScrollbar, 0, (int)(textRect.right - textRect.left + (containerWidth/2)), (int)containerWidth); + + if (drgui_sb_is_thumb_visible(pTB->pHorzScrollbar)) { + if (!drgui_is_visible(pTB->pHorzScrollbar)) { + drgui_show(pTB->pHorzScrollbar); + drgui_textbox__refresh_line_numbers(pTBElement); + } + } else { + if (drgui_is_visible(pTB->pHorzScrollbar)) { + drgui_hide(pTB->pHorzScrollbar); + drgui_textbox__refresh_line_numbers(pTBElement); + } + } +} + +DRGUI_PRIVATE void drgui_textbox__refresh_scrollbar_layouts(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + float offsetLeft = pTB->borderWidth; + float offsetTop = pTB->borderWidth; + float offsetRight = pTB->borderWidth; + float offsetBottom = pTB->borderWidth; + + float scrollbarSizeH = (drgui_sb_is_thumb_visible(pTB->pHorzScrollbar) && pTB->isHorzScrollbarEnabled) ? pTB->horzScrollbarSize : 0; + float scrollbarSizeV = (drgui_sb_is_thumb_visible(pTB->pVertScrollbar) && pTB->isVertScrollbarEnabled) ? pTB->vertScrollbarSize : 0; + + drgui_set_size(pTB->pVertScrollbar, scrollbarSizeV, drgui_get_height(pTBElement) - scrollbarSizeH - (offsetTop + offsetBottom)); + drgui_set_size(pTB->pHorzScrollbar, drgui_get_width(pTBElement) - scrollbarSizeV - (offsetLeft + offsetRight), scrollbarSizeH); + + drgui_set_relative_position(pTB->pVertScrollbar, drgui_get_width(pTBElement) - scrollbarSizeV - offsetRight, offsetTop); + drgui_set_relative_position(pTB->pHorzScrollbar, offsetLeft, drgui_get_height(pTBElement) - scrollbarSizeH - offsetBottom); + + + // A change in the layout of the horizontal scrollbar will affect the layout of the line numbers. + drgui_textbox__refresh_line_numbers(pTBElement); +} + +DRGUI_PRIVATE drgui_rect drgui_textbox__get_scrollbar_dead_space_rect(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + float offsetLeft = pTB->borderWidth; + float offsetTop = pTB->borderWidth; + float offsetRight = pTB->borderWidth; + float offsetBottom = pTB->borderWidth; + + float scrollbarSizeH = (drgui_is_visible(pTB->pHorzScrollbar) && pTB->isHorzScrollbarEnabled) ? drgui_get_width(pTB->pHorzScrollbar) : 0; + float scrollbarSizeV = (drgui_is_visible(pTB->pVertScrollbar) && pTB->isHorzScrollbarEnabled) ? drgui_get_height(pTB->pVertScrollbar) : 0; + + if (scrollbarSizeH == 0 && scrollbarSizeV == 0) { + return drgui_make_rect(0, 0, 0, 0); + } + + return drgui_make_rect(scrollbarSizeH + offsetLeft, scrollbarSizeV + offsetTop, drgui_get_width(pTBElement) - offsetRight, drgui_get_height(pTBElement) - offsetBottom); +} + + +DRGUI_PRIVATE void drgui_textbox__on_mouse_move_line_numbers(drgui_element* pLineNumbers, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)relativeMousePosX; + + drgui_element* pTBElement = *(drgui_element**)drgui_get_extra_data(pLineNumbers); + assert(pTBElement != NULL); + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + if ((stateFlags & DRGUI_MOUSE_BUTTON_LEFT_DOWN) != 0) + { + if (drgui_get_element_with_mouse_capture(pLineNumbers->pContext) == pLineNumbers) + { + // We just move the cursor around based on the line number we've moved over. + drgui_text_engine_enter_selection_mode(pTB->pTL); + { + //float offsetX = pTextEditorData->padding; + float offsetY = pTB->padding; + size_t iLine = drgui_text_engine_get_line_at_pos_y(pTB->pTL, relativeMousePosY - offsetY); + size_t iAnchorLine = pTB->iLineSelectAnchor; + size_t lineCount = drgui_text_engine_get_line_count(pTB->pTL); + + size_t iSelectionFirstLine = drgui_text_engine_get_selection_first_line(pTB->pTL); + size_t iSelectionLastLine = drgui_text_engine_get_selection_last_line(pTB->pTL); + if (iSelectionLastLine != iSelectionFirstLine) { + iSelectionLastLine -= 1; + } + + // If we're moving updwards we want to position the cursor at the start of the line. Otherwise we want to move the cursor to the start + // of the next line, or the end of the text. + bool movingUp = false; + if (iLine < iAnchorLine) { + movingUp = true; + } + + // If we're moving up the selection anchor needs to be placed at the end of the last line. Otherwise we need to move it to the start + // of the first line. + if (movingUp) { + if (iAnchorLine + 1 < lineCount) { + drgui_text_engine_move_selection_anchor_to_start_of_line(pTB->pTL, iAnchorLine + 1); + } else { + drgui_text_engine_move_selection_anchor_to_end_of_line(pTB->pTL, iAnchorLine); + } + } else { + drgui_text_engine_move_selection_anchor_to_start_of_line(pTB->pTL, iAnchorLine); + } + + + // If we're moving up we want the cursor to be placed at the start of the selection range. Otherwise we want to place the cursor + // at the end of the selection range. + if (movingUp) { + drgui_text_engine_move_cursor_to_start_of_line_by_index(pTB->pTL, iLine); + } else { + if (iLine + 1 < lineCount) { + drgui_text_engine_move_cursor_to_start_of_line_by_index(pTB->pTL, iLine + 1); + } else { + drgui_text_engine_move_cursor_to_end_of_line_by_index(pTB->pTL, iLine); + } + } + } + drgui_text_engine_leave_selection_mode(pTB->pTL); + } + } +} + +DRGUI_PRIVATE void drgui_textbox__on_mouse_button_down_line_numbers(drgui_element* pLineNumbers, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)relativeMousePosX; + (void)stateFlags; + + drgui_element* pTBElement = *(drgui_element**)drgui_get_extra_data(pLineNumbers); + assert(pTBElement != NULL); + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + if (mouseButton == DRGUI_MOUSE_BUTTON_LEFT) + { + //float offsetX = pTextEditorData->padding; + float offsetY = pTB->padding; + pTB->iLineSelectAnchor = drgui_text_engine_get_line_at_pos_y(pTB->pTL, relativeMousePosY - offsetY); + + drgui_text_engine_deselect_all(pTB->pTL); + + drgui_text_engine_move_cursor_to_start_of_line_by_index(pTB->pTL, pTB->iLineSelectAnchor); + + drgui_text_engine_enter_selection_mode(pTB->pTL); + { + if (pTB->iLineSelectAnchor + 1 < drgui_text_engine_get_line_count(pTB->pTL)) { + drgui_text_engine_move_cursor_to_start_of_line_by_index(pTB->pTL, pTB->iLineSelectAnchor + 1); + } else { + drgui_text_engine_move_cursor_to_end_of_line(pTB->pTL); + } + } + drgui_text_engine_leave_selection_mode(pTB->pTL); + + drgui_capture_mouse(pLineNumbers); + } +} + +DRGUI_PRIVATE void drgui_textbox__on_mouse_button_up_line_numbers(drgui_element* pLineNumbers, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)relativeMousePosX; + (void)relativeMousePosY; + (void)stateFlags; + + if (mouseButton == DRGUI_MOUSE_BUTTON_LEFT) { + if (drgui_has_mouse_capture(pLineNumbers)) { + drgui_release_mouse(pLineNumbers->pContext); + } + } +} + +DRGUI_PRIVATE void drgui_textbox__on_paint_rect_line_numbers(drgui_text_engine* pLayout, drgui_rect rect, drgui_color color, drgui_element* pTBElement, void* pPaintData) +{ + (void)pLayout; + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + float offsetX = pTB->padding; + float offsetY = pTB->padding; + + drgui_draw_rect(pTB->pLineNumbers, drgui_offset_rect(rect, offsetX, offsetY), color, pPaintData); +} + +DRGUI_PRIVATE void drgui_textbox__on_paint_text_line_numbers(drgui_text_engine* pLayout, drgui_text_run* pRun, drgui_element* pTBElement, void* pPaintData) +{ + (void)pLayout; + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + float offsetX = pTB->padding; + float offsetY = pTB->padding; + drgui_draw_text(pTB->pLineNumbers, pRun->pFont, pRun->text, (int)pRun->textLength, (float)pRun->posX + offsetX, (float)pRun->posY + offsetY, pRun->textColor, pRun->backgroundColor, pPaintData); +} + +DRGUI_PRIVATE void drgui_textbox__on_paint_line_numbers(drgui_element* pLineNumbers, drgui_rect relativeRect, void* pPaintData) +{ + (void)relativeRect; + + drgui_element* pTBElement = *((drgui_element**)drgui_get_extra_data(pLineNumbers)); + assert(pTBElement != NULL); + + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + float lineNumbersWidth = drgui_get_width(pLineNumbers) - (pTB->padding*2) - pTB->lineNumbersPaddingRight; + float lineNumbersHeight = drgui_get_height(pLineNumbers) - (pTB->padding*2); + + drgui_text_engine_paint_line_numbers(pTB->pTL, lineNumbersWidth, lineNumbersHeight, pTB->lineNumbersColor, pTB->lineNumbersBackgroundColor, drgui_textbox__on_paint_text_line_numbers, drgui_textbox__on_paint_rect_line_numbers, pTBElement, pPaintData); + + drgui_draw_rect_outline(pLineNumbers, drgui_get_local_rect(pLineNumbers), pTB->lineNumbersBackgroundColor, pTB->padding, pPaintData); + + // Right padding. + drgui_rect rightPaddingRect = drgui_get_local_rect(pLineNumbers); + rightPaddingRect.right -= pTB->padding; + rightPaddingRect.left = rightPaddingRect.right - pTB->lineNumbersPaddingRight; + drgui_draw_rect(pLineNumbers, rightPaddingRect, pTB->lineNumbersBackgroundColor, pPaintData); +} + +DRGUI_PRIVATE void drgui_textbox__refresh_line_numbers(drgui_element* pTBElement) +{ + drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement); + assert(pTB != NULL); + + float lineNumbersWidth = 0; + if (drgui_is_visible(pTB->pLineNumbers)) { + lineNumbersWidth = pTB->lineNumbersWidth; + } + + float scrollbarHeight = drgui_is_visible(pTB->pHorzScrollbar) ? drgui_get_height(pTB->pHorzScrollbar) : 0; + drgui_set_size(pTB->pLineNumbers, lineNumbersWidth, drgui_get_height(pTBElement) - scrollbarHeight); + + + // The size of the text container may have changed. + float textEditorWidth; + float textEditorHeight; + drgui_textbox__calculate_text_engine_container_size(pTBElement, &textEditorWidth, &textEditorHeight); + drgui_text_engine_set_container_size(pTB->pTL, textEditorWidth, textEditorHeight); + + + // Force a redraw just to be sure everything is in a valid state. + drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement)); +} +#endif //DR_GUI_IMPLEMENTATION +#endif //DRGUI_NO_TEXT_EDITING + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// Tree View +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +// QUICK NOTES +// +// Tree-View Controls +// - A tree-view control is a complex control with a hierarchy of items. They are typically used for file explorers. + +#ifndef drgui_tree_view_h +#define drgui_tree_view_h + +#ifdef __cplusplus +extern "C" { +#endif + +#define EG_MAX_TREE_VIEW_ITEM_TEXT_LENGTH 256 + +typedef struct drgui_tree_view_item drgui_tree_view_item; + +typedef void (* drgui_tvi_on_mouse_move_proc) (drgui_tree_view_item* pItem, int relativeMousePosX, int relativeMousePosY, bool* pIsOverArrow); +typedef void (* drgui_tvi_on_mouse_leave_proc) (drgui_tree_view_item* pItem); +typedef void (* drgui_tvi_on_paint_proc) (drgui_element* pTVElement, drgui_tree_view_item* pItem, drgui_rect relativeClippingRect, drgui_color backgroundColor, float offsetX, float offsetY, float width, float height, void* pPaintData); +typedef void (* drgui_tvi_measure_proc) (drgui_tree_view_item* pItem, float* pWidthOut, float* pHeightOut); +typedef void (* drgui_tvi_on_picked_proc) (drgui_tree_view_item* pItem); + +/////////////////////////////////////////////////////////////////////////////// +// +// Tree-View +// +/////////////////////////////////////////////////////////////////////////////// + +/// Creates a tree-view control. +drgui_element* drgui_create_tree_view(drgui_context* pContext, drgui_element* pParent, size_t extraDataSize, const void* pExtraData); + +/// Deletes the given tree-view control. +void drgui_delete_tree_view(drgui_element* pTVElement); + + +/// Retrieves the size of the extra data associated with the given tree-view control. +size_t drgui_tv_get_extra_data_size(drgui_element* pTVElement); + +/// Retrieves a pointer to the buffer containing the given tree-view's extra data. +void* drgui_tv_get_extra_data(drgui_element* pTVElement); + +/// Retrieves a pointer to the root element of the given tree view control. +drgui_tree_view_item* drgui_tv_get_root_item(drgui_element* pTVElement); + +/// Retrieves a pointer to the vertical scrollbar. +drgui_element* drgui_tv_get_vertical_scrollbar(drgui_element* pTVElement); + +/// Retrieves a pointer to the horizontal scrollbar. +drgui_element* drgui_tv_get_horizontal_scrollbar(drgui_element* pTVElement); + + +/// Sets the default background color. +void drgui_tv_set_default_background_color(drgui_element* pTVElement, drgui_color color); + +/// Retrieves the default background color. +drgui_color drgui_tv_get_default_background_color(drgui_element* pTVElement); + +/// Sets the default background color of hovered items. +void drgui_tv_set_hovered_background_color(drgui_element* pTVElement, drgui_color color); + +/// Retrieves the default background color of hovered items. +drgui_color drgui_tv_get_hovered_background_color(drgui_element* pTVElement); + +/// Sets the default background color of selected items. +void drgui_tv_set_selected_background_color(drgui_element* pTVElement, drgui_color color); + +/// Retrieves the default background color of selected items. +drgui_color drgui_tv_get_selected_background_color(drgui_element* pTVElement); + +/// Sets the amount of indentation to apply to each child item in the given tree-view. +void drgui_tv_set_child_offset_x(drgui_element* pTVElement, float childOffsetX); + +/// Retrieves the amount of indentation to apply to each child item in the given tree-view. +float drgui_tv_get_child_offset_x(drgui_element* pTVElement); + + +/// Measures the given item. +bool drgui_tv_measure_item(drgui_element* pTVElement, drgui_tree_view_item* pItem, float* pWidthOut, float* pHeightOut); + +/// Deselects every tree-view item. +void drgui_tv_deselect_all_items(drgui_element* pTVElement); + +/// Enables multi-select. +/// +/// @remarks +/// While this is enabled, selections will accumulate. Typically you would call this when the user hits +/// the CTRL key, and then call drgui_tv_disable_multi_select() when the user releases it. +void drgui_tv_enable_multi_select(drgui_element* pTVElement); + +/// Disables multi-select. +void drgui_tv_disable_multi_select(drgui_element* pTVElement); + +/// Determines whether or not multi-select is enabled. +bool drgui_tv_is_multi_select_enabled(drgui_element* pTVElement); + + +/// Retrieves the first selected item. +/// +/// @remarks +/// This runs in linear time. +drgui_tree_view_item* drgui_tv_get_first_selected_item(drgui_element* pTVElement); + +/// Retrieves the next select item, not including the given item. +/// +/// @remarks +/// Use this in conjunction with drgui_tv_get_first_selected_item() to iterate over each selected item. +/// @par +/// The order in which retrieving selected items is based on their location in the hierarchy, and not the +/// order in which they were selected. +drgui_tree_view_item* drgui_tv_get_next_selected_item(drgui_element* pTVElement, drgui_tree_view_item* pItem); + + +/// Sets the function to call when the mouse is moved while over a tree-view item. +void drgui_tv_set_on_item_mouse_move(drgui_element* pTVElement, drgui_tvi_on_mouse_move_proc proc); + +/// Sets the function call when the mouse leaves a tree-view item. +void drgui_tv_set_on_item_mouse_leave(drgui_element* pTVElement, drgui_tvi_on_mouse_leave_proc proc); + +/// Sets the function to call when a tree-view item needs to be drawn. +void drgui_tv_set_on_item_paint(drgui_element* pTVElement, drgui_tvi_on_paint_proc proc); + +/// Sets the function to call when a tree-view item needs to be measured. +void drgui_tv_set_on_item_measure(drgui_element* pTVElement, drgui_tvi_measure_proc proc); + +/// Sets the function to call when a tree-view item is picked. +/// +/// @remarks +/// An item is "picked" when it is a leaf item (has no children) and is double-clicked. +void drgui_tv_set_on_item_picked(drgui_element* pTVElement, drgui_tvi_on_picked_proc proc); + + +/// Called when the size event needs to be processed for the given tree-view control. +void drgui_tv_on_size(drgui_element* pTVElement, float newWidth, float newHeight); + +/// Called when the mouse leave event needs to be processed for the given tree-view control. +void drgui_tv_on_mouse_leave(drgui_element* pTVElement); + +/// Called when the mouse move event needs to be processed for the given tree-view control. +void drgui_tv_on_mouse_move(drgui_element* pTVElement, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when the mouse button down event needs to be processed for the given tree-view control. +void drgui_tv_on_mouse_button_down(drgui_element* pTVElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when the mouse button up event needs to be processed for the given tree-view control. +void drgui_tv_on_mouse_button_up(drgui_element* pTVElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when the mouse button double-click event needs to be processed for the given tree-view control. +void drgui_tv_on_mouse_button_dblclick(drgui_element* pTVElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when the mouse wheel event needs to be processed for the given tree-view control. +void drgui_tv_on_mouse_wheel(drgui_element* pTVElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags); + +/// Called when the paint event needs to be processed for the given tree-view control. +void drgui_tv_on_paint(drgui_element* pTVElement, drgui_rect relativeClippingRect, void* pPaintData); + + +/////////////////////////////////////////////////////////////////////////////// +// +// Tree-View Item +// +/////////////////////////////////////////////////////////////////////////////// + +/// Creates a tree view item. +/// +/// @remarks +/// When pParent is non-null, the tree-view control must match that of the tree-view control that owns the +/// parent item. +drgui_tree_view_item* drgui_tv_create_item(drgui_element* pTVElement, drgui_tree_view_item* pParent, size_t extraDataSize, const void* pExtraData); + +/// Recursively deletes a tree view item. +void drgui_tvi_delete(drgui_tree_view_item* pItem); + +/// Retrieves the tree-view GUI element that owns the given item. +drgui_element* drgui_tvi_get_tree_view_element(drgui_tree_view_item* pItem); + +/// Retrieves the size of the extra data associated with the given tree-view item. +size_t drgui_tvi_get_extra_data_size(drgui_tree_view_item* pItem); + +/// Retrieves a pointer to the extra data associated with the given tree-view item. +void* drgui_tvi_get_extra_data(drgui_tree_view_item* pItem); + + +/// Retrieves the parent tree-view item. +drgui_tree_view_item* drgui_tvi_get_parent(drgui_tree_view_item* pItem); + +/// Retrieves a pointer to the first child of the given tree-view item. +drgui_tree_view_item* drgui_tvi_get_first_child(drgui_tree_view_item* pItem); + +/// Retrieves a pointer to the last child of the given tree-view item. +drgui_tree_view_item* drgui_tvi_get_last_child(drgui_tree_view_item* pItem); + +/// Retrieves a pointer to the next sibling of the given tree-view item. +drgui_tree_view_item* drgui_tvi_get_next_sibling(drgui_tree_view_item* pItem); + +/// Retrieves a pointer to the previous sibling of the given tree-view item. +drgui_tree_view_item* drgui_tvi_get_prev_sibling(drgui_tree_view_item* pItem); + +/// Appends a tree view item as a child of the given parent item. +void drgui_tvi_append(drgui_tree_view_item* pItem, drgui_tree_view_item* pParent); + +/// Prepends a tree view item as a child of the given parent item. +void drgui_tvi_prepend(drgui_tree_view_item* pItem, drgui_tree_view_item* pParent); + +/// Appends the given tree view item to the given sibling. +void drgui_tvi_append_sibling(drgui_tree_view_item* pItemToAppend, drgui_tree_view_item* pItemToAppendTo); + +/// Prepends the given tree view item to the given sibling. +void drgui_tvi_prepend_sibling(drgui_tree_view_item* pItemToPrepend, drgui_tree_view_item* pItemToPrependTo); + + +/// Determines whether or not the given item has any children. +bool drgui_tvi_has_children(drgui_tree_view_item* pItem); + +/// Retrieves the depth of the item. +/// +/// @remarks +/// This is a recursive call and runs in linear time. +int drgui_tvi_get_depth(drgui_tree_view_item* pItem); + +/// Retrieves a pointer to the next visible item in the hierarchy that is not a child. +/// +/// @remarks +/// This is used for iterating. +/// @par +/// is an input and output parameter that is decremented whenver the next item is an ancestor. +drgui_tree_view_item* drgui_tvi_next_visible_non_child(drgui_tree_view_item* pItem, int* pDepthInOut); + + +/// Selects the given item. +void drgui_tvi_select(drgui_tree_view_item* pItem); + +/// Deselects the given item. +void drgui_tvi_deselect(drgui_tree_view_item* pItem); + +/// Determines whether or not the given tree view item is selected. +bool drgui_tvi_is_selected(drgui_tree_view_item* pItem); + +/// Expands the given item. +void drgui_tvi_expand(drgui_tree_view_item* pItem); + +/// Collapses the given item. +void drgui_tvi_collapse(drgui_tree_view_item* pItem); + +/// Determines whether or not the given item is expanded. +bool drgui_tvi_is_expanded(drgui_tree_view_item* pItem); + + + +#ifdef __cplusplus +} +#endif +#endif //drgui_tree_view_h + + +#ifdef DR_GUI_IMPLEMENTATION +typedef struct drgui_tree_view drgui_tree_view; + +struct drgui_tree_view +{ + /// The root tree-view item. + drgui_tree_view_item* pRootItem; + + /// The vertical scrollbar. + drgui_element* pScrollbarV; + + /// The horizontal scrollbar. + drgui_element* pScrollbarH; + + + /// The default background color. + drgui_color defaultBGColor; + + /// The hovered background color. + drgui_color hoveredBGColor; + + /// The selected background color. + drgui_color selectedBGColor; + + /// The amount of indentation to apply to each child item. + float childOffsetX; + + + /// The function to call when an item needs to handle a mouse movement event. + drgui_tvi_on_mouse_move_proc onItemMouseMove; + + /// The function to call when an item needs to handle a mouse leave event. + drgui_tvi_on_mouse_leave_proc onItemMouseLeave; + + /// The function to call when an item needs to be drawn. + drgui_tvi_on_paint_proc onItemPaint; + + /// The function to call when an item needs to be measured. + drgui_tvi_measure_proc onItemMeasure; + + /// The function to call when an item is picked. + drgui_tvi_on_picked_proc onItemPicked; + + + /// A pointer to the item the mouse is current hovered over. + drgui_tree_view_item* pHoveredItem; + + /// Whether or not the mouse is hovered over the arrow of pHoveredItem. + bool isMouseOverArrow; + + /// Whether or not the mouse is over the given element. + bool isMouseOver; + + /// The relative position of the mouse on the x axis. This is updated whenever the mouse_move event is received. + int relativeMousePosX; + + /// The relative position of the mouse on the y axis. This is updated whenever the mouse_move event is received. + int relativeMousePosY; + + + /// Whether or not multi-select is enabled. + bool isMultiSelectEnabled; + + /// Whether or not range-select is enabled. + bool isRangeSelectEnabled; + + + /// The size of the extra data. + size_t extraDataSize; + + /// A pointer to the extra data buffer. + char pExtraData[1]; +}; + +struct drgui_tree_view_item +{ + /// The tree-view control that owns this item. + drgui_element* pTVElement; + + + /// A pointer to the parent item. + drgui_tree_view_item* pParent; + + /// A pointer to the first child. + drgui_tree_view_item* pFirstChild; + + /// A pointer to the last child. + drgui_tree_view_item* pLastChild; + + /// A pointer to the next sibling. + drgui_tree_view_item* pNextSibling; + + /// A pointer to the prev sibling. + drgui_tree_view_item* pPrevSibling; + + + /// Whether or not the item is select. + bool isSelected; + + /// Whether or not the item is expanded. + bool isExpanded; + + + /// The size of the extra data. + size_t extraDataSize; + + /// A pointer to the extra data buffer. + char pExtraData[1]; +}; + +typedef struct +{ + /// A pointer to the relevant item. + drgui_tree_view_item* pItem; + + /// The width of the item. + float width; + + /// The height of the item. + float height; + + /// The position of the item on the x axis. + float posX; + + /// Top position of the item on the y axis. + float posY; + + /// The depth of the item. This is used to calculate the offset of the item. + int depth; + +} drgui_tree_view_iterator; + +typedef struct +{ + /// The width of the item. + float width; + + /// The height of the item. + float height; + + /// The position of the item on the x axis. + float posX; + + /// Top position of the item on the y axis. + float posY; + +} drgui_tree_view_item_metrics; + +typedef struct +{ + /// A pointer to the tree-view control that owns the scrollbar. + drgui_element* pTVElement; + +} drgui_tree_view_scrollbar_data; + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Tree-View +// +/////////////////////////////////////////////////////////////////////////////// + +/// Refreshes the layout of the given tree-view control and schedules a redraw. +static void drgui_tv_refresh_and_redraw(drgui_element* pTVElement); + +/// Repositions and resizes the scrollbars of the given tree-view control. +static void drgui_tv_refresh_scrollbar_layouts(drgui_element* pTVElement); + +/// Refreshes the ranges and page sizes of the scrollbars of the given tree-view control. +static void drgui_tv_refresh_scrollbar_ranges(drgui_element* pTVElement); + +/// Retrieves the rectangle of the little space that sits between the two scrollbars. +static drgui_rect drgui_tv_get_scrollbar_dead_space_rect(drgui_element* pTVElement); + +/// Retrieves the rectangle region that does not include the scrollbars. This rectangle is used for clipping when drawing the tree-view. +static drgui_rect drgui_tv_get_inner_rect(drgui_element* pTVElement); + +/// Paints the items of the given tree-view control. +static void drgui_tv_paint_items(drgui_element* pTVElement, drgui_rect relativeClippingRect, void* pPaintData, float* pItemsBottomOut); + +/// Creates an iterator beginning at the given item. +static bool drgui_tv_begin_at(drgui_tree_view_item* pFirst, drgui_tree_view_iterator* pIteratorOut); + +/// Moves to the next item in the iterator. +static bool drgui_tv_next_visible(drgui_tree_view_iterator* pIterator); + +/// Paints the given item. +static void drgui_tv_paint_item(drgui_element* pTVElement, drgui_tree_view_item* pItem, drgui_rect relativeClippingRect, float posX, float posY, float width, float height, void* pPaintData); + +/// Finds the item under the given point. +static drgui_tree_view_item* drgui_tv_find_item_under_point(drgui_element* pTV, float relativePosX, float relativePosY, drgui_tree_view_item_metrics* pMetricsOut); + +/// Recursively deselects every item, including the given one. +static void drgui_tv_deselect_all_items_recursive(drgui_tree_view_item* pItem); + +/// Called when the mouse enters a scrollbar. We use this to ensure there are no items marked as hovered as the use moves the +/// mouse from the tree-view to the scrollbars. +static void drgui_tv_on_mouse_enter_scrollbar(drgui_element* pSBElement); + +/// Called when the vertical scrollbar is scrolled. +static void drgui_tv_on_scroll_v(drgui_element* pSBElement, int scrollPos); + +/// Called when the horizontal scrollbar is scrolled. +static void drgui_tv_on_scroll_h(drgui_element* pSBElement, int scrollPos); + +/// Retrieves a pointer to the first visible item on the page, based on the scroll position. +static drgui_tree_view_item* drgui_tv_find_first_visible_item_on_page(drgui_element* pTVElement); + + +drgui_element* drgui_create_tree_view(drgui_context* pContext, drgui_element* pParent, size_t extraDataSize, const void* pExtraData) +{ + drgui_element* pTVElement = drgui_create_element(pContext, pParent, sizeof(drgui_tree_view) + extraDataSize, NULL); + if (pTVElement == NULL) { + return NULL; + } + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return NULL; + } + + pTV->pRootItem = drgui_tv_create_item(pTVElement, NULL, 0, NULL); + if (pTV->pRootItem == NULL) { + return NULL; + } + + + drgui_tree_view_scrollbar_data sbdata; + sbdata.pTVElement = pTVElement; + + pTV->pScrollbarV = drgui_create_scrollbar(pContext, pTVElement, drgui_sb_orientation_vertical, sizeof(sbdata), &sbdata); + drgui_set_on_mouse_enter(pTV->pScrollbarV, drgui_tv_on_mouse_enter_scrollbar); + drgui_sb_set_on_scroll(pTV->pScrollbarV, drgui_tv_on_scroll_v); + + pTV->pScrollbarH = drgui_create_scrollbar(pContext, pTVElement, drgui_sb_orientation_horizontal, sizeof(sbdata), &sbdata); + drgui_set_on_mouse_enter(pTV->pScrollbarH, drgui_tv_on_mouse_enter_scrollbar); + drgui_sb_set_on_scroll(pTV->pScrollbarH, drgui_tv_on_scroll_h); + + + pTV->defaultBGColor = drgui_rgb(96, 96, 96); + pTV->hoveredBGColor = drgui_rgb(112, 112, 112); + pTV->selectedBGColor = drgui_rgb(80, 160, 255); + pTV->childOffsetX = 16; + + pTV->onItemMouseMove = NULL; + pTV->onItemMouseLeave = NULL; + pTV->onItemPaint = NULL; + pTV->onItemMeasure = NULL; + pTV->onItemPicked = NULL; + + pTV->pHoveredItem = NULL; + pTV->isMouseOverArrow = false; + pTV->isMouseOver = false; + pTV->relativeMousePosX = 0; + pTV->relativeMousePosY = 0; + + pTV->isMultiSelectEnabled = false; + pTV->isRangeSelectEnabled = false; + + pTV->extraDataSize = extraDataSize; + if (pExtraData != NULL) { + memcpy(pTV->pExtraData, pExtraData, extraDataSize); + } + + + // Default event handlers. + drgui_set_on_size(pTVElement, drgui_tv_on_size); + drgui_set_on_mouse_leave(pTVElement, drgui_tv_on_mouse_leave); + drgui_set_on_mouse_move(pTVElement, drgui_tv_on_mouse_move); + drgui_set_on_mouse_button_down(pTVElement, drgui_tv_on_mouse_button_down); + drgui_set_on_mouse_button_up(pTVElement, drgui_tv_on_mouse_button_up); + drgui_set_on_mouse_button_dblclick(pTVElement, drgui_tv_on_mouse_button_dblclick); + drgui_set_on_mouse_wheel(pTVElement, drgui_tv_on_mouse_wheel); + drgui_set_on_paint(pTVElement, drgui_tv_on_paint); + + + // Set the mouse wheel scale to 3 by default for the vertical scrollbar. + drgui_sb_set_mouse_wheel_scele(pTV->pScrollbarV, 3); + + + return pTVElement; +} + +void drgui_delete_tree_view(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + // Recursively delete the tree view items. + drgui_tvi_delete(pTV->pRootItem); + + // Delete the element last. + drgui_delete_element(pTVElement); +} + + +size_t drgui_tv_get_extra_data_size(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return 0; + } + + return pTV->extraDataSize; +} + +void* drgui_tv_get_extra_data(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return NULL; + } + + return pTV->pExtraData; +} + +drgui_tree_view_item* drgui_tv_get_root_item(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return NULL; + } + + return pTV->pRootItem; +} + +drgui_element* drgui_tv_get_vertical_scrollbar(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return NULL; + } + + return pTV->pScrollbarV; +} + +drgui_element* drgui_tv_get_horizontal_scrollbar(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return NULL; + } + + return pTV->pScrollbarH; +} + + +void drgui_tv_set_default_background_color(drgui_element* pTVElement, drgui_color color) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + pTV->defaultBGColor = color; +} + +drgui_color drgui_tv_get_default_background_color(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return drgui_rgb(0, 0, 0); + } + + return pTV->defaultBGColor; +} + +void drgui_tv_set_hovered_background_color(drgui_element* pTVElement, drgui_color color) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + pTV->hoveredBGColor = color; +} + +drgui_color drgui_tv_get_hovered_background_color(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return drgui_rgb(0, 0, 0); + } + + return pTV->hoveredBGColor; +} + +void drgui_tv_set_selected_background_color(drgui_element* pTVElement, drgui_color color) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + pTV->selectedBGColor = color; +} + +drgui_color drgui_tv_get_selected_background_color(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return drgui_rgb(0, 0, 0); + } + + return pTV->selectedBGColor; +} + +void drgui_tv_set_child_offset_x(drgui_element* pTVElement, float childOffsetX) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + pTV->childOffsetX = childOffsetX; +} + +float drgui_tv_get_child_offset_x(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return 0; + } + + return pTV->childOffsetX; +} + + +bool drgui_tv_measure_item(drgui_element* pTVElement, drgui_tree_view_item* pItem, float* pWidthOut, float* pHeightOut) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return false; + } + + if (pItem == NULL || pItem->pTVElement != pTVElement) { + return false; + } + + if (pTV->onItemMeasure) + { + pTV->onItemMeasure(pItem, pWidthOut, pHeightOut); + return true; + } + + return false; +} + +void drgui_tv_deselect_all_items(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + drgui_tv_deselect_all_items_recursive(pTV->pRootItem); + + // TODO: Only redraw the region that actually changed. + drgui_dirty(pTVElement, drgui_get_local_rect(pTVElement)); +} + + +void drgui_tv_enable_multi_select(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + pTV->isMultiSelectEnabled = true; +} + +void drgui_tv_disable_multi_select(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + pTV->isMultiSelectEnabled = false; +} + +bool drgui_tv_is_multi_select_enabled(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return false; + } + + return pTV->isMultiSelectEnabled; +} + +drgui_tree_view_item* drgui_tv_get_first_selected_item(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return NULL; + } + + drgui_tree_view_iterator i; + if (drgui_tv_begin_at(pTV->pRootItem->pFirstChild, &i)) + { + do + { + if (drgui_tvi_is_selected(i.pItem)) { + return i.pItem; + } + + } while (drgui_tv_next_visible(&i)); + } + + return NULL; +} + +drgui_tree_view_item* drgui_tv_get_next_selected_item(drgui_element* pTVElement, drgui_tree_view_item* pItem) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return NULL; + } + + drgui_tree_view_iterator i; + if (drgui_tv_begin_at(pItem, &i)) + { + // Note that we're not including in this iteration. + while (drgui_tv_next_visible(&i)) + { + if (drgui_tvi_is_selected(i.pItem)) { + return i.pItem; + } + } + } + + return NULL; +} + + +void drgui_tv_set_on_item_mouse_move(drgui_element* pTVElement, drgui_tvi_on_mouse_move_proc proc) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + pTV->onItemMouseMove = proc; +} + +void drgui_tv_set_on_item_mouse_leave(drgui_element* pTVElement, drgui_tvi_on_mouse_leave_proc proc) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + pTV->onItemMouseLeave = proc; +} + +void drgui_tv_set_on_item_paint(drgui_element* pTVElement, drgui_tvi_on_paint_proc proc) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + pTV->onItemPaint = proc; +} + +void drgui_tv_set_on_item_measure(drgui_element* pTVElement, drgui_tvi_measure_proc proc) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + pTV->onItemMeasure = proc; +} + +void drgui_tv_set_on_item_picked(drgui_element* pTVElement, drgui_tvi_on_picked_proc proc) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + pTV->onItemPicked = proc; +} + + +void drgui_tv_on_size(drgui_element* pTVElement, float newWidth, float newHeight) +{ + (void)newWidth; + (void)newHeight; + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + // Move the scrollbars. + drgui_tv_refresh_scrollbar_layouts(pTVElement); + + // Refresh the scrollbar ranges. + drgui_tv_refresh_scrollbar_ranges(pTVElement); +} + +void drgui_tv_on_mouse_leave(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + pTV->isMouseOver = false; + + if (pTV->pHoveredItem != NULL || pTV->isMouseOverArrow) + { + if (pTV->onItemMouseLeave) { + pTV->onItemMouseLeave(pTV->pHoveredItem); + } + + pTV->pHoveredItem = NULL; + pTV->isMouseOverArrow = false; + + // For now just redraw the entire control, but should optimize this to only redraw the regions of the new and old hovered items. + drgui_dirty(pTVElement, drgui_get_local_rect(pTVElement)); + } +} + +void drgui_tv_on_mouse_move(drgui_element* pTVElement, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)stateFlags; + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + pTV->isMouseOver = true; + pTV->relativeMousePosX = relativeMousePosX; + pTV->relativeMousePosY = relativeMousePosY; + + // If the mouse has entered into the dead space between the scrollbars, we just pretend the mouse has left the tree-view + // control entirely by posting a manual on_mouse_leave event and returning straight away. + if (drgui_rect_contains_point(drgui_tv_get_scrollbar_dead_space_rect(pTVElement), (float)relativeMousePosX, (float)relativeMousePosY)) { + drgui_tv_on_mouse_leave(pTVElement); + return; + } + + + drgui_tree_view_item_metrics newHoveredItemMetrics; + drgui_tree_view_item* pNewHoveredItem = drgui_tv_find_item_under_point(pTVElement, (float)relativeMousePosX, (float)relativeMousePosY, &newHoveredItemMetrics); + drgui_tree_view_item* pOldHoveredItem = pTV->pHoveredItem; + + bool wasMouseOverArrow = pTV->isMouseOverArrow; + pTV->isMouseOverArrow = false; + + if (pNewHoveredItem != NULL) + { + if (pTV->onItemMouseMove) + { + float relativeMousePosXToItem = (float)relativeMousePosX - newHoveredItemMetrics.posX + drgui_sb_get_scroll_position(pTV->pScrollbarH); + float relativeMousePosYToItem = (float)relativeMousePosY - newHoveredItemMetrics.posY; + + if (relativeMousePosXToItem >= 0 && relativeMousePosXToItem < newHoveredItemMetrics.width && + relativeMousePosYToItem >= 0 && relativeMousePosYToItem < newHoveredItemMetrics.height) + { + pTV->onItemMouseMove(pNewHoveredItem, (int)relativeMousePosXToItem, (int)relativeMousePosYToItem, &pTV->isMouseOverArrow); + } + } + } + + if (pNewHoveredItem != pOldHoveredItem || wasMouseOverArrow != pTV->isMouseOverArrow) + { + if (pNewHoveredItem != pOldHoveredItem && pOldHoveredItem != NULL) + { + if (pTV->onItemMouseLeave) { + pTV->onItemMouseLeave(pOldHoveredItem); + } + } + + + pTV->pHoveredItem = pNewHoveredItem; + + // TODO: Optimize this so that only the rectangle region encompassing the two relevant items is marked as dirty. + drgui_dirty(pTVElement, drgui_get_local_rect(pTVElement)); + } +} + +void drgui_tv_on_mouse_button_down(drgui_element* pTVElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)relativeMousePosX; + (void)relativeMousePosY; + (void)stateFlags; + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + if (mouseButton == DRGUI_MOUSE_BUTTON_LEFT) + { + if (pTV->isMouseOverArrow) + { + if (drgui_tvi_is_expanded(pTV->pHoveredItem)) { + drgui_tvi_collapse(pTV->pHoveredItem); + } else { + drgui_tvi_expand(pTV->pHoveredItem); + } + } + else + { + if (pTV->isMultiSelectEnabled) + { + if (drgui_tvi_is_selected(pTV->pHoveredItem)) { + drgui_tvi_deselect(pTV->pHoveredItem); + } else { + drgui_tvi_select(pTV->pHoveredItem); + } + } + else + { + // TODO: Check if range selection is enabled and handle it here. + + drgui_tv_deselect_all_items(pTVElement); + drgui_tvi_select(pTV->pHoveredItem); + } + } + } +} + +void drgui_tv_on_mouse_button_up(drgui_element* pTVElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)mouseButton; + (void)relativeMousePosX; + (void)relativeMousePosY; + (void)stateFlags; + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + // TODO: Implement me. +} + +void drgui_tv_on_mouse_button_dblclick(drgui_element* pTVElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)relativeMousePosX; + (void)relativeMousePosY; + (void)stateFlags; + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + if (mouseButton == DRGUI_MOUSE_BUTTON_LEFT) + { + if (!pTV->isMouseOverArrow) + { + if (drgui_tvi_has_children(pTV->pHoveredItem)) + { + // It is a parent item, so toggle it. + if (drgui_tvi_is_expanded(pTV->pHoveredItem)) { + drgui_tvi_collapse(pTV->pHoveredItem); + } else { + drgui_tvi_expand(pTV->pHoveredItem); + } + } + else + { + // It is a leaf item, so pick it. + if (pTV->onItemPicked) { + pTV->onItemPicked(pTV->pHoveredItem); + } + } + } + } +} + +void drgui_tv_on_mouse_wheel(drgui_element* pTVElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags) +{ + (void)relativeMousePosX; + (void)relativeMousePosY; + (void)stateFlags; + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + drgui_sb_scroll(pTV->pScrollbarV, -delta * drgui_sb_get_mouse_wheel_scale(pTV->pScrollbarV)); +} + +void drgui_tv_on_paint(drgui_element* pTVElement, drgui_rect relativeClippingRect, void* pPaintData) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + // The dead space between the scrollbars should always be drawn with the default background color. + drgui_draw_rect(pTVElement, drgui_tv_get_scrollbar_dead_space_rect(pTVElement), pTV->defaultBGColor, pPaintData); + + // The clipping rectangle needs to be clamped to the local rectangle that is shrunk such that it does not + // include the scrollbars. If we don't do this we'll end up drawing underneath the scrollbars which will + // cause flickering. + drgui_rect innerClippingRect = drgui_clamp_rect(drgui_tv_get_inner_rect(pTVElement), relativeClippingRect); + drgui_set_clip(pTVElement, innerClippingRect, pPaintData); + + + // The main content of the tree-view is drawn in two parts. The first part (the top part) contains all of + // the tree-view items. The second part (the bottom part) is just the background region that is not covered + // by items. + + // We draw the tree-view items (the top part) first. This will retrieve the position of the bottom of the + // items which is used to determine how much empty space is remaining below it so we can draw a quad over + // that part. + float itemsBottom = 0; + drgui_tv_paint_items(pTVElement, innerClippingRect, pPaintData, &itemsBottom); + + + // At this point the items have been drawn. All that remains is the part of the background that is not + // covered by items. We can determine this by looking at . + if (itemsBottom < relativeClippingRect.bottom && itemsBottom < drgui_get_relative_position_y(pTV->pScrollbarH)) + { + drgui_draw_rect(pTVElement, drgui_make_rect(0, itemsBottom, drgui_get_relative_position_x(pTV->pScrollbarV), drgui_get_relative_position_y(pTV->pScrollbarH)), pTV->defaultBGColor, pPaintData); + } +} + + +static void drgui_tv_refresh_and_redraw(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + + // Refresh scrollbar ranges and page sizes. + drgui_tv_refresh_scrollbar_ranges(pTVElement); + + // For now, just redraw the entire control. + drgui_dirty(pTVElement, drgui_get_local_rect(pTVElement)); +} + +static void drgui_tv_refresh_scrollbar_layouts(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + + // Vertical scrollbar. + drgui_set_size(pTV->pScrollbarV, 16, drgui_get_height(pTVElement) - 16); + drgui_set_relative_position(pTV->pScrollbarV, drgui_get_width(pTVElement) - drgui_get_width(pTV->pScrollbarV), 0); + + // Horizontal scrollbar. + drgui_set_size(pTV->pScrollbarH, drgui_get_width(pTVElement) - 16, 16); + drgui_set_relative_position(pTV->pScrollbarH, 0, drgui_get_height(pTVElement) - drgui_get_height(pTV->pScrollbarH)); +} + +static void drgui_tv_refresh_scrollbar_ranges(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + float innerWidth = 0; + unsigned int totalItemCount = 0; + unsigned int pageItemCount = 0; + + drgui_tree_view_iterator i; + if (drgui_tv_begin_at(pTV->pRootItem->pFirstChild, &i)) + { + do + { + float itemRight = i.posX + i.width; + if (itemRight > innerWidth) { + innerWidth = itemRight; + } + + float itemBottom = i.posY + i.height; + if (itemBottom > 0 && itemBottom < drgui_get_relative_position_y(pTV->pScrollbarH)) { + pageItemCount += 1; + } + + totalItemCount += 1; + + } while (drgui_tv_next_visible(&i)); + } + + if (totalItemCount == 0) + { + // Vertical. + drgui_sb_set_range(pTV->pScrollbarV, 0, 0); + drgui_sb_set_page_size(pTV->pScrollbarV, 0); + + // Horizontal. + drgui_sb_set_range(pTV->pScrollbarH, 0, 0); + drgui_sb_set_page_size(pTV->pScrollbarH, 0); + } + else + { + // Vertical. + drgui_sb_set_range(pTV->pScrollbarV, 0, (int)totalItemCount - 1); // - 1 because it's a 0-based range. + drgui_sb_set_page_size(pTV->pScrollbarV, pageItemCount); + + // Horizontal. + drgui_sb_set_range(pTV->pScrollbarH, 0, (int)innerWidth); + drgui_sb_set_page_size(pTV->pScrollbarH, (int)drgui_get_relative_position_x(pTV->pScrollbarV)); + } +} + +static drgui_rect drgui_tv_get_scrollbar_dead_space_rect(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return drgui_make_rect(0, 0, 0, 0); + } + + return drgui_make_rect(drgui_get_width(pTV->pScrollbarH), drgui_get_height(pTV->pScrollbarV), drgui_get_width(pTVElement), drgui_get_height(pTVElement)); +} + +static drgui_rect drgui_tv_get_inner_rect(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return drgui_make_rect(0, 0, 0, 0); + } + + drgui_rect result = drgui_get_local_rect(pTVElement); + result.right -= drgui_get_width(pTV->pScrollbarV); + result.bottom -= drgui_get_height(pTV->pScrollbarH); + + return result; +} + +static void drgui_tv_paint_items(drgui_element* pTVElement, drgui_rect relativeClippingRect, void* pPaintData, float* pItemsBottomOut) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + float itemsBottom = 0; + + // For now we will begin at the root item, but later we want to begin at the first visible item which will be based on the + // scroll position. + drgui_tree_view_iterator i; + if (drgui_tv_begin_at(drgui_tv_find_first_visible_item_on_page(pTVElement), &i)) + { + do + { + drgui_tv_paint_item(pTVElement, i.pItem, relativeClippingRect, i.posX, i.posY, i.width, i.height, pPaintData); + + // Restore the clipping rectangle in case the application changed the clipping rectangle. + drgui_set_clip(pTVElement, relativeClippingRect, pPaintData); + + itemsBottom = i.posY + i.height; + + } while (itemsBottom < relativeClippingRect.bottom && drgui_tv_next_visible(&i)); + } + + + if (pItemsBottomOut != NULL) { + *pItemsBottomOut = itemsBottom; + } +} + +static bool drgui_tv_begin_at(drgui_tree_view_item* pFirst, drgui_tree_view_iterator* pIteratorOut) +{ + if (pFirst == NULL || pIteratorOut == NULL) { + return false; + } + + if (!drgui_tv_measure_item(pFirst->pTVElement, pFirst, &pIteratorOut->width, &pIteratorOut->height)) { + return false; + } + + const int depth = drgui_tvi_get_depth(pFirst); + + pIteratorOut->pItem = pFirst; + pIteratorOut->depth = depth; + pIteratorOut->posX = depth * drgui_tv_get_child_offset_x(pFirst->pTVElement); + pIteratorOut->posY = 0; + + return true; +} + +static bool drgui_tv_next_visible(drgui_tree_view_iterator* pIterator) +{ + assert(pIterator != NULL); + + if (pIterator->pItem == NULL) { + return false; + } + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pIterator->pItem->pTVElement); + if (pTV == NULL) { + return false; + } + + if (drgui_tvi_has_children(pIterator->pItem) && drgui_tvi_is_expanded(pIterator->pItem)) + { + pIterator->pItem = pIterator->pItem->pFirstChild; + pIterator->depth += 1; + } + else + { + pIterator->pItem = drgui_tvi_next_visible_non_child(pIterator->pItem, &pIterator->depth); + } + + + if (pIterator->pItem == NULL) { + return false; + } + + pIterator->posX = pIterator->depth * drgui_tv_get_child_offset_x(pIterator->pItem->pTVElement); + pIterator->posY += pIterator->height; + + if (!drgui_tv_measure_item(pIterator->pItem->pTVElement, pIterator->pItem, &pIterator->width, &pIterator->height)) { + return false; + } + + return true; +} + +static void drgui_tv_paint_item(drgui_element* pTVElement, drgui_tree_view_item* pItem, drgui_rect relativeClippingRect, float posX, float posY, float width, float height, void* pPaintData) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return; + } + + if (pTV->onItemPaint) + { + // We draw an item in two main parts, with the first part being the background section to the left and right of the item and the + // second part being the item itself. The first part we do ourselves, whereas the second part we pass off to the host application. + + // The background section to the left and right of the main content is done first, by us. + drgui_color bgcolor; + if (drgui_tvi_is_selected(pItem)) { + bgcolor = pTV->selectedBGColor; + } else if (pTV->pHoveredItem == pItem) { + bgcolor = pTV->hoveredBGColor; + } else { + bgcolor = pTV->defaultBGColor; + } + + float innerOffsetX = (float)-drgui_sb_get_scroll_position(pTV->pScrollbarH); + + // Left. + if (posX + innerOffsetX > 0) { + drgui_draw_rect(pTVElement, drgui_make_rect(0, posY, posX + innerOffsetX, posY + height), bgcolor, pPaintData); + } + + // Right. + if (posX + width + innerOffsetX < drgui_get_relative_position_x(pTV->pScrollbarV)) { + drgui_draw_rect(pTVElement, drgui_make_rect(posX + width + innerOffsetX, posY, drgui_get_relative_position_x(pTV->pScrollbarV), posY + height), bgcolor, pPaintData); + } + + + // At this point if were to finish drawing we'd have a hole where the main content of the item should be. To fill this we need to + // let the host application do it. + pTV->onItemPaint(pTVElement, pItem, relativeClippingRect, bgcolor, posX + innerOffsetX, posY, width, height, pPaintData); + } +} + +static drgui_tree_view_item* drgui_tv_find_item_under_point(drgui_element* pTVElement, float relativePosX, float relativePosY, drgui_tree_view_item_metrics* pMetricsOut) +{ + (void)relativePosX; // <-- Unused because we treat items as though they are infinitely wide. + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return NULL; + } + + // For now we will begin at the root item, but later we want to begin at the first visible item which will be based on the + // scroll position. + drgui_tree_view_iterator i; + if (drgui_tv_begin_at(drgui_tv_find_first_visible_item_on_page(pTVElement), &i)) + { + do + { + if (relativePosY >= i.posY && relativePosY < i.posY + i.height) + { + if (pMetricsOut != NULL) + { + pMetricsOut->posX = i.posX; + pMetricsOut->posY = i.posY; + pMetricsOut->width = i.width; + pMetricsOut->height = i.height; + } + + return i.pItem; + } + + } while ((i.posY + i.height < drgui_get_relative_position_y(pTV->pScrollbarH)) && drgui_tv_next_visible(&i)); + } + + return NULL; +} + +static void drgui_tv_deselect_all_items_recursive(drgui_tree_view_item* pItem) +{ + pItem->isSelected = false; + + for (drgui_tree_view_item* pChild = pItem->pFirstChild; pChild != NULL; pChild = pChild->pNextSibling) + { + drgui_tv_deselect_all_items_recursive(pChild); + } +} + +static void drgui_tv_on_mouse_enter_scrollbar(drgui_element* pSBElement) +{ + drgui_tree_view_scrollbar_data* pSB = (drgui_tree_view_scrollbar_data*)drgui_sb_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + // We just pretend the mouse has left the tree-view entirely. This will ensure any item marked as hovered is unmarked and redrawn. + drgui_tv_on_mouse_leave(pSB->pTVElement); +} + +static void drgui_tv_on_scroll_v(drgui_element* pSBElement, int scrollPos) +{ + (void)scrollPos; + + drgui_tree_view_scrollbar_data* pSB = (drgui_tree_view_scrollbar_data*)drgui_sb_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pSB->pTVElement); + if (pTV == NULL) { + return; + } + + // As we scroll, the mouse will be placed over a different item. We just post a manual mouse_move event to trigger a refresh. + if (pTV->isMouseOver) { + drgui_tv_on_mouse_move(pSB->pTVElement, pTV->relativeMousePosX, pTV->relativeMousePosY, 0); + } + + // The paint routine is tied directly to the scrollbars, so all we need to do is mark it as dirty to trigger a redraw. + drgui_dirty(pSB->pTVElement, drgui_get_local_rect(pSB->pTVElement)); +} + +static void drgui_tv_on_scroll_h(drgui_element* pSBElement, int scrollPos) +{ + (void)scrollPos; + + drgui_tree_view_scrollbar_data* pSB = (drgui_tree_view_scrollbar_data*)drgui_sb_get_extra_data(pSBElement); + if (pSB == NULL) { + return; + } + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pSB->pTVElement); + if (pTV == NULL) { + return; + } + + // The paint routine is tied directly to the scrollbars, so all we need to do is mark it as dirty to trigger a redraw. + drgui_dirty(pSB->pTVElement, drgui_get_local_rect(pSB->pTVElement)); +} + +static drgui_tree_view_item* drgui_tv_find_first_visible_item_on_page(drgui_element* pTVElement) +{ + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement); + if (pTV == NULL) { + return NULL; + } + + // We just keep iterating until we hit the index of the scroll position. + int index = 0; + + drgui_tree_view_iterator i; + if (drgui_tv_begin_at(pTV->pRootItem->pFirstChild, &i)) + { + do + { + if (index == drgui_sb_get_scroll_position(pTV->pScrollbarV)) { + return i.pItem; + } + + index += 1; + + } while (drgui_tv_next_visible(&i)); + } + + return NULL; +} + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Tree-View Item +// +/////////////////////////////////////////////////////////////////////////////// + +/// Detaches the given tree-view item from it's parent and siblings. +static void drgui_tvi_detach(drgui_tree_view_item* pItem); + +drgui_tree_view_item* drgui_tv_create_item(drgui_element* pTVElement, drgui_tree_view_item* pParent, size_t extraDataSize, const void* pExtraData) +{ + if (pTVElement == NULL) { + return NULL; + } + + if (pParent != NULL && pParent->pTVElement != pTVElement) { + return NULL; + } + + + drgui_tree_view_item* pItem = (drgui_tree_view_item*)malloc(sizeof(*pItem) + extraDataSize); + if (pItem == NULL) { + return NULL; + } + + pItem->pTVElement = pTVElement; + pItem->pParent = NULL; + pItem->pFirstChild = NULL; + pItem->pLastChild = NULL; + pItem->pNextSibling = NULL; + pItem->pPrevSibling = NULL; + pItem->isSelected = false; + pItem->isExpanded = false; + + pItem->extraDataSize = extraDataSize; + if (pExtraData != NULL) { + memcpy(pItem->pExtraData, pExtraData, extraDataSize); + } + + + // Append the item to the end of the parent item. + drgui_tvi_append(pItem, pParent); + + return pItem; +} + +void drgui_tvi_delete(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return; + } + + + // Children need to be deleted first. + while (pItem->pFirstChild != NULL) { + drgui_tvi_delete(pItem->pFirstChild); + } + + // We need to grab a pointer to the main tree-view control so we can refresh and redraw it after we have detached the item. + drgui_element* pTVElement = pItem->pTVElement; + + // The item needs to be completely detached first. + drgui_tvi_detach(pItem); + + // Refresh the layout and redraw the tree-view control. + drgui_tv_refresh_and_redraw(pTVElement); + + // Free the item last for safety. + free(pItem); +} + +drgui_element* drgui_tvi_get_tree_view_element(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return NULL; + } + + return pItem->pTVElement; +} + +size_t drgui_tvi_get_extra_data_size(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return 0; + } + + return pItem->extraDataSize; +} + +void* drgui_tvi_get_extra_data(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return NULL; + } + + return pItem->pExtraData; +} + + +drgui_tree_view_item* drgui_tvi_get_parent(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return NULL; + } + + return pItem->pParent; +} + +drgui_tree_view_item* drgui_tvi_get_first_child(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return NULL; + } + + return pItem->pFirstChild; +} + +drgui_tree_view_item* drgui_tvi_get_last_child(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return NULL; + } + + return pItem->pLastChild; +} + +drgui_tree_view_item* drgui_tvi_get_next_sibling(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return NULL; + } + + return pItem->pNextSibling; +} + +drgui_tree_view_item* drgui_tvi_get_prev_sibling(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return NULL; + } + + return pItem->pPrevSibling; +} + +void drgui_tvi_append(drgui_tree_view_item* pItem, drgui_tree_view_item* pParent) +{ + if (pItem == NULL) { + return; + } + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pItem->pTVElement); + if (pTV == NULL) { + return; + } + + + // If a parent was not specified, append to the root item. + if (pParent == NULL) + { + if (pTV->pRootItem != NULL) { + drgui_tvi_append(pItem, pTV->pRootItem); + } + } + else + { + assert(pItem->pTVElement == pParent->pTVElement); + + // Detach the child from it's current parent first. + drgui_tvi_detach(pItem); + + pItem->pParent = pParent; + assert(pItem->pParent != NULL); + + if (pItem->pParent->pLastChild != NULL) { + pItem->pPrevSibling = pItem->pParent->pLastChild; + pItem->pPrevSibling->pNextSibling = pItem; + } + + if (pItem->pParent->pFirstChild == NULL) { + pItem->pParent->pFirstChild = pItem; + } + + pItem->pParent->pLastChild = pItem; + + + // Refresh the layout and redraw the tree-view control. + drgui_tv_refresh_and_redraw(pItem->pTVElement); + } +} + +void drgui_tvi_prepend(drgui_tree_view_item* pItem, drgui_tree_view_item* pParent) +{ + if (pItem == NULL) { + return; + } + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pItem->pTVElement); + if (pTV == NULL) { + return; + } + + + // If a parent was not specified, prepend to the root item. + if (pParent == NULL) + { + if (pTV->pRootItem != NULL) { + drgui_tvi_prepend(pItem, pTV->pRootItem); + } + } + else + { + assert(pItem->pTVElement == pParent->pTVElement); + + // Detach the child from it's current parent first. + drgui_tvi_detach(pItem); + + pItem->pParent = pParent; + assert(pItem->pParent != NULL); + + if (pItem->pParent->pFirstChild != NULL) { + pItem->pNextSibling = pItem->pParent->pFirstChild; + pItem->pNextSibling->pPrevSibling = pItem; + } + + if (pItem->pParent->pLastChild == NULL) { + pItem->pParent->pLastChild = pItem; + } + + pItem->pParent->pFirstChild = pItem; + + + // Refresh the layout and redraw the tree-view control. + drgui_tv_refresh_and_redraw(pItem->pTVElement); + } +} + +void drgui_tvi_append_sibling(drgui_tree_view_item* pItemToAppend, drgui_tree_view_item* pItemToAppendTo) +{ + if (pItemToAppend == NULL) { + return; + } + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pItemToAppend->pTVElement); + if (pTV == NULL) { + return; + } + + + // If a parent was not specified, append to the root item. + if (pItemToAppendTo == NULL) + { + if (pTV->pRootItem != NULL) { + drgui_tvi_append(pItemToAppend, pTV->pRootItem); + } + } + else + { + assert(pItemToAppend->pTVElement == pItemToAppendTo->pTVElement); + + // Detach the child from it's current parent first. + drgui_tvi_detach(pItemToAppend); + + + pItemToAppend->pParent = pItemToAppendTo->pParent; + assert(pItemToAppend->pParent != NULL); + + pItemToAppend->pNextSibling = pItemToAppendTo->pNextSibling; + pItemToAppend->pPrevSibling = pItemToAppendTo; + + pItemToAppendTo->pNextSibling->pPrevSibling = pItemToAppend; + pItemToAppendTo->pNextSibling = pItemToAppend; + + if (pItemToAppend->pParent->pLastChild == pItemToAppendTo) { + pItemToAppend->pParent->pLastChild = pItemToAppend; + } + + + // Refresh the layout and redraw the tree-view control. + drgui_tv_refresh_and_redraw(pItemToAppend->pTVElement); + } +} + +void drgui_tvi_prepend_sibling(drgui_tree_view_item* pItemToPrepend, drgui_tree_view_item* pItemToPrependTo) +{ + if (pItemToPrepend == NULL) { + return; + } + + drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pItemToPrepend->pTVElement); + if (pTV == NULL) { + return; + } + + + // If a parent was not specified, prepend to the root item. + if (pItemToPrependTo == NULL) + { + if (pTV->pRootItem != NULL) { + drgui_tvi_prepend(pItemToPrepend, pTV->pRootItem); + } + } + else + { + assert(pItemToPrepend->pTVElement == pItemToPrependTo->pTVElement); + + // Detach the child from it's current parent first. + drgui_tvi_detach(pItemToPrepend); + + + pItemToPrepend->pParent = pItemToPrependTo->pParent; + assert(pItemToPrepend->pParent != NULL); + + pItemToPrepend->pPrevSibling = pItemToPrependTo->pNextSibling; + pItemToPrepend->pNextSibling = pItemToPrependTo; + + pItemToPrependTo->pPrevSibling->pNextSibling = pItemToPrepend; + pItemToPrependTo->pNextSibling = pItemToPrepend; + + if (pItemToPrepend->pParent->pFirstChild == pItemToPrependTo) { + pItemToPrepend->pParent->pFirstChild = pItemToPrepend; + } + + + // Refresh the layout and redraw the tree-view control. + drgui_tv_refresh_and_redraw(pItemToPrepend->pTVElement); + } +} + + +bool drgui_tvi_has_children(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return false; + } + + return pItem->pFirstChild != NULL; +} + +int drgui_tvi_get_depth(drgui_tree_view_item* pItem) +{ + if (pItem->pParent == NULL || pItem->pParent == drgui_tv_get_root_item(pItem->pTVElement)) { + return 0; + } + + return drgui_tvi_get_depth(pItem->pParent) + 1; +} + +drgui_tree_view_item* drgui_tvi_next_visible_non_child(drgui_tree_view_item* pItem, int* pDepthInOut) +{ + if (pItem == NULL) { + return NULL; + } + + if (pItem->pNextSibling != NULL) { + return pItem->pNextSibling; + } + + + if (pDepthInOut != NULL) { + *pDepthInOut -= 1; + } + + return drgui_tvi_next_visible_non_child(pItem->pParent, pDepthInOut); +} + + +void drgui_tvi_select(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return; + } + + if (!pItem->isSelected) + { + pItem->isSelected = true; + drgui_dirty(pItem->pTVElement, drgui_get_local_rect(pItem->pTVElement)); + } +} + +void drgui_tvi_deselect(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return; + } + + if (pItem->isSelected) + { + pItem->isSelected = false; + drgui_dirty(pItem->pTVElement, drgui_get_local_rect(pItem->pTVElement)); + } +} + +bool drgui_tvi_is_selected(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return false; + } + + return pItem->isSelected; +} + +void drgui_tvi_expand(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return; + } + + if (!pItem->isExpanded) + { + pItem->isExpanded = true; + drgui_tv_refresh_and_redraw(pItem->pTVElement); + } +} + +void drgui_tvi_collapse(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return; + } + + if (pItem->isExpanded) + { + pItem->isExpanded = false; + drgui_tv_refresh_and_redraw(pItem->pTVElement); + } +} + +bool drgui_tvi_is_expanded(drgui_tree_view_item* pItem) +{ + if (pItem == NULL) { + return false; + } + + return pItem->isExpanded; +} + + + +static void drgui_tvi_detach(drgui_tree_view_item* pItem) +{ + assert(pItem != NULL); + + if (pItem->pParent != NULL) + { + if (pItem->pParent->pFirstChild == pItem) { + pItem->pParent->pFirstChild = pItem->pNextSibling; + } + + if (pItem->pParent->pLastChild == pItem) { + pItem->pParent->pLastChild = pItem->pPrevSibling; + } + + + if (pItem->pPrevSibling != NULL) { + pItem->pPrevSibling->pNextSibling = pItem->pNextSibling; + } + + if (pItem->pNextSibling != NULL) { + pItem->pNextSibling->pPrevSibling = pItem->pPrevSibling; + } + } + + pItem->pParent = NULL; + pItem->pPrevSibling = NULL; + pItem->pNextSibling = NULL; +} +#endif //DR_GUI_IMPLEMENTATION + + +#ifdef DR_GUI_INCLUDE_WIP +// #include WIP files here. +#endif //DR_GUI_INCLUDE_WIP + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ diff --git a/source/engine/thirdparty/dr_libs/old/dr_math.h b/source/engine/thirdparty/dr_libs/old/dr_math.h new file mode 100644 index 0000000..0a74c05 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/old/dr_math.h @@ -0,0 +1,676 @@ +// Public Domain. See "unlicense" statement at the end of this file. + +// NOTE: This is still very much work in progress and is only being updated as I need it. You don't want to be using this library +// in its current state. + +// QUICK NOTES +// - This library does not use SSE for its basic types (vec4, etc.). Rationale: 1) It keeps things simple; 2) SSE is not always +// faster than the FPU(s) on modern CPUs; 3) The library can always implement functions that work on __m128 variables directly +// in the future if the need arises; 4) It doesn't work well with the pass-by-value API this library uses. +// - Use DISABLE_SSE to disable SSE optimized functions. +// - Angles are always specified in radians, unless otherwise noted. Rationale: Consistency with the standard library and most +// other math libraries. +// - Use radians() and degrees() to convert between the two. + +#ifndef dr_math_h +#define dr_math_h + +#include + +#if defined(_MSC_VER) +#define DR_MATHCALL static __forceinline +#else +#define DR_MATHCALL static inline +#endif + +#define DR_PI 3.14159265358979323846 +#define DR_PIF 3.14159265358979323846f + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct +{ + float x; + float y; + float z; + float w; +} vec4; + +typedef struct +{ + float x; + float y; + float z; +} vec3; + +typedef struct +{ + float x; + float y; +} vec2; + +typedef struct +{ + vec4 col[4]; +} mat4; + +typedef struct +{ + float x; + float y; + float z; + float w; +} quat; + + +// Radians to degrees. +DR_MATHCALL float dr_degrees(float radians) +{ + return radians * 57.29577951308232087685f; +} + +// Degrees to radians. +DR_MATHCALL float dr_radians(float degrees) +{ + return degrees * 0.01745329251994329577f; +} + + + +/////////////////////////////////////////////// +// +// VEC4 +// +/////////////////////////////////////////////// + +DR_MATHCALL vec4 vec4f(float x, float y, float z, float w) +{ + vec4 result; + result.x = x; + result.y = y; + result.z = z; + result.w = w; + + return result; +} +DR_MATHCALL vec4 vec4v(const float* v) +{ + return vec4f(v[0], v[1], v[2], v[3]); +} +DR_MATHCALL vec4 vec4_zero() +{ + return vec4f(0, 0, 0, 0); +} +DR_MATHCALL vec4 vec4_one() +{ + return vec4f(1, 1, 1, 1); +} + + + +DR_MATHCALL vec4 vec4_add(vec4 a, vec4 b) +{ + return vec4f(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} + +DR_MATHCALL vec4 vec4_sub(vec4 a, vec4 b) +{ + return vec4f(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} + + +DR_MATHCALL vec4 vec4_mul(vec4 a, vec4 b) +{ + return vec4f(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} +DR_MATHCALL vec4 vec4_mul_1f(vec4 a, float x) +{ + return vec4f(a.x * x, a.y * x, a.z * x, a.w * x); +} +DR_MATHCALL vec4 vec4_mul_mat4(vec4 v, mat4 m) +{ + const vec4 m0 = m.col[0]; + const vec4 m1 = m.col[1]; + const vec4 m2 = m.col[2]; + const vec4 m3 = m.col[3]; + + return vec4f( + m0.x*v.x + m0.y*v.y + m0.z*v.z + m0.w*v.w, + m1.x*v.x + m1.y*v.y + m1.z*v.z + m1.w*v.w, + m2.x*v.x + m2.y*v.y + m2.z*v.z + m2.w*v.w, + m3.x*v.x + m3.y*v.y + m3.z*v.z + m3.w*v.w + ); +} + + +DR_MATHCALL vec4 vec4_div(vec4 a, vec4 b) +{ + return vec4f(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} + + + + + + +/////////////////////////////////////////////// +// +// VEC3 +// +/////////////////////////////////////////////// + +DR_MATHCALL vec3 vec3f(float x, float y, float z) +{ + vec3 result; + result.x = x; + result.y = y; + result.z = z; + + return result; +} +DR_MATHCALL vec3 vec3v(const float* v) +{ + return vec3f(v[0], v[1], v[2]); +} +DR_MATHCALL vec3 vec3_zero() +{ + return vec3f(0, 0, 0); +} +DR_MATHCALL vec3 vec3_one() +{ + return vec3f(1, 1, 1); +} + + +DR_MATHCALL vec3 vec3_add(vec3 a, vec3 b) +{ + return vec3f(a.x + b.x, a.y + b.y, a.z + b.z); +} + +DR_MATHCALL vec3 vec3_sub(vec3 a, vec3 b) +{ + return vec3f(a.x - b.x, a.y - b.y, a.z - b.z); +} + + +DR_MATHCALL vec3 vec3_mul(vec3 a, vec3 b) +{ + return vec3f(a.x * b.x, a.y * b.y, a.z * b.z); +} +DR_MATHCALL vec3 vec3_mul_1f(vec3 a, float x) +{ + return vec3f(a.x * x, a.y * x, a.z * x); +} + + +DR_MATHCALL vec3 vec3_div(vec3 a, vec3 b) +{ + return vec3f(a.x / b.x, a.y / b.y, a.z / b.z); +} + + +DR_MATHCALL float vec3_dot(vec3 a, vec3 b) +{ + return a.x*b.x + a.y*b.y + a.z*b.z; +} + + +DR_MATHCALL float vec3_length2(vec3 a) +{ + return vec3_dot(a, a); +} + +DR_MATHCALL float vec3_length(vec3 a) +{ + return sqrtf(vec3_length2(a)); +} + + +DR_MATHCALL vec3 vec3_normalize(vec3 a) +{ + float len = vec3_length(a); + + return vec3f( + a.x / len, + a.y / len, + a.z / len + ); +} + +DR_MATHCALL vec3 vec3_cross(vec3 a, vec3 b) +{ + return vec3f( + a.y*b.z - a.z*b.y, + a.z*b.x - a.x*b.z, + a.x*b.y - a.y*b.x + ); +} + + +DR_MATHCALL vec3 vec3_triangle_normal(vec3 p1, vec3 p2, vec3 p3) +{ + vec3 u = vec3_sub(p2, p1); + vec3 v = vec3_sub(p3, p1); + return vec3_normalize(vec3_cross(u, v)); +} + + + +/////////////////////////////////////////////// +// +// VEC2 +// +/////////////////////////////////////////////// + +DR_MATHCALL vec2 vec2f(float x, float y) +{ + vec2 result; + result.x = x; + result.y = y; + + return result; +} +DR_MATHCALL vec2 vec2v(const float* v) +{ + return vec2f(v[0], v[1]); +} +DR_MATHCALL vec2 vec2_zero() +{ + return vec2f(0, 0); +} +DR_MATHCALL vec2 vec2_one() +{ + return vec2f(1, 1); +} + + +DR_MATHCALL vec2 vec2_add(vec2 a, vec2 b) +{ + return vec2f(a.x + b.x, a.y + b.y); +} + +DR_MATHCALL vec2 vec2_sub(vec2 a, vec2 b) +{ + return vec2f(a.x - b.x, a.y - b.y); +} + + +DR_MATHCALL vec2 vec2_mul(vec2 a, vec2 b) +{ + return vec2f(a.x * b.x, a.y * b.y); +} +DR_MATHCALL vec2 vec2_mul_1f(vec2 a, float x) +{ + return vec2f(a.x * x, a.y * x); +} + + +DR_MATHCALL vec2 vec2_div(vec2 a, vec2 b) +{ + return vec2f(a.x / b.x, a.y / b.y); +} + + +DR_MATHCALL float vec2_dot(vec2 a, vec2 b) +{ + return a.x*b.x + a.y*b.y; +} + + +DR_MATHCALL float vec2_length2(vec2 a) +{ + return vec2_dot(a, a); +} + +DR_MATHCALL float vec2_length(vec2 a) +{ + return sqrtf(vec2_length2(a)); +} + + +DR_MATHCALL vec2 vec2_normalize(vec2 a) +{ + float len = vec2_length(a); + + return vec2f( + a.x / len, + a.y / len + ); +} + + +DR_MATHCALL float vec2_angle(vec2 a, vec2 b) +{ + return atanf(a.y / a.x) - atanf(b.y / b.x); +} + +DR_MATHCALL vec2 vec2_rotate(vec2 a, float angleInRadians) +{ + float c = cosf(angleInRadians); + float s = sinf(angleInRadians); + + return vec2f( + a.x*c - a.y*s, + a.x*s + a.y*c + ); +} + + +/////////////////////////////////////////////// +// +// MAT4 +// +/////////////////////////////////////////////// + +DR_MATHCALL mat4 mat4f(vec4 col0, vec4 col1, vec4 col2, vec4 col3) +{ + mat4 result; + result.col[0] = col0; + result.col[1] = col1; + result.col[2] = col2; + result.col[3] = col3; + + return result; +} + +DR_MATHCALL mat4 mat4_identity() +{ + mat4 result; + result.col[0] = vec4f(1, 0, 0, 0); + result.col[1] = vec4f(0, 1, 0, 0); + result.col[2] = vec4f(0, 0, 1, 0); + result.col[3] = vec4f(0, 0, 0, 1); + + return result; +} + +DR_MATHCALL mat4 mat4_ortho(float left, float right, float bottom, float top, float znear, float zfar) +{ + float rml = right - left; + float tmb = top - bottom; + float fmn = zfar - znear; + + float rpl = right + left; + float tpb = top + bottom; + float fpn = zfar + znear; + + mat4 result; + result.col[0] = vec4f(2/rml, 0, 0, 0); + result.col[1] = vec4f(0, 2/tmb, 0, 0); + result.col[2] = vec4f(0, 0, -2/fmn, 0); + result.col[3] = vec4f(-(rpl/rml), -(tpb/tmb), -(fpn/fmn), 1); + + return result; +} + +DR_MATHCALL mat4 mat4_perspective(float fovy, float aspect, float znear, float zfar) +{ + float f = (float)tan(DR_PI/2 - fovy/2); + + mat4 result; + result.col[0] = vec4f(f / aspect, 0, 0, 0); + result.col[1] = vec4f(0, f, 0, 0); + result.col[2] = vec4f(0, 0, (zfar + znear) / (znear - zfar), -1); + result.col[3] = vec4f(0, 0, (2 * zfar * znear) / (znear - zfar), 0); + + return result; +} + +DR_MATHCALL mat4 mat4_vulkan_clip_correction() +{ + mat4 result; + result.col[0] = vec4f(1, 0, 0, 0); + result.col[1] = vec4f(0, -1, 0, 0); + result.col[2] = vec4f(0, 0, 0.5f, 0); + result.col[3] = vec4f(0, 0, 0.5f, 1); + + return result; +} + +DR_MATHCALL mat4 mat4_translate(vec3 translation) +{ + mat4 result; + result.col[0] = vec4f(1, 0, 0, 0); + result.col[1] = vec4f(0, 1, 0, 0); + result.col[2] = vec4f(0, 0, 1, 0); + result.col[3] = vec4f(translation.x, translation.y, translation.z, 1); + + return result; +} + +DR_MATHCALL mat4 mat4_rotate(float angleInRadians, vec3 axis) +{ + float c = cosf(angleInRadians); + float s = sinf(angleInRadians); + + float x = axis.x; + float y = axis.y; + float z = axis.z; + + float xx = x*x; + float xy = x*y; + float xz = x*z; + float yy = y*y; + float yz = y*z; + float zz = z*z; + + float xs = x*s; + float ys = y*s; + float zs = z*s; + + mat4 result; + result.col[0] = vec4f(xx * (1 - c) + c, xy * (1 - c) - zs, xz * (1 - c) + ys, 0); + result.col[1] = vec4f(xy * (1 - c) + zs, yy * (1 - c) + c, yz * (1 - c) - xs, 0); + result.col[2] = vec4f(xz * (1 - c) - ys, yz * (1 - c) + xs, zz * (1 - c) + c, 0); + result.col[3] = vec4f(0, 0, 0, 1); + + return result; +} + +DR_MATHCALL mat4 mat4_scale(vec3 scale) +{ + mat4 result; + result.col[0] = vec4f(scale.x, 0, 0, 0); + result.col[1] = vec4f(0, scale.y, 0, 0); + result.col[2] = vec4f(0, 0, scale.z, 0); + result.col[3] = vec4f(0, 0, 0, 1); + + return result; +} + + +DR_MATHCALL mat4 mat4_mul(mat4 a, mat4 b) +{ + const vec4 a0 = a.col[0]; + const vec4 a1 = a.col[1]; + const vec4 a2 = a.col[2]; + const vec4 a3 = a.col[3]; + + const vec4 b0 = b.col[0]; + const vec4 b1 = b.col[1]; + const vec4 b2 = b.col[2]; + const vec4 b3 = b.col[3]; + + mat4 result; + result.col[0] = vec4f( + a0.x*b0.x + a1.x*b0.y + a2.x*b0.z + a3.x*b0.w, + a0.y*b0.x + a1.y*b0.y + a2.y*b0.z + a3.y*b0.w, + a0.z*b0.x + a1.z*b0.y + a2.z*b0.z + a3.z*b0.w, + a0.w*b0.x + a1.w*b0.y + a2.w*b0.z + a3.w*b0.w + ); + + result.col[1] = vec4f( + a0.x*b1.x + a1.x*b1.y + a2.x*b1.z + a3.x*b1.w, + a0.y*b1.x + a1.y*b1.y + a2.y*b1.z + a3.y*b1.w, + a0.z*b1.x + a1.z*b1.y + a2.z*b1.z + a3.z*b1.w, + a0.w*b1.x + a1.w*b1.y + a2.w*b1.z + a3.w*b1.w + ); + + result.col[2] = vec4f( + a0.x*b2.x + a1.x*b2.y + a2.x*b2.z + a3.x*b2.w, + a0.y*b2.x + a1.y*b2.y + a2.y*b2.z + a3.y*b2.w, + a0.z*b2.x + a1.z*b2.y + a2.z*b2.z + a3.z*b2.w, + a0.w*b2.x + a1.w*b2.y + a2.w*b2.z + a3.w*b2.w + ); + + result.col[3] = vec4f( + a0.x*b3.x + a1.x*b3.y + a2.x*b3.z + a3.x*b3.w, + a0.y*b3.x + a1.y*b3.y + a2.y*b3.z + a3.y*b3.w, + a0.z*b3.x + a1.z*b3.y + a2.z*b3.z + a3.z*b3.w, + a0.w*b3.x + a1.w*b3.y + a2.w*b3.z + a3.w*b3.w + ); + + return result; +} + +DR_MATHCALL vec4 mat4_mul_vec4(mat4 m, vec4 v) +{ + const vec4 m0 = m.col[0]; + const vec4 m1 = m.col[1]; + const vec4 m2 = m.col[2]; + const vec4 m3 = m.col[3]; + + return vec4f( + m0.x*v.x + m1.x*v.y + m2.x*v.z + m3.x*v.w, + m0.y*v.x + m1.y*v.y + m2.y*v.z + m3.y*v.w, + m0.z*v.x + m1.z*v.y + m2.z*v.z + m3.z*v.w, + m0.w*v.x + m1.w*v.y + m2.w*v.z + m3.w*v.w + ); +} + + +/////////////////////////////////////////////// +// +// QUAT +// +/////////////////////////////////////////////// + +DR_MATHCALL quat quatf(float x, float y, float z, float w) +{ + quat result; + result.x = x; + result.y = y; + result.z = z; + result.w = w; + + return result; +} +DR_MATHCALL quat quatv(const float* v) +{ + return quatf(v[0], v[1], v[2], v[3]); +} + +DR_MATHCALL quat quat_identity() +{ + return quatf(0, 0, 0, 1); +} + + + + + +/////////////////////////////////////////////// +// +// TRANSFORM +// +/////////////////////////////////////////////// + +typedef struct +{ + vec3 position; + quat rotation; + vec3 scale; +}transform_t; + +DR_MATHCALL transform_t transform_init(vec3 position, quat rotation, vec3 scale) +{ + transform_t result; + result.position = position; + result.rotation = rotation; + result.scale = scale; + + return result; +} + +DR_MATHCALL transform_t transform_identity() +{ + transform_t result; + result.position = vec3_zero(); + result.rotation = quat_identity(); + result.scale = vec3_one(); + + return result; +} + + +DR_MATHCALL transform_t transform_translate(transform_t transform, vec3 offset) +{ + transform_t result = transform; + result.position = vec3_add(transform.position, offset); + + return result; +} + + + + +/////////////////////////////////////////////// +// +// SSE IMPLEMENTATION +// +/////////////////////////////////////////////// + +// Not supporting SSE on x86/MSVC due to pass-by-value errors with aligned types. +#if (defined(_MSC_VER) && defined(_M_X64)) || defined(__SSE2__) +#define SUPPORTS_SSE +#endif + +#if !defined(DISABLE_SSE) && defined(SUPPORTS_SSE) +#define ENABLE_SSE +#endif + +#ifdef ENABLE_SSE +#if defined(__MINGW32__) +#include +#endif +#include +#endif + + + + + +#ifdef __cplusplus +} +#endif + +#endif //dr_math_h + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ diff --git a/source/engine/thirdparty/dr_libs/old/dr_mtl.h b/source/engine/thirdparty/dr_libs/old/dr_mtl.h new file mode 100644 index 0000000..cf97a98 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/old/dr_mtl.h @@ -0,0 +1,4512 @@ +// Public Domain. See "unlicense" statement at the end of this file. + +// ABOUT +// +// dr_mtl is a library for loading materials for use in 2D or 3D graphics. But it's a bit different to +// what you may expect. It does not reprsent materials as a static data, but rather as a series of +// instructions that interface with a set of inputs. Indeed, this library is more like a compiler - you +// specify an input file (such as a Wavefront MTL file), compile it into an intermediate bytecode +// represenation, and then run it through a code generator to generate shader code such as GLSL, HLSL, +// Spir-V, etc. +// +// +// +// USAGE +// +// This is a single-file library. To use it, do something like the following in one .c file. +// #define DR_MTL_IMPLEMENTATION +// #include "dr_mtl.h" +// +// You can then #include dr_mtl.h in other parts of the program as you would with any other header file. +// +// +// +// OPTIONS +// +// #define DRMTL_NO_MTL_COMPILER +// Disables the Wavefront MTL compiler. +// +// #define DRMTL_NO_GLSL_CODEGEN +// Disables the GLSL code generator. +// +// +// +// TODO +// - Add more documentation. +// - Add a demo to demonstrate the awesomeness of this library. +// - Add trigonometric instructions. + +#ifndef dr_mtl_h +#define dr_mtl_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4201) // Non-standard extension used: nameless struct/union. +#endif + + +#define DRMTL_MAGIC_NUMBER 0x81DF7405 +#define DRMTL_CURRENT_VERSION 1 + +#define DRMTL_INPUT_DESC_CONSTI ((unsigned char)-2) +#define DRMTL_INPUT_DESC_CONSTF ((unsigned char)-1) +#define DRMTL_INPUT_DESC_VARX ((unsigned char)0) +#define DRMTL_INPUT_DESC_VARY ((unsigned char)1) +#define DRMTL_INPUT_DESC_VARZ ((unsigned char)2) +#define DRMTL_INPUT_DESC_VARW ((unsigned char)3) + +#define DRMTL_MAX_IDENTIFIER_NAME 28 +#define DRMTL_MAX_CHANNEL_NAME 28 +#define DRMTL_MAX_PROPERTY_NAME 28 + +#define DRMTL_MAX_INPUT_PATH 252 +#define DRMTL_MAX_PROPERTY_PATH 224 + + +typedef unsigned char drmtl_uint8; +typedef unsigned int drmtl_uint32; + +typedef struct drmtl_material drmtl_material; + + +/// The various data type available to the material. +typedef enum +{ + drmtl_type_float = 1, + drmtl_type_float2 = 2, + drmtl_type_float3 = 3, + drmtl_type_float4 = 4, + drmtl_type_int = 5, + drmtl_type_int2 = 6, + drmtl_type_int3 = 7, + drmtl_type_int4 = 8, + drmtl_type_tex1d = 9, + drmtl_type_tex2d = 10, + drmtl_type_tex3d = 11, + drmtl_type_texcube = 12, + drmtl_type_bool = 13 + +} drmtl_type; + +/// The various run-time opcodes. +typedef enum +{ + ////////////////////////// + // Assignment + + // mov + drmtl_opcode_movf1 = 0x00000001, + drmtl_opcode_movf2 = 0x00000002, + drmtl_opcode_movf3 = 0x00000003, + drmtl_opcode_movf4 = 0x00000004, + drmtl_opcode_movi1 = 0x00000005, + drmtl_opcode_movi2 = 0x00000006, + drmtl_opcode_movi3 = 0x00000007, + drmtl_opcode_movi4 = 0x00000008, + + + ////////////////////////// + // Arithmetic + + // add + drmtl_opcode_addf1 = 0x00001001, + drmtl_opcode_addf2 = 0x00001002, + drmtl_opcode_addf3 = 0x00001003, + drmtl_opcode_addf4 = 0x00001004, + drmtl_opcode_addi1 = 0x00001005, + drmtl_opcode_addi2 = 0x00001006, + drmtl_opcode_addi3 = 0x00001007, + drmtl_opcode_addi4 = 0x00001008, + + // sub + drmtl_opcode_subf1 = 0x00001101, + drmtl_opcode_subf2 = 0x00001102, + drmtl_opcode_subf3 = 0x00001103, + drmtl_opcode_subf4 = 0x00001104, + drmtl_opcode_subi1 = 0x00001105, + drmtl_opcode_subi2 = 0x00001106, + drmtl_opcode_subi3 = 0x00001107, + drmtl_opcode_subi4 = 0x00001108, + + // mul + drmtl_opcode_mulf1 = 0x00001201, + drmtl_opcode_mulf2 = 0x00001202, + drmtl_opcode_mulf3 = 0x00001203, + drmtl_opcode_mulf4 = 0x00001204, + drmtl_opcode_muli1 = 0x00001205, + drmtl_opcode_muli2 = 0x00001206, + drmtl_opcode_muli3 = 0x00001207, + drmtl_opcode_muli4 = 0x00001208, + + // div + drmtl_opcode_divf1 = 0x00001301, + drmtl_opcode_divf2 = 0x00001302, + drmtl_opcode_divf3 = 0x00001303, + drmtl_opcode_divf4 = 0x00001304, + drmtl_opcode_divi1 = 0x00001305, + drmtl_opcode_divi2 = 0x00001306, + drmtl_opcode_divi3 = 0x00001307, + drmtl_opcode_divi4 = 0x00001308, + + // pow + drmtl_opcode_powf1 = 0x00001401, + drmtl_opcode_powf2 = 0x00001402, + drmtl_opcode_powf3 = 0x00001403, + drmtl_opcode_powf4 = 0x00001404, + drmtl_opcode_powi1 = 0x00001405, + drmtl_opcode_powi2 = 0x00001406, + drmtl_opcode_powi3 = 0x00001407, + drmtl_opcode_powi4 = 0x00001408, + + + ////////////////////////// + // Textures + + // tex + drmtl_opcode_tex1 = 0x00002001, + drmtl_opcode_tex2 = 0x00002002, + drmtl_opcode_tex3 = 0x00002003, + drmtl_opcode_texcube = 0x00002004, + + + ////////////////////////// + // Miscellaneous + + // var + drmtl_opcode_var = 0x00003000, + + // ret + drmtl_opcode_retf1 = 0x00003001, + drmtl_opcode_retf2 = 0x00003002, + drmtl_opcode_retf3 = 0x00003003, + drmtl_opcode_retf4 = 0x00003004, + drmtl_opcode_reti1 = 0x00003005, + drmtl_opcode_reti2 = 0x00003006, + drmtl_opcode_reti3 = 0x00003007, + drmtl_opcode_reti4 = 0x00003008 + +} drmtl_opcode; + + +/// Structure containing information about an identifier. An identifier contains a type (float, float2, etc.) and a name. The +/// total size of this structure is 32 bytes. The name is null terminated. +typedef struct +{ + /// The type of the identifier. + drmtl_type type; + + /// The name of the identifier. + char name[DRMTL_MAX_IDENTIFIER_NAME]; + +} drmtl_identifier; + + +/// Structure containing information about an input variable. +typedef struct +{ + /// The index into the identifier table that this input variable is identified by. + unsigned int identifierIndex; + + /// The default value of the input variable. + union + { + struct + { + float x; + } f1; + struct + { + float x; + float y; + } f2; + struct + { + float x; + float y; + float z; + } f3; + struct + { + float x; + float y; + float z; + float w; + } f4; + + struct + { + int x; + } i1; + struct + { + int x; + int y; + } i2; + struct + { + int x; + int y; + int z; + } i3; + struct + { + int x; + int y; + int z; + int w; + } i4; + + struct + { + char value[DRMTL_MAX_INPUT_PATH]; // Enough room for a path, but less to keep the total size of the structure at 256 bytes. Null terminated. + } path; + + + struct + { + drmtl_uint32 x; + drmtl_uint32 y; + drmtl_uint32 z; + drmtl_uint32 w; + } raw4; + }; + +} drmtl_input; + + +typedef struct +{ + /// The return type of the channel. + drmtl_type type; + + /// The name of the channel. Null terminated. + char name[DRMTL_MAX_CHANNEL_NAME]; + +} drmtl_channel; + + +/// An instruction input value. An input value to an instruction can usually be a constant or the identifier index of the +/// applicable variable. +typedef union +{ + /// The identifier index of the applicable variable. + unsigned int id; + + /// The constant value, as a float. + float valuef; + + /// The constant value, as an integer. + int valuei; + +}drmtl_instruction_input; + + +/// Structure used for describing an instructions input data. +typedef struct +{ + unsigned char x; + unsigned char y; + unsigned char z; + unsigned char w; + +} drmtl_instruction_input_descriptor; + +/// Structure containing information about an instruction. +typedef struct +{ + /// The instruction's opcode. + drmtl_opcode opcode; + + /// The instruction's data. + union + { + // mov data. + struct + { + drmtl_instruction_input_descriptor inputDesc; + drmtl_instruction_input inputX; + drmtl_instruction_input inputY; + drmtl_instruction_input inputZ; + drmtl_instruction_input inputW; + unsigned int output; + } mov; + + + // add data. + struct + { + drmtl_instruction_input_descriptor inputDesc; + drmtl_instruction_input inputX; + drmtl_instruction_input inputY; + drmtl_instruction_input inputZ; + drmtl_instruction_input inputW; + unsigned int output; + } add; + + // sub data. + struct + { + drmtl_instruction_input_descriptor inputDesc; + drmtl_instruction_input inputX; + drmtl_instruction_input inputY; + drmtl_instruction_input inputZ; + drmtl_instruction_input inputW; + unsigned int output; + } sub; + + // mul data. + struct + { + drmtl_instruction_input_descriptor inputDesc; + drmtl_instruction_input inputX; + drmtl_instruction_input inputY; + drmtl_instruction_input inputZ; + drmtl_instruction_input inputW; + unsigned int output; + } mul; + + // div data. + struct + { + drmtl_instruction_input_descriptor inputDesc; + drmtl_instruction_input inputX; + drmtl_instruction_input inputY; + drmtl_instruction_input inputZ; + drmtl_instruction_input inputW; + unsigned int output; + } div; + + // pow data. + struct + { + drmtl_instruction_input_descriptor inputDesc; + drmtl_instruction_input inputX; + drmtl_instruction_input inputY; + drmtl_instruction_input inputZ; + drmtl_instruction_input inputW; + unsigned int output; + } pow; + + + // tex data. + struct + { + drmtl_instruction_input_descriptor inputDesc; + drmtl_instruction_input inputX; + drmtl_instruction_input inputY; + drmtl_instruction_input inputZ; + drmtl_instruction_input inputW; + unsigned int texture; + unsigned int output; + } tex; + + + // ret data. + struct + { + drmtl_instruction_input_descriptor inputDesc; + drmtl_instruction_input inputX; + drmtl_instruction_input inputY; + drmtl_instruction_input inputZ; + drmtl_instruction_input inputW; + } ret; + + + // var data + struct + { + //drmtl_type type; + unsigned int identifierIndex; + } var; + + + // Ensures the size of the instruction is always 32 bytes. + struct + { + drmtl_uint8 _unused[32]; + } unused; + }; + +} drmtl_instruction; + + +typedef struct +{ + /// The type of the property. + drmtl_type type; + + /// The name of the property. + char name[DRMTL_MAX_PROPERTY_NAME]; + + /// The default value of the input variable. + union + { + struct + { + float x; + } f1; + struct + { + float x; + float y; + } f2; + struct + { + float x; + float y; + float z; + } f3; + struct + { + float x; + float y; + float z; + float w; + } f4; + + struct + { + int x; + } i1; + struct + { + int x; + int y; + } i2; + struct + { + int x; + int y; + int z; + } i3; + struct + { + int x; + int y; + int z; + int w; + } i4; + + struct + { + char value[DRMTL_MAX_PROPERTY_PATH]; // Enough room for a path, but less to keep the total size of the structure at 256 bytes. Null terminated. + } path; + + struct + { + int x; + } b1; + }; + +} drmtl_property; + + +/// Structure containing the header information of the material. +typedef struct +{ + /// The magic number: 0x81DF7405 + unsigned int magic; + + /// The version number. There is currently only a single version - version 1. In the future there may be other versions + /// which will affect how the file is formatted. + unsigned int version; + + + /// The size in bytes of an identifier. + unsigned int identifierSizeInBytes; + + /// The size in bytes of an input variable. + unsigned int inputSizeInBytes; + + /// The size of a channel header, in bytes. + unsigned int channelHeaderSizeInBytes; + + /// The size in bytes of an instruction. + unsigned int instructionSizeInBytes; + + /// The size in bytes of a property. + unsigned int propertySizeInBytes; + + + /// The total number of identifiers. + unsigned int identifierCount; + + /// The total number of private input variables. + unsigned int privateInputCount; + + /// The total number of public input variables. + unsigned int publicInputCount; + + /// The total number of channels. + unsigned int channelCount; + + /// The total number of properties. + unsigned int propertyCount; + + + /// The offset of the identifiers. + unsigned int identifiersOffset; + + /// The offset of the input variables. + unsigned int inputsOffset; + + /// The offset of the channels. + unsigned int channelsOffset; + + /// The offset of the properties. + unsigned int propertiesOffset; + + +} drmtl_header; + +typedef struct +{ + /// The channel information. + drmtl_channel channel; + + /// The instruction count of the channel. + unsigned int instructionCount; + +} drmtl_channel_header; + + +/// Structure containing the definition of the material. +struct drmtl_material +{ + /// A pointer to the raw data. This will at least be the size of drmtl_header (128 bytes, as of version 1). + drmtl_uint8* pRawData; + + /// The size of the data, in bytes. + unsigned int sizeInBytes; + + /// The size of the buffer, in bytes. This is used to determine when the buffer needs to be inflated. + unsigned int bufferSizeInBytes; + + + /// The current stage of the construction process of the material. A material must be constructed in order, so this + /// keeps track of the current stage to ensure the proper errors are returned. + unsigned int currentStage; + + /// The offset of the current channel. This is needed to we can determine which bytes need to be updated as + /// instructions are added to the channel. + unsigned int currentChannelOffset; + + + /// Whether or not the material data is owned by this library. When this is set to false, the library will + /// never modify the original pointer. + bool ownsRawData; +}; + + + +//////////////////////////////////////////////////////// +// Low-Level APIs +// +// Use these APIs to work on materials directly. Note that these are intentionally restrictive in that things can +// be added to the material, however they can never be removed. In addition, everything must be added in order which +// means identifiers need to be added first, followed by private inputs, followed by public inputs, followed by +// channels, followed by properties. +// +// Use these to construct a material after everything has been processed at a higher level. + +/// Initializes the given material. +/// +/// @param pMaterial [in] A pointer to the material to initialize. +/// +/// @return True if the material is initialized successfully; false otherwise. +bool drmtl_init(drmtl_material* pMaterial); +bool drmtl_initfromexisting(drmtl_material* pMaterial, const void* pRawData, unsigned int dataSizeInBytes); +bool drmtl_initfromexisting_nocopy(drmtl_material* pMaterial, const void* pRawData, unsigned int dataSizeInBytes); + +/// Uninitializes the given material. +/// +/// @param pMaterial [in] A pointer to the material to uninitialize. +void drmtl_uninit(drmtl_material* pMaterial); + + +/// Retrieve a pointer to the header information. +drmtl_header* drmtl_getheader(drmtl_material* pMaterial); + + +/// Appends an identifier to the end of the identifier list. Use drmtl_getidentifiercount() to determine it's index. +/// +/// @param pMaterial [in] A pointer to the material to append the identifier to. +bool drmtl_appendidentifier(drmtl_material* pMaterial, drmtl_identifier identifier, unsigned int* indexOut); + +/// Appends a private input variable. +bool drmtl_appendprivateinput(drmtl_material* pMaterial, drmtl_input input); + +/// Appends a public input variable. +bool drmtl_appendpublicinput(drmtl_material* pMaterial, drmtl_input input); + +/// Begins a new channel. +/// +/// @remarks +/// Any instructions that are appended from now on will be part of this channel until another channel is begun. +/// @par +/// The end of the channel is marked when a new channel is appended or a property begins. +bool drmtl_appendchannel(drmtl_material* pMaterial, drmtl_channel channelHeader); + +/// Appends an instruction to the most recently appended channel. +bool drmtl_appendinstruction(drmtl_material* pMaterial, drmtl_instruction instruction); + +/// Append a property. +bool drmtl_appendproperty(drmtl_material* pMaterial, drmtl_property prop); + + +/// Retrieves a pointer to the channel header by it's index. +/// +/// @remarks +/// This runs in linear time. +drmtl_channel_header* drmtl_getchannelheaderbyindex(drmtl_material* pMaterial, unsigned int channelIndex); + +/// Retrieves a pointer to the channel header by it's name. +/// +/// @remarks +/// This runs in linear time. +drmtl_channel_header* drmtl_getchannelheaderbyname(drmtl_material* pMaterial, const char* channelName); + + +/// Retrieves a pointer to the buffer containing the list of identifiers. +drmtl_identifier* drmtl_getidentifiers(drmtl_material* pMaterial); +drmtl_identifier* drmtl_getidentifier(drmtl_material* pMaterial, unsigned int index); + +/// Retrieves the number of identifiers defined by the given material. +unsigned int drmtl_getidentifiercount(drmtl_material* pMaterial); + + +/// Retrieves the number of private + public input variables. +unsigned int drmtl_getinputcount(drmtl_material* pMaterial); + +/// Retrieves the input variable by it's index. +drmtl_input* drmtl_getinputbyindex(drmtl_material* pMaterial, unsigned int index); + +/// Retrieves the number of private input variables. +unsigned int drmtl_getprivateinputcount(drmtl_material* pMaterial); + +/// Retrieves the private input variable by it's index. +drmtl_input* drmtl_getprivateinputbyindex(drmtl_material* pMaterial, unsigned int index); + +/// Retrieves the number of public input variables. +unsigned int drmtl_getpublicinputcount(drmtl_material* pMaterial); + +/// Retrieves the public input variable by it's index. +drmtl_input* drmtl_getpublicinputbyindex(drmtl_material* pMaterial, unsigned int index); + + +/// Retrieves the number of properties. +unsigned int drmtl_getpropertycount(drmtl_material* pMaterial); + +/// Retrieves a property by it's index. +drmtl_property* drmtl_getpropertybyindex(drmtl_material* pMaterial, unsigned int index); + +/// Retrieves a properties by it's name. +/// +/// @remarks +/// This is case-sensitive. +drmtl_property* drmtl_getpropertybyname(drmtl_material* pMaterial, const char* name); + + + +//////////////////////////////////////////////////////// +// Mid-Level APIs + +/// Helper for creating an identifier. +drmtl_identifier drmtl_identifier_float(const char* name); +drmtl_identifier drmtl_identifier_float2(const char* name); +drmtl_identifier drmtl_identifier_float3(const char* name); +drmtl_identifier drmtl_identifier_float4(const char* name); +drmtl_identifier drmtl_identifier_int(const char* name); +drmtl_identifier drmtl_identifier_int2(const char* name); +drmtl_identifier drmtl_identifier_int3(const char* name); +drmtl_identifier drmtl_identifier_int4(const char* name); +drmtl_identifier drmtl_identifier_tex2d(const char* name); + +/// Helper for creating an input variable. +drmtl_input drmtl_input_float(unsigned int identifierIndex, float x); +drmtl_input drmtl_input_float2(unsigned int identifierIndex, float x, float y); +drmtl_input drmtl_input_float3(unsigned int identifierIndex, float x, float y, float z); +drmtl_input drmtl_input_float4(unsigned int identifierIndex, float x, float y, float z, float w); +drmtl_input drmtl_input_int(unsigned int identifierIndex, int x); +drmtl_input drmtl_input_int2(unsigned int identifierIndex, int x, int y); +drmtl_input drmtl_input_int3(unsigned int identifierIndex, int x, int y, int z); +drmtl_input drmtl_input_int4(unsigned int identifierIndex, int x, int y, int z, int w); +drmtl_input drmtl_input_tex(unsigned int identifierIndex, const char* path); + +/// Helper for creating a channel. +drmtl_channel drmtl_channel_float(const char* name); +drmtl_channel drmtl_channel_float2(const char* name); +drmtl_channel drmtl_channel_float3(const char* name); +drmtl_channel drmtl_channel_float4(const char* name); +drmtl_channel drmtl_channel_int(const char* name); +drmtl_channel drmtl_channel_int2(const char* name); +drmtl_channel drmtl_channel_int3(const char* name); +drmtl_channel drmtl_channel_int4(const char* name); + +/// Helper for creating an instruction. These are heavily simplified and more complex setups are possible using lower level APIs. +drmtl_instruction drmtl_movf1_v1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_movf1_c1(unsigned int outputIdentifierIndex, float x); +drmtl_instruction drmtl_movf2_v2(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_movf2_c2(unsigned int outputIdentifierIndex, float x, float y); +drmtl_instruction drmtl_movf3_v3(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_movf3_c3(unsigned int outputIdentifierIndex, float x, float y, float z); +drmtl_instruction drmtl_movf4_v4(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_movf4_c4(unsigned int outputIdentifierIndex, float x, float y, float z, float w); + +drmtl_instruction drmtl_addf1_v1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_addf1_c1(unsigned int outputIdentifierIndex, float x); +drmtl_instruction drmtl_addf2_v2(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_addf2_c2(unsigned int outputIdentifierIndex, float x, float y); +drmtl_instruction drmtl_addf3_v3(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_addf3_c3(unsigned int outputIdentifierIndex, float x, float y, float z); +drmtl_instruction drmtl_addf4_v4(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_addf4_c4(unsigned int outputIdentifierIndex, float x, float y, float z, float w); + +drmtl_instruction drmtl_subf1_v1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_subf1_c1(unsigned int outputIdentifierIndex, float x); +drmtl_instruction drmtl_subf2_v2(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_subf2_c2(unsigned int outputIdentifierIndex, float x, float y); +drmtl_instruction drmtl_subf3_v3(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_subf3_c3(unsigned int outputIdentifierIndex, float x, float y, float z); +drmtl_instruction drmtl_subf4_v4(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_subf4_c4(unsigned int outputIdentifierIndex, float x, float y, float z, float w); + +drmtl_instruction drmtl_mulf1_v1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_mulf1_c1(unsigned int outputIdentifierIndex, float x); +drmtl_instruction drmtl_mulf2_v2(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_mulf2_c2(unsigned int outputIdentifierIndex, float x, float y); +drmtl_instruction drmtl_mulf3_v3(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_mulf3_c3(unsigned int outputIdentifierIndex, float x, float y, float z); +drmtl_instruction drmtl_mulf4_v4(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_mulf4_c4(unsigned int outputIdentifierIndex, float x, float y, float z, float w); +drmtl_instruction drmtl_mulf4_v3v1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndexXYZ, unsigned int inputIdentifierIndexW); +drmtl_instruction drmtl_mulf4_v3c1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex, float w); +drmtl_instruction drmtl_mulf4_v2c2(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex, float z, float w); +drmtl_instruction drmtl_mulf4_v1c3(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex, float y, float z, float w); +drmtl_instruction drmtl_mulf4_v1v1v1v1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndexX, unsigned int inputIdentifierIndexY, unsigned int inputIdentifierIndexZ, unsigned int inputIdentifierIndexW); + +drmtl_instruction drmtl_divf1_v1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_divf1_c1(unsigned int outputIdentifierIndex, float x); +drmtl_instruction drmtl_divf2_v2(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_divf2_c2(unsigned int outputIdentifierIndex, float x, float y); +drmtl_instruction drmtl_divf3_v3(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_divf3_c3(unsigned int outputIdentifierIndex, float x, float y, float z); +drmtl_instruction drmtl_divf4_v4(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex); +drmtl_instruction drmtl_divf4_c4(unsigned int outputIdentifierIndex, float x, float y, float z, float w); + +drmtl_instruction drmtl_tex2(unsigned int outputIdentifierIndex, unsigned int textureIdentifierIndex, unsigned int texcoordIdentifierIndex); +drmtl_instruction drmtl_var(unsigned int identifierIndex); +drmtl_instruction drmtl_retf1(unsigned int identifierIndex); +drmtl_instruction drmtl_retf2(unsigned int identifierIndex); +drmtl_instruction drmtl_retf3(unsigned int identifierIndex); +drmtl_instruction drmtl_retf4(unsigned int identifierIndex); +drmtl_instruction drmtl_retf1_c1(float x); +drmtl_instruction drmtl_retf2_c2(float x, float y); +drmtl_instruction drmtl_retf3_c3(float x, float y, float z); +drmtl_instruction drmtl_retf4_c4(float x, float y, float z, float w); +drmtl_instruction drmtl_reti1(unsigned int identifierIndex); +drmtl_instruction drmtl_reti2(unsigned int identifierIndex); +drmtl_instruction drmtl_reti3(unsigned int identifierIndex); +drmtl_instruction drmtl_reti4(unsigned int identifierIndex); +drmtl_instruction drmtl_reti1_c1(int x); +drmtl_instruction drmtl_reti2_c2(int x, int y); +drmtl_instruction drmtl_reti3_c3(int x, int y, int z); +drmtl_instruction drmtl_reti4_c4(int x, int y, int z, int w); + +/// Helper for creating a property. +drmtl_property drmtl_property_float(const char* name, float x); +drmtl_property drmtl_property_float2(const char* name, float x, float y); +drmtl_property drmtl_property_float3(const char* name, float x, float y, float z); +drmtl_property drmtl_property_float4(const char* name, float x, float y, float z, float w); +drmtl_property drmtl_property_int(const char* name, int x); +drmtl_property drmtl_property_int2(const char* name, int x, int y); +drmtl_property drmtl_property_int3(const char* name, int x, int y, int z); +drmtl_property drmtl_property_int4(const char* name, int x, int y, int z, int w); +drmtl_property drmtl_property_bool(const char* name, bool value); + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// Compilers +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef DRMTL_NO_MTL_COMPILER +/// Compiles a Wavefront MTL file. +/// +/// @param pMaterial [in] A pointer to the destination material. +/// +/// @remarks +/// This will compile the material at the first occurance of the "newmtl" statement, and will end at either the next +/// occurance of "newmtl" of when the input buffer has been exhausted. +/// @par +/// This will initialize the material, so ensure that you have not already initialized it before calling this. If this +/// returns successfully, call drmtl_uninit() to uninitialize the material. +/// @par +/// MTL files require texture coordinates in order to know how to select the appropriate sample from textures. The +/// of the variable to use is specified in "texcoordInputName", and assumed to have at least 2 components (x and y). +bool drmtl_compile_wavefront_mtl(drmtl_material* pMaterial, const char* mtlData, size_t mtlDataSizeInBytes, const char* texcoordInputName); +#endif + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// Code Generators +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef DRMTL_NO_GLSL_CODEGEN +/// Generates GLSL code for the channel with the given name. +bool drmtl_codegen_glsl_channel(drmtl_material* pMaterial, const char* channelName, char* codeOut, size_t codeOutSizeInBytes, size_t* pBytesWrittenOut); + +/// Generates GLSL code for the uniform variables as defined by the material's public input variables. +bool drmtl_codegen_glsl_uniforms(drmtl_material* pMaterial, char* codeOut, size_t codeOutSizeInBytes, size_t* pBytesWritteOut); +#endif + + + + + +#if defined(_MSC_VER) + #pragma warning(pop) +#endif + +#ifdef __cplusplus +} +#endif + +#endif //dr_mtl_h + + +/////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef DR_MTL_IMPLEMENTATION +#include +#include +#include +#include +#include + +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-align" +#endif + + +// When constructing the material's raw data, memory is allocated in blocks of this amount. This must be at least 256. +#define DRMTL_CHUNK_SIZE 4096 + +#define DRMTL_STAGE_IDS 0 +#define DRMTL_STAGE_PRIVATE_INPUTS 1 +#define DRMTL_STAGE_PUBLIC_INPUTS 2 +#define DRMTL_STAGE_CHANNELS 3 +#define DRMTL_STAGE_PROPERTIES 4 +#define DRMTL_STAGE_COMPLETE UINT_MAX + + +//////////////////////////////////////////////////////// +// Utilities + +// strcpy() +static int drmtl_strcpy(char* dst, size_t dstSizeInBytes, const char* src) +{ +#if defined(_MSC_VER) + return strcpy_s(dst, dstSizeInBytes, src); +#else + if (dst == 0) { + return EINVAL; + } + if (dstSizeInBytes == 0) { + return ERANGE; + } + if (src == 0) { + dst[0] = '\0'; + return EINVAL; + } + + char* iDst = dst; + const char* iSrc = src; + size_t remainingSizeInBytes = dstSizeInBytes; + while (remainingSizeInBytes > 0 && iSrc[0] != '\0') + { + iDst[0] = iSrc[0]; + + iDst += 1; + iSrc += 1; + remainingSizeInBytes -= 1; + } + + if (remainingSizeInBytes > 0) { + iDst[0] = '\0'; + } else { + dst[0] = '\0'; + return ERANGE; + } + + return 0; +#endif +} + + + +/// Inflates the materials data buffer by DRMTL_CHUNK_SIZE. +bool _drmtl_inflate(drmtl_material* pMaterial); + + +bool drmtl_init(drmtl_material* pMaterial) +{ + if (pMaterial != NULL) + { + assert(DRMTL_CHUNK_SIZE >= 256); + + pMaterial->pRawData = (drmtl_uint8*)malloc(DRMTL_CHUNK_SIZE); + if (pMaterial->pRawData != NULL) + { + pMaterial->sizeInBytes = sizeof(drmtl_header); + pMaterial->bufferSizeInBytes = DRMTL_CHUNK_SIZE; + pMaterial->currentStage = DRMTL_STAGE_IDS; + pMaterial->currentChannelOffset = 0; + pMaterial->ownsRawData = 1; + + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + pHeader->magic = DRMTL_MAGIC_NUMBER; + pHeader->version = DRMTL_CURRENT_VERSION; + pHeader->identifierSizeInBytes = sizeof(drmtl_identifier); + pHeader->inputSizeInBytes = sizeof(drmtl_input); + pHeader->channelHeaderSizeInBytes = sizeof(drmtl_channel_header); + pHeader->instructionSizeInBytes = sizeof(drmtl_instruction); + pHeader->propertySizeInBytes = sizeof(drmtl_property); + pHeader->identifierCount = 0; + pHeader->privateInputCount = 0; + pHeader->publicInputCount = 0; + pHeader->channelCount = 0; + pHeader->propertyCount = 0; + pHeader->identifiersOffset = pMaterial->sizeInBytes; + pHeader->inputsOffset = pMaterial->sizeInBytes; + pHeader->channelsOffset = pMaterial->sizeInBytes; + pHeader->propertiesOffset = pMaterial->sizeInBytes; + + return 1; + } + } + + return 0; +} + +bool drmtl_initfromexisting(drmtl_material* pMaterial, const void* pRawData, unsigned int dataSizeInBytes) +{ + if (pMaterial != NULL) + { + if (pRawData != NULL && dataSizeInBytes >= sizeof(drmtl_header)) + { + if (((drmtl_header*)pMaterial->pRawData)->magic == DRMTL_MAGIC_NUMBER) + { + pMaterial->pRawData = (drmtl_uint8*)malloc(DRMTL_CHUNK_SIZE); + if (pMaterial->pRawData != NULL) + { + memcpy(pMaterial->pRawData, pRawData, dataSizeInBytes); + pMaterial->sizeInBytes = dataSizeInBytes; + pMaterial->bufferSizeInBytes = dataSizeInBytes; + pMaterial->currentStage = DRMTL_STAGE_COMPLETE; + pMaterial->currentChannelOffset = 0; + pMaterial->ownsRawData = 1; + + return 1; + } + } + } + } + + return 0; +} + +bool drmtl_initfromexisting_nocopy(drmtl_material* pMaterial, const void* pRawData, unsigned int dataSizeInBytes) +{ + if (pMaterial != NULL) + { + if (pRawData != NULL && dataSizeInBytes >= sizeof(drmtl_header)) + { + if (((const drmtl_header*)pRawData)->magic == DRMTL_MAGIC_NUMBER) + { + pMaterial->pRawData = (drmtl_uint8*)pRawData; + pMaterial->sizeInBytes = dataSizeInBytes; + pMaterial->bufferSizeInBytes = dataSizeInBytes; + pMaterial->currentStage = DRMTL_STAGE_COMPLETE; + pMaterial->currentChannelOffset = 0; + pMaterial->ownsRawData = 0; + + return 1; + } + } + } + + return 0; +} + +void drmtl_uninit(drmtl_material* pMaterial) +{ + if (pMaterial != NULL) + { + if (pMaterial->ownsRawData) + { + free(pMaterial->pRawData); + } + + pMaterial->pRawData = NULL; + } +} + + +drmtl_header* drmtl_getheader(drmtl_material* pMaterial) +{ + if (pMaterial != NULL) + { + return (drmtl_header*)pMaterial->pRawData; + } + + return NULL; +} + + +bool drmtl_appendidentifier(drmtl_material* pMaterial, drmtl_identifier identifier, unsigned int* indexOut) +{ + if (pMaterial != NULL) + { + if (pMaterial->currentStage <= DRMTL_STAGE_IDS) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + if (pHeader != NULL) + { + if (pMaterial->sizeInBytes + pHeader->identifierSizeInBytes > pMaterial->bufferSizeInBytes) + { + if (!_drmtl_inflate(pMaterial)) + { + // An error occured when trying to inflate the buffer. Might be out of memory. + return 0; + } + + pHeader = drmtl_getheader(pMaterial); + assert(pMaterial->sizeInBytes + pHeader->identifierSizeInBytes <= pMaterial->bufferSizeInBytes); + } + + + memcpy(pMaterial->pRawData + pHeader->inputsOffset, &identifier, pHeader->identifierSizeInBytes); + pMaterial->sizeInBytes += pHeader->identifierSizeInBytes; + + pHeader->identifierCount += 1; + pHeader->inputsOffset += pHeader->identifierSizeInBytes; + pHeader->channelsOffset += pHeader->identifierSizeInBytes; + pHeader->propertiesOffset += pHeader->identifierSizeInBytes; + + + if (indexOut != NULL) + { + *indexOut = pHeader->identifierCount - 1; + } + + return 1; + } + } + } + + return 0; +} + +bool drmtl_appendprivateinput(drmtl_material* pMaterial, drmtl_input input) +{ + if (pMaterial != NULL) + { + if (pMaterial->currentStage <= DRMTL_STAGE_PRIVATE_INPUTS) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + if (pHeader != NULL) + { + if (pMaterial->sizeInBytes + pHeader->inputSizeInBytes > pMaterial->bufferSizeInBytes) + { + if (!_drmtl_inflate(pMaterial)) + { + // An error occured when trying to inflate the buffer. Might be out of memory. + return 0; + } + + pHeader = drmtl_getheader(pMaterial); + assert(pMaterial->sizeInBytes + pHeader->inputSizeInBytes <= pMaterial->bufferSizeInBytes); + } + + + memcpy(pMaterial->pRawData + pHeader->channelsOffset, &input, pHeader->inputSizeInBytes); + pMaterial->sizeInBytes += pHeader->inputSizeInBytes; + + pHeader->privateInputCount += 1; + pHeader->channelsOffset += pHeader->inputSizeInBytes; + pHeader->propertiesOffset += pHeader->inputSizeInBytes; + + + pMaterial->currentStage = DRMTL_STAGE_PRIVATE_INPUTS; + return 1; + } + } + } + + return 0; +} + +bool drmtl_appendpublicinput(drmtl_material* pMaterial, drmtl_input input) +{ + if (pMaterial != NULL) + { + if (pMaterial->currentStage <= DRMTL_STAGE_PUBLIC_INPUTS) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + if (pHeader != NULL) + { + if (pMaterial->sizeInBytes + pHeader->inputSizeInBytes > pMaterial->bufferSizeInBytes) + { + if (!_drmtl_inflate(pMaterial)) + { + // An error occured when trying to inflate the buffer. Might be out of memory. + return 0; + } + + pHeader = drmtl_getheader(pMaterial); + assert(pMaterial->sizeInBytes + pHeader->inputSizeInBytes <= pMaterial->bufferSizeInBytes); + } + + + memcpy(pMaterial->pRawData + pHeader->channelsOffset, &input, pHeader->inputSizeInBytes); + pMaterial->sizeInBytes += pHeader->inputSizeInBytes; + + pHeader->publicInputCount += 1; + pHeader->channelsOffset += pHeader->inputSizeInBytes; + pHeader->propertiesOffset += pHeader->inputSizeInBytes; + + + pMaterial->currentStage = DRMTL_STAGE_PUBLIC_INPUTS; + return 1; + } + } + } + + return 0; +} + +bool drmtl_appendchannel(drmtl_material* pMaterial, drmtl_channel channel) +{ + if (pMaterial != NULL) + { + if (pMaterial->currentStage <= DRMTL_STAGE_CHANNELS) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + if (pHeader != NULL) + { + drmtl_channel_header channelHeader; + channelHeader.channel = channel; + channelHeader.instructionCount = 0; + + if (pMaterial->sizeInBytes + pHeader->channelHeaderSizeInBytes > pMaterial->bufferSizeInBytes) + { + if (!_drmtl_inflate(pMaterial)) + { + // An error occured when trying to inflate the buffer. Might be out of memory. + return 0; + } + + pHeader = drmtl_getheader(pMaterial); + assert(pMaterial->sizeInBytes + pHeader->channelHeaderSizeInBytes <= pMaterial->bufferSizeInBytes); + } + + + memcpy(pMaterial->pRawData + pHeader->propertiesOffset, &channelHeader, pHeader->channelHeaderSizeInBytes); + pMaterial->currentChannelOffset = pMaterial->sizeInBytes; + pMaterial->sizeInBytes += pHeader->channelHeaderSizeInBytes; + + pHeader->channelCount += 1; + pHeader->propertiesOffset += pHeader->channelHeaderSizeInBytes; + + + pMaterial->currentStage = DRMTL_STAGE_CHANNELS; + return 1; + } + } + } + + return 0; +} + +bool drmtl_appendinstruction(drmtl_material* pMaterial, drmtl_instruction instruction) +{ + if (pMaterial != NULL) + { + if (pMaterial->currentStage == DRMTL_STAGE_CHANNELS) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + if (pHeader != NULL) + { + if (pMaterial->sizeInBytes + pHeader->instructionSizeInBytes > pMaterial->bufferSizeInBytes) + { + if (!_drmtl_inflate(pMaterial)) + { + // An error occured when trying to inflate the buffer. Might be out of memory. + return 0; + } + + pHeader = drmtl_getheader(pMaterial); + assert(pMaterial->sizeInBytes + pHeader->instructionSizeInBytes <= pMaterial->bufferSizeInBytes); + } + + + memcpy(pMaterial->pRawData + pHeader->propertiesOffset, &instruction, pHeader->instructionSizeInBytes); + pMaterial->sizeInBytes += pHeader->instructionSizeInBytes; + + drmtl_channel_header* pChannelHeader = (drmtl_channel_header*)(pMaterial->pRawData + pMaterial->currentChannelOffset); + if (pChannelHeader != NULL) + { + pChannelHeader->instructionCount += 1; + } + + pHeader->propertiesOffset += pHeader->instructionSizeInBytes; + + + return 1; + } + } + } + + return 0; +} + +bool drmtl_appendproperty(drmtl_material* pMaterial, drmtl_property prop) +{ + if (pMaterial != NULL) + { + if (pMaterial->currentStage <= DRMTL_STAGE_CHANNELS) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + if (pHeader != NULL) + { + if (pMaterial->sizeInBytes + pHeader->propertySizeInBytes > pMaterial->bufferSizeInBytes) + { + if (!_drmtl_inflate(pMaterial)) + { + // An error occured when trying to inflate the buffer. Might be out of memory. + return 0; + } + + pHeader = drmtl_getheader(pMaterial); + assert(pMaterial->sizeInBytes + pHeader->propertySizeInBytes <= pMaterial->bufferSizeInBytes); + } + + + memcpy(pMaterial->pRawData + pMaterial->sizeInBytes, &prop, pHeader->propertySizeInBytes); + pMaterial->sizeInBytes += pHeader->propertySizeInBytes; + + pHeader->propertyCount += 1; + + + pMaterial->currentStage = DRMTL_STAGE_PROPERTIES; + return 1; + } + } + } + + return 0; +} + + +drmtl_channel_header* drmtl_getchannelheaderbyindex(drmtl_material* pMaterial, unsigned int channelIndex) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + if (channelIndex < pHeader->channelCount) + { + drmtl_uint8* pChannelHeader = pMaterial->pRawData + pHeader->channelsOffset; + for (unsigned int iChannel = 0; iChannel < channelIndex; ++iChannel) + { + pChannelHeader += sizeof(drmtl_channel_header) + (sizeof(drmtl_instruction) * ((drmtl_channel_header*)pChannelHeader)->instructionCount); + } + + return (drmtl_channel_header*)pChannelHeader; + } + } + + return NULL; +} + +drmtl_channel_header* drmtl_getchannelheaderbyname(drmtl_material* pMaterial, const char* channelName) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + drmtl_uint8* pChannelHeader = pMaterial->pRawData + pHeader->channelsOffset; + for (unsigned int iChannel = 0; iChannel < pHeader->channelCount; ++iChannel) + { + if (strcmp(((drmtl_channel_header*)pChannelHeader)->channel.name, channelName) == 0) + { + return (drmtl_channel_header*)pChannelHeader; + } + + pChannelHeader += sizeof(drmtl_channel_header) + (sizeof(drmtl_instruction) * ((drmtl_channel_header*)pChannelHeader)->instructionCount); + } + } + + return NULL; +} + +drmtl_identifier* drmtl_getidentifiers(drmtl_material* pMaterial) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + return (drmtl_identifier*)(pMaterial->pRawData + pHeader->identifiersOffset); + } + + return NULL; +} + +drmtl_identifier* drmtl_getidentifier(drmtl_material* pMaterial, unsigned int index) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + if (index < pHeader->identifierCount) + { + drmtl_identifier* firstIdentifier = (drmtl_identifier*)(pMaterial->pRawData + pHeader->identifiersOffset); + return firstIdentifier + index; + } + } + + return NULL; +} + +unsigned int drmtl_getidentifiercount(drmtl_material* pMaterial) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + return pHeader->identifierCount; + } + + return 0; +} + + +unsigned int drmtl_getinputcount(drmtl_material* pMaterial) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + return pHeader->privateInputCount + pHeader->publicInputCount; + } + + return 0; +} + +drmtl_input* drmtl_getinputbyindex(drmtl_material* pMaterial, unsigned int index) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + if (index < (pHeader->privateInputCount + pHeader->publicInputCount)) + { + drmtl_input* firstInput = (drmtl_input*)(pMaterial->pRawData + pHeader->inputsOffset); + return firstInput + index; + } + } + + return NULL; +} + +unsigned int drmtl_getprivateinputcount(drmtl_material* pMaterial) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + return pHeader->privateInputCount; + } + + return 0; +} + +drmtl_input* drmtl_getprivateinputbyindex(drmtl_material* pMaterial, unsigned int index) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + if (index < pHeader->privateInputCount) + { + drmtl_input* firstInput = (drmtl_input*)(pMaterial->pRawData + pHeader->inputsOffset); + return firstInput + index; + } + } + + return NULL; +} + +unsigned int drmtl_getpublicinputcount(drmtl_material* pMaterial) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + return pHeader->publicInputCount; + } + + return 0; +} + +drmtl_input* drmtl_getpublicinputbyindex(drmtl_material* pMaterial, unsigned int index) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + if (index < pHeader->publicInputCount) + { + drmtl_input* firstInput = (drmtl_input*)(pMaterial->pRawData + pHeader->inputsOffset); + return firstInput + pHeader->privateInputCount + index; + } + } + + return NULL; +} + + +unsigned int drmtl_getpropertycount(drmtl_material* pMaterial) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + return pHeader->propertyCount; + } + + return 0; +} + +drmtl_property* drmtl_getpropertybyindex(drmtl_material* pMaterial, unsigned int index) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + if (index < pHeader->propertyCount) + { + drmtl_property* firstProperty = (drmtl_property*)(pMaterial->pRawData + pHeader->propertiesOffset); + return firstProperty + index; + } + } + + return NULL; +} + +drmtl_property* drmtl_getpropertybyname(drmtl_material* pMaterial, const char* name) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + assert(pHeader != NULL); + + for (unsigned int i = 0; i < pHeader->propertyCount; ++i) + { + drmtl_property* pProperty = ((drmtl_property*)(pMaterial->pRawData + pHeader->propertiesOffset)) + i; + assert(pProperty != NULL); + + if (strcmp(pProperty->name, name) == 0) + { + return pProperty; + } + } + } + + return NULL; +} + + +////////////////////////////////// +// Private low-level API. + +bool _drmtl_inflate(drmtl_material* pMaterial) +{ + assert(pMaterial != NULL); + + drmtl_uint8* pOldBuffer = pMaterial->pRawData; + drmtl_uint8* pNewBuffer = (drmtl_uint8*)malloc(pMaterial->bufferSizeInBytes + DRMTL_CHUNK_SIZE); + if (pNewBuffer != NULL) + { + memcpy(pNewBuffer, pOldBuffer, pMaterial->sizeInBytes); + pMaterial->pRawData = pNewBuffer; + pMaterial->bufferSizeInBytes += DRMTL_CHUNK_SIZE; + + free(pOldBuffer); + return 1; + } + + return 0; +} + + + +//////////////////////////////////////////////////////// +// Mid-Level APIs + +drmtl_identifier drmtl_identifier_float(const char* name) +{ + drmtl_identifier identifier; + identifier.type = drmtl_type_float; + drmtl_strcpy(identifier.name, DRMTL_MAX_IDENTIFIER_NAME, name); + + return identifier; +} + +drmtl_identifier drmtl_identifier_float2(const char* name) +{ + drmtl_identifier identifier; + identifier.type = drmtl_type_float2; + drmtl_strcpy(identifier.name, DRMTL_MAX_IDENTIFIER_NAME, name); + + return identifier; +} + +drmtl_identifier drmtl_identifier_float3(const char* name) +{ + drmtl_identifier identifier; + identifier.type = drmtl_type_float3; + drmtl_strcpy(identifier.name, DRMTL_MAX_IDENTIFIER_NAME, name); + + return identifier; +} + +drmtl_identifier drmtl_identifier_float4(const char* name) +{ + drmtl_identifier identifier; + identifier.type = drmtl_type_float4; + drmtl_strcpy(identifier.name, DRMTL_MAX_IDENTIFIER_NAME, name); + + return identifier; +} + +drmtl_identifier drmtl_identifier_int(const char* name) +{ + drmtl_identifier identifier; + identifier.type = drmtl_type_int; + drmtl_strcpy(identifier.name, DRMTL_MAX_IDENTIFIER_NAME, name); + + return identifier; +} + +drmtl_identifier drmtl_identifier_int2(const char* name) +{ + drmtl_identifier identifier; + identifier.type = drmtl_type_int2; + drmtl_strcpy(identifier.name, DRMTL_MAX_IDENTIFIER_NAME, name); + + return identifier; +} + +drmtl_identifier drmtl_identifier_int3(const char* name) +{ + drmtl_identifier identifier; + identifier.type = drmtl_type_int3; + drmtl_strcpy(identifier.name, DRMTL_MAX_IDENTIFIER_NAME, name); + + return identifier; +} + +drmtl_identifier drmtl_identifier_int4(const char* name) +{ + drmtl_identifier identifier; + identifier.type = drmtl_type_int4; + drmtl_strcpy(identifier.name, DRMTL_MAX_IDENTIFIER_NAME, name); + + return identifier; +} + +drmtl_identifier drmtl_identifier_tex2d(const char* name) +{ + drmtl_identifier identifier; + identifier.type = drmtl_type_tex2d; + drmtl_strcpy(identifier.name, DRMTL_MAX_IDENTIFIER_NAME, name); + + return identifier; +} + + +drmtl_input drmtl_input_float(unsigned int identifierIndex, float x) +{ + drmtl_input input; + input.identifierIndex = identifierIndex; + input.f1.x = x; + + return input; +} + +drmtl_input drmtl_input_float2(unsigned int identifierIndex, float x, float y) +{ + drmtl_input input; + input.identifierIndex = identifierIndex; + input.f2.x = x; + input.f2.y = y; + + return input; +} + +drmtl_input drmtl_input_float3(unsigned int identifierIndex, float x, float y, float z) +{ + drmtl_input input; + input.identifierIndex = identifierIndex; + input.f3.x = x; + input.f3.y = y; + input.f3.z = z; + + return input; +} + +drmtl_input drmtl_input_float4(unsigned int identifierIndex, float x, float y, float z, float w) +{ + drmtl_input input; + input.identifierIndex = identifierIndex; + input.f4.x = x; + input.f4.y = y; + input.f4.z = z; + input.f4.w = w; + + return input; +} + +drmtl_input drmtl_input_int(unsigned int identifierIndex, int x) +{ + drmtl_input input; + input.identifierIndex = identifierIndex; + input.i1.x = x; + + return input; +} + +drmtl_input drmtl_input_int2(unsigned int identifierIndex, int x, int y) +{ + drmtl_input input; + input.identifierIndex = identifierIndex; + input.i2.x = x; + input.i2.y = y; + + return input; +} + +drmtl_input drmtl_input_int3(unsigned int identifierIndex, int x, int y, int z) +{ + drmtl_input input; + input.identifierIndex = identifierIndex; + input.i3.x = x; + input.i3.y = y; + input.i3.z = z; + + return input; +} + +drmtl_input drmtl_input_int4(unsigned int identifierIndex, int x, int y, int z, int w) +{ + drmtl_input input; + input.identifierIndex = identifierIndex; + input.i4.x = x; + input.i4.y = y; + input.i4.z = z; + input.i4.w = w; + + return input; +} + +drmtl_input drmtl_input_tex(unsigned int identifierIndex, const char* path) +{ + drmtl_input input; + input.identifierIndex = identifierIndex; + drmtl_strcpy(input.path.value, DRMTL_MAX_INPUT_PATH, path); + + return input; +} + + +drmtl_channel drmtl_channel_float(const char* name) +{ + drmtl_channel channel; + channel.type = drmtl_type_float; + drmtl_strcpy(channel.name, DRMTL_MAX_CHANNEL_NAME, name); + + return channel; +} + +drmtl_channel drmtl_channel_float2(const char* name) +{ + drmtl_channel channel; + channel.type = drmtl_type_float2; + drmtl_strcpy(channel.name, DRMTL_MAX_CHANNEL_NAME, name); + + return channel; +} + +drmtl_channel drmtl_channel_float3(const char* name) +{ + drmtl_channel channel; + channel.type = drmtl_type_float3; + drmtl_strcpy(channel.name, DRMTL_MAX_CHANNEL_NAME, name); + + return channel; +} + +drmtl_channel drmtl_channel_float4(const char* name) +{ + drmtl_channel channel; + channel.type = drmtl_type_float4; + drmtl_strcpy(channel.name, DRMTL_MAX_CHANNEL_NAME, name); + + return channel; +} + +drmtl_channel drmtl_channel_int(const char* name) +{ + drmtl_channel channel; + channel.type = drmtl_type_int; + drmtl_strcpy(channel.name, DRMTL_MAX_CHANNEL_NAME, name); + + return channel; +} + +drmtl_channel drmtl_channel_int2(const char* name) +{ + drmtl_channel channel; + channel.type = drmtl_type_int2; + drmtl_strcpy(channel.name, DRMTL_MAX_CHANNEL_NAME, name); + + return channel; +} + +drmtl_channel drmtl_channel_int3(const char* name) +{ + drmtl_channel channel; + channel.type = drmtl_type_int3; + drmtl_strcpy(channel.name, DRMTL_MAX_CHANNEL_NAME, name); + + return channel; +} + +drmtl_channel drmtl_channel_int4(const char* name) +{ + drmtl_channel channel; + channel.type = drmtl_type_int4; + drmtl_strcpy(channel.name, DRMTL_MAX_CHANNEL_NAME, name); + + return channel; +} + + +drmtl_instruction drmtl_movf1_v1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_movf1; + inst.mov.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.mov.inputX.id = inputIdentifierIndex; + inst.mov.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_movf1_c1(unsigned int outputIdentifierIndex, float x) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_movf1; + inst.mov.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.mov.inputX.valuef = x; + inst.mov.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_movf2_v2(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_movf2; + inst.mov.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.mov.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.mov.inputX.id = inputIdentifierIndex; + inst.mov.inputY.id = inputIdentifierIndex; + inst.mov.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_movf2_c2(unsigned int outputIdentifierIndex, float x, float y) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_movf2; + inst.mov.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.mov.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.mov.inputX.valuef = x; + inst.mov.inputY.valuef = y; + inst.mov.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_movf3_v3(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_movf3; + inst.mov.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.mov.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.mov.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.mov.inputX.id = inputIdentifierIndex; + inst.mov.inputY.id = inputIdentifierIndex; + inst.mov.inputZ.id = inputIdentifierIndex; + inst.mov.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_movf3_c3(unsigned int outputIdentifierIndex, float x, float y, float z) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_movf3; + inst.mov.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.mov.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.mov.inputDesc.z = DRMTL_INPUT_DESC_CONSTF; + inst.mov.inputX.valuef = x; + inst.mov.inputY.valuef = y; + inst.mov.inputZ.valuef = z; + inst.mov.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_movf4_v4(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_movf4; + inst.mov.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.mov.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.mov.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.mov.inputDesc.w = DRMTL_INPUT_DESC_VARW; + inst.mov.inputX.id = inputIdentifierIndex; + inst.mov.inputY.id = inputIdentifierIndex; + inst.mov.inputZ.id = inputIdentifierIndex; + inst.mov.inputW.id = inputIdentifierIndex; + inst.mov.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_movf4_c4(unsigned int outputIdentifierIndex, float x, float y, float z, float w) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_movf4; + inst.mov.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.mov.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.mov.inputDesc.z = DRMTL_INPUT_DESC_CONSTF; + inst.mov.inputDesc.w = DRMTL_INPUT_DESC_CONSTF; + inst.mov.inputX.valuef = x; + inst.mov.inputY.valuef = y; + inst.mov.inputZ.valuef = z; + inst.mov.inputW.valuef = w; + inst.mov.output = outputIdentifierIndex; + + return inst; +} + + +drmtl_instruction drmtl_addf1_v1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_addf1; + inst.add.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.add.inputX.id = inputIdentifierIndex; + inst.add.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_addf1_c1(unsigned int outputIdentifierIndex, float x) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_addf1; + inst.add.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.add.inputX.valuef = x; + inst.add.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_addf2_v2(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_addf2; + inst.add.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.add.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.add.inputX.id = inputIdentifierIndex; + inst.add.inputY.id = inputIdentifierIndex; + inst.add.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_addf2_c2(unsigned int outputIdentifierIndex, float x, float y) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_addf2; + inst.add.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.add.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.add.inputX.valuef = x; + inst.add.inputY.valuef = y; + inst.add.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_addf3_v3(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_addf3; + inst.add.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.add.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.add.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.add.inputX.id = inputIdentifierIndex; + inst.add.inputY.id = inputIdentifierIndex; + inst.add.inputZ.id = inputIdentifierIndex; + inst.add.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_addf3_c3(unsigned int outputIdentifierIndex, float x, float y, float z) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_addf3; + inst.add.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.add.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.add.inputDesc.z = DRMTL_INPUT_DESC_CONSTF; + inst.add.inputX.valuef = x; + inst.add.inputY.valuef = y; + inst.add.inputZ.valuef = z; + inst.add.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_addf4_v4(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_addf4; + inst.add.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.add.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.add.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.add.inputDesc.w = DRMTL_INPUT_DESC_VARW; + inst.add.inputX.id = inputIdentifierIndex; + inst.add.inputY.id = inputIdentifierIndex; + inst.add.inputZ.id = inputIdentifierIndex; + inst.add.inputW.id = inputIdentifierIndex; + inst.add.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_addf4_c4(unsigned int outputIdentifierIndex, float x, float y, float z, float w) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_addf4; + inst.add.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.add.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.add.inputDesc.z = DRMTL_INPUT_DESC_CONSTF; + inst.add.inputDesc.w = DRMTL_INPUT_DESC_CONSTF; + inst.add.inputX.valuef = x; + inst.add.inputY.valuef = y; + inst.add.inputZ.valuef = z; + inst.add.inputW.valuef = w; + inst.add.output = outputIdentifierIndex; + + return inst; +} + + +drmtl_instruction drmtl_subf1_v1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_subf1; + inst.sub.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.sub.inputX.id = inputIdentifierIndex; + inst.sub.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_subf1_c1(unsigned int outputIdentifierIndex, float x) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_subf1; + inst.sub.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.sub.inputX.valuef = x; + inst.sub.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_subf2_v2(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_subf2; + inst.sub.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.sub.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.sub.inputX.id = inputIdentifierIndex; + inst.sub.inputY.id = inputIdentifierIndex; + inst.sub.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_subf2_c2(unsigned int outputIdentifierIndex, float x, float y) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_subf2; + inst.sub.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.sub.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.sub.inputX.valuef = x; + inst.sub.inputY.valuef = y; + inst.sub.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_subf3_v3(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_subf3; + inst.sub.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.sub.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.sub.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.sub.inputX.id = inputIdentifierIndex; + inst.sub.inputY.id = inputIdentifierIndex; + inst.sub.inputZ.id = inputIdentifierIndex; + inst.sub.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_subf3_c3(unsigned int outputIdentifierIndex, float x, float y, float z) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_subf3; + inst.sub.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.sub.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.sub.inputDesc.z = DRMTL_INPUT_DESC_CONSTF; + inst.sub.inputX.valuef = x; + inst.sub.inputY.valuef = y; + inst.sub.inputZ.valuef = z; + inst.sub.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_subf4_v4(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_subf4; + inst.sub.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.sub.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.sub.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.sub.inputDesc.w = DRMTL_INPUT_DESC_VARW; + inst.sub.inputX.id = inputIdentifierIndex; + inst.sub.inputY.id = inputIdentifierIndex; + inst.sub.inputZ.id = inputIdentifierIndex; + inst.sub.inputW.id = inputIdentifierIndex; + inst.sub.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_subf4_c4(unsigned int outputIdentifierIndex, float x, float y, float z, float w) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_subf4; + inst.sub.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.sub.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.sub.inputDesc.z = DRMTL_INPUT_DESC_CONSTF; + inst.sub.inputDesc.w = DRMTL_INPUT_DESC_CONSTF; + inst.sub.inputX.valuef = x; + inst.sub.inputY.valuef = y; + inst.sub.inputZ.valuef = z; + inst.sub.inputW.valuef = w; + inst.sub.output = outputIdentifierIndex; + + return inst; +} + + +drmtl_instruction drmtl_mulf1_v1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_mulf1; + inst.mul.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.mul.inputX.id = inputIdentifierIndex; + inst.mul.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_mulf1_c1(unsigned int outputIdentifierIndex, float x) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_mulf1; + inst.mul.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputX.valuef = x; + inst.mul.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_mulf2_v2(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_mulf2; + inst.mul.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.mul.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.mul.inputX.id = inputIdentifierIndex; + inst.mul.inputY.id = inputIdentifierIndex; + inst.mul.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_mulf2_c2(unsigned int outputIdentifierIndex, float x, float y) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_mulf2; + inst.mul.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputX.valuef = x; + inst.mul.inputY.valuef = y; + inst.mul.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_mulf3_v3(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_mulf3; + inst.mul.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.mul.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.mul.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.mul.inputX.id = inputIdentifierIndex; + inst.mul.inputY.id = inputIdentifierIndex; + inst.mul.inputZ.id = inputIdentifierIndex; + inst.mul.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_mulf3_c3(unsigned int outputIdentifierIndex, float x, float y, float z) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_mulf3; + inst.mul.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputDesc.z = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputX.valuef = x; + inst.mul.inputY.valuef = y; + inst.mul.inputZ.valuef = z; + inst.mul.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_mulf4_v4(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_mulf4; + inst.mul.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.mul.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.mul.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.mul.inputDesc.w = DRMTL_INPUT_DESC_VARW; + inst.mul.inputX.id = inputIdentifierIndex; + inst.mul.inputY.id = inputIdentifierIndex; + inst.mul.inputZ.id = inputIdentifierIndex; + inst.mul.inputW.id = inputIdentifierIndex; + inst.mul.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_mulf4_c4(unsigned int outputIdentifierIndex, float x, float y, float z, float w) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_mulf4; + inst.mul.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputDesc.z = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputDesc.w = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputX.valuef = x; + inst.mul.inputY.valuef = y; + inst.mul.inputZ.valuef = z; + inst.mul.inputW.valuef = w; + inst.mul.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_mulf4_v3v1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndexXYZ, unsigned int inputIdentifierIndexW) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_mulf4; + inst.mul.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.mul.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.mul.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.mul.inputDesc.w = DRMTL_INPUT_DESC_VARX; + inst.mul.inputX.id = inputIdentifierIndexXYZ; + inst.mul.inputY.id = inputIdentifierIndexXYZ; + inst.mul.inputZ.id = inputIdentifierIndexXYZ; + inst.mul.inputW.id = inputIdentifierIndexW; + inst.mul.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_mulf4_v3c1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex, float w) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_mulf4; + inst.mul.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.mul.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.mul.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.mul.inputDesc.w = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputX.id = inputIdentifierIndex; + inst.mul.inputY.id = inputIdentifierIndex; + inst.mul.inputZ.id = inputIdentifierIndex; + inst.mul.inputW.valuef = w; + inst.mul.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_mulf4_v2c2(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex, float z, float w) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_mulf4; + inst.mul.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.mul.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.mul.inputDesc.z = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputDesc.w = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputX.id = inputIdentifierIndex; + inst.mul.inputY.id = inputIdentifierIndex; + inst.mul.inputZ.valuef = z; + inst.mul.inputW.valuef = w; + inst.mul.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_mulf4_v1c3(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex, float y, float z, float w) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_mulf4; + inst.mul.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.mul.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputDesc.z = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputDesc.w = DRMTL_INPUT_DESC_CONSTF; + inst.mul.inputX.id = inputIdentifierIndex; + inst.mul.inputY.valuef = y; + inst.mul.inputZ.valuef = z; + inst.mul.inputW.valuef = w; + inst.mul.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_mulf4_v1v1v1v1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndexX, unsigned int inputIdentifierIndexY, unsigned int inputIdentifierIndexZ, unsigned int inputIdentifierIndexW) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_mulf4; + inst.mul.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.mul.inputDesc.y = DRMTL_INPUT_DESC_VARX; + inst.mul.inputDesc.z = DRMTL_INPUT_DESC_VARX; + inst.mul.inputDesc.w = DRMTL_INPUT_DESC_VARX; + inst.mul.inputX.id = inputIdentifierIndexX; + inst.mul.inputY.id = inputIdentifierIndexY; + inst.mul.inputZ.id = inputIdentifierIndexZ; + inst.mul.inputW.id = inputIdentifierIndexW; + inst.mul.output = outputIdentifierIndex; + + return inst; +} + + +drmtl_instruction drmtl_divf1_v1(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_divf1; + inst.div.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.div.inputX.id = inputIdentifierIndex; + inst.div.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_divf1_c1(unsigned int outputIdentifierIndex, float x) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_divf1; + inst.div.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.div.inputX.valuef = x; + inst.div.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_divf2_v2(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_divf2; + inst.div.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.div.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.div.inputX.id = inputIdentifierIndex; + inst.div.inputY.id = inputIdentifierIndex; + inst.div.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_divf2_c2(unsigned int outputIdentifierIndex, float x, float y) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_divf2; + inst.div.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.div.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.div.inputX.valuef = x; + inst.div.inputY.valuef = y; + inst.div.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_divf3_v3(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_divf3; + inst.div.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.div.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.div.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.div.inputX.id = inputIdentifierIndex; + inst.div.inputY.id = inputIdentifierIndex; + inst.div.inputZ.id = inputIdentifierIndex; + inst.div.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_divf3_c3(unsigned int outputIdentifierIndex, float x, float y, float z) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_divf3; + inst.div.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.div.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.div.inputDesc.z = DRMTL_INPUT_DESC_CONSTF; + inst.div.inputX.valuef = x; + inst.div.inputY.valuef = y; + inst.div.inputZ.valuef = z; + inst.div.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_divf4_v4(unsigned int outputIdentifierIndex, unsigned int inputIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_divf4; + inst.div.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.div.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.div.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.div.inputDesc.w = DRMTL_INPUT_DESC_VARW; + inst.div.inputX.id = inputIdentifierIndex; + inst.div.inputY.id = inputIdentifierIndex; + inst.div.inputZ.id = inputIdentifierIndex; + inst.div.inputW.id = inputIdentifierIndex; + inst.div.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_divf4_c4(unsigned int outputIdentifierIndex, float x, float y, float z, float w) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_divf4; + inst.div.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.div.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.div.inputDesc.z = DRMTL_INPUT_DESC_CONSTF; + inst.div.inputDesc.w = DRMTL_INPUT_DESC_CONSTF; + inst.div.inputX.valuef = x; + inst.div.inputY.valuef = y; + inst.div.inputZ.valuef = z; + inst.div.inputW.valuef = w; + inst.div.output = outputIdentifierIndex; + + return inst; +} + + +drmtl_instruction drmtl_tex2(unsigned int outputIdentifierIndex, unsigned int textureIdentifierIndex, unsigned int texcoordIdentifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_tex2; + inst.tex.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.tex.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.tex.inputX.id = texcoordIdentifierIndex; + inst.tex.inputY.id = texcoordIdentifierIndex; + inst.tex.texture = textureIdentifierIndex; + inst.tex.output = outputIdentifierIndex; + + return inst; +} + +drmtl_instruction drmtl_var(unsigned int identifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_var; + inst.var.identifierIndex = identifierIndex; + + return inst; +} + +drmtl_instruction drmtl_retf1(unsigned int identifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_retf1; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.ret.inputX.id = identifierIndex; + + return inst; +} + +drmtl_instruction drmtl_retf2(unsigned int identifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_retf2; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.ret.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.ret.inputX.id = identifierIndex; + inst.ret.inputY.id = identifierIndex; + + return inst; +} + +drmtl_instruction drmtl_retf3(unsigned int identifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_retf3; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.ret.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.ret.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.ret.inputX.id = identifierIndex; + inst.ret.inputY.id = identifierIndex; + inst.ret.inputZ.id = identifierIndex; + + return inst; +} + +drmtl_instruction drmtl_retf4(unsigned int identifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_retf4; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.ret.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.ret.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.ret.inputDesc.w = DRMTL_INPUT_DESC_VARW; + inst.ret.inputX.id = identifierIndex; + inst.ret.inputY.id = identifierIndex; + inst.ret.inputZ.id = identifierIndex; + inst.ret.inputW.id = identifierIndex; + + return inst; +} + +drmtl_instruction drmtl_retf1_c1(float x) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_retf1; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.ret.inputX.valuef = x; + + return inst; +} + +drmtl_instruction drmtl_retf2_c2(float x, float y) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_retf2; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.ret.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.ret.inputX.valuef = x; + inst.ret.inputY.valuef = y; + + return inst; +} + +drmtl_instruction drmtl_retf3_c3(float x, float y, float z) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_retf3; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.ret.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.ret.inputDesc.z = DRMTL_INPUT_DESC_CONSTF; + inst.ret.inputX.valuef = x; + inst.ret.inputY.valuef = y; + inst.ret.inputZ.valuef = z; + + return inst; +} + +drmtl_instruction drmtl_retf4_c4(float x, float y, float z, float w) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_retf4; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_CONSTF; + inst.ret.inputDesc.y = DRMTL_INPUT_DESC_CONSTF; + inst.ret.inputDesc.z = DRMTL_INPUT_DESC_CONSTF; + inst.ret.inputDesc.w = DRMTL_INPUT_DESC_CONSTF; + inst.ret.inputX.valuef = x; + inst.ret.inputY.valuef = y; + inst.ret.inputZ.valuef = z; + inst.ret.inputW.valuef = w; + + return inst; +} + +drmtl_instruction drmtl_reti1(unsigned int identifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_reti1; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.ret.inputX.id = identifierIndex; + + return inst; +} + +drmtl_instruction drmtl_reti2(unsigned int identifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_reti2; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.ret.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.ret.inputX.id = identifierIndex; + inst.ret.inputY.id = identifierIndex; + + return inst; +} + +drmtl_instruction drmtl_reti3(unsigned int identifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_reti3; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.ret.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.ret.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.ret.inputX.id = identifierIndex; + inst.ret.inputY.id = identifierIndex; + inst.ret.inputZ.id = identifierIndex; + + return inst; +} + +drmtl_instruction drmtl_reti4(unsigned int identifierIndex) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_reti4; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_VARX; + inst.ret.inputDesc.y = DRMTL_INPUT_DESC_VARY; + inst.ret.inputDesc.z = DRMTL_INPUT_DESC_VARZ; + inst.ret.inputDesc.w = DRMTL_INPUT_DESC_VARW; + inst.ret.inputX.id = identifierIndex; + inst.ret.inputY.id = identifierIndex; + inst.ret.inputZ.id = identifierIndex; + inst.ret.inputW.id = identifierIndex; + + return inst; +} + +drmtl_instruction drmtl_reti1_c1(int x) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_reti1; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_CONSTI; + inst.ret.inputX.valuei = x; + + return inst; +} + +drmtl_instruction drmtl_reti2_c2(int x, int y) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_reti2; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_CONSTI; + inst.ret.inputDesc.y = DRMTL_INPUT_DESC_CONSTI; + inst.ret.inputX.valuei = x; + inst.ret.inputY.valuei = y; + + return inst; +} + +drmtl_instruction drmtl_reti3_c3(int x, int y, int z) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_reti3; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_CONSTI; + inst.ret.inputDesc.y = DRMTL_INPUT_DESC_CONSTI; + inst.ret.inputDesc.z = DRMTL_INPUT_DESC_CONSTI; + inst.ret.inputX.valuei = x; + inst.ret.inputY.valuei = y; + inst.ret.inputZ.valuei = z; + + return inst; +} + +drmtl_instruction drmtl_reti4_c4(int x, int y, int z, int w) +{ + drmtl_instruction inst; + inst.opcode = drmtl_opcode_reti4; + inst.ret.inputDesc.x = DRMTL_INPUT_DESC_CONSTI; + inst.ret.inputDesc.y = DRMTL_INPUT_DESC_CONSTI; + inst.ret.inputDesc.z = DRMTL_INPUT_DESC_CONSTI; + inst.ret.inputDesc.w = DRMTL_INPUT_DESC_CONSTI; + inst.ret.inputX.valuei = x; + inst.ret.inputY.valuei = y; + inst.ret.inputZ.valuei = z; + inst.ret.inputW.valuei = w; + + return inst; +} + + + +drmtl_property drmtl_property_float(const char* name, float x) +{ + drmtl_property prop; + prop.type = drmtl_type_float; + drmtl_strcpy(prop.name, DRMTL_MAX_PROPERTY_NAME, name); + prop.f1.x = x; + + return prop; +} + +drmtl_property drmtl_property_float2(const char* name, float x, float y) +{ + drmtl_property prop; + prop.type = drmtl_type_float2; + drmtl_strcpy(prop.name, DRMTL_MAX_PROPERTY_NAME, name); + prop.f2.x = x; + prop.f2.y = y; + + return prop; +} + +drmtl_property drmtl_property_float3(const char* name, float x, float y, float z) +{ + drmtl_property prop; + prop.type = drmtl_type_float3; + drmtl_strcpy(prop.name, DRMTL_MAX_PROPERTY_NAME, name); + prop.f3.x = x; + prop.f3.y = y; + prop.f3.z = z; + + return prop; +} + +drmtl_property drmtl_property_float4(const char* name, float x, float y, float z, float w) +{ + drmtl_property prop; + prop.type = drmtl_type_float4; + drmtl_strcpy(prop.name, DRMTL_MAX_PROPERTY_NAME, name); + prop.f4.x = x; + prop.f4.y = y; + prop.f4.z = z; + prop.f4.w = w; + + return prop; +} + +drmtl_property drmtl_property_int(const char* name, int x) +{ + drmtl_property prop; + prop.type = drmtl_type_int; + drmtl_strcpy(prop.name, DRMTL_MAX_PROPERTY_NAME, name); + prop.i1.x = x; + + return prop; +} + +drmtl_property drmtl_property_int2(const char* name, int x, int y) +{ + drmtl_property prop; + prop.type = drmtl_type_int2; + drmtl_strcpy(prop.name, DRMTL_MAX_PROPERTY_NAME, name); + prop.i2.x = x; + prop.i2.y = y; + + return prop; +} + +drmtl_property drmtl_property_int3(const char* name, int x, int y, int z) +{ + drmtl_property prop; + prop.type = drmtl_type_int3; + drmtl_strcpy(prop.name, DRMTL_MAX_PROPERTY_NAME, name); + prop.i3.x = x; + prop.i3.y = y; + prop.i3.z = z; + + return prop; +} + +drmtl_property drmtl_property_int4(const char* name, int x, int y, int z, int w) +{ + drmtl_property prop; + prop.type = drmtl_type_int4; + drmtl_strcpy(prop.name, DRMTL_MAX_PROPERTY_NAME, name); + prop.i4.x = x; + prop.i4.y = y; + prop.i4.z = z; + prop.i4.w = w; + + return prop; +} + +drmtl_property drmtl_property_bool(const char* name, bool value) +{ + drmtl_property prop; + prop.type = drmtl_type_bool; + drmtl_strcpy(prop.name, DRMTL_MAX_PROPERTY_NAME, name); + prop.b1.x = value; + + return prop; +} + + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// Compilers +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef DRMTL_NO_MTL_COMPILER +typedef struct +{ + /// A pointer to the buffer containing the Wavefront MTL data. + const char* pData; + + /// The size of the data buffer size. + unsigned int dataSizeInBytes; + + /// A pointer to the next bytes to read. + const char* pDataCur; + + /// A pointer to the end of the buffer. + const char* pDataEnd; + + + /// The diffuse colour. + float diffuse[3]; + + /// The diffuse map. + char diffuseMap[DRMTL_MAX_INPUT_PATH]; + + + /// The specular colour. + float specular[3]; + + /// The specular map. + char specularMap[DRMTL_MAX_INPUT_PATH]; + + + /// The specular exponent. + float specularExponent; + + /// The specular exponent map. + char specularExponentMap[DRMTL_MAX_INPUT_PATH]; + + + /// The alpha transparency value. + float alpha; + + /// The alpha transparency map. + char alphaMap[DRMTL_MAX_INPUT_PATH]; + + +} drmtl_wavefront; + +bool drmtl_wavefront_is_whitespace(char c) +{ + return c == ' ' || c == '\t'; +} + +bool drmtl_wavefront_is_valid_digit(char c) +{ + return c >= '0' && c <= '9'; +} + +bool drmtl_wavefront_atof(const char* str, const char* strEnd, const char** strEndOut, float* valueOut) +{ + // Skip leading whitespace. + while (str < strEnd && drmtl_wavefront_is_whitespace(*str)) + { + str += 1; + } + + + // Check that we have a string after moving the whitespace. + if (str < strEnd) + { + float sign = 1.0f; + float value = 0.0f; + + // Sign. + if (*str == '-') + { + sign = -1.0f; + str += 1; + } + else if (*str == '+') + { + sign = 1.0f; + str += 1; + } + + + // Digits before the decimal point. + while (str < strEnd && drmtl_wavefront_is_valid_digit(*str)) + { + value = value * 10.0f + (*str - '0'); + + str += 1; + } + + // Digits after the decimal point. + if (*str == '.') + { + float pow10 = 10.0f; + + str += 1; + while (str < strEnd && drmtl_wavefront_is_valid_digit(*str)) + { + value += (*str - '0') / pow10; + pow10 *= 10.0f; + + str += 1; + } + } + + + if (strEndOut != NULL) + { + *strEndOut = str; + } + + if (valueOut != NULL) + { + *valueOut = sign * value; + } + + return 1; + } + else + { + // Empty string. Leave output untouched and return 0. + return 0; + } +} + +bool drmtl_wavefront_atof_3(const char* str, const char* strEnd, const char** strEndOut, float valueOut[3]) +{ + float value[3]; + if (drmtl_wavefront_atof(str, strEnd, &str, &value[0])) + { + value[1] = value[0]; + value[2] = value[0]; + + if (drmtl_wavefront_atof(str, strEnd, &str, &value[1])) + { + // We got two numbers which means we must have the third for this to be successful. + if (!drmtl_wavefront_atof(str, strEnd, strEndOut, &value[2])) + { + // Failed to get the third number. We only found 2 which is not valid. Error. + return 0; + } + } + + + valueOut[0] = value[0]; + valueOut[1] = value[1]; + valueOut[2] = value[2]; + + return 1; + } + + return 0; +} + +const char* drmtl_wavefront_find_end_of_line(const char* pDataCur, const char* pDataEnd) +{ + assert(pDataCur != NULL); + assert(pDataEnd != NULL); + + while (pDataCur < pDataEnd) + { + if (pDataCur[0] == '\n') + { + return pDataCur; + } + else + { + if (pDataCur + 1 < pDataEnd) + { + if (pDataCur[0] == '\r' && pDataCur[1] == '\n') + { + return pDataCur; + } + } + } + + pDataCur += 1; + } + + // If we get here it means we hit the end of the file before find a new-line character. + return pDataEnd; +} + +const char* drmtl_wavefront_find_next_line(const char* pDataCur, const char* pDataEnd) +{ + assert(pDataCur != NULL); + assert(pDataEnd != NULL); + + pDataCur = drmtl_wavefront_find_end_of_line(pDataCur, pDataEnd); + if (pDataCur != NULL) + { + if (pDataCur < pDataEnd) + { + if (pDataCur[0] == '\n') + { + return pDataCur + 1; + } + else + { + if (pDataCur + 1 < pDataEnd) + { + if (pDataCur[0] == '\r' && pDataCur[1] == '\n') + { + return pDataCur + 2; + } + } + } + } + } + + return NULL; +} + +const char* drmtl_wavefront_find_next_newmtl(const char* pDataCur, const char* pDataEnd) +{ + assert(pDataCur != NULL); + assert(pDataEnd != NULL); + + while (pDataCur + 7 < pDataEnd) // +7 for "newmtl" + a whitespace character. + { + if (pDataCur[0] == 'n' && pDataCur[1] == 'e' && pDataCur[2] == 'w' && pDataCur[3] == 'm' && pDataCur[4] == 't' && pDataCur[5] == 'l') + { + // We found "newmtl", however the next line must be whitespace. + if (drmtl_wavefront_is_whitespace(pDataCur[6])) + { + // We found it. + return pDataCur; + } + } + + + const char* nextLineStart = drmtl_wavefront_find_next_line(pDataCur, pDataEnd); + if (nextLineStart != NULL) + { + pDataCur = nextLineStart; + } + else + { + // Reached the end before finding "newmtl". Return null. + return NULL; + } + } + + return NULL; +} + +const char* drmtl_wavefront_find_next_nonwhitespace(const char* pDataCur, const char* pDataEnd) +{ + assert(pDataCur != NULL); + assert(pDataEnd != NULL); + + while (pDataCur < pDataEnd) + { + if (!drmtl_wavefront_is_whitespace(pDataCur[0])) + { + return pDataCur; + } + + pDataCur += 1; + } + + return NULL; +} + + +bool drmtl_wavefront_parse_K(const char* pDataCur, const char* pDataEnd, float valueOut[3]) +{ + assert(pDataCur != NULL); + assert(pDataEnd != NULL); + + return drmtl_wavefront_atof_3(pDataCur, pDataEnd, &pDataEnd, valueOut); +} + +bool drmtl_wavefront_parse_N(const char* pDataCur, const char* pDataEnd, float* valueOut) +{ + assert(pDataCur != NULL); + assert(pDataEnd != NULL); + + return drmtl_wavefront_atof(pDataCur, pDataEnd, &pDataEnd, valueOut); +} + +bool drmtl_wavefront_parse_map(const char* pDataCur, const char* pDataEnd, char* pathOut, unsigned int pathSizeInBytes) +{ + assert(pDataCur != NULL); + assert(pDataEnd != NULL); + + // For now we're not supporting options, however support for that will be added later. + + const char* pPathStart = drmtl_wavefront_find_next_nonwhitespace(pDataCur, pDataEnd); + if (pPathStart != NULL) + { + if (pPathStart < pDataEnd) + { + if (pPathStart[0] != '#') + { + // Find the last non-whitespace, making sure we don't include comments. + pDataCur = pPathStart; + const char* pPathEnd = pDataCur; + while (pDataCur < pDataEnd && pDataCur[0] != '#') + { + if (!drmtl_wavefront_is_whitespace(pDataCur[0])) + { + pPathEnd = pDataCur + 1; + } + + pDataCur += 1; + } + + assert(pPathStart < pPathEnd); + + ptrdiff_t pathLength = pPathEnd - pPathStart; + if ((size_t)pathLength + 1 < pathSizeInBytes) + { + memcpy(pathOut, pPathStart, (size_t)pathLength); + pathOut[pathLength] = '\0'; + + return 1; + } + } + } + } + + return 0; +} + + +bool drmtl_wavefront_seek_to_next_line(drmtl_wavefront* pWavefront) +{ + assert(pWavefront != NULL); + + const char* lineStart = drmtl_wavefront_find_next_line(pWavefront->pDataCur, pWavefront->pDataEnd); + if (lineStart != NULL) + { + pWavefront->pDataCur = lineStart; + return 1; + } + + return 0; +} + +bool drmtl_wavefront_seek_to_newmtl(drmtl_wavefront* pWavefront) +{ + assert(pWavefront != NULL); + + const char* usemtl = drmtl_wavefront_find_next_newmtl(pWavefront->pDataCur, pWavefront->pDataEnd); + if (usemtl != NULL) + { + pWavefront->pDataCur = usemtl; + return 1; + } + + return 0; +} + +bool drmtl_wavefront_parse(drmtl_wavefront* pWavefront) +{ + assert(pWavefront != NULL); + + if (drmtl_wavefront_seek_to_newmtl(pWavefront) && drmtl_wavefront_seek_to_next_line(pWavefront)) + { + // Set the end of the material to the start of the second usemtl statement, if it exists. + const char* usemtl2 = drmtl_wavefront_find_next_newmtl(pWavefront->pDataCur, pWavefront->pDataEnd); + if (usemtl2 != NULL) + { + pWavefront->pDataEnd = usemtl2; + } + + + while (pWavefront->pDataCur < pWavefront->pDataEnd) + { + const char* lineCur = pWavefront->pDataCur; + const char* lineEnd = drmtl_wavefront_find_end_of_line(lineCur, pWavefront->pDataEnd); + + lineCur = drmtl_wavefront_find_next_nonwhitespace(lineCur, lineEnd); + if (lineCur != NULL && (lineCur + 2 < lineEnd)) + { + if (lineCur[0] == 'K' && lineCur[1] == 'd' && drmtl_wavefront_is_whitespace(lineCur[2])) // Diffuse colour + { + lineCur += 3; + drmtl_wavefront_parse_K(lineCur, lineEnd, pWavefront->diffuse); + } + else if (lineCur[0] == 'K' && lineCur[1] == 's' && drmtl_wavefront_is_whitespace(lineCur[2])) // Specular colour + { + lineCur += 3; + drmtl_wavefront_parse_K(lineCur, lineEnd, pWavefront->specular); + } + else if (lineCur[0] == 'N' && lineCur[1] == 's' && drmtl_wavefront_is_whitespace(lineCur[2])) // Specular exponent + { + lineCur += 3; + drmtl_wavefront_parse_N(lineCur, lineEnd, &pWavefront->specularExponent); + } + else if (lineCur[0] == 'd' && drmtl_wavefront_is_whitespace(lineCur[1])) // Opacity/Alpha + { + lineCur += 2; + drmtl_wavefront_parse_N(lineCur, lineEnd, &pWavefront->alpha); + } + else + { + // Check for maps. + if (lineCur + 6 < lineEnd) + { + if (lineCur[0] == 'm' && lineCur[1] == 'a' && lineCur[2] == 'p' && lineCur[3] == '_') + { + if (lineCur[4] == 'K' && lineCur[5] == 'd' && drmtl_wavefront_is_whitespace(lineCur[6])) // Diffuse map + { + lineCur += 7; + drmtl_wavefront_parse_map(lineCur, lineEnd, pWavefront->diffuseMap, DRMTL_MAX_INPUT_PATH); + } + else if (lineCur[4] == 'K' && lineCur[5] == 's' && drmtl_wavefront_is_whitespace(lineCur[6])) // Specular map + { + lineCur += 7; + drmtl_wavefront_parse_map(lineCur, lineEnd, pWavefront->specularMap, DRMTL_MAX_INPUT_PATH); + } + else if (lineCur[4] == 'N' && lineCur[5] == 's' && drmtl_wavefront_is_whitespace(lineCur[6])) // Specular exponent map + { + lineCur += 7; + drmtl_wavefront_parse_map(lineCur, lineEnd, pWavefront->specularExponentMap, DRMTL_MAX_INPUT_PATH); + } + else if (lineCur[4] == 'd' && drmtl_wavefront_is_whitespace(lineCur[5])) // Opacity/Alpha map + { + lineCur += 6; + drmtl_wavefront_parse_map(lineCur, lineEnd, pWavefront->alphaMap, DRMTL_MAX_INPUT_PATH); + } + } + } + } + } + + + // Move to the end of the line. + pWavefront->pDataCur = lineEnd; + + // Move to the start of the next line. If this fails it probably means we've reached the end of the data so we just break from the loop + if (!drmtl_wavefront_seek_to_next_line(pWavefront)) + { + break; + } + } + + + return 1; + } + + return 0; +} + +bool drmtl_wavefront_compile(drmtl_material* pMaterial, drmtl_wavefront* pWavefront, const char* texcoordInputName) +{ + assert(pMaterial != NULL); + assert(pWavefront != NULL); + + unsigned int texCoordID; // Private input for texture coordinates. + unsigned int diffuseID; + unsigned int specularID; + unsigned int specularExponentID; + unsigned int alphaID; + unsigned int diffuseMapID = (unsigned int)-1; + unsigned int specularMapID = (unsigned int)-1; + unsigned int specularExponentMapID = (unsigned int)-1; + unsigned int alphaMapID = (unsigned int)-1; + unsigned int diffuseResultID = (unsigned int)-1; + unsigned int specularResultID = (unsigned int)-1; + unsigned int specularExponentResultID = (unsigned int)-1; + unsigned int alphaResultID = (unsigned int)-1; + + + // Identifiers. + drmtl_appendidentifier(pMaterial, drmtl_identifier_float2(texcoordInputName), &texCoordID); + drmtl_appendidentifier(pMaterial, drmtl_identifier_float4("DiffuseColor"), &diffuseID); + drmtl_appendidentifier(pMaterial, drmtl_identifier_float3("SpecularColor"), &specularID); + drmtl_appendidentifier(pMaterial, drmtl_identifier_float("SpecularExponent"), &specularExponentID); + drmtl_appendidentifier(pMaterial, drmtl_identifier_float("Alpha"), &alphaID); + + if (pWavefront->diffuseMap[0] != '\0') { + drmtl_appendidentifier(pMaterial, drmtl_identifier_tex2d("DiffuseMap"), &diffuseMapID); + drmtl_appendidentifier(pMaterial, drmtl_identifier_float4("DiffuseResult"), &diffuseResultID); + } + if (pWavefront->specularMap[0] != '\0') { + drmtl_appendidentifier(pMaterial, drmtl_identifier_tex2d("SpecularMap"), &specularMapID); + drmtl_appendidentifier(pMaterial, drmtl_identifier_float4("SpecularResult"), &specularResultID); + } + if (pWavefront->specularExponentMap[0] != '\0') { + drmtl_appendidentifier(pMaterial, drmtl_identifier_tex2d("SpecularExponentMap"), &specularExponentMapID); + drmtl_appendidentifier(pMaterial, drmtl_identifier_float4("SpecularExponentResult"), &specularExponentResultID); + } + if (pWavefront->alphaMap[0] != '\0') { + drmtl_appendidentifier(pMaterial, drmtl_identifier_tex2d("AlphaMap"), &alphaMapID); + drmtl_appendidentifier(pMaterial, drmtl_identifier_float4("AlphaResult"), &alphaResultID); + } + + + // Inputs. + drmtl_appendprivateinput(pMaterial, drmtl_input_float2(texCoordID, 0, 0)); + drmtl_appendpublicinput(pMaterial, drmtl_input_float4(diffuseID, pWavefront->diffuse[0], pWavefront->diffuse[1], pWavefront->diffuse[2], 1.0f)); + drmtl_appendpublicinput(pMaterial, drmtl_input_float3(specularID, pWavefront->specular[0], pWavefront->specular[1], pWavefront->specular[2])); + drmtl_appendpublicinput(pMaterial, drmtl_input_float(specularExponentID, pWavefront->specularExponent)); + drmtl_appendpublicinput(pMaterial, drmtl_input_float(alphaID, pWavefront->alpha)); + + if (pWavefront->diffuseMap[0] != '\0') { + drmtl_appendpublicinput(pMaterial, drmtl_input_tex(diffuseMapID, pWavefront->diffuseMap)); + } + if (pWavefront->specularMap[0] != '\0') { + drmtl_appendpublicinput(pMaterial, drmtl_input_tex(specularMapID, pWavefront->specularMap)); + } + if (pWavefront->specularExponentMap[0] != '\0') { + drmtl_appendpublicinput(pMaterial, drmtl_input_tex(specularExponentMapID, pWavefront->specularExponentMap)); + } + if (pWavefront->alphaMap[0] != '\0') { + drmtl_appendpublicinput(pMaterial, drmtl_input_tex(alphaMapID, pWavefront->alphaMap)); + } + + + // Channels. + drmtl_appendchannel(pMaterial, drmtl_channel_float4("DiffuseChannel")); + if (pWavefront->diffuseMap[0] != '\0') { + drmtl_appendinstruction(pMaterial, drmtl_var(diffuseResultID)); + drmtl_appendinstruction(pMaterial, drmtl_tex2(diffuseResultID, diffuseMapID, texCoordID)); + drmtl_appendinstruction(pMaterial, drmtl_mulf4_v3c1(diffuseResultID, diffuseID, 1.0f)); + drmtl_appendinstruction(pMaterial, drmtl_retf4(diffuseResultID)); + } else { + drmtl_appendinstruction(pMaterial, drmtl_retf4(diffuseID)); + } + + drmtl_appendchannel(pMaterial, drmtl_channel_float3("SpecularChannel")); + if (pWavefront->specularMap[0] != '\0') { + drmtl_appendinstruction(pMaterial, drmtl_var(specularResultID)); + drmtl_appendinstruction(pMaterial, drmtl_tex2(specularResultID, specularMapID, texCoordID)); + drmtl_appendinstruction(pMaterial, drmtl_mulf4_v3c1(specularResultID, specularID, 1.0f)); + drmtl_appendinstruction(pMaterial, drmtl_retf3(specularResultID)); + } else { + drmtl_appendinstruction(pMaterial, drmtl_retf3(specularID)); + } + + drmtl_appendchannel(pMaterial, drmtl_channel_float("SpecularExponentChannel")); + if (pWavefront->specularExponentMap[0] != '\0') { + drmtl_appendinstruction(pMaterial, drmtl_var(specularExponentResultID)); + drmtl_appendinstruction(pMaterial, drmtl_tex2(specularResultID, specularMapID, texCoordID)); + drmtl_appendinstruction(pMaterial, drmtl_mulf4_v1c3(specularResultID, specularID, 1.0f, 1.0f, 1.0f)); + drmtl_appendinstruction(pMaterial, drmtl_retf1(specularResultID)); + } else { + drmtl_appendinstruction(pMaterial, drmtl_retf1(specularExponentID)); + } + + drmtl_appendchannel(pMaterial, drmtl_channel_float("AlphaChannel")); + if (pWavefront->alphaMap[0] != '\0') { + drmtl_appendinstruction(pMaterial, drmtl_var(alphaResultID)); + drmtl_appendinstruction(pMaterial, drmtl_tex2(alphaResultID, alphaMapID, texCoordID)); + drmtl_appendinstruction(pMaterial, drmtl_mulf4_v1c3(alphaResultID, alphaID, 1.0f, 1.0f, 1.0f)); + drmtl_appendinstruction(pMaterial, drmtl_retf1(alphaResultID)); + } else { + drmtl_appendinstruction(pMaterial, drmtl_retf1(alphaID)); + } + + + + // Properties. + if (pWavefront->alphaMap[0] != '\0' || pWavefront->alpha < 1) + { + drmtl_appendproperty(pMaterial, drmtl_property_bool("IsTransparent", 1)); + } + + return 1; +} + + +bool drmtl_compile_wavefront_mtl(drmtl_material* pMaterial, const char* mtlData, size_t mtlDataSizeInBytes, const char* texcoordInputName) +{ + if (pMaterial != NULL && mtlData != NULL && mtlDataSizeInBytes > 0) + { + if (drmtl_init(pMaterial)) + { + drmtl_wavefront wavefront; + wavefront.pData = mtlData; + wavefront.dataSizeInBytes = mtlDataSizeInBytes; + wavefront.pDataCur = wavefront.pData; + wavefront.pDataEnd = wavefront.pData + wavefront.dataSizeInBytes; + wavefront.diffuse[0] = 1; wavefront.diffuse[1] = 1; wavefront.diffuse[2] = 1; + wavefront.diffuseMap[0] = '\0'; + wavefront.specular[0] = 1; wavefront.specular[1] = 1; wavefront.specular[2] = 1; + wavefront.specularMap[0] = '\0'; + wavefront.specularExponent = 10; + wavefront.specularExponentMap[0] = '\0'; + wavefront.alpha = 1; + wavefront.alphaMap[0] = '\0'; + + if (drmtl_wavefront_parse(&wavefront)) + { + if (drmtl_wavefront_compile(pMaterial, &wavefront, texcoordInputName)) + { + return 1; + } + else + { + // Failed to compile. + drmtl_uninit(pMaterial); + } + } + else + { + // Failed to parse the file. + drmtl_uninit(pMaterial); + } + } + } + + return 0; +} +#endif + + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// Code Generators +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef DRMTL_NO_GLSL_CODEGEN +#include + +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wswitch-enum" + #pragma GCC diagnostic ignored "-Wcovered-switch-default" + #pragma GCC diagnostic ignored "-Wused-but-marked-unused" // This ie emitted for snprintf() for some reason. Need to investigate... +#endif + +typedef struct +{ + /// The output buffer. Can be null, in which case nothing is written. + char* pBufferOut; + + /// The size of the output buffer. + unsigned int bufferOutSizeInBytes; + + /// The current length of the string copied to the output buffer. + unsigned int runningLength; + + + /// A pointer to the material that is being used as the source of the code generation. + drmtl_material* pMaterial; + + /// A pointer to the buffer containing the material's identifiers. + drmtl_identifier* pIdentifiers; + + /// The number of identifiers. + unsigned int identifierCount; + + + /// The current indentation level, in spaces. + unsigned int indentationLevel; + +} drmtl_codegen_glsl; + +bool drmtl_codegen_glsl_write(drmtl_codegen_glsl* pCodegen, const char* src) +{ + assert(pCodegen != NULL); + assert(src != NULL); + + if (pCodegen->pBufferOut != NULL) + { + unsigned int dstSizeInBytes = (pCodegen->bufferOutSizeInBytes - pCodegen->runningLength); + while (dstSizeInBytes > 0 && src[0] != '\0') + { + pCodegen->pBufferOut[pCodegen->runningLength + 0] = src[0]; + + pCodegen->runningLength += 1; + src += 1; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes > 0) + { + // There's enough room for the null terminator which means there was enough room in the buffer. All good. + pCodegen->pBufferOut[pCodegen->runningLength] = '\0'; + return 1; + } + else + { + // There's not enough room for the null terminator which means there was NOT enough room in the buffer. Error. + return 0; + } + } + else + { + // We're just measuring. + pCodegen->runningLength += (unsigned int)strlen(src); + return 1; + } +} + +bool drmtl_codegen_glsl_write_float(drmtl_codegen_glsl* pCodegen, float src) +{ + assert(pCodegen != NULL); + + char str[32]; + if (snprintf(str, 32, "%f", src) > 0) + { + return drmtl_codegen_glsl_write(pCodegen, str); + } + else + { + return 0; + } +} + +bool drmtl_codegen_glsl_write_int(drmtl_codegen_glsl* pCodegen, int src) +{ + assert(pCodegen != NULL); + + char str[32]; + if (snprintf(str, 32, "%d", src) > 0) + { + return drmtl_codegen_glsl_write(pCodegen, str); + } + else + { + return 0; + } +} + +bool drmtl_codegen_glsl_write_indentation(drmtl_codegen_glsl* pCodegen) +{ + assert(pCodegen != NULL); + + for (unsigned int i = 0; i < pCodegen->indentationLevel; ++i) + { + drmtl_codegen_glsl_write(pCodegen, " "); + } + + return 1; +} + +bool drmtl_codegen_glsl_write_type(drmtl_codegen_glsl* pCodegen, drmtl_type type) +{ + assert(pCodegen != NULL); + + switch (type) + { + case drmtl_type_float: + { + if (!drmtl_codegen_glsl_write(pCodegen, "float")) + { + return 0; + } + + break; + } + case drmtl_type_float2: + { + if (!drmtl_codegen_glsl_write(pCodegen, "vec2")) + { + return 0; + } + + break; + } + case drmtl_type_float3: + { + if (!drmtl_codegen_glsl_write(pCodegen, "vec3")) + { + return 0; + } + + break; + } + case drmtl_type_float4: + { + if (!drmtl_codegen_glsl_write(pCodegen, "vec4")) + { + return 0; + } + + break; + } + + case drmtl_type_int: + { + if (!drmtl_codegen_glsl_write(pCodegen, "int")) + { + return 0; + } + + break; + } + case drmtl_type_int2: + { + if (!drmtl_codegen_glsl_write(pCodegen, "ivec2")) + { + return 0; + } + + break; + } + case drmtl_type_int3: + { + if (!drmtl_codegen_glsl_write(pCodegen, "ivec3")) + { + return 0; + } + + break; + } + case drmtl_type_int4: + { + if (!drmtl_codegen_glsl_write(pCodegen, "ivec4")) + { + return 0; + } + + break; + } + + case drmtl_type_tex1d: + { + if (!drmtl_codegen_glsl_write(pCodegen, "sampler1D")) + { + return 0; + } + + break; + } + case drmtl_type_tex2d: + { + if (!drmtl_codegen_glsl_write(pCodegen, "sampler2D")) + { + return 0; + } + + break; + } + case drmtl_type_tex3d: + { + if (!drmtl_codegen_glsl_write(pCodegen, "sampler3D")) + { + return 0; + } + + break; + } + case drmtl_type_texcube: + { + if (!drmtl_codegen_glsl_write(pCodegen, "samplerCube")) + { + return 0; + } + + break; + } + + default: + { + // Unsupported return type. + return 0; + } + } + + return 1; +} + +bool drmtl_codegen_glsl_write_instruction_input_scalar(drmtl_codegen_glsl* pCodegen, unsigned char descriptor, drmtl_instruction_input* pInput) +{ + assert(pCodegen != NULL); + assert(pInput != NULL); + + if (descriptor == DRMTL_INPUT_DESC_CONSTF) + { + // It's a constant float. + return drmtl_codegen_glsl_write_float(pCodegen, pInput->valuef); + } + else if (descriptor == DRMTL_INPUT_DESC_CONSTI) + { + // It's a constant int. + return drmtl_codegen_glsl_write_int(pCodegen, pInput->valuei); + } + else + { + // It's a variable. + if (pInput->id < pCodegen->identifierCount) + { + drmtl_identifier* pIdentifier = pCodegen->pIdentifiers + pInput->id; + assert(pIdentifier != NULL); + + if (pIdentifier->type == drmtl_type_float) + { + // The input variable is a float, so we don't want to use any selectors. + return drmtl_codegen_glsl_write(pCodegen, pIdentifier->name); + } + else + { + if (drmtl_codegen_glsl_write(pCodegen, pIdentifier->name) && drmtl_codegen_glsl_write(pCodegen, ".")) + { + switch (descriptor) + { + case 0: return drmtl_codegen_glsl_write(pCodegen, "x"); + case 1: return drmtl_codegen_glsl_write(pCodegen, "y"); + case 2: return drmtl_codegen_glsl_write(pCodegen, "z"); + case 3: return drmtl_codegen_glsl_write(pCodegen, "w"); + default: return 0; + } + } + } + } + } + + return 0; +} + +bool drmtl_codegen_glsl_write_instruction_input_initializer(drmtl_codegen_glsl* pCodegen, drmtl_type type, drmtl_instruction_input_descriptor inputDesc, drmtl_instruction_input* pInputs) +{ + assert(pCodegen != NULL); + assert(pInputs != NULL); + + switch (type) + { + case drmtl_type_float: + { + return drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.x, pInputs + 0); + } + + case drmtl_type_float2: + { + if (drmtl_codegen_glsl_write(pCodegen, "vec2(")) + { + if (drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.x, pInputs + 0) && drmtl_codegen_glsl_write(pCodegen, ", ") && + drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.y, pInputs + 1)) + { + return drmtl_codegen_glsl_write(pCodegen, ")"); + } + } + + break; + } + + case drmtl_type_float3: + { + if (drmtl_codegen_glsl_write(pCodegen, "vec3(")) + { + if (drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.x, pInputs + 0) && drmtl_codegen_glsl_write(pCodegen, ", ") && + drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.y, pInputs + 1) && drmtl_codegen_glsl_write(pCodegen, ", ") && + drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.z, pInputs + 2)) + { + return drmtl_codegen_glsl_write(pCodegen, ")"); + } + } + + break; + } + + case drmtl_type_float4: + { + if (drmtl_codegen_glsl_write(pCodegen, "vec4(")) + { + if (drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.x, pInputs + 0) && drmtl_codegen_glsl_write(pCodegen, ", ") && + drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.y, pInputs + 1) && drmtl_codegen_glsl_write(pCodegen, ", ") && + drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.z, pInputs + 2) && drmtl_codegen_glsl_write(pCodegen, ", ") && + drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.w, pInputs + 3)) + { + return drmtl_codegen_glsl_write(pCodegen, ")"); + } + } + + break; + } + + + case drmtl_type_int: + { + return drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.x, pInputs + 0); + } + + case drmtl_type_int2: + { + if (drmtl_codegen_glsl_write(pCodegen, "ivec2(")) + { + if (drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.x, pInputs + 0) && drmtl_codegen_glsl_write(pCodegen, ", ") && + drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.y, pInputs + 1)) + { + return drmtl_codegen_glsl_write(pCodegen, ")"); + } + } + + break; + } + + case drmtl_type_int3: + { + if (drmtl_codegen_glsl_write(pCodegen, "ivec3(")) + { + if (drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.x, pInputs + 0) && drmtl_codegen_glsl_write(pCodegen, ", ") && + drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.y, pInputs + 1) && drmtl_codegen_glsl_write(pCodegen, ", ") && + drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.z, pInputs + 2)) + { + return drmtl_codegen_glsl_write(pCodegen, ")"); + } + } + + break; + } + + case drmtl_type_int4: + { + if (drmtl_codegen_glsl_write(pCodegen, "ivec4(")) + { + if (drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.x, pInputs + 0) && drmtl_codegen_glsl_write(pCodegen, ", ") && + drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.y, pInputs + 1) && drmtl_codegen_glsl_write(pCodegen, ", ") && + drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.z, pInputs + 2) && drmtl_codegen_glsl_write(pCodegen, ", ") && + drmtl_codegen_glsl_write_instruction_input_scalar(pCodegen, inputDesc.w, pInputs + 3)) + { + return drmtl_codegen_glsl_write(pCodegen, ")"); + } + } + + break; + } + + + default: + { + // Unsupported return type. + return 0; + } + } + + return 0; +} + + +bool drmtl_codegen_glsl_write_instruction_mov(drmtl_codegen_glsl* pCodegen, drmtl_instruction* pInstruction) +{ + assert(pCodegen != NULL); + assert(pInstruction != NULL); + + if (pInstruction->mov.output < pCodegen->identifierCount) + { + drmtl_identifier* pOutputIdentifier = pCodegen->pIdentifiers + pInstruction->mov.output; + assert(pOutputIdentifier != NULL); + + if (drmtl_codegen_glsl_write(pCodegen, pOutputIdentifier->name) && drmtl_codegen_glsl_write(pCodegen, " = ")) + { + drmtl_type type; + switch (pInstruction->opcode) + { + case drmtl_opcode_movf1: type = drmtl_type_float; break; + case drmtl_opcode_movf2: type = drmtl_type_float2; break; + case drmtl_opcode_movf3: type = drmtl_type_float3; break; + case drmtl_opcode_movf4: type = drmtl_type_float4; break; + case drmtl_opcode_movi1: type = drmtl_type_int; break; + case drmtl_opcode_movi2: type = drmtl_type_int2; break; + case drmtl_opcode_movi3: type = drmtl_type_int3; break; + case drmtl_opcode_movi4: type = drmtl_type_int4; break; + default: return 0; + } + + return drmtl_codegen_glsl_write_instruction_input_initializer(pCodegen, type, pInstruction->mov.inputDesc, &pInstruction->mov.inputX) && drmtl_codegen_glsl_write(pCodegen, ";\n"); + } + } + + return 0; +} + +bool drmtl_codegen_glsl_write_instruction_add(drmtl_codegen_glsl* pCodegen, drmtl_instruction* pInstruction) +{ + assert(pCodegen != NULL); + assert(pInstruction != NULL); + + if (pInstruction->add.output < pCodegen->identifierCount) + { + drmtl_identifier* pOutputIdentifier = pCodegen->pIdentifiers + pInstruction->add.output; + assert(pOutputIdentifier != NULL); + + if (drmtl_codegen_glsl_write(pCodegen, pOutputIdentifier->name) && drmtl_codegen_glsl_write(pCodegen, " += ")) + { + drmtl_type type; + switch (pInstruction->opcode) + { + case drmtl_opcode_addf1: type = drmtl_type_float; break; + case drmtl_opcode_addf2: type = drmtl_type_float2; break; + case drmtl_opcode_addf3: type = drmtl_type_float3; break; + case drmtl_opcode_addf4: type = drmtl_type_float4; break; + case drmtl_opcode_addi1: type = drmtl_type_int; break; + case drmtl_opcode_addi2: type = drmtl_type_int2; break; + case drmtl_opcode_addi3: type = drmtl_type_int3; break; + case drmtl_opcode_addi4: type = drmtl_type_int4; break; + default: return 0; + } + + return drmtl_codegen_glsl_write_instruction_input_initializer(pCodegen, type, pInstruction->add.inputDesc, &pInstruction->add.inputX) && drmtl_codegen_glsl_write(pCodegen, ";\n"); + } + } + + return 0; +} + +bool drmtl_codegen_glsl_write_instruction_sub(drmtl_codegen_glsl* pCodegen, drmtl_instruction* pInstruction) +{ + assert(pCodegen != NULL); + assert(pInstruction != NULL); + + if (pInstruction->add.output < pCodegen->identifierCount) + { + drmtl_identifier* pOutputIdentifier = pCodegen->pIdentifiers + pInstruction->sub.output; + assert(pOutputIdentifier != NULL); + + if (drmtl_codegen_glsl_write(pCodegen, pOutputIdentifier->name) && drmtl_codegen_glsl_write(pCodegen, " -= ")) + { + drmtl_type type; + switch (pInstruction->opcode) + { + case drmtl_opcode_subf1: type = drmtl_type_float; break; + case drmtl_opcode_subf2: type = drmtl_type_float2; break; + case drmtl_opcode_subf3: type = drmtl_type_float3; break; + case drmtl_opcode_subf4: type = drmtl_type_float4; break; + case drmtl_opcode_subi1: type = drmtl_type_int; break; + case drmtl_opcode_subi2: type = drmtl_type_int2; break; + case drmtl_opcode_subi3: type = drmtl_type_int3; break; + case drmtl_opcode_subi4: type = drmtl_type_int4; break; + default: return 0; + } + + return drmtl_codegen_glsl_write_instruction_input_initializer(pCodegen, type, pInstruction->sub.inputDesc, &pInstruction->sub.inputX) && drmtl_codegen_glsl_write(pCodegen, ";\n"); + } + } + + return 0; +} + +bool drmtl_codegen_glsl_write_instruction_mul(drmtl_codegen_glsl* pCodegen, drmtl_instruction* pInstruction) +{ + assert(pCodegen != NULL); + assert(pInstruction != NULL); + + if (pInstruction->mul.output < pCodegen->identifierCount) + { + drmtl_identifier* pOutputIdentifier = pCodegen->pIdentifiers + pInstruction->mul.output; + assert(pOutputIdentifier != NULL); + + if (drmtl_codegen_glsl_write(pCodegen, pOutputIdentifier->name) && drmtl_codegen_glsl_write(pCodegen, " *= ")) + { + drmtl_type type; + switch (pInstruction->opcode) + { + case drmtl_opcode_mulf1: type = drmtl_type_float; break; + case drmtl_opcode_mulf2: type = drmtl_type_float2; break; + case drmtl_opcode_mulf3: type = drmtl_type_float3; break; + case drmtl_opcode_mulf4: type = drmtl_type_float4; break; + case drmtl_opcode_muli1: type = drmtl_type_int; break; + case drmtl_opcode_muli2: type = drmtl_type_int2; break; + case drmtl_opcode_muli3: type = drmtl_type_int3; break; + case drmtl_opcode_muli4: type = drmtl_type_int4; break; + default: return 0; + } + + return drmtl_codegen_glsl_write_instruction_input_initializer(pCodegen, type, pInstruction->mul.inputDesc, &pInstruction->mul.inputX) && drmtl_codegen_glsl_write(pCodegen, ";\n"); + } + } + + return 0; +} + +bool drmtl_codegen_glsl_write_instruction_div(drmtl_codegen_glsl* pCodegen, drmtl_instruction* pInstruction) +{ + assert(pCodegen != NULL); + assert(pInstruction != NULL); + + if (pInstruction->div.output < pCodegen->identifierCount) + { + drmtl_identifier* pOutputIdentifier = pCodegen->pIdentifiers + pInstruction->div.output; + assert(pOutputIdentifier != NULL); + + if (drmtl_codegen_glsl_write(pCodegen, pOutputIdentifier->name) && drmtl_codegen_glsl_write(pCodegen, " = ")) + { + drmtl_type type; + switch (pInstruction->opcode) + { + case drmtl_opcode_divf1: type = drmtl_type_float; break; + case drmtl_opcode_divf2: type = drmtl_type_float2; break; + case drmtl_opcode_divf3: type = drmtl_type_float3; break; + case drmtl_opcode_divf4: type = drmtl_type_float4; break; + case drmtl_opcode_divi1: type = drmtl_type_int; break; + case drmtl_opcode_divi2: type = drmtl_type_int2; break; + case drmtl_opcode_divi3: type = drmtl_type_int3; break; + case drmtl_opcode_divi4: type = drmtl_type_int4; break; + default: return 0; + } + + return drmtl_codegen_glsl_write_instruction_input_initializer(pCodegen, type, pInstruction->div.inputDesc, &pInstruction->div.inputX) && drmtl_codegen_glsl_write(pCodegen, ";\n"); + } + } + + return 0; +} + +bool drmtl_codegen_glsl_write_instruction_pow(drmtl_codegen_glsl* pCodegen, drmtl_instruction* pInstruction) +{ + assert(pCodegen != NULL); + assert(pInstruction != NULL); + + if (pInstruction->pow.output < pCodegen->identifierCount) + { + drmtl_identifier* pOutputIdentifier = pCodegen->pIdentifiers + pInstruction->pow.output; + assert(pOutputIdentifier != NULL); + + if (drmtl_codegen_glsl_write(pCodegen, pOutputIdentifier->name) && drmtl_codegen_glsl_write(pCodegen, " = pow(") && drmtl_codegen_glsl_write(pCodegen, pOutputIdentifier->name) && drmtl_codegen_glsl_write(pCodegen, ", ")) + { + drmtl_type type; + switch (pInstruction->opcode) + { + case drmtl_opcode_powf1: type = drmtl_type_float; break; + case drmtl_opcode_powf2: type = drmtl_type_float2; break; + case drmtl_opcode_powf3: type = drmtl_type_float3; break; + case drmtl_opcode_powf4: type = drmtl_type_float4; break; + case drmtl_opcode_powi1: type = drmtl_type_int; break; + case drmtl_opcode_powi2: type = drmtl_type_int2; break; + case drmtl_opcode_powi3: type = drmtl_type_int3; break; + case drmtl_opcode_powi4: type = drmtl_type_int4; break; + default: return 0; + } + + return drmtl_codegen_glsl_write_instruction_input_initializer(pCodegen, type, pInstruction->pow.inputDesc, &pInstruction->pow.inputX) && drmtl_codegen_glsl_write(pCodegen, ");\n"); + } + } + + return 0; +} + +bool drmtl_codegen_glsl_write_instruction_tex(drmtl_codegen_glsl* pCodegen, drmtl_instruction* pInstruction) +{ + assert(pCodegen != NULL); + assert(pInstruction != NULL); + + if (pInstruction->tex.output < pCodegen->identifierCount && pInstruction->tex.texture < pCodegen->identifierCount) + { + drmtl_identifier* pOutputIdentifier = pCodegen->pIdentifiers + pInstruction->tex.output; + assert(pOutputIdentifier != NULL); + + drmtl_identifier* pTextureIdentifier = pCodegen->pIdentifiers + pInstruction->tex.texture; + assert(pTextureIdentifier != NULL); + + if (drmtl_codegen_glsl_write(pCodegen, pOutputIdentifier->name) && drmtl_codegen_glsl_write(pCodegen, " = ")) + { + drmtl_type type; + switch (pInstruction->opcode) + { + case drmtl_opcode_tex1: + { + type = drmtl_type_float; + if (!drmtl_codegen_glsl_write(pCodegen, "texture1D(")) + { + return 0; + } + + break; + } + + case drmtl_opcode_tex2: + { + type = drmtl_type_float2; + if (!drmtl_codegen_glsl_write(pCodegen, "texture2D(")) + { + return 0; + } + + break; + } + + case drmtl_opcode_tex3: + { + type = drmtl_type_float3; + if (!drmtl_codegen_glsl_write(pCodegen, "texture3D(")) + { + return 0; + } + + break; + } + + case drmtl_opcode_texcube: + { + type = drmtl_type_float3; + if (!drmtl_codegen_glsl_write(pCodegen, "textureCube(")) + { + return 0; + } + + break; + } + + default: return 0; + } + + return + drmtl_codegen_glsl_write(pCodegen, pTextureIdentifier->name) && + drmtl_codegen_glsl_write(pCodegen, ", ") && + drmtl_codegen_glsl_write_instruction_input_initializer(pCodegen, type, pInstruction->tex.inputDesc, &pInstruction->tex.inputX) && + drmtl_codegen_glsl_write(pCodegen, ");\n"); + } + } + + return 0; +} + +bool drmtl_codegen_glsl_write_instruction_var(drmtl_codegen_glsl* pCodegen, drmtl_instruction* pInstruction) +{ + assert(pCodegen != NULL); + assert(pInstruction != NULL); + + if (pInstruction->var.identifierIndex < pCodegen->identifierCount) + { + drmtl_identifier* pIdentifier = pCodegen->pIdentifiers + pInstruction->var.identifierIndex; + assert(pIdentifier != NULL); + + return drmtl_codegen_glsl_write_type(pCodegen, pIdentifier->type) && drmtl_codegen_glsl_write(pCodegen, " ") && drmtl_codegen_glsl_write(pCodegen, pIdentifier->name) && drmtl_codegen_glsl_write(pCodegen, ";\n"); + } + + return 0; +} + +bool drmtl_codegen_glsl_write_instruction_ret(drmtl_codegen_glsl* pCodegen, drmtl_instruction* pInstruction) +{ + assert(pCodegen != NULL); + assert(pInstruction != NULL); + + if (drmtl_codegen_glsl_write(pCodegen, "return ")) + { + drmtl_type type; + switch (pInstruction->opcode) + { + case drmtl_opcode_retf1: type = drmtl_type_float; break; + case drmtl_opcode_retf2: type = drmtl_type_float2; break; + case drmtl_opcode_retf3: type = drmtl_type_float3; break; + case drmtl_opcode_retf4: type = drmtl_type_float4; break; + case drmtl_opcode_reti1: type = drmtl_type_int; break; + case drmtl_opcode_reti2: type = drmtl_type_int2; break; + case drmtl_opcode_reti3: type = drmtl_type_int3; break; + case drmtl_opcode_reti4: type = drmtl_type_int4; break; + default: return 0; + } + + return drmtl_codegen_glsl_write_instruction_input_initializer(pCodegen, type, pInstruction->ret.inputDesc, &pInstruction->ret.inputX) && drmtl_codegen_glsl_write(pCodegen, ";\n"); + } + + return 0; +} + +bool drmtl_codegen_glsl_write_instruction(drmtl_codegen_glsl* pCodegen, drmtl_instruction* pInstruction) +{ + assert(pCodegen != NULL); + assert(pInstruction != NULL); + + if (drmtl_codegen_glsl_write_indentation(pCodegen)) + { + switch (pInstruction->opcode) + { + case drmtl_opcode_movf1: + case drmtl_opcode_movf2: + case drmtl_opcode_movf3: + case drmtl_opcode_movf4: + case drmtl_opcode_movi1: + case drmtl_opcode_movi2: + case drmtl_opcode_movi3: + case drmtl_opcode_movi4: + { + return drmtl_codegen_glsl_write_instruction_mov(pCodegen, pInstruction); + } + + + case drmtl_opcode_addf1: + case drmtl_opcode_addf2: + case drmtl_opcode_addf3: + case drmtl_opcode_addf4: + case drmtl_opcode_addi1: + case drmtl_opcode_addi2: + case drmtl_opcode_addi3: + case drmtl_opcode_addi4: + { + return drmtl_codegen_glsl_write_instruction_add(pCodegen, pInstruction); + } + + case drmtl_opcode_subf1: + case drmtl_opcode_subf2: + case drmtl_opcode_subf3: + case drmtl_opcode_subf4: + case drmtl_opcode_subi1: + case drmtl_opcode_subi2: + case drmtl_opcode_subi3: + case drmtl_opcode_subi4: + { + return drmtl_codegen_glsl_write_instruction_sub(pCodegen, pInstruction); + } + + case drmtl_opcode_mulf1: + case drmtl_opcode_mulf2: + case drmtl_opcode_mulf3: + case drmtl_opcode_mulf4: + case drmtl_opcode_muli1: + case drmtl_opcode_muli2: + case drmtl_opcode_muli3: + case drmtl_opcode_muli4: + { + return drmtl_codegen_glsl_write_instruction_mul(pCodegen, pInstruction); + } + + case drmtl_opcode_divf1: + case drmtl_opcode_divf2: + case drmtl_opcode_divf3: + case drmtl_opcode_divf4: + case drmtl_opcode_divi1: + case drmtl_opcode_divi2: + case drmtl_opcode_divi3: + case drmtl_opcode_divi4: + { + return drmtl_codegen_glsl_write_instruction_div(pCodegen, pInstruction); + } + + case drmtl_opcode_powf1: + case drmtl_opcode_powf2: + case drmtl_opcode_powf3: + case drmtl_opcode_powf4: + case drmtl_opcode_powi1: + case drmtl_opcode_powi2: + case drmtl_opcode_powi3: + case drmtl_opcode_powi4: + { + return drmtl_codegen_glsl_write_instruction_pow(pCodegen, pInstruction); + } + + case drmtl_opcode_tex1: + case drmtl_opcode_tex2: + case drmtl_opcode_tex3: + case drmtl_opcode_texcube: + { + return drmtl_codegen_glsl_write_instruction_tex(pCodegen, pInstruction); + } + + + case drmtl_opcode_var: + { + return drmtl_codegen_glsl_write_instruction_var(pCodegen, pInstruction); + } + + case drmtl_opcode_retf1: + case drmtl_opcode_retf2: + case drmtl_opcode_retf3: + case drmtl_opcode_retf4: + case drmtl_opcode_reti1: + case drmtl_opcode_reti2: + case drmtl_opcode_reti3: + case drmtl_opcode_reti4: + { + return drmtl_codegen_glsl_write_instruction_ret(pCodegen, pInstruction); + } + + + default: + { + // Unknown or unsupported opcode. + break; + } + } + } + + return 0; +} + +bool drmtl_codegen_glsl_write_instructions(drmtl_codegen_glsl* pCodegen, drmtl_instruction* pInstructions, unsigned int instructionCount) +{ + assert(pCodegen != NULL); + assert(pInstructions != NULL); + + for (unsigned int iInstruction = 0; iInstruction < instructionCount; ++iInstruction) + { + drmtl_instruction* pInstruction = pInstructions + iInstruction; + assert(pInstruction != NULL); + + if (!drmtl_codegen_glsl_write_instruction(pCodegen, pInstruction)) + { + return 0; + } + } + + return 1; +} + +bool drmtl_codegen_glsl_channel_function_begin(drmtl_codegen_glsl* pCodegen, drmtl_channel_header* pChannelHeader) +{ + assert(pCodegen != NULL); + assert(pChannelHeader != NULL); + + // {\n + bool result = + drmtl_codegen_glsl_write_type(pCodegen, pChannelHeader->channel.type) && + drmtl_codegen_glsl_write(pCodegen, " ") && + drmtl_codegen_glsl_write(pCodegen, pChannelHeader->channel.name) && + drmtl_codegen_glsl_write(pCodegen, "() {\n"); + if (result) + { + pCodegen->indentationLevel += 4; + } + + return result; +} + +bool drmtl_codegen_glsl_channel_function_close(drmtl_codegen_glsl* pCodegen) +{ + assert(pCodegen != NULL); + + if (pCodegen->indentationLevel > 4) { + pCodegen->indentationLevel -= 4; + } else { + pCodegen->indentationLevel = 0; + } + + return drmtl_codegen_glsl_write(pCodegen, "}\n"); +} + +bool drmtl_codegen_glsl_channel(drmtl_material* pMaterial, const char* channelName, char* codeOut, size_t codeOutSizeInBytes, size_t* pBytesWrittenOut) +{ + if (pMaterial != NULL) + { + drmtl_header* pHeader = drmtl_getheader(pMaterial); + if (pHeader != NULL) + { + drmtl_channel_header* pChannelHeader = drmtl_getchannelheaderbyname(pMaterial, channelName); + if (pChannelHeader != NULL) + { + drmtl_codegen_glsl codegen; + codegen.pBufferOut = codeOut; + codegen.bufferOutSizeInBytes = codeOutSizeInBytes; + codegen.runningLength = 0; + codegen.pMaterial = pMaterial; + codegen.pIdentifiers = drmtl_getidentifiers(pMaterial); + codegen.identifierCount = drmtl_getidentifiercount(pMaterial); + codegen.indentationLevel = 0; + + if (drmtl_codegen_glsl_channel_function_begin(&codegen, pChannelHeader)) + { + drmtl_instruction* pInstructions = (drmtl_instruction*)(pChannelHeader + 1); + assert(pInstructions != NULL); + + if (drmtl_codegen_glsl_write_instructions(&codegen, pInstructions, pChannelHeader->instructionCount)) + { + bool result = drmtl_codegen_glsl_channel_function_close(&codegen); + if (result) + { + if (pBytesWrittenOut != NULL) + { + *pBytesWrittenOut = codegen.runningLength + 1; + } + } + + return result; + } + } + } + } + } + + return 0; +} + + + +bool drmtl_codegen_glsl_uniform(drmtl_codegen_glsl* pCodegen, drmtl_input* pInput) +{ + assert(pCodegen != NULL); + assert(pInput != NULL); + + if (pInput->identifierIndex < pCodegen->identifierCount) + { + drmtl_identifier* pIdentifier = pCodegen->pIdentifiers + pInput->identifierIndex; + assert(pIdentifier != NULL); + + // uniform ; + return + drmtl_codegen_glsl_write(pCodegen, "uniform ") && + drmtl_codegen_glsl_write_type(pCodegen, pIdentifier->type) && + drmtl_codegen_glsl_write(pCodegen, " ") && + drmtl_codegen_glsl_write(pCodegen, pIdentifier->name) && + drmtl_codegen_glsl_write(pCodegen, ";\n"); + } + + return 0; +} + +bool drmtl_codegen_glsl_uniforms(drmtl_material* pMaterial, char* codeOut, size_t codeOutSizeInBytes, size_t* pBytesWritteOut) +{ + if (pMaterial != NULL) + { + drmtl_codegen_glsl codegen; + codegen.pBufferOut = codeOut; + codegen.bufferOutSizeInBytes = codeOutSizeInBytes; + codegen.runningLength = 0; + codegen.pMaterial = pMaterial; + codegen.pIdentifiers = drmtl_getidentifiers(pMaterial); + codegen.identifierCount = drmtl_getidentifiercount(pMaterial); + codegen.indentationLevel = 0; + + + unsigned int inputCount = drmtl_getpublicinputcount(pMaterial); + if (inputCount > 0) + { + for (unsigned int iInput = 0; iInput < inputCount; ++iInput) + { + drmtl_input* pInput = drmtl_getpublicinputbyindex(pMaterial, iInput); + assert(pInput != NULL); + + if (!drmtl_codegen_glsl_uniform(&codegen, pInput)) + { + // There was an error writing one of the uniforms. Return false. + return 0; + } + } + } + else + { + // No inputs. Just write an empty string. + drmtl_codegen_glsl_write(&codegen, ""); + } + + if (pBytesWritteOut != NULL) + { + *pBytesWritteOut = codegen.runningLength + 1; + } + + return 1; + } + + return 0; +} + +#if defined(__clang__) + #pragma GCC diagnostic pop +#endif +#endif + +#if defined(__clang__) + #pragma GCC diagnostic pop +#endif +#endif + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ diff --git a/source/engine/thirdparty/dr_libs/old/dr_pcx.h b/source/engine/thirdparty/dr_libs/old/dr_pcx.h new file mode 100644 index 0000000..04f9adc --- /dev/null +++ b/source/engine/thirdparty/dr_libs/old/dr_pcx.h @@ -0,0 +1,793 @@ +// PCX image loader. Public domain. See "unlicense" statement at the end of this file. +// dr_pcx - v0.3.1 - 2018-09-11 +// +// David Reid - mackron@gmail.com + +// USAGE +// +// dr_pcx is a single-file library. To use it, do something like the following in one .c file. +// #define DR_PCX_IMPLEMENTATION +// #include "dr_pcx.h" +// +// You can then #include this file in other parts of the program as you would with any other header file. Do something like +// the following to load and decode an image: +// +// int width; +// int height; +// int components +// drpcx_uint8* pImageData = drpcx_load_file("my_image.pcx", DRPCX_FALSE, &width, &height, &components, 0); +// if (pImageData == NULL) { +// // Failed to load image. +// } +// +// ... +// +// drpcx_free(pImageData); +// +// The boolean parameter (second argument in the above example) is whether or not the image should be flipped upside down. +// +// +// +// OPTIONS +// #define these options before including this file. +// +// #define DR_PCX_NO_STDIO +// Disable drpcx_load_file(). +// +// +// +// QUICK NOTES +// - 2-bpp/4-plane and 4-bpp/1-plane formats have not been tested. + +#ifndef dr_pcx_h +#define dr_pcx_h + +#include + +#if defined(_MSC_VER) && _MSC_VER < 1600 +typedef signed char drpcx_int8; +typedef unsigned char drpcx_uint8; +typedef signed short drpcx_int16; +typedef unsigned short drpcx_uint16; +typedef signed int drpcx_int32; +typedef unsigned int drpcx_uint32; +typedef signed __int64 drpcx_int64; +typedef unsigned __int64 drpcx_uint64; +#else +#include +typedef int8_t drpcx_int8; +typedef uint8_t drpcx_uint8; +typedef int16_t drpcx_int16; +typedef uint16_t drpcx_uint16; +typedef int32_t drpcx_int32; +typedef uint32_t drpcx_uint32; +typedef int64_t drpcx_int64; +typedef uint64_t drpcx_uint64; +#endif +typedef drpcx_uint8 drpcx_bool8; +typedef drpcx_uint32 drpcx_bool32; +#define DRPCX_TRUE 1 +#define DRPCX_FALSE 0 + +#ifdef __cplusplus +extern "C" { +#endif + +// Callback for when data is read. Return value is the number of bytes actually read. +typedef size_t (* drpcx_read_proc)(void* userData, void* bufferOut, size_t bytesToRead); + + +// Loads a PCX file using the given callbacks. +drpcx_uint8* drpcx_load(drpcx_read_proc onRead, void* pUserData, drpcx_bool32 flipped, int* x, int* y, int* internalComponents, int desiredComponents); + +// Frees memory returned by drpcx_load() and family. +void drpcx_free(void* pReturnValueFromLoad); + + +#ifndef DR_PCX_NO_STDIO +// Loads an PCX file from an actual file. +drpcx_uint8* drpcx_load_file(const char* filename, drpcx_bool32 flipped, int* x, int* y, int* internalComponents, int desiredComponents); +#endif + +// Helper for loading an PCX file from a block of memory. +drpcx_uint8* drpcx_load_memory(const void* data, size_t dataSize, drpcx_bool32 flipped, int* x, int* y, int* internalComponents, int desiredComponents); + + +#ifdef __cplusplus +} +#endif + +#endif // dr_pcx_h + + +/////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef DR_PCX_IMPLEMENTATION +#include +#include +#include + +#ifndef DR_PCX_NO_STDIO +#include + +static size_t drpcx__on_read_stdio(void* pUserData, void* bufferOut, size_t bytesToRead) +{ + return fread(bufferOut, 1, bytesToRead, (FILE*)pUserData); +} + +drpcx_uint8* drpcx_load_file(const char* filename, drpcx_bool32 flipped, int* x, int* y, int* internalComponents, int desiredComponents) +{ + FILE* pFile; +#ifdef _MSC_VER + if (fopen_s(&pFile, filename, "rb") != 0) { + return NULL; + } +#else + pFile = fopen(filename, "rb"); + if (pFile == NULL) { + return NULL; + } +#endif + + drpcx_uint8* pImageData = drpcx_load(drpcx__on_read_stdio, pFile, flipped, x, y, internalComponents, desiredComponents); + + fclose(pFile); + return pImageData; +} +#endif // DR_PCX_NO_STDIO + + +typedef struct +{ + // A pointer to the beginning of the data. We use a char as the type here for easy offsetting. + const unsigned char* data; + size_t dataSize; + size_t currentReadPos; +} drpcx_memory; + +static size_t drpcx__on_read_memory(void* pUserData, void* bufferOut, size_t bytesToRead) +{ + drpcx_memory* memory = (drpcx_memory*)pUserData; + assert(memory != NULL); + assert(memory->dataSize >= memory->currentReadPos); + + size_t bytesRemaining = memory->dataSize - memory->currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + memcpy(bufferOut, memory->data + memory->currentReadPos, bytesToRead); + memory->currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +drpcx_uint8* drpcx_load_memory(const void* data, size_t dataSize, drpcx_bool32 flipped, int* x, int* y, int* internalComponents, int desiredComponents) +{ + drpcx_memory memory; + memory.data = (const unsigned char*)data; + memory.dataSize = dataSize; + memory.currentReadPos = 0; + return drpcx_load(drpcx__on_read_memory, &memory, flipped, x, y, internalComponents, desiredComponents); +} + + +typedef struct +{ + drpcx_uint8 header; + drpcx_uint8 version; + drpcx_uint8 encoding; + drpcx_uint8 bpp; + drpcx_uint16 left; + drpcx_uint16 top; + drpcx_uint16 right; + drpcx_uint16 bottom; + drpcx_uint16 hres; + drpcx_uint16 vres; + drpcx_uint8 palette16[48]; + drpcx_uint8 reserved1; + drpcx_uint8 bitPlanes; + drpcx_uint16 bytesPerLine; + drpcx_uint16 paletteType; + drpcx_uint16 screenSizeH; + drpcx_uint16 screenSizeV; + drpcx_uint8 reserved2[54]; +} drpcx_header; + +typedef struct +{ + drpcx_read_proc onRead; + void* pUserData; + drpcx_bool32 flipped; + drpcx_header header; + + drpcx_uint32 width; + drpcx_uint32 height; + drpcx_uint32 components; // 3 = RGB; 4 = RGBA. Only 3 and 4 are supported. + drpcx_uint8* pImageData; +} drpcx; + + +static drpcx_uint8 drpcx__read_byte(drpcx* pPCX) +{ + drpcx_uint8 byte = 0; + pPCX->onRead(pPCX->pUserData, &byte, 1); + + return byte; +} + +static drpcx_uint8* drpcx__row_ptr(drpcx* pPCX, drpcx_uint32 row) +{ + drpcx_uint32 stride = pPCX->width * pPCX->components; + + drpcx_uint8* pRow = pPCX->pImageData; + if (pPCX->flipped) { + pRow += (pPCX->height - row - 1) * stride; + } else { + pRow += row * stride; + } + + return pRow; +} + +static drpcx_uint8 drpcx__rle(drpcx* pPCX, drpcx_uint8* pRLEValueOut) +{ + drpcx_uint8 rleCount; + drpcx_uint8 rleValue; + + rleValue = drpcx__read_byte(pPCX); + if ((rleValue & 0xC0) == 0xC0) { + rleCount = rleValue & 0x3F; + rleValue = drpcx__read_byte(pPCX); + } else { + rleCount = 1; + } + + + *pRLEValueOut = rleValue; + return rleCount; +} + + +drpcx_bool32 drpcx__decode_1bit(drpcx* pPCX) +{ + drpcx_uint8 rleCount = 0; + drpcx_uint8 rleValue = 0; + + switch (pPCX->header.bitPlanes) + { + case 1: + { + for (drpcx_uint32 y = 0; y < pPCX->height; ++y) { + drpcx_uint8* pRow = drpcx__row_ptr(pPCX, y); + for (drpcx_uint32 x = 0; x < pPCX->header.bytesPerLine; ++x) { + if (rleCount == 0) { + rleCount = drpcx__rle(pPCX, &rleValue); + } + rleCount -= 1; + + for (int bit = 0; (bit < 8) && ((x*8 + bit) < pPCX->width); ++bit) { + drpcx_uint8 mask = (1 << (7 - bit)); + drpcx_uint8 paletteIndex = (rleValue & mask) >> (7 - bit); + + pRow[0] = paletteIndex * 255; + pRow[1] = paletteIndex * 255; + pRow[2] = paletteIndex * 255; + pRow += 3; + } + } + } + + return DRPCX_TRUE; + + } break; + + case 2: + case 3: + case 4: + { + for (drpcx_uint32 y = 0; y < pPCX->height; ++y) { + for (drpcx_uint32 c = 0; c < pPCX->header.bitPlanes; ++c) { + drpcx_uint8* pRow = drpcx__row_ptr(pPCX, y); + for (drpcx_uint32 x = 0; x < pPCX->header.bytesPerLine; ++x) { + if (rleCount == 0) { + rleCount = drpcx__rle(pPCX, &rleValue); + } + rleCount -= 1; + + for (int bit = 0; (bit < 8) && ((x*8 + bit) < pPCX->width); ++bit) { + drpcx_uint8 mask = (1 << (7 - bit)); + drpcx_uint8 paletteIndex = (rleValue & mask) >> (7 - bit); + + pRow[0] |= ((paletteIndex & 0x01) << c); + pRow += pPCX->components; + } + } + } + + + drpcx_uint8* pRow = drpcx__row_ptr(pPCX, y); + for (drpcx_uint32 x = 0; x < pPCX->width; ++x) { + drpcx_uint8 paletteIndex = pRow[0]; + for (drpcx_uint32 c = 0; c < pPCX->components; ++c) { + pRow[c] = pPCX->header.palette16[paletteIndex*3 + c]; + } + + pRow += pPCX->components; + } + } + + return DRPCX_TRUE; + } + + default: return DRPCX_FALSE; + } +} + +drpcx_bool32 drpcx__decode_2bit(drpcx* pPCX) +{ + drpcx_uint8 rleCount = 0; + drpcx_uint8 rleValue = 0; + + switch (pPCX->header.bitPlanes) + { + case 1: + { + drpcx_uint8 paletteCGA[48]; + paletteCGA[ 0] = 0x00; paletteCGA[ 1] = 0x00; paletteCGA[ 2] = 0x00; // #000000 + paletteCGA[ 3] = 0x00; paletteCGA[ 4] = 0x00; paletteCGA[ 5] = 0xAA; // #0000AA + paletteCGA[ 6] = 0x00; paletteCGA[ 7] = 0xAA; paletteCGA[ 8] = 0x00; // #00AA00 + paletteCGA[ 9] = 0x00; paletteCGA[10] = 0xAA; paletteCGA[11] = 0xAA; // #00AAAA + paletteCGA[12] = 0xAA; paletteCGA[13] = 0x00; paletteCGA[14] = 0x00; // #AA0000 + paletteCGA[15] = 0xAA; paletteCGA[16] = 0x00; paletteCGA[17] = 0xAA; // #AA00AA + paletteCGA[18] = 0xAA; paletteCGA[19] = 0x55; paletteCGA[20] = 0x00; // #AA5500 + paletteCGA[21] = 0xAA; paletteCGA[22] = 0xAA; paletteCGA[23] = 0xAA; // #AAAAAA + paletteCGA[24] = 0x55; paletteCGA[25] = 0x55; paletteCGA[26] = 0x55; // #555555 + paletteCGA[27] = 0x55; paletteCGA[28] = 0x55; paletteCGA[29] = 0xFF; // #5555FF + paletteCGA[30] = 0x55; paletteCGA[31] = 0xFF; paletteCGA[32] = 0x55; // #55FF55 + paletteCGA[33] = 0x55; paletteCGA[34] = 0xFF; paletteCGA[35] = 0xFF; // #55FFFF + paletteCGA[36] = 0xFF; paletteCGA[37] = 0x55; paletteCGA[38] = 0x55; // #FF5555 + paletteCGA[39] = 0xFF; paletteCGA[40] = 0x55; paletteCGA[41] = 0xFF; // #FF55FF + paletteCGA[42] = 0xFF; paletteCGA[43] = 0xFF; paletteCGA[44] = 0x55; // #FFFF55 + paletteCGA[45] = 0xFF; paletteCGA[46] = 0xFF; paletteCGA[47] = 0xFF; // #FFFFFF + + drpcx_uint8 cgaBGColor = pPCX->header.palette16[0] >> 4; + drpcx_uint8 i = (pPCX->header.palette16[3] & 0x20) >> 5; + drpcx_uint8 p = (pPCX->header.palette16[3] & 0x40) >> 6; + //drpcx_uint8 c = (pPCX->header.palette16[3] & 0x80) >> 7; // Color or monochrome. How is monochrome handled? + + for (drpcx_uint32 y = 0; y < pPCX->height; ++y) { + drpcx_uint8* pRow = drpcx__row_ptr(pPCX, y); + for (drpcx_uint32 x = 0; x < pPCX->header.bytesPerLine; ++x) { + if (rleCount == 0) { + rleCount = drpcx__rle(pPCX, &rleValue); + } + rleCount -= 1; + + for (int bit = 0; bit < 4; ++bit) { + if (x*4 + bit < pPCX->width) { + drpcx_uint8 mask = (3 << ((3 - bit) * 2)); + drpcx_uint8 paletteIndex = (rleValue & mask) >> ((3 - bit) * 2); + + drpcx_uint8 cgaIndex; + if (paletteIndex == 0) { // Background. + cgaIndex = cgaBGColor; + } else { // Foreground + cgaIndex = (((paletteIndex << 1) + p) + (i << 3)); + } + + pRow[0] = paletteCGA[cgaIndex*3 + 0]; + pRow[1] = paletteCGA[cgaIndex*3 + 1]; + pRow[2] = paletteCGA[cgaIndex*3 + 2]; + pRow += 3; + } + } + } + } + + // TODO: According to http://www.fysnet.net/pcxfile.htm, we should use the palette at the end of the file + // instead of the standard CGA palette if the version is equal to 5. With my test files the palette + // at the end of the file does not exist. Research this one. + if (pPCX->header.version == 5) { + drpcx_uint8 paletteMarker = drpcx__read_byte(pPCX); + if (paletteMarker == 0x0C) { + // TODO: Implement Me. + } + } + + return DRPCX_TRUE; + }; + + case 4: + { + // NOTE: This is completely untested. If anybody knows where I can get a test file please let me know or send it through to me! + // TODO: Test Me. + + for (drpcx_uint32 y = 0; y < pPCX->height; ++y) { + for (drpcx_uint32 c = 0; c < pPCX->header.bitPlanes; ++c) { + drpcx_uint8* pRow = drpcx__row_ptr(pPCX, y); + for (drpcx_uint32 x = 0; x < pPCX->header.bytesPerLine; ++x) { + if (rleCount == 0) { + rleCount = drpcx__rle(pPCX, &rleValue); + } + rleCount -= 1; + + for (int bitpair = 0; (bitpair < 4) && ((x*4 + bitpair) < pPCX->width); ++bitpair) { + drpcx_uint8 mask = (4 << (3 - bitpair)); + drpcx_uint8 paletteIndex = (rleValue & mask) >> (3 - bitpair); + + pRow[0] |= ((paletteIndex & 0x03) << (c*2)); + pRow += pPCX->components; + } + } + } + + + drpcx_uint8* pRow = drpcx__row_ptr(pPCX, y); + for (drpcx_uint32 x = 0; x < pPCX->width; ++x) { + drpcx_uint8 paletteIndex = pRow[0]; + for (drpcx_uint32 c = 0; c < pPCX->header.bitPlanes; ++c) { + pRow[c] = pPCX->header.palette16[paletteIndex*3 + c]; + } + + pRow += pPCX->components; + } + } + + return DRPCX_TRUE; + }; + + default: return DRPCX_FALSE; + } +} + +drpcx_bool32 drpcx__decode_4bit(drpcx* pPCX) +{ + // NOTE: This is completely untested. If anybody knows where I can get a test file please let me know or send it through to me! + // TODO: Test Me. + + if (pPCX->header.bitPlanes > 1) { + return DRPCX_FALSE; + } + + drpcx_uint8 rleCount = 0; + drpcx_uint8 rleValue = 0; + + for (drpcx_uint32 y = 0; y < pPCX->height; ++y) { + for (drpcx_uint32 c = 0; c < pPCX->header.bitPlanes; ++c) { + drpcx_uint8* pRow = drpcx__row_ptr(pPCX, y); + for (drpcx_uint32 x = 0; x < pPCX->header.bytesPerLine; ++x) { + if (rleCount == 0) { + rleCount = drpcx__rle(pPCX, &rleValue); + } + rleCount -= 1; + + for (int nibble = 0; (nibble < 2) && ((x*2 + nibble) < pPCX->width); ++nibble) + { + drpcx_uint8 mask = (4 << (1 - nibble)); + drpcx_uint8 paletteIndex = (rleValue & mask) >> (1 - nibble); + + pRow[0] |= ((paletteIndex & 0x0F) << (c*4)); + pRow += pPCX->components; + } + } + } + + + drpcx_uint8* pRow = drpcx__row_ptr(pPCX, y); + for (drpcx_uint32 x = 0; x < pPCX->width; ++x) { + drpcx_uint8 paletteIndex = pRow[0]; + for (drpcx_uint32 c = 0; c < pPCX->components; ++c) { + pRow[c] = pPCX->header.palette16[paletteIndex*3 + c]; + } + + pRow += pPCX->components; + } + } + + return DRPCX_TRUE; +} + +drpcx_bool32 drpcx__decode_8bit(drpcx* pPCX) +{ + drpcx_uint8 rleCount = 0; + drpcx_uint8 rleValue = 0; + drpcx_uint32 stride = pPCX->width * pPCX->components; + + switch (pPCX->header.bitPlanes) + { + case 1: + { + for (drpcx_uint32 y = 0; y < pPCX->height; ++y) { + drpcx_uint8* pRow = drpcx__row_ptr(pPCX, y); + for (drpcx_uint32 x = 0; x < pPCX->header.bytesPerLine; ++x) { + if (rleCount == 0) { + rleCount = drpcx__rle(pPCX, &rleValue); + } + rleCount -= 1; + + if (x < pPCX->width) { + pRow[0] = rleValue; + pRow[1] = rleValue; + pRow[2] = rleValue; + pRow += 3; + } + } + } + + // At this point we can know if we are dealing with a palette or a grayscale image by checking the next byte. If it's equal to 0x0C, we + // need to do a simple palette lookup. + drpcx_uint8 paletteMarker = drpcx__read_byte(pPCX); + if (paletteMarker == 0x0C) { + // A palette is present - we need to do a second pass. + drpcx_uint8 palette256[768]; + if (pPCX->onRead(pPCX->pUserData, palette256, sizeof(palette256)) != sizeof(palette256)) { + return DRPCX_FALSE; + } + + for (drpcx_uint32 y = 0; y < pPCX->height; ++y) { + drpcx_uint8* pRow = pPCX->pImageData + (y * stride); + for (drpcx_uint32 x = 0; x < pPCX->width; ++x) { + drpcx_uint8 index = pRow[0]; + pRow[0] = palette256[index*3 + 0]; + pRow[1] = palette256[index*3 + 1]; + pRow[2] = palette256[index*3 + 2]; + pRow += 3; + } + } + } + + return DRPCX_TRUE; + } + + case 3: + case 4: + { + for (drpcx_uint32 y = 0; y < pPCX->height; ++y) { + for (drpcx_uint32 c = 0; c < pPCX->components; ++c) { + drpcx_uint8* pRow = drpcx__row_ptr(pPCX, y); + for (drpcx_uint32 x = 0; x < pPCX->header.bytesPerLine; ++x) { + if (rleCount == 0) { + rleCount = drpcx__rle(pPCX, &rleValue); + } + rleCount -= 1; + + if (x < pPCX->width) { + pRow[c] = rleValue; + pRow += pPCX->components; + } + } + } + } + + return DRPCX_TRUE; + } + } + + return DRPCX_TRUE; +} + +drpcx_uint8* drpcx_load(drpcx_read_proc onRead, void* pUserData, drpcx_bool32 flipped, int* x, int* y, int* internalComponents, int desiredComponents) +{ + if (onRead == NULL) return NULL; + if (desiredComponents > 4) return NULL; + + drpcx pcx; + pcx.onRead = onRead; + pcx.pUserData = pUserData; + pcx.flipped = flipped; + if (onRead(pUserData, &pcx.header, sizeof(pcx.header)) != sizeof(pcx.header)) { + return NULL; // Failed to read the header. + } + + if (pcx.header.header != 10) { + return NULL; // Not a PCX file. + } + + if (pcx.header.encoding != 1) { + return NULL; // Not supporting non-RLE encoding. Would assume a value of 0 indicates raw, unencoded, but that is apparently never used. + } + + if (pcx.header.bpp != 1 && pcx.header.bpp != 2 && pcx.header.bpp != 4 && pcx.header.bpp != 8) { + return NULL; // Unsupported pixel format. + } + + + if (pcx.header.left > pcx.header.right) { + drpcx_uint16 temp = pcx.header.left; + pcx.header.left = pcx.header.right; + pcx.header.right = temp; + } + if (pcx.header.top > pcx.header.bottom) { + drpcx_uint16 temp = pcx.header.top; + pcx.header.top = pcx.header.bottom; + pcx.header.bottom = temp; + } + + pcx.width = pcx.header.right - pcx.header.left + 1; + pcx.height = pcx.header.bottom - pcx.header.top + 1; + pcx.components = (pcx.header.bpp == 8 && pcx.header.bitPlanes == 4) ? 4 : 3; + + size_t dataSize = pcx.width * pcx.height * pcx.components; + pcx.pImageData = (drpcx_uint8*)calloc(1, dataSize); // <-- Clearing to zero is important! Required for proper decoding. + if (pcx.pImageData == NULL) { + return NULL; // Failed to allocate memory. + } + + drpcx_bool32 result = DRPCX_FALSE; + switch (pcx.header.bpp) + { + case 1: + { + result = drpcx__decode_1bit(&pcx); + } break; + + case 2: + { + result = drpcx__decode_2bit(&pcx); + } break; + + case 4: + { + result = drpcx__decode_4bit(&pcx); + } break; + + case 8: + { + result = drpcx__decode_8bit(&pcx); + } break; + } + + if (!result) { + free(pcx.pImageData); + return NULL; + } + + // There's an annoying amount of branching when loading PCX files so for simplicity I'm doing the component conversion as + // a second pass. + if (desiredComponents == 0) desiredComponents = pcx.components; + if (desiredComponents != (int)pcx.components) { + drpcx_uint8* pNewImageData = (drpcx_uint8*)malloc(pcx.width * pcx.height * desiredComponents); + if (pNewImageData == NULL) { + free(pcx.pImageData); + return NULL; + } + + drpcx_uint8* pSrcData = pcx.pImageData; + drpcx_uint8* pDstData = pNewImageData; + if (desiredComponents < (int)pcx.components) { + // We're reducing the number of components. Just drop the excess. + for (drpcx_uint32 i = 0; i < pcx.width*pcx.height; ++i) { + for (int c = 0; c < desiredComponents; ++c) { + pDstData[c] = pSrcData[c]; + } + + pSrcData += pcx.components; + pDstData += desiredComponents; + } + } else { + // We're increasing the number of components. Always ensure the alpha channel is set to 0xFF. + if (pcx.components == 1) { + for (drpcx_uint32 i = 0; i < pcx.width*pcx.height; ++i) { + for (int c = 0; c < desiredComponents; ++c) { + pDstData[c] = pSrcData[0]; + } + + pSrcData += pcx.components; + pDstData += desiredComponents; + } + } else if (pcx.components == 2) { + for (drpcx_uint32 i = 0; i < pcx.width*pcx.height; ++i) { + pDstData[0] = pSrcData[0]; + pDstData[1] = pSrcData[1]; + pDstData[2] = 0x00; + if (desiredComponents == 4) pDstData[3] = 0xFF; + + pSrcData += pcx.components; + pDstData += desiredComponents; + } + } else { + assert(pcx.components == 3); + assert(desiredComponents == 4); + for (drpcx_uint32 i = 0; i < pcx.width*pcx.height; ++i) { + pDstData[0] = pSrcData[0]; + pDstData[1] = pSrcData[1]; + pDstData[2] = pSrcData[2]; + pDstData[3] = 0xFF; + + pSrcData += pcx.components; + pDstData += desiredComponents; + } + } + } + + free(pcx.pImageData); + pcx.pImageData = pNewImageData; + } + + if (x) *x = pcx.width; + if (y) *y = pcx.height; + if (internalComponents) *internalComponents = pcx.components; + return pcx.pImageData; +} + +void drpcx_free(void* pReturnValueFromLoad) +{ + free(pReturnValueFromLoad); +} + +#endif // DR_PCX_IMPLEMENTATION + + +// REVISION HISTORY +// +// v0.3.1 - 2018-09-11 +// - Styling fixes. +// - Fix a typo. +// +// v0.3 - 2018-02-08 +// - API CHANGE: Rename dr_* types to drpcx_*. +// +// v0.2c - 2018-02-07 +// - Fix a crash. +// +// v0.2b - 2018-02-02 +// - Fix compilation error. +// +// v0.2a - 2017-07-16 +// - Change underlying type for booleans to unsigned. +// +// v0.2 - 2016-10-28 +// - API CHANGE: Add a parameter to drpcx_load() and family to control the number of output components. +// - Use custom sized types rather than built-in ones to improve support for older MSVC compilers. +// +// v0.1c - 2016-10-23 +// - A minor change to drpcx_bool8 and drpcx_bool32 types. +// +// v0.1b - 2016-10-11 +// - Use drpcx_bool32 instead of the built-in "bool" type. The reason for this change is that it helps maintain API/ABI consistency +// between C and C++ builds. +// +// v0.1a - 2016-09-18 +// - Change date format to ISO 8601 (YYYY-MM-DD) +// +// v0.1 - 2016-05-04 +// - Initial versioned release. + + +// TODO +// - Test 2-bpp/4-plane and 4-bpp/1-plane formats. + + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ + diff --git a/source/engine/thirdparty/dr_libs/tests/README.md b/source/engine/thirdparty/dr_libs/tests/README.md new file mode 100644 index 0000000..9a0eb4b --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/README.md @@ -0,0 +1,49 @@ +Building +======== +Move into this directory and run the build script for the relevant platform. Run from this directory. + + clear && ./build_opus && ./bin/dr_opus_test_0 + +Alternatively you can compile a specific test manually: + + clear && gcc ./opus/dr_opus_test_0 -o ./bin/dr_opus_test_0 && ./bin/dr_opus_test_0 + +Test vectors will be loaded from the "testvectors" folder, relative to this directory. Therefore, you need to run +each test program from this directory: + + ./bin/dr_opus_test_0 + + +Building and Running WAV Tests +------------------------------ +The WAV tests use libsndfile as a benchmark. The tests programs dynamically link to libsndfile at runtime which +means you don't need to link to it at compile time. However, you will need the headers installed in a standard +location. The batch files for the Windows build will allow you to customize the include path. On the Windows build +you will need to drop two versions of libsndfile-1.dll into the bin directory. For the 32-bit build you need to +name it libsndfile-1-x86.dll and for the 64-bit build you need to name it libsndfile-1-x64.dll. + + +Test Vectors +============ +In order to run certain tests you will need to download test vectors for the relevant project and place them into the +"testvectors" folder. + +Opus +---- +- Download both the original and new test vectors from https://opus-codec.org/testvectors/ and place them into + the "testvectors/opus" folder. +- Download the Ogg Opus test vectors from https://wiki.xiph.org/OggOpus/testvectors and place them into the + "testvectors/opus" folder. +- The folder structure should like like the following: + - testvectors + - opus + - opus_testvectors + - opus_newvectors + - oggopus + - failure_cases + - opus_multichannel_examples + +FLAC +---- +- Download the FLAC testbench from https://wiki.hydrogenaud.io/index.php?title=FLAC_decoder_testbench and place + them into the "testvectors/flac/testbench" folder. diff --git a/source/engine/thirdparty/dr_libs/tests/bin/DO_NOT_DELETE.md b/source/engine/thirdparty/dr_libs/tests/bin/DO_NOT_DELETE.md new file mode 100644 index 0000000..e69de29 diff --git a/source/engine/thirdparty/dr_libs/tests/build_flac_linux.sh b/source/engine/thirdparty/dr_libs/tests/build_flac_linux.sh new file mode 100755 index 0000000..21297f0 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/build_flac_linux.sh @@ -0,0 +1,4 @@ +#!/bin/sh +gcc ./flac/dr_flac_test_0.c -o ./bin/dr_flac_test_0 -std=c89 -ansi -pedantic -O3 -s -Wall -ldl +gcc ./flac/dr_flac_decoding.c -o ./bin/dr_flac_decoding -std=c89 -ansi -pedantic -Wall -O3 -s -lFLAC -ldl +gcc ./flac/dr_flac_seeking.c -o ./bin/dr_flac_seeking -std=c89 -ansi -pedantic -Wall -O3 -s -lFLAC -ldl diff --git a/source/engine/thirdparty/dr_libs/tests/build_flac_win32.bat b/source/engine/thirdparty/dr_libs/tests/build_flac_win32.bat new file mode 100644 index 0000000..56c2a67 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/build_flac_win32.bat @@ -0,0 +1,37 @@ +:: NOTES +:: +:: These tests use libFLAC as a benchmark. Since Windows doesn't have good standard paths for library files, this +:: script will use "flac/include" as an additional search path for headers and "flac/lib/win32" as an additional +:: search path for libraries. You will need to place FLAC headers in the "flac/include/FLAC" directory and libogg +:: headers int the "flac/include/ogg" directory. +:: +:: Examples are linked against "-lFLAC" and "-logg". These need to be placed in a standard directory or "flac/lib/win32". + +@echo off +SET c_compiler=gcc +SET cpp_compiler=g++ + +:: Configure the "arch" option to test different instruction sets. +SET arch= +SET arch=-msse4.1 +::SET arch=-mfpu=neon + +:: libFLAC and libogg are required for benchmarking. +SET libFLAC=-I./flac/include -L./flac/lib/win32 -lFLAC -logg + +:: C options +SET c_options=-std=c89 -ansi + +:: C++ options +SET cpp_options= + +SET options=-Wall -Wpedantic -pedantic -O3 -s -DNDEBUG %arch% %libFLAC% + +SET buildc=%c_compiler% %c_options% +SET buildcpp=%cpp_compiler% %cpp_options% +@echo on + +%buildc% ./flac/dr_flac_test_0.c -o ./bin/dr_flac_test_0.exe %options% +%buildcpp% ./flac/dr_flac_test_0.cpp -o ./bin/dr_flac_test_0_cpp.exe %options% +%buildc% ./flac/dr_flac_decoding.c -o ./bin/dr_flac_decoding.exe %options% +%buildcpp% ./flac/dr_flac_decoding.cpp -o ./bin/dr_flac_decoding_cpp.exe %options% \ No newline at end of file diff --git a/source/engine/thirdparty/dr_libs/tests/build_mp3_win32.bat b/source/engine/thirdparty/dr_libs/tests/build_mp3_win32.bat new file mode 100644 index 0000000..2692cff --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/build_mp3_win32.bat @@ -0,0 +1,2 @@ +gcc ./mp3/dr_mp3_test_0.c -o ./bin/dr_mp3_test_0.exe -std=c89 -ansi -pedantic -Wall +g++ ./mp3/dr_mp3_test_0.cpp -o ./bin/dr_mp3_test_0.exe -pedantic -Wall \ No newline at end of file diff --git a/source/engine/thirdparty/dr_libs/tests/build_opus_win32.bat b/source/engine/thirdparty/dr_libs/tests/build_opus_win32.bat new file mode 100644 index 0000000..dbb8a85 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/build_opus_win32.bat @@ -0,0 +1 @@ +gcc ./opus/dr_opus_decoding.c -o ./bin/dr_opus_decoding.exe -std=c89 -ansi -pedantic -Wall -Wextra \ No newline at end of file diff --git a/source/engine/thirdparty/dr_libs/tests/build_wav_win32.bat b/source/engine/thirdparty/dr_libs/tests/build_wav_win32.bat new file mode 100644 index 0000000..e0f948c --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/build_wav_win32.bat @@ -0,0 +1,38 @@ +:: NOTES +:: +:: These tests use libsndfile as a benchmark. Since Windows doesn't have good standard paths for library files, this +:: script will use "wav/include" as an additional search path for headers. You will need to place sndfile.h to the +:: "wav/include" directory. Tests will link to libsndfile dynamically at run-time. On the Windows build you'll just +:: need to put a copy of the 32- and 64-bit versions of libsndfile-1.dll into the "bin" directory, with the names +:: libsndfile-1-x86.dll and libsndfile-1-x64.dll respectively. Both versions are required so that both the 32- and +:: 64-bit builds can be tested and benchmarked. + +@echo off + +SET c_compiler=gcc +SET cpp_compiler=g++ + +:: Configure the "arch" option to test different instruction sets. +SET arch= +SET arch=-msse4.1 +::SET arch=-mfpu=neon + +:: libsndfile is required for benchmarking. +SET libsndfile=-I./wav/include + +:: C options +SET c_options=-std=c89 -ansi + +:: C++ options +SET cpp_options= + +SET options=-Wall -Wpedantic -pedantic -O3 -s -DNDEBUG %arch% %libsndfile% + +SET buildc=%c_compiler% %c_options% %options% +SET buildcpp=%cpp_compiler% %cpp_options% %options% +@echo on + +%buildc% ./wav/dr_wav_test_0.c -o ./bin/dr_wav_test_0.exe +%buildcpp% ./wav/dr_wav_test_0.cpp -o ./bin/dr_wav_test_0_cpp.exe +%buildc% ./wav/dr_wav_decoding.c -o ./bin/dr_wav_decoding.exe +%buildcpp% ./wav/dr_wav_decoding.cpp -o ./bin/dr_wav_decoding_cpp.exe \ No newline at end of file diff --git a/source/engine/thirdparty/dr_libs/tests/common/dr_common.c b/source/engine/thirdparty/dr_libs/tests/common/dr_common.c new file mode 100644 index 0000000..46b34b8 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/common/dr_common.c @@ -0,0 +1,957 @@ + +#ifdef _WIN32 +#include +#endif + +#if defined(_MSC_VER) || defined(__DMC__) +#else +#include +#endif + +#include +#include +#include /* So we can seed the random number generator based on time. */ +#include + +#if !defined(_WIN32) +#include +#include +#include +#include +#include +#endif + +#include /* For size_t. */ + +/* Sized types. Prefer built-in types. Fall back to stdint. */ +#ifdef _MSC_VER + #if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wlanguage-extension-token" + #pragma GCC diagnostic ignored "-Wlong-long" + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif + typedef signed __int8 dr_int8; + typedef unsigned __int8 dr_uint8; + typedef signed __int16 dr_int16; + typedef unsigned __int16 dr_uint16; + typedef signed __int32 dr_int32; + typedef unsigned __int32 dr_uint32; + typedef signed __int64 dr_int64; + typedef unsigned __int64 dr_uint64; + #if defined(__clang__) + #pragma GCC diagnostic pop + #endif +#else + #define MA_HAS_STDINT + #include + typedef int8_t dr_int8; + typedef uint8_t dr_uint8; + typedef int16_t dr_int16; + typedef uint16_t dr_uint16; + typedef int32_t dr_int32; + typedef uint32_t dr_uint32; + typedef int64_t dr_int64; + typedef uint64_t dr_uint64; +#endif + +#ifdef MA_HAS_STDINT + typedef uintptr_t dr_uintptr; +#else + #if defined(_WIN32) + #if defined(_WIN64) + typedef dr_uint64 dr_uintptr; + #else + typedef dr_uint32 dr_uintptr; + #endif + #elif defined(__GNUC__) + #if defined(__LP64__) + typedef dr_uint64 dr_uintptr; + #else + typedef dr_uint32 dr_uintptr; + #endif + #else + typedef dr_uint64 dr_uintptr; /* Fallback. */ + #endif +#endif + +typedef dr_uint8 dr_bool8; +typedef dr_uint32 dr_bool32; +#define DR_TRUE 1 +#define DR_FALSE 0 + +typedef void* dr_handle; +typedef void* dr_ptr; +typedef void (* dr_proc)(void); + +#if defined(SIZE_MAX) + #define DR_SIZE_MAX SIZE_MAX +#else + #define DR_SIZE_MAX 0xFFFFFFFF +#endif + +/* +Return Values: + 0: Success + 22: EINVAL + 34: ERANGE + +Not using symbolic constants for errors because I want to avoid #including errno.h +*/ +int dr_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src) +{ + size_t i; + + if (dst == 0) { + return 22; + } + if (dstSizeInBytes == 0) { + return 34; + } + if (src == 0) { + dst[0] = '\0'; + return 22; + } + + for (i = 0; i < dstSizeInBytes && src[i] != '\0'; ++i) { + dst[i] = src[i]; + } + + if (i < dstSizeInBytes) { + dst[i] = '\0'; + return 0; + } + + dst[0] = '\0'; + return 34; +} + +int dr_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) +{ + size_t maxcount; + size_t i; + + if (dst == 0) { + return 22; + } + if (dstSizeInBytes == 0) { + return 34; + } + if (src == 0) { + dst[0] = '\0'; + return 22; + } + + maxcount = count; + if (count == ((size_t)-1) || count >= dstSizeInBytes) { /* -1 = _TRUNCATE */ + maxcount = dstSizeInBytes - 1; + } + + for (i = 0; i < maxcount && src[i] != '\0'; ++i) { + dst[i] = src[i]; + } + + if (src[i] == '\0' || i == count || count == ((size_t)-1)) { + dst[i] = '\0'; + return 0; + } + + dst[0] = '\0'; + return 34; +} + +int dr_strcat_s(char* dst, size_t dstSizeInBytes, const char* src) +{ + char* dstorig; + + if (dst == 0) { + return 22; + } + if (dstSizeInBytes == 0) { + return 34; + } + if (src == 0) { + dst[0] = '\0'; + return 22; + } + + dstorig = dst; + + while (dstSizeInBytes > 0 && dst[0] != '\0') { + dst += 1; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes == 0) { + return 22; /* Unterminated. */ + } + + + while (dstSizeInBytes > 0 && src[0] != '\0') { + *dst++ = *src++; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes > 0) { + dst[0] = '\0'; + } else { + dstorig[0] = '\0'; + return 34; + } + + return 0; +} + +int dr_strncat_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) +{ + char* dstorig; + + if (dst == 0) { + return 22; + } + if (dstSizeInBytes == 0) { + return 34; + } + if (src == 0) { + return 22; + } + + dstorig = dst; + + while (dstSizeInBytes > 0 && dst[0] != '\0') { + dst += 1; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes == 0) { + return 22; /* Unterminated. */ + } + + + if (count == ((size_t)-1)) { /* _TRUNCATE */ + count = dstSizeInBytes - 1; + } + + while (dstSizeInBytes > 0 && src[0] != '\0' && count > 0) { + *dst++ = *src++; + dstSizeInBytes -= 1; + count -= 1; + } + + if (dstSizeInBytes > 0) { + dst[0] = '\0'; + } else { + dstorig[0] = '\0'; + return 34; + } + + return 0; +} + +/* +String Helpers +*/ +int dr_append_path(char* dst, size_t dstSize, const char* base, const char* other) +{ + int err; + size_t len; + + /* TODO: Return the correct error codes here. */ + if (dst == NULL) { + return -1; + } + if (base == NULL || other == NULL) { + return -1; + } + + err = dr_strcpy_s(dst, dstSize, base); + if (err != 0) { + return err; + } + + len = strlen(dst); + if (len > 0) { + /* Append the slash if required. */ + if (dst[len-1] != '/' && dst[len-1] != '\\') { + err = dr_strcat_s(dst, dstSize, "/"); + if (err != 0) { + dst[0] = '\0'; + return err; + } + + len += 1; /* +1 to the length to account for the slash. */ + } + } + + err = dr_strcat_s(dst, dstSize, other); + if (err != 0) { + dst[0] = '\0'; + return err; + } + + /* Success. */ + return 0; +} + +const char* dr_path_file_name(const char* path) +{ + const char* fileName = path; + + if (path == NULL) { + return NULL; + } + + /* We just loop through the path until we find the last slash. */ + while (path[0] != '\0') { + if (path[0] == '/' || path[0] == '\\') { + fileName = path; + } + + path += 1; + } + + /* At this point the file name is sitting on a slash, so just move forward. */ + while (fileName[0] != '\0' && (fileName[0] == '/' || fileName[0] == '\\')) { + fileName += 1; + } + + return fileName; +} + +const char* dr_extension(const char* path) +{ + const char* extension = path; + const char* lastoccurance = NULL; + + if (path == NULL) { + return NULL; + } + + /* Just find the last '.' and return. */ + while (extension[0] != '\0') { + if (extension[0] == '.') { + extension += 1; + lastoccurance = extension; + } + + extension += 1; + } + + return (lastoccurance != 0) ? lastoccurance : extension; +} + +dr_bool32 dr_extension_equal(const char* path, const char* extension) +{ + const char* ext1; + const char* ext2; + + if (path == NULL || extension == NULL) { + return 0; + } + + ext1 = extension; + ext2 = dr_extension(path); + +#if defined(_MSC_VER) || defined(__DMC__) + return _stricmp(ext1, ext2) == 0; +#else + return strcasecmp(ext1, ext2) == 0; +#endif +} + + + + +/* +File Iterator + +dr_file_iterator state; +dr_file_iterator* pFile = dr_file_iterator_begin("the/folder/path", &state); +while (pFile != NULL) { + // Do something with pFile. + pFile = dr_file_iterator_next(pFile); +} + +Limitations: + - Only supports file paths up to 256 characters. +*/ +typedef struct +{ + char folderPath[256]; + char relativePath[256]; /* Relative to the original folder path. */ + char absolutePath[256]; /* Concatenation of folderPath and relativePath. */ + dr_bool32 isDirectory; +#ifdef _WIN32 + HANDLE hFind; +#else + DIR* dir; +#endif +} dr_file_iterator; + +dr_file_iterator* dr_file_iterator_begin(const char* pFolderPath, dr_file_iterator* pState) +{ +#ifdef _WIN32 + char searchQuery[MAX_PATH]; + unsigned int searchQueryLength; + WIN32_FIND_DATAA ffd; + HANDLE hFind; +#else + DIR* dir; +#endif + + if (pState == NULL) { + return NULL; + } + + memset(pState, 0, sizeof(*pState)); + + if (pFolderPath == NULL) { + return NULL; + } + +#ifdef _WIN32 + dr_strcpy_s(searchQuery, sizeof(searchQuery), pFolderPath); + + searchQueryLength = (unsigned int)strlen(searchQuery); + if (searchQueryLength >= MAX_PATH - 3) { + return NULL; /* Path is too long. */ + } + + searchQuery[searchQueryLength + 0] = '\\'; + searchQuery[searchQueryLength + 1] = '*'; + searchQuery[searchQueryLength + 2] = '\0'; + + hFind = FindFirstFileA(searchQuery, &ffd); + if (hFind == INVALID_HANDLE_VALUE) { + return NULL; /* Failed to begin search. */ + } + + /* Skip past "." and ".." directories. */ + while (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) { + if (!FindNextFileA(hFind, &ffd)) { + FindClose(hFind); + return NULL; /* Couldn't find anything. */ + } + } + + pState->hFind = hFind; + + + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + pState->isDirectory = 1; + } else { + pState->isDirectory = 0; + } + + dr_strcpy_s(pState->relativePath, sizeof(pState->relativePath), ffd.cFileName); +#else + dir = opendir(pFolderPath); + if (dir == NULL) { + return NULL; /* Couldn't find anything. */ + } + + /* Select the first file. */ + for (;;) { + struct dirent* info; + struct stat fileinfo; + char filePath[4096]; + + info = readdir(dir); + if (info == NULL) { + closedir(dir); + return NULL; + } + + if (strcmp(info->d_name, ".") == 0 || strcmp(info->d_name, "..") == 0) { + continue; /* Skip past "." and ".." directories. */ + } + + dr_strcpy_s(pState->relativePath, sizeof(pState->relativePath), info->d_name); + dr_append_path(filePath, sizeof(filePath), pFolderPath, pState->relativePath); + + if (stat(filePath, &fileinfo) != 0) { + continue; + } + + if (S_ISDIR(fileinfo.st_mode)) { + pState->isDirectory = 1; + } else { + pState->isDirectory = 0; + } + + break; + } + + pState->dir = dir; +#endif + + /* Getting here means everything was successful. We can now set some state before returning. */ + dr_strcpy_s(pState->folderPath, sizeof(pState->folderPath), pFolderPath); + dr_append_path(pState->absolutePath, sizeof(pState->absolutePath), pState->folderPath, pState->relativePath); + + return pState; +} + +dr_file_iterator* dr_file_iterator_next(dr_file_iterator* pState) +{ +#ifdef _WIN32 + WIN32_FIND_DATAA ffd; +#endif + + if (pState == NULL) { + return NULL; + } + +#ifdef _WIN32 + if (!FindNextFileA(pState->hFind, &ffd)) { + /* Couldn't find anything else. */ + FindClose(pState->hFind); + pState->hFind = NULL; + return NULL; + } + + /* Found something. */ + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + pState->isDirectory = 1; + } else { + pState->isDirectory = 0; + } + + dr_strcpy_s(pState->relativePath, sizeof(pState->relativePath), ffd.cFileName); +#else + /* Enter a loop here so we can skip past "." and ".." directories. */ + for (;;) { + struct dirent* info; + struct stat fileinfo; + char filePath[4096]; + + info = readdir(pState->dir); + if (info == NULL) { + closedir(pState->dir); + pState->dir = NULL; + return NULL; + } + + if (strcmp(info->d_name, ".") == 0 || strcmp(info->d_name, "..") == 0) { + continue; /* Skip past "." and ".." directories. */ + } + + dr_strcpy_s(pState->relativePath, sizeof(pState->relativePath), info->d_name); + dr_append_path(filePath, sizeof(filePath), pState->folderPath, pState->relativePath); + + /*printf("Done: %s\n", pState->relativePath);*/ + + if (stat(filePath, &fileinfo) != 0) { + continue; + } + + if (S_ISDIR(fileinfo.st_mode)) { + pState->isDirectory = 1; + } else { + pState->isDirectory = 0; + } + + break; + } +#endif + + /* Success */ + dr_append_path(pState->absolutePath, sizeof(pState->absolutePath), pState->folderPath, pState->relativePath); + return pState; +} + +void dr_file_iterator_end(dr_file_iterator* pState) +{ + if (pState == NULL) { + return; + } + +#ifdef _WIN32 + FindClose(pState->hFind); + pState->hFind = NULL; +#else + closedir(pState->dir); + pState->dir = NULL; +#endif +} + + +/* +File Management + +Free file data with free(). +*/ +static int dr_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err; +#endif + + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return -1; /* Invalid args. */ + } + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + err = fopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return err; + } +#else +#if defined(_WIN32) || defined(__APPLE__) + *ppFile = fopen(pFilePath, pOpenMode); +#else + #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) + *ppFile = fopen64(pFilePath, pOpenMode); + #else + *ppFile = fopen(pFilePath, pOpenMode); + #endif +#endif + if (*ppFile == NULL) { + return errno; + } +#endif + + return 0; +} + +void* dr_open_and_read_file_with_extra_data(const char* pFilePath, size_t* pFileSizeOut, size_t extraBytes) +{ + FILE* pFile; + size_t fileSize; + size_t bytesRead; + void* pFileData; + + /* Safety. */ + if (pFileSizeOut) { + *pFileSizeOut = 0; + } + + if (pFilePath == NULL) { + return NULL; + } + + /* TODO: Use 64-bit versions of the FILE APIs. */ + + if (dr_fopen(&pFile, pFilePath, "rb") != 0) { + return NULL; + } + + fseek(pFile, 0, SEEK_END); + fileSize = ftell(pFile); + fseek(pFile, 0, SEEK_SET); + + /* Need to make sure we have enough room for the extra bytes, if any. */ + if (fileSize == DR_SIZE_MAX && extraBytes > 0) { + fclose(pFile); + return NULL; /* File is too big. */ + } + + pFileData = malloc((size_t)fileSize + extraBytes); /* <-- Safe cast due to the check above. */ + if (pFileData == NULL) { + fclose(pFile); + return NULL; /* Failed to allocate memory for the file. Good chance the file is too big. */ + } + + bytesRead = fread(pFileData, 1, (size_t)fileSize, pFile); + if (bytesRead != fileSize) { + free(pFileData); + fclose(pFile); + return NULL; /* Failed to read every byte from the file. */ + } + + fclose(pFile); + + if (pFileSizeOut) { + *pFileSizeOut = (size_t)fileSize; + } + + return pFileData; +} + +void* dr_open_and_read_file(const char* pFilePath, size_t* pFileSizeOut) +{ + return dr_open_and_read_file_with_extra_data(pFilePath, pFileSizeOut, 0); +} + + +dr_bool32 dr_argv_is_set(int argc, char** argv, const char* value) +{ + int iarg; + for (iarg = 0; iarg < argc; ++iarg) { + if (strcmp(argv[iarg], value) == 0) { + return DR_TRUE; + } + } + + return DR_FALSE; +} + + +int dr_vprintf_fixed(int width, const char* const format, va_list args) +{ + int i; + int len; + char buffer[4096]; + + if (width <= 0) { + return -1; /* Width cannot be negative or 0. */ + } + + if ((unsigned int)width > sizeof(buffer)) { + return -1; /* Width is too big. */ + } + + /* We need to print this into a string (truncated). */ +#if (defined(_MSC_VER) && _MSC_VER > 1200) || ((defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 500) || defined(_ISOC99_SOURCE) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L)) + len = vsnprintf(buffer, width+1, format, args); +#else + len = vsprintf(buffer, format, args); + if (len > width) { + len = width; + } + + buffer[len] = '\0'; +#endif + + printf("%s", buffer); + for (i = len; i < width; ++i) { + printf(" "); + } + + + return len; +} + +int dr_printf_fixed(int width, const char* const format, ...) +{ + int result; + + va_list args; + va_start(args, format); + { + result = dr_vprintf_fixed(width, format, args); + } + va_end(args); + + return result; +} + +int dr_vprintf_fixed_with_margin(int width, int margin, const char* const format, va_list args) +{ + int i; + + /* Margin. */ + for (i = 0; i < margin; ++i) { + printf(" "); + } + + return dr_vprintf_fixed(width - margin, format, args); +} + +int dr_printf_fixed_with_margin(int width, int margin, const char* const format, ...) +{ + int result; + + va_list args; + va_start(args, format); + { + result = dr_vprintf_fixed_with_margin(width, margin, format, args); + } + va_end(args); + + return result; +} + + + +#ifdef _WIN32 +static LARGE_INTEGER g_DRTimerFrequency = {{0}}; +double dr_timer_now() +{ + LARGE_INTEGER counter; + + if (g_DRTimerFrequency.QuadPart == 0) { + QueryPerformanceFrequency(&g_DRTimerFrequency); + } + + QueryPerformanceCounter(&counter); + + return counter.QuadPart / (double)g_DRTimerFrequency.QuadPart; +} +#else +#if _POSIX_C_SOURCE >= 199309L + #if defined(CLOCK_MONOTONIC) + #define MA_CLOCK_ID CLOCK_MONOTONIC + #else + #define MA_CLOCK_ID CLOCK_REALTIME + #endif + double dr_timer_now() + { + struct timespec newTime; + clock_gettime(CLOCK_MONOTONIC, &newTime); + + return ((newTime.tv_sec * 1000000000LL) + newTime.tv_nsec) / 1000000000.0; + } +#else + double dr_timer_now() + { + struct timeval newTime; + gettimeofday(&newTime, NULL); + + return ((newTime.tv_sec * 1000000) + newTime.tv_usec) / 1000000.0; + } +#endif +#endif + + +float dr_scale_to_range_f32(float x, float lo, float hi) +{ + return lo + x*(hi-lo); +} + + +#define DR_LCG_M 2147483647 +#define DR_LCG_A 48271 +#define DR_LCG_C 0 +static int g_drLCG; + +void dr_seed(int seed) +{ + g_drLCG = seed; +} + +int dr_rand_s32() +{ + int lcg = g_drLCG; + int r = (DR_LCG_A * lcg + DR_LCG_C) % DR_LCG_M; + g_drLCG = r; + return r; +} + +unsigned int dr_rand_u32() +{ + return (unsigned int)dr_rand_s32(); +} + +dr_uint64 dr_rand_u64() +{ + return ((dr_uint64)dr_rand_u32() << 32) | dr_rand_u32(); +} + +double dr_rand_f64() +{ + return dr_rand_s32() / (double)0x7FFFFFFF; +} + +float dr_rand_f32() +{ + return (float)dr_rand_f64(); +} + +float dr_rand_range_f32(float lo, float hi) +{ + return dr_scale_to_range_f32(dr_rand_f32(), lo, hi); +} + +int dr_rand_range_s32(int lo, int hi) +{ + if (lo == hi) { + return lo; + } + + return lo + dr_rand_u32() / (0xFFFFFFFF / (hi - lo + 1) + 1); +} + +dr_uint64 dr_rand_range_u64(dr_uint64 lo, dr_uint64 hi) +{ + if (lo == hi) { + return lo; + } + + return lo + dr_rand_u64() / ((~(dr_uint64)0) / (hi - lo + 1) + 1); +} + + + +void dr_pcm_s32_to_f32(void* dst, const void* src, dr_uint64 count) +{ + float* dst_f32 = (float*)dst; + const dr_int32* src_s32 = (const dr_int32*)src; + + dr_uint64 i; + for (i = 0; i < count; i += 1) { + double x = src_s32[i]; + +#if 0 + x = x + 2147483648.0; + x = x * 0.0000000004656612873077392578125; + x = x - 1; +#else + x = x / 2147483648.0; +#endif + + dst_f32[i] = (float)x; + } +} + +void dr_pcm_s32_to_s16(void* dst, const void* src, dr_uint64 count) +{ + dr_int16* dst_s16 = (dr_int16*)dst; + const dr_int32* src_s32 = (const dr_int32*)src; + + dr_uint64 i; + for (i = 0; i < count; i += 1) { + dr_int32 x = src_s32[i]; + x = x >> 16; + dst_s16[i] = (dr_int16)x; + } +} + + + + +dr_handle dr_dlopen(const char* filename) +{ + dr_handle handle; + +#ifdef _WIN32 + handle = (dr_handle)LoadLibraryA(filename); +#else + handle = (dr_handle)dlopen(filename, RTLD_NOW); +#endif + + return handle; +} + +void dr_dlclose(dr_handle handle) +{ +#ifdef _WIN32 + FreeLibrary((HMODULE)handle); +#else + dlclose((void*)handle); +#endif +} + +dr_proc dr_dlsym(dr_handle handle, const char* symbol) +{ + dr_proc proc; + +#ifdef _WIN32 + proc = (dr_proc)GetProcAddress((HMODULE)handle, symbol); +#else +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpedantic" +#endif + proc = (dr_proc)dlsym((void*)handle, symbol); +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) + #pragma GCC diagnostic pop +#endif +#endif + + return proc; +} diff --git a/source/engine/thirdparty/dr_libs/tests/external/miniaudio b/source/engine/thirdparty/dr_libs/tests/external/miniaudio new file mode 160000 index 0000000..d1a166c --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/external/miniaudio @@ -0,0 +1 @@ +Subproject commit d1a166c83ab445b1c14bc83d37c84e18d172e5f5 diff --git a/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_common.c b/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_common.c new file mode 100644 index 0000000..45342b9 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_common.c @@ -0,0 +1,332 @@ +#define DR_FLAC_IMPLEMENTATION +#include "../../dr_flac.h" + +/* libFLAC has a warning in their header. *sigh*. */ +#if defined(__GNUC__) || defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpedantic" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc99-extensions" + #endif +#endif +#include +#if defined(__GNUC__) + #pragma GCC diagnostic pop +#endif + +#include "../common/dr_common.c" + + +/* +The libflac object is used to make it easier to compare things against the reference implementation. I don't think +libFLAC's API is all that easy to use or intuitive so I'm wrapping it. This is deliberately simple. It decodes the +entire file into memory upon initialization. +*/ +typedef struct +{ + drflac_int32* pPCMFrames; /* Interleaved. */ + drflac_uint64 pcmFrameCount; + drflac_uint64 pcmFrameCap; /* The capacity of the pPCMFrames buffer in PCM frames. */ + drflac_uint32 channels; + drflac_uint32 sampleRate; + drflac_uint64 currentPCMFrame; /* The index of the PCM frame the decoder is currently sitting on. */ + double decodeTimeInSeconds; /* The total amount of time it took to decode the file. This is used for profiling. */ + drflac_uint8* pFileData; + size_t fileSizeInBytes; + size_t fileReadPos; +} libflac; + +static FLAC__StreamDecoderReadStatus libflac__read_callback(const FLAC__StreamDecoder *pStreamDecoder, FLAC__byte buffer[], size_t *bytes, void *client_data) +{ + libflac* pDecoder = (libflac*)client_data; + size_t bytesToRead = *bytes; + size_t bytesRemaining = pDecoder->fileSizeInBytes - pDecoder->fileReadPos; + + (void)pStreamDecoder; + + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + memcpy(buffer, pDecoder->pFileData + pDecoder->fileReadPos, bytesToRead); + pDecoder->fileReadPos += bytesToRead; + } + + *bytes = bytesToRead; + return (bytesToRead == 0 && bytesRemaining == 0) ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + +static FLAC__StreamDecoderWriteStatus libflac__write_callback(const FLAC__StreamDecoder *pStreamDecoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data) +{ + libflac* pDecoder = (libflac*)client_data; + drflac_uint32 pcmFramesInFLACFrame; + drflac_int32* pNextSampleInNewFrame; + drflac_uint32 i, j; + + (void)pStreamDecoder; + + pcmFramesInFLACFrame = frame->header.blocksize; + + /* Make sure there's room in the buffer. */ + if ((pDecoder->pcmFrameCount + pcmFramesInFLACFrame) > pDecoder->pcmFrameCap) { + drflac_int32* pNewPCMFrames; + + pDecoder->pcmFrameCap *= 2; + if (pDecoder->pcmFrameCap < (pDecoder->pcmFrameCount + pcmFramesInFLACFrame)) { + pDecoder->pcmFrameCap = (pDecoder->pcmFrameCount + pcmFramesInFLACFrame); + } + + pNewPCMFrames = (drflac_int32*)realloc(pDecoder->pPCMFrames, (size_t)(pDecoder->pcmFrameCap * pDecoder->channels * sizeof(drflac_int32))); + if (pNewPCMFrames == NULL) { + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + + pDecoder->pPCMFrames = pNewPCMFrames; + } + + /* Make sure the decoded samples are interleaved. */ + pNextSampleInNewFrame = pDecoder->pPCMFrames + (pDecoder->pcmFrameCount * pDecoder->channels); + for (i = 0; i < pcmFramesInFLACFrame; i += 1) { + for (j = 0; j < pDecoder->channels; ++j) { + *pNextSampleInNewFrame++ = buffer[j][i] << (32 - frame->header.bits_per_sample); + } + } + + pDecoder->pcmFrameCount += pcmFramesInFLACFrame; + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +static FLAC__StreamDecoderLengthStatus libflac__length_callback(const FLAC__StreamDecoder *pStreamDecoder, FLAC__uint64 *stream_length, void *client_data) +{ + libflac* pDecoder = (libflac*)client_data; + + (void)pStreamDecoder; + + *stream_length = pDecoder->fileSizeInBytes; + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; +} + +static void libflac__metadata_callback(const FLAC__StreamDecoder *pStreamDecoder, const FLAC__StreamMetadata *metadata, void *client_data) +{ + libflac* pDecoder = (libflac*)client_data; + + (void)pStreamDecoder; + + /* Here is where we initialize the buffer for the decoded data. */ + if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) { + pDecoder->pcmFrameCount = 0; /* Always set this to 0. Don't be tempted to set it to metadata->data.stream_info.total_samples. It's increment in libflac__write_callback. */ + pDecoder->channels = metadata->data.stream_info.channels; + pDecoder->sampleRate = metadata->data.stream_info.sample_rate; + + /* Allocate an initial block of memory if we know the total size. */ + if (metadata->data.stream_info.total_samples > 0) { + pDecoder->pcmFrameCap = metadata->data.stream_info.total_samples; + pDecoder->pPCMFrames = (drflac_int32*)malloc((size_t)(pDecoder->pcmFrameCap * pDecoder->channels * sizeof(drflac_int32))); + if (pDecoder->pPCMFrames != NULL) { + DRFLAC_ZERO_MEMORY(pDecoder->pPCMFrames, (size_t)(pDecoder->pcmFrameCap * pDecoder->channels * sizeof(drflac_int32))); + } + } + } +} + +static void libflac__error_callback(const FLAC__StreamDecoder *pStreamDecoder, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + (void)pStreamDecoder; + (void)status; + (void)client_data; +} + +/* +Initializes a libflac object. + +This will perform a full decode of the file using libFLAC. The time it takes to decode the file will be stored in decodeTimeInSeconds +for the purpose of profiling. The decoding process will load the entire file into memory before calling into libFLAC. This way profiling +will exclude any IO time. +*/ +drflac_result libflac_init_file(const char* pFilePath, libflac* pDecoder) +{ + FLAC__StreamDecoder* pStreamDecoder; + FLAC__bool libflacResult; + FLAC__StreamDecoderInitStatus libflacStatus; + double decodeTimeBeg; + double decodeTimeEnd; + + DRFLAC_ZERO_MEMORY(pDecoder, sizeof(*pDecoder)); + + pDecoder->pFileData = (drflac_uint8*)dr_open_and_read_file(pFilePath, &pDecoder->fileSizeInBytes); + if (pDecoder->pFileData == NULL) { + return DRFLAC_ERROR; /* Failed to open the file. */ + } + + + /* Initialize the libFLAC decoder. */ + pStreamDecoder = FLAC__stream_decoder_new(); + if (pDecoder == NULL) { + return DRFLAC_ERROR; /* Failed to create a new stream decoder. Out of memory. */ + } + + if (dr_extension_equal(pFilePath, "ogg") || dr_extension_equal(pFilePath, "oga") || dr_extension_equal(pFilePath, "ogv")) { + libflacStatus = FLAC__stream_decoder_init_ogg_stream(pStreamDecoder, libflac__read_callback, NULL, NULL, libflac__length_callback, NULL, libflac__write_callback, libflac__metadata_callback, libflac__error_callback, pDecoder); + } else { + libflacStatus = FLAC__stream_decoder_init_stream(pStreamDecoder, libflac__read_callback, NULL, NULL, libflac__length_callback, NULL, libflac__write_callback, libflac__metadata_callback, libflac__error_callback, pDecoder); + } + + if (libflacStatus != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + FLAC__stream_decoder_delete(pStreamDecoder); + free(pDecoder->pFileData); + return DRFLAC_ERROR; /* Failed to initialize the stream. */ + } + + /* Read past the metadata first. This will fire the libflac__metadata_callback which will do a bit of initialization for us. */ + libflacResult = FLAC__stream_decoder_process_until_end_of_metadata(pStreamDecoder); + if (libflacResult == DRFLAC_FALSE) { + FLAC__stream_decoder_delete(pStreamDecoder); + free(pDecoder->pFileData); + return DRFLAC_ERROR; /* Failed to read metadata from the FLAC stream. */ + } + + /* Now we can just decode the entire file. */ + decodeTimeBeg = dr_timer_now(); + libflacResult = FLAC__stream_decoder_process_until_end_of_stream(pStreamDecoder); + decodeTimeEnd = dr_timer_now(); + + pDecoder->decodeTimeInSeconds = decodeTimeEnd - decodeTimeBeg; + + /* We're done with the libFLAC decoder. */ + FLAC__stream_decoder_delete(pStreamDecoder); + pStreamDecoder = NULL; + + + /* Free the file data. */ + free(pDecoder->pFileData); + pDecoder->pFileData = NULL; + pDecoder->fileSizeInBytes = 0; + pDecoder->fileReadPos = 0; + + if (libflacResult == DRFLAC_FALSE) { + return DRFLAC_ERROR; /* Some error ocurred while decoding. */ + } + + return DRFLAC_SUCCESS; +} + +void libflac_uninit(libflac* pDecoder) +{ + if (pDecoder == NULL) { + return; + } + + free(pDecoder->pPCMFrames); +} + +drflac_uint64 libflac_read_pcm_frames_s32(libflac* pDecoder, drflac_uint64 framesToRead, drflac_int32* pBufferOut) +{ + drflac_uint64 pcmFramesRemaining; + + if (pDecoder == NULL) { + return 0; + } + + pcmFramesRemaining = pDecoder->pcmFrameCount - pDecoder->currentPCMFrame; + if (framesToRead > pcmFramesRemaining) { + framesToRead = pcmFramesRemaining; + } + + if (framesToRead == 0) { + return 0; + } + + DRFLAC_COPY_MEMORY(pBufferOut, pDecoder->pPCMFrames + (pDecoder->currentPCMFrame * pDecoder->channels), (size_t)(framesToRead * pDecoder->channels * sizeof(drflac_int32))); + pDecoder->currentPCMFrame += framesToRead; + + return framesToRead; +} + +drflac_uint64 libflac_read_pcm_frames_f32(libflac* pDecoder, drflac_uint64 framesToRead, float* pBufferOut) +{ + drflac_uint64 pcmFramesRemaining; + + if (pDecoder == NULL) { + return 0; + } + + pcmFramesRemaining = pDecoder->pcmFrameCount - pDecoder->currentPCMFrame; + if (framesToRead > pcmFramesRemaining) { + framesToRead = pcmFramesRemaining; + } + + if (framesToRead == 0) { + return 0; + } + + /* s32 to f32 */ + dr_pcm_s32_to_f32(pBufferOut, pDecoder->pPCMFrames + (pDecoder->currentPCMFrame * pDecoder->channels), framesToRead * pDecoder->channels); + pDecoder->currentPCMFrame += framesToRead; + + return framesToRead; +} + +drflac_uint64 libflac_read_pcm_frames_s16(libflac* pDecoder, drflac_uint64 framesToRead, drflac_int16* pBufferOut) +{ + drflac_uint64 pcmFramesRemaining; + + if (pDecoder == NULL) { + return 0; + } + + pcmFramesRemaining = pDecoder->pcmFrameCount - pDecoder->currentPCMFrame; + if (framesToRead > pcmFramesRemaining) { + framesToRead = pcmFramesRemaining; + } + + if (framesToRead == 0) { + return 0; + } + + /* s32 to f32 */ + dr_pcm_s32_to_s16(pBufferOut, pDecoder->pPCMFrames + (pDecoder->currentPCMFrame * pDecoder->channels), framesToRead * pDecoder->channels); + pDecoder->currentPCMFrame += framesToRead; + + return framesToRead; +} + +drflac_bool32 libflac_seek_to_pcm_frame(libflac* pDecoder, drflac_uint64 targetPCMFrameIndex) +{ + if (pDecoder == NULL) { + return DRFLAC_FALSE; + } + + if (targetPCMFrameIndex > pDecoder->pcmFrameCount) { + return DRFLAC_FALSE; /* Trying to seek too far forward. */ + } + + pDecoder->currentPCMFrame = targetPCMFrameIndex; + + return DRFLAC_TRUE; +} + + +/* Helper for printing CPU caps from dr_flac. */ +void print_cpu_caps() +{ +#if defined(DRFLAC_64BIT) + printf("64 Bit\n"); +#else + printf("32 Bit\n"); +#endif + + drflac__init_cpu_caps(); + +#if defined(DRFLAC_X86) || defined(DRFLAC_X64) + #if defined(DRFLAC_X64) + printf("Architecture: x64\n"); + #else + printf("Architecture: x86\n"); + #endif + printf("Has SSE2: %s\n", drflac_has_sse2() ? "YES" : "NO"); + printf("Has SSE4.1: %s\n", drflac_has_sse41() ? "YES" : "NO"); + printf("Has LZCNT: %s\n", drflac__is_lzcnt_supported() ? "YES" : "NO"); +#endif +} diff --git a/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_decoding.c b/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_decoding.c new file mode 100644 index 0000000..fbaf413 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_decoding.c @@ -0,0 +1,825 @@ +/*#define DR_FLAC_NO_CRC*/ +/*#define DR_FLAC_NO_SIMD*/ +/*#define DR_FLAC_BUFFER_SIZE 4096*/ +#include "dr_flac_common.c" + +#define FILE_NAME_WIDTH 40 +#define NUMBER_WIDTH 10 +#define TABLE_MARGIN 2 + +#define DEFAULT_SOURCE_DIR "testvectors/flac/testbench" + +drflac_result decode_test__read_and_compare_pcm_frames_s32(libflac* pLibFlac, drflac* pFlac, drflac_uint64 pcmFrameCount, drflac_int32* pPCMFrames_libflac, drflac_int32* pPCMFrames_drflac) +{ + drflac_uint64 pcmFrameCount_libflac; + drflac_uint64 pcmFrameCount_drflac; + drflac_uint64 iPCMFrame; + + /* To test decoding we just read a number of PCM frames from each decoder and compare. */ + pcmFrameCount_libflac = libflac_read_pcm_frames_s32(pLibFlac, pcmFrameCount, pPCMFrames_libflac); + pcmFrameCount_drflac = drflac_read_pcm_frames_s32(pFlac, pcmFrameCount, pPCMFrames_drflac); + + /* The total number of frames we decoded need to match. */ + if (pcmFrameCount_libflac != pcmFrameCount_drflac) { + printf(" Decoded frame counts differ: pcmFrameCount=%d, libFLAC=%d, dr_flac=%d", (int)pcmFrameCount, (int)pcmFrameCount_libflac, (int)pcmFrameCount_drflac); + return DRFLAC_ERROR; + } + + /* Each of the decoded PCM frames need to match. */ + DRFLAC_ASSERT(pcmFrameCount_libflac == pcmFrameCount_drflac); + + for (iPCMFrame = 0; iPCMFrame < pcmFrameCount_libflac; iPCMFrame += 1) { + drflac_int32* pPCMFrame_libflac = pPCMFrames_libflac + (iPCMFrame * pLibFlac->channels); + drflac_int32* pPCMFrame_drflac = pPCMFrames_drflac + (iPCMFrame * pLibFlac->channels); + drflac_uint32 iChannel; + drflac_bool32 hasError = DRFLAC_FALSE; + + for (iChannel = 0; iChannel < pLibFlac->channels; iChannel += 1) { + if (pPCMFrame_libflac[iChannel] != pPCMFrame_drflac[iChannel]) { + printf(" PCM Frame @ %d[%d] does not match: pcmFrameCount=%d\n", (int)iPCMFrame, iChannel, (int)pcmFrameCount); + hasError = DRFLAC_TRUE; + break; + } + } + + if (hasError) { + return DRFLAC_ERROR; /* Decoded frames do not match. */ + } + } + + /* Done. */ + return DRFLAC_SUCCESS; +} + +drflac_result decode_test__read_and_compare_pcm_frame_chunks_s32(libflac* pLibFlac, drflac* pFlac, drflac_uint64 pcmFrameChunkSize) +{ + drflac_result result = DRFLAC_SUCCESS; + drflac_uint64 iPCMFrame; + drflac_int32* pPCMFrames_libflac; + drflac_int32* pPCMFrames_drflac; + + /* Make sure the decoder's are seeked back to the start first. */ + drflac_seek_to_pcm_frame(pFlac, 0); + libflac_seek_to_pcm_frame(pLibFlac, 0); + + pPCMFrames_libflac = (drflac_int32*)malloc((size_t)(pcmFrameChunkSize * pLibFlac->channels * sizeof(drflac_int32))); + if (pPCMFrames_libflac == NULL) { + printf(" [libFLAC] Out of memory"); + return DRFLAC_ERROR; + } + + pPCMFrames_drflac = (drflac_int32*)malloc((size_t)(pcmFrameChunkSize * pLibFlac->channels * sizeof(drflac_int32))); + if (pPCMFrames_drflac == NULL) { + free(pPCMFrames_libflac); + printf(" [dr_flac] Out of memory"); + return DRFLAC_ERROR; + } + + for (iPCMFrame = 0; iPCMFrame < pLibFlac->pcmFrameCount; iPCMFrame += pcmFrameChunkSize) { + result = decode_test__read_and_compare_pcm_frames_s32(pLibFlac, pFlac, pcmFrameChunkSize, pPCMFrames_libflac, pPCMFrames_drflac); + if (result != DRFLAC_SUCCESS) { + break; + } + } + + free(pPCMFrames_libflac); + free(pPCMFrames_drflac); + + return result; +} + + +drflac_result decode_test__read_and_compare_pcm_frames_f32(libflac* pLibFlac, drflac* pFlac, drflac_uint64 pcmFrameCount, float* pPCMFrames_libflac, float* pPCMFrames_drflac) +{ + drflac_uint64 pcmFrameCount_libflac; + drflac_uint64 pcmFrameCount_drflac; + drflac_uint64 iPCMFrame; + + /* To test decoding we just read a number of PCM frames from each decoder and compare. */ + pcmFrameCount_libflac = libflac_read_pcm_frames_f32(pLibFlac, pcmFrameCount, pPCMFrames_libflac); + pcmFrameCount_drflac = drflac_read_pcm_frames_f32(pFlac, pcmFrameCount, pPCMFrames_drflac); + + /* The total number of frames we decoded need to match. */ + if (pcmFrameCount_libflac != pcmFrameCount_drflac) { + printf(" Decoded frame counts differ: pcmFrameCount=%d, libFLAC=%d, dr_flac=%d", (int)pcmFrameCount, (int)pLibFlac->currentPCMFrame, (int)pFlac->currentPCMFrame); + return DRFLAC_ERROR; + } + + /* Each of the decoded PCM frames need to match. */ + DRFLAC_ASSERT(pcmFrameCount_libflac == pcmFrameCount_drflac); + + for (iPCMFrame = 0; iPCMFrame < pcmFrameCount_libflac; iPCMFrame += 1) { + float* pPCMFrame_libflac = pPCMFrames_libflac + (iPCMFrame * pLibFlac->channels); + float* pPCMFrame_drflac = pPCMFrames_drflac + (iPCMFrame * pLibFlac->channels); + drflac_uint32 iChannel; + drflac_bool32 hasError = DRFLAC_FALSE; + + for (iChannel = 0; iChannel < pLibFlac->channels; iChannel += 1) { + if (pPCMFrame_libflac[iChannel] != pPCMFrame_drflac[iChannel]) { + printf(" PCM Frame @ %d[%d] does not match: pcmFrameCount=%d", (int)iPCMFrame, iChannel, (int)pcmFrameCount); + hasError = DRFLAC_TRUE; + break; + } + } + + if (hasError) { + return DRFLAC_ERROR; /* Decoded frames do not match. */ + } + } + + /* Done. */ + return DRFLAC_SUCCESS; +} + +drflac_result decode_test__read_and_compare_pcm_frame_chunks_f32(libflac* pLibFlac, drflac* pFlac, drflac_uint64 pcmFrameChunkSize) +{ + drflac_result result = DRFLAC_SUCCESS; + drflac_uint64 iPCMFrame; + float* pPCMFrames_libflac; + float* pPCMFrames_drflac; + + /* Make sure the decoder's are seeked back to the start first. */ + drflac_seek_to_pcm_frame(pFlac, 0); + libflac_seek_to_pcm_frame(pLibFlac, 0); + + pPCMFrames_libflac = (float*)malloc((size_t)(pcmFrameChunkSize * pLibFlac->channels * sizeof(float))); + if (pPCMFrames_libflac == NULL) { + printf(" [libFLAC] Out of memory"); + return DRFLAC_ERROR; + } + + pPCMFrames_drflac = (float*)malloc((size_t)(pcmFrameChunkSize * pLibFlac->channels * sizeof(float))); + if (pPCMFrames_drflac == NULL) { + free(pPCMFrames_libflac); + printf(" [dr_flac] Out of memory"); + return DRFLAC_ERROR; + } + + for (iPCMFrame = 0; iPCMFrame < pLibFlac->pcmFrameCount; iPCMFrame += pcmFrameChunkSize) { + result = decode_test__read_and_compare_pcm_frames_f32(pLibFlac, pFlac, pcmFrameChunkSize, pPCMFrames_libflac, pPCMFrames_drflac); + if (result != DRFLAC_SUCCESS) { + break; + } + } + + free(pPCMFrames_libflac); + free(pPCMFrames_drflac); + + return result; +} + + +drflac_result decode_test__read_and_compare_pcm_frames_s16(libflac* pLibFlac, drflac* pFlac, drflac_uint64 pcmFrameCount, drflac_int16* pPCMFrames_libflac, drflac_int16* pPCMFrames_drflac) +{ + drflac_uint64 pcmFrameCount_libflac; + drflac_uint64 pcmFrameCount_drflac; + drflac_uint64 iPCMFrame; + + /* To test decoding we just read a number of PCM frames from each decoder and compare. */ + pcmFrameCount_libflac = libflac_read_pcm_frames_s16(pLibFlac, pcmFrameCount, pPCMFrames_libflac); + pcmFrameCount_drflac = drflac_read_pcm_frames_s16(pFlac, pcmFrameCount, pPCMFrames_drflac); + + /* The total number of frames we decoded need to match. */ + if (pcmFrameCount_libflac != pcmFrameCount_drflac) { + printf(" Decoded frame counts differ: pcmFrameCount=%d, libFLAC=%d, dr_flac=%d", (int)pcmFrameCount, (int)pLibFlac->currentPCMFrame, (int)pFlac->currentPCMFrame); + return DRFLAC_ERROR; + } + + /* Each of the decoded PCM frames need to match. */ + DRFLAC_ASSERT(pcmFrameCount_libflac == pcmFrameCount_drflac); + + for (iPCMFrame = 0; iPCMFrame < pcmFrameCount_libflac; iPCMFrame += 1) { + drflac_int16* pPCMFrame_libflac = pPCMFrames_libflac + (iPCMFrame * pLibFlac->channels); + drflac_int16* pPCMFrame_drflac = pPCMFrames_drflac + (iPCMFrame * pLibFlac->channels); + drflac_uint32 iChannel; + drflac_bool32 hasError = DRFLAC_FALSE; + + for (iChannel = 0; iChannel < pLibFlac->channels; iChannel += 1) { + if (pPCMFrame_libflac[iChannel] != pPCMFrame_drflac[iChannel]) { + printf(" PCM Frame @ %d[%d] does not match: pcmFrameCount=%d", (int)iPCMFrame, iChannel, (int)pcmFrameCount); + hasError = DRFLAC_TRUE; + break; + } + } + + if (hasError) { + return DRFLAC_ERROR; /* Decoded frames do not match. */ + } + } + + /* Done. */ + return DRFLAC_SUCCESS; +} + +drflac_result decode_test__read_and_compare_pcm_frame_chunks_s16(libflac* pLibFlac, drflac* pFlac, drflac_uint64 pcmFrameChunkSize) +{ + drflac_result result = DRFLAC_SUCCESS; + drflac_uint64 iPCMFrame; + drflac_int16* pPCMFrames_libflac; + drflac_int16* pPCMFrames_drflac; + + /* Make sure the decoder's are seeked back to the start first. */ + drflac_seek_to_pcm_frame(pFlac, 0); + libflac_seek_to_pcm_frame(pLibFlac, 0); + + pPCMFrames_libflac = (drflac_int16*)malloc((size_t)(pcmFrameChunkSize * pLibFlac->channels * sizeof(drflac_int16))); + if (pPCMFrames_libflac == NULL) { + printf(" [libFLAC] Out of memory"); + return DRFLAC_ERROR; + } + + pPCMFrames_drflac = (drflac_int16*)malloc((size_t)(pcmFrameChunkSize * pLibFlac->channels * sizeof(drflac_int16))); + if (pPCMFrames_drflac == NULL) { + free(pPCMFrames_libflac); + printf(" [dr_flac] Out of memory"); + return DRFLAC_ERROR; + } + + for (iPCMFrame = 0; iPCMFrame < pLibFlac->pcmFrameCount; iPCMFrame += pcmFrameChunkSize) { + result = decode_test__read_and_compare_pcm_frames_s16(pLibFlac, pFlac, pcmFrameChunkSize, pPCMFrames_libflac, pPCMFrames_drflac); + if (result != DRFLAC_SUCCESS) { + break; + } + } + + free(pPCMFrames_libflac); + free(pPCMFrames_drflac); + + return result; +} + + + +drflac_result decode_test_file_s32(libflac* pLibFlac, drflac* pFlac) +{ + drflac_result result = DRFLAC_SUCCESS; + + /* Start with reading the entire file in one go. */ + if (result == DRFLAC_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_s32(pLibFlac, pFlac, pLibFlac->pcmFrameCount); + } + + /* Now try with reading one PCM frame at a time.*/ + if (result == DRFLAC_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_s32(pLibFlac, pFlac, 1); + } + + /* Now test FLAC frame boundaries. */ + if (result == DRFLAC_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_s32(pLibFlac, pFlac, (pFlac->maxBlockSizeInPCMFrames > 0) ? pFlac->maxBlockSizeInPCMFrames : 4096); + } + + return result; +} + +drflac_result decode_test_file_f32(libflac* pLibFlac, drflac* pFlac) +{ + drflac_result result = DRFLAC_SUCCESS; + + /* Start with reading the entire file in one go. */ + if (result == DRFLAC_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_f32(pLibFlac, pFlac, pLibFlac->pcmFrameCount); + } + + /* Now try with reading one PCM frame at a time.*/ + if (result == DRFLAC_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_f32(pLibFlac, pFlac, 1); + } + + /* Now test FLAC frame boundaries. */ + if (result == DRFLAC_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_f32(pLibFlac, pFlac, (pFlac->maxBlockSizeInPCMFrames > 0) ? pFlac->maxBlockSizeInPCMFrames : 4096); + } + + return result; +} + +drflac_result decode_test_file_s16(libflac* pLibFlac, drflac* pFlac) +{ + drflac_result result = DRFLAC_SUCCESS; + + /* Start with reading the entire file in one go. */ + if (result == DRFLAC_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_s16(pLibFlac, pFlac, pLibFlac->pcmFrameCount); + } + + /* Now try with reading one PCM frame at a time.*/ + if (result == DRFLAC_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_s16(pLibFlac, pFlac, 1); + } + + /* Now test FLAC frame boundaries. */ + if (result == DRFLAC_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_s16(pLibFlac, pFlac, (pFlac->maxBlockSizeInPCMFrames > 0) ? pFlac->maxBlockSizeInPCMFrames : 4096); + } + + return result; +} + +static void on_meta(void* pUserData, drflac_metadata* pMetadata) +{ + (void)pUserData; + + switch (pMetadata->type) + { + case DRFLAC_METADATA_BLOCK_TYPE_CUESHEET: + { + /* + This section is here just to make it easy to test cuesheets. It's not designed to integrate cleanly with the output of this program + so I'm just selectively enabling and disabling this section as required. + */ + #if 0 + drflac_cuesheet_track_iterator i; + drflac_cuesheet_track track; + + printf("Cuesheet Found. Track Count = %d\n", (int)pMetadata->data.cuesheet.trackCount); + + drflac_init_cuesheet_track_iterator(&i, pMetadata->data.cuesheet.trackCount, pMetadata->data.cuesheet.pTrackData); + while (drflac_next_cuesheet_track(&i, &track)) { + drflac_uint32 iTrackIndex; + + printf("Cuesheet Track %d. Index Count = %d:\n", track.trackNumber, track.indexCount); + for (iTrackIndex = 0; iTrackIndex < track.indexCount; iTrackIndex += 1) { + printf(" Index %d - Offset %llu\n", iTrackIndex, track.pIndexPoints[iTrackIndex].offset); + } + } + #endif + } break; + } +} + +drflac_result decode_test_file(const char* pFilePath) +{ + /* To test seeking we just seek to our target PCM frame and then decode whatever is remaining and compare it against libFLAC. */ + drflac_result result; + libflac libflac; + drflac* pFlac; + + dr_printf_fixed_with_margin(FILE_NAME_WIDTH, TABLE_MARGIN, "%s", dr_path_file_name(pFilePath)); + + /* First load the decoder from libFLAC. */ + result = libflac_init_file(pFilePath, &libflac); + if (result != DRFLAC_SUCCESS) { + printf(" Failed to open via libFLAC."); + return result; + } + + /* Now load from dr_flac. */ + pFlac = drflac_open_file_with_metadata(pFilePath, on_meta, NULL, NULL); + if (pFlac == NULL) { + printf(" Failed to open via dr_flac."); + libflac_uninit(&libflac); + return DRFLAC_ERROR; /* Failed to load dr_flac decoder. */ + } + + /* At this point we should have both libFLAC and dr_flac decoders open. We can now perform identical operations on each of them and compare. */ + result = decode_test_file_s32(&libflac, pFlac); + if (result != DRFLAC_SUCCESS) { + drflac_close(pFlac); + libflac_uninit(&libflac); + return result; + } + + result = decode_test_file_f32(&libflac, pFlac); + if (result != DRFLAC_SUCCESS) { + drflac_close(pFlac); + libflac_uninit(&libflac); + return result; + } + + result = decode_test_file_s16(&libflac, pFlac); + if (result != DRFLAC_SUCCESS) { + drflac_close(pFlac); + libflac_uninit(&libflac); + return result; + } + + + /* We're done with our decoders. */ + drflac_close(pFlac); + libflac_uninit(&libflac); + + if (result == DRFLAC_SUCCESS) { + printf(" Passed"); + } + + return result; +} + +drflac_result decode_test_directory(const char* pDirectoryPath) +{ + dr_file_iterator iteratorState; + dr_file_iterator* pFile; + + dr_printf_fixed(FILE_NAME_WIDTH, "%s", pDirectoryPath); + dr_printf_fixed_with_margin(NUMBER_WIDTH, TABLE_MARGIN, "RESULT"); + printf("\n"); + + pFile = dr_file_iterator_begin(pDirectoryPath, &iteratorState); + while (pFile != NULL) { + drflac_result result; + + /* Skip directories for now, but we may want to look at doing recursive file iteration. */ + if (!pFile->isDirectory) { + result = decode_test_file(pFile->absolutePath); + (void)result; + + printf("\n"); + } + + pFile = dr_file_iterator_next(pFile); + } + + return DRFLAC_SUCCESS; +} + +drflac_result decode_test() +{ + drflac_result result = DRFLAC_SUCCESS; + + /* Directories. */ + { + result = decode_test_directory(DEFAULT_SOURCE_DIR); + (void)result; + } + + return result; +} + + + +drflac_result open_and_read_test_file_s32(libflac* pLibFlac, const char* pFilePath) +{ + drflac_int32* pPCMFrames; + drflac_uint64 pcmFrameCount; + drflac_uint32 channels; + drflac_uint32 sampleRate; + drflac_uint64 iPCMFrame; + + pPCMFrames = drflac_open_file_and_read_pcm_frames_s32(pFilePath, &channels, &sampleRate, &pcmFrameCount, NULL); + if (pPCMFrames == NULL) { + printf(" drflac_open_and_read failed."); + return DRFLAC_ERROR; /* Error decoding */ + } + + if (pcmFrameCount != pLibFlac->pcmFrameCount) { + printf(" Decoded frame counts differ: pcmFrameCount=%d, libFLAC=%d, dr_flac=%d", (int)pcmFrameCount, (int)pLibFlac->currentPCMFrame, (int)pcmFrameCount); + drflac_free(pPCMFrames, NULL); + return DRFLAC_ERROR; + } + + for (iPCMFrame = 0; iPCMFrame < pLibFlac->pcmFrameCount; iPCMFrame += 1) { + drflac_int32* pPCMFrame_libflac = pLibFlac->pPCMFrames + (iPCMFrame * pLibFlac->channels); + drflac_int32* pPCMFrame_drflac = pPCMFrames + (iPCMFrame * pLibFlac->channels); + drflac_uint32 iChannel; + drflac_bool32 hasError = DRFLAC_FALSE; + + for (iChannel = 0; iChannel < pLibFlac->channels; iChannel += 1) { + if (pPCMFrame_libflac[iChannel] != pPCMFrame_drflac[iChannel]) { + printf(" PCM Frame @ %d[%d] does not match: pcmFrameCount=%d", (int)iPCMFrame, iChannel, (int)pcmFrameCount); + hasError = DRFLAC_TRUE; + break; + } + } + + if (hasError) { + drflac_free(pPCMFrames, NULL); + return DRFLAC_ERROR; /* Decoded frames do not match. */ + } + } + + drflac_free(pPCMFrames, NULL); + return DRFLAC_SUCCESS; +} + +drflac_result open_and_read_test_file_f32(libflac* pLibFlac, const char* pFilePath) +{ + float* pPCMFrames; + drflac_uint64 pcmFrameCount; + drflac_uint32 channels; + drflac_uint32 sampleRate; + drflac_uint64 iPCMFrame; + + pPCMFrames = drflac_open_file_and_read_pcm_frames_f32(pFilePath, &channels, &sampleRate, &pcmFrameCount, NULL); + if (pPCMFrames == NULL) { + printf(" drflac_open_and_read failed."); + return DRFLAC_ERROR; /* Error decoding */ + } + + if (pcmFrameCount != pLibFlac->pcmFrameCount) { + printf(" Decoded frame counts differ: pcmFrameCount=%d, libFLAC=%d, dr_flac=%d", (int)pcmFrameCount, (int)pLibFlac->currentPCMFrame, (int)pcmFrameCount); + drflac_free(pPCMFrames, NULL); + return DRFLAC_ERROR; + } + + for (iPCMFrame = 0; iPCMFrame < pLibFlac->pcmFrameCount; iPCMFrame += 1) { + drflac_int32* pPCMFrame_libflac = pLibFlac->pPCMFrames + (iPCMFrame * pLibFlac->channels); + float* pPCMFrame_drflac = pPCMFrames + (iPCMFrame * pLibFlac->channels); + drflac_uint32 iChannel; + drflac_bool32 hasError = DRFLAC_FALSE; + + for (iChannel = 0; iChannel < pLibFlac->channels; iChannel += 1) { + if ((pPCMFrame_libflac[iChannel] / 2147483648.0) != pPCMFrame_drflac[iChannel]) { + printf(" PCM Frame @ %d[%d] does not match: pcmFrameCount=%d", (int)iPCMFrame, iChannel, (int)pcmFrameCount); + hasError = DRFLAC_TRUE; + break; + } + } + + if (hasError) { + drflac_free(pPCMFrames, NULL); + return DRFLAC_ERROR; /* Decoded frames do not match. */ + } + } + + drflac_free(pPCMFrames, NULL); + return DRFLAC_SUCCESS; +} + +drflac_result open_and_read_test_file_s16(libflac* pLibFlac, const char* pFilePath) +{ + drflac_int16* pPCMFrames; + drflac_uint64 pcmFrameCount; + drflac_uint32 channels; + drflac_uint32 sampleRate; + drflac_uint64 iPCMFrame; + + pPCMFrames = drflac_open_file_and_read_pcm_frames_s16(pFilePath, &channels, &sampleRate, &pcmFrameCount, NULL); + if (pPCMFrames == NULL) { + printf(" drflac_open_and_read failed."); + return DRFLAC_ERROR; /* Error decoding */ + } + + if (pcmFrameCount != pLibFlac->pcmFrameCount) { + printf(" Decoded frame counts differ: pcmFrameCount=%d, libFLAC=%d, dr_flac=%d", (int)pcmFrameCount, (int)pLibFlac->currentPCMFrame, (int)pcmFrameCount); + drflac_free(pPCMFrames, NULL); + return DRFLAC_ERROR; + } + + for (iPCMFrame = 0; iPCMFrame < pLibFlac->pcmFrameCount; iPCMFrame += 1) { + drflac_int32* pPCMFrame_libflac = pLibFlac->pPCMFrames + (iPCMFrame * pLibFlac->channels); + drflac_int16* pPCMFrame_drflac = pPCMFrames + (iPCMFrame * pLibFlac->channels); + drflac_uint32 iChannel; + drflac_bool32 hasError = DRFLAC_FALSE; + + for (iChannel = 0; iChannel < pLibFlac->channels; iChannel += 1) { + if ((pPCMFrame_libflac[iChannel] >> 16) != pPCMFrame_drflac[iChannel]) { + printf(" PCM Frame @ %d[%d] does not match: pcmFrameCount=%d", (int)iPCMFrame, iChannel, (int)pcmFrameCount); + hasError = DRFLAC_TRUE; + break; + } + } + + if (hasError) { + drflac_free(pPCMFrames, NULL); + return DRFLAC_ERROR; /* Decoded frames do not match. */ + } + } + + drflac_free(pPCMFrames, NULL); + return DRFLAC_SUCCESS; +} + +drflac_result open_and_read_test_file(const char* pFilePath) +{ + drflac_result result; + libflac libflac; + + dr_printf_fixed_with_margin(FILE_NAME_WIDTH, TABLE_MARGIN, "%s", dr_path_file_name(pFilePath)); + + /* First load the decoder from libFLAC. */ + result = libflac_init_file(pFilePath, &libflac); + if (result != DRFLAC_SUCCESS) { + printf(" Failed to open via libFLAC."); + return result; + } + + result = open_and_read_test_file_s32(&libflac, pFilePath); + if (result != DRFLAC_SUCCESS) { + return result; + } + + open_and_read_test_file_f32(&libflac, pFilePath); + if (result != DRFLAC_SUCCESS) { + return result; + } + + open_and_read_test_file_s16(&libflac, pFilePath); + if (result != DRFLAC_SUCCESS) { + return result; + } + + libflac_uninit(&libflac); + + if (result == DRFLAC_SUCCESS) { + printf(" Passed"); + } + + return result; +} + +drflac_result open_and_read_test_directory(const char* pDirectoryPath) +{ + dr_file_iterator iteratorState; + dr_file_iterator* pFile; + + dr_printf_fixed(FILE_NAME_WIDTH, "%s", pDirectoryPath); + dr_printf_fixed_with_margin(NUMBER_WIDTH, TABLE_MARGIN, "RESULT"); + printf("\n"); + + pFile = dr_file_iterator_begin(pDirectoryPath, &iteratorState); + while (pFile != NULL) { + drflac_result result; + + /* Skip directories for now, but we may want to look at doing recursive file iteration. */ + if (!pFile->isDirectory) { + result = open_and_read_test_file(pFile->absolutePath); + (void)result; + + printf("\n"); + } + + pFile = dr_file_iterator_next(pFile); + } + + return DRFLAC_SUCCESS; +} + +drflac_result open_and_read_test() +{ + drflac_result result = DRFLAC_SUCCESS; + + /* Directories. */ + { + result = open_and_read_test_directory(DEFAULT_SOURCE_DIR); + (void)result; + } + + return result; +} + + + +drflac_result decode_profiling_file(const char* pFilePath) +{ + drflac_result result; + libflac libflac; + drflac* pFlac; + drflac_int32* pTempBuffer; + double decodeTimeBeg; + double decodeTimeEnd; + double drflacDecodeTimeInSeconds; + void* pFileData; + size_t fileSizeInBytes; + + dr_printf_fixed_with_margin(FILE_NAME_WIDTH, 2, "%s", dr_path_file_name(pFilePath)); + + /* libFLAC */ + result = libflac_init_file(pFilePath, &libflac); + if (result != DRFLAC_SUCCESS) { + printf(" [libFLAC] Failed to load file"); + return result; + } + + /* dr_flac */ + pFileData = dr_open_and_read_file(pFilePath, &fileSizeInBytes); + if (pFileData == NULL) { + printf(" Failed to load file"); + return DRFLAC_ERROR; /* Failed to open the file. */ + } + + pFlac = drflac_open_memory(pFileData, fileSizeInBytes, NULL); + if (pFlac == NULL) { + free(pFileData); + printf(" [dr_flac] Failed to load file."); + return DRFLAC_ERROR; + } + + /* libFLAC decode time. */ + dr_printf_fixed_with_margin(NUMBER_WIDTH, TABLE_MARGIN, "%.2fms", libflac.decodeTimeInSeconds*1000); + + /* dr_flac decode time. */ + pTempBuffer = (drflac_int32*)malloc((size_t)(libflac.pcmFrameCount * libflac.channels * sizeof(drflac_int32))); + if (pTempBuffer == NULL) { + libflac_uninit(&libflac); + drflac_close(pFlac); + free(pFileData); + printf(" Out of memory."); + return DRFLAC_ERROR; /* Out of memory. */ + } + + DRFLAC_ZERO_MEMORY(pTempBuffer, (size_t)(libflac.pcmFrameCount * libflac.channels * sizeof(drflac_int32))); + + decodeTimeBeg = dr_timer_now(); + drflac_read_pcm_frames_s32(pFlac, libflac.pcmFrameCount, pTempBuffer); + decodeTimeEnd = dr_timer_now(); + + free(pTempBuffer); + free(pFileData); + + drflacDecodeTimeInSeconds = decodeTimeEnd - decodeTimeBeg; + dr_printf_fixed_with_margin(NUMBER_WIDTH, TABLE_MARGIN, "%.2fms", drflacDecodeTimeInSeconds*1000); + + /* Difference. */ + dr_printf_fixed_with_margin(NUMBER_WIDTH, TABLE_MARGIN, "%d%%", (int)(drflacDecodeTimeInSeconds/libflac.decodeTimeInSeconds * 100)); + + libflac_uninit(&libflac); + drflac_close(pFlac); + + return DRFLAC_SUCCESS; +} + +drflac_result decode_profiling_directory(const char* pDirectoryPath) +{ + dr_file_iterator iteratorState; + dr_file_iterator* pFile; + drflac_bool32 foundError = DRFLAC_FALSE; + + dr_printf_fixed(FILE_NAME_WIDTH, "%s", pDirectoryPath); + dr_printf_fixed_with_margin(NUMBER_WIDTH, TABLE_MARGIN, "libFLAC"); + dr_printf_fixed_with_margin(NUMBER_WIDTH, TABLE_MARGIN, "dr_flac"); + printf("\n"); + + pFile = dr_file_iterator_begin(pDirectoryPath, &iteratorState); + while (pFile != NULL) { + drflac_result result; + + /* Skip directories for now, but we may want to look at doing recursive file iteration. */ + if (!pFile->isDirectory) { + result = decode_profiling_file(pFile->absolutePath); + if (result != DRFLAC_SUCCESS) { + foundError = DRFLAC_TRUE; + } + + printf("\n"); + } + + pFile = dr_file_iterator_next(pFile); + } + + return (foundError) ? DRFLAC_ERROR : DRFLAC_SUCCESS; +} + +drflac_result decode_profiling() +{ + drflac_result result = DRFLAC_SUCCESS; + + /* Directories. */ + { + result = decode_profiling_directory(DEFAULT_SOURCE_DIR); + } + + return result; +} + + +int main(int argc, char** argv) +{ + drflac_result result = DRFLAC_SUCCESS; + drflac_bool32 doTesting = DRFLAC_TRUE; + drflac_bool32 doProfiling = DRFLAC_TRUE; + + /* This program has two main parts. The first is just a normal functionality test. The second is a profiling of the different seeking methods. */ + if (dr_argv_is_set(argc, argv, "--onlyprofile")) { + doTesting = DRFLAC_FALSE; + } + + print_cpu_caps(); + + /* Exhaustive seek test. */ + if (doTesting) { + printf("=======================================================================\n"); + printf("DECODE TESTING\n"); + printf("=======================================================================\n"); + result = decode_test(); + if (result != DRFLAC_SUCCESS) { + return (int)result; /* Don't continue if an error occurs during testing. */ + } + printf("\n"); + + printf("=======================================================================\n"); + printf("OPEN-AND-READ TESTING - drflac_open_*_and_read_pcm_frames_*()\n"); + printf("=======================================================================\n"); + result = open_and_read_test(); + if (result != DRFLAC_SUCCESS) { + return (int)result; /* Don't continue if an error occurs during testing. */ + } + printf("\n"); + } else { + printf("=======================================================================\n"); + printf("WARNING: Correctness Tests Disabled\n"); + printf("=======================================================================\n"); + } + + /* Profiling. */ + if (doProfiling) { + printf("=======================================================================\n"); + printf("DECODE PROFILING (LOWER IS BETTER)\n"); + printf("=======================================================================\n"); + result = decode_profiling(); + printf("\n"); + } + + /*getchar();*/ + return (int)result; +} diff --git a/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_decoding.cpp b/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_decoding.cpp new file mode 100644 index 0000000..48d966c --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_decoding.cpp @@ -0,0 +1 @@ +#include "dr_flac_decoding.c" diff --git a/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_seeking.c b/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_seeking.c new file mode 100644 index 0000000..70494d6 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_seeking.c @@ -0,0 +1,483 @@ +/*#define DR_FLAC_NO_CRC*/ +#include "dr_flac_common.c" + +#define PROFILING_NAME_WIDTH 40 +#define PROFILING_NUMBER_WIDTH 10 +#define PROFILING_NUMBER_MARGIN 2 + +typedef struct +{ + double totalSeconds_BruteForce; + double totalSeconds_BinarySearch; + double totalSeconds_SeekTable; +} profiling_state; + +void profiling_state_init(profiling_state* pProfiling) +{ + DRFLAC_ZERO_MEMORY(pProfiling, sizeof(*pProfiling)); +} + +profiling_state profiling_state_sum(const profiling_state* pA, const profiling_state* pB) +{ + profiling_state result; + result.totalSeconds_BruteForce = pA->totalSeconds_BruteForce + pB->totalSeconds_BruteForce; + result.totalSeconds_BinarySearch = pA->totalSeconds_BinarySearch + pB->totalSeconds_BinarySearch; + result.totalSeconds_SeekTable = pA->totalSeconds_SeekTable + pB->totalSeconds_SeekTable; + + return result; +} + + +drflac_result seek_test_pcm_frame(libflac* pLibFlac, drflac* pFlac, drflac_uint64 targetPCMFrameIndex) +{ + drflac_bool32 seekResult; + drflac_uint64 pcmFrameCount_libflac; + drflac_uint64 pcmFrameCount_drflac; + drflac_int32* pPCMFrames_libflac; + drflac_int32* pPCMFrames_drflac; + drflac_uint64 iPCMFrame; + + if (pFlac->_noSeekTableSeek == DRFLAC_FALSE && pFlac->_noBinarySearchSeek == DRFLAC_TRUE && pFlac->_noBruteForceSeek == DRFLAC_TRUE) { + if (pFlac->seekpointCount == 0) { + printf(" No seek table"); + return DRFLAC_ERROR; + } + } + + /* + To test seeking we just seek to the PCM frame, and then decode the rest of the file. If the PCM frames we read + differs between the two implementations there's something wrong with one of them (probably dr_flac). + */ + seekResult = libflac_seek_to_pcm_frame(pLibFlac, targetPCMFrameIndex); + if (seekResult == DRFLAC_FALSE) { + printf(" [libFLAC] Failed to seek to PCM frame @ %d", (int)targetPCMFrameIndex); + return DRFLAC_ERROR; + } + + seekResult = drflac_seek_to_pcm_frame(pFlac, targetPCMFrameIndex); + if (seekResult == DRFLAC_FALSE) { + printf(" [dr_flac] Failed to seek to PCM frame @ %d", (int)targetPCMFrameIndex); + return DRFLAC_ERROR; + } + + if (pLibFlac->currentPCMFrame != pFlac->currentPCMFrame) { + printf(" Current PCM frame inconsistent @ %d: libFLAC=%d, dr_flac=%d", (int)targetPCMFrameIndex, (int)pLibFlac->currentPCMFrame, (int)pFlac->currentPCMFrame); + return DRFLAC_ERROR; + } + + /* + Now we decode the rest of the file and compare the samples. Note that we try reading the _entire_ file, not just the leftovers, to ensure we + haven't seeked too short. + */ + pPCMFrames_libflac = (drflac_int32*)malloc((size_t)(pLibFlac->pcmFrameCount * pLibFlac->channels * sizeof(drflac_int32))); + if (pPCMFrames_libflac == NULL) { + printf(" [libFLAC] Out of memory"); + return DRFLAC_ERROR; + } + pcmFrameCount_libflac = libflac_read_pcm_frames_s32(pLibFlac, pLibFlac->pcmFrameCount, pPCMFrames_libflac); + + pPCMFrames_drflac = (drflac_int32*)malloc((size_t)(pLibFlac->pcmFrameCount * pLibFlac->channels * sizeof(drflac_int32))); + if (pPCMFrames_drflac == NULL) { + free(pPCMFrames_libflac); + printf(" [dr_flac] Out of memory"); + return DRFLAC_ERROR; + } + pcmFrameCount_drflac = drflac_read_pcm_frames_s32(pFlac, pLibFlac->pcmFrameCount, pPCMFrames_drflac); + + /* The total number of frames we decoded need to match. */ + if (pcmFrameCount_libflac != pcmFrameCount_drflac) { + free(pPCMFrames_drflac); + free(pPCMFrames_libflac); + printf(" Decoded frame counts differ @ %d: libFLAC=%d, dr_flac=%d", (int)targetPCMFrameIndex, (int)pLibFlac->currentPCMFrame, (int)pFlac->currentPCMFrame); + return DRFLAC_ERROR; + } + + /* Each of the decoded PCM frames need to match. */ + DRFLAC_ASSERT(pcmFrameCount_libflac == pcmFrameCount_drflac); + + for (iPCMFrame = 0; iPCMFrame < pcmFrameCount_libflac; iPCMFrame += 1) { + drflac_int32* pPCMFrame_libflac = pPCMFrames_libflac + (iPCMFrame * pLibFlac->channels); + drflac_int32* pPCMFrame_drflac = pPCMFrames_drflac + (iPCMFrame * pLibFlac->channels); + drflac_uint32 iChannel; + drflac_bool32 hasError = DRFLAC_FALSE; + + for (iChannel = 0; iChannel < pLibFlac->channels; iChannel += 1) { + if (pPCMFrame_libflac[iChannel] != pPCMFrame_drflac[iChannel]) { + printf(" PCM Frame @ %d[%d] does not match: targetPCMFrameIndex=%d", (int)iPCMFrame, iChannel, (int)targetPCMFrameIndex); + hasError = DRFLAC_TRUE; + break; + } + } + + if (hasError) { + free(pPCMFrames_drflac); + free(pPCMFrames_libflac); + return DRFLAC_ERROR; /* Decoded frames do not match. */ + } + } + + + /* Done. */ + free(pPCMFrames_drflac); + free(pPCMFrames_libflac); + + return DRFLAC_SUCCESS; +} + +drflac_result seek_test_file(const char* pFilePath) +{ + /* To test seeking we just seek to our target PCM frame and then decode whatever is remaining and compare it against libFLAC. */ + drflac_result result; + libflac libflac; + drflac* pFlac; + drflac_uint32 iteration; + drflac_uint32 totalIterationCount = 10; + + dr_printf_fixed_with_margin(PROFILING_NAME_WIDTH, PROFILING_NUMBER_MARGIN, "%s", dr_path_file_name(pFilePath)); + + /* First load the decoder from libFLAC. */ + result = libflac_init_file(pFilePath, &libflac); + if (result != DRFLAC_SUCCESS) { + printf(" Failed to open via libFLAC."); + return result; + } + + /* Now load from dr_flac. */ + pFlac = drflac_open_file(pFilePath, NULL); + if (pFlac == NULL) { + printf(" Failed to open via dr_flac."); + libflac_uninit(&libflac); + return DRFLAC_ERROR; /* Failed to load dr_flac decoder. */ + } + + /* Use these to use specific seeking methods. Set all to false to use the normal prioritization (seek table, then binary search, then brute force). */ + pFlac->_noSeekTableSeek = DRFLAC_FALSE; + pFlac->_noBinarySearchSeek = DRFLAC_FALSE; + pFlac->_noBruteForceSeek = DRFLAC_FALSE; + + /* At this point we should have both libFLAC and dr_flac decoders open. We can now perform identical operations on each of them and compare. */ + + /* Start with the basics: Seek to the very end, and then the very start. */ + if (result == DRFLAC_SUCCESS) { + result = seek_test_pcm_frame(&libflac, pFlac, libflac.pcmFrameCount); + } + if (result == DRFLAC_SUCCESS) { + result = seek_test_pcm_frame(&libflac, pFlac, 0); + } + + /* Now we'll try seeking to random locations. */ + dr_seed(1234); + + iteration = 0; + while (result == DRFLAC_SUCCESS && iteration < totalIterationCount) { + dr_uint64 targetPCMFrame = dr_rand_range_u64(0, libflac.pcmFrameCount); + if (targetPCMFrame > libflac.pcmFrameCount) { + DRFLAC_ASSERT(DRFLAC_FALSE); /* Should never hit this, but if we do it means our random number generation routine is wrong. */ + } + + result = seek_test_pcm_frame(&libflac, pFlac, targetPCMFrame); + iteration += 1; + } + + /* We're done with our decoders. */ + drflac_close(pFlac); + libflac_uninit(&libflac); + + if (result == DRFLAC_SUCCESS) { + printf(" Passed"); + } + + return result; +} + +drflac_result seek_test_directory(const char* pDirectoryPath) +{ + dr_file_iterator iteratorState; + dr_file_iterator* pFile; + + dr_printf_fixed(PROFILING_NAME_WIDTH, "%s", pDirectoryPath); + dr_printf_fixed_with_margin(PROFILING_NUMBER_WIDTH, PROFILING_NUMBER_MARGIN, "RESULT"); + printf("\n"); + + pFile = dr_file_iterator_begin(pDirectoryPath, &iteratorState); + while (pFile != NULL) { + drflac_result result; + + /* Skip directories for now, but we may want to look at doing recursive file iteration. */ + if (!pFile->isDirectory) { + result = seek_test_file(pFile->absolutePath); + (void)result; + + printf("\n"); + } + + pFile = dr_file_iterator_next(pFile); + } + + return DRFLAC_SUCCESS; +} + +drflac_result seek_test() +{ + drflac_result result = DRFLAC_SUCCESS; + + /* Directories. */ + { + result = seek_test_directory("testvectors/flac/tests"); + (void)result; + } + + return result; +} + + +drflac_result seek_profiling_drflac_and_close(drflac* pFlac, double* pProcessingTime) +{ + drflac_result result = DRFLAC_SUCCESS; + int i; + + if (pFlac == NULL) { + result = DRFLAC_INVALID_ARGS; + goto done; + } + + if (pFlac->totalPCMFrameCount == 0) { + result = DRFLAC_INVALID_ARGS; + goto done; + } + + if (pProcessingTime != NULL) { + *pProcessingTime = 0; + } + + /* Seek back to the start to keep everything normalized. */ + drflac_seek_to_pcm_frame(pFlac, 0); + + /* Random seek points based on a seed. */ + dr_seed(1234); + /*dr_seed(4321);*/ + for (i = 0; i < 100; ++i) { + double startTime; + double endTime; + dr_uint64 targetPCMFrame = dr_rand_range_u64(0, pFlac->totalPCMFrameCount); + + startTime = dr_timer_now(); + { + drflac_seek_to_pcm_frame(pFlac, targetPCMFrame); + } + endTime = dr_timer_now(); + + if (pProcessingTime != NULL) { + *pProcessingTime += (endTime - startTime); + } + } + +done: + drflac_close(pFlac); + return result; +} + +drflac_result seek_profiling_file__seek_table(const char* pFilePath, double* pProcessingTime) +{ + drflac* pFlac; + + if (pFilePath == NULL) { + return DRFLAC_INVALID_ARGS; + } + + pFlac = drflac_open_file(pFilePath, NULL); + if (pFlac == NULL) { + return DRFLAC_ERROR; + } + + pFlac->_noSeekTableSeek = DRFLAC_FALSE; + pFlac->_noBinarySearchSeek = DRFLAC_TRUE; + pFlac->_noBruteForceSeek = DRFLAC_TRUE; + + return seek_profiling_drflac_and_close(pFlac, pProcessingTime); +} + +drflac_result seek_profiling_file__binary_search(const char* pFilePath, double* pProcessingTime) +{ + drflac* pFlac; + + if (pFilePath == NULL) { + return DRFLAC_INVALID_ARGS; + } + + pFlac = drflac_open_file(pFilePath, NULL); + if (pFlac == NULL) { + return DRFLAC_ERROR; + } + + pFlac->_noSeekTableSeek = DRFLAC_TRUE; + pFlac->_noBinarySearchSeek = DRFLAC_FALSE; + pFlac->_noBruteForceSeek = DRFLAC_TRUE; + + return seek_profiling_drflac_and_close(pFlac, pProcessingTime); +} + +drflac_result seek_profiling_file__brute_force(const char* pFilePath, double* pProcessingTime) +{ + drflac* pFlac; + + if (pFilePath == NULL) { + return DRFLAC_INVALID_ARGS; + } + + pFlac = drflac_open_file(pFilePath, NULL); + if (pFlac == NULL) { + return DRFLAC_ERROR; + } + + pFlac->_noSeekTableSeek = DRFLAC_TRUE; + pFlac->_noBinarySearchSeek = DRFLAC_TRUE; + pFlac->_noBruteForceSeek = DRFLAC_FALSE; + + return seek_profiling_drflac_and_close(pFlac, pProcessingTime); +} + +drflac_result seek_profiling_file(const char* pFilePath, profiling_state* pProfiling) +{ + drflac_result result; + + profiling_state_init(pProfiling); + + /* + There are different seeking modes, and each one is profiled so that we can compare the results: + - Brute Force + - Binary Search + - Seek Table + + In order to keep the total run time fair, we can only include files with a seek table. + */ + dr_printf_fixed_with_margin(PROFILING_NAME_WIDTH, 2, "%s", dr_path_file_name(pFilePath)); + + /* Start off with the seek table version. If this fails we don't bother continuing. */ +#if 1 + result = seek_profiling_file__seek_table(pFilePath, &pProfiling->totalSeconds_SeekTable); + if (result != DRFLAC_SUCCESS) { + return result; + } + dr_printf_fixed_with_margin(PROFILING_NUMBER_WIDTH, PROFILING_NUMBER_MARGIN, "%f", pProfiling->totalSeconds_SeekTable); +#else + dr_printf_fixed_with_margin(PROFILING_NUMBER_WIDTH, PROFILING_NUMBER_MARGIN, ""); +#endif + +#if 1 + result = seek_profiling_file__binary_search(pFilePath, &pProfiling->totalSeconds_BinarySearch); + if (result != DRFLAC_SUCCESS) { + return result; + } + dr_printf_fixed_with_margin(PROFILING_NUMBER_WIDTH, PROFILING_NUMBER_MARGIN, "%f", pProfiling->totalSeconds_BinarySearch); +#else + dr_printf_fixed_with_margin(PROFILING_NUMBER_WIDTH, PROFILING_NUMBER_MARGIN, ""); +#endif + +#if 1 + result = seek_profiling_file__brute_force(pFilePath, &pProfiling->totalSeconds_BruteForce); + if (result != DRFLAC_SUCCESS) { + return result; + } + dr_printf_fixed_with_margin(PROFILING_NUMBER_WIDTH, PROFILING_NUMBER_MARGIN, "%f", pProfiling->totalSeconds_BruteForce); +#else + dr_printf_fixed_with_margin(PROFILING_NUMBER_WIDTH, PROFILING_NUMBER_MARGIN, ""); +#endif + + return DRFLAC_SUCCESS; +} + +drflac_result seek_profiling_directory(const char* pDirectoryPath, profiling_state* pProfiling) +{ + dr_file_iterator iteratorState; + dr_file_iterator* pFile; + + profiling_state_init(pProfiling); + + dr_printf_fixed(PROFILING_NAME_WIDTH, "%s", pDirectoryPath); + dr_printf_fixed_with_margin(PROFILING_NUMBER_WIDTH, PROFILING_NUMBER_MARGIN, "S/Table"); + dr_printf_fixed_with_margin(PROFILING_NUMBER_WIDTH, PROFILING_NUMBER_MARGIN, "Bin Srch"); + dr_printf_fixed_with_margin(PROFILING_NUMBER_WIDTH, PROFILING_NUMBER_MARGIN, "B/Force"); + printf("\n"); + + pFile = dr_file_iterator_begin(pDirectoryPath, &iteratorState); + while (pFile != NULL) { + drflac_result result; + profiling_state fileProfiling; + + /* Skip directories for now, but we may want to look at doing recursive file iteration. */ + if (!pFile->isDirectory) { + result = seek_profiling_file(pFile->absolutePath, &fileProfiling); + if (result == DRFLAC_SUCCESS) { + *pProfiling = profiling_state_sum(pProfiling, &fileProfiling); + } + + printf("\n"); + } + + pFile = dr_file_iterator_next(pFile); + } + + return DRFLAC_SUCCESS; +} + +drflac_result seek_profiling() +{ + drflac_result result = DRFLAC_SUCCESS; + profiling_state globalProfiling; + + profiling_state_init(&globalProfiling); + + /* Directories. */ + { + profiling_state directoryProfiling; + + result = seek_profiling_directory("testvectors/flac/tests", &directoryProfiling); + if (result == DRFLAC_SUCCESS) { + globalProfiling = profiling_state_sum(&globalProfiling, &directoryProfiling); + } + } + + return result; +} + + +int main(int argc, char** argv) +{ + drflac_result result = DRFLAC_SUCCESS; + drflac_bool32 doTesting = DRFLAC_TRUE; + drflac_bool32 doProfiling = DRFLAC_TRUE; + + /* This program has two main parts. The first is just a normal functionality test. The second is a profiling of the different seeking methods. */ + if (dr_argv_is_set(argc, argv, "--onlyprofile")) { + doTesting = DRFLAC_FALSE; + } + + /* Exhaustive seek test. */ + if (doTesting) { + printf("=======================================================================\n"); + printf("SEEK TESTING\n"); + printf("=======================================================================\n"); + result = seek_test(); + if (result != DRFLAC_SUCCESS) { + return (int)result; /* Don't continue if an error occurs during testing. */ + } + printf("\n"); + } else { + printf("=======================================================================\n"); + printf("WARNING: Correctness Tests Disabled\n"); + printf("=======================================================================\n"); + } + + /* Profiling. */ + if (doProfiling) { + printf("=======================================================================\n"); + printf("SEEK PROFILING\n"); + printf("=======================================================================\n"); + result = seek_profiling(); + printf("\n"); + } + + /*getchar();*/ + return (int)result; +} diff --git a/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_test_0.c b/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_test_0.c new file mode 100644 index 0000000..ffc28c8 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_test_0.c @@ -0,0 +1,11 @@ +#define DR_FLAC_IMPLEMENTATION +#include "../../dr_flac.h" + +#include "../common/dr_common.c" + +int main(int argc, char** argv) +{ + (void)argc; + (void)argv; + return 0; +} diff --git a/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_test_0.cpp b/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_test_0.cpp new file mode 100644 index 0000000..ce77013 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/flac/dr_flac_test_0.cpp @@ -0,0 +1 @@ +#include "dr_flac_test_0.c" diff --git a/source/engine/thirdparty/dr_libs/tests/flac/fuzz_dr_flac.c b/source/engine/thirdparty/dr_libs/tests/flac/fuzz_dr_flac.c new file mode 100644 index 0000000..f4b6274 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/flac/fuzz_dr_flac.c @@ -0,0 +1,76 @@ +/* + * Fuzz tester for dr_flac.h + * + * compile with + * clang -g -O1 -fsanitize=fuzzer,address -o fuzz_dr_flac fuzz_dr_flac.c + * + * and run ./fuzz_dr_flac to run fuzz testing + * + * Other sanitizers are possible, for example + * -fsanitize=fuzzer,memory + * -fsanitize=fuzzer,undefined + * + * For more options, run ./fuzz_dr_flac -help=1 + * + * If a problem is found, the problematic input is saved and can be + * rerun (with for example a debugger) with + * + * ./fuzz_dr_flac file + * + */ + +#include + +#define DR_FLAC_IMPLEMENTATION +#define DR_FLAC_NO_CRC +#define DR_FLAC_NO_STDIO +#include "../../dr_flac.h" + +#define MIN(a,b) (((a)<(b))?(a):(b)) + +uint8_t fuzz_flacstream[4096] = {0}; +size_t fuzz_flacstream_position; +size_t fuzz_flacstream_length; + +static size_t read_fuzz_flacstream(void* pUserData, void* bufferOut, size_t bytesToRead) +{ + size_t readsize = MIN(bytesToRead, fuzz_flacstream_length-fuzz_flacstream_position); + if (readsize > 0) { + memcpy(bufferOut, fuzz_flacstream+fuzz_flacstream_position, readsize); + fuzz_flacstream_position += readsize; + return readsize; + } else { + return 0; + } +} + +static drflac_bool32 seek_fuzz_flacstream(void* pUserData, int offset, drflac_seek_origin origin) +{ + if ((int)fuzz_flacstream_position+offset < 0 || (int)fuzz_flacstream_position+offset > fuzz_flacstream_length) { + return 1; + } else { + fuzz_flacstream_position = (int)fuzz_flacstream_position+offset; + return 0; + } +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + if (size > 2) { + drflac * drflac_fuzzer; + drflac_int32 drflac_fuzzer_out[2048] = {0}; /* 256 samples over 8 channels */ + drflac_container container = data[0] & 1 ? drflac_container_native : drflac_container_ogg; + + memcpy(fuzz_flacstream, data, (size-1)<4096?(size-1):4096); + + fuzz_flacstream_position = 0; + fuzz_flacstream_length = size-1; + + drflac_fuzzer = drflac_open_relaxed(read_fuzz_flacstream, seek_fuzz_flacstream, container, NULL, NULL); + + while (drflac_read_pcm_frames_s32(drflac_fuzzer, 256, drflac_fuzzer_out)); + + drflac_close(drflac_fuzzer); + } + return 0; +} diff --git a/source/engine/thirdparty/dr_libs/tests/mp3/dr_mp3_common.c b/source/engine/thirdparty/dr_libs/tests/mp3/dr_mp3_common.c new file mode 100644 index 0000000..813e9df --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/mp3/dr_mp3_common.c @@ -0,0 +1,4 @@ +#define DR_MP3_IMPLEMENTATION +#include "../../dr_mp3.h" + +#include "../common/dr_common.c" diff --git a/source/engine/thirdparty/dr_libs/tests/mp3/dr_mp3_playback.c b/source/engine/thirdparty/dr_libs/tests/mp3/dr_mp3_playback.c new file mode 100644 index 0000000..17953a3 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/mp3/dr_mp3_playback.c @@ -0,0 +1,161 @@ +#include "dr_mp3_common.c" + +#define MA_NO_DECODING +#define MA_NO_ENCODING +#define MINIAUDIO_IMPLEMENTATION +#include "../external/miniaudio/miniaudio.h" + +void data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + drmp3* pMP3; + + pMP3 = (drmp3*)pDevice->pUserData; + DRMP3_ASSERT(pMP3 != NULL); + + if (pDevice->playback.format == ma_format_f32) { + drmp3_read_pcm_frames_f32(pMP3, frameCount, pFramesOut); + } else if (pDevice->playback.format == ma_format_s16) { + drmp3_read_pcm_frames_s16(pMP3, frameCount, pFramesOut); + } else { + DRMP3_ASSERT(DRMP3_FALSE); /* Should never get here. */ + } + + (void)pFramesIn; +} + +int do_decoding_validation(const char* pFilePath) +{ + int result = 0; + drmp3 mp3Memory; + drmp3 mp3File; + size_t dataSize; + void* pData; + + /* + When opening from a memory buffer, dr_mp3 will take a different path for decoding which is optimized to reduce data movement. Since it's + running on a separate path, we need to ensure it's returning consistent results with the other code path which will be used when decoding + from a file. + */ + + /* Initialize the memory decoder. */ + pData = dr_open_and_read_file(pFilePath, &dataSize); + if (pData == NULL) { + printf("Failed to open file \"%s\"", pFilePath); + return -1; + } + + if (!drmp3_init_memory(&mp3Memory, pData, dataSize, NULL)) { + free(pData); + printf("Failed to init MP3 decoder \"%s\"", pFilePath); + return -1; + } + + + /* Initialize the file decoder. */ + if (!drmp3_init_file(&mp3File, pFilePath, NULL)) { + drmp3_uninit(&mp3Memory); + free(pData); + printf("Failed to open file \"%s\"", pFilePath); + return -1; + } + + DRMP3_ASSERT(mp3Memory.channels == mp3File.channels); + + + /* Compare. */ + for (;;) { + drmp3_uint64 iSample; + drmp3_uint64 pcmFrameCountMemory; + drmp3_uint64 pcmFrameCountFile; + drmp3_int16 pcmFramesMemory[4096]; + drmp3_int16 pcmFramesFile[4096]; + + pcmFrameCountMemory = drmp3_read_pcm_frames_s16(&mp3Memory, DRMP3_COUNTOF(pcmFramesMemory) / mp3Memory.channels, pcmFramesMemory); + pcmFrameCountFile = drmp3_read_pcm_frames_s16(&mp3File, DRMP3_COUNTOF(pcmFramesFile) / mp3File.channels, pcmFramesFile); + + /* Check the frame count first. */ + if (pcmFrameCountMemory != pcmFrameCountFile) { + printf("Frame counts differ: memory = %d; file = %d\n", (int)pcmFrameCountMemory, (int)pcmFrameCountFile); + result = -1; + break; + } + + /* Check individual frames. */ + DRMP3_ASSERT(pcmFrameCountMemory == pcmFrameCountFile); + for (iSample = 0; iSample < pcmFrameCountMemory * mp3Memory.channels; iSample += 1) { + if (pcmFramesMemory[iSample] != pcmFramesFile[iSample]) { + printf("Samples differ: memory = %d; file = %d\n", (int)pcmFramesMemory[iSample], (int)pcmFramesFile[iSample]); + result = -1; + break; + } + } + + /* We've reached the end if we didn't return any PCM frames. */ + if (pcmFrameCountMemory == 0 || pcmFrameCountFile == 0) { + break; + } + } + + + drmp3_uninit(&mp3File); + drmp3_uninit(&mp3Memory); + free(pData); + return result; +} + +int main(int argc, char** argv) +{ + drmp3 mp3; + ma_result resultMA; + ma_device_config deviceConfig; + ma_device device; + const char* pInputFilePath; + + if (argc < 2) { + printf("No input file.\n"); + return -1; + } + + pInputFilePath = argv[1]; + + /* Quick validation test first. */ + if (do_decoding_validation(pInputFilePath) != 0) { + return -1; + } + + + if (!drmp3_init_file(&mp3, pInputFilePath, NULL)) { + printf("Failed to open file \"%s\"", pInputFilePath); + return -1; + } + + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.format = ma_format_s16; + deviceConfig.playback.channels = mp3.channels; + deviceConfig.sampleRate = mp3.sampleRate; + deviceConfig.dataCallback = data_callback; + deviceConfig.pUserData = &mp3; + resultMA = ma_device_init(NULL, &deviceConfig, &device); + if (resultMA != MA_SUCCESS) { + drmp3_uninit(&mp3); + printf("Failed to initialize playback device: %s.\n", ma_result_description(resultMA)); + return -1; + } + + resultMA = ma_device_start(&device); + if (resultMA != MA_SUCCESS) { + ma_device_uninit(&device); + drmp3_uninit(&mp3); + printf("Failed to start playback device: %s.\n", ma_result_description(resultMA)); + return -1; + } + + printf("Press Enter to quit..."); + getchar(); + + /* We're done. */ + ma_device_uninit(&device); + drmp3_uninit(&mp3); + + return 0; +} \ No newline at end of file diff --git a/source/engine/thirdparty/dr_libs/tests/mp3/dr_mp3_test_0.c b/source/engine/thirdparty/dr_libs/tests/mp3/dr_mp3_test_0.c new file mode 100644 index 0000000..db14b7b --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/mp3/dr_mp3_test_0.c @@ -0,0 +1,12 @@ +#define DR_MP3_IMPLEMENTATION +#include "../../dr_mp3.h" + +#include "../common/dr_common.c" + +int main(int argc, char** argv) +{ + (void)argc; + (void)argv; + return 0; +} + diff --git a/source/engine/thirdparty/dr_libs/tests/opus/dr_opus_decoding.c b/source/engine/thirdparty/dr_libs/tests/opus/dr_opus_decoding.c new file mode 100644 index 0000000..76c5c60 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/opus/dr_opus_decoding.c @@ -0,0 +1,205 @@ +/*#define DR_OPUS_DEBUGGING*/ + +#define DR_OPUS_IMPLEMENTATION +#include "../../wip/dr_opus.h" +#include "../common/dr_common.c" + +const char* dropus_mode_to_string(dropus_mode mode) /* Move this into dr_opus.h? */ +{ + switch (mode) + { + case dropus_mode_silk: return "SILK"; + case dropus_mode_celt: return "CELT"; + case dropus_mode_hybrid: return "Hybrid"; + default: break; + } + + return "Unknown"; +} + +#include + +/* Forward declare our debugging entry point if necessary. */ +#ifdef DR_OPUS_DEBUGGING +int main_debugging(int argc, char** argv); +#endif + +dropus_result test_standard_vector(const char* pFilePath) +{ + dropus_result result; + dropus_stream stream; + void* pFileData; + size_t fileSize; + const dropus_uint8* pRunningData8; + size_t runningPos; + dropus_uint32 iPacket = 0; /* For error reporting. */ + + + /* Load the entire file into memory first. */ + pFileData = dr_open_and_read_file(pFilePath, &fileSize); + if (pFileData == NULL) { + return DROPUS_ERROR; /* File not found. */ + } + + /* Now initialize the stream in preparation for decoding packets. */ + result = dropus_stream_init(&stream); + if (result != DROPUS_SUCCESS) { + free(pFileData); + return result; + } + + /* + Here is where we scan the test vector. The way the file is structured is quite simple. It is made up of a list of Opus packets, each + of which include an 8 byte header where the first 4 bytes is the size in bytes of the Opus packet (little endian) and the next 4 bytes + contain the 32-bit range state which we'll use for validation. + */ + pRunningData8 = (const dropus_uint8*)pFileData; + runningPos = 0; + + /* For each packet... */ + while (runningPos < fileSize) { + dropus_result decodeResult; + dropus_uint32 packetSize; + dropus_uint32 rangeState; + + memcpy(&packetSize, pRunningData8 + 0, 4); + memcpy(&rangeState, pRunningData8 + 4, 4); + + packetSize = dropus__be2host_32(packetSize); + rangeState = dropus__be2host_32(rangeState); + + pRunningData8 += 8; + runningPos += 8; + + /* Safety. Break if we've run out of data. */ + if ((runningPos + packetSize) > fileSize) { + printf("WARNING: Ran out of data before the end of the file.\n"); + break; + } + + decodeResult = dropus_stream_decode_packet(&stream, pRunningData8, packetSize); + if (decodeResult != DROPUS_SUCCESS) { + result = DROPUS_ERROR; + printf("Failed to decode packet %d\n", iPacket); + } + + printf("Opus Packet %d: Mode=%s\n", iPacket, dropus_mode_to_string(dropus_toc_mode(stream.packet.toc))); + + pRunningData8 += packetSize; + runningPos += packetSize; + iPacket += 1; + } + + free(pFileData); + return result; +} + +dropus_result test_standard_vectors_folder(const char* pFolderPath) +{ + dropus_bool32 foundError = DROPUS_FALSE; + dr_file_iterator iteratorState; + dr_file_iterator* pFile; + + pFile = dr_file_iterator_begin(pFolderPath, &iteratorState); + while (pFile != NULL) { + /* Only look at files with the extension "bit". */ + if (dr_extension_equal(pFile->relativePath, "bit")) { + if (test_standard_vector(pFile->absolutePath) != DROPUS_SUCCESS) { + foundError = DROPUS_TRUE; + } + } + + pFile = dr_file_iterator_next(pFile); + } + + if (foundError) { + return DROPUS_ERROR; + } else { + return DROPUS_SUCCESS; + } +} + +dropus_result test_standard_vectors() +{ + dropus_bool32 foundError = DROPUS_FALSE; + + /* Two groups of standard test vectors. The original vectors and the "new" vectors. */ + + /* Original vectors. */ + if (test_standard_vectors_folder("testvectors/opus/opus_testvectors") != DROPUS_SUCCESS) { + foundError = DROPUS_TRUE; + } + + /* New vectors. */ + if (test_standard_vectors_folder("testvectors/opus/opus_newvectors") != DROPUS_SUCCESS) { + foundError = DROPUS_TRUE; + } + + if (foundError) { + return DROPUS_ERROR; + } else { + return DROPUS_SUCCESS; + } +} + +dropus_result test_ogg_vectors() +{ + dropus_result result; + + /* Ogg Opus test vectors are in the "oggopus" folder. */ + result = DROPUS_SUCCESS; + + return result; +} + +int main(int argc, char** argv) +{ + dropus_result result; + + /* Do debugging stuff first. */ +#ifdef DR_OPUS_DEBUGGING + main_debugging(argc, argv); +#endif + + result = test_standard_vector("testvectors/opus/opus_testvectors/testvector02.bit"); + if (result != DROPUS_SUCCESS) { + /*return (int)result;*/ + } + + /* Test standard vectors first. */ + result = test_standard_vectors(); + if (result != DROPUS_SUCCESS) { + /*return (int)result;*/ + } + + /* Ogg Opus. */ + result = test_ogg_vectors(); + if (result != DROPUS_SUCCESS) { + /*return (int)result;*/ + } + + (void)argc; + (void)argv; + return 0; +} + +#ifdef DR_OPUS_DEBUGGING +int main_debugging(int argc, char** argv) +{ + dr_file_iterator iterator; + dr_file_iterator* pFile = dr_file_iterator_begin("testvectors/opus/oggopus", &iterator); + while (pFile != NULL) { + if (pFile->isDirectory) { + printf("DIRECTORY: %s : %s\n", pFile->relativePath, pFile->absolutePath); + } else { + printf("FILE: %s : %s\n", pFile->relativePath, pFile->absolutePath); + } + + pFile = dr_file_iterator_next(pFile); + } + + (void)argc; + (void)argv; + return 0; +} +#endif diff --git a/source/engine/thirdparty/dr_libs/tests/opus/dr_opus_test_0.c b/source/engine/thirdparty/dr_libs/tests/opus/dr_opus_test_0.c new file mode 100644 index 0000000..0e073b7 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/opus/dr_opus_test_0.c @@ -0,0 +1,9 @@ +#define DR_OPUS_IMPLEMENTATION +#include "../../wip/dr_opus.h" + +int main(int argc, char** argv) +{ + (void)argc; + (void)argv; + return 0; +} \ No newline at end of file diff --git a/source/engine/thirdparty/dr_libs/tests/opus/dr_opus_test_0.cpp b/source/engine/thirdparty/dr_libs/tests/opus/dr_opus_test_0.cpp new file mode 100644 index 0000000..97401ce --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/opus/dr_opus_test_0.cpp @@ -0,0 +1 @@ +#include "dr_opus_test_0.c" diff --git a/source/engine/thirdparty/dr_libs/tests/testvectors/DO_NOT_DELETE.md b/source/engine/thirdparty/dr_libs/tests/testvectors/DO_NOT_DELETE.md new file mode 100644 index 0000000..e69de29 diff --git a/source/engine/thirdparty/dr_libs/tests/testvectors/flac/DO_NOT_DELETE.md b/source/engine/thirdparty/dr_libs/tests/testvectors/flac/DO_NOT_DELETE.md new file mode 100644 index 0000000..e69de29 diff --git a/source/engine/thirdparty/dr_libs/tests/testvectors/mp3/DO_NOT_DELETE.md b/source/engine/thirdparty/dr_libs/tests/testvectors/mp3/DO_NOT_DELETE.md new file mode 100644 index 0000000..e69de29 diff --git a/source/engine/thirdparty/dr_libs/tests/testvectors/opus/DO_NOT_DELETE.md b/source/engine/thirdparty/dr_libs/tests/testvectors/opus/DO_NOT_DELETE.md new file mode 100644 index 0000000..e69de29 diff --git a/source/engine/thirdparty/dr_libs/tests/testvectors/wav/DO_NOT_DELETE.md b/source/engine/thirdparty/dr_libs/tests/testvectors/wav/DO_NOT_DELETE.md new file mode 100644 index 0000000..e69de29 diff --git a/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_common.c b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_common.c new file mode 100644 index 0000000..165b4ad --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_common.c @@ -0,0 +1,326 @@ +#define DR_WAV_IMPLEMENTATION +#include "../../dr_wav.h" +#include +#include "../common/dr_common.c" + +dr_handle g_libsndfile = NULL; + +typedef SNDFILE* (* pfn_sf_open_virtual)(SF_VIRTUAL_IO* sfvirtual, int mode, SF_INFO* sfinfo, void* user_data); +typedef int (* pfn_sf_close) (SNDFILE* sndfile); +typedef sf_count_t (* pfn_sf_readf_short) (SNDFILE *sndfile, short *ptr, sf_count_t frames); +typedef sf_count_t (* pfn_sf_readf_int) (SNDFILE *sndfile, int *ptr, sf_count_t frames); +typedef sf_count_t (* pfn_sf_readf_float) (SNDFILE *sndfile, float *ptr, sf_count_t frames); +typedef sf_count_t (* pfn_sf_readf_double)(SNDFILE *sndfile, double *ptr, sf_count_t frames); +typedef sf_count_t (* pfn_sf_seek) (SNDFILE *sndfile, sf_count_t frames, int whence); + +pfn_sf_open_virtual libsndfile__sf_open_virtual; +pfn_sf_close libsndfile__sf_close; +pfn_sf_readf_short libsndfile__sf_readf_short; +pfn_sf_readf_int libsndfile__sf_readf_int; +pfn_sf_readf_float libsndfile__sf_readf_float; +pfn_sf_readf_double libsndfile__sf_readf_double; +pfn_sf_seek libsndfile__sf_seek; + +drwav_result libsndfile_init_api() +{ + unsigned int i; + const char* pFileNames[] = { +#if defined(_WIN32) + #if defined(_WIN64) + "libsndfile-1-x64.dll", + #else + "libsndfile-1-x86.dll", + #endif + "libsndfile-1.dll" +#else + "libsndfile-1.so", + "libsndfile.so.1" +#endif + }; + + if (g_libsndfile != NULL) { + return DRWAV_INVALID_OPERATION; /* Already initialized. */ + } + + for (i = 0; i < sizeof(pFileNames)/sizeof(pFileNames[0]); i += 1) { + g_libsndfile = dr_dlopen(pFileNames[i]); + if (g_libsndfile != NULL) { + break; + } + } + + if (g_libsndfile == NULL) { + return DRWAV_ERROR; /* Unable to load libsndfile-1.so/dll. */ + } + + libsndfile__sf_open_virtual = (pfn_sf_open_virtual)dr_dlsym(g_libsndfile, "sf_open_virtual"); + libsndfile__sf_close = (pfn_sf_close) dr_dlsym(g_libsndfile, "sf_close"); + libsndfile__sf_readf_short = (pfn_sf_readf_short) dr_dlsym(g_libsndfile, "sf_readf_short"); + libsndfile__sf_readf_int = (pfn_sf_readf_int) dr_dlsym(g_libsndfile, "sf_readf_int"); + libsndfile__sf_readf_float = (pfn_sf_readf_float) dr_dlsym(g_libsndfile, "sf_readf_float"); + libsndfile__sf_readf_double = (pfn_sf_readf_double)dr_dlsym(g_libsndfile, "sf_readf_double"); + libsndfile__sf_seek = (pfn_sf_seek) dr_dlsym(g_libsndfile, "sf_seek"); + + return DRWAV_SUCCESS; +} + +void libsndfile_uninit_api() +{ + if (g_libsndfile == NULL) { + return; /* Invalid operation. Not initialized. */ + } + + dr_dlclose(g_libsndfile); + g_libsndfile = NULL; +} + +typedef struct +{ + SNDFILE* pHandle; + SF_INFO info; + drwav_uint8* pFileData; + size_t fileSizeInBytes; + size_t fileReadPos; +} libsndfile; + +sf_count_t libsndfile__on_filelen(void *user_data) +{ + libsndfile* pSndFile = (libsndfile*)user_data; + return (sf_count_t)pSndFile->fileSizeInBytes; +} + +sf_count_t libsndfile__on_seek(sf_count_t offset, int whence, void *user_data) +{ + libsndfile* pSndFile = (libsndfile*)user_data; + + switch (whence) + { + case SF_SEEK_SET: + { + pSndFile->fileReadPos = (size_t)offset; + } break; + case SF_SEEK_CUR: + { + pSndFile->fileReadPos += (size_t)offset; + } break; + case SF_SEEK_END: + { + pSndFile->fileReadPos = pSndFile->fileSizeInBytes - (size_t)offset; + } break; + } + + return (sf_count_t)pSndFile->fileReadPos; +} + +sf_count_t libsndfile__on_read(void *ptr, sf_count_t count, void *user_data) +{ + libsndfile* pSndFile = (libsndfile*)user_data; + + DRWAV_COPY_MEMORY(ptr, pSndFile->pFileData + pSndFile->fileReadPos, (size_t)count); + pSndFile->fileReadPos += (size_t)count; + + return count; +} + +sf_count_t libsndfile__on_write(const void *ptr, sf_count_t count, void *user_data) +{ + /* We're not doing anything with writing. */ + (void)ptr; + (void)count; + (void)user_data; + return 0; +} + +sf_count_t libsndfile__on_tell(void *user_data) +{ + libsndfile* pSndFile = (libsndfile*)user_data; + return (sf_count_t)pSndFile->fileReadPos; +} + +drwav_result libsndfile_init_file(const char* pFilePath, libsndfile* pSndFile) +{ + SF_VIRTUAL_IO callbacks; + + if (pFilePath == NULL || pSndFile == NULL) { + return DRWAV_INVALID_ARGS; + } + + DRWAV_ZERO_MEMORY(pSndFile, sizeof(*pSndFile)); + + /* We use libsndfile's virtual IO technique because we want to load from memory to make speed benchmarking fairer. */ + pSndFile->pFileData = (drwav_uint8*)dr_open_and_read_file(pFilePath, &pSndFile->fileSizeInBytes); + if (pSndFile->pFileData == NULL) { + return DRWAV_ERROR; /* Failed to open the file. */ + } + + DRWAV_ZERO_MEMORY(&callbacks, sizeof(callbacks)); + callbacks.get_filelen = libsndfile__on_filelen; + callbacks.seek = libsndfile__on_seek; + callbacks.read = libsndfile__on_read; + callbacks.write = libsndfile__on_write; + callbacks.tell = libsndfile__on_tell; + + pSndFile->pHandle = libsndfile__sf_open_virtual(&callbacks, SFM_READ, &pSndFile->info, pSndFile); + if (pSndFile->pHandle == NULL) { + free(pSndFile->pFileData); + return DRWAV_ERROR; + } + + return DRWAV_SUCCESS; +} + +void libsndfile_uninit(libsndfile* pSndFile) +{ + if (pSndFile == NULL) { + return; + } + + libsndfile__sf_close(pSndFile->pHandle); + free(pSndFile->pFileData); +} + +drwav_uint64 libsndfile_read_pcm_frames_s16(libsndfile* pSndFile, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + if (pSndFile == NULL || pBufferOut == NULL) { + return 0; + } + + /* Unfortunately it looks like libsndfile does not return correct integral values when the source file is floating point. */ + if ((pSndFile->info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_FLOAT) { + /* Read as float and convert. */ + drwav_uint64 totalFramesRead = 0; + while (totalFramesRead < framesToRead) { + float temp[4096]; + drwav_uint64 framesRemaining = framesToRead - totalFramesRead; + drwav_uint64 framesReadThisIteration; + drwav_uint64 framesToReadThisIteration = sizeof(temp)/sizeof(temp[0]) / pSndFile->info.channels; + if (framesToReadThisIteration > framesRemaining) { + framesToReadThisIteration = framesRemaining; + } + + framesReadThisIteration = libsndfile__sf_readf_float(pSndFile->pHandle, temp, (sf_count_t)framesToReadThisIteration); + + drwav_f32_to_s16(pBufferOut, temp, (size_t)(framesReadThisIteration*pSndFile->info.channels)); + + totalFramesRead += framesReadThisIteration; + pBufferOut += framesReadThisIteration*pSndFile->info.channels; + + /* If we read less frames than we requested we've reached the end of the file. */ + if (framesReadThisIteration < framesToReadThisIteration) { + break; + } + } + + return totalFramesRead; + } else if ((pSndFile->info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_DOUBLE) { + /* Read as double and convert. */ + drwav_uint64 totalFramesRead = 0; + while (totalFramesRead < framesToRead) { + double temp[4096]; + drwav_uint64 framesRemaining = framesToRead - totalFramesRead; + drwav_uint64 framesReadThisIteration; + drwav_uint64 framesToReadThisIteration = sizeof(temp)/sizeof(temp[0]) / pSndFile->info.channels; + if (framesToReadThisIteration > framesRemaining) { + framesToReadThisIteration = framesRemaining; + } + + framesReadThisIteration = libsndfile__sf_readf_double(pSndFile->pHandle, temp, (sf_count_t)framesToReadThisIteration); + + drwav_f64_to_s16(pBufferOut, temp, (size_t)(framesReadThisIteration*pSndFile->info.channels)); + + totalFramesRead += framesReadThisIteration; + pBufferOut += framesReadThisIteration*pSndFile->info.channels; + + /* If we read less frames than we requested we've reached the end of the file. */ + if (framesReadThisIteration < framesToReadThisIteration) { + break; + } + } + + return totalFramesRead; + } else { + return libsndfile__sf_readf_short(pSndFile->pHandle, pBufferOut, framesToRead); + } +} + +drwav_uint64 libsndfile_read_pcm_frames_f32(libsndfile* pSndFile, drwav_uint64 framesToRead, float* pBufferOut) +{ + if (pSndFile == NULL || pBufferOut == NULL) { + return 0; + } + + return libsndfile__sf_readf_float(pSndFile->pHandle, pBufferOut, framesToRead); +} + +drwav_uint64 libsndfile_read_pcm_frames_s32(libsndfile* pSndFile, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + if (pSndFile == NULL || pBufferOut == NULL) { + return 0; + } + + /* Unfortunately it looks like libsndfile does not return correct integral values when the source file is floating point. */ + if ((pSndFile->info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_FLOAT) { + /* Read floats and convert. */ + drwav_uint64 totalFramesRead = 0; + while (totalFramesRead < framesToRead) { + float temp[4096]; + drwav_uint64 framesRemaining = framesToRead - totalFramesRead; + drwav_uint64 framesReadThisIteration; + drwav_uint64 framesToReadThisIteration = sizeof(temp)/sizeof(temp[0]) / pSndFile->info.channels; + if (framesToReadThisIteration > framesRemaining) { + framesToReadThisIteration = framesRemaining; + } + + framesReadThisIteration = libsndfile__sf_readf_float(pSndFile->pHandle, temp, (sf_count_t)framesToReadThisIteration); + + drwav_f32_to_s32(pBufferOut, temp, (size_t)(framesReadThisIteration*pSndFile->info.channels)); + + totalFramesRead += framesReadThisIteration; + pBufferOut += framesReadThisIteration*pSndFile->info.channels; + + /* If we read less frames than we requested we've reached the end of the file. */ + if (framesReadThisIteration < framesToReadThisIteration) { + break; + } + } + + return totalFramesRead; + } else if ((pSndFile->info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_DOUBLE) { + /* Read doubles and convert. */ + drwav_uint64 totalFramesRead = 0; + while (totalFramesRead < framesToRead) { + double temp[4096]; + drwav_uint64 framesRemaining = framesToRead - totalFramesRead; + drwav_uint64 framesReadThisIteration; + drwav_uint64 framesToReadThisIteration = sizeof(temp)/sizeof(temp[0]) / pSndFile->info.channels; + if (framesToReadThisIteration > framesRemaining) { + framesToReadThisIteration = framesRemaining; + } + + framesReadThisIteration = libsndfile__sf_readf_double(pSndFile->pHandle, temp, (sf_count_t)framesToReadThisIteration); + + drwav_f64_to_s32(pBufferOut, temp, (size_t)(framesReadThisIteration*pSndFile->info.channels)); + + totalFramesRead += framesReadThisIteration; + pBufferOut += framesReadThisIteration*pSndFile->info.channels; + + /* If we read less frames than we requested we've reached the end of the file. */ + if (framesReadThisIteration < framesToReadThisIteration) { + break; + } + } + + return totalFramesRead; + } else { + return libsndfile__sf_readf_int(pSndFile->pHandle, pBufferOut, framesToRead); + } +} + +drwav_bool32 libsndfile_seek_to_pcm_frame(libsndfile* pSndFile, drwav_uint64 targetPCMFrameIndex) +{ + if (pSndFile == NULL) { + return DRWAV_FALSE; + } + + return libsndfile__sf_seek(pSndFile->pHandle, (sf_count_t)targetPCMFrameIndex, SF_SEEK_SET) == (sf_count_t)targetPCMFrameIndex; +} diff --git a/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_decoding.c b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_decoding.c new file mode 100644 index 0000000..bf9decb --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_decoding.c @@ -0,0 +1,446 @@ +#define DR_WAV_LIBSNDFILE_COMPAT +#include "dr_wav_common.c" + +#define FILE_NAME_WIDTH 40 +#define NUMBER_WIDTH 10 +#define TABLE_MARGIN 2 + +#define DEFAULT_SOURCE_DIR "testvectors/wav/tests" + + +drwav_result decode_test__read_and_compare_pcm_frames_s32(libsndfile* pSndFile, drwav* pWav, drwav_uint64 pcmFrameCount, drwav_int32* pPCMFrames_libsndfile, drwav_int32* pPCMFrames_drwav) +{ + drwav_uint64 pcmFrameCount_libsndfile; + drwav_uint64 pcmFrameCount_drwav; + drwav_uint64 iPCMFrame; + + /* To test decoding we just read a number of PCM frames from each decoder and compare. */ + pcmFrameCount_libsndfile = libsndfile_read_pcm_frames_s32(pSndFile, pcmFrameCount, pPCMFrames_libsndfile); + pcmFrameCount_drwav = drwav_read_pcm_frames_s32(pWav, pcmFrameCount, pPCMFrames_drwav); + + /* The total number of frames we decoded need to match. */ + if (pcmFrameCount_libsndfile != pcmFrameCount_drwav) { + printf(" Decoded frame counts differ: pcmFrameCount=%d, libsndfile=%d, dr_wav=%d", (int)pcmFrameCount, (int)pcmFrameCount_libsndfile, (int)pcmFrameCount_drwav); + return DRWAV_ERROR; + } + + /* Each of the decoded PCM frames need to match. */ + DRWAV_ASSERT(pcmFrameCount_libsndfile == pcmFrameCount_drwav); + + for (iPCMFrame = 0; iPCMFrame < pcmFrameCount_libsndfile; iPCMFrame += 1) { + drwav_int32* pPCMFrame_libsndfile = pPCMFrames_libsndfile + (iPCMFrame * pWav->channels); + drwav_int32* pPCMFrame_drwav = pPCMFrames_drwav + (iPCMFrame * pWav->channels); + drwav_uint32 iChannel; + drwav_bool32 hasError = DRWAV_FALSE; + + for (iChannel = 0; iChannel < pWav->channels; iChannel += 1) { + if (pPCMFrame_libsndfile[iChannel] != pPCMFrame_drwav[iChannel]) { + printf(" PCM Frame @ %d[%d] does not match: pcmFrameCount=%d", (int)iPCMFrame, iChannel, (int)pcmFrameCount); + hasError = DRWAV_TRUE; + break; + } + } + + if (hasError) { + return DRWAV_ERROR; /* Decoded frames do not match. */ + } + } + + /* Done. */ + return DRWAV_SUCCESS; +} + +drwav_result decode_test__read_and_compare_pcm_frame_chunks_s32(libsndfile* pSndFile, drwav* pWav, drwav_uint64 pcmFrameChunkSize) +{ + drwav_result result = DRWAV_SUCCESS; + drwav_uint64 iPCMFrame; + drwav_int32* pPCMFrames_libsndfile; + drwav_int32* pPCMFrames_drwav; + + /* Make sure the decoder's are seeked back to the start first. */ + drwav_seek_to_pcm_frame(pWav, 0); + libsndfile_seek_to_pcm_frame(pSndFile, 0); + + pPCMFrames_libsndfile = (drwav_int32*)malloc((size_t)(pcmFrameChunkSize * pWav->channels * sizeof(drwav_int32))); + if (pPCMFrames_libsndfile == NULL) { + printf(" [libsndfile] Out of memory"); + return DRWAV_ERROR; + } + + pPCMFrames_drwav = (drwav_int32*)malloc((size_t)(pcmFrameChunkSize * pWav->channels * sizeof(drwav_int32))); + if (pPCMFrames_drwav == NULL) { + free(pPCMFrames_libsndfile); + printf(" [dr_wav] Out of memory"); + return DRWAV_ERROR; + } + + for (iPCMFrame = 0; iPCMFrame < pWav->totalPCMFrameCount; iPCMFrame += pcmFrameChunkSize) { + result = decode_test__read_and_compare_pcm_frames_s32(pSndFile, pWav, pcmFrameChunkSize, pPCMFrames_libsndfile, pPCMFrames_drwav); + if (result != DRWAV_SUCCESS) { + break; + } + } + + free(pPCMFrames_libsndfile); + free(pPCMFrames_drwav); + + return result; +} + + +drwav_result decode_test__read_and_compare_pcm_frames_f32(libsndfile* pSndFile, drwav* pWav, drwav_uint64 pcmFrameCount, float* pPCMFrames_libsndfile, float* pPCMFrames_drwav) +{ + drwav_uint64 pcmFrameCount_libsndfile; + drwav_uint64 pcmFrameCount_drwav; + drwav_uint64 iPCMFrame; + + /* To test decoding we just read a number of PCM frames from each decoder and compare. */ + pcmFrameCount_libsndfile = libsndfile_read_pcm_frames_f32(pSndFile, pcmFrameCount, pPCMFrames_libsndfile); + pcmFrameCount_drwav = drwav_read_pcm_frames_f32(pWav, pcmFrameCount, pPCMFrames_drwav); + + /* The total number of frames we decoded need to match. */ + if (pcmFrameCount_libsndfile != pcmFrameCount_drwav) { + printf(" Decoded frame counts differ: pcmFrameCount=%d, libsndfile=%d, dr_wav=%d", (int)pcmFrameCount, (int)pcmFrameCount_libsndfile, (int)pcmFrameCount_drwav); + return DRWAV_ERROR; + } + + /* Each of the decoded PCM frames need to match. */ + DRWAV_ASSERT(pcmFrameCount_libsndfile == pcmFrameCount_drwav); + + for (iPCMFrame = 0; iPCMFrame < pcmFrameCount_libsndfile; iPCMFrame += 1) { + float* pPCMFrame_libsndfile = pPCMFrames_libsndfile + (iPCMFrame * pWav->channels); + float* pPCMFrame_drwav = pPCMFrames_drwav + (iPCMFrame * pWav->channels); + drwav_uint32 iChannel; + drwav_bool32 hasError = DRWAV_FALSE; + + for (iChannel = 0; iChannel < pWav->channels; iChannel += 1) { + if (pPCMFrame_libsndfile[iChannel] != pPCMFrame_drwav[iChannel]) { + printf(" PCM Frame @ %d[%d] does not match: pcmFrameCount=%d", (int)iPCMFrame, iChannel, (int)pcmFrameCount); + hasError = DRWAV_TRUE; + break; + } + } + + if (hasError) { + return DRWAV_ERROR; /* Decoded frames do not match. */ + } + } + + /* Done. */ + return DRWAV_SUCCESS; +} + +drwav_result decode_test__read_and_compare_pcm_frame_chunks_f32(libsndfile* pSndFile, drwav* pWav, drwav_uint64 pcmFrameChunkSize) +{ + drwav_result result = DRWAV_SUCCESS; + drwav_uint64 iPCMFrame; + float* pPCMFrames_libsndfile; + float* pPCMFrames_drwav; + + /* Make sure the decoder's are seeked back to the start first. */ + drwav_seek_to_pcm_frame(pWav, 0); + libsndfile_seek_to_pcm_frame(pSndFile, 0); + + pPCMFrames_libsndfile = (float*)malloc((size_t)(pcmFrameChunkSize * pWav->channels * sizeof(float))); + if (pPCMFrames_libsndfile == NULL) { + printf(" [libsndfile] Out of memory"); + return DRWAV_ERROR; + } + + pPCMFrames_drwav = (float*)malloc((size_t)(pcmFrameChunkSize * pWav->channels * sizeof(float))); + if (pPCMFrames_drwav == NULL) { + free(pPCMFrames_libsndfile); + printf(" [dr_wav] Out of memory"); + return DRWAV_ERROR; + } + + for (iPCMFrame = 0; iPCMFrame < pWav->totalPCMFrameCount; iPCMFrame += pcmFrameChunkSize) { + result = decode_test__read_and_compare_pcm_frames_f32(pSndFile, pWav, pcmFrameChunkSize, pPCMFrames_libsndfile, pPCMFrames_drwav); + if (result != DRWAV_SUCCESS) { + break; + } + } + + free(pPCMFrames_libsndfile); + free(pPCMFrames_drwav); + + return result; +} + + +drwav_result decode_test__read_and_compare_pcm_frames_s16(libsndfile* pSndFile, drwav* pWav, drwav_uint64 pcmFrameCount, drwav_int16* pPCMFrames_libsndfile, drwav_int16* pPCMFrames_drwav) +{ + drwav_uint64 pcmFrameCount_libsndfile; + drwav_uint64 pcmFrameCount_drwav; + drwav_uint64 iPCMFrame; + + /* To test decoding we just read a number of PCM frames from each decoder and compare. */ + pcmFrameCount_libsndfile = libsndfile_read_pcm_frames_s16(pSndFile, pcmFrameCount, pPCMFrames_libsndfile); + pcmFrameCount_drwav = drwav_read_pcm_frames_s16(pWav, pcmFrameCount, pPCMFrames_drwav); + + /* The total number of frames we decoded need to match. */ + if (pcmFrameCount_libsndfile != pcmFrameCount_drwav) { + printf(" Decoded frame counts differ: pcmFrameCount=%d, libsndfile=%d, dr_wav=%d", (int)pcmFrameCount, (int)pcmFrameCount_libsndfile, (int)pcmFrameCount_drwav); + return DRWAV_ERROR; + } + + /* Each of the decoded PCM frames need to match. */ + DRWAV_ASSERT(pcmFrameCount_libsndfile == pcmFrameCount_drwav); + + for (iPCMFrame = 0; iPCMFrame < pcmFrameCount_libsndfile; iPCMFrame += 1) { + drwav_int16* pPCMFrame_libsndfile = pPCMFrames_libsndfile + (iPCMFrame * pWav->channels); + drwav_int16* pPCMFrame_drwav = pPCMFrames_drwav + (iPCMFrame * pWav->channels); + drwav_uint32 iChannel; + drwav_bool32 hasError = DRWAV_FALSE; + + for (iChannel = 0; iChannel < pWav->channels; iChannel += 1) { + if (pPCMFrame_libsndfile[iChannel] != pPCMFrame_drwav[iChannel]) { + printf(" PCM Frame @ %d[%d] does not match: pcmFrameCount=%d", (int)iPCMFrame, iChannel, (int)pcmFrameCount); + hasError = DRWAV_TRUE; + break; + } + } + + if (hasError) { + return DRWAV_ERROR; /* Decoded frames do not match. */ + } + } + + /* Done. */ + return DRWAV_SUCCESS; +} + +drwav_result decode_test__read_and_compare_pcm_frame_chunks_s16(libsndfile* pSndFile, drwav* pWav, drwav_uint64 pcmFrameChunkSize) +{ + drwav_result result = DRWAV_SUCCESS; + drwav_uint64 iPCMFrame; + drwav_int16* pPCMFrames_libsndfile; + drwav_int16* pPCMFrames_drwav; + + /* Make sure the decoder's are seeked back to the start first. */ + drwav_seek_to_pcm_frame(pWav, 0); + libsndfile_seek_to_pcm_frame(pSndFile, 0); + + pPCMFrames_libsndfile = (drwav_int16*)malloc((size_t)(pcmFrameChunkSize * pWav->channels * sizeof(drwav_int16))); + if (pPCMFrames_libsndfile == NULL) { + printf(" [libsndfile] Out of memory"); + return DRWAV_ERROR; + } + + pPCMFrames_drwav = (drwav_int16*)malloc((size_t)(pcmFrameChunkSize * pWav->channels * sizeof(drwav_int16))); + if (pPCMFrames_drwav == NULL) { + free(pPCMFrames_libsndfile); + printf(" [dr_wav] Out of memory"); + return DRWAV_ERROR; + } + + for (iPCMFrame = 0; iPCMFrame < pWav->totalPCMFrameCount; iPCMFrame += pcmFrameChunkSize) { + result = decode_test__read_and_compare_pcm_frames_s16(pSndFile, pWav, pcmFrameChunkSize, pPCMFrames_libsndfile, pPCMFrames_drwav); + if (result != DRWAV_SUCCESS) { + break; + } + } + + free(pPCMFrames_libsndfile); + free(pPCMFrames_drwav); + + return result; +} + + +drwav_result decode_test_file_s32(libsndfile* pSndFile, drwav* pWav) +{ + drwav_result result = DRWAV_SUCCESS; + + /* Start with reading the entire file in one go. */ + if (result == DRWAV_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_s32(pSndFile, pWav, pWav->totalPCMFrameCount); + } + + /* Now try with reading one PCM frame at a time.*/ + if (result == DRWAV_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_s32(pSndFile, pWav, 1); + } + + return result; +} + +drwav_result decode_test_file_f32(libsndfile* pSndFile, drwav* pWav) +{ + drwav_result result = DRWAV_SUCCESS; + + /* Start with reading the entire file in one go. */ + if (result == DRWAV_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_f32(pSndFile, pWav, pWav->totalPCMFrameCount); + } + + /* Now try with reading one PCM frame at a time.*/ + if (result == DRWAV_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_f32(pSndFile, pWav, 1); + } + + return result; +} + +drwav_result decode_test_file_s16(libsndfile* pSndFile, drwav* pWav) +{ + drwav_result result = DRWAV_SUCCESS; + + /* Start with reading the entire file in one go. */ + if (result == DRWAV_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_s16(pSndFile, pWav, pWav->totalPCMFrameCount); + } + + /* Now try with reading one PCM frame at a time.*/ + if (result == DRWAV_SUCCESS) { + result = decode_test__read_and_compare_pcm_frame_chunks_s16(pSndFile, pWav, 1); + } + + return result; +} + +drwav_result decode_test_file(const char* pFilePath) +{ + /* To test seeking we just seek to our target PCM frame and then decode whatever is remaining and compare it against libsndfile. */ + drwav_result result; + libsndfile libsndfile; + drwav wav; + + dr_printf_fixed_with_margin(FILE_NAME_WIDTH, TABLE_MARGIN, "%s", dr_path_file_name(pFilePath)); + + /* First load the decoder from libsndfile. */ + result = libsndfile_init_file(pFilePath, &libsndfile); + if (result != DRWAV_SUCCESS) { + printf(" Failed to open via libsndfile."); + return result; + } + + /* Now load from dr_wav. */ + if (!drwav_init_file_with_metadata(&wav, pFilePath, 0, NULL)) { + printf(" Failed to open via dr_wav."); + libsndfile_uninit(&libsndfile); + return DRWAV_ERROR; /* Failed to load dr_wav decoder. */ + } + + /* At this point we should have both libsndfile and dr_wav decoders open. We can now perform identical operations on each of them and compare. */ + result = decode_test_file_s32(&libsndfile, &wav); + if (result != DRWAV_SUCCESS) { + drwav_uninit(&wav); + libsndfile_uninit(&libsndfile); + return result; + } + + result = decode_test_file_f32(&libsndfile, &wav); + if (result != DRWAV_SUCCESS) { + drwav_uninit(&wav); + libsndfile_uninit(&libsndfile); + return result; + } + + result = decode_test_file_s16(&libsndfile, &wav); + if (result != DRWAV_SUCCESS) { + drwav_uninit(&wav); + libsndfile_uninit(&libsndfile); + return result; + } + + + /* We're done with our decoders. */ + drwav_uninit(&wav); + libsndfile_uninit(&libsndfile); + + if (result == DRWAV_SUCCESS) { + printf(" Passed"); + } + + return result; +} + +drwav_result decode_test_directory(const char* pDirectoryPath) +{ + dr_file_iterator iteratorState; + dr_file_iterator* pFile; + + dr_printf_fixed(FILE_NAME_WIDTH, "%s", pDirectoryPath); + dr_printf_fixed_with_margin(NUMBER_WIDTH, TABLE_MARGIN, "RESULT"); + printf("\n"); + + pFile = dr_file_iterator_begin(pDirectoryPath, &iteratorState); + while (pFile != NULL) { + drwav_result result; + + /* Skip directories for now, but we may want to look at doing recursive file iteration. */ + if (!pFile->isDirectory) { + result = decode_test_file(pFile->absolutePath); + (void)result; + + printf("\n"); + } + + pFile = dr_file_iterator_next(pFile); + } + + return DRWAV_SUCCESS; +} + +drwav_result decode_test() +{ + drwav_result result = DRWAV_SUCCESS; + + /* Directories. */ + { + result = decode_test_directory(DEFAULT_SOURCE_DIR); + (void)result; + } + + return result; +} + +drwav_result decode_profiling() +{ + return DRWAV_SUCCESS; +} + +int main(int argc, char** argv) +{ + drwav_result result = DRWAV_SUCCESS; + drwav_bool32 doTesting = DRWAV_TRUE; + drwav_bool32 doProfiling = DRWAV_TRUE; + + if (dr_argv_is_set(argc, argv, "--onlyprofile")) { + doTesting = DRWAV_FALSE; + } + + if (libsndfile_init_api() != DRWAV_SUCCESS) { + printf("Failed to initialize libsndfile API."); + return -1; + } + + if (doTesting) { + printf("=======================================================================\n"); + printf("DECODE TESTING\n"); + printf("=======================================================================\n"); + result = decode_test(); + if (result != DRWAV_SUCCESS) { + goto done; /* Don't continue if an error occurs during testing. */ + } + printf("\n"); + } else { + printf("=======================================================================\n"); + printf("WARNING: Correctness Tests Disabled\n"); + printf("=======================================================================\n"); + } + + /* Profiling. */ + if (doProfiling) { + printf("=======================================================================\n"); + printf("DECODE PROFILING (LOWER IS BETTER)\n"); + printf("=======================================================================\n"); + result = decode_profiling(); + printf("\n"); + } + +done: + libsndfile_uninit_api(); + return (int)result; +} diff --git a/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_decoding.cpp b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_decoding.cpp new file mode 100644 index 0000000..a66ae71 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_decoding.cpp @@ -0,0 +1 @@ +#include "dr_wav_decoding.c" diff --git a/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_encoding.c b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_encoding.c new file mode 100644 index 0000000..8506632 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_encoding.c @@ -0,0 +1,68 @@ +#define DR_WAV_IMPLEMENTATION +#include "../../dr_wav.h" +#include + +void generate_sine_wave(float* pOutput, drwav_uint64 frameCount, drwav_uint32 channels, drwav_uint32 sampleRate, float frequency, float* t) +{ + float x = *t; + float a = 1.0f / sampleRate; + drwav_uint64 iFrame; + drwav_uint32 iChannel; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + float s = (float)(sin(3.1415965*2 * x * frequency) * 0.25); + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pOutput[iFrame*channels + iChannel] = s; + } + + x += a; + } + + *t = x; +} + +int main(int argc, char** argv) +{ + drwav_data_format format; + drwav wav; + float t = 0; + float tempFrames[4096]; + drwav_uint64 totalFramesToWrite; + drwav_uint64 totalFramesWritten = 0; + + + if (argc < 2) { + printf("No output file specified.\n"); + return -1; + } + + format.container = drwav_container_riff; + format.format = DR_WAVE_FORMAT_IEEE_FLOAT; + format.channels = 2; + format.sampleRate = 44100; + format.bitsPerSample = 32; + if (!drwav_init_file_write(&wav, argv[1], &format, NULL)) { + printf("Failed to open file.\n"); + return -1; + } + + totalFramesToWrite = format.sampleRate * 1; + totalFramesWritten = 0; + + while (totalFramesToWrite > totalFramesWritten) { + drwav_uint64 framesRemaining = totalFramesToWrite - totalFramesWritten; + drwav_uint64 framesToWriteNow = drwav_countof(tempFrames) / format.channels; + if (framesToWriteNow > framesRemaining) { + framesToWriteNow = framesRemaining; + } + + generate_sine_wave(tempFrames, framesToWriteNow, format.channels, format.sampleRate, 400, &t); + drwav_write_pcm_frames(&wav, framesToWriteNow, tempFrames); + + totalFramesWritten += framesToWriteNow; + } + + drwav_uninit(&wav); + + return 0; +} \ No newline at end of file diff --git a/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_playback.c b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_playback.c new file mode 100644 index 0000000..4410b85 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_playback.c @@ -0,0 +1,70 @@ +#define MA_NO_DECODING +#define MA_NO_ENCODING +#define MA_IMPLEMENTATION +#include "../../../miniaudio/miniaudio.h" + +#define DR_WAV_IMPLEMENTATION +#include "../../dr_wav.h" +#include "../common/dr_common.c" + +drwav g_wav; + +void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + /* Assuming format is always s16 for now. */ + if (pDevice->playback.format == ma_format_s16) { + drwav_read_pcm_frames_s16(&g_wav, frameCount, (drwav_int16*)pOutput); + } else if (pDevice->playback.format == ma_format_f32) { + drwav_read_pcm_frames_f32(&g_wav, frameCount, (float*)pOutput); + } else { + /* Unsupported format. */ + } + + (void)pInput; +} + +int main(int argc, char** argv) +{ + ma_result result; + ma_device device; + ma_device_config deviceConfig; + + if (argc < 2) { + printf("No input file specified."); + return -1; + } + + if (!drwav_init_file_with_metadata(&g_wav, argv[1], 0, NULL)) { + printf("Failed to load file: %s", argv[1]); + return -1; + } + + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.format = ma_format_s16; + deviceConfig.playback.channels = g_wav.channels; + deviceConfig.sampleRate = g_wav.sampleRate; + deviceConfig.dataCallback = data_callback; + + result = ma_device_init(NULL, &deviceConfig, &device); + if (result != MA_SUCCESS) { + printf("Failed to initialize playback device."); + drwav_uninit(&g_wav); + return -1; + } + + result = ma_device_start(&device); + if (result != MA_SUCCESS) { + printf("Failed to start playback device."); + ma_device_uninit(&device); + drwav_uninit(&g_wav); + return -1; + } + + printf("Press Enter to quit..."); + getchar(); + + ma_device_uninit(&device); + drwav_uninit(&g_wav); + + return 0; +} \ No newline at end of file diff --git a/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_playback.cpp b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_playback.cpp new file mode 100644 index 0000000..0127e1a --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_playback.cpp @@ -0,0 +1 @@ +#include "dr_wav_playback.c" diff --git a/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_test_0.c b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_test_0.c new file mode 100644 index 0000000..7f6a88b --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_test_0.c @@ -0,0 +1,11 @@ +#define DR_WAV_IMPLEMENTATION +#include "../../dr_wav.h" + +#include "../common/dr_common.c" + +int main(int argc, char** argv) +{ + (void)argc; + (void)argv; + return 0; +} diff --git a/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_test_0.cpp b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_test_0.cpp new file mode 100644 index 0000000..f446b65 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/tests/wav/dr_wav_test_0.cpp @@ -0,0 +1 @@ +#include "dr_wav_test_0.c" diff --git a/source/engine/thirdparty/dr_libs/wip/dr_opus.h b/source/engine/thirdparty/dr_libs/wip/dr_opus.h new file mode 100644 index 0000000..f399886 --- /dev/null +++ b/source/engine/thirdparty/dr_libs/wip/dr_opus.h @@ -0,0 +1,2565 @@ +/* +Opus audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. +dr_opus - v0.0.0 (Unreleased) - xxxx-xx-xx + +David Reid - mackron@gmail.com +*/ + +/* ====== WORK-IN-PROGRESSS ====== */ + +#ifndef dr_opus_h +#define dr_opus_h + +#include /* For size_t. */ + +/* Sized types. Prefer built-in types. Fall back to stdint. */ +#ifdef _MSC_VER + #if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wlanguage-extension-token" + #pragma GCC diagnostic ignored "-Wlong-long" + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif + typedef signed __int8 dropus_int8; + typedef unsigned __int8 dropus_uint8; + typedef signed __int16 dropus_int16; + typedef unsigned __int16 dropus_uint16; + typedef signed __int32 dropus_int32; + typedef unsigned __int32 dropus_uint32; + typedef signed __int64 dropus_int64; + typedef unsigned __int64 dropus_uint64; + #if defined(__clang__) + #pragma GCC diagnostic pop + #endif + + #if defined(_WIN32) + #if defined(_WIN64) + typedef dropus_uint64 dropus_uintptr; + #else + typedef dropus_uint32 dropus_uintptr; + #endif + #elif defined(__GNUC__) + #if defined(__LP64__) + typedef dropus_uint64 dropus_uintptr; + #else + typedef dropus_uint32 dropus_uintptr; + #endif + #else + typedef dropus_uint64 dropus_uintptr; /* Fallback. */ + #endif +#else + #include + typedef int8_t dropus_int8; + typedef uint8_t dropus_uint8; + typedef int16_t dropus_int16; + typedef uint16_t dropus_uint16; + typedef int32_t dropus_int32; + typedef uint32_t dropus_uint32; + typedef int64_t dropus_int64; + typedef uint64_t dropus_uint64; + typedef uintptr_t dropus_uintptr; +#endif +typedef dropus_uint8 dropus_bool8; +typedef dropus_uint32 dropus_bool32; +#define DROPUS_TRUE 1 +#define DROPUS_FALSE 0 + +typedef void* dropus_handle; +typedef void* dropus_ptr; +typedef void (* dropus_proc)(void); + +#ifndef NULL +#define NULL 0 +#endif + +#if defined(SIZE_MAX) + #define DROPUS_SIZE_MAX SIZE_MAX +#else + #define DROPUS_SIZE_MAX 0xFFFFFFFF /* When SIZE_MAX is not defined by the standard library just default to the maximum 32-bit unsigned integer. */ +#endif + +#ifdef _MSC_VER + #define DROPUS_INLINE __forceinline +#elif defined(__GNUC__) + /* + I've had a bug report where GCC is emitting warnings about functions possibly not being inlineable. This warning happens when + the __attribute__((always_inline)) attribute is defined without an "inline" statement. I think therefore there must be some + case where "__inline__" is not always defined, thus the compiler emitting these warnings. When using -std=c89 or -ansi on the + command line, we cannot use the "inline" keyword and instead need to use "__inline__". In an attempt to work around this issue + I am using "__inline__" only when we're compiling in strict ANSI mode. + */ + #if defined(__STRICT_ANSI__) + #define DROPUS_INLINE __inline__ __attribute__((always_inline)) + #else + #define DROPUS_INLINE inline __attribute__((always_inline)) + #endif +#else + #define DROPUS_INLINE +#endif + +#if !defined(DROPUS_API) + #if defined(DROPUS_DLL) + #if defined(_WIN32) + #define DROPUS_DLL_IMPORT __declspec(dllimport) + #define DROPUS_DLL_EXPORT __declspec(dllexport) + #define DROPUS_DLL_PRIVATE static + #else + #if defined(__GNUC__) && __GNUC__ >= 4 + #define DROPUS_DLL_IMPORT __attribute__((visibility("default"))) + #define DROPUS_DLL_EXPORT __attribute__((visibility("default"))) + #define DROPUS_DLL_PRIVATE __attribute__((visibility("hidden"))) + #else + #define DROPUS_DLL_IMPORT + #define DROPUS_DLL_EXPORT + #define DROPUS_DLL_PRIVATE static + #endif + #endif + + #if defined(DR_OPUS_IMPLEMENTATION) || defined(DROPUS_IMPLEMENTATION) + #define DROPUS_API DROPUS_DLL_EXPORT + #else + #define DROPUS_API DROPUS_DLL_IMPORT + #endif + #define DROPUS_PRIVATE DROPUS_DLL_PRIVATE + #else + #define DROPUS_API extern + #define DROPUS_PRIVATE static + #endif +#endif + +typedef int dropus_result; +#define DROPUS_SUCCESS 0 +#define DROPUS_ERROR -1 /* A generic error. */ +#define DROPUS_INVALID_ARGS -2 +#define DROPUS_INVALID_OPERATION -3 +#define DROPUS_OUT_OF_MEMORY -4 +#define DROPUS_OUT_OF_RANGE -5 +#define DROPUS_ACCESS_DENIED -6 +#define DROPUS_DOES_NOT_EXIST -7 +#define DROPUS_ALREADY_EXISTS -8 +#define DROPUS_TOO_MANY_OPEN_FILES -9 +#define DROPUS_INVALID_FILE -10 +#define DROPUS_TOO_BIG -11 +#define DROPUS_PATH_TOO_LONG -12 +#define DROPUS_NAME_TOO_LONG -13 +#define DROPUS_NOT_DIRECTORY -14 +#define DROPUS_IS_DIRECTORY -15 +#define DROPUS_DIRECTORY_NOT_EMPTY -16 +#define DROPUS_END_OF_FILE -17 +#define DROPUS_NO_SPACE -18 +#define DROPUS_BUSY -19 +#define DROPUS_IO_ERROR -20 +#define DROPUS_INTERRUPT -21 +#define DROPUS_UNAVAILABLE -22 +#define DROPUS_ALREADY_IN_USE -23 +#define DROPUS_BAD_ADDRESS -24 +#define DROPUS_BAD_SEEK -25 +#define DROPUS_BAD_PIPE -26 +#define DROPUS_DEADLOCK -27 +#define DROPUS_TOO_MANY_LINKS -28 +#define DROPUS_NOT_IMPLEMENTED -29 +#define DROPUS_NO_MESSAGE -30 +#define DROPUS_BAD_MESSAGE -31 +#define DROPUS_NO_DATA_AVAILABLE -32 +#define DROPUS_INVALID_DATA -33 +#define DROPUS_TIMEOUT -34 +#define DROPUS_NO_NETWORK -35 +#define DROPUS_NOT_UNIQUE -36 +#define DROPUS_NOT_SOCKET -37 +#define DROPUS_NO_ADDRESS -38 +#define DROPUS_BAD_PROTOCOL -39 +#define DROPUS_PROTOCOL_UNAVAILABLE -40 +#define DROPUS_PROTOCOL_NOT_SUPPORTED -41 +#define DROPUS_PROTOCOL_FAMILY_NOT_SUPPORTED -42 +#define DROPUS_ADDRESS_FAMILY_NOT_SUPPORTED -43 +#define DROPUS_SOCKET_NOT_SUPPORTED -44 +#define DROPUS_CONNECTION_RESET -45 +#define DROPUS_ALREADY_CONNECTED -46 +#define DROPUS_NOT_CONNECTED -47 +#define DROPUS_CONNECTION_REFUSED -48 +#define DROPUS_NO_HOST -49 +#define DROPUS_IN_PROGRESS -50 +#define DROPUS_CANCELLED -51 +#define DROPUS_MEMORY_ALREADY_MAPPED -52 +#define DROPUS_AT_END -53 +#define DROPUS_CRC_MISMATCH -100 +#define DROPUS_BAD_DATA -101 + +/*********************************************************************************************************************************************************** + +Low-Level Opus Stream API + +This API is where the low-level decoding takes place. In order to use this, you must know the packet structure of the Opus stream. This is usually the job of +encapsulations such as Ogg and Matroska. + +************************************************************************************************************************************************************/ +#define DROPUS_MAX_OPUS_FRAMES_PER_PACKET 48 /* RFC 6716 - Section 3.2.5 */ +#define DROPUS_MAX_PCM_FRAMES_PER_PACKET 6144 /* RFC 6716 - Section 3.2.5. Maximum of 120ms. Maximum rate is 48kHz. 6144 = 120*48. */ + +typedef enum +{ + dropus_mode_silk, + dropus_mode_celt, + dropus_mode_hybrid +} dropus_mode; + +typedef struct +{ + dropus_uint16 sizeInBytes; +} dropus_stream_frame; + +typedef struct +{ + dropus_uint8 toc; /* TOC byte. RFC 6716 - Section 3.1 */ + dropus_stream_frame frames[DROPUS_MAX_OPUS_FRAMES_PER_PACKET]; +} dropus_stream_packet; + +typedef struct +{ + dropus_stream_packet packet; /* The current packet. */ + struct + { + dropus_int32 w0_Q13_prev; /* Previous stereo weights. */ + dropus_int32 w1_Q13_prev; + } silk; +} dropus_stream; + +/* +Initializes a new low-level Opus stream object. +*/ +DROPUS_API dropus_result dropus_stream_init(dropus_stream* pOpusStream); + +/* +Decodes a packet from raw compressed data. +*/ +DROPUS_API dropus_result dropus_stream_decode_packet(dropus_stream* pOpusStream, const void* pData, size_t dataSize); + + + +/*********************************************************************************************************************************************************** + +High-Level Opus Decoding API + +************************************************************************************************************************************************************/ +typedef enum +{ + dropus_seek_origin_start, + dropus_seek_origin_current +} dropus_seek_origin; + +typedef size_t (* dropus_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); +typedef dropus_bool32 (* dropus_seek_proc)(void* pUserData, int offset, dropus_seek_origin origin); + +typedef struct +{ + void* pUserData; + void* (* onMalloc)(size_t sz, void* pUserData); + void* (* onRealloc)(void* p, size_t sz, void* pUserData); + void (* onFree)(void* p, void* pUserData); +} dropus_allocation_callbacks; + +typedef struct +{ + dropus_read_proc onRead; + dropus_seek_proc onSeek; + void* pUserData; + dropus_allocation_callbacks allocationCallbacks; + void* pFile; /* Only used for decoders that were opened against a file. */ + struct + { + const dropus_uint8* pData; + size_t dataSize; + size_t currentReadPos; + } memory; /* Only used for decoders that were opened against a block of memory. */ +} dropus; + +/* +Initializes a pre-allocated decoder object from callbacks. +*/ +DROPUS_API dropus_result dropus_init(dropus* pOpus, dropus_read_proc onRead, dropus_seek_proc onSeek, void* pUserData, const dropus_allocation_callbacks* pAllocationCallbacks); + +#ifndef DR_OPUS_NO_STDIO +/* +Initializes a pre-allocated decoder object from a file. + +This keeps hold of the file handle throughout the lifetime of the decoder and closes it in dropus_uninit(). +*/ +DROPUS_API dropus_result dropus_init_file(dropus* pOpus, const char* pFilePath, const dropus_allocation_callbacks* pAllocationCallbacks); +DROPUS_API dropus_result dropus_init_file_w(dropus* pOpus, const wchar_t* pFilePath, const dropus_allocation_callbacks* pAllocationCallbacks); +#endif + +/* +Initializes a pre-allocated decoder object from a block of memory. + +This does not make a copy of the memory. +*/ +DROPUS_API dropus_result dropus_init_memory(dropus* pOpus, const void* pData, size_t dataSize, const dropus_allocation_callbacks* pAllocationCallbacks); + +/* +Uninitializes an Opus decoder. +*/ +DROPUS_API void dropus_uninit(dropus* pOpus); + + + +/************************************************************************************************************************************************************ + +Utilities + +************************************************************************************************************************************************************/ +/* +Retrieves a human readable description of the given result code. +*/ +DROPUS_API const char* dropus_result_description(dropus_result result); + +/* +Allocates memory from callbacks. + + +Parameters +---------- +sz (in) + The size of the allocation in bytes. + +pAllocationCallbacks (in, optional) + A pointer to the `dropus_allocation_callbacks` object containing pointers to the allocation routines. + + +Return Value +------------ +A pointer to the allocated block of memory. NULL if an error occurs. + + +Remarks +------- +`pAllocationCallbacks` can be NULL, in which case DROPUS_MALLOC() will be used. Otherwise, if `pAllocationCallbacks` is not null, either `onMalloc` or +`onRealloc` must be set. If `onMalloc` is NULL, it will fall back to `onRealloc`. If both `onMalloc` and `onRealloc` are NULL, NULL will be returned. +*/ +DROPUS_API void* dropus_malloc(size_t sz, const dropus_allocation_callbacks* pAllocationCallbacks); + +/* +Reallocates memory from callbacks. + + +Parameters +---------- +p (in) + A pointer to the memory being reallocated. + +sz (in) + The size of the allocation in bytes. + +pAllocationCallbacks (in, optional) + A pointer to the `dropus_allocation_callbacks` object containing pointers to the allocation routines. + + +Return Value +------------ +A pointer to the allocated block of memory. NULL if an error occurs. + + +Remarks +------- +`pAllocationCallbacks` can be NULL, in which case DROPUS_REALLOC() will be used. If `onRealloc` is NULL, this will fail and NULL will be returned. +*/ +DROPUS_API void* dropus_realloc(void* p, size_t sz, const dropus_allocation_callbacks* pAllocationCallbacks); + +/* +Frees memory allocated by `dropus_malloc()` or `dropus_realloc()`. + + +Parameters +---------- +p (in) + A pointer to the memory being freed. + + +Remarks +------- +`pAllocationCallbacks` can be NULL in which case DROPUS_FREE() will be used. If `onFree` is NULL, this will be a no-op. +*/ +DROPUS_API void dropus_free(void* p, const dropus_allocation_callbacks* pAllocationCallbacks); + + +#endif /* dr_opus_h */ + +/************************************************************************************************************************************************************ + ************************************************************************************************************************************************************ + + IMPLEMENTATION + + ************************************************************************************************************************************************************ + ************************************************************************************************************************************************************/ +#if defined(DR_OPUS_IMPLEMENTATION) || defined(DROPUS_IMPLEMENTATION) +#include +#include +#ifndef DR_OPUS_NO_STDIO +#include +#include /* For wcslen(), wcsrtombs() */ +#endif + +/* CPU Architecture. */ +#if defined(__x86_64__) || defined(_M_X64) + #define DROPUS_X64 +#elif defined(__i386) || defined(_M_IX86) + #define DROPUS_X86 +#elif defined(__arm__) || defined(_M_ARM) + #define DROPUS_ARM +#endif + +/* Compile-time CPU feature support. */ +#if !defined(DR_OPUS_NO_SIMD) && (defined(DROPUS_X86) || defined(DROPUS_X64)) + #if defined(_MSC_VER) && !defined(__clang__) + #if _MSC_VER >= 1400 + #include + static DROPUS_INLINE void dropus__cpuid(int info[4], int fid) + { + __cpuid(info, fid); + } + #else + #define DROPUS_NO_CPUID + #endif + #else + #if defined(__GNUC__) || defined(__clang__) + static DROPUS_INLINE void dropus__cpuid(int info[4], int fid) + { + /* + It looks like the -fPIC option uses the ebx register which GCC complains about. We can work around this by just using a different register, the + specific register of which I'm letting the compiler decide on. The "k" prefix is used to specify a 32-bit register. The {...} syntax is for + supporting different assembly dialects. + + What's basically happening is that we're saving and restoring the ebx register manually. + */ + #if defined(DROPUS_X86) && defined(__PIC__) + __asm__ __volatile__ ( + "xchg{l} {%%}ebx, %k1;" + "cpuid;" + "xchg{l} {%%}ebx, %k1;" + : "=a"(info[0]), "=&r"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) + ); + #else + __asm__ __volatile__ ( + "cpuid" : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) + ); + #endif + } + #else + #define DROPUS_NO_CPUID + #endif + #endif +#else + #define DROPUS_NO_CPUID +#endif + + +#if defined(_MSC_VER) && _MSC_VER >= 1500 && (defined(DROPUS_X86) || defined(DROPUS_X64)) + #define DROPUS_HAS_LZCNT_INTRINSIC +#elif (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) + #define DROPUS_HAS_LZCNT_INTRINSIC +#elif defined(__clang__) + #if __has_builtin(__builtin_clzll) || __has_builtin(__builtin_clzl) + #define DROPUS_HAS_LZCNT_INTRINSIC + #endif +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + #define DROPUS_HAS_BYTESWAP16_INTRINSIC + #define DROPUS_HAS_BYTESWAP32_INTRINSIC + #define DROPUS_HAS_BYTESWAP64_INTRINSIC +#elif defined(__clang__) + #if __has_builtin(__builtin_bswap16) + #define DROPUS_HAS_BYTESWAP16_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap32) + #define DROPUS_HAS_BYTESWAP32_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap64) + #define DROPUS_HAS_BYTESWAP64_INTRINSIC + #endif +#elif defined(__GNUC__) + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) + #define DROPUS_HAS_BYTESWAP32_INTRINSIC + #define DROPUS_HAS_BYTESWAP64_INTRINSIC + #endif + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #define DROPUS_HAS_BYTESWAP16_INTRINSIC + #endif +#endif + + +#ifndef DROPUS_ASSERT +#include +#define DROPUS_ASSERT(expression) assert(expression) +#endif +#ifndef DROPUS_MALLOC +#define DROPUS_MALLOC(sz) malloc((sz)) +#endif +#ifndef DROPUS_REALLOC +#define DROPUS_REALLOC(p, sz) realloc((p), (sz)) +#endif +#ifndef DROPUS_FREE +#define DROPUS_FREE(p) free((p)) +#endif +#ifndef DROPUS_COPY_MEMORY +#define DROPUS_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#ifndef DROPUS_ZERO_MEMORY +#define DROPUS_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif +#ifndef DROPUS_ZERO_OBJECT +#define DROPUS_ZERO_OBJECT(p) DROPUS_ZERO_MEMORY((p), sizeof(*(p))) +#endif + +#ifndef DROPUS_MIN +#define DROPUS_MIN(x, y) (((x) < (y)) ? (x) : (y)) +#endif +#ifndef DROPUS_MAX +#define DROPUS_MAX(x, y) (((x) > (y)) ? (x) : (y)) +#endif +#ifndef DROPUS_COUNTOF +#define DROPUS_COUNTOF(p) (sizeof(p) / sizeof((p)[0])) +#endif + + +/*********************************** +Endian Management +************************************/ +static DROPUS_INLINE dropus_bool32 dropus__is_little_endian() +{ +#if defined(DROPUS_X86) || defined(DROPUS_X64) + return DROPUS_TRUE; +#elif defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN + return DROPUS_TRUE; +#else + int n = 1; + return (*(char*)&n) == 1; +#endif +} + +static DROPUS_INLINE dropus_uint16 dropus__swap_endian_uint16(dropus_uint16 n) +{ +#ifdef DROPUS_HAS_BYTESWAP16_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_ushort(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap16(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & 0xFF00) >> 8) | + ((n & 0x00FF) << 8); +#endif +} + +static DROPUS_INLINE dropus_uint32 dropus__swap_endian_uint32(dropus_uint32 n) +{ +#ifdef DROPUS_HAS_BYTESWAP32_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_ulong(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap32(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & 0xFF000000) >> 24) | + ((n & 0x00FF0000) >> 8) | + ((n & 0x0000FF00) << 8) | + ((n & 0x000000FF) << 24); +#endif +} + +static DROPUS_INLINE dropus_uint64 dropus__swap_endian_uint64(dropus_uint64 n) +{ +#ifdef DROPUS_HAS_BYTESWAP64_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_uint64(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap64(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & (dropus_uint64)0xFF00000000000000) >> 56) | + ((n & (dropus_uint64)0x00FF000000000000) >> 40) | + ((n & (dropus_uint64)0x0000FF0000000000) >> 24) | + ((n & (dropus_uint64)0x000000FF00000000) >> 8) | + ((n & (dropus_uint64)0x00000000FF000000) << 8) | + ((n & (dropus_uint64)0x0000000000FF0000) << 24) | + ((n & (dropus_uint64)0x000000000000FF00) << 40) | + ((n & (dropus_uint64)0x00000000000000FF) << 56); +#endif +} + + +static DROPUS_INLINE dropus_uint16 dropus__be2host_16(dropus_uint16 n) +{ + if (dropus__is_little_endian()) { + return dropus__swap_endian_uint16(n); + } + + return n; +} + +static DROPUS_INLINE dropus_uint32 dropus__be2host_32(dropus_uint32 n) +{ + if (dropus__is_little_endian()) { + return dropus__swap_endian_uint32(n); + } + + return n; +} + +static DROPUS_INLINE dropus_uint64 dropus__be2host_64(dropus_uint64 n) +{ + if (dropus__is_little_endian()) { + return dropus__swap_endian_uint64(n); + } + + return n; +} + + +static DROPUS_INLINE dropus_uint16 dropus__le2host_16(dropus_uint16 n) +{ + if (!dropus__is_little_endian()) { + return dropus__swap_endian_uint16(n); + } + + return n; +} + +static DROPUS_INLINE dropus_uint32 dropus__le2host_32(dropus_uint32 n) +{ + if (!dropus__is_little_endian()) { + return dropus__swap_endian_uint32(n); + } + + return n; +} + +static DROPUS_INLINE dropus_uint64 dropus__le2host_64(dropus_uint64 n) +{ + if (!dropus__is_little_endian()) { + return dropus__swap_endian_uint64(n); + } + + return n; +} + + +/*********************************************************************************************************************************************************** + +Low-Level Opus Stream API + +************************************************************************************************************************************************************/ +#define DROPUS_MAX_FRAME_SIZE_IN_BYTES 1275 /* RFC 6716 - Section 3.4 [R2] */ +#define DROPUS_MAX_PACKET_SIZE_IN_BYTES DROPUS_MAX_FRAME_SIZE_IN_BYTES*DROPUS_MAX_OPUS_FRAMES_PER_PACKET + +/*********************************** +RFC 6716 - Section 3.1 The TOC Byte +************************************/ +static DROPUS_INLINE dropus_uint8 dropus_toc_config(dropus_uint8 toc) +{ + return (toc & 0xF8) >> 3; +} + +static DROPUS_INLINE dropus_uint8 dropus_toc_s(dropus_uint8 toc) +{ + return (toc & 0x04) >> 2; +} + +static DROPUS_INLINE dropus_uint8 dropus_toc_c(dropus_uint8 toc) +{ + return (toc & 0x03); +} + +static DROPUS_INLINE dropus_mode dropus_toc_config_mode(dropus_uint8 config) +{ + /* Table 2 in RFC 6716 */ + static dropus_mode modes[32] = { + dropus_mode_silk, dropus_mode_silk, dropus_mode_silk, dropus_mode_silk, /* 0...3 */ + dropus_mode_silk, dropus_mode_silk, dropus_mode_silk, dropus_mode_silk, /* 4...7 */ + dropus_mode_silk, dropus_mode_silk, dropus_mode_silk, dropus_mode_silk, /* 8...11 */ + dropus_mode_hybrid, dropus_mode_hybrid, /* 12...13 */ + dropus_mode_hybrid, dropus_mode_hybrid, /* 14...15 */ + dropus_mode_celt, dropus_mode_celt, dropus_mode_celt, dropus_mode_celt, /* 16...19 */ + dropus_mode_celt, dropus_mode_celt, dropus_mode_celt, dropus_mode_celt, /* 20...23 */ + dropus_mode_celt, dropus_mode_celt, dropus_mode_celt, dropus_mode_celt, /* 24...27 */ + dropus_mode_celt, dropus_mode_celt, dropus_mode_celt, dropus_mode_celt /* 28...31 */ + }; + + DROPUS_ASSERT(config < 32); + return modes[config]; +} + +static DROPUS_INLINE dropus_mode dropus_toc_mode(dropus_uint8 toc) +{ + return dropus_toc_config_mode(dropus_toc_config(toc)); +} + +static DROPUS_INLINE dropus_uint32 dropus_toc_config_sample_rate(dropus_uint8 config) +{ + /* Table 2 with Table 1 in RFC 6716 */ + static dropus_uint32 rates[32] = { + 8000, 8000, 8000, 8000, /* 0...3 */ + 12000, 12000, 12000, 12000, /* 4...7 */ + 16000, 16000, 16000, 16000, /* 8...11 */ + 24000, 24000, /* 12...13 */ + 48000, 48000, /* 14...15 */ + 8000, 8000, 8000, 8000, /* 16...19 */ + 16000, 16000, 16000, 16000, /* 20...23 */ + 24000, 24000, 24000, 24000, /* 24...27 */ + 48000, 48000, 48000, 48000 /* 28...31 */ + }; + + DROPUS_ASSERT(config < 32); + return rates[config]; +} + +static DROPUS_INLINE dropus_uint32 dropus_toc_sample_rate(dropus_uint8 toc) +{ + return dropus_toc_config_sample_rate(dropus_toc_config(toc)); +} + +static DROPUS_INLINE dropus_uint32 dropus_toc_sample_rate_ms(dropus_uint8 toc) +{ + return dropus_toc_sample_rate(toc) / 1000; +} + +static DROPUS_INLINE dropus_uint32 dropus_toc_config_frame_size_in_pcm_frames(dropus_uint8 config) +{ + /* Table 2 with Table 1 in RFC 6716 */ + static dropus_uint32 sizes[32] = { + 80, 160, 320, 480, /* 0...3 */ + 120, 240, 480, 720, /* 4...7 */ + 160, 320, 640, 960, /* 8...11 */ + 240, 480, /* 12...13 */ + 480, 960, /* 14...15 */ + 20, 40, 80, 160, /* 16...19 */ + 40, 80, 160, 320, /* 20...23 */ + 60, 120, 240, 480, /* 24...27 */ + 120, 240, 480, 960 /* 28...31 */ + }; + + DROPUS_ASSERT(config < 32); + return sizes[config]; +} + +static DROPUS_INLINE dropus_uint32 dropus_toc_frame_size_in_pcm_frames(dropus_uint8 toc) +{ + return dropus_toc_config_frame_size_in_pcm_frames(dropus_toc_config(toc)); +} + +static DROPUS_INLINE dropus_uint8 dropus_toc_config_silk_frame_count(dropus_uint8 config) +{ + /* Table 2 in RFC 6716 */ + static dropus_uint8 counts[32] = { + 1, 1, 2, 3, /* 0...3 */ + 1, 1, 2, 3, /* 4...7 */ + 1, 1, 2, 3, /* 8...11 */ + 1, 1, /* 12...13 */ + 1, 1, /* 14...15 */ + 0, 0, 0, 0, /* 16...19 */ /* Here down is CELT-only. */ + 0, 0, 0, 0, /* 20...23 */ + 0, 0, 0, 0, /* 24...27 */ + 0, 0, 0, 0 /* 28...31 */ + }; + + DROPUS_ASSERT(config < 32); + return counts[config]; +} + +static DROPUS_INLINE dropus_uint8 dropus_toc_silk_frame_count(dropus_uint8 toc) +{ + return dropus_toc_config_silk_frame_count(dropus_toc_config(toc)); +} + +static DROPUS_INLINE dropus_int32 dropus_Q13(dropus_uint16 index) +{ + /* Table 7 in RFC 6716 */ + static dropus_int32 Q13[16] = { + -13732, -10050, -8266, -7526, + -6500, -5000, -2950, -820, + 820, 2950, 5000, 6500, + 7526, 8266, 10050, 13732 + }; + + DROPUS_ASSERT(index < DROPUS_COUNTOF(Q13)); + return Q13[index]; +} + + +typedef struct +{ + const dropus_uint8* pData; + dropus_uint16 dataSize; + dropus_uint16 readPointer; + dropus_uint8 b0; + dropus_uint32 rng; /* RFC 6716 - Section 4.1 - Both val and rng are 32-bit unsigned integer values. */ + dropus_uint32 val; /* ^^^ */ +} dropus_range_decoder; + +static DROPUS_INLINE void dropus_range_decoder_normalize(dropus_range_decoder* pRangeDecoder) +{ + dropus_uint8 sym; + dropus_uint8 b1; + + DROPUS_ASSERT(pRangeDecoder != NULL); + + while (pRangeDecoder->rng <= 0x800000) { + pRangeDecoder->rng = (pRangeDecoder->rng << 8); /* RFC 6716 - Section 4.1.2.1 - First, it sets rng to (rng<<8). */ + + /* RFC 6716 - Section 4.1.2.1 - If no more input bytes remain, it uses zero bits instead. */ + if (pRangeDecoder->dataSize > pRangeDecoder->readPointer) { + b1 = pRangeDecoder->pData[pRangeDecoder->readPointer++]; + } else { + b1 = 0; + } + + /* RFC 6716 - Section 4.1.2.1 - ... using the leftover bit buffered from the previous byte as the high bit and the top 7 bits of the byte just read as the other 7 bits of sym. */ + sym = ((pRangeDecoder->b0 & 0x01) << 7) | (b1 >> 7); + + /* RFC 6716 - Section 4.1.2.1 - The remaining bit in the byte just read is buffered for use in the next iteration. */ + pRangeDecoder->b0 = (b1 & 0x01); + + /* val */ + pRangeDecoder->val = ((pRangeDecoder->val << 8) + (255 - sym)) & 0x7FFFFFFF; + } + + DROPUS_ASSERT(pRangeDecoder->rng > 0x800000); +} + +static DROPUS_INLINE void dropus_range_decoder_init(const dropus_uint8* pData, dropus_uint16 dataSize, dropus_range_decoder* pRangeDecoder) +{ + DROPUS_ASSERT(pRangeDecoder != NULL); + + pRangeDecoder->pData = pData; + pRangeDecoder->dataSize = dataSize; + pRangeDecoder->readPointer = 0; + + pRangeDecoder->b0 = 0; /* RFC 6716 - Section 4.1.1 - Let b0 be an 8-bit unsigned integer containing first input byte (or containing zero if there are no bytes in this Opus frame). */ + if (dataSize > 0) { + pRangeDecoder->b0 = pData[pRangeDecoder->readPointer++]; + } + + pRangeDecoder->rng = 128; /* RFC 6716 - Section 4.1.1 - The decoder initializes rng to 128 ... */ + pRangeDecoder->val = 127 - (pRangeDecoder->b0 >> 1); /* ... and initializes val to (127 - (b0>>1)) ...*/ + + /* + It saves the remaining bit, (b0&1), for use in the renormalization + procedure described in Section 4.1.2.1, which the decoder invokes + immediately after initialization to read additional bits and + establish the invariant that rng > 2**23. + */ + dropus_range_decoder_normalize(pRangeDecoder); +} + +static DROPUS_INLINE dropus_uint16 dropus_range_decoder_fs(dropus_range_decoder* pRangeDecoder, dropus_uint16 ft) +{ + /* Implements RFC 6716 - Section 4.1.2 (first step) */ + + DROPUS_ASSERT(pRangeDecoder != NULL); + /*DROPUS_ASSERT(ft <= 65535);*/ /* RFC 6716 - Section 4.1 */ /* Always true due to range limit of `ft`. */ + + return (dropus_uint16)(ft - DROPUS_MIN((pRangeDecoder->val / (pRangeDecoder->rng/ft)) + 1, ft)); +} + +static DROPUS_INLINE dropus_uint16 dropus_range_decoder_k(dropus_uint16* f, dropus_uint16 n, dropus_uint16 fs, dropus_uint16* flOut, dropus_uint16* fhOut) +{ + dropus_uint8 i; + dropus_uint16 k = 0; + dropus_uint16 fl = 0; + dropus_uint16 fh = 0; + for (i = 0; i < n; ++i) { + fh = fl + f[i]; + if (fl <= fs && fs < fh) { + k = i; + break; + } + + fl += f[i]; + } + + *flOut = fl; + *fhOut = fh; + return k; +} + +static DROPUS_INLINE dropus_uint16 dropus_range_decoder_update(dropus_range_decoder* pRangeDecoder, dropus_uint16* f, dropus_uint16 n, dropus_uint16 ft, dropus_uint16 fs) +{ + /* Implements RFC 6716 - Section 4.1.2 (second step) */ + dropus_uint16 k; + dropus_uint16 fl; + dropus_uint16 fh; + + DROPUS_ASSERT(pRangeDecoder != NULL); + DROPUS_ASSERT(f != NULL); + DROPUS_ASSERT(n > 0); + DROPUS_ASSERT(ft > 0); + + k = dropus_range_decoder_k(f, n, fs, &fl, &fh); + + /*DROPUS_ASSERT(0 <= fl);*/ /* RFC 6716 - Section 4.1 */ /* Always true due to `fl` being unsigned. */ + DROPUS_ASSERT(fl < fh); /* RFC 6716 - Section 4.1 */ + DROPUS_ASSERT(fh <= ft); /* RFC 6716 - Section 4.1 */ + /*DROPUS_ASSERT(ft <= 65535);*/ /* RFC 6716 - Section 4.1 */ /* Always true due to range limit of `ft`. */ + + /* val */ + pRangeDecoder->val = pRangeDecoder->val - (pRangeDecoder->rng/ft) * (ft - fh); + + /* rng */ + if (fl > 0) { /* RFC 6716 - Section 4.1.2 - If fl[k] is greater than zero, then the decoder updates rng using... */ + pRangeDecoder->rng = (pRangeDecoder->rng/ft) * (fh - fl); + } else { /* RFC 6716 - Section 4.1.2 - Otherwise, it updates rng using... */ + pRangeDecoder->rng = pRangeDecoder->rng - (pRangeDecoder->rng/ft) * (ft - fh); + } + + /* + RFC 6716 - Section 4.1.2 + + After the updates, implemented by ec_dec_update() (entdec.c), the + decoder normalizes the range using the procedure in the next section, + and returns the index k. + */ + dropus_range_decoder_normalize(pRangeDecoder); + + return k; +} + +static DROPUS_INLINE dropus_uint16 dropus_range_decoder_decode(dropus_range_decoder* pRangeDecoder, dropus_uint16* f, dropus_uint16 n, dropus_uint16 ft) +{ + dropus_uint16 fs; + + DROPUS_ASSERT(pRangeDecoder != NULL); + DROPUS_ASSERT(f != NULL); + DROPUS_ASSERT(n > 0); + DROPUS_ASSERT(ft > 0); + + /* Step 1 from RFC 6716 - Section 4.1.2. */ + fs = dropus_range_decoder_fs(pRangeDecoder, ft); + + /* Step 2 from RFC 6716 - Section 4.1.2. */ + return dropus_range_decoder_update(pRangeDecoder, f, n, ft, fs); +} + + +DROPUS_API dropus_result dropus_stream_init(dropus_stream* pOpusStream) +{ + if (pOpusStream == NULL) { + return DROPUS_INVALID_ARGS; + } + + DROPUS_ZERO_OBJECT(pOpusStream); + + return DROPUS_SUCCESS; +} + + +#define DROPUS_MAX_SILK_FRAME_SIZE_IN_PCM_FRAMES 20*16 /* 20 = 20ms SILK frame length. 16 = Wideband sample rate. */ + + +typedef enum +{ + dropus_silk_frame_regularity_regular, + dropus_silk_frame_regularity_lbrr, +} dropus_silk_frame_regularity; + +typedef struct +{ + union + { + dropus_int16 s16[DROPUS_MAX_SILK_FRAME_SIZE_IN_PCM_FRAMES]; + float f32[DROPUS_MAX_SILK_FRAME_SIZE_IN_PCM_FRAMES]; + } pcm; +} dropus_silk_frame; + +static dropus_result dropus_stream_decode_silk_frame(dropus_stream* pOpusStream, dropus_range_decoder* pRD, dropus_silk_frame_regularity regularity, dropus_uint8 iSILKFrame, dropus_uint8 iChannel, dropus_uint8* pFlagsVAD, dropus_uint8* pFlagsLBRR, dropus_silk_frame* pSILKFrame) +{ + dropus_uint8 channels; + dropus_uint32 w0_Q13 = 0; + dropus_uint32 w1_Q13 = 0; + dropus_uint8 midOnlyFlag = 0; + + channels = dropus_toc_s(pOpusStream->packet.toc) + 1; + + /* + RFC 6716 - Section 4.2.7.1 + + ... these weights are coded if and only if + - This is a stereo Opus frame (Section 3.1), and + - The current SILK frame corresponds to the mid channel. + */ + if (channels == 2 && iChannel == 0) { + dropus_uint16 f_Stage1[] = {7, 2, 1, 1, 1, 10, 24, 8, 1, 1, 3, 23, 92, 23, 3, 1, 1, 8, 24, 10, 1, 1, 1, 2, 7}, ft_Stage1 = 256; + dropus_uint16 f_Stage2[] = {85, 86, 85}, ft_Stage2 = 256; + dropus_uint16 f_Stage3[] = {51, 51, 52, 51, 51}, ft_Stage3 = 256; + dropus_uint16 n; + dropus_uint16 i0, i1, i2, i3; + dropus_uint16 wi0, wi1; + + n = dropus_range_decoder_decode(pRD, f_Stage1, DROPUS_COUNTOF(f_Stage1), ft_Stage1); + i0 = dropus_range_decoder_decode(pRD, f_Stage2, DROPUS_COUNTOF(f_Stage2), ft_Stage2); + i1 = dropus_range_decoder_decode(pRD, f_Stage3, DROPUS_COUNTOF(f_Stage3), ft_Stage3); + i2 = dropus_range_decoder_decode(pRD, f_Stage2, DROPUS_COUNTOF(f_Stage2), ft_Stage2); + i3 = dropus_range_decoder_decode(pRD, f_Stage3, DROPUS_COUNTOF(f_Stage3), ft_Stage3); + + wi0 = i0 + 3 * (n / 5); + wi1 = i2 + 3 * (n % 5); + + /* Note that w0_Q13 depends on w1_Q13 so must be calculated afterwards. */ + w1_Q13 = dropus_Q13(wi1) + (((dropus_Q13((dropus_uint16)(wi1 + 1)) - dropus_Q13(wi1)) * 6554) >> 16) * ((2 * i3) + 1); + w0_Q13 = dropus_Q13(wi0) + (((dropus_Q13((dropus_uint16)(wi0 + 1)) - dropus_Q13(wi0)) * 6554) >> 16) * ((2 * i1) + 1) - w1_Q13; + + /* RFC 6716 - Section 4.2.7.2 - Mid-Only Flag */ + { + dropus_uint16 f_MOF[] = {192, 64}, ft_MOF = 256; + midOnlyFlag = (dropus_uint8)dropus_range_decoder_decode(pRD, f_MOF, DROPUS_COUNTOF(f_MOF), ft_MOF); + } + } +} + +static dropus_result dropus_stream_decode_silk_frame__mono(dropus_stream* pOpusStream, dropus_range_decoder* pRD, dropus_silk_frame_regularity regularity, dropus_uint8 iSILKFrame, dropus_uint8* pFlagsVAD, dropus_uint8* pFlagsLBRR, dropus_silk_frame* pSILKFrames) +{ + +} + +static dropus_result dropus_stream_decode_silk_frame__stereo(dropus_stream* pOpusStream, dropus_range_decoder* pRD, dropus_silk_frame_regularity regularity, dropus_uint8 iSILKFrame, dropus_uint8* pFlagsVAD, dropus_uint8* pFlagsLBRR, dropus_silk_frame* pSILKFrames) +{ + dropus_uint8 iChannel; + dropus_uint16 f_Stage1[] = {7, 2, 1, 1, 1, 10, 24, 8, 1, 1, 3, 23, 92, 23, 3, 1, 1, 8, 24, 10, 1, 1, 1, 2, 7}, ft_Stage1 = 256; + dropus_uint16 f_Stage2[] = {85, 86, 85}, ft_Stage2 = 256; + dropus_uint16 f_Stage3[] = {51, 51, 52, 51, 51}, ft_Stage3 = 256; + dropus_uint16 n; + dropus_uint16 i0, i1, i2, i3; + dropus_uint16 wi0, wi1; + + /* + RFC 6716 - Section 4.2.7.1 + + ... these weights are coded if and only if + - This is a stereo Opus frame (Section 3.1), and + - The current SILK frame corresponds to the mid channel. + */ + + for (iChannel = 0; iChannel < 2; iChannel += 1) { + /* Don't include LBRR frames if we don't have one. */ + if (regularity == dropus_silk_frame_regularity_lbrr && (pFlagsLBRR[iChannel] & (1 << iSILKFrame)) == 0) { + continue; + } + + /* The mid channel is always the first one in a SILK frame. */ + if (iChannel == 0) { + n = dropus_range_decoder_decode(pRD, f_Stage1, DROPUS_COUNTOF(f_Stage1), ft_Stage1); + i0 = dropus_range_decoder_decode(pRD, f_Stage2, DROPUS_COUNTOF(f_Stage2), ft_Stage2); + i1 = dropus_range_decoder_decode(pRD, f_Stage3, DROPUS_COUNTOF(f_Stage3), ft_Stage3); + i2 = dropus_range_decoder_decode(pRD, f_Stage2, DROPUS_COUNTOF(f_Stage2), ft_Stage2); + i3 = dropus_range_decoder_decode(pRD, f_Stage3, DROPUS_COUNTOF(f_Stage3), ft_Stage3); + + wi0 = i0 + 3 * (n / 5); + wi1 = i2 + 3 * (n % 5); + + #if 0 + /* Note that w0_Q13 depends on w1_Q13 so must be calculated afterwards. */ + w1_Q13 = dropus_Q13(wi1) + (((dropus_Q13((dropus_uint16)(wi1 + 1)) - dropus_Q13(wi1)) * 6554) >> 16) * ((2 * i3) + 1); + w0_Q13 = dropus_Q13(wi0) + (((dropus_Q13((dropus_uint16)(wi0 + 1)) - dropus_Q13(wi0)) * 6554) >> 16) * ((2 * i1) + 1) - w1_Q13; + + /* RFC 6716 - Section 4.2.7.2 - Mid-Only Flag */ + { + dropus_uint16 f_MOF[] = {192, 64}, ft_MOF = 256; + midOnlyFlag = (dropus_uint8)dropus_range_decoder_decode(pRD, f_MOF, DROPUS_COUNTOF(f_MOF), ft_MOF); + } + #endif + } + } +} + +static dropus_result dropus_stream_decode_silk_frames__stereo(dropus_stream* pOpusStream, dropus_range_decoder* pRD, dropus_silk_frame_regularity regularity, dropus_uint8* pFlagsVAD, dropus_uint8* pFlagsLBRR, dropus_silk_frame* pSILKFrames) +{ + dropus_result result; + dropus_uint8 silkFrameCount; + dropus_uint8 iSILKFrame; + + silkFrameCount = dropus_toc_silk_frame_count(pOpusStream->packet.toc); + DROPUS_ASSERT(silkFrameCount > 0); + + for (iSILKFrame = 0; iSILKFrame < silkFrameCount; iSILKFrame += 1) { + result = dropus_stream_decode_silk_frame__stereo(pOpusStream, pRD, regularity, iSILKFrame, pFlagsVAD, pFlagsLBRR, pSILKFrames); + if (result != DROPUS_SUCCESS) { + return result; + } + } + + return DROPUS_SUCCESS; +} + + +static dropus_result dropus_stream_decode_frame__silk(dropus_stream* pOpusStream, dropus_stream_frame* pOpusFrame, const dropus_uint8* pData, size_t dataSize) +{ + dropus_result result; + dropus_range_decoder rd; + + DROPUS_ASSERT(pOpusStream != NULL); + DROPUS_ASSERT(pOpusFrame != NULL); + DROPUS_ASSERT(pData != NULL); + DROPUS_ASSERT(dataSize <= DROPUS_MAX_FRAME_SIZE_IN_BYTES); + + pOpusFrame->sizeInBytes = (dropus_uint16)dataSize; /* Safe cast because dataSize <= DROPUS_MAX_FRAME_SIZE_IN_BYTES <= 1275. */ + + /* Everything is fed through the range decoder. */ + dropus_range_decoder_init(pData, pOpusFrame->sizeInBytes, &rd); + { + dropus_uint16 f_Flags[2] = {1, 1}, ft_Flags = 2; + + dropus_uint8 iChannel; + dropus_uint8 channels; + dropus_uint8 iFrameSILK; + dropus_uint8 frameCountSILK; + dropus_uint16 k; + dropus_uint8 flagsVAD[2] = {0, 0}; /* One for each channel. */ + dropus_uint8 flagsLBRR[2] = {0, 0}; /* One for each channel. */ + dropus_silk_frame silkFrames_LBRR[2][3]; + dropus_silk_frame silkFrames_Regular[2][3]; + + dropus_uint32 w0_Q13[3] = {0, 0, 0}; /* One for each SILK frame (max 3). */ + dropus_uint32 w1_Q13[3] = {0, 0, 0}; /* One for each SILK frame (max 3). */ + dropus_uint8 midOnlyFlag[3] = {0, 0, 0}; /* One for each SILK frame (max 3). */ + + frameCountSILK = dropus_toc_silk_frame_count(pOpusStream->packet.toc); /* SILK frame count. Between 1 and 3. Either 1 10ms SILK frame, or between 1 and 3 20ms frames (20ms, 40ms, 60ms). */ + if (frameCountSILK == 0) { + return DROPUS_BAD_DATA; + } + + channels = dropus_toc_s(pOpusStream->packet.toc) + 1; + + /* Header flags. */ + for (iChannel = 0; iChannel < channels; ++iChannel) { + for (iFrameSILK = 0; iFrameSILK < frameCountSILK; ++iFrameSILK) { + k = dropus_range_decoder_decode(&rd, f_Flags, DROPUS_COUNTOF(f_Flags), ft_Flags); + DROPUS_ASSERT(k <= 1); + flagsVAD[iChannel] |= (k << iFrameSILK); + } + + k = dropus_range_decoder_decode(&rd, f_Flags, DROPUS_COUNTOF(f_Flags), ft_Flags); + DROPUS_ASSERT(k <= 1); + flagsLBRR[iChannel] = (dropus_uint8)k; + } + + /* RFC 6716 - 4.2.4. Per-Frame LBRR Flags. */ + if (frameCountSILK > 1) { + dropus_uint8 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + dropus_uint16 f_40[4] = {0, 53, 53, 150}, ft_40 = 256; + dropus_uint16 f_60[8] = {0, 41, 20, 29, 41, 15, 28, 82}, ft_60 = 256; + if (flagsLBRR[iChannel] != 0) { + if (frameCountSILK == 2) { + k = dropus_range_decoder_decode(&rd, f_40, DROPUS_COUNTOF(f_40), ft_40); + DROPUS_ASSERT(k <= 0b11); + } else { + DROPUS_ASSERT(frameCountSILK == 3); + k = dropus_range_decoder_decode(&rd, f_60, DROPUS_COUNTOF(f_60), ft_60); + DROPUS_ASSERT(k <= 0b111); + } + + flagsLBRR[iChannel] = (dropus_uint8)k; + } + } + } + + /* LBRR frames. Only do this if the relevant flag is set. */ + for (iFrameSILK = 0; iFrameSILK < frameCountSILK; ++iFrameSILK) { + if (channels == 1) { + /*result = dropus_stream_decode_silk_frames__mono(pOpusStream, &rd, );*/ + } else { + result = dropus_stream_decode_silk_frames__stereo(pOpusStream, &rd, dropus_silk_frame_regularity_lbrr, flagsVAD, flagsLBRR, silkFrames_LBRR); + } + + for (iChannel = 0; iChannel < channels; ++iChannel) { + if ((flagsLBRR[iChannel] & (1 << iFrameSILK)) != 0) { + result = dropus_stream_decode_frame__silk_frame(pOpusStream, &rd, &silkFrames_LBRR[iFrameSILK]); + if (result != DROPUS_SUCCESS) { + return result; + } + } + } + } + + /* TODO: Don't forget to set the previous stereo weights. Don't just blindly set it without first checking the rules in RFC 6716 - Section 4.2.7.1. */ + /* RFC 6716 - Section 4.2.7.1 - These prediction weights are never included in a mono Opus frame, and the previous weights are reset to zeros on any transition from mono to stereo. */ + if (channels == 1) { + pOpusStream->silk.w0_Q13_prev = 0; + pOpusStream->silk.w1_Q13_prev = 0; + } + + + } + + return DROPUS_SUCCESS; +} + +static dropus_result dropus_stream_decode_frame__celt(dropus_stream* pOpusStream, dropus_stream_frame* pOpusFrame, const dropus_uint8* pData, size_t dataSize) +{ + dropus_range_decoder rd; + + DROPUS_ASSERT(pOpusStream != NULL); + DROPUS_ASSERT(pOpusFrame != NULL); + DROPUS_ASSERT(pData != NULL); + DROPUS_ASSERT(dataSize <= DROPUS_MAX_FRAME_SIZE_IN_BYTES); + + pOpusFrame->sizeInBytes = (dropus_uint16)dataSize; /* Safe cast because dataSize <= DROPUS_MAX_FRAME_SIZE_IN_BYTES <= 1275. */ + + /* Everything is fed through the range decoder. */ + dropus_range_decoder_init(pData, pOpusFrame->sizeInBytes, &rd); + { + + } + + return DROPUS_SUCCESS; +} + +static dropus_result dropus_stream_decode_frame__hybrid(dropus_stream* pOpusStream, dropus_stream_frame* pOpusFrame, const dropus_uint8* pData, size_t dataSize) +{ + dropus_range_decoder rd; + + DROPUS_ASSERT(pOpusStream != NULL); + DROPUS_ASSERT(pOpusFrame != NULL); + DROPUS_ASSERT(pData != NULL); + DROPUS_ASSERT(dataSize <= DROPUS_MAX_FRAME_SIZE_IN_BYTES); + + pOpusFrame->sizeInBytes = (dropus_uint16)dataSize; /* Safe cast because dataSize <= DROPUS_MAX_FRAME_SIZE_IN_BYTES <= 1275. */ + + /* Everything is fed through the range decoder. */ + dropus_range_decoder_init(pData, pOpusFrame->sizeInBytes, &rd); + { + + } + + return DROPUS_SUCCESS; +} + +DROPUS_API dropus_result dropus_stream_decode_packet(dropus_stream* pOpusStream, const void* pData, size_t dataSize) +{ + const dropus_uint8* pRunningData8 = (const dropus_uint8*)pData; + dropus_uint8 toc; /* Table of Contents byte. */ + dropus_uint8 opusFrameCount; + dropus_uint16 opusFrameSizes[DROPUS_MAX_OPUS_FRAMES_PER_PACKET]; + dropus_uint32 code; /* Determines the structure of the Opus packet. */ + + if (pOpusStream == NULL || pData == NULL) { + return DROPUS_INVALID_ARGS; + } + + DROPUS_ASSERT(DROPUS_MAX_PACKET_SIZE_IN_BYTES < 65536); + if (dataSize > DROPUS_MAX_PACKET_SIZE_IN_BYTES) { + return DROPUS_BAD_DATA; + } + + /* RFC 6716 - Section 3.4 [R1] Packets are at least one byte. */ + if (dataSize < 1) { + return DROPUS_BAD_DATA; + } + + /* The TOC byte specifies the structure of the packet. */ + toc = pRunningData8[0]; + pRunningData8 += 1; + + /* + We need to look at the code to know the frames making up the packet are structured. We will do a pre-processing step to + extract basic information about each frame in the packet. + */ + code = dropus_toc_c(toc); + switch (code) { + case 0: /* RFC 6716 - Section 3.2.2. Code 0: One Frame in the Packet */ + { + dropus_uint16 opusFrameSize = (dropus_uint16)(dataSize-1); + + /* RFC 6716 - Section 3.4 [R2] No implicit frame length is larger than 1275 bytes. */ + if (opusFrameSize > DROPUS_MAX_FRAME_SIZE_IN_BYTES) { + return DROPUS_BAD_DATA; + } + + opusFrameCount = 1; + opusFrameSizes[0] = opusFrameSize; + } break; + + case 1: /* RFC 6716 - Section 3.2.3. Code 1: Two Frames in the Packet, Each with Equal Compressed Size */ + { + dropus_uint16 opusFrameSize; + + /* RFC 6716 - Section 3.4 [R3] Code 1 packets have an odd total length, N, so that (N-1)/2 is an integer. */ + if ((dataSize & 1) == 0) { + return DROPUS_BAD_DATA; + } + + opusFrameSize = (dropus_uint16)(dataSize-1)/2; + + /* RFC 6716 - Section 3.4 [R2] No implicit frame length is larger than 1275 bytes. */ + if (opusFrameSize > DROPUS_MAX_FRAME_SIZE_IN_BYTES) { + return DROPUS_BAD_DATA; + } + + opusFrameCount = 2; + opusFrameSizes[0] = opusFrameSize; + opusFrameSizes[1] = opusFrameSize; + } break; + + case 2: /* RFC 6716 - Section 3.2.4. Code 2: Two Frames in the Packet, with Different Compressed Sizes */ + { + dropus_uint8 byte0; + dropus_uint8 byte1; + dropus_uint16 opusFrameSize0 = 0; + dropus_uint16 opusFrameSize1 = 0; + dropus_uint16 headerByteCount; + + /* RFC 6716 - Section 3.4 [R4] Code 2 packets have enough bytes after the TOC for a valid frame length, and that length is no larger than the number of bytes remaining in the packet. */ + if (dataSize < 2) { + return DROPUS_BAD_DATA; + } + + /* RFC 6716 - Section 3.2.1. Frame Length Coding */ + byte0 = pRunningData8[0]; pRunningData8 += 1; + if (byte0 == 0) { + /* + Section 3.2.1 of RFC 6716 says the following: + + "Any Opus frame in any mode MAY have a length of 0. + + This implies to me that this is a valid case. dr_opus is going to handle this by setting the PCM frame count to 0 for this packet. + */ + opusFrameSize0 = 0; + opusFrameSize1 = 0; + } else { + if (byte0 >= 1 && byte0 <= 251) { + opusFrameSize0 = byte0; + } + if (byte0 >= 252 /*&& byte0 <= 255*/) { + /* RFC 6716 - Section 3.4 [R4] Code 2 packets have enough bytes after the TOC for a valid frame length, and that length is no larger than the number of bytes remaining in the packet. */ + if (dataSize < 3) { + return DROPUS_BAD_DATA; + } + + byte1 = pRunningData8[0]; pRunningData8 += 1; + opusFrameSize0 = (byte1*4) + byte0; + } + + headerByteCount = (dropus_uint16)(pRunningData8 - (const dropus_uint8*)pData); /* This is a safe case because the maximum difference will be 3. */ + + /* RFC 6716 - Section 3.4 [R2] No implicit frame length is larger than 1275 bytes. */ + if (opusFrameSize0 > DROPUS_MAX_FRAME_SIZE_IN_BYTES) { + return DROPUS_BAD_DATA; + } + + /* RFC 6716 - Section 3.4 [R4] Code 2 packets have enough bytes after the TOC for a valid frame length, and that length is no larger than the number of bytes remaining in the packet. */ + if ((dataSize-headerByteCount) > dataSize) { + return DROPUS_BAD_DATA; + } + + opusFrameSize1 = (dropus_uint16)(dataSize-headerByteCount-opusFrameSize0); /* Safe cast because dataSize is guaranteed to be < 65536 at this point since it was checked at the top of this function. */ + + /* RFC 6716 - Section 3.4 [R2] No implicit frame length is larger than 1275 bytes. */ + if (opusFrameSize1 > DROPUS_MAX_FRAME_SIZE_IN_BYTES) { + return DROPUS_BAD_DATA; + } + + /* RFC 6716 - Section 3.4 [R4] Code 2 packets have enough bytes after the TOC for a valid frame length, and that length is no larger than the number of bytes remaining in the packet. */ + if ((size_t)(headerByteCount + opusFrameSize0 + opusFrameSize1) > dataSize) { + return DROPUS_BAD_DATA; + } + } + + opusFrameCount = 2; + opusFrameSizes[0] = opusFrameSize0; + opusFrameSizes[1] = opusFrameSize1; + } break; + + case 3: /* RFC 6716 - Section 3.2.5. Code 3: A Signaled Number of Frames in the Packet */ + { + dropus_uint16 iFrame; + dropus_uint8 frameCountByte; + dropus_uint8 v; /* Is VBR? */ + dropus_uint8 p; /* Has padding? */ + dropus_uint8 M; /* Frame count. */ + dropus_uint16 P; /* The size of the padding. Must never be more than dataSize-2. */ + dropus_uint16 R; /* The number of bytes remaining in the packet after subtracting the TOC, frame count byte and padding. */ + dropus_uint32 ms; /* Total length in milliseconds. */ + dropus_uint32 paddingByteCount; /* The number of bytes making up the size of the padding. Only used for validation. */ + + /* + RFC 6716 - Section 3.2.5: + "Code 3 packets MUST have at least 2 bytes [R6,R7]." + */ + if (dataSize < 2) { + return DROPUS_BAD_DATA; + } + + frameCountByte = pRunningData8[0]; pRunningData8 += 1; + v = (frameCountByte & 0x80) >> 7; + p = (frameCountByte & 0x40) >> 6; + M = (frameCountByte & 0x3F); + + /* RFC 6716 - Section 3.4 [R5] Code 3 packets contain at least one frame, but no more than 120 ms of audio total. */ + ms = (M * dropus_toc_frame_size_in_pcm_frames(toc)) / dropus_toc_sample_rate_ms(toc); + if (M < 1 || ms > 120) { + return DROPUS_BAD_DATA; + } + + /* Sanity check to ensure the frame count is never greather than the maximum allowed. */ + if (M > DROPUS_MAX_OPUS_FRAMES_PER_PACKET) { + return DROPUS_BAD_DATA; + } + + /* Padding bytes. Need to run this in a loop. */ + P = 0; + paddingByteCount = 0; + if (p != 0) { + size_t iPaddingByte; + for (iPaddingByte = 0; iPaddingByte < dataSize-2; ++iPaddingByte) { + dropus_uint8 paddingByte = pRunningData8[0]; pRunningData8 += 1; + P += paddingByte; + paddingByteCount += 1; + + /* A padding byte not equal to 255 signals the last padding byte. */ + if (paddingByte == 255) { + /* There must be an additional byte available in this case. */ + if (iPaddingByte+1 >= dataSize-2) { + return DROPUS_BAD_DATA; + } + } else { + break; /* Reached the end of the padding bytes. */ + } + } + } + + /* Safety check. */ + if (P > dataSize-2) { + return DROPUS_BAD_DATA; + } + + /* R = bytes remaining. */ + R = (dropus_uint16)(dataSize-2-P); + + if (v == 0) { + /* CBR */ + dropus_uint16 frameSize = R/M; + + /* RFC 6716 - Section 3.4 [R2] No implicit frame length is larger than 1275 bytes. */ + if (frameSize > DROPUS_MAX_FRAME_SIZE_IN_BYTES) { + return DROPUS_BAD_DATA; + } + + /* RFC 6716 - Section 3.4 [R6] ... */ + if (dataSize < 2) { /* ... The length of a CBR code 3 packet, N, is at least two bytes ... */ + return DROPUS_BAD_DATA; + } + if (paddingByteCount+P > dataSize-2) { /* ... the number of bytes added to indicate the padding size plus the trailing padding bytes themselves, P, is no more than N-2 ... */ + return DROPUS_BAD_DATA; + } + if (frameSize*M != (dropus_uint16)(dataSize-2-P)) { /* ... the frame count, M, satisfies the constraint that (N-2-P) is a non-negative integer multiple of M ... */ + return DROPUS_BAD_DATA; + } + + opusFrameCount = M; + for (iFrame = 0; iFrame < opusFrameCount; ++iFrame) { + opusFrameSizes[iFrame] = frameSize; + } + } else { + /* VBR */ + dropus_uint16 totalFrameSizeExceptLast = 0; /* Used later for checking [R7]. */ + dropus_uintptr headerSizeInBytes; /* For validation and deriving the size of the last frame. */ + + opusFrameCount = M; + for (iFrame = 0; iFrame < opusFrameCount-1; ++iFrame) { + dropus_uint8 byte0; + dropus_uint8 byte1; + + if (pRunningData8 >= ((const dropus_uint8*)pData) + dataSize) { + return DROPUS_BAD_DATA; /* Ran out of data in the packet. Implicitly handles part of [R7]. */ + } + + byte0 = pRunningData8[0]; pRunningData8 += 1; + if (byte0 == 0) { + opusFrameSizes[iFrame] = 0; + } else { + if (byte0 >= 1 && byte0 <= 251) { + opusFrameSizes[iFrame] = byte0; + } + if (byte0 >= 252 /*&& byte0 <= 255*/) { + if (pRunningData8 >= ((const dropus_uint8*)pData) + dataSize) { + return DROPUS_BAD_DATA; /* Ran out of data in the packet. Implicitly handles part of [R7]. */ + } + + byte1 = pRunningData8[0]; pRunningData8 += 1; + opusFrameSizes[iFrame] = (byte1*4) + byte0; + + /* RFC 6716 - Section 3.4 [R2] No implicit frame length is larger than 1275 bytes. */ + if (opusFrameSizes[iFrame] > DROPUS_MAX_FRAME_SIZE_IN_BYTES) { + return DROPUS_BAD_DATA; + } + } + } + + totalFrameSizeExceptLast += opusFrameSizes[iFrame]; + } + + headerSizeInBytes = (dropus_uintptr)(pRunningData8 - (const dropus_uint8*)pData); + + /* + RFC 6716 - Section 3.4 [R6] + VBR code 3 packets are large enough to contain all the header + bytes (TOC byte, frame count byte, any padding length bytes, + and any frame length bytes), plus the length of the first M-1 + frames, plus any trailing padding bytes. + */ + if ((headerSizeInBytes + totalFrameSizeExceptLast + P) > dataSize) { + return DROPUS_BAD_DATA; + } + + /* The size of the last frame is derived. */ + opusFrameSizes[opusFrameCount-1] = (dropus_uint16)(dataSize - headerSizeInBytes - totalFrameSizeExceptLast - P); /* Safe cast thanks to the myriad of validation done beforehand. */ + + /* RFC 6716 - Section 3.4 [R2] No implicit frame length is larger than 1275 bytes. */ + if (opusFrameSizes[opusFrameCount-1] > DROPUS_MAX_FRAME_SIZE_IN_BYTES) { + return DROPUS_BAD_DATA; + } + } + } break; + + /* Will never hit this, but need the default to keep some compilers quiet. */ + default: return DROPUS_BAD_DATA; + } + + pOpusStream->packet.toc = toc; + + /* Make sure the frame count is within a valid range. */ + if (opusFrameCount > DROPUS_MAX_OPUS_FRAMES_PER_PACKET) { + return DROPUS_BAD_DATA; + } + + /* At this point, pRunningData8 should be sitting on the first byte of the first frame in the packet. */ + + /* Decoding. */ + { + dropus_result result; + dropus_uint16 iFrame; + dropus_mode mode; + + mode = dropus_toc_mode(pOpusStream->packet.toc); + + /* Would be slightly more optimal to pull the mode checks out of the loop, but it's more compact, readable and maintainable to do it inside. We can let the compiler decide what to do with it. */ + for (iFrame = 0; iFrame < opusFrameCount; ++iFrame) { + /* */ if (mode == dropus_mode_silk) { + result = dropus_stream_decode_frame__silk(pOpusStream, &pOpusStream->packet.frames[iFrame], pRunningData8, opusFrameSizes[iFrame]); + } else if (mode == dropus_mode_celt) { + result = dropus_stream_decode_frame__celt(pOpusStream, &pOpusStream->packet.frames[iFrame], pRunningData8, opusFrameSizes[iFrame]); + } else { + DROPUS_ASSERT(mode == dropus_mode_hybrid); + result = dropus_stream_decode_frame__hybrid(pOpusStream, &pOpusStream->packet.frames[iFrame], pRunningData8, opusFrameSizes[iFrame]); + } + + if (result != DROPUS_SUCCESS) { + return result; /* Probably a corrupt frame. */ + } + + pRunningData8 += opusFrameSizes[iFrame]; + } + } + + return DROPUS_SUCCESS; +} + + + +/*********************************************************************************************************************************************************** + +High-Level Opus Decoding API + +************************************************************************************************************************************************************/ +static void* dropus__malloc_default(size_t sz, void* pUserData) +{ + (void)pUserData; + return DROPUS_MALLOC(sz); +} + +static void* dropus__realloc_default(void* p, size_t sz, void* pUserData) +{ + (void)pUserData; + return DROPUS_REALLOC(p, sz); +} + +static void dropus__free_default(void* p, void* pUserData) +{ + (void)pUserData; + DROPUS_FREE(p); +} + + +static void* dropus__malloc_from_callbacks(size_t sz, const dropus_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } + + if (pAllocationCallbacks->onMalloc != NULL) { + return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); + } + + /* Try using realloc(). */ + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); + } + + return NULL; +} + +static void* dropus__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const dropus_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } + + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); + } + + /* Try emulating realloc() in terms of malloc()/free(). */ + if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { + void* p2; + + p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); + if (p2 == NULL) { + return NULL; + } + + if (p != NULL) { + DROPUS_COPY_MEMORY(p2, p, szOld); + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } + + return p2; + } + + return NULL; +} + +static void dropus__free_from_callbacks(void* p, const dropus_allocation_callbacks* pAllocationCallbacks) +{ + if (p == NULL || pAllocationCallbacks == NULL) { + return; + } + + if (pAllocationCallbacks->onFree != NULL) { + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } +} + + +static dropus_allocation_callbacks dropus__copy_allocation_callbacks_or_defaults(const dropus_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + /* Copy. */ + return *pAllocationCallbacks; + } else { + /* Defaults. */ + dropus_allocation_callbacks allocationCallbacks; + allocationCallbacks.pUserData = NULL; + allocationCallbacks.onMalloc = dropus__malloc_default; + allocationCallbacks.onRealloc = dropus__realloc_default; + allocationCallbacks.onFree = dropus__free_default; + return allocationCallbacks; + } +} + +static dropus_result dropus_init_internal(dropus* pOpus, dropus_read_proc onRead, dropus_seek_proc onSeek, void* pUserData, const dropus_allocation_callbacks* pAllocationCallbacks) +{ + DROPUS_ASSERT(pOpus != NULL); + DROPUS_ASSERT(onRead != NULL); + + /* Must always have an onRead callback. */ + if (onRead == NULL) { + return DROPUS_INVALID_ARGS; + } + + pOpus->onRead = onRead; + pOpus->onSeek = onSeek; + pOpus->pUserData = pUserData; + pOpus->allocationCallbacks = dropus__copy_allocation_callbacks_or_defaults(pAllocationCallbacks); + + /* Basic validation for allocation callbacks. free() and at least one of malloc() or realloc() must be set. */ + if (pOpus->allocationCallbacks.onFree == NULL || (pOpus->allocationCallbacks.onMalloc == NULL && pOpus->allocationCallbacks.onRealloc == NULL)) { + return DROPUS_INVALID_ARGS; + } + + /* TODO: Implement me. */ + + return DROPUS_SUCCESS; +} + +DROPUS_API dropus_result dropus_init(dropus* pOpus, dropus_read_proc onRead, dropus_seek_proc onSeek, void* pUserData, const dropus_allocation_callbacks* pAllocationCallbacks) +{ + if (pOpus == NULL) { + return DROPUS_INVALID_ARGS; + } + + DROPUS_ZERO_OBJECT(pOpus); + + return dropus_init_internal(pOpus, onRead, onSeek, pUserData, pAllocationCallbacks); +} + +#ifndef DR_OPUS_NO_STDIO +#include +static dropus_result dropus_result_from_errno(int e) +{ + switch (e) + { + case 0: return DROPUS_SUCCESS; + #ifdef EPERM + case EPERM: return DROPUS_INVALID_OPERATION; + #endif + #ifdef ENOENT + case ENOENT: return DROPUS_DOES_NOT_EXIST; + #endif + #ifdef ESRCH + case ESRCH: return DROPUS_DOES_NOT_EXIST; + #endif + #ifdef EINTR + case EINTR: return DROPUS_INTERRUPT; + #endif + #ifdef EIO + case EIO: return DROPUS_IO_ERROR; + #endif + #ifdef ENXIO + case ENXIO: return DROPUS_DOES_NOT_EXIST; + #endif + #ifdef E2BIG + case E2BIG: return DROPUS_INVALID_ARGS; + #endif + #ifdef ENOEXEC + case ENOEXEC: return DROPUS_INVALID_FILE; + #endif + #ifdef EBADF + case EBADF: return DROPUS_INVALID_FILE; + #endif + #ifdef ECHILD + case ECHILD: return DROPUS_ERROR; + #endif + #ifdef EAGAIN + case EAGAIN: return DROPUS_UNAVAILABLE; + #endif + #ifdef ENOMEM + case ENOMEM: return DROPUS_OUT_OF_MEMORY; + #endif + #ifdef EACCES + case EACCES: return DROPUS_ACCESS_DENIED; + #endif + #ifdef EFAULT + case EFAULT: return DROPUS_BAD_ADDRESS; + #endif + #ifdef ENOTBLK + case ENOTBLK: return DROPUS_ERROR; + #endif + #ifdef EBUSY + case EBUSY: return DROPUS_BUSY; + #endif + #ifdef EEXIST + case EEXIST: return DROPUS_ALREADY_EXISTS; + #endif + #ifdef EXDEV + case EXDEV: return DROPUS_ERROR; + #endif + #ifdef ENODEV + case ENODEV: return DROPUS_DOES_NOT_EXIST; + #endif + #ifdef ENOTDIR + case ENOTDIR: return DROPUS_NOT_DIRECTORY; + #endif + #ifdef EISDIR + case EISDIR: return DROPUS_IS_DIRECTORY; + #endif + #ifdef EINVAL + case EINVAL: return DROPUS_INVALID_ARGS; + #endif + #ifdef ENFILE + case ENFILE: return DROPUS_TOO_MANY_OPEN_FILES; + #endif + #ifdef EMFILE + case EMFILE: return DROPUS_TOO_MANY_OPEN_FILES; + #endif + #ifdef ENOTTY + case ENOTTY: return DROPUS_INVALID_OPERATION; + #endif + #ifdef ETXTBSY + case ETXTBSY: return DROPUS_BUSY; + #endif + #ifdef EFBIG + case EFBIG: return DROPUS_TOO_BIG; + #endif + #ifdef ENOSPC + case ENOSPC: return DROPUS_NO_SPACE; + #endif + #ifdef ESPIPE + case ESPIPE: return DROPUS_BAD_SEEK; + #endif + #ifdef EROFS + case EROFS: return DROPUS_ACCESS_DENIED; + #endif + #ifdef EMLINK + case EMLINK: return DROPUS_TOO_MANY_LINKS; + #endif + #ifdef EPIPE + case EPIPE: return DROPUS_BAD_PIPE; + #endif + #ifdef EDOM + case EDOM: return DROPUS_OUT_OF_RANGE; + #endif + #ifdef ERANGE + case ERANGE: return DROPUS_OUT_OF_RANGE; + #endif + #ifdef EDEADLK + case EDEADLK: return DROPUS_DEADLOCK; + #endif + #ifdef ENAMETOOLONG + case ENAMETOOLONG: return DROPUS_PATH_TOO_LONG; + #endif + #ifdef ENOLCK + case ENOLCK: return DROPUS_ERROR; + #endif + #ifdef ENOSYS + case ENOSYS: return DROPUS_NOT_IMPLEMENTED; + #endif + #ifdef ENOTEMPTY + case ENOTEMPTY: return DROPUS_DIRECTORY_NOT_EMPTY; + #endif + #ifdef ELOOP + case ELOOP: return DROPUS_TOO_MANY_LINKS; + #endif + #ifdef ENOMSG + case ENOMSG: return DROPUS_NO_MESSAGE; + #endif + #ifdef EIDRM + case EIDRM: return DROPUS_ERROR; + #endif + #ifdef ECHRNG + case ECHRNG: return DROPUS_ERROR; + #endif + #ifdef EL2NSYNC + case EL2NSYNC: return DROPUS_ERROR; + #endif + #ifdef EL3HLT + case EL3HLT: return DROPUS_ERROR; + #endif + #ifdef EL3RST + case EL3RST: return DROPUS_ERROR; + #endif + #ifdef ELNRNG + case ELNRNG: return DROPUS_OUT_OF_RANGE; + #endif + #ifdef EUNATCH + case EUNATCH: return DROPUS_ERROR; + #endif + #ifdef ENOCSI + case ENOCSI: return DROPUS_ERROR; + #endif + #ifdef EL2HLT + case EL2HLT: return DROPUS_ERROR; + #endif + #ifdef EBADE + case EBADE: return DROPUS_ERROR; + #endif + #ifdef EBADR + case EBADR: return DROPUS_ERROR; + #endif + #ifdef EXFULL + case EXFULL: return DROPUS_ERROR; + #endif + #ifdef ENOANO + case ENOANO: return DROPUS_ERROR; + #endif + #ifdef EBADRQC + case EBADRQC: return DROPUS_ERROR; + #endif + #ifdef EBADSLT + case EBADSLT: return DROPUS_ERROR; + #endif + #ifdef EBFONT + case EBFONT: return DROPUS_INVALID_FILE; + #endif + #ifdef ENOSTR + case ENOSTR: return DROPUS_ERROR; + #endif + #ifdef ENODATA + case ENODATA: return DROPUS_NO_DATA_AVAILABLE; + #endif + #ifdef ETIME + case ETIME: return DROPUS_TIMEOUT; + #endif + #ifdef ENOSR + case ENOSR: return DROPUS_NO_DATA_AVAILABLE; + #endif + #ifdef ENONET + case ENONET: return DROPUS_NO_NETWORK; + #endif + #ifdef ENOPKG + case ENOPKG: return DROPUS_ERROR; + #endif + #ifdef EREMOTE + case EREMOTE: return DROPUS_ERROR; + #endif + #ifdef ENOLINK + case ENOLINK: return DROPUS_ERROR; + #endif + #ifdef EADV + case EADV: return DROPUS_ERROR; + #endif + #ifdef ESRMNT + case ESRMNT: return DROPUS_ERROR; + #endif + #ifdef ECOMM + case ECOMM: return DROPUS_ERROR; + #endif + #ifdef EPROTO + case EPROTO: return DROPUS_ERROR; + #endif + #ifdef EMULTIHOP + case EMULTIHOP: return DROPUS_ERROR; + #endif + #ifdef EDOTDOT + case EDOTDOT: return DROPUS_ERROR; + #endif + #ifdef EBADMSG + case EBADMSG: return DROPUS_BAD_MESSAGE; + #endif + #ifdef EOVERFLOW + case EOVERFLOW: return DROPUS_TOO_BIG; + #endif + #ifdef ENOTUNIQ + case ENOTUNIQ: return DROPUS_NOT_UNIQUE; + #endif + #ifdef EBADFD + case EBADFD: return DROPUS_ERROR; + #endif + #ifdef EREMCHG + case EREMCHG: return DROPUS_ERROR; + #endif + #ifdef ELIBACC + case ELIBACC: return DROPUS_ACCESS_DENIED; + #endif + #ifdef ELIBBAD + case ELIBBAD: return DROPUS_INVALID_FILE; + #endif + #ifdef ELIBSCN + case ELIBSCN: return DROPUS_INVALID_FILE; + #endif + #ifdef ELIBMAX + case ELIBMAX: return DROPUS_ERROR; + #endif + #ifdef ELIBEXEC + case ELIBEXEC: return DROPUS_ERROR; + #endif + #ifdef EILSEQ + case EILSEQ: return DROPUS_INVALID_DATA; + #endif + #ifdef ERESTART + case ERESTART: return DROPUS_ERROR; + #endif + #ifdef ESTRPIPE + case ESTRPIPE: return DROPUS_ERROR; + #endif + #ifdef EUSERS + case EUSERS: return DROPUS_ERROR; + #endif + #ifdef ENOTSOCK + case ENOTSOCK: return DROPUS_NOT_SOCKET; + #endif + #ifdef EDESTADDRREQ + case EDESTADDRREQ: return DROPUS_NO_ADDRESS; + #endif + #ifdef EMSGSIZE + case EMSGSIZE: return DROPUS_TOO_BIG; + #endif + #ifdef EPROTOTYPE + case EPROTOTYPE: return DROPUS_BAD_PROTOCOL; + #endif + #ifdef ENOPROTOOPT + case ENOPROTOOPT: return DROPUS_PROTOCOL_UNAVAILABLE; + #endif + #ifdef EPROTONOSUPPORT + case EPROTONOSUPPORT: return DROPUS_PROTOCOL_NOT_SUPPORTED; + #endif + #ifdef ESOCKTNOSUPPORT + case ESOCKTNOSUPPORT: return DROPUS_SOCKET_NOT_SUPPORTED; + #endif + #ifdef EOPNOTSUPP + case EOPNOTSUPP: return DROPUS_INVALID_OPERATION; + #endif + #ifdef EPFNOSUPPORT + case EPFNOSUPPORT: return DROPUS_PROTOCOL_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EAFNOSUPPORT + case EAFNOSUPPORT: return DROPUS_ADDRESS_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EADDRINUSE + case EADDRINUSE: return DROPUS_ALREADY_IN_USE; + #endif + #ifdef EADDRNOTAVAIL + case EADDRNOTAVAIL: return DROPUS_ERROR; + #endif + #ifdef ENETDOWN + case ENETDOWN: return DROPUS_NO_NETWORK; + #endif + #ifdef ENETUNREACH + case ENETUNREACH: return DROPUS_NO_NETWORK; + #endif + #ifdef ENETRESET + case ENETRESET: return DROPUS_NO_NETWORK; + #endif + #ifdef ECONNABORTED + case ECONNABORTED: return DROPUS_NO_NETWORK; + #endif + #ifdef ECONNRESET + case ECONNRESET: return DROPUS_CONNECTION_RESET; + #endif + #ifdef ENOBUFS + case ENOBUFS: return DROPUS_NO_SPACE; + #endif + #ifdef EISCONN + case EISCONN: return DROPUS_ALREADY_CONNECTED; + #endif + #ifdef ENOTCONN + case ENOTCONN: return DROPUS_NOT_CONNECTED; + #endif + #ifdef ESHUTDOWN + case ESHUTDOWN: return DROPUS_ERROR; + #endif + #ifdef ETOOMANYREFS + case ETOOMANYREFS: return DROPUS_ERROR; + #endif + #ifdef ETIMEDOUT + case ETIMEDOUT: return DROPUS_TIMEOUT; + #endif + #ifdef ECONNREFUSED + case ECONNREFUSED: return DROPUS_CONNECTION_REFUSED; + #endif + #ifdef EHOSTDOWN + case EHOSTDOWN: return DROPUS_NO_HOST; + #endif + #ifdef EHOSTUNREACH + case EHOSTUNREACH: return DROPUS_NO_HOST; + #endif + #ifdef EALREADY + case EALREADY: return DROPUS_IN_PROGRESS; + #endif + #ifdef EINPROGRESS + case EINPROGRESS: return DROPUS_IN_PROGRESS; + #endif + #ifdef ESTALE + case ESTALE: return DROPUS_INVALID_FILE; + #endif + #ifdef EUCLEAN + case EUCLEAN: return DROPUS_ERROR; + #endif + #ifdef ENOTNAM + case ENOTNAM: return DROPUS_ERROR; + #endif + #ifdef ENAVAIL + case ENAVAIL: return DROPUS_ERROR; + #endif + #ifdef EISNAM + case EISNAM: return DROPUS_ERROR; + #endif + #ifdef EREMOTEIO + case EREMOTEIO: return DROPUS_IO_ERROR; + #endif + #ifdef EDQUOT + case EDQUOT: return DROPUS_NO_SPACE; + #endif + #ifdef ENOMEDIUM + case ENOMEDIUM: return DROPUS_DOES_NOT_EXIST; + #endif + #ifdef EMEDIUMTYPE + case EMEDIUMTYPE: return DROPUS_ERROR; + #endif + #ifdef ECANCELED + case ECANCELED: return DROPUS_CANCELLED; + #endif + #ifdef ENOKEY + case ENOKEY: return DROPUS_ERROR; + #endif + #ifdef EKEYEXPIRED + case EKEYEXPIRED: return DROPUS_ERROR; + #endif + #ifdef EKEYREVOKED + case EKEYREVOKED: return DROPUS_ERROR; + #endif + #ifdef EKEYREJECTED + case EKEYREJECTED: return DROPUS_ERROR; + #endif + #ifdef EOWNERDEAD + case EOWNERDEAD: return DROPUS_ERROR; + #endif + #ifdef ENOTRECOVERABLE + case ENOTRECOVERABLE: return DROPUS_ERROR; + #endif + #ifdef ERFKILL + case ERFKILL: return DROPUS_ERROR; + #endif + #ifdef EHWPOISON + case EHWPOISON: return DROPUS_ERROR; + #endif + default: return DROPUS_ERROR; + } +} + +static dropus_result dropus_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err; +#endif + + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DROPUS_INVALID_ARGS; + } + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + err = fopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return dropus_result_from_errno(err); + } +#else +#if defined(_WIN32) || defined(__APPLE__) + *ppFile = fopen(pFilePath, pOpenMode); +#else + #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) + *ppFile = fopen64(pFilePath, pOpenMode); + #else + *ppFile = fopen(pFilePath, pOpenMode); + #endif +#endif + if (*ppFile == NULL) { + dropus_result result = dropus_result_from_errno(errno); + if (result == DROPUS_SUCCESS) { + result = DROPUS_ERROR; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ + } + + return result; + } +#endif + + return DROPUS_SUCCESS; +} + +/* +_wfopen() isn't always available in all compilation environments. + + * Windows only. + * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). + * MinGW-64 (both 32- and 64-bit) seems to support it. + * MinGW wraps it in !defined(__STRICT_ANSI__). + +This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() +fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. +*/ +#if defined(_WIN32) + #if defined(_MSC_VER) || defined(__MINGW64__) || !defined(__STRICT_ANSI__) + #define DROPUS_HAS_WFOPEN + #endif +#endif + +static dropus_result dropus_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const dropus_allocation_callbacks* pAllocationCallbacks) +{ + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DROPUS_INVALID_ARGS; + } + +#if defined(DROPUS_HAS_WFOPEN) + { + /* Use _wfopen() on Windows. */ + #if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return dropus_result_from_errno(err); + } + #else + *ppFile = _wfopen(pFilePath, pOpenMode); + if (*ppFile == NULL) { + return dropus_result_from_errno(errno); + } + #endif + (void)pAllocationCallbacks; + } +#else + /* + Use fopen() on anything other than Windows. Requires a conversion. This is annoying because fopen() is locale specific. The only real way I can + think of to do this is with wcsrtombs(). Note that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for + maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler error I'll look into improving compatibility. + */ + { + mbstate_t mbs; + size_t lenMB; + const wchar_t* pFilePathTemp = pFilePath; + char* pFilePathMB = NULL; + char pOpenModeMB[32] = {0}; + + /* Get the length first. */ + DROPUS_ZERO_OBJECT(&mbs); + lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); + if (lenMB == (size_t)-1) { + return dropus_result_from_errno(errno); + } + + pFilePathMB = (char*)dropus__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks); + if (pFilePathMB == NULL) { + return DROPUS_OUT_OF_MEMORY; + } + + pFilePathTemp = pFilePath; + DROPUS_ZERO_OBJECT(&mbs); + wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); + + /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ + { + size_t i = 0; + for (;;) { + if (pOpenMode[i] == 0) { + pOpenModeMB[i] = '\0'; + break; + } + + pOpenModeMB[i] = (char)pOpenMode[i]; + i += 1; + } + } + + *ppFile = fopen(pFilePathMB, pOpenModeMB); + + dropus__free_from_callbacks(pFilePathMB, pAllocationCallbacks); + } + + if (*ppFile == NULL) { + return DROPUS_ERROR; + } +#endif + + return DROPUS_SUCCESS; +} + + +static size_t dropus_on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData); +} + +static dropus_bool32 dropus_on_seek_stdio(void* pUserData, int offset, dropus_seek_origin origin) +{ + return fseek((FILE*)pUserData, offset, (origin == dropus_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; +} + +DROPUS_API dropus_result dropus_init_file(dropus* pOpus, const char* pFilePath, const dropus_allocation_callbacks* pAllocationCallbacks) +{ + dropus_result result; + FILE* pFile; + + if (pOpus == NULL) { + return DROPUS_INVALID_ARGS; + } + + DROPUS_ZERO_OBJECT(pOpus); + + if (pFilePath == NULL || pFilePath[0] == '\0') { + return DROPUS_INVALID_ARGS; + } + + result = dropus_fopen(&pFile, pFilePath, "rb"); + if (result != DROPUS_SUCCESS) { + return result; + } + + pOpus->pFile = (void*)pFile; + + result = dropus_init_internal(pOpus, dropus_on_read_stdio, dropus_on_seek_stdio, NULL, pAllocationCallbacks); + if (result != DROPUS_SUCCESS) { + fclose(pFile); + return result; + } + + return DROPUS_SUCCESS; +} + +DROPUS_API dropus_result dropus_init_file_w(dropus* pOpus, const wchar_t* pFilePath, const dropus_allocation_callbacks* pAllocationCallbacks) +{ + dropus_result result; + FILE* pFile; + + if (pOpus == NULL) { + return DROPUS_INVALID_ARGS; + } + + DROPUS_ZERO_OBJECT(pOpus); + + if (pFilePath == NULL || pFilePath[0] == '\0') { + return DROPUS_INVALID_ARGS; + } + + result = dropus_wfopen(&pFile, pFilePath, L"rb", pAllocationCallbacks); + if (result != DROPUS_SUCCESS) { + return result; + } + + pOpus->pFile = (void*)pFile; + + result = dropus_init_internal(pOpus, dropus_on_read_stdio, dropus_on_seek_stdio, NULL, pAllocationCallbacks); + if (result != DROPUS_SUCCESS) { + fclose(pFile); + return result; + } + + return DROPUS_SUCCESS; +} +#endif + +static size_t dropus_on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + size_t bytesRemaining; + dropus* pOpus = (dropus*)pUserData; + + DROPUS_ASSERT(pOpus != NULL); + DROPUS_ASSERT(pOpus->memory.dataSize >= pOpus->memory.currentReadPos); + + bytesRemaining = pOpus->memory.dataSize - pOpus->memory.currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + DROPUS_COPY_MEMORY(pBufferOut, pOpus->memory.pData + pOpus->memory.currentReadPos, bytesToRead); + pOpus->memory.currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +static dropus_bool32 dropus_on_seek_memory(void* pUserData, int byteOffset, dropus_seek_origin origin) +{ + dropus* pOpus = (dropus*)pUserData; + DROPUS_ASSERT(pOpus != NULL); + + if (origin == dropus_seek_origin_current) { + if (byteOffset > 0) { + if (pOpus->memory.currentReadPos + byteOffset > pOpus->memory.dataSize) { + byteOffset = (int)(pOpus->memory.dataSize - pOpus->memory.currentReadPos); /* Trying to seek too far forward. */ + } + } else { + if (pOpus->memory.currentReadPos < (size_t)-byteOffset) { + byteOffset = -(int)pOpus->memory.currentReadPos; /* Trying to seek too far backwards. */ + } + } + + /* This will never underflow thanks to the clamps above. */ + pOpus->memory.currentReadPos += byteOffset; + } else { + if ((dropus_uint32)byteOffset <= pOpus->memory.dataSize) { + pOpus->memory.currentReadPos = byteOffset; + } else { + pOpus->memory.currentReadPos = pOpus->memory.dataSize; /* Trying to seek too far forward. */ + } + } + + return DROPUS_TRUE; +} + +DROPUS_API dropus_result dropus_init_memory(dropus* pOpus, const void* pData, size_t dataSize, const dropus_allocation_callbacks* pAllocationCallbacks) +{ + if (pOpus == NULL) { + return DROPUS_INVALID_ARGS; + } + + DROPUS_ZERO_OBJECT(pOpus); + + if (pData == NULL || dataSize == 0) { + return DROPUS_INVALID_ARGS; + } + + pOpus->memory.pData = (const dropus_uint8*)pData; + pOpus->memory.dataSize = dataSize; + pOpus->memory.currentReadPos = 0; + + return dropus_init_internal(pOpus, dropus_on_read_memory, dropus_on_seek_memory, NULL, pAllocationCallbacks); +} + + +DROPUS_API void dropus_uninit(dropus* pOpus) +{ + if (pOpus == NULL) { + return; + } + +#ifndef DR_OPUS_NO_STDIO + /* Since dr_opus manages the stdio FILE object make sure it's closed on uninitialization. */ + if (pOpus->pFile != NULL) { + fclose((FILE*)pOpus->pFile); + } +#endif +} + + + +DROPUS_API const char* dropus_result_description(dropus_result result) +{ + switch (result) + { + case DROPUS_SUCCESS: return "No error"; + case DROPUS_ERROR: return "Unknown error"; + case DROPUS_INVALID_ARGS: return "Invalid argument"; + case DROPUS_INVALID_OPERATION: return "Invalid operation"; + case DROPUS_OUT_OF_MEMORY: return "Out of memory"; + case DROPUS_OUT_OF_RANGE: return "Out of range"; + case DROPUS_ACCESS_DENIED: return "Permission denied"; + case DROPUS_DOES_NOT_EXIST: return "Resource does not exist"; + case DROPUS_ALREADY_EXISTS: return "Resource already exists"; + case DROPUS_TOO_MANY_OPEN_FILES: return "Too many open files"; + case DROPUS_INVALID_FILE: return "Invalid file"; + case DROPUS_TOO_BIG: return "Too large"; + case DROPUS_PATH_TOO_LONG: return "Path too long"; + case DROPUS_NAME_TOO_LONG: return "Name too long"; + case DROPUS_NOT_DIRECTORY: return "Not a directory"; + case DROPUS_IS_DIRECTORY: return "Is a directory"; + case DROPUS_DIRECTORY_NOT_EMPTY: return "Directory not empty"; + case DROPUS_END_OF_FILE: return "End of file"; + case DROPUS_NO_SPACE: return "No space available"; + case DROPUS_BUSY: return "Device or resource busy"; + case DROPUS_IO_ERROR: return "Input/output error"; + case DROPUS_INTERRUPT: return "Interrupted"; + case DROPUS_UNAVAILABLE: return "Resource unavailable"; + case DROPUS_ALREADY_IN_USE: return "Resource already in use"; + case DROPUS_BAD_ADDRESS: return "Bad address"; + case DROPUS_BAD_SEEK: return "Illegal seek"; + case DROPUS_BAD_PIPE: return "Broken pipe"; + case DROPUS_DEADLOCK: return "Deadlock"; + case DROPUS_TOO_MANY_LINKS: return "Too many links"; + case DROPUS_NOT_IMPLEMENTED: return "Not implemented"; + case DROPUS_NO_MESSAGE: return "No message of desired type"; + case DROPUS_BAD_MESSAGE: return "Invalid message"; + case DROPUS_NO_DATA_AVAILABLE: return "No data available"; + case DROPUS_INVALID_DATA: return "Invalid data"; + case DROPUS_TIMEOUT: return "Timeout"; + case DROPUS_NO_NETWORK: return "Network unavailable"; + case DROPUS_NOT_UNIQUE: return "Not unique"; + case DROPUS_NOT_SOCKET: return "Socket operation on non-socket"; + case DROPUS_NO_ADDRESS: return "Destination address required"; + case DROPUS_BAD_PROTOCOL: return "Protocol wrong type for socket"; + case DROPUS_PROTOCOL_UNAVAILABLE: return "Protocol not available"; + case DROPUS_PROTOCOL_NOT_SUPPORTED: return "Protocol not supported"; + case DROPUS_PROTOCOL_FAMILY_NOT_SUPPORTED: return "Protocol family not supported"; + case DROPUS_ADDRESS_FAMILY_NOT_SUPPORTED: return "Address family not supported"; + case DROPUS_SOCKET_NOT_SUPPORTED: return "Socket type not supported"; + case DROPUS_CONNECTION_RESET: return "Connection reset"; + case DROPUS_ALREADY_CONNECTED: return "Already connected"; + case DROPUS_NOT_CONNECTED: return "Not connected"; + case DROPUS_CONNECTION_REFUSED: return "Connection refused"; + case DROPUS_NO_HOST: return "No host"; + case DROPUS_IN_PROGRESS: return "Operation in progress"; + case DROPUS_CANCELLED: return "Operation cancelled"; + case DROPUS_MEMORY_ALREADY_MAPPED: return "Memory already mapped"; + case DROPUS_AT_END: return "Reached end of collection"; + + /* dr_opus specific result codes. */ + case DROPUS_CRC_MISMATCH: return "CRC mismatch"; + case DROPUS_BAD_DATA: return "Bad data"; + + default: return "Unknown error"; + } +} + +DROPUS_API void* dropus_malloc(size_t sz, const dropus_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return dropus__malloc_default(sz, NULL); + } else { + return dropus__malloc_from_callbacks(sz, pAllocationCallbacks); + } +} + +DROPUS_API void* dropus_realloc(void* p, size_t sz, const dropus_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return dropus__realloc_default(p, sz, NULL); + } else { + /* + We need to do an explicit check for onRealloc because dropus__realloc_from_callbacks() will fall back to a malloc based emulation if onRealloc is missing + which we do not want to do dropus_realloc(). + */ + if (pAllocationCallbacks->onRealloc == NULL) { + return NULL; + } + + return dropus__realloc_from_callbacks(p, sz, /*szOld*/ 0, pAllocationCallbacks); /* Safe to pass 0 for szOld thanks to the onRealloc check above. */ + } +} + +DROPUS_API void dropus_free(void* p, const dropus_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + dropus__free_default(p, NULL); + } else { + dropus__free_from_callbacks(p, pAllocationCallbacks); + } +} +#endif /* DR_OPUS_IMPLEMENTATION */ + +/* +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - Public Domain (www.unlicense.org) +=============================================================================== +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright 2020 David Reid + +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. + +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/source/engine/thirdparty/dr_libs/wip/dr_opus_log.txt b/source/engine/thirdparty/dr_libs/wip/dr_opus_log.txt new file mode 100644 index 0000000..69fc4cf --- /dev/null +++ b/source/engine/thirdparty/dr_libs/wip/dr_opus_log.txt @@ -0,0 +1,318 @@ +This is a log of my development of dr_opus - a single file public domain Opus decoder in C. The purpose of this log is +to track the development of the project and log how I solved various problems. + +References: + RFC 6716 - https://tools.ietf.org/html/rfc6716 - Definition of the Opus Audio Codec + RFC 7845 - https://tools.ietf.org/html/rfc7845 - Ogg Encapsulation for the Opus Audio Codec + + +The main reference for development is RFC 6716. An important detail with this specification is in Section 1 + + The primary normative part of this specification is provided by the + source code in Appendix A. Only the decoder portion of this software + is normative, though a significant amount of code is shared by both + the encoder and decoder. Section 6 provides a decoder conformance + test. The decoder contains a great deal of integer and fixed-point + arithmetic that needs to be performed exactly, including all rounding + considerations, so any useful specification requires domain-specific + symbolic language to adequately define these operations. + Additionally, any conflict between the symbolic representation and + the included reference implementation must be resolved. For the + practical reasons of compatibility and testability, it would be + advantageous to give the reference implementation priority in any + disagreement. The C language is also one of the most widely + understood, human-readable symbolic representations for machine + behavior. For these reasons, this RFC uses the reference + implementation as the sole symbolic representation of the codec. + +What this is basically saying is that the source code should be considered the main point of reference for the format of +the Opus bit stream. We cannot, however, just willy nilly take code from the reference implementation because that would +cause licensing problems. However, I'm going to make a prediction before I even begin: I think RFC 6716 with a bit +intuition, problem solving and help from others it can be figured out. Indeed, I think RFC 6716 may even be enough, and +that perhaps the authors of said specification refer to the source code as a self-defence mechanism to guard themselves +from possible subtle errors in the specification. If I'm wrong, the log below will track it. + +Lets begin... + + +2018/11/30 6:55 PM +================== +- Inserted skeleton code with licensing information. Dual licensed as Public Domain _or_ MIT, whichever you prefer. API + is undecided as of yet. +- Decided on supporting C89. Not yet decided on how the standard library will be used. +- Deciding for now to use the init() style of API from dr_wav and dr_mp3 which takes a pre-allocated decoder object. May + need to change this to dr_flac style open() (or perhaps new()/delete()) if the size of the structure is too big. +- dr_wav, etc. use booleans for results status so I'm copying that for consistency. +- Added boilerplate for standard sized types (copied from mini_al). +- Added a comment at the top of the file to show that this isn't complete (even though it's in the "wip" folder!). +- Added boilerplate for read and seek callbacks (copied from dr_flac). +- Added shell APIs: + - dropus_init() + - Not requiring onSeek for the moment because I'm hoping that we can make that optional somehow, with restriction. + - dropus_uninit() + - Does nothing at the moment, but in future will need to close a file when the decoder is opened against a file. +- Added some common overrideable macros (taken from dr_flac): + - DROPUS_ASSERT + - DROPUS_COPY_MEMORY + - DROPUS_ZERO_MEMORY + - DROPUS_ZERO_OBJECT +- Decided to use the C standard library for now. This simplifies things like memset() and assert(). Currently using the + following headers: + - stddef.h + - stdint.h + - stdlib.h + - string.h + - assert.h +- Added some infrastructure for future APIs: dropus_init_file() and dropus_init_memory() + - In dr_flac, dr_wav and dr_mp3, a mistake was made with how the internal file handle is managed by the decoder. In + these libraries the pUserData member of the decoder structure is recycled to hold the FILE pointer. dr_opus is + changing this to be it's own member of the main decoder structure. + - The internal members for use with dropus_init_memory() use the same system as dr_mp3. See drmp3.memory. +- Added dropus_init_file() + - All init() APIs call dropus_init_internal() which is where the main initialization work is done. + - This is using the stdio FILE API. + - dropus_uninit() has been updated to close the file. + - Added dropus_fopen() for compiler compatibility. Taken from dr_flac, but added the "mode" parameter for consistency + with fopen(). + - Added dropus_fclose() for namespace consistency with dropus_fopen(). +- Added dropus_init_memory() + - onRead and onSeek callbacks taken from dr_mp3. +- INITIAL COMMIT + +2018/12/01 12:46 PM +=================== +- Downloading all official test vectors in preparation for future testing. + +2018/12/01 03:58 PM +=================== +- Decided to figure out the different encapsulations for Opus so I can figure out a design for data flow. + - Ogg: https://tools.ietf.org/html/rfc7845 + - Matroska: https://wiki.xiph.org/MatroskaOpus + - WebM: https://www.webmproject.org/docs/container/ + - MPEG-TS: https://wiki.xiph.org/OpusTS + - Mysterious encapsulation used by the official test vectors. Looking at this it's hard to tell what they are using for + this, but I'm assuming it's not documented. Will need to figure that one out for myself later. +- Since WebM is a subset of Matroska, can we combine those together to avoid code duplication? +- Upon reading into Ogg and MPEG-TS encapsulation (not looked into Matroska in detail yet) it looks like handling large + channel counts is going to be a pain. It can support up to 255 channels, which are made up of multiple streams. My + instict is telling me that we won't be able to use a fixed sized data structure and instead might require dynamic + allocations. Darn... +- Leaning towards a two-tier architecture - a low-level decoder for raw Opus streams, and a high-level API for encapsulated + streams. Opus packets support only 1 or 2 channels which means the size of the structure _can_ be fixed. + - Currently I'm thinking something like the following: + - struct dropus_stream - low-level Opus stream decoding + - struct dropus - high-level encapsulated Opus stream decoding + - struct dropus_ogg (Ogg Opus) + - struct dropus_mk (Matroska) + - struct dropus_ts (MPEG-TS) + - struct dropus_tv (That annoying non-standard and undocumented encapsulation used by the test vectors) +- The maximum number of samples per frame (per-channel) is 960 because: + - The maximum frame size for wideband (16kHz) is 60: 16*60 = 960 + - The maximum frame size for fullband (48kHz) is 20: 48*20 = 960 + - To account for stereo frames we would need to multiply 960 by 2: 960*2 = 1920 + - This is small enough that it can be stored statically inside the dropus_stream structure. +- It looks like low level Opus streams won't be able to do efficient seeking without an encapsulation. +- An Opus stream is made up of packets. Each packet has 1 or more frames. Each frame can be made up of 1 or 2 channels. +- The size of the packet in bytes is unknown at the Opus stream level - it depends on the encapsulation to know the + size of the packet. This could be a bit of a problem because we may need to know the size of a frame in order to know + how much data we need from the client. I'm wondering if the low-level API should look something like the following: + - dropus_result dropus_stream_decode_packet(dropus_stream* pOpusStream, const void* pCompressedData, size_t compressedDataSize); + - This will require the caller to have information on the size of the packet. +- Added struct dropus_stream_packet, dropus_stream_frame. +- Each packet contains a byte that defines the structure of the packet. It's called the TOC byte + (Section 3.1. The TOC Byte) in the RFC 6716. +- Added skeleton implementations for dropus_stream_init() and dropus_stream_decode_packet(). +- Added helpers for decoding the different parts of TOC byte. + - dropus_toc_config(), dropus_toc_s() and dropus_toc_c() +- Added enum for the three different modes as outlined in Section 3.1 of RFC 6716. Also added an API to extract this + mode from the config component of the TOC: dropus_toc_config_mode() and dropus_toc_mode(). +- Added API to extract sample rate from the TOC: dropus_toc_config_sample_rate() and dropus_toc_sample_rate() +- Added API to extract the size of an Opus frame in PCM frames from the TOC: + - dropus_toc_config_frame_size_in_pcm_frames() and dropus_toc_frame_size_in_pcm_frames(). +- COMMIT + +2018/12/02 09:45 AM +=================== +- Compiling for the first time. Targetting C89 so need to get it all working property right from the start. +- "test" folder added to my local repository. Not version controlled yet as I'm not sure I want to include all of + the test stuff in the repository. Will definitely not be version controlling the test vectors. Perhaps use a submodule? +- Have decided to not support the mysterious encapsulation format used by the test vectors in dr_opus itself. Instead + I'm just doing it manually in the test program. +- After attempting to build the C++ version, it appears Clang will attempt to #include stdint.h from Visual Studio's + standard library which fails. May need to come up with a way to avoid the dependency on stdint.h. Same thing also + happens with stdlib.h. +- Have cleaned up the sized types stuff for Clang to avoid the dependency on stdint, however errors are still happening + due to the use of stdlib.h. Leaving the Clang C++ issue for now. It's technically a Clang issue rather than dr_opus. +- COMMIT +- Have decided to version control test programs, but not test vectors. Instead I'm putting a note in a readme about + where to download and place the test vectors. +- In my experience I have found that having a testing infrastructure set up earlier saves a lot of time in the long run + so the next thing I have decided to work on is a test program. It makes sense to test against the official test vectors, + so the first thing is to figure out this undocumented encapsulation format... + +Test Vector Encapsulation Format +-------------------------------- +- All test vectors seem to start with two bytes of 0x00. + - This does not feel like a magic number or identifier. +- Noticed that first 4 bytes add up to a relatively small number. A counter of some kind in little endian format? +- A pattern can be seen in testvector02.bit: + - The bytes 00 00 00 XX can been seen repeating. This indicates some kind of blocking. + - From the first 4 bytes you see the hex value of 0x0000001E which equals 30 in decimal + - Counting forward 30 bytes brings us 4 bytes before the next set of 00 00 00 XX bytes. Definitely starting to feel like + the first 4 bytes are a byte counter. + - Skipping past those four bytes and looking at the next 00 00 00 XX bytes we get 0x0000001D which is 29 in decimal. + - Again, counting forward by that number of bytes brings us to 4 bytes prior to the next 00 00 00 XX block. First 4 + bytes are definitely a byte counter. +- It makes sense that each of these blocks are an Opus packet. +- Still don't know what the second set of 4 bytes could represent... Does it matter for our purposes? All we care about + is the raw Opus bit stream. + - A Google search for "opus test vector format" brings up the following result (a PDF document): + - https://www.ietf.org/proceedings/82/slides/codec-4.pdf + - From the "Force Multipliers" page: + - "The range coder has 32 bits of state which must match between the encoder and decoder" + - "Provides a checksum of all encoding and decoding decisions" + - "opus_demo bitstreams include the range value with every packet and test for mismatches" + - Willing to bet that the extra 4 bytes are that "32 bits of state". This is actually good for dr_opus. +- I think the format is just a list of Opus packets with 8 bytes of header data for each one: + 4 bytes: Opus packet size in bytes + 4 bytes: 32 bit of range coder state + [Opus Packet] +-------------------------------- + +- The test program can either hard code a list of vector files or we can enumerate over files in known directories. I'm + preferring the latter to make it easier to add new vectors. It's also consistent with the way dr_flac does it which has + been pretty good in the past. +- Started work on a "common" lib for test programs. First thing is the new file iterator. + - File iterator now done, but only supporting Windows currently. +- Have implemented the logic for loading test vectors and the byte counter (and I'm assuming the range state) is actually + in big-endian, not little-endian. I'm an idiot! +- Copying endian management APIs from dr_flac over to dr_opus. +- Basic infrastructure for testing against the standard test vectors is now done. +- COMMIT + +2018/12/03 6:15 PM +================== +- The packet enumeration in the test program seems to work so far, but I'm still not 100% sure that my assumptions are + correct. Adding more validation to dropus_stream_decode_packet() to increase my certainty. The next stage is to + implement all of the main packet validation. +- Question for later. How are "Discontinuous Transmission (DTX) or lost packet" handled? Should + dropus_stream_decode_packet() return an error in this case? +- Code for validating packet codes 0, 1 and 2 are now in, however untested so far. Code 3 is a bit more complicated and + right now I need to eat dinner... +- COMMIT + +2018/12/03 6:40 PM +================== +- Code 3 CBR is done (no VBR yet). Still untested. +- Code 3 VBR is done. Still untested. +- Testing and debugging against the standard test vectors coming next. +- Fixed a few compiler warnings. +- COMMIT + +2019/03/08 6:28 PM +================== +- Starting on range decoding. From figure 4 in RFC 6716 the diagram shows that both the SILK and CELT decoders draw + their data from the Range Decoder. Therefore the Range Decoder needs to be figured out before SILK and CELT decoding. + I have no prior knowledge on range coding, so hopefully RFC 6716 has enough information for a complete enough + implementation for purpose of Opus decoding. +- Section 4.1.1 of RGC 6716 is how to initialize the decoder. It references a variable called "rng", but doesn't + specificy it's possible ranges. Setting to a 32-bit unsigned integer for now. + - In this section, it says "or containing zero if there are no bytes in this Opus frame". I'm assuming we do this + by looping over each frame in order... + - Just noticed that the end of section 4.1.1 says "rng > 2**23". So "rng" needs to be 32-bits. +- And now I've just noticed that section 4.1 explicitly says the following: "Both val and rng are 32-bit unsigned integer + values." Problem solved. +- Having read over the range coding stuff in RFC 6716 I had some difficulty figuring out where symbols frequencies are + being stored. They're not stored, but rather specified in the specification in a per-context basis. In the spec there + are references to "PDFs" which is like this "{f[0], f[1]}/ft". Example: "{1, 1}/2". Simple, now that I've finally + figured it out... +- I can therefore not do the range decoder independant of some higher level context. I will start with the SILK decoder + since that's the next section... + +2019/03/09 6:52 AM +================== +- I think I'm getting a feel for the requirements for the range decoding API. I've decided for now to use a structure + for this called dropus_range_decoder. +- dropus_range_decoder_init() implemented based on section 4.1.1. +- Added stub for dropus_stream_decode_frame(). I think the idea is that we iterate over each frame in the packet and + decode them in order? Not fully sure yet how frame transitions will work, so may need to rethink this. +- I'm still not sure if the range decoder is supposed to be initialized once per frame or once per packet... +- After more testing it turns out my test program wasn't reporting errors properly. *Sigh*. These bugs were originating + from the packet decoding section. +- COMMIT +- Section 4.1.2 mentions that there's two steps to decoding a symbol. The first step seems easy enough. Implemented in + dropus_range_decoder_fs(). +- The second step requires updating "val" and "rng" of the range decoder based on (fl[k], fh[k] and ft). Implemented in + dropus_range_decoder_update(pRangeDecoder, fl, fh, ft). +- Normalization of the range decoder is easy (RFC 6716 - 4.1.2.1). Implemented in dropus_range_decoder_normalize(). +- I think at this point we have the foundations done for the range decoder. Still more to do for CELT, but I think I + now have enough to hack away a bit more at the SILK-specific data. +- COMMIT +- I'm still not 100% sure how range decoding works - I know it depends on the context of the specific thing being + decoded, but not sure how the index "k" is handled. The first part of the SILK frame is the VAD flags PDF={1,1}/2. + Do I use the value of k (which I think is 0 or 1) to know whether or not the bit is set? I am going to assume this + for now. + +2019/03/10 7:40 AM +================== +- At the moment I am using what feels like a very cumbersome way of decoding "k" from the range decoder. I'm calling + dropus_range_decoder_fs(), followed by a function called dropus_range_decoder_k() to retrieve "k", and then + dropus_range_decoder_update() at the end of it. I can simplify this into a single function, I think, which I'm + calling dropus_range_decode() which will perform the "fs" decode, perform the update, then return "k". +- As I'm writing dropus_range_decoder_decode() I've realized that I can simplify my dropus_range_decoder_update() + function to do both the "k" lookup and the the update from section 4.1.2. It just needs to be changed to take + the "context" probabilities and "fs" and to return "k". +- This _seems_ to work. Or at least, the results are consistent with my first attempt. Committing. +- Added dropus_Q13() for doing the stereo weight lookup (RFC 7616 - Table 7). +- Section 4.2.7.1 mentions that the weights are linearly interpolated with the weights from the previous frame. What + factor do I use for the interpolation? +- Finished initial work on RFC 7616 - Section 4.2.7.1. Still don't know how the interpolation part works. I'm hoping + this is explained later in the spec. +- COMMIT +- Completed initial implementation of RFC 7616 - Section 4.2.7.2. + +2020/03/29 6:33 AM +================== +- After a whole year, time to take another look at this. Only problem is, I've forgotten everything! Let's do a + quick pass on the high level stuff to get it consistent with dr_flac. Two mistakes I made with dr_flac: Not having + a low-level API that works on the FLAC frame level; and returning booleans instead of result codes. The first one + is already being handled, but lets update the high level APIs to use result codes. +- High level APIs now updated to return result codes. +- People are going to want to log errors: added dropus_result_description() to retrieve a human readable description + of a result code for logging and error reporting purposes. +- I've had reports of people having compilation errors with inlining before. Need to bring over the INLINE macro from + dr_flac to ensure we don't get more reports about that. +- I've had people request the ability to customize whether or not public APIs are marked as static or extern in other + libraries, so adding the DROPUS_API decoration. It's better to do this sooner than later, because this happened with + miniaudio recently which was a ~40K line library at the time, and it was nightmare to update! +- There will be a requirement to allocate memory for high level APIs. Support for allocation macros and allocation + callbacks need to be added now so we can avoid breaking APIs later. +- There have been requests in the past to support loading files from a wchar_t encoded path. Adding support by pulling + in custom fopen() and wfopen() implementations from dr_flac. +- Have not yet compiled on VC6 so lets do a pass on that to make it easier for us later. +- VC6 is now compiling clean. Time to check GCC and Clang in strict C89 mode. +- Getting compiler errors about some Linux specific code for handling byte swaps. Removed and replaced with a cross- + platform solution. +- From what I can see it looks like our boilerplate is now up to date with dr_flac, dr_wav and dr_mp3. Time to get onto + some actual Opus decoding. +- Looks like a typo in dropus_range_decoder_update() for handling the `fl[k] > 0` case. Changed this, but now getting + a division by zero. Debugging... + - And fixed. The last paragraph in RFC 6716 - Section 4.1.2 is the part I was missing: + After the updates, implemented by ec_dec_update() (entdec.c), the + decoder normalizes the range using the procedure in the next section, + and returns the index k. +- Reading through dropus_stream_decode_packet() to refresh my memory and some stuff needs cleaning up. Important from + now on to distinguish between PCM frames and Opus frames. All variables referring to one of the other needs to be + prefixed with "pcm" or "opus", depending on it's use. Example: opusFrameCount / pcmFrameCount. +- Added infrastructure for handling CELT and Hybrid frames when the time comes. For now still focused on getting SILK + frames working. +- Reviewing SILK decoding logic to refresh my memory and looks like I've misinterpreted the encoding for per-frame + LBRR flags. When there's more than one SILK frame, the first LBRR flag is just used to determine whether or not + the per-frame LBRR flags is present. I was _always_ reading the LBRR flags regardless of the value of the primary + LBRR flag. + +2020/03/30 7:30 AM +================== +- The decoding of SILK frames needs to be put in their own function in order to handle LBRR and regular SILK frames. + Looking at the spec, there's some complicated branching logic for determine what needs to be decoded. I've put in + a rough draft as a start, but needs some work. Will continue on this later. \ No newline at end of file diff --git a/source/engine/thirdparty/quickjs/.github/workflows/ci.yml b/source/engine/thirdparty/quickjs/.github/workflows/ci.yml new file mode 100644 index 0000000..9eab46f --- /dev/null +++ b/source/engine/thirdparty/quickjs/.github/workflows/ci.yml @@ -0,0 +1,166 @@ +name: ci + +on: + pull_request: + paths: + - '**' + - '!.gitignore' + - '!LICENSE' + - '!TODO' + - '!doc/**' + - '!examples/**' + - '.github/workflows/ci.yml' + push: + branches: + - '*' + +jobs: + linux: + name: Linux (Ubuntu) + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Build + run: | + make -j$(getconf _NPROCESSORS_ONLN) CONFIG_WERROR=y + - name: Stats + run: | + ./qjs -qd + - name: Run built-in tests + run: | + make test + - name: Run microbench + run: | + make microbench + + linux-asan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Build + run: | + make -j$(getconf _NPROCESSORS_ONLN) CONFIG_WERROR=y CONFIG_ASAN=y + - name: Run built-in tests + env: + ASAN_OPTIONS: halt_on_error=1 + run: | + make CONFIG_ASAN=y test + + linux-msan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Build + env: + CC: clang + run: | + make -j$(getconf _NPROCESSORS_ONLN) CONFIG_WERROR=y CONFIG_MSAN=y CONFIG_CLANG=y + - name: Run built-in tests + env: + MSAN_OPTIONS: halt_on_error=1 + run: | + make CONFIG_MSAN=y CONFIG_CLANG=y test + + linux-ubsan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Build + run: | + make -j$(getconf _NPROCESSORS_ONLN) CONFIG_WERROR=y CONFIG_UBSAN=y + - name: Run built-in tests + env: + UBSAN_OPTIONS: halt_on_error=1 + run: | + make CONFIG_UBSAN=y test + + macos: + name: macOS + runs-on: macos-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v4 + - name: Build + run: | + make -j$(getconf _NPROCESSORS_ONLN) CONFIG_WERROR=y + - name: Stats + run: | + ./qjs -qd + - name: Run built-in tests + run: | + make test + + macos-asan: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Build + run: | + make -j$(getconf _NPROCESSORS_ONLN) CONFIG_WERROR=y CONFIG_ASAN=y + - name: Run built-in tests + env: + ASAN_OPTIONS: halt_on_error=1 + run: | + make CONFIG_ASAN=y test + + macos-ubsan: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Build + run: | + make -j$(getconf _NPROCESSORS_ONLN) CONFIG_WERROR=y CONFIG_UBSAN=y + - name: Run built-in tests + env: + UBSAN_OPTIONS: halt_on_error=1 + run: | + make CONFIG_UBSAN=y test + + freebsd: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build + test + uses: vmactions/freebsd-vm@v1 + with: + usesh: true + prepare: | + pkg install -y gmake + run: | + gmake + ./qjs -qd + gmake test + + qemu-alpine: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + platform: + - i386 + - arm32v6 + - arm32v7 + - arm64v8 + - s390x + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Get qemu + run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + - name: Run tests on ${{ matrix.platform }} + run: docker run --rm --interactive --mount type=bind,source=$(pwd),target=/host ${{ matrix.platform }}/alpine sh -c "apk add git patch make gcc libc-dev && cd /host && make test" + diff --git a/source/engine/thirdparty/quickjs/.gitignore b/source/engine/thirdparty/quickjs/.gitignore new file mode 100644 index 0000000..5988f92 --- /dev/null +++ b/source/engine/thirdparty/quickjs/.gitignore @@ -0,0 +1,24 @@ +*.a +.obj/ +tests/bjson.so +examples/test_fib +test_fib.c +examples/*.so +examples/hello +examples/hello_module +hello.c +microbench*.txt +qjs +qjsc +qjscalc +qjscalc.c +repl.c +run-test262 +test262 +test262_*.txt +test262o +test262o_*.txt +unicode +unicode_gen +run_octane +run_sunspider_like diff --git a/source/engine/thirdparty/quickjs/Changelog b/source/engine/thirdparty/quickjs/Changelog new file mode 100644 index 0000000..dd099cd --- /dev/null +++ b/source/engine/thirdparty/quickjs/Changelog @@ -0,0 +1,175 @@ +2024-01-13: + +- top-level-await support in modules +- allow 'await' in the REPL +- added Array.prototype.{with,toReversed,toSpliced,toSorted} and +TypedArray.prototype.{with,toReversed,toSorted} +- added String.prototype.isWellFormed and String.prototype.toWellFormed +- added Object.groupBy and Map.groupBy +- added Promise.withResolvers +- class static block +- 'in' operator support for private fields +- optional chaining fixes +- added RegExp 'd' flag +- fixed RegExp zero length match logic +- fixed RegExp case insensitive flag +- added os.sleepAsync(), os.getpid() and os.now() +- added cosmopolitan build +- misc bug fixes + +2023-12-09: + +- added Object.hasOwn, {String|Array|TypedArray}.prototype.at, + {Array|TypedArray}.prototype.findLast{Index} +- BigInt support is enabled even if CONFIG_BIGNUM disabled +- updated to Unicode 15.0.0 +- misc bug fixes + +2021-03-27: + +- faster Array.prototype.push and Array.prototype.unshift +- added JS_UpdateStackTop() +- fixed Windows console +- misc bug fixes + +2020-11-08: + +- improved function parameter initializers +- added std.setenv(), std.unsetenv() and std.getenviron() +- added JS_EvalThis() +- misc bug fixes + +2020-09-06: + +- added logical assignment operators +- added IsHTMLDDA support +- faster for-of loops +- os.Worker now takes a module filename as parameter +- qjsc: added -D option to compile dynamically loaded modules or workers +- misc bug fixes + +2020-07-05: + +- modified JS_GetPrototype() to return a live value +- REPL: support unicode characters larger than 16 bits +- added os.Worker +- improved object serialization +- added std.parseExtJSON +- misc bug fixes + +2020-04-12: + +- added cross realm support +- added AggregateError and Promise.any +- added env, uid and gid options in os.exec() +- misc bug fixes + +2020-03-16: + +- reworked error handling in std and os libraries: suppressed I/O + exceptions in std FILE functions and return a positive errno value + when it is explicit +- output exception messages to stderr +- added std.loadFile(), std.strerror(), std.FILE.prototype.tello() +- added JS_GetRuntimeOpaque(), JS_SetRuntimeOpaque(), JS_NewUint32() +- updated to Unicode 13.0.0 +- misc bug fixes + +2020-01-19: + +- keep CONFIG_BIGNUM in the makefile +- added os.chdir() +- qjs: added -I option +- more memory checks in the bignum operations +- modified operator overloading semantics to be closer to the TC39 + proposal +- suppressed "use bigint" mode. Simplified "use math" mode +- BigDecimal: changed suffix from 'd' to 'm' +- misc bug fixes + +2020-01-05: + +- always compile the bignum code. Added '--bignum' option to qjs. +- added BigDecimal +- added String.prototype.replaceAll +- misc bug fixes + +2019-12-21: + +- added nullish coalescing operator (ES2020) +- added optional chaining (ES2020) +- removed recursions in garbage collector +- test stack overflow in the parser +- improved backtrace logic +- added JS_SetHostPromiseRejectionTracker() +- allow exotic constructors +- improved c++ compatibility +- misc bug fixes + +2019-10-27: + +- added example of C class in a module (examples/test_point.js) +- added JS_GetTypedArrayBuffer() +- misc bug fixes + +2019-09-18: + +- added os.exec and other system calls +- exported JS_ValueToAtom() +- qjsc: added 'qjsc_' prefix to the generated C identifiers +- added cross-compilation support +- misc bug fixes + +2019-09-01: + +- added globalThis +- documented JS_EVAL_FLAG_COMPILE_ONLY +- added import.meta.url and import.meta.main +- added 'debugger' statement +- misc bug fixes + +2019-08-18: + +- added os.realpath, os.getcwd, os.mkdir, os.stat, os.lstat, + os.readlink, os.readdir, os.utimes, std.popen +- module autodetection +- added import.meta +- misc bug fixes + +2019-08-10: + +- added public class fields and private class fields, methods and + accessors (TC39 proposal) +- changed JS_ToCStringLen() prototype +- qjsc: handle '-' in module names and modules with the same filename +- added std.urlGet +- exported JS_GetOwnPropertyNames() and JS_GetOwnProperty() +- exported some bigint C functions +- added support for eshost in run-test262 +- misc bug fixes + +2019-07-28: + +- added dynamic import +- added Promise.allSettled +- added String.prototype.matchAll +- added Object.fromEntries +- reduced number of ticks in await +- added BigInt support in Atomics +- exported JS_NewPromiseCapability() +- misc async function and async generator fixes +- enabled hashbang support by default + +2019-07-21: + +- updated test262 tests +- updated to Unicode version 12.1.0 +- fixed missing Date object in qjsc +- fixed multi-context creation +- misc ES2020 related fixes +- simplified power and division operators in bignum extension +- fixed several crash conditions + +2019-07-09: + +- first public release diff --git a/source/engine/thirdparty/quickjs/LICENSE b/source/engine/thirdparty/quickjs/LICENSE new file mode 100644 index 0000000..2cf449d --- /dev/null +++ b/source/engine/thirdparty/quickjs/LICENSE @@ -0,0 +1,22 @@ +QuickJS Javascript Engine + +Copyright (c) 2017-2021 Fabrice Bellard +Copyright (c) 2017-2021 Charlie Gordon + +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/source/engine/thirdparty/quickjs/Makefile b/source/engine/thirdparty/quickjs/Makefile new file mode 100644 index 0000000..0270a6a --- /dev/null +++ b/source/engine/thirdparty/quickjs/Makefile @@ -0,0 +1,564 @@ +# +# QuickJS Javascript Engine +# +# Copyright (c) 2017-2021 Fabrice Bellard +# Copyright (c) 2017-2021 Charlie Gordon +# +# 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. + +ifeq ($(shell uname -s),Darwin) +CONFIG_DARWIN=y +endif +ifeq ($(shell uname -s),FreeBSD) +CONFIG_FREEBSD=y +endif +# Windows cross compilation from Linux +#CONFIG_WIN32=y +# use link time optimization (smaller and faster executables but slower build) +#CONFIG_LTO=y +# consider warnings as errors (for development) +#CONFIG_WERROR=y +# force 32 bit build for some utilities +#CONFIG_M32=y +# cosmopolitan build (see https://github.com/jart/cosmopolitan) +#CONFIG_COSMO=y + +# installation directory +PREFIX?=/usr/local + +# use the gprof profiler +#CONFIG_PROFILE=y +# use address sanitizer +#CONFIG_ASAN=y +# use memory sanitizer +#CONFIG_MSAN=y +# use UB sanitizer +#CONFIG_UBSAN=y + +# include the code for BigFloat/BigDecimal and math mode +CONFIG_BIGNUM=y + +OBJDIR=.obj + +ifdef CONFIG_ASAN +OBJDIR:=$(OBJDIR)/asan +endif +ifdef CONFIG_MSAN +OBJDIR:=$(OBJDIR)/msan +endif +ifdef CONFIG_UBSAN +OBJDIR:=$(OBJDIR)/ubsan +endif + +ifdef CONFIG_DARWIN +# use clang instead of gcc +CONFIG_CLANG=y +CONFIG_DEFAULT_AR=y +endif +ifdef CONFIG_FREEBSD +# use clang instead of gcc +CONFIG_CLANG=y +CONFIG_DEFAULT_AR=y +CONFIG_LTO= +endif + +ifdef CONFIG_WIN32 + ifdef CONFIG_M32 + CROSS_PREFIX?=i686-w64-mingw32- + else + CROSS_PREFIX?=x86_64-w64-mingw32- + endif + EXE=.exe +else + CROSS_PREFIX?= + EXE= +endif + +ifdef CONFIG_CLANG + HOST_CC=clang + CC=$(CROSS_PREFIX)clang + CFLAGS+=-g -Wall -MMD -MF $(OBJDIR)/$(@F).d + CFLAGS += -Wextra + CFLAGS += -Wno-sign-compare + CFLAGS += -Wno-missing-field-initializers + CFLAGS += -Wundef -Wuninitialized + CFLAGS += -Wunused -Wno-unused-parameter + CFLAGS += -Wwrite-strings + CFLAGS += -Wchar-subscripts -funsigned-char + CFLAGS += -MMD -MF $(OBJDIR)/$(@F).d + ifdef CONFIG_DEFAULT_AR + AR=$(CROSS_PREFIX)ar + else + ifdef CONFIG_LTO + AR=$(CROSS_PREFIX)llvm-ar + else + AR=$(CROSS_PREFIX)ar + endif + endif +else ifdef CONFIG_COSMO + CONFIG_LTO= + HOST_CC=gcc + CC=cosmocc + # cosmocc does not correct support -MF + CFLAGS=-g -Wall #-MMD -MF $(OBJDIR)/$(@F).d + CFLAGS += -Wno-array-bounds -Wno-format-truncation + AR=cosmoar +else + HOST_CC=gcc + CC=$(CROSS_PREFIX)gcc + CFLAGS+=-g -Wall -MMD -MF $(OBJDIR)/$(@F).d + CFLAGS += -Wno-array-bounds -Wno-format-truncation + ifdef CONFIG_LTO + AR=$(CROSS_PREFIX)gcc-ar + else + AR=$(CROSS_PREFIX)ar + endif +endif +STRIP?=$(CROSS_PREFIX)strip +CFLAGS+=-fwrapv # ensure that signed overflows behave as expected +ifdef CONFIG_WERROR +CFLAGS+=-Werror +endif +DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(shell cat VERSION)\" +ifdef CONFIG_BIGNUM +DEFINES+=-DCONFIG_BIGNUM +endif +ifdef CONFIG_WIN32 +DEFINES+=-D__USE_MINGW_ANSI_STDIO # for standard snprintf behavior +endif + +CFLAGS+=$(DEFINES) +CFLAGS_DEBUG=$(CFLAGS) -O0 +CFLAGS_SMALL=$(CFLAGS) -Os +CFLAGS_OPT=$(CFLAGS) -O2 +CFLAGS_NOLTO:=$(CFLAGS_OPT) +ifdef CONFIG_COSMO +LDFLAGS+=-s # better to strip by default +else +LDFLAGS+=-g +endif +ifdef CONFIG_LTO +CFLAGS_SMALL+=-flto +CFLAGS_OPT+=-flto +LDFLAGS+=-flto +endif +ifdef CONFIG_PROFILE +CFLAGS+=-p +LDFLAGS+=-p +endif +ifdef CONFIG_ASAN +CFLAGS+=-fsanitize=address -fno-omit-frame-pointer +LDFLAGS+=-fsanitize=address -fno-omit-frame-pointer +endif +ifdef CONFIG_MSAN +CFLAGS+=-fsanitize=memory -fno-omit-frame-pointer +LDFLAGS+=-fsanitize=memory -fno-omit-frame-pointer +endif +ifdef CONFIG_UBSAN +CFLAGS+=-fsanitize=undefined -fno-omit-frame-pointer +LDFLAGS+=-fsanitize=undefined -fno-omit-frame-pointer +endif +ifdef CONFIG_WIN32 +LDEXPORT= +else +LDEXPORT=-rdynamic +endif + +ifndef CONFIG_COSMO +ifndef CONFIG_DARWIN +CONFIG_SHARED_LIBS=y # building shared libraries is supported +endif +endif + +PROGS=qjs$(EXE) qjsc$(EXE) run-test262 +ifneq ($(CROSS_PREFIX),) +QJSC_CC=gcc +QJSC=./host-qjsc +PROGS+=$(QJSC) +else +QJSC_CC=$(CC) +QJSC=./qjsc$(EXE) +endif +ifndef CONFIG_WIN32 +PROGS+=qjscalc +endif +ifdef CONFIG_M32 +PROGS+=qjs32 qjs32_s +endif +PROGS+=libquickjs.a +ifdef CONFIG_LTO +PROGS+=libquickjs.lto.a +endif + +# examples +ifeq ($(CROSS_PREFIX),) +ifndef CONFIG_ASAN +ifndef CONFIG_MSAN +ifndef CONFIG_UBSAN +PROGS+=examples/hello examples/hello_module examples/test_fib +ifdef CONFIG_SHARED_LIBS +PROGS+=examples/fib.so examples/point.so +endif +endif +endif +endif +endif + +all: $(OBJDIR) $(OBJDIR)/quickjs.check.o $(OBJDIR)/qjs.check.o $(PROGS) + +QJS_LIB_OBJS=$(OBJDIR)/quickjs.o $(OBJDIR)/libregexp.o $(OBJDIR)/libunicode.o $(OBJDIR)/cutils.o $(OBJDIR)/quickjs-libc.o $(OBJDIR)/libbf.o + +QJS_OBJS=$(OBJDIR)/qjs.o $(OBJDIR)/repl.o $(QJS_LIB_OBJS) +ifdef CONFIG_BIGNUM +QJS_OBJS+=$(OBJDIR)/qjscalc.o +endif + +HOST_LIBS=-lm -ldl -lpthread +LIBS=-lm +ifndef CONFIG_WIN32 +LIBS+=-ldl -lpthread +endif +LIBS+=$(EXTRA_LIBS) + +$(OBJDIR): + mkdir -p $(OBJDIR) $(OBJDIR)/examples $(OBJDIR)/tests + +qjs$(EXE): $(QJS_OBJS) + $(CC) $(LDFLAGS) $(LDEXPORT) -o $@ $^ $(LIBS) + +qjs-debug$(EXE): $(patsubst %.o, %.debug.o, $(QJS_OBJS)) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +qjsc$(EXE): $(OBJDIR)/qjsc.o $(QJS_LIB_OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +ifneq ($(CROSS_PREFIX),) + +$(QJSC): $(OBJDIR)/qjsc.host.o \ + $(patsubst %.o, %.host.o, $(QJS_LIB_OBJS)) + $(HOST_CC) $(LDFLAGS) -o $@ $^ $(HOST_LIBS) + +endif #CROSS_PREFIX + +QJSC_DEFINES:=-DCONFIG_CC=\"$(QJSC_CC)\" -DCONFIG_PREFIX=\"$(PREFIX)\" +ifdef CONFIG_LTO +QJSC_DEFINES+=-DCONFIG_LTO +endif +QJSC_HOST_DEFINES:=-DCONFIG_CC=\"$(HOST_CC)\" -DCONFIG_PREFIX=\"$(PREFIX)\" + +$(OBJDIR)/qjsc.o: CFLAGS+=$(QJSC_DEFINES) +$(OBJDIR)/qjsc.host.o: CFLAGS+=$(QJSC_HOST_DEFINES) + +qjs32: $(patsubst %.o, %.m32.o, $(QJS_OBJS)) + $(CC) -m32 $(LDFLAGS) $(LDEXPORT) -o $@ $^ $(LIBS) + +qjs32_s: $(patsubst %.o, %.m32s.o, $(QJS_OBJS)) + $(CC) -m32 $(LDFLAGS) -o $@ $^ $(LIBS) + @size $@ + +qjscalc: qjs + ln -sf $< $@ + +ifdef CONFIG_LTO +LTOEXT=.lto +else +LTOEXT= +endif + +libquickjs$(LTOEXT).a: $(QJS_LIB_OBJS) + $(AR) rcs $@ $^ + +ifdef CONFIG_LTO +libquickjs.a: $(patsubst %.o, %.nolto.o, $(QJS_LIB_OBJS)) + $(AR) rcs $@ $^ +endif # CONFIG_LTO + +repl.c: $(QJSC) repl.js + $(QJSC) -c -o $@ -m repl.js + +qjscalc.c: $(QJSC) qjscalc.js + $(QJSC) -fbignum -c -o $@ qjscalc.js + +ifneq ($(wildcard unicode/UnicodeData.txt),) +$(OBJDIR)/libunicode.o $(OBJDIR)/libunicode.m32.o $(OBJDIR)/libunicode.m32s.o \ + $(OBJDIR)/libunicode.nolto.o: libunicode-table.h + +libunicode-table.h: unicode_gen + ./unicode_gen unicode $@ +endif + +run-test262: $(OBJDIR)/run-test262.o $(QJS_LIB_OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +run-test262-debug: $(patsubst %.o, %.debug.o, $(OBJDIR)/run-test262.o $(QJS_LIB_OBJS)) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +run-test262-32: $(patsubst %.o, %.m32.o, $(OBJDIR)/run-test262.o $(QJS_LIB_OBJS)) + $(CC) -m32 $(LDFLAGS) -o $@ $^ $(LIBS) + +# object suffix order: nolto, [m32|m32s] + +$(OBJDIR)/%.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS_OPT) -c -o $@ $< + +$(OBJDIR)/%.host.o: %.c | $(OBJDIR) + $(HOST_CC) $(CFLAGS_OPT) -c -o $@ $< + +$(OBJDIR)/%.pic.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS_OPT) -fPIC -DJS_SHARED_LIBRARY -c -o $@ $< + +$(OBJDIR)/%.nolto.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS_NOLTO) -c -o $@ $< + +$(OBJDIR)/%.m32.o: %.c | $(OBJDIR) + $(CC) -m32 $(CFLAGS_OPT) -c -o $@ $< + +$(OBJDIR)/%.m32s.o: %.c | $(OBJDIR) + $(CC) -m32 $(CFLAGS_SMALL) -c -o $@ $< + +$(OBJDIR)/%.debug.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS_DEBUG) -c -o $@ $< + +$(OBJDIR)/%.check.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS) -DCONFIG_CHECK_JSVALUE -c -o $@ $< + +regexp_test: libregexp.c libunicode.c cutils.c + $(CC) $(LDFLAGS) $(CFLAGS) -DTEST -o $@ libregexp.c libunicode.c cutils.c $(LIBS) + +unicode_gen: $(OBJDIR)/unicode_gen.host.o $(OBJDIR)/cutils.host.o libunicode.c unicode_gen_def.h + $(HOST_CC) $(LDFLAGS) $(CFLAGS) -o $@ $(OBJDIR)/unicode_gen.host.o $(OBJDIR)/cutils.host.o + +clean: + rm -f repl.c qjscalc.c out.c + rm -f *.a *.o *.d *~ unicode_gen regexp_test $(PROGS) + rm -f hello.c test_fib.c + rm -f examples/*.so tests/*.so + rm -rf $(OBJDIR)/ *.dSYM/ qjs-debug + rm -rf run-test262-debug run-test262-32 + rm -f run_octane run_sunspider_like + +install: all + mkdir -p "$(DESTDIR)$(PREFIX)/bin" + $(STRIP) qjs$(EXE) qjsc$(EXE) + install -m755 qjs$(EXE) qjsc$(EXE) "$(DESTDIR)$(PREFIX)/bin" + ln -sf qjs$(EXE) "$(DESTDIR)$(PREFIX)/bin/qjscalc$(EXE)" + mkdir -p "$(DESTDIR)$(PREFIX)/lib/quickjs" + install -m644 libquickjs.a "$(DESTDIR)$(PREFIX)/lib/quickjs" +ifdef CONFIG_LTO + install -m644 libquickjs.lto.a "$(DESTDIR)$(PREFIX)/lib/quickjs" +endif + mkdir -p "$(DESTDIR)$(PREFIX)/include/quickjs" + install -m644 quickjs.h quickjs-libc.h "$(DESTDIR)$(PREFIX)/include/quickjs" + +############################################################################### +# examples + +# example of static JS compilation +HELLO_SRCS=examples/hello.js +HELLO_OPTS=-fno-string-normalize -fno-map -fno-promise -fno-typedarray \ + -fno-typedarray -fno-regexp -fno-json -fno-eval -fno-proxy \ + -fno-date -fno-module-loader -fno-bigint + +hello.c: $(QJSC) $(HELLO_SRCS) + $(QJSC) -e $(HELLO_OPTS) -o $@ $(HELLO_SRCS) + +ifdef CONFIG_M32 +examples/hello: $(OBJDIR)/hello.m32s.o $(patsubst %.o, %.m32s.o, $(QJS_LIB_OBJS)) + $(CC) -m32 $(LDFLAGS) -o $@ $^ $(LIBS) +else +examples/hello: $(OBJDIR)/hello.o $(QJS_LIB_OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) +endif + +# example of static JS compilation with modules +HELLO_MODULE_SRCS=examples/hello_module.js +HELLO_MODULE_OPTS=-fno-string-normalize -fno-map -fno-promise -fno-typedarray \ + -fno-typedarray -fno-regexp -fno-json -fno-eval -fno-proxy \ + -fno-date -m +examples/hello_module: $(QJSC) libquickjs$(LTOEXT).a $(HELLO_MODULE_SRCS) + $(QJSC) $(HELLO_MODULE_OPTS) -o $@ $(HELLO_MODULE_SRCS) + +# use of an external C module (static compilation) + +test_fib.c: $(QJSC) examples/test_fib.js + $(QJSC) -e -M examples/fib.so,fib -m -o $@ examples/test_fib.js + +examples/test_fib: $(OBJDIR)/test_fib.o $(OBJDIR)/examples/fib.o libquickjs$(LTOEXT).a + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +examples/fib.so: $(OBJDIR)/examples/fib.pic.o + $(CC) $(LDFLAGS) -shared -o $@ $^ + +examples/point.so: $(OBJDIR)/examples/point.pic.o + $(CC) $(LDFLAGS) -shared -o $@ $^ + +############################################################################### +# documentation + +DOCS=doc/quickjs.pdf doc/quickjs.html doc/jsbignum.pdf doc/jsbignum.html + +build_doc: $(DOCS) + +clean_doc: + rm -f $(DOCS) + +doc/%.pdf: doc/%.texi + texi2pdf --clean -o $@ -q $< + +doc/%.html.pre: doc/%.texi + makeinfo --html --no-headers --no-split --number-sections -o $@ $< + +doc/%.html: doc/%.html.pre + sed -e 's||\n|' < $< > $@ + +############################################################################### +# tests + +ifdef CONFIG_SHARED_LIBS +test: tests/bjson.so examples/point.so +endif +ifdef CONFIG_M32 +test: qjs32 +endif + +test: qjs + ./qjs tests/test_closure.js + ./qjs tests/test_language.js + ./qjs --std tests/test_builtin.js + ./qjs tests/test_loop.js + ./qjs tests/test_bignum.js + ./qjs tests/test_std.js + ./qjs tests/test_worker.js +ifdef CONFIG_SHARED_LIBS +ifdef CONFIG_BIGNUM + ./qjs --bignum tests/test_bjson.js +else + ./qjs tests/test_bjson.js +endif + ./qjs examples/test_point.js +endif +ifdef CONFIG_BIGNUM + ./qjs --bignum tests/test_op_overloading.js + ./qjs --bignum tests/test_bigfloat.js + ./qjs --qjscalc tests/test_qjscalc.js +endif +ifdef CONFIG_M32 + ./qjs32 tests/test_closure.js + ./qjs32 tests/test_language.js + ./qjs32 --std tests/test_builtin.js + ./qjs32 tests/test_loop.js + ./qjs32 tests/test_bignum.js + ./qjs32 tests/test_std.js + ./qjs32 tests/test_worker.js +ifdef CONFIG_BIGNUM + ./qjs32 --bignum tests/test_op_overloading.js + ./qjs32 --bignum tests/test_bigfloat.js + ./qjs32 --qjscalc tests/test_qjscalc.js +endif +endif + +stats: qjs qjs32 + ./qjs -qd + ./qjs32 -qd + +microbench: qjs + ./qjs --std tests/microbench.js + +microbench-32: qjs32 + ./qjs32 --std tests/microbench.js + +ifeq ($(wildcard test262o/tests.txt),) +test2o test2o-32 test2o-update: + @echo test262o tests not installed +else +# ES5 tests (obsolete) +test2o: run-test262 + time ./run-test262 -t -m -c test262o.conf + +test2o-32: run-test262-32 + time ./run-test262-32 -t -m -c test262o.conf + +test2o-update: run-test262 + ./run-test262 -t -u -c test262o.conf +endif + +ifeq ($(wildcard test262o/tests.txt),) +test2 test2-32 test2-update test2-default test2-check: + @echo test262 tests not installed +else +# Test262 tests +test2-default: run-test262 + time ./run-test262 -t -m -c test262.conf + +test2: run-test262 + time ./run-test262 -t -m -c test262.conf -a + +test2-32: run-test262-32 + time ./run-test262-32 -t -m -c test262.conf -a + +test2-update: run-test262 + ./run-test262 -t -u -c test262.conf -a + +test2-check: run-test262 + time ./run-test262 -t -m -c test262.conf -E -a +endif + +testall: all test microbench test2o test2 + +testall-32: all test-32 microbench-32 test2o-32 test2-32 + +testall-complete: testall testall-32 + +node-test: + node tests/test_closure.js + node tests/test_language.js + node tests/test_builtin.js + node tests/test_loop.js + node tests/test_bignum.js + +node-microbench: + node tests/microbench.js -s microbench-node.txt + node --jitless tests/microbench.js -s microbench-node-jitless.txt + +bench-v8: qjs + make -C tests/bench-v8 + ./qjs -d tests/bench-v8/combined.js + +node-bench-v8: + make -C tests/bench-v8 + node --jitless tests/bench-v8/combined.js + +tests/bjson.so: $(OBJDIR)/tests/bjson.pic.o + $(CC) $(LDFLAGS) -shared -o $@ $^ $(LIBS) + +BENCHMARKDIR=../quickjs-benchmarks + +run_sunspider_like: $(BENCHMARKDIR)/run_sunspider_like.c + $(CC) $(CFLAGS) $(LDFLAGS) -DNO_INCLUDE_DIR -I. -o $@ $< libquickjs$(LTOEXT).a $(LIBS) + +run_octane: $(BENCHMARKDIR)/run_octane.c + $(CC) $(CFLAGS) $(LDFLAGS) -DNO_INCLUDE_DIR -I. -o $@ $< libquickjs$(LTOEXT).a $(LIBS) + +benchmarks: run_sunspider_like run_octane + ./run_sunspider_like $(BENCHMARKDIR)/kraken-1.0/ + ./run_sunspider_like $(BENCHMARKDIR)/kraken-1.1/ + ./run_sunspider_like $(BENCHMARKDIR)/sunspider-1.0/ + ./run_octane $(BENCHMARKDIR)/ + +-include $(wildcard $(OBJDIR)/*.d) diff --git a/source/engine/thirdparty/quickjs/TODO b/source/engine/thirdparty/quickjs/TODO new file mode 100644 index 0000000..f243dee --- /dev/null +++ b/source/engine/thirdparty/quickjs/TODO @@ -0,0 +1,67 @@ +Misc ideas: +- use custom printf to avoid compatibility issues with floating point numbers +- consistent naming for preprocessor defines +- unify coding style and naming conventions +- use names from the ECMA spec in library implementation +- use byte code emitters with typed arguments (for clarity) +- use 2 bytecode DynBufs in JSFunctionDef, one for reading, one for writing + and use the same wrappers in all phases +- use more generic method for line numbers in resolve_variables and resolve_labels +- use custom timezone support to avoid C library compatibility issues + +Memory: +- use memory pools for objects, etc? +- test border cases for max number of atoms, object properties, string length +- add emergency malloc mode for out of memory exceptions. +- test all DynBuf memory errors +- test all js_realloc memory errors +- improve JS_ComputeMemoryUsage() with more info + +Built-in standard library: +- BSD sockets +- modules: use realpath in module name normalizer and put it in quickjs-libc +- modules: if no ".", use a well known module loading path ? +- get rid of __loadScript, use more common name + +REPL: +- debugger +- readline: support MS Windows terminal +- readline: handle dynamic terminal resizing +- readline: handle double width unicode characters +- multiline editing +- runtime object and function inspectors +- interactive object browser +- use more generic approach to display evaluation results +- improve directive handling: dispatch, colorize, completion... +- save history +- close all predefined methods in repl.js and jscalc.js + +Optimization ideas: +- 64-bit atoms in 64-bit mode ? +- 64-bit small bigint in 64-bit mode ? +- reuse stack slots for disjoint scopes, if strip +- add heuristic to avoid some cycles in closures +- small String (0-2 charcodes) with immediate storage +- perform static string concatenation at compile time +- optimize string concatenation with ropes or miniropes? +- add implicit numeric strings for Uint32 numbers? +- optimize `s += a + b`, `s += a.b` and similar simple expressions +- ensure string canonical representation and optimise comparisons and hashes? +- remove JSObject.first_weak_ref, use bit+context based hashed array for weak references +- property access optimization on the global object, functions, + prototypes and special non extensible objects. +- create object literals with the correct length by backpatching length argument +- remove redundant set_loc_uninitialized/check_uninitialized opcodes +- peephole optim: push_atom_value, to_propkey -> push_atom_value +- peephole optim: put_loc x, get_loc_check x -> set_loc x +- convert slow array to fast array when all properties != length are numeric +- optimize destructuring assignments for global and local variables +- implement some form of tail-call-optimization +- optimize OP_apply +- optimize f(...b) + +Test262o: 0/11262 errors, 463 excluded +Test262o commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch) + +Result: 8/76947 errors, 1497 excluded, 8117 skipped +Test262 commit: 6cbb6da9473c56d95358d8e679c5a6d2b4574efb diff --git a/source/engine/thirdparty/quickjs/VERSION b/source/engine/thirdparty/quickjs/VERSION new file mode 100644 index 0000000..e32e065 --- /dev/null +++ b/source/engine/thirdparty/quickjs/VERSION @@ -0,0 +1 @@ +2024-02-14 diff --git a/source/engine/thirdparty/quickjs/cutils.c b/source/engine/thirdparty/quickjs/cutils.c new file mode 100644 index 0000000..c0aacef --- /dev/null +++ b/source/engine/thirdparty/quickjs/cutils.c @@ -0,0 +1,631 @@ +/* + * C utilities + * + * Copyright (c) 2017 Fabrice Bellard + * Copyright (c) 2018 Charlie Gordon + * + * 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. + */ +#include +#include +#include +#include + +#include "cutils.h" + +void pstrcpy(char *buf, int buf_size, const char *str) +{ + int c; + char *q = buf; + + if (buf_size <= 0) + return; + + for(;;) { + c = *str++; + if (c == 0 || q >= buf + buf_size - 1) + break; + *q++ = c; + } + *q = '\0'; +} + +/* strcat and truncate. */ +char *pstrcat(char *buf, int buf_size, const char *s) +{ + int len; + len = strlen(buf); + if (len < buf_size) + pstrcpy(buf + len, buf_size - len, s); + return buf; +} + +int strstart(const char *str, const char *val, const char **ptr) +{ + const char *p, *q; + p = str; + q = val; + while (*q != '\0') { + if (*p != *q) + return 0; + p++; + q++; + } + if (ptr) + *ptr = p; + return 1; +} + +int has_suffix(const char *str, const char *suffix) +{ + size_t len = strlen(str); + size_t slen = strlen(suffix); + return (len >= slen && !memcmp(str + len - slen, suffix, slen)); +} + +/* Dynamic buffer package */ + +static void *dbuf_default_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func) +{ + memset(s, 0, sizeof(*s)); + if (!realloc_func) + realloc_func = dbuf_default_realloc; + s->opaque = opaque; + s->realloc_func = realloc_func; +} + +void dbuf_init(DynBuf *s) +{ + dbuf_init2(s, NULL, NULL); +} + +/* return < 0 if error */ +int dbuf_realloc(DynBuf *s, size_t new_size) +{ + size_t size; + uint8_t *new_buf; + if (new_size > s->allocated_size) { + if (s->error) + return -1; + size = s->allocated_size * 3 / 2; + if (size > new_size) + new_size = size; + new_buf = s->realloc_func(s->opaque, s->buf, new_size); + if (!new_buf) { + s->error = TRUE; + return -1; + } + s->buf = new_buf; + s->allocated_size = new_size; + } + return 0; +} + +int dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, size_t len) +{ + size_t end; + end = offset + len; + if (dbuf_realloc(s, end)) + return -1; + memcpy(s->buf + offset, data, len); + if (end > s->size) + s->size = end; + return 0; +} + +int dbuf_put(DynBuf *s, const uint8_t *data, size_t len) +{ + if (unlikely((s->size + len) > s->allocated_size)) { + if (dbuf_realloc(s, s->size + len)) + return -1; + } + memcpy_no_ub(s->buf + s->size, data, len); + s->size += len; + return 0; +} + +int dbuf_put_self(DynBuf *s, size_t offset, size_t len) +{ + if (unlikely((s->size + len) > s->allocated_size)) { + if (dbuf_realloc(s, s->size + len)) + return -1; + } + memcpy(s->buf + s->size, s->buf + offset, len); + s->size += len; + return 0; +} + +int dbuf_putc(DynBuf *s, uint8_t c) +{ + return dbuf_put(s, &c, 1); +} + +int dbuf_putstr(DynBuf *s, const char *str) +{ + return dbuf_put(s, (const uint8_t *)str, strlen(str)); +} + +int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s, + const char *fmt, ...) +{ + va_list ap; + char buf[128]; + int len; + + va_start(ap, fmt); + len = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (len < sizeof(buf)) { + /* fast case */ + return dbuf_put(s, (uint8_t *)buf, len); + } else { + if (dbuf_realloc(s, s->size + len + 1)) + return -1; + va_start(ap, fmt); + vsnprintf((char *)(s->buf + s->size), s->allocated_size - s->size, + fmt, ap); + va_end(ap); + s->size += len; + } + return 0; +} + +void dbuf_free(DynBuf *s) +{ + /* we test s->buf as a fail safe to avoid crashing if dbuf_free() + is called twice */ + if (s->buf) { + s->realloc_func(s->opaque, s->buf, 0); + } + memset(s, 0, sizeof(*s)); +} + +/* Note: at most 31 bits are encoded. At most UTF8_CHAR_LEN_MAX bytes + are output. */ +int unicode_to_utf8(uint8_t *buf, unsigned int c) +{ + uint8_t *q = buf; + + if (c < 0x80) { + *q++ = c; + } else { + if (c < 0x800) { + *q++ = (c >> 6) | 0xc0; + } else { + if (c < 0x10000) { + *q++ = (c >> 12) | 0xe0; + } else { + if (c < 0x00200000) { + *q++ = (c >> 18) | 0xf0; + } else { + if (c < 0x04000000) { + *q++ = (c >> 24) | 0xf8; + } else if (c < 0x80000000) { + *q++ = (c >> 30) | 0xfc; + *q++ = ((c >> 24) & 0x3f) | 0x80; + } else { + return 0; + } + *q++ = ((c >> 18) & 0x3f) | 0x80; + } + *q++ = ((c >> 12) & 0x3f) | 0x80; + } + *q++ = ((c >> 6) & 0x3f) | 0x80; + } + *q++ = (c & 0x3f) | 0x80; + } + return q - buf; +} + +static const unsigned int utf8_min_code[5] = { + 0x80, 0x800, 0x10000, 0x00200000, 0x04000000, +}; + +static const unsigned char utf8_first_code_mask[5] = { + 0x1f, 0xf, 0x7, 0x3, 0x1, +}; + +/* return -1 if error. *pp is not updated in this case. max_len must + be >= 1. The maximum length for a UTF8 byte sequence is 6 bytes. */ +int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp) +{ + int l, c, b, i; + + c = *p++; + if (c < 0x80) { + *pp = p; + return c; + } + switch(c) { + case 0xc0: case 0xc1: case 0xc2: case 0xc3: + case 0xc4: case 0xc5: case 0xc6: case 0xc7: + case 0xc8: case 0xc9: case 0xca: case 0xcb: + case 0xcc: case 0xcd: case 0xce: case 0xcf: + case 0xd0: case 0xd1: case 0xd2: case 0xd3: + case 0xd4: case 0xd5: case 0xd6: case 0xd7: + case 0xd8: case 0xd9: case 0xda: case 0xdb: + case 0xdc: case 0xdd: case 0xde: case 0xdf: + l = 1; + break; + case 0xe0: case 0xe1: case 0xe2: case 0xe3: + case 0xe4: case 0xe5: case 0xe6: case 0xe7: + case 0xe8: case 0xe9: case 0xea: case 0xeb: + case 0xec: case 0xed: case 0xee: case 0xef: + l = 2; + break; + case 0xf0: case 0xf1: case 0xf2: case 0xf3: + case 0xf4: case 0xf5: case 0xf6: case 0xf7: + l = 3; + break; + case 0xf8: case 0xf9: case 0xfa: case 0xfb: + l = 4; + break; + case 0xfc: case 0xfd: + l = 5; + break; + default: + return -1; + } + /* check that we have enough characters */ + if (l > (max_len - 1)) + return -1; + c &= utf8_first_code_mask[l - 1]; + for(i = 0; i < l; i++) { + b = *p++; + if (b < 0x80 || b >= 0xc0) + return -1; + c = (c << 6) | (b & 0x3f); + } + if (c < utf8_min_code[l - 1]) + return -1; + *pp = p; + return c; +} + +#if 0 + +#if defined(EMSCRIPTEN) || defined(__ANDROID__) + +static void *rqsort_arg; +static int (*rqsort_cmp)(const void *, const void *, void *); + +static int rqsort_cmp2(const void *p1, const void *p2) +{ + return rqsort_cmp(p1, p2, rqsort_arg); +} + +/* not reentrant, but not needed with emscripten */ +void rqsort(void *base, size_t nmemb, size_t size, + int (*cmp)(const void *, const void *, void *), + void *arg) +{ + rqsort_arg = arg; + rqsort_cmp = cmp; + qsort(base, nmemb, size, rqsort_cmp2); +} + +#endif + +#else + +typedef void (*exchange_f)(void *a, void *b, size_t size); +typedef int (*cmp_f)(const void *, const void *, void *opaque); + +static void exchange_bytes(void *a, void *b, size_t size) { + uint8_t *ap = (uint8_t *)a; + uint8_t *bp = (uint8_t *)b; + + while (size-- != 0) { + uint8_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_byte(void *a, void *b, size_t size) { + uint8_t *ap = (uint8_t *)a; + uint8_t *bp = (uint8_t *)b; + uint8_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int16s(void *a, void *b, size_t size) { + uint16_t *ap = (uint16_t *)a; + uint16_t *bp = (uint16_t *)b; + + for (size /= sizeof(uint16_t); size-- != 0;) { + uint16_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int16(void *a, void *b, size_t size) { + uint16_t *ap = (uint16_t *)a; + uint16_t *bp = (uint16_t *)b; + uint16_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int32s(void *a, void *b, size_t size) { + uint32_t *ap = (uint32_t *)a; + uint32_t *bp = (uint32_t *)b; + + for (size /= sizeof(uint32_t); size-- != 0;) { + uint32_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int32(void *a, void *b, size_t size) { + uint32_t *ap = (uint32_t *)a; + uint32_t *bp = (uint32_t *)b; + uint32_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int64s(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + + for (size /= sizeof(uint64_t); size-- != 0;) { + uint64_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int64(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + uint64_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int128s(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + + for (size /= sizeof(uint64_t) * 2; size-- != 0; ap += 2, bp += 2) { + uint64_t t = ap[0]; + uint64_t u = ap[1]; + ap[0] = bp[0]; + ap[1] = bp[1]; + bp[0] = t; + bp[1] = u; + } +} + +static void exchange_one_int128(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + uint64_t t = ap[0]; + uint64_t u = ap[1]; + ap[0] = bp[0]; + ap[1] = bp[1]; + bp[0] = t; + bp[1] = u; +} + +static inline exchange_f exchange_func(const void *base, size_t size) { + switch (((uintptr_t)base | (uintptr_t)size) & 15) { + case 0: + if (size == sizeof(uint64_t) * 2) + return exchange_one_int128; + else + return exchange_int128s; + case 8: + if (size == sizeof(uint64_t)) + return exchange_one_int64; + else + return exchange_int64s; + case 4: + case 12: + if (size == sizeof(uint32_t)) + return exchange_one_int32; + else + return exchange_int32s; + case 2: + case 6: + case 10: + case 14: + if (size == sizeof(uint16_t)) + return exchange_one_int16; + else + return exchange_int16s; + default: + if (size == 1) + return exchange_one_byte; + else + return exchange_bytes; + } +} + +static void heapsortx(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque) +{ + uint8_t *basep = (uint8_t *)base; + size_t i, n, c, r; + exchange_f swap = exchange_func(base, size); + + if (nmemb > 1) { + i = (nmemb / 2) * size; + n = nmemb * size; + + while (i > 0) { + i -= size; + for (r = i; (c = r * 2 + size) < n; r = c) { + if (c < n - size && cmp(basep + c, basep + c + size, opaque) <= 0) + c += size; + if (cmp(basep + r, basep + c, opaque) > 0) + break; + swap(basep + r, basep + c, size); + } + } + for (i = n - size; i > 0; i -= size) { + swap(basep, basep + i, size); + + for (r = 0; (c = r * 2 + size) < i; r = c) { + if (c < i - size && cmp(basep + c, basep + c + size, opaque) <= 0) + c += size; + if (cmp(basep + r, basep + c, opaque) > 0) + break; + swap(basep + r, basep + c, size); + } + } + } +} + +static inline void *med3(void *a, void *b, void *c, cmp_f cmp, void *opaque) +{ + return cmp(a, b, opaque) < 0 ? + (cmp(b, c, opaque) < 0 ? b : (cmp(a, c, opaque) < 0 ? c : a )) : + (cmp(b, c, opaque) > 0 ? b : (cmp(a, c, opaque) < 0 ? a : c )); +} + +/* pointer based version with local stack and insertion sort threshhold */ +void rqsort(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque) +{ + struct { uint8_t *base; size_t count; int depth; } stack[50], *sp = stack; + uint8_t *ptr, *pi, *pj, *plt, *pgt, *top, *m; + size_t m4, i, lt, gt, span, span2; + int c, depth; + exchange_f swap = exchange_func(base, size); + exchange_f swap_block = exchange_func(base, size | 128); + + if (nmemb < 2 || size <= 0) + return; + + sp->base = (uint8_t *)base; + sp->count = nmemb; + sp->depth = 0; + sp++; + + while (sp > stack) { + sp--; + ptr = sp->base; + nmemb = sp->count; + depth = sp->depth; + + while (nmemb > 6) { + if (++depth > 50) { + /* depth check to ensure worst case logarithmic time */ + heapsortx(ptr, nmemb, size, cmp, opaque); + nmemb = 0; + break; + } + /* select median of 3 from 1/4, 1/2, 3/4 positions */ + /* should use median of 5 or 9? */ + m4 = (nmemb >> 2) * size; + m = med3(ptr + m4, ptr + 2 * m4, ptr + 3 * m4, cmp, opaque); + swap(ptr, m, size); /* move the pivot to the start or the array */ + i = lt = 1; + pi = plt = ptr + size; + gt = nmemb; + pj = pgt = top = ptr + nmemb * size; + for (;;) { + while (pi < pj && (c = cmp(ptr, pi, opaque)) >= 0) { + if (c == 0) { + swap(plt, pi, size); + lt++; + plt += size; + } + i++; + pi += size; + } + while (pi < (pj -= size) && (c = cmp(ptr, pj, opaque)) <= 0) { + if (c == 0) { + gt--; + pgt -= size; + swap(pgt, pj, size); + } + } + if (pi >= pj) + break; + swap(pi, pj, size); + i++; + pi += size; + } + /* array has 4 parts: + * from 0 to lt excluded: elements identical to pivot + * from lt to pi excluded: elements smaller than pivot + * from pi to gt excluded: elements greater than pivot + * from gt to n excluded: elements identical to pivot + */ + /* move elements identical to pivot in the middle of the array: */ + /* swap values in ranges [0..lt[ and [i-lt..i[ + swapping the smallest span between lt and i-lt is sufficient + */ + span = plt - ptr; + span2 = pi - plt; + lt = i - lt; + if (span > span2) + span = span2; + swap_block(ptr, pi - span, span); + /* swap values in ranges [gt..top[ and [i..top-(top-gt)[ + swapping the smallest span between top-gt and gt-i is sufficient + */ + span = top - pgt; + span2 = pgt - pi; + pgt = top - span2; + gt = nmemb - (gt - i); + if (span > span2) + span = span2; + swap_block(pi, top - span, span); + + /* now array has 3 parts: + * from 0 to lt excluded: elements smaller than pivot + * from lt to gt excluded: elements identical to pivot + * from gt to n excluded: elements greater than pivot + */ + /* stack the larger segment and keep processing the smaller one + to minimize stack use for pathological distributions */ + if (lt > nmemb - gt) { + sp->base = ptr; + sp->count = lt; + sp->depth = depth; + sp++; + ptr = pgt; + nmemb -= gt; + } else { + sp->base = pgt; + sp->count = nmemb - gt; + sp->depth = depth; + sp++; + nmemb = lt; + } + } + /* Use insertion sort for small fragments */ + for (pi = ptr + size, top = ptr + nmemb * size; pi < top; pi += size) { + for (pj = pi; pj > ptr && cmp(pj - size, pj, opaque) > 0; pj -= size) + swap(pj, pj - size, size); + } + } +} + +#endif diff --git a/source/engine/thirdparty/quickjs/cutils.h b/source/engine/thirdparty/quickjs/cutils.h new file mode 100644 index 0000000..11246e3 --- /dev/null +++ b/source/engine/thirdparty/quickjs/cutils.h @@ -0,0 +1,341 @@ +/* + * C utilities + * + * Copyright (c) 2017 Fabrice Bellard + * Copyright (c) 2018 Charlie Gordon + * + * 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. + */ +#ifndef CUTILS_H +#define CUTILS_H + +#include +#include +#include + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#define force_inline inline __attribute__((always_inline)) +#define no_inline __attribute__((noinline)) +#define __maybe_unused __attribute__((unused)) + +#define xglue(x, y) x ## y +#define glue(x, y) xglue(x, y) +#define stringify(s) tostring(s) +#define tostring(s) #s + +#ifndef offsetof +#define offsetof(type, field) ((size_t) &((type *)0)->field) +#endif +#ifndef countof +#define countof(x) (sizeof(x) / sizeof((x)[0])) +#endif +#ifndef container_of +/* return the pointer of type 'type *' containing 'ptr' as field 'member' */ +#define container_of(ptr, type, member) ((type *)((uint8_t *)(ptr) - offsetof(type, member))) +#endif + +typedef int BOOL; + +#ifndef FALSE +enum { + FALSE = 0, + TRUE = 1, +}; +#endif + +void pstrcpy(char *buf, int buf_size, const char *str); +char *pstrcat(char *buf, int buf_size, const char *s); +int strstart(const char *str, const char *val, const char **ptr); +int has_suffix(const char *str, const char *suffix); + +/* Prevent UB when n == 0 and (src == NULL or dest == NULL) */ +static inline void memcpy_no_ub(void *dest, const void *src, size_t n) { + if (n) + memcpy(dest, src, n); +} + +static inline int max_int(int a, int b) +{ + if (a > b) + return a; + else + return b; +} + +static inline int min_int(int a, int b) +{ + if (a < b) + return a; + else + return b; +} + +static inline uint32_t max_uint32(uint32_t a, uint32_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline uint32_t min_uint32(uint32_t a, uint32_t b) +{ + if (a < b) + return a; + else + return b; +} + +static inline int64_t max_int64(int64_t a, int64_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline int64_t min_int64(int64_t a, int64_t b) +{ + if (a < b) + return a; + else + return b; +} + +/* WARNING: undefined if a = 0 */ +static inline int clz32(unsigned int a) +{ + return __builtin_clz(a); +} + +/* WARNING: undefined if a = 0 */ +static inline int clz64(uint64_t a) +{ + return __builtin_clzll(a); +} + +/* WARNING: undefined if a = 0 */ +static inline int ctz32(unsigned int a) +{ + return __builtin_ctz(a); +} + +/* WARNING: undefined if a = 0 */ +static inline int ctz64(uint64_t a) +{ + return __builtin_ctzll(a); +} + +struct __attribute__((packed)) packed_u64 { + uint64_t v; +}; + +struct __attribute__((packed)) packed_u32 { + uint32_t v; +}; + +struct __attribute__((packed)) packed_u16 { + uint16_t v; +}; + +static inline uint64_t get_u64(const uint8_t *tab) +{ + return ((const struct packed_u64 *)tab)->v; +} + +static inline int64_t get_i64(const uint8_t *tab) +{ + return (int64_t)((const struct packed_u64 *)tab)->v; +} + +static inline void put_u64(uint8_t *tab, uint64_t val) +{ + ((struct packed_u64 *)tab)->v = val; +} + +static inline uint32_t get_u32(const uint8_t *tab) +{ + return ((const struct packed_u32 *)tab)->v; +} + +static inline int32_t get_i32(const uint8_t *tab) +{ + return (int32_t)((const struct packed_u32 *)tab)->v; +} + +static inline void put_u32(uint8_t *tab, uint32_t val) +{ + ((struct packed_u32 *)tab)->v = val; +} + +static inline uint32_t get_u16(const uint8_t *tab) +{ + return ((const struct packed_u16 *)tab)->v; +} + +static inline int32_t get_i16(const uint8_t *tab) +{ + return (int16_t)((const struct packed_u16 *)tab)->v; +} + +static inline void put_u16(uint8_t *tab, uint16_t val) +{ + ((struct packed_u16 *)tab)->v = val; +} + +static inline uint32_t get_u8(const uint8_t *tab) +{ + return *tab; +} + +static inline int32_t get_i8(const uint8_t *tab) +{ + return (int8_t)*tab; +} + +static inline void put_u8(uint8_t *tab, uint8_t val) +{ + *tab = val; +} + +#ifndef bswap16 +static inline uint16_t bswap16(uint16_t x) +{ + return (x >> 8) | (x << 8); +} +#endif + +#ifndef bswap32 +static inline uint32_t bswap32(uint32_t v) +{ + return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) | + ((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24); +} +#endif + +#ifndef bswap64 +static inline uint64_t bswap64(uint64_t v) +{ + return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) | + ((v & ((uint64_t)0xff << (6 * 8))) >> (5 * 8)) | + ((v & ((uint64_t)0xff << (5 * 8))) >> (3 * 8)) | + ((v & ((uint64_t)0xff << (4 * 8))) >> (1 * 8)) | + ((v & ((uint64_t)0xff << (3 * 8))) << (1 * 8)) | + ((v & ((uint64_t)0xff << (2 * 8))) << (3 * 8)) | + ((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) | + ((v & ((uint64_t)0xff << (0 * 8))) << (7 * 8)); +} +#endif + +/* XXX: should take an extra argument to pass slack information to the caller */ +typedef void *DynBufReallocFunc(void *opaque, void *ptr, size_t size); + +typedef struct DynBuf { + uint8_t *buf; + size_t size; + size_t allocated_size; + BOOL error; /* true if a memory allocation error occurred */ + DynBufReallocFunc *realloc_func; + void *opaque; /* for realloc_func */ +} DynBuf; + +void dbuf_init(DynBuf *s); +void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func); +int dbuf_realloc(DynBuf *s, size_t new_size); +int dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, size_t len); +int dbuf_put(DynBuf *s, const uint8_t *data, size_t len); +int dbuf_put_self(DynBuf *s, size_t offset, size_t len); +int dbuf_putc(DynBuf *s, uint8_t c); +int dbuf_putstr(DynBuf *s, const char *str); +static inline int dbuf_put_u16(DynBuf *s, uint16_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 2); +} +static inline int dbuf_put_u32(DynBuf *s, uint32_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 4); +} +static inline int dbuf_put_u64(DynBuf *s, uint64_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 8); +} +int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s, + const char *fmt, ...); +void dbuf_free(DynBuf *s); +static inline BOOL dbuf_error(DynBuf *s) { + return s->error; +} +static inline void dbuf_set_error(DynBuf *s) +{ + s->error = TRUE; +} + +#define UTF8_CHAR_LEN_MAX 6 + +int unicode_to_utf8(uint8_t *buf, unsigned int c); +int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp); + +static inline BOOL is_surrogate(uint32_t c) +{ + return (c >> 11) == (0xD800 >> 11); // 0xD800-0xDFFF +} + +static inline BOOL is_hi_surrogate(uint32_t c) +{ + return (c >> 10) == (0xD800 >> 10); // 0xD800-0xDBFF +} + +static inline BOOL is_lo_surrogate(uint32_t c) +{ + return (c >> 10) == (0xDC00 >> 10); // 0xDC00-0xDFFF +} + +static inline uint32_t get_hi_surrogate(uint32_t c) +{ + return (c >> 10) - (0x10000 >> 10) + 0xD800; +} + +static inline uint32_t get_lo_surrogate(uint32_t c) +{ + return (c & 0x3FF) | 0xDC00; +} + +static inline uint32_t from_surrogate(uint32_t hi, uint32_t lo) +{ + return 0x10000 + 0x400 * (hi - 0xD800) + (lo - 0xDC00); +} + +static inline int from_hex(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else + return -1; +} + +void rqsort(void *base, size_t nmemb, size_t size, + int (*cmp)(const void *, const void *, void *), + void *arg); + +#endif /* CUTILS_H */ diff --git a/source/engine/thirdparty/quickjs/doc/jsbignum.texi b/source/engine/thirdparty/quickjs/doc/jsbignum.texi new file mode 100644 index 0000000..95fee54 --- /dev/null +++ b/source/engine/thirdparty/quickjs/doc/jsbignum.texi @@ -0,0 +1,589 @@ +\input texinfo + +@iftex +@afourpaper +@headings double +@end iftex + +@titlepage +@afourpaper +@sp 7 +@center @titlefont{Javascript Bignum Extensions} +@sp 3 +@center Version 2020-01-11 +@sp 3 +@center Author: Fabrice Bellard +@end titlepage + +@setfilename jsbignum.info +@settitle Javascript Bignum Extensions + +@contents + +@chapter Introduction + +The Bignum extensions add the following features to the Javascript +language while being 100% backward compatible: + +@itemize + +@item Operator overloading with a dispatch logic inspired from the proposal available at @url{https://github.com/tc39/proposal-operator-overloading/}. + +@item Arbitrarily large floating point numbers (@code{BigFloat}) in base 2 using the IEEE 754 semantics. + +@item Arbitrarily large floating point numbers (@code{BigDecimal}) in base 10 based on the proposal available at +@url{https://github.com/littledan/proposal-bigdecimal}. + +@item @code{math} mode: arbitrarily large integers and floating point numbers are available by default. The integer division and power can be overloaded for example to return a fraction. The modulo operator (@code{%}) is defined as the Euclidian +remainder. @code{^} is an alias to the power operator +(@code{**}). @code{^^} is used as the exclusive or operator. + +@end itemize + +The extensions are independent from each other except the @code{math} +mode which relies on BigFloat and operator overloading. + +@chapter Operator overloading + +Operator overloading is inspired from the proposal available at +@url{https://github.com/tc39/proposal-operator-overloading/}. It +implements the same dispatch logic but finds the operator sets by +looking at the @code{Symbol.operatorSet} property in the objects. The +changes were done in order to simplify the implementation. + +More precisely, the following modifications were made: + +@itemize + +@item @code{with operators from} is not supported. Operator overloading is always enabled. + +@item The dispatch is not based on a static @code{[[OperatorSet]]} field in all instances. Instead, a dynamic lookup of the @code{Symbol.operatorSet} property is done. This property is typically added in the prototype of each object. + +@item @code{Operators.create(...dictionaries)} is used to create a new OperatorSet object. The @code{Operators} function is supported as an helper to be closer to the TC39 proposal. + +@item @code{[]} cannot be overloaded. + +@item In math mode, the BigInt division and power operators can be overloaded with @code{Operators.updateBigIntOperators(dictionary)}. + +@end itemize + +@chapter BigInt extensions + +A few properties are added to the BigInt object: + +@table @code + +@item tdiv(a, b) +Return @math{trunc(a/b)}. @code{b = 0} raises a RangeError +exception. + +@item fdiv(a, b) +Return @math{\lfloor a/b \rfloor}. @code{b = 0} raises a RangeError +exception. + +@item cdiv(a, b) +Return @math{\lceil a/b \rceil}. @code{b = 0} raises a RangeError +exception. + +@item ediv(a, b) +Return @math{sgn(b) \lfloor a/{|b|} \rfloor} (Euclidian +division). @code{b = 0} raises a RangeError exception. + +@item tdivrem(a, b) +@item fdivrem(a, b) +@item cdivrem(a, b) +@item edivrem(a, b) +Return an array of two elements. The first element is the quotient, +the second is the remainder. The same rounding is done as the +corresponding division operation. + +@item sqrt(a) +Return @math{\lfloor \sqrt(a) \rfloor}. A RangeError exception is +raised if @math{a < 0}. + +@item sqrtrem(a) +Return an array of two elements. The first element is @math{\lfloor +\sqrt{a} \rfloor}. The second element is @math{a-\lfloor \sqrt{a} +\rfloor^2}. A RangeError exception is raised if @math{a < 0}. + +@item floorLog2(a) +Return -1 if @math{a \leq 0} otherwise return @math{\lfloor \log2(a) \rfloor}. + +@item ctz(a) +Return the number of trailing zeros in the two's complement binary representation of a. Return -1 if @math{a=0}. + +@end table + +@chapter BigFloat + +@section Introduction + +This extension adds the @code{BigFloat} primitive type. The +@code{BigFloat} type represents floating point numbers in base 2 +with the IEEE 754 semantics. A floating +point number is represented as a sign, mantissa and exponent. The +special values @code{NaN}, @code{+/-Infinity}, @code{+0} and @code{-0} +are supported. The mantissa and exponent can have any bit length with +an implementation specific minimum and maximum. + +@section Floating point rounding + +Each floating point operation operates with infinite precision and +then rounds the result according to the specified floating point +environment (@code{BigFloatEnv} object). The status flags of the +environment are also set according to the result of the operation. + +If no floating point environment is provided, the global floating +point environment is used. + +The rounding mode of the global floating point environment is always +@code{RNDN} (``round to nearest with ties to even'')@footnote{The +rationale is that the rounding mode changes must always be +explicit.}. The status flags of the global environment cannot be +read@footnote{The rationale is to avoid side effects for the built-in +operators.}. The precision of the global environment is +@code{BigFloatEnv.prec}. The number of exponent bits of the global +environment is @code{BigFloatEnv.expBits}. The global environment +subnormal flag is set to @code{true}. + +For example, @code{prec = 53} and @code{ expBits = 11} exactly give +the same precision as the IEEE 754 64 bit floating point format. The +default precision is @code{prec = 113} and @code{ expBits = 15} (IEEE +754 128 bit floating point format). + +The global floating point environment can only be modified temporarily +when calling a function (see @code{BigFloatEnv.setPrec}). Hence a +function can change the global floating point environment for its +callees but not for its caller. + +@section Operators + +The builtin operators are extended so that a BigFloat is returned if +at least one operand is a BigFloat. The computations are always done +with infinite precision and rounded according to the global floating +point environment. + +@code{typeof} applied on a @code{BigFloat} returns @code{bigfloat}. + +BigFloat can be compared with all the other numeric types and the +result follows the expected mathematical relations. + +However, since BigFloat and Number are different types they are never +equal when using the strict comparison operators (e.g. @code{0.0 === +0.0l} is false). + +@section BigFloat literals + +BigFloat literals are floating point numbers with a trailing @code{l} +suffix. BigFloat literals have an infinite precision. They are rounded +according to the global floating point environment when they are +evaluated.@footnote{Base 10 floating point literals cannot usually be +exactly represented as base 2 floating point number. In order to +ensure that the literal is represented accurately with the current +precision, it must be evaluated at runtime.} + +@section Builtin Object changes + +@subsection @code{BigFloat} function + +The @code{BigFloat} function cannot be invoked as a constructor. When +invoked as a function: the parameter is converted to a primitive +type. If the result is a numeric type, it is converted to BigFloat +without rounding. If the result is a string, it is converted to +BigFloat using the precision of the global floating point environment. + +@code{BigFloat} properties: + +@table @code + +@item LN2 +@item PI +Getter. Return the value of the corresponding mathematical constant +rounded to nearest, ties to even with the current global +precision. The constant values are cached for small precisions. + +@item MIN_VALUE +@item MAX_VALUE +@item EPSILON +Getter. Return the minimum, maximum and epsilon @code{BigFloat} values +(same definition as the corresponding @code{Number} constants). + +@item fpRound(a[, e]) +Round the floating point number @code{a} according to the floating +point environment @code{e} or the global environment if @code{e} is +undefined. + +@item parseFloat(a[, radix[, e]]) +Parse the string @code{a} as a floating point number in radix +@code{radix}. The radix is 0 (default) or from 2 to 36. The radix 0 +means radix 10 unless there is a hexadecimal or binary prefix. The +result is rounded according to the floating point environment @code{e} +or the global environment if @code{e} is undefined. + +@item isFinite(a) +Return true if @code{a} is a finite bigfloat. + +@item isNaN(a) +Return true if @code{a} is a NaN bigfloat. + +@item add(a, b[, e]) +@item sub(a, b[, e]) +@item mul(a, b[, e]) +@item div(a, b[, e]) +Perform the specified floating point operation and round the floating +point number @code{a} according to the floating point environment +@code{e} or the global environment if @code{e} is undefined. If +@code{e} is specified, the floating point status flags are updated. + +@item floor(x) +@item ceil(x) +@item round(x) +@item trunc(x) +Round to an integer. No additional rounding is performed. + +@item abs(x) +Return the absolute value of x. No additional rounding is performed. + +@item fmod(x, y[, e]) +@item remainder(x, y[, e]) +Floating point remainder. The quotient is truncated to zero (fmod) or +to the nearest integer with ties to even (remainder). @code{e} is an +optional floating point environment. + +@item sqrt(x[, e]) +Square root. Return a rounded floating point number. @code{e} is an +optional floating point environment. + +@item sin(x[, e]) +@item cos(x[, e]) +@item tan(x[, e]) +@item asin(x[, e]) +@item acos(x[, e]) +@item atan(x[, e]) +@item atan2(x, y[, e]) +@item exp(x[, e]) +@item log(x[, e]) +@item pow(x, y[, e]) +Transcendental operations. Return a rounded floating point +number. @code{e} is an optional floating point environment. + +@end table + +@subsection @code{BigFloat.prototype} + +The following properties are modified: + +@table @code +@item valueOf() +Return the bigfloat primitive value corresponding to @code{this}. + +@item toString(radix) + +For floating point numbers: + +@itemize +@item +If the radix is a power of two, the conversion is done with infinite +precision. +@item +Otherwise, the number is rounded to nearest with ties to even using +the global precision. It is then converted to string using the minimum +number of digits so that its conversion back to a floating point using +the global precision and round to nearest gives the same number. + +@end itemize + +The exponent letter is @code{e} for base 10, @code{p} for bases 2, 8, +16 with a binary exponent and @code{@@} for the other bases. + +@item toPrecision(p, rnd_mode = BigFloatEnv.RNDNA, radix = 10) +@item toFixed(p, rnd_mode = BigFloatEnv.RNDNA, radix = 10) +@item toExponential(p, rnd_mode = BigFloatEnv.RNDNA, radix = 10) +Same semantics as the corresponding @code{Number} functions with +BigFloats. There is no limit on the accepted precision @code{p}. The +rounding mode and radix can be optionally specified. The radix must be +between 2 and 36. + +@end table + +@subsection @code{BigFloatEnv} constructor + +The @code{BigFloatEnv([p, [,rndMode]]} constructor cannot be invoked as a +function. The floating point environment contains: + +@itemize +@item the mantissa precision in bits + +@item the exponent size in bits assuming an IEEE 754 representation; + +@item the subnormal flag (if true, subnormal floating point numbers can +be generated by the floating point operations). + +@item the rounding mode + +@item the floating point status. The status flags can only be set by the floating point operations. They can be reset with @code{BigFloatEnv.prototype.clearStatus()} or with the various status flag setters. + +@end itemize + +@code{new BigFloatEnv([p, [,rndMode]]} creates a new floating point +environment. The status flags are reset. If no parameter is given the +precision, exponent bits and subnormal flags are copied from the +global floating point environment. Otherwise, the precision is set to +@code{p}, the number of exponent bits is set to @code{expBitsMax} and the +subnormal flags is set to @code{false}. If @code{rndMode} is +@code{undefined}, the rounding mode is set to @code{RNDN}. + +@code{BigFloatEnv} properties: + +@table @code + +@item prec +Getter. Return the precision in bits of the global floating point +environment. The initial value is @code{113}. + +@item expBits +Getter. Return the exponent size in bits of the global floating point +environment assuming an IEEE 754 representation. The initial value is +@code{15}. + +@item setPrec(f, p[, e]) +Set the precision of the global floating point environment to @code{p} +and the exponent size to @code{e} then call the function +@code{f}. Then the Float precision and exponent size are reset to +their precious value and the return value of @code{f} is returned (or +an exception is raised if @code{f} raised an exception). If @code{e} +is @code{undefined} it is set to @code{BigFloatEnv.expBitsMax}. + +@item precMin +Read-only integer. Return the minimum allowed precision. Must be at least 2. + +@item precMax +Read-only integer. Return the maximum allowed precision. Must be at least 113. + +@item expBitsMin +Read-only integer. Return the minimum allowed exponent size in +bits. Must be at least 3. + +@item expBitsMax +Read-only integer. Return the maximum allowed exponent size in +bits. Must be at least 15. + +@item RNDN +Read-only integer. Round to nearest, with ties to even rounding mode. + +@item RNDZ +Read-only integer. Round to zero rounding mode. + +@item RNDD +Read-only integer. Round to -Infinity rounding mode. + +@item RNDU +Read-only integer. Round to +Infinity rounding mode. + +@item RNDNA +Read-only integer. Round to nearest, with ties away from zero rounding mode. + +@item RNDA +Read-only integer. Round away from zero rounding mode. + +@item RNDF@footnote{Could be removed in case a deterministic behavior for floating point operations is required.} +Read-only integer. Faithful rounding mode. The result is +non-deterministically rounded to -Infinity or +Infinity. This rounding +mode usually gives a faster and deterministic running time for the +floating point operations. + +@end table + +@code{BigFloatEnv.prototype} properties: + +@table @code + +@item prec +Getter and setter (Integer). Return or set the precision in bits. + +@item expBits +Getter and setter (Integer). Return or set the exponent size in bits +assuming an IEEE 754 representation. + +@item rndMode +Getter and setter (Integer). Return or set the rounding mode. + +@item subnormal +Getter and setter (Boolean). subnormal flag. It is false when +@code{expBits = expBitsMax}. + +@item clearStatus() +Clear the status flags. + +@item invalidOperation +@item divideByZero +@item overflow +@item underflow +@item inexact +Getter and setter (Boolean). Status flags. + +@end table + +@chapter BigDecimal + +This extension adds the @code{BigDecimal} primitive type. The +@code{BigDecimal} type represents floating point numbers in base +10. It is inspired from the proposal available at +@url{https://github.com/littledan/proposal-bigdecimal}. + +The @code{BigDecimal} floating point numbers are always normalized and +finite. There is no concept of @code{-0}, @code{Infinity} or +@code{NaN}. By default, all the computations are done with infinite +precision. + +@section Operators + +The following builtin operators support BigDecimal: + +@table @code + +@item + +@item - +@item * +Both operands must be BigDecimal. The result is computed with infinite +precision. +@item % +Both operands must be BigDecimal. The result is computed with infinite +precision. A range error is throws in case of division by zero. + +@item / +Both operands must be BigDecimal. A range error is throws in case of +division by zero or if the result cannot be represented with infinite +precision (use @code{BigDecimal.div} to specify the rounding). + +@item ** +Both operands must be BigDecimal. The exponent must be a positive +integer. The result is computed with infinite precision. + +@item === +When one of the operand is a BigDecimal, return true if both operands +are a BigDecimal and if they are equal. + +@item == +@item != +@item <= +@item >= +@item < +@item > + +Numerical comparison. When one of the operand is not a BigDecimal, it is +converted to BigDecimal by using ToString(). Hence comparisons between +Number and BigDecimal do not use the exact mathematical value of the +Number value. + +@end table + +@section BigDecimal literals + +BigDecimal literals are decimal floating point numbers with a trailing +@code{m} suffix. + +@section Builtin Object changes + +@subsection The @code{BigDecimal} function. + +It returns @code{0m} if no parameter is provided. Otherwise the first +parameter is converted to a bigdecimal by using ToString(). Hence +Number values are not converted to their exact numerical value as +BigDecimal. + +@subsection Properties of the @code{BigDecimal} object + +@table @code + +@item add(a, b[, e]) +@item sub(a, b[, e]) +@item mul(a, b[, e]) +@item div(a, b[, e]) +@item mod(a, b[, e]) +@item sqrt(a, e) +@item round(a, e) +Perform the specified floating point operation and round the floating +point result according to the rounding object @code{e}. If the +rounding object is not present, the operation is executed with +infinite precision. + +For @code{div}, a @code{RangeError} exception is thrown in case of +division by zero or if the result cannot be represented with infinite +precision if no rounding object is present. + +For @code{sqrt}, a range error is thrown if @code{a} is less than +zero. + +The rounding object must contain the following properties: +@code{roundingMode} is a string specifying the rounding mode +(@code{"floor"}, @code{"ceiling"}, @code{"down"}, @code{"up"}, +@code{"half-even"}, @code{"half-up"}). Either +@code{maximumSignificantDigits} or @code{maximumFractionDigits} must +be present to specify respectively the number of significant digits +(must be >= 1) or the number of digits after the decimal point (must +be >= 0). + +@end table + +@subsection Properties of the @code{BigDecimal.prototype} object + +@table @code +@item valueOf() +Return the bigdecimal primitive value corresponding to @code{this}. + +@item toString() +Convert @code{this} to a string with infinite precision in base 10. + +@item toPrecision(p, rnd_mode = "half-up") +@item toFixed(p, rnd_mode = "half-up") +@item toExponential(p, rnd_mode = "half-up") +Convert the BigDecimal @code{this} to string with the specified +precision @code{p}. There is no limit on the accepted precision +@code{p}. The rounding mode can be optionally +specified. @code{toPrecision} outputs either in decimal fixed notation +or in decimal exponential notation with a @code{p} digits of +precision. @code{toExponential} outputs in decimal exponential +notation with @code{p} digits after the decimal point. @code{toFixed} +outputs in decimal notation with @code{p} digits after the decimal +point. + +@end table + +@chapter Math mode + +A new @emph{math mode} is enabled with the @code{"use math"} +directive. It propagates the same way as the @emph{strict mode}. It is +designed so that arbitrarily large integers and floating point numbers +are available by default. In order to minimize the number of changes +in the Javascript semantics, integers are represented either as Number +or BigInt depending on their magnitude. Floating point numbers are +always represented as BigFloat. + +The following changes are made to the Javascript semantics: + +@itemize + +@item Floating point literals (i.e. number with a decimal point or an exponent) are @code{BigFloat} by default (i.e. a @code{l} suffix is implied). Hence @code{typeof 1.0 === "bigfloat"}. + +@item Integer literals (i.e. numbers without a decimal point or an exponent) with or without the @code{n} suffix are @code{BigInt} if their value cannot be represented as a safe integer. A safe integer is defined as a integer whose absolute value is smaller or equal to @code{2**53-1}. Hence @code{typeof 1 === "number "}, @code{typeof 1n === "number"} but @code{typeof 9007199254740992 === "bigint" }. + +@item All the bigint builtin operators and functions are modified so that their result is returned as a Number if it is a safe integer. Otherwise the result stays a BigInt. + +@item The builtin operators are modified so that they return an exact result (which can be a BigInt) if their operands are safe integers. Operands between Number and BigInt are accepted provided the Number operand is a safe integer. The integer power with a negative exponent returns a BigFloat as result. The integer division returns a BigFloat as result. + +@item The @code{^} operator is an alias to the power operator (@code{**}). + +@item The power operator (both @code{^} and @code{**}) grammar is modified so that @code{-2^2} is allowed and yields @code{-4}. + +@item The logical xor operator is still available with the @code{^^} operator. + +@item The modulo operator (@code{%}) returns the Euclidian remainder (always positive) instead of the truncated remainder. + +@item The integer division operator can be overloaded with @code{Operators.updateBigIntOperators(dictionary)}. + +@item The integer power operator with a non zero negative exponent can be overloaded with @code{Operators.updateBigIntOperators(dictionary)}. + +@end itemize + +@bye diff --git a/source/engine/thirdparty/quickjs/doc/quickjs.texi b/source/engine/thirdparty/quickjs/doc/quickjs.texi new file mode 100644 index 0000000..34ddf35 --- /dev/null +++ b/source/engine/thirdparty/quickjs/doc/quickjs.texi @@ -0,0 +1,1122 @@ +\input texinfo + +@iftex +@afourpaper +@headings double +@end iftex + +@titlepage +@afourpaper +@sp 7 +@center @titlefont{QuickJS Javascript Engine} +@sp 3 +@end titlepage + +@setfilename spec.info +@settitle QuickJS Javascript Engine + +@contents + +@chapter Introduction + +QuickJS is a small and embeddable Javascript engine. It supports most of the +ES2023 specification +@footnote{@url{https://tc39.es/ecma262/2023 }} +including modules, asynchronous generators, proxies and BigInt. + +It supports mathematical extensions such as big decimal float float +numbers (BigDecimal), big binary floating point numbers (BigFloat), +and operator overloading. + +@section Main Features + +@itemize + +@item Small and easily embeddable: just a few C files, no external dependency, 210 KiB of x86 code for a simple ``hello world'' program. + +@item Fast interpreter with very low startup time: runs the 77000 tests of the ECMAScript Test Suite@footnote{@url{https://github.com/tc39/test262}} in less than 2 minutes on a single core of a desktop PC. The complete life cycle of a runtime instance completes in less than 300 microseconds. + +@item Almost complete ES2023 support including modules, asynchronous +generators and full Annex B support (legacy web compatibility). Some +features from the upcoming ES2024 specification +@footnote{@url{https://tc39.es/ecma262/}} are also supported. + +@item Passes nearly 100% of the ECMAScript Test Suite tests when selecting the ES2023 features. + +@item Compile Javascript sources to executables with no external dependency. + +@item Garbage collection using reference counting (to reduce memory usage and have deterministic behavior) with cycle removal. + +@item Mathematical extensions: BigDecimal, BigFloat, operator overloading, bigint mode, math mode. + +@item Command line interpreter with contextual colorization and completion implemented in Javascript. + +@item Small built-in standard library with C library wrappers. + +@end itemize + +@chapter Usage + +@section Installation + +A Makefile is provided to compile the engine on Linux or MacOS/X. A +preliminary Windows support is available thru cross compilation on a +Linux host with the MingGW tools. + +Edit the top of the @code{Makefile} if you wish to select specific +options then run @code{make}. + +You can type @code{make install} as root if you wish to install the binaries and support files to +@code{/usr/local} (this is not necessary to use QuickJS). + +Note: On some OSes atomic operations are not available or need a +specific library. If you get related errors, you should either add +@code{-latomics} in the Makefile @code{LIBS} variable or disable +@code{CONFIG_ATOMICS} in @file{quickjs.c}. + +@section Quick start + +@code{qjs} is the command line interpreter (Read-Eval-Print Loop). You can pass +Javascript files and/or expressions as arguments to execute them: + +@example +./qjs examples/hello.js +@end example + +@code{qjsc} is the command line compiler: + +@example +./qjsc -o hello examples/hello.js +./hello +@end example + +generates a @code{hello} executable with no external dependency. + +@section Command line options + +@subsection @code{qjs} interpreter + +@verbatim +usage: qjs [options] [file [args]] +@end verbatim + +Options are: +@table @code +@item -h +@item --help +List options. + +@item -e @code{EXPR} +@item --eval @code{EXPR} +Evaluate EXPR. + +@item -i +@item --interactive +Go to interactive mode (it is not the default when files are provided on the command line). + +@item -m +@item --module +Load as ES6 module (default=autodetect). A module is autodetected if +the filename extension is @code{.mjs} or if the first keyword of the +source is @code{import}. + +@item --script +Load as ES6 script (default=autodetect). + +@item --bignum +Enable the bignum extensions: BigDecimal object, BigFloat object and +the @code{"use math"} directive. + +@item -I file +@item --include file +Include an additional file. + +@end table + +Advanced options are: + +@table @code +@item --std +Make the @code{std} and @code{os} modules available to the loaded +script even if it is not a module. + +@item -d +@item --dump +Dump the memory usage stats. + +@item -q +@item --quit +just instantiate the interpreter and quit. + +@end table + +@subsection @code{qjsc} compiler + +@verbatim +usage: qjsc [options] [files] +@end verbatim + +Options are: +@table @code +@item -c +Only output bytecode in a C file. The default is to output an executable file. +@item -e +Output @code{main()} and bytecode in a C file. The default is to output an +executable file. +@item -o output +Set the output filename (default = @file{out.c} or @file{a.out}). + +@item -N cname +Set the C name of the generated data. + +@item -m +Compile as Javascript module (default=autodetect). + +@item -D module_name +Compile a dynamically loaded module and its dependencies. This option +is needed when your code uses the @code{import} keyword or the +@code{os.Worker} constructor because the compiler cannot statically +find the name of the dynamically loaded modules. + +@item -M module_name[,cname] +Add initialization code for an external C module. See the +@code{c_module} example. + +@item -x +Byte swapped output (only used for cross compilation). + +@item -flto +Use link time optimization. The compilation is slower but the +executable is smaller and faster. This option is automatically set +when the @code{-fno-x} options are used. + +@item -fno-[eval|string-normalize|regexp|json|proxy|map|typedarray|promise|bigint] +Disable selected language features to produce a smaller executable file. + +@item -fbignum +Enable the bignum extensions: BigDecimal object, BigFloat object and +the @code{"use math"} directive. + +@end table + +@section @code{qjscalc} application + +The @code{qjscalc} application is a superset of the @code{qjs} +command line interpreter implementing a Javascript calculator with +arbitrarily large integer and floating point numbers, fractions, +complex numbers, polynomials and matrices. The source code is in +@file{qjscalc.js}. More documentation and a web version are available at +@url{http://numcalc.com}. + +@section Built-in tests + +Run @code{make test} to run the few built-in tests included in the +QuickJS archive. + +@section Test262 (ECMAScript Test Suite) + +A test262 runner is included in the QuickJS archive. The test262 tests +can be installed in the QuickJS source directory with: + +@example +git clone https://github.com/tc39/test262.git test262 +cd test262 +patch -p1 < ../tests/test262.patch +cd .. +@end example + +The patch adds the implementation specific @code{harness} functions +and optimizes the inefficient RegExp character classes and Unicode +property escapes tests (the tests themselves are not modified, only a +slow string initialization function is optimized). + +The tests can be run with +@example +make test2 +@end example + +The configuration files @code{test262.conf} +(resp. @code{test262o.conf} for the old ES5.1 tests@footnote{The old +ES5.1 tests can be extracted with @code{git clone --single-branch +--branch es5-tests https://github.com/tc39/test262.git test262o}})) +contain the options to run the various tests. Tests can be excluded +based on features or filename. + +The file @code{test262_errors.txt} contains the current list of +errors. The runner displays a message when a new error appears or when +an existing error is corrected or modified. Use the @code{-u} option +to update the current list of errors (or @code{make test2-update}). + +The file @code{test262_report.txt} contains the logs of all the +tests. It is useful to have a clearer analysis of a particular +error. In case of crash, the last line corresponds to the failing +test. + +Use the syntax @code{./run-test262 -c test262.conf -f filename.js} to +run a single test. Use the syntax @code{./run-test262 -c test262.conf +N} to start testing at test number @code{N}. + +For more information, run @code{./run-test262} to see the command line +options of the test262 runner. + +@code{run-test262} accepts the @code{-N} option to be invoked from +@code{test262-harness}@footnote{@url{https://github.com/bterlson/test262-harness}} +thru @code{eshost}. Unless you want to compare QuickJS with other +engines under the same conditions, we do not recommend to run the +tests this way as it is much slower (typically half an hour instead of +about 100 seconds). + +@chapter Specifications + +@section Language support + +@subsection ES2023 support + +The ES2023 specification is almost fully supported including the Annex +B (legacy web compatibility) and the Unicode related features. + +The following features are not supported yet: + +@itemize + +@item Tail calls@footnote{We believe the current specification of tails calls is too complicated and presents limited practical interests.} + +@item WeakRef and FinalizationRegistry objects + +@item Symbols as WeakMap keys + +@end itemize + +@subsection ECMA402 + +ECMA402 (Internationalization API) is not supported. + +@subsection Extensions + +@itemize + +@item The directive @code{"use strip"} indicates that the debug information (including the source code of the functions) should not be retained to save memory. As @code{"use strict"}, the directive can be global to a script or local to a function. + +@item The first line of a script beginning with @code{#!} is ignored. + +@end itemize + +@subsection Mathematical extensions + +The mathematical extensions are fully backward compatible with +standard Javascript. See @code{jsbignum.pdf} for more information. + +@itemize + +@item @code{BigDecimal} support: arbitrary large floating point numbers in base 10. + +@item @code{BigFloat} support: arbitrary large floating point numbers in base 2. + +@item Operator overloading. + +@item The directive @code{"use bigint"} enables the bigint mode where integers are @code{BigInt} by default. + +@item The directive @code{"use math"} enables the math mode where the division and power operators on integers produce fractions. Floating point literals are @code{BigFloat} by default and integers are @code{BigInt} by default. + +@end itemize + +@section Modules + +ES6 modules are fully supported. The default name resolution is the +following: + +@itemize + +@item Module names with a leading @code{.} or @code{..} are relative +to the current module path. + +@item Module names without a leading @code{.} or @code{..} are system +modules, such as @code{std} or @code{os}. + +@item Module names ending with @code{.so} are native modules using the +QuickJS C API. + +@end itemize + +@section Standard library + +The standard library is included by default in the command line +interpreter. It contains the two modules @code{std} and @code{os} and +a few global objects. + +@subsection Global objects + +@table @code +@item scriptArgs +Provides the command line arguments. The first argument is the script name. +@item print(...args) +Print the arguments separated by spaces and a trailing newline. +@item console.log(...args) +Same as print(). + +@end table + +@subsection @code{std} module + +The @code{std} module provides wrappers to the libc @file{stdlib.h} +and @file{stdio.h} and a few other utilities. + +Available exports: + +@table @code + +@item exit(n) +Exit the process. + +@item evalScript(str, options = undefined) +Evaluate the string @code{str} as a script (global +eval). @code{options} is an optional object containing the following +optional properties: + + @table @code + @item backtrace_barrier + Boolean (default = false). If true, error backtraces do not list the + stack frames below the evalScript. + @item async + Boolean (default = false). If true, @code{await} is accepted in the + script and a promise is returned. The promise is resolved with an + object whose @code{value} property holds the value returned by the + script. + @end table + +@item loadScript(filename) +Evaluate the file @code{filename} as a script (global eval). + +@item loadFile(filename) +Load the file @code{filename} and return it as a string assuming UTF-8 +encoding. Return @code{null} in case of I/O error. + +@item open(filename, flags, errorObj = undefined) +Open a file (wrapper to the libc @code{fopen()}). Return the FILE +object or @code{null} in case of I/O error. If @code{errorObj} is not +undefined, set its @code{errno} property to the error code or to 0 if +no error occured. + +@item popen(command, flags, errorObj = undefined) +Open a process by creating a pipe (wrapper to the libc +@code{popen()}). Return the FILE +object or @code{null} in case of I/O error. If @code{errorObj} is not +undefined, set its @code{errno} property to the error code or to 0 if +no error occured. + +@item fdopen(fd, flags, errorObj = undefined) +Open a file from a file handle (wrapper to the libc +@code{fdopen()}). Return the FILE +object or @code{null} in case of I/O error. If @code{errorObj} is not +undefined, set its @code{errno} property to the error code or to 0 if +no error occured. + +@item tmpfile(errorObj = undefined) +Open a temporary file. Return the FILE +object or @code{null} in case of I/O error. If @code{errorObj} is not +undefined, set its @code{errno} property to the error code or to 0 if +no error occured. + +@item puts(str) +Equivalent to @code{std.out.puts(str)}. + +@item printf(fmt, ...args) +Equivalent to @code{std.out.printf(fmt, ...args)}. + +@item sprintf(fmt, ...args) +Equivalent to the libc sprintf(). + +@item in +@item out +@item err +Wrappers to the libc file @code{stdin}, @code{stdout}, @code{stderr}. + +@item SEEK_SET +@item SEEK_CUR +@item SEEK_END +Constants for seek(). + +@item Error + +Enumeration object containing the integer value of common errors +(additional error codes may be defined): + + @table @code + @item EINVAL + @item EIO + @item EACCES + @item EEXIST + @item ENOSPC + @item ENOSYS + @item EBUSY + @item ENOENT + @item EPERM + @item EPIPE + @end table + +@item strerror(errno) +Return a string that describes the error @code{errno}. + +@item gc() +Manually invoke the cycle removal algorithm. The cycle removal +algorithm is automatically started when needed, so this function is +useful in case of specific memory constraints or for testing. + +@item getenv(name) +Return the value of the environment variable @code{name} or +@code{undefined} if it is not defined. + +@item setenv(name, value) +Set the value of the environment variable @code{name} to the string +@code{value}. + +@item unsetenv(name) +Delete the environment variable @code{name}. + +@item getenviron() +Return an object containing the environment variables as key-value pairs. + +@item urlGet(url, options = undefined) + +Download @code{url} using the @file{curl} command line +utility. @code{options} is an optional object containing the following +optional properties: + + @table @code + @item binary + Boolean (default = false). If true, the response is an ArrayBuffer + instead of a string. When a string is returned, the data is assumed + to be UTF-8 encoded. + + @item full + + Boolean (default = false). If true, return the an object contains + the properties @code{response} (response content), + @code{responseHeaders} (headers separated by CRLF), @code{status} + (status code). @code{response} is @code{null} is case of protocol or + network error. If @code{full} is false, only the response is + returned if the status is between 200 and 299. Otherwise @code{null} + is returned. + + @end table + +@item parseExtJSON(str) + + Parse @code{str} using a superset of @code{JSON.parse}. The + following extensions are accepted: + + @itemize + @item Single line and multiline comments + @item unquoted properties (ASCII-only Javascript identifiers) + @item trailing comma in array and object definitions + @item single quoted strings + @item @code{\f} and @code{\v} are accepted as space characters + @item leading plus in numbers + @item octal (@code{0o} prefix) and hexadecimal (@code{0x} prefix) numbers + @end itemize +@end table + +FILE prototype: + +@table @code +@item close() +Close the file. Return 0 if OK or @code{-errno} in case of I/O error. +@item puts(str) +Outputs the string with the UTF-8 encoding. +@item printf(fmt, ...args) +Formatted printf. + +The same formats as the standard C library @code{printf} are +supported. Integer format types (e.g. @code{%d}) truncate the Numbers +or BigInts to 32 bits. Use the @code{l} modifier (e.g. @code{%ld}) to +truncate to 64 bits. + +@item flush() +Flush the buffered file. +@item seek(offset, whence) +Seek to a give file position (whence is +@code{std.SEEK_*}). @code{offset} can be a number or a bigint. Return +0 if OK or @code{-errno} in case of I/O error. +@item tell() +Return the current file position. +@item tello() +Return the current file position as a bigint. +@item eof() +Return true if end of file. +@item fileno() +Return the associated OS handle. +@item error() +Return true if there was an error. +@item clearerr() +Clear the error indication. + +@item read(buffer, position, length) +Read @code{length} bytes from the file to the ArrayBuffer @code{buffer} at byte +position @code{position} (wrapper to the libc @code{fread}). + +@item write(buffer, position, length) +Write @code{length} bytes to the file from the ArrayBuffer @code{buffer} at byte +position @code{position} (wrapper to the libc @code{fwrite}). + +@item getline() +Return the next line from the file, assuming UTF-8 encoding, excluding +the trailing line feed. + +@item readAsString(max_size = undefined) +Read @code{max_size} bytes from the file and return them as a string +assuming UTF-8 encoding. If @code{max_size} is not present, the file +is read up its end. + +@item getByte() +Return the next byte from the file. Return -1 if the end of file is reached. + +@item putByte(c) +Write one byte to the file. +@end table + +@subsection @code{os} module + +The @code{os} module provides Operating System specific functions: + +@itemize +@item low level file access +@item signals +@item timers +@item asynchronous I/O +@item workers (threads) +@end itemize + +The OS functions usually return 0 if OK or an OS specific negative +error code. + +Available exports: + +@table @code +@item open(filename, flags, mode = 0o666) +Open a file. Return a handle or < 0 if error. + +@item O_RDONLY +@item O_WRONLY +@item O_RDWR +@item O_APPEND +@item O_CREAT +@item O_EXCL +@item O_TRUNC +POSIX open flags. + +@item O_TEXT +(Windows specific). Open the file in text mode. The default is binary mode. + +@item close(fd) +Close the file handle @code{fd}. + +@item seek(fd, offset, whence) +Seek in the file. Use @code{std.SEEK_*} for +@code{whence}. @code{offset} is either a number or a bigint. If +@code{offset} is a bigint, a bigint is returned too. + +@item read(fd, buffer, offset, length) +Read @code{length} bytes from the file handle @code{fd} to the +ArrayBuffer @code{buffer} at byte position @code{offset}. +Return the number of read bytes or < 0 if error. + +@item write(fd, buffer, offset, length) +Write @code{length} bytes to the file handle @code{fd} from the +ArrayBuffer @code{buffer} at byte position @code{offset}. +Return the number of written bytes or < 0 if error. + +@item isatty(fd) +Return @code{true} is @code{fd} is a TTY (terminal) handle. + +@item ttyGetWinSize(fd) +Return the TTY size as @code{[width, height]} or @code{null} if not available. + +@item ttySetRaw(fd) +Set the TTY in raw mode. + +@item remove(filename) +Remove a file. Return 0 if OK or @code{-errno}. + +@item rename(oldname, newname) +Rename a file. Return 0 if OK or @code{-errno}. + +@item realpath(path) +Return @code{[str, err]} where @code{str} is the canonicalized absolute +pathname of @code{path} and @code{err} the error code. + +@item getcwd() +Return @code{[str, err]} where @code{str} is the current working directory +and @code{err} the error code. + +@item chdir(path) +Change the current directory. Return 0 if OK or @code{-errno}. + +@item mkdir(path, mode = 0o777) +Create a directory at @code{path}. Return 0 if OK or @code{-errno}. + +@item stat(path) +@item lstat(path) + +Return @code{[obj, err]} where @code{obj} is an object containing the +file status of @code{path}. @code{err} is the error code. The +following fields are defined in @code{obj}: dev, ino, mode, nlink, +uid, gid, rdev, size, blocks, atime, mtime, ctime. The times are +specified in milliseconds since 1970. @code{lstat()} is the same as +@code{stat()} excepts that it returns information about the link +itself. + +@item S_IFMT +@item S_IFIFO +@item S_IFCHR +@item S_IFDIR +@item S_IFBLK +@item S_IFREG +@item S_IFSOCK +@item S_IFLNK +@item S_ISGID +@item S_ISUID +Constants to interpret the @code{mode} property returned by +@code{stat()}. They have the same value as in the C system header +@file{sys/stat.h}. + +@item utimes(path, atime, mtime) +Change the access and modification times of the file @code{path}. The +times are specified in milliseconds since 1970. Return 0 if OK or @code{-errno}. + +@item symlink(target, linkpath) +Create a link at @code{linkpath} containing the string @code{target}. Return 0 if OK or @code{-errno}. + +@item readlink(path) +Return @code{[str, err]} where @code{str} is the link target and @code{err} +the error code. + +@item readdir(path) +Return @code{[array, err]} where @code{array} is an array of strings +containing the filenames of the directory @code{path}. @code{err} is +the error code. + +@item setReadHandler(fd, func) +Add a read handler to the file handle @code{fd}. @code{func} is called +each time there is data pending for @code{fd}. A single read handler +per file handle is supported. Use @code{func = null} to remove the +handler. + +@item setWriteHandler(fd, func) +Add a write handler to the file handle @code{fd}. @code{func} is +called each time data can be written to @code{fd}. A single write +handler per file handle is supported. Use @code{func = null} to remove +the handler. + +@item signal(signal, func) +Call the function @code{func} when the signal @code{signal} +happens. Only a single handler per signal number is supported. Use +@code{null} to set the default handler or @code{undefined} to ignore +the signal. Signal handlers can only be defined in the main thread. + +@item SIGINT +@item SIGABRT +@item SIGFPE +@item SIGILL +@item SIGSEGV +@item SIGTERM +POSIX signal numbers. + +@item kill(pid, sig) +Send the signal @code{sig} to the process @code{pid}. + +@item exec(args[, options]) +Execute a process with the arguments @code{args}. @code{options} is an +object containing optional parameters: + + @table @code + @item block + Boolean (default = true). If true, wait until the process is + terminated. In this case, @code{exec} return the exit code if positive + or the negated signal number if the process was interrupted by a + signal. If false, do not block and return the process id of the child. + + @item usePath + Boolean (default = true). If true, the file is searched in the + @code{PATH} environment variable. + + @item file + String (default = @code{args[0]}). Set the file to be executed. + + @item cwd + String. If present, set the working directory of the new process. + + @item stdin + @item stdout + @item stderr + If present, set the handle in the child for stdin, stdout or stderr. + + @item env + Object. If present, set the process environment from the object + key-value pairs. Otherwise use the same environment as the current + process. + + @item uid + Integer. If present, the process uid with @code{setuid}. + + @item gid + Integer. If present, the process gid with @code{setgid}. + + @end table + +@item getpid() +Return the current process ID. + +@item waitpid(pid, options) +@code{waitpid} Unix system call. Return the array @code{[ret, +status]}. @code{ret} contains @code{-errno} in case of error. + +@item WNOHANG +Constant for the @code{options} argument of @code{waitpid}. + +@item dup(fd) +@code{dup} Unix system call. + +@item dup2(oldfd, newfd) +@code{dup2} Unix system call. + +@item pipe() +@code{pipe} Unix system call. Return two handles as @code{[read_fd, +write_fd]} or null in case of error. + +@item sleep(delay_ms) +Sleep during @code{delay_ms} milliseconds. + +@item sleepAsync(delay_ms) +Asynchronouse sleep during @code{delay_ms} milliseconds. Returns a promise. Example: +@example +await os.sleepAsync(500); +@end example + +@item now() +Return a timestamp in milliseconds with more precision than +@code{Date.now()}. The time origin is unspecified and is normally not +impacted by system clock adjustments. + +@item setTimeout(func, delay) +Call the function @code{func} after @code{delay} ms. Return a handle +to the timer. + +@item clearTimeout(handle) +Cancel a timer. + +@item platform +Return a string representing the platform: @code{"linux"}, @code{"darwin"}, +@code{"win32"} or @code{"js"}. + +@item Worker(module_filename) +Constructor to create a new thread (worker) with an API close to the +@code{WebWorkers}. @code{module_filename} is a string specifying the +module filename which is executed in the newly created thread. As for +dynamically imported module, it is relative to the current script or +module path. Threads normally don't share any data and communicate +between each other with messages. Nested workers are not supported. An +example is available in @file{tests/test_worker.js}. + +The worker class has the following static properties: + + @table @code + @item parent + In the created worker, @code{Worker.parent} represents the parent + worker and is used to send or receive messages. + @end table + +The worker instances have the following properties: + + @table @code + @item postMessage(msg) + + Send a message to the corresponding worker. @code{msg} is cloned in + the destination worker using an algorithm similar to the @code{HTML} + structured clone algorithm. @code{SharedArrayBuffer} are shared + between workers. + + Current limitations: @code{Map} and @code{Set} are not supported + yet. + + @item onmessage + + Getter and setter. Set a function which is called each time a + message is received. The function is called with a single + argument. It is an object with a @code{data} property containing the + received message. The thread is not terminated if there is at least + one non @code{null} @code{onmessage} handler. + + @end table + +@end table + +@section QuickJS C API + +The C API was designed to be simple and efficient. The C API is +defined in the header @code{quickjs.h}. + +@subsection Runtime and contexts + +@code{JSRuntime} represents a Javascript runtime corresponding to an +object heap. Several runtimes can exist at the same time but they +cannot exchange objects. Inside a given runtime, no multi-threading is +supported. + +@code{JSContext} represents a Javascript context (or Realm). Each +JSContext has its own global objects and system objects. There can be +several JSContexts per JSRuntime and they can share objects, similar +to frames of the same origin sharing Javascript objects in a +web browser. + +@subsection JSValue + +@code{JSValue} represents a Javascript value which can be a primitive +type or an object. Reference counting is used, so it is important to +explicitly duplicate (@code{JS_DupValue()}, increment the reference +count) or free (@code{JS_FreeValue()}, decrement the reference count) +JSValues. + +@subsection C functions + +C functions can be created with +@code{JS_NewCFunction()}. @code{JS_SetPropertyFunctionList()} is a +shortcut to easily add functions, setters and getters properties to a +given object. + +Unlike other embedded Javascript engines, there is no implicit stack, +so C functions get their parameters as normal C parameters. As a +general rule, C functions take constant @code{JSValue}s as parameters +(so they don't need to free them) and return a newly allocated (=live) +@code{JSValue}. + +@subsection Exceptions + +Exceptions: most C functions can return a Javascript exception. It +must be explicitly tested and handled by the C code. The specific +@code{JSValue} @code{JS_EXCEPTION} indicates that an exception +occurred. The actual exception object is stored in the +@code{JSContext} and can be retrieved with @code{JS_GetException()}. + +@subsection Script evaluation + +Use @code{JS_Eval()} to evaluate a script or module source. + +If the script or module was compiled to bytecode with @code{qjsc}, it +can be evaluated by calling @code{js_std_eval_binary()}. The advantage +is that no compilation is needed so it is faster and smaller because +the compiler can be removed from the executable if no @code{eval} is +required. + +Note: the bytecode format is linked to a given QuickJS +version. Moreover, no security check is done before its +execution. Hence the bytecode should not be loaded from untrusted +sources. That's why there is no option to output the bytecode to a +binary file in @code{qjsc}. + +@subsection JS Classes + +C opaque data can be attached to a Javascript object. The type of the +C opaque data is determined with the class ID (@code{JSClassID}) of +the object. Hence the first step is to register a new class ID and JS +class (@code{JS_NewClassID()}, @code{JS_NewClass()}). Then you can +create objects of this class with @code{JS_NewObjectClass()} and get or +set the C opaque point with +@code{JS_GetOpaque()}/@code{JS_SetOpaque()}. + +When defining a new JS class, it is possible to declare a finalizer +which is called when the object is destroyed. The finalizer should be +used to release C resources. It is invalid to execute JS code from +it. A @code{gc_mark} method can be provided so that the cycle removal +algorithm can find the other objects referenced by this object. Other +methods are available to define exotic object behaviors. + +The Class ID are globally allocated (i.e. for all runtimes). The +JSClass are allocated per @code{JSRuntime}. @code{JS_SetClassProto()} +is used to define a prototype for a given class in a given +JSContext. @code{JS_NewObjectClass()} sets this prototype in the +created object. + +Examples are available in @file{quickjs-libc.c}. + +@subsection C Modules + +Native ES6 modules are supported and can be dynamically or statically +linked. Look at the @file{test_bjson} and @file{bjson.so} +examples. The standard library @file{quickjs-libc.c} is also a good example +of a native module. + +@subsection Memory handling + +Use @code{JS_SetMemoryLimit()} to set a global memory allocation limit +to a given JSRuntime. + +Custom memory allocation functions can be provided with +@code{JS_NewRuntime2()}. + +The maximum system stack size can be set with @code{JS_SetMaxStackSize()}. + +@subsection Execution timeout and interrupts + +Use @code{JS_SetInterruptHandler()} to set a callback which is +regularly called by the engine when it is executing code. This +callback can be used to implement an execution timeout. + +It is used by the command line interpreter to implement a +@code{Ctrl-C} handler. + +@chapter Internals + +@section Bytecode + +The compiler generates bytecode directly with no intermediate +representation such as a parse tree, hence it is very fast. Several +optimizations passes are done over the generated bytecode. + +A stack-based bytecode was chosen because it is simple and generates +compact code. + +For each function, the maximum stack size is computed at compile time so that +no runtime stack overflow tests are needed. + +A separate compressed line number table is maintained for the debug +information. + +Access to closure variables is optimized and is almost as fast as local +variables. + +Direct @code{eval} in strict mode is optimized. + +@section Executable generation + +@subsection @code{qjsc} compiler + +The @code{qjsc} compiler generates C sources from Javascript files. By +default the C sources are compiled with the system compiler +(@code{gcc} or @code{clang}). + +The generated C source contains the bytecode of the compiled functions +or modules. If a full complete executable is needed, it also +contains a @code{main()} function with the necessary C code to initialize the +Javascript engine and to load and execute the compiled functions and +modules. + +Javascript code can be mixed with C modules. + +In order to have smaller executables, specific Javascript features can +be disabled, in particular @code{eval} or the regular expressions. The +code removal relies on the Link Time Optimization of the system +compiler. + +@subsection Binary JSON + +@code{qjsc} works by compiling scripts or modules and then serializing +them to a binary format. A subset of this format (without functions or +modules) can be used as binary JSON. The example @file{test_bjson.js} +shows how to use it. + +Warning: the binary JSON format may change without notice, so it +should not be used to store persistent data. The @file{test_bjson.js} +example is only used to test the binary object format functions. + +@section Runtime + +@subsection Strings + +Strings are stored either as an 8 bit or a 16 bit array of +characters. Hence random access to characters is always fast. + +The C API provides functions to convert Javascript Strings to C UTF-8 encoded +strings. The most common case where the Javascript string contains +only ASCII characters involves no copying. + +@subsection Objects + +The object shapes (object prototype, property names and flags) are shared +between objects to save memory. + +Arrays with no holes (except at the end of the array) are optimized. + +TypedArray accesses are optimized. + +@subsection Atoms + +Object property names and some strings are stored as Atoms (unique +strings) to save memory and allow fast comparison. Atoms are +represented as a 32 bit integer. Half of the atom range is reserved for +immediate integer literals from @math{0} to @math{2^{31}-1}. + +@subsection Numbers + +Numbers are represented either as 32-bit signed integers or 64-bit IEEE-754 +floating point values. Most operations have fast paths for the 32-bit +integer case. + +@subsection Garbage collection + +Reference counting is used to free objects automatically and +deterministically. A separate cycle removal pass is done when the allocated +memory becomes too large. The cycle removal algorithm only uses the +reference counts and the object content, so no explicit garbage +collection roots need to be manipulated in the C code. + +@subsection JSValue + +It is a Javascript value which can be a primitive type (such as +Number, String, ...) or an Object. NaN boxing is used in the 32-bit version +to store 64-bit floating point numbers. The representation is +optimized so that 32-bit integers and reference counted values can be +efficiently tested. + +In 64-bit code, JSValue are 128-bit large and no NaN boxing is used. The +rationale is that in 64-bit code memory usage is less critical. + +In both cases (32 or 64 bits), JSValue exactly fits two CPU registers, +so it can be efficiently returned by C functions. + +@subsection Function call + +The engine is optimized so that function calls are fast. The system +stack holds the Javascript parameters and local variables. + +@section RegExp + +A specific regular expression engine was developed. It is both small +and efficient and supports all the ES2023 features including the +Unicode properties. As the Javascript compiler, it directly generates +bytecode without a parse tree. + +Backtracking with an explicit stack is used so that there is no +recursion on the system stack. Simple quantifiers are specifically +optimized to avoid recursions. + +The full regexp library weights about 15 KiB (x86 code), excluding the +Unicode library. + +@section Unicode + +A specific Unicode library was developed so that there is no +dependency on an external large Unicode library such as ICU. All the +Unicode tables are compressed while keeping a reasonable access +speed. + +The library supports case conversion, Unicode normalization, Unicode +script queries, Unicode general category queries and all Unicode +binary properties. + +The full Unicode library weights about 45 KiB (x86 code). + +@section BigInt, BigFloat, BigDecimal + +BigInt, BigFloat and BigDecimal are implemented with the @code{libbf} +library@footnote{@url{https://bellard.org/libbf}}. It weights about 90 +KiB (x86 code) and provides arbitrary precision IEEE 754 floating +point operations and transcendental functions with exact rounding. + +@chapter License + +QuickJS is released under the MIT license. + +Unless otherwise specified, the QuickJS sources are copyright Fabrice +Bellard and Charlie Gordon. + +@bye diff --git a/source/engine/thirdparty/quickjs/examples/fib.c b/source/engine/thirdparty/quickjs/examples/fib.c new file mode 100644 index 0000000..be90af5 --- /dev/null +++ b/source/engine/thirdparty/quickjs/examples/fib.c @@ -0,0 +1,72 @@ +/* + * QuickJS: Example of C module + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * 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. + */ +#include "../quickjs.h" + +#define countof(x) (sizeof(x) / sizeof((x)[0])) + +static int fib(int n) +{ + if (n <= 0) + return 0; + else if (n == 1) + return 1; + else + return fib(n - 1) + fib(n - 2); +} + +static JSValue js_fib(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int n, res; + if (JS_ToInt32(ctx, &n, argv[0])) + return JS_EXCEPTION; + res = fib(n); + return JS_NewInt32(ctx, res); +} + +static const JSCFunctionListEntry js_fib_funcs[] = { + JS_CFUNC_DEF("fib", 1, js_fib ), +}; + +static int js_fib_init(JSContext *ctx, JSModuleDef *m) +{ + return JS_SetModuleExportList(ctx, m, js_fib_funcs, + countof(js_fib_funcs)); +} + +#ifdef JS_SHARED_LIBRARY +#define JS_INIT_MODULE js_init_module +#else +#define JS_INIT_MODULE js_init_module_fib +#endif + +JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) +{ + JSModuleDef *m; + m = JS_NewCModule(ctx, module_name, js_fib_init); + if (!m) + return NULL; + JS_AddModuleExportList(ctx, m, js_fib_funcs, countof(js_fib_funcs)); + return m; +} diff --git a/source/engine/thirdparty/quickjs/examples/fib_module.js b/source/engine/thirdparty/quickjs/examples/fib_module.js new file mode 100644 index 0000000..6a81071 --- /dev/null +++ b/source/engine/thirdparty/quickjs/examples/fib_module.js @@ -0,0 +1,10 @@ +/* fib module */ +export function fib(n) +{ + if (n <= 0) + return 0; + else if (n == 1) + return 1; + else + return fib(n - 1) + fib(n - 2); +} diff --git a/source/engine/thirdparty/quickjs/examples/hello.js b/source/engine/thirdparty/quickjs/examples/hello.js new file mode 100644 index 0000000..accefce --- /dev/null +++ b/source/engine/thirdparty/quickjs/examples/hello.js @@ -0,0 +1 @@ +console.log("Hello World"); diff --git a/source/engine/thirdparty/quickjs/examples/hello_module.js b/source/engine/thirdparty/quickjs/examples/hello_module.js new file mode 100644 index 0000000..463660f --- /dev/null +++ b/source/engine/thirdparty/quickjs/examples/hello_module.js @@ -0,0 +1,6 @@ +/* example of JS module */ + +import { fib } from "./fib_module.js"; + +console.log("Hello World"); +console.log("fib(10)=", fib(10)); diff --git a/source/engine/thirdparty/quickjs/examples/pi_bigdecimal.js b/source/engine/thirdparty/quickjs/examples/pi_bigdecimal.js new file mode 100644 index 0000000..7cb7ad6 --- /dev/null +++ b/source/engine/thirdparty/quickjs/examples/pi_bigdecimal.js @@ -0,0 +1,68 @@ +/* + * PI computation in Javascript using the QuickJS bigdecimal type + * (decimal floating point) + */ +"use strict"; + +/* compute PI with a precision of 'prec' digits */ +function calc_pi(prec) { + const CHUD_A = 13591409m; + const CHUD_B = 545140134m; + const CHUD_C = 640320m; + const CHUD_C3 = 10939058860032000m; /* C^3/24 */ + const CHUD_DIGITS_PER_TERM = 14.18164746272548; /* log10(C/12)*3 */ + + /* return [P, Q, G] */ + function chud_bs(a, b, need_G) { + var c, P, Q, G, P1, Q1, G1, P2, Q2, G2, b1; + if (a == (b - 1n)) { + b1 = BigDecimal(b); + G = (2m * b1 - 1m) * (6m * b1 - 1m) * (6m * b1 - 5m); + P = G * (CHUD_B * b1 + CHUD_A); + if (b & 1n) + P = -P; + G = G; + Q = b1 * b1 * b1 * CHUD_C3; + } else { + c = (a + b) >> 1n; + [P1, Q1, G1] = chud_bs(a, c, true); + [P2, Q2, G2] = chud_bs(c, b, need_G); + P = P1 * Q2 + P2 * G1; + Q = Q1 * Q2; + if (need_G) + G = G1 * G2; + else + G = 0m; + } + return [P, Q, G]; + } + + var n, P, Q, G; + /* number of serie terms */ + n = BigInt(Math.ceil(prec / CHUD_DIGITS_PER_TERM)) + 10n; + [P, Q, G] = chud_bs(0n, n, false); + Q = BigDecimal.div(Q, (P + Q * CHUD_A), + { roundingMode: "half-even", + maximumSignificantDigits: prec }); + G = (CHUD_C / 12m) * BigDecimal.sqrt(CHUD_C, + { roundingMode: "half-even", + maximumSignificantDigits: prec }); + return Q * G; +} + +(function() { + var r, n_digits, n_bits; + if (typeof scriptArgs != "undefined") { + if (scriptArgs.length < 2) { + print("usage: pi n_digits"); + return; + } + n_digits = scriptArgs[1] | 0; + } else { + n_digits = 1000; + } + /* we add more digits to reduce the probability of bad rounding for + the last digits */ + r = calc_pi(n_digits + 20); + print(r.toFixed(n_digits, "down")); +})(); diff --git a/source/engine/thirdparty/quickjs/examples/pi_bigfloat.js b/source/engine/thirdparty/quickjs/examples/pi_bigfloat.js new file mode 100644 index 0000000..8372379 --- /dev/null +++ b/source/engine/thirdparty/quickjs/examples/pi_bigfloat.js @@ -0,0 +1,66 @@ +/* + * PI computation in Javascript using the QuickJS bigfloat type + * (binary floating point) + */ +"use strict"; + +/* compute PI with a precision of 'prec' bits */ +function calc_pi() { + const CHUD_A = 13591409n; + const CHUD_B = 545140134n; + const CHUD_C = 640320n; + const CHUD_C3 = 10939058860032000n; /* C^3/24 */ + const CHUD_BITS_PER_TERM = 47.11041313821584202247; /* log2(C/12)*3 */ + + /* return [P, Q, G] */ + function chud_bs(a, b, need_G) { + var c, P, Q, G, P1, Q1, G1, P2, Q2, G2; + if (a == (b - 1n)) { + G = (2n * b - 1n) * (6n * b - 1n) * (6n * b - 5n); + P = BigFloat(G * (CHUD_B * b + CHUD_A)); + if (b & 1n) + P = -P; + G = BigFloat(G); + Q = BigFloat(b * b * b * CHUD_C3); + } else { + c = (a + b) >> 1n; + [P1, Q1, G1] = chud_bs(a, c, true); + [P2, Q2, G2] = chud_bs(c, b, need_G); + P = P1 * Q2 + P2 * G1; + Q = Q1 * Q2; + if (need_G) + G = G1 * G2; + else + G = 0l; + } + return [P, Q, G]; + } + + var n, P, Q, G; + /* number of serie terms */ + n = BigInt(Math.ceil(BigFloatEnv.prec / CHUD_BITS_PER_TERM)) + 10n; + [P, Q, G] = chud_bs(0n, n, false); + Q = Q / (P + Q * BigFloat(CHUD_A)); + G = BigFloat((CHUD_C / 12n)) * BigFloat.sqrt(BigFloat(CHUD_C)); + return Q * G; +} + +(function() { + var r, n_digits, n_bits; + if (typeof scriptArgs != "undefined") { + if (scriptArgs.length < 2) { + print("usage: pi n_digits"); + return; + } + n_digits = scriptArgs[1]; + } else { + n_digits = 1000; + } + n_bits = Math.ceil(n_digits * Math.log2(10)); + /* we add more bits to reduce the probability of bad rounding for + the last digits */ + BigFloatEnv.setPrec( () => { + r = calc_pi(); + print(r.toFixed(n_digits, BigFloatEnv.RNDZ)); + }, n_bits + 32); +})(); diff --git a/source/engine/thirdparty/quickjs/examples/pi_bigint.js b/source/engine/thirdparty/quickjs/examples/pi_bigint.js new file mode 100644 index 0000000..e15abd1 --- /dev/null +++ b/source/engine/thirdparty/quickjs/examples/pi_bigint.js @@ -0,0 +1,118 @@ +/* + * PI computation in Javascript using the BigInt type + */ +"use strict"; + +/* return floor(log2(a)) for a > 0 and 0 for a = 0 */ +function floor_log2(a) +{ + var k_max, a1, k, i; + k_max = 0n; + while ((a >> (2n ** k_max)) != 0n) { + k_max++; + } + k = 0n; + a1 = a; + for(i = k_max - 1n; i >= 0n; i--) { + a1 = a >> (2n ** i); + if (a1 != 0n) { + a = a1; + k |= (1n << i); + } + } + return k; +} + +/* return ceil(log2(a)) for a > 0 */ +function ceil_log2(a) +{ + return floor_log2(a - 1n) + 1n; +} + +/* return floor(sqrt(a)) (not efficient but simple) */ +function int_sqrt(a) +{ + var l, u, s; + if (a == 0n) + return a; + l = ceil_log2(a); + u = 1n << ((l + 1n) / 2n); + /* u >= floor(sqrt(a)) */ + for(;;) { + s = u; + u = ((a / s) + s) / 2n; + if (u >= s) + break; + } + return s; +} + +/* return pi * 2**prec */ +function calc_pi(prec) { + const CHUD_A = 13591409n; + const CHUD_B = 545140134n; + const CHUD_C = 640320n; + const CHUD_C3 = 10939058860032000n; /* C^3/24 */ + const CHUD_BITS_PER_TERM = 47.11041313821584202247; /* log2(C/12)*3 */ + + /* return [P, Q, G] */ + function chud_bs(a, b, need_G) { + var c, P, Q, G, P1, Q1, G1, P2, Q2, G2; + if (a == (b - 1n)) { + G = (2n * b - 1n) * (6n * b - 1n) * (6n * b - 5n); + P = G * (CHUD_B * b + CHUD_A); + if (b & 1n) + P = -P; + Q = b * b * b * CHUD_C3; + } else { + c = (a + b) >> 1n; + [P1, Q1, G1] = chud_bs(a, c, true); + [P2, Q2, G2] = chud_bs(c, b, need_G); + P = P1 * Q2 + P2 * G1; + Q = Q1 * Q2; + if (need_G) + G = G1 * G2; + else + G = 0n; + } + return [P, Q, G]; + } + + var n, P, Q, G; + /* number of serie terms */ + n = BigInt(Math.ceil(Number(prec) / CHUD_BITS_PER_TERM)) + 10n; + [P, Q, G] = chud_bs(0n, n, false); + Q = (CHUD_C / 12n) * (Q << prec) / (P + Q * CHUD_A); + G = int_sqrt(CHUD_C << (2n * prec)); + return (Q * G) >> prec; +} + +function main(args) { + var r, n_digits, n_bits, out; + if (args.length < 1) { + print("usage: pi n_digits"); + return; + } + n_digits = args[0] | 0; + + /* we add more bits to reduce the probability of bad rounding for + the last digits */ + n_bits = BigInt(Math.ceil(n_digits * Math.log2(10))) + 32n; + r = calc_pi(n_bits); + r = ((10n ** BigInt(n_digits)) * r) >> n_bits; + out = r.toString(); + print(out[0] + "." + out.slice(1)); +} + +var args; +if (typeof scriptArgs != "undefined") { + args = scriptArgs; + args.shift(); +} else if (typeof arguments != "undefined") { + args = arguments; +} else { + /* default: 1000 digits */ + args=[1000]; +} + +main(args); diff --git a/source/engine/thirdparty/quickjs/examples/point.c b/source/engine/thirdparty/quickjs/examples/point.c new file mode 100644 index 0000000..0386230 --- /dev/null +++ b/source/engine/thirdparty/quickjs/examples/point.c @@ -0,0 +1,151 @@ +/* + * QuickJS: Example of C module with a class + * + * Copyright (c) 2019 Fabrice Bellard + * + * 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. + */ +#include "../quickjs.h" +#include + +#define countof(x) (sizeof(x) / sizeof((x)[0])) + +/* Point Class */ + +typedef struct { + int x; + int y; +} JSPointData; + +static JSClassID js_point_class_id; + +static void js_point_finalizer(JSRuntime *rt, JSValue val) +{ + JSPointData *s = JS_GetOpaque(val, js_point_class_id); + /* Note: 's' can be NULL in case JS_SetOpaque() was not called */ + js_free_rt(rt, s); +} + +static JSValue js_point_ctor(JSContext *ctx, + JSValueConst new_target, + int argc, JSValueConst *argv) +{ + JSPointData *s; + JSValue obj = JS_UNDEFINED; + JSValue proto; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &s->x, argv[0])) + goto fail; + if (JS_ToInt32(ctx, &s->y, argv[1])) + goto fail; + /* using new_target to get the prototype is necessary when the + class is extended. */ + proto = JS_GetPropertyStr(ctx, new_target, "prototype"); + if (JS_IsException(proto)) + goto fail; + obj = JS_NewObjectProtoClass(ctx, proto, js_point_class_id); + JS_FreeValue(ctx, proto); + if (JS_IsException(obj)) + goto fail; + JS_SetOpaque(obj, s); + return obj; + fail: + js_free(ctx, s); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_point_get_xy(JSContext *ctx, JSValueConst this_val, int magic) +{ + JSPointData *s = JS_GetOpaque2(ctx, this_val, js_point_class_id); + if (!s) + return JS_EXCEPTION; + if (magic == 0) + return JS_NewInt32(ctx, s->x); + else + return JS_NewInt32(ctx, s->y); +} + +static JSValue js_point_set_xy(JSContext *ctx, JSValueConst this_val, JSValue val, int magic) +{ + JSPointData *s = JS_GetOpaque2(ctx, this_val, js_point_class_id); + int v; + if (!s) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &v, val)) + return JS_EXCEPTION; + if (magic == 0) + s->x = v; + else + s->y = v; + return JS_UNDEFINED; +} + +static JSValue js_point_norm(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSPointData *s = JS_GetOpaque2(ctx, this_val, js_point_class_id); + if (!s) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, sqrt((double)s->x * s->x + (double)s->y * s->y)); +} + +static JSClassDef js_point_class = { + "Point", + .finalizer = js_point_finalizer, +}; + +static const JSCFunctionListEntry js_point_proto_funcs[] = { + JS_CGETSET_MAGIC_DEF("x", js_point_get_xy, js_point_set_xy, 0), + JS_CGETSET_MAGIC_DEF("y", js_point_get_xy, js_point_set_xy, 1), + JS_CFUNC_DEF("norm", 0, js_point_norm), +}; + +static int js_point_init(JSContext *ctx, JSModuleDef *m) +{ + JSValue point_proto, point_class; + + /* create the Point class */ + JS_NewClassID(&js_point_class_id); + JS_NewClass(JS_GetRuntime(ctx), js_point_class_id, &js_point_class); + + point_proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, point_proto, js_point_proto_funcs, countof(js_point_proto_funcs)); + + point_class = JS_NewCFunction2(ctx, js_point_ctor, "Point", 2, JS_CFUNC_constructor, 0); + /* set proto.constructor and ctor.prototype */ + JS_SetConstructor(ctx, point_class, point_proto); + JS_SetClassProto(ctx, js_point_class_id, point_proto); + + JS_SetModuleExport(ctx, m, "Point", point_class); + return 0; +} + +JSModuleDef *js_init_module(JSContext *ctx, const char *module_name) +{ + JSModuleDef *m; + m = JS_NewCModule(ctx, module_name, js_point_init); + if (!m) + return NULL; + JS_AddModuleExport(ctx, m, "Point"); + return m; +} diff --git a/source/engine/thirdparty/quickjs/examples/test_fib.js b/source/engine/thirdparty/quickjs/examples/test_fib.js new file mode 100644 index 0000000..70d26bd --- /dev/null +++ b/source/engine/thirdparty/quickjs/examples/test_fib.js @@ -0,0 +1,6 @@ +/* example of JS module importing a C module */ + +import { fib } from "./fib.so"; + +console.log("Hello World"); +console.log("fib(10)=", fib(10)); diff --git a/source/engine/thirdparty/quickjs/examples/test_point.js b/source/engine/thirdparty/quickjs/examples/test_point.js new file mode 100644 index 0000000..0659bc3 --- /dev/null +++ b/source/engine/thirdparty/quickjs/examples/test_point.js @@ -0,0 +1,40 @@ +/* example of JS module importing a C module */ +import { Point } from "./point.so"; + +function assert(b, str) +{ + if (b) { + return; + } else { + throw Error("assertion failed: " + str); + } +} + +class ColorPoint extends Point { + constructor(x, y, color) { + super(x, y); + this.color = color; + } + get_color() { + return this.color; + } +}; + +function main() +{ + var pt, pt2; + + pt = new Point(2, 3); + assert(pt.x === 2); + assert(pt.y === 3); + pt.x = 4; + assert(pt.x === 4); + assert(pt.norm() == 5); + + pt2 = new ColorPoint(2, 3, 0xffffff); + assert(pt2.x === 2); + assert(pt2.color === 0xffffff); + assert(pt2.get_color() === 0xffffff); +} + +main(); diff --git a/source/engine/thirdparty/quickjs/libbf.c b/source/engine/thirdparty/quickjs/libbf.c new file mode 100644 index 0000000..dee394a --- /dev/null +++ b/source/engine/thirdparty/quickjs/libbf.c @@ -0,0 +1,8473 @@ +/* + * Tiny arbitrary precision floating point library + * + * Copyright (c) 2017-2021 Fabrice Bellard + * + * 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. + */ +#include +#include +#include +#include +#include +#include + +#ifdef __AVX2__ +#include +#endif + +#include "cutils.h" +#include "libbf.h" + +/* enable it to check the multiplication result */ +//#define USE_MUL_CHECK +#ifdef CONFIG_BIGNUM +/* enable it to use FFT/NTT multiplication */ +#define USE_FFT_MUL +/* enable decimal floating point support */ +#define USE_BF_DEC +#endif + +//#define inline __attribute__((always_inline)) + +#ifdef __AVX2__ +#define FFT_MUL_THRESHOLD 100 /* in limbs of the smallest factor */ +#else +#define FFT_MUL_THRESHOLD 100 /* in limbs of the smallest factor */ +#endif + +/* XXX: adjust */ +#define DIVNORM_LARGE_THRESHOLD 50 +#define UDIV1NORM_THRESHOLD 3 + +#if LIMB_BITS == 64 +#define FMT_LIMB1 "%" PRIx64 +#define FMT_LIMB "%016" PRIx64 +#define PRId_LIMB PRId64 +#define PRIu_LIMB PRIu64 + +#else + +#define FMT_LIMB1 "%x" +#define FMT_LIMB "%08x" +#define PRId_LIMB "d" +#define PRIu_LIMB "u" + +#endif + +typedef intptr_t mp_size_t; + +typedef int bf_op2_func_t(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags); + +#ifdef USE_FFT_MUL + +#define FFT_MUL_R_OVERLAP_A (1 << 0) +#define FFT_MUL_R_OVERLAP_B (1 << 1) +#define FFT_MUL_R_NORESIZE (1 << 2) + +static no_inline int fft_mul(bf_context_t *s, + bf_t *res, limb_t *a_tab, limb_t a_len, + limb_t *b_tab, limb_t b_len, int mul_flags); +static void fft_clear_cache(bf_context_t *s); +#endif +#ifdef USE_BF_DEC +static limb_t get_digit(const limb_t *tab, limb_t len, slimb_t pos); +#endif + + +/* could leading zeros */ +static inline int clz(limb_t a) +{ + if (a == 0) { + return LIMB_BITS; + } else { +#if LIMB_BITS == 64 + return clz64(a); +#else + return clz32(a); +#endif + } +} + +static inline int ctz(limb_t a) +{ + if (a == 0) { + return LIMB_BITS; + } else { +#if LIMB_BITS == 64 + return ctz64(a); +#else + return ctz32(a); +#endif + } +} + +static inline int ceil_log2(limb_t a) +{ + if (a <= 1) + return 0; + else + return LIMB_BITS - clz(a - 1); +} + +/* b must be >= 1 */ +static inline slimb_t ceil_div(slimb_t a, slimb_t b) +{ + if (a >= 0) + return (a + b - 1) / b; + else + return a / b; +} + +/* b must be >= 1 */ +static inline slimb_t floor_div(slimb_t a, slimb_t b) +{ + if (a >= 0) { + return a / b; + } else { + return (a - b + 1) / b; + } +} + +/* return r = a modulo b (0 <= r <= b - 1. b must be >= 1 */ +static inline limb_t smod(slimb_t a, slimb_t b) +{ + a = a % (slimb_t)b; + if (a < 0) + a += b; + return a; +} + +/* signed addition with saturation */ +static inline slimb_t sat_add(slimb_t a, slimb_t b) +{ + slimb_t r; + r = a + b; + /* overflow ? */ + if (((a ^ r) & (b ^ r)) < 0) + r = (a >> (LIMB_BITS - 1)) ^ (((limb_t)1 << (LIMB_BITS - 1)) - 1); + return r; +} + +static inline __maybe_unused limb_t shrd(limb_t low, limb_t high, long shift) +{ + if (shift != 0) + low = (low >> shift) | (high << (LIMB_BITS - shift)); + return low; +} + +static inline __maybe_unused limb_t shld(limb_t a1, limb_t a0, long shift) +{ + if (shift != 0) + return (a1 << shift) | (a0 >> (LIMB_BITS - shift)); + else + return a1; +} + +#define malloc(s) malloc_is_forbidden(s) +#define free(p) free_is_forbidden(p) +#define realloc(p, s) realloc_is_forbidden(p, s) + +void bf_context_init(bf_context_t *s, bf_realloc_func_t *realloc_func, + void *realloc_opaque) +{ + memset(s, 0, sizeof(*s)); + s->realloc_func = realloc_func; + s->realloc_opaque = realloc_opaque; +} + +void bf_context_end(bf_context_t *s) +{ + bf_clear_cache(s); +} + +void bf_init(bf_context_t *s, bf_t *r) +{ + r->ctx = s; + r->sign = 0; + r->expn = BF_EXP_ZERO; + r->len = 0; + r->tab = NULL; +} + +/* return 0 if OK, -1 if alloc error */ +int bf_resize(bf_t *r, limb_t len) +{ + limb_t *tab; + + if (len != r->len) { + tab = bf_realloc(r->ctx, r->tab, len * sizeof(limb_t)); + if (!tab && len != 0) + return -1; + r->tab = tab; + r->len = len; + } + return 0; +} + +/* return 0 or BF_ST_MEM_ERROR */ +int bf_set_ui(bf_t *r, uint64_t a) +{ + r->sign = 0; + if (a == 0) { + r->expn = BF_EXP_ZERO; + bf_resize(r, 0); /* cannot fail */ + } +#if LIMB_BITS == 32 + else if (a <= 0xffffffff) +#else + else +#endif + { + int shift; + if (bf_resize(r, 1)) + goto fail; + shift = clz(a); + r->tab[0] = a << shift; + r->expn = LIMB_BITS - shift; + } +#if LIMB_BITS == 32 + else { + uint32_t a1, a0; + int shift; + if (bf_resize(r, 2)) + goto fail; + a0 = a; + a1 = a >> 32; + shift = clz(a1); + r->tab[0] = a0 << shift; + r->tab[1] = shld(a1, a0, shift); + r->expn = 2 * LIMB_BITS - shift; + } +#endif + return 0; + fail: + bf_set_nan(r); + return BF_ST_MEM_ERROR; +} + +/* return 0 or BF_ST_MEM_ERROR */ +int bf_set_si(bf_t *r, int64_t a) +{ + int ret; + + if (a < 0) { + ret = bf_set_ui(r, -a); + r->sign = 1; + } else { + ret = bf_set_ui(r, a); + } + return ret; +} + +void bf_set_nan(bf_t *r) +{ + bf_resize(r, 0); /* cannot fail */ + r->expn = BF_EXP_NAN; + r->sign = 0; +} + +void bf_set_zero(bf_t *r, int is_neg) +{ + bf_resize(r, 0); /* cannot fail */ + r->expn = BF_EXP_ZERO; + r->sign = is_neg; +} + +void bf_set_inf(bf_t *r, int is_neg) +{ + bf_resize(r, 0); /* cannot fail */ + r->expn = BF_EXP_INF; + r->sign = is_neg; +} + +/* return 0 or BF_ST_MEM_ERROR */ +int bf_set(bf_t *r, const bf_t *a) +{ + if (r == a) + return 0; + if (bf_resize(r, a->len)) { + bf_set_nan(r); + return BF_ST_MEM_ERROR; + } + r->sign = a->sign; + r->expn = a->expn; + memcpy_no_ub(r->tab, a->tab, a->len * sizeof(limb_t)); + return 0; +} + +/* equivalent to bf_set(r, a); bf_delete(a) */ +void bf_move(bf_t *r, bf_t *a) +{ + bf_context_t *s = r->ctx; + if (r == a) + return; + bf_free(s, r->tab); + *r = *a; +} + +static limb_t get_limbz(const bf_t *a, limb_t idx) +{ + if (idx >= a->len) + return 0; + else + return a->tab[idx]; +} + +/* get LIMB_BITS at bit position 'pos' in tab */ +static inline limb_t get_bits(const limb_t *tab, limb_t len, slimb_t pos) +{ + limb_t i, a0, a1; + int p; + + i = pos >> LIMB_LOG2_BITS; + p = pos & (LIMB_BITS - 1); + if (i < len) + a0 = tab[i]; + else + a0 = 0; + if (p == 0) { + return a0; + } else { + i++; + if (i < len) + a1 = tab[i]; + else + a1 = 0; + return (a0 >> p) | (a1 << (LIMB_BITS - p)); + } +} + +static inline limb_t get_bit(const limb_t *tab, limb_t len, slimb_t pos) +{ + slimb_t i; + i = pos >> LIMB_LOG2_BITS; + if (i < 0 || i >= len) + return 0; + return (tab[i] >> (pos & (LIMB_BITS - 1))) & 1; +} + +static inline limb_t limb_mask(int start, int last) +{ + limb_t v; + int n; + n = last - start + 1; + if (n == LIMB_BITS) + v = -1; + else + v = (((limb_t)1 << n) - 1) << start; + return v; +} + +static limb_t mp_scan_nz(const limb_t *tab, mp_size_t n) +{ + mp_size_t i; + for(i = 0; i < n; i++) { + if (tab[i] != 0) + return 1; + } + return 0; +} + +/* return != 0 if one bit between 0 and bit_pos inclusive is not zero. */ +static inline limb_t scan_bit_nz(const bf_t *r, slimb_t bit_pos) +{ + slimb_t pos; + limb_t v; + + pos = bit_pos >> LIMB_LOG2_BITS; + if (pos < 0) + return 0; + v = r->tab[pos] & limb_mask(0, bit_pos & (LIMB_BITS - 1)); + if (v != 0) + return 1; + pos--; + while (pos >= 0) { + if (r->tab[pos] != 0) + return 1; + pos--; + } + return 0; +} + +/* return the addend for rounding. Note that prec can be <= 0 (for + BF_FLAG_RADPNT_PREC) */ +static int bf_get_rnd_add(int *pret, const bf_t *r, limb_t l, + slimb_t prec, int rnd_mode) +{ + int add_one, inexact; + limb_t bit1, bit0; + + if (rnd_mode == BF_RNDF) { + bit0 = 1; /* faithful rounding does not honor the INEXACT flag */ + } else { + /* starting limb for bit 'prec + 1' */ + bit0 = scan_bit_nz(r, l * LIMB_BITS - 1 - bf_max(0, prec + 1)); + } + + /* get the bit at 'prec' */ + bit1 = get_bit(r->tab, l, l * LIMB_BITS - 1 - prec); + inexact = (bit1 | bit0) != 0; + + add_one = 0; + switch(rnd_mode) { + case BF_RNDZ: + break; + case BF_RNDN: + if (bit1) { + if (bit0) { + add_one = 1; + } else { + /* round to even */ + add_one = + get_bit(r->tab, l, l * LIMB_BITS - 1 - (prec - 1)); + } + } + break; + case BF_RNDD: + case BF_RNDU: + if (r->sign == (rnd_mode == BF_RNDD)) + add_one = inexact; + break; + case BF_RNDA: + add_one = inexact; + break; + case BF_RNDNA: + case BF_RNDF: + add_one = bit1; + break; + default: + abort(); + } + + if (inexact) + *pret |= BF_ST_INEXACT; + return add_one; +} + +static int bf_set_overflow(bf_t *r, int sign, limb_t prec, bf_flags_t flags) +{ + slimb_t i, l, e_max; + int rnd_mode; + + rnd_mode = flags & BF_RND_MASK; + if (prec == BF_PREC_INF || + rnd_mode == BF_RNDN || + rnd_mode == BF_RNDNA || + rnd_mode == BF_RNDA || + (rnd_mode == BF_RNDD && sign == 1) || + (rnd_mode == BF_RNDU && sign == 0)) { + bf_set_inf(r, sign); + } else { + /* set to maximum finite number */ + l = (prec + LIMB_BITS - 1) / LIMB_BITS; + if (bf_resize(r, l)) { + bf_set_nan(r); + return BF_ST_MEM_ERROR; + } + r->tab[0] = limb_mask((-prec) & (LIMB_BITS - 1), + LIMB_BITS - 1); + for(i = 1; i < l; i++) + r->tab[i] = (limb_t)-1; + e_max = (limb_t)1 << (bf_get_exp_bits(flags) - 1); + r->expn = e_max; + r->sign = sign; + } + return BF_ST_OVERFLOW | BF_ST_INEXACT; +} + +/* round to prec1 bits assuming 'r' is non zero and finite. 'r' is + assumed to have length 'l' (1 <= l <= r->len). Note: 'prec1' can be + infinite (BF_PREC_INF). 'ret' is 0 or BF_ST_INEXACT if the result + is known to be inexact. Can fail with BF_ST_MEM_ERROR in case of + overflow not returning infinity. */ +static int __bf_round(bf_t *r, limb_t prec1, bf_flags_t flags, limb_t l, + int ret) +{ + limb_t v, a; + int shift, add_one, rnd_mode; + slimb_t i, bit_pos, pos, e_min, e_max, e_range, prec; + + /* e_min and e_max are computed to match the IEEE 754 conventions */ + e_range = (limb_t)1 << (bf_get_exp_bits(flags) - 1); + e_min = -e_range + 3; + e_max = e_range; + + if (flags & BF_FLAG_RADPNT_PREC) { + /* 'prec' is the precision after the radix point */ + if (prec1 != BF_PREC_INF) + prec = r->expn + prec1; + else + prec = prec1; + } else if (unlikely(r->expn < e_min) && (flags & BF_FLAG_SUBNORMAL)) { + /* restrict the precision in case of potentially subnormal + result */ + assert(prec1 != BF_PREC_INF); + prec = prec1 - (e_min - r->expn); + } else { + prec = prec1; + } + + /* round to prec bits */ + rnd_mode = flags & BF_RND_MASK; + add_one = bf_get_rnd_add(&ret, r, l, prec, rnd_mode); + + if (prec <= 0) { + if (add_one) { + bf_resize(r, 1); /* cannot fail */ + r->tab[0] = (limb_t)1 << (LIMB_BITS - 1); + r->expn += 1 - prec; + ret |= BF_ST_UNDERFLOW | BF_ST_INEXACT; + return ret; + } else { + goto underflow; + } + } else if (add_one) { + limb_t carry; + + /* add one starting at digit 'prec - 1' */ + bit_pos = l * LIMB_BITS - 1 - (prec - 1); + pos = bit_pos >> LIMB_LOG2_BITS; + carry = (limb_t)1 << (bit_pos & (LIMB_BITS - 1)); + + for(i = pos; i < l; i++) { + v = r->tab[i] + carry; + carry = (v < carry); + r->tab[i] = v; + if (carry == 0) + break; + } + if (carry) { + /* shift right by one digit */ + v = 1; + for(i = l - 1; i >= pos; i--) { + a = r->tab[i]; + r->tab[i] = (a >> 1) | (v << (LIMB_BITS - 1)); + v = a; + } + r->expn++; + } + } + + /* check underflow */ + if (unlikely(r->expn < e_min)) { + if (flags & BF_FLAG_SUBNORMAL) { + /* if inexact, also set the underflow flag */ + if (ret & BF_ST_INEXACT) + ret |= BF_ST_UNDERFLOW; + } else { + underflow: + ret |= BF_ST_UNDERFLOW | BF_ST_INEXACT; + bf_set_zero(r, r->sign); + return ret; + } + } + + /* check overflow */ + if (unlikely(r->expn > e_max)) + return bf_set_overflow(r, r->sign, prec1, flags); + + /* keep the bits starting at 'prec - 1' */ + bit_pos = l * LIMB_BITS - 1 - (prec - 1); + i = bit_pos >> LIMB_LOG2_BITS; + if (i >= 0) { + shift = bit_pos & (LIMB_BITS - 1); + if (shift != 0) + r->tab[i] &= limb_mask(shift, LIMB_BITS - 1); + } else { + i = 0; + } + /* remove trailing zeros */ + while (r->tab[i] == 0) + i++; + if (i > 0) { + l -= i; + memmove(r->tab, r->tab + i, l * sizeof(limb_t)); + } + bf_resize(r, l); /* cannot fail */ + return ret; +} + +/* 'r' must be a finite number. */ +int bf_normalize_and_round(bf_t *r, limb_t prec1, bf_flags_t flags) +{ + limb_t l, v, a; + int shift, ret; + slimb_t i; + + // bf_print_str("bf_renorm", r); + l = r->len; + while (l > 0 && r->tab[l - 1] == 0) + l--; + if (l == 0) { + /* zero */ + r->expn = BF_EXP_ZERO; + bf_resize(r, 0); /* cannot fail */ + ret = 0; + } else { + r->expn -= (r->len - l) * LIMB_BITS; + /* shift to have the MSB set to '1' */ + v = r->tab[l - 1]; + shift = clz(v); + if (shift != 0) { + v = 0; + for(i = 0; i < l; i++) { + a = r->tab[i]; + r->tab[i] = (a << shift) | (v >> (LIMB_BITS - shift)); + v = a; + } + r->expn -= shift; + } + ret = __bf_round(r, prec1, flags, l, 0); + } + // bf_print_str("r_final", r); + return ret; +} + +/* return true if rounding can be done at precision 'prec' assuming + the exact result r is such that |r-a| <= 2^(EXP(a)-k). */ +/* XXX: check the case where the exponent would be incremented by the + rounding */ +int bf_can_round(const bf_t *a, slimb_t prec, bf_rnd_t rnd_mode, slimb_t k) +{ + BOOL is_rndn; + slimb_t bit_pos, n; + limb_t bit; + + if (a->expn == BF_EXP_INF || a->expn == BF_EXP_NAN) + return FALSE; + if (rnd_mode == BF_RNDF) { + return (k >= (prec + 1)); + } + if (a->expn == BF_EXP_ZERO) + return FALSE; + is_rndn = (rnd_mode == BF_RNDN || rnd_mode == BF_RNDNA); + if (k < (prec + 2)) + return FALSE; + bit_pos = a->len * LIMB_BITS - 1 - prec; + n = k - prec; + /* bit pattern for RNDN or RNDNA: 0111.. or 1000... + for other rounding modes: 000... or 111... + */ + bit = get_bit(a->tab, a->len, bit_pos); + bit_pos--; + n--; + bit ^= is_rndn; + /* XXX: slow, but a few iterations on average */ + while (n != 0) { + if (get_bit(a->tab, a->len, bit_pos) != bit) + return TRUE; + bit_pos--; + n--; + } + return FALSE; +} + +/* Cannot fail with BF_ST_MEM_ERROR. */ +int bf_round(bf_t *r, limb_t prec, bf_flags_t flags) +{ + if (r->len == 0) + return 0; + return __bf_round(r, prec, flags, r->len, 0); +} + +/* for debugging */ +static __maybe_unused void dump_limbs(const char *str, const limb_t *tab, limb_t n) +{ + limb_t i; + printf("%s: len=%" PRId_LIMB "\n", str, n); + for(i = 0; i < n; i++) { + printf("%" PRId_LIMB ": " FMT_LIMB "\n", + i, tab[i]); + } +} + +void mp_print_str(const char *str, const limb_t *tab, limb_t n) +{ + slimb_t i; + printf("%s= 0x", str); + for(i = n - 1; i >= 0; i--) { + if (i != (n - 1)) + printf("_"); + printf(FMT_LIMB, tab[i]); + } + printf("\n"); +} + +static __maybe_unused void mp_print_str_h(const char *str, + const limb_t *tab, limb_t n, + limb_t high) +{ + slimb_t i; + printf("%s= 0x", str); + printf(FMT_LIMB, high); + for(i = n - 1; i >= 0; i--) { + printf("_"); + printf(FMT_LIMB, tab[i]); + } + printf("\n"); +} + +/* for debugging */ +void bf_print_str(const char *str, const bf_t *a) +{ + slimb_t i; + printf("%s=", str); + + if (a->expn == BF_EXP_NAN) { + printf("NaN"); + } else { + if (a->sign) + putchar('-'); + if (a->expn == BF_EXP_ZERO) { + putchar('0'); + } else if (a->expn == BF_EXP_INF) { + printf("Inf"); + } else { + printf("0x0."); + for(i = a->len - 1; i >= 0; i--) + printf(FMT_LIMB, a->tab[i]); + printf("p%" PRId_LIMB, a->expn); + } + } + printf("\n"); +} + +/* compare the absolute value of 'a' and 'b'. Return < 0 if a < b, 0 + if a = b and > 0 otherwise. */ +int bf_cmpu(const bf_t *a, const bf_t *b) +{ + slimb_t i; + limb_t len, v1, v2; + + if (a->expn != b->expn) { + if (a->expn < b->expn) + return -1; + else + return 1; + } + len = bf_max(a->len, b->len); + for(i = len - 1; i >= 0; i--) { + v1 = get_limbz(a, a->len - len + i); + v2 = get_limbz(b, b->len - len + i); + if (v1 != v2) { + if (v1 < v2) + return -1; + else + return 1; + } + } + return 0; +} + +/* Full order: -0 < 0, NaN == NaN and NaN is larger than all other numbers */ +int bf_cmp_full(const bf_t *a, const bf_t *b) +{ + int res; + + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + if (a->expn == b->expn) + res = 0; + else if (a->expn == BF_EXP_NAN) + res = 1; + else + res = -1; + } else if (a->sign != b->sign) { + res = 1 - 2 * a->sign; + } else { + res = bf_cmpu(a, b); + if (a->sign) + res = -res; + } + return res; +} + +/* Standard floating point comparison: return 2 if one of the operands + is NaN (unordered) or -1, 0, 1 depending on the ordering assuming + -0 == +0 */ +int bf_cmp(const bf_t *a, const bf_t *b) +{ + int res; + + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + res = 2; + } else if (a->sign != b->sign) { + if (a->expn == BF_EXP_ZERO && b->expn == BF_EXP_ZERO) + res = 0; + else + res = 1 - 2 * a->sign; + } else { + res = bf_cmpu(a, b); + if (a->sign) + res = -res; + } + return res; +} + +/* Compute the number of bits 'n' matching the pattern: + a= X1000..0 + b= X0111..1 + + When computing a-b, the result will have at least n leading zero + bits. + + Precondition: a > b and a.expn - b.expn = 0 or 1 +*/ +static limb_t count_cancelled_bits(const bf_t *a, const bf_t *b) +{ + slimb_t bit_offset, b_offset, n; + int p, p1; + limb_t v1, v2, mask; + + bit_offset = a->len * LIMB_BITS - 1; + b_offset = (b->len - a->len) * LIMB_BITS - (LIMB_BITS - 1) + + a->expn - b->expn; + n = 0; + + /* first search the equals bits */ + for(;;) { + v1 = get_limbz(a, bit_offset >> LIMB_LOG2_BITS); + v2 = get_bits(b->tab, b->len, bit_offset + b_offset); + // printf("v1=" FMT_LIMB " v2=" FMT_LIMB "\n", v1, v2); + if (v1 != v2) + break; + n += LIMB_BITS; + bit_offset -= LIMB_BITS; + } + /* find the position of the first different bit */ + p = clz(v1 ^ v2) + 1; + n += p; + /* then search for '0' in a and '1' in b */ + p = LIMB_BITS - p; + if (p > 0) { + /* search in the trailing p bits of v1 and v2 */ + mask = limb_mask(0, p - 1); + p1 = bf_min(clz(v1 & mask), clz((~v2) & mask)) - (LIMB_BITS - p); + n += p1; + if (p1 != p) + goto done; + } + bit_offset -= LIMB_BITS; + for(;;) { + v1 = get_limbz(a, bit_offset >> LIMB_LOG2_BITS); + v2 = get_bits(b->tab, b->len, bit_offset + b_offset); + // printf("v1=" FMT_LIMB " v2=" FMT_LIMB "\n", v1, v2); + if (v1 != 0 || v2 != -1) { + /* different: count the matching bits */ + p1 = bf_min(clz(v1), clz(~v2)); + n += p1; + break; + } + n += LIMB_BITS; + bit_offset -= LIMB_BITS; + } + done: + return n; +} + +static int bf_add_internal(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags, int b_neg) +{ + const bf_t *tmp; + int is_sub, ret, cmp_res, a_sign, b_sign; + + a_sign = a->sign; + b_sign = b->sign ^ b_neg; + is_sub = a_sign ^ b_sign; + cmp_res = bf_cmpu(a, b); + if (cmp_res < 0) { + tmp = a; + a = b; + b = tmp; + a_sign = b_sign; /* b_sign is never used later */ + } + /* abs(a) >= abs(b) */ + if (cmp_res == 0 && is_sub && a->expn < BF_EXP_INF) { + /* zero result */ + bf_set_zero(r, (flags & BF_RND_MASK) == BF_RNDD); + ret = 0; + } else if (a->len == 0 || b->len == 0) { + ret = 0; + if (a->expn >= BF_EXP_INF) { + if (a->expn == BF_EXP_NAN) { + /* at least one operand is NaN */ + bf_set_nan(r); + } else if (b->expn == BF_EXP_INF && is_sub) { + /* infinities with different signs */ + bf_set_nan(r); + ret = BF_ST_INVALID_OP; + } else { + bf_set_inf(r, a_sign); + } + } else { + /* at least one zero and not subtract */ + bf_set(r, a); + r->sign = a_sign; + goto renorm; + } + } else { + slimb_t d, a_offset, b_bit_offset, i, cancelled_bits; + limb_t carry, v1, v2, u, r_len, carry1, precl, tot_len, z, sub_mask; + + r->sign = a_sign; + r->expn = a->expn; + d = a->expn - b->expn; + /* must add more precision for the leading cancelled bits in + subtraction */ + if (is_sub) { + if (d <= 1) + cancelled_bits = count_cancelled_bits(a, b); + else + cancelled_bits = 1; + } else { + cancelled_bits = 0; + } + + /* add two extra bits for rounding */ + precl = (cancelled_bits + prec + 2 + LIMB_BITS - 1) / LIMB_BITS; + tot_len = bf_max(a->len, b->len + (d + LIMB_BITS - 1) / LIMB_BITS); + r_len = bf_min(precl, tot_len); + if (bf_resize(r, r_len)) + goto fail; + a_offset = a->len - r_len; + b_bit_offset = (b->len - r_len) * LIMB_BITS + d; + + /* compute the bits before for the rounding */ + carry = is_sub; + z = 0; + sub_mask = -is_sub; + i = r_len - tot_len; + while (i < 0) { + slimb_t ap, bp; + BOOL inflag; + + ap = a_offset + i; + bp = b_bit_offset + i * LIMB_BITS; + inflag = FALSE; + if (ap >= 0 && ap < a->len) { + v1 = a->tab[ap]; + inflag = TRUE; + } else { + v1 = 0; + } + if (bp + LIMB_BITS > 0 && bp < (slimb_t)(b->len * LIMB_BITS)) { + v2 = get_bits(b->tab, b->len, bp); + inflag = TRUE; + } else { + v2 = 0; + } + if (!inflag) { + /* outside 'a' and 'b': go directly to the next value + inside a or b so that the running time does not + depend on the exponent difference */ + i = 0; + if (ap < 0) + i = bf_min(i, -a_offset); + /* b_bit_offset + i * LIMB_BITS + LIMB_BITS >= 1 + equivalent to + i >= ceil(-b_bit_offset + 1 - LIMB_BITS) / LIMB_BITS) + */ + if (bp + LIMB_BITS <= 0) + i = bf_min(i, (-b_bit_offset) >> LIMB_LOG2_BITS); + } else { + i++; + } + v2 ^= sub_mask; + u = v1 + v2; + carry1 = u < v1; + u += carry; + carry = (u < carry) | carry1; + z |= u; + } + /* and the result */ + for(i = 0; i < r_len; i++) { + v1 = get_limbz(a, a_offset + i); + v2 = get_bits(b->tab, b->len, b_bit_offset + i * LIMB_BITS); + v2 ^= sub_mask; + u = v1 + v2; + carry1 = u < v1; + u += carry; + carry = (u < carry) | carry1; + r->tab[i] = u; + } + /* set the extra bits for the rounding */ + r->tab[0] |= (z != 0); + + /* carry is only possible in add case */ + if (!is_sub && carry) { + if (bf_resize(r, r_len + 1)) + goto fail; + r->tab[r_len] = 1; + r->expn += LIMB_BITS; + } + renorm: + ret = bf_normalize_and_round(r, prec, flags); + } + return ret; + fail: + bf_set_nan(r); + return BF_ST_MEM_ERROR; +} + +static int __bf_add(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_add_internal(r, a, b, prec, flags, 0); +} + +static int __bf_sub(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_add_internal(r, a, b, prec, flags, 1); +} + +limb_t mp_add(limb_t *res, const limb_t *op1, const limb_t *op2, + limb_t n, limb_t carry) +{ + slimb_t i; + limb_t k, a, v, k1; + + k = carry; + for(i=0;i v; + v = a - k; + k = (v > a) | k1; + res[i] = v; + } + return k; +} + +/* compute 0 - op2 */ +static limb_t mp_neg(limb_t *res, const limb_t *op2, mp_size_t n, limb_t carry) +{ + int i; + limb_t k, a, v, k1; + + k = carry; + for(i=0;i v; + v = a - k; + k = (v > a) | k1; + res[i] = v; + } + return k; +} + +limb_t mp_sub_ui(limb_t *tab, limb_t b, mp_size_t n) +{ + mp_size_t i; + limb_t k, a, v; + + k=b; + for(i=0;i v; + tab[i] = a; + if (k == 0) + break; + } + return k; +} + +/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). + 1 <= shift <= LIMB_BITS - 1 */ +static limb_t mp_shr(limb_t *tab_r, const limb_t *tab, mp_size_t n, + int shift, limb_t high) +{ + mp_size_t i; + limb_t l, a; + + assert(shift >= 1 && shift < LIMB_BITS); + l = high; + for(i = n - 1; i >= 0; i--) { + a = tab[i]; + tab_r[i] = (a >> shift) | (l << (LIMB_BITS - shift)); + l = a; + } + return l & (((limb_t)1 << shift) - 1); +} + +/* tabr[] = taba[] * b + l. Return the high carry */ +static limb_t mp_mul1(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t l) +{ + limb_t i; + dlimb_t t; + + for(i = 0; i < n; i++) { + t = (dlimb_t)taba[i] * (dlimb_t)b + l; + tabr[i] = t; + l = t >> LIMB_BITS; + } + return l; +} + +/* tabr[] += taba[] * b, return the high word. */ +static limb_t mp_add_mul1(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b) +{ + limb_t i, l; + dlimb_t t; + + l = 0; + for(i = 0; i < n; i++) { + t = (dlimb_t)taba[i] * (dlimb_t)b + l + tabr[i]; + tabr[i] = t; + l = t >> LIMB_BITS; + } + return l; +} + +/* size of the result : op1_size + op2_size. */ +static void mp_mul_basecase(limb_t *result, + const limb_t *op1, limb_t op1_size, + const limb_t *op2, limb_t op2_size) +{ + limb_t i, r; + + result[op1_size] = mp_mul1(result, op1, op1_size, op2[0], 0); + for(i=1;i= FFT_MUL_THRESHOLD)) { + bf_t r_s, *r = &r_s; + r->tab = result; + /* XXX: optimize memory usage in API */ + if (fft_mul(s, r, (limb_t *)op1, op1_size, + (limb_t *)op2, op2_size, FFT_MUL_R_NORESIZE)) + return -1; + } else +#endif + { + mp_mul_basecase(result, op1, op1_size, op2, op2_size); + } + return 0; +} + +/* tabr[] -= taba[] * b. Return the value to substract to the high + word. */ +static limb_t mp_sub_mul1(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b) +{ + limb_t i, l; + dlimb_t t; + + l = 0; + for(i = 0; i < n; i++) { + t = tabr[i] - (dlimb_t)taba[i] * (dlimb_t)b - l; + tabr[i] = t; + l = -(t >> LIMB_BITS); + } + return l; +} + +/* WARNING: d must be >= 2^(LIMB_BITS-1) */ +static inline limb_t udiv1norm_init(limb_t d) +{ + limb_t a0, a1; + a1 = -d - 1; + a0 = -1; + return (((dlimb_t)a1 << LIMB_BITS) | a0) / d; +} + +/* return the quotient and the remainder in '*pr'of 'a1*2^LIMB_BITS+a0 + / d' with 0 <= a1 < d. */ +static inline limb_t udiv1norm(limb_t *pr, limb_t a1, limb_t a0, + limb_t d, limb_t d_inv) +{ + limb_t n1m, n_adj, q, r, ah; + dlimb_t a; + n1m = ((slimb_t)a0 >> (LIMB_BITS - 1)); + n_adj = a0 + (n1m & d); + a = (dlimb_t)d_inv * (a1 - n1m) + n_adj; + q = (a >> LIMB_BITS) + a1; + /* compute a - q * r and update q so that the remainder is\ + between 0 and d - 1 */ + a = ((dlimb_t)a1 << LIMB_BITS) | a0; + a = a - (dlimb_t)q * d - d; + ah = a >> LIMB_BITS; + q += 1 + ah; + r = (limb_t)a + (ah & d); + *pr = r; + return q; +} + +/* b must be >= 1 << (LIMB_BITS - 1) */ +static limb_t mp_div1norm(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t r) +{ + slimb_t i; + + if (n >= UDIV1NORM_THRESHOLD) { + limb_t b_inv; + b_inv = udiv1norm_init(b); + for(i = n - 1; i >= 0; i--) { + tabr[i] = udiv1norm(&r, r, taba[i], b, b_inv); + } + } else { + dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((dlimb_t)r << LIMB_BITS) | taba[i]; + tabr[i] = a1 / b; + r = a1 % b; + } + } + return r; +} + +static int mp_divnorm_large(bf_context_t *s, + limb_t *tabq, limb_t *taba, limb_t na, + const limb_t *tabb, limb_t nb); + +/* base case division: divides taba[0..na-1] by tabb[0..nb-1]. tabb[nb + - 1] must be >= 1 << (LIMB_BITS - 1). na - nb must be >= 0. 'taba' + is modified and contains the remainder (nb limbs). tabq[0..na-nb] + contains the quotient with tabq[na - nb] <= 1. */ +static int mp_divnorm(bf_context_t *s, limb_t *tabq, limb_t *taba, limb_t na, + const limb_t *tabb, limb_t nb) +{ + limb_t r, a, c, q, v, b1, b1_inv, n, dummy_r; + slimb_t i, j; + + b1 = tabb[nb - 1]; + if (nb == 1) { + taba[0] = mp_div1norm(tabq, taba, na, b1, 0); + return 0; + } + n = na - nb; + if (bf_min(n, nb) >= DIVNORM_LARGE_THRESHOLD) { + return mp_divnorm_large(s, tabq, taba, na, tabb, nb); + } + + if (n >= UDIV1NORM_THRESHOLD) + b1_inv = udiv1norm_init(b1); + else + b1_inv = 0; + + /* first iteration: the quotient is only 0 or 1 */ + q = 1; + for(j = nb - 1; j >= 0; j--) { + if (taba[n + j] != tabb[j]) { + if (taba[n + j] < tabb[j]) + q = 0; + break; + } + } + tabq[n] = q; + if (q) { + mp_sub(taba + n, taba + n, tabb, nb, 0); + } + + for(i = n - 1; i >= 0; i--) { + if (unlikely(taba[i + nb] >= b1)) { + q = -1; + } else if (b1_inv) { + q = udiv1norm(&dummy_r, taba[i + nb], taba[i + nb - 1], b1, b1_inv); + } else { + dlimb_t al; + al = ((dlimb_t)taba[i + nb] << LIMB_BITS) | taba[i + nb - 1]; + q = al / b1; + r = al % b1; + } + r = mp_sub_mul1(taba + i, tabb, nb, q); + + v = taba[i + nb]; + a = v - r; + c = (a > v); + taba[i + nb] = a; + + if (c != 0) { + /* negative result */ + for(;;) { + q--; + c = mp_add(taba + i, taba + i, tabb, nb, 0); + /* propagate carry and test if positive result */ + if (c != 0) { + if (++taba[i + nb] == 0) { + break; + } + } + } + } + tabq[i] = q; + } + return 0; +} + +/* compute r=B^(2*n)/a such as a*r < B^(2*n) < a*r + 2 with n >= 1. 'a' + has n limbs with a[n-1] >= B/2 and 'r' has n+1 limbs with r[n] = 1. + + See Modern Computer Arithmetic by Richard P. Brent and Paul + Zimmermann, algorithm 3.5 */ +int mp_recip(bf_context_t *s, limb_t *tabr, const limb_t *taba, limb_t n) +{ + mp_size_t l, h, k, i; + limb_t *tabxh, *tabt, c, *tabu; + + if (n <= 2) { + /* return ceil(B^(2*n)/a) - 1 */ + /* XXX: could avoid allocation */ + tabu = bf_malloc(s, sizeof(limb_t) * (2 * n + 1)); + tabt = bf_malloc(s, sizeof(limb_t) * (n + 2)); + if (!tabt || !tabu) + goto fail; + for(i = 0; i < 2 * n; i++) + tabu[i] = 0; + tabu[2 * n] = 1; + if (mp_divnorm(s, tabt, tabu, 2 * n + 1, taba, n)) + goto fail; + for(i = 0; i < n + 1; i++) + tabr[i] = tabt[i]; + if (mp_scan_nz(tabu, n) == 0) { + /* only happens for a=B^n/2 */ + mp_sub_ui(tabr, 1, n + 1); + } + } else { + l = (n - 1) / 2; + h = n - l; + /* n=2p -> l=p-1, h = p + 1, k = p + 3 + n=2p+1-> l=p, h = p + 1; k = p + 2 + */ + tabt = bf_malloc(s, sizeof(limb_t) * (n + h + 1)); + tabu = bf_malloc(s, sizeof(limb_t) * (n + 2 * h - l + 2)); + if (!tabt || !tabu) + goto fail; + tabxh = tabr + l; + if (mp_recip(s, tabxh, taba + l, h)) + goto fail; + if (mp_mul(s, tabt, taba, n, tabxh, h + 1)) /* n + h + 1 limbs */ + goto fail; + while (tabt[n + h] != 0) { + mp_sub_ui(tabxh, 1, h + 1); + c = mp_sub(tabt, tabt, taba, n, 0); + mp_sub_ui(tabt + n, c, h + 1); + } + /* T = B^(n+h) - T */ + mp_neg(tabt, tabt, n + h + 1, 0); + tabt[n + h]++; + if (mp_mul(s, tabu, tabt + l, n + h + 1 - l, tabxh, h + 1)) + goto fail; + /* n + 2*h - l + 2 limbs */ + k = 2 * h - l; + for(i = 0; i < l; i++) + tabr[i] = tabu[i + k]; + mp_add(tabr + l, tabr + l, tabu + 2 * h, h, 0); + } + bf_free(s, tabt); + bf_free(s, tabu); + return 0; + fail: + bf_free(s, tabt); + bf_free(s, tabu); + return -1; +} + +/* return -1, 0 or 1 */ +static int mp_cmp(const limb_t *taba, const limb_t *tabb, mp_size_t n) +{ + mp_size_t i; + for(i = n - 1; i >= 0; i--) { + if (taba[i] != tabb[i]) { + if (taba[i] < tabb[i]) + return -1; + else + return 1; + } + } + return 0; +} + +//#define DEBUG_DIVNORM_LARGE +//#define DEBUG_DIVNORM_LARGE2 + +/* subquadratic divnorm */ +static int mp_divnorm_large(bf_context_t *s, + limb_t *tabq, limb_t *taba, limb_t na, + const limb_t *tabb, limb_t nb) +{ + limb_t *tabb_inv, nq, *tabt, i, n; + nq = na - nb; +#ifdef DEBUG_DIVNORM_LARGE + printf("na=%d nb=%d nq=%d\n", (int)na, (int)nb, (int)nq); + mp_print_str("a", taba, na); + mp_print_str("b", tabb, nb); +#endif + assert(nq >= 1); + n = nq; + if (nq < nb) + n++; + tabb_inv = bf_malloc(s, sizeof(limb_t) * (n + 1)); + tabt = bf_malloc(s, sizeof(limb_t) * 2 * (n + 1)); + if (!tabb_inv || !tabt) + goto fail; + + if (n >= nb) { + for(i = 0; i < n - nb; i++) + tabt[i] = 0; + for(i = 0; i < nb; i++) + tabt[i + n - nb] = tabb[i]; + } else { + /* truncate B: need to increment it so that the approximate + inverse is smaller that the exact inverse */ + for(i = 0; i < n; i++) + tabt[i] = tabb[i + nb - n]; + if (mp_add_ui(tabt, 1, n)) { + /* tabt = B^n : tabb_inv = B^n */ + memset(tabb_inv, 0, n * sizeof(limb_t)); + tabb_inv[n] = 1; + goto recip_done; + } + } + if (mp_recip(s, tabb_inv, tabt, n)) + goto fail; + recip_done: + /* Q=A*B^-1 */ + if (mp_mul(s, tabt, tabb_inv, n + 1, taba + na - (n + 1), n + 1)) + goto fail; + + for(i = 0; i < nq + 1; i++) + tabq[i] = tabt[i + 2 * (n + 1) - (nq + 1)]; +#ifdef DEBUG_DIVNORM_LARGE + mp_print_str("q", tabq, nq + 1); +#endif + + bf_free(s, tabt); + bf_free(s, tabb_inv); + tabb_inv = NULL; + + /* R=A-B*Q */ + tabt = bf_malloc(s, sizeof(limb_t) * (na + 1)); + if (!tabt) + goto fail; + if (mp_mul(s, tabt, tabq, nq + 1, tabb, nb)) + goto fail; + /* we add one more limb for the result */ + mp_sub(taba, taba, tabt, nb + 1, 0); + bf_free(s, tabt); + /* the approximated quotient is smaller than than the exact one, + hence we may have to increment it */ +#ifdef DEBUG_DIVNORM_LARGE2 + int cnt = 0; + static int cnt_max; +#endif + for(;;) { + if (taba[nb] == 0 && mp_cmp(taba, tabb, nb) < 0) + break; + taba[nb] -= mp_sub(taba, taba, tabb, nb, 0); + mp_add_ui(tabq, 1, nq + 1); +#ifdef DEBUG_DIVNORM_LARGE2 + cnt++; +#endif + } +#ifdef DEBUG_DIVNORM_LARGE2 + if (cnt > cnt_max) { + cnt_max = cnt; + printf("\ncnt=%d nq=%d nb=%d\n", cnt_max, (int)nq, (int)nb); + } +#endif + return 0; + fail: + bf_free(s, tabb_inv); + bf_free(s, tabt); + return -1; +} + +int bf_mul(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags) +{ + int ret, r_sign; + + if (a->len < b->len) { + const bf_t *tmp = a; + a = b; + b = tmp; + } + r_sign = a->sign ^ b->sign; + /* here b->len <= a->len */ + if (b->len == 0) { + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + bf_set_nan(r); + ret = 0; + } else if (a->expn == BF_EXP_INF || b->expn == BF_EXP_INF) { + if ((a->expn == BF_EXP_INF && b->expn == BF_EXP_ZERO) || + (a->expn == BF_EXP_ZERO && b->expn == BF_EXP_INF)) { + bf_set_nan(r); + ret = BF_ST_INVALID_OP; + } else { + bf_set_inf(r, r_sign); + ret = 0; + } + } else { + bf_set_zero(r, r_sign); + ret = 0; + } + } else { + bf_t tmp, *r1 = NULL; + limb_t a_len, b_len, precl; + limb_t *a_tab, *b_tab; + + a_len = a->len; + b_len = b->len; + + if ((flags & BF_RND_MASK) == BF_RNDF) { + /* faithful rounding does not require using the full inputs */ + precl = (prec + 2 + LIMB_BITS - 1) / LIMB_BITS; + a_len = bf_min(a_len, precl); + b_len = bf_min(b_len, precl); + } + a_tab = a->tab + a->len - a_len; + b_tab = b->tab + b->len - b_len; + +#ifdef USE_FFT_MUL + if (b_len >= FFT_MUL_THRESHOLD) { + int mul_flags = 0; + if (r == a) + mul_flags |= FFT_MUL_R_OVERLAP_A; + if (r == b) + mul_flags |= FFT_MUL_R_OVERLAP_B; + if (fft_mul(r->ctx, r, a_tab, a_len, b_tab, b_len, mul_flags)) + goto fail; + } else +#endif + { + if (r == a || r == b) { + bf_init(r->ctx, &tmp); + r1 = r; + r = &tmp; + } + if (bf_resize(r, a_len + b_len)) { +#ifdef USE_FFT_MUL + fail: +#endif + bf_set_nan(r); + ret = BF_ST_MEM_ERROR; + goto done; + } + mp_mul_basecase(r->tab, a_tab, a_len, b_tab, b_len); + } + r->sign = r_sign; + r->expn = a->expn + b->expn; + ret = bf_normalize_and_round(r, prec, flags); + done: + if (r == &tmp) + bf_move(r1, &tmp); + } + return ret; +} + +/* multiply 'r' by 2^e */ +int bf_mul_2exp(bf_t *r, slimb_t e, limb_t prec, bf_flags_t flags) +{ + slimb_t e_max; + if (r->len == 0) + return 0; + e_max = ((limb_t)1 << BF_EXT_EXP_BITS_MAX) - 1; + e = bf_max(e, -e_max); + e = bf_min(e, e_max); + r->expn += e; + return __bf_round(r, prec, flags, r->len, 0); +} + +/* Return e such as a=m*2^e with m odd integer. return 0 if a is zero, + Infinite or Nan. */ +slimb_t bf_get_exp_min(const bf_t *a) +{ + slimb_t i; + limb_t v; + int k; + + for(i = 0; i < a->len; i++) { + v = a->tab[i]; + if (v != 0) { + k = ctz(v); + return a->expn - (a->len - i) * LIMB_BITS + k; + } + } + return 0; +} + +/* a and b must be finite numbers with a >= 0 and b > 0. 'q' is the + integer defined as floor(a/b) and r = a - q * b. */ +static void bf_tdivremu(bf_t *q, bf_t *r, + const bf_t *a, const bf_t *b) +{ + if (bf_cmpu(a, b) < 0) { + bf_set_ui(q, 0); + bf_set(r, a); + } else { + bf_div(q, a, b, bf_max(a->expn - b->expn + 1, 2), BF_RNDZ); + bf_rint(q, BF_RNDZ); + bf_mul(r, q, b, BF_PREC_INF, BF_RNDZ); + bf_sub(r, a, r, BF_PREC_INF, BF_RNDZ); + } +} + +static int __bf_div(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags) +{ + bf_context_t *s = r->ctx; + int ret, r_sign; + limb_t n, nb, precl; + + r_sign = a->sign ^ b->sign; + if (a->expn >= BF_EXP_INF || b->expn >= BF_EXP_INF) { + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF && b->expn == BF_EXP_INF) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else if (a->expn == BF_EXP_INF) { + bf_set_inf(r, r_sign); + return 0; + } else { + bf_set_zero(r, r_sign); + return 0; + } + } else if (a->expn == BF_EXP_ZERO) { + if (b->expn == BF_EXP_ZERO) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_set_zero(r, r_sign); + return 0; + } + } else if (b->expn == BF_EXP_ZERO) { + bf_set_inf(r, r_sign); + return BF_ST_DIVIDE_ZERO; + } + + /* number of limbs of the quotient (2 extra bits for rounding) */ + precl = (prec + 2 + LIMB_BITS - 1) / LIMB_BITS; + nb = b->len; + n = bf_max(a->len, precl); + + { + limb_t *taba, na; + slimb_t d; + + na = n + nb; + taba = bf_malloc(s, (na + 1) * sizeof(limb_t)); + if (!taba) + goto fail; + d = na - a->len; + memset(taba, 0, d * sizeof(limb_t)); + memcpy(taba + d, a->tab, a->len * sizeof(limb_t)); + if (bf_resize(r, n + 1)) + goto fail1; + if (mp_divnorm(s, r->tab, taba, na, b->tab, nb)) { + fail1: + bf_free(s, taba); + goto fail; + } + /* see if non zero remainder */ + if (mp_scan_nz(taba, nb)) + r->tab[0] |= 1; + bf_free(r->ctx, taba); + r->expn = a->expn - b->expn + LIMB_BITS; + r->sign = r_sign; + ret = bf_normalize_and_round(r, prec, flags); + } + return ret; + fail: + bf_set_nan(r); + return BF_ST_MEM_ERROR; +} + +/* division and remainder. + + rnd_mode is the rounding mode for the quotient. The additional + rounding mode BF_RND_EUCLIDIAN is supported. + + 'q' is an integer. 'r' is rounded with prec and flags (prec can be + BF_PREC_INF). +*/ +int bf_divrem(bf_t *q, bf_t *r, const bf_t *a, const bf_t *b, + limb_t prec, bf_flags_t flags, int rnd_mode) +{ + bf_t a1_s, *a1 = &a1_s; + bf_t b1_s, *b1 = &b1_s; + int q_sign, ret; + BOOL is_ceil, is_rndn; + + assert(q != a && q != b); + assert(r != a && r != b); + assert(q != r); + + if (a->len == 0 || b->len == 0) { + bf_set_zero(q, 0); + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF || b->expn == BF_EXP_ZERO) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_set(r, a); + return bf_round(r, prec, flags); + } + } + + q_sign = a->sign ^ b->sign; + is_rndn = (rnd_mode == BF_RNDN || rnd_mode == BF_RNDNA); + switch(rnd_mode) { + default: + case BF_RNDZ: + case BF_RNDN: + case BF_RNDNA: + is_ceil = FALSE; + break; + case BF_RNDD: + is_ceil = q_sign; + break; + case BF_RNDU: + is_ceil = q_sign ^ 1; + break; + case BF_RNDA: + is_ceil = TRUE; + break; + case BF_DIVREM_EUCLIDIAN: + is_ceil = a->sign; + break; + } + + a1->expn = a->expn; + a1->tab = a->tab; + a1->len = a->len; + a1->sign = 0; + + b1->expn = b->expn; + b1->tab = b->tab; + b1->len = b->len; + b1->sign = 0; + + /* XXX: could improve to avoid having a large 'q' */ + bf_tdivremu(q, r, a1, b1); + if (bf_is_nan(q) || bf_is_nan(r)) + goto fail; + + if (r->len != 0) { + if (is_rndn) { + int res; + b1->expn--; + res = bf_cmpu(r, b1); + b1->expn++; + if (res > 0 || + (res == 0 && + (rnd_mode == BF_RNDNA || + get_bit(q->tab, q->len, q->len * LIMB_BITS - q->expn)))) { + goto do_sub_r; + } + } else if (is_ceil) { + do_sub_r: + ret = bf_add_si(q, q, 1, BF_PREC_INF, BF_RNDZ); + ret |= bf_sub(r, r, b1, BF_PREC_INF, BF_RNDZ); + if (ret & BF_ST_MEM_ERROR) + goto fail; + } + } + + r->sign ^= a->sign; + q->sign = q_sign; + return bf_round(r, prec, flags); + fail: + bf_set_nan(q); + bf_set_nan(r); + return BF_ST_MEM_ERROR; +} + +int bf_rem(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags, int rnd_mode) +{ + bf_t q_s, *q = &q_s; + int ret; + + bf_init(r->ctx, q); + ret = bf_divrem(q, r, a, b, prec, flags, rnd_mode); + bf_delete(q); + return ret; +} + +static inline int bf_get_limb(slimb_t *pres, const bf_t *a, int flags) +{ +#if LIMB_BITS == 32 + return bf_get_int32(pres, a, flags); +#else + return bf_get_int64(pres, a, flags); +#endif +} + +int bf_remquo(slimb_t *pq, bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags, int rnd_mode) +{ + bf_t q_s, *q = &q_s; + int ret; + + bf_init(r->ctx, q); + ret = bf_divrem(q, r, a, b, prec, flags, rnd_mode); + bf_get_limb(pq, q, BF_GET_INT_MOD); + bf_delete(q); + return ret; +} + +static __maybe_unused inline limb_t mul_mod(limb_t a, limb_t b, limb_t m) +{ + dlimb_t t; + t = (dlimb_t)a * (dlimb_t)b; + return t % m; +} + +#if defined(USE_MUL_CHECK) +static limb_t mp_mod1(const limb_t *tab, limb_t n, limb_t m, limb_t r) +{ + slimb_t i; + dlimb_t t; + + for(i = n - 1; i >= 0; i--) { + t = ((dlimb_t)r << LIMB_BITS) | tab[i]; + r = t % m; + } + return r; +} +#endif + +static const uint16_t sqrt_table[192] = { +128,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,144,145,146,147,148,149,150,150,151,152,153,154,155,155,156,157,158,159,160,160,161,162,163,163,164,165,166,167,167,168,169,170,170,171,172,173,173,174,175,176,176,177,178,178,179,180,181,181,182,183,183,184,185,185,186,187,187,188,189,189,190,191,192,192,193,193,194,195,195,196,197,197,198,199,199,200,201,201,202,203,203,204,204,205,206,206,207,208,208,209,209,210,211,211,212,212,213,214,214,215,215,216,217,217,218,218,219,219,220,221,221,222,222,223,224,224,225,225,226,226,227,227,228,229,229,230,230,231,231,232,232,233,234,234,235,235,236,236,237,237,238,238,239,240,240,241,241,242,242,243,243,244,244,245,245,246,246,247,247,248,248,249,249,250,250,251,251,252,252,253,253,254,254,255, +}; + +/* a >= 2^(LIMB_BITS - 2). Return (s, r) with s=floor(sqrt(a)) and + r=a-s^2. 0 <= r <= 2 * s */ +static limb_t mp_sqrtrem1(limb_t *pr, limb_t a) +{ + limb_t s1, r1, s, r, q, u, num; + + /* use a table for the 16 -> 8 bit sqrt */ + s1 = sqrt_table[(a >> (LIMB_BITS - 8)) - 64]; + r1 = (a >> (LIMB_BITS - 16)) - s1 * s1; + if (r1 > 2 * s1) { + r1 -= 2 * s1 + 1; + s1++; + } + + /* one iteration to get a 32 -> 16 bit sqrt */ + num = (r1 << 8) | ((a >> (LIMB_BITS - 32 + 8)) & 0xff); + q = num / (2 * s1); /* q <= 2^8 */ + u = num % (2 * s1); + s = (s1 << 8) + q; + r = (u << 8) | ((a >> (LIMB_BITS - 32)) & 0xff); + r -= q * q; + if ((slimb_t)r < 0) { + s--; + r += 2 * s + 1; + } + +#if LIMB_BITS == 64 + s1 = s; + r1 = r; + /* one more iteration for 64 -> 32 bit sqrt */ + num = (r1 << 16) | ((a >> (LIMB_BITS - 64 + 16)) & 0xffff); + q = num / (2 * s1); /* q <= 2^16 */ + u = num % (2 * s1); + s = (s1 << 16) + q; + r = (u << 16) | ((a >> (LIMB_BITS - 64)) & 0xffff); + r -= q * q; + if ((slimb_t)r < 0) { + s--; + r += 2 * s + 1; + } +#endif + *pr = r; + return s; +} + +/* return floor(sqrt(a)) */ +limb_t bf_isqrt(limb_t a) +{ + limb_t s, r; + int k; + + if (a == 0) + return 0; + k = clz(a) & ~1; + s = mp_sqrtrem1(&r, a << k); + s >>= (k >> 1); + return s; +} + +static limb_t mp_sqrtrem2(limb_t *tabs, limb_t *taba) +{ + limb_t s1, r1, s, q, u, a0, a1; + dlimb_t r, num; + int l; + + a0 = taba[0]; + a1 = taba[1]; + s1 = mp_sqrtrem1(&r1, a1); + l = LIMB_BITS / 2; + num = ((dlimb_t)r1 << l) | (a0 >> l); + q = num / (2 * s1); + u = num % (2 * s1); + s = (s1 << l) + q; + r = ((dlimb_t)u << l) | (a0 & (((limb_t)1 << l) - 1)); + if (unlikely((q >> l) != 0)) + r -= (dlimb_t)1 << LIMB_BITS; /* special case when q=2^l */ + else + r -= q * q; + if ((slimb_t)(r >> LIMB_BITS) < 0) { + s--; + r += 2 * (dlimb_t)s + 1; + } + tabs[0] = s; + taba[0] = r; + return r >> LIMB_BITS; +} + +//#define DEBUG_SQRTREM + +/* tmp_buf must contain (n / 2 + 1 limbs). *prh contains the highest + limb of the remainder. */ +static int mp_sqrtrem_rec(bf_context_t *s, limb_t *tabs, limb_t *taba, limb_t n, + limb_t *tmp_buf, limb_t *prh) +{ + limb_t l, h, rh, ql, qh, c, i; + + if (n == 1) { + *prh = mp_sqrtrem2(tabs, taba); + return 0; + } +#ifdef DEBUG_SQRTREM + mp_print_str("a", taba, 2 * n); +#endif + l = n / 2; + h = n - l; + if (mp_sqrtrem_rec(s, tabs + l, taba + 2 * l, h, tmp_buf, &qh)) + return -1; +#ifdef DEBUG_SQRTREM + mp_print_str("s1", tabs + l, h); + mp_print_str_h("r1", taba + 2 * l, h, qh); + mp_print_str_h("r2", taba + l, n, qh); +#endif + + /* the remainder is in taba + 2 * l. Its high bit is in qh */ + if (qh) { + mp_sub(taba + 2 * l, taba + 2 * l, tabs + l, h, 0); + } + /* instead of dividing by 2*s, divide by s (which is normalized) + and update q and r */ + if (mp_divnorm(s, tmp_buf, taba + l, n, tabs + l, h)) + return -1; + qh += tmp_buf[l]; + for(i = 0; i < l; i++) + tabs[i] = tmp_buf[i]; + ql = mp_shr(tabs, tabs, l, 1, qh & 1); + qh = qh >> 1; /* 0 or 1 */ + if (ql) + rh = mp_add(taba + l, taba + l, tabs + l, h, 0); + else + rh = 0; +#ifdef DEBUG_SQRTREM + mp_print_str_h("q", tabs, l, qh); + mp_print_str_h("u", taba + l, h, rh); +#endif + + mp_add_ui(tabs + l, qh, h); +#ifdef DEBUG_SQRTREM + mp_print_str_h("s2", tabs, n, sh); +#endif + + /* q = qh, tabs[l - 1 ... 0], r = taba[n - 1 ... l] */ + /* subtract q^2. if qh = 1 then q = B^l, so we can take shortcuts */ + if (qh) { + c = qh; + } else { + if (mp_mul(s, taba + n, tabs, l, tabs, l)) + return -1; + c = mp_sub(taba, taba, taba + n, 2 * l, 0); + } + rh -= mp_sub_ui(taba + 2 * l, c, n - 2 * l); + if ((slimb_t)rh < 0) { + mp_sub_ui(tabs, 1, n); + rh += mp_add_mul1(taba, tabs, n, 2); + rh += mp_add_ui(taba, 1, n); + } + *prh = rh; + return 0; +} + +/* 'taba' has 2*n limbs with n >= 1 and taba[2*n-1] >= 2 ^ (LIMB_BITS + - 2). Return (s, r) with s=floor(sqrt(a)) and r=a-s^2. 0 <= r <= 2 + * s. tabs has n limbs. r is returned in the lower n limbs of + taba. Its r[n] is the returned value of the function. */ +/* Algorithm from the article "Karatsuba Square Root" by Paul Zimmermann and + inspirated from its GMP implementation */ +int mp_sqrtrem(bf_context_t *s, limb_t *tabs, limb_t *taba, limb_t n) +{ + limb_t tmp_buf1[8]; + limb_t *tmp_buf; + mp_size_t n2; + int ret; + n2 = n / 2 + 1; + if (n2 <= countof(tmp_buf1)) { + tmp_buf = tmp_buf1; + } else { + tmp_buf = bf_malloc(s, sizeof(limb_t) * n2); + if (!tmp_buf) + return -1; + } + ret = mp_sqrtrem_rec(s, tabs, taba, n, tmp_buf, taba + n); + if (tmp_buf != tmp_buf1) + bf_free(s, tmp_buf); + return ret; +} + +/* Integer square root with remainder. 'a' must be an integer. r = + floor(sqrt(a)) and rem = a - r^2. BF_ST_INEXACT is set if the result + is inexact. 'rem' can be NULL if the remainder is not needed. */ +int bf_sqrtrem(bf_t *r, bf_t *rem1, const bf_t *a) +{ + int ret; + + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + } else if (a->expn == BF_EXP_INF && a->sign) { + goto invalid_op; + } else { + bf_set(r, a); + } + if (rem1) + bf_set_ui(rem1, 0); + ret = 0; + } else if (a->sign) { + invalid_op: + bf_set_nan(r); + if (rem1) + bf_set_ui(rem1, 0); + ret = BF_ST_INVALID_OP; + } else { + bf_t rem_s, *rem; + + bf_sqrt(r, a, (a->expn + 1) / 2, BF_RNDZ); + bf_rint(r, BF_RNDZ); + /* see if the result is exact by computing the remainder */ + if (rem1) { + rem = rem1; + } else { + rem = &rem_s; + bf_init(r->ctx, rem); + } + /* XXX: could avoid recomputing the remainder */ + bf_mul(rem, r, r, BF_PREC_INF, BF_RNDZ); + bf_neg(rem); + bf_add(rem, rem, a, BF_PREC_INF, BF_RNDZ); + if (bf_is_nan(rem)) { + ret = BF_ST_MEM_ERROR; + goto done; + } + if (rem->len != 0) { + ret = BF_ST_INEXACT; + } else { + ret = 0; + } + done: + if (!rem1) + bf_delete(rem); + } + return ret; +} + +int bf_sqrt(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = a->ctx; + int ret; + + assert(r != a); + + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + } else if (a->expn == BF_EXP_INF && a->sign) { + goto invalid_op; + } else { + bf_set(r, a); + } + ret = 0; + } else if (a->sign) { + invalid_op: + bf_set_nan(r); + ret = BF_ST_INVALID_OP; + } else { + limb_t *a1; + slimb_t n, n1; + limb_t res; + + /* convert the mantissa to an integer with at least 2 * + prec + 4 bits */ + n = (2 * (prec + 2) + 2 * LIMB_BITS - 1) / (2 * LIMB_BITS); + if (bf_resize(r, n)) + goto fail; + a1 = bf_malloc(s, sizeof(limb_t) * 2 * n); + if (!a1) + goto fail; + n1 = bf_min(2 * n, a->len); + memset(a1, 0, (2 * n - n1) * sizeof(limb_t)); + memcpy(a1 + 2 * n - n1, a->tab + a->len - n1, n1 * sizeof(limb_t)); + if (a->expn & 1) { + res = mp_shr(a1, a1, 2 * n, 1, 0); + } else { + res = 0; + } + if (mp_sqrtrem(s, r->tab, a1, n)) { + bf_free(s, a1); + goto fail; + } + if (!res) { + res = mp_scan_nz(a1, n + 1); + } + bf_free(s, a1); + if (!res) { + res = mp_scan_nz(a->tab, a->len - n1); + } + if (res != 0) + r->tab[0] |= 1; + r->sign = 0; + r->expn = (a->expn + 1) >> 1; + ret = bf_round(r, prec, flags); + } + return ret; + fail: + bf_set_nan(r); + return BF_ST_MEM_ERROR; +} + +static no_inline int bf_op2(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags, bf_op2_func_t *func) +{ + bf_t tmp; + int ret; + + if (r == a || r == b) { + bf_init(r->ctx, &tmp); + ret = func(&tmp, a, b, prec, flags); + bf_move(r, &tmp); + } else { + ret = func(r, a, b, prec, flags); + } + return ret; +} + +int bf_add(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_op2(r, a, b, prec, flags, __bf_add); +} + +int bf_sub(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_op2(r, a, b, prec, flags, __bf_sub); +} + +int bf_div(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_op2(r, a, b, prec, flags, __bf_div); +} + +int bf_mul_ui(bf_t *r, const bf_t *a, uint64_t b1, limb_t prec, + bf_flags_t flags) +{ + bf_t b; + int ret; + bf_init(r->ctx, &b); + ret = bf_set_ui(&b, b1); + ret |= bf_mul(r, a, &b, prec, flags); + bf_delete(&b); + return ret; +} + +int bf_mul_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, + bf_flags_t flags) +{ + bf_t b; + int ret; + bf_init(r->ctx, &b); + ret = bf_set_si(&b, b1); + ret |= bf_mul(r, a, &b, prec, flags); + bf_delete(&b); + return ret; +} + +int bf_add_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, + bf_flags_t flags) +{ + bf_t b; + int ret; + + bf_init(r->ctx, &b); + ret = bf_set_si(&b, b1); + ret |= bf_add(r, a, &b, prec, flags); + bf_delete(&b); + return ret; +} + +static int bf_pow_ui(bf_t *r, const bf_t *a, limb_t b, limb_t prec, + bf_flags_t flags) +{ + int ret, n_bits, i; + + assert(r != a); + if (b == 0) + return bf_set_ui(r, 1); + ret = bf_set(r, a); + n_bits = LIMB_BITS - clz(b); + for(i = n_bits - 2; i >= 0; i--) { + ret |= bf_mul(r, r, r, prec, flags); + if ((b >> i) & 1) + ret |= bf_mul(r, r, a, prec, flags); + } + return ret; +} + +static int bf_pow_ui_ui(bf_t *r, limb_t a1, limb_t b, + limb_t prec, bf_flags_t flags) +{ + bf_t a; + int ret; + +#ifdef USE_BF_DEC + if (a1 == 10 && b <= LIMB_DIGITS) { + /* use precomputed powers. We do not round at this point + because we expect the caller to do it */ + ret = bf_set_ui(r, mp_pow_dec[b]); + } else +#endif + { + bf_init(r->ctx, &a); + ret = bf_set_ui(&a, a1); + ret |= bf_pow_ui(r, &a, b, prec, flags); + bf_delete(&a); + } + return ret; +} + +/* convert to integer (infinite precision) */ +int bf_rint(bf_t *r, int rnd_mode) +{ + return bf_round(r, 0, rnd_mode | BF_FLAG_RADPNT_PREC); +} + +/* logical operations */ +#define BF_LOGIC_OR 0 +#define BF_LOGIC_XOR 1 +#define BF_LOGIC_AND 2 + +static inline limb_t bf_logic_op1(limb_t a, limb_t b, int op) +{ + switch(op) { + case BF_LOGIC_OR: + return a | b; + case BF_LOGIC_XOR: + return a ^ b; + default: + case BF_LOGIC_AND: + return a & b; + } +} + +static int bf_logic_op(bf_t *r, const bf_t *a1, const bf_t *b1, int op) +{ + bf_t b1_s, a1_s, *a, *b; + limb_t a_sign, b_sign, r_sign; + slimb_t l, i, a_bit_offset, b_bit_offset; + limb_t v1, v2, v1_mask, v2_mask, r_mask; + int ret; + + assert(r != a1 && r != b1); + + if (a1->expn <= 0) + a_sign = 0; /* minus zero is considered as positive */ + else + a_sign = a1->sign; + + if (b1->expn <= 0) + b_sign = 0; /* minus zero is considered as positive */ + else + b_sign = b1->sign; + + if (a_sign) { + a = &a1_s; + bf_init(r->ctx, a); + if (bf_add_si(a, a1, 1, BF_PREC_INF, BF_RNDZ)) { + b = NULL; + goto fail; + } + } else { + a = (bf_t *)a1; + } + + if (b_sign) { + b = &b1_s; + bf_init(r->ctx, b); + if (bf_add_si(b, b1, 1, BF_PREC_INF, BF_RNDZ)) + goto fail; + } else { + b = (bf_t *)b1; + } + + r_sign = bf_logic_op1(a_sign, b_sign, op); + if (op == BF_LOGIC_AND && r_sign == 0) { + /* no need to compute extra zeros for and */ + if (a_sign == 0 && b_sign == 0) + l = bf_min(a->expn, b->expn); + else if (a_sign == 0) + l = a->expn; + else + l = b->expn; + } else { + l = bf_max(a->expn, b->expn); + } + /* Note: a or b can be zero */ + l = (bf_max(l, 1) + LIMB_BITS - 1) / LIMB_BITS; + if (bf_resize(r, l)) + goto fail; + a_bit_offset = a->len * LIMB_BITS - a->expn; + b_bit_offset = b->len * LIMB_BITS - b->expn; + v1_mask = -a_sign; + v2_mask = -b_sign; + r_mask = -r_sign; + for(i = 0; i < l; i++) { + v1 = get_bits(a->tab, a->len, a_bit_offset + i * LIMB_BITS) ^ v1_mask; + v2 = get_bits(b->tab, b->len, b_bit_offset + i * LIMB_BITS) ^ v2_mask; + r->tab[i] = bf_logic_op1(v1, v2, op) ^ r_mask; + } + r->expn = l * LIMB_BITS; + r->sign = r_sign; + bf_normalize_and_round(r, BF_PREC_INF, BF_RNDZ); /* cannot fail */ + if (r_sign) { + if (bf_add_si(r, r, -1, BF_PREC_INF, BF_RNDZ)) + goto fail; + } + ret = 0; + done: + if (a == &a1_s) + bf_delete(a); + if (b == &b1_s) + bf_delete(b); + return ret; + fail: + bf_set_nan(r); + ret = BF_ST_MEM_ERROR; + goto done; +} + +/* 'a' and 'b' must be integers. Return 0 or BF_ST_MEM_ERROR. */ +int bf_logic_or(bf_t *r, const bf_t *a, const bf_t *b) +{ + return bf_logic_op(r, a, b, BF_LOGIC_OR); +} + +/* 'a' and 'b' must be integers. Return 0 or BF_ST_MEM_ERROR. */ +int bf_logic_xor(bf_t *r, const bf_t *a, const bf_t *b) +{ + return bf_logic_op(r, a, b, BF_LOGIC_XOR); +} + +/* 'a' and 'b' must be integers. Return 0 or BF_ST_MEM_ERROR. */ +int bf_logic_and(bf_t *r, const bf_t *a, const bf_t *b) +{ + return bf_logic_op(r, a, b, BF_LOGIC_AND); +} + +/* conversion between fixed size types */ + +typedef union { + double d; + uint64_t u; +} Float64Union; + +int bf_get_float64(const bf_t *a, double *pres, bf_rnd_t rnd_mode) +{ + Float64Union u; + int e, ret; + uint64_t m; + + ret = 0; + if (a->expn == BF_EXP_NAN) { + u.u = 0x7ff8000000000000; /* quiet nan */ + } else { + bf_t b_s, *b = &b_s; + + bf_init(a->ctx, b); + bf_set(b, a); + if (bf_is_finite(b)) { + ret = bf_round(b, 53, rnd_mode | BF_FLAG_SUBNORMAL | bf_set_exp_bits(11)); + } + if (b->expn == BF_EXP_INF) { + e = (1 << 11) - 1; + m = 0; + } else if (b->expn == BF_EXP_ZERO) { + e = 0; + m = 0; + } else { + e = b->expn + 1023 - 1; +#if LIMB_BITS == 32 + if (b->len == 2) { + m = ((uint64_t)b->tab[1] << 32) | b->tab[0]; + } else { + m = ((uint64_t)b->tab[0] << 32); + } +#else + m = b->tab[0]; +#endif + if (e <= 0) { + /* subnormal */ + m = m >> (12 - e); + e = 0; + } else { + m = (m << 1) >> 12; + } + } + u.u = m | ((uint64_t)e << 52) | ((uint64_t)b->sign << 63); + bf_delete(b); + } + *pres = u.d; + return ret; +} + +int bf_set_float64(bf_t *a, double d) +{ + Float64Union u; + uint64_t m; + int shift, e, sgn; + + u.d = d; + sgn = u.u >> 63; + e = (u.u >> 52) & ((1 << 11) - 1); + m = u.u & (((uint64_t)1 << 52) - 1); + if (e == ((1 << 11) - 1)) { + if (m != 0) { + bf_set_nan(a); + } else { + bf_set_inf(a, sgn); + } + } else if (e == 0) { + if (m == 0) { + bf_set_zero(a, sgn); + } else { + /* subnormal number */ + m <<= 12; + shift = clz64(m); + m <<= shift; + e = -shift; + goto norm; + } + } else { + m = (m << 11) | ((uint64_t)1 << 63); + norm: + a->expn = e - 1023 + 1; +#if LIMB_BITS == 32 + if (bf_resize(a, 2)) + goto fail; + a->tab[0] = m; + a->tab[1] = m >> 32; +#else + if (bf_resize(a, 1)) + goto fail; + a->tab[0] = m; +#endif + a->sign = sgn; + } + return 0; +fail: + bf_set_nan(a); + return BF_ST_MEM_ERROR; +} + +/* The rounding mode is always BF_RNDZ. Return BF_ST_INVALID_OP if there + is an overflow and 0 otherwise. */ +int bf_get_int32(int *pres, const bf_t *a, int flags) +{ + uint32_t v; + int ret; + if (a->expn >= BF_EXP_INF) { + ret = BF_ST_INVALID_OP; + if (flags & BF_GET_INT_MOD) { + v = 0; + } else if (a->expn == BF_EXP_INF) { + v = (uint32_t)INT32_MAX + a->sign; + } else { + v = INT32_MAX; + } + } else if (a->expn <= 0) { + v = 0; + ret = 0; + } else if (a->expn <= 31) { + v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn); + if (a->sign) + v = -v; + ret = 0; + } else if (!(flags & BF_GET_INT_MOD)) { + ret = BF_ST_INVALID_OP; + if (a->sign) { + v = (uint32_t)INT32_MAX + 1; + if (a->expn == 32 && + (a->tab[a->len - 1] >> (LIMB_BITS - 32)) == v) { + ret = 0; + } + } else { + v = INT32_MAX; + } + } else { + v = get_bits(a->tab, a->len, a->len * LIMB_BITS - a->expn); + if (a->sign) + v = -v; + ret = 0; + } + *pres = v; + return ret; +} + +/* The rounding mode is always BF_RNDZ. Return BF_ST_INVALID_OP if there + is an overflow and 0 otherwise. */ +int bf_get_int64(int64_t *pres, const bf_t *a, int flags) +{ + uint64_t v; + int ret; + if (a->expn >= BF_EXP_INF) { + ret = BF_ST_INVALID_OP; + if (flags & BF_GET_INT_MOD) { + v = 0; + } else if (a->expn == BF_EXP_INF) { + v = (uint64_t)INT64_MAX + a->sign; + } else { + v = INT64_MAX; + } + } else if (a->expn <= 0) { + v = 0; + ret = 0; + } else if (a->expn <= 63) { +#if LIMB_BITS == 32 + if (a->expn <= 32) + v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn); + else + v = (((uint64_t)a->tab[a->len - 1] << 32) | + get_limbz(a, a->len - 2)) >> (64 - a->expn); +#else + v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn); +#endif + if (a->sign) + v = -v; + ret = 0; + } else if (!(flags & BF_GET_INT_MOD)) { + ret = BF_ST_INVALID_OP; + if (a->sign) { + uint64_t v1; + v = (uint64_t)INT64_MAX + 1; + if (a->expn == 64) { + v1 = a->tab[a->len - 1]; +#if LIMB_BITS == 32 + v1 = (v1 << 32) | get_limbz(a, a->len - 2); +#endif + if (v1 == v) + ret = 0; + } + } else { + v = INT64_MAX; + } + } else { + slimb_t bit_pos = a->len * LIMB_BITS - a->expn; + v = get_bits(a->tab, a->len, bit_pos); +#if LIMB_BITS == 32 + v |= (uint64_t)get_bits(a->tab, a->len, bit_pos + 32) << 32; +#endif + if (a->sign) + v = -v; + ret = 0; + } + *pres = v; + return ret; +} + +/* The rounding mode is always BF_RNDZ. Return BF_ST_INVALID_OP if there + is an overflow and 0 otherwise. */ +int bf_get_uint64(uint64_t *pres, const bf_t *a) +{ + uint64_t v; + int ret; + if (a->expn == BF_EXP_NAN) { + goto overflow; + } else if (a->expn <= 0) { + v = 0; + ret = 0; + } else if (a->sign) { + v = 0; + ret = BF_ST_INVALID_OP; + } else if (a->expn <= 64) { +#if LIMB_BITS == 32 + if (a->expn <= 32) + v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn); + else + v = (((uint64_t)a->tab[a->len - 1] << 32) | + get_limbz(a, a->len - 2)) >> (64 - a->expn); +#else + v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn); +#endif + ret = 0; + } else { + overflow: + v = UINT64_MAX; + ret = BF_ST_INVALID_OP; + } + *pres = v; + return ret; +} + +/* base conversion from radix */ + +static const uint8_t digits_per_limb_table[BF_RADIX_MAX - 1] = { +#if LIMB_BITS == 32 +32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +#else +64,40,32,27,24,22,21,20,19,18,17,17,16,16,16,15,15,15,14,14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12, +#endif +}; + +static limb_t get_limb_radix(int radix) +{ + int i, k; + limb_t radixl; + + k = digits_per_limb_table[radix - 2]; + radixl = radix; + for(i = 1; i < k; i++) + radixl *= radix; + return radixl; +} + +/* return != 0 if error */ +static int bf_integer_from_radix_rec(bf_t *r, const limb_t *tab, + limb_t n, int level, limb_t n0, + limb_t radix, bf_t *pow_tab) +{ + int ret; + if (n == 1) { + ret = bf_set_ui(r, tab[0]); + } else { + bf_t T_s, *T = &T_s, *B; + limb_t n1, n2; + + n2 = (((n0 * 2) >> (level + 1)) + 1) / 2; + n1 = n - n2; + // printf("level=%d n0=%ld n1=%ld n2=%ld\n", level, n0, n1, n2); + B = &pow_tab[level]; + if (B->len == 0) { + ret = bf_pow_ui_ui(B, radix, n2, BF_PREC_INF, BF_RNDZ); + if (ret) + return ret; + } + ret = bf_integer_from_radix_rec(r, tab + n2, n1, level + 1, n0, + radix, pow_tab); + if (ret) + return ret; + ret = bf_mul(r, r, B, BF_PREC_INF, BF_RNDZ); + if (ret) + return ret; + bf_init(r->ctx, T); + ret = bf_integer_from_radix_rec(T, tab, n2, level + 1, n0, + radix, pow_tab); + if (!ret) + ret = bf_add(r, r, T, BF_PREC_INF, BF_RNDZ); + bf_delete(T); + } + return ret; + // bf_print_str(" r=", r); +} + +/* return 0 if OK != 0 if memory error */ +static int bf_integer_from_radix(bf_t *r, const limb_t *tab, + limb_t n, limb_t radix) +{ + bf_context_t *s = r->ctx; + int pow_tab_len, i, ret; + limb_t radixl; + bf_t *pow_tab; + + radixl = get_limb_radix(radix); + pow_tab_len = ceil_log2(n) + 2; /* XXX: check */ + pow_tab = bf_malloc(s, sizeof(pow_tab[0]) * pow_tab_len); + if (!pow_tab) + return -1; + for(i = 0; i < pow_tab_len; i++) + bf_init(r->ctx, &pow_tab[i]); + ret = bf_integer_from_radix_rec(r, tab, n, 0, n, radixl, pow_tab); + for(i = 0; i < pow_tab_len; i++) { + bf_delete(&pow_tab[i]); + } + bf_free(s, pow_tab); + return ret; +} + +/* compute and round T * radix^expn. */ +int bf_mul_pow_radix(bf_t *r, const bf_t *T, limb_t radix, + slimb_t expn, limb_t prec, bf_flags_t flags) +{ + int ret, expn_sign, overflow; + slimb_t e, extra_bits, prec1, ziv_extra_bits; + bf_t B_s, *B = &B_s; + + if (T->len == 0) { + return bf_set(r, T); + } else if (expn == 0) { + ret = bf_set(r, T); + ret |= bf_round(r, prec, flags); + return ret; + } + + e = expn; + expn_sign = 0; + if (e < 0) { + e = -e; + expn_sign = 1; + } + bf_init(r->ctx, B); + if (prec == BF_PREC_INF) { + /* infinite precision: only used if the result is known to be exact */ + ret = bf_pow_ui_ui(B, radix, e, BF_PREC_INF, BF_RNDN); + if (expn_sign) { + ret |= bf_div(r, T, B, T->len * LIMB_BITS, BF_RNDN); + } else { + ret |= bf_mul(r, T, B, BF_PREC_INF, BF_RNDN); + } + } else { + ziv_extra_bits = 16; + for(;;) { + prec1 = prec + ziv_extra_bits; + /* XXX: correct overflow/underflow handling */ + /* XXX: rigorous error analysis needed */ + extra_bits = ceil_log2(e) * 2 + 1; + ret = bf_pow_ui_ui(B, radix, e, prec1 + extra_bits, BF_RNDN | BF_FLAG_EXT_EXP); + overflow = !bf_is_finite(B); + /* XXX: if bf_pow_ui_ui returns an exact result, can stop + after the next operation */ + if (expn_sign) + ret |= bf_div(r, T, B, prec1 + extra_bits, BF_RNDN | BF_FLAG_EXT_EXP); + else + ret |= bf_mul(r, T, B, prec1 + extra_bits, BF_RNDN | BF_FLAG_EXT_EXP); + if (ret & BF_ST_MEM_ERROR) + break; + if ((ret & BF_ST_INEXACT) && + !bf_can_round(r, prec, flags & BF_RND_MASK, prec1) && + !overflow) { + /* and more precision and retry */ + ziv_extra_bits = ziv_extra_bits + (ziv_extra_bits / 2); + } else { + /* XXX: need to use __bf_round() to pass the inexact + flag for the subnormal case */ + ret = bf_round(r, prec, flags) | (ret & BF_ST_INEXACT); + break; + } + } + } + bf_delete(B); + return ret; +} + +static inline int to_digit(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + return c - 'a' + 10; + else + return 36; +} + +/* add a limb at 'pos' and decrement pos. new space is created if + needed. Return 0 if OK, -1 if memory error */ +static int bf_add_limb(bf_t *a, slimb_t *ppos, limb_t v) +{ + slimb_t pos; + pos = *ppos; + if (unlikely(pos < 0)) { + limb_t new_size, d, *new_tab; + new_size = bf_max(a->len + 1, a->len * 3 / 2); + new_tab = bf_realloc(a->ctx, a->tab, sizeof(limb_t) * new_size); + if (!new_tab) + return -1; + a->tab = new_tab; + d = new_size - a->len; + memmove(a->tab + d, a->tab, a->len * sizeof(limb_t)); + a->len = new_size; + pos += d; + } + a->tab[pos--] = v; + *ppos = pos; + return 0; +} + +static int bf_tolower(int c) +{ + if (c >= 'A' && c <= 'Z') + c = c - 'A' + 'a'; + return c; +} + +static int strcasestart(const char *str, const char *val, const char **ptr) +{ + const char *p, *q; + p = str; + q = val; + while (*q != '\0') { + if (bf_tolower(*p) != *q) + return 0; + p++; + q++; + } + if (ptr) + *ptr = p; + return 1; +} + +static int bf_atof_internal(bf_t *r, slimb_t *pexponent, + const char *str, const char **pnext, int radix, + limb_t prec, bf_flags_t flags, BOOL is_dec) +{ + const char *p, *p_start; + int is_neg, radix_bits, exp_is_neg, ret, digits_per_limb, shift; + limb_t cur_limb; + slimb_t pos, expn, int_len, digit_count; + BOOL has_decpt, is_bin_exp; + bf_t a_s, *a; + + *pexponent = 0; + p = str; + if (!(flags & BF_ATOF_NO_NAN_INF) && radix <= 16 && + strcasestart(p, "nan", &p)) { + bf_set_nan(r); + ret = 0; + goto done; + } + is_neg = 0; + + if (p[0] == '+') { + p++; + p_start = p; + } else if (p[0] == '-') { + is_neg = 1; + p++; + p_start = p; + } else { + p_start = p; + } + if (p[0] == '0') { + if ((p[1] == 'x' || p[1] == 'X') && + (radix == 0 || radix == 16) && + !(flags & BF_ATOF_NO_HEX)) { + radix = 16; + p += 2; + } else if ((p[1] == 'o' || p[1] == 'O') && + radix == 0 && (flags & BF_ATOF_BIN_OCT)) { + p += 2; + radix = 8; + } else if ((p[1] == 'b' || p[1] == 'B') && + radix == 0 && (flags & BF_ATOF_BIN_OCT)) { + p += 2; + radix = 2; + } else { + goto no_prefix; + } + /* there must be a digit after the prefix */ + if (to_digit((uint8_t)*p) >= radix) { + bf_set_nan(r); + ret = 0; + goto done; + } + no_prefix: ; + } else { + if (!(flags & BF_ATOF_NO_NAN_INF) && radix <= 16 && + strcasestart(p, "inf", &p)) { + bf_set_inf(r, is_neg); + ret = 0; + goto done; + } + } + + if (radix == 0) + radix = 10; + if (is_dec) { + assert(radix == 10); + radix_bits = 0; + a = r; + } else if ((radix & (radix - 1)) != 0) { + radix_bits = 0; /* base is not a power of two */ + a = &a_s; + bf_init(r->ctx, a); + } else { + radix_bits = ceil_log2(radix); + a = r; + } + + /* skip leading zeros */ + /* XXX: could also skip zeros after the decimal point */ + while (*p == '0') + p++; + + if (radix_bits) { + shift = digits_per_limb = LIMB_BITS; + } else { + radix_bits = 0; + shift = digits_per_limb = digits_per_limb_table[radix - 2]; + } + cur_limb = 0; + bf_resize(a, 1); + pos = 0; + has_decpt = FALSE; + int_len = digit_count = 0; + for(;;) { + limb_t c; + if (*p == '.' && (p > p_start || to_digit(p[1]) < radix)) { + if (has_decpt) + break; + has_decpt = TRUE; + int_len = digit_count; + p++; + } + c = to_digit(*p); + if (c >= radix) + break; + digit_count++; + p++; + if (radix_bits) { + shift -= radix_bits; + if (shift <= 0) { + cur_limb |= c >> (-shift); + if (bf_add_limb(a, &pos, cur_limb)) + goto mem_error; + if (shift < 0) + cur_limb = c << (LIMB_BITS + shift); + else + cur_limb = 0; + shift += LIMB_BITS; + } else { + cur_limb |= c << shift; + } + } else { + cur_limb = cur_limb * radix + c; + shift--; + if (shift == 0) { + if (bf_add_limb(a, &pos, cur_limb)) + goto mem_error; + shift = digits_per_limb; + cur_limb = 0; + } + } + } + if (!has_decpt) + int_len = digit_count; + + /* add the last limb and pad with zeros */ + if (shift != digits_per_limb) { + if (radix_bits == 0) { + while (shift != 0) { + cur_limb *= radix; + shift--; + } + } + if (bf_add_limb(a, &pos, cur_limb)) { + mem_error: + ret = BF_ST_MEM_ERROR; + if (!radix_bits) + bf_delete(a); + bf_set_nan(r); + goto done; + } + } + + /* reset the next limbs to zero (we prefer to reallocate in the + renormalization) */ + memset(a->tab, 0, (pos + 1) * sizeof(limb_t)); + + if (p == p_start) { + ret = 0; + if (!radix_bits) + bf_delete(a); + bf_set_nan(r); + goto done; + } + + /* parse the exponent, if any */ + expn = 0; + is_bin_exp = FALSE; + if (((radix == 10 && (*p == 'e' || *p == 'E')) || + (radix != 10 && (*p == '@' || + (radix_bits && (*p == 'p' || *p == 'P'))))) && + p > p_start) { + is_bin_exp = (*p == 'p' || *p == 'P'); + p++; + exp_is_neg = 0; + if (*p == '+') { + p++; + } else if (*p == '-') { + exp_is_neg = 1; + p++; + } + for(;;) { + int c; + c = to_digit(*p); + if (c >= 10) + break; + if (unlikely(expn > ((BF_RAW_EXP_MAX - 2 - 9) / 10))) { + /* exponent overflow */ + if (exp_is_neg) { + bf_set_zero(r, is_neg); + ret = BF_ST_UNDERFLOW | BF_ST_INEXACT; + } else { + bf_set_inf(r, is_neg); + ret = BF_ST_OVERFLOW | BF_ST_INEXACT; + } + goto done; + } + p++; + expn = expn * 10 + c; + } + if (exp_is_neg) + expn = -expn; + } + if (is_dec) { + a->expn = expn + int_len; + a->sign = is_neg; + ret = bfdec_normalize_and_round((bfdec_t *)a, prec, flags); + } else if (radix_bits) { + /* XXX: may overflow */ + if (!is_bin_exp) + expn *= radix_bits; + a->expn = expn + (int_len * radix_bits); + a->sign = is_neg; + ret = bf_normalize_and_round(a, prec, flags); + } else { + limb_t l; + pos++; + l = a->len - pos; /* number of limbs */ + if (l == 0) { + bf_set_zero(r, is_neg); + ret = 0; + } else { + bf_t T_s, *T = &T_s; + + expn -= l * digits_per_limb - int_len; + bf_init(r->ctx, T); + if (bf_integer_from_radix(T, a->tab + pos, l, radix)) { + bf_set_nan(r); + ret = BF_ST_MEM_ERROR; + } else { + T->sign = is_neg; + if (flags & BF_ATOF_EXPONENT) { + /* return the exponent */ + *pexponent = expn; + ret = bf_set(r, T); + } else { + ret = bf_mul_pow_radix(r, T, radix, expn, prec, flags); + } + } + bf_delete(T); + } + bf_delete(a); + } + done: + if (pnext) + *pnext = p; + return ret; +} + +/* + Return (status, n, exp). 'status' is the floating point status. 'n' + is the parsed number. + + If (flags & BF_ATOF_EXPONENT) and if the radix is not a power of + two, the parsed number is equal to r * + (*pexponent)^radix. Otherwise *pexponent = 0. +*/ +int bf_atof2(bf_t *r, slimb_t *pexponent, + const char *str, const char **pnext, int radix, + limb_t prec, bf_flags_t flags) +{ + return bf_atof_internal(r, pexponent, str, pnext, radix, prec, flags, + FALSE); +} + +int bf_atof(bf_t *r, const char *str, const char **pnext, int radix, + limb_t prec, bf_flags_t flags) +{ + slimb_t dummy_exp; + return bf_atof_internal(r, &dummy_exp, str, pnext, radix, prec, flags, FALSE); +} + +/* base conversion to radix */ + +#if LIMB_BITS == 64 +#define RADIXL_10 UINT64_C(10000000000000000000) +#else +#define RADIXL_10 UINT64_C(1000000000) +#endif + +static const uint32_t inv_log2_radix[BF_RADIX_MAX - 1][LIMB_BITS / 32 + 1] = { +#if LIMB_BITS == 32 +{ 0x80000000, 0x00000000,}, +{ 0x50c24e60, 0xd4d4f4a7,}, +{ 0x40000000, 0x00000000,}, +{ 0x372068d2, 0x0a1ee5ca,}, +{ 0x3184648d, 0xb8153e7a,}, +{ 0x2d983275, 0x9d5369c4,}, +{ 0x2aaaaaaa, 0xaaaaaaab,}, +{ 0x28612730, 0x6a6a7a54,}, +{ 0x268826a1, 0x3ef3fde6,}, +{ 0x25001383, 0xbac8a744,}, +{ 0x23b46706, 0x82c0c709,}, +{ 0x229729f1, 0xb2c83ded,}, +{ 0x219e7ffd, 0xa5ad572b,}, +{ 0x20c33b88, 0xda7c29ab,}, +{ 0x20000000, 0x00000000,}, +{ 0x1f50b57e, 0xac5884b3,}, +{ 0x1eb22cc6, 0x8aa6e26f,}, +{ 0x1e21e118, 0x0c5daab2,}, +{ 0x1d9dcd21, 0x439834e4,}, +{ 0x1d244c78, 0x367a0d65,}, +{ 0x1cb40589, 0xac173e0c,}, +{ 0x1c4bd95b, 0xa8d72b0d,}, +{ 0x1bead768, 0x98f8ce4c,}, +{ 0x1b903469, 0x050f72e5,}, +{ 0x1b3b433f, 0x2eb06f15,}, +{ 0x1aeb6f75, 0x9c46fc38,}, +{ 0x1aa038eb, 0x0e3bfd17,}, +{ 0x1a593062, 0xb38d8c56,}, +{ 0x1a15f4c3, 0x2b95a2e6,}, +{ 0x19d630dc, 0xcc7ddef9,}, +{ 0x19999999, 0x9999999a,}, +{ 0x195fec80, 0x8a609431,}, +{ 0x1928ee7b, 0x0b4f22f9,}, +{ 0x18f46acf, 0x8c06e318,}, +{ 0x18c23246, 0xdc0a9f3d,}, +#else +{ 0x80000000, 0x00000000, 0x00000000,}, +{ 0x50c24e60, 0xd4d4f4a7, 0x021f57bc,}, +{ 0x40000000, 0x00000000, 0x00000000,}, +{ 0x372068d2, 0x0a1ee5ca, 0x19ea911b,}, +{ 0x3184648d, 0xb8153e7a, 0x7fc2d2e1,}, +{ 0x2d983275, 0x9d5369c4, 0x4dec1661,}, +{ 0x2aaaaaaa, 0xaaaaaaaa, 0xaaaaaaab,}, +{ 0x28612730, 0x6a6a7a53, 0x810fabde,}, +{ 0x268826a1, 0x3ef3fde6, 0x23e2566b,}, +{ 0x25001383, 0xbac8a744, 0x385a3349,}, +{ 0x23b46706, 0x82c0c709, 0x3f891718,}, +{ 0x229729f1, 0xb2c83ded, 0x15fba800,}, +{ 0x219e7ffd, 0xa5ad572a, 0xe169744b,}, +{ 0x20c33b88, 0xda7c29aa, 0x9bddee52,}, +{ 0x20000000, 0x00000000, 0x00000000,}, +{ 0x1f50b57e, 0xac5884b3, 0x70e28eee,}, +{ 0x1eb22cc6, 0x8aa6e26f, 0x06d1a2a2,}, +{ 0x1e21e118, 0x0c5daab1, 0x81b4f4bf,}, +{ 0x1d9dcd21, 0x439834e3, 0x81667575,}, +{ 0x1d244c78, 0x367a0d64, 0xc8204d6d,}, +{ 0x1cb40589, 0xac173e0c, 0x3b7b16ba,}, +{ 0x1c4bd95b, 0xa8d72b0d, 0x5879f25a,}, +{ 0x1bead768, 0x98f8ce4c, 0x66cc2858,}, +{ 0x1b903469, 0x050f72e5, 0x0cf5488e,}, +{ 0x1b3b433f, 0x2eb06f14, 0x8c89719c,}, +{ 0x1aeb6f75, 0x9c46fc37, 0xab5fc7e9,}, +{ 0x1aa038eb, 0x0e3bfd17, 0x1bd62080,}, +{ 0x1a593062, 0xb38d8c56, 0x7998ab45,}, +{ 0x1a15f4c3, 0x2b95a2e6, 0x46aed6a0,}, +{ 0x19d630dc, 0xcc7ddef9, 0x5aadd61b,}, +{ 0x19999999, 0x99999999, 0x9999999a,}, +{ 0x195fec80, 0x8a609430, 0xe1106014,}, +{ 0x1928ee7b, 0x0b4f22f9, 0x5f69791d,}, +{ 0x18f46acf, 0x8c06e318, 0x4d2aeb2c,}, +{ 0x18c23246, 0xdc0a9f3d, 0x3fe16970,}, +#endif +}; + +static const limb_t log2_radix[BF_RADIX_MAX - 1] = { +#if LIMB_BITS == 32 +0x20000000, +0x32b80347, +0x40000000, +0x4a4d3c26, +0x52b80347, +0x59d5d9fd, +0x60000000, +0x6570068e, +0x6a4d3c26, +0x6eb3a9f0, +0x72b80347, +0x766a008e, +0x79d5d9fd, +0x7d053f6d, +0x80000000, +0x82cc7edf, +0x8570068e, +0x87ef05ae, +0x8a4d3c26, +0x8c8ddd45, +0x8eb3a9f0, +0x90c10501, +0x92b80347, +0x949a784c, +0x966a008e, +0x982809d6, +0x99d5d9fd, +0x9b74948f, +0x9d053f6d, +0x9e88c6b3, +0xa0000000, +0xa16bad37, +0xa2cc7edf, +0xa4231623, +0xa570068e, +#else +0x2000000000000000, +0x32b803473f7ad0f4, +0x4000000000000000, +0x4a4d3c25e68dc57f, +0x52b803473f7ad0f4, +0x59d5d9fd5010b366, +0x6000000000000000, +0x6570068e7ef5a1e8, +0x6a4d3c25e68dc57f, +0x6eb3a9f01975077f, +0x72b803473f7ad0f4, +0x766a008e4788cbcd, +0x79d5d9fd5010b366, +0x7d053f6d26089673, +0x8000000000000000, +0x82cc7edf592262d0, +0x8570068e7ef5a1e8, +0x87ef05ae409a0289, +0x8a4d3c25e68dc57f, +0x8c8ddd448f8b845a, +0x8eb3a9f01975077f, +0x90c10500d63aa659, +0x92b803473f7ad0f4, +0x949a784bcd1b8afe, +0x966a008e4788cbcd, +0x982809d5be7072dc, +0x99d5d9fd5010b366, +0x9b74948f5532da4b, +0x9d053f6d26089673, +0x9e88c6b3626a72aa, +0xa000000000000000, +0xa16bad3758efd873, +0xa2cc7edf592262d0, +0xa4231623369e78e6, +0xa570068e7ef5a1e8, +#endif +}; + +/* compute floor(a*b) or ceil(a*b) with b = log2(radix) or + b=1/log2(radix). For is_inv = 0, strict accuracy is not guaranteed + when radix is not a power of two. */ +slimb_t bf_mul_log2_radix(slimb_t a1, unsigned int radix, int is_inv, + int is_ceil1) +{ + int is_neg; + limb_t a; + BOOL is_ceil; + + is_ceil = is_ceil1; + a = a1; + if (a1 < 0) { + a = -a; + is_neg = 1; + } else { + is_neg = 0; + } + is_ceil ^= is_neg; + if ((radix & (radix - 1)) == 0) { + int radix_bits; + /* radix is a power of two */ + radix_bits = ceil_log2(radix); + if (is_inv) { + if (is_ceil) + a += radix_bits - 1; + a = a / radix_bits; + } else { + a = a * radix_bits; + } + } else { + const uint32_t *tab; + limb_t b0, b1; + dlimb_t t; + + if (is_inv) { + tab = inv_log2_radix[radix - 2]; +#if LIMB_BITS == 32 + b1 = tab[0]; + b0 = tab[1]; +#else + b1 = ((limb_t)tab[0] << 32) | tab[1]; + b0 = (limb_t)tab[2] << 32; +#endif + t = (dlimb_t)b0 * (dlimb_t)a; + t = (dlimb_t)b1 * (dlimb_t)a + (t >> LIMB_BITS); + a = t >> (LIMB_BITS - 1); + } else { + b0 = log2_radix[radix - 2]; + t = (dlimb_t)b0 * (dlimb_t)a; + a = t >> (LIMB_BITS - 3); + } + /* a = floor(result) and 'result' cannot be an integer */ + a += is_ceil; + } + if (is_neg) + a = -a; + return a; +} + +/* 'n' is the number of output limbs */ +static int bf_integer_to_radix_rec(bf_t *pow_tab, + limb_t *out, const bf_t *a, limb_t n, + int level, limb_t n0, limb_t radixl, + unsigned int radixl_bits) +{ + limb_t n1, n2, q_prec; + int ret; + + assert(n >= 1); + if (n == 1) { + out[0] = get_bits(a->tab, a->len, a->len * LIMB_BITS - a->expn); + } else if (n == 2) { + dlimb_t t; + slimb_t pos; + pos = a->len * LIMB_BITS - a->expn; + t = ((dlimb_t)get_bits(a->tab, a->len, pos + LIMB_BITS) << LIMB_BITS) | + get_bits(a->tab, a->len, pos); + if (likely(radixl == RADIXL_10)) { + /* use division by a constant when possible */ + out[0] = t % RADIXL_10; + out[1] = t / RADIXL_10; + } else { + out[0] = t % radixl; + out[1] = t / radixl; + } + } else { + bf_t Q, R, *B, *B_inv; + int q_add; + bf_init(a->ctx, &Q); + bf_init(a->ctx, &R); + n2 = (((n0 * 2) >> (level + 1)) + 1) / 2; + n1 = n - n2; + B = &pow_tab[2 * level]; + B_inv = &pow_tab[2 * level + 1]; + ret = 0; + if (B->len == 0) { + /* compute BASE^n2 */ + ret |= bf_pow_ui_ui(B, radixl, n2, BF_PREC_INF, BF_RNDZ); + /* we use enough bits for the maximum possible 'n1' value, + i.e. n2 + 1 */ + ret |= bf_set_ui(&R, 1); + ret |= bf_div(B_inv, &R, B, (n2 + 1) * radixl_bits + 2, BF_RNDN); + } + // printf("%d: n1=% " PRId64 " n2=%" PRId64 "\n", level, n1, n2); + q_prec = n1 * radixl_bits; + ret |= bf_mul(&Q, a, B_inv, q_prec, BF_RNDN); + ret |= bf_rint(&Q, BF_RNDZ); + + ret |= bf_mul(&R, &Q, B, BF_PREC_INF, BF_RNDZ); + ret |= bf_sub(&R, a, &R, BF_PREC_INF, BF_RNDZ); + + if (ret & BF_ST_MEM_ERROR) + goto fail; + /* adjust if necessary */ + q_add = 0; + while (R.sign && R.len != 0) { + if (bf_add(&R, &R, B, BF_PREC_INF, BF_RNDZ)) + goto fail; + q_add--; + } + while (bf_cmpu(&R, B) >= 0) { + if (bf_sub(&R, &R, B, BF_PREC_INF, BF_RNDZ)) + goto fail; + q_add++; + } + if (q_add != 0) { + if (bf_add_si(&Q, &Q, q_add, BF_PREC_INF, BF_RNDZ)) + goto fail; + } + if (bf_integer_to_radix_rec(pow_tab, out + n2, &Q, n1, level + 1, n0, + radixl, radixl_bits)) + goto fail; + if (bf_integer_to_radix_rec(pow_tab, out, &R, n2, level + 1, n0, + radixl, radixl_bits)) { + fail: + bf_delete(&Q); + bf_delete(&R); + return -1; + } + bf_delete(&Q); + bf_delete(&R); + } + return 0; +} + +/* return 0 if OK != 0 if memory error */ +static int bf_integer_to_radix(bf_t *r, const bf_t *a, limb_t radixl) +{ + bf_context_t *s = r->ctx; + limb_t r_len; + bf_t *pow_tab; + int i, pow_tab_len, ret; + + r_len = r->len; + pow_tab_len = (ceil_log2(r_len) + 2) * 2; /* XXX: check */ + pow_tab = bf_malloc(s, sizeof(pow_tab[0]) * pow_tab_len); + if (!pow_tab) + return -1; + for(i = 0; i < pow_tab_len; i++) + bf_init(r->ctx, &pow_tab[i]); + + ret = bf_integer_to_radix_rec(pow_tab, r->tab, a, r_len, 0, r_len, radixl, + ceil_log2(radixl)); + + for(i = 0; i < pow_tab_len; i++) { + bf_delete(&pow_tab[i]); + } + bf_free(s, pow_tab); + return ret; +} + +/* a must be >= 0. 'P' is the wanted number of digits in radix + 'radix'. 'r' is the mantissa represented as an integer. *pE + contains the exponent. Return != 0 if memory error. */ +static int bf_convert_to_radix(bf_t *r, slimb_t *pE, + const bf_t *a, int radix, + limb_t P, bf_rnd_t rnd_mode, + BOOL is_fixed_exponent) +{ + slimb_t E, e, prec, extra_bits, ziv_extra_bits, prec0; + bf_t B_s, *B = &B_s; + int e_sign, ret, res; + + if (a->len == 0) { + /* zero case */ + *pE = 0; + return bf_set(r, a); + } + + if (is_fixed_exponent) { + E = *pE; + } else { + /* compute the new exponent */ + E = 1 + bf_mul_log2_radix(a->expn - 1, radix, TRUE, FALSE); + } + // bf_print_str("a", a); + // printf("E=%ld P=%ld radix=%d\n", E, P, radix); + + for(;;) { + e = P - E; + e_sign = 0; + if (e < 0) { + e = -e; + e_sign = 1; + } + /* Note: precision for log2(radix) is not critical here */ + prec0 = bf_mul_log2_radix(P, radix, FALSE, TRUE); + ziv_extra_bits = 16; + for(;;) { + prec = prec0 + ziv_extra_bits; + /* XXX: rigorous error analysis needed */ + extra_bits = ceil_log2(e) * 2 + 1; + ret = bf_pow_ui_ui(r, radix, e, prec + extra_bits, + BF_RNDN | BF_FLAG_EXT_EXP); + if (!e_sign) + ret |= bf_mul(r, r, a, prec + extra_bits, + BF_RNDN | BF_FLAG_EXT_EXP); + else + ret |= bf_div(r, a, r, prec + extra_bits, + BF_RNDN | BF_FLAG_EXT_EXP); + if (ret & BF_ST_MEM_ERROR) + return BF_ST_MEM_ERROR; + /* if the result is not exact, check that it can be safely + rounded to an integer */ + if ((ret & BF_ST_INEXACT) && + !bf_can_round(r, r->expn, rnd_mode, prec)) { + /* and more precision and retry */ + ziv_extra_bits = ziv_extra_bits + (ziv_extra_bits / 2); + continue; + } else { + ret = bf_rint(r, rnd_mode); + if (ret & BF_ST_MEM_ERROR) + return BF_ST_MEM_ERROR; + break; + } + } + if (is_fixed_exponent) + break; + /* check that the result is < B^P */ + /* XXX: do a fast approximate test first ? */ + bf_init(r->ctx, B); + ret = bf_pow_ui_ui(B, radix, P, BF_PREC_INF, BF_RNDZ); + if (ret) { + bf_delete(B); + return ret; + } + res = bf_cmpu(r, B); + bf_delete(B); + if (res < 0) + break; + /* try a larger exponent */ + E++; + } + *pE = E; + return 0; +} + +static void limb_to_a(char *buf, limb_t n, unsigned int radix, int len) +{ + int digit, i; + + if (radix == 10) { + /* specific case with constant divisor */ + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % 10; + n = (limb_t)n / 10; + buf[i] = digit + '0'; + } + } else { + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % radix; + n = (limb_t)n / radix; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + buf[i] = digit; + } + } +} + +/* for power of 2 radixes */ +static void limb_to_a2(char *buf, limb_t n, unsigned int radix_bits, int len) +{ + int digit, i; + unsigned int mask; + + mask = (1 << radix_bits) - 1; + for(i = len - 1; i >= 0; i--) { + digit = n & mask; + n >>= radix_bits; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + buf[i] = digit; + } +} + +/* 'a' must be an integer if the is_dec = FALSE or if the radix is not + a power of two. A dot is added before the 'dot_pos' digit. dot_pos + = n_digits does not display the dot. 0 <= dot_pos <= + n_digits. n_digits >= 1. */ +static void output_digits(DynBuf *s, const bf_t *a1, int radix, limb_t n_digits, + limb_t dot_pos, BOOL is_dec) +{ + limb_t i, v, l; + slimb_t pos, pos_incr; + int digits_per_limb, buf_pos, radix_bits, first_buf_pos; + char buf[65]; + bf_t a_s, *a; + + if (is_dec) { + digits_per_limb = LIMB_DIGITS; + a = (bf_t *)a1; + radix_bits = 0; + pos = a->len; + pos_incr = 1; + first_buf_pos = 0; + } else if ((radix & (radix - 1)) == 0) { + a = (bf_t *)a1; + radix_bits = ceil_log2(radix); + digits_per_limb = LIMB_BITS / radix_bits; + pos_incr = digits_per_limb * radix_bits; + /* digits are aligned relative to the radix point */ + pos = a->len * LIMB_BITS + smod(-a->expn, radix_bits); + first_buf_pos = 0; + } else { + limb_t n, radixl; + + digits_per_limb = digits_per_limb_table[radix - 2]; + radixl = get_limb_radix(radix); + a = &a_s; + bf_init(a1->ctx, a); + n = (n_digits + digits_per_limb - 1) / digits_per_limb; + if (bf_resize(a, n)) { + dbuf_set_error(s); + goto done; + } + if (bf_integer_to_radix(a, a1, radixl)) { + dbuf_set_error(s); + goto done; + } + radix_bits = 0; + pos = n; + pos_incr = 1; + first_buf_pos = pos * digits_per_limb - n_digits; + } + buf_pos = digits_per_limb; + i = 0; + while (i < n_digits) { + if (buf_pos == digits_per_limb) { + pos -= pos_incr; + if (radix_bits == 0) { + v = get_limbz(a, pos); + limb_to_a(buf, v, radix, digits_per_limb); + } else { + v = get_bits(a->tab, a->len, pos); + limb_to_a2(buf, v, radix_bits, digits_per_limb); + } + buf_pos = first_buf_pos; + first_buf_pos = 0; + } + if (i < dot_pos) { + l = dot_pos; + } else { + if (i == dot_pos) + dbuf_putc(s, '.'); + l = n_digits; + } + l = bf_min(digits_per_limb - buf_pos, l - i); + dbuf_put(s, (uint8_t *)(buf + buf_pos), l); + buf_pos += l; + i += l; + } + done: + if (a != a1) + bf_delete(a); +} + +static void *bf_dbuf_realloc(void *opaque, void *ptr, size_t size) +{ + bf_context_t *s = opaque; + return bf_realloc(s, ptr, size); +} + +/* return the length in bytes. A trailing '\0' is added */ +static char *bf_ftoa_internal(size_t *plen, const bf_t *a2, int radix, + limb_t prec, bf_flags_t flags, BOOL is_dec) +{ + bf_context_t *ctx = a2->ctx; + DynBuf s_s, *s = &s_s; + int radix_bits; + + // bf_print_str("ftoa", a2); + // printf("radix=%d\n", radix); + dbuf_init2(s, ctx, bf_dbuf_realloc); + if (a2->expn == BF_EXP_NAN) { + dbuf_putstr(s, "NaN"); + } else { + if (a2->sign) + dbuf_putc(s, '-'); + if (a2->expn == BF_EXP_INF) { + if (flags & BF_FTOA_JS_QUIRKS) + dbuf_putstr(s, "Infinity"); + else + dbuf_putstr(s, "Inf"); + } else { + int fmt, ret; + slimb_t n_digits, n, i, n_max, n1; + bf_t a1_s, *a1 = &a1_s; + + if ((radix & (radix - 1)) != 0) + radix_bits = 0; + else + radix_bits = ceil_log2(radix); + + fmt = flags & BF_FTOA_FORMAT_MASK; + bf_init(ctx, a1); + if (fmt == BF_FTOA_FORMAT_FRAC) { + if (is_dec || radix_bits != 0) { + if (bf_set(a1, a2)) + goto fail1; +#ifdef USE_BF_DEC + if (is_dec) { + if (bfdec_round((bfdec_t *)a1, prec, (flags & BF_RND_MASK) | BF_FLAG_RADPNT_PREC) & BF_ST_MEM_ERROR) + goto fail1; + n = a1->expn; + } else +#endif + { + if (bf_round(a1, prec * radix_bits, (flags & BF_RND_MASK) | BF_FLAG_RADPNT_PREC) & BF_ST_MEM_ERROR) + goto fail1; + n = ceil_div(a1->expn, radix_bits); + } + if (flags & BF_FTOA_ADD_PREFIX) { + if (radix == 16) + dbuf_putstr(s, "0x"); + else if (radix == 8) + dbuf_putstr(s, "0o"); + else if (radix == 2) + dbuf_putstr(s, "0b"); + } + if (a1->expn == BF_EXP_ZERO) { + dbuf_putstr(s, "0"); + if (prec > 0) { + dbuf_putstr(s, "."); + for(i = 0; i < prec; i++) { + dbuf_putc(s, '0'); + } + } + } else { + n_digits = prec + n; + if (n <= 0) { + /* 0.x */ + dbuf_putstr(s, "0."); + for(i = 0; i < -n; i++) { + dbuf_putc(s, '0'); + } + if (n_digits > 0) { + output_digits(s, a1, radix, n_digits, n_digits, is_dec); + } + } else { + output_digits(s, a1, radix, n_digits, n, is_dec); + } + } + } else { + size_t pos, start; + bf_t a_s, *a = &a_s; + + /* make a positive number */ + a->tab = a2->tab; + a->len = a2->len; + a->expn = a2->expn; + a->sign = 0; + + /* one more digit for the rounding */ + n = 1 + bf_mul_log2_radix(bf_max(a->expn, 0), radix, TRUE, TRUE); + n_digits = n + prec; + n1 = n; + if (bf_convert_to_radix(a1, &n1, a, radix, n_digits, + flags & BF_RND_MASK, TRUE)) + goto fail1; + start = s->size; + output_digits(s, a1, radix, n_digits, n, is_dec); + /* remove leading zeros because we allocated one more digit */ + pos = start; + while ((pos + 1) < s->size && s->buf[pos] == '0' && + s->buf[pos + 1] != '.') + pos++; + if (pos > start) { + memmove(s->buf + start, s->buf + pos, s->size - pos); + s->size -= (pos - start); + } + } + } else { +#ifdef USE_BF_DEC + if (is_dec) { + if (bf_set(a1, a2)) + goto fail1; + if (fmt == BF_FTOA_FORMAT_FIXED) { + n_digits = prec; + n_max = n_digits; + if (bfdec_round((bfdec_t *)a1, prec, (flags & BF_RND_MASK)) & BF_ST_MEM_ERROR) + goto fail1; + } else { + /* prec is ignored */ + prec = n_digits = a1->len * LIMB_DIGITS; + /* remove the trailing zero digits */ + while (n_digits > 1 && + get_digit(a1->tab, a1->len, prec - n_digits) == 0) { + n_digits--; + } + n_max = n_digits + 4; + } + n = a1->expn; + } else +#endif + if (radix_bits != 0) { + if (bf_set(a1, a2)) + goto fail1; + if (fmt == BF_FTOA_FORMAT_FIXED) { + slimb_t prec_bits; + n_digits = prec; + n_max = n_digits; + /* align to the radix point */ + prec_bits = prec * radix_bits - + smod(-a1->expn, radix_bits); + if (bf_round(a1, prec_bits, + (flags & BF_RND_MASK)) & BF_ST_MEM_ERROR) + goto fail1; + } else { + limb_t digit_mask; + slimb_t pos; + /* position of the digit before the most + significant digit in bits */ + pos = a1->len * LIMB_BITS + + smod(-a1->expn, radix_bits); + n_digits = ceil_div(pos, radix_bits); + /* remove the trailing zero digits */ + digit_mask = ((limb_t)1 << radix_bits) - 1; + while (n_digits > 1 && + (get_bits(a1->tab, a1->len, pos - n_digits * radix_bits) & digit_mask) == 0) { + n_digits--; + } + n_max = n_digits + 4; + } + n = ceil_div(a1->expn, radix_bits); + } else { + bf_t a_s, *a = &a_s; + + /* make a positive number */ + a->tab = a2->tab; + a->len = a2->len; + a->expn = a2->expn; + a->sign = 0; + + if (fmt == BF_FTOA_FORMAT_FIXED) { + n_digits = prec; + n_max = n_digits; + } else { + slimb_t n_digits_max, n_digits_min; + + assert(prec != BF_PREC_INF); + n_digits = 1 + bf_mul_log2_radix(prec, radix, TRUE, TRUE); + /* max number of digits for non exponential + notation. The rational is to have the same rule + as JS i.e. n_max = 21 for 64 bit float in base 10. */ + n_max = n_digits + 4; + if (fmt == BF_FTOA_FORMAT_FREE_MIN) { + bf_t b_s, *b = &b_s; + + /* find the minimum number of digits by + dichotomy. */ + /* XXX: inefficient */ + n_digits_max = n_digits; + n_digits_min = 1; + bf_init(ctx, b); + while (n_digits_min < n_digits_max) { + n_digits = (n_digits_min + n_digits_max) / 2; + if (bf_convert_to_radix(a1, &n, a, radix, n_digits, + flags & BF_RND_MASK, FALSE)) { + bf_delete(b); + goto fail1; + } + /* convert back to a number and compare */ + ret = bf_mul_pow_radix(b, a1, radix, n - n_digits, + prec, + (flags & ~BF_RND_MASK) | + BF_RNDN); + if (ret & BF_ST_MEM_ERROR) { + bf_delete(b); + goto fail1; + } + if (bf_cmpu(b, a) == 0) { + n_digits_max = n_digits; + } else { + n_digits_min = n_digits + 1; + } + } + bf_delete(b); + n_digits = n_digits_max; + } + } + if (bf_convert_to_radix(a1, &n, a, radix, n_digits, + flags & BF_RND_MASK, FALSE)) { + fail1: + bf_delete(a1); + goto fail; + } + } + if (a1->expn == BF_EXP_ZERO && + fmt != BF_FTOA_FORMAT_FIXED && + !(flags & BF_FTOA_FORCE_EXP)) { + /* just output zero */ + dbuf_putstr(s, "0"); + } else { + if (flags & BF_FTOA_ADD_PREFIX) { + if (radix == 16) + dbuf_putstr(s, "0x"); + else if (radix == 8) + dbuf_putstr(s, "0o"); + else if (radix == 2) + dbuf_putstr(s, "0b"); + } + if (a1->expn == BF_EXP_ZERO) + n = 1; + if ((flags & BF_FTOA_FORCE_EXP) || + n <= -6 || n > n_max) { + const char *fmt; + /* exponential notation */ + output_digits(s, a1, radix, n_digits, 1, is_dec); + if (radix_bits != 0 && radix <= 16) { + if (flags & BF_FTOA_JS_QUIRKS) + fmt = "p%+" PRId_LIMB; + else + fmt = "p%" PRId_LIMB; + dbuf_printf(s, fmt, (n - 1) * radix_bits); + } else { + if (flags & BF_FTOA_JS_QUIRKS) + fmt = "%c%+" PRId_LIMB; + else + fmt = "%c%" PRId_LIMB; + dbuf_printf(s, fmt, + radix <= 10 ? 'e' : '@', n - 1); + } + } else if (n <= 0) { + /* 0.x */ + dbuf_putstr(s, "0."); + for(i = 0; i < -n; i++) { + dbuf_putc(s, '0'); + } + output_digits(s, a1, radix, n_digits, n_digits, is_dec); + } else { + if (n_digits <= n) { + /* no dot */ + output_digits(s, a1, radix, n_digits, n_digits, is_dec); + for(i = 0; i < (n - n_digits); i++) + dbuf_putc(s, '0'); + } else { + output_digits(s, a1, radix, n_digits, n, is_dec); + } + } + } + } + bf_delete(a1); + } + } + dbuf_putc(s, '\0'); + if (dbuf_error(s)) + goto fail; + if (plen) + *plen = s->size - 1; + return (char *)s->buf; + fail: + bf_free(ctx, s->buf); + if (plen) + *plen = 0; + return NULL; +} + +char *bf_ftoa(size_t *plen, const bf_t *a, int radix, limb_t prec, + bf_flags_t flags) +{ + return bf_ftoa_internal(plen, a, radix, prec, flags, FALSE); +} + +/***************************************************************/ +/* transcendental functions */ + +/* Note: the algorithm is from MPFR */ +static void bf_const_log2_rec(bf_t *T, bf_t *P, bf_t *Q, limb_t n1, + limb_t n2, BOOL need_P) +{ + bf_context_t *s = T->ctx; + if ((n2 - n1) == 1) { + if (n1 == 0) { + bf_set_ui(P, 3); + } else { + bf_set_ui(P, n1); + P->sign = 1; + } + bf_set_ui(Q, 2 * n1 + 1); + Q->expn += 2; + bf_set(T, P); + } else { + limb_t m; + bf_t T1_s, *T1 = &T1_s; + bf_t P1_s, *P1 = &P1_s; + bf_t Q1_s, *Q1 = &Q1_s; + + m = n1 + ((n2 - n1) >> 1); + bf_const_log2_rec(T, P, Q, n1, m, TRUE); + bf_init(s, T1); + bf_init(s, P1); + bf_init(s, Q1); + bf_const_log2_rec(T1, P1, Q1, m, n2, need_P); + bf_mul(T, T, Q1, BF_PREC_INF, BF_RNDZ); + bf_mul(T1, T1, P, BF_PREC_INF, BF_RNDZ); + bf_add(T, T, T1, BF_PREC_INF, BF_RNDZ); + if (need_P) + bf_mul(P, P, P1, BF_PREC_INF, BF_RNDZ); + bf_mul(Q, Q, Q1, BF_PREC_INF, BF_RNDZ); + bf_delete(T1); + bf_delete(P1); + bf_delete(Q1); + } +} + +/* compute log(2) with faithful rounding at precision 'prec' */ +static void bf_const_log2_internal(bf_t *T, limb_t prec) +{ + limb_t w, N; + bf_t P_s, *P = &P_s; + bf_t Q_s, *Q = &Q_s; + + w = prec + 15; + N = w / 3 + 1; + bf_init(T->ctx, P); + bf_init(T->ctx, Q); + bf_const_log2_rec(T, P, Q, 0, N, FALSE); + bf_div(T, T, Q, prec, BF_RNDN); + bf_delete(P); + bf_delete(Q); +} + +/* PI constant */ + +#define CHUD_A 13591409 +#define CHUD_B 545140134 +#define CHUD_C 640320 +#define CHUD_BITS_PER_TERM 47 + +static void chud_bs(bf_t *P, bf_t *Q, bf_t *G, int64_t a, int64_t b, int need_g, + limb_t prec) +{ + bf_context_t *s = P->ctx; + int64_t c; + + if (a == (b - 1)) { + bf_t T0, T1; + + bf_init(s, &T0); + bf_init(s, &T1); + bf_set_ui(G, 2 * b - 1); + bf_mul_ui(G, G, 6 * b - 1, prec, BF_RNDN); + bf_mul_ui(G, G, 6 * b - 5, prec, BF_RNDN); + bf_set_ui(&T0, CHUD_B); + bf_mul_ui(&T0, &T0, b, prec, BF_RNDN); + bf_set_ui(&T1, CHUD_A); + bf_add(&T0, &T0, &T1, prec, BF_RNDN); + bf_mul(P, G, &T0, prec, BF_RNDN); + P->sign = b & 1; + + bf_set_ui(Q, b); + bf_mul_ui(Q, Q, b, prec, BF_RNDN); + bf_mul_ui(Q, Q, b, prec, BF_RNDN); + bf_mul_ui(Q, Q, (uint64_t)CHUD_C * CHUD_C * CHUD_C / 24, prec, BF_RNDN); + bf_delete(&T0); + bf_delete(&T1); + } else { + bf_t P2, Q2, G2; + + bf_init(s, &P2); + bf_init(s, &Q2); + bf_init(s, &G2); + + c = (a + b) / 2; + chud_bs(P, Q, G, a, c, 1, prec); + chud_bs(&P2, &Q2, &G2, c, b, need_g, prec); + + /* Q = Q1 * Q2 */ + /* G = G1 * G2 */ + /* P = P1 * Q2 + P2 * G1 */ + bf_mul(&P2, &P2, G, prec, BF_RNDN); + if (!need_g) + bf_set_ui(G, 0); + bf_mul(P, P, &Q2, prec, BF_RNDN); + bf_add(P, P, &P2, prec, BF_RNDN); + bf_delete(&P2); + + bf_mul(Q, Q, &Q2, prec, BF_RNDN); + bf_delete(&Q2); + if (need_g) + bf_mul(G, G, &G2, prec, BF_RNDN); + bf_delete(&G2); + } +} + +/* compute Pi with faithful rounding at precision 'prec' using the + Chudnovsky formula */ +static void bf_const_pi_internal(bf_t *Q, limb_t prec) +{ + bf_context_t *s = Q->ctx; + int64_t n, prec1; + bf_t P, G; + + /* number of serie terms */ + n = prec / CHUD_BITS_PER_TERM + 1; + /* XXX: precision analysis */ + prec1 = prec + 32; + + bf_init(s, &P); + bf_init(s, &G); + + chud_bs(&P, Q, &G, 0, n, 0, BF_PREC_INF); + + bf_mul_ui(&G, Q, CHUD_A, prec1, BF_RNDN); + bf_add(&P, &G, &P, prec1, BF_RNDN); + bf_div(Q, Q, &P, prec1, BF_RNDF); + + bf_set_ui(&P, CHUD_C); + bf_sqrt(&G, &P, prec1, BF_RNDF); + bf_mul_ui(&G, &G, (uint64_t)CHUD_C / 12, prec1, BF_RNDF); + bf_mul(Q, Q, &G, prec, BF_RNDN); + bf_delete(&P); + bf_delete(&G); +} + +static int bf_const_get(bf_t *T, limb_t prec, bf_flags_t flags, + BFConstCache *c, + void (*func)(bf_t *res, limb_t prec), int sign) +{ + limb_t ziv_extra_bits, prec1; + + ziv_extra_bits = 32; + for(;;) { + prec1 = prec + ziv_extra_bits; + if (c->prec < prec1) { + if (c->val.len == 0) + bf_init(T->ctx, &c->val); + func(&c->val, prec1); + c->prec = prec1; + } else { + prec1 = c->prec; + } + bf_set(T, &c->val); + T->sign = sign; + if (!bf_can_round(T, prec, flags & BF_RND_MASK, prec1)) { + /* and more precision and retry */ + ziv_extra_bits = ziv_extra_bits + (ziv_extra_bits / 2); + } else { + break; + } + } + return bf_round(T, prec, flags); +} + +static void bf_const_free(BFConstCache *c) +{ + bf_delete(&c->val); + memset(c, 0, sizeof(*c)); +} + +int bf_const_log2(bf_t *T, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = T->ctx; + return bf_const_get(T, prec, flags, &s->log2_cache, bf_const_log2_internal, 0); +} + +/* return rounded pi * (1 - 2 * sign) */ +static int bf_const_pi_signed(bf_t *T, int sign, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = T->ctx; + return bf_const_get(T, prec, flags, &s->pi_cache, bf_const_pi_internal, + sign); +} + +int bf_const_pi(bf_t *T, limb_t prec, bf_flags_t flags) +{ + return bf_const_pi_signed(T, 0, prec, flags); +} + +void bf_clear_cache(bf_context_t *s) +{ +#ifdef USE_FFT_MUL + fft_clear_cache(s); +#endif + bf_const_free(&s->log2_cache); + bf_const_free(&s->pi_cache); +} + +/* ZivFunc should compute the result 'r' with faithful rounding at + precision 'prec'. For efficiency purposes, the final bf_round() + does not need to be done in the function. */ +typedef int ZivFunc(bf_t *r, const bf_t *a, limb_t prec, void *opaque); + +static int bf_ziv_rounding(bf_t *r, const bf_t *a, + limb_t prec, bf_flags_t flags, + ZivFunc *f, void *opaque) +{ + int rnd_mode, ret; + slimb_t prec1, ziv_extra_bits; + + rnd_mode = flags & BF_RND_MASK; + if (rnd_mode == BF_RNDF) { + /* no need to iterate */ + f(r, a, prec, opaque); + ret = 0; + } else { + ziv_extra_bits = 32; + for(;;) { + prec1 = prec + ziv_extra_bits; + ret = f(r, a, prec1, opaque); + if (ret & (BF_ST_OVERFLOW | BF_ST_UNDERFLOW | BF_ST_MEM_ERROR)) { + /* overflow or underflow should never happen because + it indicates the rounding cannot be done correctly, + but we do not catch all the cases */ + return ret; + } + /* if the result is exact, we can stop */ + if (!(ret & BF_ST_INEXACT)) { + ret = 0; + break; + } + if (bf_can_round(r, prec, rnd_mode, prec1)) { + ret = BF_ST_INEXACT; + break; + } + ziv_extra_bits = ziv_extra_bits * 2; + // printf("ziv_extra_bits=%" PRId64 "\n", (int64_t)ziv_extra_bits); + } + } + if (r->len == 0) + return ret; + else + return __bf_round(r, prec, flags, r->len, ret); +} + +/* add (1 - 2*e_sign) * 2^e */ +static int bf_add_epsilon(bf_t *r, const bf_t *a, slimb_t e, int e_sign, + limb_t prec, int flags) +{ + bf_t T_s, *T = &T_s; + int ret; + /* small argument case: result = 1 + epsilon * sign(x) */ + bf_init(a->ctx, T); + bf_set_ui(T, 1); + T->sign = e_sign; + T->expn += e; + ret = bf_add(r, r, T, prec, flags); + bf_delete(T); + return ret; +} + +/* Compute the exponential using faithful rounding at precision 'prec'. + Note: the algorithm is from MPFR */ +static int bf_exp_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + slimb_t n, K, l, i, prec1; + + assert(r != a); + + /* argument reduction: + T = a - n*log(2) with 0 <= T < log(2) and n integer. + */ + bf_init(s, T); + if (a->expn <= -1) { + /* 0 <= abs(a) <= 0.5 */ + if (a->sign) + n = -1; + else + n = 0; + } else { + bf_const_log2(T, LIMB_BITS, BF_RNDZ); + bf_div(T, a, T, LIMB_BITS, BF_RNDD); + bf_get_limb(&n, T, 0); + } + + K = bf_isqrt((prec + 1) / 2); + l = (prec - 1) / K + 1; + /* XXX: precision analysis ? */ + prec1 = prec + (K + 2 * l + 18) + K + 8; + if (a->expn > 0) + prec1 += a->expn; + // printf("n=%ld K=%ld prec1=%ld\n", n, K, prec1); + + bf_const_log2(T, prec1, BF_RNDF); + bf_mul_si(T, T, n, prec1, BF_RNDN); + bf_sub(T, a, T, prec1, BF_RNDN); + + /* reduce the range of T */ + bf_mul_2exp(T, -K, BF_PREC_INF, BF_RNDZ); + + /* Taylor expansion around zero : + 1 + x + x^2/2 + ... + x^n/n! + = (1 + x * (1 + x/2 * (1 + ... (x/n)))) + */ + { + bf_t U_s, *U = &U_s; + + bf_init(s, U); + bf_set_ui(r, 1); + for(i = l ; i >= 1; i--) { + bf_set_ui(U, i); + bf_div(U, T, U, prec1, BF_RNDN); + bf_mul(r, r, U, prec1, BF_RNDN); + bf_add_si(r, r, 1, prec1, BF_RNDN); + } + bf_delete(U); + } + bf_delete(T); + + /* undo the range reduction */ + for(i = 0; i < K; i++) { + bf_mul(r, r, r, prec1, BF_RNDN | BF_FLAG_EXT_EXP); + } + + /* undo the argument reduction */ + bf_mul_2exp(r, n, BF_PREC_INF, BF_RNDZ | BF_FLAG_EXT_EXP); + + return BF_ST_INEXACT; +} + +/* crude overflow and underflow tests for exp(a). a_low <= a <= a_high */ +static int check_exp_underflow_overflow(bf_context_t *s, bf_t *r, + const bf_t *a_low, const bf_t *a_high, + limb_t prec, bf_flags_t flags) +{ + bf_t T_s, *T = &T_s; + bf_t log2_s, *log2 = &log2_s; + slimb_t e_min, e_max; + + if (a_high->expn <= 0) + return 0; + + e_max = (limb_t)1 << (bf_get_exp_bits(flags) - 1); + e_min = -e_max + 3; + if (flags & BF_FLAG_SUBNORMAL) + e_min -= (prec - 1); + + bf_init(s, T); + bf_init(s, log2); + bf_const_log2(log2, LIMB_BITS, BF_RNDU); + bf_mul_ui(T, log2, e_max, LIMB_BITS, BF_RNDU); + /* a_low > e_max * log(2) implies exp(a) > e_max */ + if (bf_cmp_lt(T, a_low) > 0) { + /* overflow */ + bf_delete(T); + bf_delete(log2); + return bf_set_overflow(r, 0, prec, flags); + } + /* a_high < (e_min - 2) * log(2) implies exp(a) < (e_min - 2) */ + bf_const_log2(log2, LIMB_BITS, BF_RNDD); + bf_mul_si(T, log2, e_min - 2, LIMB_BITS, BF_RNDD); + if (bf_cmp_lt(a_high, T)) { + int rnd_mode = flags & BF_RND_MASK; + + /* underflow */ + bf_delete(T); + bf_delete(log2); + if (rnd_mode == BF_RNDU) { + /* set the smallest value */ + bf_set_ui(r, 1); + r->expn = e_min; + } else { + bf_set_zero(r, 0); + } + return BF_ST_UNDERFLOW | BF_ST_INEXACT; + } + bf_delete(log2); + bf_delete(T); + return 0; +} + +int bf_exp(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = r->ctx; + int ret; + assert(r != a); + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + } else if (a->expn == BF_EXP_INF) { + if (a->sign) + bf_set_zero(r, 0); + else + bf_set_inf(r, 0); + } else { + bf_set_ui(r, 1); + } + return 0; + } + + ret = check_exp_underflow_overflow(s, r, a, a, prec, flags); + if (ret) + return ret; + if (a->expn < 0 && (-a->expn) >= (prec + 2)) { + /* small argument case: result = 1 + epsilon * sign(x) */ + bf_set_ui(r, 1); + return bf_add_epsilon(r, r, -(prec + 2), a->sign, prec, flags); + } + + return bf_ziv_rounding(r, a, prec, flags, bf_exp_internal, NULL); +} + +static int bf_log_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + bf_t U_s, *U = &U_s; + bf_t V_s, *V = &V_s; + slimb_t n, prec1, l, i, K; + + assert(r != a); + + bf_init(s, T); + /* argument reduction 1 */ + /* T=a*2^n with 2/3 <= T <= 4/3 */ + { + bf_t U_s, *U = &U_s; + bf_set(T, a); + n = T->expn; + T->expn = 0; + /* U= ~ 2/3 */ + bf_init(s, U); + bf_set_ui(U, 0xaaaaaaaa); + U->expn = 0; + if (bf_cmp_lt(T, U)) { + T->expn++; + n--; + } + bf_delete(U); + } + // printf("n=%ld\n", n); + // bf_print_str("T", T); + + /* XXX: precision analysis */ + /* number of iterations for argument reduction 2 */ + K = bf_isqrt((prec + 1) / 2); + /* order of Taylor expansion */ + l = prec / (2 * K) + 1; + /* precision of the intermediate computations */ + prec1 = prec + K + 2 * l + 32; + + bf_init(s, U); + bf_init(s, V); + + /* Note: cancellation occurs here, so we use more precision (XXX: + reduce the precision by computing the exact cancellation) */ + bf_add_si(T, T, -1, BF_PREC_INF, BF_RNDN); + + /* argument reduction 2 */ + for(i = 0; i < K; i++) { + /* T = T / (1 + sqrt(1 + T)) */ + bf_add_si(U, T, 1, prec1, BF_RNDN); + bf_sqrt(V, U, prec1, BF_RNDF); + bf_add_si(U, V, 1, prec1, BF_RNDN); + bf_div(T, T, U, prec1, BF_RNDN); + } + + { + bf_t Y_s, *Y = &Y_s; + bf_t Y2_s, *Y2 = &Y2_s; + bf_init(s, Y); + bf_init(s, Y2); + + /* compute ln(1+x) = ln((1+y)/(1-y)) with y=x/(2+x) + = y + y^3/3 + ... + y^(2*l + 1) / (2*l+1) + with Y=Y^2 + = y*(1+Y/3+Y^2/5+...) = y*(1+Y*(1/3+Y*(1/5 + ...))) + */ + bf_add_si(Y, T, 2, prec1, BF_RNDN); + bf_div(Y, T, Y, prec1, BF_RNDN); + + bf_mul(Y2, Y, Y, prec1, BF_RNDN); + bf_set_ui(r, 0); + for(i = l; i >= 1; i--) { + bf_set_ui(U, 1); + bf_set_ui(V, 2 * i + 1); + bf_div(U, U, V, prec1, BF_RNDN); + bf_add(r, r, U, prec1, BF_RNDN); + bf_mul(r, r, Y2, prec1, BF_RNDN); + } + bf_add_si(r, r, 1, prec1, BF_RNDN); + bf_mul(r, r, Y, prec1, BF_RNDN); + bf_delete(Y); + bf_delete(Y2); + } + bf_delete(V); + bf_delete(U); + + /* multiplication by 2 for the Taylor expansion and undo the + argument reduction 2*/ + bf_mul_2exp(r, K + 1, BF_PREC_INF, BF_RNDZ); + + /* undo the argument reduction 1 */ + bf_const_log2(T, prec1, BF_RNDF); + bf_mul_si(T, T, n, prec1, BF_RNDN); + bf_add(r, r, T, prec1, BF_RNDN); + + bf_delete(T); + return BF_ST_INEXACT; +} + +int bf_log(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + + assert(r != a); + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF) { + if (a->sign) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_set_inf(r, 0); + return 0; + } + } else { + bf_set_inf(r, 1); + return 0; + } + } + if (a->sign) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } + bf_init(s, T); + bf_set_ui(T, 1); + if (bf_cmp_eq(a, T)) { + bf_set_zero(r, 0); + bf_delete(T); + return 0; + } + bf_delete(T); + + return bf_ziv_rounding(r, a, prec, flags, bf_log_internal, NULL); +} + +/* x and y finite and x > 0 */ +static int bf_pow_generic(bf_t *r, const bf_t *x, limb_t prec, void *opaque) +{ + bf_context_t *s = r->ctx; + const bf_t *y = opaque; + bf_t T_s, *T = &T_s; + limb_t prec1; + + bf_init(s, T); + /* XXX: proof for the added precision */ + prec1 = prec + 32; + bf_log(T, x, prec1, BF_RNDF | BF_FLAG_EXT_EXP); + bf_mul(T, T, y, prec1, BF_RNDF | BF_FLAG_EXT_EXP); + if (bf_is_nan(T)) + bf_set_nan(r); + else + bf_exp_internal(r, T, prec1, NULL); /* no overflow/underlow test needed */ + bf_delete(T); + return BF_ST_INEXACT; +} + +/* x and y finite, x > 0, y integer and y fits on one limb */ +static int bf_pow_int(bf_t *r, const bf_t *x, limb_t prec, void *opaque) +{ + bf_context_t *s = r->ctx; + const bf_t *y = opaque; + bf_t T_s, *T = &T_s; + limb_t prec1; + int ret; + slimb_t y1; + + bf_get_limb(&y1, y, 0); + if (y1 < 0) + y1 = -y1; + /* XXX: proof for the added precision */ + prec1 = prec + ceil_log2(y1) * 2 + 8; + ret = bf_pow_ui(r, x, y1 < 0 ? -y1 : y1, prec1, BF_RNDN | BF_FLAG_EXT_EXP); + if (y->sign) { + bf_init(s, T); + bf_set_ui(T, 1); + ret |= bf_div(r, T, r, prec1, BF_RNDN | BF_FLAG_EXT_EXP); + bf_delete(T); + } + return ret; +} + +/* x must be a finite non zero float. Return TRUE if there is a + floating point number r such as x=r^(2^n) and return this floating + point number 'r'. Otherwise return FALSE and r is undefined. */ +static BOOL check_exact_power2n(bf_t *r, const bf_t *x, slimb_t n) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + slimb_t e, i, er; + limb_t v; + + /* x = m*2^e with m odd integer */ + e = bf_get_exp_min(x); + /* fast check on the exponent */ + if (n > (LIMB_BITS - 1)) { + if (e != 0) + return FALSE; + er = 0; + } else { + if ((e & (((limb_t)1 << n) - 1)) != 0) + return FALSE; + er = e >> n; + } + /* every perfect odd square = 1 modulo 8 */ + v = get_bits(x->tab, x->len, x->len * LIMB_BITS - x->expn + e); + if ((v & 7) != 1) + return FALSE; + + bf_init(s, T); + bf_set(T, x); + T->expn -= e; + for(i = 0; i < n; i++) { + if (i != 0) + bf_set(T, r); + if (bf_sqrtrem(r, NULL, T) != 0) + return FALSE; + } + r->expn += er; + return TRUE; +} + +/* prec = BF_PREC_INF is accepted for x and y integers and y >= 0 */ +int bf_pow(bf_t *r, const bf_t *x, const bf_t *y, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + bf_t ytmp_s; + BOOL y_is_int, y_is_odd; + int r_sign, ret, rnd_mode; + slimb_t y_emin; + + if (x->len == 0 || y->len == 0) { + if (y->expn == BF_EXP_ZERO) { + /* pow(x, 0) = 1 */ + bf_set_ui(r, 1); + } else if (x->expn == BF_EXP_NAN) { + bf_set_nan(r); + } else { + int cmp_x_abs_1; + bf_set_ui(r, 1); + cmp_x_abs_1 = bf_cmpu(x, r); + if (cmp_x_abs_1 == 0 && (flags & BF_POW_JS_QUIRKS) && + (y->expn >= BF_EXP_INF)) { + bf_set_nan(r); + } else if (cmp_x_abs_1 == 0 && + (!x->sign || y->expn != BF_EXP_NAN)) { + /* pow(1, y) = 1 even if y = NaN */ + /* pow(-1, +/-inf) = 1 */ + } else if (y->expn == BF_EXP_NAN) { + bf_set_nan(r); + } else if (y->expn == BF_EXP_INF) { + if (y->sign == (cmp_x_abs_1 > 0)) { + bf_set_zero(r, 0); + } else { + bf_set_inf(r, 0); + } + } else { + y_emin = bf_get_exp_min(y); + y_is_odd = (y_emin == 0); + if (y->sign == (x->expn == BF_EXP_ZERO)) { + bf_set_inf(r, y_is_odd & x->sign); + if (y->sign) { + /* pow(0, y) with y < 0 */ + return BF_ST_DIVIDE_ZERO; + } + } else { + bf_set_zero(r, y_is_odd & x->sign); + } + } + } + return 0; + } + bf_init(s, T); + bf_set(T, x); + y_emin = bf_get_exp_min(y); + y_is_int = (y_emin >= 0); + rnd_mode = flags & BF_RND_MASK; + if (x->sign) { + if (!y_is_int) { + bf_set_nan(r); + bf_delete(T); + return BF_ST_INVALID_OP; + } + y_is_odd = (y_emin == 0); + r_sign = y_is_odd; + /* change the directed rounding mode if the sign of the result + is changed */ + if (r_sign && (rnd_mode == BF_RNDD || rnd_mode == BF_RNDU)) + flags ^= 1; + bf_neg(T); + } else { + r_sign = 0; + } + + bf_set_ui(r, 1); + if (bf_cmp_eq(T, r)) { + /* abs(x) = 1: nothing more to do */ + ret = 0; + } else { + /* check the overflow/underflow cases */ + { + bf_t al_s, *al = &al_s; + bf_t ah_s, *ah = &ah_s; + limb_t precl = LIMB_BITS; + + bf_init(s, al); + bf_init(s, ah); + /* compute bounds of log(abs(x)) * y with a low precision */ + /* XXX: compute bf_log() once */ + /* XXX: add a fast test before this slow test */ + bf_log(al, T, precl, BF_RNDD); + bf_log(ah, T, precl, BF_RNDU); + bf_mul(al, al, y, precl, BF_RNDD ^ y->sign); + bf_mul(ah, ah, y, precl, BF_RNDU ^ y->sign); + ret = check_exp_underflow_overflow(s, r, al, ah, prec, flags); + bf_delete(al); + bf_delete(ah); + if (ret) + goto done; + } + + if (y_is_int) { + slimb_t T_bits, e; + int_pow: + T_bits = T->expn - bf_get_exp_min(T); + if (T_bits == 1) { + /* pow(2^b, y) = 2^(b*y) */ + bf_mul_si(T, y, T->expn - 1, LIMB_BITS, BF_RNDZ); + bf_get_limb(&e, T, 0); + bf_set_ui(r, 1); + ret = bf_mul_2exp(r, e, prec, flags); + } else if (prec == BF_PREC_INF) { + slimb_t y1; + /* specific case for infinite precision (integer case) */ + bf_get_limb(&y1, y, 0); + assert(!y->sign); + /* x must be an integer, so abs(x) >= 2 */ + if (y1 >= ((slimb_t)1 << BF_EXP_BITS_MAX)) { + bf_delete(T); + return bf_set_overflow(r, 0, BF_PREC_INF, flags); + } + ret = bf_pow_ui(r, T, y1, BF_PREC_INF, BF_RNDZ); + } else { + if (y->expn <= 31) { + /* small enough power: use exponentiation in all cases */ + } else if (y->sign) { + /* cannot be exact */ + goto general_case; + } else { + if (rnd_mode == BF_RNDF) + goto general_case; /* no need to track exact results */ + /* see if the result has a chance to be exact: + if x=a*2^b (a odd), x^y=a^y*2^(b*y) + x^y needs a precision of at least floor_log2(a)*y bits + */ + bf_mul_si(r, y, T_bits - 1, LIMB_BITS, BF_RNDZ); + bf_get_limb(&e, r, 0); + if (prec < e) + goto general_case; + } + ret = bf_ziv_rounding(r, T, prec, flags, bf_pow_int, (void *)y); + } + } else { + if (rnd_mode != BF_RNDF) { + bf_t *y1; + if (y_emin < 0 && check_exact_power2n(r, T, -y_emin)) { + /* the problem is reduced to a power to an integer */ +#if 0 + printf("\nn=%" PRId64 "\n", -(int64_t)y_emin); + bf_print_str("T", T); + bf_print_str("r", r); +#endif + bf_set(T, r); + y1 = &ytmp_s; + y1->tab = y->tab; + y1->len = y->len; + y1->sign = y->sign; + y1->expn = y->expn - y_emin; + y = y1; + goto int_pow; + } + } + general_case: + ret = bf_ziv_rounding(r, T, prec, flags, bf_pow_generic, (void *)y); + } + } + done: + bf_delete(T); + r->sign = r_sign; + return ret; +} + +/* compute sqrt(-2*x-x^2) to get |sin(x)| from cos(x) - 1. */ +static void bf_sqrt_sin(bf_t *r, const bf_t *x, limb_t prec1) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + bf_init(s, T); + bf_set(T, x); + bf_mul(r, T, T, prec1, BF_RNDN); + bf_mul_2exp(T, 1, BF_PREC_INF, BF_RNDZ); + bf_add(T, T, r, prec1, BF_RNDN); + bf_neg(T); + bf_sqrt(r, T, prec1, BF_RNDF); + bf_delete(T); +} + +static int bf_sincos(bf_t *s, bf_t *c, const bf_t *a, limb_t prec) +{ + bf_context_t *s1 = a->ctx; + bf_t T_s, *T = &T_s; + bf_t U_s, *U = &U_s; + bf_t r_s, *r = &r_s; + slimb_t K, prec1, i, l, mod, prec2; + int is_neg; + + assert(c != a && s != a); + + bf_init(s1, T); + bf_init(s1, U); + bf_init(s1, r); + + /* XXX: precision analysis */ + K = bf_isqrt(prec / 2); + l = prec / (2 * K) + 1; + prec1 = prec + 2 * K + l + 8; + + /* after the modulo reduction, -pi/4 <= T <= pi/4 */ + if (a->expn <= -1) { + /* abs(a) <= 0.25: no modulo reduction needed */ + bf_set(T, a); + mod = 0; + } else { + slimb_t cancel; + cancel = 0; + for(;;) { + prec2 = prec1 + a->expn + cancel; + bf_const_pi(U, prec2, BF_RNDF); + bf_mul_2exp(U, -1, BF_PREC_INF, BF_RNDZ); + bf_remquo(&mod, T, a, U, prec2, BF_RNDN, BF_RNDN); + // printf("T.expn=%ld prec2=%ld\n", T->expn, prec2); + if (mod == 0 || (T->expn != BF_EXP_ZERO && + (T->expn + prec2) >= (prec1 - 1))) + break; + /* increase the number of bits until the precision is good enough */ + cancel = bf_max(-T->expn, (cancel + 1) * 3 / 2); + } + mod &= 3; + } + + is_neg = T->sign; + + /* compute cosm1(x) = cos(x) - 1 */ + bf_mul(T, T, T, prec1, BF_RNDN); + bf_mul_2exp(T, -2 * K, BF_PREC_INF, BF_RNDZ); + + /* Taylor expansion: + -x^2/2 + x^4/4! - x^6/6! + ... + */ + bf_set_ui(r, 1); + for(i = l ; i >= 1; i--) { + bf_set_ui(U, 2 * i - 1); + bf_mul_ui(U, U, 2 * i, BF_PREC_INF, BF_RNDZ); + bf_div(U, T, U, prec1, BF_RNDN); + bf_mul(r, r, U, prec1, BF_RNDN); + bf_neg(r); + if (i != 1) + bf_add_si(r, r, 1, prec1, BF_RNDN); + } + bf_delete(U); + + /* undo argument reduction: + cosm1(2*x)= 2*(2*cosm1(x)+cosm1(x)^2) + */ + for(i = 0; i < K; i++) { + bf_mul(T, r, r, prec1, BF_RNDN); + bf_mul_2exp(r, 1, BF_PREC_INF, BF_RNDZ); + bf_add(r, r, T, prec1, BF_RNDN); + bf_mul_2exp(r, 1, BF_PREC_INF, BF_RNDZ); + } + bf_delete(T); + + if (c) { + if ((mod & 1) == 0) { + bf_add_si(c, r, 1, prec1, BF_RNDN); + } else { + bf_sqrt_sin(c, r, prec1); + c->sign = is_neg ^ 1; + } + c->sign ^= mod >> 1; + } + if (s) { + if ((mod & 1) == 0) { + bf_sqrt_sin(s, r, prec1); + s->sign = is_neg; + } else { + bf_add_si(s, r, 1, prec1, BF_RNDN); + } + s->sign ^= mod >> 1; + } + bf_delete(r); + return BF_ST_INEXACT; +} + +static int bf_cos_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) +{ + return bf_sincos(NULL, r, a, prec); +} + +int bf_cos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_set_ui(r, 1); + return 0; + } + } + + /* small argument case: result = 1+r(x) with r(x) = -x^2/2 + + O(X^4). We assume r(x) < 2^(2*EXP(x) - 1). */ + if (a->expn < 0) { + slimb_t e; + e = 2 * a->expn - 1; + if (e < -(prec + 2)) { + bf_set_ui(r, 1); + return bf_add_epsilon(r, r, e, 1, prec, flags); + } + } + + return bf_ziv_rounding(r, a, prec, flags, bf_cos_internal, NULL); +} + +static int bf_sin_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) +{ + return bf_sincos(r, NULL, a, prec); +} + +int bf_sin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_set_zero(r, a->sign); + return 0; + } + } + + /* small argument case: result = x+r(x) with r(x) = -x^3/6 + + O(X^5). We assume r(x) < 2^(3*EXP(x) - 2). */ + if (a->expn < 0) { + slimb_t e; + e = sat_add(2 * a->expn, a->expn - 2); + if (e < a->expn - bf_max(prec + 2, a->len * LIMB_BITS + 2)) { + bf_set(r, a); + return bf_add_epsilon(r, r, e, 1 - a->sign, prec, flags); + } + } + + return bf_ziv_rounding(r, a, prec, flags, bf_sin_internal, NULL); +} + +static int bf_tan_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + limb_t prec1; + + /* XXX: precision analysis */ + prec1 = prec + 8; + bf_init(s, T); + bf_sincos(r, T, a, prec1); + bf_div(r, r, T, prec1, BF_RNDF); + bf_delete(T); + return BF_ST_INEXACT; +} + +int bf_tan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + assert(r != a); + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_set_zero(r, a->sign); + return 0; + } + } + + /* small argument case: result = x+r(x) with r(x) = x^3/3 + + O(X^5). We assume r(x) < 2^(3*EXP(x) - 1). */ + if (a->expn < 0) { + slimb_t e; + e = sat_add(2 * a->expn, a->expn - 1); + if (e < a->expn - bf_max(prec + 2, a->len * LIMB_BITS + 2)) { + bf_set(r, a); + return bf_add_epsilon(r, r, e, a->sign, prec, flags); + } + } + + return bf_ziv_rounding(r, a, prec, flags, bf_tan_internal, NULL); +} + +/* if add_pi2 is true, add pi/2 to the result (used for acos(x) to + avoid cancellation) */ +static int bf_atan_internal(bf_t *r, const bf_t *a, limb_t prec, + void *opaque) +{ + bf_context_t *s = r->ctx; + BOOL add_pi2 = (BOOL)(intptr_t)opaque; + bf_t T_s, *T = &T_s; + bf_t U_s, *U = &U_s; + bf_t V_s, *V = &V_s; + bf_t X2_s, *X2 = &X2_s; + int cmp_1; + slimb_t prec1, i, K, l; + + /* XXX: precision analysis */ + K = bf_isqrt((prec + 1) / 2); + l = prec / (2 * K) + 1; + prec1 = prec + K + 2 * l + 32; + // printf("prec=%d K=%d l=%d prec1=%d\n", (int)prec, (int)K, (int)l, (int)prec1); + + bf_init(s, T); + cmp_1 = (a->expn >= 1); /* a >= 1 */ + if (cmp_1) { + bf_set_ui(T, 1); + bf_div(T, T, a, prec1, BF_RNDN); + } else { + bf_set(T, a); + } + + /* abs(T) <= 1 */ + + /* argument reduction */ + + bf_init(s, U); + bf_init(s, V); + bf_init(s, X2); + for(i = 0; i < K; i++) { + /* T = T / (1 + sqrt(1 + T^2)) */ + bf_mul(U, T, T, prec1, BF_RNDN); + bf_add_si(U, U, 1, prec1, BF_RNDN); + bf_sqrt(V, U, prec1, BF_RNDN); + bf_add_si(V, V, 1, prec1, BF_RNDN); + bf_div(T, T, V, prec1, BF_RNDN); + } + + /* Taylor series: + x - x^3/3 + ... + (-1)^ l * y^(2*l + 1) / (2*l+1) + */ + bf_mul(X2, T, T, prec1, BF_RNDN); + bf_set_ui(r, 0); + for(i = l; i >= 1; i--) { + bf_set_si(U, 1); + bf_set_ui(V, 2 * i + 1); + bf_div(U, U, V, prec1, BF_RNDN); + bf_neg(r); + bf_add(r, r, U, prec1, BF_RNDN); + bf_mul(r, r, X2, prec1, BF_RNDN); + } + bf_neg(r); + bf_add_si(r, r, 1, prec1, BF_RNDN); + bf_mul(r, r, T, prec1, BF_RNDN); + + /* undo the argument reduction */ + bf_mul_2exp(r, K, BF_PREC_INF, BF_RNDZ); + + bf_delete(U); + bf_delete(V); + bf_delete(X2); + + i = add_pi2; + if (cmp_1 > 0) { + /* undo the inversion : r = sign(a)*PI/2 - r */ + bf_neg(r); + i += 1 - 2 * a->sign; + } + /* add i*(pi/2) with -1 <= i <= 2 */ + if (i != 0) { + bf_const_pi(T, prec1, BF_RNDF); + if (i != 2) + bf_mul_2exp(T, -1, BF_PREC_INF, BF_RNDZ); + T->sign = (i < 0); + bf_add(r, T, r, prec1, BF_RNDN); + } + + bf_delete(T); + return BF_ST_INEXACT; +} + +int bf_atan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + int res; + + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF) { + /* -PI/2 or PI/2 */ + bf_const_pi_signed(r, a->sign, prec, flags); + bf_mul_2exp(r, -1, BF_PREC_INF, BF_RNDZ); + return BF_ST_INEXACT; + } else { + bf_set_zero(r, a->sign); + return 0; + } + } + + bf_init(s, T); + bf_set_ui(T, 1); + res = bf_cmpu(a, T); + bf_delete(T); + if (res == 0) { + /* short cut: abs(a) == 1 -> +/-pi/4 */ + bf_const_pi_signed(r, a->sign, prec, flags); + bf_mul_2exp(r, -2, BF_PREC_INF, BF_RNDZ); + return BF_ST_INEXACT; + } + + /* small argument case: result = x+r(x) with r(x) = -x^3/3 + + O(X^5). We assume r(x) < 2^(3*EXP(x) - 1). */ + if (a->expn < 0) { + slimb_t e; + e = sat_add(2 * a->expn, a->expn - 1); + if (e < a->expn - bf_max(prec + 2, a->len * LIMB_BITS + 2)) { + bf_set(r, a); + return bf_add_epsilon(r, r, e, 1 - a->sign, prec, flags); + } + } + + return bf_ziv_rounding(r, a, prec, flags, bf_atan_internal, (void *)FALSE); +} + +static int bf_atan2_internal(bf_t *r, const bf_t *y, limb_t prec, void *opaque) +{ + bf_context_t *s = r->ctx; + const bf_t *x = opaque; + bf_t T_s, *T = &T_s; + limb_t prec1; + int ret; + + if (y->expn == BF_EXP_NAN || x->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } + + /* compute atan(y/x) assumming inf/inf = 1 and 0/0 = 0 */ + bf_init(s, T); + prec1 = prec + 32; + if (y->expn == BF_EXP_INF && x->expn == BF_EXP_INF) { + bf_set_ui(T, 1); + T->sign = y->sign ^ x->sign; + } else if (y->expn == BF_EXP_ZERO && x->expn == BF_EXP_ZERO) { + bf_set_zero(T, y->sign ^ x->sign); + } else { + bf_div(T, y, x, prec1, BF_RNDF); + } + ret = bf_atan(r, T, prec1, BF_RNDF); + + if (x->sign) { + /* if x < 0 (it includes -0), return sign(y)*pi + atan(y/x) */ + bf_const_pi(T, prec1, BF_RNDF); + T->sign = y->sign; + bf_add(r, r, T, prec1, BF_RNDN); + ret |= BF_ST_INEXACT; + } + + bf_delete(T); + return ret; +} + +int bf_atan2(bf_t *r, const bf_t *y, const bf_t *x, + limb_t prec, bf_flags_t flags) +{ + return bf_ziv_rounding(r, y, prec, flags, bf_atan2_internal, (void *)x); +} + +static int bf_asin_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) +{ + bf_context_t *s = r->ctx; + BOOL is_acos = (BOOL)(intptr_t)opaque; + bf_t T_s, *T = &T_s; + limb_t prec1, prec2; + + /* asin(x) = atan(x/sqrt(1-x^2)) + acos(x) = pi/2 - asin(x) */ + prec1 = prec + 8; + /* increase the precision in x^2 to compensate the cancellation in + (1-x^2) if x is close to 1 */ + /* XXX: use less precision when possible */ + if (a->expn >= 0) + prec2 = BF_PREC_INF; + else + prec2 = prec1; + bf_init(s, T); + bf_mul(T, a, a, prec2, BF_RNDN); + bf_neg(T); + bf_add_si(T, T, 1, prec2, BF_RNDN); + + bf_sqrt(r, T, prec1, BF_RNDN); + bf_div(T, a, r, prec1, BF_RNDN); + if (is_acos) + bf_neg(T); + bf_atan_internal(r, T, prec1, (void *)(intptr_t)is_acos); + bf_delete(T); + return BF_ST_INEXACT; +} + +int bf_asin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + int res; + + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_set_zero(r, a->sign); + return 0; + } + } + bf_init(s, T); + bf_set_ui(T, 1); + res = bf_cmpu(a, T); + bf_delete(T); + if (res > 0) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } + + /* small argument case: result = x+r(x) with r(x) = x^3/6 + + O(X^5). We assume r(x) < 2^(3*EXP(x) - 2). */ + if (a->expn < 0) { + slimb_t e; + e = sat_add(2 * a->expn, a->expn - 2); + if (e < a->expn - bf_max(prec + 2, a->len * LIMB_BITS + 2)) { + bf_set(r, a); + return bf_add_epsilon(r, r, e, a->sign, prec, flags); + } + } + + return bf_ziv_rounding(r, a, prec, flags, bf_asin_internal, (void *)FALSE); +} + +int bf_acos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + int res; + + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_const_pi(r, prec, flags); + bf_mul_2exp(r, -1, BF_PREC_INF, BF_RNDZ); + return BF_ST_INEXACT; + } + } + bf_init(s, T); + bf_set_ui(T, 1); + res = bf_cmpu(a, T); + bf_delete(T); + if (res > 0) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else if (res == 0 && a->sign == 0) { + bf_set_zero(r, 0); + return 0; + } + + return bf_ziv_rounding(r, a, prec, flags, bf_asin_internal, (void *)TRUE); +} + +/***************************************************************/ +/* decimal floating point numbers */ + +#ifdef USE_BF_DEC + +#define adddq(r1, r0, a1, a0) \ + do { \ + limb_t __t = r0; \ + r0 += (a0); \ + r1 += (a1) + (r0 < __t); \ + } while (0) + +#define subdq(r1, r0, a1, a0) \ + do { \ + limb_t __t = r0; \ + r0 -= (a0); \ + r1 -= (a1) + (r0 > __t); \ + } while (0) + +#if LIMB_BITS == 64 + +/* Note: we assume __int128 is available */ +#define muldq(r1, r0, a, b) \ + do { \ + unsigned __int128 __t; \ + __t = (unsigned __int128)(a) * (unsigned __int128)(b); \ + r0 = __t; \ + r1 = __t >> 64; \ + } while (0) + +#define divdq(q, r, a1, a0, b) \ + do { \ + unsigned __int128 __t; \ + limb_t __b = (b); \ + __t = ((unsigned __int128)(a1) << 64) | (a0); \ + q = __t / __b; \ + r = __t % __b; \ + } while (0) + +#else + +#define muldq(r1, r0, a, b) \ + do { \ + uint64_t __t; \ + __t = (uint64_t)(a) * (uint64_t)(b); \ + r0 = __t; \ + r1 = __t >> 32; \ + } while (0) + +#define divdq(q, r, a1, a0, b) \ + do { \ + uint64_t __t; \ + limb_t __b = (b); \ + __t = ((uint64_t)(a1) << 32) | (a0); \ + q = __t / __b; \ + r = __t % __b; \ + } while (0) + +#endif /* LIMB_BITS != 64 */ + +#if LIMB_DIGITS == 19 + +/* WARNING: hardcoded for b = 1e19. It is assumed that: + 0 <= a1 < 2^63 */ +#define divdq_base(q, r, a1, a0)\ +do {\ + uint64_t __a0, __a1, __t0, __t1, __b = BF_DEC_BASE; \ + __a0 = a0;\ + __a1 = a1;\ + __t0 = __a1;\ + __t0 = shld(__t0, __a0, 1);\ + muldq(q, __t1, __t0, UINT64_C(17014118346046923173)); \ + muldq(__t1, __t0, q, __b);\ + subdq(__a1, __a0, __t1, __t0);\ + subdq(__a1, __a0, 1, __b * 2); \ + __t0 = (slimb_t)__a1 >> 1; \ + q += 2 + __t0;\ + adddq(__a1, __a0, 0, __b & __t0);\ + q += __a1; \ + __a0 += __b & __a1; \ + r = __a0;\ +} while(0) + +#elif LIMB_DIGITS == 9 + +/* WARNING: hardcoded for b = 1e9. It is assumed that: + 0 <= a1 < 2^29 */ +#define divdq_base(q, r, a1, a0)\ +do {\ + uint32_t __t0, __t1, __b = BF_DEC_BASE; \ + __t0 = a1;\ + __t1 = a0;\ + __t0 = (__t0 << 3) | (__t1 >> (32 - 3)); \ + muldq(q, __t1, __t0, 2305843009U);\ + r = a0 - q * __b;\ + __t1 = (r >= __b);\ + q += __t1;\ + if (__t1)\ + r -= __b;\ +} while(0) + +#endif + +/* fast integer division by a fixed constant */ + +typedef struct FastDivData { + limb_t m1; /* multiplier */ + int8_t shift1; + int8_t shift2; +} FastDivData; + +/* From "Division by Invariant Integers using Multiplication" by + Torborn Granlund and Peter L. Montgomery */ +/* d must be != 0 */ +static inline __maybe_unused void fast_udiv_init(FastDivData *s, limb_t d) +{ + int l; + limb_t q, r, m1; + if (d == 1) + l = 0; + else + l = 64 - clz64(d - 1); + divdq(q, r, ((limb_t)1 << l) - d, 0, d); + (void)r; + m1 = q + 1; + // printf("d=%lu l=%d m1=0x%016lx\n", d, l, m1); + s->m1 = m1; + s->shift1 = l; + if (s->shift1 > 1) + s->shift1 = 1; + s->shift2 = l - 1; + if (s->shift2 < 0) + s->shift2 = 0; +} + +static inline limb_t fast_udiv(limb_t a, const FastDivData *s) +{ + limb_t t0, t1; + muldq(t1, t0, s->m1, a); + t0 = (a - t1) >> s->shift1; + return (t1 + t0) >> s->shift2; +} + +/* contains 10^i */ +const limb_t mp_pow_dec[LIMB_DIGITS + 1] = { + 1U, + 10U, + 100U, + 1000U, + 10000U, + 100000U, + 1000000U, + 10000000U, + 100000000U, + 1000000000U, +#if LIMB_BITS == 64 + 10000000000U, + 100000000000U, + 1000000000000U, + 10000000000000U, + 100000000000000U, + 1000000000000000U, + 10000000000000000U, + 100000000000000000U, + 1000000000000000000U, + 10000000000000000000U, +#endif +}; + +/* precomputed from fast_udiv_init(10^i) */ +static const FastDivData mp_pow_div[LIMB_DIGITS + 1] = { +#if LIMB_BITS == 32 + { 0x00000001, 0, 0 }, + { 0x9999999a, 1, 3 }, + { 0x47ae147b, 1, 6 }, + { 0x0624dd30, 1, 9 }, + { 0xa36e2eb2, 1, 13 }, + { 0x4f8b588f, 1, 16 }, + { 0x0c6f7a0c, 1, 19 }, + { 0xad7f29ac, 1, 23 }, + { 0x5798ee24, 1, 26 }, + { 0x12e0be83, 1, 29 }, +#else + { 0x0000000000000001, 0, 0 }, + { 0x999999999999999a, 1, 3 }, + { 0x47ae147ae147ae15, 1, 6 }, + { 0x0624dd2f1a9fbe77, 1, 9 }, + { 0xa36e2eb1c432ca58, 1, 13 }, + { 0x4f8b588e368f0847, 1, 16 }, + { 0x0c6f7a0b5ed8d36c, 1, 19 }, + { 0xad7f29abcaf48579, 1, 23 }, + { 0x5798ee2308c39dfa, 1, 26 }, + { 0x12e0be826d694b2f, 1, 29 }, + { 0xb7cdfd9d7bdbab7e, 1, 33 }, + { 0x5fd7fe17964955fe, 1, 36 }, + { 0x19799812dea11198, 1, 39 }, + { 0xc25c268497681c27, 1, 43 }, + { 0x6849b86a12b9b01f, 1, 46 }, + { 0x203af9ee756159b3, 1, 49 }, + { 0xcd2b297d889bc2b7, 1, 53 }, + { 0x70ef54646d496893, 1, 56 }, + { 0x2725dd1d243aba0f, 1, 59 }, + { 0xd83c94fb6d2ac34d, 1, 63 }, +#endif +}; + +/* divide by 10^shift with 0 <= shift <= LIMB_DIGITS */ +static inline limb_t fast_shr_dec(limb_t a, int shift) +{ + return fast_udiv(a, &mp_pow_div[shift]); +} + +/* division and remainder by 10^shift */ +#define fast_shr_rem_dec(q, r, a, shift) q = fast_shr_dec(a, shift), r = a - q * mp_pow_dec[shift] + +limb_t mp_add_dec(limb_t *res, const limb_t *op1, const limb_t *op2, + mp_size_t n, limb_t carry) +{ + limb_t base = BF_DEC_BASE; + mp_size_t i; + limb_t k, a, v; + + k=carry; + for(i=0;i v; + if (k) + a += base; + res[i] = a; + } + return k; +} + +limb_t mp_sub_ui_dec(limb_t *tab, limb_t b, mp_size_t n) +{ + limb_t base = BF_DEC_BASE; + mp_size_t i; + limb_t k, v, a; + + k=b; + for(i=0;i v; + if (k) + a += base; + tab[i]=a; + if (k == 0) + break; + } + return k; +} + +/* taba[] = taba[] * b + l. 0 <= b, l <= base - 1. Return the high carry */ +limb_t mp_mul1_dec(limb_t *tabr, const limb_t *taba, mp_size_t n, + limb_t b, limb_t l) +{ + mp_size_t i; + limb_t t0, t1, r; + + for(i = 0; i < n; i++) { + muldq(t1, t0, taba[i], b); + adddq(t1, t0, 0, l); + divdq_base(l, r, t1, t0); + tabr[i] = r; + } + return l; +} + +/* tabr[] += taba[] * b. 0 <= b <= base - 1. Return the value to add + to the high word */ +limb_t mp_add_mul1_dec(limb_t *tabr, const limb_t *taba, mp_size_t n, + limb_t b) +{ + mp_size_t i; + limb_t l, t0, t1, r; + + l = 0; + for(i = 0; i < n; i++) { + muldq(t1, t0, taba[i], b); + adddq(t1, t0, 0, l); + adddq(t1, t0, 0, tabr[i]); + divdq_base(l, r, t1, t0); + tabr[i] = r; + } + return l; +} + +/* tabr[] -= taba[] * b. 0 <= b <= base - 1. Return the value to + substract to the high word. */ +limb_t mp_sub_mul1_dec(limb_t *tabr, const limb_t *taba, mp_size_t n, + limb_t b) +{ + limb_t base = BF_DEC_BASE; + mp_size_t i; + limb_t l, t0, t1, r, a, v, c; + + /* XXX: optimize */ + l = 0; + for(i = 0; i < n; i++) { + muldq(t1, t0, taba[i], b); + adddq(t1, t0, 0, l); + divdq_base(l, r, t1, t0); + v = tabr[i]; + a = v - r; + c = a > v; + if (c) + a += base; + /* never bigger than base because r = 0 when l = base - 1 */ + l += c; + tabr[i] = a; + } + return l; +} + +/* size of the result : op1_size + op2_size. */ +void mp_mul_basecase_dec(limb_t *result, + const limb_t *op1, mp_size_t op1_size, + const limb_t *op2, mp_size_t op2_size) +{ + mp_size_t i; + limb_t r; + + result[op1_size] = mp_mul1_dec(result, op1, op1_size, op2[0], 0); + + for(i=1;i> 1; + if (r) + r = base_div2; + for(i = na - 1; i >= 0; i--) { + t0 = taba[i]; + tabr[i] = (t0 >> 1) + r; + r = 0; + if (t0 & 1) + r = base_div2; + } + if (r) + r = 1; + } else +#endif + if (na >= UDIV1NORM_THRESHOLD) { + shift = clz(b); + if (shift == 0) { + /* normalized case: b >= 2^(LIMB_BITS-1) */ + limb_t b_inv; + b_inv = udiv1norm_init(b); + for(i = na - 1; i >= 0; i--) { + muldq(t1, t0, r, base); + adddq(t1, t0, 0, taba[i]); + q = udiv1norm(&r, t1, t0, b, b_inv); + tabr[i] = q; + } + } else { + limb_t b_inv; + b <<= shift; + b_inv = udiv1norm_init(b); + for(i = na - 1; i >= 0; i--) { + muldq(t1, t0, r, base); + adddq(t1, t0, 0, taba[i]); + t1 = (t1 << shift) | (t0 >> (LIMB_BITS - shift)); + t0 <<= shift; + q = udiv1norm(&r, t1, t0, b, b_inv); + r >>= shift; + tabr[i] = q; + } + } + } else { + for(i = na - 1; i >= 0; i--) { + muldq(t1, t0, r, base); + adddq(t1, t0, 0, taba[i]); + divdq(q, r, t1, t0, b); + tabr[i] = q; + } + } + return r; +} + +static __maybe_unused void mp_print_str_dec(const char *str, + const limb_t *tab, slimb_t n) +{ + slimb_t i; + printf("%s=", str); + for(i = n - 1; i >= 0; i--) { + if (i != n - 1) + printf("_"); + printf("%0*" PRIu_LIMB, LIMB_DIGITS, tab[i]); + } + printf("\n"); +} + +static __maybe_unused void mp_print_str_h_dec(const char *str, + const limb_t *tab, slimb_t n, + limb_t high) +{ + slimb_t i; + printf("%s=", str); + printf("%0*" PRIu_LIMB, LIMB_DIGITS, high); + for(i = n - 1; i >= 0; i--) { + printf("_"); + printf("%0*" PRIu_LIMB, LIMB_DIGITS, tab[i]); + } + printf("\n"); +} + +//#define DEBUG_DIV_SLOW + +#define DIV_STATIC_ALLOC_LEN 16 + +/* return q = a / b and r = a % b. + + taba[na] must be allocated if tabb1[nb - 1] < B / 2. tabb1[nb - 1] + must be != zero. na must be >= nb. 's' can be NULL if tabb1[nb - 1] + >= B / 2. + + The remainder is is returned in taba and contains nb libms. tabq + contains na - nb + 1 limbs. No overlap is permitted. + + Running time of the standard method: (na - nb + 1) * nb + Return 0 if OK, -1 if memory alloc error +*/ +/* XXX: optimize */ +static int mp_div_dec(bf_context_t *s, limb_t *tabq, + limb_t *taba, mp_size_t na, + const limb_t *tabb1, mp_size_t nb) +{ + limb_t base = BF_DEC_BASE; + limb_t r, mult, t0, t1, a, c, q, v, *tabb; + mp_size_t i, j; + limb_t static_tabb[DIV_STATIC_ALLOC_LEN]; + +#ifdef DEBUG_DIV_SLOW + mp_print_str_dec("a", taba, na); + mp_print_str_dec("b", tabb1, nb); +#endif + + /* normalize tabb */ + r = tabb1[nb - 1]; + assert(r != 0); + i = na - nb; + if (r >= BF_DEC_BASE / 2) { + mult = 1; + tabb = (limb_t *)tabb1; + q = 1; + for(j = nb - 1; j >= 0; j--) { + if (taba[i + j] != tabb[j]) { + if (taba[i + j] < tabb[j]) + q = 0; + break; + } + } + tabq[i] = q; + if (q) { + mp_sub_dec(taba + i, taba + i, tabb, nb, 0); + } + i--; + } else { + mult = base / (r + 1); + if (likely(nb <= DIV_STATIC_ALLOC_LEN)) { + tabb = static_tabb; + } else { + tabb = bf_malloc(s, sizeof(limb_t) * nb); + if (!tabb) + return -1; + } + mp_mul1_dec(tabb, tabb1, nb, mult, 0); + taba[na] = mp_mul1_dec(taba, taba, na, mult, 0); + } + +#ifdef DEBUG_DIV_SLOW + printf("mult=" FMT_LIMB "\n", mult); + mp_print_str_dec("a_norm", taba, na + 1); + mp_print_str_dec("b_norm", tabb, nb); +#endif + + for(; i >= 0; i--) { + if (unlikely(taba[i + nb] >= tabb[nb - 1])) { + /* XXX: check if it is really possible */ + q = base - 1; + } else { + muldq(t1, t0, taba[i + nb], base); + adddq(t1, t0, 0, taba[i + nb - 1]); + divdq(q, r, t1, t0, tabb[nb - 1]); + } + // printf("i=%d q1=%ld\n", i, q); + + r = mp_sub_mul1_dec(taba + i, tabb, nb, q); + // mp_dump("r1", taba + i, nb, bd); + // printf("r2=%ld\n", r); + + v = taba[i + nb]; + a = v - r; + c = a > v; + if (c) + a += base; + taba[i + nb] = a; + + if (c != 0) { + /* negative result */ + for(;;) { + q--; + c = mp_add_dec(taba + i, taba + i, tabb, nb, 0); + /* propagate carry and test if positive result */ + if (c != 0) { + if (++taba[i + nb] == base) { + break; + } + } + } + } + tabq[i] = q; + } + +#ifdef DEBUG_DIV_SLOW + mp_print_str_dec("q", tabq, na - nb + 1); + mp_print_str_dec("r", taba, nb); +#endif + + /* remove the normalization */ + if (mult != 1) { + mp_div1_dec(taba, taba, nb, mult, 0); + if (unlikely(tabb != static_tabb)) + bf_free(s, tabb); + } + return 0; +} + +/* divide by 10^shift */ +static limb_t mp_shr_dec(limb_t *tab_r, const limb_t *tab, mp_size_t n, + limb_t shift, limb_t high) +{ + mp_size_t i; + limb_t l, a, q, r; + + assert(shift >= 1 && shift < LIMB_DIGITS); + l = high; + for(i = n - 1; i >= 0; i--) { + a = tab[i]; + fast_shr_rem_dec(q, r, a, shift); + tab_r[i] = q + l * mp_pow_dec[LIMB_DIGITS - shift]; + l = r; + } + return l; +} + +/* multiply by 10^shift */ +static limb_t mp_shl_dec(limb_t *tab_r, const limb_t *tab, mp_size_t n, + limb_t shift, limb_t low) +{ + mp_size_t i; + limb_t l, a, q, r; + + assert(shift >= 1 && shift < LIMB_DIGITS); + l = low; + for(i = 0; i < n; i++) { + a = tab[i]; + fast_shr_rem_dec(q, r, a, LIMB_DIGITS - shift); + tab_r[i] = r * mp_pow_dec[shift] + l; + l = q; + } + return l; +} + +static limb_t mp_sqrtrem2_dec(limb_t *tabs, limb_t *taba) +{ + int k; + dlimb_t a, b, r; + limb_t taba1[2], s, r0, r1; + + /* convert to binary and normalize */ + a = (dlimb_t)taba[1] * BF_DEC_BASE + taba[0]; + k = clz(a >> LIMB_BITS) & ~1; + b = a << k; + taba1[0] = b; + taba1[1] = b >> LIMB_BITS; + mp_sqrtrem2(&s, taba1); + s >>= (k >> 1); + /* convert the remainder back to decimal */ + r = a - (dlimb_t)s * (dlimb_t)s; + divdq_base(r1, r0, r >> LIMB_BITS, r); + taba[0] = r0; + tabs[0] = s; + return r1; +} + +//#define DEBUG_SQRTREM_DEC + +/* tmp_buf must contain (n / 2 + 1 limbs) */ +static limb_t mp_sqrtrem_rec_dec(limb_t *tabs, limb_t *taba, limb_t n, + limb_t *tmp_buf) +{ + limb_t l, h, rh, ql, qh, c, i; + + if (n == 1) + return mp_sqrtrem2_dec(tabs, taba); +#ifdef DEBUG_SQRTREM_DEC + mp_print_str_dec("a", taba, 2 * n); +#endif + l = n / 2; + h = n - l; + qh = mp_sqrtrem_rec_dec(tabs + l, taba + 2 * l, h, tmp_buf); +#ifdef DEBUG_SQRTREM_DEC + mp_print_str_dec("s1", tabs + l, h); + mp_print_str_h_dec("r1", taba + 2 * l, h, qh); + mp_print_str_h_dec("r2", taba + l, n, qh); +#endif + + /* the remainder is in taba + 2 * l. Its high bit is in qh */ + if (qh) { + mp_sub_dec(taba + 2 * l, taba + 2 * l, tabs + l, h, 0); + } + /* instead of dividing by 2*s, divide by s (which is normalized) + and update q and r */ + mp_div_dec(NULL, tmp_buf, taba + l, n, tabs + l, h); + qh += tmp_buf[l]; + for(i = 0; i < l; i++) + tabs[i] = tmp_buf[i]; + ql = mp_div1_dec(tabs, tabs, l, 2, qh & 1); + qh = qh >> 1; /* 0 or 1 */ + if (ql) + rh = mp_add_dec(taba + l, taba + l, tabs + l, h, 0); + else + rh = 0; +#ifdef DEBUG_SQRTREM_DEC + mp_print_str_h_dec("q", tabs, l, qh); + mp_print_str_h_dec("u", taba + l, h, rh); +#endif + + mp_add_ui_dec(tabs + l, qh, h); +#ifdef DEBUG_SQRTREM_DEC + mp_print_str_dec("s2", tabs, n); +#endif + + /* q = qh, tabs[l - 1 ... 0], r = taba[n - 1 ... l] */ + /* subtract q^2. if qh = 1 then q = B^l, so we can take shortcuts */ + if (qh) { + c = qh; + } else { + mp_mul_basecase_dec(taba + n, tabs, l, tabs, l); + c = mp_sub_dec(taba, taba, taba + n, 2 * l, 0); + } + rh -= mp_sub_ui_dec(taba + 2 * l, c, n - 2 * l); + if ((slimb_t)rh < 0) { + mp_sub_ui_dec(tabs, 1, n); + rh += mp_add_mul1_dec(taba, tabs, n, 2); + rh += mp_add_ui_dec(taba, 1, n); + } + return rh; +} + +/* 'taba' has 2*n limbs with n >= 1 and taba[2*n-1] >= B/4. Return (s, + r) with s=floor(sqrt(a)) and r=a-s^2. 0 <= r <= 2 * s. tabs has n + limbs. r is returned in the lower n limbs of taba. Its r[n] is the + returned value of the function. */ +int mp_sqrtrem_dec(bf_context_t *s, limb_t *tabs, limb_t *taba, limb_t n) +{ + limb_t tmp_buf1[8]; + limb_t *tmp_buf; + mp_size_t n2; + n2 = n / 2 + 1; + if (n2 <= countof(tmp_buf1)) { + tmp_buf = tmp_buf1; + } else { + tmp_buf = bf_malloc(s, sizeof(limb_t) * n2); + if (!tmp_buf) + return -1; + } + taba[n] = mp_sqrtrem_rec_dec(tabs, taba, n, tmp_buf); + if (tmp_buf != tmp_buf1) + bf_free(s, tmp_buf); + return 0; +} + +/* return the number of leading zero digits, from 0 to LIMB_DIGITS */ +static int clz_dec(limb_t a) +{ + if (a == 0) + return LIMB_DIGITS; + switch(LIMB_BITS - 1 - clz(a)) { + case 0: /* 1-1 */ + return LIMB_DIGITS - 1; + case 1: /* 2-3 */ + return LIMB_DIGITS - 1; + case 2: /* 4-7 */ + return LIMB_DIGITS - 1; + case 3: /* 8-15 */ + if (a < 10) + return LIMB_DIGITS - 1; + else + return LIMB_DIGITS - 2; + case 4: /* 16-31 */ + return LIMB_DIGITS - 2; + case 5: /* 32-63 */ + return LIMB_DIGITS - 2; + case 6: /* 64-127 */ + if (a < 100) + return LIMB_DIGITS - 2; + else + return LIMB_DIGITS - 3; + case 7: /* 128-255 */ + return LIMB_DIGITS - 3; + case 8: /* 256-511 */ + return LIMB_DIGITS - 3; + case 9: /* 512-1023 */ + if (a < 1000) + return LIMB_DIGITS - 3; + else + return LIMB_DIGITS - 4; + case 10: /* 1024-2047 */ + return LIMB_DIGITS - 4; + case 11: /* 2048-4095 */ + return LIMB_DIGITS - 4; + case 12: /* 4096-8191 */ + return LIMB_DIGITS - 4; + case 13: /* 8192-16383 */ + if (a < 10000) + return LIMB_DIGITS - 4; + else + return LIMB_DIGITS - 5; + case 14: /* 16384-32767 */ + return LIMB_DIGITS - 5; + case 15: /* 32768-65535 */ + return LIMB_DIGITS - 5; + case 16: /* 65536-131071 */ + if (a < 100000) + return LIMB_DIGITS - 5; + else + return LIMB_DIGITS - 6; + case 17: /* 131072-262143 */ + return LIMB_DIGITS - 6; + case 18: /* 262144-524287 */ + return LIMB_DIGITS - 6; + case 19: /* 524288-1048575 */ + if (a < 1000000) + return LIMB_DIGITS - 6; + else + return LIMB_DIGITS - 7; + case 20: /* 1048576-2097151 */ + return LIMB_DIGITS - 7; + case 21: /* 2097152-4194303 */ + return LIMB_DIGITS - 7; + case 22: /* 4194304-8388607 */ + return LIMB_DIGITS - 7; + case 23: /* 8388608-16777215 */ + if (a < 10000000) + return LIMB_DIGITS - 7; + else + return LIMB_DIGITS - 8; + case 24: /* 16777216-33554431 */ + return LIMB_DIGITS - 8; + case 25: /* 33554432-67108863 */ + return LIMB_DIGITS - 8; + case 26: /* 67108864-134217727 */ + if (a < 100000000) + return LIMB_DIGITS - 8; + else + return LIMB_DIGITS - 9; +#if LIMB_BITS == 64 + case 27: /* 134217728-268435455 */ + return LIMB_DIGITS - 9; + case 28: /* 268435456-536870911 */ + return LIMB_DIGITS - 9; + case 29: /* 536870912-1073741823 */ + if (a < 1000000000) + return LIMB_DIGITS - 9; + else + return LIMB_DIGITS - 10; + case 30: /* 1073741824-2147483647 */ + return LIMB_DIGITS - 10; + case 31: /* 2147483648-4294967295 */ + return LIMB_DIGITS - 10; + case 32: /* 4294967296-8589934591 */ + return LIMB_DIGITS - 10; + case 33: /* 8589934592-17179869183 */ + if (a < 10000000000) + return LIMB_DIGITS - 10; + else + return LIMB_DIGITS - 11; + case 34: /* 17179869184-34359738367 */ + return LIMB_DIGITS - 11; + case 35: /* 34359738368-68719476735 */ + return LIMB_DIGITS - 11; + case 36: /* 68719476736-137438953471 */ + if (a < 100000000000) + return LIMB_DIGITS - 11; + else + return LIMB_DIGITS - 12; + case 37: /* 137438953472-274877906943 */ + return LIMB_DIGITS - 12; + case 38: /* 274877906944-549755813887 */ + return LIMB_DIGITS - 12; + case 39: /* 549755813888-1099511627775 */ + if (a < 1000000000000) + return LIMB_DIGITS - 12; + else + return LIMB_DIGITS - 13; + case 40: /* 1099511627776-2199023255551 */ + return LIMB_DIGITS - 13; + case 41: /* 2199023255552-4398046511103 */ + return LIMB_DIGITS - 13; + case 42: /* 4398046511104-8796093022207 */ + return LIMB_DIGITS - 13; + case 43: /* 8796093022208-17592186044415 */ + if (a < 10000000000000) + return LIMB_DIGITS - 13; + else + return LIMB_DIGITS - 14; + case 44: /* 17592186044416-35184372088831 */ + return LIMB_DIGITS - 14; + case 45: /* 35184372088832-70368744177663 */ + return LIMB_DIGITS - 14; + case 46: /* 70368744177664-140737488355327 */ + if (a < 100000000000000) + return LIMB_DIGITS - 14; + else + return LIMB_DIGITS - 15; + case 47: /* 140737488355328-281474976710655 */ + return LIMB_DIGITS - 15; + case 48: /* 281474976710656-562949953421311 */ + return LIMB_DIGITS - 15; + case 49: /* 562949953421312-1125899906842623 */ + if (a < 1000000000000000) + return LIMB_DIGITS - 15; + else + return LIMB_DIGITS - 16; + case 50: /* 1125899906842624-2251799813685247 */ + return LIMB_DIGITS - 16; + case 51: /* 2251799813685248-4503599627370495 */ + return LIMB_DIGITS - 16; + case 52: /* 4503599627370496-9007199254740991 */ + return LIMB_DIGITS - 16; + case 53: /* 9007199254740992-18014398509481983 */ + if (a < 10000000000000000) + return LIMB_DIGITS - 16; + else + return LIMB_DIGITS - 17; + case 54: /* 18014398509481984-36028797018963967 */ + return LIMB_DIGITS - 17; + case 55: /* 36028797018963968-72057594037927935 */ + return LIMB_DIGITS - 17; + case 56: /* 72057594037927936-144115188075855871 */ + if (a < 100000000000000000) + return LIMB_DIGITS - 17; + else + return LIMB_DIGITS - 18; + case 57: /* 144115188075855872-288230376151711743 */ + return LIMB_DIGITS - 18; + case 58: /* 288230376151711744-576460752303423487 */ + return LIMB_DIGITS - 18; + case 59: /* 576460752303423488-1152921504606846975 */ + if (a < 1000000000000000000) + return LIMB_DIGITS - 18; + else + return LIMB_DIGITS - 19; +#endif + default: + return 0; + } +} + +/* for debugging */ +void bfdec_print_str(const char *str, const bfdec_t *a) +{ + slimb_t i; + printf("%s=", str); + + if (a->expn == BF_EXP_NAN) { + printf("NaN"); + } else { + if (a->sign) + putchar('-'); + if (a->expn == BF_EXP_ZERO) { + putchar('0'); + } else if (a->expn == BF_EXP_INF) { + printf("Inf"); + } else { + printf("0."); + for(i = a->len - 1; i >= 0; i--) + printf("%0*" PRIu_LIMB, LIMB_DIGITS, a->tab[i]); + printf("e%" PRId_LIMB, a->expn); + } + } + printf("\n"); +} + +/* return != 0 if one digit between 0 and bit_pos inclusive is not zero. */ +static inline limb_t scan_digit_nz(const bfdec_t *r, slimb_t bit_pos) +{ + slimb_t pos; + limb_t v, q; + int shift; + + if (bit_pos < 0) + return 0; + pos = (limb_t)bit_pos / LIMB_DIGITS; + shift = (limb_t)bit_pos % LIMB_DIGITS; + fast_shr_rem_dec(q, v, r->tab[pos], shift + 1); + (void)q; + if (v != 0) + return 1; + pos--; + while (pos >= 0) { + if (r->tab[pos] != 0) + return 1; + pos--; + } + return 0; +} + +static limb_t get_digit(const limb_t *tab, limb_t len, slimb_t pos) +{ + slimb_t i; + int shift; + i = floor_div(pos, LIMB_DIGITS); + if (i < 0 || i >= len) + return 0; + shift = pos - i * LIMB_DIGITS; + return fast_shr_dec(tab[i], shift) % 10; +} + +#if 0 +static limb_t get_digits(const limb_t *tab, limb_t len, slimb_t pos) +{ + limb_t a0, a1; + int shift; + slimb_t i; + + i = floor_div(pos, LIMB_DIGITS); + shift = pos - i * LIMB_DIGITS; + if (i >= 0 && i < len) + a0 = tab[i]; + else + a0 = 0; + if (shift == 0) { + return a0; + } else { + i++; + if (i >= 0 && i < len) + a1 = tab[i]; + else + a1 = 0; + return fast_shr_dec(a0, shift) + + fast_urem(a1, &mp_pow_div[LIMB_DIGITS - shift]) * + mp_pow_dec[shift]; + } +} +#endif + +/* return the addend for rounding. Note that prec can be <= 0 for bf_rint() */ +static int bfdec_get_rnd_add(int *pret, const bfdec_t *r, limb_t l, + slimb_t prec, int rnd_mode) +{ + int add_one, inexact; + limb_t digit1, digit0; + + // bfdec_print_str("get_rnd_add", r); + if (rnd_mode == BF_RNDF) { + digit0 = 1; /* faithful rounding does not honor the INEXACT flag */ + } else { + /* starting limb for bit 'prec + 1' */ + digit0 = scan_digit_nz(r, l * LIMB_DIGITS - 1 - bf_max(0, prec + 1)); + } + + /* get the digit at 'prec' */ + digit1 = get_digit(r->tab, l, l * LIMB_DIGITS - 1 - prec); + inexact = (digit1 | digit0) != 0; + + add_one = 0; + switch(rnd_mode) { + case BF_RNDZ: + break; + case BF_RNDN: + if (digit1 == 5) { + if (digit0) { + add_one = 1; + } else { + /* round to even */ + add_one = + get_digit(r->tab, l, l * LIMB_DIGITS - 1 - (prec - 1)) & 1; + } + } else if (digit1 > 5) { + add_one = 1; + } + break; + case BF_RNDD: + case BF_RNDU: + if (r->sign == (rnd_mode == BF_RNDD)) + add_one = inexact; + break; + case BF_RNDNA: + case BF_RNDF: + add_one = (digit1 >= 5); + break; + case BF_RNDA: + add_one = inexact; + break; + default: + abort(); + } + + if (inexact) + *pret |= BF_ST_INEXACT; + return add_one; +} + +/* round to prec1 bits assuming 'r' is non zero and finite. 'r' is + assumed to have length 'l' (1 <= l <= r->len). prec1 can be + BF_PREC_INF. BF_FLAG_SUBNORMAL is not supported. Cannot fail with + BF_ST_MEM_ERROR. + */ +static int __bfdec_round(bfdec_t *r, limb_t prec1, bf_flags_t flags, limb_t l) +{ + int shift, add_one, rnd_mode, ret; + slimb_t i, bit_pos, pos, e_min, e_max, e_range, prec; + + /* XXX: align to IEEE 754 2008 for decimal numbers ? */ + e_range = (limb_t)1 << (bf_get_exp_bits(flags) - 1); + e_min = -e_range + 3; + e_max = e_range; + + if (flags & BF_FLAG_RADPNT_PREC) { + /* 'prec' is the precision after the decimal point */ + if (prec1 != BF_PREC_INF) + prec = r->expn + prec1; + else + prec = prec1; + } else if (unlikely(r->expn < e_min) && (flags & BF_FLAG_SUBNORMAL)) { + /* restrict the precision in case of potentially subnormal + result */ + assert(prec1 != BF_PREC_INF); + prec = prec1 - (e_min - r->expn); + } else { + prec = prec1; + } + + /* round to prec bits */ + rnd_mode = flags & BF_RND_MASK; + ret = 0; + add_one = bfdec_get_rnd_add(&ret, r, l, prec, rnd_mode); + + if (prec <= 0) { + if (add_one) { + bfdec_resize(r, 1); /* cannot fail because r is non zero */ + r->tab[0] = BF_DEC_BASE / 10; + r->expn += 1 - prec; + ret |= BF_ST_UNDERFLOW | BF_ST_INEXACT; + return ret; + } else { + goto underflow; + } + } else if (add_one) { + limb_t carry; + + /* add one starting at digit 'prec - 1' */ + bit_pos = l * LIMB_DIGITS - 1 - (prec - 1); + pos = bit_pos / LIMB_DIGITS; + carry = mp_pow_dec[bit_pos % LIMB_DIGITS]; + carry = mp_add_ui_dec(r->tab + pos, carry, l - pos); + if (carry) { + /* shift right by one digit */ + mp_shr_dec(r->tab + pos, r->tab + pos, l - pos, 1, 1); + r->expn++; + } + } + + /* check underflow */ + if (unlikely(r->expn < e_min)) { + if (flags & BF_FLAG_SUBNORMAL) { + /* if inexact, also set the underflow flag */ + if (ret & BF_ST_INEXACT) + ret |= BF_ST_UNDERFLOW; + } else { + underflow: + bfdec_set_zero(r, r->sign); + ret |= BF_ST_UNDERFLOW | BF_ST_INEXACT; + return ret; + } + } + + /* check overflow */ + if (unlikely(r->expn > e_max)) { + bfdec_set_inf(r, r->sign); + ret |= BF_ST_OVERFLOW | BF_ST_INEXACT; + return ret; + } + + /* keep the bits starting at 'prec - 1' */ + bit_pos = l * LIMB_DIGITS - 1 - (prec - 1); + i = floor_div(bit_pos, LIMB_DIGITS); + if (i >= 0) { + shift = smod(bit_pos, LIMB_DIGITS); + if (shift != 0) { + r->tab[i] = fast_shr_dec(r->tab[i], shift) * + mp_pow_dec[shift]; + } + } else { + i = 0; + } + /* remove trailing zeros */ + while (r->tab[i] == 0) + i++; + if (i > 0) { + l -= i; + memmove(r->tab, r->tab + i, l * sizeof(limb_t)); + } + bfdec_resize(r, l); /* cannot fail */ + return ret; +} + +/* Cannot fail with BF_ST_MEM_ERROR. */ +int bfdec_round(bfdec_t *r, limb_t prec, bf_flags_t flags) +{ + if (r->len == 0) + return 0; + return __bfdec_round(r, prec, flags, r->len); +} + +/* 'r' must be a finite number. Cannot fail with BF_ST_MEM_ERROR. */ +int bfdec_normalize_and_round(bfdec_t *r, limb_t prec1, bf_flags_t flags) +{ + limb_t l, v; + int shift, ret; + + // bfdec_print_str("bf_renorm", r); + l = r->len; + while (l > 0 && r->tab[l - 1] == 0) + l--; + if (l == 0) { + /* zero */ + r->expn = BF_EXP_ZERO; + bfdec_resize(r, 0); /* cannot fail */ + ret = 0; + } else { + r->expn -= (r->len - l) * LIMB_DIGITS; + /* shift to have the MSB set to '1' */ + v = r->tab[l - 1]; + shift = clz_dec(v); + if (shift != 0) { + mp_shl_dec(r->tab, r->tab, l, shift, 0); + r->expn -= shift; + } + ret = __bfdec_round(r, prec1, flags, l); + } + // bf_print_str("r_final", r); + return ret; +} + +int bfdec_set_ui(bfdec_t *r, uint64_t v) +{ +#if LIMB_BITS == 32 + if (v >= BF_DEC_BASE * BF_DEC_BASE) { + if (bfdec_resize(r, 3)) + goto fail; + r->tab[0] = v % BF_DEC_BASE; + v /= BF_DEC_BASE; + r->tab[1] = v % BF_DEC_BASE; + r->tab[2] = v / BF_DEC_BASE; + r->expn = 3 * LIMB_DIGITS; + } else +#endif + if (v >= BF_DEC_BASE) { + if (bfdec_resize(r, 2)) + goto fail; + r->tab[0] = v % BF_DEC_BASE; + r->tab[1] = v / BF_DEC_BASE; + r->expn = 2 * LIMB_DIGITS; + } else { + if (bfdec_resize(r, 1)) + goto fail; + r->tab[0] = v; + r->expn = LIMB_DIGITS; + } + r->sign = 0; + return bfdec_normalize_and_round(r, BF_PREC_INF, 0); + fail: + bfdec_set_nan(r); + return BF_ST_MEM_ERROR; +} + +int bfdec_set_si(bfdec_t *r, int64_t v) +{ + int ret; + if (v < 0) { + ret = bfdec_set_ui(r, -v); + r->sign = 1; + } else { + ret = bfdec_set_ui(r, v); + } + return ret; +} + +static int bfdec_add_internal(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, bf_flags_t flags, int b_neg) +{ + bf_context_t *s = r->ctx; + int is_sub, cmp_res, a_sign, b_sign, ret; + + a_sign = a->sign; + b_sign = b->sign ^ b_neg; + is_sub = a_sign ^ b_sign; + cmp_res = bfdec_cmpu(a, b); + if (cmp_res < 0) { + const bfdec_t *tmp; + tmp = a; + a = b; + b = tmp; + a_sign = b_sign; /* b_sign is never used later */ + } + /* abs(a) >= abs(b) */ + if (cmp_res == 0 && is_sub && a->expn < BF_EXP_INF) { + /* zero result */ + bfdec_set_zero(r, (flags & BF_RND_MASK) == BF_RNDD); + ret = 0; + } else if (a->len == 0 || b->len == 0) { + ret = 0; + if (a->expn >= BF_EXP_INF) { + if (a->expn == BF_EXP_NAN) { + /* at least one operand is NaN */ + bfdec_set_nan(r); + ret = 0; + } else if (b->expn == BF_EXP_INF && is_sub) { + /* infinities with different signs */ + bfdec_set_nan(r); + ret = BF_ST_INVALID_OP; + } else { + bfdec_set_inf(r, a_sign); + } + } else { + /* at least one zero and not subtract */ + if (bfdec_set(r, a)) + return BF_ST_MEM_ERROR; + r->sign = a_sign; + goto renorm; + } + } else { + slimb_t d, a_offset, b_offset, i, r_len; + limb_t carry; + limb_t *b1_tab; + int b_shift; + mp_size_t b1_len; + + d = a->expn - b->expn; + + /* XXX: not efficient in time and memory if the precision is + not infinite */ + r_len = bf_max(a->len, b->len + (d + LIMB_DIGITS - 1) / LIMB_DIGITS); + if (bfdec_resize(r, r_len)) + goto fail; + r->sign = a_sign; + r->expn = a->expn; + + a_offset = r_len - a->len; + for(i = 0; i < a_offset; i++) + r->tab[i] = 0; + for(i = 0; i < a->len; i++) + r->tab[a_offset + i] = a->tab[i]; + + b_shift = d % LIMB_DIGITS; + if (b_shift == 0) { + b1_len = b->len; + b1_tab = (limb_t *)b->tab; + } else { + b1_len = b->len + 1; + b1_tab = bf_malloc(s, sizeof(limb_t) * b1_len); + if (!b1_tab) + goto fail; + b1_tab[0] = mp_shr_dec(b1_tab + 1, b->tab, b->len, b_shift, 0) * + mp_pow_dec[LIMB_DIGITS - b_shift]; + } + b_offset = r_len - (b->len + (d + LIMB_DIGITS - 1) / LIMB_DIGITS); + + if (is_sub) { + carry = mp_sub_dec(r->tab + b_offset, r->tab + b_offset, + b1_tab, b1_len, 0); + if (carry != 0) { + carry = mp_sub_ui_dec(r->tab + b_offset + b1_len, carry, + r_len - (b_offset + b1_len)); + assert(carry == 0); + } + } else { + carry = mp_add_dec(r->tab + b_offset, r->tab + b_offset, + b1_tab, b1_len, 0); + if (carry != 0) { + carry = mp_add_ui_dec(r->tab + b_offset + b1_len, carry, + r_len - (b_offset + b1_len)); + } + if (carry != 0) { + if (bfdec_resize(r, r_len + 1)) { + if (b_shift != 0) + bf_free(s, b1_tab); + goto fail; + } + r->tab[r_len] = 1; + r->expn += LIMB_DIGITS; + } + } + if (b_shift != 0) + bf_free(s, b1_tab); + renorm: + ret = bfdec_normalize_and_round(r, prec, flags); + } + return ret; + fail: + bfdec_set_nan(r); + return BF_ST_MEM_ERROR; +} + +static int __bfdec_add(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags) +{ + return bfdec_add_internal(r, a, b, prec, flags, 0); +} + +static int __bfdec_sub(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags) +{ + return bfdec_add_internal(r, a, b, prec, flags, 1); +} + +int bfdec_add(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_op2((bf_t *)r, (bf_t *)a, (bf_t *)b, prec, flags, + (bf_op2_func_t *)__bfdec_add); +} + +int bfdec_sub(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_op2((bf_t *)r, (bf_t *)a, (bf_t *)b, prec, flags, + (bf_op2_func_t *)__bfdec_sub); +} + +int bfdec_mul(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags) +{ + int ret, r_sign; + + if (a->len < b->len) { + const bfdec_t *tmp = a; + a = b; + b = tmp; + } + r_sign = a->sign ^ b->sign; + /* here b->len <= a->len */ + if (b->len == 0) { + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + bfdec_set_nan(r); + ret = 0; + } else if (a->expn == BF_EXP_INF || b->expn == BF_EXP_INF) { + if ((a->expn == BF_EXP_INF && b->expn == BF_EXP_ZERO) || + (a->expn == BF_EXP_ZERO && b->expn == BF_EXP_INF)) { + bfdec_set_nan(r); + ret = BF_ST_INVALID_OP; + } else { + bfdec_set_inf(r, r_sign); + ret = 0; + } + } else { + bfdec_set_zero(r, r_sign); + ret = 0; + } + } else { + bfdec_t tmp, *r1 = NULL; + limb_t a_len, b_len; + limb_t *a_tab, *b_tab; + + a_len = a->len; + b_len = b->len; + a_tab = a->tab; + b_tab = b->tab; + + if (r == a || r == b) { + bfdec_init(r->ctx, &tmp); + r1 = r; + r = &tmp; + } + if (bfdec_resize(r, a_len + b_len)) { + bfdec_set_nan(r); + ret = BF_ST_MEM_ERROR; + goto done; + } + mp_mul_basecase_dec(r->tab, a_tab, a_len, b_tab, b_len); + r->sign = r_sign; + r->expn = a->expn + b->expn; + ret = bfdec_normalize_and_round(r, prec, flags); + done: + if (r == &tmp) + bfdec_move(r1, &tmp); + } + return ret; +} + +int bfdec_mul_si(bfdec_t *r, const bfdec_t *a, int64_t b1, limb_t prec, + bf_flags_t flags) +{ + bfdec_t b; + int ret; + bfdec_init(r->ctx, &b); + ret = bfdec_set_si(&b, b1); + ret |= bfdec_mul(r, a, &b, prec, flags); + bfdec_delete(&b); + return ret; +} + +int bfdec_add_si(bfdec_t *r, const bfdec_t *a, int64_t b1, limb_t prec, + bf_flags_t flags) +{ + bfdec_t b; + int ret; + + bfdec_init(r->ctx, &b); + ret = bfdec_set_si(&b, b1); + ret |= bfdec_add(r, a, &b, prec, flags); + bfdec_delete(&b); + return ret; +} + +static int __bfdec_div(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, + limb_t prec, bf_flags_t flags) +{ + int ret, r_sign; + limb_t n, nb, precl; + + r_sign = a->sign ^ b->sign; + if (a->expn >= BF_EXP_INF || b->expn >= BF_EXP_INF) { + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + bfdec_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF && b->expn == BF_EXP_INF) { + bfdec_set_nan(r); + return BF_ST_INVALID_OP; + } else if (a->expn == BF_EXP_INF) { + bfdec_set_inf(r, r_sign); + return 0; + } else { + bfdec_set_zero(r, r_sign); + return 0; + } + } else if (a->expn == BF_EXP_ZERO) { + if (b->expn == BF_EXP_ZERO) { + bfdec_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bfdec_set_zero(r, r_sign); + return 0; + } + } else if (b->expn == BF_EXP_ZERO) { + bfdec_set_inf(r, r_sign); + return BF_ST_DIVIDE_ZERO; + } + + nb = b->len; + if (prec == BF_PREC_INF) { + /* infinite precision: return BF_ST_INVALID_OP if not an exact + result */ + /* XXX: check */ + precl = nb + 1; + } else if (flags & BF_FLAG_RADPNT_PREC) { + /* number of digits after the decimal point */ + /* XXX: check (2 extra digits for rounding + 2 digits) */ + precl = (bf_max(a->expn - b->expn, 0) + 2 + + prec + 2 + LIMB_DIGITS - 1) / LIMB_DIGITS; + } else { + /* number of limbs of the quotient (2 extra digits for rounding) */ + precl = (prec + 2 + LIMB_DIGITS - 1) / LIMB_DIGITS; + } + n = bf_max(a->len, precl); + + { + limb_t *taba, na, i; + slimb_t d; + + na = n + nb; + taba = bf_malloc(r->ctx, (na + 1) * sizeof(limb_t)); + if (!taba) + goto fail; + d = na - a->len; + memset(taba, 0, d * sizeof(limb_t)); + memcpy(taba + d, a->tab, a->len * sizeof(limb_t)); + if (bfdec_resize(r, n + 1)) + goto fail1; + if (mp_div_dec(r->ctx, r->tab, taba, na, b->tab, nb)) { + fail1: + bf_free(r->ctx, taba); + goto fail; + } + /* see if non zero remainder */ + for(i = 0; i < nb; i++) { + if (taba[i] != 0) + break; + } + bf_free(r->ctx, taba); + if (i != nb) { + if (prec == BF_PREC_INF) { + bfdec_set_nan(r); + return BF_ST_INVALID_OP; + } else { + r->tab[0] |= 1; + } + } + r->expn = a->expn - b->expn + LIMB_DIGITS; + r->sign = r_sign; + ret = bfdec_normalize_and_round(r, prec, flags); + } + return ret; + fail: + bfdec_set_nan(r); + return BF_ST_MEM_ERROR; +} + +int bfdec_div(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_op2((bf_t *)r, (bf_t *)a, (bf_t *)b, prec, flags, + (bf_op2_func_t *)__bfdec_div); +} + +/* a and b must be finite numbers with a >= 0 and b > 0. 'q' is the + integer defined as floor(a/b) and r = a - q * b. */ +static void bfdec_tdivremu(bf_context_t *s, bfdec_t *q, bfdec_t *r, + const bfdec_t *a, const bfdec_t *b) +{ + if (bfdec_cmpu(a, b) < 0) { + bfdec_set_ui(q, 0); + bfdec_set(r, a); + } else { + bfdec_div(q, a, b, 0, BF_RNDZ | BF_FLAG_RADPNT_PREC); + bfdec_mul(r, q, b, BF_PREC_INF, BF_RNDZ); + bfdec_sub(r, a, r, BF_PREC_INF, BF_RNDZ); + } +} + +/* division and remainder. + + rnd_mode is the rounding mode for the quotient. The additional + rounding mode BF_RND_EUCLIDIAN is supported. + + 'q' is an integer. 'r' is rounded with prec and flags (prec can be + BF_PREC_INF). +*/ +int bfdec_divrem(bfdec_t *q, bfdec_t *r, const bfdec_t *a, const bfdec_t *b, + limb_t prec, bf_flags_t flags, int rnd_mode) +{ + bf_context_t *s = q->ctx; + bfdec_t a1_s, *a1 = &a1_s; + bfdec_t b1_s, *b1 = &b1_s; + bfdec_t r1_s, *r1 = &r1_s; + int q_sign, res; + BOOL is_ceil, is_rndn; + + assert(q != a && q != b); + assert(r != a && r != b); + assert(q != r); + + if (a->len == 0 || b->len == 0) { + bfdec_set_zero(q, 0); + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + bfdec_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF || b->expn == BF_EXP_ZERO) { + bfdec_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bfdec_set(r, a); + return bfdec_round(r, prec, flags); + } + } + + q_sign = a->sign ^ b->sign; + is_rndn = (rnd_mode == BF_RNDN || rnd_mode == BF_RNDNA); + switch(rnd_mode) { + default: + case BF_RNDZ: + case BF_RNDN: + case BF_RNDNA: + is_ceil = FALSE; + break; + case BF_RNDD: + is_ceil = q_sign; + break; + case BF_RNDU: + is_ceil = q_sign ^ 1; + break; + case BF_RNDA: + is_ceil = TRUE; + break; + case BF_DIVREM_EUCLIDIAN: + is_ceil = a->sign; + break; + } + + a1->expn = a->expn; + a1->tab = a->tab; + a1->len = a->len; + a1->sign = 0; + + b1->expn = b->expn; + b1->tab = b->tab; + b1->len = b->len; + b1->sign = 0; + + // bfdec_print_str("a1", a1); + // bfdec_print_str("b1", b1); + /* XXX: could improve to avoid having a large 'q' */ + bfdec_tdivremu(s, q, r, a1, b1); + if (bfdec_is_nan(q) || bfdec_is_nan(r)) + goto fail; + // bfdec_print_str("q", q); + // bfdec_print_str("r", r); + + if (r->len != 0) { + if (is_rndn) { + bfdec_init(s, r1); + if (bfdec_set(r1, r)) + goto fail; + if (bfdec_mul_si(r1, r1, 2, BF_PREC_INF, BF_RNDZ)) { + bfdec_delete(r1); + goto fail; + } + res = bfdec_cmpu(r1, b); + bfdec_delete(r1); + if (res > 0 || + (res == 0 && + (rnd_mode == BF_RNDNA || + (get_digit(q->tab, q->len, q->len * LIMB_DIGITS - q->expn) & 1) != 0))) { + goto do_sub_r; + } + } else if (is_ceil) { + do_sub_r: + res = bfdec_add_si(q, q, 1, BF_PREC_INF, BF_RNDZ); + res |= bfdec_sub(r, r, b1, BF_PREC_INF, BF_RNDZ); + if (res & BF_ST_MEM_ERROR) + goto fail; + } + } + + r->sign ^= a->sign; + q->sign = q_sign; + return bfdec_round(r, prec, flags); + fail: + bfdec_set_nan(q); + bfdec_set_nan(r); + return BF_ST_MEM_ERROR; +} + +int bfdec_rem(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags, int rnd_mode) +{ + bfdec_t q_s, *q = &q_s; + int ret; + + bfdec_init(r->ctx, q); + ret = bfdec_divrem(q, r, a, b, prec, flags, rnd_mode); + bfdec_delete(q); + return ret; +} + +/* convert to integer (infinite precision) */ +int bfdec_rint(bfdec_t *r, int rnd_mode) +{ + return bfdec_round(r, 0, rnd_mode | BF_FLAG_RADPNT_PREC); +} + +int bfdec_sqrt(bfdec_t *r, const bfdec_t *a, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = a->ctx; + int ret, k; + limb_t *a1, v; + slimb_t n, n1, prec1; + limb_t res; + + assert(r != a); + + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bfdec_set_nan(r); + } else if (a->expn == BF_EXP_INF && a->sign) { + goto invalid_op; + } else { + bfdec_set(r, a); + } + ret = 0; + } else if (a->sign || prec == BF_PREC_INF) { + invalid_op: + bfdec_set_nan(r); + ret = BF_ST_INVALID_OP; + } else { + if (flags & BF_FLAG_RADPNT_PREC) { + prec1 = bf_max(floor_div(a->expn + 1, 2) + prec, 1); + } else { + prec1 = prec; + } + /* convert the mantissa to an integer with at least 2 * + prec + 4 digits */ + n = (2 * (prec1 + 2) + 2 * LIMB_DIGITS - 1) / (2 * LIMB_DIGITS); + if (bfdec_resize(r, n)) + goto fail; + a1 = bf_malloc(s, sizeof(limb_t) * 2 * n); + if (!a1) + goto fail; + n1 = bf_min(2 * n, a->len); + memset(a1, 0, (2 * n - n1) * sizeof(limb_t)); + memcpy(a1 + 2 * n - n1, a->tab + a->len - n1, n1 * sizeof(limb_t)); + if (a->expn & 1) { + res = mp_shr_dec(a1, a1, 2 * n, 1, 0); + } else { + res = 0; + } + /* normalize so that a1 >= B^(2*n)/4. Not need for n = 1 + because mp_sqrtrem2_dec already does it */ + k = 0; + if (n > 1) { + v = a1[2 * n - 1]; + while (v < BF_DEC_BASE / 4) { + k++; + v *= 4; + } + if (k != 0) + mp_mul1_dec(a1, a1, 2 * n, 1 << (2 * k), 0); + } + if (mp_sqrtrem_dec(s, r->tab, a1, n)) { + bf_free(s, a1); + goto fail; + } + if (k != 0) + mp_div1_dec(r->tab, r->tab, n, 1 << k, 0); + if (!res) { + res = mp_scan_nz(a1, n + 1); + } + bf_free(s, a1); + if (!res) { + res = mp_scan_nz(a->tab, a->len - n1); + } + if (res != 0) + r->tab[0] |= 1; + r->sign = 0; + r->expn = (a->expn + 1) >> 1; + ret = bfdec_round(r, prec, flags); + } + return ret; + fail: + bfdec_set_nan(r); + return BF_ST_MEM_ERROR; +} + +/* The rounding mode is always BF_RNDZ. Return BF_ST_OVERFLOW if there + is an overflow and 0 otherwise. No memory error is possible. */ +int bfdec_get_int32(int *pres, const bfdec_t *a) +{ + uint32_t v; + int ret; + if (a->expn >= BF_EXP_INF) { + ret = 0; + if (a->expn == BF_EXP_INF) { + v = (uint32_t)INT32_MAX + a->sign; + /* XXX: return overflow ? */ + } else { + v = INT32_MAX; + } + } else if (a->expn <= 0) { + v = 0; + ret = 0; + } else if (a->expn <= 9) { + v = fast_shr_dec(a->tab[a->len - 1], LIMB_DIGITS - a->expn); + if (a->sign) + v = -v; + ret = 0; + } else if (a->expn == 10) { + uint64_t v1; + uint32_t v_max; +#if LIMB_BITS == 64 + v1 = fast_shr_dec(a->tab[a->len - 1], LIMB_DIGITS - a->expn); +#else + v1 = (uint64_t)a->tab[a->len - 1] * 10 + + get_digit(a->tab, a->len, (a->len - 1) * LIMB_DIGITS - 1); +#endif + v_max = (uint32_t)INT32_MAX + a->sign; + if (v1 > v_max) { + v = v_max; + ret = BF_ST_OVERFLOW; + } else { + v = v1; + if (a->sign) + v = -v; + ret = 0; + } + } else { + v = (uint32_t)INT32_MAX + a->sign; + ret = BF_ST_OVERFLOW; + } + *pres = v; + return ret; +} + +/* power to an integer with infinite precision */ +int bfdec_pow_ui(bfdec_t *r, const bfdec_t *a, limb_t b) +{ + int ret, n_bits, i; + + assert(r != a); + if (b == 0) + return bfdec_set_ui(r, 1); + ret = bfdec_set(r, a); + n_bits = LIMB_BITS - clz(b); + for(i = n_bits - 2; i >= 0; i--) { + ret |= bfdec_mul(r, r, r, BF_PREC_INF, BF_RNDZ); + if ((b >> i) & 1) + ret |= bfdec_mul(r, r, a, BF_PREC_INF, BF_RNDZ); + } + return ret; +} + +char *bfdec_ftoa(size_t *plen, const bfdec_t *a, limb_t prec, bf_flags_t flags) +{ + return bf_ftoa_internal(plen, (const bf_t *)a, 10, prec, flags, TRUE); +} + +int bfdec_atof(bfdec_t *r, const char *str, const char **pnext, + limb_t prec, bf_flags_t flags) +{ + slimb_t dummy_exp; + return bf_atof_internal((bf_t *)r, &dummy_exp, str, pnext, 10, prec, + flags, TRUE); +} + +#endif /* USE_BF_DEC */ + +#ifdef USE_FFT_MUL +/***************************************************************/ +/* Integer multiplication with FFT */ + +/* or LIMB_BITS at bit position 'pos' in tab */ +static inline void put_bits(limb_t *tab, limb_t len, slimb_t pos, limb_t val) +{ + limb_t i; + int p; + + i = pos >> LIMB_LOG2_BITS; + p = pos & (LIMB_BITS - 1); + if (i < len) + tab[i] |= val << p; + if (p != 0) { + i++; + if (i < len) { + tab[i] |= val >> (LIMB_BITS - p); + } + } +} + +#if defined(__AVX2__) + +typedef double NTTLimb; + +/* we must have: modulo >= 1 << NTT_MOD_LOG2_MIN */ +#define NTT_MOD_LOG2_MIN 50 +#define NTT_MOD_LOG2_MAX 51 +#define NB_MODS 5 +#define NTT_PROOT_2EXP 39 +static const int ntt_int_bits[NB_MODS] = { 254, 203, 152, 101, 50, }; + +static const limb_t ntt_mods[NB_MODS] = { 0x00073a8000000001, 0x0007858000000001, 0x0007a38000000001, 0x0007a68000000001, 0x0007fd8000000001, +}; + +static const limb_t ntt_proot[2][NB_MODS] = { + { 0x00056198d44332c8, 0x0002eb5d640aad39, 0x00047e31eaa35fd0, 0x0005271ac118a150, 0x00075e0ce8442bd5, }, + { 0x000461169761bcc5, 0x0002dac3cb2da688, 0x0004abc97751e3bf, 0x000656778fc8c485, 0x0000dc6469c269fa, }, +}; + +static const limb_t ntt_mods_cr[NB_MODS * (NB_MODS - 1) / 2] = { + 0x00020e4da740da8e, 0x0004c3dc09c09c1d, 0x000063bd097b4271, 0x000799d8f18f18fd, + 0x0005384222222264, 0x000572b07c1f07fe, 0x00035cd08888889a, + 0x00066015555557e3, 0x000725960b60b623, + 0x0002fc1fa1d6ce12, +}; + +#else + +typedef limb_t NTTLimb; + +#if LIMB_BITS == 64 + +#define NTT_MOD_LOG2_MIN 61 +#define NTT_MOD_LOG2_MAX 62 +#define NB_MODS 5 +#define NTT_PROOT_2EXP 51 +static const int ntt_int_bits[NB_MODS] = { 307, 246, 185, 123, 61, }; + +static const limb_t ntt_mods[NB_MODS] = { 0x28d8000000000001, 0x2a88000000000001, 0x2ed8000000000001, 0x3508000000000001, 0x3aa8000000000001, +}; + +static const limb_t ntt_proot[2][NB_MODS] = { + { 0x1b8ea61034a2bea7, 0x21a9762de58206fb, 0x02ca782f0756a8ea, 0x278384537a3e50a1, 0x106e13fee74ce0ab, }, + { 0x233513af133e13b8, 0x1d13140d1c6f75f1, 0x12cde57f97e3eeda, 0x0d6149e23cbe654f, 0x36cd204f522a1379, }, +}; + +static const limb_t ntt_mods_cr[NB_MODS * (NB_MODS - 1) / 2] = { + 0x08a9ed097b425eea, 0x18a44aaaaaaaaab3, 0x2493f57f57f57f5d, 0x126b8d0649a7f8d4, + 0x09d80ed7303b5ccc, 0x25b8bcf3cf3cf3d5, 0x2ce6ce63398ce638, + 0x0e31fad40a57eb59, 0x02a3529fd4a7f52f, + 0x3a5493e93e93e94a, +}; + +#elif LIMB_BITS == 32 + +/* we must have: modulo >= 1 << NTT_MOD_LOG2_MIN */ +#define NTT_MOD_LOG2_MIN 29 +#define NTT_MOD_LOG2_MAX 30 +#define NB_MODS 5 +#define NTT_PROOT_2EXP 20 +static const int ntt_int_bits[NB_MODS] = { 148, 119, 89, 59, 29, }; + +static const limb_t ntt_mods[NB_MODS] = { 0x0000000032b00001, 0x0000000033700001, 0x0000000036d00001, 0x0000000037300001, 0x000000003e500001, +}; + +static const limb_t ntt_proot[2][NB_MODS] = { + { 0x0000000032525f31, 0x0000000005eb3b37, 0x00000000246eda9f, 0x0000000035f25901, 0x00000000022f5768, }, + { 0x00000000051eba1a, 0x00000000107be10e, 0x000000001cd574e0, 0x00000000053806e6, 0x000000002cd6bf98, }, +}; + +static const limb_t ntt_mods_cr[NB_MODS * (NB_MODS - 1) / 2] = { + 0x000000000449559a, 0x000000001eba6ca9, 0x000000002ec18e46, 0x000000000860160b, + 0x000000000d321307, 0x000000000bf51120, 0x000000000f662938, + 0x000000000932ab3e, 0x000000002f40eef8, + 0x000000002e760905, +}; + +#endif /* LIMB_BITS */ + +#endif /* !AVX2 */ + +#if defined(__AVX2__) +#define NTT_TRIG_K_MAX 18 +#else +#define NTT_TRIG_K_MAX 19 +#endif + +typedef struct BFNTTState { + bf_context_t *ctx; + + /* used for mul_mod_fast() */ + limb_t ntt_mods_div[NB_MODS]; + + limb_t ntt_proot_pow[NB_MODS][2][NTT_PROOT_2EXP + 1]; + limb_t ntt_proot_pow_inv[NB_MODS][2][NTT_PROOT_2EXP + 1]; + NTTLimb *ntt_trig[NB_MODS][2][NTT_TRIG_K_MAX + 1]; + /* 1/2^n mod m */ + limb_t ntt_len_inv[NB_MODS][NTT_PROOT_2EXP + 1][2]; +#if defined(__AVX2__) + __m256d ntt_mods_cr_vec[NB_MODS * (NB_MODS - 1) / 2]; + __m256d ntt_mods_vec[NB_MODS]; + __m256d ntt_mods_inv_vec[NB_MODS]; +#else + limb_t ntt_mods_cr_inv[NB_MODS * (NB_MODS - 1) / 2]; +#endif +} BFNTTState; + +static NTTLimb *get_trig(BFNTTState *s, int k, int inverse, int m_idx); + +/* add modulo with up to (LIMB_BITS-1) bit modulo */ +static inline limb_t add_mod(limb_t a, limb_t b, limb_t m) +{ + limb_t r; + r = a + b; + if (r >= m) + r -= m; + return r; +} + +/* sub modulo with up to LIMB_BITS bit modulo */ +static inline limb_t sub_mod(limb_t a, limb_t b, limb_t m) +{ + limb_t r; + r = a - b; + if (r > a) + r += m; + return r; +} + +/* return (r0+r1*B) mod m + precondition: 0 <= r0+r1*B < 2^(64+NTT_MOD_LOG2_MIN) +*/ +static inline limb_t mod_fast(dlimb_t r, + limb_t m, limb_t m_inv) +{ + limb_t a1, q, t0, r1, r0; + + a1 = r >> NTT_MOD_LOG2_MIN; + + q = ((dlimb_t)a1 * m_inv) >> LIMB_BITS; + r = r - (dlimb_t)q * m - m * 2; + r1 = r >> LIMB_BITS; + t0 = (slimb_t)r1 >> 1; + r += m & t0; + r0 = r; + r1 = r >> LIMB_BITS; + r0 += m & r1; + return r0; +} + +/* faster version using precomputed modulo inverse. + precondition: 0 <= a * b < 2^(64+NTT_MOD_LOG2_MIN) */ +static inline limb_t mul_mod_fast(limb_t a, limb_t b, + limb_t m, limb_t m_inv) +{ + dlimb_t r; + r = (dlimb_t)a * (dlimb_t)b; + return mod_fast(r, m, m_inv); +} + +static inline limb_t init_mul_mod_fast(limb_t m) +{ + dlimb_t t; + assert(m < (limb_t)1 << NTT_MOD_LOG2_MAX); + assert(m >= (limb_t)1 << NTT_MOD_LOG2_MIN); + t = (dlimb_t)1 << (LIMB_BITS + NTT_MOD_LOG2_MIN); + return t / m; +} + +/* Faster version used when the multiplier is constant. 0 <= a < 2^64, + 0 <= b < m. */ +static inline limb_t mul_mod_fast2(limb_t a, limb_t b, + limb_t m, limb_t b_inv) +{ + limb_t r, q; + + q = ((dlimb_t)a * (dlimb_t)b_inv) >> LIMB_BITS; + r = a * b - q * m; + if (r >= m) + r -= m; + return r; +} + +/* Faster version used when the multiplier is constant. 0 <= a < 2^64, + 0 <= b < m. Let r = a * b mod m. The return value is 'r' or 'r + + m'. */ +static inline limb_t mul_mod_fast3(limb_t a, limb_t b, + limb_t m, limb_t b_inv) +{ + limb_t r, q; + + q = ((dlimb_t)a * (dlimb_t)b_inv) >> LIMB_BITS; + r = a * b - q * m; + return r; +} + +static inline limb_t init_mul_mod_fast2(limb_t b, limb_t m) +{ + return ((dlimb_t)b << LIMB_BITS) / m; +} + +#ifdef __AVX2__ + +static inline limb_t ntt_limb_to_int(NTTLimb a, limb_t m) +{ + slimb_t v; + v = a; + if (v < 0) + v += m; + if (v >= m) + v -= m; + return v; +} + +static inline NTTLimb int_to_ntt_limb(limb_t a, limb_t m) +{ + return (slimb_t)a; +} + +static inline NTTLimb int_to_ntt_limb2(limb_t a, limb_t m) +{ + if (a >= (m / 2)) + a -= m; + return (slimb_t)a; +} + +/* return r + m if r < 0 otherwise r. */ +static inline __m256d ntt_mod1(__m256d r, __m256d m) +{ + return _mm256_blendv_pd(r, r + m, r); +} + +/* input: abs(r) < 2 * m. Output: abs(r) < m */ +static inline __m256d ntt_mod(__m256d r, __m256d mf, __m256d m2f) +{ + return _mm256_blendv_pd(r, r + m2f, r) - mf; +} + +/* input: abs(a*b) < 2 * m^2, output: abs(r) < m */ +static inline __m256d ntt_mul_mod(__m256d a, __m256d b, __m256d mf, + __m256d m_inv) +{ + __m256d r, q, ab1, ab0, qm0, qm1; + ab1 = a * b; + q = _mm256_round_pd(ab1 * m_inv, 0); /* round to nearest */ + qm1 = q * mf; + qm0 = _mm256_fmsub_pd(q, mf, qm1); /* low part */ + ab0 = _mm256_fmsub_pd(a, b, ab1); /* low part */ + r = (ab1 - qm1) + (ab0 - qm0); + return r; +} + +static void *bf_aligned_malloc(bf_context_t *s, size_t size, size_t align) +{ + void *ptr; + void **ptr1; + ptr = bf_malloc(s, size + sizeof(void *) + align - 1); + if (!ptr) + return NULL; + ptr1 = (void **)(((uintptr_t)ptr + sizeof(void *) + align - 1) & + ~(align - 1)); + ptr1[-1] = ptr; + return ptr1; +} + +static void bf_aligned_free(bf_context_t *s, void *ptr) +{ + if (!ptr) + return; + bf_free(s, ((void **)ptr)[-1]); +} + +static void *ntt_malloc(BFNTTState *s, size_t size) +{ + return bf_aligned_malloc(s->ctx, size, 64); +} + +static void ntt_free(BFNTTState *s, void *ptr) +{ + bf_aligned_free(s->ctx, ptr); +} + +static no_inline int ntt_fft(BFNTTState *s, + NTTLimb *out_buf, NTTLimb *in_buf, + NTTLimb *tmp_buf, int fft_len_log2, + int inverse, int m_idx) +{ + limb_t nb_blocks, fft_per_block, p, k, n, stride_in, i, j; + NTTLimb *tab_in, *tab_out, *tmp, *trig; + __m256d m_inv, mf, m2f, c, a0, a1, b0, b1; + limb_t m; + int l; + + m = ntt_mods[m_idx]; + + m_inv = _mm256_set1_pd(1.0 / (double)m); + mf = _mm256_set1_pd(m); + m2f = _mm256_set1_pd(m * 2); + + n = (limb_t)1 << fft_len_log2; + assert(n >= 8); + stride_in = n / 2; + + tab_in = in_buf; + tab_out = tmp_buf; + trig = get_trig(s, fft_len_log2, inverse, m_idx); + if (!trig) + return -1; + p = 0; + for(k = 0; k < stride_in; k += 4) { + a0 = _mm256_load_pd(&tab_in[k]); + a1 = _mm256_load_pd(&tab_in[k + stride_in]); + c = _mm256_load_pd(trig); + trig += 4; + b0 = ntt_mod(a0 + a1, mf, m2f); + b1 = ntt_mul_mod(a0 - a1, c, mf, m_inv); + a0 = _mm256_permute2f128_pd(b0, b1, 0x20); + a1 = _mm256_permute2f128_pd(b0, b1, 0x31); + a0 = _mm256_permute4x64_pd(a0, 0xd8); + a1 = _mm256_permute4x64_pd(a1, 0xd8); + _mm256_store_pd(&tab_out[p], a0); + _mm256_store_pd(&tab_out[p + 4], a1); + p += 2 * 4; + } + tmp = tab_in; + tab_in = tab_out; + tab_out = tmp; + + trig = get_trig(s, fft_len_log2 - 1, inverse, m_idx); + if (!trig) + return -1; + p = 0; + for(k = 0; k < stride_in; k += 4) { + a0 = _mm256_load_pd(&tab_in[k]); + a1 = _mm256_load_pd(&tab_in[k + stride_in]); + c = _mm256_setr_pd(trig[0], trig[0], trig[1], trig[1]); + trig += 2; + b0 = ntt_mod(a0 + a1, mf, m2f); + b1 = ntt_mul_mod(a0 - a1, c, mf, m_inv); + a0 = _mm256_permute2f128_pd(b0, b1, 0x20); + a1 = _mm256_permute2f128_pd(b0, b1, 0x31); + _mm256_store_pd(&tab_out[p], a0); + _mm256_store_pd(&tab_out[p + 4], a1); + p += 2 * 4; + } + tmp = tab_in; + tab_in = tab_out; + tab_out = tmp; + + nb_blocks = n / 4; + fft_per_block = 4; + + l = fft_len_log2 - 2; + while (nb_blocks != 2) { + nb_blocks >>= 1; + p = 0; + k = 0; + trig = get_trig(s, l, inverse, m_idx); + if (!trig) + return -1; + for(i = 0; i < nb_blocks; i++) { + c = _mm256_set1_pd(trig[0]); + trig++; + for(j = 0; j < fft_per_block; j += 4) { + a0 = _mm256_load_pd(&tab_in[k + j]); + a1 = _mm256_load_pd(&tab_in[k + j + stride_in]); + b0 = ntt_mod(a0 + a1, mf, m2f); + b1 = ntt_mul_mod(a0 - a1, c, mf, m_inv); + _mm256_store_pd(&tab_out[p + j], b0); + _mm256_store_pd(&tab_out[p + j + fft_per_block], b1); + } + k += fft_per_block; + p += 2 * fft_per_block; + } + fft_per_block <<= 1; + l--; + tmp = tab_in; + tab_in = tab_out; + tab_out = tmp; + } + + tab_out = out_buf; + for(k = 0; k < stride_in; k += 4) { + a0 = _mm256_load_pd(&tab_in[k]); + a1 = _mm256_load_pd(&tab_in[k + stride_in]); + b0 = ntt_mod(a0 + a1, mf, m2f); + b1 = ntt_mod(a0 - a1, mf, m2f); + _mm256_store_pd(&tab_out[k], b0); + _mm256_store_pd(&tab_out[k + stride_in], b1); + } + return 0; +} + +static void ntt_vec_mul(BFNTTState *s, + NTTLimb *tab1, NTTLimb *tab2, limb_t fft_len_log2, + int k_tot, int m_idx) +{ + limb_t i, c_inv, n, m; + __m256d m_inv, mf, a, b, c; + + m = ntt_mods[m_idx]; + c_inv = s->ntt_len_inv[m_idx][k_tot][0]; + m_inv = _mm256_set1_pd(1.0 / (double)m); + mf = _mm256_set1_pd(m); + c = _mm256_set1_pd(int_to_ntt_limb(c_inv, m)); + n = (limb_t)1 << fft_len_log2; + for(i = 0; i < n; i += 4) { + a = _mm256_load_pd(&tab1[i]); + b = _mm256_load_pd(&tab2[i]); + a = ntt_mul_mod(a, b, mf, m_inv); + a = ntt_mul_mod(a, c, mf, m_inv); + _mm256_store_pd(&tab1[i], a); + } +} + +static no_inline void mul_trig(NTTLimb *buf, + limb_t n, limb_t c1, limb_t m, limb_t m_inv1) +{ + limb_t i, c2, c3, c4; + __m256d c, c_mul, a0, mf, m_inv; + assert(n >= 2); + + mf = _mm256_set1_pd(m); + m_inv = _mm256_set1_pd(1.0 / (double)m); + + c2 = mul_mod_fast(c1, c1, m, m_inv1); + c3 = mul_mod_fast(c2, c1, m, m_inv1); + c4 = mul_mod_fast(c2, c2, m, m_inv1); + c = _mm256_setr_pd(1, int_to_ntt_limb(c1, m), + int_to_ntt_limb(c2, m), int_to_ntt_limb(c3, m)); + c_mul = _mm256_set1_pd(int_to_ntt_limb(c4, m)); + for(i = 0; i < n; i += 4) { + a0 = _mm256_load_pd(&buf[i]); + a0 = ntt_mul_mod(a0, c, mf, m_inv); + _mm256_store_pd(&buf[i], a0); + c = ntt_mul_mod(c, c_mul, mf, m_inv); + } +} + +#else + +static void *ntt_malloc(BFNTTState *s, size_t size) +{ + return bf_malloc(s->ctx, size); +} + +static void ntt_free(BFNTTState *s, void *ptr) +{ + bf_free(s->ctx, ptr); +} + +static inline limb_t ntt_limb_to_int(NTTLimb a, limb_t m) +{ + if (a >= m) + a -= m; + return a; +} + +static inline NTTLimb int_to_ntt_limb(slimb_t a, limb_t m) +{ + return a; +} + +static no_inline int ntt_fft(BFNTTState *s, NTTLimb *out_buf, NTTLimb *in_buf, + NTTLimb *tmp_buf, int fft_len_log2, + int inverse, int m_idx) +{ + limb_t nb_blocks, fft_per_block, p, k, n, stride_in, i, j, m, m2; + NTTLimb *tab_in, *tab_out, *tmp, a0, a1, b0, b1, c, *trig, c_inv; + int l; + + m = ntt_mods[m_idx]; + m2 = 2 * m; + n = (limb_t)1 << fft_len_log2; + nb_blocks = n; + fft_per_block = 1; + stride_in = n / 2; + tab_in = in_buf; + tab_out = tmp_buf; + l = fft_len_log2; + while (nb_blocks != 2) { + nb_blocks >>= 1; + p = 0; + k = 0; + trig = get_trig(s, l, inverse, m_idx); + if (!trig) + return -1; + for(i = 0; i < nb_blocks; i++) { + c = trig[0]; + c_inv = trig[1]; + trig += 2; + for(j = 0; j < fft_per_block; j++) { + a0 = tab_in[k + j]; + a1 = tab_in[k + j + stride_in]; + b0 = add_mod(a0, a1, m2); + b1 = a0 - a1 + m2; + b1 = mul_mod_fast3(b1, c, m, c_inv); + tab_out[p + j] = b0; + tab_out[p + j + fft_per_block] = b1; + } + k += fft_per_block; + p += 2 * fft_per_block; + } + fft_per_block <<= 1; + l--; + tmp = tab_in; + tab_in = tab_out; + tab_out = tmp; + } + /* no twiddle in last step */ + tab_out = out_buf; + for(k = 0; k < stride_in; k++) { + a0 = tab_in[k]; + a1 = tab_in[k + stride_in]; + b0 = add_mod(a0, a1, m2); + b1 = sub_mod(a0, a1, m2); + tab_out[k] = b0; + tab_out[k + stride_in] = b1; + } + return 0; +} + +static void ntt_vec_mul(BFNTTState *s, + NTTLimb *tab1, NTTLimb *tab2, int fft_len_log2, + int k_tot, int m_idx) +{ + limb_t i, norm, norm_inv, a, n, m, m_inv; + + m = ntt_mods[m_idx]; + m_inv = s->ntt_mods_div[m_idx]; + norm = s->ntt_len_inv[m_idx][k_tot][0]; + norm_inv = s->ntt_len_inv[m_idx][k_tot][1]; + n = (limb_t)1 << fft_len_log2; + for(i = 0; i < n; i++) { + a = tab1[i]; + /* need to reduce the range so that the product is < + 2^(LIMB_BITS+NTT_MOD_LOG2_MIN) */ + if (a >= m) + a -= m; + a = mul_mod_fast(a, tab2[i], m, m_inv); + a = mul_mod_fast3(a, norm, m, norm_inv); + tab1[i] = a; + } +} + +static no_inline void mul_trig(NTTLimb *buf, + limb_t n, limb_t c_mul, limb_t m, limb_t m_inv) +{ + limb_t i, c0, c_mul_inv; + + c0 = 1; + c_mul_inv = init_mul_mod_fast2(c_mul, m); + for(i = 0; i < n; i++) { + buf[i] = mul_mod_fast(buf[i], c0, m, m_inv); + c0 = mul_mod_fast2(c0, c_mul, m, c_mul_inv); + } +} + +#endif /* !AVX2 */ + +static no_inline NTTLimb *get_trig(BFNTTState *s, + int k, int inverse, int m_idx) +{ + NTTLimb *tab; + limb_t i, n2, c, c_mul, m, c_mul_inv; + + if (k > NTT_TRIG_K_MAX) + return NULL; + + tab = s->ntt_trig[m_idx][inverse][k]; + if (tab) + return tab; + n2 = (limb_t)1 << (k - 1); + m = ntt_mods[m_idx]; +#ifdef __AVX2__ + tab = ntt_malloc(s, sizeof(NTTLimb) * n2); +#else + tab = ntt_malloc(s, sizeof(NTTLimb) * n2 * 2); +#endif + if (!tab) + return NULL; + c = 1; + c_mul = s->ntt_proot_pow[m_idx][inverse][k]; + c_mul_inv = s->ntt_proot_pow_inv[m_idx][inverse][k]; + for(i = 0; i < n2; i++) { +#ifdef __AVX2__ + tab[i] = int_to_ntt_limb2(c, m); +#else + tab[2 * i] = int_to_ntt_limb(c, m); + tab[2 * i + 1] = init_mul_mod_fast2(c, m); +#endif + c = mul_mod_fast2(c, c_mul, m, c_mul_inv); + } + s->ntt_trig[m_idx][inverse][k] = tab; + return tab; +} + +void fft_clear_cache(bf_context_t *s1) +{ + int m_idx, inverse, k; + BFNTTState *s = s1->ntt_state; + if (s) { + for(m_idx = 0; m_idx < NB_MODS; m_idx++) { + for(inverse = 0; inverse < 2; inverse++) { + for(k = 0; k < NTT_TRIG_K_MAX + 1; k++) { + if (s->ntt_trig[m_idx][inverse][k]) { + ntt_free(s, s->ntt_trig[m_idx][inverse][k]); + s->ntt_trig[m_idx][inverse][k] = NULL; + } + } + } + } +#if defined(__AVX2__) + bf_aligned_free(s1, s); +#else + bf_free(s1, s); +#endif + s1->ntt_state = NULL; + } +} + +#define STRIP_LEN 16 + +/* dst = buf1, src = buf2 */ +static int ntt_fft_partial(BFNTTState *s, NTTLimb *buf1, + int k1, int k2, limb_t n1, limb_t n2, int inverse, + limb_t m_idx) +{ + limb_t i, j, c_mul, c0, m, m_inv, strip_len, l; + NTTLimb *buf2, *buf3; + + buf2 = NULL; + buf3 = ntt_malloc(s, sizeof(NTTLimb) * n1); + if (!buf3) + goto fail; + if (k2 == 0) { + if (ntt_fft(s, buf1, buf1, buf3, k1, inverse, m_idx)) + goto fail; + } else { + strip_len = STRIP_LEN; + buf2 = ntt_malloc(s, sizeof(NTTLimb) * n1 * strip_len); + if (!buf2) + goto fail; + m = ntt_mods[m_idx]; + m_inv = s->ntt_mods_div[m_idx]; + c0 = s->ntt_proot_pow[m_idx][inverse][k1 + k2]; + c_mul = 1; + assert((n2 % strip_len) == 0); + for(j = 0; j < n2; j += strip_len) { + for(i = 0; i < n1; i++) { + for(l = 0; l < strip_len; l++) { + buf2[i + l * n1] = buf1[i * n2 + (j + l)]; + } + } + for(l = 0; l < strip_len; l++) { + if (inverse) + mul_trig(buf2 + l * n1, n1, c_mul, m, m_inv); + if (ntt_fft(s, buf2 + l * n1, buf2 + l * n1, buf3, k1, inverse, m_idx)) + goto fail; + if (!inverse) + mul_trig(buf2 + l * n1, n1, c_mul, m, m_inv); + c_mul = mul_mod_fast(c_mul, c0, m, m_inv); + } + + for(i = 0; i < n1; i++) { + for(l = 0; l < strip_len; l++) { + buf1[i * n2 + (j + l)] = buf2[i + l *n1]; + } + } + } + ntt_free(s, buf2); + } + ntt_free(s, buf3); + return 0; + fail: + ntt_free(s, buf2); + ntt_free(s, buf3); + return -1; +} + + +/* dst = buf1, src = buf2, tmp = buf3 */ +static int ntt_conv(BFNTTState *s, NTTLimb *buf1, NTTLimb *buf2, + int k, int k_tot, limb_t m_idx) +{ + limb_t n1, n2, i; + int k1, k2; + + if (k <= NTT_TRIG_K_MAX) { + k1 = k; + } else { + /* recursive split of the FFT */ + k1 = bf_min(k / 2, NTT_TRIG_K_MAX); + } + k2 = k - k1; + n1 = (limb_t)1 << k1; + n2 = (limb_t)1 << k2; + + if (ntt_fft_partial(s, buf1, k1, k2, n1, n2, 0, m_idx)) + return -1; + if (ntt_fft_partial(s, buf2, k1, k2, n1, n2, 0, m_idx)) + return -1; + if (k2 == 0) { + ntt_vec_mul(s, buf1, buf2, k, k_tot, m_idx); + } else { + for(i = 0; i < n1; i++) { + ntt_conv(s, buf1 + i * n2, buf2 + i * n2, k2, k_tot, m_idx); + } + } + if (ntt_fft_partial(s, buf1, k1, k2, n1, n2, 1, m_idx)) + return -1; + return 0; +} + + +static no_inline void limb_to_ntt(BFNTTState *s, + NTTLimb *tabr, limb_t fft_len, + const limb_t *taba, limb_t a_len, int dpl, + int first_m_idx, int nb_mods) +{ + slimb_t i, n; + dlimb_t a, b; + int j, shift; + limb_t base_mask1, a0, a1, a2, r, m, m_inv; + +#if 0 + for(i = 0; i < a_len; i++) { + printf("%" PRId64 ": " FMT_LIMB "\n", + (int64_t)i, taba[i]); + } +#endif + memset(tabr, 0, sizeof(NTTLimb) * fft_len * nb_mods); + shift = dpl & (LIMB_BITS - 1); + if (shift == 0) + base_mask1 = -1; + else + base_mask1 = ((limb_t)1 << shift) - 1; + n = bf_min(fft_len, (a_len * LIMB_BITS + dpl - 1) / dpl); + for(i = 0; i < n; i++) { + a0 = get_bits(taba, a_len, i * dpl); + if (dpl <= LIMB_BITS) { + a0 &= base_mask1; + a = a0; + } else { + a1 = get_bits(taba, a_len, i * dpl + LIMB_BITS); + if (dpl <= (LIMB_BITS + NTT_MOD_LOG2_MIN)) { + a = a0 | ((dlimb_t)(a1 & base_mask1) << LIMB_BITS); + } else { + if (dpl > 2 * LIMB_BITS) { + a2 = get_bits(taba, a_len, i * dpl + LIMB_BITS * 2) & + base_mask1; + } else { + a1 &= base_mask1; + a2 = 0; + } + // printf("a=0x%016lx%016lx%016lx\n", a2, a1, a0); + a = (a0 >> (LIMB_BITS - NTT_MOD_LOG2_MAX + NTT_MOD_LOG2_MIN)) | + ((dlimb_t)a1 << (NTT_MOD_LOG2_MAX - NTT_MOD_LOG2_MIN)) | + ((dlimb_t)a2 << (LIMB_BITS + NTT_MOD_LOG2_MAX - NTT_MOD_LOG2_MIN)); + a0 &= ((limb_t)1 << (LIMB_BITS - NTT_MOD_LOG2_MAX + NTT_MOD_LOG2_MIN)) - 1; + } + } + for(j = 0; j < nb_mods; j++) { + m = ntt_mods[first_m_idx + j]; + m_inv = s->ntt_mods_div[first_m_idx + j]; + r = mod_fast(a, m, m_inv); + if (dpl > (LIMB_BITS + NTT_MOD_LOG2_MIN)) { + b = ((dlimb_t)r << (LIMB_BITS - NTT_MOD_LOG2_MAX + NTT_MOD_LOG2_MIN)) | a0; + r = mod_fast(b, m, m_inv); + } + tabr[i + j * fft_len] = int_to_ntt_limb(r, m); + } + } +} + +#if defined(__AVX2__) + +#define VEC_LEN 4 + +typedef union { + __m256d v; + double d[4]; +} VecUnion; + +static no_inline void ntt_to_limb(BFNTTState *s, limb_t *tabr, limb_t r_len, + const NTTLimb *buf, int fft_len_log2, int dpl, + int nb_mods) +{ + const limb_t *mods = ntt_mods + NB_MODS - nb_mods; + const __m256d *mods_cr_vec, *mf, *m_inv; + VecUnion y[NB_MODS]; + limb_t u[NB_MODS], carry[NB_MODS], fft_len, base_mask1, r; + slimb_t i, len, pos; + int j, k, l, shift, n_limb1, p; + dlimb_t t; + + j = NB_MODS * (NB_MODS - 1) / 2 - nb_mods * (nb_mods - 1) / 2; + mods_cr_vec = s->ntt_mods_cr_vec + j; + mf = s->ntt_mods_vec + NB_MODS - nb_mods; + m_inv = s->ntt_mods_inv_vec + NB_MODS - nb_mods; + + shift = dpl & (LIMB_BITS - 1); + if (shift == 0) + base_mask1 = -1; + else + base_mask1 = ((limb_t)1 << shift) - 1; + n_limb1 = ((unsigned)dpl - 1) / LIMB_BITS; + for(j = 0; j < NB_MODS; j++) + carry[j] = 0; + for(j = 0; j < NB_MODS; j++) + u[j] = 0; /* avoid warnings */ + memset(tabr, 0, sizeof(limb_t) * r_len); + fft_len = (limb_t)1 << fft_len_log2; + len = bf_min(fft_len, (r_len * LIMB_BITS + dpl - 1) / dpl); + len = (len + VEC_LEN - 1) & ~(VEC_LEN - 1); + i = 0; + while (i < len) { + for(j = 0; j < nb_mods; j++) + y[j].v = *(__m256d *)&buf[i + fft_len * j]; + + /* Chinese remainder to get mixed radix representation */ + l = 0; + for(j = 0; j < nb_mods - 1; j++) { + y[j].v = ntt_mod1(y[j].v, mf[j]); + for(k = j + 1; k < nb_mods; k++) { + y[k].v = ntt_mul_mod(y[k].v - y[j].v, + mods_cr_vec[l], mf[k], m_inv[k]); + l++; + } + } + y[j].v = ntt_mod1(y[j].v, mf[j]); + + for(p = 0; p < VEC_LEN; p++) { + /* back to normal representation */ + u[0] = (int64_t)y[nb_mods - 1].d[p]; + l = 1; + for(j = nb_mods - 2; j >= 1; j--) { + r = (int64_t)y[j].d[p]; + for(k = 0; k < l; k++) { + t = (dlimb_t)u[k] * mods[j] + r; + r = t >> LIMB_BITS; + u[k] = t; + } + u[l] = r; + l++; + } + /* XXX: for nb_mods = 5, l should be 4 */ + + /* last step adds the carry */ + r = (int64_t)y[0].d[p]; + for(k = 0; k < l; k++) { + t = (dlimb_t)u[k] * mods[j] + r + carry[k]; + r = t >> LIMB_BITS; + u[k] = t; + } + u[l] = r + carry[l]; + +#if 0 + printf("%" PRId64 ": ", i); + for(j = nb_mods - 1; j >= 0; j--) { + printf(" %019" PRIu64, u[j]); + } + printf("\n"); +#endif + + /* write the digits */ + pos = i * dpl; + for(j = 0; j < n_limb1; j++) { + put_bits(tabr, r_len, pos, u[j]); + pos += LIMB_BITS; + } + put_bits(tabr, r_len, pos, u[n_limb1] & base_mask1); + /* shift by dpl digits and set the carry */ + if (shift == 0) { + for(j = n_limb1 + 1; j < nb_mods; j++) + carry[j - (n_limb1 + 1)] = u[j]; + } else { + for(j = n_limb1; j < nb_mods - 1; j++) { + carry[j - n_limb1] = (u[j] >> shift) | + (u[j + 1] << (LIMB_BITS - shift)); + } + carry[nb_mods - 1 - n_limb1] = u[nb_mods - 1] >> shift; + } + i++; + } + } +} +#else +static no_inline void ntt_to_limb(BFNTTState *s, limb_t *tabr, limb_t r_len, + const NTTLimb *buf, int fft_len_log2, int dpl, + int nb_mods) +{ + const limb_t *mods = ntt_mods + NB_MODS - nb_mods; + const limb_t *mods_cr, *mods_cr_inv; + limb_t y[NB_MODS], u[NB_MODS], carry[NB_MODS], fft_len, base_mask1, r; + slimb_t i, len, pos; + int j, k, l, shift, n_limb1; + dlimb_t t; + + j = NB_MODS * (NB_MODS - 1) / 2 - nb_mods * (nb_mods - 1) / 2; + mods_cr = ntt_mods_cr + j; + mods_cr_inv = s->ntt_mods_cr_inv + j; + + shift = dpl & (LIMB_BITS - 1); + if (shift == 0) + base_mask1 = -1; + else + base_mask1 = ((limb_t)1 << shift) - 1; + n_limb1 = ((unsigned)dpl - 1) / LIMB_BITS; + for(j = 0; j < NB_MODS; j++) + carry[j] = 0; + for(j = 0; j < NB_MODS; j++) + u[j] = 0; /* avoid warnings */ + memset(tabr, 0, sizeof(limb_t) * r_len); + fft_len = (limb_t)1 << fft_len_log2; + len = bf_min(fft_len, (r_len * LIMB_BITS + dpl - 1) / dpl); + for(i = 0; i < len; i++) { + for(j = 0; j < nb_mods; j++) { + y[j] = ntt_limb_to_int(buf[i + fft_len * j], mods[j]); + } + + /* Chinese remainder to get mixed radix representation */ + l = 0; + for(j = 0; j < nb_mods - 1; j++) { + for(k = j + 1; k < nb_mods; k++) { + limb_t m; + m = mods[k]; + /* Note: there is no overflow in the sub_mod() because + the modulos are sorted by increasing order */ + y[k] = mul_mod_fast2(y[k] - y[j] + m, + mods_cr[l], m, mods_cr_inv[l]); + l++; + } + } + + /* back to normal representation */ + u[0] = y[nb_mods - 1]; + l = 1; + for(j = nb_mods - 2; j >= 1; j--) { + r = y[j]; + for(k = 0; k < l; k++) { + t = (dlimb_t)u[k] * mods[j] + r; + r = t >> LIMB_BITS; + u[k] = t; + } + u[l] = r; + l++; + } + + /* last step adds the carry */ + r = y[0]; + for(k = 0; k < l; k++) { + t = (dlimb_t)u[k] * mods[j] + r + carry[k]; + r = t >> LIMB_BITS; + u[k] = t; + } + u[l] = r + carry[l]; + +#if 0 + printf("%" PRId64 ": ", (int64_t)i); + for(j = nb_mods - 1; j >= 0; j--) { + printf(" " FMT_LIMB, u[j]); + } + printf("\n"); +#endif + + /* write the digits */ + pos = i * dpl; + for(j = 0; j < n_limb1; j++) { + put_bits(tabr, r_len, pos, u[j]); + pos += LIMB_BITS; + } + put_bits(tabr, r_len, pos, u[n_limb1] & base_mask1); + /* shift by dpl digits and set the carry */ + if (shift == 0) { + for(j = n_limb1 + 1; j < nb_mods; j++) + carry[j - (n_limb1 + 1)] = u[j]; + } else { + for(j = n_limb1; j < nb_mods - 1; j++) { + carry[j - n_limb1] = (u[j] >> shift) | + (u[j + 1] << (LIMB_BITS - shift)); + } + carry[nb_mods - 1 - n_limb1] = u[nb_mods - 1] >> shift; + } + } +} +#endif + +static int ntt_static_init(bf_context_t *s1) +{ + BFNTTState *s; + int inverse, i, j, k, l; + limb_t c, c_inv, c_inv2, m, m_inv; + + if (s1->ntt_state) + return 0; +#if defined(__AVX2__) + s = bf_aligned_malloc(s1, sizeof(*s), 64); +#else + s = bf_malloc(s1, sizeof(*s)); +#endif + if (!s) + return -1; + memset(s, 0, sizeof(*s)); + s1->ntt_state = s; + s->ctx = s1; + + for(j = 0; j < NB_MODS; j++) { + m = ntt_mods[j]; + m_inv = init_mul_mod_fast(m); + s->ntt_mods_div[j] = m_inv; +#if defined(__AVX2__) + s->ntt_mods_vec[j] = _mm256_set1_pd(m); + s->ntt_mods_inv_vec[j] = _mm256_set1_pd(1.0 / (double)m); +#endif + c_inv2 = (m + 1) / 2; /* 1/2 */ + c_inv = 1; + for(i = 0; i <= NTT_PROOT_2EXP; i++) { + s->ntt_len_inv[j][i][0] = c_inv; + s->ntt_len_inv[j][i][1] = init_mul_mod_fast2(c_inv, m); + c_inv = mul_mod_fast(c_inv, c_inv2, m, m_inv); + } + + for(inverse = 0; inverse < 2; inverse++) { + c = ntt_proot[inverse][j]; + for(i = 0; i < NTT_PROOT_2EXP; i++) { + s->ntt_proot_pow[j][inverse][NTT_PROOT_2EXP - i] = c; + s->ntt_proot_pow_inv[j][inverse][NTT_PROOT_2EXP - i] = + init_mul_mod_fast2(c, m); + c = mul_mod_fast(c, c, m, m_inv); + } + } + } + + l = 0; + for(j = 0; j < NB_MODS - 1; j++) { + for(k = j + 1; k < NB_MODS; k++) { +#if defined(__AVX2__) + s->ntt_mods_cr_vec[l] = _mm256_set1_pd(int_to_ntt_limb2(ntt_mods_cr[l], + ntt_mods[k])); +#else + s->ntt_mods_cr_inv[l] = init_mul_mod_fast2(ntt_mods_cr[l], + ntt_mods[k]); +#endif + l++; + } + } + return 0; +} + +int bf_get_fft_size(int *pdpl, int *pnb_mods, limb_t len) +{ + int dpl, fft_len_log2, n_bits, nb_mods, dpl_found, fft_len_log2_found; + int int_bits, nb_mods_found; + limb_t cost, min_cost; + + min_cost = -1; + dpl_found = 0; + nb_mods_found = 4; + fft_len_log2_found = 0; + for(nb_mods = 3; nb_mods <= NB_MODS; nb_mods++) { + int_bits = ntt_int_bits[NB_MODS - nb_mods]; + dpl = bf_min((int_bits - 4) / 2, + 2 * LIMB_BITS + 2 * NTT_MOD_LOG2_MIN - NTT_MOD_LOG2_MAX); + for(;;) { + fft_len_log2 = ceil_log2((len * LIMB_BITS + dpl - 1) / dpl); + if (fft_len_log2 > NTT_PROOT_2EXP) + goto next; + n_bits = fft_len_log2 + 2 * dpl; + if (n_bits <= int_bits) { + cost = ((limb_t)(fft_len_log2 + 1) << fft_len_log2) * nb_mods; + // printf("n=%d dpl=%d: cost=%" PRId64 "\n", nb_mods, dpl, (int64_t)cost); + if (cost < min_cost) { + min_cost = cost; + dpl_found = dpl; + nb_mods_found = nb_mods; + fft_len_log2_found = fft_len_log2; + } + break; + } + dpl--; + if (dpl == 0) + break; + } + next: ; + } + if (!dpl_found) + abort(); + /* limit dpl if possible to reduce fixed cost of limb/NTT conversion */ + if (dpl_found > (LIMB_BITS + NTT_MOD_LOG2_MIN) && + ((limb_t)(LIMB_BITS + NTT_MOD_LOG2_MIN) << fft_len_log2_found) >= + len * LIMB_BITS) { + dpl_found = LIMB_BITS + NTT_MOD_LOG2_MIN; + } + *pnb_mods = nb_mods_found; + *pdpl = dpl_found; + return fft_len_log2_found; +} + +/* return 0 if OK, -1 if memory error */ +static no_inline int fft_mul(bf_context_t *s1, + bf_t *res, limb_t *a_tab, limb_t a_len, + limb_t *b_tab, limb_t b_len, int mul_flags) +{ + BFNTTState *s; + int dpl, fft_len_log2, j, nb_mods, reduced_mem; + slimb_t len, fft_len; + NTTLimb *buf1, *buf2, *ptr; +#if defined(USE_MUL_CHECK) + limb_t ha, hb, hr, h_ref; +#endif + + if (ntt_static_init(s1)) + return -1; + s = s1->ntt_state; + + /* find the optimal number of digits per limb (dpl) */ + len = a_len + b_len; + fft_len_log2 = bf_get_fft_size(&dpl, &nb_mods, len); + fft_len = (uint64_t)1 << fft_len_log2; + // printf("len=%" PRId64 " fft_len_log2=%d dpl=%d\n", len, fft_len_log2, dpl); +#if defined(USE_MUL_CHECK) + ha = mp_mod1(a_tab, a_len, BF_CHKSUM_MOD, 0); + hb = mp_mod1(b_tab, b_len, BF_CHKSUM_MOD, 0); +#endif + if ((mul_flags & (FFT_MUL_R_OVERLAP_A | FFT_MUL_R_OVERLAP_B)) == 0) { + if (!(mul_flags & FFT_MUL_R_NORESIZE)) + bf_resize(res, 0); + } else if (mul_flags & FFT_MUL_R_OVERLAP_B) { + limb_t *tmp_tab, tmp_len; + /* it is better to free 'b' first */ + tmp_tab = a_tab; + a_tab = b_tab; + b_tab = tmp_tab; + tmp_len = a_len; + a_len = b_len; + b_len = tmp_len; + } + buf1 = ntt_malloc(s, sizeof(NTTLimb) * fft_len * nb_mods); + if (!buf1) + return -1; + limb_to_ntt(s, buf1, fft_len, a_tab, a_len, dpl, + NB_MODS - nb_mods, nb_mods); + if ((mul_flags & (FFT_MUL_R_OVERLAP_A | FFT_MUL_R_OVERLAP_B)) == + FFT_MUL_R_OVERLAP_A) { + if (!(mul_flags & FFT_MUL_R_NORESIZE)) + bf_resize(res, 0); + } + reduced_mem = (fft_len_log2 >= 14); + if (!reduced_mem) { + buf2 = ntt_malloc(s, sizeof(NTTLimb) * fft_len * nb_mods); + if (!buf2) + goto fail; + limb_to_ntt(s, buf2, fft_len, b_tab, b_len, dpl, + NB_MODS - nb_mods, nb_mods); + if (!(mul_flags & FFT_MUL_R_NORESIZE)) + bf_resize(res, 0); /* in case res == b */ + } else { + buf2 = ntt_malloc(s, sizeof(NTTLimb) * fft_len); + if (!buf2) + goto fail; + } + for(j = 0; j < nb_mods; j++) { + if (reduced_mem) { + limb_to_ntt(s, buf2, fft_len, b_tab, b_len, dpl, + NB_MODS - nb_mods + j, 1); + ptr = buf2; + } else { + ptr = buf2 + fft_len * j; + } + if (ntt_conv(s, buf1 + fft_len * j, ptr, + fft_len_log2, fft_len_log2, j + NB_MODS - nb_mods)) + goto fail; + } + if (!(mul_flags & FFT_MUL_R_NORESIZE)) + bf_resize(res, 0); /* in case res == b and reduced mem */ + ntt_free(s, buf2); + buf2 = NULL; + if (!(mul_flags & FFT_MUL_R_NORESIZE)) { + if (bf_resize(res, len)) + goto fail; + } + ntt_to_limb(s, res->tab, len, buf1, fft_len_log2, dpl, nb_mods); + ntt_free(s, buf1); +#if defined(USE_MUL_CHECK) + hr = mp_mod1(res->tab, len, BF_CHKSUM_MOD, 0); + h_ref = mul_mod(ha, hb, BF_CHKSUM_MOD); + if (hr != h_ref) { + printf("ntt_mul_error: len=%" PRId_LIMB " fft_len_log2=%d dpl=%d nb_mods=%d\n", + len, fft_len_log2, dpl, nb_mods); + // printf("ha=0x" FMT_LIMB" hb=0x" FMT_LIMB " hr=0x" FMT_LIMB " expected=0x" FMT_LIMB "\n", ha, hb, hr, h_ref); + exit(1); + } +#endif + return 0; + fail: + ntt_free(s, buf1); + ntt_free(s, buf2); + return -1; +} + +#else /* USE_FFT_MUL */ + +int bf_get_fft_size(int *pdpl, int *pnb_mods, limb_t len) +{ + return 0; +} + +#endif /* !USE_FFT_MUL */ diff --git a/source/engine/thirdparty/quickjs/libbf.h b/source/engine/thirdparty/quickjs/libbf.h new file mode 100644 index 0000000..a1436ab --- /dev/null +++ b/source/engine/thirdparty/quickjs/libbf.h @@ -0,0 +1,535 @@ +/* + * Tiny arbitrary precision floating point library + * + * Copyright (c) 2017-2021 Fabrice Bellard + * + * 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. + */ +#ifndef LIBBF_H +#define LIBBF_H + +#include +#include + +#if defined(__SIZEOF_INT128__) && (INTPTR_MAX >= INT64_MAX) +#define LIMB_LOG2_BITS 6 +#else +#define LIMB_LOG2_BITS 5 +#endif + +#define LIMB_BITS (1 << LIMB_LOG2_BITS) + +#if LIMB_BITS == 64 +typedef __int128 int128_t; +typedef unsigned __int128 uint128_t; +typedef int64_t slimb_t; +typedef uint64_t limb_t; +typedef uint128_t dlimb_t; +#define BF_RAW_EXP_MIN INT64_MIN +#define BF_RAW_EXP_MAX INT64_MAX + +#define LIMB_DIGITS 19 +#define BF_DEC_BASE UINT64_C(10000000000000000000) + +#else + +typedef int32_t slimb_t; +typedef uint32_t limb_t; +typedef uint64_t dlimb_t; +#define BF_RAW_EXP_MIN INT32_MIN +#define BF_RAW_EXP_MAX INT32_MAX + +#define LIMB_DIGITS 9 +#define BF_DEC_BASE 1000000000U + +#endif + +/* in bits */ +/* minimum number of bits for the exponent */ +#define BF_EXP_BITS_MIN 3 +/* maximum number of bits for the exponent */ +#define BF_EXP_BITS_MAX (LIMB_BITS - 3) +/* extended range for exponent, used internally */ +#define BF_EXT_EXP_BITS_MAX (BF_EXP_BITS_MAX + 1) +/* minimum possible precision */ +#define BF_PREC_MIN 2 +/* minimum possible precision */ +#define BF_PREC_MAX (((limb_t)1 << (LIMB_BITS - 2)) - 2) +/* some operations support infinite precision */ +#define BF_PREC_INF (BF_PREC_MAX + 1) /* infinite precision */ + +#if LIMB_BITS == 64 +#define BF_CHKSUM_MOD (UINT64_C(975620677) * UINT64_C(9795002197)) +#else +#define BF_CHKSUM_MOD 975620677U +#endif + +#define BF_EXP_ZERO BF_RAW_EXP_MIN +#define BF_EXP_INF (BF_RAW_EXP_MAX - 1) +#define BF_EXP_NAN BF_RAW_EXP_MAX + +/* +/-zero is represented with expn = BF_EXP_ZERO and len = 0, + +/-infinity is represented with expn = BF_EXP_INF and len = 0, + NaN is represented with expn = BF_EXP_NAN and len = 0 (sign is ignored) + */ +typedef struct { + struct bf_context_t *ctx; + int sign; + slimb_t expn; + limb_t len; + limb_t *tab; +} bf_t; + +typedef struct { + /* must be kept identical to bf_t */ + struct bf_context_t *ctx; + int sign; + slimb_t expn; + limb_t len; + limb_t *tab; +} bfdec_t; + +typedef enum { + BF_RNDN, /* round to nearest, ties to even */ + BF_RNDZ, /* round to zero */ + BF_RNDD, /* round to -inf (the code relies on (BF_RNDD xor BF_RNDU) = 1) */ + BF_RNDU, /* round to +inf */ + BF_RNDNA, /* round to nearest, ties away from zero */ + BF_RNDA, /* round away from zero */ + BF_RNDF, /* faithful rounding (nondeterministic, either RNDD or RNDU, + inexact flag is always set) */ +} bf_rnd_t; + +/* allow subnormal numbers. Only available if the number of exponent + bits is <= BF_EXP_BITS_USER_MAX and prec != BF_PREC_INF. */ +#define BF_FLAG_SUBNORMAL (1 << 3) +/* 'prec' is the precision after the radix point instead of the whole + mantissa. Can only be used with bf_round() and + bfdec_[add|sub|mul|div|sqrt|round](). */ +#define BF_FLAG_RADPNT_PREC (1 << 4) + +#define BF_RND_MASK 0x7 +#define BF_EXP_BITS_SHIFT 5 +#define BF_EXP_BITS_MASK 0x3f + +/* shortcut for bf_set_exp_bits(BF_EXT_EXP_BITS_MAX) */ +#define BF_FLAG_EXT_EXP (BF_EXP_BITS_MASK << BF_EXP_BITS_SHIFT) + +/* contains the rounding mode and number of exponents bits */ +typedef uint32_t bf_flags_t; + +typedef void *bf_realloc_func_t(void *opaque, void *ptr, size_t size); + +typedef struct { + bf_t val; + limb_t prec; +} BFConstCache; + +typedef struct bf_context_t { + void *realloc_opaque; + bf_realloc_func_t *realloc_func; + BFConstCache log2_cache; + BFConstCache pi_cache; + struct BFNTTState *ntt_state; +} bf_context_t; + +static inline int bf_get_exp_bits(bf_flags_t flags) +{ + int e; + e = (flags >> BF_EXP_BITS_SHIFT) & BF_EXP_BITS_MASK; + if (e == BF_EXP_BITS_MASK) + return BF_EXP_BITS_MAX + 1; + else + return BF_EXP_BITS_MAX - e; +} + +static inline bf_flags_t bf_set_exp_bits(int n) +{ + return ((BF_EXP_BITS_MAX - n) & BF_EXP_BITS_MASK) << BF_EXP_BITS_SHIFT; +} + +/* returned status */ +#define BF_ST_INVALID_OP (1 << 0) +#define BF_ST_DIVIDE_ZERO (1 << 1) +#define BF_ST_OVERFLOW (1 << 2) +#define BF_ST_UNDERFLOW (1 << 3) +#define BF_ST_INEXACT (1 << 4) +/* indicate that a memory allocation error occured. NaN is returned */ +#define BF_ST_MEM_ERROR (1 << 5) + +#define BF_RADIX_MAX 36 /* maximum radix for bf_atof() and bf_ftoa() */ + +static inline slimb_t bf_max(slimb_t a, slimb_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline slimb_t bf_min(slimb_t a, slimb_t b) +{ + if (a < b) + return a; + else + return b; +} + +void bf_context_init(bf_context_t *s, bf_realloc_func_t *realloc_func, + void *realloc_opaque); +void bf_context_end(bf_context_t *s); +/* free memory allocated for the bf cache data */ +void bf_clear_cache(bf_context_t *s); + +static inline void *bf_realloc(bf_context_t *s, void *ptr, size_t size) +{ + return s->realloc_func(s->realloc_opaque, ptr, size); +} + +/* 'size' must be != 0 */ +static inline void *bf_malloc(bf_context_t *s, size_t size) +{ + return bf_realloc(s, NULL, size); +} + +static inline void bf_free(bf_context_t *s, void *ptr) +{ + /* must test ptr otherwise equivalent to malloc(0) */ + if (ptr) + bf_realloc(s, ptr, 0); +} + +void bf_init(bf_context_t *s, bf_t *r); + +static inline void bf_delete(bf_t *r) +{ + bf_context_t *s = r->ctx; + /* we accept to delete a zeroed bf_t structure */ + if (s && r->tab) { + bf_realloc(s, r->tab, 0); + } +} + +static inline void bf_neg(bf_t *r) +{ + r->sign ^= 1; +} + +static inline int bf_is_finite(const bf_t *a) +{ + return (a->expn < BF_EXP_INF); +} + +static inline int bf_is_nan(const bf_t *a) +{ + return (a->expn == BF_EXP_NAN); +} + +static inline int bf_is_zero(const bf_t *a) +{ + return (a->expn == BF_EXP_ZERO); +} + +static inline void bf_memcpy(bf_t *r, const bf_t *a) +{ + *r = *a; +} + +int bf_set_ui(bf_t *r, uint64_t a); +int bf_set_si(bf_t *r, int64_t a); +void bf_set_nan(bf_t *r); +void bf_set_zero(bf_t *r, int is_neg); +void bf_set_inf(bf_t *r, int is_neg); +int bf_set(bf_t *r, const bf_t *a); +void bf_move(bf_t *r, bf_t *a); +int bf_get_float64(const bf_t *a, double *pres, bf_rnd_t rnd_mode); +int bf_set_float64(bf_t *a, double d); + +int bf_cmpu(const bf_t *a, const bf_t *b); +int bf_cmp_full(const bf_t *a, const bf_t *b); +int bf_cmp(const bf_t *a, const bf_t *b); +static inline int bf_cmp_eq(const bf_t *a, const bf_t *b) +{ + return bf_cmp(a, b) == 0; +} + +static inline int bf_cmp_le(const bf_t *a, const bf_t *b) +{ + return bf_cmp(a, b) <= 0; +} + +static inline int bf_cmp_lt(const bf_t *a, const bf_t *b) +{ + return bf_cmp(a, b) < 0; +} + +int bf_add(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags); +int bf_sub(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags); +int bf_add_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, bf_flags_t flags); +int bf_mul(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags); +int bf_mul_ui(bf_t *r, const bf_t *a, uint64_t b1, limb_t prec, bf_flags_t flags); +int bf_mul_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, + bf_flags_t flags); +int bf_mul_2exp(bf_t *r, slimb_t e, limb_t prec, bf_flags_t flags); +int bf_div(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags); +#define BF_DIVREM_EUCLIDIAN BF_RNDF +int bf_divrem(bf_t *q, bf_t *r, const bf_t *a, const bf_t *b, + limb_t prec, bf_flags_t flags, int rnd_mode); +int bf_rem(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags, int rnd_mode); +int bf_remquo(slimb_t *pq, bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags, int rnd_mode); +/* round to integer with infinite precision */ +int bf_rint(bf_t *r, int rnd_mode); +int bf_round(bf_t *r, limb_t prec, bf_flags_t flags); +int bf_sqrtrem(bf_t *r, bf_t *rem1, const bf_t *a); +int bf_sqrt(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +slimb_t bf_get_exp_min(const bf_t *a); +int bf_logic_or(bf_t *r, const bf_t *a, const bf_t *b); +int bf_logic_xor(bf_t *r, const bf_t *a, const bf_t *b); +int bf_logic_and(bf_t *r, const bf_t *a, const bf_t *b); + +/* additional flags for bf_atof */ +/* do not accept hex radix prefix (0x or 0X) if radix = 0 or radix = 16 */ +#define BF_ATOF_NO_HEX (1 << 16) +/* accept binary (0b or 0B) or octal (0o or 0O) radix prefix if radix = 0 */ +#define BF_ATOF_BIN_OCT (1 << 17) +/* Do not parse NaN or Inf */ +#define BF_ATOF_NO_NAN_INF (1 << 18) +/* return the exponent separately */ +#define BF_ATOF_EXPONENT (1 << 19) + +int bf_atof(bf_t *a, const char *str, const char **pnext, int radix, + limb_t prec, bf_flags_t flags); +/* this version accepts prec = BF_PREC_INF and returns the radix + exponent */ +int bf_atof2(bf_t *r, slimb_t *pexponent, + const char *str, const char **pnext, int radix, + limb_t prec, bf_flags_t flags); +int bf_mul_pow_radix(bf_t *r, const bf_t *T, limb_t radix, + slimb_t expn, limb_t prec, bf_flags_t flags); + + +/* Conversion of floating point number to string. Return a null + terminated string or NULL if memory error. *plen contains its + length if plen != NULL. The exponent letter is "e" for base 10, + "p" for bases 2, 8, 16 with a binary exponent and "@" for the other + bases. */ + +#define BF_FTOA_FORMAT_MASK (3 << 16) + +/* fixed format: prec significant digits rounded with (flags & + BF_RND_MASK). Exponential notation is used if too many zeros are + needed.*/ +#define BF_FTOA_FORMAT_FIXED (0 << 16) +/* fractional format: prec digits after the decimal point rounded with + (flags & BF_RND_MASK) */ +#define BF_FTOA_FORMAT_FRAC (1 << 16) +/* free format: + + For binary radices with bf_ftoa() and for bfdec_ftoa(): use the minimum + number of digits to represent 'a'. The precision and the rounding + mode are ignored. + + For the non binary radices with bf_ftoa(): use as many digits as + necessary so that bf_atof() return the same number when using + precision 'prec', rounding to nearest and the subnormal + configuration of 'flags'. The result is meaningful only if 'a' is + already rounded to 'prec' bits. If the subnormal flag is set, the + exponent in 'flags' must also be set to the desired exponent range. +*/ +#define BF_FTOA_FORMAT_FREE (2 << 16) +/* same as BF_FTOA_FORMAT_FREE but uses the minimum number of digits + (takes more computation time). Identical to BF_FTOA_FORMAT_FREE for + binary radices with bf_ftoa() and for bfdec_ftoa(). */ +#define BF_FTOA_FORMAT_FREE_MIN (3 << 16) + +/* force exponential notation for fixed or free format */ +#define BF_FTOA_FORCE_EXP (1 << 20) +/* add 0x prefix for base 16, 0o prefix for base 8 or 0b prefix for + base 2 if non zero value */ +#define BF_FTOA_ADD_PREFIX (1 << 21) +/* return "Infinity" instead of "Inf" and add a "+" for positive + exponents */ +#define BF_FTOA_JS_QUIRKS (1 << 22) + +char *bf_ftoa(size_t *plen, const bf_t *a, int radix, limb_t prec, + bf_flags_t flags); + +/* modulo 2^n instead of saturation. NaN and infinity return 0 */ +#define BF_GET_INT_MOD (1 << 0) +int bf_get_int32(int *pres, const bf_t *a, int flags); +int bf_get_int64(int64_t *pres, const bf_t *a, int flags); +int bf_get_uint64(uint64_t *pres, const bf_t *a); + +/* the following functions are exported for testing only. */ +void mp_print_str(const char *str, const limb_t *tab, limb_t n); +void bf_print_str(const char *str, const bf_t *a); +int bf_resize(bf_t *r, limb_t len); +int bf_get_fft_size(int *pdpl, int *pnb_mods, limb_t len); +int bf_normalize_and_round(bf_t *r, limb_t prec1, bf_flags_t flags); +int bf_can_round(const bf_t *a, slimb_t prec, bf_rnd_t rnd_mode, slimb_t k); +slimb_t bf_mul_log2_radix(slimb_t a1, unsigned int radix, int is_inv, + int is_ceil1); +int mp_mul(bf_context_t *s, limb_t *result, + const limb_t *op1, limb_t op1_size, + const limb_t *op2, limb_t op2_size); +limb_t mp_add(limb_t *res, const limb_t *op1, const limb_t *op2, + limb_t n, limb_t carry); +limb_t mp_add_ui(limb_t *tab, limb_t b, size_t n); +int mp_sqrtrem(bf_context_t *s, limb_t *tabs, limb_t *taba, limb_t n); +int mp_recip(bf_context_t *s, limb_t *tabr, const limb_t *taba, limb_t n); +limb_t bf_isqrt(limb_t a); + +/* transcendental functions */ +int bf_const_log2(bf_t *T, limb_t prec, bf_flags_t flags); +int bf_const_pi(bf_t *T, limb_t prec, bf_flags_t flags); +int bf_exp(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_log(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +#define BF_POW_JS_QUIRKS (1 << 16) /* (+/-1)^(+/-Inf) = NaN, 1^NaN = NaN */ +int bf_pow(bf_t *r, const bf_t *x, const bf_t *y, limb_t prec, bf_flags_t flags); +int bf_cos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_sin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_tan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_atan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_atan2(bf_t *r, const bf_t *y, const bf_t *x, + limb_t prec, bf_flags_t flags); +int bf_asin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_acos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); + +/* decimal floating point */ + +static inline void bfdec_init(bf_context_t *s, bfdec_t *r) +{ + bf_init(s, (bf_t *)r); +} +static inline void bfdec_delete(bfdec_t *r) +{ + bf_delete((bf_t *)r); +} + +static inline void bfdec_neg(bfdec_t *r) +{ + r->sign ^= 1; +} + +static inline int bfdec_is_finite(const bfdec_t *a) +{ + return (a->expn < BF_EXP_INF); +} + +static inline int bfdec_is_nan(const bfdec_t *a) +{ + return (a->expn == BF_EXP_NAN); +} + +static inline int bfdec_is_zero(const bfdec_t *a) +{ + return (a->expn == BF_EXP_ZERO); +} + +static inline void bfdec_memcpy(bfdec_t *r, const bfdec_t *a) +{ + bf_memcpy((bf_t *)r, (const bf_t *)a); +} + +int bfdec_set_ui(bfdec_t *r, uint64_t a); +int bfdec_set_si(bfdec_t *r, int64_t a); + +static inline void bfdec_set_nan(bfdec_t *r) +{ + bf_set_nan((bf_t *)r); +} +static inline void bfdec_set_zero(bfdec_t *r, int is_neg) +{ + bf_set_zero((bf_t *)r, is_neg); +} +static inline void bfdec_set_inf(bfdec_t *r, int is_neg) +{ + bf_set_inf((bf_t *)r, is_neg); +} +static inline int bfdec_set(bfdec_t *r, const bfdec_t *a) +{ + return bf_set((bf_t *)r, (bf_t *)a); +} +static inline void bfdec_move(bfdec_t *r, bfdec_t *a) +{ + bf_move((bf_t *)r, (bf_t *)a); +} +static inline int bfdec_cmpu(const bfdec_t *a, const bfdec_t *b) +{ + return bf_cmpu((const bf_t *)a, (const bf_t *)b); +} +static inline int bfdec_cmp_full(const bfdec_t *a, const bfdec_t *b) +{ + return bf_cmp_full((const bf_t *)a, (const bf_t *)b); +} +static inline int bfdec_cmp(const bfdec_t *a, const bfdec_t *b) +{ + return bf_cmp((const bf_t *)a, (const bf_t *)b); +} +static inline int bfdec_cmp_eq(const bfdec_t *a, const bfdec_t *b) +{ + return bfdec_cmp(a, b) == 0; +} +static inline int bfdec_cmp_le(const bfdec_t *a, const bfdec_t *b) +{ + return bfdec_cmp(a, b) <= 0; +} +static inline int bfdec_cmp_lt(const bfdec_t *a, const bfdec_t *b) +{ + return bfdec_cmp(a, b) < 0; +} + +int bfdec_add(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags); +int bfdec_sub(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags); +int bfdec_add_si(bfdec_t *r, const bfdec_t *a, int64_t b1, limb_t prec, + bf_flags_t flags); +int bfdec_mul(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags); +int bfdec_mul_si(bfdec_t *r, const bfdec_t *a, int64_t b1, limb_t prec, + bf_flags_t flags); +int bfdec_div(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags); +int bfdec_divrem(bfdec_t *q, bfdec_t *r, const bfdec_t *a, const bfdec_t *b, + limb_t prec, bf_flags_t flags, int rnd_mode); +int bfdec_rem(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags, int rnd_mode); +int bfdec_rint(bfdec_t *r, int rnd_mode); +int bfdec_sqrt(bfdec_t *r, const bfdec_t *a, limb_t prec, bf_flags_t flags); +int bfdec_round(bfdec_t *r, limb_t prec, bf_flags_t flags); +int bfdec_get_int32(int *pres, const bfdec_t *a); +int bfdec_pow_ui(bfdec_t *r, const bfdec_t *a, limb_t b); + +char *bfdec_ftoa(size_t *plen, const bfdec_t *a, limb_t prec, bf_flags_t flags); +int bfdec_atof(bfdec_t *r, const char *str, const char **pnext, + limb_t prec, bf_flags_t flags); + +/* the following functions are exported for testing only. */ +extern const limb_t mp_pow_dec[LIMB_DIGITS + 1]; +void bfdec_print_str(const char *str, const bfdec_t *a); +static inline int bfdec_resize(bfdec_t *r, limb_t len) +{ + return bf_resize((bf_t *)r, len); +} +int bfdec_normalize_and_round(bfdec_t *r, limb_t prec1, bf_flags_t flags); + +#endif /* LIBBF_H */ diff --git a/source/engine/thirdparty/quickjs/libregexp-opcode.h b/source/engine/thirdparty/quickjs/libregexp-opcode.h new file mode 100644 index 0000000..f255e09 --- /dev/null +++ b/source/engine/thirdparty/quickjs/libregexp-opcode.h @@ -0,0 +1,57 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * 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. + */ + +#ifdef DEF + +DEF(invalid, 1) /* never used */ +DEF(char, 3) +DEF(char32, 5) +DEF(dot, 1) +DEF(any, 1) /* same as dot but match any character including line terminator */ +DEF(line_start, 1) +DEF(line_end, 1) +DEF(goto, 5) +DEF(split_goto_first, 5) +DEF(split_next_first, 5) +DEF(match, 1) +DEF(save_start, 2) /* save start position */ +DEF(save_end, 2) /* save end position, must come after saved_start */ +DEF(save_reset, 3) /* reset save positions */ +DEF(loop, 5) /* decrement the top the stack and goto if != 0 */ +DEF(push_i32, 5) /* push integer on the stack */ +DEF(drop, 1) +DEF(word_boundary, 1) +DEF(not_word_boundary, 1) +DEF(back_reference, 2) +DEF(backward_back_reference, 2) /* must come after back_reference */ +DEF(range, 3) /* variable length */ +DEF(range32, 3) /* variable length */ +DEF(lookahead, 5) +DEF(negative_lookahead, 5) +DEF(push_char_pos, 1) /* push the character position on the stack */ +DEF(check_advance, 1) /* pop one stack element and check that it is different from the character position */ +DEF(prev, 1) /* go to the previous char */ +DEF(simple_greedy_quant, 17) + +#endif /* DEF */ diff --git a/source/engine/thirdparty/quickjs/libregexp.c b/source/engine/thirdparty/quickjs/libregexp.c new file mode 100644 index 0000000..d73a19f --- /dev/null +++ b/source/engine/thirdparty/quickjs/libregexp.c @@ -0,0 +1,2528 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * 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. + */ +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "libregexp.h" + +/* + TODO: + + - Add a lock step execution mode (=linear time execution guaranteed) + when the regular expression is "simple" i.e. no backreference nor + complicated lookahead. The opcodes are designed for this execution + model. +*/ + +#if defined(TEST) +#define DUMP_REOP +#endif + +typedef enum { +#define DEF(id, size) REOP_ ## id, +#include "libregexp-opcode.h" +#undef DEF + REOP_COUNT, +} REOPCodeEnum; + +#define CAPTURE_COUNT_MAX 255 +#define STACK_SIZE_MAX 255 + +/* unicode code points */ +#define CP_LS 0x2028 +#define CP_PS 0x2029 + +#define TMP_BUF_SIZE 128 + +typedef struct { + DynBuf byte_code; + const uint8_t *buf_ptr; + const uint8_t *buf_end; + const uint8_t *buf_start; + int re_flags; + BOOL is_unicode; + BOOL ignore_case; + BOOL dotall; + int capture_count; + int total_capture_count; /* -1 = not computed yet */ + int has_named_captures; /* -1 = don't know, 0 = no, 1 = yes */ + void *opaque; + DynBuf group_names; + union { + char error_msg[TMP_BUF_SIZE]; + char tmp_buf[TMP_BUF_SIZE]; + } u; +} REParseState; + +typedef struct { +#ifdef DUMP_REOP + const char *name; +#endif + uint8_t size; +} REOpCode; + +static const REOpCode reopcode_info[REOP_COUNT] = { +#ifdef DUMP_REOP +#define DEF(id, size) { #id, size }, +#else +#define DEF(id, size) { size }, +#endif +#include "libregexp-opcode.h" +#undef DEF +}; + +#define RE_HEADER_FLAGS 0 +#define RE_HEADER_CAPTURE_COUNT 1 +#define RE_HEADER_STACK_SIZE 2 +#define RE_HEADER_BYTECODE_LEN 3 + +#define RE_HEADER_LEN 7 + +static inline int is_digit(int c) { + return c >= '0' && c <= '9'; +} + +/* insert 'len' bytes at position 'pos'. Return < 0 if error. */ +static int dbuf_insert(DynBuf *s, int pos, int len) +{ + if (dbuf_realloc(s, s->size + len)) + return -1; + memmove(s->buf + pos + len, s->buf + pos, s->size - pos); + s->size += len; + return 0; +} + +static const uint16_t char_range_d[] = { + 1, + 0x0030, 0x0039 + 1, +}; + +/* code point ranges for Zs,Zl or Zp property */ +static const uint16_t char_range_s[] = { + 10, + 0x0009, 0x000D + 1, + 0x0020, 0x0020 + 1, + 0x00A0, 0x00A0 + 1, + 0x1680, 0x1680 + 1, + 0x2000, 0x200A + 1, + /* 2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;; */ + /* 2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;; */ + 0x2028, 0x2029 + 1, + 0x202F, 0x202F + 1, + 0x205F, 0x205F + 1, + 0x3000, 0x3000 + 1, + /* FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; */ + 0xFEFF, 0xFEFF + 1, +}; + +BOOL lre_is_space(int c) +{ + int i, n, low, high; + n = (countof(char_range_s) - 1) / 2; + for(i = 0; i < n; i++) { + low = char_range_s[2 * i + 1]; + if (c < low) + return FALSE; + high = char_range_s[2 * i + 2]; + if (c < high) + return TRUE; + } + return FALSE; +} + +uint32_t const lre_id_start_table_ascii[4] = { + /* $ A-Z _ a-z */ + 0x00000000, 0x00000010, 0x87FFFFFE, 0x07FFFFFE +}; + +uint32_t const lre_id_continue_table_ascii[4] = { + /* $ 0-9 A-Z _ a-z */ + 0x00000000, 0x03FF0010, 0x87FFFFFE, 0x07FFFFFE +}; + + +static const uint16_t char_range_w[] = { + 4, + 0x0030, 0x0039 + 1, + 0x0041, 0x005A + 1, + 0x005F, 0x005F + 1, + 0x0061, 0x007A + 1, +}; + +#define CLASS_RANGE_BASE 0x40000000 + +typedef enum { + CHAR_RANGE_d, + CHAR_RANGE_D, + CHAR_RANGE_s, + CHAR_RANGE_S, + CHAR_RANGE_w, + CHAR_RANGE_W, +} CharRangeEnum; + +static const uint16_t *char_range_table[] = { + char_range_d, + char_range_s, + char_range_w, +}; + +static int cr_init_char_range(REParseState *s, CharRange *cr, uint32_t c) +{ + BOOL invert; + const uint16_t *c_pt; + int len, i; + + invert = c & 1; + c_pt = char_range_table[c >> 1]; + len = *c_pt++; + cr_init(cr, s->opaque, lre_realloc); + for(i = 0; i < len * 2; i++) { + if (cr_add_point(cr, c_pt[i])) + goto fail; + } + if (invert) { + if (cr_invert(cr)) + goto fail; + } + return 0; + fail: + cr_free(cr); + return -1; +} + +#ifdef DUMP_REOP +static __maybe_unused void lre_dump_bytecode(const uint8_t *buf, + int buf_len) +{ + int pos, len, opcode, bc_len, re_flags, i; + uint32_t val; + + assert(buf_len >= RE_HEADER_LEN); + + re_flags = lre_get_flags(buf); + bc_len = get_u32(buf + RE_HEADER_BYTECODE_LEN); + assert(bc_len + RE_HEADER_LEN <= buf_len); + printf("flags: 0x%x capture_count=%d stack_size=%d\n", + re_flags, buf[RE_HEADER_CAPTURE_COUNT], buf[RE_HEADER_STACK_SIZE]); + if (re_flags & LRE_FLAG_NAMED_GROUPS) { + const char *p; + p = (char *)buf + RE_HEADER_LEN + bc_len; + printf("named groups: "); + for(i = 1; i < buf[RE_HEADER_CAPTURE_COUNT]; i++) { + if (i != 1) + printf(","); + printf("<%s>", p); + p += strlen(p) + 1; + } + printf("\n"); + assert(p == (char *)(buf + buf_len)); + } + printf("bytecode_len=%d\n", bc_len); + + buf += RE_HEADER_LEN; + pos = 0; + while (pos < bc_len) { + printf("%5u: ", pos); + opcode = buf[pos]; + len = reopcode_info[opcode].size; + if (opcode >= REOP_COUNT) { + printf(" invalid opcode=0x%02x\n", opcode); + break; + } + if ((pos + len) > bc_len) { + printf(" buffer overflow (opcode=0x%02x)\n", opcode); + break; + } + printf("%s", reopcode_info[opcode].name); + switch(opcode) { + case REOP_char: + val = get_u16(buf + pos + 1); + if (val >= ' ' && val <= 126) + printf(" '%c'", val); + else + printf(" 0x%04x", val); + break; + case REOP_char32: + val = get_u32(buf + pos + 1); + if (val >= ' ' && val <= 126) + printf(" '%c'", val); + else + printf(" 0x%08x", val); + break; + case REOP_goto: + case REOP_split_goto_first: + case REOP_split_next_first: + case REOP_loop: + case REOP_lookahead: + case REOP_negative_lookahead: + val = get_u32(buf + pos + 1); + val += (pos + 5); + printf(" %u", val); + break; + case REOP_simple_greedy_quant: + printf(" %u %u %u %u", + get_u32(buf + pos + 1) + (pos + 17), + get_u32(buf + pos + 1 + 4), + get_u32(buf + pos + 1 + 8), + get_u32(buf + pos + 1 + 12)); + break; + case REOP_save_start: + case REOP_save_end: + case REOP_back_reference: + case REOP_backward_back_reference: + printf(" %u", buf[pos + 1]); + break; + case REOP_save_reset: + printf(" %u %u", buf[pos + 1], buf[pos + 2]); + break; + case REOP_push_i32: + val = get_u32(buf + pos + 1); + printf(" %d", val); + break; + case REOP_range: + { + int n, i; + n = get_u16(buf + pos + 1); + len += n * 4; + for(i = 0; i < n * 2; i++) { + val = get_u16(buf + pos + 3 + i * 2); + printf(" 0x%04x", val); + } + } + break; + case REOP_range32: + { + int n, i; + n = get_u16(buf + pos + 1); + len += n * 8; + for(i = 0; i < n * 2; i++) { + val = get_u32(buf + pos + 3 + i * 4); + printf(" 0x%08x", val); + } + } + break; + default: + break; + } + printf("\n"); + pos += len; + } +} +#endif + +static void re_emit_op(REParseState *s, int op) +{ + dbuf_putc(&s->byte_code, op); +} + +/* return the offset of the u32 value */ +static int re_emit_op_u32(REParseState *s, int op, uint32_t val) +{ + int pos; + dbuf_putc(&s->byte_code, op); + pos = s->byte_code.size; + dbuf_put_u32(&s->byte_code, val); + return pos; +} + +static int re_emit_goto(REParseState *s, int op, uint32_t val) +{ + int pos; + dbuf_putc(&s->byte_code, op); + pos = s->byte_code.size; + dbuf_put_u32(&s->byte_code, val - (pos + 4)); + return pos; +} + +static void re_emit_op_u8(REParseState *s, int op, uint32_t val) +{ + dbuf_putc(&s->byte_code, op); + dbuf_putc(&s->byte_code, val); +} + +static void re_emit_op_u16(REParseState *s, int op, uint32_t val) +{ + dbuf_putc(&s->byte_code, op); + dbuf_put_u16(&s->byte_code, val); +} + +static int __attribute__((format(printf, 2, 3))) re_parse_error(REParseState *s, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsnprintf(s->u.error_msg, sizeof(s->u.error_msg), fmt, ap); + va_end(ap); + return -1; +} + +static int re_parse_out_of_memory(REParseState *s) +{ + return re_parse_error(s, "out of memory"); +} + +/* If allow_overflow is false, return -1 in case of + overflow. Otherwise return INT32_MAX. */ +static int parse_digits(const uint8_t **pp, BOOL allow_overflow) +{ + const uint8_t *p; + uint64_t v; + int c; + + p = *pp; + v = 0; + for(;;) { + c = *p; + if (c < '0' || c > '9') + break; + v = v * 10 + c - '0'; + if (v >= INT32_MAX) { + if (allow_overflow) + v = INT32_MAX; + else + return -1; + } + p++; + } + *pp = p; + return v; +} + +static int re_parse_expect(REParseState *s, const uint8_t **pp, int c) +{ + const uint8_t *p; + p = *pp; + if (*p != c) + return re_parse_error(s, "expecting '%c'", c); + p++; + *pp = p; + return 0; +} + +/* Parse an escape sequence, *pp points after the '\': + allow_utf16 value: + 0 : no UTF-16 escapes allowed + 1 : UTF-16 escapes allowed + 2 : UTF-16 escapes allowed and escapes of surrogate pairs are + converted to a unicode character (unicode regexp case). + + Return the unicode char and update *pp if recognized, + return -1 if malformed escape, + return -2 otherwise. */ +int lre_parse_escape(const uint8_t **pp, int allow_utf16) +{ + const uint8_t *p; + uint32_t c; + + p = *pp; + c = *p++; + switch(c) { + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + case 'x': + case 'u': + { + int h, n, i; + uint32_t c1; + + if (*p == '{' && allow_utf16) { + p++; + c = 0; + for(;;) { + h = from_hex(*p++); + if (h < 0) + return -1; + c = (c << 4) | h; + if (c > 0x10FFFF) + return -1; + if (*p == '}') + break; + } + p++; + } else { + if (c == 'x') { + n = 2; + } else { + n = 4; + } + + c = 0; + for(i = 0; i < n; i++) { + h = from_hex(*p++); + if (h < 0) { + return -1; + } + c = (c << 4) | h; + } + if (is_hi_surrogate(c) && + allow_utf16 == 2 && p[0] == '\\' && p[1] == 'u') { + /* convert an escaped surrogate pair into a + unicode char */ + c1 = 0; + for(i = 0; i < 4; i++) { + h = from_hex(p[2 + i]); + if (h < 0) + break; + c1 = (c1 << 4) | h; + } + if (i == 4 && is_lo_surrogate(c1)) { + p += 6; + c = from_surrogate(c, c1); + } + } + } + } + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c -= '0'; + if (allow_utf16 == 2) { + /* only accept \0 not followed by digit */ + if (c != 0 || is_digit(*p)) + return -1; + } else { + /* parse a legacy octal sequence */ + uint32_t v; + v = *p - '0'; + if (v > 7) + break; + c = (c << 3) | v; + p++; + if (c >= 32) + break; + v = *p - '0'; + if (v > 7) + break; + c = (c << 3) | v; + p++; + } + break; + default: + return -2; + } + *pp = p; + return c; +} + +#ifdef CONFIG_ALL_UNICODE +/* XXX: we use the same chars for name and value */ +static BOOL is_unicode_char(int c) +{ + return ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c == '_')); +} + +static int parse_unicode_property(REParseState *s, CharRange *cr, + const uint8_t **pp, BOOL is_inv) +{ + const uint8_t *p; + char name[64], value[64]; + char *q; + BOOL script_ext; + int ret; + + p = *pp; + if (*p != '{') + return re_parse_error(s, "expecting '{' after \\p"); + p++; + q = name; + while (is_unicode_char(*p)) { + if ((q - name) >= sizeof(name) - 1) + goto unknown_property_name; + *q++ = *p++; + } + *q = '\0'; + q = value; + if (*p == '=') { + p++; + while (is_unicode_char(*p)) { + if ((q - value) >= sizeof(value) - 1) + return re_parse_error(s, "unknown unicode property value"); + *q++ = *p++; + } + } + *q = '\0'; + if (*p != '}') + return re_parse_error(s, "expecting '}'"); + p++; + // printf("name=%s value=%s\n", name, value); + + if (!strcmp(name, "Script") || !strcmp(name, "sc")) { + script_ext = FALSE; + goto do_script; + } else if (!strcmp(name, "Script_Extensions") || !strcmp(name, "scx")) { + script_ext = TRUE; + do_script: + cr_init(cr, s->opaque, lre_realloc); + ret = unicode_script(cr, value, script_ext); + if (ret) { + cr_free(cr); + if (ret == -2) + return re_parse_error(s, "unknown unicode script"); + else + goto out_of_memory; + } + } else if (!strcmp(name, "General_Category") || !strcmp(name, "gc")) { + cr_init(cr, s->opaque, lre_realloc); + ret = unicode_general_category(cr, value); + if (ret) { + cr_free(cr); + if (ret == -2) + return re_parse_error(s, "unknown unicode general category"); + else + goto out_of_memory; + } + } else if (value[0] == '\0') { + cr_init(cr, s->opaque, lre_realloc); + ret = unicode_general_category(cr, name); + if (ret == -1) { + cr_free(cr); + goto out_of_memory; + } + if (ret < 0) { + ret = unicode_prop(cr, name); + if (ret) { + cr_free(cr); + if (ret == -2) + goto unknown_property_name; + else + goto out_of_memory; + } + } + } else { + unknown_property_name: + return re_parse_error(s, "unknown unicode property name"); + } + + if (is_inv) { + if (cr_invert(cr)) { + cr_free(cr); + return -1; + } + } + *pp = p; + return 0; + out_of_memory: + return re_parse_out_of_memory(s); +} +#endif /* CONFIG_ALL_UNICODE */ + +/* return -1 if error otherwise the character or a class range + (CLASS_RANGE_BASE). In case of class range, 'cr' is + initialized. Otherwise, it is ignored. */ +static int get_class_atom(REParseState *s, CharRange *cr, + const uint8_t **pp, BOOL inclass) +{ + const uint8_t *p; + uint32_t c; + int ret; + + p = *pp; + + c = *p; + switch(c) { + case '\\': + p++; + if (p >= s->buf_end) + goto unexpected_end; + c = *p++; + switch(c) { + case 'd': + c = CHAR_RANGE_d; + goto class_range; + case 'D': + c = CHAR_RANGE_D; + goto class_range; + case 's': + c = CHAR_RANGE_s; + goto class_range; + case 'S': + c = CHAR_RANGE_S; + goto class_range; + case 'w': + c = CHAR_RANGE_w; + goto class_range; + case 'W': + c = CHAR_RANGE_W; + class_range: + if (cr_init_char_range(s, cr, c)) + return -1; + c = CLASS_RANGE_BASE; + break; + case 'c': + c = *p; + if ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (((c >= '0' && c <= '9') || c == '_') && + inclass && !s->is_unicode)) { /* Annex B.1.4 */ + c &= 0x1f; + p++; + } else if (s->is_unicode) { + goto invalid_escape; + } else { + /* otherwise return '\' and 'c' */ + p--; + c = '\\'; + } + break; +#ifdef CONFIG_ALL_UNICODE + case 'p': + case 'P': + if (s->is_unicode) { + if (parse_unicode_property(s, cr, &p, (c == 'P'))) + return -1; + c = CLASS_RANGE_BASE; + break; + } + /* fall thru */ +#endif + default: + p--; + ret = lre_parse_escape(&p, s->is_unicode * 2); + if (ret >= 0) { + c = ret; + } else { + if (ret == -2 && *p != '\0' && strchr("^$\\.*+?()[]{}|/", *p)) { + /* always valid to escape these characters */ + goto normal_char; + } else if (s->is_unicode) { + invalid_escape: + return re_parse_error(s, "invalid escape sequence in regular expression"); + } else { + /* just ignore the '\' */ + goto normal_char; + } + } + break; + } + break; + case '\0': + if (p >= s->buf_end) { + unexpected_end: + return re_parse_error(s, "unexpected end"); + } + /* fall thru */ + default: + normal_char: + /* normal char */ + if (c >= 128) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); + if ((unsigned)c > 0xffff && !s->is_unicode) { + /* XXX: should handle non BMP-1 code points */ + return re_parse_error(s, "malformed unicode char"); + } + } else { + p++; + } + break; + } + *pp = p; + return c; +} + +static int re_emit_range(REParseState *s, const CharRange *cr) +{ + int len, i; + uint32_t high; + + len = (unsigned)cr->len / 2; + if (len >= 65535) + return re_parse_error(s, "too many ranges"); + if (len == 0) { + /* not sure it can really happen. Emit a match that is always + false */ + re_emit_op_u32(s, REOP_char32, -1); + } else { + high = cr->points[cr->len - 1]; + if (high == UINT32_MAX) + high = cr->points[cr->len - 2]; + if (high <= 0xffff) { + /* can use 16 bit ranges with the conversion that 0xffff = + infinity */ + re_emit_op_u16(s, REOP_range, len); + for(i = 0; i < cr->len; i += 2) { + dbuf_put_u16(&s->byte_code, cr->points[i]); + high = cr->points[i + 1] - 1; + if (high == UINT32_MAX - 1) + high = 0xffff; + dbuf_put_u16(&s->byte_code, high); + } + } else { + re_emit_op_u16(s, REOP_range32, len); + for(i = 0; i < cr->len; i += 2) { + dbuf_put_u32(&s->byte_code, cr->points[i]); + dbuf_put_u32(&s->byte_code, cr->points[i + 1] - 1); + } + } + } + return 0; +} + +static int re_parse_char_class(REParseState *s, const uint8_t **pp) +{ + const uint8_t *p; + uint32_t c1, c2; + CharRange cr_s, *cr = &cr_s; + CharRange cr1_s, *cr1 = &cr1_s; + BOOL invert; + + cr_init(cr, s->opaque, lre_realloc); + p = *pp; + p++; /* skip '[' */ + + invert = FALSE; + if (*p == '^') { + p++; + invert = TRUE; + } + + for(;;) { + if (*p == ']') + break; + c1 = get_class_atom(s, cr1, &p, TRUE); + if ((int)c1 < 0) + goto fail; + if (*p == '-' && p[1] != ']') { + const uint8_t *p0 = p + 1; + if (c1 >= CLASS_RANGE_BASE) { + if (s->is_unicode) { + cr_free(cr1); + goto invalid_class_range; + } + /* Annex B: match '-' character */ + goto class_atom; + } + c2 = get_class_atom(s, cr1, &p0, TRUE); + if ((int)c2 < 0) + goto fail; + if (c2 >= CLASS_RANGE_BASE) { + cr_free(cr1); + if (s->is_unicode) { + goto invalid_class_range; + } + /* Annex B: match '-' character */ + goto class_atom; + } + p = p0; + if (c2 < c1) { + invalid_class_range: + re_parse_error(s, "invalid class range"); + goto fail; + } + if (cr_union_interval(cr, c1, c2)) + goto memory_error; + } else { + class_atom: + if (c1 >= CLASS_RANGE_BASE) { + int ret; + ret = cr_union1(cr, cr1->points, cr1->len); + cr_free(cr1); + if (ret) + goto memory_error; + } else { + if (cr_union_interval(cr, c1, c1)) + goto memory_error; + } + } + } + if (s->ignore_case) { + if (cr_regexp_canonicalize(cr, s->is_unicode)) + goto memory_error; + } + if (invert) { + if (cr_invert(cr)) + goto memory_error; + } + if (re_emit_range(s, cr)) + goto fail; + cr_free(cr); + p++; /* skip ']' */ + *pp = p; + return 0; + memory_error: + re_parse_out_of_memory(s); + fail: + cr_free(cr); + return -1; +} + +/* Return: + - true if the opcodes may not advance the char pointer + - false if the opcodes always advance the char pointer +*/ +static BOOL re_need_check_advance(const uint8_t *bc_buf, int bc_buf_len) +{ + int pos, opcode, len; + uint32_t val; + BOOL ret; + + ret = TRUE; + pos = 0; + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + switch(opcode) { + case REOP_range: + val = get_u16(bc_buf + pos + 1); + len += val * 4; + goto simple_char; + case REOP_range32: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + goto simple_char; + case REOP_char: + case REOP_char32: + case REOP_dot: + case REOP_any: + simple_char: + ret = FALSE; + break; + case REOP_line_start: + case REOP_line_end: + case REOP_push_i32: + case REOP_push_char_pos: + case REOP_drop: + case REOP_word_boundary: + case REOP_not_word_boundary: + case REOP_prev: + /* no effect */ + break; + case REOP_save_start: + case REOP_save_end: + case REOP_save_reset: + case REOP_back_reference: + case REOP_backward_back_reference: + break; + default: + /* safe behavior: we cannot predict the outcome */ + return TRUE; + } + pos += len; + } + return ret; +} + +/* return -1 if a simple quantifier cannot be used. Otherwise return + the number of characters in the atom. */ +static int re_is_simple_quantifier(const uint8_t *bc_buf, int bc_buf_len) +{ + int pos, opcode, len, count; + uint32_t val; + + count = 0; + pos = 0; + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + switch(opcode) { + case REOP_range: + val = get_u16(bc_buf + pos + 1); + len += val * 4; + goto simple_char; + case REOP_range32: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + goto simple_char; + case REOP_char: + case REOP_char32: + case REOP_dot: + case REOP_any: + simple_char: + count++; + break; + case REOP_line_start: + case REOP_line_end: + case REOP_word_boundary: + case REOP_not_word_boundary: + break; + default: + return -1; + } + pos += len; + } + return count; +} + +/* '*pp' is the first char after '<' */ +static int re_parse_group_name(char *buf, int buf_size, const uint8_t **pp) +{ + const uint8_t *p, *p1; + uint32_t c, d; + char *q; + + p = *pp; + q = buf; + for(;;) { + c = *p; + if (c == '\\') { + p++; + if (*p != 'u') + return -1; + c = lre_parse_escape(&p, 2); // accept surrogate pairs + } else if (c == '>') { + break; + } else if (c >= 128) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); + if (is_hi_surrogate(c)) { + d = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1); + if (is_lo_surrogate(d)) { + c = from_surrogate(c, d); + p = p1; + } + } + } else { + p++; + } + if (c > 0x10FFFF) + return -1; + if (q == buf) { + if (!lre_js_is_ident_first(c)) + return -1; + } else { + if (!lre_js_is_ident_next(c)) + return -1; + } + if ((q - buf + UTF8_CHAR_LEN_MAX + 1) > buf_size) + return -1; + if (c < 128) { + *q++ = c; + } else { + q += unicode_to_utf8((uint8_t*)q, c); + } + } + if (q == buf) + return -1; + *q = '\0'; + p++; + *pp = p; + return 0; +} + +/* if capture_name = NULL: return the number of captures + 1. + Otherwise, return the capture index corresponding to capture_name + or -1 if none */ +static int re_parse_captures(REParseState *s, int *phas_named_captures, + const char *capture_name) +{ + const uint8_t *p; + int capture_index; + char name[TMP_BUF_SIZE]; + + capture_index = 1; + *phas_named_captures = 0; + for (p = s->buf_start; p < s->buf_end; p++) { + switch (*p) { + case '(': + if (p[1] == '?') { + if (p[2] == '<' && p[3] != '=' && p[3] != '!') { + *phas_named_captures = 1; + /* potential named capture */ + if (capture_name) { + p += 3; + if (re_parse_group_name(name, sizeof(name), &p) == 0) { + if (!strcmp(name, capture_name)) + return capture_index; + } + } + capture_index++; + if (capture_index >= CAPTURE_COUNT_MAX) + goto done; + } + } else { + capture_index++; + if (capture_index >= CAPTURE_COUNT_MAX) + goto done; + } + break; + case '\\': + p++; + break; + case '[': + for (p += 1 + (*p == ']'); p < s->buf_end && *p != ']'; p++) { + if (*p == '\\') + p++; + } + break; + } + } + done: + if (capture_name) + return -1; + else + return capture_index; +} + +static int re_count_captures(REParseState *s) +{ + if (s->total_capture_count < 0) { + s->total_capture_count = re_parse_captures(s, &s->has_named_captures, + NULL); + } + return s->total_capture_count; +} + +static BOOL re_has_named_captures(REParseState *s) +{ + if (s->has_named_captures < 0) + re_count_captures(s); + return s->has_named_captures; +} + +static int find_group_name(REParseState *s, const char *name) +{ + const char *p, *buf_end; + size_t len, name_len; + int capture_index; + + p = (char *)s->group_names.buf; + if (!p) return -1; + buf_end = (char *)s->group_names.buf + s->group_names.size; + name_len = strlen(name); + capture_index = 1; + while (p < buf_end) { + len = strlen(p); + if (len == name_len && memcmp(name, p, name_len) == 0) + return capture_index; + p += len + 1; + capture_index++; + } + return -1; +} + +static int re_parse_disjunction(REParseState *s, BOOL is_backward_dir); + +static int re_parse_term(REParseState *s, BOOL is_backward_dir) +{ + const uint8_t *p; + int c, last_atom_start, quant_min, quant_max, last_capture_count; + BOOL greedy, add_zero_advance_check, is_neg, is_backward_lookahead; + CharRange cr_s, *cr = &cr_s; + + last_atom_start = -1; + last_capture_count = 0; + p = s->buf_ptr; + c = *p; + switch(c) { + case '^': + p++; + re_emit_op(s, REOP_line_start); + break; + case '$': + p++; + re_emit_op(s, REOP_line_end); + break; + case '.': + p++; + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + re_emit_op(s, s->dotall ? REOP_any : REOP_dot); + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + case '{': + if (s->is_unicode) { + return re_parse_error(s, "syntax error"); + } else if (!is_digit(p[1])) { + /* Annex B: we accept '{' not followed by digits as a + normal atom */ + goto parse_class_atom; + } else { + const uint8_t *p1 = p + 1; + /* Annex B: error if it is like a repetition count */ + parse_digits(&p1, TRUE); + if (*p1 == ',') { + p1++; + if (is_digit(*p1)) { + parse_digits(&p1, TRUE); + } + } + if (*p1 != '}') { + goto parse_class_atom; + } + } + /* fall thru */ + case '*': + case '+': + case '?': + return re_parse_error(s, "nothing to repeat"); + case '(': + if (p[1] == '?') { + if (p[2] == ':') { + p += 3; + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_dir)) + return -1; + p = s->buf_ptr; + if (re_parse_expect(s, &p, ')')) + return -1; + } else if ((p[2] == '=' || p[2] == '!')) { + is_neg = (p[2] == '!'); + is_backward_lookahead = FALSE; + p += 3; + goto lookahead; + } else if (p[2] == '<' && + (p[3] == '=' || p[3] == '!')) { + int pos; + is_neg = (p[3] == '!'); + is_backward_lookahead = TRUE; + p += 4; + /* lookahead */ + lookahead: + /* Annex B allows lookahead to be used as an atom for + the quantifiers */ + if (!s->is_unicode && !is_backward_lookahead) { + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + } + pos = re_emit_op_u32(s, REOP_lookahead + is_neg, 0); + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_lookahead)) + return -1; + p = s->buf_ptr; + if (re_parse_expect(s, &p, ')')) + return -1; + re_emit_op(s, REOP_match); + /* jump after the 'match' after the lookahead is successful */ + if (dbuf_error(&s->byte_code)) + return -1; + put_u32(s->byte_code.buf + pos, s->byte_code.size - (pos + 4)); + } else if (p[2] == '<') { + p += 3; + if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), + &p)) { + return re_parse_error(s, "invalid group name"); + } + if (find_group_name(s, s->u.tmp_buf) > 0) { + return re_parse_error(s, "duplicate group name"); + } + /* group name with a trailing zero */ + dbuf_put(&s->group_names, (uint8_t *)s->u.tmp_buf, + strlen(s->u.tmp_buf) + 1); + s->has_named_captures = 1; + goto parse_capture; + } else { + return re_parse_error(s, "invalid group"); + } + } else { + int capture_index; + p++; + /* capture without group name */ + dbuf_putc(&s->group_names, 0); + parse_capture: + if (s->capture_count >= CAPTURE_COUNT_MAX) + return re_parse_error(s, "too many captures"); + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + capture_index = s->capture_count++; + re_emit_op_u8(s, REOP_save_start + is_backward_dir, + capture_index); + + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_dir)) + return -1; + p = s->buf_ptr; + + re_emit_op_u8(s, REOP_save_start + 1 - is_backward_dir, + capture_index); + + if (re_parse_expect(s, &p, ')')) + return -1; + } + break; + case '\\': + switch(p[1]) { + case 'b': + case 'B': + re_emit_op(s, REOP_word_boundary + (p[1] != 'b')); + p += 2; + break; + case 'k': + { + const uint8_t *p1; + int dummy_res; + + p1 = p; + if (p1[2] != '<') { + /* annex B: we tolerate invalid group names in non + unicode mode if there is no named capture + definition */ + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "expecting group name"); + else + goto parse_class_atom; + } + p1 += 3; + if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), + &p1)) { + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "invalid group name"); + else + goto parse_class_atom; + } + c = find_group_name(s, s->u.tmp_buf); + if (c < 0) { + /* no capture name parsed before, try to look + after (inefficient, but hopefully not common */ + c = re_parse_captures(s, &dummy_res, s->u.tmp_buf); + if (c < 0) { + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "group name not defined"); + else + goto parse_class_atom; + } + } + p = p1; + } + goto emit_back_reference; + case '0': + p += 2; + c = 0; + if (s->is_unicode) { + if (is_digit(*p)) { + return re_parse_error(s, "invalid decimal escape in regular expression"); + } + } else { + /* Annex B.1.4: accept legacy octal */ + if (*p >= '0' && *p <= '7') { + c = *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + } + } + } + goto normal_char; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + { + const uint8_t *q = ++p; + + c = parse_digits(&p, FALSE); + if (c < 0 || (c >= s->capture_count && c >= re_count_captures(s))) { + if (!s->is_unicode) { + /* Annex B.1.4: accept legacy octal */ + p = q; + if (*p <= '7') { + c = 0; + if (*p <= '3') + c = *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + } + } + } else { + c = *p++; + } + goto normal_char; + } + return re_parse_error(s, "back reference out of range in regular expression"); + } + emit_back_reference: + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + re_emit_op_u8(s, REOP_back_reference + is_backward_dir, c); + } + break; + default: + goto parse_class_atom; + } + break; + case '[': + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + if (re_parse_char_class(s, &p)) + return -1; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + case ']': + case '}': + if (s->is_unicode) + return re_parse_error(s, "syntax error"); + goto parse_class_atom; + default: + parse_class_atom: + c = get_class_atom(s, cr, &p, FALSE); + if ((int)c < 0) + return -1; + normal_char: + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + if (c >= CLASS_RANGE_BASE) { + int ret; + /* Note: canonicalization is not needed */ + ret = re_emit_range(s, cr); + cr_free(cr); + if (ret) + return -1; + } else { + if (s->ignore_case) + c = lre_canonicalize(c, s->is_unicode); + if (c <= 0xffff) + re_emit_op_u16(s, REOP_char, c); + else + re_emit_op_u32(s, REOP_char32, c); + } + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + } + + /* quantifier */ + if (last_atom_start >= 0) { + c = *p; + switch(c) { + case '*': + p++; + quant_min = 0; + quant_max = INT32_MAX; + goto quantifier; + case '+': + p++; + quant_min = 1; + quant_max = INT32_MAX; + goto quantifier; + case '?': + p++; + quant_min = 0; + quant_max = 1; + goto quantifier; + case '{': + { + const uint8_t *p1 = p; + /* As an extension (see ES6 annex B), we accept '{' not + followed by digits as a normal atom */ + if (!is_digit(p[1])) { + if (s->is_unicode) + goto invalid_quant_count; + break; + } + p++; + quant_min = parse_digits(&p, TRUE); + quant_max = quant_min; + if (*p == ',') { + p++; + if (is_digit(*p)) { + quant_max = parse_digits(&p, TRUE); + if (quant_max < quant_min) { + invalid_quant_count: + return re_parse_error(s, "invalid repetition count"); + } + } else { + quant_max = INT32_MAX; /* infinity */ + } + } + if (*p != '}' && !s->is_unicode) { + /* Annex B: normal atom if invalid '{' syntax */ + p = p1; + break; + } + if (re_parse_expect(s, &p, '}')) + return -1; + } + quantifier: + greedy = TRUE; + if (*p == '?') { + p++; + greedy = FALSE; + } + if (last_atom_start < 0) { + return re_parse_error(s, "nothing to repeat"); + } + if (greedy) { + int len, pos; + + if (quant_max > 0) { + /* specific optimization for simple quantifiers */ + if (dbuf_error(&s->byte_code)) + goto out_of_memory; + len = re_is_simple_quantifier(s->byte_code.buf + last_atom_start, + s->byte_code.size - last_atom_start); + if (len > 0) { + re_emit_op(s, REOP_match); + + if (dbuf_insert(&s->byte_code, last_atom_start, 17)) + goto out_of_memory; + pos = last_atom_start; + s->byte_code.buf[pos++] = REOP_simple_greedy_quant; + put_u32(&s->byte_code.buf[pos], + s->byte_code.size - last_atom_start - 17); + pos += 4; + put_u32(&s->byte_code.buf[pos], quant_min); + pos += 4; + put_u32(&s->byte_code.buf[pos], quant_max); + pos += 4; + put_u32(&s->byte_code.buf[pos], len); + pos += 4; + goto done; + } + } + + if (dbuf_error(&s->byte_code)) + goto out_of_memory; + /* the spec tells that if there is no advance when + running the atom after the first quant_min times, + then there is no match. We remove this test when we + are sure the atom always advances the position. */ + add_zero_advance_check = re_need_check_advance(s->byte_code.buf + last_atom_start, + s->byte_code.size - last_atom_start); + } else { + add_zero_advance_check = FALSE; + } + + { + int len, pos; + len = s->byte_code.size - last_atom_start; + if (quant_min == 0) { + /* need to reset the capture in case the atom is + not executed */ + if (last_capture_count != s->capture_count) { + if (dbuf_insert(&s->byte_code, last_atom_start, 3)) + goto out_of_memory; + s->byte_code.buf[last_atom_start++] = REOP_save_reset; + s->byte_code.buf[last_atom_start++] = last_capture_count; + s->byte_code.buf[last_atom_start++] = s->capture_count - 1; + } + if (quant_max == 0) { + s->byte_code.size = last_atom_start; + } else if (quant_max == 1 || quant_max == INT32_MAX) { + BOOL has_goto = (quant_max == INT32_MAX); + if (dbuf_insert(&s->byte_code, last_atom_start, 5 + add_zero_advance_check)) + goto out_of_memory; + s->byte_code.buf[last_atom_start] = REOP_split_goto_first + + greedy; + put_u32(s->byte_code.buf + last_atom_start + 1, + len + 5 * has_goto + add_zero_advance_check * 2); + if (add_zero_advance_check) { + s->byte_code.buf[last_atom_start + 1 + 4] = REOP_push_char_pos; + re_emit_op(s, REOP_check_advance); + } + if (has_goto) + re_emit_goto(s, REOP_goto, last_atom_start); + } else { + if (dbuf_insert(&s->byte_code, last_atom_start, 10 + add_zero_advance_check)) + goto out_of_memory; + pos = last_atom_start; + s->byte_code.buf[pos++] = REOP_push_i32; + put_u32(s->byte_code.buf + pos, quant_max); + pos += 4; + s->byte_code.buf[pos++] = REOP_split_goto_first + greedy; + put_u32(s->byte_code.buf + pos, len + 5 + add_zero_advance_check * 2); + pos += 4; + if (add_zero_advance_check) { + s->byte_code.buf[pos++] = REOP_push_char_pos; + re_emit_op(s, REOP_check_advance); + } + re_emit_goto(s, REOP_loop, last_atom_start + 5); + re_emit_op(s, REOP_drop); + } + } else if (quant_min == 1 && quant_max == INT32_MAX && + !add_zero_advance_check) { + re_emit_goto(s, REOP_split_next_first - greedy, + last_atom_start); + } else { + if (quant_min == 1) { + /* nothing to add */ + } else { + if (dbuf_insert(&s->byte_code, last_atom_start, 5)) + goto out_of_memory; + s->byte_code.buf[last_atom_start] = REOP_push_i32; + put_u32(s->byte_code.buf + last_atom_start + 1, + quant_min); + last_atom_start += 5; + re_emit_goto(s, REOP_loop, last_atom_start); + re_emit_op(s, REOP_drop); + } + if (quant_max == INT32_MAX) { + pos = s->byte_code.size; + re_emit_op_u32(s, REOP_split_goto_first + greedy, + len + 5 + add_zero_advance_check * 2); + if (add_zero_advance_check) + re_emit_op(s, REOP_push_char_pos); + /* copy the atom */ + dbuf_put_self(&s->byte_code, last_atom_start, len); + if (add_zero_advance_check) + re_emit_op(s, REOP_check_advance); + re_emit_goto(s, REOP_goto, pos); + } else if (quant_max > quant_min) { + re_emit_op_u32(s, REOP_push_i32, quant_max - quant_min); + pos = s->byte_code.size; + re_emit_op_u32(s, REOP_split_goto_first + greedy, + len + 5 + add_zero_advance_check * 2); + if (add_zero_advance_check) + re_emit_op(s, REOP_push_char_pos); + /* copy the atom */ + dbuf_put_self(&s->byte_code, last_atom_start, len); + if (add_zero_advance_check) + re_emit_op(s, REOP_check_advance); + re_emit_goto(s, REOP_loop, pos); + re_emit_op(s, REOP_drop); + } + } + last_atom_start = -1; + } + break; + default: + break; + } + } + done: + s->buf_ptr = p; + return 0; + out_of_memory: + return re_parse_out_of_memory(s); +} + +static int re_parse_alternative(REParseState *s, BOOL is_backward_dir) +{ + const uint8_t *p; + int ret; + size_t start, term_start, end, term_size; + + start = s->byte_code.size; + for(;;) { + p = s->buf_ptr; + if (p >= s->buf_end) + break; + if (*p == '|' || *p == ')') + break; + term_start = s->byte_code.size; + ret = re_parse_term(s, is_backward_dir); + if (ret) + return ret; + if (is_backward_dir) { + /* reverse the order of the terms (XXX: inefficient, but + speed is not really critical here) */ + end = s->byte_code.size; + term_size = end - term_start; + if (dbuf_realloc(&s->byte_code, end + term_size)) + return -1; + memmove(s->byte_code.buf + start + term_size, + s->byte_code.buf + start, + end - start); + memcpy(s->byte_code.buf + start, s->byte_code.buf + end, + term_size); + } + } + return 0; +} + +static int re_parse_disjunction(REParseState *s, BOOL is_backward_dir) +{ + int start, len, pos; + + if (lre_check_stack_overflow(s->opaque, 0)) + return re_parse_error(s, "stack overflow"); + + start = s->byte_code.size; + if (re_parse_alternative(s, is_backward_dir)) + return -1; + while (*s->buf_ptr == '|') { + s->buf_ptr++; + + len = s->byte_code.size - start; + + /* insert a split before the first alternative */ + if (dbuf_insert(&s->byte_code, start, 5)) { + return re_parse_out_of_memory(s); + } + s->byte_code.buf[start] = REOP_split_next_first; + put_u32(s->byte_code.buf + start + 1, len + 5); + + pos = re_emit_op_u32(s, REOP_goto, 0); + + if (re_parse_alternative(s, is_backward_dir)) + return -1; + + /* patch the goto */ + len = s->byte_code.size - (pos + 4); + put_u32(s->byte_code.buf + pos, len); + } + return 0; +} + +/* the control flow is recursive so the analysis can be linear */ +static int compute_stack_size(const uint8_t *bc_buf, int bc_buf_len) +{ + int stack_size, stack_size_max, pos, opcode, len; + uint32_t val; + + stack_size = 0; + stack_size_max = 0; + bc_buf += RE_HEADER_LEN; + bc_buf_len -= RE_HEADER_LEN; + pos = 0; + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + assert(opcode < REOP_COUNT); + assert((pos + len) <= bc_buf_len); + switch(opcode) { + case REOP_push_i32: + case REOP_push_char_pos: + stack_size++; + if (stack_size > stack_size_max) { + if (stack_size > STACK_SIZE_MAX) + return -1; + stack_size_max = stack_size; + } + break; + case REOP_drop: + case REOP_check_advance: + assert(stack_size > 0); + stack_size--; + break; + case REOP_range: + val = get_u16(bc_buf + pos + 1); + len += val * 4; + break; + case REOP_range32: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + break; + } + pos += len; + } + return stack_size_max; +} + +/* 'buf' must be a zero terminated UTF-8 string of length buf_len. + Return NULL if error and allocate an error message in *perror_msg, + otherwise the compiled bytecode and its length in plen. +*/ +uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, + const char *buf, size_t buf_len, int re_flags, + void *opaque) +{ + REParseState s_s, *s = &s_s; + int stack_size; + BOOL is_sticky; + + memset(s, 0, sizeof(*s)); + s->opaque = opaque; + s->buf_ptr = (const uint8_t *)buf; + s->buf_end = s->buf_ptr + buf_len; + s->buf_start = s->buf_ptr; + s->re_flags = re_flags; + s->is_unicode = ((re_flags & LRE_FLAG_UNICODE) != 0); + is_sticky = ((re_flags & LRE_FLAG_STICKY) != 0); + s->ignore_case = ((re_flags & LRE_FLAG_IGNORECASE) != 0); + s->dotall = ((re_flags & LRE_FLAG_DOTALL) != 0); + s->capture_count = 1; + s->total_capture_count = -1; + s->has_named_captures = -1; + + dbuf_init2(&s->byte_code, opaque, lre_realloc); + dbuf_init2(&s->group_names, opaque, lre_realloc); + + dbuf_putc(&s->byte_code, re_flags); /* first element is the flags */ + dbuf_putc(&s->byte_code, 0); /* second element is the number of captures */ + dbuf_putc(&s->byte_code, 0); /* stack size */ + dbuf_put_u32(&s->byte_code, 0); /* bytecode length */ + + if (!is_sticky) { + /* iterate thru all positions (about the same as .*?( ... ) ) + . We do it without an explicit loop so that lock step + thread execution will be possible in an optimized + implementation */ + re_emit_op_u32(s, REOP_split_goto_first, 1 + 5); + re_emit_op(s, REOP_any); + re_emit_op_u32(s, REOP_goto, -(5 + 1 + 5)); + } + re_emit_op_u8(s, REOP_save_start, 0); + + if (re_parse_disjunction(s, FALSE)) { + error: + dbuf_free(&s->byte_code); + dbuf_free(&s->group_names); + pstrcpy(error_msg, error_msg_size, s->u.error_msg); + *plen = 0; + return NULL; + } + + re_emit_op_u8(s, REOP_save_end, 0); + + re_emit_op(s, REOP_match); + + if (*s->buf_ptr != '\0') { + re_parse_error(s, "extraneous characters at the end"); + goto error; + } + + if (dbuf_error(&s->byte_code)) { + re_parse_out_of_memory(s); + goto error; + } + + stack_size = compute_stack_size(s->byte_code.buf, s->byte_code.size); + if (stack_size < 0) { + re_parse_error(s, "too many imbricated quantifiers"); + goto error; + } + + s->byte_code.buf[RE_HEADER_CAPTURE_COUNT] = s->capture_count; + s->byte_code.buf[RE_HEADER_STACK_SIZE] = stack_size; + put_u32(s->byte_code.buf + RE_HEADER_BYTECODE_LEN, + s->byte_code.size - RE_HEADER_LEN); + + /* add the named groups if needed */ + if (s->group_names.size > (s->capture_count - 1)) { + dbuf_put(&s->byte_code, s->group_names.buf, s->group_names.size); + s->byte_code.buf[RE_HEADER_FLAGS] |= LRE_FLAG_NAMED_GROUPS; + } + dbuf_free(&s->group_names); + +#ifdef DUMP_REOP + lre_dump_bytecode(s->byte_code.buf, s->byte_code.size); +#endif + + error_msg[0] = '\0'; + *plen = s->byte_code.size; + return s->byte_code.buf; +} + +static BOOL is_line_terminator(uint32_t c) +{ + return (c == '\n' || c == '\r' || c == CP_LS || c == CP_PS); +} + +static BOOL is_word_char(uint32_t c) +{ + return ((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c == '_')); +} + +#define GET_CHAR(c, cptr, cbuf_end, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = *cptr++; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr; \ + const uint16_t *_end = (const uint16_t *)cbuf_end; \ + c = *_p++; \ + if (is_hi_surrogate(c) && cbuf_type == 2) { \ + if (_p < _end && is_lo_surrogate(*_p)) { \ + c = from_surrogate(c, *_p++); \ + } \ + } \ + cptr = (const void *)_p; \ + } \ + } while (0) + +#define PEEK_CHAR(c, cptr, cbuf_end, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = cptr[0]; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr; \ + const uint16_t *_end = (const uint16_t *)cbuf_end; \ + c = *_p++; \ + if (is_hi_surrogate(c) && cbuf_type == 2) { \ + if (_p < _end && is_lo_surrogate(*_p)) { \ + c = from_surrogate(c, *_p); \ + } \ + } \ + } \ + } while (0) + +#define PEEK_PREV_CHAR(c, cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = cptr[-1]; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + c = *_p; \ + if (is_lo_surrogate(c) && cbuf_type == 2) { \ + if (_p > _start && is_hi_surrogate(_p[-1])) { \ + c = from_surrogate(*--_p, c); \ + } \ + } \ + } \ + } while (0) + +#define GET_PREV_CHAR(c, cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + cptr--; \ + c = cptr[0]; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + c = *_p; \ + if (is_lo_surrogate(c) && cbuf_type == 2) { \ + if (_p > _start && is_hi_surrogate(_p[-1])) { \ + c = from_surrogate(*--_p, c); \ + } \ + } \ + cptr = (const void *)_p; \ + } \ + } while (0) + +#define PREV_CHAR(cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + cptr--; \ + } else { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + if (is_lo_surrogate(*_p) && cbuf_type == 2) { \ + if (_p > _start && is_hi_surrogate(_p[-1])) { \ + --_p; \ + } \ + } \ + cptr = (const void *)_p; \ + } \ + } while (0) + +typedef uintptr_t StackInt; + +typedef enum { + RE_EXEC_STATE_SPLIT, + RE_EXEC_STATE_LOOKAHEAD, + RE_EXEC_STATE_NEGATIVE_LOOKAHEAD, + RE_EXEC_STATE_GREEDY_QUANT, +} REExecStateEnum; + +typedef struct REExecState { + REExecStateEnum type : 8; + uint8_t stack_len; + size_t count; /* only used for RE_EXEC_STATE_GREEDY_QUANT */ + const uint8_t *cptr; + const uint8_t *pc; + void *buf[0]; +} REExecState; + +typedef struct { + const uint8_t *cbuf; + const uint8_t *cbuf_end; + /* 0 = 8 bit chars, 1 = 16 bit chars, 2 = 16 bit chars, UTF-16 */ + int cbuf_type; + int capture_count; + int stack_size_max; + BOOL multi_line; + BOOL ignore_case; + BOOL is_unicode; + void *opaque; /* used for stack overflow check */ + + size_t state_size; + uint8_t *state_stack; + size_t state_stack_size; + size_t state_stack_len; +} REExecContext; + +static int push_state(REExecContext *s, + uint8_t **capture, + StackInt *stack, size_t stack_len, + const uint8_t *pc, const uint8_t *cptr, + REExecStateEnum type, size_t count) +{ + REExecState *rs; + uint8_t *new_stack; + size_t new_size, i, n; + StackInt *stack_buf; + + if (unlikely((s->state_stack_len + 1) > s->state_stack_size)) { + /* reallocate the stack */ + new_size = s->state_stack_size * 3 / 2; + if (new_size < 8) + new_size = 8; + new_stack = lre_realloc(s->opaque, s->state_stack, new_size * s->state_size); + if (!new_stack) + return -1; + s->state_stack_size = new_size; + s->state_stack = new_stack; + } + rs = (REExecState *)(s->state_stack + s->state_stack_len * s->state_size); + s->state_stack_len++; + rs->type = type; + rs->count = count; + rs->stack_len = stack_len; + rs->cptr = cptr; + rs->pc = pc; + n = 2 * s->capture_count; + for(i = 0; i < n; i++) + rs->buf[i] = capture[i]; + stack_buf = (StackInt *)(rs->buf + n); + for(i = 0; i < stack_len; i++) + stack_buf[i] = stack[i]; + return 0; +} + +/* return 1 if match, 0 if not match or -1 if error. */ +static intptr_t lre_exec_backtrack(REExecContext *s, uint8_t **capture, + StackInt *stack, int stack_len, + const uint8_t *pc, const uint8_t *cptr, + BOOL no_recurse) +{ + int opcode, ret; + int cbuf_type; + uint32_t val, c; + const uint8_t *cbuf_end; + + cbuf_type = s->cbuf_type; + cbuf_end = s->cbuf_end; + + for(;;) { + // printf("top=%p: pc=%d\n", th_list.top, (int)(pc - (bc_buf + RE_HEADER_LEN))); + opcode = *pc++; + switch(opcode) { + case REOP_match: + { + REExecState *rs; + if (no_recurse) + return (intptr_t)cptr; + ret = 1; + goto recurse; + no_match: + if (no_recurse) + return 0; + ret = 0; + recurse: + for(;;) { + if (s->state_stack_len == 0) + return ret; + rs = (REExecState *)(s->state_stack + + (s->state_stack_len - 1) * s->state_size); + if (rs->type == RE_EXEC_STATE_SPLIT) { + if (!ret) { + pop_state: + memcpy(capture, rs->buf, + sizeof(capture[0]) * 2 * s->capture_count); + pop_state1: + pc = rs->pc; + cptr = rs->cptr; + stack_len = rs->stack_len; + memcpy(stack, rs->buf + 2 * s->capture_count, + stack_len * sizeof(stack[0])); + s->state_stack_len--; + break; + } + } else if (rs->type == RE_EXEC_STATE_GREEDY_QUANT) { + if (!ret) { + uint32_t char_count, i; + memcpy(capture, rs->buf, + sizeof(capture[0]) * 2 * s->capture_count); + stack_len = rs->stack_len; + memcpy(stack, rs->buf + 2 * s->capture_count, + stack_len * sizeof(stack[0])); + pc = rs->pc; + cptr = rs->cptr; + /* go backward */ + char_count = get_u32(pc + 12); + for(i = 0; i < char_count; i++) { + PREV_CHAR(cptr, s->cbuf, cbuf_type); + } + pc = (pc + 16) + (int)get_u32(pc); + rs->cptr = cptr; + rs->count--; + if (rs->count == 0) { + s->state_stack_len--; + } + break; + } + } else { + ret = ((rs->type == RE_EXEC_STATE_LOOKAHEAD && ret) || + (rs->type == RE_EXEC_STATE_NEGATIVE_LOOKAHEAD && !ret)); + if (ret) { + /* keep the capture in case of positive lookahead */ + if (rs->type == RE_EXEC_STATE_LOOKAHEAD) + goto pop_state1; + else + goto pop_state; + } + } + s->state_stack_len--; + } + } + break; + case REOP_char32: + val = get_u32(pc); + pc += 4; + goto test_char; + case REOP_char: + val = get_u16(pc); + pc += 2; + test_char: + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (s->ignore_case) { + c = lre_canonicalize(c, s->is_unicode); + } + if (val != c) + goto no_match; + break; + case REOP_split_goto_first: + case REOP_split_next_first: + { + const uint8_t *pc1; + + val = get_u32(pc); + pc += 4; + if (opcode == REOP_split_next_first) { + pc1 = pc + (int)val; + } else { + pc1 = pc; + pc = pc + (int)val; + } + ret = push_state(s, capture, stack, stack_len, + pc1, cptr, RE_EXEC_STATE_SPLIT, 0); + if (ret < 0) + return -1; + break; + } + case REOP_lookahead: + case REOP_negative_lookahead: + val = get_u32(pc); + pc += 4; + ret = push_state(s, capture, stack, stack_len, + pc + (int)val, cptr, + RE_EXEC_STATE_LOOKAHEAD + opcode - REOP_lookahead, + 0); + if (ret < 0) + return -1; + break; + + case REOP_goto: + val = get_u32(pc); + pc += 4 + (int)val; + break; + case REOP_line_start: + if (cptr == s->cbuf) + break; + if (!s->multi_line) + goto no_match; + PEEK_PREV_CHAR(c, cptr, s->cbuf, cbuf_type); + if (!is_line_terminator(c)) + goto no_match; + break; + case REOP_line_end: + if (cptr == cbuf_end) + break; + if (!s->multi_line) + goto no_match; + PEEK_CHAR(c, cptr, cbuf_end, cbuf_type); + if (!is_line_terminator(c)) + goto no_match; + break; + case REOP_dot: + if (cptr == cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (is_line_terminator(c)) + goto no_match; + break; + case REOP_any: + if (cptr == cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + break; + case REOP_save_start: + case REOP_save_end: + val = *pc++; + assert(val < s->capture_count); + capture[2 * val + opcode - REOP_save_start] = (uint8_t *)cptr; + break; + case REOP_save_reset: + { + uint32_t val2; + val = pc[0]; + val2 = pc[1]; + pc += 2; + assert(val2 < s->capture_count); + while (val <= val2) { + capture[2 * val] = NULL; + capture[2 * val + 1] = NULL; + val++; + } + } + break; + case REOP_push_i32: + val = get_u32(pc); + pc += 4; + stack[stack_len++] = val; + break; + case REOP_drop: + stack_len--; + break; + case REOP_loop: + val = get_u32(pc); + pc += 4; + if (--stack[stack_len - 1] != 0) { + pc += (int)val; + } + break; + case REOP_push_char_pos: + stack[stack_len++] = (uintptr_t)cptr; + break; + case REOP_check_advance: + if (stack[--stack_len] == (uintptr_t)cptr) + goto no_match; + break; + case REOP_word_boundary: + case REOP_not_word_boundary: + { + BOOL v1, v2; + /* char before */ + if (cptr == s->cbuf) { + v1 = FALSE; + } else { + PEEK_PREV_CHAR(c, cptr, s->cbuf, cbuf_type); + v1 = is_word_char(c); + } + /* current char */ + if (cptr >= cbuf_end) { + v2 = FALSE; + } else { + PEEK_CHAR(c, cptr, cbuf_end, cbuf_type); + v2 = is_word_char(c); + } + if (v1 ^ v2 ^ (REOP_not_word_boundary - opcode)) + goto no_match; + } + break; + case REOP_back_reference: + case REOP_backward_back_reference: + { + const uint8_t *cptr1, *cptr1_end, *cptr1_start; + uint32_t c1, c2; + + val = *pc++; + if (val >= s->capture_count) + goto no_match; + cptr1_start = capture[2 * val]; + cptr1_end = capture[2 * val + 1]; + if (!cptr1_start || !cptr1_end) + break; + if (opcode == REOP_back_reference) { + cptr1 = cptr1_start; + while (cptr1 < cptr1_end) { + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c1, cptr1, cptr1_end, cbuf_type); + GET_CHAR(c2, cptr, cbuf_end, cbuf_type); + if (s->ignore_case) { + c1 = lre_canonicalize(c1, s->is_unicode); + c2 = lre_canonicalize(c2, s->is_unicode); + } + if (c1 != c2) + goto no_match; + } + } else { + cptr1 = cptr1_end; + while (cptr1 > cptr1_start) { + if (cptr == s->cbuf) + goto no_match; + GET_PREV_CHAR(c1, cptr1, cptr1_start, cbuf_type); + GET_PREV_CHAR(c2, cptr, s->cbuf, cbuf_type); + if (s->ignore_case) { + c1 = lre_canonicalize(c1, s->is_unicode); + c2 = lre_canonicalize(c2, s->is_unicode); + } + if (c1 != c2) + goto no_match; + } + } + } + break; + case REOP_range: + { + int n; + uint32_t low, high, idx_min, idx_max, idx; + + n = get_u16(pc); /* n must be >= 1 */ + pc += 2; + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (s->ignore_case) { + c = lre_canonicalize(c, s->is_unicode); + } + idx_min = 0; + low = get_u16(pc + 0 * 4); + if (c < low) + goto no_match; + idx_max = n - 1; + high = get_u16(pc + idx_max * 4 + 2); + /* 0xffff in for last value means +infinity */ + if (unlikely(c >= 0xffff) && high == 0xffff) + goto range_match; + if (c > high) + goto no_match; + while (idx_min <= idx_max) { + idx = (idx_min + idx_max) / 2; + low = get_u16(pc + idx * 4); + high = get_u16(pc + idx * 4 + 2); + if (c < low) + idx_max = idx - 1; + else if (c > high) + idx_min = idx + 1; + else + goto range_match; + } + goto no_match; + range_match: + pc += 4 * n; + } + break; + case REOP_range32: + { + int n; + uint32_t low, high, idx_min, idx_max, idx; + + n = get_u16(pc); /* n must be >= 1 */ + pc += 2; + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (s->ignore_case) { + c = lre_canonicalize(c, s->is_unicode); + } + idx_min = 0; + low = get_u32(pc + 0 * 8); + if (c < low) + goto no_match; + idx_max = n - 1; + high = get_u32(pc + idx_max * 8 + 4); + if (c > high) + goto no_match; + while (idx_min <= idx_max) { + idx = (idx_min + idx_max) / 2; + low = get_u32(pc + idx * 8); + high = get_u32(pc + idx * 8 + 4); + if (c < low) + idx_max = idx - 1; + else if (c > high) + idx_min = idx + 1; + else + goto range32_match; + } + goto no_match; + range32_match: + pc += 8 * n; + } + break; + case REOP_prev: + /* go to the previous char */ + if (cptr == s->cbuf) + goto no_match; + PREV_CHAR(cptr, s->cbuf, cbuf_type); + break; + case REOP_simple_greedy_quant: + { + uint32_t next_pos, quant_min, quant_max; + size_t q; + intptr_t res; + const uint8_t *pc1; + + next_pos = get_u32(pc); + quant_min = get_u32(pc + 4); + quant_max = get_u32(pc + 8); + pc += 16; + pc1 = pc; + pc += (int)next_pos; + + q = 0; + for(;;) { + res = lre_exec_backtrack(s, capture, stack, stack_len, + pc1, cptr, TRUE); + if (res == -1) + return res; + if (!res) + break; + cptr = (uint8_t *)res; + q++; + if (q >= quant_max && quant_max != INT32_MAX) + break; + } + if (q < quant_min) + goto no_match; + if (q > quant_min) { + /* will examine all matches down to quant_min */ + ret = push_state(s, capture, stack, stack_len, + pc1 - 16, cptr, + RE_EXEC_STATE_GREEDY_QUANT, + q - quant_min); + if (ret < 0) + return -1; + } + } + break; + default: + abort(); + } + } +} + +/* Return 1 if match, 0 if not match or -1 if error. cindex is the + starting position of the match and must be such as 0 <= cindex <= + clen. */ +int lre_exec(uint8_t **capture, + const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, + int cbuf_type, void *opaque) +{ + REExecContext s_s, *s = &s_s; + int re_flags, i, alloca_size, ret; + StackInt *stack_buf; + + re_flags = lre_get_flags(bc_buf); + s->multi_line = (re_flags & LRE_FLAG_MULTILINE) != 0; + s->ignore_case = (re_flags & LRE_FLAG_IGNORECASE) != 0; + s->is_unicode = (re_flags & LRE_FLAG_UNICODE) != 0; + s->capture_count = bc_buf[RE_HEADER_CAPTURE_COUNT]; + s->stack_size_max = bc_buf[RE_HEADER_STACK_SIZE]; + s->cbuf = cbuf; + s->cbuf_end = cbuf + (clen << cbuf_type); + s->cbuf_type = cbuf_type; + if (s->cbuf_type == 1 && s->is_unicode) + s->cbuf_type = 2; + s->opaque = opaque; + + s->state_size = sizeof(REExecState) + + s->capture_count * sizeof(capture[0]) * 2 + + s->stack_size_max * sizeof(stack_buf[0]); + s->state_stack = NULL; + s->state_stack_len = 0; + s->state_stack_size = 0; + + for(i = 0; i < s->capture_count * 2; i++) + capture[i] = NULL; + alloca_size = s->stack_size_max * sizeof(stack_buf[0]); + stack_buf = alloca(alloca_size); + ret = lre_exec_backtrack(s, capture, stack_buf, 0, bc_buf + RE_HEADER_LEN, + cbuf + (cindex << cbuf_type), FALSE); + lre_realloc(s->opaque, s->state_stack, 0); + return ret; +} + +int lre_get_capture_count(const uint8_t *bc_buf) +{ + return bc_buf[RE_HEADER_CAPTURE_COUNT]; +} + +int lre_get_flags(const uint8_t *bc_buf) +{ + return bc_buf[RE_HEADER_FLAGS]; +} + +/* Return NULL if no group names. Otherwise, return a pointer to + 'capture_count - 1' zero terminated UTF-8 strings. */ +const char *lre_get_groupnames(const uint8_t *bc_buf) +{ + uint32_t re_bytecode_len; + if ((lre_get_flags(bc_buf) & LRE_FLAG_NAMED_GROUPS) == 0) + return NULL; + re_bytecode_len = get_u32(bc_buf + RE_HEADER_BYTECODE_LEN); + return (const char *)(bc_buf + RE_HEADER_LEN + re_bytecode_len); +} + +#ifdef TEST + +BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size) +{ + return FALSE; +} + +void *lre_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +int main(int argc, char **argv) +{ + int len, flags, ret, i; + uint8_t *bc; + char error_msg[64]; + uint8_t *capture[CAPTURE_COUNT_MAX * 2]; + const char *input; + int input_len, capture_count; + + if (argc < 4) { + printf("usage: %s regexp flags input\n", argv[0]); + return 1; + } + flags = atoi(argv[2]); + bc = lre_compile(&len, error_msg, sizeof(error_msg), argv[1], + strlen(argv[1]), flags, NULL); + if (!bc) { + fprintf(stderr, "error: %s\n", error_msg); + exit(1); + } + + input = argv[3]; + input_len = strlen(input); + + ret = lre_exec(capture, bc, (uint8_t *)input, 0, input_len, 0, NULL); + printf("ret=%d\n", ret); + if (ret == 1) { + capture_count = lre_get_capture_count(bc); + for(i = 0; i < 2 * capture_count; i++) { + uint8_t *ptr; + ptr = capture[i]; + printf("%d: ", i); + if (!ptr) + printf(""); + else + printf("%u", (int)(ptr - (uint8_t *)input)); + printf("\n"); + } + } + return 0; +} +#endif diff --git a/source/engine/thirdparty/quickjs/libregexp.h b/source/engine/thirdparty/quickjs/libregexp.h new file mode 100644 index 0000000..757b277 --- /dev/null +++ b/source/engine/thirdparty/quickjs/libregexp.h @@ -0,0 +1,92 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * 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. + */ +#ifndef LIBREGEXP_H +#define LIBREGEXP_H + +#include + +#include "libunicode.h" + +#define LRE_BOOL int /* for documentation purposes */ + +#define LRE_FLAG_GLOBAL (1 << 0) +#define LRE_FLAG_IGNORECASE (1 << 1) +#define LRE_FLAG_MULTILINE (1 << 2) +#define LRE_FLAG_DOTALL (1 << 3) +#define LRE_FLAG_UNICODE (1 << 4) +#define LRE_FLAG_STICKY (1 << 5) +#define LRE_FLAG_INDICES (1 << 6) /* Unused by libregexp, just recorded. */ +#define LRE_FLAG_NAMED_GROUPS (1 << 7) /* named groups are present in the regexp */ + +uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, + const char *buf, size_t buf_len, int re_flags, + void *opaque); +int lre_get_capture_count(const uint8_t *bc_buf); +int lre_get_flags(const uint8_t *bc_buf); +const char *lre_get_groupnames(const uint8_t *bc_buf); +int lre_exec(uint8_t **capture, + const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, + int cbuf_type, void *opaque); + +int lre_parse_escape(const uint8_t **pp, int allow_utf16); +LRE_BOOL lre_is_space(int c); + +/* must be provided by the user */ +LRE_BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size); +void *lre_realloc(void *opaque, void *ptr, size_t size); + +/* JS identifier test */ +extern uint32_t const lre_id_start_table_ascii[4]; +extern uint32_t const lre_id_continue_table_ascii[4]; + +static inline int lre_js_is_ident_first(int c) +{ + if ((uint32_t)c < 128) { + return (lre_id_start_table_ascii[c >> 5] >> (c & 31)) & 1; + } else { +#ifdef CONFIG_ALL_UNICODE + return lre_is_id_start(c); +#else + return !lre_is_space(c); +#endif + } +} + +static inline int lre_js_is_ident_next(int c) +{ + if ((uint32_t)c < 128) { + return (lre_id_continue_table_ascii[c >> 5] >> (c & 31)) & 1; + } else { + /* ZWNJ and ZWJ are accepted in identifiers */ +#ifdef CONFIG_ALL_UNICODE + return lre_is_id_continue(c) || c == 0x200C || c == 0x200D; +#else + return !lre_is_space(c) || c == 0x200C || c == 0x200D; +#endif + } +} + +#undef LRE_BOOL + +#endif /* LIBREGEXP_H */ diff --git a/source/engine/thirdparty/quickjs/libunicode-table.h b/source/engine/thirdparty/quickjs/libunicode-table.h new file mode 100644 index 0000000..513ed94 --- /dev/null +++ b/source/engine/thirdparty/quickjs/libunicode-table.h @@ -0,0 +1,4486 @@ +/* Compressed unicode tables */ +/* Automatically generated file - do not edit */ + +#include + +static const uint32_t case_conv_table1[370] = { + 0x00209a30, 0x00309a00, 0x005a8173, 0x00601730, + 0x006c0730, 0x006f81b3, 0x00701700, 0x007c0700, + 0x007f8100, 0x00803040, 0x009801c3, 0x00988190, + 0x00990640, 0x009c9040, 0x00a481b4, 0x00a52e40, + 0x00bc0130, 0x00bc8640, 0x00bf8170, 0x00c00100, + 0x00c08130, 0x00c10440, 0x00c30130, 0x00c38240, + 0x00c48230, 0x00c58240, 0x00c70130, 0x00c78130, + 0x00c80130, 0x00c88240, 0x00c98130, 0x00ca0130, + 0x00ca8100, 0x00cb0130, 0x00cb8130, 0x00cc0240, + 0x00cd0100, 0x00ce0130, 0x00ce8130, 0x00cf0100, + 0x00cf8130, 0x00d00640, 0x00d30130, 0x00d38240, + 0x00d48130, 0x00d60240, 0x00d70130, 0x00d78240, + 0x00d88230, 0x00d98440, 0x00db8130, 0x00dc0240, + 0x00de0240, 0x00df8100, 0x00e20350, 0x00e38350, + 0x00e50350, 0x00e69040, 0x00ee8100, 0x00ef1240, + 0x00f801b4, 0x00f88350, 0x00fa0240, 0x00fb0130, + 0x00fb8130, 0x00fc2840, 0x01100130, 0x01111240, + 0x011d0131, 0x011d8240, 0x011e8130, 0x011f0131, + 0x011f8201, 0x01208240, 0x01218130, 0x01220130, + 0x01228130, 0x01230a40, 0x01280101, 0x01288101, + 0x01290101, 0x01298100, 0x012a0100, 0x012b0200, + 0x012c8100, 0x012d8100, 0x012e0101, 0x01300100, + 0x01308101, 0x01318100, 0x01328101, 0x01330101, + 0x01340100, 0x01348100, 0x01350101, 0x01358101, + 0x01360101, 0x01378100, 0x01388101, 0x01390100, + 0x013a8100, 0x013e8101, 0x01400100, 0x01410101, + 0x01418100, 0x01438101, 0x01440100, 0x01448100, + 0x01450200, 0x01460100, 0x01490100, 0x014e8101, + 0x014f0101, 0x01a28173, 0x01b80440, 0x01bb0240, + 0x01bd8300, 0x01bf8130, 0x01c30130, 0x01c40330, + 0x01c60130, 0x01c70230, 0x01c801d0, 0x01c89130, + 0x01d18930, 0x01d60100, 0x01d68300, 0x01d801d3, + 0x01d89100, 0x01e10173, 0x01e18900, 0x01e60100, + 0x01e68200, 0x01e78130, 0x01e80173, 0x01e88173, + 0x01ea8173, 0x01eb0173, 0x01eb8100, 0x01ec1840, + 0x01f80173, 0x01f88173, 0x01f90100, 0x01f98100, + 0x01fa01a0, 0x01fa8173, 0x01fb8240, 0x01fc8130, + 0x01fd0240, 0x01fe8330, 0x02001030, 0x02082030, + 0x02182000, 0x02281000, 0x02302240, 0x02453640, + 0x02600130, 0x02608e40, 0x02678100, 0x02686040, + 0x0298a630, 0x02b0a600, 0x02c381b5, 0x08502631, + 0x08638131, 0x08668131, 0x08682b00, 0x087e8300, + 0x09d05011, 0x09f80610, 0x09fc0620, 0x0e400174, + 0x0e408174, 0x0e410174, 0x0e418174, 0x0e420174, + 0x0e428174, 0x0e430174, 0x0e438180, 0x0e440180, + 0x0e482b30, 0x0e5e8330, 0x0ebc8101, 0x0ebe8101, + 0x0ec70101, 0x0f007e40, 0x0f3f1840, 0x0f4b01b5, + 0x0f4b81b6, 0x0f4c01b6, 0x0f4c81b6, 0x0f4d01b7, + 0x0f4d8180, 0x0f4f0130, 0x0f506040, 0x0f800800, + 0x0f840830, 0x0f880600, 0x0f8c0630, 0x0f900800, + 0x0f940830, 0x0f980800, 0x0f9c0830, 0x0fa00600, + 0x0fa40630, 0x0fa801b0, 0x0fa88100, 0x0fa901d3, + 0x0fa98100, 0x0faa01d3, 0x0faa8100, 0x0fab01d3, + 0x0fab8100, 0x0fac8130, 0x0fad8130, 0x0fae8130, + 0x0faf8130, 0x0fb00800, 0x0fb40830, 0x0fb80200, + 0x0fb90400, 0x0fbb0200, 0x0fbc0201, 0x0fbd0201, + 0x0fbe0201, 0x0fc008b7, 0x0fc40867, 0x0fc808b8, + 0x0fcc0868, 0x0fd008b8, 0x0fd40868, 0x0fd80200, + 0x0fd901b9, 0x0fd981b1, 0x0fda01b9, 0x0fdb01b1, + 0x0fdb81d7, 0x0fdc0230, 0x0fdd0230, 0x0fde0161, + 0x0fdf0173, 0x0fe101b9, 0x0fe181b2, 0x0fe201ba, + 0x0fe301b2, 0x0fe381d8, 0x0fe40430, 0x0fe60162, + 0x0fe80200, 0x0fe901d0, 0x0fe981d0, 0x0feb01b0, + 0x0feb81d0, 0x0fec0230, 0x0fed0230, 0x0ff00201, + 0x0ff101d3, 0x0ff181d3, 0x0ff201ba, 0x0ff28101, + 0x0ff301b0, 0x0ff381d3, 0x0ff40230, 0x0ff50230, + 0x0ff60131, 0x0ff901ba, 0x0ff981b2, 0x0ffa01bb, + 0x0ffb01b2, 0x0ffb81d9, 0x0ffc0230, 0x0ffd0230, + 0x0ffe0162, 0x109301a0, 0x109501a0, 0x109581a0, + 0x10990131, 0x10a70101, 0x10b01031, 0x10b81001, + 0x10c18240, 0x125b1a31, 0x12681a01, 0x16003031, + 0x16183001, 0x16300240, 0x16310130, 0x16318130, + 0x16320130, 0x16328100, 0x16330100, 0x16338640, + 0x16368130, 0x16370130, 0x16378130, 0x16380130, + 0x16390240, 0x163a8240, 0x163f0230, 0x16406440, + 0x16758440, 0x16790240, 0x16802600, 0x16938100, + 0x16968100, 0x53202e40, 0x53401c40, 0x53910e40, + 0x53993e40, 0x53bc8440, 0x53be8130, 0x53bf0a40, + 0x53c58240, 0x53c68130, 0x53c80440, 0x53ca0101, + 0x53cb1440, 0x53d50130, 0x53d58130, 0x53d60130, + 0x53d68130, 0x53d70130, 0x53d80130, 0x53d88130, + 0x53d90130, 0x53d98131, 0x53da1040, 0x53e20131, + 0x53e28130, 0x53e30130, 0x53e38440, 0x53e80240, + 0x53eb0440, 0x53fa8240, 0x55a98101, 0x55b85020, + 0x7d8001b2, 0x7d8081b2, 0x7d8101b2, 0x7d8181da, + 0x7d8201da, 0x7d8281b3, 0x7d8301b3, 0x7d8981bb, + 0x7d8a01bb, 0x7d8a81bb, 0x7d8b01bc, 0x7d8b81bb, + 0x7f909a31, 0x7fa09a01, 0x82002831, 0x82142801, + 0x82582431, 0x826c2401, 0x82b80b31, 0x82be0f31, + 0x82c60731, 0x82ca0231, 0x82cb8b01, 0x82d18f01, + 0x82d98701, 0x82dd8201, 0x86403331, 0x86603301, + 0x8c502031, 0x8c602001, 0xb7202031, 0xb7302001, + 0xf4802231, 0xf4912201, +}; + +static const uint8_t case_conv_table2[370] = { + 0x01, 0x00, 0x9c, 0x06, 0x07, 0x4d, 0x03, 0x04, + 0x10, 0x00, 0x8f, 0x0b, 0x00, 0x00, 0x11, 0x00, + 0x08, 0x00, 0x53, 0x4a, 0x51, 0x00, 0x52, 0x00, + 0x53, 0x00, 0x3a, 0x54, 0x55, 0x00, 0x57, 0x59, + 0x3f, 0x5d, 0x5c, 0x00, 0x46, 0x61, 0x63, 0x42, + 0x64, 0x00, 0x66, 0x00, 0x68, 0x00, 0x6a, 0x00, + 0x6c, 0x00, 0x6e, 0x00, 0x00, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x1a, 0x00, 0x93, 0x00, 0x00, 0x20, + 0x35, 0x00, 0x27, 0x00, 0x21, 0x00, 0x24, 0x22, + 0x2a, 0x00, 0x13, 0x6b, 0x6d, 0x00, 0x26, 0x24, + 0x27, 0x14, 0x16, 0x18, 0x1b, 0x1c, 0x3e, 0x1e, + 0x3f, 0x1f, 0x39, 0x3d, 0x22, 0x21, 0x41, 0x1e, + 0x40, 0x25, 0x25, 0x26, 0x28, 0x20, 0x2a, 0x48, + 0x2c, 0x43, 0x2e, 0x4b, 0x30, 0x4c, 0x32, 0x44, + 0x42, 0x99, 0x00, 0x00, 0x95, 0x8f, 0x7d, 0x7e, + 0x83, 0x84, 0x12, 0x80, 0x82, 0x76, 0x77, 0x12, + 0x7b, 0xa3, 0x7c, 0x78, 0x79, 0x8a, 0x92, 0x98, + 0xa6, 0xa0, 0x85, 0x00, 0x9a, 0xa1, 0x93, 0x75, + 0x33, 0x95, 0x00, 0x8e, 0x00, 0x74, 0x99, 0x98, + 0x97, 0x96, 0x00, 0x00, 0x9e, 0x00, 0x9c, 0x00, + 0xa1, 0xa0, 0x15, 0x2e, 0x2f, 0x30, 0xb4, 0xb5, + 0x4f, 0xaa, 0xa9, 0x12, 0x14, 0x1e, 0x21, 0x22, + 0x22, 0x2a, 0x34, 0x35, 0xa6, 0xa7, 0x36, 0x1f, + 0x49, 0x00, 0x00, 0x97, 0x01, 0x5a, 0xda, 0x1d, + 0x36, 0x05, 0x00, 0xc4, 0xc3, 0xc6, 0xc5, 0xc8, + 0xc7, 0xca, 0xc9, 0xcc, 0xcb, 0xc4, 0xd5, 0x45, + 0xd6, 0x42, 0xd7, 0x46, 0xd8, 0xce, 0xd0, 0xd2, + 0xd4, 0xda, 0xd9, 0xee, 0xf6, 0xfe, 0x0e, 0x07, + 0x0f, 0x80, 0x9f, 0x00, 0x21, 0x80, 0xa3, 0xed, + 0x00, 0xc0, 0x40, 0xc6, 0x60, 0xe7, 0xdb, 0xe6, + 0x99, 0xc0, 0x00, 0x00, 0x06, 0x60, 0xdc, 0x29, + 0xfd, 0x15, 0x12, 0x06, 0x16, 0xf8, 0xdd, 0x06, + 0x15, 0x12, 0x84, 0x08, 0xc6, 0x16, 0xff, 0xdf, + 0x03, 0xc0, 0x40, 0x00, 0x46, 0x60, 0xde, 0xe0, + 0x6d, 0x37, 0x38, 0x39, 0x15, 0x14, 0x17, 0x16, + 0x00, 0x1a, 0x19, 0x1c, 0x1b, 0x00, 0x5f, 0xb7, + 0x65, 0x44, 0x47, 0x00, 0x4f, 0x62, 0x4e, 0x50, + 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0xa3, 0xa4, + 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb6, 0x00, + 0x00, 0x5a, 0x00, 0x47, 0x00, 0x5b, 0x56, 0x58, + 0x60, 0x5e, 0x70, 0x69, 0x6f, 0x4e, 0x00, 0x3b, + 0x67, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x45, 0xa8, + 0x8a, 0x8b, 0x8c, 0xab, 0xac, 0x58, 0x58, 0xaf, + 0x94, 0xb0, 0x6f, 0xb2, 0x5d, 0x5c, 0x5f, 0x5e, + 0x61, 0x60, 0x66, 0x67, 0x68, 0x69, 0x62, 0x63, + 0x64, 0x65, 0x6b, 0x6a, 0x6d, 0x6c, 0x6f, 0x6e, + 0x71, 0x70, +}; + +static const uint16_t case_conv_ext[58] = { + 0x0399, 0x0308, 0x0301, 0x03a5, 0x0313, 0x0300, 0x0342, 0x0391, + 0x0397, 0x03a9, 0x0046, 0x0049, 0x004c, 0x0053, 0x0069, 0x0307, + 0x02bc, 0x004e, 0x004a, 0x030c, 0x0535, 0x0552, 0x0048, 0x0331, + 0x0054, 0x0057, 0x030a, 0x0059, 0x0041, 0x02be, 0x1f08, 0x1f80, + 0x1f28, 0x1f90, 0x1f68, 0x1fa0, 0x1fba, 0x0386, 0x1fb3, 0x1fca, + 0x0389, 0x1fc3, 0x03a1, 0x1ffa, 0x038f, 0x1ff3, 0x0544, 0x0546, + 0x053b, 0x054e, 0x053d, 0x03b8, 0x0462, 0xa64a, 0x1e60, 0x03c9, + 0x006b, 0x00e5, +}; + +static const uint8_t unicode_prop_Cased1_table[196] = { + 0x40, 0xa9, 0x80, 0x8e, 0x80, 0xfc, 0x80, 0xd3, + 0x80, 0x8c, 0x80, 0x8d, 0x81, 0x8d, 0x02, 0x80, + 0xe1, 0x80, 0x91, 0x85, 0x9a, 0x01, 0x00, 0x01, + 0x11, 0x00, 0x01, 0x04, 0x08, 0x01, 0x08, 0x30, + 0x08, 0x01, 0x15, 0x20, 0x00, 0x39, 0x99, 0x31, + 0x9d, 0x84, 0x40, 0x94, 0x80, 0xd6, 0x82, 0xa6, + 0x80, 0x41, 0x62, 0x80, 0xa6, 0x80, 0x4b, 0x72, + 0x80, 0x4c, 0x02, 0xf8, 0x02, 0x80, 0x8f, 0x80, + 0xb0, 0x40, 0xdb, 0x08, 0x80, 0x41, 0xd0, 0x80, + 0x8c, 0x80, 0x8f, 0x8c, 0xe4, 0x03, 0x01, 0x89, + 0x00, 0x14, 0x28, 0x10, 0x11, 0x02, 0x01, 0x18, + 0x0b, 0x24, 0x4b, 0x26, 0x01, 0x01, 0x86, 0xe5, + 0x80, 0x60, 0x79, 0xb6, 0x81, 0x40, 0x91, 0x81, + 0xbd, 0x88, 0x94, 0x05, 0x80, 0x98, 0x80, 0xa2, + 0x00, 0x80, 0x9b, 0x12, 0x82, 0x43, 0x34, 0xa2, + 0x06, 0x80, 0x8d, 0x60, 0x5c, 0x15, 0x01, 0x10, + 0xa9, 0x80, 0x88, 0x60, 0xcc, 0x44, 0xd4, 0x80, + 0xc6, 0x01, 0x08, 0x09, 0x0b, 0x80, 0x8b, 0x00, + 0x06, 0x80, 0xc0, 0x03, 0x0f, 0x06, 0x80, 0x9b, + 0x03, 0x04, 0x00, 0x16, 0x80, 0x41, 0x53, 0x81, + 0x98, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, + 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, + 0x9e, 0x80, 0x98, 0x07, 0x47, 0x33, 0x89, 0x80, + 0x93, 0x2d, 0x41, 0x04, 0xbd, 0x50, 0xc1, 0x99, + 0x85, 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Cased1_index[21] = { + 0xb9, 0x02, 0xe0, 0xc0, 0x1d, 0x20, 0xe5, 0x2c, + 0x20, 0xb1, 0x07, 0x21, 0xc1, 0xd6, 0x21, 0x4a, + 0xf1, 0x01, 0x8a, 0xf1, 0x01, +}; + +static const uint8_t unicode_prop_Case_Ignorable_table[737] = { + 0xa6, 0x05, 0x80, 0x8a, 0x80, 0xa2, 0x00, 0x80, + 0xc6, 0x03, 0x00, 0x03, 0x01, 0x81, 0x41, 0xf6, + 0x40, 0xbf, 0x19, 0x18, 0x88, 0x08, 0x80, 0x40, + 0xfa, 0x86, 0x40, 0xce, 0x04, 0x80, 0xb0, 0xac, + 0x00, 0x01, 0x01, 0x00, 0xab, 0x80, 0x8a, 0x85, + 0x89, 0x8a, 0x00, 0xa2, 0x80, 0x89, 0x94, 0x8f, + 0x80, 0xe4, 0x38, 0x89, 0x03, 0xa0, 0x00, 0x80, + 0x9d, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0x18, 0x08, + 0x97, 0x97, 0xaa, 0x82, 0xab, 0x06, 0x0d, 0x87, + 0xa8, 0xb9, 0xb6, 0x00, 0x03, 0x3b, 0x02, 0x86, + 0x89, 0x81, 0x8c, 0x80, 0x8e, 0x80, 0xb9, 0x03, + 0x1f, 0x80, 0x93, 0x81, 0x99, 0x01, 0x81, 0xb8, + 0x03, 0x0b, 0x09, 0x12, 0x80, 0x9d, 0x0a, 0x80, + 0x8a, 0x81, 0xb8, 0x03, 0x20, 0x0b, 0x80, 0x93, + 0x81, 0x95, 0x28, 0x80, 0xb9, 0x01, 0x00, 0x1f, + 0x06, 0x81, 0x8a, 0x81, 0x9d, 0x80, 0xbc, 0x80, + 0x8b, 0x80, 0xb1, 0x02, 0x80, 0xb6, 0x00, 0x14, + 0x10, 0x1e, 0x81, 0x8a, 0x81, 0x9c, 0x80, 0xb9, + 0x01, 0x05, 0x04, 0x81, 0x93, 0x81, 0x9b, 0x81, + 0xb8, 0x0b, 0x1f, 0x80, 0x93, 0x81, 0x9c, 0x80, + 0xc7, 0x06, 0x10, 0x80, 0xd9, 0x01, 0x86, 0x8a, + 0x88, 0xe1, 0x01, 0x88, 0x88, 0x00, 0x86, 0xc8, + 0x81, 0x9a, 0x00, 0x00, 0x80, 0xb6, 0x8d, 0x04, + 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, 0x80, 0xe5, + 0x18, 0x28, 0x09, 0x81, 0x98, 0x0b, 0x82, 0x8f, + 0x83, 0x8c, 0x01, 0x0d, 0x80, 0x8e, 0x80, 0xdd, + 0x80, 0x42, 0x5f, 0x82, 0x43, 0xb1, 0x82, 0x9c, + 0x81, 0x9d, 0x81, 0x9d, 0x81, 0xbf, 0x08, 0x37, + 0x01, 0x8a, 0x10, 0x20, 0xac, 0x84, 0xb2, 0x80, + 0xc0, 0x81, 0xa1, 0x80, 0xf5, 0x13, 0x81, 0x88, + 0x05, 0x82, 0x40, 0xda, 0x09, 0x80, 0xb9, 0x00, + 0x30, 0x00, 0x01, 0x3d, 0x89, 0x08, 0xa6, 0x07, + 0x9e, 0xb0, 0x83, 0xaf, 0x00, 0x20, 0x04, 0x80, + 0xa7, 0x88, 0x8b, 0x81, 0x9f, 0x19, 0x08, 0x82, + 0xb7, 0x00, 0x0a, 0x00, 0x82, 0xb9, 0x39, 0x81, + 0xbf, 0x85, 0xd1, 0x10, 0x8c, 0x06, 0x18, 0x28, + 0x11, 0xb1, 0xbe, 0x8c, 0x80, 0xa1, 0xe4, 0x41, + 0xbc, 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, + 0x82, 0x8c, 0x81, 0x8b, 0x27, 0x81, 0x89, 0x01, + 0x01, 0x84, 0xb0, 0x20, 0x89, 0x00, 0x8c, 0x80, + 0x8f, 0x8c, 0xb2, 0xa0, 0x4b, 0x8a, 0x81, 0xf0, + 0x82, 0xfc, 0x80, 0x8e, 0x80, 0xdf, 0x9f, 0xae, + 0x80, 0x41, 0xd4, 0x80, 0xa3, 0x1a, 0x24, 0x80, + 0xdc, 0x85, 0xdc, 0x82, 0x60, 0x6f, 0x15, 0x80, + 0x44, 0xe1, 0x85, 0x41, 0x0d, 0x80, 0xe1, 0x18, + 0x89, 0x00, 0x9b, 0x83, 0xcf, 0x81, 0x8d, 0xa1, + 0xcd, 0x80, 0x96, 0x82, 0xe6, 0x12, 0x0f, 0x02, + 0x03, 0x80, 0x98, 0x0c, 0x80, 0x40, 0x96, 0x81, + 0x99, 0x91, 0x8c, 0x80, 0xa5, 0x87, 0x98, 0x8a, + 0xad, 0x82, 0xaf, 0x01, 0x19, 0x81, 0x90, 0x80, + 0x94, 0x81, 0xc1, 0x29, 0x09, 0x81, 0x8b, 0x07, + 0x80, 0xa2, 0x80, 0x8a, 0x80, 0xb2, 0x00, 0x11, + 0x0c, 0x08, 0x80, 0x9a, 0x80, 0x8d, 0x0c, 0x08, + 0x80, 0xe3, 0x84, 0x88, 0x82, 0xf8, 0x01, 0x03, + 0x80, 0x60, 0x4f, 0x2f, 0x80, 0x40, 0x92, 0x90, + 0x42, 0x3c, 0x8f, 0x10, 0x8b, 0x8f, 0xa1, 0x01, + 0x80, 0x40, 0xa8, 0x06, 0x05, 0x80, 0x8a, 0x80, + 0xa2, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, + 0x80, 0x94, 0x82, 0x42, 0x00, 0x80, 0x40, 0xe1, + 0x80, 0x40, 0x94, 0x84, 0x44, 0x04, 0x28, 0xa9, + 0x80, 0x88, 0x42, 0x45, 0x10, 0x0c, 0x83, 0xa7, + 0x13, 0x80, 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x83, + 0x41, 0x82, 0x81, 0xcf, 0x82, 0xc5, 0x8a, 0xb0, + 0x83, 0xfa, 0x80, 0xb5, 0x8e, 0xa8, 0x01, 0x81, + 0x89, 0x82, 0xb0, 0x19, 0x09, 0x03, 0x80, 0x89, + 0x80, 0xb1, 0x82, 0xa3, 0x20, 0x87, 0xbd, 0x80, + 0x8b, 0x81, 0xb3, 0x88, 0x89, 0x19, 0x80, 0xde, + 0x11, 0x00, 0x0d, 0x01, 0x80, 0x40, 0x9c, 0x02, + 0x87, 0x94, 0x81, 0xb8, 0x0a, 0x80, 0xa4, 0x32, + 0x84, 0x40, 0xc2, 0x39, 0x10, 0x80, 0x96, 0x80, + 0xd3, 0x28, 0x03, 0x08, 0x81, 0x40, 0xed, 0x1d, + 0x08, 0x81, 0x9a, 0x81, 0xd4, 0x39, 0x00, 0x81, + 0xe9, 0x00, 0x01, 0x28, 0x80, 0xe4, 0x11, 0x18, + 0x84, 0x41, 0x02, 0x88, 0x01, 0x40, 0xff, 0x08, + 0x03, 0x80, 0x40, 0x8f, 0x19, 0x0b, 0x80, 0x9f, + 0x89, 0xa7, 0x29, 0x1f, 0x80, 0x88, 0x29, 0x82, + 0xad, 0x8c, 0x01, 0x41, 0x95, 0x30, 0x28, 0x80, + 0xd1, 0x95, 0x0e, 0x01, 0x01, 0xf9, 0x2a, 0x00, + 0x08, 0x30, 0x80, 0xc7, 0x0a, 0x00, 0x80, 0x41, + 0x5a, 0x81, 0x8a, 0x81, 0xb3, 0x24, 0x00, 0x80, + 0x54, 0xec, 0x90, 0x85, 0x8e, 0x60, 0x36, 0x99, + 0x84, 0xba, 0x86, 0x88, 0x83, 0x44, 0x0a, 0x80, + 0xbe, 0x90, 0xbf, 0x08, 0x81, 0x60, 0x40, 0x0a, + 0x18, 0x30, 0x81, 0x4c, 0x9d, 0x08, 0x83, 0x52, + 0x5b, 0xad, 0x81, 0x96, 0x42, 0x1f, 0x82, 0x88, + 0x8f, 0x0e, 0x9d, 0x83, 0x40, 0x93, 0x82, 0x47, + 0xba, 0xb6, 0x83, 0xb1, 0x38, 0x8d, 0x80, 0x95, + 0x20, 0x8e, 0x45, 0x4f, 0x30, 0x90, 0x0e, 0x01, + 0x04, 0x84, 0xbd, 0xa0, 0x80, 0x40, 0x9f, 0x8d, + 0x41, 0x6f, 0x80, 0xbc, 0x83, 0x41, 0xfa, 0x84, + 0x43, 0xdf, 0x86, 0xec, 0x87, 0x4a, 0xae, 0x84, + 0x6c, 0x0c, 0x00, 0x80, 0x9d, 0xdf, 0xff, 0x40, + 0xef, +}; + +static const uint8_t unicode_prop_Case_Ignorable_index[69] = { + 0xbe, 0x05, 0x00, 0xfe, 0x07, 0x00, 0x52, 0x0a, + 0xa0, 0xc1, 0x0b, 0x00, 0x82, 0x0d, 0x00, 0x3f, + 0x10, 0x80, 0xd4, 0x17, 0x40, 0xcf, 0x1a, 0x20, + 0xf5, 0x1c, 0x00, 0x80, 0x20, 0x00, 0x16, 0xa0, + 0x00, 0xc6, 0xa8, 0x00, 0xc2, 0xaa, 0x60, 0x56, + 0xfe, 0x20, 0xb1, 0x07, 0x01, 0x75, 0x10, 0x01, + 0xeb, 0x12, 0x21, 0x41, 0x16, 0x01, 0x5c, 0x1a, + 0x01, 0x43, 0x1f, 0x01, 0x2e, 0xcf, 0x41, 0x25, + 0xe0, 0x01, 0xf0, 0x01, 0x0e, +}; + +static const uint8_t unicode_prop_ID_Start_table[1100] = { + 0xc0, 0x99, 0x85, 0x99, 0xae, 0x80, 0x89, 0x03, + 0x04, 0x96, 0x80, 0x9e, 0x80, 0x41, 0xc9, 0x83, + 0x8b, 0x8d, 0x26, 0x00, 0x80, 0x40, 0x80, 0x20, + 0x09, 0x18, 0x05, 0x00, 0x10, 0x00, 0x93, 0x80, + 0xd2, 0x80, 0x40, 0x8a, 0x87, 0x40, 0xa5, 0x80, + 0xa5, 0x08, 0x85, 0xa8, 0xc6, 0x9a, 0x1b, 0xac, + 0xaa, 0xa2, 0x08, 0xe2, 0x00, 0x8e, 0x0e, 0x81, + 0x89, 0x11, 0x80, 0x8f, 0x00, 0x9d, 0x9c, 0xd8, + 0x8a, 0x80, 0x97, 0xa0, 0x88, 0x0b, 0x04, 0x95, + 0x18, 0x88, 0x02, 0x80, 0x96, 0x98, 0x86, 0x8a, + 0x84, 0x97, 0x05, 0x90, 0xa9, 0xb9, 0xb5, 0x10, + 0x91, 0x06, 0x89, 0x8e, 0x8f, 0x1f, 0x09, 0x81, + 0x95, 0x06, 0x00, 0x13, 0x10, 0x8f, 0x80, 0x8c, + 0x08, 0x82, 0x8d, 0x81, 0x89, 0x07, 0x2b, 0x09, + 0x95, 0x06, 0x01, 0x01, 0x01, 0x9e, 0x18, 0x80, + 0x92, 0x82, 0x8f, 0x88, 0x02, 0x80, 0x95, 0x06, + 0x01, 0x04, 0x10, 0x91, 0x80, 0x8e, 0x81, 0x96, + 0x80, 0x8a, 0x39, 0x09, 0x95, 0x06, 0x01, 0x04, + 0x10, 0x9d, 0x08, 0x82, 0x8e, 0x80, 0x90, 0x00, + 0x2a, 0x10, 0x1a, 0x08, 0x00, 0x0a, 0x0a, 0x12, + 0x8b, 0x95, 0x80, 0xb3, 0x38, 0x10, 0x96, 0x80, + 0x8f, 0x10, 0x99, 0x11, 0x01, 0x81, 0x9d, 0x03, + 0x38, 0x10, 0x96, 0x80, 0x89, 0x04, 0x10, 0x9e, + 0x08, 0x81, 0x8e, 0x81, 0x90, 0x88, 0x02, 0x80, + 0xa8, 0x08, 0x8f, 0x04, 0x17, 0x82, 0x97, 0x2c, + 0x91, 0x82, 0x97, 0x80, 0x88, 0x00, 0x0e, 0xb9, + 0xaf, 0x01, 0x8b, 0x86, 0xb9, 0x08, 0x00, 0x20, + 0x97, 0x00, 0x80, 0x89, 0x01, 0x88, 0x01, 0x20, + 0x80, 0x94, 0x83, 0x9f, 0x80, 0xbe, 0x38, 0xa3, + 0x9a, 0x84, 0xf2, 0xaa, 0x93, 0x80, 0x8f, 0x2b, + 0x1a, 0x02, 0x0e, 0x13, 0x8c, 0x8b, 0x80, 0x90, + 0xa5, 0x00, 0x20, 0x81, 0xaa, 0x80, 0x41, 0x4c, + 0x03, 0x0e, 0x00, 0x03, 0x81, 0xa8, 0x03, 0x81, + 0xa0, 0x03, 0x0e, 0x00, 0x03, 0x81, 0x8e, 0x80, + 0xb8, 0x03, 0x81, 0xc2, 0xa4, 0x8f, 0x8f, 0xd5, + 0x0d, 0x82, 0x42, 0x6b, 0x81, 0x90, 0x80, 0x99, + 0x84, 0xca, 0x82, 0x8a, 0x86, 0x91, 0x8c, 0x92, + 0x8d, 0x91, 0x8d, 0x8c, 0x02, 0x8e, 0xb3, 0xa2, + 0x03, 0x80, 0xc2, 0xd8, 0x86, 0xa8, 0x00, 0x84, + 0xc5, 0x89, 0x9e, 0xb0, 0x9d, 0x0c, 0x8a, 0xab, + 0x83, 0x99, 0xb5, 0x96, 0x88, 0xb4, 0xd1, 0x80, + 0xdc, 0xae, 0x90, 0x87, 0xb5, 0x9d, 0x8c, 0x81, + 0x89, 0xab, 0x99, 0xa3, 0xa8, 0x82, 0x89, 0xa3, + 0x81, 0x88, 0x86, 0xaa, 0x0a, 0xa8, 0x18, 0x28, + 0x0a, 0x04, 0x40, 0xbf, 0xbf, 0x41, 0x15, 0x0d, + 0x81, 0xa5, 0x0d, 0x0f, 0x00, 0x00, 0x00, 0x80, + 0x9e, 0x81, 0xb4, 0x06, 0x00, 0x12, 0x06, 0x13, + 0x0d, 0x83, 0x8c, 0x22, 0x06, 0xf3, 0x80, 0x8c, + 0x80, 0x8f, 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, + 0x0d, 0x28, 0x00, 0x00, 0x80, 0x8f, 0x0b, 0x24, + 0x18, 0x90, 0xa8, 0x4a, 0x76, 0x40, 0xe4, 0x2b, + 0x11, 0x8b, 0xa5, 0x00, 0x20, 0x81, 0xb7, 0x30, + 0x8f, 0x96, 0x88, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x86, 0x42, 0x25, 0x82, 0x98, 0x88, + 0x34, 0x0c, 0x83, 0xd5, 0x1c, 0x80, 0xd9, 0x03, + 0x84, 0xaa, 0x80, 0xdd, 0x90, 0x9f, 0xaf, 0x8f, + 0x41, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x56, 0x8c, + 0xc2, 0xad, 0x81, 0x41, 0x0c, 0x82, 0x8f, 0x89, + 0x81, 0x93, 0xae, 0x8f, 0x9e, 0x81, 0xcf, 0xa6, + 0x88, 0x81, 0xe6, 0x81, 0xbf, 0x21, 0x00, 0x04, + 0x97, 0x8f, 0x02, 0x03, 0x80, 0x96, 0x9c, 0xb3, + 0x8d, 0xb1, 0xbd, 0x2a, 0x00, 0x81, 0x8a, 0x9b, + 0x89, 0x96, 0x98, 0x9c, 0x86, 0xae, 0x9b, 0x80, + 0x8f, 0x20, 0x89, 0x89, 0x20, 0xa8, 0x96, 0x10, + 0x87, 0x93, 0x96, 0x10, 0x82, 0xb1, 0x00, 0x11, + 0x0c, 0x08, 0x00, 0x97, 0x11, 0x8a, 0x32, 0x8b, + 0x29, 0x29, 0x85, 0x88, 0x30, 0x30, 0xaa, 0x80, + 0x8d, 0x85, 0xf2, 0x9c, 0x60, 0x2b, 0xa3, 0x8b, + 0x96, 0x83, 0xb0, 0x60, 0x21, 0x03, 0x41, 0x6d, + 0x81, 0xe9, 0xa5, 0x86, 0x8b, 0x24, 0x00, 0x89, + 0x80, 0x8c, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, + 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, + 0x8b, 0xf3, 0x20, 0x40, 0x86, 0xa3, 0x99, 0x85, + 0x99, 0x8a, 0xd8, 0x15, 0x0d, 0x0d, 0x0a, 0xa2, + 0x8b, 0x80, 0x99, 0x80, 0x92, 0x01, 0x80, 0x8e, + 0x81, 0x8d, 0xa1, 0xfa, 0xc4, 0xb4, 0x41, 0x0a, + 0x9c, 0x82, 0xb0, 0xae, 0x9f, 0x8c, 0x9d, 0x84, + 0xa5, 0x89, 0x9d, 0x81, 0xa3, 0x1f, 0x04, 0xa9, + 0x40, 0x9d, 0x91, 0xa3, 0x83, 0xa3, 0x83, 0xa7, + 0x87, 0xb3, 0x8b, 0x8a, 0x80, 0x8e, 0x06, 0x01, + 0x80, 0x8a, 0x80, 0x8e, 0x06, 0x01, 0xc2, 0x41, + 0x36, 0x88, 0x95, 0x89, 0x87, 0x97, 0x28, 0xa9, + 0x80, 0x88, 0xc4, 0x29, 0x00, 0xab, 0x01, 0x10, + 0x81, 0x96, 0x89, 0x96, 0x88, 0x9e, 0xc0, 0x92, + 0x01, 0x89, 0x95, 0x89, 0x99, 0xc5, 0xb7, 0x29, + 0xbf, 0x80, 0x8e, 0x18, 0x10, 0x9c, 0xa9, 0x9c, + 0x82, 0x9c, 0xa2, 0x38, 0x9b, 0x9a, 0xb5, 0x89, + 0x95, 0x89, 0x92, 0x8c, 0x91, 0xed, 0xc8, 0xb6, + 0xb2, 0x8c, 0xb2, 0x8c, 0xa3, 0x41, 0x5b, 0xa9, + 0x29, 0xcd, 0x9c, 0x89, 0x07, 0x95, 0xa9, 0x91, + 0xad, 0x94, 0x9a, 0x96, 0x8b, 0xb4, 0xb8, 0x09, + 0x80, 0x8c, 0xac, 0x9f, 0x98, 0x99, 0xa3, 0x9c, + 0x01, 0x07, 0xa2, 0x10, 0x8b, 0xaf, 0x8d, 0x83, + 0x94, 0x00, 0x80, 0xa2, 0x91, 0x80, 0x98, 0x92, + 0x81, 0xbe, 0x30, 0x00, 0x18, 0x8e, 0x80, 0x89, + 0x86, 0xae, 0xa5, 0x39, 0x09, 0x95, 0x06, 0x01, + 0x04, 0x10, 0x91, 0x80, 0x8b, 0x84, 0x40, 0x9d, + 0xb4, 0x91, 0x83, 0x93, 0x82, 0x9d, 0xaf, 0x93, + 0x08, 0x80, 0x40, 0xb7, 0xae, 0xa8, 0x83, 0xa3, + 0xaf, 0x93, 0x80, 0xba, 0xaa, 0x8c, 0x80, 0xc6, + 0x9a, 0xa4, 0x86, 0x40, 0xb8, 0xab, 0xf3, 0xbf, + 0x9e, 0x39, 0x01, 0x38, 0x08, 0x97, 0x8e, 0x00, + 0x80, 0xdd, 0x39, 0xa6, 0x8f, 0x00, 0x80, 0x9b, + 0x80, 0x89, 0xa7, 0x30, 0x94, 0x80, 0x8a, 0xad, + 0x92, 0x80, 0x91, 0xc8, 0x41, 0x06, 0x88, 0x80, + 0xa4, 0x90, 0x80, 0xb0, 0x9d, 0xef, 0x30, 0x08, + 0xa5, 0x94, 0x80, 0x98, 0x28, 0x08, 0x9f, 0x8d, + 0x80, 0x41, 0x46, 0x92, 0x8e, 0x00, 0x8c, 0x80, + 0xa1, 0xfb, 0x80, 0xce, 0x43, 0x99, 0xe5, 0xee, + 0x90, 0x40, 0xc3, 0x4a, 0x4b, 0xe0, 0x8e, 0x44, + 0x2f, 0x90, 0x85, 0x4f, 0xb8, 0x42, 0x46, 0x60, + 0x21, 0xb8, 0x42, 0x38, 0x86, 0x9e, 0x90, 0xce, + 0x90, 0x9d, 0x91, 0xaf, 0x8f, 0x83, 0x9e, 0x94, + 0x84, 0x92, 0x42, 0xaf, 0xbf, 0xff, 0xca, 0x20, + 0xc1, 0x8c, 0xbf, 0x08, 0x80, 0x9b, 0x57, 0xf7, + 0x87, 0x44, 0xd5, 0xa9, 0x88, 0x60, 0x22, 0xe6, + 0x18, 0x30, 0x08, 0x41, 0x22, 0x8e, 0x80, 0x9c, + 0x11, 0x80, 0x8d, 0x1f, 0x41, 0x8b, 0x49, 0x03, + 0xea, 0x84, 0x8c, 0x82, 0x88, 0x86, 0x89, 0x57, + 0x65, 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, + 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, + 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, + 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x07, 0x47, + 0x33, 0x9e, 0x2d, 0x41, 0x04, 0xbd, 0x40, 0x91, + 0xac, 0x89, 0x86, 0x8f, 0x80, 0x41, 0x40, 0x9d, + 0x91, 0xab, 0x41, 0xe3, 0x9b, 0x42, 0xf3, 0x30, + 0x18, 0x08, 0x8e, 0x80, 0x40, 0xc4, 0xba, 0xc3, + 0x30, 0x44, 0xb3, 0x18, 0x9a, 0x01, 0x00, 0x08, + 0x80, 0x89, 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, + 0x00, 0x02, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, + 0x80, 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, + 0x51, 0x43, 0x60, 0xa6, 0xdf, 0x9f, 0x50, 0x39, + 0x85, 0x40, 0xdd, 0x81, 0x56, 0x81, 0x8d, 0x5d, + 0x30, 0x4c, 0x1e, 0x42, 0x1d, 0x45, 0xe1, 0x53, + 0x4a, 0x84, 0x50, 0x5f, +}; + +static const uint8_t unicode_prop_ID_Start_index[105] = { + 0xf6, 0x03, 0x20, 0xa6, 0x07, 0x00, 0xa9, 0x09, + 0x20, 0xb1, 0x0a, 0x00, 0xba, 0x0b, 0x20, 0x3b, + 0x0d, 0x20, 0xc7, 0x0e, 0x20, 0x49, 0x12, 0x00, + 0x9b, 0x16, 0x00, 0xac, 0x19, 0x00, 0xc0, 0x1d, + 0x80, 0x80, 0x20, 0x20, 0x70, 0x2d, 0x00, 0x00, + 0x32, 0x00, 0xda, 0xa7, 0x00, 0x4c, 0xaa, 0x20, + 0xc7, 0xd7, 0x20, 0xfc, 0xfd, 0x20, 0x9d, 0x02, + 0x21, 0x96, 0x05, 0x01, 0xf3, 0x08, 0x01, 0xb3, + 0x0c, 0x21, 0x73, 0x11, 0x61, 0x34, 0x13, 0x01, + 0x1b, 0x17, 0x21, 0x8a, 0x1a, 0x01, 0x34, 0x1f, + 0x21, 0xbf, 0x6a, 0x01, 0x23, 0xb1, 0xa1, 0xad, + 0xd4, 0x01, 0x6f, 0xd7, 0x01, 0xff, 0xe7, 0x61, + 0x5e, 0xee, 0x01, 0xe1, 0xeb, 0x22, 0xb0, 0x23, + 0x03, +}; + +static const uint8_t unicode_prop_ID_Continue1_table[660] = { + 0xaf, 0x89, 0xa4, 0x80, 0xd6, 0x80, 0x42, 0x47, + 0xef, 0x96, 0x80, 0x40, 0xfa, 0x84, 0x41, 0x08, + 0xac, 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, + 0x9e, 0x28, 0xe4, 0x31, 0x29, 0x08, 0x19, 0x89, + 0x96, 0x80, 0x9d, 0x9a, 0xda, 0x8a, 0x8e, 0x89, + 0xa0, 0x88, 0x88, 0x80, 0x97, 0x18, 0x88, 0x02, + 0x04, 0xaa, 0x82, 0xbb, 0x87, 0xa9, 0x97, 0x80, + 0xa0, 0xb5, 0x10, 0x91, 0x06, 0x89, 0x09, 0x89, + 0x90, 0x82, 0xb7, 0x00, 0x31, 0x09, 0x82, 0x88, + 0x80, 0x89, 0x09, 0x89, 0x8d, 0x01, 0x82, 0xb7, + 0x00, 0x23, 0x09, 0x12, 0x80, 0x93, 0x8b, 0x10, + 0x8a, 0x82, 0xb7, 0x00, 0x38, 0x10, 0x82, 0x93, + 0x09, 0x89, 0x89, 0x28, 0x82, 0xb7, 0x00, 0x31, + 0x09, 0x16, 0x82, 0x89, 0x09, 0x89, 0x91, 0x80, + 0xba, 0x22, 0x10, 0x83, 0x88, 0x80, 0x8d, 0x89, + 0x8f, 0x84, 0xb6, 0x00, 0x30, 0x10, 0x1e, 0x81, + 0x8a, 0x09, 0x89, 0x90, 0x82, 0xb7, 0x00, 0x30, + 0x10, 0x1e, 0x81, 0x8a, 0x09, 0x89, 0x10, 0x8b, + 0x83, 0xb6, 0x08, 0x30, 0x10, 0x83, 0x88, 0x80, + 0x89, 0x09, 0x89, 0x90, 0x82, 0xc5, 0x03, 0x28, + 0x00, 0x3d, 0x89, 0x09, 0xbc, 0x01, 0x86, 0x8b, + 0x38, 0x89, 0xd6, 0x01, 0x88, 0x8a, 0x30, 0x89, + 0xbd, 0x0d, 0x89, 0x8a, 0x00, 0x00, 0x03, 0x81, + 0xb0, 0x93, 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, + 0x80, 0xe3, 0x93, 0x80, 0x89, 0x8b, 0x1b, 0x10, + 0x11, 0x32, 0x83, 0x8c, 0x8b, 0x80, 0x8e, 0x42, + 0xbe, 0x82, 0x88, 0x88, 0x43, 0x9f, 0x83, 0x9b, + 0x82, 0x9c, 0x81, 0x9d, 0x81, 0xbf, 0x9f, 0x88, + 0x01, 0x89, 0xa0, 0x10, 0x8a, 0x40, 0x8e, 0x80, + 0xf5, 0x8b, 0x83, 0x8b, 0x89, 0x89, 0xff, 0x8a, + 0xbb, 0x84, 0xb8, 0x89, 0x80, 0x9c, 0x81, 0x8a, + 0x85, 0x89, 0x95, 0x8d, 0x80, 0x8f, 0xb0, 0x84, + 0xae, 0x90, 0x8a, 0x89, 0x90, 0x88, 0x8b, 0x82, + 0x9d, 0x8c, 0x81, 0x89, 0xab, 0x8d, 0xaf, 0x93, + 0x87, 0x89, 0x85, 0x89, 0xf5, 0x10, 0x94, 0x18, + 0x28, 0x0a, 0x40, 0xc5, 0xbf, 0x42, 0x3e, 0x81, + 0x92, 0x80, 0xfa, 0x8c, 0x18, 0x82, 0x8b, 0x4b, + 0xfd, 0x82, 0x40, 0x8c, 0x80, 0xdf, 0x9f, 0x42, + 0x29, 0x85, 0xe8, 0x81, 0x60, 0x75, 0x84, 0x89, + 0xc4, 0x03, 0x89, 0x9f, 0x81, 0xcf, 0x81, 0x41, + 0x0f, 0x02, 0x03, 0x80, 0x96, 0x23, 0x80, 0xd2, + 0x81, 0xb1, 0x91, 0x89, 0x89, 0x85, 0x91, 0x8c, + 0x8a, 0x9b, 0x87, 0x98, 0x8c, 0xab, 0x83, 0xae, + 0x8d, 0x8e, 0x89, 0x8a, 0x80, 0x89, 0x89, 0xae, + 0x8d, 0x8b, 0x07, 0x09, 0x89, 0xa0, 0x82, 0xb1, + 0x00, 0x11, 0x0c, 0x08, 0x80, 0xa8, 0x24, 0x81, + 0x40, 0xeb, 0x38, 0x09, 0x89, 0x60, 0x4f, 0x23, + 0x80, 0x42, 0xe0, 0x8f, 0x8f, 0x8f, 0x11, 0x97, + 0x82, 0x40, 0xbf, 0x89, 0xa4, 0x80, 0x42, 0xbc, + 0x80, 0x40, 0xe1, 0x80, 0x40, 0x94, 0x84, 0x41, + 0x24, 0x89, 0x45, 0x56, 0x10, 0x0c, 0x83, 0xa7, + 0x13, 0x80, 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x1f, + 0x89, 0x41, 0x70, 0x81, 0xcf, 0x82, 0xc5, 0x8a, + 0xb0, 0x83, 0xf9, 0x82, 0xb4, 0x8e, 0x9e, 0x8a, + 0x09, 0x89, 0x83, 0xac, 0x8a, 0x30, 0xac, 0x89, + 0x2a, 0xa3, 0x8d, 0x80, 0x89, 0x21, 0xab, 0x80, + 0x8b, 0x82, 0xaf, 0x8d, 0x3b, 0x80, 0x8b, 0xd1, + 0x8b, 0x28, 0x08, 0x40, 0x9c, 0x8b, 0x84, 0x89, + 0x2b, 0xb6, 0x08, 0x31, 0x09, 0x82, 0x88, 0x80, + 0x89, 0x09, 0x32, 0x84, 0x40, 0xbf, 0x91, 0x88, + 0x89, 0x18, 0xd0, 0x93, 0x8b, 0x89, 0x40, 0xd4, + 0x31, 0x88, 0x9a, 0x81, 0xd1, 0x90, 0x8e, 0x89, + 0xd0, 0x8c, 0x87, 0x89, 0xd2, 0x8e, 0x83, 0x89, + 0x40, 0xf1, 0x8e, 0x40, 0xa4, 0x89, 0xc5, 0x28, + 0x09, 0x18, 0x00, 0x81, 0x8b, 0x89, 0xf6, 0x31, + 0x32, 0x80, 0x9b, 0x89, 0xa7, 0x30, 0x1f, 0x80, + 0x88, 0x8a, 0xad, 0x8f, 0x41, 0x94, 0x38, 0x87, + 0x8f, 0x89, 0xb7, 0x95, 0x80, 0x8d, 0xf9, 0x2a, + 0x00, 0x08, 0x30, 0x07, 0x89, 0xaf, 0x20, 0x08, + 0x27, 0x89, 0x41, 0x48, 0x83, 0x88, 0x08, 0x80, + 0xaf, 0x32, 0x84, 0x8c, 0x89, 0x54, 0xe5, 0x05, + 0x8e, 0x60, 0x36, 0x09, 0x89, 0xd5, 0x89, 0xa5, + 0x84, 0xba, 0x86, 0x98, 0x89, 0x43, 0xf4, 0x00, + 0xb6, 0x33, 0xd0, 0x80, 0x8a, 0x81, 0x60, 0x4c, + 0xaa, 0x81, 0x52, 0x60, 0xad, 0x81, 0x96, 0x42, + 0x1d, 0x22, 0x2f, 0x39, 0x86, 0x9d, 0x83, 0x40, + 0x93, 0x82, 0x45, 0x88, 0xb1, 0x41, 0xff, 0xb6, + 0x83, 0xb1, 0x38, 0x8d, 0x80, 0x95, 0x20, 0x8e, + 0x45, 0x4f, 0x30, 0x90, 0x0e, 0x01, 0x04, 0xe3, + 0x80, 0x40, 0x9f, 0x86, 0x88, 0x89, 0x41, 0x63, + 0x80, 0xbc, 0x8d, 0x41, 0xf1, 0x8d, 0x43, 0xd5, + 0x86, 0xec, 0x34, 0x89, 0x52, 0x95, 0x89, 0x6c, + 0x05, 0x05, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_ID_Continue1_index[63] = { + 0xfa, 0x06, 0x00, 0x70, 0x09, 0x00, 0xf0, 0x0a, + 0x40, 0x57, 0x0c, 0x00, 0xf0, 0x0d, 0x60, 0xc7, + 0x0f, 0x20, 0xea, 0x17, 0x40, 0x05, 0x1b, 0x00, + 0x41, 0x20, 0x00, 0x0c, 0xa8, 0x80, 0x37, 0xaa, + 0x20, 0x50, 0xfe, 0x20, 0x3a, 0x0d, 0x21, 0x74, + 0x11, 0x01, 0x5a, 0x14, 0x21, 0x44, 0x19, 0x81, + 0x5a, 0x1d, 0xa1, 0xf5, 0x6a, 0x21, 0x45, 0xd2, + 0x41, 0xaf, 0xe2, 0x21, 0xf0, 0x01, 0x0e, +}; + +#ifdef CONFIG_ALL_UNICODE + +static const uint8_t unicode_cc_table[899] = { + 0xb2, 0xcf, 0xd4, 0x00, 0xe8, 0x03, 0xdc, 0x00, + 0xe8, 0x00, 0xd8, 0x04, 0xdc, 0x01, 0xca, 0x03, + 0xdc, 0x01, 0xca, 0x0a, 0xdc, 0x04, 0x01, 0x03, + 0xdc, 0xc7, 0x00, 0xf0, 0xc0, 0x02, 0xdc, 0xc2, + 0x01, 0xdc, 0x80, 0xc2, 0x03, 0xdc, 0xc0, 0x00, + 0xe8, 0x01, 0xdc, 0xc0, 0x41, 0xe9, 0x00, 0xea, + 0x41, 0xe9, 0x00, 0xea, 0x00, 0xe9, 0xcc, 0xb0, + 0xe2, 0xc4, 0xb0, 0xd8, 0x00, 0xdc, 0xc3, 0x00, + 0xdc, 0xc2, 0x00, 0xde, 0x00, 0xdc, 0xc5, 0x05, + 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xde, 0x00, + 0xe4, 0xc0, 0x49, 0x0a, 0x43, 0x13, 0x80, 0x00, + 0x17, 0x80, 0x41, 0x18, 0x80, 0xc0, 0x00, 0xdc, + 0x80, 0x00, 0x12, 0xb0, 0x17, 0xc7, 0x42, 0x1e, + 0xaf, 0x47, 0x1b, 0xc1, 0x01, 0xdc, 0xc4, 0x00, + 0xdc, 0xc1, 0x00, 0xdc, 0x8f, 0x00, 0x23, 0xb0, + 0x34, 0xc6, 0x81, 0xc3, 0x00, 0xdc, 0xc0, 0x81, + 0xc1, 0x80, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xa2, + 0x00, 0x24, 0x9d, 0xc0, 0x00, 0xdc, 0xc1, 0x00, + 0xdc, 0xc1, 0x02, 0xdc, 0xc0, 0x01, 0xdc, 0xc0, + 0x00, 0xdc, 0xc2, 0x00, 0xdc, 0xc0, 0x00, 0xdc, + 0xc0, 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, + 0x6f, 0xc6, 0x00, 0xdc, 0xc0, 0x88, 0x00, 0xdc, + 0x97, 0xc3, 0x80, 0xc8, 0x80, 0xc2, 0x80, 0xc4, + 0xaa, 0x02, 0xdc, 0xb0, 0x0b, 0xc0, 0x02, 0xdc, + 0xc3, 0xa9, 0xc4, 0x04, 0xdc, 0xcd, 0x80, 0x00, + 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xc2, + 0x02, 0xdc, 0x42, 0x1b, 0xc2, 0x00, 0xdc, 0xc1, + 0x01, 0xdc, 0xc4, 0xb0, 0x0b, 0x00, 0x07, 0x8f, + 0x00, 0x09, 0x82, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, + 0x36, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xaf, 0xc0, + 0xb0, 0x0c, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, + 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3d, + 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x4e, 0x00, + 0x09, 0xb0, 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, + 0x86, 0x00, 0x54, 0x00, 0x5b, 0xb0, 0x34, 0x00, + 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3c, 0x01, 0x09, + 0x8f, 0x00, 0x09, 0xb0, 0x4b, 0x00, 0x09, 0xb0, + 0x3c, 0x01, 0x67, 0x00, 0x09, 0x8c, 0x03, 0x6b, + 0xb0, 0x3b, 0x01, 0x76, 0x00, 0x09, 0x8c, 0x03, + 0x7a, 0xb0, 0x1b, 0x01, 0xdc, 0x9a, 0x00, 0xdc, + 0x80, 0x00, 0xdc, 0x80, 0x00, 0xd8, 0xb0, 0x06, + 0x41, 0x81, 0x80, 0x00, 0x84, 0x84, 0x03, 0x82, + 0x81, 0x00, 0x82, 0x80, 0xc1, 0x00, 0x09, 0x80, + 0xc1, 0xb0, 0x0d, 0x00, 0xdc, 0xb0, 0x3f, 0x00, + 0x07, 0x80, 0x01, 0x09, 0xb0, 0x21, 0x00, 0xdc, + 0xb2, 0x9e, 0xc2, 0xb3, 0x83, 0x01, 0x09, 0x9d, + 0x00, 0x09, 0xb0, 0x6c, 0x00, 0x09, 0x89, 0xc0, + 0xb0, 0x9a, 0x00, 0xe4, 0xb0, 0x5e, 0x00, 0xde, + 0xc0, 0x00, 0xdc, 0xb0, 0xaa, 0xc0, 0x00, 0xdc, + 0xb0, 0x16, 0x00, 0x09, 0x93, 0xc7, 0x81, 0x00, + 0xdc, 0xaf, 0xc4, 0x05, 0xdc, 0xc1, 0x00, 0xdc, + 0x80, 0x01, 0xdc, 0xc1, 0x01, 0xdc, 0xc4, 0x00, + 0xdc, 0xc3, 0xb0, 0x34, 0x00, 0x07, 0x8e, 0x00, + 0x09, 0xa5, 0xc0, 0x00, 0xdc, 0xc6, 0xb0, 0x05, + 0x01, 0x09, 0xb0, 0x09, 0x00, 0x07, 0x8a, 0x01, + 0x09, 0xb0, 0x12, 0x00, 0x07, 0xb0, 0x67, 0xc2, + 0x41, 0x00, 0x04, 0xdc, 0xc1, 0x03, 0xdc, 0xc0, + 0x41, 0x00, 0x05, 0x01, 0x83, 0x00, 0xdc, 0x85, + 0xc0, 0x82, 0xc1, 0xb0, 0x95, 0xc1, 0x00, 0xdc, + 0xc6, 0x00, 0xdc, 0xc1, 0x00, 0xea, 0x00, 0xd6, + 0x00, 0xdc, 0x00, 0xca, 0xe4, 0x00, 0xe8, 0x01, + 0xe4, 0x00, 0xdc, 0x00, 0xda, 0xc0, 0x00, 0xe9, + 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xb2, 0x9f, 0xc1, + 0x01, 0x01, 0xc3, 0x02, 0x01, 0xc1, 0x83, 0xc0, + 0x82, 0x01, 0x01, 0xc0, 0x00, 0xdc, 0xc0, 0x01, + 0x01, 0x03, 0xdc, 0xc0, 0xb8, 0x03, 0xcd, 0xc2, + 0xb0, 0x5c, 0x00, 0x09, 0xb0, 0x2f, 0xdf, 0xb1, + 0xf9, 0x00, 0xda, 0x00, 0xe4, 0x00, 0xe8, 0x00, + 0xde, 0x01, 0xe0, 0xb0, 0x38, 0x01, 0x08, 0xb8, + 0x6d, 0xa3, 0xc0, 0x83, 0xc9, 0x9f, 0xc1, 0xb0, + 0x1f, 0xc1, 0xb0, 0xe3, 0x00, 0x09, 0xa4, 0x00, + 0x09, 0xb0, 0x66, 0x00, 0x09, 0x9a, 0xd1, 0xb0, + 0x08, 0x02, 0xdc, 0xa4, 0x00, 0x09, 0xb0, 0x2e, + 0x00, 0x07, 0x8b, 0x00, 0x09, 0xb0, 0xbe, 0xc0, + 0x80, 0xc1, 0x00, 0xdc, 0x81, 0xc1, 0x84, 0xc1, + 0x80, 0xc0, 0xb0, 0x03, 0x00, 0x09, 0xb0, 0xc5, + 0x00, 0x09, 0xb8, 0x46, 0xff, 0x00, 0x1a, 0xb2, + 0xd0, 0xc6, 0x06, 0xdc, 0xc1, 0xb3, 0x9c, 0x00, + 0xdc, 0xb0, 0xb1, 0x00, 0xdc, 0xb0, 0x64, 0xc4, + 0xb6, 0x61, 0x00, 0xdc, 0x80, 0xc0, 0xa7, 0xc0, + 0x00, 0x01, 0x00, 0xdc, 0x83, 0x00, 0x09, 0xb0, + 0x74, 0xc0, 0x00, 0xdc, 0xb2, 0x0c, 0xc3, 0xb1, + 0x52, 0xc1, 0xb0, 0x1f, 0x02, 0xdc, 0xb0, 0x15, + 0x01, 0xdc, 0xc2, 0x00, 0xdc, 0xc0, 0x03, 0xdc, + 0xb0, 0x00, 0xc0, 0x00, 0xdc, 0xc0, 0x00, 0xdc, + 0xb0, 0x8f, 0x00, 0x09, 0xa8, 0x00, 0x09, 0x8d, + 0x00, 0x09, 0xb0, 0x08, 0x00, 0x09, 0x00, 0x07, + 0xb0, 0x14, 0xc2, 0xaf, 0x01, 0x09, 0xb0, 0x0d, + 0x00, 0x07, 0xb0, 0x1b, 0x00, 0x09, 0x88, 0x00, + 0x07, 0xb0, 0x39, 0x00, 0x09, 0x00, 0x07, 0xb0, + 0x81, 0x00, 0x07, 0x00, 0x09, 0xb0, 0x1f, 0x01, + 0x07, 0x8f, 0x00, 0x09, 0x97, 0xc6, 0x82, 0xc4, + 0xb0, 0x9c, 0x00, 0x09, 0x82, 0x00, 0x07, 0x96, + 0xc0, 0xb0, 0x32, 0x00, 0x09, 0x00, 0x07, 0xb0, + 0xca, 0x00, 0x09, 0x00, 0x07, 0xb0, 0x4d, 0x00, + 0x09, 0xb0, 0x45, 0x00, 0x09, 0x00, 0x07, 0xb0, + 0x42, 0x00, 0x09, 0xb0, 0xdc, 0x00, 0x09, 0x00, + 0x07, 0xb0, 0xd1, 0x01, 0x09, 0x83, 0x00, 0x07, + 0xb0, 0x6b, 0x00, 0x09, 0xb0, 0x22, 0x00, 0x09, + 0x91, 0x00, 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, + 0x74, 0x00, 0x09, 0xb0, 0xd1, 0x00, 0x07, 0x80, + 0x01, 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, 0x78, + 0x01, 0x09, 0xb8, 0x43, 0x7c, 0x04, 0x01, 0xb0, + 0x0a, 0xc6, 0xb4, 0x88, 0x01, 0x06, 0xb8, 0x44, + 0x7b, 0x00, 0x01, 0xb8, 0x0c, 0x95, 0x01, 0xd8, + 0x02, 0x01, 0x82, 0x00, 0xe2, 0x04, 0xd8, 0x87, + 0x07, 0xdc, 0x81, 0xc4, 0x01, 0xdc, 0x9d, 0xc3, + 0xb0, 0x63, 0xc2, 0xb8, 0x05, 0x8a, 0xc6, 0x80, + 0xd0, 0x81, 0xc6, 0x80, 0xc1, 0x80, 0xc4, 0xb0, + 0x33, 0xc0, 0xb0, 0x6f, 0xc6, 0xb1, 0x46, 0xc0, + 0xb0, 0x0c, 0xc3, 0xb1, 0xcb, 0x01, 0xe8, 0x00, + 0xdc, 0xc0, 0xb3, 0xaf, 0x06, 0xdc, 0xb0, 0x3c, + 0xc5, 0x00, 0x07, +}; + +static const uint8_t unicode_cc_index[87] = { + 0x4d, 0x03, 0x00, 0x97, 0x05, 0x20, 0xc6, 0x05, + 0x00, 0xe7, 0x06, 0x00, 0x45, 0x07, 0x00, 0x9c, + 0x08, 0x00, 0x4d, 0x09, 0x00, 0x3c, 0x0b, 0x00, + 0x3d, 0x0d, 0x00, 0x36, 0x0f, 0x00, 0x38, 0x10, + 0x20, 0x3a, 0x19, 0x00, 0xcb, 0x1a, 0x20, 0xd3, + 0x1c, 0x00, 0xcf, 0x1d, 0x00, 0xe2, 0x20, 0x00, + 0x2e, 0x30, 0x20, 0x2b, 0xa9, 0x20, 0xed, 0xab, + 0x00, 0x39, 0x0a, 0x01, 0x51, 0x0f, 0x01, 0x73, + 0x11, 0x01, 0x75, 0x13, 0x01, 0x2b, 0x17, 0x21, + 0x3f, 0x1c, 0x21, 0x9e, 0xbc, 0x21, 0x08, 0xe0, + 0x01, 0x44, 0xe9, 0x01, 0x4b, 0xe9, 0x01, +}; + +static const uint32_t unicode_decomp_table1[699] = { + 0x00280081, 0x002a0097, 0x002a8081, 0x002bc097, + 0x002c8115, 0x002d0097, 0x002d4081, 0x002e0097, + 0x002e4115, 0x002f0199, 0x00302016, 0x00400842, + 0x00448a42, 0x004a0442, 0x004c0096, 0x004c8117, + 0x004d0242, 0x004e4342, 0x004fc12f, 0x0050c342, + 0x005240bf, 0x00530342, 0x00550942, 0x005a0842, + 0x005e0096, 0x005e4342, 0x005fc081, 0x00680142, + 0x006bc142, 0x00710185, 0x0071c317, 0x00734844, + 0x00778344, 0x00798342, 0x007b02be, 0x007c4197, + 0x007d0142, 0x007e0444, 0x00800e42, 0x00878142, + 0x00898744, 0x00ac0483, 0x00b60317, 0x00b80283, + 0x00d00214, 0x00d10096, 0x00dd0080, 0x00de8097, + 0x00df8080, 0x00e10097, 0x00e1413e, 0x00e1c080, + 0x00e204be, 0x00ea83ae, 0x00f282ae, 0x00f401ad, + 0x00f4c12e, 0x00f54103, 0x00fc0303, 0x00fe4081, + 0x0100023e, 0x0101c0be, 0x010301be, 0x010640be, + 0x010e40be, 0x0114023e, 0x0115c0be, 0x011701be, + 0x011d8144, 0x01304144, 0x01340244, 0x01358144, + 0x01368344, 0x01388344, 0x013a8644, 0x013e0144, + 0x0161c085, 0x018882ae, 0x019d422f, 0x01b00184, + 0x01b4c084, 0x024a4084, 0x024c4084, 0x024d0084, + 0x0256042e, 0x0272c12e, 0x02770120, 0x0277c084, + 0x028cc084, 0x028d8084, 0x029641ae, 0x02978084, + 0x02d20084, 0x02d2c12e, 0x02d70120, 0x02e50084, + 0x02f281ae, 0x03120084, 0x03300084, 0x0331c122, + 0x0332812e, 0x035281ae, 0x03768084, 0x037701ae, + 0x038cc085, 0x03acc085, 0x03b7012f, 0x03c30081, + 0x03d0c084, 0x03d34084, 0x03d48084, 0x03d5c084, + 0x03d70084, 0x03da4084, 0x03dcc084, 0x03dd412e, + 0x03ddc085, 0x03de0084, 0x03de4085, 0x03e04084, + 0x03e4c084, 0x03e74084, 0x03e88084, 0x03e9c084, + 0x03eb0084, 0x03ee4084, 0x04098084, 0x043f0081, + 0x06c18484, 0x06c48084, 0x06cec184, 0x06d00120, + 0x06d0c084, 0x074b0383, 0x074cc41f, 0x074f1783, + 0x075e0081, 0x0766d283, 0x07801d44, 0x078e8942, + 0x07931844, 0x079f0d42, 0x07a58216, 0x07a68085, + 0x07a6c0be, 0x07a80d44, 0x07aea044, 0x07c00122, + 0x07c08344, 0x07c20122, 0x07c28344, 0x07c40122, + 0x07c48244, 0x07c60122, 0x07c68244, 0x07c8113e, + 0x07d08244, 0x07d20122, 0x07d28244, 0x07d40122, + 0x07d48344, 0x07d64c3e, 0x07dc4080, 0x07dc80be, + 0x07dcc080, 0x07dd00be, 0x07dd4080, 0x07dd80be, + 0x07ddc080, 0x07de00be, 0x07de4080, 0x07de80be, + 0x07dec080, 0x07df00be, 0x07df4080, 0x07e00820, + 0x07e40820, 0x07e80820, 0x07ec05be, 0x07eec080, + 0x07ef00be, 0x07ef4097, 0x07ef8080, 0x07efc117, + 0x07f0443e, 0x07f24080, 0x07f280be, 0x07f2c080, + 0x07f303be, 0x07f4c080, 0x07f582ae, 0x07f6c080, + 0x07f7433e, 0x07f8c080, 0x07f903ae, 0x07fac080, + 0x07fb013e, 0x07fb8102, 0x07fc83be, 0x07fe4080, + 0x07fe80be, 0x07fec080, 0x07ff00be, 0x07ff4080, + 0x07ff8097, 0x0800011e, 0x08008495, 0x08044081, + 0x0805c097, 0x08090081, 0x08094097, 0x08098099, + 0x080bc081, 0x080cc085, 0x080d00b1, 0x080d8085, + 0x080dc0b1, 0x080f0197, 0x0811c197, 0x0815c0b3, + 0x0817c081, 0x081c0595, 0x081ec081, 0x081f0215, + 0x0820051f, 0x08228583, 0x08254415, 0x082a0097, + 0x08400119, 0x08408081, 0x0840c0bf, 0x08414119, + 0x0841c081, 0x084240bf, 0x0842852d, 0x08454081, + 0x08458097, 0x08464295, 0x08480097, 0x08484099, + 0x08488097, 0x08490081, 0x08498080, 0x084a0081, + 0x084a8102, 0x084b0495, 0x084d421f, 0x084e4081, + 0x084ec099, 0x084f0283, 0x08514295, 0x08540119, + 0x0854809b, 0x0854c619, 0x0857c097, 0x08580081, + 0x08584097, 0x08588099, 0x0858c097, 0x08590081, + 0x08594097, 0x08598099, 0x0859c09b, 0x085a0097, + 0x085a4081, 0x085a8097, 0x085ac099, 0x085b0295, + 0x085c4097, 0x085c8099, 0x085cc097, 0x085d0081, + 0x085d4097, 0x085d8099, 0x085dc09b, 0x085e0097, + 0x085e4081, 0x085e8097, 0x085ec099, 0x085f0215, + 0x08624099, 0x0866813e, 0x086b80be, 0x087341be, + 0x088100be, 0x088240be, 0x088300be, 0x088901be, + 0x088b0085, 0x088b40b1, 0x088bc085, 0x088c00b1, + 0x089040be, 0x089100be, 0x0891c1be, 0x089801be, + 0x089b42be, 0x089d0144, 0x089e0144, 0x08a00144, + 0x08a10144, 0x08a20144, 0x08ab023e, 0x08b80244, + 0x08ba8220, 0x08ca411e, 0x0918049f, 0x091a4523, + 0x091cc097, 0x091d04a5, 0x091f452b, 0x0921c09b, + 0x092204a1, 0x09244525, 0x0926c099, 0x09270d25, + 0x092d8d1f, 0x09340d1f, 0x093a8081, 0x0a8300b3, + 0x0a9d0099, 0x0a9d4097, 0x0a9d8099, 0x0ab700be, + 0x0b1f0115, 0x0b5bc081, 0x0ba7c081, 0x0bbcc081, + 0x0bc004ad, 0x0bc244ad, 0x0bc484ad, 0x0bc6f383, + 0x0be0852d, 0x0be31d03, 0x0bf1882d, 0x0c000081, + 0x0c0d8283, 0x0c130b84, 0x0c194284, 0x0c1c0122, + 0x0c1cc122, 0x0c1d8122, 0x0c1e4122, 0x0c1f0122, + 0x0c250084, 0x0c26c123, 0x0c278084, 0x0c27c085, + 0x0c2b0b84, 0x0c314284, 0x0c340122, 0x0c34c122, + 0x0c358122, 0x0c364122, 0x0c370122, 0x0c3d0084, + 0x0c3dc220, 0x0c3f8084, 0x0c3fc085, 0x0c4c4a2d, + 0x0c51451f, 0x0c53ca9f, 0x0c5915ad, 0x0c648703, + 0x0c800741, 0x0c838089, 0x0c83c129, 0x0c8441a9, + 0x0c850089, 0x0c854129, 0x0c85c2a9, 0x0c870089, + 0x0c87408f, 0x0c87808d, 0x0c881241, 0x0c910203, + 0x0c940099, 0x0c9444a3, 0x0c968323, 0x0c98072d, + 0x0c9b84af, 0x0c9dc2a1, 0x0c9f00b5, 0x0c9f40b3, + 0x0c9f8085, 0x0ca01883, 0x0cac4223, 0x0cad4523, + 0x0cafc097, 0x0cb004a1, 0x0cb241a5, 0x0cb30097, + 0x0cb34099, 0x0cb38097, 0x0cb3c099, 0x0cb417ad, + 0x0cbfc085, 0x0cc001b3, 0x0cc0c0b1, 0x0cc100b3, + 0x0cc14131, 0x0cc1c0b5, 0x0cc200b3, 0x0cc241b1, + 0x0cc30133, 0x0cc38131, 0x0cc40085, 0x0cc440b1, + 0x0cc48133, 0x0cc50085, 0x0cc540b5, 0x0cc580b7, + 0x0cc5c0b5, 0x0cc600b1, 0x0cc64135, 0x0cc6c0b3, + 0x0cc701b1, 0x0cc7c0b3, 0x0cc800b5, 0x0cc840b3, + 0x0cc881b1, 0x0cc9422f, 0x0cca4131, 0x0ccac0b5, + 0x0ccb00b1, 0x0ccb40b3, 0x0ccb80b5, 0x0ccbc0b1, + 0x0ccc012f, 0x0ccc80b5, 0x0cccc0b3, 0x0ccd00b5, + 0x0ccd40b1, 0x0ccd80b5, 0x0ccdc085, 0x0cce02b1, + 0x0ccf40b3, 0x0ccf80b1, 0x0ccfc085, 0x0cd001b1, + 0x0cd0c0b3, 0x0cd101b1, 0x0cd1c0b5, 0x0cd200b3, + 0x0cd24085, 0x0cd280b5, 0x0cd2c085, 0x0cd30133, + 0x0cd381b1, 0x0cd440b3, 0x0cd48085, 0x0cd4c0b1, + 0x0cd500b3, 0x0cd54085, 0x0cd580b5, 0x0cd5c0b1, + 0x0cd60521, 0x0cd88525, 0x0cdb02a5, 0x0cdc4099, + 0x0cdc8117, 0x0cdd0099, 0x0cdd4197, 0x0cde0127, + 0x0cde8285, 0x0cdfc089, 0x0ce0043f, 0x0ce20099, + 0x0ce2409b, 0x0ce283bf, 0x0ce44219, 0x0ce54205, + 0x0ce6433f, 0x0ce7c131, 0x0ce84085, 0x0ce881b1, + 0x0ce94085, 0x0ce98107, 0x0cea0089, 0x0cea4097, + 0x0cea8219, 0x0ceb809d, 0x0cebc08d, 0x0cec083f, + 0x0cf00105, 0x0cf0809b, 0x0cf0c197, 0x0cf1809b, + 0x0cf1c099, 0x0cf20517, 0x0cf48099, 0x0cf4c117, + 0x0cf54119, 0x0cf5c097, 0x0cf6009b, 0x0cf64099, + 0x0cf68217, 0x0cf78119, 0x0cf804a1, 0x0cfa4525, + 0x0cfcc525, 0x0cff4125, 0x0cffc099, 0x29a70103, + 0x29dc0081, 0x29fc8195, 0x29fe0103, 0x2ad70203, + 0x2ada4081, 0x3e401482, 0x3e4a7f82, 0x3e6a3f82, + 0x3e8aa102, 0x3e9b0110, 0x3e9c2f82, 0x3eb3c590, + 0x3ec00197, 0x3ec0c119, 0x3ec1413f, 0x3ec4c2af, + 0x3ec74184, 0x3ec804ad, 0x3eca4081, 0x3eca8304, + 0x3ecc03a0, 0x3ece02a0, 0x3ecf8084, 0x3ed00120, + 0x3ed0c120, 0x3ed184ae, 0x3ed3c085, 0x3ed4312d, + 0x3ef4cbad, 0x3efa892f, 0x3eff022d, 0x3f002f2f, + 0x3f1782a5, 0x3f18c0b1, 0x3f1907af, 0x3f1cffaf, + 0x3f3c81a5, 0x3f3d64af, 0x3f542031, 0x3f649b31, + 0x3f7c0131, 0x3f7c83b3, 0x3f7e40b1, 0x3f7e80bd, + 0x3f7ec0bb, 0x3f7f00b3, 0x3f840503, 0x3f8c01ad, + 0x3f8cc315, 0x3f8e462d, 0x3f91cc03, 0x3f97c695, + 0x3f9c01af, 0x3f9d0085, 0x3f9d852f, 0x3fa03aad, + 0x3fbd442f, 0x3fc06f1f, 0x3fd7c11f, 0x3fd85fad, + 0x3fe80081, 0x3fe84f1f, 0x3ff0831f, 0x3ff2831f, + 0x3ff4831f, 0x3ff6819f, 0x3ff80783, 0x41e04d83, + 0x41e70f91, 0x44268192, 0x442ac092, 0x444b8112, + 0x44d2c112, 0x452ec212, 0x456e8112, 0x464e0092, + 0x74578392, 0x746ec312, 0x75000d1f, 0x75068d1f, + 0x750d0d1f, 0x7513839f, 0x7515891f, 0x751a0d1f, + 0x75208d1f, 0x75271015, 0x752f439f, 0x7531459f, + 0x75340d1f, 0x753a8d1f, 0x75410395, 0x7543441f, + 0x7545839f, 0x75478d1f, 0x754e0795, 0x7552839f, + 0x75548d1f, 0x755b0d1f, 0x75618d1f, 0x75680d1f, + 0x756e8d1f, 0x75750d1f, 0x757b8d1f, 0x75820d1f, + 0x75888d1f, 0x758f0d1f, 0x75958d1f, 0x759c0d1f, + 0x75a28d1f, 0x75a90103, 0x75aa089f, 0x75ae4081, + 0x75ae839f, 0x75b04081, 0x75b08c9f, 0x75b6c081, + 0x75b7032d, 0x75b8889f, 0x75bcc081, 0x75bd039f, + 0x75bec081, 0x75bf0c9f, 0x75c54081, 0x75c5832d, + 0x75c7089f, 0x75cb4081, 0x75cb839f, 0x75cd4081, + 0x75cd8c9f, 0x75d3c081, 0x75d4032d, 0x75d5889f, + 0x75d9c081, 0x75da039f, 0x75dbc081, 0x75dc0c9f, + 0x75e24081, 0x75e2832d, 0x75e4089f, 0x75e84081, + 0x75e8839f, 0x75ea4081, 0x75ea8c9f, 0x75f0c081, + 0x75f1042d, 0x75f3851f, 0x75f6051f, 0x75f8851f, + 0x75fb051f, 0x75fd851f, 0x780c049f, 0x780e419f, + 0x780f059f, 0x7811c203, 0x7812d0ad, 0x781b0103, + 0x7b80022d, 0x7b814dad, 0x7b884203, 0x7b89c081, + 0x7b8a452d, 0x7b8d0403, 0x7b908081, 0x7b91dc03, + 0x7ba0052d, 0x7ba2c8ad, 0x7ba84483, 0x7baac8ad, + 0x7c400097, 0x7c404521, 0x7c440d25, 0x7c4a8087, + 0x7c4ac115, 0x7c4b4117, 0x7c4c0d1f, 0x7c528217, + 0x7c538099, 0x7c53c097, 0x7c5a8197, 0x7c640097, + 0x7c80012f, 0x7c808081, 0x7c841603, 0x7c9004c1, + 0x7c940103, 0x7efc051f, 0xbe0001ac, 0xbe00d110, + 0xbe0947ac, 0xbe0d3910, 0xbe29872c, 0xbe2d022c, + 0xbe2e3790, 0xbe49ff90, 0xbe69bc10, +}; + +static const uint16_t unicode_decomp_table2[699] = { + 0x0020, 0x0000, 0x0061, 0x0002, 0x0004, 0x0006, 0x03bc, 0x0008, + 0x000a, 0x000c, 0x0015, 0x0095, 0x00a5, 0x00b9, 0x00c1, 0x00c3, + 0x00c7, 0x00cb, 0x00d1, 0x00d7, 0x00dd, 0x00e0, 0x00e6, 0x00f8, + 0x0108, 0x010a, 0x0073, 0x0110, 0x0112, 0x0114, 0x0120, 0x012c, + 0x0144, 0x014d, 0x0153, 0x0162, 0x0168, 0x016a, 0x0176, 0x0192, + 0x0194, 0x01a9, 0x01bb, 0x01c7, 0x01d1, 0x01d5, 0x02b9, 0x01d7, + 0x003b, 0x01d9, 0x01db, 0x00b7, 0x01e1, 0x01fc, 0x020c, 0x0218, + 0x021d, 0x0223, 0x0227, 0x03a3, 0x0233, 0x023f, 0x0242, 0x024b, + 0x024e, 0x0251, 0x025d, 0x0260, 0x0269, 0x026c, 0x026f, 0x0275, + 0x0278, 0x0281, 0x028a, 0x029c, 0x029f, 0x02a3, 0x02af, 0x02b9, + 0x02c5, 0x02c9, 0x02cd, 0x02d1, 0x02d5, 0x02e7, 0x02ed, 0x02f1, + 0x02f5, 0x02f9, 0x02fd, 0x0305, 0x0309, 0x030d, 0x0313, 0x0317, + 0x031b, 0x0323, 0x0327, 0x032b, 0x032f, 0x0335, 0x033d, 0x0341, + 0x0349, 0x034d, 0x0351, 0x0f0b, 0x0357, 0x035b, 0x035f, 0x0363, + 0x0367, 0x036b, 0x036f, 0x0373, 0x0379, 0x037d, 0x0381, 0x0385, + 0x0389, 0x038d, 0x0391, 0x0395, 0x0399, 0x039d, 0x03a1, 0x10dc, + 0x03a5, 0x03c9, 0x03cd, 0x03d9, 0x03dd, 0x03e1, 0x03ef, 0x03f1, + 0x043d, 0x044f, 0x0499, 0x04f0, 0x0502, 0x054a, 0x0564, 0x056c, + 0x0570, 0x0573, 0x059a, 0x05fa, 0x05fe, 0x0607, 0x060b, 0x0614, + 0x0618, 0x061e, 0x0622, 0x0628, 0x068e, 0x0694, 0x0698, 0x069e, + 0x06a2, 0x06ab, 0x03ac, 0x06f3, 0x03ad, 0x06f6, 0x03ae, 0x06f9, + 0x03af, 0x06fc, 0x03cc, 0x06ff, 0x03cd, 0x0702, 0x03ce, 0x0705, + 0x0709, 0x070d, 0x0711, 0x0386, 0x0732, 0x0735, 0x03b9, 0x0737, + 0x073b, 0x0388, 0x0753, 0x0389, 0x0756, 0x0390, 0x076b, 0x038a, + 0x0777, 0x03b0, 0x0789, 0x038e, 0x0799, 0x079f, 0x07a3, 0x038c, + 0x07b8, 0x038f, 0x07bb, 0x00b4, 0x07be, 0x07c0, 0x07c2, 0x2010, + 0x07cb, 0x002e, 0x07cd, 0x07cf, 0x0020, 0x07d2, 0x07d6, 0x07db, + 0x07df, 0x07e4, 0x07ea, 0x07f0, 0x0020, 0x07f6, 0x2212, 0x0801, + 0x0805, 0x0807, 0x081d, 0x0825, 0x0827, 0x0043, 0x082d, 0x0830, + 0x0190, 0x0836, 0x0839, 0x004e, 0x0845, 0x0847, 0x084c, 0x084e, + 0x0851, 0x005a, 0x03a9, 0x005a, 0x0853, 0x0857, 0x0860, 0x0069, + 0x0862, 0x0865, 0x086f, 0x0874, 0x087a, 0x087e, 0x08a2, 0x0049, + 0x08a4, 0x08a6, 0x08a9, 0x0056, 0x08ab, 0x08ad, 0x08b0, 0x08b4, + 0x0058, 0x08b6, 0x08b8, 0x08bb, 0x08c0, 0x08c2, 0x08c5, 0x0076, + 0x08c7, 0x08c9, 0x08cc, 0x08d0, 0x0078, 0x08d2, 0x08d4, 0x08d7, + 0x08db, 0x08de, 0x08e4, 0x08e7, 0x08f0, 0x08f3, 0x08f6, 0x08f9, + 0x0902, 0x0906, 0x090b, 0x090f, 0x0914, 0x0917, 0x091a, 0x0923, + 0x092c, 0x093b, 0x093e, 0x0941, 0x0944, 0x0947, 0x094a, 0x0956, + 0x095c, 0x0960, 0x0962, 0x0964, 0x0968, 0x096a, 0x0970, 0x0978, + 0x097c, 0x0980, 0x0986, 0x0989, 0x098f, 0x0991, 0x0030, 0x0993, + 0x0999, 0x099c, 0x099e, 0x09a1, 0x09a4, 0x2d61, 0x6bcd, 0x9f9f, + 0x09a6, 0x09b1, 0x09bc, 0x09c7, 0x0a95, 0x0aa1, 0x0b15, 0x0020, + 0x0b27, 0x0b31, 0x0b8d, 0x0ba1, 0x0ba5, 0x0ba9, 0x0bad, 0x0bb1, + 0x0bb5, 0x0bb9, 0x0bbd, 0x0bc1, 0x0bc5, 0x0c21, 0x0c35, 0x0c39, + 0x0c3d, 0x0c41, 0x0c45, 0x0c49, 0x0c4d, 0x0c51, 0x0c55, 0x0c59, + 0x0c6f, 0x0c71, 0x0c73, 0x0ca0, 0x0cbc, 0x0cdc, 0x0ce4, 0x0cec, + 0x0cf4, 0x0cfc, 0x0d04, 0x0d0c, 0x0d14, 0x0d22, 0x0d2e, 0x0d7a, + 0x0d82, 0x0d85, 0x0d89, 0x0d8d, 0x0d9d, 0x0db1, 0x0db5, 0x0dbc, + 0x0dc2, 0x0dc6, 0x0e28, 0x0e2c, 0x0e30, 0x0e32, 0x0e36, 0x0e3c, + 0x0e3e, 0x0e41, 0x0e43, 0x0e46, 0x0e77, 0x0e7b, 0x0e89, 0x0e8e, + 0x0e94, 0x0e9c, 0x0ea3, 0x0ea9, 0x0eb4, 0x0ebe, 0x0ec6, 0x0eca, + 0x0ecf, 0x0ed9, 0x0edd, 0x0ee4, 0x0eec, 0x0ef3, 0x0ef8, 0x0f04, + 0x0f0a, 0x0f15, 0x0f1b, 0x0f22, 0x0f28, 0x0f33, 0x0f3d, 0x0f45, + 0x0f4c, 0x0f51, 0x0f57, 0x0f5e, 0x0f63, 0x0f69, 0x0f70, 0x0f76, + 0x0f7d, 0x0f82, 0x0f89, 0x0f8d, 0x0f9e, 0x0fa4, 0x0fa9, 0x0fad, + 0x0fb8, 0x0fbe, 0x0fc9, 0x0fd0, 0x0fd6, 0x0fda, 0x0fe1, 0x0fe5, + 0x0fef, 0x0ffa, 0x1000, 0x1004, 0x1009, 0x100f, 0x1013, 0x101a, + 0x101f, 0x1023, 0x1029, 0x102f, 0x1032, 0x1036, 0x1039, 0x103f, + 0x1045, 0x1059, 0x1061, 0x1079, 0x107c, 0x1080, 0x1095, 0x10a1, + 0x10b1, 0x10c3, 0x10cb, 0x10cf, 0x10da, 0x10de, 0x10ea, 0x10f2, + 0x10f4, 0x1100, 0x1105, 0x1111, 0x1141, 0x1149, 0x114d, 0x1153, + 0x1157, 0x115a, 0x116e, 0x1171, 0x1175, 0x117b, 0x117d, 0x1181, + 0x1184, 0x118c, 0x1192, 0x1196, 0x119c, 0x11a2, 0x11a8, 0x11ab, + 0xa76f, 0x11af, 0x11b2, 0x11b6, 0x028d, 0x11be, 0x1210, 0x130e, + 0x140c, 0x1490, 0x1495, 0x1553, 0x156c, 0x1572, 0x1578, 0x157e, + 0x158a, 0x1596, 0x002b, 0x15a1, 0x15b9, 0x15bd, 0x15c1, 0x15c5, + 0x15c9, 0x15cd, 0x15e1, 0x15e5, 0x1649, 0x1662, 0x1688, 0x168e, + 0x174c, 0x1752, 0x1757, 0x1777, 0x1877, 0x187d, 0x1911, 0x19d3, + 0x1a77, 0x1a7f, 0x1a9d, 0x1aa2, 0x1ab6, 0x1ac0, 0x1ac6, 0x1ada, + 0x1adf, 0x1ae5, 0x1af3, 0x1b23, 0x1b30, 0x1b38, 0x1b3c, 0x1b52, + 0x1bc9, 0x1bdb, 0x1bdd, 0x1bdf, 0x3164, 0x1c20, 0x1c22, 0x1c24, + 0x1c26, 0x1c28, 0x1c2a, 0x1c48, 0x1c7e, 0x1cc4, 0x1cd2, 0x1cd7, + 0x1ce0, 0x1ce9, 0x1cfb, 0x1d04, 0x1d09, 0x1d29, 0x1d44, 0x1d46, + 0x1d48, 0x1d4a, 0x1d4c, 0x1d4e, 0x1d50, 0x1d52, 0x1d72, 0x1d74, + 0x1d76, 0x1d78, 0x1d7a, 0x1d81, 0x1d83, 0x1d85, 0x1d87, 0x1d96, + 0x1d98, 0x1d9a, 0x1d9c, 0x1d9e, 0x1da0, 0x1da2, 0x1da4, 0x1da6, + 0x1da8, 0x1daa, 0x1dac, 0x1dae, 0x1db0, 0x1db2, 0x1db6, 0x03f4, + 0x1db8, 0x2207, 0x1dba, 0x2202, 0x1dbc, 0x1dc4, 0x03f4, 0x1dc6, + 0x2207, 0x1dc8, 0x2202, 0x1dca, 0x1dd2, 0x03f4, 0x1dd4, 0x2207, + 0x1dd6, 0x2202, 0x1dd8, 0x1de0, 0x03f4, 0x1de2, 0x2207, 0x1de4, + 0x2202, 0x1de6, 0x1dee, 0x03f4, 0x1df0, 0x2207, 0x1df2, 0x2202, + 0x1df4, 0x1dfe, 0x1e00, 0x1e02, 0x1e04, 0x1e06, 0x1e08, 0x1e0a, + 0x1e0c, 0x1e0e, 0x1e16, 0x1e39, 0x1e3d, 0x1e43, 0x1e60, 0x062d, + 0x1e68, 0x1e74, 0x062c, 0x1e84, 0x1ef4, 0x1f00, 0x1f13, 0x1f25, + 0x1f38, 0x1f3a, 0x1f3e, 0x1f44, 0x1f4a, 0x1f4c, 0x1f50, 0x1f52, + 0x1f5a, 0x1f5d, 0x1f5f, 0x1f65, 0x1f67, 0x30b5, 0x1f6d, 0x1fc5, + 0x1fdb, 0x1fdf, 0x1fe1, 0x1fe6, 0x2033, 0x2044, 0x2145, 0x2155, + 0x215b, 0x2255, 0x2373, +}; + +static const uint8_t unicode_decomp_data[9345] = { + 0x20, 0x88, 0x20, 0x84, 0x32, 0x33, 0x20, 0x81, + 0x20, 0xa7, 0x31, 0x6f, 0x31, 0xd0, 0x34, 0x31, + 0xd0, 0x32, 0x33, 0xd0, 0x34, 0x41, 0x80, 0x41, + 0x81, 0x41, 0x82, 0x41, 0x83, 0x41, 0x88, 0x41, + 0x8a, 0x00, 0x00, 0x43, 0xa7, 0x45, 0x80, 0x45, + 0x81, 0x45, 0x82, 0x45, 0x88, 0x49, 0x80, 0x49, + 0x81, 0x49, 0x82, 0x49, 0x88, 0x00, 0x00, 0x4e, + 0x83, 0x4f, 0x80, 0x4f, 0x81, 0x4f, 0x82, 0x4f, + 0x83, 0x4f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x55, + 0x80, 0x55, 0x81, 0x55, 0x82, 0x55, 0x88, 0x59, + 0x81, 0x00, 0x00, 0x00, 0x00, 0x61, 0x80, 0x61, + 0x81, 0x61, 0x82, 0x61, 0x83, 0x61, 0x88, 0x61, + 0x8a, 0x00, 0x00, 0x63, 0xa7, 0x65, 0x80, 0x65, + 0x81, 0x65, 0x82, 0x65, 0x88, 0x69, 0x80, 0x69, + 0x81, 0x69, 0x82, 0x69, 0x88, 0x00, 0x00, 0x6e, + 0x83, 0x6f, 0x80, 0x6f, 0x81, 0x6f, 0x82, 0x6f, + 0x83, 0x6f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x75, + 0x80, 0x75, 0x81, 0x75, 0x82, 0x75, 0x88, 0x79, + 0x81, 0x00, 0x00, 0x79, 0x88, 0x41, 0x84, 0x41, + 0x86, 0x41, 0xa8, 0x43, 0x81, 0x43, 0x82, 0x43, + 0x87, 0x43, 0x8c, 0x44, 0x8c, 0x45, 0x84, 0x45, + 0x86, 0x45, 0x87, 0x45, 0xa8, 0x45, 0x8c, 0x47, + 0x82, 0x47, 0x86, 0x47, 0x87, 0x47, 0xa7, 0x48, + 0x82, 0x49, 0x83, 0x49, 0x84, 0x49, 0x86, 0x49, + 0xa8, 0x49, 0x87, 0x49, 0x4a, 0x69, 0x6a, 0x4a, + 0x82, 0x4b, 0xa7, 0x4c, 0x81, 0x4c, 0xa7, 0x4c, + 0x8c, 0x4c, 0x00, 0x00, 0x6b, 0x20, 0x6b, 0x4e, + 0x81, 0x4e, 0xa7, 0x4e, 0x8c, 0xbc, 0x02, 0x6e, + 0x4f, 0x84, 0x4f, 0x86, 0x4f, 0x8b, 0x52, 0x81, + 0x52, 0xa7, 0x52, 0x8c, 0x53, 0x81, 0x53, 0x82, + 0x53, 0xa7, 0x53, 0x8c, 0x54, 0xa7, 0x54, 0x8c, + 0x55, 0x83, 0x55, 0x84, 0x55, 0x86, 0x55, 0x8a, + 0x55, 0x8b, 0x55, 0xa8, 0x57, 0x82, 0x59, 0x82, + 0x59, 0x88, 0x5a, 0x81, 0x5a, 0x87, 0x5a, 0x8c, + 0x4f, 0x9b, 0x55, 0x9b, 0x44, 0x00, 0x7d, 0x01, + 0x44, 0x00, 0x7e, 0x01, 0x64, 0x00, 0x7e, 0x01, + 0x4c, 0x4a, 0x4c, 0x6a, 0x6c, 0x6a, 0x4e, 0x4a, + 0x4e, 0x6a, 0x6e, 0x6a, 0x41, 0x00, 0x8c, 0x49, + 0x00, 0x8c, 0x4f, 0x00, 0x8c, 0x55, 0x00, 0x8c, + 0xdc, 0x00, 0x84, 0xdc, 0x00, 0x81, 0xdc, 0x00, + 0x8c, 0xdc, 0x00, 0x80, 0xc4, 0x00, 0x84, 0x26, + 0x02, 0x84, 0xc6, 0x00, 0x84, 0x47, 0x8c, 0x4b, + 0x8c, 0x4f, 0xa8, 0xea, 0x01, 0x84, 0xeb, 0x01, + 0x84, 0xb7, 0x01, 0x8c, 0x92, 0x02, 0x8c, 0x6a, + 0x00, 0x8c, 0x44, 0x5a, 0x44, 0x7a, 0x64, 0x7a, + 0x47, 0x81, 0x4e, 0x00, 0x80, 0xc5, 0x00, 0x81, + 0xc6, 0x00, 0x81, 0xd8, 0x00, 0x81, 0x41, 0x8f, + 0x41, 0x91, 0x45, 0x8f, 0x45, 0x91, 0x49, 0x8f, + 0x49, 0x91, 0x4f, 0x8f, 0x4f, 0x91, 0x52, 0x8f, + 0x52, 0x91, 0x55, 0x8f, 0x55, 0x91, 0x53, 0xa6, + 0x54, 0xa6, 0x48, 0x8c, 0x41, 0x00, 0x87, 0x45, + 0x00, 0xa7, 0xd6, 0x00, 0x84, 0xd5, 0x00, 0x84, + 0x4f, 0x00, 0x87, 0x2e, 0x02, 0x84, 0x59, 0x00, + 0x84, 0x68, 0x00, 0x66, 0x02, 0x6a, 0x00, 0x72, + 0x00, 0x79, 0x02, 0x7b, 0x02, 0x81, 0x02, 0x77, + 0x00, 0x79, 0x00, 0x20, 0x86, 0x20, 0x87, 0x20, + 0x8a, 0x20, 0xa8, 0x20, 0x83, 0x20, 0x8b, 0x63, + 0x02, 0x6c, 0x00, 0x73, 0x00, 0x78, 0x00, 0x95, + 0x02, 0x80, 0x81, 0x00, 0x93, 0x88, 0x81, 0x20, + 0xc5, 0x20, 0x81, 0xa8, 0x00, 0x81, 0x91, 0x03, + 0x81, 0x95, 0x03, 0x81, 0x97, 0x03, 0x81, 0x99, + 0x03, 0x81, 0x00, 0x00, 0x00, 0x9f, 0x03, 0x81, + 0x00, 0x00, 0x00, 0xa5, 0x03, 0x81, 0xa9, 0x03, + 0x81, 0xca, 0x03, 0x81, 0x01, 0x03, 0x98, 0x07, + 0xa4, 0x07, 0xb0, 0x00, 0xb4, 0x00, 0xb6, 0x00, + 0xb8, 0x00, 0xca, 0x00, 0x01, 0x03, 0xb8, 0x07, + 0xc4, 0x07, 0xbe, 0x00, 0xc4, 0x00, 0xc8, 0x00, + 0xa5, 0x03, 0x0d, 0x13, 0x00, 0x01, 0x03, 0xd1, + 0x00, 0xd1, 0x07, 0xc6, 0x03, 0xc0, 0x03, 0xba, + 0x03, 0xc1, 0x03, 0xc2, 0x03, 0x00, 0x00, 0x98, + 0x03, 0xb5, 0x03, 0x15, 0x04, 0x80, 0x15, 0x04, + 0x88, 0x00, 0x00, 0x00, 0x13, 0x04, 0x81, 0x06, + 0x04, 0x88, 0x1a, 0x04, 0x81, 0x18, 0x04, 0x80, + 0x23, 0x04, 0x86, 0x18, 0x04, 0x86, 0x38, 0x04, + 0x86, 0x35, 0x04, 0x80, 0x35, 0x04, 0x88, 0x00, + 0x00, 0x00, 0x33, 0x04, 0x81, 0x56, 0x04, 0x88, + 0x3a, 0x04, 0x81, 0x38, 0x04, 0x80, 0x43, 0x04, + 0x86, 0x74, 0x04, 0x8f, 0x16, 0x04, 0x86, 0x10, + 0x04, 0x86, 0x10, 0x04, 0x88, 0x15, 0x04, 0x86, + 0xd8, 0x04, 0x88, 0x16, 0x04, 0x88, 0x17, 0x04, + 0x88, 0x18, 0x04, 0x84, 0x18, 0x04, 0x88, 0x1e, + 0x04, 0x88, 0xe8, 0x04, 0x88, 0x2d, 0x04, 0x88, + 0x23, 0x04, 0x84, 0x23, 0x04, 0x88, 0x23, 0x04, + 0x8b, 0x27, 0x04, 0x88, 0x2b, 0x04, 0x88, 0x65, + 0x05, 0x82, 0x05, 0x27, 0x06, 0x00, 0x2c, 0x00, + 0x2d, 0x21, 0x2d, 0x00, 0x2e, 0x23, 0x2d, 0x27, + 0x06, 0x00, 0x4d, 0x21, 0x4d, 0xa0, 0x4d, 0x23, + 0x4d, 0xd5, 0x06, 0x54, 0x06, 0x00, 0x00, 0x00, + 0x00, 0xc1, 0x06, 0x54, 0x06, 0xd2, 0x06, 0x54, + 0x06, 0x28, 0x09, 0x3c, 0x09, 0x30, 0x09, 0x3c, + 0x09, 0x33, 0x09, 0x3c, 0x09, 0x15, 0x09, 0x00, + 0x27, 0x01, 0x27, 0x02, 0x27, 0x07, 0x27, 0x0c, + 0x27, 0x0d, 0x27, 0x16, 0x27, 0x1a, 0x27, 0xbe, + 0x09, 0x09, 0x00, 0x09, 0x19, 0xa1, 0x09, 0xbc, + 0x09, 0xaf, 0x09, 0xbc, 0x09, 0x32, 0x0a, 0x3c, + 0x0a, 0x38, 0x0a, 0x3c, 0x0a, 0x16, 0x0a, 0x00, + 0x26, 0x01, 0x26, 0x06, 0x26, 0x2b, 0x0a, 0x3c, + 0x0a, 0x47, 0x0b, 0x56, 0x0b, 0x3e, 0x0b, 0x09, + 0x00, 0x09, 0x19, 0x21, 0x0b, 0x3c, 0x0b, 0x92, + 0x0b, 0xd7, 0x0b, 0xbe, 0x0b, 0x08, 0x00, 0x09, + 0x00, 0x08, 0x19, 0x46, 0x0c, 0x56, 0x0c, 0xbf, + 0x0c, 0xd5, 0x0c, 0xc6, 0x0c, 0xd5, 0x0c, 0xc2, + 0x0c, 0x04, 0x00, 0x08, 0x13, 0x3e, 0x0d, 0x08, + 0x00, 0x09, 0x00, 0x08, 0x19, 0xd9, 0x0d, 0xca, + 0x0d, 0xca, 0x0d, 0x0f, 0x05, 0x12, 0x00, 0x0f, + 0x15, 0x4d, 0x0e, 0x32, 0x0e, 0xcd, 0x0e, 0xb2, + 0x0e, 0x99, 0x0e, 0x12, 0x00, 0x12, 0x08, 0x42, + 0x0f, 0xb7, 0x0f, 0x4c, 0x0f, 0xb7, 0x0f, 0x51, + 0x0f, 0xb7, 0x0f, 0x56, 0x0f, 0xb7, 0x0f, 0x5b, + 0x0f, 0xb7, 0x0f, 0x40, 0x0f, 0xb5, 0x0f, 0x71, + 0x0f, 0x72, 0x0f, 0x71, 0x0f, 0x00, 0x03, 0x41, + 0x0f, 0xb2, 0x0f, 0x81, 0x0f, 0xb3, 0x0f, 0x80, + 0x0f, 0xb3, 0x0f, 0x81, 0x0f, 0x71, 0x0f, 0x80, + 0x0f, 0x92, 0x0f, 0xb7, 0x0f, 0x9c, 0x0f, 0xb7, + 0x0f, 0xa1, 0x0f, 0xb7, 0x0f, 0xa6, 0x0f, 0xb7, + 0x0f, 0xab, 0x0f, 0xb7, 0x0f, 0x90, 0x0f, 0xb5, + 0x0f, 0x25, 0x10, 0x2e, 0x10, 0x05, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x09, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x1b, 0x35, + 0x1b, 0x11, 0x1b, 0x35, 0x1b, 0x3a, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x1b, 0x35, + 0x1b, 0x3e, 0x1b, 0x35, 0x1b, 0x42, 0x1b, 0x35, + 0x1b, 0x41, 0x00, 0xc6, 0x00, 0x42, 0x00, 0x00, + 0x00, 0x44, 0x00, 0x45, 0x00, 0x8e, 0x01, 0x47, + 0x00, 0x4f, 0x00, 0x22, 0x02, 0x50, 0x00, 0x52, + 0x00, 0x54, 0x00, 0x55, 0x00, 0x57, 0x00, 0x61, + 0x00, 0x50, 0x02, 0x51, 0x02, 0x02, 0x1d, 0x62, + 0x00, 0x64, 0x00, 0x65, 0x00, 0x59, 0x02, 0x5b, + 0x02, 0x5c, 0x02, 0x67, 0x00, 0x00, 0x00, 0x6b, + 0x00, 0x6d, 0x00, 0x4b, 0x01, 0x6f, 0x00, 0x54, + 0x02, 0x16, 0x1d, 0x17, 0x1d, 0x70, 0x00, 0x74, + 0x00, 0x75, 0x00, 0x1d, 0x1d, 0x6f, 0x02, 0x76, + 0x00, 0x25, 0x1d, 0xb2, 0x03, 0xb3, 0x03, 0xb4, + 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x69, 0x00, 0x72, + 0x00, 0x75, 0x00, 0x76, 0x00, 0xb2, 0x03, 0xb3, + 0x03, 0xc1, 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x52, + 0x02, 0x63, 0x00, 0x55, 0x02, 0xf0, 0x00, 0x5c, + 0x02, 0x66, 0x00, 0x5f, 0x02, 0x61, 0x02, 0x65, + 0x02, 0x68, 0x02, 0x69, 0x02, 0x6a, 0x02, 0x7b, + 0x1d, 0x9d, 0x02, 0x6d, 0x02, 0x85, 0x1d, 0x9f, + 0x02, 0x71, 0x02, 0x70, 0x02, 0x72, 0x02, 0x73, + 0x02, 0x74, 0x02, 0x75, 0x02, 0x78, 0x02, 0x82, + 0x02, 0x83, 0x02, 0xab, 0x01, 0x89, 0x02, 0x8a, + 0x02, 0x1c, 0x1d, 0x8b, 0x02, 0x8c, 0x02, 0x7a, + 0x00, 0x90, 0x02, 0x91, 0x02, 0x92, 0x02, 0xb8, + 0x03, 0x41, 0x00, 0xa5, 0x42, 0x00, 0x87, 0x42, + 0x00, 0xa3, 0x42, 0x00, 0xb1, 0xc7, 0x00, 0x81, + 0x44, 0x00, 0x87, 0x44, 0x00, 0xa3, 0x44, 0x00, + 0xb1, 0x44, 0x00, 0xa7, 0x44, 0x00, 0xad, 0x12, + 0x01, 0x80, 0x12, 0x01, 0x81, 0x45, 0x00, 0xad, + 0x45, 0x00, 0xb0, 0x28, 0x02, 0x86, 0x46, 0x00, + 0x87, 0x47, 0x00, 0x84, 0x48, 0x00, 0x87, 0x48, + 0x00, 0xa3, 0x48, 0x00, 0x88, 0x48, 0x00, 0xa7, + 0x48, 0x00, 0xae, 0x49, 0x00, 0xb0, 0xcf, 0x00, + 0x81, 0x4b, 0x00, 0x81, 0x4b, 0x00, 0xa3, 0x4b, + 0x00, 0xb1, 0x4c, 0x00, 0xa3, 0x36, 0x1e, 0x84, + 0x4c, 0xb1, 0x4c, 0xad, 0x4d, 0x81, 0x4d, 0x87, + 0x4d, 0xa3, 0x4e, 0x87, 0x4e, 0xa3, 0x4e, 0xb1, + 0x4e, 0xad, 0xd5, 0x00, 0x81, 0xd5, 0x00, 0x88, + 0x4c, 0x01, 0x80, 0x4c, 0x01, 0x81, 0x50, 0x00, + 0x81, 0x50, 0x00, 0x87, 0x52, 0x00, 0x87, 0x52, + 0x00, 0xa3, 0x5a, 0x1e, 0x84, 0x52, 0x00, 0xb1, + 0x53, 0x00, 0x87, 0x53, 0x00, 0xa3, 0x5a, 0x01, + 0x87, 0x60, 0x01, 0x87, 0x62, 0x1e, 0x87, 0x54, + 0x00, 0x87, 0x54, 0x00, 0xa3, 0x54, 0x00, 0xb1, + 0x54, 0x00, 0xad, 0x55, 0x00, 0xa4, 0x55, 0x00, + 0xb0, 0x55, 0x00, 0xad, 0x68, 0x01, 0x81, 0x6a, + 0x01, 0x88, 0x56, 0x83, 0x56, 0xa3, 0x57, 0x80, + 0x57, 0x81, 0x57, 0x88, 0x57, 0x87, 0x57, 0xa3, + 0x58, 0x87, 0x58, 0x88, 0x59, 0x87, 0x5a, 0x82, + 0x5a, 0xa3, 0x5a, 0xb1, 0x68, 0xb1, 0x74, 0x88, + 0x77, 0x8a, 0x79, 0x8a, 0x61, 0x00, 0xbe, 0x02, + 0x7f, 0x01, 0x87, 0x41, 0x00, 0xa3, 0x41, 0x00, + 0x89, 0xc2, 0x00, 0x81, 0xc2, 0x00, 0x80, 0xc2, + 0x00, 0x89, 0xc2, 0x00, 0x83, 0xa0, 0x1e, 0x82, + 0x02, 0x01, 0x81, 0x02, 0x01, 0x80, 0x02, 0x01, + 0x89, 0x02, 0x01, 0x83, 0xa0, 0x1e, 0x86, 0x45, + 0x00, 0xa3, 0x45, 0x00, 0x89, 0x45, 0x00, 0x83, + 0xca, 0x00, 0x81, 0xca, 0x00, 0x80, 0xca, 0x00, + 0x89, 0xca, 0x00, 0x83, 0xb8, 0x1e, 0x82, 0x49, + 0x00, 0x89, 0x49, 0x00, 0xa3, 0x4f, 0x00, 0xa3, + 0x4f, 0x00, 0x89, 0xd4, 0x00, 0x81, 0xd4, 0x00, + 0x80, 0xd4, 0x00, 0x89, 0xd4, 0x00, 0x83, 0xcc, + 0x1e, 0x82, 0xa0, 0x01, 0x81, 0xa0, 0x01, 0x80, + 0xa0, 0x01, 0x89, 0xa0, 0x01, 0x83, 0xa0, 0x01, + 0xa3, 0x55, 0x00, 0xa3, 0x55, 0x00, 0x89, 0xaf, + 0x01, 0x81, 0xaf, 0x01, 0x80, 0xaf, 0x01, 0x89, + 0xaf, 0x01, 0x83, 0xaf, 0x01, 0xa3, 0x59, 0x00, + 0x80, 0x59, 0x00, 0xa3, 0x59, 0x00, 0x89, 0x59, + 0x00, 0x83, 0xb1, 0x03, 0x13, 0x03, 0x00, 0x1f, + 0x80, 0x00, 0x1f, 0x81, 0x00, 0x1f, 0xc2, 0x91, + 0x03, 0x13, 0x03, 0x08, 0x1f, 0x80, 0x08, 0x1f, + 0x81, 0x08, 0x1f, 0xc2, 0xb5, 0x03, 0x13, 0x03, + 0x10, 0x1f, 0x80, 0x10, 0x1f, 0x81, 0x95, 0x03, + 0x13, 0x03, 0x18, 0x1f, 0x80, 0x18, 0x1f, 0x81, + 0xb7, 0x03, 0x93, 0xb7, 0x03, 0x94, 0x20, 0x1f, + 0x80, 0x21, 0x1f, 0x80, 0x20, 0x1f, 0x81, 0x21, + 0x1f, 0x81, 0x20, 0x1f, 0xc2, 0x21, 0x1f, 0xc2, + 0x97, 0x03, 0x93, 0x97, 0x03, 0x94, 0x28, 0x1f, + 0x80, 0x29, 0x1f, 0x80, 0x28, 0x1f, 0x81, 0x29, + 0x1f, 0x81, 0x28, 0x1f, 0xc2, 0x29, 0x1f, 0xc2, + 0xb9, 0x03, 0x93, 0xb9, 0x03, 0x94, 0x30, 0x1f, + 0x80, 0x31, 0x1f, 0x80, 0x30, 0x1f, 0x81, 0x31, + 0x1f, 0x81, 0x30, 0x1f, 0xc2, 0x31, 0x1f, 0xc2, + 0x99, 0x03, 0x93, 0x99, 0x03, 0x94, 0x38, 0x1f, + 0x80, 0x39, 0x1f, 0x80, 0x38, 0x1f, 0x81, 0x39, + 0x1f, 0x81, 0x38, 0x1f, 0xc2, 0x39, 0x1f, 0xc2, + 0xbf, 0x03, 0x93, 0xbf, 0x03, 0x94, 0x40, 0x1f, + 0x80, 0x40, 0x1f, 0x81, 0x9f, 0x03, 0x13, 0x03, + 0x48, 0x1f, 0x80, 0x48, 0x1f, 0x81, 0xc5, 0x03, + 0x13, 0x03, 0x50, 0x1f, 0x80, 0x50, 0x1f, 0x81, + 0x50, 0x1f, 0xc2, 0xa5, 0x03, 0x94, 0x00, 0x00, + 0x00, 0x59, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x59, + 0x1f, 0x81, 0x00, 0x00, 0x00, 0x59, 0x1f, 0xc2, + 0xc9, 0x03, 0x93, 0xc9, 0x03, 0x94, 0x60, 0x1f, + 0x80, 0x61, 0x1f, 0x80, 0x60, 0x1f, 0x81, 0x61, + 0x1f, 0x81, 0x60, 0x1f, 0xc2, 0x61, 0x1f, 0xc2, + 0xa9, 0x03, 0x93, 0xa9, 0x03, 0x94, 0x68, 0x1f, + 0x80, 0x69, 0x1f, 0x80, 0x68, 0x1f, 0x81, 0x69, + 0x1f, 0x81, 0x68, 0x1f, 0xc2, 0x69, 0x1f, 0xc2, + 0xb1, 0x03, 0x80, 0xb5, 0x03, 0x80, 0xb7, 0x03, + 0x80, 0xb9, 0x03, 0x80, 0xbf, 0x03, 0x80, 0xc5, + 0x03, 0x80, 0xc9, 0x03, 0x80, 0x00, 0x1f, 0x45, + 0x03, 0x20, 0x1f, 0x45, 0x03, 0x60, 0x1f, 0x45, + 0x03, 0xb1, 0x03, 0x86, 0xb1, 0x03, 0x84, 0x70, + 0x1f, 0xc5, 0xb1, 0x03, 0xc5, 0xac, 0x03, 0xc5, + 0x00, 0x00, 0x00, 0xb1, 0x03, 0xc2, 0xb6, 0x1f, + 0xc5, 0x91, 0x03, 0x86, 0x91, 0x03, 0x84, 0x91, + 0x03, 0x80, 0x91, 0x03, 0xc5, 0x20, 0x93, 0x20, + 0x93, 0x20, 0xc2, 0xa8, 0x00, 0xc2, 0x74, 0x1f, + 0xc5, 0xb7, 0x03, 0xc5, 0xae, 0x03, 0xc5, 0x00, + 0x00, 0x00, 0xb7, 0x03, 0xc2, 0xc6, 0x1f, 0xc5, + 0x95, 0x03, 0x80, 0x97, 0x03, 0x80, 0x97, 0x03, + 0xc5, 0xbf, 0x1f, 0x80, 0xbf, 0x1f, 0x81, 0xbf, + 0x1f, 0xc2, 0xb9, 0x03, 0x86, 0xb9, 0x03, 0x84, + 0xca, 0x03, 0x80, 0x00, 0x03, 0xb9, 0x42, 0xca, + 0x42, 0x99, 0x06, 0x99, 0x04, 0x99, 0x00, 0xfe, + 0x1f, 0x80, 0xfe, 0x1f, 0x81, 0xfe, 0x1f, 0xc2, + 0xc5, 0x03, 0x86, 0xc5, 0x03, 0x84, 0xcb, 0x03, + 0x80, 0x00, 0x03, 0xc1, 0x13, 0xc1, 0x14, 0xc5, + 0x42, 0xcb, 0x42, 0xa5, 0x06, 0xa5, 0x04, 0xa5, + 0x00, 0xa1, 0x03, 0x94, 0xa8, 0x00, 0x80, 0x85, + 0x03, 0x60, 0x00, 0x7c, 0x1f, 0xc5, 0xc9, 0x03, + 0xc5, 0xce, 0x03, 0xc5, 0x00, 0x00, 0x00, 0xc9, + 0x03, 0xc2, 0xf6, 0x1f, 0xc5, 0x9f, 0x03, 0x80, + 0xa9, 0x03, 0x80, 0xa9, 0x03, 0xc5, 0x20, 0x94, + 0x02, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0xb3, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x32, 0x20, 0x32, 0x20, 0x32, 0x20, + 0x00, 0x00, 0x00, 0x35, 0x20, 0x35, 0x20, 0x35, + 0x20, 0x00, 0x00, 0x00, 0x21, 0x21, 0x00, 0x00, + 0x20, 0x85, 0x3f, 0x3f, 0x3f, 0x21, 0x21, 0x3f, + 0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x30, 0x69, + 0x00, 0x00, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x2b, 0x3d, 0x28, 0x29, 0x6e, 0x30, 0x00, 0x2b, + 0x00, 0x12, 0x22, 0x3d, 0x00, 0x28, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x61, 0x00, 0x65, 0x00, 0x6f, + 0x00, 0x78, 0x00, 0x59, 0x02, 0x68, 0x6b, 0x6c, + 0x6d, 0x6e, 0x70, 0x73, 0x74, 0x52, 0x73, 0x61, + 0x2f, 0x63, 0x61, 0x2f, 0x73, 0xb0, 0x00, 0x43, + 0x63, 0x2f, 0x6f, 0x63, 0x2f, 0x75, 0xb0, 0x00, + 0x46, 0x48, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, + 0xdf, 0x01, 0x01, 0x04, 0x24, 0x4e, 0x6f, 0x50, + 0x51, 0x52, 0x52, 0x52, 0x53, 0x4d, 0x54, 0x45, + 0x4c, 0x54, 0x4d, 0x4b, 0x00, 0xc5, 0x00, 0x42, + 0x43, 0x00, 0x65, 0x45, 0x46, 0x00, 0x4d, 0x6f, + 0xd0, 0x05, 0x46, 0x41, 0x58, 0xc0, 0x03, 0xb3, + 0x03, 0x93, 0x03, 0xa0, 0x03, 0x11, 0x22, 0x44, + 0x64, 0x65, 0x69, 0x6a, 0x31, 0xd0, 0x37, 0x31, + 0xd0, 0x39, 0x31, 0xd0, 0x31, 0x30, 0x31, 0xd0, + 0x33, 0x32, 0xd0, 0x33, 0x31, 0xd0, 0x35, 0x32, + 0xd0, 0x35, 0x33, 0xd0, 0x35, 0x34, 0xd0, 0x35, + 0x31, 0xd0, 0x36, 0x35, 0xd0, 0x36, 0x31, 0xd0, + 0x38, 0x33, 0xd0, 0x38, 0x35, 0xd0, 0x38, 0x37, + 0xd0, 0x38, 0x31, 0xd0, 0x49, 0x49, 0x49, 0x49, + 0x49, 0x49, 0x56, 0x56, 0x49, 0x56, 0x49, 0x49, + 0x56, 0x49, 0x49, 0x49, 0x49, 0x58, 0x58, 0x49, + 0x58, 0x49, 0x49, 0x4c, 0x43, 0x44, 0x4d, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x76, 0x76, + 0x69, 0x76, 0x69, 0x69, 0x76, 0x69, 0x69, 0x69, + 0x69, 0x78, 0x78, 0x69, 0x78, 0x69, 0x69, 0x6c, + 0x63, 0x64, 0x6d, 0x30, 0xd0, 0x33, 0x90, 0x21, + 0xb8, 0x92, 0x21, 0xb8, 0x94, 0x21, 0xb8, 0xd0, + 0x21, 0xb8, 0xd4, 0x21, 0xb8, 0xd2, 0x21, 0xb8, + 0x03, 0x22, 0xb8, 0x08, 0x22, 0xb8, 0x0b, 0x22, + 0xb8, 0x23, 0x22, 0xb8, 0x00, 0x00, 0x00, 0x25, + 0x22, 0xb8, 0x2b, 0x22, 0x2b, 0x22, 0x2b, 0x22, + 0x00, 0x00, 0x00, 0x2e, 0x22, 0x2e, 0x22, 0x2e, + 0x22, 0x00, 0x00, 0x00, 0x3c, 0x22, 0xb8, 0x43, + 0x22, 0xb8, 0x45, 0x22, 0xb8, 0x00, 0x00, 0x00, + 0x48, 0x22, 0xb8, 0x3d, 0x00, 0xb8, 0x00, 0x00, + 0x00, 0x61, 0x22, 0xb8, 0x4d, 0x22, 0xb8, 0x3c, + 0x00, 0xb8, 0x3e, 0x00, 0xb8, 0x64, 0x22, 0xb8, + 0x65, 0x22, 0xb8, 0x72, 0x22, 0xb8, 0x76, 0x22, + 0xb8, 0x7a, 0x22, 0xb8, 0x82, 0x22, 0xb8, 0x86, + 0x22, 0xb8, 0xa2, 0x22, 0xb8, 0xa8, 0x22, 0xb8, + 0xa9, 0x22, 0xb8, 0xab, 0x22, 0xb8, 0x7c, 0x22, + 0xb8, 0x91, 0x22, 0xb8, 0xb2, 0x22, 0x38, 0x03, + 0x08, 0x30, 0x31, 0x00, 0x31, 0x00, 0x30, 0x00, + 0x32, 0x30, 0x28, 0x00, 0x31, 0x00, 0x29, 0x00, + 0x28, 0x00, 0x31, 0x00, 0x30, 0x00, 0x29, 0x00, + 0x28, 0x32, 0x30, 0x29, 0x31, 0x00, 0x2e, 0x00, + 0x31, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x32, 0x30, + 0x2e, 0x28, 0x00, 0x61, 0x00, 0x29, 0x00, 0x41, + 0x00, 0x61, 0x00, 0x2b, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x3a, 0x3a, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0xdd, 0x2a, 0xb8, 0x6a, 0x56, 0x00, 0x4e, + 0x00, 0x28, 0x36, 0x3f, 0x59, 0x85, 0x8c, 0xa0, + 0xba, 0x3f, 0x51, 0x00, 0x26, 0x2c, 0x43, 0x57, + 0x6c, 0xa1, 0xb6, 0xc1, 0x9b, 0x52, 0x00, 0x5e, + 0x7a, 0x7f, 0x9d, 0xa6, 0xc1, 0xce, 0xe7, 0xb6, + 0x53, 0xc8, 0x53, 0xe3, 0x53, 0xd7, 0x56, 0x1f, + 0x57, 0xeb, 0x58, 0x02, 0x59, 0x0a, 0x59, 0x15, + 0x59, 0x27, 0x59, 0x73, 0x59, 0x50, 0x5b, 0x80, + 0x5b, 0xf8, 0x5b, 0x0f, 0x5c, 0x22, 0x5c, 0x38, + 0x5c, 0x6e, 0x5c, 0x71, 0x5c, 0xdb, 0x5d, 0xe5, + 0x5d, 0xf1, 0x5d, 0xfe, 0x5d, 0x72, 0x5e, 0x7a, + 0x5e, 0x7f, 0x5e, 0xf4, 0x5e, 0xfe, 0x5e, 0x0b, + 0x5f, 0x13, 0x5f, 0x50, 0x5f, 0x61, 0x5f, 0x73, + 0x5f, 0xc3, 0x5f, 0x08, 0x62, 0x36, 0x62, 0x4b, + 0x62, 0x2f, 0x65, 0x34, 0x65, 0x87, 0x65, 0x97, + 0x65, 0xa4, 0x65, 0xb9, 0x65, 0xe0, 0x65, 0xe5, + 0x65, 0xf0, 0x66, 0x08, 0x67, 0x28, 0x67, 0x20, + 0x6b, 0x62, 0x6b, 0x79, 0x6b, 0xb3, 0x6b, 0xcb, + 0x6b, 0xd4, 0x6b, 0xdb, 0x6b, 0x0f, 0x6c, 0x14, + 0x6c, 0x34, 0x6c, 0x6b, 0x70, 0x2a, 0x72, 0x36, + 0x72, 0x3b, 0x72, 0x3f, 0x72, 0x47, 0x72, 0x59, + 0x72, 0x5b, 0x72, 0xac, 0x72, 0x84, 0x73, 0x89, + 0x73, 0xdc, 0x74, 0xe6, 0x74, 0x18, 0x75, 0x1f, + 0x75, 0x28, 0x75, 0x30, 0x75, 0x8b, 0x75, 0x92, + 0x75, 0x76, 0x76, 0x7d, 0x76, 0xae, 0x76, 0xbf, + 0x76, 0xee, 0x76, 0xdb, 0x77, 0xe2, 0x77, 0xf3, + 0x77, 0x3a, 0x79, 0xb8, 0x79, 0xbe, 0x79, 0x74, + 0x7a, 0xcb, 0x7a, 0xf9, 0x7a, 0x73, 0x7c, 0xf8, + 0x7c, 0x36, 0x7f, 0x51, 0x7f, 0x8a, 0x7f, 0xbd, + 0x7f, 0x01, 0x80, 0x0c, 0x80, 0x12, 0x80, 0x33, + 0x80, 0x7f, 0x80, 0x89, 0x80, 0xe3, 0x81, 0x00, + 0x07, 0x10, 0x19, 0x29, 0x38, 0x3c, 0x8b, 0x8f, + 0x95, 0x4d, 0x86, 0x6b, 0x86, 0x40, 0x88, 0x4c, + 0x88, 0x63, 0x88, 0x7e, 0x89, 0x8b, 0x89, 0xd2, + 0x89, 0x00, 0x8a, 0x37, 0x8c, 0x46, 0x8c, 0x55, + 0x8c, 0x78, 0x8c, 0x9d, 0x8c, 0x64, 0x8d, 0x70, + 0x8d, 0xb3, 0x8d, 0xab, 0x8e, 0xca, 0x8e, 0x9b, + 0x8f, 0xb0, 0x8f, 0xb5, 0x8f, 0x91, 0x90, 0x49, + 0x91, 0xc6, 0x91, 0xcc, 0x91, 0xd1, 0x91, 0x77, + 0x95, 0x80, 0x95, 0x1c, 0x96, 0xb6, 0x96, 0xb9, + 0x96, 0xe8, 0x96, 0x51, 0x97, 0x5e, 0x97, 0x62, + 0x97, 0x69, 0x97, 0xcb, 0x97, 0xed, 0x97, 0xf3, + 0x97, 0x01, 0x98, 0xa8, 0x98, 0xdb, 0x98, 0xdf, + 0x98, 0x96, 0x99, 0x99, 0x99, 0xac, 0x99, 0xa8, + 0x9a, 0xd8, 0x9a, 0xdf, 0x9a, 0x25, 0x9b, 0x2f, + 0x9b, 0x32, 0x9b, 0x3c, 0x9b, 0x5a, 0x9b, 0xe5, + 0x9c, 0x75, 0x9e, 0x7f, 0x9e, 0xa5, 0x9e, 0x00, + 0x16, 0x1e, 0x28, 0x2c, 0x54, 0x58, 0x69, 0x6e, + 0x7b, 0x96, 0xa5, 0xad, 0xe8, 0xf7, 0xfb, 0x12, + 0x30, 0x00, 0x00, 0x41, 0x53, 0x44, 0x53, 0x45, + 0x53, 0x4b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x4d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x4f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x51, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x53, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x55, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x57, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x59, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x61, 0x30, 0x99, 0x30, 0x64, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x66, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x68, 0x30, 0x99, + 0x30, 0x6f, 0x30, 0x99, 0x30, 0x72, 0x30, 0x99, + 0x30, 0x75, 0x30, 0x99, 0x30, 0x78, 0x30, 0x99, + 0x30, 0x7b, 0x30, 0x99, 0x30, 0x46, 0x30, 0x99, + 0x30, 0x20, 0x00, 0x99, 0x30, 0x9d, 0x30, 0x99, + 0x30, 0x88, 0x30, 0x8a, 0x30, 0xab, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xad, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xaf, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x30, 0x99, + 0x30, 0xc4, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0xc6, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0xc8, 0x30, 0x99, 0x30, 0xcf, 0x30, 0x99, + 0x30, 0xd2, 0x30, 0x99, 0x30, 0xd5, 0x30, 0x99, + 0x30, 0xd8, 0x30, 0x99, 0x30, 0xdb, 0x30, 0x99, + 0x30, 0xa6, 0x30, 0x99, 0x30, 0xef, 0x30, 0x99, + 0x30, 0xfd, 0x30, 0x99, 0x30, 0xb3, 0x30, 0xc8, + 0x30, 0x00, 0x11, 0x00, 0x01, 0xaa, 0x02, 0xac, + 0xad, 0x03, 0x04, 0x05, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb4, 0xb5, 0x1a, 0x06, 0x07, 0x08, 0x21, 0x09, + 0x11, 0x61, 0x11, 0x14, 0x11, 0x4c, 0x00, 0x01, + 0xb3, 0xb4, 0xb8, 0xba, 0xbf, 0xc3, 0xc5, 0x08, + 0xc9, 0xcb, 0x09, 0x0a, 0x0c, 0x0e, 0x0f, 0x13, + 0x15, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1e, 0x22, + 0x2c, 0x33, 0x38, 0xdd, 0xde, 0x43, 0x44, 0x45, + 0x70, 0x71, 0x74, 0x7d, 0x7e, 0x80, 0x8a, 0x8d, + 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, + 0x0a, 0x4e, 0x2d, 0x4e, 0x0b, 0x4e, 0x32, 0x75, + 0x59, 0x4e, 0x19, 0x4e, 0x01, 0x4e, 0x29, 0x59, + 0x30, 0x57, 0xba, 0x4e, 0x28, 0x00, 0x29, 0x00, + 0x00, 0x11, 0x02, 0x11, 0x03, 0x11, 0x05, 0x11, + 0x06, 0x11, 0x07, 0x11, 0x09, 0x11, 0x0b, 0x11, + 0x0c, 0x11, 0x0e, 0x11, 0x0f, 0x11, 0x10, 0x11, + 0x11, 0x11, 0x12, 0x11, 0x28, 0x00, 0x00, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x02, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x05, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x09, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0e, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0c, 0x11, + 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, + 0x69, 0x11, 0x0c, 0x11, 0x65, 0x11, 0xab, 0x11, + 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, 0x69, 0x11, + 0x12, 0x11, 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, + 0x29, 0x00, 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, + 0xdb, 0x56, 0x94, 0x4e, 0x6d, 0x51, 0x03, 0x4e, + 0x6b, 0x51, 0x5d, 0x4e, 0x41, 0x53, 0x08, 0x67, + 0x6b, 0x70, 0x34, 0x6c, 0x28, 0x67, 0xd1, 0x91, + 0x1f, 0x57, 0xe5, 0x65, 0x2a, 0x68, 0x09, 0x67, + 0x3e, 0x79, 0x0d, 0x54, 0x79, 0x72, 0xa1, 0x8c, + 0x5d, 0x79, 0xb4, 0x52, 0xe3, 0x4e, 0x7c, 0x54, + 0x66, 0x5b, 0xe3, 0x76, 0x01, 0x4f, 0xc7, 0x8c, + 0x54, 0x53, 0x6d, 0x79, 0x11, 0x4f, 0xea, 0x81, + 0xf3, 0x81, 0x4f, 0x55, 0x7c, 0x5e, 0x87, 0x65, + 0x8f, 0x7b, 0x50, 0x54, 0x45, 0x32, 0x00, 0x31, + 0x00, 0x33, 0x00, 0x30, 0x00, 0x00, 0x11, 0x00, + 0x02, 0x03, 0x05, 0x06, 0x07, 0x09, 0x0b, 0x0c, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x00, 0x11, 0x00, + 0x61, 0x02, 0x61, 0x03, 0x61, 0x05, 0x61, 0x06, + 0x61, 0x07, 0x61, 0x09, 0x61, 0x0b, 0x61, 0x0c, + 0x61, 0x0e, 0x11, 0x61, 0x11, 0x00, 0x11, 0x0e, + 0x61, 0xb7, 0x00, 0x69, 0x0b, 0x11, 0x01, 0x63, + 0x00, 0x69, 0x0b, 0x11, 0x6e, 0x11, 0x00, 0x4e, + 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, 0x94, 0x4e, + 0x6d, 0x51, 0x03, 0x4e, 0x6b, 0x51, 0x5d, 0x4e, + 0x41, 0x53, 0x08, 0x67, 0x6b, 0x70, 0x34, 0x6c, + 0x28, 0x67, 0xd1, 0x91, 0x1f, 0x57, 0xe5, 0x65, + 0x2a, 0x68, 0x09, 0x67, 0x3e, 0x79, 0x0d, 0x54, + 0x79, 0x72, 0xa1, 0x8c, 0x5d, 0x79, 0xb4, 0x52, + 0xd8, 0x79, 0x37, 0x75, 0x73, 0x59, 0x69, 0x90, + 0x2a, 0x51, 0x70, 0x53, 0xe8, 0x6c, 0x05, 0x98, + 0x11, 0x4f, 0x99, 0x51, 0x63, 0x6b, 0x0a, 0x4e, + 0x2d, 0x4e, 0x0b, 0x4e, 0xe6, 0x5d, 0xf3, 0x53, + 0x3b, 0x53, 0x97, 0x5b, 0x66, 0x5b, 0xe3, 0x76, + 0x01, 0x4f, 0xc7, 0x8c, 0x54, 0x53, 0x1c, 0x59, + 0x33, 0x00, 0x36, 0x00, 0x34, 0x00, 0x30, 0x00, + 0x35, 0x30, 0x31, 0x00, 0x08, 0x67, 0x31, 0x00, + 0x30, 0x00, 0x08, 0x67, 0x48, 0x67, 0x65, 0x72, + 0x67, 0x65, 0x56, 0x4c, 0x54, 0x44, 0xa2, 0x30, + 0x00, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0b, 0x0d, + 0x0f, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1b, 0x1d, + 0x1f, 0x22, 0x24, 0x26, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x30, 0x33, 0x36, 0x39, 0x3c, 0x3d, + 0x3e, 0x3f, 0x40, 0x42, 0x44, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x4b, 0x4d, 0x4e, 0x4f, 0x50, 0xe4, + 0x4e, 0x8c, 0x54, 0xa1, 0x30, 0x01, 0x30, 0x5b, + 0x27, 0x01, 0x4a, 0x34, 0x00, 0x01, 0x52, 0x39, + 0x01, 0xa2, 0x30, 0x00, 0x5a, 0x49, 0xa4, 0x30, + 0x00, 0x27, 0x4f, 0x0c, 0xa4, 0x30, 0x00, 0x4f, + 0x1d, 0x02, 0x05, 0x4f, 0xa8, 0x30, 0x00, 0x11, + 0x07, 0x54, 0x21, 0xa8, 0x30, 0x00, 0x54, 0x03, + 0x54, 0xa4, 0x30, 0x06, 0x4f, 0x15, 0x06, 0x58, + 0x3c, 0x07, 0x00, 0x46, 0xab, 0x30, 0x00, 0x3e, + 0x18, 0x1d, 0x00, 0x42, 0x3f, 0x51, 0xac, 0x30, + 0x00, 0x41, 0x47, 0x00, 0x47, 0x32, 0xae, 0x30, + 0xac, 0x30, 0xae, 0x30, 0x00, 0x1d, 0x4e, 0xad, + 0x30, 0x00, 0x38, 0x3d, 0x4f, 0x01, 0x3e, 0x13, + 0x4f, 0xad, 0x30, 0xed, 0x30, 0xad, 0x30, 0x00, + 0x40, 0x03, 0x3c, 0x33, 0xad, 0x30, 0x00, 0x40, + 0x34, 0x4f, 0x1b, 0x3e, 0xad, 0x30, 0x00, 0x40, + 0x42, 0x16, 0x1b, 0xb0, 0x30, 0x00, 0x39, 0x30, + 0xa4, 0x30, 0x0c, 0x45, 0x3c, 0x24, 0x4f, 0x0b, + 0x47, 0x18, 0x00, 0x49, 0xaf, 0x30, 0x00, 0x3e, + 0x4d, 0x1e, 0xb1, 0x30, 0x00, 0x4b, 0x08, 0x02, + 0x3a, 0x19, 0x02, 0x4b, 0x2c, 0xa4, 0x30, 0x11, + 0x00, 0x0b, 0x47, 0xb5, 0x30, 0x00, 0x3e, 0x0c, + 0x47, 0x2b, 0xb0, 0x30, 0x07, 0x3a, 0x43, 0x00, + 0xb9, 0x30, 0x02, 0x3a, 0x08, 0x02, 0x3a, 0x0f, + 0x07, 0x43, 0x00, 0xb7, 0x30, 0x10, 0x00, 0x12, + 0x34, 0x11, 0x3c, 0x13, 0x17, 0xa4, 0x30, 0x2a, + 0x1f, 0x24, 0x2b, 0x00, 0x20, 0xbb, 0x30, 0x16, + 0x41, 0x00, 0x38, 0x0d, 0xc4, 0x30, 0x0d, 0x38, + 0x00, 0xd0, 0x30, 0x00, 0x2c, 0x1c, 0x1b, 0xa2, + 0x30, 0x32, 0x00, 0x17, 0x26, 0x49, 0xaf, 0x30, + 0x25, 0x00, 0x3c, 0xb3, 0x30, 0x21, 0x00, 0x20, + 0x38, 0xa1, 0x30, 0x34, 0x00, 0x48, 0x22, 0x28, + 0xa3, 0x30, 0x32, 0x00, 0x59, 0x25, 0xa7, 0x30, + 0x2f, 0x1c, 0x10, 0x00, 0x44, 0xd5, 0x30, 0x00, + 0x14, 0x1e, 0xaf, 0x30, 0x29, 0x00, 0x10, 0x4d, + 0x3c, 0xda, 0x30, 0xbd, 0x30, 0xb8, 0x30, 0x22, + 0x13, 0x1a, 0x20, 0x33, 0x0c, 0x22, 0x3b, 0x01, + 0x22, 0x44, 0x00, 0x21, 0x44, 0x07, 0xa4, 0x30, + 0x39, 0x00, 0x4f, 0x24, 0xc8, 0x30, 0x14, 0x23, + 0x00, 0xdb, 0x30, 0xf3, 0x30, 0xc9, 0x30, 0x14, + 0x2a, 0x00, 0x12, 0x33, 0x22, 0x12, 0x33, 0x2a, + 0xa4, 0x30, 0x3a, 0x00, 0x0b, 0x49, 0xa4, 0x30, + 0x3a, 0x00, 0x47, 0x3a, 0x1f, 0x2b, 0x3a, 0x47, + 0x0b, 0xb7, 0x30, 0x27, 0x3c, 0x00, 0x30, 0x3c, + 0xaf, 0x30, 0x30, 0x00, 0x3e, 0x44, 0xdf, 0x30, + 0xea, 0x30, 0xd0, 0x30, 0x0f, 0x1a, 0x00, 0x2c, + 0x1b, 0xe1, 0x30, 0xac, 0x30, 0xac, 0x30, 0x35, + 0x00, 0x1c, 0x47, 0x35, 0x50, 0x1c, 0x3f, 0xa2, + 0x30, 0x42, 0x5a, 0x27, 0x42, 0x5a, 0x49, 0x44, + 0x00, 0x51, 0xc3, 0x30, 0x27, 0x00, 0x05, 0x28, + 0xea, 0x30, 0xe9, 0x30, 0xd4, 0x30, 0x17, 0x00, + 0x28, 0xd6, 0x30, 0x15, 0x26, 0x00, 0x15, 0xec, + 0x30, 0xe0, 0x30, 0xb2, 0x30, 0x3a, 0x41, 0x16, + 0x00, 0x41, 0xc3, 0x30, 0x2c, 0x00, 0x05, 0x30, + 0x00, 0xb9, 0x70, 0x31, 0x00, 0x30, 0x00, 0xb9, + 0x70, 0x32, 0x00, 0x30, 0x00, 0xb9, 0x70, 0x68, + 0x50, 0x61, 0x64, 0x61, 0x41, 0x55, 0x62, 0x61, + 0x72, 0x6f, 0x56, 0x70, 0x63, 0x64, 0x6d, 0x64, + 0x00, 0x6d, 0x00, 0xb2, 0x00, 0x49, 0x00, 0x55, + 0x00, 0x73, 0x5e, 0x10, 0x62, 0x2d, 0x66, 0x8c, + 0x54, 0x27, 0x59, 0x63, 0x6b, 0x0e, 0x66, 0xbb, + 0x6c, 0x2a, 0x68, 0x0f, 0x5f, 0x1a, 0x4f, 0x3e, + 0x79, 0x70, 0x00, 0x41, 0x6e, 0x00, 0x41, 0xbc, + 0x03, 0x41, 0x6d, 0x00, 0x41, 0x6b, 0x00, 0x41, + 0x4b, 0x00, 0x42, 0x4d, 0x00, 0x42, 0x47, 0x00, + 0x42, 0x63, 0x61, 0x6c, 0x6b, 0x63, 0x61, 0x6c, + 0x70, 0x00, 0x46, 0x6e, 0x00, 0x46, 0xbc, 0x03, + 0x46, 0xbc, 0x03, 0x67, 0x6d, 0x00, 0x67, 0x6b, + 0x00, 0x67, 0x48, 0x00, 0x7a, 0x6b, 0x48, 0x7a, + 0x4d, 0x48, 0x7a, 0x47, 0x48, 0x7a, 0x54, 0x48, + 0x7a, 0xbc, 0x03, 0x13, 0x21, 0x6d, 0x00, 0x13, + 0x21, 0x64, 0x00, 0x13, 0x21, 0x6b, 0x00, 0x13, + 0x21, 0x66, 0x00, 0x6d, 0x6e, 0x00, 0x6d, 0xbc, + 0x03, 0x6d, 0x6d, 0x00, 0x6d, 0x63, 0x00, 0x6d, + 0x6b, 0x00, 0x6d, 0x63, 0x00, 0x0a, 0x0a, 0x4f, + 0x00, 0x0a, 0x4f, 0x6d, 0x00, 0xb2, 0x00, 0x63, + 0x00, 0x08, 0x0a, 0x4f, 0x0a, 0x0a, 0x50, 0x00, + 0x0a, 0x50, 0x6d, 0x00, 0xb3, 0x00, 0x6b, 0x00, + 0x6d, 0x00, 0xb3, 0x00, 0x6d, 0x00, 0x15, 0x22, + 0x73, 0x00, 0x6d, 0x00, 0x15, 0x22, 0x73, 0x00, + 0xb2, 0x00, 0x50, 0x61, 0x6b, 0x50, 0x61, 0x4d, + 0x50, 0x61, 0x47, 0x50, 0x61, 0x72, 0x61, 0x64, + 0x72, 0x61, 0x64, 0xd1, 0x73, 0x72, 0x00, 0x61, + 0x00, 0x64, 0x00, 0x15, 0x22, 0x73, 0x00, 0xb2, + 0x00, 0x70, 0x00, 0x73, 0x6e, 0x00, 0x73, 0xbc, + 0x03, 0x73, 0x6d, 0x00, 0x73, 0x70, 0x00, 0x56, + 0x6e, 0x00, 0x56, 0xbc, 0x03, 0x56, 0x6d, 0x00, + 0x56, 0x6b, 0x00, 0x56, 0x4d, 0x00, 0x56, 0x70, + 0x00, 0x57, 0x6e, 0x00, 0x57, 0xbc, 0x03, 0x57, + 0x6d, 0x00, 0x57, 0x6b, 0x00, 0x57, 0x4d, 0x00, + 0x57, 0x6b, 0x00, 0xa9, 0x03, 0x4d, 0x00, 0xa9, + 0x03, 0x61, 0x2e, 0x6d, 0x2e, 0x42, 0x71, 0x63, + 0x63, 0x63, 0x64, 0x43, 0xd1, 0x6b, 0x67, 0x43, + 0x6f, 0x2e, 0x64, 0x42, 0x47, 0x79, 0x68, 0x61, + 0x48, 0x50, 0x69, 0x6e, 0x4b, 0x4b, 0x4b, 0x4d, + 0x6b, 0x74, 0x6c, 0x6d, 0x6c, 0x6e, 0x6c, 0x6f, + 0x67, 0x6c, 0x78, 0x6d, 0x62, 0x6d, 0x69, 0x6c, + 0x6d, 0x6f, 0x6c, 0x50, 0x48, 0x70, 0x2e, 0x6d, + 0x2e, 0x50, 0x50, 0x4d, 0x50, 0x52, 0x73, 0x72, + 0x53, 0x76, 0x57, 0x62, 0x56, 0xd1, 0x6d, 0x41, + 0xd1, 0x6d, 0x31, 0x00, 0xe5, 0x65, 0x31, 0x00, + 0x30, 0x00, 0xe5, 0x65, 0x32, 0x00, 0x30, 0x00, + 0xe5, 0x65, 0x33, 0x00, 0x30, 0x00, 0xe5, 0x65, + 0x67, 0x61, 0x6c, 0x4a, 0x04, 0x4c, 0x04, 0x43, + 0x46, 0x51, 0x26, 0x01, 0x53, 0x01, 0x27, 0xa7, + 0x37, 0xab, 0x6b, 0x02, 0x52, 0xab, 0x48, 0x8c, + 0xf4, 0x66, 0xca, 0x8e, 0xc8, 0x8c, 0xd1, 0x6e, + 0x32, 0x4e, 0xe5, 0x53, 0x9c, 0x9f, 0x9c, 0x9f, + 0x51, 0x59, 0xd1, 0x91, 0x87, 0x55, 0x48, 0x59, + 0xf6, 0x61, 0x69, 0x76, 0x85, 0x7f, 0x3f, 0x86, + 0xba, 0x87, 0xf8, 0x88, 0x8f, 0x90, 0x02, 0x6a, + 0x1b, 0x6d, 0xd9, 0x70, 0xde, 0x73, 0x3d, 0x84, + 0x6a, 0x91, 0xf1, 0x99, 0x82, 0x4e, 0x75, 0x53, + 0x04, 0x6b, 0x1b, 0x72, 0x2d, 0x86, 0x1e, 0x9e, + 0x50, 0x5d, 0xeb, 0x6f, 0xcd, 0x85, 0x64, 0x89, + 0xc9, 0x62, 0xd8, 0x81, 0x1f, 0x88, 0xca, 0x5e, + 0x17, 0x67, 0x6a, 0x6d, 0xfc, 0x72, 0xce, 0x90, + 0x86, 0x4f, 0xb7, 0x51, 0xde, 0x52, 0xc4, 0x64, + 0xd3, 0x6a, 0x10, 0x72, 0xe7, 0x76, 0x01, 0x80, + 0x06, 0x86, 0x5c, 0x86, 0xef, 0x8d, 0x32, 0x97, + 0x6f, 0x9b, 0xfa, 0x9d, 0x8c, 0x78, 0x7f, 0x79, + 0xa0, 0x7d, 0xc9, 0x83, 0x04, 0x93, 0x7f, 0x9e, + 0xd6, 0x8a, 0xdf, 0x58, 0x04, 0x5f, 0x60, 0x7c, + 0x7e, 0x80, 0x62, 0x72, 0xca, 0x78, 0xc2, 0x8c, + 0xf7, 0x96, 0xd8, 0x58, 0x62, 0x5c, 0x13, 0x6a, + 0xda, 0x6d, 0x0f, 0x6f, 0x2f, 0x7d, 0x37, 0x7e, + 0x4b, 0x96, 0xd2, 0x52, 0x8b, 0x80, 0xdc, 0x51, + 0xcc, 0x51, 0x1c, 0x7a, 0xbe, 0x7d, 0xf1, 0x83, + 0x75, 0x96, 0x80, 0x8b, 0xcf, 0x62, 0x02, 0x6a, + 0xfe, 0x8a, 0x39, 0x4e, 0xe7, 0x5b, 0x12, 0x60, + 0x87, 0x73, 0x70, 0x75, 0x17, 0x53, 0xfb, 0x78, + 0xbf, 0x4f, 0xa9, 0x5f, 0x0d, 0x4e, 0xcc, 0x6c, + 0x78, 0x65, 0x22, 0x7d, 0xc3, 0x53, 0x5e, 0x58, + 0x01, 0x77, 0x49, 0x84, 0xaa, 0x8a, 0xba, 0x6b, + 0xb0, 0x8f, 0x88, 0x6c, 0xfe, 0x62, 0xe5, 0x82, + 0xa0, 0x63, 0x65, 0x75, 0xae, 0x4e, 0x69, 0x51, + 0xc9, 0x51, 0x81, 0x68, 0xe7, 0x7c, 0x6f, 0x82, + 0xd2, 0x8a, 0xcf, 0x91, 0xf5, 0x52, 0x42, 0x54, + 0x73, 0x59, 0xec, 0x5e, 0xc5, 0x65, 0xfe, 0x6f, + 0x2a, 0x79, 0xad, 0x95, 0x6a, 0x9a, 0x97, 0x9e, + 0xce, 0x9e, 0x9b, 0x52, 0xc6, 0x66, 0x77, 0x6b, + 0x62, 0x8f, 0x74, 0x5e, 0x90, 0x61, 0x00, 0x62, + 0x9a, 0x64, 0x23, 0x6f, 0x49, 0x71, 0x89, 0x74, + 0xca, 0x79, 0xf4, 0x7d, 0x6f, 0x80, 0x26, 0x8f, + 0xee, 0x84, 0x23, 0x90, 0x4a, 0x93, 0x17, 0x52, + 0xa3, 0x52, 0xbd, 0x54, 0xc8, 0x70, 0xc2, 0x88, + 0xaa, 0x8a, 0xc9, 0x5e, 0xf5, 0x5f, 0x7b, 0x63, + 0xae, 0x6b, 0x3e, 0x7c, 0x75, 0x73, 0xe4, 0x4e, + 0xf9, 0x56, 0xe7, 0x5b, 0xba, 0x5d, 0x1c, 0x60, + 0xb2, 0x73, 0x69, 0x74, 0x9a, 0x7f, 0x46, 0x80, + 0x34, 0x92, 0xf6, 0x96, 0x48, 0x97, 0x18, 0x98, + 0x8b, 0x4f, 0xae, 0x79, 0xb4, 0x91, 0xb8, 0x96, + 0xe1, 0x60, 0x86, 0x4e, 0xda, 0x50, 0xee, 0x5b, + 0x3f, 0x5c, 0x99, 0x65, 0x02, 0x6a, 0xce, 0x71, + 0x42, 0x76, 0xfc, 0x84, 0x7c, 0x90, 0x8d, 0x9f, + 0x88, 0x66, 0x2e, 0x96, 0x89, 0x52, 0x7b, 0x67, + 0xf3, 0x67, 0x41, 0x6d, 0x9c, 0x6e, 0x09, 0x74, + 0x59, 0x75, 0x6b, 0x78, 0x10, 0x7d, 0x5e, 0x98, + 0x6d, 0x51, 0x2e, 0x62, 0x78, 0x96, 0x2b, 0x50, + 0x19, 0x5d, 0xea, 0x6d, 0x2a, 0x8f, 0x8b, 0x5f, + 0x44, 0x61, 0x17, 0x68, 0x87, 0x73, 0x86, 0x96, + 0x29, 0x52, 0x0f, 0x54, 0x65, 0x5c, 0x13, 0x66, + 0x4e, 0x67, 0xa8, 0x68, 0xe5, 0x6c, 0x06, 0x74, + 0xe2, 0x75, 0x79, 0x7f, 0xcf, 0x88, 0xe1, 0x88, + 0xcc, 0x91, 0xe2, 0x96, 0x3f, 0x53, 0xba, 0x6e, + 0x1d, 0x54, 0xd0, 0x71, 0x98, 0x74, 0xfa, 0x85, + 0xa3, 0x96, 0x57, 0x9c, 0x9f, 0x9e, 0x97, 0x67, + 0xcb, 0x6d, 0xe8, 0x81, 0xcb, 0x7a, 0x20, 0x7b, + 0x92, 0x7c, 0xc0, 0x72, 0x99, 0x70, 0x58, 0x8b, + 0xc0, 0x4e, 0x36, 0x83, 0x3a, 0x52, 0x07, 0x52, + 0xa6, 0x5e, 0xd3, 0x62, 0xd6, 0x7c, 0x85, 0x5b, + 0x1e, 0x6d, 0xb4, 0x66, 0x3b, 0x8f, 0x4c, 0x88, + 0x4d, 0x96, 0x8b, 0x89, 0xd3, 0x5e, 0x40, 0x51, + 0xc0, 0x55, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x58, + 0x00, 0x00, 0x74, 0x66, 0x00, 0x00, 0x00, 0x00, + 0xde, 0x51, 0x2a, 0x73, 0xca, 0x76, 0x3c, 0x79, + 0x5e, 0x79, 0x65, 0x79, 0x8f, 0x79, 0x56, 0x97, + 0xbe, 0x7c, 0xbd, 0x7f, 0x00, 0x00, 0x12, 0x86, + 0x00, 0x00, 0xf8, 0x8a, 0x00, 0x00, 0x00, 0x00, + 0x38, 0x90, 0xfd, 0x90, 0xef, 0x98, 0xfc, 0x98, + 0x28, 0x99, 0xb4, 0x9d, 0xde, 0x90, 0xb7, 0x96, + 0xae, 0x4f, 0xe7, 0x50, 0x4d, 0x51, 0xc9, 0x52, + 0xe4, 0x52, 0x51, 0x53, 0x9d, 0x55, 0x06, 0x56, + 0x68, 0x56, 0x40, 0x58, 0xa8, 0x58, 0x64, 0x5c, + 0x6e, 0x5c, 0x94, 0x60, 0x68, 0x61, 0x8e, 0x61, + 0xf2, 0x61, 0x4f, 0x65, 0xe2, 0x65, 0x91, 0x66, + 0x85, 0x68, 0x77, 0x6d, 0x1a, 0x6e, 0x22, 0x6f, + 0x6e, 0x71, 0x2b, 0x72, 0x22, 0x74, 0x91, 0x78, + 0x3e, 0x79, 0x49, 0x79, 0x48, 0x79, 0x50, 0x79, + 0x56, 0x79, 0x5d, 0x79, 0x8d, 0x79, 0x8e, 0x79, + 0x40, 0x7a, 0x81, 0x7a, 0xc0, 0x7b, 0xf4, 0x7d, + 0x09, 0x7e, 0x41, 0x7e, 0x72, 0x7f, 0x05, 0x80, + 0xed, 0x81, 0x79, 0x82, 0x79, 0x82, 0x57, 0x84, + 0x10, 0x89, 0x96, 0x89, 0x01, 0x8b, 0x39, 0x8b, + 0xd3, 0x8c, 0x08, 0x8d, 0xb6, 0x8f, 0x38, 0x90, + 0xe3, 0x96, 0xff, 0x97, 0x3b, 0x98, 0x75, 0x60, + 0xee, 0x42, 0x18, 0x82, 0x02, 0x26, 0x4e, 0xb5, + 0x51, 0x68, 0x51, 0x80, 0x4f, 0x45, 0x51, 0x80, + 0x51, 0xc7, 0x52, 0xfa, 0x52, 0x9d, 0x55, 0x55, + 0x55, 0x99, 0x55, 0xe2, 0x55, 0x5a, 0x58, 0xb3, + 0x58, 0x44, 0x59, 0x54, 0x59, 0x62, 0x5a, 0x28, + 0x5b, 0xd2, 0x5e, 0xd9, 0x5e, 0x69, 0x5f, 0xad, + 0x5f, 0xd8, 0x60, 0x4e, 0x61, 0x08, 0x61, 0x8e, + 0x61, 0x60, 0x61, 0xf2, 0x61, 0x34, 0x62, 0xc4, + 0x63, 0x1c, 0x64, 0x52, 0x64, 0x56, 0x65, 0x74, + 0x66, 0x17, 0x67, 0x1b, 0x67, 0x56, 0x67, 0x79, + 0x6b, 0xba, 0x6b, 0x41, 0x6d, 0xdb, 0x6e, 0xcb, + 0x6e, 0x22, 0x6f, 0x1e, 0x70, 0x6e, 0x71, 0xa7, + 0x77, 0x35, 0x72, 0xaf, 0x72, 0x2a, 0x73, 0x71, + 0x74, 0x06, 0x75, 0x3b, 0x75, 0x1d, 0x76, 0x1f, + 0x76, 0xca, 0x76, 0xdb, 0x76, 0xf4, 0x76, 0x4a, + 0x77, 0x40, 0x77, 0xcc, 0x78, 0xb1, 0x7a, 0xc0, + 0x7b, 0x7b, 0x7c, 0x5b, 0x7d, 0xf4, 0x7d, 0x3e, + 0x7f, 0x05, 0x80, 0x52, 0x83, 0xef, 0x83, 0x79, + 0x87, 0x41, 0x89, 0x86, 0x89, 0x96, 0x89, 0xbf, + 0x8a, 0xf8, 0x8a, 0xcb, 0x8a, 0x01, 0x8b, 0xfe, + 0x8a, 0xed, 0x8a, 0x39, 0x8b, 0x8a, 0x8b, 0x08, + 0x8d, 0x38, 0x8f, 0x72, 0x90, 0x99, 0x91, 0x76, + 0x92, 0x7c, 0x96, 0xe3, 0x96, 0x56, 0x97, 0xdb, + 0x97, 0xff, 0x97, 0x0b, 0x98, 0x3b, 0x98, 0x12, + 0x9b, 0x9c, 0x9f, 0x4a, 0x28, 0x44, 0x28, 0xd5, + 0x33, 0x9d, 0x3b, 0x18, 0x40, 0x39, 0x40, 0x49, + 0x52, 0xd0, 0x5c, 0xd3, 0x7e, 0x43, 0x9f, 0x8e, + 0x9f, 0x2a, 0xa0, 0x02, 0x66, 0x66, 0x66, 0x69, + 0x66, 0x6c, 0x66, 0x66, 0x69, 0x66, 0x66, 0x6c, + 0x7f, 0x01, 0x74, 0x73, 0x00, 0x74, 0x65, 0x05, + 0x0f, 0x11, 0x0f, 0x00, 0x0f, 0x06, 0x19, 0x11, + 0x0f, 0x08, 0xd9, 0x05, 0xb4, 0x05, 0x00, 0x00, + 0x00, 0x00, 0xf2, 0x05, 0xb7, 0x05, 0xd0, 0x05, + 0x12, 0x00, 0x03, 0x04, 0x0b, 0x0c, 0x0d, 0x18, + 0x1a, 0xe9, 0x05, 0xc1, 0x05, 0xe9, 0x05, 0xc2, + 0x05, 0x49, 0xfb, 0xc1, 0x05, 0x49, 0xfb, 0xc2, + 0x05, 0xd0, 0x05, 0xb7, 0x05, 0xd0, 0x05, 0xb8, + 0x05, 0xd0, 0x05, 0xbc, 0x05, 0xd8, 0x05, 0xbc, + 0x05, 0xde, 0x05, 0xbc, 0x05, 0xe0, 0x05, 0xbc, + 0x05, 0xe3, 0x05, 0xbc, 0x05, 0xb9, 0x05, 0x2d, + 0x03, 0x2e, 0x03, 0x2f, 0x03, 0x30, 0x03, 0x31, + 0x03, 0x1c, 0x00, 0x18, 0x06, 0x22, 0x06, 0x2b, + 0x06, 0xd0, 0x05, 0xdc, 0x05, 0x71, 0x06, 0x00, + 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0f, 0x0f, 0x0f, 0x0f, 0x09, 0x09, 0x09, + 0x09, 0x0e, 0x0e, 0x0e, 0x0e, 0x08, 0x08, 0x08, + 0x08, 0x33, 0x33, 0x33, 0x33, 0x35, 0x35, 0x35, + 0x35, 0x13, 0x13, 0x13, 0x13, 0x12, 0x12, 0x12, + 0x12, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, + 0x16, 0x1c, 0x1c, 0x1b, 0x1b, 0x1d, 0x1d, 0x17, + 0x17, 0x27, 0x27, 0x20, 0x20, 0x38, 0x38, 0x38, + 0x38, 0x3e, 0x3e, 0x3e, 0x3e, 0x42, 0x42, 0x42, + 0x42, 0x40, 0x40, 0x40, 0x40, 0x49, 0x49, 0x4a, + 0x4a, 0x4a, 0x4a, 0x4f, 0x4f, 0x50, 0x50, 0x50, + 0x50, 0x4d, 0x4d, 0x4d, 0x4d, 0x61, 0x61, 0x62, + 0x62, 0x49, 0x06, 0x64, 0x64, 0x64, 0x64, 0x7e, + 0x7e, 0x7d, 0x7d, 0x7f, 0x7f, 0x2e, 0x82, 0x82, + 0x7c, 0x7c, 0x80, 0x80, 0x87, 0x87, 0x87, 0x87, + 0x00, 0x00, 0x26, 0x06, 0x00, 0x01, 0x00, 0x01, + 0x00, 0xaf, 0x00, 0xaf, 0x00, 0x22, 0x00, 0x22, + 0x00, 0xa1, 0x00, 0xa1, 0x00, 0xa0, 0x00, 0xa0, + 0x00, 0xa2, 0x00, 0xa2, 0x00, 0xaa, 0x00, 0xaa, + 0x00, 0xaa, 0x00, 0x23, 0x00, 0x23, 0x00, 0x23, + 0xcc, 0x06, 0x00, 0x00, 0x00, 0x00, 0x26, 0x06, + 0x00, 0x06, 0x00, 0x07, 0x00, 0x1f, 0x00, 0x23, + 0x00, 0x24, 0x02, 0x06, 0x02, 0x07, 0x02, 0x08, + 0x02, 0x1f, 0x02, 0x23, 0x02, 0x24, 0x04, 0x06, + 0x04, 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x23, + 0x04, 0x24, 0x05, 0x06, 0x05, 0x1f, 0x05, 0x23, + 0x05, 0x24, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, + 0x07, 0x1f, 0x08, 0x06, 0x08, 0x07, 0x08, 0x1f, + 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x08, 0x0d, 0x1f, + 0x0f, 0x07, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, + 0x10, 0x08, 0x10, 0x1f, 0x11, 0x07, 0x11, 0x1f, + 0x12, 0x1f, 0x13, 0x06, 0x13, 0x1f, 0x14, 0x06, + 0x14, 0x1f, 0x1b, 0x06, 0x1b, 0x07, 0x1b, 0x08, + 0x1b, 0x1f, 0x1b, 0x23, 0x1b, 0x24, 0x1c, 0x07, + 0x1c, 0x1f, 0x1c, 0x23, 0x1c, 0x24, 0x1d, 0x01, + 0x1d, 0x06, 0x1d, 0x07, 0x1d, 0x08, 0x1d, 0x1e, + 0x1d, 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x06, + 0x1e, 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x23, + 0x1e, 0x24, 0x1f, 0x06, 0x1f, 0x07, 0x1f, 0x08, + 0x1f, 0x1f, 0x1f, 0x23, 0x1f, 0x24, 0x20, 0x06, + 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, 0x23, + 0x20, 0x24, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x23, + 0x21, 0x24, 0x24, 0x06, 0x24, 0x07, 0x24, 0x08, + 0x24, 0x1f, 0x24, 0x23, 0x24, 0x24, 0x0a, 0x4a, + 0x0b, 0x4a, 0x23, 0x4a, 0x20, 0x00, 0x4c, 0x06, + 0x51, 0x06, 0x51, 0x06, 0xff, 0x00, 0x1f, 0x26, + 0x06, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x1f, 0x00, + 0x20, 0x00, 0x23, 0x00, 0x24, 0x02, 0x0b, 0x02, + 0x0c, 0x02, 0x1f, 0x02, 0x20, 0x02, 0x23, 0x02, + 0x24, 0x04, 0x0b, 0x04, 0x0c, 0x04, 0x1f, 0x26, + 0x06, 0x04, 0x20, 0x04, 0x23, 0x04, 0x24, 0x05, + 0x0b, 0x05, 0x0c, 0x05, 0x1f, 0x05, 0x20, 0x05, + 0x23, 0x05, 0x24, 0x1b, 0x23, 0x1b, 0x24, 0x1c, + 0x23, 0x1c, 0x24, 0x1d, 0x01, 0x1d, 0x1e, 0x1d, + 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x1f, 0x1e, + 0x23, 0x1e, 0x24, 0x1f, 0x01, 0x1f, 0x1f, 0x20, + 0x0b, 0x20, 0x0c, 0x20, 0x1f, 0x20, 0x20, 0x20, + 0x23, 0x20, 0x24, 0x23, 0x4a, 0x24, 0x0b, 0x24, + 0x0c, 0x24, 0x1f, 0x24, 0x20, 0x24, 0x23, 0x24, + 0x24, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x1f, 0x00, 0x21, 0x02, 0x06, 0x02, 0x07, 0x02, + 0x08, 0x02, 0x1f, 0x02, 0x21, 0x04, 0x06, 0x04, + 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x21, 0x05, + 0x1f, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, 0x07, + 0x1f, 0x08, 0x06, 0x08, 0x1f, 0x0d, 0x06, 0x0d, + 0x07, 0x0d, 0x08, 0x0d, 0x1f, 0x0f, 0x07, 0x0f, + 0x08, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, 0x10, + 0x08, 0x10, 0x1f, 0x11, 0x07, 0x12, 0x1f, 0x13, + 0x06, 0x13, 0x1f, 0x14, 0x06, 0x14, 0x1f, 0x1b, + 0x06, 0x1b, 0x07, 0x1b, 0x08, 0x1b, 0x1f, 0x1c, + 0x07, 0x1c, 0x1f, 0x1d, 0x06, 0x1d, 0x07, 0x1d, + 0x08, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x06, 0x1e, + 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x21, 0x1f, + 0x06, 0x1f, 0x07, 0x1f, 0x08, 0x1f, 0x1f, 0x20, + 0x06, 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, + 0x21, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x4a, 0x24, + 0x06, 0x24, 0x07, 0x24, 0x08, 0x24, 0x1f, 0x24, + 0x21, 0x00, 0x1f, 0x00, 0x21, 0x02, 0x1f, 0x02, + 0x21, 0x04, 0x1f, 0x04, 0x21, 0x05, 0x1f, 0x05, + 0x21, 0x0d, 0x1f, 0x0d, 0x21, 0x0e, 0x1f, 0x0e, + 0x21, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x1f, 0x20, + 0x1f, 0x20, 0x21, 0x24, 0x1f, 0x24, 0x21, 0x40, + 0x06, 0x4e, 0x06, 0x51, 0x06, 0x27, 0x06, 0x10, + 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, + 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, + 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, 0x05, + 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, + 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0d, + 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x10, + 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, + 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, + 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, 0x05, + 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, + 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0d, + 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0c, + 0x20, 0x0d, 0x20, 0x10, 0x1e, 0x0c, 0x05, 0x0c, + 0x06, 0x0c, 0x07, 0x0d, 0x05, 0x0d, 0x06, 0x0d, + 0x07, 0x10, 0x1e, 0x11, 0x1e, 0x00, 0x24, 0x00, + 0x24, 0x2a, 0x06, 0x00, 0x02, 0x1b, 0x00, 0x03, + 0x02, 0x00, 0x03, 0x02, 0x00, 0x03, 0x1b, 0x00, + 0x04, 0x1b, 0x00, 0x1b, 0x02, 0x00, 0x1b, 0x03, + 0x00, 0x1b, 0x04, 0x02, 0x1b, 0x03, 0x02, 0x1b, + 0x03, 0x03, 0x1b, 0x20, 0x03, 0x1b, 0x1f, 0x09, + 0x03, 0x02, 0x09, 0x02, 0x03, 0x09, 0x02, 0x1f, + 0x09, 0x1b, 0x03, 0x09, 0x1b, 0x03, 0x09, 0x1b, + 0x02, 0x09, 0x1b, 0x1b, 0x09, 0x1b, 0x1b, 0x0b, + 0x03, 0x03, 0x0b, 0x03, 0x03, 0x0b, 0x1b, 0x1b, + 0x0a, 0x03, 0x1b, 0x0a, 0x03, 0x1b, 0x0a, 0x02, + 0x20, 0x0a, 0x1b, 0x04, 0x0a, 0x1b, 0x04, 0x0a, + 0x1b, 0x1b, 0x0a, 0x1b, 0x1b, 0x0c, 0x03, 0x1f, + 0x0c, 0x04, 0x1b, 0x0c, 0x04, 0x1b, 0x0d, 0x1b, + 0x03, 0x0d, 0x1b, 0x03, 0x0d, 0x1b, 0x1b, 0x0d, + 0x1b, 0x20, 0x0f, 0x02, 0x1b, 0x0f, 0x1b, 0x1b, + 0x0f, 0x1b, 0x1b, 0x0f, 0x1b, 0x1f, 0x10, 0x1b, + 0x1b, 0x10, 0x1b, 0x20, 0x10, 0x1b, 0x1f, 0x17, + 0x04, 0x1b, 0x17, 0x04, 0x1b, 0x18, 0x1b, 0x03, + 0x18, 0x1b, 0x1b, 0x1a, 0x03, 0x1b, 0x1a, 0x03, + 0x20, 0x1a, 0x03, 0x1f, 0x1a, 0x02, 0x02, 0x1a, + 0x02, 0x02, 0x1a, 0x04, 0x1b, 0x1a, 0x04, 0x1b, + 0x1a, 0x1b, 0x03, 0x1a, 0x1b, 0x03, 0x1b, 0x03, + 0x02, 0x1b, 0x03, 0x1b, 0x1b, 0x03, 0x20, 0x1b, + 0x02, 0x03, 0x1b, 0x02, 0x1b, 0x1b, 0x04, 0x02, + 0x1b, 0x04, 0x1b, 0x28, 0x06, 0x1d, 0x04, 0x06, + 0x1f, 0x1d, 0x04, 0x1f, 0x1d, 0x1d, 0x1e, 0x05, + 0x1d, 0x1e, 0x05, 0x21, 0x1e, 0x04, 0x1d, 0x1e, + 0x04, 0x1d, 0x1e, 0x04, 0x21, 0x1e, 0x1d, 0x22, + 0x1e, 0x1d, 0x21, 0x22, 0x1d, 0x1d, 0x22, 0x1d, + 0x1d, 0x00, 0x06, 0x22, 0x02, 0x04, 0x22, 0x02, + 0x04, 0x21, 0x02, 0x06, 0x22, 0x02, 0x06, 0x21, + 0x02, 0x1d, 0x22, 0x02, 0x1d, 0x21, 0x04, 0x1d, + 0x22, 0x04, 0x05, 0x21, 0x04, 0x1d, 0x21, 0x0b, + 0x06, 0x21, 0x0d, 0x05, 0x22, 0x0c, 0x05, 0x22, + 0x0e, 0x05, 0x22, 0x1c, 0x04, 0x22, 0x1c, 0x1d, + 0x22, 0x22, 0x05, 0x22, 0x22, 0x04, 0x22, 0x22, + 0x1d, 0x22, 0x1d, 0x1d, 0x22, 0x1a, 0x1d, 0x22, + 0x1e, 0x05, 0x22, 0x1a, 0x1d, 0x05, 0x1c, 0x05, + 0x1d, 0x11, 0x1d, 0x22, 0x1b, 0x1d, 0x22, 0x1e, + 0x04, 0x05, 0x1d, 0x06, 0x22, 0x1c, 0x04, 0x1d, + 0x1b, 0x1d, 0x1d, 0x1c, 0x04, 0x1d, 0x1e, 0x04, + 0x05, 0x04, 0x05, 0x22, 0x05, 0x04, 0x22, 0x1d, + 0x04, 0x22, 0x19, 0x1d, 0x22, 0x00, 0x05, 0x22, + 0x1b, 0x1d, 0x1d, 0x11, 0x04, 0x1d, 0x0d, 0x1d, + 0x1d, 0x0b, 0x06, 0x22, 0x1e, 0x04, 0x22, 0x35, + 0x06, 0x00, 0x0f, 0x9d, 0x0d, 0x0f, 0x9d, 0x27, + 0x06, 0x00, 0x1d, 0x1d, 0x20, 0x00, 0x1c, 0x01, + 0x0a, 0x1e, 0x06, 0x1e, 0x08, 0x0e, 0x1d, 0x12, + 0x1e, 0x0a, 0x0c, 0x21, 0x1d, 0x12, 0x1d, 0x23, + 0x20, 0x21, 0x0c, 0x1d, 0x1e, 0x35, 0x06, 0x00, + 0x0f, 0x14, 0x27, 0x06, 0x0e, 0x1d, 0x22, 0xff, + 0x00, 0x1d, 0x1d, 0x20, 0xff, 0x12, 0x1d, 0x23, + 0x20, 0xff, 0x21, 0x0c, 0x1d, 0x1e, 0x27, 0x06, + 0x05, 0x1d, 0xff, 0x05, 0x1d, 0x00, 0x1d, 0x20, + 0x27, 0x06, 0x0a, 0xa5, 0x00, 0x1d, 0x2c, 0x00, + 0x01, 0x30, 0x02, 0x30, 0x3a, 0x00, 0x3b, 0x00, + 0x21, 0x00, 0x3f, 0x00, 0x16, 0x30, 0x17, 0x30, + 0x26, 0x20, 0x13, 0x20, 0x12, 0x01, 0x00, 0x5f, + 0x5f, 0x28, 0x29, 0x7b, 0x7d, 0x08, 0x30, 0x0c, + 0x0d, 0x08, 0x09, 0x02, 0x03, 0x00, 0x01, 0x04, + 0x05, 0x06, 0x07, 0x5b, 0x00, 0x5d, 0x00, 0x3e, + 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x5f, + 0x00, 0x5f, 0x00, 0x5f, 0x00, 0x2c, 0x00, 0x01, + 0x30, 0x2e, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x3a, + 0x00, 0x3f, 0x00, 0x21, 0x00, 0x14, 0x20, 0x28, + 0x00, 0x29, 0x00, 0x7b, 0x00, 0x7d, 0x00, 0x14, + 0x30, 0x15, 0x30, 0x23, 0x26, 0x2a, 0x2b, 0x2d, + 0x3c, 0x3e, 0x3d, 0x00, 0x5c, 0x24, 0x25, 0x40, + 0x40, 0x06, 0xff, 0x0b, 0x00, 0x0b, 0xff, 0x0c, + 0x20, 0x00, 0x4d, 0x06, 0x40, 0x06, 0xff, 0x0e, + 0x00, 0x0e, 0xff, 0x0f, 0x00, 0x0f, 0xff, 0x10, + 0x00, 0x10, 0xff, 0x11, 0x00, 0x11, 0xff, 0x12, + 0x00, 0x12, 0x21, 0x06, 0x00, 0x01, 0x01, 0x02, + 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x05, + 0x05, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, + 0x08, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, + 0x0f, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x12, + 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, + 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, + 0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, + 0x18, 0x19, 0x19, 0x19, 0x19, 0x20, 0x20, 0x20, + 0x20, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, + 0x22, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, 0x24, + 0x24, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, + 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x29, + 0x29, 0x22, 0x06, 0x22, 0x00, 0x22, 0x00, 0x22, + 0x01, 0x22, 0x01, 0x22, 0x03, 0x22, 0x03, 0x22, + 0x05, 0x22, 0x05, 0x21, 0x00, 0x85, 0x29, 0x01, + 0x30, 0x01, 0x0b, 0x0c, 0x00, 0xfa, 0xf1, 0xa0, + 0xa2, 0xa4, 0xa6, 0xa8, 0xe2, 0xe4, 0xe6, 0xc2, + 0xfb, 0xa1, 0xa3, 0xa5, 0xa7, 0xa9, 0xaa, 0xac, + 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, + 0xbe, 0xc0, 0xc3, 0xc5, 0xc7, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xd1, 0xd4, 0xd7, 0xda, 0xdd, + 0xde, 0xdf, 0xe0, 0xe1, 0xe3, 0xe5, 0xe7, 0xe8, + 0xe9, 0xea, 0xeb, 0xec, 0xee, 0xf2, 0x98, 0x99, + 0x31, 0x31, 0x4f, 0x31, 0x55, 0x31, 0x5b, 0x31, + 0x61, 0x31, 0xa2, 0x00, 0xa3, 0x00, 0xac, 0x00, + 0xaf, 0x00, 0xa6, 0x00, 0xa5, 0x00, 0xa9, 0x20, + 0x00, 0x00, 0x02, 0x25, 0x90, 0x21, 0x91, 0x21, + 0x92, 0x21, 0x93, 0x21, 0xa0, 0x25, 0xcb, 0x25, + 0xd0, 0x02, 0xd1, 0x02, 0xe6, 0x00, 0x99, 0x02, + 0x53, 0x02, 0x00, 0x00, 0xa3, 0x02, 0x66, 0xab, + 0xa5, 0x02, 0xa4, 0x02, 0x56, 0x02, 0x57, 0x02, + 0x91, 0x1d, 0x58, 0x02, 0x5e, 0x02, 0xa9, 0x02, + 0x64, 0x02, 0x62, 0x02, 0x60, 0x02, 0x9b, 0x02, + 0x27, 0x01, 0x9c, 0x02, 0x67, 0x02, 0x84, 0x02, + 0xaa, 0x02, 0xab, 0x02, 0x6c, 0x02, 0x04, 0xdf, + 0x8e, 0xa7, 0x6e, 0x02, 0x05, 0xdf, 0x8e, 0x02, + 0x06, 0xdf, 0xf8, 0x00, 0x76, 0x02, 0x77, 0x02, + 0x71, 0x00, 0x7a, 0x02, 0x08, 0xdf, 0x7d, 0x02, + 0x7e, 0x02, 0x80, 0x02, 0xa8, 0x02, 0xa6, 0x02, + 0x67, 0xab, 0xa7, 0x02, 0x88, 0x02, 0x71, 0x2c, + 0x00, 0x00, 0x8f, 0x02, 0xa1, 0x02, 0xa2, 0x02, + 0x98, 0x02, 0xc0, 0x01, 0xc1, 0x01, 0xc2, 0x01, + 0x0a, 0xdf, 0x1e, 0xdf, 0x41, 0x04, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x14, 0x99, 0x10, 0xba, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x9b, 0x10, 0xba, 0x10, + 0x05, 0x05, 0xa5, 0x10, 0xba, 0x10, 0x05, 0x31, + 0x11, 0x27, 0x11, 0x32, 0x11, 0x27, 0x11, 0x55, + 0x47, 0x13, 0x3e, 0x13, 0x47, 0x13, 0x57, 0x13, + 0x55, 0xb9, 0x14, 0xba, 0x14, 0xb9, 0x14, 0xb0, + 0x14, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x14, 0xbd, + 0x14, 0x55, 0x50, 0xb8, 0x15, 0xaf, 0x15, 0xb9, + 0x15, 0xaf, 0x15, 0x55, 0x35, 0x19, 0x30, 0x19, + 0x05, 0x57, 0xd1, 0x65, 0xd1, 0x58, 0xd1, 0x65, + 0xd1, 0x5f, 0xd1, 0x6e, 0xd1, 0x5f, 0xd1, 0x6f, + 0xd1, 0x5f, 0xd1, 0x70, 0xd1, 0x5f, 0xd1, 0x71, + 0xd1, 0x5f, 0xd1, 0x72, 0xd1, 0x55, 0x55, 0x55, + 0x05, 0xb9, 0xd1, 0x65, 0xd1, 0xba, 0xd1, 0x65, + 0xd1, 0xbb, 0xd1, 0x6e, 0xd1, 0xbc, 0xd1, 0x6e, + 0xd1, 0xbb, 0xd1, 0x6f, 0xd1, 0xbc, 0xd1, 0x6f, + 0xd1, 0x55, 0x55, 0x55, 0x41, 0x00, 0x61, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x69, 0x00, 0x41, 0x00, + 0x61, 0x00, 0x41, 0x00, 0x43, 0x44, 0x00, 0x00, + 0x47, 0x00, 0x00, 0x4a, 0x4b, 0x00, 0x00, 0x4e, + 0x4f, 0x50, 0x51, 0x00, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, + 0x00, 0x66, 0x68, 0x00, 0x70, 0x00, 0x41, 0x00, + 0x61, 0x00, 0x41, 0x42, 0x00, 0x44, 0x45, 0x46, + 0x47, 0x4a, 0x00, 0x53, 0x00, 0x61, 0x00, 0x41, + 0x42, 0x00, 0x44, 0x45, 0x46, 0x47, 0x00, 0x49, + 0x4a, 0x4b, 0x4c, 0x4d, 0x00, 0x4f, 0x53, 0x00, + 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, + 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, + 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, + 0x61, 0x00, 0x31, 0x01, 0x37, 0x02, 0x91, 0x03, + 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, + 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, + 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, + 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, + 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, + 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, + 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, + 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, + 0x1f, 0x04, 0x20, 0x05, 0x0b, 0x0c, 0x30, 0x00, + 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x30, 0x04, 0x3a, 0x04, 0x3e, 0x04, 0x4b, 0x04, + 0x4d, 0x04, 0x4e, 0x04, 0x89, 0xa6, 0x30, 0x04, + 0xa9, 0x26, 0x28, 0xb9, 0x7f, 0x9f, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0a, + 0x0b, 0x0e, 0x0f, 0x11, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x1a, 0x1b, 0x61, 0x26, 0x25, 0x2f, + 0x7b, 0x51, 0xa6, 0xb1, 0x04, 0x27, 0x06, 0x00, + 0x01, 0x05, 0x08, 0x2a, 0x06, 0x1e, 0x08, 0x03, + 0x0d, 0x20, 0x19, 0x1a, 0x1b, 0x1c, 0x09, 0x0f, + 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, 0x01, 0x04, + 0x06, 0x0c, 0x0e, 0x10, 0x44, 0x90, 0x77, 0x45, + 0x28, 0x06, 0x2c, 0x06, 0x00, 0x00, 0x47, 0x06, + 0x33, 0x06, 0x17, 0x10, 0x11, 0x12, 0x13, 0x00, + 0x06, 0x0e, 0x02, 0x0f, 0x34, 0x06, 0x2a, 0x06, + 0x2b, 0x06, 0x2e, 0x06, 0x00, 0x00, 0x36, 0x06, + 0x00, 0x00, 0x3a, 0x06, 0x2d, 0x06, 0x00, 0x00, + 0x4a, 0x06, 0x00, 0x00, 0x44, 0x06, 0x00, 0x00, + 0x46, 0x06, 0x33, 0x06, 0x39, 0x06, 0x00, 0x00, + 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, 0x34, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x2e, 0x06, 0x00, 0x00, + 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, 0x00, 0x00, + 0xba, 0x06, 0x00, 0x00, 0x6f, 0x06, 0x00, 0x00, + 0x28, 0x06, 0x2c, 0x06, 0x00, 0x00, 0x47, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x2d, 0x06, 0x37, 0x06, + 0x4a, 0x06, 0x43, 0x06, 0x00, 0x00, 0x45, 0x06, + 0x46, 0x06, 0x33, 0x06, 0x39, 0x06, 0x41, 0x06, + 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, 0x34, 0x06, + 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, 0x00, 0x00, + 0x36, 0x06, 0x38, 0x06, 0x3a, 0x06, 0x6e, 0x06, + 0x00, 0x00, 0xa1, 0x06, 0x27, 0x06, 0x00, 0x01, + 0x05, 0x08, 0x20, 0x21, 0x0b, 0x06, 0x10, 0x23, + 0x2a, 0x06, 0x1a, 0x1b, 0x1c, 0x09, 0x0f, 0x17, + 0x0b, 0x18, 0x07, 0x0a, 0x00, 0x01, 0x04, 0x06, + 0x0c, 0x0e, 0x10, 0x28, 0x06, 0x2c, 0x06, 0x2f, + 0x06, 0x00, 0x00, 0x48, 0x06, 0x32, 0x06, 0x2d, + 0x06, 0x37, 0x06, 0x4a, 0x06, 0x2a, 0x06, 0x1a, + 0x1b, 0x1c, 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, + 0x0a, 0x00, 0x01, 0x04, 0x06, 0x0c, 0x0e, 0x10, + 0x30, 0x2e, 0x30, 0x00, 0x2c, 0x00, 0x28, 0x00, + 0x41, 0x00, 0x29, 0x00, 0x14, 0x30, 0x53, 0x00, + 0x15, 0x30, 0x43, 0x52, 0x43, 0x44, 0x57, 0x5a, + 0x41, 0x00, 0x48, 0x56, 0x4d, 0x56, 0x53, 0x44, + 0x53, 0x53, 0x50, 0x50, 0x56, 0x57, 0x43, 0x4d, + 0x43, 0x4d, 0x44, 0x4d, 0x52, 0x44, 0x4a, 0x4b, + 0x30, 0x30, 0x00, 0x68, 0x68, 0x4b, 0x62, 0x57, + 0x5b, 0xcc, 0x53, 0xc7, 0x30, 0x8c, 0x4e, 0x1a, + 0x59, 0xe3, 0x89, 0x29, 0x59, 0xa4, 0x4e, 0x20, + 0x66, 0x21, 0x71, 0x99, 0x65, 0x4d, 0x52, 0x8c, + 0x5f, 0x8d, 0x51, 0xb0, 0x65, 0x1d, 0x52, 0x42, + 0x7d, 0x1f, 0x75, 0xa9, 0x8c, 0xf0, 0x58, 0x39, + 0x54, 0x14, 0x6f, 0x95, 0x62, 0x55, 0x63, 0x00, + 0x4e, 0x09, 0x4e, 0x4a, 0x90, 0xe6, 0x5d, 0x2d, + 0x4e, 0xf3, 0x53, 0x07, 0x63, 0x70, 0x8d, 0x53, + 0x62, 0x81, 0x79, 0x7a, 0x7a, 0x08, 0x54, 0x80, + 0x6e, 0x09, 0x67, 0x08, 0x67, 0x33, 0x75, 0x72, + 0x52, 0xb6, 0x55, 0x4d, 0x91, 0x14, 0x30, 0x15, + 0x30, 0x2c, 0x67, 0x09, 0x4e, 0x8c, 0x4e, 0x89, + 0x5b, 0xb9, 0x70, 0x53, 0x62, 0xd7, 0x76, 0xdd, + 0x52, 0x57, 0x65, 0x97, 0x5f, 0xef, 0x53, 0x30, + 0x00, 0x38, 0x4e, 0x05, 0x00, 0x09, 0x22, 0x01, + 0x60, 0x4f, 0xae, 0x4f, 0xbb, 0x4f, 0x02, 0x50, + 0x7a, 0x50, 0x99, 0x50, 0xe7, 0x50, 0xcf, 0x50, + 0x9e, 0x34, 0x3a, 0x06, 0x4d, 0x51, 0x54, 0x51, + 0x64, 0x51, 0x77, 0x51, 0x1c, 0x05, 0xb9, 0x34, + 0x67, 0x51, 0x8d, 0x51, 0x4b, 0x05, 0x97, 0x51, + 0xa4, 0x51, 0xcc, 0x4e, 0xac, 0x51, 0xb5, 0x51, + 0xdf, 0x91, 0xf5, 0x51, 0x03, 0x52, 0xdf, 0x34, + 0x3b, 0x52, 0x46, 0x52, 0x72, 0x52, 0x77, 0x52, + 0x15, 0x35, 0x02, 0x00, 0x20, 0x80, 0x80, 0x00, + 0x08, 0x00, 0x00, 0xc7, 0x52, 0x00, 0x02, 0x1d, + 0x33, 0x3e, 0x3f, 0x50, 0x82, 0x8a, 0x93, 0xac, + 0xb6, 0xb8, 0xb8, 0xb8, 0x2c, 0x0a, 0x70, 0x70, + 0xca, 0x53, 0xdf, 0x53, 0x63, 0x0b, 0xeb, 0x53, + 0xf1, 0x53, 0x06, 0x54, 0x9e, 0x54, 0x38, 0x54, + 0x48, 0x54, 0x68, 0x54, 0xa2, 0x54, 0xf6, 0x54, + 0x10, 0x55, 0x53, 0x55, 0x63, 0x55, 0x84, 0x55, + 0x84, 0x55, 0x99, 0x55, 0xab, 0x55, 0xb3, 0x55, + 0xc2, 0x55, 0x16, 0x57, 0x06, 0x56, 0x17, 0x57, + 0x51, 0x56, 0x74, 0x56, 0x07, 0x52, 0xee, 0x58, + 0xce, 0x57, 0xf4, 0x57, 0x0d, 0x58, 0x8b, 0x57, + 0x32, 0x58, 0x31, 0x58, 0xac, 0x58, 0xe4, 0x14, + 0xf2, 0x58, 0xf7, 0x58, 0x06, 0x59, 0x1a, 0x59, + 0x22, 0x59, 0x62, 0x59, 0xa8, 0x16, 0xea, 0x16, + 0xec, 0x59, 0x1b, 0x5a, 0x27, 0x5a, 0xd8, 0x59, + 0x66, 0x5a, 0xee, 0x36, 0xfc, 0x36, 0x08, 0x5b, + 0x3e, 0x5b, 0x3e, 0x5b, 0xc8, 0x19, 0xc3, 0x5b, + 0xd8, 0x5b, 0xe7, 0x5b, 0xf3, 0x5b, 0x18, 0x1b, + 0xff, 0x5b, 0x06, 0x5c, 0x53, 0x5f, 0x22, 0x5c, + 0x81, 0x37, 0x60, 0x5c, 0x6e, 0x5c, 0xc0, 0x5c, + 0x8d, 0x5c, 0xe4, 0x1d, 0x43, 0x5d, 0xe6, 0x1d, + 0x6e, 0x5d, 0x6b, 0x5d, 0x7c, 0x5d, 0xe1, 0x5d, + 0xe2, 0x5d, 0x2f, 0x38, 0xfd, 0x5d, 0x28, 0x5e, + 0x3d, 0x5e, 0x69, 0x5e, 0x62, 0x38, 0x83, 0x21, + 0x7c, 0x38, 0xb0, 0x5e, 0xb3, 0x5e, 0xb6, 0x5e, + 0xca, 0x5e, 0x92, 0xa3, 0xfe, 0x5e, 0x31, 0x23, + 0x31, 0x23, 0x01, 0x82, 0x22, 0x5f, 0x22, 0x5f, + 0xc7, 0x38, 0xb8, 0x32, 0xda, 0x61, 0x62, 0x5f, + 0x6b, 0x5f, 0xe3, 0x38, 0x9a, 0x5f, 0xcd, 0x5f, + 0xd7, 0x5f, 0xf9, 0x5f, 0x81, 0x60, 0x3a, 0x39, + 0x1c, 0x39, 0x94, 0x60, 0xd4, 0x26, 0xc7, 0x60, + 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, 0x02, 0x08, + 0x00, 0x80, 0x08, 0x00, 0x00, 0x08, 0x80, 0x28, + 0x80, 0x02, 0x00, 0x00, 0x02, 0x48, 0x61, 0x00, + 0x04, 0x06, 0x04, 0x32, 0x46, 0x6a, 0x5c, 0x67, + 0x96, 0xaa, 0xae, 0xc8, 0xd3, 0x5d, 0x62, 0x00, + 0x54, 0x77, 0xf3, 0x0c, 0x2b, 0x3d, 0x63, 0xfc, + 0x62, 0x68, 0x63, 0x83, 0x63, 0xe4, 0x63, 0xf1, + 0x2b, 0x22, 0x64, 0xc5, 0x63, 0xa9, 0x63, 0x2e, + 0x3a, 0x69, 0x64, 0x7e, 0x64, 0x9d, 0x64, 0x77, + 0x64, 0x6c, 0x3a, 0x4f, 0x65, 0x6c, 0x65, 0x0a, + 0x30, 0xe3, 0x65, 0xf8, 0x66, 0x49, 0x66, 0x19, + 0x3b, 0x91, 0x66, 0x08, 0x3b, 0xe4, 0x3a, 0x92, + 0x51, 0x95, 0x51, 0x00, 0x67, 0x9c, 0x66, 0xad, + 0x80, 0xd9, 0x43, 0x17, 0x67, 0x1b, 0x67, 0x21, + 0x67, 0x5e, 0x67, 0x53, 0x67, 0xc3, 0x33, 0x49, + 0x3b, 0xfa, 0x67, 0x85, 0x67, 0x52, 0x68, 0x85, + 0x68, 0x6d, 0x34, 0x8e, 0x68, 0x1f, 0x68, 0x14, + 0x69, 0x9d, 0x3b, 0x42, 0x69, 0xa3, 0x69, 0xea, + 0x69, 0xa8, 0x6a, 0xa3, 0x36, 0xdb, 0x6a, 0x18, + 0x3c, 0x21, 0x6b, 0xa7, 0x38, 0x54, 0x6b, 0x4e, + 0x3c, 0x72, 0x6b, 0x9f, 0x6b, 0xba, 0x6b, 0xbb, + 0x6b, 0x8d, 0x3a, 0x0b, 0x1d, 0xfa, 0x3a, 0x4e, + 0x6c, 0xbc, 0x3c, 0xbf, 0x6c, 0xcd, 0x6c, 0x67, + 0x6c, 0x16, 0x6d, 0x3e, 0x6d, 0x77, 0x6d, 0x41, + 0x6d, 0x69, 0x6d, 0x78, 0x6d, 0x85, 0x6d, 0x1e, + 0x3d, 0x34, 0x6d, 0x2f, 0x6e, 0x6e, 0x6e, 0x33, + 0x3d, 0xcb, 0x6e, 0xc7, 0x6e, 0xd1, 0x3e, 0xf9, + 0x6d, 0x6e, 0x6f, 0x5e, 0x3f, 0x8e, 0x3f, 0xc6, + 0x6f, 0x39, 0x70, 0x1e, 0x70, 0x1b, 0x70, 0x96, + 0x3d, 0x4a, 0x70, 0x7d, 0x70, 0x77, 0x70, 0xad, + 0x70, 0x25, 0x05, 0x45, 0x71, 0x63, 0x42, 0x9c, + 0x71, 0xab, 0x43, 0x28, 0x72, 0x35, 0x72, 0x50, + 0x72, 0x08, 0x46, 0x80, 0x72, 0x95, 0x72, 0x35, + 0x47, 0x02, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, 0x02, 0x02, + 0x80, 0x8a, 0x00, 0x00, 0x20, 0x00, 0x08, 0x0a, + 0x00, 0x80, 0x88, 0x80, 0x20, 0x14, 0x48, 0x7a, + 0x73, 0x8b, 0x73, 0xac, 0x3e, 0xa5, 0x73, 0xb8, + 0x3e, 0xb8, 0x3e, 0x47, 0x74, 0x5c, 0x74, 0x71, + 0x74, 0x85, 0x74, 0xca, 0x74, 0x1b, 0x3f, 0x24, + 0x75, 0x36, 0x4c, 0x3e, 0x75, 0x92, 0x4c, 0x70, + 0x75, 0x9f, 0x21, 0x10, 0x76, 0xa1, 0x4f, 0xb8, + 0x4f, 0x44, 0x50, 0xfc, 0x3f, 0x08, 0x40, 0xf4, + 0x76, 0xf3, 0x50, 0xf2, 0x50, 0x19, 0x51, 0x33, + 0x51, 0x1e, 0x77, 0x1f, 0x77, 0x1f, 0x77, 0x4a, + 0x77, 0x39, 0x40, 0x8b, 0x77, 0x46, 0x40, 0x96, + 0x40, 0x1d, 0x54, 0x4e, 0x78, 0x8c, 0x78, 0xcc, + 0x78, 0xe3, 0x40, 0x26, 0x56, 0x56, 0x79, 0x9a, + 0x56, 0xc5, 0x56, 0x8f, 0x79, 0xeb, 0x79, 0x2f, + 0x41, 0x40, 0x7a, 0x4a, 0x7a, 0x4f, 0x7a, 0x7c, + 0x59, 0xa7, 0x5a, 0xa7, 0x5a, 0xee, 0x7a, 0x02, + 0x42, 0xab, 0x5b, 0xc6, 0x7b, 0xc9, 0x7b, 0x27, + 0x42, 0x80, 0x5c, 0xd2, 0x7c, 0xa0, 0x42, 0xe8, + 0x7c, 0xe3, 0x7c, 0x00, 0x7d, 0x86, 0x5f, 0x63, + 0x7d, 0x01, 0x43, 0xc7, 0x7d, 0x02, 0x7e, 0x45, + 0x7e, 0x34, 0x43, 0x28, 0x62, 0x47, 0x62, 0x59, + 0x43, 0xd9, 0x62, 0x7a, 0x7f, 0x3e, 0x63, 0x95, + 0x7f, 0xfa, 0x7f, 0x05, 0x80, 0xda, 0x64, 0x23, + 0x65, 0x60, 0x80, 0xa8, 0x65, 0x70, 0x80, 0x5f, + 0x33, 0xd5, 0x43, 0xb2, 0x80, 0x03, 0x81, 0x0b, + 0x44, 0x3e, 0x81, 0xb5, 0x5a, 0xa7, 0x67, 0xb5, + 0x67, 0x93, 0x33, 0x9c, 0x33, 0x01, 0x82, 0x04, + 0x82, 0x9e, 0x8f, 0x6b, 0x44, 0x91, 0x82, 0x8b, + 0x82, 0x9d, 0x82, 0xb3, 0x52, 0xb1, 0x82, 0xb3, + 0x82, 0xbd, 0x82, 0xe6, 0x82, 0x3c, 0x6b, 0xe5, + 0x82, 0x1d, 0x83, 0x63, 0x83, 0xad, 0x83, 0x23, + 0x83, 0xbd, 0x83, 0xe7, 0x83, 0x57, 0x84, 0x53, + 0x83, 0xca, 0x83, 0xcc, 0x83, 0xdc, 0x83, 0x36, + 0x6c, 0x6b, 0x6d, 0x02, 0x00, 0x00, 0x20, 0x22, + 0x2a, 0xa0, 0x0a, 0x00, 0x20, 0x80, 0x28, 0x00, + 0xa8, 0x20, 0x20, 0x00, 0x02, 0x80, 0x22, 0x02, + 0x8a, 0x08, 0x00, 0xaa, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x28, 0xd5, 0x6c, 0x2b, 0x45, 0xf1, + 0x84, 0xf3, 0x84, 0x16, 0x85, 0xca, 0x73, 0x64, + 0x85, 0x2c, 0x6f, 0x5d, 0x45, 0x61, 0x45, 0xb1, + 0x6f, 0xd2, 0x70, 0x6b, 0x45, 0x50, 0x86, 0x5c, + 0x86, 0x67, 0x86, 0x69, 0x86, 0xa9, 0x86, 0x88, + 0x86, 0x0e, 0x87, 0xe2, 0x86, 0x79, 0x87, 0x28, + 0x87, 0x6b, 0x87, 0x86, 0x87, 0xd7, 0x45, 0xe1, + 0x87, 0x01, 0x88, 0xf9, 0x45, 0x60, 0x88, 0x63, + 0x88, 0x67, 0x76, 0xd7, 0x88, 0xde, 0x88, 0x35, + 0x46, 0xfa, 0x88, 0xbb, 0x34, 0xae, 0x78, 0x66, + 0x79, 0xbe, 0x46, 0xc7, 0x46, 0xa0, 0x8a, 0xed, + 0x8a, 0x8a, 0x8b, 0x55, 0x8c, 0xa8, 0x7c, 0xab, + 0x8c, 0xc1, 0x8c, 0x1b, 0x8d, 0x77, 0x8d, 0x2f, + 0x7f, 0x04, 0x08, 0xcb, 0x8d, 0xbc, 0x8d, 0xf0, + 0x8d, 0xde, 0x08, 0xd4, 0x8e, 0x38, 0x8f, 0xd2, + 0x85, 0xed, 0x85, 0x94, 0x90, 0xf1, 0x90, 0x11, + 0x91, 0x2e, 0x87, 0x1b, 0x91, 0x38, 0x92, 0xd7, + 0x92, 0xd8, 0x92, 0x7c, 0x92, 0xf9, 0x93, 0x15, + 0x94, 0xfa, 0x8b, 0x8b, 0x95, 0x95, 0x49, 0xb7, + 0x95, 0x77, 0x8d, 0xe6, 0x49, 0xc3, 0x96, 0xb2, + 0x5d, 0x23, 0x97, 0x45, 0x91, 0x1a, 0x92, 0x6e, + 0x4a, 0x76, 0x4a, 0xe0, 0x97, 0x0a, 0x94, 0xb2, + 0x4a, 0x96, 0x94, 0x0b, 0x98, 0x0b, 0x98, 0x29, + 0x98, 0xb6, 0x95, 0xe2, 0x98, 0x33, 0x4b, 0x29, + 0x99, 0xa7, 0x99, 0xc2, 0x99, 0xfe, 0x99, 0xce, + 0x4b, 0x30, 0x9b, 0x12, 0x9b, 0x40, 0x9c, 0xfd, + 0x9c, 0xce, 0x4c, 0xed, 0x4c, 0x67, 0x9d, 0xce, + 0xa0, 0xf8, 0x4c, 0x05, 0xa1, 0x0e, 0xa2, 0x91, + 0xa2, 0xbb, 0x9e, 0x56, 0x4d, 0xf9, 0x9e, 0xfe, + 0x9e, 0x05, 0x9f, 0x0f, 0x9f, 0x16, 0x9f, 0x3b, + 0x9f, 0x00, 0xa6, 0x02, 0x88, 0xa0, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x00, 0x28, 0x00, 0x08, 0xa0, + 0x80, 0xa0, 0x80, 0x00, 0x80, 0x80, 0x00, 0x0a, + 0x88, 0x80, 0x00, 0x80, 0x00, 0x20, 0x2a, 0x00, + 0x80, +}; + +static const uint16_t unicode_comp_table[945] = { + 0x4a01, 0x49c0, 0x4a02, 0x0280, 0x0281, 0x0282, 0x0283, 0x02c0, + 0x02c2, 0x0a00, 0x0284, 0x2442, 0x0285, 0x07c0, 0x0980, 0x0982, + 0x2440, 0x2280, 0x02c4, 0x2282, 0x2284, 0x2286, 0x02c6, 0x02c8, + 0x02ca, 0x02cc, 0x0287, 0x228a, 0x02ce, 0x228c, 0x2290, 0x2292, + 0x228e, 0x0288, 0x0289, 0x028a, 0x2482, 0x0300, 0x0302, 0x0304, + 0x028b, 0x2480, 0x0308, 0x0984, 0x0986, 0x2458, 0x0a02, 0x0306, + 0x2298, 0x229a, 0x229e, 0x0900, 0x030a, 0x22a0, 0x030c, 0x030e, + 0x0840, 0x0310, 0x0312, 0x22a2, 0x22a6, 0x09c0, 0x22a4, 0x22a8, + 0x22aa, 0x028c, 0x028d, 0x028e, 0x0340, 0x0342, 0x0344, 0x0380, + 0x028f, 0x248e, 0x07c2, 0x0988, 0x098a, 0x2490, 0x0346, 0x22ac, + 0x0400, 0x22b0, 0x0842, 0x22b2, 0x0402, 0x22b4, 0x0440, 0x0444, + 0x22b6, 0x0442, 0x22c2, 0x22c0, 0x22c4, 0x22c6, 0x22c8, 0x0940, + 0x04c0, 0x0291, 0x22ca, 0x04c4, 0x22cc, 0x04c2, 0x22d0, 0x22ce, + 0x0292, 0x0293, 0x0294, 0x0295, 0x0540, 0x0542, 0x0a08, 0x0296, + 0x2494, 0x0544, 0x07c4, 0x098c, 0x098e, 0x06c0, 0x2492, 0x0844, + 0x2308, 0x230a, 0x0580, 0x230c, 0x0584, 0x0990, 0x0992, 0x230e, + 0x0582, 0x2312, 0x0586, 0x0588, 0x2314, 0x058c, 0x2316, 0x0998, + 0x058a, 0x231e, 0x0590, 0x2320, 0x099a, 0x058e, 0x2324, 0x2322, + 0x0299, 0x029a, 0x029b, 0x05c0, 0x05c2, 0x05c4, 0x029c, 0x24ac, + 0x05c6, 0x05c8, 0x07c6, 0x0994, 0x0996, 0x0700, 0x24aa, 0x2326, + 0x05ca, 0x232a, 0x2328, 0x2340, 0x2342, 0x2344, 0x2346, 0x05cc, + 0x234a, 0x2348, 0x234c, 0x234e, 0x2350, 0x24b8, 0x029d, 0x05ce, + 0x24be, 0x0a0c, 0x2352, 0x0600, 0x24bc, 0x24ba, 0x0640, 0x2354, + 0x0642, 0x0644, 0x2356, 0x2358, 0x02a0, 0x02a1, 0x02a2, 0x02a3, + 0x02c1, 0x02c3, 0x0a01, 0x02a4, 0x2443, 0x02a5, 0x07c1, 0x0981, + 0x0983, 0x2441, 0x2281, 0x02c5, 0x2283, 0x2285, 0x2287, 0x02c7, + 0x02c9, 0x02cb, 0x02cd, 0x02a7, 0x228b, 0x02cf, 0x228d, 0x2291, + 0x2293, 0x228f, 0x02a8, 0x02a9, 0x02aa, 0x2483, 0x0301, 0x0303, + 0x0305, 0x02ab, 0x2481, 0x0309, 0x0985, 0x0987, 0x2459, 0x0a03, + 0x0307, 0x2299, 0x229b, 0x229f, 0x0901, 0x030b, 0x22a1, 0x030d, + 0x030f, 0x0841, 0x0311, 0x0313, 0x22a3, 0x22a7, 0x09c1, 0x22a5, + 0x22a9, 0x22ab, 0x2380, 0x02ac, 0x02ad, 0x02ae, 0x0341, 0x0343, + 0x0345, 0x02af, 0x248f, 0x07c3, 0x0989, 0x098b, 0x2491, 0x0347, + 0x22ad, 0x0401, 0x0884, 0x22b1, 0x0843, 0x22b3, 0x0403, 0x22b5, + 0x0441, 0x0445, 0x22b7, 0x0443, 0x22c3, 0x22c1, 0x22c5, 0x22c7, + 0x22c9, 0x0941, 0x04c1, 0x02b1, 0x22cb, 0x04c5, 0x22cd, 0x04c3, + 0x22d1, 0x22cf, 0x02b2, 0x02b3, 0x02b4, 0x02b5, 0x0541, 0x0543, + 0x0a09, 0x02b6, 0x2495, 0x0545, 0x07c5, 0x098d, 0x098f, 0x06c1, + 0x2493, 0x0845, 0x2309, 0x230b, 0x0581, 0x230d, 0x0585, 0x0991, + 0x0993, 0x230f, 0x0583, 0x2313, 0x0587, 0x0589, 0x2315, 0x058d, + 0x2317, 0x0999, 0x058b, 0x231f, 0x2381, 0x0591, 0x2321, 0x099b, + 0x058f, 0x2325, 0x2323, 0x02b9, 0x02ba, 0x02bb, 0x05c1, 0x05c3, + 0x05c5, 0x02bc, 0x24ad, 0x05c7, 0x05c9, 0x07c7, 0x0995, 0x0997, + 0x0701, 0x24ab, 0x2327, 0x05cb, 0x232b, 0x2329, 0x2341, 0x2343, + 0x2345, 0x2347, 0x05cd, 0x234b, 0x2349, 0x2382, 0x234d, 0x234f, + 0x2351, 0x24b9, 0x02bd, 0x05cf, 0x24bf, 0x0a0d, 0x2353, 0x02bf, + 0x24bd, 0x2383, 0x24bb, 0x0641, 0x2355, 0x0643, 0x0645, 0x2357, + 0x2359, 0x3101, 0x0c80, 0x2e00, 0x2446, 0x2444, 0x244a, 0x2448, + 0x0800, 0x0942, 0x0944, 0x0804, 0x2288, 0x2486, 0x2484, 0x248a, + 0x2488, 0x22ae, 0x2498, 0x2496, 0x249c, 0x249a, 0x2300, 0x0a06, + 0x2302, 0x0a04, 0x0946, 0x07ce, 0x07ca, 0x07c8, 0x07cc, 0x2447, + 0x2445, 0x244b, 0x2449, 0x0801, 0x0943, 0x0945, 0x0805, 0x2289, + 0x2487, 0x2485, 0x248b, 0x2489, 0x22af, 0x2499, 0x2497, 0x249d, + 0x249b, 0x2301, 0x0a07, 0x2303, 0x0a05, 0x0947, 0x07cf, 0x07cb, + 0x07c9, 0x07cd, 0x2450, 0x244e, 0x2454, 0x2452, 0x2451, 0x244f, + 0x2455, 0x2453, 0x2294, 0x2296, 0x2295, 0x2297, 0x2304, 0x2306, + 0x2305, 0x2307, 0x2318, 0x2319, 0x231a, 0x231b, 0x232c, 0x232d, + 0x232e, 0x232f, 0x2400, 0x24a2, 0x24a0, 0x24a6, 0x24a4, 0x24a8, + 0x24a3, 0x24a1, 0x24a7, 0x24a5, 0x24a9, 0x24b0, 0x24ae, 0x24b4, + 0x24b2, 0x24b6, 0x24b1, 0x24af, 0x24b5, 0x24b3, 0x24b7, 0x0882, + 0x0880, 0x0881, 0x0802, 0x0803, 0x229c, 0x229d, 0x0a0a, 0x0a0b, + 0x0883, 0x0b40, 0x2c8a, 0x0c81, 0x2c89, 0x2c88, 0x2540, 0x2541, + 0x2d00, 0x2e07, 0x0d00, 0x2640, 0x2641, 0x2e80, 0x0d01, 0x26c8, + 0x26c9, 0x2f00, 0x2f84, 0x0d02, 0x2f83, 0x2f82, 0x0d40, 0x26d8, + 0x26d9, 0x3186, 0x0d04, 0x2740, 0x2741, 0x3100, 0x3086, 0x0d06, + 0x3085, 0x3084, 0x0d41, 0x2840, 0x3200, 0x0d07, 0x284f, 0x2850, + 0x3280, 0x2c84, 0x2e03, 0x2857, 0x0d42, 0x2c81, 0x2c80, 0x24c0, + 0x24c1, 0x2c86, 0x2c83, 0x28c0, 0x0d43, 0x25c0, 0x25c1, 0x2940, + 0x0d44, 0x26c0, 0x26c1, 0x2e05, 0x2e02, 0x29c0, 0x0d45, 0x2f05, + 0x2f04, 0x0d80, 0x26d0, 0x26d1, 0x2f80, 0x2a40, 0x0d82, 0x26e0, + 0x26e1, 0x3080, 0x3081, 0x2ac0, 0x0d83, 0x3004, 0x3003, 0x0d81, + 0x27c0, 0x27c1, 0x3082, 0x2b40, 0x0d84, 0x2847, 0x2848, 0x3184, + 0x3181, 0x2f06, 0x0d08, 0x2f81, 0x3005, 0x0d46, 0x3083, 0x3182, + 0x0e00, 0x0e01, 0x0f40, 0x1180, 0x1182, 0x0f03, 0x0f00, 0x11c0, + 0x0f01, 0x1140, 0x1202, 0x1204, 0x0f81, 0x1240, 0x0fc0, 0x1242, + 0x0f80, 0x1244, 0x1284, 0x0f82, 0x1286, 0x1288, 0x128a, 0x12c0, + 0x1282, 0x1181, 0x1183, 0x1043, 0x1040, 0x11c1, 0x1041, 0x1141, + 0x1203, 0x1205, 0x10c1, 0x1241, 0x1000, 0x1243, 0x10c0, 0x1245, + 0x1285, 0x10c2, 0x1287, 0x1289, 0x128b, 0x12c1, 0x1283, 0x1080, + 0x1100, 0x1101, 0x1200, 0x1201, 0x1280, 0x1281, 0x1340, 0x1341, + 0x1343, 0x1342, 0x1344, 0x13c2, 0x1400, 0x13c0, 0x1440, 0x1480, + 0x14c0, 0x1540, 0x1541, 0x1740, 0x1700, 0x1741, 0x17c0, 0x1800, + 0x1802, 0x1801, 0x1840, 0x1880, 0x1900, 0x18c0, 0x18c1, 0x1901, + 0x1940, 0x1942, 0x1941, 0x1980, 0x19c0, 0x19c2, 0x19c1, 0x1c80, + 0x1cc0, 0x1dc0, 0x1f80, 0x2000, 0x2002, 0x2004, 0x2006, 0x2008, + 0x2040, 0x2080, 0x2082, 0x20c0, 0x20c1, 0x2100, 0x22b8, 0x22b9, + 0x2310, 0x2311, 0x231c, 0x231d, 0x244c, 0x2456, 0x244d, 0x2457, + 0x248c, 0x248d, 0x249e, 0x249f, 0x2500, 0x2502, 0x2504, 0x2bc0, + 0x2501, 0x2503, 0x2505, 0x2bc1, 0x2bc2, 0x2bc3, 0x2bc4, 0x2bc5, + 0x2bc6, 0x2bc7, 0x2580, 0x2582, 0x2584, 0x2bc8, 0x2581, 0x2583, + 0x2585, 0x2bc9, 0x2bca, 0x2bcb, 0x2bcc, 0x2bcd, 0x2bce, 0x2bcf, + 0x2600, 0x2602, 0x2601, 0x2603, 0x2680, 0x2682, 0x2681, 0x2683, + 0x26c2, 0x26c4, 0x26c6, 0x2c00, 0x26c3, 0x26c5, 0x26c7, 0x2c01, + 0x2c02, 0x2c03, 0x2c04, 0x2c05, 0x2c06, 0x2c07, 0x26ca, 0x26cc, + 0x26ce, 0x2c08, 0x26cb, 0x26cd, 0x26cf, 0x2c09, 0x2c0a, 0x2c0b, + 0x2c0c, 0x2c0d, 0x2c0e, 0x2c0f, 0x26d2, 0x26d4, 0x26d6, 0x26d3, + 0x26d5, 0x26d7, 0x26da, 0x26dc, 0x26de, 0x26db, 0x26dd, 0x26df, + 0x2700, 0x2702, 0x2701, 0x2703, 0x2780, 0x2782, 0x2781, 0x2783, + 0x2800, 0x2802, 0x2804, 0x2801, 0x2803, 0x2805, 0x2842, 0x2844, + 0x2846, 0x2849, 0x284b, 0x284d, 0x2c40, 0x284a, 0x284c, 0x284e, + 0x2c41, 0x2c42, 0x2c43, 0x2c44, 0x2c45, 0x2c46, 0x2c47, 0x2851, + 0x2853, 0x2855, 0x2c48, 0x2852, 0x2854, 0x2856, 0x2c49, 0x2c4a, + 0x2c4b, 0x2c4c, 0x2c4d, 0x2c4e, 0x2c4f, 0x2c82, 0x2e01, 0x3180, + 0x2c87, 0x2f01, 0x2f02, 0x2f03, 0x2e06, 0x3185, 0x3000, 0x3001, + 0x3002, 0x4640, 0x4641, 0x4680, 0x46c0, 0x46c2, 0x46c1, 0x4700, + 0x4740, 0x4780, 0x47c0, 0x47c2, 0x4900, 0x4940, 0x4980, 0x4982, + 0x4a00, 0x49c2, 0x4a03, 0x4a04, 0x4a40, 0x4a41, 0x4a80, 0x4a81, + 0x4ac0, 0x4ac1, 0x4bc0, 0x4bc1, 0x4b00, 0x4b01, 0x4b40, 0x4b41, + 0x4bc2, 0x4bc3, 0x4b80, 0x4b81, 0x4b82, 0x4b83, 0x4c00, 0x4c01, + 0x4c02, 0x4c03, 0x5600, 0x5440, 0x5442, 0x5444, 0x5446, 0x5448, + 0x544a, 0x544c, 0x544e, 0x5450, 0x5452, 0x5454, 0x5456, 0x5480, + 0x5482, 0x5484, 0x54c0, 0x54c1, 0x5500, 0x5501, 0x5540, 0x5541, + 0x5580, 0x5581, 0x55c0, 0x55c1, 0x5680, 0x58c0, 0x5700, 0x5702, + 0x5704, 0x5706, 0x5708, 0x570a, 0x570c, 0x570e, 0x5710, 0x5712, + 0x5714, 0x5716, 0x5740, 0x5742, 0x5744, 0x5780, 0x5781, 0x57c0, + 0x57c1, 0x5800, 0x5801, 0x5840, 0x5841, 0x5880, 0x5881, 0x5900, + 0x5901, 0x5902, 0x5903, 0x5940, 0x8f40, 0x8f42, 0x8f80, 0x8fc0, + 0x8fc1, 0x9000, 0x9001, 0x9041, 0x9040, 0x9043, 0x9080, 0x9081, + 0x90c0, +}; + +typedef enum { + UNICODE_GC_Cn, + UNICODE_GC_Lu, + UNICODE_GC_Ll, + UNICODE_GC_Lt, + UNICODE_GC_Lm, + UNICODE_GC_Lo, + UNICODE_GC_Mn, + UNICODE_GC_Mc, + UNICODE_GC_Me, + UNICODE_GC_Nd, + UNICODE_GC_Nl, + UNICODE_GC_No, + UNICODE_GC_Sm, + UNICODE_GC_Sc, + UNICODE_GC_Sk, + UNICODE_GC_So, + UNICODE_GC_Pc, + UNICODE_GC_Pd, + UNICODE_GC_Ps, + UNICODE_GC_Pe, + UNICODE_GC_Pi, + UNICODE_GC_Pf, + UNICODE_GC_Po, + UNICODE_GC_Zs, + UNICODE_GC_Zl, + UNICODE_GC_Zp, + UNICODE_GC_Cc, + UNICODE_GC_Cf, + UNICODE_GC_Cs, + UNICODE_GC_Co, + UNICODE_GC_LC, + UNICODE_GC_L, + UNICODE_GC_M, + UNICODE_GC_N, + UNICODE_GC_S, + UNICODE_GC_P, + UNICODE_GC_Z, + UNICODE_GC_C, + UNICODE_GC_COUNT, +} UnicodeGCEnum; + +static const char unicode_gc_name_table[] = + "Cn,Unassigned" "\0" + "Lu,Uppercase_Letter" "\0" + "Ll,Lowercase_Letter" "\0" + "Lt,Titlecase_Letter" "\0" + "Lm,Modifier_Letter" "\0" + "Lo,Other_Letter" "\0" + "Mn,Nonspacing_Mark" "\0" + "Mc,Spacing_Mark" "\0" + "Me,Enclosing_Mark" "\0" + "Nd,Decimal_Number,digit" "\0" + "Nl,Letter_Number" "\0" + "No,Other_Number" "\0" + "Sm,Math_Symbol" "\0" + "Sc,Currency_Symbol" "\0" + "Sk,Modifier_Symbol" "\0" + "So,Other_Symbol" "\0" + "Pc,Connector_Punctuation" "\0" + "Pd,Dash_Punctuation" "\0" + "Ps,Open_Punctuation" "\0" + "Pe,Close_Punctuation" "\0" + "Pi,Initial_Punctuation" "\0" + "Pf,Final_Punctuation" "\0" + "Po,Other_Punctuation" "\0" + "Zs,Space_Separator" "\0" + "Zl,Line_Separator" "\0" + "Zp,Paragraph_Separator" "\0" + "Cc,Control,cntrl" "\0" + "Cf,Format" "\0" + "Cs,Surrogate" "\0" + "Co,Private_Use" "\0" + "LC,Cased_Letter" "\0" + "L,Letter" "\0" + "M,Mark,Combining_Mark" "\0" + "N,Number" "\0" + "S,Symbol" "\0" + "P,Punctuation,punct" "\0" + "Z,Separator" "\0" + "C,Other" "\0" +; + +static const uint8_t unicode_gc_table[3948] = { + 0xfa, 0x18, 0x17, 0x56, 0x0d, 0x56, 0x12, 0x13, + 0x16, 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, + 0x4c, 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, + 0x10, 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, + 0xfa, 0x19, 0x17, 0x16, 0x6d, 0x0f, 0x16, 0x0e, + 0x0f, 0x05, 0x14, 0x0c, 0x1b, 0x0f, 0x0e, 0x0f, + 0x0c, 0x2b, 0x0e, 0x02, 0x36, 0x0e, 0x0b, 0x05, + 0x15, 0x4b, 0x16, 0xe1, 0x0f, 0x0c, 0xc1, 0xe2, + 0x10, 0x0c, 0xe2, 0x00, 0xff, 0x30, 0x02, 0xff, + 0x08, 0x02, 0xff, 0x27, 0xbf, 0x22, 0x21, 0x02, + 0x5f, 0x5f, 0x21, 0x22, 0x61, 0x02, 0x21, 0x02, + 0x41, 0x42, 0x21, 0x02, 0x21, 0x02, 0x9f, 0x7f, + 0x02, 0x5f, 0x5f, 0x21, 0x02, 0x5f, 0x3f, 0x02, + 0x05, 0x3f, 0x22, 0x65, 0x01, 0x03, 0x02, 0x01, + 0x03, 0x02, 0x01, 0x03, 0x02, 0xff, 0x08, 0x02, + 0xff, 0x0a, 0x02, 0x01, 0x03, 0x02, 0x5f, 0x21, + 0x02, 0xff, 0x32, 0xa2, 0x21, 0x02, 0x21, 0x22, + 0x5f, 0x41, 0x02, 0xff, 0x00, 0xe2, 0x3c, 0x05, + 0xe2, 0x13, 0xe4, 0x0a, 0x6e, 0xe4, 0x04, 0xee, + 0x06, 0x84, 0xce, 0x04, 0x0e, 0x04, 0xee, 0x09, + 0xe6, 0x68, 0x7f, 0x04, 0x0e, 0x3f, 0x20, 0x04, + 0x42, 0x16, 0x01, 0x60, 0x2e, 0x01, 0x16, 0x41, + 0x00, 0x01, 0x00, 0x21, 0x02, 0xe1, 0x09, 0x00, + 0xe1, 0x01, 0xe2, 0x1b, 0x3f, 0x02, 0x41, 0x42, + 0xff, 0x10, 0x62, 0x3f, 0x0c, 0x5f, 0x3f, 0x02, + 0xe1, 0x2b, 0xe2, 0x28, 0xff, 0x1a, 0x0f, 0x86, + 0x28, 0xff, 0x2f, 0xff, 0x06, 0x02, 0xff, 0x58, + 0x00, 0xe1, 0x1e, 0x20, 0x04, 0xb6, 0xe2, 0x21, + 0x16, 0x11, 0x20, 0x2f, 0x0d, 0x00, 0xe6, 0x25, + 0x11, 0x06, 0x16, 0x26, 0x16, 0x26, 0x16, 0x06, + 0xe0, 0x00, 0xe5, 0x13, 0x60, 0x65, 0x36, 0xe0, + 0x03, 0xbb, 0x4c, 0x36, 0x0d, 0x36, 0x2f, 0xe6, + 0x03, 0x16, 0x1b, 0x56, 0xe5, 0x18, 0x04, 0xe5, + 0x02, 0xe6, 0x0d, 0xe9, 0x02, 0x76, 0x25, 0x06, + 0xe5, 0x5b, 0x16, 0x05, 0xc6, 0x1b, 0x0f, 0xa6, + 0x24, 0x26, 0x0f, 0x66, 0x25, 0xe9, 0x02, 0x45, + 0x2f, 0x05, 0xf6, 0x06, 0x00, 0x1b, 0x05, 0x06, + 0xe5, 0x16, 0xe6, 0x13, 0x20, 0xe5, 0x51, 0xe6, + 0x03, 0x05, 0xe0, 0x06, 0xe9, 0x02, 0xe5, 0x19, + 0xe6, 0x01, 0x24, 0x0f, 0x56, 0x04, 0x20, 0x06, + 0x2d, 0xe5, 0x0e, 0x66, 0x04, 0xe6, 0x01, 0x04, + 0x46, 0x04, 0x86, 0x20, 0xf6, 0x07, 0x00, 0xe5, + 0x11, 0x46, 0x20, 0x16, 0x00, 0xe5, 0x03, 0x80, + 0xe5, 0x10, 0x0e, 0xa5, 0x00, 0x3b, 0xa0, 0xe6, + 0x00, 0xe5, 0x21, 0x04, 0xe6, 0x10, 0x1b, 0xe6, + 0x18, 0x07, 0xe5, 0x2e, 0x06, 0x07, 0x06, 0x05, + 0x47, 0xe6, 0x00, 0x67, 0x06, 0x27, 0x05, 0xc6, + 0xe5, 0x02, 0x26, 0x36, 0xe9, 0x02, 0x16, 0x04, + 0xe5, 0x07, 0x06, 0x27, 0x00, 0xe5, 0x00, 0x20, + 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x05, + 0x40, 0x65, 0x20, 0x06, 0x05, 0x47, 0x66, 0x20, + 0x27, 0x20, 0x27, 0x06, 0x05, 0xe0, 0x00, 0x07, + 0x60, 0x25, 0x00, 0x45, 0x26, 0x20, 0xe9, 0x02, + 0x25, 0x2d, 0xab, 0x0f, 0x0d, 0x05, 0x16, 0x06, + 0x20, 0x26, 0x07, 0x00, 0xa5, 0x60, 0x25, 0x20, + 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, 0x25, + 0x00, 0x25, 0x20, 0x06, 0x00, 0x47, 0x26, 0x60, + 0x26, 0x20, 0x46, 0x40, 0x06, 0xc0, 0x65, 0x00, + 0x05, 0xc0, 0xe9, 0x02, 0x26, 0x45, 0x06, 0x16, + 0xe0, 0x02, 0x26, 0x07, 0x00, 0xe5, 0x01, 0x00, + 0x45, 0x00, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, + 0x00, 0x85, 0x20, 0x06, 0x05, 0x47, 0x86, 0x00, + 0x26, 0x07, 0x00, 0x27, 0x06, 0x20, 0x05, 0xe0, + 0x07, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x16, 0x0d, + 0xc0, 0x05, 0xa6, 0x00, 0x06, 0x27, 0x00, 0xe5, + 0x00, 0x20, 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, + 0x00, 0x25, 0x00, 0x85, 0x20, 0x06, 0x05, 0x07, + 0x06, 0x07, 0x66, 0x20, 0x27, 0x20, 0x27, 0x06, + 0xc0, 0x26, 0x07, 0x60, 0x25, 0x00, 0x45, 0x26, + 0x20, 0xe9, 0x02, 0x0f, 0x05, 0xab, 0xe0, 0x02, + 0x06, 0x05, 0x00, 0xa5, 0x40, 0x45, 0x00, 0x65, + 0x40, 0x25, 0x00, 0x05, 0x00, 0x25, 0x40, 0x25, + 0x40, 0x45, 0x40, 0xe5, 0x04, 0x60, 0x27, 0x06, + 0x27, 0x40, 0x47, 0x00, 0x47, 0x06, 0x20, 0x05, + 0xa0, 0x07, 0xe0, 0x06, 0xe9, 0x02, 0x4b, 0xaf, + 0x0d, 0x0f, 0x80, 0x06, 0x47, 0x06, 0xe5, 0x00, + 0x00, 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x08, + 0x20, 0x06, 0x05, 0x46, 0x67, 0x00, 0x46, 0x00, + 0x66, 0xc0, 0x26, 0x00, 0x45, 0x20, 0x05, 0x20, + 0x25, 0x26, 0x20, 0xe9, 0x02, 0xc0, 0x16, 0xcb, + 0x0f, 0x05, 0x06, 0x27, 0x16, 0xe5, 0x00, 0x00, + 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x02, 0x00, + 0x85, 0x20, 0x06, 0x05, 0x07, 0x06, 0x87, 0x00, + 0x06, 0x27, 0x00, 0x27, 0x26, 0xc0, 0x27, 0xa0, + 0x25, 0x00, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x00, + 0x25, 0x07, 0xe0, 0x04, 0x26, 0x27, 0xe5, 0x01, + 0x00, 0x45, 0x00, 0xe5, 0x21, 0x26, 0x05, 0x47, + 0x66, 0x00, 0x47, 0x00, 0x47, 0x06, 0x05, 0x0f, + 0x60, 0x45, 0x07, 0xcb, 0x45, 0x26, 0x20, 0xe9, + 0x02, 0xeb, 0x01, 0x0f, 0xa5, 0x00, 0x06, 0x27, + 0x00, 0xe5, 0x0a, 0x40, 0xe5, 0x10, 0x00, 0xe5, + 0x01, 0x00, 0x05, 0x20, 0xc5, 0x40, 0x06, 0x60, + 0x47, 0x46, 0x00, 0x06, 0x00, 0xe7, 0x00, 0xa0, + 0xe9, 0x02, 0x20, 0x27, 0x16, 0xe0, 0x04, 0xe5, + 0x28, 0x06, 0x25, 0xc6, 0x60, 0x0d, 0xa5, 0x04, + 0xe6, 0x00, 0x16, 0xe9, 0x02, 0x36, 0xe0, 0x1d, + 0x25, 0x00, 0x05, 0x00, 0x85, 0x00, 0xe5, 0x10, + 0x00, 0x05, 0x00, 0xe5, 0x02, 0x06, 0x25, 0xe6, + 0x01, 0x05, 0x20, 0x85, 0x00, 0x04, 0x00, 0xc6, + 0x00, 0xe9, 0x02, 0x20, 0x65, 0xe0, 0x18, 0x05, + 0x4f, 0xf6, 0x07, 0x0f, 0x16, 0x4f, 0x26, 0xaf, + 0xe9, 0x02, 0xeb, 0x02, 0x0f, 0x06, 0x0f, 0x06, + 0x0f, 0x06, 0x12, 0x13, 0x12, 0x13, 0x27, 0xe5, + 0x00, 0x00, 0xe5, 0x1c, 0x60, 0xe6, 0x06, 0x07, + 0x86, 0x16, 0x26, 0x85, 0xe6, 0x03, 0x00, 0xe6, + 0x1c, 0x00, 0xef, 0x00, 0x06, 0xaf, 0x00, 0x2f, + 0x96, 0x6f, 0x36, 0xe0, 0x1d, 0xe5, 0x23, 0x27, + 0x66, 0x07, 0xa6, 0x07, 0x26, 0x27, 0x26, 0x05, + 0xe9, 0x02, 0xb6, 0xa5, 0x27, 0x26, 0x65, 0x46, + 0x05, 0x47, 0x25, 0xc7, 0x45, 0x66, 0xe5, 0x05, + 0x06, 0x27, 0x26, 0xa7, 0x06, 0x05, 0x07, 0xe9, + 0x02, 0x47, 0x06, 0x2f, 0xe1, 0x1e, 0x00, 0x01, + 0x80, 0x01, 0x20, 0xe2, 0x23, 0x16, 0x04, 0x42, + 0xe5, 0x80, 0xc1, 0x00, 0x65, 0x20, 0xc5, 0x00, + 0x05, 0x00, 0x65, 0x20, 0xe5, 0x21, 0x00, 0x65, + 0x20, 0xe5, 0x19, 0x00, 0x65, 0x20, 0xc5, 0x00, + 0x05, 0x00, 0x65, 0x20, 0xe5, 0x07, 0x00, 0xe5, + 0x31, 0x00, 0x65, 0x20, 0xe5, 0x3b, 0x20, 0x46, + 0xf6, 0x01, 0xeb, 0x0c, 0x40, 0xe5, 0x08, 0xef, + 0x02, 0xa0, 0xe1, 0x4e, 0x20, 0xa2, 0x20, 0x11, + 0xe5, 0x81, 0xe4, 0x0f, 0x16, 0xe5, 0x09, 0x17, + 0xe5, 0x12, 0x12, 0x13, 0x40, 0xe5, 0x43, 0x56, + 0x4a, 0xe5, 0x00, 0xc0, 0xe5, 0x0a, 0x46, 0x07, + 0xe0, 0x01, 0xe5, 0x0b, 0x26, 0x07, 0x36, 0xe0, + 0x01, 0xe5, 0x0a, 0x26, 0xe0, 0x04, 0xe5, 0x05, + 0x00, 0x45, 0x00, 0x26, 0xe0, 0x04, 0xe5, 0x2c, + 0x26, 0x07, 0xc6, 0xe7, 0x00, 0x06, 0x27, 0xe6, + 0x03, 0x56, 0x04, 0x56, 0x0d, 0x05, 0x06, 0x20, + 0xe9, 0x02, 0xa0, 0xeb, 0x02, 0xa0, 0xb6, 0x11, + 0x76, 0x46, 0x1b, 0x06, 0xe9, 0x02, 0xa0, 0xe5, + 0x1b, 0x04, 0xe5, 0x2d, 0xc0, 0x85, 0x26, 0xe5, + 0x1a, 0x06, 0x05, 0x80, 0xe5, 0x3e, 0xe0, 0x02, + 0xe5, 0x17, 0x00, 0x46, 0x67, 0x26, 0x47, 0x60, + 0x27, 0x06, 0xa7, 0x46, 0x60, 0x0f, 0x40, 0x36, + 0xe9, 0x02, 0xe5, 0x16, 0x20, 0x85, 0xe0, 0x03, + 0xe5, 0x24, 0x60, 0xe5, 0x12, 0xa0, 0xe9, 0x02, + 0x0b, 0x40, 0xef, 0x1a, 0xe5, 0x0f, 0x26, 0x27, + 0x06, 0x20, 0x36, 0xe5, 0x2d, 0x07, 0x06, 0x07, + 0xc6, 0x00, 0x06, 0x07, 0x06, 0x27, 0xe6, 0x00, + 0xa7, 0xe6, 0x02, 0x20, 0x06, 0xe9, 0x02, 0xa0, + 0xe9, 0x02, 0xa0, 0xd6, 0x04, 0xb6, 0x20, 0xe6, + 0x06, 0x08, 0xe6, 0x08, 0xe0, 0x29, 0x66, 0x07, + 0xe5, 0x27, 0x06, 0x07, 0x86, 0x07, 0x06, 0x87, + 0x06, 0x27, 0xe5, 0x00, 0x40, 0xe9, 0x02, 0xd6, + 0xef, 0x02, 0xe6, 0x01, 0xef, 0x01, 0x36, 0x00, + 0x26, 0x07, 0xe5, 0x16, 0x07, 0x66, 0x27, 0x26, + 0x07, 0x46, 0x25, 0xe9, 0x02, 0xe5, 0x24, 0x06, + 0x07, 0x26, 0x47, 0x06, 0x07, 0x46, 0x27, 0xe0, + 0x00, 0x76, 0xe5, 0x1c, 0xe7, 0x00, 0xe6, 0x00, + 0x27, 0x26, 0x40, 0x96, 0xe9, 0x02, 0x40, 0x45, + 0xe9, 0x02, 0xe5, 0x16, 0xa4, 0x36, 0xe2, 0x01, + 0xc0, 0xe1, 0x23, 0x20, 0x41, 0xf6, 0x00, 0xe0, + 0x00, 0x46, 0x16, 0xe6, 0x05, 0x07, 0xc6, 0x65, + 0x06, 0xa5, 0x06, 0x25, 0x07, 0x26, 0x05, 0x80, + 0xe2, 0x24, 0xe4, 0x37, 0xe2, 0x05, 0x04, 0xe2, + 0x1a, 0xe4, 0x1d, 0xe6, 0x38, 0xff, 0x80, 0x0e, + 0xe2, 0x00, 0xff, 0x5a, 0xe2, 0x00, 0xe1, 0x00, + 0xa2, 0x20, 0xa1, 0x20, 0xe2, 0x00, 0xe1, 0x00, + 0xe2, 0x00, 0xe1, 0x00, 0xa2, 0x20, 0xa1, 0x20, + 0xe2, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x3f, 0xc2, 0xe1, 0x00, 0xe2, 0x06, 0x20, + 0xe2, 0x00, 0xe3, 0x00, 0xe2, 0x00, 0xe3, 0x00, + 0xe2, 0x00, 0xe3, 0x00, 0x82, 0x00, 0x22, 0x61, + 0x03, 0x0e, 0x02, 0x4e, 0x42, 0x00, 0x22, 0x61, + 0x03, 0x4e, 0x62, 0x20, 0x22, 0x61, 0x00, 0x4e, + 0xe2, 0x00, 0x81, 0x4e, 0x20, 0x42, 0x00, 0x22, + 0x61, 0x03, 0x2e, 0x00, 0xf7, 0x03, 0x9b, 0xb1, + 0x36, 0x14, 0x15, 0x12, 0x34, 0x15, 0x12, 0x14, + 0xf6, 0x00, 0x18, 0x19, 0x9b, 0x17, 0xf6, 0x01, + 0x14, 0x15, 0x76, 0x30, 0x56, 0x0c, 0x12, 0x13, + 0xf6, 0x03, 0x0c, 0x16, 0x10, 0xf6, 0x02, 0x17, + 0x9b, 0x00, 0xfb, 0x02, 0x0b, 0x04, 0x20, 0xab, + 0x4c, 0x12, 0x13, 0x04, 0xeb, 0x02, 0x4c, 0x12, + 0x13, 0x00, 0xe4, 0x05, 0x40, 0xed, 0x19, 0xe0, + 0x07, 0xe6, 0x05, 0x68, 0x06, 0x48, 0xe6, 0x04, + 0xe0, 0x07, 0x2f, 0x01, 0x6f, 0x01, 0x2f, 0x02, + 0x41, 0x22, 0x41, 0x02, 0x0f, 0x01, 0x2f, 0x0c, + 0x81, 0xaf, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, + 0x61, 0x0f, 0x02, 0x61, 0x02, 0x65, 0x02, 0x2f, + 0x22, 0x21, 0x8c, 0x3f, 0x42, 0x0f, 0x0c, 0x2f, + 0x02, 0x0f, 0xeb, 0x08, 0xea, 0x1b, 0x3f, 0x6a, + 0x0b, 0x2f, 0x60, 0x8c, 0x8f, 0x2c, 0x6f, 0x0c, + 0x2f, 0x0c, 0x2f, 0x0c, 0xcf, 0x0c, 0xef, 0x17, + 0x2c, 0x2f, 0x0c, 0x0f, 0x0c, 0xef, 0x17, 0xec, + 0x80, 0x84, 0xef, 0x00, 0x12, 0x13, 0x12, 0x13, + 0xef, 0x0c, 0x2c, 0xcf, 0x12, 0x13, 0xef, 0x49, + 0x0c, 0xef, 0x16, 0xec, 0x11, 0xef, 0x20, 0xac, + 0xef, 0x3d, 0xe0, 0x11, 0xef, 0x03, 0xe0, 0x0d, + 0xeb, 0x34, 0xef, 0x46, 0xeb, 0x0e, 0xef, 0x80, + 0x2f, 0x0c, 0xef, 0x01, 0x0c, 0xef, 0x2e, 0xec, + 0x00, 0xef, 0x67, 0x0c, 0xef, 0x80, 0x70, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0xeb, 0x16, 0xef, + 0x24, 0x8c, 0x12, 0x13, 0xec, 0x17, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0xec, 0x08, 0xef, 0x80, 0x78, 0xec, 0x7b, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0xec, 0x37, 0x12, + 0x13, 0x12, 0x13, 0xec, 0x18, 0x12, 0x13, 0xec, + 0x80, 0x7a, 0xef, 0x28, 0xec, 0x0d, 0x2f, 0xac, + 0xef, 0x1f, 0x20, 0xef, 0x18, 0x00, 0xef, 0x61, + 0xe1, 0x28, 0xe2, 0x28, 0x5f, 0x21, 0x22, 0xdf, + 0x41, 0x02, 0x3f, 0x02, 0x3f, 0x82, 0x24, 0x41, + 0x02, 0xff, 0x5a, 0x02, 0xaf, 0x7f, 0x46, 0x3f, + 0x80, 0x76, 0x0b, 0x36, 0xe2, 0x1e, 0x00, 0x02, + 0x80, 0x02, 0x20, 0xe5, 0x30, 0xc0, 0x04, 0x16, + 0xe0, 0x06, 0x06, 0xe5, 0x0f, 0xe0, 0x01, 0xc5, + 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, + 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xe6, + 0x18, 0x36, 0x14, 0x15, 0x14, 0x15, 0x56, 0x14, + 0x15, 0x16, 0x14, 0x15, 0xf6, 0x01, 0x11, 0x36, + 0x11, 0x16, 0x14, 0x15, 0x36, 0x14, 0x15, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x96, + 0x04, 0xf6, 0x02, 0x31, 0x76, 0x11, 0x16, 0x12, + 0xf6, 0x05, 0x2f, 0x56, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x11, 0xe0, 0x1a, 0xef, + 0x12, 0x00, 0xef, 0x51, 0xe0, 0x04, 0xef, 0x80, + 0x4e, 0xe0, 0x12, 0xef, 0x04, 0x60, 0x17, 0x56, + 0x0f, 0x04, 0x05, 0x0a, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x2f, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, + 0x12, 0x33, 0x0f, 0xea, 0x01, 0x66, 0x27, 0x11, + 0x84, 0x2f, 0x4a, 0x04, 0x05, 0x16, 0x2f, 0x00, + 0xe5, 0x4e, 0x20, 0x26, 0x2e, 0x24, 0x05, 0x11, + 0xe5, 0x52, 0x16, 0x44, 0x05, 0x80, 0xe5, 0x23, + 0x00, 0xe5, 0x56, 0x00, 0x2f, 0x6b, 0xef, 0x02, + 0xe5, 0x18, 0xef, 0x1c, 0xe0, 0x04, 0xe5, 0x08, + 0xef, 0x17, 0x00, 0xeb, 0x02, 0xef, 0x16, 0xeb, + 0x00, 0x0f, 0xeb, 0x07, 0xef, 0x18, 0xeb, 0x02, + 0xef, 0x1f, 0xeb, 0x07, 0xef, 0x80, 0xb8, 0xe5, + 0x99, 0x38, 0xef, 0x38, 0xe5, 0xc0, 0x11, 0x8d, + 0x04, 0xe5, 0x83, 0xef, 0x40, 0xef, 0x2f, 0xe0, + 0x01, 0xe5, 0x20, 0xa4, 0x36, 0xe5, 0x80, 0x84, + 0x04, 0x56, 0xe5, 0x08, 0xe9, 0x02, 0x25, 0xe0, + 0x0c, 0xff, 0x26, 0x05, 0x06, 0x48, 0x16, 0xe6, + 0x02, 0x16, 0x04, 0xff, 0x14, 0x24, 0x26, 0xe5, + 0x3e, 0xea, 0x02, 0x26, 0xb6, 0xe0, 0x00, 0xee, + 0x0f, 0xe4, 0x01, 0x2e, 0xff, 0x06, 0x22, 0xff, + 0x36, 0x04, 0xe2, 0x00, 0x9f, 0xff, 0x02, 0x04, + 0x2e, 0x7f, 0x05, 0x7f, 0x22, 0xff, 0x0d, 0x61, + 0x02, 0x81, 0x02, 0xff, 0x07, 0x41, 0x02, 0x3f, + 0x80, 0x3f, 0x00, 0x02, 0x00, 0x02, 0x7f, 0xe0, + 0x10, 0x44, 0x3f, 0x05, 0x24, 0x02, 0xc5, 0x06, + 0x45, 0x06, 0x65, 0x06, 0xe5, 0x0f, 0x27, 0x26, + 0x07, 0x6f, 0x06, 0x40, 0xab, 0x2f, 0x0d, 0x0f, + 0xa0, 0xe5, 0x2c, 0x76, 0xe0, 0x00, 0x27, 0xe5, + 0x2a, 0xe7, 0x08, 0x26, 0xe0, 0x00, 0x36, 0xe9, + 0x02, 0xa0, 0xe6, 0x0a, 0xa5, 0x56, 0x05, 0x16, + 0x25, 0x06, 0xe9, 0x02, 0xe5, 0x14, 0xe6, 0x00, + 0x36, 0xe5, 0x0f, 0xe6, 0x03, 0x27, 0xe0, 0x03, + 0x16, 0xe5, 0x15, 0x40, 0x46, 0x07, 0xe5, 0x27, + 0x06, 0x27, 0x66, 0x27, 0x26, 0x47, 0xf6, 0x05, + 0x00, 0x04, 0xe9, 0x02, 0x60, 0x36, 0x85, 0x06, + 0x04, 0xe5, 0x01, 0xe9, 0x02, 0x85, 0x00, 0xe5, + 0x21, 0xa6, 0x27, 0x26, 0x27, 0x26, 0xe0, 0x01, + 0x45, 0x06, 0xe5, 0x00, 0x06, 0x07, 0x20, 0xe9, + 0x02, 0x20, 0x76, 0xe5, 0x08, 0x04, 0xa5, 0x4f, + 0x05, 0x07, 0x06, 0x07, 0xe5, 0x2a, 0x06, 0x05, + 0x46, 0x25, 0x26, 0x85, 0x26, 0x05, 0x06, 0x05, + 0xe0, 0x10, 0x25, 0x04, 0x36, 0xe5, 0x03, 0x07, + 0x26, 0x27, 0x36, 0x05, 0x24, 0x07, 0x06, 0xe0, + 0x02, 0xa5, 0x20, 0xa5, 0x20, 0xa5, 0xe0, 0x01, + 0xc5, 0x00, 0xc5, 0x00, 0xe2, 0x23, 0x0e, 0x64, + 0xe2, 0x01, 0x04, 0x2e, 0x60, 0xe2, 0x48, 0xe5, + 0x1b, 0x27, 0x06, 0x27, 0x06, 0x27, 0x16, 0x07, + 0x06, 0x20, 0xe9, 0x02, 0xa0, 0xe5, 0xab, 0x1c, + 0xe0, 0x04, 0xe5, 0x0f, 0x60, 0xe5, 0x29, 0x60, + 0xfc, 0x87, 0x78, 0xfd, 0x98, 0x78, 0xe5, 0x80, + 0xe6, 0x20, 0xe5, 0x62, 0xe0, 0x1e, 0xc2, 0xe0, + 0x04, 0x82, 0x80, 0x05, 0x06, 0xe5, 0x02, 0x0c, + 0xe5, 0x05, 0x00, 0x85, 0x00, 0x05, 0x00, 0x25, + 0x00, 0x25, 0x00, 0xe5, 0x64, 0xee, 0x09, 0xe0, + 0x08, 0xe5, 0x80, 0xe3, 0x13, 0x12, 0xef, 0x08, + 0xe5, 0x38, 0x20, 0xe5, 0x2e, 0xc0, 0x0f, 0xe0, + 0x18, 0xe5, 0x04, 0x0d, 0x4f, 0xe6, 0x08, 0xd6, + 0x12, 0x13, 0x16, 0xa0, 0xe6, 0x08, 0x16, 0x31, + 0x30, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x36, 0x12, 0x13, 0x76, 0x50, 0x56, 0x00, + 0x76, 0x11, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x56, 0x0c, 0x11, 0x4c, 0x00, 0x16, 0x0d, 0x36, + 0x60, 0x85, 0x00, 0xe5, 0x7f, 0x20, 0x1b, 0x00, + 0x56, 0x0d, 0x56, 0x12, 0x13, 0x16, 0x0c, 0x16, + 0x11, 0x36, 0xe9, 0x02, 0x36, 0x4c, 0x36, 0xe1, + 0x12, 0x12, 0x16, 0x13, 0x0e, 0x10, 0x0e, 0xe2, + 0x12, 0x12, 0x0c, 0x13, 0x0c, 0x12, 0x13, 0x16, + 0x12, 0x13, 0x36, 0xe5, 0x02, 0x04, 0xe5, 0x25, + 0x24, 0xe5, 0x17, 0x40, 0xa5, 0x20, 0xa5, 0x20, + 0xa5, 0x20, 0x45, 0x40, 0x2d, 0x0c, 0x0e, 0x0f, + 0x2d, 0x00, 0x0f, 0x6c, 0x2f, 0xe0, 0x02, 0x5b, + 0x2f, 0x20, 0xe5, 0x04, 0x00, 0xe5, 0x12, 0x00, + 0xe5, 0x0b, 0x00, 0x25, 0x00, 0xe5, 0x07, 0x20, + 0xe5, 0x06, 0xe0, 0x1a, 0xe5, 0x73, 0x80, 0x56, + 0x60, 0xeb, 0x25, 0x40, 0xef, 0x01, 0xea, 0x2d, + 0x6b, 0xef, 0x09, 0x2b, 0x4f, 0x00, 0xef, 0x05, + 0x40, 0x0f, 0xe0, 0x27, 0xef, 0x25, 0x06, 0xe0, + 0x7a, 0xe5, 0x15, 0x40, 0xe5, 0x29, 0xe0, 0x07, + 0x06, 0xeb, 0x13, 0x60, 0xe5, 0x18, 0x6b, 0xe0, + 0x01, 0xe5, 0x0c, 0x0a, 0xe5, 0x00, 0x0a, 0x80, + 0xe5, 0x1e, 0x86, 0x80, 0xe5, 0x16, 0x00, 0x16, + 0xe5, 0x1c, 0x60, 0xe5, 0x00, 0x16, 0x8a, 0xe0, + 0x22, 0xe1, 0x20, 0xe2, 0x20, 0xe5, 0x46, 0x20, + 0xe9, 0x02, 0xa0, 0xe1, 0x1c, 0x60, 0xe2, 0x1c, + 0x60, 0xe5, 0x20, 0xe0, 0x00, 0xe5, 0x2c, 0xe0, + 0x03, 0x16, 0xe1, 0x03, 0x00, 0xe1, 0x07, 0x00, + 0xc1, 0x00, 0x21, 0x00, 0xe2, 0x03, 0x00, 0xe2, + 0x07, 0x00, 0xc2, 0x00, 0x22, 0xe0, 0x3b, 0xe5, + 0x80, 0xaf, 0xe0, 0x01, 0xe5, 0x0e, 0xe0, 0x02, + 0xe5, 0x00, 0xe0, 0x10, 0xa4, 0x00, 0xe4, 0x22, + 0x00, 0xe4, 0x01, 0xe0, 0x3d, 0xa5, 0x20, 0x05, + 0x00, 0xe5, 0x24, 0x00, 0x25, 0x40, 0x05, 0x20, + 0xe5, 0x0f, 0x00, 0x16, 0xeb, 0x00, 0xe5, 0x0f, + 0x2f, 0xcb, 0xe5, 0x17, 0xe0, 0x00, 0xeb, 0x01, + 0xe0, 0x28, 0xe5, 0x0b, 0x00, 0x25, 0x80, 0x8b, + 0xe5, 0x0e, 0xab, 0x40, 0x16, 0xe5, 0x12, 0x80, + 0x16, 0xe0, 0x38, 0xe5, 0x30, 0x60, 0x2b, 0x25, + 0xeb, 0x08, 0x20, 0xeb, 0x26, 0x05, 0x46, 0x00, + 0x26, 0x80, 0x66, 0x65, 0x00, 0x45, 0x00, 0xe5, + 0x15, 0x20, 0x46, 0x60, 0x06, 0xeb, 0x01, 0xc0, + 0xf6, 0x01, 0xc0, 0xe5, 0x15, 0x2b, 0x16, 0xe5, + 0x15, 0x4b, 0xe0, 0x18, 0xe5, 0x00, 0x0f, 0xe5, + 0x14, 0x26, 0x60, 0x8b, 0xd6, 0xe0, 0x01, 0xe5, + 0x2e, 0x40, 0xd6, 0xe5, 0x0e, 0x20, 0xeb, 0x00, + 0xe5, 0x0b, 0x80, 0xeb, 0x00, 0xe5, 0x0a, 0xc0, + 0x76, 0xe0, 0x04, 0xcb, 0xe0, 0x48, 0xe5, 0x41, + 0xe0, 0x2f, 0xe1, 0x2b, 0xe0, 0x05, 0xe2, 0x2b, + 0xc0, 0xab, 0xe5, 0x1c, 0x66, 0xe0, 0x00, 0xe9, + 0x02, 0xe0, 0x80, 0x9e, 0xeb, 0x17, 0x00, 0xe5, + 0x22, 0x00, 0x26, 0x11, 0x20, 0x25, 0xe0, 0x43, + 0x46, 0xe5, 0x15, 0xeb, 0x02, 0x05, 0xe0, 0x00, + 0xe5, 0x0e, 0xe6, 0x03, 0x6b, 0x96, 0xe0, 0x0e, + 0xe5, 0x0a, 0x66, 0x76, 0xe0, 0x1e, 0xe5, 0x0d, + 0xcb, 0xe0, 0x0c, 0xe5, 0x0f, 0xe0, 0x01, 0x07, + 0x06, 0x07, 0xe5, 0x2d, 0xe6, 0x07, 0xd6, 0x60, + 0xeb, 0x0c, 0xe9, 0x02, 0x06, 0x25, 0x26, 0x05, + 0xe0, 0x01, 0x46, 0x07, 0xe5, 0x25, 0x47, 0x66, + 0x27, 0x26, 0x36, 0x1b, 0x76, 0x06, 0xe0, 0x02, + 0x1b, 0x20, 0xe5, 0x11, 0xc0, 0xe9, 0x02, 0xa0, + 0x46, 0xe5, 0x1c, 0x86, 0x07, 0xe6, 0x00, 0x00, + 0xe9, 0x02, 0x76, 0x05, 0x27, 0x05, 0xe0, 0x00, + 0xe5, 0x1b, 0x06, 0x36, 0x05, 0xe0, 0x01, 0x26, + 0x07, 0xe5, 0x28, 0x47, 0xe6, 0x01, 0x27, 0x65, + 0x76, 0x66, 0x16, 0x07, 0x06, 0xe9, 0x02, 0x05, + 0x16, 0x05, 0x56, 0x00, 0xeb, 0x0c, 0xe0, 0x03, + 0xe5, 0x0a, 0x00, 0xe5, 0x11, 0x47, 0x46, 0x27, + 0x06, 0x07, 0x26, 0xb6, 0x06, 0x25, 0x06, 0xe0, + 0x36, 0xc5, 0x00, 0x05, 0x00, 0x65, 0x00, 0xe5, + 0x07, 0x00, 0xe5, 0x02, 0x16, 0xa0, 0xe5, 0x27, + 0x06, 0x47, 0xe6, 0x00, 0x80, 0xe9, 0x02, 0xa0, + 0x26, 0x27, 0x00, 0xe5, 0x00, 0x20, 0x25, 0x20, + 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, 0x85, + 0x00, 0x26, 0x05, 0x27, 0x06, 0x67, 0x20, 0x27, + 0x20, 0x47, 0x20, 0x05, 0xa0, 0x07, 0x80, 0x85, + 0x27, 0x20, 0xc6, 0x40, 0x86, 0xe0, 0x80, 0x03, + 0xe5, 0x2d, 0x47, 0xe6, 0x00, 0x27, 0x46, 0x07, + 0x06, 0x65, 0x96, 0xe9, 0x02, 0x36, 0x00, 0x16, + 0x06, 0x45, 0xe0, 0x16, 0xe5, 0x28, 0x47, 0xa6, + 0x07, 0x06, 0x67, 0x26, 0x07, 0x26, 0x25, 0x16, + 0x05, 0xe0, 0x00, 0xe9, 0x02, 0xe0, 0x80, 0x1e, + 0xe5, 0x27, 0x47, 0x66, 0x20, 0x67, 0x26, 0x07, + 0x26, 0xf6, 0x0f, 0x65, 0x26, 0xe0, 0x1a, 0xe5, + 0x28, 0x47, 0xe6, 0x00, 0x27, 0x06, 0x07, 0x26, + 0x56, 0x05, 0xe0, 0x03, 0xe9, 0x02, 0xa0, 0xf6, + 0x05, 0xe0, 0x0b, 0xe5, 0x23, 0x06, 0x07, 0x06, + 0x27, 0xa6, 0x07, 0x06, 0x05, 0x16, 0xa0, 0xe9, + 0x02, 0xe0, 0x2e, 0xe5, 0x13, 0x20, 0x46, 0x27, + 0x66, 0x07, 0x86, 0x60, 0xe9, 0x02, 0x2b, 0x56, + 0x0f, 0xc5, 0xe0, 0x80, 0x31, 0xe5, 0x24, 0x47, + 0xe6, 0x01, 0x07, 0x26, 0x16, 0xe0, 0x5c, 0xe1, + 0x18, 0xe2, 0x18, 0xe9, 0x02, 0xeb, 0x01, 0xe0, + 0x04, 0xe5, 0x00, 0x20, 0x05, 0x20, 0xe5, 0x00, + 0x00, 0x25, 0x00, 0xe5, 0x10, 0xa7, 0x00, 0x27, + 0x20, 0x26, 0x07, 0x06, 0x05, 0x07, 0x05, 0x07, + 0x06, 0x56, 0xe0, 0x01, 0xe9, 0x02, 0xe0, 0x3e, + 0xe5, 0x00, 0x20, 0xe5, 0x1f, 0x47, 0x66, 0x20, + 0x26, 0x67, 0x06, 0x05, 0x16, 0x05, 0x07, 0xe0, + 0x13, 0x05, 0xe6, 0x02, 0xe5, 0x20, 0xa6, 0x07, + 0x05, 0x66, 0xf6, 0x00, 0x06, 0xe0, 0x00, 0x05, + 0xa6, 0x27, 0x46, 0xe5, 0x26, 0xe6, 0x05, 0x07, + 0x26, 0x56, 0x05, 0x96, 0xe0, 0x05, 0xe5, 0x41, + 0xc0, 0xf6, 0x02, 0xe0, 0x80, 0x6e, 0xe5, 0x01, + 0x00, 0xe5, 0x1d, 0x07, 0xc6, 0x00, 0xa6, 0x07, + 0x06, 0x05, 0x96, 0xe0, 0x02, 0xe9, 0x02, 0xeb, + 0x0b, 0x40, 0x36, 0xe5, 0x16, 0x20, 0xe6, 0x0e, + 0x00, 0x07, 0xc6, 0x07, 0x26, 0x07, 0x26, 0xe0, + 0x41, 0xc5, 0x00, 0x25, 0x00, 0xe5, 0x1e, 0xa6, + 0x40, 0x06, 0x00, 0x26, 0x00, 0xc6, 0x05, 0x06, + 0xe0, 0x00, 0xe9, 0x02, 0xa0, 0xa5, 0x00, 0x25, + 0x00, 0xe5, 0x18, 0x87, 0x00, 0x26, 0x00, 0x27, + 0x06, 0x07, 0x06, 0x05, 0xc0, 0xe9, 0x02, 0xe0, + 0x80, 0xae, 0xe5, 0x0b, 0x26, 0x27, 0x36, 0xc0, + 0x26, 0x05, 0x07, 0xe5, 0x05, 0x00, 0xe5, 0x1a, + 0x27, 0x86, 0x40, 0x27, 0x06, 0x07, 0x06, 0xf6, + 0x05, 0xe9, 0x02, 0xe0, 0x4e, 0x05, 0xe0, 0x07, + 0xeb, 0x0d, 0xef, 0x00, 0x6d, 0xef, 0x09, 0xe0, + 0x05, 0x16, 0xe5, 0x83, 0x12, 0xe0, 0x5e, 0xea, + 0x67, 0x00, 0x96, 0xe0, 0x03, 0xe5, 0x80, 0x3c, + 0xe0, 0x89, 0xc4, 0xe5, 0x59, 0x36, 0xe0, 0x05, + 0xe5, 0x83, 0xa8, 0xfb, 0x08, 0x06, 0xa5, 0xe6, + 0x07, 0xe0, 0x8f, 0x22, 0xe5, 0x81, 0xbf, 0xe0, + 0xa1, 0x31, 0xe5, 0x81, 0xb1, 0xc0, 0xe5, 0x17, + 0x00, 0xe9, 0x02, 0x60, 0x36, 0xe5, 0x47, 0x00, + 0xe9, 0x02, 0xa0, 0xe5, 0x16, 0x20, 0x86, 0x16, + 0xe0, 0x02, 0xe5, 0x28, 0xc6, 0x96, 0x6f, 0x64, + 0x16, 0x0f, 0xe0, 0x02, 0xe9, 0x02, 0x00, 0xcb, + 0x00, 0xe5, 0x0d, 0x80, 0xe5, 0x0b, 0xe0, 0x82, + 0x28, 0xe1, 0x18, 0xe2, 0x18, 0xeb, 0x0f, 0x76, + 0xe0, 0x5d, 0xe5, 0x43, 0x60, 0x06, 0x05, 0xe7, + 0x2f, 0xc0, 0x66, 0xe4, 0x05, 0xe0, 0x38, 0x24, + 0x16, 0x04, 0x06, 0xe0, 0x03, 0x27, 0xe0, 0x06, + 0xe5, 0x97, 0x70, 0xe0, 0x00, 0xe5, 0x84, 0x4e, + 0xe0, 0x22, 0xe5, 0x01, 0xe0, 0xa2, 0x5f, 0x64, + 0x00, 0xc4, 0x00, 0x24, 0x00, 0xe5, 0x80, 0x9b, + 0xe0, 0x07, 0x05, 0xe0, 0x15, 0x45, 0x20, 0x05, + 0xe0, 0x06, 0x65, 0xe0, 0x00, 0xe5, 0x81, 0x04, + 0xe0, 0x88, 0x7c, 0xe5, 0x63, 0x80, 0xe5, 0x05, + 0x40, 0xe5, 0x01, 0xc0, 0xe5, 0x02, 0x20, 0x0f, + 0x26, 0x16, 0x7b, 0xe0, 0x91, 0xd4, 0xe6, 0x26, + 0x20, 0xe6, 0x0f, 0xe0, 0x01, 0xef, 0x6c, 0xe0, + 0x34, 0xef, 0x80, 0x6e, 0xe0, 0x02, 0xef, 0x1f, + 0x20, 0xef, 0x34, 0x27, 0x46, 0x4f, 0xa7, 0xfb, + 0x00, 0xe6, 0x00, 0x2f, 0xc6, 0xef, 0x16, 0x66, + 0xef, 0x35, 0xe0, 0x0d, 0xef, 0x3a, 0x46, 0x0f, + 0xe0, 0x72, 0xeb, 0x0c, 0xe0, 0x04, 0xeb, 0x0c, + 0xe0, 0x04, 0xef, 0x4f, 0xe0, 0x01, 0xeb, 0x11, + 0xe0, 0x7f, 0xe1, 0x12, 0xe2, 0x12, 0xe1, 0x12, + 0xc2, 0x00, 0xe2, 0x0a, 0xe1, 0x12, 0xe2, 0x12, + 0x01, 0x00, 0x21, 0x20, 0x01, 0x20, 0x21, 0x20, + 0x61, 0x00, 0xe1, 0x00, 0x62, 0x00, 0x02, 0x00, + 0xc2, 0x00, 0xe2, 0x03, 0xe1, 0x12, 0xe2, 0x12, + 0x21, 0x00, 0x61, 0x20, 0xe1, 0x00, 0x00, 0xc1, + 0x00, 0xe2, 0x12, 0x21, 0x00, 0x61, 0x00, 0x81, + 0x00, 0x01, 0x40, 0xc1, 0x00, 0xe2, 0x12, 0xe1, + 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, + 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, + 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x14, 0x20, + 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0xe1, + 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0xe1, 0x11, + 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, + 0xe2, 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, + 0x11, 0x0c, 0xa2, 0x3f, 0x20, 0xe9, 0x2a, 0xef, + 0x81, 0x78, 0xe6, 0x2f, 0x6f, 0xe6, 0x2a, 0xef, + 0x00, 0x06, 0xef, 0x06, 0x06, 0x2f, 0x96, 0xe0, + 0x07, 0x86, 0x00, 0xe6, 0x07, 0xe0, 0x83, 0xc8, + 0xe2, 0x02, 0x05, 0xe2, 0x0c, 0xa0, 0xa2, 0xe0, + 0x80, 0x4d, 0xc6, 0x00, 0xe6, 0x09, 0x20, 0xc6, + 0x00, 0x26, 0x00, 0x86, 0x80, 0xe4, 0x36, 0xe0, + 0x19, 0x06, 0xe0, 0x68, 0xe5, 0x25, 0x40, 0xc6, + 0xc4, 0x20, 0xe9, 0x02, 0x60, 0x05, 0x0f, 0xe0, + 0x80, 0xb8, 0xe5, 0x16, 0x06, 0xe0, 0x09, 0xe5, + 0x24, 0x66, 0xe9, 0x02, 0x80, 0x0d, 0xe0, 0x81, + 0x48, 0xe5, 0x13, 0x04, 0x66, 0xe9, 0x02, 0xe0, + 0x82, 0x5e, 0xc5, 0x00, 0x65, 0x00, 0x25, 0x00, + 0xe5, 0x07, 0x00, 0xe5, 0x80, 0x3d, 0x20, 0xeb, + 0x01, 0xc6, 0xe0, 0x21, 0xe1, 0x1a, 0xe2, 0x1a, + 0xc6, 0x04, 0x60, 0xe9, 0x02, 0x60, 0x36, 0xe0, + 0x82, 0x89, 0xeb, 0x33, 0x0f, 0x4b, 0x0d, 0x6b, + 0xe0, 0x44, 0xeb, 0x25, 0x0f, 0xeb, 0x07, 0xe0, + 0x80, 0x3a, 0x65, 0x00, 0xe5, 0x13, 0x00, 0x25, + 0x00, 0x05, 0x20, 0x05, 0x00, 0xe5, 0x02, 0x00, + 0x65, 0x00, 0x05, 0x00, 0x05, 0xa0, 0x05, 0x60, + 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x45, 0x00, + 0x25, 0x00, 0x05, 0x20, 0x05, 0x00, 0x05, 0x00, + 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x25, 0x00, + 0x05, 0x20, 0x65, 0x00, 0xc5, 0x00, 0x65, 0x00, + 0x65, 0x00, 0x05, 0x00, 0xe5, 0x02, 0x00, 0xe5, + 0x09, 0x80, 0x45, 0x00, 0x85, 0x00, 0xe5, 0x09, + 0xe0, 0x2c, 0x2c, 0xe0, 0x80, 0x86, 0xef, 0x24, + 0x60, 0xef, 0x5c, 0xe0, 0x04, 0xef, 0x07, 0x20, + 0xef, 0x07, 0x00, 0xef, 0x07, 0x00, 0xef, 0x1d, + 0xe0, 0x02, 0xeb, 0x05, 0xef, 0x80, 0x19, 0xe0, + 0x30, 0xef, 0x15, 0xe0, 0x05, 0xef, 0x24, 0x60, + 0xef, 0x01, 0xc0, 0x2f, 0xe0, 0x06, 0xaf, 0xe0, + 0x80, 0x12, 0xef, 0x80, 0x73, 0x8e, 0xef, 0x82, + 0x50, 0x60, 0xef, 0x09, 0x40, 0xef, 0x05, 0x40, + 0xef, 0x6f, 0x60, 0xef, 0x57, 0xa0, 0xef, 0x04, + 0x60, 0x0f, 0xe0, 0x07, 0xef, 0x04, 0x60, 0xef, + 0x30, 0xe0, 0x00, 0xef, 0x02, 0xa0, 0xef, 0x20, + 0xe0, 0x00, 0xef, 0x16, 0x20, 0x2f, 0xe0, 0x46, + 0xef, 0x80, 0xcc, 0xe0, 0x04, 0xef, 0x06, 0x20, + 0xef, 0x05, 0x40, 0xef, 0x01, 0xc0, 0xef, 0x26, + 0x00, 0xcf, 0xe0, 0x00, 0xef, 0x06, 0x60, 0xef, + 0x01, 0xc0, 0xef, 0x01, 0xc0, 0xef, 0x80, 0x0b, + 0x00, 0xef, 0x2f, 0xe0, 0x1d, 0xe9, 0x02, 0xe0, + 0x83, 0x7e, 0xe5, 0xc0, 0x66, 0x58, 0xe0, 0x18, + 0xe5, 0x8f, 0xb2, 0xa0, 0xe5, 0x80, 0x56, 0x20, + 0xe5, 0x95, 0xfa, 0xe0, 0x06, 0xe5, 0x9c, 0xa9, + 0xe0, 0x8b, 0x97, 0xe5, 0x81, 0x96, 0xe0, 0x85, + 0x5a, 0xe5, 0x92, 0xc3, 0x80, 0xe5, 0x8f, 0xd8, + 0xe0, 0xca, 0x9b, 0xc9, 0x1b, 0xe0, 0x16, 0xfb, + 0x58, 0xe0, 0x78, 0xe6, 0x80, 0x68, 0xe0, 0xc0, + 0xbd, 0x88, 0xfd, 0xc0, 0xbf, 0x76, 0x20, 0xfd, + 0xc0, 0xbf, 0x76, 0x20, +}; + +typedef enum { + UNICODE_SCRIPT_Unknown, + UNICODE_SCRIPT_Adlam, + UNICODE_SCRIPT_Ahom, + UNICODE_SCRIPT_Anatolian_Hieroglyphs, + UNICODE_SCRIPT_Arabic, + UNICODE_SCRIPT_Armenian, + UNICODE_SCRIPT_Avestan, + UNICODE_SCRIPT_Balinese, + UNICODE_SCRIPT_Bamum, + UNICODE_SCRIPT_Bassa_Vah, + UNICODE_SCRIPT_Batak, + UNICODE_SCRIPT_Bengali, + UNICODE_SCRIPT_Bhaiksuki, + UNICODE_SCRIPT_Bopomofo, + UNICODE_SCRIPT_Brahmi, + UNICODE_SCRIPT_Braille, + UNICODE_SCRIPT_Buginese, + UNICODE_SCRIPT_Buhid, + UNICODE_SCRIPT_Canadian_Aboriginal, + UNICODE_SCRIPT_Carian, + UNICODE_SCRIPT_Caucasian_Albanian, + UNICODE_SCRIPT_Chakma, + UNICODE_SCRIPT_Cham, + UNICODE_SCRIPT_Cherokee, + UNICODE_SCRIPT_Chorasmian, + UNICODE_SCRIPT_Common, + UNICODE_SCRIPT_Coptic, + UNICODE_SCRIPT_Cuneiform, + UNICODE_SCRIPT_Cypriot, + UNICODE_SCRIPT_Cyrillic, + UNICODE_SCRIPT_Cypro_Minoan, + UNICODE_SCRIPT_Deseret, + UNICODE_SCRIPT_Devanagari, + UNICODE_SCRIPT_Dives_Akuru, + UNICODE_SCRIPT_Dogra, + UNICODE_SCRIPT_Duployan, + UNICODE_SCRIPT_Egyptian_Hieroglyphs, + UNICODE_SCRIPT_Elbasan, + UNICODE_SCRIPT_Elymaic, + UNICODE_SCRIPT_Ethiopic, + UNICODE_SCRIPT_Georgian, + UNICODE_SCRIPT_Glagolitic, + UNICODE_SCRIPT_Gothic, + UNICODE_SCRIPT_Grantha, + UNICODE_SCRIPT_Greek, + UNICODE_SCRIPT_Gujarati, + UNICODE_SCRIPT_Gunjala_Gondi, + UNICODE_SCRIPT_Gurmukhi, + UNICODE_SCRIPT_Han, + UNICODE_SCRIPT_Hangul, + UNICODE_SCRIPT_Hanifi_Rohingya, + UNICODE_SCRIPT_Hanunoo, + UNICODE_SCRIPT_Hatran, + UNICODE_SCRIPT_Hebrew, + UNICODE_SCRIPT_Hiragana, + UNICODE_SCRIPT_Imperial_Aramaic, + UNICODE_SCRIPT_Inherited, + UNICODE_SCRIPT_Inscriptional_Pahlavi, + UNICODE_SCRIPT_Inscriptional_Parthian, + UNICODE_SCRIPT_Javanese, + UNICODE_SCRIPT_Kaithi, + UNICODE_SCRIPT_Kannada, + UNICODE_SCRIPT_Katakana, + UNICODE_SCRIPT_Kawi, + UNICODE_SCRIPT_Kayah_Li, + UNICODE_SCRIPT_Kharoshthi, + UNICODE_SCRIPT_Khmer, + UNICODE_SCRIPT_Khojki, + UNICODE_SCRIPT_Khitan_Small_Script, + UNICODE_SCRIPT_Khudawadi, + UNICODE_SCRIPT_Lao, + UNICODE_SCRIPT_Latin, + UNICODE_SCRIPT_Lepcha, + UNICODE_SCRIPT_Limbu, + UNICODE_SCRIPT_Linear_A, + UNICODE_SCRIPT_Linear_B, + UNICODE_SCRIPT_Lisu, + UNICODE_SCRIPT_Lycian, + UNICODE_SCRIPT_Lydian, + UNICODE_SCRIPT_Makasar, + UNICODE_SCRIPT_Mahajani, + UNICODE_SCRIPT_Malayalam, + UNICODE_SCRIPT_Mandaic, + UNICODE_SCRIPT_Manichaean, + UNICODE_SCRIPT_Marchen, + UNICODE_SCRIPT_Masaram_Gondi, + UNICODE_SCRIPT_Medefaidrin, + UNICODE_SCRIPT_Meetei_Mayek, + UNICODE_SCRIPT_Mende_Kikakui, + UNICODE_SCRIPT_Meroitic_Cursive, + UNICODE_SCRIPT_Meroitic_Hieroglyphs, + UNICODE_SCRIPT_Miao, + UNICODE_SCRIPT_Modi, + UNICODE_SCRIPT_Mongolian, + UNICODE_SCRIPT_Mro, + UNICODE_SCRIPT_Multani, + UNICODE_SCRIPT_Myanmar, + UNICODE_SCRIPT_Nabataean, + UNICODE_SCRIPT_Nag_Mundari, + UNICODE_SCRIPT_Nandinagari, + UNICODE_SCRIPT_New_Tai_Lue, + UNICODE_SCRIPT_Newa, + UNICODE_SCRIPT_Nko, + UNICODE_SCRIPT_Nushu, + UNICODE_SCRIPT_Nyiakeng_Puachue_Hmong, + UNICODE_SCRIPT_Ogham, + UNICODE_SCRIPT_Ol_Chiki, + UNICODE_SCRIPT_Old_Hungarian, + UNICODE_SCRIPT_Old_Italic, + UNICODE_SCRIPT_Old_North_Arabian, + UNICODE_SCRIPT_Old_Permic, + UNICODE_SCRIPT_Old_Persian, + UNICODE_SCRIPT_Old_Sogdian, + UNICODE_SCRIPT_Old_South_Arabian, + UNICODE_SCRIPT_Old_Turkic, + UNICODE_SCRIPT_Old_Uyghur, + UNICODE_SCRIPT_Oriya, + UNICODE_SCRIPT_Osage, + UNICODE_SCRIPT_Osmanya, + UNICODE_SCRIPT_Pahawh_Hmong, + UNICODE_SCRIPT_Palmyrene, + UNICODE_SCRIPT_Pau_Cin_Hau, + UNICODE_SCRIPT_Phags_Pa, + UNICODE_SCRIPT_Phoenician, + UNICODE_SCRIPT_Psalter_Pahlavi, + UNICODE_SCRIPT_Rejang, + UNICODE_SCRIPT_Runic, + UNICODE_SCRIPT_Samaritan, + UNICODE_SCRIPT_Saurashtra, + UNICODE_SCRIPT_Sharada, + UNICODE_SCRIPT_Shavian, + UNICODE_SCRIPT_Siddham, + UNICODE_SCRIPT_SignWriting, + UNICODE_SCRIPT_Sinhala, + UNICODE_SCRIPT_Sogdian, + UNICODE_SCRIPT_Sora_Sompeng, + UNICODE_SCRIPT_Soyombo, + UNICODE_SCRIPT_Sundanese, + UNICODE_SCRIPT_Syloti_Nagri, + UNICODE_SCRIPT_Syriac, + UNICODE_SCRIPT_Tagalog, + UNICODE_SCRIPT_Tagbanwa, + UNICODE_SCRIPT_Tai_Le, + UNICODE_SCRIPT_Tai_Tham, + UNICODE_SCRIPT_Tai_Viet, + UNICODE_SCRIPT_Takri, + UNICODE_SCRIPT_Tamil, + UNICODE_SCRIPT_Tangut, + UNICODE_SCRIPT_Telugu, + UNICODE_SCRIPT_Thaana, + UNICODE_SCRIPT_Thai, + UNICODE_SCRIPT_Tibetan, + UNICODE_SCRIPT_Tifinagh, + UNICODE_SCRIPT_Tirhuta, + UNICODE_SCRIPT_Tangsa, + UNICODE_SCRIPT_Toto, + UNICODE_SCRIPT_Ugaritic, + UNICODE_SCRIPT_Vai, + UNICODE_SCRIPT_Vithkuqi, + UNICODE_SCRIPT_Wancho, + UNICODE_SCRIPT_Warang_Citi, + UNICODE_SCRIPT_Yezidi, + UNICODE_SCRIPT_Yi, + UNICODE_SCRIPT_Zanabazar_Square, + UNICODE_SCRIPT_COUNT, +} UnicodeScriptEnum; + +static const char unicode_script_name_table[] = + "Adlam,Adlm" "\0" + "Ahom,Ahom" "\0" + "Anatolian_Hieroglyphs,Hluw" "\0" + "Arabic,Arab" "\0" + "Armenian,Armn" "\0" + "Avestan,Avst" "\0" + "Balinese,Bali" "\0" + "Bamum,Bamu" "\0" + "Bassa_Vah,Bass" "\0" + "Batak,Batk" "\0" + "Bengali,Beng" "\0" + "Bhaiksuki,Bhks" "\0" + "Bopomofo,Bopo" "\0" + "Brahmi,Brah" "\0" + "Braille,Brai" "\0" + "Buginese,Bugi" "\0" + "Buhid,Buhd" "\0" + "Canadian_Aboriginal,Cans" "\0" + "Carian,Cari" "\0" + "Caucasian_Albanian,Aghb" "\0" + "Chakma,Cakm" "\0" + "Cham,Cham" "\0" + "Cherokee,Cher" "\0" + "Chorasmian,Chrs" "\0" + "Common,Zyyy" "\0" + "Coptic,Copt,Qaac" "\0" + "Cuneiform,Xsux" "\0" + "Cypriot,Cprt" "\0" + "Cyrillic,Cyrl" "\0" + "Cypro_Minoan,Cpmn" "\0" + "Deseret,Dsrt" "\0" + "Devanagari,Deva" "\0" + "Dives_Akuru,Diak" "\0" + "Dogra,Dogr" "\0" + "Duployan,Dupl" "\0" + "Egyptian_Hieroglyphs,Egyp" "\0" + "Elbasan,Elba" "\0" + "Elymaic,Elym" "\0" + "Ethiopic,Ethi" "\0" + "Georgian,Geor" "\0" + "Glagolitic,Glag" "\0" + "Gothic,Goth" "\0" + "Grantha,Gran" "\0" + "Greek,Grek" "\0" + "Gujarati,Gujr" "\0" + "Gunjala_Gondi,Gong" "\0" + "Gurmukhi,Guru" "\0" + "Han,Hani" "\0" + "Hangul,Hang" "\0" + "Hanifi_Rohingya,Rohg" "\0" + "Hanunoo,Hano" "\0" + "Hatran,Hatr" "\0" + "Hebrew,Hebr" "\0" + "Hiragana,Hira" "\0" + "Imperial_Aramaic,Armi" "\0" + "Inherited,Zinh,Qaai" "\0" + "Inscriptional_Pahlavi,Phli" "\0" + "Inscriptional_Parthian,Prti" "\0" + "Javanese,Java" "\0" + "Kaithi,Kthi" "\0" + "Kannada,Knda" "\0" + "Katakana,Kana" "\0" + "Kawi,Kawi" "\0" + "Kayah_Li,Kali" "\0" + "Kharoshthi,Khar" "\0" + "Khmer,Khmr" "\0" + "Khojki,Khoj" "\0" + "Khitan_Small_Script,Kits" "\0" + "Khudawadi,Sind" "\0" + "Lao,Laoo" "\0" + "Latin,Latn" "\0" + "Lepcha,Lepc" "\0" + "Limbu,Limb" "\0" + "Linear_A,Lina" "\0" + "Linear_B,Linb" "\0" + "Lisu,Lisu" "\0" + "Lycian,Lyci" "\0" + "Lydian,Lydi" "\0" + "Makasar,Maka" "\0" + "Mahajani,Mahj" "\0" + "Malayalam,Mlym" "\0" + "Mandaic,Mand" "\0" + "Manichaean,Mani" "\0" + "Marchen,Marc" "\0" + "Masaram_Gondi,Gonm" "\0" + "Medefaidrin,Medf" "\0" + "Meetei_Mayek,Mtei" "\0" + "Mende_Kikakui,Mend" "\0" + "Meroitic_Cursive,Merc" "\0" + "Meroitic_Hieroglyphs,Mero" "\0" + "Miao,Plrd" "\0" + "Modi,Modi" "\0" + "Mongolian,Mong" "\0" + "Mro,Mroo" "\0" + "Multani,Mult" "\0" + "Myanmar,Mymr" "\0" + "Nabataean,Nbat" "\0" + "Nag_Mundari,Nagm" "\0" + "Nandinagari,Nand" "\0" + "New_Tai_Lue,Talu" "\0" + "Newa,Newa" "\0" + "Nko,Nkoo" "\0" + "Nushu,Nshu" "\0" + "Nyiakeng_Puachue_Hmong,Hmnp" "\0" + "Ogham,Ogam" "\0" + "Ol_Chiki,Olck" "\0" + "Old_Hungarian,Hung" "\0" + "Old_Italic,Ital" "\0" + "Old_North_Arabian,Narb" "\0" + "Old_Permic,Perm" "\0" + "Old_Persian,Xpeo" "\0" + "Old_Sogdian,Sogo" "\0" + "Old_South_Arabian,Sarb" "\0" + "Old_Turkic,Orkh" "\0" + "Old_Uyghur,Ougr" "\0" + "Oriya,Orya" "\0" + "Osage,Osge" "\0" + "Osmanya,Osma" "\0" + "Pahawh_Hmong,Hmng" "\0" + "Palmyrene,Palm" "\0" + "Pau_Cin_Hau,Pauc" "\0" + "Phags_Pa,Phag" "\0" + "Phoenician,Phnx" "\0" + "Psalter_Pahlavi,Phlp" "\0" + "Rejang,Rjng" "\0" + "Runic,Runr" "\0" + "Samaritan,Samr" "\0" + "Saurashtra,Saur" "\0" + "Sharada,Shrd" "\0" + "Shavian,Shaw" "\0" + "Siddham,Sidd" "\0" + "SignWriting,Sgnw" "\0" + "Sinhala,Sinh" "\0" + "Sogdian,Sogd" "\0" + "Sora_Sompeng,Sora" "\0" + "Soyombo,Soyo" "\0" + "Sundanese,Sund" "\0" + "Syloti_Nagri,Sylo" "\0" + "Syriac,Syrc" "\0" + "Tagalog,Tglg" "\0" + "Tagbanwa,Tagb" "\0" + "Tai_Le,Tale" "\0" + "Tai_Tham,Lana" "\0" + "Tai_Viet,Tavt" "\0" + "Takri,Takr" "\0" + "Tamil,Taml" "\0" + "Tangut,Tang" "\0" + "Telugu,Telu" "\0" + "Thaana,Thaa" "\0" + "Thai,Thai" "\0" + "Tibetan,Tibt" "\0" + "Tifinagh,Tfng" "\0" + "Tirhuta,Tirh" "\0" + "Tangsa,Tnsa" "\0" + "Toto,Toto" "\0" + "Ugaritic,Ugar" "\0" + "Vai,Vaii" "\0" + "Vithkuqi,Vith" "\0" + "Wancho,Wcho" "\0" + "Warang_Citi,Wara" "\0" + "Yezidi,Yezi" "\0" + "Yi,Yiii" "\0" + "Zanabazar_Square,Zanb" "\0" +; + +static const uint8_t unicode_script_table[2720] = { + 0xc0, 0x19, 0x99, 0x47, 0x85, 0x19, 0x99, 0x47, + 0xae, 0x19, 0x80, 0x47, 0x8e, 0x19, 0x80, 0x47, + 0x84, 0x19, 0x96, 0x47, 0x80, 0x19, 0x9e, 0x47, + 0x80, 0x19, 0xe1, 0x60, 0x47, 0xa6, 0x19, 0x84, + 0x47, 0x84, 0x19, 0x81, 0x0d, 0x93, 0x19, 0xe0, + 0x0f, 0x38, 0x83, 0x2c, 0x80, 0x19, 0x82, 0x2c, + 0x01, 0x83, 0x2c, 0x80, 0x19, 0x80, 0x2c, 0x03, + 0x80, 0x2c, 0x80, 0x19, 0x80, 0x2c, 0x80, 0x19, + 0x82, 0x2c, 0x00, 0x80, 0x2c, 0x00, 0x93, 0x2c, + 0x00, 0xbe, 0x2c, 0x8d, 0x1a, 0x8f, 0x2c, 0xe0, + 0x24, 0x1d, 0x81, 0x38, 0xe0, 0x48, 0x1d, 0x00, + 0xa5, 0x05, 0x01, 0xb1, 0x05, 0x01, 0x82, 0x05, + 0x00, 0xb6, 0x35, 0x07, 0x9a, 0x35, 0x03, 0x85, + 0x35, 0x0a, 0x84, 0x04, 0x80, 0x19, 0x85, 0x04, + 0x80, 0x19, 0x8d, 0x04, 0x80, 0x19, 0x82, 0x04, + 0x80, 0x19, 0x9f, 0x04, 0x80, 0x19, 0x89, 0x04, + 0x8a, 0x38, 0x99, 0x04, 0x80, 0x38, 0xe0, 0x0b, + 0x04, 0x80, 0x19, 0xa1, 0x04, 0x8d, 0x8b, 0x00, + 0xbb, 0x8b, 0x01, 0x82, 0x8b, 0xaf, 0x04, 0xb1, + 0x95, 0x0d, 0xba, 0x66, 0x01, 0x82, 0x66, 0xad, + 0x7f, 0x01, 0x8e, 0x7f, 0x00, 0x9b, 0x52, 0x01, + 0x80, 0x52, 0x00, 0x8a, 0x8b, 0x04, 0x9e, 0x04, + 0x00, 0x81, 0x04, 0x05, 0xc9, 0x04, 0x80, 0x19, + 0x9c, 0x04, 0xd0, 0x20, 0x83, 0x38, 0x8e, 0x20, + 0x81, 0x19, 0x99, 0x20, 0x83, 0x0b, 0x00, 0x87, + 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x95, 0x0b, 0x00, + 0x86, 0x0b, 0x00, 0x80, 0x0b, 0x02, 0x83, 0x0b, + 0x01, 0x88, 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x83, + 0x0b, 0x07, 0x80, 0x0b, 0x03, 0x81, 0x0b, 0x00, + 0x84, 0x0b, 0x01, 0x98, 0x0b, 0x01, 0x82, 0x2f, + 0x00, 0x85, 0x2f, 0x03, 0x81, 0x2f, 0x01, 0x95, + 0x2f, 0x00, 0x86, 0x2f, 0x00, 0x81, 0x2f, 0x00, + 0x81, 0x2f, 0x00, 0x81, 0x2f, 0x01, 0x80, 0x2f, + 0x00, 0x84, 0x2f, 0x03, 0x81, 0x2f, 0x01, 0x82, + 0x2f, 0x02, 0x80, 0x2f, 0x06, 0x83, 0x2f, 0x00, + 0x80, 0x2f, 0x06, 0x90, 0x2f, 0x09, 0x82, 0x2d, + 0x00, 0x88, 0x2d, 0x00, 0x82, 0x2d, 0x00, 0x95, + 0x2d, 0x00, 0x86, 0x2d, 0x00, 0x81, 0x2d, 0x00, + 0x84, 0x2d, 0x01, 0x89, 0x2d, 0x00, 0x82, 0x2d, + 0x00, 0x82, 0x2d, 0x01, 0x80, 0x2d, 0x0e, 0x83, + 0x2d, 0x01, 0x8b, 0x2d, 0x06, 0x86, 0x2d, 0x00, + 0x82, 0x74, 0x00, 0x87, 0x74, 0x01, 0x81, 0x74, + 0x01, 0x95, 0x74, 0x00, 0x86, 0x74, 0x00, 0x81, + 0x74, 0x00, 0x84, 0x74, 0x01, 0x88, 0x74, 0x01, + 0x81, 0x74, 0x01, 0x82, 0x74, 0x06, 0x82, 0x74, + 0x03, 0x81, 0x74, 0x00, 0x84, 0x74, 0x01, 0x91, + 0x74, 0x09, 0x81, 0x92, 0x00, 0x85, 0x92, 0x02, + 0x82, 0x92, 0x00, 0x83, 0x92, 0x02, 0x81, 0x92, + 0x00, 0x80, 0x92, 0x00, 0x81, 0x92, 0x02, 0x81, + 0x92, 0x02, 0x82, 0x92, 0x02, 0x8b, 0x92, 0x03, + 0x84, 0x92, 0x02, 0x82, 0x92, 0x00, 0x83, 0x92, + 0x01, 0x80, 0x92, 0x05, 0x80, 0x92, 0x0d, 0x94, + 0x92, 0x04, 0x8c, 0x94, 0x00, 0x82, 0x94, 0x00, + 0x96, 0x94, 0x00, 0x8f, 0x94, 0x01, 0x88, 0x94, + 0x00, 0x82, 0x94, 0x00, 0x83, 0x94, 0x06, 0x81, + 0x94, 0x00, 0x82, 0x94, 0x01, 0x80, 0x94, 0x01, + 0x83, 0x94, 0x01, 0x89, 0x94, 0x06, 0x88, 0x94, + 0x8c, 0x3d, 0x00, 0x82, 0x3d, 0x00, 0x96, 0x3d, + 0x00, 0x89, 0x3d, 0x00, 0x84, 0x3d, 0x01, 0x88, + 0x3d, 0x00, 0x82, 0x3d, 0x00, 0x83, 0x3d, 0x06, + 0x81, 0x3d, 0x05, 0x81, 0x3d, 0x00, 0x83, 0x3d, + 0x01, 0x89, 0x3d, 0x00, 0x82, 0x3d, 0x0b, 0x8c, + 0x51, 0x00, 0x82, 0x51, 0x00, 0xb2, 0x51, 0x00, + 0x82, 0x51, 0x00, 0x85, 0x51, 0x03, 0x8f, 0x51, + 0x01, 0x99, 0x51, 0x00, 0x82, 0x85, 0x00, 0x91, + 0x85, 0x02, 0x97, 0x85, 0x00, 0x88, 0x85, 0x00, + 0x80, 0x85, 0x01, 0x86, 0x85, 0x02, 0x80, 0x85, + 0x03, 0x85, 0x85, 0x00, 0x80, 0x85, 0x00, 0x87, + 0x85, 0x05, 0x89, 0x85, 0x01, 0x82, 0x85, 0x0b, + 0xb9, 0x96, 0x03, 0x80, 0x19, 0x9b, 0x96, 0x24, + 0x81, 0x46, 0x00, 0x80, 0x46, 0x00, 0x84, 0x46, + 0x00, 0x97, 0x46, 0x00, 0x80, 0x46, 0x00, 0x96, + 0x46, 0x01, 0x84, 0x46, 0x00, 0x80, 0x46, 0x00, + 0x86, 0x46, 0x00, 0x89, 0x46, 0x01, 0x83, 0x46, + 0x1f, 0xc7, 0x97, 0x00, 0xa3, 0x97, 0x03, 0xa6, + 0x97, 0x00, 0xa3, 0x97, 0x00, 0x8e, 0x97, 0x00, + 0x86, 0x97, 0x83, 0x19, 0x81, 0x97, 0x24, 0xe0, + 0x3f, 0x60, 0xa5, 0x28, 0x00, 0x80, 0x28, 0x04, + 0x80, 0x28, 0x01, 0xaa, 0x28, 0x80, 0x19, 0x83, + 0x28, 0xe0, 0x9f, 0x31, 0xc8, 0x27, 0x00, 0x83, + 0x27, 0x01, 0x86, 0x27, 0x00, 0x80, 0x27, 0x00, + 0x83, 0x27, 0x01, 0xa8, 0x27, 0x00, 0x83, 0x27, + 0x01, 0xa0, 0x27, 0x00, 0x83, 0x27, 0x01, 0x86, + 0x27, 0x00, 0x80, 0x27, 0x00, 0x83, 0x27, 0x01, + 0x8e, 0x27, 0x00, 0xb8, 0x27, 0x00, 0x83, 0x27, + 0x01, 0xc2, 0x27, 0x01, 0x9f, 0x27, 0x02, 0x99, + 0x27, 0x05, 0xd5, 0x17, 0x01, 0x85, 0x17, 0x01, + 0xe2, 0x1f, 0x12, 0x9c, 0x69, 0x02, 0xca, 0x7e, + 0x82, 0x19, 0x8a, 0x7e, 0x06, 0x95, 0x8c, 0x08, + 0x80, 0x8c, 0x94, 0x33, 0x81, 0x19, 0x08, 0x93, + 0x11, 0x0b, 0x8c, 0x8d, 0x00, 0x82, 0x8d, 0x00, + 0x81, 0x8d, 0x0b, 0xdd, 0x42, 0x01, 0x89, 0x42, + 0x05, 0x89, 0x42, 0x05, 0x81, 0x5d, 0x81, 0x19, + 0x80, 0x5d, 0x80, 0x19, 0x93, 0x5d, 0x05, 0xd8, + 0x5d, 0x06, 0xaa, 0x5d, 0x04, 0xc5, 0x12, 0x09, + 0x9e, 0x49, 0x00, 0x8b, 0x49, 0x03, 0x8b, 0x49, + 0x03, 0x80, 0x49, 0x02, 0x8b, 0x49, 0x9d, 0x8e, + 0x01, 0x84, 0x8e, 0x0a, 0xab, 0x64, 0x03, 0x99, + 0x64, 0x05, 0x8a, 0x64, 0x02, 0x81, 0x64, 0x9f, + 0x42, 0x9b, 0x10, 0x01, 0x81, 0x10, 0xbe, 0x8f, + 0x00, 0x9c, 0x8f, 0x01, 0x8a, 0x8f, 0x05, 0x89, + 0x8f, 0x05, 0x8d, 0x8f, 0x01, 0x9e, 0x38, 0x30, + 0xcc, 0x07, 0x02, 0xae, 0x07, 0x00, 0xbf, 0x89, + 0xb3, 0x0a, 0x07, 0x83, 0x0a, 0xb7, 0x48, 0x02, + 0x8e, 0x48, 0x02, 0x82, 0x48, 0xaf, 0x6a, 0x88, + 0x1d, 0x06, 0xaa, 0x28, 0x01, 0x82, 0x28, 0x87, + 0x89, 0x07, 0x82, 0x38, 0x80, 0x19, 0x8c, 0x38, + 0x80, 0x19, 0x86, 0x38, 0x83, 0x19, 0x80, 0x38, + 0x85, 0x19, 0x80, 0x38, 0x82, 0x19, 0x81, 0x38, + 0x80, 0x19, 0x04, 0xa5, 0x47, 0x84, 0x2c, 0x80, + 0x1d, 0xb0, 0x47, 0x84, 0x2c, 0x83, 0x47, 0x84, + 0x2c, 0x8c, 0x47, 0x80, 0x1d, 0xc5, 0x47, 0x80, + 0x2c, 0xbf, 0x38, 0xe0, 0x9f, 0x47, 0x95, 0x2c, + 0x01, 0x85, 0x2c, 0x01, 0xa5, 0x2c, 0x01, 0x85, + 0x2c, 0x01, 0x87, 0x2c, 0x00, 0x80, 0x2c, 0x00, + 0x80, 0x2c, 0x00, 0x80, 0x2c, 0x00, 0x9e, 0x2c, + 0x01, 0xb4, 0x2c, 0x00, 0x8e, 0x2c, 0x00, 0x8d, + 0x2c, 0x01, 0x85, 0x2c, 0x00, 0x92, 0x2c, 0x01, + 0x82, 0x2c, 0x00, 0x88, 0x2c, 0x00, 0x8b, 0x19, + 0x81, 0x38, 0xd6, 0x19, 0x00, 0x8a, 0x19, 0x80, + 0x47, 0x01, 0x8a, 0x19, 0x80, 0x47, 0x8e, 0x19, + 0x00, 0x8c, 0x47, 0x02, 0xa0, 0x19, 0x0e, 0xa0, + 0x38, 0x0e, 0xa5, 0x19, 0x80, 0x2c, 0x82, 0x19, + 0x81, 0x47, 0x85, 0x19, 0x80, 0x47, 0x9a, 0x19, + 0x80, 0x47, 0x90, 0x19, 0xa8, 0x47, 0x82, 0x19, + 0x03, 0xe2, 0x36, 0x19, 0x18, 0x8a, 0x19, 0x14, + 0xe3, 0x3f, 0x19, 0xe0, 0x9f, 0x0f, 0xe2, 0x13, + 0x19, 0x01, 0x9f, 0x19, 0x00, 0xe0, 0x08, 0x19, + 0xdf, 0x29, 0x9f, 0x47, 0xe0, 0x13, 0x1a, 0x04, + 0x86, 0x1a, 0xa5, 0x28, 0x00, 0x80, 0x28, 0x04, + 0x80, 0x28, 0x01, 0xb7, 0x98, 0x06, 0x81, 0x98, + 0x0d, 0x80, 0x98, 0x96, 0x27, 0x08, 0x86, 0x27, + 0x00, 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, 0x86, + 0x27, 0x00, 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, + 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, 0x9f, 0x1d, + 0xdd, 0x19, 0x21, 0x99, 0x30, 0x00, 0xd8, 0x30, + 0x0b, 0xe0, 0x75, 0x30, 0x19, 0x8b, 0x19, 0x03, + 0x84, 0x19, 0x80, 0x30, 0x80, 0x19, 0x80, 0x30, + 0x98, 0x19, 0x88, 0x30, 0x83, 0x38, 0x81, 0x31, + 0x87, 0x19, 0x83, 0x30, 0x83, 0x19, 0x00, 0xd5, + 0x36, 0x01, 0x81, 0x38, 0x81, 0x19, 0x82, 0x36, + 0x80, 0x19, 0xd9, 0x3e, 0x81, 0x19, 0x82, 0x3e, + 0x04, 0xaa, 0x0d, 0x00, 0xdd, 0x31, 0x00, 0x8f, + 0x19, 0x9f, 0x0d, 0xa3, 0x19, 0x0b, 0x8f, 0x3e, + 0x9e, 0x31, 0x00, 0xbf, 0x19, 0x9e, 0x31, 0xd0, + 0x19, 0xae, 0x3e, 0x80, 0x19, 0xd7, 0x3e, 0xe0, + 0x47, 0x19, 0xf0, 0x09, 0x5f, 0x30, 0xbf, 0x19, + 0xf0, 0x41, 0x9f, 0x30, 0xe4, 0x2c, 0xa2, 0x02, + 0xb6, 0xa2, 0x08, 0xaf, 0x4c, 0xe0, 0xcb, 0x9d, + 0x13, 0xdf, 0x1d, 0xd7, 0x08, 0x07, 0xa1, 0x19, + 0xe0, 0x05, 0x47, 0x82, 0x19, 0xbf, 0x47, 0x04, + 0x81, 0x47, 0x00, 0x80, 0x47, 0x00, 0x84, 0x47, + 0x17, 0x8d, 0x47, 0xac, 0x8a, 0x02, 0x89, 0x19, + 0x05, 0xb7, 0x7a, 0x07, 0xc5, 0x80, 0x07, 0x8b, + 0x80, 0x05, 0x9f, 0x20, 0xad, 0x40, 0x80, 0x19, + 0x80, 0x40, 0xa3, 0x7d, 0x0a, 0x80, 0x7d, 0x9c, + 0x31, 0x02, 0xcd, 0x3b, 0x00, 0x80, 0x19, 0x89, + 0x3b, 0x03, 0x81, 0x3b, 0x9e, 0x60, 0x00, 0xb6, + 0x16, 0x08, 0x8d, 0x16, 0x01, 0x89, 0x16, 0x01, + 0x83, 0x16, 0x9f, 0x60, 0xc2, 0x90, 0x17, 0x84, + 0x90, 0x96, 0x57, 0x09, 0x85, 0x27, 0x01, 0x85, + 0x27, 0x01, 0x85, 0x27, 0x08, 0x86, 0x27, 0x00, + 0x86, 0x27, 0x00, 0xaa, 0x47, 0x80, 0x19, 0x88, + 0x47, 0x80, 0x2c, 0x83, 0x47, 0x81, 0x19, 0x03, + 0xcf, 0x17, 0xad, 0x57, 0x01, 0x89, 0x57, 0x05, + 0xf0, 0x1b, 0x43, 0x31, 0x0b, 0x96, 0x31, 0x03, + 0xb0, 0x31, 0x70, 0x10, 0xa3, 0xe1, 0x0d, 0x30, + 0x01, 0xe0, 0x09, 0x30, 0x25, 0x86, 0x47, 0x0b, + 0x84, 0x05, 0x04, 0x99, 0x35, 0x00, 0x84, 0x35, + 0x00, 0x80, 0x35, 0x00, 0x81, 0x35, 0x00, 0x81, + 0x35, 0x00, 0x89, 0x35, 0xe0, 0x12, 0x04, 0x0f, + 0xe1, 0x0a, 0x04, 0x81, 0x19, 0xcf, 0x04, 0x01, + 0xb5, 0x04, 0x06, 0x80, 0x04, 0x1f, 0x8f, 0x04, + 0x8f, 0x38, 0x89, 0x19, 0x05, 0x8d, 0x38, 0x81, + 0x1d, 0xa2, 0x19, 0x00, 0x92, 0x19, 0x00, 0x83, + 0x19, 0x03, 0x84, 0x04, 0x00, 0xe0, 0x26, 0x04, + 0x01, 0x80, 0x19, 0x00, 0x9f, 0x19, 0x99, 0x47, + 0x85, 0x19, 0x99, 0x47, 0x8a, 0x19, 0x89, 0x3e, + 0x80, 0x19, 0xac, 0x3e, 0x81, 0x19, 0x9e, 0x31, + 0x02, 0x85, 0x31, 0x01, 0x85, 0x31, 0x01, 0x85, + 0x31, 0x01, 0x82, 0x31, 0x02, 0x86, 0x19, 0x00, + 0x86, 0x19, 0x09, 0x84, 0x19, 0x01, 0x8b, 0x4b, + 0x00, 0x99, 0x4b, 0x00, 0x92, 0x4b, 0x00, 0x81, + 0x4b, 0x00, 0x8e, 0x4b, 0x01, 0x8d, 0x4b, 0x21, + 0xe0, 0x1a, 0x4b, 0x04, 0x82, 0x19, 0x03, 0xac, + 0x19, 0x02, 0x88, 0x19, 0xce, 0x2c, 0x00, 0x8c, + 0x19, 0x02, 0x80, 0x2c, 0x2e, 0xac, 0x19, 0x80, + 0x38, 0x60, 0x21, 0x9c, 0x4d, 0x02, 0xb0, 0x13, + 0x0e, 0x80, 0x38, 0x9a, 0x19, 0x03, 0xa3, 0x6c, + 0x08, 0x82, 0x6c, 0x9a, 0x2a, 0x04, 0xaa, 0x6e, + 0x04, 0x9d, 0x9c, 0x00, 0x80, 0x9c, 0xa3, 0x6f, + 0x03, 0x8d, 0x6f, 0x29, 0xcf, 0x1f, 0xaf, 0x82, + 0x9d, 0x76, 0x01, 0x89, 0x76, 0x05, 0xa3, 0x75, + 0x03, 0xa3, 0x75, 0x03, 0xa7, 0x25, 0x07, 0xb3, + 0x14, 0x0a, 0x80, 0x14, 0x8a, 0x9e, 0x00, 0x8e, + 0x9e, 0x00, 0x86, 0x9e, 0x00, 0x81, 0x9e, 0x00, + 0x8a, 0x9e, 0x00, 0x8e, 0x9e, 0x00, 0x86, 0x9e, + 0x00, 0x81, 0x9e, 0x42, 0xe0, 0xd6, 0x4a, 0x08, + 0x95, 0x4a, 0x09, 0x87, 0x4a, 0x17, 0x85, 0x47, + 0x00, 0xa9, 0x47, 0x00, 0x88, 0x47, 0x44, 0x85, + 0x1c, 0x01, 0x80, 0x1c, 0x00, 0xab, 0x1c, 0x00, + 0x81, 0x1c, 0x02, 0x80, 0x1c, 0x01, 0x80, 0x1c, + 0x95, 0x37, 0x00, 0x88, 0x37, 0x9f, 0x78, 0x9e, + 0x61, 0x07, 0x88, 0x61, 0x2f, 0x92, 0x34, 0x00, + 0x81, 0x34, 0x04, 0x84, 0x34, 0x9b, 0x7b, 0x02, + 0x80, 0x7b, 0x99, 0x4e, 0x04, 0x80, 0x4e, 0x3f, + 0x9f, 0x5a, 0x97, 0x59, 0x03, 0x93, 0x59, 0x01, + 0xad, 0x59, 0x83, 0x41, 0x00, 0x81, 0x41, 0x04, + 0x87, 0x41, 0x00, 0x82, 0x41, 0x00, 0x9c, 0x41, + 0x01, 0x82, 0x41, 0x03, 0x89, 0x41, 0x06, 0x88, + 0x41, 0x06, 0x9f, 0x71, 0x9f, 0x6d, 0x1f, 0xa6, + 0x53, 0x03, 0x8b, 0x53, 0x08, 0xb5, 0x06, 0x02, + 0x86, 0x06, 0x95, 0x3a, 0x01, 0x87, 0x3a, 0x92, + 0x39, 0x04, 0x87, 0x39, 0x91, 0x7c, 0x06, 0x83, + 0x7c, 0x0b, 0x86, 0x7c, 0x4f, 0xc8, 0x72, 0x36, + 0xb2, 0x6b, 0x0c, 0xb2, 0x6b, 0x06, 0x85, 0x6b, + 0xa7, 0x32, 0x07, 0x89, 0x32, 0x60, 0xc5, 0x9e, + 0x04, 0x00, 0xa9, 0xa1, 0x00, 0x82, 0xa1, 0x01, + 0x81, 0xa1, 0x4a, 0x82, 0x04, 0xa7, 0x70, 0x07, + 0xa9, 0x86, 0x15, 0x99, 0x73, 0x25, 0x9b, 0x18, + 0x13, 0x96, 0x26, 0x08, 0xcd, 0x0e, 0x03, 0xa3, + 0x0e, 0x08, 0x80, 0x0e, 0xc2, 0x3c, 0x09, 0x80, + 0x3c, 0x01, 0x98, 0x87, 0x06, 0x89, 0x87, 0x05, + 0xb4, 0x15, 0x00, 0x91, 0x15, 0x07, 0xa6, 0x50, + 0x08, 0xdf, 0x81, 0x00, 0x93, 0x85, 0x0a, 0x91, + 0x43, 0x00, 0xae, 0x43, 0x3d, 0x86, 0x5f, 0x00, + 0x80, 0x5f, 0x00, 0x83, 0x5f, 0x00, 0x8e, 0x5f, + 0x00, 0x8a, 0x5f, 0x05, 0xba, 0x45, 0x04, 0x89, + 0x45, 0x05, 0x83, 0x2b, 0x00, 0x87, 0x2b, 0x01, + 0x81, 0x2b, 0x01, 0x95, 0x2b, 0x00, 0x86, 0x2b, + 0x00, 0x81, 0x2b, 0x00, 0x84, 0x2b, 0x00, 0x80, + 0x38, 0x88, 0x2b, 0x01, 0x81, 0x2b, 0x01, 0x82, + 0x2b, 0x01, 0x80, 0x2b, 0x05, 0x80, 0x2b, 0x04, + 0x86, 0x2b, 0x01, 0x86, 0x2b, 0x02, 0x84, 0x2b, + 0x60, 0x2a, 0xdb, 0x65, 0x00, 0x84, 0x65, 0x1d, + 0xc7, 0x99, 0x07, 0x89, 0x99, 0x60, 0x45, 0xb5, + 0x83, 0x01, 0xa5, 0x83, 0x21, 0xc4, 0x5c, 0x0a, + 0x89, 0x5c, 0x05, 0x8c, 0x5d, 0x12, 0xb9, 0x91, + 0x05, 0x89, 0x91, 0x35, 0x9a, 0x02, 0x01, 0x8e, + 0x02, 0x03, 0x96, 0x02, 0x60, 0x58, 0xbb, 0x22, + 0x60, 0x03, 0xd2, 0xa0, 0x0b, 0x80, 0xa0, 0x86, + 0x21, 0x01, 0x80, 0x21, 0x01, 0x87, 0x21, 0x00, + 0x81, 0x21, 0x00, 0x9d, 0x21, 0x00, 0x81, 0x21, + 0x01, 0x8b, 0x21, 0x08, 0x89, 0x21, 0x45, 0x87, + 0x63, 0x01, 0xad, 0x63, 0x01, 0x8a, 0x63, 0x1a, + 0xc7, 0xa3, 0x07, 0xd2, 0x88, 0x0c, 0x8f, 0x12, + 0xb8, 0x79, 0x06, 0x89, 0x20, 0x60, 0x95, 0x88, + 0x0c, 0x00, 0xac, 0x0c, 0x00, 0x8d, 0x0c, 0x09, + 0x9c, 0x0c, 0x02, 0x9f, 0x54, 0x01, 0x95, 0x54, + 0x00, 0x8d, 0x54, 0x48, 0x86, 0x55, 0x00, 0x81, + 0x55, 0x00, 0xab, 0x55, 0x02, 0x80, 0x55, 0x00, + 0x81, 0x55, 0x00, 0x88, 0x55, 0x07, 0x89, 0x55, + 0x05, 0x85, 0x2e, 0x00, 0x81, 0x2e, 0x00, 0xa4, + 0x2e, 0x00, 0x81, 0x2e, 0x00, 0x85, 0x2e, 0x06, + 0x89, 0x2e, 0x60, 0xd5, 0x98, 0x4f, 0x06, 0x90, + 0x3f, 0x00, 0xa8, 0x3f, 0x02, 0x9b, 0x3f, 0x55, + 0x80, 0x4c, 0x0e, 0xb1, 0x92, 0x0c, 0x80, 0x92, + 0xe3, 0x39, 0x1b, 0x60, 0x05, 0xe0, 0x0e, 0x1b, + 0x00, 0x84, 0x1b, 0x0a, 0xe0, 0x63, 0x1b, 0x69, + 0xeb, 0xe0, 0x02, 0x1e, 0x0c, 0xe3, 0xf5, 0x24, + 0x6f, 0x49, 0xe1, 0xe6, 0x03, 0x70, 0x11, 0x58, + 0xe1, 0xd8, 0x08, 0x06, 0x9e, 0x5e, 0x00, 0x89, + 0x5e, 0x03, 0x81, 0x5e, 0xce, 0x9a, 0x00, 0x89, + 0x9a, 0x05, 0x9d, 0x09, 0x01, 0x85, 0x09, 0x09, + 0xc5, 0x77, 0x09, 0x89, 0x77, 0x00, 0x86, 0x77, + 0x00, 0x94, 0x77, 0x04, 0x92, 0x77, 0x62, 0x4f, + 0xda, 0x56, 0x60, 0x04, 0xca, 0x5b, 0x03, 0xb8, + 0x5b, 0x06, 0x90, 0x5b, 0x3f, 0x80, 0x93, 0x80, + 0x67, 0x81, 0x30, 0x80, 0x44, 0x0a, 0x81, 0x30, + 0x0d, 0xf0, 0x07, 0x97, 0x93, 0x07, 0xe2, 0x9f, + 0x93, 0xe1, 0x75, 0x44, 0x29, 0x88, 0x93, 0x70, + 0x12, 0x86, 0x83, 0x3e, 0x00, 0x86, 0x3e, 0x00, + 0x81, 0x3e, 0x00, 0x80, 0x3e, 0xe0, 0xbe, 0x36, + 0x82, 0x3e, 0x0e, 0x80, 0x36, 0x1c, 0x82, 0x36, + 0x01, 0x80, 0x3e, 0x0d, 0x83, 0x3e, 0x07, 0xe1, + 0x2b, 0x67, 0x68, 0xa3, 0xe0, 0x0a, 0x23, 0x04, + 0x8c, 0x23, 0x02, 0x88, 0x23, 0x06, 0x89, 0x23, + 0x01, 0x83, 0x23, 0x83, 0x19, 0x70, 0x01, 0xfb, + 0xad, 0x38, 0x01, 0x96, 0x38, 0x08, 0xe0, 0x13, + 0x19, 0x3b, 0xe0, 0x95, 0x19, 0x09, 0xa6, 0x19, + 0x01, 0xbd, 0x19, 0x82, 0x38, 0x90, 0x19, 0x87, + 0x38, 0x81, 0x19, 0x86, 0x38, 0x9d, 0x19, 0x83, + 0x38, 0xbc, 0x19, 0x14, 0xc5, 0x2c, 0x60, 0x19, + 0x93, 0x19, 0x0b, 0x93, 0x19, 0x0b, 0xd6, 0x19, + 0x08, 0x98, 0x19, 0x60, 0x26, 0xd4, 0x19, 0x00, + 0xc6, 0x19, 0x00, 0x81, 0x19, 0x01, 0x80, 0x19, + 0x01, 0x81, 0x19, 0x01, 0x83, 0x19, 0x00, 0x8b, + 0x19, 0x00, 0x80, 0x19, 0x00, 0x86, 0x19, 0x00, + 0xc0, 0x19, 0x00, 0x83, 0x19, 0x01, 0x87, 0x19, + 0x00, 0x86, 0x19, 0x00, 0x9b, 0x19, 0x00, 0x83, + 0x19, 0x00, 0x84, 0x19, 0x00, 0x80, 0x19, 0x02, + 0x86, 0x19, 0x00, 0xe0, 0xf3, 0x19, 0x01, 0xe0, + 0xc3, 0x19, 0x01, 0xb1, 0x19, 0xe2, 0x2b, 0x84, + 0x0e, 0x84, 0x84, 0x00, 0x8e, 0x84, 0x63, 0xef, + 0x9e, 0x47, 0x05, 0x85, 0x47, 0x60, 0x74, 0x86, + 0x29, 0x00, 0x90, 0x29, 0x01, 0x86, 0x29, 0x00, + 0x81, 0x29, 0x00, 0x84, 0x29, 0x04, 0xbd, 0x1d, + 0x20, 0x80, 0x1d, 0x60, 0x0f, 0xac, 0x68, 0x02, + 0x8d, 0x68, 0x01, 0x89, 0x68, 0x03, 0x81, 0x68, + 0x60, 0xdf, 0x9e, 0x9b, 0x10, 0xb9, 0x9f, 0x04, + 0x80, 0x9f, 0x61, 0x6f, 0xa9, 0x62, 0x62, 0x85, + 0x86, 0x27, 0x00, 0x83, 0x27, 0x00, 0x81, 0x27, + 0x00, 0x8e, 0x27, 0x00, 0xe0, 0x64, 0x58, 0x01, + 0x8f, 0x58, 0x28, 0xcb, 0x01, 0x03, 0x89, 0x01, + 0x03, 0x81, 0x01, 0x62, 0xb0, 0xc3, 0x19, 0x4b, + 0xbc, 0x19, 0x60, 0x61, 0x83, 0x04, 0x00, 0x9a, + 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, 0x04, 0x01, + 0x80, 0x04, 0x00, 0x89, 0x04, 0x00, 0x83, 0x04, + 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x05, 0x80, + 0x04, 0x03, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, + 0x80, 0x04, 0x00, 0x82, 0x04, 0x00, 0x81, 0x04, + 0x00, 0x80, 0x04, 0x01, 0x80, 0x04, 0x00, 0x80, + 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, + 0x80, 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, 0x04, + 0x01, 0x83, 0x04, 0x00, 0x86, 0x04, 0x00, 0x83, + 0x04, 0x00, 0x83, 0x04, 0x00, 0x80, 0x04, 0x00, + 0x89, 0x04, 0x00, 0x90, 0x04, 0x04, 0x82, 0x04, + 0x00, 0x84, 0x04, 0x00, 0x90, 0x04, 0x33, 0x81, + 0x04, 0x60, 0xad, 0xab, 0x19, 0x03, 0xe0, 0x03, + 0x19, 0x0b, 0x8e, 0x19, 0x01, 0x8e, 0x19, 0x00, + 0x8e, 0x19, 0x00, 0xa4, 0x19, 0x09, 0xe0, 0x4d, + 0x19, 0x37, 0x99, 0x19, 0x80, 0x36, 0x81, 0x19, + 0x0c, 0xab, 0x19, 0x03, 0x88, 0x19, 0x06, 0x81, + 0x19, 0x0d, 0x85, 0x19, 0x60, 0x39, 0xe3, 0x77, + 0x19, 0x03, 0x90, 0x19, 0x02, 0x8c, 0x19, 0x02, + 0xe0, 0x16, 0x19, 0x03, 0xde, 0x19, 0x05, 0x8b, + 0x19, 0x03, 0x80, 0x19, 0x0e, 0x8b, 0x19, 0x03, + 0xb7, 0x19, 0x07, 0x89, 0x19, 0x05, 0xa7, 0x19, + 0x07, 0x9d, 0x19, 0x01, 0x81, 0x19, 0x4d, 0xe0, + 0xf3, 0x19, 0x0b, 0x8d, 0x19, 0x01, 0x8c, 0x19, + 0x02, 0x88, 0x19, 0x06, 0xad, 0x19, 0x00, 0x86, + 0x19, 0x07, 0x8d, 0x19, 0x03, 0x88, 0x19, 0x06, + 0x88, 0x19, 0x06, 0xe0, 0x32, 0x19, 0x00, 0xb6, + 0x19, 0x24, 0x89, 0x19, 0x63, 0xa5, 0xf0, 0x96, + 0x7f, 0x30, 0x1f, 0xef, 0xd9, 0x30, 0x05, 0xe0, + 0x7d, 0x30, 0x01, 0xf0, 0x06, 0x21, 0x30, 0x0d, + 0xf0, 0x0c, 0xd0, 0x30, 0x6b, 0xbe, 0xe1, 0xbd, + 0x30, 0x65, 0x81, 0xf0, 0x02, 0xea, 0x30, 0x04, + 0xef, 0xff, 0x30, 0x7a, 0xcb, 0xf0, 0x80, 0x19, + 0x1d, 0xdf, 0x19, 0x60, 0x1f, 0xe0, 0x8f, 0x38, +}; + +static const uint8_t unicode_script_ext_table[828] = { + 0x82, 0xc1, 0x00, 0x00, 0x01, 0x2c, 0x01, 0x00, + 0x00, 0x01, 0x2c, 0x1c, 0x00, 0x0c, 0x01, 0x47, + 0x80, 0x92, 0x00, 0x00, 0x02, 0x1d, 0x6e, 0x00, + 0x02, 0x1d, 0x29, 0x01, 0x02, 0x1d, 0x47, 0x00, + 0x02, 0x1d, 0x29, 0x81, 0x03, 0x00, 0x00, 0x06, + 0x04, 0x66, 0x32, 0x8b, 0x95, 0xa1, 0x0d, 0x00, + 0x00, 0x06, 0x04, 0x66, 0x32, 0x8b, 0x95, 0xa1, + 0x00, 0x03, 0x04, 0x8b, 0x95, 0x01, 0x00, 0x00, + 0x07, 0x01, 0x04, 0x66, 0x32, 0x8b, 0x95, 0xa1, + 0x1f, 0x00, 0x00, 0x09, 0x01, 0x04, 0x52, 0x53, + 0x73, 0x7c, 0x32, 0x86, 0x8b, 0x09, 0x00, 0x0a, + 0x02, 0x04, 0x8b, 0x09, 0x00, 0x09, 0x03, 0x04, + 0x95, 0xa1, 0x05, 0x00, 0x00, 0x02, 0x04, 0x8b, + 0x62, 0x00, 0x00, 0x02, 0x04, 0x32, 0x81, 0xfb, + 0x00, 0x00, 0x0d, 0x0b, 0x20, 0x2b, 0x2d, 0x2f, + 0x3d, 0x47, 0x51, 0x74, 0x81, 0x92, 0x94, 0x99, + 0x00, 0x0c, 0x0b, 0x20, 0x2b, 0x2d, 0x2f, 0x3d, + 0x47, 0x51, 0x74, 0x92, 0x94, 0x99, 0x10, 0x00, + 0x00, 0x14, 0x0b, 0x20, 0x22, 0x2e, 0x55, 0x2b, + 0x2d, 0x2f, 0x3d, 0x50, 0x51, 0x63, 0x74, 0x45, + 0x85, 0x8a, 0x91, 0x92, 0x94, 0x99, 0x00, 0x15, + 0x0b, 0x20, 0x22, 0x2e, 0x55, 0x2b, 0x2d, 0x2f, + 0x3d, 0x49, 0x50, 0x51, 0x63, 0x74, 0x45, 0x85, + 0x8a, 0x91, 0x92, 0x94, 0x99, 0x09, 0x04, 0x20, + 0x22, 0x3c, 0x50, 0x75, 0x00, 0x09, 0x03, 0x0b, + 0x15, 0x8a, 0x75, 0x00, 0x09, 0x02, 0x2f, 0x5f, + 0x75, 0x00, 0x09, 0x02, 0x2d, 0x43, 0x80, 0x75, + 0x00, 0x0d, 0x02, 0x2b, 0x92, 0x80, 0x71, 0x00, + 0x09, 0x02, 0x3d, 0x63, 0x82, 0xcf, 0x00, 0x09, + 0x03, 0x15, 0x60, 0x8e, 0x80, 0x30, 0x00, 0x00, + 0x02, 0x28, 0x47, 0x85, 0xb8, 0x00, 0x01, 0x04, + 0x11, 0x33, 0x8d, 0x8c, 0x80, 0x4a, 0x00, 0x01, + 0x02, 0x5d, 0x7a, 0x00, 0x00, 0x00, 0x02, 0x5d, + 0x7a, 0x84, 0x49, 0x00, 0x00, 0x04, 0x0b, 0x20, + 0x2b, 0x3d, 0x00, 0x01, 0x20, 0x00, 0x04, 0x0b, + 0x20, 0x2b, 0x3d, 0x00, 0x02, 0x20, 0x2b, 0x00, + 0x01, 0x20, 0x01, 0x02, 0x0b, 0x20, 0x00, 0x02, + 0x20, 0x81, 0x00, 0x02, 0x0b, 0x20, 0x00, 0x02, + 0x20, 0x81, 0x00, 0x06, 0x20, 0x3d, 0x51, 0x74, + 0x92, 0x94, 0x00, 0x01, 0x20, 0x01, 0x02, 0x20, + 0x81, 0x01, 0x01, 0x20, 0x00, 0x02, 0x20, 0x81, + 0x00, 0x02, 0x0b, 0x20, 0x06, 0x01, 0x20, 0x00, + 0x02, 0x20, 0x63, 0x00, 0x02, 0x0b, 0x20, 0x01, + 0x01, 0x20, 0x00, 0x02, 0x0b, 0x20, 0x03, 0x01, + 0x20, 0x00, 0x08, 0x0b, 0x20, 0x2b, 0x3d, 0x63, + 0x74, 0x94, 0x99, 0x00, 0x02, 0x20, 0x2b, 0x00, + 0x03, 0x20, 0x2b, 0x3d, 0x01, 0x02, 0x0b, 0x20, + 0x00, 0x01, 0x0b, 0x01, 0x02, 0x20, 0x2b, 0x00, + 0x01, 0x63, 0x80, 0x44, 0x00, 0x01, 0x01, 0x2c, + 0x35, 0x00, 0x00, 0x02, 0x1d, 0x8b, 0x00, 0x00, + 0x00, 0x01, 0x8b, 0x81, 0xb3, 0x00, 0x00, 0x02, + 0x47, 0x5d, 0x80, 0x3f, 0x00, 0x00, 0x03, 0x20, + 0x2b, 0x47, 0x8c, 0xd1, 0x00, 0x00, 0x02, 0x1d, + 0x29, 0x81, 0x3c, 0x00, 0x01, 0x06, 0x0d, 0x31, + 0x30, 0x36, 0x3e, 0xa2, 0x00, 0x05, 0x0d, 0x31, + 0x30, 0x36, 0x3e, 0x01, 0x00, 0x00, 0x01, 0x30, + 0x00, 0x00, 0x09, 0x06, 0x0d, 0x31, 0x30, 0x36, + 0x3e, 0xa2, 0x00, 0x00, 0x00, 0x05, 0x0d, 0x31, + 0x30, 0x36, 0x3e, 0x07, 0x06, 0x0d, 0x31, 0x30, + 0x36, 0x3e, 0xa2, 0x03, 0x05, 0x0d, 0x31, 0x30, + 0x36, 0x3e, 0x09, 0x00, 0x03, 0x02, 0x0d, 0x30, + 0x01, 0x00, 0x00, 0x05, 0x0d, 0x31, 0x30, 0x36, + 0x3e, 0x04, 0x02, 0x36, 0x3e, 0x00, 0x00, 0x00, + 0x05, 0x0d, 0x31, 0x30, 0x36, 0x3e, 0x03, 0x00, + 0x01, 0x03, 0x30, 0x36, 0x3e, 0x01, 0x01, 0x30, + 0x58, 0x00, 0x03, 0x02, 0x36, 0x3e, 0x02, 0x00, + 0x00, 0x02, 0x36, 0x3e, 0x59, 0x00, 0x00, 0x06, + 0x0d, 0x31, 0x30, 0x36, 0x3e, 0xa2, 0x00, 0x02, + 0x36, 0x3e, 0x80, 0x12, 0x00, 0x0f, 0x01, 0x30, + 0x1f, 0x00, 0x23, 0x01, 0x30, 0x3b, 0x00, 0x27, + 0x01, 0x30, 0x37, 0x00, 0x30, 0x01, 0x30, 0x0e, + 0x00, 0x0b, 0x01, 0x30, 0x32, 0x00, 0x00, 0x01, + 0x30, 0x57, 0x00, 0x18, 0x01, 0x30, 0x09, 0x00, + 0x04, 0x01, 0x30, 0x5f, 0x00, 0x1e, 0x01, 0x30, + 0xc0, 0x31, 0xef, 0x00, 0x00, 0x02, 0x1d, 0x29, + 0x80, 0x0f, 0x00, 0x07, 0x02, 0x30, 0x47, 0x80, + 0xa7, 0x00, 0x02, 0x0e, 0x20, 0x22, 0x2d, 0x2f, + 0x43, 0x3d, 0x3c, 0x50, 0x51, 0x5c, 0x63, 0x45, + 0x91, 0x99, 0x02, 0x0d, 0x20, 0x22, 0x2d, 0x2f, + 0x43, 0x3d, 0x3c, 0x50, 0x5c, 0x63, 0x45, 0x91, + 0x99, 0x03, 0x0b, 0x20, 0x22, 0x2d, 0x2f, 0x43, + 0x3c, 0x50, 0x5c, 0x45, 0x91, 0x99, 0x80, 0x36, + 0x00, 0x00, 0x02, 0x0b, 0x20, 0x00, 0x00, 0x00, + 0x02, 0x20, 0x92, 0x39, 0x00, 0x00, 0x03, 0x40, + 0x47, 0x60, 0x80, 0x1f, 0x00, 0x00, 0x02, 0x10, + 0x3b, 0xc0, 0x12, 0xed, 0x00, 0x01, 0x02, 0x04, + 0x66, 0x80, 0x31, 0x00, 0x00, 0x02, 0x04, 0x95, + 0x09, 0x00, 0x00, 0x02, 0x04, 0x95, 0x46, 0x00, + 0x01, 0x05, 0x0d, 0x31, 0x30, 0x36, 0x3e, 0x80, + 0x99, 0x00, 0x04, 0x06, 0x0d, 0x31, 0x30, 0x36, + 0x3e, 0xa2, 0x09, 0x00, 0x00, 0x02, 0x36, 0x3e, + 0x2c, 0x00, 0x01, 0x02, 0x36, 0x3e, 0x80, 0xdf, + 0x00, 0x01, 0x03, 0x1e, 0x1c, 0x4b, 0x00, 0x02, + 0x1c, 0x4b, 0x03, 0x00, 0x2c, 0x03, 0x1c, 0x4a, + 0x4b, 0x02, 0x00, 0x08, 0x02, 0x1c, 0x4b, 0x81, + 0x1f, 0x00, 0x1b, 0x02, 0x04, 0x1a, 0x87, 0x75, + 0x00, 0x00, 0x02, 0x53, 0x73, 0x87, 0x8d, 0x00, + 0x00, 0x02, 0x2b, 0x92, 0x00, 0x00, 0x00, 0x02, + 0x2b, 0x92, 0x36, 0x00, 0x01, 0x02, 0x2b, 0x92, + 0x8c, 0x12, 0x00, 0x01, 0x02, 0x2b, 0x92, 0x00, + 0x00, 0x00, 0x02, 0x2b, 0x92, 0xc0, 0x5c, 0x4b, + 0x00, 0x03, 0x01, 0x23, 0x96, 0x3b, 0x00, 0x11, + 0x01, 0x30, 0x9e, 0x5d, 0x00, 0x01, 0x01, 0x30, + 0xce, 0xcd, 0x2d, 0x00, +}; + +static const uint8_t unicode_prop_Hyphen_table[28] = { + 0xac, 0x80, 0xfe, 0x80, 0x44, 0xdb, 0x80, 0x52, + 0x7a, 0x80, 0x48, 0x08, 0x81, 0x4e, 0x04, 0x80, + 0x42, 0xe2, 0x80, 0x60, 0xcd, 0x66, 0x80, 0x40, + 0xa8, 0x80, 0xd6, 0x80, +}; + +static const uint8_t unicode_prop_Other_Math_table[200] = { + 0xdd, 0x80, 0x43, 0x70, 0x11, 0x80, 0x99, 0x09, + 0x81, 0x5c, 0x1f, 0x80, 0x9a, 0x82, 0x8a, 0x80, + 0x9f, 0x83, 0x97, 0x81, 0x8d, 0x81, 0xc0, 0x8c, + 0x18, 0x11, 0x1c, 0x91, 0x03, 0x01, 0x89, 0x00, + 0x14, 0x28, 0x11, 0x09, 0x02, 0x05, 0x13, 0x24, + 0xca, 0x21, 0x18, 0x08, 0x08, 0x00, 0x21, 0x0b, + 0x0b, 0x91, 0x09, 0x00, 0x06, 0x00, 0x29, 0x41, + 0x21, 0x83, 0x40, 0xa7, 0x08, 0x80, 0x97, 0x80, + 0x90, 0x80, 0x41, 0xbc, 0x81, 0x8b, 0x88, 0x24, + 0x21, 0x09, 0x14, 0x8d, 0x00, 0x01, 0x85, 0x97, + 0x81, 0xb8, 0x00, 0x80, 0x9c, 0x83, 0x88, 0x81, + 0x41, 0x55, 0x81, 0x9e, 0x89, 0x41, 0x92, 0x95, + 0xbe, 0x83, 0x9f, 0x81, 0x60, 0xd4, 0x62, 0x00, + 0x03, 0x80, 0x40, 0xd2, 0x00, 0x80, 0x60, 0xd4, + 0xc0, 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, + 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, + 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, + 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x07, 0x81, + 0xb1, 0x55, 0xff, 0x18, 0x9a, 0x01, 0x00, 0x08, + 0x80, 0x89, 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, + 0x00, 0x02, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, + 0x80, 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, +}; + +static const uint8_t unicode_prop_Other_Alphabetic_table[428] = { + 0x43, 0x44, 0x80, 0x42, 0x69, 0x8d, 0x00, 0x01, + 0x01, 0x00, 0xc7, 0x8a, 0xaf, 0x8c, 0x06, 0x8f, + 0x80, 0xe4, 0x33, 0x19, 0x0b, 0x80, 0xa2, 0x80, + 0x9d, 0x8f, 0xe5, 0x8a, 0xe4, 0x0a, 0x88, 0x02, + 0x03, 0x40, 0xa6, 0x8b, 0x16, 0x85, 0x93, 0xb5, + 0x09, 0x8e, 0x01, 0x22, 0x89, 0x81, 0x9c, 0x82, + 0xb9, 0x31, 0x09, 0x81, 0x89, 0x80, 0x89, 0x81, + 0x9c, 0x82, 0xb9, 0x23, 0x09, 0x0b, 0x80, 0x9d, + 0x0a, 0x80, 0x8a, 0x82, 0xb9, 0x38, 0x10, 0x81, + 0x94, 0x81, 0x95, 0x13, 0x82, 0xb9, 0x31, 0x09, + 0x81, 0x88, 0x81, 0x89, 0x81, 0x9d, 0x80, 0xba, + 0x22, 0x10, 0x82, 0x89, 0x80, 0xa7, 0x84, 0xb8, + 0x30, 0x10, 0x17, 0x81, 0x8a, 0x81, 0x9c, 0x82, + 0xb9, 0x30, 0x10, 0x17, 0x81, 0x8a, 0x81, 0x8e, + 0x80, 0x8b, 0x83, 0xb9, 0x30, 0x10, 0x82, 0x89, + 0x80, 0x89, 0x81, 0x9c, 0x82, 0xca, 0x28, 0x00, + 0x87, 0x91, 0x81, 0xbc, 0x01, 0x86, 0x91, 0x80, + 0xe2, 0x01, 0x28, 0x81, 0x8f, 0x80, 0x40, 0xa2, + 0x92, 0x88, 0x8a, 0x80, 0xa3, 0xed, 0x8b, 0x00, + 0x0b, 0x96, 0x1b, 0x10, 0x11, 0x32, 0x83, 0x8c, + 0x8b, 0x00, 0x89, 0x83, 0x46, 0x73, 0x81, 0x9d, + 0x81, 0x9d, 0x81, 0x9d, 0x81, 0xc1, 0x92, 0x40, + 0xbb, 0x81, 0xa1, 0x80, 0xf5, 0x8b, 0x83, 0x88, + 0x40, 0xdd, 0x84, 0xb8, 0x89, 0x81, 0x93, 0xc9, + 0x81, 0x8a, 0x82, 0xb0, 0x84, 0xaf, 0x8e, 0xbb, + 0x82, 0x9d, 0x88, 0x09, 0xb8, 0x8a, 0xb1, 0x92, + 0x41, 0xaf, 0x8d, 0x46, 0xc0, 0xb3, 0x48, 0xf5, + 0x9f, 0x60, 0x78, 0x73, 0x87, 0xa1, 0x81, 0x41, + 0x61, 0x07, 0x80, 0x96, 0x84, 0xd7, 0x81, 0xb1, + 0x8f, 0x00, 0xb8, 0x80, 0xa5, 0x84, 0x9b, 0x8b, + 0xac, 0x83, 0xaf, 0x8b, 0xa4, 0x80, 0xc2, 0x8d, + 0x8b, 0x07, 0x81, 0xac, 0x82, 0xb1, 0x00, 0x11, + 0x0c, 0x80, 0xab, 0x24, 0x80, 0x40, 0xec, 0x87, + 0x60, 0x4f, 0x32, 0x80, 0x48, 0x56, 0x84, 0x46, + 0x85, 0x10, 0x0c, 0x83, 0x43, 0x13, 0x83, 0x41, + 0x82, 0x81, 0x41, 0x52, 0x82, 0xb4, 0x8d, 0xac, + 0x81, 0x8a, 0x82, 0xac, 0x88, 0x88, 0x80, 0xbc, + 0x82, 0xa3, 0x8b, 0x91, 0x81, 0xb8, 0x82, 0xaf, + 0x8c, 0x8d, 0x81, 0xdb, 0x88, 0x08, 0x28, 0x08, + 0x40, 0x9c, 0x89, 0x96, 0x83, 0xb9, 0x31, 0x09, + 0x81, 0x89, 0x80, 0x89, 0x81, 0x40, 0xd0, 0x8c, + 0x02, 0xe9, 0x91, 0x40, 0xec, 0x31, 0x86, 0x9c, + 0x81, 0xd1, 0x8e, 0x00, 0xe9, 0x8a, 0xe6, 0x8d, + 0x41, 0x00, 0x8c, 0x40, 0xf6, 0x28, 0x09, 0x0a, + 0x00, 0x80, 0x40, 0x8d, 0x31, 0x2b, 0x80, 0x9b, + 0x89, 0xa9, 0x20, 0x83, 0x91, 0x8a, 0xad, 0x8d, + 0x41, 0x96, 0x38, 0x86, 0xd2, 0x95, 0x80, 0x8d, + 0xf9, 0x2a, 0x00, 0x08, 0x10, 0x02, 0x80, 0xc1, + 0x20, 0x08, 0x83, 0x41, 0x5b, 0x83, 0x88, 0x08, + 0x80, 0xaf, 0x32, 0x82, 0x60, 0x50, 0x0d, 0x00, + 0xb6, 0x33, 0xdc, 0x81, 0x60, 0x4c, 0xab, 0x80, + 0x60, 0x23, 0x60, 0x30, 0x90, 0x0e, 0x01, 0x04, + 0xe3, 0x80, 0x48, 0xb6, 0x80, 0x47, 0xe7, 0x99, + 0x85, 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Other_Lowercase_table[69] = { + 0x40, 0xa9, 0x80, 0x8e, 0x80, 0x41, 0xf4, 0x88, + 0x31, 0x9d, 0x84, 0xdf, 0x80, 0xb3, 0x80, 0x4d, + 0x80, 0x80, 0x4c, 0x2e, 0xbe, 0x8c, 0x80, 0xa1, + 0xa4, 0x42, 0xb0, 0x80, 0x8c, 0x80, 0x8f, 0x8c, + 0x40, 0xd2, 0x8f, 0x43, 0x4f, 0x99, 0x47, 0x91, + 0x81, 0x60, 0x7a, 0x1d, 0x81, 0x40, 0xd1, 0x80, + 0x40, 0x80, 0x12, 0x81, 0x43, 0x61, 0x83, 0x88, + 0x80, 0x60, 0x5c, 0x15, 0x01, 0x10, 0xa9, 0x80, + 0x88, 0x60, 0xd8, 0x74, 0xbd, +}; + +static const uint8_t unicode_prop_Other_Uppercase_table[15] = { + 0x60, 0x21, 0x5f, 0x8f, 0x43, 0x45, 0x99, 0x61, + 0xcc, 0x5f, 0x99, 0x85, 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Other_Grapheme_Extend_table[65] = { + 0x49, 0xbd, 0x80, 0x97, 0x80, 0x41, 0x65, 0x80, + 0x97, 0x80, 0xe5, 0x80, 0x97, 0x80, 0x40, 0xe9, + 0x80, 0x91, 0x81, 0xe6, 0x80, 0x97, 0x80, 0xf6, + 0x80, 0x8e, 0x80, 0x4d, 0x54, 0x80, 0x44, 0xd5, + 0x80, 0x50, 0x20, 0x81, 0x60, 0xcf, 0x6d, 0x81, + 0x53, 0x9d, 0x80, 0x97, 0x80, 0x41, 0x57, 0x80, + 0x8b, 0x80, 0x40, 0xf0, 0x80, 0x43, 0x7f, 0x80, + 0x60, 0xb8, 0x33, 0x07, 0x84, 0x6c, 0x2e, 0xac, + 0xdf, +}; + +static const uint8_t unicode_prop_Other_Default_Ignorable_Code_Point_table[32] = { + 0x43, 0x4e, 0x80, 0x4e, 0x0e, 0x81, 0x46, 0x52, + 0x81, 0x48, 0xae, 0x80, 0x50, 0xfd, 0x80, 0x60, + 0xce, 0x3a, 0x80, 0xce, 0x88, 0x6d, 0x00, 0x06, + 0x00, 0x9d, 0xdf, 0xff, 0x40, 0xef, 0x4e, 0x0f, +}; + +static const uint8_t unicode_prop_Other_ID_Start_table[11] = { + 0x58, 0x84, 0x81, 0x48, 0x90, 0x80, 0x94, 0x80, + 0x4f, 0x6b, 0x81, +}; + +static const uint8_t unicode_prop_Other_ID_Continue_table[12] = { + 0x40, 0xb6, 0x80, 0x42, 0xce, 0x80, 0x4f, 0xe0, + 0x88, 0x46, 0x67, 0x80, +}; + +static const uint8_t unicode_prop_Prepended_Concatenation_Mark_table[19] = { + 0x45, 0xff, 0x85, 0x40, 0xd6, 0x80, 0xb0, 0x80, + 0x41, 0x7f, 0x81, 0xcf, 0x80, 0x61, 0x07, 0xd9, + 0x80, 0x8e, 0x80, +}; + +static const uint8_t unicode_prop_XID_Start1_table[31] = { + 0x43, 0x79, 0x80, 0x4a, 0xb7, 0x80, 0xfe, 0x80, + 0x60, 0x21, 0xe6, 0x81, 0x60, 0xcb, 0xc0, 0x85, + 0x41, 0x95, 0x81, 0xf3, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x41, 0x1e, 0x81, +}; + +static const uint8_t unicode_prop_XID_Continue1_table[23] = { + 0x43, 0x79, 0x80, 0x60, 0x2d, 0x1f, 0x81, 0x60, + 0xcb, 0xc0, 0x85, 0x41, 0x95, 0x81, 0xf3, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_Titlecased1_table[22] = { + 0x41, 0xc3, 0x08, 0x08, 0x81, 0xa4, 0x81, 0x4e, + 0xdc, 0xaa, 0x0a, 0x4e, 0x87, 0x3f, 0x3f, 0x87, + 0x8b, 0x80, 0x8e, 0x80, 0xae, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_Casefolded1_table[29] = { + 0x41, 0xef, 0x80, 0x41, 0x9e, 0x80, 0x9e, 0x80, + 0x5a, 0xe4, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, + 0x80, 0xde, 0x06, 0x06, 0x80, 0x8a, 0x09, 0x81, + 0x89, 0x10, 0x81, 0x8d, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_NFKC_Casefolded1_table[447] = { + 0x40, 0x9f, 0x06, 0x00, 0x01, 0x00, 0x01, 0x12, + 0x10, 0x82, 0xf3, 0x80, 0x8b, 0x80, 0x40, 0x84, + 0x01, 0x01, 0x80, 0xa2, 0x01, 0x80, 0x40, 0xbb, + 0x88, 0x9e, 0x29, 0x84, 0xda, 0x08, 0x81, 0x89, + 0x80, 0xa3, 0x04, 0x02, 0x04, 0x08, 0x07, 0x80, + 0x9e, 0x80, 0xa0, 0x82, 0x9c, 0x80, 0x42, 0x28, + 0x80, 0xd7, 0x83, 0x42, 0xde, 0x87, 0xfb, 0x08, + 0x80, 0xd2, 0x01, 0x80, 0xa1, 0x11, 0x80, 0x40, + 0xfc, 0x81, 0x42, 0xd4, 0x80, 0xfe, 0x80, 0xa7, + 0x81, 0xad, 0x80, 0xb5, 0x80, 0x88, 0x03, 0x03, + 0x03, 0x80, 0x8b, 0x80, 0x88, 0x00, 0x26, 0x80, + 0x90, 0x80, 0x88, 0x03, 0x03, 0x03, 0x80, 0x8b, + 0x80, 0x41, 0x41, 0x80, 0xe1, 0x81, 0x46, 0x52, + 0x81, 0xd4, 0x84, 0x45, 0x1b, 0x10, 0x8a, 0x80, + 0x91, 0x80, 0x9b, 0x8c, 0x80, 0xa1, 0xa4, 0x40, + 0xd5, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, 0x80, + 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0xb7, 0x05, 0x00, 0x13, 0x05, 0x11, 0x02, 0x0c, + 0x11, 0x00, 0x00, 0x0c, 0x15, 0x05, 0x08, 0x8f, + 0x00, 0x20, 0x8b, 0x12, 0x2a, 0x08, 0x0b, 0x00, + 0x07, 0x82, 0x8c, 0x06, 0x92, 0x81, 0x9a, 0x80, + 0x8c, 0x8a, 0x80, 0xd6, 0x18, 0x10, 0x8a, 0x01, + 0x0c, 0x0a, 0x00, 0x10, 0x11, 0x02, 0x06, 0x05, + 0x1c, 0x85, 0x8f, 0x8f, 0x8f, 0x88, 0x80, 0x40, + 0xa1, 0x08, 0x81, 0x40, 0xf7, 0x81, 0x41, 0x34, + 0xd5, 0x99, 0x9a, 0x45, 0x20, 0x80, 0xe6, 0x82, + 0xe4, 0x80, 0x41, 0x9e, 0x81, 0x40, 0xf0, 0x80, + 0x41, 0x2e, 0x80, 0xd2, 0x80, 0x8b, 0x40, 0xd5, + 0xa9, 0x80, 0xb4, 0x00, 0x82, 0xdf, 0x09, 0x80, + 0xde, 0x80, 0xb0, 0xdd, 0x82, 0x8d, 0xdf, 0x9e, + 0x80, 0xa7, 0x87, 0xae, 0x80, 0x41, 0x7f, 0x60, + 0x72, 0x9b, 0x81, 0x40, 0xd1, 0x80, 0x40, 0x80, + 0x12, 0x81, 0x43, 0x61, 0x83, 0x88, 0x80, 0x60, + 0x4d, 0x95, 0x41, 0x0d, 0x08, 0x00, 0x81, 0x89, + 0x00, 0x00, 0x09, 0x82, 0xc3, 0x81, 0xe9, 0xc2, + 0x00, 0x97, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, + 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, + 0x8c, 0x82, 0x99, 0x95, 0x94, 0x81, 0x8b, 0x80, + 0x92, 0x03, 0x1a, 0x00, 0x80, 0x40, 0x86, 0x08, + 0x80, 0x9f, 0x99, 0x40, 0x83, 0x15, 0x0d, 0x0d, + 0x0a, 0x16, 0x06, 0x80, 0x88, 0x47, 0x87, 0x20, + 0xa9, 0x80, 0x88, 0x60, 0xb4, 0xe4, 0x83, 0x54, + 0xb9, 0x86, 0x8d, 0x87, 0xbf, 0x85, 0x42, 0x3e, + 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, 0x80, + 0x8b, 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, 0x06, + 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, 0x41, + 0x53, 0x81, 0x41, 0x23, 0x81, 0xb1, 0x48, 0x2f, + 0xbd, 0x4d, 0x91, 0x18, 0x9a, 0x01, 0x00, 0x08, + 0x80, 0x89, 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, + 0x00, 0x02, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, + 0x80, 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, + 0x42, 0x43, 0x8a, 0x84, 0x9e, 0x80, 0x9f, 0x99, + 0x82, 0xa2, 0x80, 0xee, 0x82, 0x8c, 0xab, 0x83, + 0x88, 0x31, 0x49, 0x9d, 0x89, 0x60, 0xfc, 0x05, + 0x42, 0x1d, 0x6b, 0x05, 0xe1, 0x4f, 0xff, +}; + +static const uint8_t unicode_prop_ASCII_Hex_Digit_table[5] = { + 0xaf, 0x89, 0x35, 0x99, 0x85, +}; + +static const uint8_t unicode_prop_Bidi_Control_table[10] = { + 0x46, 0x1b, 0x80, 0x59, 0xf0, 0x81, 0x99, 0x84, + 0xb6, 0x83, +}; + +static const uint8_t unicode_prop_Dash_table[55] = { + 0xac, 0x80, 0x45, 0x5b, 0x80, 0xb2, 0x80, 0x4e, + 0x40, 0x80, 0x44, 0x04, 0x80, 0x48, 0x08, 0x85, + 0xbc, 0x80, 0xa6, 0x80, 0x8e, 0x80, 0x41, 0x85, + 0x80, 0x4c, 0x03, 0x01, 0x80, 0x9e, 0x0b, 0x80, + 0x9b, 0x80, 0x41, 0xbd, 0x80, 0x92, 0x80, 0xee, + 0x80, 0x60, 0xcd, 0x8f, 0x81, 0xa4, 0x80, 0x89, + 0x80, 0x40, 0xa8, 0x80, 0x4f, 0x9e, 0x80, +}; + +static const uint8_t unicode_prop_Deprecated_table[23] = { + 0x41, 0x48, 0x80, 0x45, 0x28, 0x80, 0x49, 0x02, + 0x00, 0x80, 0x48, 0x28, 0x81, 0x48, 0xc4, 0x85, + 0x42, 0xb8, 0x81, 0x6d, 0xdc, 0xd5, 0x80, +}; + +static const uint8_t unicode_prop_Diacritic_table[399] = { + 0xdd, 0x00, 0x80, 0xc6, 0x05, 0x03, 0x01, 0x81, + 0x41, 0xf6, 0x40, 0x9e, 0x07, 0x25, 0x90, 0x0b, + 0x80, 0x88, 0x81, 0x40, 0xfc, 0x84, 0x40, 0xd0, + 0x80, 0xb6, 0x90, 0x80, 0x9a, 0x00, 0x01, 0x00, + 0x40, 0x85, 0x3b, 0x81, 0x40, 0x85, 0x0b, 0x0a, + 0x82, 0xc2, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0xa1, + 0x81, 0xfd, 0x87, 0xa8, 0x89, 0x8f, 0x9b, 0xbc, + 0x80, 0x8f, 0x02, 0x83, 0x9b, 0x80, 0xc9, 0x80, + 0x8f, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, 0x80, + 0x8f, 0x80, 0xae, 0x82, 0xbb, 0x80, 0x8f, 0x06, + 0x80, 0xf6, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, + 0x80, 0x8f, 0x80, 0xec, 0x81, 0x8f, 0x80, 0xfb, + 0x80, 0xfb, 0x28, 0x80, 0xea, 0x80, 0x8c, 0x84, + 0xca, 0x81, 0x9a, 0x00, 0x00, 0x03, 0x81, 0xc1, + 0x10, 0x81, 0xbd, 0x80, 0xef, 0x00, 0x81, 0xa7, + 0x0b, 0x84, 0x98, 0x30, 0x80, 0x89, 0x81, 0x42, + 0xc0, 0x82, 0x43, 0xb3, 0x81, 0x40, 0xb2, 0x8a, + 0x88, 0x80, 0x41, 0x5a, 0x82, 0x41, 0x38, 0x39, + 0x80, 0xaf, 0x8e, 0x81, 0x8a, 0xe7, 0x80, 0x8e, + 0x80, 0xa5, 0x88, 0xb5, 0x81, 0x40, 0x89, 0x81, + 0xbf, 0x85, 0xd1, 0x98, 0x18, 0x28, 0x0a, 0xb1, + 0xbe, 0xd8, 0x8b, 0xa4, 0x8a, 0x41, 0xbc, 0x00, + 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, 0x82, 0x8c, + 0x81, 0x4c, 0xef, 0x82, 0x41, 0x3c, 0x80, 0x41, + 0xf9, 0x85, 0xe8, 0x83, 0xde, 0x80, 0x60, 0x75, + 0x71, 0x80, 0x8b, 0x08, 0x80, 0x9b, 0x81, 0xd1, + 0x81, 0x8d, 0xa1, 0xe5, 0x82, 0xec, 0x81, 0x40, + 0xc9, 0x80, 0x9a, 0x91, 0xb8, 0x83, 0xa3, 0x80, + 0xde, 0x80, 0x8b, 0x80, 0xa3, 0x80, 0x40, 0x94, + 0x82, 0xc0, 0x83, 0xb2, 0x80, 0xe3, 0x84, 0x88, + 0x82, 0xff, 0x81, 0x60, 0x4f, 0x2f, 0x80, 0x43, + 0x00, 0x8f, 0x41, 0x0d, 0x00, 0x80, 0xae, 0x80, + 0xac, 0x81, 0xc2, 0x80, 0x42, 0xfb, 0x80, 0x44, + 0x9e, 0x28, 0xa9, 0x80, 0x88, 0x43, 0x29, 0x81, + 0x42, 0x3a, 0x85, 0x41, 0xd4, 0x82, 0xc5, 0x8a, + 0xb0, 0x83, 0x40, 0xbf, 0x80, 0xa8, 0x80, 0xc7, + 0x81, 0xf7, 0x81, 0xbd, 0x80, 0xcb, 0x80, 0x88, + 0x82, 0xe7, 0x81, 0x40, 0xb1, 0x81, 0xd0, 0x80, + 0x8f, 0x80, 0x97, 0x32, 0x84, 0x40, 0xcc, 0x02, + 0x80, 0xfa, 0x81, 0x40, 0xfa, 0x81, 0xfd, 0x80, + 0xf5, 0x81, 0xf2, 0x80, 0x41, 0x0c, 0x81, 0x41, + 0x01, 0x0b, 0x80, 0x40, 0x9b, 0x80, 0xd2, 0x80, + 0x91, 0x80, 0xd0, 0x80, 0x41, 0xa4, 0x80, 0x41, + 0x01, 0x00, 0x81, 0xd0, 0x80, 0x56, 0xae, 0x8e, + 0x60, 0x36, 0x99, 0x84, 0xba, 0x86, 0x44, 0x57, + 0x90, 0xcf, 0x81, 0x60, 0x3f, 0xfd, 0x18, 0x30, + 0x81, 0x5f, 0x00, 0xad, 0x81, 0x96, 0x42, 0x1f, + 0x12, 0x2f, 0x39, 0x86, 0x9d, 0x83, 0x4e, 0x81, + 0xbd, 0x40, 0xc1, 0x86, 0x41, 0x76, 0x80, 0xbc, + 0x83, 0x45, 0xdf, 0x86, 0xec, 0x10, 0x82, +}; + +static const uint8_t unicode_prop_Extender_table[92] = { + 0x40, 0xb6, 0x80, 0x42, 0x17, 0x81, 0x43, 0x6d, + 0x80, 0x41, 0xb8, 0x80, 0x43, 0x59, 0x80, 0x42, + 0xef, 0x80, 0xfe, 0x80, 0x49, 0x42, 0x80, 0xb7, + 0x80, 0x42, 0x62, 0x80, 0x41, 0x8d, 0x80, 0xc3, + 0x80, 0x53, 0x88, 0x80, 0xaa, 0x84, 0xe6, 0x81, + 0xdc, 0x82, 0x60, 0x6f, 0x15, 0x80, 0x45, 0xf5, + 0x80, 0x43, 0xc1, 0x80, 0x95, 0x80, 0x40, 0x88, + 0x80, 0xeb, 0x80, 0x94, 0x81, 0x60, 0x54, 0x7a, + 0x80, 0x48, 0x0f, 0x81, 0x4b, 0xd9, 0x80, 0x42, + 0x67, 0x82, 0x44, 0xce, 0x80, 0x60, 0x50, 0xa8, + 0x81, 0x44, 0x9b, 0x08, 0x80, 0x60, 0x71, 0x57, + 0x81, 0x48, 0x05, 0x82, +}; + +static const uint8_t unicode_prop_Hex_Digit_table[12] = { + 0xaf, 0x89, 0x35, 0x99, 0x85, 0x60, 0xfe, 0xa8, + 0x89, 0x35, 0x99, 0x85, +}; + +static const uint8_t unicode_prop_IDS_Binary_Operator_table[5] = { + 0x60, 0x2f, 0xef, 0x09, 0x87, +}; + +static const uint8_t unicode_prop_IDS_Trinary_Operator_table[4] = { + 0x60, 0x2f, 0xf1, 0x81, +}; + +static const uint8_t unicode_prop_Ideographic_table[69] = { + 0x60, 0x30, 0x05, 0x81, 0x98, 0x88, 0x8d, 0x82, + 0x43, 0xc4, 0x59, 0xbf, 0xbf, 0x60, 0x51, 0xff, + 0x60, 0x58, 0xff, 0x41, 0x6d, 0x81, 0xe9, 0x60, + 0x75, 0x09, 0x80, 0x9a, 0x57, 0xf7, 0x87, 0x44, + 0xd5, 0xa9, 0x88, 0x60, 0x24, 0x66, 0x41, 0x8b, + 0x60, 0x4d, 0x03, 0x60, 0xa6, 0xdf, 0x9f, 0x50, + 0x39, 0x85, 0x40, 0xdd, 0x81, 0x56, 0x81, 0x8d, + 0x5d, 0x30, 0x4c, 0x1e, 0x42, 0x1d, 0x45, 0xe1, + 0x53, 0x4a, 0x84, 0x50, 0x5f, +}; + +static const uint8_t unicode_prop_Join_Control_table[4] = { + 0x60, 0x20, 0x0b, 0x81, +}; + +static const uint8_t unicode_prop_Logical_Order_Exception_table[15] = { + 0x4e, 0x3f, 0x84, 0xfa, 0x84, 0x4a, 0xef, 0x11, + 0x80, 0x60, 0x90, 0xf9, 0x09, 0x00, 0x81, +}; + +static const uint8_t unicode_prop_Noncharacter_Code_Point_table[71] = { + 0x60, 0xfd, 0xcf, 0x9f, 0x42, 0x0d, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, +}; + +static const uint8_t unicode_prop_Pattern_Syntax_table[58] = { + 0xa0, 0x8e, 0x89, 0x86, 0x99, 0x18, 0x80, 0x99, + 0x83, 0xa1, 0x30, 0x00, 0x08, 0x00, 0x0b, 0x03, + 0x02, 0x80, 0x96, 0x80, 0x9e, 0x80, 0x5f, 0x17, + 0x97, 0x87, 0x8e, 0x81, 0x92, 0x80, 0x89, 0x41, + 0x30, 0x42, 0xcf, 0x40, 0x9f, 0x42, 0x75, 0x9d, + 0x44, 0x6b, 0x41, 0xff, 0xff, 0x41, 0x80, 0x13, + 0x98, 0x8e, 0x80, 0x60, 0xcd, 0x0c, 0x81, 0x41, + 0x04, 0x81, +}; + +static const uint8_t unicode_prop_Pattern_White_Space_table[11] = { + 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x5f, 0x87, + 0x81, 0x97, 0x81, +}; + +static const uint8_t unicode_prop_Quotation_Mark_table[31] = { + 0xa1, 0x03, 0x80, 0x40, 0x82, 0x80, 0x8e, 0x80, + 0x5f, 0x5b, 0x87, 0x98, 0x81, 0x4e, 0x06, 0x80, + 0x41, 0xc8, 0x83, 0x8c, 0x82, 0x60, 0xce, 0x20, + 0x83, 0x40, 0xbc, 0x03, 0x80, 0xd9, 0x81, +}; + +static const uint8_t unicode_prop_Radical_table[9] = { + 0x60, 0x2e, 0x7f, 0x99, 0x80, 0xd8, 0x8b, 0x40, + 0xd5, +}; + +static const uint8_t unicode_prop_Regional_Indicator_table[4] = { + 0x61, 0xf1, 0xe5, 0x99, +}; + +static const uint8_t unicode_prop_Sentence_Terminal_table[196] = { + 0xa0, 0x80, 0x8b, 0x80, 0x8f, 0x80, 0x45, 0x48, + 0x80, 0x40, 0x92, 0x82, 0x40, 0xb3, 0x80, 0xaa, + 0x82, 0x40, 0xf5, 0x80, 0xbc, 0x00, 0x02, 0x81, + 0x41, 0x24, 0x81, 0x46, 0xe3, 0x81, 0x43, 0x15, + 0x03, 0x81, 0x43, 0x04, 0x80, 0x40, 0xc5, 0x81, + 0x40, 0xcb, 0x04, 0x80, 0x41, 0x39, 0x81, 0x41, + 0x61, 0x83, 0x40, 0xad, 0x09, 0x81, 0x9c, 0x81, + 0x40, 0xbb, 0x81, 0xc0, 0x81, 0x43, 0xbb, 0x81, + 0x88, 0x82, 0x4d, 0xe3, 0x80, 0x8c, 0x80, 0x95, + 0x81, 0x41, 0xac, 0x80, 0x60, 0x74, 0xfb, 0x80, + 0x41, 0x0d, 0x81, 0x40, 0xe2, 0x02, 0x80, 0x41, + 0x7d, 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, 0x97, + 0x81, 0x40, 0x92, 0x82, 0x40, 0x8f, 0x81, 0x40, + 0xf8, 0x80, 0x60, 0x52, 0x65, 0x02, 0x81, 0x40, + 0xa8, 0x80, 0x8b, 0x80, 0x8f, 0x80, 0xc0, 0x80, + 0x4a, 0xf3, 0x81, 0x44, 0xfc, 0x84, 0xab, 0x83, + 0x40, 0xbc, 0x81, 0xf4, 0x83, 0xfe, 0x82, 0x40, + 0x80, 0x0d, 0x80, 0x8f, 0x81, 0xd7, 0x08, 0x81, + 0xeb, 0x80, 0x41, 0xa0, 0x81, 0x41, 0x74, 0x0c, + 0x8e, 0xe8, 0x81, 0x40, 0xf8, 0x82, 0x42, 0x04, + 0x00, 0x80, 0x40, 0xfa, 0x81, 0xd6, 0x81, 0x41, + 0xa3, 0x81, 0x42, 0xb3, 0x81, 0xc9, 0x81, 0x60, + 0x4b, 0x28, 0x81, 0x40, 0x84, 0x80, 0xc0, 0x81, + 0x8a, 0x80, 0x43, 0x52, 0x80, 0x60, 0x4e, 0x05, + 0x80, 0x5d, 0xe7, 0x80, +}; + +static const uint8_t unicode_prop_Soft_Dotted_table[79] = { + 0xe8, 0x81, 0x40, 0xc3, 0x80, 0x41, 0x18, 0x80, + 0x9d, 0x80, 0xb3, 0x80, 0x93, 0x80, 0x41, 0x3f, + 0x80, 0xe1, 0x00, 0x80, 0x59, 0x08, 0x80, 0xb2, + 0x80, 0x8c, 0x02, 0x80, 0x40, 0x83, 0x80, 0x40, + 0x9c, 0x80, 0x41, 0xa4, 0x80, 0x40, 0xd5, 0x81, + 0x4b, 0x31, 0x80, 0x61, 0xa7, 0xa4, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0x48, + 0x85, 0x80, 0x41, 0x30, 0x81, 0x99, 0x80, +}; + +static const uint8_t unicode_prop_Terminal_Punctuation_table[248] = { + 0xa0, 0x80, 0x89, 0x00, 0x80, 0x8a, 0x0a, 0x80, + 0x43, 0x3d, 0x07, 0x80, 0x42, 0x00, 0x80, 0xb8, + 0x80, 0xc7, 0x80, 0x8d, 0x00, 0x82, 0x40, 0xb3, + 0x80, 0xaa, 0x8a, 0x00, 0x40, 0xea, 0x81, 0xb5, + 0x8e, 0x9e, 0x80, 0x41, 0x04, 0x81, 0x44, 0xf3, + 0x81, 0x40, 0xab, 0x03, 0x85, 0x41, 0x36, 0x81, + 0x43, 0x14, 0x87, 0x43, 0x04, 0x80, 0xfb, 0x82, + 0xc6, 0x81, 0x40, 0x9c, 0x12, 0x80, 0xa6, 0x19, + 0x81, 0x41, 0x39, 0x81, 0x41, 0x61, 0x83, 0x40, + 0xad, 0x08, 0x82, 0x9c, 0x81, 0x40, 0xbb, 0x84, + 0xbd, 0x81, 0x43, 0xbb, 0x81, 0x88, 0x82, 0x4d, + 0xe3, 0x80, 0x8c, 0x03, 0x80, 0x89, 0x00, 0x0a, + 0x81, 0x41, 0xab, 0x81, 0x60, 0x74, 0xfa, 0x81, + 0x41, 0x0c, 0x82, 0x40, 0xe2, 0x84, 0x41, 0x7d, + 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, 0x96, 0x82, + 0x40, 0x92, 0x82, 0xfe, 0x80, 0x8f, 0x81, 0x40, + 0xf8, 0x80, 0x60, 0x52, 0x63, 0x10, 0x83, 0x40, + 0xa8, 0x80, 0x89, 0x00, 0x80, 0x8a, 0x0a, 0x80, + 0xc0, 0x01, 0x80, 0x44, 0x39, 0x80, 0xaf, 0x80, + 0x44, 0x85, 0x80, 0x40, 0xc6, 0x80, 0x41, 0x35, + 0x81, 0x40, 0x97, 0x85, 0xc3, 0x85, 0xd8, 0x83, + 0x43, 0xb7, 0x84, 0xab, 0x83, 0x40, 0xbc, 0x86, + 0xef, 0x83, 0xfe, 0x82, 0x40, 0x80, 0x0d, 0x80, + 0x8f, 0x81, 0xd7, 0x84, 0xeb, 0x80, 0x41, 0xa0, + 0x82, 0x8b, 0x81, 0x41, 0x65, 0x1a, 0x8e, 0xe8, + 0x81, 0x40, 0xf8, 0x82, 0x42, 0x04, 0x00, 0x80, + 0x40, 0xfa, 0x81, 0xd6, 0x0b, 0x81, 0x41, 0x9d, + 0x82, 0xac, 0x80, 0x42, 0x84, 0x81, 0xc9, 0x81, + 0x45, 0x2a, 0x84, 0x60, 0x45, 0xf8, 0x81, 0x40, + 0x84, 0x80, 0xc0, 0x82, 0x89, 0x80, 0x43, 0x51, + 0x81, 0x60, 0x4e, 0x05, 0x80, 0x5d, 0xe6, 0x83, +}; + +static const uint8_t unicode_prop_Unified_Ideograph_table[45] = { + 0x60, 0x33, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x51, + 0xff, 0x60, 0x5a, 0x0d, 0x08, 0x00, 0x81, 0x89, + 0x00, 0x00, 0x09, 0x82, 0x61, 0x05, 0xd5, 0x60, + 0xa6, 0xdf, 0x9f, 0x50, 0x39, 0x85, 0x40, 0xdd, + 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x54, 0x1e, + 0x53, 0x4a, 0x84, 0x50, 0x5f, +}; + +static const uint8_t unicode_prop_Variation_Selector_table[13] = { + 0x58, 0x0a, 0x10, 0x80, 0x60, 0xe5, 0xef, 0x8f, + 0x6d, 0x02, 0xef, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_White_Space_table[22] = { + 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x99, 0x80, + 0x55, 0xde, 0x80, 0x49, 0x7e, 0x8a, 0x9c, 0x0c, + 0x80, 0xae, 0x80, 0x4f, 0x9f, 0x80, +}; + +static const uint8_t unicode_prop_Bidi_Mirrored_table[173] = { + 0xa7, 0x81, 0x91, 0x00, 0x80, 0x9b, 0x00, 0x80, + 0x9c, 0x00, 0x80, 0xac, 0x80, 0x8e, 0x80, 0x4e, + 0x7d, 0x83, 0x47, 0x5c, 0x81, 0x49, 0x9b, 0x81, + 0x89, 0x81, 0xb5, 0x81, 0x8d, 0x81, 0x40, 0xb0, + 0x80, 0x40, 0xbf, 0x1a, 0x2a, 0x02, 0x0a, 0x18, + 0x18, 0x00, 0x03, 0x88, 0x20, 0x80, 0x91, 0x23, + 0x88, 0x08, 0x00, 0x39, 0x9e, 0x0b, 0x20, 0x88, + 0x09, 0x92, 0x21, 0x88, 0x21, 0x0b, 0x97, 0x81, + 0x8f, 0x3b, 0x93, 0x0e, 0x81, 0x44, 0x3c, 0x8d, + 0xc9, 0x01, 0x18, 0x08, 0x14, 0x1c, 0x12, 0x8d, + 0x41, 0x92, 0x95, 0x0d, 0x80, 0x8d, 0x38, 0x35, + 0x10, 0x1c, 0x01, 0x0c, 0x18, 0x02, 0x09, 0x89, + 0x29, 0x81, 0x8b, 0x92, 0x03, 0x08, 0x00, 0x08, + 0x03, 0x21, 0x2a, 0x97, 0x81, 0x8a, 0x0b, 0x18, + 0x09, 0x0b, 0xaa, 0x0f, 0x80, 0xa7, 0x20, 0x00, + 0x14, 0x22, 0x18, 0x14, 0x00, 0x40, 0xff, 0x80, + 0x42, 0x02, 0x1a, 0x08, 0x81, 0x8d, 0x09, 0x89, + 0xaa, 0x87, 0x41, 0xaa, 0x89, 0x0f, 0x60, 0xce, + 0x3c, 0x2c, 0x81, 0x40, 0xa1, 0x81, 0x91, 0x00, + 0x80, 0x9b, 0x00, 0x80, 0x9c, 0x00, 0x00, 0x08, + 0x81, 0x60, 0xd7, 0x76, 0x80, 0xb8, 0x80, 0xb8, + 0x80, 0xb8, 0x80, 0xb8, 0x80, +}; + +static const uint8_t unicode_prop_Emoji_table[239] = { + 0xa2, 0x05, 0x04, 0x89, 0xee, 0x03, 0x80, 0x5f, + 0x8c, 0x80, 0x8b, 0x80, 0x40, 0xd7, 0x80, 0x95, + 0x80, 0xd9, 0x85, 0x8e, 0x81, 0x41, 0x6e, 0x81, + 0x8b, 0x80, 0x40, 0xa5, 0x80, 0x98, 0x8a, 0x1a, + 0x40, 0xc6, 0x80, 0x40, 0xe6, 0x81, 0x89, 0x80, + 0x88, 0x80, 0xb9, 0x18, 0x84, 0x88, 0x01, 0x01, + 0x09, 0x03, 0x01, 0x00, 0x09, 0x02, 0x02, 0x0f, + 0x14, 0x00, 0x04, 0x8b, 0x8a, 0x09, 0x00, 0x08, + 0x80, 0x91, 0x01, 0x81, 0x91, 0x28, 0x00, 0x0a, + 0x0c, 0x01, 0x0b, 0x81, 0x8a, 0x0c, 0x09, 0x04, + 0x08, 0x00, 0x81, 0x93, 0x0c, 0x28, 0x19, 0x03, + 0x01, 0x01, 0x28, 0x01, 0x00, 0x00, 0x05, 0x02, + 0x05, 0x80, 0x89, 0x81, 0x8e, 0x01, 0x03, 0x00, + 0x03, 0x10, 0x80, 0x8a, 0x81, 0xaf, 0x82, 0x88, + 0x80, 0x8d, 0x80, 0x8d, 0x80, 0x41, 0x73, 0x81, + 0x41, 0xce, 0x82, 0x92, 0x81, 0xb2, 0x03, 0x80, + 0x44, 0xd9, 0x80, 0x8b, 0x80, 0x42, 0x58, 0x00, + 0x80, 0x61, 0xbd, 0x69, 0x80, 0x40, 0xc9, 0x80, + 0x40, 0x9f, 0x81, 0x8b, 0x81, 0x8d, 0x01, 0x89, + 0xca, 0x99, 0x01, 0x96, 0x80, 0x93, 0x01, 0x88, + 0x94, 0x81, 0x40, 0xad, 0xa1, 0x81, 0xef, 0x09, + 0x02, 0x81, 0xd2, 0x0a, 0x80, 0x41, 0x06, 0x80, + 0xbe, 0x8a, 0x28, 0x97, 0x31, 0x0f, 0x8b, 0x01, + 0x19, 0x03, 0x81, 0x8c, 0x09, 0x07, 0x81, 0x88, + 0x04, 0x82, 0x8b, 0x17, 0x11, 0x00, 0x03, 0x05, + 0x02, 0x05, 0xd5, 0xaf, 0xc5, 0x27, 0x0a, 0x83, + 0x89, 0x10, 0x01, 0x10, 0x81, 0x89, 0x40, 0xe2, + 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, 0x89, 0x80, + 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x88, 0x86, 0xad, + 0x06, 0x87, 0x8d, 0x83, 0x88, 0x86, 0x88, +}; + +static const uint8_t unicode_prop_Emoji_Component_table[28] = { + 0xa2, 0x05, 0x04, 0x89, 0x5f, 0xd2, 0x80, 0x40, + 0xd4, 0x80, 0x60, 0xdd, 0x2a, 0x80, 0x60, 0xf3, + 0xd5, 0x99, 0x41, 0xfa, 0x84, 0x45, 0xaf, 0x83, + 0x6c, 0x06, 0x6b, 0xdf, +}; + +static const uint8_t unicode_prop_Emoji_Modifier_table[4] = { + 0x61, 0xf3, 0xfa, 0x84, +}; + +static const uint8_t unicode_prop_Emoji_Modifier_Base_table[71] = { + 0x60, 0x26, 0x1c, 0x80, 0x40, 0xda, 0x80, 0x8f, + 0x83, 0x61, 0xcc, 0x76, 0x80, 0xbb, 0x11, 0x01, + 0x82, 0xf4, 0x09, 0x8a, 0x94, 0x92, 0x10, 0x1a, + 0x02, 0x30, 0x00, 0x97, 0x80, 0x40, 0xc8, 0x0b, + 0x80, 0x94, 0x03, 0x81, 0x40, 0xad, 0x12, 0x84, + 0xd2, 0x80, 0x8f, 0x82, 0x88, 0x80, 0x8a, 0x80, + 0x42, 0x3e, 0x01, 0x07, 0x3d, 0x80, 0x88, 0x89, + 0x0a, 0xb7, 0x80, 0xbc, 0x08, 0x08, 0x80, 0x90, + 0x10, 0x8c, 0x40, 0xe4, 0x82, 0xa9, 0x88, +}; + +static const uint8_t unicode_prop_Emoji_Presentation_table[145] = { + 0x60, 0x23, 0x19, 0x81, 0x40, 0xcc, 0x1a, 0x01, + 0x80, 0x42, 0x08, 0x81, 0x94, 0x81, 0xb1, 0x8b, + 0xaa, 0x80, 0x92, 0x80, 0x8c, 0x07, 0x81, 0x90, + 0x0c, 0x0f, 0x04, 0x80, 0x94, 0x06, 0x08, 0x03, + 0x01, 0x06, 0x03, 0x81, 0x9b, 0x80, 0xa2, 0x00, + 0x03, 0x10, 0x80, 0xbc, 0x82, 0x97, 0x80, 0x8d, + 0x80, 0x43, 0x5a, 0x81, 0xb2, 0x03, 0x80, 0x61, + 0xc4, 0xad, 0x80, 0x40, 0xc9, 0x80, 0x40, 0xbd, + 0x01, 0x89, 0xca, 0x99, 0x00, 0x97, 0x80, 0x93, + 0x01, 0x20, 0x82, 0x94, 0x81, 0x40, 0xad, 0xa0, + 0x8b, 0x88, 0x80, 0xc5, 0x80, 0x95, 0x8b, 0xaa, + 0x1c, 0x8b, 0x90, 0x10, 0x82, 0xc6, 0x00, 0x80, + 0x40, 0xba, 0x81, 0xbe, 0x8c, 0x18, 0x97, 0x91, + 0x80, 0x99, 0x81, 0x8c, 0x80, 0xd5, 0xd4, 0xaf, + 0xc5, 0x28, 0x12, 0x0a, 0x1b, 0x8a, 0x0e, 0x88, + 0x40, 0xe2, 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, + 0x89, 0x80, 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x88, + 0x86, 0xad, 0x06, 0x87, 0x8d, 0x83, 0x88, 0x86, + 0x88, +}; + +static const uint8_t unicode_prop_Extended_Pictographic_table[156] = { + 0x40, 0xa8, 0x03, 0x80, 0x5f, 0x8c, 0x80, 0x8b, + 0x80, 0x40, 0xd7, 0x80, 0x95, 0x80, 0xd9, 0x85, + 0x8e, 0x81, 0x41, 0x6e, 0x81, 0x8b, 0x80, 0xde, + 0x80, 0xc5, 0x80, 0x98, 0x8a, 0x1a, 0x40, 0xc6, + 0x80, 0x40, 0xe6, 0x81, 0x89, 0x80, 0x88, 0x80, + 0xb9, 0x18, 0x28, 0x8b, 0x80, 0xf1, 0x89, 0xf5, + 0x81, 0x8a, 0x00, 0x00, 0x28, 0x10, 0x28, 0x89, + 0x81, 0x8e, 0x01, 0x03, 0x00, 0x03, 0x10, 0x80, + 0x8a, 0x84, 0xac, 0x82, 0x88, 0x80, 0x8d, 0x80, + 0x8d, 0x80, 0x41, 0x73, 0x81, 0x41, 0xce, 0x82, + 0x92, 0x81, 0xb2, 0x03, 0x80, 0x44, 0xd9, 0x80, + 0x8b, 0x80, 0x42, 0x58, 0x00, 0x80, 0x61, 0xbd, + 0x65, 0x40, 0xff, 0x8c, 0x82, 0x9e, 0x80, 0xbb, + 0x85, 0x8b, 0x81, 0x8d, 0x01, 0x89, 0x91, 0xb8, + 0x9a, 0x8e, 0x89, 0x80, 0x93, 0x01, 0x88, 0x03, + 0x88, 0x41, 0xb1, 0x84, 0x41, 0x3d, 0x87, 0x41, + 0x09, 0xaf, 0xff, 0xf3, 0x8b, 0xd4, 0xaa, 0x8b, + 0x83, 0xb7, 0x87, 0x89, 0x85, 0xa7, 0x87, 0x9d, + 0xd1, 0x8b, 0xae, 0x80, 0x89, 0x80, 0x41, 0xb8, + 0x40, 0xff, 0x43, 0xfd, +}; + +static const uint8_t unicode_prop_Default_Ignorable_Code_Point_table[51] = { + 0x40, 0xac, 0x80, 0x42, 0xa0, 0x80, 0x42, 0xcb, + 0x80, 0x4b, 0x41, 0x81, 0x46, 0x52, 0x81, 0xd4, + 0x84, 0x47, 0xfa, 0x84, 0x99, 0x84, 0xb0, 0x8f, + 0x50, 0xf3, 0x80, 0x60, 0xcc, 0x9a, 0x8f, 0x40, + 0xee, 0x80, 0x40, 0x9f, 0x80, 0xce, 0x88, 0x60, + 0xbc, 0xa6, 0x83, 0x54, 0xce, 0x87, 0x6c, 0x2e, + 0x84, 0x4f, 0xff, +}; + +typedef enum { + UNICODE_PROP_Hyphen, + UNICODE_PROP_Other_Math, + UNICODE_PROP_Other_Alphabetic, + UNICODE_PROP_Other_Lowercase, + UNICODE_PROP_Other_Uppercase, + UNICODE_PROP_Other_Grapheme_Extend, + UNICODE_PROP_Other_Default_Ignorable_Code_Point, + UNICODE_PROP_Other_ID_Start, + UNICODE_PROP_Other_ID_Continue, + UNICODE_PROP_Prepended_Concatenation_Mark, + UNICODE_PROP_ID_Continue1, + UNICODE_PROP_XID_Start1, + UNICODE_PROP_XID_Continue1, + UNICODE_PROP_Changes_When_Titlecased1, + UNICODE_PROP_Changes_When_Casefolded1, + UNICODE_PROP_Changes_When_NFKC_Casefolded1, + UNICODE_PROP_ASCII_Hex_Digit, + UNICODE_PROP_Bidi_Control, + UNICODE_PROP_Dash, + UNICODE_PROP_Deprecated, + UNICODE_PROP_Diacritic, + UNICODE_PROP_Extender, + UNICODE_PROP_Hex_Digit, + UNICODE_PROP_IDS_Binary_Operator, + UNICODE_PROP_IDS_Trinary_Operator, + UNICODE_PROP_Ideographic, + UNICODE_PROP_Join_Control, + UNICODE_PROP_Logical_Order_Exception, + UNICODE_PROP_Noncharacter_Code_Point, + UNICODE_PROP_Pattern_Syntax, + UNICODE_PROP_Pattern_White_Space, + UNICODE_PROP_Quotation_Mark, + UNICODE_PROP_Radical, + UNICODE_PROP_Regional_Indicator, + UNICODE_PROP_Sentence_Terminal, + UNICODE_PROP_Soft_Dotted, + UNICODE_PROP_Terminal_Punctuation, + UNICODE_PROP_Unified_Ideograph, + UNICODE_PROP_Variation_Selector, + UNICODE_PROP_White_Space, + UNICODE_PROP_Bidi_Mirrored, + UNICODE_PROP_Emoji, + UNICODE_PROP_Emoji_Component, + UNICODE_PROP_Emoji_Modifier, + UNICODE_PROP_Emoji_Modifier_Base, + UNICODE_PROP_Emoji_Presentation, + UNICODE_PROP_Extended_Pictographic, + UNICODE_PROP_Default_Ignorable_Code_Point, + UNICODE_PROP_ID_Start, + UNICODE_PROP_Case_Ignorable, + UNICODE_PROP_ASCII, + UNICODE_PROP_Alphabetic, + UNICODE_PROP_Any, + UNICODE_PROP_Assigned, + UNICODE_PROP_Cased, + UNICODE_PROP_Changes_When_Casefolded, + UNICODE_PROP_Changes_When_Casemapped, + UNICODE_PROP_Changes_When_Lowercased, + UNICODE_PROP_Changes_When_NFKC_Casefolded, + UNICODE_PROP_Changes_When_Titlecased, + UNICODE_PROP_Changes_When_Uppercased, + UNICODE_PROP_Grapheme_Base, + UNICODE_PROP_Grapheme_Extend, + UNICODE_PROP_ID_Continue, + UNICODE_PROP_Lowercase, + UNICODE_PROP_Math, + UNICODE_PROP_Uppercase, + UNICODE_PROP_XID_Continue, + UNICODE_PROP_XID_Start, + UNICODE_PROP_Cased1, + UNICODE_PROP_COUNT, +} UnicodePropertyEnum; + +static const char unicode_prop_name_table[] = + "ASCII_Hex_Digit,AHex" "\0" + "Bidi_Control,Bidi_C" "\0" + "Dash" "\0" + "Deprecated,Dep" "\0" + "Diacritic,Dia" "\0" + "Extender,Ext" "\0" + "Hex_Digit,Hex" "\0" + "IDS_Binary_Operator,IDSB" "\0" + "IDS_Trinary_Operator,IDST" "\0" + "Ideographic,Ideo" "\0" + "Join_Control,Join_C" "\0" + "Logical_Order_Exception,LOE" "\0" + "Noncharacter_Code_Point,NChar" "\0" + "Pattern_Syntax,Pat_Syn" "\0" + "Pattern_White_Space,Pat_WS" "\0" + "Quotation_Mark,QMark" "\0" + "Radical" "\0" + "Regional_Indicator,RI" "\0" + "Sentence_Terminal,STerm" "\0" + "Soft_Dotted,SD" "\0" + "Terminal_Punctuation,Term" "\0" + "Unified_Ideograph,UIdeo" "\0" + "Variation_Selector,VS" "\0" + "White_Space,space" "\0" + "Bidi_Mirrored,Bidi_M" "\0" + "Emoji" "\0" + "Emoji_Component,EComp" "\0" + "Emoji_Modifier,EMod" "\0" + "Emoji_Modifier_Base,EBase" "\0" + "Emoji_Presentation,EPres" "\0" + "Extended_Pictographic,ExtPict" "\0" + "Default_Ignorable_Code_Point,DI" "\0" + "ID_Start,IDS" "\0" + "Case_Ignorable,CI" "\0" + "ASCII" "\0" + "Alphabetic,Alpha" "\0" + "Any" "\0" + "Assigned" "\0" + "Cased" "\0" + "Changes_When_Casefolded,CWCF" "\0" + "Changes_When_Casemapped,CWCM" "\0" + "Changes_When_Lowercased,CWL" "\0" + "Changes_When_NFKC_Casefolded,CWKCF" "\0" + "Changes_When_Titlecased,CWT" "\0" + "Changes_When_Uppercased,CWU" "\0" + "Grapheme_Base,Gr_Base" "\0" + "Grapheme_Extend,Gr_Ext" "\0" + "ID_Continue,IDC" "\0" + "Lowercase,Lower" "\0" + "Math" "\0" + "Uppercase,Upper" "\0" + "XID_Continue,XIDC" "\0" + "XID_Start,XIDS" "\0" +; + +static const uint8_t * const unicode_prop_table[] = { + unicode_prop_Hyphen_table, + unicode_prop_Other_Math_table, + unicode_prop_Other_Alphabetic_table, + unicode_prop_Other_Lowercase_table, + unicode_prop_Other_Uppercase_table, + unicode_prop_Other_Grapheme_Extend_table, + unicode_prop_Other_Default_Ignorable_Code_Point_table, + unicode_prop_Other_ID_Start_table, + unicode_prop_Other_ID_Continue_table, + unicode_prop_Prepended_Concatenation_Mark_table, + unicode_prop_ID_Continue1_table, + unicode_prop_XID_Start1_table, + unicode_prop_XID_Continue1_table, + unicode_prop_Changes_When_Titlecased1_table, + unicode_prop_Changes_When_Casefolded1_table, + unicode_prop_Changes_When_NFKC_Casefolded1_table, + unicode_prop_ASCII_Hex_Digit_table, + unicode_prop_Bidi_Control_table, + unicode_prop_Dash_table, + unicode_prop_Deprecated_table, + unicode_prop_Diacritic_table, + unicode_prop_Extender_table, + unicode_prop_Hex_Digit_table, + unicode_prop_IDS_Binary_Operator_table, + unicode_prop_IDS_Trinary_Operator_table, + unicode_prop_Ideographic_table, + unicode_prop_Join_Control_table, + unicode_prop_Logical_Order_Exception_table, + unicode_prop_Noncharacter_Code_Point_table, + unicode_prop_Pattern_Syntax_table, + unicode_prop_Pattern_White_Space_table, + unicode_prop_Quotation_Mark_table, + unicode_prop_Radical_table, + unicode_prop_Regional_Indicator_table, + unicode_prop_Sentence_Terminal_table, + unicode_prop_Soft_Dotted_table, + unicode_prop_Terminal_Punctuation_table, + unicode_prop_Unified_Ideograph_table, + unicode_prop_Variation_Selector_table, + unicode_prop_White_Space_table, + unicode_prop_Bidi_Mirrored_table, + unicode_prop_Emoji_table, + unicode_prop_Emoji_Component_table, + unicode_prop_Emoji_Modifier_table, + unicode_prop_Emoji_Modifier_Base_table, + unicode_prop_Emoji_Presentation_table, + unicode_prop_Extended_Pictographic_table, + unicode_prop_Default_Ignorable_Code_Point_table, + unicode_prop_ID_Start_table, + unicode_prop_Case_Ignorable_table, +}; + +static const uint16_t unicode_prop_len_table[] = { + countof(unicode_prop_Hyphen_table), + countof(unicode_prop_Other_Math_table), + countof(unicode_prop_Other_Alphabetic_table), + countof(unicode_prop_Other_Lowercase_table), + countof(unicode_prop_Other_Uppercase_table), + countof(unicode_prop_Other_Grapheme_Extend_table), + countof(unicode_prop_Other_Default_Ignorable_Code_Point_table), + countof(unicode_prop_Other_ID_Start_table), + countof(unicode_prop_Other_ID_Continue_table), + countof(unicode_prop_Prepended_Concatenation_Mark_table), + countof(unicode_prop_ID_Continue1_table), + countof(unicode_prop_XID_Start1_table), + countof(unicode_prop_XID_Continue1_table), + countof(unicode_prop_Changes_When_Titlecased1_table), + countof(unicode_prop_Changes_When_Casefolded1_table), + countof(unicode_prop_Changes_When_NFKC_Casefolded1_table), + countof(unicode_prop_ASCII_Hex_Digit_table), + countof(unicode_prop_Bidi_Control_table), + countof(unicode_prop_Dash_table), + countof(unicode_prop_Deprecated_table), + countof(unicode_prop_Diacritic_table), + countof(unicode_prop_Extender_table), + countof(unicode_prop_Hex_Digit_table), + countof(unicode_prop_IDS_Binary_Operator_table), + countof(unicode_prop_IDS_Trinary_Operator_table), + countof(unicode_prop_Ideographic_table), + countof(unicode_prop_Join_Control_table), + countof(unicode_prop_Logical_Order_Exception_table), + countof(unicode_prop_Noncharacter_Code_Point_table), + countof(unicode_prop_Pattern_Syntax_table), + countof(unicode_prop_Pattern_White_Space_table), + countof(unicode_prop_Quotation_Mark_table), + countof(unicode_prop_Radical_table), + countof(unicode_prop_Regional_Indicator_table), + countof(unicode_prop_Sentence_Terminal_table), + countof(unicode_prop_Soft_Dotted_table), + countof(unicode_prop_Terminal_Punctuation_table), + countof(unicode_prop_Unified_Ideograph_table), + countof(unicode_prop_Variation_Selector_table), + countof(unicode_prop_White_Space_table), + countof(unicode_prop_Bidi_Mirrored_table), + countof(unicode_prop_Emoji_table), + countof(unicode_prop_Emoji_Component_table), + countof(unicode_prop_Emoji_Modifier_table), + countof(unicode_prop_Emoji_Modifier_Base_table), + countof(unicode_prop_Emoji_Presentation_table), + countof(unicode_prop_Extended_Pictographic_table), + countof(unicode_prop_Default_Ignorable_Code_Point_table), + countof(unicode_prop_ID_Start_table), + countof(unicode_prop_Case_Ignorable_table), +}; + +#endif /* CONFIG_ALL_UNICODE */ diff --git a/source/engine/thirdparty/quickjs/libunicode.c b/source/engine/thirdparty/quickjs/libunicode.c new file mode 100644 index 0000000..4200252 --- /dev/null +++ b/source/engine/thirdparty/quickjs/libunicode.c @@ -0,0 +1,1788 @@ +/* + * Unicode utilities + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * 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. + */ +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "libunicode.h" +#include "libunicode-table.h" + +enum { + RUN_TYPE_U, + RUN_TYPE_L, + RUN_TYPE_UF, + RUN_TYPE_LF, + RUN_TYPE_UL, + RUN_TYPE_LSU, + RUN_TYPE_U2L_399_EXT2, + RUN_TYPE_UF_D20, + RUN_TYPE_UF_D1_EXT, + RUN_TYPE_U_EXT, + RUN_TYPE_LF_EXT, + RUN_TYPE_UF_EXT2, + RUN_TYPE_LF_EXT2, + RUN_TYPE_UF_EXT3, +}; + +static int lre_case_conv1(uint32_t c, int conv_type) +{ + uint32_t res[LRE_CC_RES_LEN_MAX]; + lre_case_conv(res, c, conv_type); + return res[0]; +} + +/* case conversion using the table entry 'idx' with value 'v' */ +static int lre_case_conv_entry(uint32_t *res, uint32_t c, int conv_type, uint32_t idx, uint32_t v) +{ + uint32_t code, data, type, a, is_lower; + is_lower = (conv_type != 0); + type = (v >> (32 - 17 - 7 - 4)) & 0xf; + data = ((v & 0xf) << 8) | case_conv_table2[idx]; + code = v >> (32 - 17); + switch(type) { + case RUN_TYPE_U: + case RUN_TYPE_L: + case RUN_TYPE_UF: + case RUN_TYPE_LF: + if (conv_type == (type & 1) || + (type >= RUN_TYPE_UF && conv_type == 2)) { + c = c - code + (case_conv_table1[data] >> (32 - 17)); + } + break; + case RUN_TYPE_UL: + a = c - code; + if ((a & 1) != (1 - is_lower)) + break; + c = (a ^ 1) + code; + break; + case RUN_TYPE_LSU: + a = c - code; + if (a == 1) { + c += 2 * is_lower - 1; + } else if (a == (1 - is_lower) * 2) { + c += (2 * is_lower - 1) * 2; + } + break; + case RUN_TYPE_U2L_399_EXT2: + if (!is_lower) { + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = 0x399; + return 2; + } else { + c = c - code + case_conv_ext[data & 0x3f]; + } + break; + case RUN_TYPE_UF_D20: + if (conv_type == 1) + break; + c = data + (conv_type == 2) * 0x20; + break; + case RUN_TYPE_UF_D1_EXT: + if (conv_type == 1) + break; + c = case_conv_ext[data] + (conv_type == 2); + break; + case RUN_TYPE_U_EXT: + case RUN_TYPE_LF_EXT: + if (is_lower != (type - RUN_TYPE_U_EXT)) + break; + c = case_conv_ext[data]; + break; + case RUN_TYPE_LF_EXT2: + if (!is_lower) + break; + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = case_conv_ext[data & 0x3f]; + return 2; + case RUN_TYPE_UF_EXT2: + if (conv_type == 1) + break; + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = case_conv_ext[data & 0x3f]; + if (conv_type == 2) { + /* convert to lower */ + res[0] = lre_case_conv1(res[0], 1); + res[1] = lre_case_conv1(res[1], 1); + } + return 2; + default: + case RUN_TYPE_UF_EXT3: + if (conv_type == 1) + break; + res[0] = case_conv_ext[data >> 8]; + res[1] = case_conv_ext[(data >> 4) & 0xf]; + res[2] = case_conv_ext[data & 0xf]; + if (conv_type == 2) { + /* convert to lower */ + res[0] = lre_case_conv1(res[0], 1); + res[1] = lre_case_conv1(res[1], 1); + res[2] = lre_case_conv1(res[2], 1); + } + return 3; + } + res[0] = c; + return 1; +} + +/* conv_type: + 0 = to upper + 1 = to lower + 2 = case folding (= to lower with modifications) +*/ +int lre_case_conv(uint32_t *res, uint32_t c, int conv_type) +{ + if (c < 128) { + if (conv_type) { + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + } else { + if (c >= 'a' && c <= 'z') { + c = c - 'a' + 'A'; + } + } + } else { + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return lre_case_conv_entry(res, c, conv_type, idx, v); + } + } + } + res[0] = c; + return 1; +} + +static int lre_case_folding_entry(uint32_t c, uint32_t idx, uint32_t v, BOOL is_unicode) +{ + uint32_t res[LRE_CC_RES_LEN_MAX]; + int len; + + if (is_unicode) { + len = lre_case_conv_entry(res, c, 2, idx, v); + if (len == 1) { + c = res[0]; + } else { + /* handle the few specific multi-character cases (see + unicode_gen.c:dump_case_folding_special_cases()) */ + if (c == 0xfb06) { + c = 0xfb05; + } else if (c == 0x01fd3) { + c = 0x390; + } else if (c == 0x01fe3) { + c = 0x3b0; + } + } + } else { + if (likely(c < 128)) { + if (c >= 'a' && c <= 'z') + c = c - 'a' + 'A'; + } else { + /* legacy regexp: to upper case if single char >= 128 */ + len = lre_case_conv_entry(res, c, FALSE, idx, v); + if (len == 1 && res[0] >= 128) + c = res[0]; + } + } + return c; +} + +/* JS regexp specific rules for case folding */ +int lre_canonicalize(uint32_t c, BOOL is_unicode) +{ + if (c < 128) { + /* fast case */ + if (is_unicode) { + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + } else { + if (c >= 'a' && c <= 'z') { + c = c - 'a' + 'A'; + } + } + } else { + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return lre_case_folding_entry(c, idx, v, is_unicode); + } + } + } + return c; +} + +static uint32_t get_le24(const uint8_t *ptr) +{ +#if defined(__x86__) || defined(__x86_64__) + return *(uint16_t *)ptr | (ptr[2] << 16); +#else + return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16); +#endif +} + +#define UNICODE_INDEX_BLOCK_LEN 32 + +/* return -1 if not in table, otherwise the offset in the block */ +static int get_index_pos(uint32_t *pcode, uint32_t c, + const uint8_t *index_table, int index_table_len) +{ + uint32_t code, v; + int idx_min, idx_max, idx; + + idx_min = 0; + v = get_le24(index_table); + code = v & ((1 << 21) - 1); + if (c < code) { + *pcode = 0; + return 0; + } + idx_max = index_table_len - 1; + code = get_le24(index_table + idx_max * 3); + if (c >= code) + return -1; + /* invariant: tab[idx_min] <= c < tab2[idx_max] */ + while ((idx_max - idx_min) > 1) { + idx = (idx_max + idx_min) / 2; + v = get_le24(index_table + idx * 3); + code = v & ((1 << 21) - 1); + if (c < code) { + idx_max = idx; + } else { + idx_min = idx; + } + } + v = get_le24(index_table + idx_min * 3); + *pcode = v & ((1 << 21) - 1); + return (idx_min + 1) * UNICODE_INDEX_BLOCK_LEN + (v >> 21); +} + +static BOOL lre_is_in_table(uint32_t c, const uint8_t *table, + const uint8_t *index_table, int index_table_len) +{ + uint32_t code, b, bit; + int pos; + const uint8_t *p; + + pos = get_index_pos(&code, c, index_table, index_table_len); + if (pos < 0) + return FALSE; /* outside the table */ + p = table + pos; + bit = 0; + for(;;) { + b = *p++; + if (b < 64) { + code += (b >> 3) + 1; + if (c < code) + return bit; + bit ^= 1; + code += (b & 7) + 1; + } else if (b >= 0x80) { + code += b - 0x80 + 1; + } else if (b < 0x60) { + code += (((b - 0x40) << 8) | p[0]) + 1; + p++; + } else { + code += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1; + p += 2; + } + if (c < code) + return bit; + bit ^= 1; + } +} + +BOOL lre_is_cased(uint32_t c) +{ + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return TRUE; + } + } + return lre_is_in_table(c, unicode_prop_Cased1_table, + unicode_prop_Cased1_index, + sizeof(unicode_prop_Cased1_index) / 3); +} + +BOOL lre_is_case_ignorable(uint32_t c) +{ + return lre_is_in_table(c, unicode_prop_Case_Ignorable_table, + unicode_prop_Case_Ignorable_index, + sizeof(unicode_prop_Case_Ignorable_index) / 3); +} + +/* character range */ + +static __maybe_unused void cr_dump(CharRange *cr) +{ + int i; + for(i = 0; i < cr->len; i++) + printf("%d: 0x%04x\n", i, cr->points[i]); +} + +static void *cr_default_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +void cr_init(CharRange *cr, void *mem_opaque, DynBufReallocFunc *realloc_func) +{ + cr->len = cr->size = 0; + cr->points = NULL; + cr->mem_opaque = mem_opaque; + cr->realloc_func = realloc_func ? realloc_func : cr_default_realloc; +} + +void cr_free(CharRange *cr) +{ + cr->realloc_func(cr->mem_opaque, cr->points, 0); +} + +int cr_realloc(CharRange *cr, int size) +{ + int new_size; + uint32_t *new_buf; + + if (size > cr->size) { + new_size = max_int(size, cr->size * 3 / 2); + new_buf = cr->realloc_func(cr->mem_opaque, cr->points, + new_size * sizeof(cr->points[0])); + if (!new_buf) + return -1; + cr->points = new_buf; + cr->size = new_size; + } + return 0; +} + +int cr_copy(CharRange *cr, const CharRange *cr1) +{ + if (cr_realloc(cr, cr1->len)) + return -1; + memcpy(cr->points, cr1->points, sizeof(cr->points[0]) * cr1->len); + cr->len = cr1->len; + return 0; +} + +/* merge consecutive intervals and remove empty intervals */ +static void cr_compress(CharRange *cr) +{ + int i, j, k, len; + uint32_t *pt; + + pt = cr->points; + len = cr->len; + i = 0; + j = 0; + k = 0; + while ((i + 1) < len) { + if (pt[i] == pt[i + 1]) { + /* empty interval */ + i += 2; + } else { + j = i; + while ((j + 3) < len && pt[j + 1] == pt[j + 2]) + j += 2; + /* just copy */ + pt[k] = pt[i]; + pt[k + 1] = pt[j + 1]; + k += 2; + i = j + 2; + } + } + cr->len = k; +} + +/* union or intersection */ +int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, + const uint32_t *b_pt, int b_len, int op) +{ + int a_idx, b_idx, is_in; + uint32_t v; + + a_idx = 0; + b_idx = 0; + for(;;) { + /* get one more point from a or b in increasing order */ + if (a_idx < a_len && b_idx < b_len) { + if (a_pt[a_idx] < b_pt[b_idx]) { + goto a_add; + } else if (a_pt[a_idx] == b_pt[b_idx]) { + v = a_pt[a_idx]; + a_idx++; + b_idx++; + } else { + goto b_add; + } + } else if (a_idx < a_len) { + a_add: + v = a_pt[a_idx++]; + } else if (b_idx < b_len) { + b_add: + v = b_pt[b_idx++]; + } else { + break; + } + /* add the point if the in/out status changes */ + switch(op) { + case CR_OP_UNION: + is_in = (a_idx & 1) | (b_idx & 1); + break; + case CR_OP_INTER: + is_in = (a_idx & 1) & (b_idx & 1); + break; + case CR_OP_XOR: + is_in = (a_idx & 1) ^ (b_idx & 1); + break; + default: + abort(); + } + if (is_in != (cr->len & 1)) { + if (cr_add_point(cr, v)) + return -1; + } + } + cr_compress(cr); + return 0; +} + +int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len) +{ + CharRange a = *cr; + int ret; + cr->len = 0; + cr->size = 0; + cr->points = NULL; + ret = cr_op(cr, a.points, a.len, b_pt, b_len, CR_OP_UNION); + cr_free(&a); + return ret; +} + +int cr_invert(CharRange *cr) +{ + int len; + len = cr->len; + if (cr_realloc(cr, len + 2)) + return -1; + memmove(cr->points + 1, cr->points, len * sizeof(cr->points[0])); + cr->points[0] = 0; + cr->points[len + 1] = UINT32_MAX; + cr->len = len + 2; + cr_compress(cr); + return 0; +} + +#ifdef CONFIG_ALL_UNICODE + +BOOL lre_is_id_start(uint32_t c) +{ + return lre_is_in_table(c, unicode_prop_ID_Start_table, + unicode_prop_ID_Start_index, + sizeof(unicode_prop_ID_Start_index) / 3); +} + +BOOL lre_is_id_continue(uint32_t c) +{ + return lre_is_id_start(c) || + lre_is_in_table(c, unicode_prop_ID_Continue1_table, + unicode_prop_ID_Continue1_index, + sizeof(unicode_prop_ID_Continue1_index) / 3); +} + +#define UNICODE_DECOMP_LEN_MAX 18 + +typedef enum { + DECOMP_TYPE_C1, /* 16 bit char */ + DECOMP_TYPE_L1, /* 16 bit char table */ + DECOMP_TYPE_L2, + DECOMP_TYPE_L3, + DECOMP_TYPE_L4, + DECOMP_TYPE_L5, /* XXX: not used */ + DECOMP_TYPE_L6, /* XXX: could remove */ + DECOMP_TYPE_L7, /* XXX: could remove */ + DECOMP_TYPE_LL1, /* 18 bit char table */ + DECOMP_TYPE_LL2, + DECOMP_TYPE_S1, /* 8 bit char table */ + DECOMP_TYPE_S2, + DECOMP_TYPE_S3, + DECOMP_TYPE_S4, + DECOMP_TYPE_S5, + DECOMP_TYPE_I1, /* increment 16 bit char value */ + DECOMP_TYPE_I2_0, + DECOMP_TYPE_I2_1, + DECOMP_TYPE_I3_1, + DECOMP_TYPE_I3_2, + DECOMP_TYPE_I4_1, + DECOMP_TYPE_I4_2, + DECOMP_TYPE_B1, /* 16 bit base + 8 bit offset */ + DECOMP_TYPE_B2, + DECOMP_TYPE_B3, + DECOMP_TYPE_B4, + DECOMP_TYPE_B5, + DECOMP_TYPE_B6, + DECOMP_TYPE_B7, + DECOMP_TYPE_B8, + DECOMP_TYPE_B18, + DECOMP_TYPE_LS2, + DECOMP_TYPE_PAT3, + DECOMP_TYPE_S2_UL, + DECOMP_TYPE_LS2_UL, +} DecompTypeEnum; + +static uint32_t unicode_get_short_code(uint32_t c) +{ + static const uint16_t unicode_short_table[2] = { 0x2044, 0x2215 }; + + if (c < 0x80) + return c; + else if (c < 0x80 + 0x50) + return c - 0x80 + 0x300; + else + return unicode_short_table[c - 0x80 - 0x50]; +} + +static uint32_t unicode_get_lower_simple(uint32_t c) +{ + if (c < 0x100 || (c >= 0x410 && c <= 0x42f)) + c += 0x20; + else + c++; + return c; +} + +static uint16_t unicode_get16(const uint8_t *p) +{ + return p[0] | (p[1] << 8); +} + +static int unicode_decomp_entry(uint32_t *res, uint32_t c, + int idx, uint32_t code, uint32_t len, + uint32_t type) +{ + uint32_t c1; + int l, i, p; + const uint8_t *d; + + if (type == DECOMP_TYPE_C1) { + res[0] = unicode_decomp_table2[idx]; + return 1; + } else { + d = unicode_decomp_data + unicode_decomp_table2[idx]; + switch(type) { + case DECOMP_TYPE_L1: + case DECOMP_TYPE_L2: + case DECOMP_TYPE_L3: + case DECOMP_TYPE_L4: + case DECOMP_TYPE_L5: + case DECOMP_TYPE_L6: + case DECOMP_TYPE_L7: + l = type - DECOMP_TYPE_L1 + 1; + d += (c - code) * l * 2; + for(i = 0; i < l; i++) { + if ((res[i] = unicode_get16(d + 2 * i)) == 0) + return 0; + } + return l; + case DECOMP_TYPE_LL1: + case DECOMP_TYPE_LL2: + { + uint32_t k, p; + l = type - DECOMP_TYPE_LL1 + 1; + k = (c - code) * l; + p = len * l * 2; + for(i = 0; i < l; i++) { + c1 = unicode_get16(d + 2 * k) | + (((d[p + (k / 4)] >> ((k % 4) * 2)) & 3) << 16); + if (!c1) + return 0; + res[i] = c1; + k++; + } + } + return l; + case DECOMP_TYPE_S1: + case DECOMP_TYPE_S2: + case DECOMP_TYPE_S3: + case DECOMP_TYPE_S4: + case DECOMP_TYPE_S5: + l = type - DECOMP_TYPE_S1 + 1; + d += (c - code) * l; + for(i = 0; i < l; i++) { + if ((res[i] = unicode_get_short_code(d[i])) == 0) + return 0; + } + return l; + case DECOMP_TYPE_I1: + l = 1; + p = 0; + goto decomp_type_i; + case DECOMP_TYPE_I2_0: + case DECOMP_TYPE_I2_1: + case DECOMP_TYPE_I3_1: + case DECOMP_TYPE_I3_2: + case DECOMP_TYPE_I4_1: + case DECOMP_TYPE_I4_2: + l = 2 + ((type - DECOMP_TYPE_I2_0) >> 1); + p = ((type - DECOMP_TYPE_I2_0) & 1) + (l > 2); + decomp_type_i: + for(i = 0; i < l; i++) { + c1 = unicode_get16(d + 2 * i); + if (i == p) + c1 += c - code; + res[i] = c1; + } + return l; + case DECOMP_TYPE_B18: + l = 18; + goto decomp_type_b; + case DECOMP_TYPE_B1: + case DECOMP_TYPE_B2: + case DECOMP_TYPE_B3: + case DECOMP_TYPE_B4: + case DECOMP_TYPE_B5: + case DECOMP_TYPE_B6: + case DECOMP_TYPE_B7: + case DECOMP_TYPE_B8: + l = type - DECOMP_TYPE_B1 + 1; + decomp_type_b: + { + uint32_t c_min; + c_min = unicode_get16(d); + d += 2 + (c - code) * l; + for(i = 0; i < l; i++) { + c1 = d[i]; + if (c1 == 0xff) + c1 = 0x20; + else + c1 += c_min; + res[i] = c1; + } + } + return l; + case DECOMP_TYPE_LS2: + d += (c - code) * 3; + if (!(res[0] = unicode_get16(d))) + return 0; + res[1] = unicode_get_short_code(d[2]); + return 2; + case DECOMP_TYPE_PAT3: + res[0] = unicode_get16(d); + res[2] = unicode_get16(d + 2); + d += 4 + (c - code) * 2; + res[1] = unicode_get16(d); + return 3; + case DECOMP_TYPE_S2_UL: + case DECOMP_TYPE_LS2_UL: + c1 = c - code; + if (type == DECOMP_TYPE_S2_UL) { + d += c1 & ~1; + c = unicode_get_short_code(*d); + d++; + } else { + d += (c1 >> 1) * 3; + c = unicode_get16(d); + d += 2; + } + if (c1 & 1) + c = unicode_get_lower_simple(c); + res[0] = c; + res[1] = unicode_get_short_code(*d); + return 2; + } + } + return 0; +} + + +/* return the length of the decomposition (length <= + UNICODE_DECOMP_LEN_MAX) or 0 if no decomposition */ +static int unicode_decomp_char(uint32_t *res, uint32_t c, BOOL is_compat1) +{ + uint32_t v, type, is_compat, code, len; + int idx_min, idx_max, idx; + + idx_min = 0; + idx_max = countof(unicode_decomp_table1) - 1; + while (idx_min <= idx_max) { + idx = (idx_max + idx_min) / 2; + v = unicode_decomp_table1[idx]; + code = v >> (32 - 18); + len = (v >> (32 - 18 - 7)) & 0x7f; + // printf("idx=%d code=%05x len=%d\n", idx, code, len); + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + is_compat = v & 1; + if (is_compat1 < is_compat) + break; + type = (v >> (32 - 18 - 7 - 6)) & 0x3f; + return unicode_decomp_entry(res, c, idx, code, len, type); + } + } + return 0; +} + +/* return 0 if no pair found */ +static int unicode_compose_pair(uint32_t c0, uint32_t c1) +{ + uint32_t code, len, type, v, idx1, d_idx, d_offset, ch; + int idx_min, idx_max, idx, d; + uint32_t pair[2]; + + idx_min = 0; + idx_max = countof(unicode_comp_table) - 1; + while (idx_min <= idx_max) { + idx = (idx_max + idx_min) / 2; + idx1 = unicode_comp_table[idx]; + + /* idx1 represent an entry of the decomposition table */ + d_idx = idx1 >> 6; + d_offset = idx1 & 0x3f; + v = unicode_decomp_table1[d_idx]; + code = v >> (32 - 18); + len = (v >> (32 - 18 - 7)) & 0x7f; + type = (v >> (32 - 18 - 7 - 6)) & 0x3f; + ch = code + d_offset; + unicode_decomp_entry(pair, ch, d_idx, code, len, type); + d = c0 - pair[0]; + if (d == 0) + d = c1 - pair[1]; + if (d < 0) { + idx_max = idx - 1; + } else if (d > 0) { + idx_min = idx + 1; + } else { + return ch; + } + } + return 0; +} + +/* return the combining class of character c (between 0 and 255) */ +static int unicode_get_cc(uint32_t c) +{ + uint32_t code, n, type, cc, c1, b; + int pos; + const uint8_t *p; + + pos = get_index_pos(&code, c, + unicode_cc_index, sizeof(unicode_cc_index) / 3); + if (pos < 0) + return 0; + p = unicode_cc_table + pos; + for(;;) { + b = *p++; + type = b >> 6; + n = b & 0x3f; + if (n < 48) { + } else if (n < 56) { + n = (n - 48) << 8; + n |= *p++; + n += 48; + } else { + n = (n - 56) << 8; + n |= *p++ << 8; + n |= *p++; + n += 48 + (1 << 11); + } + if (type <= 1) + p++; + c1 = code + n + 1; + if (c < c1) { + switch(type) { + case 0: + cc = p[-1]; + break; + case 1: + cc = p[-1] + c - code; + break; + case 2: + cc = 0; + break; + default: + case 3: + cc = 230; + break; + } + return cc; + } + code = c1; + } +} + +static void sort_cc(int *buf, int len) +{ + int i, j, k, cc, cc1, start, ch1; + + for(i = 0; i < len; i++) { + cc = unicode_get_cc(buf[i]); + if (cc != 0) { + start = i; + j = i + 1; + while (j < len) { + ch1 = buf[j]; + cc1 = unicode_get_cc(ch1); + if (cc1 == 0) + break; + k = j - 1; + while (k >= start) { + if (unicode_get_cc(buf[k]) <= cc1) + break; + buf[k + 1] = buf[k]; + k--; + } + buf[k + 1] = ch1; + j++; + } +#if 0 + printf("cc:"); + for(k = start; k < j; k++) { + printf(" %3d", unicode_get_cc(buf[k])); + } + printf("\n"); +#endif + i = j; + } + } +} + +static void to_nfd_rec(DynBuf *dbuf, + const int *src, int src_len, int is_compat) +{ + uint32_t c, v; + int i, l; + uint32_t res[UNICODE_DECOMP_LEN_MAX]; + + for(i = 0; i < src_len; i++) { + c = src[i]; + if (c >= 0xac00 && c < 0xd7a4) { + /* Hangul decomposition */ + c -= 0xac00; + dbuf_put_u32(dbuf, 0x1100 + c / 588); + dbuf_put_u32(dbuf, 0x1161 + (c % 588) / 28); + v = c % 28; + if (v != 0) + dbuf_put_u32(dbuf, 0x11a7 + v); + } else { + l = unicode_decomp_char(res, c, is_compat); + if (l) { + to_nfd_rec(dbuf, (int *)res, l, is_compat); + } else { + dbuf_put_u32(dbuf, c); + } + } + } +} + +/* return 0 if not found */ +static int compose_pair(uint32_t c0, uint32_t c1) +{ + /* Hangul composition */ + if (c0 >= 0x1100 && c0 < 0x1100 + 19 && + c1 >= 0x1161 && c1 < 0x1161 + 21) { + return 0xac00 + (c0 - 0x1100) * 588 + (c1 - 0x1161) * 28; + } else if (c0 >= 0xac00 && c0 < 0xac00 + 11172 && + (c0 - 0xac00) % 28 == 0 && + c1 >= 0x11a7 && c1 < 0x11a7 + 28) { + return c0 + c1 - 0x11a7; + } else { + return unicode_compose_pair(c0, c1); + } +} + +int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, + UnicodeNormalizationEnum n_type, + void *opaque, DynBufReallocFunc *realloc_func) +{ + int *buf, buf_len, i, p, starter_pos, cc, last_cc, out_len; + BOOL is_compat; + DynBuf dbuf_s, *dbuf = &dbuf_s; + + is_compat = n_type >> 1; + + dbuf_init2(dbuf, opaque, realloc_func); + if (dbuf_realloc(dbuf, sizeof(int) * src_len)) + goto fail; + + /* common case: latin1 is unaffected by NFC */ + if (n_type == UNICODE_NFC) { + for(i = 0; i < src_len; i++) { + if (src[i] >= 0x100) + goto not_latin1; + } + buf = (int *)dbuf->buf; + memcpy(buf, src, src_len * sizeof(int)); + *pdst = (uint32_t *)buf; + return src_len; + not_latin1: ; + } + + to_nfd_rec(dbuf, (const int *)src, src_len, is_compat); + if (dbuf_error(dbuf)) { + fail: + *pdst = NULL; + return -1; + } + buf = (int *)dbuf->buf; + buf_len = dbuf->size / sizeof(int); + + sort_cc(buf, buf_len); + + if (buf_len <= 1 || (n_type & 1) != 0) { + /* NFD / NFKD */ + *pdst = (uint32_t *)buf; + return buf_len; + } + + i = 1; + out_len = 1; + while (i < buf_len) { + /* find the starter character and test if it is blocked from + the character at 'i' */ + last_cc = unicode_get_cc(buf[i]); + starter_pos = out_len - 1; + while (starter_pos >= 0) { + cc = unicode_get_cc(buf[starter_pos]); + if (cc == 0) + break; + if (cc >= last_cc) + goto next; + last_cc = 256; + starter_pos--; + } + if (starter_pos >= 0 && + (p = compose_pair(buf[starter_pos], buf[i])) != 0) { + buf[starter_pos] = p; + i++; + } else { + next: + buf[out_len++] = buf[i++]; + } + } + *pdst = (uint32_t *)buf; + return out_len; +} + +/* char ranges for various unicode properties */ + +static int unicode_find_name(const char *name_table, const char *name) +{ + const char *p, *r; + int pos; + size_t name_len, len; + + p = name_table; + pos = 0; + name_len = strlen(name); + while (*p) { + for(;;) { + r = strchr(p, ','); + if (!r) + len = strlen(p); + else + len = r - p; + if (len == name_len && !memcmp(p, name, name_len)) + return pos; + p += len + 1; + if (!r) + break; + } + pos++; + } + return -1; +} + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +int unicode_script(CharRange *cr, + const char *script_name, BOOL is_ext) +{ + int script_idx; + const uint8_t *p, *p_end; + uint32_t c, c1, b, n, v, v_len, i, type; + CharRange cr1_s, *cr1; + CharRange cr2_s, *cr2 = &cr2_s; + BOOL is_common; + + script_idx = unicode_find_name(unicode_script_name_table, script_name); + if (script_idx < 0) + return -2; + /* Note: we remove the "Unknown" Script */ + script_idx += UNICODE_SCRIPT_Unknown + 1; + + is_common = (script_idx == UNICODE_SCRIPT_Common || + script_idx == UNICODE_SCRIPT_Inherited); + if (is_ext) { + cr1 = &cr1_s; + cr_init(cr1, cr->mem_opaque, cr->realloc_func); + cr_init(cr2, cr->mem_opaque, cr->realloc_func); + } else { + cr1 = cr; + } + + p = unicode_script_table; + p_end = unicode_script_table + countof(unicode_script_table); + c = 0; + while (p < p_end) { + b = *p++; + type = b >> 7; + n = b & 0x7f; + if (n < 96) { + } else if (n < 112) { + n = (n - 96) << 8; + n |= *p++; + n += 96; + } else { + n = (n - 112) << 16; + n |= *p++ << 8; + n |= *p++; + n += 96 + (1 << 12); + } + if (type == 0) + v = 0; + else + v = *p++; + c1 = c + n + 1; + if (v == script_idx) { + if (cr_add_interval(cr1, c, c1)) + goto fail; + } + c = c1; + } + + if (is_ext) { + /* add the script extensions */ + p = unicode_script_ext_table; + p_end = unicode_script_ext_table + countof(unicode_script_ext_table); + c = 0; + while (p < p_end) { + b = *p++; + if (b < 128) { + n = b; + } else if (b < 128 + 64) { + n = (b - 128) << 8; + n |= *p++; + n += 128; + } else { + n = (b - 128 - 64) << 16; + n |= *p++ << 8; + n |= *p++; + n += 128 + (1 << 14); + } + c1 = c + n + 1; + v_len = *p++; + if (is_common) { + if (v_len != 0) { + if (cr_add_interval(cr2, c, c1)) + goto fail; + } + } else { + for(i = 0; i < v_len; i++) { + if (p[i] == script_idx) { + if (cr_add_interval(cr2, c, c1)) + goto fail; + break; + } + } + } + p += v_len; + c = c1; + } + if (is_common) { + /* remove all the characters with script extensions */ + if (cr_invert(cr2)) + goto fail; + if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len, + CR_OP_INTER)) + goto fail; + } else { + if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len, + CR_OP_UNION)) + goto fail; + } + cr_free(cr1); + cr_free(cr2); + } + return 0; + fail: + if (is_ext) { + cr_free(cr1); + cr_free(cr2); + } + goto fail; +} + +#define M(id) (1U << UNICODE_GC_ ## id) + +static int unicode_general_category1(CharRange *cr, uint32_t gc_mask) +{ + const uint8_t *p, *p_end; + uint32_t c, c0, b, n, v; + + p = unicode_gc_table; + p_end = unicode_gc_table + countof(unicode_gc_table); + c = 0; + while (p < p_end) { + b = *p++; + n = b >> 5; + v = b & 0x1f; + if (n == 7) { + n = *p++; + if (n < 128) { + n += 7; + } else if (n < 128 + 64) { + n = (n - 128) << 8; + n |= *p++; + n += 7 + 128; + } else { + n = (n - 128 - 64) << 16; + n |= *p++ << 8; + n |= *p++; + n += 7 + 128 + (1 << 14); + } + } + c0 = c; + c += n + 1; + if (v == 31) { + /* run of Lu / Ll */ + b = gc_mask & (M(Lu) | M(Ll)); + if (b != 0) { + if (b == (M(Lu) | M(Ll))) { + goto add_range; + } else { + c0 += ((gc_mask & M(Ll)) != 0); + for(; c0 < c; c0 += 2) { + if (cr_add_interval(cr, c0, c0 + 1)) + return -1; + } + } + } + } else if ((gc_mask >> v) & 1) { + add_range: + if (cr_add_interval(cr, c0, c)) + return -1; + } + } + return 0; +} + +static int unicode_prop1(CharRange *cr, int prop_idx) +{ + const uint8_t *p, *p_end; + uint32_t c, c0, b, bit; + + p = unicode_prop_table[prop_idx]; + p_end = p + unicode_prop_len_table[prop_idx]; + c = 0; + bit = 0; + while (p < p_end) { + c0 = c; + b = *p++; + if (b < 64) { + c += (b >> 3) + 1; + if (bit) { + if (cr_add_interval(cr, c0, c)) + return -1; + } + bit ^= 1; + c0 = c; + c += (b & 7) + 1; + } else if (b >= 0x80) { + c += b - 0x80 + 1; + } else if (b < 0x60) { + c += (((b - 0x40) << 8) | p[0]) + 1; + p++; + } else { + c += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1; + p += 2; + } + if (bit) { + if (cr_add_interval(cr, c0, c)) + return -1; + } + bit ^= 1; + } + return 0; +} + +#define CASE_U (1 << 0) +#define CASE_L (1 << 1) +#define CASE_F (1 << 2) + +/* use the case conversion table to generate range of characters. + CASE_U: set char if modified by uppercasing, + CASE_L: set char if modified by lowercasing, + CASE_F: set char if modified by case folding, + */ +static int unicode_case1(CharRange *cr, int case_mask) +{ +#define MR(x) (1 << RUN_TYPE_ ## x) + const uint32_t tab_run_mask[3] = { + MR(U) | MR(UF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(UF_D20) | + MR(UF_D1_EXT) | MR(U_EXT) | MR(UF_EXT2) | MR(UF_EXT3), + + MR(L) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2), + + MR(UF) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2) | MR(UF_D20) | MR(UF_D1_EXT) | MR(LF_EXT) | MR(UF_EXT2) | MR(UF_EXT3), + }; +#undef MR + uint32_t mask, v, code, type, len, i, idx; + + if (case_mask == 0) + return 0; + mask = 0; + for(i = 0; i < 3; i++) { + if ((case_mask >> i) & 1) + mask |= tab_run_mask[i]; + } + for(idx = 0; idx < countof(case_conv_table1); idx++) { + v = case_conv_table1[idx]; + type = (v >> (32 - 17 - 7 - 4)) & 0xf; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if ((mask >> type) & 1) { + // printf("%d: type=%d %04x %04x\n", idx, type, code, code + len - 1); + switch(type) { + case RUN_TYPE_UL: + if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) + goto def_case; + code += ((case_mask & CASE_U) != 0); + for(i = 0; i < len; i += 2) { + if (cr_add_interval(cr, code + i, code + i + 1)) + return -1; + } + break; + case RUN_TYPE_LSU: + if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) + goto def_case; + if (!(case_mask & CASE_U)) { + if (cr_add_interval(cr, code, code + 1)) + return -1; + } + if (cr_add_interval(cr, code + 1, code + 2)) + return -1; + if (case_mask & CASE_U) { + if (cr_add_interval(cr, code + 2, code + 3)) + return -1; + } + break; + default: + def_case: + if (cr_add_interval(cr, code, code + len)) + return -1; + break; + } + } + } + return 0; +} + +static int point_cmp(const void *p1, const void *p2, void *arg) +{ + uint32_t v1 = *(uint32_t *)p1; + uint32_t v2 = *(uint32_t *)p2; + return (v1 > v2) - (v1 < v2); +} + +static void cr_sort_and_remove_overlap(CharRange *cr) +{ + uint32_t start, end, start1, end1, i, j; + + /* the resulting ranges are not necessarily sorted and may overlap */ + rqsort(cr->points, cr->len / 2, sizeof(cr->points[0]) * 2, point_cmp, NULL); + j = 0; + for(i = 0; i < cr->len; ) { + start = cr->points[i]; + end = cr->points[i + 1]; + i += 2; + while (i < cr->len) { + start1 = cr->points[i]; + end1 = cr->points[i + 1]; + if (start1 > end) { + /* |------| + * |-------| */ + break; + } else if (end1 <= end) { + /* |------| + * |--| */ + i += 2; + } else { + /* |------| + * |-------| */ + end = end1; + i += 2; + } + } + cr->points[j] = start; + cr->points[j + 1] = end; + j += 2; + } + cr->len = j; +} + +/* canonicalize a character set using the JS regex case folding rules + (see lre_canonicalize()) */ +int cr_regexp_canonicalize(CharRange *cr, BOOL is_unicode) +{ + CharRange cr_inter, cr_mask, cr_result, cr_sub; + uint32_t v, code, len, i, idx, start, end, c, d_start, d_end, d; + + cr_init(&cr_mask, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_inter, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_result, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_sub, cr->mem_opaque, cr->realloc_func); + + if (unicode_case1(&cr_mask, is_unicode ? CASE_F : CASE_U)) + goto fail; + if (cr_op(&cr_inter, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) + goto fail; + + if (cr_invert(&cr_mask)) + goto fail; + if (cr_op(&cr_sub, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) + goto fail; + + /* cr_inter = cr & cr_mask */ + /* cr_sub = cr & ~cr_mask */ + + /* use the case conversion table to compute the result */ + d_start = -1; + d_end = -1; + idx = 0; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + for(i = 0; i < cr_inter.len; i += 2) { + start = cr_inter.points[i]; + end = cr_inter.points[i + 1]; + + for(c = start; c < end; c++) { + for(;;) { + if (c >= code && c < code + len) + break; + idx++; + assert(idx < countof(case_conv_table1)); + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + } + d = lre_case_folding_entry(c, idx, v, is_unicode); + /* try to merge with the current interval */ + if (d_start == -1) { + d_start = d; + d_end = d + 1; + } else if (d_end == d) { + d_end++; + } else { + cr_add_interval(&cr_result, d_start, d_end); + d_start = d; + d_end = d + 1; + } + } + } + if (d_start != -1) { + if (cr_add_interval(&cr_result, d_start, d_end)) + goto fail; + } + + /* the resulting ranges are not necessarily sorted and may overlap */ + cr_sort_and_remove_overlap(&cr_result); + + /* or with the character not affected by the case folding */ + cr->len = 0; + if (cr_op(cr, cr_result.points, cr_result.len, cr_sub.points, cr_sub.len, CR_OP_UNION)) + goto fail; + + cr_free(&cr_inter); + cr_free(&cr_mask); + cr_free(&cr_result); + cr_free(&cr_sub); + return 0; + fail: + cr_free(&cr_inter); + cr_free(&cr_mask); + cr_free(&cr_result); + cr_free(&cr_sub); + return -1; +} + +typedef enum { + POP_GC, + POP_PROP, + POP_CASE, + POP_UNION, + POP_INTER, + POP_XOR, + POP_INVERT, + POP_END, +} PropOPEnum; + +#define POP_STACK_LEN_MAX 4 + +static int unicode_prop_ops(CharRange *cr, ...) +{ + va_list ap; + CharRange stack[POP_STACK_LEN_MAX]; + int stack_len, op, ret, i; + uint32_t a; + + va_start(ap, cr); + stack_len = 0; + for(;;) { + op = va_arg(ap, int); + switch(op) { + case POP_GC: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_general_category1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_PROP: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_prop1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_CASE: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_case1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_UNION: + case POP_INTER: + case POP_XOR: + { + CharRange *cr1, *cr2, *cr3; + assert(stack_len >= 2); + assert(stack_len < POP_STACK_LEN_MAX); + cr1 = &stack[stack_len - 2]; + cr2 = &stack[stack_len - 1]; + cr3 = &stack[stack_len++]; + cr_init(cr3, cr->mem_opaque, cr->realloc_func); + if (cr_op(cr3, cr1->points, cr1->len, + cr2->points, cr2->len, op - POP_UNION + CR_OP_UNION)) + goto fail; + cr_free(cr1); + cr_free(cr2); + *cr1 = *cr3; + stack_len -= 2; + } + break; + case POP_INVERT: + assert(stack_len >= 1); + if (cr_invert(&stack[stack_len - 1])) + goto fail; + break; + case POP_END: + goto done; + default: + abort(); + } + } + done: + assert(stack_len == 1); + ret = cr_copy(cr, &stack[0]); + cr_free(&stack[0]); + return ret; + fail: + for(i = 0; i < stack_len; i++) + cr_free(&stack[i]); + return -1; +} + +static const uint32_t unicode_gc_mask_table[] = { + M(Lu) | M(Ll) | M(Lt), /* LC */ + M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo), /* L */ + M(Mn) | M(Mc) | M(Me), /* M */ + M(Nd) | M(Nl) | M(No), /* N */ + M(Sm) | M(Sc) | M(Sk) | M(So), /* S */ + M(Pc) | M(Pd) | M(Ps) | M(Pe) | M(Pi) | M(Pf) | M(Po), /* P */ + M(Zs) | M(Zl) | M(Zp), /* Z */ + M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn), /* C */ +}; + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +int unicode_general_category(CharRange *cr, const char *gc_name) +{ + int gc_idx; + uint32_t gc_mask; + + gc_idx = unicode_find_name(unicode_gc_name_table, gc_name); + if (gc_idx < 0) + return -2; + if (gc_idx <= UNICODE_GC_Co) { + gc_mask = (uint64_t)1 << gc_idx; + } else { + gc_mask = unicode_gc_mask_table[gc_idx - UNICODE_GC_LC]; + } + return unicode_general_category1(cr, gc_mask); +} + + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +int unicode_prop(CharRange *cr, const char *prop_name) +{ + int prop_idx, ret; + + prop_idx = unicode_find_name(unicode_prop_name_table, prop_name); + if (prop_idx < 0) + return -2; + prop_idx += UNICODE_PROP_ASCII_Hex_Digit; + + ret = 0; + switch(prop_idx) { + case UNICODE_PROP_ASCII: + if (cr_add_interval(cr, 0x00, 0x7f + 1)) + return -1; + break; + case UNICODE_PROP_Any: + if (cr_add_interval(cr, 0x00000, 0x10ffff + 1)) + return -1; + break; + case UNICODE_PROP_Assigned: + ret = unicode_prop_ops(cr, + POP_GC, M(Cn), + POP_INVERT, + POP_END); + break; + case UNICODE_PROP_Math: + ret = unicode_prop_ops(cr, + POP_GC, M(Sm), + POP_PROP, UNICODE_PROP_Other_Math, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Lowercase: + ret = unicode_prop_ops(cr, + POP_GC, M(Ll), + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Uppercase: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Cased: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Alphabetic: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Alphabetic, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Grapheme_Base: + ret = unicode_prop_ops(cr, + POP_GC, M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn) | M(Zl) | M(Zp) | M(Me) | M(Mn), + POP_PROP, UNICODE_PROP_Other_Grapheme_Extend, + POP_UNION, + POP_INVERT, + POP_END); + break; + case UNICODE_PROP_Grapheme_Extend: + ret = unicode_prop_ops(cr, + POP_GC, M(Me) | M(Mn), + POP_PROP, UNICODE_PROP_Other_Grapheme_Extend, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_XID_Start: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), + POP_PROP, UNICODE_PROP_Other_ID_Start, + POP_UNION, + POP_PROP, UNICODE_PROP_Pattern_Syntax, + POP_PROP, UNICODE_PROP_Pattern_White_Space, + POP_UNION, + POP_PROP, UNICODE_PROP_XID_Start1, + POP_UNION, + POP_INVERT, + POP_INTER, + POP_END); + break; + case UNICODE_PROP_XID_Continue: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl) | + M(Mn) | M(Mc) | M(Nd) | M(Pc), + POP_PROP, UNICODE_PROP_Other_ID_Start, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_ID_Continue, + POP_UNION, + POP_PROP, UNICODE_PROP_Pattern_Syntax, + POP_PROP, UNICODE_PROP_Pattern_White_Space, + POP_UNION, + POP_PROP, UNICODE_PROP_XID_Continue1, + POP_UNION, + POP_INVERT, + POP_INTER, + POP_END); + break; + case UNICODE_PROP_Changes_When_Uppercased: + ret = unicode_case1(cr, CASE_U); + break; + case UNICODE_PROP_Changes_When_Lowercased: + ret = unicode_case1(cr, CASE_L); + break; + case UNICODE_PROP_Changes_When_Casemapped: + ret = unicode_case1(cr, CASE_U | CASE_L | CASE_F); + break; + case UNICODE_PROP_Changes_When_Titlecased: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_U, + POP_PROP, UNICODE_PROP_Changes_When_Titlecased1, + POP_XOR, + POP_END); + break; + case UNICODE_PROP_Changes_When_Casefolded: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_F, + POP_PROP, UNICODE_PROP_Changes_When_Casefolded1, + POP_XOR, + POP_END); + break; + case UNICODE_PROP_Changes_When_NFKC_Casefolded: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_F, + POP_PROP, UNICODE_PROP_Changes_When_NFKC_Casefolded1, + POP_XOR, + POP_END); + break; +#if 0 + case UNICODE_PROP_ID_Start: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), + POP_PROP, UNICODE_PROP_Other_ID_Start, + POP_UNION, + POP_PROP, UNICODE_PROP_Pattern_Syntax, + POP_PROP, UNICODE_PROP_Pattern_White_Space, + POP_UNION, + POP_INVERT, + POP_INTER, + POP_END); + break; + case UNICODE_PROP_ID_Continue: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl) | + M(Mn) | M(Mc) | M(Nd) | M(Pc), + POP_PROP, UNICODE_PROP_Other_ID_Start, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_ID_Continue, + POP_UNION, + POP_PROP, UNICODE_PROP_Pattern_Syntax, + POP_PROP, UNICODE_PROP_Pattern_White_Space, + POP_UNION, + POP_INVERT, + POP_INTER, + POP_END); + break; + case UNICODE_PROP_Case_Ignorable: + ret = unicode_prop_ops(cr, + POP_GC, M(Mn) | M(Cf) | M(Lm) | M(Sk), + POP_PROP, UNICODE_PROP_Case_Ignorable1, + POP_XOR, + POP_END); + break; +#else + /* we use the existing tables */ + case UNICODE_PROP_ID_Continue: + ret = unicode_prop_ops(cr, + POP_PROP, UNICODE_PROP_ID_Start, + POP_PROP, UNICODE_PROP_ID_Continue1, + POP_XOR, + POP_END); + break; +#endif + default: + if (prop_idx >= countof(unicode_prop_table)) + return -2; + ret = unicode_prop1(cr, prop_idx); + break; + } + return ret; +} + +#endif /* CONFIG_ALL_UNICODE */ diff --git a/source/engine/thirdparty/quickjs/libunicode.h b/source/engine/thirdparty/quickjs/libunicode.h new file mode 100644 index 0000000..f416157 --- /dev/null +++ b/source/engine/thirdparty/quickjs/libunicode.h @@ -0,0 +1,127 @@ +/* + * Unicode utilities + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * 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. + */ +#ifndef LIBUNICODE_H +#define LIBUNICODE_H + +#include + +#define LRE_BOOL int /* for documentation purposes */ + +/* define it to include all the unicode tables (40KB larger) */ +#define CONFIG_ALL_UNICODE + +#define LRE_CC_RES_LEN_MAX 3 + +typedef enum { + UNICODE_NFC, + UNICODE_NFD, + UNICODE_NFKC, + UNICODE_NFKD, +} UnicodeNormalizationEnum; + +int lre_case_conv(uint32_t *res, uint32_t c, int conv_type); +int lre_canonicalize(uint32_t c, LRE_BOOL is_unicode); +LRE_BOOL lre_is_cased(uint32_t c); +LRE_BOOL lre_is_case_ignorable(uint32_t c); + +/* char ranges */ + +typedef struct { + int len; /* in points, always even */ + int size; + uint32_t *points; /* points sorted by increasing value */ + void *mem_opaque; + void *(*realloc_func)(void *opaque, void *ptr, size_t size); +} CharRange; + +typedef enum { + CR_OP_UNION, + CR_OP_INTER, + CR_OP_XOR, +} CharRangeOpEnum; + +void cr_init(CharRange *cr, void *mem_opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); +void cr_free(CharRange *cr); +int cr_realloc(CharRange *cr, int size); +int cr_copy(CharRange *cr, const CharRange *cr1); + +static inline int cr_add_point(CharRange *cr, uint32_t v) +{ + if (cr->len >= cr->size) { + if (cr_realloc(cr, cr->len + 1)) + return -1; + } + cr->points[cr->len++] = v; + return 0; +} + +static inline int cr_add_interval(CharRange *cr, uint32_t c1, uint32_t c2) +{ + if ((cr->len + 2) > cr->size) { + if (cr_realloc(cr, cr->len + 2)) + return -1; + } + cr->points[cr->len++] = c1; + cr->points[cr->len++] = c2; + return 0; +} + +int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len); + +static inline int cr_union_interval(CharRange *cr, uint32_t c1, uint32_t c2) +{ + uint32_t b_pt[2]; + b_pt[0] = c1; + b_pt[1] = c2 + 1; + return cr_union1(cr, b_pt, 2); +} + +int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, + const uint32_t *b_pt, int b_len, int op); + +int cr_invert(CharRange *cr); + +int cr_regexp_canonicalize(CharRange *cr, LRE_BOOL is_unicode); + +#ifdef CONFIG_ALL_UNICODE + +LRE_BOOL lre_is_id_start(uint32_t c); +LRE_BOOL lre_is_id_continue(uint32_t c); + +int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, + UnicodeNormalizationEnum n_type, + void *opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); + +/* Unicode character range functions */ + +int unicode_script(CharRange *cr, + const char *script_name, LRE_BOOL is_ext); +int unicode_general_category(CharRange *cr, const char *gc_name); +int unicode_prop(CharRange *cr, const char *prop_name); + +#endif /* CONFIG_ALL_UNICODE */ + +#undef LRE_BOOL + +#endif /* LIBUNICODE_H */ diff --git a/source/engine/thirdparty/quickjs/list.h b/source/engine/thirdparty/quickjs/list.h new file mode 100644 index 0000000..8098311 --- /dev/null +++ b/source/engine/thirdparty/quickjs/list.h @@ -0,0 +1,99 @@ +/* + * Linux klist like system + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * 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. + */ +#ifndef LIST_H +#define LIST_H + +#ifndef NULL +#include +#endif + +struct list_head { + struct list_head *prev; + struct list_head *next; +}; + +#define LIST_HEAD_INIT(el) { &(el), &(el) } + +/* return the pointer of type 'type *' containing 'el' as field 'member' */ +#define list_entry(el, type, member) container_of(el, type, member) + +static inline void init_list_head(struct list_head *head) +{ + head->prev = head; + head->next = head; +} + +/* insert 'el' between 'prev' and 'next' */ +static inline void __list_add(struct list_head *el, + struct list_head *prev, struct list_head *next) +{ + prev->next = el; + el->prev = prev; + el->next = next; + next->prev = el; +} + +/* add 'el' at the head of the list 'head' (= after element head) */ +static inline void list_add(struct list_head *el, struct list_head *head) +{ + __list_add(el, head, head->next); +} + +/* add 'el' at the end of the list 'head' (= before element head) */ +static inline void list_add_tail(struct list_head *el, struct list_head *head) +{ + __list_add(el, head->prev, head); +} + +static inline void list_del(struct list_head *el) +{ + struct list_head *prev, *next; + prev = el->prev; + next = el->next; + prev->next = next; + next->prev = prev; + el->prev = NULL; /* fail safe */ + el->next = NULL; /* fail safe */ +} + +static inline int list_empty(struct list_head *el) +{ + return el->next == el; +} + +#define list_for_each(el, head) \ + for(el = (head)->next; el != (head); el = el->next) + +#define list_for_each_safe(el, el1, head) \ + for(el = (head)->next, el1 = el->next; el != (head); \ + el = el1, el1 = el->next) + +#define list_for_each_prev(el, head) \ + for(el = (head)->prev; el != (head); el = el->prev) + +#define list_for_each_prev_safe(el, el1, head) \ + for(el = (head)->prev, el1 = el->prev; el != (head); \ + el = el1, el1 = el->prev) + +#endif /* LIST_H */ diff --git a/source/engine/thirdparty/quickjs/qjs.c b/source/engine/thirdparty/quickjs/qjs.c new file mode 100644 index 0000000..0332895 --- /dev/null +++ b/source/engine/thirdparty/quickjs/qjs.c @@ -0,0 +1,564 @@ +/* + * QuickJS stand alone interpreter + * + * Copyright (c) 2017-2021 Fabrice Bellard + * Copyright (c) 2017-2021 Charlie Gordon + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__APPLE__) +#include +#elif defined(__linux__) +#include +#elif defined(__FreeBSD__) +#include +#endif + +#include "cutils.h" +#include "quickjs-libc.h" + +extern const uint8_t qjsc_repl[]; +extern const uint32_t qjsc_repl_size; +#ifdef CONFIG_BIGNUM +extern const uint8_t qjsc_qjscalc[]; +extern const uint32_t qjsc_qjscalc_size; +static int bignum_ext; +#endif + +static int eval_buf(JSContext *ctx, const void *buf, int buf_len, + const char *filename, int eval_flags) +{ + JSValue val; + int ret; + + if ((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE) { + /* for the modules, we compile then run to be able to set + import.meta */ + val = JS_Eval(ctx, buf, buf_len, filename, + eval_flags | JS_EVAL_FLAG_COMPILE_ONLY); + if (!JS_IsException(val)) { + js_module_set_import_meta(ctx, val, TRUE, TRUE); + val = JS_EvalFunction(ctx, val); + } + val = js_std_await(ctx, val); + } else { + val = JS_Eval(ctx, buf, buf_len, filename, eval_flags); + } + if (JS_IsException(val)) { + js_std_dump_error(ctx); + ret = -1; + } else { + ret = 0; + } + JS_FreeValue(ctx, val); + return ret; +} + +static int eval_file(JSContext *ctx, const char *filename, int module) +{ + uint8_t *buf; + int ret, eval_flags; + size_t buf_len; + + buf = js_load_file(ctx, &buf_len, filename); + if (!buf) { + perror(filename); + exit(1); + } + + if (module < 0) { + module = (has_suffix(filename, ".mjs") || + JS_DetectModule((const char *)buf, buf_len)); + } + if (module) + eval_flags = JS_EVAL_TYPE_MODULE; + else + eval_flags = JS_EVAL_TYPE_GLOBAL; + ret = eval_buf(ctx, buf, buf_len, filename, eval_flags); + js_free(ctx, buf); + return ret; +} + +/* also used to initialize the worker context */ +static JSContext *JS_NewCustomContext(JSRuntime *rt) +{ + JSContext *ctx; + ctx = JS_NewContext(rt); + if (!ctx) + return NULL; +#ifdef CONFIG_BIGNUM + if (bignum_ext) { + JS_AddIntrinsicBigFloat(ctx); + JS_AddIntrinsicBigDecimal(ctx); + JS_AddIntrinsicOperators(ctx); + JS_EnableBignumExt(ctx, TRUE); + } +#endif + /* system modules */ + js_init_module_std(ctx, "std"); + js_init_module_os(ctx, "os"); + return ctx; +} + +#if defined(__APPLE__) +#define MALLOC_OVERHEAD 0 +#else +#define MALLOC_OVERHEAD 8 +#endif + +struct trace_malloc_data { + uint8_t *base; +}; + +static inline unsigned long long js_trace_malloc_ptr_offset(uint8_t *ptr, + struct trace_malloc_data *dp) +{ + return ptr - dp->base; +} + +/* default memory allocation functions with memory limitation */ +static size_t js_trace_malloc_usable_size(const void *ptr) +{ +#if defined(__APPLE__) + return malloc_size(ptr); +#elif defined(_WIN32) + return _msize((void *)ptr); +#elif defined(EMSCRIPTEN) + return 0; +#elif defined(__linux__) + return malloc_usable_size((void *)ptr); +#else + /* change this to `return 0;` if compilation fails */ + return malloc_usable_size((void *)ptr); +#endif +} + +static void +#ifdef _WIN32 +/* mingw printf is used */ +__attribute__((format(gnu_printf, 2, 3))) +#else +__attribute__((format(printf, 2, 3))) +#endif + js_trace_malloc_printf(JSMallocState *s, const char *fmt, ...) +{ + va_list ap; + int c; + + va_start(ap, fmt); + while ((c = *fmt++) != '\0') { + if (c == '%') { + /* only handle %p and %zd */ + if (*fmt == 'p') { + uint8_t *ptr = va_arg(ap, void *); + if (ptr == NULL) { + printf("NULL"); + } else { + printf("H%+06lld.%zd", + js_trace_malloc_ptr_offset(ptr, s->opaque), + js_trace_malloc_usable_size(ptr)); + } + fmt++; + continue; + } + if (fmt[0] == 'z' && fmt[1] == 'd') { + size_t sz = va_arg(ap, size_t); + printf("%zd", sz); + fmt += 2; + continue; + } + } + putc(c, stdout); + } + va_end(ap); +} + +static void js_trace_malloc_init(struct trace_malloc_data *s) +{ + free(s->base = malloc(8)); +} + +static void *js_trace_malloc(JSMallocState *s, size_t size) +{ + void *ptr; + + /* Do not allocate zero bytes: behavior is platform dependent */ + assert(size != 0); + + if (unlikely(s->malloc_size + size > s->malloc_limit)) + return NULL; + ptr = malloc(size); + js_trace_malloc_printf(s, "A %zd -> %p\n", size, ptr); + if (ptr) { + s->malloc_count++; + s->malloc_size += js_trace_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + } + return ptr; +} + +static void js_trace_free(JSMallocState *s, void *ptr) +{ + if (!ptr) + return; + + js_trace_malloc_printf(s, "F %p\n", ptr); + s->malloc_count--; + s->malloc_size -= js_trace_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + free(ptr); +} + +static void *js_trace_realloc(JSMallocState *s, void *ptr, size_t size) +{ + size_t old_size; + + if (!ptr) { + if (size == 0) + return NULL; + return js_trace_malloc(s, size); + } + old_size = js_trace_malloc_usable_size(ptr); + if (size == 0) { + js_trace_malloc_printf(s, "R %zd %p\n", size, ptr); + s->malloc_count--; + s->malloc_size -= old_size + MALLOC_OVERHEAD; + free(ptr); + return NULL; + } + if (s->malloc_size + size - old_size > s->malloc_limit) + return NULL; + + js_trace_malloc_printf(s, "R %zd %p", size, ptr); + + ptr = realloc(ptr, size); + js_trace_malloc_printf(s, " -> %p\n", ptr); + if (ptr) { + s->malloc_size += js_trace_malloc_usable_size(ptr) - old_size; + } + return ptr; +} + +static const JSMallocFunctions trace_mf = { + js_trace_malloc, + js_trace_free, + js_trace_realloc, + js_trace_malloc_usable_size, +}; + +#define PROG_NAME "qjs" + +void help(void) +{ + printf("QuickJS version " CONFIG_VERSION "\n" + "usage: " PROG_NAME " [options] [file [args]]\n" + "-h --help list options\n" + "-e --eval EXPR evaluate EXPR\n" + "-i --interactive go to interactive mode\n" + "-m --module load as ES6 module (default=autodetect)\n" + " --script load as ES6 script (default=autodetect)\n" + "-I --include file include an additional file\n" + " --std make 'std' and 'os' available to the loaded script\n" +#ifdef CONFIG_BIGNUM + " --bignum enable the bignum extensions (BigFloat, BigDecimal)\n" + " --qjscalc load the QJSCalc runtime (default if invoked as qjscalc)\n" +#endif + "-T --trace trace memory allocation\n" + "-d --dump dump the memory usage stats\n" + " --memory-limit n limit the memory usage to 'n' bytes\n" + " --stack-size n limit the stack size to 'n' bytes\n" + " --unhandled-rejection dump unhandled promise rejections\n" + "-q --quit just instantiate the interpreter and quit\n"); + exit(1); +} + +int main(int argc, char **argv) +{ + JSRuntime *rt; + JSContext *ctx; + struct trace_malloc_data trace_data = { NULL }; + int optind; + char *expr = NULL; + int interactive = 0; + int dump_memory = 0; + int trace_memory = 0; + int empty_run = 0; + int module = -1; + int load_std = 0; + int dump_unhandled_promise_rejection = 0; + size_t memory_limit = 0; + char *include_list[32]; + int i, include_count = 0; +#ifdef CONFIG_BIGNUM + int load_jscalc; +#endif + size_t stack_size = 0; + +#ifdef CONFIG_BIGNUM + /* load jscalc runtime if invoked as 'qjscalc' */ + { + const char *p, *exename; + exename = argv[0]; + p = strrchr(exename, '/'); + if (p) + exename = p + 1; + load_jscalc = !strcmp(exename, "qjscalc"); + } +#endif + + /* cannot use getopt because we want to pass the command line to + the script */ + optind = 1; + while (optind < argc && *argv[optind] == '-') { + char *arg = argv[optind] + 1; + const char *longopt = ""; + /* a single - is not an option, it also stops argument scanning */ + if (!*arg) + break; + optind++; + if (*arg == '-') { + longopt = arg + 1; + arg += strlen(arg); + /* -- stops argument scanning */ + if (!*longopt) + break; + } + for (; *arg || *longopt; longopt = "") { + char opt = *arg; + if (opt) + arg++; + if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) { + help(); + continue; + } + if (opt == 'e' || !strcmp(longopt, "eval")) { + if (*arg) { + expr = arg; + break; + } + if (optind < argc) { + expr = argv[optind++]; + break; + } + fprintf(stderr, "qjs: missing expression for -e\n"); + exit(2); + } + if (opt == 'I' || !strcmp(longopt, "include")) { + if (optind >= argc) { + fprintf(stderr, "expecting filename"); + exit(1); + } + if (include_count >= countof(include_list)) { + fprintf(stderr, "too many included files"); + exit(1); + } + include_list[include_count++] = argv[optind++]; + continue; + } + if (opt == 'i' || !strcmp(longopt, "interactive")) { + interactive++; + continue; + } + if (opt == 'm' || !strcmp(longopt, "module")) { + module = 1; + continue; + } + if (!strcmp(longopt, "script")) { + module = 0; + continue; + } + if (opt == 'd' || !strcmp(longopt, "dump")) { + dump_memory++; + continue; + } + if (opt == 'T' || !strcmp(longopt, "trace")) { + trace_memory++; + continue; + } + if (!strcmp(longopt, "std")) { + load_std = 1; + continue; + } + if (!strcmp(longopt, "unhandled-rejection")) { + dump_unhandled_promise_rejection = 1; + continue; + } +#ifdef CONFIG_BIGNUM + if (!strcmp(longopt, "bignum")) { + bignum_ext = 1; + continue; + } + if (!strcmp(longopt, "qjscalc")) { + load_jscalc = 1; + continue; + } +#endif + if (opt == 'q' || !strcmp(longopt, "quit")) { + empty_run++; + continue; + } + if (!strcmp(longopt, "memory-limit")) { + if (optind >= argc) { + fprintf(stderr, "expecting memory limit"); + exit(1); + } + memory_limit = (size_t)strtod(argv[optind++], NULL); + continue; + } + if (!strcmp(longopt, "stack-size")) { + if (optind >= argc) { + fprintf(stderr, "expecting stack size"); + exit(1); + } + stack_size = (size_t)strtod(argv[optind++], NULL); + continue; + } + if (opt) { + fprintf(stderr, "qjs: unknown option '-%c'\n", opt); + } else { + fprintf(stderr, "qjs: unknown option '--%s'\n", longopt); + } + help(); + } + } + +#ifdef CONFIG_BIGNUM + if (load_jscalc) + bignum_ext = 1; +#endif + + if (trace_memory) { + js_trace_malloc_init(&trace_data); + rt = JS_NewRuntime2(&trace_mf, &trace_data); + } else { + rt = JS_NewRuntime(); + } + if (!rt) { + fprintf(stderr, "qjs: cannot allocate JS runtime\n"); + exit(2); + } + if (memory_limit != 0) + JS_SetMemoryLimit(rt, memory_limit); + if (stack_size != 0) + JS_SetMaxStackSize(rt, stack_size); + js_std_set_worker_new_context_func(JS_NewCustomContext); + js_std_init_handlers(rt); + ctx = JS_NewCustomContext(rt); + if (!ctx) { + fprintf(stderr, "qjs: cannot allocate JS context\n"); + exit(2); + } + + /* loader for ES6 modules */ + JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL); + + if (dump_unhandled_promise_rejection) { + JS_SetHostPromiseRejectionTracker(rt, js_std_promise_rejection_tracker, + NULL); + } + + if (!empty_run) { +#ifdef CONFIG_BIGNUM + if (load_jscalc) { + js_std_eval_binary(ctx, qjsc_qjscalc, qjsc_qjscalc_size, 0); + } +#endif + js_std_add_helpers(ctx, argc - optind, argv + optind); + + /* make 'std' and 'os' visible to non module code */ + if (load_std) { + const char *str = "import * as std from 'std';\n" + "import * as os from 'os';\n" + "globalThis.std = std;\n" + "globalThis.os = os;\n"; + eval_buf(ctx, str, strlen(str), "", JS_EVAL_TYPE_MODULE); + } + + for(i = 0; i < include_count; i++) { + if (eval_file(ctx, include_list[i], module)) + goto fail; + } + + if (expr) { + if (eval_buf(ctx, expr, strlen(expr), "", 0)) + goto fail; + } else + if (optind >= argc) { + /* interactive mode */ + interactive = 1; + } else { + const char *filename; + filename = argv[optind]; + if (eval_file(ctx, filename, module)) + goto fail; + } + if (interactive) { + js_std_eval_binary(ctx, qjsc_repl, qjsc_repl_size, 0); + } + js_std_loop(ctx); + } + + if (dump_memory) { + JSMemoryUsage stats; + JS_ComputeMemoryUsage(rt, &stats); + JS_DumpMemoryUsage(stdout, &stats, rt); + } + js_std_free_handlers(rt); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + if (empty_run && dump_memory) { + clock_t t[5]; + double best[5]; + int i, j; + for (i = 0; i < 100; i++) { + t[0] = clock(); + rt = JS_NewRuntime(); + t[1] = clock(); + ctx = JS_NewContext(rt); + t[2] = clock(); + JS_FreeContext(ctx); + t[3] = clock(); + JS_FreeRuntime(rt); + t[4] = clock(); + for (j = 4; j > 0; j--) { + double ms = 1000.0 * (t[j] - t[j - 1]) / CLOCKS_PER_SEC; + if (i == 0 || best[j] > ms) + best[j] = ms; + } + } + printf("\nInstantiation times (ms): %.3f = %.3f+%.3f+%.3f+%.3f\n", + best[1] + best[2] + best[3] + best[4], + best[1], best[2], best[3], best[4]); + } + return 0; + fail: + js_std_free_handlers(rt); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return 1; +} diff --git a/source/engine/thirdparty/quickjs/qjsc.c b/source/engine/thirdparty/quickjs/qjsc.c new file mode 100644 index 0000000..46f52a6 --- /dev/null +++ b/source/engine/thirdparty/quickjs/qjsc.c @@ -0,0 +1,761 @@ +/* + * QuickJS command line compiler + * + * Copyright (c) 2018-2021 Fabrice Bellard + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#if !defined(_WIN32) +#include +#endif + +#include "cutils.h" +#include "quickjs-libc.h" + +typedef struct { + char *name; + char *short_name; + int flags; +} namelist_entry_t; + +typedef struct namelist_t { + namelist_entry_t *array; + int count; + int size; +} namelist_t; + +typedef struct { + const char *option_name; + const char *init_name; +} FeatureEntry; + +static namelist_t cname_list; +static namelist_t cmodule_list; +static namelist_t init_module_list; +static uint64_t feature_bitmap; +static FILE *outfile; +static BOOL byte_swap; +static BOOL dynamic_export; +static const char *c_ident_prefix = "qjsc_"; + +#define FE_ALL (-1) + +static const FeatureEntry feature_list[] = { + { "date", "Date" }, + { "eval", "Eval" }, + { "string-normalize", "StringNormalize" }, + { "regexp", "RegExp" }, + { "json", "JSON" }, + { "proxy", "Proxy" }, + { "map", "MapSet" }, + { "typedarray", "TypedArrays" }, + { "promise", "Promise" }, +#define FE_MODULE_LOADER 9 + { "module-loader", NULL }, + { "bigint", "BigInt" }, +}; + +void namelist_add(namelist_t *lp, const char *name, const char *short_name, + int flags) +{ + namelist_entry_t *e; + if (lp->count == lp->size) { + size_t newsize = lp->size + (lp->size >> 1) + 4; + namelist_entry_t *a = + realloc(lp->array, sizeof(lp->array[0]) * newsize); + /* XXX: check for realloc failure */ + lp->array = a; + lp->size = newsize; + } + e = &lp->array[lp->count++]; + e->name = strdup(name); + if (short_name) + e->short_name = strdup(short_name); + else + e->short_name = NULL; + e->flags = flags; +} + +void namelist_free(namelist_t *lp) +{ + while (lp->count > 0) { + namelist_entry_t *e = &lp->array[--lp->count]; + free(e->name); + free(e->short_name); + } + free(lp->array); + lp->array = NULL; + lp->size = 0; +} + +namelist_entry_t *namelist_find(namelist_t *lp, const char *name) +{ + int i; + for(i = 0; i < lp->count; i++) { + namelist_entry_t *e = &lp->array[i]; + if (!strcmp(e->name, name)) + return e; + } + return NULL; +} + +static void get_c_name(char *buf, size_t buf_size, const char *file) +{ + const char *p, *r; + size_t len, i; + int c; + char *q; + + p = strrchr(file, '/'); + if (!p) + p = file; + else + p++; + r = strrchr(p, '.'); + if (!r) + len = strlen(p); + else + len = r - p; + pstrcpy(buf, buf_size, c_ident_prefix); + q = buf + strlen(buf); + for(i = 0; i < len; i++) { + c = p[i]; + if (!((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'))) { + c = '_'; + } + if ((q - buf) < buf_size - 1) + *q++ = c; + } + *q = '\0'; +} + +static void dump_hex(FILE *f, const uint8_t *buf, size_t len) +{ + size_t i, col; + col = 0; + for(i = 0; i < len; i++) { + fprintf(f, " 0x%02x,", buf[i]); + if (++col == 8) { + fprintf(f, "\n"); + col = 0; + } + } + if (col != 0) + fprintf(f, "\n"); +} + +static void output_object_code(JSContext *ctx, + FILE *fo, JSValueConst obj, const char *c_name, + BOOL load_only) +{ + uint8_t *out_buf; + size_t out_buf_len; + int flags; + flags = JS_WRITE_OBJ_BYTECODE; + if (byte_swap) + flags |= JS_WRITE_OBJ_BSWAP; + out_buf = JS_WriteObject(ctx, &out_buf_len, obj, flags); + if (!out_buf) { + js_std_dump_error(ctx); + exit(1); + } + + namelist_add(&cname_list, c_name, NULL, load_only); + + fprintf(fo, "const uint32_t %s_size = %u;\n\n", + c_name, (unsigned int)out_buf_len); + fprintf(fo, "const uint8_t %s[%u] = {\n", + c_name, (unsigned int)out_buf_len); + dump_hex(fo, out_buf, out_buf_len); + fprintf(fo, "};\n\n"); + + js_free(ctx, out_buf); +} + +static int js_module_dummy_init(JSContext *ctx, JSModuleDef *m) +{ + /* should never be called when compiling JS code */ + abort(); +} + +static void find_unique_cname(char *cname, size_t cname_size) +{ + char cname1[1024]; + int suffix_num; + size_t len, max_len; + assert(cname_size >= 32); + /* find a C name not matching an existing module C name by + adding a numeric suffix */ + len = strlen(cname); + max_len = cname_size - 16; + if (len > max_len) + cname[max_len] = '\0'; + suffix_num = 1; + for(;;) { + snprintf(cname1, sizeof(cname1), "%s_%d", cname, suffix_num); + if (!namelist_find(&cname_list, cname1)) + break; + suffix_num++; + } + pstrcpy(cname, cname_size, cname1); +} + +JSModuleDef *jsc_module_loader(JSContext *ctx, + const char *module_name, void *opaque) +{ + JSModuleDef *m; + namelist_entry_t *e; + + /* check if it is a declared C or system module */ + e = namelist_find(&cmodule_list, module_name); + if (e) { + /* add in the static init module list */ + namelist_add(&init_module_list, e->name, e->short_name, 0); + /* create a dummy module */ + m = JS_NewCModule(ctx, module_name, js_module_dummy_init); + } else if (has_suffix(module_name, ".so")) { + fprintf(stderr, "Warning: binary module '%s' will be dynamically loaded\n", module_name); + /* create a dummy module */ + m = JS_NewCModule(ctx, module_name, js_module_dummy_init); + /* the resulting executable will export its symbols for the + dynamic library */ + dynamic_export = TRUE; + } else { + size_t buf_len; + uint8_t *buf; + JSValue func_val; + char cname[1024]; + + buf = js_load_file(ctx, &buf_len, module_name); + if (!buf) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s'", + module_name); + return NULL; + } + + /* compile the module */ + func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + js_free(ctx, buf); + if (JS_IsException(func_val)) + return NULL; + get_c_name(cname, sizeof(cname), module_name); + if (namelist_find(&cname_list, cname)) { + find_unique_cname(cname, sizeof(cname)); + } + output_object_code(ctx, outfile, func_val, cname, TRUE); + + /* the module is already referenced, so we must free it */ + m = JS_VALUE_GET_PTR(func_val); + JS_FreeValue(ctx, func_val); + } + return m; +} + +static void compile_file(JSContext *ctx, FILE *fo, + const char *filename, + const char *c_name1, + int module) +{ + uint8_t *buf; + char c_name[1024]; + int eval_flags; + JSValue obj; + size_t buf_len; + + buf = js_load_file(ctx, &buf_len, filename); + if (!buf) { + fprintf(stderr, "Could not load '%s'\n", filename); + exit(1); + } + eval_flags = JS_EVAL_FLAG_COMPILE_ONLY; + if (module < 0) { + module = (has_suffix(filename, ".mjs") || + JS_DetectModule((const char *)buf, buf_len)); + } + if (module) + eval_flags |= JS_EVAL_TYPE_MODULE; + else + eval_flags |= JS_EVAL_TYPE_GLOBAL; + obj = JS_Eval(ctx, (const char *)buf, buf_len, filename, eval_flags); + if (JS_IsException(obj)) { + js_std_dump_error(ctx); + exit(1); + } + js_free(ctx, buf); + if (c_name1) { + pstrcpy(c_name, sizeof(c_name), c_name1); + } else { + get_c_name(c_name, sizeof(c_name), filename); + } + output_object_code(ctx, fo, obj, c_name, FALSE); + JS_FreeValue(ctx, obj); +} + +static const char main_c_template1[] = + "int main(int argc, char **argv)\n" + "{\n" + " JSRuntime *rt;\n" + " JSContext *ctx;\n" + " rt = JS_NewRuntime();\n" + " js_std_set_worker_new_context_func(JS_NewCustomContext);\n" + " js_std_init_handlers(rt);\n" + ; + +static const char main_c_template2[] = + " js_std_loop(ctx);\n" + " js_std_free_handlers(rt);\n" + " JS_FreeContext(ctx);\n" + " JS_FreeRuntime(rt);\n" + " return 0;\n" + "}\n"; + +#define PROG_NAME "qjsc" + +void help(void) +{ + printf("QuickJS Compiler version " CONFIG_VERSION "\n" + "usage: " PROG_NAME " [options] [files]\n" + "\n" + "options are:\n" + "-c only output bytecode to a C file\n" + "-e output main() and bytecode to a C file (default = executable output)\n" + "-o output set the output filename\n" + "-N cname set the C name of the generated data\n" + "-m compile as Javascript module (default=autodetect)\n" + "-D module_name compile a dynamically loaded module or worker\n" + "-M module_name[,cname] add initialization code for an external C module\n" + "-x byte swapped output\n" + "-p prefix set the prefix of the generated C names\n" + "-S n set the maximum stack size to 'n' bytes (default=%d)\n", + JS_DEFAULT_STACK_SIZE); +#ifdef CONFIG_LTO + { + int i; + printf("-flto use link time optimization\n"); + printf("-fbignum enable bignum extensions\n"); + printf("-fno-["); + for(i = 0; i < countof(feature_list); i++) { + if (i != 0) + printf("|"); + printf("%s", feature_list[i].option_name); + } + printf("]\n" + " disable selected language features (smaller code size)\n"); + } +#endif + exit(1); +} + +#if defined(CONFIG_CC) && !defined(_WIN32) + +int exec_cmd(char **argv) +{ + int pid, status, ret; + + pid = fork(); + if (pid == 0) { + execvp(argv[0], argv); + exit(1); + } + + for(;;) { + ret = waitpid(pid, &status, 0); + if (ret == pid && WIFEXITED(status)) + break; + } + return WEXITSTATUS(status); +} + +static int output_executable(const char *out_filename, const char *cfilename, + BOOL use_lto, BOOL verbose, const char *exename) +{ + const char *argv[64]; + const char **arg, *bn_suffix, *lto_suffix; + char libjsname[1024]; + char exe_dir[1024], inc_dir[1024], lib_dir[1024], buf[1024], *p; + int ret; + + /* get the directory of the executable */ + pstrcpy(exe_dir, sizeof(exe_dir), exename); + p = strrchr(exe_dir, '/'); + if (p) { + *p = '\0'; + } else { + pstrcpy(exe_dir, sizeof(exe_dir), "."); + } + + /* if 'quickjs.h' is present at the same path as the executable, we + use it as include and lib directory */ + snprintf(buf, sizeof(buf), "%s/quickjs.h", exe_dir); + if (access(buf, R_OK) == 0) { + pstrcpy(inc_dir, sizeof(inc_dir), exe_dir); + pstrcpy(lib_dir, sizeof(lib_dir), exe_dir); + } else { + snprintf(inc_dir, sizeof(inc_dir), "%s/include/quickjs", CONFIG_PREFIX); + snprintf(lib_dir, sizeof(lib_dir), "%s/lib/quickjs", CONFIG_PREFIX); + } + + lto_suffix = ""; + bn_suffix = ""; + + arg = argv; + *arg++ = CONFIG_CC; + *arg++ = "-O2"; +#ifdef CONFIG_LTO + if (use_lto) { + *arg++ = "-flto"; + lto_suffix = ".lto"; + } +#endif + /* XXX: use the executable path to find the includes files and + libraries */ + *arg++ = "-D"; + *arg++ = "_GNU_SOURCE"; + *arg++ = "-I"; + *arg++ = inc_dir; + *arg++ = "-o"; + *arg++ = out_filename; + if (dynamic_export) + *arg++ = "-rdynamic"; + *arg++ = cfilename; + snprintf(libjsname, sizeof(libjsname), "%s/libquickjs%s%s.a", + lib_dir, bn_suffix, lto_suffix); + *arg++ = libjsname; + *arg++ = "-lm"; + *arg++ = "-ldl"; + *arg++ = "-lpthread"; + *arg = NULL; + + if (verbose) { + for(arg = argv; *arg != NULL; arg++) + printf("%s ", *arg); + printf("\n"); + } + + ret = exec_cmd((char **)argv); + unlink(cfilename); + return ret; +} +#else +static int output_executable(const char *out_filename, const char *cfilename, + BOOL use_lto, BOOL verbose, const char *exename) +{ + fprintf(stderr, "Executable output is not supported for this target\n"); + exit(1); + return 0; +} +#endif + + +typedef enum { + OUTPUT_C, + OUTPUT_C_MAIN, + OUTPUT_EXECUTABLE, +} OutputTypeEnum; + +int main(int argc, char **argv) +{ + int c, i, verbose; + const char *out_filename, *cname; + char cfilename[1024]; + FILE *fo; + JSRuntime *rt; + JSContext *ctx; + BOOL use_lto; + int module; + OutputTypeEnum output_type; + size_t stack_size; +#ifdef CONFIG_BIGNUM + BOOL bignum_ext = FALSE; +#endif + namelist_t dynamic_module_list; + + out_filename = NULL; + output_type = OUTPUT_EXECUTABLE; + cname = NULL; + feature_bitmap = FE_ALL; + module = -1; + byte_swap = FALSE; + verbose = 0; + use_lto = FALSE; + stack_size = 0; + memset(&dynamic_module_list, 0, sizeof(dynamic_module_list)); + + /* add system modules */ + namelist_add(&cmodule_list, "std", "std", 0); + namelist_add(&cmodule_list, "os", "os", 0); + + for(;;) { + c = getopt(argc, argv, "ho:cN:f:mxevM:p:S:D:"); + if (c == -1) + break; + switch(c) { + case 'h': + help(); + case 'o': + out_filename = optarg; + break; + case 'c': + output_type = OUTPUT_C; + break; + case 'e': + output_type = OUTPUT_C_MAIN; + break; + case 'N': + cname = optarg; + break; + case 'f': + { + const char *p; + p = optarg; + if (!strcmp(optarg, "lto")) { + use_lto = TRUE; + } else if (strstart(p, "no-", &p)) { + use_lto = TRUE; + for(i = 0; i < countof(feature_list); i++) { + if (!strcmp(p, feature_list[i].option_name)) { + feature_bitmap &= ~((uint64_t)1 << i); + break; + } + } + if (i == countof(feature_list)) + goto bad_feature; + } else +#ifdef CONFIG_BIGNUM + if (!strcmp(optarg, "bignum")) { + bignum_ext = TRUE; + } else +#endif + { + bad_feature: + fprintf(stderr, "unsupported feature: %s\n", optarg); + exit(1); + } + } + break; + case 'm': + module = 1; + break; + case 'M': + { + char *p; + char path[1024]; + char cname[1024]; + pstrcpy(path, sizeof(path), optarg); + p = strchr(path, ','); + if (p) { + *p = '\0'; + pstrcpy(cname, sizeof(cname), p + 1); + } else { + get_c_name(cname, sizeof(cname), path); + } + namelist_add(&cmodule_list, path, cname, 0); + } + break; + case 'D': + namelist_add(&dynamic_module_list, optarg, NULL, 0); + break; + case 'x': + byte_swap = TRUE; + break; + case 'v': + verbose++; + break; + case 'p': + c_ident_prefix = optarg; + break; + case 'S': + stack_size = (size_t)strtod(optarg, NULL); + break; + default: + break; + } + } + + if (optind >= argc) + help(); + + if (!out_filename) { + if (output_type == OUTPUT_EXECUTABLE) { + out_filename = "a.out"; + } else { + out_filename = "out.c"; + } + } + + if (output_type == OUTPUT_EXECUTABLE) { +#if defined(_WIN32) || defined(__ANDROID__) + /* XXX: find a /tmp directory ? */ + snprintf(cfilename, sizeof(cfilename), "out%d.c", getpid()); +#else + snprintf(cfilename, sizeof(cfilename), "/tmp/out%d.c", getpid()); +#endif + } else { + pstrcpy(cfilename, sizeof(cfilename), out_filename); + } + + fo = fopen(cfilename, "w"); + if (!fo) { + perror(cfilename); + exit(1); + } + outfile = fo; + + rt = JS_NewRuntime(); + ctx = JS_NewContext(rt); +#ifdef CONFIG_BIGNUM + if (bignum_ext) { + JS_AddIntrinsicBigFloat(ctx); + JS_AddIntrinsicBigDecimal(ctx); + JS_AddIntrinsicOperators(ctx); + JS_EnableBignumExt(ctx, TRUE); + } +#endif + + /* loader for ES6 modules */ + JS_SetModuleLoaderFunc(rt, NULL, jsc_module_loader, NULL); + + fprintf(fo, "/* File generated automatically by the QuickJS compiler. */\n" + "\n" + ); + + if (output_type != OUTPUT_C) { + fprintf(fo, "#include \"quickjs-libc.h\"\n" + "\n" + ); + } else { + fprintf(fo, "#include \n" + "\n" + ); + } + + for(i = optind; i < argc; i++) { + const char *filename = argv[i]; + compile_file(ctx, fo, filename, cname, module); + cname = NULL; + } + + for(i = 0; i < dynamic_module_list.count; i++) { + if (!jsc_module_loader(ctx, dynamic_module_list.array[i].name, NULL)) { + fprintf(stderr, "Could not load dynamic module '%s'\n", + dynamic_module_list.array[i].name); + exit(1); + } + } + + if (output_type != OUTPUT_C) { + fprintf(fo, + "static JSContext *JS_NewCustomContext(JSRuntime *rt)\n" + "{\n" + " JSContext *ctx = JS_NewContextRaw(rt);\n" + " if (!ctx)\n" + " return NULL;\n"); + /* add the basic objects */ + fprintf(fo, " JS_AddIntrinsicBaseObjects(ctx);\n"); + for(i = 0; i < countof(feature_list); i++) { + if ((feature_bitmap & ((uint64_t)1 << i)) && + feature_list[i].init_name) { + fprintf(fo, " JS_AddIntrinsic%s(ctx);\n", + feature_list[i].init_name); + } + } +#ifdef CONFIG_BIGNUM + if (bignum_ext) { + fprintf(fo, + " JS_AddIntrinsicBigFloat(ctx);\n" + " JS_AddIntrinsicBigDecimal(ctx);\n" + " JS_AddIntrinsicOperators(ctx);\n" + " JS_EnableBignumExt(ctx, 1);\n"); + } +#endif + /* add the precompiled modules (XXX: could modify the module + loader instead) */ + for(i = 0; i < init_module_list.count; i++) { + namelist_entry_t *e = &init_module_list.array[i]; + /* initialize the static C modules */ + + fprintf(fo, + " {\n" + " extern JSModuleDef *js_init_module_%s(JSContext *ctx, const char *name);\n" + " js_init_module_%s(ctx, \"%s\");\n" + " }\n", + e->short_name, e->short_name, e->name); + } + for(i = 0; i < cname_list.count; i++) { + namelist_entry_t *e = &cname_list.array[i]; + if (e->flags) { + fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, 1);\n", + e->name, e->name); + } + } + fprintf(fo, + " return ctx;\n" + "}\n\n"); + + fputs(main_c_template1, fo); + + if (stack_size != 0) { + fprintf(fo, " JS_SetMaxStackSize(rt, %u);\n", + (unsigned int)stack_size); + } + + /* add the module loader if necessary */ + if (feature_bitmap & (1 << FE_MODULE_LOADER)) { + fprintf(fo, " JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);\n"); + } + + fprintf(fo, + " ctx = JS_NewCustomContext(rt);\n" + " js_std_add_helpers(ctx, argc, argv);\n"); + + for(i = 0; i < cname_list.count; i++) { + namelist_entry_t *e = &cname_list.array[i]; + if (!e->flags) { + fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, 0);\n", + e->name, e->name); + } + } + fputs(main_c_template2, fo); + } + + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + + fclose(fo); + + if (output_type == OUTPUT_EXECUTABLE) { + return output_executable(out_filename, cfilename, use_lto, verbose, + argv[0]); + } + namelist_free(&cname_list); + namelist_free(&cmodule_list); + namelist_free(&init_module_list); + return 0; +} diff --git a/source/engine/thirdparty/quickjs/qjscalc.js b/source/engine/thirdparty/quickjs/qjscalc.js new file mode 100644 index 0000000..1400dc0 --- /dev/null +++ b/source/engine/thirdparty/quickjs/qjscalc.js @@ -0,0 +1,2657 @@ +/* + * QuickJS Javascript Calculator + * + * Copyright (c) 2017-2020 Fabrice Bellard + * + * 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. + */ +"use strict"; +"use math"; + +var Integer, Float, Fraction, Complex, Mod, Polynomial, PolyMod, RationalFunction, Series, Matrix; + +(function(global) { + global.Integer = global.BigInt; + global.Float = global.BigFloat; + global.algebraicMode = true; + + /* add non enumerable properties */ + function add_props(obj, props) { + var i, val, prop, tab, desc; + tab = Reflect.ownKeys(props); + for(i = 0; i < tab.length; i++) { + prop = tab[i]; + desc = Object.getOwnPropertyDescriptor(props, prop); + desc.enumerable = false; + if ("value" in desc) { + if (typeof desc.value !== "function") { + desc.writable = false; + desc.configurable = false; + } + } else { + /* getter/setter */ + desc.configurable = false; + } + Object.defineProperty(obj, prop, desc); + } + } + + /* same as proto[Symbol.operatorSet] = Operators.create(..op_list) + but allow shortcuts: left: [], right: [] or both + */ + function operators_set(proto, ...op_list) + { + var new_op_list, i, a, j, b, k, obj, tab; + var fields = [ "left", "right" ]; + new_op_list = []; + for(i = 0; i < op_list.length; i++) { + a = op_list[i]; + if (a.left || a.right) { + tab = [ a.left, a.right ]; + delete a.left; + delete a.right; + for(k = 0; k < 2; k++) { + obj = tab[k]; + if (obj) { + if (!Array.isArray(obj)) { + obj = [ obj ]; + } + for(j = 0; j < obj.length; j++) { + b = {}; + Object.assign(b, a); + b[fields[k]] = obj[j]; + new_op_list.push(b); + } + } + } + } else { + new_op_list.push(a); + } + } + proto[Symbol.operatorSet] = + Operators.create.call(null, ...new_op_list); + } + + /* Integer */ + + function generic_pow(a, b) { + var r, is_neg, i; + if (!Integer.isInteger(b)) { + return exp(log(a) * b); + } + if (Array.isArray(a) && !(a instanceof Polynomial || + a instanceof Series)) { + r = idn(Matrix.check_square(a)); + } else { + r = 1; + } + if (b == 0) + return r; + is_neg = false; + if (b < 0) { + is_neg = true; + b = -b; + } + r = a; + for(i = Integer.floorLog2(b) - 1; i >= 0; i--) { + r *= r; + if ((b >> i) & 1) + r *= a; + } + if (is_neg) { + if (typeof r.inverse != "function") + throw "negative powers are not supported for this type"; + r = r.inverse(); + } + return r; + } + + var small_primes = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499 ]; + + function miller_rabin_test(n, t) { + var d, r, s, i, j, a; + d = n - 1; + s = 0; + while ((d & 1) == 0) { + d >>= 1; + s++; + } + if (small_primes.length < t) + t = small_primes.length; + loop: for(j = 0; j < t; j++) { + a = small_primes[j]; + r = Integer.pmod(a, d, n); + if (r == 1 || r == (n - 1)) + continue; + for(i = 1; i < s; i++) { + r = (r * r) % n; + if (r == 1) + return false; + if (r == (n - 1)) + continue loop; + } + return false; /* n is composite */ + } + return true; /* n is probably prime with probability (1-0.5^t) */ + } + + function fact_rec(a, b) { /* assumes a <= b */ + var i, r; + if ((b - a) <= 5) { + r = a; + for(i = a + 1; i <= b; i++) + r *= i; + return r; + } else { + /* to avoid a quadratic running time it is better to + multiply numbers of similar size */ + i = (a + b) >> 1; + return fact_rec(a, i) * fact_rec(i + 1, b); + } + } + + /* math mode specific quirk to overload the integer division and power */ + Operators.updateBigIntOperators( + { + "/"(a, b) { + if (algebraicMode) { + return Fraction.toFraction(a, b); + } else { + return Float(a) / Float(b); + } + }, + "**"(a, b) { + if (algebraicMode) { + return generic_pow(a, b); + } else { + return Float(a) ** Float(b); + } + } + }); + + add_props(Integer, { + isInteger(a) { + /* integers are represented either as bigint or as number */ + return typeof a === "bigint" || + (typeof a === "number" && Number.isSafeInteger(a)); + }, + gcd(a, b) { + var r; + while (b != 0) { + r = a % b; + a = b; + b = r; + } + return a; + }, + fact(n) { + return n <= 0 ? 1 : fact_rec(1, n); + }, + /* binomial coefficient */ + comb(n, k) { + if (k < 0 || k > n) + return 0; + if (k > n - k) + k = n - k; + if (k == 0) + return 1; + return Integer.tdiv(fact_rec(n - k + 1, n), fact_rec(1, k)); + }, + /* inverse of x modulo y */ + invmod(x, y) { + var q, u, v, a, c, t; + u = x; + v = y; + c = 1; + a = 0; + while (u != 0) { + t = Integer.fdivrem(v, u); + q = t[0]; + v = u; + u = t[1]; + t = c; + c = a - q * c; + a = t; + } + /* v = gcd(x, y) */ + if (v != 1) + throw RangeError("not invertible"); + return a % y; + }, + /* return a ^ b modulo m */ + pmod(a, b, m) { + var r; + if (b == 0) + return 1; + if (b < 0) { + a = Integer.invmod(a, m); + b = -b; + } + r = 1; + for(;;) { + if (b & 1) { + r = (r * a) % m; + } + b >>= 1; + if (b == 0) + break; + a = (a * a) % m; + } + return r; + }, + + /* return true if n is prime (or probably prime with + probability 1-0.5^t) */ + isPrime(n, t) { + var i, d, n1; + if (!Integer.isInteger(n)) + throw TypeError("invalid type"); + if (n <= 1) + return false; + n1 = small_primes.length; + /* XXX: need Integer.sqrt() */ + for(i = 0; i < n1; i++) { + d = small_primes[i]; + if (d == n) + return true; + if (d > n) + return false; + if ((n % d) == 0) + return false; + } + if (n < d * d) + return true; + if (typeof t == "undefined") + t = 64; + return miller_rabin_test(n, t); + }, + nextPrime(n) { + if (!Integer.isInteger(n)) + throw TypeError("invalid type"); + if (n < 1) + n = 1; + for(;;) { + n++; + if (Integer.isPrime(n)) + return n; + } + }, + factor(n) { + var r, d; + if (!Integer.isInteger(n)) + throw TypeError("invalid type"); + r = []; + if (abs(n) <= 1) { + r.push(n); + return r; + } + if (n < 0) { + r.push(-1); + n = -n; + } + + while ((n % 2) == 0) { + n >>= 1; + r.push(2); + } + + d = 3; + while (n != 1) { + if (Integer.isPrime(n)) { + r.push(n); + break; + } + /* we are sure there is at least one divisor, so one test */ + for(;;) { + if ((n % d) == 0) + break; + d += 2; + } + for(;;) { + r.push(d); + n = Integer.tdiv(n, d); + if ((n % d) != 0) + break; + } + } + return r; + }, + }); + + add_props(Integer.prototype, { + inverse() { + return 1 / this; + }, + norm2() { + return this * this; + }, + abs() { + var v = this; + if (v < 0) + v = -v; + return v; + }, + conj() { + return this; + }, + arg() { + if (this >= 0) + return 0; + else + return Float.PI; + }, + exp() { + if (this == 0) + return 1; + else + return Float.exp(this); + }, + log() { + if (this == 1) + return 0; + else + return Float(this).log(); + }, + }); + + /* Fraction */ + + Fraction = function Fraction(a, b) + { + var d, r, obj; + + if (new.target) + throw TypeError("not a constructor"); + if (a instanceof Fraction) + return a; + if (!Integer.isInteger(a)) + throw TypeError("integer expected"); + if (typeof b === "undefined") { + b = 1; + } else { + if (!Integer.isInteger(b)) + throw TypeError("integer expected"); + if (b == 0) + throw RangeError("division by zero"); + d = Integer.gcd(a, b); + if (d != 1) { + a = Integer.tdiv(a, d); + b = Integer.tdiv(b, d); + } + + /* the fractions are normalized with den > 0 */ + if (b < 0) { + a = -a; + b = -b; + } + } + obj = Object.create(Fraction.prototype); + obj.num = a; + obj.den = b; + return obj; + } + + function fraction_add(a, b) { + a = Fraction(a); + b = Fraction(b); + return Fraction.toFraction(a.num * b.den + a.den * b.num, a.den * b.den); + } + function fraction_sub(a, b) { + a = Fraction(a); + b = Fraction(b); + return Fraction.toFraction(a.num * b.den - a.den * b.num, a.den * b.den); + } + function fraction_mul(a, b) { + a = Fraction(a); + b = Fraction(b); + return Fraction.toFraction(a.num * b.num, a.den * b.den); + } + function fraction_div(a, b) { + a = Fraction(a); + b = Fraction(b); + return Fraction.toFraction(a.num * b.den, a.den * b.num); + } + function fraction_mod(a, b) { + var a1 = Fraction(a); + var b1 = Fraction(b); + return a - Integer.ediv(a1.num * b1.den, a1.den * b1.num) * b; + } + function fraction_eq(a, b) { + a = Fraction(a); + b = Fraction(b); + /* we assume the fractions are normalized */ + return (a.num == b.num && a.den == b.den); + } + function fraction_lt(a, b) { + a = Fraction(a); + b = Fraction(b); + return (a.num * b.den < b.num * a.den); + } + + /* operators are needed for fractions */ + function float_add(a, b) { + return Float(a) + Float(b); + } + function float_sub(a, b) { + return Float(a) - Float(b); + } + function float_mul(a, b) { + return Float(a) * Float(b); + } + function float_div(a, b) { + return Float(a) / Float(b); + } + function float_mod(a, b) { + return Float(a) % Float(b); + } + function float_pow(a, b) { + return Float(a) ** Float(b); + } + function float_eq(a, b) { + /* XXX: may be better to use infinite precision for the comparison */ + return Float(a) === Float(b); + } + function float_lt(a, b) { + a = Float(a); + b = Float(b); + /* XXX: may be better to use infinite precision for the comparison */ + if (Float.isNaN(a) || Float.isNaN(b)) + return undefined; + else + return a < b; + } + + operators_set(Fraction.prototype, + { + "+": fraction_add, + "-": fraction_sub, + "*": fraction_mul, + "/": fraction_div, + "%": fraction_mod, + "**": generic_pow, + "==": fraction_eq, + "<": fraction_lt, + "pos"(a) { + return a; + }, + "neg"(a) { + return Fraction(-a.num, a.den); + }, + }, + { + left: [Number, BigInt], + right: [Number, BigInt], + "+": fraction_add, + "-": fraction_sub, + "*": fraction_mul, + "/": fraction_div, + "%": fraction_mod, + "**": generic_pow, + "==": fraction_eq, + "<": fraction_lt, + }, + { + left: Float, + right: Float, + "+": float_add, + "-": float_sub, + "*": float_mul, + "/": float_div, + "%": float_mod, + "**": float_pow, + "==": float_eq, + "<": float_lt, + }); + + add_props(Fraction, { + /* (internal use) simplify 'a' to an integer when possible */ + toFraction(a, b) { + var r = Fraction(a, b); + if (algebraicMode && r.den == 1) + return r.num; + else + return r; + }, + }); + + add_props(Fraction.prototype, { + [Symbol.toPrimitive](hint) { + if (hint === "string") { + return this.toString(); + } else { + return Float(this.num) / this.den; + } + }, + inverse() { + return Fraction(this.den, this.num); + }, + toString() { + return this.num + "/" + this.den; + }, + norm2() { + return this * this; + }, + abs() { + if (this.num < 0) + return -this; + else + return this; + }, + conj() { + return this; + }, + arg() { + if (this.num >= 0) + return 0; + else + return Float.PI; + }, + exp() { + return Float.exp(Float(this)); + }, + log() { + return Float(this).log(); + }, + }); + + /* Number (Float64) */ + + add_props(Number.prototype, { + inverse() { + return 1 / this; + }, + norm2() { + return this * this; + }, + abs() { + return Math.abs(this); + }, + conj() { + return this; + }, + arg() { + if (this >= 0) + return 0; + else + return Float.PI; + }, + exp() { + return Float.exp(this); + }, + log() { + if (this < 0) { + return Complex(this).log(); + } else { + return Float.log(this); + } + }, + }); + + /* Float */ + + var const_tab = []; + + /* we cache the constants for small precisions */ + function get_const(n) { + var t, c, p; + t = const_tab[n]; + p = BigFloatEnv.prec; + if (t && t.prec == p) { + return t.val; + } else { + switch(n) { + case 0: c = Float.exp(1); break; + case 1: c = Float.log(10); break; +// case 2: c = Float.log(2); break; + case 3: c = 1/Float.log(2); break; + case 4: c = 1/Float.log(10); break; +// case 5: c = Float.atan(1) * 4; break; + case 6: c = Float.sqrt(0.5); break; + case 7: c = Float.sqrt(2); break; + } + if (p <= 1024) { + const_tab[n] = { prec: p, val: c }; + } + return c; + } + } + + add_props(Float, { + isFloat(a) { + return typeof a === "number" || typeof a === "bigfloat"; + }, + bestappr(u, b) { + var num1, num0, den1, den0, u, num, den, n; + + if (typeof b === "undefined") + throw TypeError("second argument expected"); + num1 = 1; + num0 = 0; + den1 = 0; + den0 = 1; + for(;;) { + n = Integer(Float.floor(u)); + num = n * num1 + num0; + den = n * den1 + den0; + if (den > b) + break; + u = 1.0 / (u - n); + num0 = num1; + num1 = num; + den0 = den1; + den1 = den; + } + return Fraction(num1, den1); + }, + /* similar constants as Math.x */ + get E() { return get_const(0); }, + get LN10() { return get_const(1); }, +// get LN2() { return get_const(2); }, + get LOG2E() { return get_const(3); }, + get LOG10E() { return get_const(4); }, +// get PI() { return get_const(5); }, + get SQRT1_2() { return get_const(6); }, + get SQRT2() { return get_const(7); }, + }); + + add_props(Float.prototype, { + inverse() { + return 1.0 / this; + }, + norm2() { + return this * this; + }, + abs() { + return Float.abs(this); + }, + conj() { + return this; + }, + arg() { + if (this >= 0) + return 0; + else + return Float.PI; + }, + exp() { + return Float.exp(this); + }, + log() { + if (this < 0) { + return Complex(this).log(); + } else { + return Float.log(this); + } + }, + }); + + /* Complex */ + + Complex = function Complex(re, im) + { + var obj; + if (new.target) + throw TypeError("not a constructor"); + if (re instanceof Complex) + return re; + if (typeof im === "undefined") { + im = 0; + } + obj = Object.create(Complex.prototype); + obj.re = re; + obj.im = im; + return obj; + } + + + function complex_add(a, b) { + a = Complex(a); + b = Complex(b); + return Complex.toComplex(a.re + b.re, a.im + b.im); + } + function complex_sub(a, b) { + a = Complex(a); + b = Complex(b); + return Complex.toComplex(a.re - b.re, a.im - b.im); + } + function complex_mul(a, b) { + a = Complex(a); + b = Complex(b); + return Complex.toComplex(a.re * b.re - a.im * b.im, + a.re * b.im + a.im * b.re); + } + function complex_div(a, b) { + a = Complex(a); + b = Complex(b); + return a * b.inverse(); + } + function complex_eq(a, b) { + a = Complex(a); + b = Complex(b); + return a.re == b.re && a.im == b.im; + } + + operators_set(Complex.prototype, + { + "+": complex_add, + "-": complex_sub, + "*": complex_mul, + "/": complex_div, + "**": generic_pow, + "==": complex_eq, + "pos"(a) { + return a; + }, + "neg"(a) { + return Complex(-a.re, -a.im); + } + }, + { + left: [Number, BigInt, Float, Fraction], + right: [Number, BigInt, Float, Fraction], + "+": complex_add, + "-": complex_sub, + "*": complex_mul, + "/": complex_div, + "**": generic_pow, + "==": complex_eq, + }); + + add_props(Complex, { + /* simplify to real number when possible */ + toComplex(re, im) { + if (algebraicMode && im == 0) + return re; + else + return Complex(re, im); + }, + }); + + add_props(Complex.prototype, { + inverse() { + var c = this.norm2(); + return Complex(this.re / c, -this.im / c); + }, + toString() { + var v, s = "", a = this; + if (a.re != 0) + s += a.re.toString(); + if (a.im == 1) { + if (s != "") + s += "+"; + s += "I"; + } else if (a.im == -1) { + s += "-I"; + } else { + v = a.im.toString(); + if (v[0] != "-" && s != "") + s += "+"; + s += v + "*I"; + } + return s; + }, + norm2() { + return this.re * this.re + this.im * this.im; + }, + abs() { + return Float.sqrt(norm2(this)); + }, + conj() { + return Complex(this.re, -this.im); + }, + arg() { + return Float.atan2(this.im, this.re); + }, + exp() { + var arg = this.im, r = this.re.exp(); + return Complex(r * cos(arg), r * sin(arg)); + }, + log() { + return Complex(abs(this).log(), atan2(this.im, this.re)); + }, + }); + + /* Mod */ + + Mod = function Mod(a, m) { + var obj, t; + if (new.target) + throw TypeError("not a constructor"); + obj = Object.create(Mod.prototype); + if (Integer.isInteger(m)) { + if (m <= 0) + throw RangeError("the modulo cannot be <= 0"); + if (Integer.isInteger(a)) { + a %= m; + } else if (a instanceof Fraction) { + return Mod(a.num, m) / a.den; + } else { + throw TypeError("invalid types"); + } + } else { + throw TypeError("invalid types"); + } + obj.res = a; + obj.mod = m; + return obj; + }; + + function mod_add(a, b) { + if (!(a instanceof Mod)) { + return Mod(a + b.res, b.mod); + } else if (!(b instanceof Mod)) { + return Mod(a.res + b, a.mod); + } else { + if (a.mod != b.mod) + throw TypeError("different modulo for binary operator"); + return Mod(a.res + b.res, a.mod); + } + } + function mod_sub(a, b) { + if (!(a instanceof Mod)) { + return Mod(a - b.res, b.mod); + } else if (!(b instanceof Mod)) { + return Mod(a.res - b, a.mod); + } else { + if (a.mod != b.mod) + throw TypeError("different modulo for binary operator"); + return Mod(a.res - b.res, a.mod); + } + } + function mod_mul(a, b) { + if (!(a instanceof Mod)) { + return Mod(a * b.res, b.mod); + } else if (!(b instanceof Mod)) { + return Mod(a.res * b, a.mod); + } else { + if (a.mod != b.mod) + throw TypeError("different modulo for binary operator"); + return Mod(a.res * b.res, a.mod); + } + } + function mod_div(a, b) { + if (!(b instanceof Mod)) + b = Mod(b, a.mod); + return mod_mul(a, b.inverse()); + } + function mod_eq(a, b) { + return (a.mod == b.mod && a.res == b.res); + } + + operators_set(Mod.prototype, + { + "+": mod_add, + "-": mod_sub, + "*": mod_mul, + "/": mod_div, + "**": generic_pow, + "==": mod_eq, + "pos"(a) { + return a; + }, + "neg"(a) { + return Mod(-a.res, a.mod); + } + }, + { + left: [Number, BigInt, Float, Fraction], + right: [Number, BigInt, Float, Fraction], + "+": mod_add, + "-": mod_sub, + "*": mod_mul, + "/": mod_div, + "**": generic_pow, + }); + + add_props(Mod.prototype, { + inverse() { + var a = this, m = a.mod; + if (Integer.isInteger(m)) { + return Mod(Integer.invmod(a.res, m), m); + } else { + throw TypeError("unsupported type"); + } + }, + toString() { + return "Mod(" + this.res + "," + this.mod + ")"; + }, + }); + + /* Polynomial */ + + function polynomial_is_scalar(a) + { + if (typeof a === "number" || + typeof a === "bigint" || + typeof a === "bigfloat") + return true; + if (a instanceof Fraction || + a instanceof Complex || + a instanceof Mod) + return true; + return false; + } + + Polynomial = function Polynomial(a) + { + if (new.target) + throw TypeError("not a constructor"); + if (a instanceof Polynomial) { + return a; + } else if (Array.isArray(a)) { + if (a.length == 0) + a = [ 0 ]; + Object.setPrototypeOf(a, Polynomial.prototype); + return a.trim(); + } else if (polynomial_is_scalar(a)) { + a = [a]; + Object.setPrototypeOf(a, Polynomial.prototype); + return a; + } else { + throw TypeError("invalid type"); + } + } + + function number_need_paren(c) + { + return !(Integer.isInteger(c) || + Float.isFloat(c) || + c instanceof Fraction || + (c instanceof Complex && c.re == 0)); + } + + /* string for c*X^i */ + function monomial_toString(c, i) + { + var str1; + if (i == 0) { + str1 = c.toString(); + } else { + if (c == 1) { + str1 = ""; + } else if (c == -1) { + str1 = "-"; + } else { + if (number_need_paren(c)) { + str1 = "(" + c + ")"; + } else { + str1 = String(c); + } + str1 += "*"; + } + str1 += "X"; + if (i != 1) { + str1 += "^" + i; + } + } + return str1; + } + + /* find one complex root of 'p' starting from z at precision eps using + at most max_it iterations. Return null if could not find root. */ + function poly_root_laguerre1(p, z, max_it) + { + var p1, p2, i, z0, z1, z2, d, t0, t1, d1, d2, e, el, zl; + + d = p.deg(); + if (d == 1) { + /* monomial case */ + return -p[0] / p[1]; + } + /* trivial zero */ + if (p[0] == 0) + return 0.0; + + p1 = p.deriv(); + p2 = p1.deriv(); + el = 0.0; + zl = 0.0; + for(i = 0; i < max_it; i++) { + z0 = p.apply(z); + if (z0 == 0) + return z; /* simple exit case */ + + /* Ward stopping criteria */ + e = abs(z - zl); +// print("e", i, e); + if (i >= 2 && e >= el) { + if (abs(zl) < 1e-4) { + if (e < 1e-7) + return zl; + } else { + if (e < abs(zl) * 1e-3) + return zl; + } + } + el = e; + zl = z; + + z1 = p1.apply(z); + z2 = p2.apply(z); + t0 = (d - 1) * z1; + t0 = t0 * t0; + t1 = d * (d - 1) * z0 * z2; + t0 = sqrt(t0 - t1); + d1 = z1 + t0; + d2 = z1 - t0; + if (norm2(d2) > norm2(d1)) + d1 = d2; + if (d1 == 0) + return null; + z = z - d * z0 / d1; + } + return null; + } + + function poly_roots(p) + { + var d, i, roots, j, z, eps; + var start_points = [ 0.1, -1.4, 1.7 ]; + + if (!(p instanceof Polynomial)) + throw TypeError("polynomial expected"); + d = p.deg(); + if (d <= 0) + return []; + eps = 2.0 ^ (-BigFloatEnv.prec); + roots = []; + for(i = 0; i < d; i++) { + /* XXX: should select another start point if error */ + for(j = 0; j < 3; j++) { + z = poly_root_laguerre1(p, start_points[j], 100); + if (z !== null) + break; + } + if (j == 3) + throw RangeError("error in root finding algorithm"); + roots[i] = z; + p = Polynomial.divrem(p, X - z)[0]; + } + return roots; + } + + add_props(Polynomial.prototype, { + trim() { + var a = this, i; + i = a.length; + while (i > 1 && a[i - 1] == 0) + i--; + a.length = i; + return a; + }, + conj() { + var r, i, n, a; + a = this; + n = a.length; + r = []; + for(i = 0; i < n; i++) + r[i] = a[i].conj(); + return Polynomial(r); + }, + inverse() { + return RationalFunction(Polynomial([1]), this); + }, + toString() { + var i, str, str1, c, a = this; + if (a.length == 1) { + return a[0].toString(); + } + str=""; + for(i = a.length - 1; i >= 0; i--) { + c = a[i]; + if (c == 0 || + (c instanceof Mod) && c.res == 0) + continue; + str1 = monomial_toString(c, i); + if (str1[0] != "-") { + if (str != "") + str += "+"; + } + str += str1; + } + return str; + }, + deg() { + if (this.length == 1 && this[0] == 0) + return -Infinity; + else + return this.length - 1; + }, + apply(b) { + var i, n, r, a = this; + n = a.length - 1; + r = a[n]; + while (n > 0) { + n--; + r = r * b + a[n]; + } + return r; + }, + deriv() { + var a = this, n, r, i; + n = a.length; + if (n == 1) { + return Polynomial(0); + } else { + r = []; + for(i = 1; i < n; i++) { + r[i - 1] = i * a[i]; + } + return Polynomial(r); + } + }, + integ() { + var a = this, n, r, i; + n = a.length; + r = [0]; + for(i = 0; i < n; i++) { + r[i + 1] = a[i] / (i + 1); + } + return Polynomial(r); + }, + norm2() { + var a = this, n, r, i; + n = a.length; + r = 0; + for(i = 0; i < n; i++) { + r += a[i].norm2(); + } + return r; + }, + }); + + + function polynomial_add(a, b) { + var tmp, r, i, n1, n2; + a = Polynomial(a); + b = Polynomial(b); + if (a.length < b.length) { + tmp = a; + a = b; + b = tmp; + } + n1 = b.length; + n2 = a.length; + r = []; + for(i = 0; i < n1; i++) + r[i] = a[i] + b[i]; + for(i = n1; i < n2; i++) + r[i] = a[i]; + return Polynomial(r); + } + function polynomial_sub(a, b) { + return polynomial_add(a, -b); + } + function polynomial_mul(a, b) { + var i, j, n1, n2, n, r; + a = Polynomial(a); + b = Polynomial(b); + n1 = a.length; + n2 = b.length; + n = n1 + n2 - 1; + r = []; + for(i = 0; i < n; i++) + r[i] = 0; + for(i = 0; i < n1; i++) { + for(j = 0; j < n2; j++) { + r[i + j] += a[i] * b[j]; + } + } + return Polynomial(r); + } + function polynomial_div_scalar(a, b) { + return a * (1 / b); + } + function polynomial_div(a, b) + { + return RationalFunction(Polynomial(a), + Polynomial(b)); + } + function polynomial_mod(a, b) { + return Polynomial.divrem(a, b)[1]; + } + function polynomial_eq(a, b) { + var n, i; + n = a.length; + if (n != b.length) + return false; + for(i = 0; i < n; i++) { + if (a[i] != b[i]) + return false; + } + return true; + } + + operators_set(Polynomial.prototype, + { + "+": polynomial_add, + "-": polynomial_sub, + "*": polynomial_mul, + "/": polynomial_div, + "**": generic_pow, + "==": polynomial_eq, + "pos"(a) { + return a; + }, + "neg"(a) { + var r, i, n, a; + n = a.length; + r = []; + for(i = 0; i < n; i++) + r[i] = -a[i]; + return Polynomial(r); + }, + }, + { + left: [Number, BigInt, Float, Fraction, Complex, Mod], + "+": polynomial_add, + "-": polynomial_sub, + "*": polynomial_mul, + "/": polynomial_div, + "**": generic_pow, /* XXX: only for integer */ + }, + { + right: [Number, BigInt, Float, Fraction, Complex, Mod], + "+": polynomial_add, + "-": polynomial_sub, + "*": polynomial_mul, + "/": polynomial_div_scalar, + "**": generic_pow, /* XXX: only for integer */ + }); + + add_props(Polynomial, { + divrem(a, b) { + var n1, n2, i, j, q, r, n, c; + if (b.deg() < 0) + throw RangeError("division by zero"); + n1 = a.length; + n2 = b.length; + if (n1 < n2) + return [Polynomial([0]), a]; + r = Array.prototype.dup.call(a); + q = []; + n2--; + n = n1 - n2; + for(i = 0; i < n; i++) + q[i] = 0; + for(i = n - 1; i >= 0; i--) { + c = r[i + n2]; + if (c != 0) { + c = c / b[n2]; + r[i + n2] = 0; + for(j = 0; j < n2; j++) { + r[i + j] -= b[j] * c; + } + q[i] = c; + } + } + return [Polynomial(q), Polynomial(r)]; + }, + gcd(a, b) { + var t; + while (b.deg() >= 0) { + t = Polynomial.divrem(a, b); + a = b; + b = t[1]; + } + /* convert to monic form */ + return a / a[a.length - 1]; + }, + invmod(x, y) { + var q, u, v, a, c, t; + u = x; + v = y; + c = Polynomial([1]); + a = Polynomial([0]); + while (u.deg() >= 0) { + t = Polynomial.divrem(v, u); + q = t[0]; + v = u; + u = t[1]; + t = c; + c = a - q * c; + a = t; + } + /* v = gcd(x, y) */ + if (v.deg() > 0) + throw RangeError("not invertible"); + return Polynomial.divrem(a, y)[1]; + }, + roots(p) { + return poly_roots(p); + } + }); + + /* Polynomial Modulo Q */ + + PolyMod = function PolyMod(a, m) { + var obj, t; + if (new.target) + throw TypeError("not a constructor"); + obj = Object.create(PolyMod.prototype); + if (m instanceof Polynomial) { + if (m.deg() <= 0) + throw RangeError("the modulo cannot have a degree <= 0"); + if (a instanceof RationalFunction) { + return PolyMod(a.num, m) / a.den; + } else { + a = Polynomial(a); + t = Polynomial.divrem(a, m); + a = t[1]; + } + } else { + throw TypeError("invalid types"); + } + obj.res = a; + obj.mod = m; + return obj; + }; + + function polymod_add(a, b) { + if (!(a instanceof PolyMod)) { + return PolyMod(a + b.res, b.mod); + } else if (!(b instanceof PolyMod)) { + return PolyMod(a.res + b, a.mod); + } else { + if (a.mod != b.mod) + throw TypeError("different modulo for binary operator"); + return PolyMod(a.res + b.res, a.mod); + } + } + function polymod_sub(a, b) { + return polymod_add(a, -b); + } + function polymod_mul(a, b) { + if (!(a instanceof PolyMod)) { + return PolyMod(a * b.res, b.mod); + } else if (!(b instanceof PolyMod)) { + return PolyMod(a.res * b, a.mod); + } else { + if (a.mod != b.mod) + throw TypeError("different modulo for binary operator"); + return PolyMod(a.res * b.res, a.mod); + } + } + function polymod_div(a, b) { + if (!(b instanceof PolyMod)) + b = PolyMod(b, a.mod); + return polymod_mul(a, b.inverse()); + } + function polymod_eq(a, b) { + return (a.mod == b.mod && a.res == b.res); + } + + operators_set(PolyMod.prototype, + { + "+": polymod_add, + "-": polymod_sub, + "*": polymod_mul, + "/": polymod_div, + "**": generic_pow, + "==": polymod_eq, + "pos"(a) { + return a; + }, + "neg"(a) { + return PolyMod(-a.res, a.mod); + }, + }, + { + left: [Number, BigInt, Float, Fraction, Complex, Mod, Polynomial], + right: [Number, BigInt, Float, Fraction, Complex, Mod, Polynomial], + "+": polymod_add, + "-": polymod_sub, + "*": polymod_mul, + "/": polymod_div, + "**": generic_pow, /* XXX: only for integer */ + }); + + add_props(PolyMod.prototype, { + inverse() { + var a = this, m = a.mod; + if (m instanceof Polynomial) { + return PolyMod(Polynomial.invmod(a.res, m), m); + } else { + throw TypeError("unsupported type"); + } + }, + toString() { + return "PolyMod(" + this.res + "," + this.mod + ")"; + }, + }); + + /* Rational function */ + + RationalFunction = function RationalFunction(a, b) + { + var t, r, d, obj; + if (new.target) + throw TypeError("not a constructor"); + if (!(a instanceof Polynomial) || + !(b instanceof Polynomial)) + throw TypeError("polynomial expected"); + t = Polynomial.divrem(a, b); + r = t[1]; + if (r.deg() < 0) + return t[0]; /* no need for a fraction */ + d = Polynomial.gcd(b, r); + if (d.deg() > 0) { + a = Polynomial.divrem(a, d)[0]; + b = Polynomial.divrem(b, d)[0]; + } + obj = Object.create(RationalFunction.prototype); + obj.num = a; + obj.den = b; + return obj; + } + + add_props(RationalFunction.prototype, { + inverse() { + return RationalFunction(this.den, this.num); + }, + conj() { + return RationalFunction(this.num.conj(), this.den.conj()); + }, + toString() { + var str; + if (this.num.deg() <= 0 && + !number_need_paren(this.num[0])) + str = this.num.toString(); + else + str = "(" + this.num.toString() + ")"; + str += "/(" + this.den.toString() + ")" + return str; + }, + apply(b) { + return this.num.apply(b) / this.den.apply(b); + }, + deriv() { + var n = this.num, d = this.den; + return RationalFunction(n.deriv() * d - n * d.deriv(), d * d); + }, + }); + + function ratfunc_add(a, b) { + a = RationalFunction.toRationalFunction(a); + b = RationalFunction.toRationalFunction(b); + return RationalFunction(a.num * b.den + a.den * b.num, a.den * b.den); + } + function ratfunc_sub(a, b) { + a = RationalFunction.toRationalFunction(a); + b = RationalFunction.toRationalFunction(b); + return RationalFunction(a.num * b.den - a.den * b.num, a.den * b.den); + } + function ratfunc_mul(a, b) { + a = RationalFunction.toRationalFunction(a); + b = RationalFunction.toRationalFunction(b); + return RationalFunction(a.num * b.num, a.den * b.den); + } + function ratfunc_div(a, b) { + a = RationalFunction.toRationalFunction(a); + b = RationalFunction.toRationalFunction(b); + return RationalFunction(a.num * b.den, a.den * b.num); + } + function ratfunc_eq(a, b) { + a = RationalFunction.toRationalFunction(a); + b = RationalFunction.toRationalFunction(b); + /* we assume the fractions are normalized */ + return (a.num == b.num && a.den == b.den); + } + + operators_set(RationalFunction.prototype, + { + "+": ratfunc_add, + "-": ratfunc_sub, + "*": ratfunc_mul, + "/": ratfunc_div, + "**": generic_pow, + "==": ratfunc_eq, + "pos"(a) { + return a; + }, + "neg"(a) { + return RationalFunction(-this.num, this.den); + }, + }, + { + left: [Number, BigInt, Float, Fraction, Complex, Mod, Polynomial], + right: [Number, BigInt, Float, Fraction, Complex, Mod, Polynomial], + "+": ratfunc_add, + "-": ratfunc_sub, + "*": ratfunc_mul, + "/": ratfunc_div, + "**": generic_pow, /* should only be used with integers */ + }); + + add_props(RationalFunction, { + /* This function always return a RationalFunction object even + if it could simplified to a polynomial, so it is not + equivalent to RationalFunction(a) */ + toRationalFunction(a) { + var obj; + if (a instanceof RationalFunction) { + return a; + } else { + obj = Object.create(RationalFunction.prototype); + obj.num = Polynomial(a); + obj.den = Polynomial(1); + return obj; + } + }, + }); + + /* Power series */ + + /* 'a' is an array */ + function get_emin(a) { + var i, n; + n = a.length; + for(i = 0; i < n; i++) { + if (a[i] != 0) + return i; + } + return n; + }; + + function series_is_scalar_or_polynomial(a) + { + return polynomial_is_scalar(a) || + (a instanceof Polynomial); + } + + /* n is the maximum number of terms if 'a' is not a serie */ + Series = function Series(a, n) { + var emin, r, i; + + if (a instanceof Series) { + return a; + } else if (series_is_scalar_or_polynomial(a)) { + if (n <= 0) { + /* XXX: should still use the polynomial degree */ + return Series.zero(0, 0); + } else { + a = Polynomial(a); + emin = get_emin(a); + r = Series.zero(n, emin); + n = Math.min(a.length - emin, n); + for(i = 0; i < n; i++) + r[i] = a[i + emin]; + return r; + } + } else if (a instanceof RationalFunction) { + return Series(a.num, n) / a.den; + } else { + throw TypeError("invalid type"); + } + }; + + function series_add(v1, v2) { + var tmp, d, emin, n, r, i, j, v2_emin, c1, c2; + if (!(v1 instanceof Series)) { + tmp = v1; + v1 = v2; + v2 = tmp; + } + d = v1.emin + v1.length; + if (series_is_scalar_or_polynomial(v2)) { + v2 = Polynomial(v2); + if (d <= 0) + return v1; + v2_emin = 0; + } else if (v2 instanceof RationalFunction) { + /* compute the emin of the rational fonction */ + i = get_emin(v2.num) - get_emin(v2.den); + if (d <= i) + return v1; + /* compute the serie with the required terms */ + v2 = Series(v2, d - i); + v2_emin = v2.emin; + } else { + v2_emin = v2.emin; + d = Math.min(d, v2_emin + v2.length); + } + emin = Math.min(v1.emin, v2_emin); + n = d - emin; + r = Series.zero(n, emin); + /* XXX: slow */ + for(i = emin; i < d; i++) { + j = i - v1.emin; + if (j >= 0 && j < v1.length) + c1 = v1[j]; + else + c1 = 0; + j = i - v2_emin; + if (j >= 0 && j < v2.length) + c2 = v2[j]; + else + c2 = 0; + r[i - emin] = c1 + c2; + } + return r.trim(); + } + function series_sub(a, b) { + return series_add(a, -b); + } + function series_mul(v1, v2) { + var n, i, j, r, n, emin, n1, n2, k; + if (!(v1 instanceof Series)) + v1 = Series(v1, v2.length); + else if (!(v2 instanceof Series)) + v2 = Series(v2, v1.length); + emin = v1.emin + v2.emin; + n = Math.min(v1.length, v2.length); + n1 = v1.length; + n2 = v2.length; + r = Series.zero(n, emin); + for(i = 0; i < n1; i++) { + k = Math.min(n2, n - i); + for(j = 0; j < k; j++) { + r[i + j] += v1[i] * v2[j]; + } + } + return r.trim(); + } + function series_div(v1, v2) { + if (!(v2 instanceof Series)) + v2 = Series(v2, v1.length); + return series_mul(v1, v2.inverse()); + } + function series_pow(a, b) { + if (Integer.isInteger(b)) { + return generic_pow(a, b); + } else { + if (!(a instanceof Series)) + a = Series(a, b.length); + return exp(log(a) * b); + } + } + function series_eq(a, b) { + var n, i; + if (a.emin != b.emin) + return false; + n = a.length; + if (n != b.length) + return false; + for(i = 0; i < n; i++) { + if (a[i] != b[i]) + return false; + } + return true; + } + + operators_set(Series.prototype, + { + "+": series_add, + "-": series_sub, + "*": series_mul, + "/": series_div, + "**": series_pow, + "==": series_eq, + "pos"(a) { + return a; + }, + "neg"(a) { + var obj, n, i; + n = a.length; + obj = Series.zero(a.length, a.emin); + for(i = 0; i < n; i++) { + obj[i] = -a[i]; + } + return obj; + }, + }, + { + left: [Number, BigInt, Float, Fraction, Complex, Mod, Polynomial], + right: [Number, BigInt, Float, Fraction, Complex, Mod, Polynomial], + "+": series_add, + "-": series_sub, + "*": series_mul, + "/": series_div, + "**": series_pow, + }); + + add_props(Series.prototype, { + conj() { + var obj, n, i; + n = this.length; + obj = Series.zero(this.length, this.emin); + for(i = 0; i < n; i++) { + obj[i] = this[i].conj(); + } + return obj; + }, + inverse() { + var r, n, i, j, sum, v1 = this; + n = v1.length; + if (n == 0) + throw RangeError("division by zero"); + r = Series.zero(n, -v1.emin); + r[0] = 1 / v1[0]; + for(i = 1; i < n; i++) { + sum = 0; + for(j = 1; j <= i; j++) { + sum += v1[j] * r[i - j]; + } + r[i] = -sum * r[0]; + } + return r; + }, + /* remove leading zero terms */ + trim() { + var i, j, n, r, v1 = this; + n = v1.length; + i = 0; + while (i < n && v1[i] == 0) + i++; + if (i == 0) + return v1; + for(j = i; j < n; j++) + v1[j - i] = v1[j]; + v1.length = n - i; + v1.__proto__.emin += i; + return v1; + }, + toString() { + var i, j, str, str1, c, a = this, emin, n; + str=""; + emin = this.emin; + n = this.length; + for(j = 0; j < n; j++) { + i = j + emin; + c = a[j]; + if (c != 0) { + str1 = monomial_toString(c, i); + if (str1[0] != "-") { + if (str != "") + str += "+"; + } + str += str1; + } + } + if (str != "") + str += "+"; + str += "O(" + monomial_toString(1, n + emin) + ")"; + return str; + }, + apply(b) { + var i, n, r, a = this; + n = a.length; + if (n == 0) + return 0; + r = a[--n]; + while (n > 0) { + n--; + r = r * b + a[n]; + } + if (a.emin != 0) + r *= b ^ a.emin; + return r; + }, + deriv() { + var a = this, n = a.length, emin = a.emin, r, i, j; + if (n == 0 && emin == 0) { + return Series.zero(0, 0); + } else { + r = Series.zero(n, emin - 1); + for(i = 0; i < n; i++) { + j = emin + i; + if (j == 0) + r[i] = 0; + else + r[i] = j * a[i]; + } + return r.trim(); + } + }, + integ() { + var a = this, n = a.length, emin = a.emin, i, j, r; + r = Series.zero(n, emin + 1); + for(i = 0; i < n; i++) { + j = emin + i; + if (j == -1) { + if (a[i] != 0) + throw RangeError("cannot represent integ(1/X)"); + } else { + r[i] = a[i] / (j + 1); + } + } + return r.trim(); + }, + exp() { + var c, i, r, n, a = this; + if (a.emin < 0) + throw RangeError("negative exponent in exp"); + n = a.emin + a.length; + if (a.emin > 0 || a[0] == 0) { + c = 1; + } else { + c = global.exp(a[0]); + a -= a[0]; + } + r = Series.zero(n, 0); + for(i = 0; i < n; i++) { + r[i] = c / fact(i); + } + return r.apply(a); + }, + log() { + var a = this, r; + if (a.emin != 0) + throw RangeError("log argument must have a non zero constant term"); + r = integ(deriv(a) / a); + /* add the constant term */ + r += global.log(a[0]); + return r; + }, + }); + + add_props(Series, { + /* new series of length n and first exponent emin */ + zero(n, emin) { + var r, i, obj; + + r = []; + for(i = 0; i < n; i++) + r[i] = 0; + /* we return an array and store emin in its prototype */ + obj = Object.create(Series.prototype); + obj.emin = emin; + Object.setPrototypeOf(r, obj); + return r; + }, + O(a) { + function ErrorO() { + return TypeError("invalid O() argument"); + } + var n; + if (series_is_scalar_or_polynomial(a)) { + a = Polynomial(a); + n = a.deg(); + if (n < 0) + throw ErrorO(); + } else if (a instanceof RationalFunction) { + if (a.num.deg() != 0) + throw ErrorO(); + n = a.den.deg(); + if (n < 0) + throw ErrorO(); + n = -n; + } else + throw ErrorO(); + return Series.zero(0, n); + }, + }); + + /* Array (Matrix) */ + + Matrix = function Matrix(h, w) { + var i, j, r, rl; + if (typeof w === "undefined") + w = h; + r = []; + for(i = 0; i < h; i++) { + rl = []; + for(j = 0; j < w; j++) + rl[j] = 0; + r[i] = rl; + } + return r; + }; + + add_props(Matrix, { + idn(n) { + var r, i; + r = Matrix(n, n); + for(i = 0; i < n; i++) + r[i][i] = 1; + return r; + }, + diag(a) { + var r, i, n; + n = a.length; + r = Matrix(n, n); + for(i = 0; i < n; i++) + r[i][i] = a[i]; + return r; + }, + hilbert(n) { + var i, j, r; + r = Matrix(n); + for(i = 0; i < n; i++) { + for(j = 0; j < n; j++) { + r[i][j] = 1 / (1 + i + j); + } + } + return r; + }, + trans(a) { + var h, w, r, i, j; + if (!Array.isArray(a)) + throw TypeError("matrix expected"); + h = a.length; + if (!Array.isArray(a[0])) { + w = 1; + r = Matrix(w, h); + for(i = 0; i < h; i++) { + r[0][i] = a[i]; + } + } else { + w = a[0].length; + r = Matrix(w, h); + for(i = 0; i < h; i++) { + for(j = 0; j < w; j++) { + r[j][i] = a[i][j]; + } + } + } + return r; + }, + check_square(a) { + var a, n; + if (!Array.isArray(a)) + throw TypeError("array expected"); + n = a.length; + if (!Array.isArray(a[0]) || n != a[0].length) + throw TypeError("square matrix expected"); + return n; + }, + trace(a) { + var n, r, i; + n = Matrix.check_square(a); + r = a[0][0]; + for(i = 1; i < n; i++) { + r += a[i][i]; + } + return r; + }, + charpoly(a) { + var n, p, c, i, j, coef; + n = Matrix.check_square(a); + p = []; + for(i = 0; i < n + 1; i++) + p[i] = 0; + p[n] = 1; + c = Matrix.idn(n); + for(i = 0; i < n; i++) { + c = c * a; + coef = -trace(c) / (i + 1); + p[n - i - 1] = coef; + for(j = 0; j < n; j++) + c[j][j] += coef; + } + return Polynomial(p); + }, + eigenvals(a) { + return Polynomial.roots(Matrix.charpoly(a)); + }, + det(a) { + var n, i, j, k, s, src, v, c; + + n = Matrix.check_square(a); + s = 1; + src = a.dup(); + for(i=0;i") +DEF(_ret_, "") +DEF(_var_, "") +DEF(_arg_var_, "") +DEF(_with_, "") +DEF(lastIndex, "lastIndex") +DEF(target, "target") +DEF(index, "index") +DEF(input, "input") +DEF(defineProperties, "defineProperties") +DEF(apply, "apply") +DEF(join, "join") +DEF(concat, "concat") +DEF(split, "split") +DEF(construct, "construct") +DEF(getPrototypeOf, "getPrototypeOf") +DEF(setPrototypeOf, "setPrototypeOf") +DEF(isExtensible, "isExtensible") +DEF(preventExtensions, "preventExtensions") +DEF(has, "has") +DEF(deleteProperty, "deleteProperty") +DEF(defineProperty, "defineProperty") +DEF(getOwnPropertyDescriptor, "getOwnPropertyDescriptor") +DEF(ownKeys, "ownKeys") +DEF(add, "add") +DEF(done, "done") +DEF(next, "next") +DEF(values, "values") +DEF(source, "source") +DEF(flags, "flags") +DEF(global, "global") +DEF(unicode, "unicode") +DEF(raw, "raw") +DEF(new_target, "new.target") +DEF(this_active_func, "this.active_func") +DEF(home_object, "") +DEF(computed_field, "") +DEF(static_computed_field, "") /* must come after computed_fields */ +DEF(class_fields_init, "") +DEF(brand, "") +DEF(hash_constructor, "#constructor") +DEF(as, "as") +DEF(from, "from") +DEF(meta, "meta") +DEF(_default_, "*default*") +DEF(_star_, "*") +DEF(Module, "Module") +DEF(then, "then") +DEF(resolve, "resolve") +DEF(reject, "reject") +DEF(promise, "promise") +DEF(proxy, "proxy") +DEF(revoke, "revoke") +DEF(async, "async") +DEF(exec, "exec") +DEF(groups, "groups") +DEF(indices, "indices") +DEF(status, "status") +DEF(reason, "reason") +DEF(globalThis, "globalThis") +DEF(bigint, "bigint") +#ifdef CONFIG_BIGNUM +DEF(bigfloat, "bigfloat") +DEF(bigdecimal, "bigdecimal") +DEF(roundingMode, "roundingMode") +DEF(maximumSignificantDigits, "maximumSignificantDigits") +DEF(maximumFractionDigits, "maximumFractionDigits") +#endif +/* the following 3 atoms are only used with CONFIG_ATOMICS */ +DEF(not_equal, "not-equal") +DEF(timed_out, "timed-out") +DEF(ok, "ok") +/* */ +DEF(toJSON, "toJSON") +/* class names */ +DEF(Object, "Object") +DEF(Array, "Array") +DEF(Error, "Error") +DEF(Number, "Number") +DEF(String, "String") +DEF(Boolean, "Boolean") +DEF(Symbol, "Symbol") +DEF(Arguments, "Arguments") +DEF(Math, "Math") +DEF(JSON, "JSON") +DEF(Date, "Date") +DEF(Function, "Function") +DEF(GeneratorFunction, "GeneratorFunction") +DEF(ForInIterator, "ForInIterator") +DEF(RegExp, "RegExp") +DEF(ArrayBuffer, "ArrayBuffer") +DEF(SharedArrayBuffer, "SharedArrayBuffer") +/* must keep same order as class IDs for typed arrays */ +DEF(Uint8ClampedArray, "Uint8ClampedArray") +DEF(Int8Array, "Int8Array") +DEF(Uint8Array, "Uint8Array") +DEF(Int16Array, "Int16Array") +DEF(Uint16Array, "Uint16Array") +DEF(Int32Array, "Int32Array") +DEF(Uint32Array, "Uint32Array") +DEF(BigInt64Array, "BigInt64Array") +DEF(BigUint64Array, "BigUint64Array") +DEF(Float32Array, "Float32Array") +DEF(Float64Array, "Float64Array") +DEF(DataView, "DataView") +DEF(BigInt, "BigInt") +#ifdef CONFIG_BIGNUM +DEF(BigFloat, "BigFloat") +DEF(BigFloatEnv, "BigFloatEnv") +DEF(BigDecimal, "BigDecimal") +DEF(OperatorSet, "OperatorSet") +DEF(Operators, "Operators") +#endif +DEF(Map, "Map") +DEF(Set, "Set") /* Map + 1 */ +DEF(WeakMap, "WeakMap") /* Map + 2 */ +DEF(WeakSet, "WeakSet") /* Map + 3 */ +DEF(Map_Iterator, "Map Iterator") +DEF(Set_Iterator, "Set Iterator") +DEF(Array_Iterator, "Array Iterator") +DEF(String_Iterator, "String Iterator") +DEF(RegExp_String_Iterator, "RegExp String Iterator") +DEF(Generator, "Generator") +DEF(Proxy, "Proxy") +DEF(Promise, "Promise") +DEF(PromiseResolveFunction, "PromiseResolveFunction") +DEF(PromiseRejectFunction, "PromiseRejectFunction") +DEF(AsyncFunction, "AsyncFunction") +DEF(AsyncFunctionResolve, "AsyncFunctionResolve") +DEF(AsyncFunctionReject, "AsyncFunctionReject") +DEF(AsyncGeneratorFunction, "AsyncGeneratorFunction") +DEF(AsyncGenerator, "AsyncGenerator") +DEF(EvalError, "EvalError") +DEF(RangeError, "RangeError") +DEF(ReferenceError, "ReferenceError") +DEF(SyntaxError, "SyntaxError") +DEF(TypeError, "TypeError") +DEF(URIError, "URIError") +DEF(InternalError, "InternalError") +/* private symbols */ +DEF(Private_brand, "") +/* symbols */ +DEF(Symbol_toPrimitive, "Symbol.toPrimitive") +DEF(Symbol_iterator, "Symbol.iterator") +DEF(Symbol_match, "Symbol.match") +DEF(Symbol_matchAll, "Symbol.matchAll") +DEF(Symbol_replace, "Symbol.replace") +DEF(Symbol_search, "Symbol.search") +DEF(Symbol_split, "Symbol.split") +DEF(Symbol_toStringTag, "Symbol.toStringTag") +DEF(Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") +DEF(Symbol_hasInstance, "Symbol.hasInstance") +DEF(Symbol_species, "Symbol.species") +DEF(Symbol_unscopables, "Symbol.unscopables") +DEF(Symbol_asyncIterator, "Symbol.asyncIterator") +#ifdef CONFIG_BIGNUM +DEF(Symbol_operatorSet, "Symbol.operatorSet") +#endif + +#endif /* DEF */ diff --git a/source/engine/thirdparty/quickjs/quickjs-libc.c b/source/engine/thirdparty/quickjs/quickjs-libc.c new file mode 100644 index 0000000..b00dc16 --- /dev/null +++ b/source/engine/thirdparty/quickjs/quickjs-libc.c @@ -0,0 +1,4034 @@ +/* + * QuickJS C library + * + * Copyright (c) 2017-2021 Fabrice Bellard + * Copyright (c) 2017-2021 Charlie Gordon + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(_WIN32) +#include +#include +#include +#else +#include +#include +#include +#include + +#if defined(__FreeBSD__) +extern char **environ; +#endif + +#if defined(__APPLE__) || defined(__FreeBSD__) +typedef sig_t sighandler_t; +#endif + +#if defined(__APPLE__) +#if !defined(environ) +#include +#define environ (*_NSGetEnviron()) +#endif +#endif /* __APPLE__ */ + +#endif + +#if !defined(_WIN32) +/* enable the os.Worker API. IT relies on POSIX threads */ +#define USE_WORKER +#endif + +#ifdef USE_WORKER +#include +#include +#endif + +#include "cutils.h" +#include "list.h" +#include "quickjs-libc.h" + +/* TODO: + - add socket calls +*/ + +typedef struct { + struct list_head link; + int fd; + JSValue rw_func[2]; +} JSOSRWHandler; + +typedef struct { + struct list_head link; + int sig_num; + JSValue func; +} JSOSSignalHandler; + +typedef struct { + struct list_head link; + int timer_id; + int64_t timeout; + JSValue func; +} JSOSTimer; + +typedef struct { + struct list_head link; + uint8_t *data; + size_t data_len; + /* list of SharedArrayBuffers, necessary to free the message */ + uint8_t **sab_tab; + size_t sab_tab_len; +} JSWorkerMessage; + +typedef struct { + int ref_count; +#ifdef USE_WORKER + pthread_mutex_t mutex; +#endif + struct list_head msg_queue; /* list of JSWorkerMessage.link */ + int read_fd; + int write_fd; +} JSWorkerMessagePipe; + +typedef struct { + struct list_head link; + JSWorkerMessagePipe *recv_pipe; + JSValue on_message_func; +} JSWorkerMessageHandler; + +typedef struct JSThreadState { + struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */ + struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */ + struct list_head os_timers; /* list of JSOSTimer.link */ + struct list_head port_list; /* list of JSWorkerMessageHandler.link */ + int eval_script_recurse; /* only used in the main thread */ + int next_timer_id; /* for setTimeout() */ + /* not used in the main thread */ + JSWorkerMessagePipe *recv_pipe, *send_pipe; +} JSThreadState; + +static uint64_t os_pending_signals; +static int (*os_poll_func)(JSContext *ctx); + +static void js_std_dbuf_init(JSContext *ctx, DynBuf *s) +{ + dbuf_init2(s, JS_GetRuntime(ctx), (DynBufReallocFunc *)js_realloc_rt); +} + +static BOOL my_isdigit(int c) +{ + return (c >= '0' && c <= '9'); +} + +static JSValue js_printf_internal(JSContext *ctx, + int argc, JSValueConst *argv, FILE *fp) +{ + char fmtbuf[32]; + uint8_t cbuf[UTF8_CHAR_LEN_MAX+1]; + JSValue res; + DynBuf dbuf; + const char *fmt_str = NULL; + const uint8_t *fmt, *fmt_end; + const uint8_t *p; + char *q; + int i, c, len, mod; + size_t fmt_len; + int32_t int32_arg; + int64_t int64_arg; + double double_arg; + const char *string_arg; + /* Use indirect call to dbuf_printf to prevent gcc warning */ + int (*dbuf_printf_fun)(DynBuf *s, const char *fmt, ...) = (void*)dbuf_printf; + + js_std_dbuf_init(ctx, &dbuf); + + if (argc > 0) { + fmt_str = JS_ToCStringLen(ctx, &fmt_len, argv[0]); + if (!fmt_str) + goto fail; + + i = 1; + fmt = (const uint8_t *)fmt_str; + fmt_end = fmt + fmt_len; + while (fmt < fmt_end) { + for (p = fmt; fmt < fmt_end && *fmt != '%'; fmt++) + continue; + dbuf_put(&dbuf, p, fmt - p); + if (fmt >= fmt_end) + break; + q = fmtbuf; + *q++ = *fmt++; /* copy '%' */ + + /* flags */ + for(;;) { + c = *fmt; + if (c == '0' || c == '#' || c == '+' || c == '-' || c == ' ' || + c == '\'') { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = c; + fmt++; + } else { + break; + } + } + /* width */ + if (*fmt == '*') { + if (i >= argc) + goto missing; + if (JS_ToInt32(ctx, &int32_arg, argv[i++])) + goto fail; + q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); + fmt++; + } else { + while (my_isdigit(*fmt)) { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = *fmt++; + } + } + if (*fmt == '.') { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = *fmt++; + if (*fmt == '*') { + if (i >= argc) + goto missing; + if (JS_ToInt32(ctx, &int32_arg, argv[i++])) + goto fail; + q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); + fmt++; + } else { + while (my_isdigit(*fmt)) { + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = *fmt++; + } + } + } + + /* we only support the "l" modifier for 64 bit numbers */ + mod = ' '; + if (*fmt == 'l') { + mod = *fmt++; + } + + /* type */ + c = *fmt++; + if (q >= fmtbuf + sizeof(fmtbuf) - 1) + goto invalid; + *q++ = c; + *q = '\0'; + + switch (c) { + case 'c': + if (i >= argc) + goto missing; + if (JS_IsString(argv[i])) { + string_arg = JS_ToCString(ctx, argv[i++]); + if (!string_arg) + goto fail; + int32_arg = unicode_from_utf8((const uint8_t *)string_arg, UTF8_CHAR_LEN_MAX, &p); + JS_FreeCString(ctx, string_arg); + } else { + if (JS_ToInt32(ctx, &int32_arg, argv[i++])) + goto fail; + } + /* handle utf-8 encoding explicitly */ + if ((unsigned)int32_arg > 0x10FFFF) + int32_arg = 0xFFFD; + /* ignore conversion flags, width and precision */ + len = unicode_to_utf8(cbuf, int32_arg); + dbuf_put(&dbuf, cbuf, len); + break; + + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + if (i >= argc) + goto missing; + if (JS_ToInt64Ext(ctx, &int64_arg, argv[i++])) + goto fail; + if (mod == 'l') { + /* 64 bit number */ +#if defined(_WIN32) + if (q >= fmtbuf + sizeof(fmtbuf) - 3) + goto invalid; + q[2] = q[-1]; + q[-1] = 'I'; + q[0] = '6'; + q[1] = '4'; + q[3] = '\0'; + dbuf_printf_fun(&dbuf, fmtbuf, (int64_t)int64_arg); +#else + if (q >= fmtbuf + sizeof(fmtbuf) - 2) + goto invalid; + q[1] = q[-1]; + q[-1] = q[0] = 'l'; + q[2] = '\0'; + dbuf_printf_fun(&dbuf, fmtbuf, (long long)int64_arg); +#endif + } else { + dbuf_printf_fun(&dbuf, fmtbuf, (int)int64_arg); + } + break; + + case 's': + if (i >= argc) + goto missing; + /* XXX: handle strings containing null characters */ + string_arg = JS_ToCString(ctx, argv[i++]); + if (!string_arg) + goto fail; + dbuf_printf_fun(&dbuf, fmtbuf, string_arg); + JS_FreeCString(ctx, string_arg); + break; + + case 'e': + case 'f': + case 'g': + case 'a': + case 'E': + case 'F': + case 'G': + case 'A': + if (i >= argc) + goto missing; + if (JS_ToFloat64(ctx, &double_arg, argv[i++])) + goto fail; + dbuf_printf_fun(&dbuf, fmtbuf, double_arg); + break; + + case '%': + dbuf_putc(&dbuf, '%'); + break; + + default: + /* XXX: should support an extension mechanism */ + invalid: + JS_ThrowTypeError(ctx, "invalid conversion specifier in format string"); + goto fail; + missing: + JS_ThrowReferenceError(ctx, "missing argument for conversion specifier"); + goto fail; + } + } + JS_FreeCString(ctx, fmt_str); + } + if (dbuf.error) { + res = JS_ThrowOutOfMemory(ctx); + } else { + if (fp) { + len = fwrite(dbuf.buf, 1, dbuf.size, fp); + res = JS_NewInt32(ctx, len); + } else { + res = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size); + } + } + dbuf_free(&dbuf); + return res; + +fail: + JS_FreeCString(ctx, fmt_str); + dbuf_free(&dbuf); + return JS_EXCEPTION; +} + +uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename) +{ + FILE *f; + uint8_t *buf; + size_t buf_len; + long lret; + + f = fopen(filename, "rb"); + if (!f) + return NULL; + if (fseek(f, 0, SEEK_END) < 0) + goto fail; + lret = ftell(f); + if (lret < 0) + goto fail; + /* XXX: on Linux, ftell() return LONG_MAX for directories */ + if (lret == LONG_MAX) { + errno = EISDIR; + goto fail; + } + buf_len = lret; + if (fseek(f, 0, SEEK_SET) < 0) + goto fail; + if (ctx) + buf = js_malloc(ctx, buf_len + 1); + else + buf = malloc(buf_len + 1); + if (!buf) + goto fail; + if (fread(buf, 1, buf_len, f) != buf_len) { + errno = EIO; + if (ctx) + js_free(ctx, buf); + else + free(buf); + fail: + fclose(f); + return NULL; + } + buf[buf_len] = '\0'; + fclose(f); + *pbuf_len = buf_len; + return buf; +} + +/* load and evaluate a file */ +static JSValue js_loadScript(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *buf; + const char *filename; + JSValue ret; + size_t buf_len; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + return JS_EXCEPTION; + buf = js_load_file(ctx, &buf_len, filename); + if (!buf) { + JS_ThrowReferenceError(ctx, "could not load '%s'", filename); + JS_FreeCString(ctx, filename); + return JS_EXCEPTION; + } + ret = JS_Eval(ctx, (char *)buf, buf_len, filename, + JS_EVAL_TYPE_GLOBAL); + js_free(ctx, buf); + JS_FreeCString(ctx, filename); + return ret; +} + +/* load a file as a UTF-8 encoded string */ +static JSValue js_std_loadFile(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + uint8_t *buf; + const char *filename; + JSValue ret; + size_t buf_len; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + return JS_EXCEPTION; + buf = js_load_file(ctx, &buf_len, filename); + JS_FreeCString(ctx, filename); + if (!buf) + return JS_NULL; + ret = JS_NewStringLen(ctx, (char *)buf, buf_len); + js_free(ctx, buf); + return ret; +} + +typedef JSModuleDef *(JSInitModuleFunc)(JSContext *ctx, + const char *module_name); + + +#if defined(_WIN32) +static JSModuleDef *js_module_loader_so(JSContext *ctx, + const char *module_name) +{ + JS_ThrowReferenceError(ctx, "shared library modules are not supported yet"); + return NULL; +} +#else +static JSModuleDef *js_module_loader_so(JSContext *ctx, + const char *module_name) +{ + JSModuleDef *m; + void *hd; + JSInitModuleFunc *init; + char *filename; + + if (!strchr(module_name, '/')) { + /* must add a '/' so that the DLL is not searched in the + system library paths */ + filename = js_malloc(ctx, strlen(module_name) + 2 + 1); + if (!filename) + return NULL; + strcpy(filename, "./"); + strcpy(filename + 2, module_name); + } else { + filename = (char *)module_name; + } + + /* C module */ + hd = dlopen(filename, RTLD_NOW | RTLD_LOCAL); + if (filename != module_name) + js_free(ctx, filename); + if (!hd) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s' as shared library", + module_name); + goto fail; + } + + init = dlsym(hd, "js_init_module"); + if (!init) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s': js_init_module not found", + module_name); + goto fail; + } + + m = init(ctx, module_name); + if (!m) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s': initialization error", + module_name); + fail: + if (hd) + dlclose(hd); + return NULL; + } + return m; +} +#endif /* !_WIN32 */ + +int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, + JS_BOOL use_realpath, JS_BOOL is_main) +{ + JSModuleDef *m; + char buf[PATH_MAX + 16]; + JSValue meta_obj; + JSAtom module_name_atom; + const char *module_name; + + assert(JS_VALUE_GET_TAG(func_val) == JS_TAG_MODULE); + m = JS_VALUE_GET_PTR(func_val); + + module_name_atom = JS_GetModuleName(ctx, m); + module_name = JS_AtomToCString(ctx, module_name_atom); + JS_FreeAtom(ctx, module_name_atom); + if (!module_name) + return -1; + if (!strchr(module_name, ':')) { + strcpy(buf, "file://"); +#if !defined(_WIN32) + /* realpath() cannot be used with modules compiled with qjsc + because the corresponding module source code is not + necessarily present */ + if (use_realpath) { + char *res = realpath(module_name, buf + strlen(buf)); + if (!res) { + JS_ThrowTypeError(ctx, "realpath failure"); + JS_FreeCString(ctx, module_name); + return -1; + } + } else +#endif + { + pstrcat(buf, sizeof(buf), module_name); + } + } else { + pstrcpy(buf, sizeof(buf), module_name); + } + JS_FreeCString(ctx, module_name); + + meta_obj = JS_GetImportMeta(ctx, m); + if (JS_IsException(meta_obj)) + return -1; + JS_DefinePropertyValueStr(ctx, meta_obj, "url", + JS_NewString(ctx, buf), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, meta_obj, "main", + JS_NewBool(ctx, is_main), + JS_PROP_C_W_E); + JS_FreeValue(ctx, meta_obj); + return 0; +} + +JSModuleDef *js_module_loader(JSContext *ctx, + const char *module_name, void *opaque) +{ + JSModuleDef *m; + + if (has_suffix(module_name, ".so")) { + m = js_module_loader_so(ctx, module_name); + } else { + size_t buf_len; + uint8_t *buf; + JSValue func_val; + + buf = js_load_file(ctx, &buf_len, module_name); + if (!buf) { + JS_ThrowReferenceError(ctx, "could not load module filename '%s'", + module_name); + return NULL; + } + + /* compile the module */ + func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + js_free(ctx, buf); + if (JS_IsException(func_val)) + return NULL; + /* XXX: could propagate the exception */ + js_module_set_import_meta(ctx, func_val, TRUE, FALSE); + /* the module is already referenced, so we must free it */ + m = JS_VALUE_GET_PTR(func_val); + JS_FreeValue(ctx, func_val); + } + return m; +} + +static JSValue js_std_exit(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int status; + if (JS_ToInt32(ctx, &status, argv[0])) + status = -1; + exit(status); + return JS_UNDEFINED; +} + +static JSValue js_std_getenv(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *name, *str; + name = JS_ToCString(ctx, argv[0]); + if (!name) + return JS_EXCEPTION; + str = getenv(name); + JS_FreeCString(ctx, name); + if (!str) + return JS_UNDEFINED; + else + return JS_NewString(ctx, str); +} + +#if defined(_WIN32) +static void setenv(const char *name, const char *value, int overwrite) +{ + char *str; + size_t name_len, value_len; + name_len = strlen(name); + value_len = strlen(value); + str = malloc(name_len + 1 + value_len + 1); + memcpy(str, name, name_len); + str[name_len] = '='; + memcpy(str + name_len + 1, value, value_len); + str[name_len + 1 + value_len] = '\0'; + _putenv(str); + free(str); +} + +static void unsetenv(const char *name) +{ + setenv(name, "", TRUE); +} +#endif /* _WIN32 */ + +static JSValue js_std_setenv(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *name, *value; + name = JS_ToCString(ctx, argv[0]); + if (!name) + return JS_EXCEPTION; + value = JS_ToCString(ctx, argv[1]); + if (!value) { + JS_FreeCString(ctx, name); + return JS_EXCEPTION; + } + setenv(name, value, TRUE); + JS_FreeCString(ctx, name); + JS_FreeCString(ctx, value); + return JS_UNDEFINED; +} + +static JSValue js_std_unsetenv(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *name; + name = JS_ToCString(ctx, argv[0]); + if (!name) + return JS_EXCEPTION; + unsetenv(name); + JS_FreeCString(ctx, name); + return JS_UNDEFINED; +} + +/* return an object containing the list of the available environment + variables. */ +static JSValue js_std_getenviron(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + char **envp; + const char *name, *p, *value; + JSValue obj; + uint32_t idx; + size_t name_len; + JSAtom atom; + int ret; + + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + envp = environ; + for(idx = 0; envp[idx] != NULL; idx++) { + name = envp[idx]; + p = strchr(name, '='); + name_len = p - name; + if (!p) + continue; + value = p + 1; + atom = JS_NewAtomLen(ctx, name, name_len); + if (atom == JS_ATOM_NULL) + goto fail; + ret = JS_DefinePropertyValue(ctx, obj, atom, JS_NewString(ctx, value), + JS_PROP_C_W_E); + JS_FreeAtom(ctx, atom); + if (ret < 0) + goto fail; + } + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_std_gc(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JS_RunGC(JS_GetRuntime(ctx)); + return JS_UNDEFINED; +} + +static int interrupt_handler(JSRuntime *rt, void *opaque) +{ + return (os_pending_signals >> SIGINT) & 1; +} + +static int get_bool_option(JSContext *ctx, BOOL *pbool, + JSValueConst obj, + const char *option) +{ + JSValue val; + val = JS_GetPropertyStr(ctx, obj, option); + if (JS_IsException(val)) + return -1; + if (!JS_IsUndefined(val)) { + *pbool = JS_ToBool(ctx, val); + } + JS_FreeValue(ctx, val); + return 0; +} + +static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + const char *str; + size_t len; + JSValue ret; + JSValueConst options_obj; + BOOL backtrace_barrier = FALSE; + BOOL is_async = FALSE; + int flags; + + if (argc >= 2) { + options_obj = argv[1]; + if (get_bool_option(ctx, &backtrace_barrier, options_obj, + "backtrace_barrier")) + return JS_EXCEPTION; + if (get_bool_option(ctx, &is_async, options_obj, + "async")) + return JS_EXCEPTION; + } + + str = JS_ToCStringLen(ctx, &len, argv[0]); + if (!str) + return JS_EXCEPTION; + if (!ts->recv_pipe && ++ts->eval_script_recurse == 1) { + /* install the interrupt handler */ + JS_SetInterruptHandler(JS_GetRuntime(ctx), interrupt_handler, NULL); + } + flags = JS_EVAL_TYPE_GLOBAL; + if (backtrace_barrier) + flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER; + if (is_async) + flags |= JS_EVAL_FLAG_ASYNC; + ret = JS_Eval(ctx, str, len, "", flags); + JS_FreeCString(ctx, str); + if (!ts->recv_pipe && --ts->eval_script_recurse == 0) { + /* remove the interrupt handler */ + JS_SetInterruptHandler(JS_GetRuntime(ctx), NULL, NULL); + os_pending_signals &= ~((uint64_t)1 << SIGINT); + /* convert the uncatchable "interrupted" error into a normal error + so that it can be caught by the REPL */ + if (JS_IsException(ret)) + JS_ResetUncatchableError(ctx); + } + return ret; +} + +static JSClassID js_std_file_class_id; + +typedef struct { + FILE *f; + BOOL close_in_finalizer; + BOOL is_popen; +} JSSTDFile; + +static void js_std_file_finalizer(JSRuntime *rt, JSValue val) +{ + JSSTDFile *s = JS_GetOpaque(val, js_std_file_class_id); + if (s) { + if (s->f && s->close_in_finalizer) { + if (s->is_popen) + pclose(s->f); + else + fclose(s->f); + } + js_free_rt(rt, s); + } +} + +static ssize_t js_get_errno(ssize_t ret) +{ + if (ret == -1) + ret = -errno; + return ret; +} + +static JSValue js_std_strerror(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int err; + if (JS_ToInt32(ctx, &err, argv[0])) + return JS_EXCEPTION; + return JS_NewString(ctx, strerror(err)); +} + +static JSValue js_std_parseExtJSON(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue obj; + const char *str; + size_t len; + + str = JS_ToCStringLen(ctx, &len, argv[0]); + if (!str) + return JS_EXCEPTION; + obj = JS_ParseJSON2(ctx, str, len, "", JS_PARSE_JSON_EXT); + JS_FreeCString(ctx, str); + return obj; +} + +static JSValue js_new_std_file(JSContext *ctx, FILE *f, + BOOL close_in_finalizer, + BOOL is_popen) +{ + JSSTDFile *s; + JSValue obj; + obj = JS_NewObjectClass(ctx, js_std_file_class_id); + if (JS_IsException(obj)) + return obj; + s = js_mallocz(ctx, sizeof(*s)); + if (!s) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + s->close_in_finalizer = close_in_finalizer; + s->is_popen = is_popen; + s->f = f; + JS_SetOpaque(obj, s); + return obj; +} + +static void js_set_error_object(JSContext *ctx, JSValue obj, int err) +{ + if (!JS_IsUndefined(obj)) { + JS_SetPropertyStr(ctx, obj, "errno", JS_NewInt32(ctx, err)); + } +} + +static JSValue js_std_open(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *filename, *mode = NULL; + FILE *f; + int err; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + goto fail; + mode = JS_ToCString(ctx, argv[1]); + if (!mode) + goto fail; + if (mode[strspn(mode, "rwa+b")] != '\0') { + JS_ThrowTypeError(ctx, "invalid file mode"); + goto fail; + } + + f = fopen(filename, mode); + if (!f) + err = errno; + else + err = 0; + if (argc >= 3) + js_set_error_object(ctx, argv[2], err); + JS_FreeCString(ctx, filename); + JS_FreeCString(ctx, mode); + if (!f) + return JS_NULL; + return js_new_std_file(ctx, f, TRUE, FALSE); + fail: + JS_FreeCString(ctx, filename); + JS_FreeCString(ctx, mode); + return JS_EXCEPTION; +} + +static JSValue js_std_popen(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *filename, *mode = NULL; + FILE *f; + int err; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + goto fail; + mode = JS_ToCString(ctx, argv[1]); + if (!mode) + goto fail; + if (mode[strspn(mode, "rw")] != '\0') { + JS_ThrowTypeError(ctx, "invalid file mode"); + goto fail; + } + + f = popen(filename, mode); + if (!f) + err = errno; + else + err = 0; + if (argc >= 3) + js_set_error_object(ctx, argv[2], err); + JS_FreeCString(ctx, filename); + JS_FreeCString(ctx, mode); + if (!f) + return JS_NULL; + return js_new_std_file(ctx, f, TRUE, TRUE); + fail: + JS_FreeCString(ctx, filename); + JS_FreeCString(ctx, mode); + return JS_EXCEPTION; +} + +static JSValue js_std_fdopen(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *mode; + FILE *f; + int fd, err; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + mode = JS_ToCString(ctx, argv[1]); + if (!mode) + goto fail; + if (mode[strspn(mode, "rwa+")] != '\0') { + JS_ThrowTypeError(ctx, "invalid file mode"); + goto fail; + } + + f = fdopen(fd, mode); + if (!f) + err = errno; + else + err = 0; + if (argc >= 3) + js_set_error_object(ctx, argv[2], err); + JS_FreeCString(ctx, mode); + if (!f) + return JS_NULL; + return js_new_std_file(ctx, f, TRUE, FALSE); + fail: + JS_FreeCString(ctx, mode); + return JS_EXCEPTION; +} + +static JSValue js_std_tmpfile(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f; + f = tmpfile(); + if (argc >= 1) + js_set_error_object(ctx, argv[0], f ? 0 : errno); + if (!f) + return JS_NULL; + return js_new_std_file(ctx, f, TRUE, FALSE); +} + +static JSValue js_std_sprintf(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return js_printf_internal(ctx, argc, argv, NULL); +} + +static JSValue js_std_printf(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return js_printf_internal(ctx, argc, argv, stdout); +} + +static FILE *js_std_file_get(JSContext *ctx, JSValueConst obj) +{ + JSSTDFile *s = JS_GetOpaque2(ctx, obj, js_std_file_class_id); + if (!s) + return NULL; + if (!s->f) { + JS_ThrowTypeError(ctx, "invalid file handle"); + return NULL; + } + return s->f; +} + +static JSValue js_std_file_puts(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + FILE *f; + int i; + const char *str; + size_t len; + + if (magic == 0) { + f = stdout; + } else { + f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + } + + for(i = 0; i < argc; i++) { + str = JS_ToCStringLen(ctx, &len, argv[i]); + if (!str) + return JS_EXCEPTION; + fwrite(str, 1, len, f); + JS_FreeCString(ctx, str); + } + return JS_UNDEFINED; +} + +static JSValue js_std_file_close(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSSTDFile *s = JS_GetOpaque2(ctx, this_val, js_std_file_class_id); + int err; + if (!s) + return JS_EXCEPTION; + if (!s->f) + return JS_ThrowTypeError(ctx, "invalid file handle"); + if (s->is_popen) + err = js_get_errno(pclose(s->f)); + else + err = js_get_errno(fclose(s->f)); + s->f = NULL; + return JS_NewInt32(ctx, err); +} + +static JSValue js_std_file_printf(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return js_printf_internal(ctx, argc, argv, f); +} + +static JSValue js_std_file_flush(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + fflush(f); + return JS_UNDEFINED; +} + +static JSValue js_std_file_tell(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int is_bigint) +{ + FILE *f = js_std_file_get(ctx, this_val); + int64_t pos; + if (!f) + return JS_EXCEPTION; +#if defined(__linux__) + pos = ftello(f); +#else + pos = ftell(f); +#endif + if (is_bigint) + return JS_NewBigInt64(ctx, pos); + else + return JS_NewInt64(ctx, pos); +} + +static JSValue js_std_file_seek(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + int64_t pos; + int whence, ret; + if (!f) + return JS_EXCEPTION; + if (JS_ToInt64Ext(ctx, &pos, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &whence, argv[1])) + return JS_EXCEPTION; +#if defined(__linux__) + ret = fseeko(f, pos, whence); +#else + ret = fseek(f, pos, whence); +#endif + if (ret < 0) + ret = -errno; + return JS_NewInt32(ctx, ret); +} + +static JSValue js_std_file_eof(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return JS_NewBool(ctx, feof(f)); +} + +static JSValue js_std_file_error(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return JS_NewBool(ctx, ferror(f)); +} + +static JSValue js_std_file_clearerr(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + clearerr(f); + return JS_UNDEFINED; +} + +static JSValue js_std_file_fileno(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return JS_NewInt32(ctx, fileno(f)); +} + +static JSValue js_std_file_read_write(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + FILE *f = js_std_file_get(ctx, this_val); + uint64_t pos, len; + size_t size, ret; + uint8_t *buf; + + if (!f) + return JS_EXCEPTION; + if (JS_ToIndex(ctx, &pos, argv[1])) + return JS_EXCEPTION; + if (JS_ToIndex(ctx, &len, argv[2])) + return JS_EXCEPTION; + buf = JS_GetArrayBuffer(ctx, &size, argv[0]); + if (!buf) + return JS_EXCEPTION; + if (pos + len > size) + return JS_ThrowRangeError(ctx, "read/write array buffer overflow"); + if (magic) + ret = fwrite(buf + pos, 1, len, f); + else + ret = fread(buf + pos, 1, len, f); + return JS_NewInt64(ctx, ret); +} + +/* XXX: could use less memory and go faster */ +static JSValue js_std_file_getline(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + int c; + DynBuf dbuf; + JSValue obj; + + if (!f) + return JS_EXCEPTION; + + js_std_dbuf_init(ctx, &dbuf); + for(;;) { + c = fgetc(f); + if (c == EOF) { + if (dbuf.size == 0) { + /* EOF */ + dbuf_free(&dbuf); + return JS_NULL; + } else { + break; + } + } + if (c == '\n') + break; + if (dbuf_putc(&dbuf, c)) { + dbuf_free(&dbuf); + return JS_ThrowOutOfMemory(ctx); + } + } + obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size); + dbuf_free(&dbuf); + return obj; +} + +/* XXX: could use less memory and go faster */ +static JSValue js_std_file_readAsString(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + int c; + DynBuf dbuf; + JSValue obj; + uint64_t max_size64; + size_t max_size; + JSValueConst max_size_val; + + if (!f) + return JS_EXCEPTION; + + if (argc >= 1) + max_size_val = argv[0]; + else + max_size_val = JS_UNDEFINED; + max_size = (size_t)-1; + if (!JS_IsUndefined(max_size_val)) { + if (JS_ToIndex(ctx, &max_size64, max_size_val)) + return JS_EXCEPTION; + if (max_size64 < max_size) + max_size = max_size64; + } + + js_std_dbuf_init(ctx, &dbuf); + while (max_size != 0) { + c = fgetc(f); + if (c == EOF) + break; + if (dbuf_putc(&dbuf, c)) { + dbuf_free(&dbuf); + return JS_EXCEPTION; + } + max_size--; + } + obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size); + dbuf_free(&dbuf); + return obj; +} + +static JSValue js_std_file_getByte(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + if (!f) + return JS_EXCEPTION; + return JS_NewInt32(ctx, fgetc(f)); +} + +static JSValue js_std_file_putByte(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + FILE *f = js_std_file_get(ctx, this_val); + int c; + if (!f) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &c, argv[0])) + return JS_EXCEPTION; + c = fputc(c, f); + return JS_NewInt32(ctx, c); +} + +/* urlGet */ + +#define URL_GET_PROGRAM "curl -s -i --" +#define URL_GET_BUF_SIZE 4096 + +static int http_get_header_line(FILE *f, char *buf, size_t buf_size, + DynBuf *dbuf) +{ + int c; + char *p; + + p = buf; + for(;;) { + c = fgetc(f); + if (c < 0) + return -1; + if ((p - buf) < buf_size - 1) + *p++ = c; + if (dbuf) + dbuf_putc(dbuf, c); + if (c == '\n') + break; + } + *p = '\0'; + return 0; +} + +static int http_get_status(const char *buf) +{ + const char *p = buf; + while (*p != ' ' && *p != '\0') + p++; + if (*p != ' ') + return 0; + while (*p == ' ') + p++; + return atoi(p); +} + +static JSValue js_std_urlGet(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *url; + DynBuf cmd_buf; + DynBuf data_buf_s, *data_buf = &data_buf_s; + DynBuf header_buf_s, *header_buf = &header_buf_s; + char *buf; + size_t i, len; + int status; + JSValue response = JS_UNDEFINED, ret_obj; + JSValueConst options_obj; + FILE *f; + BOOL binary_flag, full_flag; + + url = JS_ToCString(ctx, argv[0]); + if (!url) + return JS_EXCEPTION; + + binary_flag = FALSE; + full_flag = FALSE; + + if (argc >= 2) { + options_obj = argv[1]; + + if (get_bool_option(ctx, &binary_flag, options_obj, "binary")) + goto fail_obj; + + if (get_bool_option(ctx, &full_flag, options_obj, "full")) { + fail_obj: + JS_FreeCString(ctx, url); + return JS_EXCEPTION; + } + } + + js_std_dbuf_init(ctx, &cmd_buf); + dbuf_printf(&cmd_buf, "%s '", URL_GET_PROGRAM); + for(i = 0; url[i] != '\0'; i++) { + unsigned char c = url[i]; + switch (c) { + case '\'': + /* shell single quoted string does not support \' */ + dbuf_putstr(&cmd_buf, "'\\''"); + break; + case '[': case ']': case '{': case '}': case '\\': + /* prevent interpretation by curl as range or set specification */ + dbuf_putc(&cmd_buf, '\\'); + /* FALLTHROUGH */ + default: + dbuf_putc(&cmd_buf, c); + break; + } + } + JS_FreeCString(ctx, url); + dbuf_putstr(&cmd_buf, "'"); + dbuf_putc(&cmd_buf, '\0'); + if (dbuf_error(&cmd_buf)) { + dbuf_free(&cmd_buf); + return JS_EXCEPTION; + } + // printf("%s\n", (char *)cmd_buf.buf); + f = popen((char *)cmd_buf.buf, "r"); + dbuf_free(&cmd_buf); + if (!f) { + return JS_ThrowTypeError(ctx, "could not start curl"); + } + + js_std_dbuf_init(ctx, data_buf); + js_std_dbuf_init(ctx, header_buf); + + buf = js_malloc(ctx, URL_GET_BUF_SIZE); + if (!buf) + goto fail; + + /* get the HTTP status */ + if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, NULL) < 0) { + status = 0; + goto bad_header; + } + status = http_get_status(buf); + if (!full_flag && !(status >= 200 && status <= 299)) { + goto bad_header; + } + + /* wait until there is an empty line */ + for(;;) { + if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, header_buf) < 0) { + bad_header: + response = JS_NULL; + goto done; + } + if (!strcmp(buf, "\r\n")) + break; + } + if (dbuf_error(header_buf)) + goto fail; + header_buf->size -= 2; /* remove the trailing CRLF */ + + /* download the data */ + for(;;) { + len = fread(buf, 1, URL_GET_BUF_SIZE, f); + if (len == 0) + break; + dbuf_put(data_buf, (uint8_t *)buf, len); + } + if (dbuf_error(data_buf)) + goto fail; + if (binary_flag) { + response = JS_NewArrayBufferCopy(ctx, + data_buf->buf, data_buf->size); + } else { + response = JS_NewStringLen(ctx, (char *)data_buf->buf, data_buf->size); + } + if (JS_IsException(response)) + goto fail; + done: + js_free(ctx, buf); + buf = NULL; + pclose(f); + f = NULL; + dbuf_free(data_buf); + data_buf = NULL; + + if (full_flag) { + ret_obj = JS_NewObject(ctx); + if (JS_IsException(ret_obj)) + goto fail; + JS_DefinePropertyValueStr(ctx, ret_obj, "response", + response, + JS_PROP_C_W_E); + if (!JS_IsNull(response)) { + JS_DefinePropertyValueStr(ctx, ret_obj, "responseHeaders", + JS_NewStringLen(ctx, (char *)header_buf->buf, + header_buf->size), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, ret_obj, "status", + JS_NewInt32(ctx, status), + JS_PROP_C_W_E); + } + } else { + ret_obj = response; + } + dbuf_free(header_buf); + return ret_obj; + fail: + if (f) + pclose(f); + js_free(ctx, buf); + if (data_buf) + dbuf_free(data_buf); + if (header_buf) + dbuf_free(header_buf); + JS_FreeValue(ctx, response); + return JS_EXCEPTION; +} + +static JSClassDef js_std_file_class = { + "FILE", + .finalizer = js_std_file_finalizer, +}; + +static const JSCFunctionListEntry js_std_error_props[] = { + /* various errno values */ +#define DEF(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE ) + DEF(EINVAL), + DEF(EIO), + DEF(EACCES), + DEF(EEXIST), + DEF(ENOSPC), + DEF(ENOSYS), + DEF(EBUSY), + DEF(ENOENT), + DEF(EPERM), + DEF(EPIPE), + DEF(EBADF), +#undef DEF +}; + +static const JSCFunctionListEntry js_std_funcs[] = { + JS_CFUNC_DEF("exit", 1, js_std_exit ), + JS_CFUNC_DEF("gc", 0, js_std_gc ), + JS_CFUNC_DEF("evalScript", 1, js_evalScript ), + JS_CFUNC_DEF("loadScript", 1, js_loadScript ), + JS_CFUNC_DEF("getenv", 1, js_std_getenv ), + JS_CFUNC_DEF("setenv", 1, js_std_setenv ), + JS_CFUNC_DEF("unsetenv", 1, js_std_unsetenv ), + JS_CFUNC_DEF("getenviron", 1, js_std_getenviron ), + JS_CFUNC_DEF("urlGet", 1, js_std_urlGet ), + JS_CFUNC_DEF("loadFile", 1, js_std_loadFile ), + JS_CFUNC_DEF("strerror", 1, js_std_strerror ), + JS_CFUNC_DEF("parseExtJSON", 1, js_std_parseExtJSON ), + + /* FILE I/O */ + JS_CFUNC_DEF("open", 2, js_std_open ), + JS_CFUNC_DEF("popen", 2, js_std_popen ), + JS_CFUNC_DEF("fdopen", 2, js_std_fdopen ), + JS_CFUNC_DEF("tmpfile", 0, js_std_tmpfile ), + JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 0 ), + JS_CFUNC_DEF("printf", 1, js_std_printf ), + JS_CFUNC_DEF("sprintf", 1, js_std_sprintf ), + JS_PROP_INT32_DEF("SEEK_SET", SEEK_SET, JS_PROP_CONFIGURABLE ), + JS_PROP_INT32_DEF("SEEK_CUR", SEEK_CUR, JS_PROP_CONFIGURABLE ), + JS_PROP_INT32_DEF("SEEK_END", SEEK_END, JS_PROP_CONFIGURABLE ), + JS_OBJECT_DEF("Error", js_std_error_props, countof(js_std_error_props), JS_PROP_CONFIGURABLE), +}; + +static const JSCFunctionListEntry js_std_file_proto_funcs[] = { + JS_CFUNC_DEF("close", 0, js_std_file_close ), + JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 1 ), + JS_CFUNC_DEF("printf", 1, js_std_file_printf ), + JS_CFUNC_DEF("flush", 0, js_std_file_flush ), + JS_CFUNC_MAGIC_DEF("tell", 0, js_std_file_tell, 0 ), + JS_CFUNC_MAGIC_DEF("tello", 0, js_std_file_tell, 1 ), + JS_CFUNC_DEF("seek", 2, js_std_file_seek ), + JS_CFUNC_DEF("eof", 0, js_std_file_eof ), + JS_CFUNC_DEF("fileno", 0, js_std_file_fileno ), + JS_CFUNC_DEF("error", 0, js_std_file_error ), + JS_CFUNC_DEF("clearerr", 0, js_std_file_clearerr ), + JS_CFUNC_MAGIC_DEF("read", 3, js_std_file_read_write, 0 ), + JS_CFUNC_MAGIC_DEF("write", 3, js_std_file_read_write, 1 ), + JS_CFUNC_DEF("getline", 0, js_std_file_getline ), + JS_CFUNC_DEF("readAsString", 0, js_std_file_readAsString ), + JS_CFUNC_DEF("getByte", 0, js_std_file_getByte ), + JS_CFUNC_DEF("putByte", 1, js_std_file_putByte ), + /* setvbuf, ... */ +}; + +static int js_std_init(JSContext *ctx, JSModuleDef *m) +{ + JSValue proto; + + /* FILE class */ + /* the class ID is created once */ + JS_NewClassID(&js_std_file_class_id); + /* the class is created once per runtime */ + JS_NewClass(JS_GetRuntime(ctx), js_std_file_class_id, &js_std_file_class); + proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, proto, js_std_file_proto_funcs, + countof(js_std_file_proto_funcs)); + JS_SetClassProto(ctx, js_std_file_class_id, proto); + + JS_SetModuleExportList(ctx, m, js_std_funcs, + countof(js_std_funcs)); + JS_SetModuleExport(ctx, m, "in", js_new_std_file(ctx, stdin, FALSE, FALSE)); + JS_SetModuleExport(ctx, m, "out", js_new_std_file(ctx, stdout, FALSE, FALSE)); + JS_SetModuleExport(ctx, m, "err", js_new_std_file(ctx, stderr, FALSE, FALSE)); + return 0; +} + +JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name) +{ + JSModuleDef *m; + m = JS_NewCModule(ctx, module_name, js_std_init); + if (!m) + return NULL; + JS_AddModuleExportList(ctx, m, js_std_funcs, countof(js_std_funcs)); + JS_AddModuleExport(ctx, m, "in"); + JS_AddModuleExport(ctx, m, "out"); + JS_AddModuleExport(ctx, m, "err"); + return m; +} + +/**********************************************************/ +/* 'os' object */ + +static JSValue js_os_open(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *filename; + int flags, mode, ret; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &flags, argv[1])) + goto fail; + if (argc >= 3 && !JS_IsUndefined(argv[2])) { + if (JS_ToInt32(ctx, &mode, argv[2])) { + fail: + JS_FreeCString(ctx, filename); + return JS_EXCEPTION; + } + } else { + mode = 0666; + } +#if defined(_WIN32) + /* force binary mode by default */ + if (!(flags & O_TEXT)) + flags |= O_BINARY; +#endif + ret = js_get_errno(open(filename, flags, mode)); + JS_FreeCString(ctx, filename); + return JS_NewInt32(ctx, ret); +} + +static JSValue js_os_close(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd, ret; + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + ret = js_get_errno(close(fd)); + return JS_NewInt32(ctx, ret); +} + +static JSValue js_os_seek(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd, whence; + int64_t pos, ret; + BOOL is_bigint; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + is_bigint = JS_IsBigInt(ctx, argv[1]); + if (JS_ToInt64Ext(ctx, &pos, argv[1])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &whence, argv[2])) + return JS_EXCEPTION; + ret = lseek(fd, pos, whence); + if (ret == -1) + ret = -errno; + if (is_bigint) + return JS_NewBigInt64(ctx, ret); + else + return JS_NewInt64(ctx, ret); +} + +static JSValue js_os_read_write(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + int fd; + uint64_t pos, len; + size_t size; + ssize_t ret; + uint8_t *buf; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + if (JS_ToIndex(ctx, &pos, argv[2])) + return JS_EXCEPTION; + if (JS_ToIndex(ctx, &len, argv[3])) + return JS_EXCEPTION; + buf = JS_GetArrayBuffer(ctx, &size, argv[1]); + if (!buf) + return JS_EXCEPTION; + if (pos + len > size) + return JS_ThrowRangeError(ctx, "read/write array buffer overflow"); + if (magic) + ret = js_get_errno(write(fd, buf + pos, len)); + else + ret = js_get_errno(read(fd, buf + pos, len)); + return JS_NewInt64(ctx, ret); +} + +static JSValue js_os_isatty(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd; + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + return JS_NewBool(ctx, (isatty(fd) != 0)); +} + +#if defined(_WIN32) +static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd; + HANDLE handle; + CONSOLE_SCREEN_BUFFER_INFO info; + JSValue obj; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + handle = (HANDLE)_get_osfhandle(fd); + + if (!GetConsoleScreenBufferInfo(handle, &info)) + return JS_NULL; + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, info.dwSize.X), JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, info.dwSize.Y), JS_PROP_C_W_E); + return obj; +} + +/* Windows 10 built-in VT100 emulation */ +#define __ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 +#define __ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 + +static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd; + HANDLE handle; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + handle = (HANDLE)_get_osfhandle(fd); + SetConsoleMode(handle, ENABLE_WINDOW_INPUT | __ENABLE_VIRTUAL_TERMINAL_INPUT); + _setmode(fd, _O_BINARY); + if (fd == 0) { + handle = (HANDLE)_get_osfhandle(1); /* corresponding output */ + SetConsoleMode(handle, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | __ENABLE_VIRTUAL_TERMINAL_PROCESSING); + } + return JS_UNDEFINED; +} +#else +static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd; + struct winsize ws; + JSValue obj; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + if (ioctl(fd, TIOCGWINSZ, &ws) == 0 && + ws.ws_col >= 4 && ws.ws_row >= 4) { + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ws.ws_col), JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, ws.ws_row), JS_PROP_C_W_E); + return obj; + } else { + return JS_NULL; + } +} + +static struct termios oldtty; + +static void term_exit(void) +{ + tcsetattr(0, TCSANOW, &oldtty); +} + +/* XXX: should add a way to go back to normal mode */ +static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + struct termios tty; + int fd; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + + memset(&tty, 0, sizeof(tty)); + tcgetattr(fd, &tty); + oldtty = tty; + + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP + |INLCR|IGNCR|ICRNL|IXON); + tty.c_oflag |= OPOST; + tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + tty.c_cflag &= ~(CSIZE|PARENB); + tty.c_cflag |= CS8; + tty.c_cc[VMIN] = 1; + tty.c_cc[VTIME] = 0; + + tcsetattr(fd, TCSANOW, &tty); + + atexit(term_exit); + return JS_UNDEFINED; +} + +#endif /* !_WIN32 */ + +static JSValue js_os_remove(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *filename; + int ret; + + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + return JS_EXCEPTION; +#if defined(_WIN32) + { + struct stat st; + if (stat(filename, &st) == 0 && S_ISDIR(st.st_mode)) { + ret = rmdir(filename); + } else { + ret = unlink(filename); + } + } +#else + ret = remove(filename); +#endif + ret = js_get_errno(ret); + JS_FreeCString(ctx, filename); + return JS_NewInt32(ctx, ret); +} + +static JSValue js_os_rename(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *oldpath, *newpath; + int ret; + + oldpath = JS_ToCString(ctx, argv[0]); + if (!oldpath) + return JS_EXCEPTION; + newpath = JS_ToCString(ctx, argv[1]); + if (!newpath) { + JS_FreeCString(ctx, oldpath); + return JS_EXCEPTION; + } + ret = js_get_errno(rename(oldpath, newpath)); + JS_FreeCString(ctx, oldpath); + JS_FreeCString(ctx, newpath); + return JS_NewInt32(ctx, ret); +} + +static BOOL is_main_thread(JSRuntime *rt) +{ + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + return !ts->recv_pipe; +} + +static JSOSRWHandler *find_rh(JSThreadState *ts, int fd) +{ + JSOSRWHandler *rh; + struct list_head *el; + + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + if (rh->fd == fd) + return rh; + } + return NULL; +} + +static void free_rw_handler(JSRuntime *rt, JSOSRWHandler *rh) +{ + int i; + list_del(&rh->link); + for(i = 0; i < 2; i++) { + JS_FreeValueRT(rt, rh->rw_func[i]); + } + js_free_rt(rt, rh); +} + +static JSValue js_os_setReadHandler(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSOSRWHandler *rh; + int fd; + JSValueConst func; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + func = argv[1]; + if (JS_IsNull(func)) { + rh = find_rh(ts, fd); + if (rh) { + JS_FreeValue(ctx, rh->rw_func[magic]); + rh->rw_func[magic] = JS_NULL; + if (JS_IsNull(rh->rw_func[0]) && + JS_IsNull(rh->rw_func[1])) { + /* remove the entry */ + free_rw_handler(JS_GetRuntime(ctx), rh); + } + } + } else { + if (!JS_IsFunction(ctx, func)) + return JS_ThrowTypeError(ctx, "not a function"); + rh = find_rh(ts, fd); + if (!rh) { + rh = js_mallocz(ctx, sizeof(*rh)); + if (!rh) + return JS_EXCEPTION; + rh->fd = fd; + rh->rw_func[0] = JS_NULL; + rh->rw_func[1] = JS_NULL; + list_add_tail(&rh->link, &ts->os_rw_handlers); + } + JS_FreeValue(ctx, rh->rw_func[magic]); + rh->rw_func[magic] = JS_DupValue(ctx, func); + } + return JS_UNDEFINED; +} + +static JSOSSignalHandler *find_sh(JSThreadState *ts, int sig_num) +{ + JSOSSignalHandler *sh; + struct list_head *el; + list_for_each(el, &ts->os_signal_handlers) { + sh = list_entry(el, JSOSSignalHandler, link); + if (sh->sig_num == sig_num) + return sh; + } + return NULL; +} + +static void free_sh(JSRuntime *rt, JSOSSignalHandler *sh) +{ + list_del(&sh->link); + JS_FreeValueRT(rt, sh->func); + js_free_rt(rt, sh); +} + +static void os_signal_handler(int sig_num) +{ + os_pending_signals |= ((uint64_t)1 << sig_num); +} + +#if defined(_WIN32) +typedef void (*sighandler_t)(int sig_num); +#endif + +static JSValue js_os_signal(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSOSSignalHandler *sh; + uint32_t sig_num; + JSValueConst func; + sighandler_t handler; + + if (!is_main_thread(rt)) + return JS_ThrowTypeError(ctx, "signal handler can only be set in the main thread"); + + if (JS_ToUint32(ctx, &sig_num, argv[0])) + return JS_EXCEPTION; + if (sig_num >= 64) + return JS_ThrowRangeError(ctx, "invalid signal number"); + func = argv[1]; + /* func = null: SIG_DFL, func = undefined, SIG_IGN */ + if (JS_IsNull(func) || JS_IsUndefined(func)) { + sh = find_sh(ts, sig_num); + if (sh) { + free_sh(JS_GetRuntime(ctx), sh); + } + if (JS_IsNull(func)) + handler = SIG_DFL; + else + handler = SIG_IGN; + signal(sig_num, handler); + } else { + if (!JS_IsFunction(ctx, func)) + return JS_ThrowTypeError(ctx, "not a function"); + sh = find_sh(ts, sig_num); + if (!sh) { + sh = js_mallocz(ctx, sizeof(*sh)); + if (!sh) + return JS_EXCEPTION; + sh->sig_num = sig_num; + list_add_tail(&sh->link, &ts->os_signal_handlers); + } + JS_FreeValue(ctx, sh->func); + sh->func = JS_DupValue(ctx, func); + signal(sig_num, os_signal_handler); + } + return JS_UNDEFINED; +} + +#if defined(__linux__) || defined(__APPLE__) +static int64_t get_time_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); +} + +static int64_t get_time_ns(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec; +} +#else +/* more portable, but does not work if the date is updated */ +static int64_t get_time_ms(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); +} + +static int64_t get_time_ns(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (int64_t)tv.tv_sec * 1000000000 + (tv.tv_usec * 1000); +} +#endif + +static JSValue js_os_now(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return JS_NewFloat64(ctx, (double)get_time_ns() / 1e6); +} + +static void free_timer(JSRuntime *rt, JSOSTimer *th) +{ + list_del(&th->link); + JS_FreeValueRT(rt, th->func); + js_free_rt(rt, th); +} + +static JSValue js_os_setTimeout(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + int64_t delay; + JSValueConst func; + JSOSTimer *th; + + func = argv[0]; + if (!JS_IsFunction(ctx, func)) + return JS_ThrowTypeError(ctx, "not a function"); + if (JS_ToInt64(ctx, &delay, argv[1])) + return JS_EXCEPTION; + th = js_mallocz(ctx, sizeof(*th)); + if (!th) + return JS_EXCEPTION; + th->timer_id = ts->next_timer_id; + if (ts->next_timer_id == INT32_MAX) + ts->next_timer_id = 1; + else + ts->next_timer_id++; + th->timeout = get_time_ms() + delay; + th->func = JS_DupValue(ctx, func); + list_add_tail(&th->link, &ts->os_timers); + return JS_NewInt32(ctx, th->timer_id); +} + +static JSOSTimer *find_timer_by_id(JSThreadState *ts, int timer_id) +{ + struct list_head *el; + if (timer_id <= 0) + return NULL; + list_for_each(el, &ts->os_timers) { + JSOSTimer *th = list_entry(el, JSOSTimer, link); + if (th->timer_id == timer_id) + return th; + } + return NULL; +} + +static JSValue js_os_clearTimeout(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSOSTimer *th; + int timer_id; + + if (JS_ToInt32(ctx, &timer_id, argv[0])) + return JS_EXCEPTION; + th = find_timer_by_id(ts, timer_id); + if (!th) + return JS_UNDEFINED; + free_timer(rt, th); + return JS_UNDEFINED; +} + +/* return a promise */ +static JSValue js_os_sleepAsync(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + int64_t delay; + JSOSTimer *th; + JSValue promise, resolving_funcs[2]; + + if (JS_ToInt64(ctx, &delay, argv[0])) + return JS_EXCEPTION; + promise = JS_NewPromiseCapability(ctx, resolving_funcs); + if (JS_IsException(promise)) + return JS_EXCEPTION; + + th = js_mallocz(ctx, sizeof(*th)); + if (!th) { + JS_FreeValue(ctx, promise); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return JS_EXCEPTION; + } + th->timer_id = -1; + th->timeout = get_time_ms() + delay; + th->func = JS_DupValue(ctx, resolving_funcs[0]); + list_add_tail(&th->link, &ts->os_timers); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return promise; +} + +static void call_handler(JSContext *ctx, JSValueConst func) +{ + JSValue ret, func1; + /* 'func' might be destroyed when calling itself (if it frees the + handler), so must take extra care */ + func1 = JS_DupValue(ctx, func); + ret = JS_Call(ctx, func1, JS_UNDEFINED, 0, NULL); + JS_FreeValue(ctx, func1); + if (JS_IsException(ret)) + js_std_dump_error(ctx); + JS_FreeValue(ctx, ret); +} + +#if defined(_WIN32) + +static int js_os_poll(JSContext *ctx) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + int min_delay, console_fd; + int64_t cur_time, delay; + JSOSRWHandler *rh; + struct list_head *el; + + /* XXX: handle signals if useful */ + + if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_timers)) + return -1; /* no more events */ + + /* XXX: only timers and basic console input are supported */ + if (!list_empty(&ts->os_timers)) { + cur_time = get_time_ms(); + min_delay = 10000; + list_for_each(el, &ts->os_timers) { + JSOSTimer *th = list_entry(el, JSOSTimer, link); + delay = th->timeout - cur_time; + if (delay <= 0) { + JSValue func; + /* the timer expired */ + func = th->func; + th->func = JS_UNDEFINED; + free_timer(rt, th); + call_handler(ctx, func); + JS_FreeValue(ctx, func); + return 0; + } else if (delay < min_delay) { + min_delay = delay; + } + } + } else { + min_delay = -1; + } + + console_fd = -1; + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) { + console_fd = rh->fd; + break; + } + } + + if (console_fd >= 0) { + DWORD ti, ret; + HANDLE handle; + if (min_delay == -1) + ti = INFINITE; + else + ti = min_delay; + handle = (HANDLE)_get_osfhandle(console_fd); + ret = WaitForSingleObject(handle, ti); + if (ret == WAIT_OBJECT_0) { + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + if (rh->fd == console_fd && !JS_IsNull(rh->rw_func[0])) { + call_handler(ctx, rh->rw_func[0]); + /* must stop because the list may have been modified */ + break; + } + } + } + } else { + Sleep(min_delay); + } + return 0; +} +#else + +#ifdef USE_WORKER + +static void js_free_message(JSWorkerMessage *msg); + +/* return 1 if a message was handled, 0 if no message */ +static int handle_posted_message(JSRuntime *rt, JSContext *ctx, + JSWorkerMessageHandler *port) +{ + JSWorkerMessagePipe *ps = port->recv_pipe; + int ret; + struct list_head *el; + JSWorkerMessage *msg; + JSValue obj, data_obj, func, retval; + + pthread_mutex_lock(&ps->mutex); + if (!list_empty(&ps->msg_queue)) { + el = ps->msg_queue.next; + msg = list_entry(el, JSWorkerMessage, link); + + /* remove the message from the queue */ + list_del(&msg->link); + + if (list_empty(&ps->msg_queue)) { + uint8_t buf[16]; + int ret; + for(;;) { + ret = read(ps->read_fd, buf, sizeof(buf)); + if (ret >= 0) + break; + if (errno != EAGAIN && errno != EINTR) + break; + } + } + + pthread_mutex_unlock(&ps->mutex); + + data_obj = JS_ReadObject(ctx, msg->data, msg->data_len, + JS_READ_OBJ_SAB | JS_READ_OBJ_REFERENCE); + + js_free_message(msg); + + if (JS_IsException(data_obj)) + goto fail; + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, data_obj); + goto fail; + } + JS_DefinePropertyValueStr(ctx, obj, "data", data_obj, JS_PROP_C_W_E); + + /* 'func' might be destroyed when calling itself (if it frees the + handler), so must take extra care */ + func = JS_DupValue(ctx, port->on_message_func); + retval = JS_Call(ctx, func, JS_UNDEFINED, 1, (JSValueConst *)&obj); + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, func); + if (JS_IsException(retval)) { + fail: + js_std_dump_error(ctx); + } else { + JS_FreeValue(ctx, retval); + } + ret = 1; + } else { + pthread_mutex_unlock(&ps->mutex); + ret = 0; + } + return ret; +} +#else +static int handle_posted_message(JSRuntime *rt, JSContext *ctx, + JSWorkerMessageHandler *port) +{ + return 0; +} +#endif + +static int js_os_poll(JSContext *ctx) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + int ret, fd_max, min_delay; + int64_t cur_time, delay; + fd_set rfds, wfds; + JSOSRWHandler *rh; + struct list_head *el; + struct timeval tv, *tvp; + + /* only check signals in the main thread */ + if (!ts->recv_pipe && + unlikely(os_pending_signals != 0)) { + JSOSSignalHandler *sh; + uint64_t mask; + + list_for_each(el, &ts->os_signal_handlers) { + sh = list_entry(el, JSOSSignalHandler, link); + mask = (uint64_t)1 << sh->sig_num; + if (os_pending_signals & mask) { + os_pending_signals &= ~mask; + call_handler(ctx, sh->func); + return 0; + } + } + } + + if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_timers) && + list_empty(&ts->port_list)) + return -1; /* no more events */ + + if (!list_empty(&ts->os_timers)) { + cur_time = get_time_ms(); + min_delay = 10000; + list_for_each(el, &ts->os_timers) { + JSOSTimer *th = list_entry(el, JSOSTimer, link); + delay = th->timeout - cur_time; + if (delay <= 0) { + JSValue func; + /* the timer expired */ + func = th->func; + th->func = JS_UNDEFINED; + free_timer(rt, th); + call_handler(ctx, func); + JS_FreeValue(ctx, func); + return 0; + } else if (delay < min_delay) { + min_delay = delay; + } + } + tv.tv_sec = min_delay / 1000; + tv.tv_usec = (min_delay % 1000) * 1000; + tvp = &tv; + } else { + tvp = NULL; + } + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + fd_max = -1; + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + fd_max = max_int(fd_max, rh->fd); + if (!JS_IsNull(rh->rw_func[0])) + FD_SET(rh->fd, &rfds); + if (!JS_IsNull(rh->rw_func[1])) + FD_SET(rh->fd, &wfds); + } + + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (!JS_IsNull(port->on_message_func)) { + JSWorkerMessagePipe *ps = port->recv_pipe; + fd_max = max_int(fd_max, ps->read_fd); + FD_SET(ps->read_fd, &rfds); + } + } + + ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp); + if (ret > 0) { + list_for_each(el, &ts->os_rw_handlers) { + rh = list_entry(el, JSOSRWHandler, link); + if (!JS_IsNull(rh->rw_func[0]) && + FD_ISSET(rh->fd, &rfds)) { + call_handler(ctx, rh->rw_func[0]); + /* must stop because the list may have been modified */ + goto done; + } + if (!JS_IsNull(rh->rw_func[1]) && + FD_ISSET(rh->fd, &wfds)) { + call_handler(ctx, rh->rw_func[1]); + /* must stop because the list may have been modified */ + goto done; + } + } + + list_for_each(el, &ts->port_list) { + JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); + if (!JS_IsNull(port->on_message_func)) { + JSWorkerMessagePipe *ps = port->recv_pipe; + if (FD_ISSET(ps->read_fd, &rfds)) { + if (handle_posted_message(rt, ctx, port)) + goto done; + } + } + } + } + done: + return 0; +} +#endif /* !_WIN32 */ + +static JSValue make_obj_error(JSContext *ctx, + JSValue obj, + int err) +{ + JSValue arr; + if (JS_IsException(obj)) + return obj; + arr = JS_NewArray(ctx); + if (JS_IsException(arr)) + return JS_EXCEPTION; + JS_DefinePropertyValueUint32(ctx, arr, 0, obj, + JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, arr, 1, JS_NewInt32(ctx, err), + JS_PROP_C_W_E); + return arr; +} + +static JSValue make_string_error(JSContext *ctx, + const char *buf, + int err) +{ + return make_obj_error(ctx, JS_NewString(ctx, buf), err); +} + +/* return [cwd, errorcode] */ +static JSValue js_os_getcwd(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + char buf[PATH_MAX]; + int err; + + if (!getcwd(buf, sizeof(buf))) { + buf[0] = '\0'; + err = errno; + } else { + err = 0; + } + return make_string_error(ctx, buf, err); +} + +static JSValue js_os_chdir(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *target; + int err; + + target = JS_ToCString(ctx, argv[0]); + if (!target) + return JS_EXCEPTION; + err = js_get_errno(chdir(target)); + JS_FreeCString(ctx, target); + return JS_NewInt32(ctx, err); +} + +static JSValue js_os_mkdir(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int mode, ret; + const char *path; + + if (argc >= 2) { + if (JS_ToInt32(ctx, &mode, argv[1])) + return JS_EXCEPTION; + } else { + mode = 0777; + } + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; +#if defined(_WIN32) + (void)mode; + ret = js_get_errno(mkdir(path)); +#else + ret = js_get_errno(mkdir(path, mode)); +#endif + JS_FreeCString(ctx, path); + return JS_NewInt32(ctx, ret); +} + +/* return [array, errorcode] */ +static JSValue js_os_readdir(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *path; + DIR *f; + struct dirent *d; + JSValue obj; + int err; + uint32_t len; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) { + JS_FreeCString(ctx, path); + return JS_EXCEPTION; + } + f = opendir(path); + if (!f) + err = errno; + else + err = 0; + JS_FreeCString(ctx, path); + if (!f) + goto done; + len = 0; + for(;;) { + errno = 0; + d = readdir(f); + if (!d) { + err = errno; + break; + } + JS_DefinePropertyValueUint32(ctx, obj, len++, + JS_NewString(ctx, d->d_name), + JS_PROP_C_W_E); + } + closedir(f); + done: + return make_obj_error(ctx, obj, err); +} + +#if !defined(_WIN32) +static int64_t timespec_to_ms(const struct timespec *tv) +{ + return (int64_t)tv->tv_sec * 1000 + (tv->tv_nsec / 1000000); +} +#endif + +/* return [obj, errcode] */ +static JSValue js_os_stat(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int is_lstat) +{ + const char *path; + int err, res; + struct stat st; + JSValue obj; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; +#if defined(_WIN32) + res = stat(path, &st); +#else + if (is_lstat) + res = lstat(path, &st); + else + res = stat(path, &st); +#endif + if (res < 0) + err = errno; + else + err = 0; + JS_FreeCString(ctx, path); + if (res < 0) { + obj = JS_NULL; + } else { + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + JS_DefinePropertyValueStr(ctx, obj, "dev", + JS_NewInt64(ctx, st.st_dev), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ino", + JS_NewInt64(ctx, st.st_ino), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "mode", + JS_NewInt32(ctx, st.st_mode), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "nlink", + JS_NewInt64(ctx, st.st_nlink), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "uid", + JS_NewInt64(ctx, st.st_uid), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "gid", + JS_NewInt64(ctx, st.st_gid), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "rdev", + JS_NewInt64(ctx, st.st_rdev), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "size", + JS_NewInt64(ctx, st.st_size), + JS_PROP_C_W_E); +#if !defined(_WIN32) + JS_DefinePropertyValueStr(ctx, obj, "blocks", + JS_NewInt64(ctx, st.st_blocks), + JS_PROP_C_W_E); +#endif +#if defined(_WIN32) + JS_DefinePropertyValueStr(ctx, obj, "atime", + JS_NewInt64(ctx, (int64_t)st.st_atime * 1000), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "mtime", + JS_NewInt64(ctx, (int64_t)st.st_mtime * 1000), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ctime", + JS_NewInt64(ctx, (int64_t)st.st_ctime * 1000), + JS_PROP_C_W_E); +#elif defined(__APPLE__) + JS_DefinePropertyValueStr(ctx, obj, "atime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_atimespec)), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "mtime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_mtimespec)), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ctime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_ctimespec)), + JS_PROP_C_W_E); +#else + JS_DefinePropertyValueStr(ctx, obj, "atime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_atim)), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "mtime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_mtim)), + JS_PROP_C_W_E); + JS_DefinePropertyValueStr(ctx, obj, "ctime", + JS_NewInt64(ctx, timespec_to_ms(&st.st_ctim)), + JS_PROP_C_W_E); +#endif + } + return make_obj_error(ctx, obj, err); +} + +#if !defined(_WIN32) +static void ms_to_timeval(struct timeval *tv, uint64_t v) +{ + tv->tv_sec = v / 1000; + tv->tv_usec = (v % 1000) * 1000; +} +#endif + +static JSValue js_os_utimes(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *path; + int64_t atime, mtime; + int ret; + + if (JS_ToInt64(ctx, &atime, argv[1])) + return JS_EXCEPTION; + if (JS_ToInt64(ctx, &mtime, argv[2])) + return JS_EXCEPTION; + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; +#if defined(_WIN32) + { + struct _utimbuf times; + times.actime = atime / 1000; + times.modtime = mtime / 1000; + ret = js_get_errno(_utime(path, ×)); + } +#else + { + struct timeval times[2]; + ms_to_timeval(×[0], atime); + ms_to_timeval(×[1], mtime); + ret = js_get_errno(utimes(path, times)); + } +#endif + JS_FreeCString(ctx, path); + return JS_NewInt32(ctx, ret); +} + +/* sleep(delay_ms) */ +static JSValue js_os_sleep(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int64_t delay; + int ret; + + if (JS_ToInt64(ctx, &delay, argv[0])) + return JS_EXCEPTION; + if (delay < 0) + delay = 0; +#if defined(_WIN32) + { + if (delay > INT32_MAX) + delay = INT32_MAX; + Sleep(delay); + ret = 0; + } +#else + { + struct timespec ts; + + ts.tv_sec = delay / 1000; + ts.tv_nsec = (delay % 1000) * 1000000; + ret = js_get_errno(nanosleep(&ts, NULL)); + } +#endif + return JS_NewInt32(ctx, ret); +} + +#if defined(_WIN32) +static char *realpath(const char *path, char *buf) +{ + if (!_fullpath(buf, path, PATH_MAX)) { + errno = ENOENT; + return NULL; + } else { + return buf; + } +} +#endif + +/* return [path, errorcode] */ +static JSValue js_os_realpath(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *path; + char buf[PATH_MAX], *res; + int err; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; + res = realpath(path, buf); + JS_FreeCString(ctx, path); + if (!res) { + buf[0] = '\0'; + err = errno; + } else { + err = 0; + } + return make_string_error(ctx, buf, err); +} + +#if !defined(_WIN32) +static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *target, *linkpath; + int err; + + target = JS_ToCString(ctx, argv[0]); + if (!target) + return JS_EXCEPTION; + linkpath = JS_ToCString(ctx, argv[1]); + if (!linkpath) { + JS_FreeCString(ctx, target); + return JS_EXCEPTION; + } + err = js_get_errno(symlink(target, linkpath)); + JS_FreeCString(ctx, target); + JS_FreeCString(ctx, linkpath); + return JS_NewInt32(ctx, err); +} + +/* return [path, errorcode] */ +static JSValue js_os_readlink(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + const char *path; + char buf[PATH_MAX]; + int err; + ssize_t res; + + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; + res = readlink(path, buf, sizeof(buf) - 1); + if (res < 0) { + buf[0] = '\0'; + err = errno; + } else { + buf[res] = '\0'; + err = 0; + } + JS_FreeCString(ctx, path); + return make_string_error(ctx, buf, err); +} + +static char **build_envp(JSContext *ctx, JSValueConst obj) +{ + uint32_t len, i; + JSPropertyEnum *tab; + char **envp, *pair; + const char *key, *str; + JSValue val; + size_t key_len, str_len; + + if (JS_GetOwnPropertyNames(ctx, &tab, &len, obj, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) + return NULL; + envp = js_mallocz(ctx, sizeof(envp[0]) * ((size_t)len + 1)); + if (!envp) + goto fail; + for(i = 0; i < len; i++) { + val = JS_GetProperty(ctx, obj, tab[i].atom); + if (JS_IsException(val)) + goto fail; + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) + goto fail; + key = JS_AtomToCString(ctx, tab[i].atom); + if (!key) { + JS_FreeCString(ctx, str); + goto fail; + } + key_len = strlen(key); + str_len = strlen(str); + pair = js_malloc(ctx, key_len + str_len + 2); + if (!pair) { + JS_FreeCString(ctx, key); + JS_FreeCString(ctx, str); + goto fail; + } + memcpy(pair, key, key_len); + pair[key_len] = '='; + memcpy(pair + key_len + 1, str, str_len); + pair[key_len + 1 + str_len] = '\0'; + envp[i] = pair; + JS_FreeCString(ctx, key); + JS_FreeCString(ctx, str); + } + done: + for(i = 0; i < len; i++) + JS_FreeAtom(ctx, tab[i].atom); + js_free(ctx, tab); + return envp; + fail: + if (envp) { + for(i = 0; i < len; i++) + js_free(ctx, envp[i]); + js_free(ctx, envp); + envp = NULL; + } + goto done; +} + +/* execvpe is not available on non GNU systems */ +static int my_execvpe(const char *filename, char **argv, char **envp) +{ + char *path, *p, *p_next, *p1; + char buf[PATH_MAX]; + size_t filename_len, path_len; + BOOL eacces_error; + + filename_len = strlen(filename); + if (filename_len == 0) { + errno = ENOENT; + return -1; + } + if (strchr(filename, '/')) + return execve(filename, argv, envp); + + path = getenv("PATH"); + if (!path) + path = (char *)"/bin:/usr/bin"; + eacces_error = FALSE; + p = path; + for(p = path; p != NULL; p = p_next) { + p1 = strchr(p, ':'); + if (!p1) { + p_next = NULL; + path_len = strlen(p); + } else { + p_next = p1 + 1; + path_len = p1 - p; + } + /* path too long */ + if ((path_len + 1 + filename_len + 1) > PATH_MAX) + continue; + memcpy(buf, p, path_len); + buf[path_len] = '/'; + memcpy(buf + path_len + 1, filename, filename_len); + buf[path_len + 1 + filename_len] = '\0'; + + execve(buf, argv, envp); + + switch(errno) { + case EACCES: + eacces_error = TRUE; + break; + case ENOENT: + case ENOTDIR: + break; + default: + return -1; + } + } + if (eacces_error) + errno = EACCES; + return -1; +} + +/* exec(args[, options]) -> exitcode */ +static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValueConst options, args = argv[0]; + JSValue val, ret_val; + const char **exec_argv, *file = NULL, *str, *cwd = NULL; + char **envp = environ; + uint32_t exec_argc, i; + int ret, pid, status; + BOOL block_flag = TRUE, use_path = TRUE; + static const char *std_name[3] = { "stdin", "stdout", "stderr" }; + int std_fds[3]; + uint32_t uid = -1, gid = -1; + + val = JS_GetPropertyStr(ctx, args, "length"); + if (JS_IsException(val)) + return JS_EXCEPTION; + ret = JS_ToUint32(ctx, &exec_argc, val); + JS_FreeValue(ctx, val); + if (ret) + return JS_EXCEPTION; + /* arbitrary limit to avoid overflow */ + if (exec_argc < 1 || exec_argc > 65535) { + return JS_ThrowTypeError(ctx, "invalid number of arguments"); + } + exec_argv = js_mallocz(ctx, sizeof(exec_argv[0]) * (exec_argc + 1)); + if (!exec_argv) + return JS_EXCEPTION; + for(i = 0; i < exec_argc; i++) { + val = JS_GetPropertyUint32(ctx, args, i); + if (JS_IsException(val)) + goto exception; + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) + goto exception; + exec_argv[i] = str; + } + exec_argv[exec_argc] = NULL; + + for(i = 0; i < 3; i++) + std_fds[i] = i; + + /* get the options, if any */ + if (argc >= 2) { + options = argv[1]; + + if (get_bool_option(ctx, &block_flag, options, "block")) + goto exception; + if (get_bool_option(ctx, &use_path, options, "usePath")) + goto exception; + + val = JS_GetPropertyStr(ctx, options, "file"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + file = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!file) + goto exception; + } + + val = JS_GetPropertyStr(ctx, options, "cwd"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + cwd = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!cwd) + goto exception; + } + + /* stdin/stdout/stderr handles */ + for(i = 0; i < 3; i++) { + val = JS_GetPropertyStr(ctx, options, std_name[i]); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + int fd; + ret = JS_ToInt32(ctx, &fd, val); + JS_FreeValue(ctx, val); + if (ret) + goto exception; + std_fds[i] = fd; + } + } + + val = JS_GetPropertyStr(ctx, options, "env"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + envp = build_envp(ctx, val); + JS_FreeValue(ctx, val); + if (!envp) + goto exception; + } + + val = JS_GetPropertyStr(ctx, options, "uid"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + ret = JS_ToUint32(ctx, &uid, val); + JS_FreeValue(ctx, val); + if (ret) + goto exception; + } + + val = JS_GetPropertyStr(ctx, options, "gid"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + ret = JS_ToUint32(ctx, &gid, val); + JS_FreeValue(ctx, val); + if (ret) + goto exception; + } + } + + pid = fork(); + if (pid < 0) { + JS_ThrowTypeError(ctx, "fork error"); + goto exception; + } + if (pid == 0) { + /* child */ + int fd_max = sysconf(_SC_OPEN_MAX); + + /* remap the stdin/stdout/stderr handles if necessary */ + for(i = 0; i < 3; i++) { + if (std_fds[i] != i) { + if (dup2(std_fds[i], i) < 0) + _exit(127); + } + } + + for(i = 3; i < fd_max; i++) + close(i); + if (cwd) { + if (chdir(cwd) < 0) + _exit(127); + } + if (uid != -1) { + if (setuid(uid) < 0) + _exit(127); + } + if (gid != -1) { + if (setgid(gid) < 0) + _exit(127); + } + + if (!file) + file = exec_argv[0]; + if (use_path) + ret = my_execvpe(file, (char **)exec_argv, envp); + else + ret = execve(file, (char **)exec_argv, envp); + _exit(127); + } + /* parent */ + if (block_flag) { + for(;;) { + ret = waitpid(pid, &status, 0); + if (ret == pid) { + if (WIFEXITED(status)) { + ret = WEXITSTATUS(status); + break; + } else if (WIFSIGNALED(status)) { + ret = -WTERMSIG(status); + break; + } + } + } + } else { + ret = pid; + } + ret_val = JS_NewInt32(ctx, ret); + done: + JS_FreeCString(ctx, file); + JS_FreeCString(ctx, cwd); + for(i = 0; i < exec_argc; i++) + JS_FreeCString(ctx, exec_argv[i]); + js_free(ctx, exec_argv); + if (envp != environ) { + char **p; + p = envp; + while (*p != NULL) { + js_free(ctx, *p); + p++; + } + js_free(ctx, envp); + } + return ret_val; + exception: + ret_val = JS_EXCEPTION; + goto done; +} + +/* getpid() -> pid */ +static JSValue js_os_getpid(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return JS_NewInt32(ctx, getpid()); +} + +/* waitpid(pid, block) -> [pid, status] */ +static JSValue js_os_waitpid(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pid, status, options, ret; + JSValue obj; + + if (JS_ToInt32(ctx, &pid, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &options, argv[1])) + return JS_EXCEPTION; + + ret = waitpid(pid, &status, options); + if (ret < 0) { + ret = -errno; + status = 0; + } + + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ret), + JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, status), + JS_PROP_C_W_E); + return obj; +} + +/* pipe() -> [read_fd, write_fd] or null if error */ +static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pipe_fds[2], ret; + JSValue obj; + + ret = pipe(pipe_fds); + if (ret < 0) + return JS_NULL; + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, pipe_fds[0]), + JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, pipe_fds[1]), + JS_PROP_C_W_E); + return obj; +} + +/* kill(pid, sig) */ +static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pid, sig, ret; + + if (JS_ToInt32(ctx, &pid, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &sig, argv[1])) + return JS_EXCEPTION; + ret = js_get_errno(kill(pid, sig)); + return JS_NewInt32(ctx, ret); +} + +/* dup(fd) */ +static JSValue js_os_dup(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd, ret; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + ret = js_get_errno(dup(fd)); + return JS_NewInt32(ctx, ret); +} + +/* dup2(fd) */ +static JSValue js_os_dup2(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int fd, fd2, ret; + + if (JS_ToInt32(ctx, &fd, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &fd2, argv[1])) + return JS_EXCEPTION; + ret = js_get_errno(dup2(fd, fd2)); + return JS_NewInt32(ctx, ret); +} + +#endif /* !_WIN32 */ + +#ifdef USE_WORKER + +/* Worker */ + +typedef struct { + JSWorkerMessagePipe *recv_pipe; + JSWorkerMessagePipe *send_pipe; + JSWorkerMessageHandler *msg_handler; +} JSWorkerData; + +typedef struct { + char *filename; /* module filename */ + char *basename; /* module base name */ + JSWorkerMessagePipe *recv_pipe, *send_pipe; +} WorkerFuncArgs; + +typedef struct { + int ref_count; + uint64_t buf[0]; +} JSSABHeader; + +static JSClassID js_worker_class_id; +static JSContext *(*js_worker_new_context_func)(JSRuntime *rt); + +static int atomic_add_int(int *ptr, int v) +{ + return atomic_fetch_add((_Atomic(uint32_t) *)ptr, v) + v; +} + +/* shared array buffer allocator */ +static void *js_sab_alloc(void *opaque, size_t size) +{ + JSSABHeader *sab; + sab = malloc(sizeof(JSSABHeader) + size); + if (!sab) + return NULL; + sab->ref_count = 1; + return sab->buf; +} + +static void js_sab_free(void *opaque, void *ptr) +{ + JSSABHeader *sab; + int ref_count; + sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); + ref_count = atomic_add_int(&sab->ref_count, -1); + assert(ref_count >= 0); + if (ref_count == 0) { + free(sab); + } +} + +static void js_sab_dup(void *opaque, void *ptr) +{ + JSSABHeader *sab; + sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); + atomic_add_int(&sab->ref_count, 1); +} + +static JSWorkerMessagePipe *js_new_message_pipe(void) +{ + JSWorkerMessagePipe *ps; + int pipe_fds[2]; + + if (pipe(pipe_fds) < 0) + return NULL; + + ps = malloc(sizeof(*ps)); + if (!ps) { + close(pipe_fds[0]); + close(pipe_fds[1]); + return NULL; + } + ps->ref_count = 1; + init_list_head(&ps->msg_queue); + pthread_mutex_init(&ps->mutex, NULL); + ps->read_fd = pipe_fds[0]; + ps->write_fd = pipe_fds[1]; + return ps; +} + +static JSWorkerMessagePipe *js_dup_message_pipe(JSWorkerMessagePipe *ps) +{ + atomic_add_int(&ps->ref_count, 1); + return ps; +} + +static void js_free_message(JSWorkerMessage *msg) +{ + size_t i; + /* free the SAB */ + for(i = 0; i < msg->sab_tab_len; i++) { + js_sab_free(NULL, msg->sab_tab[i]); + } + free(msg->sab_tab); + free(msg->data); + free(msg); +} + +static void js_free_message_pipe(JSWorkerMessagePipe *ps) +{ + struct list_head *el, *el1; + JSWorkerMessage *msg; + int ref_count; + + if (!ps) + return; + + ref_count = atomic_add_int(&ps->ref_count, -1); + assert(ref_count >= 0); + if (ref_count == 0) { + list_for_each_safe(el, el1, &ps->msg_queue) { + msg = list_entry(el, JSWorkerMessage, link); + js_free_message(msg); + } + pthread_mutex_destroy(&ps->mutex); + close(ps->read_fd); + close(ps->write_fd); + free(ps); + } +} + +static void js_free_port(JSRuntime *rt, JSWorkerMessageHandler *port) +{ + if (port) { + js_free_message_pipe(port->recv_pipe); + JS_FreeValueRT(rt, port->on_message_func); + list_del(&port->link); + js_free_rt(rt, port); + } +} + +static void js_worker_finalizer(JSRuntime *rt, JSValue val) +{ + JSWorkerData *worker = JS_GetOpaque(val, js_worker_class_id); + if (worker) { + js_free_message_pipe(worker->recv_pipe); + js_free_message_pipe(worker->send_pipe); + js_free_port(rt, worker->msg_handler); + js_free_rt(rt, worker); + } +} + +static JSClassDef js_worker_class = { + "Worker", + .finalizer = js_worker_finalizer, +}; + +static void *worker_func(void *opaque) +{ + WorkerFuncArgs *args = opaque; + JSRuntime *rt; + JSThreadState *ts; + JSContext *ctx; + JSValue val; + + rt = JS_NewRuntime(); + if (rt == NULL) { + fprintf(stderr, "JS_NewRuntime failure"); + exit(1); + } + js_std_init_handlers(rt); + + JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL); + + /* set the pipe to communicate with the parent */ + ts = JS_GetRuntimeOpaque(rt); + ts->recv_pipe = args->recv_pipe; + ts->send_pipe = args->send_pipe; + + /* function pointer to avoid linking the whole JS_NewContext() if + not needed */ + ctx = js_worker_new_context_func(rt); + if (ctx == NULL) { + fprintf(stderr, "JS_NewContext failure"); + } + + JS_SetCanBlock(rt, TRUE); + + js_std_add_helpers(ctx, -1, NULL); + + val = JS_LoadModule(ctx, args->basename, args->filename); + free(args->filename); + free(args->basename); + free(args); + val = js_std_await(ctx, val); + if (JS_IsException(val)) + js_std_dump_error(ctx); + JS_FreeValue(ctx, val); + + js_std_loop(ctx); + + JS_FreeContext(ctx); + js_std_free_handlers(rt); + JS_FreeRuntime(rt); + return NULL; +} + +static JSValue js_worker_ctor_internal(JSContext *ctx, JSValueConst new_target, + JSWorkerMessagePipe *recv_pipe, + JSWorkerMessagePipe *send_pipe) +{ + JSValue obj = JS_UNDEFINED, proto; + JSWorkerData *s; + + /* create the object */ + if (JS_IsUndefined(new_target)) { + proto = JS_GetClassProto(ctx, js_worker_class_id); + } else { + proto = JS_GetPropertyStr(ctx, new_target, "prototype"); + if (JS_IsException(proto)) + goto fail; + } + obj = JS_NewObjectProtoClass(ctx, proto, js_worker_class_id); + JS_FreeValue(ctx, proto); + if (JS_IsException(obj)) + goto fail; + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + goto fail; + s->recv_pipe = js_dup_message_pipe(recv_pipe); + s->send_pipe = js_dup_message_pipe(send_pipe); + + JS_SetOpaque(obj, s); + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_worker_ctor(JSContext *ctx, JSValueConst new_target, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + WorkerFuncArgs *args = NULL; + pthread_t tid; + pthread_attr_t attr; + JSValue obj = JS_UNDEFINED; + int ret; + const char *filename = NULL, *basename; + JSAtom basename_atom; + + /* XXX: in order to avoid problems with resource liberation, we + don't support creating workers inside workers */ + if (!is_main_thread(rt)) + return JS_ThrowTypeError(ctx, "cannot create a worker inside a worker"); + + /* base name, assuming the calling function is a normal JS + function */ + basename_atom = JS_GetScriptOrModuleName(ctx, 1); + if (basename_atom == JS_ATOM_NULL) { + return JS_ThrowTypeError(ctx, "could not determine calling script or module name"); + } + basename = JS_AtomToCString(ctx, basename_atom); + JS_FreeAtom(ctx, basename_atom); + if (!basename) + goto fail; + + /* module name */ + filename = JS_ToCString(ctx, argv[0]); + if (!filename) + goto fail; + + args = malloc(sizeof(*args)); + if (!args) + goto oom_fail; + memset(args, 0, sizeof(*args)); + args->filename = strdup(filename); + args->basename = strdup(basename); + + /* ports */ + args->recv_pipe = js_new_message_pipe(); + if (!args->recv_pipe) + goto oom_fail; + args->send_pipe = js_new_message_pipe(); + if (!args->send_pipe) + goto oom_fail; + + obj = js_worker_ctor_internal(ctx, new_target, + args->send_pipe, args->recv_pipe); + if (JS_IsException(obj)) + goto fail; + + pthread_attr_init(&attr); + /* no join at the end */ + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + ret = pthread_create(&tid, &attr, worker_func, args); + pthread_attr_destroy(&attr); + if (ret != 0) { + JS_ThrowTypeError(ctx, "could not create worker"); + goto fail; + } + JS_FreeCString(ctx, basename); + JS_FreeCString(ctx, filename); + return obj; + oom_fail: + JS_ThrowOutOfMemory(ctx); + fail: + JS_FreeCString(ctx, basename); + JS_FreeCString(ctx, filename); + if (args) { + free(args->filename); + free(args->basename); + js_free_message_pipe(args->recv_pipe); + js_free_message_pipe(args->send_pipe); + free(args); + } + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_worker_postMessage(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); + JSWorkerMessagePipe *ps; + size_t data_len, sab_tab_len, i; + uint8_t *data; + JSWorkerMessage *msg; + uint8_t **sab_tab; + + if (!worker) + return JS_EXCEPTION; + + data = JS_WriteObject2(ctx, &data_len, argv[0], + JS_WRITE_OBJ_SAB | JS_WRITE_OBJ_REFERENCE, + &sab_tab, &sab_tab_len); + if (!data) + return JS_EXCEPTION; + + msg = malloc(sizeof(*msg)); + if (!msg) + goto fail; + msg->data = NULL; + msg->sab_tab = NULL; + + /* must reallocate because the allocator may be different */ + msg->data = malloc(data_len); + if (!msg->data) + goto fail; + memcpy(msg->data, data, data_len); + msg->data_len = data_len; + + if (sab_tab_len > 0) { + msg->sab_tab = malloc(sizeof(msg->sab_tab[0]) * sab_tab_len); + if (!msg->sab_tab) + goto fail; + memcpy(msg->sab_tab, sab_tab, sizeof(msg->sab_tab[0]) * sab_tab_len); + } + msg->sab_tab_len = sab_tab_len; + + js_free(ctx, data); + js_free(ctx, sab_tab); + + /* increment the SAB reference counts */ + for(i = 0; i < msg->sab_tab_len; i++) { + js_sab_dup(NULL, msg->sab_tab[i]); + } + + ps = worker->send_pipe; + pthread_mutex_lock(&ps->mutex); + /* indicate that data is present */ + if (list_empty(&ps->msg_queue)) { + uint8_t ch = '\0'; + int ret; + for(;;) { + ret = write(ps->write_fd, &ch, 1); + if (ret == 1) + break; + if (ret < 0 && (errno != EAGAIN || errno != EINTR)) + break; + } + } + list_add_tail(&msg->link, &ps->msg_queue); + pthread_mutex_unlock(&ps->mutex); + return JS_UNDEFINED; + fail: + if (msg) { + free(msg->data); + free(msg->sab_tab); + free(msg); + } + js_free(ctx, data); + js_free(ctx, sab_tab); + return JS_EXCEPTION; + +} + +static JSValue js_worker_set_onmessage(JSContext *ctx, JSValueConst this_val, + JSValueConst func) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); + JSWorkerMessageHandler *port; + + if (!worker) + return JS_EXCEPTION; + + port = worker->msg_handler; + if (JS_IsNull(func)) { + if (port) { + js_free_port(rt, port); + worker->msg_handler = NULL; + } + } else { + if (!JS_IsFunction(ctx, func)) + return JS_ThrowTypeError(ctx, "not a function"); + if (!port) { + port = js_mallocz(ctx, sizeof(*port)); + if (!port) + return JS_EXCEPTION; + port->recv_pipe = js_dup_message_pipe(worker->recv_pipe); + port->on_message_func = JS_NULL; + list_add_tail(&port->link, &ts->port_list); + worker->msg_handler = port; + } + JS_FreeValue(ctx, port->on_message_func); + port->on_message_func = JS_DupValue(ctx, func); + } + return JS_UNDEFINED; +} + +static JSValue js_worker_get_onmessage(JSContext *ctx, JSValueConst this_val) +{ + JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); + JSWorkerMessageHandler *port; + if (!worker) + return JS_EXCEPTION; + port = worker->msg_handler; + if (port) { + return JS_DupValue(ctx, port->on_message_func); + } else { + return JS_NULL; + } +} + +static const JSCFunctionListEntry js_worker_proto_funcs[] = { + JS_CFUNC_DEF("postMessage", 1, js_worker_postMessage ), + JS_CGETSET_DEF("onmessage", js_worker_get_onmessage, js_worker_set_onmessage ), +}; + +#endif /* USE_WORKER */ + +void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)) +{ +#ifdef USE_WORKER + js_worker_new_context_func = func; +#endif +} + +#if defined(_WIN32) +#define OS_PLATFORM "win32" +#elif defined(__APPLE__) +#define OS_PLATFORM "darwin" +#elif defined(EMSCRIPTEN) +#define OS_PLATFORM "js" +#else +#define OS_PLATFORM "linux" +#endif + +#define OS_FLAG(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE ) + +static const JSCFunctionListEntry js_os_funcs[] = { + JS_CFUNC_DEF("open", 2, js_os_open ), + OS_FLAG(O_RDONLY), + OS_FLAG(O_WRONLY), + OS_FLAG(O_RDWR), + OS_FLAG(O_APPEND), + OS_FLAG(O_CREAT), + OS_FLAG(O_EXCL), + OS_FLAG(O_TRUNC), +#if defined(_WIN32) + OS_FLAG(O_BINARY), + OS_FLAG(O_TEXT), +#endif + JS_CFUNC_DEF("close", 1, js_os_close ), + JS_CFUNC_DEF("seek", 3, js_os_seek ), + JS_CFUNC_MAGIC_DEF("read", 4, js_os_read_write, 0 ), + JS_CFUNC_MAGIC_DEF("write", 4, js_os_read_write, 1 ), + JS_CFUNC_DEF("isatty", 1, js_os_isatty ), + JS_CFUNC_DEF("ttyGetWinSize", 1, js_os_ttyGetWinSize ), + JS_CFUNC_DEF("ttySetRaw", 1, js_os_ttySetRaw ), + JS_CFUNC_DEF("remove", 1, js_os_remove ), + JS_CFUNC_DEF("rename", 2, js_os_rename ), + JS_CFUNC_MAGIC_DEF("setReadHandler", 2, js_os_setReadHandler, 0 ), + JS_CFUNC_MAGIC_DEF("setWriteHandler", 2, js_os_setReadHandler, 1 ), + JS_CFUNC_DEF("signal", 2, js_os_signal ), + OS_FLAG(SIGINT), + OS_FLAG(SIGABRT), + OS_FLAG(SIGFPE), + OS_FLAG(SIGILL), + OS_FLAG(SIGSEGV), + OS_FLAG(SIGTERM), +#if !defined(_WIN32) + OS_FLAG(SIGQUIT), + OS_FLAG(SIGPIPE), + OS_FLAG(SIGALRM), + OS_FLAG(SIGUSR1), + OS_FLAG(SIGUSR2), + OS_FLAG(SIGCHLD), + OS_FLAG(SIGCONT), + OS_FLAG(SIGSTOP), + OS_FLAG(SIGTSTP), + OS_FLAG(SIGTTIN), + OS_FLAG(SIGTTOU), +#endif + JS_CFUNC_DEF("now", 0, js_os_now ), + JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ), + JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ), + JS_CFUNC_DEF("sleepAsync", 1, js_os_sleepAsync ), + JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ), + JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ), + JS_CFUNC_DEF("chdir", 0, js_os_chdir ), + JS_CFUNC_DEF("mkdir", 1, js_os_mkdir ), + JS_CFUNC_DEF("readdir", 1, js_os_readdir ), + /* st_mode constants */ + OS_FLAG(S_IFMT), + OS_FLAG(S_IFIFO), + OS_FLAG(S_IFCHR), + OS_FLAG(S_IFDIR), + OS_FLAG(S_IFBLK), + OS_FLAG(S_IFREG), +#if !defined(_WIN32) + OS_FLAG(S_IFSOCK), + OS_FLAG(S_IFLNK), + OS_FLAG(S_ISGID), + OS_FLAG(S_ISUID), +#endif + JS_CFUNC_MAGIC_DEF("stat", 1, js_os_stat, 0 ), + JS_CFUNC_DEF("utimes", 3, js_os_utimes ), + JS_CFUNC_DEF("sleep", 1, js_os_sleep ), + JS_CFUNC_DEF("realpath", 1, js_os_realpath ), +#if !defined(_WIN32) + JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), + JS_CFUNC_DEF("symlink", 2, js_os_symlink ), + JS_CFUNC_DEF("readlink", 1, js_os_readlink ), + JS_CFUNC_DEF("exec", 1, js_os_exec ), + JS_CFUNC_DEF("getpid", 0, js_os_getpid ), + JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ), + OS_FLAG(WNOHANG), + JS_CFUNC_DEF("pipe", 0, js_os_pipe ), + JS_CFUNC_DEF("kill", 2, js_os_kill ), + JS_CFUNC_DEF("dup", 1, js_os_dup ), + JS_CFUNC_DEF("dup2", 2, js_os_dup2 ), +#endif +}; + +static int js_os_init(JSContext *ctx, JSModuleDef *m) +{ + os_poll_func = js_os_poll; + +#ifdef USE_WORKER + { + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + JSValue proto, obj; + /* Worker class */ + JS_NewClassID(&js_worker_class_id); + JS_NewClass(JS_GetRuntime(ctx), js_worker_class_id, &js_worker_class); + proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, proto, js_worker_proto_funcs, countof(js_worker_proto_funcs)); + + obj = JS_NewCFunction2(ctx, js_worker_ctor, "Worker", 1, + JS_CFUNC_constructor, 0); + JS_SetConstructor(ctx, obj, proto); + + JS_SetClassProto(ctx, js_worker_class_id, proto); + + /* set 'Worker.parent' if necessary */ + if (ts->recv_pipe && ts->send_pipe) { + JS_DefinePropertyValueStr(ctx, obj, "parent", + js_worker_ctor_internal(ctx, JS_UNDEFINED, ts->recv_pipe, ts->send_pipe), + JS_PROP_C_W_E); + } + + JS_SetModuleExport(ctx, m, "Worker", obj); + } +#endif /* USE_WORKER */ + + return JS_SetModuleExportList(ctx, m, js_os_funcs, + countof(js_os_funcs)); +} + +JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name) +{ + JSModuleDef *m; + m = JS_NewCModule(ctx, module_name, js_os_init); + if (!m) + return NULL; + JS_AddModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs)); +#ifdef USE_WORKER + JS_AddModuleExport(ctx, m, "Worker"); +#endif + return m; +} + +/**********************************************************/ + +static JSValue js_print(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int i; + const char *str; + size_t len; + + for(i = 0; i < argc; i++) { + if (i != 0) + putchar(' '); + str = JS_ToCStringLen(ctx, &len, argv[i]); + if (!str) + return JS_EXCEPTION; + fwrite(str, 1, len, stdout); + JS_FreeCString(ctx, str); + } + putchar('\n'); + return JS_UNDEFINED; +} + +void js_std_add_helpers(JSContext *ctx, int argc, char **argv) +{ + JSValue global_obj, console, args; + int i; + + /* XXX: should these global definitions be enumerable? */ + global_obj = JS_GetGlobalObject(ctx); + + console = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, console, "log", + JS_NewCFunction(ctx, js_print, "log", 1)); + JS_SetPropertyStr(ctx, global_obj, "console", console); + + /* same methods as the mozilla JS shell */ + if (argc >= 0) { + args = JS_NewArray(ctx); + for(i = 0; i < argc; i++) { + JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, argv[i])); + } + JS_SetPropertyStr(ctx, global_obj, "scriptArgs", args); + } + + JS_SetPropertyStr(ctx, global_obj, "print", + JS_NewCFunction(ctx, js_print, "print", 1)); + JS_SetPropertyStr(ctx, global_obj, "__loadScript", + JS_NewCFunction(ctx, js_loadScript, "__loadScript", 1)); + + JS_FreeValue(ctx, global_obj); +} + +void js_std_init_handlers(JSRuntime *rt) +{ + JSThreadState *ts; + + ts = malloc(sizeof(*ts)); + if (!ts) { + fprintf(stderr, "Could not allocate memory for the worker"); + exit(1); + } + memset(ts, 0, sizeof(*ts)); + init_list_head(&ts->os_rw_handlers); + init_list_head(&ts->os_signal_handlers); + init_list_head(&ts->os_timers); + init_list_head(&ts->port_list); + ts->next_timer_id = 1; + + JS_SetRuntimeOpaque(rt, ts); + +#ifdef USE_WORKER + /* set the SharedArrayBuffer memory handlers */ + { + JSSharedArrayBufferFunctions sf; + memset(&sf, 0, sizeof(sf)); + sf.sab_alloc = js_sab_alloc; + sf.sab_free = js_sab_free; + sf.sab_dup = js_sab_dup; + JS_SetSharedArrayBufferFunctions(rt, &sf); + } +#endif +} + +void js_std_free_handlers(JSRuntime *rt) +{ + JSThreadState *ts = JS_GetRuntimeOpaque(rt); + struct list_head *el, *el1; + + list_for_each_safe(el, el1, &ts->os_rw_handlers) { + JSOSRWHandler *rh = list_entry(el, JSOSRWHandler, link); + free_rw_handler(rt, rh); + } + + list_for_each_safe(el, el1, &ts->os_signal_handlers) { + JSOSSignalHandler *sh = list_entry(el, JSOSSignalHandler, link); + free_sh(rt, sh); + } + + list_for_each_safe(el, el1, &ts->os_timers) { + JSOSTimer *th = list_entry(el, JSOSTimer, link); + free_timer(rt, th); + } + +#ifdef USE_WORKER + /* XXX: free port_list ? */ + js_free_message_pipe(ts->recv_pipe); + js_free_message_pipe(ts->send_pipe); +#endif + + free(ts); + JS_SetRuntimeOpaque(rt, NULL); /* fail safe */ +} + +static void js_dump_obj(JSContext *ctx, FILE *f, JSValueConst val) +{ + const char *str; + + str = JS_ToCString(ctx, val); + if (str) { + fprintf(f, "%s\n", str); + JS_FreeCString(ctx, str); + } else { + fprintf(f, "[exception]\n"); + } +} + +static void js_std_dump_error1(JSContext *ctx, JSValueConst exception_val) +{ + JSValue val; + BOOL is_error; + + is_error = JS_IsError(ctx, exception_val); + js_dump_obj(ctx, stderr, exception_val); + if (is_error) { + val = JS_GetPropertyStr(ctx, exception_val, "stack"); + if (!JS_IsUndefined(val)) { + js_dump_obj(ctx, stderr, val); + } + JS_FreeValue(ctx, val); + } +} + +void js_std_dump_error(JSContext *ctx) +{ + JSValue exception_val; + + exception_val = JS_GetException(ctx); + js_std_dump_error1(ctx, exception_val); + JS_FreeValue(ctx, exception_val); +} + +void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, + JSValueConst reason, + BOOL is_handled, void *opaque) +{ + if (!is_handled) { + fprintf(stderr, "Possibly unhandled promise rejection: "); + js_std_dump_error1(ctx, reason); + } +} + +/* main loop which calls the user JS callbacks */ +void js_std_loop(JSContext *ctx) +{ + JSContext *ctx1; + int err; + + for(;;) { + /* execute the pending jobs */ + for(;;) { + err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); + if (err <= 0) { + if (err < 0) { + js_std_dump_error(ctx1); + } + break; + } + } + + if (!os_poll_func || os_poll_func(ctx)) + break; + } +} + +/* Wait for a promise and execute pending jobs while waiting for + it. Return the promise result or JS_EXCEPTION in case of promise + rejection. */ +JSValue js_std_await(JSContext *ctx, JSValue obj) +{ + JSValue ret; + int state; + + for(;;) { + state = JS_PromiseState(ctx, obj); + if (state == JS_PROMISE_FULFILLED) { + ret = JS_PromiseResult(ctx, obj); + JS_FreeValue(ctx, obj); + break; + } else if (state == JS_PROMISE_REJECTED) { + ret = JS_Throw(ctx, JS_PromiseResult(ctx, obj)); + JS_FreeValue(ctx, obj); + break; + } else if (state == JS_PROMISE_PENDING) { + JSContext *ctx1; + int err; + err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); + if (err < 0) { + js_std_dump_error(ctx1); + } + if (os_poll_func) + os_poll_func(ctx); + } else { + /* not a promise */ + ret = obj; + break; + } + } + return ret; +} + +void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, + int load_only) +{ + JSValue obj, val; + obj = JS_ReadObject(ctx, buf, buf_len, JS_READ_OBJ_BYTECODE); + if (JS_IsException(obj)) + goto exception; + if (load_only) { + if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { + js_module_set_import_meta(ctx, obj, FALSE, FALSE); + } + } else { + if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { + if (JS_ResolveModule(ctx, obj) < 0) { + JS_FreeValue(ctx, obj); + goto exception; + } + js_module_set_import_meta(ctx, obj, FALSE, TRUE); + val = JS_EvalFunction(ctx, obj); + val = js_std_await(ctx, val); + } else { + val = JS_EvalFunction(ctx, obj); + } + if (JS_IsException(val)) { + exception: + js_std_dump_error(ctx); + exit(1); + } + JS_FreeValue(ctx, val); + } +} diff --git a/source/engine/thirdparty/quickjs/quickjs-libc.h b/source/engine/thirdparty/quickjs/quickjs-libc.h new file mode 100644 index 0000000..850484f --- /dev/null +++ b/source/engine/thirdparty/quickjs/quickjs-libc.h @@ -0,0 +1,60 @@ +/* + * QuickJS C library + * + * Copyright (c) 2017-2018 Fabrice Bellard + * + * 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. + */ +#ifndef QUICKJS_LIBC_H +#define QUICKJS_LIBC_H + +#include +#include + +#include "quickjs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name); +JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name); +void js_std_add_helpers(JSContext *ctx, int argc, char **argv); +void js_std_loop(JSContext *ctx); +JSValue js_std_await(JSContext *ctx, JSValue obj); +void js_std_init_handlers(JSRuntime *rt); +void js_std_free_handlers(JSRuntime *rt); +void js_std_dump_error(JSContext *ctx); +uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename); +int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, + JS_BOOL use_realpath, JS_BOOL is_main); +JSModuleDef *js_module_loader(JSContext *ctx, + const char *module_name, void *opaque); +void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, + int flags); +void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, + JSValueConst reason, + JS_BOOL is_handled, void *opaque); +void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)); + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* QUICKJS_LIBC_H */ diff --git a/source/engine/thirdparty/quickjs/quickjs-opcode.h b/source/engine/thirdparty/quickjs/quickjs-opcode.h new file mode 100644 index 0000000..1e18212 --- /dev/null +++ b/source/engine/thirdparty/quickjs/quickjs-opcode.h @@ -0,0 +1,372 @@ +/* + * QuickJS opcode definitions + * + * Copyright (c) 2017-2018 Fabrice Bellard + * Copyright (c) 2017-2018 Charlie Gordon + * + * 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. + */ + +#ifdef FMT +FMT(none) +FMT(none_int) +FMT(none_loc) +FMT(none_arg) +FMT(none_var_ref) +FMT(u8) +FMT(i8) +FMT(loc8) +FMT(const8) +FMT(label8) +FMT(u16) +FMT(i16) +FMT(label16) +FMT(npop) +FMT(npopx) +FMT(npop_u16) +FMT(loc) +FMT(arg) +FMT(var_ref) +FMT(u32) +FMT(i32) +FMT(const) +FMT(label) +FMT(atom) +FMT(atom_u8) +FMT(atom_u16) +FMT(atom_label_u8) +FMT(atom_label_u16) +FMT(label_u16) +#undef FMT +#endif /* FMT */ + +#ifdef DEF + +#ifndef def +#define def(id, size, n_pop, n_push, f) DEF(id, size, n_pop, n_push, f) +#endif + +DEF(invalid, 1, 0, 0, none) /* never emitted */ + +/* push values */ +DEF( push_i32, 5, 0, 1, i32) +DEF( push_const, 5, 0, 1, const) +DEF( fclosure, 5, 0, 1, const) /* must follow push_const */ +DEF(push_atom_value, 5, 0, 1, atom) +DEF( private_symbol, 5, 0, 1, atom) +DEF( undefined, 1, 0, 1, none) +DEF( null, 1, 0, 1, none) +DEF( push_this, 1, 0, 1, none) /* only used at the start of a function */ +DEF( push_false, 1, 0, 1, none) +DEF( push_true, 1, 0, 1, none) +DEF( object, 1, 0, 1, none) +DEF( special_object, 2, 0, 1, u8) /* only used at the start of a function */ +DEF( rest, 3, 0, 1, u16) /* only used at the start of a function */ + +DEF( drop, 1, 1, 0, none) /* a -> */ +DEF( nip, 1, 2, 1, none) /* a b -> b */ +DEF( nip1, 1, 3, 2, none) /* a b c -> b c */ +DEF( dup, 1, 1, 2, none) /* a -> a a */ +DEF( dup1, 1, 2, 3, none) /* a b -> a a b */ +DEF( dup2, 1, 2, 4, none) /* a b -> a b a b */ +DEF( dup3, 1, 3, 6, none) /* a b c -> a b c a b c */ +DEF( insert2, 1, 2, 3, none) /* obj a -> a obj a (dup_x1) */ +DEF( insert3, 1, 3, 4, none) /* obj prop a -> a obj prop a (dup_x2) */ +DEF( insert4, 1, 4, 5, none) /* this obj prop a -> a this obj prop a */ +DEF( perm3, 1, 3, 3, none) /* obj a b -> a obj b */ +DEF( perm4, 1, 4, 4, none) /* obj prop a b -> a obj prop b */ +DEF( perm5, 1, 5, 5, none) /* this obj prop a b -> a this obj prop b */ +DEF( swap, 1, 2, 2, none) /* a b -> b a */ +DEF( swap2, 1, 4, 4, none) /* a b c d -> c d a b */ +DEF( rot3l, 1, 3, 3, none) /* x a b -> a b x */ +DEF( rot3r, 1, 3, 3, none) /* a b x -> x a b */ +DEF( rot4l, 1, 4, 4, none) /* x a b c -> a b c x */ +DEF( rot5l, 1, 5, 5, none) /* x a b c d -> a b c d x */ + +DEF(call_constructor, 3, 2, 1, npop) /* func new.target args -> ret. arguments are not counted in n_pop */ +DEF( call, 3, 1, 1, npop) /* arguments are not counted in n_pop */ +DEF( tail_call, 3, 1, 0, npop) /* arguments are not counted in n_pop */ +DEF( call_method, 3, 2, 1, npop) /* arguments are not counted in n_pop */ +DEF(tail_call_method, 3, 2, 0, npop) /* arguments are not counted in n_pop */ +DEF( array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */ +DEF( apply, 3, 3, 1, u16) +DEF( return, 1, 1, 0, none) +DEF( return_undef, 1, 0, 0, none) +DEF(check_ctor_return, 1, 1, 2, none) +DEF( check_ctor, 1, 0, 0, none) +DEF( check_brand, 1, 2, 2, none) /* this_obj func -> this_obj func */ +DEF( add_brand, 1, 2, 0, none) /* this_obj home_obj -> */ +DEF( return_async, 1, 1, 0, none) +DEF( throw, 1, 1, 0, none) +DEF( throw_error, 6, 0, 0, atom_u8) +DEF( eval, 5, 1, 1, npop_u16) /* func args... -> ret_val */ +DEF( apply_eval, 3, 2, 1, u16) /* func array -> ret_eval */ +DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a + bytecode string */ +DEF( get_super, 1, 1, 1, none) +DEF( import, 1, 1, 1, none) /* dynamic module import */ + +DEF( check_var, 5, 0, 1, atom) /* check if a variable exists */ +DEF( get_var_undef, 5, 0, 1, atom) /* push undefined if the variable does not exist */ +DEF( get_var, 5, 0, 1, atom) /* throw an exception if the variable does not exist */ +DEF( put_var, 5, 1, 0, atom) /* must come after get_var */ +DEF( put_var_init, 5, 1, 0, atom) /* must come after put_var. Used to initialize a global lexical variable */ +DEF( put_var_strict, 5, 2, 0, atom) /* for strict mode variable write */ + +DEF( get_ref_value, 1, 2, 3, none) +DEF( put_ref_value, 1, 3, 0, none) + +DEF( define_var, 6, 0, 0, atom_u8) +DEF(check_define_var, 6, 0, 0, atom_u8) +DEF( define_func, 6, 1, 0, atom_u8) +DEF( get_field, 5, 1, 1, atom) +DEF( get_field2, 5, 1, 2, atom) +DEF( put_field, 5, 2, 0, atom) +DEF( get_private_field, 1, 2, 1, none) /* obj prop -> value */ +DEF( put_private_field, 1, 3, 0, none) /* obj value prop -> */ +DEF(define_private_field, 1, 3, 1, none) /* obj prop value -> obj */ +DEF( get_array_el, 1, 2, 1, none) +DEF( get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */ +DEF( put_array_el, 1, 3, 0, none) +DEF(get_super_value, 1, 3, 1, none) /* this obj prop -> value */ +DEF(put_super_value, 1, 4, 0, none) /* this obj prop value -> */ +DEF( define_field, 5, 2, 1, atom) +DEF( set_name, 5, 1, 1, atom) +DEF(set_name_computed, 1, 2, 2, none) +DEF( set_proto, 1, 2, 1, none) +DEF(set_home_object, 1, 2, 2, none) +DEF(define_array_el, 1, 3, 2, none) +DEF( append, 1, 3, 2, none) /* append enumerated object, update length */ +DEF(copy_data_properties, 2, 3, 3, u8) +DEF( define_method, 6, 2, 1, atom_u8) +DEF(define_method_computed, 2, 3, 1, u8) /* must come after define_method */ +DEF( define_class, 6, 2, 2, atom_u8) /* parent ctor -> ctor proto */ +DEF( define_class_computed, 6, 3, 3, atom_u8) /* field_name parent ctor -> field_name ctor proto (class with computed name) */ + +DEF( get_loc, 3, 0, 1, loc) +DEF( put_loc, 3, 1, 0, loc) /* must come after get_loc */ +DEF( set_loc, 3, 1, 1, loc) /* must come after put_loc */ +DEF( get_arg, 3, 0, 1, arg) +DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */ +DEF( set_arg, 3, 1, 1, arg) /* must come after put_arg */ +DEF( get_var_ref, 3, 0, 1, var_ref) +DEF( put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */ +DEF( set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */ +DEF(set_loc_uninitialized, 3, 0, 0, loc) +DEF( get_loc_check, 3, 0, 1, loc) +DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */ +DEF( put_loc_check_init, 3, 1, 0, loc) +DEF(get_loc_checkthis, 3, 0, 1, loc) +DEF(get_var_ref_check, 3, 0, 1, var_ref) +DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */ +DEF(put_var_ref_check_init, 3, 1, 0, var_ref) +DEF( close_loc, 3, 0, 0, loc) +DEF( if_false, 5, 1, 0, label) +DEF( if_true, 5, 1, 0, label) /* must come after if_false */ +DEF( goto, 5, 0, 0, label) /* must come after if_true */ +DEF( catch, 5, 0, 1, label) +DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */ +DEF( ret, 1, 1, 0, none) /* used to return from the finally block */ +DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */ + +DEF( to_object, 1, 1, 1, none) +//DEF( to_string, 1, 1, 1, none) +DEF( to_propkey, 1, 1, 1, none) +DEF( to_propkey2, 1, 2, 2, none) + +DEF( with_get_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_put_var, 10, 2, 1, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF(with_delete_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_make_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_get_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF(with_get_ref_undef, 10, 1, 0, atom_label_u8) + +DEF( make_loc_ref, 7, 0, 2, atom_u16) +DEF( make_arg_ref, 7, 0, 2, atom_u16) +DEF(make_var_ref_ref, 7, 0, 2, atom_u16) +DEF( make_var_ref, 5, 0, 2, atom) + +DEF( for_in_start, 1, 1, 1, none) +DEF( for_of_start, 1, 1, 3, none) +DEF(for_await_of_start, 1, 1, 3, none) +DEF( for_in_next, 1, 1, 3, none) +DEF( for_of_next, 2, 3, 5, u8) +DEF(iterator_check_object, 1, 1, 1, none) +DEF(iterator_get_value_done, 1, 1, 2, none) +DEF( iterator_close, 1, 3, 0, none) +DEF( iterator_next, 1, 4, 4, none) +DEF( iterator_call, 2, 4, 5, u8) +DEF( initial_yield, 1, 0, 0, none) +DEF( yield, 1, 1, 2, none) +DEF( yield_star, 1, 1, 2, none) +DEF(async_yield_star, 1, 1, 2, none) +DEF( await, 1, 1, 1, none) + +/* arithmetic/logic operations */ +DEF( neg, 1, 1, 1, none) +DEF( plus, 1, 1, 1, none) +DEF( dec, 1, 1, 1, none) +DEF( inc, 1, 1, 1, none) +DEF( post_dec, 1, 1, 2, none) +DEF( post_inc, 1, 1, 2, none) +DEF( dec_loc, 2, 0, 0, loc8) +DEF( inc_loc, 2, 0, 0, loc8) +DEF( add_loc, 2, 1, 0, loc8) +DEF( not, 1, 1, 1, none) +DEF( lnot, 1, 1, 1, none) +DEF( typeof, 1, 1, 1, none) +DEF( delete, 1, 2, 1, none) +DEF( delete_var, 5, 0, 1, atom) + +DEF( mul, 1, 2, 1, none) +DEF( div, 1, 2, 1, none) +DEF( mod, 1, 2, 1, none) +DEF( add, 1, 2, 1, none) +DEF( sub, 1, 2, 1, none) +DEF( pow, 1, 2, 1, none) +DEF( shl, 1, 2, 1, none) +DEF( sar, 1, 2, 1, none) +DEF( shr, 1, 2, 1, none) +DEF( lt, 1, 2, 1, none) +DEF( lte, 1, 2, 1, none) +DEF( gt, 1, 2, 1, none) +DEF( gte, 1, 2, 1, none) +DEF( instanceof, 1, 2, 1, none) +DEF( in, 1, 2, 1, none) +DEF( eq, 1, 2, 1, none) +DEF( neq, 1, 2, 1, none) +DEF( strict_eq, 1, 2, 1, none) +DEF( strict_neq, 1, 2, 1, none) +DEF( and, 1, 2, 1, none) +DEF( xor, 1, 2, 1, none) +DEF( or, 1, 2, 1, none) +DEF(is_undefined_or_null, 1, 1, 1, none) +DEF( private_in, 1, 2, 1, none) +#ifdef CONFIG_BIGNUM +DEF( mul_pow10, 1, 2, 1, none) +DEF( math_mod, 1, 2, 1, none) +#endif +/* must be the last non short and non temporary opcode */ +DEF( nop, 1, 0, 0, none) + +/* temporary opcodes: never emitted in the final bytecode */ + +def( enter_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ +def( leave_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ + +def( label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 */ + +/* the following opcodes must be in the same order as the 'with_x' and + get_var_undef, get_var and put_var opcodes */ +def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_delete_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_get_var_checkthis, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2, only used to return 'this' in derived class constructors */ +def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */ +def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */ +def(scope_put_private_field, 7, 2, 0, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */ +def(scope_in_private_field, 7, 1, 1, atom_u16) /* obj -> res emitted in phase 1, removed in phase 2 */ +def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */ +def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */ +def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */ + +def( line_num, 5, 0, 0, u32) /* emitted in phase 1, removed in phase 3 */ + +#if SHORT_OPCODES +DEF( push_minus1, 1, 0, 1, none_int) +DEF( push_0, 1, 0, 1, none_int) +DEF( push_1, 1, 0, 1, none_int) +DEF( push_2, 1, 0, 1, none_int) +DEF( push_3, 1, 0, 1, none_int) +DEF( push_4, 1, 0, 1, none_int) +DEF( push_5, 1, 0, 1, none_int) +DEF( push_6, 1, 0, 1, none_int) +DEF( push_7, 1, 0, 1, none_int) +DEF( push_i8, 2, 0, 1, i8) +DEF( push_i16, 3, 0, 1, i16) +DEF( push_const8, 2, 0, 1, const8) +DEF( fclosure8, 2, 0, 1, const8) /* must follow push_const8 */ +DEF(push_empty_string, 1, 0, 1, none) + +DEF( get_loc8, 2, 0, 1, loc8) +DEF( put_loc8, 2, 1, 0, loc8) +DEF( set_loc8, 2, 1, 1, loc8) + +DEF( get_loc0, 1, 0, 1, none_loc) +DEF( get_loc1, 1, 0, 1, none_loc) +DEF( get_loc2, 1, 0, 1, none_loc) +DEF( get_loc3, 1, 0, 1, none_loc) +DEF( put_loc0, 1, 1, 0, none_loc) +DEF( put_loc1, 1, 1, 0, none_loc) +DEF( put_loc2, 1, 1, 0, none_loc) +DEF( put_loc3, 1, 1, 0, none_loc) +DEF( set_loc0, 1, 1, 1, none_loc) +DEF( set_loc1, 1, 1, 1, none_loc) +DEF( set_loc2, 1, 1, 1, none_loc) +DEF( set_loc3, 1, 1, 1, none_loc) +DEF( get_arg0, 1, 0, 1, none_arg) +DEF( get_arg1, 1, 0, 1, none_arg) +DEF( get_arg2, 1, 0, 1, none_arg) +DEF( get_arg3, 1, 0, 1, none_arg) +DEF( put_arg0, 1, 1, 0, none_arg) +DEF( put_arg1, 1, 1, 0, none_arg) +DEF( put_arg2, 1, 1, 0, none_arg) +DEF( put_arg3, 1, 1, 0, none_arg) +DEF( set_arg0, 1, 1, 1, none_arg) +DEF( set_arg1, 1, 1, 1, none_arg) +DEF( set_arg2, 1, 1, 1, none_arg) +DEF( set_arg3, 1, 1, 1, none_arg) +DEF( get_var_ref0, 1, 0, 1, none_var_ref) +DEF( get_var_ref1, 1, 0, 1, none_var_ref) +DEF( get_var_ref2, 1, 0, 1, none_var_ref) +DEF( get_var_ref3, 1, 0, 1, none_var_ref) +DEF( put_var_ref0, 1, 1, 0, none_var_ref) +DEF( put_var_ref1, 1, 1, 0, none_var_ref) +DEF( put_var_ref2, 1, 1, 0, none_var_ref) +DEF( put_var_ref3, 1, 1, 0, none_var_ref) +DEF( set_var_ref0, 1, 1, 1, none_var_ref) +DEF( set_var_ref1, 1, 1, 1, none_var_ref) +DEF( set_var_ref2, 1, 1, 1, none_var_ref) +DEF( set_var_ref3, 1, 1, 1, none_var_ref) + +DEF( get_length, 1, 1, 1, none) + +DEF( if_false8, 2, 1, 0, label8) +DEF( if_true8, 2, 1, 0, label8) /* must come after if_false8 */ +DEF( goto8, 2, 0, 0, label8) /* must come after if_true8 */ +DEF( goto16, 3, 0, 0, label16) + +DEF( call0, 1, 1, 1, npopx) +DEF( call1, 1, 1, 1, npopx) +DEF( call2, 1, 1, 1, npopx) +DEF( call3, 1, 1, 1, npopx) + +DEF( is_undefined, 1, 1, 1, none) +DEF( is_null, 1, 1, 1, none) +DEF(typeof_is_undefined, 1, 1, 1, none) +DEF( typeof_is_function, 1, 1, 1, none) +#endif + +#undef DEF +#undef def +#endif /* DEF */ diff --git a/source/engine/thirdparty/quickjs/quickjs.c b/source/engine/thirdparty/quickjs/quickjs.c new file mode 100644 index 0000000..ebf45a9 --- /dev/null +++ b/source/engine/thirdparty/quickjs/quickjs.c @@ -0,0 +1,55869 @@ +/* + * QuickJS Javascript Engine + * + * Copyright (c) 2017-2021 Fabrice Bellard + * Copyright (c) 2017-2021 Charlie Gordon + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__APPLE__) +#include +#elif defined(__linux__) +#include +#elif defined(__FreeBSD__) +#include +#endif + +#include "cutils.h" +#include "list.h" +#include "quickjs.h" +#include "libregexp.h" +#include "libbf.h" + +#define OPTIMIZE 1 +#define SHORT_OPCODES 1 +#if defined(EMSCRIPTEN) +#define DIRECT_DISPATCH 0 +#else +#define DIRECT_DISPATCH 1 +#endif + +#if defined(__APPLE__) +#define MALLOC_OVERHEAD 0 +#else +#define MALLOC_OVERHEAD 8 +#endif + +#if !defined(_WIN32) +/* define it if printf uses the RNDN rounding mode instead of RNDNA */ +#define CONFIG_PRINTF_RNDN +#endif + +/* define to include Atomics.* operations which depend on the OS + threads */ +#if !defined(EMSCRIPTEN) +#define CONFIG_ATOMICS +#endif + +#if !defined(EMSCRIPTEN) +/* enable stack limitation */ +#define CONFIG_STACK_CHECK +#endif + + +/* dump object free */ +//#define DUMP_FREE +//#define DUMP_CLOSURE +/* dump the bytecode of the compiled functions: combination of bits + 1: dump pass 3 final byte code + 2: dump pass 2 code + 4: dump pass 1 code + 8: dump stdlib functions + 16: dump bytecode in hex + 32: dump line number table + 64: dump compute_stack_size + */ +//#define DUMP_BYTECODE (1) +/* dump the occurence of the automatic GC */ +//#define DUMP_GC +/* dump objects freed by the garbage collector */ +//#define DUMP_GC_FREE +/* dump objects leaking when freeing the runtime */ +//#define DUMP_LEAKS 1 +/* dump memory usage before running the garbage collector */ +//#define DUMP_MEM +//#define DUMP_OBJECTS /* dump objects in JS_FreeContext */ +//#define DUMP_ATOMS /* dump atoms in JS_FreeContext */ +//#define DUMP_SHAPES /* dump shapes in JS_FreeContext */ +//#define DUMP_MODULE_RESOLVE +//#define DUMP_PROMISE +//#define DUMP_READ_OBJECT + +/* test the GC by forcing it before each object allocation */ +//#define FORCE_GC_AT_MALLOC + +#ifdef CONFIG_ATOMICS +#include +#include +#include +#endif + +enum { + /* classid tag */ /* union usage | properties */ + JS_CLASS_OBJECT = 1, /* must be first */ + JS_CLASS_ARRAY, /* u.array | length */ + JS_CLASS_ERROR, + JS_CLASS_NUMBER, /* u.object_data */ + JS_CLASS_STRING, /* u.object_data */ + JS_CLASS_BOOLEAN, /* u.object_data */ + JS_CLASS_SYMBOL, /* u.object_data */ + JS_CLASS_ARGUMENTS, /* u.array | length */ + JS_CLASS_MAPPED_ARGUMENTS, /* | length */ + JS_CLASS_DATE, /* u.object_data */ + JS_CLASS_MODULE_NS, + JS_CLASS_C_FUNCTION, /* u.cfunc */ + JS_CLASS_BYTECODE_FUNCTION, /* u.func */ + JS_CLASS_BOUND_FUNCTION, /* u.bound_function */ + JS_CLASS_C_FUNCTION_DATA, /* u.c_function_data_record */ + JS_CLASS_GENERATOR_FUNCTION, /* u.func */ + JS_CLASS_FOR_IN_ITERATOR, /* u.for_in_iterator */ + JS_CLASS_REGEXP, /* u.regexp */ + JS_CLASS_ARRAY_BUFFER, /* u.array_buffer */ + JS_CLASS_SHARED_ARRAY_BUFFER, /* u.array_buffer */ + JS_CLASS_UINT8C_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT8_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT8_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_DATAVIEW, /* u.typed_array */ + JS_CLASS_BIG_INT, /* u.object_data */ +#ifdef CONFIG_BIGNUM + JS_CLASS_BIG_FLOAT, /* u.object_data */ + JS_CLASS_FLOAT_ENV, /* u.float_env */ + JS_CLASS_BIG_DECIMAL, /* u.object_data */ + JS_CLASS_OPERATOR_SET, /* u.operator_set */ +#endif + JS_CLASS_MAP, /* u.map_state */ + JS_CLASS_SET, /* u.map_state */ + JS_CLASS_WEAKMAP, /* u.map_state */ + JS_CLASS_WEAKSET, /* u.map_state */ + JS_CLASS_MAP_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_SET_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_ARRAY_ITERATOR, /* u.array_iterator_data */ + JS_CLASS_STRING_ITERATOR, /* u.array_iterator_data */ + JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */ + JS_CLASS_GENERATOR, /* u.generator_data */ + JS_CLASS_PROXY, /* u.proxy_data */ + JS_CLASS_PROMISE, /* u.promise_data */ + JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */ + JS_CLASS_PROMISE_REJECT_FUNCTION, /* u.promise_function_data */ + JS_CLASS_ASYNC_FUNCTION, /* u.func */ + JS_CLASS_ASYNC_FUNCTION_RESOLVE, /* u.async_function_data */ + JS_CLASS_ASYNC_FUNCTION_REJECT, /* u.async_function_data */ + JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */ + JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */ + JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */ + + JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ +}; + +/* number of typed array types */ +#define JS_TYPED_ARRAY_COUNT (JS_CLASS_FLOAT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1) +static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT]; +#define typed_array_size_log2(classid) (typed_array_size_log2[(classid)- JS_CLASS_UINT8C_ARRAY]) + +typedef enum JSErrorEnum { + JS_EVAL_ERROR, + JS_RANGE_ERROR, + JS_REFERENCE_ERROR, + JS_SYNTAX_ERROR, + JS_TYPE_ERROR, + JS_URI_ERROR, + JS_INTERNAL_ERROR, + JS_AGGREGATE_ERROR, + + JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ +} JSErrorEnum; + +#define JS_MAX_LOCAL_VARS 65535 +#define JS_STACK_SIZE_MAX 65534 +#define JS_STRING_LEN_MAX ((1 << 30) - 1) + +#define __exception __attribute__((warn_unused_result)) + +typedef struct JSShape JSShape; +typedef struct JSString JSString; +typedef struct JSString JSAtomStruct; + +typedef enum { + JS_GC_PHASE_NONE, + JS_GC_PHASE_DECREF, + JS_GC_PHASE_REMOVE_CYCLES, +} JSGCPhaseEnum; + +typedef enum OPCodeEnum OPCodeEnum; + +/* function pointers are used for numeric operations so that it is + possible to remove some numeric types */ +typedef struct { + JSValue (*to_string)(JSContext *ctx, JSValueConst val); + JSValue (*from_string)(JSContext *ctx, const char *buf, + int radix, int flags, slimb_t *pexponent); + int (*unary_arith)(JSContext *ctx, + JSValue *pres, OPCodeEnum op, JSValue op1); + int (*binary_arith)(JSContext *ctx, OPCodeEnum op, + JSValue *pres, JSValue op1, JSValue op2); + int (*compare)(JSContext *ctx, OPCodeEnum op, + JSValue op1, JSValue op2); + /* only for bigfloat: */ + JSValue (*mul_pow10_to_float64)(JSContext *ctx, const bf_t *a, + int64_t exponent); + int (*mul_pow10)(JSContext *ctx, JSValue *sp); +} JSNumericOperations; + +struct JSRuntime { + JSMallocFunctions mf; + JSMallocState malloc_state; + const char *rt_info; + + int atom_hash_size; /* power of two */ + int atom_count; + int atom_size; + int atom_count_resize; /* resize hash table at this count */ + uint32_t *atom_hash; + JSAtomStruct **atom_array; + int atom_free_index; /* 0 = none */ + + int class_count; /* size of class_array */ + JSClass *class_array; + + struct list_head context_list; /* list of JSContext.link */ + /* list of JSGCObjectHeader.link. List of allocated GC objects (used + by the garbage collector) */ + struct list_head gc_obj_list; + /* list of JSGCObjectHeader.link. Used during JS_FreeValueRT() */ + struct list_head gc_zero_ref_count_list; + struct list_head tmp_obj_list; /* used during GC */ + JSGCPhaseEnum gc_phase : 8; + size_t malloc_gc_threshold; +#ifdef DUMP_LEAKS + struct list_head string_list; /* list of JSString.link */ +#endif + /* stack limitation */ + uintptr_t stack_size; /* in bytes, 0 if no limit */ + uintptr_t stack_top; + uintptr_t stack_limit; /* lower stack limit */ + + JSValue current_exception; + /* true if inside an out of memory error, to avoid recursing */ + BOOL in_out_of_memory : 8; + + struct JSStackFrame *current_stack_frame; + + JSInterruptHandler *interrupt_handler; + void *interrupt_opaque; + + JSHostPromiseRejectionTracker *host_promise_rejection_tracker; + void *host_promise_rejection_tracker_opaque; + + struct list_head job_list; /* list of JSJobEntry.link */ + + JSModuleNormalizeFunc *module_normalize_func; + JSModuleLoaderFunc *module_loader_func; + void *module_loader_opaque; + /* timestamp for internal use in module evaluation */ + int64_t module_async_evaluation_next_timestamp; + + BOOL can_block : 8; /* TRUE if Atomics.wait can block */ + /* used to allocate, free and clone SharedArrayBuffers */ + JSSharedArrayBufferFunctions sab_funcs; + + /* Shape hash table */ + int shape_hash_bits; + int shape_hash_size; + int shape_hash_count; /* number of hashed shapes */ + JSShape **shape_hash; + bf_context_t bf_ctx; + JSNumericOperations bigint_ops; +#ifdef CONFIG_BIGNUM + JSNumericOperations bigfloat_ops; + JSNumericOperations bigdecimal_ops; + uint32_t operator_count; +#endif + void *user_opaque; +}; + +struct JSClass { + uint32_t class_id; /* 0 means free entry */ + JSAtom class_name; + JSClassFinalizer *finalizer; + JSClassGCMark *gc_mark; + JSClassCall *call; + /* pointers for exotic behavior, can be NULL if none are present */ + const JSClassExoticMethods *exotic; +}; + +#define JS_MODE_STRICT (1 << 0) +#define JS_MODE_STRIP (1 << 1) +#define JS_MODE_MATH (1 << 2) +#define JS_MODE_ASYNC (1 << 3) /* async function */ + +typedef struct JSStackFrame { + struct JSStackFrame *prev_frame; /* NULL if first stack frame */ + JSValue cur_func; /* current function, JS_UNDEFINED if the frame is detached */ + JSValue *arg_buf; /* arguments */ + JSValue *var_buf; /* variables */ + struct list_head var_ref_list; /* list of JSVarRef.var_ref_link */ + const uint8_t *cur_pc; /* only used in bytecode functions : PC of the + instruction after the call */ + int arg_count; + int js_mode; /* for C functions, only JS_MODE_MATH may be set */ + /* only used in generators. Current stack pointer value. NULL if + the function is running. */ + JSValue *cur_sp; +} JSStackFrame; + +typedef enum { + JS_GC_OBJ_TYPE_JS_OBJECT, + JS_GC_OBJ_TYPE_FUNCTION_BYTECODE, + JS_GC_OBJ_TYPE_SHAPE, + JS_GC_OBJ_TYPE_VAR_REF, + JS_GC_OBJ_TYPE_ASYNC_FUNCTION, + JS_GC_OBJ_TYPE_JS_CONTEXT, +} JSGCObjectTypeEnum; + +/* header for GC objects. GC objects are C data structures with a + reference count that can reference other GC objects. JS Objects are + a particular type of GC object. */ +struct JSGCObjectHeader { + int ref_count; /* must come first, 32-bit */ + JSGCObjectTypeEnum gc_obj_type : 4; + uint8_t mark : 4; /* used by the GC */ + uint8_t dummy1; /* not used by the GC */ + uint16_t dummy2; /* not used by the GC */ + struct list_head link; +}; + +typedef struct JSVarRef { + union { + JSGCObjectHeader header; /* must come first */ + struct { + int __gc_ref_count; /* corresponds to header.ref_count */ + uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ + uint8_t is_detached : 1; + uint8_t is_arg : 1; + uint16_t var_idx; /* index of the corresponding function variable on + the stack */ + }; + }; + JSValue *pvalue; /* pointer to the value, either on the stack or + to 'value' */ + union { + JSValue value; /* used when is_detached = TRUE */ + struct { + struct list_head var_ref_link; /* JSStackFrame.var_ref_list list */ + struct JSAsyncFunctionState *async_func; /* != NULL if async stack frame */ + }; /* used when is_detached = FALSE */ + }; +} JSVarRef; + +/* the same structure is used for big integers and big floats. Big + integers are never infinite or NaNs */ +typedef struct JSBigFloat { + JSRefCountHeader header; /* must come first, 32-bit */ + bf_t num; +} JSBigFloat; + +#ifdef CONFIG_BIGNUM +typedef struct JSFloatEnv { + limb_t prec; + bf_flags_t flags; + unsigned int status; +} JSFloatEnv; + +typedef struct JSBigDecimal { + JSRefCountHeader header; /* must come first, 32-bit */ + bfdec_t num; +} JSBigDecimal; +#endif + +typedef enum { + JS_AUTOINIT_ID_PROTOTYPE, + JS_AUTOINIT_ID_MODULE_NS, + JS_AUTOINIT_ID_PROP, +} JSAutoInitIDEnum; + +/* must be large enough to have a negligible runtime cost and small + enough to call the interrupt callback often. */ +#define JS_INTERRUPT_COUNTER_INIT 10000 + +struct JSContext { + JSGCObjectHeader header; /* must come first */ + JSRuntime *rt; + struct list_head link; + + uint16_t binary_object_count; + int binary_object_size; + + JSShape *array_shape; /* initial shape for Array objects */ + + JSValue *class_proto; + JSValue function_proto; + JSValue function_ctor; + JSValue array_ctor; + JSValue regexp_ctor; + JSValue promise_ctor; + JSValue native_error_proto[JS_NATIVE_ERROR_COUNT]; + JSValue iterator_proto; + JSValue async_iterator_proto; + JSValue array_proto_values; + JSValue throw_type_error; + JSValue eval_obj; + + JSValue global_obj; /* global object */ + JSValue global_var_obj; /* contains the global let/const definitions */ + + uint64_t random_state; + bf_context_t *bf_ctx; /* points to rt->bf_ctx, shared by all contexts */ +#ifdef CONFIG_BIGNUM + JSFloatEnv fp_env; /* global FP environment */ + BOOL bignum_ext : 8; /* enable math mode */ + BOOL allow_operator_overloading : 8; +#endif + /* when the counter reaches zero, JSRutime.interrupt_handler is called */ + int interrupt_counter; + + struct list_head loaded_modules; /* list of JSModuleDef.link */ + + /* if NULL, RegExp compilation is not supported */ + JSValue (*compile_regexp)(JSContext *ctx, JSValueConst pattern, + JSValueConst flags); + /* if NULL, eval is not supported */ + JSValue (*eval_internal)(JSContext *ctx, JSValueConst this_obj, + const char *input, size_t input_len, + const char *filename, int flags, int scope_idx); + void *user_opaque; +}; + +typedef union JSFloat64Union { + double d; + uint64_t u64; + uint32_t u32[2]; +} JSFloat64Union; + +enum { + JS_ATOM_TYPE_STRING = 1, + JS_ATOM_TYPE_GLOBAL_SYMBOL, + JS_ATOM_TYPE_SYMBOL, + JS_ATOM_TYPE_PRIVATE, +}; + +enum { + JS_ATOM_HASH_SYMBOL, + JS_ATOM_HASH_PRIVATE, +}; + +typedef enum { + JS_ATOM_KIND_STRING, + JS_ATOM_KIND_SYMBOL, + JS_ATOM_KIND_PRIVATE, +} JSAtomKindEnum; + +#define JS_ATOM_HASH_MASK ((1 << 30) - 1) + +struct JSString { + JSRefCountHeader header; /* must come first, 32-bit */ + uint32_t len : 31; + uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ + /* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3, + for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3 + XXX: could change encoding to have one more bit in hash */ + uint32_t hash : 30; + uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */ + uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */ +#ifdef DUMP_LEAKS + struct list_head link; /* string list */ +#endif + union { + uint8_t str8[0]; /* 8 bit strings will get an extra null terminator */ + uint16_t str16[0]; + } u; +}; + +typedef struct JSClosureVar { + uint8_t is_local : 1; + uint8_t is_arg : 1; + uint8_t is_const : 1; + uint8_t is_lexical : 1; + uint8_t var_kind : 4; /* see JSVarKindEnum */ + /* 8 bits available */ + uint16_t var_idx; /* is_local = TRUE: index to a normal variable of the + parent function. otherwise: index to a closure + variable of the parent function */ + JSAtom var_name; +} JSClosureVar; + +#define ARG_SCOPE_INDEX 1 +#define ARG_SCOPE_END (-2) + +typedef struct JSVarScope { + int parent; /* index into fd->scopes of the enclosing scope */ + int first; /* index into fd->vars of the last variable in this scope */ +} JSVarScope; + +typedef enum { + /* XXX: add more variable kinds here instead of using bit fields */ + JS_VAR_NORMAL, + JS_VAR_FUNCTION_DECL, /* lexical var with function declaration */ + JS_VAR_NEW_FUNCTION_DECL, /* lexical var with async/generator + function declaration */ + JS_VAR_CATCH, + JS_VAR_FUNCTION_NAME, /* function expression name */ + JS_VAR_PRIVATE_FIELD, + JS_VAR_PRIVATE_METHOD, + JS_VAR_PRIVATE_GETTER, + JS_VAR_PRIVATE_SETTER, /* must come after JS_VAR_PRIVATE_GETTER */ + JS_VAR_PRIVATE_GETTER_SETTER, /* must come after JS_VAR_PRIVATE_SETTER */ +} JSVarKindEnum; + +/* XXX: could use a different structure in bytecode functions to save + memory */ +typedef struct JSVarDef { + JSAtom var_name; + /* index into fd->scopes of this variable lexical scope */ + int scope_level; + /* during compilation: + - if scope_level = 0: scope in which the variable is defined + - if scope_level != 0: index into fd->vars of the next + variable in the same or enclosing lexical scope + in a bytecode function: + index into fd->vars of the next + variable in the same or enclosing lexical scope + */ + int scope_next; + uint8_t is_const : 1; + uint8_t is_lexical : 1; + uint8_t is_captured : 1; + uint8_t is_static_private : 1; /* only used during private class field parsing */ + uint8_t var_kind : 4; /* see JSVarKindEnum */ + /* only used during compilation: function pool index for lexical + variables with var_kind = + JS_VAR_FUNCTION_DECL/JS_VAR_NEW_FUNCTION_DECL or scope level of + the definition of the 'var' variables (they have scope_level = + 0) */ + int func_pool_idx : 24; /* only used during compilation : index in + the constant pool for hoisted function + definition */ +} JSVarDef; + +/* for the encoding of the pc2line table */ +#define PC2LINE_BASE (-1) +#define PC2LINE_RANGE 5 +#define PC2LINE_OP_FIRST 1 +#define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE) + +typedef enum JSFunctionKindEnum { + JS_FUNC_NORMAL = 0, + JS_FUNC_GENERATOR = (1 << 0), + JS_FUNC_ASYNC = (1 << 1), + JS_FUNC_ASYNC_GENERATOR = (JS_FUNC_GENERATOR | JS_FUNC_ASYNC), +} JSFunctionKindEnum; + +typedef struct JSFunctionBytecode { + JSGCObjectHeader header; /* must come first */ + uint8_t js_mode; + uint8_t has_prototype : 1; /* true if a prototype field is necessary */ + uint8_t has_simple_parameter_list : 1; + uint8_t is_derived_class_constructor : 1; + /* true if home_object needs to be initialized */ + uint8_t need_home_object : 1; + uint8_t func_kind : 2; + uint8_t new_target_allowed : 1; + uint8_t super_call_allowed : 1; + uint8_t super_allowed : 1; + uint8_t arguments_allowed : 1; + uint8_t has_debug : 1; + uint8_t backtrace_barrier : 1; /* stop backtrace on this function */ + uint8_t read_only_bytecode : 1; + uint8_t is_direct_or_indirect_eval : 1; /* used by JS_GetScriptOrModuleName() */ + /* XXX: 10 bits available */ + uint8_t *byte_code_buf; /* (self pointer) */ + int byte_code_len; + JSAtom func_name; + JSVarDef *vardefs; /* arguments + local variables (arg_count + var_count) (self pointer) */ + JSClosureVar *closure_var; /* list of variables in the closure (self pointer) */ + uint16_t arg_count; + uint16_t var_count; + uint16_t defined_arg_count; /* for length function property */ + uint16_t stack_size; /* maximum stack size */ + JSContext *realm; /* function realm */ + JSValue *cpool; /* constant pool (self pointer) */ + int cpool_count; + int closure_var_count; + struct { + /* debug info, move to separate structure to save memory? */ + JSAtom filename; + int line_num; + int source_len; + int pc2line_len; + uint8_t *pc2line_buf; + char *source; + } debug; +} JSFunctionBytecode; + +typedef struct JSBoundFunction { + JSValue func_obj; + JSValue this_val; + int argc; + JSValue argv[0]; +} JSBoundFunction; + +typedef enum JSIteratorKindEnum { + JS_ITERATOR_KIND_KEY, + JS_ITERATOR_KIND_VALUE, + JS_ITERATOR_KIND_KEY_AND_VALUE, +} JSIteratorKindEnum; + +typedef struct JSForInIterator { + JSValue obj; + uint32_t idx; + uint32_t atom_count; + uint8_t in_prototype_chain; + uint8_t is_array; + JSPropertyEnum *tab_atom; /* is_array = FALSE */ +} JSForInIterator; + +typedef struct JSRegExp { + JSString *pattern; + JSString *bytecode; /* also contains the flags */ +} JSRegExp; + +typedef struct JSProxyData { + JSValue target; + JSValue handler; + uint8_t is_func; + uint8_t is_revoked; +} JSProxyData; + +typedef struct JSArrayBuffer { + int byte_length; /* 0 if detached */ + uint8_t detached; + uint8_t shared; /* if shared, the array buffer cannot be detached */ + uint8_t *data; /* NULL if detached */ + struct list_head array_list; + void *opaque; + JSFreeArrayBufferDataFunc *free_func; +} JSArrayBuffer; + +typedef struct JSTypedArray { + struct list_head link; /* link to arraybuffer */ + JSObject *obj; /* back pointer to the TypedArray/DataView object */ + JSObject *buffer; /* based array buffer */ + uint32_t offset; /* offset in the array buffer */ + uint32_t length; /* length in the array buffer */ +} JSTypedArray; + +typedef struct JSAsyncFunctionState { + JSGCObjectHeader header; + JSValue this_val; /* 'this' argument */ + int argc; /* number of function arguments */ + BOOL throw_flag; /* used to throw an exception in JS_CallInternal() */ + BOOL is_completed; /* TRUE if the function has returned. The stack + frame is no longer valid */ + JSValue resolving_funcs[2]; /* only used in JS async functions */ + JSStackFrame frame; +} JSAsyncFunctionState; + +typedef enum { + /* binary operators */ + JS_OVOP_ADD, + JS_OVOP_SUB, + JS_OVOP_MUL, + JS_OVOP_DIV, + JS_OVOP_MOD, + JS_OVOP_POW, + JS_OVOP_OR, + JS_OVOP_AND, + JS_OVOP_XOR, + JS_OVOP_SHL, + JS_OVOP_SAR, + JS_OVOP_SHR, + JS_OVOP_EQ, + JS_OVOP_LESS, + + JS_OVOP_BINARY_COUNT, + /* unary operators */ + JS_OVOP_POS = JS_OVOP_BINARY_COUNT, + JS_OVOP_NEG, + JS_OVOP_INC, + JS_OVOP_DEC, + JS_OVOP_NOT, + + JS_OVOP_COUNT, +} JSOverloadableOperatorEnum; + +typedef struct { + uint32_t operator_index; + JSObject *ops[JS_OVOP_BINARY_COUNT]; /* self operators */ +} JSBinaryOperatorDefEntry; + +typedef struct { + int count; + JSBinaryOperatorDefEntry *tab; +} JSBinaryOperatorDef; + +typedef struct { + uint32_t operator_counter; + BOOL is_primitive; /* OperatorSet for a primitive type */ + /* NULL if no operator is defined */ + JSObject *self_ops[JS_OVOP_COUNT]; /* self operators */ + JSBinaryOperatorDef left; + JSBinaryOperatorDef right; +} JSOperatorSetData; + +typedef struct JSReqModuleEntry { + JSAtom module_name; + JSModuleDef *module; /* used using resolution */ +} JSReqModuleEntry; + +typedef enum JSExportTypeEnum { + JS_EXPORT_TYPE_LOCAL, + JS_EXPORT_TYPE_INDIRECT, +} JSExportTypeEnum; + +typedef struct JSExportEntry { + union { + struct { + int var_idx; /* closure variable index */ + JSVarRef *var_ref; /* if != NULL, reference to the variable */ + } local; /* for local export */ + int req_module_idx; /* module for indirect export */ + } u; + JSExportTypeEnum export_type; + JSAtom local_name; /* '*' if export ns from. not used for local + export after compilation */ + JSAtom export_name; /* exported variable name */ +} JSExportEntry; + +typedef struct JSStarExportEntry { + int req_module_idx; /* in req_module_entries */ +} JSStarExportEntry; + +typedef struct JSImportEntry { + int var_idx; /* closure variable index */ + JSAtom import_name; + int req_module_idx; /* in req_module_entries */ +} JSImportEntry; + +typedef enum { + JS_MODULE_STATUS_UNLINKED, + JS_MODULE_STATUS_LINKING, + JS_MODULE_STATUS_LINKED, + JS_MODULE_STATUS_EVALUATING, + JS_MODULE_STATUS_EVALUATING_ASYNC, + JS_MODULE_STATUS_EVALUATED, +} JSModuleStatus; + +struct JSModuleDef { + JSRefCountHeader header; /* must come first, 32-bit */ + JSAtom module_name; + struct list_head link; + + JSReqModuleEntry *req_module_entries; + int req_module_entries_count; + int req_module_entries_size; + + JSExportEntry *export_entries; + int export_entries_count; + int export_entries_size; + + JSStarExportEntry *star_export_entries; + int star_export_entries_count; + int star_export_entries_size; + + JSImportEntry *import_entries; + int import_entries_count; + int import_entries_size; + + JSValue module_ns; + JSValue func_obj; /* only used for JS modules */ + JSModuleInitFunc *init_func; /* only used for C modules */ + BOOL has_tla : 8; /* true if func_obj contains await */ + BOOL resolved : 8; + BOOL func_created : 8; + JSModuleStatus status : 8; + /* temp use during js_module_link() & js_module_evaluate() */ + int dfs_index, dfs_ancestor_index; + JSModuleDef *stack_prev; + /* temp use during js_module_evaluate() */ + JSModuleDef **async_parent_modules; + int async_parent_modules_count; + int async_parent_modules_size; + int pending_async_dependencies; + BOOL async_evaluation; + int64_t async_evaluation_timestamp; + JSModuleDef *cycle_root; + JSValue promise; /* corresponds to spec field: capability */ + JSValue resolving_funcs[2]; /* corresponds to spec field: capability */ + + /* true if evaluation yielded an exception. It is saved in + eval_exception */ + BOOL eval_has_exception : 8; + JSValue eval_exception; + JSValue meta_obj; /* for import.meta */ +}; + +typedef struct JSJobEntry { + struct list_head link; + JSContext *ctx; + JSJobFunc *job_func; + int argc; + JSValue argv[0]; +} JSJobEntry; + +typedef struct JSProperty { + union { + JSValue value; /* JS_PROP_NORMAL */ + struct { /* JS_PROP_GETSET */ + JSObject *getter; /* NULL if undefined */ + JSObject *setter; /* NULL if undefined */ + } getset; + JSVarRef *var_ref; /* JS_PROP_VARREF */ + struct { /* JS_PROP_AUTOINIT */ + /* in order to use only 2 pointers, we compress the realm + and the init function pointer */ + uintptr_t realm_and_id; /* realm and init_id (JS_AUTOINIT_ID_x) + in the 2 low bits */ + void *opaque; + } init; + } u; +} JSProperty; + +#define JS_PROP_INITIAL_SIZE 2 +#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */ +#define JS_ARRAY_INITIAL_SIZE 2 + +typedef struct JSShapeProperty { + uint32_t hash_next : 26; /* 0 if last in list */ + uint32_t flags : 6; /* JS_PROP_XXX */ + JSAtom atom; /* JS_ATOM_NULL = free property entry */ +} JSShapeProperty; + +struct JSShape { + /* hash table of size hash_mask + 1 before the start of the + structure (see prop_hash_end()). */ + JSGCObjectHeader header; + /* true if the shape is inserted in the shape hash table. If not, + JSShape.hash is not valid */ + uint8_t is_hashed; + /* If true, the shape may have small array index properties 'n' with 0 + <= n <= 2^31-1. If false, the shape is guaranteed not to have + small array index properties */ + uint8_t has_small_array_index; + uint32_t hash; /* current hash value */ + uint32_t prop_hash_mask; + int prop_size; /* allocated properties */ + int prop_count; /* include deleted properties */ + int deleted_prop_count; + JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */ + JSObject *proto; + JSShapeProperty prop[0]; /* prop_size elements */ +}; + +struct JSObject { + union { + JSGCObjectHeader header; + struct { + int __gc_ref_count; /* corresponds to header.ref_count */ + uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ + + uint8_t extensible : 1; + uint8_t free_mark : 1; /* only used when freeing objects with cycles */ + uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ + uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS and typed arrays) */ + uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ + uint8_t is_uncatchable_error : 1; /* if TRUE, error is not catchable */ + uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ + uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ + uint16_t class_id; /* see JS_CLASS_x */ + }; + }; + /* byte offsets: 16/24 */ + JSShape *shape; /* prototype and property names + flag */ + JSProperty *prop; /* array of properties */ + /* byte offsets: 24/40 */ + struct JSMapRecord *first_weak_ref; /* XXX: use a bit and an external hash table? */ + /* byte offsets: 28/48 */ + union { + void *opaque; + struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */ + struct JSCFunctionDataRecord *c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */ + struct JSForInIterator *for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */ + struct JSArrayBuffer *array_buffer; /* JS_CLASS_ARRAY_BUFFER, JS_CLASS_SHARED_ARRAY_BUFFER */ + struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */ +#ifdef CONFIG_BIGNUM + struct JSFloatEnv *float_env; /* JS_CLASS_FLOAT_ENV */ + struct JSOperatorSetData *operator_set; /* JS_CLASS_OPERATOR_SET */ +#endif + struct JSMapState *map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ + struct JSMapIteratorData *map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */ + struct JSArrayIteratorData *array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */ + struct JSRegExpStringIteratorData *regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ + struct JSGeneratorData *generator_data; /* JS_CLASS_GENERATOR */ + struct JSProxyData *proxy_data; /* JS_CLASS_PROXY */ + struct JSPromiseData *promise_data; /* JS_CLASS_PROMISE */ + struct JSPromiseFunctionData *promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */ + struct JSAsyncFunctionState *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ + struct JSAsyncFromSyncIteratorData *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */ + struct JSAsyncGeneratorData *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */ + struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */ + /* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION and JS_CLASS_ASYNC_GENERATOR_FUNCTION */ + struct JSFunctionBytecode *function_bytecode; + JSVarRef **var_refs; + JSObject *home_object; /* for 'super' access */ + } func; + struct { /* JS_CLASS_C_FUNCTION: 12/20 bytes */ + JSContext *realm; + JSCFunctionType c_function; + uint8_t length; + uint8_t cproto; + int16_t magic; + } cfunc; + /* array part for fast arrays and typed arrays */ + struct { /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + union { + uint32_t size; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ + struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + } u1; + union { + JSValue *values; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ + void *ptr; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + int8_t *int8_ptr; /* JS_CLASS_INT8_ARRAY */ + uint8_t *uint8_ptr; /* JS_CLASS_UINT8_ARRAY, JS_CLASS_UINT8C_ARRAY */ + int16_t *int16_ptr; /* JS_CLASS_INT16_ARRAY */ + uint16_t *uint16_ptr; /* JS_CLASS_UINT16_ARRAY */ + int32_t *int32_ptr; /* JS_CLASS_INT32_ARRAY */ + uint32_t *uint32_ptr; /* JS_CLASS_UINT32_ARRAY */ + int64_t *int64_ptr; /* JS_CLASS_INT64_ARRAY */ + uint64_t *uint64_ptr; /* JS_CLASS_UINT64_ARRAY */ + float *float_ptr; /* JS_CLASS_FLOAT32_ARRAY */ + double *double_ptr; /* JS_CLASS_FLOAT64_ARRAY */ + } u; + uint32_t count; /* <= 2^31-1. 0 for a detached typed array */ + } array; /* 12/20 bytes */ + JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */ + JSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */ + } u; + /* byte sizes: 40/48/72 */ +}; +enum { + __JS_ATOM_NULL = JS_ATOM_NULL, +#define DEF(name, str) JS_ATOM_ ## name, +#include "quickjs-atom.h" +#undef DEF + JS_ATOM_END, +}; +#define JS_ATOM_LAST_KEYWORD JS_ATOM_super +#define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield + +static const char js_atom_init[] = +#define DEF(name, str) str "\0" +#include "quickjs-atom.h" +#undef DEF +; + +typedef enum OPCodeFormat { +#define FMT(f) OP_FMT_ ## f, +#define DEF(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" +#undef DEF +#undef FMT +} OPCodeFormat; + +enum OPCodeEnum { +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) OP_ ## id, +#define def(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT + OP_COUNT, /* excluding temporary opcodes */ + /* temporary opcodes : overlap with the short opcodes */ + OP_TEMP_START = OP_nop + 1, + OP___dummy = OP_TEMP_START - 1, +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) +#define def(id, size, n_pop, n_push, f) OP_ ## id, +#include "quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT + OP_TEMP_END, +}; + +static int JS_InitAtoms(JSRuntime *rt); +static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, + int atom_type); +static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p); +static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b); +static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags); +static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags); +static JSValue JS_CallInternal(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, JSValueConst new_target, + int argc, JSValue *argv, int flags); +static JSValue JS_CallConstructorInternal(JSContext *ctx, + JSValueConst func_obj, + JSValueConst new_target, + int argc, JSValue *argv, int flags); +static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv); +static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, + int argc, JSValueConst *argv); +static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, + JSValue val, BOOL is_array_ctor); +static JSValue JS_EvalObject(JSContext *ctx, JSValueConst this_obj, + JSValueConst val, int flags, int scope_idx); +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...); +static __maybe_unused void JS_DumpAtoms(JSRuntime *rt); +static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p); +static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt); +static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p); +static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p); +static __maybe_unused void JS_DumpValueShort(JSRuntime *rt, JSValueConst val); +static __maybe_unused void JS_DumpValue(JSContext *ctx, JSValueConst val); +static __maybe_unused void JS_PrintValue(JSContext *ctx, + const char *str, + JSValueConst val); +static __maybe_unused void JS_DumpShapes(JSRuntime *rt); +static JSValue js_function_apply(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); +static void js_array_finalizer(JSRuntime *rt, JSValue val); +static void js_array_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); +static void js_object_data_finalizer(JSRuntime *rt, JSValue val); +static void js_object_data_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); +static void js_c_function_finalizer(JSRuntime *rt, JSValue val); +static void js_c_function_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func); +static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val); +static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_bound_function_finalizer(JSRuntime *rt, JSValue val); +static void js_bound_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValue val); +static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_regexp_finalizer(JSRuntime *rt, JSValue val); +static void js_array_buffer_finalizer(JSRuntime *rt, JSValue val); +static void js_typed_array_finalizer(JSRuntime *rt, JSValue val); +static void js_typed_array_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_proxy_finalizer(JSRuntime *rt, JSValue val); +static void js_proxy_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_map_finalizer(JSRuntime *rt, JSValue val); +static void js_map_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_map_iterator_finalizer(JSRuntime *rt, JSValue val); +static void js_map_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_array_iterator_finalizer(JSRuntime *rt, JSValue val); +static void js_array_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_regexp_string_iterator_finalizer(JSRuntime *rt, JSValue val); +static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_generator_finalizer(JSRuntime *rt, JSValue obj); +static void js_generator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_promise_finalizer(JSRuntime *rt, JSValue val); +static void js_promise_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValue val); +static void js_promise_resolve_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +#ifdef CONFIG_BIGNUM +static void js_operator_set_finalizer(JSRuntime *rt, JSValue val); +static void js_operator_set_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +#endif +static JSValue JS_ToStringFree(JSContext *ctx, JSValue val); +static int JS_ToBoolFree(JSContext *ctx, JSValue val); +static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val); +static int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val); +static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val); +static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern, + JSValueConst flags); +static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValueConst ctor, + JSValue pattern, JSValue bc); +static void gc_decref(JSRuntime *rt); +static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, + const JSClassDef *class_def, JSAtom name); + +typedef enum JSStrictEqModeEnum { + JS_EQ_STRICT, + JS_EQ_SAME_VALUE, + JS_EQ_SAME_VALUE_ZERO, +} JSStrictEqModeEnum; + +static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, + JSStrictEqModeEnum eq_mode); +static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2); +static BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2); +static BOOL js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2); +static JSValue JS_ToObject(JSContext *ctx, JSValueConst val); +static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val); +static JSProperty *add_property(JSContext *ctx, + JSObject *p, JSAtom prop, int prop_flags); +static JSValue JS_NewBigInt(JSContext *ctx); +static inline bf_t *JS_GetBigInt(JSValueConst val) +{ + JSBigFloat *p = JS_VALUE_GET_PTR(val); + return &p->num; +} +static JSValue JS_CompactBigInt1(JSContext *ctx, JSValue val, + BOOL convert_to_safe_integer); +static JSValue JS_CompactBigInt(JSContext *ctx, JSValue val); +static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val); +static bf_t *JS_ToBigInt(JSContext *ctx, bf_t *buf, JSValueConst val); +static void JS_FreeBigInt(JSContext *ctx, bf_t *a, bf_t *buf); +#ifdef CONFIG_BIGNUM +static void js_float_env_finalizer(JSRuntime *rt, JSValue val); +static JSValue JS_NewBigFloat(JSContext *ctx); +static inline bf_t *JS_GetBigFloat(JSValueConst val) +{ + JSBigFloat *p = JS_VALUE_GET_PTR(val); + return &p->num; +} +static JSValue JS_NewBigDecimal(JSContext *ctx); +static inline bfdec_t *JS_GetBigDecimal(JSValueConst val) +{ + JSBigDecimal *p = JS_VALUE_GET_PTR(val); + return &p->num; +} +static bf_t *JS_ToBigFloat(JSContext *ctx, bf_t *buf, JSValueConst val); +static JSValue JS_ToBigDecimalFree(JSContext *ctx, JSValue val, + BOOL allow_null_or_undefined); +static bfdec_t *JS_ToBigDecimal(JSContext *ctx, JSValueConst val); +#endif +JSValue JS_ThrowOutOfMemory(JSContext *ctx); +static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx); +static JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValueConst obj); +static int js_proxy_setPrototypeOf(JSContext *ctx, JSValueConst obj, + JSValueConst proto_val, BOOL throw_flag); +static int js_proxy_isExtensible(JSContext *ctx, JSValueConst obj); +static int js_proxy_preventExtensions(JSContext *ctx, JSValueConst obj); +static int js_proxy_isArray(JSContext *ctx, JSValueConst obj); +static int JS_CreateProperty(JSContext *ctx, JSObject *p, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, + int flags); +static int js_string_memcmp(const JSString *p1, const JSString *p2, int len); +static void reset_weak_ref(JSRuntime *rt, JSObject *p); +static JSValue js_array_buffer_constructor3(JSContext *ctx, + JSValueConst new_target, + uint64_t len, JSClassID class_id, + uint8_t *buf, + JSFreeArrayBufferDataFunc *free_func, + void *opaque, BOOL alloc_flag); +static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValueConst obj); +static JSValue js_typed_array_constructor(JSContext *ctx, + JSValueConst this_val, + int argc, JSValueConst *argv, + int classid); +static JSValue js_typed_array_constructor_ta(JSContext *ctx, + JSValueConst new_target, + JSValueConst src_obj, + int classid); +static BOOL typed_array_is_detached(JSContext *ctx, JSObject *p); +static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p); +static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx); +static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, + BOOL is_arg); +static void __async_func_free(JSRuntime *rt, JSAsyncFunctionState *s); +static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s); +static JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags); +static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val); +static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, + const char *input, size_t input_len, + const char *filename, int flags, int scope_idx); +static void js_free_module_def(JSContext *ctx, JSModuleDef *m); +static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m, + JS_MarkFunc *mark_func); +static JSValue js_import_meta(JSContext *ctx); +static JSValue js_dynamic_import(JSContext *ctx, JSValueConst specifier); +static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref); +static JSValue js_new_promise_capability(JSContext *ctx, + JSValue *resolving_funcs, + JSValueConst ctor); +static __exception int perform_promise_then(JSContext *ctx, + JSValueConst promise, + JSValueConst *resolve_reject, + JSValueConst *cap_resolving_funcs); +static JSValue js_promise_resolve(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); +static JSValue js_promise_then(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static int js_string_compare(JSContext *ctx, + const JSString *p1, const JSString *p2); +static JSValue JS_ToNumber(JSContext *ctx, JSValueConst val); +static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop, JSValue val, int flags); +static int JS_NumberIsInteger(JSContext *ctx, JSValueConst val); +static BOOL JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val); +static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val); +static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, + JSObject *p, JSAtom prop); +static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc); +static void JS_AddIntrinsicBasicObjects(JSContext *ctx); +static void js_free_shape(JSRuntime *rt, JSShape *sh); +static void js_free_shape_null(JSRuntime *rt, JSShape *sh); +static int js_shape_prepare_update(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs); +static int init_shape_hash(JSRuntime *rt); +static __exception int js_get_length32(JSContext *ctx, uint32_t *pres, + JSValueConst obj); +static __exception int js_get_length64(JSContext *ctx, int64_t *pres, + JSValueConst obj); +static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len); +static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen, + JSValueConst array_arg); +static BOOL js_get_fast_array(JSContext *ctx, JSValueConst obj, + JSValue **arrpp, uint32_t *countp); +static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx, + JSValueConst sync_iter); +static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val); +static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static JSValue js_c_function_data_call(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_val, + int argc, JSValueConst *argv, int flags); +static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val); +static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, + JSGCObjectTypeEnum type); +static void remove_gc_object(JSGCObjectHeader *h); +static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); +static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, + void *opaque); +static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p, + JSAtom atom, void *opaque); +void JS_SetUncatchableError(JSContext *ctx, JSValueConst val, BOOL flag); +static JSValue js_object_groupBy(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int is_map); + +static const JSClassExoticMethods js_arguments_exotic_methods; +static const JSClassExoticMethods js_string_exotic_methods; +static const JSClassExoticMethods js_proxy_exotic_methods; +static const JSClassExoticMethods js_module_ns_exotic_methods; +static JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT; + +static void js_trigger_gc(JSRuntime *rt, size_t size) +{ + BOOL force_gc; +#ifdef FORCE_GC_AT_MALLOC + force_gc = TRUE; +#else + force_gc = ((rt->malloc_state.malloc_size + size) > + rt->malloc_gc_threshold); +#endif + if (force_gc) { +#ifdef DUMP_GC + printf("GC: size=%" PRIu64 "\n", + (uint64_t)rt->malloc_state.malloc_size); +#endif + JS_RunGC(rt); + rt->malloc_gc_threshold = rt->malloc_state.malloc_size + + (rt->malloc_state.malloc_size >> 1); + } +} + +static size_t js_malloc_usable_size_unknown(const void *ptr) +{ + return 0; +} + +void *js_malloc_rt(JSRuntime *rt, size_t size) +{ + return rt->mf.js_malloc(&rt->malloc_state, size); +} + +void js_free_rt(JSRuntime *rt, void *ptr) +{ + rt->mf.js_free(&rt->malloc_state, ptr); +} + +void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size) +{ + return rt->mf.js_realloc(&rt->malloc_state, ptr, size); +} + +size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr) +{ + return rt->mf.js_malloc_usable_size(ptr); +} + +void *js_mallocz_rt(JSRuntime *rt, size_t size) +{ + void *ptr; + ptr = js_malloc_rt(rt, size); + if (!ptr) + return NULL; + return memset(ptr, 0, size); +} + +/* called by libbf */ +static void *js_bf_realloc(void *opaque, void *ptr, size_t size) +{ + JSRuntime *rt = opaque; + return js_realloc_rt(rt, ptr, size); +} + +/* Throw out of memory in case of error */ +void *js_malloc(JSContext *ctx, size_t size) +{ + void *ptr; + ptr = js_malloc_rt(ctx->rt, size); + if (unlikely(!ptr)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ptr; +} + +/* Throw out of memory in case of error */ +void *js_mallocz(JSContext *ctx, size_t size) +{ + void *ptr; + ptr = js_mallocz_rt(ctx->rt, size); + if (unlikely(!ptr)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ptr; +} + +void js_free(JSContext *ctx, void *ptr) +{ + js_free_rt(ctx->rt, ptr); +} + +/* Throw out of memory in case of error */ +void *js_realloc(JSContext *ctx, void *ptr, size_t size) +{ + void *ret; + ret = js_realloc_rt(ctx->rt, ptr, size); + if (unlikely(!ret && size != 0)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ret; +} + +/* store extra allocated size in *pslack if successful */ +void *js_realloc2(JSContext *ctx, void *ptr, size_t size, size_t *pslack) +{ + void *ret; + ret = js_realloc_rt(ctx->rt, ptr, size); + if (unlikely(!ret && size != 0)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + if (pslack) { + size_t new_size = js_malloc_usable_size_rt(ctx->rt, ret); + *pslack = (new_size > size) ? new_size - size : 0; + } + return ret; +} + +size_t js_malloc_usable_size(JSContext *ctx, const void *ptr) +{ + return js_malloc_usable_size_rt(ctx->rt, ptr); +} + +/* Throw out of memory exception in case of error */ +char *js_strndup(JSContext *ctx, const char *s, size_t n) +{ + char *ptr; + ptr = js_malloc(ctx, n + 1); + if (ptr) { + memcpy(ptr, s, n); + ptr[n] = '\0'; + } + return ptr; +} + +char *js_strdup(JSContext *ctx, const char *str) +{ + return js_strndup(ctx, str, strlen(str)); +} + +static no_inline int js_realloc_array(JSContext *ctx, void **parray, + int elem_size, int *psize, int req_size) +{ + int new_size; + size_t slack; + void *new_array; + /* XXX: potential arithmetic overflow */ + new_size = max_int(req_size, *psize * 3 / 2); + new_array = js_realloc2(ctx, *parray, new_size * elem_size, &slack); + if (!new_array) + return -1; + new_size += slack / elem_size; + *psize = new_size; + *parray = new_array; + return 0; +} + +/* resize the array and update its size if req_size > *psize */ +static inline int js_resize_array(JSContext *ctx, void **parray, int elem_size, + int *psize, int req_size) +{ + if (unlikely(req_size > *psize)) + return js_realloc_array(ctx, parray, elem_size, psize, req_size); + else + return 0; +} + +static inline void js_dbuf_init(JSContext *ctx, DynBuf *s) +{ + dbuf_init2(s, ctx->rt, (DynBufReallocFunc *)js_realloc_rt); +} + +static inline int is_digit(int c) { + return c >= '0' && c <= '9'; +} + +static inline int string_get(const JSString *p, int idx) { + return p->is_wide_char ? p->u.str16[idx] : p->u.str8[idx]; +} + +typedef struct JSClassShortDef { + JSAtom class_name; + JSClassFinalizer *finalizer; + JSClassGCMark *gc_mark; +} JSClassShortDef; + +static JSClassShortDef const js_std_class_def[] = { + { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_OBJECT */ + { JS_ATOM_Array, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARRAY */ + { JS_ATOM_Error, NULL, NULL }, /* JS_CLASS_ERROR */ + { JS_ATOM_Number, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_NUMBER */ + { JS_ATOM_String, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_STRING */ + { JS_ATOM_Boolean, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BOOLEAN */ + { JS_ATOM_Symbol, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_SYMBOL */ + { JS_ATOM_Arguments, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARGUMENTS */ + { JS_ATOM_Arguments, NULL, NULL }, /* JS_CLASS_MAPPED_ARGUMENTS */ + { JS_ATOM_Date, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_DATE */ + { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_MODULE_NS */ + { JS_ATOM_Function, js_c_function_finalizer, js_c_function_mark }, /* JS_CLASS_C_FUNCTION */ + { JS_ATOM_Function, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_BYTECODE_FUNCTION */ + { JS_ATOM_Function, js_bound_function_finalizer, js_bound_function_mark }, /* JS_CLASS_BOUND_FUNCTION */ + { JS_ATOM_Function, js_c_function_data_finalizer, js_c_function_data_mark }, /* JS_CLASS_C_FUNCTION_DATA */ + { JS_ATOM_GeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_GENERATOR_FUNCTION */ + { JS_ATOM_ForInIterator, js_for_in_iterator_finalizer, js_for_in_iterator_mark }, /* JS_CLASS_FOR_IN_ITERATOR */ + { JS_ATOM_RegExp, js_regexp_finalizer, NULL }, /* JS_CLASS_REGEXP */ + { JS_ATOM_ArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_ARRAY_BUFFER */ + { JS_ATOM_SharedArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_SHARED_ARRAY_BUFFER */ + { JS_ATOM_Uint8ClampedArray, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8C_ARRAY */ + { JS_ATOM_Int8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT8_ARRAY */ + { JS_ATOM_Uint8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8_ARRAY */ + { JS_ATOM_Int16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT16_ARRAY */ + { JS_ATOM_Uint16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT16_ARRAY */ + { JS_ATOM_Int32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT32_ARRAY */ + { JS_ATOM_Uint32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT32_ARRAY */ + { JS_ATOM_BigInt64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_INT64_ARRAY */ + { JS_ATOM_BigUint64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_UINT64_ARRAY */ + { JS_ATOM_Float32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT32_ARRAY */ + { JS_ATOM_Float64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT64_ARRAY */ + { JS_ATOM_DataView, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_DATAVIEW */ + { JS_ATOM_BigInt, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_INT */ +#ifdef CONFIG_BIGNUM + { JS_ATOM_BigFloat, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_FLOAT */ + { JS_ATOM_BigFloatEnv, js_float_env_finalizer, NULL }, /* JS_CLASS_FLOAT_ENV */ + { JS_ATOM_BigDecimal, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_DECIMAL */ + { JS_ATOM_OperatorSet, js_operator_set_finalizer, js_operator_set_mark }, /* JS_CLASS_OPERATOR_SET */ +#endif + { JS_ATOM_Map, js_map_finalizer, js_map_mark }, /* JS_CLASS_MAP */ + { JS_ATOM_Set, js_map_finalizer, js_map_mark }, /* JS_CLASS_SET */ + { JS_ATOM_WeakMap, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKMAP */ + { JS_ATOM_WeakSet, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKSET */ + { JS_ATOM_Map_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_MAP_ITERATOR */ + { JS_ATOM_Set_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_SET_ITERATOR */ + { JS_ATOM_Array_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_ARRAY_ITERATOR */ + { JS_ATOM_String_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */ + { JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_REGEXP_STRING_ITERATOR */ + { JS_ATOM_Generator, js_generator_finalizer, js_generator_mark }, /* JS_CLASS_GENERATOR */ +}; + +static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab, + int start, int count) +{ + JSClassDef cm_s, *cm = &cm_s; + int i, class_id; + + for(i = 0; i < count; i++) { + class_id = i + start; + memset(cm, 0, sizeof(*cm)); + cm->finalizer = tab[i].finalizer; + cm->gc_mark = tab[i].gc_mark; + if (JS_NewClass1(rt, class_id, cm, tab[i].class_name) < 0) + return -1; + } + return 0; +} + +static JSValue JS_ThrowUnsupportedOperation(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "unsupported operation"); +} + +static JSValue invalid_to_string(JSContext *ctx, JSValueConst val) +{ + return JS_ThrowUnsupportedOperation(ctx); +} + +static JSValue invalid_from_string(JSContext *ctx, const char *buf, + int radix, int flags, slimb_t *pexponent) +{ + return JS_NAN; +} + +static int invalid_unary_arith(JSContext *ctx, + JSValue *pres, OPCodeEnum op, JSValue op1) +{ + JS_FreeValue(ctx, op1); + JS_ThrowUnsupportedOperation(ctx); + return -1; +} + +static int invalid_binary_arith(JSContext *ctx, OPCodeEnum op, + JSValue *pres, JSValue op1, JSValue op2) +{ + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + JS_ThrowUnsupportedOperation(ctx); + return -1; +} + +static JSValue invalid_mul_pow10_to_float64(JSContext *ctx, const bf_t *a, + int64_t exponent) +{ + return JS_ThrowUnsupportedOperation(ctx); +} + +static int invalid_mul_pow10(JSContext *ctx, JSValue *sp) +{ + JS_ThrowUnsupportedOperation(ctx); + return -1; +} + +static void set_dummy_numeric_ops(JSNumericOperations *ops) +{ + ops->to_string = invalid_to_string; + ops->from_string = invalid_from_string; + ops->unary_arith = invalid_unary_arith; + ops->binary_arith = invalid_binary_arith; + ops->mul_pow10_to_float64 = invalid_mul_pow10_to_float64; + ops->mul_pow10 = invalid_mul_pow10; +} + +#if !defined(CONFIG_STACK_CHECK) +/* no stack limitation */ +static inline uintptr_t js_get_stack_pointer(void) +{ + return 0; +} + +static inline BOOL js_check_stack_overflow(JSRuntime *rt, size_t alloca_size) +{ + return FALSE; +} +#else +/* Note: OS and CPU dependent */ +static inline uintptr_t js_get_stack_pointer(void) +{ + return (uintptr_t)__builtin_frame_address(0); +} + +static inline BOOL js_check_stack_overflow(JSRuntime *rt, size_t alloca_size) +{ + uintptr_t sp; + sp = js_get_stack_pointer() - alloca_size; + return unlikely(sp < rt->stack_limit); +} +#endif + +JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) +{ + JSRuntime *rt; + JSMallocState ms; + + memset(&ms, 0, sizeof(ms)); + ms.opaque = opaque; + ms.malloc_limit = -1; + + rt = mf->js_malloc(&ms, sizeof(JSRuntime)); + if (!rt) + return NULL; + memset(rt, 0, sizeof(*rt)); + rt->mf = *mf; + if (!rt->mf.js_malloc_usable_size) { + /* use dummy function if none provided */ + rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown; + } + rt->malloc_state = ms; + rt->malloc_gc_threshold = 256 * 1024; + + bf_context_init(&rt->bf_ctx, js_bf_realloc, rt); + set_dummy_numeric_ops(&rt->bigint_ops); +#ifdef CONFIG_BIGNUM + set_dummy_numeric_ops(&rt->bigfloat_ops); + set_dummy_numeric_ops(&rt->bigdecimal_ops); +#endif + + init_list_head(&rt->context_list); + init_list_head(&rt->gc_obj_list); + init_list_head(&rt->gc_zero_ref_count_list); + rt->gc_phase = JS_GC_PHASE_NONE; + +#ifdef DUMP_LEAKS + init_list_head(&rt->string_list); +#endif + init_list_head(&rt->job_list); + + if (JS_InitAtoms(rt)) + goto fail; + + /* create the object, array and function classes */ + if (init_class_range(rt, js_std_class_def, JS_CLASS_OBJECT, + countof(js_std_class_def)) < 0) + goto fail; + rt->class_array[JS_CLASS_ARGUMENTS].exotic = &js_arguments_exotic_methods; + rt->class_array[JS_CLASS_STRING].exotic = &js_string_exotic_methods; + rt->class_array[JS_CLASS_MODULE_NS].exotic = &js_module_ns_exotic_methods; + + rt->class_array[JS_CLASS_C_FUNCTION].call = js_call_c_function; + rt->class_array[JS_CLASS_C_FUNCTION_DATA].call = js_c_function_data_call; + rt->class_array[JS_CLASS_BOUND_FUNCTION].call = js_call_bound_function; + rt->class_array[JS_CLASS_GENERATOR_FUNCTION].call = js_generator_function_call; + if (init_shape_hash(rt)) + goto fail; + + rt->stack_size = JS_DEFAULT_STACK_SIZE; + JS_UpdateStackTop(rt); + + rt->current_exception = JS_NULL; + + return rt; + fail: + JS_FreeRuntime(rt); + return NULL; +} + +void *JS_GetRuntimeOpaque(JSRuntime *rt) +{ + return rt->user_opaque; +} + +void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque) +{ + rt->user_opaque = opaque; +} + +/* default memory allocation functions with memory limitation */ +static size_t js_def_malloc_usable_size(const void *ptr) +{ +#if defined(__APPLE__) + return malloc_size(ptr); +#elif defined(_WIN32) + return _msize((void *)ptr); +#elif defined(EMSCRIPTEN) + return 0; +#elif defined(__linux__) + return malloc_usable_size((void *)ptr); +#else + /* change this to `return 0;` if compilation fails */ + return malloc_usable_size((void *)ptr); +#endif +} + +static void *js_def_malloc(JSMallocState *s, size_t size) +{ + void *ptr; + + /* Do not allocate zero bytes: behavior is platform dependent */ + assert(size != 0); + + if (unlikely(s->malloc_size + size > s->malloc_limit)) + return NULL; + + ptr = malloc(size); + if (!ptr) + return NULL; + + s->malloc_count++; + s->malloc_size += js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + return ptr; +} + +static void js_def_free(JSMallocState *s, void *ptr) +{ + if (!ptr) + return; + + s->malloc_count--; + s->malloc_size -= js_def_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + free(ptr); +} + +static void *js_def_realloc(JSMallocState *s, void *ptr, size_t size) +{ + size_t old_size; + + if (!ptr) { + if (size == 0) + return NULL; + return js_def_malloc(s, size); + } + old_size = js_def_malloc_usable_size(ptr); + if (size == 0) { + s->malloc_count--; + s->malloc_size -= old_size + MALLOC_OVERHEAD; + free(ptr); + return NULL; + } + if (s->malloc_size + size - old_size > s->malloc_limit) + return NULL; + + ptr = realloc(ptr, size); + if (!ptr) + return NULL; + + s->malloc_size += js_def_malloc_usable_size(ptr) - old_size; + return ptr; +} + +static const JSMallocFunctions def_malloc_funcs = { + js_def_malloc, + js_def_free, + js_def_realloc, + js_def_malloc_usable_size, +}; + +JSRuntime *JS_NewRuntime(void) +{ + return JS_NewRuntime2(&def_malloc_funcs, NULL); +} + +void JS_SetMemoryLimit(JSRuntime *rt, size_t limit) +{ + rt->malloc_state.malloc_limit = limit; +} + +/* use -1 to disable automatic GC */ +void JS_SetGCThreshold(JSRuntime *rt, size_t gc_threshold) +{ + rt->malloc_gc_threshold = gc_threshold; +} + +#define malloc(s) malloc_is_forbidden(s) +#define free(p) free_is_forbidden(p) +#define realloc(p,s) realloc_is_forbidden(p,s) + +void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque) +{ + rt->interrupt_handler = cb; + rt->interrupt_opaque = opaque; +} + +void JS_SetCanBlock(JSRuntime *rt, BOOL can_block) +{ + rt->can_block = can_block; +} + +void JS_SetSharedArrayBufferFunctions(JSRuntime *rt, + const JSSharedArrayBufferFunctions *sf) +{ + rt->sab_funcs = *sf; +} + +/* return 0 if OK, < 0 if exception */ +int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = ctx->rt; + JSJobEntry *e; + int i; + + e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue)); + if (!e) + return -1; + e->ctx = ctx; + e->job_func = job_func; + e->argc = argc; + for(i = 0; i < argc; i++) { + e->argv[i] = JS_DupValue(ctx, argv[i]); + } + list_add_tail(&e->link, &rt->job_list); + return 0; +} + +BOOL JS_IsJobPending(JSRuntime *rt) +{ + return !list_empty(&rt->job_list); +} + +/* return < 0 if exception, 0 if no job pending, 1 if a job was + executed successfully. the context of the job is stored in '*pctx' */ +int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx) +{ + JSContext *ctx; + JSJobEntry *e; + JSValue res; + int i, ret; + + if (list_empty(&rt->job_list)) { + *pctx = NULL; + return 0; + } + + /* get the first pending job and execute it */ + e = list_entry(rt->job_list.next, JSJobEntry, link); + list_del(&e->link); + ctx = e->ctx; + res = e->job_func(e->ctx, e->argc, (JSValueConst *)e->argv); + for(i = 0; i < e->argc; i++) + JS_FreeValue(ctx, e->argv[i]); + if (JS_IsException(res)) + ret = -1; + else + ret = 1; + JS_FreeValue(ctx, res); + js_free(ctx, e); + *pctx = ctx; + return ret; +} + +static inline uint32_t atom_get_free(const JSAtomStruct *p) +{ + return (uintptr_t)p >> 1; +} + +static inline BOOL atom_is_free(const JSAtomStruct *p) +{ + return (uintptr_t)p & 1; +} + +static inline JSAtomStruct *atom_set_free(uint32_t v) +{ + return (JSAtomStruct *)(((uintptr_t)v << 1) | 1); +} + +/* Note: the string contents are uninitialized */ +static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char) +{ + JSString *str; + str = js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char); + if (unlikely(!str)) + return NULL; + str->header.ref_count = 1; + str->is_wide_char = is_wide_char; + str->len = max_len; + str->atom_type = 0; + str->hash = 0; /* optional but costless */ + str->hash_next = 0; /* optional */ +#ifdef DUMP_LEAKS + list_add_tail(&str->link, &rt->string_list); +#endif + return str; +} + +static JSString *js_alloc_string(JSContext *ctx, int max_len, int is_wide_char) +{ + JSString *p; + p = js_alloc_string_rt(ctx->rt, max_len, is_wide_char); + if (unlikely(!p)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return p; +} + +/* same as JS_FreeValueRT() but faster */ +static inline void js_free_string(JSRuntime *rt, JSString *str) +{ + if (--str->header.ref_count <= 0) { + if (str->atom_type) { + JS_FreeAtomStruct(rt, str); + } else { +#ifdef DUMP_LEAKS + list_del(&str->link); +#endif + js_free_rt(rt, str); + } + } +} + +void JS_SetRuntimeInfo(JSRuntime *rt, const char *s) +{ + if (rt) + rt->rt_info = s; +} + +void JS_FreeRuntime(JSRuntime *rt) +{ + struct list_head *el, *el1; + int i; + + JS_FreeValueRT(rt, rt->current_exception); + + list_for_each_safe(el, el1, &rt->job_list) { + JSJobEntry *e = list_entry(el, JSJobEntry, link); + for(i = 0; i < e->argc; i++) + JS_FreeValueRT(rt, e->argv[i]); + js_free_rt(rt, e); + } + init_list_head(&rt->job_list); + + JS_RunGC(rt); + +#ifdef DUMP_LEAKS + /* leaking objects */ + { + BOOL header_done; + JSGCObjectHeader *p; + int count; + + /* remove the internal refcounts to display only the object + referenced externally */ + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + p->mark = 0; + } + gc_decref(rt); + + header_done = FALSE; + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + if (p->ref_count != 0) { + if (!header_done) { + printf("Object leaks:\n"); + JS_DumpObjectHeader(rt); + header_done = TRUE; + } + JS_DumpGCObject(rt, p); + } + } + + count = 0; + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + if (p->ref_count == 0) { + count++; + } + } + if (count != 0) + printf("Secondary object leaks: %d\n", count); + } +#endif + assert(list_empty(&rt->gc_obj_list)); + + /* free the classes */ + for(i = 0; i < rt->class_count; i++) { + JSClass *cl = &rt->class_array[i]; + if (cl->class_id != 0) { + JS_FreeAtomRT(rt, cl->class_name); + } + } + js_free_rt(rt, rt->class_array); + + bf_context_end(&rt->bf_ctx); + +#ifdef DUMP_LEAKS + /* only the atoms defined in JS_InitAtoms() should be left */ + { + BOOL header_done = FALSE; + + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p) /* && p->str*/) { + if (i >= JS_ATOM_END || p->header.ref_count != 1) { + if (!header_done) { + header_done = TRUE; + if (rt->rt_info) { + printf("%s:1: atom leakage:", rt->rt_info); + } else { + printf("Atom leaks:\n" + " %6s %6s %s\n", + "ID", "REFCNT", "NAME"); + } + } + if (rt->rt_info) { + printf(" "); + } else { + printf(" %6u %6u ", i, p->header.ref_count); + } + switch (p->atom_type) { + case JS_ATOM_TYPE_STRING: + JS_DumpString(rt, p); + break; + case JS_ATOM_TYPE_GLOBAL_SYMBOL: + printf("Symbol.for("); + JS_DumpString(rt, p); + printf(")"); + break; + case JS_ATOM_TYPE_SYMBOL: + if (p->hash == JS_ATOM_HASH_SYMBOL) { + printf("Symbol("); + JS_DumpString(rt, p); + printf(")"); + } else { + printf("Private("); + JS_DumpString(rt, p); + printf(")"); + } + break; + } + if (rt->rt_info) { + printf(":%u", p->header.ref_count); + } else { + printf("\n"); + } + } + } + } + if (rt->rt_info && header_done) + printf("\n"); + } +#endif + + /* free the atoms */ + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p)) { +#ifdef DUMP_LEAKS + list_del(&p->link); +#endif + js_free_rt(rt, p); + } + } + js_free_rt(rt, rt->atom_array); + js_free_rt(rt, rt->atom_hash); + js_free_rt(rt, rt->shape_hash); +#ifdef DUMP_LEAKS + if (!list_empty(&rt->string_list)) { + if (rt->rt_info) { + printf("%s:1: string leakage:", rt->rt_info); + } else { + printf("String leaks:\n" + " %6s %s\n", + "REFCNT", "VALUE"); + } + list_for_each_safe(el, el1, &rt->string_list) { + JSString *str = list_entry(el, JSString, link); + if (rt->rt_info) { + printf(" "); + } else { + printf(" %6u ", str->header.ref_count); + } + JS_DumpString(rt, str); + if (rt->rt_info) { + printf(":%u", str->header.ref_count); + } else { + printf("\n"); + } + list_del(&str->link); + js_free_rt(rt, str); + } + if (rt->rt_info) + printf("\n"); + } + { + JSMallocState *s = &rt->malloc_state; + if (s->malloc_count > 1) { + if (rt->rt_info) + printf("%s:1: ", rt->rt_info); + printf("Memory leak: %"PRIu64" bytes lost in %"PRIu64" block%s\n", + (uint64_t)(s->malloc_size - sizeof(JSRuntime)), + (uint64_t)(s->malloc_count - 1), &"s"[s->malloc_count == 2]); + } + } +#endif + + { + JSMallocState ms = rt->malloc_state; + rt->mf.js_free(&ms, rt); + } +} + +JSContext *JS_NewContextRaw(JSRuntime *rt) +{ + JSContext *ctx; + int i; + + ctx = js_mallocz_rt(rt, sizeof(JSContext)); + if (!ctx) + return NULL; + ctx->header.ref_count = 1; + add_gc_object(rt, &ctx->header, JS_GC_OBJ_TYPE_JS_CONTEXT); + + ctx->class_proto = js_malloc_rt(rt, sizeof(ctx->class_proto[0]) * + rt->class_count); + if (!ctx->class_proto) { + js_free_rt(rt, ctx); + return NULL; + } + ctx->rt = rt; + list_add_tail(&ctx->link, &rt->context_list); + ctx->bf_ctx = &rt->bf_ctx; +#ifdef CONFIG_BIGNUM + ctx->fp_env.prec = 113; + ctx->fp_env.flags = bf_set_exp_bits(15) | BF_RNDN | BF_FLAG_SUBNORMAL; +#endif + for(i = 0; i < rt->class_count; i++) + ctx->class_proto[i] = JS_NULL; + ctx->array_ctor = JS_NULL; + ctx->regexp_ctor = JS_NULL; + ctx->promise_ctor = JS_NULL; + init_list_head(&ctx->loaded_modules); + + JS_AddIntrinsicBasicObjects(ctx); + return ctx; +} + +JSContext *JS_NewContext(JSRuntime *rt) +{ + JSContext *ctx; + + ctx = JS_NewContextRaw(rt); + if (!ctx) + return NULL; + + JS_AddIntrinsicBaseObjects(ctx); + JS_AddIntrinsicDate(ctx); + JS_AddIntrinsicEval(ctx); + JS_AddIntrinsicStringNormalize(ctx); + JS_AddIntrinsicRegExp(ctx); + JS_AddIntrinsicJSON(ctx); + JS_AddIntrinsicProxy(ctx); + JS_AddIntrinsicMapSet(ctx); + JS_AddIntrinsicTypedArrays(ctx); + JS_AddIntrinsicPromise(ctx); + JS_AddIntrinsicBigInt(ctx); + return ctx; +} + +void *JS_GetContextOpaque(JSContext *ctx) +{ + return ctx->user_opaque; +} + +void JS_SetContextOpaque(JSContext *ctx, void *opaque) +{ + ctx->user_opaque = opaque; +} + +/* set the new value and free the old value after (freeing the value + can reallocate the object data) */ +static inline void set_value(JSContext *ctx, JSValue *pval, JSValue new_val) +{ + JSValue old_val; + old_val = *pval; + *pval = new_val; + JS_FreeValue(ctx, old_val); +} + +void JS_SetClassProto(JSContext *ctx, JSClassID class_id, JSValue obj) +{ + JSRuntime *rt = ctx->rt; + assert(class_id < rt->class_count); + set_value(ctx, &ctx->class_proto[class_id], obj); +} + +JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id) +{ + JSRuntime *rt = ctx->rt; + assert(class_id < rt->class_count); + return JS_DupValue(ctx, ctx->class_proto[class_id]); +} + +typedef enum JSFreeModuleEnum { + JS_FREE_MODULE_ALL, + JS_FREE_MODULE_NOT_RESOLVED, +} JSFreeModuleEnum; + +/* XXX: would be more efficient with separate module lists */ +static void js_free_modules(JSContext *ctx, JSFreeModuleEnum flag) +{ + struct list_head *el, *el1; + list_for_each_safe(el, el1, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el, JSModuleDef, link); + if (flag == JS_FREE_MODULE_ALL || + (flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved)) { + js_free_module_def(ctx, m); + } + } +} + +JSContext *JS_DupContext(JSContext *ctx) +{ + ctx->header.ref_count++; + return ctx; +} + +/* used by the GC */ +static void JS_MarkContext(JSRuntime *rt, JSContext *ctx, + JS_MarkFunc *mark_func) +{ + int i; + struct list_head *el; + + /* modules are not seen by the GC, so we directly mark the objects + referenced by each module */ + list_for_each(el, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el, JSModuleDef, link); + js_mark_module_def(rt, m, mark_func); + } + + JS_MarkValue(rt, ctx->global_obj, mark_func); + JS_MarkValue(rt, ctx->global_var_obj, mark_func); + + JS_MarkValue(rt, ctx->throw_type_error, mark_func); + JS_MarkValue(rt, ctx->eval_obj, mark_func); + + JS_MarkValue(rt, ctx->array_proto_values, mark_func); + for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + JS_MarkValue(rt, ctx->native_error_proto[i], mark_func); + } + for(i = 0; i < rt->class_count; i++) { + JS_MarkValue(rt, ctx->class_proto[i], mark_func); + } + JS_MarkValue(rt, ctx->iterator_proto, mark_func); + JS_MarkValue(rt, ctx->async_iterator_proto, mark_func); + JS_MarkValue(rt, ctx->promise_ctor, mark_func); + JS_MarkValue(rt, ctx->array_ctor, mark_func); + JS_MarkValue(rt, ctx->regexp_ctor, mark_func); + JS_MarkValue(rt, ctx->function_ctor, mark_func); + JS_MarkValue(rt, ctx->function_proto, mark_func); + + if (ctx->array_shape) + mark_func(rt, &ctx->array_shape->header); +} + +void JS_FreeContext(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + int i; + + if (--ctx->header.ref_count > 0) + return; + assert(ctx->header.ref_count == 0); + +#ifdef DUMP_ATOMS + JS_DumpAtoms(ctx->rt); +#endif +#ifdef DUMP_SHAPES + JS_DumpShapes(ctx->rt); +#endif +#ifdef DUMP_OBJECTS + { + struct list_head *el; + JSGCObjectHeader *p; + printf("JSObjects: {\n"); + JS_DumpObjectHeader(ctx->rt); + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + JS_DumpGCObject(rt, p); + } + printf("}\n"); + } +#endif +#ifdef DUMP_MEM + { + JSMemoryUsage stats; + JS_ComputeMemoryUsage(rt, &stats); + JS_DumpMemoryUsage(stdout, &stats, rt); + } +#endif + + js_free_modules(ctx, JS_FREE_MODULE_ALL); + + JS_FreeValue(ctx, ctx->global_obj); + JS_FreeValue(ctx, ctx->global_var_obj); + + JS_FreeValue(ctx, ctx->throw_type_error); + JS_FreeValue(ctx, ctx->eval_obj); + + JS_FreeValue(ctx, ctx->array_proto_values); + for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + JS_FreeValue(ctx, ctx->native_error_proto[i]); + } + for(i = 0; i < rt->class_count; i++) { + JS_FreeValue(ctx, ctx->class_proto[i]); + } + js_free_rt(rt, ctx->class_proto); + JS_FreeValue(ctx, ctx->iterator_proto); + JS_FreeValue(ctx, ctx->async_iterator_proto); + JS_FreeValue(ctx, ctx->promise_ctor); + JS_FreeValue(ctx, ctx->array_ctor); + JS_FreeValue(ctx, ctx->regexp_ctor); + JS_FreeValue(ctx, ctx->function_ctor); + JS_FreeValue(ctx, ctx->function_proto); + + js_free_shape_null(ctx->rt, ctx->array_shape); + + list_del(&ctx->link); + remove_gc_object(&ctx->header); + js_free_rt(ctx->rt, ctx); +} + +JSRuntime *JS_GetRuntime(JSContext *ctx) +{ + return ctx->rt; +} + +static void update_stack_limit(JSRuntime *rt) +{ + if (rt->stack_size == 0) { + rt->stack_limit = 0; /* no limit */ + } else { + rt->stack_limit = rt->stack_top - rt->stack_size; + } +} + +void JS_SetMaxStackSize(JSRuntime *rt, size_t stack_size) +{ + rt->stack_size = stack_size; + update_stack_limit(rt); +} + +void JS_UpdateStackTop(JSRuntime *rt) +{ + rt->stack_top = js_get_stack_pointer(); + update_stack_limit(rt); +} + +static inline BOOL is_strict_mode(JSContext *ctx) +{ + JSStackFrame *sf = ctx->rt->current_stack_frame; + return (sf && (sf->js_mode & JS_MODE_STRICT)); +} + +#ifdef CONFIG_BIGNUM +static inline BOOL is_math_mode(JSContext *ctx) +{ + JSStackFrame *sf = ctx->rt->current_stack_frame; + return (sf && (sf->js_mode & JS_MODE_MATH)); +} +#else +static inline BOOL is_math_mode(JSContext *ctx) +{ + return FALSE; +} +#endif + +/* JSAtom support */ + +#define JS_ATOM_TAG_INT (1U << 31) +#define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1) +#define JS_ATOM_MAX ((1U << 30) - 1) + +/* return the max count from the hash size */ +#define JS_ATOM_COUNT_RESIZE(n) ((n) * 2) + +static inline BOOL __JS_AtomIsConst(JSAtom v) +{ +#if defined(DUMP_LEAKS) && DUMP_LEAKS > 1 + return (int32_t)v <= 0; +#else + return (int32_t)v < JS_ATOM_END; +#endif +} + +static inline BOOL __JS_AtomIsTaggedInt(JSAtom v) +{ + return (v & JS_ATOM_TAG_INT) != 0; +} + +static inline JSAtom __JS_AtomFromUInt32(uint32_t v) +{ + return v | JS_ATOM_TAG_INT; +} + +static inline uint32_t __JS_AtomToUInt32(JSAtom atom) +{ + return atom & ~JS_ATOM_TAG_INT; +} + +static inline int is_num(int c) +{ + return c >= '0' && c <= '9'; +} + +/* return TRUE if the string is a number n with 0 <= n <= 2^32-1 */ +static inline BOOL is_num_string(uint32_t *pval, const JSString *p) +{ + uint32_t n; + uint64_t n64; + int c, i, len; + + len = p->len; + if (len == 0 || len > 10) + return FALSE; + c = string_get(p, 0); + if (is_num(c)) { + if (c == '0') { + if (len != 1) + return FALSE; + n = 0; + } else { + n = c - '0'; + for(i = 1; i < len; i++) { + c = string_get(p, i); + if (!is_num(c)) + return FALSE; + n64 = (uint64_t)n * 10 + (c - '0'); + if ((n64 >> 32) != 0) + return FALSE; + n = n64; + } + } + *pval = n; + return TRUE; + } else { + return FALSE; + } +} + +/* XXX: could use faster version ? */ +static inline uint32_t hash_string8(const uint8_t *str, size_t len, uint32_t h) +{ + size_t i; + + for(i = 0; i < len; i++) + h = h * 263 + str[i]; + return h; +} + +static inline uint32_t hash_string16(const uint16_t *str, + size_t len, uint32_t h) +{ + size_t i; + + for(i = 0; i < len; i++) + h = h * 263 + str[i]; + return h; +} + +static uint32_t hash_string(const JSString *str, uint32_t h) +{ + if (str->is_wide_char) + h = hash_string16(str->u.str16, str->len, h); + else + h = hash_string8(str->u.str8, str->len, h); + return h; +} + +static __maybe_unused void JS_DumpChar(JSRuntime *rt, int c, int sep) +{ + if (c == sep || c == '\\') { + putchar('\\'); + putchar(c); + } else if (c >= ' ' && c <= 126) { + putchar(c); + } else if (c == '\n') { + putchar('\\'); + putchar('n'); + } else { + printf("\\u%04x", c); + } +} + +static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p) +{ + int i, sep; + + if (p == NULL) { + printf(""); + return; + } + printf("%d", p->header.ref_count); + sep = (p->header.ref_count == 1) ? '\"' : '\''; + putchar(sep); + for(i = 0; i < p->len; i++) { + JS_DumpChar(rt, string_get(p, i), sep); + } + putchar(sep); +} + +static __maybe_unused void JS_DumpAtoms(JSRuntime *rt) +{ + JSAtomStruct *p; + int h, i; + /* This only dumps hashed atoms, not JS_ATOM_TYPE_SYMBOL atoms */ + printf("JSAtom count=%d size=%d hash_size=%d:\n", + rt->atom_count, rt->atom_size, rt->atom_hash_size); + printf("JSAtom hash table: {\n"); + for(i = 0; i < rt->atom_hash_size; i++) { + h = rt->atom_hash[i]; + if (h) { + printf(" %d:", i); + while (h) { + p = rt->atom_array[h]; + printf(" "); + JS_DumpString(rt, p); + h = p->hash_next; + } + printf("\n"); + } + } + printf("}\n"); + printf("JSAtom table: {\n"); + for(i = 0; i < rt->atom_size; i++) { + p = rt->atom_array[i]; + if (!atom_is_free(p)) { + printf(" %d: { %d %08x ", i, p->atom_type, p->hash); + if (!(p->len == 0 && p->is_wide_char != 0)) + JS_DumpString(rt, p); + printf(" %d }\n", p->hash_next); + } + } + printf("}\n"); +} + +static int JS_ResizeAtomHash(JSRuntime *rt, int new_hash_size) +{ + JSAtomStruct *p; + uint32_t new_hash_mask, h, i, hash_next1, j, *new_hash; + + assert((new_hash_size & (new_hash_size - 1)) == 0); /* power of two */ + new_hash_mask = new_hash_size - 1; + new_hash = js_mallocz_rt(rt, sizeof(rt->atom_hash[0]) * new_hash_size); + if (!new_hash) + return -1; + for(i = 0; i < rt->atom_hash_size; i++) { + h = rt->atom_hash[i]; + while (h != 0) { + p = rt->atom_array[h]; + hash_next1 = p->hash_next; + /* add in new hash table */ + j = p->hash & new_hash_mask; + p->hash_next = new_hash[j]; + new_hash[j] = h; + h = hash_next1; + } + } + js_free_rt(rt, rt->atom_hash); + rt->atom_hash = new_hash; + rt->atom_hash_size = new_hash_size; + rt->atom_count_resize = JS_ATOM_COUNT_RESIZE(new_hash_size); + // JS_DumpAtoms(rt); + return 0; +} + +static int JS_InitAtoms(JSRuntime *rt) +{ + int i, len, atom_type; + const char *p; + + rt->atom_hash_size = 0; + rt->atom_hash = NULL; + rt->atom_count = 0; + rt->atom_size = 0; + rt->atom_free_index = 0; + if (JS_ResizeAtomHash(rt, 256)) /* there are at least 195 predefined atoms */ + return -1; + + p = js_atom_init; + for(i = 1; i < JS_ATOM_END; i++) { + if (i == JS_ATOM_Private_brand) + atom_type = JS_ATOM_TYPE_PRIVATE; + else if (i >= JS_ATOM_Symbol_toPrimitive) + atom_type = JS_ATOM_TYPE_SYMBOL; + else + atom_type = JS_ATOM_TYPE_STRING; + len = strlen(p); + if (__JS_NewAtomInit(rt, p, len, atom_type) == JS_ATOM_NULL) + return -1; + p = p + len + 1; + } + return 0; +} + +static JSAtom JS_DupAtomRT(JSRuntime *rt, JSAtom v) +{ + JSAtomStruct *p; + + if (!__JS_AtomIsConst(v)) { + p = rt->atom_array[v]; + p->header.ref_count++; + } + return v; +} + +JSAtom JS_DupAtom(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + if (!__JS_AtomIsConst(v)) { + rt = ctx->rt; + p = rt->atom_array[v]; + p->header.ref_count++; + } + return v; +} + +static JSAtomKindEnum JS_AtomGetKind(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + rt = ctx->rt; + if (__JS_AtomIsTaggedInt(v)) + return JS_ATOM_KIND_STRING; + p = rt->atom_array[v]; + switch(p->atom_type) { + case JS_ATOM_TYPE_STRING: + return JS_ATOM_KIND_STRING; + case JS_ATOM_TYPE_GLOBAL_SYMBOL: + return JS_ATOM_KIND_SYMBOL; + case JS_ATOM_TYPE_SYMBOL: + switch(p->hash) { + case JS_ATOM_HASH_SYMBOL: + return JS_ATOM_KIND_SYMBOL; + case JS_ATOM_HASH_PRIVATE: + return JS_ATOM_KIND_PRIVATE; + default: + abort(); + } + default: + abort(); + } +} + +static BOOL JS_AtomIsString(JSContext *ctx, JSAtom v) +{ + return JS_AtomGetKind(ctx, v) == JS_ATOM_KIND_STRING; +} + +static JSAtom js_get_atom_index(JSRuntime *rt, JSAtomStruct *p) +{ + uint32_t i = p->hash_next; /* atom_index */ + if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { + JSAtomStruct *p1; + + i = rt->atom_hash[p->hash & (rt->atom_hash_size - 1)]; + p1 = rt->atom_array[i]; + while (p1 != p) { + assert(i != 0); + i = p1->hash_next; + p1 = rt->atom_array[i]; + } + } + return i; +} + +/* string case (internal). Return JS_ATOM_NULL if error. 'str' is + freed. */ +static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) +{ + uint32_t h, h1, i; + JSAtomStruct *p; + int len; + +#if 0 + printf("__JS_NewAtom: "); JS_DumpString(rt, str); printf("\n"); +#endif + if (atom_type < JS_ATOM_TYPE_SYMBOL) { + /* str is not NULL */ + if (str->atom_type == atom_type) { + /* str is the atom, return its index */ + i = js_get_atom_index(rt, str); + /* reduce string refcount and increase atom's unless constant */ + if (__JS_AtomIsConst(i)) + str->header.ref_count--; + return i; + } + /* try and locate an already registered atom */ + len = str->len; + h = hash_string(str, atom_type); + h &= JS_ATOM_HASH_MASK; + h1 = h & (rt->atom_hash_size - 1); + i = rt->atom_hash[h1]; + while (i != 0) { + p = rt->atom_array[i]; + if (p->hash == h && + p->atom_type == atom_type && + p->len == len && + js_string_memcmp(p, str, len) == 0) { + if (!__JS_AtomIsConst(i)) + p->header.ref_count++; + goto done; + } + i = p->hash_next; + } + } else { + h1 = 0; /* avoid warning */ + if (atom_type == JS_ATOM_TYPE_SYMBOL) { + h = JS_ATOM_HASH_SYMBOL; + } else { + h = JS_ATOM_HASH_PRIVATE; + atom_type = JS_ATOM_TYPE_SYMBOL; + } + } + + if (rt->atom_free_index == 0) { + /* allow new atom entries */ + uint32_t new_size, start; + JSAtomStruct **new_array; + + /* alloc new with size progression 3/2: + 4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092 + preallocating space for predefined atoms (at least 195). + */ + new_size = max_int(211, rt->atom_size * 3 / 2); + if (new_size > JS_ATOM_MAX) + goto fail; + /* XXX: should use realloc2 to use slack space */ + new_array = js_realloc_rt(rt, rt->atom_array, sizeof(*new_array) * new_size); + if (!new_array) + goto fail; + /* Note: the atom 0 is not used */ + start = rt->atom_size; + if (start == 0) { + /* JS_ATOM_NULL entry */ + p = js_mallocz_rt(rt, sizeof(JSAtomStruct)); + if (!p) { + js_free_rt(rt, new_array); + goto fail; + } + p->header.ref_count = 1; /* not refcounted */ + p->atom_type = JS_ATOM_TYPE_SYMBOL; +#ifdef DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + new_array[0] = p; + rt->atom_count++; + start = 1; + } + rt->atom_size = new_size; + rt->atom_array = new_array; + rt->atom_free_index = start; + for(i = start; i < new_size; i++) { + uint32_t next; + if (i == (new_size - 1)) + next = 0; + else + next = i + 1; + rt->atom_array[i] = atom_set_free(next); + } + } + + if (str) { + if (str->atom_type == 0) { + p = str; + p->atom_type = atom_type; + } else { + p = js_malloc_rt(rt, sizeof(JSString) + + (str->len << str->is_wide_char) + + 1 - str->is_wide_char); + if (unlikely(!p)) + goto fail; + p->header.ref_count = 1; + p->is_wide_char = str->is_wide_char; + p->len = str->len; +#ifdef DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + memcpy(p->u.str8, str->u.str8, (str->len << str->is_wide_char) + + 1 - str->is_wide_char); + js_free_string(rt, str); + } + } else { + p = js_malloc_rt(rt, sizeof(JSAtomStruct)); /* empty wide string */ + if (!p) + return JS_ATOM_NULL; + p->header.ref_count = 1; + p->is_wide_char = 1; /* Hack to represent NULL as a JSString */ + p->len = 0; +#ifdef DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + } + + /* use an already free entry */ + i = rt->atom_free_index; + rt->atom_free_index = atom_get_free(rt->atom_array[i]); + rt->atom_array[i] = p; + + p->hash = h; + p->hash_next = i; /* atom_index */ + p->atom_type = atom_type; + + rt->atom_count++; + + if (atom_type != JS_ATOM_TYPE_SYMBOL) { + p->hash_next = rt->atom_hash[h1]; + rt->atom_hash[h1] = i; + if (unlikely(rt->atom_count >= rt->atom_count_resize)) + JS_ResizeAtomHash(rt, rt->atom_hash_size * 2); + } + + // JS_DumpAtoms(rt); + return i; + + fail: + i = JS_ATOM_NULL; + done: + if (str) + js_free_string(rt, str); + return i; +} + +/* only works with zero terminated 8 bit strings */ +static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, + int atom_type) +{ + JSString *p; + p = js_alloc_string_rt(rt, len, 0); + if (!p) + return JS_ATOM_NULL; + memcpy(p->u.str8, str, len); + p->u.str8[len] = '\0'; + return __JS_NewAtom(rt, p, atom_type); +} + +/* Warning: str must be ASCII only */ +static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len, + int atom_type) +{ + uint32_t h, h1, i; + JSAtomStruct *p; + + h = hash_string8((const uint8_t *)str, len, JS_ATOM_TYPE_STRING); + h &= JS_ATOM_HASH_MASK; + h1 = h & (rt->atom_hash_size - 1); + i = rt->atom_hash[h1]; + while (i != 0) { + p = rt->atom_array[i]; + if (p->hash == h && + p->atom_type == JS_ATOM_TYPE_STRING && + p->len == len && + p->is_wide_char == 0 && + memcmp(p->u.str8, str, len) == 0) { + if (!__JS_AtomIsConst(i)) + p->header.ref_count++; + return i; + } + i = p->hash_next; + } + return JS_ATOM_NULL; +} + +static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p) +{ +#if 0 /* JS_ATOM_NULL is not refcounted: __JS_AtomIsConst() includes 0 */ + if (unlikely(i == JS_ATOM_NULL)) { + p->header.ref_count = INT32_MAX / 2; + return; + } +#endif + uint32_t i = p->hash_next; /* atom_index */ + if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { + JSAtomStruct *p0, *p1; + uint32_t h0; + + h0 = p->hash & (rt->atom_hash_size - 1); + i = rt->atom_hash[h0]; + p1 = rt->atom_array[i]; + if (p1 == p) { + rt->atom_hash[h0] = p1->hash_next; + } else { + for(;;) { + assert(i != 0); + p0 = p1; + i = p1->hash_next; + p1 = rt->atom_array[i]; + if (p1 == p) { + p0->hash_next = p1->hash_next; + break; + } + } + } + } + /* insert in free atom list */ + rt->atom_array[i] = atom_set_free(rt->atom_free_index); + rt->atom_free_index = i; + /* free the string structure */ +#ifdef DUMP_LEAKS + list_del(&p->link); +#endif + js_free_rt(rt, p); + rt->atom_count--; + assert(rt->atom_count >= 0); +} + +static void __JS_FreeAtom(JSRuntime *rt, uint32_t i) +{ + JSAtomStruct *p; + + p = rt->atom_array[i]; + if (--p->header.ref_count > 0) + return; + JS_FreeAtomStruct(rt, p); +} + +/* Warning: 'p' is freed */ +static JSAtom JS_NewAtomStr(JSContext *ctx, JSString *p) +{ + JSRuntime *rt = ctx->rt; + uint32_t n; + if (is_num_string(&n, p)) { + if (n <= JS_ATOM_MAX_INT) { + js_free_string(rt, p); + return __JS_AtomFromUInt32(n); + } + } + /* XXX: should generate an exception */ + return __JS_NewAtom(rt, p, JS_ATOM_TYPE_STRING); +} + +/* str is UTF-8 encoded */ +JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len) +{ + JSValue val; + + if (len == 0 || !is_digit(*str)) { + // XXX: this will not work if UTF-8 encoded str contains non ASCII bytes + JSAtom atom = __JS_FindAtom(ctx->rt, str, len, JS_ATOM_TYPE_STRING); + if (atom) + return atom; + } + val = JS_NewStringLen(ctx, str, len); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(val)); +} + +JSAtom JS_NewAtom(JSContext *ctx, const char *str) +{ + return JS_NewAtomLen(ctx, str, strlen(str)); +} + +JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n) +{ + if (n <= JS_ATOM_MAX_INT) { + return __JS_AtomFromUInt32(n); + } else { + char buf[11]; + JSValue val; + snprintf(buf, sizeof(buf), "%u", n); + val = JS_NewString(ctx, buf); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), + JS_ATOM_TYPE_STRING); + } +} + +static JSAtom JS_NewAtomInt64(JSContext *ctx, int64_t n) +{ + if ((uint64_t)n <= JS_ATOM_MAX_INT) { + return __JS_AtomFromUInt32((uint32_t)n); + } else { + char buf[24]; + JSValue val; + snprintf(buf, sizeof(buf), "%" PRId64 , n); + val = JS_NewString(ctx, buf); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), + JS_ATOM_TYPE_STRING); + } +} + +/* 'p' is freed */ +static JSValue JS_NewSymbol(JSContext *ctx, JSString *p, int atom_type) +{ + JSRuntime *rt = ctx->rt; + JSAtom atom; + atom = __JS_NewAtom(rt, p, atom_type); + if (atom == JS_ATOM_NULL) + return JS_ThrowOutOfMemory(ctx); + return JS_MKPTR(JS_TAG_SYMBOL, rt->atom_array[atom]); +} + +/* descr must be a non-numeric string atom */ +static JSValue JS_NewSymbolFromAtom(JSContext *ctx, JSAtom descr, + int atom_type) +{ + JSRuntime *rt = ctx->rt; + JSString *p; + + assert(!__JS_AtomIsTaggedInt(descr)); + assert(descr < rt->atom_size); + p = rt->atom_array[descr]; + JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); + return JS_NewSymbol(ctx, p, atom_type); +} + +#define ATOM_GET_STR_BUF_SIZE 64 + +/* Should only be used for debug. */ +static const char *JS_AtomGetStrRT(JSRuntime *rt, char *buf, int buf_size, + JSAtom atom) +{ + if (__JS_AtomIsTaggedInt(atom)) { + snprintf(buf, buf_size, "%u", __JS_AtomToUInt32(atom)); + } else { + JSAtomStruct *p; + assert(atom < rt->atom_size); + if (atom == JS_ATOM_NULL) { + snprintf(buf, buf_size, ""); + } else { + int i, c; + char *q; + JSString *str; + + q = buf; + p = rt->atom_array[atom]; + assert(!atom_is_free(p)); + str = p; + if (str) { + if (!str->is_wide_char) { + /* special case ASCII strings */ + c = 0; + for(i = 0; i < str->len; i++) { + c |= str->u.str8[i]; + } + if (c < 0x80) + return (const char *)str->u.str8; + } + for(i = 0; i < str->len; i++) { + c = string_get(str, i); + if ((q - buf) >= buf_size - UTF8_CHAR_LEN_MAX) + break; + if (c < 128) { + *q++ = c; + } else { + q += unicode_to_utf8((uint8_t *)q, c); + } + } + } + *q = '\0'; + } + } + return buf; +} + +static const char *JS_AtomGetStr(JSContext *ctx, char *buf, int buf_size, JSAtom atom) +{ + return JS_AtomGetStrRT(ctx->rt, buf, buf_size, atom); +} + +static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, BOOL force_string) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + + if (__JS_AtomIsTaggedInt(atom)) { + snprintf(buf, sizeof(buf), "%u", __JS_AtomToUInt32(atom)); + return JS_NewString(ctx, buf); + } else { + JSRuntime *rt = ctx->rt; + JSAtomStruct *p; + assert(atom < rt->atom_size); + p = rt->atom_array[atom]; + if (p->atom_type == JS_ATOM_TYPE_STRING) { + goto ret_string; + } else if (force_string) { + if (p->len == 0 && p->is_wide_char != 0) { + /* no description string */ + p = rt->atom_array[JS_ATOM_empty_string]; + } + ret_string: + return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); + } else { + return JS_DupValue(ctx, JS_MKPTR(JS_TAG_SYMBOL, p)); + } + } +} + +JSValue JS_AtomToValue(JSContext *ctx, JSAtom atom) +{ + return __JS_AtomToValue(ctx, atom, FALSE); +} + +JSValue JS_AtomToString(JSContext *ctx, JSAtom atom) +{ + return __JS_AtomToValue(ctx, atom, TRUE); +} + +/* return TRUE if the atom is an array index (i.e. 0 <= index <= + 2^32-2 and return its value */ +static BOOL JS_AtomIsArrayIndex(JSContext *ctx, uint32_t *pval, JSAtom atom) +{ + if (__JS_AtomIsTaggedInt(atom)) { + *pval = __JS_AtomToUInt32(atom); + return TRUE; + } else { + JSRuntime *rt = ctx->rt; + JSAtomStruct *p; + uint32_t val; + + assert(atom < rt->atom_size); + p = rt->atom_array[atom]; + if (p->atom_type == JS_ATOM_TYPE_STRING && + is_num_string(&val, p) && val != -1) { + *pval = val; + return TRUE; + } else { + *pval = 0; + return FALSE; + } + } +} + +/* This test must be fast if atom is not a numeric index (e.g. a + method name). Return JS_UNDEFINED if not a numeric + index. JS_EXCEPTION can also be returned. */ +static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom) +{ + JSRuntime *rt = ctx->rt; + JSAtomStruct *p1; + JSString *p; + int c, len, ret; + JSValue num, str; + + if (__JS_AtomIsTaggedInt(atom)) + return JS_NewInt32(ctx, __JS_AtomToUInt32(atom)); + assert(atom < rt->atom_size); + p1 = rt->atom_array[atom]; + if (p1->atom_type != JS_ATOM_TYPE_STRING) + return JS_UNDEFINED; + p = p1; + len = p->len; + if (p->is_wide_char) { + const uint16_t *r = p->u.str16, *r_end = p->u.str16 + len; + if (r >= r_end) + return JS_UNDEFINED; + c = *r; + if (c == '-') { + if (r >= r_end) + return JS_UNDEFINED; + r++; + c = *r; + /* -0 case is specific */ + if (c == '0' && len == 2) + goto minus_zero; + } + /* XXX: should test NaN, but the tests do not check it */ + if (!is_num(c)) { + /* XXX: String should be normalized, therefore 8-bit only */ + const uint16_t nfinity16[7] = { 'n', 'f', 'i', 'n', 'i', 't', 'y' }; + if (!(c =='I' && (r_end - r) == 8 && + !memcmp(r + 1, nfinity16, sizeof(nfinity16)))) + return JS_UNDEFINED; + } + } else { + const uint8_t *r = p->u.str8, *r_end = p->u.str8 + len; + if (r >= r_end) + return JS_UNDEFINED; + c = *r; + if (c == '-') { + if (r >= r_end) + return JS_UNDEFINED; + r++; + c = *r; + /* -0 case is specific */ + if (c == '0' && len == 2) { + minus_zero: + return __JS_NewFloat64(ctx, -0.0); + } + } + if (!is_num(c)) { + if (!(c =='I' && (r_end - r) == 8 && + !memcmp(r + 1, "nfinity", 7))) + return JS_UNDEFINED; + } + } + /* XXX: bignum: would be better to only accept integer to avoid + relying on current floating point precision */ + /* this is ECMA CanonicalNumericIndexString primitive */ + num = JS_ToNumber(ctx, JS_MKPTR(JS_TAG_STRING, p)); + if (JS_IsException(num)) + return num; + str = JS_ToString(ctx, num); + if (JS_IsException(str)) { + JS_FreeValue(ctx, num); + return str; + } + ret = js_string_compare(ctx, p, JS_VALUE_GET_STRING(str)); + JS_FreeValue(ctx, str); + if (ret == 0) { + return num; + } else { + JS_FreeValue(ctx, num); + return JS_UNDEFINED; + } +} + +/* return -1 if exception or TRUE/FALSE */ +static int JS_AtomIsNumericIndex(JSContext *ctx, JSAtom atom) +{ + JSValue num; + num = JS_AtomIsNumericIndex1(ctx, atom); + if (likely(JS_IsUndefined(num))) + return FALSE; + if (JS_IsException(num)) + return -1; + JS_FreeValue(ctx, num); + return TRUE; +} + +void JS_FreeAtom(JSContext *ctx, JSAtom v) +{ + if (!__JS_AtomIsConst(v)) + __JS_FreeAtom(ctx->rt, v); +} + +void JS_FreeAtomRT(JSRuntime *rt, JSAtom v) +{ + if (!__JS_AtomIsConst(v)) + __JS_FreeAtom(rt, v); +} + +/* return TRUE if 'v' is a symbol with a string description */ +static BOOL JS_AtomSymbolHasDescription(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + rt = ctx->rt; + if (__JS_AtomIsTaggedInt(v)) + return FALSE; + p = rt->atom_array[v]; + return (((p->atom_type == JS_ATOM_TYPE_SYMBOL && + p->hash == JS_ATOM_HASH_SYMBOL) || + p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) && + !(p->len == 0 && p->is_wide_char != 0)); +} + +static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + const char *p; + int i; + + /* XXX: should handle embedded null characters */ + /* XXX: should move encoding code to JS_AtomGetStr */ + p = JS_AtomGetStr(ctx, buf, sizeof(buf), atom); + for (i = 0; p[i]; i++) { + int c = (unsigned char)p[i]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) + break; + } + if (i > 0 && p[i] == '\0') { + printf("%s", p); + } else { + putchar('"'); + printf("%.*s", i, p); + for (; p[i]; i++) { + int c = (unsigned char)p[i]; + if (c == '\"' || c == '\\') { + putchar('\\'); + putchar(c); + } else if (c >= ' ' && c <= 126) { + putchar(c); + } else if (c == '\n') { + putchar('\\'); + putchar('n'); + } else { + printf("\\u%04x", c); + } + } + putchar('\"'); + } +} + +/* free with JS_FreeCString() */ +const char *JS_AtomToCString(JSContext *ctx, JSAtom atom) +{ + JSValue str; + const char *cstr; + + str = JS_AtomToString(ctx, atom); + if (JS_IsException(str)) + return NULL; + cstr = JS_ToCString(ctx, str); + JS_FreeValue(ctx, str); + return cstr; +} + +/* return a string atom containing name concatenated with str1 */ +static JSAtom js_atom_concat_str(JSContext *ctx, JSAtom name, const char *str1) +{ + JSValue str; + JSAtom atom; + const char *cstr; + char *cstr2; + size_t len, len1; + + str = JS_AtomToString(ctx, name); + if (JS_IsException(str)) + return JS_ATOM_NULL; + cstr = JS_ToCStringLen(ctx, &len, str); + if (!cstr) + goto fail; + len1 = strlen(str1); + cstr2 = js_malloc(ctx, len + len1 + 1); + if (!cstr2) + goto fail; + memcpy(cstr2, cstr, len); + memcpy(cstr2 + len, str1, len1); + cstr2[len + len1] = '\0'; + atom = JS_NewAtomLen(ctx, cstr2, len + len1); + js_free(ctx, cstr2); + JS_FreeCString(ctx, cstr); + JS_FreeValue(ctx, str); + return atom; + fail: + JS_FreeCString(ctx, cstr); + JS_FreeValue(ctx, str); + return JS_ATOM_NULL; +} + +static JSAtom js_atom_concat_num(JSContext *ctx, JSAtom name, uint32_t n) +{ + char buf[16]; + snprintf(buf, sizeof(buf), "%u", n); + return js_atom_concat_str(ctx, name, buf); +} + +static inline BOOL JS_IsEmptyString(JSValueConst v) +{ + return JS_VALUE_GET_TAG(v) == JS_TAG_STRING && JS_VALUE_GET_STRING(v)->len == 0; +} + +/* JSClass support */ + +#ifdef CONFIG_ATOMICS +static pthread_mutex_t js_class_id_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +/* a new class ID is allocated if *pclass_id != 0 */ +JSClassID JS_NewClassID(JSClassID *pclass_id) +{ + JSClassID class_id; +#ifdef CONFIG_ATOMICS + pthread_mutex_lock(&js_class_id_mutex); +#endif + class_id = *pclass_id; + if (class_id == 0) { + class_id = js_class_id_alloc++; + *pclass_id = class_id; + } +#ifdef CONFIG_ATOMICS + pthread_mutex_unlock(&js_class_id_mutex); +#endif + return class_id; +} + +JSClassID JS_GetClassID(JSValue v) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(v) != JS_TAG_OBJECT) + return JS_INVALID_CLASS_ID; + p = JS_VALUE_GET_OBJ(v); + return p->class_id; +} + +BOOL JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id) +{ + return (class_id < rt->class_count && + rt->class_array[class_id].class_id != 0); +} + +/* create a new object internal class. Return -1 if error, 0 if + OK. The finalizer can be NULL if none is needed. */ +static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, + const JSClassDef *class_def, JSAtom name) +{ + int new_size, i; + JSClass *cl, *new_class_array; + struct list_head *el; + + if (class_id >= (1 << 16)) + return -1; + if (class_id < rt->class_count && + rt->class_array[class_id].class_id != 0) + return -1; + + if (class_id >= rt->class_count) { + new_size = max_int(JS_CLASS_INIT_COUNT, + max_int(class_id + 1, rt->class_count * 3 / 2)); + + /* reallocate the context class prototype array, if any */ + list_for_each(el, &rt->context_list) { + JSContext *ctx = list_entry(el, JSContext, link); + JSValue *new_tab; + new_tab = js_realloc_rt(rt, ctx->class_proto, + sizeof(ctx->class_proto[0]) * new_size); + if (!new_tab) + return -1; + for(i = rt->class_count; i < new_size; i++) + new_tab[i] = JS_NULL; + ctx->class_proto = new_tab; + } + /* reallocate the class array */ + new_class_array = js_realloc_rt(rt, rt->class_array, + sizeof(JSClass) * new_size); + if (!new_class_array) + return -1; + memset(new_class_array + rt->class_count, 0, + (new_size - rt->class_count) * sizeof(JSClass)); + rt->class_array = new_class_array; + rt->class_count = new_size; + } + cl = &rt->class_array[class_id]; + cl->class_id = class_id; + cl->class_name = JS_DupAtomRT(rt, name); + cl->finalizer = class_def->finalizer; + cl->gc_mark = class_def->gc_mark; + cl->call = class_def->call; + cl->exotic = class_def->exotic; + return 0; +} + +int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def) +{ + int ret, len; + JSAtom name; + + len = strlen(class_def->class_name); + name = __JS_FindAtom(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); + if (name == JS_ATOM_NULL) { + name = __JS_NewAtomInit(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); + if (name == JS_ATOM_NULL) + return -1; + } + ret = JS_NewClass1(rt, class_id, class_def, name); + JS_FreeAtomRT(rt, name); + return ret; +} + +static JSValue js_new_string8(JSContext *ctx, const uint8_t *buf, int len) +{ + JSString *str; + + if (len <= 0) { + return JS_AtomToString(ctx, JS_ATOM_empty_string); + } + str = js_alloc_string(ctx, len, 0); + if (!str) + return JS_EXCEPTION; + memcpy(str->u.str8, buf, len); + str->u.str8[len] = '\0'; + return JS_MKPTR(JS_TAG_STRING, str); +} + +static JSValue js_new_string16(JSContext *ctx, const uint16_t *buf, int len) +{ + JSString *str; + str = js_alloc_string(ctx, len, 1); + if (!str) + return JS_EXCEPTION; + memcpy(str->u.str16, buf, len * 2); + return JS_MKPTR(JS_TAG_STRING, str); +} + +static JSValue js_new_string_char(JSContext *ctx, uint16_t c) +{ + if (c < 0x100) { + uint8_t ch8 = c; + return js_new_string8(ctx, &ch8, 1); + } else { + uint16_t ch16 = c; + return js_new_string16(ctx, &ch16, 1); + } +} + +static JSValue js_sub_string(JSContext *ctx, JSString *p, int start, int end) +{ + int len = end - start; + if (start == 0 && end == p->len) { + return JS_DupValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); + } + if (p->is_wide_char && len > 0) { + JSString *str; + int i; + uint16_t c = 0; + for (i = start; i < end; i++) { + c |= p->u.str16[i]; + } + if (c > 0xFF) + return js_new_string16(ctx, p->u.str16 + start, len); + + str = js_alloc_string(ctx, len, 0); + if (!str) + return JS_EXCEPTION; + for (i = 0; i < len; i++) { + str->u.str8[i] = p->u.str16[start + i]; + } + str->u.str8[len] = '\0'; + return JS_MKPTR(JS_TAG_STRING, str); + } else { + return js_new_string8(ctx, p->u.str8 + start, len); + } +} + +typedef struct StringBuffer { + JSContext *ctx; + JSString *str; + int len; + int size; + int is_wide_char; + int error_status; +} StringBuffer; + +/* It is valid to call string_buffer_end() and all string_buffer functions even + if string_buffer_init() or another string_buffer function returns an error. + If the error_status is set, string_buffer_end() returns JS_EXCEPTION. + */ +static int string_buffer_init2(JSContext *ctx, StringBuffer *s, int size, + int is_wide) +{ + s->ctx = ctx; + s->size = size; + s->len = 0; + s->is_wide_char = is_wide; + s->error_status = 0; + s->str = js_alloc_string(ctx, size, is_wide); + if (unlikely(!s->str)) { + s->size = 0; + return s->error_status = -1; + } +#ifdef DUMP_LEAKS + /* the StringBuffer may reallocate the JSString, only link it at the end */ + list_del(&s->str->link); +#endif + return 0; +} + +static inline int string_buffer_init(JSContext *ctx, StringBuffer *s, int size) +{ + return string_buffer_init2(ctx, s, size, 0); +} + +static void string_buffer_free(StringBuffer *s) +{ + js_free(s->ctx, s->str); + s->str = NULL; +} + +static int string_buffer_set_error(StringBuffer *s) +{ + js_free(s->ctx, s->str); + s->str = NULL; + s->size = 0; + s->len = 0; + return s->error_status = -1; +} + +static no_inline int string_buffer_widen(StringBuffer *s, int size) +{ + JSString *str; + size_t slack; + int i; + + if (s->error_status) + return -1; + + str = js_realloc2(s->ctx, s->str, sizeof(JSString) + (size << 1), &slack); + if (!str) + return string_buffer_set_error(s); + size += slack >> 1; + for(i = s->len; i-- > 0;) { + str->u.str16[i] = str->u.str8[i]; + } + s->is_wide_char = 1; + s->size = size; + s->str = str; + return 0; +} + +static no_inline int string_buffer_realloc(StringBuffer *s, int new_len, int c) +{ + JSString *new_str; + int new_size; + size_t new_size_bytes, slack; + + if (s->error_status) + return -1; + + if (new_len > JS_STRING_LEN_MAX) { + JS_ThrowInternalError(s->ctx, "string too long"); + return string_buffer_set_error(s); + } + new_size = min_int(max_int(new_len, s->size * 3 / 2), JS_STRING_LEN_MAX); + if (!s->is_wide_char && c >= 0x100) { + return string_buffer_widen(s, new_size); + } + new_size_bytes = sizeof(JSString) + (new_size << s->is_wide_char) + 1 - s->is_wide_char; + new_str = js_realloc2(s->ctx, s->str, new_size_bytes, &slack); + if (!new_str) + return string_buffer_set_error(s); + new_size = min_int(new_size + (slack >> s->is_wide_char), JS_STRING_LEN_MAX); + s->size = new_size; + s->str = new_str; + return 0; +} + +static no_inline int string_buffer_putc_slow(StringBuffer *s, uint32_t c) +{ + if (unlikely(s->len >= s->size)) { + if (string_buffer_realloc(s, s->len + 1, c)) + return -1; + } + if (s->is_wide_char) { + s->str->u.str16[s->len++] = c; + } else if (c < 0x100) { + s->str->u.str8[s->len++] = c; + } else { + if (string_buffer_widen(s, s->size)) + return -1; + s->str->u.str16[s->len++] = c; + } + return 0; +} + +/* 0 <= c <= 0xff */ +static int string_buffer_putc8(StringBuffer *s, uint32_t c) +{ + if (unlikely(s->len >= s->size)) { + if (string_buffer_realloc(s, s->len + 1, c)) + return -1; + } + if (s->is_wide_char) { + s->str->u.str16[s->len++] = c; + } else { + s->str->u.str8[s->len++] = c; + } + return 0; +} + +/* 0 <= c <= 0xffff */ +static int string_buffer_putc16(StringBuffer *s, uint32_t c) +{ + if (likely(s->len < s->size)) { + if (s->is_wide_char) { + s->str->u.str16[s->len++] = c; + return 0; + } else if (c < 0x100) { + s->str->u.str8[s->len++] = c; + return 0; + } + } + return string_buffer_putc_slow(s, c); +} + +/* 0 <= c <= 0x10ffff */ +static int string_buffer_putc(StringBuffer *s, uint32_t c) +{ + if (unlikely(c >= 0x10000)) { + /* surrogate pair */ + if (string_buffer_putc16(s, get_hi_surrogate(c))) + return -1; + c = get_lo_surrogate(c); + } + return string_buffer_putc16(s, c); +} + +static int string_getc(const JSString *p, int *pidx) +{ + int idx, c, c1; + idx = *pidx; + if (p->is_wide_char) { + c = p->u.str16[idx++]; + if (is_hi_surrogate(c) && idx < p->len) { + c1 = p->u.str16[idx]; + if (is_lo_surrogate(c1)) { + c = from_surrogate(c, c1); + idx++; + } + } + } else { + c = p->u.str8[idx++]; + } + *pidx = idx; + return c; +} + +static int string_buffer_write8(StringBuffer *s, const uint8_t *p, int len) +{ + int i; + + if (s->len + len > s->size) { + if (string_buffer_realloc(s, s->len + len, 0)) + return -1; + } + if (s->is_wide_char) { + for (i = 0; i < len; i++) { + s->str->u.str16[s->len + i] = p[i]; + } + s->len += len; + } else { + memcpy(&s->str->u.str8[s->len], p, len); + s->len += len; + } + return 0; +} + +static int string_buffer_write16(StringBuffer *s, const uint16_t *p, int len) +{ + int c = 0, i; + + for (i = 0; i < len; i++) { + c |= p[i]; + } + if (s->len + len > s->size) { + if (string_buffer_realloc(s, s->len + len, c)) + return -1; + } else if (!s->is_wide_char && c >= 0x100) { + if (string_buffer_widen(s, s->size)) + return -1; + } + if (s->is_wide_char) { + memcpy(&s->str->u.str16[s->len], p, len << 1); + s->len += len; + } else { + for (i = 0; i < len; i++) { + s->str->u.str8[s->len + i] = p[i]; + } + s->len += len; + } + return 0; +} + +/* appending an ASCII string */ +static int string_buffer_puts8(StringBuffer *s, const char *str) +{ + return string_buffer_write8(s, (const uint8_t *)str, strlen(str)); +} + +static int string_buffer_concat(StringBuffer *s, const JSString *p, + uint32_t from, uint32_t to) +{ + if (to <= from) + return 0; + if (p->is_wide_char) + return string_buffer_write16(s, p->u.str16 + from, to - from); + else + return string_buffer_write8(s, p->u.str8 + from, to - from); +} + +static int string_buffer_concat_value(StringBuffer *s, JSValueConst v) +{ + JSString *p; + JSValue v1; + int res; + + if (s->error_status) { + /* prevent exception overload */ + return -1; + } + if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { + v1 = JS_ToString(s->ctx, v); + if (JS_IsException(v1)) + return string_buffer_set_error(s); + p = JS_VALUE_GET_STRING(v1); + res = string_buffer_concat(s, p, 0, p->len); + JS_FreeValue(s->ctx, v1); + return res; + } + p = JS_VALUE_GET_STRING(v); + return string_buffer_concat(s, p, 0, p->len); +} + +static int string_buffer_concat_value_free(StringBuffer *s, JSValue v) +{ + JSString *p; + int res; + + if (s->error_status) { + /* prevent exception overload */ + JS_FreeValue(s->ctx, v); + return -1; + } + if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { + v = JS_ToStringFree(s->ctx, v); + if (JS_IsException(v)) + return string_buffer_set_error(s); + } + p = JS_VALUE_GET_STRING(v); + res = string_buffer_concat(s, p, 0, p->len); + JS_FreeValue(s->ctx, v); + return res; +} + +static int string_buffer_fill(StringBuffer *s, int c, int count) +{ + /* XXX: optimize */ + if (s->len + count > s->size) { + if (string_buffer_realloc(s, s->len + count, c)) + return -1; + } + while (count-- > 0) { + if (string_buffer_putc16(s, c)) + return -1; + } + return 0; +} + +static JSValue string_buffer_end(StringBuffer *s) +{ + JSString *str; + str = s->str; + if (s->error_status) + return JS_EXCEPTION; + if (s->len == 0) { + js_free(s->ctx, str); + s->str = NULL; + return JS_AtomToString(s->ctx, JS_ATOM_empty_string); + } + if (s->len < s->size) { + /* smaller size so js_realloc should not fail, but OK if it does */ + /* XXX: should add some slack to avoid unnecessary calls */ + /* XXX: might need to use malloc+free to ensure smaller size */ + str = js_realloc_rt(s->ctx->rt, str, sizeof(JSString) + + (s->len << s->is_wide_char) + 1 - s->is_wide_char); + if (str == NULL) + str = s->str; + s->str = str; + } + if (!s->is_wide_char) + str->u.str8[s->len] = 0; +#ifdef DUMP_LEAKS + list_add_tail(&str->link, &s->ctx->rt->string_list); +#endif + str->is_wide_char = s->is_wide_char; + str->len = s->len; + s->str = NULL; + return JS_MKPTR(JS_TAG_STRING, str); +} + +/* create a string from a UTF-8 buffer */ +JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t buf_len) +{ + const uint8_t *p, *p_end, *p_start, *p_next; + uint32_t c; + StringBuffer b_s, *b = &b_s; + size_t len1; + + p_start = (const uint8_t *)buf; + p_end = p_start + buf_len; + p = p_start; + while (p < p_end && *p < 128) + p++; + len1 = p - p_start; + if (len1 > JS_STRING_LEN_MAX) + return JS_ThrowInternalError(ctx, "string too long"); + if (p == p_end) { + /* ASCII string */ + return js_new_string8(ctx, (const uint8_t *)buf, buf_len); + } else { + if (string_buffer_init(ctx, b, buf_len)) + goto fail; + string_buffer_write8(b, p_start, len1); + while (p < p_end) { + if (*p < 128) { + string_buffer_putc8(b, *p++); + } else { + /* parse utf-8 sequence, return 0xFFFFFFFF for error */ + c = unicode_from_utf8(p, p_end - p, &p_next); + if (c < 0x10000) { + p = p_next; + } else if (c <= 0x10FFFF) { + p = p_next; + /* surrogate pair */ + string_buffer_putc16(b, get_hi_surrogate(c)); + c = get_lo_surrogate(c); + } else { + /* invalid char */ + c = 0xfffd; + /* skip the invalid chars */ + /* XXX: seems incorrect. Why not just use c = *p++; ? */ + while (p < p_end && (*p >= 0x80 && *p < 0xc0)) + p++; + if (p < p_end) { + p++; + while (p < p_end && (*p >= 0x80 && *p < 0xc0)) + p++; + } + } + string_buffer_putc16(b, c); + } + } + } + return string_buffer_end(b); + + fail: + string_buffer_free(b); + return JS_EXCEPTION; +} + +static JSValue JS_ConcatString3(JSContext *ctx, const char *str1, + JSValue str2, const char *str3) +{ + StringBuffer b_s, *b = &b_s; + int len1, len3; + JSString *p; + + if (unlikely(JS_VALUE_GET_TAG(str2) != JS_TAG_STRING)) { + str2 = JS_ToStringFree(ctx, str2); + if (JS_IsException(str2)) + goto fail; + } + p = JS_VALUE_GET_STRING(str2); + len1 = strlen(str1); + len3 = strlen(str3); + + if (string_buffer_init2(ctx, b, len1 + p->len + len3, p->is_wide_char)) + goto fail; + + string_buffer_write8(b, (const uint8_t *)str1, len1); + string_buffer_concat(b, p, 0, p->len); + string_buffer_write8(b, (const uint8_t *)str3, len3); + + JS_FreeValue(ctx, str2); + return string_buffer_end(b); + + fail: + JS_FreeValue(ctx, str2); + return JS_EXCEPTION; +} + +JSValue JS_NewString(JSContext *ctx, const char *str) +{ + return JS_NewStringLen(ctx, str, strlen(str)); +} + +JSValue JS_NewAtomString(JSContext *ctx, const char *str) +{ + JSAtom atom = JS_NewAtom(ctx, str); + if (atom == JS_ATOM_NULL) + return JS_EXCEPTION; + JSValue val = JS_AtomToString(ctx, atom); + JS_FreeAtom(ctx, atom); + return val; +} + +/* return (NULL, 0) if exception. */ +/* return pointer into a JSString with a live ref_count */ +/* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 sequences */ +const char *JS_ToCStringLen2(JSContext *ctx, size_t *plen, JSValueConst val1, BOOL cesu8) +{ + JSValue val; + JSString *str, *str_new; + int pos, len, c, c1; + uint8_t *q; + + if (JS_VALUE_GET_TAG(val1) != JS_TAG_STRING) { + val = JS_ToString(ctx, val1); + if (JS_IsException(val)) + goto fail; + } else { + val = JS_DupValue(ctx, val1); + } + + str = JS_VALUE_GET_STRING(val); + len = str->len; + if (!str->is_wide_char) { + const uint8_t *src = str->u.str8; + int count; + + /* count the number of non-ASCII characters */ + /* Scanning the whole string is required for ASCII strings, + and computing the number of non-ASCII bytes is less expensive + than testing each byte, hence this method is faster for ASCII + strings, which is the most common case. + */ + count = 0; + for (pos = 0; pos < len; pos++) { + count += src[pos] >> 7; + } + if (count == 0) { + if (plen) + *plen = len; + return (const char *)src; + } + str_new = js_alloc_string(ctx, len + count, 0); + if (!str_new) + goto fail; + q = str_new->u.str8; + for (pos = 0; pos < len; pos++) { + c = src[pos]; + if (c < 0x80) { + *q++ = c; + } else { + *q++ = (c >> 6) | 0xc0; + *q++ = (c & 0x3f) | 0x80; + } + } + } else { + const uint16_t *src = str->u.str16; + /* Allocate 3 bytes per 16 bit code point. Surrogate pairs may + produce 4 bytes but use 2 code points. + */ + str_new = js_alloc_string(ctx, len * 3, 0); + if (!str_new) + goto fail; + q = str_new->u.str8; + pos = 0; + while (pos < len) { + c = src[pos++]; + if (c < 0x80) { + *q++ = c; + } else { + if (is_hi_surrogate(c)) { + if (pos < len && !cesu8) { + c1 = src[pos]; + if (is_lo_surrogate(c1)) { + pos++; + c = from_surrogate(c, c1); + } else { + /* Keep unmatched surrogate code points */ + /* c = 0xfffd; */ /* error */ + } + } else { + /* Keep unmatched surrogate code points */ + /* c = 0xfffd; */ /* error */ + } + } + q += unicode_to_utf8(q, c); + } + } + } + + *q = '\0'; + str_new->len = q - str_new->u.str8; + JS_FreeValue(ctx, val); + if (plen) + *plen = str_new->len; + return (const char *)str_new->u.str8; + fail: + if (plen) + *plen = 0; + return NULL; +} + +void JS_FreeCString(JSContext *ctx, const char *ptr) +{ + JSString *p; + if (!ptr) + return; + /* purposely removing constness */ + p = container_of(ptr, JSString, u); + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); +} + +static int memcmp16_8(const uint16_t *src1, const uint8_t *src2, int len) +{ + int c, i; + for(i = 0; i < len; i++) { + c = src1[i] - src2[i]; + if (c != 0) + return c; + } + return 0; +} + +static int memcmp16(const uint16_t *src1, const uint16_t *src2, int len) +{ + int c, i; + for(i = 0; i < len; i++) { + c = src1[i] - src2[i]; + if (c != 0) + return c; + } + return 0; +} + +static int js_string_memcmp(const JSString *p1, const JSString *p2, int len) +{ + int res; + + if (likely(!p1->is_wide_char)) { + if (likely(!p2->is_wide_char)) + res = memcmp(p1->u.str8, p2->u.str8, len); + else + res = -memcmp16_8(p2->u.str16, p1->u.str8, len); + } else { + if (!p2->is_wide_char) + res = memcmp16_8(p1->u.str16, p2->u.str8, len); + else + res = memcmp16(p1->u.str16, p2->u.str16, len); + } + return res; +} + +/* return < 0, 0 or > 0 */ +static int js_string_compare(JSContext *ctx, + const JSString *p1, const JSString *p2) +{ + int res, len; + len = min_int(p1->len, p2->len); + res = js_string_memcmp(p1, p2, len); + if (res == 0) { + if (p1->len == p2->len) + res = 0; + else if (p1->len < p2->len) + res = -1; + else + res = 1; + } + return res; +} + +static void copy_str16(uint16_t *dst, const JSString *p, int offset, int len) +{ + if (p->is_wide_char) { + memcpy(dst, p->u.str16 + offset, len * 2); + } else { + const uint8_t *src1 = p->u.str8 + offset; + int i; + + for(i = 0; i < len; i++) + dst[i] = src1[i]; + } +} + +static JSValue JS_ConcatString1(JSContext *ctx, + const JSString *p1, const JSString *p2) +{ + JSString *p; + uint32_t len; + int is_wide_char; + + len = p1->len + p2->len; + if (len > JS_STRING_LEN_MAX) + return JS_ThrowInternalError(ctx, "string too long"); + is_wide_char = p1->is_wide_char | p2->is_wide_char; + p = js_alloc_string(ctx, len, is_wide_char); + if (!p) + return JS_EXCEPTION; + if (!is_wide_char) { + memcpy(p->u.str8, p1->u.str8, p1->len); + memcpy(p->u.str8 + p1->len, p2->u.str8, p2->len); + p->u.str8[len] = '\0'; + } else { + copy_str16(p->u.str16, p1, 0, p1->len); + copy_str16(p->u.str16 + p1->len, p2, 0, p2->len); + } + return JS_MKPTR(JS_TAG_STRING, p); +} + +static BOOL JS_ConcatStringInPlace(JSContext *ctx, JSString *p1, JSValueConst op2) { + if (JS_VALUE_GET_TAG(op2) == JS_TAG_STRING) { + JSString *p2 = JS_VALUE_GET_STRING(op2); + size_t size1; + + if (p2->len == 0) + return TRUE; + if (p1->header.ref_count != 1) + return FALSE; + size1 = js_malloc_usable_size(ctx, p1); + if (p1->is_wide_char) { + if (size1 >= sizeof(*p1) + ((p1->len + p2->len) << 1)) { + if (p2->is_wide_char) { + memcpy(p1->u.str16 + p1->len, p2->u.str16, p2->len << 1); + p1->len += p2->len; + return TRUE; + } else { + size_t i; + for (i = 0; i < p2->len; i++) { + p1->u.str16[p1->len++] = p2->u.str8[i]; + } + return TRUE; + } + } + } else if (!p2->is_wide_char) { + if (size1 >= sizeof(*p1) + p1->len + p2->len + 1) { + memcpy(p1->u.str8 + p1->len, p2->u.str8, p2->len); + p1->len += p2->len; + p1->u.str8[p1->len] = '\0'; + return TRUE; + } + } + } + return FALSE; +} + +/* op1 and op2 are converted to strings. For convenience, op1 or op2 = + JS_EXCEPTION are accepted and return JS_EXCEPTION. */ +static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2) +{ + JSValue ret; + JSString *p1, *p2; + + if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_STRING)) { + op1 = JS_ToStringFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + return JS_EXCEPTION; + } + } + if (unlikely(JS_VALUE_GET_TAG(op2) != JS_TAG_STRING)) { + op2 = JS_ToStringFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + return JS_EXCEPTION; + } + } + p1 = JS_VALUE_GET_STRING(op1); + if (JS_ConcatStringInPlace(ctx, p1, op2)) { + JS_FreeValue(ctx, op2); + return op1; + } + p2 = JS_VALUE_GET_STRING(op2); + ret = JS_ConcatString1(ctx, p1, p2); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return ret; +} + +/* Shape support */ + +static inline size_t get_shape_size(size_t hash_size, size_t prop_size) +{ + return hash_size * sizeof(uint32_t) + sizeof(JSShape) + + prop_size * sizeof(JSShapeProperty); +} + +static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size) +{ + return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size); +} + +static inline uint32_t *prop_hash_end(JSShape *sh) +{ + return (uint32_t *)sh; +} + +static inline void *get_alloc_from_shape(JSShape *sh) +{ + return prop_hash_end(sh) - ((intptr_t)sh->prop_hash_mask + 1); +} + +static inline JSShapeProperty *get_shape_prop(JSShape *sh) +{ + return sh->prop; +} + +static int init_shape_hash(JSRuntime *rt) +{ + rt->shape_hash_bits = 4; /* 16 shapes */ + rt->shape_hash_size = 1 << rt->shape_hash_bits; + rt->shape_hash_count = 0; + rt->shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * + rt->shape_hash_size); + if (!rt->shape_hash) + return -1; + return 0; +} + +/* same magic hash multiplier as the Linux kernel */ +static uint32_t shape_hash(uint32_t h, uint32_t val) +{ + return (h + val) * 0x9e370001; +} + +/* truncate the shape hash to 'hash_bits' bits */ +static uint32_t get_shape_hash(uint32_t h, int hash_bits) +{ + return h >> (32 - hash_bits); +} + +static uint32_t shape_initial_hash(JSObject *proto) +{ + uint32_t h; + h = shape_hash(1, (uintptr_t)proto); + if (sizeof(proto) > 4) + h = shape_hash(h, (uint64_t)(uintptr_t)proto >> 32); + return h; +} + +static int resize_shape_hash(JSRuntime *rt, int new_shape_hash_bits) +{ + int new_shape_hash_size, i; + uint32_t h; + JSShape **new_shape_hash, *sh, *sh_next; + + new_shape_hash_size = 1 << new_shape_hash_bits; + new_shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * + new_shape_hash_size); + if (!new_shape_hash) + return -1; + for(i = 0; i < rt->shape_hash_size; i++) { + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh_next) { + sh_next = sh->shape_hash_next; + h = get_shape_hash(sh->hash, new_shape_hash_bits); + sh->shape_hash_next = new_shape_hash[h]; + new_shape_hash[h] = sh; + } + } + js_free_rt(rt, rt->shape_hash); + rt->shape_hash_bits = new_shape_hash_bits; + rt->shape_hash_size = new_shape_hash_size; + rt->shape_hash = new_shape_hash; + return 0; +} + +static void js_shape_hash_link(JSRuntime *rt, JSShape *sh) +{ + uint32_t h; + h = get_shape_hash(sh->hash, rt->shape_hash_bits); + sh->shape_hash_next = rt->shape_hash[h]; + rt->shape_hash[h] = sh; + rt->shape_hash_count++; +} + +static void js_shape_hash_unlink(JSRuntime *rt, JSShape *sh) +{ + uint32_t h; + JSShape **psh; + + h = get_shape_hash(sh->hash, rt->shape_hash_bits); + psh = &rt->shape_hash[h]; + while (*psh != sh) + psh = &(*psh)->shape_hash_next; + *psh = sh->shape_hash_next; + rt->shape_hash_count--; +} + +/* create a new empty shape with prototype 'proto' */ +static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto, + int hash_size, int prop_size) +{ + JSRuntime *rt = ctx->rt; + void *sh_alloc; + JSShape *sh; + + /* resize the shape hash table if necessary */ + if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) { + resize_shape_hash(rt, rt->shape_hash_bits + 1); + } + + sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size)); + if (!sh_alloc) + return NULL; + sh = get_shape_from_alloc(sh_alloc, hash_size); + sh->header.ref_count = 1; + add_gc_object(rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); + if (proto) + JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, proto)); + sh->proto = proto; + memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) * + hash_size); + sh->prop_hash_mask = hash_size - 1; + sh->prop_size = prop_size; + sh->prop_count = 0; + sh->deleted_prop_count = 0; + + /* insert in the hash table */ + sh->hash = shape_initial_hash(proto); + sh->is_hashed = TRUE; + sh->has_small_array_index = FALSE; + js_shape_hash_link(ctx->rt, sh); + return sh; +} + +static JSShape *js_new_shape(JSContext *ctx, JSObject *proto) +{ + return js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE, + JS_PROP_INITIAL_SIZE); +} + +/* The shape is cloned. The new shape is not inserted in the shape + hash table */ +static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1) +{ + JSShape *sh; + void *sh_alloc, *sh_alloc1; + size_t size; + JSShapeProperty *pr; + uint32_t i, hash_size; + + hash_size = sh1->prop_hash_mask + 1; + size = get_shape_size(hash_size, sh1->prop_size); + sh_alloc = js_malloc(ctx, size); + if (!sh_alloc) + return NULL; + sh_alloc1 = get_alloc_from_shape(sh1); + memcpy(sh_alloc, sh_alloc1, size); + sh = get_shape_from_alloc(sh_alloc, hash_size); + sh->header.ref_count = 1; + add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); + sh->is_hashed = FALSE; + if (sh->proto) { + JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + } + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { + JS_DupAtom(ctx, pr->atom); + } + return sh; +} + +static JSShape *js_dup_shape(JSShape *sh) +{ + sh->header.ref_count++; + return sh; +} + +static void js_free_shape0(JSRuntime *rt, JSShape *sh) +{ + uint32_t i; + JSShapeProperty *pr; + + assert(sh->header.ref_count == 0); + if (sh->is_hashed) + js_shape_hash_unlink(rt, sh); + if (sh->proto != NULL) { + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + } + pr = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JS_FreeAtomRT(rt, pr->atom); + pr++; + } + remove_gc_object(&sh->header); + js_free_rt(rt, get_alloc_from_shape(sh)); +} + +static void js_free_shape(JSRuntime *rt, JSShape *sh) +{ + if (unlikely(--sh->header.ref_count <= 0)) { + js_free_shape0(rt, sh); + } +} + +static void js_free_shape_null(JSRuntime *rt, JSShape *sh) +{ + if (sh) + js_free_shape(rt, sh); +} + +/* make space to hold at least 'count' properties */ +static no_inline int resize_properties(JSContext *ctx, JSShape **psh, + JSObject *p, uint32_t count) +{ + JSShape *sh; + uint32_t new_size, new_hash_size, new_hash_mask, i; + JSShapeProperty *pr; + void *sh_alloc; + intptr_t h; + JSShape *old_sh; + + sh = *psh; + new_size = max_int(count, sh->prop_size * 3 / 2); + /* Reallocate prop array first to avoid crash or size inconsistency + in case of memory allocation failure */ + if (p) { + JSProperty *new_prop; + new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); + if (unlikely(!new_prop)) + return -1; + p->prop = new_prop; + } + new_hash_size = sh->prop_hash_mask + 1; + while (new_hash_size < new_size) + new_hash_size = 2 * new_hash_size; + /* resize the property shapes. Using js_realloc() is not possible in + case the GC runs during the allocation */ + old_sh = sh; + sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh_alloc) + return -1; + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_del(&old_sh->header.link); + /* copy all the shape properties */ + memcpy(sh, old_sh, + sizeof(JSShape) + sizeof(sh->prop[0]) * old_sh->prop_count); + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + + if (new_hash_size != (sh->prop_hash_mask + 1)) { + /* resize the hash table and the properties */ + new_hash_mask = new_hash_size - 1; + sh->prop_hash_mask = new_hash_mask; + memset(prop_hash_end(sh) - new_hash_size, 0, + sizeof(prop_hash_end(sh)[0]) * new_hash_size); + for(i = 0, pr = sh->prop; i < sh->prop_count; i++, pr++) { + if (pr->atom != JS_ATOM_NULL) { + h = ((uintptr_t)pr->atom & new_hash_mask); + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = i + 1; + } + } + } else { + /* just copy the previous hash table */ + memcpy(prop_hash_end(sh) - new_hash_size, prop_hash_end(old_sh) - new_hash_size, + sizeof(prop_hash_end(sh)[0]) * new_hash_size); + } + js_free(ctx, get_alloc_from_shape(old_sh)); + *psh = sh; + sh->prop_size = new_size; + return 0; +} + +/* remove the deleted properties. */ +static int compact_properties(JSContext *ctx, JSObject *p) +{ + JSShape *sh, *old_sh; + void *sh_alloc; + intptr_t h; + uint32_t new_hash_size, i, j, new_hash_mask, new_size; + JSShapeProperty *old_pr, *pr; + JSProperty *prop, *new_prop; + + sh = p->shape; + assert(!sh->is_hashed); + + new_size = max_int(JS_PROP_INITIAL_SIZE, + sh->prop_count - sh->deleted_prop_count); + assert(new_size <= sh->prop_size); + + new_hash_size = sh->prop_hash_mask + 1; + while ((new_hash_size / 2) >= new_size) + new_hash_size = new_hash_size / 2; + new_hash_mask = new_hash_size - 1; + + /* resize the hash table and the properties */ + old_sh = sh; + sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh_alloc) + return -1; + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_del(&old_sh->header.link); + memcpy(sh, old_sh, sizeof(JSShape)); + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + + memset(prop_hash_end(sh) - new_hash_size, 0, + sizeof(prop_hash_end(sh)[0]) * new_hash_size); + + j = 0; + old_pr = old_sh->prop; + pr = sh->prop; + prop = p->prop; + for(i = 0; i < sh->prop_count; i++) { + if (old_pr->atom != JS_ATOM_NULL) { + pr->atom = old_pr->atom; + pr->flags = old_pr->flags; + h = ((uintptr_t)old_pr->atom & new_hash_mask); + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = j + 1; + prop[j] = prop[i]; + j++; + pr++; + } + old_pr++; + } + assert(j == (sh->prop_count - sh->deleted_prop_count)); + sh->prop_hash_mask = new_hash_mask; + sh->prop_size = new_size; + sh->deleted_prop_count = 0; + sh->prop_count = j; + + p->shape = sh; + js_free(ctx, get_alloc_from_shape(old_sh)); + + /* reduce the size of the object properties */ + new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); + if (new_prop) + p->prop = new_prop; + return 0; +} + +static int add_shape_property(JSContext *ctx, JSShape **psh, + JSObject *p, JSAtom atom, int prop_flags) +{ + JSRuntime *rt = ctx->rt; + JSShape *sh = *psh; + JSShapeProperty *pr, *prop; + uint32_t hash_mask, new_shape_hash = 0; + intptr_t h; + + /* update the shape hash */ + if (sh->is_hashed) { + js_shape_hash_unlink(rt, sh); + new_shape_hash = shape_hash(shape_hash(sh->hash, atom), prop_flags); + } + + if (unlikely(sh->prop_count >= sh->prop_size)) { + if (resize_properties(ctx, psh, p, sh->prop_count + 1)) { + /* in case of error, reinsert in the hash table. + sh is still valid if resize_properties() failed */ + if (sh->is_hashed) + js_shape_hash_link(rt, sh); + return -1; + } + sh = *psh; + } + if (sh->is_hashed) { + sh->hash = new_shape_hash; + js_shape_hash_link(rt, sh); + } + /* Initialize the new shape property. + The object property at p->prop[sh->prop_count] is uninitialized */ + prop = get_shape_prop(sh); + pr = &prop[sh->prop_count++]; + pr->atom = JS_DupAtom(ctx, atom); + pr->flags = prop_flags; + sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom); + /* add in hash table */ + hash_mask = sh->prop_hash_mask; + h = atom & hash_mask; + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = sh->prop_count; + return 0; +} + +/* find a hashed empty shape matching the prototype. Return NULL if + not found */ +static JSShape *find_hashed_shape_proto(JSRuntime *rt, JSObject *proto) +{ + JSShape *sh1; + uint32_t h, h1; + + h = shape_initial_hash(proto); + h1 = get_shape_hash(h, rt->shape_hash_bits); + for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { + if (sh1->hash == h && + sh1->proto == proto && + sh1->prop_count == 0) { + return sh1; + } + } + return NULL; +} + +/* find a hashed shape matching sh + (prop, prop_flags). Return NULL if + not found */ +static JSShape *find_hashed_shape_prop(JSRuntime *rt, JSShape *sh, + JSAtom atom, int prop_flags) +{ + JSShape *sh1; + uint32_t h, h1, i, n; + + h = sh->hash; + h = shape_hash(h, atom); + h = shape_hash(h, prop_flags); + h1 = get_shape_hash(h, rt->shape_hash_bits); + for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { + /* we test the hash first so that the rest is done only if the + shapes really match */ + if (sh1->hash == h && + sh1->proto == sh->proto && + sh1->prop_count == ((n = sh->prop_count) + 1)) { + for(i = 0; i < n; i++) { + if (unlikely(sh1->prop[i].atom != sh->prop[i].atom) || + unlikely(sh1->prop[i].flags != sh->prop[i].flags)) + goto next; + } + if (unlikely(sh1->prop[n].atom != atom) || + unlikely(sh1->prop[n].flags != prop_flags)) + goto next; + return sh1; + } + next: ; + } + return NULL; +} + +static __maybe_unused void JS_DumpShape(JSRuntime *rt, int i, JSShape *sh) +{ + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + int j; + + /* XXX: should output readable class prototype */ + printf("%5d %3d%c %14p %5d %5d", i, + sh->header.ref_count, " *"[sh->is_hashed], + (void *)sh->proto, sh->prop_size, sh->prop_count); + for(j = 0; j < sh->prop_count; j++) { + printf(" %s", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), + sh->prop[j].atom)); + } + printf("\n"); +} + +static __maybe_unused void JS_DumpShapes(JSRuntime *rt) +{ + int i; + JSShape *sh; + struct list_head *el; + JSObject *p; + JSGCObjectHeader *gp; + + printf("JSShapes: {\n"); + printf("%5s %4s %14s %5s %5s %s\n", "SLOT", "REFS", "PROTO", "SIZE", "COUNT", "PROPS"); + for(i = 0; i < rt->shape_hash_size; i++) { + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { + JS_DumpShape(rt, i, sh); + assert(sh->is_hashed); + } + } + /* dump non-hashed shapes */ + list_for_each(el, &rt->gc_obj_list) { + gp = list_entry(el, JSGCObjectHeader, link); + if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + p = (JSObject *)gp; + if (!p->shape->is_hashed) { + JS_DumpShape(rt, -1, p->shape); + } + } + } + printf("}\n"); +} + +static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID class_id) +{ + JSObject *p; + + js_trigger_gc(ctx->rt, sizeof(JSObject)); + p = js_malloc(ctx, sizeof(JSObject)); + if (unlikely(!p)) + goto fail; + p->class_id = class_id; + p->extensible = TRUE; + p->free_mark = 0; + p->is_exotic = 0; + p->fast_array = 0; + p->is_constructor = 0; + p->is_uncatchable_error = 0; + p->tmp_mark = 0; + p->is_HTMLDDA = 0; + p->first_weak_ref = NULL; + p->u.opaque = NULL; + p->shape = sh; + p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size); + if (unlikely(!p->prop)) { + js_free(ctx, p); + fail: + js_free_shape(ctx->rt, sh); + return JS_EXCEPTION; + } + + switch(class_id) { + case JS_CLASS_OBJECT: + break; + case JS_CLASS_ARRAY: + { + JSProperty *pr; + p->is_exotic = 1; + p->fast_array = 1; + p->u.array.u.values = NULL; + p->u.array.count = 0; + p->u.array.u1.size = 0; + /* the length property is always the first one */ + if (likely(sh == ctx->array_shape)) { + pr = &p->prop[0]; + } else { + /* only used for the first array */ + /* cannot fail */ + pr = add_property(ctx, p, JS_ATOM_length, + JS_PROP_WRITABLE | JS_PROP_LENGTH); + } + pr->u.value = JS_NewInt32(ctx, 0); + } + break; + case JS_CLASS_C_FUNCTION: + p->prop[0].u.value = JS_UNDEFINED; + break; + case JS_CLASS_ARGUMENTS: + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + p->is_exotic = 1; + p->fast_array = 1; + p->u.array.u.ptr = NULL; + p->u.array.count = 0; + break; + case JS_CLASS_DATAVIEW: + p->u.array.u.ptr = NULL; + p->u.array.count = 0; + break; + case JS_CLASS_NUMBER: + case JS_CLASS_STRING: + case JS_CLASS_BOOLEAN: + case JS_CLASS_SYMBOL: + case JS_CLASS_DATE: + case JS_CLASS_BIG_INT: +#ifdef CONFIG_BIGNUM + case JS_CLASS_BIG_FLOAT: + case JS_CLASS_BIG_DECIMAL: +#endif + p->u.object_data = JS_UNDEFINED; + goto set_exotic; + case JS_CLASS_REGEXP: + p->u.regexp.pattern = NULL; + p->u.regexp.bytecode = NULL; + goto set_exotic; + default: + set_exotic: + if (ctx->rt->class_array[class_id].exotic) { + p->is_exotic = 1; + } + break; + } + p->header.ref_count = 1; + add_gc_object(ctx->rt, &p->header, JS_GC_OBJ_TYPE_JS_OBJECT); + return JS_MKPTR(JS_TAG_OBJECT, p); +} + +static JSObject *get_proto_obj(JSValueConst proto_val) +{ + if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) + return NULL; + else + return JS_VALUE_GET_OBJ(proto_val); +} + +/* WARNING: proto must be an object or JS_NULL */ +JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValueConst proto_val, + JSClassID class_id) +{ + JSShape *sh; + JSObject *proto; + + proto = get_proto_obj(proto_val); + sh = find_hashed_shape_proto(ctx->rt, proto); + if (likely(sh)) { + sh = js_dup_shape(sh); + } else { + sh = js_new_shape(ctx, proto); + if (!sh) + return JS_EXCEPTION; + } + return JS_NewObjectFromShape(ctx, sh, class_id); +} + +#if 0 +static JSValue JS_GetObjectData(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(obj); + switch(p->class_id) { + case JS_CLASS_NUMBER: + case JS_CLASS_STRING: + case JS_CLASS_BOOLEAN: + case JS_CLASS_SYMBOL: + case JS_CLASS_DATE: + case JS_CLASS_BIG_INT: +#ifdef CONFIG_BIGNUM + case JS_CLASS_BIG_FLOAT: + case JS_CLASS_BIG_DECIMAL: +#endif + return JS_DupValue(ctx, p->u.object_data); + } + } + return JS_UNDEFINED; +} +#endif + +static int JS_SetObjectData(JSContext *ctx, JSValueConst obj, JSValue val) +{ + JSObject *p; + + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(obj); + switch(p->class_id) { + case JS_CLASS_NUMBER: + case JS_CLASS_STRING: + case JS_CLASS_BOOLEAN: + case JS_CLASS_SYMBOL: + case JS_CLASS_DATE: + case JS_CLASS_BIG_INT: +#ifdef CONFIG_BIGNUM + case JS_CLASS_BIG_FLOAT: + case JS_CLASS_BIG_DECIMAL: +#endif + JS_FreeValue(ctx, p->u.object_data); + p->u.object_data = val; + return 0; + } + } + JS_FreeValue(ctx, val); + if (!JS_IsException(obj)) + JS_ThrowTypeError(ctx, "invalid object type"); + return -1; +} + +JSValue JS_NewObjectClass(JSContext *ctx, int class_id) +{ + return JS_NewObjectProtoClass(ctx, ctx->class_proto[class_id], class_id); +} + +JSValue JS_NewObjectProto(JSContext *ctx, JSValueConst proto) +{ + return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT); +} + +JSValue JS_NewArray(JSContext *ctx) +{ + return JS_NewObjectFromShape(ctx, js_dup_shape(ctx->array_shape), + JS_CLASS_ARRAY); +} + +JSValue JS_NewObject(JSContext *ctx) +{ + /* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */ + return JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT); +} + +static void js_function_set_properties(JSContext *ctx, JSValueConst func_obj, + JSAtom name, int len) +{ + /* ES6 feature non compatible with ES5.1: length is configurable */ + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length, JS_NewInt32(ctx, len), + JS_PROP_CONFIGURABLE); + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, + JS_AtomToString(ctx, name), JS_PROP_CONFIGURABLE); +} + +static BOOL js_class_has_bytecode(JSClassID class_id) +{ + return (class_id == JS_CLASS_BYTECODE_FUNCTION || + class_id == JS_CLASS_GENERATOR_FUNCTION || + class_id == JS_CLASS_ASYNC_FUNCTION || + class_id == JS_CLASS_ASYNC_GENERATOR_FUNCTION); +} + +/* return NULL without exception if not a function or no bytecode */ +static JSFunctionBytecode *JS_GetFunctionBytecode(JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return NULL; + p = JS_VALUE_GET_OBJ(val); + if (!js_class_has_bytecode(p->class_id)) + return NULL; + return p->u.func.function_bytecode; +} + +static void js_method_set_home_object(JSContext *ctx, JSValueConst func_obj, + JSValueConst home_obj) +{ + JSObject *p, *p1; + JSFunctionBytecode *b; + + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(func_obj); + if (!js_class_has_bytecode(p->class_id)) + return; + b = p->u.func.function_bytecode; + if (b->need_home_object) { + p1 = p->u.func.home_object; + if (p1) { + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); + } + if (JS_VALUE_GET_TAG(home_obj) == JS_TAG_OBJECT) + p1 = JS_VALUE_GET_OBJ(JS_DupValue(ctx, home_obj)); + else + p1 = NULL; + p->u.func.home_object = p1; + } +} + +static JSValue js_get_function_name(JSContext *ctx, JSAtom name) +{ + JSValue name_str; + + name_str = JS_AtomToString(ctx, name); + if (JS_AtomSymbolHasDescription(ctx, name)) { + name_str = JS_ConcatString3(ctx, "[", name_str, "]"); + } + return name_str; +} + +/* Modify the name of a method according to the atom and + 'flags'. 'flags' is a bitmask of JS_PROP_HAS_GET and + JS_PROP_HAS_SET. Also set the home object of the method. + Return < 0 if exception. */ +static int js_method_set_properties(JSContext *ctx, JSValueConst func_obj, + JSAtom name, int flags, JSValueConst home_obj) +{ + JSValue name_str; + + name_str = js_get_function_name(ctx, name); + if (flags & JS_PROP_HAS_GET) { + name_str = JS_ConcatString3(ctx, "get ", name_str, ""); + } else if (flags & JS_PROP_HAS_SET) { + name_str = JS_ConcatString3(ctx, "set ", name_str, ""); + } + if (JS_IsException(name_str)) + return -1; + if (JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name_str, + JS_PROP_CONFIGURABLE) < 0) + return -1; + js_method_set_home_object(ctx, func_obj, home_obj); + return 0; +} + +/* Note: at least 'length' arguments will be readable in 'argv' */ +static JSValue JS_NewCFunction3(JSContext *ctx, JSCFunction *func, + const char *name, + int length, JSCFunctionEnum cproto, int magic, + JSValueConst proto_val) +{ + JSValue func_obj; + JSObject *p; + JSAtom name_atom; + + func_obj = JS_NewObjectProtoClass(ctx, proto_val, JS_CLASS_C_FUNCTION); + if (JS_IsException(func_obj)) + return func_obj; + p = JS_VALUE_GET_OBJ(func_obj); + p->u.cfunc.realm = JS_DupContext(ctx); + p->u.cfunc.c_function.generic = func; + p->u.cfunc.length = length; + p->u.cfunc.cproto = cproto; + p->u.cfunc.magic = magic; + p->is_constructor = (cproto == JS_CFUNC_constructor || + cproto == JS_CFUNC_constructor_magic || + cproto == JS_CFUNC_constructor_or_func || + cproto == JS_CFUNC_constructor_or_func_magic); + if (!name) + name = ""; + name_atom = JS_NewAtom(ctx, name); + js_function_set_properties(ctx, func_obj, name_atom, length); + JS_FreeAtom(ctx, name_atom); + return func_obj; +} + +/* Note: at least 'length' arguments will be readable in 'argv' */ +JSValue JS_NewCFunction2(JSContext *ctx, JSCFunction *func, + const char *name, + int length, JSCFunctionEnum cproto, int magic) +{ + return JS_NewCFunction3(ctx, func, name, length, cproto, magic, + ctx->function_proto); +} + +typedef struct JSCFunctionDataRecord { + JSCFunctionData *func; + uint8_t length; + uint8_t data_len; + uint16_t magic; + JSValue data[0]; +} JSCFunctionDataRecord; + +static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val) +{ + JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); + int i; + + if (s) { + for(i = 0; i < s->data_len; i++) { + JS_FreeValueRT(rt, s->data[i]); + } + js_free_rt(rt, s); + } +} + +static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); + int i; + + if (s) { + for(i = 0; i < s->data_len; i++) { + JS_MarkValue(rt, s->data[i], mark_func); + } + } +} + +static JSValue js_c_function_data_call(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_val, + int argc, JSValueConst *argv, int flags) +{ + JSCFunctionDataRecord *s = JS_GetOpaque(func_obj, JS_CLASS_C_FUNCTION_DATA); + JSValueConst *arg_buf; + int i; + + /* XXX: could add the function on the stack for debug */ + if (unlikely(argc < s->length)) { + arg_buf = alloca(sizeof(arg_buf[0]) * s->length); + for(i = 0; i < argc; i++) + arg_buf[i] = argv[i]; + for(i = argc; i < s->length; i++) + arg_buf[i] = JS_UNDEFINED; + } else { + arg_buf = argv; + } + + return s->func(ctx, this_val, argc, arg_buf, s->magic, s->data); +} + +JSValue JS_NewCFunctionData(JSContext *ctx, JSCFunctionData *func, + int length, int magic, int data_len, + JSValueConst *data) +{ + JSCFunctionDataRecord *s; + JSValue func_obj; + int i; + + func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto, + JS_CLASS_C_FUNCTION_DATA); + if (JS_IsException(func_obj)) + return func_obj; + s = js_malloc(ctx, sizeof(*s) + data_len * sizeof(JSValue)); + if (!s) { + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; + } + s->func = func; + s->length = length; + s->data_len = data_len; + s->magic = magic; + for(i = 0; i < data_len; i++) + s->data[i] = JS_DupValue(ctx, data[i]); + JS_SetOpaque(func_obj, s); + js_function_set_properties(ctx, func_obj, + JS_ATOM_empty_string, length); + return func_obj; +} + +static JSContext *js_autoinit_get_realm(JSProperty *pr) +{ + return (JSContext *)(pr->u.init.realm_and_id & ~3); +} + +static JSAutoInitIDEnum js_autoinit_get_id(JSProperty *pr) +{ + return pr->u.init.realm_and_id & 3; +} + +static void js_autoinit_free(JSRuntime *rt, JSProperty *pr) +{ + JS_FreeContext(js_autoinit_get_realm(pr)); +} + +static void js_autoinit_mark(JSRuntime *rt, JSProperty *pr, + JS_MarkFunc *mark_func) +{ + mark_func(rt, &js_autoinit_get_realm(pr)->header); +} + +static void free_property(JSRuntime *rt, JSProperty *pr, int prop_flags) +{ + if (unlikely(prop_flags & JS_PROP_TMASK)) { + if ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (pr->u.getset.getter) + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + free_var_ref(rt, pr->u.var_ref); + } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + js_autoinit_free(rt, pr); + } + } else { + JS_FreeValueRT(rt, pr->u.value); + } +} + +static force_inline JSShapeProperty *find_own_property1(JSObject *p, + JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *prop; + intptr_t h; + sh = p->shape; + h = (uintptr_t)atom & sh->prop_hash_mask; + h = prop_hash_end(sh)[-h - 1]; + prop = get_shape_prop(sh); + while (h) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + return pr; + } + h = pr->hash_next; + } + return NULL; +} + +static force_inline JSShapeProperty *find_own_property(JSProperty **ppr, + JSObject *p, + JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *prop; + intptr_t h; + sh = p->shape; + h = (uintptr_t)atom & sh->prop_hash_mask; + h = prop_hash_end(sh)[-h - 1]; + prop = get_shape_prop(sh); + while (h) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + *ppr = &p->prop[h - 1]; + /* the compiler should be able to assume that pr != NULL here */ + return pr; + } + h = pr->hash_next; + } + *ppr = NULL; + return NULL; +} + +/* indicate that the object may be part of a function prototype cycle */ +static void set_cycle_flag(JSContext *ctx, JSValueConst obj) +{ +} + +static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref) +{ + if (var_ref) { + assert(var_ref->header.ref_count > 0); + if (--var_ref->header.ref_count == 0) { + if (var_ref->is_detached) { + JS_FreeValueRT(rt, var_ref->value); + } else { + list_del(&var_ref->var_ref_link); /* still on the stack */ + if (var_ref->async_func) + async_func_free(rt, var_ref->async_func); + } + remove_gc_object(&var_ref->header); + js_free_rt(rt, var_ref); + } + } +} + +static void js_array_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + int i; + + for(i = 0; i < p->u.array.count; i++) { + JS_FreeValueRT(rt, p->u.array.u.values[i]); + } + js_free_rt(rt, p->u.array.u.values); +} + +static void js_array_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + int i; + + for(i = 0; i < p->u.array.count; i++) { + JS_MarkValue(rt, p->u.array.u.values[i], mark_func); + } +} + +static void js_object_data_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JS_FreeValueRT(rt, p->u.object_data); + p->u.object_data = JS_UNDEFINED; +} + +static void js_object_data_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JS_MarkValue(rt, p->u.object_data, mark_func); +} + +static void js_c_function_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + + if (p->u.cfunc.realm) + JS_FreeContext(p->u.cfunc.realm); +} + +static void js_c_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + + if (p->u.cfunc.realm) + mark_func(rt, &p->u.cfunc.realm->header); +} + +static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p1, *p = JS_VALUE_GET_OBJ(val); + JSFunctionBytecode *b; + JSVarRef **var_refs; + int i; + + p1 = p->u.func.home_object; + if (p1) { + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, p1)); + } + b = p->u.func.function_bytecode; + if (b) { + var_refs = p->u.func.var_refs; + if (var_refs) { + for(i = 0; i < b->closure_var_count; i++) + free_var_ref(rt, var_refs[i]); + js_free_rt(rt, var_refs); + } + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b)); + } +} + +static void js_bytecode_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSVarRef **var_refs = p->u.func.var_refs; + JSFunctionBytecode *b = p->u.func.function_bytecode; + int i; + + if (p->u.func.home_object) { + JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object), + mark_func); + } + if (b) { + if (var_refs) { + for(i = 0; i < b->closure_var_count; i++) { + JSVarRef *var_ref = var_refs[i]; + if (var_ref) { + mark_func(rt, &var_ref->header); + } + } + } + /* must mark the function bytecode because template objects may be + part of a cycle */ + JS_MarkValue(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b), mark_func); + } +} + +static void js_bound_function_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSBoundFunction *bf = p->u.bound_function; + int i; + + JS_FreeValueRT(rt, bf->func_obj); + JS_FreeValueRT(rt, bf->this_val); + for(i = 0; i < bf->argc; i++) { + JS_FreeValueRT(rt, bf->argv[i]); + } + js_free_rt(rt, bf); +} + +static void js_bound_function_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSBoundFunction *bf = p->u.bound_function; + int i; + + JS_MarkValue(rt, bf->func_obj, mark_func); + JS_MarkValue(rt, bf->this_val, mark_func); + for(i = 0; i < bf->argc; i++) + JS_MarkValue(rt, bf->argv[i], mark_func); +} + +static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSForInIterator *it = p->u.for_in_iterator; + int i; + + JS_FreeValueRT(rt, it->obj); + if (!it->is_array) { + for(i = 0; i < it->atom_count; i++) { + JS_FreeAtomRT(rt, it->tab_atom[i].atom); + } + js_free_rt(rt, it->tab_atom); + } + js_free_rt(rt, it); +} + +static void js_for_in_iterator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSForInIterator *it = p->u.for_in_iterator; + JS_MarkValue(rt, it->obj, mark_func); +} + +static void free_object(JSRuntime *rt, JSObject *p) +{ + int i; + JSClassFinalizer *finalizer; + JSShape *sh; + JSShapeProperty *pr; + + p->free_mark = 1; /* used to tell the object is invalid when + freeing cycles */ + /* free all the fields */ + sh = p->shape; + pr = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + free_property(rt, &p->prop[i], pr->flags); + pr++; + } + js_free_rt(rt, p->prop); + /* as an optimization we destroy the shape immediately without + putting it in gc_zero_ref_count_list */ + js_free_shape(rt, sh); + + /* fail safe */ + p->shape = NULL; + p->prop = NULL; + + if (unlikely(p->first_weak_ref)) { + reset_weak_ref(rt, p); + } + + finalizer = rt->class_array[p->class_id].finalizer; + if (finalizer) + (*finalizer)(rt, JS_MKPTR(JS_TAG_OBJECT, p)); + + /* fail safe */ + p->class_id = 0; + p->u.opaque = NULL; + p->u.func.var_refs = NULL; + p->u.func.home_object = NULL; + + remove_gc_object(&p->header); + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && p->header.ref_count != 0) { + list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list); + } else { + js_free_rt(rt, p); + } +} + +static void free_gc_object(JSRuntime *rt, JSGCObjectHeader *gp) +{ + switch(gp->gc_obj_type) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + free_object(rt, (JSObject *)gp); + break; + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + free_function_bytecode(rt, (JSFunctionBytecode *)gp); + break; + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + __async_func_free(rt, (JSAsyncFunctionState *)gp); + break; + default: + abort(); + } +} + +static void free_zero_refcount(JSRuntime *rt) +{ + struct list_head *el; + JSGCObjectHeader *p; + + rt->gc_phase = JS_GC_PHASE_DECREF; + for(;;) { + el = rt->gc_zero_ref_count_list.next; + if (el == &rt->gc_zero_ref_count_list) + break; + p = list_entry(el, JSGCObjectHeader, link); + assert(p->ref_count == 0); + free_gc_object(rt, p); + } + rt->gc_phase = JS_GC_PHASE_NONE; +} + +/* called with the ref_count of 'v' reaches zero. */ +void __JS_FreeValueRT(JSRuntime *rt, JSValue v) +{ + uint32_t tag = JS_VALUE_GET_TAG(v); + +#ifdef DUMP_FREE + { + printf("Freeing "); + if (tag == JS_TAG_OBJECT) { + JS_DumpObject(rt, JS_VALUE_GET_OBJ(v)); + } else { + JS_DumpValueShort(rt, v); + printf("\n"); + } + } +#endif + + switch(tag) { + case JS_TAG_STRING: + { + JSString *p = JS_VALUE_GET_STRING(v); + if (p->atom_type) { + JS_FreeAtomStruct(rt, p); + } else { +#ifdef DUMP_LEAKS + list_del(&p->link); +#endif + js_free_rt(rt, p); + } + } + break; + case JS_TAG_OBJECT: + case JS_TAG_FUNCTION_BYTECODE: + { + JSGCObjectHeader *p = JS_VALUE_GET_PTR(v); + if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { + list_del(&p->link); + list_add(&p->link, &rt->gc_zero_ref_count_list); + if (rt->gc_phase == JS_GC_PHASE_NONE) { + free_zero_refcount(rt); + } + } + } + break; + case JS_TAG_MODULE: + abort(); /* never freed here */ + break; + case JS_TAG_BIG_INT: +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: +#endif + { + JSBigFloat *bf = JS_VALUE_GET_PTR(v); + bf_delete(&bf->num); + js_free_rt(rt, bf); + } + break; +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_DECIMAL: + { + JSBigDecimal *bf = JS_VALUE_GET_PTR(v); + bfdec_delete(&bf->num); + js_free_rt(rt, bf); + } + break; +#endif + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(v); + JS_FreeAtomStruct(rt, p); + } + break; + default: + printf("__JS_FreeValue: unknown tag=%d\n", tag); + abort(); + } +} + +void __JS_FreeValue(JSContext *ctx, JSValue v) +{ + __JS_FreeValueRT(ctx->rt, v); +} + +/* garbage collection */ + +static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, + JSGCObjectTypeEnum type) +{ + h->mark = 0; + h->gc_obj_type = type; + list_add_tail(&h->link, &rt->gc_obj_list); +} + +static void remove_gc_object(JSGCObjectHeader *h) +{ + list_del(&h->link); +} + +void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) +{ + if (JS_VALUE_HAS_REF_COUNT(val)) { + switch(JS_VALUE_GET_TAG(val)) { + case JS_TAG_OBJECT: + case JS_TAG_FUNCTION_BYTECODE: + mark_func(rt, JS_VALUE_GET_PTR(val)); + break; + default: + break; + } + } +} + +static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp, + JS_MarkFunc *mark_func) +{ + switch(gp->gc_obj_type) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + { + JSObject *p = (JSObject *)gp; + JSShapeProperty *prs; + JSShape *sh; + int i; + sh = p->shape; + mark_func(rt, &sh->header); + /* mark all the fields */ + prs = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JSProperty *pr = &p->prop[i]; + if (prs->atom != JS_ATOM_NULL) { + if (prs->flags & JS_PROP_TMASK) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (pr->u.getset.getter) + mark_func(rt, &pr->u.getset.getter->header); + if (pr->u.getset.setter) + mark_func(rt, &pr->u.getset.setter->header); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + /* Note: the tag does not matter + provided it is a GC object */ + mark_func(rt, &pr->u.var_ref->header); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + js_autoinit_mark(rt, pr, mark_func); + } + } else { + JS_MarkValue(rt, pr->u.value, mark_func); + } + } + prs++; + } + + if (p->class_id != JS_CLASS_OBJECT) { + JSClassGCMark *gc_mark; + gc_mark = rt->class_array[p->class_id].gc_mark; + if (gc_mark) + gc_mark(rt, JS_MKPTR(JS_TAG_OBJECT, p), mark_func); + } + } + break; + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + /* the template objects can be part of a cycle */ + { + JSFunctionBytecode *b = (JSFunctionBytecode *)gp; + int i; + for(i = 0; i < b->cpool_count; i++) { + JS_MarkValue(rt, b->cpool[i], mark_func); + } + if (b->realm) + mark_func(rt, &b->realm->header); + } + break; + case JS_GC_OBJ_TYPE_VAR_REF: + { + JSVarRef *var_ref = (JSVarRef *)gp; + if (var_ref->is_detached) { + JS_MarkValue(rt, *var_ref->pvalue, mark_func); + } else if (var_ref->async_func) { + mark_func(rt, &var_ref->async_func->header); + } + } + break; + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + { + JSAsyncFunctionState *s = (JSAsyncFunctionState *)gp; + JSStackFrame *sf = &s->frame; + JSValue *sp; + + if (!s->is_completed) { + JS_MarkValue(rt, sf->cur_func, mark_func); + JS_MarkValue(rt, s->this_val, mark_func); + /* sf->cur_sp = NULL if the function is running */ + if (sf->cur_sp) { + /* if the function is running, cur_sp is not known so we + cannot mark the stack. Marking the variables is not needed + because a running function cannot be part of a removable + cycle */ + for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) + JS_MarkValue(rt, *sp, mark_func); + } + } + JS_MarkValue(rt, s->resolving_funcs[0], mark_func); + JS_MarkValue(rt, s->resolving_funcs[1], mark_func); + } + break; + case JS_GC_OBJ_TYPE_SHAPE: + { + JSShape *sh = (JSShape *)gp; + if (sh->proto != NULL) { + mark_func(rt, &sh->proto->header); + } + } + break; + case JS_GC_OBJ_TYPE_JS_CONTEXT: + { + JSContext *ctx = (JSContext *)gp; + JS_MarkContext(rt, ctx, mark_func); + } + break; + default: + abort(); + } +} + +static void gc_decref_child(JSRuntime *rt, JSGCObjectHeader *p) +{ + assert(p->ref_count > 0); + p->ref_count--; + if (p->ref_count == 0 && p->mark == 1) { + list_del(&p->link); + list_add_tail(&p->link, &rt->tmp_obj_list); + } +} + +static void gc_decref(JSRuntime *rt) +{ + struct list_head *el, *el1; + JSGCObjectHeader *p; + + init_list_head(&rt->tmp_obj_list); + + /* decrement the refcount of all the children of all the GC + objects and move the GC objects with zero refcount to + tmp_obj_list */ + list_for_each_safe(el, el1, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(p->mark == 0); + mark_children(rt, p, gc_decref_child); + p->mark = 1; + if (p->ref_count == 0) { + list_del(&p->link); + list_add_tail(&p->link, &rt->tmp_obj_list); + } + } +} + +static void gc_scan_incref_child(JSRuntime *rt, JSGCObjectHeader *p) +{ + p->ref_count++; + if (p->ref_count == 1) { + /* ref_count was 0: remove from tmp_obj_list and add at the + end of gc_obj_list */ + list_del(&p->link); + list_add_tail(&p->link, &rt->gc_obj_list); + p->mark = 0; /* reset the mark for the next GC call */ + } +} + +static void gc_scan_incref_child2(JSRuntime *rt, JSGCObjectHeader *p) +{ + p->ref_count++; +} + +static void gc_scan(JSRuntime *rt) +{ + struct list_head *el; + JSGCObjectHeader *p; + + /* keep the objects with a refcount > 0 and their children. */ + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(p->ref_count > 0); + p->mark = 0; /* reset the mark for the next GC call */ + mark_children(rt, p, gc_scan_incref_child); + } + + /* restore the refcount of the objects to be deleted. */ + list_for_each(el, &rt->tmp_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + mark_children(rt, p, gc_scan_incref_child2); + } +} + +static void gc_free_cycles(JSRuntime *rt) +{ + struct list_head *el, *el1; + JSGCObjectHeader *p; +#ifdef DUMP_GC_FREE + BOOL header_done = FALSE; +#endif + + rt->gc_phase = JS_GC_PHASE_REMOVE_CYCLES; + + for(;;) { + el = rt->tmp_obj_list.next; + if (el == &rt->tmp_obj_list) + break; + p = list_entry(el, JSGCObjectHeader, link); + /* Only need to free the GC object associated with JS values + or async functions. The rest will be automatically removed + because they must be referenced by them. */ + switch(p->gc_obj_type) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: +#ifdef DUMP_GC_FREE + if (!header_done) { + printf("Freeing cycles:\n"); + JS_DumpObjectHeader(rt); + header_done = TRUE; + } + JS_DumpGCObject(rt, p); +#endif + free_gc_object(rt, p); + break; + default: + list_del(&p->link); + list_add_tail(&p->link, &rt->gc_zero_ref_count_list); + break; + } + } + rt->gc_phase = JS_GC_PHASE_NONE; + + list_for_each_safe(el, el1, &rt->gc_zero_ref_count_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT || + p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE || + p->gc_obj_type == JS_GC_OBJ_TYPE_ASYNC_FUNCTION); + js_free_rt(rt, p); + } + + init_list_head(&rt->gc_zero_ref_count_list); +} + +void JS_RunGC(JSRuntime *rt) +{ + /* decrement the reference of the children of each object. mark = + 1 after this pass. */ + gc_decref(rt); + + /* keep the GC objects with a non zero refcount and their childs */ + gc_scan(rt); + + /* free the GC objects in a cycle */ + gc_free_cycles(rt); +} + +/* Return false if not an object or if the object has already been + freed (zombie objects are visible in finalizers when freeing + cycles). */ +BOOL JS_IsLiveObject(JSRuntime *rt, JSValueConst obj) +{ + JSObject *p; + if (!JS_IsObject(obj)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + return !p->free_mark; +} + +/* Compute memory used by various object types */ +/* XXX: poor man's approach to handling multiply referenced objects */ +typedef struct JSMemoryUsage_helper { + double memory_used_count; + double str_count; + double str_size; + int64_t js_func_count; + double js_func_size; + int64_t js_func_code_size; + int64_t js_func_pc2line_count; + int64_t js_func_pc2line_size; +} JSMemoryUsage_helper; + +static void compute_value_size(JSValueConst val, JSMemoryUsage_helper *hp); + +static void compute_jsstring_size(JSString *str, JSMemoryUsage_helper *hp) +{ + if (!str->atom_type) { /* atoms are handled separately */ + double s_ref_count = str->header.ref_count; + hp->str_count += 1 / s_ref_count; + hp->str_size += ((sizeof(*str) + (str->len << str->is_wide_char) + + 1 - str->is_wide_char) / s_ref_count); + } +} + +static void compute_bytecode_size(JSFunctionBytecode *b, JSMemoryUsage_helper *hp) +{ + int memory_used_count, js_func_size, i; + + memory_used_count = 0; + js_func_size = offsetof(JSFunctionBytecode, debug); + if (b->vardefs) { + js_func_size += (b->arg_count + b->var_count) * sizeof(*b->vardefs); + } + if (b->cpool) { + js_func_size += b->cpool_count * sizeof(*b->cpool); + for (i = 0; i < b->cpool_count; i++) { + JSValueConst val = b->cpool[i]; + compute_value_size(val, hp); + } + } + if (b->closure_var) { + js_func_size += b->closure_var_count * sizeof(*b->closure_var); + } + if (!b->read_only_bytecode && b->byte_code_buf) { + hp->js_func_code_size += b->byte_code_len; + } + if (b->has_debug) { + js_func_size += sizeof(*b) - offsetof(JSFunctionBytecode, debug); + if (b->debug.source) { + memory_used_count++; + js_func_size += b->debug.source_len + 1; + } + if (b->debug.pc2line_len) { + memory_used_count++; + hp->js_func_pc2line_count += 1; + hp->js_func_pc2line_size += b->debug.pc2line_len; + } + } + hp->js_func_size += js_func_size; + hp->js_func_count += 1; + hp->memory_used_count += memory_used_count; +} + +static void compute_value_size(JSValueConst val, JSMemoryUsage_helper *hp) +{ + switch(JS_VALUE_GET_TAG(val)) { + case JS_TAG_STRING: + compute_jsstring_size(JS_VALUE_GET_STRING(val), hp); + break; + case JS_TAG_BIG_INT: +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + case JS_TAG_BIG_DECIMAL: +#endif + /* should track JSBigFloat usage */ + break; + } +} + +void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) +{ + struct list_head *el, *el1; + int i; + JSMemoryUsage_helper mem = { 0 }, *hp = &mem; + + memset(s, 0, sizeof(*s)); + s->malloc_count = rt->malloc_state.malloc_count; + s->malloc_size = rt->malloc_state.malloc_size; + s->malloc_limit = rt->malloc_state.malloc_limit; + + s->memory_used_count = 2; /* rt + rt->class_array */ + s->memory_used_size = sizeof(JSRuntime) + sizeof(JSValue) * rt->class_count; + + list_for_each(el, &rt->context_list) { + JSContext *ctx = list_entry(el, JSContext, link); + JSShape *sh = ctx->array_shape; + s->memory_used_count += 2; /* ctx + ctx->class_proto */ + s->memory_used_size += sizeof(JSContext) + + sizeof(JSValue) * rt->class_count; + s->binary_object_count += ctx->binary_object_count; + s->binary_object_size += ctx->binary_object_size; + + /* the hashed shapes are counted separately */ + if (sh && !sh->is_hashed) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + list_for_each(el1, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el1, JSModuleDef, link); + s->memory_used_count += 1; + s->memory_used_size += sizeof(*m); + if (m->req_module_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->req_module_entries_count * sizeof(*m->req_module_entries); + } + if (m->export_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->export_entries_count * sizeof(*m->export_entries); + for (i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + if (me->export_type == JS_EXPORT_TYPE_LOCAL && me->u.local.var_ref) { + /* potential multiple count */ + s->memory_used_count += 1; + compute_value_size(me->u.local.var_ref->value, hp); + } + } + } + if (m->star_export_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->star_export_entries_count * sizeof(*m->star_export_entries); + } + if (m->import_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->import_entries_count * sizeof(*m->import_entries); + } + compute_value_size(m->module_ns, hp); + compute_value_size(m->func_obj, hp); + } + } + + list_for_each(el, &rt->gc_obj_list) { + JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); + JSObject *p; + JSShape *sh; + JSShapeProperty *prs; + + /* XXX: could count the other GC object types too */ + if (gp->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { + compute_bytecode_size((JSFunctionBytecode *)gp, hp); + continue; + } else if (gp->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT) { + continue; + } + p = (JSObject *)gp; + sh = p->shape; + s->obj_count++; + if (p->prop) { + s->memory_used_count++; + s->prop_size += sh->prop_size * sizeof(*p->prop); + s->prop_count += sh->prop_count; + prs = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JSProperty *pr = &p->prop[i]; + if (prs->atom != JS_ATOM_NULL && !(prs->flags & JS_PROP_TMASK)) { + compute_value_size(pr->u.value, hp); + } + prs++; + } + } + /* the hashed shapes are counted separately */ + if (!sh->is_hashed) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + + switch(p->class_id) { + case JS_CLASS_ARRAY: /* u.array | length */ + case JS_CLASS_ARGUMENTS: /* u.array | length */ + s->array_count++; + if (p->fast_array) { + s->fast_array_count++; + if (p->u.array.u.values) { + s->memory_used_count++; + s->memory_used_size += p->u.array.count * + sizeof(*p->u.array.u.values); + s->fast_array_elements += p->u.array.count; + for (i = 0; i < p->u.array.count; i++) { + compute_value_size(p->u.array.u.values[i], hp); + } + } + } + break; + case JS_CLASS_NUMBER: /* u.object_data */ + case JS_CLASS_STRING: /* u.object_data */ + case JS_CLASS_BOOLEAN: /* u.object_data */ + case JS_CLASS_SYMBOL: /* u.object_data */ + case JS_CLASS_DATE: /* u.object_data */ + case JS_CLASS_BIG_INT: /* u.object_data */ +#ifdef CONFIG_BIGNUM + case JS_CLASS_BIG_FLOAT: /* u.object_data */ + case JS_CLASS_BIG_DECIMAL: /* u.object_data */ +#endif + compute_value_size(p->u.object_data, hp); + break; + case JS_CLASS_C_FUNCTION: /* u.cfunc */ + s->c_func_count++; + break; + case JS_CLASS_BYTECODE_FUNCTION: /* u.func */ + { + JSFunctionBytecode *b = p->u.func.function_bytecode; + JSVarRef **var_refs = p->u.func.var_refs; + /* home_object: object will be accounted for in list scan */ + if (var_refs) { + s->memory_used_count++; + s->js_func_size += b->closure_var_count * sizeof(*var_refs); + for (i = 0; i < b->closure_var_count; i++) { + if (var_refs[i]) { + double ref_count = var_refs[i]->header.ref_count; + s->memory_used_count += 1 / ref_count; + s->js_func_size += sizeof(*var_refs[i]) / ref_count; + /* handle non object closed values */ + if (var_refs[i]->pvalue == &var_refs[i]->value) { + /* potential multiple count */ + compute_value_size(var_refs[i]->value, hp); + } + } + } + } + } + break; + case JS_CLASS_BOUND_FUNCTION: /* u.bound_function */ + { + JSBoundFunction *bf = p->u.bound_function; + /* func_obj and this_val are objects */ + for (i = 0; i < bf->argc; i++) { + compute_value_size(bf->argv[i], hp); + } + s->memory_used_count += 1; + s->memory_used_size += sizeof(*bf) + bf->argc * sizeof(*bf->argv); + } + break; + case JS_CLASS_C_FUNCTION_DATA: /* u.c_function_data_record */ + { + JSCFunctionDataRecord *fd = p->u.c_function_data_record; + if (fd) { + for (i = 0; i < fd->data_len; i++) { + compute_value_size(fd->data[i], hp); + } + s->memory_used_count += 1; + s->memory_used_size += sizeof(*fd) + fd->data_len * sizeof(*fd->data); + } + } + break; + case JS_CLASS_REGEXP: /* u.regexp */ + compute_jsstring_size(p->u.regexp.pattern, hp); + compute_jsstring_size(p->u.regexp.bytecode, hp); + break; + + case JS_CLASS_FOR_IN_ITERATOR: /* u.for_in_iterator */ + { + JSForInIterator *it = p->u.for_in_iterator; + if (it) { + compute_value_size(it->obj, hp); + s->memory_used_count += 1; + s->memory_used_size += sizeof(*it); + } + } + break; + case JS_CLASS_ARRAY_BUFFER: /* u.array_buffer */ + case JS_CLASS_SHARED_ARRAY_BUFFER: /* u.array_buffer */ + { + JSArrayBuffer *abuf = p->u.array_buffer; + if (abuf) { + s->memory_used_count += 1; + s->memory_used_size += sizeof(*abuf); + if (abuf->data) { + s->memory_used_count += 1; + s->memory_used_size += abuf->byte_length; + } + } + } + break; + case JS_CLASS_GENERATOR: /* u.generator_data */ + case JS_CLASS_UINT8C_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT8_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT8_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT16_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT16_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_BIG_INT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_BIG_UINT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_FLOAT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_FLOAT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_DATAVIEW: /* u.typed_array */ +#ifdef CONFIG_BIGNUM + case JS_CLASS_FLOAT_ENV: /* u.float_env */ +#endif + case JS_CLASS_MAP: /* u.map_state */ + case JS_CLASS_SET: /* u.map_state */ + case JS_CLASS_WEAKMAP: /* u.map_state */ + case JS_CLASS_WEAKSET: /* u.map_state */ + case JS_CLASS_MAP_ITERATOR: /* u.map_iterator_data */ + case JS_CLASS_SET_ITERATOR: /* u.map_iterator_data */ + case JS_CLASS_ARRAY_ITERATOR: /* u.array_iterator_data */ + case JS_CLASS_STRING_ITERATOR: /* u.array_iterator_data */ + case JS_CLASS_PROXY: /* u.proxy_data */ + case JS_CLASS_PROMISE: /* u.promise_data */ + case JS_CLASS_PROMISE_RESOLVE_FUNCTION: /* u.promise_function_data */ + case JS_CLASS_PROMISE_REJECT_FUNCTION: /* u.promise_function_data */ + case JS_CLASS_ASYNC_FUNCTION_RESOLVE: /* u.async_function_data */ + case JS_CLASS_ASYNC_FUNCTION_REJECT: /* u.async_function_data */ + case JS_CLASS_ASYNC_FROM_SYNC_ITERATOR: /* u.async_from_sync_iterator_data */ + case JS_CLASS_ASYNC_GENERATOR: /* u.async_generator_data */ + /* TODO */ + default: + /* XXX: class definition should have an opaque block size */ + if (p->u.opaque) { + s->memory_used_count += 1; + } + break; + } + } + s->obj_size += s->obj_count * sizeof(JSObject); + + /* hashed shapes */ + s->memory_used_count++; /* rt->shape_hash */ + s->memory_used_size += sizeof(rt->shape_hash[0]) * rt->shape_hash_size; + for(i = 0; i < rt->shape_hash_size; i++) { + JSShape *sh; + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + } + + /* atoms */ + s->memory_used_count += 2; /* rt->atom_array, rt->atom_hash */ + s->atom_count = rt->atom_count; + s->atom_size = sizeof(rt->atom_array[0]) * rt->atom_size + + sizeof(rt->atom_hash[0]) * rt->atom_hash_size; + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p)) { + s->atom_size += (sizeof(*p) + (p->len << p->is_wide_char) + + 1 - p->is_wide_char); + } + } + s->str_count = round(mem.str_count); + s->str_size = round(mem.str_size); + s->js_func_count = mem.js_func_count; + s->js_func_size = round(mem.js_func_size); + s->js_func_code_size = mem.js_func_code_size; + s->js_func_pc2line_count = mem.js_func_pc2line_count; + s->js_func_pc2line_size = mem.js_func_pc2line_size; + s->memory_used_count += round(mem.memory_used_count) + + s->atom_count + s->str_count + + s->obj_count + s->shape_count + + s->js_func_count + s->js_func_pc2line_count; + s->memory_used_size += s->atom_size + s->str_size + + s->obj_size + s->prop_size + s->shape_size + + s->js_func_size + s->js_func_code_size + s->js_func_pc2line_size; +} + +void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) +{ + fprintf(fp, "QuickJS memory usage -- " +#ifdef CONFIG_BIGNUM + "BigNum " +#endif + CONFIG_VERSION " version, %d-bit, malloc limit: %"PRId64"\n\n", + (int)sizeof(void *) * 8, s->malloc_limit); +#if 1 + if (rt) { + static const struct { + const char *name; + size_t size; + } object_types[] = { + { "JSRuntime", sizeof(JSRuntime) }, + { "JSContext", sizeof(JSContext) }, + { "JSObject", sizeof(JSObject) }, + { "JSString", sizeof(JSString) }, + { "JSFunctionBytecode", sizeof(JSFunctionBytecode) }, + }; + int i, usage_size_ok = 0; + for(i = 0; i < countof(object_types); i++) { + unsigned int size = object_types[i].size; + void *p = js_malloc_rt(rt, size); + if (p) { + unsigned int size1 = js_malloc_usable_size_rt(rt, p); + if (size1 >= size) { + usage_size_ok = 1; + fprintf(fp, " %3u + %-2u %s\n", + size, size1 - size, object_types[i].name); + } + js_free_rt(rt, p); + } + } + if (!usage_size_ok) { + fprintf(fp, " malloc_usable_size unavailable\n"); + } + { + int obj_classes[JS_CLASS_INIT_COUNT + 1] = { 0 }; + int class_id; + struct list_head *el; + list_for_each(el, &rt->gc_obj_list) { + JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); + JSObject *p; + if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + p = (JSObject *)gp; + obj_classes[min_uint32(p->class_id, JS_CLASS_INIT_COUNT)]++; + } + } + fprintf(fp, "\n" "JSObject classes\n"); + if (obj_classes[0]) + fprintf(fp, " %5d %2.0d %s\n", obj_classes[0], 0, "none"); + for (class_id = 1; class_id < JS_CLASS_INIT_COUNT; class_id++) { + if (obj_classes[class_id] && class_id < rt->class_count) { + char buf[ATOM_GET_STR_BUF_SIZE]; + fprintf(fp, " %5d %2.0d %s\n", obj_classes[class_id], class_id, + JS_AtomGetStrRT(rt, buf, sizeof(buf), rt->class_array[class_id].class_name)); + } + } + if (obj_classes[JS_CLASS_INIT_COUNT]) + fprintf(fp, " %5d %2.0d %s\n", obj_classes[JS_CLASS_INIT_COUNT], 0, "other"); + } + fprintf(fp, "\n"); + } +#endif + fprintf(fp, "%-20s %8s %8s\n", "NAME", "COUNT", "SIZE"); + + if (s->malloc_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per block)\n", + "memory allocated", s->malloc_count, s->malloc_size, + (double)s->malloc_size / s->malloc_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%d overhead, %0.1f average slack)\n", + "memory used", s->memory_used_count, s->memory_used_size, + MALLOC_OVERHEAD, ((double)(s->malloc_size - s->memory_used_size) / + s->memory_used_count)); + } + if (s->atom_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per atom)\n", + "atoms", s->atom_count, s->atom_size, + (double)s->atom_size / s->atom_count); + } + if (s->str_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per string)\n", + "strings", s->str_count, s->str_size, + (double)s->str_size / s->str_count); + } + if (s->obj_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", + "objects", s->obj_count, s->obj_size, + (double)s->obj_size / s->obj_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", + " properties", s->prop_count, s->prop_size, + (double)s->prop_count / s->obj_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per shape)\n", + " shapes", s->shape_count, s->shape_size, + (double)s->shape_size / s->shape_count); + } + if (s->js_func_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", + "bytecode functions", s->js_func_count, s->js_func_size); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", + " bytecode", s->js_func_count, s->js_func_code_size, + (double)s->js_func_code_size / s->js_func_count); + if (s->js_func_pc2line_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", + " pc2line", s->js_func_pc2line_count, + s->js_func_pc2line_size, + (double)s->js_func_pc2line_size / s->js_func_pc2line_count); + } + } + if (s->c_func_count) { + fprintf(fp, "%-20s %8"PRId64"\n", "C functions", s->c_func_count); + } + if (s->array_count) { + fprintf(fp, "%-20s %8"PRId64"\n", "arrays", s->array_count); + if (s->fast_array_count) { + fprintf(fp, "%-20s %8"PRId64"\n", " fast arrays", s->fast_array_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per fast array)\n", + " elements", s->fast_array_elements, + s->fast_array_elements * (int)sizeof(JSValue), + (double)s->fast_array_elements / s->fast_array_count); + } + } + if (s->binary_object_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", + "binary objects", s->binary_object_count, s->binary_object_size); + } +} + +JSValue JS_GetGlobalObject(JSContext *ctx) +{ + return JS_DupValue(ctx, ctx->global_obj); +} + +/* WARNING: obj is freed */ +JSValue JS_Throw(JSContext *ctx, JSValue obj) +{ + JSRuntime *rt = ctx->rt; + JS_FreeValue(ctx, rt->current_exception); + rt->current_exception = obj; + return JS_EXCEPTION; +} + +/* return the pending exception (cannot be called twice). */ +JSValue JS_GetException(JSContext *ctx) +{ + JSValue val; + JSRuntime *rt = ctx->rt; + val = rt->current_exception; + rt->current_exception = JS_NULL; + return val; +} + +static void dbuf_put_leb128(DynBuf *s, uint32_t v) +{ + uint32_t a; + for(;;) { + a = v & 0x7f; + v >>= 7; + if (v != 0) { + dbuf_putc(s, a | 0x80); + } else { + dbuf_putc(s, a); + break; + } + } +} + +static void dbuf_put_sleb128(DynBuf *s, int32_t v1) +{ + uint32_t v = v1; + dbuf_put_leb128(s, (2 * v) ^ -(v >> 31)); +} + +static int get_leb128(uint32_t *pval, const uint8_t *buf, + const uint8_t *buf_end) +{ + const uint8_t *ptr = buf; + uint32_t v, a, i; + v = 0; + for(i = 0; i < 5; i++) { + if (unlikely(ptr >= buf_end)) + break; + a = *ptr++; + v |= (a & 0x7f) << (i * 7); + if (!(a & 0x80)) { + *pval = v; + return ptr - buf; + } + } + *pval = 0; + return -1; +} + +static int get_sleb128(int32_t *pval, const uint8_t *buf, + const uint8_t *buf_end) +{ + int ret; + uint32_t val; + ret = get_leb128(&val, buf, buf_end); + if (ret < 0) { + *pval = 0; + return -1; + } + *pval = (val >> 1) ^ -(val & 1); + return ret; +} + +static int find_line_num(JSContext *ctx, JSFunctionBytecode *b, + uint32_t pc_value) +{ + const uint8_t *p_end, *p; + int new_line_num, line_num, pc, v, ret; + unsigned int op; + + if (!b->has_debug || !b->debug.pc2line_buf) { + /* function was stripped */ + return -1; + } + + p = b->debug.pc2line_buf; + p_end = p + b->debug.pc2line_len; + pc = 0; + line_num = b->debug.line_num; + while (p < p_end) { + op = *p++; + if (op == 0) { + uint32_t val; + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + pc += val; + p += ret; + ret = get_sleb128(&v, p, p_end); + if (ret < 0) { + fail: + /* should never happen */ + return b->debug.line_num; + } + p += ret; + new_line_num = line_num + v; + } else { + op -= PC2LINE_OP_FIRST; + pc += (op / PC2LINE_RANGE); + new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; + } + if (pc_value < pc) + return line_num; + line_num = new_line_num; + } + return line_num; +} + +/* in order to avoid executing arbitrary code during the stack trace + generation, we only look at simple 'name' properties containing a + string. */ +static const char *get_func_name(JSContext *ctx, JSValueConst func) +{ + JSProperty *pr; + JSShapeProperty *prs; + JSValueConst val; + + if (JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT) + return NULL; + prs = find_own_property(&pr, JS_VALUE_GET_OBJ(func), JS_ATOM_name); + if (!prs) + return NULL; + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) + return NULL; + val = pr->u.value; + if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) + return NULL; + return JS_ToCString(ctx, val); +} + +#define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0) +/* only taken into account if filename is provided */ +#define JS_BACKTRACE_FLAG_SINGLE_LEVEL (1 << 1) + +/* if filename != NULL, an additional level is added with the filename + and line number information (used for parse error). */ +static void build_backtrace(JSContext *ctx, JSValueConst error_obj, + const char *filename, int line_num, + int backtrace_flags) +{ + JSStackFrame *sf; + JSValue str; + DynBuf dbuf; + const char *func_name_str; + const char *str1; + JSObject *p; + BOOL backtrace_barrier; + + js_dbuf_init(ctx, &dbuf); + if (filename) { + dbuf_printf(&dbuf, " at %s", filename); + if (line_num != -1) + dbuf_printf(&dbuf, ":%d", line_num); + dbuf_putc(&dbuf, '\n'); + str = JS_NewString(ctx, filename); + JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_fileName, str, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_lineNumber, JS_NewInt32(ctx, line_num), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (backtrace_flags & JS_BACKTRACE_FLAG_SINGLE_LEVEL) + goto done; + } + for(sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { + if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) { + backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL; + continue; + } + func_name_str = get_func_name(ctx, sf->cur_func); + if (!func_name_str || func_name_str[0] == '\0') + str1 = ""; + else + str1 = func_name_str; + dbuf_printf(&dbuf, " at %s", str1); + JS_FreeCString(ctx, func_name_str); + + p = JS_VALUE_GET_OBJ(sf->cur_func); + backtrace_barrier = FALSE; + if (js_class_has_bytecode(p->class_id)) { + JSFunctionBytecode *b; + const char *atom_str; + int line_num1; + + b = p->u.func.function_bytecode; + backtrace_barrier = b->backtrace_barrier; + if (b->has_debug) { + line_num1 = find_line_num(ctx, b, + sf->cur_pc - b->byte_code_buf - 1); + atom_str = JS_AtomToCString(ctx, b->debug.filename); + dbuf_printf(&dbuf, " (%s", + atom_str ? atom_str : ""); + JS_FreeCString(ctx, atom_str); + if (line_num1 != -1) + dbuf_printf(&dbuf, ":%d", line_num1); + dbuf_putc(&dbuf, ')'); + } + } else { + dbuf_printf(&dbuf, " (native)"); + } + dbuf_putc(&dbuf, '\n'); + /* stop backtrace if JS_EVAL_FLAG_BACKTRACE_BARRIER was used */ + if (backtrace_barrier) + break; + } + done: + dbuf_putc(&dbuf, '\0'); + if (dbuf_error(&dbuf)) + str = JS_NULL; + else + str = JS_NewString(ctx, (char *)dbuf.buf); + dbuf_free(&dbuf); + JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_stack, str, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); +} + +/* Note: it is important that no exception is returned by this function */ +static BOOL is_backtrace_needed(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id != JS_CLASS_ERROR) + return FALSE; + if (find_own_property1(p, JS_ATOM_stack)) + return FALSE; + return TRUE; +} + +JSValue JS_NewError(JSContext *ctx) +{ + return JS_NewObjectClass(ctx, JS_CLASS_ERROR); +} + +static JSValue JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num, + const char *fmt, va_list ap, BOOL add_backtrace) +{ + char buf[256]; + JSValue obj, ret; + + vsnprintf(buf, sizeof(buf), fmt, ap); + obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[error_num], + JS_CLASS_ERROR); + if (unlikely(JS_IsException(obj))) { + /* out of memory: throw JS_NULL to avoid recursing */ + obj = JS_NULL; + } else { + JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, + JS_NewString(ctx, buf), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + } + if (add_backtrace) { + build_backtrace(ctx, obj, NULL, 0, 0); + } + ret = JS_Throw(ctx, obj); + return ret; +} + +static JSValue JS_ThrowError(JSContext *ctx, JSErrorEnum error_num, + const char *fmt, va_list ap) +{ + JSRuntime *rt = ctx->rt; + JSStackFrame *sf; + BOOL add_backtrace; + + /* the backtrace is added later if called from a bytecode function */ + sf = rt->current_stack_frame; + add_backtrace = !rt->in_out_of_memory && + (!sf || (JS_GetFunctionBytecode(sf->cur_func) == NULL)); + return JS_ThrowError2(ctx, error_num, fmt, ap, add_backtrace); +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowSyntaxError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_SYNTAX_ERROR, fmt, ap); + va_end(ap); + return val; +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowTypeError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap); + va_end(ap); + return val; +} + +static int __attribute__((format(printf, 3, 4))) JS_ThrowTypeErrorOrFalse(JSContext *ctx, int flags, const char *fmt, ...) +{ + va_list ap; + + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + va_start(ap, fmt); + JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap); + va_end(ap); + return -1; + } else { + return FALSE; + } +} + +/* never use it directly */ +static JSValue __attribute__((format(printf, 3, 4))) __JS_ThrowTypeErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowTypeError(ctx, fmt, + JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); +} + +/* never use it directly */ +static JSValue __attribute__((format(printf, 3, 4))) __JS_ThrowSyntaxErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowSyntaxError(ctx, fmt, + JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); +} + +/* %s is replaced by 'atom'. The macro is used so that gcc can check + the format string. */ +#define JS_ThrowTypeErrorAtom(ctx, fmt, atom) __JS_ThrowTypeErrorAtom(ctx, atom, fmt, "") +#define JS_ThrowSyntaxErrorAtom(ctx, fmt, atom) __JS_ThrowSyntaxErrorAtom(ctx, atom, fmt, "") + +static int JS_ThrowTypeErrorReadOnly(JSContext *ctx, int flags, JSAtom atom) +{ + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeErrorAtom(ctx, "'%s' is read-only", atom); + return -1; + } else { + return FALSE; + } +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowReferenceError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_REFERENCE_ERROR, fmt, ap); + va_end(ap); + return val; +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowRangeError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_RANGE_ERROR, fmt, ap); + va_end(ap); + return val; +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_INTERNAL_ERROR, fmt, ap); + va_end(ap); + return val; +} + +JSValue JS_ThrowOutOfMemory(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + if (!rt->in_out_of_memory) { + rt->in_out_of_memory = TRUE; + JS_ThrowInternalError(ctx, "out of memory"); + rt->in_out_of_memory = FALSE; + } + return JS_EXCEPTION; +} + +static JSValue JS_ThrowStackOverflow(JSContext *ctx) +{ + return JS_ThrowInternalError(ctx, "stack overflow"); +} + +static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "not an object"); +} + +static JSValue JS_ThrowTypeErrorNotASymbol(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "not a symbol"); +} + +static JSValue JS_ThrowReferenceErrorNotDefined(JSContext *ctx, JSAtom name) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowReferenceError(ctx, "'%s' is not defined", + JS_AtomGetStr(ctx, buf, sizeof(buf), name)); +} + +static JSValue JS_ThrowReferenceErrorUninitialized(JSContext *ctx, JSAtom name) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowReferenceError(ctx, "%s is not initialized", + name == JS_ATOM_NULL ? "lexical variable" : + JS_AtomGetStr(ctx, buf, sizeof(buf), name)); +} + +static JSValue JS_ThrowReferenceErrorUninitialized2(JSContext *ctx, + JSFunctionBytecode *b, + int idx, BOOL is_ref) +{ + JSAtom atom = JS_ATOM_NULL; + if (is_ref) { + atom = b->closure_var[idx].var_name; + } else { + /* not present if the function is stripped and contains no eval() */ + if (b->vardefs) + atom = b->vardefs[b->arg_count + idx].var_name; + } + return JS_ThrowReferenceErrorUninitialized(ctx, atom); +} + +static JSValue JS_ThrowTypeErrorInvalidClass(JSContext *ctx, int class_id) +{ + JSRuntime *rt = ctx->rt; + JSAtom name; + name = rt->class_array[class_id].class_name; + return JS_ThrowTypeErrorAtom(ctx, "%s object expected", name); +} + +static no_inline __exception int __js_poll_interrupts(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; + if (rt->interrupt_handler) { + if (rt->interrupt_handler(rt, rt->interrupt_opaque)) { + /* XXX: should set a specific flag to avoid catching */ + JS_ThrowInternalError(ctx, "interrupted"); + JS_SetUncatchableError(ctx, ctx->rt->current_exception, TRUE); + return -1; + } + } + return 0; +} + +static inline __exception int js_poll_interrupts(JSContext *ctx) +{ + if (unlikely(--ctx->interrupt_counter <= 0)) { + return __js_poll_interrupts(ctx); + } else { + return 0; + } +} + +/* return -1 (exception) or TRUE/FALSE */ +static int JS_SetPrototypeInternal(JSContext *ctx, JSValueConst obj, + JSValueConst proto_val, + BOOL throw_flag) +{ + JSObject *proto, *p, *p1; + JSShape *sh; + + if (throw_flag) { + if (JS_VALUE_GET_TAG(obj) == JS_TAG_NULL || + JS_VALUE_GET_TAG(obj) == JS_TAG_UNDEFINED) + goto not_obj; + } else { + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + goto not_obj; + } + p = JS_VALUE_GET_OBJ(obj); + if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) { + if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_NULL) { + not_obj: + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + proto = NULL; + } else { + proto = JS_VALUE_GET_OBJ(proto_val); + } + + if (throw_flag && JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return TRUE; + + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_setPrototypeOf(ctx, obj, proto_val, throw_flag); + sh = p->shape; + if (sh->proto == proto) + return TRUE; + if (!p->extensible) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "object is not extensible"); + return -1; + } else { + return FALSE; + } + } + if (proto) { + /* check if there is a cycle */ + p1 = proto; + do { + if (p1 == p) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "circular prototype chain"); + return -1; + } else { + return FALSE; + } + } + /* Note: for Proxy objects, proto is NULL */ + p1 = p1->shape->proto; + } while (p1 != NULL); + JS_DupValue(ctx, proto_val); + } + + if (js_shape_prepare_update(ctx, p, NULL)) + return -1; + sh = p->shape; + if (sh->proto) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + sh->proto = proto; + return TRUE; +} + +/* return -1 (exception) or TRUE/FALSE */ +int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValueConst proto_val) +{ + return JS_SetPrototypeInternal(ctx, obj, proto_val, TRUE); +} + +/* Only works for primitive types, otherwise return JS_NULL. */ +static JSValueConst JS_GetPrototypePrimitive(JSContext *ctx, JSValueConst val) +{ + switch(JS_VALUE_GET_NORM_TAG(val)) { + case JS_TAG_BIG_INT: + val = ctx->class_proto[JS_CLASS_BIG_INT]; + break; +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + val = ctx->class_proto[JS_CLASS_BIG_FLOAT]; + break; + case JS_TAG_BIG_DECIMAL: + val = ctx->class_proto[JS_CLASS_BIG_DECIMAL]; + break; +#endif + case JS_TAG_INT: + case JS_TAG_FLOAT64: + val = ctx->class_proto[JS_CLASS_NUMBER]; + break; + case JS_TAG_BOOL: + val = ctx->class_proto[JS_CLASS_BOOLEAN]; + break; + case JS_TAG_STRING: + val = ctx->class_proto[JS_CLASS_STRING]; + break; + case JS_TAG_SYMBOL: + val = ctx->class_proto[JS_CLASS_SYMBOL]; + break; + case JS_TAG_OBJECT: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + default: + val = JS_NULL; + break; + } + return val; +} + +/* Return an Object, JS_NULL or JS_EXCEPTION in case of Proxy object. */ +JSValue JS_GetPrototype(JSContext *ctx, JSValueConst obj) +{ + JSValue val; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->class_id == JS_CLASS_PROXY)) { + val = js_proxy_getPrototypeOf(ctx, obj); + } else { + p = p->shape->proto; + if (!p) + val = JS_NULL; + else + val = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + } + } else { + val = JS_DupValue(ctx, JS_GetPrototypePrimitive(ctx, obj)); + } + return val; +} + +static JSValue JS_GetPrototypeFree(JSContext *ctx, JSValue obj) +{ + JSValue obj1; + obj1 = JS_GetPrototype(ctx, obj); + JS_FreeValue(ctx, obj); + return obj1; +} + +/* return TRUE, FALSE or (-1) in case of exception */ +static int JS_OrdinaryIsInstanceOf(JSContext *ctx, JSValueConst val, + JSValueConst obj) +{ + JSValue obj_proto; + JSObject *proto; + const JSObject *p, *proto1; + BOOL ret; + + if (!JS_IsFunction(ctx, obj)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_BOUND_FUNCTION) { + JSBoundFunction *s = p->u.bound_function; + return JS_IsInstanceOf(ctx, val, s->func_obj); + } + + /* Only explicitly boxed values are instances of constructors */ + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + obj_proto = JS_GetProperty(ctx, obj, JS_ATOM_prototype); + if (JS_VALUE_GET_TAG(obj_proto) != JS_TAG_OBJECT) { + if (!JS_IsException(obj_proto)) + JS_ThrowTypeError(ctx, "operand 'prototype' property is not an object"); + ret = -1; + goto done; + } + proto = JS_VALUE_GET_OBJ(obj_proto); + p = JS_VALUE_GET_OBJ(val); + for(;;) { + proto1 = p->shape->proto; + if (!proto1) { + /* slow case if proxy in the prototype chain */ + if (unlikely(p->class_id == JS_CLASS_PROXY)) { + JSValue obj1; + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, (JSObject *)p)); + for(;;) { + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsException(obj1)) { + ret = -1; + break; + } + if (JS_IsNull(obj1)) { + ret = FALSE; + break; + } + if (proto == JS_VALUE_GET_OBJ(obj1)) { + JS_FreeValue(ctx, obj1); + ret = TRUE; + break; + } + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) { + JS_FreeValue(ctx, obj1); + ret = -1; + break; + } + } + } else { + ret = FALSE; + } + break; + } + p = proto1; + if (proto == p) { + ret = TRUE; + break; + } + } +done: + JS_FreeValue(ctx, obj_proto); + return ret; +} + +/* return TRUE, FALSE or (-1) in case of exception */ +int JS_IsInstanceOf(JSContext *ctx, JSValueConst val, JSValueConst obj) +{ + JSValue method; + + if (!JS_IsObject(obj)) + goto fail; + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_hasInstance); + if (JS_IsException(method)) + return -1; + if (!JS_IsNull(method) && !JS_IsUndefined(method)) { + JSValue ret; + ret = JS_CallFree(ctx, method, obj, 1, &val); + return JS_ToBoolFree(ctx, ret); + } + + /* legacy case */ + if (!JS_IsFunction(ctx, obj)) { + fail: + JS_ThrowTypeError(ctx, "invalid 'instanceof' right operand"); + return -1; + } + return JS_OrdinaryIsInstanceOf(ctx, val, obj); +} + +/* return the value associated to the autoinit property or an exception */ +typedef JSValue JSAutoInitFunc(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); + +static JSAutoInitFunc *js_autoinit_func_table[] = { + js_instantiate_prototype, /* JS_AUTOINIT_ID_PROTOTYPE */ + js_module_ns_autoinit, /* JS_AUTOINIT_ID_MODULE_NS */ + JS_InstantiateFunctionListItem2, /* JS_AUTOINIT_ID_PROP */ +}; + +/* warning: 'prs' is reallocated after it */ +static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, + JSProperty *pr, JSShapeProperty *prs) +{ + JSValue val; + JSContext *realm; + JSAutoInitFunc *func; + + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + + realm = js_autoinit_get_realm(pr); + func = js_autoinit_func_table[js_autoinit_get_id(pr)]; + /* 'func' shall not modify the object properties 'pr' */ + val = func(realm, p, prop, pr->u.init.opaque); + js_autoinit_free(ctx->rt, pr); + prs->flags &= ~JS_PROP_TMASK; + pr->u.value = JS_UNDEFINED; + if (JS_IsException(val)) + return -1; + pr->u.value = val; + return 0; +} + +JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, + JSAtom prop, JSValueConst this_obj, + BOOL throw_ref_error) +{ + JSObject *p; + JSProperty *pr; + JSShapeProperty *prs; + uint32_t tag; + + tag = JS_VALUE_GET_TAG(obj); + if (unlikely(tag != JS_TAG_OBJECT)) { + switch(tag) { + case JS_TAG_NULL: + return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null", prop); + case JS_TAG_UNDEFINED: + return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of undefined", prop); + case JS_TAG_EXCEPTION: + return JS_EXCEPTION; + case JS_TAG_STRING: + { + JSString *p1 = JS_VALUE_GET_STRING(obj); + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx, ch; + idx = __JS_AtomToUInt32(prop); + if (idx < p1->len) { + if (p1->is_wide_char) + ch = p1->u.str16[idx]; + else + ch = p1->u.str8[idx]; + return js_new_string_char(ctx, ch); + } + } else if (prop == JS_ATOM_length) { + return JS_NewInt32(ctx, p1->len); + } + } + break; + default: + break; + } + /* cannot raise an exception */ + p = JS_VALUE_GET_OBJ(JS_GetPrototypePrimitive(ctx, obj)); + if (!p) + return JS_UNDEFINED; + } else { + p = JS_VALUE_GET_OBJ(obj); + } + + for(;;) { + prs = find_own_property(&pr, p, prop); + if (prs) { + /* found */ + if (unlikely(prs->flags & JS_PROP_TMASK)) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (unlikely(!pr->u.getset.getter)) { + return JS_UNDEFINED; + } else { + JSValue func = JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter); + /* Note: the field could be removed in the getter */ + func = JS_DupValue(ctx, func); + return JS_CallFree(ctx, func, this_obj, 0, NULL); + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + JSValue val = *pr->u.var_ref->pvalue; + if (unlikely(JS_IsUninitialized(val))) + return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return JS_DupValue(ctx, val); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return JS_EXCEPTION; + continue; + } + } else { + return JS_DupValue(ctx, pr->u.value); + } + } + if (unlikely(p->is_exotic)) { + /* exotic behaviors */ + if (p->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + /* we avoid duplicating the code */ + return JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + return JS_UNDEFINED; + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + int ret; + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return JS_EXCEPTION; + return JS_UNDEFINED; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em) { + if (em->get_property) { + JSValue obj1, retval; + /* XXX: should pass throw_ref_error */ + /* Note: if 'p' is a prototype, it can be + freed in the called function */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + retval = em->get_property(ctx, obj1, prop, this_obj); + JS_FreeValue(ctx, obj1); + return retval; + } + if (em->get_own_property) { + JSPropertyDescriptor desc; + int ret; + JSValue obj1; + + /* Note: if 'p' is a prototype, it can be + freed in the called function */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + ret = em->get_own_property(ctx, &desc, obj1, prop); + JS_FreeValue(ctx, obj1); + if (ret < 0) + return JS_EXCEPTION; + if (ret) { + if (desc.flags & JS_PROP_GETSET) { + JS_FreeValue(ctx, desc.setter); + return JS_CallFree(ctx, desc.getter, this_obj, 0, NULL); + } else { + return desc.value; + } + } + } + } + } + } + p = p->shape->proto; + if (!p) + break; + } + if (unlikely(throw_ref_error)) { + return JS_ThrowReferenceErrorNotDefined(ctx, prop); + } else { + return JS_UNDEFINED; + } +} + +static JSValue JS_ThrowTypeErrorPrivateNotFound(JSContext *ctx, JSAtom atom) +{ + return JS_ThrowTypeErrorAtom(ctx, "private class field '%s' does not exist", + atom); +} + +/* Private fields can be added even on non extensible objects or + Proxies */ +static int JS_DefinePrivateField(JSContext *ctx, JSValueConst obj, + JSValueConst name, JSValue val) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto fail; + } + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { + JS_ThrowTypeErrorNotASymbol(ctx); + goto fail; + } + prop = js_symbol_to_atom(ctx, (JSValue)name); + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + JS_ThrowTypeErrorAtom(ctx, "private class field '%s' already exists", + prop); + goto fail; + } + pr = add_property(ctx, p, prop, JS_PROP_C_W_E); + if (unlikely(!pr)) { + fail: + JS_FreeValue(ctx, val); + return -1; + } + pr->u.value = val; + return 0; +} + +static JSValue JS_GetPrivateField(JSContext *ctx, JSValueConst obj, + JSValueConst name) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return JS_ThrowTypeErrorNotAnObject(ctx); + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) + return JS_ThrowTypeErrorNotASymbol(ctx); + prop = js_symbol_to_atom(ctx, (JSValue)name); + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (!prs) { + JS_ThrowTypeErrorPrivateNotFound(ctx, prop); + return JS_EXCEPTION; + } + return JS_DupValue(ctx, pr->u.value); +} + +static int JS_SetPrivateField(JSContext *ctx, JSValueConst obj, + JSValueConst name, JSValue val) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto fail; + } + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { + JS_ThrowTypeErrorNotASymbol(ctx); + goto fail; + } + prop = js_symbol_to_atom(ctx, (JSValue)name); + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (!prs) { + JS_ThrowTypeErrorPrivateNotFound(ctx, prop); + fail: + JS_FreeValue(ctx, val); + return -1; + } + set_value(ctx, &pr->u.value, val); + return 0; +} + +/* add a private brand field to 'home_obj' if not already present and + if obj is != null add a private brand to it */ +static int JS_AddBrand(JSContext *ctx, JSValueConst obj, JSValueConst home_obj) +{ + JSObject *p, *p1; + JSShapeProperty *prs; + JSProperty *pr; + JSValue brand; + JSAtom brand_atom; + + if (unlikely(JS_VALUE_GET_TAG(home_obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(home_obj); + prs = find_own_property(&pr, p, JS_ATOM_Private_brand); + if (!prs) { + /* if the brand is not present, add it */ + brand = JS_NewSymbolFromAtom(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE); + if (JS_IsException(brand)) + return -1; + pr = add_property(ctx, p, JS_ATOM_Private_brand, JS_PROP_C_W_E); + if (!pr) { + JS_FreeValue(ctx, brand); + return -1; + } + pr->u.value = JS_DupValue(ctx, brand); + } else { + brand = JS_DupValue(ctx, pr->u.value); + } + brand_atom = js_symbol_to_atom(ctx, brand); + + if (JS_IsObject(obj)) { + p1 = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p1, brand_atom); + if (unlikely(prs)) { + JS_FreeAtom(ctx, brand_atom); + JS_ThrowTypeError(ctx, "private method is already present"); + return -1; + } + pr = add_property(ctx, p1, brand_atom, JS_PROP_C_W_E); + JS_FreeAtom(ctx, brand_atom); + if (!pr) + return -1; + pr->u.value = JS_UNDEFINED; + } else { + JS_FreeAtom(ctx, brand_atom); + } + return 0; +} + +/* return a boolean telling if the brand of the home object of 'func' + is present on 'obj' or -1 in case of exception */ +static int JS_CheckBrand(JSContext *ctx, JSValueConst obj, JSValueConst func) +{ + JSObject *p, *p1, *home_obj; + JSShapeProperty *prs; + JSProperty *pr; + JSValueConst brand; + + /* get the home object of 'func' */ + if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)) + goto not_obj; + p1 = JS_VALUE_GET_OBJ(func); + if (!js_class_has_bytecode(p1->class_id)) + goto not_obj; + home_obj = p1->u.func.home_object; + if (!home_obj) + goto not_obj; + prs = find_own_property(&pr, home_obj, JS_ATOM_Private_brand); + if (!prs) { + JS_ThrowTypeError(ctx, "expecting private field"); + return -1; + } + brand = pr->u.value; + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(brand) != JS_TAG_SYMBOL)) + goto not_obj; + + /* get the brand array of 'obj' */ + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + not_obj: + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, (JSValue)brand)); + return (prs != NULL); +} + +static uint32_t js_string_obj_get_length(JSContext *ctx, + JSValueConst obj) +{ + JSObject *p; + JSString *p1; + uint32_t len = 0; + + /* This is a class exotic method: obj class_id is JS_CLASS_STRING */ + p = JS_VALUE_GET_OBJ(obj); + if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) { + p1 = JS_VALUE_GET_STRING(p->u.object_data); + len = p1->len; + } + return len; +} + +static int num_keys_cmp(const void *p1, const void *p2, void *opaque) +{ + JSContext *ctx = opaque; + JSAtom atom1 = ((const JSPropertyEnum *)p1)->atom; + JSAtom atom2 = ((const JSPropertyEnum *)p2)->atom; + uint32_t v1, v2; + BOOL atom1_is_integer, atom2_is_integer; + + atom1_is_integer = JS_AtomIsArrayIndex(ctx, &v1, atom1); + atom2_is_integer = JS_AtomIsArrayIndex(ctx, &v2, atom2); + assert(atom1_is_integer && atom2_is_integer); + if (v1 < v2) + return -1; + else if (v1 == v2) + return 0; + else + return 1; +} + +static void js_free_prop_enum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len) +{ + uint32_t i; + if (tab) { + for(i = 0; i < len; i++) + JS_FreeAtom(ctx, tab[i].atom); + js_free(ctx, tab); + } +} + +/* return < 0 in case if exception, 0 if OK. ptab and its atoms must + be freed by the user. */ +static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx, + JSPropertyEnum **ptab, + uint32_t *plen, + JSObject *p, int flags) +{ + int i, j; + JSShape *sh; + JSShapeProperty *prs; + JSPropertyEnum *tab_atom, *tab_exotic; + JSAtom atom; + uint32_t num_keys_count, str_keys_count, sym_keys_count, atom_count; + uint32_t num_index, str_index, sym_index, exotic_count, exotic_keys_count; + BOOL is_enumerable, num_sorted; + uint32_t num_key; + JSAtomKindEnum kind; + + /* clear pointer for consistency in case of failure */ + *ptab = NULL; + *plen = 0; + + /* compute the number of returned properties */ + num_keys_count = 0; + str_keys_count = 0; + sym_keys_count = 0; + exotic_keys_count = 0; + exotic_count = 0; + tab_exotic = NULL; + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + atom = prs->atom; + if (atom != JS_ATOM_NULL) { + is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + /* need to raise an exception in case of the module + name space (implicit GetOwnProperty) */ + if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) && + (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY))) { + JSVarRef *var_ref = p->prop[i].u.var_ref; + if (unlikely(JS_IsUninitialized(*var_ref->pvalue))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + } + if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { + num_keys_count++; + } else if (kind == JS_ATOM_KIND_STRING) { + str_keys_count++; + } else { + sym_keys_count++; + } + } + } + } + + if (p->is_exotic) { + if (p->fast_array) { + if (flags & JS_GPN_STRING_MASK) { + num_keys_count += p->u.array.count; + } + } else if (p->class_id == JS_CLASS_STRING) { + if (flags & JS_GPN_STRING_MASK) { + num_keys_count += js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->get_own_property_names) { + if (em->get_own_property_names(ctx, &tab_exotic, &exotic_count, + JS_MKPTR(JS_TAG_OBJECT, p))) + return -1; + for(i = 0; i < exotic_count; i++) { + atom = tab_exotic[i].atom; + kind = JS_AtomGetKind(ctx, atom); + if (((flags >> kind) & 1) != 0) { + is_enumerable = FALSE; + if (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY)) { + JSPropertyDescriptor desc; + int res; + /* set the "is_enumerable" field if necessary */ + res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom); + if (res < 0) { + js_free_prop_enum(ctx, tab_exotic, exotic_count); + return -1; + } + if (res) { + is_enumerable = + ((desc.flags & JS_PROP_ENUMERABLE) != 0); + js_free_desc(ctx, &desc); + } + tab_exotic[i].is_enumerable = is_enumerable; + } + if (!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) { + exotic_keys_count++; + } + } + } + } + } + } + + /* fill them */ + + atom_count = num_keys_count + str_keys_count + sym_keys_count + exotic_keys_count; + /* avoid allocating 0 bytes */ + tab_atom = js_malloc(ctx, sizeof(tab_atom[0]) * max_int(atom_count, 1)); + if (!tab_atom) { + js_free_prop_enum(ctx, tab_exotic, exotic_count); + return -1; + } + + num_index = 0; + str_index = num_keys_count; + sym_index = str_index + str_keys_count; + + num_sorted = TRUE; + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + atom = prs->atom; + if (atom != JS_ATOM_NULL) { + is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { + j = num_index++; + num_sorted = FALSE; + } else if (kind == JS_ATOM_KIND_STRING) { + j = str_index++; + } else { + j = sym_index++; + } + tab_atom[j].atom = JS_DupAtom(ctx, atom); + tab_atom[j].is_enumerable = is_enumerable; + } + } + } + + if (p->is_exotic) { + int len; + if (p->fast_array) { + if (flags & JS_GPN_STRING_MASK) { + len = p->u.array.count; + goto add_array_keys; + } + } else if (p->class_id == JS_CLASS_STRING) { + if (flags & JS_GPN_STRING_MASK) { + len = js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + add_array_keys: + for(i = 0; i < len; i++) { + tab_atom[num_index].atom = __JS_AtomFromUInt32(i); + if (tab_atom[num_index].atom == JS_ATOM_NULL) { + js_free_prop_enum(ctx, tab_atom, num_index); + return -1; + } + tab_atom[num_index].is_enumerable = TRUE; + num_index++; + } + } + } else { + /* Note: exotic keys are not reordered and comes after the object own properties. */ + for(i = 0; i < exotic_count; i++) { + atom = tab_exotic[i].atom; + is_enumerable = tab_exotic[i].is_enumerable; + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + tab_atom[sym_index].atom = atom; + tab_atom[sym_index].is_enumerable = is_enumerable; + sym_index++; + } else { + JS_FreeAtom(ctx, atom); + } + } + js_free(ctx, tab_exotic); + } + } + + assert(num_index == num_keys_count); + assert(str_index == num_keys_count + str_keys_count); + assert(sym_index == atom_count); + + if (num_keys_count != 0 && !num_sorted) { + rqsort(tab_atom, num_keys_count, sizeof(tab_atom[0]), num_keys_cmp, + ctx); + } + *ptab = tab_atom; + *plen = atom_count; + return 0; +} + +int JS_GetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, + uint32_t *plen, JSValueConst obj, int flags) +{ + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen, + JS_VALUE_GET_OBJ(obj), flags); +} + +/* Return -1 if exception, + FALSE if the property does not exist, TRUE if it exists. If TRUE is + returned, the property descriptor 'desc' is filled present. */ +static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, + JSObject *p, JSAtom prop) +{ + JSShapeProperty *prs; + JSProperty *pr; + +retry: + prs = find_own_property(&pr, p, prop); + if (prs) { + if (desc) { + desc->flags = prs->flags & JS_PROP_C_W_E; + desc->getter = JS_UNDEFINED; + desc->setter = JS_UNDEFINED; + desc->value = JS_UNDEFINED; + if (unlikely(prs->flags & JS_PROP_TMASK)) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + desc->flags |= JS_PROP_GETSET; + if (pr->u.getset.getter) + desc->getter = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + desc->setter = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + JSValue val = *pr->u.var_ref->pvalue; + if (unlikely(JS_IsUninitialized(val))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + desc->value = JS_DupValue(ctx, val); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return -1; + goto retry; + } + } else { + desc->value = JS_DupValue(ctx, pr->u.value); + } + } else { + /* for consistency, send the exception even if desc is NULL */ + if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF)) { + if (unlikely(JS_IsUninitialized(*pr->u.var_ref->pvalue))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* nothing to do: delay instantiation until actual value and/or attributes are read */ + } + } + return TRUE; + } + if (p->is_exotic) { + if (p->fast_array) { + /* specific case for fast arrays */ + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx; + idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + if (desc) { + desc->flags = JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | + JS_PROP_CONFIGURABLE; + desc->getter = JS_UNDEFINED; + desc->setter = JS_UNDEFINED; + desc->value = JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); + } + return TRUE; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->get_own_property) { + return em->get_own_property(ctx, desc, + JS_MKPTR(JS_TAG_OBJECT, p), prop); + } + } + } + return FALSE; +} + +int JS_GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop) +{ + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + return JS_GetOwnPropertyInternal(ctx, desc, JS_VALUE_GET_OBJ(obj), prop); +} + +/* return -1 if exception (Proxy object only) or TRUE/FALSE */ +int JS_IsExtensible(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_isExtensible(ctx, obj); + else + return p->extensible; +} + +/* return -1 if exception (Proxy object only) or TRUE/FALSE */ +int JS_PreventExtensions(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_preventExtensions(ctx, obj); + p->extensible = FALSE; + return TRUE; +} + +/* return -1 if exception otherwise TRUE or FALSE */ +int JS_HasProperty(JSContext *ctx, JSValueConst obj, JSAtom prop) +{ + JSObject *p; + int ret; + JSValue obj1; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + for(;;) { + if (p->is_exotic) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->has_property) { + /* has_property can free the prototype */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + ret = em->has_property(ctx, obj1, prop); + JS_FreeValue(ctx, obj1); + return ret; + } + } + /* JS_GetOwnPropertyInternal can free the prototype */ + JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + ret = JS_GetOwnPropertyInternal(ctx, NULL, p, prop); + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + if (ret != 0) + return ret; + if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return -1; + return FALSE; + } + } + p = p->shape->proto; + if (!p) + break; + } + return FALSE; +} + +/* val must be a symbol */ +static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val) +{ + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + return js_get_atom_index(ctx->rt, p); +} + +/* return JS_ATOM_NULL in case of exception */ +JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val) +{ + JSAtom atom; + uint32_t tag; + tag = JS_VALUE_GET_TAG(val); + if (tag == JS_TAG_INT && + (uint32_t)JS_VALUE_GET_INT(val) <= JS_ATOM_MAX_INT) { + /* fast path for integer values */ + atom = __JS_AtomFromUInt32(JS_VALUE_GET_INT(val)); + } else if (tag == JS_TAG_SYMBOL) { + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + atom = JS_DupAtom(ctx, js_get_atom_index(ctx->rt, p)); + } else { + JSValue str; + str = JS_ToPropertyKey(ctx, val); + if (JS_IsException(str)) + return JS_ATOM_NULL; + if (JS_VALUE_GET_TAG(str) == JS_TAG_SYMBOL) { + atom = js_symbol_to_atom(ctx, str); + } else { + atom = JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(str)); + } + } + return atom; +} + +static JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop) +{ + JSAtom atom; + JSValue ret; + + if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT && + JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) { + JSObject *p; + uint32_t idx; + /* fast path for array access */ + p = JS_VALUE_GET_OBJ(this_obj); + idx = JS_VALUE_GET_INT(prop); + switch(p->class_id) { + case JS_CLASS_ARRAY: + case JS_CLASS_ARGUMENTS: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_DupValue(ctx, p->u.array.u.values[idx]); + case JS_CLASS_INT8_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewInt32(ctx, p->u.array.u.int8_ptr[idx]); + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_UINT8_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewInt32(ctx, p->u.array.u.uint8_ptr[idx]); + case JS_CLASS_INT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewInt32(ctx, p->u.array.u.int16_ptr[idx]); + case JS_CLASS_UINT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewInt32(ctx, p->u.array.u.uint16_ptr[idx]); + case JS_CLASS_INT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewInt32(ctx, p->u.array.u.int32_ptr[idx]); + case JS_CLASS_UINT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewUint32(ctx, p->u.array.u.uint32_ptr[idx]); + case JS_CLASS_BIG_INT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewBigInt64(ctx, p->u.array.u.int64_ptr[idx]); + case JS_CLASS_BIG_UINT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return JS_NewBigUint64(ctx, p->u.array.u.uint64_ptr[idx]); + case JS_CLASS_FLOAT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return __JS_NewFloat64(ctx, p->u.array.u.float_ptr[idx]); + case JS_CLASS_FLOAT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) goto slow_path; + return __JS_NewFloat64(ctx, p->u.array.u.double_ptr[idx]); + default: + goto slow_path; + } + } else { + slow_path: + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) + return JS_EXCEPTION; + ret = JS_GetProperty(ctx, this_obj, atom); + JS_FreeAtom(ctx, atom); + return ret; + } +} + +JSValue JS_GetPropertyUint32(JSContext *ctx, JSValueConst this_obj, + uint32_t idx) +{ + return JS_GetPropertyValue(ctx, this_obj, JS_NewUint32(ctx, idx)); +} + +/* Check if an object has a generalized numeric property. Return value: + -1 for exception, + TRUE if property exists, stored into *pval, + FALSE if proprty does not exist. + */ +static int JS_TryGetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, JSValue *pval) +{ + JSValue val = JS_UNDEFINED; + JSAtom prop; + int present; + + if (likely((uint64_t)idx <= JS_ATOM_MAX_INT)) { + /* fast path */ + present = JS_HasProperty(ctx, obj, __JS_AtomFromUInt32(idx)); + if (present > 0) { + val = JS_GetPropertyValue(ctx, obj, JS_NewInt32(ctx, idx)); + if (unlikely(JS_IsException(val))) + present = -1; + } + } else { + prop = JS_NewAtomInt64(ctx, idx); + present = -1; + if (likely(prop != JS_ATOM_NULL)) { + present = JS_HasProperty(ctx, obj, prop); + if (present > 0) { + val = JS_GetProperty(ctx, obj, prop); + if (unlikely(JS_IsException(val))) + present = -1; + } + JS_FreeAtom(ctx, prop); + } + } + *pval = val; + return present; +} + +static JSValue JS_GetPropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx) +{ + JSAtom prop; + JSValue val; + + if ((uint64_t)idx <= INT32_MAX) { + /* fast path for fast arrays */ + return JS_GetPropertyValue(ctx, obj, JS_NewInt32(ctx, idx)); + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) + return JS_EXCEPTION; + + val = JS_GetProperty(ctx, obj, prop); + JS_FreeAtom(ctx, prop); + return val; +} + +JSValue JS_GetPropertyStr(JSContext *ctx, JSValueConst this_obj, + const char *prop) +{ + JSAtom atom; + JSValue ret; + atom = JS_NewAtom(ctx, prop); + ret = JS_GetProperty(ctx, this_obj, atom); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* Note: the property value is not initialized. Return NULL if memory + error. */ +static JSProperty *add_property(JSContext *ctx, + JSObject *p, JSAtom prop, int prop_flags) +{ + JSShape *sh, *new_sh; + + sh = p->shape; + if (sh->is_hashed) { + /* try to find an existing shape */ + new_sh = find_hashed_shape_prop(ctx->rt, sh, prop, prop_flags); + if (new_sh) { + /* matching shape found: use it */ + /* the property array may need to be resized */ + if (new_sh->prop_size != sh->prop_size) { + JSProperty *new_prop; + new_prop = js_realloc(ctx, p->prop, sizeof(p->prop[0]) * + new_sh->prop_size); + if (!new_prop) + return NULL; + p->prop = new_prop; + } + p->shape = js_dup_shape(new_sh); + js_free_shape(ctx->rt, sh); + return &p->prop[new_sh->prop_count - 1]; + } else if (sh->header.ref_count != 1) { + /* if the shape is shared, clone it */ + new_sh = js_clone_shape(ctx, sh); + if (!new_sh) + return NULL; + /* hash the cloned shape */ + new_sh->is_hashed = TRUE; + js_shape_hash_link(ctx->rt, new_sh); + js_free_shape(ctx->rt, p->shape); + p->shape = new_sh; + } + } + assert(p->shape->header.ref_count == 1); + if (add_shape_property(ctx, &p->shape, p, prop, prop_flags)) + return NULL; + return &p->prop[p->shape->prop_count - 1]; +} + +/* can be called on Array or Arguments objects. return < 0 if + memory alloc error. */ +static no_inline __exception int convert_fast_array_to_array(JSContext *ctx, + JSObject *p) +{ + JSProperty *pr; + JSShape *sh; + JSValue *tab; + uint32_t i, len, new_count; + + if (js_shape_prepare_update(ctx, p, NULL)) + return -1; + len = p->u.array.count; + /* resize the properties once to simplify the error handling */ + sh = p->shape; + new_count = sh->prop_count + len; + if (new_count > sh->prop_size) { + if (resize_properties(ctx, &p->shape, p, new_count)) + return -1; + } + + tab = p->u.array.u.values; + for(i = 0; i < len; i++) { + /* add_property cannot fail here but + __JS_AtomFromUInt32(i) fails for i > INT32_MAX */ + pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E); + pr->u.value = *tab++; + } + js_free(ctx, p->u.array.u.values); + p->u.array.count = 0; + p->u.array.u.values = NULL; /* fail safe */ + p->u.array.u1.size = 0; + p->fast_array = 0; + return 0; +} + +static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *lpr, *prop; + JSProperty *pr1; + uint32_t lpr_idx; + intptr_t h, h1; + + redo: + sh = p->shape; + h1 = atom & sh->prop_hash_mask; + h = prop_hash_end(sh)[-h1 - 1]; + prop = get_shape_prop(sh); + lpr = NULL; + lpr_idx = 0; /* prevent warning */ + while (h != 0) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + /* found ! */ + if (!(pr->flags & JS_PROP_CONFIGURABLE)) + return FALSE; + /* realloc the shape if needed */ + if (lpr) + lpr_idx = lpr - get_shape_prop(sh); + if (js_shape_prepare_update(ctx, p, &pr)) + return -1; + sh = p->shape; + /* remove property */ + if (lpr) { + lpr = get_shape_prop(sh) + lpr_idx; + lpr->hash_next = pr->hash_next; + } else { + prop_hash_end(sh)[-h1 - 1] = pr->hash_next; + } + sh->deleted_prop_count++; + /* free the entry */ + pr1 = &p->prop[h - 1]; + free_property(ctx->rt, pr1, pr->flags); + JS_FreeAtom(ctx, pr->atom); + /* put default values */ + pr->flags = 0; + pr->atom = JS_ATOM_NULL; + pr1->u.value = JS_UNDEFINED; + + /* compact the properties if too many deleted properties */ + if (sh->deleted_prop_count >= 8 && + sh->deleted_prop_count >= ((unsigned)sh->prop_count / 2)) { + compact_properties(ctx, p); + } + return TRUE; + } + lpr = pr; + h = pr->hash_next; + } + + if (p->is_exotic) { + if (p->fast_array) { + uint32_t idx; + if (JS_AtomIsArrayIndex(ctx, &idx, atom) && + idx < p->u.array.count) { + if (p->class_id == JS_CLASS_ARRAY || + p->class_id == JS_CLASS_ARGUMENTS) { + /* Special case deleting the last element of a fast Array */ + if (idx == p->u.array.count - 1) { + JS_FreeValue(ctx, p->u.array.u.values[idx]); + p->u.array.count = idx; + return TRUE; + } + if (convert_fast_array_to_array(ctx, p)) + return -1; + goto redo; + } else { + return FALSE; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->delete_property) { + return em->delete_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), atom); + } + } + } + /* not found */ + return TRUE; +} + +static int call_setter(JSContext *ctx, JSObject *setter, + JSValueConst this_obj, JSValue val, int flags) +{ + JSValue ret, func; + if (likely(setter)) { + func = JS_MKPTR(JS_TAG_OBJECT, setter); + /* Note: the field could be removed in the setter */ + func = JS_DupValue(ctx, func); + ret = JS_CallFree(ctx, func, this_obj, 1, (JSValueConst *)&val); + JS_FreeValue(ctx, val); + if (JS_IsException(ret)) + return -1; + JS_FreeValue(ctx, ret); + return TRUE; + } else { + JS_FreeValue(ctx, val); + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeError(ctx, "no setter for property"); + return -1; + } + return FALSE; + } +} + +/* set the array length and remove the array elements if necessary. */ +static int set_array_length(JSContext *ctx, JSObject *p, JSValue val, + int flags) +{ + uint32_t len, idx, cur_len; + int i, ret; + + /* Note: this call can reallocate the properties of 'p' */ + ret = JS_ToArrayLengthFree(ctx, &len, val, FALSE); + if (ret) + return -1; + /* JS_ToArrayLengthFree() must be done before the read-only test */ + if (unlikely(!(p->shape->prop[0].flags & JS_PROP_WRITABLE))) + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + + if (likely(p->fast_array)) { + uint32_t old_len = p->u.array.count; + if (len < old_len) { + for(i = len; i < old_len; i++) { + JS_FreeValue(ctx, p->u.array.u.values[i]); + } + p->u.array.count = len; + } + p->prop[0].u.value = JS_NewUint32(ctx, len); + } else { + /* Note: length is always a uint32 because the object is an + array */ + JS_ToUint32(ctx, &cur_len, p->prop[0].u.value); + if (len < cur_len) { + uint32_t d; + JSShape *sh; + JSShapeProperty *pr; + + d = cur_len - len; + sh = p->shape; + if (d <= sh->prop_count) { + JSAtom atom; + + /* faster to iterate */ + while (cur_len > len) { + atom = JS_NewAtomUInt32(ctx, cur_len - 1); + ret = delete_property(ctx, p, atom); + JS_FreeAtom(ctx, atom); + if (unlikely(!ret)) { + /* unlikely case: property is not + configurable */ + break; + } + cur_len--; + } + } else { + /* faster to iterate thru all the properties. Need two + passes in case one of the property is not + configurable */ + cur_len = len; + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; + i++, pr++) { + if (pr->atom != JS_ATOM_NULL && + JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { + if (idx >= cur_len && + !(pr->flags & JS_PROP_CONFIGURABLE)) { + cur_len = idx + 1; + } + } + } + + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; + i++, pr++) { + if (pr->atom != JS_ATOM_NULL && + JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { + if (idx >= cur_len) { + /* remove the property */ + delete_property(ctx, p, pr->atom); + /* WARNING: the shape may have been modified */ + sh = p->shape; + pr = get_shape_prop(sh) + i; + } + } + } + } + } else { + cur_len = len; + } + set_value(ctx, &p->prop[0].u.value, JS_NewUint32(ctx, cur_len)); + if (unlikely(cur_len > len)) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "not configurable"); + } + } + return TRUE; +} + +/* return -1 if exception */ +static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len) +{ + uint32_t new_size; + size_t slack; + JSValue *new_array_prop; + /* XXX: potential arithmetic overflow */ + new_size = max_int(new_len, p->u.array.u1.size * 3 / 2); + new_array_prop = js_realloc2(ctx, p->u.array.u.values, sizeof(JSValue) * new_size, &slack); + if (!new_array_prop) + return -1; + new_size += slack / sizeof(*new_array_prop); + p->u.array.u.values = new_array_prop; + p->u.array.u1.size = new_size; + return 0; +} + +/* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array = + TRUE and p->extensible = TRUE */ +static int add_fast_array_element(JSContext *ctx, JSObject *p, + JSValue val, int flags) +{ + uint32_t new_len, array_len; + /* extend the array by one */ + /* XXX: convert to slow array if new_len > 2^31-1 elements */ + new_len = p->u.array.count + 1; + /* update the length if necessary. We assume that if the length is + not an integer, then if it >= 2^31. */ + if (likely(JS_VALUE_GET_TAG(p->prop[0].u.value) == JS_TAG_INT)) { + array_len = JS_VALUE_GET_INT(p->prop[0].u.value); + if (new_len > array_len) { + if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + } + p->prop[0].u.value = JS_NewInt32(ctx, new_len); + } + } + if (unlikely(new_len > p->u.array.u1.size)) { + if (expand_fast_array(ctx, p, new_len)) { + JS_FreeValue(ctx, val); + return -1; + } + } + p->u.array.u.values[new_len - 1] = val; + p->u.array.count = new_len; + return TRUE; +} + +/* Allocate a new fast array. Its 'length' property is set to zero. It + maximum size is 2^31-1 elements. For convenience, 'len' is a 64 bit + integer. WARNING: the content of the array is not initialized. */ +static JSValue js_allocate_fast_array(JSContext *ctx, int64_t len) +{ + JSValue arr; + JSObject *p; + + if (len > INT32_MAX) + return JS_ThrowRangeError(ctx, "invalid array length"); + arr = JS_NewArray(ctx); + if (JS_IsException(arr)) + return arr; + if (len > 0) { + p = JS_VALUE_GET_OBJ(arr); + if (expand_fast_array(ctx, p, len) < 0) { + JS_FreeValue(ctx, arr); + return JS_EXCEPTION; + } + p->u.array.count = len; + } + return arr; +} + +static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc) +{ + JS_FreeValue(ctx, desc->getter); + JS_FreeValue(ctx, desc->setter); + JS_FreeValue(ctx, desc->value); +} + +/* return -1 in case of exception or TRUE or FALSE. Warning: 'val' is + freed by the function. 'flags' is a bitmask of JS_PROP_NO_ADD, + JS_PROP_THROW or JS_PROP_THROW_STRICT. If JS_PROP_NO_ADD is set, + the new property is not added and an error is raised. 'this_obj' is + the receiver. If obj != this_obj, then obj must be an object + (Reflect.set case). */ +int JS_SetPropertyInternal(JSContext *ctx, JSValueConst obj, + JSAtom prop, JSValue val, JSValueConst this_obj, int flags) +{ + JSObject *p, *p1; + JSShapeProperty *prs; + JSProperty *pr; + uint32_t tag; + JSPropertyDescriptor desc; + int ret; +#if 0 + printf("JS_SetPropertyInternal: "); print_atom(ctx, prop); printf("\n"); +#endif + tag = JS_VALUE_GET_TAG(this_obj); + if (unlikely(tag != JS_TAG_OBJECT)) { + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = NULL; + p1 = JS_VALUE_GET_OBJ(obj); + goto prototype_lookup; + } else { + switch(tag) { + case JS_TAG_NULL: + JS_FreeValue(ctx, val); + JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of null", prop); + return -1; + case JS_TAG_UNDEFINED: + JS_FreeValue(ctx, val); + JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of undefined", prop); + return -1; + default: + /* even on a primitive type we can have setters on the prototype */ + p = NULL; + p1 = JS_VALUE_GET_OBJ(JS_GetPrototypePrimitive(ctx, obj)); + goto prototype_lookup; + } + } + } else { + p = JS_VALUE_GET_OBJ(this_obj); + p1 = JS_VALUE_GET_OBJ(obj); + if (unlikely(p != p1)) + goto retry2; + } + + /* fast path if obj == this_obj */ + retry: + prs = find_own_property(&pr, p1, prop); + if (prs) { + if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | + JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { + /* fast case */ + set_value(ctx, &pr->u.value, val); + return TRUE; + } else if (prs->flags & JS_PROP_LENGTH) { + assert(p->class_id == JS_CLASS_ARRAY); + assert(prop == JS_ATOM_length); + return set_array_length(ctx, p, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + /* JS_PROP_WRITABLE is always true for variable + references, but they are write protected in module name + spaces. */ + if (p->class_id == JS_CLASS_MODULE_NS) + goto read_only_prop; + set_value(ctx, pr->u.var_ref->pvalue, val); + return TRUE; + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry (potentially useless) */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) { + JS_FreeValue(ctx, val); + return -1; + } + goto retry; + } else { + goto read_only_prop; + } + } + + for(;;) { + if (p1->is_exotic) { + if (p1->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx < p1->u.array.count) { + if (unlikely(p == p1)) + return JS_SetPropertyValue(ctx, this_obj, JS_NewInt32(ctx, idx), val, flags); + else + break; + } else if (p1->class_id >= JS_CLASS_UINT8C_ARRAY && + p1->class_id <= JS_CLASS_FLOAT64_ARRAY) { + goto typed_array_oob; + } + } else if (p1->class_id >= JS_CLASS_UINT8C_ARRAY && + p1->class_id <= JS_CLASS_FLOAT64_ARRAY) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) { + JS_FreeValue(ctx, val); + return -1; + } + typed_array_oob: + /* must convert the argument even if out of bound access */ + if (p1->class_id == JS_CLASS_BIG_INT64_ARRAY || + p1->class_id == JS_CLASS_BIG_UINT64_ARRAY) { + int64_t v; + if (JS_ToBigInt64Free(ctx, &v, val)) + return -1; + } else { + val = JS_ToNumberFree(ctx, val); + JS_FreeValue(ctx, val); + if (JS_IsException(val)) + return -1; + } + return TRUE; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p1->class_id].exotic; + if (em) { + JSValue obj1; + if (em->set_property) { + /* set_property can free the prototype */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); + ret = em->set_property(ctx, obj1, prop, + val, this_obj, flags); + JS_FreeValue(ctx, obj1); + JS_FreeValue(ctx, val); + return ret; + } + if (em->get_own_property) { + /* get_own_property can free the prototype */ + obj1 = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); + ret = em->get_own_property(ctx, &desc, + obj1, prop); + JS_FreeValue(ctx, obj1); + if (ret < 0) { + JS_FreeValue(ctx, val); + return ret; + } + if (ret) { + if (desc.flags & JS_PROP_GETSET) { + JSObject *setter; + if (JS_IsUndefined(desc.setter)) + setter = NULL; + else + setter = JS_VALUE_GET_OBJ(desc.setter); + ret = call_setter(ctx, setter, this_obj, val, flags); + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + return ret; + } else { + JS_FreeValue(ctx, desc.value); + if (!(desc.flags & JS_PROP_WRITABLE)) + goto read_only_prop; + if (likely(p == p1)) { + ret = JS_DefineProperty(ctx, this_obj, prop, val, + JS_UNDEFINED, JS_UNDEFINED, + JS_PROP_HAS_VALUE); + JS_FreeValue(ctx, val); + return ret; + } else { + break; + } + } + } + } + } + } + } + p1 = p1->shape->proto; + prototype_lookup: + if (!p1) + break; + + retry2: + prs = find_own_property(&pr, p1, prop); + if (prs) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry (potentially useless) */ + if (JS_AutoInitProperty(ctx, p1, prop, pr, prs)) + return -1; + goto retry2; + } else if (!(prs->flags & JS_PROP_WRITABLE)) { + goto read_only_prop; + } + } + } + + if (unlikely(flags & JS_PROP_NO_ADD)) { + JS_FreeValue(ctx, val); + JS_ThrowReferenceErrorNotDefined(ctx, prop); + return -1; + } + + if (unlikely(!p)) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorOrFalse(ctx, flags, "not an object"); + } + + if (unlikely(!p->extensible)) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); + } + + if (likely(p == JS_VALUE_GET_OBJ(obj))) { + if (p->is_exotic) { + if (p->class_id == JS_CLASS_ARRAY && p->fast_array && + __JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx == p->u.array.count) { + /* fast case */ + return add_fast_array_element(ctx, p, val, flags); + } else { + goto generic_create_prop; + } + } else { + goto generic_create_prop; + } + } else { + pr = add_property(ctx, p, prop, JS_PROP_C_W_E); + if (unlikely(!pr)) { + JS_FreeValue(ctx, val); + return -1; + } + pr->u.value = val; + return TRUE; + } + } else { + /* generic case: modify the property in this_obj if it already exists */ + ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); + if (ret < 0) { + JS_FreeValue(ctx, val); + return ret; + } + if (ret) { + if (desc.flags & JS_PROP_GETSET) { + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorOrFalse(ctx, flags, "setter is forbidden"); + } else { + JS_FreeValue(ctx, desc.value); + if (!(desc.flags & JS_PROP_WRITABLE) || + p->class_id == JS_CLASS_MODULE_NS) { + read_only_prop: + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorReadOnly(ctx, flags, prop); + } + } + ret = JS_DefineProperty(ctx, this_obj, prop, val, + JS_UNDEFINED, JS_UNDEFINED, + JS_PROP_HAS_VALUE); + JS_FreeValue(ctx, val); + return ret; + } else { + generic_create_prop: + ret = JS_CreateProperty(ctx, p, prop, val, JS_UNDEFINED, JS_UNDEFINED, + flags | + JS_PROP_HAS_VALUE | + JS_PROP_HAS_ENUMERABLE | + JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_CONFIGURABLE | + JS_PROP_C_W_E); + JS_FreeValue(ctx, val); + return ret; + } + } +} + +/* flags can be JS_PROP_THROW or JS_PROP_THROW_STRICT */ +static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop, JSValue val, int flags) +{ + if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT && + JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) { + JSObject *p; + uint32_t idx; + double d; + int32_t v; + + /* fast path for array access */ + p = JS_VALUE_GET_OBJ(this_obj); + idx = JS_VALUE_GET_INT(prop); + switch(p->class_id) { + case JS_CLASS_ARRAY: + if (unlikely(idx >= (uint32_t)p->u.array.count)) { + JSObject *p1; + JSShape *sh1; + + /* fast path to add an element to the array */ + if (idx != (uint32_t)p->u.array.count || + !p->fast_array || !p->extensible) + goto slow_path; + /* check if prototype chain has a numeric property */ + p1 = p->shape->proto; + while (p1 != NULL) { + sh1 = p1->shape; + if (p1->class_id == JS_CLASS_ARRAY) { + if (unlikely(!p1->fast_array)) + goto slow_path; + } else if (p1->class_id == JS_CLASS_OBJECT) { + if (unlikely(sh1->has_small_array_index)) + goto slow_path; + } else { + goto slow_path; + } + p1 = sh1->proto; + } + /* add element */ + return add_fast_array_element(ctx, p, val, flags); + } + set_value(ctx, &p->u.array.u.values[idx], val); + break; + case JS_CLASS_ARGUMENTS: + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto slow_path; + set_value(ctx, &p->u.array.u.values[idx], val); + break; + case JS_CLASS_UINT8C_ARRAY: + if (JS_ToUint8ClampFree(ctx, &v, val)) + return -1; + /* Note: the conversion can detach the typed array, so the + array bound check must be done after */ + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint8_ptr[idx] = v; + break; + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + return -1; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint8_ptr[idx] = v; + break; + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + return -1; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint16_ptr[idx] = v; + break; + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + return -1; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint32_ptr[idx] = v; + break; + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + /* XXX: need specific conversion function */ + { + int64_t v; + if (JS_ToBigInt64Free(ctx, &v, val)) + return -1; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint64_ptr[idx] = v; + } + break; + case JS_CLASS_FLOAT32_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) + return -1; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.float_ptr[idx] = d; + break; + case JS_CLASS_FLOAT64_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) + return -1; + if (unlikely(idx >= (uint32_t)p->u.array.count)) { + ta_out_of_bound: + return TRUE; + } + p->u.array.u.double_ptr[idx] = d; + break; + default: + goto slow_path; + } + return TRUE; + } else { + JSAtom atom; + int ret; + slow_path: + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, this_obj, flags); + JS_FreeAtom(ctx, atom); + return ret; + } +} + +int JS_SetPropertyUint32(JSContext *ctx, JSValueConst this_obj, + uint32_t idx, JSValue val) +{ + return JS_SetPropertyValue(ctx, this_obj, JS_NewUint32(ctx, idx), val, + JS_PROP_THROW); +} + +int JS_SetPropertyInt64(JSContext *ctx, JSValueConst this_obj, + int64_t idx, JSValue val) +{ + JSAtom prop; + int res; + + if ((uint64_t)idx <= INT32_MAX) { + /* fast path for fast arrays */ + return JS_SetPropertyValue(ctx, this_obj, JS_NewInt32(ctx, idx), val, + JS_PROP_THROW); + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) { + JS_FreeValue(ctx, val); + return -1; + } + res = JS_SetProperty(ctx, this_obj, prop, val); + JS_FreeAtom(ctx, prop); + return res; +} + +int JS_SetPropertyStr(JSContext *ctx, JSValueConst this_obj, + const char *prop, JSValue val) +{ + JSAtom atom; + int ret; + atom = JS_NewAtom(ctx, prop); + ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, this_obj, JS_PROP_THROW); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* compute the property flags. For each flag: (JS_PROP_HAS_x forces + it, otherwise def_flags is used) + Note: makes assumption about the bit pattern of the flags +*/ +static int get_prop_flags(int flags, int def_flags) +{ + int mask; + mask = (flags >> JS_PROP_HAS_SHIFT) & JS_PROP_C_W_E; + return (flags & mask) | (def_flags & ~mask); +} + +static int JS_CreateProperty(JSContext *ctx, JSObject *p, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, + int flags) +{ + JSProperty *pr; + int ret, prop_flags; + + /* add a new property or modify an existing exotic one */ + if (p->is_exotic) { + if (p->class_id == JS_CLASS_ARRAY) { + uint32_t idx, len; + + if (p->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + idx = __JS_AtomToUInt32(prop); + if (idx == p->u.array.count) { + if (!p->extensible) + goto not_extensible; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) + goto convert_to_array; + prop_flags = get_prop_flags(flags, 0); + if (prop_flags != JS_PROP_C_W_E) + goto convert_to_array; + return add_fast_array_element(ctx, p, + JS_DupValue(ctx, val), flags); + } else { + goto convert_to_array; + } + } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { + /* convert the fast array to normal array */ + convert_to_array: + if (convert_fast_array_to_array(ctx, p)) + return -1; + goto generic_array; + } + } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { + JSProperty *plen; + JSShapeProperty *pslen; + generic_array: + /* update the length field */ + plen = &p->prop[0]; + JS_ToUint32(ctx, &len, plen->u.value); + if ((idx + 1) > len) { + pslen = get_shape_prop(p->shape); + if (unlikely(!(pslen->flags & JS_PROP_WRITABLE))) + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + /* XXX: should update the length after defining + the property */ + len = idx + 1; + set_value(ctx, &plen->u.value, JS_NewUint32(ctx, len)); + } + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return -1; + return JS_ThrowTypeErrorOrFalse(ctx, flags, "cannot create numeric index in typed array"); + } + } else if (!(flags & JS_PROP_NO_EXOTIC)) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em) { + if (em->define_own_property) { + return em->define_own_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), + prop, val, getter, setter, flags); + } + ret = JS_IsExtensible(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + if (ret < 0) + return -1; + if (!ret) + goto not_extensible; + } + } + } + + if (!p->extensible) { + not_extensible: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); + } + + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + prop_flags = (flags & (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | + JS_PROP_GETSET; + } else { + prop_flags = flags & JS_PROP_C_W_E; + } + pr = add_property(ctx, p, prop, prop_flags); + if (unlikely(!pr)) + return -1; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + pr->u.getset.getter = NULL; + if ((flags & JS_PROP_HAS_GET) && JS_IsFunction(ctx, getter)) { + pr->u.getset.getter = + JS_VALUE_GET_OBJ(JS_DupValue(ctx, getter)); + } + pr->u.getset.setter = NULL; + if ((flags & JS_PROP_HAS_SET) && JS_IsFunction(ctx, setter)) { + pr->u.getset.setter = + JS_VALUE_GET_OBJ(JS_DupValue(ctx, setter)); + } + } else { + if (flags & JS_PROP_HAS_VALUE) { + pr->u.value = JS_DupValue(ctx, val); + } else { + pr->u.value = JS_UNDEFINED; + } + } + return TRUE; +} + +/* return FALSE if not OK */ +static BOOL check_define_prop_flags(int prop_flags, int flags) +{ + BOOL has_accessor, is_getset; + + if (!(prop_flags & JS_PROP_CONFIGURABLE)) { + if ((flags & (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) == + (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) { + return FALSE; + } + if ((flags & JS_PROP_HAS_ENUMERABLE) && + (flags & JS_PROP_ENUMERABLE) != (prop_flags & JS_PROP_ENUMERABLE)) + return FALSE; + } + if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + if (!(prop_flags & JS_PROP_CONFIGURABLE)) { + has_accessor = ((flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) != 0); + is_getset = ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET); + if (has_accessor != is_getset) + return FALSE; + if (!has_accessor && !is_getset && !(prop_flags & JS_PROP_WRITABLE)) { + /* not writable: cannot set the writable bit */ + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == + (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) + return FALSE; + } + } + } + return TRUE; +} + +/* ensure that the shape can be safely modified */ +static int js_shape_prepare_update(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs) +{ + JSShape *sh; + uint32_t idx = 0; /* prevent warning */ + + sh = p->shape; + if (sh->is_hashed) { + if (sh->header.ref_count != 1) { + if (pprs) + idx = *pprs - get_shape_prop(sh); + /* clone the shape (the resulting one is no longer hashed) */ + sh = js_clone_shape(ctx, sh); + if (!sh) + return -1; + js_free_shape(ctx->rt, p->shape); + p->shape = sh; + if (pprs) + *pprs = get_shape_prop(sh) + idx; + } else { + js_shape_hash_unlink(ctx->rt, sh); + sh->is_hashed = FALSE; + } + } + return 0; +} + +static int js_update_property_flags(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs, int flags) +{ + if (flags != (*pprs)->flags) { + if (js_shape_prepare_update(ctx, p, pprs)) + return -1; + (*pprs)->flags = flags; + } + return 0; +} + +/* allowed flags: + JS_PROP_CONFIGURABLE, JS_PROP_WRITABLE, JS_PROP_ENUMERABLE + JS_PROP_HAS_GET, JS_PROP_HAS_SET, JS_PROP_HAS_VALUE, + JS_PROP_HAS_CONFIGURABLE, JS_PROP_HAS_WRITABLE, JS_PROP_HAS_ENUMERABLE, + JS_PROP_THROW, JS_PROP_NO_EXOTIC. + If JS_PROP_THROW is set, return an exception instead of FALSE. + if JS_PROP_NO_EXOTIC is set, do not call the exotic + define_own_property callback. + return -1 (exception), FALSE or TRUE. +*/ +int JS_DefineProperty(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, int flags) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + int mask, res; + + if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(this_obj); + + redo_prop_update: + prs = find_own_property(&pr, p, prop); + if (prs) { + /* the range of the Array length property is always tested before */ + if ((prs->flags & JS_PROP_LENGTH) && (flags & JS_PROP_HAS_VALUE)) { + uint32_t array_length; + if (JS_ToArrayLengthFree(ctx, &array_length, + JS_DupValue(ctx, val), FALSE)) { + return -1; + } + /* this code relies on the fact that Uint32 are never allocated */ + val = (JSValueConst)JS_NewUint32(ctx, array_length); + /* prs may have been modified */ + prs = find_own_property(&pr, p, prop); + assert(prs != NULL); + } + /* property already exists */ + if (!check_define_prop_flags(prs->flags, flags)) { + not_configurable: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable"); + } + + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return -1; + goto redo_prop_update; + } + + if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + JSObject *new_getter, *new_setter; + + if (JS_IsFunction(ctx, getter)) { + new_getter = JS_VALUE_GET_OBJ(getter); + } else { + new_getter = NULL; + } + if (JS_IsFunction(ctx, setter)) { + new_setter = JS_VALUE_GET_OBJ(setter); + } else { + new_setter = NULL; + } + + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_GETSET) { + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + /* convert to getset */ + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + free_var_ref(ctx->rt, pr->u.var_ref); + } else { + JS_FreeValue(ctx, pr->u.value); + } + prs->flags = (prs->flags & + (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | + JS_PROP_GETSET; + pr->u.getset.getter = NULL; + pr->u.getset.setter = NULL; + } else { + if (!(prs->flags & JS_PROP_CONFIGURABLE)) { + if ((flags & JS_PROP_HAS_GET) && + new_getter != pr->u.getset.getter) { + goto not_configurable; + } + if ((flags & JS_PROP_HAS_SET) && + new_setter != pr->u.getset.setter) { + goto not_configurable; + } + } + } + if (flags & JS_PROP_HAS_GET) { + if (pr->u.getset.getter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (new_getter) + JS_DupValue(ctx, getter); + pr->u.getset.getter = new_getter; + } + if (flags & JS_PROP_HAS_SET) { + if (pr->u.getset.setter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + if (new_setter) + JS_DupValue(ctx, setter); + pr->u.getset.setter = new_setter; + } + } else { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + /* convert to data descriptor */ + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + if (pr->u.getset.getter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); + pr->u.value = JS_UNDEFINED; + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + /* Note: JS_PROP_VARREF is always writable */ + } else { + if ((prs->flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 && + (flags & JS_PROP_HAS_VALUE)) { + if (!js_same_value(ctx, val, pr->u.value)) { + goto not_configurable; + } else { + return TRUE; + } + } + } + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + if (flags & JS_PROP_HAS_VALUE) { + if (p->class_id == JS_CLASS_MODULE_NS) { + /* JS_PROP_WRITABLE is always true for variable + references, but they are write protected in module name + spaces. */ + if (!js_same_value(ctx, val, *pr->u.var_ref->pvalue)) + goto not_configurable; + } else { + /* update the reference */ + set_value(ctx, pr->u.var_ref->pvalue, + JS_DupValue(ctx, val)); + } + } + /* if writable is set to false, no longer a + reference (for mapped arguments) */ + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == JS_PROP_HAS_WRITABLE) { + JSValue val1; + if (p->class_id == JS_CLASS_MODULE_NS) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "module namespace properties have writable = false"); + } + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + val1 = JS_DupValue(ctx, *pr->u.var_ref->pvalue); + free_var_ref(ctx->rt, pr->u.var_ref); + pr->u.value = val1; + prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); + } + } else if (prs->flags & JS_PROP_LENGTH) { + if (flags & JS_PROP_HAS_VALUE) { + /* Note: no JS code is executable because + 'val' is guaranted to be a Uint32 */ + res = set_array_length(ctx, p, JS_DupValue(ctx, val), + flags); + } else { + res = TRUE; + } + /* still need to reset the writable flag if + needed. The JS_PROP_LENGTH is kept because the + Uint32 test is still done if the length + property is read-only. */ + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == + JS_PROP_HAS_WRITABLE) { + prs = get_shape_prop(p->shape); + if (js_update_property_flags(ctx, p, &prs, + prs->flags & ~JS_PROP_WRITABLE)) + return -1; + } + return res; + } else { + if (flags & JS_PROP_HAS_VALUE) { + JS_FreeValue(ctx, pr->u.value); + pr->u.value = JS_DupValue(ctx, val); + } + if (flags & JS_PROP_HAS_WRITABLE) { + if (js_update_property_flags(ctx, p, &prs, + (prs->flags & ~JS_PROP_WRITABLE) | + (flags & JS_PROP_WRITABLE))) + return -1; + } + } + } + } + mask = 0; + if (flags & JS_PROP_HAS_CONFIGURABLE) + mask |= JS_PROP_CONFIGURABLE; + if (flags & JS_PROP_HAS_ENUMERABLE) + mask |= JS_PROP_ENUMERABLE; + if (js_update_property_flags(ctx, p, &prs, + (prs->flags & ~mask) | (flags & mask))) + return -1; + return TRUE; + } + + /* handle modification of fast array elements */ + if (p->fast_array) { + uint32_t idx; + uint32_t prop_flags; + if (p->class_id == JS_CLASS_ARRAY) { + if (__JS_AtomIsTaggedInt(prop)) { + idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + prop_flags = get_prop_flags(flags, JS_PROP_C_W_E); + if (prop_flags != JS_PROP_C_W_E) + goto convert_to_slow_array; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + convert_to_slow_array: + if (convert_fast_array_to_array(ctx, p)) + return -1; + else + goto redo_prop_update; + } + if (flags & JS_PROP_HAS_VALUE) { + set_value(ctx, &p->u.array.u.values[idx], JS_DupValue(ctx, val)); + } + return TRUE; + } + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + JSValue num; + int ret; + + if (!__JS_AtomIsTaggedInt(prop)) { + /* slow path with to handle all numeric indexes */ + num = JS_AtomIsNumericIndex1(ctx, prop); + if (JS_IsUndefined(num)) + goto typed_array_done; + if (JS_IsException(num)) + return -1; + ret = JS_NumberIsInteger(ctx, num); + if (ret < 0) { + JS_FreeValue(ctx, num); + return -1; + } + if (!ret) { + JS_FreeValue(ctx, num); + return JS_ThrowTypeErrorOrFalse(ctx, flags, "non integer index in typed array"); + } + ret = JS_NumberIsNegativeOrMinusZero(ctx, num); + JS_FreeValue(ctx, num); + if (ret) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "negative index in typed array"); + } + if (!__JS_AtomIsTaggedInt(prop)) + goto typed_array_oob; + } + idx = __JS_AtomToUInt32(prop); + /* if the typed array is detached, p->u.array.count = 0 */ + if (idx >= p->u.array.count) { + typed_array_oob: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound index in typed array"); + } + prop_flags = get_prop_flags(flags, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET) || + prop_flags != (JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE)) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "invalid descriptor flags"); + } + if (flags & JS_PROP_HAS_VALUE) { + return JS_SetPropertyValue(ctx, this_obj, JS_NewInt32(ctx, idx), JS_DupValue(ctx, val), flags); + } + return TRUE; + typed_array_done: ; + } + } + + return JS_CreateProperty(ctx, p, prop, val, getter, setter, flags); +} + +static int JS_DefineAutoInitProperty(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSAutoInitIDEnum id, + void *opaque, int flags) +{ + JSObject *p; + JSProperty *pr; + + if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) + return FALSE; + + p = JS_VALUE_GET_OBJ(this_obj); + + if (find_own_property(&pr, p, prop)) { + /* property already exists */ + abort(); + return FALSE; + } + + /* Specialized CreateProperty */ + pr = add_property(ctx, p, prop, (flags & JS_PROP_C_W_E) | JS_PROP_AUTOINIT); + if (unlikely(!pr)) + return -1; + pr->u.init.realm_and_id = (uintptr_t)JS_DupContext(ctx); + assert((pr->u.init.realm_and_id & 3) == 0); + assert(id <= 3); + pr->u.init.realm_and_id |= id; + pr->u.init.opaque = opaque; + return TRUE; +} + +/* shortcut to add or redefine a new property value */ +int JS_DefinePropertyValue(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSValue val, int flags) +{ + int ret; + ret = JS_DefineProperty(ctx, this_obj, prop, val, JS_UNDEFINED, JS_UNDEFINED, + flags | JS_PROP_HAS_VALUE | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE); + JS_FreeValue(ctx, val); + return ret; +} + +int JS_DefinePropertyValueValue(JSContext *ctx, JSValueConst this_obj, + JSValue prop, JSValue val, int flags) +{ + JSAtom atom; + int ret; + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); + JS_FreeAtom(ctx, atom); + return ret; +} + +int JS_DefinePropertyValueUint32(JSContext *ctx, JSValueConst this_obj, + uint32_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, JS_NewUint32(ctx, idx), + val, flags); +} + +int JS_DefinePropertyValueInt64(JSContext *ctx, JSValueConst this_obj, + int64_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, JS_NewInt64(ctx, idx), + val, flags); +} + +int JS_DefinePropertyValueStr(JSContext *ctx, JSValueConst this_obj, + const char *prop, JSValue val, int flags) +{ + JSAtom atom; + int ret; + atom = JS_NewAtom(ctx, prop); + ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* shortcut to add getter & setter */ +int JS_DefinePropertyGetSet(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSValue getter, JSValue setter, + int flags) +{ + int ret; + ret = JS_DefineProperty(ctx, this_obj, prop, JS_UNDEFINED, getter, setter, + flags | JS_PROP_HAS_GET | JS_PROP_HAS_SET | + JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE); + JS_FreeValue(ctx, getter); + JS_FreeValue(ctx, setter); + return ret; +} + +static int JS_CreateDataPropertyUint32(JSContext *ctx, JSValueConst this_obj, + int64_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, JS_NewInt64(ctx, idx), + val, flags | JS_PROP_CONFIGURABLE | + JS_PROP_ENUMERABLE | JS_PROP_WRITABLE); +} + + +/* return TRUE if 'obj' has a non empty 'name' string */ +static BOOL js_object_has_name(JSContext *ctx, JSValueConst obj) +{ + JSProperty *pr; + JSShapeProperty *prs; + JSValueConst val; + JSString *p; + + prs = find_own_property(&pr, JS_VALUE_GET_OBJ(obj), JS_ATOM_name); + if (!prs) + return FALSE; + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) + return TRUE; + val = pr->u.value; + if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) + return TRUE; + p = JS_VALUE_GET_STRING(val); + return (p->len != 0); +} + +static int JS_DefineObjectName(JSContext *ctx, JSValueConst obj, + JSAtom name, int flags) +{ + if (name != JS_ATOM_NULL + && JS_IsObject(obj) + && !js_object_has_name(ctx, obj) + && JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, JS_AtomToString(ctx, name), flags) < 0) { + return -1; + } + return 0; +} + +static int JS_DefineObjectNameComputed(JSContext *ctx, JSValueConst obj, + JSValueConst str, int flags) +{ + if (JS_IsObject(obj) && + !js_object_has_name(ctx, obj)) { + JSAtom prop; + JSValue name_str; + prop = JS_ValueToAtom(ctx, str); + if (prop == JS_ATOM_NULL) + return -1; + name_str = js_get_function_name(ctx, prop); + JS_FreeAtom(ctx, prop); + if (JS_IsException(name_str)) + return -1; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, name_str, flags) < 0) + return -1; + } + return 0; +} + +#define DEFINE_GLOBAL_LEX_VAR (1 << 7) +#define DEFINE_GLOBAL_FUNC_VAR (1 << 6) + +static JSValue JS_ThrowSyntaxErrorVarRedeclaration(JSContext *ctx, JSAtom prop) +{ + return JS_ThrowSyntaxErrorAtom(ctx, "redeclaration of '%s'", prop); +} + +/* flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */ +/* XXX: could support exotic global object. */ +static int JS_CheckDefineGlobalVar(JSContext *ctx, JSAtom prop, int flags) +{ + JSObject *p; + JSShapeProperty *prs; + + p = JS_VALUE_GET_OBJ(ctx->global_obj); + prs = find_own_property1(p, prop); + /* XXX: should handle JS_PROP_AUTOINIT */ + if (flags & DEFINE_GLOBAL_LEX_VAR) { + if (prs && !(prs->flags & JS_PROP_CONFIGURABLE)) + goto fail_redeclaration; + } else { + if (!prs && !p->extensible) + goto define_error; + if (flags & DEFINE_GLOBAL_FUNC_VAR) { + if (prs) { + if (!(prs->flags & JS_PROP_CONFIGURABLE) && + ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET || + ((prs->flags & (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)) != + (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)))) { + define_error: + JS_ThrowTypeErrorAtom(ctx, "cannot define variable '%s'", + prop); + return -1; + } + } + } + } + /* check if there already is a lexical declaration */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property1(p, prop); + if (prs) { + fail_redeclaration: + JS_ThrowSyntaxErrorVarRedeclaration(ctx, prop); + return -1; + } + return 0; +} + +/* def_flags is (0, DEFINE_GLOBAL_LEX_VAR) | + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE */ +/* XXX: could support exotic global object. */ +static int JS_DefineGlobalVar(JSContext *ctx, JSAtom prop, int def_flags) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSValue val; + int flags; + + if (def_flags & DEFINE_GLOBAL_LEX_VAR) { + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + flags = JS_PROP_ENUMERABLE | (def_flags & JS_PROP_WRITABLE) | + JS_PROP_CONFIGURABLE; + val = JS_UNINITIALIZED; + } else { + p = JS_VALUE_GET_OBJ(ctx->global_obj); + flags = JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | + (def_flags & JS_PROP_CONFIGURABLE); + val = JS_UNDEFINED; + } + prs = find_own_property1(p, prop); + if (prs) + return 0; + if (!p->extensible) + return 0; + pr = add_property(ctx, p, prop, flags); + if (unlikely(!pr)) + return -1; + pr->u.value = val; + return 0; +} + +/* 'def_flags' is 0 or JS_PROP_CONFIGURABLE. */ +/* XXX: could support exotic global object. */ +static int JS_DefineGlobalFunction(JSContext *ctx, JSAtom prop, + JSValueConst func, int def_flags) +{ + + JSObject *p; + JSShapeProperty *prs; + int flags; + + p = JS_VALUE_GET_OBJ(ctx->global_obj); + prs = find_own_property1(p, prop); + flags = JS_PROP_HAS_VALUE | JS_PROP_THROW; + if (!prs || (prs->flags & JS_PROP_CONFIGURABLE)) { + flags |= JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | def_flags | + JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE; + } + if (JS_DefineProperty(ctx, ctx->global_obj, prop, func, + JS_UNDEFINED, JS_UNDEFINED, flags) < 0) + return -1; + return 0; +} + +static JSValue JS_GetGlobalVar(JSContext *ctx, JSAtom prop, + BOOL throw_ref_error) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + /* XXX: should handle JS_PROP_TMASK properties */ + if (unlikely(JS_IsUninitialized(pr->u.value))) + return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return JS_DupValue(ctx, pr->u.value); + } + return JS_GetPropertyInternal(ctx, ctx->global_obj, prop, + ctx->global_obj, throw_ref_error); +} + +/* construct a reference to a global variable */ +static int JS_GetGlobalVarRef(JSContext *ctx, JSAtom prop, JSValue *sp) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + /* XXX: should handle JS_PROP_AUTOINIT properties? */ + /* XXX: conformance: do these tests in + OP_put_var_ref/OP_get_var_ref ? */ + if (unlikely(JS_IsUninitialized(pr->u.value))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { + return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); + } + sp[0] = JS_DupValue(ctx, ctx->global_var_obj); + } else { + int ret; + ret = JS_HasProperty(ctx, ctx->global_obj, prop); + if (ret < 0) + return -1; + if (ret) { + sp[0] = JS_DupValue(ctx, ctx->global_obj); + } else { + sp[0] = JS_UNDEFINED; + } + } + sp[1] = JS_AtomToValue(ctx, prop); + return 0; +} + +/* use for strict variable access: test if the variable exists */ +static int JS_CheckGlobalVar(JSContext *ctx, JSAtom prop) +{ + JSObject *p; + JSShapeProperty *prs; + int ret; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property1(p, prop); + if (prs) { + ret = TRUE; + } else { + ret = JS_HasProperty(ctx, ctx->global_obj, prop); + if (ret < 0) + return -1; + } + return ret; +} + +/* flag = 0: normal variable write + flag = 1: initialize lexical variable + flag = 2: normal variable write, strict check was done before +*/ +static int JS_SetGlobalVar(JSContext *ctx, JSAtom prop, JSValue val, + int flag) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + int flags; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + /* XXX: should handle JS_PROP_AUTOINIT properties? */ + if (flag != 1) { + if (unlikely(JS_IsUninitialized(pr->u.value))) { + JS_FreeValue(ctx, val); + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); + } + } + set_value(ctx, &pr->u.value, val); + return 0; + } + flags = JS_PROP_THROW_STRICT; + if (is_strict_mode(ctx)) + flags |= JS_PROP_NO_ADD; + return JS_SetPropertyInternal(ctx, ctx->global_obj, prop, val, ctx->global_obj, flags); +} + +/* return -1, FALSE or TRUE. return FALSE if not configurable or + invalid object. return -1 in case of exception. + flags can be 0, JS_PROP_THROW or JS_PROP_THROW_STRICT */ +int JS_DeleteProperty(JSContext *ctx, JSValueConst obj, JSAtom prop, int flags) +{ + JSValue obj1; + JSObject *p; + int res; + + obj1 = JS_ToObject(ctx, obj); + if (JS_IsException(obj1)) + return -1; + p = JS_VALUE_GET_OBJ(obj1); + res = delete_property(ctx, p, prop); + JS_FreeValue(ctx, obj1); + if (res != FALSE) + return res; + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeError(ctx, "could not delete property"); + return -1; + } + return FALSE; +} + +int JS_DeletePropertyInt64(JSContext *ctx, JSValueConst obj, int64_t idx, int flags) +{ + JSAtom prop; + int res; + + if ((uint64_t)idx <= JS_ATOM_MAX_INT) { + /* fast path for fast arrays */ + return JS_DeleteProperty(ctx, obj, __JS_AtomFromUInt32(idx), flags); + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) + return -1; + res = JS_DeleteProperty(ctx, obj, prop, flags); + JS_FreeAtom(ctx, prop); + return res; +} + +BOOL JS_IsFunction(JSContext *ctx, JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(val); + switch(p->class_id) { + case JS_CLASS_BYTECODE_FUNCTION: + return TRUE; + case JS_CLASS_PROXY: + return p->u.proxy_data->is_func; + default: + return (ctx->rt->class_array[p->class_id].call != NULL); + } +} + +BOOL JS_IsCFunction(JSContext *ctx, JSValueConst val, JSCFunction *func, int magic) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(val); + if (p->class_id == JS_CLASS_C_FUNCTION) + return (p->u.cfunc.c_function.generic == func && p->u.cfunc.magic == magic); + else + return FALSE; +} + +BOOL JS_IsConstructor(JSContext *ctx, JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(val); + return p->is_constructor; +} + +BOOL JS_SetConstructorBit(JSContext *ctx, JSValueConst func_obj, BOOL val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(func_obj); + p->is_constructor = val; + return TRUE; +} + +BOOL JS_IsError(JSContext *ctx, JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(val); + return (p->class_id == JS_CLASS_ERROR); +} + +/* used to avoid catching interrupt exceptions */ +BOOL JS_IsUncatchableError(JSContext *ctx, JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(val); + return p->class_id == JS_CLASS_ERROR && p->is_uncatchable_error; +} + +void JS_SetUncatchableError(JSContext *ctx, JSValueConst val, BOOL flag) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(val); + if (p->class_id == JS_CLASS_ERROR) + p->is_uncatchable_error = flag; +} + +void JS_ResetUncatchableError(JSContext *ctx) +{ + JS_SetUncatchableError(ctx, ctx->rt->current_exception, FALSE); +} + +void JS_SetOpaque(JSValue obj, void *opaque) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(obj); + p->u.opaque = opaque; + } +} + +/* return NULL if not an object of class class_id */ +void *JS_GetOpaque(JSValueConst obj, JSClassID class_id) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return NULL; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id != class_id) + return NULL; + return p->u.opaque; +} + +void *JS_GetOpaque2(JSContext *ctx, JSValueConst obj, JSClassID class_id) +{ + void *p = JS_GetOpaque(obj, class_id); + if (unlikely(!p)) { + JS_ThrowTypeErrorInvalidClass(ctx, class_id); + } + return p; +} + +#define HINT_STRING 0 +#define HINT_NUMBER 1 +#define HINT_NONE 2 +/* don't try Symbol.toPrimitive */ +#define HINT_FORCE_ORDINARY (1 << 4) + +static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint) +{ + int i; + BOOL force_ordinary; + + JSAtom method_name; + JSValue method, ret; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return val; + force_ordinary = hint & HINT_FORCE_ORDINARY; + hint &= ~HINT_FORCE_ORDINARY; + if (!force_ordinary) { + method = JS_GetProperty(ctx, val, JS_ATOM_Symbol_toPrimitive); + if (JS_IsException(method)) + goto exception; + /* ECMA says *If exoticToPrim is not undefined* but tests in + test262 use null as a non callable converter */ + if (!JS_IsUndefined(method) && !JS_IsNull(method)) { + JSAtom atom; + JSValue arg; + switch(hint) { + case HINT_STRING: + atom = JS_ATOM_string; + break; + case HINT_NUMBER: + atom = JS_ATOM_number; + break; + default: + case HINT_NONE: + atom = JS_ATOM_default; + break; + } + arg = JS_AtomToString(ctx, atom); + ret = JS_CallFree(ctx, method, val, 1, (JSValueConst *)&arg); + JS_FreeValue(ctx, arg); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, val); + if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) + return ret; + JS_FreeValue(ctx, ret); + return JS_ThrowTypeError(ctx, "toPrimitive"); + } + } + if (hint != HINT_STRING) + hint = HINT_NUMBER; + for(i = 0; i < 2; i++) { + if ((i ^ hint) == 0) { + method_name = JS_ATOM_toString; + } else { + method_name = JS_ATOM_valueOf; + } + method = JS_GetProperty(ctx, val, method_name); + if (JS_IsException(method)) + goto exception; + if (JS_IsFunction(ctx, method)) { + ret = JS_CallFree(ctx, method, val, 0, NULL); + if (JS_IsException(ret)) + goto exception; + if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) { + JS_FreeValue(ctx, val); + return ret; + } + JS_FreeValue(ctx, ret); + } else { + JS_FreeValue(ctx, method); + } + } + JS_ThrowTypeError(ctx, "toPrimitive"); +exception: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +static JSValue JS_ToPrimitive(JSContext *ctx, JSValueConst val, int hint) +{ + return JS_ToPrimitiveFree(ctx, JS_DupValue(ctx, val), hint); +} + +void JS_SetIsHTMLDDA(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(obj); + p->is_HTMLDDA = TRUE; +} + +static inline BOOL JS_IsHTMLDDA(JSContext *ctx, JSValueConst obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + return p->is_HTMLDDA; +} + +static int JS_ToBoolFree(JSContext *ctx, JSValue val) +{ + uint32_t tag = JS_VALUE_GET_TAG(val); + switch(tag) { + case JS_TAG_INT: + return JS_VALUE_GET_INT(val) != 0; + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + return JS_VALUE_GET_INT(val); + case JS_TAG_EXCEPTION: + return -1; + case JS_TAG_STRING: + { + BOOL ret = JS_VALUE_GET_STRING(val)->len != 0; + JS_FreeValue(ctx, val); + return ret; + } + case JS_TAG_BIG_INT: +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: +#endif + { + JSBigFloat *p = JS_VALUE_GET_PTR(val); + BOOL ret; + ret = p->num.expn != BF_EXP_ZERO && p->num.expn != BF_EXP_NAN; + JS_FreeValue(ctx, val); + return ret; + } +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_DECIMAL: + { + JSBigDecimal *p = JS_VALUE_GET_PTR(val); + BOOL ret; + ret = p->num.expn != BF_EXP_ZERO && p->num.expn != BF_EXP_NAN; + JS_FreeValue(ctx, val); + return ret; + } +#endif + case JS_TAG_OBJECT: + { + JSObject *p = JS_VALUE_GET_OBJ(val); + BOOL ret; + ret = !p->is_HTMLDDA; + JS_FreeValue(ctx, val); + return ret; + } + break; + default: + if (JS_TAG_IS_FLOAT64(tag)) { + double d = JS_VALUE_GET_FLOAT64(val); + return !isnan(d) && d != 0; + } else { + JS_FreeValue(ctx, val); + return TRUE; + } + } +} + +int JS_ToBool(JSContext *ctx, JSValueConst val) +{ + return JS_ToBoolFree(ctx, JS_DupValue(ctx, val)); +} + +static int skip_spaces(const char *pc) +{ + const uint8_t *p, *p_next, *p_start; + uint32_t c; + + p = p_start = (const uint8_t *)pc; + for (;;) { + c = *p; + if (c < 128) { + if (!((c >= 0x09 && c <= 0x0d) || (c == 0x20))) + break; + p++; + } else { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next); + if (!lre_is_space(c)) + break; + p = p_next; + } + } + return p - p_start; +} + +static inline int to_digit(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + return c - 'a' + 10; + else + return 36; +} + +/* XXX: remove */ +static double js_strtod(const char *str, int radix, BOOL is_float) +{ + double d; + int c; + + if (!is_float || radix != 10) { + const char *p = str; + uint64_t n_max, n; + int int_exp, is_neg; + + is_neg = 0; + if (*p == '-') { + is_neg = 1; + p++; + } + + /* skip leading zeros */ + while (*p == '0') + p++; + n = 0; + if (radix == 10) + n_max = ((uint64_t)-1 - 9) / 10; /* most common case */ + else + n_max = ((uint64_t)-1 - (radix - 1)) / radix; + /* XXX: could be more precise */ + int_exp = 0; + while (*p != '\0') { + c = to_digit((uint8_t)*p); + if (c >= radix) + break; + if (n <= n_max) { + n = n * radix + c; + } else { + if (radix == 10) + goto strtod_case; + int_exp++; + } + p++; + } + d = n; + if (int_exp != 0) { + d *= pow(radix, int_exp); + } + if (is_neg) + d = -d; + } else { + strtod_case: + d = strtod(str, NULL); + } + return d; +} + +#define ATOD_INT_ONLY (1 << 0) +/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ +#define ATOD_ACCEPT_BIN_OCT (1 << 2) +/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ +#define ATOD_ACCEPT_LEGACY_OCTAL (1 << 4) +/* accept _ between digits as a digit separator */ +#define ATOD_ACCEPT_UNDERSCORES (1 << 5) +/* allow a suffix to override the type */ +#define ATOD_ACCEPT_SUFFIX (1 << 6) +/* default type */ +#define ATOD_TYPE_MASK (3 << 7) +#define ATOD_TYPE_FLOAT64 (0 << 7) +#define ATOD_TYPE_BIG_INT (1 << 7) +#ifdef CONFIG_BIGNUM +#define ATOD_TYPE_BIG_FLOAT (2 << 7) +#define ATOD_TYPE_BIG_DECIMAL (3 << 7) +/* assume bigint mode: floats are parsed as integers if no decimal + point nor exponent */ +#define ATOD_MODE_BIGINT (1 << 9) +#endif +/* accept -0x1 */ +#define ATOD_ACCEPT_PREFIX_AFTER_SIGN (1 << 10) + +static JSValue js_string_to_bigint(JSContext *ctx, const char *buf, + int radix, int flags, slimb_t *pexponent) +{ + bf_t a_s, *a = &a_s; + int ret; + JSValue val; + val = JS_NewBigInt(ctx); + if (JS_IsException(val)) + return val; + a = JS_GetBigInt(val); + ret = bf_atof(a, buf, NULL, radix, BF_PREC_INF, BF_RNDZ); + if (ret & BF_ST_MEM_ERROR) { + JS_FreeValue(ctx, val); + return JS_ThrowOutOfMemory(ctx); + } +#ifdef CONFIG_BIGNUM + val = JS_CompactBigInt1(ctx, val, (flags & ATOD_MODE_BIGINT) != 0); +#else + val = JS_CompactBigInt1(ctx, val, FALSE); +#endif + return val; +} + +#ifdef CONFIG_BIGNUM +static JSValue js_string_to_bigfloat(JSContext *ctx, const char *buf, + int radix, int flags, slimb_t *pexponent) +{ + bf_t *a; + int ret; + JSValue val; + + val = JS_NewBigFloat(ctx); + if (JS_IsException(val)) + return val; + a = JS_GetBigFloat(val); + if (flags & ATOD_ACCEPT_SUFFIX) { + /* return the exponent to get infinite precision */ + ret = bf_atof2(a, pexponent, buf, NULL, radix, BF_PREC_INF, + BF_RNDZ | BF_ATOF_EXPONENT); + } else { + ret = bf_atof(a, buf, NULL, radix, ctx->fp_env.prec, + ctx->fp_env.flags); + } + if (ret & BF_ST_MEM_ERROR) { + JS_FreeValue(ctx, val); + return JS_ThrowOutOfMemory(ctx); + } + return val; +} + +static JSValue js_string_to_bigdecimal(JSContext *ctx, const char *buf, + int radix, int flags, slimb_t *pexponent) +{ + bfdec_t *a; + int ret; + JSValue val; + + val = JS_NewBigDecimal(ctx); + if (JS_IsException(val)) + return val; + a = JS_GetBigDecimal(val); + ret = bfdec_atof(a, buf, NULL, BF_PREC_INF, + BF_RNDZ | BF_ATOF_NO_NAN_INF); + if (ret & BF_ST_MEM_ERROR) { + JS_FreeValue(ctx, val); + return JS_ThrowOutOfMemory(ctx); + } + return val; +} +#endif + +/* return an exception in case of memory error. Return JS_NAN if + invalid syntax */ +#ifdef CONFIG_BIGNUM +static JSValue js_atof2(JSContext *ctx, const char *str, const char **pp, + int radix, int flags, slimb_t *pexponent) +#else +static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, + int radix, int flags) +#endif +{ + const char *p, *p_start; + int sep, is_neg; + BOOL is_float, has_legacy_octal; + int atod_type = flags & ATOD_TYPE_MASK; + char buf1[64], *buf; + int i, j, len; + BOOL buf_allocated = FALSE; + JSValue val; + + /* optional separator between digits */ + sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; + has_legacy_octal = FALSE; + + p = str; + p_start = p; + is_neg = 0; + if (p[0] == '+') { + p++; + p_start++; + if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) + goto no_radix_prefix; + } else if (p[0] == '-') { + p++; + p_start++; + is_neg = 1; + if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) + goto no_radix_prefix; + } + if (p[0] == '0') { + if ((p[1] == 'x' || p[1] == 'X') && + (radix == 0 || radix == 16)) { + p += 2; + radix = 16; + } else if ((p[1] == 'o' || p[1] == 'O') && + radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 8; + } else if ((p[1] == 'b' || p[1] == 'B') && + radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 2; + } else if ((p[1] >= '0' && p[1] <= '9') && + radix == 0 && (flags & ATOD_ACCEPT_LEGACY_OCTAL)) { + int i; + has_legacy_octal = TRUE; + sep = 256; + for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) + continue; + if (p[i] == '8' || p[i] == '9') + goto no_prefix; + p += 1; + radix = 8; + } else { + goto no_prefix; + } + /* there must be a digit after the prefix */ + if (to_digit((uint8_t)*p) >= radix) + goto fail; + no_prefix: ; + } else { + no_radix_prefix: + if (!(flags & ATOD_INT_ONLY) && + (atod_type == ATOD_TYPE_FLOAT64 +#ifdef CONFIG_BIGNUM + || atod_type == ATOD_TYPE_BIG_FLOAT +#endif + ) && + strstart(p, "Infinity", &p)) { +#ifdef CONFIG_BIGNUM + if (atod_type == ATOD_TYPE_BIG_FLOAT) { + bf_t *a; + val = JS_NewBigFloat(ctx); + if (JS_IsException(val)) + goto done; + a = JS_GetBigFloat(val); + bf_set_inf(a, is_neg); + } else +#endif + { + double d = 1.0 / 0.0; + if (is_neg) + d = -d; + val = JS_NewFloat64(ctx, d); + } + goto done; + } + } + if (radix == 0) + radix = 10; + is_float = FALSE; + p_start = p; + while (to_digit((uint8_t)*p) < radix + || (*p == sep && (radix != 10 || + p != p_start + 1 || p[-1] != '0') && + to_digit((uint8_t)p[1]) < radix)) { + p++; + } + if (!(flags & ATOD_INT_ONLY)) { + if (*p == '.' && (p > p_start || to_digit((uint8_t)p[1]) < radix)) { + is_float = TRUE; + p++; + if (*p == sep) + goto fail; + while (to_digit((uint8_t)*p) < radix || + (*p == sep && to_digit((uint8_t)p[1]) < radix)) + p++; + } + if (p > p_start && + (((*p == 'e' || *p == 'E') && radix == 10) || + ((*p == 'p' || *p == 'P') && (radix == 2 || radix == 8 || radix == 16)))) { + const char *p1 = p + 1; + is_float = TRUE; + if (*p1 == '+') { + p1++; + } else if (*p1 == '-') { + p1++; + } + if (is_digit((uint8_t)*p1)) { + p = p1 + 1; + while (is_digit((uint8_t)*p) || (*p == sep && is_digit((uint8_t)p[1]))) + p++; + } + } + } + if (p == p_start) + goto fail; + + buf = buf1; + buf_allocated = FALSE; + len = p - p_start; + if (unlikely((len + 2) > sizeof(buf1))) { + buf = js_malloc_rt(ctx->rt, len + 2); /* no exception raised */ + if (!buf) + goto mem_error; + buf_allocated = TRUE; + } + /* remove the separators and the radix prefixes */ + j = 0; + if (is_neg) + buf[j++] = '-'; + for (i = 0; i < len; i++) { + if (p_start[i] != '_') + buf[j++] = p_start[i]; + } + buf[j] = '\0'; + + if (flags & ATOD_ACCEPT_SUFFIX) { + if (*p == 'n') { + p++; + atod_type = ATOD_TYPE_BIG_INT; + } else +#ifdef CONFIG_BIGNUM + if (*p == 'l') { + p++; + atod_type = ATOD_TYPE_BIG_FLOAT; + } else if (*p == 'm') { + p++; + atod_type = ATOD_TYPE_BIG_DECIMAL; + } else if (flags & ATOD_MODE_BIGINT) { + if (!is_float) + atod_type = ATOD_TYPE_BIG_INT; + if (has_legacy_octal) + goto fail; + } else +#endif + { + if (is_float && radix != 10) + goto fail; + } + } else { + if (atod_type == ATOD_TYPE_FLOAT64) { +#ifdef CONFIG_BIGNUM + if (flags & ATOD_MODE_BIGINT) { + if (!is_float) + atod_type = ATOD_TYPE_BIG_INT; + if (has_legacy_octal) + goto fail; + } else +#endif + { + if (is_float && radix != 10) + goto fail; + } + } + } + + switch(atod_type) { + case ATOD_TYPE_FLOAT64: + { + double d; + d = js_strtod(buf, radix, is_float); + /* return int or float64 */ + val = JS_NewFloat64(ctx, d); + } + break; + case ATOD_TYPE_BIG_INT: + if (has_legacy_octal || is_float) + goto fail; + val = ctx->rt->bigint_ops.from_string(ctx, buf, radix, flags, NULL); + break; +#ifdef CONFIG_BIGNUM + case ATOD_TYPE_BIG_FLOAT: + if (has_legacy_octal) + goto fail; + val = ctx->rt->bigfloat_ops.from_string(ctx, buf, radix, flags, + pexponent); + break; + case ATOD_TYPE_BIG_DECIMAL: + if (radix != 10) + goto fail; + val = ctx->rt->bigdecimal_ops.from_string(ctx, buf, radix, flags, NULL); + break; +#endif + default: + abort(); + } + +done: + if (buf_allocated) + js_free_rt(ctx->rt, buf); + if (pp) + *pp = p; + return val; + fail: + val = JS_NAN; + goto done; + mem_error: + val = JS_ThrowOutOfMemory(ctx); + goto done; +} + +#ifdef CONFIG_BIGNUM +static JSValue js_atof(JSContext *ctx, const char *str, const char **pp, + int radix, int flags) +{ + return js_atof2(ctx, str, pp, radix, flags, NULL); +} +#endif + +typedef enum JSToNumberHintEnum { + TON_FLAG_NUMBER, + TON_FLAG_NUMERIC, +} JSToNumberHintEnum; + +static JSValue JS_ToNumberHintFree(JSContext *ctx, JSValue val, + JSToNumberHintEnum flag) +{ + uint32_t tag; + JSValue ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_BIG_INT: + if (flag != TON_FLAG_NUMERIC) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert bigint to number"); + } + ret = val; + break; +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_DECIMAL: + if (flag != TON_FLAG_NUMERIC) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert bigdecimal to number"); + } + ret = val; + break; + case JS_TAG_BIG_FLOAT: + if (flag != TON_FLAG_NUMERIC) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert bigfloat to number"); + } + ret = val; + break; +#endif + case JS_TAG_FLOAT64: + case JS_TAG_INT: + case JS_TAG_EXCEPTION: + ret = val; + break; + case JS_TAG_BOOL: + case JS_TAG_NULL: + ret = JS_NewInt32(ctx, JS_VALUE_GET_INT(val)); + break; + case JS_TAG_UNDEFINED: + ret = JS_NAN; + break; + case JS_TAG_OBJECT: + val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); + if (JS_IsException(val)) + return JS_EXCEPTION; + goto redo; + case JS_TAG_STRING: + { + const char *str; + const char *p; + size_t len; + + str = JS_ToCStringLen(ctx, &len, val); + JS_FreeValue(ctx, val); + if (!str) + return JS_EXCEPTION; + p = str; + p += skip_spaces(p); + if ((p - str) == len) { + ret = JS_NewInt32(ctx, 0); + } else { + int flags = ATOD_ACCEPT_BIN_OCT; + ret = js_atof(ctx, p, &p, 0, flags); + if (!JS_IsException(ret)) { + p += skip_spaces(p); + if ((p - str) != len) { + JS_FreeValue(ctx, ret); + ret = JS_NAN; + } + } + } + JS_FreeCString(ctx, str); + } + break; + case JS_TAG_SYMBOL: + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert symbol to number"); + default: + JS_FreeValue(ctx, val); + ret = JS_NAN; + break; + } + return ret; +} + +static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val) +{ + return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMBER); +} + +static JSValue JS_ToNumericFree(JSContext *ctx, JSValue val) +{ + return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMERIC); +} + +static JSValue JS_ToNumeric(JSContext *ctx, JSValueConst val) +{ + return JS_ToNumericFree(ctx, JS_DupValue(ctx, val)); +} + +static __exception int __JS_ToFloat64Free(JSContext *ctx, double *pres, + JSValue val) +{ + double d; + uint32_t tag; + + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = JS_FLOAT64_NAN; + return -1; + } + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + d = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + d = JS_VALUE_GET_FLOAT64(val); + break; + case JS_TAG_BIG_INT: +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: +#endif + { + JSBigFloat *p = JS_VALUE_GET_PTR(val); + /* XXX: there can be a double rounding issue with some + primitives (such as JS_ToUint8ClampFree()), but it is + not critical to fix it. */ + bf_get_float64(&p->num, &d, BF_RNDN); + JS_FreeValue(ctx, val); + } + break; + default: + abort(); + } + *pres = d; + return 0; +} + +static inline int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val) +{ + uint32_t tag; + + tag = JS_VALUE_GET_TAG(val); + if (tag <= JS_TAG_NULL) { + *pres = JS_VALUE_GET_INT(val); + return 0; + } else if (JS_TAG_IS_FLOAT64(tag)) { + *pres = JS_VALUE_GET_FLOAT64(val); + return 0; + } else { + return __JS_ToFloat64Free(ctx, pres, val); + } +} + +int JS_ToFloat64(JSContext *ctx, double *pres, JSValueConst val) +{ + return JS_ToFloat64Free(ctx, pres, JS_DupValue(ctx, val)); +} + +static JSValue JS_ToNumber(JSContext *ctx, JSValueConst val) +{ + return JS_ToNumberFree(ctx, JS_DupValue(ctx, val)); +} + +/* same as JS_ToNumber() but return 0 in case of NaN/Undefined */ +static __maybe_unused JSValue JS_ToIntegerFree(JSContext *ctx, JSValue val) +{ + uint32_t tag; + JSValue ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_NewInt32(ctx, JS_VALUE_GET_INT(val)); + break; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + ret = JS_NewInt32(ctx, 0); + } else { + /* convert -0 to +0 */ + d = trunc(d) + 0.0; + ret = JS_NewFloat64(ctx, d); + } + } + break; +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + { + bf_t a_s, *a, r_s, *r = &r_s; + BOOL is_nan; + + a = JS_ToBigFloat(ctx, &a_s, val); + if (!a) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + if (!bf_is_finite(a)) { + is_nan = bf_is_nan(a); + if (is_nan) + ret = JS_NewInt32(ctx, 0); + else + ret = JS_DupValue(ctx, val); + } else { + ret = JS_NewBigInt(ctx); + if (!JS_IsException(ret)) { + r = JS_GetBigInt(ret); + bf_set(r, a); + bf_rint(r, BF_RNDZ); + ret = JS_CompactBigInt(ctx, ret); + } + } + if (a == &a_s) + bf_delete(a); + JS_FreeValue(ctx, val); + } + break; +#endif + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return val; + goto redo; + } + return ret; +} + +/* Note: the integer value is satured to 32 bits */ +static int JS_ToInt32SatFree(JSContext *ctx, int *pres, JSValue val) +{ + uint32_t tag; + int ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_EXCEPTION: + *pres = 0; + return -1; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + ret = 0; + } else { + if (d < INT32_MIN) + ret = INT32_MIN; + else if (d > INT32_MAX) + ret = INT32_MAX; + else + ret = (int)d; + } + } + break; +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + { + JSBigFloat *p = JS_VALUE_GET_PTR(val); + bf_get_int32(&ret, &p->num, 0); + JS_FreeValue(ctx, val); + } + break; +#endif + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValueConst val) +{ + return JS_ToInt32SatFree(ctx, pres, JS_DupValue(ctx, val)); +} + +int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValueConst val, + int min, int max, int min_offset) +{ + int res = JS_ToInt32SatFree(ctx, pres, JS_DupValue(ctx, val)); + if (res == 0) { + if (*pres < min) { + *pres += min_offset; + if (*pres < min) + *pres = min; + } else { + if (*pres > max) + *pres = max; + } + } + return res; +} + +static int JS_ToInt64SatFree(JSContext *ctx, int64_t *pres, JSValue val) +{ + uint32_t tag; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + *pres = JS_VALUE_GET_INT(val); + return 0; + case JS_TAG_EXCEPTION: + *pres = 0; + return -1; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + *pres = 0; + } else { + if (d < INT64_MIN) + *pres = INT64_MIN; + else if (d >= 0x1p63) /* must use INT64_MAX + 1 because INT64_MAX cannot be exactly represented as a double */ + *pres = INT64_MAX; + else + *pres = (int64_t)d; + } + } + return 0; +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + { + JSBigFloat *p = JS_VALUE_GET_PTR(val); + bf_get_int64(pres, &p->num, 0); + JS_FreeValue(ctx, val); + } + return 0; +#endif + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } +} + +int JS_ToInt64Sat(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + return JS_ToInt64SatFree(ctx, pres, JS_DupValue(ctx, val)); +} + +int JS_ToInt64Clamp(JSContext *ctx, int64_t *pres, JSValueConst val, + int64_t min, int64_t max, int64_t neg_offset) +{ + int res = JS_ToInt64SatFree(ctx, pres, JS_DupValue(ctx, val)); + if (res == 0) { + if (*pres < 0) + *pres += neg_offset; + if (*pres < min) + *pres = min; + else if (*pres > max) + *pres = max; + } + return res; +} + +/* Same as JS_ToInt32Free() but with a 64 bit result. Return (<0, 0) + in case of exception */ +static int JS_ToInt64Free(JSContext *ctx, int64_t *pres, JSValue val) +{ + uint32_t tag; + int64_t ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + double d; + int e; + d = JS_VALUE_GET_FLOAT64(val); + u.d = d; + /* we avoid doing fmod(x, 2^64) */ + e = (u.u64 >> 52) & 0x7ff; + if (likely(e <= (1023 + 62))) { + /* fast case */ + ret = (int64_t)d; + } else if (e <= (1023 + 62 + 53)) { + uint64_t v; + /* remainder modulo 2^64 */ + v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + ret = v << ((e - 1023) - 52); + /* take the sign into account */ + if (u.u64 >> 63) + ret = -ret; + } else { + ret = 0; /* also handles NaN and +inf */ + } + } + break; +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + { + JSBigFloat *p = JS_VALUE_GET_PTR(val); + bf_get_int64(&ret, &p->num, BF_GET_INT_MOD); + JS_FreeValue(ctx, val); + } + break; +#endif + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +int JS_ToInt64(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + return JS_ToInt64Free(ctx, pres, JS_DupValue(ctx, val)); +} + +int JS_ToInt64Ext(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + if (JS_IsBigInt(ctx, val)) + return JS_ToBigInt64(ctx, pres, val); + else + return JS_ToInt64(ctx, pres, val); +} + +/* return (<0, 0) in case of exception */ +static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val) +{ + uint32_t tag; + int32_t ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + double d; + int e; + d = JS_VALUE_GET_FLOAT64(val); + u.d = d; + /* we avoid doing fmod(x, 2^32) */ + e = (u.u64 >> 52) & 0x7ff; + if (likely(e <= (1023 + 30))) { + /* fast case */ + ret = (int32_t)d; + } else if (e <= (1023 + 30 + 53)) { + uint64_t v; + /* remainder modulo 2^32 */ + v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + v = v << ((e - 1023) - 52 + 32); + ret = v >> 32; + /* take the sign into account */ + if (u.u64 >> 63) + ret = -ret; + } else { + ret = 0; /* also handles NaN and +inf */ + } + } + break; +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + { + JSBigFloat *p = JS_VALUE_GET_PTR(val); + bf_get_int32(&ret, &p->num, BF_GET_INT_MOD); + JS_FreeValue(ctx, val); + } + break; +#endif + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +int JS_ToInt32(JSContext *ctx, int32_t *pres, JSValueConst val) +{ + return JS_ToInt32Free(ctx, pres, JS_DupValue(ctx, val)); +} + +static inline int JS_ToUint32Free(JSContext *ctx, uint32_t *pres, JSValue val) +{ + return JS_ToInt32Free(ctx, (int32_t *)pres, val); +} + +static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val) +{ + uint32_t tag; + int res; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + res = JS_VALUE_GET_INT(val); +#ifdef CONFIG_BIGNUM + int_clamp: +#endif + res = max_int(0, min_int(255, res)); + break; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + res = 0; + } else { + if (d < 0) + res = 0; + else if (d > 255) + res = 255; + else + res = lrint(d); + } + } + break; +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + { + JSBigFloat *p = JS_VALUE_GET_PTR(val); + bf_t r_s, *r = &r_s; + bf_init(ctx->bf_ctx, r); + bf_set(r, &p->num); + bf_rint(r, BF_RNDN); + bf_get_int32(&res, r, 0); + bf_delete(r); + JS_FreeValue(ctx, val); + } + goto int_clamp; +#endif + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = res; + return 0; +} + +static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, + JSValue val, BOOL is_array_ctor) +{ + uint32_t tag, len; + + tag = JS_VALUE_GET_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + { + int v; + v = JS_VALUE_GET_INT(val); + if (v < 0) + goto fail; + len = v; + } + break; + case JS_TAG_BIG_INT: +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: +#endif + { + JSBigFloat *p = JS_VALUE_GET_PTR(val); + bf_t a; + BOOL res; + bf_get_int32((int32_t *)&len, &p->num, BF_GET_INT_MOD); + bf_init(ctx->bf_ctx, &a); + bf_set_ui(&a, len); + res = bf_cmp_eq(&a, &p->num); + bf_delete(&a); + JS_FreeValue(ctx, val); + if (!res) + goto fail; + } + break; + default: + if (JS_TAG_IS_FLOAT64(tag)) { + double d; + d = JS_VALUE_GET_FLOAT64(val); + if (!(d >= 0 && d <= UINT32_MAX)) + goto fail; + len = (uint32_t)d; + if (len != d) + goto fail; + } else { + uint32_t len1; + + if (is_array_ctor) { + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return -1; + /* cannot recurse because val is a number */ + if (JS_ToArrayLengthFree(ctx, &len, val, TRUE)) + return -1; + } else { + /* legacy behavior: must do the conversion twice and compare */ + if (JS_ToUint32(ctx, &len, val)) { + JS_FreeValue(ctx, val); + return -1; + } + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return -1; + /* cannot recurse because val is a number */ + if (JS_ToArrayLengthFree(ctx, &len1, val, FALSE)) + return -1; + if (len1 != len) { + fail: + JS_ThrowRangeError(ctx, "invalid array length"); + return -1; + } + } + } + break; + } + *plen = len; + return 0; +} + +#define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1) + +static BOOL is_safe_integer(double d) +{ + return isfinite(d) && floor(d) == d && + fabs(d) <= (double)MAX_SAFE_INTEGER; +} + +int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValueConst val) +{ + int64_t v; + if (JS_ToInt64Sat(ctx, &v, val)) + return -1; + if (v < 0 || v > MAX_SAFE_INTEGER) { + JS_ThrowRangeError(ctx, "invalid array index"); + *plen = 0; + return -1; + } + *plen = v; + return 0; +} + +/* convert a value to a length between 0 and MAX_SAFE_INTEGER. + return -1 for exception */ +static __exception int JS_ToLengthFree(JSContext *ctx, int64_t *plen, + JSValue val) +{ + int res = JS_ToInt64Clamp(ctx, plen, val, 0, MAX_SAFE_INTEGER, 0); + JS_FreeValue(ctx, val); + return res; +} + +/* Note: can return an exception */ +static int JS_NumberIsInteger(JSContext *ctx, JSValueConst val) +{ + double d; + if (!JS_IsNumber(val)) + return FALSE; + if (unlikely(JS_ToFloat64(ctx, &d, val))) + return -1; + return isfinite(d) && floor(d) == d; +} + +static BOOL JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValueConst val) +{ + uint32_t tag; + + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + { + int v; + v = JS_VALUE_GET_INT(val); + return (v < 0); + } + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + u.d = JS_VALUE_GET_FLOAT64(val); + return (u.u64 >> 63); + } + case JS_TAG_BIG_INT: + { + JSBigFloat *p = JS_VALUE_GET_PTR(val); + /* Note: integer zeros are not necessarily positive */ + return p->num.sign && !bf_is_zero(&p->num); + } +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + { + JSBigFloat *p = JS_VALUE_GET_PTR(val); + return p->num.sign; + } + break; + case JS_TAG_BIG_DECIMAL: + { + JSBigDecimal *p = JS_VALUE_GET_PTR(val); + return p->num.sign; + } + break; +#endif + default: + return FALSE; + } +} + +static JSValue js_bigint_to_string1(JSContext *ctx, JSValueConst val, int radix) +{ + JSValue ret; + bf_t a_s, *a; + char *str; + int saved_sign; + + a = JS_ToBigInt(ctx, &a_s, val); + if (!a) + return JS_EXCEPTION; + saved_sign = a->sign; + if (a->expn == BF_EXP_ZERO) + a->sign = 0; + str = bf_ftoa(NULL, a, radix, 0, BF_RNDZ | BF_FTOA_FORMAT_FRAC | + BF_FTOA_JS_QUIRKS); + a->sign = saved_sign; + JS_FreeBigInt(ctx, a, &a_s); + if (!str) + return JS_ThrowOutOfMemory(ctx); + ret = JS_NewString(ctx, str); + bf_free(ctx->bf_ctx, str); + return ret; +} + +static JSValue js_bigint_to_string(JSContext *ctx, JSValueConst val) +{ + return js_bigint_to_string1(ctx, val, 10); +} + +#ifdef CONFIG_BIGNUM + +static JSValue js_ftoa(JSContext *ctx, JSValueConst val1, int radix, + limb_t prec, bf_flags_t flags) +{ + JSValue val, ret; + bf_t a_s, *a; + char *str; + int saved_sign; + + val = JS_ToNumeric(ctx, val1); + if (JS_IsException(val)) + return val; + a = JS_ToBigFloat(ctx, &a_s, val); + if (!a) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + saved_sign = a->sign; + if (a->expn == BF_EXP_ZERO) + a->sign = 0; + flags |= BF_FTOA_JS_QUIRKS; + if ((flags & BF_FTOA_FORMAT_MASK) == BF_FTOA_FORMAT_FREE_MIN) { + /* Note: for floating point numbers with a radix which is not + a power of two, the current precision is used to compute + the number of digits. */ + if ((radix & (radix - 1)) != 0) { + bf_t r_s, *r = &r_s; + int prec, flags1; + /* must round first */ + if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_FLOAT) { + prec = ctx->fp_env.prec; + flags1 = ctx->fp_env.flags & + (BF_FLAG_SUBNORMAL | (BF_EXP_BITS_MASK << BF_EXP_BITS_SHIFT)); + } else { + prec = 53; + flags1 = bf_set_exp_bits(11) | BF_FLAG_SUBNORMAL; + } + bf_init(ctx->bf_ctx, r); + bf_set(r, a); + bf_round(r, prec, flags1 | BF_RNDN); + str = bf_ftoa(NULL, r, radix, prec, flags1 | flags); + bf_delete(r); + } else { + str = bf_ftoa(NULL, a, radix, BF_PREC_INF, flags); + } + } else { + str = bf_ftoa(NULL, a, radix, prec, flags); + } + a->sign = saved_sign; + if (a == &a_s) + bf_delete(a); + JS_FreeValue(ctx, val); + if (!str) + return JS_ThrowOutOfMemory(ctx); + ret = JS_NewString(ctx, str); + bf_free(ctx->bf_ctx, str); + return ret; +} + +static JSValue js_bigfloat_to_string(JSContext *ctx, JSValueConst val) +{ + return js_ftoa(ctx, val, 10, 0, BF_RNDN | BF_FTOA_FORMAT_FREE_MIN); +} + +static JSValue js_bigdecimal_to_string1(JSContext *ctx, JSValueConst val, + limb_t prec, int flags) +{ + JSValue ret; + bfdec_t *a; + char *str; + int saved_sign; + + a = JS_ToBigDecimal(ctx, val); + if (!a) + return JS_EXCEPTION; + saved_sign = a->sign; + if (a->expn == BF_EXP_ZERO) + a->sign = 0; + str = bfdec_ftoa(NULL, a, prec, flags | BF_FTOA_JS_QUIRKS); + a->sign = saved_sign; + if (!str) + return JS_ThrowOutOfMemory(ctx); + ret = JS_NewString(ctx, str); + bf_free(ctx->bf_ctx, str); + return ret; +} + +static JSValue js_bigdecimal_to_string(JSContext *ctx, JSValueConst val) +{ + return js_bigdecimal_to_string1(ctx, val, 0, + BF_RNDZ | BF_FTOA_FORMAT_FREE); +} + +#endif /* CONFIG_BIGNUM */ + +/* 2 <= base <= 36 */ +static char const digits[36] = "0123456789abcdefghijklmnopqrstuvwxyz"; + +static char *i64toa(char *buf_end, int64_t n, unsigned int base) +{ + char *q = buf_end; + int digit, is_neg; + + is_neg = 0; + if (n < 0) { + is_neg = 1; + n = -n; + } + *--q = '\0'; + if (base == 10) { + /* division by known base uses multiplication */ + do { + digit = (uint64_t)n % 10; + n = (uint64_t)n / 10; + *--q = '0' + digit; + } while (n != 0); + } else { + do { + digit = (uint64_t)n % base; + n = (uint64_t)n / base; + *--q = digits[digit]; + } while (n != 0); + } + if (is_neg) + *--q = '-'; + return q; +} + +/* buf1 contains the printf result */ +static void js_ecvt1(double d, int n_digits, int *decpt, int *sign, char *buf, + int rounding_mode, char *buf1, int buf1_size) +{ + if (rounding_mode != FE_TONEAREST) + fesetround(rounding_mode); + snprintf(buf1, buf1_size, "%+.*e", n_digits - 1, d); + if (rounding_mode != FE_TONEAREST) + fesetround(FE_TONEAREST); + *sign = (buf1[0] == '-'); + /* mantissa */ + buf[0] = buf1[1]; + if (n_digits > 1) + memcpy(buf + 1, buf1 + 3, n_digits - 1); + buf[n_digits] = '\0'; + /* exponent */ + *decpt = atoi(buf1 + n_digits + 2 + (n_digits > 1)) + 1; +} + +/* maximum buffer size for js_dtoa */ +#define JS_DTOA_BUF_SIZE 128 + +/* needed because ecvt usually limits the number of digits to + 17. Return the number of digits. */ +static int js_ecvt(double d, int n_digits, int *decpt, int *sign, char *buf, + BOOL is_fixed) +{ + int rounding_mode; + char buf_tmp[JS_DTOA_BUF_SIZE]; + + if (!is_fixed) { + unsigned int n_digits_min, n_digits_max; + /* find the minimum amount of digits (XXX: inefficient but simple) */ + n_digits_min = 1; + n_digits_max = 17; + while (n_digits_min < n_digits_max) { + n_digits = (n_digits_min + n_digits_max) / 2; + js_ecvt1(d, n_digits, decpt, sign, buf, FE_TONEAREST, + buf_tmp, sizeof(buf_tmp)); + if (strtod(buf_tmp, NULL) == d) { + /* no need to keep the trailing zeros */ + while (n_digits >= 2 && buf[n_digits - 1] == '0') + n_digits--; + n_digits_max = n_digits; + } else { + n_digits_min = n_digits + 1; + } + } + n_digits = n_digits_max; + rounding_mode = FE_TONEAREST; + } else { + rounding_mode = FE_TONEAREST; +#ifdef CONFIG_PRINTF_RNDN + { + char buf1[JS_DTOA_BUF_SIZE], buf2[JS_DTOA_BUF_SIZE]; + int decpt1, sign1, decpt2, sign2; + /* The JS rounding is specified as round to nearest ties away + from zero (RNDNA), but in printf the "ties" case is not + specified (for example it is RNDN for glibc, RNDNA for + Windows), so we must round manually. */ + js_ecvt1(d, n_digits + 1, &decpt1, &sign1, buf1, FE_TONEAREST, + buf_tmp, sizeof(buf_tmp)); + /* XXX: could use 2 digits to reduce the average running time */ + if (buf1[n_digits] == '5') { + js_ecvt1(d, n_digits + 1, &decpt1, &sign1, buf1, FE_DOWNWARD, + buf_tmp, sizeof(buf_tmp)); + js_ecvt1(d, n_digits + 1, &decpt2, &sign2, buf2, FE_UPWARD, + buf_tmp, sizeof(buf_tmp)); + if (memcmp(buf1, buf2, n_digits + 1) == 0 && decpt1 == decpt2) { + /* exact result: round away from zero */ + if (sign1) + rounding_mode = FE_DOWNWARD; + else + rounding_mode = FE_UPWARD; + } + } + } +#endif /* CONFIG_PRINTF_RNDN */ + } + js_ecvt1(d, n_digits, decpt, sign, buf, rounding_mode, + buf_tmp, sizeof(buf_tmp)); + return n_digits; +} + +static int js_fcvt1(char (*buf)[JS_DTOA_BUF_SIZE], double d, int n_digits, + int rounding_mode) +{ + int n; + if (rounding_mode != FE_TONEAREST) + fesetround(rounding_mode); + n = snprintf(*buf, sizeof(*buf), "%.*f", n_digits, d); + if (rounding_mode != FE_TONEAREST) + fesetround(FE_TONEAREST); + assert(n < sizeof(*buf)); + return n; +} + +static void js_fcvt(char (*buf)[JS_DTOA_BUF_SIZE], double d, int n_digits) +{ + int rounding_mode; + rounding_mode = FE_TONEAREST; +#ifdef CONFIG_PRINTF_RNDN + { + int n1, n2; + char buf1[JS_DTOA_BUF_SIZE]; + char buf2[JS_DTOA_BUF_SIZE]; + + /* The JS rounding is specified as round to nearest ties away from + zero (RNDNA), but in printf the "ties" case is not specified + (for example it is RNDN for glibc, RNDNA for Windows), so we + must round manually. */ + n1 = js_fcvt1(&buf1, d, n_digits + 1, FE_TONEAREST); + rounding_mode = FE_TONEAREST; + /* XXX: could use 2 digits to reduce the average running time */ + if (buf1[n1 - 1] == '5') { + n1 = js_fcvt1(&buf1, d, n_digits + 1, FE_DOWNWARD); + n2 = js_fcvt1(&buf2, d, n_digits + 1, FE_UPWARD); + if (n1 == n2 && memcmp(buf1, buf2, n1) == 0) { + /* exact result: round away from zero */ + if (buf1[0] == '-') + rounding_mode = FE_DOWNWARD; + else + rounding_mode = FE_UPWARD; + } + } + } +#endif /* CONFIG_PRINTF_RNDN */ + js_fcvt1(buf, d, n_digits, rounding_mode); +} + +/* radix != 10 is only supported with flags = JS_DTOA_VAR_FORMAT */ +/* use as many digits as necessary */ +#define JS_DTOA_VAR_FORMAT (0 << 0) +/* use n_digits significant digits (1 <= n_digits <= 101) */ +#define JS_DTOA_FIXED_FORMAT (1 << 0) +/* force fractional format: [-]dd.dd with n_digits fractional digits */ +#define JS_DTOA_FRAC_FORMAT (2 << 0) +/* force exponential notation either in fixed or variable format */ +#define JS_DTOA_FORCE_EXP (1 << 2) + +/* XXX: slow and maybe not fully correct. Use libbf when it is fast enough. + XXX: radix != 10 is only supported for small integers +*/ +static void js_dtoa1(char (*buf)[JS_DTOA_BUF_SIZE], double d, + int radix, int n_digits, int flags) +{ + char *q; + + if (!isfinite(d)) { + if (isnan(d)) { + pstrcpy(*buf, sizeof(*buf), "NaN"); + } else if (d < 0) { + pstrcpy(*buf, sizeof(*buf), "-Infinity"); + } else { + pstrcpy(*buf, sizeof(*buf), "Infinity"); + } + } else if (flags == JS_DTOA_VAR_FORMAT) { + int64_t i64; + char buf1[70], *ptr; + if (d > (double)MAX_SAFE_INTEGER || d < (double)-MAX_SAFE_INTEGER) + goto generic_conv; + i64 = (int64_t)d; + if (d != i64) + goto generic_conv; + /* fast path for integers */ + ptr = i64toa(buf1 + sizeof(buf1), i64, radix); + pstrcpy(*buf, sizeof(*buf), ptr); + } else { + if (d == 0.0) + d = 0.0; /* convert -0 to 0 */ + if (flags == JS_DTOA_FRAC_FORMAT) { + js_fcvt(buf, d, n_digits); + } else { + char buf1[JS_DTOA_BUF_SIZE]; + int sign, decpt, k, n, i, p, n_max; + BOOL is_fixed; + generic_conv: + is_fixed = ((flags & 3) == JS_DTOA_FIXED_FORMAT); + if (is_fixed) { + n_max = n_digits; + } else { + n_max = 21; + } + /* the number has k digits (k >= 1) */ + k = js_ecvt(d, n_digits, &decpt, &sign, buf1, is_fixed); + n = decpt; /* d=10^(n-k)*(buf1) i.e. d= < x.yyyy 10^(n-1) */ + q = *buf; + if (sign) + *q++ = '-'; + if (flags & JS_DTOA_FORCE_EXP) + goto force_exp; + if (n >= 1 && n <= n_max) { + if (k <= n) { + memcpy(q, buf1, k); + q += k; + for(i = 0; i < (n - k); i++) + *q++ = '0'; + *q = '\0'; + } else { + /* k > n */ + memcpy(q, buf1, n); + q += n; + *q++ = '.'; + for(i = 0; i < (k - n); i++) + *q++ = buf1[n + i]; + *q = '\0'; + } + } else if (n >= -5 && n <= 0) { + *q++ = '0'; + *q++ = '.'; + for(i = 0; i < -n; i++) + *q++ = '0'; + memcpy(q, buf1, k); + q += k; + *q = '\0'; + } else { + force_exp: + /* exponential notation */ + *q++ = buf1[0]; + if (k > 1) { + *q++ = '.'; + for(i = 1; i < k; i++) + *q++ = buf1[i]; + } + *q++ = 'e'; + p = n - 1; + if (p >= 0) + *q++ = '+'; + snprintf(q, *buf + sizeof(*buf) - q, "%d", p); + } + } + } +} + +static JSValue js_dtoa(JSContext *ctx, + double d, int radix, int n_digits, int flags) +{ + char buf[JS_DTOA_BUF_SIZE]; + js_dtoa1(&buf, d, radix, n_digits, flags); + return JS_NewString(ctx, buf); +} + +static JSValue js_dtoa_radix(JSContext *ctx, double d, int radix) +{ + char buf[2200], *ptr, *ptr2; + /* d is finite */ + int sign = d < 0; + int digit; + double frac, d0; + int64_t n0 = 0; + d = fabs(d); + d0 = trunc(d); + frac = d - d0; + ptr = buf + 1100; + *ptr = '\0'; + if (d0 <= MAX_SAFE_INTEGER) { + int64_t n = n0 = (int64_t)d0; + while (n >= radix) { + digit = n % radix; + n = n / radix; + *--ptr = digits[digit]; + } + *--ptr = digits[(int)n]; + } else { + /* no decimals */ + while (d0 >= radix) { + digit = fmod(d0, radix); + d0 = trunc(d0 / radix); + if (d0 >= MAX_SAFE_INTEGER) + digit = 0; + *--ptr = digits[digit]; + } + *--ptr = digits[(int)d0]; + goto done; + } + if (frac != 0) { + double log2_radix = log2(radix); + double prec = 1023 + 51; // handle subnormals + ptr2 = buf + 1100; + *ptr2++ = '.'; + while (frac != 0 && n0 <= MAX_SAFE_INTEGER/2 && prec > 0) { + frac *= radix; + digit = trunc(frac); + frac -= digit; + *ptr2++ = digits[digit]; + n0 = n0 * radix + digit; + prec -= log2_radix; + } + *ptr2 = '\0'; + if (frac * radix >= radix / 2) { + char nine = digits[radix - 1]; + // round to closest + while (ptr2[-1] == nine) + *--ptr2 = '\0'; + if (ptr2[-1] == '.') { + *--ptr2 = '\0'; + while (ptr2[-1] == nine) + *--ptr2 = '0'; + } + if (ptr2 - 1 == ptr) + *--ptr = '1'; + else + ptr2[-1] += 1; + } else { + while (ptr2[-1] == '0') + *--ptr2 = '\0'; + if (ptr2[-1] == '.') + *--ptr2 = '\0'; + } + } +done: + ptr[-1] = '-'; + ptr -= sign; + return JS_NewString(ctx, ptr); +} + +JSValue JS_ToStringInternal(JSContext *ctx, JSValueConst val, BOOL is_ToPropertyKey) +{ + uint32_t tag; + const char *str; + char buf[32]; + + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_STRING: + return JS_DupValue(ctx, val); + case JS_TAG_INT: + snprintf(buf, sizeof(buf), "%d", JS_VALUE_GET_INT(val)); + str = buf; + goto new_string; + case JS_TAG_BOOL: + return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ? + JS_ATOM_true : JS_ATOM_false); + case JS_TAG_NULL: + return JS_AtomToString(ctx, JS_ATOM_null); + case JS_TAG_UNDEFINED: + return JS_AtomToString(ctx, JS_ATOM_undefined); + case JS_TAG_EXCEPTION: + return JS_EXCEPTION; + case JS_TAG_OBJECT: + { + JSValue val1, ret; + val1 = JS_ToPrimitive(ctx, val, HINT_STRING); + if (JS_IsException(val1)) + return val1; + ret = JS_ToStringInternal(ctx, val1, is_ToPropertyKey); + JS_FreeValue(ctx, val1); + return ret; + } + break; + case JS_TAG_FUNCTION_BYTECODE: + str = "[function bytecode]"; + goto new_string; + case JS_TAG_SYMBOL: + if (is_ToPropertyKey) { + return JS_DupValue(ctx, val); + } else { + return JS_ThrowTypeError(ctx, "cannot convert symbol to string"); + } + case JS_TAG_FLOAT64: + return js_dtoa(ctx, JS_VALUE_GET_FLOAT64(val), 10, 0, + JS_DTOA_VAR_FORMAT); + case JS_TAG_BIG_INT: + return ctx->rt->bigint_ops.to_string(ctx, val); +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + return ctx->rt->bigfloat_ops.to_string(ctx, val); + case JS_TAG_BIG_DECIMAL: + return ctx->rt->bigdecimal_ops.to_string(ctx, val); +#endif + default: + str = "[unsupported type]"; + new_string: + return JS_NewString(ctx, str); + } +} + +JSValue JS_ToString(JSContext *ctx, JSValueConst val) +{ + return JS_ToStringInternal(ctx, val, FALSE); +} + +static JSValue JS_ToStringFree(JSContext *ctx, JSValue val) +{ + JSValue ret; + ret = JS_ToString(ctx, val); + JS_FreeValue(ctx, val); + return ret; +} + +static JSValue JS_ToLocaleStringFree(JSContext *ctx, JSValue val) +{ + if (JS_IsUndefined(val) || JS_IsNull(val)) + return JS_ToStringFree(ctx, val); + return JS_InvokeFree(ctx, val, JS_ATOM_toLocaleString, 0, NULL); +} + +JSValue JS_ToPropertyKey(JSContext *ctx, JSValueConst val) +{ + return JS_ToStringInternal(ctx, val, TRUE); +} + +static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValueConst val) +{ + uint32_t tag = JS_VALUE_GET_TAG(val); + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) + return JS_ThrowTypeError(ctx, "null or undefined are forbidden"); + return JS_ToString(ctx, val); +} + +static JSValue JS_ToQuotedString(JSContext *ctx, JSValueConst val1) +{ + JSValue val; + JSString *p; + int i; + uint32_t c; + StringBuffer b_s, *b = &b_s; + char buf[16]; + + val = JS_ToStringCheckObject(ctx, val1); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_STRING(val); + + if (string_buffer_init(ctx, b, p->len + 2)) + goto fail; + + if (string_buffer_putc8(b, '\"')) + goto fail; + for(i = 0; i < p->len; ) { + c = string_getc(p, &i); + switch(c) { + case '\t': + c = 't'; + goto quote; + case '\r': + c = 'r'; + goto quote; + case '\n': + c = 'n'; + goto quote; + case '\b': + c = 'b'; + goto quote; + case '\f': + c = 'f'; + goto quote; + case '\"': + case '\\': + quote: + if (string_buffer_putc8(b, '\\')) + goto fail; + if (string_buffer_putc8(b, c)) + goto fail; + break; + default: + if (c < 32 || is_surrogate(c)) { + snprintf(buf, sizeof(buf), "\\u%04x", c); + if (string_buffer_puts8(b, buf)) + goto fail; + } else { + if (string_buffer_putc(b, c)) + goto fail; + } + break; + } + } + if (string_buffer_putc8(b, '\"')) + goto fail; + JS_FreeValue(ctx, val); + return string_buffer_end(b); + fail: + JS_FreeValue(ctx, val); + string_buffer_free(b); + return JS_EXCEPTION; +} + +static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt) +{ + printf("%14s %4s %4s %14s %10s %s\n", + "ADDRESS", "REFS", "SHRF", "PROTO", "CLASS", "PROPS"); +} + +/* for debug only: dump an object without side effect */ +static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) +{ + uint32_t i; + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + JSShape *sh; + JSShapeProperty *prs; + JSProperty *pr; + BOOL is_first = TRUE; + + /* XXX: should encode atoms with special characters */ + sh = p->shape; /* the shape can be NULL while freeing an object */ + printf("%14p %4d ", + (void *)p, + p->header.ref_count); + if (sh) { + printf("%3d%c %14p ", + sh->header.ref_count, + " *"[sh->is_hashed], + (void *)sh->proto); + } else { + printf("%3s %14s ", "-", "-"); + } + printf("%10s ", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), rt->class_array[p->class_id].class_name)); + if (p->is_exotic && p->fast_array) { + printf("[ "); + for(i = 0; i < p->u.array.count; i++) { + if (i != 0) + printf(", "); + switch (p->class_id) { + case JS_CLASS_ARRAY: + case JS_CLASS_ARGUMENTS: + JS_DumpValueShort(rt, p->u.array.u.values[i]); + break; + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + { + int size = 1 << typed_array_size_log2(p->class_id); + const uint8_t *b = p->u.array.u.uint8_ptr + i * size; + while (size-- > 0) + printf("%02X", *b++); + } + break; + } + } + printf(" ] "); + } + + if (sh) { + printf("{ "); + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + if (prs->atom != JS_ATOM_NULL) { + pr = &p->prop[i]; + if (!is_first) + printf(", "); + printf("%s: ", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), prs->atom)); + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + printf("[getset %p %p]", (void *)pr->u.getset.getter, + (void *)pr->u.getset.setter); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + printf("[varref %p]", (void *)pr->u.var_ref); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + printf("[autoinit %p %d %p]", + (void *)js_autoinit_get_realm(pr), + js_autoinit_get_id(pr), + (void *)pr->u.init.opaque); + } else { + JS_DumpValueShort(rt, pr->u.value); + } + is_first = FALSE; + } + } + printf(" }"); + } + + if (js_class_has_bytecode(p->class_id)) { + JSFunctionBytecode *b = p->u.func.function_bytecode; + JSVarRef **var_refs; + if (b->closure_var_count) { + var_refs = p->u.func.var_refs; + printf(" Closure:"); + for(i = 0; i < b->closure_var_count; i++) { + printf(" "); + JS_DumpValueShort(rt, var_refs[i]->value); + } + if (p->u.func.home_object) { + printf(" HomeObject: "); + JS_DumpValueShort(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object)); + } + } + } + printf("\n"); +} + +static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) +{ + if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + JS_DumpObject(rt, (JSObject *)p); + } else { + printf("%14p %4d ", + (void *)p, + p->ref_count); + switch(p->gc_obj_type) { + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + printf("[function bytecode]"); + break; + case JS_GC_OBJ_TYPE_SHAPE: + printf("[shape]"); + break; + case JS_GC_OBJ_TYPE_VAR_REF: + printf("[var_ref]"); + break; + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + printf("[async_function]"); + break; + case JS_GC_OBJ_TYPE_JS_CONTEXT: + printf("[js_context]"); + break; + default: + printf("[unknown %d]", p->gc_obj_type); + break; + } + printf("\n"); + } +} + +static __maybe_unused void JS_DumpValueShort(JSRuntime *rt, + JSValueConst val) +{ + uint32_t tag = JS_VALUE_GET_NORM_TAG(val); + const char *str; + + switch(tag) { + case JS_TAG_INT: + printf("%d", JS_VALUE_GET_INT(val)); + break; + case JS_TAG_BOOL: + if (JS_VALUE_GET_BOOL(val)) + str = "true"; + else + str = "false"; + goto print_str; + case JS_TAG_NULL: + str = "null"; + goto print_str; + case JS_TAG_EXCEPTION: + str = "exception"; + goto print_str; + case JS_TAG_UNINITIALIZED: + str = "uninitialized"; + goto print_str; + case JS_TAG_UNDEFINED: + str = "undefined"; + print_str: + printf("%s", str); + break; + case JS_TAG_FLOAT64: + printf("%.14g", JS_VALUE_GET_FLOAT64(val)); + break; + case JS_TAG_BIG_INT: + { + JSBigFloat *p = JS_VALUE_GET_PTR(val); + char *str; + str = bf_ftoa(NULL, &p->num, 10, 0, + BF_RNDZ | BF_FTOA_FORMAT_FRAC); + printf("%sn", str); + bf_realloc(&rt->bf_ctx, str, 0); + } + break; +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + { + JSBigFloat *p = JS_VALUE_GET_PTR(val); + char *str; + str = bf_ftoa(NULL, &p->num, 16, BF_PREC_INF, + BF_RNDZ | BF_FTOA_FORMAT_FREE | BF_FTOA_ADD_PREFIX); + printf("%sl", str); + bf_free(&rt->bf_ctx, str); + } + break; + case JS_TAG_BIG_DECIMAL: + { + JSBigDecimal *p = JS_VALUE_GET_PTR(val); + char *str; + str = bfdec_ftoa(NULL, &p->num, BF_PREC_INF, + BF_RNDZ | BF_FTOA_FORMAT_FREE); + printf("%sm", str); + bf_free(&rt->bf_ctx, str); + } + break; +#endif + case JS_TAG_STRING: + { + JSString *p; + p = JS_VALUE_GET_STRING(val); + JS_DumpString(rt, p); + } + break; + case JS_TAG_FUNCTION_BYTECODE: + { + JSFunctionBytecode *b = JS_VALUE_GET_PTR(val); + char buf[ATOM_GET_STR_BUF_SIZE]; + printf("[bytecode %s]", JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name)); + } + break; + case JS_TAG_OBJECT: + { + JSObject *p = JS_VALUE_GET_OBJ(val); + JSAtom atom = rt->class_array[p->class_id].class_name; + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + printf("[%s %p]", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), atom), (void *)p); + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + printf("Symbol(%s)", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), js_get_atom_index(rt, p))); + } + break; + case JS_TAG_MODULE: + printf("[module]"); + break; + default: + printf("[unknown tag %d]", tag); + break; + } +} + +static __maybe_unused void JS_DumpValue(JSContext *ctx, + JSValueConst val) +{ + JS_DumpValueShort(ctx->rt, val); +} + +static __maybe_unused void JS_PrintValue(JSContext *ctx, + const char *str, + JSValueConst val) +{ + printf("%s=", str); + JS_DumpValueShort(ctx->rt, val); + printf("\n"); +} + +/* return -1 if exception (proxy case) or TRUE/FALSE */ +int JS_IsArray(JSContext *ctx, JSValueConst val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(val); + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_isArray(ctx, val); + else + return p->class_id == JS_CLASS_ARRAY; + } else { + return FALSE; + } +} + +static double js_pow(double a, double b) +{ + if (unlikely(!isfinite(b)) && fabs(a) == 1) { + /* not compatible with IEEE 754 */ + return JS_FLOAT64_NAN; + } else { + return pow(a, b); + } +} + +JSValue JS_NewBigInt64_1(JSContext *ctx, int64_t v) +{ + JSValue val; + bf_t *a; + val = JS_NewBigInt(ctx); + if (JS_IsException(val)) + return val; + a = JS_GetBigInt(val); + if (bf_set_si(a, v)) { + JS_FreeValue(ctx, val); + return JS_ThrowOutOfMemory(ctx); + } + return val; +} + +JSValue JS_NewBigInt64(JSContext *ctx, int64_t v) +{ + if (is_math_mode(ctx) && + v >= -MAX_SAFE_INTEGER && v <= MAX_SAFE_INTEGER) { + return JS_NewInt64(ctx, v); + } else { + return JS_NewBigInt64_1(ctx, v); + } +} + +JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v) +{ + JSValue val; + if (is_math_mode(ctx) && v <= MAX_SAFE_INTEGER) { + val = JS_NewInt64(ctx, v); + } else { + bf_t *a; + val = JS_NewBigInt(ctx); + if (JS_IsException(val)) + return val; + a = JS_GetBigInt(val); + if (bf_set_ui(a, v)) { + JS_FreeValue(ctx, val); + return JS_ThrowOutOfMemory(ctx); + } + } + return val; +} + +/* return NaN if bad bigint literal */ +static JSValue JS_StringToBigInt(JSContext *ctx, JSValue val) +{ + const char *str, *p; + size_t len; + int flags; + + str = JS_ToCStringLen(ctx, &len, val); + JS_FreeValue(ctx, val); + if (!str) + return JS_EXCEPTION; + p = str; + p += skip_spaces(p); + if ((p - str) == len) { + val = JS_NewBigInt64(ctx, 0); + } else { + flags = ATOD_INT_ONLY | ATOD_ACCEPT_BIN_OCT | ATOD_TYPE_BIG_INT; +#ifdef CONFIG_BIGNUM + if (is_math_mode(ctx)) + flags |= ATOD_MODE_BIGINT; +#endif + val = js_atof(ctx, p, &p, 0, flags); + p += skip_spaces(p); + if (!JS_IsException(val)) { + if ((p - str) != len) { + JS_FreeValue(ctx, val); + val = JS_NAN; + } + } + } + JS_FreeCString(ctx, str); + return val; +} + +static JSValue JS_StringToBigIntErr(JSContext *ctx, JSValue val) +{ + val = JS_StringToBigInt(ctx, val); + if (JS_VALUE_IS_NAN(val)) + return JS_ThrowSyntaxError(ctx, "invalid bigint literal"); + return val; +} + +/* if the returned bigfloat is allocated it is equal to + 'buf'. Otherwise it is a pointer to the bigfloat in 'val'. */ +static bf_t *JS_ToBigIntFree(JSContext *ctx, bf_t *buf, JSValue val) +{ + uint32_t tag; + bf_t *r; + JSBigFloat *p; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + if (!is_math_mode(ctx)) + goto fail; + /* fall tru */ + case JS_TAG_BOOL: + r = buf; + bf_init(ctx->bf_ctx, r); + bf_set_si(r, JS_VALUE_GET_INT(val)); + break; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (!is_math_mode(ctx)) + goto fail; + if (!isfinite(d)) + goto fail; + r = buf; + bf_init(ctx->bf_ctx, r); + d = trunc(d); + bf_set_float64(r, d); + } + break; + case JS_TAG_BIG_INT: + p = JS_VALUE_GET_PTR(val); + r = &p->num; + break; +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + if (!is_math_mode(ctx)) + goto fail; + p = JS_VALUE_GET_PTR(val); + if (!bf_is_finite(&p->num)) + goto fail; + r = buf; + bf_init(ctx->bf_ctx, r); + bf_set(r, &p->num); + bf_rint(r, BF_RNDZ); + JS_FreeValue(ctx, val); + break; +#endif + case JS_TAG_STRING: + val = JS_StringToBigIntErr(ctx, val); + if (JS_IsException(val)) + return NULL; + goto redo; + case JS_TAG_OBJECT: + val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); + if (JS_IsException(val)) + return NULL; + goto redo; + default: + fail: + JS_FreeValue(ctx, val); + JS_ThrowTypeError(ctx, "cannot convert to bigint"); + return NULL; + } + return r; +} + +static bf_t *JS_ToBigInt(JSContext *ctx, bf_t *buf, JSValueConst val) +{ + return JS_ToBigIntFree(ctx, buf, JS_DupValue(ctx, val)); +} + +static __maybe_unused JSValue JS_ToBigIntValueFree(JSContext *ctx, JSValue val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_INT) { + return val; + } else { + bf_t a_s, *a, *r; + int ret; + JSValue res; + + res = JS_NewBigInt(ctx); + if (JS_IsException(res)) + return JS_EXCEPTION; + a = JS_ToBigIntFree(ctx, &a_s, val); + if (!a) { + JS_FreeValue(ctx, res); + return JS_EXCEPTION; + } + r = JS_GetBigInt(res); + ret = bf_set(r, a); + JS_FreeBigInt(ctx, a, &a_s); + if (ret) { + JS_FreeValue(ctx, res); + return JS_ThrowOutOfMemory(ctx); + } + return JS_CompactBigInt(ctx, res); + } +} + +/* free the bf_t allocated by JS_ToBigInt */ +static void JS_FreeBigInt(JSContext *ctx, bf_t *a, bf_t *buf) +{ + if (a == buf) { + bf_delete(a); + } else { + JSBigFloat *p = (JSBigFloat *)((uint8_t *)a - + offsetof(JSBigFloat, num)); + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_BIG_INT, p)); + } +} + +/* XXX: merge with JS_ToInt64Free with a specific flag */ +static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val) +{ + bf_t a_s, *a; + + a = JS_ToBigIntFree(ctx, &a_s, val); + if (!a) { + *pres = 0; + return -1; + } + bf_get_int64(pres, a, BF_GET_INT_MOD); + JS_FreeBigInt(ctx, a, &a_s); + return 0; +} + +int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValueConst val) +{ + return JS_ToBigInt64Free(ctx, pres, JS_DupValue(ctx, val)); +} + +static JSBigFloat *js_new_bf(JSContext *ctx) +{ + JSBigFloat *p; + p = js_malloc(ctx, sizeof(*p)); + if (!p) + return NULL; + p->header.ref_count = 1; + bf_init(ctx->bf_ctx, &p->num); + return p; +} + +static JSValue JS_NewBigInt(JSContext *ctx) +{ + JSBigFloat *p; + p = js_malloc(ctx, sizeof(*p)); + if (!p) + return JS_EXCEPTION; + p->header.ref_count = 1; + bf_init(ctx->bf_ctx, &p->num); + return JS_MKPTR(JS_TAG_BIG_INT, p); +} + +static JSValue JS_CompactBigInt1(JSContext *ctx, JSValue val, + BOOL convert_to_safe_integer) +{ + int64_t v; + bf_t *a; + + if (JS_VALUE_GET_TAG(val) != JS_TAG_BIG_INT) + return val; /* fail safe */ + a = JS_GetBigInt(val); + if (convert_to_safe_integer && bf_get_int64(&v, a, 0) == 0 && + v >= -MAX_SAFE_INTEGER && v <= MAX_SAFE_INTEGER) { + JS_FreeValue(ctx, val); + return JS_NewInt64(ctx, v); + } else if (a->expn == BF_EXP_ZERO && a->sign) { + JSBigFloat *p = JS_VALUE_GET_PTR(val); + assert(p->header.ref_count == 1); + a->sign = 0; + } + return val; +} + +/* Convert the big int to a safe integer if in math mode. normalize + the zero representation. Could also be used to convert the bigint + to a short bigint value. The reference count of the value must be + 1. Cannot fail */ +static JSValue JS_CompactBigInt(JSContext *ctx, JSValue val) +{ + return JS_CompactBigInt1(ctx, val, is_math_mode(ctx)); +} + +static JSValue throw_bf_exception(JSContext *ctx, int status) +{ + const char *str; + if (status & BF_ST_MEM_ERROR) + return JS_ThrowOutOfMemory(ctx); + if (status & BF_ST_DIVIDE_ZERO) { + str = "division by zero"; + } else if (status & BF_ST_INVALID_OP) { + str = "invalid operation"; + } else { + str = "integer overflow"; + } + return JS_ThrowRangeError(ctx, "%s", str); +} + +/* if the returned bigfloat is allocated it is equal to + 'buf'. Otherwise it is a pointer to the bigfloat in 'val'. Return + NULL in case of error. */ +static bf_t *JS_ToBigFloat(JSContext *ctx, bf_t *buf, JSValueConst val) +{ + uint32_t tag; + bf_t *r; + JSBigFloat *p; + + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + r = buf; + bf_init(ctx->bf_ctx, r); + if (bf_set_si(r, JS_VALUE_GET_INT(val))) + goto fail; + break; + case JS_TAG_FLOAT64: + r = buf; + bf_init(ctx->bf_ctx, r); + if (bf_set_float64(r, JS_VALUE_GET_FLOAT64(val))) { + fail: + bf_delete(r); + return NULL; + } + break; + case JS_TAG_BIG_INT: +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: +#endif + p = JS_VALUE_GET_PTR(val); + r = &p->num; + break; + case JS_TAG_UNDEFINED: + default: + r = buf; + bf_init(ctx->bf_ctx, r); + bf_set_nan(r); + break; + } + return r; +} + +#ifdef CONFIG_BIGNUM +/* return NULL if invalid type */ +static bfdec_t *JS_ToBigDecimal(JSContext *ctx, JSValueConst val) +{ + uint32_t tag; + JSBigDecimal *p; + bfdec_t *r; + + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_BIG_DECIMAL: + p = JS_VALUE_GET_PTR(val); + r = &p->num; + break; + default: + JS_ThrowTypeError(ctx, "bigdecimal expected"); + r = NULL; + break; + } + return r; +} + +static JSValue JS_NewBigFloat(JSContext *ctx) +{ + JSBigFloat *p; + p = js_malloc(ctx, sizeof(*p)); + if (!p) + return JS_EXCEPTION; + p->header.ref_count = 1; + bf_init(ctx->bf_ctx, &p->num); + return JS_MKPTR(JS_TAG_BIG_FLOAT, p); +} + +static JSValue JS_NewBigDecimal(JSContext *ctx) +{ + JSBigDecimal *p; + p = js_malloc(ctx, sizeof(*p)); + if (!p) + return JS_EXCEPTION; + p->header.ref_count = 1; + bfdec_init(ctx->bf_ctx, &p->num); + return JS_MKPTR(JS_TAG_BIG_DECIMAL, p); +} + +/* must be kept in sync with JSOverloadableOperatorEnum */ +/* XXX: use atoms ? */ +static const char js_overloadable_operator_names[JS_OVOP_COUNT][4] = { + "+", + "-", + "*", + "/", + "%", + "**", + "|", + "&", + "^", + "<<", + ">>", + ">>>", + "==", + "<", + "pos", + "neg", + "++", + "--", + "~", +}; + +static int get_ovop_from_opcode(OPCodeEnum op) +{ + switch(op) { + case OP_add: + return JS_OVOP_ADD; + case OP_sub: + return JS_OVOP_SUB; + case OP_mul: + return JS_OVOP_MUL; + case OP_div: + return JS_OVOP_DIV; + case OP_mod: + case OP_math_mod: + return JS_OVOP_MOD; + case OP_pow: + return JS_OVOP_POW; + case OP_or: + return JS_OVOP_OR; + case OP_and: + return JS_OVOP_AND; + case OP_xor: + return JS_OVOP_XOR; + case OP_shl: + return JS_OVOP_SHL; + case OP_sar: + return JS_OVOP_SAR; + case OP_shr: + return JS_OVOP_SHR; + case OP_eq: + case OP_neq: + return JS_OVOP_EQ; + case OP_lt: + case OP_lte: + case OP_gt: + case OP_gte: + return JS_OVOP_LESS; + case OP_plus: + return JS_OVOP_POS; + case OP_neg: + return JS_OVOP_NEG; + case OP_inc: + return JS_OVOP_INC; + case OP_dec: + return JS_OVOP_DEC; + default: + abort(); + } +} + +/* return NULL if not present */ +static JSObject *find_binary_op(JSBinaryOperatorDef *def, + uint32_t operator_index, + JSOverloadableOperatorEnum op) +{ + JSBinaryOperatorDefEntry *ent; + int i; + for(i = 0; i < def->count; i++) { + ent = &def->tab[i]; + if (ent->operator_index == operator_index) + return ent->ops[op]; + } + return NULL; +} + +/* return -1 if exception, 0 if no operator overloading, 1 if + overloaded operator called */ +static __exception int js_call_binary_op_fallback(JSContext *ctx, + JSValue *pret, + JSValueConst op1, + JSValueConst op2, + OPCodeEnum op, + BOOL is_numeric, + int hint) +{ + JSValue opset1_obj, opset2_obj, method, ret, new_op1, new_op2; + JSOperatorSetData *opset1, *opset2; + JSOverloadableOperatorEnum ovop; + JSObject *p; + JSValueConst args[2]; + + if (!ctx->allow_operator_overloading) + return 0; + + opset2_obj = JS_UNDEFINED; + opset1_obj = JS_GetProperty(ctx, op1, JS_ATOM_Symbol_operatorSet); + if (JS_IsException(opset1_obj)) + goto exception; + if (JS_IsUndefined(opset1_obj)) + return 0; + opset1 = JS_GetOpaque2(ctx, opset1_obj, JS_CLASS_OPERATOR_SET); + if (!opset1) + goto exception; + + opset2_obj = JS_GetProperty(ctx, op2, JS_ATOM_Symbol_operatorSet); + if (JS_IsException(opset2_obj)) + goto exception; + if (JS_IsUndefined(opset2_obj)) { + JS_FreeValue(ctx, opset1_obj); + return 0; + } + opset2 = JS_GetOpaque2(ctx, opset2_obj, JS_CLASS_OPERATOR_SET); + if (!opset2) + goto exception; + + if (opset1->is_primitive && opset2->is_primitive) { + JS_FreeValue(ctx, opset1_obj); + JS_FreeValue(ctx, opset2_obj); + return 0; + } + + ovop = get_ovop_from_opcode(op); + + if (opset1->operator_counter == opset2->operator_counter) { + p = opset1->self_ops[ovop]; + } else if (opset1->operator_counter > opset2->operator_counter) { + p = find_binary_op(&opset1->left, opset2->operator_counter, ovop); + } else { + p = find_binary_op(&opset2->right, opset1->operator_counter, ovop); + } + if (!p) { + JS_ThrowTypeError(ctx, "operator %s: no function defined", + js_overloadable_operator_names[ovop]); + goto exception; + } + + if (opset1->is_primitive) { + if (is_numeric) { + new_op1 = JS_ToNumeric(ctx, op1); + } else { + new_op1 = JS_ToPrimitive(ctx, op1, hint); + } + if (JS_IsException(new_op1)) + goto exception; + } else { + new_op1 = JS_DupValue(ctx, op1); + } + + if (opset2->is_primitive) { + if (is_numeric) { + new_op2 = JS_ToNumeric(ctx, op2); + } else { + new_op2 = JS_ToPrimitive(ctx, op2, hint); + } + if (JS_IsException(new_op2)) { + JS_FreeValue(ctx, new_op1); + goto exception; + } + } else { + new_op2 = JS_DupValue(ctx, op2); + } + + /* XXX: could apply JS_ToPrimitive() if primitive type so that the + operator function does not get a value object */ + + method = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + if (ovop == JS_OVOP_LESS && (op == OP_lte || op == OP_gt)) { + args[0] = new_op2; + args[1] = new_op1; + } else { + args[0] = new_op1; + args[1] = new_op2; + } + ret = JS_CallFree(ctx, method, JS_UNDEFINED, 2, args); + JS_FreeValue(ctx, new_op1); + JS_FreeValue(ctx, new_op2); + if (JS_IsException(ret)) + goto exception; + if (ovop == JS_OVOP_EQ) { + BOOL res = JS_ToBoolFree(ctx, ret); + if (op == OP_neq) + res ^= 1; + ret = JS_NewBool(ctx, res); + } else if (ovop == JS_OVOP_LESS) { + if (JS_IsUndefined(ret)) { + ret = JS_FALSE; + } else { + BOOL res = JS_ToBoolFree(ctx, ret); + if (op == OP_lte || op == OP_gte) + res ^= 1; + ret = JS_NewBool(ctx, res); + } + } + JS_FreeValue(ctx, opset1_obj); + JS_FreeValue(ctx, opset2_obj); + *pret = ret; + return 1; + exception: + JS_FreeValue(ctx, opset1_obj); + JS_FreeValue(ctx, opset2_obj); + *pret = JS_UNDEFINED; + return -1; +} + +/* try to call the operation on the operatorSet field of 'obj'. Only + used for "/" and "**" on the BigInt prototype in math mode */ +static __exception int js_call_binary_op_simple(JSContext *ctx, + JSValue *pret, + JSValueConst obj, + JSValueConst op1, + JSValueConst op2, + OPCodeEnum op) +{ + JSValue opset1_obj, method, ret, new_op1, new_op2; + JSOperatorSetData *opset1; + JSOverloadableOperatorEnum ovop; + JSObject *p; + JSValueConst args[2]; + + opset1_obj = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_operatorSet); + if (JS_IsException(opset1_obj)) + goto exception; + if (JS_IsUndefined(opset1_obj)) + return 0; + opset1 = JS_GetOpaque2(ctx, opset1_obj, JS_CLASS_OPERATOR_SET); + if (!opset1) + goto exception; + ovop = get_ovop_from_opcode(op); + + p = opset1->self_ops[ovop]; + if (!p) { + JS_FreeValue(ctx, opset1_obj); + return 0; + } + + new_op1 = JS_ToNumeric(ctx, op1); + if (JS_IsException(new_op1)) + goto exception; + new_op2 = JS_ToNumeric(ctx, op2); + if (JS_IsException(new_op2)) { + JS_FreeValue(ctx, new_op1); + goto exception; + } + + method = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + args[0] = new_op1; + args[1] = new_op2; + ret = JS_CallFree(ctx, method, JS_UNDEFINED, 2, args); + JS_FreeValue(ctx, new_op1); + JS_FreeValue(ctx, new_op2); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, opset1_obj); + *pret = ret; + return 1; + exception: + JS_FreeValue(ctx, opset1_obj); + *pret = JS_UNDEFINED; + return -1; +} + +/* return -1 if exception, 0 if no operator overloading, 1 if + overloaded operator called */ +static __exception int js_call_unary_op_fallback(JSContext *ctx, + JSValue *pret, + JSValueConst op1, + OPCodeEnum op) +{ + JSValue opset1_obj, method, ret; + JSOperatorSetData *opset1; + JSOverloadableOperatorEnum ovop; + JSObject *p; + + if (!ctx->allow_operator_overloading) + return 0; + + opset1_obj = JS_GetProperty(ctx, op1, JS_ATOM_Symbol_operatorSet); + if (JS_IsException(opset1_obj)) + goto exception; + if (JS_IsUndefined(opset1_obj)) + return 0; + opset1 = JS_GetOpaque2(ctx, opset1_obj, JS_CLASS_OPERATOR_SET); + if (!opset1) + goto exception; + if (opset1->is_primitive) { + JS_FreeValue(ctx, opset1_obj); + return 0; + } + + ovop = get_ovop_from_opcode(op); + + p = opset1->self_ops[ovop]; + if (!p) { + JS_ThrowTypeError(ctx, "no overloaded operator %s", + js_overloadable_operator_names[ovop]); + goto exception; + } + method = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + ret = JS_CallFree(ctx, method, JS_UNDEFINED, 1, &op1); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, opset1_obj); + *pret = ret; + return 1; + exception: + JS_FreeValue(ctx, opset1_obj); + *pret = JS_UNDEFINED; + return -1; +} + +static int js_unary_arith_bigfloat(JSContext *ctx, + JSValue *pres, OPCodeEnum op, JSValue op1) +{ + bf_t a_s, *r, *a; + int ret, v; + JSValue res; + + if (op == OP_plus && !is_math_mode(ctx)) { + JS_ThrowTypeError(ctx, "bigfloat argument with unary +"); + JS_FreeValue(ctx, op1); + return -1; + } + + res = JS_NewBigFloat(ctx); + if (JS_IsException(res)) { + JS_FreeValue(ctx, op1); + return -1; + } + r = JS_GetBigFloat(res); + a = JS_ToBigFloat(ctx, &a_s, op1); + if (!a) { + JS_FreeValue(ctx, res); + JS_FreeValue(ctx, op1); + return -1; + } + ret = 0; + switch(op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + ret = bf_add_si(r, a, v, ctx->fp_env.prec, ctx->fp_env.flags); + break; + case OP_plus: + ret = bf_set(r, a); + break; + case OP_neg: + ret = bf_set(r, a); + bf_neg(r); + break; + default: + abort(); + } + if (a == &a_s) + bf_delete(a); + JS_FreeValue(ctx, op1); + if (unlikely(ret & BF_ST_MEM_ERROR)) { + JS_FreeValue(ctx, res); + throw_bf_exception(ctx, ret); + return -1; + } + *pres = res; + return 0; +} + +static int js_unary_arith_bigdecimal(JSContext *ctx, + JSValue *pres, OPCodeEnum op, JSValue op1) +{ + bfdec_t *r, *a; + int ret, v; + JSValue res; + + if (op == OP_plus && !is_math_mode(ctx)) { + JS_ThrowTypeError(ctx, "bigdecimal argument with unary +"); + JS_FreeValue(ctx, op1); + return -1; + } + + res = JS_NewBigDecimal(ctx); + if (JS_IsException(res)) { + JS_FreeValue(ctx, op1); + return -1; + } + r = JS_GetBigDecimal(res); + a = JS_ToBigDecimal(ctx, op1); + if (!a) { + JS_FreeValue(ctx, res); + JS_FreeValue(ctx, op1); + return -1; + } + ret = 0; + switch(op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + ret = bfdec_add_si(r, a, v, BF_PREC_INF, BF_RNDZ); + break; + case OP_plus: + ret = bfdec_set(r, a); + break; + case OP_neg: + ret = bfdec_set(r, a); + bfdec_neg(r); + break; + default: + abort(); + } + JS_FreeValue(ctx, op1); + if (unlikely(ret)) { + JS_FreeValue(ctx, res); + throw_bf_exception(ctx, ret); + return -1; + } + *pres = res; + return 0; +} + +#endif /* CONFIG_BIGNUM */ + +static int js_unary_arith_bigint(JSContext *ctx, + JSValue *pres, OPCodeEnum op, JSValue op1) +{ + bf_t a_s, *r, *a; + int ret, v; + JSValue res; + + if (op == OP_plus && !is_math_mode(ctx)) { + JS_ThrowTypeError(ctx, "bigint argument with unary +"); + JS_FreeValue(ctx, op1); + return -1; + } + res = JS_NewBigInt(ctx); + if (JS_IsException(res)) { + JS_FreeValue(ctx, op1); + return -1; + } + r = JS_GetBigInt(res); + a = JS_ToBigInt(ctx, &a_s, op1); + if (!a) { + JS_FreeValue(ctx, res); + JS_FreeValue(ctx, op1); + return -1; + } + ret = 0; + switch(op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + ret = bf_add_si(r, a, v, BF_PREC_INF, BF_RNDZ); + break; + case OP_plus: + ret = bf_set(r, a); + break; + case OP_neg: + ret = bf_set(r, a); + bf_neg(r); + break; + case OP_not: + ret = bf_add_si(r, a, 1, BF_PREC_INF, BF_RNDZ); + bf_neg(r); + break; + default: + abort(); + } + JS_FreeBigInt(ctx, a, &a_s); + JS_FreeValue(ctx, op1); + if (unlikely(ret)) { + JS_FreeValue(ctx, res); + throw_bf_exception(ctx, ret); + return -1; + } + res = JS_CompactBigInt(ctx, res); + *pres = res; + return 0; +} + +static no_inline __exception int js_unary_arith_slow(JSContext *ctx, + JSValue *sp, + OPCodeEnum op) +{ + JSValue op1; + int v; + uint32_t tag; + + op1 = sp[-1]; + /* fast path for float64 */ + if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1))) + goto handle_float64; +#ifdef CONFIG_BIGNUM + if (JS_IsObject(op1)) { + JSValue val; + int ret = js_call_unary_op_fallback(ctx, &val, op1, op); + if (ret < 0) + return -1; + if (ret) { + JS_FreeValue(ctx, op1); + sp[-1] = val; + return 0; + } + } +#endif + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) + goto exception; + tag = JS_VALUE_GET_TAG(op1); + switch(tag) { + case JS_TAG_INT: + { + int64_t v64; + v64 = JS_VALUE_GET_INT(op1); + switch(op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + v64 += v; + break; + case OP_plus: + break; + case OP_neg: + if (v64 == 0) { + sp[-1] = __JS_NewFloat64(ctx, -0.0); + return 0; + } else { + v64 = -v64; + } + break; + default: + abort(); + } + sp[-1] = JS_NewInt64(ctx, v64); + } + break; + case JS_TAG_BIG_INT: + handle_bigint: + if (ctx->rt->bigint_ops.unary_arith(ctx, sp - 1, op, op1)) + goto exception; + break; +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + if (ctx->rt->bigfloat_ops.unary_arith(ctx, sp - 1, op, op1)) + goto exception; + break; + case JS_TAG_BIG_DECIMAL: + if (ctx->rt->bigdecimal_ops.unary_arith(ctx, sp - 1, op, op1)) + goto exception; + break; +#endif + default: + handle_float64: + { + double d; + if (is_math_mode(ctx)) + goto handle_bigint; + d = JS_VALUE_GET_FLOAT64(op1); + switch(op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + d += v; + break; + case OP_plus: + break; + case OP_neg: + d = -d; + break; + default: + abort(); + } + sp[-1] = __JS_NewFloat64(ctx, d); + } + break; + } + return 0; + exception: + sp[-1] = JS_UNDEFINED; + return -1; +} + +static __exception int js_post_inc_slow(JSContext *ctx, + JSValue *sp, OPCodeEnum op) +{ + JSValue op1; + + /* XXX: allow custom operators */ + op1 = sp[-1]; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + sp[-1] = JS_UNDEFINED; + return -1; + } + sp[-1] = op1; + sp[0] = JS_DupValue(ctx, op1); + return js_unary_arith_slow(ctx, sp + 1, op - OP_post_dec + OP_dec); +} + +static no_inline int js_not_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1; + + op1 = sp[-1]; +#ifdef CONFIG_BIGNUM + if (JS_IsObject(op1)) { + JSValue val; + int ret = js_call_unary_op_fallback(ctx, &val, op1, OP_not); + if (ret < 0) + return -1; + if (ret) { + JS_FreeValue(ctx, op1); + sp[-1] = val; + return 0; + } + } +#endif + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) + goto exception; + if (is_math_mode(ctx) || JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT) { + if (ctx->rt->bigint_ops.unary_arith(ctx, sp - 1, OP_not, op1)) + goto exception; + } else { + int32_t v1; + if (unlikely(JS_ToInt32Free(ctx, &v1, op1))) + goto exception; + sp[-1] = JS_NewInt32(ctx, ~v1); + } + return 0; + exception: + sp[-1] = JS_UNDEFINED; + return -1; +} + +static int js_binary_arith_bigint(JSContext *ctx, OPCodeEnum op, + JSValue *pres, JSValue op1, JSValue op2) +{ + bf_t a_s, b_s, *r, *a, *b; + int ret; + JSValue res; + + res = JS_NewBigInt(ctx); + if (JS_IsException(res)) + goto fail; + a = JS_ToBigInt(ctx, &a_s, op1); + if (!a) + goto fail; + b = JS_ToBigInt(ctx, &b_s, op2); + if (!b) { + JS_FreeBigInt(ctx, a, &a_s); + goto fail; + } + r = JS_GetBigInt(res); + ret = 0; + switch(op) { + case OP_add: + ret = bf_add(r, a, b, BF_PREC_INF, BF_RNDZ); + break; + case OP_sub: + ret = bf_sub(r, a, b, BF_PREC_INF, BF_RNDZ); + break; + case OP_mul: + ret = bf_mul(r, a, b, BF_PREC_INF, BF_RNDZ); + break; + case OP_div: + if (!is_math_mode(ctx)) { + bf_t rem_s, *rem = &rem_s; + bf_init(ctx->bf_ctx, rem); + ret = bf_divrem(r, rem, a, b, BF_PREC_INF, BF_RNDZ, + BF_RNDZ); + bf_delete(rem); + } else { + goto math_mode_div_pow; + } + break; +#ifdef CONFIG_BIGNUM + case OP_math_mod: + /* Euclidian remainder */ + ret = bf_rem(r, a, b, BF_PREC_INF, BF_RNDZ, + BF_DIVREM_EUCLIDIAN) & BF_ST_INVALID_OP; + break; +#endif + case OP_mod: + ret = bf_rem(r, a, b, BF_PREC_INF, BF_RNDZ, + BF_RNDZ) & BF_ST_INVALID_OP; + break; + case OP_pow: + if (b->sign) { + if (!is_math_mode(ctx)) { + ret = BF_ST_INVALID_OP; + } else { + math_mode_div_pow: +#ifdef CONFIG_BIGNUM + JS_FreeValue(ctx, res); + ret = js_call_binary_op_simple(ctx, &res, ctx->class_proto[JS_CLASS_BIG_INT], op1, op2, op); + if (ret != 0) { + JS_FreeBigInt(ctx, a, &a_s); + JS_FreeBigInt(ctx, b, &b_s); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (ret < 0) { + return -1; + } else { + *pres = res; + return 0; + } + } + /* if no BigInt power operator defined, return a + bigfloat */ + res = JS_NewBigFloat(ctx); + if (JS_IsException(res)) { + JS_FreeBigInt(ctx, a, &a_s); + JS_FreeBigInt(ctx, b, &b_s); + goto fail; + } + r = JS_GetBigFloat(res); + if (op == OP_div) { + ret = bf_div(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags) & BF_ST_MEM_ERROR; + } else { + ret = bf_pow(r, a, b, ctx->fp_env.prec, + ctx->fp_env.flags | BF_POW_JS_QUIRKS) & BF_ST_MEM_ERROR; + } + JS_FreeBigInt(ctx, a, &a_s); + JS_FreeBigInt(ctx, b, &b_s); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (unlikely(ret)) { + JS_FreeValue(ctx, res); + throw_bf_exception(ctx, ret); + return -1; + } + *pres = res; + return 0; +#else + abort(); +#endif + } + } else { + ret = bf_pow(r, a, b, BF_PREC_INF, BF_RNDZ | BF_POW_JS_QUIRKS); + } + break; + + /* logical operations */ + case OP_shl: + case OP_sar: + { + slimb_t v2; +#if LIMB_BITS == 32 + bf_get_int32(&v2, b, 0); + if (v2 == INT32_MIN) + v2 = INT32_MIN + 1; +#else + bf_get_int64(&v2, b, 0); + if (v2 == INT64_MIN) + v2 = INT64_MIN + 1; +#endif + if (op == OP_sar) + v2 = -v2; + ret = bf_set(r, a); + ret |= bf_mul_2exp(r, v2, BF_PREC_INF, BF_RNDZ); + if (v2 < 0) { + ret |= bf_rint(r, BF_RNDD) & (BF_ST_OVERFLOW | BF_ST_MEM_ERROR); + } + } + break; + case OP_and: + ret = bf_logic_and(r, a, b); + break; + case OP_or: + ret = bf_logic_or(r, a, b); + break; + case OP_xor: + ret = bf_logic_xor(r, a, b); + break; + default: + abort(); + } + JS_FreeBigInt(ctx, a, &a_s); + JS_FreeBigInt(ctx, b, &b_s); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (unlikely(ret)) { + JS_FreeValue(ctx, res); + throw_bf_exception(ctx, ret); + return -1; + } + *pres = JS_CompactBigInt(ctx, res); + return 0; + fail: + JS_FreeValue(ctx, res); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return -1; +} + +#ifdef CONFIG_BIGNUM +static int js_binary_arith_bigfloat(JSContext *ctx, OPCodeEnum op, + JSValue *pres, JSValue op1, JSValue op2) +{ + bf_t a_s, b_s, *r, *a, *b; + int ret; + JSValue res; + + res = JS_NewBigFloat(ctx); + if (JS_IsException(res)) + goto fail; + r = JS_GetBigFloat(res); + a = JS_ToBigFloat(ctx, &a_s, op1); + if (!a) { + JS_FreeValue(ctx, res); + goto fail; + } + b = JS_ToBigFloat(ctx, &b_s, op2); + if (!b) { + if (a == &a_s) + bf_delete(a); + JS_FreeValue(ctx, res); + goto fail; + } + bf_init(ctx->bf_ctx, r); + switch(op) { + case OP_add: + ret = bf_add(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags); + break; + case OP_sub: + ret = bf_sub(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags); + break; + case OP_mul: + ret = bf_mul(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags); + break; + case OP_div: + ret = bf_div(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags); + break; + case OP_math_mod: + /* Euclidian remainder */ + ret = bf_rem(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags, + BF_DIVREM_EUCLIDIAN); + break; + case OP_mod: + ret = bf_rem(r, a, b, ctx->fp_env.prec, ctx->fp_env.flags, + BF_RNDZ); + break; + case OP_pow: + ret = bf_pow(r, a, b, ctx->fp_env.prec, + ctx->fp_env.flags | BF_POW_JS_QUIRKS); + break; + default: + abort(); + } + if (a == &a_s) + bf_delete(a); + if (b == &b_s) + bf_delete(b); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (unlikely(ret & BF_ST_MEM_ERROR)) { + JS_FreeValue(ctx, res); + throw_bf_exception(ctx, ret); + return -1; + } + *pres = res; + return 0; + fail: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return -1; +} + +/* b must be a positive integer */ +static int js_bfdec_pow(bfdec_t *r, const bfdec_t *a, const bfdec_t *b) +{ + bfdec_t b1; + int32_t b2; + int ret; + + bfdec_init(b->ctx, &b1); + ret = bfdec_set(&b1, b); + if (ret) { + bfdec_delete(&b1); + return ret; + } + ret = bfdec_rint(&b1, BF_RNDZ); + if (ret) { + bfdec_delete(&b1); + return BF_ST_INVALID_OP; /* must be an integer */ + } + ret = bfdec_get_int32(&b2, &b1); + bfdec_delete(&b1); + if (ret) + return ret; /* overflow */ + if (b2 < 0) + return BF_ST_INVALID_OP; /* must be positive */ + return bfdec_pow_ui(r, a, b2); +} + +static int js_binary_arith_bigdecimal(JSContext *ctx, OPCodeEnum op, + JSValue *pres, JSValue op1, JSValue op2) +{ + bfdec_t *r, *a, *b; + int ret; + JSValue res; + + res = JS_NewBigDecimal(ctx); + if (JS_IsException(res)) + goto fail; + r = JS_GetBigDecimal(res); + + a = JS_ToBigDecimal(ctx, op1); + if (!a) + goto fail; + b = JS_ToBigDecimal(ctx, op2); + if (!b) + goto fail; + switch(op) { + case OP_add: + ret = bfdec_add(r, a, b, BF_PREC_INF, BF_RNDZ); + break; + case OP_sub: + ret = bfdec_sub(r, a, b, BF_PREC_INF, BF_RNDZ); + break; + case OP_mul: + ret = bfdec_mul(r, a, b, BF_PREC_INF, BF_RNDZ); + break; + case OP_div: + ret = bfdec_div(r, a, b, BF_PREC_INF, BF_RNDZ); + break; + case OP_math_mod: + /* Euclidian remainder */ + ret = bfdec_rem(r, a, b, BF_PREC_INF, BF_RNDZ, BF_DIVREM_EUCLIDIAN); + break; + case OP_mod: + ret = bfdec_rem(r, a, b, BF_PREC_INF, BF_RNDZ, BF_RNDZ); + break; + case OP_pow: + ret = js_bfdec_pow(r, a, b); + break; + default: + abort(); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (unlikely(ret)) { + JS_FreeValue(ctx, res); + throw_bf_exception(ctx, ret); + return -1; + } + *pres = res; + return 0; + fail: + JS_FreeValue(ctx, res); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return -1; +} +#endif /* CONFIG_BIGNUM */ + +static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + double d1, d2; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + /* fast path for float operations */ + if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + d2 = JS_VALUE_GET_FLOAT64(op2); + goto handle_float64; + } + +#ifdef CONFIG_BIGNUM + /* try to call an overloaded operator */ + if ((tag1 == JS_TAG_OBJECT && + (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED)) || + (tag2 == JS_TAG_OBJECT && + (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED))) { + JSValue res; + int ret = js_call_binary_op_fallback(ctx, &res, op1, op2, op, TRUE, 0); + if (ret != 0) { + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (ret < 0) { + goto exception; + } else { + sp[-2] = res; + return 0; + } + } + } +#endif + + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + int32_t v1, v2; + int64_t v; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + switch(op) { + case OP_sub: + v = (int64_t)v1 - (int64_t)v2; + break; + case OP_mul: + v = (int64_t)v1 * (int64_t)v2; + if (is_math_mode(ctx) && + (v < -MAX_SAFE_INTEGER || v > MAX_SAFE_INTEGER)) + goto handle_bigint; + if (v == 0 && (v1 | v2) < 0) { + sp[-2] = __JS_NewFloat64(ctx, -0.0); + return 0; + } + break; + case OP_div: + if (is_math_mode(ctx)) + goto handle_bigint; + sp[-2] = __JS_NewFloat64(ctx, (double)v1 / (double)v2); + return 0; +#ifdef CONFIG_BIGNUM + case OP_math_mod: + if (unlikely(v2 == 0)) { + throw_bf_exception(ctx, BF_ST_DIVIDE_ZERO); + goto exception; + } + v = (int64_t)v1 % (int64_t)v2; + if (v < 0) { + if (v2 < 0) + v -= v2; + else + v += v2; + } + break; +#endif + case OP_mod: + if (v1 < 0 || v2 <= 0) { + sp[-2] = JS_NewFloat64(ctx, fmod(v1, v2)); + return 0; + } else { + v = (int64_t)v1 % (int64_t)v2; + } + break; + case OP_pow: + if (!is_math_mode(ctx)) { + sp[-2] = JS_NewFloat64(ctx, js_pow(v1, v2)); + return 0; + } else { + goto handle_bigint; + } + break; + default: + abort(); + } + sp[-2] = JS_NewInt64(ctx, v); + } else +#ifdef CONFIG_BIGNUM + if (tag1 == JS_TAG_BIG_DECIMAL || tag2 == JS_TAG_BIG_DECIMAL) { + if (ctx->rt->bigdecimal_ops.binary_arith(ctx, op, sp - 2, op1, op2)) + goto exception; + } else if (tag1 == JS_TAG_BIG_FLOAT || tag2 == JS_TAG_BIG_FLOAT) { + if (ctx->rt->bigfloat_ops.binary_arith(ctx, op, sp - 2, op1, op2)) + goto exception; + } else +#endif + if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { + handle_bigint: + if (ctx->rt->bigint_ops.binary_arith(ctx, op, sp - 2, op1, op2)) + goto exception; + } else { + double dr; + /* float64 result */ + if (JS_ToFloat64Free(ctx, &d1, op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (JS_ToFloat64Free(ctx, &d2, op2)) + goto exception; + handle_float64: + if (is_math_mode(ctx) && is_safe_integer(d1) && is_safe_integer(d2)) + goto handle_bigint; + switch(op) { + case OP_sub: + dr = d1 - d2; + break; + case OP_mul: + dr = d1 * d2; + break; + case OP_div: + dr = d1 / d2; + break; + case OP_mod: + dr = fmod(d1, d2); + break; +#ifdef CONFIG_BIGNUM + case OP_math_mod: + d2 = fabs(d2); + dr = fmod(d1, d2); + /* XXX: loss of accuracy if dr < 0 */ + if (dr < 0) + dr += d2; + break; +#endif + case OP_pow: + dr = js_pow(d1, d2); + break; + default: + abort(); + } + sp[-2] = __JS_NewFloat64(ctx, dr); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + /* fast path for float64 */ + if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { + double d1, d2; + d1 = JS_VALUE_GET_FLOAT64(op1); + d2 = JS_VALUE_GET_FLOAT64(op2); + sp[-2] = __JS_NewFloat64(ctx, d1 + d2); + return 0; + } + + if (tag1 == JS_TAG_OBJECT || tag2 == JS_TAG_OBJECT) { +#ifdef CONFIG_BIGNUM + /* try to call an overloaded operator */ + if ((tag1 == JS_TAG_OBJECT && + (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED && + tag2 != JS_TAG_STRING)) || + (tag2 == JS_TAG_OBJECT && + (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED && + tag1 != JS_TAG_STRING))) { + JSValue res; + int ret = js_call_binary_op_fallback(ctx, &res, op1, op2, OP_add, + FALSE, HINT_NONE); + if (ret != 0) { + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (ret < 0) { + goto exception; + } else { + sp[-2] = res; + return 0; + } + } + } +#endif + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + } + + if (tag1 == JS_TAG_STRING || tag2 == JS_TAG_STRING) { + sp[-2] = JS_ConcatString(ctx, op1, op2); + if (JS_IsException(sp[-2])) + goto exception; + return 0; + } + + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + int32_t v1, v2; + int64_t v; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + v = (int64_t)v1 + (int64_t)v2; + sp[-2] = JS_NewInt64(ctx, v); + } else +#ifdef CONFIG_BIGNUM + if (tag1 == JS_TAG_BIG_DECIMAL || tag2 == JS_TAG_BIG_DECIMAL) { + if (ctx->rt->bigdecimal_ops.binary_arith(ctx, OP_add, sp - 2, op1, op2)) + goto exception; + } else if (tag1 == JS_TAG_BIG_FLOAT || tag2 == JS_TAG_BIG_FLOAT) { + if (ctx->rt->bigfloat_ops.binary_arith(ctx, OP_add, sp - 2, op1, op2)) + goto exception; + } else +#endif + if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { + handle_bigint: + if (ctx->rt->bigint_ops.binary_arith(ctx, OP_add, sp - 2, op1, op2)) + goto exception; + } else { + double d1, d2; + /* float64 result */ + if (JS_ToFloat64Free(ctx, &d1, op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (JS_ToFloat64Free(ctx, &d2, op2)) + goto exception; + if (is_math_mode(ctx) && is_safe_integer(d1) && is_safe_integer(d2)) + goto handle_bigint; + sp[-2] = __JS_NewFloat64(ctx, d1 + d2); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline __exception int js_binary_logic_slow(JSContext *ctx, + JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + uint32_t v1, v2, r; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + +#ifdef CONFIG_BIGNUM + /* try to call an overloaded operator */ + if ((tag1 == JS_TAG_OBJECT && + (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED)) || + (tag2 == JS_TAG_OBJECT && + (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED))) { + JSValue res; + int ret = js_call_binary_op_fallback(ctx, &res, op1, op2, op, TRUE, 0); + if (ret != 0) { + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (ret < 0) { + goto exception; + } else { + sp[-2] = res; + return 0; + } + } + } +#endif + + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + + if (is_math_mode(ctx)) + goto bigint_op; + + tag1 = JS_VALUE_GET_TAG(op1); + tag2 = JS_VALUE_GET_TAG(op2); + if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { + if (tag1 != tag2) { + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + JS_ThrowTypeError(ctx, "both operands must be bigint"); + goto exception; + } else { + bigint_op: + if (ctx->rt->bigint_ops.binary_arith(ctx, op, sp - 2, op1, op2)) + goto exception; + } + } else { + if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v1, op1))) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v2, op2))) + goto exception; + switch(op) { + case OP_shl: + r = v1 << (v2 & 0x1f); + break; + case OP_sar: + r = (int)v1 >> (v2 & 0x1f); + break; + case OP_and: + r = v1 & v2; + break; + case OP_or: + r = v1 | v2; + break; + case OP_xor: + r = v1 ^ v2; + break; + default: + abort(); + } + sp[-2] = JS_NewInt32(ctx, r); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +/* Note: also used for bigint */ +static int js_compare_bigfloat(JSContext *ctx, OPCodeEnum op, + JSValue op1, JSValue op2) +{ + bf_t a_s, b_s, *a, *b; + int res; + + a = JS_ToBigFloat(ctx, &a_s, op1); + if (!a) { + JS_FreeValue(ctx, op2); + return -1; + } + b = JS_ToBigFloat(ctx, &b_s, op2); + if (!b) { + if (a == &a_s) + bf_delete(a); + JS_FreeValue(ctx, op1); + return -1; + } + switch(op) { + case OP_lt: + res = bf_cmp_lt(a, b); /* if NaN return false */ + break; + case OP_lte: + res = bf_cmp_le(a, b); /* if NaN return false */ + break; + case OP_gt: + res = bf_cmp_lt(b, a); /* if NaN return false */ + break; + case OP_gte: + res = bf_cmp_le(b, a); /* if NaN return false */ + break; + case OP_eq: + res = bf_cmp_eq(a, b); /* if NaN return false */ + break; + default: + abort(); + } + if (a == &a_s) + bf_delete(a); + if (b == &b_s) + bf_delete(b); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return res; +} + +#ifdef CONFIG_BIGNUM +static int js_compare_bigdecimal(JSContext *ctx, OPCodeEnum op, + JSValue op1, JSValue op2) +{ + bfdec_t *a, *b; + int res; + + /* Note: binary floats are converted to bigdecimal with + toString(). It is not mathematically correct but is consistent + with the BigDecimal() constructor behavior */ + op1 = JS_ToBigDecimalFree(ctx, op1, TRUE); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + return -1; + } + op2 = JS_ToBigDecimalFree(ctx, op2, TRUE); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + return -1; + } + a = JS_ToBigDecimal(ctx, op1); /* cannot fail */ + b = JS_ToBigDecimal(ctx, op2); /* cannot fail */ + + switch(op) { + case OP_lt: + res = bfdec_cmp_lt(a, b); /* if NaN return false */ + break; + case OP_lte: + res = bfdec_cmp_le(a, b); /* if NaN return false */ + break; + case OP_gt: + res = bfdec_cmp_lt(b, a); /* if NaN return false */ + break; + case OP_gte: + res = bfdec_cmp_le(b, a); /* if NaN return false */ + break; + case OP_eq: + res = bfdec_cmp_eq(a, b); /* if NaN return false */ + break; + default: + abort(); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return res; +} +#endif /* !CONFIG_BIGNUM */ + +static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + int res; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); +#ifdef CONFIG_BIGNUM + /* try to call an overloaded operator */ + if ((tag1 == JS_TAG_OBJECT && + (tag2 != JS_TAG_NULL && tag2 != JS_TAG_UNDEFINED)) || + (tag2 == JS_TAG_OBJECT && + (tag1 != JS_TAG_NULL && tag1 != JS_TAG_UNDEFINED))) { + JSValue ret; + res = js_call_binary_op_fallback(ctx, &ret, op1, op2, op, + FALSE, HINT_NUMBER); + if (res != 0) { + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (res < 0) { + goto exception; + } else { + sp[-2] = ret; + return 0; + } + } + } +#endif + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NUMBER); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NUMBER); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { + JSString *p1, *p2; + p1 = JS_VALUE_GET_STRING(op1); + p2 = JS_VALUE_GET_STRING(op2); + res = js_string_compare(ctx, p1, p2); + switch(op) { + case OP_lt: + res = (res < 0); + break; + case OP_lte: + res = (res <= 0); + break; + case OP_gt: + res = (res > 0); + break; + default: + case OP_gte: + res = (res >= 0); + break; + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } else if ((tag1 <= JS_TAG_NULL || tag1 == JS_TAG_FLOAT64) && + (tag2 <= JS_TAG_NULL || tag2 == JS_TAG_FLOAT64)) { + /* fast path for float64/int */ + goto float64_compare; + } else { + if (((tag1 == JS_TAG_BIG_INT && tag2 == JS_TAG_STRING) || + (tag2 == JS_TAG_BIG_INT && tag1 == JS_TAG_STRING)) && + !is_math_mode(ctx)) { + if (tag1 == JS_TAG_STRING) { + op1 = JS_StringToBigInt(ctx, op1); + if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT) + goto invalid_bigint_string; + } + if (tag2 == JS_TAG_STRING) { + op2 = JS_StringToBigInt(ctx, op2); + if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) { + invalid_bigint_string: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + res = FALSE; + goto done; + } + } + } else { + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + } + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + +#ifdef CONFIG_BIGNUM + if (tag1 == JS_TAG_BIG_DECIMAL || tag2 == JS_TAG_BIG_DECIMAL) { + res = ctx->rt->bigdecimal_ops.compare(ctx, op, op1, op2); + if (res < 0) + goto exception; + } else if (tag1 == JS_TAG_BIG_FLOAT || tag2 == JS_TAG_BIG_FLOAT) { + res = ctx->rt->bigfloat_ops.compare(ctx, op, op1, op2); + if (res < 0) + goto exception; + } else +#endif + if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { + res = ctx->rt->bigint_ops.compare(ctx, op, op1, op2); + if (res < 0) + goto exception; + } else { + double d1, d2; + + float64_compare: + /* can use floating point comparison */ + if (tag1 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + } else { + d1 = JS_VALUE_GET_INT(op1); + } + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else { + d2 = JS_VALUE_GET_INT(op2); + } + switch(op) { + case OP_lt: + res = (d1 < d2); /* if NaN return false */ + break; + case OP_lte: + res = (d1 <= d2); /* if NaN return false */ + break; + case OP_gt: + res = (d1 > d2); /* if NaN return false */ + break; + default: + case OP_gte: + res = (d1 >= d2); /* if NaN return false */ + break; + } + } + } + done: + sp[-2] = JS_NewBool(ctx, res); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static BOOL tag_is_number(uint32_t tag) +{ + return (tag == JS_TAG_INT || tag == JS_TAG_BIG_INT || + tag == JS_TAG_FLOAT64 +#ifdef CONFIG_BIGNUM + || tag == JS_TAG_BIG_FLOAT || tag == JS_TAG_BIG_DECIMAL +#endif + ); +} + +static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, + BOOL is_neq) +{ + JSValue op1, op2; +#ifdef CONFIG_BIGNUM + JSValue ret; +#endif + int res; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + redo: + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + if (tag_is_number(tag1) && tag_is_number(tag2)) { + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); + } else if ((tag1 == JS_TAG_FLOAT64 && + (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) || + (tag2 == JS_TAG_FLOAT64 && + (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64))) { + double d1, d2; + if (tag1 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + } else { + d1 = JS_VALUE_GET_INT(op1); + } + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else { + d2 = JS_VALUE_GET_INT(op2); + } + res = (d1 == d2); + } else +#ifdef CONFIG_BIGNUM + if (tag1 == JS_TAG_BIG_DECIMAL || tag2 == JS_TAG_BIG_DECIMAL) { + res = ctx->rt->bigdecimal_ops.compare(ctx, OP_eq, op1, op2); + if (res < 0) + goto exception; + } else if (tag1 == JS_TAG_BIG_FLOAT || tag2 == JS_TAG_BIG_FLOAT) { + res = ctx->rt->bigfloat_ops.compare(ctx, OP_eq, op1, op2); + if (res < 0) + goto exception; + } else +#endif + { + res = ctx->rt->bigint_ops.compare(ctx, OP_eq, op1, op2); + if (res < 0) + goto exception; + } + } else if (tag1 == tag2) { +#ifdef CONFIG_BIGNUM + if (tag1 == JS_TAG_OBJECT) { + /* try the fallback operator */ + res = js_call_binary_op_fallback(ctx, &ret, op1, op2, + is_neq ? OP_neq : OP_eq, + FALSE, HINT_NONE); + if (res != 0) { + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (res < 0) { + goto exception; + } else { + sp[-2] = ret; + return 0; + } + } + } +#endif + res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); + } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) || + (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) { + res = TRUE; + } else if ((tag1 == JS_TAG_STRING && tag_is_number(tag2)) || + (tag2 == JS_TAG_STRING && tag_is_number(tag1))) { + + if ((tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) && + !is_math_mode(ctx)) { + if (tag1 == JS_TAG_STRING) { + op1 = JS_StringToBigInt(ctx, op1); + if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT) + goto invalid_bigint_string; + } + if (tag2 == JS_TAG_STRING) { + op2 = JS_StringToBigInt(ctx, op2); + if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) { + invalid_bigint_string: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + res = FALSE; + goto done; + } + } + } else { + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + } + res = js_strict_eq(ctx, op1, op2); + } else if (tag1 == JS_TAG_BOOL) { + op1 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op1)); + goto redo; + } else if (tag2 == JS_TAG_BOOL) { + op2 = JS_NewInt32(ctx, JS_VALUE_GET_INT(op2)); + goto redo; + } else if ((tag1 == JS_TAG_OBJECT && + (tag_is_number(tag2) || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) || + (tag2 == JS_TAG_OBJECT && + (tag_is_number(tag1) || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL))) { +#ifdef CONFIG_BIGNUM + /* try the fallback operator */ + res = js_call_binary_op_fallback(ctx, &ret, op1, op2, + is_neq ? OP_neq : OP_eq, + FALSE, HINT_NONE); + if (res != 0) { + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + if (res < 0) { + goto exception; + } else { + sp[-2] = ret; + return 0; + } + } +#endif + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + goto redo; + } else { + /* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */ + if ((JS_IsHTMLDDA(ctx, op1) && + (tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) || + (JS_IsHTMLDDA(ctx, op2) && + (tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) { + res = TRUE; + } else { + res = FALSE; + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } + done: + sp[-2] = JS_NewBool(ctx, res ^ is_neq); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline int js_shr_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + uint32_t v1, v2, r; + + op1 = sp[-2]; + op2 = sp[-1]; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + /* XXX: could forbid >>> in bignum mode */ + if (!is_math_mode(ctx) && + (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT || + JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_INT)) { + JS_ThrowTypeError(ctx, "bigint operands are forbidden for >>>"); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + goto exception; + } + /* cannot give an exception */ + JS_ToUint32Free(ctx, &v1, op1); + JS_ToUint32Free(ctx, &v2, op2); + r = v1 >> (v2 & 0x1f); + sp[-2] = JS_NewUint32(ctx, r); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +#ifdef CONFIG_BIGNUM +static JSValue js_mul_pow10_to_float64(JSContext *ctx, const bf_t *a, + int64_t exponent) +{ + bf_t r_s, *r = &r_s; + double d; + int ret; + + /* always convert to Float64 */ + bf_init(ctx->bf_ctx, r); + ret = bf_mul_pow_radix(r, a, 10, exponent, + 53, bf_set_exp_bits(11) | BF_RNDN | + BF_FLAG_SUBNORMAL); + bf_get_float64(r, &d, BF_RNDN); + bf_delete(r); + if (ret & BF_ST_MEM_ERROR) + return JS_ThrowOutOfMemory(ctx); + else + return __JS_NewFloat64(ctx, d); +} + +static no_inline int js_mul_pow10(JSContext *ctx, JSValue *sp) +{ + bf_t a_s, *a, *r; + JSValue op1, op2, res; + int64_t e; + int ret; + + res = JS_NewBigFloat(ctx); + if (JS_IsException(res)) + return -1; + r = JS_GetBigFloat(res); + op1 = sp[-2]; + op2 = sp[-1]; + a = JS_ToBigFloat(ctx, &a_s, op1); + if (!a) { + JS_FreeValue(ctx, res); + return -1; + } + if (JS_IsBigInt(ctx, op2)) { + ret = JS_ToBigInt64(ctx, &e, op2); + } else { + ret = JS_ToInt64(ctx, &e, op2); + } + if (ret) { + if (a == &a_s) + bf_delete(a); + JS_FreeValue(ctx, res); + return -1; + } + + bf_mul_pow_radix(r, a, 10, e, ctx->fp_env.prec, ctx->fp_env.flags); + if (a == &a_s) + bf_delete(a); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = res; + return 0; +} +#endif + +/* XXX: Should take JSValueConst arguments */ +static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, + JSStrictEqModeEnum eq_mode) +{ + BOOL res; + int tag1, tag2; + double d1, d2; + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + switch(tag1) { + case JS_TAG_BOOL: + if (tag1 != tag2) { + res = FALSE; + } else { + res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); + goto done_no_free; + } + break; + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + res = (tag1 == tag2); + break; + case JS_TAG_STRING: + { + JSString *p1, *p2; + if (tag1 != tag2) { + res = FALSE; + } else { + p1 = JS_VALUE_GET_STRING(op1); + p2 = JS_VALUE_GET_STRING(op2); + res = (js_string_compare(ctx, p1, p2) == 0); + } + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p1, *p2; + if (tag1 != tag2) { + res = FALSE; + } else { + p1 = JS_VALUE_GET_PTR(op1); + p2 = JS_VALUE_GET_PTR(op2); + res = (p1 == p2); + } + } + break; + case JS_TAG_OBJECT: + if (tag1 != tag2) + res = FALSE; + else + res = JS_VALUE_GET_OBJ(op1) == JS_VALUE_GET_OBJ(op2); + break; + case JS_TAG_INT: + d1 = JS_VALUE_GET_INT(op1); + if (tag2 == JS_TAG_INT) { + d2 = JS_VALUE_GET_INT(op2); + goto number_test; + } else if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + goto number_test; + } else { + res = FALSE; + } + break; + case JS_TAG_FLOAT64: + d1 = JS_VALUE_GET_FLOAT64(op1); + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else if (tag2 == JS_TAG_INT) { + d2 = JS_VALUE_GET_INT(op2); + } else { + res = FALSE; + break; + } + number_test: + if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) { + JSFloat64Union u1, u2; + /* NaN is not always normalized, so this test is necessary */ + if (isnan(d1) || isnan(d2)) { + res = isnan(d1) == isnan(d2); + } else if (eq_mode == JS_EQ_SAME_VALUE_ZERO) { + res = (d1 == d2); /* +0 == -0 */ + } else { + u1.d = d1; + u2.d = d2; + res = (u1.u64 == u2.u64); /* +0 != -0 */ + } + } else { + res = (d1 == d2); /* if NaN return false and +0 == -0 */ + } + goto done_no_free; + case JS_TAG_BIG_INT: + { + bf_t a_s, *a, b_s, *b; + if (tag1 != tag2) { + res = FALSE; + break; + } + a = JS_ToBigFloat(ctx, &a_s, op1); /* cannot fail */ + b = JS_ToBigFloat(ctx, &b_s, op2); /* cannot fail */ + res = bf_cmp_eq(a, b); + if (a == &a_s) + bf_delete(a); + if (b == &b_s) + bf_delete(b); + } + break; +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + { + JSBigFloat *p1, *p2; + const bf_t *a, *b; + if (tag1 != tag2) { + res = FALSE; + break; + } + p1 = JS_VALUE_GET_PTR(op1); + p2 = JS_VALUE_GET_PTR(op2); + a = &p1->num; + b = &p2->num; + if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) { + if (eq_mode == JS_EQ_SAME_VALUE_ZERO && + a->expn == BF_EXP_ZERO && b->expn == BF_EXP_ZERO) { + res = TRUE; + } else { + res = (bf_cmp_full(a, b) == 0); + } + } else { + res = bf_cmp_eq(a, b); + } + } + break; + case JS_TAG_BIG_DECIMAL: + { + JSBigDecimal *p1, *p2; + const bfdec_t *a, *b; + if (tag1 != tag2) { + res = FALSE; + break; + } + p1 = JS_VALUE_GET_PTR(op1); + p2 = JS_VALUE_GET_PTR(op2); + a = &p1->num; + b = &p2->num; + res = bfdec_cmp_eq(a, b); + } + break; +#endif + default: + res = FALSE; + break; + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + done_no_free: + return res; +} + +static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2) +{ + return js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); +} + +static BOOL js_same_value(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_strict_eq2(ctx, + JS_DupValue(ctx, op1), JS_DupValue(ctx, op2), + JS_EQ_SAME_VALUE); +} + +static BOOL js_same_value_zero(JSContext *ctx, JSValueConst op1, JSValueConst op2) +{ + return js_strict_eq2(ctx, + JS_DupValue(ctx, op1), JS_DupValue(ctx, op2), + JS_EQ_SAME_VALUE_ZERO); +} + +static no_inline int js_strict_eq_slow(JSContext *ctx, JSValue *sp, + BOOL is_neq) +{ + BOOL res; + res = js_strict_eq(ctx, sp[-2], sp[-1]); + sp[-2] = JS_NewBool(ctx, res ^ is_neq); + return 0; +} + +static __exception int js_operator_in(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + JSAtom atom; + int ret; + + op1 = sp[-2]; + op2 = sp[-1]; + + if (JS_VALUE_GET_TAG(op2) != JS_TAG_OBJECT) { + JS_ThrowTypeError(ctx, "invalid 'in' operand"); + return -1; + } + atom = JS_ValueToAtom(ctx, op1); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + ret = JS_HasProperty(ctx, op2, atom); + JS_FreeAtom(ctx, atom); + if (ret < 0) + return -1; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = JS_NewBool(ctx, ret); + return 0; +} + +static __exception int js_operator_private_in(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + int ret; + + op1 = sp[-2]; /* object */ + op2 = sp[-1]; /* field name or method function */ + + if (JS_VALUE_GET_TAG(op1) != JS_TAG_OBJECT) { + JS_ThrowTypeError(ctx, "invalid 'in' operand"); + return -1; + } + if (JS_IsObject(op2)) { + /* method: use the brand */ + ret = JS_CheckBrand(ctx, op1, op2); + if (ret < 0) + return -1; + } else { + JSAtom atom; + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + /* field */ + atom = JS_ValueToAtom(ctx, op2); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + p = JS_VALUE_GET_OBJ(op1); + prs = find_own_property(&pr, p, atom); + JS_FreeAtom(ctx, atom); + ret = (prs != NULL); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = JS_NewBool(ctx, ret); + return 0; +} + +static __exception int js_has_unscopable(JSContext *ctx, JSValueConst obj, + JSAtom atom) +{ + JSValue arr, val; + int ret; + + arr = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_unscopables); + if (JS_IsException(arr)) + return -1; + ret = 0; + if (JS_IsObject(arr)) { + val = JS_GetProperty(ctx, arr, atom); + ret = JS_ToBoolFree(ctx, val); + } + JS_FreeValue(ctx, arr); + return ret; +} + +static __exception int js_operator_instanceof(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + BOOL ret; + + op1 = sp[-2]; + op2 = sp[-1]; + ret = JS_IsInstanceOf(ctx, op1, op2); + if (ret < 0) + return ret; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = JS_NewBool(ctx, ret); + return 0; +} + +static __exception int js_operator_typeof(JSContext *ctx, JSValueConst op1) +{ + JSAtom atom; + uint32_t tag; + + tag = JS_VALUE_GET_NORM_TAG(op1); + switch(tag) { + case JS_TAG_BIG_INT: + atom = JS_ATOM_bigint; + break; +#ifdef CONFIG_BIGNUM + case JS_TAG_BIG_FLOAT: + atom = JS_ATOM_bigfloat; + break; + case JS_TAG_BIG_DECIMAL: + atom = JS_ATOM_bigdecimal; + break; +#endif + case JS_TAG_INT: + case JS_TAG_FLOAT64: + atom = JS_ATOM_number; + break; + case JS_TAG_UNDEFINED: + atom = JS_ATOM_undefined; + break; + case JS_TAG_BOOL: + atom = JS_ATOM_boolean; + break; + case JS_TAG_STRING: + atom = JS_ATOM_string; + break; + case JS_TAG_OBJECT: + { + JSObject *p; + p = JS_VALUE_GET_OBJ(op1); + if (unlikely(p->is_HTMLDDA)) + atom = JS_ATOM_undefined; + else if (JS_IsFunction(ctx, op1)) + atom = JS_ATOM_function; + else + goto obj_type; + } + break; + case JS_TAG_NULL: + obj_type: + atom = JS_ATOM_object; + break; + case JS_TAG_SYMBOL: + atom = JS_ATOM_symbol; + break; + default: + atom = JS_ATOM_unknown; + break; + } + return atom; +} + +static __exception int js_operator_delete(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + JSAtom atom; + int ret; + + op1 = sp[-2]; + op2 = sp[-1]; + atom = JS_ValueToAtom(ctx, op2); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + ret = JS_DeleteProperty(ctx, op1, atom, JS_PROP_THROW_STRICT); + JS_FreeAtom(ctx, atom); + if (unlikely(ret < 0)) + return -1; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = JS_NewBool(ctx, ret); + return 0; +} + +static JSValue js_throw_type_error(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return JS_ThrowTypeError(ctx, "invalid property access"); +} + +/* XXX: not 100% compatible, but mozilla seems to use a similar + implementation to ensure that caller in non strict mode does not + throw (ES5 compatibility) */ +static JSValue js_function_proto_caller(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (!b || (b->js_mode & JS_MODE_STRICT) || !b->has_prototype) { + return js_throw_type_error(ctx, this_val, 0, NULL); + } + return JS_UNDEFINED; +} + +static JSValue js_function_proto_fileName(JSContext *ctx, + JSValueConst this_val) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (b && b->has_debug) { + return JS_AtomToString(ctx, b->debug.filename); + } + return JS_UNDEFINED; +} + +static JSValue js_function_proto_lineNumber(JSContext *ctx, + JSValueConst this_val) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (b && b->has_debug) { + return JS_NewInt32(ctx, b->debug.line_num); + } + return JS_UNDEFINED; +} + +static int js_arguments_define_own_property(JSContext *ctx, + JSValueConst this_obj, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, int flags) +{ + JSObject *p; + uint32_t idx; + p = JS_VALUE_GET_OBJ(this_obj); + /* convert to normal array when redefining an existing numeric field */ + if (p->fast_array && JS_AtomIsArrayIndex(ctx, &idx, prop) && + idx < p->u.array.count) { + if (convert_fast_array_to_array(ctx, p)) + return -1; + } + /* run the default define own property */ + return JS_DefineProperty(ctx, this_obj, prop, val, getter, setter, + flags | JS_PROP_NO_EXOTIC); +} + +static const JSClassExoticMethods js_arguments_exotic_methods = { + .define_own_property = js_arguments_define_own_property, +}; + +static JSValue js_build_arguments(JSContext *ctx, int argc, JSValueConst *argv) +{ + JSValue val, *tab; + JSProperty *pr; + JSObject *p; + int i; + + val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], + JS_CLASS_ARGUMENTS); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_OBJ(val); + + /* add the length field (cannot fail) */ + pr = add_property(ctx, p, JS_ATOM_length, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + pr->u.value = JS_NewInt32(ctx, argc); + + /* initialize the fast array part */ + tab = NULL; + if (argc > 0) { + tab = js_malloc(ctx, sizeof(tab[0]) * argc); + if (!tab) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + for(i = 0; i < argc; i++) { + tab[i] = JS_DupValue(ctx, argv[i]); + } + } + p->u.array.u.values = tab; + p->u.array.count = argc; + + JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator, + JS_DupValue(ctx, ctx->array_proto_values), + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); + /* add callee property to throw a TypeError in strict mode */ + JS_DefineProperty(ctx, val, JS_ATOM_callee, JS_UNDEFINED, + ctx->throw_type_error, ctx->throw_type_error, + JS_PROP_HAS_GET | JS_PROP_HAS_SET); + return val; +} + +#define GLOBAL_VAR_OFFSET 0x40000000 +#define ARGUMENT_VAR_OFFSET 0x20000000 + +/* legacy arguments object: add references to the function arguments */ +static JSValue js_build_mapped_arguments(JSContext *ctx, int argc, + JSValueConst *argv, + JSStackFrame *sf, int arg_count) +{ + JSValue val; + JSProperty *pr; + JSObject *p; + int i; + + val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], + JS_CLASS_MAPPED_ARGUMENTS); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_OBJ(val); + + /* add the length field (cannot fail) */ + pr = add_property(ctx, p, JS_ATOM_length, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + pr->u.value = JS_NewInt32(ctx, argc); + + for(i = 0; i < arg_count; i++) { + JSVarRef *var_ref; + var_ref = get_var_ref(ctx, sf, i, TRUE); + if (!var_ref) + goto fail; + pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E | JS_PROP_VARREF); + if (!pr) { + free_var_ref(ctx->rt, var_ref); + goto fail; + } + pr->u.var_ref = var_ref; + } + + /* the arguments not mapped to the arguments of the function can + be normal properties */ + for(i = arg_count; i < argc; i++) { + if (JS_DefinePropertyValueUint32(ctx, val, i, + JS_DupValue(ctx, argv[i]), + JS_PROP_C_W_E) < 0) + goto fail; + } + + JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator, + JS_DupValue(ctx, ctx->array_proto_values), + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); + /* callee returns this function in non strict mode */ + JS_DefinePropertyValue(ctx, val, JS_ATOM_callee, + JS_DupValue(ctx, ctx->rt->current_stack_frame->cur_func), + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); + return val; + fail: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +static JSValue js_build_rest(JSContext *ctx, int first, int argc, JSValueConst *argv) +{ + JSValue val; + int i, ret; + + val = JS_NewArray(ctx); + if (JS_IsException(val)) + return val; + for (i = first; i < argc; i++) { + ret = JS_DefinePropertyValueUint32(ctx, val, i - first, + JS_DupValue(ctx, argv[i]), + JS_PROP_C_W_E); + if (ret < 0) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + } + return val; +} + +static JSValue build_for_in_iterator(JSContext *ctx, JSValue obj) +{ + JSObject *p, *p1; + JSPropertyEnum *tab_atom; + int i; + JSValue enum_obj; + JSForInIterator *it; + uint32_t tag, tab_atom_count; + + tag = JS_VALUE_GET_TAG(obj); + if (tag != JS_TAG_OBJECT && tag != JS_TAG_NULL && tag != JS_TAG_UNDEFINED) { + obj = JS_ToObjectFree(ctx, obj); + } + + it = js_malloc(ctx, sizeof(*it)); + if (!it) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + enum_obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_FOR_IN_ITERATOR); + if (JS_IsException(enum_obj)) { + js_free(ctx, it); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + it->is_array = FALSE; + it->obj = obj; + it->idx = 0; + it->tab_atom = NULL; + it->atom_count = 0; + it->in_prototype_chain = FALSE; + p1 = JS_VALUE_GET_OBJ(enum_obj); + p1->u.for_in_iterator = it; + + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) + return enum_obj; + + p = JS_VALUE_GET_OBJ(obj); + if (p->fast_array) { + JSShape *sh; + JSShapeProperty *prs; + /* check that there are no enumerable normal fields */ + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + if (prs->flags & JS_PROP_ENUMERABLE) + goto normal_case; + } + /* for fast arrays, we only store the number of elements */ + it->is_array = TRUE; + it->atom_count = p->u.array.count; + } else { + normal_case: + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, + JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { + JS_FreeValue(ctx, enum_obj); + return JS_EXCEPTION; + } + it->tab_atom = tab_atom; + it->atom_count = tab_atom_count; + } + return enum_obj; +} + +/* obj -> enum_obj */ +static __exception int js_for_in_start(JSContext *ctx, JSValue *sp) +{ + sp[-1] = build_for_in_iterator(ctx, sp[-1]); + if (JS_IsException(sp[-1])) + return -1; + return 0; +} + +/* return -1 if exception, 0 if slow case, 1 if the enumeration is finished */ +static __exception int js_for_in_prepare_prototype_chain_enum(JSContext *ctx, + JSValueConst enum_obj) +{ + JSObject *p; + JSForInIterator *it; + JSPropertyEnum *tab_atom; + uint32_t tab_atom_count, i; + JSValue obj1; + + p = JS_VALUE_GET_OBJ(enum_obj); + it = p->u.for_in_iterator; + + /* check if there are enumerable properties in the prototype chain (fast path) */ + obj1 = JS_DupValue(ctx, it->obj); + for(;;) { + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsNull(obj1)) + break; + if (JS_IsException(obj1)) + goto fail; + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, + JS_VALUE_GET_OBJ(obj1), + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + if (tab_atom_count != 0) { + JS_FreeValue(ctx, obj1); + goto slow_path; + } + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + } + JS_FreeValue(ctx, obj1); + return 1; + + slow_path: + /* add the visited properties, even if they are not enumerable */ + if (it->is_array) { + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, + JS_VALUE_GET_OBJ(it->obj), + JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { + goto fail; + } + it->is_array = FALSE; + it->tab_atom = tab_atom; + it->atom_count = tab_atom_count; + } + + for(i = 0; i < it->atom_count; i++) { + if (JS_DefinePropertyValue(ctx, enum_obj, it->tab_atom[i].atom, JS_NULL, JS_PROP_ENUMERABLE) < 0) + goto fail; + } + return 0; + fail: + return -1; +} + +/* enum_obj -> enum_obj value done */ +static __exception int js_for_in_next(JSContext *ctx, JSValue *sp) +{ + JSValueConst enum_obj; + JSObject *p; + JSAtom prop; + JSForInIterator *it; + JSPropertyEnum *tab_atom; + uint32_t tab_atom_count; + int ret; + + enum_obj = sp[-1]; + /* fail safe */ + if (JS_VALUE_GET_TAG(enum_obj) != JS_TAG_OBJECT) + goto done; + p = JS_VALUE_GET_OBJ(enum_obj); + if (p->class_id != JS_CLASS_FOR_IN_ITERATOR) + goto done; + it = p->u.for_in_iterator; + + for(;;) { + if (it->idx >= it->atom_count) { + if (JS_IsNull(it->obj) || JS_IsUndefined(it->obj)) + goto done; /* not an object */ + /* no more property in the current object: look in the prototype */ + if (!it->in_prototype_chain) { + ret = js_for_in_prepare_prototype_chain_enum(ctx, enum_obj); + if (ret < 0) + return -1; + if (ret) + goto done; + it->in_prototype_chain = TRUE; + } + it->obj = JS_GetPrototypeFree(ctx, it->obj); + if (JS_IsException(it->obj)) + return -1; + if (JS_IsNull(it->obj)) + goto done; /* no more prototype */ + + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) + return -1; + + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, + JS_VALUE_GET_OBJ(it->obj), + JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { + return -1; + } + js_free_prop_enum(ctx, it->tab_atom, it->atom_count); + it->tab_atom = tab_atom; + it->atom_count = tab_atom_count; + it->idx = 0; + } else { + if (it->is_array) { + prop = __JS_AtomFromUInt32(it->idx); + it->idx++; + } else { + BOOL is_enumerable; + prop = it->tab_atom[it->idx].atom; + is_enumerable = it->tab_atom[it->idx].is_enumerable; + it->idx++; + if (it->in_prototype_chain) { + /* slow case: we are in the prototype chain */ + ret = JS_GetOwnPropertyInternal(ctx, NULL, JS_VALUE_GET_OBJ(enum_obj), prop); + if (ret < 0) + return ret; + if (ret) + continue; /* already visited */ + /* add to the visited property list */ + if (JS_DefinePropertyValue(ctx, enum_obj, prop, JS_NULL, + JS_PROP_ENUMERABLE) < 0) + return -1; + } + if (!is_enumerable) + continue; + } + /* check if the property was deleted */ + ret = JS_GetOwnPropertyInternal(ctx, NULL, JS_VALUE_GET_OBJ(it->obj), prop); + if (ret < 0) + return ret; + if (ret) + break; + } + } + /* return the property */ + sp[0] = JS_AtomToValue(ctx, prop); + sp[1] = JS_FALSE; + return 0; + done: + /* return the end */ + sp[0] = JS_UNDEFINED; + sp[1] = JS_TRUE; + return 0; +} + +static JSValue JS_GetIterator2(JSContext *ctx, JSValueConst obj, + JSValueConst method) +{ + JSValue enum_obj; + + enum_obj = JS_Call(ctx, method, obj, 0, NULL); + if (JS_IsException(enum_obj)) + return enum_obj; + if (!JS_IsObject(enum_obj)) { + JS_FreeValue(ctx, enum_obj); + return JS_ThrowTypeErrorNotAnObject(ctx); + } + return enum_obj; +} + +static JSValue JS_GetIterator(JSContext *ctx, JSValueConst obj, BOOL is_async) +{ + JSValue method, ret, sync_iter; + + if (is_async) { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_asyncIterator); + if (JS_IsException(method)) + return method; + if (JS_IsUndefined(method) || JS_IsNull(method)) { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) + return method; + sync_iter = JS_GetIterator2(ctx, obj, method); + JS_FreeValue(ctx, method); + if (JS_IsException(sync_iter)) + return sync_iter; + ret = JS_CreateAsyncFromSyncIterator(ctx, sync_iter); + JS_FreeValue(ctx, sync_iter); + return ret; + } + } else { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) + return method; + } + if (!JS_IsFunction(ctx, method)) { + JS_FreeValue(ctx, method); + return JS_ThrowTypeError(ctx, "value is not iterable"); + } + ret = JS_GetIterator2(ctx, obj, method); + JS_FreeValue(ctx, method); + return ret; +} + +/* return *pdone = 2 if the iterator object is not parsed */ +static JSValue JS_IteratorNext2(JSContext *ctx, JSValueConst enum_obj, + JSValueConst method, + int argc, JSValueConst *argv, int *pdone) +{ + JSValue obj; + + /* fast path for the built-in iterators (avoid creating the + intermediate result object) */ + if (JS_IsObject(method)) { + JSObject *p = JS_VALUE_GET_OBJ(method); + if (p->class_id == JS_CLASS_C_FUNCTION && + p->u.cfunc.cproto == JS_CFUNC_iterator_next) { + JSCFunctionType func; + JSValueConst args[1]; + + /* in case the function expects one argument */ + if (argc == 0) { + args[0] = JS_UNDEFINED; + argv = args; + } + func = p->u.cfunc.c_function; + return func.iterator_next(ctx, enum_obj, argc, argv, + pdone, p->u.cfunc.magic); + } + } + obj = JS_Call(ctx, method, enum_obj, argc, argv); + if (JS_IsException(obj)) + goto fail; + if (!JS_IsObject(obj)) { + JS_FreeValue(ctx, obj); + JS_ThrowTypeError(ctx, "iterator must return an object"); + goto fail; + } + *pdone = 2; + return obj; + fail: + *pdone = FALSE; + return JS_EXCEPTION; +} + +static JSValue JS_IteratorNext(JSContext *ctx, JSValueConst enum_obj, + JSValueConst method, + int argc, JSValueConst *argv, BOOL *pdone) +{ + JSValue obj, value, done_val; + int done; + + obj = JS_IteratorNext2(ctx, enum_obj, method, argc, argv, &done); + if (JS_IsException(obj)) + goto fail; + if (done != 2) { + *pdone = done; + return obj; + } else { + done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); + if (JS_IsException(done_val)) + goto fail; + *pdone = JS_ToBoolFree(ctx, done_val); + value = JS_UNDEFINED; + if (!*pdone) { + value = JS_GetProperty(ctx, obj, JS_ATOM_value); + } + JS_FreeValue(ctx, obj); + return value; + } + fail: + JS_FreeValue(ctx, obj); + *pdone = FALSE; + return JS_EXCEPTION; +} + +/* return < 0 in case of exception */ +static int JS_IteratorClose(JSContext *ctx, JSValueConst enum_obj, + BOOL is_exception_pending) +{ + JSValue method, ret, ex_obj; + int res; + + if (is_exception_pending) { + ex_obj = ctx->rt->current_exception; + ctx->rt->current_exception = JS_NULL; + res = -1; + } else { + ex_obj = JS_UNDEFINED; + res = 0; + } + method = JS_GetProperty(ctx, enum_obj, JS_ATOM_return); + if (JS_IsException(method)) { + res = -1; + goto done; + } + if (JS_IsUndefined(method) || JS_IsNull(method)) { + goto done; + } + ret = JS_CallFree(ctx, method, enum_obj, 0, NULL); + if (!is_exception_pending) { + if (JS_IsException(ret)) { + res = -1; + } else if (!JS_IsObject(ret)) { + JS_ThrowTypeErrorNotAnObject(ctx); + res = -1; + } + } + JS_FreeValue(ctx, ret); + done: + if (is_exception_pending) { + JS_Throw(ctx, ex_obj); + } + return res; +} + +/* obj -> enum_rec (3 slots) */ +static __exception int js_for_of_start(JSContext *ctx, JSValue *sp, + BOOL is_async) +{ + JSValue op1, obj, method; + op1 = sp[-1]; + obj = JS_GetIterator(ctx, op1, is_async); + if (JS_IsException(obj)) + return -1; + JS_FreeValue(ctx, op1); + sp[-1] = obj; + method = JS_GetProperty(ctx, obj, JS_ATOM_next); + if (JS_IsException(method)) + return -1; + sp[0] = method; + return 0; +} + +/* enum_rec [objs] -> enum_rec [objs] value done. There are 'offset' + objs. If 'done' is true or in case of exception, 'enum_rec' is set + to undefined. If 'done' is true, 'value' is always set to + undefined. */ +static __exception int js_for_of_next(JSContext *ctx, JSValue *sp, int offset) +{ + JSValue value = JS_UNDEFINED; + int done = 1; + + if (likely(!JS_IsUndefined(sp[offset]))) { + value = JS_IteratorNext(ctx, sp[offset], sp[offset + 1], 0, NULL, &done); + if (JS_IsException(value)) + done = -1; + if (done) { + /* value is JS_UNDEFINED or JS_EXCEPTION */ + /* replace the iteration object with undefined */ + JS_FreeValue(ctx, sp[offset]); + sp[offset] = JS_UNDEFINED; + if (done < 0) { + return -1; + } else { + JS_FreeValue(ctx, value); + value = JS_UNDEFINED; + } + } + } + sp[0] = value; + sp[1] = JS_NewBool(ctx, done); + return 0; +} + +static JSValue JS_IteratorGetCompleteValue(JSContext *ctx, JSValueConst obj, + BOOL *pdone) +{ + JSValue done_val, value; + BOOL done; + done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); + if (JS_IsException(done_val)) + goto fail; + done = JS_ToBoolFree(ctx, done_val); + value = JS_GetProperty(ctx, obj, JS_ATOM_value); + if (JS_IsException(value)) + goto fail; + *pdone = done; + return value; + fail: + *pdone = FALSE; + return JS_EXCEPTION; +} + +static __exception int js_iterator_get_value_done(JSContext *ctx, JSValue *sp) +{ + JSValue obj, value; + BOOL done; + obj = sp[-1]; + if (!JS_IsObject(obj)) { + JS_ThrowTypeError(ctx, "iterator must return an object"); + return -1; + } + value = JS_IteratorGetCompleteValue(ctx, obj, &done); + if (JS_IsException(value)) + return -1; + JS_FreeValue(ctx, obj); + sp[-1] = value; + sp[0] = JS_NewBool(ctx, done); + return 0; +} + +static JSValue js_create_iterator_result(JSContext *ctx, + JSValue val, + BOOL done) +{ + JSValue obj; + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, val); + return obj; + } + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_value, + val, JS_PROP_C_W_E) < 0) { + goto fail; + } + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_done, + JS_NewBool(ctx, done), JS_PROP_C_W_E) < 0) { + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + BOOL *pdone, int magic); + +static JSValue js_create_array_iterator(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic); + +static BOOL js_is_fast_array(JSContext *ctx, JSValueConst obj) +{ + /* Try and handle fast arrays explicitly */ + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { + return TRUE; + } + } + return FALSE; +} + +/* Access an Array's internal JSValue array if available */ +static BOOL js_get_fast_array(JSContext *ctx, JSValueConst obj, + JSValue **arrpp, uint32_t *countp) +{ + /* Try and handle fast arrays explicitly */ + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { + *countp = p->u.array.count; + *arrpp = p->u.array.u.values; + return TRUE; + } + } + return FALSE; +} + +static __exception int js_append_enumerate(JSContext *ctx, JSValue *sp) +{ + JSValue iterator, enumobj, method, value; + int is_array_iterator; + JSValue *arrp; + uint32_t i, count32, pos; + + if (JS_VALUE_GET_TAG(sp[-2]) != JS_TAG_INT) { + JS_ThrowInternalError(ctx, "invalid index for append"); + return -1; + } + + pos = JS_VALUE_GET_INT(sp[-2]); + + /* XXX: further optimisations: + - use ctx->array_proto_values? + - check if array_iterator_prototype next method is built-in and + avoid constructing actual iterator object? + - build this into js_for_of_start and use in all `for (x of o)` loops + */ + iterator = JS_GetProperty(ctx, sp[-1], JS_ATOM_Symbol_iterator); + if (JS_IsException(iterator)) + return -1; + is_array_iterator = JS_IsCFunction(ctx, iterator, + (JSCFunction *)js_create_array_iterator, + JS_ITERATOR_KIND_VALUE); + JS_FreeValue(ctx, iterator); + + enumobj = JS_GetIterator(ctx, sp[-1], FALSE); + if (JS_IsException(enumobj)) + return -1; + method = JS_GetProperty(ctx, enumobj, JS_ATOM_next); + if (JS_IsException(method)) { + JS_FreeValue(ctx, enumobj); + return -1; + } + if (is_array_iterator + && JS_IsCFunction(ctx, method, (JSCFunction *)js_array_iterator_next, 0) + && js_get_fast_array(ctx, sp[-1], &arrp, &count32)) { + uint32_t len; + if (js_get_length32(ctx, &len, sp[-1])) + goto exception; + /* if len > count32, the elements >= count32 might be read in + the prototypes and might have side effects */ + if (len != count32) + goto general_case; + /* Handle fast arrays explicitly */ + for (i = 0; i < count32; i++) { + if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, + JS_DupValue(ctx, arrp[i]), JS_PROP_C_W_E) < 0) + goto exception; + } + } else { + general_case: + for (;;) { + BOOL done; + value = JS_IteratorNext(ctx, enumobj, method, 0, NULL, &done); + if (JS_IsException(value)) + goto exception; + if (done) { + /* value is JS_UNDEFINED */ + break; + } + if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, value, JS_PROP_C_W_E) < 0) + goto exception; + } + } + /* Note: could raise an error if too many elements */ + sp[-2] = JS_NewInt32(ctx, pos); + JS_FreeValue(ctx, enumobj); + JS_FreeValue(ctx, method); + return 0; + +exception: + JS_IteratorClose(ctx, enumobj, TRUE); + JS_FreeValue(ctx, enumobj); + JS_FreeValue(ctx, method); + return -1; +} + +static __exception int JS_CopyDataProperties(JSContext *ctx, + JSValueConst target, + JSValueConst source, + JSValueConst excluded, + BOOL setprop) +{ + JSPropertyEnum *tab_atom; + JSValue val; + uint32_t i, tab_atom_count; + JSObject *p; + JSObject *pexcl = NULL; + int ret, gpn_flags; + JSPropertyDescriptor desc; + BOOL is_enumerable; + + if (JS_VALUE_GET_TAG(source) != JS_TAG_OBJECT) + return 0; + + if (JS_VALUE_GET_TAG(excluded) == JS_TAG_OBJECT) + pexcl = JS_VALUE_GET_OBJ(excluded); + + p = JS_VALUE_GET_OBJ(source); + + gpn_flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY; + if (p->is_exotic) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + /* cannot use JS_GPN_ENUM_ONLY with e.g. proxies because it + introduces a visible change */ + if (em && em->get_own_property_names) { + gpn_flags &= ~JS_GPN_ENUM_ONLY; + } + } + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, + gpn_flags)) + return -1; + + for (i = 0; i < tab_atom_count; i++) { + if (pexcl) { + ret = JS_GetOwnPropertyInternal(ctx, NULL, pexcl, tab_atom[i].atom); + if (ret) { + if (ret < 0) + goto exception; + continue; + } + } + if (!(gpn_flags & JS_GPN_ENUM_ONLY)) { + /* test if the property is enumerable */ + ret = JS_GetOwnPropertyInternal(ctx, &desc, p, tab_atom[i].atom); + if (ret < 0) + goto exception; + if (!ret) + continue; + is_enumerable = (desc.flags & JS_PROP_ENUMERABLE) != 0; + js_free_desc(ctx, &desc); + if (!is_enumerable) + continue; + } + val = JS_GetProperty(ctx, source, tab_atom[i].atom); + if (JS_IsException(val)) + goto exception; + if (setprop) + ret = JS_SetProperty(ctx, target, tab_atom[i].atom, val); + else + ret = JS_DefinePropertyValue(ctx, target, tab_atom[i].atom, val, + JS_PROP_C_W_E); + if (ret < 0) + goto exception; + } + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + return 0; + exception: + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + return -1; +} + +/* only valid inside C functions */ +static JSValueConst JS_GetActiveFunction(JSContext *ctx) +{ + return ctx->rt->current_stack_frame->cur_func; +} + +static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, + int var_idx, BOOL is_arg) +{ + JSVarRef *var_ref; + struct list_head *el; + + list_for_each(el, &sf->var_ref_list) { + var_ref = list_entry(el, JSVarRef, var_ref_link); + if (var_ref->var_idx == var_idx && var_ref->is_arg == is_arg) { + var_ref->header.ref_count++; + return var_ref; + } + } + /* create a new one */ + var_ref = js_malloc(ctx, sizeof(JSVarRef)); + if (!var_ref) + return NULL; + var_ref->header.ref_count = 1; + add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); + var_ref->is_detached = FALSE; + var_ref->is_arg = is_arg; + var_ref->var_idx = var_idx; + list_add_tail(&var_ref->var_ref_link, &sf->var_ref_list); + if (sf->js_mode & JS_MODE_ASYNC) { + /* The stack frame is detached and may be destroyed at any + time so its reference count must be increased. Calling + close_var_refs() when destroying the stack frame is not + possible because it would change the graph between the GC + objects. Another solution could be to temporarily detach + the JSVarRef of async functions during the GC. It would + have the advantage of allowing the release of unused stack + frames in a cycle. */ + var_ref->async_func = container_of(sf, JSAsyncFunctionState, frame); + var_ref->async_func->header.ref_count++; + } else { + var_ref->async_func = NULL; + } + if (is_arg) + var_ref->pvalue = &sf->arg_buf[var_idx]; + else + var_ref->pvalue = &sf->var_buf[var_idx]; + return var_ref; +} + +static JSValue js_closure2(JSContext *ctx, JSValue func_obj, + JSFunctionBytecode *b, + JSVarRef **cur_var_refs, + JSStackFrame *sf) +{ + JSObject *p; + JSVarRef **var_refs; + int i; + + p = JS_VALUE_GET_OBJ(func_obj); + p->u.func.function_bytecode = b; + p->u.func.home_object = NULL; + p->u.func.var_refs = NULL; + if (b->closure_var_count) { + var_refs = js_mallocz(ctx, sizeof(var_refs[0]) * b->closure_var_count); + if (!var_refs) + goto fail; + p->u.func.var_refs = var_refs; + for(i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + JSVarRef *var_ref; + if (cv->is_local) { + /* reuse the existing variable reference if it already exists */ + var_ref = get_var_ref(ctx, sf, cv->var_idx, cv->is_arg); + if (!var_ref) + goto fail; + } else { + var_ref = cur_var_refs[cv->var_idx]; + var_ref->header.ref_count++; + } + var_refs[i] = var_ref; + } + } + return func_obj; + fail: + /* bfunc is freed when func_obj is freed */ + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; +} + +static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque) +{ + JSValue obj, this_val; + int ret; + + this_val = JS_MKPTR(JS_TAG_OBJECT, p); + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + set_cycle_flag(ctx, obj); + set_cycle_flag(ctx, this_val); + ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_constructor, + JS_DupValue(ctx, this_val), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (ret < 0) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +static const uint16_t func_kind_to_class_id[] = { + [JS_FUNC_NORMAL] = JS_CLASS_BYTECODE_FUNCTION, + [JS_FUNC_GENERATOR] = JS_CLASS_GENERATOR_FUNCTION, + [JS_FUNC_ASYNC] = JS_CLASS_ASYNC_FUNCTION, + [JS_FUNC_ASYNC_GENERATOR] = JS_CLASS_ASYNC_GENERATOR_FUNCTION, +}; + +static JSValue js_closure(JSContext *ctx, JSValue bfunc, + JSVarRef **cur_var_refs, + JSStackFrame *sf) +{ + JSFunctionBytecode *b; + JSValue func_obj; + JSAtom name_atom; + + b = JS_VALUE_GET_PTR(bfunc); + func_obj = JS_NewObjectClass(ctx, func_kind_to_class_id[b->func_kind]); + if (JS_IsException(func_obj)) { + JS_FreeValue(ctx, bfunc); + return JS_EXCEPTION; + } + func_obj = js_closure2(ctx, func_obj, b, cur_var_refs, sf); + if (JS_IsException(func_obj)) { + /* bfunc has been freed */ + goto fail; + } + name_atom = b->func_name; + if (name_atom == JS_ATOM_NULL) + name_atom = JS_ATOM_empty_string; + js_function_set_properties(ctx, func_obj, name_atom, + b->defined_arg_count); + + if (b->func_kind & JS_FUNC_GENERATOR) { + JSValue proto; + int proto_class_id; + /* generators have a prototype field which is used as + prototype for the generator object */ + if (b->func_kind == JS_FUNC_ASYNC_GENERATOR) + proto_class_id = JS_CLASS_ASYNC_GENERATOR; + else + proto_class_id = JS_CLASS_GENERATOR; + proto = JS_NewObjectProto(ctx, ctx->class_proto[proto_class_id]); + if (JS_IsException(proto)) + goto fail; + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype, proto, + JS_PROP_WRITABLE); + } else if (b->has_prototype) { + /* add the 'prototype' property: delay instantiation to avoid + creating cycles for every javascript function. The prototype + object is created on the fly when first accessed */ + JS_SetConstructorBit(ctx, func_obj, TRUE); + JS_DefineAutoInitProperty(ctx, func_obj, JS_ATOM_prototype, + JS_AUTOINIT_ID_PROTOTYPE, NULL, + JS_PROP_WRITABLE); + } + return func_obj; + fail: + /* bfunc is freed when func_obj is freed */ + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; +} + +#define JS_DEFINE_CLASS_HAS_HERITAGE (1 << 0) + +static int js_op_define_class(JSContext *ctx, JSValue *sp, + JSAtom class_name, int class_flags, + JSVarRef **cur_var_refs, + JSStackFrame *sf, BOOL is_computed_name) +{ + JSValue bfunc, parent_class, proto = JS_UNDEFINED; + JSValue ctor = JS_UNDEFINED, parent_proto = JS_UNDEFINED; + JSFunctionBytecode *b; + + parent_class = sp[-2]; + bfunc = sp[-1]; + + if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE) { + if (JS_IsNull(parent_class)) { + parent_proto = JS_NULL; + parent_class = JS_DupValue(ctx, ctx->function_proto); + } else { + if (!JS_IsConstructor(ctx, parent_class)) { + JS_ThrowTypeError(ctx, "parent class must be constructor"); + goto fail; + } + parent_proto = JS_GetProperty(ctx, parent_class, JS_ATOM_prototype); + if (JS_IsException(parent_proto)) + goto fail; + if (!JS_IsNull(parent_proto) && !JS_IsObject(parent_proto)) { + JS_ThrowTypeError(ctx, "parent prototype must be an object or null"); + goto fail; + } + } + } else { + /* parent_class is JS_UNDEFINED in this case */ + parent_proto = JS_DupValue(ctx, ctx->class_proto[JS_CLASS_OBJECT]); + parent_class = JS_DupValue(ctx, ctx->function_proto); + } + proto = JS_NewObjectProto(ctx, parent_proto); + if (JS_IsException(proto)) + goto fail; + + b = JS_VALUE_GET_PTR(bfunc); + assert(b->func_kind == JS_FUNC_NORMAL); + ctor = JS_NewObjectProtoClass(ctx, parent_class, + JS_CLASS_BYTECODE_FUNCTION); + if (JS_IsException(ctor)) + goto fail; + ctor = js_closure2(ctx, ctor, b, cur_var_refs, sf); + bfunc = JS_UNDEFINED; + if (JS_IsException(ctor)) + goto fail; + js_method_set_home_object(ctx, ctor, proto); + JS_SetConstructorBit(ctx, ctor, TRUE); + + JS_DefinePropertyValue(ctx, ctor, JS_ATOM_length, + JS_NewInt32(ctx, b->defined_arg_count), + JS_PROP_CONFIGURABLE); + + if (is_computed_name) { + if (JS_DefineObjectNameComputed(ctx, ctor, sp[-3], + JS_PROP_CONFIGURABLE) < 0) + goto fail; + } else { + if (JS_DefineObjectName(ctx, ctor, class_name, JS_PROP_CONFIGURABLE) < 0) + goto fail; + } + + /* the constructor property must be first. It can be overriden by + computed property names */ + if (JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor, + JS_DupValue(ctx, ctor), + JS_PROP_CONFIGURABLE | + JS_PROP_WRITABLE | JS_PROP_THROW) < 0) + goto fail; + /* set the prototype property */ + if (JS_DefinePropertyValue(ctx, ctor, JS_ATOM_prototype, + JS_DupValue(ctx, proto), JS_PROP_THROW) < 0) + goto fail; + set_cycle_flag(ctx, ctor); + set_cycle_flag(ctx, proto); + + JS_FreeValue(ctx, parent_proto); + JS_FreeValue(ctx, parent_class); + + sp[-2] = ctor; + sp[-1] = proto; + return 0; + fail: + JS_FreeValue(ctx, parent_class); + JS_FreeValue(ctx, parent_proto); + JS_FreeValue(ctx, bfunc); + JS_FreeValue(ctx, proto); + JS_FreeValue(ctx, ctor); + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static void close_var_refs(JSRuntime *rt, JSStackFrame *sf) +{ + struct list_head *el, *el1; + JSVarRef *var_ref; + int var_idx; + + list_for_each_safe(el, el1, &sf->var_ref_list) { + var_ref = list_entry(el, JSVarRef, var_ref_link); + /* no need to unlink var_ref->var_ref_link as the list is never used afterwards */ + if (var_ref->async_func) + async_func_free(rt, var_ref->async_func); + var_idx = var_ref->var_idx; + if (var_ref->is_arg) + var_ref->value = JS_DupValueRT(rt, sf->arg_buf[var_idx]); + else + var_ref->value = JS_DupValueRT(rt, sf->var_buf[var_idx]); + var_ref->pvalue = &var_ref->value; + /* the reference is no longer to a local variable */ + var_ref->is_detached = TRUE; + } +} + +static void close_lexical_var(JSContext *ctx, JSStackFrame *sf, int idx, int is_arg) +{ + struct list_head *el, *el1; + JSVarRef *var_ref; + int var_idx = idx; + + list_for_each_safe(el, el1, &sf->var_ref_list) { + var_ref = list_entry(el, JSVarRef, var_ref_link); + if (var_idx == var_ref->var_idx && var_ref->is_arg == is_arg) { + list_del(&var_ref->var_ref_link); + if (var_ref->async_func) + async_func_free(ctx->rt, var_ref->async_func); + var_ref->value = JS_DupValue(ctx, sf->var_buf[var_idx]); + var_ref->pvalue = &var_ref->value; + /* the reference is no longer to a local variable */ + var_ref->is_detached = TRUE; + } + } +} + +#define JS_CALL_FLAG_COPY_ARGV (1 << 1) +#define JS_CALL_FLAG_GENERATOR (1 << 2) + +static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags) +{ + JSRuntime *rt = ctx->rt; + JSCFunctionType func; + JSObject *p; + JSStackFrame sf_s, *sf = &sf_s, *prev_sf; + JSValue ret_val; + JSValueConst *arg_buf; + int arg_count, i; + JSCFunctionEnum cproto; + + p = JS_VALUE_GET_OBJ(func_obj); + cproto = p->u.cfunc.cproto; + arg_count = p->u.cfunc.length; + + /* better to always check stack overflow */ + if (js_check_stack_overflow(rt, sizeof(arg_buf[0]) * arg_count)) + return JS_ThrowStackOverflow(ctx); + + prev_sf = rt->current_stack_frame; + sf->prev_frame = prev_sf; + rt->current_stack_frame = sf; + ctx = p->u.cfunc.realm; /* change the current realm */ + +#ifdef CONFIG_BIGNUM + /* we only propagate the bignum mode as some runtime functions + test it */ + if (prev_sf) + sf->js_mode = prev_sf->js_mode & JS_MODE_MATH; + else + sf->js_mode = 0; +#else + sf->js_mode = 0; +#endif + sf->cur_func = (JSValue)func_obj; + sf->arg_count = argc; + arg_buf = argv; + + if (unlikely(argc < arg_count)) { + /* ensure that at least argc_count arguments are readable */ + arg_buf = alloca(sizeof(arg_buf[0]) * arg_count); + for(i = 0; i < argc; i++) + arg_buf[i] = argv[i]; + for(i = argc; i < arg_count; i++) + arg_buf[i] = JS_UNDEFINED; + sf->arg_count = arg_count; + } + sf->arg_buf = (JSValue*)arg_buf; + + func = p->u.cfunc.c_function; + switch(cproto) { + case JS_CFUNC_constructor: + case JS_CFUNC_constructor_or_func: + if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { + if (cproto == JS_CFUNC_constructor) { + not_a_constructor: + ret_val = JS_ThrowTypeError(ctx, "must be called with new"); + break; + } else { + this_obj = JS_UNDEFINED; + } + } + /* here this_obj is new_target */ + /* fall thru */ + case JS_CFUNC_generic: + ret_val = func.generic(ctx, this_obj, argc, arg_buf); + break; + case JS_CFUNC_constructor_magic: + case JS_CFUNC_constructor_or_func_magic: + if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { + if (cproto == JS_CFUNC_constructor_magic) { + goto not_a_constructor; + } else { + this_obj = JS_UNDEFINED; + } + } + /* fall thru */ + case JS_CFUNC_generic_magic: + ret_val = func.generic_magic(ctx, this_obj, argc, arg_buf, + p->u.cfunc.magic); + break; + case JS_CFUNC_getter: + ret_val = func.getter(ctx, this_obj); + break; + case JS_CFUNC_setter: + ret_val = func.setter(ctx, this_obj, arg_buf[0]); + break; + case JS_CFUNC_getter_magic: + ret_val = func.getter_magic(ctx, this_obj, p->u.cfunc.magic); + break; + case JS_CFUNC_setter_magic: + ret_val = func.setter_magic(ctx, this_obj, arg_buf[0], p->u.cfunc.magic); + break; + case JS_CFUNC_f_f: + { + double d1; + + if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { + ret_val = JS_EXCEPTION; + break; + } + ret_val = JS_NewFloat64(ctx, func.f_f(d1)); + } + break; + case JS_CFUNC_f_f_f: + { + double d1, d2; + + if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { + ret_val = JS_EXCEPTION; + break; + } + if (unlikely(JS_ToFloat64(ctx, &d2, arg_buf[1]))) { + ret_val = JS_EXCEPTION; + break; + } + ret_val = JS_NewFloat64(ctx, func.f_f_f(d1, d2)); + } + break; + case JS_CFUNC_iterator_next: + { + int done; + ret_val = func.iterator_next(ctx, this_obj, argc, arg_buf, + &done, p->u.cfunc.magic); + if (!JS_IsException(ret_val) && done != 2) { + ret_val = js_create_iterator_result(ctx, ret_val, done); + } + } + break; + default: + abort(); + } + + rt->current_stack_frame = sf->prev_frame; + return ret_val; +} + +static JSValue js_call_bound_function(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags) +{ + JSObject *p; + JSBoundFunction *bf; + JSValueConst *arg_buf, new_target; + int arg_count, i; + + p = JS_VALUE_GET_OBJ(func_obj); + bf = p->u.bound_function; + arg_count = bf->argc + argc; + if (js_check_stack_overflow(ctx->rt, sizeof(JSValue) * arg_count)) + return JS_ThrowStackOverflow(ctx); + arg_buf = alloca(sizeof(JSValue) * arg_count); + for(i = 0; i < bf->argc; i++) { + arg_buf[i] = bf->argv[i]; + } + for(i = 0; i < argc; i++) { + arg_buf[bf->argc + i] = argv[i]; + } + if (flags & JS_CALL_FLAG_CONSTRUCTOR) { + new_target = this_obj; + if (js_same_value(ctx, func_obj, new_target)) + new_target = bf->func_obj; + return JS_CallConstructor2(ctx, bf->func_obj, new_target, + arg_count, arg_buf); + } else { + return JS_Call(ctx, bf->func_obj, bf->this_val, + arg_count, arg_buf); + } +} + +/* argument of OP_special_object */ +typedef enum { + OP_SPECIAL_OBJECT_ARGUMENTS, + OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS, + OP_SPECIAL_OBJECT_THIS_FUNC, + OP_SPECIAL_OBJECT_NEW_TARGET, + OP_SPECIAL_OBJECT_HOME_OBJECT, + OP_SPECIAL_OBJECT_VAR_OBJECT, + OP_SPECIAL_OBJECT_IMPORT_META, +} OPSpecialObjectEnum; + +#define FUNC_RET_AWAIT 0 +#define FUNC_RET_YIELD 1 +#define FUNC_RET_YIELD_STAR 2 +#define FUNC_RET_INITIAL_YIELD 3 + +/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ +static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, + JSValueConst this_obj, JSValueConst new_target, + int argc, JSValue *argv, int flags) +{ + JSRuntime *rt = caller_ctx->rt; + JSContext *ctx; + JSObject *p; + JSFunctionBytecode *b; + JSStackFrame sf_s, *sf = &sf_s; + const uint8_t *pc; + int opcode, arg_allocated_size, i; + JSValue *local_buf, *stack_buf, *var_buf, *arg_buf, *sp, ret_val, *pval; + JSVarRef **var_refs; + size_t alloca_size; + +#if !DIRECT_DISPATCH +#define SWITCH(pc) switch (opcode = *pc++) +#define CASE(op) case op +#define DEFAULT default +#define BREAK break +#else + static const void * const dispatch_table[256] = { +#define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id, +#if SHORT_OPCODES +#define def(id, size, n_pop, n_push, f) +#else +#define def(id, size, n_pop, n_push, f) && case_default, +#endif +#include "quickjs-opcode.h" + [ OP_COUNT ... 255 ] = &&case_default + }; +#define SWITCH(pc) goto *dispatch_table[opcode = *pc++]; +#define CASE(op) case_ ## op +#define DEFAULT case_default +#define BREAK SWITCH(pc) +#endif + + if (js_poll_interrupts(caller_ctx)) + return JS_EXCEPTION; + if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) { + if (flags & JS_CALL_FLAG_GENERATOR) { + JSAsyncFunctionState *s = JS_VALUE_GET_PTR(func_obj); + /* func_obj get contains a pointer to JSFuncAsyncState */ + /* the stack frame is already allocated */ + sf = &s->frame; + p = JS_VALUE_GET_OBJ(sf->cur_func); + b = p->u.func.function_bytecode; + ctx = b->realm; + var_refs = p->u.func.var_refs; + local_buf = arg_buf = sf->arg_buf; + var_buf = sf->var_buf; + stack_buf = sf->var_buf + b->var_count; + sp = sf->cur_sp; + sf->cur_sp = NULL; /* cur_sp is NULL if the function is running */ + pc = sf->cur_pc; + sf->prev_frame = rt->current_stack_frame; + rt->current_stack_frame = sf; + if (s->throw_flag) + goto exception; + else + goto restart; + } else { + goto not_a_function; + } + } + p = JS_VALUE_GET_OBJ(func_obj); + if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { + JSClassCall *call_func; + call_func = rt->class_array[p->class_id].call; + if (!call_func) { + not_a_function: + return JS_ThrowTypeError(caller_ctx, "not a function"); + } + return call_func(caller_ctx, func_obj, this_obj, argc, + (JSValueConst *)argv, flags); + } + b = p->u.func.function_bytecode; + + if (unlikely(argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV))) { + arg_allocated_size = b->arg_count; + } else { + arg_allocated_size = 0; + } + + alloca_size = sizeof(JSValue) * (arg_allocated_size + b->var_count + + b->stack_size); + if (js_check_stack_overflow(rt, alloca_size)) + return JS_ThrowStackOverflow(caller_ctx); + + sf->js_mode = b->js_mode; + arg_buf = argv; + sf->arg_count = argc; + sf->cur_func = (JSValue)func_obj; + init_list_head(&sf->var_ref_list); + var_refs = p->u.func.var_refs; + + local_buf = alloca(alloca_size); + if (unlikely(arg_allocated_size)) { + int n = min_int(argc, b->arg_count); + arg_buf = local_buf; + for(i = 0; i < n; i++) + arg_buf[i] = JS_DupValue(caller_ctx, argv[i]); + for(; i < b->arg_count; i++) + arg_buf[i] = JS_UNDEFINED; + sf->arg_count = b->arg_count; + } + var_buf = local_buf + arg_allocated_size; + sf->var_buf = var_buf; + sf->arg_buf = arg_buf; + + for(i = 0; i < b->var_count; i++) + var_buf[i] = JS_UNDEFINED; + + stack_buf = var_buf + b->var_count; + sp = stack_buf; + pc = b->byte_code_buf; + sf->prev_frame = rt->current_stack_frame; + rt->current_stack_frame = sf; + ctx = b->realm; /* set the current realm */ + + restart: + for(;;) { + int call_argc; + JSValue *call_argv; + + SWITCH(pc) { + CASE(OP_push_i32): + *sp++ = JS_NewInt32(ctx, get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_push_const): + *sp++ = JS_DupValue(ctx, b->cpool[get_u32(pc)]); + pc += 4; + BREAK; +#if SHORT_OPCODES + CASE(OP_push_minus1): + CASE(OP_push_0): + CASE(OP_push_1): + CASE(OP_push_2): + CASE(OP_push_3): + CASE(OP_push_4): + CASE(OP_push_5): + CASE(OP_push_6): + CASE(OP_push_7): + *sp++ = JS_NewInt32(ctx, opcode - OP_push_0); + BREAK; + CASE(OP_push_i8): + *sp++ = JS_NewInt32(ctx, get_i8(pc)); + pc += 1; + BREAK; + CASE(OP_push_i16): + *sp++ = JS_NewInt32(ctx, get_i16(pc)); + pc += 2; + BREAK; + CASE(OP_push_const8): + *sp++ = JS_DupValue(ctx, b->cpool[*pc++]); + BREAK; + CASE(OP_fclosure8): + *sp++ = js_closure(ctx, JS_DupValue(ctx, b->cpool[*pc++]), var_refs, sf); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + BREAK; + CASE(OP_push_empty_string): + *sp++ = JS_AtomToString(ctx, JS_ATOM_empty_string); + BREAK; + CASE(OP_get_length): + { + JSValue val; + + val = JS_GetProperty(ctx, sp[-1], JS_ATOM_length); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = val; + } + BREAK; +#endif + CASE(OP_push_atom_value): + *sp++ = JS_AtomToValue(ctx, get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_undefined): + *sp++ = JS_UNDEFINED; + BREAK; + CASE(OP_null): + *sp++ = JS_NULL; + BREAK; + CASE(OP_push_this): + /* OP_push_this is only called at the start of a function */ + { + JSValue val; + if (!(b->js_mode & JS_MODE_STRICT)) { + uint32_t tag = JS_VALUE_GET_TAG(this_obj); + if (likely(tag == JS_TAG_OBJECT)) + goto normal_this; + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) { + val = JS_DupValue(ctx, ctx->global_obj); + } else { + val = JS_ToObject(ctx, this_obj); + if (JS_IsException(val)) + goto exception; + } + } else { + normal_this: + val = JS_DupValue(ctx, this_obj); + } + *sp++ = val; + } + BREAK; + CASE(OP_push_false): + *sp++ = JS_FALSE; + BREAK; + CASE(OP_push_true): + *sp++ = JS_TRUE; + BREAK; + CASE(OP_object): + *sp++ = JS_NewObject(ctx); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + BREAK; + CASE(OP_special_object): + { + int arg = *pc++; + switch(arg) { + case OP_SPECIAL_OBJECT_ARGUMENTS: + *sp++ = js_build_arguments(ctx, argc, (JSValueConst *)argv); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS: + *sp++ = js_build_mapped_arguments(ctx, argc, (JSValueConst *)argv, + sf, min_int(argc, b->arg_count)); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_THIS_FUNC: + *sp++ = JS_DupValue(ctx, sf->cur_func); + break; + case OP_SPECIAL_OBJECT_NEW_TARGET: + *sp++ = JS_DupValue(ctx, new_target); + break; + case OP_SPECIAL_OBJECT_HOME_OBJECT: + { + JSObject *p1; + p1 = p->u.func.home_object; + if (unlikely(!p1)) + *sp++ = JS_UNDEFINED; + else + *sp++ = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); + } + break; + case OP_SPECIAL_OBJECT_VAR_OBJECT: + *sp++ = JS_NewObjectProto(ctx, JS_NULL); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_IMPORT_META: + *sp++ = js_import_meta(ctx); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + default: + abort(); + } + } + BREAK; + CASE(OP_rest): + { + int first = get_u16(pc); + pc += 2; + *sp++ = js_build_rest(ctx, first, argc, (JSValueConst *)argv); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + } + BREAK; + + CASE(OP_drop): + JS_FreeValue(ctx, sp[-1]); + sp--; + BREAK; + CASE(OP_nip): + JS_FreeValue(ctx, sp[-2]); + sp[-2] = sp[-1]; + sp--; + BREAK; + CASE(OP_nip1): /* a b c -> b c */ + JS_FreeValue(ctx, sp[-3]); + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp--; + BREAK; + CASE(OP_dup): + sp[0] = JS_DupValue(ctx, sp[-1]); + sp++; + BREAK; + CASE(OP_dup2): /* a b -> a b a b */ + sp[0] = JS_DupValue(ctx, sp[-2]); + sp[1] = JS_DupValue(ctx, sp[-1]); + sp += 2; + BREAK; + CASE(OP_dup3): /* a b c -> a b c a b c */ + sp[0] = JS_DupValue(ctx, sp[-3]); + sp[1] = JS_DupValue(ctx, sp[-2]); + sp[2] = JS_DupValue(ctx, sp[-1]); + sp += 3; + BREAK; + CASE(OP_dup1): /* a b -> a a b */ + sp[0] = sp[-1]; + sp[-1] = JS_DupValue(ctx, sp[-2]); + sp++; + BREAK; + CASE(OP_insert2): /* obj a -> a obj a (dup_x1) */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = JS_DupValue(ctx, sp[0]); + sp++; + BREAK; + CASE(OP_insert3): /* obj prop a -> a obj prop a (dup_x2) */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = JS_DupValue(ctx, sp[0]); + sp++; + BREAK; + CASE(OP_insert4): /* this obj prop a -> a this obj prop a */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = JS_DupValue(ctx, sp[0]); + sp++; + BREAK; + CASE(OP_perm3): /* obj a b -> a obj b (213) */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = tmp; + } + BREAK; + CASE(OP_rot3l): /* x a b -> a b x (231) */ + { + JSValue tmp; + tmp = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot4l): /* x a b c -> a b c x */ + { + JSValue tmp; + tmp = sp[-4]; + sp[-4] = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot5l): /* x a b c d -> a b c d x */ + { + JSValue tmp; + tmp = sp[-5]; + sp[-5] = sp[-4]; + sp[-4] = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot3r): /* a b x -> x a b (312) */ + { + JSValue tmp; + tmp = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = tmp; + } + BREAK; + CASE(OP_perm4): /* obj prop a b -> a obj prop b */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = tmp; + } + BREAK; + CASE(OP_perm5): /* this obj prop a b -> a this obj prop b */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = sp[-5]; + sp[-5] = tmp; + } + BREAK; + CASE(OP_swap): /* a b -> b a */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_swap2): /* a b c d -> c d a b */ + { + JSValue tmp1, tmp2; + tmp1 = sp[-4]; + tmp2 = sp[-3]; + sp[-4] = sp[-2]; + sp[-3] = sp[-1]; + sp[-2] = tmp1; + sp[-1] = tmp2; + } + BREAK; + + CASE(OP_fclosure): + { + JSValue bfunc = JS_DupValue(ctx, b->cpool[get_u32(pc)]); + pc += 4; + *sp++ = js_closure(ctx, bfunc, var_refs, sf); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + } + BREAK; +#if SHORT_OPCODES + CASE(OP_call0): + CASE(OP_call1): + CASE(OP_call2): + CASE(OP_call3): + call_argc = opcode - OP_call0; + goto has_call_argc; +#endif + CASE(OP_call): + CASE(OP_tail_call): + { + call_argc = get_u16(pc); + pc += 2; + goto has_call_argc; + has_call_argc: + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, + JS_UNDEFINED, call_argc, call_argv, 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + if (opcode == OP_tail_call) + goto done; + for(i = -1; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 1; + *sp++ = ret_val; + } + BREAK; + CASE(OP_call_constructor): + { + call_argc = get_u16(pc); + pc += 2; + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallConstructorInternal(ctx, call_argv[-2], + call_argv[-1], + call_argc, call_argv, 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + for(i = -2; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 2; + *sp++ = ret_val; + } + BREAK; + CASE(OP_call_method): + CASE(OP_tail_call_method): + { + call_argc = get_u16(pc); + pc += 2; + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallInternal(ctx, call_argv[-1], call_argv[-2], + JS_UNDEFINED, call_argc, call_argv, 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + if (opcode == OP_tail_call_method) + goto done; + for(i = -2; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 2; + *sp++ = ret_val; + } + BREAK; + CASE(OP_array_from): + { + int i, ret; + + call_argc = get_u16(pc); + pc += 2; + ret_val = JS_NewArray(ctx); + if (unlikely(JS_IsException(ret_val))) + goto exception; + call_argv = sp - call_argc; + for(i = 0; i < call_argc; i++) { + ret = JS_DefinePropertyValue(ctx, ret_val, __JS_AtomFromUInt32(i), call_argv[i], + JS_PROP_C_W_E | JS_PROP_THROW); + call_argv[i] = JS_UNDEFINED; + if (ret < 0) { + JS_FreeValue(ctx, ret_val); + goto exception; + } + } + sp -= call_argc; + *sp++ = ret_val; + } + BREAK; + + CASE(OP_apply): + { + int magic; + magic = get_u16(pc); + pc += 2; + + ret_val = js_function_apply(ctx, sp[-3], 2, (JSValueConst *)&sp[-2], magic); + if (unlikely(JS_IsException(ret_val))) + goto exception; + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 3; + *sp++ = ret_val; + } + BREAK; + CASE(OP_return): + ret_val = *--sp; + goto done; + CASE(OP_return_undef): + ret_val = JS_UNDEFINED; + goto done; + + CASE(OP_check_ctor_return): + /* return TRUE if 'this' should be returned */ + if (!JS_IsObject(sp[-1])) { + if (!JS_IsUndefined(sp[-1])) { + JS_ThrowTypeError(caller_ctx, "derived class constructor must return an object or undefined"); + goto exception; + } + sp[0] = JS_TRUE; + } else { + sp[0] = JS_FALSE; + } + sp++; + BREAK; + CASE(OP_check_ctor): + if (JS_IsUndefined(new_target)) { + JS_ThrowTypeError(ctx, "class constructors must be invoked with 'new'"); + goto exception; + } + BREAK; + CASE(OP_check_brand): + { + int ret = JS_CheckBrand(ctx, sp[-2], sp[-1]); + if (ret < 0) + goto exception; + if (!ret) { + JS_ThrowTypeError(ctx, "invalid brand on object"); + goto exception; + } + } + BREAK; + CASE(OP_add_brand): + if (JS_AddBrand(ctx, sp[-2], sp[-1]) < 0) + goto exception; + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + BREAK; + + CASE(OP_throw): + JS_Throw(ctx, *--sp); + goto exception; + + CASE(OP_throw_error): +#define JS_THROW_VAR_RO 0 +#define JS_THROW_VAR_REDECL 1 +#define JS_THROW_VAR_UNINITIALIZED 2 +#define JS_THROW_ERROR_DELETE_SUPER 3 +#define JS_THROW_ERROR_ITERATOR_THROW 4 + { + JSAtom atom; + int type; + atom = get_u32(pc); + type = pc[4]; + pc += 5; + if (type == JS_THROW_VAR_RO) + JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, atom); + else + if (type == JS_THROW_VAR_REDECL) + JS_ThrowSyntaxErrorVarRedeclaration(ctx, atom); + else + if (type == JS_THROW_VAR_UNINITIALIZED) + JS_ThrowReferenceErrorUninitialized(ctx, atom); + else + if (type == JS_THROW_ERROR_DELETE_SUPER) + JS_ThrowReferenceError(ctx, "unsupported reference to 'super'"); + else + if (type == JS_THROW_ERROR_ITERATOR_THROW) + JS_ThrowTypeError(ctx, "iterator does not have a throw method"); + else + JS_ThrowInternalError(ctx, "invalid throw var type %d", type); + } + goto exception; + + CASE(OP_eval): + { + JSValueConst obj; + int scope_idx; + call_argc = get_u16(pc); + scope_idx = get_u16(pc + 2) - 1; + pc += 4; + call_argv = sp - call_argc; + sf->cur_pc = pc; + if (js_same_value(ctx, call_argv[-1], ctx->eval_obj)) { + if (call_argc >= 1) + obj = call_argv[0]; + else + obj = JS_UNDEFINED; + ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, + JS_EVAL_TYPE_DIRECT, scope_idx); + } else { + ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, + JS_UNDEFINED, call_argc, call_argv, 0); + } + if (unlikely(JS_IsException(ret_val))) + goto exception; + for(i = -1; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 1; + *sp++ = ret_val; + } + BREAK; + /* could merge with OP_apply */ + CASE(OP_apply_eval): + { + int scope_idx; + uint32_t len; + JSValue *tab; + JSValueConst obj; + + scope_idx = get_u16(pc) - 1; + pc += 2; + tab = build_arg_list(ctx, &len, sp[-1]); + if (!tab) + goto exception; + if (js_same_value(ctx, sp[-2], ctx->eval_obj)) { + if (len >= 1) + obj = tab[0]; + else + obj = JS_UNDEFINED; + ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, + JS_EVAL_TYPE_DIRECT, scope_idx); + } else { + ret_val = JS_Call(ctx, sp[-2], JS_UNDEFINED, len, + (JSValueConst *)tab); + } + free_arg_list(ctx, tab, len); + if (unlikely(JS_IsException(ret_val))) + goto exception; + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + *sp++ = ret_val; + } + BREAK; + + CASE(OP_regexp): + { + sp[-2] = js_regexp_constructor_internal(ctx, JS_UNDEFINED, + sp[-2], sp[-1]); + sp--; + } + BREAK; + + CASE(OP_get_super): + { + JSValue proto; + proto = JS_GetPrototype(ctx, sp[-1]); + if (JS_IsException(proto)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = proto; + } + BREAK; + + CASE(OP_import): + { + JSValue val; + val = js_dynamic_import(ctx, sp[-1]); + if (JS_IsException(val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = val; + } + BREAK; + + CASE(OP_check_var): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_CheckGlobalVar(ctx, atom); + if (ret < 0) + goto exception; + *sp++ = JS_NewBool(ctx, ret); + } + BREAK; + + CASE(OP_get_var_undef): + CASE(OP_get_var): + { + JSValue val; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + val = JS_GetGlobalVar(ctx, atom, opcode - OP_get_var_undef); + if (unlikely(JS_IsException(val))) + goto exception; + *sp++ = val; + } + BREAK; + + CASE(OP_put_var): + CASE(OP_put_var_init): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_SetGlobalVar(ctx, atom, sp[-1], opcode - OP_put_var); + sp--; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_put_var_strict): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + /* sp[-2] is JS_TRUE or JS_FALSE */ + if (unlikely(!JS_VALUE_GET_INT(sp[-2]))) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + goto exception; + } + ret = JS_SetGlobalVar(ctx, atom, sp[-1], 2); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_check_define_var): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + if (JS_CheckDefineGlobalVar(ctx, atom, flags)) + goto exception; + } + BREAK; + CASE(OP_define_var): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + if (JS_DefineGlobalVar(ctx, atom, flags)) + goto exception; + } + BREAK; + CASE(OP_define_func): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + if (JS_DefineGlobalFunction(ctx, atom, sp[-1], flags)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp--; + } + BREAK; + + CASE(OP_get_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + sp[0] = JS_DupValue(ctx, var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], JS_DupValue(ctx, sp[-1])); + } + BREAK; + CASE(OP_get_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + sp[0] = JS_DupValue(ctx, arg_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &arg_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &arg_buf[idx], JS_DupValue(ctx, sp[-1])); + } + BREAK; + +#if SHORT_OPCODES + CASE(OP_get_loc8): *sp++ = JS_DupValue(ctx, var_buf[*pc++]); BREAK; + CASE(OP_put_loc8): set_value(ctx, &var_buf[*pc++], *--sp); BREAK; + CASE(OP_set_loc8): set_value(ctx, &var_buf[*pc++], JS_DupValue(ctx, sp[-1])); BREAK; + + CASE(OP_get_loc0): *sp++ = JS_DupValue(ctx, var_buf[0]); BREAK; + CASE(OP_get_loc1): *sp++ = JS_DupValue(ctx, var_buf[1]); BREAK; + CASE(OP_get_loc2): *sp++ = JS_DupValue(ctx, var_buf[2]); BREAK; + CASE(OP_get_loc3): *sp++ = JS_DupValue(ctx, var_buf[3]); BREAK; + CASE(OP_put_loc0): set_value(ctx, &var_buf[0], *--sp); BREAK; + CASE(OP_put_loc1): set_value(ctx, &var_buf[1], *--sp); BREAK; + CASE(OP_put_loc2): set_value(ctx, &var_buf[2], *--sp); BREAK; + CASE(OP_put_loc3): set_value(ctx, &var_buf[3], *--sp); BREAK; + CASE(OP_set_loc0): set_value(ctx, &var_buf[0], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_loc1): set_value(ctx, &var_buf[1], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_loc2): set_value(ctx, &var_buf[2], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_loc3): set_value(ctx, &var_buf[3], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_get_arg0): *sp++ = JS_DupValue(ctx, arg_buf[0]); BREAK; + CASE(OP_get_arg1): *sp++ = JS_DupValue(ctx, arg_buf[1]); BREAK; + CASE(OP_get_arg2): *sp++ = JS_DupValue(ctx, arg_buf[2]); BREAK; + CASE(OP_get_arg3): *sp++ = JS_DupValue(ctx, arg_buf[3]); BREAK; + CASE(OP_put_arg0): set_value(ctx, &arg_buf[0], *--sp); BREAK; + CASE(OP_put_arg1): set_value(ctx, &arg_buf[1], *--sp); BREAK; + CASE(OP_put_arg2): set_value(ctx, &arg_buf[2], *--sp); BREAK; + CASE(OP_put_arg3): set_value(ctx, &arg_buf[3], *--sp); BREAK; + CASE(OP_set_arg0): set_value(ctx, &arg_buf[0], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_arg1): set_value(ctx, &arg_buf[1], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_arg2): set_value(ctx, &arg_buf[2], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_arg3): set_value(ctx, &arg_buf[3], JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_get_var_ref0): *sp++ = JS_DupValue(ctx, *var_refs[0]->pvalue); BREAK; + CASE(OP_get_var_ref1): *sp++ = JS_DupValue(ctx, *var_refs[1]->pvalue); BREAK; + CASE(OP_get_var_ref2): *sp++ = JS_DupValue(ctx, *var_refs[2]->pvalue); BREAK; + CASE(OP_get_var_ref3): *sp++ = JS_DupValue(ctx, *var_refs[3]->pvalue); BREAK; + CASE(OP_put_var_ref0): set_value(ctx, var_refs[0]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref1): set_value(ctx, var_refs[1]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref2): set_value(ctx, var_refs[2]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref3): set_value(ctx, var_refs[3]->pvalue, *--sp); BREAK; + CASE(OP_set_var_ref0): set_value(ctx, var_refs[0]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_var_ref1): set_value(ctx, var_refs[1]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_var_ref2): set_value(ctx, var_refs[2]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; + CASE(OP_set_var_ref3): set_value(ctx, var_refs[3]->pvalue, JS_DupValue(ctx, sp[-1])); BREAK; +#endif + + CASE(OP_get_var_ref): + { + int idx; + JSValue val; + idx = get_u16(pc); + pc += 2; + val = *var_refs[idx]->pvalue; + sp[0] = JS_DupValue(ctx, val); + sp++; + } + BREAK; + CASE(OP_put_var_ref): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_var_ref): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, var_refs[idx]->pvalue, JS_DupValue(ctx, sp[-1])); + } + BREAK; + CASE(OP_get_var_ref_check): + { + int idx; + JSValue val; + idx = get_u16(pc); + pc += 2; + val = *var_refs[idx]->pvalue; + if (unlikely(JS_IsUninitialized(val))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); + goto exception; + } + sp[0] = JS_DupValue(ctx, val); + sp++; + } + BREAK; + CASE(OP_put_var_ref_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(*var_refs[idx]->pvalue))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); + goto exception; + } + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_put_var_ref_check_init): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(!JS_IsUninitialized(*var_refs[idx]->pvalue))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); + goto exception; + } + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_loc_uninitialized): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], JS_UNINITIALIZED); + } + BREAK; + CASE(OP_get_loc_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, FALSE); + goto exception; + } + sp[0] = JS_DupValue(ctx, var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_get_loc_checkthis): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, FALSE); + goto exception; + } + sp[0] = JS_DupValue(ctx, var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_loc_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, FALSE); + goto exception; + } + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_put_loc_check_init): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(!JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceError(ctx, "'this' can be initialized only once"); + goto exception; + } + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_close_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + close_lexical_var(ctx, sf, idx, FALSE); + } + BREAK; + + CASE(OP_make_loc_ref): + CASE(OP_make_arg_ref): + CASE(OP_make_var_ref_ref): + { + JSVarRef *var_ref; + JSProperty *pr; + JSAtom atom; + int idx; + atom = get_u32(pc); + idx = get_u16(pc + 4); + pc += 6; + *sp++ = JS_NewObjectProto(ctx, JS_NULL); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + if (opcode == OP_make_var_ref_ref) { + var_ref = var_refs[idx]; + var_ref->header.ref_count++; + } else { + var_ref = get_var_ref(ctx, sf, idx, opcode == OP_make_arg_ref); + if (!var_ref) + goto exception; + } + pr = add_property(ctx, JS_VALUE_GET_OBJ(sp[-1]), atom, + JS_PROP_WRITABLE | JS_PROP_VARREF); + if (!pr) { + free_var_ref(rt, var_ref); + goto exception; + } + pr->u.var_ref = var_ref; + *sp++ = JS_AtomToValue(ctx, atom); + } + BREAK; + CASE(OP_make_var_ref): + { + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + if (JS_GetGlobalVarRef(ctx, atom, sp)) + goto exception; + sp += 2; + } + BREAK; + + CASE(OP_goto): + pc += (int32_t)get_u32(pc); + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; +#if SHORT_OPCODES + CASE(OP_goto16): + pc += (int16_t)get_u16(pc); + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; + CASE(OP_goto8): + pc += (int8_t)pc[0]; + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; +#endif + CASE(OP_if_true): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 4; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (res) { + pc += (int32_t)get_u32(pc - 4) - 4; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_false): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 4; + /* quick and dirty test for JS_TAG_INT, JS_TAG_BOOL, JS_TAG_NULL and JS_TAG_UNDEFINED */ + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (!res) { + pc += (int32_t)get_u32(pc - 4) - 4; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; +#if SHORT_OPCODES + CASE(OP_if_true8): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 1; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (res) { + pc += (int8_t)pc[-1] - 1; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_false8): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 1; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (!res) { + pc += (int8_t)pc[-1] - 1; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; +#endif + CASE(OP_catch): + { + int32_t diff; + diff = get_u32(pc); + sp[0] = JS_NewCatchOffset(ctx, pc + diff - b->byte_code_buf); + sp++; + pc += 4; + } + BREAK; + CASE(OP_gosub): + { + int32_t diff; + diff = get_u32(pc); + /* XXX: should have a different tag to avoid security flaw */ + sp[0] = JS_NewInt32(ctx, pc + 4 - b->byte_code_buf); + sp++; + pc += diff; + } + BREAK; + CASE(OP_ret): + { + JSValue op1; + uint32_t pos; + op1 = sp[-1]; + if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_INT)) + goto ret_fail; + pos = JS_VALUE_GET_INT(op1); + if (unlikely(pos >= b->byte_code_len)) { + ret_fail: + JS_ThrowInternalError(ctx, "invalid ret value"); + goto exception; + } + sp--; + pc = b->byte_code_buf + pos; + } + BREAK; + + CASE(OP_for_in_start): + if (js_for_in_start(ctx, sp)) + goto exception; + BREAK; + CASE(OP_for_in_next): + if (js_for_in_next(ctx, sp)) + goto exception; + sp += 2; + BREAK; + CASE(OP_for_of_start): + if (js_for_of_start(ctx, sp, FALSE)) + goto exception; + sp += 1; + *sp++ = JS_NewCatchOffset(ctx, 0); + BREAK; + CASE(OP_for_of_next): + { + int offset = -3 - pc[0]; + pc += 1; + if (js_for_of_next(ctx, sp, offset)) + goto exception; + sp += 2; + } + BREAK; + CASE(OP_for_await_of_start): + if (js_for_of_start(ctx, sp, TRUE)) + goto exception; + sp += 1; + *sp++ = JS_NewCatchOffset(ctx, 0); + BREAK; + CASE(OP_iterator_get_value_done): + if (js_iterator_get_value_done(ctx, sp)) + goto exception; + sp += 1; + BREAK; + CASE(OP_iterator_check_object): + if (unlikely(!JS_IsObject(sp[-1]))) { + JS_ThrowTypeError(ctx, "iterator must return an object"); + goto exception; + } + BREAK; + + CASE(OP_iterator_close): + /* iter_obj next catch_offset -> */ + sp--; /* drop the catch offset to avoid getting caught by exception */ + JS_FreeValue(ctx, sp[-1]); /* drop the next method */ + sp--; + if (!JS_IsUndefined(sp[-1])) { + if (JS_IteratorClose(ctx, sp[-1], FALSE)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + } + sp--; + BREAK; + CASE(OP_nip_catch): + { + JSValue ret_val; + /* catch_offset ... ret_val -> ret_eval */ + ret_val = *--sp; + while (sp > stack_buf && + JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) { + JS_FreeValue(ctx, *--sp); + } + if (unlikely(sp == stack_buf)) { + JS_ThrowInternalError(ctx, "nip_catch"); + JS_FreeValue(ctx, ret_val); + goto exception; + } + sp[-1] = ret_val; + } + BREAK; + + CASE(OP_iterator_next): + /* stack: iter_obj next catch_offset val */ + { + JSValue ret; + ret = JS_Call(ctx, sp[-3], sp[-4], + 1, (JSValueConst *)(sp - 1)); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret; + } + BREAK; + + CASE(OP_iterator_call): + /* stack: iter_obj next catch_offset val */ + { + JSValue method, ret; + BOOL ret_flag; + int flags; + flags = *pc++; + method = JS_GetProperty(ctx, sp[-4], (flags & 1) ? + JS_ATOM_throw : JS_ATOM_return); + if (JS_IsException(method)) + goto exception; + if (JS_IsUndefined(method) || JS_IsNull(method)) { + ret_flag = TRUE; + } else { + if (flags & 2) { + /* no argument */ + ret = JS_CallFree(ctx, method, sp[-4], + 0, NULL); + } else { + ret = JS_CallFree(ctx, method, sp[-4], + 1, (JSValueConst *)(sp - 1)); + } + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret; + ret_flag = FALSE; + } + sp[0] = JS_NewBool(ctx, ret_flag); + sp += 1; + } + BREAK; + + CASE(OP_lnot): + { + int res; + JSValue op1; + + op1 = sp[-1]; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1) != 0; + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp[-1] = JS_NewBool(ctx, !res); + } + BREAK; + + CASE(OP_get_field): + { + JSValue val; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + val = JS_GetProperty(ctx, sp[-1], atom); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = val; + } + BREAK; + + CASE(OP_get_field2): + { + JSValue val; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + val = JS_GetProperty(ctx, sp[-1], atom); + if (unlikely(JS_IsException(val))) + goto exception; + *sp++ = val; + } + BREAK; + + CASE(OP_put_field): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_SetPropertyInternal(ctx, sp[-2], atom, sp[-1], sp[-2], + JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp[-2]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_private_symbol): + { + JSAtom atom; + JSValue val; + + atom = get_u32(pc); + pc += 4; + val = JS_NewSymbolFromAtom(ctx, atom, JS_ATOM_TYPE_PRIVATE); + if (JS_IsException(val)) + goto exception; + *sp++ = val; + } + BREAK; + + CASE(OP_get_private_field): + { + JSValue val; + + val = JS_GetPrivateField(ctx, sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp[-2] = val; + sp--; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_put_private_field): + { + int ret; + ret = JS_SetPrivateField(ctx, sp[-3], sp[-1], sp[-2]); + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-1]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_private_field): + { + int ret; + ret = JS_DefinePrivateField(ctx, sp[-3], sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_field): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_DefinePropertyValue(ctx, sp[-2], atom, sp[-1], + JS_PROP_C_W_E | JS_PROP_THROW); + sp--; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_set_name): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_DefineObjectName(ctx, sp[-1], atom, JS_PROP_CONFIGURABLE); + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + CASE(OP_set_name_computed): + { + int ret; + ret = JS_DefineObjectNameComputed(ctx, sp[-1], sp[-2], JS_PROP_CONFIGURABLE); + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + CASE(OP_set_proto): + { + JSValue proto; + proto = sp[-1]; + if (JS_IsObject(proto) || JS_IsNull(proto)) { + if (JS_SetPrototypeInternal(ctx, sp[-2], proto, TRUE) < 0) + goto exception; + } + JS_FreeValue(ctx, proto); + sp--; + } + BREAK; + CASE(OP_set_home_object): + js_method_set_home_object(ctx, sp[-1], sp[-2]); + BREAK; + CASE(OP_define_method): + CASE(OP_define_method_computed): + { + JSValue getter, setter, value; + JSValueConst obj; + JSAtom atom; + int flags, ret, op_flags; + BOOL is_computed; +#define OP_DEFINE_METHOD_METHOD 0 +#define OP_DEFINE_METHOD_GETTER 1 +#define OP_DEFINE_METHOD_SETTER 2 +#define OP_DEFINE_METHOD_ENUMERABLE 4 + + is_computed = (opcode == OP_define_method_computed); + if (is_computed) { + atom = JS_ValueToAtom(ctx, sp[-2]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + opcode += OP_define_method - OP_define_method_computed; + } else { + atom = get_u32(pc); + pc += 4; + } + op_flags = *pc++; + + obj = sp[-2 - is_computed]; + flags = JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE | + JS_PROP_HAS_ENUMERABLE | JS_PROP_THROW; + if (op_flags & OP_DEFINE_METHOD_ENUMERABLE) + flags |= JS_PROP_ENUMERABLE; + op_flags &= 3; + value = JS_UNDEFINED; + getter = JS_UNDEFINED; + setter = JS_UNDEFINED; + if (op_flags == OP_DEFINE_METHOD_METHOD) { + value = sp[-1]; + flags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE; + } else if (op_flags == OP_DEFINE_METHOD_GETTER) { + getter = sp[-1]; + flags |= JS_PROP_HAS_GET; + } else { + setter = sp[-1]; + flags |= JS_PROP_HAS_SET; + } + ret = js_method_set_properties(ctx, sp[-1], atom, flags, obj); + if (ret >= 0) { + ret = JS_DefineProperty(ctx, obj, atom, value, + getter, setter, flags); + } + JS_FreeValue(ctx, sp[-1]); + if (is_computed) { + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, sp[-2]); + } + sp -= 1 + is_computed; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_class): + CASE(OP_define_class_computed): + { + int class_flags; + JSAtom atom; + + atom = get_u32(pc); + class_flags = pc[4]; + pc += 5; + if (js_op_define_class(ctx, sp, atom, class_flags, + var_refs, sf, + (opcode == OP_define_class_computed)) < 0) + goto exception; + } + BREAK; + + CASE(OP_get_array_el): + { + JSValue val; + + val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp[-2] = val; + sp--; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_get_array_el2): + { + JSValue val; + + val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); + sp[-1] = val; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_get_ref_value): + { + JSValue val; + if (unlikely(JS_IsUndefined(sp[-2]))) { + JSAtom atom = JS_ValueToAtom(ctx, sp[-1]); + if (atom != JS_ATOM_NULL) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + } + goto exception; + } + val = JS_GetPropertyValue(ctx, sp[-2], + JS_DupValue(ctx, sp[-1])); + if (unlikely(JS_IsException(val))) + goto exception; + sp[0] = val; + sp++; + } + BREAK; + + CASE(OP_get_super_value): + { + JSValue val; + JSAtom atom; + atom = JS_ValueToAtom(ctx, sp[-1]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + val = JS_GetPropertyInternal(ctx, sp[-2], atom, sp[-3], FALSE); + JS_FreeAtom(ctx, atom); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-3]); + sp[-3] = val; + sp -= 2; + } + BREAK; + + CASE(OP_put_array_el): + { + int ret; + + ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_put_ref_value): + { + int ret, flags; + flags = JS_PROP_THROW_STRICT; + if (unlikely(JS_IsUndefined(sp[-3]))) { + if (is_strict_mode(ctx)) { + JSAtom atom = JS_ValueToAtom(ctx, sp[-2]); + if (atom != JS_ATOM_NULL) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + } + goto exception; + } else { + sp[-3] = JS_DupValue(ctx, ctx->global_obj); + } + } else { + if (is_strict_mode(ctx)) + flags |= JS_PROP_NO_ADD; + } + ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], flags); + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_put_super_value): + { + int ret; + JSAtom atom; + if (JS_VALUE_GET_TAG(sp[-3]) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto exception; + } + atom = JS_ValueToAtom(ctx, sp[-2]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + ret = JS_SetPropertyInternal(ctx, sp[-3], atom, sp[-1], sp[-4], + JS_PROP_THROW_STRICT); + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, sp[-4]); + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-2]); + sp -= 4; + if (ret < 0) + goto exception; + } + BREAK; + + CASE(OP_define_array_el): + { + int ret; + ret = JS_DefinePropertyValueValue(ctx, sp[-3], JS_DupValue(ctx, sp[-2]), sp[-1], + JS_PROP_C_W_E | JS_PROP_THROW); + sp -= 1; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_append): /* array pos enumobj -- array pos */ + { + if (js_append_enumerate(ctx, sp)) + goto exception; + JS_FreeValue(ctx, *--sp); + } + BREAK; + + CASE(OP_copy_data_properties): /* target source excludeList */ + { + /* stack offsets (-1 based): + 2 bits for target, + 3 bits for source, + 2 bits for exclusionList */ + int mask; + + mask = *pc++; + if (JS_CopyDataProperties(ctx, sp[-1 - (mask & 3)], + sp[-1 - ((mask >> 2) & 7)], + sp[-1 - ((mask >> 5) & 7)], 0)) + goto exception; + } + BREAK; + + CASE(OP_add): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(op1) + JS_VALUE_GET_INT(op2); + if (unlikely((int)r != r)) + goto add_slow; + sp[-2] = JS_NewInt32(ctx, r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + sp[-2] = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) + + JS_VALUE_GET_FLOAT64(op2)); + sp--; + } else if (JS_IsString(op1) && JS_IsString(op2)) { + sp[-2] = JS_ConcatString(ctx, op1, op2); + sp--; + if (JS_IsException(sp[-1])) + goto exception; + } else { + add_slow: + if (js_add_slow(ctx, sp)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_add_loc): + { + JSValue op2; + JSValue *pv; + int idx; + idx = *pc; + pc += 1; + + op2 = sp[-1]; + pv = &var_buf[idx]; + if (likely(JS_VALUE_IS_BOTH_INT(*pv, op2))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(*pv) + JS_VALUE_GET_INT(op2); + if (unlikely((int)r != r)) + goto add_loc_slow; + *pv = JS_NewInt32(ctx, r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(*pv, op2)) { + *pv = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(*pv) + + JS_VALUE_GET_FLOAT64(op2)); + sp--; + } else if (JS_VALUE_GET_TAG(*pv) == JS_TAG_STRING) { + sp--; + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); + if (JS_IsException(op2)) + goto exception; + if (JS_ConcatStringInPlace(ctx, JS_VALUE_GET_STRING(*pv), op2)) { + JS_FreeValue(ctx, op2); + } else { + op2 = JS_ConcatString(ctx, JS_DupValue(ctx, *pv), op2); + if (JS_IsException(op2)) + goto exception; + set_value(ctx, pv, op2); + } + } else { + JSValue ops[2]; + add_loc_slow: + /* In case of exception, js_add_slow frees ops[0] + and ops[1], so we must duplicate *pv */ + ops[0] = JS_DupValue(ctx, *pv); + ops[1] = op2; + sp--; + if (js_add_slow(ctx, ops + 2)) + goto exception; + set_value(ctx, pv, ops[0]); + } + } + BREAK; + CASE(OP_sub): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(op1) - JS_VALUE_GET_INT(op2); + if (unlikely((int)r != r)) + goto binary_arith_slow; + sp[-2] = JS_NewInt32(ctx, r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + sp[-2] = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) - + JS_VALUE_GET_FLOAT64(op2)); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_mul): + { + JSValue op1, op2; + double d; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int32_t v1, v2; + int64_t r; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + r = (int64_t)v1 * v2; + if (unlikely((int)r != r)) { +#ifdef CONFIG_BIGNUM + if (unlikely(sf->js_mode & JS_MODE_MATH) && + (r < -MAX_SAFE_INTEGER || r > MAX_SAFE_INTEGER)) + goto binary_arith_slow; +#endif + d = (double)r; + goto mul_fp_res; + } + /* need to test zero case for -0 result */ + if (unlikely(r == 0 && (v1 | v2) < 0)) { + d = -0.0; + goto mul_fp_res; + } + sp[-2] = JS_NewInt32(ctx, r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { +#ifdef CONFIG_BIGNUM + if (unlikely(sf->js_mode & JS_MODE_MATH)) + goto binary_arith_slow; +#endif + d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2); + mul_fp_res: + sp[-2] = __JS_NewFloat64(ctx, d); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_div): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2; + if (unlikely(sf->js_mode & JS_MODE_MATH)) + goto binary_arith_slow; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + sp[-2] = JS_NewFloat64(ctx, (double)v1 / (double)v2); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_mod): +#ifdef CONFIG_BIGNUM + CASE(OP_math_mod): +#endif + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2, r; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + /* We must avoid v2 = 0, v1 = INT32_MIN and v2 = + -1 and the cases where the result is -0. */ + if (unlikely(v1 < 0 || v2 <= 0)) + goto binary_arith_slow; + r = v1 % v2; + sp[-2] = JS_NewInt32(ctx, r); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_pow): + binary_arith_slow: + if (js_binary_arith_slow(ctx, sp, opcode)) + goto exception; + sp--; + BREAK; + + CASE(OP_plus): + { + JSValue op1; + uint32_t tag; + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG(op1); + if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag)) { + } else { + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_neg): + { + JSValue op1; + uint32_t tag; + int val; + double d; + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG(op1); + if (tag == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + /* Note: -0 cannot be expressed as integer */ + if (unlikely(val == 0)) { + d = -0.0; + goto neg_fp_res; + } + if (unlikely(val == INT32_MIN)) { + d = -(double)val; + goto neg_fp_res; + } + sp[-1] = JS_NewInt32(ctx, -val); + } else if (JS_TAG_IS_FLOAT64(tag)) { + d = -JS_VALUE_GET_FLOAT64(op1); + neg_fp_res: + sp[-1] = __JS_NewFloat64(ctx, d); + } else { + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_inc): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto inc_slow; + sp[-1] = JS_NewInt32(ctx, val + 1); + } else { + inc_slow: + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_dec): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto dec_slow; + sp[-1] = JS_NewInt32(ctx, val - 1); + } else { + dec_slow: + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_post_inc): + CASE(OP_post_dec): + if (js_post_inc_slow(ctx, sp, opcode)) + goto exception; + sp++; + BREAK; + CASE(OP_inc_loc): + { + JSValue op1; + int val; + int idx; + idx = *pc; + pc += 1; + + op1 = var_buf[idx]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto inc_loc_slow; + var_buf[idx] = JS_NewInt32(ctx, val + 1); + } else { + inc_loc_slow: + /* must duplicate otherwise the variable value may + be destroyed before JS code accesses it */ + op1 = JS_DupValue(ctx, op1); + if (js_unary_arith_slow(ctx, &op1 + 1, OP_inc)) + goto exception; + set_value(ctx, &var_buf[idx], op1); + } + } + BREAK; + CASE(OP_dec_loc): + { + JSValue op1; + int val; + int idx; + idx = *pc; + pc += 1; + + op1 = var_buf[idx]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto dec_loc_slow; + var_buf[idx] = JS_NewInt32(ctx, val - 1); + } else { + dec_loc_slow: + /* must duplicate otherwise the variable value may + be destroyed before JS code accesses it */ + op1 = JS_DupValue(ctx, op1); + if (js_unary_arith_slow(ctx, &op1 + 1, OP_dec)) + goto exception; + set_value(ctx, &var_buf[idx], op1); + } + } + BREAK; + CASE(OP_not): + { + JSValue op1; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + sp[-1] = JS_NewInt32(ctx, ~JS_VALUE_GET_INT(op1)); + } else { + if (js_not_slow(ctx, sp)) + goto exception; + } + } + BREAK; + + CASE(OP_shl): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v1, v2; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); +#ifdef CONFIG_BIGNUM + { + int64_t r; + if (unlikely(sf->js_mode & JS_MODE_MATH)) { + if (v2 > 0x1f) + goto shl_slow; + r = (int64_t)v1 << v2; + if ((int)r != r) + goto shl_slow; + } else { + v2 &= 0x1f; + } + } +#else + v2 &= 0x1f; +#endif + sp[-2] = JS_NewInt32(ctx, v1 << v2); + sp--; + } else { +#ifdef CONFIG_BIGNUM + shl_slow: +#endif + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_shr): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v2; + v2 = JS_VALUE_GET_INT(op2); + /* v1 >>> v2 retains its JS semantics if CONFIG_BIGNUM */ + v2 &= 0x1f; + sp[-2] = JS_NewUint32(ctx, + (uint32_t)JS_VALUE_GET_INT(op1) >> + v2); + sp--; + } else { + if (js_shr_slow(ctx, sp)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_sar): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v2; + v2 = JS_VALUE_GET_INT(op2); +#ifdef CONFIG_BIGNUM + if (unlikely(v2 > 0x1f)) { + if (unlikely(sf->js_mode & JS_MODE_MATH)) + goto sar_slow; + else + v2 &= 0x1f; + } +#else + v2 &= 0x1f; +#endif + sp[-2] = JS_NewInt32(ctx, + (int)JS_VALUE_GET_INT(op1) >> v2); + sp--; + } else { +#ifdef CONFIG_BIGNUM + sar_slow: +#endif + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_and): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = JS_NewInt32(ctx, + JS_VALUE_GET_INT(op1) & + JS_VALUE_GET_INT(op2)); + sp--; + } else { + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_or): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = JS_NewInt32(ctx, + JS_VALUE_GET_INT(op1) | + JS_VALUE_GET_INT(op2)); + sp--; + } else { + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_xor): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = JS_NewInt32(ctx, + JS_VALUE_GET_INT(op1) ^ + JS_VALUE_GET_INT(op2)); + sp--; + } else { + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + + +#define OP_CMP(opcode, binary_op, slow_call) \ + CASE(opcode): \ + { \ + JSValue op1, op2; \ + op1 = sp[-2]; \ + op2 = sp[-1]; \ + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \ + sp[-2] = JS_NewBool(ctx, JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \ + sp--; \ + } else { \ + if (slow_call) \ + goto exception; \ + sp--; \ + } \ + } \ + BREAK + + OP_CMP(OP_lt, <, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_lte, <=, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_gt, >, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_gte, >=, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_eq, ==, js_eq_slow(ctx, sp, 0)); + OP_CMP(OP_neq, !=, js_eq_slow(ctx, sp, 1)); + OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, sp, 0)); + OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, sp, 1)); + +#ifdef CONFIG_BIGNUM + CASE(OP_mul_pow10): + if (rt->bigfloat_ops.mul_pow10(ctx, sp)) + goto exception; + sp--; + BREAK; +#endif + CASE(OP_in): + if (js_operator_in(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_private_in): + if (js_operator_private_in(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_instanceof): + if (js_operator_instanceof(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_typeof): + { + JSValue op1; + JSAtom atom; + + op1 = sp[-1]; + atom = js_operator_typeof(ctx, op1); + JS_FreeValue(ctx, op1); + sp[-1] = JS_AtomToString(ctx, atom); + } + BREAK; + CASE(OP_delete): + if (js_operator_delete(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_delete_var): + { + JSAtom atom; + int ret; + + atom = get_u32(pc); + pc += 4; + + ret = JS_DeleteProperty(ctx, ctx->global_obj, atom, 0); + if (unlikely(ret < 0)) + goto exception; + *sp++ = JS_NewBool(ctx, ret); + } + BREAK; + + CASE(OP_to_object): + if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_OBJECT) { + ret_val = JS_ToObject(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + } + BREAK; + + CASE(OP_to_propkey): + switch (JS_VALUE_GET_TAG(sp[-1])) { + case JS_TAG_INT: + case JS_TAG_STRING: + case JS_TAG_SYMBOL: + break; + default: + ret_val = JS_ToPropertyKey(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + break; + } + BREAK; + + CASE(OP_to_propkey2): + /* must be tested first */ + if (unlikely(JS_IsUndefined(sp[-2]) || JS_IsNull(sp[-2]))) { + JS_ThrowTypeError(ctx, "value has no property"); + goto exception; + } + switch (JS_VALUE_GET_TAG(sp[-1])) { + case JS_TAG_INT: + case JS_TAG_STRING: + case JS_TAG_SYMBOL: + break; + default: + ret_val = JS_ToPropertyKey(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + break; + } + BREAK; +#if 0 + CASE(OP_to_string): + if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_STRING) { + ret_val = JS_ToString(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + } + BREAK; +#endif + CASE(OP_with_get_var): + CASE(OP_with_put_var): + CASE(OP_with_delete_var): + CASE(OP_with_make_ref): + CASE(OP_with_get_ref): + CASE(OP_with_get_ref_undef): + { + JSAtom atom; + int32_t diff; + JSValue obj, val; + int ret, is_with; + atom = get_u32(pc); + diff = get_u32(pc + 4); + is_with = pc[8]; + pc += 9; + + obj = sp[-1]; + ret = JS_HasProperty(ctx, obj, atom); + if (unlikely(ret < 0)) + goto exception; + if (ret) { + if (is_with) { + ret = js_has_unscopable(ctx, obj, atom); + if (unlikely(ret < 0)) + goto exception; + if (ret) + goto no_with; + } + switch (opcode) { + case OP_with_get_var: + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + set_value(ctx, &sp[-1], val); + break; + case OP_with_put_var: + /* XXX: check if strict mode */ + ret = JS_SetPropertyInternal(ctx, obj, atom, sp[-2], obj, + JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + break; + case OP_with_delete_var: + ret = JS_DeleteProperty(ctx, obj, atom, 0); + if (unlikely(ret < 0)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = JS_NewBool(ctx, ret); + break; + case OP_with_make_ref: + /* produce a pair object/propname on the stack */ + *sp++ = JS_AtomToValue(ctx, atom); + break; + case OP_with_get_ref: + /* produce a pair object/method on the stack */ + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + *sp++ = val; + break; + case OP_with_get_ref_undef: + /* produce a pair undefined/function on the stack */ + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = JS_UNDEFINED; + *sp++ = val; + break; + } + pc += diff - 5; + } else { + no_with: + /* if not jumping, drop the object argument */ + JS_FreeValue(ctx, sp[-1]); + sp--; + } + } + BREAK; + + CASE(OP_await): + ret_val = JS_NewInt32(ctx, FUNC_RET_AWAIT); + goto done_generator; + CASE(OP_yield): + ret_val = JS_NewInt32(ctx, FUNC_RET_YIELD); + goto done_generator; + CASE(OP_yield_star): + CASE(OP_async_yield_star): + ret_val = JS_NewInt32(ctx, FUNC_RET_YIELD_STAR); + goto done_generator; + CASE(OP_return_async): + ret_val = JS_UNDEFINED; + goto done_generator; + CASE(OP_initial_yield): + ret_val = JS_NewInt32(ctx, FUNC_RET_INITIAL_YIELD); + goto done_generator; + + CASE(OP_nop): + BREAK; + CASE(OP_is_undefined_or_null): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED || + JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { + goto set_true; + } else { + goto free_and_set_false; + } +#if SHORT_OPCODES + CASE(OP_is_undefined): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED) { + goto set_true; + } else { + goto free_and_set_false; + } + CASE(OP_is_null): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { + goto set_true; + } else { + goto free_and_set_false; + } + /* XXX: could merge to a single opcode */ + CASE(OP_typeof_is_undefined): + /* different from OP_is_undefined because of isHTMLDDA */ + if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_undefined) { + goto free_and_set_true; + } else { + goto free_and_set_false; + } + CASE(OP_typeof_is_function): + if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_function) { + goto free_and_set_true; + } else { + goto free_and_set_false; + } + free_and_set_true: + JS_FreeValue(ctx, sp[-1]); +#endif + set_true: + sp[-1] = JS_TRUE; + BREAK; + free_and_set_false: + JS_FreeValue(ctx, sp[-1]); + sp[-1] = JS_FALSE; + BREAK; + CASE(OP_invalid): + DEFAULT: + JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x", + (int)(pc - b->byte_code_buf - 1), opcode); + goto exception; + } + } + exception: + if (is_backtrace_needed(ctx, rt->current_exception)) { + /* add the backtrace information now (it is not done + before if the exception happens in a bytecode + operation */ + sf->cur_pc = pc; + build_backtrace(ctx, rt->current_exception, NULL, 0, 0); + } + if (!JS_IsUncatchableError(ctx, rt->current_exception)) { + while (sp > stack_buf) { + JSValue val = *--sp; + JS_FreeValue(ctx, val); + if (JS_VALUE_GET_TAG(val) == JS_TAG_CATCH_OFFSET) { + int pos = JS_VALUE_GET_INT(val); + if (pos == 0) { + /* enumerator: close it with a throw */ + JS_FreeValue(ctx, sp[-1]); /* drop the next method */ + sp--; + JS_IteratorClose(ctx, sp[-1], TRUE); + } else { + *sp++ = rt->current_exception; + rt->current_exception = JS_NULL; + pc = b->byte_code_buf + pos; + goto restart; + } + } + } + } + ret_val = JS_EXCEPTION; + /* the local variables are freed by the caller in the generator + case. Hence the label 'done' should never be reached in a + generator function. */ + if (b->func_kind != JS_FUNC_NORMAL) { + done_generator: + sf->cur_pc = pc; + sf->cur_sp = sp; + } else { + done: + if (unlikely(!list_empty(&sf->var_ref_list))) { + /* variable references reference the stack: must close them */ + close_var_refs(rt, sf); + } + /* free the local variables and stack */ + for(pval = local_buf; pval < sp; pval++) { + JS_FreeValue(ctx, *pval); + } + } + rt->current_stack_frame = sf->prev_frame; + return ret_val; +} + +JSValue JS_Call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv) +{ + return JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED, + argc, (JSValue *)argv, JS_CALL_FLAG_COPY_ARGV); +} + +static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv) +{ + JSValue res = JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED, + argc, (JSValue *)argv, JS_CALL_FLAG_COPY_ARGV); + JS_FreeValue(ctx, func_obj); + return res; +} + +/* warning: the refcount of the context is not incremented. Return + NULL in case of exception (case of revoked proxy only) */ +static JSContext *JS_GetFunctionRealm(JSContext *ctx, JSValueConst func_obj) +{ + JSObject *p; + JSContext *realm; + + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return ctx; + p = JS_VALUE_GET_OBJ(func_obj); + switch(p->class_id) { + case JS_CLASS_C_FUNCTION: + realm = p->u.cfunc.realm; + break; + case JS_CLASS_BYTECODE_FUNCTION: + case JS_CLASS_GENERATOR_FUNCTION: + case JS_CLASS_ASYNC_FUNCTION: + case JS_CLASS_ASYNC_GENERATOR_FUNCTION: + { + JSFunctionBytecode *b; + b = p->u.func.function_bytecode; + realm = b->realm; + } + break; + case JS_CLASS_PROXY: + { + JSProxyData *s = p->u.opaque; + if (!s) + return ctx; + if (s->is_revoked) { + JS_ThrowTypeErrorRevokedProxy(ctx); + return NULL; + } else { + realm = JS_GetFunctionRealm(ctx, s->target); + } + } + break; + case JS_CLASS_BOUND_FUNCTION: + { + JSBoundFunction *bf = p->u.bound_function; + realm = JS_GetFunctionRealm(ctx, bf->func_obj); + } + break; + default: + realm = ctx; + break; + } + return realm; +} + +static JSValue js_create_from_ctor(JSContext *ctx, JSValueConst ctor, + int class_id) +{ + JSValue proto, obj; + JSContext *realm; + + if (JS_IsUndefined(ctor)) { + proto = JS_DupValue(ctx, ctx->class_proto[class_id]); + } else { + proto = JS_GetProperty(ctx, ctor, JS_ATOM_prototype); + if (JS_IsException(proto)) + return proto; + if (!JS_IsObject(proto)) { + JS_FreeValue(ctx, proto); + realm = JS_GetFunctionRealm(ctx, ctor); + if (!realm) + return JS_EXCEPTION; + proto = JS_DupValue(ctx, realm->class_proto[class_id]); + } + } + obj = JS_NewObjectProtoClass(ctx, proto, class_id); + JS_FreeValue(ctx, proto); + return obj; +} + +/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ +static JSValue JS_CallConstructorInternal(JSContext *ctx, + JSValueConst func_obj, + JSValueConst new_target, + int argc, JSValue *argv, int flags) +{ + JSObject *p; + JSFunctionBytecode *b; + + if (js_poll_interrupts(ctx)) + return JS_EXCEPTION; + flags |= JS_CALL_FLAG_CONSTRUCTOR; + if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) + goto not_a_function; + p = JS_VALUE_GET_OBJ(func_obj); + if (unlikely(!p->is_constructor)) + return JS_ThrowTypeError(ctx, "not a constructor"); + if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { + JSClassCall *call_func; + call_func = ctx->rt->class_array[p->class_id].call; + if (!call_func) { + not_a_function: + return JS_ThrowTypeError(ctx, "not a function"); + } + return call_func(ctx, func_obj, new_target, argc, + (JSValueConst *)argv, flags); + } + + b = p->u.func.function_bytecode; + if (b->is_derived_class_constructor) { + return JS_CallInternal(ctx, func_obj, JS_UNDEFINED, new_target, argc, argv, flags); + } else { + JSValue obj, ret; + /* legacy constructor behavior */ + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT); + if (JS_IsException(obj)) + return JS_EXCEPTION; + ret = JS_CallInternal(ctx, func_obj, obj, new_target, argc, argv, flags); + if (JS_VALUE_GET_TAG(ret) == JS_TAG_OBJECT || + JS_IsException(ret)) { + JS_FreeValue(ctx, obj); + return ret; + } else { + JS_FreeValue(ctx, ret); + return obj; + } + } +} + +JSValue JS_CallConstructor2(JSContext *ctx, JSValueConst func_obj, + JSValueConst new_target, + int argc, JSValueConst *argv) +{ + return JS_CallConstructorInternal(ctx, func_obj, new_target, + argc, (JSValue *)argv, + JS_CALL_FLAG_COPY_ARGV); +} + +JSValue JS_CallConstructor(JSContext *ctx, JSValueConst func_obj, + int argc, JSValueConst *argv) +{ + return JS_CallConstructorInternal(ctx, func_obj, func_obj, + argc, (JSValue *)argv, + JS_CALL_FLAG_COPY_ARGV); +} + +JSValue JS_Invoke(JSContext *ctx, JSValueConst this_val, JSAtom atom, + int argc, JSValueConst *argv) +{ + JSValue func_obj; + func_obj = JS_GetProperty(ctx, this_val, atom); + if (JS_IsException(func_obj)) + return func_obj; + return JS_CallFree(ctx, func_obj, this_val, argc, argv); +} + +static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, + int argc, JSValueConst *argv) +{ + JSValue res = JS_Invoke(ctx, this_val, atom, argc, argv); + JS_FreeValue(ctx, this_val); + return res; +} + +/* JSAsyncFunctionState (used by generator and async functions) */ +static JSAsyncFunctionState *async_func_init(JSContext *ctx, + JSValueConst func_obj, JSValueConst this_obj, + int argc, JSValueConst *argv) +{ + JSAsyncFunctionState *s; + JSObject *p; + JSFunctionBytecode *b; + JSStackFrame *sf; + int local_count, i, arg_buf_len, n; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return NULL; + s->header.ref_count = 1; + add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION); + + sf = &s->frame; + init_list_head(&sf->var_ref_list); + p = JS_VALUE_GET_OBJ(func_obj); + b = p->u.func.function_bytecode; + sf->js_mode = b->js_mode | JS_MODE_ASYNC; + sf->cur_pc = b->byte_code_buf; + arg_buf_len = max_int(b->arg_count, argc); + local_count = arg_buf_len + b->var_count + b->stack_size; + sf->arg_buf = js_malloc(ctx, sizeof(JSValue) * max_int(local_count, 1)); + if (!sf->arg_buf) { + js_free(ctx, s); + return NULL; + } + sf->cur_func = JS_DupValue(ctx, func_obj); + s->this_val = JS_DupValue(ctx, this_obj); + s->argc = argc; + sf->arg_count = arg_buf_len; + sf->var_buf = sf->arg_buf + arg_buf_len; + sf->cur_sp = sf->var_buf + b->var_count; + for(i = 0; i < argc; i++) + sf->arg_buf[i] = JS_DupValue(ctx, argv[i]); + n = arg_buf_len + b->var_count; + for(i = argc; i < n; i++) + sf->arg_buf[i] = JS_UNDEFINED; + s->resolving_funcs[0] = JS_UNDEFINED; + s->resolving_funcs[1] = JS_UNDEFINED; + s->is_completed = FALSE; + return s; +} + +static void async_func_free_frame(JSRuntime *rt, JSAsyncFunctionState *s) +{ + JSStackFrame *sf = &s->frame; + JSValue *sp; + + if (sf->arg_buf) { + /* cannot free the function if it is running */ + assert(sf->cur_sp != NULL); + for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) { + JS_FreeValueRT(rt, *sp); + } + js_free_rt(rt, sf->arg_buf); + sf->arg_buf = NULL; + } + JS_FreeValueRT(rt, sf->cur_func); + JS_FreeValueRT(rt, s->this_val); +} + +static JSValue async_func_resume(JSContext *ctx, JSAsyncFunctionState *s) +{ + JSRuntime *rt = ctx->rt; + JSStackFrame *sf = &s->frame; + JSValue func_obj, ret; + + assert(!s->is_completed); + if (js_check_stack_overflow(ctx->rt, 0)) { + ret = JS_ThrowStackOverflow(ctx); + } else { + /* the tag does not matter provided it is not an object */ + func_obj = JS_MKPTR(JS_TAG_INT, s); + ret = JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED, + s->argc, sf->arg_buf, JS_CALL_FLAG_GENERATOR); + } + if (JS_IsException(ret) || JS_IsUndefined(ret)) { + if (JS_IsUndefined(ret)) { + ret = sf->cur_sp[-1]; + sf->cur_sp[-1] = JS_UNDEFINED; + } + /* end of execution */ + s->is_completed = TRUE; + + /* close the closure variables. */ + close_var_refs(rt, sf); + + async_func_free_frame(rt, s); + } + return ret; +} + +static void __async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) +{ + /* cannot close the closure variables here because it would + potentially modify the object graph */ + if (!s->is_completed) { + async_func_free_frame(rt, s); + } + + JS_FreeValueRT(rt, s->resolving_funcs[0]); + JS_FreeValueRT(rt, s->resolving_funcs[1]); + + remove_gc_object(&s->header); + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && s->header.ref_count != 0) { + list_add_tail(&s->header.link, &rt->gc_zero_ref_count_list); + } else { + js_free_rt(rt, s); + } +} + +static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) +{ + if (--s->header.ref_count == 0) { + if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { + list_del(&s->header.link); + list_add(&s->header.link, &rt->gc_zero_ref_count_list); + if (rt->gc_phase == JS_GC_PHASE_NONE) { + free_zero_refcount(rt); + } + } + } +} + +/* Generators */ + +typedef enum JSGeneratorStateEnum { + JS_GENERATOR_STATE_SUSPENDED_START, + JS_GENERATOR_STATE_SUSPENDED_YIELD, + JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR, + JS_GENERATOR_STATE_EXECUTING, + JS_GENERATOR_STATE_COMPLETED, +} JSGeneratorStateEnum; + +typedef struct JSGeneratorData { + JSGeneratorStateEnum state; + JSAsyncFunctionState *func_state; +} JSGeneratorData; + +static void free_generator_stack_rt(JSRuntime *rt, JSGeneratorData *s) +{ + if (s->state == JS_GENERATOR_STATE_COMPLETED) + return; + if (s->func_state) { + async_func_free(rt, s->func_state); + s->func_state = NULL; + } + s->state = JS_GENERATOR_STATE_COMPLETED; +} + +static void js_generator_finalizer(JSRuntime *rt, JSValue obj) +{ + JSGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_GENERATOR); + + if (s) { + free_generator_stack_rt(rt, s); + js_free_rt(rt, s); + } +} + +static void free_generator_stack(JSContext *ctx, JSGeneratorData *s) +{ + free_generator_stack_rt(ctx->rt, s); +} + +static void js_generator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSGeneratorData *s = p->u.generator_data; + + if (!s || !s->func_state) + return; + mark_func(rt, &s->func_state->header); +} + +/* XXX: use enum */ +#define GEN_MAGIC_NEXT 0 +#define GEN_MAGIC_RETURN 1 +#define GEN_MAGIC_THROW 2 + +static JSValue js_generator_next(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + BOOL *pdone, int magic) +{ + JSGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_GENERATOR); + JSStackFrame *sf; + JSValue ret, func_ret; + + *pdone = TRUE; + if (!s) + return JS_ThrowTypeError(ctx, "not a generator"); + switch(s->state) { + default: + case JS_GENERATOR_STATE_SUSPENDED_START: + sf = &s->func_state->frame; + if (magic == GEN_MAGIC_NEXT) { + goto exec_no_arg; + } else { + free_generator_stack(ctx, s); + goto done; + } + break; + case JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR: + case JS_GENERATOR_STATE_SUSPENDED_YIELD: + sf = &s->func_state->frame; + /* cur_sp[-1] was set to JS_UNDEFINED in the previous call */ + ret = JS_DupValue(ctx, argv[0]); + if (magic == GEN_MAGIC_THROW && + s->state == JS_GENERATOR_STATE_SUSPENDED_YIELD) { + JS_Throw(ctx, ret); + s->func_state->throw_flag = TRUE; + } else { + sf->cur_sp[-1] = ret; + sf->cur_sp[0] = JS_NewInt32(ctx, magic); + sf->cur_sp++; + exec_no_arg: + s->func_state->throw_flag = FALSE; + } + s->state = JS_GENERATOR_STATE_EXECUTING; + func_ret = async_func_resume(ctx, s->func_state); + s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD; + if (s->func_state->is_completed) { + /* finalize the execution in case of exception or normal return */ + free_generator_stack(ctx, s); + return func_ret; + } else { + assert(JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT); + /* get the returned yield value at the top of the stack */ + ret = sf->cur_sp[-1]; + sf->cur_sp[-1] = JS_UNDEFINED; + if (JS_VALUE_GET_INT(func_ret) == FUNC_RET_YIELD_STAR) { + s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR; + /* return (value, done) object */ + *pdone = 2; + } else { + *pdone = FALSE; + } + } + break; + case JS_GENERATOR_STATE_COMPLETED: + done: + /* execution is finished */ + switch(magic) { + default: + case GEN_MAGIC_NEXT: + ret = JS_UNDEFINED; + break; + case GEN_MAGIC_RETURN: + ret = JS_DupValue(ctx, argv[0]); + break; + case GEN_MAGIC_THROW: + ret = JS_Throw(ctx, JS_DupValue(ctx, argv[0])); + break; + } + break; + case JS_GENERATOR_STATE_EXECUTING: + ret = JS_ThrowTypeError(ctx, "cannot invoke a running generator"); + break; + } + return ret; +} + +static JSValue js_generator_function_call(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags) +{ + JSValue obj, func_ret; + JSGeneratorData *s; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + s->state = JS_GENERATOR_STATE_SUSPENDED_START; + s->func_state = async_func_init(ctx, func_obj, this_obj, argc, argv); + if (!s->func_state) { + s->state = JS_GENERATOR_STATE_COMPLETED; + goto fail; + } + + /* execute the function up to 'OP_initial_yield' */ + func_ret = async_func_resume(ctx, s->func_state); + if (JS_IsException(func_ret)) + goto fail; + JS_FreeValue(ctx, func_ret); + + obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_GENERATOR); + if (JS_IsException(obj)) + goto fail; + JS_SetOpaque(obj, s); + return obj; + fail: + free_generator_stack_rt(ctx->rt, s); + js_free(ctx, s); + return JS_EXCEPTION; +} + +/* AsyncFunction */ + +static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSAsyncFunctionState *s = p->u.async_function_data; + if (s) { + async_func_free(rt, s); + } +} + +static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSAsyncFunctionState *s = p->u.async_function_data; + if (s) { + mark_func(rt, &s->header); + } +} + +static int js_async_function_resolve_create(JSContext *ctx, + JSAsyncFunctionState *s, + JSValue *resolving_funcs) +{ + int i; + JSObject *p; + + for(i = 0; i < 2; i++) { + resolving_funcs[i] = + JS_NewObjectProtoClass(ctx, ctx->function_proto, + JS_CLASS_ASYNC_FUNCTION_RESOLVE + i); + if (JS_IsException(resolving_funcs[i])) { + if (i == 1) + JS_FreeValue(ctx, resolving_funcs[0]); + return -1; + } + p = JS_VALUE_GET_OBJ(resolving_funcs[i]); + s->header.ref_count++; + p->u.async_function_data = s; + } + return 0; +} + +static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionState *s) +{ + JSValue func_ret, ret2; + + func_ret = async_func_resume(ctx, s); + if (s->is_completed) { + if (JS_IsException(func_ret)) { + JSValue error; + fail: + error = JS_GetException(ctx); + ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED, + 1, (JSValueConst *)&error); + JS_FreeValue(ctx, error); + JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */ + } else { + /* normal return */ + ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED, + 1, (JSValueConst *)&func_ret); + JS_FreeValue(ctx, func_ret); + JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */ + } + } else { + JSValue value, promise, resolving_funcs[2], resolving_funcs1[2]; + int i, res; + + value = s->frame.cur_sp[-1]; + s->frame.cur_sp[-1] = JS_UNDEFINED; + + /* await */ + JS_FreeValue(ctx, func_ret); /* not used */ + promise = js_promise_resolve(ctx, ctx->promise_ctor, + 1, (JSValueConst *)&value, 0); + JS_FreeValue(ctx, value); + if (JS_IsException(promise)) + goto fail; + if (js_async_function_resolve_create(ctx, s, resolving_funcs)) { + JS_FreeValue(ctx, promise); + goto fail; + } + + /* Note: no need to create 'thrownawayCapability' as in + the spec */ + for(i = 0; i < 2; i++) + resolving_funcs1[i] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + (JSValueConst *)resolving_funcs, + (JSValueConst *)resolving_funcs1); + JS_FreeValue(ctx, promise); + for(i = 0; i < 2; i++) + JS_FreeValue(ctx, resolving_funcs[i]); + if (res) + goto fail; + } +} + +static JSValue js_async_function_resolve_call(JSContext *ctx, + JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags) +{ + JSObject *p = JS_VALUE_GET_OBJ(func_obj); + JSAsyncFunctionState *s = p->u.async_function_data; + BOOL is_reject = p->class_id - JS_CLASS_ASYNC_FUNCTION_RESOLVE; + JSValueConst arg; + + if (argc > 0) + arg = argv[0]; + else + arg = JS_UNDEFINED; + s->throw_flag = is_reject; + if (is_reject) { + JS_Throw(ctx, JS_DupValue(ctx, arg)); + } else { + /* return value of await */ + s->frame.cur_sp[-1] = JS_DupValue(ctx, arg); + } + js_async_function_resume(ctx, s); + return JS_UNDEFINED; +} + +static JSValue js_async_function_call(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, int flags) +{ + JSValue promise; + JSAsyncFunctionState *s; + + s = async_func_init(ctx, func_obj, this_obj, argc, argv); + if (!s) + return JS_EXCEPTION; + + promise = JS_NewPromiseCapability(ctx, s->resolving_funcs); + if (JS_IsException(promise)) { + async_func_free(ctx->rt, s); + return JS_EXCEPTION; + } + + js_async_function_resume(ctx, s); + + async_func_free(ctx->rt, s); + + return promise; +} + +/* AsyncGenerator */ + +typedef enum JSAsyncGeneratorStateEnum { + JS_ASYNC_GENERATOR_STATE_SUSPENDED_START, + JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD, + JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR, + JS_ASYNC_GENERATOR_STATE_EXECUTING, + JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN, + JS_ASYNC_GENERATOR_STATE_COMPLETED, +} JSAsyncGeneratorStateEnum; + +typedef struct JSAsyncGeneratorRequest { + struct list_head link; + /* completion */ + int completion_type; /* GEN_MAGIC_x */ + JSValue result; + /* promise capability */ + JSValue promise; + JSValue resolving_funcs[2]; +} JSAsyncGeneratorRequest; + +typedef struct JSAsyncGeneratorData { + JSObject *generator; /* back pointer to the object (const) */ + JSAsyncGeneratorStateEnum state; + /* func_state is NULL is state AWAITING_RETURN and COMPLETED */ + JSAsyncFunctionState *func_state; + struct list_head queue; /* list of JSAsyncGeneratorRequest.link */ +} JSAsyncGeneratorData; + +static void js_async_generator_free(JSRuntime *rt, + JSAsyncGeneratorData *s) +{ + struct list_head *el, *el1; + JSAsyncGeneratorRequest *req; + + list_for_each_safe(el, el1, &s->queue) { + req = list_entry(el, JSAsyncGeneratorRequest, link); + JS_FreeValueRT(rt, req->result); + JS_FreeValueRT(rt, req->promise); + JS_FreeValueRT(rt, req->resolving_funcs[0]); + JS_FreeValueRT(rt, req->resolving_funcs[1]); + js_free_rt(rt, req); + } + if (s->func_state) + async_func_free(rt, s->func_state); + js_free_rt(rt, s); +} + +static void js_async_generator_finalizer(JSRuntime *rt, JSValue obj) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_ASYNC_GENERATOR); + + if (s) { + js_async_generator_free(rt, s); + } +} + +static void js_async_generator_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(val, JS_CLASS_ASYNC_GENERATOR); + struct list_head *el; + JSAsyncGeneratorRequest *req; + if (s) { + list_for_each(el, &s->queue) { + req = list_entry(el, JSAsyncGeneratorRequest, link); + JS_MarkValue(rt, req->result, mark_func); + JS_MarkValue(rt, req->promise, mark_func); + JS_MarkValue(rt, req->resolving_funcs[0], mark_func); + JS_MarkValue(rt, req->resolving_funcs[1], mark_func); + } + if (s->func_state) { + mark_func(rt, &s->func_state->header); + } + } +} + +static JSValue js_async_generator_resolve_function(JSContext *ctx, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int magic, JSValue *func_data); + +static int js_async_generator_resolve_function_create(JSContext *ctx, + JSValueConst generator, + JSValue *resolving_funcs, + BOOL is_resume_next) +{ + int i; + JSValue func; + + for(i = 0; i < 2; i++) { + func = JS_NewCFunctionData(ctx, js_async_generator_resolve_function, 1, + i + is_resume_next * 2, 1, &generator); + if (JS_IsException(func)) { + if (i == 1) + JS_FreeValue(ctx, resolving_funcs[0]); + return -1; + } + resolving_funcs[i] = func; + } + return 0; +} + +static int js_async_generator_await(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst value) +{ + JSValue promise, resolving_funcs[2], resolving_funcs1[2]; + int i, res; + + promise = js_promise_resolve(ctx, ctx->promise_ctor, + 1, &value, 0); + if (JS_IsException(promise)) + goto fail; + + if (js_async_generator_resolve_function_create(ctx, JS_MKPTR(JS_TAG_OBJECT, s->generator), + resolving_funcs, FALSE)) { + JS_FreeValue(ctx, promise); + goto fail; + } + + /* Note: no need to create 'thrownawayCapability' as in + the spec */ + for(i = 0; i < 2; i++) + resolving_funcs1[i] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + (JSValueConst *)resolving_funcs, + (JSValueConst *)resolving_funcs1); + JS_FreeValue(ctx, promise); + for(i = 0; i < 2; i++) + JS_FreeValue(ctx, resolving_funcs[i]); + if (res) + goto fail; + return 0; + fail: + return -1; +} + +static void js_async_generator_resolve_or_reject(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst result, + int is_reject) +{ + JSAsyncGeneratorRequest *next; + JSValue ret; + + next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); + list_del(&next->link); + ret = JS_Call(ctx, next->resolving_funcs[is_reject], JS_UNDEFINED, 1, + &result); + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, next->result); + JS_FreeValue(ctx, next->promise); + JS_FreeValue(ctx, next->resolving_funcs[0]); + JS_FreeValue(ctx, next->resolving_funcs[1]); + js_free(ctx, next); +} + +static void js_async_generator_resolve(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst value, + BOOL done) +{ + JSValue result; + result = js_create_iterator_result(ctx, JS_DupValue(ctx, value), done); + /* XXX: better exception handling ? */ + js_async_generator_resolve_or_reject(ctx, s, result, 0); + JS_FreeValue(ctx, result); + } + +static void js_async_generator_reject(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst exception) +{ + js_async_generator_resolve_or_reject(ctx, s, exception, 1); +} + +static void js_async_generator_complete(JSContext *ctx, + JSAsyncGeneratorData *s) +{ + if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) { + s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; + async_func_free(ctx->rt, s->func_state); + s->func_state = NULL; + } +} + +static int js_async_generator_completed_return(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValueConst value) +{ + JSValue promise, resolving_funcs[2], resolving_funcs1[2]; + int res; + + // Can fail looking up JS_ATOM_constructor when is_reject==0. + promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, &value, + /*is_reject*/0); + // A poisoned .constructor property is observable and the resulting + // exception should be delivered to the catch handler. + if (JS_IsException(promise)) { + JSValue err = JS_GetException(ctx); + promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, (JSValueConst *)&err, + /*is_reject*/1); + JS_FreeValue(ctx, err); + if (JS_IsException(promise)) + return -1; + } + if (js_async_generator_resolve_function_create(ctx, + JS_MKPTR(JS_TAG_OBJECT, s->generator), + resolving_funcs1, + TRUE)) { + JS_FreeValue(ctx, promise); + return -1; + } + resolving_funcs[0] = JS_UNDEFINED; + resolving_funcs[1] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + (JSValueConst *)resolving_funcs1, + (JSValueConst *)resolving_funcs); + JS_FreeValue(ctx, resolving_funcs1[0]); + JS_FreeValue(ctx, resolving_funcs1[1]); + JS_FreeValue(ctx, promise); + return res; +} + +static void js_async_generator_resume_next(JSContext *ctx, + JSAsyncGeneratorData *s) +{ + JSAsyncGeneratorRequest *next; + JSValue func_ret, value; + + for(;;) { + if (list_empty(&s->queue)) + break; + next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); + switch(s->state) { + case JS_ASYNC_GENERATOR_STATE_EXECUTING: + /* only happens when restarting execution after await() */ + goto resume_exec; + case JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN: + goto done; + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_START: + if (next->completion_type == GEN_MAGIC_NEXT) { + goto exec_no_arg; + } else { + js_async_generator_complete(ctx, s); + } + break; + case JS_ASYNC_GENERATOR_STATE_COMPLETED: + if (next->completion_type == GEN_MAGIC_NEXT) { + js_async_generator_resolve(ctx, s, JS_UNDEFINED, TRUE); + } else if (next->completion_type == GEN_MAGIC_RETURN) { + s->state = JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN; + js_async_generator_completed_return(ctx, s, next->result); + } else { + js_async_generator_reject(ctx, s, next->result); + } + goto done; + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD: + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR: + value = JS_DupValue(ctx, next->result); + if (next->completion_type == GEN_MAGIC_THROW && + s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) { + JS_Throw(ctx, value); + s->func_state->throw_flag = TRUE; + } else { + /* 'yield' returns a value. 'yield *' also returns a value + in case the 'throw' method is called */ + s->func_state->frame.cur_sp[-1] = value; + s->func_state->frame.cur_sp[0] = + JS_NewInt32(ctx, next->completion_type); + s->func_state->frame.cur_sp++; + exec_no_arg: + s->func_state->throw_flag = FALSE; + } + s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING; + resume_exec: + func_ret = async_func_resume(ctx, s->func_state); + if (s->func_state->is_completed) { + if (JS_IsException(func_ret)) { + value = JS_GetException(ctx); + js_async_generator_complete(ctx, s); + js_async_generator_reject(ctx, s, value); + JS_FreeValue(ctx, value); + } else { + /* end of function */ + js_async_generator_complete(ctx, s); + js_async_generator_resolve(ctx, s, func_ret, TRUE); + JS_FreeValue(ctx, func_ret); + } + } else { + int func_ret_code, ret; + assert(JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT); + func_ret_code = JS_VALUE_GET_INT(func_ret); + value = s->func_state->frame.cur_sp[-1]; + s->func_state->frame.cur_sp[-1] = JS_UNDEFINED; + switch(func_ret_code) { + case FUNC_RET_YIELD: + case FUNC_RET_YIELD_STAR: + if (func_ret_code == FUNC_RET_YIELD_STAR) + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR; + else + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD; + js_async_generator_resolve(ctx, s, value, FALSE); + JS_FreeValue(ctx, value); + break; + case FUNC_RET_AWAIT: + ret = js_async_generator_await(ctx, s, value); + JS_FreeValue(ctx, value); + if (ret < 0) { + /* exception: throw it */ + s->func_state->throw_flag = TRUE; + goto resume_exec; + } + goto done; + default: + abort(); + } + } + break; + default: + abort(); + } + } + done: ; +} + +static JSValue js_async_generator_resolve_function(JSContext *ctx, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int magic, JSValue *func_data) +{ + BOOL is_reject = magic & 1; + JSAsyncGeneratorData *s = JS_GetOpaque(func_data[0], JS_CLASS_ASYNC_GENERATOR); + JSValueConst arg = argv[0]; + + /* XXX: what if s == NULL */ + + if (magic >= 2) { + /* resume next case in AWAITING_RETURN state */ + assert(s->state == JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN || + s->state == JS_ASYNC_GENERATOR_STATE_COMPLETED); + s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; + if (is_reject) { + js_async_generator_reject(ctx, s, arg); + } else { + js_async_generator_resolve(ctx, s, arg, TRUE); + } + } else { + /* restart function execution after await() */ + assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING); + s->func_state->throw_flag = is_reject; + if (is_reject) { + JS_Throw(ctx, JS_DupValue(ctx, arg)); + } else { + /* return value of await */ + s->func_state->frame.cur_sp[-1] = JS_DupValue(ctx, arg); + } + js_async_generator_resume_next(ctx, s); + } + return JS_UNDEFINED; +} + +/* magic = GEN_MAGIC_x */ +static JSValue js_async_generator_next(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, + int magic) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_GENERATOR); + JSValue promise, resolving_funcs[2]; + JSAsyncGeneratorRequest *req; + + promise = JS_NewPromiseCapability(ctx, resolving_funcs); + if (JS_IsException(promise)) + return JS_EXCEPTION; + if (!s) { + JSValue err, res2; + JS_ThrowTypeError(ctx, "not an AsyncGenerator object"); + err = JS_GetException(ctx); + res2 = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, + 1, (JSValueConst *)&err); + JS_FreeValue(ctx, err); + JS_FreeValue(ctx, res2); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return promise; + } + req = js_mallocz(ctx, sizeof(*req)); + if (!req) + goto fail; + req->completion_type = magic; + req->result = JS_DupValue(ctx, argv[0]); + req->promise = JS_DupValue(ctx, promise); + req->resolving_funcs[0] = resolving_funcs[0]; + req->resolving_funcs[1] = resolving_funcs[1]; + list_add_tail(&req->link, &s->queue); + if (s->state != JS_ASYNC_GENERATOR_STATE_EXECUTING) { + js_async_generator_resume_next(ctx, s); + } + return promise; + fail: + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + JS_FreeValue(ctx, promise); + return JS_EXCEPTION; +} + +static JSValue js_async_generator_function_call(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, + int argc, JSValueConst *argv, + int flags) +{ + JSValue obj, func_ret; + JSAsyncGeneratorData *s; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_START; + init_list_head(&s->queue); + s->func_state = async_func_init(ctx, func_obj, this_obj, argc, argv); + if (!s->func_state) + goto fail; + /* execute the function up to 'OP_initial_yield' (no yield nor + await are possible) */ + func_ret = async_func_resume(ctx, s->func_state); + if (JS_IsException(func_ret)) + goto fail; + JS_FreeValue(ctx, func_ret); + + obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_ASYNC_GENERATOR); + if (JS_IsException(obj)) + goto fail; + s->generator = JS_VALUE_GET_OBJ(obj); + JS_SetOpaque(obj, s); + return obj; + fail: + js_async_generator_free(ctx->rt, s); + return JS_EXCEPTION; +} + +/* JS parser */ + +enum { + TOK_NUMBER = -128, + TOK_STRING, + TOK_TEMPLATE, + TOK_IDENT, + TOK_REGEXP, + /* warning: order matters (see js_parse_assign_expr) */ + TOK_MUL_ASSIGN, + TOK_DIV_ASSIGN, + TOK_MOD_ASSIGN, + TOK_PLUS_ASSIGN, + TOK_MINUS_ASSIGN, + TOK_SHL_ASSIGN, + TOK_SAR_ASSIGN, + TOK_SHR_ASSIGN, + TOK_AND_ASSIGN, + TOK_XOR_ASSIGN, + TOK_OR_ASSIGN, +#ifdef CONFIG_BIGNUM + TOK_MATH_POW_ASSIGN, +#endif + TOK_POW_ASSIGN, + TOK_LAND_ASSIGN, + TOK_LOR_ASSIGN, + TOK_DOUBLE_QUESTION_MARK_ASSIGN, + TOK_DEC, + TOK_INC, + TOK_SHL, + TOK_SAR, + TOK_SHR, + TOK_LT, + TOK_LTE, + TOK_GT, + TOK_GTE, + TOK_EQ, + TOK_STRICT_EQ, + TOK_NEQ, + TOK_STRICT_NEQ, + TOK_LAND, + TOK_LOR, +#ifdef CONFIG_BIGNUM + TOK_MATH_POW, +#endif + TOK_POW, + TOK_ARROW, + TOK_ELLIPSIS, + TOK_DOUBLE_QUESTION_MARK, + TOK_QUESTION_MARK_DOT, + TOK_ERROR, + TOK_PRIVATE_NAME, + TOK_EOF, + /* keywords: WARNING: same order as atoms */ + TOK_NULL, /* must be first */ + TOK_FALSE, + TOK_TRUE, + TOK_IF, + TOK_ELSE, + TOK_RETURN, + TOK_VAR, + TOK_THIS, + TOK_DELETE, + TOK_VOID, + TOK_TYPEOF, + TOK_NEW, + TOK_IN, + TOK_INSTANCEOF, + TOK_DO, + TOK_WHILE, + TOK_FOR, + TOK_BREAK, + TOK_CONTINUE, + TOK_SWITCH, + TOK_CASE, + TOK_DEFAULT, + TOK_THROW, + TOK_TRY, + TOK_CATCH, + TOK_FINALLY, + TOK_FUNCTION, + TOK_DEBUGGER, + TOK_WITH, + /* FutureReservedWord */ + TOK_CLASS, + TOK_CONST, + TOK_ENUM, + TOK_EXPORT, + TOK_EXTENDS, + TOK_IMPORT, + TOK_SUPER, + /* FutureReservedWords when parsing strict mode code */ + TOK_IMPLEMENTS, + TOK_INTERFACE, + TOK_LET, + TOK_PACKAGE, + TOK_PRIVATE, + TOK_PROTECTED, + TOK_PUBLIC, + TOK_STATIC, + TOK_YIELD, + TOK_AWAIT, /* must be last */ + TOK_OF, /* only used for js_parse_skip_parens_token() */ +}; + +#define TOK_FIRST_KEYWORD TOK_NULL +#define TOK_LAST_KEYWORD TOK_AWAIT + +/* unicode code points */ +#define CP_NBSP 0x00a0 +#define CP_BOM 0xfeff + +#define CP_LS 0x2028 +#define CP_PS 0x2029 + +typedef struct BlockEnv { + struct BlockEnv *prev; + JSAtom label_name; /* JS_ATOM_NULL if none */ + int label_break; /* -1 if none */ + int label_cont; /* -1 if none */ + int drop_count; /* number of stack elements to drop */ + int label_finally; /* -1 if none */ + int scope_level; + int has_iterator; +} BlockEnv; + +typedef struct JSGlobalVar { + int cpool_idx; /* if >= 0, index in the constant pool for hoisted + function defintion*/ + uint8_t force_init : 1; /* force initialization to undefined */ + uint8_t is_lexical : 1; /* global let/const definition */ + uint8_t is_const : 1; /* const definition */ + int scope_level; /* scope of definition */ + JSAtom var_name; /* variable name */ +} JSGlobalVar; + +typedef struct RelocEntry { + struct RelocEntry *next; + uint32_t addr; /* address to patch */ + int size; /* address size: 1, 2 or 4 bytes */ +} RelocEntry; + +typedef struct JumpSlot { + int op; + int size; + int pos; + int label; +} JumpSlot; + +typedef struct LabelSlot { + int ref_count; + int pos; /* phase 1 address, -1 means not resolved yet */ + int pos2; /* phase 2 address, -1 means not resolved yet */ + int addr; /* phase 3 address, -1 means not resolved yet */ + RelocEntry *first_reloc; +} LabelSlot; + +typedef struct LineNumberSlot { + uint32_t pc; + int line_num; +} LineNumberSlot; + +typedef enum JSParseFunctionEnum { + JS_PARSE_FUNC_STATEMENT, + JS_PARSE_FUNC_VAR, + JS_PARSE_FUNC_EXPR, + JS_PARSE_FUNC_ARROW, + JS_PARSE_FUNC_GETTER, + JS_PARSE_FUNC_SETTER, + JS_PARSE_FUNC_METHOD, + JS_PARSE_FUNC_CLASS_STATIC_INIT, + JS_PARSE_FUNC_CLASS_CONSTRUCTOR, + JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR, +} JSParseFunctionEnum; + +typedef enum JSParseExportEnum { + JS_PARSE_EXPORT_NONE, + JS_PARSE_EXPORT_NAMED, + JS_PARSE_EXPORT_DEFAULT, +} JSParseExportEnum; + +typedef struct JSFunctionDef { + JSContext *ctx; + struct JSFunctionDef *parent; + int parent_cpool_idx; /* index in the constant pool of the parent + or -1 if none */ + int parent_scope_level; /* scope level in parent at point of definition */ + struct list_head child_list; /* list of JSFunctionDef.link */ + struct list_head link; + + BOOL is_eval; /* TRUE if eval code */ + int eval_type; /* only valid if is_eval = TRUE */ + BOOL is_global_var; /* TRUE if variables are not defined locally: + eval global, eval module or non strict eval */ + BOOL is_func_expr; /* TRUE if function expression */ + BOOL has_home_object; /* TRUE if the home object is available */ + BOOL has_prototype; /* true if a prototype field is necessary */ + BOOL has_simple_parameter_list; + BOOL has_parameter_expressions; /* if true, an argument scope is created */ + BOOL has_use_strict; /* to reject directive in special cases */ + BOOL has_eval_call; /* true if the function contains a call to eval() */ + BOOL has_arguments_binding; /* true if the 'arguments' binding is + available in the function */ + BOOL has_this_binding; /* true if the 'this' and new.target binding are + available in the function */ + BOOL new_target_allowed; /* true if the 'new.target' does not + throw a syntax error */ + BOOL super_call_allowed; /* true if super() is allowed */ + BOOL super_allowed; /* true if super. or super[] is allowed */ + BOOL arguments_allowed; /* true if the 'arguments' identifier is allowed */ + BOOL is_derived_class_constructor; + BOOL in_function_body; + BOOL backtrace_barrier; + JSFunctionKindEnum func_kind : 8; + JSParseFunctionEnum func_type : 8; + uint8_t js_mode; /* bitmap of JS_MODE_x */ + JSAtom func_name; /* JS_ATOM_NULL if no name */ + + JSVarDef *vars; + int var_size; /* allocated size for vars[] */ + int var_count; + JSVarDef *args; + int arg_size; /* allocated size for args[] */ + int arg_count; /* number of arguments */ + int defined_arg_count; + int var_object_idx; /* -1 if none */ + int arg_var_object_idx; /* -1 if none (var object for the argument scope) */ + int arguments_var_idx; /* -1 if none */ + int arguments_arg_idx; /* argument variable definition in argument scope, + -1 if none */ + int func_var_idx; /* variable containing the current function (-1 + if none, only used if is_func_expr is true) */ + int eval_ret_idx; /* variable containing the return value of the eval, -1 if none */ + int this_var_idx; /* variable containg the 'this' value, -1 if none */ + int new_target_var_idx; /* variable containg the 'new.target' value, -1 if none */ + int this_active_func_var_idx; /* variable containg the 'this.active_func' value, -1 if none */ + int home_object_var_idx; + BOOL need_home_object; + + int scope_level; /* index into fd->scopes if the current lexical scope */ + int scope_first; /* index into vd->vars of first lexically scoped variable */ + int scope_size; /* allocated size of fd->scopes array */ + int scope_count; /* number of entries used in the fd->scopes array */ + JSVarScope *scopes; + JSVarScope def_scope_array[4]; + int body_scope; /* scope of the body of the function or eval */ + + int global_var_count; + int global_var_size; + JSGlobalVar *global_vars; + + DynBuf byte_code; + int last_opcode_pos; /* -1 if no last opcode */ + int last_opcode_line_num; + BOOL use_short_opcodes; /* true if short opcodes are used in byte_code */ + + LabelSlot *label_slots; + int label_size; /* allocated size for label_slots[] */ + int label_count; + BlockEnv *top_break; /* break/continue label stack */ + + /* constant pool (strings, functions, numbers) */ + JSValue *cpool; + int cpool_count; + int cpool_size; + + /* list of variables in the closure */ + int closure_var_count; + int closure_var_size; + JSClosureVar *closure_var; + + JumpSlot *jump_slots; + int jump_size; + int jump_count; + + LineNumberSlot *line_number_slots; + int line_number_size; + int line_number_count; + int line_number_last; + int line_number_last_pc; + + /* pc2line table */ + JSAtom filename; + int line_num; + DynBuf pc2line; + + char *source; /* raw source, utf-8 encoded */ + int source_len; + + JSModuleDef *module; /* != NULL when parsing a module */ + BOOL has_await; /* TRUE if await is used (used in module eval) */ +} JSFunctionDef; + +typedef struct JSToken { + int val; + int line_num; /* line number of token start */ + const uint8_t *ptr; + union { + struct { + JSValue str; + int sep; + } str; + struct { + JSValue val; +#ifdef CONFIG_BIGNUM + slimb_t exponent; /* may be != 0 only if val is a float */ +#endif + } num; + struct { + JSAtom atom; + BOOL has_escape; + BOOL is_reserved; + } ident; + struct { + JSValue body; + JSValue flags; + } regexp; + } u; +} JSToken; + +typedef struct JSParseState { + JSContext *ctx; + int last_line_num; /* line number of last token */ + int line_num; /* line number of current offset */ + const char *filename; + JSToken token; + BOOL got_lf; /* true if got line feed before the current token */ + const uint8_t *last_ptr; + const uint8_t *buf_ptr; + const uint8_t *buf_end; + + /* current function code */ + JSFunctionDef *cur_func; + BOOL is_module; /* parsing a module */ + BOOL allow_html_comments; + BOOL ext_json; /* true if accepting JSON superset */ +} JSParseState; + +typedef struct JSOpCode { +#ifdef DUMP_BYTECODE + const char *name; +#endif + uint8_t size; /* in bytes */ + /* the opcodes remove n_pop items from the top of the stack, then + pushes n_push items */ + uint8_t n_pop; + uint8_t n_push; + uint8_t fmt; +} JSOpCode; + +static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = { +#define FMT(f) +#ifdef DUMP_BYTECODE +#define DEF(id, size, n_pop, n_push, f) { #id, size, n_pop, n_push, OP_FMT_ ## f }, +#else +#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_ ## f }, +#endif +#include "quickjs-opcode.h" +#undef DEF +#undef FMT +}; + +#if SHORT_OPCODES +/* After the final compilation pass, short opcodes are used. Their + opcodes overlap with the temporary opcodes which cannot appear in + the final bytecode. Their description is after the temporary + opcodes in opcode_info[]. */ +#define short_opcode_info(op) \ + opcode_info[(op) >= OP_TEMP_START ? \ + (op) + (OP_TEMP_END - OP_TEMP_START) : (op)] +#else +#define short_opcode_info(op) opcode_info[op] +#endif + +static __exception int next_token(JSParseState *s); + +static void free_token(JSParseState *s, JSToken *token) +{ + switch(token->val) { + case TOK_NUMBER: + JS_FreeValue(s->ctx, token->u.num.val); + break; + case TOK_STRING: + case TOK_TEMPLATE: + JS_FreeValue(s->ctx, token->u.str.str); + break; + case TOK_REGEXP: + JS_FreeValue(s->ctx, token->u.regexp.body); + JS_FreeValue(s->ctx, token->u.regexp.flags); + break; + case TOK_IDENT: + case TOK_PRIVATE_NAME: + JS_FreeAtom(s->ctx, token->u.ident.atom); + break; + default: + if (token->val >= TOK_FIRST_KEYWORD && + token->val <= TOK_LAST_KEYWORD) { + JS_FreeAtom(s->ctx, token->u.ident.atom); + } + break; + } +} + +static void __attribute((unused)) dump_token(JSParseState *s, + const JSToken *token) +{ + switch(token->val) { + case TOK_NUMBER: + { + double d; + JS_ToFloat64(s->ctx, &d, token->u.num.val); /* no exception possible */ + printf("number: %.14g\n", d); + } + break; + case TOK_IDENT: + dump_atom: + { + char buf[ATOM_GET_STR_BUF_SIZE]; + printf("ident: '%s'\n", + JS_AtomGetStr(s->ctx, buf, sizeof(buf), token->u.ident.atom)); + } + break; + case TOK_STRING: + { + const char *str; + /* XXX: quote the string */ + str = JS_ToCString(s->ctx, token->u.str.str); + printf("string: '%s'\n", str); + JS_FreeCString(s->ctx, str); + } + break; + case TOK_TEMPLATE: + { + const char *str; + str = JS_ToCString(s->ctx, token->u.str.str); + printf("template: `%s`\n", str); + JS_FreeCString(s->ctx, str); + } + break; + case TOK_REGEXP: + { + const char *str, *str2; + str = JS_ToCString(s->ctx, token->u.regexp.body); + str2 = JS_ToCString(s->ctx, token->u.regexp.flags); + printf("regexp: '%s' '%s'\n", str, str2); + JS_FreeCString(s->ctx, str); + JS_FreeCString(s->ctx, str2); + } + break; + case TOK_EOF: + printf("eof\n"); + break; + default: + if (s->token.val >= TOK_NULL && s->token.val <= TOK_LAST_KEYWORD) { + goto dump_atom; + } else if (s->token.val >= 256) { + printf("token: %d\n", token->val); + } else { + printf("token: '%c'\n", token->val); + } + break; + } +} + +int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const char *fmt, ...) +{ + JSContext *ctx = s->ctx; + va_list ap; + int backtrace_flags; + + va_start(ap, fmt); + JS_ThrowError2(ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE); + va_end(ap); + backtrace_flags = 0; + if (s->cur_func && s->cur_func->backtrace_barrier) + backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL; + build_backtrace(ctx, ctx->rt->current_exception, s->filename, s->line_num, + backtrace_flags); + return -1; +} + +static int js_parse_expect(JSParseState *s, int tok) +{ + if (s->token.val != tok) { + /* XXX: dump token correctly in all cases */ + return js_parse_error(s, "expecting '%c'", tok); + } + return next_token(s); +} + +static int js_parse_expect_semi(JSParseState *s) +{ + if (s->token.val != ';') { + /* automatic insertion of ';' */ + if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf) { + return 0; + } + return js_parse_error(s, "expecting '%c'", ';'); + } + return next_token(s); +} + +static int js_parse_error_reserved_identifier(JSParseState *s) +{ + char buf1[ATOM_GET_STR_BUF_SIZE]; + return js_parse_error(s, "'%s' is a reserved identifier", + JS_AtomGetStr(s->ctx, buf1, sizeof(buf1), + s->token.u.ident.atom)); +} + +static __exception int js_parse_template_part(JSParseState *s, const uint8_t *p) +{ + uint32_t c; + StringBuffer b_s, *b = &b_s; + + /* p points to the first byte of the template part */ + if (string_buffer_init(s->ctx, b, 32)) + goto fail; + for(;;) { + if (p >= s->buf_end) + goto unexpected_eof; + c = *p++; + if (c == '`') { + /* template end part */ + break; + } + if (c == '$' && *p == '{') { + /* template start or middle part */ + p++; + break; + } + if (c == '\\') { + if (string_buffer_putc8(b, c)) + goto fail; + if (p >= s->buf_end) + goto unexpected_eof; + c = *p++; + } + /* newline sequences are normalized as single '\n' bytes */ + if (c == '\r') { + if (*p == '\n') + p++; + c = '\n'; + } + if (c == '\n') { + s->line_num++; + } else if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + js_parse_error(s, "invalid UTF-8 sequence"); + goto fail; + } + p = p_next; + } + if (string_buffer_putc(b, c)) + goto fail; + } + s->token.val = TOK_TEMPLATE; + s->token.u.str.sep = c; + s->token.u.str.str = string_buffer_end(b); + s->buf_ptr = p; + return 0; + + unexpected_eof: + js_parse_error(s, "unexpected end of string"); + fail: + string_buffer_free(b); + return -1; +} + +static __exception int js_parse_string(JSParseState *s, int sep, + BOOL do_throw, const uint8_t *p, + JSToken *token, const uint8_t **pp) +{ + int ret; + uint32_t c; + StringBuffer b_s, *b = &b_s; + + /* string */ + if (string_buffer_init(s->ctx, b, 32)) + goto fail; + for(;;) { + if (p >= s->buf_end) + goto invalid_char; + c = *p; + if (c < 0x20) { + if (!s->cur_func) { + if (do_throw) + js_parse_error(s, "invalid character in a JSON string"); + goto fail; + } + if (sep == '`') { + if (c == '\r') { + if (p[1] == '\n') + p++; + c = '\n'; + } + /* do not update s->line_num */ + } else if (c == '\n' || c == '\r') + goto invalid_char; + } + p++; + if (c == sep) + break; + if (c == '$' && *p == '{' && sep == '`') { + /* template start or middle part */ + p++; + break; + } + if (c == '\\') { + c = *p; + /* XXX: need a specific JSON case to avoid + accepting invalid escapes */ + switch(c) { + case '\0': + if (p >= s->buf_end) + goto invalid_char; + p++; + break; + case '\'': + case '\"': + case '\\': + p++; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { + p++; + } + /* fall thru */ + case '\n': + /* ignore escaped newline sequence */ + p++; + if (sep != '`') + s->line_num++; + continue; + default: + if (c >= '0' && c <= '9') { + if (!s->cur_func) + goto invalid_escape; /* JSON case */ + if (!(s->cur_func->js_mode & JS_MODE_STRICT) && sep != '`') + goto parse_escape; + if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) { + p++; + c = '\0'; + } else { + if (c >= '8' || sep == '`') { + /* Note: according to ES2021, \8 and \9 are not + accepted in strict mode or in templates. */ + goto invalid_escape; + } else { + if (do_throw) + js_parse_error(s, "octal escape sequences are not allowed in strict mode"); + } + goto fail; + } + } else if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + goto invalid_utf8; + } + p = p_next; + /* LS or PS are skipped */ + if (c == CP_LS || c == CP_PS) + continue; + } else { + parse_escape: + ret = lre_parse_escape(&p, TRUE); + if (ret == -1) { + invalid_escape: + if (do_throw) + js_parse_error(s, "malformed escape sequence in string literal"); + goto fail; + } else if (ret < 0) { + /* ignore the '\' (could output a warning) */ + p++; + } else { + c = ret; + } + } + break; + } + } else if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) + goto invalid_utf8; + p = p_next; + } + if (string_buffer_putc(b, c)) + goto fail; + } + token->val = TOK_STRING; + token->u.str.sep = c; + token->u.str.str = string_buffer_end(b); + *pp = p; + return 0; + + invalid_utf8: + if (do_throw) + js_parse_error(s, "invalid UTF-8 sequence"); + goto fail; + invalid_char: + if (do_throw) + js_parse_error(s, "unexpected end of string"); + fail: + string_buffer_free(b); + return -1; +} + +static inline BOOL token_is_pseudo_keyword(JSParseState *s, JSAtom atom) { + return s->token.val == TOK_IDENT && s->token.u.ident.atom == atom && + !s->token.u.ident.has_escape; +} + +static __exception int js_parse_regexp(JSParseState *s) +{ + const uint8_t *p; + BOOL in_class; + StringBuffer b_s, *b = &b_s; + StringBuffer b2_s, *b2 = &b2_s; + uint32_t c; + + p = s->buf_ptr; + p++; + in_class = FALSE; + if (string_buffer_init(s->ctx, b, 32)) + return -1; + if (string_buffer_init(s->ctx, b2, 1)) + goto fail; + for(;;) { + if (p >= s->buf_end) { + eof_error: + js_parse_error(s, "unexpected end of regexp"); + goto fail; + } + c = *p++; + if (c == '\n' || c == '\r') { + goto eol_error; + } else if (c == '/') { + if (!in_class) + break; + } else if (c == '[') { + in_class = TRUE; + } else if (c == ']') { + /* XXX: incorrect as the first character in a class */ + in_class = FALSE; + } else if (c == '\\') { + if (string_buffer_putc8(b, c)) + goto fail; + c = *p++; + if (c == '\n' || c == '\r') + goto eol_error; + else if (c == '\0' && p >= s->buf_end) + goto eof_error; + else if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + goto invalid_utf8; + } + p = p_next; + if (c == CP_LS || c == CP_PS) + goto eol_error; + } + } else if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8(p - 1, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + invalid_utf8: + js_parse_error(s, "invalid UTF-8 sequence"); + goto fail; + } + p = p_next; + /* LS or PS are considered as line terminator */ + if (c == CP_LS || c == CP_PS) { + eol_error: + js_parse_error(s, "unexpected line terminator in regexp"); + goto fail; + } + } + if (string_buffer_putc(b, c)) + goto fail; + } + + /* flags */ + for(;;) { + const uint8_t *p_next = p; + c = *p_next++; + if (c >= 0x80) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + goto invalid_utf8; + } + } + if (!lre_js_is_ident_next(c)) + break; + if (string_buffer_putc(b2, c)) + goto fail; + p = p_next; + } + + s->token.val = TOK_REGEXP; + s->token.u.regexp.body = string_buffer_end(b); + s->token.u.regexp.flags = string_buffer_end(b2); + s->buf_ptr = p; + return 0; + fail: + string_buffer_free(b); + string_buffer_free(b2); + return -1; +} + +static __exception int ident_realloc(JSContext *ctx, char **pbuf, size_t *psize, + char *static_buf) +{ + char *buf, *new_buf; + size_t size, new_size; + + buf = *pbuf; + size = *psize; + if (size >= (SIZE_MAX / 3) * 2) + new_size = SIZE_MAX; + else + new_size = size + (size >> 1); + if (buf == static_buf) { + new_buf = js_malloc(ctx, new_size); + if (!new_buf) + return -1; + memcpy(new_buf, buf, size); + } else { + new_buf = js_realloc(ctx, buf, new_size); + if (!new_buf) + return -1; + } + *pbuf = new_buf; + *psize = new_size; + return 0; +} + +/* convert a TOK_IDENT to a keyword when needed */ +static void update_token_ident(JSParseState *s) +{ + if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD || + (s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD && + (s->cur_func->js_mode & JS_MODE_STRICT)) || + (s->token.u.ident.atom == JS_ATOM_yield && + ((s->cur_func->func_kind & JS_FUNC_GENERATOR) || + (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && + !s->cur_func->in_function_body && s->cur_func->parent && + (s->cur_func->parent->func_kind & JS_FUNC_GENERATOR)))) || + (s->token.u.ident.atom == JS_ATOM_await && + (s->is_module || + (s->cur_func->func_kind & JS_FUNC_ASYNC) || + s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT || + (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && + !s->cur_func->in_function_body && s->cur_func->parent && + ((s->cur_func->parent->func_kind & JS_FUNC_ASYNC) || + s->cur_func->parent->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT))))) { + if (s->token.u.ident.has_escape) { + s->token.u.ident.is_reserved = TRUE; + s->token.val = TOK_IDENT; + } else { + /* The keywords atoms are pre allocated */ + s->token.val = s->token.u.ident.atom - 1 + TOK_FIRST_KEYWORD; + } + } +} + +/* if the current token is an identifier or keyword, reparse it + according to the current function type */ +static void reparse_ident_token(JSParseState *s) +{ + if (s->token.val == TOK_IDENT || + (s->token.val >= TOK_FIRST_KEYWORD && + s->token.val <= TOK_LAST_KEYWORD)) { + s->token.val = TOK_IDENT; + s->token.u.ident.is_reserved = FALSE; + update_token_ident(s); + } +} + +/* 'c' is the first character. Return JS_ATOM_NULL in case of error */ +static JSAtom parse_ident(JSParseState *s, const uint8_t **pp, + BOOL *pident_has_escape, int c, BOOL is_private) +{ + const uint8_t *p, *p1; + char ident_buf[128], *buf; + size_t ident_size, ident_pos; + JSAtom atom; + + p = *pp; + buf = ident_buf; + ident_size = sizeof(ident_buf); + ident_pos = 0; + if (is_private) + buf[ident_pos++] = '#'; + for(;;) { + p1 = p; + + if (c < 128) { + buf[ident_pos++] = c; + } else { + ident_pos += unicode_to_utf8((uint8_t*)buf + ident_pos, c); + } + c = *p1++; + if (c == '\\' && *p1 == 'u') { + c = lre_parse_escape(&p1, TRUE); + *pident_has_escape = TRUE; + } else if (c >= 128) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1); + } + if (!lre_js_is_ident_next(c)) + break; + p = p1; + if (unlikely(ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { + if (ident_realloc(s->ctx, &buf, &ident_size, ident_buf)) { + atom = JS_ATOM_NULL; + goto done; + } + } + } + atom = JS_NewAtomLen(s->ctx, buf, ident_pos); + done: + if (unlikely(buf != ident_buf)) + js_free(s->ctx, buf); + *pp = p; + return atom; +} + + +static __exception int next_token(JSParseState *s) +{ + const uint8_t *p; + int c; + BOOL ident_has_escape; + JSAtom atom; + + if (js_check_stack_overflow(s->ctx->rt, 0)) { + return js_parse_error(s, "stack overflow"); + } + + free_token(s, &s->token); + + p = s->last_ptr = s->buf_ptr; + s->got_lf = FALSE; + s->last_line_num = s->token.line_num; + redo: + s->token.line_num = s->line_num; + s->token.ptr = p; + c = *p; + switch(c) { + case 0: + if (p >= s->buf_end) { + s->token.val = TOK_EOF; + } else { + goto def_token; + } + break; + case '`': + if (js_parse_template_part(s, p + 1)) + goto fail; + p = s->buf_ptr; + break; + case '\'': + case '\"': + if (js_parse_string(s, c, TRUE, p + 1, &s->token, &p)) + goto fail; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { + p++; + } + /* fall thru */ + case '\n': + p++; + line_terminator: + s->got_lf = TRUE; + s->line_num++; + goto redo; + case '\f': + case '\v': + case ' ': + case '\t': + p++; + goto redo; + case '/': + if (p[1] == '*') { + /* comment */ + p += 2; + for(;;) { + if (*p == '\0' && p >= s->buf_end) { + js_parse_error(s, "unexpected end of comment"); + goto fail; + } + if (p[0] == '*' && p[1] == '/') { + p += 2; + break; + } + if (*p == '\n') { + s->line_num++; + s->got_lf = TRUE; /* considered as LF for ASI */ + p++; + } else if (*p == '\r') { + s->got_lf = TRUE; /* considered as LF for ASI */ + p++; + } else if (*p >= 0x80) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); + if (c == CP_LS || c == CP_PS) { + s->got_lf = TRUE; /* considered as LF for ASI */ + } else if (c == -1) { + p++; /* skip invalid UTF-8 */ + } + } else { + p++; + } + } + goto redo; + } else if (p[1] == '/') { + /* line comment */ + p += 2; + skip_line_comment: + for(;;) { + if (*p == '\0' && p >= s->buf_end) + break; + if (*p == '\r' || *p == '\n') + break; + if (*p >= 0x80) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); + /* LS or PS are considered as line terminator */ + if (c == CP_LS || c == CP_PS) { + break; + } else if (c == -1) { + p++; /* skip invalid UTF-8 */ + } + } else { + p++; + } + } + goto redo; + } else if (p[1] == '=') { + p += 2; + s->token.val = TOK_DIV_ASSIGN; + } else { + p++; + s->token.val = c; + } + break; + case '\\': + if (p[1] == 'u') { + const uint8_t *p1 = p + 1; + int c1 = lre_parse_escape(&p1, TRUE); + if (c1 >= 0 && lre_js_is_ident_first(c1)) { + c = c1; + p = p1; + ident_has_escape = TRUE; + goto has_ident; + } else { + /* XXX: syntax error? */ + } + } + goto def_token; + case 'a': case 'b': case 'c': case 'd': + case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': + case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case '$': + /* identifier */ + p++; + ident_has_escape = FALSE; + has_ident: + atom = parse_ident(s, &p, &ident_has_escape, c, FALSE); + if (atom == JS_ATOM_NULL) + goto fail; + s->token.u.ident.atom = atom; + s->token.u.ident.has_escape = ident_has_escape; + s->token.u.ident.is_reserved = FALSE; + s->token.val = TOK_IDENT; + update_token_ident(s); + break; + case '#': + /* private name */ + { + const uint8_t *p1; + p++; + p1 = p; + c = *p1++; + if (c == '\\' && *p1 == 'u') { + c = lre_parse_escape(&p1, TRUE); + } else if (c >= 128) { + c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1); + } + if (!lre_js_is_ident_first(c)) { + js_parse_error(s, "invalid first character of private name"); + goto fail; + } + p = p1; + ident_has_escape = FALSE; /* not used */ + atom = parse_ident(s, &p, &ident_has_escape, c, TRUE); + if (atom == JS_ATOM_NULL) + goto fail; + s->token.u.ident.atom = atom; + s->token.val = TOK_PRIVATE_NAME; + } + break; + case '.': + if (p[1] == '.' && p[2] == '.') { + p += 3; + s->token.val = TOK_ELLIPSIS; + break; + } + if (p[1] >= '0' && p[1] <= '9') { + goto parse_number; + } else { + goto def_token; + } + break; + case '0': + /* in strict mode, octal literals are not accepted */ + if (is_digit(p[1]) && (s->cur_func->js_mode & JS_MODE_STRICT)) { + js_parse_error(s, "octal literals are deprecated in strict mode"); + goto fail; + } + goto parse_number; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + /* number */ + parse_number: + { + JSValue ret; + const uint8_t *p1; + int flags, radix; + flags = ATOD_ACCEPT_BIN_OCT | ATOD_ACCEPT_LEGACY_OCTAL | + ATOD_ACCEPT_UNDERSCORES; + flags |= ATOD_ACCEPT_SUFFIX; +#ifdef CONFIG_BIGNUM + if (s->cur_func->js_mode & JS_MODE_MATH) { + flags |= ATOD_MODE_BIGINT; + if (s->cur_func->js_mode & JS_MODE_MATH) + flags |= ATOD_TYPE_BIG_FLOAT; + } +#endif + radix = 0; +#ifdef CONFIG_BIGNUM + s->token.u.num.exponent = 0; + ret = js_atof2(s->ctx, (const char *)p, (const char **)&p, radix, + flags, &s->token.u.num.exponent); +#else + ret = js_atof(s->ctx, (const char *)p, (const char **)&p, radix, + flags); +#endif + if (JS_IsException(ret)) + goto fail; + /* reject `10instanceof Number` */ + if (JS_VALUE_IS_NAN(ret) || + lre_js_is_ident_next(unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1))) { + JS_FreeValue(s->ctx, ret); + js_parse_error(s, "invalid number literal"); + goto fail; + } + s->token.val = TOK_NUMBER; + s->token.u.num.val = ret; + } + break; + case '*': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MUL_ASSIGN; + } else if (p[1] == '*') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_POW_ASSIGN; + } else { + p += 2; + s->token.val = TOK_POW; + } + } else { + goto def_token; + } + break; + case '%': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MOD_ASSIGN; + } else { + goto def_token; + } + break; + case '+': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_PLUS_ASSIGN; + } else if (p[1] == '+') { + p += 2; + s->token.val = TOK_INC; + } else { + goto def_token; + } + break; + case '-': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MINUS_ASSIGN; + } else if (p[1] == '-') { + if (s->allow_html_comments && + p[2] == '>' && s->last_line_num != s->line_num) { + /* Annex B: `-->` at beginning of line is an html comment end. + It extends to the end of the line. + */ + goto skip_line_comment; + } + p += 2; + s->token.val = TOK_DEC; + } else { + goto def_token; + } + break; + case '<': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_LTE; + } else if (p[1] == '<') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_SHL_ASSIGN; + } else { + p += 2; + s->token.val = TOK_SHL; + } + } else if (s->allow_html_comments && + p[1] == '!' && p[2] == '-' && p[3] == '-') { + /* Annex B: handle ` 0;) { + } + return n; +} + +function empty_do_loop(n) { + var j = n; + do { } while (--j > 0); + return n; +} + +function date_now(n) { + var j; + for(j = 0; j < n; j++) { + Date.now(); + } + return n; +} + +function date_parse(n) { + var x0 = 0, dx = 0; + var j; + for(j = 0; j < n; j++) { + var x1 = x0 - x0 % 1000; + var x2 = -x0; + var x3 = -x1; + var d0 = new Date(x0); + var d1 = new Date(x1); + var d2 = new Date(x2); + var d3 = new Date(x3); + if (Date.parse(d0.toISOString()) != x0 + || Date.parse(d1.toGMTString()) != x1 + || Date.parse(d1.toString()) != x1 + || Date.parse(d2.toISOString()) != x2 + || Date.parse(d3.toGMTString()) != x3 + || Date.parse(d3.toString()) != x3) { + console.log("Date.parse error for " + x0); + return -1; + } + dx = (dx * 1.1 + 1) >> 0; + x0 = (x0 + dx) % 8.64e15; + } + return n * 6; +} + +function prop_read(n) +{ + var obj, sum, j; + obj = {a: 1, b: 2, c:3, d:4 }; + sum = 0; + for(j = 0; j < n; j++) { + sum += obj.a; + sum += obj.b; + sum += obj.c; + sum += obj.d; + } + global_res = sum; + return n * 4; +} + +function prop_write(n) +{ + var obj, j; + obj = {a: 1, b: 2, c:3, d:4 }; + for(j = 0; j < n; j++) { + obj.a = j; + obj.b = j; + obj.c = j; + obj.d = j; + } + return n * 4; +} + +function prop_update(n) +{ + var obj, j; + obj = {a: 1, b: 2, c:3, d:4 }; + for(j = 0; j < n; j++) { + obj.a += j; + obj.b += j; + obj.c += j; + obj.d += j; + } + return n * 4; +} + +function prop_create(n) +{ + var obj, i, j; + for(j = 0; j < n; j++) { + obj = {}; + obj.a = 1; + obj.b = 2; + obj.c = 3; + obj.d = 4; + obj.e = 5; + obj.f = 6; + obj.g = 7; + obj.h = 8; + obj.i = 9; + obj.j = 10; + for(i = 0; i < 10; i++) { + obj[i] = i; + } + } + return n * 20; +} + +function prop_clone(n) +{ + var ref, obj, j, k; + ref = { a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9, j:10 }; + for(k = 0; k < 10; k++) { + ref[k] = k; + } + for (j = 0; j < n; j++) { + global_res = { ...ref }; + } + return n * 20; +} + +function prop_delete(n) +{ + var ref, obj, j, k; + ref = { a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9, j:10 }; + for(k = 0; k < 10; k++) { + ref[k] = k; + } + for (j = 0; j < n; j++) { + obj = { ...ref }; + delete obj.a; + delete obj.b; + delete obj.c; + delete obj.d; + delete obj.e; + delete obj.f; + delete obj.g; + delete obj.h; + delete obj.i; + delete obj.j; + for(k = 0; k < 10; k++) { + delete obj[k]; + } + } + return n * 20; +} + +function array_read(n) +{ + var tab, len, sum, i, j; + tab = []; + len = 10; + for(i = 0; i < len; i++) + tab[i] = i; + sum = 0; + for(j = 0; j < n; j++) { + sum += tab[0]; + sum += tab[1]; + sum += tab[2]; + sum += tab[3]; + sum += tab[4]; + sum += tab[5]; + sum += tab[6]; + sum += tab[7]; + sum += tab[8]; + sum += tab[9]; + } + global_res = sum; + return len * n; +} + +function array_write(n) +{ + var tab, len, i, j; + tab = []; + len = 10; + for(i = 0; i < len; i++) + tab[i] = i; + for(j = 0; j < n; j++) { + tab[0] = j; + tab[1] = j; + tab[2] = j; + tab[3] = j; + tab[4] = j; + tab[5] = j; + tab[6] = j; + tab[7] = j; + tab[8] = j; + tab[9] = j; + } + return len * n; +} + +function array_prop_create(n) +{ + var tab, i, j, len; + len = 1000; + for(j = 0; j < n; j++) { + tab = []; + for(i = 0; i < len; i++) + tab[i] = i; + } + return len * n; +} + +function array_slice(n) +{ + var ref, a, i, j, len; + len = 1000; + ref = []; + for(i = 0; i < len; i++) + ref[i] = i; + for(j = 0; j < n; j++) { + ref[0] = j; + a = ref.slice(); + a[0] = 0; + global_res = a; + } + return len * n; +} + +function array_length_decr(n) +{ + var tab, ref, i, j, len; + len = 1000; + ref = []; + for(i = 0; i < len; i++) + ref[i] = i; + for(j = 0; j < n; j++) { + tab = ref.slice(); + for(i = len; i --> 0;) + tab.length = i; + } + return len * n; +} + +function array_hole_length_decr(n) +{ + var tab, ref, i, j, len; + len = 1000; + ref = []; + for(i = 0; i < len; i++) { + if (i % 10 == 9) + ref[i] = i; + } + for(j = 0; j < n; j++) { + tab = ref.slice(); + for(i = len; i --> 0;) + tab.length = i; + } + return len * n; +} + +function array_push(n) +{ + var tab, i, j, len; + len = 500; + for(j = 0; j < n; j++) { + tab = []; + for(i = 0; i < len; i++) + tab.push(i); + } + return len * n; +} + +function array_pop(n) +{ + var tab, ref, i, j, len, sum; + len = 500; + ref = []; + for(i = 0; i < len; i++) + ref[i] = i; + for(j = 0; j < n; j++) { + tab = ref.slice(); + sum = 0; + for(i = 0; i < len; i++) + sum += tab.pop(); + global_res = sum; + } + return len * n; +} + +function typed_array_read(n) +{ + var tab, len, sum, i, j; + len = 10; + tab = new Int32Array(len); + for(i = 0; i < len; i++) + tab[i] = i; + sum = 0; + for(j = 0; j < n; j++) { + sum += tab[0]; + sum += tab[1]; + sum += tab[2]; + sum += tab[3]; + sum += tab[4]; + sum += tab[5]; + sum += tab[6]; + sum += tab[7]; + sum += tab[8]; + sum += tab[9]; + } + global_res = sum; + return len * n; +} + +function typed_array_write(n) +{ + var tab, len, i, j; + len = 10; + tab = new Int32Array(len); + for(i = 0; i < len; i++) + tab[i] = i; + for(j = 0; j < n; j++) { + tab[0] = j; + tab[1] = j; + tab[2] = j; + tab[3] = j; + tab[4] = j; + tab[5] = j; + tab[6] = j; + tab[7] = j; + tab[8] = j; + tab[9] = j; + } + return len * n; +} + +var global_var0; + +function global_read(n) +{ + var sum, j; + global_var0 = 0; + sum = 0; + for(j = 0; j < n; j++) { + sum += global_var0; + sum += global_var0; + sum += global_var0; + sum += global_var0; + } + global_res = sum; + return n * 4; +} + +// non strict version +var global_write = + (1, eval)(`(function global_write(n) + { + var j; + for(j = 0; j < n; j++) { + global_var0 = j; + global_var0 = j; + global_var0 = j; + global_var0 = j; + } + return n * 4; + })`); + +function global_write_strict(n) +{ + var j; + for(j = 0; j < n; j++) { + global_var0 = j; + global_var0 = j; + global_var0 = j; + global_var0 = j; + } + return n * 4; +} + +function local_destruct(n) +{ + var j, v1, v2, v3, v4; + var array = [ 1, 2, 3, 4, 5]; + var o = { a:1, b:2, c:3, d:4 }; + var a, b, c, d; + for(j = 0; j < n; j++) { + [ v1, v2,, v3, ...v4] = array; + ({ a, b, c, d } = o); + ({ a: a, b: b, c: c, d: d } = o); + } + return n * 12; +} + +var global_v1, global_v2, global_v3, global_v4; +var global_a, global_b, global_c, global_d; + +// non strict version +var global_destruct = + (1, eval)(`(function global_destruct(n) + { + var j, v1, v2, v3, v4; + var array = [ 1, 2, 3, 4, 5 ]; + var o = { a:1, b:2, c:3, d:4 }; + var a, b, c, d; + for(j = 0; j < n; j++) { + [ global_v1, global_v2,, global_v3, ...global_v4] = array; + ({ a: global_a, b: global_b, c: global_c, d: global_d } = o); + } + return n * 8; + })`); + +function global_destruct_strict(n) +{ + var j, v1, v2, v3, v4; + var array = [ 1, 2, 3, 4, 5 ]; + var o = { a:1, b:2, c:3, d:4 }; + var a, b, c, d; + for(j = 0; j < n; j++) { + [ global_v1, global_v2,, global_v3, ...global_v4] = array; + ({ a: global_a, b: global_b, c: global_c, d: global_d } = o); + } + return n * 8; +} + +function g(a) +{ + return 1; +} + +function global_func_call(n) +{ + var j, sum; + sum = 0; + for(j = 0; j < n; j++) { + sum += g(j); + sum += g(j); + sum += g(j); + sum += g(j); + } + global_res = sum; + return n * 4; +} + +function func_call(n) +{ + function f(a) + { + return 1; + } + + var j, sum; + sum = 0; + for(j = 0; j < n; j++) { + sum += f(j); + sum += f(j); + sum += f(j); + sum += f(j); + } + global_res = sum; + return n * 4; +} + +function func_closure_call(n) +{ + function f(a) + { + sum++; + } + + var j, sum; + sum = 0; + for(j = 0; j < n; j++) { + f(j); + f(j); + f(j); + f(j); + } + global_res = sum; + return n * 4; +} + +function int_arith(n) +{ + var i, j, sum; + global_res = 0; + for(j = 0; j < n; j++) { + sum = 0; + for(i = 0; i < 1000; i++) { + sum += i * i; + } + global_res += sum; + } + return n * 1000; +} + +function float_arith(n) +{ + var i, j, sum, a, incr, a0; + global_res = 0; + a0 = 0.1; + incr = 1.1; + for(j = 0; j < n; j++) { + sum = 0; + a = a0; + for(i = 0; i < 1000; i++) { + sum += a * a; + a += incr; + } + global_res += sum; + } + return n * 1000; +} + +function bigfloat_arith(n) +{ + var i, j, sum, a, incr, a0; + global_res = 0; + a0 = BigFloat("0.1"); + incr = BigFloat("1.1"); + for(j = 0; j < n; j++) { + sum = 0; + a = a0; + for(i = 0; i < 1000; i++) { + sum += a * a; + a += incr; + } + global_res += sum; + } + return n * 1000; +} + +function float256_arith(n) +{ + return BigFloatEnv.setPrec(bigfloat_arith.bind(null, n), 237, 19); +} + +function bigint_arith(n, bits) +{ + var i, j, sum, a, incr, a0, sum0; + sum0 = global_res = BigInt(0); + a0 = BigInt(1) << BigInt(Math.floor((bits - 10) * 0.5)); + incr = BigInt(1); + for(j = 0; j < n; j++) { + sum = sum0; + a = a0; + for(i = 0; i < 1000; i++) { + sum += a * a; + a += incr; + } + global_res += sum; + } + return n * 1000; +} + +function bigint64_arith(n) +{ + return bigint_arith(n, 64); +} + +function bigint256_arith(n) +{ + return bigint_arith(n, 256); +} + +function set_collection_add(n) +{ + var s, i, j, len = 100; + for(j = 0; j < n; j++) { + s = new Set(); + for(i = 0; i < len; i++) { + s.add(String(i), i); + } + for(i = 0; i < len; i++) { + if (!s.has(String(i))) + throw Error("bug in Set"); + } + } + return n * len; +} + +function array_for(n) +{ + var r, i, j, sum, len = 100; + r = []; + for(i = 0; i < len; i++) + r[i] = i; + for(j = 0; j < n; j++) { + sum = 0; + for(i = 0; i < len; i++) { + sum += r[i]; + } + global_res = sum; + } + return n * len; +} + +function array_for_in(n) +{ + var r, i, j, sum, len = 100; + r = []; + for(i = 0; i < len; i++) + r[i] = i; + for(j = 0; j < n; j++) { + sum = 0; + for(i in r) { + sum += r[i]; + } + global_res = sum; + } + return n * len; +} + +function array_for_of(n) +{ + var r, i, j, sum, len = 100; + r = []; + for(i = 0; i < len; i++) + r[i] = i; + for(j = 0; j < n; j++) { + sum = 0; + for(i of r) { + sum += i; + } + global_res = sum; + } + return n * len; +} + +function math_min(n) +{ + var i, j, r; + r = 0; + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = Math.min(i, 500); + global_res = r; + } + return n * 1000; +} + +function regexp_ascii(n) +{ + var i, j, r, s; + s = "the quick brown fox jumped over the lazy dog" + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = /the quick brown fox/.exec(s) + global_res = r; + } + return n * 1000; +} + +function regexp_utf16(n) +{ + var i, j, r, s; + s = "the quick brown ᶠᵒˣ jumped over the lazy ᵈᵒᵍ" + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = /the quick brown ᶠᵒˣ/.exec(s) + global_res = r; + } + return n * 1000; +} + +/* incremental string contruction as local var */ +function string_build1(n) +{ + var i, j, r; + for(j = 0; j < n; j++) { + r = ""; + for(i = 0; i < 1000; i++) + r += "x"; + global_res = r; + } + return n * 1000; +} + +/* incremental string contruction using + */ +function string_build1x(n) +{ + var i, j, r; + for(j = 0; j < n; j++) { + r = ""; + for(i = 0; i < 1000; i++) + r = r + "x"; + global_res = r; + } + return n * 1000; +} + +/* incremental string contruction using +2c */ +function string_build2c(n) +{ + var i, j; + for(j = 0; j < n; j++) { + var r = ""; + for(i = 0; i < 1000; i++) + r += "xy"; + global_res = r; + } + return n * 1000; +} + +/* incremental string contruction as arg */ +function string_build2(n, r) +{ + var i, j; + for(j = 0; j < n; j++) { + r = ""; + for(i = 0; i < 1000; i++) + r += "x"; + global_res = r; + } + return n * 1000; +} + +/* incremental string contruction by prepending */ +function string_build3(n) +{ + var i, j, r; + for(j = 0; j < n; j++) { + r = ""; + for(i = 0; i < 1000; i++) + r = "x" + r; + global_res = r; + } + return n * 1000; +} + +/* incremental string contruction with multiple reference */ +function string_build4(n) +{ + var i, j, r, s; + for(j = 0; j < n; j++) { + r = ""; + for(i = 0; i < 1000; i++) { + s = r; + r += "x"; + } + global_res = r; + } + return n * 1000; +} + +/* sort bench */ + +function sort_bench(text) { + function random(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(Math.random() * n) >> 0]; + } + function random8(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(Math.random() * 256) >> 0]; + } + function random1(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(Math.random() * 2) >> 0]; + } + function hill(arr, n, def) { + var mid = n >> 1; + for (var i = 0; i < mid; i++) + arr[i] = def[i]; + for (var i = mid; i < n; i++) + arr[i] = def[n - i]; + } + function comb(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(i & 1) * i]; + } + function crisscross(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(i & 1) ? n - i : i]; + } + function zero(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[0]; + } + function increasing(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i]; + } + function decreasing(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[n - 1 - i]; + } + function alternate(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i ^ 1]; + } + function jigsaw(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i % (n >> 4)]; + } + function incbutone(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i]; + if (n > 0) + arr[n >> 2] = def[n]; + } + function incbutfirst(arr, n, def) { + if (n > 0) + arr[0] = def[n]; + for (var i = 1; i < n; i++) + arr[i] = def[i]; + } + function incbutlast(arr, n, def) { + for (var i = 0; i < n - 1; i++) + arr[i] = def[i + 1]; + if (n > 0) + arr[n - 1] = def[0]; + } + + var sort_cases = [ random, random8, random1, jigsaw, hill, comb, + crisscross, zero, increasing, decreasing, alternate, + incbutone, incbutlast, incbutfirst ]; + + var n = sort_bench.array_size || 10000; + var array_type = sort_bench.array_type || Array; + var def, arr; + var i, j, x, y; + var total = 0; + + var save_total_score = total_score; + var save_total_scale = total_scale; + + // initialize default sorted array (n + 1 elements) + def = new array_type(n + 1); + if (array_type == Array) { + for (i = 0; i <= n; i++) { + def[i] = i + ""; + } + } else { + for (i = 0; i <= n; i++) { + def[i] = i; + } + } + def.sort(); + for (var f of sort_cases) { + var ti = 0, tx = 0; + for (j = 0; j < 100; j++) { + arr = new array_type(n); + f(arr, n, def); + var t1 = get_clock(); + arr.sort(); + t1 = get_clock() - t1; + tx += t1; + if (!ti || ti > t1) + ti = t1; + if (tx >= clocks_per_sec) + break; + } + total += ti; + + i = 0; + x = arr[0]; + if (x !== void 0) { + for (i = 1; i < n; i++) { + y = arr[i]; + if (y === void 0) + break; + if (x > y) + break; + x = y; + } + } + while (i < n && arr[i] === void 0) + i++; + if (i < n) { + console.log("sort_bench: out of order error for " + f.name + + " at offset " + (i - 1) + + ": " + arr[i - 1] + " > " + arr[i]); + } + if (sort_bench.verbose) + log_one("sort_" + f.name, 1, ti / 100); + } + total_score = save_total_score; + total_scale = save_total_scale; + return total / n / 100; +} +sort_bench.bench = true; +sort_bench.verbose = false; + +function int_to_string(n) +{ + var s, j; + for(j = 0; j < n; j++) { + s = (j % 1000).toString(); + s = (1234000 + j % 1000).toString(); + } + global_res = s; + return n * 2; +} + +function float_to_string(n) +{ + var s, j; + for(j = 0; j < n; j++) { + s = (j + 0.1).toString(); + } + global_res = s; + return n; +} + +function string_to_int(n) +{ + var s, r, j; + r = 0; + s = "12345"; + for(j = 0; j < n; j++) { + r += (s | 0); + } + global_res = r; + return n; +} + +function string_to_float(n) +{ + var s, r, j; + r = 0; + s = "12345.6"; + for(j = 0; j < n; j++) { + r -= s; + } + global_res = r; + return n; +} + +function load_result(filename) +{ + var has_filename = filename; + var has_error = false; + var str, res; + + if (!filename) + filename = "microbench.txt"; + + if (typeof fs !== "undefined") { + // read the file in Node.js + try { + str = fs.readFileSync(filename, { encoding: "utf8" }); + } catch { + has_error = true; + } + } else + if (typeof std !== "undefined") { + // read the file in QuickJS + var f = std.open(filename, "r"); + if (f) { + str = f.readAsString(); + f.close(); + } else { + has_error = true; + } + } else { + return null; + } + if (has_error) { + if (has_filename) { + // Should throw exception? + console.log("cannot load " + filename); + } + return null; + } + res = JSON.parse(str); + return res; +} + +function save_result(filename, obj) +{ + var str = JSON.stringify(obj, null, 2) + "\n"; + var has_error = false; + + if (typeof fs !== "undefined") { + // save the file in Node.js + try { + str = fs.writeFileSync(filename, str, { encoding: "utf8" }); + } catch { + has_error = true; + } + } else + if (typeof std !== "undefined") { + // save the file in QuickJS + var f = std.open(filename, "w"); + if (f) { + f.puts(str); + f.close(); + } else { + has_error = 'true'; + } + } else { + return; + } + if (has_error) + console.log("cannot save " + filename); +} + +function main(argc, argv, g) +{ + var test_list = [ + empty_loop, + empty_down_loop, + empty_down_loop2, + empty_do_loop, + date_now, + date_parse, + prop_read, + prop_write, + prop_update, + prop_create, + prop_clone, + prop_delete, + array_read, + array_write, + array_prop_create, + array_slice, + array_length_decr, + array_hole_length_decr, + array_push, + array_pop, + typed_array_read, + typed_array_write, + global_read, + global_write, + global_write_strict, + local_destruct, + global_destruct, + global_destruct_strict, + global_func_call, + func_call, + func_closure_call, + int_arith, + float_arith, + set_collection_add, + array_for, + array_for_in, + array_for_of, + math_min, + regexp_ascii, + regexp_utf16, + string_build1, + string_build1x, + string_build2c, + string_build2, + string_build3, + string_build4, + int_to_string, + float_to_string, + string_to_int, + string_to_float, + ]; + var tests = []; + var i, j, n, f, name, found; + var ref_file, new_ref_file = "microbench-new.txt"; + + if (typeof BigInt === "function") { + /* BigInt test */ + test_list.push(bigint64_arith); + test_list.push(bigint256_arith); + } + if (typeof BigFloat === "function") { + /* BigFloat test */ + test_list.push(float256_arith); + } + test_list.push(sort_bench); + + for (i = 1; i < argc;) { + name = argv[i++]; + if (name == "-a") { + sort_bench.verbose = true; + continue; + } + if (name == "-t") { + name = argv[i++]; + sort_bench.array_type = g[name]; + if (typeof sort_bench.array_type !== "function") { + console.log("unknown array type: " + name); + return 1; + } + continue; + } + if (name == "-n") { + sort_bench.array_size = +argv[i++]; + continue; + } + if (name == "-r") { + ref_file = argv[i++]; + continue; + } + if (name == "-s") { + new_ref_file = argv[i++]; + continue; + } + for (j = 0, found = false; j < test_list.length; j++) { + f = test_list[j]; + if (f.name.startsWith(name)) { + tests.push(f); + found = true; + } + } + if (!found) { + console.log("unknown benchmark: " + name); + return 1; + } + } + if (tests.length == 0) + tests = test_list; + + ref_data = load_result(ref_file); + log_data = {}; + log_line.apply(null, heads); + n = 0; + + for(i = 0; i < tests.length; i++) { + f = tests[i]; + bench(f, f.name, ref_data, log_data); + if (ref_data && ref_data[f.name]) + n++; + } + if (ref_data) + log_line("total", "", total[2], total[3], Math.round(total_scale * 1000 / total_score)); + else + log_line("total", "", total[2]); + + if (tests == test_list && new_ref_file) + save_result(new_ref_file, log_data); +} + +if (typeof scriptArgs === "undefined") { + scriptArgs = []; + if (typeof process.argv === "object") + scriptArgs = process.argv.slice(1); +} +main(scriptArgs.length, scriptArgs, this); diff --git a/source/engine/thirdparty/quickjs/tests/test262.patch b/source/engine/thirdparty/quickjs/tests/test262.patch new file mode 100644 index 0000000..a46fa30 --- /dev/null +++ b/source/engine/thirdparty/quickjs/tests/test262.patch @@ -0,0 +1,71 @@ +diff --git a/harness/atomicsHelper.js b/harness/atomicsHelper.js +index 9c1217351e..3c24755558 100644 +--- a/harness/atomicsHelper.js ++++ b/harness/atomicsHelper.js +@@ -227,10 +227,14 @@ $262.agent.waitUntil = function(typedArray, index, expected) { + * } + */ + $262.agent.timeouts = { +- yield: 100, +- small: 200, +- long: 1000, +- huge: 10000, ++// yield: 100, ++// small: 200, ++// long: 1000, ++// huge: 10000, ++ yield: 20, ++ small: 20, ++ long: 100, ++ huge: 1000, + }; + + /** +diff --git a/harness/regExpUtils.js b/harness/regExpUtils.js +index be7039fda0..7b38abf8df 100644 +--- a/harness/regExpUtils.js ++++ b/harness/regExpUtils.js +@@ -6,24 +6,27 @@ description: | + defines: [buildString, testPropertyEscapes, matchValidator] + ---*/ + ++if ($262 && typeof $262.codePointRange === "function") { ++ /* use C function to build the codePointRange (much faster with ++ slow JS engines) */ ++ codePointRange = $262.codePointRange; ++} else { ++ codePointRange = function codePointRange(start, end) { ++ const codePoints = []; ++ let length = 0; ++ for (codePoint = start; codePoint < end; codePoint++) { ++ codePoints[length++] = codePoint; ++ } ++ return String.fromCodePoint.apply(null, codePoints); ++ } ++} ++ + function buildString({ loneCodePoints, ranges }) { +- const CHUNK_SIZE = 10000; +- let result = Reflect.apply(String.fromCodePoint, null, loneCodePoints); +- for (let i = 0; i < ranges.length; i++) { +- const range = ranges[i]; +- const start = range[0]; +- const end = range[1]; +- const codePoints = []; +- for (let length = 0, codePoint = start; codePoint <= end; codePoint++) { +- codePoints[length++] = codePoint; +- if (length === CHUNK_SIZE) { +- result += Reflect.apply(String.fromCodePoint, null, codePoints); +- codePoints.length = length = 0; +- } ++ let result = String.fromCodePoint.apply(null, loneCodePoints); ++ for (const [start, end] of ranges) { ++ result += codePointRange(start, end + 1); + } +- result += Reflect.apply(String.fromCodePoint, null, codePoints); +- } +- return result; ++ return result; + } + + function testPropertyEscapes(regex, string, expression) { diff --git a/source/engine/thirdparty/quickjs/tests/test_bigfloat.js b/source/engine/thirdparty/quickjs/tests/test_bigfloat.js new file mode 100644 index 0000000..c35fb72 --- /dev/null +++ b/source/engine/thirdparty/quickjs/tests/test_bigfloat.js @@ -0,0 +1,279 @@ +"use strict"; + +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function assertThrows(err, func) +{ + var ex; + ex = false; + try { + func(); + } catch(e) { + ex = true; + assert(e instanceof err); + } + assert(ex, true, "exception expected"); +} + +// load more elaborate version of assert if available +try { __loadScript("test_assert.js"); } catch(e) {} + +/*----------------*/ + +/* a must be < b */ +function test_less(a, b) +{ + assert(a < b); + assert(!(b < a)); + assert(a <= b); + assert(!(b <= a)); + assert(b > a); + assert(!(a > b)); + assert(b >= a); + assert(!(a >= b)); + assert(a != b); + assert(!(a == b)); +} + +/* a must be numerically equal to b */ +function test_eq(a, b) +{ + assert(a == b); + assert(b == a); + assert(!(a != b)); + assert(!(b != a)); + assert(a <= b); + assert(b <= a); + assert(!(a < b)); + assert(a >= b); + assert(b >= a); + assert(!(a > b)); +} + +function test_divrem(div1, a, b, q) +{ + var div, divrem, t; + div = BigInt[div1]; + divrem = BigInt[div1 + "rem"]; + assert(div(a, b) == q); + t = divrem(a, b); + assert(t[0] == q); + assert(a == b * q + t[1]); +} + +function test_idiv1(div, a, b, r) +{ + test_divrem(div, a, b, r[0]); + test_divrem(div, -a, b, r[1]); + test_divrem(div, a, -b, r[2]); + test_divrem(div, -a, -b, r[3]); +} + +/* QuickJS BigInt extensions */ +function test_bigint_ext() +{ + var r; + assert(BigInt.floorLog2(0n) === -1n); + assert(BigInt.floorLog2(7n) === 2n); + + assert(BigInt.sqrt(0xffffffc000000000000000n) === 17592185913343n); + r = BigInt.sqrtrem(0xffffffc000000000000000n); + assert(r[0] === 17592185913343n); + assert(r[1] === 35167191957503n); + + test_idiv1("tdiv", 3n, 2n, [1n, -1n, -1n, 1n]); + test_idiv1("fdiv", 3n, 2n, [1n, -2n, -2n, 1n]); + test_idiv1("cdiv", 3n, 2n, [2n, -1n, -1n, 2n]); + test_idiv1("ediv", 3n, 2n, [1n, -2n, -1n, 2n]); +} + +function test_bigfloat() +{ + var e, a, b, sqrt2; + + assert(typeof 1n === "bigint"); + assert(typeof 1l === "bigfloat"); + assert(1 == 1.0l); + assert(1 !== 1.0l); + + test_less(2l, 3l); + test_eq(3l, 3l); + + test_less(2, 3l); + test_eq(3, 3l); + + test_less(2.1, 3l); + test_eq(Math.sqrt(9), 3l); + + test_less(2n, 3l); + test_eq(3n, 3l); + + e = new BigFloatEnv(128); + assert(e.prec == 128); + a = BigFloat.sqrt(2l, e); + assert(a === BigFloat.parseFloat("0x1.6a09e667f3bcc908b2fb1366ea957d3e", 0, e)); + assert(e.inexact === true); + assert(BigFloat.fpRound(a) == 0x1.6a09e667f3bcc908b2fb1366ea95l); + + b = BigFloatEnv.setPrec(BigFloat.sqrt.bind(null, 2), 128); + assert(a === b); + + assert(BigFloat.isNaN(BigFloat(NaN))); + assert(BigFloat.isFinite(1l)); + assert(!BigFloat.isFinite(1l/0l)); + + assert(BigFloat.abs(-3l) === 3l); + assert(BigFloat.sign(-3l) === -1l); + + assert(BigFloat.exp(0.2l) === 1.2214027581601698339210719946396742l); + assert(BigFloat.log(3l) === 1.0986122886681096913952452369225256l); + assert(BigFloat.pow(2.1l, 1.6l) === 3.277561666451861947162828744873745l); + + assert(BigFloat.sin(-1l) === -0.841470984807896506652502321630299l); + assert(BigFloat.cos(1l) === 0.5403023058681397174009366074429766l); + assert(BigFloat.tan(0.1l) === 0.10033467208545054505808004578111154l); + + assert(BigFloat.asin(0.3l) === 0.30469265401539750797200296122752915l); + assert(BigFloat.acos(0.4l) === 1.1592794807274085998465837940224159l); + assert(BigFloat.atan(0.7l) === 0.610725964389208616543758876490236l); + assert(BigFloat.atan2(7.1l, -5.1l) === 2.1937053809751415549388104628759813l); + + assert(BigFloat.floor(2.5l) === 2l); + assert(BigFloat.ceil(2.5l) === 3l); + assert(BigFloat.trunc(-2.5l) === -2l); + assert(BigFloat.round(2.5l) === 3l); + + assert(BigFloat.fmod(3l,2l) === 1l); + assert(BigFloat.remainder(3l,2l) === -1l); + + /* string conversion */ + assert((1234.125l).toString(), "1234.125"); + assert((1234.125l).toFixed(2), "1234.13"); + assert((1234.125l).toFixed(2, "down"), "1234.12"); + assert((1234.125l).toExponential(), "1.234125e+3"); + assert((1234.125l).toExponential(5), "1.23413e+3"); + assert((1234.125l).toExponential(5, BigFloatEnv.RNDZ), "1.23412e+3"); + assert((1234.125l).toPrecision(6), "1234.13"); + assert((1234.125l).toPrecision(6, BigFloatEnv.RNDZ), "1234.12"); + + /* string conversion with binary base */ + assert((0x123.438l).toString(16), "123.438"); + assert((0x323.438l).toString(16), "323.438"); + assert((0x723.438l).toString(16), "723.438"); + assert((0xf23.438l).toString(16), "f23.438"); + assert((0x123.438l).toFixed(2, BigFloatEnv.RNDNA, 16), "123.44"); + assert((0x323.438l).toFixed(2, BigFloatEnv.RNDNA, 16), "323.44"); + assert((0x723.438l).toFixed(2, BigFloatEnv.RNDNA, 16), "723.44"); + assert((0xf23.438l).toFixed(2, BigFloatEnv.RNDNA, 16), "f23.44"); + assert((0x0.0000438l).toFixed(6, BigFloatEnv.RNDNA, 16), "0.000044"); + assert((0x1230000000l).toFixed(1, BigFloatEnv.RNDNA, 16), "1230000000.0"); + assert((0x123.438l).toPrecision(5, BigFloatEnv.RNDNA, 16), "123.44"); + assert((0x123.438l).toPrecision(5, BigFloatEnv.RNDZ, 16), "123.43"); + assert((0x323.438l).toPrecision(5, BigFloatEnv.RNDNA, 16), "323.44"); + assert((0x723.438l).toPrecision(5, BigFloatEnv.RNDNA, 16), "723.44"); + assert((-0xf23.438l).toPrecision(5, BigFloatEnv.RNDD, 16), "-f23.44"); + assert((0x123.438l).toExponential(4, BigFloatEnv.RNDNA, 16), "1.2344p+8"); +} + +function test_bigdecimal() +{ + assert(1m === 1m); + assert(1m !== 2m); + test_less(1m, 2m); + test_eq(2m, 2m); + + test_less(1, 2m); + test_eq(2, 2m); + + test_less(1.1, 2m); + test_eq(Math.sqrt(4), 2m); + + test_less(2n, 3m); + test_eq(3n, 3m); + + assert(BigDecimal("1234.1") === 1234.1m); + assert(BigDecimal(" 1234.1") === 1234.1m); + assert(BigDecimal(" 1234.1 ") === 1234.1m); + + assert(BigDecimal(0.1) === 0.1m); + assert(BigDecimal(123) === 123m); + assert(BigDecimal(true) === 1m); + + assert(123m + 1m === 124m); + assert(123m - 1m === 122m); + + assert(3.2m * 3m === 9.6m); + assert(10m / 2m === 5m); + assertThrows(RangeError, () => { 10m / 3m } ); + + assert(10m % 3m === 1m); + assert(-10m % 3m === -1m); + + assert(1234.5m ** 3m === 1881365963.625m); + assertThrows(RangeError, () => { 2m ** 3.1m } ); + assertThrows(RangeError, () => { 2m ** -3m } ); + + assert(BigDecimal.sqrt(2m, + { roundingMode: "half-even", + maximumSignificantDigits: 4 }) === 1.414m); + assert(BigDecimal.sqrt(101m, + { roundingMode: "half-even", + maximumFractionDigits: 3 }) === 10.050m); + assert(BigDecimal.sqrt(0.002m, + { roundingMode: "half-even", + maximumFractionDigits: 3 }) === 0.045m); + + assert(BigDecimal.round(3.14159m, + { roundingMode: "half-even", + maximumFractionDigits: 3 }) === 3.142m); + + assert(BigDecimal.add(3.14159m, 0.31212m, + { roundingMode: "half-even", + maximumFractionDigits: 2 }) === 3.45m); + assert(BigDecimal.sub(3.14159m, 0.31212m, + { roundingMode: "down", + maximumFractionDigits: 2 }) === 2.82m); + assert(BigDecimal.mul(3.14159m, 0.31212m, + { roundingMode: "half-even", + maximumFractionDigits: 3 }) === 0.981m); + assert(BigDecimal.mod(3.14159m, 0.31211m, + { roundingMode: "half-even", + maximumFractionDigits: 4 }) === 0.0205m); + assert(BigDecimal.div(20m, 3m, + { roundingMode: "half-even", + maximumSignificantDigits: 3 }) === 6.67m); + assert(BigDecimal.div(20m, 3m, + { roundingMode: "half-even", + maximumFractionDigits: 50 }) === + 6.66666666666666666666666666666666666666666666666667m); + + /* string conversion */ + assert((1234.125m).toString(), "1234.125"); + assert((1234.125m).toFixed(2), "1234.13"); + assert((1234.125m).toFixed(2, "down"), "1234.12"); + assert((1234.125m).toExponential(), "1.234125e+3"); + assert((1234.125m).toExponential(5), "1.23413e+3"); + assert((1234.125m).toExponential(5, "down"), "1.23412e+3"); + assert((1234.125m).toPrecision(6), "1234.13"); + assert((1234.125m).toPrecision(6, "down"), "1234.12"); + assert((-1234.125m).toPrecision(6, "floor"), "-1234.13"); +} + +test_bigint_ext(); +test_bigfloat(); +test_bigdecimal(); diff --git a/source/engine/thirdparty/quickjs/tests/test_bignum.js b/source/engine/thirdparty/quickjs/tests/test_bignum.js new file mode 100644 index 0000000..1520d82 --- /dev/null +++ b/source/engine/thirdparty/quickjs/tests/test_bignum.js @@ -0,0 +1,114 @@ +"use strict"; + +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function assertThrows(err, func) +{ + var ex; + ex = false; + try { + func(); + } catch(e) { + ex = true; + assert(e instanceof err); + } + assert(ex, true, "exception expected"); +} + +// load more elaborate version of assert if available +try { __loadScript("test_assert.js"); } catch(e) {} + +/*----------------*/ + +function bigint_pow(a, n) +{ + var r, i; + r = 1n; + for(i = 0n; i < n; i++) + r *= a; + return r; +} + +/* a must be < b */ +function test_less(a, b) +{ + assert(a < b); + assert(!(b < a)); + assert(a <= b); + assert(!(b <= a)); + assert(b > a); + assert(!(a > b)); + assert(b >= a); + assert(!(a >= b)); + assert(a != b); + assert(!(a == b)); +} + +/* a must be numerically equal to b */ +function test_eq(a, b) +{ + assert(a == b); + assert(b == a); + assert(!(a != b)); + assert(!(b != a)); + assert(a <= b); + assert(b <= a); + assert(!(a < b)); + assert(a >= b); + assert(b >= a); + assert(!(a > b)); +} + +function test_bigint1() +{ + var a, r; + + test_less(2n, 3n); + test_eq(3n, 3n); + + test_less(2, 3n); + test_eq(3, 3n); + + test_less(2.1, 3n); + test_eq(Math.sqrt(4), 2n); + + a = bigint_pow(3n, 100n); + assert((a - 1n) != a); + assert(a == 515377520732011331036461129765621272702107522001n); + assert(a == 0x5a4653ca673768565b41f775d6947d55cf3813d1n); + + r = 1n << 31n; + assert(r, 2147483648n, "1 << 31n === 2147483648n"); + + r = 1n << 32n; + assert(r, 4294967296n, "1 << 32n === 4294967296n"); +} + +function test_bigint2() +{ + assert(BigInt(""), 0n); + assert(BigInt(" 123"), 123n); + assert(BigInt(" 123 "), 123n); + assertThrows(SyntaxError, () => { BigInt("+") } ); + assertThrows(SyntaxError, () => { BigInt("-") } ); + assertThrows(SyntaxError, () => { BigInt("\x00a") } ); + assertThrows(SyntaxError, () => { BigInt(" 123 r") } ); +} + +test_bigint1(); +test_bigint2(); diff --git a/source/engine/thirdparty/quickjs/tests/test_bjson.js b/source/engine/thirdparty/quickjs/tests/test_bjson.js new file mode 100644 index 0000000..f15ef91 --- /dev/null +++ b/source/engine/thirdparty/quickjs/tests/test_bjson.js @@ -0,0 +1,191 @@ +import * as bjson from "./bjson.so"; + +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function toHex(a) +{ + var i, s = "", tab, v; + tab = new Uint8Array(a); + for(i = 0; i < tab.length; i++) { + v = tab[i].toString(16); + if (v.length < 2) + v = "0" + v; + if (i !== 0) + s += " "; + s += v; + } + return s; +} + +function isArrayLike(a) +{ + return Array.isArray(a) || + (a instanceof Uint8ClampedArray) || + (a instanceof Uint8Array) || + (a instanceof Uint16Array) || + (a instanceof Uint32Array) || + (a instanceof Int8Array) || + (a instanceof Int16Array) || + (a instanceof Int32Array) || + (a instanceof Float32Array) || + (a instanceof Float64Array); +} + +function toStr(a) +{ + var s, i, props, prop; + + switch(typeof(a)) { + case "object": + if (a === null) + return "null"; + if (a instanceof Date) { + s = "Date(" + toStr(a.valueOf()) + ")"; + } else if (a instanceof Number) { + s = "Number(" + toStr(a.valueOf()) + ")"; + } else if (a instanceof String) { + s = "String(" + toStr(a.valueOf()) + ")"; + } else if (a instanceof Boolean) { + s = "Boolean(" + toStr(a.valueOf()) + ")"; + } else if (isArrayLike(a)) { + s = "["; + for(i = 0; i < a.length; i++) { + if (i != 0) + s += ","; + s += toStr(a[i]); + } + s += "]"; + } else { + props = Object.keys(a); + s = "{"; + for(i = 0; i < props.length; i++) { + if (i != 0) + s += ","; + prop = props[i]; + s += prop + ":" + toStr(a[prop]); + } + s += "}"; + } + return s; + case "undefined": + return "undefined"; + case "string": + return a.__quote(); + case "number": + case "bigfloat": + if (a == 0 && 1 / a < 0) + return "-0"; + else + return a.toString(); + break; + default: + return a.toString(); + } +} + +function bjson_test(a) +{ + var buf, r, a_str, r_str; + a_str = toStr(a); + buf = bjson.write(a); + if (0) { + print(a_str, "->", toHex(buf)); + } + r = bjson.read(buf, 0, buf.byteLength); + r_str = toStr(r); + if (a_str != r_str) { + print(a_str); + print(r_str); + assert(false); + } +} + +/* test multiple references to an object including circular + references */ +function bjson_test_reference() +{ + var array, buf, i, n, array_buffer; + n = 16; + array = []; + for(i = 0; i < n; i++) + array[i] = {}; + array_buffer = new ArrayBuffer(n); + for(i = 0; i < n; i++) { + array[i].next = array[(i + 1) % n]; + array[i].idx = i; + array[i].typed_array = new Uint8Array(array_buffer, i, 1); + } + buf = bjson.write(array, true); + + array = bjson.read(buf, 0, buf.byteLength, true); + + /* check the result */ + for(i = 0; i < n; i++) { + assert(array[i].next, array[(i + 1) % n]); + assert(array[i].idx, i); + assert(array[i].typed_array.buffer, array_buffer); + assert(array[i].typed_array.length, 1); + assert(array[i].typed_array.byteOffset, i); + } +} + +function bjson_test_all() +{ + var obj; + + bjson_test({x:1, y:2, if:3}); + bjson_test([1, 2, 3]); + bjson_test([1.0, "aa", true, false, undefined, null, NaN, -Infinity, -0.0]); + if (typeof BigInt !== "undefined") { + bjson_test([BigInt("1"), -BigInt("0x123456789"), + BigInt("0x123456789abcdef123456789abcdef")]); + } + if (typeof BigFloat !== "undefined") { + BigFloatEnv.setPrec(function () { + bjson_test([BigFloat("0.1"), BigFloat("-1e30"), BigFloat("0"), + BigFloat("-0"), BigFloat("Infinity"), BigFloat("-Infinity"), + 0.0 / BigFloat("0"), BigFloat.MAX_VALUE, + BigFloat.MIN_VALUE]); + }, 113, 15); + } + if (typeof BigDecimal !== "undefined") { + bjson_test([BigDecimal("0"), + BigDecimal("0.8"), BigDecimal("123321312321321e100"), + BigDecimal("-1233213123213214332333223332e100"), + BigDecimal("1.233e-1000")]); + } + + bjson_test([new Date(1234), new String("abc"), new Number(-12.1), new Boolean(true)]); + + bjson_test(new Int32Array([123123, 222111, -32222])); + bjson_test(new Float64Array([123123, 222111.5])); + + /* tested with a circular reference */ + obj = {}; + obj.x = obj; + try { + bjson.write(obj); + assert(false); + } catch(e) { + assert(e instanceof TypeError); + } + + bjson_test_reference(); +} + +bjson_test_all(); diff --git a/source/engine/thirdparty/quickjs/tests/test_builtin.js b/source/engine/thirdparty/quickjs/tests/test_builtin.js new file mode 100644 index 0000000..03a56ac --- /dev/null +++ b/source/engine/thirdparty/quickjs/tests/test_builtin.js @@ -0,0 +1,856 @@ +"use strict"; + +var status = 0; +var throw_errors = true; + +function throw_error(msg) { + if (throw_errors) + throw Error(msg); + console.log(msg); + status = 1; +} + +function assert(actual, expected, message) { + function get_full_type(o) { + var type = typeof(o); + if (type === 'object') { + if (o === null) + return 'null'; + if (o.constructor && o.constructor.name) + return o.constructor.name; + } + return type; + } + + if (arguments.length == 1) + expected = true; + + if (typeof actual === typeof expected) { + if (actual === expected) { + if (actual !== 0 || (1 / actual) === (1 / expected)) + return; + } + if (typeof actual === 'number') { + if (isNaN(actual) && isNaN(expected)) + return true; + } + if (typeof actual === 'object') { + if (actual !== null && expected !== null + && actual.constructor === expected.constructor + && actual.toString() === expected.toString()) + return; + } + } + // Should output the source file and line number and extract + // the expression from the assert call + throw_error("assertion failed: got " + + get_full_type(actual) + ":|" + actual + "|, expected " + + get_full_type(expected) + ":|" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function assert_throws(expected_error, func) +{ + var err = false; + try { + func(); + } catch(e) { + err = true; + if (!(e instanceof expected_error)) { + // Should output the source file and line number and extract + // the expression from the assert_throws() call + throw_error("unexpected exception type"); + return; + } + } + if (!err) { + // Should output the source file and line number and extract + // the expression from the assert_throws() call + throw_error("expected exception"); + } +} + +// load more elaborate version of assert if available +try { __loadScript("test_assert.js"); } catch(e) {} + +/*----------------*/ + +function my_func(a, b) +{ + return a + b; +} + +function test_function() +{ + function f(a, b) { + var i, tab = []; + tab.push(this); + for(i = 0; i < arguments.length; i++) + tab.push(arguments[i]); + return tab; + } + function constructor1(a) { + this.x = a; + } + + var r, g; + + r = my_func.call(null, 1, 2); + assert(r, 3, "call"); + + r = my_func.apply(null, [1, 2]); + assert(r, 3, "apply"); + + r = (function () { return 1; }).apply(null, undefined); + assert(r, 1); + + assert_throws(TypeError, (function() { + Reflect.apply((function () { return 1; }), null, undefined); + })); + + r = new Function("a", "b", "return a + b;"); + assert(r(2,3), 5, "function"); + + g = f.bind(1, 2); + assert(g.length, 1); + assert(g.name, "bound f"); + assert(g(3), [1,2,3]); + + g = constructor1.bind(null, 1); + r = new g(); + assert(r.x, 1); +} + +function test() +{ + var r, a, b, c, err; + + r = Error("hello"); + assert(r.message, "hello", "Error"); + + a = new Object(); + a.x = 1; + assert(a.x, 1, "Object"); + + assert(Object.getPrototypeOf(a), Object.prototype, "getPrototypeOf"); + Object.defineProperty(a, "y", { value: 3, writable: true, configurable: true, enumerable: true }); + assert(a.y, 3, "defineProperty"); + + Object.defineProperty(a, "z", { get: function () { return 4; }, set: function(val) { this.z_val = val; }, configurable: true, enumerable: true }); + assert(a.z, 4, "get"); + a.z = 5; + assert(a.z_val, 5, "set"); + + a = { get z() { return 4; }, set z(val) { this.z_val = val; } }; + assert(a.z, 4, "get"); + a.z = 5; + assert(a.z_val, 5, "set"); + + b = Object.create(a); + assert(Object.getPrototypeOf(b), a, "create"); + c = {u:2}; + /* XXX: refcount bug in 'b' instead of 'a' */ + Object.setPrototypeOf(a, c); + assert(Object.getPrototypeOf(a), c, "setPrototypeOf"); + + a = {}; + assert(a.toString(), "[object Object]", "toString"); + + a = {x:1}; + assert(Object.isExtensible(a), true, "extensible"); + Object.preventExtensions(a); + + err = false; + try { + a.y = 2; + } catch(e) { + err = true; + } + assert(Object.isExtensible(a), false, "extensible"); + assert(typeof a.y, "undefined", "extensible"); + assert(err, true, "extensible"); +} + +function test_enum() +{ + var a, tab; + a = {x:1, + "18014398509481984": 1, + "9007199254740992": 1, + "9007199254740991": 1, + "4294967296": 1, + "4294967295": 1, + y:1, + "4294967294": 1, + "1": 2}; + tab = Object.keys(a); +// console.log("tab=" + tab.toString()); + assert(tab, ["1","4294967294","x","18014398509481984","9007199254740992","9007199254740991","4294967296","4294967295","y"], "keys"); +} + +function test_array() +{ + var a, err; + + a = [1, 2, 3]; + assert(a.length, 3, "array"); + assert(a[2], 3, "array1"); + + a = new Array(10); + assert(a.length, 10, "array2"); + + a = new Array(1, 2); + assert(a.length === 2 && a[0] === 1 && a[1] === 2, true, "array3"); + + a = [1, 2, 3]; + a.length = 2; + assert(a.length === 2 && a[0] === 1 && a[1] === 2, true, "array4"); + + a = []; + a[1] = 10; + a[4] = 3; + assert(a.length, 5); + + a = [1,2]; + a.length = 5; + a[4] = 1; + a.length = 4; + assert(a[4] !== 1, true, "array5"); + + a = [1,2]; + a.push(3,4); + assert(a.join(), "1,2,3,4", "join"); + + a = [1,2,3,4,5]; + Object.defineProperty(a, "3", { configurable: false }); + err = false; + try { + a.length = 2; + } catch(e) { + err = true; + } + assert(err && a.toString() === "1,2,3,4"); +} + +function test_string() +{ + var a; + a = String("abc"); + assert(a.length, 3, "string"); + assert(a[1], "b", "string"); + assert(a.charCodeAt(1), 0x62, "string"); + assert(String.fromCharCode(65), "A", "string"); + assert(String.fromCharCode.apply(null, [65, 66, 67]), "ABC", "string"); + assert(a.charAt(1), "b"); + assert(a.charAt(-1), ""); + assert(a.charAt(3), ""); + + a = "abcd"; + assert(a.substring(1, 3), "bc", "substring"); + a = String.fromCharCode(0x20ac); + assert(a.charCodeAt(0), 0x20ac, "unicode"); + assert(a, "€", "unicode"); + assert(a, "\u20ac", "unicode"); + assert(a, "\u{20ac}", "unicode"); + assert("a", "\x61", "unicode"); + + a = "\u{10ffff}"; + assert(a.length, 2, "unicode"); + assert(a, "\u{dbff}\u{dfff}", "unicode"); + assert(a.codePointAt(0), 0x10ffff); + assert(String.fromCodePoint(0x10ffff), a); + + assert("a".concat("b", "c"), "abc"); + + assert("abcabc".indexOf("cab"), 2); + assert("abcabc".indexOf("cab2"), -1); + assert("abc".indexOf("c"), 2); + + assert("aaa".indexOf("a"), 0); + assert("aaa".indexOf("a", NaN), 0); + assert("aaa".indexOf("a", -Infinity), 0); + assert("aaa".indexOf("a", -1), 0); + assert("aaa".indexOf("a", -0), 0); + assert("aaa".indexOf("a", 0), 0); + assert("aaa".indexOf("a", 1), 1); + assert("aaa".indexOf("a", 2), 2); + assert("aaa".indexOf("a", 3), -1); + assert("aaa".indexOf("a", 4), -1); + assert("aaa".indexOf("a", Infinity), -1); + + assert("aaa".indexOf(""), 0); + assert("aaa".indexOf("", NaN), 0); + assert("aaa".indexOf("", -Infinity), 0); + assert("aaa".indexOf("", -1), 0); + assert("aaa".indexOf("", -0), 0); + assert("aaa".indexOf("", 0), 0); + assert("aaa".indexOf("", 1), 1); + assert("aaa".indexOf("", 2), 2); + assert("aaa".indexOf("", 3), 3); + assert("aaa".indexOf("", 4), 3); + assert("aaa".indexOf("", Infinity), 3); + + assert("aaa".lastIndexOf("a"), 2); + assert("aaa".lastIndexOf("a", NaN), 2); + assert("aaa".lastIndexOf("a", -Infinity), 0); + assert("aaa".lastIndexOf("a", -1), 0); + assert("aaa".lastIndexOf("a", -0), 0); + assert("aaa".lastIndexOf("a", 0), 0); + assert("aaa".lastIndexOf("a", 1), 1); + assert("aaa".lastIndexOf("a", 2), 2); + assert("aaa".lastIndexOf("a", 3), 2); + assert("aaa".lastIndexOf("a", 4), 2); + assert("aaa".lastIndexOf("a", Infinity), 2); + + assert("aaa".lastIndexOf(""), 3); + assert("aaa".lastIndexOf("", NaN), 3); + assert("aaa".lastIndexOf("", -Infinity), 0); + assert("aaa".lastIndexOf("", -1), 0); + assert("aaa".lastIndexOf("", -0), 0); + assert("aaa".lastIndexOf("", 0), 0); + assert("aaa".lastIndexOf("", 1), 1); + assert("aaa".lastIndexOf("", 2), 2); + assert("aaa".lastIndexOf("", 3), 3); + assert("aaa".lastIndexOf("", 4), 3); + assert("aaa".lastIndexOf("", Infinity), 3); + + assert("a,b,c".split(","), ["a","b","c"]); + assert(",b,c".split(","), ["","b","c"]); + assert("a,b,".split(","), ["a","b",""]); + + assert("aaaa".split(), [ "aaaa" ]); + assert("aaaa".split(undefined, 0), [ ]); + assert("aaaa".split(""), [ "a", "a", "a", "a" ]); + assert("aaaa".split("", 0), [ ]); + assert("aaaa".split("", 1), [ "a" ]); + assert("aaaa".split("", 2), [ "a", "a" ]); + assert("aaaa".split("a"), [ "", "", "", "", "" ]); + assert("aaaa".split("a", 2), [ "", "" ]); + assert("aaaa".split("aa"), [ "", "", "" ]); + assert("aaaa".split("aa", 0), [ ]); + assert("aaaa".split("aa", 1), [ "" ]); + assert("aaaa".split("aa", 2), [ "", "" ]); + assert("aaaa".split("aaa"), [ "", "a" ]); + assert("aaaa".split("aaaa"), [ "", "" ]); + assert("aaaa".split("aaaaa"), [ "aaaa" ]); + assert("aaaa".split("aaaaa", 0), [ ]); + assert("aaaa".split("aaaaa", 1), [ "aaaa" ]); + + assert(eval('"\0"'), "\0"); + + assert("abc".padStart(Infinity, ""), "abc"); +} + +function test_math() +{ + var a; + a = 1.4; + assert(Math.floor(a), 1); + assert(Math.ceil(a), 2); + assert(Math.imul(0x12345678, 123), -1088058456); + assert(Math.imul(0xB505, 0xB504), 2147441940); + assert(Math.imul(0xB505, 0xB505), -2147479015); + assert(Math.imul((-2)**31, (-2)**31), 0); + assert(Math.imul(2**31-1, 2**31-1), 1); + assert(Math.fround(0.1), 0.10000000149011612); + assert(Math.hypot(), 0); + assert(Math.hypot(-2), 2); + assert(Math.hypot(3, 4), 5); + assert(Math.abs(Math.hypot(3, 4, 5) - 7.0710678118654755) <= 1e-15); +} + +function test_number() +{ + assert(parseInt("123"), 123); + assert(parseInt(" 123r"), 123); + assert(parseInt("0x123"), 0x123); + assert(parseInt("0o123"), 0); + assert(+" 123 ", 123); + assert(+"0b111", 7); + assert(+"0o123", 83); + assert(parseFloat("2147483647"), 2147483647); + assert(parseFloat("2147483648"), 2147483648); + assert(parseFloat("-2147483647"), -2147483647); + assert(parseFloat("-2147483648"), -2147483648); + assert(parseFloat("0x1234"), 0); + assert(parseFloat("Infinity"), Infinity); + assert(parseFloat("-Infinity"), -Infinity); + assert(parseFloat("123.2"), 123.2); + assert(parseFloat("123.2e3"), 123200); + assert(Number.isNaN(Number("+"))); + assert(Number.isNaN(Number("-"))); + assert(Number.isNaN(Number("\x00a"))); + + // TODO: Fix rounding errors on Windows/Cygwin. + if (typeof os !== 'undefined' && ['win32', 'cygwin'].includes(os.platform)) { + return; + } + + assert((25).toExponential(0), "3e+1"); + assert((-25).toExponential(0), "-3e+1"); + assert((2.5).toPrecision(1), "3"); + assert((-2.5).toPrecision(1), "-3"); + assert((1.125).toFixed(2), "1.13"); + assert((-1.125).toFixed(2), "-1.13"); +} + +function test_eval2() +{ + var g_call_count = 0; + /* force non strict mode for f1 and f2 */ + var f1 = new Function("eval", "eval(1, 2)"); + var f2 = new Function("eval", "eval(...[1, 2])"); + function g(a, b) { + assert(a, 1); + assert(b, 2); + g_call_count++; + } + f1(g); + f2(g); + assert(g_call_count, 2); +} + +function test_eval() +{ + function f(b) { + var x = 1; + return eval(b); + } + var r, a; + + r = eval("1+1;"); + assert(r, 2, "eval"); + + r = eval("var my_var=2; my_var;"); + assert(r, 2, "eval"); + assert(typeof my_var, "undefined"); + + assert(eval("if (1) 2; else 3;"), 2); + assert(eval("if (0) 2; else 3;"), 3); + + assert(f.call(1, "this"), 1); + + a = 2; + assert(eval("a"), 2); + + eval("a = 3"); + assert(a, 3); + + assert(f("arguments.length", 1), 2); + assert(f("arguments[1]", 1), 1); + + a = 4; + assert(f("a"), 4); + f("a=3"); + assert(a, 3); + + test_eval2(); +} + +function test_typed_array() +{ + var buffer, a, i, str; + + a = new Uint8Array(4); + assert(a.length, 4); + for(i = 0; i < a.length; i++) + a[i] = i; + assert(a.join(","), "0,1,2,3"); + a[0] = -1; + assert(a[0], 255); + + a = new Int8Array(3); + a[0] = 255; + assert(a[0], -1); + + a = new Int32Array(3); + a[0] = Math.pow(2, 32) - 1; + assert(a[0], -1); + assert(a.BYTES_PER_ELEMENT, 4); + + a = new Uint8ClampedArray(4); + a[0] = -100; + a[1] = 1.5; + a[2] = 0.5; + a[3] = 1233.5; + assert(a.toString(), "0,2,0,255"); + + buffer = new ArrayBuffer(16); + assert(buffer.byteLength, 16); + a = new Uint32Array(buffer, 12, 1); + assert(a.length, 1); + a[0] = -1; + + a = new Uint16Array(buffer, 2); + a[0] = -1; + + a = new Float32Array(buffer, 8, 1); + a[0] = 1; + + a = new Uint8Array(buffer); + + str = a.toString(); + /* test little and big endian cases */ + if (str !== "0,0,255,255,0,0,0,0,0,0,128,63,255,255,255,255" && + str !== "0,0,255,255,0,0,0,0,63,128,0,0,255,255,255,255") { + assert(false); + } + + assert(a.buffer, buffer); + + a = new Uint8Array([1, 2, 3, 4]); + assert(a.toString(), "1,2,3,4"); + a.set([10, 11], 2); + assert(a.toString(), "1,2,10,11"); +} + +function test_json() +{ + var a, s; + s = '{"x":1,"y":true,"z":null,"a":[1,2,3],"s":"str"}'; + a = JSON.parse(s); + assert(a.x, 1); + assert(a.y, true); + assert(a.z, null); + assert(JSON.stringify(a), s); + + /* indentation test */ + assert(JSON.stringify([[{x:1,y:{},z:[]},2,3]],undefined,1), +`[ + [ + { + "x": 1, + "y": {}, + "z": [] + }, + 2, + 3 + ] +]`); +} + +function test_date() +{ + // Date Time String format is YYYY-MM-DDTHH:mm:ss.sssZ + // accepted date formats are: YYYY, YYYY-MM and YYYY-MM-DD + // accepted time formats are: THH:mm, THH:mm:ss, THH:mm:ss.sss + // expanded years are represented with 6 digits prefixed by + or - + // -000000 is invalid. + // A string containing out-of-bounds or nonconforming elements + // is not a valid instance of this format. + // Hence the fractional part after . should have 3 digits and how + // a different number of digits is handled is implementation defined. + assert(Date.parse(""), NaN); + assert(Date.parse("2000"), 946684800000); + assert(Date.parse("2000-01"), 946684800000); + assert(Date.parse("2000-01-01"), 946684800000); + //assert(Date.parse("2000-01-01T"), NaN); + //assert(Date.parse("2000-01-01T00Z"), NaN); + assert(Date.parse("2000-01-01T00:00Z"), 946684800000); + assert(Date.parse("2000-01-01T00:00:00Z"), 946684800000); + assert(Date.parse("2000-01-01T00:00:00.1Z"), 946684800100); + assert(Date.parse("2000-01-01T00:00:00.10Z"), 946684800100); + assert(Date.parse("2000-01-01T00:00:00.100Z"), 946684800100); + assert(Date.parse("2000-01-01T00:00:00.1000Z"), 946684800100); + assert(Date.parse("2000-01-01T00:00:00+00:00"), 946684800000); + //assert(Date.parse("2000-01-01T00:00:00+00:30"), 946686600000); + var d = new Date("2000T00:00"); // Jan 1st 2000, 0:00:00 local time + assert(typeof d === 'object' && d.toString() != 'Invalid Date'); + assert((new Date('Jan 1 2000')).toISOString(), + d.toISOString()); + assert((new Date('Jan 1 2000 00:00')).toISOString(), + d.toISOString()); + assert((new Date('Jan 1 2000 00:00:00')).toISOString(), + d.toISOString()); + assert((new Date('Jan 1 2000 00:00:00 GMT+0100')).toISOString(), + '1999-12-31T23:00:00.000Z'); + assert((new Date('Jan 1 2000 00:00:00 GMT+0200')).toISOString(), + '1999-12-31T22:00:00.000Z'); + assert((new Date('Sat Jan 1 2000')).toISOString(), + d.toISOString()); + assert((new Date('Sat Jan 1 2000 00:00')).toISOString(), + d.toISOString()); + assert((new Date('Sat Jan 1 2000 00:00:00')).toISOString(), + d.toISOString()); + assert((new Date('Sat Jan 1 2000 00:00:00 GMT+0100')).toISOString(), + '1999-12-31T23:00:00.000Z'); + assert((new Date('Sat Jan 1 2000 00:00:00 GMT+0200')).toISOString(), + '1999-12-31T22:00:00.000Z'); + + var d = new Date(1506098258091); + assert(d.toISOString(), "2017-09-22T16:37:38.091Z"); + d.setUTCHours(18, 10, 11); + assert(d.toISOString(), "2017-09-22T18:10:11.091Z"); + var a = Date.parse(d.toISOString()); + assert((new Date(a)).toISOString(), d.toISOString()); + + assert((new Date("2020-01-01T01:01:01.123Z")).toISOString(), + "2020-01-01T01:01:01.123Z"); + /* implementation defined behavior */ + assert((new Date("2020-01-01T01:01:01.1Z")).toISOString(), + "2020-01-01T01:01:01.100Z"); + assert((new Date("2020-01-01T01:01:01.12Z")).toISOString(), + "2020-01-01T01:01:01.120Z"); + assert((new Date("2020-01-01T01:01:01.1234Z")).toISOString(), + "2020-01-01T01:01:01.123Z"); + assert((new Date("2020-01-01T01:01:01.12345Z")).toISOString(), + "2020-01-01T01:01:01.123Z"); + assert((new Date("2020-01-01T01:01:01.1235Z")).toISOString(), + "2020-01-01T01:01:01.123Z"); + assert((new Date("2020-01-01T01:01:01.9999Z")).toISOString(), + "2020-01-01T01:01:01.999Z"); + + assert(Date.UTC(2017), 1483228800000); + assert(Date.UTC(2017, 9), 1506816000000); + assert(Date.UTC(2017, 9, 22), 1508630400000); + assert(Date.UTC(2017, 9, 22, 18), 1508695200000); + assert(Date.UTC(2017, 9, 22, 18, 10), 1508695800000); + assert(Date.UTC(2017, 9, 22, 18, 10, 11), 1508695811000); + assert(Date.UTC(2017, 9, 22, 18, 10, 11, 91), 1508695811091); + + assert(Date.UTC(NaN), NaN); + assert(Date.UTC(2017, NaN), NaN); + assert(Date.UTC(2017, 9, NaN), NaN); + assert(Date.UTC(2017, 9, 22, NaN), NaN); + assert(Date.UTC(2017, 9, 22, 18, NaN), NaN); + assert(Date.UTC(2017, 9, 22, 18, 10, NaN), NaN); + assert(Date.UTC(2017, 9, 22, 18, 10, 11, NaN), NaN); + assert(Date.UTC(2017, 9, 22, 18, 10, 11, 91, NaN), 1508695811091); + + // TODO: Fix rounding errors on Windows/Cygwin. + if (!(typeof os !== 'undefined' && ['win32', 'cygwin'].includes(os.platform))) { + // from test262/test/built-ins/Date/UTC/fp-evaluation-order.js + assert(Date.UTC(1970, 0, 1, 80063993375, 29, 1, -288230376151711740), 29312, + 'order of operations / precision in MakeTime'); + assert(Date.UTC(1970, 0, 213503982336, 0, 0, 0, -18446744073709552000), 34447360, + 'precision in MakeDate'); + } + //assert(Date.UTC(2017 - 1e9, 9 + 12e9), 1506816000000); // node fails this + assert(Date.UTC(2017, 9, 22 - 1e10, 18 + 24e10), 1508695200000); + assert(Date.UTC(2017, 9, 22, 18 - 1e10, 10 + 60e10), 1508695800000); + assert(Date.UTC(2017, 9, 22, 18, 10 - 1e10, 11 + 60e10), 1508695811000); + assert(Date.UTC(2017, 9, 22, 18, 10, 11 - 1e12, 91 + 1000e12), 1508695811091); +} + +function test_regexp() +{ + var a, str; + str = "abbbbbc"; + a = /(b+)c/.exec(str); + assert(a[0], "bbbbbc"); + assert(a[1], "bbbbb"); + assert(a.index, 1); + assert(a.input, str); + a = /(b+)c/.test(str); + assert(a, true); + assert(/\x61/.exec("a")[0], "a"); + assert(/\u0061/.exec("a")[0], "a"); + assert(/\ca/.exec("\x01")[0], "\x01"); + assert(/\\a/.exec("\\a")[0], "\\a"); + assert(/\c0/.exec("\\c0")[0], "\\c0"); + + a = /(\.(?=com|org)|\/)/.exec("ah.com"); + assert(a.index === 2 && a[0] === "."); + + a = /(\.(?!com|org)|\/)/.exec("ah.com"); + assert(a, null); + + a = /(?=(a+))/.exec("baaabac"); + assert(a.index === 1 && a[0] === "" && a[1] === "aaa"); + + a = /(z)((a+)?(b+)?(c))*/.exec("zaacbbbcac"); + assert(a, ["zaacbbbcac","z","ac","a",,"c"]); + + a = eval("/\0a/"); + assert(a.toString(), "/\0a/"); + assert(a.exec("\0a")[0], "\0a"); + + assert(/{1a}/.toString(), "/{1a}/"); + a = /a{1+/.exec("a{11"); + assert(a, ["a{11"]); + + /* test zero length matches */ + a = /(?:(?=(abc)))a/.exec("abc"); + assert(a, ["a", "abc"]); + a = /(?:(?=(abc)))?a/.exec("abc"); + assert(a, ["a", undefined]); + a = /(?:(?=(abc))){0,2}a/.exec("abc"); + assert(a, ["a", undefined]); + a = /(?:|[\w])+([0-9])/.exec("123a23"); + assert(a, ["123a23", "3"]); +} + +function test_symbol() +{ + var a, b, obj, c; + a = Symbol("abc"); + obj = {}; + obj[a] = 2; + assert(obj[a], 2); + assert(typeof obj["abc"], "undefined"); + assert(String(a), "Symbol(abc)"); + b = Symbol("abc"); + assert(a == a); + assert(a === a); + assert(a != b); + assert(a !== b); + + b = Symbol.for("abc"); + c = Symbol.for("abc"); + assert(b === c); + assert(b !== a); + + assert(Symbol.keyFor(b), "abc"); + assert(Symbol.keyFor(a), undefined); + + a = Symbol("aaa"); + assert(a.valueOf(), a); + assert(a.toString(), "Symbol(aaa)"); + + b = Object(a); + assert(b.valueOf(), a); + assert(b.toString(), "Symbol(aaa)"); +} + +function test_map() +{ + var a, i, n, tab, o, v; + n = 1000; + + a = new Map(); + for (var i = 0; i < n; i++) { + a.set(i, i); + } + a.set(-2147483648, 1); + assert(a.get(-2147483648), 1); + assert(a.get(-2147483647 - 1), 1); + assert(a.get(-2147483647.5 - 0.5), 1); + + a.set(1n, 1n); + assert(a.get(1n), 1n); + assert(a.get(2n**1000n - (2n**1000n - 1n)), 1n); + + a = new Map(); + tab = []; + for(i = 0; i < n; i++) { + v = { }; + o = { id: i }; + tab[i] = [o, v]; + a.set(o, v); + } + + assert(a.size, n); + for(i = 0; i < n; i++) { + assert(a.get(tab[i][0]), tab[i][1]); + } + + i = 0; + a.forEach(function (v, o) { + assert(o, tab[i++][0]); + assert(a.has(o)); + assert(a.delete(o)); + assert(!a.has(o)); + }); + + assert(a.size, 0); +} + +function test_weak_map() +{ + var a, i, n, tab, o, v, n2; + a = new WeakMap(); + n = 10; + tab = []; + for(i = 0; i < n; i++) { + v = { }; + o = { id: i }; + tab[i] = [o, v]; + a.set(o, v); + } + o = null; + + n2 = n >> 1; + for(i = 0; i < n2; i++) { + a.delete(tab[i][0]); + } + for(i = n2; i < n; i++) { + tab[i][0] = null; /* should remove the object from the WeakMap too */ + } + /* the WeakMap should be empty here */ +} + +function test_generator() +{ + function *f() { + var ret; + yield 1; + ret = yield 2; + assert(ret, "next_arg"); + return 3; + } + function *f2() { + yield 1; + yield 2; + return "ret_val"; + } + function *f1() { + var ret = yield *f2(); + assert(ret, "ret_val"); + return 3; + } + function *f3() { + var ret; + /* test stack consistency with nip_n to handle yield return + + * finally clause */ + try { + ret = 2 + (yield 1); + } catch(e) { + } finally { + ret++; + } + return ret; + } + var g, v; + g = f(); + v = g.next(); + assert(v.value === 1 && v.done === false); + v = g.next(); + assert(v.value === 2 && v.done === false); + v = g.next("next_arg"); + assert(v.value === 3 && v.done === true); + v = g.next(); + assert(v.value === undefined && v.done === true); + + g = f1(); + v = g.next(); + assert(v.value === 1 && v.done === false); + v = g.next(); + assert(v.value === 2 && v.done === false); + v = g.next(); + assert(v.value === 3 && v.done === true); + v = g.next(); + assert(v.value === undefined && v.done === true); + + g = f3(); + v = g.next(); + assert(v.value === 1 && v.done === false); + v = g.next(3); + assert(v.value === 6 && v.done === true); +} + +test(); +test_function(); +test_enum(); +test_array(); +test_string(); +test_math(); +test_number(); +test_eval(); +test_typed_array(); +test_json(); +test_date(); +test_regexp(); +test_symbol(); +test_map(); +test_weak_map(); +test_generator(); diff --git a/source/engine/thirdparty/quickjs/tests/test_closure.js b/source/engine/thirdparty/quickjs/tests/test_closure.js new file mode 100644 index 0000000..f5d4160 --- /dev/null +++ b/source/engine/thirdparty/quickjs/tests/test_closure.js @@ -0,0 +1,221 @@ +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +// load more elaborate version of assert if available +try { __loadScript("test_assert.js"); } catch(e) {} + +/*----------------*/ + +var log_str = ""; + +function log(str) +{ + log_str += str + ","; +} + +function f(a, b, c) +{ + var x = 10; + log("a="+a); + function g(d) { + function h() { + log("d=" + d); + log("x=" + x); + } + log("b=" + b); + log("c=" + c); + h(); + } + g(4); + return g; +} + +var g1 = f(1, 2, 3); +g1(5); + +assert(log_str, "a=1,b=2,c=3,d=4,x=10,b=2,c=3,d=5,x=10,", "closure1"); + +function test_closure1() +{ + function f2() + { + var val = 1; + + function set(a) { + val = a; + } + function get(a) { + return val; + } + return { "set": set, "get": get }; + } + + var obj = f2(); + obj.set(10); + var r; + r = obj.get(); + assert(r, 10, "closure2"); +} + +function test_closure2() +{ + var expr_func = function myfunc1(n) { + function myfunc2(n) { + return myfunc1(n - 1); + } + if (n == 0) + return 0; + else + return myfunc2(n); + }; + var r; + r = expr_func(1); + assert(r, 0, "expr_func"); +} + +function test_closure3() +{ + function fib(n) + { + if (n <= 0) + return 0; + else if (n == 1) + return 1; + else + return fib(n - 1) + fib(n - 2); + } + + var fib_func = function fib1(n) + { + if (n <= 0) + return 0; + else if (n == 1) + return 1; + else + return fib1(n - 1) + fib1(n - 2); + }; + + assert(fib(6), 8, "fib"); + assert(fib_func(6), 8, "fib_func"); +} + +function test_arrow_function() +{ + "use strict"; + + function f1() { + return (() => arguments)(); + } + function f2() { + return (() => this)(); + } + function f3() { + return (() => eval("this"))(); + } + function f4() { + return (() => eval("new.target"))(); + } + var a; + + a = f1(1, 2); + assert(a.length, 2); + assert(a[0] === 1 && a[1] === 2); + + assert(f2.call("this_val") === "this_val"); + assert(f3.call("this_val") === "this_val"); + assert(new f4() === f4); + + var o1 = { f() { return this; } }; + var o2 = { f() { + return (() => eval("super.f()"))(); + } }; + o2.__proto__ = o1; + + assert(o2.f() === o2); +} + +function test_with() +{ + var o1 = { x: "o1", y: "o1" }; + var x = "local"; + eval('var z="var_obj";'); + assert(z === "var_obj"); + with (o1) { + assert(x === "o1"); + assert(eval("x") === "o1"); + var f = function () { + o2 = { x: "o2" }; + with (o2) { + assert(x === "o2"); + assert(y === "o1"); + assert(z === "var_obj"); + assert(eval("x") === "o2"); + assert(eval("y") === "o1"); + assert(eval("z") === "var_obj"); + assert(eval('eval("x")') === "o2"); + } + }; + f(); + } +} + +function test_eval_closure() +{ + var tab; + + tab = []; + for(let i = 0; i < 3; i++) { + eval("tab.push(function g1() { return i; })"); + } + for(let i = 0; i < 3; i++) { + assert(tab[i]() === i); + } + + tab = []; + for(let i = 0; i < 3; i++) { + let f = function f() { + eval("tab.push(function g2() { return i; })"); + }; + f(); + } + for(let i = 0; i < 3; i++) { + assert(tab[i]() === i); + } +} + +function test_eval_const() +{ + const a = 1; + var success = false; + var f = function () { + eval("a = 1"); + }; + try { + f(); + } catch(e) { + success = (e instanceof TypeError); + } + assert(success); +} + +test_closure1(); +test_closure2(); +test_closure3(); +test_arrow_function(); +test_with(); +test_eval_closure(); +test_eval_const(); diff --git a/source/engine/thirdparty/quickjs/tests/test_language.js b/source/engine/thirdparty/quickjs/tests/test_language.js new file mode 100644 index 0000000..0e9bb31 --- /dev/null +++ b/source/engine/thirdparty/quickjs/tests/test_language.js @@ -0,0 +1,620 @@ +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function assert_throws(expected_error, func) +{ + var err = false; + try { + func(); + } catch(e) { + err = true; + if (!(e instanceof expected_error)) { + throw Error("unexpected exception type"); + } + } + if (!err) { + throw Error("expected exception"); + } +} + +// load more elaborate version of assert if available +try { __loadScript("test_assert.js"); } catch(e) {} + +/*----------------*/ + +function test_op1() +{ + var r, a; + r = 1 + 2; + assert(r, 3, "1 + 2 === 3"); + + r = 1 - 2; + assert(r, -1, "1 - 2 === -1"); + + r = -1; + assert(r, -1, "-1 === -1"); + + r = +2; + assert(r, 2, "+2 === 2"); + + r = 2 * 3; + assert(r, 6, "2 * 3 === 6"); + + r = 4 / 2; + assert(r, 2, "4 / 2 === 2"); + + r = 4 % 3; + assert(r, 1, "4 % 3 === 3"); + + r = 4 << 2; + assert(r, 16, "4 << 2 === 16"); + + r = 1 << 0; + assert(r, 1, "1 << 0 === 1"); + + r = 1 << 31; + assert(r, -2147483648, "1 << 31 === -2147483648"); + + r = 1 << 32; + assert(r, 1, "1 << 32 === 1"); + + r = (1 << 31) < 0; + assert(r, true, "(1 << 31) < 0 === true"); + + r = -4 >> 1; + assert(r, -2, "-4 >> 1 === -2"); + + r = -4 >>> 1; + assert(r, 0x7ffffffe, "-4 >>> 1 === 0x7ffffffe"); + + r = 1 & 1; + assert(r, 1, "1 & 1 === 1"); + + r = 0 | 1; + assert(r, 1, "0 | 1 === 1"); + + r = 1 ^ 1; + assert(r, 0, "1 ^ 1 === 0"); + + r = ~1; + assert(r, -2, "~1 === -2"); + + r = !1; + assert(r, false, "!1 === false"); + + assert((1 < 2), true, "(1 < 2) === true"); + + assert((2 > 1), true, "(2 > 1) === true"); + + assert(('b' > 'a'), true, "('b' > 'a') === true"); + + assert(2 ** 8, 256, "2 ** 8 === 256"); +} + +function test_cvt() +{ + assert((NaN | 0) === 0); + assert((Infinity | 0) === 0); + assert(((-Infinity) | 0) === 0); + assert(("12345" | 0) === 12345); + assert(("0x12345" | 0) === 0x12345); + assert(((4294967296 * 3 - 4) | 0) === -4); + + assert(("12345" >>> 0) === 12345); + assert(("0x12345" >>> 0) === 0x12345); + assert((NaN >>> 0) === 0); + assert((Infinity >>> 0) === 0); + assert(((-Infinity) >>> 0) === 0); + assert(((4294967296 * 3 - 4) >>> 0) === (4294967296 - 4)); + assert((19686109595169230000).toString() === "19686109595169230000"); +} + +function test_eq() +{ + assert(null == undefined); + assert(undefined == null); + assert(true == 1); + assert(0 == false); + assert("" == 0); + assert("123" == 123); + assert("122" != 123); + assert((new Number(1)) == 1); + assert(2 == (new Number(2))); + assert((new String("abc")) == "abc"); + assert({} != "abc"); +} + +function test_inc_dec() +{ + var a, r; + + a = 1; + r = a++; + assert(r === 1 && a === 2, true, "++"); + + a = 1; + r = ++a; + assert(r === 2 && a === 2, true, "++"); + + a = 1; + r = a--; + assert(r === 1 && a === 0, true, "--"); + + a = 1; + r = --a; + assert(r === 0 && a === 0, true, "--"); + + a = {x:true}; + a.x++; + assert(a.x, 2, "++"); + + a = {x:true}; + a.x--; + assert(a.x, 0, "--"); + + a = [true]; + a[0]++; + assert(a[0], 2, "++"); + + a = {x:true}; + r = a.x++; + assert(r === 1 && a.x === 2, true, "++"); + + a = {x:true}; + r = a.x--; + assert(r === 1 && a.x === 0, true, "--"); + + a = [true]; + r = a[0]++; + assert(r === 1 && a[0] === 2, true, "++"); + + a = [true]; + r = a[0]--; + assert(r === 1 && a[0] === 0, true, "--"); +} + +function F(x) +{ + this.x = x; +} + +function test_op2() +{ + var a, b; + a = new Object; + a.x = 1; + assert(a.x, 1, "new"); + b = new F(2); + assert(b.x, 2, "new"); + + a = {x : 2}; + assert(("x" in a), true, "in"); + assert(("y" in a), false, "in"); + + a = {}; + assert((a instanceof Object), true, "instanceof"); + assert((a instanceof String), false, "instanceof"); + + assert((typeof 1), "number", "typeof"); + assert((typeof Object), "function", "typeof"); + assert((typeof null), "object", "typeof"); + assert((typeof unknown_var), "undefined", "typeof"); + + a = {x: 1, if: 2, async: 3}; + assert(a.if === 2); + assert(a.async === 3); +} + +function test_delete() +{ + var a, err; + + a = {x: 1, y: 1}; + assert((delete a.x), true, "delete"); + assert(("x" in a), false, "delete"); + + /* the following are not tested by test262 */ + assert(delete "abc"[100], true); + + err = false; + try { + delete null.a; + } catch(e) { + err = (e instanceof TypeError); + } + assert(err, true, "delete"); + + err = false; + try { + a = { f() { delete super.a; } }; + a.f(); + } catch(e) { + err = (e instanceof ReferenceError); + } + assert(err, true, "delete"); +} + +function test_prototype() +{ + var f = function f() { }; + assert(f.prototype.constructor, f, "prototype"); + + var g = function g() { }; + /* QuickJS bug */ + Object.defineProperty(g, "prototype", { writable: false }); + assert(g.prototype.constructor, g, "prototype"); +} + +function test_arguments() +{ + function f2() { + assert(arguments.length, 2, "arguments"); + assert(arguments[0], 1, "arguments"); + assert(arguments[1], 3, "arguments"); + } + f2(1, 3); +} + +function test_class() +{ + var o; + class C { + constructor() { + this.x = 10; + } + f() { + return 1; + } + static F() { + return -1; + } + get y() { + return 12; + } + }; + class D extends C { + constructor() { + super(); + this.z = 20; + } + g() { + return 2; + } + static G() { + return -2; + } + h() { + return super.f(); + } + static H() { + return super["F"](); + } + } + + assert(C.F() === -1); + assert(Object.getOwnPropertyDescriptor(C.prototype, "y").get.name === "get y"); + + o = new C(); + assert(o.f() === 1); + assert(o.x === 10); + + assert(D.F() === -1); + assert(D.G() === -2); + assert(D.H() === -1); + + o = new D(); + assert(o.f() === 1); + assert(o.g() === 2); + assert(o.x === 10); + assert(o.z === 20); + assert(o.h() === 1); + + /* test class name scope */ + var E1 = class E { static F() { return E; } }; + assert(E1 === E1.F()); + + class S { + static x = 42; + static y = S.x; + static z = this.x; + } + assert(S.x === 42); + assert(S.y === 42); + assert(S.z === 42); +}; + +function test_template() +{ + var a, b; + b = 123; + a = `abc${b}d`; + assert(a, "abc123d"); + + a = String.raw `abc${b}d`; + assert(a, "abc123d"); + + a = "aaa"; + b = "bbb"; + assert(`aaa${a, b}ccc`, "aaabbbccc"); +} + +function test_template_skip() +{ + var a = "Bar"; + var { b = `${a + `a${a}` }baz` } = {}; + assert(b, "BaraBarbaz"); +} + +function test_object_literal() +{ + var x = 0, get = 1, set = 2; async = 3; + a = { get: 2, set: 3, async: 4 }; + assert(JSON.stringify(a), '{"get":2,"set":3,"async":4}'); + + a = { x, get, set, async }; + assert(JSON.stringify(a), '{"x":0,"get":1,"set":2,"async":3}'); +} + +function test_regexp_skip() +{ + var a, b; + [a, b = /abc\(/] = [1]; + assert(a === 1); + + [a, b =/abc\(/] = [2]; + assert(a === 2); +} + +function test_labels() +{ + do x: { break x; } while(0); + if (1) + x: { break x; } + else + x: { break x; } + with ({}) x: { break x; }; + while (0) x: { break x; }; +} + +function test_destructuring() +{ + function * g () { return 0; }; + var [x] = g(); + assert(x, void 0); +} + +function test_spread() +{ + var x; + x = [1, 2, ...[3, 4]]; + assert(x.toString(), "1,2,3,4"); + + x = [ ...[ , ] ]; + assert(Object.getOwnPropertyNames(x).toString(), "0,length"); +} + +function test_function_length() +{ + assert( ((a, b = 1, c) => {}).length, 1); + assert( (([a,b]) => {}).length, 1); + assert( (({a,b}) => {}).length, 1); + assert( ((c, [a,b] = 1, d) => {}).length, 1); +} + +function test_argument_scope() +{ + var f; + var c = "global"; + + (function() { + "use strict"; + // XXX: node only throws in strict mode + f = function(a = eval("var arguments")) {}; + assert_throws(SyntaxError, f); + })(); + + f = function(a = eval("1"), b = arguments[0]) { return b; }; + assert(f(12), 12); + + f = function(a, b = arguments[0]) { return b; }; + assert(f(12), 12); + + f = function(a, b = () => arguments) { return b; }; + assert(f(12)()[0], 12); + + f = function(a = eval("1"), b = () => arguments) { return b; }; + assert(f(12)()[0], 12); + + (function() { + "use strict"; + f = function(a = this) { return a; }; + assert(f.call(123), 123); + + f = function f(a = f) { return a; }; + assert(f(), f); + + f = function f(a = eval("f")) { return a; }; + assert(f(), f); + })(); + + f = (a = eval("var c = 1"), probe = () => c) => { + var c = 2; + assert(c, 2); + assert(probe(), 1); + } + f(); + + f = (a = eval("var arguments = 1"), probe = () => arguments) => { + var arguments = 2; + assert(arguments, 2); + assert(probe(), 1); + } + f(); + + f = function f(a = eval("var c = 1"), b = c, probe = () => c) { + assert(b, 1); + assert(c, 1); + assert(probe(), 1) + } + f(); + + assert(c, "global"); + f = function f(a, b = c, probe = () => c) { + eval("var c = 1"); + assert(c, 1); + assert(b, "global"); + assert(probe(), "global") + } + f(); + assert(c, "global"); + + f = function f(a = eval("var c = 1"), probe = (d = eval("c")) => d) { + assert(probe(), 1) + } + f(); +} + +function test_function_expr_name() +{ + var f; + + /* non strict mode test : assignment to the function name silently + fails */ + + f = function myfunc() { + myfunc = 1; + return myfunc; + }; + assert(f(), f); + + f = function myfunc() { + myfunc = 1; + (() => { + myfunc = 1; + })(); + return myfunc; + }; + assert(f(), f); + + f = function myfunc() { + eval("myfunc = 1"); + return myfunc; + }; + assert(f(), f); + + /* strict mode test : assignment to the function name raises a + TypeError exception */ + + f = function myfunc() { + "use strict"; + myfunc = 1; + }; + assert_throws(TypeError, f); + + f = function myfunc() { + "use strict"; + (() => { + myfunc = 1; + })(); + }; + assert_throws(TypeError, f); + + f = function myfunc() { + "use strict"; + eval("myfunc = 1"); + }; + assert_throws(TypeError, f); +} + +function test_parse_semicolon() +{ + /* 'yield' or 'await' may not be considered as a token if the + previous ';' is missing */ + function *f() + { + function func() { + } + yield 1; + var h = x => x + 1 + yield 2; + } + async function g() + { + function func() { + } + await 1; + var h = x => x + 1 + await 2; + } +} + +function test_parse_arrow_function() +{ + assert(typeof eval("() => {}\n() => {}"), "function"); + assert(eval("() => {}\n+1"), 1); + assert(typeof eval("x => {}\n() => {}"), "function"); + assert(typeof eval("async () => {}\n() => {}"), "function"); + assert(typeof eval("async x => {}\n() => {}"), "function"); +} + +/* optional chaining tests not present in test262 */ +function test_optional_chaining() +{ + var a, z; + z = null; + a = { b: { c: 2 } }; + assert(delete z?.b.c, true); + assert(delete a?.b.c, true); + assert(JSON.stringify(a), '{"b":{}}', "optional chaining delete"); + + a = { b: { c: 2 } }; + assert(delete z?.b["c"], true); + assert(delete a?.b["c"], true); + assert(JSON.stringify(a), '{"b":{}}'); + + a = { + b() { return this._b; }, + _b: { c: 42 } + }; + + assert((a?.b)().c, 42); + + assert((a?.["b"])().c, 42); +} + +test_op1(); +test_cvt(); +test_eq(); +test_inc_dec(); +test_op2(); +test_delete(); +test_prototype(); +test_arguments(); +test_class(); +test_template(); +test_template_skip(); +test_object_literal(); +test_regexp_skip(); +test_labels(); +test_destructuring(); +test_spread(); +test_function_length(); +test_argument_scope(); +test_function_expr_name(); +test_parse_semicolon(); +test_optional_chaining(); +test_parse_arrow_function(); diff --git a/source/engine/thirdparty/quickjs/tests/test_loop.js b/source/engine/thirdparty/quickjs/tests/test_loop.js new file mode 100644 index 0000000..d387cad --- /dev/null +++ b/source/engine/thirdparty/quickjs/tests/test_loop.js @@ -0,0 +1,392 @@ +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +// load more elaborate version of assert if available +try { __loadScript("test_assert.js"); } catch(e) {} + +/*----------------*/ + +function test_while() +{ + var i, c; + i = 0; + c = 0; + while (i < 3) { + c++; + i++; + } + assert(c === 3); +} + +function test_while_break() +{ + var i, c; + i = 0; + c = 0; + while (i < 3) { + c++; + if (i == 1) + break; + i++; + } + assert(c === 2 && i === 1); +} + +function test_do_while() +{ + var i, c; + i = 0; + c = 0; + do { + c++; + i++; + } while (i < 3); + assert(c === 3 && i === 3); +} + +function test_for() +{ + var i, c; + c = 0; + for(i = 0; i < 3; i++) { + c++; + } + assert(c === 3 && i === 3); + + c = 0; + for(var j = 0; j < 3; j++) { + c++; + } + assert(c === 3 && j === 3); +} + +function test_for_in() +{ + var i, tab, a, b; + + tab = []; + for(i in {x:1, y: 2}) { + tab.push(i); + } + assert(tab.toString(), "x,y", "for_in"); + + /* prototype chain test */ + a = {x:2, y: 2, "1": 3}; + b = {"4" : 3 }; + Object.setPrototypeOf(a, b); + tab = []; + for(i in a) { + tab.push(i); + } + assert(tab.toString(), "1,x,y,4", "for_in"); + + /* non enumerable properties hide enumerables ones in the + prototype chain */ + a = {y: 2, "1": 3}; + Object.defineProperty(a, "x", { value: 1 }); + b = {"x" : 3 }; + Object.setPrototypeOf(a, b); + tab = []; + for(i in a) { + tab.push(i); + } + assert(tab.toString(), "1,y", "for_in"); + + /* array optimization */ + a = []; + for(i = 0; i < 10; i++) + a.push(i); + tab = []; + for(i in a) { + tab.push(i); + } + assert(tab.toString(), "0,1,2,3,4,5,6,7,8,9", "for_in"); + + /* iterate with a field */ + a={x:0}; + tab = []; + for(a.x in {x:1, y: 2}) { + tab.push(a.x); + } + assert(tab.toString(), "x,y", "for_in"); + + /* iterate with a variable field */ + a=[0]; + tab = []; + for(a[0] in {x:1, y: 2}) { + tab.push(a[0]); + } + assert(tab.toString(), "x,y", "for_in"); + + /* variable definition in the for in */ + tab = []; + for(var j in {x:1, y: 2}) { + tab.push(j); + } + assert(tab.toString(), "x,y", "for_in"); + + /* variable assigment in the for in */ + tab = []; + for(var k = 2 in {x:1, y: 2}) { + tab.push(k); + } + assert(tab.toString(), "x,y", "for_in"); +} + +function test_for_in2() +{ + var i; + tab = []; + for(i in {x:1, y: 2, z:3}) { + if (i === "y") + continue; + tab.push(i); + } + assert(tab.toString() == "x,z"); + + tab = []; + for(i in {x:1, y: 2, z:3}) { + if (i === "z") + break; + tab.push(i); + } + assert(tab.toString() == "x,y"); +} + +function test_for_in_proxy() { + let removed_key = ""; + let target = {} + let proxy = new Proxy(target, { + ownKeys: function() { + return ["a", "b", "c"]; + }, + getOwnPropertyDescriptor: function(target, key) { + if (removed_key != "" && key == removed_key) + return undefined; + else + return { enumerable: true, configurable: true, value: this[key] }; + } + }); + let str = ""; + for(let o in proxy) { + str += " " + o; + if (o == "a") + removed_key = "b"; + } + assert(str == " a c"); +} + +function test_for_break() +{ + var i, c; + c = 0; + L1: for(i = 0; i < 3; i++) { + c++; + if (i == 0) + continue; + while (1) { + break L1; + } + } + assert(c === 2 && i === 1); +} + +function test_switch1() +{ + var i, a, s; + s = ""; + for(i = 0; i < 3; i++) { + a = "?"; + switch(i) { + case 0: + a = "a"; + break; + case 1: + a = "b"; + break; + default: + a = "c"; + break; + } + s += a; + } + assert(s === "abc" && i === 3); +} + +function test_switch2() +{ + var i, a, s; + s = ""; + for(i = 0; i < 4; i++) { + a = "?"; + switch(i) { + case 0: + a = "a"; + break; + case 1: + a = "b"; + break; + case 2: + continue; + default: + a = "" + i; + break; + } + s += a; + } + assert(s === "ab3" && i === 4); +} + +function test_try_catch1() +{ + try { + throw "hello"; + } catch (e) { + assert(e, "hello", "catch"); + return; + } + assert(false, "catch"); +} + +function test_try_catch2() +{ + var a; + try { + a = 1; + } catch (e) { + a = 2; + } + assert(a, 1, "catch"); +} + +function test_try_catch3() +{ + var s; + s = ""; + try { + s += "t"; + } catch (e) { + s += "c"; + } finally { + s += "f"; + } + assert(s, "tf", "catch"); +} + +function test_try_catch4() +{ + var s; + s = ""; + try { + s += "t"; + throw "c"; + } catch (e) { + s += e; + } finally { + s += "f"; + } + assert(s, "tcf", "catch"); +} + +function test_try_catch5() +{ + var s; + s = ""; + for(;;) { + try { + s += "t"; + break; + s += "b"; + } finally { + s += "f"; + } + } + assert(s, "tf", "catch"); +} + +function test_try_catch6() +{ + function f() { + try { + s += 't'; + return 1; + } finally { + s += "f"; + } + } + var s = ""; + assert(f() === 1); + assert(s, "tf", "catch6"); +} + +function test_try_catch7() +{ + var s; + s = ""; + + try { + try { + s += "t"; + throw "a"; + } finally { + s += "f"; + } + } catch(e) { + s += e; + } finally { + s += "g"; + } + assert(s, "tfag", "catch"); +} + +function test_try_catch8() +{ + var i, s; + + s = ""; + for(var i in {x:1, y:2}) { + try { + s += i; + throw "a"; + } catch (e) { + s += e; + } finally { + s += "f"; + } + } + assert(s === "xafyaf"); +} + +test_while(); +test_while_break(); +test_do_while(); +test_for(); +test_for_break(); +test_switch1(); +test_switch2(); +test_for_in(); +test_for_in2(); +test_for_in_proxy(); + +test_try_catch1(); +test_try_catch2(); +test_try_catch3(); +test_try_catch4(); +test_try_catch5(); +test_try_catch6(); +test_try_catch7(); +test_try_catch8(); diff --git a/source/engine/thirdparty/quickjs/tests/test_op_overloading.js b/source/engine/thirdparty/quickjs/tests/test_op_overloading.js new file mode 100644 index 0000000..269abb2 --- /dev/null +++ b/source/engine/thirdparty/quickjs/tests/test_op_overloading.js @@ -0,0 +1,207 @@ +"use strict"; + +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +/* operators overloading with Operators.create() */ +function test_operators_create() { + class Vec2 + { + constructor(x, y) { + this.x = x; + this.y = y; + } + static mul_scalar(p1, a) { + var r = new Vec2(); + r.x = p1.x * a; + r.y = p1.y * a; + return r; + } + toString() { + return "Vec2(" + this.x + "," + this.y + ")"; + } + } + + Vec2.prototype[Symbol.operatorSet] = Operators.create( + { + "+"(p1, p2) { + var r = new Vec2(); + r.x = p1.x + p2.x; + r.y = p1.y + p2.y; + return r; + }, + "-"(p1, p2) { + var r = new Vec2(); + r.x = p1.x - p2.x; + r.y = p1.y - p2.y; + return r; + }, + "=="(a, b) { + return a.x == b.x && a.y == b.y; + }, + "<"(a, b) { + var r; + /* lexicographic order */ + if (a.x == b.x) + r = (a.y < b.y); + else + r = (a.x < b.x); + return r; + }, + "++"(a) { + var r = new Vec2(); + r.x = a.x + 1; + r.y = a.y + 1; + return r; + } + }, + { + left: Number, + "*"(a, b) { + return Vec2.mul_scalar(b, a); + } + }, + { + right: Number, + "*"(a, b) { + return Vec2.mul_scalar(a, b); + } + }); + + var a = new Vec2(1, 2); + var b = new Vec2(3, 4); + var r; + + r = a * 2 + 3 * b; + assert(r.x === 11 && r.y === 16); + assert(a == a, true); + assert(a == b, false); + assert(a != a, false); + assert(a < b, true); + assert(a <= b, true); + assert(b < a, false); + assert(b <= a, false); + assert(a <= a, true); + assert(a >= a, true); + a++; + assert(a.x === 2 && a.y === 3); + r = ++a; + assert(a.x === 3 && a.y === 4); + assert(r === a); +} + +/* operators overloading thru inheritance */ +function test_operators() +{ + var Vec2; + + function mul_scalar(p1, a) { + var r = new Vec2(); + r.x = p1.x * a; + r.y = p1.y * a; + return r; + } + + var vec2_ops = Operators({ + "+"(p1, p2) { + var r = new Vec2(); + r.x = p1.x + p2.x; + r.y = p1.y + p2.y; + return r; + }, + "-"(p1, p2) { + var r = new Vec2(); + r.x = p1.x - p2.x; + r.y = p1.y - p2.y; + return r; + }, + "=="(a, b) { + return a.x == b.x && a.y == b.y; + }, + "<"(a, b) { + var r; + /* lexicographic order */ + if (a.x == b.x) + r = (a.y < b.y); + else + r = (a.x < b.x); + return r; + }, + "++"(a) { + var r = new Vec2(); + r.x = a.x + 1; + r.y = a.y + 1; + return r; + } + }, + { + left: Number, + "*"(a, b) { + return mul_scalar(b, a); + } + }, + { + right: Number, + "*"(a, b) { + return mul_scalar(a, b); + } + }); + + Vec2 = class Vec2 extends vec2_ops + { + constructor(x, y) { + super(); + this.x = x; + this.y = y; + } + toString() { + return "Vec2(" + this.x + "," + this.y + ")"; + } + } + + var a = new Vec2(1, 2); + var b = new Vec2(3, 4); + var r; + + r = a * 2 + 3 * b; + assert(r.x === 11 && r.y === 16); + assert(a == a, true); + assert(a == b, false); + assert(a != a, false); + assert(a < b, true); + assert(a <= b, true); + assert(b < a, false); + assert(b <= a, false); + assert(a <= a, true); + assert(a >= a, true); + a++; + assert(a.x === 2 && a.y === 3); + r = ++a; + assert(a.x === 3 && a.y === 4); + assert(r === a); +} + +function test_default_op() +{ + assert(Object(1) + 2, 3); + assert(Object(1) + true, 2); + assert(-Object(1), -1); +} + +test_operators_create(); +test_operators(); +test_default_op(); diff --git a/source/engine/thirdparty/quickjs/tests/test_qjscalc.js b/source/engine/thirdparty/quickjs/tests/test_qjscalc.js new file mode 100644 index 0000000..e97dd31 --- /dev/null +++ b/source/engine/thirdparty/quickjs/tests/test_qjscalc.js @@ -0,0 +1,256 @@ +"use math"; +"use strict"; + +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function assertThrows(err, func) +{ + var ex; + ex = false; + try { + func(); + } catch(e) { + ex = true; + assert(e instanceof err); + } + assert(ex, true, "exception expected"); +} + +// load more elaborate version of assert if available +try { __loadScript("test_assert.js"); } catch(e) {} + +/*----------------*/ + +function pow(a, n) +{ + var r, i; + r = 1; + for(i = 0; i < n; i++) + r *= a; + return r; +} + +function test_integer() +{ + var a, r; + a = pow(3, 100); + assert((a - 1) != a); + assert(a == 515377520732011331036461129765621272702107522001); + assert(a == 0x5a4653ca673768565b41f775d6947d55cf3813d1); + assert(Integer.isInteger(1) === true); + assert(Integer.isInteger(1.0) === false); + + assert(Integer.floorLog2(0) === -1); + assert(Integer.floorLog2(7) === 2); + + r = 1 << 31; + assert(r, 2147483648, "1 << 31 === 2147483648"); + + r = 1 << 32; + assert(r, 4294967296, "1 << 32 === 4294967296"); + + r = (1 << 31) < 0; + assert(r, false, "(1 << 31) < 0 === false"); + + assert(typeof 1 === "number"); + assert(typeof 9007199254740991 === "number"); + assert(typeof 9007199254740992 === "bigint"); +} + +function test_float() +{ + assert(typeof 1.0 === "bigfloat"); + assert(1 == 1.0); + assert(1 !== 1.0); +} + +/* jscalc tests */ + +function test_modulo() +{ + var i, p, a, b; + + /* Euclidian modulo operator */ + assert((-3) % 2 == 1); + assert(3 % (-2) == 1); + + p = 101; + for(i = 1; i < p; i++) { + a = Integer.invmod(i, p); + assert(a >= 0 && a < p); + assert((i * a) % p == 1); + } + + assert(Integer.isPrime(2^107-1)); + assert(!Integer.isPrime((2^107-1) * (2^89-1))); + a = Integer.factor((2^89-1)*2^3*11*13^2*1009); + assert(a == [ 2,2,2,11,13,13,1009,618970019642690137449562111 ]); +} + +function test_fraction() +{ + assert((1/3 + 1).toString(), "4/3") + assert((2/3)^30, 1073741824/205891132094649); + assert(1/3 < 2/3); + assert(1/3 < 1); + assert(1/3 == 1.0/3); + assert(1.0/3 < 2/3); +} + +function test_mod() +{ + var a, b, p; + + a = Mod(3, 101); + b = Mod(-1, 101); + assert((a + b) == Mod(2, 101)); + assert(a ^ 100 == Mod(1, 101)); + + p = 2 ^ 607 - 1; /* mersenne prime */ + a = Mod(3, p) ^ (p - 1); + assert(a == Mod(1, p)); +} + +function test_polynomial() +{ + var a, b, q, r, t, i; + a = (1 + X) ^ 4; + assert(a == X^4+4*X^3+6*X^2+4*X+1); + + r = (1 + X); + q = (1+X+X^2); + b = (1 - X^2); + a = q * b + r; + t = Polynomial.divrem(a, b); + assert(t[0] == q); + assert(t[1] == r); + + a = 1 + 2*X + 3*X^2; + assert(a.apply(0.1) == 1.23); + + a = 1-2*X^2+2*X^3; + assert(deriv(a) == (6*X^2-4*X)); + assert(deriv(integ(a)) == a); + + a = (X-1)*(X-2)*(X-3)*(X-4)*(X-0.1); + r = polroots(a); + for(i = 0; i < r.length; i++) { + b = abs(a.apply(r[i])); + assert(b <= 1e-13); + } +} + +function test_poly_mod() +{ + var a, p; + + /* modulo using polynomials */ + p = X^2 + X + 1; + a = PolyMod(3+X, p) ^ 10; + assert(a == PolyMod(-3725*X-18357, p)); + + a = PolyMod(1/X, 1+X^2); + assert(a == PolyMod(-X, X^2+1)); +} + +function test_rfunc() +{ + var a; + a = (X+1)/((X+1)*(X-1)); + assert(a == 1/(X-1)); + a = (X + 2) / (X - 2); + assert(a.apply(1/3) == -7/5); + + assert(deriv((X^2-X+1)/(X-1)) == (X^2-2*X)/(X^2-2*X+1)); +} + +function test_series() +{ + var a, b; + a = 1+X+O(X^5); + b = a.inverse(); + assert(b == 1-X+X^2-X^3+X^4+O(X^5)); + assert(deriv(b) == -1+2*X-3*X^2+4*X^3+O(X^4)); + assert(deriv(integ(b)) == b); + + a = Series(1/(1-X), 5); + assert(a == 1+X+X^2+X^3+X^4+O(X^5)); + b = a.apply(0.1); + assert(b == 1.1111); + + assert(exp(3*X^2+O(X^10)) == 1+3*X^2+9/2*X^4+9/2*X^6+27/8*X^8+O(X^10)); + assert(sin(X+O(X^6)) == X-1/6*X^3+1/120*X^5+O(X^6)); + assert(cos(X+O(X^6)) == 1-1/2*X^2+1/24*X^4+O(X^6)); + assert(tan(X+O(X^8)) == X+1/3*X^3+2/15*X^5+17/315*X^7+O(X^8)); + assert((1+X+O(X^6))^(2+X) == 1+2*X+2*X^2+3/2*X^3+5/6*X^4+5/12*X^5+O(X^6)); +} + +function test_matrix() +{ + var a, b, r; + a = [[1, 2],[3, 4]]; + b = [3, 4]; + r = a * b; + assert(r == [11, 25]); + r = (a^-1) * 2; + assert(r == [[-4, 2],[3, -1]]); + + assert(norm2([1,2,3]) == 14); + + assert(diag([1,2,3]) == [ [ 1, 0, 0 ], [ 0, 2, 0 ], [ 0, 0, 3 ] ]); + assert(trans(a) == [ [ 1, 3 ], [ 2, 4 ] ]); + assert(trans([1,2,3]) == [[1,2,3]]); + assert(trace(a) == 5); + + assert(charpoly(Matrix.hilbert(4)) == X^4-176/105*X^3+3341/12600*X^2-41/23625*X+1/6048000); + assert(det(Matrix.hilbert(4)) == 1/6048000); + + a = [[1,2,1],[-2,-3,1],[3,5,0]]; + assert(rank(a) == 2); + assert(ker(a) == [ [ 5 ], [ -3 ], [ 1 ] ]); + + assert(dp([1, 2, 3], [3, -4, -7]) === -26); + assert(cp([1, 2, 3], [3, -4, -7]) == [ -2, 16, -10 ]); +} + +function assert_eq(a, ref) +{ + assert(abs(a / ref - 1.0) <= 1e-15); +} + +function test_trig() +{ + assert_eq(sin(1/2), 0.479425538604203); + assert_eq(sin(2+3*I), 9.154499146911428-4.168906959966565*I); + assert_eq(cos(2+3*I), -4.189625690968807-9.109227893755337*I); + assert_eq((2+0.5*I)^(1.1-0.5*I), 2.494363021357619-0.23076804554558092*I); + assert_eq(sqrt(2*I), 1 + I); +} + +test_integer(); +test_float(); + +test_modulo(); +test_fraction(); +test_mod(); +test_polynomial(); +test_poly_mod(); +test_rfunc(); +test_series(); +test_matrix(); +test_trig(); diff --git a/source/engine/thirdparty/quickjs/tests/test_std.js b/source/engine/thirdparty/quickjs/tests/test_std.js new file mode 100644 index 0000000..c844869 --- /dev/null +++ b/source/engine/thirdparty/quickjs/tests/test_std.js @@ -0,0 +1,307 @@ +#! (shebang test) +import * as std from "std"; +import * as os from "os"; + +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +// load more elaborate version of assert if available +try { std.loadScript("test_assert.js"); } catch(e) {} + +/*----------------*/ + +function test_printf() +{ + assert(std.sprintf("a=%d s=%s", 123, "abc"), "a=123 s=abc"); + assert(std.sprintf("%010d", 123), "0000000123"); + assert(std.sprintf("%x", -2), "fffffffe"); + assert(std.sprintf("%lx", -2), "fffffffffffffffe"); + assert(std.sprintf("%10.1f", 2.1), " 2.1"); + assert(std.sprintf("%*.*f", 10, 2, -2.13), " -2.13"); + assert(std.sprintf("%#lx", 0x7fffffffffffffffn), "0x7fffffffffffffff"); +} + +function test_file1() +{ + var f, len, str, size, buf, ret, i, str1; + + f = std.tmpfile(); + str = "hello world\n"; + f.puts(str); + + f.seek(0, std.SEEK_SET); + str1 = f.readAsString(); + assert(str1 === str); + + f.seek(0, std.SEEK_END); + size = f.tell(); + assert(size === str.length); + + f.seek(0, std.SEEK_SET); + + buf = new Uint8Array(size); + ret = f.read(buf.buffer, 0, size); + assert(ret === size); + for(i = 0; i < size; i++) + assert(buf[i] === str.charCodeAt(i)); + + f.close(); +} + +function test_file2() +{ + var f, str, i, size; + f = std.tmpfile(); + str = "hello world\n"; + size = str.length; + for(i = 0; i < size; i++) + f.putByte(str.charCodeAt(i)); + f.seek(0, std.SEEK_SET); + for(i = 0; i < size; i++) { + assert(str.charCodeAt(i) === f.getByte()); + } + assert(f.getByte() === -1); + f.close(); +} + +function test_getline() +{ + var f, line, line_count, lines, i; + + lines = ["hello world", "line 1", "line 2" ]; + f = std.tmpfile(); + for(i = 0; i < lines.length; i++) { + f.puts(lines[i], "\n"); + } + + f.seek(0, std.SEEK_SET); + assert(!f.eof()); + line_count = 0; + for(;;) { + line = f.getline(); + if (line === null) + break; + assert(line == lines[line_count]); + line_count++; + } + assert(f.eof()); + assert(line_count === lines.length); + + f.close(); +} + +function test_popen() +{ + var str, f, fname = "tmp_file.txt"; + var content = "hello world"; + + f = std.open(fname, "w"); + f.puts(content); + f.close(); + + /* test loadFile */ + assert(std.loadFile(fname), content); + + /* execute the 'cat' shell command */ + f = std.popen("cat " + fname, "r"); + str = f.readAsString(); + f.close(); + + assert(str, content); + + os.remove(fname); +} + +function test_ext_json() +{ + var expected, input, obj; + expected = '{"x":false,"y":true,"z2":null,"a":[1,8,160],"s":"str"}'; + input = `{ "x":false, /*comments are allowed */ + "y":true, // also a comment + z2:null, // unquoted property names + "a":[+1,0o10,0xa0,], // plus prefix, octal, hexadecimal + "s":"str",} // trailing comma in objects and arrays + `; + obj = std.parseExtJSON(input); + assert(JSON.stringify(obj), expected); +} + +function test_os() +{ + var fd, fpath, fname, fdir, buf, buf2, i, files, err, fdate, st, link_path; + + const stdinIsTTY = !os.exec(["/bin/sh", "-c", "test -t 0"], { usePath: false }); + + assert(os.isatty(0), stdinIsTTY, `isatty(STDIN)`); + + fdir = "test_tmp_dir"; + fname = "tmp_file.txt"; + fpath = fdir + "/" + fname; + link_path = fdir + "/test_link"; + + os.remove(link_path); + os.remove(fpath); + os.remove(fdir); + + err = os.mkdir(fdir, 0o755); + assert(err === 0); + + fd = os.open(fpath, os.O_RDWR | os.O_CREAT | os.O_TRUNC); + assert(fd >= 0); + + buf = new Uint8Array(10); + for(i = 0; i < buf.length; i++) + buf[i] = i; + assert(os.write(fd, buf.buffer, 0, buf.length) === buf.length); + + assert(os.seek(fd, 0, std.SEEK_SET) === 0); + buf2 = new Uint8Array(buf.length); + assert(os.read(fd, buf2.buffer, 0, buf2.length) === buf2.length); + + for(i = 0; i < buf.length; i++) + assert(buf[i] == buf2[i]); + + if (typeof BigInt !== "undefined") { + assert(os.seek(fd, BigInt(6), std.SEEK_SET), BigInt(6)); + assert(os.read(fd, buf2.buffer, 0, 1) === 1); + assert(buf[6] == buf2[0]); + } + + assert(os.close(fd) === 0); + + [files, err] = os.readdir(fdir); + assert(err, 0); + assert(files.indexOf(fname) >= 0); + + fdate = 10000; + + err = os.utimes(fpath, fdate, fdate); + assert(err, 0); + + [st, err] = os.stat(fpath); + assert(err, 0); + assert(st.mode & os.S_IFMT, os.S_IFREG); + assert(st.mtime, fdate); + + err = os.symlink(fname, link_path); + assert(err === 0); + + [st, err] = os.lstat(link_path); + assert(err, 0); + assert(st.mode & os.S_IFMT, os.S_IFLNK); + + [buf, err] = os.readlink(link_path); + assert(err, 0); + assert(buf, fname); + + assert(os.remove(link_path) === 0); + + [buf, err] = os.getcwd(); + assert(err, 0); + + [buf2, err] = os.realpath("."); + assert(err, 0); + + assert(buf, buf2); + + assert(os.remove(fpath) === 0); + + fd = os.open(fpath, os.O_RDONLY); + assert(fd < 0); + + assert(os.remove(fdir) === 0); +} + +function test_os_exec() +{ + var ret, fds, pid, f, status; + + ret = os.exec(["true"]); + assert(ret, 0); + + ret = os.exec(["/bin/sh", "-c", "exit 1"], { usePath: false }); + assert(ret, 1); + + fds = os.pipe(); + pid = os.exec(["sh", "-c", "echo $FOO"], { + stdout: fds[1], + block: false, + env: { FOO: "hello" }, + } ); + assert(pid >= 0); + os.close(fds[1]); /* close the write end (as it is only in the child) */ + f = std.fdopen(fds[0], "r"); + assert(f.getline(), "hello"); + assert(f.getline(), null); + f.close(); + [ret, status] = os.waitpid(pid, 0); + assert(ret, pid); + assert(status & 0x7f, 0); /* exited */ + assert(status >> 8, 0); /* exit code */ + + pid = os.exec(["cat"], { block: false } ); + assert(pid >= 0); + os.kill(pid, os.SIGTERM); + [ret, status] = os.waitpid(pid, 0); + assert(ret, pid); + assert(status !== 0, true, `expect nonzero exit code (got ${status})`); + assert(status & 0x7f, os.SIGTERM); +} + +function test_timer() +{ + var th, i; + + /* just test that a timer can be inserted and removed */ + th = []; + for(i = 0; i < 3; i++) + th[i] = os.setTimeout(function () { }, 1000); + for(i = 0; i < 3; i++) + os.clearTimeout(th[i]); +} + +/* test closure variable handling when freeing asynchronous + function */ +function test_async_gc() +{ + (async function run () { + let obj = {} + + let done = () => { + obj + std.gc(); + } + + Promise.resolve().then(done) + + const p = new Promise(() => {}) + + await p + })(); +} + +test_printf(); +test_file1(); +test_file2(); +test_getline(); +test_popen(); +test_os(); +test_os_exec(); +test_timer(); +test_ext_json(); +test_async_gc(); + diff --git a/source/engine/thirdparty/quickjs/tests/test_worker.js b/source/engine/thirdparty/quickjs/tests/test_worker.js new file mode 100644 index 0000000..4b52bf8 --- /dev/null +++ b/source/engine/thirdparty/quickjs/tests/test_worker.js @@ -0,0 +1,62 @@ +/* os.Worker API test */ +import * as std from "std"; +import * as os from "os"; + +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +var worker; + +function test_worker() +{ + var counter; + + worker = new os.Worker("./test_worker_module.js"); + + counter = 0; + worker.onmessage = function (e) { + var ev = e.data; +// print("recv", JSON.stringify(ev)); + switch(ev.type) { + case "num": + assert(ev.num, counter); + counter++; + if (counter == 10) { + /* test SharedArrayBuffer modification */ + let sab = new SharedArrayBuffer(10); + let buf = new Uint8Array(sab); + worker.postMessage({ type: "sab", buf: buf }); + } + break; + case "sab_done": + { + let buf = ev.buf; + /* check that the SharedArrayBuffer was modified */ + assert(buf[2], 10); + worker.postMessage({ type: "abort" }); + } + break; + case "done": + /* terminate */ + worker.onmessage = null; + break; + } + }; +} + + +test_worker(); diff --git a/source/engine/thirdparty/quickjs/tests/test_worker_module.js b/source/engine/thirdparty/quickjs/tests/test_worker_module.js new file mode 100644 index 0000000..bad1a6b --- /dev/null +++ b/source/engine/thirdparty/quickjs/tests/test_worker_module.js @@ -0,0 +1,32 @@ +/* Worker code for test_worker.js */ +import * as std from "std"; +import * as os from "os"; + +var parent = os.Worker.parent; + +function handle_msg(e) { + var ev = e.data; + // print("child_recv", JSON.stringify(ev)); + switch(ev.type) { + case "abort": + parent.postMessage({ type: "done" }); + parent.onMessage = null; /* terminate the worker */ + break; + case "sab": + /* modify the SharedArrayBuffer */ + ev.buf[2] = 10; + parent.postMessage({ type: "sab_done", buf: ev.buf }); + break; + } +} + +function worker_main() { + var i; + + parent.onmessage = handle_msg; + for(i = 0; i < 10; i++) { + parent.postMessage({ type: "num", num: i }); + } +} + +worker_main(); diff --git a/source/engine/thirdparty/quickjs/unicode_download.sh b/source/engine/thirdparty/quickjs/unicode_download.sh new file mode 100755 index 0000000..222925e --- /dev/null +++ b/source/engine/thirdparty/quickjs/unicode_download.sh @@ -0,0 +1,19 @@ +#!/bin/sh +set -e + +url="ftp://ftp.unicode.org/Public/15.0.0/ucd" +emoji_url="${url}/emoji/emoji-data.txt" + +files="CaseFolding.txt DerivedNormalizationProps.txt PropList.txt \ +SpecialCasing.txt CompositionExclusions.txt ScriptExtensions.txt \ +UnicodeData.txt DerivedCoreProperties.txt NormalizationTest.txt Scripts.txt \ +PropertyValueAliases.txt" + +mkdir -p unicode + +for f in $files; do + g="${url}/${f}" + wget $g -O unicode/$f +done + +wget $emoji_url -O unicode/emoji-data.txt diff --git a/source/engine/thirdparty/quickjs/unicode_gen.c b/source/engine/thirdparty/quickjs/unicode_gen.c new file mode 100644 index 0000000..9a7babb --- /dev/null +++ b/source/engine/thirdparty/quickjs/unicode_gen.c @@ -0,0 +1,3169 @@ +/* + * Generation of Unicode tables + * + * Copyright (c) 2017-2018 Fabrice Bellard + * Copyright (c) 2017-2018 Charlie Gordon + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" + +/* define it to be able to test unicode.c */ +//#define USE_TEST +/* profile tests */ +//#define PROFILE + +//#define DUMP_CASE_CONV_TABLE +//#define DUMP_TABLE_SIZE +//#define DUMP_CC_TABLE +//#define DUMP_DECOMP_TABLE +//#define DUMP_CASE_FOLDING_SPECIAL_CASES + +/* Ideas: + - Generalize run length encoding + index for all tables + - remove redundant tables for ID_start, ID_continue, Case_Ignorable, Cased + + Case conversion: + - use a single entry for consecutive U/LF runs + - allow EXT runs of length > 1 + + Decomposition: + - Greek lower case (+1f10/1f10) ? + - allow holes in B runs + - suppress more upper / lower case redundancy +*/ + +#ifdef USE_TEST +#include "libunicode.c" +#endif + +#define CHARCODE_MAX 0x10ffff +#define CC_LEN_MAX 3 + +void *mallocz(size_t size) +{ + void *ptr; + ptr = malloc(size); + memset(ptr, 0, size); + return ptr; +} + +const char *get_field(const char *p, int n) +{ + int i; + for(i = 0; i < n; i++) { + while (*p != ';' && *p != '\0') + p++; + if (*p == '\0') + return NULL; + p++; + } + return p; +} + +const char *get_field_buf(char *buf, size_t buf_size, const char *p, int n) +{ + char *q; + p = get_field(p, n); + q = buf; + while (*p != ';' && *p != '\0') { + if ((q - buf) < buf_size - 1) + *q++ = *p; + p++; + } + *q = '\0'; + return buf; +} + +void add_char(int **pbuf, int *psize, int *plen, int c) +{ + int len, size, *buf; + buf = *pbuf; + size = *psize; + len = *plen; + if (len >= size) { + size = *psize; + size = max_int(len + 1, size * 3 / 2); + buf = realloc(buf, sizeof(buf[0]) * size); + *pbuf = buf; + *psize = size; + } + buf[len++] = c; + *plen = len; +} + +int *get_field_str(int *plen, const char *str, int n) +{ + const char *p; + int *buf, len, size; + p = get_field(str, n); + if (!p) { + *plen = 0; + return NULL; + } + len = 0; + size = 0; + buf = NULL; + for(;;) { + while (isspace(*p)) + p++; + if (!isxdigit(*p)) + break; + add_char(&buf, &size, &len, strtoul(p, (char **)&p, 16)); + } + *plen = len; + return buf; +} + +char *get_line(char *buf, int buf_size, FILE *f) +{ + int len; + if (!fgets(buf, buf_size, f)) + return NULL; + len = strlen(buf); + if (len > 0 && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + return buf; +} + +#define UNICODE_GENERAL_CATEGORY + +typedef enum { +#define DEF(id, str) GCAT_ ## id, +#include "unicode_gen_def.h" +#undef DEF + GCAT_COUNT, +} UnicodeGCEnum1; + +static const char *unicode_gc_name[] = { +#define DEF(id, str) #id, +#include "unicode_gen_def.h" +#undef DEF +}; + +static const char *unicode_gc_short_name[] = { +#define DEF(id, str) str, +#include "unicode_gen_def.h" +#undef DEF +}; + +#undef UNICODE_GENERAL_CATEGORY + +#define UNICODE_SCRIPT + +typedef enum { +#define DEF(id, str) SCRIPT_ ## id, +#include "unicode_gen_def.h" +#undef DEF + SCRIPT_COUNT, +} UnicodeScriptEnum1; + +static const char *unicode_script_name[] = { +#define DEF(id, str) #id, +#include "unicode_gen_def.h" +#undef DEF +}; + +const char *unicode_script_short_name[] = { +#define DEF(id, str) str, +#include "unicode_gen_def.h" +#undef DEF +}; + +#undef UNICODE_SCRIPT + +#define UNICODE_PROP_LIST + +typedef enum { +#define DEF(id, str) PROP_ ## id, +#include "unicode_gen_def.h" +#undef DEF + PROP_COUNT, +} UnicodePropEnum1; + +static const char *unicode_prop_name[] = { +#define DEF(id, str) #id, +#include "unicode_gen_def.h" +#undef DEF +}; + +static const char *unicode_prop_short_name[] = { +#define DEF(id, str) str, +#include "unicode_gen_def.h" +#undef DEF +}; + +#undef UNICODE_PROP_LIST + +typedef struct { + /* case conv */ + uint8_t u_len; + uint8_t l_len; + uint8_t f_len; + int u_data[CC_LEN_MAX]; /* to upper case */ + int l_data[CC_LEN_MAX]; /* to lower case */ + int f_data[CC_LEN_MAX]; /* to case folding */ + + uint8_t combining_class; + uint8_t is_compat:1; + uint8_t is_excluded:1; + uint8_t general_category; + uint8_t script; + uint8_t script_ext_len; + uint8_t *script_ext; + uint32_t prop_bitmap_tab[3]; + /* decomposition */ + int decomp_len; + int *decomp_data; +} CCInfo; + +CCInfo *unicode_db; + +int find_name(const char **tab, int tab_len, const char *name) +{ + int i, len, name_len; + const char *p, *r; + + name_len = strlen(name); + for(i = 0; i < tab_len; i++) { + p = tab[i]; + for(;;) { + r = strchr(p, ','); + if (!r) + len = strlen(p); + else + len = r - p; + if (len == name_len && memcmp(p, name, len) == 0) + return i; + if (!r) + break; + p = r + 1; + } + } + return -1; +} + +static int get_prop(uint32_t c, int prop_idx) +{ + return (unicode_db[c].prop_bitmap_tab[prop_idx >> 5] >> (prop_idx & 0x1f)) & 1; +} + +static void set_prop(uint32_t c, int prop_idx, int val) +{ + uint32_t mask; + mask = 1U << (prop_idx & 0x1f); + if (val) + unicode_db[c].prop_bitmap_tab[prop_idx >> 5] |= mask; + else + unicode_db[c].prop_bitmap_tab[prop_idx >> 5] &= ~mask; +} + +void parse_unicode_data(const char *filename) +{ + FILE *f; + char line[1024]; + char buf1[256]; + const char *p; + int code, lc, uc, last_code; + CCInfo *ci, *tab = unicode_db; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + last_code = 0; + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#') + continue; + + p = get_field(line, 0); + if (!p) + continue; + code = strtoul(p, NULL, 16); + lc = 0; + uc = 0; + + p = get_field(line, 12); + if (p && *p != ';') { + uc = strtoul(p, NULL, 16); + } + + p = get_field(line, 13); + if (p && *p != ';') { + lc = strtoul(p, NULL, 16); + } + ci = &tab[code]; + if (uc > 0 || lc > 0) { + assert(code <= CHARCODE_MAX); + if (uc > 0) { + assert(ci->u_len == 0); + ci->u_len = 1; + ci->u_data[0] = uc; + } + if (lc > 0) { + assert(ci->l_len == 0); + ci->l_len = 1; + ci->l_data[0] = lc; + } + } + + { + int i; + get_field_buf(buf1, sizeof(buf1), line, 2); + i = find_name(unicode_gc_name, countof(unicode_gc_name), buf1); + if (i < 0) { + fprintf(stderr, "General category '%s' not found\n", + buf1); + exit(1); + } + ci->general_category = i; + } + + p = get_field(line, 3); + if (p && *p != ';' && *p != '\0') { + int cc; + cc = strtoul(p, NULL, 0); + if (cc != 0) { + assert(code <= CHARCODE_MAX); + ci->combining_class = cc; + // printf("%05x: %d\n", code, ci->combining_class); + } + } + + p = get_field(line, 5); + if (p && *p != ';' && *p != '\0') { + int size; + assert(code <= CHARCODE_MAX); + ci->is_compat = 0; + if (*p == '<') { + while (*p != '\0' && *p != '>') + p++; + if (*p == '>') + p++; + ci->is_compat = 1; + } + size = 0; + for(;;) { + while (isspace(*p)) + p++; + if (!isxdigit(*p)) + break; + add_char(&ci->decomp_data, &size, &ci->decomp_len, strtoul(p, (char **)&p, 16)); + } +#if 0 + { + int i; + static int count, d_count; + + printf("%05x: %c", code, ci->is_compat ? 'C': ' '); + for(i = 0; i < ci->decomp_len; i++) + printf(" %05x", ci->decomp_data[i]); + printf("\n"); + count++; + d_count += ci->decomp_len; + // printf("%d %d\n", count, d_count); + } +#endif + } + + p = get_field(line, 9); + if (p && *p == 'Y') { + set_prop(code, PROP_Bidi_Mirrored, 1); + } + + /* handle ranges */ + get_field_buf(buf1, sizeof(buf1), line, 1); + if (strstr(buf1, " Last>")) { + int i; + // printf("range: 0x%x-%0x\n", last_code, code); + assert(ci->decomp_len == 0); + assert(ci->script_ext_len == 0); + for(i = last_code + 1; i < code; i++) { + unicode_db[i] = *ci; + } + } + last_code = code; + } + + fclose(f); +} + +void parse_special_casing(CCInfo *tab, const char *filename) +{ + FILE *f; + char line[1024]; + const char *p; + int code; + CCInfo *ci; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#') + continue; + + p = get_field(line, 0); + if (!p) + continue; + code = strtoul(p, NULL, 16); + assert(code <= CHARCODE_MAX); + ci = &tab[code]; + + p = get_field(line, 4); + if (p) { + /* locale dependent casing */ + while (isspace(*p)) + p++; + if (*p != '#' && *p != '\0') + continue; + } + + + p = get_field(line, 1); + if (p && *p != ';') { + ci->l_len = 0; + for(;;) { + while (isspace(*p)) + p++; + if (*p == ';') + break; + assert(ci->l_len < CC_LEN_MAX); + ci->l_data[ci->l_len++] = strtoul(p, (char **)&p, 16); + } + + if (ci->l_len == 1 && ci->l_data[0] == code) + ci->l_len = 0; + } + + p = get_field(line, 3); + if (p && *p != ';') { + ci->u_len = 0; + for(;;) { + while (isspace(*p)) + p++; + if (*p == ';') + break; + assert(ci->u_len < CC_LEN_MAX); + ci->u_data[ci->u_len++] = strtoul(p, (char **)&p, 16); + } + + if (ci->u_len == 1 && ci->u_data[0] == code) + ci->u_len = 0; + } + } + + fclose(f); +} + +void parse_case_folding(CCInfo *tab, const char *filename) +{ + FILE *f; + char line[1024]; + const char *p; + int code, status; + CCInfo *ci; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#') + continue; + + p = get_field(line, 0); + if (!p) + continue; + code = strtoul(p, NULL, 16); + assert(code <= CHARCODE_MAX); + ci = &tab[code]; + + p = get_field(line, 1); + if (!p) + continue; + /* locale dependent casing */ + while (isspace(*p)) + p++; + status = *p; + if (status != 'C' && status != 'S' && status != 'F') + continue; + + p = get_field(line, 2); + assert(p != NULL); + if (status == 'S') { + /* we always select the simple case folding and assume it + * comes after the full case folding case */ + assert(ci->f_len >= 2); + ci->f_len = 0; + } else { + assert(ci->f_len == 0); + } + for(;;) { + while (isspace(*p)) + p++; + if (*p == ';') + break; + assert(ci->l_len < CC_LEN_MAX); + ci->f_data[ci->f_len++] = strtoul(p, (char **)&p, 16); + } + } + + fclose(f); +} + +void parse_composition_exclusions(const char *filename) +{ + FILE *f; + char line[4096], *p; + uint32_t c0; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@' || *p == '\0') + continue; + c0 = strtoul(p, (char **)&p, 16); + assert(c0 > 0 && c0 <= CHARCODE_MAX); + unicode_db[c0].is_excluded = TRUE; + } + fclose(f); +} + +void parse_derived_core_properties(const char *filename) +{ + FILE *f; + char line[4096], *p, buf[256], *q; + uint32_t c0, c1, c; + int i; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@' || *p == '\0') + continue; + c0 = strtoul(p, (char **)&p, 16); + if (*p == '.' && p[1] == '.') { + p += 2; + c1 = strtoul(p, (char **)&p, 16); + } else { + c1 = c0; + } + assert(c1 <= CHARCODE_MAX); + p += strspn(p, " \t"); + if (*p == ';') { + p++; + p += strspn(p, " \t"); + q = buf; + while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t') { + if ((q - buf) < sizeof(buf) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + i = find_name(unicode_prop_name, + countof(unicode_prop_name), buf); + if (i < 0) { + if (!strcmp(buf, "Grapheme_Link")) + goto next; + fprintf(stderr, "Property not found: %s\n", buf); + exit(1); + } + for(c = c0; c <= c1; c++) { + set_prop(c, i, 1); + } +next: ; + } + } + fclose(f); +} + +void parse_derived_norm_properties(const char *filename) +{ + FILE *f; + char line[4096], *p, buf[256], *q; + uint32_t c0, c1, c; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@' || *p == '\0') + continue; + c0 = strtoul(p, (char **)&p, 16); + if (*p == '.' && p[1] == '.') { + p += 2; + c1 = strtoul(p, (char **)&p, 16); + } else { + c1 = c0; + } + assert(c1 <= CHARCODE_MAX); + p += strspn(p, " \t"); + if (*p == ';') { + p++; + p += strspn(p, " \t"); + q = buf; + while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t') { + if ((q - buf) < sizeof(buf) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + if (!strcmp(buf, "Changes_When_NFKC_Casefolded")) { + for(c = c0; c <= c1; c++) { + set_prop(c, PROP_Changes_When_NFKC_Casefolded, 1); + } + } + } + } + fclose(f); +} + +void parse_prop_list(const char *filename) +{ + FILE *f; + char line[4096], *p, buf[256], *q; + uint32_t c0, c1, c; + int i; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@' || *p == '\0') + continue; + c0 = strtoul(p, (char **)&p, 16); + if (*p == '.' && p[1] == '.') { + p += 2; + c1 = strtoul(p, (char **)&p, 16); + } else { + c1 = c0; + } + assert(c1 <= CHARCODE_MAX); + p += strspn(p, " \t"); + if (*p == ';') { + p++; + p += strspn(p, " \t"); + q = buf; + while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t') { + if ((q - buf) < sizeof(buf) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + i = find_name(unicode_prop_name, + countof(unicode_prop_name), buf); + if (i < 0) { + fprintf(stderr, "Property not found: %s\n", buf); + exit(1); + } + for(c = c0; c <= c1; c++) { + set_prop(c, i, 1); + } + } + } + fclose(f); +} + +void parse_scripts(const char *filename) +{ + FILE *f; + char line[4096], *p, buf[256], *q; + uint32_t c0, c1, c; + int i; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@' || *p == '\0') + continue; + c0 = strtoul(p, (char **)&p, 16); + if (*p == '.' && p[1] == '.') { + p += 2; + c1 = strtoul(p, (char **)&p, 16); + } else { + c1 = c0; + } + assert(c1 <= CHARCODE_MAX); + p += strspn(p, " \t"); + if (*p == ';') { + p++; + p += strspn(p, " \t"); + q = buf; + while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t') { + if ((q - buf) < sizeof(buf) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + i = find_name(unicode_script_name, + countof(unicode_script_name), buf); + if (i < 0) { + fprintf(stderr, "Unknown script: '%s'\n", buf); + exit(1); + } + for(c = c0; c <= c1; c++) + unicode_db[c].script = i; + } + } + fclose(f); +} + +void parse_script_extensions(const char *filename) +{ + FILE *f; + char line[4096], *p, buf[256], *q; + uint32_t c0, c1, c; + int i; + uint8_t script_ext[255]; + int script_ext_len; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@' || *p == '\0') + continue; + c0 = strtoul(p, (char **)&p, 16); + if (*p == '.' && p[1] == '.') { + p += 2; + c1 = strtoul(p, (char **)&p, 16); + } else { + c1 = c0; + } + assert(c1 <= CHARCODE_MAX); + p += strspn(p, " \t"); + script_ext_len = 0; + if (*p == ';') { + p++; + for(;;) { + p += strspn(p, " \t"); + q = buf; + while (*p != '\0' && *p != ' ' && *p != '#' && *p != '\t') { + if ((q - buf) < sizeof(buf) - 1) + *q++ = *p; + p++; + } + *q = '\0'; + if (buf[0] == '\0') + break; + i = find_name(unicode_script_short_name, + countof(unicode_script_short_name), buf); + if (i < 0) { + fprintf(stderr, "Script not found: %s\n", buf); + exit(1); + } + assert(script_ext_len < sizeof(script_ext)); + script_ext[script_ext_len++] = i; + } + for(c = c0; c <= c1; c++) { + CCInfo *ci = &unicode_db[c]; + ci->script_ext_len = script_ext_len; + ci->script_ext = malloc(sizeof(ci->script_ext[0]) * script_ext_len); + for(i = 0; i < script_ext_len; i++) + ci->script_ext[i] = script_ext[i]; + } + } + } + fclose(f); +} + +void dump_cc_info(CCInfo *ci, int i) +{ + int j; + printf("%05x:", i); + if (ci->u_len != 0) { + printf(" U:"); + for(j = 0; j < ci->u_len; j++) + printf(" %05x", ci->u_data[j]); + } + if (ci->l_len != 0) { + printf(" L:"); + for(j = 0; j < ci->l_len; j++) + printf(" %05x", ci->l_data[j]); + } + if (ci->f_len != 0) { + printf(" F:"); + for(j = 0; j < ci->f_len; j++) + printf(" %05x", ci->f_data[j]); + } + printf("\n"); +} + +void dump_unicode_data(CCInfo *tab) +{ + int i; + CCInfo *ci; + for(i = 0; i <= CHARCODE_MAX; i++) { + ci = &tab[i]; + if (ci->u_len != 0 || ci->l_len != 0 || ci->f_len != 0) { + dump_cc_info(ci, i); + } + } +} + +BOOL is_complicated_case(const CCInfo *ci) +{ + return (ci->u_len > 1 || ci->l_len > 1 || + (ci->u_len > 0 && ci->l_len > 0) || + (ci->f_len != ci->l_len) || + (memcmp(ci->f_data, ci->l_data, ci->f_len * sizeof(ci->f_data[0])) != 0)); +} + +#ifndef USE_TEST +enum { + RUN_TYPE_U, + RUN_TYPE_L, + RUN_TYPE_UF, + RUN_TYPE_LF, + RUN_TYPE_UL, + RUN_TYPE_LSU, + RUN_TYPE_U2L_399_EXT2, + RUN_TYPE_UF_D20, + RUN_TYPE_UF_D1_EXT, + RUN_TYPE_U_EXT, + RUN_TYPE_LF_EXT, + RUN_TYPE_UF_EXT2, + RUN_TYPE_LF_EXT2, + RUN_TYPE_UF_EXT3, +}; +#endif + +const char *run_type_str[] = { + "U", + "L", + "UF", + "LF", + "UL", + "LSU", + "U2L_399_EXT2", + "UF_D20", + "UF_D1_EXT", + "U_EXT", + "LF_EXT", + "UF_EXT2", + "LF_EXT2", + "UF_EXT3", +}; + +typedef struct { + int code; + int len; + int type; + int data; + int ext_len; + int ext_data[3]; + int data_index; /* 'data' coming from the table */ +} TableEntry; + +static int simple_to_lower(CCInfo *tab, int c) +{ + if (tab[c].l_len != 1) + return c; + return tab[c].l_data[0]; +} + +/* code (17), len (7), type (4) */ + +void find_run_type(TableEntry *te, CCInfo *tab, int code) +{ + int is_lower, len; + CCInfo *ci, *ci1, *ci2; + + ci = &tab[code]; + ci1 = &tab[code + 1]; + ci2 = &tab[code + 2]; + te->code = code; + + if (ci->l_len == 1 && ci->l_data[0] == code + 2 && + ci->f_len == 1 && ci->f_data[0] == ci->l_data[0] && + ci->u_len == 0 && + + ci1->l_len == 1 && ci1->l_data[0] == code + 2 && + ci1->f_len == 1 && ci1->f_data[0] == ci1->l_data[0] && + ci1->u_len == 1 && ci1->u_data[0] == code && + + ci2->l_len == 0 && + ci2->f_len == 0 && + ci2->u_len == 1 && ci2->u_data[0] == code) { + te->len = 3; + te->data = 0; + te->type = RUN_TYPE_LSU; + return; + } + + if (is_complicated_case(ci)) { + len = 1; + while (code + len <= CHARCODE_MAX) { + ci1 = &tab[code + len]; + if (ci1->u_len != 1 || + ci1->u_data[0] != ci->u_data[0] + len || + ci1->l_len != 0 || + ci1->f_len != 1 || ci1->f_data[0] != ci1->u_data[0]) + break; + len++; + } + if (len > 1) { + te->len = len; + te->type = RUN_TYPE_UF; + te->data = ci->u_data[0]; + return; + } + + if (ci->l_len == 0 && + ci->u_len == 2 && ci->u_data[1] == 0x399 && + ci->f_len == 2 && ci->f_data[1] == 0x3B9 && + ci->f_data[0] == simple_to_lower(tab, ci->u_data[0])) { + len = 1; + while (code + len <= CHARCODE_MAX) { + ci1 = &tab[code + len]; + if (!(ci1->u_len == 2 && + ci1->u_data[1] == ci->u_data[1] && + ci1->u_data[0] == ci->u_data[0] + len && + ci1->f_len == 2 && + ci1->f_data[1] == ci->f_data[1] && + ci1->f_data[0] == ci->f_data[0] + len && + ci1->l_len == 0)) + break; + len++; + } + te->len = len; + te->type = RUN_TYPE_UF_EXT2; + te->ext_data[0] = ci->u_data[0]; + te->ext_data[1] = ci->u_data[1]; + te->ext_len = 2; + return; + } + + if (ci->u_len == 2 && ci->u_data[1] == 0x399 && + ci->l_len == 1 && + ci->f_len == 1 && ci->f_data[0] == ci->l_data[0]) { + len = 1; + while (code + len <= CHARCODE_MAX) { + ci1 = &tab[code + len]; + if (!(ci1->u_len == 2 && + ci1->u_data[1] == 0x399 && + ci1->u_data[0] == ci->u_data[0] + len && + ci1->l_len == 1 && + ci1->l_data[0] == ci->l_data[0] + len && + ci1->f_len == 1 && ci1->f_data[0] == ci1->l_data[0])) + break; + len++; + } + te->len = len; + te->type = RUN_TYPE_U2L_399_EXT2; + te->ext_data[0] = ci->u_data[0]; + te->ext_data[1] = ci->l_data[0]; + te->ext_len = 2; + return; + } + + if (ci->l_len == 1 && ci->u_len == 0 && ci->f_len == 0) { + len = 1; + while (code + len <= CHARCODE_MAX) { + ci1 = &tab[code + len]; + if (!(ci1->l_len == 1 && + ci1->l_data[0] == ci->l_data[0] + len && + ci1->u_len == 0 && ci1->f_len == 0)) + break; + len++; + } + te->len = len; + te->type = RUN_TYPE_L; + te->data = ci->l_data[0]; + return; + } + + if (ci->l_len == 0 && + ci->u_len == 1 && + ci->u_data[0] < 0x1000 && + ci->f_len == 1 && ci->f_data[0] == ci->u_data[0] + 0x20) { + te->len = 1; + te->type = RUN_TYPE_UF_D20; + te->data = ci->u_data[0]; + } else if (ci->l_len == 0 && + ci->u_len == 1 && + ci->f_len == 1 && ci->f_data[0] == ci->u_data[0] + 1) { + te->len = 1; + te->type = RUN_TYPE_UF_D1_EXT; + te->ext_data[0] = ci->u_data[0]; + te->ext_len = 1; + } else if (ci->l_len == 2 && ci->u_len == 0 && ci->f_len == 2 && + ci->l_data[0] == ci->f_data[0] && + ci->l_data[1] == ci->f_data[1]) { + te->len = 1; + te->type = RUN_TYPE_LF_EXT2; + te->ext_data[0] = ci->l_data[0]; + te->ext_data[1] = ci->l_data[1]; + te->ext_len = 2; + } else if (ci->u_len == 2 && ci->l_len == 0 && ci->f_len == 2 && + ci->f_data[0] == simple_to_lower(tab, ci->u_data[0]) && + ci->f_data[1] == simple_to_lower(tab, ci->u_data[1])) { + te->len = 1; + te->type = RUN_TYPE_UF_EXT2; + te->ext_data[0] = ci->u_data[0]; + te->ext_data[1] = ci->u_data[1]; + te->ext_len = 2; + } else if (ci->u_len == 3 && ci->l_len == 0 && ci->f_len == 3 && + ci->f_data[0] == simple_to_lower(tab, ci->u_data[0]) && + ci->f_data[1] == simple_to_lower(tab, ci->u_data[1]) && + ci->f_data[2] == simple_to_lower(tab, ci->u_data[2])) { + te->len = 1; + te->type = RUN_TYPE_UF_EXT3; + te->ext_data[0] = ci->u_data[0]; + te->ext_data[1] = ci->u_data[1]; + te->ext_data[2] = ci->u_data[2]; + te->ext_len = 3; + } else { + printf("unsupported encoding case:\n"); + dump_cc_info(ci, code); + abort(); + } + } else { + /* look for a run of identical conversions */ + len = 0; + for(;;) { + if (code >= CHARCODE_MAX || len >= 126) + break; + ci = &tab[code + len]; + ci1 = &tab[code + len + 1]; + if (is_complicated_case(ci) || is_complicated_case(ci1)) { + break; + } + if (ci->l_len != 1 || ci->l_data[0] != code + len + 1) + break; + if (ci1->u_len != 1 || ci1->u_data[0] != code + len) + break; + len += 2; + } + if (len > 0) { + te->len = len; + te->type = RUN_TYPE_UL; + te->data = 0; + return; + } + + ci = &tab[code]; + is_lower = ci->l_len > 0; + len = 1; + while (code + len <= CHARCODE_MAX) { + ci1 = &tab[code + len]; + if (is_complicated_case(ci1)) + break; + if (is_lower) { + if (ci1->l_len != 1 || + ci1->l_data[0] != ci->l_data[0] + len) + break; + } else { + if (ci1->u_len != 1 || + ci1->u_data[0] != ci->u_data[0] + len) + break; + } + len++; + } + te->len = len; + if (is_lower) { + te->type = RUN_TYPE_LF; + te->data = ci->l_data[0]; + } else { + te->type = RUN_TYPE_U; + te->data = ci->u_data[0]; + } + } +} + +TableEntry conv_table[1000]; +int conv_table_len; +int ext_data[1000]; +int ext_data_len; + +void dump_case_conv_table1(void) +{ + int i, j; + const TableEntry *te; + + for(i = 0; i < conv_table_len; i++) { + te = &conv_table[i]; + printf("%05x %02x %-10s %05x", + te->code, te->len, run_type_str[te->type], te->data); + for(j = 0; j < te->ext_len; j++) { + printf(" %05x", te->ext_data[j]); + } + printf("\n"); + } + printf("table_len=%d ext_len=%d\n", conv_table_len, ext_data_len); +} + +int find_data_index(const TableEntry *conv_table, int len, int data) +{ + int i; + const TableEntry *te; + for(i = 0; i < len; i++) { + te = &conv_table[i]; + if (te->code == data) + return i; + } + return -1; +} + +int find_ext_data_index(int data) +{ + int i; + for(i = 0; i < ext_data_len; i++) { + if (ext_data[i] == data) + return i; + } + assert(ext_data_len < countof(ext_data)); + ext_data[ext_data_len++] = data; + return ext_data_len - 1; +} + +void build_conv_table(CCInfo *tab) +{ + int code, i, j; + CCInfo *ci; + TableEntry *te; + + te = conv_table; + for(code = 0; code <= CHARCODE_MAX; code++) { + ci = &tab[code]; + if (ci->u_len == 0 && ci->l_len == 0 && ci->f_len == 0) + continue; + assert(te - conv_table < countof(conv_table)); + find_run_type(te, tab, code); +#if 0 + if (te->type == RUN_TYPE_TODO) { + printf("TODO: "); + dump_cc_info(ci, code); + } +#endif + assert(te->len <= 127); + code += te->len - 1; + te++; + } + conv_table_len = te - conv_table; + + /* find the data index */ + for(i = 0; i < conv_table_len; i++) { + int data_index; + te = &conv_table[i]; + + switch(te->type) { + case RUN_TYPE_U: + case RUN_TYPE_L: + case RUN_TYPE_UF: + case RUN_TYPE_LF: + data_index = find_data_index(conv_table, conv_table_len, te->data); + if (data_index < 0) { + switch(te->type) { + case RUN_TYPE_U: + te->type = RUN_TYPE_U_EXT; + te->ext_len = 1; + te->ext_data[0] = te->data; + break; + case RUN_TYPE_LF: + te->type = RUN_TYPE_LF_EXT; + te->ext_len = 1; + te->ext_data[0] = te->data; + break; + default: + printf("%05x: index not found\n", te->code); + exit(1); + } + } else { + te->data_index = data_index; + } + break; + case RUN_TYPE_UF_D20: + te->data_index = te->data; + break; + } + } + + /* find the data index for ext_data */ + for(i = 0; i < conv_table_len; i++) { + te = &conv_table[i]; + if (te->type == RUN_TYPE_UF_EXT3) { + int p, v; + v = 0; + for(j = 0; j < 3; j++) { + p = find_ext_data_index(te->ext_data[j]); + assert(p < 16); + v = (v << 4) | p; + } + te->data_index = v; + } + } + + for(i = 0; i < conv_table_len; i++) { + te = &conv_table[i]; + if (te->type == RUN_TYPE_LF_EXT2 || + te->type == RUN_TYPE_UF_EXT2 || + te->type == RUN_TYPE_U2L_399_EXT2) { + int p, v; + v = 0; + for(j = 0; j < 2; j++) { + p = find_ext_data_index(te->ext_data[j]); + assert(p < 64); + v = (v << 6) | p; + } + te->data_index = v; + } + } + + for(i = 0; i < conv_table_len; i++) { + te = &conv_table[i]; + if (te->type == RUN_TYPE_UF_D1_EXT || + te->type == RUN_TYPE_U_EXT || + te->type == RUN_TYPE_LF_EXT) { + te->data_index = find_ext_data_index(te->ext_data[0]); + } + } +#ifdef DUMP_CASE_CONV_TABLE + dump_case_conv_table1(); +#endif +} + +void dump_case_conv_table(FILE *f) +{ + int i; + uint32_t v; + const TableEntry *te; + + fprintf(f, "static const uint32_t case_conv_table1[%u] = {", conv_table_len); + for(i = 0; i < conv_table_len; i++) { + if (i % 4 == 0) + fprintf(f, "\n "); + te = &conv_table[i]; + v = te->code << (32 - 17); + v |= te->len << (32 - 17 - 7); + v |= te->type << (32 - 17 - 7 - 4); + v |= te->data_index >> 8; + fprintf(f, " 0x%08x,", v); + } + fprintf(f, "\n};\n\n"); + + fprintf(f, "static const uint8_t case_conv_table2[%u] = {", conv_table_len); + for(i = 0; i < conv_table_len; i++) { + if (i % 8 == 0) + fprintf(f, "\n "); + te = &conv_table[i]; + fprintf(f, " 0x%02x,", te->data_index & 0xff); + } + fprintf(f, "\n};\n\n"); + + fprintf(f, "static const uint16_t case_conv_ext[%u] = {", ext_data_len); + for(i = 0; i < ext_data_len; i++) { + if (i % 8 == 0) + fprintf(f, "\n "); + fprintf(f, " 0x%04x,", ext_data[i]); + } + fprintf(f, "\n};\n\n"); +} + + +static CCInfo *global_tab; + +static int sp_cc_cmp(const void *p1, const void *p2) +{ + CCInfo *c1 = &global_tab[*(const int *)p1]; + CCInfo *c2 = &global_tab[*(const int *)p2]; + if (c1->f_len < c2->f_len) { + return -1; + } else if (c2->f_len < c1->f_len) { + return 1; + } else { + return memcmp(c1->f_data, c2->f_data, sizeof(c1->f_data[0]) * c1->f_len); + } +} + +/* dump the case special cases (multi character results which are + identical and need specific handling in lre_canonicalize() */ +void dump_case_folding_special_cases(CCInfo *tab) +{ + int i, len, j; + int *perm; + + perm = malloc(sizeof(perm[0]) * (CHARCODE_MAX + 1)); + for(i = 0; i <= CHARCODE_MAX; i++) + perm[i] = i; + global_tab = tab; + qsort(perm, CHARCODE_MAX + 1, sizeof(perm[0]), sp_cc_cmp); + for(i = 0; i <= CHARCODE_MAX;) { + if (tab[perm[i]].f_len <= 1) { + i++; + } else { + len = 1; + while ((i + len) <= CHARCODE_MAX && !sp_cc_cmp(&perm[i], &perm[i + len])) + len++; + + if (len > 1) { + for(j = i; j < i + len; j++) + dump_cc_info(&tab[perm[j]], perm[j]); + } + i += len; + } + } + free(perm); + global_tab = NULL; +} + + +int tabcmp(const int *tab1, const int *tab2, int n) +{ + int i; + for(i = 0; i < n; i++) { + if (tab1[i] != tab2[i]) + return -1; + } + return 0; +} + +void dump_str(const char *str, const int *buf, int len) +{ + int i; + printf("%s=", str); + for(i = 0; i < len; i++) + printf(" %05x", buf[i]); + printf("\n"); +} + +void compute_internal_props(void) +{ + int i; + BOOL has_ul; + + for(i = 0; i <= CHARCODE_MAX; i++) { + CCInfo *ci = &unicode_db[i]; + has_ul = (ci->u_len != 0 || ci->l_len != 0 || ci->f_len != 0); + if (has_ul) { + assert(get_prop(i, PROP_Cased)); + } else { + set_prop(i, PROP_Cased1, get_prop(i, PROP_Cased)); + } + set_prop(i, PROP_ID_Continue1, + get_prop(i, PROP_ID_Continue) & (get_prop(i, PROP_ID_Start) ^ 1)); + set_prop(i, PROP_XID_Start1, + get_prop(i, PROP_ID_Start) ^ get_prop(i, PROP_XID_Start)); + set_prop(i, PROP_XID_Continue1, + get_prop(i, PROP_ID_Continue) ^ get_prop(i, PROP_XID_Continue)); + set_prop(i, PROP_Changes_When_Titlecased1, + get_prop(i, PROP_Changes_When_Titlecased) ^ (ci->u_len != 0)); + set_prop(i, PROP_Changes_When_Casefolded1, + get_prop(i, PROP_Changes_When_Casefolded) ^ (ci->f_len != 0)); + /* XXX: reduce table size (438 bytes) */ + set_prop(i, PROP_Changes_When_NFKC_Casefolded1, + get_prop(i, PROP_Changes_When_NFKC_Casefolded) ^ (ci->f_len != 0)); +#if 0 + /* TEST */ +#define M(x) (1U << GCAT_ ## x) + { + int b; + b = ((M(Mn) | M(Cf) | M(Lm) | M(Sk)) >> + unicode_db[i].general_category) & 1; + set_prop(i, PROP_Cased1, + get_prop(i, PROP_Case_Ignorable) ^ b); + } +#undef M +#endif + } +} + +void dump_byte_table(FILE *f, const char *cname, const uint8_t *tab, int len) +{ + int i; + fprintf(f, "static const uint8_t %s[%d] = {", cname, len); + for(i = 0; i < len; i++) { + if (i % 8 == 0) + fprintf(f, "\n "); + fprintf(f, " 0x%02x,", tab[i]); + } + fprintf(f, "\n};\n\n"); +} + +#define PROP_BLOCK_LEN 32 + +void build_prop_table(FILE *f, int prop_index, BOOL add_index) +{ + int i, j, n, v, offset, code; + DynBuf dbuf_s, *dbuf = &dbuf_s; + DynBuf dbuf1_s, *dbuf1 = &dbuf1_s; + DynBuf dbuf2_s, *dbuf2 = &dbuf2_s; + const uint32_t *buf; + int buf_len, block_end_pos, bit; + char cname[128]; + + dbuf_init(dbuf1); + + for(i = 0; i <= CHARCODE_MAX;) { + v = get_prop(i, prop_index); + j = i + 1; + while (j <= CHARCODE_MAX && get_prop(j, prop_index) == v) { + j++; + } + n = j - i; + if (j == (CHARCODE_MAX + 1) && v == 0) + break; /* no need to encode last zero run */ + //printf("%05x: %d %d\n", i, n, v); + dbuf_put_u32(dbuf1, n - 1); + i += n; + } + + dbuf_init(dbuf); + dbuf_init(dbuf2); + buf = (uint32_t *)dbuf1->buf; + buf_len = dbuf1->size / sizeof(buf[0]); + + /* the first value is assumed to be 0 */ + assert(get_prop(0, prop_index) == 0); + + block_end_pos = PROP_BLOCK_LEN; + i = 0; + code = 0; + bit = 0; + while (i < buf_len) { + if (add_index && dbuf->size >= block_end_pos && bit == 0) { + offset = (dbuf->size - block_end_pos); + /* XXX: offset could be larger in case of runs of small + lengths. Could add code to change the encoding to + prevent it at the expense of one byte loss */ + assert(offset <= 7); + v = code | (offset << 21); + dbuf_putc(dbuf2, v); + dbuf_putc(dbuf2, v >> 8); + dbuf_putc(dbuf2, v >> 16); + block_end_pos += PROP_BLOCK_LEN; + } + + v = buf[i]; + code += v + 1; + bit ^= 1; + if (v < 8 && (i + 1) < buf_len && buf[i + 1] < 8) { + code += buf[i + 1] + 1; + bit ^= 1; + dbuf_putc(dbuf, (v << 3) | buf[i + 1]); + i += 2; + } else if (v < 128) { + dbuf_putc(dbuf, 0x80 + v); + i++; + } else if (v < (1 << 13)) { + dbuf_putc(dbuf, 0x40 + (v >> 8)); + dbuf_putc(dbuf, v); + i++; + } else { + assert(v < (1 << 21)); + dbuf_putc(dbuf, 0x60 + (v >> 16)); + dbuf_putc(dbuf, v >> 8); + dbuf_putc(dbuf, v); + i++; + } + } + + if (add_index) { + /* last index entry */ + v = code; + dbuf_putc(dbuf2, v); + dbuf_putc(dbuf2, v >> 8); + dbuf_putc(dbuf2, v >> 16); + } + +#ifdef DUMP_TABLE_SIZE + printf("prop %s: length=%d bytes\n", unicode_prop_name[prop_index], + (int)(dbuf->size + dbuf2->size)); +#endif + snprintf(cname, sizeof(cname), "unicode_prop_%s_table", unicode_prop_name[prop_index]); + dump_byte_table(f, cname, dbuf->buf, dbuf->size); + if (add_index) { + snprintf(cname, sizeof(cname), "unicode_prop_%s_index", unicode_prop_name[prop_index]); + dump_byte_table(f, cname, dbuf2->buf, dbuf2->size); + } + + dbuf_free(dbuf); + dbuf_free(dbuf1); + dbuf_free(dbuf2); +} + +void build_flags_tables(FILE *f) +{ + build_prop_table(f, PROP_Cased1, TRUE); + build_prop_table(f, PROP_Case_Ignorable, TRUE); + build_prop_table(f, PROP_ID_Start, TRUE); + build_prop_table(f, PROP_ID_Continue1, TRUE); +} + +void dump_name_table(FILE *f, const char *cname, const char **tab_name, int len, + const char **tab_short_name) +{ + int i, w, maxw; + + maxw = 0; + for(i = 0; i < len; i++) { + w = strlen(tab_name[i]); + if (tab_short_name[i][0] != '\0') { + w += 1 + strlen(tab_short_name[i]); + } + if (maxw < w) + maxw = w; + } + + /* generate a sequence of strings terminated by an empty string */ + fprintf(f, "static const char %s[] =\n", cname); + for(i = 0; i < len; i++) { + fprintf(f, " \""); + w = fprintf(f, "%s", tab_name[i]); + if (tab_short_name[i][0] != '\0') { + w += fprintf(f, ",%s", tab_short_name[i]); + } + fprintf(f, "\"%*s\"\\0\"\n", 1 + maxw - w, ""); + } + fprintf(f, ";\n\n"); +} + +void build_general_category_table(FILE *f) +{ + int i, v, j, n, n1; + DynBuf dbuf_s, *dbuf = &dbuf_s; +#ifdef DUMP_TABLE_SIZE + int cw_count, cw_len_count[4], cw_start; +#endif + + fprintf(f, "typedef enum {\n"); + for(i = 0; i < GCAT_COUNT; i++) + fprintf(f, " UNICODE_GC_%s,\n", unicode_gc_name[i]); + fprintf(f, " UNICODE_GC_COUNT,\n"); + fprintf(f, "} UnicodeGCEnum;\n\n"); + + dump_name_table(f, "unicode_gc_name_table", + unicode_gc_name, GCAT_COUNT, + unicode_gc_short_name); + + + dbuf_init(dbuf); +#ifdef DUMP_TABLE_SIZE + cw_count = 0; + for(i = 0; i < 4; i++) + cw_len_count[i] = 0; +#endif + for(i = 0; i <= CHARCODE_MAX;) { + v = unicode_db[i].general_category; + j = i + 1; + while (j <= CHARCODE_MAX && unicode_db[j].general_category == v) + j++; + n = j - i; + /* compress Lu/Ll runs */ + if (v == GCAT_Lu) { + n1 = 1; + while ((i + n1) <= CHARCODE_MAX && unicode_db[i + n1].general_category == (v + (n1 & 1))) { + n1++; + } + if (n1 > n) { + v = 31; + n = n1; + } + } + // printf("%05x %05x %d\n", i, n, v); + n--; +#ifdef DUMP_TABLE_SIZE + cw_count++; + cw_start = dbuf->size; +#endif + if (n < 7) { + dbuf_putc(dbuf, (n << 5) | v); + } else if (n < 7 + 128) { + n1 = n - 7; + assert(n1 < 128); + dbuf_putc(dbuf, (0xf << 5) | v); + dbuf_putc(dbuf, n1); + } else if (n < 7 + 128 + (1 << 14)) { + n1 = n - (7 + 128); + assert(n1 < (1 << 14)); + dbuf_putc(dbuf, (0xf << 5) | v); + dbuf_putc(dbuf, (n1 >> 8) + 128); + dbuf_putc(dbuf, n1); + } else { + n1 = n - (7 + 128 + (1 << 14)); + assert(n1 < (1 << 22)); + dbuf_putc(dbuf, (0xf << 5) | v); + dbuf_putc(dbuf, (n1 >> 16) + 128 + 64); + dbuf_putc(dbuf, n1 >> 8); + dbuf_putc(dbuf, n1); + } +#ifdef DUMP_TABLE_SIZE + cw_len_count[dbuf->size - cw_start - 1]++; +#endif + i += n + 1; + } +#ifdef DUMP_TABLE_SIZE + printf("general category: %d entries [", cw_count); + for(i = 0; i < 4; i++) + printf(" %d", cw_len_count[i]); + printf(" ], length=%d bytes\n", (int)dbuf->size); +#endif + + dump_byte_table(f, "unicode_gc_table", dbuf->buf, dbuf->size); + + dbuf_free(dbuf); +} + +void build_script_table(FILE *f) +{ + int i, v, j, n, n1, type; + DynBuf dbuf_s, *dbuf = &dbuf_s; +#ifdef DUMP_TABLE_SIZE + int cw_count, cw_len_count[4], cw_start; +#endif + + fprintf(f, "typedef enum {\n"); + for(i = 0; i < SCRIPT_COUNT; i++) + fprintf(f, " UNICODE_SCRIPT_%s,\n", unicode_script_name[i]); + fprintf(f, " UNICODE_SCRIPT_COUNT,\n"); + fprintf(f, "} UnicodeScriptEnum;\n\n"); + + i = 1; + dump_name_table(f, "unicode_script_name_table", + unicode_script_name + i, SCRIPT_COUNT - i, + unicode_script_short_name + i); + + dbuf_init(dbuf); +#ifdef DUMP_TABLE_SIZE + cw_count = 0; + for(i = 0; i < 4; i++) + cw_len_count[i] = 0; +#endif + for(i = 0; i <= CHARCODE_MAX;) { + v = unicode_db[i].script; + j = i + 1; + while (j <= CHARCODE_MAX && unicode_db[j].script == v) + j++; + n = j - i; + if (v == 0 && j == (CHARCODE_MAX + 1)) + break; + // printf("%05x %05x %d\n", i, n, v); + n--; +#ifdef DUMP_TABLE_SIZE + cw_count++; + cw_start = dbuf->size; +#endif + if (v == 0) + type = 0; + else + type = 1; + if (n < 96) { + dbuf_putc(dbuf, n | (type << 7)); + } else if (n < 96 + (1 << 12)) { + n1 = n - 96; + assert(n1 < (1 << 12)); + dbuf_putc(dbuf, ((n1 >> 8) + 96) | (type << 7)); + dbuf_putc(dbuf, n1); + } else { + n1 = n - (96 + (1 << 12)); + assert(n1 < (1 << 20)); + dbuf_putc(dbuf, ((n1 >> 16) + 112) | (type << 7)); + dbuf_putc(dbuf, n1 >> 8); + dbuf_putc(dbuf, n1); + } + if (type != 0) + dbuf_putc(dbuf, v); + +#ifdef DUMP_TABLE_SIZE + cw_len_count[dbuf->size - cw_start - 1]++; +#endif + i += n + 1; + } +#ifdef DUMP_TABLE_SIZE + printf("script: %d entries [", cw_count); + for(i = 0; i < 4; i++) + printf(" %d", cw_len_count[i]); + printf(" ], length=%d bytes\n", (int)dbuf->size); +#endif + + dump_byte_table(f, "unicode_script_table", dbuf->buf, dbuf->size); + + dbuf_free(dbuf); +} + +void build_script_ext_table(FILE *f) +{ + int i, j, n, n1, script_ext_len; + DynBuf dbuf_s, *dbuf = &dbuf_s; +#if defined(DUMP_TABLE_SIZE) + int cw_count = 0; +#endif + + dbuf_init(dbuf); + for(i = 0; i <= CHARCODE_MAX;) { + script_ext_len = unicode_db[i].script_ext_len; + j = i + 1; + while (j <= CHARCODE_MAX && + unicode_db[j].script_ext_len == script_ext_len && + !memcmp(unicode_db[j].script_ext, unicode_db[i].script_ext, + script_ext_len)) { + j++; + } + n = j - i; +#if defined(DUMP_TABLE_SIZE) + cw_count++; +#endif + n--; + if (n < 128) { + dbuf_putc(dbuf, n); + } else if (n < 128 + (1 << 14)) { + n1 = n - 128; + assert(n1 < (1 << 14)); + dbuf_putc(dbuf, (n1 >> 8) + 128); + dbuf_putc(dbuf, n1); + } else { + n1 = n - (128 + (1 << 14)); + assert(n1 < (1 << 22)); + dbuf_putc(dbuf, (n1 >> 16) + 128 + 64); + dbuf_putc(dbuf, n1 >> 8); + dbuf_putc(dbuf, n1); + } + dbuf_putc(dbuf, script_ext_len); + for(j = 0; j < script_ext_len; j++) + dbuf_putc(dbuf, unicode_db[i].script_ext[j]); + i += n + 1; + } +#ifdef DUMP_TABLE_SIZE + printf("script_ext: %d entries", cw_count); + printf(", length=%d bytes\n", (int)dbuf->size); +#endif + + dump_byte_table(f, "unicode_script_ext_table", dbuf->buf, dbuf->size); + + dbuf_free(dbuf); +} + +/* the following properties are synthetized so no table is necessary */ +#define PROP_TABLE_COUNT PROP_ASCII + +void build_prop_list_table(FILE *f) +{ + int i; + + for(i = 0; i < PROP_TABLE_COUNT; i++) { + if (i == PROP_ID_Start || + i == PROP_Case_Ignorable || + i == PROP_ID_Continue1) { + /* already generated */ + } else { + build_prop_table(f, i, FALSE); + } + } + + fprintf(f, "typedef enum {\n"); + for(i = 0; i < PROP_COUNT; i++) + fprintf(f, " UNICODE_PROP_%s,\n", unicode_prop_name[i]); + fprintf(f, " UNICODE_PROP_COUNT,\n"); + fprintf(f, "} UnicodePropertyEnum;\n\n"); + + i = PROP_ASCII_Hex_Digit; + dump_name_table(f, "unicode_prop_name_table", + unicode_prop_name + i, PROP_XID_Start - i + 1, + unicode_prop_short_name + i); + + fprintf(f, "static const uint8_t * const unicode_prop_table[] = {\n"); + for(i = 0; i < PROP_TABLE_COUNT; i++) { + fprintf(f, " unicode_prop_%s_table,\n", unicode_prop_name[i]); + } + fprintf(f, "};\n\n"); + + fprintf(f, "static const uint16_t unicode_prop_len_table[] = {\n"); + for(i = 0; i < PROP_TABLE_COUNT; i++) { + fprintf(f, " countof(unicode_prop_%s_table),\n", unicode_prop_name[i]); + } + fprintf(f, "};\n\n"); +} + +#ifdef USE_TEST +int check_conv(uint32_t *res, uint32_t c, int conv_type) +{ + return lre_case_conv(res, c, conv_type); +} + +void check_case_conv(void) +{ + CCInfo *tab = unicode_db; + uint32_t res[3]; + int l, error; + CCInfo ci_s, *ci1, *ci = &ci_s; + int code; + + for(code = 0; code <= CHARCODE_MAX; code++) { + ci1 = &tab[code]; + *ci = *ci1; + if (ci->l_len == 0) { + ci->l_len = 1; + ci->l_data[0] = code; + } + if (ci->u_len == 0) { + ci->u_len = 1; + ci->u_data[0] = code; + } + if (ci->f_len == 0) { + ci->f_len = 1; + ci->f_data[0] = code; + } + + error = 0; + l = check_conv(res, code, 0); + if (l != ci->u_len || tabcmp((int *)res, ci->u_data, l)) { + printf("ERROR: L\n"); + error++; + } + l = check_conv(res, code, 1); + if (l != ci->l_len || tabcmp((int *)res, ci->l_data, l)) { + printf("ERROR: U\n"); + error++; + } + l = check_conv(res, code, 2); + if (l != ci->f_len || tabcmp((int *)res, ci->f_data, l)) { + printf("ERROR: F\n"); + error++; + } + if (error) { + dump_cc_info(ci, code); + exit(1); + } + } +} + +#ifdef PROFILE +static int64_t get_time_ns(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000000000 + ts.tv_nsec; +} +#endif + + +void check_flags(void) +{ + int c; + BOOL flag_ref, flag; + for(c = 0; c <= CHARCODE_MAX; c++) { + flag_ref = get_prop(c, PROP_Cased); + flag = lre_is_cased(c); + if (flag != flag_ref) { + printf("ERROR: c=%05x cased=%d ref=%d\n", + c, flag, flag_ref); + exit(1); + } + + flag_ref = get_prop(c, PROP_Case_Ignorable); + flag = lre_is_case_ignorable(c); + if (flag != flag_ref) { + printf("ERROR: c=%05x case_ignorable=%d ref=%d\n", + c, flag, flag_ref); + exit(1); + } + + flag_ref = get_prop(c, PROP_ID_Start); + flag = lre_is_id_start(c); + if (flag != flag_ref) { + printf("ERROR: c=%05x id_start=%d ref=%d\n", + c, flag, flag_ref); + exit(1); + } + + flag_ref = get_prop(c, PROP_ID_Continue); + flag = lre_is_id_continue(c); + if (flag != flag_ref) { + printf("ERROR: c=%05x id_cont=%d ref=%d\n", + c, flag, flag_ref); + exit(1); + } + } +#ifdef PROFILE + { + int64_t ti, count; + ti = get_time_ns(); + count = 0; + for(c = 0x20; c <= 0xffff; c++) { + flag_ref = get_prop(c, PROP_ID_Start); + flag = lre_is_id_start(c); + assert(flag == flag_ref); + count++; + } + ti = get_time_ns() - ti; + printf("flags time=%0.1f ns/char\n", + (double)ti / count); + } +#endif +} + +#endif + +#define CC_BLOCK_LEN 32 + +void build_cc_table(FILE *f) +{ + int i, cc, n, type, n1, block_end_pos; + DynBuf dbuf_s, *dbuf = &dbuf_s; + DynBuf dbuf1_s, *dbuf1 = &dbuf1_s; +#if defined(DUMP_CC_TABLE) || defined(DUMP_TABLE_SIZE) + int cw_len_tab[3], cw_start, cc_table_len; +#endif + uint32_t v; + + dbuf_init(dbuf); + dbuf_init(dbuf1); +#if defined(DUMP_CC_TABLE) || defined(DUMP_TABLE_SIZE) + cc_table_len = 0; + for(i = 0; i < countof(cw_len_tab); i++) + cw_len_tab[i] = 0; +#endif + block_end_pos = CC_BLOCK_LEN; + for(i = 0; i <= CHARCODE_MAX;) { + cc = unicode_db[i].combining_class; + assert(cc <= 255); + /* check increasing values */ + n = 1; + while ((i + n) <= CHARCODE_MAX && + unicode_db[i + n].combining_class == (cc + n)) + n++; + if (n >= 2) { + type = 1; + } else { + type = 0; + n = 1; + while ((i + n) <= CHARCODE_MAX && + unicode_db[i + n].combining_class == cc) + n++; + } + /* no need to encode the last run */ + if (cc == 0 && (i + n - 1) == CHARCODE_MAX) + break; +#ifdef DUMP_CC_TABLE + printf("%05x %6d %d %d\n", i, n, type, cc); +#endif + if (type == 0) { + if (cc == 0) + type = 2; + else if (cc == 230) + type = 3; + } + n1 = n - 1; + + /* add an entry to the index if necessary */ + if (dbuf->size >= block_end_pos) { + v = i | ((dbuf->size - block_end_pos) << 21); + dbuf_putc(dbuf1, v); + dbuf_putc(dbuf1, v >> 8); + dbuf_putc(dbuf1, v >> 16); + block_end_pos += CC_BLOCK_LEN; + } +#if defined(DUMP_CC_TABLE) || defined(DUMP_TABLE_SIZE) + cw_start = dbuf->size; +#endif + if (n1 < 48) { + dbuf_putc(dbuf, n1 | (type << 6)); + } else if (n1 < 48 + (1 << 11)) { + n1 -= 48; + dbuf_putc(dbuf, ((n1 >> 8) + 48) | (type << 6)); + dbuf_putc(dbuf, n1); + } else { + n1 -= 48 + (1 << 11); + assert(n1 < (1 << 20)); + dbuf_putc(dbuf, ((n1 >> 16) + 56) | (type << 6)); + dbuf_putc(dbuf, n1 >> 8); + dbuf_putc(dbuf, n1); + } +#if defined(DUMP_CC_TABLE) || defined(DUMP_TABLE_SIZE) + cw_len_tab[dbuf->size - cw_start - 1]++; + cc_table_len++; +#endif + if (type == 0 || type == 1) + dbuf_putc(dbuf, cc); + i += n; + } + + /* last index entry */ + v = i; + dbuf_putc(dbuf1, v); + dbuf_putc(dbuf1, v >> 8); + dbuf_putc(dbuf1, v >> 16); + + dump_byte_table(f, "unicode_cc_table", dbuf->buf, dbuf->size); + dump_byte_table(f, "unicode_cc_index", dbuf1->buf, dbuf1->size); + +#if defined(DUMP_CC_TABLE) || defined(DUMP_TABLE_SIZE) + printf("CC table: size=%d (%d entries) [", + (int)(dbuf->size + dbuf1->size), + cc_table_len); + for(i = 0; i < countof(cw_len_tab); i++) + printf(" %d", cw_len_tab[i]); + printf(" ]\n"); +#endif + dbuf_free(dbuf); + dbuf_free(dbuf1); +} + +/* maximum length of decomposition: 18 chars (1), then 8 */ +#ifndef USE_TEST +typedef enum { + DECOMP_TYPE_C1, /* 16 bit char */ + DECOMP_TYPE_L1, /* 16 bit char table */ + DECOMP_TYPE_L2, + DECOMP_TYPE_L3, + DECOMP_TYPE_L4, + DECOMP_TYPE_L5, /* XXX: not used */ + DECOMP_TYPE_L6, /* XXX: could remove */ + DECOMP_TYPE_L7, /* XXX: could remove */ + DECOMP_TYPE_LL1, /* 18 bit char table */ + DECOMP_TYPE_LL2, + DECOMP_TYPE_S1, /* 8 bit char table */ + DECOMP_TYPE_S2, + DECOMP_TYPE_S3, + DECOMP_TYPE_S4, + DECOMP_TYPE_S5, + DECOMP_TYPE_I1, /* increment 16 bit char value */ + DECOMP_TYPE_I2_0, + DECOMP_TYPE_I2_1, + DECOMP_TYPE_I3_1, + DECOMP_TYPE_I3_2, + DECOMP_TYPE_I4_1, + DECOMP_TYPE_I4_2, + DECOMP_TYPE_B1, /* 16 bit base + 8 bit offset */ + DECOMP_TYPE_B2, + DECOMP_TYPE_B3, + DECOMP_TYPE_B4, + DECOMP_TYPE_B5, + DECOMP_TYPE_B6, + DECOMP_TYPE_B7, + DECOMP_TYPE_B8, + DECOMP_TYPE_B18, + DECOMP_TYPE_LS2, + DECOMP_TYPE_PAT3, + DECOMP_TYPE_S2_UL, + DECOMP_TYPE_LS2_UL, +} DecompTypeEnum; +#endif + +const char *decomp_type_str[] = { + "C1", + "L1", + "L2", + "L3", + "L4", + "L5", + "L6", + "L7", + "LL1", + "LL2", + "S1", + "S2", + "S3", + "S4", + "S5", + "I1", + "I2_0", + "I2_1", + "I3_1", + "I3_2", + "I4_1", + "I4_2", + "B1", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B18", + "LS2", + "PAT3", + "S2_UL", + "LS2_UL", +}; + +const int decomp_incr_tab[4][4] = { + { DECOMP_TYPE_I1, 0, -1 }, + { DECOMP_TYPE_I2_0, 0, 1, -1 }, + { DECOMP_TYPE_I3_1, 1, 2, -1 }, + { DECOMP_TYPE_I4_1, 1, 2, -1 }, +}; + +/* + entry size: + type bits + code 18 + len 7 + compat 1 + type 5 + index 16 + total 47 +*/ + +typedef struct { + int code; + uint8_t len; + uint8_t type; + uint8_t c_len; + uint16_t c_min; + uint16_t data_index; + int cost; /* size in bytes from this entry to the end */ +} DecompEntry; + +int get_decomp_run_size(const DecompEntry *de) +{ + int s; + s = 6; + if (de->type <= DECOMP_TYPE_C1) { + /* nothing more */ + } else if (de->type <= DECOMP_TYPE_L7) { + s += de->len * de->c_len * 2; + } else if (de->type <= DECOMP_TYPE_LL2) { + /* 18 bits per char */ + s += (de->len * de->c_len * 18 + 7) / 8; + } else if (de->type <= DECOMP_TYPE_S5) { + s += de->len * de->c_len; + } else if (de->type <= DECOMP_TYPE_I4_2) { + s += de->c_len * 2; + } else if (de->type <= DECOMP_TYPE_B18) { + s += 2 + de->len * de->c_len; + } else if (de->type <= DECOMP_TYPE_LS2) { + s += de->len * 3; + } else if (de->type <= DECOMP_TYPE_PAT3) { + s += 4 + de->len * 2; + } else if (de->type <= DECOMP_TYPE_S2_UL) { + s += de->len; + } else if (de->type <= DECOMP_TYPE_LS2_UL) { + s += (de->len / 2) * 3; + } else { + abort(); + } + return s; +} + +static const uint16_t unicode_short_table[2] = { 0x2044, 0x2215 }; + +/* return -1 if not found */ +int get_short_code(int c) +{ + int i; + if (c < 0x80) { + return c; + } else if (c >= 0x300 && c < 0x350) { + return c - 0x300 + 0x80; + } else { + for(i = 0; i < countof(unicode_short_table); i++) { + if (c == unicode_short_table[i]) + return i + 0x80 + 0x50; + } + return -1; + } +} + +static BOOL is_short(int code) +{ + return get_short_code(code) >= 0; +} + +static BOOL is_short_tab(const int *tab, int len) +{ + int i; + for(i = 0; i < len; i++) { + if (!is_short(tab[i])) + return FALSE; + } + return TRUE; +} + +static BOOL is_16bit(const int *tab, int len) +{ + int i; + for(i = 0; i < len; i++) { + if (tab[i] > 0xffff) + return FALSE; + } + return TRUE; +} + +static uint32_t to_lower_simple(uint32_t c) +{ + /* Latin1 and Cyrillic */ + if (c < 0x100 || (c >= 0x410 && c <= 0x42f)) + c += 0x20; + else + c++; + return c; +} + +/* select best encoding with dynamic programming */ +void find_decomp_run(DecompEntry *tab_de, int i) +{ + DecompEntry de_s, *de = &de_s; + CCInfo *ci, *ci1, *ci2; + int l, j, n, len_max; + + ci = &unicode_db[i]; + l = ci->decomp_len; + if (l == 0) { + tab_de[i].cost = tab_de[i + 1].cost; + return; + } + + /* the offset for the compose table has only 6 bits, so we must + limit if it can be used by the compose table */ + if (!ci->is_compat && !ci->is_excluded && l == 2) + len_max = 64; + else + len_max = 127; + + tab_de[i].cost = 0x7fffffff; + + if (!is_16bit(ci->decomp_data, l)) { + assert(l <= 2); + + n = 1; + for(;;) { + de->code = i; + de->len = n; + de->type = DECOMP_TYPE_LL1 + l - 1; + de->c_len = l; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + if (!((i + n) <= CHARCODE_MAX && n < len_max)) + break; + ci1 = &unicode_db[i + n]; + /* Note: we accept a hole */ + if (!(ci1->decomp_len == 0 || + (ci1->decomp_len == l && + ci1->is_compat == ci->is_compat))) + break; + n++; + } + return; + } + + if (l <= 7) { + n = 1; + for(;;) { + de->code = i; + de->len = n; + if (l == 1 && n == 1) { + de->type = DECOMP_TYPE_C1; + } else { + assert(l <= 8); + de->type = DECOMP_TYPE_L1 + l - 1; + } + de->c_len = l; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + + if (!((i + n) <= CHARCODE_MAX && n < len_max)) + break; + ci1 = &unicode_db[i + n]; + /* Note: we accept a hole */ + if (!(ci1->decomp_len == 0 || + (ci1->decomp_len == l && + ci1->is_compat == ci->is_compat && + is_16bit(ci1->decomp_data, l)))) + break; + n++; + } + } + + if (l <= 8 || l == 18) { + int c_min, c_max, c; + c_min = c_max = -1; + n = 1; + for(;;) { + ci1 = &unicode_db[i + n - 1]; + for(j = 0; j < l; j++) { + c = ci1->decomp_data[j]; + if (c == 0x20) { + /* we accept space for Arabic */ + } else if (c_min == -1) { + c_min = c_max = c; + } else { + c_min = min_int(c_min, c); + c_max = max_int(c_max, c); + } + } + if ((c_max - c_min) > 254) + break; + de->code = i; + de->len = n; + if (l == 18) + de->type = DECOMP_TYPE_B18; + else + de->type = DECOMP_TYPE_B1 + l - 1; + de->c_len = l; + de->c_min = c_min; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + if (!((i + n) <= CHARCODE_MAX && n < len_max)) + break; + ci1 = &unicode_db[i + n]; + if (!(ci1->decomp_len == l && + ci1->is_compat == ci->is_compat)) + break; + n++; + } + } + + /* find an ascii run */ + if (l <= 5 && is_short_tab(ci->decomp_data, l)) { + n = 1; + for(;;) { + de->code = i; + de->len = n; + de->type = DECOMP_TYPE_S1 + l - 1; + de->c_len = l; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + + if (!((i + n) <= CHARCODE_MAX && n < len_max)) + break; + ci1 = &unicode_db[i + n]; + /* Note: we accept a hole */ + if (!(ci1->decomp_len == 0 || + (ci1->decomp_len == l && + ci1->is_compat == ci->is_compat && + is_short_tab(ci1->decomp_data, l)))) + break; + n++; + } + } + + /* check if a single char is increasing */ + if (l <= 4) { + int idx1, idx; + + for(idx1 = 1; (idx = decomp_incr_tab[l - 1][idx1]) >= 0; idx1++) { + n = 1; + for(;;) { + de->code = i; + de->len = n; + de->type = decomp_incr_tab[l - 1][0] + idx1 - 1; + de->c_len = l; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + + if (!((i + n) <= CHARCODE_MAX && n < len_max)) + break; + ci1 = &unicode_db[i + n]; + if (!(ci1->decomp_len == l && + ci1->is_compat == ci->is_compat)) + goto next1; + for(j = 0; j < l; j++) { + if (j == idx) { + if (ci1->decomp_data[j] != ci->decomp_data[j] + n) + goto next1; + } else { + if (ci1->decomp_data[j] != ci->decomp_data[j]) + goto next1; + } + } + n++; + } + next1: ; + } + } + + if (l == 3) { + n = 1; + for(;;) { + de->code = i; + de->len = n; + de->type = DECOMP_TYPE_PAT3; + de->c_len = l; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + if (!((i + n) <= CHARCODE_MAX && n < len_max)) + break; + ci1 = &unicode_db[i + n]; + if (!(ci1->decomp_len == l && + ci1->is_compat == ci->is_compat && + ci1->decomp_data[1] <= 0xffff && + ci1->decomp_data[0] == ci->decomp_data[0] && + ci1->decomp_data[l - 1] == ci->decomp_data[l - 1])) + break; + n++; + } + } + + if (l == 2 && is_short(ci->decomp_data[1])) { + n = 1; + for(;;) { + de->code = i; + de->len = n; + de->type = DECOMP_TYPE_LS2; + de->c_len = l; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + if (!((i + n) <= CHARCODE_MAX && n < len_max)) + break; + ci1 = &unicode_db[i + n]; + if (!(ci1->decomp_len == 0 || + (ci1->decomp_len == l && + ci1->is_compat == ci->is_compat && + ci1->decomp_data[0] <= 0xffff && + is_short(ci1->decomp_data[1])))) + break; + n++; + } + } + + if (l == 2) { + BOOL is_16bit; + + n = 0; + is_16bit = FALSE; + for(;;) { + if (!((i + n + 1) <= CHARCODE_MAX && n + 2 <= len_max)) + break; + ci1 = &unicode_db[i + n]; + if (!(ci1->decomp_len == l && + ci1->is_compat == ci->is_compat && + is_short(ci1->decomp_data[1]))) + break; + if (!is_16bit && !is_short(ci1->decomp_data[0])) + is_16bit = TRUE; + ci2 = &unicode_db[i + n + 1]; + if (!(ci2->decomp_len == l && + ci2->is_compat == ci->is_compat && + ci2->decomp_data[0] == to_lower_simple(ci1->decomp_data[0]) && + ci2->decomp_data[1] == ci1->decomp_data[1])) + break; + n += 2; + de->code = i; + de->len = n; + de->type = DECOMP_TYPE_S2_UL + is_16bit; + de->c_len = l; + de->cost = get_decomp_run_size(de) + tab_de[i + n].cost; + if (de->cost < tab_de[i].cost) { + tab_de[i] = *de; + } + } + } +} + +void put16(uint8_t *data_buf, int *pidx, uint16_t c) +{ + int idx; + idx = *pidx; + data_buf[idx++] = c; + data_buf[idx++] = c >> 8; + *pidx = idx; +} + +void add_decomp_data(uint8_t *data_buf, int *pidx, DecompEntry *de) +{ + int i, j, idx, c; + CCInfo *ci; + + idx = *pidx; + de->data_index = idx; + if (de->type <= DECOMP_TYPE_C1) { + ci = &unicode_db[de->code]; + assert(ci->decomp_len == 1); + de->data_index = ci->decomp_data[0]; + } else if (de->type <= DECOMP_TYPE_L7) { + for(i = 0; i < de->len; i++) { + ci = &unicode_db[de->code + i]; + for(j = 0; j < de->c_len; j++) { + if (ci->decomp_len == 0) + c = 0; + else + c = ci->decomp_data[j]; + put16(data_buf, &idx, c); + } + } + } else if (de->type <= DECOMP_TYPE_LL2) { + int n, p, k; + n = (de->len * de->c_len * 18 + 7) / 8; + p = de->len * de->c_len * 2; + memset(data_buf + idx, 0, n); + k = 0; + for(i = 0; i < de->len; i++) { + ci = &unicode_db[de->code + i]; + for(j = 0; j < de->c_len; j++) { + if (ci->decomp_len == 0) + c = 0; + else + c = ci->decomp_data[j]; + data_buf[idx + k * 2] = c; + data_buf[idx + k * 2 + 1] = c >> 8; + data_buf[idx + p + (k / 4)] |= (c >> 16) << ((k % 4) * 2); + k++; + } + } + idx += n; + } else if (de->type <= DECOMP_TYPE_S5) { + for(i = 0; i < de->len; i++) { + ci = &unicode_db[de->code + i]; + for(j = 0; j < de->c_len; j++) { + if (ci->decomp_len == 0) + c = 0; + else + c = ci->decomp_data[j]; + c = get_short_code(c); + assert(c >= 0); + data_buf[idx++] = c; + } + } + } else if (de->type <= DECOMP_TYPE_I4_2) { + ci = &unicode_db[de->code]; + assert(ci->decomp_len == de->c_len); + for(j = 0; j < de->c_len; j++) + put16(data_buf, &idx, ci->decomp_data[j]); + } else if (de->type <= DECOMP_TYPE_B18) { + c = de->c_min; + data_buf[idx++] = c; + data_buf[idx++] = c >> 8; + for(i = 0; i < de->len; i++) { + ci = &unicode_db[de->code + i]; + for(j = 0; j < de->c_len; j++) { + assert(ci->decomp_len == de->c_len); + c = ci->decomp_data[j]; + if (c == 0x20) { + c = 0xff; + } else { + c -= de->c_min; + assert((uint32_t)c <= 254); + } + data_buf[idx++] = c; + } + } + } else if (de->type <= DECOMP_TYPE_LS2) { + assert(de->c_len == 2); + for(i = 0; i < de->len; i++) { + ci = &unicode_db[de->code + i]; + if (ci->decomp_len == 0) + c = 0; + else + c = ci->decomp_data[0]; + put16(data_buf, &idx, c); + + if (ci->decomp_len == 0) + c = 0; + else + c = ci->decomp_data[1]; + c = get_short_code(c); + assert(c >= 0); + data_buf[idx++] = c; + } + } else if (de->type <= DECOMP_TYPE_PAT3) { + ci = &unicode_db[de->code]; + assert(ci->decomp_len == 3); + put16(data_buf, &idx, ci->decomp_data[0]); + put16(data_buf, &idx, ci->decomp_data[2]); + for(i = 0; i < de->len; i++) { + ci = &unicode_db[de->code + i]; + assert(ci->decomp_len == 3); + put16(data_buf, &idx, ci->decomp_data[1]); + } + } else if (de->type <= DECOMP_TYPE_S2_UL) { + for(i = 0; i < de->len; i += 2) { + ci = &unicode_db[de->code + i]; + c = ci->decomp_data[0]; + c = get_short_code(c); + assert(c >= 0); + data_buf[idx++] = c; + c = ci->decomp_data[1]; + c = get_short_code(c); + assert(c >= 0); + data_buf[idx++] = c; + } + } else if (de->type <= DECOMP_TYPE_LS2_UL) { + for(i = 0; i < de->len; i += 2) { + ci = &unicode_db[de->code + i]; + c = ci->decomp_data[0]; + put16(data_buf, &idx, c); + c = ci->decomp_data[1]; + c = get_short_code(c); + assert(c >= 0); + data_buf[idx++] = c; + } + } else { + abort(); + } + *pidx = idx; +} + +#if 0 +void dump_large_char(void) +{ + int i, j; + for(i = 0; i <= CHARCODE_MAX; i++) { + CCInfo *ci = &unicode_db[i]; + for(j = 0; j < ci->decomp_len; j++) { + if (ci->decomp_data[j] > 0xffff) + printf("%05x\n", ci->decomp_data[j]); + } + } +} +#endif + +void build_compose_table(FILE *f, const DecompEntry *tab_de); + +void build_decompose_table(FILE *f) +{ + int i, array_len, code_max, data_len, count; + DecompEntry *tab_de, de_s, *de = &de_s; + uint8_t *data_buf; + + code_max = CHARCODE_MAX; + + tab_de = mallocz((code_max + 2) * sizeof(*tab_de)); + + for(i = code_max; i >= 0; i--) { + find_decomp_run(tab_de, i); + } + + /* build the data buffer */ + data_buf = malloc(100000); + data_len = 0; + array_len = 0; + for(i = 0; i <= code_max; i++) { + de = &tab_de[i]; + if (de->len != 0) { + add_decomp_data(data_buf, &data_len, de); + i += de->len - 1; + array_len++; + } + } + +#ifdef DUMP_DECOMP_TABLE + /* dump */ + { + int size, size1; + + printf("START LEN TYPE L C SIZE\n"); + size = 0; + for(i = 0; i <= code_max; i++) { + de = &tab_de[i]; + if (de->len != 0) { + size1 = get_decomp_run_size(de); + printf("%05x %3d %6s %2d %1d %4d\n", i, de->len, + decomp_type_str[de->type], de->c_len, + unicode_db[i].is_compat, size1); + i += de->len - 1; + size += size1; + } + } + + printf("array_len=%d estimated size=%d bytes actual=%d bytes\n", + array_len, size, array_len * 6 + data_len); + } +#endif + + fprintf(f, "static const uint32_t unicode_decomp_table1[%u] = {", + array_len); + count = 0; + for(i = 0; i <= code_max; i++) { + de = &tab_de[i]; + if (de->len != 0) { + uint32_t v; + if (count++ % 4 == 0) + fprintf(f, "\n "); + v = (de->code << (32 - 18)) | + (de->len << (32 - 18 - 7)) | + (de->type << (32 - 18 - 7 - 6)) | + unicode_db[de->code].is_compat; + fprintf(f, " 0x%08x,", v); + i += de->len - 1; + } + } + fprintf(f, "\n};\n\n"); + + fprintf(f, "static const uint16_t unicode_decomp_table2[%u] = {", + array_len); + count = 0; + for(i = 0; i <= code_max; i++) { + de = &tab_de[i]; + if (de->len != 0) { + if (count++ % 8 == 0) + fprintf(f, "\n "); + fprintf(f, " 0x%04x,", de->data_index); + i += de->len - 1; + } + } + fprintf(f, "\n};\n\n"); + + fprintf(f, "static const uint8_t unicode_decomp_data[%u] = {", + data_len); + for(i = 0; i < data_len; i++) { + if (i % 8 == 0) + fprintf(f, "\n "); + fprintf(f, " 0x%02x,", data_buf[i]); + } + fprintf(f, "\n};\n\n"); + + build_compose_table(f, tab_de); + + free(data_buf); + + free(tab_de); +} + +typedef struct { + uint32_t c[2]; + uint32_t p; +} ComposeEntry; + +#define COMPOSE_LEN_MAX 10000 + +static int ce_cmp(const void *p1, const void *p2) +{ + const ComposeEntry *ce1 = p1; + const ComposeEntry *ce2 = p2; + int i; + + for(i = 0; i < 2; i++) { + if (ce1->c[i] < ce2->c[i]) + return -1; + else if (ce1->c[i] > ce2->c[i]) + return 1; + } + return 0; +} + + +static int get_decomp_pos(const DecompEntry *tab_de, int c) +{ + int i, v, k; + const DecompEntry *de; + + k = 0; + for(i = 0; i <= CHARCODE_MAX; i++) { + de = &tab_de[i]; + if (de->len != 0) { + if (c >= de->code && c < de->code + de->len) { + v = c - de->code; + assert(v < 64); + v |= k << 6; + assert(v < 65536); + return v; + } + i += de->len - 1; + k++; + } + } + return -1; +} + +void build_compose_table(FILE *f, const DecompEntry *tab_de) +{ + int i, v, tab_ce_len; + ComposeEntry *ce, *tab_ce; + + tab_ce = malloc(sizeof(*tab_ce) * COMPOSE_LEN_MAX); + tab_ce_len = 0; + for(i = 0; i <= CHARCODE_MAX; i++) { + CCInfo *ci = &unicode_db[i]; + if (ci->decomp_len == 2 && !ci->is_compat && + !ci->is_excluded) { + assert(tab_ce_len < COMPOSE_LEN_MAX); + ce = &tab_ce[tab_ce_len++]; + ce->c[0] = ci->decomp_data[0]; + ce->c[1] = ci->decomp_data[1]; + ce->p = i; + } + } + qsort(tab_ce, tab_ce_len, sizeof(*tab_ce), ce_cmp); + +#if 0 + { + printf("tab_ce_len=%d\n", tab_ce_len); + for(i = 0; i < tab_ce_len; i++) { + ce = &tab_ce[i]; + printf("%05x %05x %05x\n", ce->c[0], ce->c[1], ce->p); + } + } +#endif + + fprintf(f, "static const uint16_t unicode_comp_table[%u] = {", + tab_ce_len); + for(i = 0; i < tab_ce_len; i++) { + if (i % 8 == 0) + fprintf(f, "\n "); + v = get_decomp_pos(tab_de, tab_ce[i].p); + if (v < 0) { + printf("ERROR: entry for c=%04x not found\n", + tab_ce[i].p); + exit(1); + } + fprintf(f, " 0x%04x,", v); + } + fprintf(f, "\n};\n\n"); + + free(tab_ce); +} + +#ifdef USE_TEST +void check_decompose_table(void) +{ + int c; + CCInfo *ci; + int res[UNICODE_DECOMP_LEN_MAX], *ref; + int len, ref_len, is_compat; + + for(is_compat = 0; is_compat <= 1; is_compat++) { + for(c = 0; c < CHARCODE_MAX; c++) { + ci = &unicode_db[c]; + ref_len = ci->decomp_len; + ref = ci->decomp_data; + if (!is_compat && ci->is_compat) { + ref_len = 0; + } + len = unicode_decomp_char((uint32_t *)res, c, is_compat); + if (len != ref_len || + tabcmp(res, ref, ref_len) != 0) { + printf("ERROR c=%05x compat=%d\n", c, is_compat); + dump_str("res", res, len); + dump_str("ref", ref, ref_len); + exit(1); + } + } + } +} + +void check_compose_table(void) +{ + int i, p; + /* XXX: we don't test all the cases */ + + for(i = 0; i <= CHARCODE_MAX; i++) { + CCInfo *ci = &unicode_db[i]; + if (ci->decomp_len == 2 && !ci->is_compat && + !ci->is_excluded) { + p = unicode_compose_pair(ci->decomp_data[0], ci->decomp_data[1]); + if (p != i) { + printf("ERROR compose: c=%05x %05x -> %05x ref=%05x\n", + ci->decomp_data[0], ci->decomp_data[1], p, i); + exit(1); + } + } + } + + + +} + +#endif + + + +#ifdef USE_TEST + +void check_str(const char *msg, int num, const int *in_buf, int in_len, + const int *buf1, int len1, + const int *buf2, int len2) +{ + if (len1 != len2 || tabcmp(buf1, buf2, len1) != 0) { + printf("%d: ERROR %s:\n", num, msg); + dump_str(" in", in_buf, in_len); + dump_str("res", buf1, len1); + dump_str("ref", buf2, len2); + exit(1); + } +} + +void check_cc_table(void) +{ + int cc, cc_ref, c; + + for(c = 0; c <= CHARCODE_MAX; c++) { + cc_ref = unicode_db[c].combining_class; + cc = unicode_get_cc(c); + if (cc != cc_ref) { + printf("ERROR: c=%04x cc=%d cc_ref=%d\n", + c, cc, cc_ref); + exit(1); + } + } +#ifdef PROFILE + { + int64_t ti, count; + + ti = get_time_ns(); + count = 0; + /* only do it on meaningful chars */ + for(c = 0x20; c <= 0xffff; c++) { + cc_ref = unicode_db[c].combining_class; + cc = unicode_get_cc(c); + count++; + } + ti = get_time_ns() - ti; + printf("cc time=%0.1f ns/char\n", + (double)ti / count); + } +#endif +} + +void normalization_test(const char *filename) +{ + FILE *f; + char line[4096], *p; + int *in_str, *nfc_str, *nfd_str, *nfkc_str, *nfkd_str; + int in_len, nfc_len, nfd_len, nfkc_len, nfkd_len; + int *buf, buf_len, pos; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + pos = 0; + for(;;) { + if (!get_line(line, sizeof(line), f)) + break; + pos++; + p = line; + while (isspace(*p)) + p++; + if (*p == '#' || *p == '@') + continue; + in_str = get_field_str(&in_len, p, 0); + nfc_str = get_field_str(&nfc_len, p, 1); + nfd_str = get_field_str(&nfd_len, p, 2); + nfkc_str = get_field_str(&nfkc_len, p, 3); + nfkd_str = get_field_str(&nfkd_len, p, 4); + + // dump_str("in", in_str, in_len); + + buf_len = unicode_normalize((uint32_t **)&buf, (uint32_t *)in_str, in_len, UNICODE_NFD, NULL, NULL); + check_str("nfd", pos, in_str, in_len, buf, buf_len, nfd_str, nfd_len); + free(buf); + + buf_len = unicode_normalize((uint32_t **)&buf, (uint32_t *)in_str, in_len, UNICODE_NFKD, NULL, NULL); + check_str("nfkd", pos, in_str, in_len, buf, buf_len, nfkd_str, nfkd_len); + free(buf); + + buf_len = unicode_normalize((uint32_t **)&buf, (uint32_t *)in_str, in_len, UNICODE_NFC, NULL, NULL); + check_str("nfc", pos, in_str, in_len, buf, buf_len, nfc_str, nfc_len); + free(buf); + + buf_len = unicode_normalize((uint32_t **)&buf, (uint32_t *)in_str, in_len, UNICODE_NFKC, NULL, NULL); + check_str("nfkc", pos, in_str, in_len, buf, buf_len, nfkc_str, nfkc_len); + free(buf); + + free(in_str); + free(nfc_str); + free(nfd_str); + free(nfkc_str); + free(nfkd_str); + } + fclose(f); +} +#endif + +int main(int argc, char **argv) +{ + const char *unicode_db_path, *outfilename; + char filename[1024]; + + if (argc < 2) { + printf("usage: %s unicode_db_path [output_file]\n" + "\n" + "If no output_file is given, a self test is done using the current unicode library\n", + argv[0]); + exit(1); + } + unicode_db_path = argv[1]; + outfilename = NULL; + if (argc >= 3) + outfilename = argv[2]; + + unicode_db = mallocz(sizeof(unicode_db[0]) * (CHARCODE_MAX + 1)); + + snprintf(filename, sizeof(filename), "%s/UnicodeData.txt", unicode_db_path); + + parse_unicode_data(filename); + + snprintf(filename, sizeof(filename), "%s/SpecialCasing.txt", unicode_db_path); + parse_special_casing(unicode_db, filename); + + snprintf(filename, sizeof(filename), "%s/CaseFolding.txt", unicode_db_path); + parse_case_folding(unicode_db, filename); + + snprintf(filename, sizeof(filename), "%s/CompositionExclusions.txt", unicode_db_path); + parse_composition_exclusions(filename); + + snprintf(filename, sizeof(filename), "%s/DerivedCoreProperties.txt", unicode_db_path); + parse_derived_core_properties(filename); + + snprintf(filename, sizeof(filename), "%s/DerivedNormalizationProps.txt", unicode_db_path); + parse_derived_norm_properties(filename); + + snprintf(filename, sizeof(filename), "%s/PropList.txt", unicode_db_path); + parse_prop_list(filename); + + snprintf(filename, sizeof(filename), "%s/Scripts.txt", unicode_db_path); + parse_scripts(filename); + + snprintf(filename, sizeof(filename), "%s/ScriptExtensions.txt", + unicode_db_path); + parse_script_extensions(filename); + + snprintf(filename, sizeof(filename), "%s/emoji-data.txt", + unicode_db_path); + parse_prop_list(filename); + + // dump_unicode_data(unicode_db); + build_conv_table(unicode_db); + +#ifdef DUMP_CASE_FOLDING_SPECIAL_CASES + dump_case_folding_special_cases(unicode_db); +#endif + + if (!outfilename) { +#ifdef USE_TEST + check_case_conv(); + check_flags(); + check_decompose_table(); + check_compose_table(); + check_cc_table(); + snprintf(filename, sizeof(filename), "%s/NormalizationTest.txt", unicode_db_path); + normalization_test(filename); +#else + fprintf(stderr, "Tests are not compiled\n"); + exit(1); +#endif + } else + { + FILE *fo = fopen(outfilename, "wb"); + + if (!fo) { + perror(outfilename); + exit(1); + } + fprintf(fo, + "/* Compressed unicode tables */\n" + "/* Automatically generated file - do not edit */\n" + "\n" + "#include \n" + "\n"); + dump_case_conv_table(fo); + compute_internal_props(); + build_flags_tables(fo); + fprintf(fo, "#ifdef CONFIG_ALL_UNICODE\n\n"); + build_cc_table(fo); + build_decompose_table(fo); + build_general_category_table(fo); + build_script_table(fo); + build_script_ext_table(fo); + build_prop_list_table(fo); + fprintf(fo, "#endif /* CONFIG_ALL_UNICODE */\n"); + fclose(fo); + } + return 0; +} diff --git a/source/engine/thirdparty/quickjs/unicode_gen_def.h b/source/engine/thirdparty/quickjs/unicode_gen_def.h new file mode 100644 index 0000000..e7c2464 --- /dev/null +++ b/source/engine/thirdparty/quickjs/unicode_gen_def.h @@ -0,0 +1,291 @@ +#ifdef UNICODE_GENERAL_CATEGORY +DEF(Cn, "Unassigned") /* must be zero */ +DEF(Lu, "Uppercase_Letter") +DEF(Ll, "Lowercase_Letter") +DEF(Lt, "Titlecase_Letter") +DEF(Lm, "Modifier_Letter") +DEF(Lo, "Other_Letter") +DEF(Mn, "Nonspacing_Mark") +DEF(Mc, "Spacing_Mark") +DEF(Me, "Enclosing_Mark") +DEF(Nd, "Decimal_Number,digit") +DEF(Nl, "Letter_Number") +DEF(No, "Other_Number") +DEF(Sm, "Math_Symbol") +DEF(Sc, "Currency_Symbol") +DEF(Sk, "Modifier_Symbol") +DEF(So, "Other_Symbol") +DEF(Pc, "Connector_Punctuation") +DEF(Pd, "Dash_Punctuation") +DEF(Ps, "Open_Punctuation") +DEF(Pe, "Close_Punctuation") +DEF(Pi, "Initial_Punctuation") +DEF(Pf, "Final_Punctuation") +DEF(Po, "Other_Punctuation") +DEF(Zs, "Space_Separator") +DEF(Zl, "Line_Separator") +DEF(Zp, "Paragraph_Separator") +DEF(Cc, "Control,cntrl") +DEF(Cf, "Format") +DEF(Cs, "Surrogate") +DEF(Co, "Private_Use") +/* synthetic properties */ +DEF(LC, "Cased_Letter") +DEF(L, "Letter") +DEF(M, "Mark,Combining_Mark") +DEF(N, "Number") +DEF(S, "Symbol") +DEF(P, "Punctuation,punct") +DEF(Z, "Separator") +DEF(C, "Other") +#endif + +#ifdef UNICODE_SCRIPT +/* scripts aliases names in PropertyValueAliases.txt */ +DEF(Unknown, "Zzzz") +DEF(Adlam, "Adlm") +DEF(Ahom, "Ahom") +DEF(Anatolian_Hieroglyphs, "Hluw") +DEF(Arabic, "Arab") +DEF(Armenian, "Armn") +DEF(Avestan, "Avst") +DEF(Balinese, "Bali") +DEF(Bamum, "Bamu") +DEF(Bassa_Vah, "Bass") +DEF(Batak, "Batk") +DEF(Bengali, "Beng") +DEF(Bhaiksuki, "Bhks") +DEF(Bopomofo, "Bopo") +DEF(Brahmi, "Brah") +DEF(Braille, "Brai") +DEF(Buginese, "Bugi") +DEF(Buhid, "Buhd") +DEF(Canadian_Aboriginal, "Cans") +DEF(Carian, "Cari") +DEF(Caucasian_Albanian, "Aghb") +DEF(Chakma, "Cakm") +DEF(Cham, "Cham") +DEF(Cherokee, "Cher") +DEF(Chorasmian, "Chrs") +DEF(Common, "Zyyy") +DEF(Coptic, "Copt,Qaac") +DEF(Cuneiform, "Xsux") +DEF(Cypriot, "Cprt") +DEF(Cyrillic, "Cyrl") +DEF(Cypro_Minoan, "Cpmn") +DEF(Deseret, "Dsrt") +DEF(Devanagari, "Deva") +DEF(Dives_Akuru, "Diak") +DEF(Dogra, "Dogr") +DEF(Duployan, "Dupl") +DEF(Egyptian_Hieroglyphs, "Egyp") +DEF(Elbasan, "Elba") +DEF(Elymaic, "Elym") +DEF(Ethiopic, "Ethi") +DEF(Georgian, "Geor") +DEF(Glagolitic, "Glag") +DEF(Gothic, "Goth") +DEF(Grantha, "Gran") +DEF(Greek, "Grek") +DEF(Gujarati, "Gujr") +DEF(Gunjala_Gondi, "Gong") +DEF(Gurmukhi, "Guru") +DEF(Han, "Hani") +DEF(Hangul, "Hang") +DEF(Hanifi_Rohingya, "Rohg") +DEF(Hanunoo, "Hano") +DEF(Hatran, "Hatr") +DEF(Hebrew, "Hebr") +DEF(Hiragana, "Hira") +DEF(Imperial_Aramaic, "Armi") +DEF(Inherited, "Zinh,Qaai") +DEF(Inscriptional_Pahlavi, "Phli") +DEF(Inscriptional_Parthian, "Prti") +DEF(Javanese, "Java") +DEF(Kaithi, "Kthi") +DEF(Kannada, "Knda") +DEF(Katakana, "Kana") +DEF(Kawi, "Kawi") +DEF(Kayah_Li, "Kali") +DEF(Kharoshthi, "Khar") +DEF(Khmer, "Khmr") +DEF(Khojki, "Khoj") +DEF(Khitan_Small_Script, "Kits") +DEF(Khudawadi, "Sind") +DEF(Lao, "Laoo") +DEF(Latin, "Latn") +DEF(Lepcha, "Lepc") +DEF(Limbu, "Limb") +DEF(Linear_A, "Lina") +DEF(Linear_B, "Linb") +DEF(Lisu, "Lisu") +DEF(Lycian, "Lyci") +DEF(Lydian, "Lydi") +DEF(Makasar, "Maka") +DEF(Mahajani, "Mahj") +DEF(Malayalam, "Mlym") +DEF(Mandaic, "Mand") +DEF(Manichaean, "Mani") +DEF(Marchen, "Marc") +DEF(Masaram_Gondi, "Gonm") +DEF(Medefaidrin, "Medf") +DEF(Meetei_Mayek, "Mtei") +DEF(Mende_Kikakui, "Mend") +DEF(Meroitic_Cursive, "Merc") +DEF(Meroitic_Hieroglyphs, "Mero") +DEF(Miao, "Plrd") +DEF(Modi, "Modi") +DEF(Mongolian, "Mong") +DEF(Mro, "Mroo") +DEF(Multani, "Mult") +DEF(Myanmar, "Mymr") +DEF(Nabataean, "Nbat") +DEF(Nag_Mundari, "Nagm") +DEF(Nandinagari, "Nand") +DEF(New_Tai_Lue, "Talu") +DEF(Newa, "Newa") +DEF(Nko, "Nkoo") +DEF(Nushu, "Nshu") +DEF(Nyiakeng_Puachue_Hmong, "Hmnp") +DEF(Ogham, "Ogam") +DEF(Ol_Chiki, "Olck") +DEF(Old_Hungarian, "Hung") +DEF(Old_Italic, "Ital") +DEF(Old_North_Arabian, "Narb") +DEF(Old_Permic, "Perm") +DEF(Old_Persian, "Xpeo") +DEF(Old_Sogdian, "Sogo") +DEF(Old_South_Arabian, "Sarb") +DEF(Old_Turkic, "Orkh") +DEF(Old_Uyghur, "Ougr") +DEF(Oriya, "Orya") +DEF(Osage, "Osge") +DEF(Osmanya, "Osma") +DEF(Pahawh_Hmong, "Hmng") +DEF(Palmyrene, "Palm") +DEF(Pau_Cin_Hau, "Pauc") +DEF(Phags_Pa, "Phag") +DEF(Phoenician, "Phnx") +DEF(Psalter_Pahlavi, "Phlp") +DEF(Rejang, "Rjng") +DEF(Runic, "Runr") +DEF(Samaritan, "Samr") +DEF(Saurashtra, "Saur") +DEF(Sharada, "Shrd") +DEF(Shavian, "Shaw") +DEF(Siddham, "Sidd") +DEF(SignWriting, "Sgnw") +DEF(Sinhala, "Sinh") +DEF(Sogdian, "Sogd") +DEF(Sora_Sompeng, "Sora") +DEF(Soyombo, "Soyo") +DEF(Sundanese, "Sund") +DEF(Syloti_Nagri, "Sylo") +DEF(Syriac, "Syrc") +DEF(Tagalog, "Tglg") +DEF(Tagbanwa, "Tagb") +DEF(Tai_Le, "Tale") +DEF(Tai_Tham, "Lana") +DEF(Tai_Viet, "Tavt") +DEF(Takri, "Takr") +DEF(Tamil, "Taml") +DEF(Tangut, "Tang") +DEF(Telugu, "Telu") +DEF(Thaana, "Thaa") +DEF(Thai, "Thai") +DEF(Tibetan, "Tibt") +DEF(Tifinagh, "Tfng") +DEF(Tirhuta, "Tirh") +DEF(Tangsa, "Tnsa") +DEF(Toto, "Toto") +DEF(Ugaritic, "Ugar") +DEF(Vai, "Vaii") +DEF(Vithkuqi, "Vith") +DEF(Wancho, "Wcho") +DEF(Warang_Citi, "Wara") +DEF(Yezidi, "Yezi") +DEF(Yi, "Yiii") +DEF(Zanabazar_Square, "Zanb") +#endif + +#ifdef UNICODE_PROP_LIST +/* Prop list not exported to regexp */ +DEF(Hyphen, "") +DEF(Other_Math, "") +DEF(Other_Alphabetic, "") +DEF(Other_Lowercase, "") +DEF(Other_Uppercase, "") +DEF(Other_Grapheme_Extend, "") +DEF(Other_Default_Ignorable_Code_Point, "") +DEF(Other_ID_Start, "") +DEF(Other_ID_Continue, "") +DEF(Prepended_Concatenation_Mark, "") +/* additional computed properties for smaller tables */ +DEF(ID_Continue1, "") +DEF(XID_Start1, "") +DEF(XID_Continue1, "") +DEF(Changes_When_Titlecased1, "") +DEF(Changes_When_Casefolded1, "") +DEF(Changes_When_NFKC_Casefolded1, "") + +/* Prop list exported to JS */ +DEF(ASCII_Hex_Digit, "AHex") +DEF(Bidi_Control, "Bidi_C") +DEF(Dash, "") +DEF(Deprecated, "Dep") +DEF(Diacritic, "Dia") +DEF(Extender, "Ext") +DEF(Hex_Digit, "Hex") +DEF(IDS_Binary_Operator, "IDSB") +DEF(IDS_Trinary_Operator, "IDST") +DEF(Ideographic, "Ideo") +DEF(Join_Control, "Join_C") +DEF(Logical_Order_Exception, "LOE") +DEF(Noncharacter_Code_Point, "NChar") +DEF(Pattern_Syntax, "Pat_Syn") +DEF(Pattern_White_Space, "Pat_WS") +DEF(Quotation_Mark, "QMark") +DEF(Radical, "") +DEF(Regional_Indicator, "RI") +DEF(Sentence_Terminal, "STerm") +DEF(Soft_Dotted, "SD") +DEF(Terminal_Punctuation, "Term") +DEF(Unified_Ideograph, "UIdeo") +DEF(Variation_Selector, "VS") +DEF(White_Space, "space") +DEF(Bidi_Mirrored, "Bidi_M") +DEF(Emoji, "") +DEF(Emoji_Component, "EComp") +DEF(Emoji_Modifier, "EMod") +DEF(Emoji_Modifier_Base, "EBase") +DEF(Emoji_Presentation, "EPres") +DEF(Extended_Pictographic, "ExtPict") +DEF(Default_Ignorable_Code_Point, "DI") +DEF(ID_Start, "IDS") +DEF(Case_Ignorable, "CI") + +/* other binary properties */ +DEF(ASCII,"") +DEF(Alphabetic, "Alpha") +DEF(Any, "") +DEF(Assigned,"") +DEF(Cased, "") +DEF(Changes_When_Casefolded, "CWCF") +DEF(Changes_When_Casemapped, "CWCM") +DEF(Changes_When_Lowercased, "CWL") +DEF(Changes_When_NFKC_Casefolded, "CWKCF") +DEF(Changes_When_Titlecased, "CWT") +DEF(Changes_When_Uppercased, "CWU") +DEF(Grapheme_Base, "Gr_Base") +DEF(Grapheme_Extend, "Gr_Ext") +DEF(ID_Continue, "IDC") +DEF(Lowercase, "Lower") +DEF(Math, "") +DEF(Uppercase, "Upper") +DEF(XID_Continue, "XIDC") +DEF(XID_Start, "XIDS") + +/* internal tables with index */ +DEF(Cased1, "") + +#endif diff --git a/source/engine/thirdparty/sokol b/source/engine/thirdparty/sokol deleted file mode 160000 index 058a4c5..0000000 --- a/source/engine/thirdparty/sokol +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 058a4c5d2b1b35a2fa183625cc04604f2f5033ec diff --git a/source/engine/thirdparty/sokol/.editorconfig b/source/engine/thirdparty/sokol/.editorconfig new file mode 100644 index 0000000..2e85449 --- /dev/null +++ b/source/engine/thirdparty/sokol/.editorconfig @@ -0,0 +1,9 @@ +root=true +[**] +indent_style=space +indent_size=4 +trim_trailing_whitespace=true +insert_final_newline=true + +[*.yml] +indent_size=2 diff --git a/source/engine/thirdparty/sokol/.github/workflows/gen_bindings.yml b/source/engine/thirdparty/sokol/.github/workflows/gen_bindings.yml new file mode 100644 index 0000000..c1b52b7 --- /dev/null +++ b/source/engine/thirdparty/sokol/.github/workflows/gen_bindings.yml @@ -0,0 +1,331 @@ +name: Bindings + +on: [push, pull_request] + +jobs: + test-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: test_win + run: | + cd tests + test_win.cmd + shell: cmd + + test-mac: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: seanmiddleditch/gha-setup-ninja@master + - name: test_macos + run: | + cd tests + ./test_macos.sh + + test-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: seanmiddleditch/gha-setup-ninja@master + - name: prepare + run: | + sudo apt-get update + sudo apt-get install libgl1-mesa-dev libegl1-mesa-dev mesa-common-dev xorg-dev libasound-dev + - name: test_linux + run: | + cd tests + ./test_linux.sh + + gen-bindings: + needs: [ test-windows, test-mac, test-linux ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/checkout@v4 + with: + repository: floooh/sokol-zig + path: bindgen/sokol-zig + - uses: actions/checkout@v4 + with: + repository: floooh/sokol-nim + path: bindgen/sokol-nim + - uses: actions/checkout@v4 + with: + repository: floooh/sokol-odin + path: bindgen/sokol-odin + - uses: actions/checkout@v4 + with: + repository: floooh/sokol-rust + path: bindgen/sokol-rust + - name: generate + run: | + cd bindgen + python3 gen_all.py + - name: upload-zig-artifact + uses: actions/upload-artifact@v4 + with: + name: ignore-me-zig + retention-days: 1 + path: bindgen/sokol-zig/src/sokol + - name: upload-nim-artifact + uses: actions/upload-artifact@v4 + with: + name: ignore-me-nim + retention-days: 1 + path: bindgen/sokol-nim/src/sokol + - name: upload-odin-artifact + uses: actions/upload-artifact@v4 + with: + name: ignore-me-odin + retention-days: 1 + path: | + bindgen/sokol-odin/sokol + bindgen/sokol-odin/c + - name: upload-rust-artifact + uses: actions/upload-artifact@v4 + with: + name: ignore-me-rust + retention-days: 1 + path: bindgen/sokol-rust/src + + test-zig: + needs: gen-bindings + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v4 + with: + repository: floooh/sokol-zig + - uses: goto-bus-stop/setup-zig@v2 + - uses: actions/download-artifact@v4 + with: + name: ignore-me-zig + path: src/sokol + - name: prepare + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install libgl1-mesa-dev libegl1-mesa-dev mesa-common-dev xorg-dev libasound-dev + - name: build + run: zig build + + test-nim: + needs: gen-bindings + strategy: + fail-fast: false + matrix: + #os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest ] + runs-on: ${{matrix.os}} + steps: + - uses: jiro4989/setup-nim-action@v1 + with: + nim-version: '2.x' + repo-token: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v4 + with: + repository: floooh/sokol-nim + - uses: actions/download-artifact@v4 + with: + name: ignore-me-nim + path: src/sokol + - if: runner.os == 'Linux' + name: prepare + run: | + sudo apt-get update + sudo apt-get install libgl1-mesa-dev libegl1-mesa-dev mesa-common-dev xorg-dev libasound-dev + - name: build + run: | + nimble install -Y + nimble install glm -Y + nimble build_all + + test-odin: + needs: gen-bindings + strategy: + fail-fast: false + matrix: + # FIXME: macOS Odin vs Homebrew LLVM currently seems broken + # os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, windows-latest] + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v4 + with: + repository: floooh/sokol-odin + - uses: actions/download-artifact@v4 + with: + name: ignore-me-odin + # NOTE: see https://github.com/floooh/sokol-odin/blob/main/.github/workflows/main.yml + - uses: ilammy/msvc-dev-cmd@v1 + - if: runner.os == 'Linux' + name: prepare-linux + run: | + sudo apt-get update + sudo apt-get install libglu1-mesa-dev mesa-common-dev xorg-dev libasound-dev llvm-14 + curl -L https://github.com/odin-lang/Odin/releases/download/dev-2023-08/odin-ubuntu-amd64-dev-2023-08.zip --output odin.zip + unzip odin.zip + chmod a+x ./odin + ./build_clibs_linux.sh + - if: runner.os == 'macOS' + name: prepare-macos + run: | + brew install llvm@14 + curl -L https://github.com/odin-lang/Odin/releases/download/dev-2023-08/odin-macos-amd64-dev-2023-08.zip --output odin.zip + unzip odin.zip + chmod a+x ./odin + ./build_clibs_macos.sh + - if: runner.os == 'Windows' + name: prepare-windows + shell: cmd + run: | + curl -L https://github.com/odin-lang/Odin/releases/download/dev-2023-08/odin-windows-amd64-dev-2023-08.zip --output odin.zip + unzip odin.zip + build_clibs_windows.cmd + - name: build + run: | + ./odin build examples/clear -debug + ./odin build examples/triangle -debug + ./odin build examples/quad -debug + ./odin build examples/bufferoffsets -debug + ./odin build examples/cube -debug + ./odin build examples/noninterleaved -debug + ./odin build examples/texcube -debug + ./odin build examples/shapes -debug + ./odin build examples/offscreen -debug + ./odin build examples/instancing -debug + ./odin build examples/mrt -debug + ./odin build examples/blend -debug + ./odin build examples/debugtext -debug + ./odin build examples/debugtext-print -debug + ./odin build examples/debugtext-userfont -debug + ./odin build examples/saudio -debug + ./odin build examples/sgl -debug + ./odin build examples/sgl-points -debug + ./odin build examples/sgl-context -debug + + test-rust: + needs: gen-bindings + env: + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + strategy: + fail-fast: false + matrix: + # os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, windows-latest] + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v4 + with: + repository: floooh/sokol-rust + - uses: actions/download-artifact@v4 + with: + name: ignore-me-rust + path: src + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + - name: prepare-linux + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install libglu1-mesa-dev mesa-common-dev xorg-dev libasound-dev + - name: build + run: | + cargo --version + cargo build --examples --verbose + + # only deploy the bindings for commits on the main branch + deploy-zig: + needs: test-zig + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + repository: floooh/sokol-zig + ssh-key: ${{ secrets.GHACTIONS_ZIG_PUSH }} + - uses: actions/download-artifact@v4 + with: + name: ignore-me-zig + path: src/sokol + - name: "commit and push" + run: | + git config user.email "none" + git config user.name "GH Action" + git add -A + git diff-index --quiet HEAD || git commit -m "updated (https://github.com/floooh/sokol/commit/${{ github.sha }})" + git push + + deploy-nim: + needs: test-nim + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + repository: floooh/sokol-nim + ssh-key: ${{ secrets.GHACTIONS_NIM_PUSH }} + - uses: actions/download-artifact@v4 + with: + name: ignore-me-nim + path: src/sokol + - name: "commit and push" + run: | + git config user.email "none" + git config user.name "GH Action" + git add -A + git diff-index --quiet HEAD || git commit -m "updated (https://github.com/floooh/sokol/commit/${{ github.sha }})" + git push + + deploy-odin: + needs: test-odin + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + repository: floooh/sokol-odin + ssh-key: ${{ secrets.GHACTIONS_ODIN_PUSH }} + - uses: actions/download-artifact@v4 + with: + name: ignore-me-odin + - name: "commit and push" + run: | + git config user.email "none" + git config user.name "GH Action" + git add -A + git diff-index --quiet HEAD || git commit -m "updated (https://github.com/floooh/sokol/commit/${{ github.sha }})" + git push + + deploy-rust: + needs: test-rust + env: + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + repository: floooh/sokol-rust + ssh-key: ${{ secrets.GHACTIONS_RUST_PUSH }} + - uses: actions/download-artifact@v4 + with: + name: ignore-me-rust + path: src + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + - name: "cargo fmt" + run: cargo fmt + - name: "commit and push" + run: | + git config user.email "none" + git config user.name "GH Action" + git status -vv + git add -A + git diff-index --quiet HEAD || git commit -m "updated (https://github.com/floooh/sokol/commit/${{ github.sha }})" + git push diff --git a/source/engine/thirdparty/sokol/.github/workflows/main.yml b/source/engine/thirdparty/sokol/.github/workflows/main.yml new file mode 100644 index 0000000..4d7ddf3 --- /dev/null +++ b/source/engine/thirdparty/sokol/.github/workflows/main.yml @@ -0,0 +1,65 @@ +name: "Build & Test" + +on: [push, pull_request] + +jobs: + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: test_win + run: | + cd tests + test_win.cmd + shell: cmd + mac: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: seanmiddleditch/gha-setup-ninja@master + - name: test_macos + run: | + cd tests + ./test_macos.sh + ios: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: test_ios + run: | + cd tests + ./test_ios.sh + linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: seanmiddleditch/gha-setup-ninja@master + - name: prepare + run: | + sudo apt-get update + sudo apt-get install libgl1-mesa-dev libegl1-mesa-dev mesa-common-dev xorg-dev libasound-dev + - name: test_linux + run: | + cd tests + ./test_linux.sh + emscripten: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: seanmiddleditch/gha-setup-ninja@master + - name: test_emscripten + run: | + cd tests + ./test_emscripten.sh + android: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: seanmiddleditch/gha-setup-ninja@master + - uses: actions/setup-java@v1 + with: + java-version: '8' + - name: test_android + run: | + cd tests + ./test_android.sh diff --git a/source/engine/thirdparty/sokol/.gitignore b/source/engine/thirdparty/sokol/.gitignore new file mode 100644 index 0000000..58627d6 --- /dev/null +++ b/source/engine/thirdparty/sokol/.gitignore @@ -0,0 +1,7 @@ +.vscode/ +build/ +#>fips +# this area is managed by fips, do not edit +.fips-* +*.pyc +# NOTE: if you use sokol_gfx.h and sokol_app.h together, make sure to update both. This is +because the pixel format enum in sokol_gfx.h has been shuffled around a bit, and as a result, some internal +pixel format constants in sokol_app.h had to move too! + +- sokol_gfx.h: some minor new features (non-breaking): + - the struct `sg_pixel_format` has two new items: + - `bool compressed`: true if this is a hardware-compressed pixel format + - `int bytes_per_pixel`: as the name says, with the caveat that this is + zero for compressed pixel formats (because the smallest element in compressed formats is a block, not a pixel) + - two previously private helper functions have been exposed to help with size computations + for texture data, these may be useful when preparing image data for consumption by `sg_make_image()` + and `sg_update_image()`: + - `int sg_query_row_pitch(sg_pixel_format fmt, int width, int row_align_bytes)`: + Computes the number of bytes in a texture row for a given pixel format. A 'row' has + different meanings for uncompressed vs compressed formats: For uncompressed pixel + formats, a row is a single line of pixels, while for compressed formats, a row is + a line of 'compression blocks'. `width` is always in pixels. + - `int sg_query_surface_pitch(sg_pixel_format fmt, int width, int height, int row_align_bytes)`: + Computes number of bytes in a texture surface (e.g. a single mipmap) for a given + pixel format. `width` and `height` are always in pixels. + + The `row_align_bytes` parameter is for added flexibility. For image data that goes into + the `sg_make_image()` or `sg_update_image()` functions 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. +- Related issue: https://github.com/floooh/sokol/issues/946, and PR: https://github.com/floooh/sokol/pull/962 + +#### 03-Jan-2024 + +- sokol_nuklear.h: `snk_handle_event()` now returns a bool to indicate whether the + event was handled by Nuklear (this allows an application to skip its own event + handling if Nuklear already handled the event). Issue link: https://github.com/floooh/sokol/issues/958, + fixed in PR: https://github.com/floooh/sokol/pull/959. Many thanks to @adamrt for the PR! + +#### 02-Jan-2024 + +Happy New Year! A couple of input-related changes in the sokol_app.h Emscripten backend: + +- Mouse and touch events now bubble up to the HTML document instead of being consumed, in some scenarios this + allows better integration with the surrounding web page. To prevent event bubbling, + call `sapp_consume_event()` from within the sokol_app.h event callback function. +- **NOTE**: wheel/scroll events behave as before and are always consumed. This prevents + an ugly "scroll bumping" effect when a wheel event bubbles up on a page where + scrolling shouldn't be possible. +- The hidden HTML text input field hack for text input on mobile browsers has been + removed. This idea never really worked across all browsers, and it actually + interfered with Dear ImGui text input fields because the hidden HTML text field + generated focus-in/out events which confused the Dear ImGui input handling code. + +Those changes fix a couple of problem when trying to integrate sokol_app.h applications +into VSCode webview panels, see: https://marketplace.visualstudio.com/items?itemName=floooh.vscode-kcide + +Related PR: https://github.com/floooh/sokol/pull/939 + +#### 10-Nov-2023 + +A small change in the sokol_gfx.h GL backend on Windows only: + +PR https://github.com/floooh/sokol/pull/839 has been merged, in debug mode this creates +the GL context with WGL_CONTEXT_DEBUG_BIT_ARB. Thanks to @castano for the PR! + +#### 06-Nov-2023 + +A bugfix in the sokol_gfx.h D3D11 backend, and some related cleanup when creating depth-stencil +render target images and resource views: + +- fixed: render target images with format SG_PIXELFORMAT_DEPTH_STENCIL triggered a validation + error because the pixel format capabilities code marked them as non-renderable. Now + the SG_PIXELFORMAT_DEPTH_STENCIL pixel format is properly reported as renderable. +- the DXGIFormats for SG_PIXELFORMAT_DEPTH_STENCIL images are now as follows: + - D3D11 texture object: DXGI_FORMAT_R24G8_TYPELESS + - D3D11 shader-resource-view object: DXGI_FORMAT_R24_UNORM_X8_TYPELESS + - D3D11 depth-stencil-view object: DXGI_FORMAT_D24_UNORM_S8_UINT + +Related PR: https://github.com/floooh/sokol/pull/937 + +#### 30-Oct-2023 + +Some sokol_gfx.h backend-specific updates and tweaks (very minor chance that this is breaking if you are injecting textures into the D3D11 backend). + +- a new set of public API functions to access the native backend 3D-API resource objects of + sokol-gfx resource objects: + + ``` + sg_[api]_[type]_info sg_[api]_query_[type]_info(sg_[type]) + ``` + ...where `[api]` is any of `[gl, d3d11, mtl, wgpu]` and `[type]` is any of `[buffer, image, sampler, shader, pipeline, pass]`. + + This is mainly useful when mixing native 3D-API code with sokol-gfx code. + + See issue https://github.com/floooh/sokol/issues/931 for details. + +- WebGPU backend: `sg_make_image()` will no longer automatically create a WebGPU texture-view object when injecting a WebGPU texture object, instead +this must now be explicitly provided. + +- D3D11 backend: `sg_make_image()` will no longer automatically create a +shader-resource-view object when injecting a D3D11 texture object, and +vice versa, a texture object will no longer be looked up from an injected +shader-resource-view object (e.g. the injection rules are now more straightforward and explicit). See issue https://github.com/floooh/sokol/issues/930 for details. + +For the detailed changes, see PR https://github.com/floooh/sokol/pull/932. + +#### 27-Oct-2023 + +Fix broken render-to-mipmap in the sokol_gfx.h GL backend. + +There was a subtle bug / "feature gap" lurking in sokol_gfx.h GL backend: trying +to render to any mipmap except the top-level mipmap resulted in a black screen +because of an incomplete-framebuffer error. This is fixed now. The changes in detail: + +- creating a texture in the GL backend now sets the GL_TEXTURE_MAX_LEVEL property + (this is the fix to make everything work) +- the framebuffer completeness check in the GL backend now has more detailed error logging +- in the validation layer, the requirement that a sampler that's used with a + single-mipmap-texture must use `.mipmap_filter = SG_FILTER_NONE` has been + relaxed (a later update will remove SG_FILTER_NONE entirely since it's not needed anymore + and the concept of a "none" mipmap filter only exists in GL and Metal, but not D3D, WebGPU + and Vulkan) + +Ticket: https://github.com/floooh/sokol/issues/923 + +PR: https://github.com/floooh/sokol/pull/924 + +There's also a new render-to-mipmap sample which covers to close this 'feature gap': + +https://floooh.github.io/sokol-html5/miprender-sapp.html + +A couple of similar samples will follow over the next few days +(rendering to texture array layers and 3d texture slices). + +#### 26-Oct-2023 + +- sokol_app.h gl: fix a regression introduced in https://github.com/floooh/sokol/pull/916 + which could select the wrong framebuffer pixel format and break rendering + on some GL drivers (in my case: an older Intel GPU). + + If you are using the GL backend on Windows, please make sure to upgrade! + +#### 23-Oct-2023 + +- sokol_app.h gl: some further startup optimizations in the WGL code path + via PR https://github.com/floooh/sokol/pull/916 + +#### 21-Oct-2023 + +The major topic of this update is the 'finalized' WebGPU support in sokol_gfx.h and sokol_app.h. + +- WebGPU samples are hosted here: + + https://floooh.github.io/sokol-webgpu/ + +- WebGL2 samples remain hosted here: + + https://floooh.github.io/sokol-html5/ + +- Please read the following blog post as introduction: + + https://floooh.github.io/2023/10/16/sokol-webgpu.html + +- ...and the changelog and updated documentation in the sokol-shdc repository: + + https://github.com/floooh/sokol-tools + +- You'll also need to update the sokol-shdc binaries: + + https://github.com/floooh/sokol-tools-bin + +- Please also read the following new or updated sections in the embedded sokol_gfx.h header documentation: + + - `ON SHADER CREATION` + - `ON SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT AND SG_SAMPLERTYPE_NONFILTERING` + - `WEBGPU CAVEATS` + + Please do this especially when using any of the following texture pixel formats, as you will most likely encounter new validation layer errors: + + - `SG_PIXELFORMAT_R32F` + - `SG_PIXELFORMAT_RG32F` + - `SG_PIXELFORMAT_RGBA32F` + +- There is a tiny breaking change in the sokol_gfx.h API (only requires action when not using sokol-shdc): + + - the following `sg_sampler_type` enum items have been renamed to better match their WebGPU counterparts: + - SG_SAMPLERTYPE_SAMPLE => SG_SAMPLERTYPE_FILTERING + - SG_SAMPLERTYPE_COMPARE => SG_SAMPLERTYPE_COMPARISON + + - the enum `sg_image_sample_type` gained a new item: + - SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT + + - the enum `sg_sampler_type` gained a new item: + - SG_SAMPLERTYPE_NONFILTERING + +- The sokol_gfx.h struct `sg_desc` has two new items: + - `.wgpu_bindgroups_cache_size` - must be power-of-2, default: 1024 + - `.wgpu_disable_bindgroups_cache` - default: false + +- sokol_gfx.h gained the following new public API functions to query per-frame information: + - `sg_frame_stats sg_query_frame_stats()` + - `void sg_enable_frame_stats(void)` + - `void sg_disable_frame_stats(void)` + - `bool sg_frame_stats_enabled(void)` + + Frame statistics gathering is enabled after startup, but can be temporarily + disabled and enabled again via `sg_disable_frame_stats()` and `sg_enable_frame_stats`. + +- The sokol_gfx.h validation layer has new validation checks in `sg_make_shader()` + regarding image/sampler pair compatibility (WebGPU is particularly strict about + this stuff). + +- In sokol_app.h, the old wip WebGPU device and swapchain setup code is now implemented + in pure C code (previously this was a mix of Javascript and C). + +- Also note that sokol_app.h currently only supports WebGPU in the Emscripten backend. + If you want to use sokol_gfx.h with the WebGPU backend in a native scenario, you'll have + to use a different window system glue library (like GLFW). The sokol-samples directory + has a handful of examples for using sokol_gfx.h + Dawn + GLFW. + +- The following headers have been made compatible with the sokol_gfx.h WebGPU backend + (mainly by embedding WGSL shader code): + - sokol_debugtext.h + - sokol_fontstash.h + - sokol_gl.h + - sokol_spine.h + - sokol_imgui.h (also required some more changes for embedding `unfilterable-float` + textures, since these now require separate shader and pipeline objects) + - sokol_nuklear.h (works in WebGPU, but doesn't contain the work from sokol_imgui.h + to support `unfilterable-float` user textures) + +- sokol_gfx_imgui.h gained a new function `sg_imgui_draw_menu()` which renders a + menu panel to show/hide all debug windows. Previously this had to be done + outside the header. + +- sokol_gfx_imgui.h gained a new 'frame stats' window, which allows to peak into + sokol_gfx.h frame-rendering internals. This basically visualizes the struct + `sg_frame_stats` returned by the new sokol_gfx.h function `sg_query_frame_stats()`. + +- The sokol-samples repository gained 3 new samples: + - cubemap-jpeg-sapp.c (load a cubemap from separate JPEG files) + - cubemaprt-sapp.c (render into cubemap faces - this demo actually existed a while but wasn't "official" so far) + - drawcallperf-sapp.c (a sample to explore the performance overhead of sg_apply_bindings, sg_apply_uniforms and sg_draw) + +#### 03-Oct-2023 + +- sokol_app.h win/gl: PR https://github.com/floooh/sokol/pull/886 has been merged, this makes + GL context initialization on Windows slightly more efficient. Many thanks to @dtrebilco! + +#### 25-Sep-2023 + +- The allocator callback functions in all headers that support custom allocators have been renamed + from `alloc` and `free` to `alloc_fn` and `free_fn`, this is because the symbol `free` is quite + likely to collide with a preprocessor macro of the same name if the standard C allocator is + replaced with a custom allocator. + + This is a breaking change only if you've been providing your own allocator functions to + the sokol headers. + + See issue https://github.com/floooh/sokol/issues/903 and PR https://github.com/floooh/sokol/pull/908 + for details. + +#### 23-Sep-2023 + +- sokol_gfx.h gl: Allow to inject an external GL framebuffer id into the sokol-gfx default + pass. See PR https://github.com/floooh/sokol/pull/899 and issue https://github.com/floooh/sokol/issues/892 + for details. Many thanks to @danielchasehooper for the discussion and PR! + + Further down the road I want to make the whole topic more flexible while at the same time + simplifying the sokol-gfx API, see here: https://github.com/floooh/sokol/issues/904 + +#### 22-Sep-2023 + +- sokol_gfx.h: Fixed a Metal validation error on Intel Macs when creating textures (Intel Macs + have unified memory, but don't support textures in shared storage mode). This was a regression + in the image/sampler split update in mid-July 2023. Fixes issue https://github.com/floooh/sokol/issues/905 + via PR https://github.com/floooh/sokol/pull/907. + +#### 19-Sep-2023 + +- sokol_fetch.h: fixed a minor issue where a request that was cancelled before it was dispatched + had an incomplete response state set in the response callback (the `finished`, `failed` and + `error_code` fields were not set). This fixes issue https://github.com/floooh/sokol/issues/882 + via PR https://github.com/floooh/sokol/pull/898 + +#### 18-Sep-2023 + +- PR https://github.com/floooh/sokol/pull/893 has been merged, this fixes a minor issue + in the GL backend when using an injected texture as framebuffer attachment. +- Issue https://github.com/floooh/sokol/issues/884 has been fixed via PR https://github.com/floooh/sokol/pull/894, + this adds missing error code paths in the Metal backend when Metal object creation fails. +- Clarified `sapp_run()` behaviour in the sokol_app.h documentation header (search for `OPTIONAL: DON'T HIJACK main()`) +- sokol_args.h now fully supports "key-only args", see issue https://github.com/floooh/sokol/issues/876 for details, + fixed via PR https://github.com/floooh/sokol/pull/896 + +#### 17-Sep-2023 + +- The sokol-gfx Metal backend now adds debug labels to Metal resource objects and + also passes through the `sg_push/pop_debug_group()` calls. If you use the push/pop + debug group calls, please be aware of the following limitations: + + - a push inside a render pass must have an associated pop inside the same render pass + - a push outside any render pass must have an associated pop outside any render pass + - Metal will ignore any push/pop calls outside render passes (this is because in Metal + these are MTLCommandEncoder methods) + + Associated issue: https://github.com/floooh/sokol/issues/889, and PR: https://github.com/floooh/sokol/pull/890. + +#### 09-Sep-2023 + +- a small PR has been merged which fixes a redundant glBindFramebuffer() in the GLES3 backend + in `sg_end_pass()` (see: https://github.com/floooh/sokol/pull/878), many thanks to @danielchasehooper + for catching that issue! +- sokol_imgui.h has been fixed for cimgui 1.89.9 (see https://github.com/floooh/sokol/issues/879) + +#### 28-Aug-2023 + +**sokol_gfx.h metal**: A new attempt at fixing a rare Metal validation layer +error about MTKView swapchain resource lifetimes. See PR https://github.com/floooh/sokol/pull/873 +for details. + +#### 26-Jul-2023 + +**sokol_nuklear.h**: The same image+sampler support has been added as in sokol_imgui.h +three days ago: + +- a new object type `snk_image_t` which wraps a sokol-gfx image and sampler + under a common handle +- new functions: + - snk_make_image() + - snk_destroy_image() + - snk_query_image_desc() + - snk_image_from_nkhandle() +- the function snk_nkhandle() now takes an snk_image_t handle instead of an sg_image handle +- the nuklear.h header needs to be included before the declaration (not just the implementation), + this was already required before, but now you get a proper error message if the include is missing +- the 'standard' logging- and error-reporting callback has been added as in the other sokol headers + (don't forget to add a logging callback in snk_setup(), otherwise sokol-nuklear will be silent) +- since sokol-nuklear now needs to allocate memory, an allocator can now be provided to the + snk_setup() call (otherwise malloc/free will be used) + +Please also read the new documentation section `ON USER-PROVIDED IMAGES AND SAMPLERS` +in sokol_nuklear.h, and also check out the (rewritten) sample: + +https://floooh.github.io/sokol-html5/nuklear-images-sapp.html + +Associated PR: https://github.com/floooh/sokol/pull/862 + +#### 23-Jul-2023 + +**sokol_imgui.h**: Add proper support for injecting user-provided sokol-gfx +images and samplers into Dear ImGui UIs. With the introduction of separate +sampler objects in sokol_gfx.h there's a temporary feature regression in +sokol_imgui.h and sokol_nuklear.h in that user provided images had to use a +shared sampler that's hardwired into the respective headers. This update fixes +this problem for sokol_imgui.h, with a similar fix for sokol_nuklear.h coming +up next. + +The sokol_imgui.h changes in detail are: + +- a new object type `simgui_image_t` which wraps a sokol-gfx image and sampler + object under a common handle +- two new function `simgui_make_image()` and `simgui_destroy_image()` to + create and destroy such a new `simgui_image_t` object. +- the existing function `simgui_imtextureid()` has been changed to take + an `simgui_image_t` +- sokol_imgui.h now also uses the same error-handling and logging callback + as the other sokol headers (this was needed because creating an `simgui_image_t` + object may fail because the object pool is exhausted) - don't forget + to provide a logging callback (for instance via sokol_log.h), otherwise + sokol_imgui.h will be entirely silent in case of errors. + +Please also read the new documentation section `ON USER-PROVIDED IMAGES AND SAMPLERS` +in sokol_imgui.h, and also check out the new sample: + +https://floooh.github.io/sokol-html5/imgui-images-sapp.html + +Associated PR: https://github.com/floooh/sokol/pull/861 + +#### 16-Jul-2023 + +**BREAKING CHANGES** + +The main topic of this update is to separate sampler state from image state in +sokol_gfx.h which became possible after GLES2 support had been removed from +sokol_gfx.h. + +This also causes some 'collateral changes' in shader authoring and +other sokol headers, but there was opportunity to fill a few feature gaps +in sokol_gfx.h as well: + +- it's now possible to sample depth textures in shaders both with regular + samplers, and with 'comparison samplers' (which is mainly useful for shadow mapping) +- it's now possible to create render passes without color attachments for + 'depth-only' rendering + +See the new [shadows-depthtex-sapp](https://floooh.github.io/sokol-html5/shadows-depthtex-sapp.html) sample which demonstrates both features. + +> NOTE: all related projects have a git tag `pre-separate-samplers` in case you are not ready yet to make the switch + +> NOTE 2: if you use sokol-gfx with the sokol-shdc shader compiler, you'll also need +> to update the sokol-shdc binaries from https://github.com/floooh/sokol-tools-bin + +##### **sokol_gfx.h** + +- texture sampler state has been removed from `sg_image_desc`, instead you now + need to create separate sampler objects: + + ```c + sg_sampler smp = sg_make_sampler(&(sg_sampler_desc){ + .min_filter = SG_FILTER_LINEAR, + .mag_filter = SG_FILTER_LINEAR, + .wrap_u = SG_WRAP_CLAMP_TO_EDGE, + .wrap_v = SG_WRAP_CLAMP_TO_EDGE + }); + ``` + +- texture filtering is now described by 3 separate filters: + - min_filter = SG_FILTER_NEAREST | SG_FILTER_LINEAR + - mag_filter = SG_FILTER_NEAREST | SG_FILTER_LINEAR + - mipmap_filter = SG_FILTER_NONE | SG_FILTER_NEAREST | SG_FILTER_LINEAR + + ...this basically switches from the esoteric GL convention to a convention + that's used by all other 3D APIs. There's still a limitation that's caused by + GL though: a sampler which is going to be used with an image that has a + `mipmap_count = 1` requires that `.mipmap_filter = SG_FILTER_NONE`. + +- another new sampler state in `sg_sampler_desc` is `sg_compare_func compare;`, + this allows to create 'comparison samplers' for shadow mapping + +- when calling `sg_apply_bindings()` the struct `sg_bindings` now has changed + to also include sampler objects, note that there is no 1:1 relationship + between images and samplers required: + + ```c + sg_apply_bindings(&(sg_bindings){ + .vertex_buffers[0] = vbuf, + .fs = { + .images = { + [SLOT_tex0] = img0, + [SLOT_tex1] = img1, + [SLOT_tex2] = img2, + }, + .samplers[SLOT_smp] = smp, + } + }); + ``` + +- if you use sokol-shdc, you need to rewrite your shaders from 'OpenGL GLSL style' (with + combined image samplers) to 'Vulkan GLSL style' (with separate textures and samplers): + + E.g. the old GL-style shader with combined image samplers: + + ```glsl + uniform sampler2D tex; + + void main() { + frag_color = texture(tex, uv); + } + ``` + ...now needs to look like this: + + ```glsl + uniform texture2D tex; + uniform sampler smp; + + void main() { + frag_color = texture(sampler2D(tex, smp), uv); + } + ``` + + sokol-shdc will now throw an error if it encounters an 'old' shader using combined + image-samplers, this helps you to catch all places where a rewrite to separate + texture and sampler objects is required. + +- If you *don't* use sokol-shdc and instead provide your own backend-specific + shaders, you need to provide more shader interface reflection info about the texture + and sampler usage in a shader when calling `sg_make_shader`. + Please see the new documentation block `ON SHADER CREATION` in sokol_gfx.h for more details! + + Also refer to the updated 3D-backend-specific samples here: + + - for GL: https://github.com/floooh/sokol-samples/tree/master/glfw + - for GLES3: https://github.com/floooh/sokol-samples/tree/master/html5 + - for D3D11: https://github.com/floooh/sokol-samples/tree/master/d3d11 + - for Metal: https://github.com/floooh/sokol-samples/tree/master/metal + +- it's now possible to create `sg_pass` objects without color attachments to + enable depth-only rendering, see the new sample [shadows-depthtex-sapp](https://floooh.github.io/sokol-html5/shadows-depthtex-sapp.html) for details, + specifically be aware of the caveat that a depth-only-compatible `sg_pipeline` object + needs to 'deactivate' the first color target by setting its pixel format + to `NONE`: + + ```c + sg_pipeline pip = sg_make_pipeline(&(sg_pipeline_desc){ + ... + .colors[0].pixel_format = SG_PIXELFORMAT_NONE, + ... + }); + ``` + +- the following struct names have been changed to be more in line with related + struct names, this also makes those names similar to WebGPU types: + + - `sg_buffer_layout_desc` => `sg_vertex_buffer_layout_state` + - `sg_vertex_attr_desc` => `sg_vertex_attr_state` + - `sg_layout_desc` => `sg_vertex_layout_state` + - `sg_color_state` => `sg_color_target_state` + +- bugfixes and under-the-hood changes + - `sg_begin_pass()` used the wrong framebuffer size when rendering to a mip-level != 0 + - the Metal backend code started to use the `if (@available(...))` statement + to check for runtime-availability of macOS/iOS API features + - **NOTE:** this change (`if (@available(...))`) caused linking problems in + the Zig and Rust bindings on GH Actions (missing symbol + `___isPlatformVersionAtLeast`) which I could not reproduce locally on my + M1 Mac. On Zig this could be fixed by moving to the latest zig-0.11.0-dev + version, but for Rust this still needs to be fixed). + - on macOS the Metal backend now creates resources in Shared resource storage mode if + supported by the device + - on iOS the Metal backend now supports clamp-to-border-color if possible (depends on + iOS version and GPU family) + +##### **sokol_gl.h** + +- The function `sgl_texture(sg_image img)` has been changed to accept a sampler + object to `sgl_texture(sg_image img, sg_sampler smp)`. Passing an invalid image handle + will use the builtin default (white) texture, and passing an invalid sampler + handle will use the builtin default sampler. + +##### **sokol_shape.h** + +- Some sokol-shape functions have been renamed to match renamed structs in sokol-gfx: + + - `sshape_buffer_layout_desc()` => `sshape_vertex_buffer_layout_state()` + - `sshape_position_attr_desc()` => `sshape_position_vertex_attr_state()` + - `sshape_normal_attr_desc()` => `sshape_normal_vertex_attr_state()` + - `sshape_texcoord_attr_desc()` => `sshape_texcoord_vertex_attr_state()` + - `sshape_color_attr_desc()` => `sshape_color_vertex_attr_state()` + +##### **sokol_spine.h** + +- A sokol-spine atlas object now allocates both an `sg_image` and `sg_sampler` handle + and expects the user code to initialize those handles to complete image and + sampler objects. Check the updated sokol-spine samples here for more details: + + https://github.com/floooh/sokol-samples/tree/master/sapp + +##### **sokol_imgui.h** + +- sokol_imgui.h has a new public function to create an ImTextureID handle from + an `sg_image` handle which can be used like this: + + ```c + ImTextureID tex_id = simgui_imtextureid(img); + ``` + + Note that sokol-imgui currently doesn't currently allow to pass user-provided `sg_sampler` + object with the user-provided image. + +##### **sokol_nuklear.h** + +- similar to sokol_imgui.h, there's a new public function `snk_nkhandle()` + which creates a Nuklear handle from a sokol-gfx image handle which can be + used like this to create a Nuklear image handle: + + ```c + nk_image nki = nk_image_handle(snk_nkhandle(img)); + ``` + + As with sokol_imgui.h, it's currently not possible to pass a user-provided `sg_sampler` + object with the image. + + +#### 20-May-2023 + +Some minor event-related cleanup in sokol_app.h and a touchscreen fix in sokol_imgui.h + +- in the event `SAPP_EVENTTYPE_FILESDROPPED`: + - the `sapp_event.modifier` field now contains the active modifier keys + at the time of the file drop operations on the platforms macOS, Emscripten + and Win32 (on Linux I haven't figured out how this might work with the + Xlib API) + - on macOS, the `sapp_event.mouse_x/y` fields now contain the window-relative + mouse position where the drop happened (this already worked as expected on + the other desktop platforms) + - on macOS and Linux, the `sapp_event.mouse_dx/dy` fields are now set to zero + (this already was the case on Emscripten and Win32) +- in the events `SAPP_EVENTTYPE_MOUSE_ENTER` and `SAPP_EVENTTYPE_MOUSE_LEAVE`: + - the `sapp_event.mouse_dx/dy` fields are now set to zero, previously this + could be a very big value on some desktop platforms + +Many thanks to @castano for the initial PR (https://github.com/floooh/sokol/pull/830)! + +- In sokol_imgui.h, the new io.AddMouseSourceEvent() function in Dear ImGui 1.89.5 + is called to differentiate between mouse- and touch-events, this makes ui tabs + work with a single tap (previously a double-tap on the tab was needed). The code + won't break if the ImGui version is older (in this case the function simply isn't called) + + +#### 19-May-2023 + +**BREAKING CHANGES**_ in sokol_gfx.h: Render passes are now more 'harmonized' +with Metal and WebGPU by exposing a 'store action', and making MSAA resolve attachments +explicit. The changes in detail: + + - A new documentation section `ON RENDER PASSES` has been added to sokol_gfx.h, this + gives a much more detailed overview of the new render pass behaviour than this + changelog, please make sure to give it a read - especially when you are using + MSAA offscreen render passes in your code. + - `sg_action` has been renamed to `sg_load_action`. + - A new enum `sg_store_action` has been added. + - In `sg_pass_action`: + - `.action` has been renamed to `.load_action`. + - `.value` has been renamed to `.clear_value`. + - A new field `.store_action` has been added. + - An `sg_image` object with a sample count > 1 no longer contains a second implicit + texture for the msaa-resolve operation. + - When creating a pass object, there's now an array of `sg_image` objects + called `resolve_attachments[]`. When a resolve attachment image is set, the + color attachment at the same slot index must be an image with a sample count > + 1, and an 'msaa-resolve' operation from the color attachment into the + resolve attachment will take place in `sg_end_pass()`. + - Pass attachments are now more flexible (there were a couple of gaps where specific + image types were not allowed as pass attachments, especially for the depth-stencil- + attachment - but this hadn't actually been checked by the validation layer). + - Some gaps in the validation layer around images and passes have been tightened up, + those usually don't work in one backend or another, but have been ignored so far + in the validation layer, mainly: + - MSAA images must have num_mipmaps = 1. + - 3D images cannot have a sample_count > 1. + - 3D images cannot have depth or depth-stencil image formats. + - It's not allowed to bind MSAA images as texture. + - It's not allowed to bind depth or depth-stencil images as texture. + - (I'll see if I can relax some of those restrictions after the WebGPU backend release) + - **A lot** of new tests have been added to cover validation layer checks when creating + image and pass objects. + + Next up: WebGPU! + +#### 30-Apr-2023 + +GLES2/WebGL1 support has been removed from the sokol headers (now that + all browsers support WebGL2, and WebGPU is around the corner I feel like it's finally + time to ditch GLES2. + + This is a breaking API change in sokol_gfx.h and sokol_app.h. + + Common changes across all headers: + - (breaking change) the `SOKOL_GLES2` config define is no longer accepted and will cause a compile error + (use `SOKOL_GLES3` instead) + - (breaking change) on Emscripten use the linker option `-s USE_WEBGL2=1` + - any embedded GLES shaders have been updated from glsl100 to glsl300es (but glsl100 shaders + still work fine with the GLES3 backend) + + Changes in sokol_gfx.h: + - (breaking change) the following `sg_features` members have been removed (because those features + are no longer optional, but guaranteed across all backends): + - `sg_features.instancing` + - `sg_features.multiple_render_targets` + - `sg_features.msaa_render_targets` + - `sg_features.imagetype_3d` + - `sg_features.imagetype_array` + - (breaking change) the struct `sg_gl_context_desc` and its embedded instance `sg_desc.gl` have been removed + - `sg_image` objects with `SG_PIXELFORMAT_DEPTH` or `SG_PIXELFORMAT_DEPTH_STENCIL` with + a `sample_count == 1` are now regular textures in the GL backend (this is not true + for MSAA depth textures unfortunately, those are still GL render buffer objects) + - in the GL backend, `SG_PIXELFORMAT_DEPTH` now resolves to `GL_DEPTH_COMPONENT32F` (same + as in the other backends), previously it was `GL_DEPTH_COMPONENT16` + - in `sg_begin_pass()`, the GL backend now only uses the new `glClearBuffer*` functions, the + old GLES2 clear functions have been removed + - in `sg_end_pass()`, the GLES3 backend now invalidates MSAA render buffers after they have + been resolved (via `glInvalidateFramebuffer`) - more control over this will come soon-ish + when this ticket is implemented: https://github.com/floooh/sokol/issues/816 + - the instanced rendering functions are no longer wrapped in C macros in the GL backend + + Changes in sokol_app.h: + - (breaking) the config item `sapp_desc.gl_force_gles2` has been removed + - (breaking) the function `sapp_gles2()` has been removed + - any fallback logic from GLES3 to GLES2 has been removed (in the Emscripten, Android and + iOS backends) + +- **20-Feb-2023**: sokol_gfx.h has a new set of functions to get a 'best-effort' + desc struct with the creation parameters of a specific resource object: + + ```c + sg_buffer_desc sg_query_buffer_desc(sg_buffer buf); + sg_image_desc sg_query_image_desc(sg_image img); + sg_shader_desc sg_query_shader_desc(sg_shader shd); + sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip); + sg_pass_desc sg_query_pass_desc(sg_pass pass); + ``` + + The returned structs will *not* be an exact copy of the desc struct that + was used for creation the resource object, instead: + + - references to external data (like buffer and image content or + shader sources) will be zeroed + - any attributes that have not been kept around internally after + creation will be zeroed (the ```sg_shader_desc``` struct is most + affected by this, the other structs are fairly complete). + + Calling the functions with an invalid or dangling resource handle + will return a completely zeroed struct (thus it may make sense + to first check the resource state via ```sg_query_*_state()```) + + Nevertheless, those functions may be useful to get a partially filled out + 'creation blueprint' for creating similar resources without the need + to keep and pass around the original desc structs. + + >MINOR BREAKING CHANGE: the struct members ```sg_image_info.width``` and + ```sg_image_info.height``` have been removed, this information is now + returned by ```sg_query_image_desc()```. + + PR: https://github.com/floooh/sokol/pull/796, fixes: https://github.com/floooh/sokol/issues/568 + +- **17-Feb-2023**: sokol_app.h on macOS now has a proper fix for the problem + that macOS doesn't send key-up events while the Cmd key is held down. + Previously this was handled through a workaround of immediately sending a + key-up event after its key-down event if the Cmd key is currently held down + to prevent a 'stuck key'. The proper fix is now to install an "event monitor" + callback (many thanks to GLFW for finding and implementing the solution). + Unfortunately there's no such solution for the Emscripten code path, which + also don't send a key-up event while Cmd is pressed on macOS (the workaround + there to send a key-up event right on key-down while Cmd is held down to + prevent a stuck key is still in place) For more details, see: + https://github.com/floooh/sokol/issues/794 + +- **15-Feb-2023**: A fix in the sokol_gfx.h GL backend: due to a bug in the + state cache, the GL backend could only bind a total of + SG_MAX_SHADERSTAGE_IMAGES (= 12) when it actually should be twice that amount + (12 per shader stage). Note however that the total amount of texture bindings + is still internally limited by the GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS + runtime variable (~~currently this is not exposed in sg_limits though~~). Many + thanks to @allcreater for PR https://github.com/floooh/sokol/pull/787. + PS: sg_limits now exposes GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS as + ```sg_limits.gl_max_combined_texture_image_units```, and the + value can also be inspected via the debug UI in sokol_gfx_imgui.h. + +- **13-Feb-2023**: The way logging works has been completely revamped in + the sokol headers. UWP support has been removed from sokol_audio.h + and sokol_app.h (this also means that the sokol headers no longer contain + any C++ code). + + **REQUIRED ACTION**: Since the sokol headers are now completely silent + without a logging callback (explanation below), it is highly recommended + to use the standard logging callback provided by the new header ```sokol_log.h```. + For instance for sokol_gfx.h it looks like this: + + ```c + #include "sokol_log.h" + //... + sg_setup(&(sg_desc){ + //... + .logger.func = slog_func, + }); + ``` + + All sokol samples have been updated to use sokol_log.h for logging. + + The former logging callback is now a combined + logging- and error-reporting callback, and more information is available + to the logging function: + - a 'tag string' which identifies the sokol headers, this string + is identical with the API prefix (e.g. "sg" for sokol_gfx.h, + "sapp" for sokol_app.h etc...) + - a numeric log level: 0=panic, 1=error, 2=warning, 3=info + - a numeric 'log item id' (think of it as error code, but since + not only errors are reported I called it a log item id) + - a human readable error message + - a source file line number where the log item was reported + - the file path of the sokol header + + Log level ```panic``` is special in that it terminates execution inside + the log function. When a sokol header issues a panic log message, it means + that the problem is so big that execution can not continue. By default, + the sokol headers and the standard log function in sokol_log.h call + ```abort()``` when a panic log message is issued. + + In debug mode (NDEBUG not defined, or SOKOL_DEBUG defined), a log message + (in this case from sokol_spine.h) will 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 information can be 'parsed' like this: + - ```[sspine]```: it's a message from sokol_spine.h + - ```[error]```: it's an error + - ```[id:12]```: the numeric log item id (associated with ```SKELETON_DESC_NO_ATLAS``` below) + - source file path and line number in a compiler-specific format - in some IDEs and terminals + this is a clickable link + - the line below is the human readable log item id and message + + In release mode (NDEBUG is defined and SOKOL_DEBUG is not defined), log messages + are drastically reduced (the reason is to not bloat the executable with all the extra string data): + + ``` + [sspine][error][id:12][line:3472] + ``` + ...this reduced information still gives all the necessary information to identify the location and type of error. + + A custom logging function must adhere to a few rules: + + - 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 + + A new header ```sokol_log.h``` has been added to provide a standard logging callback implementation + which provides logging output on all platforms to stderr and/or platform specific logging + facilities. ```sokol_log.h``` only uses fputs() and platform specific logging function instead + of fprintf() to preserve some executable size. + + **QUESTION**: Why are the sokol headers now silent, unless a logging callback is installed? + This is mainly because a standard logging function which does something meaningful on all + platforms (including Windows and Android) isn't trivial. E.g. printing to stderr is not + enough. It's better to move that stuff into a centralized place in a separate header, + but since the core sokol headers must not (statically) depend on other sokol headers + the only solution that made sense was to provide a standard logging function which must + be 'registered' as a callback. + +- **26-Jan-2023**: Work on SRGB support in sokol_gfx.h has started, but + this requires more effort to be really usable. For now, only a new + pixel format has been added: SG_PIXELFORMAT_SRGB8A8 (see https://github.com/floooh/sokol/pull/758, + many thanks to @allcreater). The sokol-gfx GL backend has a temporary + workaround to align behaviour with D3D11 and Metal: automatic SRGB conversion + is enabled for offscreen render passes, but disabled for the default + framebuffer. A proper fix will require separate work on sokol_app.h to + support an SRGB default framebuffer and communicate to sokol-gfx + whether the default framebuffer is SRGB enabled or not. + +- **24-Jan-2023**: sokol_gfx.h Metal: A minor inconsistency has been fixed in + the validation layer and an assert for the function ```sg_apply_uniforms()``` + which checks the size of the incoming data against the uniform block size. + The validation layer and Metal backend did a ```<=``` test while the D3D11 + and GL backends checked for an exact size match. Both the validation layer + and the Metal backend now also check for an exact match. Thanks to @nmr8acme + for noticing the issue and providing a PR! (https://github.com/floooh/sokol/pull/776) + +- **23-Jan-2023**: A couple more sokol_audio.h updates: + - an AAudio backend has been added for Android, and made the default. This + means you now need to link with ```aaudio``` instead of ```OpenSLES``` when + using sokol_audio.h on Android. The OpenSLES backend code still exists (for + now), but must be explicitly selected by compiling the sokol_audio.h + implementation with the define ```SAUDIO_ANDROID_SLES``` (e.g. there is + no runtime fallback from AAudio to OpenSLES). AAudio is fully supported + since Android 8.1. Many thanks to @oviano for the initial AAudio PR + (https://github.com/floooh/sokol/pull/484) + - in the WebAudio backend, WebAudio is now properly activated on the first + input action again on Chrome for Android (at some point activating WebAudio + via a ```touchstart``` event stopped working and had to be moved to the + ```touchend``` event, see https://github.com/floooh/sokol/issues/701) + - audio backend initialization on iOS and macOS is now a bit more fault-tolerant, + errors during initialization now properly set sokol_audio.h to 'silent mode' + instead of asserting (or in release mode ignoring the error) + - ...and some minor general code cleanup things in sokol_audio.h: backend-specific + functions now generally have a matching prefix (like ```_saudio_alsa_...()```) + for better searchability + +- **16-Jan-2023**: + - sokol_audio.h android: https://github.com/floooh/sokol/pull/747 has been merged + which adds a couple more error checks at OpenSLES startup. + - sokol_gfx.h: support for half-float vertex formats has been added via + PR https://github.com/floooh/sokol/pull/745 + - sokol_imgui.h: fixes for Dear ImGui 1.89 deprecations (via PR https://github.com/floooh/sokol/pull/761) + +- **15-Jan-2023**: two bugfixes in sokol_app.h and sokol_gfx.h: + - sokol_app.h x11: Mouse button events now always return valid mouse + coordinates, also when no mouse movement happened yet + (fixes https://github.com/floooh/sokol/issues/770) + - sokol_gfx.h gl: The GL context is now configured with + GL_UNPACK_ALIGNMENT = 1, this should bring texture creation and updating + behaviour in line with the other backends for tightly packed texture + data that doesn't have a row-pitch with a multiple of 4 + (fixes https://github.com/floooh/sokol/issues/767) + +- **14-Jan-2023**: sokol_app.h x11: a drag'n'drop related bugfix, the + XdndFinished reply event was sent with the wrong window handle which + confused some apps where the drag operation originated + (see https://github.com/floooh/sokol/pull/765#issuecomment-1382750611) + +- **16-Dec-2022**: In the sokol_gfx.h Metal backend: A fix for a Metal + validation layer error which I just discovered yesterday (seems to be new in + macOS 13). When the validation layer is active, and the application window + becomes fully obscured, the validation layer throws an error after a short + time (for details see: https://github.com/floooh/sokol/issues/762). + The reason appears to be that sokol_gfx.h creates a command buffer with + 'unretained references' (e.g. the command buffer doesn't manage the + lifetime of resources used by the commands stored in the buffer). This + seems to clash with MTKView's and/or CAMetalLayer's expectations. I fixed + this now by creating a second command buffer with 'retained references', + which only holds the ```presentDrawable``` command. That way, regular + draw commands don't have the refcounting overhead (because they're stored + in an unretained-references cmdbuffer), while the drawable surface is + still properly lifetime managed (because it's used in a separate command + buffer with retained references). + +- **15-Dec-2022**: A small but important update in sokol_imgui.h which fixes + touch input handling on mobile devices. Many thanks to github user @Xadiant + for the bug investigation and [PR](https://github.com/floooh/sokol/pull/760). + +- **25-Nov-2022**: Some code cleanup around resource creation and destruction in sokol_gfx.h: + - It's now safe to call the destroy, uninit and dealloc functions in any + resource state, in general, the functions will do the right thing without + assertions getting in the way (there are however new log warnings in some + cases though, such as attempting to call an ```sg_dealloc_*()``` function on + a resource object that's not in ALLOC state) + - A related **minor breaking change**: the ```sg_uninit_*()``` functions now return + void instead of bool, this is because ```sg_dealloc_*()``` no longer asserts + when called in the wrong resource state + - Related internal code cleanup in the backend-agnostic resource creation + and cleanup code, better or more consistent function names, etc... + - The validation layer can now be disabled in debug mode with a runtime + flag during setup: ```sg_desc.disable_validation```. This is mainly useful + for test code. + - Creating a pass object with invalid image objects now no longer asserts, + but instead results in a pass object in FAILED state. In debug mode, + the validation layer will still stop at this problem though (it's mostly + an 'undefined API behaviour' fix in release mode). + - Calling ```sg_shutdown()``` with existing resources in ALLOC state will + no longer print a log message about an 'active context mismatch'. + - A new header documentation blurb about the two-step resource creation + and destruction functions (search for RESOURCE CREATION AND DESTRUCTION IN DETAIL) + +- **16-Nov-2022**: Render layer support has been added to sokol_debugtext.h, + same general changes as in sokol_gl.h with two new functions: + sdtx_layer(layer_id) to select the layer to record text into, and + sdtx_draw_layer(layer_id) to draw the recorded text in that layer inside a + sokol-gfx render pass. The new sample [debugtext-layers-sapp](https://floooh.github.io/sokol-html5/debugtext-layers-sapp) demonstrates the feature together with + sokol-gl. + + +- **11-Nov-2022**: sokol_gl.h has 2 new public API functions which enable + layered rendering: sgl_layer(), sgl_draw_layer() (technically it's three + functions: there's also sgl_context_draw_layer(), but that's just a variant of + sgl_draw_layer()). This allows to 'interleave' sokol-gl rendering + with other render operations. The [spine-layers-sapp](https://floooh.github.io/sokol-html5/spine-layers-sapp.html) + sample has been updated to use multiple sokol-gl layers. + +- **09-Nov-2022**: sokol_gfx.h now allows to add 'commit listeners', these + are callback functions which are called from inside sg_commit(). This is + mainly useful for libraries which build on top of sokol-gfx to be notified + about the start/end point of a frame, which in turn may simplify the public + API, or the internal implementation, because the library no longer needs to + 'guess' when a new frame starts. + + For more details, search for 'COMMIT LISTENERS' in the sokol_gfx.h header. + + This also results in a minor breaking change in sokol_spine.h: The function + ```sspine_new_frame()``` has been removed and replaced with an internal commit + listener. + + Likewise, sokol_gl.h now uses a commit listener in the implementation, but + without changing the public API (the feature will be important for an upcoming + sokol-gl feature to support rendering layers, and for this a 'new-frame-function' + would have been needed). + +- **05-Nov-2022** A breaking change in sokol_fetch.h, and a minor change in + sokol_app.h which should only break for very few users: + - An ```sfetch_range_t``` ptr/size pair struct has been added to sokol_fetch.h, + and discrete ptr/size pairs have been replaced with sfetch_range_t + items. This affects the structs ```sfetch_request_t``` and ```sfetch_response_t```, + and the function ```sfetch_bind_buffer()```. + - The required changes in ```sfetch_response_t``` might be a bit non-obviois: To + access the fetched data, previous ```.buffer_ptr``` and ```.fetched_size``` + was used. The fetched data is now accessible through an ```sfetch_range_t data``` + item (```data.ptr``` and ```data.size```). The old ```.fetched_offset``` item + has been renamed to ```.data_offset``` to better conform with the new naming. + - The last two occurrences of discrete ptr/size pairs in sokol_app.h now have also + been replaced with ```sapp_range_t``` items, this only affects the structs + ```sapp_html5_fetch_request``` and ```sapp_html5_fetch_response```. + +- **03-Nov-2022** The language bindings generation has been updated for Zig 0.10.0, + and clang-14 (there was a minor change in the JSON ast-dump format). + Many thanks to github user @kcbanner for the Zig PR! + +- **02-Nov-2022** A new header sokol_spine.h (in the util dir), this is a + renderer and 'handle wrapper' around the spine-c runtime (Spine is a popular 2D + character anim system: http://esotericsoftware.com/). This turned out a much bigger + rabbit-hole than I initially expected, but the effort is justified by being a + experimentation testbed for a couple of things I want to add to other sokol + headers (for instance cleaned up handle pool code, a new logging- and error-reporting + system, render layers which will be useful for sokol_gl.h and sokol_debugtext.h). + +- **22-Oct-2022** All sokol headers now allow to override logging with a + callback function (installed in the setup call) instead of defining a SOKOL_LOG + macro. Overriding SOKOL_LOG still works as default fallback, but this is no + longer documented, consider this deprecated. Many thanks to github user + @Manuzor for the PR (see https://github.com/floooh/sokol/pull/721 for details) + +- **21-Oct-2022** RGB9E5 pixel format support in sokol_gfx.h and a GLES2 related + bugfix in the sokol_app.h Android backend: + - sokol_gfx.h now supports RGB9E5 textures (3*9 bit RGB + 5 bit shared exponent), + this works in all backends except GLES2 and WebGL1 (use ```sg_query_pixelformat()``` + to check for runtime support). Many thanks to github user @allcreater for the PR! + - a bugfix in the sokol_app.h Android backend: when forcing a GLES2 context via + sapp_desc.gl_force_gles2, the Android backend correctly created a GLES2 context, + but then didn't communicate this through the function ```sapp_gles2()``` (which + still returned false in this case). This caused the sokol_gfx.h GL backend to + use the GLES3 code path instead GLES2 (which surprisingly seemed to have worked + fine, at least for the sokol samples which force GLES2). + +- **19-Oct-2022** Some fixes in the embedded Javascript code blocks (via EM_JS) + in sokol_app.h, sokol_args.h, sokol_audio.h and sokol_fetch.h: + - the JS code has been 'modernized' (e.g. const and let instead of var, + ```() => { ... }``` instead of ```function () { ... }``` for callbacks) + - false positives in the Closure static analysis have been suppressed + via inline hints + +- **16-Oct-2022** The Odin bindings generator and the generated bindings have + been simplified (the Odin binding now don't have separate wrapper functions). + Requires the latest Odin release. Also note: On M1 Macs I'm currently seeing + what looks like an ABI problem (in functions which pass color values to the C + side as uint8_t, the colors come out wrong). This also happened with the + previous binding version, so it looks like a regression in Odin. Might be + related to this recent bugfix (which I haven't tested yet): + https://github.com/odin-lang/Odin/issues/2121 Many thanks to @thePHTest for the + PR! (https://github.com/floooh/sokol/pull/719) + +- **15-Oct-2022** + - fixes for Emscripten 3.1.24: the sokol headers now use the new + **EM_JS_DEPS()** macro to declare 'indirect dependencies on JS library functions'. + This is a (much more robust) follow-up fix to the Emscripten related fixes from 10-Sep-2022. + The new Emscripten SDK also displays a couple of Javascript "static analyzer" warnings + by the Closure compiler (used in release mode to optimize and minify the generated + JS code). I fixed a couple of those warnings, but some warnings persist (all of them + false positives). Not sure yet if these can be fixed or need to be suppressed, but + that's for another time. + - the webkitAudioContext() fallback in sokol_audio.h's Emscripten backend + has been removed (only AudioContext is supported now), the fallback also + triggered a Closure warning, so it probably never worked as intended anyway. + - I also had to undo an older workaround in sokol_app.h on iOS (https://github.com/floooh/sokol/issues/645) + because this is now triggering a Metal validation layer error (https://github.com/floooh/sokol/issues/726). + The original case is no longer reproducible, so undoing the old workaround seems to + be a quick fix. Eventually I want to get rid of MTKView though, and go down to + CAMetalLayer. + +- **08-Oct-2022** sokol_app.h Android backend: the ```sapp_touchpoint``` struct + now has a new item ```sapp_android_tooltype android_tooltype;```. This exposes the + result of the Android NDK function ```AMotionEvent_getToolType()```. + Many thanks to @Wertzui123 for the initial PR (https://github.com/floooh/sokol/pull/717). + +- **25-Sep-2022**: sokol_app.h on Linux now optionally supports EGL instead of + GLX for the window system glue code and can create a GLES2 or GLES3 context + instead of a 'desktop GL' context. + To get EGL+GLES2/GLES3, just define SOKOL_GLES2 or SOKOL_GLES3 to compile the + implementation. To get EGL+GL, define SOKOL_GLCORE33 *and* SOKOL_FORCE_EGL. + By default, defining just SOKOL_GLCORE33 uses GLX for the window system glue + (just as before). Many thanks to GH user @billzez for the PR! + +- **10-Sep-2022**: sokol_app.h and sokol_args.h has been fixed for Emscripten 3.21, those headers + used the Emscripten Javascript helper function ```ccall()``` which is now part of the + 'legacy runtime' and causes linker errors. Instead of ```ccall()``` sokol_app.h and sokol_args.h + now drop down to a lower level set of Emscripten JS helper functions (which hopefully won't + go away anytime soon). + +- **05-Aug-2022**: New officially supported and automatically updated language bindings for Odin: + https://github.com/floooh/sokol-odin (also see [gen_odin.py](https://github.com/floooh/sokol/blob/master/bindgen/gen_odin.py)) + +- **10-Jul-2022**: New features in sokol_app.h and sokol_imgui.h: + - In sokol_app.h it's now possible to set a mouse cursor type from a number of predefined + types via the new function ```sapp_set_mouse_cursor(sapp_mouse_cursor cursor)```. The + available cursor types are compatible with GLFW and Dear ImGui. Supported platforms + are: macOS, linux, win32, uwp and web. + - ```sapp_show_mouse(bool shown)``` now also works on the web platform. + - In sokol_app.h, the poorly defined 'user cursor' feature has been removed (```sapp_desc.user_cursor``` + and ```SAPP_EVENTTYPE_UPDATE_CURSOR```). This was a hack to allow changing the mouse cursor and + only worked on Win32 and macOS (with different behaviour). Since setting the cursor type + is now 'properly supported, this hack was removed. + - sokol_imgui.h will now set the cursor type via ```sapp_set_mouse_cursor()```. This can be + disabled with the new ```simgui_desc_t``` item ```disable_set_mouse_cursor```. + - sokol_imgui.h now automatically enables resizing windows from edges (not just the bottom-right corner), + this behaviour can be disabled with the new ```simgui_desc_t``` item ```disable_windows_resize_from_edges```. + - sokol_imgui.h can now optionally write to the alpha channel (useful if you want to render the UI + into a separate render target, which is later composed onto the default framebuffer). The feature + is enabled with the new ```simgui_desc_t``` item ```write_alpha_channel```. + + Many thanks to **@tomc1998** for the initial [Linux/X11 mouse cursor type PR](https://github.com/floooh/sokol/pull/678) and + **@luigi-rosso** for the [sokol_imgui.h alpha channel PR](https://github.com/floooh/sokol/pull/687)! + +- **03-Jul-2022**: A new sokol_gfx.h function ```bool sg_query_buffer_will_overflow(sg_buffer buf, size_t size)``` +which allows to check if a call to ```sg_append_buffer()``` would overflow the buffer. This +is an alternative to the ```sg_query_buffer_overflow()``` function which only reports +the overflow after the fact. Many thanks to @RandyGaul for the PR! + +- **29-Jun-2022**: In sokol_app.h with the D3D11 backend, if SOKOL_DEBUG is +defined, and the D3D11 device creation fails, there's now a fallback code +path which tries to create the device again without the D3D11_CREATE_DEVICE_DEBUG +flag. Turns out the D3D11 debug support may suddenly stop working (just happened +to me, indicated by the Win10 "Graphics Tool" feature being silently uninstalled +and failing to install when asked to do so). This fix at least allows sokol_app.h +applications compiled in debug mode to run, even if the D3D11 debug layer doesn't +work. + +- **29-May-2022**: The code generation scripts for the +[sokol-nim](https://github.com/floooh/sokol-nim) language bindings have been +revised and updated, many thanks to Gustav Olsson for the PR! (I'm planning to +spend a few more days integrating the bindings generation with Github Actions, +so that it's easier to publish new bindings after updates to the sokol headers). + +- **26-May-2022**: The GL backend in sokol_app.h now allows to override the GL + context version via two new items in the ```sapp_desc``` struct: + ```sapp_desc.gl_major_version``` and ```sapp_desc.gl_minor_version```. The + default GL context version remains at 3.2. Overriding the GL version might make + sense if you're not using sokol_app.h together with sokol_gfx.h, or otherwise + want to call GL functions directly. Note that this only works for the + 'desktop GL' backends (Windows, Linux and macOS), but not for the GLES backends + (Android, iOS, web). Furthermore, on macOS only the GL versions 3.2 and 4.1 + are available (plus the special config major=1 minor=0 creates an + NSOpenGLProfileVersionLegacy context). In general: use at your risk :) Many + thanks to Github user @pplux for the PR! + +- **15-May-2022**: The way internal memory allocation can be overridden with + your own functions has been changed from global macros to callbacks + provided in the API setup call. For instance in sokol_gfx.h: + + ```c + void* my_malloc(size_t size, void* userdata) { + (void)userdata; // unused + return malloc(size); + } + + void my_free(void* ptr, void* userdata) { + (void)userdata; // unused + free(ptr); + } + + //... + sg_setup(&(sg_desc){ + //... + .allocator = { + .alloc = my_malloc, + .free = my_free, + .user_data = ..., + } + }); + ``` + + sokol_gfx.h will now call ```my_malloc()``` and ```my_free()``` whenever it needs + to allocate or free memory (note however that allocations inside OS + functions or 3rd party libraries are not affected). + + If no override functions are provided, the standard library functions ```malloc()``` and ```free()``` + will be used, just as before. + + This change breaks source compatibility in the following headers: + + - **sokol_fontstash.h**: the function signature of ```sfons_create()``` has changed, + this now takes a pointer to a new ```sfons_desc_t``` struct instead of + individual parameters. + - **sokol_gfx_imgui.h** (NOT sokol_imgui.h!): likewise, the function signature of + ```sg_imgui_init()``` has changed, this now takes an additional parameter + which is a pointer to a new ```sg_imgui_desc_t``` struct. + + All affected headers also have a preprocessor check for the outdated + macros ```SOKOL_MALLOC```, ```SOKOL_CALLOC``` and ```SOKOL_FREE``` and throw + a compilation error if those macros are detected. + + (if configuration through macros is still desired this could be added back in + the future, but I figured that the new way is more flexible in most situations). + + The header sokol_memtrack.h and the sample [restart-sapp](https://floooh.github.io/sokol-html5/restart-sapp.html) have been updated accordingly. + + Also search for ```MEMORY ALLOCATION OVERRIDE``` in the header documentation block + for more details. + +- **14-May-2022**: added a helper function ```simgui_map_keycode()``` to + sokol_imgui.h to map sokol_app.h keycodes (```sapp_keycode```, + ```SAPP_KEYCODE_*```) to Dear ImGui keycodes (```ImGuiKey```, ```ImGuiKey_*```). + If you're using Dear ImGui function to check for key input, you'll need to + update the code like this: + + - Old: + ```cpp + ImGui::IsKeyPressed(SAPP_KEYCODE_A); + ``` + - New: + ```cpp + ImGui::IsKeyPressed(simgui_map_keycode(SAPP_KEYCODE_A)); + ``` + + This was basically 'fallout' from rewriting the input system in sokol_imgui.h + to the new evented IO system in Dear ImGui. + +- **08-Feb-2022**: sokol_imgui.h has been updated for Dear ImGui 1.87: + - sokol_imgui.h's input code has been rewritten to use the new evented IO + system and extended virtual key codes in Dear ImGui + - on non-Emscripten platforms, mouse buttons are no longer "cancelled" when + the mouse leaves the window (since the native desktop platforms + automatically capture the mouse when mouse buttons are pressed, but mouse + capture is not supported in the sokol_app.h Emscripten backend) + +- **28-Jan-2022**: some window size behaviour changes in sokol_app.h. + - Asking for a default-sized window (via sapp_desc.width/height = 0) now + behaves a bit differently on desktop platforms. Previously this set the + window size to 640x480, now a default window covers more screen area: + - on Windows CW_USEDEFAULT will be used for the size + - on macOS and Linux, the window size will be 4/5 of the + display size + - no behaviour changes on other platforms + - On Windows and Linux, the window is now centered (in a later update, + more control over the initial window position, and new functions for + positioning and sizing might be provided) + - On Windows, when toggling between windowed and fullscreen, the + window position and size will now be restored (on other platforms + this already happened automatically through the window system) + - On all desktop platforms if an application starts in fullscreen and + then is toggled back to windowed, the window will now be of the + expected size (provided in sapp_desc.width/height) + +- **20-Jan-2022**: + - sokol_audio.h: A compatibility fix in the sokol_audio.h WASAPI backend (Windows): On + some configs the IAudioClient::Initialize() call could fail because + of a mismatch between the requested number of channels and speaker config. + See [#614](https://github.com/floooh/sokol/issues/614) for details. + - sokol_app.h D3D11/DXGI: Fix an (uncritical) COM interface leak warning for IDXGIAdapter and + IDXGIFactory at shutdown, introduced with the recent disabling of Alt-Enter. + +- **18-Jan-2022**: + - sokol_app.h now has per-monitor DPI support on Windows and macOS: when + the application window is moved to a monitor with different DPI, the values + returned by sapp_dpi_scale(), sapp_width() and sapp_height() will update + accordingly (only if the application requested high-dpi rendering with + ```sapp_desc.high_dpi=true```, otherwise the dpi scale value remains + fixed at 1.0f). The application will receive an SAPP_EVENTTYPE_RESIZED event + if the default framebuffer size has changed because of a DPI change. + On Windows this feature requires Win10 version 1703 or later (aka the + 'Creators Update'), older Windows version simply behave as before. + Many thank to @tjachmann for the initial PR with the Windows implementation! + - sokol_app.h: DPI scale computation on macOS is now more robust using the + NSScreen.backingScaleFactor value + - sokol_app.h: the new frame timing code in sokol_app.h now detects if the display + refresh rate changes and adjusts itself accordingly (for instance if the + window is moved between displays with different refresh rate) + - sokol_app.h D3D11/DXGI: during window movement and resize, the frame is now + presented with DXGI_PRESENT_DO_NOT_WAIT, this fixes some window system + stuttering issues on Win10 configs with recent NVIDIA drivers. + - sokol_app.h D3D11/DXGI: the application will no longer appear to freeze for + 0.5 seconds when the title bar is grabbed with the mouse for movement, but + then not moving the mouse. + - sokol_app.h D3D11/DXGI: DXGI's automatic windowed/fullscreen switching via + Alt-Enter has been disabled, because this switched to 'real' fullscreen mode, + while sokol_app.h's fullscreen mode uses a borderless window. Use the + programmatic fullscreen/window switching via ```sapp_toggle_fullscreen()``` + instead. + - **BREAKING CHANGE** in sokol_imgui.h: because the applications' DPI scale + can now change at any time, the DPI scale value is now communicated to + sokol_imgui.h in the ```simgui_new_frame()``` function. This has been + changed to accept a pointer to a new ```simgui_frame_desc_t``` struct. + With C99, change the simgui_new_frame() call as follows (if also using + sokol_app.h): + ```c + simgui_new_frame(&(simgui_frame_desc_t){ + .width = sapp_width(), + .height = sapp_height(), + .delta_time = sapp_frame_duration(), + .dpi_scale = sapp_dpi_scale() + }); + ``` + On C++ this works: + ```c++ + simgui_new_frame({ sapp_width(), sapp_height(), sapp_frame_duration(), sapp_dpi_scale() }); + ``` + ...or in C++20: + ```c++ + simgui_new_frame({ + .width = sapp_width(), + .height = sapp_height(), + .delta_time = sapp_frame_duration(), + .dpi_scale = sapp_dpi_scale() + }); + ``` + - **KNOWN ISSUE**: the recent change in sokol-audio's WASAPI backend to directly consume + float samples doesn't appear to work on some configs (see [#614](https://github.com/floooh/sokol/issues/614)), + investigation is underway + +- **15-Jan-2022**: + - A bugfix in the GL backend for uniform arrays using the 'native' uniform block layout. + The bug was a regression in the recent 'uniform data handling' update. See + [PR #611](https://github.com/floooh/sokol/pull/611) for details, and this [new sample/test](https://github.com/floooh/sokol-samples/blob/master/glfw/uniformarrays-glfw.c). + Many thanks to @nmr8acme for the PR! + +- **08-Jan-2022**: some enhancements and cleanup to uniform data handling in sokol_gfx.h + and the sokol-shdc shader compiler: + - *IMPORTANT*: when updating sokol_gfx.h (and you're using the sokol-shdc shader compiler), + don't forget to update the sokol-shdc binaries too! + - The GLSL uniform types int, ivec2, ivec3 and + ivec4 can now be used in shader code, those are exposed to the GL + backends with the new ```sg_uniform_type``` items + ```SG_UNIFORM_TYPE_INT[2,3,4]```. + - A new enum ```sg_uniform_layout```, currently with the values SG_UNIFORMLAYOUT_NATIVE + and SG_UNIFORMLAYOUT_STD140. The enum is used in ```sg_shader_uniform_block_desc``` + as a 'packing rule hint', so that the GL backend can properly locate the offset + of uniform block members. The default (SG_UNIFORMLAYOUT_NATIVE) keeps the same + behaviour, so existing code shouldn't need to be changed. With the packing + rule SG_UNIFORMLAYOUT_STD140 the uniform block interior is expected to be + laid out according to the OpenGL std140 packing rule. + - Note that the SG_UNIFORMLAYOUT_STD140 only allows a subset of the actual std140 + packing rule: arrays are only allowed for the types vec4, int4 and mat4. + This is because the uniform data must still be compatible with + ```glUniform()``` calls in the GL backends (which have different + 'interior alignment' for arrays). + - The sokol-shdc compiler supports the new uniform types and will annotate the + code-generated sg_shader_desc structs with SG_UNIFORMLAYOUT_STD140, + and there are new errors to make sure that uniform blocks are compatible + with all sokol_gfx.h backends. + - Likewise, sokol_gfx.h has tighter validation for the ```sg_shader_uniform_block``` + desc struct, but only when the GL backend is used (in general, the interior + layout of uniform blocks is only relevant for GL backends, on all other backends + sokol_gfx.h just passes the uniform data as an opaque block to the shader) + For more details see: + - [new sections in the sokol_gfx.h documentation](https://github.com/floooh/sokol/blob/ba64add0b67cac16fc86fb6b64d1da5f67e80c0f/sokol_gfx.h#L343-L450) + - [documentation of ```sg_uniform_layout```](https://github.com/floooh/sokol/blob/ba64add0b67cac16fc86fb6b64d1da5f67e80c0f/sokol_gfx.h#L1322-L1355) + - [enhanced sokol-shdc documentation](https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md#glsl-uniform-blocks-and-c-structs) + - [a new sample 'uniformtypes-sapp'](https://floooh.github.io/sokol-html5/uniformtypes-sapp.html) + + PS: and an unrelated change: the frame latency on Win32+D3D11 has been slightly improved + via IDXGIDevice1::SetMaximumFrameLatency() + +- **27-Dec-2021**: sokol_app.h frame timing improvements: + - A new function ```double sapp_frame_duration(void)``` which returns the frame + duration in seconds, averaged over the last 256 frames to smooth out + jittering spikes. If available, this uses platform/backend specific + functions of the swapchain API: + - On Windows: DXGI's GetFrameStatistics().SyncQPCTime. + - On Emscripten: the timestamp provided by the RAF callback, this will + still be clamped and jittered on some browsers, but averaged over + a number of frames yields a pretty accurate approximation + of the actual frame duration. + - On Metal, ```MTLDrawable addPresentedHandler + presentedTime``` + doesn't appear to function correctly on macOS Monterey and/or M1 Macs, so + instead mach_absolute_time() is called at the start of the MTKView + frame callback. + - In all other situations, the same timing method is used as + in sokol_time.h. + - On macOS and iOS, sokol_app.h now queries the maximum display refresh rate + of the main display and uses this as base to compute the preferred frame + rate (by multiplying with ```sapp_desc.swap_interval```), previously the + preferred frame rate was hardwired to ```60 * swap_interval```. This means + that native macOS and iOS applications may now run at 120Hz instead of + 60Hz depending on the device (I realize that this isn't ideal, there + will probably be a different way to hint the preferred interval at + which the frame callback is called, which would also support disabling + vsync and probably also adaptive vsync). + +- **19-Dec-2021**: some sokol_audio.h changes: + - on Windows, sokol_audio.h no longer converts audio samples + from float to int16_t, but instead configures WASAPI to directly accept + float samples. Many thanks to github user iOrange for the PR! + - sokol_audio.h has a new public function ```saudio_suspended()``` which + returns true if the audio device/context is currently in suspended mode. + On all backends except WebAudio this always returns false. This allows + to show a visual hint to the user that audio is muted until the first + input event is received. + +- **18-Dec-2021**: the sokol_gfx.h ```sg_draw()``` function now uses the currently applied + pipeline object to decide if the GL or D3D11 backend's instanced drawing function + should be called instead of the ```num_instances``` argument. This fixes a + bug on some WebGL configs when instanced rendering is configured + but ```sg_draw()``` is called with an instance count of 1. + +- **18-Nov-2021**: sokol_gl.h has a new function to control the point size for + point list rendering: ```void sgl_point_size(float size)```. Note that on D3D11 + the point size is currently ignored (since D3D11 doesn't support a point size at + all, the feature will need to be emulated in sokol_gl.h when the D3D11 backend is active). + Also note that points cannot currently be textured, only colored. + +- **08-Oct-2021**: texture compression support in sokol_gfx.h has been revisited: + - tighter validation checks on texture creation: + - content data validation now also happens in ```sg_make_image()``` (previously only in ```sg_update_image()```) + - validate that compressed textures are immutable + - separate "no data" validation checks for immutable vs dynamic/stream textures + - provided data size for creating or updating textures must match the expected surface sizes exactly + - fix PVRTC row and surface pitch computation according to the GL PVRTC extension spec + - better adhere to Metal documentation for the ```MTLTexture.replaceRegion``` parameters (when bytesPerImage is expected to be zero or not) + +- **02-Sep-2021**: some minor non-breaking additions: + - sokol_app.h: new events FOCUSED and UNFOCUSED to indicate that the + window has gained or lost the focused state (Win32: WM_SETFOCUS/WM_KILLFOCUS, + macOS: windowDidBecomeKey/windowDidResignKey, X11: FocusIn/FocusOut, + HTML5: focus/blur). + - sokol_app.h Emscripten backend: the input event keycode is now extracted + from the HTML5 code string which yields the actual unmapped virtual key code. + +- **21-Aug-2021**: some minor API tweaks in sokol_gl.h and sokol_debugtext.h, + one of them breaking (still minor though): + - sokol_gl.h has a new function ```sgl_default_context()``` which returns the + default context handle, it's the same as the global constant SGL_DEFAULT_CONTEXT, + but wrapping this in a function is better for language bindings + - ...and a similar function in sokol_debugtext.h: ```sdtx_default_context()``` + - The sokol_gl.h function ```sgl_default_pipeline()``` has been renamed to + ```sgl_load_default_pipeline()```. This fits better with the related + function ```sgl_load_pipeline()``` and doesn't 'semantically clash' + with the new function sgl_default_context(). The sgl_default_pipeline() + function is rarely used, so it's quite unlikely that this change breaks + your code. + +- **19-Aug-2021**: sokol_gl.h gained rendering context support, this allows + sokol-gl to render into different sokol-gfx render passes. No changes are + needed for existing sokol-gl code. Check the updated + [header documentation](https://github.com/floooh/sokol/blob/master/util/sokol_gl.h) + and the new sample + [sgl-context-sapp](https://floooh.github.io/sokol-html5/sgl-context-sapp.html) + for details! + +- **21-Jun-2021**: A new utility header sokol_color.h has been added, which adds + sokol_gfx.h-compatible named color constants and a handful initial utility + functions. See the [header documentation](https://github.com/floooh/sokol/blob/master/util/sokol_color.h) + for details. Many thanks to Stuart Adams (@nyalloc) for contributing the header! + +- **12-Apr-2021**: Minor new feature in sokol_app.h: mouse buttons are now + also reported as modifier flags in most input events (similar to the + Ctrl-, Alt-, Shift- and Super-key modifiers). This lets you quickly check + what mouse buttons are currently pressed in any input event without having + to keep track of pressed mouse buttons yourself. This is implemented in the following + sokol_app.h backends: Win32, UWP, Emscripten, X11 and macOS. Example + code is in the [events-sapp.cc](https://floooh.github.io/sokol-html5/events-sapp.html) sample + +- **10-Apr-2021**: followup fixes from yesterday: custom icon support on macOS + has been added (since macOS has no regular window icons, the dock icon is + updated instead), and a bugfix in the internal helper which select the + best matching candidate image (this actually always selected the first + candidate image) + +- **09-Apr-2021**: sokol_app.h now allows to programmatically set the window + icon in the Win32, X11 and HTML5 backends. Search for "WINDOW ICON SUPPORT" + in sokol_app.h for documentation, and see the new + [icon sample](https://floooh.github.io/sokol-html5/icon-sapp.html) for example code. + +- **01-Apr-2021**: some fixes in sokol_app.h's iOS backend: + - In the iOS Metal backend, high-dpi vs low-dpi works again. Some time + ago (around iOS 12.x) MTKView started to ignore the contentScaleFactor + property, which lead to sokol_app.h always setting up a HighDPI + framebuffer even when sapp_desc.high_dpi wasn't set. The fix is to set + the MTKView's drawableSize explicitly now. + - The iOS GL backend didn't support MSAA multisampling so far, this has + been fixed now, but only one MSAA mode (4x) is available, which will be + selected when sapp_desc.sample_count is greater than 1. + +- **31-Mar-2021**: sokol_audio.h on macOS no longer includes system framework + headers (AudioToolbox/AudioToolbox.h), instead the necessary declarations + are embedded directly in sokol_audio.h (to get the old behaviour and + force inclusion of AudioToolbox/AudioToolbox.h, define + ```SAUDIO_OSX_USE_SYSTEM_HEADERS``` before including the sokol_audio.h + implementation). This "fix" is both an experiment and an immediate workaround + for a current issue in Zig's HEAD version (what will eventually become + zig 0.8.0). See this issue for details: https://github.com/ziglang/zig/issues/8360). + The experiment is basically to see whether this approach generally makes sense + (replacing system headers with embedded declarations, so that the sokol headers + only depend on C standard library headers). This approach might + simplify cross-compilation and integration with other languages than C and C++. + +- **20-Mar-2021**: The Windows-specific OpenGL loader, and the platform-specific +GL header includes have been moved from sokol_app.h to sokol_gfx.h. This means: + - In general, the sokol_gfx.h implementation can now simply be included + without having to include other headers which provide the GL API declarations + first (e.g. when sokol_gfx.h is used without sokol_app.h, you don't need to + use a GL loader, or include the system-specific GL headers yourself). + - When sokol_gfx.h is used together with sokol_app.h, the include order + for the implementations doesn't matter anymore (until now, the sokol_app.h + implementation had to be included before the sokol_gfx.h implementation). + - The only "downside" (not really a downside) is that sokol_gfx.h now has + platform detection ifdefs to include the correct GL headers for a given + platform. Until now this problem was "delegated" to the library user. + - The old macro **SOKOL_WIN32_NO_GL_LOADER** has been removed, and replaced + with a more general **SOKOL_EXTERNAL_GL_LOADER**. Define this before + including the sokol_gfx.h implementation if you are using your own GL + loader or provide the GL API declarations in any other way. In this case, + sokol_gfx.h will not include any platform GL headers, and the embedded + Win32 GL loader will be disabled. + +- **22-Feb-2021**: Mouse input latency in sokol_app.h's macOS backend has been + quite significantly reduced, please see the detailed explanation [in this + PR](https://github.com/floooh/sokol/pull/483). Many thanks to @randrew for + the PR! + +- **19-Feb-2021**: sokol_app.h learned some Windows-specific config options +to redirect stdout/stderr to the parent terminal or a separate console +window, and allow outputting UTF-8 encoded text. For details, search for +"WINDOWS CONSOLE OUTPUT" in +[sokol_app.h](https://github.com/floooh/sokol/blob/master/sokol_app.h). Many +thanks to @garettbass for the initial PR! + +- **17-Feb-2021**: When compiled for iOS, the sokol_audio.h CoreAudio backend now +uses the **AVAudioSession** class to activate and deactivate audio output as needed. +This fixes sokol_audio.h for iPhones (so far, sokol_audio.h accidentally only worked +for iPads). Please see [this issue](https://github.com/floooh/sokol/issues/431) for details. +A somewhat unfortunate side effect of this fix is that sokol_audio.h must now be compiled +as Objective-C when targeting iOS, also note that a new framework must be linked: ```AVFoundation```. +Many thanks to @oviano for providing the PR! + +- **14-Feb-2021**: The Dear ImGui rendering backend in [sokol_imgui.h](https://github.com/floooh/sokol/blob/master/util/sokol_imgui.h) has been rewritten to only do a single +buffer-update per frame each for vertex- and index-data. This addresses performance-problems +with sg_append_buffer() in the GL backend on some platforms (see [this issue](https://github.com/floooh/sokol/issues/399) for details. + +- **13-Feb-2021**: A new utility header [sokol_nuklear.h](https://github.com/floooh/sokol/blob/master/util/sokol_nuklear.h) +has been added which implements a rendering backend for [Nuklear](https://github.com/Immediate-Mode-UI/Nuklear) +on top of sokol_gfx.h. Also see the new sample [nuklear-sapp](https://floooh.github.io/sokol-html5/nuklear-sapp.html). +Many thanks to **@wmerrifield** for the PR! + +- **10-Feb-2021**: The breaking API-update has been merged (mainly sokol_gfx.h). +Please see [this blogpost](https://floooh.github.io/2021/02/07/sokol-api-overhaul.html) +and the updates [sokol samples](https://floooh.github.io/sokol-html5/) for details. +I also created a git tag named 'pre-feb2021-api-changes' which captures the previous +state in all related projects. Please also update the [sokol-tools-bin](https://github.com/floooh/sokol-tools-bin) if you're using the sokol-shdc shader compiler. + +- **07-Feb-2021**: A PSA about upcoming breaking changes in (mainly) sokol_gfx.h: https://floooh.github.io/2021/02/07/sokol-api-overhaul.html + +- **20-Dec-2020**: A couple of minor breaking changes in the sokol_gfx.h and +sokol_app.h APIs as preparation for the upcoming automatic language binding +generation: + - in **sokol_gfx.h** nested unions have been removed: + - **sg_image_desc.depth/.layers** has been renamed to **.num_slices** + - **sg_attachment_desc.face/.layer/.slice** has been unified to **.slice** + - in **sokol_app.h** the return value of **sapp_run()** has been changed from + **int** to **void** (the function always returned zero anyway) + + Non-breaking (or at most potentially breaking) changes: + - expressions in enums have been replaced with integer literals (e.g. (1<<2) becomes 4) + - the value of **SAPP_MOUSEBUTTON_INVALID** has been changed from -1 to 0x100 + + For more information about the upcoming automatic language-bindings generation [see this bog post](https://floooh.github.io/2020/08/23/sokol-bindgen.html) + +- **02-Dec-2020**: sokol_gfx.h has a couple new public API functions for +destroying resources in two steps: + - sg_uninit_buffer + sg_dealloc_buffer + - sg_uninit_image + sg_dealloc_image + - sg_uninit_shader + sg_dealloc_shader + - sg_uninit_pipeline + sg_dealloc_pipeline + - sg_uninit_pass + sg_dealloc_pass + + Calling both functions in this order is identical with calling the + traditional sg_destroy_xxx() functions. See this PR for more details: + https://github.com/floooh/sokol/pull/435. Many thanks to @oviano for the + PR! + +- **28-Nov-2020**: In addition to the generic SOKOL_API_DECL and SOKOL_IMPL +defines there are now header-specific versions SOKOL_xxx_API_DECL and +SOKOL_xxx_IMPL (for instance SOKOL_GFX_API_DECL and SOKOL_GFX_IMPL). The +original motivation for splitting the SOKOL_API_DECL defines up is described +here: https://github.com/floooh/sokol/issues/428). The same change for +SOKOL_IMPL also finally unifies the approach used in the utility headers (in +the ```util``` subdirectory), which exclusively used the SOKOL_xxx_IMPL +pattern with the core headers which exclusively used SOKOL_IMPL before (all +headers accept both patterns now). Many thanks to @iboB for providing the +API_DECL PR! + +- **17-Nov-2020**: A new utility header **sokol_shape.h** to generate + vertices+indices for simple shapes (plane, box, sphere, cylinder and torus), + which seamlessly plug into the sokol_gfx.h resource creation functions. As + with most new utility headers, the initial functionality is a bit bare bones + and the public API shouldn't be considered stable yet. Check the sokol-samples + webpage for new and updates samples: https://floooh.github.io/sokol-html5/ + +- **08-Nov-2020** PSA: It appears that RenderDoc v1.10 chokes on the new + D3D11/DXGI swapchain code from 10-Oct-2020 in sokol_app.h. The current + RenderDoc Nightly Build works, so I guess in v1.11 everything will be fine. + +- **03-Nov-2020**: sokol_app.h: the missing drag'n'drop support for HTML5/WASM + has been added. This adds two platform-specific functions + ```sapp_html5_get_dropped_file_size()``` and + ```sapp_html5_fetch_dropped_file()```. Please read the documentation + section in sokol_app.h under 'DRAG AND DROP SUPPORT' for additional + details and example code. Also consult the source code of the new + ```droptest-sapp``` sample for an example of how to load the content + of dropped files on the web and native platforms: + + https://floooh.github.io/sokol-html5/droptest-sapp.html + + +- **27-Oct-2020**: I committed a bugfix for a longstanding WebGL canvas id versus + css-selector confusion in the emscripten/WASM backend code in sokol_app.h. + I think the fix should not require any changes in your code (because if + you'd be using a canvas name different from the default "canvas" it wouldn't + have worked before anyway). See this bug for details: https://github.com/floooh/sokol/issues/407 + +- **22-Oct-2020**: sokol_app.h now has file drag'n'drop support on Win32, + macOS and Linux. WASM/HTML5 support will be added soon-ish. This will + work a bit differently because of security-related restrictions in the + HTML5 drag'n'drop API, but more on that later. For documentation, + search for 'DRAG AND DROP SUPPORT' in [sokol_app.h](https://github.com/floooh/sokol/blob/master/sokol_app.h). + + Check out [events-sapp.c](https://github.com/floooh/sokol-samples/blob/master/sapp/events-sapp.cc) + for a simple usage example (I will also add a more real-world example to my + chips emulators once the WASM/HTML5 implementation is ready). + + Many thanks for @prime31 and @hb3p8 for the initial PRs and valuable feature + discussions! + +- **10-Oct-2020**: Improvements to the sokol_app.h Win32+D3D11 and UWP+D3D11 swapchain code: + - In the Win32+D3D11 backend and when running on Win10, + ```DXGI_SWAP_EFFECT_FLIP_DISCARD``` is now used. This gets rid of a + deprecation warning in the debugger console and also should allow slightly + more efficient swaps in some situations. When running on Win7 or Win8, the + traditional ```DXGI_SWAP_EFFECT_DISCARD``` is used. + - The UWP backend now supports MSAA multisampling (the required fixes for + this are the same as in the Win32 backend with the new swap effect: a + separate MSAA texture and render-target-view is created where + rendering goes into, and this MSAA texture is resolved into the actual + swapchain surface before presentation). + +- **07-Oct-2020**: + A fix in the ALSA/Linux backend initialization in sokol_audio.h: Previously, + initialization would fail if ALSA can't allocate the exact requested + buffer size. Instead sokol_audio.h let's now pick ALSA a suitable buffer + size. Also better log messages in the ALSA initialization code if something + goes wrong. Unfortunately I'm not able to reproduce the buffer allocation + problem on my Linux machine. Details are in this issue: https://github.com/floooh/sokol/issues/400 + + **NARRATOR**: the fix didn't work. + +- **02-Oct-2020**: + The sokol_app.h Win32 backend can now render while moving and resizing + the window. NOTE that resizing the swapchain buffers (and receiving + SAPP_EVENTTYPE_RESIZED events) is deferred until the resizing finished. + Resizing the swapchain buffers each frame created a substantial temporary + memory spike of up to several hundred MBytes. I need to figure out a better + swapchain resizing strategy. + +- **30-Sep-2020**: + sokol_audio.h now works on UWP, thanks again to Alberto Fustinoni + (@albertofustinoni) for the PR! + +- **26-Sep-2020**: + sokol_app.h gained a new function sapp_set_window_title() to change + the window title on Windows, macOS and Linux. Many thanks to + @medvednikov for the initial PR! + +- **23-Sep-2020**: + sokol_app.h now has initial UWP support using the C++/WinRT set of APIs. + Currently this requires "bleeding edge" tools: A recent VS2019 version, + and a very recent Windows SDK version (at least version 10.0.19041.0). + Furthermore the sokol_app.h implementation must be compiled as C++17 + (this is a requirement of the C++/WinRT headers). Note that the Win32 + backend will remain the primary and recommended backend on Windows. The UWP + backend should only be used when the Win32 backend is not an option. + The [sokol-samples](https://github.com/floooh/sokol-samples) project + has two new build configs ```sapp-uwp-vstudio-debug``` and + ```sapp-uwp-vstudio-release``` to build the sokol-app samples for UWP. + + Many thanks to Alberto Fustinoni (@albertofustinoni) for providing + the initial PR! + + (also NOTE: UWP-related fixes in other sokol headers will follow) + +- **22-Sep-2020**: + A small fix in sokol_app.h's Win32 backend: when a mouse button is pressed, + mouse input is now 'captured' by calling SetCapture(), and when the last + mouse button is released, ReleaseCapture() is called. This also provides + mouse events outside the window area as long as a mouse button is pressed, + which is useful for windowed UI applicactions (this is not the same as the + more 'rigorous' and explicit pointer-lock feature which is more useful for + camera-controls) + +- **31-Aug-2020**: + Internal change: The D3D11/DXGI backend code in sokol_gfx.h and sokol_app.h + now use the D3D11 and DXGI C++-APIs when the implementation is compiled as + C++, and the C-APIs when the implementation is compiled as C (before, the C + API was also used when the implementation is compiled as C++). The new + behaviour is useful when another header *must* use the D3D11/DXGI C++ APIs + but should be included in the same compilation unit as sokol_gfx.h an + sokol_app.h (for example see this PR: + https://github.com/floooh/sokol/pull/351). + +- **24-Aug-2020**: + The backend-specific callback functions that are provided to sokol_gfx.h + in the ```sg_setup()``` initialization call now have alternative + versions which accept a userdata-pointer argument. The userdata-free functions + still exist, so no changes are required for existing code. + +- **02-Aug-2020**: + - sokol_app.h now has a mouse-lock feature (aka pointer-lock) via two + new functions ```void sapp_lock_mouse(bool lock)``` and ```bool sapp_mouse_locked(void)```. + For documentation, please search for 'MOUSE LOCK' in sokol_app.h. + The sokol-app samples [events-sapp](https://floooh.github.io/sokol-html5/events-sapp.html) + and [cgltf-sapp](https://floooh.github.io/sokol-html5/cgltf-sapp.html) have been + updated to demonstrate the feature. + - sokol_app.h Linux: mouse pointer visibility (via ```void sapp_show_mouse(bool show)```) + has been implemented for Linux/X11 + - sokol_app.h WASM: mouse wheel scroll deltas are now 'normalized' between + the different scroll modes (pixels, lines, pages). See this issue: + https://github.com/floooh/sokol/issues/339. Many thanks to @bqqbarbhg for + investigating the issue and providing a solution! + - sokol_app.h now has [better documentation](https://github.com/floooh/sokol/blob/89a3bb8da0a2df843d6cc60a270ddc69f9aa69d6/sokol_app.h#L70) + what system libraries must be linked on the various platforms (and on Linux two additional libraries must be + linked now: Xcursor and Xi) + +- **22-Jul-2020**: **PLEASE NOTE** cmake 3.18 breaks some of sokol samples when + compiling with the Visual Studio toolchain because some C files now actually + compile as C++ for some reason (see: + https://twitter.com/FlohOfWoe/status/1285996526117040128). Until this is + fixed, or I have come up with a workaround, please use an older cmake version + to build the sokol samples with the Visual Studio compiler. + + (Update: I have added a workaround to fips: https://github.com/floooh/fips/commit/89997b8ebdca6fc9455a5cfe6145eecaa017df49 + which fixes the issue at least for fips projects) + +- **14-Jul-2020**: + - sapp_mouse_shown() has been implemented for macOS (thanks to @slmjkdbtl for + providing the initial PR!) + - On macOS, the lower-level functions CGDisplayShowCursor and CGDisplayHideCursor + are now used instead of the NSCursor class. This is in preparation for the + 'pointer lock' feature which will also use CGDisplay* functions. + - Calling ```sapp_show_mouse(bool visible)``` no longer 'stacks' (e.g. there's + no 'hidden counter' underneath anymore, instead calling ```sapp_show_mouse(true)``` + will always show the cursor and ```sapp_show_mouse(false)``` will always + hide it. This is a different behaviour than the underlying Win32 and + macOS functions ShowCursor() and CGDisplaShow/HideCursor() + - The mouse show/hide behaviour can now be tested in the ```events-sapp``` sample + (so far this only works on Windows and macOS). + +- **13-Jul-2020**: + - On macOS and iOS, sokol_app.h and sokol_gfx.h can now be compiled with + ARC (Automatic Reference Counting) **disabled** (previously ARC had to be + enabled). + - Compiling with ARC enabled is still supported but with a little caveat: + if you're compiling sokol_app.h or sokol_gfx.h in ObjC mode (not ObjC++ + mode) *AND* ARC is enabled, then the Xcode version must be more recent + than before (the language feature ```__has_feature(objc_arc_fields)``` + must be supported, which I think has been added in Xcode 10.2, I couldn't + find this mentioned in any Xcode release notes though). Compiling with + ARC disabled should also work on older Xcode versions though. + - Various internal code cleanup things: + - sokol_app.h had the same 'structural cleanup' as sokol_gfx.h in + January, all internal state (including ObjC id's) has been merged into + a single big state structure. Backend specific struct declarations + have been moved closer together in the header, and + backend-specific structures and functions have been named more + consistently for better 'searchability' + - The 'mini GL' loader in the sokol_app.h Win32+WGL backend has been + rewritten to use X-Macros (less redundant lines of code) + - All macOS and iOS code has been revised and cleaned up + - On macOS a workaround for a (what looks like) post-Catalina + NSOpenGLView issue has been added: if the sokol_app.h window doesn't + fit on screen (and was thus 'clamped' by Cocoa) *AND* the + content-size was not set to native Retina resolution, the initial + content size was reported as if it was in Retina resolution. This + caused an empty screen to be rendered in the imgui-sapp demo. The + workaround is to hook into the NSOpenGLView reshape event at which + point the reported content size is correct. + - On macOS and iOS, the various 'view delegate' objects have been + removed, and rendering happens instead in the subclasses of MTKView, + GLKView and NSOpenGLView. + - On macOS and iOS, there's now proper cleanup code in the + applicationWillTerminate callback (although note that on iOS this + function isn't guaranteed to be called, because an application can + also simply be killed by the operating system. + +- **22-Jun-2020**: The X11/GLX backend in sokol_app.h now has (soft-)fullscreen +support, bringing the feature on par with Windows and macOS. Many thanks to +@medvednikov for the PR! + +- **20-Jun-2020**: Some work to better support older DX10-level GPUs in the +sokol_gfx.h D3D11 backend: + - sg_make_shader() now by default compiles HLSL shader code as shader model 4.0 + (previously shader model 5.0 which caused problems with some older + Intel GPUs still in use, see this issue: https://github.com/floooh/sokol/issues/179) + - A new string item ```const char* d3d11_target``` in ```sg_shader_stage_desc``` now allows + to pass in the D3D shader model for compiling shaders. This defaults to + "vs_4_0" for the vertex shader stage and "ps_4_0" for the fragment shader stage. + The minimal DX shader model for use with the sokol_gfx.h D3D11 backend is + shader model 4.0, because that's the first shader model supporting + constant buffers. + - The *sokol-shdc* shader compiler tool has a new output option ```hlsl4``` + to generate HLSL4 source code and shader model 4.0 byte code. + - All embedded D3D shader byte code in the sokol utility headers has been + changed from shader model 5.0 to 4.0 + + If you are using sokol_gfx.h with sokol-shdc, please update both at the same time + to avoid compilation errors caused by the new ```sg_shader_stage_desc.d3d11_target``` + item. The sg_shader_desc initialization code in sokol-shdc has now been made more + robust to prevent similar problems in the future. + +- **14-Jun-2020**: I have added a very simple utility header ```sokol_memtrack.h``` +which allows to track memory allocations in sokol headers (number and overall +size of allocations) by overriding the macros SOKOL_MALLOC, SOKOL_CALLOC and +SOKOL_FREE. Simply include ```sokol_memtrack.h``` before the other sokol +header implementation includes to enable memory tracking in those headers +(but remember that the sokol_memtrack.h implementation must only be included +once in the whole project, so this only works when all other sokol header +implementations are included in the same compilation unit). + +- **06-Jun-2020**: Some optimizations in the sokol_gfx.h GL backend to avoid + redundant GL calls in two areas: in the sg_begin_pass() calls when not + clearing the color- and depth-stencil-attachments, and in sg_apply_bindings() + when binding textures. Everything should behave exactly as before, but if + you notice any problems in those areas, please file a bug. Many thanks to + @edubart for the PRs! + +- **01-Jun-2020**: sokol_app.h now allows to toggle to and from fullscreen +programmatically and to query the current fullscreen state via 2 new +functions: ```sapp_toggle_fullscreen()``` and ```sapp_is_fullscreen()```. +Currently this is only implemented for Windows and macOS (not Linux). +Thanks to @mattiasljungstrom for getting the feature started and providing +the Win32 implementation! + +- **28-May-2020**: a small quality-of-life improvement for C++ coders: when the +sokol headers are included into C++, all public API functions which take a +pointer to a struct now have a C++ overload which instead takes a const-ref. +This allows to move the struct initialization right into the function call +just like in C99. For instance, in C99 one can write: + ```c + sg_buffer buf = sg_make_buffer(&(sg_buffer_desc){ + .size = sizeof(vertices), + .type = SG_BUFFERTYPE_VERTEXBUFFER, + .content = vertices + }); + ``` + In C++ it isn't possible to take the address of an 'adhoc-initialized' + struct like this, but with the new reference-wrapper functions (and C++20 + designated initialization) this should work now: + ```cpp + sg_buffer buf = sg_make_buffer({ + .size = sizeof(vertices), + .type = SG_BUFFERTYPE_VERTEXBUFFER, + .content = vertices + }); + ``` + Many thanks to @garettbass for providing the PR! + + +- **27-May-2020**: a new utility header [sokol_debugtext.h](https://github.com/floooh/sokol/blob/master/util/sokol_debugtext.h) +for rendering simple ASCII text using vintage home computer fonts via sokol_gfx.h + +- **13-May-2020**: a new function in sokol_time.h to round a measured frame time +to common display refresh rates: ```stm_round_to_common_refresh_rate()```. +See the header documentation for the motivation behind this function. + +- **02-May-2020**: sokol_app.h: the 'programmatic quit' behaviour on the +web-platform is now more in line with other platforms: calling +```sapp_quit()``` will invoke the cleanup callback function, perform +platform-specific cleanup (like unregistering JS event handlers), and finally +exit the frame loop. In typical scenarios this isn't very useful (because +usually the user will simply close the tab, which doesn't allow to run +cleanup code), but it's useful for situations where the same +code needs to run repeatedly on a web page. Many thanks to @caiiiycuk +for providing the PR! + +- **30-Apr-2020**: experimental WebGPU backend and a minor breaking change: + - sokol_gfx.h: a new WebGPU backend, expect frequent breakage for a while + because the WebGPU API is still in flux + - a new header sokol_glue.h, with interop helper functions when specific combinations + of sokol headers are used together + - changes in the way sokol_gfx.h is initialized via a new layout of the + sg_desc structure + - sokol_gfx.h: a new ```sg_sampler_type``` enum which is required for + shader creation to tell the WebGPU backend about the sampler data types + (float, signed int, or unsigned int) used in the shader + - sokol_app.h: a handful new functions to query default framebuffer attributes (color- and + depth-buffer pixel formats, and MSAA sample count) + - sokol_app.h: WebGPU device and swapchain initialization (currently only + in the emscripten code path) + - [sokol-shdc](https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md) has + been updated with WebGPU support (currently outputs SPIRV bytecode), and to output the new + ```sg_sampler_type``` enum in ```sg_shader_image_desc``` + - [sokol-samples](https://github.com/floooh/sokol-samples/) has a new set of + backend-specific WebGPU samples, and the other samples have been updated + for the new sokol-gfx initialization + - ```pre-webgpu``` tags have been added to the [sokol](https://github.com/floooh/sokol/releases/tag/pre-webgpu), [sokol-samples](https://github.com/floooh/sokol-samples/releases/tag/pre-webgpu), [sokol-tools](https://github.com/floooh/sokol-tools/releases/tag/pre-webgpu) + and [sokol-tools-bin](https://github.com/floooh/sokol-tools-bin/releases/tag/pre-webgpu) github repositories (in case you need to continue working with + the older versions) + - please see this [blog post](https://floooh.github.io/2020/04/26/sokol-spring-2020-update.html) + for more details + +- **05-Apr-2020**: A bugfix in sokol_gl.h, the (fairly recent) optimization for + merging draw calls contained a bug that could be triggered in an "empty" + sgl_begin/sgl_end pair (with no vertices recorded inbetween). This could + lead to the following draw call being rendered with the wrong uniform data. + +- **30-Jan-2020**: Some cleanup in sokol_gfx.h in the backend implementation code, + internal data structures and documentation comments. The public + API hasn't changed, so the change should be completely invisible + from the outside. + +- **02-Dec-2019**: Initial clipboard support in sokol_app.h for Windows, macOS + and HTML5. This allows to read and write UTF-8 encoded strings from and + to the target platform's shared clipboard. + + A 'real-world' example usage is in the [Visual6502 Remix project](https://github.com/floooh/v6502r). + + Unfortunately clipboard support on the HTML5 platform comes with a lot of + platform-specific caveats which can't be solved in sokol_app.h alone + because of the restrictions the web platform puts on clipboard access and + different behaviours and support levels of the various HTML5 clipboard + APIs. I'm not really happy with the current HTML5 clipboard + implementation. It sorta works, but it sure ain't pretty :) + + Maybe the situation will improve in a few years when all browsers agree + on and support the new [permission-based clipboard + API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API). + + For documentation of the clipboard feature, search for CLIPBOARD SUPPORT + in sokol_app.h + +- **08-Sep-2019**: sokol_gfx.h now supports clamp-to-border texture sampling: + - the enum ```sg_wrap``` has a new member ```SG_WRAP_CLAMP_TO_BORDER``` + - there's a new enum ```sg_border_color``` + - the struct ```sg_image_desc``` has a new member ```sg_border_color border_color``` + - new feature flag in ```sg_features```: ```image_clamp_to_border``` + + Note the following caveats: + + - clamp-to-border is only supported on a subset of platforms, support can + be checked at runtime via ```sg_query_features().image_clamp_to_border``` + (D3D11, desktop-GL and macOS-Metal support clamp-to-border, + all other platforms don't) + - there are three hardwired border colors: transparent-black, + opaque-black and opaque-white (modern 3D APIs have moved away from + a freely programmable border color) + - if clamp-to-border is not supported, sampling will fall back to + clamp-to-edge without a validation warning + + Many thanks to @martincohen for suggesting the feature and providing the initial +D3D11 implementation! + +- **31-Aug-2019**: The header **sokol_gfx_cimgui.h** has been merged into +[**sokol_gfx_imgui.h**](https://github.com/floooh/sokol/blob/master/util/sokol_gfx_imgui.h). +Same idea as merging sokol_cimgui.h into sokol_imgui.h, the implementation +is now "bilingual", and can either be included into a C++ file or into a C file. +When included into a C++ file, the Dear ImGui C++ API will be called directly, +otherwise the C API bindings via cimgui.h + +- **28-Aug-2019**: The header **sokol_cimgui.h** has been merged into +[**sokol_imgui.h**](https://github.com/floooh/sokol/blob/master/util/sokol_imgui.h). +The sokol_cimgui.h header had been created to implement Dear ImGui UIs from +pure C applications, instead of having to fall back to C++ just for the UI +code. However, there was a lot of code duplication between sokol_imgui.h and +sokol_cimgui.h, so that it made more sense to merge the two headers. The C vs +C++ code path will be selected automatically: When the implementation of +sokol_imgui.h is included into a C++ source file, the Dear ImGui C++ API will +be used. Otherwise, when the implementation is included into a C source file, +the C API via cimgui.h + +- **27-Aug-2019**: [**sokol_audio.h**](https://github.com/floooh/sokol/blob/master/sokol_audio.h) + now has an OpenSLES backend for Android. Many thanks to Sepehr Taghdisian (@septag) + for the PR! + +- **26-Aug-2019**: new utility header for text rendering, and fixes in sokol_gl.h: + - a new utility header [**sokol_fontstash.h**](https://github.com/floooh/sokol/blob/master/util/sokol_fontstash.h) + which implements a renderer for [fontstash.h](https://github.com/memononen/fontstash) + on top of sokol_gl.h + - **sokol_gl.h** updates: + - Optimization: If no relevant state between two begin/end pairs has + changed, draw commands will be merged into a single sokol-gfx draw + call. This is especially useful for text- and sprite-rendering (previously, + each begin/end pair would always result in one draw call). + - Bugfix: When calling sgl_disable_texture() the previously active + texture would still remain active which could lead to rendering + artefacts. This has been fixed. + - Feature: It's now possible to provide a custom shader in the + 'desc' argument of *sgl_make_pipeline()*, as long as the shader + is "compatible" with sokol_gl.h, see the sokol_fontstash.h + header for an example. This feature isn't "advertised" in the + sokol_gl.h documentation because it's a bit brittle (for instance + if sokol_gl.h updates uniform block structures, custom shaders + would break), but it may still come in handy in some situations. + +- **20-Aug-2019**: sokol_gfx.h has a couple new query functions to inspect the + default values of resource-creation desc structures: + + ```c + 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_shader_desc sg_query_shader_defaults(const sg_shader_desc* desc); + sg_pipeline_desc sg_query_pipeline_defaults(const sg_pipeline_desc* desc); + sg_pass_desc sg_query_pass_defaults(const sg_pass_desc* desc); + ``` + These functions take a pointer to a resource creation desc struct that + may contain zero-initialized values (to indicate default values) and + return a new struct where the zero-init values have been replaced with + concrete values. This is useful to inspect the actual creation attributes + of a resource. + +- **18-Aug-2019**: + - Pixelformat and runtime capabilities modernization in sokol_gfx.h (breaking changes): + - The list of pixel formats supported in sokol_gfx.h has been modernized, + many new formats are available, and some formats have been removed. The + supported pixel formats are now identical with what WebGPU provides, + minus the SRGB formats (if SRGB conversion is needed it should be done + in the pixel shader) + - The pixel format list is now more "orthogonal": + - one, two or four color components (R, RG, RGBA) + - 8-, 16- or 32-bit component width + - unsigned-normalized (no postfix), signed-normalized (SN postfix), + unsigned-integer (UI postfix) and signed-integer (SI postfix) + and float (F postfix) component types. + - special pixel formats BGRA8 (default render target format on + Metal and D3D11), RGB10A2 and RG11B10F + - DXT compressed formats replaced with BC1 to BC7 (BC1 to BC3 + are identical to the old DXT pixel formats) + - packed 16-bit formats (like RGBA4) have been removed + - packed 24-bit formats (RGB8) have been removed + - Use the new function ```sg_query_pixelformat()``` to get detailed + runtime capability information about a pixelformat (for instance + whether it is supported at all, can be used as render target etc...). + - Use the new function ```sg_query_limits()``` to query "numeric limits" + like maximum texture dimensions for different texture types. + - The enumeration ```sg_feature``` and the function ```sg_query_feature()``` + has been replaced with the new function ```sg_query_features()```, which + returns a struct ```sg_features``` (this contains a bool for each + optional feature). + - The default pixelformat for render target images and pipeline objects + now depends on the backend: + - for GL backends, the default pixelformat stays the same: RGBA8 + - for the Metal and D3D11 backends, the default pixelformat for + render target images is now BGRA8 (the reason is because + MTKView's pixelformat was always BGRA8 but this was "hidden" + through an internal hack, and a BGRA swapchain is more efficient + than RGBA in D3D11/DXGI) + - Because of the above RGBA/BGRA change, you may see pixelformat validation + errors in existing code if the code assumes that a render target image is + always created with a default pixelformat of RGBA8. + - Changes in sokol_app.h: + - The D3D11 backend now creates the DXGI swapchain with BGRA8 pixelformat + (previously: RGBA8), this allows more efficient presentation in some + situations since no format-conversion-blit needs to happen. + +- **18-Jul-2019**: + - sokol_fetch.h has been fixed and can be used again :) + +- **11-Jul-2019**: + - Don't use sokol_fetch.h for now, the current version assumes that + it is possible to obtain the content size of a file from the + HTTP server without downloading the entire file first. Turns out + that's not possible with vanilla HTTP when the web server serves + files compressed (in that case the Content-Length is the _compressed_ + size, yet JS/WASM only has access to the uncompressed data). + Long story short, I need to go back to the drawing board :) + +- **06-Jul-2019**: + - new header [sokol_fetch.h](https://github.com/floooh/sokol/blob/master/sokol_fetch.h) for asynchronously loading data. + - make sure to carefully read the embedded documentation + for making the best use of the header + - two new samples: [simple PNG file loadng with stb_image.h](https://floooh.github.io/sokol-html5/loadpng-sapp.html) and [MPEG1 streaming with pl_mpeg.h](https://floooh.github.io/sokol-html5/plmpeg-sapp.html) + - sokol_gfx.h: increased SG_MAX_SHADERSTAGE_BUFFERS configuration + constant from 4 to 8. + +- **10-Jun-2019**: sokol_app.h now has proper "application quit handling": + - a pending quit can be intercepted, for instance to show a "Really Quit?" dialog box + - application code can now initiate a "soft quit" (interceptable) or + "hard quit" (not interceptable) + - on the web platform, the standard "Leave Site?" dialog box implemented + by browsers can be shown when the user leaves the site + - Android and iOS currently don't have any of those features (since the + operating system may decide to terminate mobile applications at any time + anyway, if similar features are added they will most likely have + similar limitations as the web platform) + For details, search for 'APPLICATION QUIT' in the sokol_app.h documentation + header: https://github.com/floooh/sokol/blob/master/sokol_app.h + + The [imgui-highdpi-sapp](https://github.com/floooh/sokol-samples/tree/master/sapp) + contains sample code for all new quit-related features. + +- **08-Jun-2019**: some new stuff in sokol_app.h: + - the ```sapp_event``` struct has a new field ```bool key_repeat``` + which is true when a keyboard event is a key-repeat (for the + event types ```SAPP_EVENTTYPE_KEY_DOWN``` and ```SAPP_EVENTTYPE_CHAR```). + Many thanks to [Scott Lembcke](https://github.com/slembcke) for + the pull request! + - a new function to poll the internal frame counter: + ```uint64_t sapp_frame_count(void)```, previously the frame counter + was only available via ```sapp_event```. + - also check out the new [event-inspector sample](https://floooh.github.io/sokol-html5/wasm/events-sapp.html) + +- **04-Jun-2019**: All sokol headers now recognize a config-define ```SOKOL_DLL``` + if sokol should be compiled into a DLL (when used with ```SOKOL_IMPL```) + or used as a DLL. On Windows, this will prepend the public function declarations + with ```__declspec(dllexport)``` or ```__declspec(dllimport)```. + +- **31-May-2019**: if you're working with emscripten and fips, please note the + following changes: + + https://github.com/floooh/fips#public-service-announcements + +- **27-May-2019**: some D3D11 updates: + - The shader-cross-compiler can now generate D3D bytecode when + running on Windows, see the [sokol-shdc docs](https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md) for more +details. + - sokol_gfx.h no longer needs to be compiled with a + SOKOL_D3D11_SHADER_COMPILER define to enable shader compilation in the + D3D11 backend. Instead, the D3D shader compiler DLL (d3dcompiler_47.dll) + will be loaded on-demand when the first HLSL shader needs to be compiled. + If an application only uses D3D shader byte code, the compiler DLL won't + be loaded into the process. + +- **24-May-2019** The shader-cross-compiler can now generate Metal byte code +for macOS or iOS when the build is running on macOS. This is enabled +automatically with the fips-integration files in [sokol-tools-bin](https://github.com/floooh/sokol-tools-bin), +see the [sokol-shdc docs](https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md) for more +details. + +- **16-May-2019** two new utility headers: *sokol_cimgui.h* and *sokol_gfx_cimgui.h*, +those are the same as their counterparts sokol_imgui.h and sokol_gfx_imgui.h, but +use the [cimgui](https://github.com/cimgui/cimgui) C-API for Dear ImGui. This +is useful if you don't want to - or cannot - use C++ for creating Dear ImGui UIs. + + Many thanks to @prime31 for contributing those! + + sokol_cimgui.h [is used +here](https://floooh.github.io/sokol-html5/wasm/cimgui-sapp.html), and +sokol_gfx_cimgui.h is used for the [debugging UI +here](https://floooh.github.io/sokol-html5/wasm/sgl-microui-sapp-ui.html) + +- **15-May-2019** there's now an optional shader-cross-compiler solution for +sokol_gfx.h: [see here for details](https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md). +This is "V1.0" with two notable features missing: + + - an include-file feature for GLSL shaders + - compilation to Metal- and D3D-bytecode (currently + only source-code generation is supported) + + The [sokol-app samples](https://floooh.github.io/sokol-html5/) have been + ported to the new shader-cross-compilation, follow the ```src``` and + ```glsl``` links on the specific sample webpages to see the C- and GLSL- + source-code. + +- **02-May-2019** sokol_gfx.h has a new function ```sg_query_backend()```, this +will return an enum ```sg_backend``` identifying the backend sokol-gfx is +currently running on, which is one of the following values: + + - SG_BACKEND_GLCORE33 + - SG_BACKEND_GLES2 + - SG_BACKEND_GLES3 + - SG_BACKEND_D3D11 + - SG_BACKEND_METAL_MACOS + - SG_BACKEND_METAL_IOS + + When compiled with SOKOL_GLES3, sg_query_backend() may return SG_BACKEND_GLES2 + when the runtime platform doesn't support GLES3/WebGL2 and had to fallback + to GLES2/WebGL2. + + When compiled with SOKOL_METAL, sg_query_backend() will return SG_BACKEND_METAL_MACOS + when the compile target is macOS, and SG_BACKEND_METAL_IOS when the target is iOS. + +- **26-Apr-2019** Small but breaking change in **sokol_gfx.h** how the vertex +layout definition in sg_pipeline_desc works: + + Vertex component names and semantics (needed by the GLES2 and D3D11 backends) have moved from ```sg_pipeline_desc``` into ```sg_shader_desc```. + + This may seem like a rather pointless small detail to change, especially + for breaking existing code, but the whole thing will make a bit more + sense when the new shader-cross-compiler will be integrated which I'm + currently working on (here: https://github.com/floooh/sokol-tools). + + While working on getting reflection data out of the shaders (e.g. what + uniform blocks and textures the shader uses), it occurred to me that + vertex-attribute-names and -semantics are actually part of the reflection + info and belong to the shader, not to the vertex layout in the pipeline + object (which only describes how the incoming vertex data maps to + vertex-component **slots**. Instead of (optionally) mapping this + association through a name, the pipeline's vertex layout is now always + strictly defined in terms of numeric 'bind slots' for **all** sokol_gfx.h + backends. For 3D APIs where the vertex component slot isn't explicitly + defined in the shader language (GLES2/WebGL, D3D11, and optionally + GLES3/GL), the shader merely offers a lookup table how vertex-layout + slot-indices map to names/semantics (and the underlying 3D API than maps + those names back to slot indices, which shows that Metal and GL made the + right choice defining the slots right in the shader). + + Here's how the code changes (taken from the triangle-sapp.c sample): + + **OLD**: + ```c + /* create a shader */ + sg_shader shd = sg_make_shader(&(sg_shader_desc){ + .vs.source = vs_src, + .fs.source = fs_src, + }); + + /* create a pipeline object (default render states are fine for triangle) */ + pip = sg_make_pipeline(&(sg_pipeline_desc){ + /* if the vertex layout doesn't have gaps, don't need to provide strides and offsets */ + .shader = shd, + .layout = { + .attrs = { + [0] = { .name="position", .sem_name="POS", .format=SG_VERTEXFORMAT_FLOAT3 }, + [1] = { .name="color0", .sem_name="COLOR", .format=SG_VERTEXFORMAT_FLOAT4 } + } + }, + }); + ``` + + **NEW**: + ```c + /* create a shader */ + sg_shader shd = sg_make_shader(&(sg_shader_desc){ + .attrs = { + [0] = { .name="position", .sem_name="POS" }, + [1] = { .name="color0", .sem_name="COLOR" } + }, + .vs.source = vs_src, + .fs.source = fs_src, + }); + + /* create a pipeline object (default render states are fine for triangle) */ + pip = sg_make_pipeline(&(sg_pipeline_desc){ + /* if the vertex layout doesn't have gaps, don't need to provide strides and offsets */ + .shader = shd, + .layout = { + .attrs = { + [0].format=SG_VERTEXFORMAT_FLOAT3, + [1].format=SG_VERTEXFORMAT_FLOAT4 + } + }, + }); + ``` + + ```sg_shader_desc``` has a new embedded struct ```attrs``` which + contains a vertex attribute _name_ (for GLES2/WebGL) and + _sem_name/sem_index_ (for D3D11). For the Metal backend this struct is + ignored completely, and for GLES3/GL it is optional, and not required + when the vertex shader inputs are annotated with ```layout(location=N)```. + + The remaining attribute description members in ```sg_pipeline_desc``` are: + - **.format**: the format of input vertex data (this can be different + from the vertex shader's inputs when data is extended during + vertex fetch (e.g. input can be vec3 while the vertex shader + expects vec4) + - **.offset**: optional offset of the vertex component data (not needed + when the input vertex has no gaps between the components) + - **.buffer**: the vertex buffer bind slot if the vertex data is coming + from different buffers + + Also check out the various samples: + + - for GLSL (explicit slots via ```layout(location=N)```): https://github.com/floooh/sokol-samples/tree/master/glfw + - for D3D11 (semantic names/indices): https://github.com/floooh/sokol-samples/tree/master/d3d11 + - for GLES2: (vertex attribute names): https://github.com/floooh/sokol-samples/tree/master/html5 + - for Metal: (explicit slots): https://github.com/floooh/sokol-samples/tree/master/metal + - ...and all of the above combined: https://github.com/floooh/sokol-samples/tree/master/sapp + +- **19-Apr-2019** I have replaced the rather inflexible render-state handling +in **sokol_gl.h** with a *pipeline stack* (like the GL matrix stack, but with +pipeline-state-objects), along with a couple of other minor API tweaks. + + These are the new pipeline-stack functions: + ```c + sgl_pipeline sgl_make_pipeline(const sg_pipeline_desc* desc); + void sgl_destroy_pipeline(sgl_pipeline pip); + void sgl_default_pipeline(void); + void sgl_load_pipeline(sgl_pipeline pip); + void sgl_push_pipeline(void); + void sgl_pop_pipeline(void); + ``` + + A pipeline object is created just like in sokol_gfx.h, but without shaders, + vertex layout, pixel formats, primitive-type and sample count (these details + are filled in by the ```sgl_make_pipeline()``` wrapper function. For instance + to create a pipeline object for additive transparency: + + ```c + sgl_pipeline additive_pip = sgl_make_pipeline(&(sg_pipeline_desc){ + .blend = { + .enabled = true, + .src_factor_rgb = SG_BLENDFACTOR_ONE, + .dst_factor_rgb = SG_BLENDFACTOR_ONE + } + }); + ``` + + And to render with this, simply call ```sgl_load_pipeline()```: + + ```c + sgl_load_pipeline(additive_pip); + sgl_begin_triangles(); + ... + sgl_end(); + ``` + + Or to preserve and restore the previously active pipeline state: + + ```c + sgl_push_pipeline(); + sgl_load_pipeline(additive_pip); + sgl_begin_quads(); + ... + sgl_end(); + sgl_pop_pipeline(); + ``` + + You can also load the 'default pipeline' explicitly on the top of the + pipeline stack with ```sgl_default_pipeline()```. + + The other API change is: + + - ```sgl_state_texture(bool b)``` has been replaced with ```sgl_enable_texture()``` + and ```sgl_disable_texture()``` + + The code samples have been updated accordingly: + + - [sgl-sapp.c](https://github.com/floooh/sokol-samples/blob/master/sapp/sgl-sapp.c) + - [sgl-lines-sapp.c](https://github.com/floooh/sokol-samples/blob/master/sapp/sgl-lines-sapp.c) + - [sgl-microui-sapp.c](https://github.com/floooh/sokol-samples/blob/master/sapp/sgl-microui-sapp.c) + +- **01-Apr-2019** (not an April Fool's joke): There's a new **sokol_gl.h** +util header which implements an 'OpenGL-1.x-in-spirit' rendering API on top +of sokol_gfx.h (vertex specification via begin/end, and a matrix stack). This is +only a small subset of OpenGL 1.x, mainly intended for debug-visualization or +simple tasks like 2D UI rendering. As always, sample code is in the +[sokol-samples](https://github.com/floooh/sokol-samples) project. + +- **15-Mar-2019**: various Dear ImGui related changes: + - there's a new utility header sokol_imgui.h with a simple drop-in + renderer for Dear ImGui on top of sokol_gfx.h and sokol_app.h + (sokol_app.h is optional, and only used for input handling) + - the sokol_gfx_imgui.h debug inspection header no longer + depends on internal data structures and functions of sokol_gfx.h, as such + it is now a normal *utility header* and has been moved to the *utils* + directory + - the implementation macro for sokol_gfx_imgui.h has been changed + from SOKOL_IMPL to SOKOL_GFX_IMGUI_IMPL (so when you suddenly get + unresolved linker errors, that's the reason) + - all headers now have two preprocessor defines for the declaration + and implementation (for instance in sokol_gfx.h: SOKOL_GFX_INCLUDED + and SOKOL_GFX_IMPL_INCLUDED) these are checked in the utility-headers + to provide useful error message when dependent headers are missing + +- **05-Mar-2019**: sokol_gfx.h now has a 'trace hook' API, and I have started +implementing optional debug-inspection-UI headers on top of Dear ImGui: + - sokol_gfx.h has a new function *sg_install_trace_hooks()*, this allows + you to install a callback function for each public sokol_gfx.h function + (and a couple of error callbacks). For more details, search for "TRACE HOOKS" + in sokol_gfx.h + - when creating sokol_gfx.h resources, you can now set a 'debug label' + in the desc structure, this is ignored by sokol_gfx.h itself, but is + useful for debuggers or profilers hooking in via the new trace hooks + - likewise, two new functions *sg_push_debug_group()* and *sg_pop_debug_group()* + can be used to group related drawing functions under a name, this + is also ignored by sokol_gfx.h itself and only useful when hooking + into the API calls + - I have started a new 'subproject' in the 'imgui' directory, this will + contain a slowly growing set of optional debug-inspection-UI headers + which allow to peek under the hood of the Sokol headers. The UIs are + implemented with [Dear ImGui](https://github.com/ocornut/imgui). Again, + see the README in the 'imgui' directory and the headers in there + for details, and check out the live demos on the [Sokol Sample Webpage](https://floooh.github.io/sokol-html5/) + (click on the little UI buttons in the top right corner of each thumbnail) + +- **21-Feb-2019**: sokol_app.h and sokol_audio.h now have an alternative +set of callbacks with user_data arguments. This is useful if you don't +want or cannot store your own application state in global variables. +See the header documentation in sokol_app.h and sokol_audio.h for details, +and check out the samples *sapp/noentry-sapp.c* and *sapp/modplay-sapp.c* +in https://github.com/floooh/sokol-samples + +- **19-Feb-2019**: sokol_app.h now has an alternative mode where it doesn't +"hijack" the platform's main() function. Search for SOKOL_NO_ENTRY in +sokol_app.h for details and documentation. + +- **26-Jan-2019**: sokol_app.h now has an Android backend contributed by + [Gustav Olsson](https://github.com/gustavolsson)! + See the [sokol-samples readme](https://github.com/floooh/sokol-samples/blob/master/README.md) + for build instructions. + +- **21-Jan-2019**: sokol_gfx.h - pool-slot-generation-counters and a dummy backend: + - Resource pool slots now have a generation-counter for the resource-id + unique-tag, instead of a single counter for the whole pool. This allows + to create many more unique handles. + - sokol_gfx.h now has a dummy backend, activated by defining SOKOL_DUMMY_BACKEND + (instead of SOKOL_METAL, SOKOL_D3D11, ...), this allows to write + 'headless' tests (and there's now a sokol-gfx-test in the sokol-samples + repository which mainly tests the resource pool system) + +- **12-Jan-2019**: sokol_gfx.h - setting the pipeline state and resource +bindings now happens in separate calls, specifically: + - *sg_apply_draw_state()* has been replaced with *sg_apply_pipeline()* and + *sg_apply_bindings()* + - the *sg_draw_state* struct has been replaced with *sg_bindings* + - *sg_apply_uniform_block()* has been renamed to *sg_apply_uniforms()* + +All existing code will still work. See [this blog +post](https://floooh.github.io/2019/01/12/sokol-apply-pipeline.html) for +details. + +- **29-Oct-2018**: + - sokol_gfx.h has a new function **sg_append_buffer()** which allows to + append new data to a buffer multiple times per frame and interleave this + with draw calls. This basically implements the + D3D11_MAP_WRITE_NO_OVERWRITE update strategy for buffer objects. For + example usage, see the updated Dear ImGui samples in the [sokol_samples + repo](https://github.com/floooh/sokol-samples) + - the GL state cache in sokol_gfx.h handles buffers bindings in a + more robust way, previously it might have happened that the + buffer binding gets confused when creating buffers or updating + buffer contents in the render loop + +- **17-Oct-2018**: sokol_args.h added + +- **26-Sep-2018**: sokol_audio.h ready for prime time :) + +- **11-May-2018**: sokol_gfx.h now autodetects iOS vs MacOS in the Metal +backend during compilation using the standard define TARGET_OS_IPHONE defined +in the TargetConditionals.h system header, please replace the old +backend-selection defines SOKOL_METAL_MACOS and SOKOL_METAL_IOS with +**SOKOL_METAL** + +- **20-Apr-2018**: 3 new context-switching functions have been added +to make it possible to use sokol together with applications that +use multiple GL contexts. On D3D11 and Metal, the functions are currently +empty. See the new section ```WORKING WITH CONTEXTS``` in the sokol_gfx.h +header documentation, and the new sample [multiwindow-glfw](https://github.com/floooh/sokol-samples/blob/master/glfw/multiwindow-glfw.c) + +- **31-Jan-2018**: The vertex layout declaration in sg\_pipeline\_desc had +some fairly subtle flaws and has been changed to work like Metal or Vulkan. +The gist is that the vertex-buffer-layout properties (vertex stride, +vertex-step-rate and -step-function for instancing) is now defined in a +separate array from the vertex attributes. This removes some brittle backend +code which tries to guess the right vertex attribute slot if no attribute +names are given, and which was wrong for shader-code-generation pipelines +which reorder the vertex attributes (I stumbled over this when porting the +Oryol Gfx module over to sokol-gfx). Some code samples: + +```c +// a complete vertex layout declaration with a single input buffer +// with two vertex attributes +sg_pipeline pip = sg_make_pipeline(&(sg_pipeline_desc){ + .layout = { + .buffers = { + [0] = { + .stride = 20, + .step_func = SG_VERTEXSTEP_PER_VERTEX, + .step_rate = 1 + } + }, + .attrs = { + [0] = { + .name = "pos", + .offset = 0, + .format = SG_VERTEXFORMAT_FLOAT3, + .buffer_index = 0 + }, + [1] = { + .name = "uv", + .offset = 12, + .format = SG_VERTEXFORMAT_FLOAT2, + .buffer_index = 0 + } + } + }, + ... +}); + +// if the vertex layout has no gaps, we can get rid of the strides and offsets: +sg_pipeline pip = sg_make_pipeline(&(sg_pipeline_desc){ + .layout = { + .buffers = { + [0] = { + .step_func = SG_VERTEXSTEP_PER_VERTEX, + .step_rate=1 + } + }, + .attrs = { + [0] = { + .name = "pos", + .format = SG_VERTEXFORMAT_FLOAT3, + .buffer_index = 0 + }, + [1] = { + .name = "uv", + .format = SG_VERTEXFORMAT_FLOAT2, + .buffer_index = 0 + } + } + }, + ... +}); + +// we can also get rid of the other default-values, which leaves buffers[0] +// as all-defaults, so it can disappear completely: +sg_pipeline pip = sg_make_pipeline(&(sg_pipeline_desc){ + .layout = { + .attrs = { + [0] = { .name = "pos", .format = SG_VERTEXFORMAT_FLOAT3 }, + [1] = { .name = "uv", .format = SG_VERTEXFORMAT_FLOAT2 } + } + }, + ... +}); + +// and finally on GL3.3 and Metal and we don't need the attribute names +// (on D3D11, a semantic name and index must be provided though) +sg_pipeline pip = sg_make_pipeline(&(sg_pipeline_desc){ + .layout = { + .attrs = { + [0] = { .format = SG_VERTEXFORMAT_FLOAT3 }, + [1] = { .format = SG_VERTEXFORMAT_FLOAT2 } + } + }, + ... +}); +``` + +Please check the sample code in https://github.com/floooh/sokol-samples for +more examples! + +Enjoy! diff --git a/source/engine/thirdparty/sokol/LICENSE b/source/engine/thirdparty/sokol/LICENSE new file mode 100644 index 0000000..efdc02e --- /dev/null +++ b/source/engine/thirdparty/sokol/LICENSE @@ -0,0 +1,22 @@ +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. diff --git a/source/engine/thirdparty/sokol/README.md b/source/engine/thirdparty/sokol/README.md new file mode 100644 index 0000000..c6ebd17 --- /dev/null +++ b/source/engine/thirdparty/sokol/README.md @@ -0,0 +1,406 @@ +# Sokol + +Simple +[STB-style](https://github.com/nothings/stb/blob/master/docs/stb_howto.txt) +cross-platform libraries for C and C++, written in C. + +[**See what's new**](https://github.com/floooh/sokol/blob/master/CHANGELOG.md) (**29-Feb-2024**: **BREAKING CHANGES** 'unified render pass' +cleanup in sokol_gfx.h) + +[![Build](/../../actions/workflows/main.yml/badge.svg)](/../../actions/workflows/main.yml) [![Bindings](/../../actions/workflows/gen_bindings.yml/badge.svg)](/../../actions/workflows/gen_bindings.yml) [![build](https://github.com/floooh/sokol-zig/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-zig/actions/workflows/main.yml) [![build](https://github.com/floooh/sokol-nim/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-nim/actions/workflows/main.yml) [![Odin](https://github.com/floooh/sokol-odin/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-odin/actions/workflows/main.yml)[![Rust](https://github.com/floooh/sokol-rust/actions/workflows/main.yml/badge.svg)](https://github.com/floooh/sokol-rust/actions/workflows/main.yml) + +## Examples and Related Projects + +- [Live Samples](https://floooh.github.io/sokol-html5/index.html) via WASM ([source](https://github.com/floooh/sokol-samples)) + +- [Doom Shareware](https://floooh.github.io/doom-sokol/) ported to the Sokol headers ([source](https://github.com/floooh/doom-sokol)) + +- [sokol_gp.h](https://github.com/edubart/sokol_gp) a 2D shape drawing library on top of sokol_gfx.h + +- [LearnOpenGL examples ported to sokol-gfx](https://zeromake.github.io/learnopengl-examples/) ([git repo](https://github.com/zeromake/learnopengl-examples)) + +- [Dear ImGui starterkit](https://github.com/floooh/cimgui-sokol-starterkit) a self-contained starterkit for writing Dear ImGui apps in C. + +- [qoiview](https://github.com/floooh/qoiview) a basic viewer for the new QOI image file format + +- [Tiny 8-bit emulators](https://floooh.github.io/tiny8bit/) + +- A 'single-file' [Pacman clone in C99](https://github.com/floooh/pacman.c/), also available in [Zig](https://github.com/floooh/pacman.zig/) + +- [MEG-4](https://bztsrc.gitlab.io/meg4) a virtual fantasy console emulator in C89, ported to sokol + +- A [Minigolf game](https://mgerdes.github.io/minigolf.html) ([source](https://github.com/mgerdes/minigolf)). + +- ['Dealer's Dungeon'](https://dealers-dungeon.com/demo/) ([lower graphics quality](https://dealers-dungeon.com/demo/?q=3), +[source](https://github.com/bqqbarbhg/spear)) + +- [Command line tools](https://github.com/floooh/sokol-tools) (shader compiler) + +- [How to build without a build system](https://github.com/floooh/sokol-samples#how-to-build-without-a-build-system): +useful details for integrating the Sokol headers into your own project with your favourite C/C++ build system + +## Core libraries + +- [**sokol\_gfx.h**](https://github.com/floooh/sokol/blob/master/sokol_gfx.h): 3D-API wrapper (GL/GLES3/WebGL2 + Metal + D3D11 + WebGPU) +- [**sokol\_app.h**](https://github.com/floooh/sokol/blob/master/sokol_app.h): app framework wrapper (entry + window + 3D-context + input) +- [**sokol\_time.h**](https://github.com/floooh/sokol/blob/master/sokol_time.h): time measurement +- [**sokol\_audio.h**](https://github.com/floooh/sokol/blob/master/sokol_audio.h): minimal buffer-streaming audio playback +- [**sokol\_fetch.h**](https://github.com/floooh/sokol/blob/master/sokol_fetch.h): asynchronous data streaming from HTTP and local filesystem +- [**sokol\_args.h**](https://github.com/floooh/sokol/blob/master/sokol_args.h): unified cmdline/URL arg parser for web and native apps +- [**sokol\_log.h**](https://github.com/floooh/sokol/blob/master/sokol_log.h): provides a standard logging callback for the other sokol headers + +## Utility libraries + +- [**sokol\_imgui.h**](https://github.com/floooh/sokol/blob/master/util/sokol_imgui.h): sokol_gfx.h rendering backend for [Dear ImGui](https://github.com/ocornut/imgui) +- [**sokol\_nuklear.h**](https://github.com/floooh/sokol/blob/master/util/sokol_nuklear.h): sokol_gfx.h rendering backend for [Nuklear](https://github.com/Immediate-Mode-UI/Nuklear) +- [**sokol\_gl.h**](https://github.com/floooh/sokol/blob/master/util/sokol_gl.h): OpenGL 1.x style immediate-mode rendering API on top of sokol_gfx.h +- [**sokol\_fontstash.h**](https://github.com/floooh/sokol/blob/master/util/sokol_fontstash.h): sokol_gl.h rendering backend for [fontstash](https://github.com/memononen/fontstash) +- [**sokol\_gfx\_imgui.h**](https://github.com/floooh/sokol/blob/master/util/sokol_gfx_imgui.h): debug-inspection UI for sokol_gfx.h (implemented with Dear ImGui) +- [**sokol\_debugtext.h**](https://github.com/floooh/sokol/blob/master/util/sokol_debugtext.h): a simple text renderer using vintage home computer fonts +- [**sokol\_memtrack.h**](https://github.com/floooh/sokol/blob/master/util/sokol_memtrack.h): easily track memory allocations in sokol headers +- [**sokol\_shape.h**](https://github.com/floooh/sokol/blob/master/util/sokol_shape.h): generate simple shapes and plug them into sokol-gfx resource creation structs +- [**sokol\_color.h**](https://github.com/floooh/sokol/blob/master/util/sokol_color.h): X11 style color constants and functions for creating sg_color objects +- [**sokol\_spine.h**](https://github.com/floooh/sokol/blob/master/util/sokol_spine.h): a sokol-style wrapper around the Spine C runtime (http://en.esotericsoftware.com/spine-in-depth) + +## 'Official' Language Bindings + +These are automatically updated on changes to the C headers: + +- [sokol-zig](https://github.com/floooh/sokol-zig) +- [sokol-odin](https://github.com/floooh/sokol-odin) +- [sokol-nim](https://github.com/floooh/sokol-nim) +- [sokol-rust](https://github.com/floooh/sokol-rust) + +## Notes + +WebAssembly is a 'first-class citizen', one important motivation for the +Sokol headers is to provide a collection of cross-platform APIs with a +minimal footprint on the web platform while still being useful. + +The core headers are standalone and can be used independently from each other. + +### Why C: + +- easier integration with other languages +- easier integration into other projects +- adds only minimal size overhead to executables + +A blog post with more background info: [A Tour of sokol_gfx.h](http://floooh.github.io/2017/07/29/sokol-gfx-tour.html) + +# sokol_gfx.h: + +- simple, modern wrapper around GLES3/WebGL2, GL3.3, D3D11, Metal, and WebGPU +- buffers, images, shaders, pipeline-state-objects and render-passes +- does *not* handle window creation or 3D API context initialization +- does *not* provide shader dialect cross-translation (**BUT** there's now an 'official' shader-cross-compiler solution which +seamlessly integrates with sokol_gfx.h and IDEs: [see here for details](https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md) + +# sokol_app.h + +A minimal cross-platform application-wrapper library: + +- unified application entry +- single window or canvas for 3D rendering +- 3D context initialization +- event-based keyboard, mouse and touch input +- supported platforms: Win32, MacOS, Linux (X11), iOS, WASM, Android, UWP +- supported 3D-APIs: GL3.3 (GLX/WGL), Metal, D3D11, GLES3/WebGL2 + +The vanilla Hello-Triangle using sokol_gfx.h, sokol_app.h and the +sokol-shdc shader compiler (shader code not shown): + +```c +#include "sokol_app.h" +#include "sokol_gfx.h" +#include "sokol_log.h" +#include "sokol_glue.h" +#include "triangle-sapp.glsl.h" + +static struct { + sg_pipeline pip; + sg_bindings bind; + sg_pass_action pass_action; +} state; + +static void init(void) { + sg_setup(&(sg_desc){ + .environment = sglue_environment(), + .logger.func = slog_func, + }); + + float vertices[] = { + 0.0f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, + 0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, + -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f + }; + state.bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc){ + .data = SG_RANGE(vertices), + }); + + state.pip = sg_make_pipeline(&(sg_pipeline_desc){ + .shader = sg_make_shader(triangle_shader_desc(sg_query_backend())), + .layout = { + .attrs = { + [ATTR_vs_position].format = SG_VERTEXFORMAT_FLOAT3, + [ATTR_vs_color0].format = SG_VERTEXFORMAT_FLOAT4 + } + }, + }); + + state.pass_action = (sg_pass_action) { + .colors[0] = { .load_action=SG_LOADACTION_CLEAR, .clear_value={0.0f, 0.0f, 0.0f, 1.0f } } + }; +} + +void frame(void) { + sg_begin_pass(&(sg_pass){ .action = state.pass_action, .swapchain = sglue_swapchain() }); + sg_apply_pipeline(state.pip); + sg_apply_bindings(&state.bind); + sg_draw(0, 3, 1); + sg_end_pass(); + sg_commit(); +} + +void cleanup(void) { + sg_shutdown(); +} + +sapp_desc sokol_main(int argc, char* argv[]) { + (void)argc; (void)argv; + return (sapp_desc){ + .init_cb = init, + .frame_cb = frame, + .cleanup_cb = cleanup, + .width = 640, + .height = 480, + .window_title = "Triangle", + .icon.sokol_default = true, + .logger.func = slog_func, + }; +} +``` + +# sokol_audio.h + +A minimal audio-streaming API: + +- you provide a mono- or stereo-stream of 32-bit float samples which sokol_audio.h forwards into platform-specific backends +- two ways to provide the data: + 1. directly fill backend audio buffer from your callback function running in the audio thread + 2. alternatively push small packets of audio data from your main loop, + or a separate thread created by you +- platform backends: + - Windows: WASAPI + - macOS/iOS: CoreAudio + - Linux: ALSA + - emscripten: WebAudio + ScriptProcessorNode (doesn't use the emscripten-provided OpenAL or SDL Audio wrappers) + +A simple mono square-wave generator using the callback model: + +```c +// the sample callback, running in audio thread +static void stream_cb(float* buffer, int num_frames, int num_channels) { + assert(1 == num_channels); + static uint32_t count = 0; + for (int i = 0; i < num_frames; i++) { + buffer[i] = (count++ & (1<<3)) ? 0.5f : -0.5f; + } +} + +int main() { + // init sokol-audio with default params + saudio_setup(&(saudio_desc){ + .stream_cb = stream_cb, + .logger.func = slog_func, + }); + + // run main loop + ... + + // shutdown sokol-audio + saudio_shutdown(); + return 0; +``` + +The same code using the push-model + +```c +#define BUF_SIZE (32) +int main() { + // init sokol-audio with default params, no callback + saudio_setup(&(saudio_desc){ + .logger.func = slog_func, + }); + assert(saudio_channels() == 1); + + // a small intermediate buffer so we don't need to push + // individual samples, which would be quite inefficient + float buf[BUF_SIZE]; + int buf_pos = 0; + uint32_t count = 0; + + // push samples from main loop + bool done = false; + while (!done) { + // generate and push audio samples... + int num_frames = saudio_expect(); + for (int i = 0; i < num_frames; i++) { + // simple square wave generator + buf[buf_pos++] = (count++ & (1<<3)) ? 0.5f : -0.5f; + if (buf_pos == BUF_SIZE) { + buf_pos = 0; + saudio_push(buf, BUF_SIZE); + } + } + // handle other per-frame stuff... + ... + } + + // shutdown sokol-audio + saudio_shutdown(); + return 0; +} +``` + +# sokol_fetch.h + +Load entire files, or stream data asynchronously over HTTP (emscripten/wasm) +or the local filesystem (all native platforms). + +Simple C99 example loading a file into a static buffer: + +```c +#include "sokol_fetch.h" +#include "sokol_log.h" + +static void response_callback(const sfetch_response*); + +#define MAX_FILE_SIZE (1024*1024) +static uint8_t buffer[MAX_FILE_SIZE]; + +// application init +static void init(void) { + ... + // setup sokol-fetch with default config: + sfetch_setup(&(sfetch_desc_t){ .logger.func = slog_func }); + + // start loading a file into a statically allocated buffer: + sfetch_send(&(sfetch_request_t){ + .path = "hello_world.txt", + .callback = response_callback + .buffer_ptr = buffer, + .buffer_size = sizeof(buffer) + }); +} + +// per frame... +static void frame(void) { + ... + // need to call sfetch_dowork() once per frame to 'turn the gears': + sfetch_dowork(); + ... +} + +// the response callback is where the interesting stuff happens: +static void response_callback(const sfetch_response_t* response) { + if (response->fetched) { + // data has been loaded into the provided buffer, do something + // with the data... + const void* data = response->buffer_ptr; + uint64_t data_size = response->fetched_size; + } + // the finished flag is set both on success and failure + if (response->failed) { + // oops, something went wrong + switch (response->error_code) { + SFETCH_ERROR_FILE_NOT_FOUND: ... + SFETCH_ERROR_BUFFER_TOO_SMALL: ... + ... + } + } +} + +// application shutdown +static void shutdown(void) { + ... + sfetch_shutdown(); + ... +} +``` + +# sokol_time.h: + +Simple cross-platform time measurement: + +```c +#include "sokol_time.h" +... +/* initialize sokol_time */ +stm_setup(); + +/* take start timestamp */ +uint64_t start = stm_now(); + +...some code to measure... + +/* compute elapsed time */ +uint64_t elapsed = stm_since(start); + +/* convert to time units */ +double seconds = stm_sec(elapsed); +double milliseconds = stm_ms(elapsed); +double microseconds = stm_us(elapsed); +double nanoseconds = stm_ns(elapsed); + +/* difference between 2 time stamps */ +uint64_t start = stm_now(); +... +uint64_t end = stm_now(); +uint64_t elapsed = stm_diff(end, start); + +/* compute a 'lap time' (e.g. for fps) */ +uint64_t last_time = 0; +while (!done) { + ...render something... + double frame_time_ms = stm_ms(stm_laptime(&last_time)); +} +``` + +# sokol_args.h + +Unified argument parsing for web and native apps. Uses argc/argv on native +platforms and the URL query string on the web. + +Example URL with one arg: + +https://floooh.github.io/tiny8bit/kc85.html?type=kc85_4 + +The same as command line app: + +> kc85 type=kc85_4 + +Parsed like this: + +```c +#include "sokol_args.h" + +int main(int argc, char* argv[]) { + sargs_setup(&(sargs_desc){ .argc=argc, .argv=argv }); + if (sargs_exists("type")) { + if (sargs_equals("type", "kc85_4")) { + // start as KC85/4 + } + else if (sargs_equals("type", "kc85_3")) { + // start as KC85/3 + } + else { + // start as KC85/2 + } + } + sargs_shutdown(); + return 0; +} +``` + +See the sokol_args.h header for a more complete documentation, and the [Tiny +Emulators](https://floooh.github.io/tiny8bit/) for more interesting usage examples. diff --git a/source/engine/thirdparty/sokol/bindgen/.gitignore b/source/engine/thirdparty/sokol/bindgen/.gitignore new file mode 100644 index 0000000..4528e59 --- /dev/null +++ b/source/engine/thirdparty/sokol/bindgen/.gitignore @@ -0,0 +1,8 @@ +*.json +*.nim +*.zig +__pycache__/ +sokol-nim/ +sokol-zig/ +sokol-odin/ +sokol-rust/ diff --git a/source/engine/thirdparty/sokol/bindgen/README.md b/source/engine/thirdparty/sokol/bindgen/README.md new file mode 100644 index 0000000..aef1ec1 --- /dev/null +++ b/source/engine/thirdparty/sokol/bindgen/README.md @@ -0,0 +1,40 @@ +## Language Binding Generation Scripts + +> REMINDER: we can pass `-fparse-all-comments` to the clang ast-dump command line which adds the following node types to the ast-dump.json: FullComment, ParagraphComment, TextComment. This might allow us to preserve comments in the language bindings (might be useful as part of a bigger change to make sokol header comments autodoc and Intellisense-friendly) + +### Zig + +First make sure that clang and python3 are in the path: + +``` +> clang --version +> python3 --version +``` + +...on Windows I simply install those with scoop: + +``` +> scoop install llvm +> scoop install python +``` + +To update the Zig bindings: + +``` +> cd sokol/bindgen +> git clone https://github.com/floooh/sokol-zig +> git clone https://github.com/floooh/sokol-nim +> git clone https://github.com/floooh/sokol-odin +> git clone https://github.com/floooh/sokol-rust +> python3 gen_all.py +``` + +Test and run samples: + +``` +> cd sokol/bindgen/sokol-zig +> zig build run-clear +> zig build run-triangle +> zig build run-cube +... +``` diff --git a/source/engine/thirdparty/sokol/bindgen/gen_all.py b/source/engine/thirdparty/sokol/bindgen/gen_all.py new file mode 100644 index 0000000..24389e7 --- /dev/null +++ b/source/engine/thirdparty/sokol/bindgen/gen_all.py @@ -0,0 +1,37 @@ +import os, gen_nim, gen_zig, gen_odin, gen_rust + +tasks = [ + [ '../sokol_log.h', 'slog_', [] ], + [ '../sokol_gfx.h', 'sg_', [] ], + [ '../sokol_app.h', 'sapp_', [] ], + [ '../sokol_glue.h', 'sglue_', ['sg_'] ], + [ '../sokol_time.h', 'stm_', [] ], + [ '../sokol_audio.h', 'saudio_', [] ], + [ '../util/sokol_gl.h', 'sgl_', ['sg_'] ], + [ '../util/sokol_debugtext.h', 'sdtx_', ['sg_'] ], + [ '../util/sokol_shape.h', 'sshape_', ['sg_'] ], +] + +# Odin +gen_odin.prepare() +for task in tasks: + [c_header_path, main_prefix, dep_prefixes] = task + gen_odin.gen(c_header_path, main_prefix, dep_prefixes) + +# Nim +gen_nim.prepare() +for task in tasks: + [c_header_path, main_prefix, dep_prefixes] = task + gen_nim.gen(c_header_path, main_prefix, dep_prefixes) + +# Zig +gen_zig.prepare() +for task in tasks: + [c_header_path, main_prefix, dep_prefixes] = task + gen_zig.gen(c_header_path, main_prefix, dep_prefixes) + +# Rust +gen_rust.prepare() +for task in tasks: + [c_header_path, main_prefix, dep_prefixes] = task + gen_rust.gen(c_header_path, main_prefix, dep_prefixes) diff --git a/source/engine/thirdparty/sokol/bindgen/gen_ir.py b/source/engine/thirdparty/sokol/bindgen/gen_ir.py new file mode 100644 index 0000000..0089ae2 --- /dev/null +++ b/source/engine/thirdparty/sokol/bindgen/gen_ir.py @@ -0,0 +1,124 @@ +#------------------------------------------------------------------------------- +# Generate an intermediate representation of a clang AST dump. +#------------------------------------------------------------------------------- +import json, sys, subprocess + +def is_api_decl(decl, prefix): + if 'name' in decl: + return decl['name'].startswith(prefix) + elif decl['kind'] == 'EnumDecl': + # an anonymous enum, check if the items start with the prefix + return decl['inner'][0]['name'].lower().startswith(prefix) + else: + return False + +def is_dep_decl(decl, dep_prefixes): + for prefix in dep_prefixes: + if is_api_decl(decl, prefix): + return True + return False + +def dep_prefix(decl, dep_prefixes): + for prefix in dep_prefixes: + if is_api_decl(decl, prefix): + return prefix + return None + +def filter_types(str): + return str.replace('_Bool', 'bool') + +def parse_struct(decl): + outp = {} + outp['kind'] = 'struct' + outp['name'] = decl['name'] + outp['fields'] = [] + for item_decl in decl['inner']: + if item_decl['kind'] != 'FieldDecl': + sys.exit(f"ERROR: Structs must only contain simple fields ({decl['name']})") + item = {} + if 'name' in item_decl: + item['name'] = item_decl['name'] + item['type'] = filter_types(item_decl['type']['qualType']) + outp['fields'].append(item) + return outp + +def parse_enum(decl): + outp = {} + if 'name' in decl: + outp['kind'] = 'enum' + outp['name'] = decl['name'] + needs_value = False + else: + outp['kind'] = 'consts' + needs_value = True + outp['items'] = [] + for item_decl in decl['inner']: + if item_decl['kind'] == 'EnumConstantDecl': + item = {} + item['name'] = item_decl['name'] + if 'inner' in item_decl: + const_expr = item_decl['inner'][0] + if const_expr['kind'] != 'ConstantExpr': + sys.exit(f"ERROR: Enum values must be a ConstantExpr ({item_decl['name']}), is '{const_expr['kind']}'") + if const_expr['valueCategory'] != 'rvalue' and const_expr['valueCategory'] != 'prvalue': + sys.exit(f"ERROR: Enum value ConstantExpr must be 'rvalue' or 'prvalue' ({item_decl['name']}), is '{const_expr['valueCategory']}'") + if not ((len(const_expr['inner']) == 1) and (const_expr['inner'][0]['kind'] == 'IntegerLiteral')): + sys.exit(f"ERROR: Enum value ConstantExpr must have exactly one IntegerLiteral ({item_decl['name']})") + item['value'] = const_expr['inner'][0]['value'] + if needs_value and 'value' not in item: + sys.exit(f"ERROR: anonymous enum items require an explicit value") + outp['items'].append(item) + return outp + +def parse_func(decl): + outp = {} + outp['kind'] = 'func' + outp['name'] = decl['name'] + outp['type'] = filter_types(decl['type']['qualType']) + outp['params'] = [] + if 'inner' in decl: + for param in decl['inner']: + if param['kind'] != 'ParmVarDecl': + print(f" >> warning: ignoring func {decl['name']} (unsupported parameter type)") + return None + outp_param = {} + outp_param['name'] = param['name'] + outp_param['type'] = filter_types(param['type']['qualType']) + outp['params'].append(outp_param) + return outp + +def parse_decl(decl): + kind = decl['kind'] + if kind == 'RecordDecl': + return parse_struct(decl) + elif kind == 'EnumDecl': + return parse_enum(decl) + elif kind == 'FunctionDecl': + return parse_func(decl) + else: + return None + +def clang(csrc_path): + cmd = ['clang', '-Xclang', '-ast-dump=json', '-c' ] + cmd.append(csrc_path) + return subprocess.check_output(cmd) + +def gen(header_path, source_path, module, main_prefix, dep_prefixes): + ast = clang(source_path) + inp = json.loads(ast) + outp = {} + outp['module'] = module + outp['prefix'] = main_prefix + outp['dep_prefixes'] = dep_prefixes + outp['decls'] = [] + for decl in inp['inner']: + is_dep = is_dep_decl(decl, dep_prefixes) + if is_api_decl(decl, main_prefix) or is_dep: + outp_decl = parse_decl(decl) + if outp_decl is not None: + outp_decl['is_dep'] = is_dep + outp_decl['dep_prefix'] = dep_prefix(decl, dep_prefixes) + outp['decls'].append(outp_decl) + with open(f'{module}.json', 'w') as f: + f.write(json.dumps(outp, indent=2)); + return outp diff --git a/source/engine/thirdparty/sokol/bindgen/gen_nim.py b/source/engine/thirdparty/sokol/bindgen/gen_nim.py new file mode 100644 index 0000000..700305a --- /dev/null +++ b/source/engine/thirdparty/sokol/bindgen/gen_nim.py @@ -0,0 +1,613 @@ +#------------------------------------------------------------------------------- +# Generate Nim bindings +# +# Nim coding style: +# - type identifiers are PascalCase, everything else is camelCase +# - reference: https://nim-lang.org/docs/nep1.html +#------------------------------------------------------------------------------- +import gen_ir +import gen_util as util +import os, shutil, sys + +module_names = { + 'slog_': 'log', + 'sg_': 'gfx', + 'sapp_': 'app', + 'stm_': 'time', + 'saudio_': 'audio', + 'sgl_': 'gl', + 'sdtx_': 'debugtext', + 'sshape_': 'shape', + 'sglue_': 'glue', +} + +c_source_paths = { + 'slog_': 'sokol-nim/src/sokol/c/sokol_log.c', + 'sg_': 'sokol-nim/src/sokol/c/sokol_gfx.c', + 'sapp_': 'sokol-nim/src/sokol/c/sokol_app.c', + 'stm_': 'sokol-nim/src/sokol/c/sokol_time.c', + 'saudio_': 'sokol-nim/src/sokol/c/sokol_audio.c', + 'sgl_': 'sokol-nim/src/sokol/c/sokol_gl.c', + 'sdtx_': 'sokol-nim/src/sokol/c/sokol_debugtext.c', + 'sshape_': 'sokol-nim/src/sokol/c/sokol_shape.c', + 'sglue_': 'sokol-nim/src/sokol/c/sokol_glue.c', +} + +c_callbacks = [ + 'slog_func', +] + +ignores = [ + 'sdtx_printf', + 'sdtx_vprintf', +] + +overrides = { + 'sgl_error': 'sgl_get_error', + 'sgl_deg': 'sgl_as_degrees', + 'sgl_rad': 'sgl_as_radians', + 'SGL_NO_ERROR': 'SGL_ERROR_NO_ERROR', + 'SG_BUFFERTYPE_VERTEXBUFFER': 'SG_BUFFERTYPE_VERTEX_BUFFER', + 'SG_BUFFERTYPE_INDEXBUFFER': 'SG_BUFFERTYPE_INDEX_BUFFER', + 'SG_ACTION_DONTCARE': 'SG_ACTION_DONT_CARE', + 'ptr': 'addr', # range ptr + 'func': 'fn', + 'slog_func': 'fn', +} + +enumPrefixOverrides = { + # sokol_gfx.h + 'LOADACTION': 'loadAction', + 'STOREACTION': 'storeAction', + 'PIXELFORMAT': 'pixelFormat', + 'RESOURCESTATE': 'resourceState', + 'BUFFERTYPE': 'bufferType', + 'INDEXTYPE': 'indexType', + 'IMAGETYPE': 'imageType', + 'SAMPLERTYPE': 'samplerType', + 'CUBEFACE': 'cubeFace', + 'SHADERSTAGE': 'shaderStage', + 'PRIMITIVETYPE': 'primitiveType', + 'BORDERCOLOR': 'borderColor', + 'VERTEXFORMAT': 'vertexFormat', + 'VERTEXSTEP': 'vertexStep', + 'UNIFORMTYPE': 'uniformType', + 'UNIFORMLAYOUT': 'uniformLayout', + 'CULLMODE': 'cullMode', + 'FACEWINDING': 'faceWinding', + 'COMPAREFUNC': 'compareFunc', + 'STENCILOP': 'stencilOp', + 'BLENDFACTOR': 'blendFactor', + 'BLENDOP': 'blendOp', + 'COLORMASK': 'colorMask', + # sokol_app.h + 'EVENTTYPE': 'eventType', + 'KEYCODE': 'keyCode', + 'MOUSEBUTTON': 'mouseButton', +} + +prim_types = { + 'int': 'int32', + 'bool': 'bool', + 'char': 'char', + 'int8_t': 'int8', + 'uint8_t': 'uint8', + 'int16_t': 'int16', + 'uint16_t': 'uint16', + 'int32_t': 'int32', + 'uint32_t': 'uint32', + 'int64_t': 'int64', + 'uint64_t': 'uint64', + 'float': 'float32', + 'double': 'float64', + 'uintptr_t': 'uint', + 'intptr_t': 'int', + 'size_t': 'int', # not a bug, Nim's sizeof() returns int +} + +prim_defaults = { + 'int': '0', + 'bool': 'false', + 'int8_t': '0', + 'uint8_t': '0', + 'int16_t': '0', + 'uint16_t': '0', + 'int32_t': '0', + 'uint32_t': '0', + 'int64_t': '0', + 'uint64_t': '0', + 'float': '0.0f', + 'double': '0.0', + 'uintptr_t': '0', + 'intptr_t': '0', + 'size_t': '0' +} + +common_prim_types = """ +array +untyped typed void +bool byte char +int int8 int16 int32 int64 +uint uint8 uint16 uint32 uint64 +float float32 float64 +string +cchar cint csize_t +cfloat cdouble +cstring +pointer +""".split() + +keywords = """ +addr and as asm +bind block break +case cast concept const continue converter +defer discard distinct div do +elif else end enum except export +finally for from func +if import in include interface is isnot iterator +let +macro method mixin mod +nil not notin +object of or out +proc ptr +raise ref return +shl shr static +template try tuple type +using +var +when while +xor +yield +""".split() + common_prim_types + +struct_types = [] +enum_types = [] +out_lines = '' + +def reset_globals(): + global struct_types + global enum_types + global out_lines + struct_types = [] + enum_types = [] + out_lines = '' + +def l(s): + global out_lines + out_lines += s + '\n' + +def as_nim_prim_type(s): + return prim_types[s] + +# prefix_bla_blub(_t) => (dep.)BlaBlub +def as_nim_type_name(s, prefix): + parts = s.lower().split('_') + dep = parts[0] + '_' + outp = '' + if not s.startswith(prefix) and dep in module_names: + outp = module_names[dep] + '.' + for part in parts[1:]: + # ignore '_t' type postfix + if (part != 't'): + outp += part.capitalize() + return outp + +def check_override(name, default=None): + if name in overrides: + return overrides[name] + elif default is None: + return name + else: + return default + +def check_ignore(name): + return name in ignores + +def is_power_of_two(val): + return val == 0 or val & (val - 1) == 0 + +def wrap_keywords(s): + if s in keywords: + return f'`{s}`' + else: + return s + +# prefix_bla_blub => blaBlub +def as_camel_case(s, prefix, wrap=True): + outp = s.lower() + if outp.startswith(prefix): + outp = outp[len(prefix):] + parts = outp.lstrip('_').split('_') + outp = parts[0] + for part in parts[1:]: + outp += part.capitalize() + if wrap: + outp = wrap_keywords(outp) + return outp + +# PREFIX_ENUM_BLA_BLO => blaBlo +def as_enum_item_name(s, wrap=True): + outp = s.lstrip('_') + parts = outp.split('_')[1:] + if parts[0] in enumPrefixOverrides: + parts[0] = enumPrefixOverrides[parts[0]] + else: + parts[0] = parts[0].lower() + outp = parts[0] + for part in parts[1:]: + outp += part.capitalize() + if wrap: + outp = wrap_keywords(outp) + return outp + +def is_prim_type(s): + return s in prim_types + +def is_struct_type(s): + return s in struct_types + +def is_enum_type(s): + return s in enum_types + +def is_const_prim_ptr(s): + for prim_type in prim_types: + if s == f"const {prim_type} *": + return True + return False + +def is_prim_ptr(s): + for prim_type in prim_types: + if s == f"{prim_type} *": + return True + return False + +def is_const_struct_ptr(s): + for struct_type in struct_types: + if s == f"const {struct_type} *": + return True + return False + +def type_default_value(s): + return prim_defaults[s] + +def funcptr_args(field_type, prefix): + tokens = field_type[field_type.index('(*)')+4:-1].split(',') + s = "" + n = 0 + for token in tokens: + n += 1 + arg_ctype = token.strip() + if s != "": + s += ", " + arg_nimtype = as_nim_type(arg_ctype, prefix) + if arg_nimtype == "": + return "" # fun(void) + s += f"a{n}:{arg_nimtype}" + if s == "a1:void": + s = "" + return s + +def funcptr_result(field_type, prefix): + ctype = field_type[:field_type.index('(*)')].strip() + return as_nim_type(ctype, prefix) + +def as_nim_type(ctype, prefix, struct_ptr_as_value=False): + if ctype == "void": + return "" + elif is_prim_type(ctype): + return as_nim_prim_type(ctype) + elif is_struct_type(ctype): + return as_nim_type_name(ctype, prefix) + elif is_enum_type(ctype): + return as_nim_type_name(ctype, prefix) + elif util.is_string_ptr(ctype): + return "cstring" + elif util.is_void_ptr(ctype) or util.is_const_void_ptr(ctype): + return "pointer" + elif is_const_struct_ptr(ctype): + nim_type = as_nim_type(util.extract_ptr_type(ctype), prefix) + if struct_ptr_as_value: + return f"{nim_type}" + else: + return f"ptr {nim_type}" + elif is_prim_ptr(ctype) or is_const_prim_ptr(ctype): + return f"ptr {as_nim_type(util.extract_ptr_type(ctype), prefix)}" + elif util.is_func_ptr(ctype): + args = funcptr_args(ctype, prefix) + res = funcptr_result(ctype, prefix) + if res != "": + res = ":" + res + return f"proc({args}){res} {{.cdecl.}}" + elif util.is_1d_array_type(ctype): + array_ctype = util.extract_array_type(ctype) + array_sizes = util.extract_array_sizes(ctype) + return f'array[{array_sizes[0]}, {as_nim_type(array_ctype, prefix)}]' + elif util.is_2d_array_type(ctype): + array_ctype = util.extract_array_type(ctype) + array_sizes = util.extract_array_sizes(ctype) + return f'array[{array_sizes[0]}, array[{array_sizes[1]}, {as_nim_type(array_ctype, prefix)}]]' + else: + sys.exit(f"ERROR as_nim_type: {ctype}") + +def as_nim_struct_name(struct_decl, prefix): + struct_name = check_override(struct_decl['name']) + nim_type = f'{as_nim_type_name(struct_name, prefix)}' + return nim_type + +def as_nim_field_name(field_decl, prefix, check_private=True): + field_name = as_camel_case(check_override(field_decl['name']), prefix) + if check_private: + is_private = field_decl['name'].startswith('_') + if not is_private: + field_name += "*" + return field_name + +def as_nim_field_type(struct_decl, field_decl, prefix): + return as_nim_type(check_override(f"{struct_decl['name']}.{field_decl['name']}", default=field_decl['type']), prefix) + +def gen_struct(decl, prefix): + l(f"type {as_nim_struct_name(decl, prefix)}* = object") + for field in decl['fields']: + l(f" {as_nim_field_name(field, prefix)}:{as_nim_field_type(decl, field, prefix)}") + l("") + +def gen_consts(decl, prefix): + l("const") + for item in decl['items']: + item_name = check_override(item['name']) + l(f" {as_camel_case(item_name, prefix)}* = {item['value']}") + l("") + +def gen_enum(decl, prefix): + item_names_by_value = {} + value = -1 + has_explicit_values = False + for item in decl['items']: + item_name = check_override(item['name']) + if item_name.endswith("_NUM") or item_name.endswith("_FORCE_U32"): + continue + else: + if 'value' in item: + has_explicit_values = True + value = int(item['value']) + else: + value += 1 + item_names_by_value[value] = as_enum_item_name(item_name) + enum_name_nim = as_nim_type_name(decl['name'], prefix) + l('type') + l(f" {enum_name_nim}* {{.size:sizeof(int32).}} = enum") + if has_explicit_values: + # Nim requires explicit enum values to be declared in ascending order + for value in sorted(item_names_by_value): + name = item_names_by_value[value] + l(f" {name} = {value},") + else: + for name in item_names_by_value.values(): + l(f" {name},") + l("") + +# returns C prototype compatible function args (with pointers) +def funcdecl_args_c(decl, prefix): + s = "" + func_name = decl['name'] + for param_decl in decl['params']: + if s != "": + s += ", " + arg_name = param_decl['name'] + arg_type = check_override(f'{func_name}.{arg_name}', default=param_decl['type']) + s += f"{as_camel_case(arg_name, prefix)}:{as_nim_type(arg_type, prefix)}" + return s + +# returns Nim function args (pass structs by value) +def funcdecl_args_nim(decl, prefix): + s = "" + func_name = decl['name'] + for param_decl in decl['params']: + if s != "": + s += ", " + arg_name = param_decl['name'] + arg_type = check_override(f'{func_name}.{arg_name}', default=param_decl['type']) + s += f"{as_camel_case(arg_name, prefix)}:{as_nim_type(arg_type, prefix, struct_ptr_as_value=True)}" + return s + +def funcdecl_result(decl, prefix): + func_name = decl['name'] + decl_type = decl['type'] + result_type = check_override(f'{func_name}.RESULT', default=decl_type[:decl_type.index('(')].strip()) + nim_res_type = as_nim_type(result_type, prefix) + if nim_res_type == "": + nim_res_type = "void" + return nim_res_type + +def gen_func_nim(decl, prefix): + c_func_name = decl['name'] + nim_func_name = as_camel_case(check_override(c_func_name), prefix, wrap=False) + nim_res_type = funcdecl_result(decl, prefix) + if c_func_name in c_callbacks: + l(f"proc {nim_func_name}*({funcdecl_args_c(decl, prefix)}):{nim_res_type} {{.cdecl, importc:\"{c_func_name}\".}}") + else: + l(f"proc c_{nim_func_name}({funcdecl_args_c(decl, prefix)}):{nim_res_type} {{.cdecl, importc:\"{c_func_name}\".}}") + l(f"proc {wrap_keywords(nim_func_name)}*({funcdecl_args_nim(decl, prefix)}):{nim_res_type} =") + s = f" c_{nim_func_name}(" + for i, param_decl in enumerate(decl['params']): + if i > 0: + s += ", " + arg_name = param_decl['name'] + arg_type = param_decl['type'] + if is_const_struct_ptr(arg_type): + s += f"addr({arg_name})" + else: + s += arg_name + s += ")" + l(s) + l("") + +def gen_array_converters(decl, prefix): + for field in decl['fields']: + if util.is_array_type(field['type']): + array_type = util.extract_array_type(field['type']) + array_sizes = util.extract_array_sizes(field['type']) + struct_name = as_nim_struct_name(decl, prefix) + field_name = as_nim_field_name(field, prefix, check_private=False) + array_base_type = as_nim_type(array_type, prefix) + if util.is_1d_array_type(field['type']): + n = array_sizes[0] + l(f'converter to{struct_name}{field_name}*[N:static[int]](items: array[N, {array_base_type}]): array[{n}, {array_base_type}] =') + l(f' static: assert(N <= {n})') + l(f' for index,item in items.pairs: result[index]=item') + l('') + elif util.is_2d_array_type(field['type']): + x = array_sizes[1] + y = array_sizes[0] + l(f'converter to{struct_name}{field_name}*[Y:static[int], X:static[int]](items: array[Y, array[X, {array_base_type}]]): array[{y}, array[{x}, {array_base_type}]] =') + l(f' static: assert(X <= {x})') + l(f' static: assert(Y <= {y})') + l(f' for indexY,itemY in items.pairs:') + l(f' for indexX, itemX in itemY.pairs:') + l(f' result[indexY][indexX] = itemX') + l('') + else: + sys.exit('Unsupported converter array dimension (> 2)!') + +def pre_parse(inp): + global struct_types + global enum_types + for decl in inp['decls']: + kind = decl['kind'] + if kind == 'struct': + struct_types.append(decl['name']) + elif kind == 'enum': + enum_name = decl['name'] + enum_types.append(enum_name) + +def gen_imports(inp, dep_prefixes): + for dep_prefix in dep_prefixes: + dep_module_name = module_names[dep_prefix] + l(f'import {dep_module_name}') + l('') + +def gen_extra(inp): + if inp['prefix'] in ['sg_']: + # FIXME: remove when sokol-shdc has been integrated! + l('when defined gl:') + l(' const gl* = true') + l(' const d3d11* = false') + l(' const metal* = false') + l('elif defined windows:') + l(' const gl* = false') + l(' const d3d11* = true') + l(' const metal* = false') + l('elif defined macosx:') + l(' const gl* = false') + l(' const d3d11* = false') + l(' const metal* = true') + l('elif defined linux:') + l(' const gl* = true') + l(' const d3d11* = false') + l(' const metal* = false') + l('else:') + l(' error("unsupported platform")') + l('') + if inp['prefix'] in ['sg_', 'sapp_']: + l('when defined windows:') + l(' when not defined vcc:') + l(' {.passl:"-lkernel32 -luser32 -lshell32 -lgdi32".}') + l(' when defined gl:') + l(' {.passc:"-DSOKOL_GLCORE33".}') + l(' else:') + l(' {.passc:"-DSOKOL_D3D11".}') + l(' when not defined vcc:') + l(' {.passl:"-ld3d11 -ldxgi".}') + l('elif defined macosx:') + l(' {.passc:"-x objective-c".}') + l(' {.passl:"-framework Cocoa -framework QuartzCore".}') + l(' when defined gl:') + l(' {.passc:"-DSOKOL_GLCORE33".}') + l(' {.passl:"-framework OpenGL".}') + l(' else:') + l(' {.passc:"-DSOKOL_METAL".}') + l(' {.passl:"-framework Metal -framework MetalKit".}') + l('elif defined linux:') + l(' {.passc:"-DSOKOL_GLCORE33".}') + l(' {.passl:"-lX11 -lXi -lXcursor -lGL -lm -ldl -lpthread".}') + l('else:') + l(' error("unsupported platform")') + l('') + if inp['prefix'] in ['saudio_']: + l('when defined windows:') + l(' when not defined vcc:') + l(' {.passl:"-lkernel32 -lole32".}') + l('elif defined macosx:') + l(' {.passl:"-framework AudioToolbox".}') + l('elif defined linux:') + l(' {.passl:"-lasound -lm -lpthread".}') + l('else:') + l(' error("unsupported platform")') + l('') + if inp['prefix'] in ['sg_']: + l('## Convert a 4-element tuple of numbers to a gfx.Color') + l('converter toColor*[R:SomeNumber,G:SomeNumber,B:SomeNumber,A:SomeNumber](rgba: tuple [r:R,g:G,b:B,a:A]):Color =') + l(' Color(r:rgba.r.float32, g:rgba.g.float32, b:rgba.b.float32, a:rgba.a.float32)') + l('') + l('## Convert a 3-element tuple of numbers to a gfx.Color') + l('converter toColor*[R:SomeNumber,G:SomeNumber,B:SomeNumber](rgba: tuple [r:R,g:G,b:B]):Color =') + l(' Color(r:rgba.r.float32, g:rgba.g.float32, b:rgba.b.float32, a:1.float32)') + l('') + # NOTE: this simplistic to_Range() converter has various issues, some of them dangerous: + # - doesn't work as expected for slice types + # - it's very easy to create a range that points to invalid memory + # (so far observed for stack-allocated structs <= 16 bytes) + #if inp['prefix'] in ['sg_', 'sdtx_', 'sshape_']: + # l('# helper function to convert "anything" into a Range') + # l('converter to_Range*[T](source: T): Range =') + # l(' Range(addr: source.addr, size: source.sizeof.uint)') + # l('') + c_source_path = '/'.join(c_source_paths[inp['prefix']].split('/')[3:]) + l('{.passc:"-DSOKOL_NIM_IMPL".}') + l('when defined(release):') + l(' {.passc:"-DNDEBUG".}') + l(f'{{.compile:"{c_source_path}".}}') + +def gen_module(inp, dep_prefixes): + l('## machine generated, do not edit') + l('') + gen_imports(inp, dep_prefixes) + pre_parse(inp) + prefix = inp['prefix'] + for decl in inp['decls']: + if not decl['is_dep']: + kind = decl['kind'] + if kind == 'consts': + gen_consts(decl, prefix) + elif not check_ignore(decl['name']): + if kind == 'struct': + gen_struct(decl, prefix) + gen_array_converters(decl, prefix) + elif kind == 'enum': + gen_enum(decl, prefix) + elif kind == 'func': + gen_func_nim(decl, prefix) + gen_extra(inp) + +def prepare(): + print('=== Generating Nim bindings:') + if not os.path.isdir('sokol-nim/src/sokol'): + os.makedirs('sokol-nim/src/sokol') + if not os.path.isdir('sokol-nim/src/sokol/c'): + os.makedirs('sokol-nim/src/sokol/c') + +def gen(c_header_path, c_prefix, dep_c_prefixes): + if not c_prefix in module_names: + print(f' >> warning: skipping generation for {c_prefix} prefix...') + return + global out_lines + module_name = module_names[c_prefix] + c_source_path = c_source_paths[c_prefix] + print(f' {c_header_path} => {module_name}') + reset_globals() + shutil.copyfile(c_header_path, f'sokol-nim/src/sokol/c/{os.path.basename(c_header_path)}') + ir = gen_ir.gen(c_header_path, c_source_path, module_name, c_prefix, dep_c_prefixes) + gen_module(ir, dep_c_prefixes) + output_path = f"sokol-nim/src/sokol/{ir['module']}.nim" + with open(output_path, 'w', newline='\n') as f_outp: + f_outp.write(out_lines) diff --git a/source/engine/thirdparty/sokol/bindgen/gen_odin.py b/source/engine/thirdparty/sokol/bindgen/gen_odin.py new file mode 100644 index 0000000..6ba84f3 --- /dev/null +++ b/source/engine/thirdparty/sokol/bindgen/gen_odin.py @@ -0,0 +1,500 @@ +#------------------------------------------------------------------------------- +# gen_odin.py +# +# Generate Odin bindings. +#------------------------------------------------------------------------------- +import gen_ir +import gen_util as util +import os, shutil, sys + +bindings_root = 'sokol-odin' +c_root = f'{bindings_root}/c' +module_root = f'{bindings_root}/sokol' + +module_names = { + 'slog_': 'log', + 'sg_': 'gfx', + 'sapp_': 'app', + 'stm_': 'time', + 'saudio_': 'audio', + 'sgl_': 'gl', + 'sdtx_': 'debugtext', + 'sshape_': 'shape', + 'sglue_': 'glue', +} + +system_libs = { + 'sg_': { + 'windows': { + 'd3d11': "", + 'gl': "", + }, + 'macos': { + 'metal': '"system:Cocoa.framework","system:QuartzCore.framework","system:Metal.framework","system:MetalKit.framework"', + 'gl': '"system:Cocoa.framework","system:QuartzCore.framework","system:OpenGL.framework"' + }, + 'linux': { + 'gl': '"system:GL", "system:dl", "system:pthread"' + } + }, + 'sapp_': { + 'windows': { + 'd3d11': '', + 'gl': '', + }, + 'macos': { + 'metal': '"system:Cocoa.framework","system:QuartzCore.framework","system:Metal.framework","system:MetalKit.framework"', + 'gl': '"system:Cocoa.framework","system:QuartzCore.framework","system:OpenGL.framework"', + }, + 'linux': { + 'gl': '"system:X11", "system:Xi", "system:Xcursor", "system:GL", "system:dl", "system:pthread"' + } + }, + 'saudio_': { + 'windows': { + 'd3d11': '', + 'gl': '', + }, + 'macos': { + 'metal': '"system:AudioToolbox.framework"', + 'gl': '"system:AudioToolbox.framework"', + }, + 'linux': { + 'gl': '"system:asound", "system:dl", "system:pthread"', + } + } +} + +c_source_names = { + 'slog_': 'sokol_log.c', + 'sg_': 'sokol_gfx.c', + 'sapp_': 'sokol_app.c', + 'sapp_sg': 'sokol_glue.c', + 'stm_': 'sokol_time.c', + 'saudio_': 'sokol_audio.c', + 'sgl_': 'sokol_gl.c', + 'sdtx_': 'sokol_debugtext.c', + 'sshape_': 'sokol_shape.c', + 'sglue_': 'sokol_glue.c', +} + +ignores = [ + 'sdtx_printf', + 'sdtx_vprintf', + 'sg_install_trace_hooks', + 'sg_trace_hooks', +] + +# NOTE: syntax for function results: "func_name.RESULT" +overrides = { + 'context': 'ctx', # reserved keyword + 'SGL_NO_ERROR': 'SGL_ERROR_NO_ERROR', +} + +prim_types = { + 'int': 'c.int', + 'bool': 'bool', + 'char': 'u8', + 'int8_t': 'i8', + 'uint8_t': 'u8', + 'int16_t': 'i16', + 'uint16_t': 'u16', + 'int32_t': 'i32', + 'uint32_t': 'u32', + 'int64_t': 'i64', + 'uint64_t': 'u64', + 'float': 'f32', + 'double': 'f64', + 'uintptr_t': 'u64', + 'intptr_t': 'i64', + 'size_t': 'u64' +} + +prim_defaults = { + 'int': '0', + 'bool': 'false', + 'int8_t': '0', + 'uint8_t': '0', + 'int16_t': '0', + 'uint16_t': '0', + 'int32_t': '0', + 'uint32_t': '0', + 'int64_t': '0', + 'uint64_t': '0', + 'float': '0.0', + 'double': '0.0', + 'uintptr_t': '0', + 'intptr_t': '0', + 'size_t': '0' +} + +struct_types = [] +enum_types = [] +enum_items = {} +out_lines = '' + +def reset_globals(): + global struct_types + global enum_types + global enum_items + global out_lines + struct_types = [] + enum_types = [] + enum_items = {} + out_lines = '' + +def l(s): + global out_lines + out_lines += s + '\n' + +def check_override(name, default=None): + if name in overrides: + return overrides[name] + elif default is None: + return name + else: + return default + +def check_ignore(name): + return name in ignores + +# PREFIX_BLA_BLUB to BLA_BLUB, prefix_bla_blub to bla_blub +def as_snake_case(s, prefix): + outp = s + if outp.lower().startswith(prefix): + outp = outp[len(prefix):] + return outp + +def get_odin_module_path(c_prefix): + return f'{module_root}/{module_names[c_prefix]}' + +def get_csource_path(c_prefix): + return f'{c_root}/{c_source_names[c_prefix]}' + +def make_odin_module_directory(c_prefix): + path = get_odin_module_path(c_prefix) + if not os.path.isdir(path): + os.makedirs(path) + +def as_prim_type(s): + return prim_types[s] + +# prefix_bla_blub(_t) => (dep.)Bla_Blub +def as_struct_or_enum_type(s, prefix): + parts = s.lower().split('_') + outp = '' if s.startswith(prefix) else f'{parts[0]}.' + for part in parts[1:]: + # ignore '_t' type postfix + if (part != 't'): + outp += part.capitalize() + outp += '_' + outp = outp[:-1] + return outp + +# PREFIX_ENUM_BLA_BLUB => BLA_BLUB, _PREFIX_ENUM_BLA_BLUB => BLA_BLUB +def as_enum_item_name(s): + outp = s.lstrip('_') + parts = outp.split('_')[2:] + outp = '_'.join(parts) + if outp[0].isdigit(): + outp = '_' + outp + return outp + +def enum_default_item(enum_name): + return enum_items[enum_name][0] + +def is_prim_type(s): + return s in prim_types + +def is_int_type(s): + return s == "int" + +def is_struct_type(s): + return s in struct_types + +def is_enum_type(s): + return s in enum_types + +def is_const_prim_ptr(s): + for prim_type in prim_types: + if s == f"const {prim_type} *": + return True + return False + +def is_prim_ptr(s): + for prim_type in prim_types: + if s == f"{prim_type} *": + return True + return False + +def is_const_struct_ptr(s): + for struct_type in struct_types: + if s == f"const {struct_type} *": + return True + return False + +def type_default_value(s): + return prim_defaults[s] + +def map_type(type, prefix, sub_type): + if sub_type not in ['c_arg', 'odin_arg', 'struct_field']: + sys.exit(f"Error: map_type(): unknown sub_type '{sub_type}") + if type == "void": + return "" + elif is_prim_type(type): + if sub_type == 'odin_arg': + # for Odin args, maps C int (32-bit) to Odin int (pointer-sized), + # and the C bool type to Odin's bool type + if type == 'int' or type == 'uint32_t': + return 'int' + elif type == 'bool': + return 'bool' + return as_prim_type(type) + elif is_struct_type(type): + return as_struct_or_enum_type(type, prefix) + elif is_enum_type(type): + return as_struct_or_enum_type(type, prefix) + elif util.is_void_ptr(type): + return "rawptr" + elif util.is_const_void_ptr(type): + return "rawptr" + elif util.is_string_ptr(type): + return "cstring" + elif is_const_struct_ptr(type): + # pass Odin struct args by value, not by pointer + if sub_type == 'odin_arg': + return f"{as_struct_or_enum_type(util.extract_ptr_type(type), prefix)}" + else: + return f"^{as_struct_or_enum_type(util.extract_ptr_type(type), prefix)}" + elif is_prim_ptr(type): + return f"^{as_prim_type(util.extract_ptr_type(type))}" + elif is_const_prim_ptr(type): + return f"^{as_prim_type(util.extract_ptr_type(type))}" + elif util.is_1d_array_type(type): + array_type = util.extract_array_type(type) + array_sizes = util.extract_array_sizes(type) + return f"[{array_sizes[0]}]{map_type(array_type, prefix, sub_type)}" + elif util.is_2d_array_type(type): + array_type = util.extract_array_type(type) + array_sizes = util.extract_array_sizes(type) + return f"[{array_sizes[0]}][{array_sizes[1]}]{map_type(array_type, prefix, sub_type)}" + elif util.is_func_ptr(type): + res_type = funcptr_result_c(type, prefix) + res_str = '' if res_type == '' else f' -> {res_type}' + return f'proc "c" ({funcptr_args_c(type, prefix)}){res_str}' + else: + sys.exit(f"Error map_type(): unknown type '{type}'") + +def funcdecl_args_c(decl, prefix): + s = '' + func_name = decl['name'] + for param_decl in decl['params']: + if s != '': + s += ', ' + param_name = param_decl['name'] + param_type = check_override(f'{func_name}.{param_name}', default=param_decl['type']) + if is_const_struct_ptr(param_type): + s += f"#by_ptr {param_name}: {map_type(param_type, prefix, 'odin_arg')}" + elif is_int_type(param_type): + s += f"#any_int {param_name}: {map_type(param_type, prefix, 'c_arg')}" + else: + s += f"{param_name}: {map_type(param_type, prefix, 'c_arg')}" + return s + +def funcptr_args_c(field_type, prefix): + tokens = field_type[field_type.index('(*)')+4:-1].split(',') + s = '' + arg_index = 0 + for token in tokens: + arg_type = token.strip() + if s != '': + s += ', ' + c_arg = map_type(arg_type, prefix, 'c_arg') + if c_arg == '': + return '' + else: + s += f'a{arg_index}: {c_arg}' + arg_index += 1 + return s + +def funcptr_result_c(field_type, prefix): + res_type = field_type[:field_type.index('(*)')].strip() + return map_type(res_type, prefix, 'c_arg') + +def funcdecl_result_c(decl, prefix): + func_name = decl['name'] + decl_type = decl['type'] + res_c_type = decl_type[:decl_type.index('(')].strip() + return map_type(check_override(f'{func_name}.RESULT', default=res_c_type), prefix, 'c_arg') + +def get_system_libs(module, platform, backend): + if module in system_libs: + if platform in system_libs[module]: + if backend in system_libs[module][platform]: + libs = system_libs[module][platform][backend] + if libs != '': + return f", {libs}" + return '' + +def gen_c_imports(inp, c_prefix, prefix): + clib_prefix = f'sokol_{inp["module"]}' + clib_import = f'{clib_prefix}_clib' + windows_d3d11_libs = get_system_libs(prefix, 'windows', 'd3d11') + windows_gl_libs = get_system_libs(prefix, 'windows', 'gl') + macos_metal_libs = get_system_libs(prefix, 'macos', 'metal') + macos_gl_libs = get_system_libs(prefix, 'macos', 'gl') + linux_gl_libs = get_system_libs(prefix, 'linux', 'gl') + l( 'import "core:c"') + l( 'when ODIN_OS == .Windows {') + l( ' when #config(SOKOL_USE_GL,false) {') + l(f' when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_windows_x64_gl_debug.lib"{windows_gl_libs} }} }}') + l(f' else {{ foreign import {clib_import} {{ "{clib_prefix}_windows_x64_gl_release.lib"{windows_gl_libs} }} }}') + l( ' } else {') + l(f' when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_windows_x64_d3d11_debug.lib"{windows_d3d11_libs} }} }}') + l(f' else {{ foreign import {clib_import} {{ "{clib_prefix}_windows_x64_d3d11_release.lib"{windows_d3d11_libs} }} }}') + l( ' }') + l( '} else when ODIN_OS == .Darwin {') + l( ' when #config(SOKOL_USE_GL,false) {') + l( ' when ODIN_ARCH == .arm64 {') + l(f' when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_macos_arm64_gl_debug.a"{macos_gl_libs} }} }}') + l(f' else {{ foreign import {clib_import} {{ "{clib_prefix}_macos_arm64_gl_release.a"{macos_gl_libs} }} }}') + l( ' } else {') + l(f' when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_macos_x64_gl_debug.a"{macos_gl_libs} }} }}') + l(f' else {{ foreign import {clib_import} {{ "{clib_prefix}_macos_x64_gl_release.a"{macos_gl_libs} }} }}') + l( ' }') + l( ' } else {') + l( ' when ODIN_ARCH == .arm64 {') + l(f' when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_macos_arm64_metal_debug.a"{macos_metal_libs} }} }}') + l(f' else {{ foreign import {clib_import} {{ "{clib_prefix}_macos_arm64_metal_release.a"{macos_metal_libs} }} }}') + l( ' } else {') + l(f' when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_macos_x64_metal_debug.a"{macos_metal_libs} }} }}') + l(f' else {{ foreign import {clib_import} {{ "{clib_prefix}_macos_x64_metal_release.a"{macos_metal_libs} }} }}') + l( ' }') + l( ' }') + l( '}') + l( 'else {') + l(f' when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_linux_x64_gl_debug.a"{linux_gl_libs} }} }}') + l(f' else {{ foreign import {clib_import} {{ "{clib_prefix}_linux_x64_gl_release.a"{linux_gl_libs} }} }}') + l( '}') + + # Need to special case sapp_sg to avoid Odin's context keyword + if c_prefix == "sapp_sg": + l(f'@(default_calling_convention="c")') + else: + l(f'@(default_calling_convention="c", link_prefix="{c_prefix}")') + l(f"foreign {clib_import} {{") + prefix = inp['prefix'] + for decl in inp['decls']: + if decl['kind'] == 'func' and not decl['is_dep'] and not check_ignore(decl['name']): + args = funcdecl_args_c(decl, prefix) + res_type = funcdecl_result_c(decl, prefix) + res_str = '' if res_type == '' else f'-> {res_type}' + # Need to special case sapp_sg to avoid Odin's context keyword + if c_prefix == "sapp_sg": + l(f' @(link_name="{decl["name"]}")') + l(f" {check_override(as_snake_case(decl['name'], c_prefix))} :: proc({args}) {res_str} ---") + else: + l(f" {as_snake_case(decl['name'], c_prefix)} :: proc({args}) {res_str} ---") + l('}') + +def gen_consts(decl, prefix): + for item in decl['items']: + item_name = check_override(item['name']) + l(f"{as_snake_case(item_name, prefix)} :: {item['value']}") + +def gen_struct(decl, prefix): + c_struct_name = check_override(decl['name']) + struct_name = as_struct_or_enum_type(c_struct_name, prefix) + l(f'{struct_name} :: struct {{') + for field in decl['fields']: + field_name = check_override(field['name']) + field_type = map_type(check_override(f'{c_struct_name}.{field_name}', default=field['type']), prefix, 'struct_field') + # any field name starting with _ is considered private + if field_name.startswith('_'): + l(f' _ : {field_type},') + else: + l(f' {field_name} : {field_type},') + l('}') + +def gen_enum(decl, prefix): + enum_name = check_override(decl['name']) + l(f'{as_struct_or_enum_type(enum_name, prefix)} :: enum i32 {{') + for item in decl['items']: + item_name = as_enum_item_name(check_override(item['name'])) + if item_name != 'FORCE_U32': + if 'value' in item: + l(f" {item_name} = {item['value']},") + else: + l(f" {item_name},") + l('}') + +def gen_imports(dep_prefixes): + for dep_prefix in dep_prefixes: + dep_module_name = module_names[dep_prefix] + l(f'import {dep_prefix[:-1]} "../{dep_module_name}"') + l('') + +def gen_helpers(inp): + if inp['prefix'] == 'sdtx_': + l('import "core:fmt"') + l('import "core:strings"') + l('printf :: proc(s: string, args: ..any) {') + l(' fstr := fmt.tprintf(s, ..args)') + l(' putr(strings.unsafe_string_to_cstring(fstr), len(fstr))') + l('}') + +def gen_module(inp, c_prefix, dep_prefixes): + pre_parse(inp) + l('// machine generated, do not edit') + l('') + l(f"package sokol_{inp['module']}") + gen_imports(dep_prefixes) + gen_helpers(inp) + prefix = inp['prefix'] + gen_c_imports(inp, c_prefix, prefix) + for decl in inp['decls']: + if not decl['is_dep']: + kind = decl['kind'] + if kind == 'consts': + gen_consts(decl, prefix) + elif not check_ignore(decl['name']): + if kind == 'struct': + gen_struct(decl, prefix) + elif kind == 'enum': + gen_enum(decl, prefix) + +def pre_parse(inp): + global struct_types + global enum_types + for decl in inp['decls']: + kind = decl['kind'] + if kind == 'struct': + struct_types.append(decl['name']) + elif kind == 'enum': + enum_name = decl['name'] + enum_types.append(enum_name) + enum_items[enum_name] = [] + for item in decl['items']: + enum_items[enum_name].append(as_enum_item_name(item['name'])) + +def prepare(): + print('=== Generating Odin bindings:') + if not os.path.isdir(c_root): + os.makedirs(c_root) + if not os.path.isdir(module_root): + os.makedirs(module_root) + +def gen(c_header_path, c_prefix, dep_c_prefixes): + if not c_prefix in module_names: + print(f' >> warning: skipping generation for {c_prefix} prefix...') + return + reset_globals() + make_odin_module_directory(c_prefix) + print(f' {c_header_path} => {module_names[c_prefix]}') + shutil.copyfile(c_header_path, f'{c_root}/{os.path.basename(c_header_path)}') + csource_path = get_csource_path(c_prefix) + module_name = module_names[c_prefix] + ir = gen_ir.gen(c_header_path, csource_path, module_name, c_prefix, dep_c_prefixes) + gen_module(ir, c_prefix, dep_c_prefixes) + with open(f"{module_root}/{ir['module']}/{ir['module']}.odin", 'w', newline='\n') as f_outp: + f_outp.write(out_lines) diff --git a/source/engine/thirdparty/sokol/bindgen/gen_rust.py b/source/engine/thirdparty/sokol/bindgen/gen_rust.py new file mode 100644 index 0000000..0984d14 --- /dev/null +++ b/source/engine/thirdparty/sokol/bindgen/gen_rust.py @@ -0,0 +1,893 @@ +# ------------------------------------------------------------------------------- +# Generate rust bindings. +# +# rust coding style: +# - types are PascalCase +# - otherwise snake_case +# ------------------------------------------------------------------------------- +import gen_ir +import os, shutil, sys + +import gen_util as util + +module_names = { + "slog_": "log", + "sg_": "gfx", + "sapp_": "app", + "stm_": "time", + "saudio_": "audio", + "sgl_": "gl", + "sdtx_": "debugtext", + "sshape_": "shape", + "simgui_": "imgui", + "sglue_": "glue", +} + +module_requires_rust_feature = { + module_names["simgui_"]: "imgui", +} + +c_source_paths = { + "slog_": "sokol-rust/src/sokol/c/sokol_log.c", + "sg_": "sokol-rust/src/sokol/c/sokol_gfx.c", + "sapp_": "sokol-rust/src/sokol/c/sokol_app.c", + "stm_": "sokol-rust/src/sokol/c/sokol_time.c", + "saudio_": "sokol-rust/src/sokol/c/sokol_audio.c", + "sgl_": "sokol-rust/src/sokol/c/sokol_gl.c", + "sdtx_": "sokol-rust/src/sokol/c/sokol_debugtext.c", + "sshape_": "sokol-rust/src/sokol/c/sokol_shape.c", + "simgui_": "sokol-rust/src/sokol/c/sokol_imgui.c", + "sglue_": "sokol-rust/src/sokol/c/sokol_glue.c", +} + +ignores = [ + "sdtx_printf", + "sdtx_vprintf", + "simgui_add_key_event", + # "sg_install_trace_hooks", + # "sg_trace_hooks", +] + +range_struct_name = "Range" + +# functions that need to be exposed as 'raw' C callbacks without a rust wrapper function +c_callbacks = ["slog_func"] + +# NOTE: syntax for function results: "func_name.RESULT" +overrides = { + "type": "_type", + "ref": "_ref", + + "sg_apply_uniforms.ub_index": "uintptr_t", + "sg_draw.base_element": "uintptr_t", + "sg_draw.num_elements": "uintptr_t", + "sg_draw.num_instances": "uintptr_t", + "sshape_element_range_t.base_element": "uintptr_t", + "sshape_element_range_t.num_elements": "uintptr_t", + "sdtx_font.font_index": "uintptr_t", + + "sdtx_move": "sdtx_move_cursor", + "sdtx_move_x": "sdtx_move_cursor_x", + "sdtx_move_y": "sdtx_move_cursor_y", + + "sg_image_type::SG_IMAGETYPE_2D": "SG_IMAGEYPE_DIM2", + "sg_image_type::SG_IMAGETYPE_3D": "SG_IMAGETYPE_DIM3", + + "sapp_keycode::SAPP_KEYCODE_0": "SAPP_KEYCODE_NUM0", + "sapp_keycode::SAPP_KEYCODE_1": "SAPP_KEYCODE_NUM1", + "sapp_keycode::SAPP_KEYCODE_2": "SAPP_KEYCODE_NUM2", + "sapp_keycode::SAPP_KEYCODE_3": "SAPP_KEYCODE_NUM3", + "sapp_keycode::SAPP_KEYCODE_4": "SAPP_KEYCODE_NUM4", + "sapp_keycode::SAPP_KEYCODE_5": "SAPP_KEYCODE_NUM5", + "sapp_keycode::SAPP_KEYCODE_6": "SAPP_KEYCODE_NUM6", + "sapp_keycode::SAPP_KEYCODE_7": "SAPP_KEYCODE_NUM7", + "sapp_keycode::SAPP_KEYCODE_8": "SAPP_KEYCODE_NUM8", + "sapp_keycode::SAPP_KEYCODE_9": "SAPP_KEYCODE_NUM9", + + # "sgl_error": "sgl_get_error", # 'error' is reserved in zig + # "sgl_deg": "sgl_as_degrees", + # "sgl_rad": "sgl_as_radians", + # "sg_context_desc.color_format": "int", + # "SGL_NO_ERROR": "SGL_ERROR_NO_ERROR", + # "sg_context_desc.depth_format": "int", +} + +prim_types = { + "int": "i32", + "bool": "bool", + "char": "core::ffi::c_char", + "int8_t": "i8", + "uint8_t": "u8", + "int16_t": "i16", + "uint16_t": "u16", + "int32_t": "i32", + "uint32_t": "u32", + "int64_t": "i64", + "uint64_t": "u64", + "float": "f32", + "double": "f64", + "uintptr_t": "usize", + "intptr_t": "isize", + "size_t": "usize", +} + +prim_defaults = { + "int": "0", + "bool": "false", + "int8_t": "0", + "uint8_t": "0", + "int16_t": "0", + "uint16_t": "0", + "int32_t": "0", + "uint32_t": "0", + "int64_t": "0", + "uint64_t": "0", + "float": "0.0", + "double": "0.0", + "uintptr_t": "0", + "intptr_t": "0", + "size_t": "0", + "char": "0", +} + +special_constant_types = { + "SG_INVALID_ID": "u32", + "SAPP_MODIFIER_SHIFT": "u32", + "SAPP_MODIFIER_CTRL": "u32", + "SAPP_MODIFIER_ALT": "u32", + "SAPP_MODIFIER_SUPER": "u32", + "SAPP_MODIFIER_LMB": "u32", + "SAPP_MODIFIER_RMB": "u32", + "SAPP_MODIFIER_MMB": "u32", +} + +struct_types = [] +enum_types = [] +enum_items = {} +out_lines = "" + + +def reset_globals(): + global struct_types + global enum_types + global enum_items + global out_lines + struct_types = [] + enum_types = [] + enum_items = {} + out_lines = "" + + +def l(s): + global out_lines + out_lines += s + "\n" + + +def as_rust_prim_type(s): + return prim_types[s] + + +def as_upper_snake_case(s, prefix): + outp = s.lower() + if outp.startswith(prefix): + outp = outp[len(prefix):] + return outp.upper() + + +# prefix_bla_blub(_t) => (dep::)BlaBlub +def as_rust_struct_type(s, prefix): + parts = s.lower().split("_") + outp = "" if s.startswith(prefix) else f"{parts[0]}::" + for part in parts[1:]: + # ignore '_t' type postfix + if part != "t": + outp += part.capitalize() + return outp + + +# prefix_bla_blub(_t) => (dep::)BlaBlub +def as_rust_enum_type(s, prefix): + parts = s.lower().split("_") + outp = "" if s.startswith(prefix) else f"{parts[0]}::" + for part in parts[1:]: + # ignore '_t' type postfix + if part != "t": + outp += part.capitalize() + return outp + + +def check_override(name, default=None): + if name in overrides: + return overrides[name] + elif default is None: + return name + else: + return default + + +def check_ignore(name): + return name in ignores + + +# PREFIX_ENUM_BLA_BLA => BlaBla, _PREFIX_ENUM_BLA_BLA => BlaBla +def as_enum_item_name(s): + parts = s.lstrip("_").split("_") + outp = "" + for i, part in enumerate(parts[2:]): + # TODO: What to do with enum fields starting with numbers? + outp += part.capitalize() + + return outp + + +def enum_default_item(enum_name): + return enum_items[enum_name][0] + + +def is_prim_type(s): + return s in prim_types + + +def is_struct_type(s): + return s in struct_types + + +def is_enum_type(s): + return s in enum_types + + +def is_const_prim_ptr(s): + for prim_type in prim_types: + if s == f"const {prim_type} *": + return True + return False + + +def is_prim_ptr(s): + for prim_type in prim_types: + if s == f"{prim_type} *": + return True + return False + + +def is_const_struct_ptr(s): + for struct_type in struct_types: + if s == f"const {struct_type} *": + return True + return False + + +def is_struct_ptr(s): + for struct_type in struct_types: + if s == f"{struct_type} *": + return True + return False + + +def type_default_value(s): + return prim_defaults[s] + + +def as_c_arg_type(arg_prefix, arg_type, prefix): + # NOTE: if arg_prefix is None, the result is used as return value + pre = "" if arg_prefix is None else arg_prefix + + if arg_type == "void": + return "" + elif is_prim_type(arg_type): + return pre + as_rust_prim_type(arg_type) + elif is_struct_type(arg_type): + return pre + as_rust_struct_type(arg_type, prefix) + elif is_enum_type(arg_type): + return pre + as_rust_enum_type(arg_type, prefix) + elif util.is_void_ptr(arg_type): + return pre + "*mut core::ffi::c_void" + elif util.is_const_void_ptr(arg_type): + return pre + "*const core::ffi::c_void" + elif util.is_string_ptr(arg_type): + return pre + "*const core::ffi::c_char" + elif is_const_struct_ptr(arg_type): + return pre + f"*const {as_rust_struct_type(util.extract_ptr_type(arg_type), prefix)}" + elif is_struct_ptr(arg_type): + return pre + f"*mut {as_rust_struct_type(util.extract_ptr_type(arg_type), prefix)}" + elif is_prim_ptr(arg_type): + return pre + f"*mut {as_rust_prim_type(util.extract_ptr_type(arg_type))}" + elif is_const_prim_ptr(arg_type): + return pre + f"*const {as_rust_prim_type(util.extract_ptr_type(arg_type))}" + else: + sys.exit(f"ERROR as_c_arg_type(): {arg_type}") + + +def as_rust_arg_type(arg_prefix, arg_type, prefix): + # NOTE: if arg_prefix is None, the result is used as return value + pre = "" if arg_prefix is None else arg_prefix + + if arg_type == "void": + return "" + elif is_prim_type(arg_type): + return pre + as_rust_prim_type(arg_type) + elif is_struct_type(arg_type): + return pre + as_rust_struct_type(arg_type, prefix) + elif is_enum_type(arg_type): + return pre + as_rust_enum_type(arg_type, prefix) + elif util.is_void_ptr(arg_type): + return pre + "*mut core::ffi::c_void" + elif util.is_const_void_ptr(arg_type): + return pre + "*const core::ffi::c_void" + elif util.is_string_ptr(arg_type): + return pre + "&str" + elif is_const_struct_ptr(arg_type): + return pre + f"&{as_rust_struct_type(util.extract_ptr_type(arg_type), prefix)}" + elif is_struct_ptr(arg_type): + return pre + f"&mut {as_rust_struct_type(util.extract_ptr_type(arg_type), prefix)}" + elif is_prim_ptr(arg_type): + return pre + f"&mut {as_rust_prim_type(util.extract_ptr_type(arg_type))}" + elif is_const_prim_ptr(arg_type): + return pre + f"&{as_rust_prim_type(util.extract_ptr_type(arg_type))}" + else: + sys.exit(f"ERROR as_rust_arg_type(): {arg_type}") + + +def is_rust_string(rust_type): + return rust_type == "&str" or rust_type == " -> &'static str" + + +# get C-style arguments of a function pointer as string +def funcptr_args_c(field_type, prefix): + tokens = field_type[field_type.index("(*)") + 4: -1].split(",") + s = "" + for token in tokens: + arg_type = token.strip() + if s != "": + s += ", " + c_arg = as_c_arg_type(None, arg_type, prefix) + if c_arg == "void": + return "" + else: + s += c_arg + return s + + +# get C-style result of a function pointer as string +def funcptr_result_c(field_type): + res_type = field_type[: field_type.index("(*)")].strip() + if res_type == "void": + return "" + elif is_prim_type(res_type): + return f" -> {as_rust_prim_type(res_type)}" + elif util.is_const_void_ptr(res_type): + return " -> *const core::ffi::c_void" + elif util.is_void_ptr(res_type): + return " -> *mut core::ffi::c_void" + else: + sys.exit(f"ERROR funcptr_result_c(): {field_type}") + + +def funcdecl_args_c(decl, prefix): + s = "" + func_name = decl["name"] + for param_decl in decl["params"]: + if s != "": + s += ", " + param_name = param_decl["name"] + param_type = check_override( + f"{func_name}.{param_name}", default=param_decl["type"] + ) + s += f"{as_c_arg_type(f'{param_name}: ', param_type, prefix)}" + return s + + +def funcdecl_args_rust(decl, prefix): + s = "" + func_name = decl["name"] + for param_decl in decl["params"]: + if s != "": + s += ", " + param_name = param_decl["name"] + param_type = check_override( + f"{func_name}.{param_name}", default=param_decl["type"] + ) + s += f"{as_rust_arg_type(f'{param_name}: ', param_type, prefix)}" + return s + + +def funcdecl_result_c(decl, prefix): + func_name = decl["name"] + decl_type = decl["type"] + result_type = check_override( + f"{func_name}.RESULT", default=decl_type[: decl_type.index("(")].strip() + ) + + it = as_c_arg_type(None, result_type, prefix) + if it == "()" or it == "": + return "" + else: + return f" -> {it}" + + +def funcdecl_result_rust(decl, prefix): + func_name = decl["name"] + decl_type = decl["type"] + result_type = check_override( + f"{func_name}.RESULT", default=decl_type[: decl_type.index("(")].strip() + ) + rust_res_type = as_rust_arg_type(None, result_type, prefix) + + if is_rust_string(rust_res_type): + rust_res_type = "&'static str" + + if rust_res_type == "": + return "" + else: + return f" -> {rust_res_type }" + + +def gen_struct(decl, prefix): + struct_name = check_override(decl["name"]) + rust_type = as_rust_struct_type(struct_name, prefix) + rust_struct_type = rust_type + + struct_lines = [] + default_lines = [] + + for field in decl["fields"]: + field_name = check_override(field["name"]) + field_type = check_override( + f"{struct_name}.{field_name}", default=field["type"] + ) + + if is_prim_type(field_type): + struct_lines.append( + f"pub {field_name}: {as_rust_prim_type(field_type)}" + ) + default_lines.append( + f"{field_name}: {type_default_value(field_type)}" + ) + elif is_struct_type(field_type): + struct_lines.append( + f"pub {field_name}: {as_rust_struct_type(field_type, prefix)}" + ) + default_lines.append( + f"{field_name}: {as_rust_struct_type(field_type, prefix)}::new()" + ) + elif is_enum_type(field_type): + struct_lines.append( + f"pub {field_name}: {as_rust_enum_type(field_type, prefix)}" + ) + default_lines.append( + f"{field_name}: {as_rust_enum_type(field_type, prefix)}::new()" + ) + elif util.is_string_ptr(field_type): + struct_lines.append( + f"pub {field_name}: *const core::ffi::c_char" + ) + default_lines.append( + f"{field_name}: core::ptr::null()" + ) + elif util.is_const_void_ptr(field_type): + struct_lines.append( + f"pub {field_name}: *const core::ffi::c_void" + ) + default_lines.append( + f"{field_name}: core::ptr::null()" + ) + elif util.is_void_ptr(field_type): + struct_lines.append( + f"pub {field_name}: *mut core::ffi::c_void" + ) + default_lines.append( + f"{field_name}: core::ptr::null_mut()" + ) + elif is_const_prim_ptr(field_type): + struct_lines.append( + f"pub {field_name}: *const {as_rust_prim_type(util.extract_ptr_type(field_type))}" + ) + default_lines.append( + f"{field_name}: core::ptr::null()" + ) + elif is_prim_ptr(field_type): + struct_lines.append( + f"pub {field_name}: *mut {as_rust_prim_type(util.extract_ptr_type(field_type))}" + ) + default_lines.append( + f"{field_name}: core::ptr::null_mut()" + ) + elif is_const_struct_ptr(field_type): + struct_lines.append( + f"pub {field_name}: *const {as_rust_struct_type(util.extract_ptr_type(field_type), prefix)}" + ) + default_lines.append( + f"{field_name}: core::ptr::null()" + ) + elif is_struct_ptr(field_type): + struct_lines.append( + f"pub {field_name}: *mut {as_rust_struct_type(util.extract_ptr_type(field_type), prefix)}" + ) + default_lines.append( + f"{field_name}: core::ptr::null_mut()" + ) + elif util.is_func_ptr(field_type): + struct_lines.append( + f"pub {field_name}: Option" + ) + default_lines.append( + f"{field_name}: None" + ) + elif util.is_1d_array_type(field_type): + array_type = util.extract_array_type(field_type) + array_sizes = util.extract_array_sizes(field_type) + if is_prim_type(array_type) or is_struct_type(array_type): + if is_prim_type(array_type): + rust_type = as_rust_prim_type(array_type) + def_val = type_default_value(array_type) + elif is_struct_type(array_type) or is_enum_type(array_type): + rust_type = as_rust_struct_type(array_type, prefix) + def_val = f"{rust_type}::new()" + else: + sys.exit(f"ERROR gen_struct is_1d_array_type: {array_type}") + t0 = f"[{rust_type}; {array_sizes[0]}]" + # t1 = f"&{rust_type}" + struct_lines.append( + f"pub {field_name}: {t0}" + ) + default_lines.append( + f"{field_name}: [{def_val}; {array_sizes[0]}]" + ) + elif util.is_const_void_ptr(array_type): + struct_lines.append( + f"pub {field_name}: [*const core::ffi::c_void; {array_sizes[0]}]" + ) + default_lines.append( + f"{field_name}: [core::ptr::null(); {array_sizes[0]}]" + ) + else: + sys.exit( + f"ERROR gen_struct: array {field_name}: {field_type} => [{array_type}: {array_sizes[0]}]" + ) + elif util.is_2d_array_type(field_type): + array_type = util.extract_array_type(field_type) + array_sizes = util.extract_array_sizes(field_type) + + if is_prim_type(array_type): + rust_type = as_rust_prim_type(array_type) + def_val = type_default_value(array_type) + elif is_struct_type(array_type): + rust_type = as_rust_struct_type(array_type, prefix) + def_val = f"{rust_type}::new()" + else: + sys.exit(f"ERROR gen_struct is_2d_array_type: {array_type}") + + struct_lines.append( + f"pub {field_name}: [[{rust_type}; {array_sizes[1]}]; {array_sizes[0]}]" + ) + default_lines.append( + f"{field_name}: [[{def_val}; {array_sizes[1]}]; {array_sizes[0]}]" + ) + else: + sys.exit(f"ERROR gen_struct: {field_name}: {field_type};") + + # + # TODO: Is this the best way to have zero-initialization with support for constants? + # core::mem::zeroed() cleaner? + # + + l("#[repr(C)]") + l("#[derive(Copy, Clone, Debug)]") + l(f"pub struct {rust_struct_type} {{") + for line in struct_lines: + l(f" {line},") + l("}") + + l(f"impl {rust_struct_type} {{") + l(" pub const fn new() -> Self {") + l(" Self {") + for line in default_lines: + l(f" {line},") + l(" }") + l(" }") + l("}") + + l(f"impl Default for {rust_struct_type} {{") + l(" fn default() -> Self {") + l(" Self::new()") + l(" }") + l("}") + + +def gen_consts(decl, prefix): + for item in decl["items"]: + # + # TODO: What type should these constants have? Currently giving all `usize` + # unless specifically overridden by `special_constant_types` + # + + item_name = check_override(item["name"]) + if item_name in special_constant_types: + special_type = special_constant_types[item_name] + l(f"pub const {as_upper_snake_case(item_name, prefix)}: {special_type} = {item['value']};") + else: + l(f"pub const {as_upper_snake_case(item_name, prefix)}: usize = {item['value']};") + + +def gen_enum(decl, prefix): + enum_name = check_override(decl["name"]) + + names = [ + as_enum_item_name(check_override(f"{decl['name']}::{item['name']}", item['name'])) for item in decl["items"] + ] + + is_u32 = False + for name in names: + if name == "ForceU32": + is_u32 = True + break + + l("#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]") + if is_u32: + l("#[repr(u32)]") + else: + l("#[repr(i32)]") + + rust_enum_name = as_rust_enum_type(enum_name, prefix) + + l(f"pub enum {rust_enum_name} {{") + for item_name, item in zip(names, decl["items"]): + if item_name != "ForceU32": + if "value" in item: + l(f" {item_name} = {item['value']},") + else: + l(f" {item_name},") + l("}") + + default_item = enum_default_item(enum_name) + l(f"impl {rust_enum_name} {{") + l(" pub const fn new() -> Self {") + l(f" Self::{default_item}") + l(" }") + l("}") + + l(f"impl Default for {rust_enum_name} {{") + l(" fn default() -> Self {") + l(f" Self::{default_item}") + l(" }") + l("}") + + +def gen_func_c(decl, prefix): + l("pub extern \"C\" {") + l(f" fn {decl['name']}({funcdecl_args_c(decl, prefix)}){funcdecl_result_c(decl, prefix)};") + l("}") + + +def gen_c_funcs(funcs): + l("pub mod ffi {") + l(" #![allow(unused_imports)]") + l(" use super::*;") + l(" extern \"C\" {") + for decl, prefix in funcs: + l(f" pub fn {decl['name']}({funcdecl_args_c(decl, prefix)}){funcdecl_result_c(decl, prefix)};") + l(" }") + l("}") + + +def gen_rust_funcs(funcs): + for decl, prefix in funcs: + gen_func_rust(decl, prefix) + + +def gen_func_rust(decl, prefix): + c_func_name = decl["name"] + rust_func_name = util.as_lower_snake_case(check_override(decl["name"]), prefix) + rust_res_type = funcdecl_result_rust(decl, prefix) + + if c_func_name in c_callbacks: + c_res_type = funcdecl_result_c(decl, prefix) + l("#[inline]") + l(f'pub extern "C" fn {c_func_name}({funcdecl_args_c(decl, prefix)}){c_res_type} {{') + l(" unsafe {") + s = f" ffi::{c_func_name}(" + for i, param_decl in enumerate(decl["params"]): + if i > 0: + s += ", " + arg_name = param_decl["name"] + s += arg_name + s += ")" + l(s) + l(" }") + l("}") + else: + l("#[inline]") + l(f"pub fn {rust_func_name}({funcdecl_args_rust(decl, prefix)}){rust_res_type} {{") + for i, param_decl in enumerate(decl["params"]): + arg_name = param_decl["name"] + arg_type = param_decl["type"] + if util.is_string_ptr(arg_type): + l(f" let tmp_{i} = std::ffi::CString::new({arg_name}).unwrap();") + + l(" unsafe {") + if is_rust_string(rust_res_type): + # special case: convert C string to rust string slice + s = f" c_char_ptr_to_rust_str(ffi::{c_func_name}(" + else: + s = f" ffi::{c_func_name}(" + + for i, param_decl in enumerate(decl["params"]): + if i > 0: + s += ", " + arg_name = param_decl["name"] + arg_type = param_decl["type"] + + if util.is_string_ptr(arg_type): + s += f"tmp_{i}.as_ptr()" + else: + s += arg_name + + if is_rust_string(rust_res_type): + s += ")" + s += ")" + l(s) + l(" }") + l("}") + + +def pre_parse(inp): + global struct_types + global enum_types + for decl in inp["decls"]: + kind = decl["kind"] + if kind == "struct": + struct_types.append(decl["name"]) + elif kind == "enum": + enum_name = decl["name"] + enum_types.append(enum_name) + enum_items[enum_name] = [] + for item in decl["items"]: + enum_items[enum_name].append(as_enum_item_name(item["name"])) + +def gen_imports(inp, dep_prefixes): + for dep_prefix in dep_prefixes: + dep_module_name = module_names[dep_prefix] + # l(f'const {dep_prefix[:-1]} = @import("{dep_module_name}.rs");') + l(f'use crate::{dep_module_name} as {dep_prefix[:-1]};') + l("") + + +def gen_helpers(inp): + l("/// Helper function to convert a C string to a rust string slice") + l("#[inline]") + l("fn c_char_ptr_to_rust_str(c_char_ptr: *const core::ffi::c_char) -> &'static str {") + l(" let c_str = unsafe { core::ffi::CStr::from_ptr(c_char_ptr) };") + l(" c_str.to_str().expect(\"c_char_ptr contained invalid Utf8 Data\")") + l("}") + l("") + + if inp['prefix'] in ['sg_', 'sdtx_', 'sshape_', 'sapp_']: + l("/// Helper function to cast a rust slice into a sokol Range") + l(f"pub fn slice_as_range(data: &[T]) -> {range_struct_name} {{") + l(f" {range_struct_name} {{ size: std::mem::size_of_val(data), ptr: data.as_ptr() as *const _ }}") + l("}") + l("/// Helper function to cast a rust reference into a sokol Range") + l(f"pub fn value_as_range(value: &T) -> {range_struct_name} {{") + l(f" {range_struct_name} {{ size: std::mem::size_of::(), ptr: value as *const T as *const _ }}") + l("}") + l("") + l(f"impl From<&[T]> for {range_struct_name} {{") + l(" #[inline]") + l(" fn from(data: &[T]) -> Self {") + l(" slice_as_range(data)") + l(" }") + l("}") + l(f"impl From<&T> for {range_struct_name} {{") + l(" #[inline]") + l(" fn from(value: &T) -> Self {") + l(" value_as_range(value)") + l(" }") + l("}") + l("") + + # if inp["prefix"] == "sdtx_": + # l("/// std.fmt compatible Writer") + # l("pub const Writer = struct {") + # l(" pub const Error = error { };") + # l(" pub fn writeAll(self: Writer, bytes: []const u8) Error!void {") + # l(" _ = self;") + # l(" for (bytes) |byte| {") + # l(" putc(byte);") + # l(" }") + # l(" }") + # l(" pub fn writeByteNTimes(self: Writer, byte: u8, n: u64) Error!void {") + # l(" _ = self;") + # l(" var i: u64 = 0;") + # l(" while (i < n): (i += 1) {") + # l(" putc(byte);") + # l(" }") + # l(" }") + # l("};") + # l("// std.fmt-style formatted print") + # l("pub fn print(comptime fmt: anytype, args: anytype) void {") + # l(" var writer: Writer = .{};") + # l(' @import("std").fmt.format(writer, fmt, args) catch {};') + # l("}") + # l("") + + +def gen_module(inp, dep_prefixes): + module = inp['module'] + if module in module_requires_rust_feature: + feature = module_requires_rust_feature[module] + l(f"//! To use this module, enable the feature \"{feature}\"") + + l("// machine generated, do not edit") + l("") + + + l("#![allow(dead_code)]") + l("#![allow(unused_imports)]") + l("") + gen_imports(inp, dep_prefixes) + gen_helpers(inp) + pre_parse(inp) + prefix = inp["prefix"] + + funcs = [] + + for decl in inp["decls"]: + # + # HACK: gen_ir.py accidentally marks all sg_imgui_ declarations as is_dep since sg_imgui + # depends on sg_a but also starts with sg_... Fix gen_ir.py to remove this hack + # + dep_hack = False + if module == "gfx_imgui": + dep_hack = "name" in decl and decl["name"].startswith("sg_imgui_") + + if not decl["is_dep"] or dep_hack: + kind = decl["kind"] + if kind == "consts": + gen_consts(decl, prefix) + elif not check_ignore(decl["name"]): + if kind == "struct": + gen_struct(decl, prefix) + elif kind == "enum": + gen_enum(decl, prefix) + elif kind == "func": + funcs.append((decl, prefix)) + + gen_c_funcs(funcs) + gen_rust_funcs(funcs) + + +def prepare(): + print("=== Generating Rust bindings:") + if not os.path.isdir("sokol-rust/src/sokol"): + os.makedirs("sokol-rust/src/sokol") + if not os.path.isdir("sokol-rust/src/sokol/c"): + os.makedirs("sokol-rust/src/sokol/c") + + with open("sokol-rust/src/lib.rs", "w", newline="\n") as f_outp: + f_outp.write("//! Automatically generated sokol bindings for Rust\n\n") + + +def gen(c_header_path, c_prefix, dep_c_prefixes): + if c_prefix not in module_names: + print(f' >> warning: skipping generation for {c_prefix} prefix...') + return + + module_name = module_names[c_prefix] + c_source_path = c_source_paths[c_prefix] + print(f' {c_header_path} => {module_name}') + reset_globals() + c_path_in_project = f'sokol-rust/src/sokol/c/{os.path.basename(c_header_path)}' + shutil.copyfile(c_header_path, c_path_in_project) + ir = gen_ir.gen(c_header_path, c_source_path, module_name, c_prefix, dep_c_prefixes) + gen_module(ir, dep_c_prefixes) + output_path = f"sokol-rust/src/{ir['module']}.rs" + with open(output_path, 'w', newline='\n') as f_outp: + f_outp.write(out_lines) + + with open("sokol-rust/src/lib.rs", "a", newline="\n") as f_outp: + module = ir['module'] + if module in module_requires_rust_feature: + feature = module_requires_rust_feature[module] + f_outp.write(f"/// Enable feature \"{feature}\" to use\n") + f_outp.write(f"#[cfg(feature=\"{feature}\")]\n") + f_outp.write(f"pub mod {module};\n") diff --git a/source/engine/thirdparty/sokol/bindgen/gen_util.py b/source/engine/thirdparty/sokol/bindgen/gen_util.py new file mode 100644 index 0000000..a76f4b2 --- /dev/null +++ b/source/engine/thirdparty/sokol/bindgen/gen_util.py @@ -0,0 +1,57 @@ +# common utility functions for all bindings generators +import re + +re_1d_array = re.compile("^(?:const )?\w*\s*\*?\[\d*\]$") +re_2d_array = re.compile("^(?:const )?\w*\s*\*?\[\d*\]\[\d*\]$") + +def is_1d_array_type(s): + return re_1d_array.match(s) is not None + +def is_2d_array_type(s): + return re_2d_array.match(s) is not None + +def is_array_type(s): + return is_1d_array_type(s) or is_2d_array_type(s) + +def extract_array_type(s): + return s[:s.index('[')].strip() + +def extract_array_sizes(s): + return s[s.index('['):].replace('[', ' ').replace(']', ' ').split() + +def is_string_ptr(s): + return s == "const char *" + +def is_const_void_ptr(s): + return s == "const void *" + +def is_void_ptr(s): + return s == "void *" + +def is_func_ptr(s): + return '(*)' in s + +def extract_ptr_type(s): + tokens = s.split() + if tokens[0] == 'const': + return tokens[1] + else: + return tokens[0] + +# PREFIX_BLA_BLUB to bla_blub +def as_lower_snake_case(s, prefix): + outp = s.lower() + if outp.startswith(prefix): + outp = outp[len(prefix):] + return outp + +# prefix_bla_blub => blaBlub, PREFIX_BLA_BLUB => blaBlub +def as_lower_camel_case(s, prefix): + outp = s.lower() + if outp.startswith(prefix): + outp = outp[len(prefix):] + parts = outp.split('_') + outp = parts[0] + for part in parts[1:]: + outp += part.capitalize() + return outp diff --git a/source/engine/thirdparty/sokol/bindgen/gen_zig.py b/source/engine/thirdparty/sokol/bindgen/gen_zig.py new file mode 100644 index 0000000..bf7db05 --- /dev/null +++ b/source/engine/thirdparty/sokol/bindgen/gen_zig.py @@ -0,0 +1,552 @@ +#------------------------------------------------------------------------------- +# Generate Zig bindings. +# +# Zig coding style: +# - types are PascalCase +# - functions are camelCase +# - otherwise snake_case +#------------------------------------------------------------------------------- +import gen_ir +import os, shutil, sys + +import gen_util as util + +module_names = { + 'slog_': 'log', + 'sg_': 'gfx', + 'sapp_': 'app', + 'stm_': 'time', + 'saudio_': 'audio', + 'sgl_': 'gl', + 'sdtx_': 'debugtext', + 'sshape_': 'shape', + 'sglue_': 'glue', +} + +c_source_paths = { + 'slog_': 'sokol-zig/src/sokol/c/sokol_log.c', + 'sg_': 'sokol-zig/src/sokol/c/sokol_gfx.c', + 'sapp_': 'sokol-zig/src/sokol/c/sokol_app.c', + 'stm_': 'sokol-zig/src/sokol/c/sokol_time.c', + 'saudio_': 'sokol-zig/src/sokol/c/sokol_audio.c', + 'sgl_': 'sokol-zig/src/sokol/c/sokol_gl.c', + 'sdtx_': 'sokol-zig/src/sokol/c/sokol_debugtext.c', + 'sshape_': 'sokol-zig/src/sokol/c/sokol_shape.c', + 'sglue_': 'sokol-zig/src/sokol/c/sokol_glue.c', +} + +ignores = [ + 'sdtx_printf', + 'sdtx_vprintf', + 'sg_install_trace_hooks', + 'sg_trace_hooks', +] + +# functions that need to be exposed as 'raw' C callbacks without a Zig wrapper function +c_callbacks = [ + 'slog_func' +] + +# NOTE: syntax for function results: "func_name.RESULT" +overrides = { + 'sgl_error': 'sgl_get_error', # 'error' is reserved in Zig + 'sgl_deg': 'sgl_as_degrees', + 'sgl_rad': 'sgl_as_radians', + 'sg_apply_uniforms.ub_index': 'uint32_t', + 'sg_draw.base_element': 'uint32_t', + 'sg_draw.num_elements': 'uint32_t', + 'sg_draw.num_instances': 'uint32_t', + 'sshape_element_range_t.base_element': 'uint32_t', + 'sshape_element_range_t.num_elements': 'uint32_t', + 'sdtx_font.font_index': 'uint32_t', + 'SGL_NO_ERROR': 'SGL_ERROR_NO_ERROR', +} + +prim_types = { + 'int': 'i32', + 'bool': 'bool', + 'char': 'u8', + 'int8_t': 'i8', + 'uint8_t': 'u8', + 'int16_t': 'i16', + 'uint16_t': 'u16', + 'int32_t': 'i32', + 'uint32_t': 'u32', + 'int64_t': 'i64', + 'uint64_t': 'u64', + 'float': 'f32', + 'double': 'f64', + 'uintptr_t': 'usize', + 'intptr_t': 'isize', + 'size_t': 'usize' +} + +prim_defaults = { + 'int': '0', + 'bool': 'false', + 'int8_t': '0', + 'uint8_t': '0', + 'int16_t': '0', + 'uint16_t': '0', + 'int32_t': '0', + 'uint32_t': '0', + 'int64_t': '0', + 'uint64_t': '0', + 'float': '0.0', + 'double': '0.0', + 'uintptr_t': '0', + 'intptr_t': '0', + 'size_t': '0' +} + + +struct_types = [] +enum_types = [] +enum_items = {} +out_lines = '' + +def reset_globals(): + global struct_types + global enum_types + global enum_items + global out_lines + struct_types = [] + enum_types = [] + enum_items = {} + out_lines = '' + +def l(s): + global out_lines + out_lines += s + '\n' + +def as_zig_prim_type(s): + return prim_types[s] + +# prefix_bla_blub(_t) => (dep.)BlaBlub +def as_zig_struct_type(s, prefix): + parts = s.lower().split('_') + outp = '' if s.startswith(prefix) else f'{parts[0]}.' + for part in parts[1:]: + # ignore '_t' type postfix + if (part != 't'): + outp += part.capitalize() + return outp + +# prefix_bla_blub(_t) => (dep.)BlaBlub +def as_zig_enum_type(s, prefix): + parts = s.lower().split('_') + outp = '' if s.startswith(prefix) else f'{parts[0]}.' + for part in parts[1:]: + if (part != 't'): + outp += part.capitalize() + return outp + +def check_override(name, default=None): + if name in overrides: + return overrides[name] + elif default is None: + return name + else: + return default + +def check_ignore(name): + return name in ignores + +# PREFIX_ENUM_BLA => Bla, _PREFIX_ENUM_BLA => Bla +def as_enum_item_name(s): + outp = s.lstrip('_') + parts = outp.split('_')[2:] + outp = '_'.join(parts) + if outp[0].isdigit(): + outp = '_' + outp + return outp + +def enum_default_item(enum_name): + return enum_items[enum_name][0] + +def is_prim_type(s): + return s in prim_types + +def is_struct_type(s): + return s in struct_types + +def is_enum_type(s): + return s in enum_types + +def is_const_prim_ptr(s): + for prim_type in prim_types: + if s == f"const {prim_type} *": + return True + return False + +def is_prim_ptr(s): + for prim_type in prim_types: + if s == f"{prim_type} *": + return True + return False + +def is_const_struct_ptr(s): + for struct_type in struct_types: + if s == f"const {struct_type} *": + return True + return False + +def type_default_value(s): + return prim_defaults[s] + +def as_c_arg_type(arg_type, prefix): + if arg_type == "void": + return "void" + elif is_prim_type(arg_type): + return as_zig_prim_type(arg_type) + elif is_struct_type(arg_type): + return as_zig_struct_type(arg_type, prefix) + elif is_enum_type(arg_type): + return as_zig_enum_type(arg_type, prefix) + elif util.is_void_ptr(arg_type): + return "?*anyopaque" + elif util.is_const_void_ptr(arg_type): + return "?*const anyopaque" + elif util.is_string_ptr(arg_type): + return "[*c]const u8" + elif is_const_struct_ptr(arg_type): + return f"[*c]const {as_zig_struct_type(util.extract_ptr_type(arg_type), prefix)}" + elif is_prim_ptr(arg_type): + return f"[*c]{as_zig_prim_type(util.extract_ptr_type(arg_type))}" + elif is_const_prim_ptr(arg_type): + return f"[*c]const {as_zig_prim_type(util.extract_ptr_type(arg_type))}" + else: + sys.exit(f"Error as_c_arg_type(): {arg_type}") + +def as_zig_arg_type(arg_prefix, arg_type, prefix): + # NOTE: if arg_prefix is None, the result is used as return value + pre = "" if arg_prefix is None else arg_prefix + if arg_type == "void": + if arg_prefix is None: + return "void" + else: + return "" + elif is_prim_type(arg_type): + return pre + as_zig_prim_type(arg_type) + elif is_struct_type(arg_type): + return pre + as_zig_struct_type(arg_type, prefix) + elif is_enum_type(arg_type): + return pre + as_zig_enum_type(arg_type, prefix) + elif util.is_void_ptr(arg_type): + return pre + "?*anyopaque" + elif util.is_const_void_ptr(arg_type): + return pre + "?*const anyopaque" + elif util.is_string_ptr(arg_type): + return pre + "[:0]const u8" + elif is_const_struct_ptr(arg_type): + # not a bug, pass const structs by value + return pre + f"{as_zig_struct_type(util.extract_ptr_type(arg_type), prefix)}" + elif is_prim_ptr(arg_type): + return pre + f"*{as_zig_prim_type(util.extract_ptr_type(arg_type))}" + elif is_const_prim_ptr(arg_type): + return pre + f"*const {as_zig_prim_type(util.extract_ptr_type(arg_type))}" + else: + sys.exit(f"ERROR as_zig_arg_type(): {arg_type}") + +def is_zig_string(zig_type): + return zig_type == "[:0]const u8" + +# get C-style arguments of a function pointer as string +def funcptr_args_c(field_type, prefix): + tokens = field_type[field_type.index('(*)')+4:-1].split(',') + s = "" + for token in tokens: + arg_type = token.strip() + if s != "": + s += ", " + c_arg = as_c_arg_type(arg_type, prefix) + if c_arg == "void": + return "" + else: + s += c_arg + return s + +# get C-style result of a function pointer as string +def funcptr_result_c(field_type): + res_type = field_type[:field_type.index('(*)')].strip() + if res_type == 'void': + return 'void' + elif is_prim_type(res_type): + return as_zig_prim_type(res_type) + elif util.is_const_void_ptr(res_type): + return '?*const anyopaque' + elif util.is_void_ptr(res_type): + return '?*anyopaque' + else: + sys.exit(f"ERROR funcptr_result_c(): {field_type}") + +def funcdecl_args_c(decl, prefix): + s = "" + func_name = decl['name'] + for param_decl in decl['params']: + if s != "": + s += ", " + param_name = param_decl['name'] + param_type = check_override(f'{func_name}.{param_name}', default=param_decl['type']) + s += as_c_arg_type(param_type, prefix) + return s + +def funcdecl_args_zig(decl, prefix): + s = "" + func_name = decl['name'] + for param_decl in decl['params']: + if s != "": + s += ", " + param_name = param_decl['name'] + param_type = check_override(f'{func_name}.{param_name}', default=param_decl['type']) + s += f"{as_zig_arg_type(f'{param_name}: ', param_type, prefix)}" + return s + +def funcdecl_result_c(decl, prefix): + func_name = decl['name'] + decl_type = decl['type'] + result_type = check_override(f'{func_name}.RESULT', default=decl_type[:decl_type.index('(')].strip()) + return as_c_arg_type(result_type, prefix) + +def funcdecl_result_zig(decl, prefix): + func_name = decl['name'] + decl_type = decl['type'] + result_type = check_override(f'{func_name}.RESULT', default=decl_type[:decl_type.index('(')].strip()) + zig_res_type = as_zig_arg_type(None, result_type, prefix) + return zig_res_type + +def gen_struct(decl, prefix): + struct_name = check_override(decl['name']) + zig_type = as_zig_struct_type(struct_name, prefix) + l(f"pub const {zig_type} = extern struct {{") + for field in decl['fields']: + field_name = check_override(field['name']) + field_type = check_override(f'{struct_name}.{field_name}', default=field['type']) + if is_prim_type(field_type): + l(f" {field_name}: {as_zig_prim_type(field_type)} = {type_default_value(field_type)},") + elif is_struct_type(field_type): + l(f" {field_name}: {as_zig_struct_type(field_type, prefix)} = .{{}},") + elif is_enum_type(field_type): + l(f" {field_name}: {as_zig_enum_type(field_type, prefix)} = .{enum_default_item(field_type)},") + elif util.is_string_ptr(field_type): + l(f" {field_name}: [*c]const u8 = null,") + elif util.is_const_void_ptr(field_type): + l(f" {field_name}: ?*const anyopaque = null,") + elif util.is_void_ptr(field_type): + l(f" {field_name}: ?*anyopaque = null,") + elif is_const_prim_ptr(field_type): + l(f" {field_name}: ?[*]const {as_zig_prim_type(util.extract_ptr_type(field_type))} = null,") + elif util.is_func_ptr(field_type): + l(f" {field_name}: ?*const fn ({funcptr_args_c(field_type, prefix)}) callconv(.C) {funcptr_result_c(field_type)} = null,") + elif util.is_1d_array_type(field_type): + array_type = util.extract_array_type(field_type) + array_sizes = util.extract_array_sizes(field_type) + if is_prim_type(array_type) or is_struct_type(array_type): + if is_prim_type(array_type): + zig_type = as_zig_prim_type(array_type) + def_val = type_default_value(array_type) + elif is_struct_type(array_type): + zig_type = as_zig_struct_type(array_type, prefix) + def_val = '.{}' + elif is_enum_type(array_type): + zig_type = as_zig_enum_type(array_type, prefix) + def_val = '.{}' + else: + sys.exit(f"ERROR gen_struct is_1d_array_type: {array_type}") + t0 = f"[{array_sizes[0]}]{zig_type}" + t1 = f"[_]{zig_type}" + l(f" {field_name}: {t0} = {t1}{{{def_val}}} ** {array_sizes[0]},") + elif util.is_const_void_ptr(array_type): + l(f" {field_name}: [{array_sizes[0]}]?*const anyopaque = [_]?*const anyopaque{{null}} ** {array_sizes[0]},") + else: + sys.exit(f"ERROR gen_struct: array {field_name}: {field_type} => {array_type} [{array_sizes[0]}]") + elif util.is_2d_array_type(field_type): + array_type = util.extract_array_type(field_type) + array_sizes = util.extract_array_sizes(field_type) + if is_prim_type(array_type): + zig_type = as_zig_prim_type(array_type) + def_val = type_default_value(array_type) + elif is_struct_type(array_type): + zig_type = as_zig_struct_type(array_type, prefix) + def_val = ".{}" + else: + sys.exit(f"ERROR gen_struct is_2d_array_type: {array_type}") + t0 = f"[{array_sizes[0]}][{array_sizes[1]}]{zig_type}" + l(f" {field_name}: {t0} = [_][{array_sizes[1]}]{zig_type}{{[_]{zig_type}{{{def_val}}} ** {array_sizes[1]}}} ** {array_sizes[0]},") + else: + sys.exit(f"ERROR gen_struct: {field_name}: {field_type};") + l("};") + +def gen_consts(decl, prefix): + for item in decl['items']: + item_name = check_override(item['name']) + l(f"pub const {util.as_lower_snake_case(item_name, prefix)} = {item['value']};") + +def gen_enum(decl, prefix): + enum_name = check_override(decl['name']) + l(f"pub const {as_zig_enum_type(enum_name, prefix)} = enum(i32) {{") + for item in decl['items']: + item_name = as_enum_item_name(check_override(item['name'])) + if item_name != "FORCE_U32": + if 'value' in item: + l(f" {item_name} = {item['value']},") + else: + l(f" {item_name},") + l("};") + +def gen_func_c(decl, prefix): + l(f"pub extern fn {decl['name']}({funcdecl_args_c(decl, prefix)}) {funcdecl_result_c(decl, prefix)};") + +def gen_func_zig(decl, prefix): + c_func_name = decl['name'] + zig_func_name = util.as_lower_camel_case(check_override(decl['name']), prefix) + if c_func_name in c_callbacks: + # a simple forwarded C callback function + l(f"pub const {zig_func_name} = {c_func_name};") + else: + zig_res_type = funcdecl_result_zig(decl, prefix) + l(f"pub fn {zig_func_name}({funcdecl_args_zig(decl, prefix)}) {zig_res_type} {{") + if is_zig_string(zig_res_type): + # special case: convert C string to Zig string slice + s = f" return cStrToZig({c_func_name}(" + elif zig_res_type != 'void': + s = f" return {c_func_name}(" + else: + s = f" {c_func_name}(" + for i, param_decl in enumerate(decl['params']): + if i > 0: + s += ", " + arg_name = param_decl['name'] + arg_type = param_decl['type'] + if is_const_struct_ptr(arg_type): + s += f"&{arg_name}" + elif util.is_string_ptr(arg_type): + s += f"@ptrCast({arg_name})" + else: + s += arg_name + if is_zig_string(zig_res_type): + s += ")" + s += ");" + l(s) + l("}") + +def pre_parse(inp): + global struct_types + global enum_types + for decl in inp['decls']: + kind = decl['kind'] + if kind == 'struct': + struct_types.append(decl['name']) + elif kind == 'enum': + enum_name = decl['name'] + enum_types.append(enum_name) + enum_items[enum_name] = [] + for item in decl['items']: + enum_items[enum_name].append(as_enum_item_name(item['name'])) + +def gen_imports(inp, dep_prefixes): + l('const builtin = @import("builtin");') + for dep_prefix in dep_prefixes: + dep_module_name = module_names[dep_prefix] + l(f'const {dep_prefix[:-1]} = @import("{dep_module_name}.zig");') + l('') + +def gen_helpers(inp): + l('// helper function to convert a C string to a Zig string slice') + l('fn cStrToZig(c_str: [*c]const u8) [:0]const u8 {') + l(' return @import("std").mem.span(c_str);') + l('}') + if inp['prefix'] in ['sg_', 'sdtx_', 'sshape_']: + l('// helper function to convert "anything" to a Range struct') + l('pub fn asRange(val: anytype) Range {') + l(' const type_info = @typeInfo(@TypeOf(val));') + l(' switch (type_info) {') + l(' .Pointer => {') + l(' switch (type_info.Pointer.size) {') + l(' .One => return .{ .ptr = val, .size = @sizeOf(type_info.Pointer.child) },') + l(' .Slice => return .{ .ptr = val.ptr, .size = @sizeOf(type_info.Pointer.child) * val.len },') + l(' else => @compileError("FIXME: Pointer type!"),') + l(' }') + l(' },') + l(' .Struct, .Array => {') + l(' @compileError("Structs and arrays must be passed as pointers to asRange");') + l(' },') + l(' else => {') + l(' @compileError("Cannot convert to range!");') + l(' },') + l(' }') + l('}') + l('') + if inp['prefix'] == 'sdtx_': + l('// std.fmt compatible Writer') + l('pub const Writer = struct {') + l(' pub const Error = error{};') + l(' pub fn writeAll(self: Writer, bytes: []const u8) Error!void {') + l(' _ = self;') + l(' for (bytes) |byte| {') + l(' putc(byte);') + l(' }') + l(' }') + l(' pub fn writeByteNTimes(self: Writer, byte: u8, n: usize) Error!void {') + l(' _ = self;') + l(' var i: u64 = 0;') + l(' while (i < n) : (i += 1) {') + l(' putc(byte);') + l(' }') + l(' }') + l(' pub fn writeBytesNTimes(self: Writer, bytes: []const u8, n: usize) Error!void {') + l(' var i: usize = 0;') + l(' while (i < n) : (i += 1) {') + l(' try self.writeAll(bytes);') + l(' }') + l(' }') + l('};') + l('// std.fmt-style formatted print') + l('pub fn print(comptime fmt: anytype, args: anytype) void {') + l(' const writer: Writer = .{};') + l(' @import("std").fmt.format(writer, fmt, args) catch {};') + l('}') + l('') + +def gen_module(inp, dep_prefixes): + l('// machine generated, do not edit') + l('') + gen_imports(inp, dep_prefixes) + gen_helpers(inp) + pre_parse(inp) + prefix = inp['prefix'] + for decl in inp['decls']: + if not decl['is_dep']: + kind = decl['kind'] + if kind == 'consts': + gen_consts(decl, prefix) + elif not check_ignore(decl['name']): + if kind == 'struct': + gen_struct(decl, prefix) + elif kind == 'enum': + gen_enum(decl, prefix) + elif kind == 'func': + gen_func_c(decl, prefix) + gen_func_zig(decl, prefix) + +def prepare(): + print('=== Generating Zig bindings:') + if not os.path.isdir('sokol-zig/src/sokol'): + os.makedirs('sokol-zig/src/sokol') + if not os.path.isdir('sokol-zig/src/sokol/c'): + os.makedirs('sokol-zig/src/sokol/c') + +def gen(c_header_path, c_prefix, dep_c_prefixes): + if not c_prefix in module_names: + print(f' >> warning: skipping generation for {c_prefix} prefix...') + return + module_name = module_names[c_prefix] + c_source_path = c_source_paths[c_prefix] + print(f' {c_header_path} => {module_name}') + reset_globals() + shutil.copyfile(c_header_path, f'sokol-zig/src/sokol/c/{os.path.basename(c_header_path)}') + ir = gen_ir.gen(c_header_path, c_source_path, module_name, c_prefix, dep_c_prefixes) + gen_module(ir, dep_c_prefixes) + output_path = f"sokol-zig/src/sokol/{ir['module']}.zig" + with open(output_path, 'w', newline='\n') as f_outp: + f_outp.write(out_lines) diff --git a/source/engine/thirdparty/sokol/fips.yml b/source/engine/thirdparty/sokol/fips.yml new file mode 100644 index 0000000..372d8fd --- /dev/null +++ b/source/engine/thirdparty/sokol/fips.yml @@ -0,0 +1,2 @@ +exports: + header-dirs: [ ".", "util" ] diff --git a/source/engine/thirdparty/sokol/sokol_app.h b/source/engine/thirdparty/sokol/sokol_app.h new file mode 100644 index 0000000..db73ebb --- /dev/null +++ b/source/engine/thirdparty/sokol/sokol_app.h @@ -0,0 +1,11770 @@ +#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_GLCORE33 + #define SOKOL_GLES3 + #define SOKOL_D3D11 + #define SOKOL_METAL + #define SOKOL_WGPU + + 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 use a main() entry point instead of WinMain + 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. + + On Linux, SOKOL_GLCORE33 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, GL3.2, GLES3, WebGL, WebGL2 + + FEATURE/PLATFORM MATRIX + ======================= + | Windows | macOS | Linux | iOS | Android | HTML5 + --------------------+---------+-------+-------+-------+---------+-------- + gl 3.x | YES | YES | YES | --- | --- | --- + gles3/webgl2 | --- | --- | YES(2)| YES | YES | YES + metal | --- | YES | --- | YES | --- | --- + d3d11 | YES | --- | --- | --- | --- | --- + 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 | TODO | --- | --- | 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 tunnel through C code. Also note that the returned + pointers to the renderpass-descriptor and drawable 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_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. + + const uint32_t sapp_gl_get_framebuffer(void) + This returns the 'default framebuffer' of the GL context. + Typically this will be zero. + + 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 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()) { ... } + + On native platforms, the sapp_lock_mouse() and sapp_mouse_locked() + functions work as expected (mouse lock is activated or deactivated + immediately when sapp_lock_mouse() is called, and sapp_mouse_locked() + also immediately returns the new state after sapp_lock_mouse() + is called. + + On the web platform, sapp_lock_mouse() and sapp_mouse_locked() behave + differently, as dictated 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; + } + } + + 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 + + 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 no 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 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. + + 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, 3.2 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_3_2_NOT_SUPPORTED, "OpenGL 3.2 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(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; + +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 major and minor version (the default GL version is 3.2) + 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_name; // the name (id) 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_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); + +/* 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 + +// this WinRT specific hack is required when wWinMain is in a static library +#if defined(_MSC_VER) && defined(UNICODE) +#include +#if defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#pragma comment(linker, "/include:wWinMain") +#endif +#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 +#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_GLCORE33) + #error("sokol_app.h: unknown 3D API selected for MacOS, must be SOKOL_METAL or SOKOL_GLCORE33") + #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_GLCORE33) + #error("sokol_app.h: unknown 3D API selected for Win32, must be SOKOL_D3D11 or SOKOL_GLCORE33") + #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_GLCORE33) + #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_GLCORE33, SOKOL_GLES3") + #endif +#else +#error "sokol_app.h: Unknown platform" +#endif + +#if defined(SOKOL_GLCORE33) || 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 applications' responsibility to use the right subsystem + #if 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 +#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_GLCORE33) + @interface _sapp_macos_view : NSOpenGLView + - (void)timerFired:(id)sender; + @end +#endif // SOKOL_GLCORE33 + +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; + LONG mouse_locked_x, mouse_locked_y; + 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; + bool mouse_tracked; + uint8_t mouse_capture_mask; + _sapp_win32_dpi_t dpi; + bool raw_input_mousepos_valid; + LONG raw_input_mousepos_x; + LONG raw_input_mousepos_y; + uint8_t raw_input_data[256]; +} _sapp_win32_t; + +#if defined(SOKOL_GLCORE33) +#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_GLCORE33 + +#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 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 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; +} _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_GLCORE33) + _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, 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); + // NOTE: can't patch the default for gl_major_version and gl_minor_version + // independently, because a desired version 4.0 would be patched to 4.2 + // (or expressed differently: zero is a valid value for gl_minor_version + // and can't be used to indicate 'default') + if (0 == res.gl_major_version) { + res.gl_major_version = 3; + res.gl_minor_version = 2; + } + res.html5_canvas_name = _sapp_def(res.html5_canvas_name, "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_app"); + 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.html5_canvas_selector[0] = '#'; + _sapp_strcpy(_sapp.desc.html5_canvas_name, &_sapp.html5_canvas_selector[1], sizeof(_sapp.html5_canvas_selector) - 1); + _sapp.desc.html5_canvas_name = &_sapp.html5_canvas_selector[1]; + _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_GLCORE33) + 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_GLCORE33) + 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_GLCORE33) +- (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_GLCORE33) + [[_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); + } + } +} +- (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"); +#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, (const char* canvas_name_cstr), { + Module.sokol_drop_files = []; + const canvas_name = UTF8ToString(canvas_name_cstr); + const canvas = document.getElementById(canvas_name); + 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); + }; + 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, (const char* canvas_name_cstr), { + const canvas_name = UTF8ToString(canvas_name_cstr); + const canvas = document.getElementById(canvas_name); + 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), { + // lookup and store canvas object by name + const target_str = UTF8ToString(c_str_target); + Module.sapp_emsc_target = document.getElementById(target_str); + if (!Module.sapp_emsc_target) { + console.log("sokol_app.h: invalid target:" + target_str); + } + if (!Module.sapp_emsc_target.requestPointerLock) { + console.log("sokol_app.h: target doesn't support requestPointerLock:" + target_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); + + // FIXME: remove PVRTC support here and in sokol-gfx at some point + // some WebGL extension are not enabled automatically by emscripten + emscripten_webgl_enable_extension(ctx, "WEBKIT_WEBGL_compressed_texture_pvrtc"); +} +#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; + WGPUFeatureName requiredFeatures[8] = { + WGPUFeatureName_Depth32FloatStencil8, + }; + // check for optional features we're interested in + // FIXME: ASTC texture compression + if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_TextureCompressionBC)) { + requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionBC; + } else if (wgpuAdapterHasFeature(adapter, WGPUFeatureName_TextureCompressionETC2)) { + requiredFeatures[cur_feature_index++] = WGPUFeatureName_TextureCompressionETC2; + } + + 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(&_sapp.html5_canvas_selector[1]); + } + #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(&_sapp.html5_canvas_selector[1]); + } + #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); + sapp_js_init(&_sapp.html5_canvas_selector[1]); + 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_GLCORE33) +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[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 &= ~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; + + /* 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_GLCORE33) +_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_3_2_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_GLCORE33 */ + +_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 void _sapp_win32_lock_mouse(bool lock) { + if (lock == _sapp.mouse.locked) { + return; + } + _sapp.mouse.dx = 0.0f; + _sapp.mouse.dy = 0.0f; + _sapp.mouse.locked = lock; + _sapp_win32_release_mouse(0xFF); + if (_sapp.mouse.locked) { + /* store the current mouse position, so it can be restored when unlocked */ + POINT pos; + BOOL res = GetCursorPos(&pos); + SOKOL_ASSERT(res); _SOKOL_UNUSED(res); + _sapp.win32.mouse_locked_x = pos.x; + _sapp.win32.mouse_locked_y = pos.y; + + /* while the mouse is locked, make the mouse cursor invisible and + confine the mouse movement to a small rectangle inside our window + (so that we don't miss any mouse up events) + */ + RECT client_rect = { + _sapp.win32.mouse_locked_x, + _sapp.win32.mouse_locked_y, + _sapp.win32.mouse_locked_x, + _sapp.win32.mouse_locked_y + }; + ClipCursor(&client_rect); + + /* make the mouse cursor invisible, this will stack with sapp_show_mouse() */ + ShowCursor(FALSE); + + /* 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.raw_input_mousepos_valid = false; + } + else { + /* 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); + } + + /* let the mouse roam freely again */ + ClipCursor(NULL); + ShowCursor(TRUE); + + /* restore the 'pre-locked' mouse position */ + BOOL res = SetCursorPos(_sapp.win32.mouse_locked_x, _sapp.win32.mouse_locked_y); + SOKOL_ASSERT(res); _SOKOL_UNUSED(res); + } +} + +_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_GLCORE33) + _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: + /* if focus is lost for any reason, and we're in mouse locked mode, disable mouse lock */ + if (_sapp.mouse.locked) { + _sapp_win32_lock_mouse(false); + } + _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.raw_input_mousepos_valid) { + _sapp.mouse.dx = (float) (new_x - _sapp.win32.raw_input_mousepos_x); + _sapp.mouse.dy = (float) (new_y - _sapp.win32.raw_input_mousepos_y); + } + _sapp.win32.raw_input_mousepos_x = new_x; + _sapp.win32.raw_input_mousepos_y = new_y; + _sapp.win32.raw_input_mousepos_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_GLCORE33) + _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; + 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_GLCORE33) + _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_GLCORE33) + _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); + } + } + _sapp_call_cleanup(); + + #if defined(SOKOL_D3D11) + _sapp_d3d11_destroy_default_render_target(); + _sapp_d3d11_destroy_device_and_swapchain(); + #else + _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; +} +#else +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_MAIN */ +#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_CLIENT_VERSION, 3, + 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); + 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; + } + } +} + +_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_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) { + int dummy; + KeySym* keysyms = XGetKeyboardMapping(_sapp.x11.display, scancode, 1, &dummy); + SOKOL_ASSERT(keysyms); + KeySym keysym = keysyms[0]; + XFree(keysyms); + switch (keysym) { + 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; + + 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; + + 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_Begin: return SAPP_KEYCODE_KP_5; + 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; + + 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: 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_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; + } +} + +// XLib manual says keycodes are in the range [8, 255] inclusive. +// https://tronche.com/gui/x/xlib/input/keyboard-encoding.html +static bool _sapp_x11_keycodes[256]; + +_SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { + Bool filtered = XFilterEvent(event, None); + switch (event->type) { + case GenericEvent: + 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); + } + } + } + break; + case FocusIn: + // 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); + } + break; + case FocusOut: + /* 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); + } + break; + case KeyPress: + { + int keycode = (int)event->xkey.keycode; + const sapp_keycode key = _sapp_x11_translate_key(keycode); + bool repeat = _sapp_x11_keycodes[keycode & 0xFF]; + _sapp_x11_keycodes[keycode & 0xFF] = true; + 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); + } + } + break; + case KeyRelease: + { + int keycode = (int)event->xkey.keycode; + const sapp_keycode key = _sapp_x11_translate_key(keycode); + _sapp_x11_keycodes[keycode & 0xFF] = false; + 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); + } + } + break; + case ButtonPress: + { + _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; + } + } + } + break; + case ButtonRelease: + { + _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); + } + } + break; + case EnterNotify: + /* 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)); + } + break; + case LeaveNotify: + 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)); + } + break; + case MotionNotify: + 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)); + } + break; + case ConfigureNotify: + 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); + } + break; + case PropertyNotify: + 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); + } + } + } + } + break; + case ClientMessage: + if (filtered) { + 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); + } + break; + case SelectionNotify: + 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); + } + } + break; + case DestroyNotify: + break; + } +} + +#if !defined(_SAPP_GLX) + +_SOKOL_PRIVATE void _sapp_egl_init(void) { +#if defined(SOKOL_GLCORE33) + 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_GLCORE33) + 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[] = { + #if defined(SOKOL_GLCORE33) + EGL_CONTEXT_MAJOR_VERSION, _sapp.desc.gl_major_version, + EGL_CONTEXT_MINOR_VERSION, _sapp.desc.gl_minor_version, + EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + #elif defined(SOKOL_GLES3) + EGL_CONTEXT_CLIENT_VERSION, 3, + #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); + XkbSetDetectableAutoRepeat(_sapp.x11.display, true, NULL); + _sapp_x11_query_system_dpi(); + _sapp.dpi_scale = _sapp.x11.dpi / 96.0f; + _sapp_x11_init_extensions(); + _sapp_x11_create_cursors(); +#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); + #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(); + #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 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/source/engine/thirdparty/sokol/sokol_args.h b/source/engine/thirdparty/sokol/sokol_args.h new file mode 100644 index 0000000..b5b4d47 --- /dev/null +++ b/source/engine/thirdparty/sokol/sokol_args.h @@ -0,0 +1,867 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_ARGS_IMPL) +#define SOKOL_ARGS_IMPL +#endif +#ifndef SOKOL_ARGS_INCLUDED +/* + sokol_args.h -- cross-platform key/value arg-parsing for web and native + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_ARGS_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_ARGS_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_ARGS_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_args.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_ARGS_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + OVERVIEW + ======== + sokol_args.h provides a simple unified argument parsing API for WebAssembly and + native apps. + + When running as a WebAssembly app, arguments are taken from the page URL: + + https://floooh.github.io/tiny8bit/kc85.html?type=kc85_3&mod=m022&snapshot=kc85/jungle.kcc + + The same arguments provided to a command line app: + + kc85 type=kc85_3 mod=m022 snapshot=kc85/jungle.kcc + + You can also use standalone keys without value: + + https://floooh.github.io/tiny8bit/kc85.html?bla&blub + + On the command line: + + kc85 bla blub + + Such value-less keys are reported as the value being an empty string, but they + can be tested with `sapp_exists("bla")` or `sapp_boolean("blub")`. + + ARGUMENT FORMATTING + =================== + On the web platform, arguments must be formatted as a valid URL query string + with 'percent encoding' used for special characters. + + Strings are expected to be UTF-8 encoded (although sokol_args.h doesn't + contain any special UTF-8 handling). See below on how to obtain + UTF-8 encoded argc/argv values on Windows when using WinMain() as + entry point. + + On native platforms the following rules must be followed: + + Arguments have the general form + + key=value + + or + + key + + When a key has no value, the value will be assigned an empty string. + + Key/value pairs are separated by 'whitespace', valid whitespace + characters are space and tab. + + Whitespace characters in front and after the separating '=' character + are ignored: + + key = value + + ...is the same as + + key=value + + The 'key' string must be a simple string without escape sequences or whitespace. + + The 'value' string can be quoted, and quoted value strings can contain + whitespace: + + key = 'single-quoted value' + key = "double-quoted value" + + Single-quoted value strings can contain double quotes, and vice-versa: + + key = 'single-quoted value "can contain double-quotes"' + key = "double-quoted value 'can contain single-quotes'" + + Note that correct quoting can be tricky on some shells, since command + shells may remove quotes, unless they're escaped. + + Value strings can contain a small selection of escape sequences: + + \n - newline + \r - carriage return + \t - tab + \\ - escaped backslash + + (more escape codes may be added in the future). + + CODE EXAMPLE + ============ + + int main(int argc, char* argv[]) { + // initialize sokol_args with default parameters + sargs_setup(&(sargs_desc){ + .argc = argc, + .argv = argv + }); + + // check if a key exists... + if (sargs_exists("bla")) { + ... + } + + // get value string for key, if not found, return empty string "" + const char* val0 = sargs_value("bla"); + + // get value string for key, or default string if key not found + const char* val1 = sargs_value_def("bla", "default_value"); + + // check if a key matches expected value + if (sargs_equals("type", "kc85_4")) { + ... + } + + // check if a key's value is "true", "yes" or "on" or if this is a standalone key + if (sargs_boolean("joystick_enabled")) { + ... + } + + // iterate over keys and values + for (int i = 0; i < sargs_num_args(); i++) { + printf("key: %s, value: %s\n", sargs_key_at(i), sargs_value_at(i)); + } + + // lookup argument index by key string, will return -1 if key + // is not found, sargs_key_at() and sargs_value_at() will return + // an empty string for invalid indices + int index = sargs_find("bla"); + printf("key: %s, value: %s\n", sargs_key_at(index), sargs_value_at(index)); + + // shutdown sokol-args + sargs_shutdown(); + } + + WINMAIN AND ARGC / ARGV + ======================= + On Windows with WinMain() based apps, getting UTF8-encoded command line + arguments is a bit more complicated: + + First call GetCommandLineW(), this returns the entire command line + as UTF-16 string. Then call CommandLineToArgvW(), this parses the + command line string into the usual argc/argv format (but in UTF-16). + Finally convert the UTF-16 strings in argv[] into UTF-8 via + WideCharToMultiByte(). + + See the function _sapp_win32_command_line_to_utf8_argv() in sokol_app.h + for example code how to do this (if you're using sokol_app.h, it will + already convert the command line arguments to UTF-8 for you of course, + so you can plug them directly into sokol_app.h). + + API DOCUMENTATION + ================= + void sargs_setup(const sargs_desc* desc) + Initialize sokol_args, desc contains the following configuration + parameters: + int argc - the main function's argc parameter + char** argv - the main function's argv parameter + int max_args - max number of key/value pairs, default is 16 + int buf_size - size of the internal string buffer, default is 16384 + + Note that on the web, argc and argv will be ignored and the arguments + will be taken from the page URL instead. + + sargs_setup() will allocate 2 memory chunks: one for keeping track + of the key/value args of size 'max_args*8', and a string buffer + of size 'buf_size'. + + void sargs_shutdown(void) + Shutdown sokol-args and free any allocated memory. + + bool sargs_isvalid(void) + Return true between sargs_setup() and sargs_shutdown() + + bool sargs_exists(const char* key) + Test if an argument exists by its key name. + + const char* sargs_value(const char* key) + Return value associated with key. Returns an empty string ("") if the + key doesn't exist, or if the key doesn't have a value. + + const char* sargs_value_def(const char* key, const char* default) + Return value associated with key, or the provided default value if the + key doesn't exist, or this is a value-less key. + + bool sargs_equals(const char* key, const char* val); + Return true if the value associated with key matches + the 'val' argument. + + bool sargs_boolean(const char* key) + Return true if the value string of 'key' is one of 'true', 'yes', 'on', + or this is a key without value. + + int sargs_find(const char* key) + Find argument by key name and return its index, or -1 if not found. + + int sargs_num_args(void) + Return number of key/value pairs. + + const char* sargs_key_at(int index) + Return the key name of argument at index. Returns empty string if + is index is outside range. + + const char* sargs_value_at(int index) + Return the value of argument at index. Returns empty string + if the key at index has no value, or the index is out-of-range. + + + 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); + } + + ... + sargs_setup(&(sargs_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_args.h + itself though, not any allocations in OS libraries. + + TODO + ==== + - parsing 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_ARGS_INCLUDED (1) +#include +#include +#include // size_t + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_ARGS_API_DECL) +#define SOKOL_ARGS_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_ARGS_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_ARGS_IMPL) +#define SOKOL_ARGS_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_ARGS_API_DECL __declspec(dllimport) +#else +#define SOKOL_ARGS_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + sargs_allocator + + Used in sargs_desc to provide custom memory-alloc and -free functions + to sokol_args.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 sargs_allocator { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sargs_allocator; + +typedef struct sargs_desc { + int argc; + char** argv; + int max_args; + int buf_size; + sargs_allocator allocator; +} sargs_desc; + +/* setup sokol-args */ +SOKOL_ARGS_API_DECL void sargs_setup(const sargs_desc* desc); +/* shutdown sokol-args */ +SOKOL_ARGS_API_DECL void sargs_shutdown(void); +/* true between sargs_setup() and sargs_shutdown() */ +SOKOL_ARGS_API_DECL bool sargs_isvalid(void); +/* test if an argument exists by key name */ +SOKOL_ARGS_API_DECL bool sargs_exists(const char* key); +/* get value by key name, return empty string if key doesn't exist or an existing key has no value */ +SOKOL_ARGS_API_DECL const char* sargs_value(const char* key); +/* get value by key name, return provided default if key doesn't exist or has no value */ +SOKOL_ARGS_API_DECL const char* sargs_value_def(const char* key, const char* def); +/* return true if val arg matches the value associated with key */ +SOKOL_ARGS_API_DECL bool sargs_equals(const char* key, const char* val); +/* return true if key's value is "true", "yes", "on" or an existing key has no value */ +SOKOL_ARGS_API_DECL bool sargs_boolean(const char* key); +/* get index of arg by key name, return -1 if not exists */ +SOKOL_ARGS_API_DECL int sargs_find(const char* key); +/* get number of parsed arguments */ +SOKOL_ARGS_API_DECL int sargs_num_args(void); +/* get key name of argument at index, or empty string */ +SOKOL_ARGS_API_DECL const char* sargs_key_at(int index); +/* get value string of argument at index, or empty string */ +SOKOL_ARGS_API_DECL const char* sargs_value_at(int index); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for c++ */ +inline void sargs_setup(const sargs_desc& desc) { return sargs_setup(&desc); } + +#endif +#endif // SOKOL_ARGS_INCLUDED + +/*--- IMPLEMENTATION ---------------------------------------------------------*/ +#ifdef SOKOL_ARGS_IMPL +#define SOKOL_ARGS_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 sargs_desc.allocator to override memory allocation functions" +#endif + +#include // memset, strcmp +#include // malloc, free + +#if defined(__EMSCRIPTEN__) +#include +#endif + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif + +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif + +#define _sargs_def(val, def) (((val) == 0) ? (def) : (val)) + +#define _SARGS_MAX_ARGS_DEF (16) +#define _SARGS_BUF_SIZE_DEF (16*1024) + +/* parser state */ +#define _SARGS_EXPECT_KEY (1<<0) +#define _SARGS_EXPECT_SEP (1<<1) +#define _SARGS_EXPECT_VAL (1<<2) +#define _SARGS_PARSING_KEY (1<<3) +#define _SARGS_PARSING_VAL (1<<4) +#define _SARGS_ERROR (1<<5) + +/* a key/value pair struct */ +typedef struct { + int key; /* index to start of key string in buf */ + int val; /* index to start of value string in buf */ +} _sargs_kvp_t; + +/* sokol-args state */ +typedef struct { + int max_args; /* number of key/value pairs in args array */ + int num_args; /* number of valid items in args array */ + _sargs_kvp_t* args; /* key/value pair array */ + int buf_size; /* size of buffer in bytes */ + int buf_pos; /* current buffer position */ + char* buf; /* character buffer, first char is reserved and zero for 'empty string' */ + bool valid; + uint32_t parse_state; + char quote; /* current quote char, 0 if not in a quote */ + bool in_escape; /* currently in an escape sequence */ + sargs_allocator allocator; +} _sargs_state_t; +static _sargs_state_t _sargs; + +/*== PRIVATE IMPLEMENTATION FUNCTIONS ========================================*/ +_SOKOL_PRIVATE void _sargs_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +_SOKOL_PRIVATE void* _sargs_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_sargs.allocator.alloc_fn) { + ptr = _sargs.allocator.alloc_fn(size, _sargs.allocator.user_data); + } else { + ptr = malloc(size); + } + SOKOL_ASSERT(ptr); + return ptr; +} + +_SOKOL_PRIVATE void* _sargs_malloc_clear(size_t size) { + void* ptr = _sargs_malloc(size); + _sargs_clear(ptr, size); + return ptr; +} + +_SOKOL_PRIVATE void _sargs_free(void* ptr) { + if (_sargs.allocator.free_fn) { + _sargs.allocator.free_fn(ptr, _sargs.allocator.user_data); + } else { + free(ptr); + } +} + +_SOKOL_PRIVATE void _sargs_putc(char c) { + if ((_sargs.buf_pos+2) < _sargs.buf_size) { + _sargs.buf[_sargs.buf_pos++] = c; + } +} + +_SOKOL_PRIVATE const char* _sargs_str(int index) { + SOKOL_ASSERT((index >= 0) && (index < _sargs.buf_size)); + return &_sargs.buf[index]; +} + +/*-- argument parser functions ------------------*/ +_SOKOL_PRIVATE void _sargs_expect_key(void) { + _sargs.parse_state = _SARGS_EXPECT_KEY; +} + +_SOKOL_PRIVATE bool _sargs_key_expected(void) { + return 0 != (_sargs.parse_state & _SARGS_EXPECT_KEY); +} + +_SOKOL_PRIVATE void _sargs_expect_val(void) { + _sargs.parse_state = _SARGS_EXPECT_VAL; +} + +_SOKOL_PRIVATE bool _sargs_val_expected(void) { + return 0 != (_sargs.parse_state & _SARGS_EXPECT_VAL); +} + +_SOKOL_PRIVATE void _sargs_expect_sep_or_key(void) { + _sargs.parse_state = _SARGS_EXPECT_SEP | _SARGS_EXPECT_KEY; +} + +_SOKOL_PRIVATE bool _sargs_any_expected(void) { + return 0 != (_sargs.parse_state & (_SARGS_EXPECT_KEY | _SARGS_EXPECT_VAL | _SARGS_EXPECT_SEP)); +} + +_SOKOL_PRIVATE bool _sargs_is_separator(char c) { + return c == '='; +} + +_SOKOL_PRIVATE bool _sargs_is_quote(char c) { + if (0 == _sargs.quote) { + return (c == '\'') || (c == '"'); + } + else { + return c == _sargs.quote; + } +} + +_SOKOL_PRIVATE void _sargs_begin_quote(char c) { + _sargs.quote = c; +} + +_SOKOL_PRIVATE void _sargs_end_quote(void) { + _sargs.quote = 0; +} + +_SOKOL_PRIVATE bool _sargs_in_quotes(void) { + return 0 != _sargs.quote; +} + +_SOKOL_PRIVATE bool _sargs_is_whitespace(char c) { + return !_sargs_in_quotes() && ((c == ' ') || (c == '\t')); +} + +_SOKOL_PRIVATE void _sargs_start_key(void) { + SOKOL_ASSERT((_sargs.num_args >= 0) && (_sargs.num_args < _sargs.max_args)); + _sargs.parse_state = _SARGS_PARSING_KEY; + _sargs.args[_sargs.num_args].key = _sargs.buf_pos; +} + +_SOKOL_PRIVATE void _sargs_end_key(void) { + SOKOL_ASSERT((_sargs.num_args >= 0) && (_sargs.num_args < _sargs.max_args)); + _sargs_putc(0); + // declare val as empty string in case this is a key-only arg + _sargs.args[_sargs.num_args].val = _sargs.buf_pos - 1; + _sargs.num_args++; + _sargs.parse_state = 0; +} + +_SOKOL_PRIVATE bool _sargs_parsing_key(void) { + return 0 != (_sargs.parse_state & _SARGS_PARSING_KEY); +} + +_SOKOL_PRIVATE void _sargs_start_val(void) { + SOKOL_ASSERT((_sargs.num_args > 0) && (_sargs.num_args <= _sargs.max_args)); + _sargs.parse_state = _SARGS_PARSING_VAL; + _sargs.args[_sargs.num_args - 1].val = _sargs.buf_pos; +} + +_SOKOL_PRIVATE void _sargs_end_val(void) { + _sargs_putc(0); + _sargs.parse_state = 0; +} + +_SOKOL_PRIVATE bool _sargs_is_escape(char c) { + return '\\' == c; +} + +_SOKOL_PRIVATE void _sargs_start_escape(void) { + _sargs.in_escape = true; +} + +_SOKOL_PRIVATE bool _sargs_in_escape(void) { + return _sargs.in_escape; +} + +_SOKOL_PRIVATE char _sargs_escape(char c) { + switch (c) { + case 'n': return '\n'; + case 't': return '\t'; + case 'r': return '\r'; + case '\\': return '\\'; + default: return c; + } +} + +_SOKOL_PRIVATE void _sargs_end_escape(void) { + _sargs.in_escape = false; +} + +_SOKOL_PRIVATE bool _sargs_parsing_val(void) { + return 0 != (_sargs.parse_state & _SARGS_PARSING_VAL); +} + +_SOKOL_PRIVATE bool _sargs_parse_carg(const char* src) { + char c; + while (0 != (c = *src++)) { + if (_sargs_in_escape()) { + c = _sargs_escape(c); + _sargs_end_escape(); + } + else if (_sargs_is_escape(c)) { + _sargs_start_escape(); + continue; + } + if (_sargs_any_expected()) { + if (!_sargs_is_whitespace(c)) { + /* start of key, value or separator */ + if (_sargs_is_separator(c)) { + /* skip separator and expect value */ + _sargs_expect_val(); + continue; + } + else if (_sargs_key_expected()) { + /* start of new key */ + _sargs_start_key(); + } + else if (_sargs_val_expected()) { + /* start of value */ + if (_sargs_is_quote(c)) { + _sargs_begin_quote(c); + continue; + } + _sargs_start_val(); + } + } + else { + /* skip white space */ + continue; + } + } + else if (_sargs_parsing_key()) { + if (_sargs_is_whitespace(c) || _sargs_is_separator(c)) { + /* end of key string */ + _sargs_end_key(); + if (_sargs_is_separator(c)) { + _sargs_expect_val(); + } + else { + _sargs_expect_sep_or_key(); + } + continue; + } + } + else if (_sargs_parsing_val()) { + if (_sargs_in_quotes()) { + /* when in quotes, whitespace is a normal character + and a matching quote ends the value string + */ + if (_sargs_is_quote(c)) { + _sargs_end_quote(); + _sargs_end_val(); + _sargs_expect_key(); + continue; + } + } + else if (_sargs_is_whitespace(c)) { + /* end of value string (no quotes) */ + _sargs_end_val(); + _sargs_expect_key(); + continue; + } + } + _sargs_putc(c); + } + if (_sargs_parsing_key()) { + _sargs_end_key(); + _sargs_expect_sep_or_key(); + } + else if (_sargs_parsing_val() && !_sargs_in_quotes()) { + _sargs_end_val(); + _sargs_expect_key(); + } + return true; +} + +_SOKOL_PRIVATE bool _sargs_parse_cargs(int argc, const char** argv) { + _sargs_expect_key(); + bool retval = true; + for (int i = 1; i < argc; i++) { + retval &= _sargs_parse_carg(argv[i]); + } + _sargs.parse_state = 0; + return retval; +} + +/*-- EMSCRIPTEN IMPLEMENTATION -----------------------------------------------*/ +#if defined(__EMSCRIPTEN__) + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(EM_JS_DEPS) +EM_JS_DEPS(sokol_audio, "$withStackSave,$stringToUTF8OnStack"); +#endif + +EMSCRIPTEN_KEEPALIVE void _sargs_add_kvp(const char* key, const char* val) { + SOKOL_ASSERT(_sargs.valid && key && val); + if (_sargs.num_args >= _sargs.max_args) { + return; + } + + /* copy key string */ + char c; + _sargs.args[_sargs.num_args].key = _sargs.buf_pos; + const char* ptr = key; + while (0 != (c = *ptr++)) { + _sargs_putc(c); + } + _sargs_putc(0); + + /* copy value string */ + _sargs.args[_sargs.num_args].val = _sargs.buf_pos; + ptr = val; + while (0 != (c = *ptr++)) { + _sargs_putc(c); + } + _sargs_putc(0); + + _sargs.num_args++; +} +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/* JS function to extract arguments from the page URL */ +EM_JS(void, sargs_js_parse_url, (void), { + const params = new URLSearchParams(window.location.search).entries(); + for (let p = params.next(); !p.done; p = params.next()) { + const key = p.value[0]; + const val = p.value[1]; + withStackSave(() => { + const key_cstr = stringToUTF8OnStack(key); + const val_cstr = stringToUTF8OnStack(val); + __sargs_add_kvp(key_cstr, val_cstr) + }); + } +}); + +#endif /* EMSCRIPTEN */ + +/*== PUBLIC IMPLEMENTATION FUNCTIONS =========================================*/ +SOKOL_API_IMPL void sargs_setup(const sargs_desc* desc) { + SOKOL_ASSERT(desc); + _sargs_clear(&_sargs, sizeof(_sargs)); + _sargs.max_args = _sargs_def(desc->max_args, _SARGS_MAX_ARGS_DEF); + _sargs.buf_size = _sargs_def(desc->buf_size, _SARGS_BUF_SIZE_DEF); + SOKOL_ASSERT(_sargs.buf_size > 8); + _sargs.args = (_sargs_kvp_t*) _sargs_malloc_clear((size_t)_sargs.max_args * sizeof(_sargs_kvp_t)); + _sargs.buf = (char*) _sargs_malloc_clear((size_t)_sargs.buf_size * sizeof(char)); + /* the first character in buf is reserved and always zero, this is the 'empty string' */ + _sargs.buf_pos = 1; + _sargs.allocator = desc->allocator; + _sargs.valid = true; + + /* parse argc/argv */ + _sargs_parse_cargs(desc->argc, (const char**) desc->argv); + + #if defined(__EMSCRIPTEN__) + /* on emscripten, also parse the page URL*/ + sargs_js_parse_url(); + #endif +} + +SOKOL_API_IMPL void sargs_shutdown(void) { + SOKOL_ASSERT(_sargs.valid); + if (_sargs.args) { + _sargs_free(_sargs.args); + _sargs.args = 0; + } + if (_sargs.buf) { + _sargs_free(_sargs.buf); + _sargs.buf = 0; + } + _sargs.valid = false; +} + +SOKOL_API_IMPL bool sargs_isvalid(void) { + return _sargs.valid; +} + +SOKOL_API_IMPL int sargs_find(const char* key) { + SOKOL_ASSERT(_sargs.valid && key); + for (int i = 0; i < _sargs.num_args; i++) { + if (0 == strcmp(_sargs_str(_sargs.args[i].key), key)) { + return i; + } + } + return -1; +} + +SOKOL_API_IMPL int sargs_num_args(void) { + SOKOL_ASSERT(_sargs.valid); + return _sargs.num_args; +} + +SOKOL_API_IMPL const char* sargs_key_at(int index) { + SOKOL_ASSERT(_sargs.valid); + if ((index >= 0) && (index < _sargs.num_args)) { + return _sargs_str(_sargs.args[index].key); + } + else { + /* index 0 is always the empty string */ + return _sargs_str(0); + } +} + +SOKOL_API_IMPL const char* sargs_value_at(int index) { + SOKOL_ASSERT(_sargs.valid); + if ((index >= 0) && (index < _sargs.num_args)) { + return _sargs_str(_sargs.args[index].val); + } + else { + /* index 0 is always the empty string */ + return _sargs_str(0); + } +} + +SOKOL_API_IMPL bool sargs_exists(const char* key) { + SOKOL_ASSERT(_sargs.valid && key); + return -1 != sargs_find(key); +} + +SOKOL_API_IMPL const char* sargs_value(const char* key) { + SOKOL_ASSERT(_sargs.valid && key); + return sargs_value_at(sargs_find(key)); +} + +SOKOL_API_IMPL const char* sargs_value_def(const char* key, const char* def) { + SOKOL_ASSERT(_sargs.valid && key && def); + int arg_index = sargs_find(key); + if (-1 != arg_index) { + const char* res = sargs_value_at(arg_index); + SOKOL_ASSERT(res); + if (res[0] == 0) { + return def; + } else { + return res; + } + } + else { + return def; + } +} + +SOKOL_API_IMPL bool sargs_equals(const char* key, const char* val) { + SOKOL_ASSERT(_sargs.valid && key && val); + return 0 == strcmp(sargs_value(key), val); +} + +SOKOL_API_IMPL bool sargs_boolean(const char* key) { + if (sargs_exists(key)) { + const char* val = sargs_value(key); + return (0 == strcmp("true", val)) || + (0 == strcmp("yes", val)) || + (0 == strcmp("on", val)) || + (0 == strcmp("", val)); + } else { + return false; + } +} + +#endif /* SOKOL_ARGS_IMPL */ diff --git a/source/engine/thirdparty/sokol/sokol_audio.h b/source/engine/thirdparty/sokol/sokol_audio.h new file mode 100644 index 0000000..6b60937 --- /dev/null +++ b/source/engine/thirdparty/sokol/sokol_audio.h @@ -0,0 +1,2594 @@ +#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 + SAUDIO_ANDROID_AAUDIO - on Android, select the AAudio backend (default) + SAUDIO_ANDROID_SLES - on Android, select the OpenSLES backend + + 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 Linux: asound + - on Android: link with OpenSLES or 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 + - macOS: CoreAudio + - iOS: CoreAudio+AVAudioSession + - emscripten: WebAudio with ScriptProcessorNode + - Android: AAudio (default) or OpenSLES, select at build time + + 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, OpenSLES 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(USING_SLES_BACKEND, "using OpenSLES backend") \ + _SAUDIO_LOGITEM_XMACRO(SLES_CREATE_ENGINE_FAILED, "slCreateEngine() failed") \ + _SAUDIO_LOGITEM_XMACRO(SLES_ENGINE_GET_ENGINE_INTERFACE_FAILED, "GetInterface() for SL_IID_ENGINE failed") \ + _SAUDIO_LOGITEM_XMACRO(SLES_CREATE_OUTPUT_MIX_FAILED, "CreateOutputMix() failed") \ + _SAUDIO_LOGITEM_XMACRO(SLES_MIXER_GET_VOLUME_INTERFACE_FAILED, "GetInterface() for SL_IID_VOLUME failed") \ + _SAUDIO_LOGITEM_XMACRO(SLES_ENGINE_CREATE_AUDIO_PLAYER_FAILED, "CreateAudioPlayer() failed") \ + _SAUDIO_LOGITEM_XMACRO(SLES_PLAYER_GET_PLAY_INTERFACE_FAILED, "GetInterface() for SL_IID_PLAY failed") \ + _SAUDIO_LOGITEM_XMACRO(SLES_PLAYER_GET_VOLUME_INTERFACE_FAILED, "GetInterface() for SL_IID_VOLUME failed") \ + _SAUDIO_LOGITEM_XMACRO(SLES_PLAYER_GET_BUFFERQUEUE_INTERFACE_FAILED, "GetInterface() for SL_IID_ANDROIDSIMPLEBUFFERQUEUE 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) + #if !defined(SAUDIO_ANDROID_SLES) && !defined(SAUDIO_ANDROID_AAUDIO) + #define SAUDIO_ANDROID_AAUDIO (1) + #endif +#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 + #if defined(SAUDIO_ANDROID_SLES) + #include "SLES/OpenSLES_Android.h" + #elif defined(SAUDIO_ANDROID_AAUDIO) + #include "aaudio/AAudio.h" + #endif +#elif defined(_SAUDIO_LINUX) + #include + #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_SLES) + +#define SAUDIO_SLES_NUM_BUFFERS (2) + +typedef struct { + pthread_mutex_t mutex; + pthread_cond_t cond; + int count; +} _saudio_sles_semaphore_t; + +typedef struct { + SLObjectItf engine_obj; + SLEngineItf engine; + SLObjectItf output_mix_obj; + SLVolumeItf output_mix_vol; + SLDataLocator_OutputMix out_locator; + SLDataSink dst_data_sink; + SLObjectItf player_obj; + SLPlayItf player; + SLVolumeItf player_vol; + SLAndroidSimpleBufferQueueItf player_buffer_queue; + + int16_t* output_buffers[SAUDIO_SLES_NUM_BUFFERS]; + float* src_buffer; + int active_buffer; + _saudio_sles_semaphore_t buffer_sem; + pthread_t thread; + volatile int thread_stop; + SLDataLocator_AndroidSimpleBufferQueue in_locator; +} _saudio_sles_backend_t; + +#elif defined(SAUDIO_ANDROID_AAUDIO) + +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_SLES) +typedef _saudio_sles_backend_t _saudio_backend_t; +#elif defined(SAUDIO_ANDROID_AAUDIO) +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; + 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, 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_AAUDIO) + +_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; +} + +// ██████ ██████ ███████ ███ ██ ███████ ██ ███████ ███████ +// ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ +// ██ ██ ██████ █████ ██ ██ ██ ███████ ██ █████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ███████ ██ ████ ███████ ███████ ███████ ███████ +// +// >>opensles +// >>sles +#elif defined(SAUDIO_ANDROID_SLES) + +_SOKOL_PRIVATE void _saudio_sles_semaphore_init(_saudio_sles_semaphore_t* sem) { + sem->count = 0; + int r = pthread_mutex_init(&sem->mutex, NULL); + SOKOL_ASSERT(r == 0); + r = pthread_cond_init(&sem->cond, NULL); + SOKOL_ASSERT(r == 0); + (void)(r); +} + +_SOKOL_PRIVATE void _saudio_sles_semaphore_destroy(_saudio_sles_semaphore_t* sem) { + pthread_cond_destroy(&sem->cond); + pthread_mutex_destroy(&sem->mutex); +} + +_SOKOL_PRIVATE void _saudio_sles_semaphore_post(_saudio_sles_semaphore_t* sem, int count) { + int r = pthread_mutex_lock(&sem->mutex); + SOKOL_ASSERT(r == 0); + for (int ii = 0; ii < count; ii++) { + r = pthread_cond_signal(&sem->cond); + SOKOL_ASSERT(r == 0); + } + sem->count += count; + r = pthread_mutex_unlock(&sem->mutex); + SOKOL_ASSERT(r == 0); + (void)(r); +} + +_SOKOL_PRIVATE bool _saudio_sles_semaphore_wait(_saudio_sles_semaphore_t* sem) { + int r = pthread_mutex_lock(&sem->mutex); + SOKOL_ASSERT(r == 0); + while (r == 0 && sem->count <= 0) { + r = pthread_cond_wait(&sem->cond, &sem->mutex); + } + bool ok = (r == 0); + if (ok) { + --sem->count; + } + r = pthread_mutex_unlock(&sem->mutex); + (void)(r); + return ok; +} + +/* fill intermediate buffer with new data and reset buffer_pos */ +_SOKOL_PRIVATE void _saudio_sles_fill_buffer(void) { + int src_buffer_frames = _saudio.buffer_frames; + if (_saudio_has_callback()) { + _saudio_stream_callback(_saudio.backend.src_buffer, src_buffer_frames, _saudio.num_channels); + } + else { + const int src_buffer_byte_size = src_buffer_frames * _saudio.num_channels * (int)sizeof(float); + if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.src_buffer, src_buffer_byte_size)) { + /* not enough read data available, fill the entire buffer with silence */ + _saudio_clear(_saudio.backend.src_buffer, (size_t)src_buffer_byte_size); + } + } +} + +_SOKOL_PRIVATE void SLAPIENTRY _saudio_sles_play_cb(SLPlayItf player, void *context, SLuint32 event) { + _SOKOL_UNUSED(context); + _SOKOL_UNUSED(player); + if (event & SL_PLAYEVENT_HEADATEND) { + _saudio_sles_semaphore_post(&_saudio.backend.buffer_sem, 1); + } +} + +_SOKOL_PRIVATE void* _saudio_sles_thread_fn(void* param) { + _SOKOL_UNUSED(param); + while (!_saudio.backend.thread_stop) { + /* get next output buffer, advance, next buffer. */ + int16_t* out_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer]; + _saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_SLES_NUM_BUFFERS; + int16_t* next_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer]; + + /* queue this buffer */ + const int buffer_size_bytes = _saudio.buffer_frames * _saudio.num_channels * (int)sizeof(short); + (*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, out_buffer, (SLuint32)buffer_size_bytes); + + /* fill the next buffer */ + _saudio_sles_fill_buffer(); + const int num_samples = _saudio.num_channels * _saudio.buffer_frames; + for (int i = 0; i < num_samples; ++i) { + next_buffer[i] = (int16_t) (_saudio.backend.src_buffer[i] * 0x7FFF); + } + + _saudio_sles_semaphore_wait(&_saudio.backend.buffer_sem); + } + + return 0; +} + +_SOKOL_PRIVATE void _saudio_sles_backend_shutdown(void) { + _saudio.backend.thread_stop = 1; + pthread_join(_saudio.backend.thread, 0); + + if (_saudio.backend.player_obj) { + (*_saudio.backend.player_obj)->Destroy(_saudio.backend.player_obj); + } + + if (_saudio.backend.output_mix_obj) { + (*_saudio.backend.output_mix_obj)->Destroy(_saudio.backend.output_mix_obj); + } + + if (_saudio.backend.engine_obj) { + (*_saudio.backend.engine_obj)->Destroy(_saudio.backend.engine_obj); + } + + for (int i = 0; i < SAUDIO_SLES_NUM_BUFFERS; i++) { + _saudio_free(_saudio.backend.output_buffers[i]); + } + _saudio_free(_saudio.backend.src_buffer); +} + +_SOKOL_PRIVATE bool _saudio_sles_backend_init(void) { + _SAUDIO_INFO(USING_SLES_BACKEND); + + _saudio.bytes_per_frame = (int)sizeof(float) * _saudio.num_channels; + + for (int i = 0; i < SAUDIO_SLES_NUM_BUFFERS; ++i) { + const int buffer_size_bytes = (int)sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames; + _saudio.backend.output_buffers[i] = (int16_t*) _saudio_malloc_clear((size_t)buffer_size_bytes); + } + + { + const int buffer_size_bytes = _saudio.bytes_per_frame * _saudio.buffer_frames; + _saudio.backend.src_buffer = (float*) _saudio_malloc_clear((size_t)buffer_size_bytes); + } + + /* Create engine */ + const SLEngineOption opts[] = { { SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE } }; + if (slCreateEngine(&_saudio.backend.engine_obj, 1, opts, 0, NULL, NULL ) != SL_RESULT_SUCCESS) { + _SAUDIO_ERROR(SLES_CREATE_ENGINE_FAILED); + _saudio_sles_backend_shutdown(); + return false; + } + + (*_saudio.backend.engine_obj)->Realize(_saudio.backend.engine_obj, SL_BOOLEAN_FALSE); + if ((*_saudio.backend.engine_obj)->GetInterface(_saudio.backend.engine_obj, SL_IID_ENGINE, &_saudio.backend.engine) != SL_RESULT_SUCCESS) { + _SAUDIO_ERROR(SLES_ENGINE_GET_ENGINE_INTERFACE_FAILED); + _saudio_sles_backend_shutdown(); + return false; + } + + /* Create output mix. */ + { + const SLInterfaceID ids[] = { SL_IID_VOLUME }; + const SLboolean req[] = { SL_BOOLEAN_FALSE }; + + if ((*_saudio.backend.engine)->CreateOutputMix(_saudio.backend.engine, &_saudio.backend.output_mix_obj, 1, ids, req) != SL_RESULT_SUCCESS) { + _SAUDIO_ERROR(SLES_CREATE_OUTPUT_MIX_FAILED); + _saudio_sles_backend_shutdown(); + return false; + } + (*_saudio.backend.output_mix_obj)->Realize(_saudio.backend.output_mix_obj, SL_BOOLEAN_FALSE); + + if ((*_saudio.backend.output_mix_obj)->GetInterface(_saudio.backend.output_mix_obj, SL_IID_VOLUME, &_saudio.backend.output_mix_vol) != SL_RESULT_SUCCESS) { + _SAUDIO_WARN(SLES_MIXER_GET_VOLUME_INTERFACE_FAILED); + } + } + + /* android buffer queue */ + _saudio.backend.in_locator.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; + _saudio.backend.in_locator.numBuffers = SAUDIO_SLES_NUM_BUFFERS; + + /* data format */ + SLDataFormat_PCM format; + format.formatType = SL_DATAFORMAT_PCM; + format.numChannels = (SLuint32)_saudio.num_channels; + format.samplesPerSec = (SLuint32) (_saudio.sample_rate * 1000); + format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format.containerSize = 16; + format.endianness = SL_BYTEORDER_LITTLEENDIAN; + + if (_saudio.num_channels == 2) { + format.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + } else { + format.channelMask = SL_SPEAKER_FRONT_CENTER; + } + + SLDataSource src; + src.pLocator = &_saudio.backend.in_locator; + src.pFormat = &format; + + /* Output mix. */ + _saudio.backend.out_locator.locatorType = SL_DATALOCATOR_OUTPUTMIX; + _saudio.backend.out_locator.outputMix = _saudio.backend.output_mix_obj; + + _saudio.backend.dst_data_sink.pLocator = &_saudio.backend.out_locator; + _saudio.backend.dst_data_sink.pFormat = NULL; + + /* setup player */ + { + const SLInterfaceID ids[] = { SL_IID_VOLUME, SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; + const SLboolean req[] = { SL_BOOLEAN_FALSE, SL_BOOLEAN_TRUE }; + + if ((*_saudio.backend.engine)->CreateAudioPlayer(_saudio.backend.engine, &_saudio.backend.player_obj, &src, &_saudio.backend.dst_data_sink, sizeof(ids) / sizeof(ids[0]), ids, req) != SL_RESULT_SUCCESS) + { + _SAUDIO_ERROR(SLES_ENGINE_CREATE_AUDIO_PLAYER_FAILED); + _saudio_sles_backend_shutdown(); + return false; + } + (*_saudio.backend.player_obj)->Realize(_saudio.backend.player_obj, SL_BOOLEAN_FALSE); + + if ((*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_PLAY, &_saudio.backend.player) != SL_RESULT_SUCCESS) { + _SAUDIO_ERROR(SLES_PLAYER_GET_PLAY_INTERFACE_FAILED); + _saudio_sles_backend_shutdown(); + return false; + } + if ((*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_VOLUME, &_saudio.backend.player_vol) != SL_RESULT_SUCCESS) { + _SAUDIO_ERROR(SLES_PLAYER_GET_VOLUME_INTERFACE_FAILED); + } + if ((*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &_saudio.backend.player_buffer_queue) != SL_RESULT_SUCCESS) { + _SAUDIO_ERROR(SLES_PLAYER_GET_BUFFERQUEUE_INTERFACE_FAILED); + _saudio_sles_backend_shutdown(); + return false; + } + } + + /* begin */ + { + const int buffer_size_bytes = (int)sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames; + (*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, _saudio.backend.output_buffers[0], (SLuint32)buffer_size_bytes); + _saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_SLES_NUM_BUFFERS; + + (*_saudio.backend.player)->RegisterCallback(_saudio.backend.player, _saudio_sles_play_cb, NULL); + (*_saudio.backend.player)->SetCallbackEventsMask(_saudio.backend.player, SL_PLAYEVENT_HEADATEND); + (*_saudio.backend.player)->SetPlayState(_saudio.backend.player, SL_PLAYSTATE_PLAYING); + } + + /* create the buffer-streaming start thread */ + if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_sles_thread_fn, 0)) { + _saudio_sles_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_AAUDIO) + return _saudio_aaudio_backend_init(); + #elif defined(SAUDIO_ANDROID_SLES) + return _saudio_sles_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_AAUDIO) + _saudio_aaudio_backend_shutdown(); + #elif defined(SAUDIO_ANDROID_SLES) + _saudio_sles_backend_shutdown(); + #elif defined(_SAUDIO_APPLE) + return _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(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.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) { + 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) { + return _saudio.desc.user_data; +} + +SOKOL_API_IMPL saudio_desc saudio_query_desc(void) { + return _saudio.desc; +} + +SOKOL_API_IMPL int saudio_sample_rate(void) { + return _saudio.sample_rate; +} + +SOKOL_API_IMPL int saudio_buffer_frames(void) { + return _saudio.buffer_frames; +} + +SOKOL_API_IMPL int saudio_channels(void) { + return _saudio.num_channels; +} + +SOKOL_API_IMPL bool saudio_suspended(void) { + #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) { + 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(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/source/engine/thirdparty/sokol/sokol_fetch.h b/source/engine/thirdparty/sokol/sokol_fetch.h new file mode 100644 index 0000000..510c5b5 --- /dev/null +++ b/source/engine/thirdparty/sokol/sokol_fetch.h @@ -0,0 +1,2812 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_FETCH_IMPL) +#define SOKOL_FETCH_IMPL +#endif +#ifndef SOKOL_FETCH_INCLUDED +/* + sokol_fetch.h -- asynchronous data loading/streaming + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_FETCH_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Optionally provide the following defines with your own implementations: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + SOKOL_FETCH_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_FETCH_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SFETCH_MAX_PATH - max length of UTF-8 filesystem path / URL (default: 1024 bytes) + SFETCH_MAX_USERDATA_UINT64 - max size of embedded userdata in number of uint64_t, userdata + will be copied into an 8-byte aligned memory region associated + with each in-flight request, default value is 16 (== 128 bytes) + SFETCH_MAX_CHANNELS - max number of IO channels (default is 16, also see sfetch_desc_t.num_channels) + + If sokol_fetch.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_FETCH_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + NOTE: The following documentation talks a lot about "IO threads". Actual + threads are only used on platforms where threads are available. The web + version (emscripten/wasm) doesn't use POSIX-style threads, but instead + asynchronous Javascript calls chained together by callbacks. The actual + source code differences between the two approaches have been kept to + a minimum though. + + FEATURE OVERVIEW + ================ + + - Asynchronously load complete files, or stream files incrementally via + HTTP (on web platform), or the local file system (on native platforms) + + - Request / response-callback model, user code sends a request + to initiate a file-load, sokol_fetch.h calls the response callback + on the same thread when data is ready or user-code needs + to respond otherwise + + - Not limited to the main-thread or a single thread: A sokol-fetch + "context" can live on any thread, and multiple contexts + can operate side-by-side on different threads. + + - Memory management for data buffers is under full control of user code. + sokol_fetch.h won't allocate memory after it has been setup. + + - Automatic rate-limiting guarantees that only a maximum number of + requests is processed at any one time, allowing a zero-allocation + model, where all data is streamed into fixed-size, pre-allocated + buffers. + + - Active Requests can be paused, continued and cancelled from anywhere + in the user-thread which sent this request. + + + TL;DR EXAMPLE CODE + ================== + This is the most-simple example code to load a single data file with a + known maximum size: + + (1) initialize sokol-fetch with default parameters (but NOTE that the + default setup parameters provide a safe-but-slow "serialized" + operation). In order to see any logging output in case or errors + you should always provide a logging function + (such as 'slog_func' from sokol_log.h): + + sfetch_setup(&(sfetch_desc_t){ .logger.func = slog_func }); + + (2) send a fetch-request to load a file from the current directory + into a buffer big enough to hold the entire file content: + + static uint8_t buf[MAX_FILE_SIZE]; + + sfetch_send(&(sfetch_request_t){ + .path = "my_file.txt", + .callback = response_callback, + .buffer = { + .ptr = buf, + .size = sizeof(buf) + } + }); + + If 'buf' is a value (e.g. an array or struct item), the .buffer item can + be initialized with the SFETCH_RANGE() helper macro: + + sfetch_send(&(sfetch_request_t){ + .path = "my_file.txt", + .callback = response_callback, + .buffer = SFETCH_RANGE(buf) + }); + + (3) write a 'response-callback' function, this will be called whenever + the user-code must respond to state changes of the request + (most importantly when data has been loaded): + + void response_callback(const sfetch_response_t* response) { + if (response->fetched) { + // data has been loaded, and is available via the + // sfetch_range_t struct item 'data': + const void* ptr = response->data.ptr; + size_t num_bytes = response->data.size; + } + if (response->finished) { + // the 'finished'-flag is the catch-all flag for when the request + // is finished, no matter if loading was successful or failed, + // so any cleanup-work should happen here... + ... + if (response->failed) { + // 'failed' is true in (addition to 'finished') if something + // went wrong (file doesn't exist, or less bytes could be + // read from the file than expected) + } + } + } + + (4) pump the sokol-fetch message queues, and invoke response callbacks + by calling: + + sfetch_dowork(); + + In an event-driven app this should be called in the event loop. If you + use sokol-app this would be in your frame_cb function. + + (5) finally, call sfetch_shutdown() at the end of the application: + + There's many other loading-scenarios, for instance one doesn't have to + provide a buffer upfront, this can also happen in the response callback. + + Or it's possible to stream huge files into small fixed-size buffer, + complete with pausing and continuing the download. + + It's also possible to improve the 'pipeline throughput' by fetching + multiple files in parallel, but at the same time limit the maximum + number of requests that can be 'in-flight'. + + For how this all works, please read the following documentation sections :) + + + API DOCUMENTATION + ================= + + void sfetch_setup(const sfetch_desc_t* desc) + -------------------------------------------- + First call sfetch_setup(const sfetch_desc_t*) on any thread before calling + any other sokol-fetch functions on the same thread. + + sfetch_setup() takes a pointer to an sfetch_desc_t struct with setup + parameters. Parameters which should use their default values must + be zero-initialized: + + - max_requests (uint32_t): + The maximum number of requests that can be alive at any time, the + default is 128. + + - num_channels (uint32_t): + The number of "IO channels" used to parallelize and prioritize + requests, the default is 1. + + - num_lanes (uint32_t): + The number of "lanes" on a single channel. Each request which is + currently 'inflight' on a channel occupies one lane until the + request is finished. This is used for automatic rate-limiting + (search below for CHANNELS AND LANES for more details). The + default number of lanes is 1. + + For example, to setup sokol-fetch for max 1024 active requests, 4 channels, + and 8 lanes per channel in C99: + + sfetch_setup(&(sfetch_desc_t){ + .max_requests = 1024, + .num_channels = 4, + .num_lanes = 8 + }); + + sfetch_setup() is the only place where sokol-fetch will allocate memory. + + NOTE that the default setup parameters of 1 channel and 1 lane per channel + has a very poor 'pipeline throughput' since this essentially serializes + IO requests (a new request will only be processed when the last one has + finished), and since each request needs at least one roundtrip between + the user- and IO-thread the throughput will be at most one request per + frame. Search for LATENCY AND THROUGHPUT below for more information on + how to increase throughput. + + NOTE that you can call sfetch_setup() on multiple threads, each thread + will get its own thread-local sokol-fetch instance, which will work + independently from sokol-fetch instances on other threads. + + void sfetch_shutdown(void) + -------------------------- + Call sfetch_shutdown() at the end of the application to stop any + IO threads and free all memory that was allocated in sfetch_setup(). + + sfetch_handle_t sfetch_send(const sfetch_request_t* request) + ------------------------------------------------------------ + Call sfetch_send() to start loading data, the function takes a pointer to an + sfetch_request_t struct with request parameters and returns a + sfetch_handle_t identifying the request for later calls. At least + a path/URL and callback must be provided: + + sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ + .path = "my_file.txt", + .callback = my_response_callback + }); + + sfetch_send() will return an invalid handle if no request can be allocated + from the internal pool because all available request items are 'in-flight'. + + The sfetch_request_t struct contains the following parameters (optional + parameters that are not provided must be zero-initialized): + + - path (const char*, required) + Pointer to an UTF-8 encoded C string describing the filesystem + path or HTTP URL. The string will be copied into an internal data + structure, and passed "as is" (apart from any required + encoding-conversions) to fopen(), CreateFileW() or + XMLHttpRequest. The maximum length of the string is defined by + the SFETCH_MAX_PATH configuration define, the default is 1024 bytes + including the 0-terminator byte. + + - callback (sfetch_callback_t, required) + Pointer to a response-callback function which is called when the + request needs "user code attention". Search below for REQUEST + STATES AND THE RESPONSE CALLBACK for detailed information about + handling responses in the response callback. + + - channel (uint32_t, optional) + Index of the IO channel where the request should be processed. + Channels are used to parallelize and prioritize requests relative + to each other. Search below for CHANNELS AND LANES for more + information. The default channel is 0. + + - chunk_size (uint32_t, optional) + The chunk_size member is used for streaming data incrementally + in small chunks. After 'chunk_size' bytes have been loaded into + to the streaming buffer, the response callback will be called + with the buffer containing the fetched data for the current chunk. + If chunk_size is 0 (the default), than the whole file will be loaded. + Please search below for CHUNK SIZE AND HTTP COMPRESSION for + important information how streaming works if the web server + is serving compressed data. + + - buffer (sfetch_range_t) + This is a optional pointer/size pair describing a chunk of memory where + data will be loaded into (if no buffer is provided upfront, this + must happen in the response callback). If a buffer is provided, + it must be big enough to either hold the entire file (if chunk_size + is zero), or the *uncompressed* data for one downloaded chunk + (if chunk_size is > 0). + + - user_data (sfetch_range_t) + The user_data ptr/size range struct describe an optional POD blob + (plain-old-data) associated with the request which will be copied(!) + into an internal memory block. The maximum default size of this + memory block is 128 bytes (but can be overridden by defining + SFETCH_MAX_USERDATA_UINT64 before including the notification, note + that this define is in "number of uint64_t", not number of bytes). + The user-data block is 8-byte aligned, and will be copied via + memcpy() (so don't put any C++ "smart members" in there). + + NOTE that request handles are strictly thread-local and only unique + within the thread the handle was created on, and all function calls + involving a request handle must happen on that same thread. + + bool sfetch_handle_valid(sfetch_handle_t request) + ------------------------------------------------- + This checks if the provided request handle is valid, and is associated with + a currently active request. It will return false if: + + - sfetch_send() returned an invalid handle because it couldn't allocate + a new request from the internal request pool (because they're all + in flight) + - the request associated with the handle is no longer alive (because + it either finished successfully, or the request failed for some + reason) + + void sfetch_dowork(void) + ------------------------ + Call sfetch_dowork(void) in regular intervals (for instance once per frame) + on the same thread as sfetch_setup() to "turn the gears". If you are sending + requests but never hear back from them in the response callback function, then + the most likely reason is that you forgot to add the call to sfetch_dowork() + in the per-frame function. + + sfetch_dowork() roughly performs the following work: + + - any new requests that have been sent with sfetch_send() since the + last call to sfetch_dowork() will be dispatched to their IO channels + and assigned a free lane. If all lanes on that channel are occupied + by requests 'in flight', incoming requests must wait until + a lane becomes available + + - for all new requests which have been enqueued on a channel which + don't already have a buffer assigned the response callback will be + called with (response->dispatched == true) so that the response + callback can inspect the dynamically assigned lane and bind a buffer + to the request (search below for CHANNELS AND LANE for more info) + + - a state transition from "user side" to "IO thread side" happens for + each new request that has been dispatched to a channel. + + - requests dispatched to a channel are either forwarded into that + channel's worker thread (on native platforms), or cause an HTTP + request to be sent via an asynchronous XMLHttpRequest (on the web + platform) + + - for all requests which have finished their current IO operation a + state transition from "IO thread side" to "user side" happens, + and the response callback is called so that the fetched data + can be processed. + + - requests which are completely finished (either because the entire + file content has been loaded, or they are in the FAILED state) are + freed (this just changes their state in the 'request pool', no actual + memory is freed) + + - requests which are not yet finished are fed back into the + 'incoming' queue of their channel, and the cycle starts again, this + only happens for requests which perform data streaming (not load + the entire file at once). + + void sfetch_cancel(sfetch_handle_t request) + ------------------------------------------- + This cancels a request in the next sfetch_dowork() call and invokes the + response callback with (response.failed == true) and (response.finished + == true) to give user-code a chance to do any cleanup work for the + request. If sfetch_cancel() is called for a request that is no longer + alive, nothing bad will happen (the call will simply do nothing). + + void sfetch_pause(sfetch_handle_t request) + ------------------------------------------ + This pauses an active request in the next sfetch_dowork() call and puts + it into the PAUSED state. For all requests in PAUSED state, the response + callback will be called in each call to sfetch_dowork() to give user-code + a chance to CONTINUE the request (by calling sfetch_continue()). Pausing + a request makes sense for dynamic rate-limiting in streaming scenarios + (like video/audio streaming with a fixed number of streaming buffers. As + soon as all available buffers are filled with download data, downloading + more data must be prevented to allow video/audio playback to catch up and + free up empty buffers for new download data. + + void sfetch_continue(sfetch_handle_t request) + --------------------------------------------- + Continues a paused request, counterpart to the sfetch_pause() function. + + void sfetch_bind_buffer(sfetch_handle_t request, sfetch_range_t buffer) + ---------------------------------------------------------------------------------------- + This "binds" a new buffer (as pointer/size pair) to an active request. The + function *must* be called from inside the response-callback, and there + must not already be another buffer bound. + + void* sfetch_unbind_buffer(sfetch_handle_t request) + --------------------------------------------------- + This removes the current buffer binding from the request and returns + a pointer to the previous buffer (useful if the buffer was dynamically + allocated and it must be freed). + + sfetch_unbind_buffer() *must* be called from inside the response callback. + + The usual code sequence to bind a different buffer in the response + callback might look like this: + + void response_callback(const sfetch_response_t* response) { + if (response.fetched) { + ... + // switch to a different buffer (in the FETCHED state it is + // guaranteed that the request has a buffer, otherwise it + // would have gone into the FAILED state + void* old_buf_ptr = sfetch_unbind_buffer(response.handle); + free(old_buf_ptr); + void* new_buf_ptr = malloc(new_buf_size); + sfetch_bind_buffer(response.handle, new_buf_ptr, new_buf_size); + } + if (response.finished) { + // unbind and free the currently associated buffer, + // the buffer pointer could be null if the request has failed + // NOTE that it is legal to call free() with a nullptr, + // this happens if the request failed to open its file + // and never goes into the OPENED state + void* buf_ptr = sfetch_unbind_buffer(response.handle); + free(buf_ptr); + } + } + + sfetch_desc_t sfetch_desc(void) + ------------------------------- + sfetch_desc() returns a copy of the sfetch_desc_t struct passed to + sfetch_setup(), with zero-initialized values replaced with + their default values. + + int sfetch_max_userdata_bytes(void) + ----------------------------------- + This returns the value of the SFETCH_MAX_USERDATA_UINT64 config + define, but in number of bytes (so SFETCH_MAX_USERDATA_UINT64*8). + + int sfetch_max_path(void) + ------------------------- + Returns the value of the SFETCH_MAX_PATH config define. + + + REQUEST STATES AND THE RESPONSE CALLBACK + ======================================== + A request goes through a number of states during its lifetime. Depending + on the current state of a request, it will be 'owned' either by the + "user-thread" (where the request was sent) or an IO thread. + + You can think of a request as "ping-ponging" between the IO thread and + user thread, any actual IO work is done on the IO thread, while + invocations of the response-callback happen on the user-thread. + + All state transitions and callback invocations happen inside the + sfetch_dowork() function. + + An active request goes through the following states: + + ALLOCATED (user-thread) + + The request has been allocated in sfetch_send() and is + waiting to be dispatched into its IO channel. When this + happens, the request will transition into the DISPATCHED state. + + DISPATCHED (IO thread) + + The request has been dispatched into its IO channel, and a + lane has been assigned to the request. + + If a buffer was provided in sfetch_send() the request will + immediately transition into the FETCHING state and start loading + data into the buffer. + + If no buffer was provided in sfetch_send(), the response + callback will be called with (response->dispatched == true), + so that the response callback can bind a buffer to the + request. Binding the buffer in the response callback makes + sense if the buffer isn't dynamically allocated, but instead + a pre-allocated buffer must be selected from the request's + channel and lane. + + Note that it isn't possible to get a file size in the response callback + which would help with allocating a buffer of the right size, this is + because it isn't possible in HTTP to query the file size before the + entire file is downloaded (...when the web server serves files compressed). + + If opening the file failed, the request will transition into + the FAILED state with the error code SFETCH_ERROR_FILE_NOT_FOUND. + + FETCHING (IO thread) + + While a request is in the FETCHING state, data will be loaded into + the user-provided buffer. + + If no buffer was provided, the request will go into the FAILED + state with the error code SFETCH_ERROR_NO_BUFFER. + + If a buffer was provided, but it is too small to contain the + fetched data, the request will go into the FAILED state with + error code SFETCH_ERROR_BUFFER_TOO_SMALL. + + If less data can be read from the file than expected, the request + will go into the FAILED state with error code SFETCH_ERROR_UNEXPECTED_EOF. + + If loading data into the provided buffer works as expected, the + request will go into the FETCHED state. + + FETCHED (user thread) + + The request goes into the FETCHED state either when the entire file + has been loaded into the provided buffer (when request.chunk_size == 0), + or a chunk has been loaded (and optionally decompressed) into the + buffer (when request.chunk_size > 0). + + The response callback will be called so that the user-code can + process the loaded data using the following sfetch_response_t struct members: + + - data.ptr: pointer to the start of fetched data + - data.size: the number of bytes in the provided buffer + - data_offset: the byte offset of the loaded data chunk in the + overall file (this is only set to a non-zero value in a streaming + scenario) + + Once all file data has been loaded, the 'finished' flag will be set + in the response callback's sfetch_response_t argument. + + After the user callback returns, and all file data has been loaded + (response.finished flag is set) the request has reached its end-of-life + and will recycled. + + Otherwise, if there's still data to load (because streaming was + requested by providing a non-zero request.chunk_size), the request + will switch back to the FETCHING state to load the next chunk of data. + + Note that it is ok to associate a different buffer or buffer-size + with the request by calling sfetch_bind_buffer() in the response-callback. + + To check in the response callback for the FETCHED state, and + independently whether the request is finished: + + void response_callback(const sfetch_response_t* response) { + if (response->fetched) { + // request is in FETCHED state, the loaded data is available + // in .data.ptr, and the number of bytes that have been + // loaded in .data.size: + const void* data = response->data.ptr; + size_t num_bytes = response->data.size; + } + if (response->finished) { + // the finished flag is set either when all data + // has been loaded, the request has been cancelled, + // or the file operation has failed, this is where + // any required per-request cleanup work should happen + } + } + + + FAILED (user thread) + + A request will transition into the FAILED state in the following situations: + + - if the file doesn't exist or couldn't be opened for other + reasons (SFETCH_ERROR_FILE_NOT_FOUND) + - if no buffer is associated with the request in the FETCHING state + (SFETCH_ERROR_NO_BUFFER) + - if the provided buffer is too small to hold the entire file + (if request.chunk_size == 0), or the (potentially decompressed) + partial data chunk (SFETCH_ERROR_BUFFER_TOO_SMALL) + - if less bytes could be read from the file then expected + (SFETCH_ERROR_UNEXPECTED_EOF) + - if a request has been cancelled via sfetch_cancel() + (SFETCH_ERROR_CANCELLED) + + The response callback will be called once after a request goes into + the FAILED state, with the 'response->finished' and + 'response->failed' flags set to true. + + This gives the user-code a chance to cleanup any resources associated + with the request. + + To check for the failed state in the response callback: + + void response_callback(const sfetch_response_t* response) { + if (response->failed) { + // specifically check for the failed state... + } + // or you can do a catch-all check via the finished-flag: + if (response->finished) { + if (response->failed) { + // if more detailed error handling is needed: + switch (response->error_code) { + ... + } + } + } + } + + PAUSED (user thread) + + A request will transition into the PAUSED state after user-code + calls the function sfetch_pause() on the request's handle. Usually + this happens from within the response-callback in streaming scenarios + when the data streaming needs to wait for a data decoder (like + a video/audio player) to catch up. + + While a request is in PAUSED state, the response-callback will be + called in each sfetch_dowork(), so that the user-code can either + continue the request by calling sfetch_continue(), or cancel + the request by calling sfetch_cancel(). + + When calling sfetch_continue() on a paused request, the request will + transition into the FETCHING state. Otherwise if sfetch_cancel() is + called, the request will switch into the FAILED state. + + To check for the PAUSED state in the response callback: + + void response_callback(const sfetch_response_t* response) { + if (response->paused) { + // we can check here whether the request should + // continue to load data: + if (should_continue(response->handle)) { + sfetch_continue(response->handle); + } + } + } + + + CHUNK SIZE AND HTTP COMPRESSION + =============================== + TL;DR: for streaming scenarios, the provided chunk-size must be smaller + than the provided buffer-size because the web server may decide to + serve the data compressed and the chunk-size must be given in 'compressed + bytes' while the buffer receives 'uncompressed bytes'. It's not possible + in HTTP to query the uncompressed size for a compressed download until + that download has finished. + + With vanilla HTTP, it is not possible to query the actual size of a file + without downloading the entire file first (the Content-Length response + header only provides the compressed size). Furthermore, for HTTP + range-requests, the range is given on the compressed data, not the + uncompressed data. So if the web server decides to server the data + compressed, the content-length and range-request parameters don't + correspond to the uncompressed data that's arriving in the sokol-fetch + buffers, and there's no way from JS or WASM to either force uncompressed + downloads (e.g. by setting the Accept-Encoding field), or access the + compressed data. + + This has some implications for sokol_fetch.h, most notably that buffers + can't be provided in the exactly right size, because that size can't + be queried from HTTP before the data is actually downloaded. + + When downloading whole files at once, it is basically expected that you + know the maximum files size upfront through other means (for instance + through a separate meta-data-file which contains the file sizes and + other meta-data for each file that needs to be loaded). + + For streaming downloads the situation is a bit more complicated. These + use HTTP range-requests, and those ranges are defined on the (potentially) + compressed data which the JS/WASM side doesn't have access to. However, + the JS/WASM side only ever sees the uncompressed data, and it's not possible + to query the uncompressed size of a range request before that range request + has finished. + + If the provided buffer is too small to contain the uncompressed data, + the request will fail with error code SFETCH_ERROR_BUFFER_TOO_SMALL. + + + CHANNELS AND LANES + ================== + Channels and lanes are (somewhat artificial) concepts to manage + parallelization, prioritization and rate-limiting. + + Channels can be used to parallelize message processing for better 'pipeline + throughput', and to prioritize requests: user-code could reserve one + channel for streaming downloads which need to run in parallel to other + requests, another channel for "regular" downloads and yet another + high-priority channel which would only be used for small files which need + to start loading immediately. + + Each channel comes with its own IO thread and message queues for pumping + messages in and out of the thread. The channel where a request is + processed is selected manually when sending a message: + + sfetch_send(&(sfetch_request_t){ + .path = "my_file.txt", + .callback = my_response_callback, + .channel = 2 + }); + + The number of channels is configured at startup in sfetch_setup() and + cannot be changed afterwards. + + Channels are completely separate from each other, and a request will + never "hop" from one channel to another. + + Each channel consists of a fixed number of "lanes" for automatic rate + limiting: + + When a request is sent to a channel via sfetch_send(), a "free lane" will + be picked and assigned to the request. The request will occupy this lane + for its entire life time (also while it is paused). If all lanes of a + channel are currently occupied, new requests will need to wait until a + lane becomes unoccupied. + + Since the number of channels and lanes is known upfront, it is guaranteed + that there will never be more than "num_channels * num_lanes" requests + in flight at any one time. + + This guarantee eliminates unexpected load- and memory-spikes when + many requests are sent in very short time, and it allows to pre-allocate + a fixed number of memory buffers which can be reused for the entire + "lifetime" of a sokol-fetch context. + + In the most simple scenario - when a maximum file size is known - buffers + can be statically allocated like this: + + uint8_t buffer[NUM_CHANNELS][NUM_LANES][MAX_FILE_SIZE]; + + Then in the user callback pick a buffer by channel and lane, + and associate it with the request like this: + + void response_callback(const sfetch_response_t* response) { + if (response->dispatched) { + void* ptr = buffer[response->channel][response->lane]; + sfetch_bind_buffer(response->handle, ptr, MAX_FILE_SIZE); + } + ... + } + + + NOTES ON OPTIMIZING PIPELINE LATENCY AND THROUGHPUT + =================================================== + With the default configuration of 1 channel and 1 lane per channel, + sokol_fetch.h will appear to have a shockingly bad loading performance + if several files are loaded. + + This has two reasons: + + (1) all parallelization when loading data has been disabled. A new + request will only be processed, when the last request has finished. + + (2) every invocation of the response-callback adds one frame of latency + to the request, because callbacks will only be called from within + sfetch_dowork() + + sokol-fetch takes a few shortcuts to improve step (2) and reduce + the 'inherent latency' of a request: + + - if a buffer is provided upfront, the response-callback won't be + called in the DISPATCHED state, but start right with the FETCHED state + where data has already been loaded into the buffer + + - there is no separate CLOSED state where the callback is invoked + separately when loading has finished (or the request has failed), + instead the finished and failed flags will be set as part of + the last FETCHED invocation + + This means providing a big-enough buffer to fit the entire file is the + best case, the response callback will only be called once, ideally in + the next frame (or two calls to sfetch_dowork()). + + If no buffer is provided upfront, one frame of latency is added because + the response callback needs to be invoked in the DISPATCHED state so that + the user code can bind a buffer. + + This means the best case for a request without an upfront-provided + buffer is 2 frames (or 3 calls to sfetch_dowork()). + + That's about what can be done to improve the latency for a single request, + but the really important step is to improve overall throughput. If you + need to load thousands of files you don't want that to be completely + serialized. + + The most important action to increase throughput is to increase the + number of lanes per channel. This defines how many requests can be + 'in flight' on a single channel at the same time. The guiding decision + factor for how many lanes you can "afford" is the memory size you want + to set aside for buffers. Each lane needs its own buffer so that + the data loaded for one request doesn't scribble over the data + loaded for another request. + + Here's a simple example of sending 4 requests without upfront buffer + on a channel with 1, 2 and 4 lanes, each line is one frame: + + 1 LANE (8 frames): + Lane 0: + ------------- + REQ 0 DISPATCHED + REQ 0 FETCHED + REQ 1 DISPATCHED + REQ 1 FETCHED + REQ 2 DISPATCHED + REQ 2 FETCHED + REQ 3 DISPATCHED + REQ 3 FETCHED + + Note how the request don't overlap, so they can all use the same buffer. + + 2 LANES (4 frames): + Lane 0: Lane 1: + ------------------------------------ + REQ 0 DISPATCHED REQ 1 DISPATCHED + REQ 0 FETCHED REQ 1 FETCHED + REQ 2 DISPATCHED REQ 3 DISPATCHED + REQ 2 FETCHED REQ 3 FETCHED + + This reduces the overall time to 4 frames, but now you need 2 buffers so + that requests don't scribble over each other. + + 4 LANES (2 frames): + Lane 0: Lane 1: Lane 2: Lane 3: + ---------------------------------------------------------------------------- + REQ 0 DISPATCHED REQ 1 DISPATCHED REQ 2 DISPATCHED REQ 3 DISPATCHED + REQ 0 FETCHED REQ 1 FETCHED REQ 2 FETCHED REQ 3 FETCHED + + Now we're down to the same 'best-case' latency as sending a single + request. + + Apart from the memory requirements for the streaming buffers (which is + under your control), you can be generous with the number of lanes, + they don't add any processing overhead. + + The last option for tweaking latency and throughput is channels. Each + channel works independently from other channels, so while one + channel is busy working through a large number of requests (or one + very long streaming download), you can set aside a high-priority channel + for requests that need to start as soon as possible. + + On platforms with threading support, each channel runs on its own + thread, but this is mainly an implementation detail to work around + the blocking traditional file IO functions, not for performance reasons. + + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + sfetch_setup(&(sfetch_desc_t){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_fetch.h + itself though, not any allocations in OS libraries. + + Memory allocation will only happen on the same thread where sfetch_setup() + was called, so you don't need to worry about thread-safety. + + + ERROR REPORTING AND LOGGING + =========================== + To get any logging information at all you need to provide a logging callback in the setup call, + the easiest way is to use sokol_log.h: + + #include "sokol_log.h" + + sfetch_setup(&(sfetch_desc_t){ + // ... + .logger.func = slog_func + }); + + To override logging with your own callback, first write a logging function like this: + + void my_log(const char* tag, // e.g. 'sfetch' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SFETCH_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_fetch.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-fetch like this: + + sfetch_setup(&(sfetch_desc_t){ + .logger = { + .func = my_log, + .user_data = my_user_data, + } + }); + + The provided logging function must be reentrant (e.g. be callable from + different threads). + + If you don't want to provide your own custom logger it is highly recommended to use + the standard logger in sokol_log.h instead, otherwise you won't see any warnings or + errors. + + + FUTURE PLANS / V2.0 IDEA DUMP + ============================= + - An optional polling API (as alternative to callback API) + - Move buffer-management into the API? The "manual management" + can be quite tricky especially for dynamic allocation scenarios, + API support for buffer management would simplify cases like + preventing that requests scribble over each other's buffers, or + an automatic garbage collection for dynamically allocated buffers, + or automatically falling back to dynamic allocation if static + buffers aren't big enough. + - Pluggable request handlers to load data from other "sources" + (especially HTTP downloads on native platforms via e.g. libcurl + would be useful) + - I'm currently not happy how the user-data block is handled, this + should getting and updating the user-data should be wrapped by + API functions (similar to bind/unbind buffer) + + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2019 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_FETCH_INCLUDED (1) +#include // size_t +#include +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_FETCH_API_DECL) +#define SOKOL_FETCH_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_FETCH_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_FETCH_IMPL) +#define SOKOL_FETCH_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_FETCH_API_DECL __declspec(dllimport) +#else +#define SOKOL_FETCH_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + sfetch_log_item_t + + Log items are defined via X-Macros, and expanded to an + enum 'sfetch_log_item', and in debug mode only, + corresponding strings. + + Used as parameter in the logging callback. +*/ +#define _SFETCH_LOG_ITEMS \ + _SFETCH_LOGITEM_XMACRO(OK, "Ok") \ + _SFETCH_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SFETCH_LOGITEM_XMACRO(FILE_PATH_UTF8_DECODING_FAILED, "failed converting file path from UTF8 to wide") \ + _SFETCH_LOGITEM_XMACRO(SEND_QUEUE_FULL, "send queue full (adjust via sfetch_desc_t.max_requests)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_CHANNEL_INDEX_TOO_BIG, "channel index too big (adjust via sfetch_desc_t.num_channels)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_PATH_IS_NULL, "file path is nullptr (sfetch_request_t.path)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_PATH_TOO_LONG, "file path is too long (SFETCH_MAX_PATH)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_CALLBACK_MISSING, "no callback provided (sfetch_request_t.callback)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_CHUNK_SIZE_GREATER_BUFFER_SIZE, "chunk size is greater buffer size (sfetch_request_t.chunk_size vs .buffer.size)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_USERDATA_PTR_IS_SET_BUT_USERDATA_SIZE_IS_NULL, "user data ptr is set but user data size is null (sfetch_request_t.user_data.ptr vs .size)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_USERDATA_PTR_IS_NULL_BUT_USERDATA_SIZE_IS_NOT, "user data ptr is null but size is not (sfetch_request_t.user_data.ptr vs .size)") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_USERDATA_SIZE_TOO_BIG, "user data size too big (see SFETCH_MAX_USERDATA_UINT64)") \ + _SFETCH_LOGITEM_XMACRO(CLAMPING_NUM_CHANNELS_TO_MAX_CHANNELS, "clamping num channels to SFETCH_MAX_CHANNELS") \ + _SFETCH_LOGITEM_XMACRO(REQUEST_POOL_EXHAUSTED, "request pool exhausted (tweak via sfetch_desc_t.max_requests)") \ + +#define _SFETCH_LOGITEM_XMACRO(item,msg) SFETCH_LOGITEM_##item, +typedef enum sfetch_log_item_t { + _SFETCH_LOG_ITEMS +} sfetch_log_item_t; +#undef _SFETCH_LOGITEM_XMACRO + +/* + sfetch_logger_t + + Used in sfetch_desc_t to provide a custom logging and error reporting + callback to sokol-fetch. +*/ +typedef struct sfetch_logger_t { + void (*func)( + const char* tag, // always "sfetch" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SFETCH_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_fetch.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} sfetch_logger_t; + +/* + sfetch_range_t + + A pointer-size pair struct to pass memory ranges into and out of sokol-fetch. + When initialized from a value type (array or struct) you can use the + SFETCH_RANGE() helper macro to build an sfetch_range_t struct. +*/ +typedef struct sfetch_range_t { + const void* ptr; + size_t size; +} sfetch_range_t; + +// disabling this for every includer isn't great, but the warnings are also quite pointless +#if defined(_MSC_VER) +#pragma warning(disable:4221) // /W4 only: nonstandard extension used: 'x': cannot be initialized using address of automatic variable 'y' +#pragma warning(disable:4204) // VS2015: nonstandard extension used: non-constant aggregate initializer +#endif +#if defined(__cplusplus) +#define SFETCH_RANGE(x) sfetch_range_t{ &x, sizeof(x) } +#else +#define SFETCH_RANGE(x) (sfetch_range_t){ &x, sizeof(x) } +#endif + +/* + sfetch_allocator_t + + Used in sfetch_desc_t to provide custom memory-alloc and -free functions + to sokol_fetch.h. If memory management should be overridden, both the + alloc and free function must be provided (e.g. it's not valid to + override one function but not the other). +*/ +typedef struct sfetch_allocator_t { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sfetch_allocator_t; + +/* configuration values for sfetch_setup() */ +typedef struct sfetch_desc_t { + uint32_t max_requests; // max number of active requests across all channels (default: 128) + uint32_t num_channels; // number of channels to fetch requests in parallel (default: 1) + uint32_t num_lanes; // max number of requests active on the same channel (default: 1) + sfetch_allocator_t allocator; // optional memory allocation overrides (default: malloc/free) + sfetch_logger_t logger; // optional log function overrides (default: NO LOGGING!) +} sfetch_desc_t; + +/* a request handle to identify an active fetch request, returned by sfetch_send() */ +typedef struct sfetch_handle_t { uint32_t id; } sfetch_handle_t; + +/* error codes */ +typedef enum sfetch_error_t { + SFETCH_ERROR_NO_ERROR, + SFETCH_ERROR_FILE_NOT_FOUND, + SFETCH_ERROR_NO_BUFFER, + SFETCH_ERROR_BUFFER_TOO_SMALL, + SFETCH_ERROR_UNEXPECTED_EOF, + SFETCH_ERROR_INVALID_HTTP_STATUS, + SFETCH_ERROR_CANCELLED +} sfetch_error_t; + +/* the response struct passed to the response callback */ +typedef struct sfetch_response_t { + sfetch_handle_t handle; // request handle this response belongs to + bool dispatched; // true when request is in DISPATCHED state (lane has been assigned) + bool fetched; // true when request is in FETCHED state (fetched data is available) + bool paused; // request is currently in paused state + bool finished; // this is the last response for this request + bool failed; // request has failed (always set together with 'finished') + bool cancelled; // request was cancelled (always set together with 'finished') + sfetch_error_t error_code; // more detailed error code when failed is true + uint32_t channel; // the channel which processes this request + uint32_t lane; // the lane this request occupies on its channel + const char* path; // the original filesystem path of the request + void* user_data; // pointer to read/write user-data area + uint32_t data_offset; // current offset of fetched data chunk in the overall file data + sfetch_range_t data; // the fetched data as ptr/size pair (data.ptr == buffer.ptr, data.size <= buffer.size) + sfetch_range_t buffer; // the user-provided buffer which holds the fetched data +} sfetch_response_t; + +/* response callback function signature */ +typedef void(*sfetch_callback_t)(const sfetch_response_t*); + +/* request parameters passed to sfetch_send() */ +typedef struct sfetch_request_t { + uint32_t channel; // index of channel this request is assigned to (default: 0) + const char* path; // filesystem path or HTTP URL (required) + sfetch_callback_t callback; // response callback function pointer (required) + uint32_t chunk_size; // number of bytes to load per stream-block (optional) + sfetch_range_t buffer; // a memory buffer where the data will be loaded into (optional) + sfetch_range_t user_data; // ptr/size of a POD user data block which will be memcpy'd (optional) +} sfetch_request_t; + +/* setup sokol-fetch (can be called on multiple threads) */ +SOKOL_FETCH_API_DECL void sfetch_setup(const sfetch_desc_t* desc); +/* discard a sokol-fetch context */ +SOKOL_FETCH_API_DECL void sfetch_shutdown(void); +/* return true if sokol-fetch has been setup */ +SOKOL_FETCH_API_DECL bool sfetch_valid(void); +/* get the desc struct that was passed to sfetch_setup() */ +SOKOL_FETCH_API_DECL sfetch_desc_t sfetch_desc(void); +/* return the max userdata size in number of bytes (SFETCH_MAX_USERDATA_UINT64 * sizeof(uint64_t)) */ +SOKOL_FETCH_API_DECL int sfetch_max_userdata_bytes(void); +/* return the value of the SFETCH_MAX_PATH implementation config value */ +SOKOL_FETCH_API_DECL int sfetch_max_path(void); + +/* send a fetch-request, get handle to request back */ +SOKOL_FETCH_API_DECL sfetch_handle_t sfetch_send(const sfetch_request_t* request); +/* return true if a handle is valid *and* the request is alive */ +SOKOL_FETCH_API_DECL bool sfetch_handle_valid(sfetch_handle_t h); +/* do per-frame work, moves requests into and out of IO threads, and invokes response-callbacks */ +SOKOL_FETCH_API_DECL void sfetch_dowork(void); + +/* bind a data buffer to a request (request must not currently have a buffer bound, must be called from response callback */ +SOKOL_FETCH_API_DECL void sfetch_bind_buffer(sfetch_handle_t h, sfetch_range_t buffer); +/* clear the 'buffer binding' of a request, returns previous buffer pointer (can be 0), must be called from response callback */ +SOKOL_FETCH_API_DECL void* sfetch_unbind_buffer(sfetch_handle_t h); +/* cancel a request that's in flight (will call response callback with .cancelled + .finished) */ +SOKOL_FETCH_API_DECL void sfetch_cancel(sfetch_handle_t h); +/* pause a request (will call response callback each frame with .paused) */ +SOKOL_FETCH_API_DECL void sfetch_pause(sfetch_handle_t h); +/* continue a paused request */ +SOKOL_FETCH_API_DECL void sfetch_continue(sfetch_handle_t h); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for c++ */ +inline void sfetch_setup(const sfetch_desc_t& desc) { return sfetch_setup(&desc); } +inline sfetch_handle_t sfetch_send(const sfetch_request_t& request) { return sfetch_send(&request); } + +#endif +#endif // SOKOL_FETCH_INCLUDED + +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation +#ifdef SOKOL_FETCH_IMPL +#define SOKOL_FETCH_IMPL_INCLUDED (1) + +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sfetch_desc_t.allocator to override memory allocation functions" +#endif + +#include /* malloc, free */ +#include /* memset, memcpy */ + +#ifndef SFETCH_MAX_PATH +#define SFETCH_MAX_PATH (1024) +#endif +#ifndef SFETCH_MAX_USERDATA_UINT64 +#define SFETCH_MAX_USERDATA_UINT64 (16) +#endif +#ifndef SFETCH_MAX_CHANNELS +#define SFETCH_MAX_CHANNELS (16) +#endif + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif + +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif + +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +#if defined(__EMSCRIPTEN__) + #include + #define _SFETCH_PLATFORM_EMSCRIPTEN (1) + #define _SFETCH_PLATFORM_WINDOWS (0) + #define _SFETCH_PLATFORM_POSIX (0) + #define _SFETCH_HAS_THREADS (0) +#elif defined(_WIN32) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #define _SFETCH_PLATFORM_WINDOWS (1) + #define _SFETCH_PLATFORM_EMSCRIPTEN (0) + #define _SFETCH_PLATFORM_POSIX (0) + #define _SFETCH_HAS_THREADS (1) +#else + #include + #include /* fopen, fread, fseek, fclose */ + #define _SFETCH_PLATFORM_POSIX (1) + #define _SFETCH_PLATFORM_EMSCRIPTEN (0) + #define _SFETCH_PLATFORM_WINDOWS (0) + #define _SFETCH_HAS_THREADS (1) +#endif + +// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██████ ██ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██████ ██████ ██ ███████ +// +// >>structs +typedef struct _sfetch_path_t { + char buf[SFETCH_MAX_PATH]; +} _sfetch_path_t; + +/* a thread with incoming and outgoing message queue syncing */ +#if _SFETCH_PLATFORM_POSIX +typedef struct { + pthread_t thread; + pthread_cond_t incoming_cond; + pthread_mutex_t incoming_mutex; + pthread_mutex_t outgoing_mutex; + pthread_mutex_t running_mutex; + pthread_mutex_t stop_mutex; + bool stop_requested; + bool valid; +} _sfetch_thread_t; +#elif _SFETCH_PLATFORM_WINDOWS +typedef struct { + HANDLE thread; + HANDLE incoming_event; + CRITICAL_SECTION incoming_critsec; + CRITICAL_SECTION outgoing_critsec; + CRITICAL_SECTION running_critsec; + CRITICAL_SECTION stop_critsec; + bool stop_requested; + bool valid; +} _sfetch_thread_t; +#endif + +/* file handle abstraction */ +#if _SFETCH_PLATFORM_POSIX +typedef FILE* _sfetch_file_handle_t; +#define _SFETCH_INVALID_FILE_HANDLE (0) +typedef void*(*_sfetch_thread_func_t)(void*); +#elif _SFETCH_PLATFORM_WINDOWS +typedef HANDLE _sfetch_file_handle_t; +#define _SFETCH_INVALID_FILE_HANDLE (INVALID_HANDLE_VALUE) +typedef LPTHREAD_START_ROUTINE _sfetch_thread_func_t; +#endif + +/* user-side per-request state */ +typedef struct { + bool pause; /* switch item to PAUSED state if true */ + bool cont; /* switch item back to FETCHING if true */ + bool cancel; /* cancel the request, switch into FAILED state */ + /* transfer IO => user thread */ + uint32_t fetched_offset; /* number of bytes fetched so far */ + uint32_t fetched_size; /* size of last fetched chunk */ + sfetch_error_t error_code; + bool finished; + /* user thread only */ + size_t user_data_size; + uint64_t user_data[SFETCH_MAX_USERDATA_UINT64]; +} _sfetch_item_user_t; + +/* thread-side per-request state */ +typedef struct { + /* transfer IO => user thread */ + uint32_t fetched_offset; + uint32_t fetched_size; + sfetch_error_t error_code; + bool failed; + bool finished; + /* IO thread only */ + #if _SFETCH_PLATFORM_EMSCRIPTEN + uint32_t http_range_offset; + #else + _sfetch_file_handle_t file_handle; + #endif + uint32_t content_size; +} _sfetch_item_thread_t; + +/* a request goes through the following states, ping-ponging between IO and user thread */ +typedef enum _sfetch_state_t { + _SFETCH_STATE_INITIAL, /* internal: request has just been initialized */ + _SFETCH_STATE_ALLOCATED, /* internal: request has been allocated from internal pool */ + _SFETCH_STATE_DISPATCHED, /* user thread: request has been dispatched to its IO channel */ + _SFETCH_STATE_FETCHING, /* IO thread: waiting for data to be fetched */ + _SFETCH_STATE_FETCHED, /* user thread: fetched data available */ + _SFETCH_STATE_PAUSED, /* user thread: request has been paused via sfetch_pause() */ + _SFETCH_STATE_FAILED, /* user thread: follow state or FETCHING if something went wrong */ +} _sfetch_state_t; + +/* an internal request item */ +#define _SFETCH_INVALID_LANE (0xFFFFFFFF) +typedef struct { + sfetch_handle_t handle; + _sfetch_state_t state; + uint32_t channel; + uint32_t lane; + uint32_t chunk_size; + sfetch_callback_t callback; + sfetch_range_t buffer; + + /* updated by IO-thread, off-limits to user thread */ + _sfetch_item_thread_t thread; + + /* accessible by user-thread, off-limits to IO thread */ + _sfetch_item_user_t user; + + /* big stuff at the end */ + _sfetch_path_t path; +} _sfetch_item_t; + +/* a pool of internal per-request items */ +typedef struct { + uint32_t size; + uint32_t free_top; + _sfetch_item_t* items; + uint32_t* free_slots; + uint32_t* gen_ctrs; + bool valid; +} _sfetch_pool_t; + +/* a ringbuffer for pool-slot ids */ +typedef struct { + uint32_t head; + uint32_t tail; + uint32_t num; + uint32_t* buf; +} _sfetch_ring_t; + +/* an IO channel with its own IO thread */ +struct _sfetch_t; +typedef struct { + struct _sfetch_t* ctx; // back-pointer to thread-local _sfetch state pointer, since this isn't accessible from the IO threads + _sfetch_ring_t free_lanes; + _sfetch_ring_t user_sent; + _sfetch_ring_t user_incoming; + _sfetch_ring_t user_outgoing; + #if _SFETCH_HAS_THREADS + _sfetch_ring_t thread_incoming; + _sfetch_ring_t thread_outgoing; + _sfetch_thread_t thread; + #endif + void (*request_handler)(struct _sfetch_t* ctx, uint32_t slot_id); + bool valid; +} _sfetch_channel_t; + +/* the sfetch global state */ +typedef struct _sfetch_t { + bool setup; + bool valid; + bool in_callback; + sfetch_desc_t desc; + _sfetch_pool_t pool; + _sfetch_channel_t chn[SFETCH_MAX_CHANNELS]; +} _sfetch_t; +#if _SFETCH_HAS_THREADS +#if defined(_MSC_VER) +static __declspec(thread) _sfetch_t* _sfetch; +#else +static __thread _sfetch_t* _sfetch; +#endif +#else +static _sfetch_t* _sfetch; +#endif +#define _sfetch_def(val, def) (((val) == 0) ? (def) : (val)) + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SFETCH_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _sfetch_log_messages[] = { + _SFETCH_LOG_ITEMS +}; +#undef _SFETCH_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SFETCH_PANIC(code) _sfetch_log(SFETCH_LOGITEM_ ##code, 0, __LINE__) +#define _SFETCH_ERROR(code) _sfetch_log(SFETCH_LOGITEM_ ##code, 1, __LINE__) +#define _SFETCH_WARN(code) _sfetch_log(SFETCH_LOGITEM_ ##code, 2, __LINE__) +#define _SFETCH_INFO(code) _sfetch_log(SFETCH_LOGITEM_ ##code, 3, __LINE__) + +static void _sfetch_log(sfetch_log_item_t log_item, uint32_t log_level, uint32_t line_nr) { + if (_sfetch->desc.logger.func) { + #if defined(SOKOL_DEBUG) + const char* filename = __FILE__; + const char* message = _sfetch_log_messages[log_item]; + #else + const char* filename = 0; + const char* message = 0; + #endif + _sfetch->desc.logger.func("sfetch", log_level, log_item, message, line_nr, filename, _sfetch->desc.logger.user_data); + } + else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory +_SOKOL_PRIVATE void _sfetch_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +_SOKOL_PRIVATE void* _sfetch_malloc_with_allocator(const sfetch_allocator_t* allocator, size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (allocator->alloc_fn) { + ptr = allocator->alloc_fn(size, allocator->user_data); + } else { + ptr = malloc(size); + } + if (0 == ptr) { + _SFETCH_PANIC(MALLOC_FAILED); + } + return ptr; +} + +_SOKOL_PRIVATE void* _sfetch_malloc(size_t size) { + return _sfetch_malloc_with_allocator(&_sfetch->desc.allocator, size); +} + +_SOKOL_PRIVATE void* _sfetch_malloc_clear(size_t size) { + void* ptr = _sfetch_malloc(size); + _sfetch_clear(ptr, size); + return ptr; +} + +_SOKOL_PRIVATE void _sfetch_free(void* ptr) { + if (_sfetch->desc.allocator.free_fn) { + _sfetch->desc.allocator.free_fn(ptr, _sfetch->desc.allocator.user_data); + } else { + free(ptr); + } +} + +_SOKOL_PRIVATE _sfetch_t* _sfetch_ctx(void) { + return _sfetch; +} + +_SOKOL_PRIVATE void _sfetch_path_copy(_sfetch_path_t* dst, const char* src) { + SOKOL_ASSERT(dst); + if (src && (strlen(src) < SFETCH_MAX_PATH)) { + #if defined(_MSC_VER) + strncpy_s(dst->buf, SFETCH_MAX_PATH, src, (SFETCH_MAX_PATH-1)); + #else + strncpy(dst->buf, src, SFETCH_MAX_PATH); + #endif + dst->buf[SFETCH_MAX_PATH-1] = 0; + } + else { + _sfetch_clear(dst->buf, SFETCH_MAX_PATH); + } +} + +_SOKOL_PRIVATE _sfetch_path_t _sfetch_path_make(const char* str) { + _sfetch_path_t res; + _sfetch_path_copy(&res, str); + return res; +} + +// ███ ███ ███████ ███████ ███████ █████ ██████ ███████ ██████ ██ ██ ███████ ██ ██ ███████ +// ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ███████ ███████ ███████ ██ ███ █████ ██ ██ ██ ██ █████ ██ ██ █████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ███████ ███████ ██ ██ ██████ ███████ ██████ ██████ ███████ ██████ ███████ +// ▀▀ +// >>message queue +_SOKOL_PRIVATE uint32_t _sfetch_ring_wrap(const _sfetch_ring_t* rb, uint32_t i) { + return i % rb->num; +} + +_SOKOL_PRIVATE void _sfetch_ring_discard(_sfetch_ring_t* rb) { + SOKOL_ASSERT(rb); + if (rb->buf) { + _sfetch_free(rb->buf); + rb->buf = 0; + } + rb->head = 0; + rb->tail = 0; + rb->num = 0; +} + +_SOKOL_PRIVATE bool _sfetch_ring_init(_sfetch_ring_t* rb, uint32_t num_slots) { + SOKOL_ASSERT(rb && (num_slots > 0)); + SOKOL_ASSERT(0 == rb->buf); + rb->head = 0; + rb->tail = 0; + /* one slot reserved to detect full vs empty */ + rb->num = num_slots + 1; + const size_t queue_size = rb->num * sizeof(sfetch_handle_t); + rb->buf = (uint32_t*) _sfetch_malloc_clear(queue_size); + if (rb->buf) { + return true; + } + else { + _sfetch_ring_discard(rb); + return false; + } +} + +_SOKOL_PRIVATE bool _sfetch_ring_full(const _sfetch_ring_t* rb) { + SOKOL_ASSERT(rb && rb->buf); + return _sfetch_ring_wrap(rb, rb->head + 1) == rb->tail; +} + +_SOKOL_PRIVATE bool _sfetch_ring_empty(const _sfetch_ring_t* rb) { + SOKOL_ASSERT(rb && rb->buf); + return rb->head == rb->tail; +} + +_SOKOL_PRIVATE uint32_t _sfetch_ring_count(const _sfetch_ring_t* rb) { + SOKOL_ASSERT(rb && rb->buf); + uint32_t count; + if (rb->head >= rb->tail) { + count = rb->head - rb->tail; + } + else { + count = (rb->head + rb->num) - rb->tail; + } + SOKOL_ASSERT(count < rb->num); + return count; +} + +_SOKOL_PRIVATE void _sfetch_ring_enqueue(_sfetch_ring_t* rb, uint32_t slot_id) { + SOKOL_ASSERT(rb && rb->buf); + SOKOL_ASSERT(!_sfetch_ring_full(rb)); + SOKOL_ASSERT(rb->head < rb->num); + rb->buf[rb->head] = slot_id; + rb->head = _sfetch_ring_wrap(rb, rb->head + 1); +} + +_SOKOL_PRIVATE uint32_t _sfetch_ring_dequeue(_sfetch_ring_t* rb) { + SOKOL_ASSERT(rb && rb->buf); + SOKOL_ASSERT(!_sfetch_ring_empty(rb)); + SOKOL_ASSERT(rb->tail < rb->num); + uint32_t slot_id = rb->buf[rb->tail]; + rb->tail = _sfetch_ring_wrap(rb, rb->tail + 1); + return slot_id; +} + +_SOKOL_PRIVATE uint32_t _sfetch_ring_peek(const _sfetch_ring_t* rb, uint32_t index) { + SOKOL_ASSERT(rb && rb->buf); + SOKOL_ASSERT(!_sfetch_ring_empty(rb)); + SOKOL_ASSERT(index < _sfetch_ring_count(rb)); + uint32_t rb_index = _sfetch_ring_wrap(rb, rb->tail + index); + return rb->buf[rb_index]; +} + +// ██████ ███████ ██████ ██ ██ ███████ ███████ ████████ ██████ ██████ ██████ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ █████ ██ ██ ██ ██ █████ ███████ ██ ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██████ ██████ ███████ ███████ ██ ██ ██████ ██████ ███████ +// ▀▀ +// >>request pool +_SOKOL_PRIVATE uint32_t _sfetch_make_id(uint32_t index, uint32_t gen_ctr) { + return (gen_ctr<<16) | (index & 0xFFFF); +} + +_SOKOL_PRIVATE sfetch_handle_t _sfetch_make_handle(uint32_t slot_id) { + sfetch_handle_t h; + h.id = slot_id; + return h; +} + +_SOKOL_PRIVATE uint32_t _sfetch_slot_index(uint32_t slot_id) { + return slot_id & 0xFFFF; +} + +_SOKOL_PRIVATE void _sfetch_item_init(_sfetch_item_t* item, uint32_t slot_id, const sfetch_request_t* request) { + SOKOL_ASSERT(item && (0 == item->handle.id)); + SOKOL_ASSERT(request && request->path); + _sfetch_clear(item, sizeof(_sfetch_item_t)); + item->handle.id = slot_id; + item->state = _SFETCH_STATE_INITIAL; + item->channel = request->channel; + item->chunk_size = request->chunk_size; + item->lane = _SFETCH_INVALID_LANE; + item->callback = request->callback; + item->buffer = request->buffer; + item->path = _sfetch_path_make(request->path); + #if !_SFETCH_PLATFORM_EMSCRIPTEN + item->thread.file_handle = _SFETCH_INVALID_FILE_HANDLE; + #endif + if (request->user_data.ptr && + (request->user_data.size > 0) && + (request->user_data.size <= (SFETCH_MAX_USERDATA_UINT64*8))) + { + item->user.user_data_size = request->user_data.size; + memcpy(item->user.user_data, request->user_data.ptr, request->user_data.size); + } +} + +_SOKOL_PRIVATE void _sfetch_item_discard(_sfetch_item_t* item) { + SOKOL_ASSERT(item && (0 != item->handle.id)); + _sfetch_clear(item, sizeof(_sfetch_item_t)); +} + +_SOKOL_PRIVATE void _sfetch_pool_discard(_sfetch_pool_t* pool) { + SOKOL_ASSERT(pool); + if (pool->free_slots) { + _sfetch_free(pool->free_slots); + pool->free_slots = 0; + } + if (pool->gen_ctrs) { + _sfetch_free(pool->gen_ctrs); + pool->gen_ctrs = 0; + } + if (pool->items) { + _sfetch_free(pool->items); + pool->items = 0; + } + pool->size = 0; + pool->free_top = 0; + pool->valid = false; +} + +_SOKOL_PRIVATE bool _sfetch_pool_init(_sfetch_pool_t* pool, uint32_t num_items) { + SOKOL_ASSERT(pool && (num_items > 0) && (num_items < ((1<<16)-1))); + SOKOL_ASSERT(0 == pool->items); + /* NOTE: item slot 0 is reserved for the special "invalid" item index 0*/ + pool->size = num_items + 1; + pool->free_top = 0; + const size_t items_size = pool->size * sizeof(_sfetch_item_t); + pool->items = (_sfetch_item_t*) _sfetch_malloc_clear(items_size); + /* generation counters indexable by pool slot index, slot 0 is reserved */ + const size_t gen_ctrs_size = sizeof(uint32_t) * pool->size; + pool->gen_ctrs = (uint32_t*) _sfetch_malloc_clear(gen_ctrs_size); + SOKOL_ASSERT(pool->gen_ctrs); + /* NOTE: it's not a bug to only reserve num_items here */ + const size_t free_slots_size = num_items * sizeof(int); + pool->free_slots = (uint32_t*) _sfetch_malloc_clear(free_slots_size); + if (pool->items && pool->free_slots) { + /* never allocate the 0-th item, this is the reserved 'invalid item' */ + for (uint32_t i = pool->size - 1; i >= 1; i--) { + pool->free_slots[pool->free_top++] = i; + } + pool->valid = true; + } + else { + /* allocation error */ + _sfetch_pool_discard(pool); + } + return pool->valid; +} + +_SOKOL_PRIVATE uint32_t _sfetch_pool_item_alloc(_sfetch_pool_t* pool, const sfetch_request_t* request) { + SOKOL_ASSERT(pool && pool->valid); + if (pool->free_top > 0) { + uint32_t slot_index = pool->free_slots[--pool->free_top]; + SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size)); + uint32_t slot_id = _sfetch_make_id(slot_index, ++pool->gen_ctrs[slot_index]); + _sfetch_item_init(&pool->items[slot_index], slot_id, request); + pool->items[slot_index].state = _SFETCH_STATE_ALLOCATED; + return slot_id; + } + else { + /* pool exhausted, return the 'invalid handle' */ + return _sfetch_make_id(0, 0); + } +} + +_SOKOL_PRIVATE void _sfetch_pool_item_free(_sfetch_pool_t* pool, uint32_t slot_id) { + SOKOL_ASSERT(pool && pool->valid); + uint32_t slot_index = _sfetch_slot_index(slot_id); + SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size)); + SOKOL_ASSERT(pool->items[slot_index].handle.id == slot_id); + #if defined(SOKOL_DEBUG) + /* debug check against double-free */ + for (uint32_t i = 0; i < pool->free_top; i++) { + SOKOL_ASSERT(pool->free_slots[i] != slot_index); + } + #endif + _sfetch_item_discard(&pool->items[slot_index]); + pool->free_slots[pool->free_top++] = slot_index; + SOKOL_ASSERT(pool->free_top <= (pool->size - 1)); +} + +/* return pointer to item by handle without matching id check */ +_SOKOL_PRIVATE _sfetch_item_t* _sfetch_pool_item_at(_sfetch_pool_t* pool, uint32_t slot_id) { + SOKOL_ASSERT(pool && pool->valid); + uint32_t slot_index = _sfetch_slot_index(slot_id); + SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size)); + return &pool->items[slot_index]; +} + +/* return pointer to item by handle with matching id check */ +_SOKOL_PRIVATE _sfetch_item_t* _sfetch_pool_item_lookup(_sfetch_pool_t* pool, uint32_t slot_id) { + SOKOL_ASSERT(pool && pool->valid); + if (0 != slot_id) { + _sfetch_item_t* item = _sfetch_pool_item_at(pool, slot_id); + if (item->handle.id == slot_id) { + return item; + } + } + return 0; +} + +// ██████ ██████ ███████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ███████ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ███████ ██ ██ ██ +// +// >>posix +#if _SFETCH_PLATFORM_POSIX +_SOKOL_PRIVATE _sfetch_file_handle_t _sfetch_file_open(const _sfetch_path_t* path) { + return fopen(path->buf, "rb"); +} + +_SOKOL_PRIVATE void _sfetch_file_close(_sfetch_file_handle_t h) { + fclose(h); +} + +_SOKOL_PRIVATE bool _sfetch_file_handle_valid(_sfetch_file_handle_t h) { + return h != _SFETCH_INVALID_FILE_HANDLE; +} + +_SOKOL_PRIVATE uint32_t _sfetch_file_size(_sfetch_file_handle_t h) { + fseek(h, 0, SEEK_END); + return (uint32_t) ftell(h); +} + +_SOKOL_PRIVATE bool _sfetch_file_read(_sfetch_file_handle_t h, uint32_t offset, uint32_t num_bytes, void* ptr) { + fseek(h, (long)offset, SEEK_SET); + return num_bytes == fread(ptr, 1, num_bytes, h); +} + +_SOKOL_PRIVATE bool _sfetch_thread_init(_sfetch_thread_t* thread, _sfetch_thread_func_t thread_func, void* thread_arg) { + SOKOL_ASSERT(thread && !thread->valid && !thread->stop_requested); + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutex_init(&thread->incoming_mutex, &attr); + pthread_mutexattr_destroy(&attr); + + pthread_mutexattr_init(&attr); + pthread_mutex_init(&thread->outgoing_mutex, &attr); + pthread_mutexattr_destroy(&attr); + + pthread_mutexattr_init(&attr); + pthread_mutex_init(&thread->running_mutex, &attr); + pthread_mutexattr_destroy(&attr); + + pthread_mutexattr_init(&attr); + pthread_mutex_init(&thread->stop_mutex, &attr); + pthread_mutexattr_destroy(&attr); + + pthread_condattr_t cond_attr; + pthread_condattr_init(&cond_attr); + pthread_cond_init(&thread->incoming_cond, &cond_attr); + pthread_condattr_destroy(&cond_attr); + + /* FIXME: in debug mode, the threads should be named */ + pthread_mutex_lock(&thread->running_mutex); + int res = pthread_create(&thread->thread, 0, thread_func, thread_arg); + thread->valid = (0 == res); + pthread_mutex_unlock(&thread->running_mutex); + return thread->valid; +} + +_SOKOL_PRIVATE void _sfetch_thread_request_stop(_sfetch_thread_t* thread) { + pthread_mutex_lock(&thread->stop_mutex); + thread->stop_requested = true; + pthread_mutex_unlock(&thread->stop_mutex); +} + +_SOKOL_PRIVATE bool _sfetch_thread_stop_requested(_sfetch_thread_t* thread) { + pthread_mutex_lock(&thread->stop_mutex); + bool stop_requested = thread->stop_requested; + pthread_mutex_unlock(&thread->stop_mutex); + return stop_requested; +} + +_SOKOL_PRIVATE void _sfetch_thread_join(_sfetch_thread_t* thread) { + SOKOL_ASSERT(thread); + if (thread->valid) { + pthread_mutex_lock(&thread->incoming_mutex); + _sfetch_thread_request_stop(thread); + pthread_cond_signal(&thread->incoming_cond); + pthread_mutex_unlock(&thread->incoming_mutex); + pthread_join(thread->thread, 0); + thread->valid = false; + } + pthread_mutex_destroy(&thread->stop_mutex); + pthread_mutex_destroy(&thread->running_mutex); + pthread_mutex_destroy(&thread->incoming_mutex); + pthread_mutex_destroy(&thread->outgoing_mutex); + pthread_cond_destroy(&thread->incoming_cond); +} + +/* called when the thread-func is entered, this blocks the thread func until + the _sfetch_thread_t object is fully initialized +*/ +_SOKOL_PRIVATE void _sfetch_thread_entered(_sfetch_thread_t* thread) { + pthread_mutex_lock(&thread->running_mutex); +} + +/* called by the thread-func right before it is left */ +_SOKOL_PRIVATE void _sfetch_thread_leaving(_sfetch_thread_t* thread) { + pthread_mutex_unlock(&thread->running_mutex); +} + +_SOKOL_PRIVATE void _sfetch_thread_enqueue_incoming(_sfetch_thread_t* thread, _sfetch_ring_t* incoming, _sfetch_ring_t* src) { + /* called from user thread */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(incoming && incoming->buf); + SOKOL_ASSERT(src && src->buf); + if (!_sfetch_ring_empty(src)) { + pthread_mutex_lock(&thread->incoming_mutex); + while (!_sfetch_ring_full(incoming) && !_sfetch_ring_empty(src)) { + _sfetch_ring_enqueue(incoming, _sfetch_ring_dequeue(src)); + } + pthread_cond_signal(&thread->incoming_cond); + pthread_mutex_unlock(&thread->incoming_mutex); + } +} + +_SOKOL_PRIVATE uint32_t _sfetch_thread_dequeue_incoming(_sfetch_thread_t* thread, _sfetch_ring_t* incoming) { + /* called from thread function */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(incoming && incoming->buf); + pthread_mutex_lock(&thread->incoming_mutex); + while (_sfetch_ring_empty(incoming) && !thread->stop_requested) { + pthread_cond_wait(&thread->incoming_cond, &thread->incoming_mutex); + } + uint32_t item = 0; + if (!thread->stop_requested) { + item = _sfetch_ring_dequeue(incoming); + } + pthread_mutex_unlock(&thread->incoming_mutex); + return item; +} + +_SOKOL_PRIVATE bool _sfetch_thread_enqueue_outgoing(_sfetch_thread_t* thread, _sfetch_ring_t* outgoing, uint32_t item) { + /* called from thread function */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(outgoing && outgoing->buf); + SOKOL_ASSERT(0 != item); + pthread_mutex_lock(&thread->outgoing_mutex); + bool result = false; + if (!_sfetch_ring_full(outgoing)) { + _sfetch_ring_enqueue(outgoing, item); + } + pthread_mutex_unlock(&thread->outgoing_mutex); + return result; +} + +_SOKOL_PRIVATE void _sfetch_thread_dequeue_outgoing(_sfetch_thread_t* thread, _sfetch_ring_t* outgoing, _sfetch_ring_t* dst) { + /* called from user thread */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(outgoing && outgoing->buf); + SOKOL_ASSERT(dst && dst->buf); + pthread_mutex_lock(&thread->outgoing_mutex); + while (!_sfetch_ring_full(dst) && !_sfetch_ring_empty(outgoing)) { + _sfetch_ring_enqueue(dst, _sfetch_ring_dequeue(outgoing)); + } + pthread_mutex_unlock(&thread->outgoing_mutex); +} +#endif /* _SFETCH_PLATFORM_POSIX */ + +// ██ ██ ██ ███ ██ ██████ ██████ ██ ██ ███████ +// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ███████ +// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ +// ███ ███ ██ ██ ████ ██████ ██████ ███ ███ ███████ +// +// >>windows +#if _SFETCH_PLATFORM_WINDOWS +_SOKOL_PRIVATE bool _sfetch_win32_utf8_to_wide(const char* src, wchar_t* dst, int dst_num_bytes) { + SOKOL_ASSERT(src && dst && (dst_num_bytes > 1)); + _sfetch_clear(dst, (size_t)dst_num_bytes); + const int dst_chars = dst_num_bytes / (int)sizeof(wchar_t); + const int dst_needed = MultiByteToWideChar(CP_UTF8, 0, src, -1, 0, 0); + if ((dst_needed > 0) && (dst_needed < dst_chars)) { + MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, dst_chars); + return true; + } + else { + /* input string doesn't fit into destination buffer */ + return false; + } +} + +_SOKOL_PRIVATE _sfetch_file_handle_t _sfetch_file_open(const _sfetch_path_t* path) { + wchar_t w_path[SFETCH_MAX_PATH]; + if (!_sfetch_win32_utf8_to_wide(path->buf, w_path, sizeof(w_path))) { + _SFETCH_ERROR(FILE_PATH_UTF8_DECODING_FAILED); + return 0; + } + _sfetch_file_handle_t h = CreateFileW( + w_path, /* lpFileName */ + GENERIC_READ, /* dwDesiredAccess */ + FILE_SHARE_READ, /* dwShareMode */ + NULL, /* lpSecurityAttributes */ + OPEN_EXISTING, /* dwCreationDisposition */ + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN, /* dwFlagsAndAttributes */ + NULL); /* hTemplateFile */ + return h; +} + +_SOKOL_PRIVATE void _sfetch_file_close(_sfetch_file_handle_t h) { + CloseHandle(h); +} + +_SOKOL_PRIVATE bool _sfetch_file_handle_valid(_sfetch_file_handle_t h) { + return h != _SFETCH_INVALID_FILE_HANDLE; +} + +_SOKOL_PRIVATE uint32_t _sfetch_file_size(_sfetch_file_handle_t h) { + return GetFileSize(h, NULL); +} + +_SOKOL_PRIVATE bool _sfetch_file_read(_sfetch_file_handle_t h, uint32_t offset, uint32_t num_bytes, void* ptr) { + LARGE_INTEGER offset_li; + offset_li.QuadPart = offset; + BOOL seek_res = SetFilePointerEx(h, offset_li, NULL, FILE_BEGIN); + if (seek_res) { + DWORD bytes_read = 0; + BOOL read_res = ReadFile(h, ptr, (DWORD)num_bytes, &bytes_read, NULL); + return read_res && (bytes_read == num_bytes); + } + else { + return false; + } +} + +_SOKOL_PRIVATE bool _sfetch_thread_init(_sfetch_thread_t* thread, _sfetch_thread_func_t thread_func, void* thread_arg) { + SOKOL_ASSERT(thread && !thread->valid && !thread->stop_requested); + + thread->incoming_event = CreateEventA(NULL, FALSE, FALSE, NULL); + SOKOL_ASSERT(NULL != thread->incoming_event); + InitializeCriticalSection(&thread->incoming_critsec); + InitializeCriticalSection(&thread->outgoing_critsec); + InitializeCriticalSection(&thread->running_critsec); + InitializeCriticalSection(&thread->stop_critsec); + + EnterCriticalSection(&thread->running_critsec); + const SIZE_T stack_size = 512 * 1024; + thread->thread = CreateThread(NULL, stack_size, thread_func, thread_arg, 0, NULL); + thread->valid = (NULL != thread->thread); + LeaveCriticalSection(&thread->running_critsec); + return thread->valid; +} + +_SOKOL_PRIVATE void _sfetch_thread_request_stop(_sfetch_thread_t* thread) { + EnterCriticalSection(&thread->stop_critsec); + thread->stop_requested = true; + LeaveCriticalSection(&thread->stop_critsec); +} + +_SOKOL_PRIVATE bool _sfetch_thread_stop_requested(_sfetch_thread_t* thread) { + EnterCriticalSection(&thread->stop_critsec); + bool stop_requested = thread->stop_requested; + LeaveCriticalSection(&thread->stop_critsec); + return stop_requested; +} + +_SOKOL_PRIVATE void _sfetch_thread_join(_sfetch_thread_t* thread) { + if (thread->valid) { + EnterCriticalSection(&thread->incoming_critsec); + _sfetch_thread_request_stop(thread); + BOOL set_event_res = SetEvent(thread->incoming_event); + _SOKOL_UNUSED(set_event_res); + SOKOL_ASSERT(set_event_res); + LeaveCriticalSection(&thread->incoming_critsec); + WaitForSingleObject(thread->thread, INFINITE); + CloseHandle(thread->thread); + thread->valid = false; + } + CloseHandle(thread->incoming_event); + DeleteCriticalSection(&thread->stop_critsec); + DeleteCriticalSection(&thread->running_critsec); + DeleteCriticalSection(&thread->outgoing_critsec); + DeleteCriticalSection(&thread->incoming_critsec); +} + +_SOKOL_PRIVATE void _sfetch_thread_entered(_sfetch_thread_t* thread) { + EnterCriticalSection(&thread->running_critsec); +} + +/* called by the thread-func right before it is left */ +_SOKOL_PRIVATE void _sfetch_thread_leaving(_sfetch_thread_t* thread) { + LeaveCriticalSection(&thread->running_critsec); +} + +_SOKOL_PRIVATE void _sfetch_thread_enqueue_incoming(_sfetch_thread_t* thread, _sfetch_ring_t* incoming, _sfetch_ring_t* src) { + /* called from user thread */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(incoming && incoming->buf); + SOKOL_ASSERT(src && src->buf); + if (!_sfetch_ring_empty(src)) { + EnterCriticalSection(&thread->incoming_critsec); + while (!_sfetch_ring_full(incoming) && !_sfetch_ring_empty(src)) { + _sfetch_ring_enqueue(incoming, _sfetch_ring_dequeue(src)); + } + LeaveCriticalSection(&thread->incoming_critsec); + BOOL set_event_res = SetEvent(thread->incoming_event); + _SOKOL_UNUSED(set_event_res); + SOKOL_ASSERT(set_event_res); + } +} + +_SOKOL_PRIVATE uint32_t _sfetch_thread_dequeue_incoming(_sfetch_thread_t* thread, _sfetch_ring_t* incoming) { + /* called from thread function */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(incoming && incoming->buf); + EnterCriticalSection(&thread->incoming_critsec); + while (_sfetch_ring_empty(incoming) && !thread->stop_requested) { + LeaveCriticalSection(&thread->incoming_critsec); + WaitForSingleObject(thread->incoming_event, INFINITE); + EnterCriticalSection(&thread->incoming_critsec); + } + uint32_t item = 0; + if (!thread->stop_requested) { + item = _sfetch_ring_dequeue(incoming); + } + LeaveCriticalSection(&thread->incoming_critsec); + return item; +} + +_SOKOL_PRIVATE bool _sfetch_thread_enqueue_outgoing(_sfetch_thread_t* thread, _sfetch_ring_t* outgoing, uint32_t item) { + /* called from thread function */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(outgoing && outgoing->buf); + EnterCriticalSection(&thread->outgoing_critsec); + bool result = false; + if (!_sfetch_ring_full(outgoing)) { + _sfetch_ring_enqueue(outgoing, item); + } + LeaveCriticalSection(&thread->outgoing_critsec); + return result; +} + +_SOKOL_PRIVATE void _sfetch_thread_dequeue_outgoing(_sfetch_thread_t* thread, _sfetch_ring_t* outgoing, _sfetch_ring_t* dst) { + /* called from user thread */ + SOKOL_ASSERT(thread && thread->valid); + SOKOL_ASSERT(outgoing && outgoing->buf); + SOKOL_ASSERT(dst && dst->buf); + EnterCriticalSection(&thread->outgoing_critsec); + while (!_sfetch_ring_full(dst) && !_sfetch_ring_empty(outgoing)) { + _sfetch_ring_enqueue(dst, _sfetch_ring_dequeue(outgoing)); + } + LeaveCriticalSection(&thread->outgoing_critsec); +} +#endif /* _SFETCH_PLATFORM_WINDOWS */ + +// ██████ ██ ██ █████ ███ ██ ███ ██ ███████ ██ ███████ +// ██ ██ ██ ██ ██ ████ ██ ████ ██ ██ ██ ██ +// ██ ███████ ███████ ██ ██ ██ ██ ██ ██ █████ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██ ██ ██ ████ ██ ████ ███████ ███████ ███████ +// +// >>channels + +/* per-channel request handler for native platforms accessing the local filesystem */ +#if _SFETCH_HAS_THREADS +_SOKOL_PRIVATE void _sfetch_request_handler(_sfetch_t* ctx, uint32_t slot_id) { + _sfetch_state_t state; + _sfetch_path_t* path; + _sfetch_item_thread_t* thread; + sfetch_range_t* buffer; + uint32_t chunk_size; + { + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id); + if (!item) { + return; + } + state = item->state; + SOKOL_ASSERT((state == _SFETCH_STATE_FETCHING) || + (state == _SFETCH_STATE_PAUSED) || + (state == _SFETCH_STATE_FAILED)); + path = &item->path; + thread = &item->thread; + buffer = &item->buffer; + chunk_size = item->chunk_size; + } + if (thread->failed) { + return; + } + if (state == _SFETCH_STATE_FETCHING) { + if ((buffer->ptr == 0) || (buffer->size == 0)) { + thread->error_code = SFETCH_ERROR_NO_BUFFER; + thread->failed = true; + } + else { + /* open file if not happened yet */ + if (!_sfetch_file_handle_valid(thread->file_handle)) { + SOKOL_ASSERT(path->buf[0]); + SOKOL_ASSERT(thread->fetched_offset == 0); + SOKOL_ASSERT(thread->fetched_size == 0); + thread->file_handle = _sfetch_file_open(path); + if (_sfetch_file_handle_valid(thread->file_handle)) { + thread->content_size = _sfetch_file_size(thread->file_handle); + } + else { + thread->error_code = SFETCH_ERROR_FILE_NOT_FOUND; + thread->failed = true; + } + } + if (!thread->failed) { + uint32_t read_offset = 0; + uint32_t bytes_to_read = 0; + if (chunk_size == 0) { + /* load entire file */ + if (thread->content_size <= buffer->size) { + bytes_to_read = thread->content_size; + read_offset = 0; + } + else { + /* provided buffer to small to fit entire file */ + thread->error_code = SFETCH_ERROR_BUFFER_TOO_SMALL; + thread->failed = true; + } + } + else { + if (chunk_size <= buffer->size) { + bytes_to_read = chunk_size; + read_offset = thread->fetched_offset; + if ((read_offset + bytes_to_read) > thread->content_size) { + bytes_to_read = thread->content_size - read_offset; + } + } + else { + /* provided buffer to small to fit next chunk */ + thread->error_code = SFETCH_ERROR_BUFFER_TOO_SMALL; + thread->failed = true; + } + } + if (!thread->failed) { + if (_sfetch_file_read(thread->file_handle, read_offset, bytes_to_read, (void*)buffer->ptr)) { + thread->fetched_size = bytes_to_read; + thread->fetched_offset += bytes_to_read; + } + else { + thread->error_code = SFETCH_ERROR_UNEXPECTED_EOF; + thread->failed = true; + } + } + } + } + SOKOL_ASSERT(thread->fetched_offset <= thread->content_size); + if (thread->failed || (thread->fetched_offset == thread->content_size)) { + if (_sfetch_file_handle_valid(thread->file_handle)) { + _sfetch_file_close(thread->file_handle); + thread->file_handle = _SFETCH_INVALID_FILE_HANDLE; + } + thread->finished = true; + } + } + /* ignore items in PAUSED or FAILED state */ +} + +#if _SFETCH_PLATFORM_WINDOWS +_SOKOL_PRIVATE DWORD WINAPI _sfetch_channel_thread_func(LPVOID arg) { +#else +_SOKOL_PRIVATE void* _sfetch_channel_thread_func(void* arg) { +#endif + _sfetch_channel_t* chn = (_sfetch_channel_t*) arg; + _sfetch_thread_entered(&chn->thread); + while (!_sfetch_thread_stop_requested(&chn->thread)) { + /* block until work arrives */ + uint32_t slot_id = _sfetch_thread_dequeue_incoming(&chn->thread, &chn->thread_incoming); + /* slot_id will be invalid if the thread was woken up to join */ + if (!_sfetch_thread_stop_requested(&chn->thread)) { + SOKOL_ASSERT(0 != slot_id); + chn->request_handler(chn->ctx, slot_id); + SOKOL_ASSERT(!_sfetch_ring_full(&chn->thread_outgoing)); + _sfetch_thread_enqueue_outgoing(&chn->thread, &chn->thread_outgoing, slot_id); + } + } + _sfetch_thread_leaving(&chn->thread); + return 0; +} +#endif /* _SFETCH_HAS_THREADS */ + +#if _SFETCH_PLATFORM_EMSCRIPTEN +EM_JS(void, sfetch_js_send_head_request, (uint32_t slot_id, const char* path_cstr), { + const path_str = UTF8ToString(path_cstr); + const req = new XMLHttpRequest(); + req.open('HEAD', path_str); + req.onreadystatechange = function() { + if (req.readyState == XMLHttpRequest.DONE) { + if (req.status == 200) { + const content_length = req.getResponseHeader('Content-Length'); + __sfetch_emsc_head_response(slot_id, content_length); + } + else { + __sfetch_emsc_failed_http_status(slot_id, req.status); + } + } + }; + req.send(); +}); + +/* if bytes_to_read != 0, a range-request will be sent, otherwise a normal request */ +EM_JS(void, sfetch_js_send_get_request, (uint32_t slot_id, const char* path_cstr, uint32_t offset, uint32_t bytes_to_read, void* buf_ptr, uint32_t buf_size), { + const path_str = UTF8ToString(path_cstr); + const req = new XMLHttpRequest(); + req.open('GET', path_str); + req.responseType = 'arraybuffer'; + const need_range_request = (bytes_to_read > 0); + if (need_range_request) { + req.setRequestHeader('Range', 'bytes='+offset+'-'+(offset+bytes_to_read-1)); + } + req.onreadystatechange = function() { + if (req.readyState == XMLHttpRequest.DONE) { + if ((req.status == 206) || ((req.status == 200) && !need_range_request)) { + const u8_array = new Uint8Array(\x2F\x2A\x2A @type {!ArrayBuffer} \x2A\x2F (req.response)); + const content_fetched_size = u8_array.length; + if (content_fetched_size <= buf_size) { + HEAPU8.set(u8_array, buf_ptr); + __sfetch_emsc_get_response(slot_id, bytes_to_read, content_fetched_size); + } + else { + __sfetch_emsc_failed_buffer_too_small(slot_id); + } + } + else { + __sfetch_emsc_failed_http_status(slot_id, req.status); + } + } + }; + req.send(); +}); + +/*=== emscripten specific C helper functions =================================*/ +#ifdef __cplusplus +extern "C" { +#endif +void _sfetch_emsc_send_get_request(uint32_t slot_id, _sfetch_item_t* item) { + if ((item->buffer.ptr == 0) || (item->buffer.size == 0)) { + item->thread.error_code = SFETCH_ERROR_NO_BUFFER; + item->thread.failed = true; + } + else { + uint32_t offset = 0; + uint32_t bytes_to_read = 0; + if (item->chunk_size > 0) { + /* send HTTP range request */ + SOKOL_ASSERT(item->thread.content_size > 0); + SOKOL_ASSERT(item->thread.http_range_offset < item->thread.content_size); + bytes_to_read = item->thread.content_size - item->thread.http_range_offset; + if (bytes_to_read > item->chunk_size) { + bytes_to_read = item->chunk_size; + } + SOKOL_ASSERT(bytes_to_read > 0); + offset = item->thread.http_range_offset; + } + sfetch_js_send_get_request(slot_id, item->path.buf, offset, bytes_to_read, (void*)item->buffer.ptr, item->buffer.size); + } +} + +/* called by JS when an initial HEAD request finished successfully (only when streaming chunks) */ +EMSCRIPTEN_KEEPALIVE void _sfetch_emsc_head_response(uint32_t slot_id, uint32_t content_length) { + _sfetch_t* ctx = _sfetch_ctx(); + if (ctx && ctx->valid) { + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id); + if (item) { + SOKOL_ASSERT(item->buffer.ptr && (item->buffer.size > 0)); + item->thread.content_size = content_length; + _sfetch_emsc_send_get_request(slot_id, item); + } + } +} + +/* called by JS when a followup GET request finished successfully */ +EMSCRIPTEN_KEEPALIVE void _sfetch_emsc_get_response(uint32_t slot_id, uint32_t range_fetched_size, uint32_t content_fetched_size) { + _sfetch_t* ctx = _sfetch_ctx(); + if (ctx && ctx->valid) { + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id); + if (item) { + item->thread.fetched_size = content_fetched_size; + item->thread.fetched_offset += content_fetched_size; + item->thread.http_range_offset += range_fetched_size; + if (item->chunk_size == 0) { + item->thread.finished = true; + } + else if (item->thread.http_range_offset >= item->thread.content_size) { + item->thread.finished = true; + } + _sfetch_ring_enqueue(&ctx->chn[item->channel].user_outgoing, slot_id); + } + } +} + +/* called by JS when an error occurred */ +EMSCRIPTEN_KEEPALIVE void _sfetch_emsc_failed_http_status(uint32_t slot_id, uint32_t http_status) { + _sfetch_t* ctx = _sfetch_ctx(); + if (ctx && ctx->valid) { + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id); + if (item) { + if (http_status == 404) { + item->thread.error_code = SFETCH_ERROR_FILE_NOT_FOUND; + } + else { + item->thread.error_code = SFETCH_ERROR_INVALID_HTTP_STATUS; + } + item->thread.failed = true; + item->thread.finished = true; + _sfetch_ring_enqueue(&ctx->chn[item->channel].user_outgoing, slot_id); + } + } +} + +EMSCRIPTEN_KEEPALIVE void _sfetch_emsc_failed_buffer_too_small(uint32_t slot_id) { + _sfetch_t* ctx = _sfetch_ctx(); + if (ctx && ctx->valid) { + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id); + if (item) { + item->thread.error_code = SFETCH_ERROR_BUFFER_TOO_SMALL; + item->thread.failed = true; + item->thread.finished = true; + _sfetch_ring_enqueue(&ctx->chn[item->channel].user_outgoing, slot_id); + } + } +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +_SOKOL_PRIVATE void _sfetch_request_handler(_sfetch_t* ctx, uint32_t slot_id) { + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, slot_id); + if (!item) { + return; + } + if (item->state == _SFETCH_STATE_FETCHING) { + if ((item->chunk_size > 0) && (item->thread.content_size == 0)) { + /* if streaming download is requested, and the content-length isn't known + yet, need to send a HEAD request first + */ + sfetch_js_send_head_request(slot_id, item->path.buf); + } + else { + /* otherwise, this is either a request to load the entire file, or + to load the next streaming chunk + */ + _sfetch_emsc_send_get_request(slot_id, item); + } + } + else { + /* just move all other items (e.g. paused or cancelled) + into the outgoing queue, so they won't get lost + */ + _sfetch_ring_enqueue(&ctx->chn[item->channel].user_outgoing, slot_id); + } + if (item->thread.failed) { + item->thread.finished = true; + } +} +#endif /* _SFETCH_PLATFORM_EMSCRIPTEN */ + +_SOKOL_PRIVATE void _sfetch_channel_discard(_sfetch_channel_t* chn) { + SOKOL_ASSERT(chn); + #if _SFETCH_HAS_THREADS + if (chn->valid) { + _sfetch_thread_join(&chn->thread); + } + _sfetch_ring_discard(&chn->thread_incoming); + _sfetch_ring_discard(&chn->thread_outgoing); + #endif + _sfetch_ring_discard(&chn->free_lanes); + _sfetch_ring_discard(&chn->user_sent); + _sfetch_ring_discard(&chn->user_incoming); + _sfetch_ring_discard(&chn->user_outgoing); + _sfetch_ring_discard(&chn->free_lanes); + chn->valid = false; +} + +_SOKOL_PRIVATE bool _sfetch_channel_init(_sfetch_channel_t* chn, _sfetch_t* ctx, uint32_t num_items, uint32_t num_lanes, void (*request_handler)(_sfetch_t* ctx, uint32_t)) { + SOKOL_ASSERT(chn && (num_items > 0) && request_handler); + SOKOL_ASSERT(!chn->valid); + bool valid = true; + chn->request_handler = request_handler; + chn->ctx = ctx; + valid &= _sfetch_ring_init(&chn->free_lanes, num_lanes); + for (uint32_t lane = 0; lane < num_lanes; lane++) { + _sfetch_ring_enqueue(&chn->free_lanes, lane); + } + valid &= _sfetch_ring_init(&chn->user_sent, num_items); + valid &= _sfetch_ring_init(&chn->user_incoming, num_lanes); + valid &= _sfetch_ring_init(&chn->user_outgoing, num_lanes); + #if _SFETCH_HAS_THREADS + valid &= _sfetch_ring_init(&chn->thread_incoming, num_lanes); + valid &= _sfetch_ring_init(&chn->thread_outgoing, num_lanes); + #endif + if (valid) { + chn->valid = true; + #if _SFETCH_HAS_THREADS + _sfetch_thread_init(&chn->thread, _sfetch_channel_thread_func, chn); + #endif + return true; + } + else { + _sfetch_channel_discard(chn); + return false; + } +} + +/* put a request into the channels sent-queue, this is where all new requests + are stored until a lane becomes free. +*/ +_SOKOL_PRIVATE bool _sfetch_channel_send(_sfetch_channel_t* chn, uint32_t slot_id) { + SOKOL_ASSERT(chn && chn->valid); + if (!_sfetch_ring_full(&chn->user_sent)) { + _sfetch_ring_enqueue(&chn->user_sent, slot_id); + return true; + } + else { + _SFETCH_ERROR(SEND_QUEUE_FULL); + return false; + } +} + +_SOKOL_PRIVATE void _sfetch_invoke_response_callback(_sfetch_item_t* item) { + sfetch_response_t response; + _sfetch_clear(&response, sizeof(response)); + response.handle = item->handle; + response.dispatched = (item->state == _SFETCH_STATE_DISPATCHED); + response.fetched = (item->state == _SFETCH_STATE_FETCHED); + response.paused = (item->state == _SFETCH_STATE_PAUSED); + response.finished = item->user.finished; + response.failed = (item->state == _SFETCH_STATE_FAILED); + response.cancelled = item->user.cancel; + response.error_code = item->user.error_code; + response.channel = item->channel; + response.lane = item->lane; + response.path = item->path.buf; + response.user_data = item->user.user_data; + response.data_offset = item->user.fetched_offset - item->user.fetched_size; + response.data.ptr = item->buffer.ptr; + response.data.size = item->user.fetched_size; + response.buffer = item->buffer; + item->callback(&response); +} + +_SOKOL_PRIVATE void _sfetch_cancel_item(_sfetch_item_t* item) { + item->state = _SFETCH_STATE_FAILED; + item->user.finished = true; + item->user.error_code = SFETCH_ERROR_CANCELLED; +} + +/* per-frame channel stuff: move requests in and out of the IO threads, call response callbacks */ +_SOKOL_PRIVATE void _sfetch_channel_dowork(_sfetch_channel_t* chn, _sfetch_pool_t* pool) { + + /* move items from sent- to incoming-queue permitting free lanes */ + const uint32_t num_sent = _sfetch_ring_count(&chn->user_sent); + const uint32_t avail_lanes = _sfetch_ring_count(&chn->free_lanes); + const uint32_t num_move = (num_sent < avail_lanes) ? num_sent : avail_lanes; + for (uint32_t i = 0; i < num_move; i++) { + const uint32_t slot_id = _sfetch_ring_dequeue(&chn->user_sent); + _sfetch_item_t* item = _sfetch_pool_item_lookup(pool, slot_id); + SOKOL_ASSERT(item); + SOKOL_ASSERT(item->state == _SFETCH_STATE_ALLOCATED); + // if the item was cancelled early, kick it out immediately + if (item->user.cancel) { + _sfetch_cancel_item(item); + _sfetch_invoke_response_callback(item); + _sfetch_pool_item_free(pool, slot_id); + continue; + } + item->state = _SFETCH_STATE_DISPATCHED; + item->lane = _sfetch_ring_dequeue(&chn->free_lanes); + // if no buffer provided yet, invoke response callback to do so + if (0 == item->buffer.ptr) { + _sfetch_invoke_response_callback(item); + } + _sfetch_ring_enqueue(&chn->user_incoming, slot_id); + } + + /* prepare incoming items for being moved into the IO thread */ + const uint32_t num_incoming = _sfetch_ring_count(&chn->user_incoming); + for (uint32_t i = 0; i < num_incoming; i++) { + const uint32_t slot_id = _sfetch_ring_peek(&chn->user_incoming, i); + _sfetch_item_t* item = _sfetch_pool_item_lookup(pool, slot_id); + SOKOL_ASSERT(item); + SOKOL_ASSERT(item->state != _SFETCH_STATE_INITIAL); + SOKOL_ASSERT(item->state != _SFETCH_STATE_FETCHING); + /* transfer input params from user- to thread-data */ + if (item->user.pause) { + item->state = _SFETCH_STATE_PAUSED; + item->user.pause = false; + } + if (item->user.cont) { + if (item->state == _SFETCH_STATE_PAUSED) { + item->state = _SFETCH_STATE_FETCHED; + } + item->user.cont = false; + } + if (item->user.cancel) { + _sfetch_cancel_item(item); + } + switch (item->state) { + case _SFETCH_STATE_DISPATCHED: + case _SFETCH_STATE_FETCHED: + item->state = _SFETCH_STATE_FETCHING; + break; + default: break; + } + } + + #if _SFETCH_HAS_THREADS + /* move new items into the IO threads and processed items out of IO threads */ + _sfetch_thread_enqueue_incoming(&chn->thread, &chn->thread_incoming, &chn->user_incoming); + _sfetch_thread_dequeue_outgoing(&chn->thread, &chn->thread_outgoing, &chn->user_outgoing); + #else + /* without threading just directly dequeue items from the user_incoming queue and + call the request handler, the user_outgoing queue will be filled as the + asynchronous HTTP requests sent by the request handler are completed + */ + while (!_sfetch_ring_empty(&chn->user_incoming)) { + uint32_t slot_id = _sfetch_ring_dequeue(&chn->user_incoming); + _sfetch_request_handler(chn->ctx, slot_id); + } + #endif + + /* drain the outgoing queue, prepare items for invoking the response + callback, and finally call the response callback, free finished items + */ + while (!_sfetch_ring_empty(&chn->user_outgoing)) { + const uint32_t slot_id = _sfetch_ring_dequeue(&chn->user_outgoing); + SOKOL_ASSERT(slot_id); + _sfetch_item_t* item = _sfetch_pool_item_lookup(pool, slot_id); + SOKOL_ASSERT(item && item->callback); + SOKOL_ASSERT(item->state != _SFETCH_STATE_INITIAL); + SOKOL_ASSERT(item->state != _SFETCH_STATE_ALLOCATED); + SOKOL_ASSERT(item->state != _SFETCH_STATE_DISPATCHED); + SOKOL_ASSERT(item->state != _SFETCH_STATE_FETCHED); + /* transfer output params from thread- to user-data */ + item->user.fetched_offset = item->thread.fetched_offset; + item->user.fetched_size = item->thread.fetched_size; + if (item->user.cancel) { + _sfetch_cancel_item(item); + } + else { + item->user.error_code = item->thread.error_code; + } + if (item->thread.finished) { + item->user.finished = true; + } + /* state transition */ + if (item->thread.failed) { + item->state = _SFETCH_STATE_FAILED; + } + else if (item->state == _SFETCH_STATE_FETCHING) { + item->state = _SFETCH_STATE_FETCHED; + } + _sfetch_invoke_response_callback(item); + + /* when the request is finished, free the lane for another request, + otherwise feed it back into the incoming queue + */ + if (item->user.finished) { + _sfetch_ring_enqueue(&chn->free_lanes, item->lane); + _sfetch_pool_item_free(pool, slot_id); + } + else { + _sfetch_ring_enqueue(&chn->user_incoming, slot_id); + } + } +} + +_SOKOL_PRIVATE bool _sfetch_validate_request(_sfetch_t* ctx, const sfetch_request_t* req) { + if (req->channel >= ctx->desc.num_channels) { + _SFETCH_ERROR(REQUEST_CHANNEL_INDEX_TOO_BIG); + return false; + } + if (!req->path) { + _SFETCH_ERROR(REQUEST_PATH_IS_NULL); + return false; + } + if (strlen(req->path) >= (SFETCH_MAX_PATH-1)) { + _SFETCH_ERROR(REQUEST_PATH_TOO_LONG); + return false; + } + if (!req->callback) { + _SFETCH_ERROR(REQUEST_CALLBACK_MISSING); + return false; + } + if (req->chunk_size > req->buffer.size) { + _SFETCH_ERROR(REQUEST_CHUNK_SIZE_GREATER_BUFFER_SIZE); + return false; + } + if (req->user_data.ptr && (req->user_data.size == 0)) { + _SFETCH_ERROR(REQUEST_USERDATA_PTR_IS_SET_BUT_USERDATA_SIZE_IS_NULL); + return false; + } + if (!req->user_data.ptr && (req->user_data.size > 0)) { + _SFETCH_ERROR(REQUEST_USERDATA_PTR_IS_NULL_BUT_USERDATA_SIZE_IS_NOT); + return false; + } + if (req->user_data.size > SFETCH_MAX_USERDATA_UINT64 * sizeof(uint64_t)) { + _SFETCH_ERROR(REQUEST_USERDATA_SIZE_TOO_BIG); + return false; + } + return true; +} + +_SOKOL_PRIVATE sfetch_desc_t _sfetch_desc_defaults(const sfetch_desc_t* desc) { + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + sfetch_desc_t res = *desc; + res.max_requests = _sfetch_def(desc->max_requests, 128); + res.num_channels = _sfetch_def(desc->num_channels, 1); + res.num_lanes = _sfetch_def(desc->num_lanes, 1); + return res; +} + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +SOKOL_API_IMPL void sfetch_setup(const sfetch_desc_t* desc_) { + SOKOL_ASSERT(desc_); + SOKOL_ASSERT(0 == _sfetch); + + sfetch_desc_t desc = _sfetch_desc_defaults(desc_); + _sfetch = (_sfetch_t*) _sfetch_malloc_with_allocator(&desc.allocator, sizeof(_sfetch_t)); + SOKOL_ASSERT(_sfetch); + _sfetch_t* ctx = _sfetch_ctx(); + _sfetch_clear(ctx, sizeof(_sfetch_t)); + ctx->desc = desc; + ctx->setup = true; + ctx->valid = true; + + /* replace zero-init items with default values */ + if (ctx->desc.num_channels > SFETCH_MAX_CHANNELS) { + ctx->desc.num_channels = SFETCH_MAX_CHANNELS; + _SFETCH_WARN(CLAMPING_NUM_CHANNELS_TO_MAX_CHANNELS); + } + + /* setup the global request item pool */ + ctx->valid &= _sfetch_pool_init(&ctx->pool, ctx->desc.max_requests); + + /* setup IO channels (one thread per channel) */ + for (uint32_t i = 0; i < ctx->desc.num_channels; i++) { + ctx->valid &= _sfetch_channel_init(&ctx->chn[i], ctx, ctx->desc.max_requests, ctx->desc.num_lanes, _sfetch_request_handler); + } +} + +SOKOL_API_IMPL void sfetch_shutdown(void) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->setup); + ctx->valid = false; + /* IO threads must be shutdown first */ + for (uint32_t i = 0; i < ctx->desc.num_channels; i++) { + if (ctx->chn[i].valid) { + _sfetch_channel_discard(&ctx->chn[i]); + } + } + _sfetch_pool_discard(&ctx->pool); + ctx->setup = false; + _sfetch_free(ctx); + _sfetch = 0; +} + +SOKOL_API_IMPL bool sfetch_valid(void) { + _sfetch_t* ctx = _sfetch_ctx(); + return ctx && ctx->valid; +} + +SOKOL_API_IMPL sfetch_desc_t sfetch_desc(void) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->valid); + return ctx->desc; +} + +SOKOL_API_IMPL int sfetch_max_userdata_bytes(void) { + return SFETCH_MAX_USERDATA_UINT64 * 8; +} + +SOKOL_API_IMPL int sfetch_max_path(void) { + return SFETCH_MAX_PATH; +} + +SOKOL_API_IMPL bool sfetch_handle_valid(sfetch_handle_t h) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->valid); + /* shortcut invalid handle */ + if (h.id == 0) { + return false; + } + return 0 != _sfetch_pool_item_lookup(&ctx->pool, h.id); +} + +SOKOL_API_IMPL sfetch_handle_t sfetch_send(const sfetch_request_t* request) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->setup); + + const sfetch_handle_t invalid_handle = _sfetch_make_handle(0); + if (!ctx->valid) { + return invalid_handle; + } + if (!_sfetch_validate_request(ctx, request)) { + return invalid_handle; + } + SOKOL_ASSERT(request->channel < ctx->desc.num_channels); + + uint32_t slot_id = _sfetch_pool_item_alloc(&ctx->pool, request); + if (0 == slot_id) { + _SFETCH_WARN(REQUEST_POOL_EXHAUSTED); + return invalid_handle; + } + if (!_sfetch_channel_send(&ctx->chn[request->channel], slot_id)) { + /* send failed because the channels sent-queue overflowed */ + _sfetch_pool_item_free(&ctx->pool, slot_id); + return invalid_handle; + } + return _sfetch_make_handle(slot_id); +} + +SOKOL_API_IMPL void sfetch_dowork(void) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->setup); + if (!ctx->valid) { + return; + } + /* we're pumping each channel 2x so that unfinished request items coming out the + IO threads can be moved back into the IO-thread immediately without + having to wait a frame + */ + ctx->in_callback = true; + for (int pass = 0; pass < 2; pass++) { + for (uint32_t chn_index = 0; chn_index < ctx->desc.num_channels; chn_index++) { + _sfetch_channel_dowork(&ctx->chn[chn_index], &ctx->pool); + } + } + ctx->in_callback = false; +} + +SOKOL_API_IMPL void sfetch_bind_buffer(sfetch_handle_t h, sfetch_range_t buffer) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->valid); + SOKOL_ASSERT(ctx->in_callback); + SOKOL_ASSERT(buffer.ptr && (buffer.size > 0)); + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id); + if (item) { + SOKOL_ASSERT((0 == item->buffer.ptr) && (0 == item->buffer.size)); + item->buffer = buffer; + } +} + +SOKOL_API_IMPL void* sfetch_unbind_buffer(sfetch_handle_t h) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->valid); + SOKOL_ASSERT(ctx->in_callback); + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id); + if (item) { + void* prev_buf_ptr = (void*)item->buffer.ptr; + item->buffer.ptr = 0; + item->buffer.size = 0; + return prev_buf_ptr; + } + else { + return 0; + } +} + +SOKOL_API_IMPL void sfetch_pause(sfetch_handle_t h) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->valid); + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id); + if (item) { + item->user.pause = true; + item->user.cont = false; + } +} + +SOKOL_API_IMPL void sfetch_continue(sfetch_handle_t h) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->valid); + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id); + if (item) { + item->user.cont = true; + item->user.pause = false; + } +} + +SOKOL_API_IMPL void sfetch_cancel(sfetch_handle_t h) { + _sfetch_t* ctx = _sfetch_ctx(); + SOKOL_ASSERT(ctx && ctx->valid); + _sfetch_item_t* item = _sfetch_pool_item_lookup(&ctx->pool, h.id); + if (item) { + item->user.cont = false; + item->user.pause = false; + item->user.cancel = true; + } +} +#endif /* SOKOL_FETCH_IMPL */ diff --git a/source/engine/thirdparty/sokol/sokol_gfx.h b/source/engine/thirdparty/sokol/sokol_gfx.h new file mode 100644 index 0000000..598f840 --- /dev/null +++ b/source/engine/thirdparty/sokol/sokol_gfx.h @@ -0,0 +1,18866 @@ +#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_GLCORE33 + #define SOKOL_GLES3 + #define SOKOL_D3D11 + #define SOKOL_METAL + #define SOKOL_WGPU + #define SOKOL_DUMMY_BACKEND + + I.e. for the GL 3.3 Core Profile it should look like this: + + #include ... + #include ... + #define SOKOL_IMPL + #define SOKOL_GLCORE33 + #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, 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, like a 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 pass: + + sg_begin_pass(const sg_pass* pass); + + Typically, 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 all the required information to render into the swapchain's surfaces. + + To start an 'offscreen 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 }); + + --- set the render 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 call (1..N vertex buffers, 0 or 1 index buffer, 0..N image objects and + 0..N sampler objects on the vertex-shader- and fragment-shader-stage + and then call + + sg_apply_bindings(const sg_bindings* bindings) + + to update the resource bindings + + --- optionally update shader uniform data with: + + sg_apply_uniforms(sg_shader_stage stage, int ub_index, 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. + + --- finish the current rendering 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 + + 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 + countermeasure to avoid 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 + 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_apply_pipeline + sg_apply_bindings + sg_apply_uniforms + sg_draw + + 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. The sg_swapchain struct must contain the + following information: + + - 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. + + NOTE: currently multisample-images cannot be bound as textures. + + 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 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 information about the shader resource + binding interface needed in the sokol-gfx validation layer and to properly + bind shader resources on the CPU-side to be consumable by the GPU-side. + + 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- shader-stage: + - for the desktop GL backend, source code must be provided in '#version 330' syntax + - 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 best compatibility) or alternatively + in HLSL5.0 syntax (other versions may work but are not tested), NOTE: 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, shader 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") + + - Depending on backend, information about the input vertex attributes used by the + vertex shader: + - Metal: no information needed since vertex attributes are always bound + by their attribute location defined in the shader via '[[attribute(N)]]' + - WebGPU: no 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)', PLEASE NOTE that the name-lookup method + may be removed at some point + - 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' + + - Information about each uniform block used in the shader: + - 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. + - 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 texture/image used in the shader: + - 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 + (currently it's not supported to fetch data from multisampled + textures in shaders, but this is planned for a later time) + + - A description of each sampler used in the shader: + - SG_SAMPLERTYPE_FILTERING, + - SG_SAMPLERTYPE_NONFILTERING, + - SG_SAMPLERTYPE_COMPARISON, + + - 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 + + 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. + + + 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! + + + 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. + + + A NOTE ON PORTABLE PACKED VERTEX FORMATS: + ========================================= + There are two things to consider when using packed + vertex formats like UBYTE4, SHORT2, etc which need to work + across all backends: + + - D3D11 can only convert *normalized* vertex formats to + floating point during vertex fetch, normalized formats + have a trailing 'N', and are "normalized" to a range + -1.0..+1.0 (for the signed formats) or 0.0..1.0 (for the + unsigned formats): + + - SG_VERTEXFORMAT_BYTE4N + - SG_VERTEXFORMAT_UBYTE4N + - SG_VERTEXFORMAT_SHORT2N + - SG_VERTEXFORMAT_USHORT2N + - SG_VERTEXFORMAT_SHORT4N + - SG_VERTEXFORMAT_USHORT4N + + D3D11 will not convert *non-normalized* vertex formats to floating point + vertex shader inputs, those can only be uses with the *ivecn* vertex shader + input types when D3D11 is used as backend (GL and Metal can use both formats) + + - SG_VERTEXFORMAT_BYTE4, + - SG_VERTEXFORMAT_UBYTE4 + - SG_VERTEXFORMAT_SHORT2 + - SG_VERTEXFORMAT_SHORT4 + + For a vertex input layout which works on all platforms, only use the following + vertex formats, and if needed "expand" the normalized vertex shader + inputs in the vertex shader by multiplying with 127.0, 255.0, 32767.0 or + 65535.0: + + - SG_VERTEXFORMAT_FLOAT, + - SG_VERTEXFORMAT_FLOAT2, + - SG_VERTEXFORMAT_FLOAT3, + - SG_VERTEXFORMAT_FLOAT4, + - SG_VERTEXFORMAT_BYTE4N, + - SG_VERTEXFORMAT_UBYTE4N, + - SG_VERTEXFORMAT_SHORT2N, + - SG_VERTEXFORMAT_USHORT2N + - SG_VERTEXFORMAT_SHORT4N, + - SG_VERTEXFORMAT_USHORT4N + - SG_VERTEXFORMAT_UINT10_N2 + - SG_VERTEXFORMAT_HALF2 + - SG_VERTEXFORMAT_HALF4 + + + 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)`, with up to + 4 uniform blocks per shader stage. + - Vertex shader uniform block bindings must start at `@group(0) @binding(0)` + - Fragment shader uniform blocks bindings must start at `@group(0) @binding(4)` + + All textures and samplers must use `@group(1)` and start at specific + offsets depending on resource type and shader stage. + - Vertex shader textures must start at `@group(1) @binding(0)` + - Vertex shader samplers must start at `@group(1) @binding(16)` + - Fragment shader textures must start at `@group(1) @binding(32)` + - Fragment shader samplers must start at `@group(1) @binding(48)` + + Note that the actual number of allowed per-stage texture- and sampler-bindings + in sokol-gfx is currently lower than the above ranges (currently only up to + 12 textures and 8 samplers per shader stage are allowed). + + If you use sokol-shdc to generate WGSL shader code, you don't need to worry + about the above binding convention since sokol-shdc assigns bind slots + automatically. + + - 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 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_vs_hash_key_mismatch + + The value to pay attention to is `.wgpu.num_bindgroup_cache_collisions`, + if this number if 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. + + - The sokol shader compiler generally adds `diagnostic(off, derivative_uniformity);` + into the WGSL output. Currently only the Chrome WebGPU implementation seems + to recognize this. + + - The vertex format SG_VERTEXFORMAT_UINT10_N2 is currently not supported because + WebGPU lacks a matching vertex format (this is currently being worked on though, + as soon as the vertex format shows up in webgpu.h, sokol_gfx.h will add support. + + - Likewise, the following sokol-gfx vertex formats are not supported in WebGPU: + R16, R16SN, RG16, RG16SN, RGBA16, RGBA16SN and all PVRTC compressed format. + 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 targets + sg_sampler sampler object 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 + number 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 +enum { + SG_INVALID_ID = 0, + SG_NUM_SHADER_STAGES = 2, + SG_NUM_INFLIGHT_FRAMES = 2, + SG_MAX_COLOR_ATTACHMENTS = 4, + SG_MAX_VERTEX_BUFFERS = 8, + SG_MAX_SHADERSTAGE_IMAGES = 12, + SG_MAX_SHADERSTAGE_SAMPLERS = 8, + SG_MAX_SHADERSTAGE_IMAGESAMPLERPAIRS = 12, + SG_MAX_SHADERSTAGE_UBS = 4, + SG_MAX_UB_MEMBERS = 16, + SG_MAX_VERTEX_ATTRIBUTES = 16, + SG_MAX_MIPMAPS = 16, + SG_MAX_TEXTUREARRAY_LAYERS = 128 +}; + +/* + 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_GLCORE33, + 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 samples as texture with linear + filtering + - render: the pixelformat can be used for render targets + - blend: blending is supported when using the pixelformat for + render targets + - msaa: multisample-antialiasing is supported when using the + pixelformat for render targets + - 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_PVRTC_RGB_2BPP, // FIXME: deprecated + SG_PIXELFORMAT_PVRTC_RGB_4BPP, // FIXME: deprecated + SG_PIXELFORMAT_PVRTC_RGBA_2BPP, // FIXME: deprecated + SG_PIXELFORMAT_PVRTC_RGBA_4BPP, // FIXME: deprecated + SG_PIXELFORMAT_ETC2_RGB8, + SG_PIXELFORMAT_ETC2_SRGB8, + SG_PIXELFORMAT_ETC2_RGB8A1, + SG_PIXELFORMAT_ETC2_RGBA8, + SG_PIXELFORMAT_ETC2_SRGB8A8, + SG_PIXELFORMAT_ETC2_RG11, + SG_PIXELFORMAT_ETC2_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 target + bool blend; // alpha-blending is supported + bool msaa; // pixel format can be used as MSAA render target + 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 +} 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 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 + + This indicates whether a buffer contains vertex- or index-data, + 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_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_desc 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 + (after 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_shader_stage + + There are 2 shader stages: vertex- and fragment-shader-stage. + Each shader stage consists of: + + - one slot for a shader function (provided as source- or byte-code) + - SG_MAX_SHADERSTAGE_UBS slots for uniform blocks + - SG_MAX_SHADERSTAGE_IMAGES slots for images used as textures by + the shader function +*/ +typedef enum sg_shader_stage { + SG_SHADERSTAGE_VS, + SG_SHADERSTAGE_FS, + _SG_SHADERSTAGE_FORCE_U32 = 0x7FFFFFFF +} sg_shader_stage; + +/* + 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 min_filter and mag_filter the default is SG_FILTER_NEAREST. + + For mipmap_filter the default is SG_FILTER_NONE. +*/ +typedef enum sg_filter { + _SG_FILTER_DEFAULT, // value 0 reserved for default-init + SG_FILTER_NONE, // FIXME: deprecated + 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 vertex data when creating a pipeline object. +*/ +typedef enum sg_vertex_format { + SG_VERTEXFORMAT_INVALID, + SG_VERTEXFORMAT_FLOAT, + SG_VERTEXFORMAT_FLOAT2, + SG_VERTEXFORMAT_FLOAT3, + SG_VERTEXFORMAT_FLOAT4, + SG_VERTEXFORMAT_BYTE4, + SG_VERTEXFORMAT_BYTE4N, + SG_VERTEXFORMAT_UBYTE4, + SG_VERTEXFORMAT_UBYTE4N, + SG_VERTEXFORMAT_SHORT2, + SG_VERTEXFORMAT_SHORT2N, + SG_VERTEXFORMAT_USHORT2N, + SG_VERTEXFORMAT_SHORT4, + SG_VERTEXFORMAT_SHORT4N, + 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. +*/ +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 really 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 a single 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. + + sg_pipeline_desc + .depth + .compare + .stencil + .front.compare + .back.compar + + sg_sampler_desc + .compare + + The default compare func for depth- and stencil-tests is + SG_COMPAREFUNC_ALWAYS. + + The default compare func for sampler 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 members: + + 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 SG_BLENDFACTOR_ZERO for destination factors. +*/ +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 members 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_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 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 targets 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 target 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 defaul 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 rolves 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 handle 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 an offscreen rendering pass, an sg_pass_action struct and sg_attachments + object must be provided, and for swapchain passes, and sg_pass_action and + an sg_swapchain struct. It is an error to provide both an sg_attachments + handle and an initialized sg_swapchain struct in the same sg_begin_pass(). + + An sg_begin_pass() call for an offscreen pass would look like this (where + `attachments` is an sg_attachments handle): + + sg_begin_pass(&(sg_pass){ + .action = { ... }, + .attachments = attachments, + }); + + ...and a swapchain render pass would look like this (using the sokol_glue.h + helper function sglue_swapchain() which gets the swapchain properties from + sokol_app.h): + + sg_begin_pass(&(sg_pass){ + .action = { ... }, + .swapchain = sglue_swapchain(), + }); + + You can also omit the .action object to get default pass action behaviour + (clear to color=grey, depth=1 and stencil=0). +*/ +typedef struct sg_pass { + uint32_t _start_canary; + 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 resource binding slots + of the sokol_gfx render pipeline, used as argument to the + sg_apply_bindings() function. + + 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 vertex shader stage images + - 0..N vertex shader stage samplers + - 0..N fragment shader stage images + - 0..N fragment shader stage samplers + + The max number of vertex buffer and shader stage images + are defined by the SG_MAX_VERTEX_BUFFERS and + SG_MAX_SHADERSTAGE_IMAGES configuration constants. + + 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_stage_bindings { + sg_image images[SG_MAX_SHADERSTAGE_IMAGES]; + sg_sampler samplers[SG_MAX_SHADERSTAGE_SAMPLERS]; +} sg_stage_bindings; + +typedef struct sg_bindings { + uint32_t _start_canary; + sg_buffer vertex_buffers[SG_MAX_VERTEX_BUFFERS]; + int vertex_buffer_offsets[SG_MAX_VERTEX_BUFFERS]; + sg_buffer index_buffer; + int index_buffer_offset; + sg_stage_bindings vs; + sg_stage_bindings fs; + 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 trace hooks) + + The label will be ignored by sokol_gfx.h, it is only useful + when hooking into sg_make_buffer() or sg_init_buffer() via + the sg_install_trace_hooks() function. + + 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 mutable buffers without initial data, keep the .data item + zero-initialized, and set the buffer size in the .size item instead. + + 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_NONE + .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 + + The structure sg_shader_desc defines all creation parameters for shader + programs, used as input to the sg_make_shader() function: + + - reflection information for vertex attributes (vertex shader inputs): + - vertex attribute name (only optionally used by GLES3 and GL) + - a semantic name and index (required for D3D11) + - for each shader-stage (vertex and fragment): + - the shader source or bytecode + - an optional entry function name + - an optional compile target (only for D3D11 when source is provided, + defaults are "vs_4_0" and "ps_4_0") + - reflection info for each uniform block used by the shader stage: + - the size of the uniform block in bytes + - a memory layout hint (native vs std140, only required for GL backends) + - reflection info for each uniform block member (only required for GL backends): + - member name + - member type (SG_UNIFORMTYPE_xxx) + - if the member is an array, the number of array items + - reflection info for textures used in the shader stage: + - the image type (SG_IMAGETYPE_xxx) + - the image-sample type (SG_IMAGESAMPLETYPE_xxx, default is SG_IMAGESAMPLETYPE_FLOAT) + - whether the shader expects a multisampled texture + - reflection info for samplers used in the shader stage: + - the sampler type (SG_SAMPLERTYPE_xxx) + - reflection info for each image-sampler-pair used by the shader: + - the texture slot of the involved texture + - the sampler slot of the involved sampler + - for GLSL only: the name of the combined image-sampler object + + For all GL backends, shader source-code must be provided. For D3D11 and Metal, + either shader source-code or byte-code can be provided. + + 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 struct sg_shader_attr_desc { + const char* name; // GLSL vertex attribute name (optional) + const char* sem_name; // HLSL semantic name + int sem_index; // HLSL semantic index +} sg_shader_attr_desc; + +typedef struct sg_shader_uniform_desc { + const char* name; + sg_uniform_type type; + int array_count; +} sg_shader_uniform_desc; + +typedef struct sg_shader_uniform_block_desc { + size_t size; + sg_uniform_layout layout; + sg_shader_uniform_desc uniforms[SG_MAX_UB_MEMBERS]; +} sg_shader_uniform_block_desc; + +typedef struct sg_shader_image_desc { + bool used; + bool multisampled; + sg_image_type image_type; + sg_image_sample_type sample_type; +} sg_shader_image_desc; + +typedef struct sg_shader_sampler_desc { + bool used; + sg_sampler_type sampler_type; +} sg_shader_sampler_desc; + +typedef struct sg_shader_image_sampler_pair_desc { + bool used; + int image_slot; + int sampler_slot; + const char* glsl_name; +} sg_shader_image_sampler_pair_desc; + +typedef struct sg_shader_stage_desc { + const char* source; + sg_range bytecode; + const char* entry; + const char* d3d11_target; + sg_shader_uniform_block_desc uniform_blocks[SG_MAX_SHADERSTAGE_UBS]; + sg_shader_image_desc images[SG_MAX_SHADERSTAGE_IMAGES]; + sg_shader_sampler_desc samplers[SG_MAX_SHADERSTAGE_SAMPLERS]; + sg_shader_image_sampler_pair_desc image_sampler_pairs[SG_MAX_SHADERSTAGE_IMAGESAMPLERPAIRS]; +} sg_shader_stage_desc; + +typedef struct sg_shader_desc { + uint32_t _start_canary; + sg_shader_attr_desc attrs[SG_MAX_VERTEX_ATTRIBUTES]; + sg_shader_stage_desc vs; + sg_shader_stage_desc fs; + 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: + + - 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: + + .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_VERTEX_BUFFERS]; + 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; + 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)(sg_shader_stage stage, 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 (*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_pass_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 tracking stats about a + render frame. Obtained by calling sg_query_frame_stats(). The returned + struct will 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; +} 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; +} 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_ps_set_shader_resources; + uint32_t num_vs_set_samplers; + uint32_t num_ps_set_samplers; +} 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_texture; + uint32_t num_set_fragment_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; +} 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_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_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. +*/ +#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_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 (gl)") \ + _SG_LOGITEM_XMACRO(GL_TEXTURE_NAME_NOT_FOUND_IN_SHADER, "texture name not found in shader (gl)") \ + _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_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_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_VERTEX_SHADER_ENTRY_NOT_FOUND, "vertex shader entry function not found (metal)") \ + _SG_LOGITEM_XMACRO(METAL_FRAGMENT_SHADER_ENTRY_NOT_FOUND, "fragment shader entry not found (metal)") \ + _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_TOO_MANY_IMAGES, "shader uses too many sampled images on shader stage (wgpu)") \ + _SG_LOGITEM_XMACRO(WGPU_SHADER_TOO_MANY_SAMPLERS, "shader uses too many samplers on shader stage (wgpu)") \ + _SG_LOGITEM_XMACRO(WGPU_SHADER_CREATE_BINDGROUP_LAYOUT_FAILED, "wgpuDeviceCreateBindGroupLayout() for shader stage failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_PIPELINE_LAYOUT_FAILED, "wgpuDeviceCreatePipelineLayout() failed") \ + _SG_LOGITEM_XMACRO(WGPU_CREATE_RENDER_PIPELINE_FAILED, "wgpuDeviceCreateRenderPipeline() 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 SG_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(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_SIZE, "sg_buffer_desc.size and .data.size cannot both be 0") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_DATA, "immutable buffers must be initialized with data (sg_buffer_desc.data.ptr and sg_buffer_desc.data.size)") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_DATA_SIZE, "immutable buffer data size differs from buffer size") \ + _SG_LOGITEM_XMACRO(VALIDATE_BUFFERDESC_NO_DATA, "dynamic/stream usage buffers cannot be initialized with data") \ + _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_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_MINFILTER_NONE, "sg_sampler_desc.min_filter cannot be SG_FILTER_NONE") \ + _SG_LOGITEM_XMACRO(VALIDATE_SAMPLERDESC_MAGFILTER_NONE, "sg_sampler_desc.mag_filter cannot be SG_FILTER_NONE") \ + _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_SOURCE, "shader source code required") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_BYTECODE, "shader byte code required") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE, "shader source or byte code required") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_BYTECODE_SIZE, "shader byte code length (in bytes) required") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_CONT_UBS, "shader uniform blocks must occupy continuous slots") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS, "uniform block members must occupy continuous slots") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_UB_MEMBERS, "GL backend requires uniform block member declarations") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UB_MEMBER_NAME, "uniform block member name missing") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UB_SIZE_MISMATCH, "size of uniform block members doesn't match uniform block size") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UB_ARRAY_COUNT, "uniform array count must be >= 1") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_UB_STD140_ARRAY_TYPE, "uniform arrays only allowed for FLOAT4, INT4, MAT4 in std140 layout") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_CONT_IMAGES, "shader stage images must occupy continuous slots (sg_shader_desc.vs|fs.images[])") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_CONT_SAMPLERS, "shader stage samplers must occupy continuous slots (sg_shader_desc.vs|fs.samplers[])") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_IMAGE_SLOT_OUT_OF_RANGE, "shader stage: image-sampler-pair image slot index is out of range (sg_shader_desc.vs|fs.image_sampler_pairs[].image_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_SAMPLER_SLOT_OUT_OF_RANGE, "shader stage: image-sampler-pair image slot index is out of range (sg_shader_desc.vs|fs.image_sampler_pairs[].sampler_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_NAME_REQUIRED_FOR_GL, "shader stage: image-sampler-pairs must be named in GL (sg_shader_desc.vs|fs.image_sampler_pairs[].name)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_HAS_NAME_BUT_NOT_USED, "shader stage: image-sampler-pair has name but .used field not true") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_HAS_IMAGE_BUT_NOT_USED, "shader stage: image-sampler-pair has .image_slot != 0 but .used field not true") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_HAS_SAMPLER_BUT_NOT_USED, "shader stage: image-sampler-pair .sampler_slot != 0 but .used field not true") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NONFILTERING_SAMPLER_REQUIRED, "shader stage: image sample type UNFILTERABLE_FLOAT, UINT, SINT can only be used with NONFILTERING sampler") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_COMPARISON_SAMPLER_REQUIRED, "shader stage: image sample type DEPTH can only be used with COMPARISON sampler") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_IMAGE_NOT_REFERENCED_BY_IMAGE_SAMPLER_PAIRS, "shader stage: one or more images are note referenced by (sg_shader_desc.vs|fs.image_sampler_pairs[].image_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_SAMPLER_NOT_REFERENCED_BY_IMAGE_SAMPLER_PAIRS, "shader stage: one or more samplers are not referenced by image-sampler-pairs (sg_shader_desc.vs|fs.image_sampler_pairs[].sampler_slot)") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_NO_CONT_IMAGE_SAMPLER_PAIRS, "shader stage image-sampler-pairs must occupy continuous slots (sg_shader_desc.vs|fs.image_samplers[])") \ + _SG_LOGITEM_XMACRO(VALIDATE_SHADERDESC_ATTR_SEMANTICS, "D3D11 backend requires vertex attribute semantics") \ + _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_NO_ATTRS, "sg_pipeline_desc.layout.attrs is empty or not continuous") \ + _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_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_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_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_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_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_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_VBS, "sg_apply_bindings: number of vertex buffers doesn't match number of pipeline vertex layouts") \ + _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_VS_EXPECTED_IMAGE_BINDING, "sg_apply_bindings: image binding on vertex stage is missing or the image handle is invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_IMG_EXISTS, "sg_apply_bindings: image bound to vertex stage no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_IMAGE_TYPE_MISMATCH, "sg_apply_bindings: type of image bound to vertex stage doesn't match shader desc") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_IMAGE_MSAA, "sg_apply_bindings: cannot bind image with sample_count>1 to vertex stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_EXPECTED_FILTERABLE_IMAGE, "sg_apply_bindings: filterable image expected on vertex stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_EXPECTED_DEPTH_IMAGE, "sg_apply_bindings: depth image expected on vertex stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_UNEXPECTED_IMAGE_BINDING, "sg_apply_bindings: unexpected image binding on vertex stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_EXPECTED_SAMPLER_BINDING, "sg_apply_bindings: sampler binding on vertex stage is missing or the sampler handle is invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_UNEXPECTED_SAMPLER_COMPARE_NEVER, "sg_apply_bindings: shader expects SG_SAMPLERTYPE_COMPARISON on vertex stage but sampler has SG_COMPAREFUNC_NEVER") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_EXPECTED_SAMPLER_COMPARE_NEVER, "sg_apply_bindings: shader expects SG_SAMPLERTYPE_FILTERING or SG_SAMPLERTYPE_NONFILTERING on vertex stage but sampler doesn't have SG_COMPAREFUNC_NEVER") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_EXPECTED_NONFILTERING_SAMPLER, "sg_apply_bindings: shader expected SG_SAMPLERTYPE_NONFILTERING on vertex stage, but sampler has SG_FILTER_LINEAR filters") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_UNEXPECTED_SAMPLER_BINDING, "sg_apply_bindings: unexpected sampler binding on vertex stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_VS_SMP_EXISTS, "sg_apply_bindings: sampler bound to vertex stage no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_EXPECTED_IMAGE_BINDING, "sg_apply_bindings: image binding on fragment stage is missing or the image handle is invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_IMG_EXISTS, "sg_apply_bindings: image bound to fragment stage no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_IMAGE_TYPE_MISMATCH, "sg_apply_bindings: type of image bound to fragment stage doesn't match shader desc") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_IMAGE_MSAA, "sg_apply_bindings: cannot bind image with sample_count>1 to fragment stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_EXPECTED_FILTERABLE_IMAGE, "sg_apply_bindings: filterable image expected on fragment stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_EXPECTED_DEPTH_IMAGE, "sg_apply_bindings: depth image expected on fragment stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_UNEXPECTED_IMAGE_BINDING, "sg_apply_bindings: unexpected image binding on fragment stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_EXPECTED_SAMPLER_BINDING, "sg_apply_bindings: sampler binding on fragment stage is missing or the sampler handle is invalid") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_UNEXPECTED_SAMPLER_COMPARE_NEVER, "sg_apply_bindings: shader expects SG_SAMPLERTYPE_COMPARISON on fragment stage but sampler has SG_COMPAREFUNC_NEVER") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_EXPECTED_SAMPLER_COMPARE_NEVER, "sg_apply_bindings: shader expects SG_SAMPLERTYPE_FILTERING on fragment stage but sampler doesn't have SG_COMPAREFUNC_NEVER") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_EXPECTED_NONFILTERING_SAMPLER, "sg_apply_bindings: shader expected SG_SAMPLERTYPE_NONFILTERING on fragment stage, but sampler has SG_FILTER_LINEAR filters") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_UNEXPECTED_SAMPLER_BINDING, "sg_apply_bindings: unexpected sampler binding on fragment stage") \ + _SG_LOGITEM_XMACRO(VALIDATE_ABND_FS_SMP_EXISTS, "sg_apply_bindings: sampler bound to fragment stage no longer alive") \ + _SG_LOGITEM_XMACRO(VALIDATE_AUB_NO_PIPELINE, "sg_apply_uniforms: must be called after sg_apply_pipeline()") \ + _SG_LOGITEM_XMACRO(VALIDATE_AUB_NO_UB_AT_SLOT, "sg_apply_uniforms: no uniform block declaration at this shader stage UB slot") \ + _SG_LOGITEM_XMACRO(VALIDATE_AUB_SIZE, "sg_apply_uniforms: data size doesn't match declared uniform block size") \ + _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. + + NOTE that all callback function pointers come in two versions, one without + a userdata pointer, and one with a userdata pointer. You would + either initialize one or the other depending on whether you pass data + to your callbacks. + + The default configuration is: + + .buffer_pool_size 128 + .image_pool_size 128 + .sampler_pool_size 64 + .shader_pool_size 32 + .pipeline_pool_size 64 + .pass_pool_size 16 + .uniform_buffer_size 4 MB (4*1024*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 (const void*) to sokol_gfx, which will use a unretained + bridged cast (__bridged 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 for the + duration of the sokol_gfx call! + + .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 + + 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; // WGPUDevice +} 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 install + a logger (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_commit_listeners; + bool disable_validation; // disable validation layer even in debug mode, useful for tests + 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); + +// rendering 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(sg_shader_stage stage, int ub_index, 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_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); + +// 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* vs_cbufs[SG_MAX_SHADERSTAGE_UBS]; // ID3D11Buffer* (vertex stage constant buffers) + const void* fs_cbufs[SG_MAX_SHADERSTAGE_UBS]; // ID3D11Buffer* (fragment stage constant buffers) + 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* vs_lib; // id + const void* fs_lib; // id + const void* vs_func; // id + const void* fs_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* pip; // WGPURenderPipeline +} 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 in current pass (or zero if outside pass) +SOKOL_GFX_API_DECL const void* sg_mtl_render_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 +SOKOL_GFX_API_DECL const void* sg_wgpu_render_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(sg_shader_stage stage, int ub_index, const sg_range& data) { return sg_apply_uniforms(stage, ub_index, &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_GLCORE33)||defined(SOKOL_GLES3)||defined(SOKOL_D3D11)||defined(SOKOL_METAL)||defined(SOKOL_WGPU)||defined(SOKOL_DUMMY_BACKEND)) +#error "Please select a backend with SOKOL_GLCORE33, 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 +#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_GLCORE33) || 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_GLCORE33) && !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() + #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__) || defined(__ANDROID__) + #if defined(SOKOL_GLES3) + #include + #endif + #elif defined(__linux__) || defined(__unix__) + #if defined(SOKOL_GLCORE33) + #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_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 + #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_RGB_PVRTC_2BPPV1_IMG + #define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01 + #endif + #ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 + #endif + #ifndef GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG + #define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03 + #endif + #ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 + #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_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 _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 + +// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██████ ██ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██████ ██████ ██ ███████ +// +// >>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_init_pool(_sg_pool_t* pool, int num); +_SOKOL_PRIVATE void _sg_discard_pool(_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_reset_slot(_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); + +// constants +enum { + _SG_STRING_SIZE = 16, + _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_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 { + size_t size; +} _sg_shader_uniform_block_t; + +typedef struct { + sg_image_type image_type; + sg_image_sample_type sample_type; + bool multisampled; +} _sg_shader_image_t; + +typedef struct { + sg_sampler_type sampler_type; +} _sg_shader_sampler_t; + +// combined image sampler mappings, only needed on GL +typedef struct { + int image_slot; + int sampler_slot; +} _sg_shader_image_sampler_t; + +typedef struct { + int num_uniform_blocks; + int num_images; + int num_samplers; + int num_image_samplers; + _sg_shader_uniform_block_t uniform_blocks[SG_MAX_SHADERSTAGE_UBS]; + _sg_shader_image_t images[SG_MAX_SHADERSTAGE_IMAGES]; + _sg_shader_sampler_t samplers[SG_MAX_SHADERSTAGE_SAMPLERS]; + _sg_shader_image_sampler_t image_samplers[SG_MAX_SHADERSTAGE_IMAGESAMPLERPAIRS]; +} _sg_shader_stage_t; + +typedef struct { + _sg_shader_stage_t stage[SG_NUM_SHADER_STAGES]; +} _sg_shader_common_t; + +_SOKOL_PRIVATE void _sg_shader_common_init(_sg_shader_common_t* cmn, const sg_shader_desc* desc) { + for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + const sg_shader_stage_desc* stage_desc = (stage_index == SG_SHADERSTAGE_VS) ? &desc->vs : &desc->fs; + _sg_shader_stage_t* stage = &cmn->stage[stage_index]; + SOKOL_ASSERT(stage->num_uniform_blocks == 0); + for (int ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { + const sg_shader_uniform_block_desc* ub_desc = &stage_desc->uniform_blocks[ub_index]; + if (0 == ub_desc->size) { + break; + } + stage->uniform_blocks[ub_index].size = ub_desc->size; + stage->num_uniform_blocks++; + } + SOKOL_ASSERT(stage->num_images == 0); + for (int img_index = 0; img_index < SG_MAX_SHADERSTAGE_IMAGES; img_index++) { + const sg_shader_image_desc* img_desc = &stage_desc->images[img_index]; + if (!img_desc->used) { + break; + } + stage->images[img_index].multisampled = img_desc->multisampled; + stage->images[img_index].image_type = img_desc->image_type; + stage->images[img_index].sample_type = img_desc->sample_type; + stage->num_images++; + } + SOKOL_ASSERT(stage->num_samplers == 0); + for (int smp_index = 0; smp_index < SG_MAX_SHADERSTAGE_SAMPLERS; smp_index++) { + const sg_shader_sampler_desc* smp_desc = &stage_desc->samplers[smp_index]; + if (!smp_desc->used) { + break; + } + stage->samplers[smp_index].sampler_type = smp_desc->sampler_type; + stage->num_samplers++; + } + SOKOL_ASSERT(stage->num_image_samplers == 0); + for (int img_smp_index = 0; img_smp_index < SG_MAX_SHADERSTAGE_IMAGESAMPLERPAIRS; img_smp_index++) { + const sg_shader_image_sampler_pair_desc* img_smp_desc = &stage_desc->image_sampler_pairs[img_smp_index]; + if (!img_smp_desc->used) { + break; + } + SOKOL_ASSERT((img_smp_desc->image_slot >= 0) && (img_smp_desc->image_slot < stage->num_images)); + stage->image_samplers[img_smp_index].image_slot = img_smp_desc->image_slot; + SOKOL_ASSERT((img_smp_desc->sampler_slot >= 0) && (img_smp_desc->sampler_slot < stage->num_samplers)); + stage->image_samplers[img_smp_index].sampler_slot = img_smp_desc->sampler_slot; + stage->num_image_samplers++; + } + } +} + +typedef struct { + bool vertex_buffer_layout_active[SG_MAX_VERTEX_BUFFERS]; + bool use_instanced_draw; + 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)); + for (int i = 0; i < SG_MAX_VERTEX_BUFFERS; i++) { + cmn->vertex_buffer_layout_active[i] = false; + } + 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; + 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_slot_t slot; + _sg_buffer_common_t cmn; +} _sg_dummy_buffer_t; +typedef _sg_dummy_buffer_t _sg_buffer_t; + +typedef struct { + _sg_slot_t slot; + _sg_image_common_t cmn; +} _sg_dummy_image_t; +typedef _sg_dummy_image_t _sg_image_t; + +typedef struct { + _sg_slot_t slot; + _sg_sampler_common_t cmn; +} _sg_dummy_sampler_t; +typedef _sg_dummy_sampler_t _sg_sampler_t; + +typedef struct { + _sg_slot_t slot; + _sg_shader_common_t cmn; +} _sg_dummy_shader_t; +typedef _sg_dummy_shader_t _sg_shader_t; + +typedef struct { + _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_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_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 + } gl; +} _sg_gl_buffer_t; +typedef _sg_gl_buffer_t _sg_buffer_t; + +typedef struct { + _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_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_UB_MEMBERS]; +} _sg_gl_uniform_block_t; + +typedef struct { + int gl_tex_slot; +} _sg_gl_shader_image_sampler_t; + +typedef struct { + _sg_str_t name; +} _sg_gl_shader_attr_t; + +typedef struct { + _sg_gl_uniform_block_t uniform_blocks[SG_MAX_SHADERSTAGE_UBS]; + _sg_gl_shader_image_sampler_t image_samplers[SG_MAX_SHADERSTAGE_IMAGESAMPLERPAIRS]; +} _sg_gl_shader_stage_t; + +typedef struct { + _sg_slot_t slot; + _sg_shader_common_t cmn; + struct { + GLuint prog; + _sg_gl_shader_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; + _sg_gl_shader_stage_t stage[SG_NUM_SHADER_STAGES]; + } 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_gl_attr_t; + +typedef struct { + _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_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_TEXTURE_SAMPLER_CACHE_SIZE (SG_MAX_SHADERSTAGE_IMAGESAMPLERPAIRS * SG_NUM_SHADER_STAGES) + +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 stored_vertex_buffer; + GLuint stored_index_buffer; + GLuint prog; + _sg_gl_cache_texture_sampler_bind_slot texture_samplers[_SG_GL_TEXTURE_SAMPLER_CACHE_SIZE]; + _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_slot_t slot; + _sg_buffer_common_t cmn; + struct { + ID3D11Buffer* buf; + } d3d11; +} _sg_d3d11_buffer_t; +typedef _sg_d3d11_buffer_t _sg_buffer_t; + +typedef struct { + _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_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; + +typedef struct { + ID3D11Buffer* cbufs[SG_MAX_SHADERSTAGE_UBS]; +} _sg_d3d11_shader_stage_t; + +typedef struct { + _sg_slot_t slot; + _sg_shader_common_t cmn; + struct { + _sg_d3d11_shader_attr_t attrs[SG_MAX_VERTEX_ATTRIBUTES]; + _sg_d3d11_shader_stage_t stage[SG_NUM_SHADER_STAGES]; + ID3D11VertexShader* vs; + ID3D11PixelShader* fs; + void* vs_blob; + size_t vs_blob_length; + } d3d11; +} _sg_d3d11_shader_t; +typedef _sg_d3d11_shader_t _sg_shader_t; + +typedef struct { + _sg_slot_t slot; + _sg_pipeline_common_t cmn; + _sg_shader_t* shader; + struct { + UINT stencil_ref; + UINT vb_strides[SG_MAX_VERTEX_BUFFERS]; + 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_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_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_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_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_stage_t; + +typedef struct { + _sg_slot_t slot; + _sg_shader_common_t cmn; + struct { + _sg_mtl_shader_stage_t stage[SG_NUM_SHADER_STAGES]; + } mtl; +} _sg_mtl_shader_t; +typedef _sg_mtl_shader_t _sg_shader_t; + +typedef struct { + _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; + int rps; + int dss; + } 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_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 +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_vertexbuffer_offsets[SG_MAX_VERTEX_BUFFERS]; + sg_buffer cur_vertexbuffer_ids[SG_MAX_VERTEX_BUFFERS]; + sg_image cur_vs_image_ids[SG_MAX_SHADERSTAGE_IMAGES]; + sg_image cur_fs_image_ids[SG_MAX_SHADERSTAGE_IMAGES]; + sg_sampler cur_vs_sampler_ids[SG_MAX_SHADERSTAGE_SAMPLERS]; + sg_sampler cur_fs_sampler_ids[SG_MAX_SHADERSTAGE_SAMPLERS]; +} _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 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 and sampler on both shader stages +#define _SG_WGPU_UNIFORM_BINDGROUP_INDEX (0) +#define _SG_WGPU_IMAGE_SAMPLER_BINDGROUP_INDEX (1) + +typedef struct { + _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_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_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_stage_t; + +typedef struct { + _sg_slot_t slot; + _sg_shader_common_t cmn; + struct { + _sg_wgpu_shader_stage_t stage[SG_NUM_SHADER_STAGES]; + WGPUBindGroupLayout bind_group_layout; + } wgpu; +} _sg_wgpu_shader_t; +typedef _sg_wgpu_shader_t _sg_shader_t; + +typedef struct { + _sg_slot_t slot; + _sg_pipeline_common_t cmn; + _sg_shader_t* shader; + struct { + WGPURenderPipeline pip; + 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_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 + struct { + WGPUBindGroupLayout group_layout; + WGPUBindGroup group; + uint32_t offsets[SG_NUM_SHADER_STAGES][SG_MAX_SHADERSTAGE_UBS]; + } bind; +} _sg_wgpu_uniform_buffer_t; + +typedef struct { + uint32_t id; +} _sg_wgpu_bindgroup_handle_t; + +#define _SG_WGPU_BINDGROUPSCACHE_NUM_ITEMS (1 + SG_NUM_SHADER_STAGES * (SG_MAX_SHADERSTAGE_IMAGES + SG_MAX_SHADERSTAGE_SAMPLERS)) +typedef struct { + uint64_t hash; + uint32_t items[_SG_WGPU_BINDGROUPSCACHE_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; + int offset; + } vbs[SG_MAX_VERTEX_BUFFERS]; + struct { + sg_buffer buffer; + int 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 pass_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_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 num_vbs; + int num_vs_imgs; + int num_vs_smps; + int num_fs_imgs; + int num_fs_smps; + int vb_offsets[SG_MAX_VERTEX_BUFFERS]; + int ib_offset; + _sg_buffer_t* vbs[SG_MAX_VERTEX_BUFFERS]; + _sg_buffer_t* ib; + _sg_image_t* vs_imgs[SG_MAX_SHADERSTAGE_IMAGES]; + _sg_sampler_t* vs_smps[SG_MAX_SHADERSTAGE_SAMPLERS]; + _sg_image_t* fs_imgs[SG_MAX_SHADERSTAGE_IMAGES]; + _sg_sampler_t* fs_smps[SG_MAX_SHADERSTAGE_SAMPLERS]; +} _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; + 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 apply_bindings_called; + bool next_draw_valid; + #if defined(SOKOL_DEBUG) + sg_log_item validate_error; + #endif + _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, 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_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_USHORT2N: return 4; + case SG_VERTEXFORMAT_SHORT4: return 8; + case SG_VERTEXFORMAT_SHORT4N: 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 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_PVRTC_RGB_2BPP: + case SG_PIXELFORMAT_PVRTC_RGB_4BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: + 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_ETC2_RG11: + case SG_PIXELFORMAT_ETC2_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 + + For the special PVRTC pitch computation, see: + GL extension requirement (https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt) + + Quote: + + 6) How is the imageSize argument calculated for the CompressedTexImage2D + and CompressedTexSubImage2D functions. + + Resolution: For PVRTC 4BPP formats the imageSize is calculated as: + ( max(width, 8) * max(height, 8) * 4 + 7) / 8 + For PVRTC 2BPP formats the imageSize is calculated as: + ( max(width, 16) * max(height, 8) * 2 + 7) / 8 +*/ +_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: + 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_ETC2_RG11: + case SG_PIXELFORMAT_ETC2_RG11SN: + case SG_PIXELFORMAT_ASTC_4x4_RGBA: + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: + pitch = ((width + 3) / 4) * 16; + pitch = pitch < 16 ? 16 : pitch; + break; + case SG_PIXELFORMAT_PVRTC_RGB_4BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: + pitch = (_sg_max(width, 8) * 4 + 7) / 8; + break; + case SG_PIXELFORMAT_PVRTC_RGB_2BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: + pitch = (_sg_max(width, 16) * 2 + 7) / 8; + 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_ETC2_RG11: + case SG_PIXELFORMAT_ETC2_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; + case SG_PIXELFORMAT_PVRTC_RGB_4BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: + case SG_PIXELFORMAT_PVRTC_RGB_2BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: + /* NOTE: this is most likely not correct because it ignores any + PVCRTC block size, but multiplied with _sg_row_pitch() + it gives the correct surface pitch. + + See: https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt + */ + num_rows = ((_sg_max(height, 8) + 7) / 8) * 8; + 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_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); + pip->shader = shd; + for (int 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_VERTEX_BUFFERS); + pip->cmn.vertex_buffer_layout_active[a_state->buffer_index] = true; + } + 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(sg_shader_stage stage_index, int ub_index, const sg_range* data) { + _SOKOL_UNUSED(stage_index); + _SOKOL_UNUSED(ub_index); + _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_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(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)) + +// 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; + default: SOKOL_UNREACHABLE; return 0; + } +} + +_SOKOL_PRIVATE GLenum _sg_gl_texture_target(sg_image_type t) { + 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; + } +} + +_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_VS: return GL_VERTEX_SHADER; + case SG_SHADERSTAGE_FS: return GL_FRAGMENT_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_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_USHORT2N: return 2; + case SG_VERTEXFORMAT_SHORT4: return 4; + case SG_VERTEXFORMAT_SHORT4N: 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_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_USHORT2N: + 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; + 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_NONE: return GL_NEAREST; + 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_NONE: return GL_LINEAR; + 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_GLCORE33) + 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_PVRTC_RGB_2BPP: + return GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG; + case SG_PIXELFORMAT_PVRTC_RGB_4BPP: + return GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; + case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: + return GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; + case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: + return GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; + 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_ETC2_RG11: + return GL_COMPRESSED_RG11_EAC; + case SG_PIXELFORMAT_ETC2_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_PVRTC_RGB_2BPP: return GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG; + case SG_PIXELFORMAT_PVRTC_RGB_4BPP: return GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; + case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: return GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; + case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: return GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; + 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_ETC2_RG11: return GL_COMPRESSED_RG11_EAC; + case SG_PIXELFORMAT_ETC2_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_RG11B10F]); + _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]); + } + } 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]); + } + } 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]); + } 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]); + } + } +} + +_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_pvrtc(void) { + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGB_2BPP]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGB_4BPP]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGBA_2BPP]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGBA_4BPP]); +} + +_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_ETC2_RG11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_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_GLCORE33) +_SOKOL_PRIVATE void _sg_gl_init_caps_glcore33(void) { + _sg.backend = SG_BACKEND_GLCORE33; + + _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; + + // scan extensions + bool has_s3tc = false; // BC1..BC3 + bool has_rgtc = false; // BC4 and BC5 + bool has_bptc = false; // BC6H and BC7 + bool has_pvrtc = false; + 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, "_texture_compression_pvrtc")) { + has_pvrtc = 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_pvrtc) { + _sg_gl_init_pixelformats_pvrtc(); + } + 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; + + _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; + + bool has_s3tc = false; // BC1..BC3 + bool has_rgtc = false; // BC4 and BC5 + bool has_bptc = false; // BC6H and BC7 + bool has_pvrtc = false; + #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, "_texture_compression_pvrtc")) { + has_pvrtc = true; + } else if (strstr(ext, "_compressed_texture_pvrtc")) { + has_pvrtc = 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_pvrtc) { + _sg_gl_init_pixelformats_pvrtc(); + } + 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); + } +} + +_SOKOL_PRIVATE void _sg_gl_cache_bind_buffer(GLenum target, GLuint buffer) { + SOKOL_ASSERT((GL_ARRAY_BUFFER == target) || (GL_ELEMENT_ARRAY_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 (_sg.gl.cache.index_buffer != buffer) { + _sg.gl.cache.index_buffer = buffer; + glBindBuffer(target, 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 { + _sg.gl.cache.stored_index_buffer = _sg.gl.cache.index_buffer; + } +} + +_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 (_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; + } + } +} + +// called when _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.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; + } + 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_TEXTURE_SAMPLER_CACHE_SIZE) && (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(int slot_index, 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((slot_index >= 0) && (slot_index < _SG_GL_TEXTURE_SAMPLER_CACHE_SIZE)); + if (slot_index >= _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[slot_index]; + if ((slot->target != target) || (slot->texture != texture) || (slot->sampler != sampler)) { + _sg_gl_cache_active_texture((GLenum)(GL_TEXTURE0 + slot_index)); + // 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)slot_index, 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(int slot_index) { + SOKOL_ASSERT((slot_index >= 0) && (slot_index < _SG_GL_TEXTURE_SAMPLER_CACHE_SIZE)); + _sg.gl.cache.stored_texture_sampler = _sg.gl.cache.texture_samplers[slot_index]; +} + +_SOKOL_PRIVATE void _sg_gl_cache_restore_texture_sampler_binding(int slot_index) { + SOKOL_ASSERT((slot_index >= 0) && (slot_index < _SG_GL_TEXTURE_SAMPLER_CACHE_SIZE)); + _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(slot_index, 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 (int i = 0; i < _SG_GL_TEXTURE_SAMPLER_CACHE_SIZE; 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_GLCORE33) + 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_GLCORE33) + _sg_gl_init_caps_glcore33(); + #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_GLCORE33) + // 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) { + SOKOL_ASSERT(desc->data.ptr); + glBufferSubData(gl_target, 0, buf->cmn.size, desc->data.ptr); + } + _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(); + 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); + + // if this is a MSAA render target, a render buffer object will be created instead of a regulat texture + // (since GLES3 has no multisampled texture objects) + if (img->cmn.render_target && (img->cmn.sample_count > 1)) { + 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); + // 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); + 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); + 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) { + 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); + glTexImage2D(gl_img_target, mip_index, (GLint)gl_internal_format, + mip_width, mip_height, 0, gl_format, gl_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(mip_depth, mip_index); + } + if (is_compressed) { + 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); + glTexImage3D(gl_img_target, mip_index, (GLint)gl_internal_format, + mip_width, mip_height, mip_depth, 0, gl_format, gl_type, data_ptr); + } + } + } + } + _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_GLCORE33) + 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; +} + +_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(); + + // 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].name); + } + + GLuint gl_vs = _sg_gl_compile_shader(SG_SHADERSTAGE_VS, desc->vs.source); + GLuint gl_fs = _sg_gl_compile_shader(SG_SHADERSTAGE_FS, desc->fs.source); + if (!(gl_vs && gl_fs)) { + return SG_RESOURCESTATE_FAILED; + } + GLuint gl_prog = glCreateProgram(); + glAttachShader(gl_prog, gl_vs); + glAttachShader(gl_prog, gl_fs); + glLinkProgram(gl_prog); + glDeleteShader(gl_vs); + glDeleteShader(gl_fs); + _SG_GL_CHECK_ERROR(); + + 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 (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + const sg_shader_stage_desc* stage_desc = (stage_index == SG_SHADERSTAGE_VS)? &desc->vs : &desc->fs; + const _sg_shader_stage_t* stage = &shd->cmn.stage[stage_index]; + _sg_gl_shader_stage_t* gl_stage = &shd->gl.stage[stage_index]; + for (int ub_index = 0; ub_index < stage->num_uniform_blocks; ub_index++) { + const sg_shader_uniform_block_desc* ub_desc = &stage_desc->uniform_blocks[ub_index]; + SOKOL_ASSERT(ub_desc->size > 0); + _sg_gl_uniform_block_t* ub = &gl_stage->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_UB_MEMBERS; u_index++) { + const sg_shader_uniform_desc* u_desc = &ub_desc->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; + cur_uniform_offset += u_size; + if (u_desc->name) { + u->gl_loc = glGetUniformLocation(gl_prog, u_desc->name); + } else { + u->gl_loc = u_index; + } + 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); + } + } + + // resolve combined image samplers + _SG_GL_CHECK_ERROR(); + GLuint cur_prog = 0; + glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&cur_prog); + glUseProgram(gl_prog); + int gl_tex_slot = 0; + for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + const sg_shader_stage_desc* stage_desc = (stage_index == SG_SHADERSTAGE_VS)? &desc->vs : &desc->fs; + const _sg_shader_stage_t* stage = &shd->cmn.stage[stage_index]; + _sg_gl_shader_stage_t* gl_stage = &shd->gl.stage[stage_index]; + for (int img_smp_index = 0; img_smp_index < stage->num_image_samplers; img_smp_index++) { + const sg_shader_image_sampler_pair_desc* img_smp_desc = &stage_desc->image_sampler_pairs[img_smp_index]; + _sg_gl_shader_image_sampler_t* gl_img_smp = &gl_stage->image_samplers[img_smp_index]; + SOKOL_ASSERT(img_smp_desc->glsl_name); + GLint gl_loc = glGetUniformLocation(gl_prog, img_smp_desc->glsl_name); + if (gl_loc != -1) { + gl_img_smp->gl_tex_slot = gl_tex_slot++; + glUniform1i(gl_loc, gl_img_smp->gl_tex_slot); + } else { + gl_img_smp->gl_tex_slot = -1; + _SG_ERROR(GL_TEXTURE_NAME_NOT_FOUND_IN_SHADER); + _SG_LOGMSG(GL_TEXTURE_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); + pip->shader = shd; + 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; + + // 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_VERTEX_BUFFERS); + 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)); + } + SOKOL_ASSERT(attr_loc < (GLint)_sg.limits.max_vertex_attrs); + if (attr_loc != -1) { + _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); + pip->cmn.vertex_buffer_layout_active[a_state->buffer_index] = true; + } else { + _SG_ERROR(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(); + 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_GLCORE33) + glEnable(GL_FRAMEBUFFER_SRGB); + #endif + glBindFramebuffer(GL_FRAMEBUFFER, atts->gl.fb); + } else { + // default pass + #if defined(SOKOL_GLCORE33) + 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; + _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 not support, needs GL4 + 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_GLCORE33 + 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_GLCORE33 + 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 + + // 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); + } + } + _SG_GL_CHECK_ERROR(); +} + +_SOKOL_PRIVATE bool _sg_gl_apply_bindings(_sg_bindings_t* bnd) { + SOKOL_ASSERT(bnd); + SOKOL_ASSERT(bnd->pip); + _SG_GL_CHECK_ERROR(); + + // bind combined image-samplers + _SG_GL_CHECK_ERROR(); + for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + const _sg_shader_stage_t* stage = &bnd->pip->shader->cmn.stage[stage_index]; + const _sg_gl_shader_stage_t* gl_stage = &bnd->pip->shader->gl.stage[stage_index]; + _sg_image_t** imgs = (stage_index == SG_SHADERSTAGE_VS) ? bnd->vs_imgs : bnd->fs_imgs; + _sg_sampler_t** smps = (stage_index == SG_SHADERSTAGE_VS) ? bnd->vs_smps : bnd->fs_smps; + const int num_imgs = (stage_index == SG_SHADERSTAGE_VS) ? bnd->num_vs_imgs : bnd->num_fs_imgs; + const int num_smps = (stage_index == SG_SHADERSTAGE_VS) ? bnd->num_vs_smps : bnd->num_fs_smps; + SOKOL_ASSERT(num_imgs == stage->num_images); _SOKOL_UNUSED(num_imgs); + SOKOL_ASSERT(num_smps == stage->num_samplers); _SOKOL_UNUSED(num_smps); + for (int img_smp_index = 0; img_smp_index < stage->num_image_samplers; img_smp_index++) { + const int gl_tex_slot = gl_stage->image_samplers[img_smp_index].gl_tex_slot; + if (gl_tex_slot != -1) { + const int img_index = stage->image_samplers[img_smp_index].image_slot; + const int smp_index = stage->image_samplers[img_smp_index].sampler_slot; + SOKOL_ASSERT(img_index < num_imgs); + SOKOL_ASSERT(smp_index < num_smps); + _sg_image_t* img = imgs[img_index]; + _sg_sampler_t* smp = smps[smp_index]; + 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(); + + // 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 < bnd->num_vbs); + _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->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); + glVertexAttribPointer(attr_index, attr->size, attr->type, attr->normalized, 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(sg_shader_stage stage_index, int ub_index, const sg_range* data) { + SOKOL_ASSERT(_sg.gl.cache.cur_pipeline); + SOKOL_ASSERT(_sg.gl.cache.cur_pipeline->slot.id == _sg.gl.cache.cur_pipeline_id.id); + SOKOL_ASSERT(_sg.gl.cache.cur_pipeline->shader->slot.id == _sg.gl.cache.cur_pipeline->cmn.shader_id.id); + SOKOL_ASSERT(_sg.gl.cache.cur_pipeline->shader->cmn.stage[stage_index].num_uniform_blocks > ub_index); + SOKOL_ASSERT(_sg.gl.cache.cur_pipeline->shader->cmn.stage[stage_index].uniform_blocks[ub_index].size == data->size); + const _sg_gl_shader_stage_t* gl_stage = &_sg.gl.cache.cur_pipeline->shader->gl.stage[stage_index]; + const _sg_gl_uniform_block_t* gl_ub = &gl_stage->uniform_blocks[ub_index]; + 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; + 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 (_sg.gl.cache.cur_pipeline->cmn.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 (_sg.gl.cache.cur_pipeline->cmn.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_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 + +//-- 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_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_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_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 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 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_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 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 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_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_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_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; + 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.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; + 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) { + // just clear the d3d11 device context state + _sg_d3d11_clear_state(); +} + +_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); + } else { + D3D11_BUFFER_DESC d3d11_desc; + _sg_clear(&d3d11_desc, sizeof(d3d11_desc)); + d3d11_desc.ByteWidth = (UINT)buf->cmn.size; + d3d11_desc.Usage = _sg_d3d11_usage(buf->cmn.usage); + d3d11_desc.BindFlags = buf->cmn.type == SG_BUFFERTYPE_VERTEXBUFFER ? D3D11_BIND_VERTEX_BUFFER : D3D11_BIND_INDEX_BUFFER; + d3d11_desc.CPUAccessFlags = _sg_d3d11_cpu_access_flags(buf->cmn.usage); + 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) { + SOKOL_ASSERT(desc->data.ptr); + init_data.pSysMem = desc->data.ptr; + init_data_ptr = &init_data; + } + HRESULT hr = _sg_d3d11_CreateBuffer(_sg.d3d11.dev, &d3d11_desc, init_data_ptr, &buf->d3d11.buf); + if (!(SUCCEEDED(hr) && buf->d3d11.buf)) { + _SG_ERROR(D3D11_CREATE_BUFFER_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + 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); + } +} + +_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); + 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; + 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; + } + if (!msaa) { + d3d11_tex_desc.BindFlags |= D3D11_BIND_SHADER_RESOURCE; + } + 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); + } + 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; + } + + // create shader-resource-view for 2D texture + // FIXME: currently we don't support setting MSAA texture as shader resource + 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); + switch (img->cmn.type) { + case SG_IMAGETYPE_2D: + d3d11_srv_desc.ViewDimension = 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 = 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; + } + } + } + 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; + } + + // 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; + } + } + } + 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; + } + } + 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_stage_desc* stage_desc) { + if (!_sg_d3d11_load_d3dcompiler_dll()) { + return NULL; + } + SOKOL_ASSERT(stage_desc->d3d11_target); + ID3DBlob* output = NULL; + ID3DBlob* errors_or_warnings = NULL; + HRESULT hr = _sg.d3d11.D3DCompile_func( + stage_desc->source, // pSrcData + strlen(stage_desc->source), // SrcDataSize + NULL, // pSourceName + NULL, // pDefines + NULL, // pInclude + stage_desc->entry ? stage_desc->entry : "main", // pEntryPoint + stage_desc->d3d11_target, // pTarget + D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR | D3DCOMPILE_OPTIMIZATION_LEVEL3, // 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; +} + +_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.vs_blob); + HRESULT hr; + + // copy vertex attribute semantic names and indices + for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + _sg_strcpy(&shd->d3d11.attrs[i].sem_name, desc->attrs[i].sem_name); + shd->d3d11.attrs[i].sem_index = desc->attrs[i].sem_index; + } + + // shader stage uniform blocks and image slots + for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + _sg_shader_stage_t* cmn_stage = &shd->cmn.stage[stage_index]; + _sg_d3d11_shader_stage_t* d3d11_stage = &shd->d3d11.stage[stage_index]; + for (int ub_index = 0; ub_index < cmn_stage->num_uniform_blocks; ub_index++) { + const _sg_shader_uniform_block_t* ub = &cmn_stage->uniform_blocks[ub_index]; + + // create a D3D constant buffer for each uniform block + SOKOL_ASSERT(0 == d3d11_stage->cbufs[ub_index]); + 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, &d3d11_stage->cbufs[ub_index]); + if (!(SUCCEEDED(hr) && d3d11_stage->cbufs[ub_index])) { + _SG_ERROR(D3D11_CREATE_CONSTANT_BUFFER_FAILED); + return SG_RESOURCESTATE_FAILED; + } + } + } + + const void* vs_ptr = 0, *fs_ptr = 0; + SIZE_T vs_length = 0, fs_length = 0; + ID3DBlob* vs_blob = 0, *fs_blob = 0; + if (desc->vs.bytecode.ptr && desc->fs.bytecode.ptr) { + // create from shader byte code + vs_ptr = desc->vs.bytecode.ptr; + fs_ptr = desc->fs.bytecode.ptr; + vs_length = desc->vs.bytecode.size; + fs_length = desc->fs.bytecode.size; + } else { + // compile from shader source code + vs_blob = _sg_d3d11_compile_shader(&desc->vs); + fs_blob = _sg_d3d11_compile_shader(&desc->fs); + if (vs_blob && fs_blob) { + vs_ptr = _sg_d3d11_GetBufferPointer(vs_blob); + vs_length = _sg_d3d11_GetBufferSize(vs_blob); + fs_ptr = _sg_d3d11_GetBufferPointer(fs_blob); + fs_length = _sg_d3d11_GetBufferSize(fs_blob); + } + } + sg_resource_state result = SG_RESOURCESTATE_FAILED; + if (vs_ptr && fs_ptr && (vs_length > 0) && (fs_length > 0)) { + // create the D3D vertex- and pixel-shader objects + hr = _sg_d3d11_CreateVertexShader(_sg.d3d11.dev, vs_ptr, vs_length, NULL, &shd->d3d11.vs); + bool vs_succeeded = SUCCEEDED(hr) && shd->d3d11.vs; + hr = _sg_d3d11_CreatePixelShader(_sg.d3d11.dev, fs_ptr, fs_length, NULL, &shd->d3d11.fs); + bool fs_succeeded = SUCCEEDED(hr) && shd->d3d11.fs; + + // need to store the vertex shader byte code, this is needed later in sg_create_pipeline + if (vs_succeeded && fs_succeeded) { + 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); + result = SG_RESOURCESTATE_VALID; + } + } + if (vs_blob) { + _sg_d3d11_Release(vs_blob); vs_blob = 0; + } + if (fs_blob) { + _sg_d3d11_Release(fs_blob); fs_blob = 0; + } + return result; +} + +_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.vs_blob) { + _sg_free(shd->d3d11.vs_blob); + } + for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + _sg_shader_stage_t* cmn_stage = &shd->cmn.stage[stage_index]; + _sg_d3d11_shader_stage_t* d3d11_stage = &shd->d3d11.stage[stage_index]; + for (int ub_index = 0; ub_index < cmn_stage->num_uniform_blocks; ub_index++) { + if (d3d11_stage->cbufs[ub_index]) { + _sg_d3d11_Release(d3d11_stage->cbufs[ub_index]); + } + } + } +} + +_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); + 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->shader = shd; + 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)); + int 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_VERTEX_BUFFERS); + 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; + } + pip->cmn.vertex_buffer_layout_active[a_state->buffer_index] = true; + } + for (int layout_index = 0; layout_index < SG_MAX_VERTEX_BUFFERS; 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; + } + } + 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; + } + + // 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; + } + + // 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; + } + + // 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; + { + int i = 0; + for (i = 0; i < 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; + } + 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 (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->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 (int i = 0; i < 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; + } + } + 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; + } + } + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_d3d11_discard_attachments(_sg_attachments_t* atts) { + SOKOL_ASSERT(atts); + for (int 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); + + 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 (int 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 (int i = 0; i < num_rtvs; i++) { + if (action->colors[i].load_action == SG_LOADACTION_CLEAR) { + _sg_d3d11_ClearRenderTargetView(_sg.d3d11.ctx, rtvs[i], &action->colors[i].clear_value.r); + _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); + + // 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 (int i = 0; i < _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); + SOKOL_ASSERT(pip->d3d11.rs && pip->d3d11.bs && pip->d3d11.dss && pip->d3d11.il); + + _sg.d3d11.cur_pipeline = pip; + _sg.d3d11.cur_pipeline_id.id = pip->slot.id; + _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, &pip->cmn.blend_color.r, 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_MAX_SHADERSTAGE_UBS, pip->shader->d3d11.stage[SG_SHADERSTAGE_VS].cbufs); + _sg_d3d11_PSSetShader(_sg.d3d11.ctx, pip->shader->d3d11.fs, NULL, 0); + _sg_d3d11_PSSetConstantBuffers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_UBS, pip->shader->d3d11.stage[SG_SHADERSTAGE_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); + SOKOL_ASSERT(_sg.d3d11.ctx); + + // gather all the D3D11 resources into arrays + ID3D11Buffer* d3d11_ib = bnd->ib ? bnd->ib->d3d11.buf : 0; + ID3D11Buffer* d3d11_vbs[SG_MAX_VERTEX_BUFFERS] = {0}; + UINT d3d11_vb_offsets[SG_MAX_VERTEX_BUFFERS] = {0}; + ID3D11ShaderResourceView* d3d11_vs_srvs[SG_MAX_SHADERSTAGE_IMAGES] = {0}; + ID3D11ShaderResourceView* d3d11_fs_srvs[SG_MAX_SHADERSTAGE_IMAGES] = {0}; + ID3D11SamplerState* d3d11_vs_smps[SG_MAX_SHADERSTAGE_SAMPLERS] = {0}; + ID3D11SamplerState* d3d11_fs_smps[SG_MAX_SHADERSTAGE_SAMPLERS] = {0}; + for (int i = 0; i < bnd->num_vbs; i++) { + SOKOL_ASSERT(bnd->vbs[i]->d3d11.buf); + d3d11_vbs[i] = bnd->vbs[i]->d3d11.buf; + d3d11_vb_offsets[i] = (UINT)bnd->vb_offsets[i]; + } + for (int i = 0; i < bnd->num_vs_imgs; i++) { + SOKOL_ASSERT(bnd->vs_imgs[i]->d3d11.srv); + d3d11_vs_srvs[i] = bnd->vs_imgs[i]->d3d11.srv; + } + for (int i = 0; i < bnd->num_fs_imgs; i++) { + SOKOL_ASSERT(bnd->fs_imgs[i]->d3d11.srv); + d3d11_fs_srvs[i] = bnd->fs_imgs[i]->d3d11.srv; + } + for (int i = 0; i < bnd->num_vs_smps; i++) { + SOKOL_ASSERT(bnd->vs_smps[i]->d3d11.smp); + d3d11_vs_smps[i] = bnd->vs_smps[i]->d3d11.smp; + } + for (int i = 0; i < bnd->num_fs_smps; i++) { + SOKOL_ASSERT(bnd->fs_smps[i]->d3d11.smp); + d3d11_fs_smps[i] = bnd->fs_smps[i]->d3d11.smp; + } + _sg_d3d11_IASetVertexBuffers(_sg.d3d11.ctx, 0, SG_MAX_VERTEX_BUFFERS, 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_MAX_SHADERSTAGE_IMAGES, d3d11_vs_srvs); + _sg_d3d11_PSSetShaderResources(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_IMAGES, d3d11_fs_srvs); + _sg_d3d11_VSSetSamplers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_SAMPLERS, d3d11_vs_smps); + _sg_d3d11_PSSetSamplers(_sg.d3d11.ctx, 0, SG_MAX_SHADERSTAGE_SAMPLERS, 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(sg_shader_stage stage_index, int ub_index, const sg_range* data) { + SOKOL_ASSERT(_sg.d3d11.ctx); + SOKOL_ASSERT(_sg.d3d11.cur_pipeline && _sg.d3d11.cur_pipeline->slot.id == _sg.d3d11.cur_pipeline_id.id); + SOKOL_ASSERT(_sg.d3d11.cur_pipeline->shader && _sg.d3d11.cur_pipeline->shader->slot.id == _sg.d3d11.cur_pipeline->cmn.shader_id.id); + SOKOL_ASSERT(ub_index < _sg.d3d11.cur_pipeline->shader->cmn.stage[stage_index].num_uniform_blocks); + SOKOL_ASSERT(data->size == _sg.d3d11.cur_pipeline->shader->cmn.stage[stage_index].uniform_blocks[ub_index].size); + ID3D11Buffer* cb = _sg.d3d11.cur_pipeline->shader->d3d11.stage[stage_index].cbufs[ub_index]; + SOKOL_ASSERT(cb); + _sg_d3d11_UpdateSubresource(_sg.d3d11.ctx, (ID3D11Resource*)cb, 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) { + if (_sg.d3d11.use_indexed_draw) { + if (_sg.d3d11.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 (_sg.d3d11.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_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); + } +} + +_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; + 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_pitch = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1); + 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* 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)) { + // FIXME: need to handle difference in depth-pitch for 3D textures as well! + if (src_pitch == (int)d3d11_msr.RowPitch) { + memcpy(d3d11_msr.pData, slice_ptr, slice_size); + } else { + SOKOL_ASSERT(src_pitch < (int)d3d11_msr.RowPitch); + const uint8_t* src_ptr = slice_ptr; + uint8_t* dst_ptr = (uint8_t*) d3d11_msr.pData; + for (int row_index = 0; row_index < mip_height; row_index++) { + memcpy(dst_ptr, src_ptr, (size_t)src_pitch); + src_ptr += src_pitch; + dst_ptr += d3d11_msr.RowPitch; + } + } + _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_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_USHORT2N: return MTLVertexFormatUShort2Normalized; + case SG_VERTEXFORMAT_SHORT4: return MTLVertexFormatShort4; + case SG_VERTEXFORMAT_SHORT4N: return MTLVertexFormatShort4Normalized; + 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_PVRTC_RGB_2BPP: return MTLPixelFormatPVRTC_RGB_2BPP; + case SG_PIXELFORMAT_PVRTC_RGB_4BPP: return MTLPixelFormatPVRTC_RGB_4BPP; + case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: return MTLPixelFormatPVRTC_RGBA_2BPP; + case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: return MTLPixelFormatPVRTC_RGBA_4BPP; + 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_ETC2_RG11: return MTLPixelFormatEAC_RG11Unorm; + case SG_PIXELFORMAT_ETC2_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; + 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 bool _sg_mtl_is_pvrtc(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_PVRTC_RGB_2BPP: + case SG_PIXELFORMAT_PVRTC_RGB_4BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: + return true; + default: + return false; + } +} + +_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_NONE: + return MTLSamplerMipFilterNotMipmapped; + case SG_FILTER_NEAREST: + return MTLSamplerMipFilterNearest; + case SG_FILTER_LINEAR: + return MTLSamplerMipFilterLinear; + default: + SOKOL_UNREACHABLE; return (MTLSamplerMipFilter)0; + } +} + +//-- 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.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_PVRTC_RGB_2BPP]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGB_4BPP]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGBA_2BPP]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_PVRTC_RGBA_4BPP]); + _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_ETC2_RG11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_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 and MTLRenderCommandEncoder are auto-released + _sg.mtl.cmd_buffer = nil; + _sg.mtl.cmd_encoder = nil; +} + +_SOKOL_PRIVATE void _sg_mtl_bind_uniform_buffers(void) { + SOKOL_ASSERT(nil != _sg.mtl.cmd_encoder); + for (int slot = 0; slot < SG_MAX_SHADERSTAGE_UBS; slot++) { + [_sg.mtl.cmd_encoder + setVertexBuffer:_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] + offset:0 + atIndex:(NSUInteger)slot]; + [_sg.mtl.cmd_encoder + setFragmentBuffer:_sg.mtl.uniform_buffers[_sg.mtl.cur_frame_rotate_index] + offset:0 + atIndex:(NSUInteger)slot]; + } +} + +_SOKOL_PRIVATE void _sg_mtl_reset_state_cache(void) { + _sg_mtl_clear_state_cache(); + // need to restore the uniform buffer binding (normally happens in _sg_mtl_begin_pass() + if (nil != _sg.mtl.cmd_encoder) { + _sg_mtl_bind_uniform_buffers(); + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_mtl_create_buffer(_sg_buffer_t* buf, const sg_buffer_desc* desc) { + SOKOL_ASSERT(buf && desc); + 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 (buf->cmn.usage == SG_USAGE_IMMUTABLE) { + SOKOL_ASSERT(desc->data.ptr); + mtl_buf = [_sg.mtl.device newBufferWithBytes:desc->data.ptr length:(NSUInteger)buf->cmn.size options:mtl_options]; + } else { + 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); + // special case PVRTC formats: bytePerRow and bytesPerImage must be 0 + int bytes_per_row = 0; + int bytes_per_slice = 0; + if (!_sg_mtl_is_pvrtc(img->cmn.pixel_format)) { + bytes_per_row = _sg_row_pitch(img->cmn.pixel_format, mip_width, 1); + 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 = 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 sg_resource_state _sg_mtl_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && desc); + + // create metal library objects and lookup entry functions + id vs_lib = nil; + id fs_lib = nil; + id vs_func = nil; + id fs_func = nil; + const char* vs_entry = desc->vs.entry; + const char* fs_entry = desc->fs.entry; + if (desc->vs.bytecode.ptr && desc->fs.bytecode.ptr) { + // separate byte code provided + vs_lib = _sg_mtl_library_from_bytecode(desc->vs.bytecode.ptr, desc->vs.bytecode.size); + fs_lib = _sg_mtl_library_from_bytecode(desc->fs.bytecode.ptr, desc->fs.bytecode.size); + if ((nil == vs_lib) || (nil == fs_lib)) { + goto failed; + } + vs_func = [vs_lib newFunctionWithName:[NSString stringWithUTF8String:vs_entry]]; + fs_func = [fs_lib newFunctionWithName:[NSString stringWithUTF8String:fs_entry]]; + } else if (desc->vs.source && desc->fs.source) { + // separate sources provided + vs_lib = _sg_mtl_compile_library(desc->vs.source); + fs_lib = _sg_mtl_compile_library(desc->fs.source); + if ((nil == vs_lib) || (nil == fs_lib)) { + goto failed; + } + vs_func = [vs_lib newFunctionWithName:[NSString stringWithUTF8String:vs_entry]]; + fs_func = [fs_lib newFunctionWithName:[NSString stringWithUTF8String:fs_entry]]; + } else { + goto failed; + } + if (nil == vs_func) { + _SG_ERROR(METAL_VERTEX_SHADER_ENTRY_NOT_FOUND); + goto failed; + } + if (nil == fs_func) { + _SG_ERROR(METAL_FRAGMENT_SHADER_ENTRY_NOT_FOUND); + goto failed; + } + #if defined(SOKOL_DEBUG) + if (desc->label) { + vs_lib.label = [NSString stringWithFormat:@"%s.vs", desc->label]; + fs_lib.label = [NSString stringWithFormat:@"%s.fs", desc->label]; + } + #endif + // it is legal to call _sg_mtl_add_resource with a nil value, this will return a special 0xFFFFFFFF index + shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_lib = _sg_mtl_add_resource(vs_lib); + _SG_OBJC_RELEASE(vs_lib); + shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_lib = _sg_mtl_add_resource(fs_lib); + _SG_OBJC_RELEASE(fs_lib); + shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func = _sg_mtl_add_resource(vs_func); + _SG_OBJC_RELEASE(vs_func); + shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func = _sg_mtl_add_resource(fs_func); + _SG_OBJC_RELEASE(fs_func); + return SG_RESOURCESTATE_VALID; +failed: + if (vs_lib != nil) { + _SG_OBJC_RELEASE(vs_lib); + } + if (fs_lib != nil) { + _SG_OBJC_RELEASE(fs_lib); + } + if (vs_func != nil) { + _SG_OBJC_RELEASE(vs_func); + } + if (fs_func != nil) { + _SG_OBJC_RELEASE(fs_func); + } + return SG_RESOURCESTATE_FAILED; +} + +_SOKOL_PRIVATE void _sg_mtl_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + // it is valid to call _sg_mtl_release_resource with a 'null resource' + _sg_mtl_release_resource(_sg.frame_index, shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func); + _sg_mtl_release_resource(_sg.frame_index, shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_lib); + _sg_mtl_release_resource(_sg.frame_index, shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func); + _sg_mtl_release_resource(_sg.frame_index, shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_lib); +} + +_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; + + 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_VERTEX_BUFFERS); + 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 = (NSUInteger)(a_state->buffer_index + SG_MAX_SHADERSTAGE_UBS); + pip->cmn.vertex_buffer_layout_active[a_state->buffer_index] = true; + } + for (NSUInteger layout_index = 0; layout_index < SG_MAX_VERTEX_BUFFERS; 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 = layout_index + SG_MAX_SHADERSTAGE_UBS; + 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.stage[SG_SHADERSTAGE_VS].mtl_func != _SG_MTL_INVALID_SLOT_INDEX); + rp_desc.vertexFunction = _sg_mtl_id(shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func); + SOKOL_ASSERT(shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func != _SG_MTL_INVALID_SLOT_INDEX); + rp_desc.fragmentFunction = _sg_mtl_id(shd->mtl.stage[SG_SHADERSTAGE_FS].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); + } + if (@available(macOS 10.13, iOS 11.0, *)) { + for (NSUInteger i = 0; i < (SG_MAX_SHADERSTAGE_UBS+SG_MAX_VERTEX_BUFFERS); i++) { + rp_desc.vertexBuffers[i].mutability = MTLMutabilityImmutable; + } + for (NSUInteger i = 0; i < SG_MAX_SHADERSTAGE_UBS; i++) { + rp_desc.fragmentBuffers[i].mutability = MTLMutabilityImmutable; + } + } + 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); + } + #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.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_begin_pass(const sg_pass* pass) { + SOKOL_ASSERT(pass); + SOKOL_ASSERT(_sg.mtl.cmd_queue); + SOKOL_ASSERT(nil == _sg.mtl.cmd_encoder); + SOKOL_ASSERT(nil == _sg.mtl.cur_drawable); + _sg_mtl_clear_state_cache(); + + const _sg_attachments_t* atts = _sg.cur_pass.atts; + const sg_swapchain* swapchain = &pass->swapchain; + const sg_pass_action* action = &pass->action; + + /* + if this is the first pass in the frame, create command buffers + + NOTE: we're creating two command buffers here, one with unretained references + for storing the regular commands, and one with retained references for + storing the presentDrawable call (this needs to hold on the drawable until + presentation has happened - and the easiest way to do this is to let the + command buffer manage the lifetime of the drawable). + + Also see: https://github.com/floooh/sokol/issues/762 + */ + 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]; + } + + 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.cmd_encoder = [_sg.mtl.cmd_buffer renderCommandEncoderWithDescriptor:pass_desc]; + if (nil == _sg.mtl.cmd_encoder) { + _sg.cur_pass.valid = false; + return; + } + + #if defined(SOKOL_DEBUG) + if (pass->label) { + _sg.mtl.cmd_encoder.label = [NSString stringWithUTF8String:pass->label]; + } + #endif + + // bind the global uniform buffer, this only happens once per pass + _sg_mtl_bind_uniform_buffers(); +} + +_SOKOL_PRIVATE void _sg_mtl_end_pass(void) { + if (nil != _sg.mtl.cmd_encoder) { + [_sg.mtl.cmd_encoder endEncoding]; + // NOTE: MTLRenderCommandEncoder is autoreleased + _sg.mtl.cmd_encoder = nil; + } + // 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.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.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.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.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.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)); + SOKOL_ASSERT(nil != _sg.mtl.cmd_encoder); + + 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; + sg_color c = pip->cmn.blend_color; + [_sg.mtl.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.cmd_encoder setCullMode:pip->mtl.cull_mode]; + _sg_stats_add(metal.pipeline.num_set_cull_mode, 1); + [_sg.mtl.cmd_encoder setFrontFacingWinding:pip->mtl.winding]; + _sg_stats_add(metal.pipeline.num_set_front_facing_winding, 1); + [_sg.mtl.cmd_encoder setStencilReferenceValue:pip->mtl.stencil_ref]; + _sg_stats_add(metal.pipeline.num_set_stencil_reference_value, 1); + [_sg.mtl.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.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.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(nil != _sg.mtl.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 (NSUInteger slot = 0; slot < (NSUInteger)bnd->num_vbs; slot++) { + const _sg_buffer_t* vb = bnd->vbs[slot]; + const int vb_offset = bnd->vb_offsets[slot]; + if ((_sg.mtl.state_cache.cur_vertexbuffer_ids[slot].id != vb->slot.id) || + (_sg.mtl.state_cache.cur_vertexbuffer_offsets[slot] != vb_offset)) + { + _sg.mtl.state_cache.cur_vertexbuffer_offsets[slot] = vb_offset; + const NSUInteger mtl_slot = SG_MAX_SHADERSTAGE_UBS + slot; + if (_sg.mtl.state_cache.cur_vertexbuffer_ids[slot].id != vb->slot.id) { + _sg.mtl.state_cache.cur_vertexbuffer_ids[slot].id = vb->slot.id; + SOKOL_ASSERT(vb->mtl.buf[vb->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + [_sg.mtl.cmd_encoder setVertexBuffer:_sg_mtl_id(vb->mtl.buf[vb->cmn.active_slot]) + offset:(NSUInteger)vb_offset + atIndex:mtl_slot]; + } else { + [_sg.mtl.cmd_encoder setVertexBufferOffset:(NSUInteger)vb_offset atIndex:mtl_slot]; + } + _sg_stats_add(metal.bindings.num_set_vertex_buffer, 1); + } + } + + // apply vertex shader images + for (NSUInteger slot = 0; slot < (NSUInteger)bnd->num_vs_imgs; slot++) { + const _sg_image_t* img = bnd->vs_imgs[slot]; + if (_sg.mtl.state_cache.cur_vs_image_ids[slot].id != img->slot.id) { + _sg.mtl.state_cache.cur_vs_image_ids[slot].id = img->slot.id; + SOKOL_ASSERT(img->mtl.tex[img->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + [_sg.mtl.cmd_encoder setVertexTexture:_sg_mtl_id(img->mtl.tex[img->cmn.active_slot]) atIndex:slot]; + _sg_stats_add(metal.bindings.num_set_vertex_texture, 1); + } + } + + // apply vertex shader samplers + for (NSUInteger slot = 0; slot < (NSUInteger)bnd->num_vs_smps; slot++) { + const _sg_sampler_t* smp = bnd->vs_smps[slot]; + if (_sg.mtl.state_cache.cur_vs_sampler_ids[slot].id != smp->slot.id) { + _sg.mtl.state_cache.cur_vs_sampler_ids[slot].id = smp->slot.id; + SOKOL_ASSERT(smp->mtl.sampler_state != _SG_MTL_INVALID_SLOT_INDEX); + [_sg.mtl.cmd_encoder setVertexSamplerState:_sg_mtl_id(smp->mtl.sampler_state) atIndex:slot]; + _sg_stats_add(metal.bindings.num_set_vertex_sampler_state, 1); + } + } + + // apply fragment shader images + for (NSUInteger slot = 0; slot < (NSUInteger)bnd->num_fs_imgs; slot++) { + const _sg_image_t* img = bnd->fs_imgs[slot]; + if (_sg.mtl.state_cache.cur_fs_image_ids[slot].id != img->slot.id) { + _sg.mtl.state_cache.cur_fs_image_ids[slot].id = img->slot.id; + SOKOL_ASSERT(img->mtl.tex[img->cmn.active_slot] != _SG_MTL_INVALID_SLOT_INDEX); + [_sg.mtl.cmd_encoder setFragmentTexture:_sg_mtl_id(img->mtl.tex[img->cmn.active_slot]) atIndex:slot]; + _sg_stats_add(metal.bindings.num_set_fragment_texture, 1); + } + } + + // apply fragment shader samplers + for (NSUInteger slot = 0; slot < (NSUInteger)bnd->num_fs_smps; slot++) { + const _sg_sampler_t* smp = bnd->fs_smps[slot]; + if (_sg.mtl.state_cache.cur_fs_sampler_ids[slot].id != smp->slot.id) { + _sg.mtl.state_cache.cur_fs_sampler_ids[slot].id = smp->slot.id; + SOKOL_ASSERT(smp->mtl.sampler_state != _SG_MTL_INVALID_SLOT_INDEX); + [_sg.mtl.cmd_encoder setFragmentSamplerState:_sg_mtl_id(smp->mtl.sampler_state) atIndex:slot]; + _sg_stats_add(metal.bindings.num_set_fragment_sampler_state, 1); + } + } + return true; +} + +_SOKOL_PRIVATE void _sg_mtl_apply_uniforms(sg_shader_stage stage_index, int ub_index, const sg_range* data) { + SOKOL_ASSERT(nil != _sg.mtl.cmd_encoder); + 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); + SOKOL_ASSERT(_sg.mtl.state_cache.cur_pipeline && _sg.mtl.state_cache.cur_pipeline->shader); + SOKOL_ASSERT(_sg.mtl.state_cache.cur_pipeline->slot.id == _sg.mtl.state_cache.cur_pipeline_id.id); + SOKOL_ASSERT(_sg.mtl.state_cache.cur_pipeline->shader->slot.id == _sg.mtl.state_cache.cur_pipeline->cmn.shader_id.id); + SOKOL_ASSERT(ub_index < _sg.mtl.state_cache.cur_pipeline->shader->cmn.stage[stage_index].num_uniform_blocks); + SOKOL_ASSERT(data->size == _sg.mtl.state_cache.cur_pipeline->shader->cmn.stage[stage_index].uniform_blocks[ub_index].size); + + // 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_index == SG_SHADERSTAGE_VS) { + [_sg.mtl.cmd_encoder setVertexBufferOffset:(NSUInteger)_sg.mtl.cur_ub_offset atIndex:(NSUInteger)ub_index]; + _sg_stats_add(metal.uniforms.num_set_vertex_buffer_offset, 1); + } else { + [_sg.mtl.cmd_encoder setFragmentBufferOffset:(NSUInteger)_sg.mtl.cur_ub_offset atIndex:(NSUInteger)ub_index]; + _sg_stats_add(metal.uniforms.num_set_fragment_buffer_offset, 1); + } + _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.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.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.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_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.cmd_encoder) { + [_sg.mtl.cmd_encoder pushDebugGroup:[NSString stringWithUTF8String:name]]; + } +} + +_SOKOL_PRIVATE void _sg_mtl_pop_debug_group(void) { + if (_sg.mtl.cmd_encoder) { + [_sg.mtl.cmd_encoder popDebugGroup]; + } +} + +// ██ ██ ███████ ██████ ██████ ██████ ██ ██ ██████ █████ ██████ ██ ██ ███████ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ █ ██ █████ ██████ ██ ███ ██████ ██ ██ ██████ ███████ ██ █████ █████ ██ ██ ██ ██ ██ +// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███ ███ ███████ ██████ ██████ ██ ██████ ██████ ██ ██ ██████ ██ ██ ███████ ██ ████ ██████ +// +// >>webgpu +// >>wgpu +#elif defined(SOKOL_WGPU) + +_SOKOL_PRIVATE WGPUBufferUsageFlags _sg_wgpu_buffer_usage(sg_buffer_type t, sg_usage u) { + WGPUBufferUsageFlags res = 0; + if (SG_BUFFERTYPE_VERTEXBUFFER == t) { + res |= WGPUBufferUsage_Vertex; + } else { + res |= WGPUBufferUsage_Index; + } + if (SG_USAGE_IMMUTABLE != u) { + res |= WGPUBufferUsage_CopyDst; + } + return 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) { + switch (t) { + case SG_IMAGESAMPLETYPE_FLOAT: return 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_NONE: + 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_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_USHORT2N: return WGPUVertexFormat_Unorm16x2; + case SG_VERTEXFORMAT_SHORT4: return WGPUVertexFormat_Sint16x4; + case SG_VERTEXFORMAT_SHORT4N: return WGPUVertexFormat_Snorm16x4; + case SG_VERTEXFORMAT_USHORT4N: return WGPUVertexFormat_Unorm16x4; + case SG_VERTEXFORMAT_HALF2: return WGPUVertexFormat_Float16x2; + case SG_VERTEXFORMAT_HALF4: return WGPUVertexFormat_Float16x4; + // FIXME! UINT10_N2 (see https://github.com/gpuweb/gpuweb/issues/4275) + case SG_VERTEXFORMAT_UINT10_N2: return WGPUVertexFormat_Undefined; + 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_ETC2_RG11: return WGPUTextureFormat_EACR11Unorm; + case SG_PIXELFORMAT_ETC2_RG11SN: return WGPUTextureFormat_EACR11Snorm; + 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: + case SG_PIXELFORMAT_PVRTC_RGB_2BPP: + case SG_PIXELFORMAT_PVRTC_RGB_4BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: + case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: + 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; + 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 WGPUColorWriteMaskFlags _sg_wgpu_colorwritemask(uint8_t m) { + WGPUColorWriteMaskFlags 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 res; +} + +// image/sampler binding on wgpu follows this convention: +// +// - all images and sampler are in @group(1) +// - vertex stage images start at @binding(0) +// - vertex stage samplers start at @binding(16) +// - fragment stage images start at @binding(32) +// - fragment stage samplers start at @binding(48) +// +_SOKOL_PRIVATE uint32_t _sg_wgpu_image_binding(sg_shader_stage stage, int img_slot) { + SOKOL_ASSERT((img_slot >= 0) && (img_slot < 16)); + if (SG_SHADERSTAGE_VS == stage) { + return 0 + (uint32_t)img_slot; + } else { + return 32 + (uint32_t)img_slot; + } +} + +_SOKOL_PRIVATE uint32_t _sg_wgpu_sampler_binding(sg_shader_stage stage, int smp_slot) { + SOKOL_ASSERT((smp_slot >= 0) && (smp_slot < 16)); + if (SG_SHADERSTAGE_VS == stage) { + return 16 + (uint32_t)smp_slot; + } else { + return 48 + (uint32_t)smp_slot; + } +} + +_SOKOL_PRIVATE WGPUShaderStage _sg_wgpu_shader_stage(sg_shader_stage stage) { + switch (stage) { + case SG_SHADERSTAGE_VS: return WGPUShaderStage_Vertex; + case SG_SHADERSTAGE_FS: return WGPUShaderStage_Fragment; + 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; + + 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]); + + // FIXME: can be made filterable with extension + _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_ETC2_RG11]); + _sg_pixelformat_sf(&_sg.formats[SG_PIXELFORMAT_ETC2_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); + SOKOL_ASSERT(0 == _sg.wgpu.uniform.bind.group_layout); + SOKOL_ASSERT(0 == _sg.wgpu.uniform.bind.group); + + // 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); + + WGPUBindGroupLayoutEntry ub_bgle_desc[SG_NUM_SHADER_STAGES][SG_MAX_SHADERSTAGE_UBS]; + _sg_clear(ub_bgle_desc, sizeof(ub_bgle_desc)); + for (uint32_t stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + WGPUShaderStage vis = (stage_index == SG_SHADERSTAGE_VS) ? WGPUShaderStage_Vertex : WGPUShaderStage_Fragment; + for (uint32_t ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { + uint32_t bind_index = stage_index * SG_MAX_SHADERSTAGE_UBS + ub_index; + ub_bgle_desc[stage_index][ub_index].binding = bind_index; + ub_bgle_desc[stage_index][ub_index].visibility = vis; + ub_bgle_desc[stage_index][ub_index].buffer.type = WGPUBufferBindingType_Uniform; + ub_bgle_desc[stage_index][ub_index].buffer.hasDynamicOffset = true; + } + } + + WGPUBindGroupLayoutDescriptor ub_bgl_desc; + _sg_clear(&ub_bgl_desc, sizeof(ub_bgl_desc)); + ub_bgl_desc.entryCount = SG_NUM_SHADER_STAGES * SG_MAX_SHADERSTAGE_UBS; + ub_bgl_desc.entries = &ub_bgle_desc[0][0]; + _sg.wgpu.uniform.bind.group_layout = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &ub_bgl_desc); + SOKOL_ASSERT(_sg.wgpu.uniform.bind.group_layout); + + WGPUBindGroupEntry ub_bge[SG_NUM_SHADER_STAGES][SG_MAX_SHADERSTAGE_UBS]; + _sg_clear(ub_bge, sizeof(ub_bge)); + for (uint32_t stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + for (uint32_t ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { + uint32_t bind_index = stage_index * SG_MAX_SHADERSTAGE_UBS + ub_index; + ub_bge[stage_index][ub_index].binding = bind_index; + ub_bge[stage_index][ub_index].buffer = _sg.wgpu.uniform.buf; + ub_bge[stage_index][ub_index].size = _SG_WGPU_MAX_UNIFORM_UPDATE_SIZE; + } + } + WGPUBindGroupDescriptor bg_desc; + _sg_clear(&bg_desc, sizeof(bg_desc)); + bg_desc.layout = _sg.wgpu.uniform.bind.group_layout; + bg_desc.entryCount = SG_NUM_SHADER_STAGES * SG_MAX_SHADERSTAGE_UBS; + bg_desc.entries = &ub_bge[0][0]; + _sg.wgpu.uniform.bind.group = wgpuDeviceCreateBindGroup(_sg.wgpu.dev, &bg_desc); + SOKOL_ASSERT(_sg.wgpu.uniform.bind.group); +} + +_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.bind.group) { + wgpuBindGroupRelease(_sg.wgpu.uniform.bind.group); + _sg.wgpu.uniform.bind.group = 0; + } + if (_sg.wgpu.uniform.bind.group_layout) { + wgpuBindGroupLayoutRelease(_sg.wgpu.uniform.bind.group_layout); + _sg.wgpu.uniform.bind.group_layout = 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_begin_pass(void) { + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.pass_enc, + 0, // groupIndex 0 is reserved for uniform buffers + _sg.wgpu.uniform.bind.group, + SG_NUM_SHADER_STAGES * SG_MAX_SHADERSTAGE_UBS, + &_sg.wgpu.uniform.bind.offsets[0][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[0][0], 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_init_pool(&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_discard_pool(&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_reset_slot(&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 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); + SOKOL_ASSERT(bnd->num_vs_imgs <= SG_MAX_SHADERSTAGE_IMAGES); + SOKOL_ASSERT(bnd->num_vs_smps <= SG_MAX_SHADERSTAGE_SAMPLERS); + SOKOL_ASSERT(bnd->num_fs_imgs <= SG_MAX_SHADERSTAGE_IMAGES); + SOKOL_ASSERT(bnd->num_fs_smps <= SG_MAX_SHADERSTAGE_SAMPLERS); + + _sg_clear(key->items, sizeof(key->items)); + key->items[0] = bnd->pip->slot.id; + const int vs_imgs_offset = 1; + const int vs_smps_offset = vs_imgs_offset + SG_MAX_SHADERSTAGE_IMAGES; + const int fs_imgs_offset = vs_smps_offset + SG_MAX_SHADERSTAGE_SAMPLERS; + const int fs_smps_offset = fs_imgs_offset + SG_MAX_SHADERSTAGE_IMAGES; + SOKOL_ASSERT((fs_smps_offset + SG_MAX_SHADERSTAGE_SAMPLERS) == _SG_WGPU_BINDGROUPSCACHE_NUM_ITEMS); + for (int i = 0; i < bnd->num_vs_imgs; i++) { + SOKOL_ASSERT(bnd->vs_imgs[i]); + key->items[vs_imgs_offset + i] = bnd->vs_imgs[i]->slot.id; + } + for (int i = 0; i < bnd->num_vs_smps; i++) { + SOKOL_ASSERT(bnd->vs_smps[i]); + key->items[vs_smps_offset + i] = bnd->vs_smps[i]->slot.id; + } + for (int i = 0; i < bnd->num_fs_imgs; i++) { + SOKOL_ASSERT(bnd->fs_imgs[i]); + key->items[fs_imgs_offset + i] = bnd->fs_imgs[i]->slot.id; + } + for (int i = 0; i < bnd->num_fs_smps; i++) { + SOKOL_ASSERT(bnd->fs_smps[i]); + key->items[fs_smps_offset + i] = bnd->fs_smps[i]->slot.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); + SOKOL_ASSERT(bnd->pip->shader && (bnd->pip->cmn.shader_id.id == bnd->pip->shader->slot.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 + WGPUBindGroupLayout bgl = bnd->pip->shader->wgpu.bind_group_layout; + SOKOL_ASSERT(bgl); + WGPUBindGroupEntry wgpu_entries[SG_NUM_SHADER_STAGES * SG_MAX_SHADERSTAGE_IMAGES + SG_MAX_SHADERSTAGE_SAMPLERS]; + _sg_clear(&wgpu_entries, sizeof(wgpu_entries)); + int bge_index = 0; + for (int i = 0; i < bnd->num_vs_imgs; i++) { + WGPUBindGroupEntry* wgpu_entry = &wgpu_entries[bge_index++]; + wgpu_entry->binding = _sg_wgpu_image_binding(SG_SHADERSTAGE_VS, i); + wgpu_entry->textureView = bnd->vs_imgs[i]->wgpu.view; + } + for (int i = 0; i < bnd->num_vs_smps; i++) { + WGPUBindGroupEntry* wgpu_entry = &wgpu_entries[bge_index++]; + wgpu_entry->binding = _sg_wgpu_sampler_binding(SG_SHADERSTAGE_VS, i); + wgpu_entry->sampler = bnd->vs_smps[i]->wgpu.smp; + } + for (int i = 0; i < bnd->num_fs_imgs; i++) { + WGPUBindGroupEntry* wgpu_entry = &wgpu_entries[bge_index++]; + wgpu_entry->binding = _sg_wgpu_image_binding(SG_SHADERSTAGE_FS, i); + wgpu_entry->textureView = bnd->fs_imgs[i]->wgpu.view; + } + for (int i = 0; i < bnd->num_fs_smps; i++) { + WGPUBindGroupEntry* wgpu_entry = &wgpu_entries[bge_index++]; + wgpu_entry->binding = _sg_wgpu_sampler_binding(SG_SHADERSTAGE_FS, i); + wgpu_entry->sampler = bnd->fs_smps[i]->wgpu.smp; + } + WGPUBindGroupDescriptor bg_desc; + _sg_clear(&bg_desc, sizeof(bg_desc)); + bg_desc.layout = bgl; + bg_desc.entryCount = (size_t)bge_index; + bg_desc.entries = &wgpu_entries[0]; + 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; +} + +_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(int index, const _sg_buffer_t* vb, int offset) { + SOKOL_ASSERT((index >= 0) && (index < SG_MAX_VERTEX_BUFFERS)); + 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(int index, const _sg_buffer_t* vb, int offset) { + SOKOL_ASSERT((index >= 0) && (index < SG_MAX_VERTEX_BUFFERS)); + 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, int 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, int 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_image_sampler_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 (bg) { + SOKOL_ASSERT(bg->slot.state == SG_RESOURCESTATE_VALID); + SOKOL_ASSERT(bg->bindgroup); + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.pass_enc, _SG_WGPU_IMAGE_SAMPLER_BINDGROUP_INDEX, bg->bindgroup, 0, 0); + } else { + // a nullptr bindgroup means setting the empty bindgroup + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.pass_enc, _SG_WGPU_IMAGE_SAMPLER_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 ((bnd->num_vs_imgs + bnd->num_vs_smps + bnd->num_fs_imgs + bnd->num_fs_smps) > 0) { + 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_image_sampler_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_image_sampler_bindgroup(bg); + } + _sg_wgpu_discard_bindgroup(bg); + } else { + return false; + } + } + } else { + _sg_wgpu_set_image_sampler_bindgroup(0); + } + return true; +} +_SOKOL_PRIVATE bool _sg_wgpu_apply_index_buffer(_sg_bindings_t* bnd) { + if (_sg_wgpu_bindings_cache_ib_dirty(bnd->ib, bnd->ib_offset)) { + _sg_wgpu_bindings_cache_ib_update(bnd->ib, bnd->ib_offset); + if (bnd->ib) { + const WGPUIndexFormat format = _sg_wgpu_indexformat(bnd->pip->cmn.index_type); + const uint64_t buf_size = (uint64_t)bnd->ib->cmn.size; + const uint64_t offset = (uint64_t)bnd->ib_offset; + SOKOL_ASSERT(buf_size > offset); + const uint64_t max_bytes = buf_size - offset; + wgpuRenderPassEncoderSetIndexBuffer(_sg.wgpu.pass_enc, bnd->ib->wgpu.buf, format, offset, max_bytes); + _sg_stats_add(wgpu.bindings.num_set_index_buffer, 1); + } + // FIXME: else-path should actually set a null index buffer (this was just recently implemented in WebGPU) + } 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) { + for (int slot = 0; slot < bnd->num_vbs; slot++) { + if (_sg_wgpu_bindings_cache_vb_dirty(slot, bnd->vbs[slot], bnd->vb_offsets[slot])) { + _sg_wgpu_bindings_cache_vb_update(slot, bnd->vbs[slot], bnd->vb_offsets[slot]); + const uint64_t buf_size = (uint64_t)bnd->vbs[slot]->cmn.size; + const uint64_t offset = (uint64_t)bnd->vb_offsets[slot]; + SOKOL_ASSERT(buf_size > offset); + const uint64_t max_bytes = buf_size - offset; + wgpuRenderPassEncoderSetVertexBuffer(_sg.wgpu.pass_enc, (uint32_t)slot, bnd->vbs[slot]->wgpu.buf, offset, max_bytes); + _sg_stats_add(wgpu.bindings.num_set_vertex_buffer, 1); + } else { + _sg_stats_add(wgpu.bindings.num_skip_redundant_vertex_buffer, 1); + } + } + // FIXME: remaining vb slots should actually set a null vertex buffer (this was just recently implemented in WebGPU) + 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 for shader stages without bound images + // FIXME: once WebGPU supports setting null objects, this can be removed + 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); + 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); + + 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 = 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; + } + 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->wgpu.buf) { + wgpuBufferDestroy(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 = 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 = 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); + if (img->wgpu.view) { + wgpuTextureViewRelease(img->wgpu.view); + img->wgpu.view = 0; + } + if (img->wgpu.tex) { + wgpuTextureDestroy(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 = 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); + if (smp->wgpu.smp) { + wgpuSamplerRelease(smp->wgpu.smp); + smp->wgpu.smp = 0; + } +} + +_SOKOL_PRIVATE sg_resource_state _sg_wgpu_create_shader(_sg_shader_t* shd, const sg_shader_desc* desc) { + SOKOL_ASSERT(shd && desc); + SOKOL_ASSERT(desc->vs.source && desc->fs.source); + + #define _sg_wgpu_create_shader_max_bgl_entries (SG_NUM_SHADER_STAGES * (SG_MAX_SHADERSTAGE_IMAGES + SG_MAX_SHADERSTAGE_SAMPLERS)) + WGPUBindGroupLayoutEntry wgpu_bgl_entries[_sg_wgpu_create_shader_max_bgl_entries]; + _sg_clear(wgpu_bgl_entries, sizeof(wgpu_bgl_entries)); + int bgl_index = 0; + for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + const sg_shader_stage_desc* stage_desc = (stage_index == SG_SHADERSTAGE_VS) ? &desc->vs : &desc->fs; + + _sg_shader_stage_t* cmn_stage = &shd->cmn.stage[stage_index]; + _sg_wgpu_shader_stage_t* wgpu_stage = &shd->wgpu.stage[stage_index]; + _sg_strcpy(&wgpu_stage->entry, stage_desc->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 = stage_desc->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 = desc->label; + + wgpu_stage->module = wgpuDeviceCreateShaderModule(_sg.wgpu.dev, &wgpu_shdmod_desc); + if (0 == wgpu_stage->module) { + _SG_ERROR(WGPU_CREATE_SHADER_MODULE_FAILED); + return SG_RESOURCESTATE_FAILED; + } + + const int num_images = cmn_stage->num_images; + if (num_images > (int)_sg.wgpu.limits.limits.maxSampledTexturesPerShaderStage) { + _SG_ERROR(WGPU_SHADER_TOO_MANY_IMAGES); + return SG_RESOURCESTATE_FAILED; + } + const int num_samplers = cmn_stage->num_samplers; + if (num_samplers > (int)_sg.wgpu.limits.limits.maxSamplersPerShaderStage) { + _SG_ERROR(WGPU_SHADER_TOO_MANY_SAMPLERS); + return SG_RESOURCESTATE_FAILED; + } + for (int img_index = 0; img_index < num_images; img_index++) { + SOKOL_ASSERT(bgl_index < _sg_wgpu_create_shader_max_bgl_entries); + WGPUBindGroupLayoutEntry* wgpu_bgl_entry = &wgpu_bgl_entries[bgl_index++]; + const sg_shader_image_desc* img_desc = &stage_desc->images[img_index]; + wgpu_bgl_entry->binding = _sg_wgpu_image_binding((sg_shader_stage)stage_index, img_index); + wgpu_bgl_entry->visibility = _sg_wgpu_shader_stage((sg_shader_stage)stage_index); + wgpu_bgl_entry->texture.viewDimension = _sg_wgpu_texture_view_dimension(img_desc->image_type); + wgpu_bgl_entry->texture.sampleType = _sg_wgpu_texture_sample_type(img_desc->sample_type); + wgpu_bgl_entry->texture.multisampled = img_desc->multisampled; + } + for (int smp_index = 0; smp_index < num_samplers; smp_index++) { + SOKOL_ASSERT(bgl_index < _sg_wgpu_create_shader_max_bgl_entries); + WGPUBindGroupLayoutEntry* wgpu_bgl_entry = &wgpu_bgl_entries[bgl_index++]; + const sg_shader_sampler_desc* smp_desc = &stage_desc->samplers[smp_index]; + wgpu_bgl_entry->binding =_sg_wgpu_sampler_binding((sg_shader_stage)stage_index, smp_index); + wgpu_bgl_entry->visibility = _sg_wgpu_shader_stage((sg_shader_stage)stage_index); + wgpu_bgl_entry->sampler.type = _sg_wgpu_sampler_binding_type(smp_desc->sampler_type); + } + } + + WGPUBindGroupLayoutDescriptor wgpu_bgl_desc; + _sg_clear(&wgpu_bgl_desc, sizeof(wgpu_bgl_desc)); + wgpu_bgl_desc.entryCount = (size_t)bgl_index; + wgpu_bgl_desc.entries = &wgpu_bgl_entries[0]; + shd->wgpu.bind_group_layout = wgpuDeviceCreateBindGroupLayout(_sg.wgpu.dev, &wgpu_bgl_desc); + if (shd->wgpu.bind_group_layout == 0) { + _SG_ERROR(WGPU_SHADER_CREATE_BINDGROUP_LAYOUT_FAILED); + return SG_RESOURCESTATE_FAILED; + } + + #undef _sg_wgpu_create_shader_max_bgl_entries + return SG_RESOURCESTATE_VALID; +} + +_SOKOL_PRIVATE void _sg_wgpu_discard_shader(_sg_shader_t* shd) { + SOKOL_ASSERT(shd); + if (shd->wgpu.bind_group_layout) { + wgpuBindGroupLayoutRelease(shd->wgpu.bind_group_layout); + shd->wgpu.bind_group_layout = 0; + } + for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + _sg_wgpu_shader_stage_t* wgpu_stage = &shd->wgpu.stage[stage_index]; + if (wgpu_stage->module) { + wgpuShaderModuleRelease(wgpu_stage->module); + wgpu_stage->module = 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.bind_group_layout); + 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 and sampler resources + WGPUBindGroupLayout wgpu_bgl[_SG_WGPU_NUM_BINDGROUPS]; + _sg_clear(&wgpu_bgl, sizeof(wgpu_bgl)); + wgpu_bgl[_SG_WGPU_UNIFORM_BINDGROUP_INDEX] = _sg.wgpu.uniform.bind.group_layout; + wgpu_bgl[_SG_WGPU_IMAGE_SAMPLER_BINDGROUP_INDEX] = shd->wgpu.bind_group_layout; + 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); + + WGPUVertexBufferLayout wgpu_vb_layouts[SG_MAX_VERTEX_BUFFERS]; + _sg_clear(wgpu_vb_layouts, sizeof(wgpu_vb_layouts)); + WGPUVertexAttribute wgpu_vtx_attrs[SG_MAX_VERTEX_BUFFERS][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_VERTEX_BUFFERS; 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_VERTEX_BUFFERS); + pip->cmn.vertex_buffer_layout_active[vb_idx] = true; + 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 = desc->label; + wgpu_pip_desc.layout = wgpu_pip_layout; + wgpu_pip_desc.vertex.module = shd->wgpu.stage[SG_SHADERSTAGE_VS].module; + wgpu_pip_desc.vertex.entryPoint = shd->wgpu.stage[SG_SHADERSTAGE_VS].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 = 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.stage[SG_SHADERSTAGE_FS].module; + wgpu_frag_state.entryPoint = shd->wgpu.stage[SG_SHADERSTAGE_FS].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.pip = wgpuDeviceCreateRenderPipeline(_sg.wgpu.dev, &wgpu_pip_desc); + wgpuPipelineLayoutRelease(wgpu_pip_layout); + if (0 == pip->wgpu.pip) { + _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); + if (pip == _sg.wgpu.cur_pipeline) { + _sg.wgpu.cur_pipeline = 0; + _sg.wgpu.cur_pipeline_id.id = SG_INVALID_ID; + } + if (pip->wgpu.pip) { + wgpuRenderPipelineRelease(pip->wgpu.pip); + pip->wgpu.pip = 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_pass(const sg_pass* pass) { + SOKOL_ASSERT(pass); + SOKOL_ASSERT(_sg.wgpu.cmd_enc); + SOKOL_ASSERT(_sg.wgpu.dev); + + const _sg_attachments_t* atts = _sg.cur_pass.atts; + const sg_swapchain* swapchain = &pass->swapchain; + const sg_pass_action* action = &pass->action; + + _sg.wgpu.cur_pipeline = 0; + _sg.wgpu.cur_pipeline_id.id = SG_INVALID_ID; + + 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 = 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.pass_enc = wgpuCommandEncoderBeginRenderPass(_sg.wgpu.cmd_enc, &wgpu_pass_desc); + SOKOL_ASSERT(_sg.wgpu.pass_enc); + + // clear bindings cache and apply an empty image-sampler bindgroup + _sg_wgpu_bindings_cache_clear(); + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.pass_enc, _SG_WGPU_IMAGE_SAMPLER_BINDGROUP_INDEX, _sg.wgpu.empty_bind_group, 0, 0); + _sg_stats_add(wgpu.bindings.num_set_bindgroup, 1); + + // initial uniform buffer binding (required even if no uniforms are set in the frame) + _sg_wgpu_uniform_buffer_on_begin_pass(); +} + +_SOKOL_PRIVATE void _sg_wgpu_end_pass(void) { + if (_sg.wgpu.pass_enc) { + wgpuRenderPassEncoderEnd(_sg.wgpu.pass_enc); + wgpuRenderPassEncoderRelease(_sg.wgpu.pass_enc); + _sg.wgpu.pass_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.pass_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.pass_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.pass_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.pass_enc, sx, sy, sw, sh); +} + +_SOKOL_PRIVATE void _sg_wgpu_apply_pipeline(_sg_pipeline_t* pip) { + SOKOL_ASSERT(pip); + SOKOL_ASSERT(pip->wgpu.pip); + SOKOL_ASSERT(_sg.wgpu.pass_enc); + _sg.wgpu.use_indexed_draw = (pip->cmn.index_type != SG_INDEXTYPE_NONE); + _sg.wgpu.cur_pipeline = pip; + _sg.wgpu.cur_pipeline_id.id = pip->slot.id; + wgpuRenderPassEncoderSetPipeline(_sg.wgpu.pass_enc, pip->wgpu.pip); + wgpuRenderPassEncoderSetBlendConstant(_sg.wgpu.pass_enc, &pip->wgpu.blend_color); + wgpuRenderPassEncoderSetStencilReference(_sg.wgpu.pass_enc, pip->cmn.stencil.ref); +} + +_SOKOL_PRIVATE bool _sg_wgpu_apply_bindings(_sg_bindings_t* bnd) { + SOKOL_ASSERT(_sg.wgpu.pass_enc); + SOKOL_ASSERT(bnd); + SOKOL_ASSERT(bnd->pip->shader && (bnd->pip->cmn.shader_id.id == bnd->pip->shader->slot.id)); + bool retval = true; + 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(sg_shader_stage stage_index, int ub_index, const sg_range* data) { + const uint32_t alignment = _sg.wgpu.limits.limits.minUniformBufferOffsetAlignment; + SOKOL_ASSERT(_sg.wgpu.pass_enc); + SOKOL_ASSERT(_sg.wgpu.uniform.staging); + SOKOL_ASSERT((_sg.wgpu.uniform.offset + data->size) <= _sg.wgpu.uniform.num_bytes); + SOKOL_ASSERT((_sg.wgpu.uniform.offset & (alignment - 1)) == 0); + SOKOL_ASSERT(_sg.wgpu.cur_pipeline && _sg.wgpu.cur_pipeline->shader); + SOKOL_ASSERT(_sg.wgpu.cur_pipeline->slot.id == _sg.wgpu.cur_pipeline_id.id); + SOKOL_ASSERT(_sg.wgpu.cur_pipeline->shader->slot.id == _sg.wgpu.cur_pipeline->cmn.shader_id.id); + SOKOL_ASSERT(ub_index < _sg.wgpu.cur_pipeline->shader->cmn.stage[stage_index].num_uniform_blocks); + SOKOL_ASSERT(data->size <= _sg.wgpu.cur_pipeline->shader->cmn.stage[stage_index].uniform_blocks[ub_index].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[stage_index][ub_index] = _sg.wgpu.uniform.offset; + _sg.wgpu.uniform.offset = _sg_roundup_u32(_sg.wgpu.uniform.offset + (uint32_t)data->size, alignment); + wgpuRenderPassEncoderSetBindGroup(_sg.wgpu.pass_enc, + _SG_WGPU_UNIFORM_BINDGROUP_INDEX, + _sg.wgpu.uniform.bind.group, + SG_NUM_SHADER_STAGES * SG_MAX_SHADERSTAGE_UBS, + &_sg.wgpu.uniform.bind.offsets[0][0]); +} + +_SOKOL_PRIVATE void _sg_wgpu_draw(int base_element, int num_elements, int num_instances) { + SOKOL_ASSERT(_sg.wgpu.pass_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.pass_enc, (uint32_t)num_elements, (uint32_t)num_instances, (uint32_t)base_element, 0, 0); + } else { + wgpuRenderPassEncoderDraw(_sg.wgpu.pass_enc, (uint32_t)num_elements, (uint32_t)num_instances, (uint32_t)base_element, 0); + } +} + +_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(sg_shader_stage stage_index, int ub_index, const sg_range* data) { + #if defined(_SOKOL_ANY_GL) + _sg_gl_apply_uniforms(stage_index, ub_index, data); + #elif defined(SOKOL_METAL) + _sg_mtl_apply_uniforms(stage_index, ub_index, data); + #elif defined(SOKOL_D3D11) + _sg_d3d11_apply_uniforms(stage_index, ub_index, data); + #elif defined(SOKOL_WGPU) + _sg_wgpu_apply_uniforms(stage_index, ub_index, data); + #elif defined(SOKOL_DUMMY_BACKEND) + _sg_dummy_apply_uniforms(stage_index, ub_index, 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_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_init_pool(_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_discard_pool(_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_reset_slot(_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_init_pool(&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_init_pool(&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_init_pool(&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_init_pool(&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_init_pool(&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_init_pool(&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_discard_pool(&p->attachments_pool); + _sg_discard_pool(&p->pipeline_pool); + _sg_discard_pool(&p->shader_pool); + _sg_discard_pool(&p->sampler_pool); + _sg_discard_pool(&p->image_pool); + _sg_discard_pool(&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]); + } + } +} + +// ██ ██ █████ ██ ██ ██████ █████ ████████ ██ ██████ ███ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ███████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ████ ██ ██ ███████ ██ ██████ ██ ██ ██ ██ ██████ ██ ████ +// +// >>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_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)) { + _SG_VALIDATE((0 != desc->data.ptr) && (desc->data.size > 0), VALIDATE_BUFFERDESC_DATA); + _SG_VALIDATE(desc->size == desc->data.size, VALIDATE_BUFFERDESC_DATA_SIZE); + } else { + _SG_VALIDATE(0 == desc->data.ptr, VALIDATE_BUFFERDESC_NO_DATA); + } + 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); + } + } 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); + _SG_VALIDATE(desc->min_filter != SG_FILTER_NONE, VALIDATE_SAMPLERDESC_MINFILTER_NONE); + _SG_VALIDATE(desc->mag_filter != SG_FILTER_NONE, VALIDATE_SAMPLERDESC_MAGFILTER_NONE); + // 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 +} + +_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); + _sg_validate_begin(); + _SG_VALIDATE(desc->_start_canary == 0, VALIDATE_SHADERDESC_CANARY); + _SG_VALIDATE(desc->_end_canary == 0, VALIDATE_SHADERDESC_CANARY); + #if defined(SOKOL_D3D11) + _SG_VALIDATE(0 != desc->attrs[0].sem_name, VALIDATE_SHADERDESC_ATTR_SEMANTICS); + #endif + #if defined(SOKOL_GLCORE33) || defined(SOKOL_GLES3) || defined(SOKOL_WGPU) + // on GL or WebGPU, must provide shader source code + _SG_VALIDATE(0 != desc->vs.source, VALIDATE_SHADERDESC_SOURCE); + _SG_VALIDATE(0 != desc->fs.source, VALIDATE_SHADERDESC_SOURCE); + #elif defined(SOKOL_METAL) || defined(SOKOL_D3D11) + // on Metal or D3D11, must provide shader source code or byte code + _SG_VALIDATE((0 != desc->vs.source)||(0 != desc->vs.bytecode.ptr), VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE); + _SG_VALIDATE((0 != desc->fs.source)||(0 != desc->fs.bytecode.ptr), VALIDATE_SHADERDESC_SOURCE_OR_BYTECODE); + #else + // Dummy Backend, don't require source or bytecode + #endif + for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + if (desc->attrs[i].name) { + _SG_VALIDATE(strlen(desc->attrs[i].name) < _SG_STRING_SIZE, VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); + } + if (desc->attrs[i].sem_name) { + _SG_VALIDATE(strlen(desc->attrs[i].sem_name) < _SG_STRING_SIZE, VALIDATE_SHADERDESC_ATTR_STRING_TOO_LONG); + } + } + // if shader byte code, the size must also be provided + if (0 != desc->vs.bytecode.ptr) { + _SG_VALIDATE(desc->vs.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); + } + if (0 != desc->fs.bytecode.ptr) { + _SG_VALIDATE(desc->fs.bytecode.size > 0, VALIDATE_SHADERDESC_NO_BYTECODE_SIZE); + } + for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + const sg_shader_stage_desc* stage_desc = (stage_index == 0)? &desc->vs : &desc->fs; + bool uniform_blocks_continuous = true; + for (int ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { + const sg_shader_uniform_block_desc* ub_desc = &stage_desc->uniform_blocks[ub_index]; + if (ub_desc->size > 0) { + _SG_VALIDATE(uniform_blocks_continuous, VALIDATE_SHADERDESC_NO_CONT_UBS); + #if defined(_SOKOL_ANY_GL) + bool uniforms_continuous = true; + uint32_t uniform_offset = 0; + int num_uniforms = 0; + for (int u_index = 0; u_index < SG_MAX_UB_MEMBERS; u_index++) { + const sg_shader_uniform_desc* u_desc = &ub_desc->uniforms[u_index]; + if (u_desc->type != SG_UNIFORMTYPE_INVALID) { + _SG_VALIDATE(uniforms_continuous, VALIDATE_SHADERDESC_NO_CONT_UB_MEMBERS); + #if defined(SOKOL_GLES3) + _SG_VALIDATE(0 != u_desc->name, VALIDATE_SHADERDESC_UB_MEMBER_NAME); + #endif + const int array_count = u_desc->array_count; + _SG_VALIDATE(array_count > 0, VALIDATE_SHADERDESC_UB_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_UB_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_UB_SIZE_MISMATCH); + _SG_VALIDATE(num_uniforms > 0, VALIDATE_SHADERDESC_NO_UB_MEMBERS); + #endif + } else { + uniform_blocks_continuous = false; + } + } + bool images_continuous = true; + int num_images = 0; + for (int img_index = 0; img_index < SG_MAX_SHADERSTAGE_IMAGES; img_index++) { + const sg_shader_image_desc* img_desc = &stage_desc->images[img_index]; + if (img_desc->used) { + _SG_VALIDATE(images_continuous, VALIDATE_SHADERDESC_NO_CONT_IMAGES); + num_images++; + } else { + images_continuous = false; + } + } + bool samplers_continuous = true; + int num_samplers = 0; + for (int smp_index = 0; smp_index < SG_MAX_SHADERSTAGE_SAMPLERS; smp_index++) { + const sg_shader_sampler_desc* smp_desc = &stage_desc->samplers[smp_index]; + if (smp_desc->used) { + _SG_VALIDATE(samplers_continuous, VALIDATE_SHADERDESC_NO_CONT_SAMPLERS); + num_samplers++; + } else { + samplers_continuous = false; + } + } + bool image_samplers_continuous = true; + int num_image_samplers = 0; + for (int img_smp_index = 0; img_smp_index < SG_MAX_SHADERSTAGE_IMAGESAMPLERPAIRS; img_smp_index++) { + const sg_shader_image_sampler_pair_desc* img_smp_desc = &stage_desc->image_sampler_pairs[img_smp_index]; + if (img_smp_desc->used) { + _SG_VALIDATE(image_samplers_continuous, VALIDATE_SHADERDESC_NO_CONT_IMAGE_SAMPLER_PAIRS); + num_image_samplers++; + const bool img_slot_in_range = (img_smp_desc->image_slot >= 0) && (img_smp_desc->image_slot < SG_MAX_SHADERSTAGE_IMAGES); + const bool smp_slot_in_range = (img_smp_desc->sampler_slot >= 0) && (img_smp_desc->sampler_slot < SG_MAX_SHADERSTAGE_SAMPLERS); + _SG_VALIDATE(img_slot_in_range && (img_smp_desc->image_slot < num_images), VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_IMAGE_SLOT_OUT_OF_RANGE); + _SG_VALIDATE(smp_slot_in_range && (img_smp_desc->sampler_slot < num_samplers), VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_IMAGE_SLOT_OUT_OF_RANGE); + #if defined(_SOKOL_ANY_GL) + _SG_VALIDATE(img_smp_desc->glsl_name != 0, VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_NAME_REQUIRED_FOR_GL); + #endif + if (img_slot_in_range && smp_slot_in_range) { + const sg_shader_image_desc* img_desc = &stage_desc->images[img_smp_desc->image_slot]; + const sg_shader_sampler_desc* smp_desc = &stage_desc->samplers[img_smp_desc->sampler_slot]; + 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); + } + } + } else { + _SG_VALIDATE(img_smp_desc->glsl_name == 0, VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_HAS_NAME_BUT_NOT_USED); + _SG_VALIDATE(img_smp_desc->image_slot == 0, VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_HAS_IMAGE_BUT_NOT_USED); + _SG_VALIDATE(img_smp_desc->sampler_slot == 0, VALIDATE_SHADERDESC_IMAGE_SAMPLER_PAIR_HAS_SAMPLER_BUT_NOT_USED); + image_samplers_continuous = false; + } + } + // each image and sampler must be referenced by an image sampler + const uint32_t expected_img_slot_mask = (uint32_t)((1 << num_images) - 1); + const uint32_t expected_smp_slot_mask = (uint32_t)((1 << num_samplers) - 1); + uint32_t actual_img_slot_mask = 0; + uint32_t actual_smp_slot_mask = 0; + for (int img_smp_index = 0; img_smp_index < num_image_samplers; img_smp_index++) { + const sg_shader_image_sampler_pair_desc* img_smp_desc = &stage_desc->image_sampler_pairs[img_smp_index]; + actual_img_slot_mask |= (1 << ((uint32_t)img_smp_desc->image_slot & 31)); + actual_smp_slot_mask |= (1 << ((uint32_t)img_smp_desc->sampler_slot & 31)); + } + _SG_VALIDATE(expected_img_slot_mask == actual_img_slot_mask, VALIDATE_SHADERDESC_IMAGE_NOT_REFERENCED_BY_IMAGE_SAMPLER_PAIRS); + _SG_VALIDATE(expected_smp_slot_mask == actual_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); + for (int buf_index = 0; buf_index < SG_MAX_VERTEX_BUFFERS; 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); + } + _SG_VALIDATE(desc->layout.attrs[0].format != SG_VERTEXFORMAT_INVALID, VALIDATE_PIPELINEDESC_NO_ATTRS); + 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); + bool attrs_cont = true; + for (int 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_ATTRS); + SOKOL_ASSERT(a_state->buffer_index < SG_MAX_VERTEX_BUFFERS); + #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 + } + } + 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; + } + _sg_validate_begin(); + _SG_VALIDATE(pass->_start_canary == 0, VALIDATE_BEGINPASS_CANARY); + _SG_VALIDATE(pass->_end_canary == 0, VALIDATE_BEGINPASS_CANARY); + if (pass->attachments.id == SG_INVALID_ID) { + // 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); + } + // swapchain params must be all zero! + _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_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(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); + // 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(); + + // 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)); + + // has expected vertex buffers, and vertex buffers still exist + for (int i = 0; i < SG_MAX_VERTEX_BUFFERS; i++) { + if (bindings->vertex_buffers[i].id != SG_INVALID_ID) { + _SG_VALIDATE(pip->cmn.vertex_buffer_layout_active[i], VALIDATE_ABND_VBS); + // buffers in vertex-buffer-slots must be of type SG_BUFFERTYPE_VERTEXBUFFER + 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); + } + } else { + // vertex buffer provided in a slot which has no vertex layout in pipeline + _SG_VALIDATE(!pip->cmn.vertex_buffer_layout_active[i], VALIDATE_ABND_VBS); + } + } + + // 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 vertex shader images + for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { + const _sg_shader_stage_t* stage = &pip->shader->cmn.stage[SG_SHADERSTAGE_VS]; + if (stage->images[i].image_type != _SG_IMAGETYPE_DEFAULT) { + _SG_VALIDATE(bindings->vs.images[i].id != SG_INVALID_ID, VALIDATE_ABND_VS_EXPECTED_IMAGE_BINDING); + if (bindings->vs.images[i].id != SG_INVALID_ID) { + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, bindings->vs.images[i].id); + _SG_VALIDATE(img != 0, VALIDATE_ABND_VS_IMG_EXISTS); + if (img && img->slot.state == SG_RESOURCESTATE_VALID) { + _SG_VALIDATE(img->cmn.type == stage->images[i].image_type, VALIDATE_ABND_VS_IMAGE_TYPE_MISMATCH); + _SG_VALIDATE(img->cmn.sample_count == 1, VALIDATE_ABND_VS_IMAGE_MSAA); + const _sg_pixelformat_info_t* info = &_sg.formats[img->cmn.pixel_format]; + switch (stage->images[i].sample_type) { + case SG_IMAGESAMPLETYPE_FLOAT: + _SG_VALIDATE(info->filter, VALIDATE_ABND_VS_EXPECTED_FILTERABLE_IMAGE); + break; + case SG_IMAGESAMPLETYPE_DEPTH: + _SG_VALIDATE(info->depth, VALIDATE_ABND_VS_EXPECTED_DEPTH_IMAGE); + break; + default: + break; + } + } + } + } else { + _SG_VALIDATE(bindings->vs.images[i].id == SG_INVALID_ID, VALIDATE_ABND_VS_UNEXPECTED_IMAGE_BINDING); + } + } + + // has expected vertex shader image samplers + for (int i = 0; i < SG_MAX_SHADERSTAGE_SAMPLERS; i++) { + const _sg_shader_stage_t* stage = &pip->shader->cmn.stage[SG_SHADERSTAGE_VS]; + if (stage->samplers[i].sampler_type != _SG_SAMPLERTYPE_DEFAULT) { + _SG_VALIDATE(bindings->vs.samplers[i].id != SG_INVALID_ID, VALIDATE_ABND_VS_EXPECTED_SAMPLER_BINDING); + if (bindings->vs.samplers[i].id != SG_INVALID_ID) { + const _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, bindings->vs.samplers[i].id); + _SG_VALIDATE(smp != 0, VALIDATE_ABND_VS_SMP_EXISTS); + if (smp) { + if (stage->samplers[i].sampler_type == SG_SAMPLERTYPE_COMPARISON) { + _SG_VALIDATE(smp->cmn.compare != SG_COMPAREFUNC_NEVER, VALIDATE_ABND_VS_UNEXPECTED_SAMPLER_COMPARE_NEVER); + } else { + _SG_VALIDATE(smp->cmn.compare == SG_COMPAREFUNC_NEVER, VALIDATE_ABND_VS_EXPECTED_SAMPLER_COMPARE_NEVER); + } + if (stage->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_VS_EXPECTED_NONFILTERING_SAMPLER); + } + } + } + } else { + _SG_VALIDATE(bindings->vs.samplers[i].id == SG_INVALID_ID, VALIDATE_ABND_VS_UNEXPECTED_SAMPLER_BINDING); + } + } + + // has expected fragment shader images + for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { + const _sg_shader_stage_t* stage = &pip->shader->cmn.stage[SG_SHADERSTAGE_FS]; + if (stage->images[i].image_type != _SG_IMAGETYPE_DEFAULT) { + _SG_VALIDATE(bindings->fs.images[i].id != SG_INVALID_ID, VALIDATE_ABND_FS_EXPECTED_IMAGE_BINDING); + if (bindings->fs.images[i].id != SG_INVALID_ID) { + const _sg_image_t* img = _sg_lookup_image(&_sg.pools, bindings->fs.images[i].id); + _SG_VALIDATE(img != 0, VALIDATE_ABND_FS_IMG_EXISTS); + if (img && img->slot.state == SG_RESOURCESTATE_VALID) { + _SG_VALIDATE(img->cmn.type == stage->images[i].image_type, VALIDATE_ABND_FS_IMAGE_TYPE_MISMATCH); + _SG_VALIDATE(img->cmn.sample_count == 1, VALIDATE_ABND_FS_IMAGE_MSAA); + const _sg_pixelformat_info_t* info = &_sg.formats[img->cmn.pixel_format]; + switch (stage->images[i].sample_type) { + case SG_IMAGESAMPLETYPE_FLOAT: + _SG_VALIDATE(info->filter, VALIDATE_ABND_FS_EXPECTED_FILTERABLE_IMAGE); + break; + case SG_IMAGESAMPLETYPE_DEPTH: + _SG_VALIDATE(info->depth, VALIDATE_ABND_FS_EXPECTED_DEPTH_IMAGE); + break; + default: + break; + } + } + } + } else { + _SG_VALIDATE(bindings->fs.images[i].id == SG_INVALID_ID, VALIDATE_ABND_FS_UNEXPECTED_IMAGE_BINDING); + } + } + + // has expected fragment shader samplers + for (int i = 0; i < SG_MAX_SHADERSTAGE_SAMPLERS; i++) { + const _sg_shader_stage_t* stage = &pip->shader->cmn.stage[SG_SHADERSTAGE_FS]; + if (stage->samplers[i].sampler_type != _SG_SAMPLERTYPE_DEFAULT) { + _SG_VALIDATE(bindings->fs.samplers[i].id != SG_INVALID_ID, VALIDATE_ABND_FS_EXPECTED_SAMPLER_BINDING); + if (bindings->fs.samplers[i].id != SG_INVALID_ID) { + const _sg_sampler_t* smp = _sg_lookup_sampler(&_sg.pools, bindings->fs.samplers[i].id); + _SG_VALIDATE(smp != 0, VALIDATE_ABND_FS_SMP_EXISTS); + if (smp) { + if (stage->samplers[i].sampler_type == SG_SAMPLERTYPE_COMPARISON) { + _SG_VALIDATE(smp->cmn.compare != SG_COMPAREFUNC_NEVER, VALIDATE_ABND_FS_UNEXPECTED_SAMPLER_COMPARE_NEVER); + } else { + _SG_VALIDATE(smp->cmn.compare == SG_COMPAREFUNC_NEVER, VALIDATE_ABND_FS_EXPECTED_SAMPLER_COMPARE_NEVER); + } + if (stage->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_FS_EXPECTED_NONFILTERING_SAMPLER); + } + } + } + } else { + _SG_VALIDATE(bindings->fs.samplers[i].id == SG_INVALID_ID, VALIDATE_ABND_FS_UNEXPECTED_SAMPLER_BINDING); + } + } + return _sg_validate_end(); + #endif +} + +_SOKOL_PRIVATE bool _sg_validate_apply_uniforms(sg_shader_stage stage_index, int ub_index, const sg_range* data) { + #if !defined(SOKOL_DEBUG) + _SOKOL_UNUSED(stage_index); + _SOKOL_UNUSED(ub_index); + _SOKOL_UNUSED(data); + return true; + #else + if (_sg.desc.disable_validation) { + return true; + } + SOKOL_ASSERT((stage_index == SG_SHADERSTAGE_VS) || (stage_index == SG_SHADERSTAGE_FS)); + SOKOL_ASSERT((ub_index >= 0) && (ub_index < SG_MAX_SHADERSTAGE_UBS)); + _sg_validate_begin(); + _SG_VALIDATE(_sg.cur_pipeline.id != SG_INVALID_ID, VALIDATE_AUB_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)); + + // check that there is a uniform block at 'stage' and 'ub_index' + const _sg_shader_stage_t* stage = &pip->shader->cmn.stage[stage_index]; + _SG_VALIDATE(ub_index < stage->num_uniform_blocks, VALIDATE_AUB_NO_UB_AT_SLOT); + + // check that the provided data size matches the uniform block size + _SG_VALIDATE(data->size == stage->uniform_blocks[ub_index].size, VALIDATE_AUB_SIZE); + + 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; + } else if (def.data.size == 0) { + def.data.size = def.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_NONE); + 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.vs.entry = _sg_def(def.vs.entry, "_main"); + def.fs.entry = _sg_def(def.fs.entry, "_main"); + #else + def.vs.entry = _sg_def(def.vs.entry, "main"); + def.fs.entry = _sg_def(def.fs.entry, "main"); + #endif + #if defined(SOKOL_D3D11) + if (def.vs.source) { + def.vs.d3d11_target = _sg_def(def.vs.d3d11_target, "vs_4_0"); + } + if (def.fs.source) { + def.fs.d3d11_target = _sg_def(def.fs.d3d11_target, "ps_4_0"); + } + #endif + for (int stage_index = 0; stage_index < SG_NUM_SHADER_STAGES; stage_index++) { + sg_shader_stage_desc* stage_desc = (stage_index == SG_SHADERSTAGE_VS)? &def.vs : &def.fs; + for (int ub_index = 0; ub_index < SG_MAX_SHADERSTAGE_UBS; ub_index++) { + sg_shader_uniform_block_desc* ub_desc = &stage_desc->uniform_blocks[ub_index]; + if (0 == ub_desc->size) { + break; + } + ub_desc->layout = _sg_def(ub_desc->layout, SG_UNIFORMLAYOUT_NATIVE); + for (int u_index = 0; u_index < SG_MAX_UB_MEMBERS; u_index++) { + sg_shader_uniform_desc* u_desc = &ub_desc->uniforms[u_index]; + if (u_desc->type == SG_UNIFORMTYPE_INVALID) { + break; + } + u_desc->array_count = _sg_def(u_desc->array_count, 1); + } + } + for (int img_index = 0; img_index < SG_MAX_SHADERSTAGE_IMAGES; img_index++) { + sg_shader_image_desc* img_desc = &stage_desc->images[img_index]; + if (!img_desc->used) { + break; + } + 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 (int smp_index = 0; smp_index < SG_MAX_SHADERSTAGE_SAMPLERS; smp_index++) { + sg_shader_sampler_desc* smp_desc = &stage_desc->samplers[smp_index]; + if (!smp_desc->used) { + break; + } + 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; + + 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->src_factor_rgb = _sg_def(bs->src_factor_rgb, SG_BLENDFACTOR_ONE); + bs->dst_factor_rgb = _sg_def(bs->dst_factor_rgb, SG_BLENDFACTOR_ZERO); + bs->op_rgb = _sg_def(bs->op_rgb, SG_BLENDOP_ADD); + bs->src_factor_alpha = _sg_def(bs->src_factor_alpha, SG_BLENDFACTOR_ONE); + bs->dst_factor_alpha = _sg_def(bs->dst_factor_alpha, SG_BLENDFACTOR_ZERO); + bs->op_alpha = _sg_def(bs->op_alpha, SG_BLENDOP_ADD); + } + + 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_VERTEX_BUFFERS); + 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_VERTEX_BUFFERS]; + _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_VERTEX_BUFFERS); + 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_VERTEX_BUFFERS; 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_reset_slot(&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_reset_slot(&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_reset_slot(&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_reset_slot(&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_reset_slot(&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_reset_slot(&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 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_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.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_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_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.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_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); + SOKOL_ASSERT(_sg.cur_pass.in_pass); + _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); + SOKOL_ASSERT(_sg.cur_pass.in_pass); + _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); + SOKOL_ASSERT(_sg.cur_pass.in_pass); + _sg_stats_add(num_apply_pipeline, 1); + _sg.apply_bindings_called = false; + 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); + SOKOL_ASSERT(pip->shader && (pip->shader->slot.id == pip->cmn.shader_id.id)); + _sg_apply_pipeline(pip); + _SG_TRACE_ARGS(apply_pipeline, pip_id); +} + +SOKOL_API_IMPL void sg_apply_bindings(const sg_bindings* bindings) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(_sg.cur_pass.in_pass); + SOKOL_ASSERT(bindings); + SOKOL_ASSERT((bindings->_start_canary == 0) && (bindings->_end_canary==0)); + _sg_stats_add(num_apply_bindings, 1); + _sg.apply_bindings_called = true; + if (!_sg_validate_apply_bindings(bindings)) { + _sg.next_draw_valid = false; + return; + } + if (!_sg.cur_pass.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; + } + + for (int i = 0; i < SG_MAX_VERTEX_BUFFERS; i++, bnd.num_vbs++) { + if (bindings->vertex_buffers[i].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; + } + } else { + break; + } + } + + 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_SHADERSTAGE_IMAGES; i++, bnd.num_vs_imgs++) { + if (bindings->vs.images[i].id) { + bnd.vs_imgs[i] = _sg_lookup_image(&_sg.pools, bindings->vs.images[i].id); + if (bnd.vs_imgs[i]) { + _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == bnd.vs_imgs[i]->slot.state); + } else { + _sg.next_draw_valid = false; + } + } else { + break; + } + } + + for (int i = 0; i < SG_MAX_SHADERSTAGE_SAMPLERS; i++, bnd.num_vs_smps++) { + if (bindings->vs.samplers[i].id) { + bnd.vs_smps[i] = _sg_lookup_sampler(&_sg.pools, bindings->vs.samplers[i].id); + if (bnd.vs_smps[i]) { + _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == bnd.vs_smps[i]->slot.state); + } else { + _sg.next_draw_valid = false; + } + } else { + break; + } + } + + for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++, bnd.num_fs_imgs++) { + if (bindings->fs.images[i].id) { + bnd.fs_imgs[i] = _sg_lookup_image(&_sg.pools, bindings->fs.images[i].id); + if (bnd.fs_imgs[i]) { + _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == bnd.fs_imgs[i]->slot.state); + } else { + _sg.next_draw_valid = false; + } + } else { + break; + } + } + + for (int i = 0; i < SG_MAX_SHADERSTAGE_SAMPLERS; i++, bnd.num_fs_smps++) { + if (bindings->fs.samplers[i].id) { + bnd.fs_smps[i] = _sg_lookup_sampler(&_sg.pools, bindings->fs.samplers[i].id); + if (bnd.fs_smps[i]) { + _sg.next_draw_valid &= (SG_RESOURCESTATE_VALID == bnd.fs_smps[i]->slot.state); + } else { + _sg.next_draw_valid = false; + } + } else { + break; + } + } + + 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(sg_shader_stage stage, int ub_index, const sg_range* data) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(_sg.cur_pass.in_pass); + SOKOL_ASSERT((stage == SG_SHADERSTAGE_VS) || (stage == SG_SHADERSTAGE_FS)); + SOKOL_ASSERT((ub_index >= 0) && (ub_index < SG_MAX_SHADERSTAGE_UBS)); + 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); + if (!_sg_validate_apply_uniforms(stage, ub_index, data)) { + _sg.next_draw_valid = false; + return; + } + if (!_sg.cur_pass.valid) { + return; + } + if (!_sg.next_draw_valid) { + return; + } + _sg_apply_uniforms(stage, ub_index, data); + _SG_TRACE_ARGS(apply_uniforms, stage, ub_index, data); +} + +SOKOL_API_IMPL void sg_draw(int base_element, int num_elements, int num_instances) { + SOKOL_ASSERT(_sg.valid); + SOKOL_ASSERT(_sg.cur_pass.in_pass); + SOKOL_ASSERT(base_element >= 0); + SOKOL_ASSERT(num_elements >= 0); + SOKOL_ASSERT(num_instances >= 0); + _sg_stats_add(num_draw, 1); + #if defined(SOKOL_DEBUG) + if (!_sg.apply_bindings_called) { + _SG_WARN(DRAW_WITHOUT_BINDINGS); + } + #endif + if (!_sg.cur_pass.valid) { + return; + } + if (!_sg.next_draw_valid) { + return; + } + if (!_sg.apply_bindings_called) { + return; + } + /* attempting to draw with zero elements or instances is not technically an + error, but might be handled as an error in the backend API (e.g. on Metal) + */ + 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_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; + _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 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_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 (int stage_idx = 0; stage_idx < SG_NUM_SHADER_STAGES; stage_idx++) { + sg_shader_stage_desc* stage_desc = (stage_idx == 0) ? &desc.vs : &desc.fs; + const _sg_shader_stage_t* stage = &shd->cmn.stage[stage_idx]; + for (int ub_idx = 0; ub_idx < stage->num_uniform_blocks; ub_idx++) { + sg_shader_uniform_block_desc* ub_desc = &stage_desc->uniform_blocks[ub_idx]; + const _sg_shader_uniform_block_t* ub = &stage->uniform_blocks[ub_idx]; + ub_desc->size = ub->size; + } + for (int img_idx = 0; img_idx < stage->num_images; img_idx++) { + sg_shader_image_desc* img_desc = &stage_desc->images[img_idx]; + const _sg_shader_image_t* img = &stage->images[img_idx]; + img_desc->used = true; + img_desc->image_type = img->image_type; + img_desc->sample_type = img->sample_type; + img_desc->multisampled = img->multisampled; + } + for (int smp_idx = 0; smp_idx < stage->num_samplers; smp_idx++) { + sg_shader_sampler_desc* smp_desc = &stage_desc->samplers[smp_idx]; + const _sg_shader_sampler_t* smp = &stage->samplers[smp_idx]; + smp_desc->used = true; + smp_desc->sampler_type = smp->sampler_type; + } + for (int img_smp_idx = 0; img_smp_idx < stage->num_image_samplers; img_smp_idx++) { + sg_shader_image_sampler_pair_desc* img_smp_desc = &stage_desc->image_sampler_pairs[img_smp_idx]; + const _sg_shader_image_sampler_t* img_smp = &stage->image_samplers[img_smp_idx]; + img_smp_desc->used = true; + img_smp_desc->image_slot = img_smp->image_slot; + img_smp_desc->sampler_slot = img_smp->sampler_slot; + img_smp_desc->glsl_name = 0; + } + } + } + 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.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 (int i = 0; i < SG_MAX_SHADERSTAGE_UBS; i++) { + res.vs_cbufs[i] = (const void*) shd->d3d11.stage[SG_SHADERSTAGE_VS].cbufs[i]; + res.fs_cbufs[i] = (const void*) shd->d3d11.stage[SG_SHADERSTAGE_FS].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.cmd_encoder) { + return (__bridge const void*) _sg.mtl.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 vs_lib = shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_lib; + const int vs_func = shd->mtl.stage[SG_SHADERSTAGE_VS].mtl_func; + const int fs_lib = shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_lib; + const int fs_func = shd->mtl.stage[SG_SHADERSTAGE_FS].mtl_func; + if (vs_lib != 0) { + res.vs_lib = (__bridge void*) _sg_mtl_id(vs_lib); + } + if (fs_lib != 0) { + res.fs_lib = (__bridge void*) _sg_mtl_id(fs_lib); + } + if (vs_func != 0) { + res.vs_func = (__bridge void*) _sg_mtl_id(vs_func); + } + if (fs_func != 0) { + res.fs_func = (__bridge void*) _sg_mtl_id(fs_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.pass_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.stage[SG_SHADERSTAGE_VS].module; + res.fs_mod = (const void*) shd->wgpu.stage[SG_SHADERSTAGE_FS].module; + res.bgl = (const void*) shd->wgpu.bind_group_layout; + } + #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.pip = (const void*) pip->wgpu.pip; + } + #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/source/engine/thirdparty/sokol/sokol_glue.h b/source/engine/thirdparty/sokol/sokol_glue.h new file mode 100644 index 0000000..b688a87 --- /dev/null +++ b/source/engine/thirdparty/sokol/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_enviornment(), + ... + }); + + 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/source/engine/thirdparty/sokol/sokol_log.h b/source/engine/thirdparty/sokol/sokol_log.h new file mode 100644 index 0000000..58ff30b --- /dev/null +++ b/source/engine/thirdparty/sokol/sokol_log.h @@ -0,0 +1,343 @@ +#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 loom 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); + #elif defined(_SLOG_LINUX) || defined(_SLOG_APPLE) + int prio; + switch (log_level) { + case 0: prio = LOG_CRIT; break; + case 1: prio = LOG_ERR; break; + case 2: prio = LOG_WARNING; break; + default: prio = LOG_INFO; break; + } + syslog(prio, "%s", line_buf); + #endif + if (0 == log_level) { + abort(); + } +} +#endif // SOKOL_LOG_IMPL diff --git a/source/engine/thirdparty/sokol/sokol_time.h b/source/engine/thirdparty/sokol/sokol_time.h new file mode 100644 index 0000000..fd766d8 --- /dev/null +++ b/source/engine/thirdparty/sokol/sokol_time.h @@ -0,0 +1,319 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_TIME_IMPL) +#define SOKOL_TIME_IMPL +#endif +#ifndef SOKOL_TIME_INCLUDED +/* + sokol_time.h -- simple cross-platform time measurement + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_TIME_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Optionally provide the following defines with your own implementations: + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_TIME_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_TIME_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_time.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_TIME_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + void stm_setup(); + Call once before any other functions to initialize sokol_time + (this calls for instance QueryPerformanceFrequency on Windows) + + uint64_t stm_now(); + Get current point in time in unspecified 'ticks'. The value that + is returned has no relation to the 'wall-clock' time and is + not in a specific time unit, it is only useful to compute + time differences. + + uint64_t stm_diff(uint64_t new, uint64_t old); + Computes the time difference between new and old. This will always + return a positive, non-zero value. + + uint64_t stm_since(uint64_t start); + Takes the current time, and returns the elapsed time since start + (this is a shortcut for "stm_diff(stm_now(), start)") + + uint64_t stm_laptime(uint64_t* last_time); + This is useful for measuring frame time and other recurring + events. It takes the current time, returns the time difference + to the value in last_time, and stores the current time in + last_time for the next call. If the value in last_time is 0, + the return value will be zero (this usually happens on the + very first call). + + uint64_t stm_round_to_common_refresh_rate(uint64_t duration) + This oddly named function takes a measured frame time and + returns the closest "nearby" common display refresh rate frame duration + in ticks. If the input duration isn't close to any common display + refresh rate, the input duration will be returned unchanged as a fallback. + The main purpose of this function is to remove jitter/inaccuracies from + measured frame times, and instead use the display refresh rate as + frame duration. + NOTE: for more robust frame timing, consider using the + sokol_app.h function sapp_frame_duration() + + Use the following functions to convert a duration in ticks into + useful time units: + + double stm_sec(uint64_t ticks); + double stm_ms(uint64_t ticks); + double stm_us(uint64_t ticks); + double stm_ns(uint64_t ticks); + Converts a tick value into seconds, milliseconds, microseconds + or nanoseconds. Note that not all platforms will have nanosecond + or even microsecond precision. + + Uses the following time measurement functions under the hood: + + Windows: QueryPerformanceFrequency() / QueryPerformanceCounter() + MacOS/iOS: mach_absolute_time() + emscripten: emscripten_get_now() + Linux+others: clock_gettime(CLOCK_MONOTONIC) + + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_TIME_INCLUDED (1) +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_TIME_API_DECL) +#define SOKOL_TIME_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_TIME_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_TIME_IMPL) +#define SOKOL_TIME_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_TIME_API_DECL __declspec(dllimport) +#else +#define SOKOL_TIME_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +SOKOL_TIME_API_DECL void stm_setup(void); +SOKOL_TIME_API_DECL uint64_t stm_now(void); +SOKOL_TIME_API_DECL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks); +SOKOL_TIME_API_DECL uint64_t stm_since(uint64_t start_ticks); +SOKOL_TIME_API_DECL uint64_t stm_laptime(uint64_t* last_time); +SOKOL_TIME_API_DECL uint64_t stm_round_to_common_refresh_rate(uint64_t frame_ticks); +SOKOL_TIME_API_DECL double stm_sec(uint64_t ticks); +SOKOL_TIME_API_DECL double stm_ms(uint64_t ticks); +SOKOL_TIME_API_DECL double stm_us(uint64_t ticks); +SOKOL_TIME_API_DECL double stm_ns(uint64_t ticks); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif // SOKOL_TIME_INCLUDED + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_TIME_IMPL +#define SOKOL_TIME_IMPL_INCLUDED (1) +#include /* memset */ + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +typedef struct { + uint32_t initialized; + LARGE_INTEGER freq; + LARGE_INTEGER start; +} _stm_state_t; +#elif defined(__APPLE__) && defined(__MACH__) +#include +typedef struct { + uint32_t initialized; + mach_timebase_info_data_t timebase; + uint64_t start; +} _stm_state_t; +#elif defined(__EMSCRIPTEN__) +#include +typedef struct { + uint32_t initialized; + double start; +} _stm_state_t; +#else /* anything else, this will need more care for non-Linux platforms */ +#ifdef ESP8266 +// On the ESP8266, clock_gettime ignores the first argument and CLOCK_MONOTONIC isn't defined +#define CLOCK_MONOTONIC 0 +#endif +#include +typedef struct { + uint32_t initialized; + uint64_t start; +} _stm_state_t; +#endif +static _stm_state_t _stm; + +/* prevent 64-bit overflow when computing relative timestamp + see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 +*/ +#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) +_SOKOL_PRIVATE int64_t _stm_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { + int64_t q = value / denom; + int64_t r = value % denom; + return q * numer + r * numer / denom; +} +#endif + +SOKOL_API_IMPL void stm_setup(void) { + memset(&_stm, 0, sizeof(_stm)); + _stm.initialized = 0xABCDABCD; + #if defined(_WIN32) + QueryPerformanceFrequency(&_stm.freq); + QueryPerformanceCounter(&_stm.start); + #elif defined(__APPLE__) && defined(__MACH__) + mach_timebase_info(&_stm.timebase); + _stm.start = mach_absolute_time(); + #elif defined(__EMSCRIPTEN__) + _stm.start = emscripten_get_now(); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + _stm.start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; + #endif +} + +SOKOL_API_IMPL uint64_t stm_now(void) { + SOKOL_ASSERT(_stm.initialized == 0xABCDABCD); + uint64_t now; + #if defined(_WIN32) + LARGE_INTEGER qpc_t; + QueryPerformanceCounter(&qpc_t); + now = (uint64_t) _stm_int64_muldiv(qpc_t.QuadPart - _stm.start.QuadPart, 1000000000, _stm.freq.QuadPart); + #elif defined(__APPLE__) && defined(__MACH__) + const uint64_t mach_now = mach_absolute_time() - _stm.start; + now = (uint64_t) _stm_int64_muldiv((int64_t)mach_now, (int64_t)_stm.timebase.numer, (int64_t)_stm.timebase.denom); + #elif defined(__EMSCRIPTEN__) + double js_now = emscripten_get_now() - _stm.start; + now = (uint64_t) (js_now * 1000000.0); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ((uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec) - _stm.start; + #endif + return now; +} + +SOKOL_API_IMPL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks) { + if (new_ticks > old_ticks) { + return new_ticks - old_ticks; + } + else { + return 1; + } +} + +SOKOL_API_IMPL uint64_t stm_since(uint64_t start_ticks) { + return stm_diff(stm_now(), start_ticks); +} + +SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) { + SOKOL_ASSERT(last_time); + uint64_t dt = 0; + uint64_t now = stm_now(); + if (0 != *last_time) { + dt = stm_diff(now, *last_time); + } + *last_time = now; + return dt; +} + +// first number is frame duration in ns, second number is tolerance in ns, +// the resulting min/max values must not overlap! +static const uint64_t _stm_refresh_rates[][2] = { + { 16666667, 1000000 }, // 60 Hz: 16.6667 +- 1ms + { 13888889, 250000 }, // 72 Hz: 13.8889 +- 0.25ms + { 13333333, 250000 }, // 75 Hz: 13.3333 +- 0.25ms + { 11764706, 250000 }, // 85 Hz: 11.7647 +- 0.25 + { 11111111, 250000 }, // 90 Hz: 11.1111 +- 0.25ms + { 10000000, 500000 }, // 100 Hz: 10.0000 +- 0.5ms + { 8333333, 500000 }, // 120 Hz: 8.3333 +- 0.5ms + { 6944445, 500000 }, // 144 Hz: 6.9445 +- 0.5ms + { 4166667, 1000000 }, // 240 Hz: 4.1666 +- 1ms + { 0, 0 }, // keep the last element always at zero +}; + +SOKOL_API_IMPL uint64_t stm_round_to_common_refresh_rate(uint64_t ticks) { + uint64_t ns; + int i = 0; + while (0 != (ns = _stm_refresh_rates[i][0])) { + uint64_t tol = _stm_refresh_rates[i][1]; + if ((ticks > (ns - tol)) && (ticks < (ns + tol))) { + return ns; + } + i++; + } + // fallthrough: didn't fit into any buckets + return ticks; +} + +SOKOL_API_IMPL double stm_sec(uint64_t ticks) { + return (double)ticks / 1000000000.0; +} + +SOKOL_API_IMPL double stm_ms(uint64_t ticks) { + return (double)ticks / 1000000.0; +} + +SOKOL_API_IMPL double stm_us(uint64_t ticks) { + return (double)ticks / 1000.0; +} + +SOKOL_API_IMPL double stm_ns(uint64_t ticks) { + return (double)ticks; +} +#endif /* SOKOL_TIME_IMPL */ + diff --git a/source/engine/thirdparty/sokol/tests/.gitignore b/source/engine/thirdparty/sokol/tests/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/source/engine/thirdparty/sokol/tests/CMakeLists.txt b/source/engine/thirdparty/sokol/tests/CMakeLists.txt new file mode 100644 index 0000000..cfaf7d4 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/CMakeLists.txt @@ -0,0 +1,164 @@ +cmake_minimum_required(VERSION 3.20) +project(sokol-test) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 11) + +# SOKOL_GLCORE33, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU, SOKOL_DUMMY +set(SOKOL_BACKEND "SOKOL_DUMMY_BACKEND" CACHE STRING "Select 3D backend API") +set_property(CACHE SOKOL_BACKEND PROPERTY STRINGS SOKOL_GLCORE33 SOKOL_METAL SOKOL_D3D11 SOKOL_DUMMY_BACKEND) +option(SOKOL_FORCE_EGL "Force EGL with GLCORE33 backend" OFF) +option(SOKOL_FORCE_SLES "Force SLES in sokol-audio Android backend" OFF) +option(USE_ARC "Enable/disable ARC" OFF) +option(USE_ANALYZER "Enable/disable clang analyzer" OFF) + +if (CMAKE_SYSTEM_NAME STREQUAL Emscripten) + set(EMSCRIPTEN 1) +elseif (CMAKE_SYSTEM_NAME STREQUAL iOS) + set(OSX_IOS 1) +elseif (CMAKE_SYSTEM_NAME STREQUAL Android) + set(ANDROID 1) +elseif (CMAKE_SYSTEM_NAME STREQUAL Linux) + set(LINUX 1) +elseif (CMAKE_SYSTEM_NAME STREQUAL Darwin) + set(OSX_MACOS 1) +elseif (CMAKE_SYSTEM_NAME STREQUAL Windows) + set(WINDOWS 1) +else() + message(FATAL_ERROR "Unrecognized CMAKE_SYSTEM_NAME") +endif() + +message(">> CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}") +message(">> SOKOL_BACKEND: ${SOKOL_BACKEND}") +message(">> SOKOL_FORCE_EGL: ${SOKOL_FORCE_EGL}") +message(">> SOKOL_FORCE_SLES: ${SOKOL_FORCE_SLES}") +if (OSX_IOS OR OSX_MACOS) + if (USE_ARC) + message(">> ObjC ARC ENABLED") + else() + message(">> ObjC ARC DISABLED") + endif() +endif() +message(">> BUILD_TYPE: ${CMAKE_BUILD_TYPE}") +message(">> TOOLCHAIN: ${CMAKE_TOOLCHAIN_FILE}") + +set(c_flags) +set(cxx_flags) +set(link_flags) +set(system_libs) + +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + set(c_flags ${c_flags} /W4 /WX /D_CRT_SECURE_NO_WARNINGS) + set(cxx_flags ${cxx_flags} /W4 /WX /EHsc /D_CRT_SECURE_NO_WARNINGS) +else() + set(c_flags ${c_flags} -Wall -Wextra -Werror -Wsign-conversion -Wstrict-prototypes) + set(cxx_flags ${cxx_flags} -Wall -Wextra -Werror -Wsign-conversion -fno-rtti -fno-exceptions) + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(c_flags ${c_flags} -Wno-missing-field-initializers) + set(cxx_flags ${cxx_flags} -Wno-missing-field-initializers) + endif() + if (USE_ANALYZER) + # FIXME: consider using clang-tidy via CMAKE_CXX_CLANG_TIDY: https://ortogonal.github.io/cmake-clang-tidy/ + # with the default settings this spams the output with irrelevant C++ coding style warnings in 3rd party libs though + message(">> Configuring for static code analysis") + set(c_flags ${c_flags} --analyze -Xanalyzer -analyzer-opt-analyze-headers) + set(cxx_flags ${cxx_flags} --analyze -Xanalyzer -analyzer-opt-analyze-headers) + set(link_flags ${link_flags} --analyze -Wno-unused-command-line-argument) + endif() +endif() + +if (EMSCRIPTEN) + set(CMAKE_EXECUTABLE_SUFFIX ".html") + set(link_flags ${link_flags} -sNO_FILESYSTEM=1 -sASSERTIONS=0 -sMALLOC=emmalloc -sINITIAL_MEMORY=33554432 --closure=1) + if (SOKOL_BACKEND STREQUAL SOKOL_WGPU) + set(link_flags ${link_flags} -sUSE_WEBGPU=1) + else() + set(link_flags ${link_flags} -sUSE_WEBGL2=1) + endif() +elseif (OSX_IOS) + set(exe_type MACOSX_BUNDLE) + if (USE_ARC) + set(c_flags ${c_flags} -fobjc-arc) + set(cxx_flags ${cxx_flags} -fobjc-arc) + endif() + set(system_libs ${system_libs} "-framework Foundation" "-framework UIKit" "-framework AudioToolbox" "-framework AVFoundation") + if (SOKOL_BACKEND STREQUAL SOKOL_METAL) + set(system_libs ${system_libs} "-framework Metal" "-framework MetalKit") + else() + set(system_libs ${system_libs} "-framework OpenGLES" "-framework GLKit") + endif() +elseif (ANDROID) + if (SOKOL_FORCE_SLES) + set(system_libs ${system_libs} GLESv3 EGL OpenSLES log android) + else() + set(system_libs ${system_libs} GLESv3 EGL aaudio log android) + endif() +elseif (LINUX) + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) + if ((SOKOL_BACKEND STREQUAL SOKOL_GLES3) OR SOKOL_FORCE_EGL) + set(system_libs ${system_libs} X11 Xi Xcursor EGL GL asound dl m Threads::Threads) + else() + set(system_libs ${system_libs} X11 Xi Xcursor GL asound dl m Threads::Threads) + endif() +elseif (OSX_MACOS) + set(exe_type MACOSX_BUNDLE) + if (USE_ARC) + set(c_flags ${c_flags} -fobjc-arc) + set(cxx_flags ${cxx_flags} -fobjc-arc) + endif() + set(system_libs ${system_libs} "-framework QuartzCore" "-framework Cocoa" "-framework AudioToolbox") + if (SOKOL_BACKEND STREQUAL SOKOL_METAL) + set(system_libs ${system_libs} "-framework MetalKit" "-framework Metal") + else() + set(system_libs ${system_libs} "-framework OpenGL") + endif() +elseif (WINDOWS) + set(exe_type WIN32) +endif() + +macro(configure_common target) + if (SOKOL_FORCE_EGL) + target_compile_definitions(${target} PRIVATE SOKOL_FORCE_EGL) + endif() + if (SOKOL_FORCE_SLES) + target_compile_definitions(${target} PRIVATE SAUDIO_ANDROID_SLES) + endif() + target_compile_definitions(${target} PRIVATE ${SOKOL_BACKEND}) + target_link_options(${target} PRIVATE ${link_flags}) + target_link_libraries(${target} PRIVATE ${system_libs}) + target_include_directories(${target} PRIVATE ../.. ../../util) + target_include_directories(${target} PRIVATE ../ext) +endmacro() + +macro(configure_osx_properties target) + if (OSX_IOS) + target_compile_definitions(${target} PRIVATE GLES_SILENCE_DEPRECATION) + endif() + set_target_properties(${target} PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${target}") + set_target_properties(${target} PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "${target}") + set_target_properties(${target} PROPERTIES MACOSX_BUNDLE_PRODUCT_NAME "${target}") + set_target_properties(${target} PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "${target}") +endmacro() + +macro(configure_c target) + configure_common(${target}) + target_compile_options(${target} PRIVATE ${c_flags}) + if (OSX_MACOS OR OSX_IOS) + target_compile_options(${target} PRIVATE -x objective-c) + configure_osx_properties(${target}) + endif() +endmacro() + +macro(configure_cxx target) + configure_common(${target}) + target_compile_options(${target} PRIVATE ${cxx_flags}) + if (OSX_MACOS OR OSX_IOS) + target_compile_options(${target} PRIVATE -x objective-c++) + configure_osx_properties(${target}) + endif() +endmacro() + +add_subdirectory(ext) +add_subdirectory(compile) +add_subdirectory(functional) diff --git a/source/engine/thirdparty/sokol/tests/CMakePresets.json b/source/engine/thirdparty/sokol/tests/CMakePresets.json new file mode 100644 index 0000000..fe0b896 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/CMakePresets.json @@ -0,0 +1,720 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "macos_gl_debug", + "generator": "Ninja", + "binaryDir": "build/macos_gl_debug", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLCORE33", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "macos_gl_release", + "generator": "Ninja", + "binaryDir": "build/macos_gl_release", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLCORE33", + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "macos_gl_analyze", + "generator": "Ninja", + "binaryDir": "build/macos_gl_analyze", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLCORE33", + "CMAKE_BUILD_TYPE": "Debug", + "USE_ANALYZER": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "macos_metal_debug", + "generator": "Ninja", + "binaryDir": "build/macos_metal_debug", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_METAL", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "macos_metal_release", + "generator": "Ninja", + "binaryDir": "build/macos_metal_release", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_METAL", + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "macos_metal_analyze", + "generator": "Ninja", + "binaryDir": "build/macos_metal_analyze", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_METAL", + "CMAKE_BUILD_TYPE": "Debug", + "USE_ANALYZER": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "macos_arc_gl_debug", + "generator": "Ninja", + "binaryDir": "build/macos_arc_gl_debug", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLCORE33", + "USE_ARC": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "macos_arc_gl_release", + "generator": "Ninja", + "binaryDir": "build/macos_arc_gl_release", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLCORE33", + "USE_ARC": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "macos_arc_gl_analyze", + "generator": "Ninja", + "binaryDir": "build/macos_arc_gl_analyze", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLCORE33", + "USE_ARC": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_BUILD_TYPE": "Debug", + "USE_ANALYZER": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "macos_arc_metal_debug", + "generator": "Ninja", + "binaryDir": "build/macos_arc_metal_debug", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_METAL", + "USE_ARC": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "macos_arc_metal_release", + "generator": "Ninja", + "binaryDir": "build/macos_arc_metal_release", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_METAL", + "USE_ARC": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "macos_arc_metal_analyze", + "generator": "Ninja", + "binaryDir": "build/macos_arc_metal_analyze", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_METAL", + "USE_ARC": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_BUILD_TYPE": "Debug", + "USE_ANALYZER": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "ios_gl", + "generator": "Xcode", + "binaryDir": "build/ios_gl", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLES3", + "CMAKE_SYSTEM_NAME": "iOS" + } + }, + { + "name": "ios_gl_analyze", + "generator": "Ninja", + "binaryDir": "build/ios_gl_analyze", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLES3", + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_SYSTEM_NAME": "iOS", + "USE_ANALYZER": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "ios_metal", + "generator": "Xcode", + "binaryDir": "build/ios_metal", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_METAL", + "CMAKE_SYSTEM_NAME": "iOS" + } + }, + { + "name": "ios_metal_analyze", + "generator": "Ninja", + "binaryDir": "build/ios_metal_analyze", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_METAL", + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_SYSTEM_NAME": "iOS", + "USE_ANALYZER": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "ios_arc_gl", + "generator": "Xcode", + "binaryDir": "build/ios_arc_gl", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLES3", + "USE_ARC": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_SYSTEM_NAME": "iOS" + } + }, + { + "name": "ios_arc_gl_analyze", + "generator": "Ninja", + "binaryDir": "build/ios_arc_gl_analyze", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLES3", + "USE_ARC": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_SYSTEM_NAME": "iOS", + "USE_ANALYZER": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "ios_arc_metal", + "generator": "Xcode", + "binaryDir": "build/ios_arc_metal", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_METAL", + "USE_ARC": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_SYSTEM_NAME": "iOS" + } + }, + { + "name": "ios_arc_metal_analyze", + "generator": "Ninja", + "binaryDir": "build/ios_arc_metal_analyze", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_METAL", + "USE_ARC": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_SYSTEM_NAME": "iOS", + "USE_ANALYZER": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "linux_gl_debug", + "generator": "Ninja", + "binaryDir": "build/linux_gl_debug", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLCORE33", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "linux_gl_release", + "generator": "Ninja", + "binaryDir": "build/linux_gl_release", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLCORE33", + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "linux_gl_analyze", + "generator": "Ninja", + "binaryDir": "build/linux_gl_analyze", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLCORE33", + "CMAKE_BUILD_TYPE": "Debug", + "USE_ANALYZER": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "linux_gles3_debug", + "generator": "Ninja", + "binaryDir": "build/linux_gles3_debug", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLES3", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "linux_gles3_release", + "generator": "Ninja", + "binaryDir": "build/linux_gles3_release", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLES3", + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "linux_gles3_analyze", + "generator": "Ninja", + "binaryDir": "build/linux_gles3_analyze", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLES3", + "CMAKE_BUILD_TYPE": "Debug", + "USE_ANALYZER": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "linux_gl_egl_debug", + "generator": "Ninja", + "binaryDir": "build/linux_gl_egl_debug", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLCORE33", + "SOKOL_FORCE_EGL": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "linux_gl_egl_release", + "generator": "Ninja", + "binaryDir": "build/linux_gl_egl_release", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLCORE33", + "SOKOL_FORCE_EGL": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "emsc_webgl2_debug", + "generator": "Ninja", + "binaryDir": "build/emsc_webgl2_debug", + "toolchainFile": "build/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLES3", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "emsc_webgl2_release", + "generator": "Ninja", + "binaryDir": "build/emsc_webgl2_release", + "toolchainFile": "build/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLES3", + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "emsc_wgpu_debug", + "generator": "Ninja", + "binaryDir": "build/emsc_wgpu_debug", + "toolchainFile": "build/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_WGPU", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "emsc_wgpu_release", + "generator": "Ninja", + "binaryDir": "build/emsc_wgpu_release", + "toolchainFile": "build/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_WGPU", + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "android_debug", + "generator": "Ninja", + "binaryDir": "build/android_debug", + "toolchainFile": "build/android_sdk/ndk-bundle/build/cmake/android.toolchain.cmake", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLES3", + "ANDROID_ABI": "armeabi-v7a", + "ANDROID_PLATFORM": "android-28", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "android_release", + "generator": "Ninja", + "binaryDir": "build/android_release", + "toolchainFile": "build/android_sdk/ndk-bundle/build/cmake/android.toolchain.cmake", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLES3", + "ANDROID_ABI": "armeabi-v7a", + "ANDROID_PLATFORM": "android-28", + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "android_sles_debug", + "generator": "Ninja", + "binaryDir": "build/android_sles_debug", + "toolchainFile": "build/android_sdk/ndk-bundle/build/cmake/android.toolchain.cmake", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLES3", + "ANDROID_ABI": "armeabi-v7a", + "ANDROID_PLATFORM": "android-28", + "CMAKE_BUILD_TYPE": "Debug", + "SOKOL_FORCE_SLES": { + "type": "BOOL", + "value": "ON" + } + } + }, + { + "name": "android_sles_release", + "generator": "Ninja", + "binaryDir": "build/android_sles_release", + "toolchainFile": "build/android_sdk/ndk-bundle/build/cmake/android.toolchain.cmake", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLES3", + "ANDROID_ABI": "armeabi-v7a", + "ANDROID_PLATFORM": "android-28", + "CMAKE_BUILD_TYPE": "Release", + "SOKOL_FORCE_SLES": { + "type": "BOOL", + "value": "ON" + } + } + }, + { + "name": "win_gl", + "binaryDir": "build/win_gl", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLCORE33" + } + }, + { + "name": "win_gl_analyze", + "generator": "Ninja", + "binaryDir": "build/win_gl_analyze", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_GLCORE33", + "CMAKE_BUILD_TYPE": "Debug", + "USE_ANALYZER": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + }, + { + "name": "win_d3d11", + "binaryDir": "build/win_d3d11", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_D3D11" + } + }, + { + "name": "win_d3d11_analyze", + "generator": "Ninja", + "binaryDir": "build/win_d3d11_analyze", + "cacheVariables": { + "SOKOL_BACKEND": "SOKOL_D3D11", + "CMAKE_BUILD_TYPE": "Debug", + "USE_ANALYZER": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + } + } + ], + "buildPresets": [ + { + "name": "macos_gl_debug", + "configurePreset": "macos_gl_debug" + }, + { + "name": "macos_gl_release", + "configurePreset": "macos_gl_release" + }, + { + "name": "macos_gl_analyze", + "configurePreset": "macos_gl_analyze" + }, + { + "name": "macos_metal_debug", + "configurePreset": "macos_metal_debug" + }, + { + "name": "macos_metal_release", + "configurePreset": "macos_metal_release" + }, + { + "name": "macos_metal_analyze", + "configurePreset": "macos_metal_analyze" + }, + { + "name": "macos_arc_gl_debug", + "configurePreset": "macos_arc_gl_debug" + }, + { + "name": "macos_arc_gl_release", + "configurePreset": "macos_arc_gl_release" + }, + { + "name": "macos_arc_gl_analyze", + "configurePreset": "macos_arc_gl_analyze" + }, + { + "name": "macos_arc_metal_debug", + "configurePreset": "macos_arc_metal_debug" + }, + { + "name": "macos_arc_metal_release", + "configurePreset": "macos_arc_metal_release" + }, + { + "name": "macos_arc_metal_analyze", + "configurePreset": "macos_arc_metal_analyze" + }, + { + "name": "ios_gl_debug", + "configurePreset": "ios_gl", + "configuration": "Debug", + "nativeToolOptions": [ "CODE_SIGN_IDENTITY=\"\"", "CODE_SIGNING_REQUIRED=NO", "CODE_SIGNING_ALLOWED=NO" ] + }, + { + "name": "ios_gl_release", + "configurePreset": "ios_gl", + "configuration": "Release", + "nativeToolOptions": [ "CODE_SIGN_IDENTITY=\"\"", "CODE_SIGNING_REQUIRED=NO", "CODE_SIGNING_ALLOWED=NO" ] + }, + { + "name": "ios_gl_analyze", + "configurePreset": "ios_gl_analyze" + }, + { + "name": "ios_metal_debug", + "configurePreset": "ios_metal", + "configuration": "Debug", + "nativeToolOptions": [ "CODE_SIGN_IDENTITY=\"\"", "CODE_SIGNING_REQUIRED=NO", "CODE_SIGNING_ALLOWED=NO" ] + }, + { + "name": "ios_metal_release", + "configurePreset": "ios_metal", + "configuration": "Release", + "nativeToolOptions": [ "CODE_SIGN_IDENTITY=\"\"", "CODE_SIGNING_REQUIRED=NO", "CODE_SIGNING_ALLOWED=NO" ] + }, + { + "name": "ios_metal_analyze", + "configurePreset": "ios_metal_analyze" + }, + { + "name": "ios_arc_gl_debug", + "configurePreset": "ios_arc_gl", + "configuration": "Debug", + "nativeToolOptions": [ "CODE_SIGN_IDENTITY=\"\"", "CODE_SIGNING_REQUIRED=NO", "CODE_SIGNING_ALLOWED=NO" ] + }, + { + "name": "ios_arc_gl_release", + "configurePreset": "ios_arc_gl", + "configuration": "Release", + "nativeToolOptions": [ "CODE_SIGN_IDENTITY=\"\"", "CODE_SIGNING_REQUIRED=NO", "CODE_SIGNING_ALLOWED=NO" ] + }, + { + "name": "ios_arc_gl_analyze", + "configurePreset": "ios_arc_gl_analyze" + }, + { + "name": "ios_arc_metal_debug", + "configurePreset": "ios_arc_metal", + "configuration": "Debug", + "nativeToolOptions": [ "CODE_SIGN_IDENTITY=\"\"", "CODE_SIGNING_REQUIRED=NO", "CODE_SIGNING_ALLOWED=NO" ] + }, + { + "name": "ios_arc_metal_release", + "configurePreset": "ios_arc_metal", + "configuration": "Release", + "nativeToolOptions": [ "CODE_SIGN_IDENTITY=\"\"", "CODE_SIGNING_REQUIRED=NO", "CODE_SIGNING_ALLOWED=NO" ] + }, + { + "name": "ios_arc_metal_analyze", + "configurePreset": "ios_arc_metal_analyze" + }, + { + "name": "linux_gl_debug", + "configurePreset": "linux_gl_debug" + }, + { + "name": "linux_gl_release", + "configurePreset": "linux_gl_release" + }, + { + "name": "linux_gl_analyze", + "configurePreset": "linux_gl_analyze" + }, + { + "name": "linux_gles3_debug", + "configurePreset": "linux_gles3_debug" + }, + { + "name": "linux_gles3_release", + "configurePreset": "linux_gles3_release" + }, + { + "name": "linux_gles3_analyze", + "configurePreset": "linux_gles3_analyze" + }, + { + "name": "linux_gl_egl_debug", + "configurePreset": "linux_gl_egl_debug" + }, + { + "name": "linux_gl_egl_release", + "configurePreset": "linux_gl_egl_release" + }, + { + "name": "emsc_webgl2_debug", + "configurePreset": "emsc_webgl2_debug" + }, + { + "name": "emsc_webgl2_release", + "configurePreset": "emsc_webgl2_release" + }, + { + "name": "emsc_wgpu_debug", + "configurePreset": "emsc_wgpu_debug" + }, + { + "name": "emsc_wgpu_release", + "configurePreset": "emsc_wgpu_release" + }, + { + "name": "android_debug", + "configurePreset": "android_debug" + }, + { + "name": "android_release", + "configurePreset": "android_release" + }, + { + "name": "android_sles_debug", + "configurePreset": "android_sles_debug" + }, + { + "name": "android_sles_release", + "configurePreset": "android_sles_release" + }, + { + "name": "win_gl_debug", + "configurePreset": "win_gl", + "configuration": "Debug" + }, + { + "name": "win_gl_release", + "configurePreset": "win_gl", + "configuration": "Release" + }, + { + "name": "win_gl_analyze", + "configurePreset": "win_gl_analyze" + }, + { + "name": "win_d3d11_debug", + "configurePreset": "win_d3d11", + "configuration": "Debug" + }, + { + "name": "win_d3d11_release", + "configurePreset": "win_d3d11", + "configuration": "Release" + }, + { + "name": "win_d3d11_analyze", + "configurePreset": "win_d3d11_analyze" + } + ] +} diff --git a/source/engine/thirdparty/sokol/tests/analyze_ios.sh b/source/engine/thirdparty/sokol/tests/analyze_ios.sh new file mode 100755 index 0000000..5b3f474 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/analyze_ios.sh @@ -0,0 +1,6 @@ +set -e +source test_common.sh +build ios_gl_analyze ios_gl_analyze +build ios_metal_analyze ios_metal_analyze +build ios_arc_gl_analyze ios_arc_gl_analyze +build ios_arc_metal_analyze ios_arc_metal_analyze diff --git a/source/engine/thirdparty/sokol/tests/analyze_linux.sh b/source/engine/thirdparty/sokol/tests/analyze_linux.sh new file mode 100755 index 0000000..87099b9 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/analyze_linux.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -e +source test_common.sh + +build linux_gl_analyze linux_gl_analyze +build linux_gles3_analyze linux_gles3_analyze diff --git a/source/engine/thirdparty/sokol/tests/analyze_macos.sh b/source/engine/thirdparty/sokol/tests/analyze_macos.sh new file mode 100755 index 0000000..3134070 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/analyze_macos.sh @@ -0,0 +1,8 @@ +set -e +source test_common.sh + +build macos_gl_analyze macos_gl_analyze +build macos_metal_analyze macos_metal_analyze + +build macos_arc_gl_analyze macos_arc_gl_analyze +build macos_arc_metal_analyze macos_arc_metal_analyze diff --git a/source/engine/thirdparty/sokol/tests/analyze_win.cmd b/source/engine/thirdparty/sokol/tests/analyze_win.cmd new file mode 100644 index 0000000..39aa798 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/analyze_win.cmd @@ -0,0 +1,4 @@ +cmake --preset win_gl_analyze || exit /b 10 +cmake --build --preset win_gl_analyze || exit /b 10 +cmake --preset win_d3d11_analyze || exit /b 10 +cmake --build --preset win_d3d11_analyze || exit /b 10 diff --git a/source/engine/thirdparty/sokol/tests/compile/CMakeLists.txt b/source/engine/thirdparty/sokol/tests/compile/CMakeLists.txt new file mode 100644 index 0000000..e664555 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/CMakeLists.txt @@ -0,0 +1,58 @@ +set(c_sources + sokol_app.c + sokol_glue.c + sokol_gfx.c + sokol_time.c + sokol_args.c + sokol_audio.c + sokol_debugtext.c + sokol_gl.c + sokol_fontstash.c + sokol_imgui.c + sokol_gfx_imgui.c + sokol_shape.c + sokol_nuklear.c + sokol_color.c + sokol_spine.c + sokol_log.c + sokol_main.c) +if (NOT ANDROID) + set(c_sources ${c_sources} sokol_fetch.c) +endif() + +set(cxx_sources + sokol_app.cc + sokol_glue.cc + sokol_gfx.cc + sokol_time.cc + sokol_args.cc + sokol_audio.cc + sokol_debugtext.cc + sokol_gl.cc + sokol_fontstash.cc + sokol_imgui.cc + sokol_gfx_imgui.cc + sokol_shape.cc + sokol_color.cc + sokol_spine.cc + sokol_log.cc + sokol_main.cc) +if (NOT ANDROID) + set(cxx_sources ${cxx_sources} sokol_fetch.cc) +endif() + +if (ANDROID) + add_library(sokol-compiletest-c SHARED ${c_sources}) +else() + add_executable(sokol-compiletest-c ${exe_type} sokol_app.c sokol_glue.c ${c_sources}) +endif() +target_link_libraries(sokol-compiletest-c PUBLIC cimgui nuklear spine) +configure_c(sokol-compiletest-c) + +if (ANDROID) + add_library(sokol-compiletest-cxx SHARED ${cxx_sources}) +else() + add_executable(sokol-compiletest-cxx ${exe_type} ${cxx_sources}) +endif() +target_link_libraries(sokol-compiletest-cxx PUBLIC imgui nuklear spine) +configure_cxx(sokol-compiletest-cxx) diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_app.c b/source/engine/thirdparty/sokol/tests/compile/sokol_app.c new file mode 100644 index 0000000..33304cf --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_app.c @@ -0,0 +1,6 @@ +#define SOKOL_IMPL +#include "sokol_app.h" + +void use_app_impl(void) { + sapp_run(&(sapp_desc){0}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_app.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_app.cc new file mode 100644 index 0000000..49c254e --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_app.cc @@ -0,0 +1,6 @@ +#define SOKOL_IMPL +#include "sokol_app.h" + +void use_app_impl() { + sapp_run({ }); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_args.c b/source/engine/thirdparty/sokol/tests/compile/sokol_args.c new file mode 100644 index 0000000..4005283 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_args.c @@ -0,0 +1,6 @@ +#define SOKOL_IMPL +#include "sokol_args.h" + +void use_args_impl(void) { + sargs_setup(&(sargs_desc){0}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_args.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_args.cc new file mode 100644 index 0000000..7e31d46 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_args.cc @@ -0,0 +1,6 @@ +#define SOKOL_IMPL +#include "sokol_args.h" + +void use_args_impl() { + sargs_setup({}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_audio.c b/source/engine/thirdparty/sokol/tests/compile/sokol_audio.c new file mode 100644 index 0000000..1dc12f2 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_audio.c @@ -0,0 +1,6 @@ +#define SOKOL_IMPL +#include "sokol_audio.h" + +void use_audio_impl(void) { + saudio_setup(&(saudio_desc){0}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_audio.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_audio.cc new file mode 100644 index 0000000..4205d51 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_audio.cc @@ -0,0 +1,6 @@ +#define SOKOL_IMPL +#include "sokol_audio.h" + +void use_audio_impl() { + saudio_setup({}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_color.c b/source/engine/thirdparty/sokol/tests/compile/sokol_color.c new file mode 100644 index 0000000..ea3a354 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_color.c @@ -0,0 +1,8 @@ +#include "sokol_gfx.h" +#define SOKOL_IMPL +#include "sokol_color.h" + +void use_color_impl(void) { + sg_color c = sg_make_color_4b(255, 0, 0, 255); + (void)c; +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_color.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_color.cc new file mode 100644 index 0000000..ccb6d2b --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_color.cc @@ -0,0 +1,9 @@ +#include "sokol_gfx.h" +#define SOKOL_IMPL +#include "sokol_color.h" + +void use_color_impl(void) { + sg_color c = sg_make_color(255, 0, 0, 255); + (void)c; +} + diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_debugtext.c b/source/engine/thirdparty/sokol/tests/compile/sokol_debugtext.c new file mode 100644 index 0000000..1d22c63 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_debugtext.c @@ -0,0 +1,7 @@ +#include "sokol_gfx.h" +#define SOKOL_IMPL +#include "sokol_debugtext.h" + +void use_debugtext_impl(void) { + sdtx_setup(&(sdtx_desc_t){0}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_debugtext.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_debugtext.cc new file mode 100644 index 0000000..6889405 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_debugtext.cc @@ -0,0 +1,7 @@ +#include "sokol_gfx.h" +#define SOKOL_IMPL +#include "sokol_debugtext.h" + +void use_debugtext_impl() { + sdtx_setup({}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_fetch.c b/source/engine/thirdparty/sokol/tests/compile/sokol_fetch.c new file mode 100644 index 0000000..2509056 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_fetch.c @@ -0,0 +1,6 @@ +#define SOKOL_IMPL +#include "sokol_fetch.h" + +void use_fetch_impl(void) { + sfetch_setup(&(sfetch_desc_t){0}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_fetch.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_fetch.cc new file mode 100644 index 0000000..69cc228 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_fetch.cc @@ -0,0 +1,6 @@ +#define SOKOL_IMPL +#include "sokol_fetch.h" + +void use_fetch_impl() { + sfetch_setup({}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_fontstash.c b/source/engine/thirdparty/sokol/tests/compile/sokol_fontstash.c new file mode 100644 index 0000000..0232de8 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_fontstash.c @@ -0,0 +1,21 @@ +#include "sokol_gfx.h" +#include "sokol_gl.h" + +#define FONTSTASH_IMPLEMENTATION +#if defined(_MSC_VER ) +#pragma warning(disable:4996) // strncpy use in fontstash.h +#endif +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif +#include // malloc/free +#include "fontstash.h" +#define SOKOL_IMPL +#include "sokol_fontstash.h" + +void use_fontstash_impl(void) { + FONScontext* ctx = sfons_create(&(sfons_desc_t){ 0 }); + sfons_destroy(ctx); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_fontstash.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_fontstash.cc new file mode 100644 index 0000000..96d7c31 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_fontstash.cc @@ -0,0 +1,23 @@ +#include "sokol_gfx.h" +#include "sokol_gl.h" + +#define FONTSTASH_IMPLEMENTATION +#if defined(_MSC_VER ) +#pragma warning(disable:4996) // strncpy use in fontstash.h +#pragma warning(disable:4505) // unreferenced local function has been removed +#pragma warning(disable:4100) // unreferenced formal parameter +#endif +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif +#include // malloc/free +#include "fontstash.h" +#define SOKOL_IMPL +#include "sokol_fontstash.h" + +void use_fontstash_impl() { + const sfons_desc_t desc = { }; + FONScontext* ctx = sfons_create(&desc); + sfons_destroy(ctx); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_gfx.c b/source/engine/thirdparty/sokol/tests/compile/sokol_gfx.c new file mode 100644 index 0000000..8926310 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_gfx.c @@ -0,0 +1,6 @@ +#define SOKOL_IMPL +#include "sokol_gfx.h" + +void use_gfx_impl(void) { + sg_setup(&(sg_desc){0}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_gfx.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_gfx.cc new file mode 100644 index 0000000..58f6592 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_gfx.cc @@ -0,0 +1,6 @@ +#define SOKOL_IMPL +#include "sokol_gfx.h" + +void use_gfx_impl() { + sg_setup({}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_gfx_imgui.c b/source/engine/thirdparty/sokol/tests/compile/sokol_gfx_imgui.c new file mode 100644 index 0000000..5b59501 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_gfx_imgui.c @@ -0,0 +1,17 @@ +#include "sokol_app.h" +#include "sokol_gfx.h" +#define CIMGUI_DEFINE_ENUMS_AND_STRUCTS +#if defined(_MSC_VER ) +#pragma warning(disable:4201) // nonstandard extension used: nameless struct/union +#pragma warning(disable:4214) // nonstandard extension used: bit field types other than int +#endif +#include "cimgui/cimgui.h" +#include "sokol_imgui.h" +#define SOKOL_IMPL +#include "sokol_gfx_imgui.h" + +void use_gfx_imgui_impl(void) { + sgimgui_t ctx = {0}; + sgimgui_init(&ctx, &(sgimgui_desc_t){0}); + sgimgui_discard(&ctx); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_gfx_imgui.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_gfx_imgui.cc new file mode 100644 index 0000000..2e64dbc --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_gfx_imgui.cc @@ -0,0 +1,12 @@ +#include "sokol_app.h" +#include "sokol_gfx.h" +#include "imgui.h" +#include "sokol_imgui.h" +#define SOKOL_IMPL +#include "sokol_gfx_imgui.h" + +void use_gfx_imgui_impl() { + sgimgui_t ctx = {}; + sgimgui_init(&ctx, { }); + sgimgui_discard(&ctx); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_gl.c b/source/engine/thirdparty/sokol/tests/compile/sokol_gl.c new file mode 100644 index 0000000..6dbeea1 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_gl.c @@ -0,0 +1,7 @@ +#include "sokol_gfx.h" +#define SOKOL_IMPL +#include "sokol_gl.h" + +void use_gl_impl(void) { + sgl_setup(&(sgl_desc_t){0}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_gl.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_gl.cc new file mode 100644 index 0000000..a4177cb --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_gl.cc @@ -0,0 +1,7 @@ +#include "sokol_gfx.h" +#define SOKOL_IMPL +#include "sokol_gl.h" + +void use_gl_impl() { + sgl_setup({}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_glue.c b/source/engine/thirdparty/sokol/tests/compile/sokol_glue.c new file mode 100644 index 0000000..114b6e2 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_glue.c @@ -0,0 +1,9 @@ +#include "sokol_app.h" +#include "sokol_gfx.h" +#define SOKOL_IMPL +#include "sokol_glue.h" + +void use_glue_impl(void) { + const sg_environment env = sglue_environment(); + (void)env; +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_glue.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_glue.cc new file mode 100644 index 0000000..4aaf21c --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_glue.cc @@ -0,0 +1,9 @@ +#include "sokol_app.h" +#include "sokol_gfx.h" +#define SOKOL_IMPL +#include "sokol_glue.h" + +void use_glue_impl() { + const sg_environment ctx = sglue_environment(); + (void)ctx; +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_imgui.c b/source/engine/thirdparty/sokol/tests/compile/sokol_imgui.c new file mode 100644 index 0000000..90f0bb7 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_imgui.c @@ -0,0 +1,18 @@ +#include "sokol_app.h" +#include "sokol_gfx.h" +#define CIMGUI_DEFINE_ENUMS_AND_STRUCTS +#if defined(_MSC_VER ) +#pragma warning(disable:4201) // nonstandard extension used: nameless struct/union +#pragma warning(disable:4214) // nonstandard extension used: bit field types other than int +#endif +#include "cimgui/cimgui.h" +#define SOKOL_IMPL +#if defined(SOKOL_DUMMY_BACKEND) +#define SOKOL_IMGUI_NO_SOKOL_APP +#endif +#include "sokol_imgui.h" + +void use_imgui_impl(void) { + simgui_setup(&(simgui_desc_t){0}); +} + diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_imgui.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_imgui.cc new file mode 100644 index 0000000..5a6a32f --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_imgui.cc @@ -0,0 +1,12 @@ +#include "sokol_app.h" +#include "sokol_gfx.h" +#include "imgui.h" +#define SOKOL_IMPL +#if defined(SOKOL_DUMMY_BACKEND) +#define SOKOL_IMGUI_NO_SOKOL_APP +#endif +#include "sokol_imgui.h" + +void use_imgui_impl() { + simgui_setup({}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_log.c b/source/engine/thirdparty/sokol/tests/compile/sokol_log.c new file mode 100644 index 0000000..ac66ec5 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_log.c @@ -0,0 +1,6 @@ +#define SOKOL_IMPL +#include "sokol_log.h" + +void use_sokol_log(void) { + slog_func("bla", 1, 123, "123", 42, "bla.c", 0); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_log.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_log.cc new file mode 100644 index 0000000..ac66ec5 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_log.cc @@ -0,0 +1,6 @@ +#define SOKOL_IMPL +#include "sokol_log.h" + +void use_sokol_log(void) { + slog_func("bla", 1, 123, "123", 42, "bla.c", 0); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_main.c b/source/engine/thirdparty/sokol/tests/compile/sokol_main.c new file mode 100644 index 0000000..d1e34b6 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_main.c @@ -0,0 +1,13 @@ +#include "sokol_app.h" + +#if defined(SOKOL_DUMMY_BACKEND) +int main() { + return 0; +} +#else +sapp_desc sokol_main(int argc, char* argv[]) { + (void)argc; + (void)argv; + return (sapp_desc){0}; +} +#endif diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_main.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_main.cc new file mode 100644 index 0000000..c27ed3e --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_main.cc @@ -0,0 +1,7 @@ +#include "sokol_app.h" + +sapp_desc sokol_main(int argc, char* argv[]) { + (void)argc; + (void)argv; + return { }; +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_nuklear.c b/source/engine/thirdparty/sokol/tests/compile/sokol_nuklear.c new file mode 100644 index 0000000..e5311b4 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_nuklear.c @@ -0,0 +1,22 @@ +#include "sokol_app.h" +#include "sokol_gfx.h" + +// include nuklear.h before the sokol_nuklear.h implementation +#define NK_INCLUDE_FIXED_TYPES +#define NK_INCLUDE_STANDARD_IO +#define NK_INCLUDE_DEFAULT_ALLOCATOR +#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT +#define NK_INCLUDE_FONT_BAKING +#define NK_INCLUDE_DEFAULT_FONT +#define NK_INCLUDE_STANDARD_VARARGS +#include "nuklear.h" + +#define SOKOL_IMPL +#if defined(SOKOL_DUMMY_BACKEND) +#define SOKOL_NUKLEAR_NO_SOKOL_APP +#endif +#include "sokol_nuklear.h" + +void use_nuklear_impl(void) { + snk_setup(&(snk_desc_t){0}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_shape.c b/source/engine/thirdparty/sokol/tests/compile/sokol_shape.c new file mode 100644 index 0000000..72c48b6 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_shape.c @@ -0,0 +1,7 @@ +#include "sokol_gfx.h" +#define SOKOL_IMPL +#include "sokol_shape.h" + +void use_shape_impl(void) { + sshape_plane_sizes(10); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_shape.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_shape.cc new file mode 100644 index 0000000..0013ea6 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_shape.cc @@ -0,0 +1,7 @@ +#include "sokol_gfx.h" +#define SOKOL_IMPL +#include "sokol_shape.h" + +void use_shape_impl() { + sshape_plane_sizes(10); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_spine.c b/source/engine/thirdparty/sokol/tests/compile/sokol_spine.c new file mode 100644 index 0000000..4669003 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_spine.c @@ -0,0 +1,8 @@ +#include "sokol_gfx.h" +#define SOKOL_IMPL +#include "spine/spine.h" +#include "sokol_spine.h" + +void use_sspine_impl(void) { + sspine_setup(&(sspine_desc){0}); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_spine.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_spine.cc new file mode 100644 index 0000000..e484dbe --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_spine.cc @@ -0,0 +1,9 @@ +#include "sokol_gfx.h" +#define SOKOL_IMPL +#include "spine/spine.h" +#include "sokol_spine.h" + +void use_sspine_impl(void) { + const sspine_desc desc = {}; + sspine_setup(&desc); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_time.c b/source/engine/thirdparty/sokol/tests/compile/sokol_time.c new file mode 100644 index 0000000..416e500 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_time.c @@ -0,0 +1,6 @@ +#define SOKOL_IMPL +#include "sokol_time.h" + +void use_time_impl(void) { + stm_setup(); +} diff --git a/source/engine/thirdparty/sokol/tests/compile/sokol_time.cc b/source/engine/thirdparty/sokol/tests/compile/sokol_time.cc new file mode 100644 index 0000000..f0c19c9 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/compile/sokol_time.cc @@ -0,0 +1,7 @@ +#define SOKOL_IMPL +#include "sokol_time.h" + +void use_time_impl() { + stm_setup(); +} + diff --git a/source/engine/thirdparty/sokol/tests/ext/CMakeLists.txt b/source/engine/thirdparty/sokol/tests/ext/CMakeLists.txt new file mode 100644 index 0000000..4ded7c4 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/ext/CMakeLists.txt @@ -0,0 +1,97 @@ +# external dependencies + +# NOTE FetchContent is so frigging slow that we just run git directly +set(cimgui_dir ${CMAKE_BINARY_DIR}/../_deps/cimgui) +set(spineruntimes_dir ${CMAKE_BINARY_DIR}/../_deps/spineruntimes) + +if (IS_DIRECTORY ${cimgui_dir}) + message("### ${cimgui_dir} exists...") +else() + message("### Fetching cimgui to ${cimgui_dir} (this may take a while...)") + execute_process(COMMAND git clone --depth=1 --recursive https://github.com/fips-libs/fips-cimgui ${cimgui_dir}) +endif() +if (IS_DIRECTORY ${spineruntimes_dir}) + message("### ${spineruntimes_dir} exists...") +else() + message("### Fetching spine runtimes to ${spineruntimes_dir} (this may take a while...)") + execute_process(COMMAND git clone --depth=1 --recursive https://github.com/EsotericSoftware/spine-runtimes ${spineruntimes_dir}) +endif() + +add_library(cimgui + ${cimgui_dir}/cimgui/cimgui.cpp + ${cimgui_dir}/cimgui/imgui/imgui.cpp + ${cimgui_dir}/cimgui/imgui/imgui_demo.cpp + ${cimgui_dir}/cimgui/imgui/imgui_draw.cpp + ${cimgui_dir}/cimgui/imgui/imgui_tables.cpp + ${cimgui_dir}/cimgui/imgui/imgui_widgets.cpp) +target_include_directories(cimgui SYSTEM PUBLIC ${cimgui_dir}) + +add_library(imgui + ${cimgui_dir}/cimgui/imgui/imgui.cpp + ${cimgui_dir}/cimgui/imgui/imgui_demo.cpp + ${cimgui_dir}/cimgui/imgui/imgui_draw.cpp + ${cimgui_dir}/cimgui/imgui/imgui_tables.cpp + ${cimgui_dir}/cimgui/imgui/imgui_widgets.cpp) +target_include_directories(imgui SYSTEM PUBLIC ${cimgui_dir}/cimgui/imgui) + +add_library(spine + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Animation.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/AnimationState.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/AnimationStateData.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Array.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Atlas.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/AtlasAttachmentLoader.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Attachment.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/AttachmentLoader.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Bone.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/BoneData.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/BoundingBoxAttachment.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/ClippingAttachment.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Color.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Debug.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Event.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/EventData.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/IkConstraint.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/IkConstraintData.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Json.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Json.h + ${spineruntimes_dir}/spine-c/spine-c/src/spine/MeshAttachment.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/PathAttachment.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/PathConstraint.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/PathConstraintData.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/PointAttachment.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/RegionAttachment.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Sequence.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Skeleton.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/SkeletonBinary.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/SkeletonBounds.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/SkeletonClipping.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/SkeletonData.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/SkeletonJson.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Skin.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Slot.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/SlotData.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/TransformConstraint.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/TransformConstraintData.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/Triangulator.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/VertexAttachment.c + ${spineruntimes_dir}/spine-c/spine-c/src/spine/extension.c) +target_include_directories(spine SYSTEM PUBLIC ${spineruntimes_dir}/spine-c/spine-c/include) +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + target_compile_options(spine PRIVATE /wd4267 /wd4244) # conversion from 'x' to 'y' possible loss of data +endif() +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(spine PRIVATE -Wno-shorten-64-to-32) +endif() + +file(COPY ${spineruntimes_dir}/examples/spineboy/export/spineboy-pro.json DESTINATION ${CMAKE_BINARY_DIR}) +file(COPY ${spineruntimes_dir}/examples/spineboy/export/spineboy-pro.skel DESTINATION ${CMAKE_BINARY_DIR}) +file(COPY ${spineruntimes_dir}/examples/spineboy/export/spineboy.atlas DESTINATION ${CMAKE_BINARY_DIR}) +file(COPY ${spineruntimes_dir}/examples/spineboy/export/spineboy.png DESTINATION ${CMAKE_BINARY_DIR}) + +file(COPY ${spineruntimes_dir}/examples/spineboy/export/spineboy-pro.json DESTINATION ${CMAKE_BINARY_DIR}/Debug) +file(COPY ${spineruntimes_dir}/examples/spineboy/export/spineboy-pro.skel DESTINATION ${CMAKE_BINARY_DIR}/Debug) +file(COPY ${spineruntimes_dir}/examples/spineboy/export/spineboy.atlas DESTINATION ${CMAKE_BINARY_DIR}/Debug) +file(COPY ${spineruntimes_dir}/examples/spineboy/export/spineboy.png DESTINATION ${CMAKE_BINARY_DIR}/Debug) + +add_library(nuklear nuklear.c) diff --git a/source/engine/thirdparty/sokol/tests/ext/fontstash.h b/source/engine/thirdparty/sokol/tests/ext/fontstash.h new file mode 100644 index 0000000..a09e6f1 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/ext/fontstash.h @@ -0,0 +1,1714 @@ +// +// NOTE sokol: all IO functions have been removed +// +// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef FONS_H +#define FONS_H + +#ifdef __cplusplus +extern "C" { +#endif + +// To make the implementation private to the file that generates the implementation +#ifdef FONS_STATIC +#define FONS_DEF static +#else +#define FONS_DEF extern +#endif + +#define FONS_INVALID -1 + +enum FONSflags { + FONS_ZERO_TOPLEFT = 1, + FONS_ZERO_BOTTOMLEFT = 2, +}; + +enum FONSalign { + // Horizontal align + FONS_ALIGN_LEFT = 1<<0, // Default + FONS_ALIGN_CENTER = 1<<1, + FONS_ALIGN_RIGHT = 1<<2, + // Vertical align + FONS_ALIGN_TOP = 1<<3, + FONS_ALIGN_MIDDLE = 1<<4, + FONS_ALIGN_BOTTOM = 1<<5, + FONS_ALIGN_BASELINE = 1<<6, // Default +}; + +enum FONSerrorCode { + // Font atlas is full. + FONS_ATLAS_FULL = 1, + // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE. + FONS_SCRATCH_FULL = 2, + // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. + FONS_STATES_OVERFLOW = 3, + // Trying to pop too many states fonsPopState(). + FONS_STATES_UNDERFLOW = 4, +}; + +struct FONSparams { + int width, height; + unsigned char flags; + void* userPtr; + int (*renderCreate)(void* uptr, int width, int height); + int (*renderResize)(void* uptr, int width, int height); + void (*renderUpdate)(void* uptr, int* rect, const unsigned char* data); + void (*renderDraw)(void* uptr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts); + void (*renderDelete)(void* uptr); +}; +typedef struct FONSparams FONSparams; + +struct FONSquad +{ + float x0,y0,s0,t0; + float x1,y1,s1,t1; +}; +typedef struct FONSquad FONSquad; + +struct FONStextIter { + float x, y, nextx, nexty, scale, spacing; + unsigned int codepoint; + short isize, iblur; + struct FONSfont* font; + int prevGlyphIndex; + const char* str; + const char* next; + const char* end; + unsigned int utf8state; +}; +typedef struct FONStextIter FONStextIter; + +typedef struct FONScontext FONScontext; + +// Contructor and destructor. +FONS_DEF FONScontext* fonsCreateInternal(FONSparams* params); +FONS_DEF void fonsDeleteInternal(FONScontext* s); + +FONS_DEF void fonsSetErrorCallback(FONScontext* s, void (*callback)(void* uptr, int error, int val), void* uptr); +// Returns current atlas size. +FONS_DEF void fonsGetAtlasSize(FONScontext* s, int* width, int* height); +// Expands the atlas size. +FONS_DEF int fonsExpandAtlas(FONScontext* s, int width, int height); +// Resets the whole stash. +FONS_DEF int fonsResetAtlas(FONScontext* stash, int width, int height); + +// Add fonts +FONS_DEF int fonsGetFontByName(FONScontext* s, const char* name); +FONS_DEF int fonsAddFallbackFont(FONScontext* stash, int base, int fallback); + +// State handling +FONS_DEF void fonsPushState(FONScontext* s); +FONS_DEF void fonsPopState(FONScontext* s); +FONS_DEF void fonsClearState(FONScontext* s); + +// State setting +FONS_DEF void fonsSetSize(FONScontext* s, float size); +FONS_DEF void fonsSetColor(FONScontext* s, unsigned int color); +FONS_DEF void fonsSetSpacing(FONScontext* s, float spacing); +FONS_DEF void fonsSetBlur(FONScontext* s, float blur); +FONS_DEF void fonsSetAlign(FONScontext* s, int align); +FONS_DEF void fonsSetFont(FONScontext* s, int font); + +// Draw text +FONS_DEF float fonsDrawText(FONScontext* s, float x, float y, const char* string, const char* end); + +// Measure text +FONS_DEF float fonsTextBounds(FONScontext* s, float x, float y, const char* string, const char* end, float* bounds); +FONS_DEF void fonsLineBounds(FONScontext* s, float y, float* miny, float* maxy); +FONS_DEF void fonsVertMetrics(FONScontext* s, float* ascender, float* descender, float* lineh); + +// Text iterator +FONS_DEF int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, float x, float y, const char* str, const char* end); +FONS_DEF int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, struct FONSquad* quad); + +// Pull texture changes +FONS_DEF const unsigned char* fonsGetTextureData(FONScontext* stash, int* width, int* height); +FONS_DEF int fonsValidateTexture(FONScontext* s, int* dirty); + +// Draws the stash texture for debugging +FONS_DEF void fonsDrawDebug(FONScontext* s, float x, float y); + +#ifdef __cplusplus +} +#endif + +#endif // FONS_H + + +#ifdef FONTSTASH_IMPLEMENTATION + +#define FONS_NOTUSED(v) (void)sizeof(v) + +#ifdef FONS_USE_FREETYPE + +#include +#include FT_FREETYPE_H +#include FT_ADVANCES_H +#include + +struct FONSttFontImpl { + FT_Face font; +}; +typedef struct FONSttFontImpl FONSttFontImpl; + +static FT_Library ftLibrary; + +static int fons__tt_init() +{ + FT_Error ftError; + FONS_NOTUSED(context); + ftError = FT_Init_FreeType(&ftLibrary); + return ftError == 0; +} + +static int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize) +{ + FT_Error ftError; + FONS_NOTUSED(context); + + //font->font.userdata = stash; + ftError = FT_New_Memory_Face(ftLibrary, (const FT_Byte*)data, dataSize, 0, &font->font); + return ftError == 0; +} + +static void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) +{ + *ascent = font->font->ascender; + *descent = font->font->descender; + *lineGap = font->font->height - (*ascent - *descent); +} + +static float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size) +{ + return size / (font->font->ascender - font->font->descender); +} + +static int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint) +{ + return FT_Get_Char_Index(font->font, codepoint); +} + +static int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale, + int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) +{ + FT_Error ftError; + FT_GlyphSlot ftGlyph; + FT_Fixed advFixed; + FONS_NOTUSED(scale); + + ftError = FT_Set_Pixel_Sizes(font->font, 0, (FT_UInt)(size * (float)font->font->units_per_EM / (float)(font->font->ascender - font->font->descender))); + if (ftError) return 0; + ftError = FT_Load_Glyph(font->font, glyph, FT_LOAD_RENDER); + if (ftError) return 0; + ftError = FT_Get_Advance(font->font, glyph, FT_LOAD_NO_SCALE, &advFixed); + if (ftError) return 0; + ftGlyph = font->font->glyph; + *advance = (int)advFixed; + *lsb = (int)ftGlyph->metrics.horiBearingX; + *x0 = ftGlyph->bitmap_left; + *x1 = *x0 + ftGlyph->bitmap.width; + *y0 = -ftGlyph->bitmap_top; + *y1 = *y0 + ftGlyph->bitmap.rows; + return 1; +} + +static void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, + float scaleX, float scaleY, int glyph) +{ + FT_GlyphSlot ftGlyph = font->font->glyph; + int ftGlyphOffset = 0; + int x, y; + FONS_NOTUSED(outWidth); + FONS_NOTUSED(outHeight); + FONS_NOTUSED(scaleX); + FONS_NOTUSED(scaleY); + FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap + + for ( y = 0; y < ftGlyph->bitmap.rows; y++ ) { + for ( x = 0; x < ftGlyph->bitmap.width; x++ ) { + output[(y * outStride) + x] = ftGlyph->bitmap.buffer[ftGlyphOffset++]; + } + } +} + +static int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) +{ + FT_Vector ftKerning; + FT_Get_Kerning(font->font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); + return (int)((ftKerning.x + 32) >> 6); // Round up and convert to integer +} + +#else + +#define STB_TRUETYPE_IMPLEMENTATION +#define STBTT_STATIC +static void* fons__tmpalloc(size_t size, void* up); +static void fons__tmpfree(void* ptr, void* up); +#define STBTT_malloc(x,u) fons__tmpalloc(x,u) +#define STBTT_free(x,u) fons__tmpfree(x,u) +#include "stb_truetype.h" + +struct FONSttFontImpl { + stbtt_fontinfo font; +}; +typedef struct FONSttFontImpl FONSttFontImpl; + +static int fons__tt_init(FONScontext *context) +{ + FONS_NOTUSED(context); + return 1; +} + +static int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize) +{ + int stbError; + FONS_NOTUSED(dataSize); + + font->font.userdata = context; + stbError = stbtt_InitFont(&font->font, data, 0); + return stbError; +} + +static void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) +{ + stbtt_GetFontVMetrics(&font->font, ascent, descent, lineGap); +} + +static float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size) +{ + return stbtt_ScaleForPixelHeight(&font->font, size); +} + +static int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint) +{ + return stbtt_FindGlyphIndex(&font->font, codepoint); +} + +static int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale, + int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) +{ + FONS_NOTUSED(size); + stbtt_GetGlyphHMetrics(&font->font, glyph, advance, lsb); + stbtt_GetGlyphBitmapBox(&font->font, glyph, scale, scale, x0, y0, x1, y1); + return 1; +} + +static void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, + float scaleX, float scaleY, int glyph) +{ + stbtt_MakeGlyphBitmap(&font->font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); +} + +static int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) +{ + return stbtt_GetGlyphKernAdvance(&font->font, glyph1, glyph2); +} + +#endif + +#ifndef FONS_SCRATCH_BUF_SIZE +# define FONS_SCRATCH_BUF_SIZE 64000 +#endif +#ifndef FONS_HASH_LUT_SIZE +# define FONS_HASH_LUT_SIZE 256 +#endif +#ifndef FONS_INIT_FONTS +# define FONS_INIT_FONTS 4 +#endif +#ifndef FONS_INIT_GLYPHS +# define FONS_INIT_GLYPHS 256 +#endif +#ifndef FONS_INIT_ATLAS_NODES +# define FONS_INIT_ATLAS_NODES 256 +#endif +#ifndef FONS_VERTEX_COUNT +# define FONS_VERTEX_COUNT 1024 +#endif +#ifndef FONS_MAX_STATES +# define FONS_MAX_STATES 20 +#endif +#ifndef FONS_MAX_FALLBACKS +# define FONS_MAX_FALLBACKS 20 +#endif + +static unsigned int fons__hashint(unsigned int a) +{ + a += ~(a<<15); + a ^= (a>>10); + a += (a<<3); + a ^= (a>>6); + a += ~(a<<11); + a ^= (a>>16); + return a; +} + +static int fons__mini(int a, int b) +{ + return a < b ? a : b; +} + +static int fons__maxi(int a, int b) +{ + return a > b ? a : b; +} + +struct FONSglyph +{ + unsigned int codepoint; + int index; + int next; + short size, blur; + short x0,y0,x1,y1; + short xadv,xoff,yoff; +}; +typedef struct FONSglyph FONSglyph; + +struct FONSfont +{ + FONSttFontImpl font; + char name[64]; + unsigned char* data; + int dataSize; + unsigned char freeData; + float ascender; + float descender; + float lineh; + FONSglyph* glyphs; + int cglyphs; + int nglyphs; + int lut[FONS_HASH_LUT_SIZE]; + int fallbacks[FONS_MAX_FALLBACKS]; + int nfallbacks; +}; +typedef struct FONSfont FONSfont; + +struct FONSstate +{ + int font; + int align; + float size; + unsigned int color; + float blur; + float spacing; +}; +typedef struct FONSstate FONSstate; + +struct FONSatlasNode { + short x, y, width; +}; +typedef struct FONSatlasNode FONSatlasNode; + +struct FONSatlas +{ + int width, height; + FONSatlasNode* nodes; + int nnodes; + int cnodes; +}; +typedef struct FONSatlas FONSatlas; + +struct FONScontext +{ + FONSparams params; + float itw,ith; + unsigned char* texData; + int dirtyRect[4]; + FONSfont** fonts; + FONSatlas* atlas; + int cfonts; + int nfonts; + float verts[FONS_VERTEX_COUNT*2]; + float tcoords[FONS_VERTEX_COUNT*2]; + unsigned int colors[FONS_VERTEX_COUNT]; + int nverts; + unsigned char* scratch; + int nscratch; + FONSstate states[FONS_MAX_STATES]; + int nstates; + void (*handleError)(void* uptr, int error, int val); + void* errorUptr; +}; + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +static void* fons__tmpalloc(size_t size, void* up) +{ + unsigned char* ptr; + FONScontext* stash = (FONScontext*)up; + + // 16-byte align the returned pointer + size = (size + 0xf) & ~0xf; + + if (stash->nscratch+(int)size > FONS_SCRATCH_BUF_SIZE) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_SCRATCH_FULL, stash->nscratch+(int)size); + return NULL; + } + ptr = stash->scratch + stash->nscratch; + stash->nscratch += (int)size; + return ptr; +} + +static void fons__tmpfree(void* ptr, void* up) +{ + (void)ptr; + (void)up; + // empty +} + +#endif // STB_TRUETYPE_IMPLEMENTATION + +// Copyright (c) 2008-2010 Bjoern Hoehrmann +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + +#define FONS_UTF8_ACCEPT 0 +#define FONS_UTF8_REJECT 12 + +static unsigned int fons__decutf8(unsigned int* state, unsigned int* codep, unsigned int byte) +{ + static const unsigned char utf8d[] = { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, + }; + + unsigned int type = utf8d[byte]; + + *codep = (*state != FONS_UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +// Atlas based on Skyline Bin Packer by Jukka Jylänki + +static void fons__deleteAtlas(FONSatlas* atlas) +{ + if (atlas == NULL) return; + if (atlas->nodes != NULL) free(atlas->nodes); + free(atlas); +} + +static FONSatlas* fons__allocAtlas(int w, int h, int nnodes) +{ + FONSatlas* atlas = NULL; + + // Allocate memory for the font stash. + atlas = (FONSatlas*)malloc(sizeof(FONSatlas)); + if (atlas == NULL) goto error; + memset(atlas, 0, sizeof(FONSatlas)); + + atlas->width = w; + atlas->height = h; + + // Allocate space for skyline nodes + atlas->nodes = (FONSatlasNode*)malloc(sizeof(FONSatlasNode) * nnodes); + if (atlas->nodes == NULL) goto error; + memset(atlas->nodes, 0, sizeof(FONSatlasNode) * nnodes); + atlas->nnodes = 0; + atlas->cnodes = nnodes; + + // Init root node. + atlas->nodes[0].x = 0; + atlas->nodes[0].y = 0; + atlas->nodes[0].width = (short)w; + atlas->nnodes++; + + return atlas; + +error: + if (atlas) fons__deleteAtlas(atlas); + return NULL; +} + +static int fons__atlasInsertNode(FONSatlas* atlas, int idx, int x, int y, int w) +{ + int i; + // Insert node + if (atlas->nnodes+1 > atlas->cnodes) { + atlas->cnodes = atlas->cnodes == 0 ? 8 : atlas->cnodes * 2; + atlas->nodes = (FONSatlasNode*)realloc(atlas->nodes, sizeof(FONSatlasNode) * atlas->cnodes); + if (atlas->nodes == NULL) + return 0; + } + for (i = atlas->nnodes; i > idx; i--) + atlas->nodes[i] = atlas->nodes[i-1]; + atlas->nodes[idx].x = (short)x; + atlas->nodes[idx].y = (short)y; + atlas->nodes[idx].width = (short)w; + atlas->nnodes++; + + return 1; +} + +static void fons__atlasRemoveNode(FONSatlas* atlas, int idx) +{ + int i; + if (atlas->nnodes == 0) return; + for (i = idx; i < atlas->nnodes-1; i++) + atlas->nodes[i] = atlas->nodes[i+1]; + atlas->nnodes--; +} + +static void fons__atlasExpand(FONSatlas* atlas, int w, int h) +{ + // Insert node for empty space + if (w > atlas->width) + fons__atlasInsertNode(atlas, atlas->nnodes, atlas->width, 0, w - atlas->width); + atlas->width = w; + atlas->height = h; +} + +static void fons__atlasReset(FONSatlas* atlas, int w, int h) +{ + atlas->width = w; + atlas->height = h; + atlas->nnodes = 0; + + // Init root node. + atlas->nodes[0].x = 0; + atlas->nodes[0].y = 0; + atlas->nodes[0].width = (short)w; + atlas->nnodes++; +} + +static int fons__atlasAddSkylineLevel(FONSatlas* atlas, int idx, int x, int y, int w, int h) +{ + int i; + + // Insert new node + if (fons__atlasInsertNode(atlas, idx, x, y+h, w) == 0) + return 0; + + // Delete skyline segments that fall under the shadow of the new segment. + for (i = idx+1; i < atlas->nnodes; i++) { + if (atlas->nodes[i].x < atlas->nodes[i-1].x + atlas->nodes[i-1].width) { + int shrink = atlas->nodes[i-1].x + atlas->nodes[i-1].width - atlas->nodes[i].x; + atlas->nodes[i].x += (short)shrink; + atlas->nodes[i].width -= (short)shrink; + if (atlas->nodes[i].width <= 0) { + fons__atlasRemoveNode(atlas, i); + i--; + } else { + break; + } + } else { + break; + } + } + + // Merge same height skyline segments that are next to each other. + for (i = 0; i < atlas->nnodes-1; i++) { + if (atlas->nodes[i].y == atlas->nodes[i+1].y) { + atlas->nodes[i].width += atlas->nodes[i+1].width; + fons__atlasRemoveNode(atlas, i+1); + i--; + } + } + + return 1; +} + +static int fons__atlasRectFits(FONSatlas* atlas, int i, int w, int h) +{ + // Checks if there is enough space at the location of skyline span 'i', + // and return the max height of all skyline spans under that at that location, + // (think tetris block being dropped at that position). Or -1 if no space found. + int x = atlas->nodes[i].x; + int y = atlas->nodes[i].y; + int spaceLeft; + if (x + w > atlas->width) + return -1; + spaceLeft = w; + while (spaceLeft > 0) { + if (i == atlas->nnodes) return -1; + y = fons__maxi(y, atlas->nodes[i].y); + if (y + h > atlas->height) return -1; + spaceLeft -= atlas->nodes[i].width; + ++i; + } + return y; +} + +static int fons__atlasAddRect(FONSatlas* atlas, int rw, int rh, int* rx, int* ry) +{ + int besth = atlas->height, bestw = atlas->width, besti = -1; + int bestx = -1, besty = -1, i; + + // Bottom left fit heuristic. + for (i = 0; i < atlas->nnodes; i++) { + int y = fons__atlasRectFits(atlas, i, rw, rh); + if (y != -1) { + if (y + rh < besth || (y + rh == besth && atlas->nodes[i].width < bestw)) { + besti = i; + bestw = atlas->nodes[i].width; + besth = y + rh; + bestx = atlas->nodes[i].x; + besty = y; + } + } + } + + if (besti == -1) + return 0; + + // Perform the actual packing. + if (fons__atlasAddSkylineLevel(atlas, besti, bestx, besty, rw, rh) == 0) + return 0; + + *rx = bestx; + *ry = besty; + + return 1; +} + +static void fons__addWhiteRect(FONScontext* stash, int w, int h) +{ + int x, y, gx, gy; + unsigned char* dst; + if (fons__atlasAddRect(stash->atlas, w, h, &gx, &gy) == 0) + return; + + // Rasterize + dst = &stash->texData[gx + gy * stash->params.width]; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) + dst[x] = 0xff; + dst += stash->params.width; + } + + stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], gx); + stash->dirtyRect[1] = fons__mini(stash->dirtyRect[1], gy); + stash->dirtyRect[2] = fons__maxi(stash->dirtyRect[2], gx+w); + stash->dirtyRect[3] = fons__maxi(stash->dirtyRect[3], gy+h); +} + +FONScontext* fonsCreateInternal(FONSparams* params) +{ + FONScontext* stash = NULL; + + // Allocate memory for the font stash. + stash = (FONScontext*)malloc(sizeof(FONScontext)); + if (stash == NULL) goto error; + memset(stash, 0, sizeof(FONScontext)); + + stash->params = *params; + + // Allocate scratch buffer. + stash->scratch = (unsigned char*)malloc(FONS_SCRATCH_BUF_SIZE); + if (stash->scratch == NULL) goto error; + + // Initialize implementation library + if (!fons__tt_init(stash)) goto error; + + if (stash->params.renderCreate != NULL) { + if (stash->params.renderCreate(stash->params.userPtr, stash->params.width, stash->params.height) == 0) + goto error; + } + + stash->atlas = fons__allocAtlas(stash->params.width, stash->params.height, FONS_INIT_ATLAS_NODES); + if (stash->atlas == NULL) goto error; + + // Allocate space for fonts. + stash->fonts = (FONSfont**)malloc(sizeof(FONSfont*) * FONS_INIT_FONTS); + if (stash->fonts == NULL) goto error; + memset(stash->fonts, 0, sizeof(FONSfont*) * FONS_INIT_FONTS); + stash->cfonts = FONS_INIT_FONTS; + stash->nfonts = 0; + + // Create texture for the cache. + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + stash->texData = (unsigned char*)malloc(stash->params.width * stash->params.height); + if (stash->texData == NULL) goto error; + memset(stash->texData, 0, stash->params.width * stash->params.height); + + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + + // Add white rect at 0,0 for debug drawing. + fons__addWhiteRect(stash, 2,2); + + fonsPushState(stash); + fonsClearState(stash); + + return stash; + +error: + fonsDeleteInternal(stash); + return NULL; +} + +static FONSstate* fons__getState(FONScontext* stash) +{ + return &stash->states[stash->nstates-1]; +} + +int fonsAddFallbackFont(FONScontext* stash, int base, int fallback) +{ + FONSfont* baseFont = stash->fonts[base]; + if (baseFont->nfallbacks < FONS_MAX_FALLBACKS) { + baseFont->fallbacks[baseFont->nfallbacks++] = fallback; + return 1; + } + return 0; +} + +void fonsSetSize(FONScontext* stash, float size) +{ + fons__getState(stash)->size = size; +} + +void fonsSetColor(FONScontext* stash, unsigned int color) +{ + fons__getState(stash)->color = color; +} + +void fonsSetSpacing(FONScontext* stash, float spacing) +{ + fons__getState(stash)->spacing = spacing; +} + +void fonsSetBlur(FONScontext* stash, float blur) +{ + fons__getState(stash)->blur = blur; +} + +void fonsSetAlign(FONScontext* stash, int align) +{ + fons__getState(stash)->align = align; +} + +void fonsSetFont(FONScontext* stash, int font) +{ + fons__getState(stash)->font = font; +} + +void fonsPushState(FONScontext* stash) +{ + if (stash->nstates >= FONS_MAX_STATES) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_STATES_OVERFLOW, 0); + return; + } + if (stash->nstates > 0) + memcpy(&stash->states[stash->nstates], &stash->states[stash->nstates-1], sizeof(FONSstate)); + stash->nstates++; +} + +void fonsPopState(FONScontext* stash) +{ + if (stash->nstates <= 1) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_STATES_UNDERFLOW, 0); + return; + } + stash->nstates--; +} + +void fonsClearState(FONScontext* stash) +{ + FONSstate* state = fons__getState(stash); + state->size = 12.0f; + state->color = 0xffffffff; + state->font = 0; + state->blur = 0; + state->spacing = 0; + state->align = FONS_ALIGN_LEFT | FONS_ALIGN_BASELINE; +} + +static void fons__freeFont(FONSfont* font) +{ + if (font == NULL) return; + if (font->glyphs) free(font->glyphs); + if (font->freeData && font->data) free(font->data); + free(font); +} + +static int fons__allocFont(FONScontext* stash) +{ + FONSfont* font = NULL; + if (stash->nfonts+1 > stash->cfonts) { + stash->cfonts = stash->cfonts == 0 ? 8 : stash->cfonts * 2; + stash->fonts = (FONSfont**)realloc(stash->fonts, sizeof(FONSfont*) * stash->cfonts); + if (stash->fonts == NULL) + return -1; + } + font = (FONSfont*)malloc(sizeof(FONSfont)); + if (font == NULL) goto error; + memset(font, 0, sizeof(FONSfont)); + + font->glyphs = (FONSglyph*)malloc(sizeof(FONSglyph) * FONS_INIT_GLYPHS); + if (font->glyphs == NULL) goto error; + font->cglyphs = FONS_INIT_GLYPHS; + font->nglyphs = 0; + + stash->fonts[stash->nfonts++] = font; + return stash->nfonts-1; + +error: + fons__freeFont(font); + + return FONS_INVALID; +} + +int fonsAddFontMem(FONScontext* stash, const char* name, unsigned char* data, int dataSize, int freeData) +{ + int i, ascent, descent, fh, lineGap; + FONSfont* font; + + int idx = fons__allocFont(stash); + if (idx == FONS_INVALID) + return FONS_INVALID; + + font = stash->fonts[idx]; + + strncpy(font->name, name, sizeof(font->name)); + font->name[sizeof(font->name)-1] = '\0'; + + // Init hash lookup. + for (i = 0; i < FONS_HASH_LUT_SIZE; ++i) + font->lut[i] = -1; + + // Read in the font data. + font->dataSize = dataSize; + font->data = data; + font->freeData = (unsigned char)freeData; + + // Init font + stash->nscratch = 0; + if (!fons__tt_loadFont(stash, &font->font, data, dataSize)) goto error; + + // Store normalized line height. The real line height is got + // by multiplying the lineh by font size. + fons__tt_getFontVMetrics( &font->font, &ascent, &descent, &lineGap); + fh = ascent - descent; + font->ascender = (float)ascent / (float)fh; + font->descender = (float)descent / (float)fh; + font->lineh = (float)(fh + lineGap) / (float)fh; + + return idx; + +error: + fons__freeFont(font); + stash->nfonts--; + return FONS_INVALID; +} + +int fonsGetFontByName(FONScontext* s, const char* name) +{ + int i; + for (i = 0; i < s->nfonts; i++) { + if (strcmp(s->fonts[i]->name, name) == 0) + return i; + } + return FONS_INVALID; +} + + +static FONSglyph* fons__allocGlyph(FONSfont* font) +{ + if (font->nglyphs+1 > font->cglyphs) { + font->cglyphs = font->cglyphs == 0 ? 8 : font->cglyphs * 2; + font->glyphs = (FONSglyph*)realloc(font->glyphs, sizeof(FONSglyph) * font->cglyphs); + if (font->glyphs == NULL) return NULL; + } + font->nglyphs++; + return &font->glyphs[font->nglyphs-1]; +} + + +// Based on Exponential blur, Jani Huhtanen, 2006 + +#define APREC 16 +#define ZPREC 7 + +static void fons__blurCols(unsigned char* dst, int w, int h, int dstStride, int alpha) +{ + int x, y; + for (y = 0; y < h; y++) { + int z = 0; // force zero border + for (x = 1; x < w; x++) { + z += (alpha * (((int)(dst[x]) << ZPREC) - z)) >> APREC; + dst[x] = (unsigned char)(z >> ZPREC); + } + dst[w-1] = 0; // force zero border + z = 0; + for (x = w-2; x >= 0; x--) { + z += (alpha * (((int)(dst[x]) << ZPREC) - z)) >> APREC; + dst[x] = (unsigned char)(z >> ZPREC); + } + dst[0] = 0; // force zero border + dst += dstStride; + } +} + +static void fons__blurRows(unsigned char* dst, int w, int h, int dstStride, int alpha) +{ + int x, y; + for (x = 0; x < w; x++) { + int z = 0; // force zero border + for (y = dstStride; y < h*dstStride; y += dstStride) { + z += (alpha * (((int)(dst[y]) << ZPREC) - z)) >> APREC; + dst[y] = (unsigned char)(z >> ZPREC); + } + dst[(h-1)*dstStride] = 0; // force zero border + z = 0; + for (y = (h-2)*dstStride; y >= 0; y -= dstStride) { + z += (alpha * (((int)(dst[y]) << ZPREC) - z)) >> APREC; + dst[y] = (unsigned char)(z >> ZPREC); + } + dst[0] = 0; // force zero border + dst++; + } +} + + +static void fons__blur(FONScontext* stash, unsigned char* dst, int w, int h, int dstStride, int blur) +{ + int alpha; + float sigma; + (void)stash; + + if (blur < 1) + return; + // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) + sigma = (float)blur * 0.57735f; // 1 / sqrt(3) + alpha = (int)((1< 20) iblur = 20; + pad = iblur+2; + + // Reset allocator. + stash->nscratch = 0; + + // Find code point and size. + h = fons__hashint(codepoint) & (FONS_HASH_LUT_SIZE-1); + i = font->lut[h]; + while (i != -1) { + if (font->glyphs[i].codepoint == codepoint && font->glyphs[i].size == isize && font->glyphs[i].blur == iblur) + return &font->glyphs[i]; + i = font->glyphs[i].next; + } + + // Could not find glyph, create it. + g = fons__tt_getGlyphIndex(&font->font, codepoint); + // Try to find the glyph in fallback fonts. + if (g == 0) { + for (i = 0; i < font->nfallbacks; ++i) { + FONSfont* fallbackFont = stash->fonts[font->fallbacks[i]]; + int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont->font, codepoint); + if (fallbackIndex != 0) { + g = fallbackIndex; + renderFont = fallbackFont; + break; + } + } + // It is possible that we did not find a fallback glyph. + // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph. + } + scale = fons__tt_getPixelHeightScale(&renderFont->font, size); + fons__tt_buildGlyphBitmap(&renderFont->font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); + gw = x1-x0 + pad*2; + gh = y1-y0 + pad*2; + + // Find free spot for the rect in the atlas + added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); + if (added == 0 && stash->handleError != NULL) { + // Atlas is full, let the user to resize the atlas (or not), and try again. + stash->handleError(stash->errorUptr, FONS_ATLAS_FULL, 0); + added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); + } + if (added == 0) return NULL; + + // Init glyph. + glyph = fons__allocGlyph(font); + glyph->codepoint = codepoint; + glyph->size = isize; + glyph->blur = iblur; + glyph->index = g; + glyph->x0 = (short)gx; + glyph->y0 = (short)gy; + glyph->x1 = (short)(glyph->x0+gw); + glyph->y1 = (short)(glyph->y0+gh); + glyph->xadv = (short)(scale * advance * 10.0f); + glyph->xoff = (short)(x0 - pad); + glyph->yoff = (short)(y0 - pad); + glyph->next = 0; + + // Insert char to hash lookup. + glyph->next = font->lut[h]; + font->lut[h] = font->nglyphs-1; + + // Rasterize + dst = &stash->texData[(glyph->x0+pad) + (glyph->y0+pad) * stash->params.width]; + fons__tt_renderGlyphBitmap(&renderFont->font, dst, gw-pad*2,gh-pad*2, stash->params.width, scale,scale, g); + + // Make sure there is one pixel empty border. + dst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + for (y = 0; y < gh; y++) { + dst[y*stash->params.width] = 0; + dst[gw-1 + y*stash->params.width] = 0; + } + for (x = 0; x < gw; x++) { + dst[x] = 0; + dst[x + (gh-1)*stash->params.width] = 0; + } + + // Debug code to color the glyph background +/* unsigned char* fdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + for (y = 0; y < gh; y++) { + for (x = 0; x < gw; x++) { + int a = (int)fdst[x+y*stash->params.width] + 20; + if (a > 255) a = 255; + fdst[x+y*stash->params.width] = a; + } + }*/ + + // Blur + if (iblur > 0) { + stash->nscratch = 0; + bdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + fons__blur(stash, bdst, gw,gh, stash->params.width, iblur); + } + + stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], glyph->x0); + stash->dirtyRect[1] = fons__mini(stash->dirtyRect[1], glyph->y0); + stash->dirtyRect[2] = fons__maxi(stash->dirtyRect[2], glyph->x1); + stash->dirtyRect[3] = fons__maxi(stash->dirtyRect[3], glyph->y1); + + return glyph; +} + +static void fons__getQuad(FONScontext* stash, FONSfont* font, + int prevGlyphIndex, FONSglyph* glyph, + float scale, float spacing, float* x, float* y, FONSquad* q) +{ + float rx,ry,xoff,yoff,x0,y0,x1,y1; + + if (prevGlyphIndex != -1) { + float adv = fons__tt_getGlyphKernAdvance(&font->font, prevGlyphIndex, glyph->index) * scale; + *x += (int)(adv + spacing + 0.5f); + } + + // Each glyph has 2px border to allow good interpolation, + // one pixel to prevent leaking, and one to allow good interpolation for rendering. + // Inset the texture region by one pixel for correct interpolation. + xoff = (short)(glyph->xoff+1); + yoff = (short)(glyph->yoff+1); + x0 = (float)(glyph->x0+1); + y0 = (float)(glyph->y0+1); + x1 = (float)(glyph->x1-1); + y1 = (float)(glyph->y1-1); + + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + rx = (float)(int)(*x + xoff); + ry = (float)(int)(*y + yoff); + + q->x0 = rx; + q->y0 = ry; + q->x1 = rx + x1 - x0; + q->y1 = ry + y1 - y0; + + q->s0 = x0 * stash->itw; + q->t0 = y0 * stash->ith; + q->s1 = x1 * stash->itw; + q->t1 = y1 * stash->ith; + } else { + rx = (float)(int)(*x + xoff); + ry = (float)(int)(*y - yoff); + + q->x0 = rx; + q->y0 = ry; + q->x1 = rx + x1 - x0; + q->y1 = ry - y1 + y0; + + q->s0 = x0 * stash->itw; + q->t0 = y0 * stash->ith; + q->s1 = x1 * stash->itw; + q->t1 = y1 * stash->ith; + } + + *x += (int)(glyph->xadv / 10.0f + 0.5f); +} + +static void fons__flush(FONScontext* stash) +{ + // Flush texture + if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) { + if (stash->params.renderUpdate != NULL) + stash->params.renderUpdate(stash->params.userPtr, stash->dirtyRect, stash->texData); + // Reset dirty rect + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + } + + // Flush triangles + if (stash->nverts > 0) { + if (stash->params.renderDraw != NULL) + stash->params.renderDraw(stash->params.userPtr, stash->verts, stash->tcoords, stash->colors, stash->nverts); + stash->nverts = 0; + } +} + +static __inline void fons__vertex(FONScontext* stash, float x, float y, float s, float t, unsigned int c) +{ + stash->verts[stash->nverts*2+0] = x; + stash->verts[stash->nverts*2+1] = y; + stash->tcoords[stash->nverts*2+0] = s; + stash->tcoords[stash->nverts*2+1] = t; + stash->colors[stash->nverts] = c; + stash->nverts++; +} + +static float fons__getVertAlign(FONScontext* stash, FONSfont* font, int align, short isize) +{ + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + if (align & FONS_ALIGN_TOP) { + return font->ascender * (float)isize/10.0f; + } else if (align & FONS_ALIGN_MIDDLE) { + return (font->ascender + font->descender) / 2.0f * (float)isize/10.0f; + } else if (align & FONS_ALIGN_BASELINE) { + return 0.0f; + } else if (align & FONS_ALIGN_BOTTOM) { + return font->descender * (float)isize/10.0f; + } + } else { + if (align & FONS_ALIGN_TOP) { + return -font->ascender * (float)isize/10.0f; + } else if (align & FONS_ALIGN_MIDDLE) { + return -(font->ascender + font->descender) / 2.0f * (float)isize/10.0f; + } else if (align & FONS_ALIGN_BASELINE) { + return 0.0f; + } else if (align & FONS_ALIGN_BOTTOM) { + return -font->descender * (float)isize/10.0f; + } + } + return 0.0; +} + +FONS_DEF float fonsDrawText(FONScontext* stash, + float x, float y, + const char* str, const char* end) +{ + FONSstate* state = fons__getState(stash); + unsigned int codepoint; + unsigned int utf8state = 0; + FONSglyph* glyph = NULL; + FONSquad q; + int prevGlyphIndex = -1; + short isize = (short)(state->size*10.0f); + short iblur = (short)state->blur; + float scale; + FONSfont* font; + float width; + + if (stash == NULL) return x; + if (state->font < 0 || state->font >= stash->nfonts) return x; + font = stash->fonts[state->font]; + if (font->data == NULL) return x; + + scale = fons__tt_getPixelHeightScale(&font->font, (float)isize/10.0f); + + if (end == NULL) + end = str + strlen(str); + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width; + } else if (state->align & FONS_ALIGN_CENTER) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width * 0.5f; + } + // Align vertically. + y += fons__getVertAlign(stash, font, state->align, isize); + + for (; str != end; ++str) { + if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str)) + continue; + glyph = fons__getGlyph(stash, font, codepoint, isize, iblur); + if (glyph != NULL) { + fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q); + + if (stash->nverts+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + fons__vertex(stash, q.x0, q.y0, q.s0, q.t0, state->color); + fons__vertex(stash, q.x1, q.y1, q.s1, q.t1, state->color); + fons__vertex(stash, q.x1, q.y0, q.s1, q.t0, state->color); + + fons__vertex(stash, q.x0, q.y0, q.s0, q.t0, state->color); + fons__vertex(stash, q.x0, q.y1, q.s0, q.t1, state->color); + fons__vertex(stash, q.x1, q.y1, q.s1, q.t1, state->color); + } + prevGlyphIndex = glyph != NULL ? glyph->index : -1; + } + fons__flush(stash); + + return x; +} + +FONS_DEF int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, + float x, float y, const char* str, const char* end) +{ + FONSstate* state = fons__getState(stash); + float width; + + memset(iter, 0, sizeof(*iter)); + + if (stash == NULL) return 0; + if (state->font < 0 || state->font >= stash->nfonts) return 0; + iter->font = stash->fonts[state->font]; + if (iter->font->data == NULL) return 0; + + iter->isize = (short)(state->size*10.0f); + iter->iblur = (short)state->blur; + iter->scale = fons__tt_getPixelHeightScale(&iter->font->font, (float)iter->isize/10.0f); + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width; + } else if (state->align & FONS_ALIGN_CENTER) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width * 0.5f; + } + // Align vertically. + y += fons__getVertAlign(stash, iter->font, state->align, iter->isize); + + if (end == NULL) + end = str + strlen(str); + + iter->x = iter->nextx = x; + iter->y = iter->nexty = y; + iter->spacing = state->spacing; + iter->str = str; + iter->next = str; + iter->end = end; + iter->codepoint = 0; + iter->prevGlyphIndex = -1; + + return 1; +} + +FONS_DEF int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, FONSquad* quad) +{ + FONSglyph* glyph = NULL; + const char* str = iter->next; + iter->str = iter->next; + + if (str == iter->end) + return 0; + + for (; str != iter->end; str++) { + if (fons__decutf8(&iter->utf8state, &iter->codepoint, *(const unsigned char*)str)) + continue; + str++; + // Get glyph and quad + iter->x = iter->nextx; + iter->y = iter->nexty; + glyph = fons__getGlyph(stash, iter->font, iter->codepoint, iter->isize, iter->iblur); + if (glyph != NULL) + fons__getQuad(stash, iter->font, iter->prevGlyphIndex, glyph, iter->scale, iter->spacing, &iter->nextx, &iter->nexty, quad); + iter->prevGlyphIndex = glyph != NULL ? glyph->index : -1; + break; + } + iter->next = str; + + return 1; +} + +FONS_DEF void fonsDrawDebug(FONScontext* stash, float x, float y) +{ + int i; + int w = stash->params.width; + int h = stash->params.height; + float u = w == 0 ? 0 : (1.0f / w); + float v = h == 0 ? 0 : (1.0f / h); + + if (stash->nverts+6+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + // Draw background + fons__vertex(stash, x+0, y+0, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+h, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+0, u, v, 0x0fffffff); + + fons__vertex(stash, x+0, y+0, u, v, 0x0fffffff); + fons__vertex(stash, x+0, y+h, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+h, u, v, 0x0fffffff); + + // Draw texture + fons__vertex(stash, x+0, y+0, 0, 0, 0xffffffff); + fons__vertex(stash, x+w, y+h, 1, 1, 0xffffffff); + fons__vertex(stash, x+w, y+0, 1, 0, 0xffffffff); + + fons__vertex(stash, x+0, y+0, 0, 0, 0xffffffff); + fons__vertex(stash, x+0, y+h, 0, 1, 0xffffffff); + fons__vertex(stash, x+w, y+h, 1, 1, 0xffffffff); + + // Drawbug draw atlas + for (i = 0; i < stash->atlas->nnodes; i++) { + FONSatlasNode* n = &stash->atlas->nodes[i]; + + if (stash->nverts+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + fons__vertex(stash, x+n->x+0, y+n->y+0, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+1, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+0, u, v, 0xc00000ff); + + fons__vertex(stash, x+n->x+0, y+n->y+0, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+0, y+n->y+1, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+1, u, v, 0xc00000ff); + } + + fons__flush(stash); +} + +FONS_DEF float fonsTextBounds(FONScontext* stash, + float x, float y, + const char* str, const char* end, + float* bounds) +{ + FONSstate* state = fons__getState(stash); + unsigned int codepoint; + unsigned int utf8state = 0; + FONSquad q; + FONSglyph* glyph = NULL; + int prevGlyphIndex = -1; + short isize = (short)(state->size*10.0f); + short iblur = (short)state->blur; + float scale; + FONSfont* font; + float startx, advance; + float minx, miny, maxx, maxy; + + if (stash == NULL) return 0; + if (state->font < 0 || state->font >= stash->nfonts) return 0; + font = stash->fonts[state->font]; + if (font->data == NULL) return 0; + + scale = fons__tt_getPixelHeightScale(&font->font, (float)isize/10.0f); + + // Align vertically. + y += fons__getVertAlign(stash, font, state->align, isize); + + minx = maxx = x; + miny = maxy = y; + startx = x; + + if (end == NULL) + end = str + strlen(str); + + for (; str != end; ++str) { + if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str)) + continue; + glyph = fons__getGlyph(stash, font, codepoint, isize, iblur); + if (glyph != NULL) { + fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q); + if (q.x0 < minx) minx = q.x0; + if (q.x1 > maxx) maxx = q.x1; + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + if (q.y0 < miny) miny = q.y0; + if (q.y1 > maxy) maxy = q.y1; + } else { + if (q.y1 < miny) miny = q.y1; + if (q.y0 > maxy) maxy = q.y0; + } + } + prevGlyphIndex = glyph != NULL ? glyph->index : -1; + } + + advance = x - startx; + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + minx -= advance; + maxx -= advance; + } else if (state->align & FONS_ALIGN_CENTER) { + minx -= advance * 0.5f; + maxx -= advance * 0.5f; + } + + if (bounds) { + bounds[0] = minx; + bounds[1] = miny; + bounds[2] = maxx; + bounds[3] = maxy; + } + + return advance; +} + +FONS_DEF void fonsVertMetrics(FONScontext* stash, + float* ascender, float* descender, float* lineh) +{ + FONSfont* font; + FONSstate* state = fons__getState(stash); + short isize; + + if (stash == NULL) return; + if (state->font < 0 || state->font >= stash->nfonts) return; + font = stash->fonts[state->font]; + isize = (short)(state->size*10.0f); + if (font->data == NULL) return; + + if (ascender) + *ascender = font->ascender*isize/10.0f; + if (descender) + *descender = font->descender*isize/10.0f; + if (lineh) + *lineh = font->lineh*isize/10.0f; +} + +FONS_DEF void fonsLineBounds(FONScontext* stash, float y, float* miny, float* maxy) +{ + FONSfont* font; + FONSstate* state = fons__getState(stash); + short isize; + + if (stash == NULL) return; + if (state->font < 0 || state->font >= stash->nfonts) return; + font = stash->fonts[state->font]; + isize = (short)(state->size*10.0f); + if (font->data == NULL) return; + + y += fons__getVertAlign(stash, font, state->align, isize); + + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + *miny = y - font->ascender * (float)isize/10.0f; + *maxy = *miny + font->lineh*isize/10.0f; + } else { + *maxy = y + font->descender * (float)isize/10.0f; + *miny = *maxy - font->lineh*isize/10.0f; + } +} + +FONS_DEF const unsigned char* fonsGetTextureData(FONScontext* stash, int* width, int* height) +{ + if (width != NULL) + *width = stash->params.width; + if (height != NULL) + *height = stash->params.height; + return stash->texData; +} + +FONS_DEF int fonsValidateTexture(FONScontext* stash, int* dirty) +{ + if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) { + dirty[0] = stash->dirtyRect[0]; + dirty[1] = stash->dirtyRect[1]; + dirty[2] = stash->dirtyRect[2]; + dirty[3] = stash->dirtyRect[3]; + // Reset dirty rect + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + return 1; + } + return 0; +} + +FONS_DEF void fonsDeleteInternal(FONScontext* stash) +{ + int i; + if (stash == NULL) return; + + if (stash->params.renderDelete) + stash->params.renderDelete(stash->params.userPtr); + + for (i = 0; i < stash->nfonts; ++i) + fons__freeFont(stash->fonts[i]); + + if (stash->atlas) fons__deleteAtlas(stash->atlas); + if (stash->fonts) free(stash->fonts); + if (stash->texData) free(stash->texData); + if (stash->scratch) free(stash->scratch); + free(stash); +} + +FONS_DEF void fonsSetErrorCallback(FONScontext* stash, void (*callback)(void* uptr, int error, int val), void* uptr) +{ + if (stash == NULL) return; + stash->handleError = callback; + stash->errorUptr = uptr; +} + +FONS_DEF void fonsGetAtlasSize(FONScontext* stash, int* width, int* height) +{ + if (stash == NULL) return; + *width = stash->params.width; + *height = stash->params.height; +} + +FONS_DEF int fonsExpandAtlas(FONScontext* stash, int width, int height) +{ + int i, maxy = 0; + unsigned char* data = NULL; + if (stash == NULL) return 0; + + width = fons__maxi(width, stash->params.width); + height = fons__maxi(height, stash->params.height); + + if (width == stash->params.width && height == stash->params.height) + return 1; + + // Flush pending glyphs. + fons__flush(stash); + + // Create new texture + if (stash->params.renderResize != NULL) { + if (stash->params.renderResize(stash->params.userPtr, width, height) == 0) + return 0; + } + // Copy old texture data over. + data = (unsigned char*)malloc(width * height); + if (data == NULL) + return 0; + for (i = 0; i < stash->params.height; i++) { + unsigned char* dst = &data[i*width]; + unsigned char* src = &stash->texData[i*stash->params.width]; + memcpy(dst, src, stash->params.width); + if (width > stash->params.width) + memset(dst+stash->params.width, 0, width - stash->params.width); + } + if (height > stash->params.height) + memset(&data[stash->params.height * width], 0, (height - stash->params.height) * width); + + free(stash->texData); + stash->texData = data; + + // Increase atlas size + fons__atlasExpand(stash->atlas, width, height); + + // Add existing data as dirty. + for (i = 0; i < stash->atlas->nnodes; i++) + maxy = fons__maxi(maxy, stash->atlas->nodes[i].y); + stash->dirtyRect[0] = 0; + stash->dirtyRect[1] = 0; + stash->dirtyRect[2] = stash->params.width; + stash->dirtyRect[3] = maxy; + + stash->params.width = width; + stash->params.height = height; + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + + return 1; +} + +FONS_DEF int fonsResetAtlas(FONScontext* stash, int width, int height) +{ + int i, j; + if (stash == NULL) return 0; + + // Flush pending glyphs. + fons__flush(stash); + + // Create new texture + if (stash->params.renderResize != NULL) { + if (stash->params.renderResize(stash->params.userPtr, width, height) == 0) + return 0; + } + + // Reset atlas + fons__atlasReset(stash->atlas, width, height); + + // Clear texture data. + stash->texData = (unsigned char*)realloc(stash->texData, width * height); + if (stash->texData == NULL) return 0; + memset(stash->texData, 0, width * height); + + // Reset dirty rect + stash->dirtyRect[0] = width; + stash->dirtyRect[1] = height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + + // Reset cached glyphs + for (i = 0; i < stash->nfonts; i++) { + FONSfont* font = stash->fonts[i]; + font->nglyphs = 0; + for (j = 0; j < FONS_HASH_LUT_SIZE; j++) + font->lut[j] = -1; + } + + stash->params.width = width; + stash->params.height = height; + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + + // Add white rect at 0,0 for debug drawing. + fons__addWhiteRect(stash, 2,2); + + return 1; +} + +#endif // FONTSTASH_IMPLEMENTATION diff --git a/source/engine/thirdparty/sokol/tests/ext/nuklear.c b/source/engine/thirdparty/sokol/tests/ext/nuklear.c new file mode 100644 index 0000000..52712f3 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/ext/nuklear.c @@ -0,0 +1,33 @@ +// include nuklear.h before the sokol_nuklear.h implementation +#define NK_INCLUDE_FIXED_TYPES +#define NK_INCLUDE_STANDARD_IO +#define NK_INCLUDE_DEFAULT_ALLOCATOR +#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT +#define NK_INCLUDE_FONT_BAKING +#define NK_INCLUDE_DEFAULT_FONT +#define NK_INCLUDE_STANDARD_VARARGS +#define NK_IMPLEMENTATION + +#if defined(__clang__) +#pragma GCC diagnostic ignored "-Wunknown-warning-option" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wnull-pointer-subtraction" +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4996) // sprintf,fopen,localtime: This function or variable may be unsafe +#pragma warning(disable:4127) // conditional expression is constant +#pragma warning(disable:4100) // unreferenced formal parameter +#pragma warning(disable:4701) // potentially uninitialized local variable used +#endif +#include "nuklear.h" +#if defined(_MSC_VER) +#pragma warning(pop) +#endif diff --git a/source/engine/thirdparty/sokol/tests/ext/nuklear.h b/source/engine/thirdparty/sokol/tests/ext/nuklear.h new file mode 100644 index 0000000..6855f6f --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/ext/nuklear.h @@ -0,0 +1,29499 @@ +/* +/// # Nuklear +/// ![](https://cloud.githubusercontent.com/assets/8057201/11761525/ae06f0ca-a0c6-11e5-819d-5610b25f6ef4.gif) +/// +/// ## Contents +/// 1. About section +/// 2. Highlights section +/// 3. Features section +/// 4. Usage section +/// 1. Flags section +/// 2. Constants section +/// 3. Dependencies section +/// 5. Example section +/// 6. API section +/// 1. Context section +/// 2. Input section +/// 3. Drawing section +/// 4. Window section +/// 5. Layouting section +/// 6. Groups section +/// 7. Tree section +/// 8. Properties section +/// 7. License section +/// 8. Changelog section +/// 9. Gallery section +/// 10. Credits section +/// +/// ## About +/// This is a minimal state immediate mode graphical user interface toolkit +/// written in ANSI C and licensed under public domain. It was designed as a simple +/// embeddable user interface for application and does not have any dependencies, +/// a default renderbackend or OS window and input handling but instead provides a very modular +/// library approach by using simple input state for input and draw +/// commands describing primitive shapes as output. So instead of providing a +/// layered library that tries to abstract over a number of platform and +/// render backends it only focuses on the actual UI. +/// +/// ## Highlights +/// - Graphical user interface toolkit +/// - Single header library +/// - Written in C89 (a.k.a. ANSI C or ISO C90) +/// - Small codebase (~18kLOC) +/// - Focus on portability, efficiency and simplicity +/// - No dependencies (not even the standard library if not wanted) +/// - Fully skinnable and customizable +/// - Low memory footprint with total memory control if needed or wanted +/// - UTF-8 support +/// - No global or hidden state +/// - Customizable library modules (you can compile and use only what you need) +/// - Optional font baker and vertex buffer output +/// +/// ## Features +/// - Absolutely no platform dependent code +/// - Memory management control ranging from/to +/// - Ease of use by allocating everything from standard library +/// - Control every byte of memory inside the library +/// - Font handling control ranging from/to +/// - Use your own font implementation for everything +/// - Use this libraries internal font baking and handling API +/// - Drawing output control ranging from/to +/// - Simple shapes for more high level APIs which already have drawing capabilities +/// - Hardware accessible anti-aliased vertex buffer output +/// - Customizable colors and properties ranging from/to +/// - Simple changes to color by filling a simple color table +/// - Complete control with ability to use skinning to decorate widgets +/// - Bendable UI library with widget ranging from/to +/// - Basic widgets like buttons, checkboxes, slider, ... +/// - Advanced widget like abstract comboboxes, contextual menus,... +/// - Compile time configuration to only compile what you need +/// - Subset which can be used if you do not want to link or use the standard library +/// - Can be easily modified to only update on user input instead of frame updates +/// +/// ## Usage +/// This library is self contained in one single header file and can be used either +/// in header only mode or in implementation mode. The header only mode is used +/// by default when included and allows including this header in other headers +/// and does not contain the actual implementation.

    +/// +/// The implementation mode requires to define the preprocessor macro +/// NK_IMPLEMENTATION in *one* .c/.cpp file before #includeing this file, e.g.: +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~C +/// #define NK_IMPLEMENTATION +/// #include "nuklear.h" +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Also optionally define the symbols listed in the section "OPTIONAL DEFINES" +/// below in header and implementation mode if you want to use additional functionality +/// or need more control over the library. +/// +/// !!! WARNING +/// Every time nuklear is included define the same compiler flags. This very important not doing so could lead to compiler errors or even worse stack corruptions. +/// +/// ### Flags +/// Flag | Description +/// --------------------------------|------------------------------------------ +/// NK_PRIVATE | If defined declares all functions as static, so they can only be accessed inside the file that contains the implementation +/// NK_INCLUDE_FIXED_TYPES | If defined it will include header `` for fixed sized types otherwise nuklear tries to select the correct type. If that fails it will throw a compiler error and you have to select the correct types yourself. +/// NK_INCLUDE_DEFAULT_ALLOCATOR | If defined it will include header `` and provide additional functions to use this library without caring for memory allocation control and therefore ease memory management. +/// NK_INCLUDE_STANDARD_IO | If defined it will include header `` and provide additional functions depending on file loading. +/// NK_INCLUDE_STANDARD_VARARGS | If defined it will include header and provide additional functions depending on file loading. +/// NK_INCLUDE_STANDARD_BOOL | If defined it will include header `` for nk_bool otherwise nuklear defines nk_bool as int. +/// NK_INCLUDE_VERTEX_BUFFER_OUTPUT | Defining this adds a vertex draw command list backend to this library, which allows you to convert queue commands into vertex draw commands. This is mainly if you need a hardware accessible format for OpenGL, DirectX, Vulkan, Metal,... +/// NK_INCLUDE_FONT_BAKING | Defining this adds `stb_truetype` and `stb_rect_pack` implementation to this library and provides font baking and rendering. If you already have font handling or do not want to use this font handler you don't have to define it. +/// NK_INCLUDE_DEFAULT_FONT | Defining this adds the default font: ProggyClean.ttf into this library which can be loaded into a font atlas and allows using this library without having a truetype font +/// NK_INCLUDE_COMMAND_USERDATA | Defining this adds a userdata pointer into each command. Can be useful for example if you want to provide custom shaders depending on the used widget. Can be combined with the style structures. +/// NK_BUTTON_TRIGGER_ON_RELEASE | Different platforms require button clicks occurring either on buttons being pressed (up to down) or released (down to up). By default this library will react on buttons being pressed, but if you define this it will only trigger if a button is released. +/// NK_ZERO_COMMAND_MEMORY | Defining this will zero out memory for each drawing command added to a drawing queue (inside nk_command_buffer_push). Zeroing command memory is very useful for fast checking (using memcmp) if command buffers are equal and avoid drawing frames when nothing on screen has changed since previous frame. +/// NK_UINT_DRAW_INDEX | Defining this will set the size of vertex index elements when using NK_VERTEX_BUFFER_OUTPUT to 32bit instead of the default of 16bit +/// NK_KEYSTATE_BASED_INPUT | Define this if your backend uses key state for each frame rather than key press/release events +/// +/// !!! WARNING +/// The following flags will pull in the standard C library: +/// - NK_INCLUDE_DEFAULT_ALLOCATOR +/// - NK_INCLUDE_STANDARD_IO +/// - NK_INCLUDE_STANDARD_VARARGS +/// +/// !!! WARNING +/// The following flags if defined need to be defined for both header and implementation: +/// - NK_INCLUDE_FIXED_TYPES +/// - NK_INCLUDE_DEFAULT_ALLOCATOR +/// - NK_INCLUDE_STANDARD_VARARGS +/// - NK_INCLUDE_STANDARD_BOOL +/// - NK_INCLUDE_VERTEX_BUFFER_OUTPUT +/// - NK_INCLUDE_FONT_BAKING +/// - NK_INCLUDE_DEFAULT_FONT +/// - NK_INCLUDE_STANDARD_VARARGS +/// - NK_INCLUDE_COMMAND_USERDATA +/// - NK_UINT_DRAW_INDEX +/// +/// ### Constants +/// Define | Description +/// --------------------------------|--------------------------------------- +/// NK_BUFFER_DEFAULT_INITIAL_SIZE | Initial buffer size allocated by all buffers while using the default allocator functions included by defining NK_INCLUDE_DEFAULT_ALLOCATOR. If you don't want to allocate the default 4k memory then redefine it. +/// NK_MAX_NUMBER_BUFFER | Maximum buffer size for the conversion buffer between float and string Under normal circumstances this should be more than sufficient. +/// NK_INPUT_MAX | Defines the max number of bytes which can be added as text input in one frame. Under normal circumstances this should be more than sufficient. +/// +/// !!! WARNING +/// The following constants if defined need to be defined for both header and implementation: +/// - NK_MAX_NUMBER_BUFFER +/// - NK_BUFFER_DEFAULT_INITIAL_SIZE +/// - NK_INPUT_MAX +/// +/// ### Dependencies +/// Function | Description +/// ------------|--------------------------------------------------------------- +/// NK_ASSERT | If you don't define this, nuklear will use with assert(). +/// NK_MEMSET | You can define this to 'memset' or your own memset implementation replacement. If not nuklear will use its own version. +/// NK_MEMCPY | You can define this to 'memcpy' or your own memcpy implementation replacement. If not nuklear will use its own version. +/// NK_SQRT | You can define this to 'sqrt' or your own sqrt implementation replacement. If not nuklear will use its own slow and not highly accurate version. +/// NK_SIN | You can define this to 'sinf' or your own sine implementation replacement. If not nuklear will use its own approximation implementation. +/// NK_COS | You can define this to 'cosf' or your own cosine implementation replacement. If not nuklear will use its own approximation implementation. +/// NK_STRTOD | You can define this to `strtod` or your own string to double conversion implementation replacement. If not defined nuklear will use its own imprecise and possibly unsafe version (does not handle nan or infinity!). +/// NK_DTOA | You can define this to `dtoa` or your own double to string conversion implementation replacement. If not defined nuklear will use its own imprecise and possibly unsafe version (does not handle nan or infinity!). +/// NK_VSNPRINTF| If you define `NK_INCLUDE_STANDARD_VARARGS` as well as `NK_INCLUDE_STANDARD_IO` and want to be safe define this to `vsnprintf` on compilers supporting later versions of C or C++. By default nuklear will check for your stdlib version in C as well as compiler version in C++. if `vsnprintf` is available it will define it to `vsnprintf` directly. If not defined and if you have older versions of C or C++ it will be defined to `vsprintf` which is unsafe. +/// +/// !!! WARNING +/// The following dependencies will pull in the standard C library if not redefined: +/// - NK_ASSERT +/// +/// !!! WARNING +/// The following dependencies if defined need to be defined for both header and implementation: +/// - NK_ASSERT +/// +/// !!! WARNING +/// The following dependencies if defined need to be defined only for the implementation part: +/// - NK_MEMSET +/// - NK_MEMCPY +/// - NK_SQRT +/// - NK_SIN +/// - NK_COS +/// - NK_STRTOD +/// - NK_DTOA +/// - NK_VSNPRINTF +/// +/// ## Example +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// // init gui state +/// enum {EASY, HARD}; +/// static int op = EASY; +/// static float value = 0.6f; +/// static int i = 20; +/// struct nk_context ctx; +/// +/// nk_init_fixed(&ctx, calloc(1, MAX_MEMORY), MAX_MEMORY, &font); +/// if (nk_begin(&ctx, "Show", nk_rect(50, 50, 220, 220), +/// NK_WINDOW_BORDER|NK_WINDOW_MOVABLE|NK_WINDOW_CLOSABLE)) { +/// // fixed widget pixel width +/// nk_layout_row_static(&ctx, 30, 80, 1); +/// if (nk_button_label(&ctx, "button")) { +/// // event handling +/// } +/// +/// // fixed widget window ratio width +/// nk_layout_row_dynamic(&ctx, 30, 2); +/// if (nk_option_label(&ctx, "easy", op == EASY)) op = EASY; +/// if (nk_option_label(&ctx, "hard", op == HARD)) op = HARD; +/// +/// // custom widget pixel width +/// nk_layout_row_begin(&ctx, NK_STATIC, 30, 2); +/// { +/// nk_layout_row_push(&ctx, 50); +/// nk_label(&ctx, "Volume:", NK_TEXT_LEFT); +/// nk_layout_row_push(&ctx, 110); +/// nk_slider_float(&ctx, 0, &value, 1.0f, 0.1f); +/// } +/// nk_layout_row_end(&ctx); +/// } +/// nk_end(&ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// ![](https://cloud.githubusercontent.com/assets/8057201/10187981/584ecd68-675c-11e5-897c-822ef534a876.png) +/// +/// ## API +/// +*/ +#ifndef NK_SINGLE_FILE + #define NK_SINGLE_FILE +#endif + +#ifndef NK_NUKLEAR_H_ +#define NK_NUKLEAR_H_ + +#ifdef __cplusplus +extern "C" { +#endif +/* + * ============================================================== + * + * CONSTANTS + * + * =============================================================== + */ +#define NK_UNDEFINED (-1.0f) +#define NK_UTF_INVALID 0xFFFD /* internal invalid utf8 rune */ +#define NK_UTF_SIZE 4 /* describes the number of bytes a glyph consists of*/ +#ifndef NK_INPUT_MAX + #define NK_INPUT_MAX 16 +#endif +#ifndef NK_MAX_NUMBER_BUFFER + #define NK_MAX_NUMBER_BUFFER 64 +#endif +#ifndef NK_SCROLLBAR_HIDING_TIMEOUT + #define NK_SCROLLBAR_HIDING_TIMEOUT 4.0f +#endif +/* + * ============================================================== + * + * HELPER + * + * =============================================================== + */ +#ifndef NK_API + #ifdef NK_PRIVATE + #if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199409L)) + #define NK_API static inline + #elif defined(__cplusplus) + #define NK_API static inline + #else + #define NK_API static + #endif + #else + #define NK_API extern + #endif +#endif +#ifndef NK_LIB + #ifdef NK_SINGLE_FILE + #define NK_LIB static + #else + #define NK_LIB extern + #endif +#endif + +#define NK_INTERN static +#define NK_STORAGE static +#define NK_GLOBAL static + +#define NK_FLAG(x) (1 << (x)) +#define NK_STRINGIFY(x) #x +#define NK_MACRO_STRINGIFY(x) NK_STRINGIFY(x) +#define NK_STRING_JOIN_IMMEDIATE(arg1, arg2) arg1 ## arg2 +#define NK_STRING_JOIN_DELAY(arg1, arg2) NK_STRING_JOIN_IMMEDIATE(arg1, arg2) +#define NK_STRING_JOIN(arg1, arg2) NK_STRING_JOIN_DELAY(arg1, arg2) + +#ifdef _MSC_VER + #define NK_UNIQUE_NAME(name) NK_STRING_JOIN(name,__COUNTER__) +#else + #define NK_UNIQUE_NAME(name) NK_STRING_JOIN(name,__LINE__) +#endif + +#ifndef NK_STATIC_ASSERT + #define NK_STATIC_ASSERT(exp) typedef char NK_UNIQUE_NAME(_dummy_array)[(exp)?1:-1] +#endif + +#ifndef NK_FILE_LINE +#ifdef _MSC_VER + #define NK_FILE_LINE __FILE__ ":" NK_MACRO_STRINGIFY(__COUNTER__) +#else + #define NK_FILE_LINE __FILE__ ":" NK_MACRO_STRINGIFY(__LINE__) +#endif +#endif + +#define NK_MIN(a,b) ((a) < (b) ? (a) : (b)) +#define NK_MAX(a,b) ((a) < (b) ? (b) : (a)) +#define NK_CLAMP(i,v,x) (NK_MAX(NK_MIN(v,x), i)) + +#ifdef NK_INCLUDE_STANDARD_VARARGS + #include + #if defined(_MSC_VER) && (_MSC_VER >= 1600) /* VS 2010 and above */ + #include + #define NK_PRINTF_FORMAT_STRING _Printf_format_string_ + #else + #define NK_PRINTF_FORMAT_STRING + #endif + #if defined(__GNUC__) + #define NK_PRINTF_VARARG_FUNC(fmtargnumber) __attribute__((format(__printf__, fmtargnumber, fmtargnumber+1))) + #define NK_PRINTF_VALIST_FUNC(fmtargnumber) __attribute__((format(__printf__, fmtargnumber, 0))) + #else + #define NK_PRINTF_VARARG_FUNC(fmtargnumber) + #define NK_PRINTF_VALIST_FUNC(fmtargnumber) + #endif +#endif + +/* + * =============================================================== + * + * BASIC + * + * =============================================================== + */ +#ifdef NK_INCLUDE_FIXED_TYPES + #include + #define NK_INT8 int8_t + #define NK_UINT8 uint8_t + #define NK_INT16 int16_t + #define NK_UINT16 uint16_t + #define NK_INT32 int32_t + #define NK_UINT32 uint32_t + #define NK_SIZE_TYPE uintptr_t + #define NK_POINTER_TYPE uintptr_t +#else + #ifndef NK_INT8 + #define NK_INT8 signed char + #endif + #ifndef NK_UINT8 + #define NK_UINT8 unsigned char + #endif + #ifndef NK_INT16 + #define NK_INT16 signed short + #endif + #ifndef NK_UINT16 + #define NK_UINT16 unsigned short + #endif + #ifndef NK_INT32 + #if defined(_MSC_VER) + #define NK_INT32 __int32 + #else + #define NK_INT32 signed int + #endif + #endif + #ifndef NK_UINT32 + #if defined(_MSC_VER) + #define NK_UINT32 unsigned __int32 + #else + #define NK_UINT32 unsigned int + #endif + #endif + #ifndef NK_SIZE_TYPE + #if defined(_WIN64) && defined(_MSC_VER) + #define NK_SIZE_TYPE unsigned __int64 + #elif (defined(_WIN32) || defined(WIN32)) && defined(_MSC_VER) + #define NK_SIZE_TYPE unsigned __int32 + #elif defined(__GNUC__) || defined(__clang__) + #if defined(__x86_64__) || defined(__ppc64__) + #define NK_SIZE_TYPE unsigned long + #else + #define NK_SIZE_TYPE unsigned int + #endif + #else + #define NK_SIZE_TYPE unsigned long + #endif + #endif + #ifndef NK_POINTER_TYPE + #if defined(_WIN64) && defined(_MSC_VER) + #define NK_POINTER_TYPE unsigned __int64 + #elif (defined(_WIN32) || defined(WIN32)) && defined(_MSC_VER) + #define NK_POINTER_TYPE unsigned __int32 + #elif defined(__GNUC__) || defined(__clang__) + #if defined(__x86_64__) || defined(__ppc64__) + #define NK_POINTER_TYPE unsigned long + #else + #define NK_POINTER_TYPE unsigned int + #endif + #else + #define NK_POINTER_TYPE unsigned long + #endif + #endif +#endif + +#ifndef NK_BOOL + #ifdef NK_INCLUDE_STANDARD_BOOL + #include + #define NK_BOOL bool + #else + #define NK_BOOL int /* could be char, use int for drop-in replacement backwards compatibility */ + #endif +#endif + +typedef NK_INT8 nk_char; +typedef NK_UINT8 nk_uchar; +typedef NK_UINT8 nk_byte; +typedef NK_INT16 nk_short; +typedef NK_UINT16 nk_ushort; +typedef NK_INT32 nk_int; +typedef NK_UINT32 nk_uint; +typedef NK_SIZE_TYPE nk_size; +typedef NK_POINTER_TYPE nk_ptr; +typedef NK_BOOL nk_bool; + +typedef nk_uint nk_hash; +typedef nk_uint nk_flags; +typedef nk_uint nk_rune; + +/* Make sure correct type size: + * This will fire with a negative subscript error if the type sizes + * are set incorrectly by the compiler, and compile out if not */ +NK_STATIC_ASSERT(sizeof(nk_short) == 2); +NK_STATIC_ASSERT(sizeof(nk_ushort) == 2); +NK_STATIC_ASSERT(sizeof(nk_uint) == 4); +NK_STATIC_ASSERT(sizeof(nk_int) == 4); +NK_STATIC_ASSERT(sizeof(nk_byte) == 1); +NK_STATIC_ASSERT(sizeof(nk_flags) >= 4); +NK_STATIC_ASSERT(sizeof(nk_rune) >= 4); +NK_STATIC_ASSERT(sizeof(nk_size) >= sizeof(void*)); +NK_STATIC_ASSERT(sizeof(nk_ptr) >= sizeof(void*)); +#ifdef NK_INCLUDE_STANDARD_BOOL +NK_STATIC_ASSERT(sizeof(nk_bool) == sizeof(bool)); +#else +NK_STATIC_ASSERT(sizeof(nk_bool) >= 2); +#endif + +/* ============================================================================ + * + * API + * + * =========================================================================== */ +struct nk_buffer; +struct nk_allocator; +struct nk_command_buffer; +struct nk_draw_command; +struct nk_convert_config; +struct nk_style_item; +struct nk_text_edit; +struct nk_draw_list; +struct nk_user_font; +struct nk_panel; +struct nk_context; +struct nk_draw_vertex_layout_element; +struct nk_style_button; +struct nk_style_toggle; +struct nk_style_selectable; +struct nk_style_slide; +struct nk_style_progress; +struct nk_style_scrollbar; +struct nk_style_edit; +struct nk_style_property; +struct nk_style_chart; +struct nk_style_combo; +struct nk_style_tab; +struct nk_style_window_header; +struct nk_style_window; + +enum {nk_false, nk_true}; +struct nk_color {nk_byte r,g,b,a;}; +struct nk_colorf {float r,g,b,a;}; +struct nk_vec2 {float x,y;}; +struct nk_vec2i {short x, y;}; +struct nk_rect {float x,y,w,h;}; +struct nk_recti {short x,y,w,h;}; +typedef char nk_glyph[NK_UTF_SIZE]; +typedef union {void *ptr; int id;} nk_handle; +struct nk_image {nk_handle handle;unsigned short w,h;unsigned short region[4];}; +struct nk_cursor {struct nk_image img; struct nk_vec2 size, offset;}; +struct nk_scroll {nk_uint x, y;}; + +enum nk_heading {NK_UP, NK_RIGHT, NK_DOWN, NK_LEFT}; +enum nk_button_behavior {NK_BUTTON_DEFAULT, NK_BUTTON_REPEATER}; +enum nk_modify {NK_FIXED = nk_false, NK_MODIFIABLE = nk_true}; +enum nk_orientation {NK_VERTICAL, NK_HORIZONTAL}; +enum nk_collapse_states {NK_MINIMIZED = nk_false, NK_MAXIMIZED = nk_true}; +enum nk_show_states {NK_HIDDEN = nk_false, NK_SHOWN = nk_true}; +enum nk_chart_type {NK_CHART_LINES, NK_CHART_COLUMN, NK_CHART_MAX}; +enum nk_chart_event {NK_CHART_HOVERING = 0x01, NK_CHART_CLICKED = 0x02}; +enum nk_color_format {NK_RGB, NK_RGBA}; +enum nk_popup_type {NK_POPUP_STATIC, NK_POPUP_DYNAMIC}; +enum nk_layout_format {NK_DYNAMIC, NK_STATIC}; +enum nk_tree_type {NK_TREE_NODE, NK_TREE_TAB}; + +typedef void*(*nk_plugin_alloc)(nk_handle, void *old, nk_size); +typedef void (*nk_plugin_free)(nk_handle, void *old); +typedef nk_bool(*nk_plugin_filter)(const struct nk_text_edit*, nk_rune unicode); +typedef void(*nk_plugin_paste)(nk_handle, struct nk_text_edit*); +typedef void(*nk_plugin_copy)(nk_handle, const char*, int len); + +struct nk_allocator { + nk_handle userdata; + nk_plugin_alloc alloc; + nk_plugin_free free; +}; +enum nk_symbol_type { + NK_SYMBOL_NONE, + NK_SYMBOL_X, + NK_SYMBOL_UNDERSCORE, + NK_SYMBOL_CIRCLE_SOLID, + NK_SYMBOL_CIRCLE_OUTLINE, + NK_SYMBOL_RECT_SOLID, + NK_SYMBOL_RECT_OUTLINE, + NK_SYMBOL_TRIANGLE_UP, + NK_SYMBOL_TRIANGLE_DOWN, + NK_SYMBOL_TRIANGLE_LEFT, + NK_SYMBOL_TRIANGLE_RIGHT, + NK_SYMBOL_PLUS, + NK_SYMBOL_MINUS, + NK_SYMBOL_MAX +}; +/* ============================================================================= + * + * CONTEXT + * + * =============================================================================*/ +/*/// ### Context +/// Contexts are the main entry point and the majestro of nuklear and contain all required state. +/// They are used for window, memory, input, style, stack, commands and time management and need +/// to be passed into all nuklear GUI specific functions. +/// +/// #### Usage +/// To use a context it first has to be initialized which can be achieved by calling +/// one of either `nk_init_default`, `nk_init_fixed`, `nk_init`, `nk_init_custom`. +/// Each takes in a font handle and a specific way of handling memory. Memory control +/// hereby ranges from standard library to just specifying a fixed sized block of memory +/// which nuklear has to manage itself from. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_context ctx; +/// nk_init_xxx(&ctx, ...); +/// while (1) { +/// // [...] +/// nk_clear(&ctx); +/// } +/// nk_free(&ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// #### Reference +/// Function | Description +/// --------------------|------------------------------------------------------- +/// __nk_init_default__ | Initializes context with standard library memory allocation (malloc,free) +/// __nk_init_fixed__ | Initializes context from single fixed size memory block +/// __nk_init__ | Initializes context with memory allocator callbacks for alloc and free +/// __nk_init_custom__ | Initializes context from two buffers. One for draw commands the other for window/panel/table allocations +/// __nk_clear__ | Called at the end of the frame to reset and prepare the context for the next frame +/// __nk_free__ | Shutdown and free all memory allocated inside the context +/// __nk_set_user_data__| Utility function to pass user data to draw command + */ +#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR +/*/// #### nk_init_default +/// Initializes a `nk_context` struct with a default standard library allocator. +/// Should be used if you don't want to be bothered with memory management in nuklear. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_init_default(struct nk_context *ctx, const struct nk_user_font *font); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|--------------------------------------------------------------- +/// __ctx__ | Must point to an either stack or heap allocated `nk_context` struct +/// __font__ | Must point to a previously initialized font handle for more info look at font documentation +/// +/// Returns either `false(0)` on failure or `true(1)` on success. +/// +*/ +NK_API nk_bool nk_init_default(struct nk_context*, const struct nk_user_font*); +#endif +/*/// #### nk_init_fixed +/// Initializes a `nk_context` struct from single fixed size memory block +/// Should be used if you want complete control over nuklear's memory management. +/// Especially recommended for system with little memory or systems with virtual memory. +/// For the later case you can just allocate for example 16MB of virtual memory +/// and only the required amount of memory will actually be committed. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_init_fixed(struct nk_context *ctx, void *memory, nk_size size, const struct nk_user_font *font); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// !!! Warning +/// make sure the passed memory block is aligned correctly for `nk_draw_commands`. +/// +/// Parameter | Description +/// ------------|-------------------------------------------------------------- +/// __ctx__ | Must point to an either stack or heap allocated `nk_context` struct +/// __memory__ | Must point to a previously allocated memory block +/// __size__ | Must contain the total size of __memory__ +/// __font__ | Must point to a previously initialized font handle for more info look at font documentation +/// +/// Returns either `false(0)` on failure or `true(1)` on success. +*/ +NK_API nk_bool nk_init_fixed(struct nk_context*, void *memory, nk_size size, const struct nk_user_font*); +/*/// #### nk_init +/// Initializes a `nk_context` struct with memory allocation callbacks for nuklear to allocate +/// memory from. Used internally for `nk_init_default` and provides a kitchen sink allocation +/// interface to nuklear. Can be useful for cases like monitoring memory consumption. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_init(struct nk_context *ctx, struct nk_allocator *alloc, const struct nk_user_font *font); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|--------------------------------------------------------------- +/// __ctx__ | Must point to an either stack or heap allocated `nk_context` struct +/// __alloc__ | Must point to a previously allocated memory allocator +/// __font__ | Must point to a previously initialized font handle for more info look at font documentation +/// +/// Returns either `false(0)` on failure or `true(1)` on success. +*/ +NK_API nk_bool nk_init(struct nk_context*, struct nk_allocator*, const struct nk_user_font*); +/*/// #### nk_init_custom +/// Initializes a `nk_context` struct from two different either fixed or growing +/// buffers. The first buffer is for allocating draw commands while the second buffer is +/// used for allocating windows, panels and state tables. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_init_custom(struct nk_context *ctx, struct nk_buffer *cmds, struct nk_buffer *pool, const struct nk_user_font *font); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|--------------------------------------------------------------- +/// __ctx__ | Must point to an either stack or heap allocated `nk_context` struct +/// __cmds__ | Must point to a previously initialized memory buffer either fixed or dynamic to store draw commands into +/// __pool__ | Must point to a previously initialized memory buffer either fixed or dynamic to store windows, panels and tables +/// __font__ | Must point to a previously initialized font handle for more info look at font documentation +/// +/// Returns either `false(0)` on failure or `true(1)` on success. +*/ +NK_API nk_bool nk_init_custom(struct nk_context*, struct nk_buffer *cmds, struct nk_buffer *pool, const struct nk_user_font*); +/*/// #### nk_clear +/// Resets the context state at the end of the frame. This includes mostly +/// garbage collector tasks like removing windows or table not called and therefore +/// used anymore. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_clear(struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to a previously initialized `nk_context` struct +*/ +NK_API void nk_clear(struct nk_context*); +/*/// #### nk_free +/// Frees all memory allocated by nuklear. Not needed if context was +/// initialized with `nk_init_fixed`. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_free(struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to a previously initialized `nk_context` struct +*/ +NK_API void nk_free(struct nk_context*); +#ifdef NK_INCLUDE_COMMAND_USERDATA +/*/// #### nk_set_user_data +/// Sets the currently passed userdata passed down into each draw command. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_set_user_data(struct nk_context *ctx, nk_handle data); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|-------------------------------------------------------------- +/// __ctx__ | Must point to a previously initialized `nk_context` struct +/// __data__ | Handle with either pointer or index to be passed into every draw commands +*/ +NK_API void nk_set_user_data(struct nk_context*, nk_handle handle); +#endif +/* ============================================================================= + * + * INPUT + * + * =============================================================================*/ +/*/// ### Input +/// The input API is responsible for holding the current input state composed of +/// mouse, key and text input states. +/// It is worth noting that no direct OS or window handling is done in nuklear. +/// Instead all input state has to be provided by platform specific code. This on one hand +/// expects more work from the user and complicates usage but on the other hand +/// provides simple abstraction over a big number of platforms, libraries and other +/// already provided functionality. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// nk_input_begin(&ctx); +/// while (GetEvent(&evt)) { +/// if (evt.type == MOUSE_MOVE) +/// nk_input_motion(&ctx, evt.motion.x, evt.motion.y); +/// else if (evt.type == [...]) { +/// // [...] +/// } +/// } nk_input_end(&ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// #### Usage +/// Input state needs to be provided to nuklear by first calling `nk_input_begin` +/// which resets internal state like delta mouse position and button transistions. +/// After `nk_input_begin` all current input state needs to be provided. This includes +/// mouse motion, button and key pressed and released, text input and scrolling. +/// Both event- or state-based input handling are supported by this API +/// and should work without problems. Finally after all input state has been +/// mirrored `nk_input_end` needs to be called to finish input process. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_context ctx; +/// nk_init_xxx(&ctx, ...); +/// while (1) { +/// Event evt; +/// nk_input_begin(&ctx); +/// while (GetEvent(&evt)) { +/// if (evt.type == MOUSE_MOVE) +/// nk_input_motion(&ctx, evt.motion.x, evt.motion.y); +/// else if (evt.type == [...]) { +/// // [...] +/// } +/// } +/// nk_input_end(&ctx); +/// // [...] +/// nk_clear(&ctx); +/// } nk_free(&ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// #### Reference +/// Function | Description +/// --------------------|------------------------------------------------------- +/// __nk_input_begin__ | Begins the input mirroring process. Needs to be called before all other `nk_input_xxx` calls +/// __nk_input_motion__ | Mirrors mouse cursor position +/// __nk_input_key__ | Mirrors key state with either pressed or released +/// __nk_input_button__ | Mirrors mouse button state with either pressed or released +/// __nk_input_scroll__ | Mirrors mouse scroll values +/// __nk_input_char__ | Adds a single ASCII text character into an internal text buffer +/// __nk_input_glyph__ | Adds a single multi-byte UTF-8 character into an internal text buffer +/// __nk_input_unicode__| Adds a single unicode rune into an internal text buffer +/// __nk_input_end__ | Ends the input mirroring process by calculating state changes. Don't call any `nk_input_xxx` function referenced above after this call +*/ +enum nk_keys { + NK_KEY_NONE, + NK_KEY_SHIFT, + NK_KEY_CTRL, + NK_KEY_DEL, + NK_KEY_ENTER, + NK_KEY_TAB, + NK_KEY_BACKSPACE, + NK_KEY_COPY, + NK_KEY_CUT, + NK_KEY_PASTE, + NK_KEY_UP, + NK_KEY_DOWN, + NK_KEY_LEFT, + NK_KEY_RIGHT, + /* Shortcuts: text field */ + NK_KEY_TEXT_INSERT_MODE, + NK_KEY_TEXT_REPLACE_MODE, + NK_KEY_TEXT_RESET_MODE, + NK_KEY_TEXT_LINE_START, + NK_KEY_TEXT_LINE_END, + NK_KEY_TEXT_START, + NK_KEY_TEXT_END, + NK_KEY_TEXT_UNDO, + NK_KEY_TEXT_REDO, + NK_KEY_TEXT_SELECT_ALL, + NK_KEY_TEXT_WORD_LEFT, + NK_KEY_TEXT_WORD_RIGHT, + /* Shortcuts: scrollbar */ + NK_KEY_SCROLL_START, + NK_KEY_SCROLL_END, + NK_KEY_SCROLL_DOWN, + NK_KEY_SCROLL_UP, + NK_KEY_MAX +}; +enum nk_buttons { + NK_BUTTON_LEFT, + NK_BUTTON_MIDDLE, + NK_BUTTON_RIGHT, + NK_BUTTON_DOUBLE, + NK_BUTTON_MAX +}; +/*/// #### nk_input_begin +/// Begins the input mirroring process by resetting text, scroll +/// mouse, previous mouse position and movement as well as key state transitions, +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_input_begin(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to a previously initialized `nk_context` struct +*/ +NK_API void nk_input_begin(struct nk_context*); +/*/// #### nk_input_motion +/// Mirrors current mouse position to nuklear +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_input_motion(struct nk_context *ctx, int x, int y); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to a previously initialized `nk_context` struct +/// __x__ | Must hold an integer describing the current mouse cursor x-position +/// __y__ | Must hold an integer describing the current mouse cursor y-position +*/ +NK_API void nk_input_motion(struct nk_context*, int x, int y); +/*/// #### nk_input_key +/// Mirrors the state of a specific key to nuklear +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_input_key(struct nk_context*, enum nk_keys key, nk_bool down); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to a previously initialized `nk_context` struct +/// __key__ | Must be any value specified in enum `nk_keys` that needs to be mirrored +/// __down__ | Must be 0 for key is up and 1 for key is down +*/ +NK_API void nk_input_key(struct nk_context*, enum nk_keys, nk_bool down); +/*/// #### nk_input_button +/// Mirrors the state of a specific mouse button to nuklear +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_input_button(struct nk_context *ctx, enum nk_buttons btn, int x, int y, nk_bool down); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to a previously initialized `nk_context` struct +/// __btn__ | Must be any value specified in enum `nk_buttons` that needs to be mirrored +/// __x__ | Must contain an integer describing mouse cursor x-position on click up/down +/// __y__ | Must contain an integer describing mouse cursor y-position on click up/down +/// __down__ | Must be 0 for key is up and 1 for key is down +*/ +NK_API void nk_input_button(struct nk_context*, enum nk_buttons, int x, int y, nk_bool down); +/*/// #### nk_input_scroll +/// Copies the last mouse scroll value to nuklear. Is generally +/// a scroll value. So does not have to come from mouse and could also originate +/// TODO finish this sentence +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_input_scroll(struct nk_context *ctx, struct nk_vec2 val); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to a previously initialized `nk_context` struct +/// __val__ | vector with both X- as well as Y-scroll value +*/ +NK_API void nk_input_scroll(struct nk_context*, struct nk_vec2 val); +/*/// #### nk_input_char +/// Copies a single ASCII character into an internal text buffer +/// This is basically a helper function to quickly push ASCII characters into +/// nuklear. +/// +/// !!! Note +/// Stores up to NK_INPUT_MAX bytes between `nk_input_begin` and `nk_input_end`. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_input_char(struct nk_context *ctx, char c); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to a previously initialized `nk_context` struct +/// __c__ | Must be a single ASCII character preferable one that can be printed +*/ +NK_API void nk_input_char(struct nk_context*, char); +/*/// #### nk_input_glyph +/// Converts an encoded unicode rune into UTF-8 and copies the result into an +/// internal text buffer. +/// +/// !!! Note +/// Stores up to NK_INPUT_MAX bytes between `nk_input_begin` and `nk_input_end`. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_input_glyph(struct nk_context *ctx, const nk_glyph g); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to a previously initialized `nk_context` struct +/// __g__ | UTF-32 unicode codepoint +*/ +NK_API void nk_input_glyph(struct nk_context*, const nk_glyph); +/*/// #### nk_input_unicode +/// Converts a unicode rune into UTF-8 and copies the result +/// into an internal text buffer. +/// !!! Note +/// Stores up to NK_INPUT_MAX bytes between `nk_input_begin` and `nk_input_end`. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_input_unicode(struct nk_context*, nk_rune rune); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to a previously initialized `nk_context` struct +/// __rune__ | UTF-32 unicode codepoint +*/ +NK_API void nk_input_unicode(struct nk_context*, nk_rune); +/*/// #### nk_input_end +/// End the input mirroring process by resetting mouse grabbing +/// state to ensure the mouse cursor is not grabbed indefinitely. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_input_end(struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to a previously initialized `nk_context` struct +*/ +NK_API void nk_input_end(struct nk_context*); +/* ============================================================================= + * + * DRAWING + * + * =============================================================================*/ +/*/// ### Drawing +/// This library was designed to be render backend agnostic so it does +/// not draw anything to screen directly. Instead all drawn shapes, widgets +/// are made of, are buffered into memory and make up a command queue. +/// Each frame therefore fills the command buffer with draw commands +/// that then need to be executed by the user and his own render backend. +/// After that the command buffer needs to be cleared and a new frame can be +/// started. It is probably important to note that the command buffer is the main +/// drawing API and the optional vertex buffer API only takes this format and +/// converts it into a hardware accessible format. +/// +/// #### Usage +/// To draw all draw commands accumulated over a frame you need your own render +/// backend able to draw a number of 2D primitives. This includes at least +/// filled and stroked rectangles, circles, text, lines, triangles and scissors. +/// As soon as this criterion is met you can iterate over each draw command +/// and execute each draw command in a interpreter like fashion: +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// const struct nk_command *cmd = 0; +/// nk_foreach(cmd, &ctx) { +/// switch (cmd->type) { +/// case NK_COMMAND_LINE: +/// your_draw_line_function(...) +/// break; +/// case NK_COMMAND_RECT +/// your_draw_rect_function(...) +/// break; +/// case //...: +/// //[...] +/// } +/// } +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// In program flow context draw commands need to be executed after input has been +/// gathered and the complete UI with windows and their contained widgets have +/// been executed and before calling `nk_clear` which frees all previously +/// allocated draw commands. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_context ctx; +/// nk_init_xxx(&ctx, ...); +/// while (1) { +/// Event evt; +/// nk_input_begin(&ctx); +/// while (GetEvent(&evt)) { +/// if (evt.type == MOUSE_MOVE) +/// nk_input_motion(&ctx, evt.motion.x, evt.motion.y); +/// else if (evt.type == [...]) { +/// [...] +/// } +/// } +/// nk_input_end(&ctx); +/// // +/// // [...] +/// // +/// const struct nk_command *cmd = 0; +/// nk_foreach(cmd, &ctx) { +/// switch (cmd->type) { +/// case NK_COMMAND_LINE: +/// your_draw_line_function(...) +/// break; +/// case NK_COMMAND_RECT +/// your_draw_rect_function(...) +/// break; +/// case ...: +/// // [...] +/// } +/// nk_clear(&ctx); +/// } +/// nk_free(&ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// You probably noticed that you have to draw all of the UI each frame which is +/// quite wasteful. While the actual UI updating loop is quite fast rendering +/// without actually needing it is not. So there are multiple things you could do. +/// +/// First is only update on input. This of course is only an option if your +/// application only depends on the UI and does not require any outside calculations. +/// If you actually only update on input make sure to update the UI two times each +/// frame and call `nk_clear` directly after the first pass and only draw in +/// the second pass. In addition it is recommended to also add additional timers +/// to make sure the UI is not drawn more than a fixed number of frames per second. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_context ctx; +/// nk_init_xxx(&ctx, ...); +/// while (1) { +/// // [...wait for input ] +/// // [...do two UI passes ...] +/// do_ui(...) +/// nk_clear(&ctx); +/// do_ui(...) +/// // +/// // draw +/// const struct nk_command *cmd = 0; +/// nk_foreach(cmd, &ctx) { +/// switch (cmd->type) { +/// case NK_COMMAND_LINE: +/// your_draw_line_function(...) +/// break; +/// case NK_COMMAND_RECT +/// your_draw_rect_function(...) +/// break; +/// case ...: +/// //[...] +/// } +/// nk_clear(&ctx); +/// } +/// nk_free(&ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// The second probably more applicable trick is to only draw if anything changed. +/// It is not really useful for applications with continuous draw loop but +/// quite useful for desktop applications. To actually get nuklear to only +/// draw on changes you first have to define `NK_ZERO_COMMAND_MEMORY` and +/// allocate a memory buffer that will store each unique drawing output. +/// After each frame you compare the draw command memory inside the library +/// with your allocated buffer by memcmp. If memcmp detects differences +/// you have to copy the command buffer into the allocated buffer +/// and then draw like usual (this example uses fixed memory but you could +/// use dynamically allocated memory). +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// //[... other defines ...] +/// #define NK_ZERO_COMMAND_MEMORY +/// #include "nuklear.h" +/// // +/// // setup context +/// struct nk_context ctx; +/// void *last = calloc(1,64*1024); +/// void *buf = calloc(1,64*1024); +/// nk_init_fixed(&ctx, buf, 64*1024); +/// // +/// // loop +/// while (1) { +/// // [...input...] +/// // [...ui...] +/// void *cmds = nk_buffer_memory(&ctx.memory); +/// if (memcmp(cmds, last, ctx.memory.allocated)) { +/// memcpy(last,cmds,ctx.memory.allocated); +/// const struct nk_command *cmd = 0; +/// nk_foreach(cmd, &ctx) { +/// switch (cmd->type) { +/// case NK_COMMAND_LINE: +/// your_draw_line_function(...) +/// break; +/// case NK_COMMAND_RECT +/// your_draw_rect_function(...) +/// break; +/// case ...: +/// // [...] +/// } +/// } +/// } +/// nk_clear(&ctx); +/// } +/// nk_free(&ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Finally while using draw commands makes sense for higher abstracted platforms like +/// X11 and Win32 or drawing libraries it is often desirable to use graphics +/// hardware directly. Therefore it is possible to just define +/// `NK_INCLUDE_VERTEX_BUFFER_OUTPUT` which includes optional vertex output. +/// To access the vertex output you first have to convert all draw commands into +/// vertexes by calling `nk_convert` which takes in your preferred vertex format. +/// After successfully converting all draw commands just iterate over and execute all +/// vertex draw commands: +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// // fill configuration +/// struct your_vertex +/// { +/// float pos[2]; // important to keep it to 2 floats +/// float uv[2]; +/// unsigned char col[4]; +/// }; +/// struct nk_convert_config cfg = {}; +/// static const struct nk_draw_vertex_layout_element vertex_layout[] = { +/// {NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(struct your_vertex, pos)}, +/// {NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(struct your_vertex, uv)}, +/// {NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(struct your_vertex, col)}, +/// {NK_VERTEX_LAYOUT_END} +/// }; +/// cfg.shape_AA = NK_ANTI_ALIASING_ON; +/// cfg.line_AA = NK_ANTI_ALIASING_ON; +/// cfg.vertex_layout = vertex_layout; +/// cfg.vertex_size = sizeof(struct your_vertex); +/// cfg.vertex_alignment = NK_ALIGNOF(struct your_vertex); +/// cfg.circle_segment_count = 22; +/// cfg.curve_segment_count = 22; +/// cfg.arc_segment_count = 22; +/// cfg.global_alpha = 1.0f; +/// cfg.null = dev->null; +/// // +/// // setup buffers and convert +/// struct nk_buffer cmds, verts, idx; +/// nk_buffer_init_default(&cmds); +/// nk_buffer_init_default(&verts); +/// nk_buffer_init_default(&idx); +/// nk_convert(&ctx, &cmds, &verts, &idx, &cfg); +/// // +/// // draw +/// nk_draw_foreach(cmd, &ctx, &cmds) { +/// if (!cmd->elem_count) continue; +/// //[...] +/// } +/// nk_buffer_free(&cms); +/// nk_buffer_free(&verts); +/// nk_buffer_free(&idx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// #### Reference +/// Function | Description +/// --------------------|------------------------------------------------------- +/// __nk__begin__ | Returns the first draw command in the context draw command list to be drawn +/// __nk__next__ | Increments the draw command iterator to the next command inside the context draw command list +/// __nk_foreach__ | Iterates over each draw command inside the context draw command list +/// __nk_convert__ | Converts from the abstract draw commands list into a hardware accessible vertex format +/// __nk_draw_begin__ | Returns the first vertex command in the context vertex draw list to be executed +/// __nk__draw_next__ | Increments the vertex command iterator to the next command inside the context vertex command list +/// __nk__draw_end__ | Returns the end of the vertex draw list +/// __nk_draw_foreach__ | Iterates over each vertex draw command inside the vertex draw list +*/ +enum nk_anti_aliasing {NK_ANTI_ALIASING_OFF, NK_ANTI_ALIASING_ON}; +enum nk_convert_result { + NK_CONVERT_SUCCESS = 0, + NK_CONVERT_INVALID_PARAM = 1, + NK_CONVERT_COMMAND_BUFFER_FULL = NK_FLAG(1), + NK_CONVERT_VERTEX_BUFFER_FULL = NK_FLAG(2), + NK_CONVERT_ELEMENT_BUFFER_FULL = NK_FLAG(3) +}; +struct nk_draw_null_texture { + nk_handle texture; /* texture handle to a texture with a white pixel */ + struct nk_vec2 uv; /* coordinates to a white pixel in the texture */ +}; +struct nk_convert_config { + float global_alpha; /* global alpha value */ + enum nk_anti_aliasing line_AA; /* line anti-aliasing flag can be turned off if you are tight on memory */ + enum nk_anti_aliasing shape_AA; /* shape anti-aliasing flag can be turned off if you are tight on memory */ + unsigned circle_segment_count; /* number of segments used for circles: default to 22 */ + unsigned arc_segment_count; /* number of segments used for arcs: default to 22 */ + unsigned curve_segment_count; /* number of segments used for curves: default to 22 */ + struct nk_draw_null_texture null; /* handle to texture with a white pixel for shape drawing */ + const struct nk_draw_vertex_layout_element *vertex_layout; /* describes the vertex output format and packing */ + nk_size vertex_size; /* sizeof one vertex for vertex packing */ + nk_size vertex_alignment; /* vertex alignment: Can be obtained by NK_ALIGNOF */ +}; +/*/// #### nk__begin +/// Returns a draw command list iterator to iterate all draw +/// commands accumulated over one frame. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// const struct nk_command* nk__begin(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | must point to an previously initialized `nk_context` struct at the end of a frame +/// +/// Returns draw command pointer pointing to the first command inside the draw command list +*/ +NK_API const struct nk_command* nk__begin(struct nk_context*); +/*/// #### nk__next +/// Returns draw command pointer pointing to the next command inside the draw command list +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// const struct nk_command* nk__next(struct nk_context*, const struct nk_command*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct at the end of a frame +/// __cmd__ | Must point to an previously a draw command either returned by `nk__begin` or `nk__next` +/// +/// Returns draw command pointer pointing to the next command inside the draw command list +*/ +NK_API const struct nk_command* nk__next(struct nk_context*, const struct nk_command*); +/*/// #### nk_foreach +/// Iterates over each draw command inside the context draw command list +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// #define nk_foreach(c, ctx) +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct at the end of a frame +/// __cmd__ | Command pointer initialized to NULL +/// +/// Iterates over each draw command inside the context draw command list +*/ +#define nk_foreach(c, ctx) for((c) = nk__begin(ctx); (c) != 0; (c) = nk__next(ctx,c)) +#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT +/*/// #### nk_convert +/// Converts all internal draw commands into vertex draw commands and fills +/// three buffers with vertexes, vertex draw commands and vertex indices. The vertex format +/// as well as some other configuration values have to be configured by filling out a +/// `nk_convert_config` struct. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// nk_flags nk_convert(struct nk_context *ctx, struct nk_buffer *cmds, +/// struct nk_buffer *vertices, struct nk_buffer *elements, const struct nk_convert_config*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct at the end of a frame +/// __cmds__ | Must point to a previously initialized buffer to hold converted vertex draw commands +/// __vertices__| Must point to a previously initialized buffer to hold all produced vertices +/// __elements__| Must point to a previously initialized buffer to hold all produced vertex indices +/// __config__ | Must point to a filled out `nk_config` struct to configure the conversion process +/// +/// Returns one of enum nk_convert_result error codes +/// +/// Parameter | Description +/// --------------------------------|----------------------------------------------------------- +/// NK_CONVERT_SUCCESS | Signals a successful draw command to vertex buffer conversion +/// NK_CONVERT_INVALID_PARAM | An invalid argument was passed in the function call +/// NK_CONVERT_COMMAND_BUFFER_FULL | The provided buffer for storing draw commands is full or failed to allocate more memory +/// NK_CONVERT_VERTEX_BUFFER_FULL | The provided buffer for storing vertices is full or failed to allocate more memory +/// NK_CONVERT_ELEMENT_BUFFER_FULL | The provided buffer for storing indicies is full or failed to allocate more memory +*/ +NK_API nk_flags nk_convert(struct nk_context*, struct nk_buffer *cmds, struct nk_buffer *vertices, struct nk_buffer *elements, const struct nk_convert_config*); +/*/// #### nk__draw_begin +/// Returns a draw vertex command buffer iterator to iterate over the vertex draw command buffer +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// const struct nk_draw_command* nk__draw_begin(const struct nk_context*, const struct nk_buffer*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct at the end of a frame +/// __buf__ | Must point to an previously by `nk_convert` filled out vertex draw command buffer +/// +/// Returns vertex draw command pointer pointing to the first command inside the vertex draw command buffer +*/ +NK_API const struct nk_draw_command* nk__draw_begin(const struct nk_context*, const struct nk_buffer*); +/*/// #### nk__draw_end +/// Returns the vertex draw command at the end of the vertex draw command buffer +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// const struct nk_draw_command* nk__draw_end(const struct nk_context *ctx, const struct nk_buffer *buf); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct at the end of a frame +/// __buf__ | Must point to an previously by `nk_convert` filled out vertex draw command buffer +/// +/// Returns vertex draw command pointer pointing to the end of the last vertex draw command inside the vertex draw command buffer +*/ +NK_API const struct nk_draw_command* nk__draw_end(const struct nk_context*, const struct nk_buffer*); +/*/// #### nk__draw_next +/// Increments the vertex draw command buffer iterator +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// const struct nk_draw_command* nk__draw_next(const struct nk_draw_command*, const struct nk_buffer*, const struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __cmd__ | Must point to an previously either by `nk__draw_begin` or `nk__draw_next` returned vertex draw command +/// __buf__ | Must point to an previously by `nk_convert` filled out vertex draw command buffer +/// __ctx__ | Must point to an previously initialized `nk_context` struct at the end of a frame +/// +/// Returns vertex draw command pointer pointing to the end of the last vertex draw command inside the vertex draw command buffer +*/ +NK_API const struct nk_draw_command* nk__draw_next(const struct nk_draw_command*, const struct nk_buffer*, const struct nk_context*); +/*/// #### nk_draw_foreach +/// Iterates over each vertex draw command inside a vertex draw command buffer +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// #define nk_draw_foreach(cmd,ctx, b) +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __cmd__ | `nk_draw_command`iterator set to NULL +/// __buf__ | Must point to an previously by `nk_convert` filled out vertex draw command buffer +/// __ctx__ | Must point to an previously initialized `nk_context` struct at the end of a frame +*/ +#define nk_draw_foreach(cmd,ctx, b) for((cmd)=nk__draw_begin(ctx, b); (cmd)!=0; (cmd)=nk__draw_next(cmd, b, ctx)) +#endif +/* ============================================================================= + * + * WINDOW + * + * ============================================================================= +/// ### Window +/// Windows are the main persistent state used inside nuklear and are life time +/// controlled by simply "retouching" (i.e. calling) each window each frame. +/// All widgets inside nuklear can only be added inside the function pair `nk_begin_xxx` +/// and `nk_end`. Calling any widgets outside these two functions will result in an +/// assert in debug or no state change in release mode.

    +/// +/// Each window holds frame persistent state like position, size, flags, state tables, +/// and some garbage collected internal persistent widget state. Each window +/// is linked into a window stack list which determines the drawing and overlapping +/// order. The topmost window thereby is the currently active window.

    +/// +/// To change window position inside the stack occurs either automatically by +/// user input by being clicked on or programmatically by calling `nk_window_focus`. +/// Windows by default are visible unless explicitly being defined with flag +/// `NK_WINDOW_HIDDEN`, the user clicked the close button on windows with flag +/// `NK_WINDOW_CLOSABLE` or if a window was explicitly hidden by calling +/// `nk_window_show`. To explicitly close and destroy a window call `nk_window_close`.

    +/// +/// #### Usage +/// To create and keep a window you have to call one of the two `nk_begin_xxx` +/// functions to start window declarations and `nk_end` at the end. Furthermore it +/// is recommended to check the return value of `nk_begin_xxx` and only process +/// widgets inside the window if the value is not 0. Either way you have to call +/// `nk_end` at the end of window declarations. Furthermore, do not attempt to +/// nest `nk_begin_xxx` calls which will hopefully result in an assert or if not +/// in a segmentation fault. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// if (nk_begin_xxx(...) { +/// // [... widgets ...] +/// } +/// nk_end(ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// In the grand concept window and widget declarations need to occur after input +/// handling and before drawing to screen. Not doing so can result in higher +/// latency or at worst invalid behavior. Furthermore make sure that `nk_clear` +/// is called at the end of the frame. While nuklear's default platform backends +/// already call `nk_clear` for you if you write your own backend not calling +/// `nk_clear` can cause asserts or even worse undefined behavior. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_context ctx; +/// nk_init_xxx(&ctx, ...); +/// while (1) { +/// Event evt; +/// nk_input_begin(&ctx); +/// while (GetEvent(&evt)) { +/// if (evt.type == MOUSE_MOVE) +/// nk_input_motion(&ctx, evt.motion.x, evt.motion.y); +/// else if (evt.type == [...]) { +/// nk_input_xxx(...); +/// } +/// } +/// nk_input_end(&ctx); +/// +/// if (nk_begin_xxx(...) { +/// //[...] +/// } +/// nk_end(ctx); +/// +/// const struct nk_command *cmd = 0; +/// nk_foreach(cmd, &ctx) { +/// case NK_COMMAND_LINE: +/// your_draw_line_function(...) +/// break; +/// case NK_COMMAND_RECT +/// your_draw_rect_function(...) +/// break; +/// case //...: +/// //[...] +/// } +/// nk_clear(&ctx); +/// } +/// nk_free(&ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// #### Reference +/// Function | Description +/// ------------------------------------|---------------------------------------- +/// nk_begin | Starts a new window; needs to be called every frame for every window (unless hidden) or otherwise the window gets removed +/// nk_begin_titled | Extended window start with separated title and identifier to allow multiple windows with same name but not title +/// nk_end | Needs to be called at the end of the window building process to process scaling, scrollbars and general cleanup +// +/// nk_window_find | Finds and returns the window with give name +/// nk_window_get_bounds | Returns a rectangle with screen position and size of the currently processed window. +/// nk_window_get_position | Returns the position of the currently processed window +/// nk_window_get_size | Returns the size with width and height of the currently processed window +/// nk_window_get_width | Returns the width of the currently processed window +/// nk_window_get_height | Returns the height of the currently processed window +/// nk_window_get_panel | Returns the underlying panel which contains all processing state of the current window +/// nk_window_get_content_region | Returns the position and size of the currently visible and non-clipped space inside the currently processed window +/// nk_window_get_content_region_min | Returns the upper rectangle position of the currently visible and non-clipped space inside the currently processed window +/// nk_window_get_content_region_max | Returns the upper rectangle position of the currently visible and non-clipped space inside the currently processed window +/// nk_window_get_content_region_size | Returns the size of the currently visible and non-clipped space inside the currently processed window +/// nk_window_get_canvas | Returns the draw command buffer. Can be used to draw custom widgets +/// nk_window_get_scroll | Gets the scroll offset of the current window +/// nk_window_has_focus | Returns if the currently processed window is currently active +/// nk_window_is_collapsed | Returns if the window with given name is currently minimized/collapsed +/// nk_window_is_closed | Returns if the currently processed window was closed +/// nk_window_is_hidden | Returns if the currently processed window was hidden +/// nk_window_is_active | Same as nk_window_has_focus for some reason +/// nk_window_is_hovered | Returns if the currently processed window is currently being hovered by mouse +/// nk_window_is_any_hovered | Return if any window currently hovered +/// nk_item_is_any_active | Returns if any window or widgets is currently hovered or active +// +/// nk_window_set_bounds | Updates position and size of the currently processed window +/// nk_window_set_position | Updates position of the currently process window +/// nk_window_set_size | Updates the size of the currently processed window +/// nk_window_set_focus | Set the currently processed window as active window +/// nk_window_set_scroll | Sets the scroll offset of the current window +// +/// nk_window_close | Closes the window with given window name which deletes the window at the end of the frame +/// nk_window_collapse | Collapses the window with given window name +/// nk_window_collapse_if | Collapses the window with given window name if the given condition was met +/// nk_window_show | Hides a visible or reshows a hidden window +/// nk_window_show_if | Hides/shows a window depending on condition +*/ +/* +/// #### nk_panel_flags +/// Flag | Description +/// ----------------------------|---------------------------------------- +/// NK_WINDOW_BORDER | Draws a border around the window to visually separate window from the background +/// NK_WINDOW_MOVABLE | The movable flag indicates that a window can be moved by user input or by dragging the window header +/// NK_WINDOW_SCALABLE | The scalable flag indicates that a window can be scaled by user input by dragging a scaler icon at the button of the window +/// NK_WINDOW_CLOSABLE | Adds a closable icon into the header +/// NK_WINDOW_MINIMIZABLE | Adds a minimize icon into the header +/// NK_WINDOW_NO_SCROLLBAR | Removes the scrollbar from the window +/// NK_WINDOW_TITLE | Forces a header at the top at the window showing the title +/// NK_WINDOW_SCROLL_AUTO_HIDE | Automatically hides the window scrollbar if no user interaction: also requires delta time in `nk_context` to be set each frame +/// NK_WINDOW_BACKGROUND | Always keep window in the background +/// NK_WINDOW_SCALE_LEFT | Puts window scaler in the left-bottom corner instead right-bottom +/// NK_WINDOW_NO_INPUT | Prevents window of scaling, moving or getting focus +/// +/// #### nk_collapse_states +/// State | Description +/// ----------------|----------------------------------------------------------- +/// __NK_MINIMIZED__| UI section is collased and not visibile until maximized +/// __NK_MAXIMIZED__| UI section is extended and visibile until minimized +///

    +*/ +enum nk_panel_flags { + NK_WINDOW_BORDER = NK_FLAG(0), + NK_WINDOW_MOVABLE = NK_FLAG(1), + NK_WINDOW_SCALABLE = NK_FLAG(2), + NK_WINDOW_CLOSABLE = NK_FLAG(3), + NK_WINDOW_MINIMIZABLE = NK_FLAG(4), + NK_WINDOW_NO_SCROLLBAR = NK_FLAG(5), + NK_WINDOW_TITLE = NK_FLAG(6), + NK_WINDOW_SCROLL_AUTO_HIDE = NK_FLAG(7), + NK_WINDOW_BACKGROUND = NK_FLAG(8), + NK_WINDOW_SCALE_LEFT = NK_FLAG(9), + NK_WINDOW_NO_INPUT = NK_FLAG(10) +}; +/*/// #### nk_begin +/// Starts a new window; needs to be called every frame for every +/// window (unless hidden) or otherwise the window gets removed +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_begin(struct nk_context *ctx, const char *title, struct nk_rect bounds, nk_flags flags); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __title__ | Window title and identifier. Needs to be persistent over frames to identify the window +/// __bounds__ | Initial position and window size. However if you do not define `NK_WINDOW_SCALABLE` or `NK_WINDOW_MOVABLE` you can set window position and size every frame +/// __flags__ | Window flags defined in the nk_panel_flags section with a number of different window behaviors +/// +/// Returns `true(1)` if the window can be filled up with widgets from this point +/// until `nk_end` or `false(0)` otherwise for example if minimized +*/ +NK_API nk_bool nk_begin(struct nk_context *ctx, const char *title, struct nk_rect bounds, nk_flags flags); +/*/// #### nk_begin_titled +/// Extended window start with separated title and identifier to allow multiple +/// windows with same title but not name +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_begin_titled(struct nk_context *ctx, const char *name, const char *title, struct nk_rect bounds, nk_flags flags); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Window identifier. Needs to be persistent over frames to identify the window +/// __title__ | Window title displayed inside header if flag `NK_WINDOW_TITLE` or either `NK_WINDOW_CLOSABLE` or `NK_WINDOW_MINIMIZED` was set +/// __bounds__ | Initial position and window size. However if you do not define `NK_WINDOW_SCALABLE` or `NK_WINDOW_MOVABLE` you can set window position and size every frame +/// __flags__ | Window flags defined in the nk_panel_flags section with a number of different window behaviors +/// +/// Returns `true(1)` if the window can be filled up with widgets from this point +/// until `nk_end` or `false(0)` otherwise for example if minimized +*/ +NK_API nk_bool nk_begin_titled(struct nk_context *ctx, const char *name, const char *title, struct nk_rect bounds, nk_flags flags); +/*/// #### nk_end +/// Needs to be called at the end of the window building process to process scaling, scrollbars and general cleanup. +/// All widget calls after this functions will result in asserts or no state changes +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_end(struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +*/ +NK_API void nk_end(struct nk_context *ctx); +/*/// #### nk_window_find +/// Finds and returns a window from passed name +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_window *nk_window_find(struct nk_context *ctx, const char *name); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Window identifier +/// +/// Returns a `nk_window` struct pointing to the identified window or NULL if +/// no window with the given name was found +*/ +NK_API struct nk_window *nk_window_find(struct nk_context *ctx, const char *name); +/*/// #### nk_window_get_bounds +/// Returns a rectangle with screen position and size of the currently processed window +/// +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_rect nk_window_get_bounds(const struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// Returns a `nk_rect` struct with window upper left window position and size +*/ +NK_API struct nk_rect nk_window_get_bounds(const struct nk_context *ctx); +/*/// #### nk_window_get_position +/// Returns the position of the currently processed window. +/// +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_vec2 nk_window_get_position(const struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// Returns a `nk_vec2` struct with window upper left position +*/ +NK_API struct nk_vec2 nk_window_get_position(const struct nk_context *ctx); +/*/// #### nk_window_get_size +/// Returns the size with width and height of the currently processed window. +/// +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_vec2 nk_window_get_size(const struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// Returns a `nk_vec2` struct with window width and height +*/ +NK_API struct nk_vec2 nk_window_get_size(const struct nk_context*); +/*/// #### nk_window_get_width +/// Returns the width of the currently processed window. +/// +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// float nk_window_get_width(const struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// Returns the current window width +*/ +NK_API float nk_window_get_width(const struct nk_context*); +/*/// #### nk_window_get_height +/// Returns the height of the currently processed window. +/// +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// float nk_window_get_height(const struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// Returns the current window height +*/ +NK_API float nk_window_get_height(const struct nk_context*); +/*/// #### nk_window_get_panel +/// Returns the underlying panel which contains all processing state of the current window. +/// +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// !!! WARNING +/// Do not keep the returned panel pointer around, it is only valid until `nk_end` +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_panel* nk_window_get_panel(struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// Returns a pointer to window internal `nk_panel` state. +*/ +NK_API struct nk_panel* nk_window_get_panel(struct nk_context*); +/*/// #### nk_window_get_content_region +/// Returns the position and size of the currently visible and non-clipped space +/// inside the currently processed window. +/// +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_rect nk_window_get_content_region(struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// Returns `nk_rect` struct with screen position and size (no scrollbar offset) +/// of the visible space inside the current window +*/ +NK_API struct nk_rect nk_window_get_content_region(struct nk_context*); +/*/// #### nk_window_get_content_region_min +/// Returns the upper left position of the currently visible and non-clipped +/// space inside the currently processed window. +/// +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_vec2 nk_window_get_content_region_min(struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// returns `nk_vec2` struct with upper left screen position (no scrollbar offset) +/// of the visible space inside the current window +*/ +NK_API struct nk_vec2 nk_window_get_content_region_min(struct nk_context*); +/*/// #### nk_window_get_content_region_max +/// Returns the lower right screen position of the currently visible and +/// non-clipped space inside the currently processed window. +/// +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_vec2 nk_window_get_content_region_max(struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// Returns `nk_vec2` struct with lower right screen position (no scrollbar offset) +/// of the visible space inside the current window +*/ +NK_API struct nk_vec2 nk_window_get_content_region_max(struct nk_context*); +/*/// #### nk_window_get_content_region_size +/// Returns the size of the currently visible and non-clipped space inside the +/// currently processed window +/// +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_vec2 nk_window_get_content_region_size(struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// Returns `nk_vec2` struct with size the visible space inside the current window +*/ +NK_API struct nk_vec2 nk_window_get_content_region_size(struct nk_context*); +/*/// #### nk_window_get_canvas +/// Returns the draw command buffer. Can be used to draw custom widgets +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// !!! WARNING +/// Do not keep the returned command buffer pointer around it is only valid until `nk_end` +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_command_buffer* nk_window_get_canvas(struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// Returns a pointer to window internal `nk_command_buffer` struct used as +/// drawing canvas. Can be used to do custom drawing. +*/ +NK_API struct nk_command_buffer* nk_window_get_canvas(struct nk_context*); +/*/// #### nk_window_get_scroll +/// Gets the scroll offset for the current window +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_window_get_scroll(struct nk_context *ctx, nk_uint *offset_x, nk_uint *offset_y); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// -------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __offset_x__ | A pointer to the x offset output (or NULL to ignore) +/// __offset_y__ | A pointer to the y offset output (or NULL to ignore) +*/ +NK_API void nk_window_get_scroll(struct nk_context*, nk_uint *offset_x, nk_uint *offset_y); +/*/// #### nk_window_has_focus +/// Returns if the currently processed window is currently active +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_window_has_focus(const struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// Returns `false(0)` if current window is not active or `true(1)` if it is +*/ +NK_API nk_bool nk_window_has_focus(const struct nk_context*); +/*/// #### nk_window_is_hovered +/// Return if the current window is being hovered +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_window_is_hovered(struct nk_context *ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// Returns `true(1)` if current window is hovered or `false(0)` otherwise +*/ +NK_API nk_bool nk_window_is_hovered(struct nk_context*); +/*/// #### nk_window_is_collapsed +/// Returns if the window with given name is currently minimized/collapsed +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_window_is_collapsed(struct nk_context *ctx, const char *name); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Identifier of window you want to check if it is collapsed +/// +/// Returns `true(1)` if current window is minimized and `false(0)` if window not +/// found or is not minimized +*/ +NK_API nk_bool nk_window_is_collapsed(struct nk_context *ctx, const char *name); +/*/// #### nk_window_is_closed +/// Returns if the window with given name was closed by calling `nk_close` +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_window_is_closed(struct nk_context *ctx, const char *name); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Identifier of window you want to check if it is closed +/// +/// Returns `true(1)` if current window was closed or `false(0)` window not found or not closed +*/ +NK_API nk_bool nk_window_is_closed(struct nk_context*, const char*); +/*/// #### nk_window_is_hidden +/// Returns if the window with given name is hidden +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_window_is_hidden(struct nk_context *ctx, const char *name); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Identifier of window you want to check if it is hidden +/// +/// Returns `true(1)` if current window is hidden or `false(0)` window not found or visible +*/ +NK_API nk_bool nk_window_is_hidden(struct nk_context*, const char*); +/*/// #### nk_window_is_active +/// Same as nk_window_has_focus for some reason +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_window_is_active(struct nk_context *ctx, const char *name); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Identifier of window you want to check if it is active +/// +/// Returns `true(1)` if current window is active or `false(0)` window not found or not active +*/ +NK_API nk_bool nk_window_is_active(struct nk_context*, const char*); +/*/// #### nk_window_is_any_hovered +/// Returns if the any window is being hovered +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_window_is_any_hovered(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// Returns `true(1)` if any window is hovered or `false(0)` otherwise +*/ +NK_API nk_bool nk_window_is_any_hovered(struct nk_context*); +/*/// #### nk_item_is_any_active +/// Returns if the any window is being hovered or any widget is currently active. +/// Can be used to decide if input should be processed by UI or your specific input handling. +/// Example could be UI and 3D camera to move inside a 3D space. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_item_is_any_active(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// +/// Returns `true(1)` if any window is hovered or any item is active or `false(0)` otherwise +*/ +NK_API nk_bool nk_item_is_any_active(struct nk_context*); +/*/// #### nk_window_set_bounds +/// Updates position and size of window with passed in name +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_window_set_bounds(struct nk_context*, const char *name, struct nk_rect bounds); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Identifier of the window to modify both position and size +/// __bounds__ | Must point to a `nk_rect` struct with the new position and size +*/ +NK_API void nk_window_set_bounds(struct nk_context*, const char *name, struct nk_rect bounds); +/*/// #### nk_window_set_position +/// Updates position of window with passed name +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_window_set_position(struct nk_context*, const char *name, struct nk_vec2 pos); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Identifier of the window to modify both position +/// __pos__ | Must point to a `nk_vec2` struct with the new position +*/ +NK_API void nk_window_set_position(struct nk_context*, const char *name, struct nk_vec2 pos); +/*/// #### nk_window_set_size +/// Updates size of window with passed in name +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_window_set_size(struct nk_context*, const char *name, struct nk_vec2); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Identifier of the window to modify both window size +/// __size__ | Must point to a `nk_vec2` struct with new window size +*/ +NK_API void nk_window_set_size(struct nk_context*, const char *name, struct nk_vec2); +/*/// #### nk_window_set_focus +/// Sets the window with given name as active +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_window_set_focus(struct nk_context*, const char *name); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Identifier of the window to set focus on +*/ +NK_API void nk_window_set_focus(struct nk_context*, const char *name); +/*/// #### nk_window_set_scroll +/// Sets the scroll offset for the current window +/// !!! WARNING +/// Only call this function between calls `nk_begin_xxx` and `nk_end` +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_window_set_scroll(struct nk_context *ctx, nk_uint offset_x, nk_uint offset_y); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// -------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __offset_x__ | The x offset to scroll to +/// __offset_y__ | The y offset to scroll to +*/ +NK_API void nk_window_set_scroll(struct nk_context*, nk_uint offset_x, nk_uint offset_y); +/*/// #### nk_window_close +/// Closes a window and marks it for being freed at the end of the frame +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_window_close(struct nk_context *ctx, const char *name); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Identifier of the window to close +*/ +NK_API void nk_window_close(struct nk_context *ctx, const char *name); +/*/// #### nk_window_collapse +/// Updates collapse state of a window with given name +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_window_collapse(struct nk_context*, const char *name, enum nk_collapse_states state); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Identifier of the window to close +/// __state__ | value out of nk_collapse_states section +*/ +NK_API void nk_window_collapse(struct nk_context*, const char *name, enum nk_collapse_states state); +/*/// #### nk_window_collapse_if +/// Updates collapse state of a window with given name if given condition is met +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_window_collapse_if(struct nk_context*, const char *name, enum nk_collapse_states, int cond); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Identifier of the window to either collapse or maximize +/// __state__ | value out of nk_collapse_states section the window should be put into +/// __cond__ | condition that has to be met to actually commit the collapse state change +*/ +NK_API void nk_window_collapse_if(struct nk_context*, const char *name, enum nk_collapse_states, int cond); +/*/// #### nk_window_show +/// updates visibility state of a window with given name +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_window_show(struct nk_context*, const char *name, enum nk_show_states); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Identifier of the window to either collapse or maximize +/// __state__ | state with either visible or hidden to modify the window with +*/ +NK_API void nk_window_show(struct nk_context*, const char *name, enum nk_show_states); +/*/// #### nk_window_show_if +/// Updates visibility state of a window with given name if a given condition is met +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_window_show_if(struct nk_context*, const char *name, enum nk_show_states, int cond); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __name__ | Identifier of the window to either hide or show +/// __state__ | state with either visible or hidden to modify the window with +/// __cond__ | condition that has to be met to actually commit the visbility state change +*/ +NK_API void nk_window_show_if(struct nk_context*, const char *name, enum nk_show_states, int cond); +/* ============================================================================= + * + * LAYOUT + * + * ============================================================================= +/// ### Layouting +/// Layouting in general describes placing widget inside a window with position and size. +/// While in this particular implementation there are five different APIs for layouting +/// each with different trade offs between control and ease of use.

    +/// +/// All layouting methods in this library are based around the concept of a row. +/// A row has a height the window content grows by and a number of columns and each +/// layouting method specifies how each widget is placed inside the row. +/// After a row has been allocated by calling a layouting functions and then +/// filled with widgets will advance an internal pointer over the allocated row.

    +/// +/// To actually define a layout you just call the appropriate layouting function +/// and each subsequent widget call will place the widget as specified. Important +/// here is that if you define more widgets then columns defined inside the layout +/// functions it will allocate the next row without you having to make another layouting

    +/// call. +/// +/// Biggest limitation with using all these APIs outside the `nk_layout_space_xxx` API +/// is that you have to define the row height for each. However the row height +/// often depends on the height of the font.

    +/// +/// To fix that internally nuklear uses a minimum row height that is set to the +/// height plus padding of currently active font and overwrites the row height +/// value if zero.

    +/// +/// If you manually want to change the minimum row height then +/// use nk_layout_set_min_row_height, and use nk_layout_reset_min_row_height to +/// reset it back to be derived from font height.

    +/// +/// Also if you change the font in nuklear it will automatically change the minimum +/// row height for you and. This means if you change the font but still want +/// a minimum row height smaller than the font you have to repush your value.

    +/// +/// For actually more advanced UI I would even recommend using the `nk_layout_space_xxx` +/// layouting method in combination with a cassowary constraint solver (there are +/// some versions on github with permissive license model) to take over all control over widget +/// layouting yourself. However for quick and dirty layouting using all the other layouting +/// functions should be fine. +/// +/// #### Usage +/// 1. __nk_layout_row_dynamic__

    +/// The easiest layouting function is `nk_layout_row_dynamic`. It provides each +/// widgets with same horizontal space inside the row and dynamically grows +/// if the owning window grows in width. So the number of columns dictates +/// the size of each widget dynamically by formula: +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// widget_width = (window_width - padding - spacing) * (1/colum_count) +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Just like all other layouting APIs if you define more widget than columns this +/// library will allocate a new row and keep all layouting parameters previously +/// defined. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// if (nk_begin_xxx(...) { +/// // first row with height: 30 composed of two widgets +/// nk_layout_row_dynamic(&ctx, 30, 2); +/// nk_widget(...); +/// nk_widget(...); +/// // +/// // second row with same parameter as defined above +/// nk_widget(...); +/// nk_widget(...); +/// // +/// // third row uses 0 for height which will use auto layouting +/// nk_layout_row_dynamic(&ctx, 0, 2); +/// nk_widget(...); +/// nk_widget(...); +/// } +/// nk_end(...); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// 2. __nk_layout_row_static__

    +/// Another easy layouting function is `nk_layout_row_static`. It provides each +/// widget with same horizontal pixel width inside the row and does not grow +/// if the owning window scales smaller or bigger. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// if (nk_begin_xxx(...) { +/// // first row with height: 30 composed of two widgets with width: 80 +/// nk_layout_row_static(&ctx, 30, 80, 2); +/// nk_widget(...); +/// nk_widget(...); +/// // +/// // second row with same parameter as defined above +/// nk_widget(...); +/// nk_widget(...); +/// // +/// // third row uses 0 for height which will use auto layouting +/// nk_layout_row_static(&ctx, 0, 80, 2); +/// nk_widget(...); +/// nk_widget(...); +/// } +/// nk_end(...); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// 3. __nk_layout_row_xxx__

    +/// A little bit more advanced layouting API are functions `nk_layout_row_begin`, +/// `nk_layout_row_push` and `nk_layout_row_end`. They allow to directly +/// specify each column pixel or window ratio in a row. It supports either +/// directly setting per column pixel width or widget window ratio but not +/// both. Furthermore it is a immediate mode API so each value is directly +/// pushed before calling a widget. Therefore the layout is not automatically +/// repeating like the last two layouting functions. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// if (nk_begin_xxx(...) { +/// // first row with height: 25 composed of two widgets with width 60 and 40 +/// nk_layout_row_begin(ctx, NK_STATIC, 25, 2); +/// nk_layout_row_push(ctx, 60); +/// nk_widget(...); +/// nk_layout_row_push(ctx, 40); +/// nk_widget(...); +/// nk_layout_row_end(ctx); +/// // +/// // second row with height: 25 composed of two widgets with window ratio 0.25 and 0.75 +/// nk_layout_row_begin(ctx, NK_DYNAMIC, 25, 2); +/// nk_layout_row_push(ctx, 0.25f); +/// nk_widget(...); +/// nk_layout_row_push(ctx, 0.75f); +/// nk_widget(...); +/// nk_layout_row_end(ctx); +/// // +/// // third row with auto generated height: composed of two widgets with window ratio 0.25 and 0.75 +/// nk_layout_row_begin(ctx, NK_DYNAMIC, 0, 2); +/// nk_layout_row_push(ctx, 0.25f); +/// nk_widget(...); +/// nk_layout_row_push(ctx, 0.75f); +/// nk_widget(...); +/// nk_layout_row_end(ctx); +/// } +/// nk_end(...); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// 4. __nk_layout_row__

    +/// The array counterpart to API nk_layout_row_xxx is the single nk_layout_row +/// functions. Instead of pushing either pixel or window ratio for every widget +/// it allows to define it by array. The trade of for less control is that +/// `nk_layout_row` is automatically repeating. Otherwise the behavior is the +/// same. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// if (nk_begin_xxx(...) { +/// // two rows with height: 30 composed of two widgets with width 60 and 40 +/// const float size[] = {60,40}; +/// nk_layout_row(ctx, NK_STATIC, 30, 2, ratio); +/// nk_widget(...); +/// nk_widget(...); +/// nk_widget(...); +/// nk_widget(...); +/// // +/// // two rows with height: 30 composed of two widgets with window ratio 0.25 and 0.75 +/// const float ratio[] = {0.25, 0.75}; +/// nk_layout_row(ctx, NK_DYNAMIC, 30, 2, ratio); +/// nk_widget(...); +/// nk_widget(...); +/// nk_widget(...); +/// nk_widget(...); +/// // +/// // two rows with auto generated height composed of two widgets with window ratio 0.25 and 0.75 +/// const float ratio[] = {0.25, 0.75}; +/// nk_layout_row(ctx, NK_DYNAMIC, 30, 2, ratio); +/// nk_widget(...); +/// nk_widget(...); +/// nk_widget(...); +/// nk_widget(...); +/// } +/// nk_end(...); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// 5. __nk_layout_row_template_xxx__

    +/// The most complex and second most flexible API is a simplified flexbox version without +/// line wrapping and weights for dynamic widgets. It is an immediate mode API but +/// unlike `nk_layout_row_xxx` it has auto repeat behavior and needs to be called +/// before calling the templated widgets. +/// The row template layout has three different per widget size specifier. The first +/// one is the `nk_layout_row_template_push_static` with fixed widget pixel width. +/// They do not grow if the row grows and will always stay the same. +/// The second size specifier is `nk_layout_row_template_push_variable` +/// which defines a minimum widget size but it also can grow if more space is available +/// not taken by other widgets. +/// Finally there are dynamic widgets with `nk_layout_row_template_push_dynamic` +/// which are completely flexible and unlike variable widgets can even shrink +/// to zero if not enough space is provided. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// if (nk_begin_xxx(...) { +/// // two rows with height: 30 composed of three widgets +/// nk_layout_row_template_begin(ctx, 30); +/// nk_layout_row_template_push_dynamic(ctx); +/// nk_layout_row_template_push_variable(ctx, 80); +/// nk_layout_row_template_push_static(ctx, 80); +/// nk_layout_row_template_end(ctx); +/// // +/// // first row +/// nk_widget(...); // dynamic widget can go to zero if not enough space +/// nk_widget(...); // variable widget with min 80 pixel but can grow bigger if enough space +/// nk_widget(...); // static widget with fixed 80 pixel width +/// // +/// // second row same layout +/// nk_widget(...); +/// nk_widget(...); +/// nk_widget(...); +/// } +/// nk_end(...); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// 6. __nk_layout_space_xxx__

    +/// Finally the most flexible API directly allows you to place widgets inside the +/// window. The space layout API is an immediate mode API which does not support +/// row auto repeat and directly sets position and size of a widget. Position +/// and size hereby can be either specified as ratio of allocated space or +/// allocated space local position and pixel size. Since this API is quite +/// powerful there are a number of utility functions to get the available space +/// and convert between local allocated space and screen space. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// if (nk_begin_xxx(...) { +/// // static row with height: 500 (you can set column count to INT_MAX if you don't want to be bothered) +/// nk_layout_space_begin(ctx, NK_STATIC, 500, INT_MAX); +/// nk_layout_space_push(ctx, nk_rect(0,0,150,200)); +/// nk_widget(...); +/// nk_layout_space_push(ctx, nk_rect(200,200,100,200)); +/// nk_widget(...); +/// nk_layout_space_end(ctx); +/// // +/// // dynamic row with height: 500 (you can set column count to INT_MAX if you don't want to be bothered) +/// nk_layout_space_begin(ctx, NK_DYNAMIC, 500, INT_MAX); +/// nk_layout_space_push(ctx, nk_rect(0.5,0.5,0.1,0.1)); +/// nk_widget(...); +/// nk_layout_space_push(ctx, nk_rect(0.7,0.6,0.1,0.1)); +/// nk_widget(...); +/// } +/// nk_end(...); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// #### Reference +/// Function | Description +/// ----------------------------------------|------------------------------------ +/// nk_layout_set_min_row_height | Set the currently used minimum row height to a specified value +/// nk_layout_reset_min_row_height | Resets the currently used minimum row height to font height +/// nk_layout_widget_bounds | Calculates current width a static layout row can fit inside a window +/// nk_layout_ratio_from_pixel | Utility functions to calculate window ratio from pixel size +// +/// nk_layout_row_dynamic | Current layout is divided into n same sized growing columns +/// nk_layout_row_static | Current layout is divided into n same fixed sized columns +/// nk_layout_row_begin | Starts a new row with given height and number of columns +/// nk_layout_row_push | Pushes another column with given size or window ratio +/// nk_layout_row_end | Finished previously started row +/// nk_layout_row | Specifies row columns in array as either window ratio or size +// +/// nk_layout_row_template_begin | Begins the row template declaration +/// nk_layout_row_template_push_dynamic | Adds a dynamic column that dynamically grows and can go to zero if not enough space +/// nk_layout_row_template_push_variable | Adds a variable column that dynamically grows but does not shrink below specified pixel width +/// nk_layout_row_template_push_static | Adds a static column that does not grow and will always have the same size +/// nk_layout_row_template_end | Marks the end of the row template +// +/// nk_layout_space_begin | Begins a new layouting space that allows to specify each widgets position and size +/// nk_layout_space_push | Pushes position and size of the next widget in own coordinate space either as pixel or ratio +/// nk_layout_space_end | Marks the end of the layouting space +// +/// nk_layout_space_bounds | Callable after nk_layout_space_begin and returns total space allocated +/// nk_layout_space_to_screen | Converts vector from nk_layout_space coordinate space into screen space +/// nk_layout_space_to_local | Converts vector from screen space into nk_layout_space coordinates +/// nk_layout_space_rect_to_screen | Converts rectangle from nk_layout_space coordinate space into screen space +/// nk_layout_space_rect_to_local | Converts rectangle from screen space into nk_layout_space coordinates +*/ +/*/// #### nk_layout_set_min_row_height +/// Sets the currently used minimum row height. +/// !!! WARNING +/// The passed height needs to include both your preferred row height +/// as well as padding. No internal padding is added. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_set_min_row_height(struct nk_context*, float height); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +/// __height__ | New minimum row height to be used for auto generating the row height +*/ +NK_API void nk_layout_set_min_row_height(struct nk_context*, float height); +/*/// #### nk_layout_reset_min_row_height +/// Reset the currently used minimum row height back to `font_height + text_padding + padding` +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_reset_min_row_height(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +*/ +NK_API void nk_layout_reset_min_row_height(struct nk_context*); +/*/// #### nk_layout_widget_bounds +/// Returns the width of the next row allocate by one of the layouting functions +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_rect nk_layout_widget_bounds(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +/// +/// Return `nk_rect` with both position and size of the next row +*/ +NK_API struct nk_rect nk_layout_widget_bounds(struct nk_context*); +/*/// #### nk_layout_ratio_from_pixel +/// Utility functions to calculate window ratio from pixel size +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// float nk_layout_ratio_from_pixel(struct nk_context*, float pixel_width); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +/// __pixel__ | Pixel_width to convert to window ratio +/// +/// Returns `nk_rect` with both position and size of the next row +*/ +NK_API float nk_layout_ratio_from_pixel(struct nk_context*, float pixel_width); +/*/// #### nk_layout_row_dynamic +/// Sets current row layout to share horizontal space +/// between @cols number of widgets evenly. Once called all subsequent widget +/// calls greater than @cols will allocate a new row with same layout. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_row_dynamic(struct nk_context *ctx, float height, int cols); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +/// __height__ | Holds height of each widget in row or zero for auto layouting +/// __columns__ | Number of widget inside row +*/ +NK_API void nk_layout_row_dynamic(struct nk_context *ctx, float height, int cols); +/*/// #### nk_layout_row_static +/// Sets current row layout to fill @cols number of widgets +/// in row with same @item_width horizontal size. Once called all subsequent widget +/// calls greater than @cols will allocate a new row with same layout. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_row_static(struct nk_context *ctx, float height, int item_width, int cols); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +/// __height__ | Holds height of each widget in row or zero for auto layouting +/// __width__ | Holds pixel width of each widget in the row +/// __columns__ | Number of widget inside row +*/ +NK_API void nk_layout_row_static(struct nk_context *ctx, float height, int item_width, int cols); +/*/// #### nk_layout_row_begin +/// Starts a new dynamic or fixed row with given height and columns. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_row_begin(struct nk_context *ctx, enum nk_layout_format fmt, float row_height, int cols); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +/// __fmt__ | either `NK_DYNAMIC` for window ratio or `NK_STATIC` for fixed size columns +/// __height__ | holds height of each widget in row or zero for auto layouting +/// __columns__ | Number of widget inside row +*/ +NK_API void nk_layout_row_begin(struct nk_context *ctx, enum nk_layout_format fmt, float row_height, int cols); +/*/// #### nk_layout_row_push +/// Specifies either window ratio or width of a single column +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_row_push(struct nk_context*, float value); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +/// __value__ | either a window ratio or fixed width depending on @fmt in previous `nk_layout_row_begin` call +*/ +NK_API void nk_layout_row_push(struct nk_context*, float value); +/*/// #### nk_layout_row_end +/// Finished previously started row +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_row_end(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +*/ +NK_API void nk_layout_row_end(struct nk_context*); +/*/// #### nk_layout_row +/// Specifies row columns in array as either window ratio or size +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_row(struct nk_context*, enum nk_layout_format, float height, int cols, const float *ratio); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +/// __fmt__ | Either `NK_DYNAMIC` for window ratio or `NK_STATIC` for fixed size columns +/// __height__ | Holds height of each widget in row or zero for auto layouting +/// __columns__ | Number of widget inside row +*/ +NK_API void nk_layout_row(struct nk_context*, enum nk_layout_format, float height, int cols, const float *ratio); +/*/// #### nk_layout_row_template_begin +/// Begins the row template declaration +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_row_template_begin(struct nk_context*, float row_height); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +/// __height__ | Holds height of each widget in row or zero for auto layouting +*/ +NK_API void nk_layout_row_template_begin(struct nk_context*, float row_height); +/*/// #### nk_layout_row_template_push_dynamic +/// Adds a dynamic column that dynamically grows and can go to zero if not enough space +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_row_template_push_dynamic(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +/// __height__ | Holds height of each widget in row or zero for auto layouting +*/ +NK_API void nk_layout_row_template_push_dynamic(struct nk_context*); +/*/// #### nk_layout_row_template_push_variable +/// Adds a variable column that dynamically grows but does not shrink below specified pixel width +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_row_template_push_variable(struct nk_context*, float min_width); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +/// __width__ | Holds the minimum pixel width the next column must always be +*/ +NK_API void nk_layout_row_template_push_variable(struct nk_context*, float min_width); +/*/// #### nk_layout_row_template_push_static +/// Adds a static column that does not grow and will always have the same size +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_row_template_push_static(struct nk_context*, float width); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +/// __width__ | Holds the absolute pixel width value the next column must be +*/ +NK_API void nk_layout_row_template_push_static(struct nk_context*, float width); +/*/// #### nk_layout_row_template_end +/// Marks the end of the row template +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_row_template_end(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +*/ +NK_API void nk_layout_row_template_end(struct nk_context*); +/*/// #### nk_layout_space_begin +/// Begins a new layouting space that allows to specify each widgets position and size. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_space_begin(struct nk_context*, enum nk_layout_format, float height, int widget_count); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_begin_xxx` +/// __fmt__ | Either `NK_DYNAMIC` for window ratio or `NK_STATIC` for fixed size columns +/// __height__ | Holds height of each widget in row or zero for auto layouting +/// __columns__ | Number of widgets inside row +*/ +NK_API void nk_layout_space_begin(struct nk_context*, enum nk_layout_format, float height, int widget_count); +/*/// #### nk_layout_space_push +/// Pushes position and size of the next widget in own coordinate space either as pixel or ratio +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_space_push(struct nk_context *ctx, struct nk_rect bounds); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_layout_space_begin` +/// __bounds__ | Position and size in laoyut space local coordinates +*/ +NK_API void nk_layout_space_push(struct nk_context*, struct nk_rect bounds); +/*/// #### nk_layout_space_end +/// Marks the end of the layout space +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_layout_space_end(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_layout_space_begin` +*/ +NK_API void nk_layout_space_end(struct nk_context*); +/*/// #### nk_layout_space_bounds +/// Utility function to calculate total space allocated for `nk_layout_space` +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_rect nk_layout_space_bounds(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_layout_space_begin` +/// +/// Returns `nk_rect` holding the total space allocated +*/ +NK_API struct nk_rect nk_layout_space_bounds(struct nk_context*); +/*/// #### nk_layout_space_to_screen +/// Converts vector from nk_layout_space coordinate space into screen space +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_vec2 nk_layout_space_to_screen(struct nk_context*, struct nk_vec2); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_layout_space_begin` +/// __vec__ | Position to convert from layout space into screen coordinate space +/// +/// Returns transformed `nk_vec2` in screen space coordinates +*/ +NK_API struct nk_vec2 nk_layout_space_to_screen(struct nk_context*, struct nk_vec2); +/*/// #### nk_layout_space_to_local +/// Converts vector from layout space into screen space +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_vec2 nk_layout_space_to_local(struct nk_context*, struct nk_vec2); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_layout_space_begin` +/// __vec__ | Position to convert from screen space into layout coordinate space +/// +/// Returns transformed `nk_vec2` in layout space coordinates +*/ +NK_API struct nk_vec2 nk_layout_space_to_local(struct nk_context*, struct nk_vec2); +/*/// #### nk_layout_space_rect_to_screen +/// Converts rectangle from screen space into layout space +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_rect nk_layout_space_rect_to_screen(struct nk_context*, struct nk_rect); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_layout_space_begin` +/// __bounds__ | Rectangle to convert from layout space into screen space +/// +/// Returns transformed `nk_rect` in screen space coordinates +*/ +NK_API struct nk_rect nk_layout_space_rect_to_screen(struct nk_context*, struct nk_rect); +/*/// #### nk_layout_space_rect_to_local +/// Converts rectangle from layout space into screen space +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_rect nk_layout_space_rect_to_local(struct nk_context*, struct nk_rect); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after call `nk_layout_space_begin` +/// __bounds__ | Rectangle to convert from layout space into screen space +/// +/// Returns transformed `nk_rect` in layout space coordinates +*/ +NK_API struct nk_rect nk_layout_space_rect_to_local(struct nk_context*, struct nk_rect); +/* ============================================================================= + * + * GROUP + * + * ============================================================================= +/// ### Groups +/// Groups are basically windows inside windows. They allow to subdivide space +/// in a window to layout widgets as a group. Almost all more complex widget +/// layouting requirements can be solved using groups and basic layouting +/// fuctionality. Groups just like windows are identified by an unique name and +/// internally keep track of scrollbar offsets by default. However additional +/// versions are provided to directly manage the scrollbar. +/// +/// #### Usage +/// To create a group you have to call one of the three `nk_group_begin_xxx` +/// functions to start group declarations and `nk_group_end` at the end. Furthermore it +/// is required to check the return value of `nk_group_begin_xxx` and only process +/// widgets inside the window if the value is not 0. +/// Nesting groups is possible and even encouraged since many layouting schemes +/// can only be achieved by nesting. Groups, unlike windows, need `nk_group_end` +/// to be only called if the corosponding `nk_group_begin_xxx` call does not return 0: +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// if (nk_group_begin_xxx(ctx, ...) { +/// // [... widgets ...] +/// nk_group_end(ctx); +/// } +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// In the grand concept groups can be called after starting a window +/// with `nk_begin_xxx` and before calling `nk_end`: +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// struct nk_context ctx; +/// nk_init_xxx(&ctx, ...); +/// while (1) { +/// // Input +/// Event evt; +/// nk_input_begin(&ctx); +/// while (GetEvent(&evt)) { +/// if (evt.type == MOUSE_MOVE) +/// nk_input_motion(&ctx, evt.motion.x, evt.motion.y); +/// else if (evt.type == [...]) { +/// nk_input_xxx(...); +/// } +/// } +/// nk_input_end(&ctx); +/// // +/// // Window +/// if (nk_begin_xxx(...) { +/// // [...widgets...] +/// nk_layout_row_dynamic(...); +/// if (nk_group_begin_xxx(ctx, ...) { +/// //[... widgets ...] +/// nk_group_end(ctx); +/// } +/// } +/// nk_end(ctx); +/// // +/// // Draw +/// const struct nk_command *cmd = 0; +/// nk_foreach(cmd, &ctx) { +/// switch (cmd->type) { +/// case NK_COMMAND_LINE: +/// your_draw_line_function(...) +/// break; +/// case NK_COMMAND_RECT +/// your_draw_rect_function(...) +/// break; +/// case ...: +/// // [...] +/// } +/// nk_clear(&ctx); +/// } +/// nk_free(&ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// #### Reference +/// Function | Description +/// --------------------------------|------------------------------------------- +/// nk_group_begin | Start a new group with internal scrollbar handling +/// nk_group_begin_titled | Start a new group with separeted name and title and internal scrollbar handling +/// nk_group_end | Ends a group. Should only be called if nk_group_begin returned non-zero +/// nk_group_scrolled_offset_begin | Start a new group with manual separated handling of scrollbar x- and y-offset +/// nk_group_scrolled_begin | Start a new group with manual scrollbar handling +/// nk_group_scrolled_end | Ends a group with manual scrollbar handling. Should only be called if nk_group_begin returned non-zero +/// nk_group_get_scroll | Gets the scroll offset for the given group +/// nk_group_set_scroll | Sets the scroll offset for the given group +*/ +/*/// #### nk_group_begin +/// Starts a new widget group. Requires a previous layouting function to specify a pos/size. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_group_begin(struct nk_context*, const char *title, nk_flags); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __title__ | Must be an unique identifier for this group that is also used for the group header +/// __flags__ | Window flags defined in the nk_panel_flags section with a number of different group behaviors +/// +/// Returns `true(1)` if visible and fillable with widgets or `false(0)` otherwise +*/ +NK_API nk_bool nk_group_begin(struct nk_context*, const char *title, nk_flags); +/*/// #### nk_group_begin_titled +/// Starts a new widget group. Requires a previous layouting function to specify a pos/size. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_group_begin_titled(struct nk_context*, const char *name, const char *title, nk_flags); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __id__ | Must be an unique identifier for this group +/// __title__ | Group header title +/// __flags__ | Window flags defined in the nk_panel_flags section with a number of different group behaviors +/// +/// Returns `true(1)` if visible and fillable with widgets or `false(0)` otherwise +*/ +NK_API nk_bool nk_group_begin_titled(struct nk_context*, const char *name, const char *title, nk_flags); +/*/// #### nk_group_end +/// Ends a widget group +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_group_end(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +*/ +NK_API void nk_group_end(struct nk_context*); +/*/// #### nk_group_scrolled_offset_begin +/// starts a new widget group. requires a previous layouting function to specify +/// a size. Does not keep track of scrollbar. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_group_scrolled_offset_begin(struct nk_context*, nk_uint *x_offset, nk_uint *y_offset, const char *title, nk_flags flags); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __x_offset__| Scrollbar x-offset to offset all widgets inside the group horizontally. +/// __y_offset__| Scrollbar y-offset to offset all widgets inside the group vertically +/// __title__ | Window unique group title used to both identify and display in the group header +/// __flags__ | Window flags from the nk_panel_flags section +/// +/// Returns `true(1)` if visible and fillable with widgets or `false(0)` otherwise +*/ +NK_API nk_bool nk_group_scrolled_offset_begin(struct nk_context*, nk_uint *x_offset, nk_uint *y_offset, const char *title, nk_flags flags); +/*/// #### nk_group_scrolled_begin +/// Starts a new widget group. requires a previous +/// layouting function to specify a size. Does not keep track of scrollbar. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_group_scrolled_begin(struct nk_context*, struct nk_scroll *off, const char *title, nk_flags); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __off__ | Both x- and y- scroll offset. Allows for manual scrollbar control +/// __title__ | Window unique group title used to both identify and display in the group header +/// __flags__ | Window flags from nk_panel_flags section +/// +/// Returns `true(1)` if visible and fillable with widgets or `false(0)` otherwise +*/ +NK_API nk_bool nk_group_scrolled_begin(struct nk_context*, struct nk_scroll *off, const char *title, nk_flags); +/*/// #### nk_group_scrolled_end +/// Ends a widget group after calling nk_group_scrolled_offset_begin or nk_group_scrolled_begin. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_group_scrolled_end(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +*/ +NK_API void nk_group_scrolled_end(struct nk_context*); +/*/// #### nk_group_get_scroll +/// Gets the scroll position of the given group. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_group_get_scroll(struct nk_context*, const char *id, nk_uint *x_offset, nk_uint *y_offset); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// -------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __id__ | The id of the group to get the scroll position of +/// __x_offset__ | A pointer to the x offset output (or NULL to ignore) +/// __y_offset__ | A pointer to the y offset output (or NULL to ignore) +*/ +NK_API void nk_group_get_scroll(struct nk_context*, const char *id, nk_uint *x_offset, nk_uint *y_offset); +/*/// #### nk_group_set_scroll +/// Sets the scroll position of the given group. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_group_set_scroll(struct nk_context*, const char *id, nk_uint x_offset, nk_uint y_offset); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// -------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __id__ | The id of the group to scroll +/// __x_offset__ | The x offset to scroll to +/// __y_offset__ | The y offset to scroll to +*/ +NK_API void nk_group_set_scroll(struct nk_context*, const char *id, nk_uint x_offset, nk_uint y_offset); +/* ============================================================================= + * + * TREE + * + * ============================================================================= +/// ### Tree +/// Trees represent two different concept. First the concept of a collapsable +/// UI section that can be either in a hidden or visibile state. They allow the UI +/// user to selectively minimize the current set of visible UI to comprehend. +/// The second concept are tree widgets for visual UI representation of trees.

    +/// +/// Trees thereby can be nested for tree representations and multiple nested +/// collapsable UI sections. All trees are started by calling of the +/// `nk_tree_xxx_push_tree` functions and ended by calling one of the +/// `nk_tree_xxx_pop_xxx()` functions. Each starting functions takes a title label +/// and optionally an image to be displayed and the initial collapse state from +/// the nk_collapse_states section.

    +/// +/// The runtime state of the tree is either stored outside the library by the caller +/// or inside which requires a unique ID. The unique ID can either be generated +/// automatically from `__FILE__` and `__LINE__` with function `nk_tree_push`, +/// by `__FILE__` and a user provided ID generated for example by loop index with +/// function `nk_tree_push_id` or completely provided from outside by user with +/// function `nk_tree_push_hashed`. +/// +/// #### Usage +/// To create a tree you have to call one of the seven `nk_tree_xxx_push_xxx` +/// functions to start a collapsable UI section and `nk_tree_xxx_pop` to mark the +/// end. +/// Each starting function will either return `false(0)` if the tree is collapsed +/// or hidden and therefore does not need to be filled with content or `true(1)` +/// if visible and required to be filled. +/// +/// !!! Note +/// The tree header does not require and layouting function and instead +/// calculates a auto height based on the currently used font size +/// +/// The tree ending functions only need to be called if the tree content is +/// actually visible. So make sure the tree push function is guarded by `if` +/// and the pop call is only taken if the tree is visible. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// if (nk_tree_push(ctx, NK_TREE_TAB, "Tree", NK_MINIMIZED)) { +/// nk_layout_row_dynamic(...); +/// nk_widget(...); +/// nk_tree_pop(ctx); +/// } +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// #### Reference +/// Function | Description +/// ----------------------------|------------------------------------------- +/// nk_tree_push | Start a collapsable UI section with internal state management +/// nk_tree_push_id | Start a collapsable UI section with internal state management callable in a look +/// nk_tree_push_hashed | Start a collapsable UI section with internal state management with full control over internal unique ID use to store state +/// nk_tree_image_push | Start a collapsable UI section with image and label header +/// nk_tree_image_push_id | Start a collapsable UI section with image and label header and internal state management callable in a look +/// nk_tree_image_push_hashed | Start a collapsable UI section with image and label header and internal state management with full control over internal unique ID use to store state +/// nk_tree_pop | Ends a collapsable UI section +// +/// nk_tree_state_push | Start a collapsable UI section with external state management +/// nk_tree_state_image_push | Start a collapsable UI section with image and label header and external state management +/// nk_tree_state_pop | Ends a collapsabale UI section +/// +/// #### nk_tree_type +/// Flag | Description +/// ----------------|---------------------------------------- +/// NK_TREE_NODE | Highlighted tree header to mark a collapsable UI section +/// NK_TREE_TAB | Non-highighted tree header closer to tree representations +*/ +/*/// #### nk_tree_push +/// Starts a collapsable UI section with internal state management +/// !!! WARNING +/// To keep track of the runtime tree collapsable state this function uses +/// defines `__FILE__` and `__LINE__` to generate a unique ID. If you want +/// to call this function in a loop please use `nk_tree_push_id` or +/// `nk_tree_push_hashed` instead. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// #define nk_tree_push(ctx, type, title, state) +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __type__ | Value from the nk_tree_type section to visually mark a tree node header as either a collapseable UI section or tree node +/// __title__ | Label printed in the tree header +/// __state__ | Initial tree state value out of nk_collapse_states +/// +/// Returns `true(1)` if visible and fillable with widgets or `false(0)` otherwise +*/ +#define nk_tree_push(ctx, type, title, state) nk_tree_push_hashed(ctx, type, title, state, NK_FILE_LINE,nk_strlen(NK_FILE_LINE),__LINE__) +/*/// #### nk_tree_push_id +/// Starts a collapsable UI section with internal state management callable in a look +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// #define nk_tree_push_id(ctx, type, title, state, id) +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __type__ | Value from the nk_tree_type section to visually mark a tree node header as either a collapseable UI section or tree node +/// __title__ | Label printed in the tree header +/// __state__ | Initial tree state value out of nk_collapse_states +/// __id__ | Loop counter index if this function is called in a loop +/// +/// Returns `true(1)` if visible and fillable with widgets or `false(0)` otherwise +*/ +#define nk_tree_push_id(ctx, type, title, state, id) nk_tree_push_hashed(ctx, type, title, state, NK_FILE_LINE,nk_strlen(NK_FILE_LINE),id) +/*/// #### nk_tree_push_hashed +/// Start a collapsable UI section with internal state management with full +/// control over internal unique ID used to store state +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_tree_push_hashed(struct nk_context*, enum nk_tree_type, const char *title, enum nk_collapse_states initial_state, const char *hash, int len,int seed); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __type__ | Value from the nk_tree_type section to visually mark a tree node header as either a collapseable UI section or tree node +/// __title__ | Label printed in the tree header +/// __state__ | Initial tree state value out of nk_collapse_states +/// __hash__ | Memory block or string to generate the ID from +/// __len__ | Size of passed memory block or string in __hash__ +/// __seed__ | Seeding value if this function is called in a loop or default to `0` +/// +/// Returns `true(1)` if visible and fillable with widgets or `false(0)` otherwise +*/ +NK_API nk_bool nk_tree_push_hashed(struct nk_context*, enum nk_tree_type, const char *title, enum nk_collapse_states initial_state, const char *hash, int len,int seed); +/*/// #### nk_tree_image_push +/// Start a collapsable UI section with image and label header +/// !!! WARNING +/// To keep track of the runtime tree collapsable state this function uses +/// defines `__FILE__` and `__LINE__` to generate a unique ID. If you want +/// to call this function in a loop please use `nk_tree_image_push_id` or +/// `nk_tree_image_push_hashed` instead. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// #define nk_tree_image_push(ctx, type, img, title, state) +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __type__ | Value from the nk_tree_type section to visually mark a tree node header as either a collapseable UI section or tree node +/// __img__ | Image to display inside the header on the left of the label +/// __title__ | Label printed in the tree header +/// __state__ | Initial tree state value out of nk_collapse_states +/// +/// Returns `true(1)` if visible and fillable with widgets or `false(0)` otherwise +*/ +#define nk_tree_image_push(ctx, type, img, title, state) nk_tree_image_push_hashed(ctx, type, img, title, state, NK_FILE_LINE,nk_strlen(NK_FILE_LINE),__LINE__) +/*/// #### nk_tree_image_push_id +/// Start a collapsable UI section with image and label header and internal state +/// management callable in a look +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// #define nk_tree_image_push_id(ctx, type, img, title, state, id) +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __type__ | Value from the nk_tree_type section to visually mark a tree node header as either a collapseable UI section or tree node +/// __img__ | Image to display inside the header on the left of the label +/// __title__ | Label printed in the tree header +/// __state__ | Initial tree state value out of nk_collapse_states +/// __id__ | Loop counter index if this function is called in a loop +/// +/// Returns `true(1)` if visible and fillable with widgets or `false(0)` otherwise +*/ +#define nk_tree_image_push_id(ctx, type, img, title, state, id) nk_tree_image_push_hashed(ctx, type, img, title, state, NK_FILE_LINE,nk_strlen(NK_FILE_LINE),id) +/*/// #### nk_tree_image_push_hashed +/// Start a collapsable UI section with internal state management with full +/// control over internal unique ID used to store state +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_tree_image_push_hashed(struct nk_context*, enum nk_tree_type, struct nk_image, const char *title, enum nk_collapse_states initial_state, const char *hash, int len,int seed); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct +/// __type__ | Value from the nk_tree_type section to visually mark a tree node header as either a collapseable UI section or tree node +/// __img__ | Image to display inside the header on the left of the label +/// __title__ | Label printed in the tree header +/// __state__ | Initial tree state value out of nk_collapse_states +/// __hash__ | Memory block or string to generate the ID from +/// __len__ | Size of passed memory block or string in __hash__ +/// __seed__ | Seeding value if this function is called in a loop or default to `0` +/// +/// Returns `true(1)` if visible and fillable with widgets or `false(0)` otherwise +*/ +NK_API nk_bool nk_tree_image_push_hashed(struct nk_context*, enum nk_tree_type, struct nk_image, const char *title, enum nk_collapse_states initial_state, const char *hash, int len,int seed); +/*/// #### nk_tree_pop +/// Ends a collapsabale UI section +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_tree_pop(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after calling `nk_tree_xxx_push_xxx` +*/ +NK_API void nk_tree_pop(struct nk_context*); +/*/// #### nk_tree_state_push +/// Start a collapsable UI section with external state management +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_tree_state_push(struct nk_context*, enum nk_tree_type, const char *title, enum nk_collapse_states *state); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after calling `nk_tree_xxx_push_xxx` +/// __type__ | Value from the nk_tree_type section to visually mark a tree node header as either a collapseable UI section or tree node +/// __title__ | Label printed in the tree header +/// __state__ | Persistent state to update +/// +/// Returns `true(1)` if visible and fillable with widgets or `false(0)` otherwise +*/ +NK_API nk_bool nk_tree_state_push(struct nk_context*, enum nk_tree_type, const char *title, enum nk_collapse_states *state); +/*/// #### nk_tree_state_image_push +/// Start a collapsable UI section with image and label header and external state management +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_tree_state_image_push(struct nk_context*, enum nk_tree_type, struct nk_image, const char *title, enum nk_collapse_states *state); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after calling `nk_tree_xxx_push_xxx` +/// __img__ | Image to display inside the header on the left of the label +/// __type__ | Value from the nk_tree_type section to visually mark a tree node header as either a collapseable UI section or tree node +/// __title__ | Label printed in the tree header +/// __state__ | Persistent state to update +/// +/// Returns `true(1)` if visible and fillable with widgets or `false(0)` otherwise +*/ +NK_API nk_bool nk_tree_state_image_push(struct nk_context*, enum nk_tree_type, struct nk_image, const char *title, enum nk_collapse_states *state); +/*/// #### nk_tree_state_pop +/// Ends a collapsabale UI section +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_tree_state_pop(struct nk_context*); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// ------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after calling `nk_tree_xxx_push_xxx` +*/ +NK_API void nk_tree_state_pop(struct nk_context*); + +#define nk_tree_element_push(ctx, type, title, state, sel) nk_tree_element_push_hashed(ctx, type, title, state, sel, NK_FILE_LINE,nk_strlen(NK_FILE_LINE),__LINE__) +#define nk_tree_element_push_id(ctx, type, title, state, sel, id) nk_tree_element_push_hashed(ctx, type, title, state, sel, NK_FILE_LINE,nk_strlen(NK_FILE_LINE),id) +NK_API nk_bool nk_tree_element_push_hashed(struct nk_context*, enum nk_tree_type, const char *title, enum nk_collapse_states initial_state, nk_bool *selected, const char *hash, int len, int seed); +NK_API nk_bool nk_tree_element_image_push_hashed(struct nk_context*, enum nk_tree_type, struct nk_image, const char *title, enum nk_collapse_states initial_state, nk_bool *selected, const char *hash, int len,int seed); +NK_API void nk_tree_element_pop(struct nk_context*); + +/* ============================================================================= + * + * LIST VIEW + * + * ============================================================================= */ +struct nk_list_view { +/* public: */ + int begin, end, count; +/* private: */ + int total_height; + struct nk_context *ctx; + nk_uint *scroll_pointer; + nk_uint scroll_value; +}; +NK_API nk_bool nk_list_view_begin(struct nk_context*, struct nk_list_view *out, const char *id, nk_flags, int row_height, int row_count); +NK_API void nk_list_view_end(struct nk_list_view*); +/* ============================================================================= + * + * WIDGET + * + * ============================================================================= */ +enum nk_widget_layout_states { + NK_WIDGET_INVALID, /* The widget cannot be seen and is completely out of view */ + NK_WIDGET_VALID, /* The widget is completely inside the window and can be updated and drawn */ + NK_WIDGET_ROM /* The widget is partially visible and cannot be updated */ +}; +enum nk_widget_states { + NK_WIDGET_STATE_MODIFIED = NK_FLAG(1), + NK_WIDGET_STATE_INACTIVE = NK_FLAG(2), /* widget is neither active nor hovered */ + NK_WIDGET_STATE_ENTERED = NK_FLAG(3), /* widget has been hovered on the current frame */ + NK_WIDGET_STATE_HOVER = NK_FLAG(4), /* widget is being hovered */ + NK_WIDGET_STATE_ACTIVED = NK_FLAG(5),/* widget is currently activated */ + NK_WIDGET_STATE_LEFT = NK_FLAG(6), /* widget is from this frame on not hovered anymore */ + NK_WIDGET_STATE_HOVERED = NK_WIDGET_STATE_HOVER|NK_WIDGET_STATE_MODIFIED, /* widget is being hovered */ + NK_WIDGET_STATE_ACTIVE = NK_WIDGET_STATE_ACTIVED|NK_WIDGET_STATE_MODIFIED /* widget is currently activated */ +}; +NK_API enum nk_widget_layout_states nk_widget(struct nk_rect*, const struct nk_context*); +NK_API enum nk_widget_layout_states nk_widget_fitting(struct nk_rect*, struct nk_context*, struct nk_vec2); +NK_API struct nk_rect nk_widget_bounds(struct nk_context*); +NK_API struct nk_vec2 nk_widget_position(struct nk_context*); +NK_API struct nk_vec2 nk_widget_size(struct nk_context*); +NK_API float nk_widget_width(struct nk_context*); +NK_API float nk_widget_height(struct nk_context*); +NK_API nk_bool nk_widget_is_hovered(struct nk_context*); +NK_API nk_bool nk_widget_is_mouse_clicked(struct nk_context*, enum nk_buttons); +NK_API nk_bool nk_widget_has_mouse_click_down(struct nk_context*, enum nk_buttons, nk_bool down); +NK_API void nk_spacing(struct nk_context*, int cols); +/* ============================================================================= + * + * TEXT + * + * ============================================================================= */ +enum nk_text_align { + NK_TEXT_ALIGN_LEFT = 0x01, + NK_TEXT_ALIGN_CENTERED = 0x02, + NK_TEXT_ALIGN_RIGHT = 0x04, + NK_TEXT_ALIGN_TOP = 0x08, + NK_TEXT_ALIGN_MIDDLE = 0x10, + NK_TEXT_ALIGN_BOTTOM = 0x20 +}; +enum nk_text_alignment { + NK_TEXT_LEFT = NK_TEXT_ALIGN_MIDDLE|NK_TEXT_ALIGN_LEFT, + NK_TEXT_CENTERED = NK_TEXT_ALIGN_MIDDLE|NK_TEXT_ALIGN_CENTERED, + NK_TEXT_RIGHT = NK_TEXT_ALIGN_MIDDLE|NK_TEXT_ALIGN_RIGHT +}; +NK_API void nk_text(struct nk_context*, const char*, int, nk_flags); +NK_API void nk_text_colored(struct nk_context*, const char*, int, nk_flags, struct nk_color); +NK_API void nk_text_wrap(struct nk_context*, const char*, int); +NK_API void nk_text_wrap_colored(struct nk_context*, const char*, int, struct nk_color); +NK_API void nk_label(struct nk_context*, const char*, nk_flags align); +NK_API void nk_label_colored(struct nk_context*, const char*, nk_flags align, struct nk_color); +NK_API void nk_label_wrap(struct nk_context*, const char*); +NK_API void nk_label_colored_wrap(struct nk_context*, const char*, struct nk_color); +NK_API void nk_image(struct nk_context*, struct nk_image); +NK_API void nk_image_color(struct nk_context*, struct nk_image, struct nk_color); +#ifdef NK_INCLUDE_STANDARD_VARARGS +NK_API void nk_labelf(struct nk_context*, nk_flags, NK_PRINTF_FORMAT_STRING const char*, ...) NK_PRINTF_VARARG_FUNC(3); +NK_API void nk_labelf_colored(struct nk_context*, nk_flags, struct nk_color, NK_PRINTF_FORMAT_STRING const char*,...) NK_PRINTF_VARARG_FUNC(4); +NK_API void nk_labelf_wrap(struct nk_context*, NK_PRINTF_FORMAT_STRING const char*,...) NK_PRINTF_VARARG_FUNC(2); +NK_API void nk_labelf_colored_wrap(struct nk_context*, struct nk_color, NK_PRINTF_FORMAT_STRING const char*,...) NK_PRINTF_VARARG_FUNC(3); +NK_API void nk_labelfv(struct nk_context*, nk_flags, NK_PRINTF_FORMAT_STRING const char*, va_list) NK_PRINTF_VALIST_FUNC(3); +NK_API void nk_labelfv_colored(struct nk_context*, nk_flags, struct nk_color, NK_PRINTF_FORMAT_STRING const char*, va_list) NK_PRINTF_VALIST_FUNC(4); +NK_API void nk_labelfv_wrap(struct nk_context*, NK_PRINTF_FORMAT_STRING const char*, va_list) NK_PRINTF_VALIST_FUNC(2); +NK_API void nk_labelfv_colored_wrap(struct nk_context*, struct nk_color, NK_PRINTF_FORMAT_STRING const char*, va_list) NK_PRINTF_VALIST_FUNC(3); +NK_API void nk_value_bool(struct nk_context*, const char *prefix, int); +NK_API void nk_value_int(struct nk_context*, const char *prefix, int); +NK_API void nk_value_uint(struct nk_context*, const char *prefix, unsigned int); +NK_API void nk_value_float(struct nk_context*, const char *prefix, float); +NK_API void nk_value_color_byte(struct nk_context*, const char *prefix, struct nk_color); +NK_API void nk_value_color_float(struct nk_context*, const char *prefix, struct nk_color); +NK_API void nk_value_color_hex(struct nk_context*, const char *prefix, struct nk_color); +#endif +/* ============================================================================= + * + * BUTTON + * + * ============================================================================= */ +NK_API nk_bool nk_button_text(struct nk_context*, const char *title, int len); +NK_API nk_bool nk_button_label(struct nk_context*, const char *title); +NK_API nk_bool nk_button_color(struct nk_context*, struct nk_color); +NK_API nk_bool nk_button_symbol(struct nk_context*, enum nk_symbol_type); +NK_API nk_bool nk_button_image(struct nk_context*, struct nk_image img); +NK_API nk_bool nk_button_symbol_label(struct nk_context*, enum nk_symbol_type, const char*, nk_flags text_alignment); +NK_API nk_bool nk_button_symbol_text(struct nk_context*, enum nk_symbol_type, const char*, int, nk_flags alignment); +NK_API nk_bool nk_button_image_label(struct nk_context*, struct nk_image img, const char*, nk_flags text_alignment); +NK_API nk_bool nk_button_image_text(struct nk_context*, struct nk_image img, const char*, int, nk_flags alignment); +NK_API nk_bool nk_button_text_styled(struct nk_context*, const struct nk_style_button*, const char *title, int len); +NK_API nk_bool nk_button_label_styled(struct nk_context*, const struct nk_style_button*, const char *title); +NK_API nk_bool nk_button_symbol_styled(struct nk_context*, const struct nk_style_button*, enum nk_symbol_type); +NK_API nk_bool nk_button_image_styled(struct nk_context*, const struct nk_style_button*, struct nk_image img); +NK_API nk_bool nk_button_symbol_text_styled(struct nk_context*,const struct nk_style_button*, enum nk_symbol_type, const char*, int, nk_flags alignment); +NK_API nk_bool nk_button_symbol_label_styled(struct nk_context *ctx, const struct nk_style_button *style, enum nk_symbol_type symbol, const char *title, nk_flags align); +NK_API nk_bool nk_button_image_label_styled(struct nk_context*,const struct nk_style_button*, struct nk_image img, const char*, nk_flags text_alignment); +NK_API nk_bool nk_button_image_text_styled(struct nk_context*,const struct nk_style_button*, struct nk_image img, const char*, int, nk_flags alignment); +NK_API void nk_button_set_behavior(struct nk_context*, enum nk_button_behavior); +NK_API nk_bool nk_button_push_behavior(struct nk_context*, enum nk_button_behavior); +NK_API nk_bool nk_button_pop_behavior(struct nk_context*); +/* ============================================================================= + * + * CHECKBOX + * + * ============================================================================= */ +NK_API nk_bool nk_check_label(struct nk_context*, const char*, nk_bool active); +NK_API nk_bool nk_check_text(struct nk_context*, const char*, int, nk_bool active); +NK_API unsigned nk_check_flags_label(struct nk_context*, const char*, unsigned int flags, unsigned int value); +NK_API unsigned nk_check_flags_text(struct nk_context*, const char*, int, unsigned int flags, unsigned int value); +NK_API nk_bool nk_checkbox_label(struct nk_context*, const char*, nk_bool *active); +NK_API nk_bool nk_checkbox_text(struct nk_context*, const char*, int, nk_bool *active); +NK_API nk_bool nk_checkbox_flags_label(struct nk_context*, const char*, unsigned int *flags, unsigned int value); +NK_API nk_bool nk_checkbox_flags_text(struct nk_context*, const char*, int, unsigned int *flags, unsigned int value); +/* ============================================================================= + * + * RADIO BUTTON + * + * ============================================================================= */ +NK_API nk_bool nk_radio_label(struct nk_context*, const char*, nk_bool *active); +NK_API nk_bool nk_radio_text(struct nk_context*, const char*, int, nk_bool *active); +NK_API nk_bool nk_option_label(struct nk_context*, const char*, nk_bool active); +NK_API nk_bool nk_option_text(struct nk_context*, const char*, int, nk_bool active); +/* ============================================================================= + * + * SELECTABLE + * + * ============================================================================= */ +NK_API nk_bool nk_selectable_label(struct nk_context*, const char*, nk_flags align, nk_bool *value); +NK_API nk_bool nk_selectable_text(struct nk_context*, const char*, int, nk_flags align, nk_bool *value); +NK_API nk_bool nk_selectable_image_label(struct nk_context*,struct nk_image, const char*, nk_flags align, nk_bool *value); +NK_API nk_bool nk_selectable_image_text(struct nk_context*,struct nk_image, const char*, int, nk_flags align, nk_bool *value); +NK_API nk_bool nk_selectable_symbol_label(struct nk_context*,enum nk_symbol_type, const char*, nk_flags align, nk_bool *value); +NK_API nk_bool nk_selectable_symbol_text(struct nk_context*,enum nk_symbol_type, const char*, int, nk_flags align, nk_bool *value); + +NK_API nk_bool nk_select_label(struct nk_context*, const char*, nk_flags align, nk_bool value); +NK_API nk_bool nk_select_text(struct nk_context*, const char*, int, nk_flags align, nk_bool value); +NK_API nk_bool nk_select_image_label(struct nk_context*, struct nk_image,const char*, nk_flags align, nk_bool value); +NK_API nk_bool nk_select_image_text(struct nk_context*, struct nk_image,const char*, int, nk_flags align, nk_bool value); +NK_API nk_bool nk_select_symbol_label(struct nk_context*,enum nk_symbol_type, const char*, nk_flags align, nk_bool value); +NK_API nk_bool nk_select_symbol_text(struct nk_context*,enum nk_symbol_type, const char*, int, nk_flags align, nk_bool value); + +/* ============================================================================= + * + * SLIDER + * + * ============================================================================= */ +NK_API float nk_slide_float(struct nk_context*, float min, float val, float max, float step); +NK_API int nk_slide_int(struct nk_context*, int min, int val, int max, int step); +NK_API nk_bool nk_slider_float(struct nk_context*, float min, float *val, float max, float step); +NK_API nk_bool nk_slider_int(struct nk_context*, int min, int *val, int max, int step); +/* ============================================================================= + * + * PROGRESSBAR + * + * ============================================================================= */ +NK_API nk_bool nk_progress(struct nk_context*, nk_size *cur, nk_size max, nk_bool modifyable); +NK_API nk_size nk_prog(struct nk_context*, nk_size cur, nk_size max, nk_bool modifyable); + +/* ============================================================================= + * + * COLOR PICKER + * + * ============================================================================= */ +NK_API struct nk_colorf nk_color_picker(struct nk_context*, struct nk_colorf, enum nk_color_format); +NK_API nk_bool nk_color_pick(struct nk_context*, struct nk_colorf*, enum nk_color_format); +/* ============================================================================= + * + * PROPERTIES + * + * ============================================================================= +/// ### Properties +/// Properties are the main value modification widgets in Nuklear. Changing a value +/// can be achieved by dragging, adding/removing incremental steps on button click +/// or by directly typing a number. +/// +/// #### Usage +/// Each property requires a unique name for identifaction that is also used for +/// displaying a label. If you want to use the same name multiple times make sure +/// add a '#' before your name. The '#' will not be shown but will generate a +/// unique ID. Each propery also takes in a minimum and maximum value. If you want +/// to make use of the complete number range of a type just use the provided +/// type limits from `limits.h`. For example `INT_MIN` and `INT_MAX` for +/// `nk_property_int` and `nk_propertyi`. In additional each property takes in +/// a increment value that will be added or subtracted if either the increment +/// decrement button is clicked. Finally there is a value for increment per pixel +/// dragged that is added or subtracted from the value. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int value = 0; +/// struct nk_context ctx; +/// nk_init_xxx(&ctx, ...); +/// while (1) { +/// // Input +/// Event evt; +/// nk_input_begin(&ctx); +/// while (GetEvent(&evt)) { +/// if (evt.type == MOUSE_MOVE) +/// nk_input_motion(&ctx, evt.motion.x, evt.motion.y); +/// else if (evt.type == [...]) { +/// nk_input_xxx(...); +/// } +/// } +/// nk_input_end(&ctx); +/// // +/// // Window +/// if (nk_begin_xxx(...) { +/// // Property +/// nk_layout_row_dynamic(...); +/// nk_property_int(ctx, "ID", INT_MIN, &value, INT_MAX, 1, 1); +/// } +/// nk_end(ctx); +/// // +/// // Draw +/// const struct nk_command *cmd = 0; +/// nk_foreach(cmd, &ctx) { +/// switch (cmd->type) { +/// case NK_COMMAND_LINE: +/// your_draw_line_function(...) +/// break; +/// case NK_COMMAND_RECT +/// your_draw_rect_function(...) +/// break; +/// case ...: +/// // [...] +/// } +/// nk_clear(&ctx); +/// } +/// nk_free(&ctx); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// #### Reference +/// Function | Description +/// --------------------|------------------------------------------- +/// nk_property_int | Integer property directly modifing a passed in value +/// nk_property_float | Float property directly modifing a passed in value +/// nk_property_double | Double property directly modifing a passed in value +/// nk_propertyi | Integer property returning the modified int value +/// nk_propertyf | Float property returning the modified float value +/// nk_propertyd | Double property returning the modified double value +/// +*/ +/*/// #### nk_property_int +/// Integer property directly modifing a passed in value +/// !!! WARNING +/// To generate a unique property ID using the same label make sure to insert +/// a `#` at the beginning. It will not be shown but guarantees correct behavior. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_property_int(struct nk_context *ctx, const char *name, int min, int *val, int max, int step, float inc_per_pixel); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// --------------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after calling a layouting function +/// __name__ | String used both as a label as well as a unique identifier +/// __min__ | Minimum value not allowed to be underflown +/// __val__ | Integer pointer to be modified +/// __max__ | Maximum value not allowed to be overflown +/// __step__ | Increment added and subtracted on increment and decrement button +/// __inc_per_pixel__ | Value per pixel added or subtracted on dragging +*/ +NK_API void nk_property_int(struct nk_context*, const char *name, int min, int *val, int max, int step, float inc_per_pixel); +/*/// #### nk_property_float +/// Float property directly modifing a passed in value +/// !!! WARNING +/// To generate a unique property ID using the same label make sure to insert +/// a `#` at the beginning. It will not be shown but guarantees correct behavior. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_property_float(struct nk_context *ctx, const char *name, float min, float *val, float max, float step, float inc_per_pixel); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// --------------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after calling a layouting function +/// __name__ | String used both as a label as well as a unique identifier +/// __min__ | Minimum value not allowed to be underflown +/// __val__ | Float pointer to be modified +/// __max__ | Maximum value not allowed to be overflown +/// __step__ | Increment added and subtracted on increment and decrement button +/// __inc_per_pixel__ | Value per pixel added or subtracted on dragging +*/ +NK_API void nk_property_float(struct nk_context*, const char *name, float min, float *val, float max, float step, float inc_per_pixel); +/*/// #### nk_property_double +/// Double property directly modifing a passed in value +/// !!! WARNING +/// To generate a unique property ID using the same label make sure to insert +/// a `#` at the beginning. It will not be shown but guarantees correct behavior. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// void nk_property_double(struct nk_context *ctx, const char *name, double min, double *val, double max, double step, double inc_per_pixel); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// --------------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after calling a layouting function +/// __name__ | String used both as a label as well as a unique identifier +/// __min__ | Minimum value not allowed to be underflown +/// __val__ | Double pointer to be modified +/// __max__ | Maximum value not allowed to be overflown +/// __step__ | Increment added and subtracted on increment and decrement button +/// __inc_per_pixel__ | Value per pixel added or subtracted on dragging +*/ +NK_API void nk_property_double(struct nk_context*, const char *name, double min, double *val, double max, double step, float inc_per_pixel); +/*/// #### nk_propertyi +/// Integer property modifing a passed in value and returning the new value +/// !!! WARNING +/// To generate a unique property ID using the same label make sure to insert +/// a `#` at the beginning. It will not be shown but guarantees correct behavior. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// int nk_propertyi(struct nk_context *ctx, const char *name, int min, int val, int max, int step, float inc_per_pixel); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// --------------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after calling a layouting function +/// __name__ | String used both as a label as well as a unique identifier +/// __min__ | Minimum value not allowed to be underflown +/// __val__ | Current integer value to be modified and returned +/// __max__ | Maximum value not allowed to be overflown +/// __step__ | Increment added and subtracted on increment and decrement button +/// __inc_per_pixel__ | Value per pixel added or subtracted on dragging +/// +/// Returns the new modified integer value +*/ +NK_API nk_bool nk_propertyi(struct nk_context*, const char *name, int min, int val, int max, int step, float inc_per_pixel); +/*/// #### nk_propertyf +/// Float property modifing a passed in value and returning the new value +/// !!! WARNING +/// To generate a unique property ID using the same label make sure to insert +/// a `#` at the beginning. It will not be shown but guarantees correct behavior. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// float nk_propertyf(struct nk_context *ctx, const char *name, float min, float val, float max, float step, float inc_per_pixel); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// --------------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after calling a layouting function +/// __name__ | String used both as a label as well as a unique identifier +/// __min__ | Minimum value not allowed to be underflown +/// __val__ | Current float value to be modified and returned +/// __max__ | Maximum value not allowed to be overflown +/// __step__ | Increment added and subtracted on increment and decrement button +/// __inc_per_pixel__ | Value per pixel added or subtracted on dragging +/// +/// Returns the new modified float value +*/ +NK_API float nk_propertyf(struct nk_context*, const char *name, float min, float val, float max, float step, float inc_per_pixel); +/*/// #### nk_propertyd +/// Float property modifing a passed in value and returning the new value +/// !!! WARNING +/// To generate a unique property ID using the same label make sure to insert +/// a `#` at the beginning. It will not be shown but guarantees correct behavior. +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~c +/// float nk_propertyd(struct nk_context *ctx, const char *name, double min, double val, double max, double step, double inc_per_pixel); +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Parameter | Description +/// --------------------|----------------------------------------------------------- +/// __ctx__ | Must point to an previously initialized `nk_context` struct after calling a layouting function +/// __name__ | String used both as a label as well as a unique identifier +/// __min__ | Minimum value not allowed to be underflown +/// __val__ | Current double value to be modified and returned +/// __max__ | Maximum value not allowed to be overflown +/// __step__ | Increment added and subtracted on increment and decrement button +/// __inc_per_pixel__ | Value per pixel added or subtracted on dragging +/// +/// Returns the new modified double value +*/ +NK_API double nk_propertyd(struct nk_context*, const char *name, double min, double val, double max, double step, float inc_per_pixel); +/* ============================================================================= + * + * TEXT EDIT + * + * ============================================================================= */ +enum nk_edit_flags { + NK_EDIT_DEFAULT = 0, + NK_EDIT_READ_ONLY = NK_FLAG(0), + NK_EDIT_AUTO_SELECT = NK_FLAG(1), + NK_EDIT_SIG_ENTER = NK_FLAG(2), + NK_EDIT_ALLOW_TAB = NK_FLAG(3), + NK_EDIT_NO_CURSOR = NK_FLAG(4), + NK_EDIT_SELECTABLE = NK_FLAG(5), + NK_EDIT_CLIPBOARD = NK_FLAG(6), + NK_EDIT_CTRL_ENTER_NEWLINE = NK_FLAG(7), + NK_EDIT_NO_HORIZONTAL_SCROLL = NK_FLAG(8), + NK_EDIT_ALWAYS_INSERT_MODE = NK_FLAG(9), + NK_EDIT_MULTILINE = NK_FLAG(10), + NK_EDIT_GOTO_END_ON_ACTIVATE = NK_FLAG(11) +}; +enum nk_edit_types { + NK_EDIT_SIMPLE = NK_EDIT_ALWAYS_INSERT_MODE, + NK_EDIT_FIELD = NK_EDIT_SIMPLE|NK_EDIT_SELECTABLE|NK_EDIT_CLIPBOARD, + NK_EDIT_BOX = NK_EDIT_ALWAYS_INSERT_MODE| NK_EDIT_SELECTABLE| NK_EDIT_MULTILINE|NK_EDIT_ALLOW_TAB|NK_EDIT_CLIPBOARD, + NK_EDIT_EDITOR = NK_EDIT_SELECTABLE|NK_EDIT_MULTILINE|NK_EDIT_ALLOW_TAB| NK_EDIT_CLIPBOARD +}; +enum nk_edit_events { + NK_EDIT_ACTIVE = NK_FLAG(0), /* edit widget is currently being modified */ + NK_EDIT_INACTIVE = NK_FLAG(1), /* edit widget is not active and is not being modified */ + NK_EDIT_ACTIVATED = NK_FLAG(2), /* edit widget went from state inactive to state active */ + NK_EDIT_DEACTIVATED = NK_FLAG(3), /* edit widget went from state active to state inactive */ + NK_EDIT_COMMITED = NK_FLAG(4) /* edit widget has received an enter and lost focus */ +}; +NK_API nk_flags nk_edit_string(struct nk_context*, nk_flags, char *buffer, int *len, int max, nk_plugin_filter); +NK_API nk_flags nk_edit_string_zero_terminated(struct nk_context*, nk_flags, char *buffer, int max, nk_plugin_filter); +NK_API nk_flags nk_edit_buffer(struct nk_context*, nk_flags, struct nk_text_edit*, nk_plugin_filter); +NK_API void nk_edit_focus(struct nk_context*, nk_flags flags); +NK_API void nk_edit_unfocus(struct nk_context*); +/* ============================================================================= + * + * CHART + * + * ============================================================================= */ +NK_API nk_bool nk_chart_begin(struct nk_context*, enum nk_chart_type, int num, float min, float max); +NK_API nk_bool nk_chart_begin_colored(struct nk_context*, enum nk_chart_type, struct nk_color, struct nk_color active, int num, float min, float max); +NK_API void nk_chart_add_slot(struct nk_context *ctx, const enum nk_chart_type, int count, float min_value, float max_value); +NK_API void nk_chart_add_slot_colored(struct nk_context *ctx, const enum nk_chart_type, struct nk_color, struct nk_color active, int count, float min_value, float max_value); +NK_API nk_flags nk_chart_push(struct nk_context*, float); +NK_API nk_flags nk_chart_push_slot(struct nk_context*, float, int); +NK_API void nk_chart_end(struct nk_context*); +NK_API void nk_plot(struct nk_context*, enum nk_chart_type, const float *values, int count, int offset); +NK_API void nk_plot_function(struct nk_context*, enum nk_chart_type, void *userdata, float(*value_getter)(void* user, int index), int count, int offset); +/* ============================================================================= + * + * POPUP + * + * ============================================================================= */ +NK_API nk_bool nk_popup_begin(struct nk_context*, enum nk_popup_type, const char*, nk_flags, struct nk_rect bounds); +NK_API void nk_popup_close(struct nk_context*); +NK_API void nk_popup_end(struct nk_context*); +NK_API void nk_popup_get_scroll(struct nk_context*, nk_uint *offset_x, nk_uint *offset_y); +NK_API void nk_popup_set_scroll(struct nk_context*, nk_uint offset_x, nk_uint offset_y); +/* ============================================================================= + * + * COMBOBOX + * + * ============================================================================= */ +NK_API int nk_combo(struct nk_context*, const char **items, int count, int selected, int item_height, struct nk_vec2 size); +NK_API int nk_combo_separator(struct nk_context*, const char *items_separated_by_separator, int separator, int selected, int count, int item_height, struct nk_vec2 size); +NK_API int nk_combo_string(struct nk_context*, const char *items_separated_by_zeros, int selected, int count, int item_height, struct nk_vec2 size); +NK_API int nk_combo_callback(struct nk_context*, void(*item_getter)(void*, int, const char**), void *userdata, int selected, int count, int item_height, struct nk_vec2 size); +NK_API void nk_combobox(struct nk_context*, const char **items, int count, int *selected, int item_height, struct nk_vec2 size); +NK_API void nk_combobox_string(struct nk_context*, const char *items_separated_by_zeros, int *selected, int count, int item_height, struct nk_vec2 size); +NK_API void nk_combobox_separator(struct nk_context*, const char *items_separated_by_separator, int separator, int *selected, int count, int item_height, struct nk_vec2 size); +NK_API void nk_combobox_callback(struct nk_context*, void(*item_getter)(void*, int, const char**), void*, int *selected, int count, int item_height, struct nk_vec2 size); +/* ============================================================================= + * + * ABSTRACT COMBOBOX + * + * ============================================================================= */ +NK_API nk_bool nk_combo_begin_text(struct nk_context*, const char *selected, int, struct nk_vec2 size); +NK_API nk_bool nk_combo_begin_label(struct nk_context*, const char *selected, struct nk_vec2 size); +NK_API nk_bool nk_combo_begin_color(struct nk_context*, struct nk_color color, struct nk_vec2 size); +NK_API nk_bool nk_combo_begin_symbol(struct nk_context*, enum nk_symbol_type, struct nk_vec2 size); +NK_API nk_bool nk_combo_begin_symbol_label(struct nk_context*, const char *selected, enum nk_symbol_type, struct nk_vec2 size); +NK_API nk_bool nk_combo_begin_symbol_text(struct nk_context*, const char *selected, int, enum nk_symbol_type, struct nk_vec2 size); +NK_API nk_bool nk_combo_begin_image(struct nk_context*, struct nk_image img, struct nk_vec2 size); +NK_API nk_bool nk_combo_begin_image_label(struct nk_context*, const char *selected, struct nk_image, struct nk_vec2 size); +NK_API nk_bool nk_combo_begin_image_text(struct nk_context*, const char *selected, int, struct nk_image, struct nk_vec2 size); +NK_API nk_bool nk_combo_item_label(struct nk_context*, const char*, nk_flags alignment); +NK_API nk_bool nk_combo_item_text(struct nk_context*, const char*,int, nk_flags alignment); +NK_API nk_bool nk_combo_item_image_label(struct nk_context*, struct nk_image, const char*, nk_flags alignment); +NK_API nk_bool nk_combo_item_image_text(struct nk_context*, struct nk_image, const char*, int,nk_flags alignment); +NK_API nk_bool nk_combo_item_symbol_label(struct nk_context*, enum nk_symbol_type, const char*, nk_flags alignment); +NK_API nk_bool nk_combo_item_symbol_text(struct nk_context*, enum nk_symbol_type, const char*, int, nk_flags alignment); +NK_API void nk_combo_close(struct nk_context*); +NK_API void nk_combo_end(struct nk_context*); +/* ============================================================================= + * + * CONTEXTUAL + * + * ============================================================================= */ +NK_API nk_bool nk_contextual_begin(struct nk_context*, nk_flags, struct nk_vec2, struct nk_rect trigger_bounds); +NK_API nk_bool nk_contextual_item_text(struct nk_context*, const char*, int,nk_flags align); +NK_API nk_bool nk_contextual_item_label(struct nk_context*, const char*, nk_flags align); +NK_API nk_bool nk_contextual_item_image_label(struct nk_context*, struct nk_image, const char*, nk_flags alignment); +NK_API nk_bool nk_contextual_item_image_text(struct nk_context*, struct nk_image, const char*, int len, nk_flags alignment); +NK_API nk_bool nk_contextual_item_symbol_label(struct nk_context*, enum nk_symbol_type, const char*, nk_flags alignment); +NK_API nk_bool nk_contextual_item_symbol_text(struct nk_context*, enum nk_symbol_type, const char*, int, nk_flags alignment); +NK_API void nk_contextual_close(struct nk_context*); +NK_API void nk_contextual_end(struct nk_context*); +/* ============================================================================= + * + * TOOLTIP + * + * ============================================================================= */ +NK_API void nk_tooltip(struct nk_context*, const char*); +#ifdef NK_INCLUDE_STANDARD_VARARGS +NK_API void nk_tooltipf(struct nk_context*, NK_PRINTF_FORMAT_STRING const char*, ...) NK_PRINTF_VARARG_FUNC(2); +NK_API void nk_tooltipfv(struct nk_context*, NK_PRINTF_FORMAT_STRING const char*, va_list) NK_PRINTF_VALIST_FUNC(2); +#endif +NK_API nk_bool nk_tooltip_begin(struct nk_context*, float width); +NK_API void nk_tooltip_end(struct nk_context*); +/* ============================================================================= + * + * MENU + * + * ============================================================================= */ +NK_API void nk_menubar_begin(struct nk_context*); +NK_API void nk_menubar_end(struct nk_context*); +NK_API nk_bool nk_menu_begin_text(struct nk_context*, const char* title, int title_len, nk_flags align, struct nk_vec2 size); +NK_API nk_bool nk_menu_begin_label(struct nk_context*, const char*, nk_flags align, struct nk_vec2 size); +NK_API nk_bool nk_menu_begin_image(struct nk_context*, const char*, struct nk_image, struct nk_vec2 size); +NK_API nk_bool nk_menu_begin_image_text(struct nk_context*, const char*, int,nk_flags align,struct nk_image, struct nk_vec2 size); +NK_API nk_bool nk_menu_begin_image_label(struct nk_context*, const char*, nk_flags align,struct nk_image, struct nk_vec2 size); +NK_API nk_bool nk_menu_begin_symbol(struct nk_context*, const char*, enum nk_symbol_type, struct nk_vec2 size); +NK_API nk_bool nk_menu_begin_symbol_text(struct nk_context*, const char*, int,nk_flags align,enum nk_symbol_type, struct nk_vec2 size); +NK_API nk_bool nk_menu_begin_symbol_label(struct nk_context*, const char*, nk_flags align,enum nk_symbol_type, struct nk_vec2 size); +NK_API nk_bool nk_menu_item_text(struct nk_context*, const char*, int,nk_flags align); +NK_API nk_bool nk_menu_item_label(struct nk_context*, const char*, nk_flags alignment); +NK_API nk_bool nk_menu_item_image_label(struct nk_context*, struct nk_image, const char*, nk_flags alignment); +NK_API nk_bool nk_menu_item_image_text(struct nk_context*, struct nk_image, const char*, int len, nk_flags alignment); +NK_API nk_bool nk_menu_item_symbol_text(struct nk_context*, enum nk_symbol_type, const char*, int, nk_flags alignment); +NK_API nk_bool nk_menu_item_symbol_label(struct nk_context*, enum nk_symbol_type, const char*, nk_flags alignment); +NK_API void nk_menu_close(struct nk_context*); +NK_API void nk_menu_end(struct nk_context*); +/* ============================================================================= + * + * STYLE + * + * ============================================================================= */ +enum nk_style_colors { + NK_COLOR_TEXT, + NK_COLOR_WINDOW, + NK_COLOR_HEADER, + NK_COLOR_BORDER, + NK_COLOR_BUTTON, + NK_COLOR_BUTTON_HOVER, + NK_COLOR_BUTTON_ACTIVE, + NK_COLOR_TOGGLE, + NK_COLOR_TOGGLE_HOVER, + NK_COLOR_TOGGLE_CURSOR, + NK_COLOR_SELECT, + NK_COLOR_SELECT_ACTIVE, + NK_COLOR_SLIDER, + NK_COLOR_SLIDER_CURSOR, + NK_COLOR_SLIDER_CURSOR_HOVER, + NK_COLOR_SLIDER_CURSOR_ACTIVE, + NK_COLOR_PROPERTY, + NK_COLOR_EDIT, + NK_COLOR_EDIT_CURSOR, + NK_COLOR_COMBO, + NK_COLOR_CHART, + NK_COLOR_CHART_COLOR, + NK_COLOR_CHART_COLOR_HIGHLIGHT, + NK_COLOR_SCROLLBAR, + NK_COLOR_SCROLLBAR_CURSOR, + NK_COLOR_SCROLLBAR_CURSOR_HOVER, + NK_COLOR_SCROLLBAR_CURSOR_ACTIVE, + NK_COLOR_TAB_HEADER, + NK_COLOR_COUNT +}; +enum nk_style_cursor { + NK_CURSOR_ARROW, + NK_CURSOR_TEXT, + NK_CURSOR_MOVE, + NK_CURSOR_RESIZE_VERTICAL, + NK_CURSOR_RESIZE_HORIZONTAL, + NK_CURSOR_RESIZE_TOP_LEFT_DOWN_RIGHT, + NK_CURSOR_RESIZE_TOP_RIGHT_DOWN_LEFT, + NK_CURSOR_COUNT +}; +NK_API void nk_style_default(struct nk_context*); +NK_API void nk_style_from_table(struct nk_context*, const struct nk_color*); +NK_API void nk_style_load_cursor(struct nk_context*, enum nk_style_cursor, const struct nk_cursor*); +NK_API void nk_style_load_all_cursors(struct nk_context*, struct nk_cursor*); +NK_API const char* nk_style_get_color_by_name(enum nk_style_colors); +NK_API void nk_style_set_font(struct nk_context*, const struct nk_user_font*); +NK_API nk_bool nk_style_set_cursor(struct nk_context*, enum nk_style_cursor); +NK_API void nk_style_show_cursor(struct nk_context*); +NK_API void nk_style_hide_cursor(struct nk_context*); + +NK_API nk_bool nk_style_push_font(struct nk_context*, const struct nk_user_font*); +NK_API nk_bool nk_style_push_float(struct nk_context*, float*, float); +NK_API nk_bool nk_style_push_vec2(struct nk_context*, struct nk_vec2*, struct nk_vec2); +NK_API nk_bool nk_style_push_style_item(struct nk_context*, struct nk_style_item*, struct nk_style_item); +NK_API nk_bool nk_style_push_flags(struct nk_context*, nk_flags*, nk_flags); +NK_API nk_bool nk_style_push_color(struct nk_context*, struct nk_color*, struct nk_color); + +NK_API nk_bool nk_style_pop_font(struct nk_context*); +NK_API nk_bool nk_style_pop_float(struct nk_context*); +NK_API nk_bool nk_style_pop_vec2(struct nk_context*); +NK_API nk_bool nk_style_pop_style_item(struct nk_context*); +NK_API nk_bool nk_style_pop_flags(struct nk_context*); +NK_API nk_bool nk_style_pop_color(struct nk_context*); +/* ============================================================================= + * + * COLOR + * + * ============================================================================= */ +NK_API struct nk_color nk_rgb(int r, int g, int b); +NK_API struct nk_color nk_rgb_iv(const int *rgb); +NK_API struct nk_color nk_rgb_bv(const nk_byte* rgb); +NK_API struct nk_color nk_rgb_f(float r, float g, float b); +NK_API struct nk_color nk_rgb_fv(const float *rgb); +NK_API struct nk_color nk_rgb_cf(struct nk_colorf c); +NK_API struct nk_color nk_rgb_hex(const char *rgb); + +NK_API struct nk_color nk_rgba(int r, int g, int b, int a); +NK_API struct nk_color nk_rgba_u32(nk_uint); +NK_API struct nk_color nk_rgba_iv(const int *rgba); +NK_API struct nk_color nk_rgba_bv(const nk_byte *rgba); +NK_API struct nk_color nk_rgba_f(float r, float g, float b, float a); +NK_API struct nk_color nk_rgba_fv(const float *rgba); +NK_API struct nk_color nk_rgba_cf(struct nk_colorf c); +NK_API struct nk_color nk_rgba_hex(const char *rgb); + +NK_API struct nk_colorf nk_hsva_colorf(float h, float s, float v, float a); +NK_API struct nk_colorf nk_hsva_colorfv(float *c); +NK_API void nk_colorf_hsva_f(float *out_h, float *out_s, float *out_v, float *out_a, struct nk_colorf in); +NK_API void nk_colorf_hsva_fv(float *hsva, struct nk_colorf in); + +NK_API struct nk_color nk_hsv(int h, int s, int v); +NK_API struct nk_color nk_hsv_iv(const int *hsv); +NK_API struct nk_color nk_hsv_bv(const nk_byte *hsv); +NK_API struct nk_color nk_hsv_f(float h, float s, float v); +NK_API struct nk_color nk_hsv_fv(const float *hsv); + +NK_API struct nk_color nk_hsva(int h, int s, int v, int a); +NK_API struct nk_color nk_hsva_iv(const int *hsva); +NK_API struct nk_color nk_hsva_bv(const nk_byte *hsva); +NK_API struct nk_color nk_hsva_f(float h, float s, float v, float a); +NK_API struct nk_color nk_hsva_fv(const float *hsva); + +/* color (conversion nuklear --> user) */ +NK_API void nk_color_f(float *r, float *g, float *b, float *a, struct nk_color); +NK_API void nk_color_fv(float *rgba_out, struct nk_color); +NK_API struct nk_colorf nk_color_cf(struct nk_color); +NK_API void nk_color_d(double *r, double *g, double *b, double *a, struct nk_color); +NK_API void nk_color_dv(double *rgba_out, struct nk_color); + +NK_API nk_uint nk_color_u32(struct nk_color); +NK_API void nk_color_hex_rgba(char *output, struct nk_color); +NK_API void nk_color_hex_rgb(char *output, struct nk_color); + +NK_API void nk_color_hsv_i(int *out_h, int *out_s, int *out_v, struct nk_color); +NK_API void nk_color_hsv_b(nk_byte *out_h, nk_byte *out_s, nk_byte *out_v, struct nk_color); +NK_API void nk_color_hsv_iv(int *hsv_out, struct nk_color); +NK_API void nk_color_hsv_bv(nk_byte *hsv_out, struct nk_color); +NK_API void nk_color_hsv_f(float *out_h, float *out_s, float *out_v, struct nk_color); +NK_API void nk_color_hsv_fv(float *hsv_out, struct nk_color); + +NK_API void nk_color_hsva_i(int *h, int *s, int *v, int *a, struct nk_color); +NK_API void nk_color_hsva_b(nk_byte *h, nk_byte *s, nk_byte *v, nk_byte *a, struct nk_color); +NK_API void nk_color_hsva_iv(int *hsva_out, struct nk_color); +NK_API void nk_color_hsva_bv(nk_byte *hsva_out, struct nk_color); +NK_API void nk_color_hsva_f(float *out_h, float *out_s, float *out_v, float *out_a, struct nk_color); +NK_API void nk_color_hsva_fv(float *hsva_out, struct nk_color); +/* ============================================================================= + * + * IMAGE + * + * ============================================================================= */ +NK_API nk_handle nk_handle_ptr(void*); +NK_API nk_handle nk_handle_id(int); +NK_API struct nk_image nk_image_handle(nk_handle); +NK_API struct nk_image nk_image_ptr(void*); +NK_API struct nk_image nk_image_id(int); +NK_API nk_bool nk_image_is_subimage(const struct nk_image* img); +NK_API struct nk_image nk_subimage_ptr(void*, unsigned short w, unsigned short h, struct nk_rect sub_region); +NK_API struct nk_image nk_subimage_id(int, unsigned short w, unsigned short h, struct nk_rect sub_region); +NK_API struct nk_image nk_subimage_handle(nk_handle, unsigned short w, unsigned short h, struct nk_rect sub_region); +/* ============================================================================= + * + * MATH + * + * ============================================================================= */ +NK_API nk_hash nk_murmur_hash(const void *key, int len, nk_hash seed); +NK_API void nk_triangle_from_direction(struct nk_vec2 *result, struct nk_rect r, float pad_x, float pad_y, enum nk_heading); + +NK_API struct nk_vec2 nk_vec2(float x, float y); +NK_API struct nk_vec2 nk_vec2i(int x, int y); +NK_API struct nk_vec2 nk_vec2v(const float *xy); +NK_API struct nk_vec2 nk_vec2iv(const int *xy); + +NK_API struct nk_rect nk_get_null_rect(void); +NK_API struct nk_rect nk_rect(float x, float y, float w, float h); +NK_API struct nk_rect nk_recti(int x, int y, int w, int h); +NK_API struct nk_rect nk_recta(struct nk_vec2 pos, struct nk_vec2 size); +NK_API struct nk_rect nk_rectv(const float *xywh); +NK_API struct nk_rect nk_rectiv(const int *xywh); +NK_API struct nk_vec2 nk_rect_pos(struct nk_rect); +NK_API struct nk_vec2 nk_rect_size(struct nk_rect); +/* ============================================================================= + * + * STRING + * + * ============================================================================= */ +NK_API int nk_strlen(const char *str); +NK_API int nk_stricmp(const char *s1, const char *s2); +NK_API int nk_stricmpn(const char *s1, const char *s2, int n); +NK_API int nk_strtoi(const char *str, const char **endptr); +NK_API float nk_strtof(const char *str, const char **endptr); +#ifndef NK_STRTOD +#define NK_STRTOD nk_strtod +NK_API double nk_strtod(const char *str, const char **endptr); +#endif +NK_API int nk_strfilter(const char *text, const char *regexp); +NK_API int nk_strmatch_fuzzy_string(char const *str, char const *pattern, int *out_score); +NK_API int nk_strmatch_fuzzy_text(const char *txt, int txt_len, const char *pattern, int *out_score); +/* ============================================================================= + * + * UTF-8 + * + * ============================================================================= */ +NK_API int nk_utf_decode(const char*, nk_rune*, int); +NK_API int nk_utf_encode(nk_rune, char*, int); +NK_API int nk_utf_len(const char*, int byte_len); +NK_API const char* nk_utf_at(const char *buffer, int length, int index, nk_rune *unicode, int *len); +/* =============================================================== + * + * FONT + * + * ===============================================================*/ +/* Font handling in this library was designed to be quite customizable and lets + you decide what you want to use and what you want to provide. There are three + different ways to use the font atlas. The first two will use your font + handling scheme and only requires essential data to run nuklear. The next + slightly more advanced features is font handling with vertex buffer output. + Finally the most complex API wise is using nuklear's font baking API. + + 1.) Using your own implementation without vertex buffer output + -------------------------------------------------------------- + So first up the easiest way to do font handling is by just providing a + `nk_user_font` struct which only requires the height in pixel of the used + font and a callback to calculate the width of a string. This way of handling + fonts is best fitted for using the normal draw shape command API where you + do all the text drawing yourself and the library does not require any kind + of deeper knowledge about which font handling mechanism you use. + IMPORTANT: the `nk_user_font` pointer provided to nuklear has to persist + over the complete life time! I know this sucks but it is currently the only + way to switch between fonts. + + float your_text_width_calculation(nk_handle handle, float height, const char *text, int len) + { + your_font_type *type = handle.ptr; + float text_width = ...; + return text_width; + } + + struct nk_user_font font; + font.userdata.ptr = &your_font_class_or_struct; + font.height = your_font_height; + font.width = your_text_width_calculation; + + struct nk_context ctx; + nk_init_default(&ctx, &font); + + 2.) Using your own implementation with vertex buffer output + -------------------------------------------------------------- + While the first approach works fine if you don't want to use the optional + vertex buffer output it is not enough if you do. To get font handling working + for these cases you have to provide two additional parameters inside the + `nk_user_font`. First a texture atlas handle used to draw text as subimages + of a bigger font atlas texture and a callback to query a character's glyph + information (offset, size, ...). So it is still possible to provide your own + font and use the vertex buffer output. + + float your_text_width_calculation(nk_handle handle, float height, const char *text, int len) + { + your_font_type *type = handle.ptr; + float text_width = ...; + return text_width; + } + void query_your_font_glyph(nk_handle handle, float font_height, struct nk_user_font_glyph *glyph, nk_rune codepoint, nk_rune next_codepoint) + { + your_font_type *type = handle.ptr; + glyph.width = ...; + glyph.height = ...; + glyph.xadvance = ...; + glyph.uv[0].x = ...; + glyph.uv[0].y = ...; + glyph.uv[1].x = ...; + glyph.uv[1].y = ...; + glyph.offset.x = ...; + glyph.offset.y = ...; + } + + struct nk_user_font font; + font.userdata.ptr = &your_font_class_or_struct; + font.height = your_font_height; + font.width = your_text_width_calculation; + font.query = query_your_font_glyph; + font.texture.id = your_font_texture; + + struct nk_context ctx; + nk_init_default(&ctx, &font); + + 3.) Nuklear font baker + ------------------------------------ + The final approach if you do not have a font handling functionality or don't + want to use it in this library is by using the optional font baker. + The font baker APIs can be used to create a font plus font atlas texture + and can be used with or without the vertex buffer output. + + It still uses the `nk_user_font` struct and the two different approaches + previously stated still work. The font baker is not located inside + `nk_context` like all other systems since it can be understood as more of + an extension to nuklear and does not really depend on any `nk_context` state. + + Font baker need to be initialized first by one of the nk_font_atlas_init_xxx + functions. If you don't care about memory just call the default version + `nk_font_atlas_init_default` which will allocate all memory from the standard library. + If you want to control memory allocation but you don't care if the allocated + memory is temporary and therefore can be freed directly after the baking process + is over or permanent you can call `nk_font_atlas_init`. + + After successfully initializing the font baker you can add Truetype(.ttf) fonts from + different sources like memory or from file by calling one of the `nk_font_atlas_add_xxx`. + functions. Adding font will permanently store each font, font config and ttf memory block(!) + inside the font atlas and allows to reuse the font atlas. If you don't want to reuse + the font baker by for example adding additional fonts you can call + `nk_font_atlas_cleanup` after the baking process is over (after calling nk_font_atlas_end). + + As soon as you added all fonts you wanted you can now start the baking process + for every selected glyph to image by calling `nk_font_atlas_bake`. + The baking process returns image memory, width and height which can be used to + either create your own image object or upload it to any graphics library. + No matter which case you finally have to call `nk_font_atlas_end` which + will free all temporary memory including the font atlas image so make sure + you created our texture beforehand. `nk_font_atlas_end` requires a handle + to your font texture or object and optionally fills a `struct nk_draw_null_texture` + which can be used for the optional vertex output. If you don't want it just + set the argument to `NULL`. + + At this point you are done and if you don't want to reuse the font atlas you + can call `nk_font_atlas_cleanup` to free all truetype blobs and configuration + memory. Finally if you don't use the font atlas and any of it's fonts anymore + you need to call `nk_font_atlas_clear` to free all memory still being used. + + struct nk_font_atlas atlas; + nk_font_atlas_init_default(&atlas); + nk_font_atlas_begin(&atlas); + nk_font *font = nk_font_atlas_add_from_file(&atlas, "Path/To/Your/TTF_Font.ttf", 13, 0); + nk_font *font2 = nk_font_atlas_add_from_file(&atlas, "Path/To/Your/TTF_Font2.ttf", 16, 0); + const void* img = nk_font_atlas_bake(&atlas, &img_width, &img_height, NK_FONT_ATLAS_RGBA32); + nk_font_atlas_end(&atlas, nk_handle_id(texture), 0); + + struct nk_context ctx; + nk_init_default(&ctx, &font->handle); + while (1) { + + } + nk_font_atlas_clear(&atlas); + + The font baker API is probably the most complex API inside this library and + I would suggest reading some of my examples `example/` to get a grip on how + to use the font atlas. There are a number of details I left out. For example + how to merge fonts, configure a font with `nk_font_config` to use other languages, + use another texture coordinate format and a lot more: + + struct nk_font_config cfg = nk_font_config(font_pixel_height); + cfg.merge_mode = nk_false or nk_true; + cfg.range = nk_font_korean_glyph_ranges(); + cfg.coord_type = NK_COORD_PIXEL; + nk_font *font = nk_font_atlas_add_from_file(&atlas, "Path/To/Your/TTF_Font.ttf", 13, &cfg); + +*/ +struct nk_user_font_glyph; +typedef float(*nk_text_width_f)(nk_handle, float h, const char*, int len); +typedef void(*nk_query_font_glyph_f)(nk_handle handle, float font_height, + struct nk_user_font_glyph *glyph, + nk_rune codepoint, nk_rune next_codepoint); + +#if defined(NK_INCLUDE_VERTEX_BUFFER_OUTPUT) || defined(NK_INCLUDE_SOFTWARE_FONT) +struct nk_user_font_glyph { + struct nk_vec2 uv[2]; + /* texture coordinates */ + struct nk_vec2 offset; + /* offset between top left and glyph */ + float width, height; + /* size of the glyph */ + float xadvance; + /* offset to the next glyph */ +}; +#endif + +struct nk_user_font { + nk_handle userdata; + /* user provided font handle */ + float height; + /* max height of the font */ + nk_text_width_f width; + /* font string width in pixel callback */ +#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT + nk_query_font_glyph_f query; + /* font glyph callback to query drawing info */ + nk_handle texture; + /* texture handle to the used font atlas or texture */ +#endif +}; + +#ifdef NK_INCLUDE_FONT_BAKING +enum nk_font_coord_type { + NK_COORD_UV, /* texture coordinates inside font glyphs are clamped between 0-1 */ + NK_COORD_PIXEL /* texture coordinates inside font glyphs are in absolute pixel */ +}; + +struct nk_font; +struct nk_baked_font { + float height; + /* height of the font */ + float ascent, descent; + /* font glyphs ascent and descent */ + nk_rune glyph_offset; + /* glyph array offset inside the font glyph baking output array */ + nk_rune glyph_count; + /* number of glyphs of this font inside the glyph baking array output */ + const nk_rune *ranges; + /* font codepoint ranges as pairs of (from/to) and 0 as last element */ +}; + +struct nk_font_config { + struct nk_font_config *next; + /* NOTE: only used internally */ + void *ttf_blob; + /* pointer to loaded TTF file memory block. + * NOTE: not needed for nk_font_atlas_add_from_memory and nk_font_atlas_add_from_file. */ + nk_size ttf_size; + /* size of the loaded TTF file memory block + * NOTE: not needed for nk_font_atlas_add_from_memory and nk_font_atlas_add_from_file. */ + + unsigned char ttf_data_owned_by_atlas; + /* used inside font atlas: default to: 0*/ + unsigned char merge_mode; + /* merges this font into the last font */ + unsigned char pixel_snap; + /* align every character to pixel boundary (if true set oversample (1,1)) */ + unsigned char oversample_v, oversample_h; + /* rasterize at hight quality for sub-pixel position */ + unsigned char padding[3]; + + float size; + /* baked pixel height of the font */ + enum nk_font_coord_type coord_type; + /* texture coordinate format with either pixel or UV coordinates */ + struct nk_vec2 spacing; + /* extra pixel spacing between glyphs */ + const nk_rune *range; + /* list of unicode ranges (2 values per range, zero terminated) */ + struct nk_baked_font *font; + /* font to setup in the baking process: NOTE: not needed for font atlas */ + nk_rune fallback_glyph; + /* fallback glyph to use if a given rune is not found */ + struct nk_font_config *n; + struct nk_font_config *p; +}; + +struct nk_font_glyph { + nk_rune codepoint; + float xadvance; + float x0, y0, x1, y1, w, h; + float u0, v0, u1, v1; +}; + +struct nk_font { + struct nk_font *next; + struct nk_user_font handle; + struct nk_baked_font info; + float scale; + struct nk_font_glyph *glyphs; + const struct nk_font_glyph *fallback; + nk_rune fallback_codepoint; + nk_handle texture; + struct nk_font_config *config; +}; + +enum nk_font_atlas_format { + NK_FONT_ATLAS_ALPHA8, + NK_FONT_ATLAS_RGBA32 +}; + +struct nk_font_atlas { + void *pixel; + int tex_width; + int tex_height; + + struct nk_allocator permanent; + struct nk_allocator temporary; + + struct nk_recti custom; + struct nk_cursor cursors[NK_CURSOR_COUNT]; + + int glyph_count; + struct nk_font_glyph *glyphs; + struct nk_font *default_font; + struct nk_font *fonts; + struct nk_font_config *config; + int font_num; +}; + +/* some language glyph codepoint ranges */ +NK_API const nk_rune *nk_font_default_glyph_ranges(void); +NK_API const nk_rune *nk_font_chinese_glyph_ranges(void); +NK_API const nk_rune *nk_font_cyrillic_glyph_ranges(void); +NK_API const nk_rune *nk_font_korean_glyph_ranges(void); + +#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR +NK_API void nk_font_atlas_init_default(struct nk_font_atlas*); +#endif +NK_API void nk_font_atlas_init(struct nk_font_atlas*, struct nk_allocator*); +NK_API void nk_font_atlas_init_custom(struct nk_font_atlas*, struct nk_allocator *persistent, struct nk_allocator *transient); +NK_API void nk_font_atlas_begin(struct nk_font_atlas*); +NK_API struct nk_font_config nk_font_config(float pixel_height); +NK_API struct nk_font *nk_font_atlas_add(struct nk_font_atlas*, const struct nk_font_config*); +#ifdef NK_INCLUDE_DEFAULT_FONT +NK_API struct nk_font* nk_font_atlas_add_default(struct nk_font_atlas*, float height, const struct nk_font_config*); +#endif +NK_API struct nk_font* nk_font_atlas_add_from_memory(struct nk_font_atlas *atlas, void *memory, nk_size size, float height, const struct nk_font_config *config); +#ifdef NK_INCLUDE_STANDARD_IO +NK_API struct nk_font* nk_font_atlas_add_from_file(struct nk_font_atlas *atlas, const char *file_path, float height, const struct nk_font_config*); +#endif +NK_API struct nk_font *nk_font_atlas_add_compressed(struct nk_font_atlas*, void *memory, nk_size size, float height, const struct nk_font_config*); +NK_API struct nk_font* nk_font_atlas_add_compressed_base85(struct nk_font_atlas*, const char *data, float height, const struct nk_font_config *config); +NK_API const void* nk_font_atlas_bake(struct nk_font_atlas*, int *width, int *height, enum nk_font_atlas_format); +NK_API void nk_font_atlas_end(struct nk_font_atlas*, nk_handle tex, struct nk_draw_null_texture*); +NK_API const struct nk_font_glyph* nk_font_find_glyph(struct nk_font*, nk_rune unicode); +NK_API void nk_font_atlas_cleanup(struct nk_font_atlas *atlas); +NK_API void nk_font_atlas_clear(struct nk_font_atlas*); + +#endif + +/* ============================================================== + * + * MEMORY BUFFER + * + * ===============================================================*/ +/* A basic (double)-buffer with linear allocation and resetting as only + freeing policy. The buffer's main purpose is to control all memory management + inside the GUI toolkit and still leave memory control as much as possible in + the hand of the user while also making sure the library is easy to use if + not as much control is needed. + In general all memory inside this library can be provided from the user in + three different ways. + + The first way and the one providing most control is by just passing a fixed + size memory block. In this case all control lies in the hand of the user + since he can exactly control where the memory comes from and how much memory + the library should consume. Of course using the fixed size API removes the + ability to automatically resize a buffer if not enough memory is provided so + you have to take over the resizing. While being a fixed sized buffer sounds + quite limiting, it is very effective in this library since the actual memory + consumption is quite stable and has a fixed upper bound for a lot of cases. + + If you don't want to think about how much memory the library should allocate + at all time or have a very dynamic UI with unpredictable memory consumption + habits but still want control over memory allocation you can use the dynamic + allocator based API. The allocator consists of two callbacks for allocating + and freeing memory and optional userdata so you can plugin your own allocator. + + The final and easiest way can be used by defining + NK_INCLUDE_DEFAULT_ALLOCATOR which uses the standard library memory + allocation functions malloc and free and takes over complete control over + memory in this library. +*/ +struct nk_memory_status { + void *memory; + unsigned int type; + nk_size size; + nk_size allocated; + nk_size needed; + nk_size calls; +}; + +enum nk_allocation_type { + NK_BUFFER_FIXED, + NK_BUFFER_DYNAMIC +}; + +enum nk_buffer_allocation_type { + NK_BUFFER_FRONT, + NK_BUFFER_BACK, + NK_BUFFER_MAX +}; + +struct nk_buffer_marker { + nk_bool active; + nk_size offset; +}; + +struct nk_memory {void *ptr;nk_size size;}; +struct nk_buffer { + struct nk_buffer_marker marker[NK_BUFFER_MAX]; + /* buffer marker to free a buffer to a certain offset */ + struct nk_allocator pool; + /* allocator callback for dynamic buffers */ + enum nk_allocation_type type; + /* memory management type */ + struct nk_memory memory; + /* memory and size of the current memory block */ + float grow_factor; + /* growing factor for dynamic memory management */ + nk_size allocated; + /* total amount of memory allocated */ + nk_size needed; + /* totally consumed memory given that enough memory is present */ + nk_size calls; + /* number of allocation calls */ + nk_size size; + /* current size of the buffer */ +}; + +#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR +NK_API void nk_buffer_init_default(struct nk_buffer*); +#endif +NK_API void nk_buffer_init(struct nk_buffer*, const struct nk_allocator*, nk_size size); +NK_API void nk_buffer_init_fixed(struct nk_buffer*, void *memory, nk_size size); +NK_API void nk_buffer_info(struct nk_memory_status*, struct nk_buffer*); +NK_API void nk_buffer_push(struct nk_buffer*, enum nk_buffer_allocation_type type, const void *memory, nk_size size, nk_size align); +NK_API void nk_buffer_mark(struct nk_buffer*, enum nk_buffer_allocation_type type); +NK_API void nk_buffer_reset(struct nk_buffer*, enum nk_buffer_allocation_type type); +NK_API void nk_buffer_clear(struct nk_buffer*); +NK_API void nk_buffer_free(struct nk_buffer*); +NK_API void *nk_buffer_memory(struct nk_buffer*); +NK_API const void *nk_buffer_memory_const(const struct nk_buffer*); +NK_API nk_size nk_buffer_total(struct nk_buffer*); + +/* ============================================================== + * + * STRING + * + * ===============================================================*/ +/* Basic string buffer which is only used in context with the text editor + * to manage and manipulate dynamic or fixed size string content. This is _NOT_ + * the default string handling method. The only instance you should have any contact + * with this API is if you interact with an `nk_text_edit` object inside one of the + * copy and paste functions and even there only for more advanced cases. */ +struct nk_str { + struct nk_buffer buffer; + int len; /* in codepoints/runes/glyphs */ +}; + +#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR +NK_API void nk_str_init_default(struct nk_str*); +#endif +NK_API void nk_str_init(struct nk_str*, const struct nk_allocator*, nk_size size); +NK_API void nk_str_init_fixed(struct nk_str*, void *memory, nk_size size); +NK_API void nk_str_clear(struct nk_str*); +NK_API void nk_str_free(struct nk_str*); + +NK_API int nk_str_append_text_char(struct nk_str*, const char*, int); +NK_API int nk_str_append_str_char(struct nk_str*, const char*); +NK_API int nk_str_append_text_utf8(struct nk_str*, const char*, int); +NK_API int nk_str_append_str_utf8(struct nk_str*, const char*); +NK_API int nk_str_append_text_runes(struct nk_str*, const nk_rune*, int); +NK_API int nk_str_append_str_runes(struct nk_str*, const nk_rune*); + +NK_API int nk_str_insert_at_char(struct nk_str*, int pos, const char*, int); +NK_API int nk_str_insert_at_rune(struct nk_str*, int pos, const char*, int); + +NK_API int nk_str_insert_text_char(struct nk_str*, int pos, const char*, int); +NK_API int nk_str_insert_str_char(struct nk_str*, int pos, const char*); +NK_API int nk_str_insert_text_utf8(struct nk_str*, int pos, const char*, int); +NK_API int nk_str_insert_str_utf8(struct nk_str*, int pos, const char*); +NK_API int nk_str_insert_text_runes(struct nk_str*, int pos, const nk_rune*, int); +NK_API int nk_str_insert_str_runes(struct nk_str*, int pos, const nk_rune*); + +NK_API void nk_str_remove_chars(struct nk_str*, int len); +NK_API void nk_str_remove_runes(struct nk_str *str, int len); +NK_API void nk_str_delete_chars(struct nk_str*, int pos, int len); +NK_API void nk_str_delete_runes(struct nk_str*, int pos, int len); + +NK_API char *nk_str_at_char(struct nk_str*, int pos); +NK_API char *nk_str_at_rune(struct nk_str*, int pos, nk_rune *unicode, int *len); +NK_API nk_rune nk_str_rune_at(const struct nk_str*, int pos); +NK_API const char *nk_str_at_char_const(const struct nk_str*, int pos); +NK_API const char *nk_str_at_const(const struct nk_str*, int pos, nk_rune *unicode, int *len); + +NK_API char *nk_str_get(struct nk_str*); +NK_API const char *nk_str_get_const(const struct nk_str*); +NK_API int nk_str_len(struct nk_str*); +NK_API int nk_str_len_char(struct nk_str*); + +/*=============================================================== + * + * TEXT EDITOR + * + * ===============================================================*/ +/* Editing text in this library is handled by either `nk_edit_string` or + * `nk_edit_buffer`. But like almost everything in this library there are multiple + * ways of doing it and a balance between control and ease of use with memory + * as well as functionality controlled by flags. + * + * This library generally allows three different levels of memory control: + * First of is the most basic way of just providing a simple char array with + * string length. This method is probably the easiest way of handling simple + * user text input. Main upside is complete control over memory while the biggest + * downside in comparison with the other two approaches is missing undo/redo. + * + * For UIs that require undo/redo the second way was created. It is based on + * a fixed size nk_text_edit struct, which has an internal undo/redo stack. + * This is mainly useful if you want something more like a text editor but don't want + * to have a dynamically growing buffer. + * + * The final way is using a dynamically growing nk_text_edit struct, which + * has both a default version if you don't care where memory comes from and an + * allocator version if you do. While the text editor is quite powerful for its + * complexity I would not recommend editing gigabytes of data with it. + * It is rather designed for uses cases which make sense for a GUI library not for + * an full blown text editor. + */ +#ifndef NK_TEXTEDIT_UNDOSTATECOUNT +#define NK_TEXTEDIT_UNDOSTATECOUNT 99 +#endif + +#ifndef NK_TEXTEDIT_UNDOCHARCOUNT +#define NK_TEXTEDIT_UNDOCHARCOUNT 999 +#endif + +struct nk_text_edit; +struct nk_clipboard { + nk_handle userdata; + nk_plugin_paste paste; + nk_plugin_copy copy; +}; + +struct nk_text_undo_record { + int where; + short insert_length; + short delete_length; + short char_storage; +}; + +struct nk_text_undo_state { + struct nk_text_undo_record undo_rec[NK_TEXTEDIT_UNDOSTATECOUNT]; + nk_rune undo_char[NK_TEXTEDIT_UNDOCHARCOUNT]; + short undo_point; + short redo_point; + short undo_char_point; + short redo_char_point; +}; + +enum nk_text_edit_type { + NK_TEXT_EDIT_SINGLE_LINE, + NK_TEXT_EDIT_MULTI_LINE +}; + +enum nk_text_edit_mode { + NK_TEXT_EDIT_MODE_VIEW, + NK_TEXT_EDIT_MODE_INSERT, + NK_TEXT_EDIT_MODE_REPLACE +}; + +struct nk_text_edit { + struct nk_clipboard clip; + struct nk_str string; + nk_plugin_filter filter; + struct nk_vec2 scrollbar; + + int cursor; + int select_start; + int select_end; + unsigned char mode; + unsigned char cursor_at_end_of_line; + unsigned char initialized; + unsigned char has_preferred_x; + unsigned char single_line; + unsigned char active; + unsigned char padding1; + float preferred_x; + struct nk_text_undo_state undo; +}; + +/* filter function */ +NK_API nk_bool nk_filter_default(const struct nk_text_edit*, nk_rune unicode); +NK_API nk_bool nk_filter_ascii(const struct nk_text_edit*, nk_rune unicode); +NK_API nk_bool nk_filter_float(const struct nk_text_edit*, nk_rune unicode); +NK_API nk_bool nk_filter_decimal(const struct nk_text_edit*, nk_rune unicode); +NK_API nk_bool nk_filter_hex(const struct nk_text_edit*, nk_rune unicode); +NK_API nk_bool nk_filter_oct(const struct nk_text_edit*, nk_rune unicode); +NK_API nk_bool nk_filter_binary(const struct nk_text_edit*, nk_rune unicode); + +/* text editor */ +#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR +NK_API void nk_textedit_init_default(struct nk_text_edit*); +#endif +NK_API void nk_textedit_init(struct nk_text_edit*, struct nk_allocator*, nk_size size); +NK_API void nk_textedit_init_fixed(struct nk_text_edit*, void *memory, nk_size size); +NK_API void nk_textedit_free(struct nk_text_edit*); +NK_API void nk_textedit_text(struct nk_text_edit*, const char*, int total_len); +NK_API void nk_textedit_delete(struct nk_text_edit*, int where, int len); +NK_API void nk_textedit_delete_selection(struct nk_text_edit*); +NK_API void nk_textedit_select_all(struct nk_text_edit*); +NK_API nk_bool nk_textedit_cut(struct nk_text_edit*); +NK_API nk_bool nk_textedit_paste(struct nk_text_edit*, char const*, int len); +NK_API void nk_textedit_undo(struct nk_text_edit*); +NK_API void nk_textedit_redo(struct nk_text_edit*); + +/* =============================================================== + * + * DRAWING + * + * ===============================================================*/ +/* This library was designed to be render backend agnostic so it does + not draw anything to screen. Instead all drawn shapes, widgets + are made of, are buffered into memory and make up a command queue. + Each frame therefore fills the command buffer with draw commands + that then need to be executed by the user and his own render backend. + After that the command buffer needs to be cleared and a new frame can be + started. It is probably important to note that the command buffer is the main + drawing API and the optional vertex buffer API only takes this format and + converts it into a hardware accessible format. + + To use the command queue to draw your own widgets you can access the + command buffer of each window by calling `nk_window_get_canvas` after + previously having called `nk_begin`: + + void draw_red_rectangle_widget(struct nk_context *ctx) + { + struct nk_command_buffer *canvas; + struct nk_input *input = &ctx->input; + canvas = nk_window_get_canvas(ctx); + + struct nk_rect space; + enum nk_widget_layout_states state; + state = nk_widget(&space, ctx); + if (!state) return; + + if (state != NK_WIDGET_ROM) + update_your_widget_by_user_input(...); + nk_fill_rect(canvas, space, 0, nk_rgb(255,0,0)); + } + + if (nk_begin(...)) { + nk_layout_row_dynamic(ctx, 25, 1); + draw_red_rectangle_widget(ctx); + } + nk_end(..) + + Important to know if you want to create your own widgets is the `nk_widget` + call. It allocates space on the panel reserved for this widget to be used, + but also returns the state of the widget space. If your widget is not seen and does + not have to be updated it is '0' and you can just return. If it only has + to be drawn the state will be `NK_WIDGET_ROM` otherwise you can do both + update and draw your widget. The reason for separating is to only draw and + update what is actually necessary which is crucial for performance. +*/ +enum nk_command_type { + NK_COMMAND_NOP, + NK_COMMAND_SCISSOR, + NK_COMMAND_LINE, + NK_COMMAND_CURVE, + NK_COMMAND_RECT, + NK_COMMAND_RECT_FILLED, + NK_COMMAND_RECT_MULTI_COLOR, + NK_COMMAND_CIRCLE, + NK_COMMAND_CIRCLE_FILLED, + NK_COMMAND_ARC, + NK_COMMAND_ARC_FILLED, + NK_COMMAND_TRIANGLE, + NK_COMMAND_TRIANGLE_FILLED, + NK_COMMAND_POLYGON, + NK_COMMAND_POLYGON_FILLED, + NK_COMMAND_POLYLINE, + NK_COMMAND_TEXT, + NK_COMMAND_IMAGE, + NK_COMMAND_CUSTOM +}; + +/* command base and header of every command inside the buffer */ +struct nk_command { + enum nk_command_type type; + nk_size next; +#ifdef NK_INCLUDE_COMMAND_USERDATA + nk_handle userdata; +#endif +}; + +struct nk_command_scissor { + struct nk_command header; + short x, y; + unsigned short w, h; +}; + +struct nk_command_line { + struct nk_command header; + unsigned short line_thickness; + struct nk_vec2i begin; + struct nk_vec2i end; + struct nk_color color; +}; + +struct nk_command_curve { + struct nk_command header; + unsigned short line_thickness; + struct nk_vec2i begin; + struct nk_vec2i end; + struct nk_vec2i ctrl[2]; + struct nk_color color; +}; + +struct nk_command_rect { + struct nk_command header; + unsigned short rounding; + unsigned short line_thickness; + short x, y; + unsigned short w, h; + struct nk_color color; +}; + +struct nk_command_rect_filled { + struct nk_command header; + unsigned short rounding; + short x, y; + unsigned short w, h; + struct nk_color color; +}; + +struct nk_command_rect_multi_color { + struct nk_command header; + short x, y; + unsigned short w, h; + struct nk_color left; + struct nk_color top; + struct nk_color bottom; + struct nk_color right; +}; + +struct nk_command_triangle { + struct nk_command header; + unsigned short line_thickness; + struct nk_vec2i a; + struct nk_vec2i b; + struct nk_vec2i c; + struct nk_color color; +}; + +struct nk_command_triangle_filled { + struct nk_command header; + struct nk_vec2i a; + struct nk_vec2i b; + struct nk_vec2i c; + struct nk_color color; +}; + +struct nk_command_circle { + struct nk_command header; + short x, y; + unsigned short line_thickness; + unsigned short w, h; + struct nk_color color; +}; + +struct nk_command_circle_filled { + struct nk_command header; + short x, y; + unsigned short w, h; + struct nk_color color; +}; + +struct nk_command_arc { + struct nk_command header; + short cx, cy; + unsigned short r; + unsigned short line_thickness; + float a[2]; + struct nk_color color; +}; + +struct nk_command_arc_filled { + struct nk_command header; + short cx, cy; + unsigned short r; + float a[2]; + struct nk_color color; +}; + +struct nk_command_polygon { + struct nk_command header; + struct nk_color color; + unsigned short line_thickness; + unsigned short point_count; + struct nk_vec2i points[1]; +}; + +struct nk_command_polygon_filled { + struct nk_command header; + struct nk_color color; + unsigned short point_count; + struct nk_vec2i points[1]; +}; + +struct nk_command_polyline { + struct nk_command header; + struct nk_color color; + unsigned short line_thickness; + unsigned short point_count; + struct nk_vec2i points[1]; +}; + +struct nk_command_image { + struct nk_command header; + short x, y; + unsigned short w, h; + struct nk_image img; + struct nk_color col; +}; + +typedef void (*nk_command_custom_callback)(void *canvas, short x,short y, + unsigned short w, unsigned short h, nk_handle callback_data); +struct nk_command_custom { + struct nk_command header; + short x, y; + unsigned short w, h; + nk_handle callback_data; + nk_command_custom_callback callback; +}; + +struct nk_command_text { + struct nk_command header; + const struct nk_user_font *font; + struct nk_color background; + struct nk_color foreground; + short x, y; + unsigned short w, h; + float height; + int length; + char string[1]; +}; + +enum nk_command_clipping { + NK_CLIPPING_OFF = nk_false, + NK_CLIPPING_ON = nk_true +}; + +struct nk_command_buffer { + struct nk_buffer *base; + struct nk_rect clip; + int use_clipping; + nk_handle userdata; + nk_size begin, end, last; +}; + +/* shape outlines */ +NK_API void nk_stroke_line(struct nk_command_buffer *b, float x0, float y0, float x1, float y1, float line_thickness, struct nk_color); +NK_API void nk_stroke_curve(struct nk_command_buffer*, float, float, float, float, float, float, float, float, float line_thickness, struct nk_color); +NK_API void nk_stroke_rect(struct nk_command_buffer*, struct nk_rect, float rounding, float line_thickness, struct nk_color); +NK_API void nk_stroke_circle(struct nk_command_buffer*, struct nk_rect, float line_thickness, struct nk_color); +NK_API void nk_stroke_arc(struct nk_command_buffer*, float cx, float cy, float radius, float a_min, float a_max, float line_thickness, struct nk_color); +NK_API void nk_stroke_triangle(struct nk_command_buffer*, float, float, float, float, float, float, float line_thichness, struct nk_color); +NK_API void nk_stroke_polyline(struct nk_command_buffer*, float *points, int point_count, float line_thickness, struct nk_color col); +NK_API void nk_stroke_polygon(struct nk_command_buffer*, float*, int point_count, float line_thickness, struct nk_color); + +/* filled shades */ +NK_API void nk_fill_rect(struct nk_command_buffer*, struct nk_rect, float rounding, struct nk_color); +NK_API void nk_fill_rect_multi_color(struct nk_command_buffer*, struct nk_rect, struct nk_color left, struct nk_color top, struct nk_color right, struct nk_color bottom); +NK_API void nk_fill_circle(struct nk_command_buffer*, struct nk_rect, struct nk_color); +NK_API void nk_fill_arc(struct nk_command_buffer*, float cx, float cy, float radius, float a_min, float a_max, struct nk_color); +NK_API void nk_fill_triangle(struct nk_command_buffer*, float x0, float y0, float x1, float y1, float x2, float y2, struct nk_color); +NK_API void nk_fill_polygon(struct nk_command_buffer*, float*, int point_count, struct nk_color); + +/* misc */ +NK_API void nk_draw_image(struct nk_command_buffer*, struct nk_rect, const struct nk_image*, struct nk_color); +NK_API void nk_draw_text(struct nk_command_buffer*, struct nk_rect, const char *text, int len, const struct nk_user_font*, struct nk_color, struct nk_color); +NK_API void nk_push_scissor(struct nk_command_buffer*, struct nk_rect); +NK_API void nk_push_custom(struct nk_command_buffer*, struct nk_rect, nk_command_custom_callback, nk_handle usr); + +/* =============================================================== + * + * INPUT + * + * ===============================================================*/ +struct nk_mouse_button { + nk_bool down; + unsigned int clicked; + struct nk_vec2 clicked_pos; +}; +struct nk_mouse { + struct nk_mouse_button buttons[NK_BUTTON_MAX]; + struct nk_vec2 pos; + struct nk_vec2 prev; + struct nk_vec2 delta; + struct nk_vec2 scroll_delta; + unsigned char grab; + unsigned char grabbed; + unsigned char ungrab; +}; + +struct nk_key { + nk_bool down; + unsigned int clicked; +}; +struct nk_keyboard { + struct nk_key keys[NK_KEY_MAX]; + char text[NK_INPUT_MAX]; + int text_len; +}; + +struct nk_input { + struct nk_keyboard keyboard; + struct nk_mouse mouse; +}; + +NK_API nk_bool nk_input_has_mouse_click(const struct nk_input*, enum nk_buttons); +NK_API nk_bool nk_input_has_mouse_click_in_rect(const struct nk_input*, enum nk_buttons, struct nk_rect); +NK_API nk_bool nk_input_has_mouse_click_down_in_rect(const struct nk_input*, enum nk_buttons, struct nk_rect, nk_bool down); +NK_API nk_bool nk_input_is_mouse_click_in_rect(const struct nk_input*, enum nk_buttons, struct nk_rect); +NK_API nk_bool nk_input_is_mouse_click_down_in_rect(const struct nk_input *i, enum nk_buttons id, struct nk_rect b, nk_bool down); +NK_API nk_bool nk_input_any_mouse_click_in_rect(const struct nk_input*, struct nk_rect); +NK_API nk_bool nk_input_is_mouse_prev_hovering_rect(const struct nk_input*, struct nk_rect); +NK_API nk_bool nk_input_is_mouse_hovering_rect(const struct nk_input*, struct nk_rect); +NK_API nk_bool nk_input_mouse_clicked(const struct nk_input*, enum nk_buttons, struct nk_rect); +NK_API nk_bool nk_input_is_mouse_down(const struct nk_input*, enum nk_buttons); +NK_API nk_bool nk_input_is_mouse_pressed(const struct nk_input*, enum nk_buttons); +NK_API nk_bool nk_input_is_mouse_released(const struct nk_input*, enum nk_buttons); +NK_API nk_bool nk_input_is_key_pressed(const struct nk_input*, enum nk_keys); +NK_API nk_bool nk_input_is_key_released(const struct nk_input*, enum nk_keys); +NK_API nk_bool nk_input_is_key_down(const struct nk_input*, enum nk_keys); + +/* =============================================================== + * + * DRAW LIST + * + * ===============================================================*/ +#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT +/* The optional vertex buffer draw list provides a 2D drawing context + with antialiasing functionality which takes basic filled or outlined shapes + or a path and outputs vertexes, elements and draw commands. + The actual draw list API is not required to be used directly while using this + library since converting the default library draw command output is done by + just calling `nk_convert` but I decided to still make this library accessible + since it can be useful. + + The draw list is based on a path buffering and polygon and polyline + rendering API which allows a lot of ways to draw 2D content to screen. + In fact it is probably more powerful than needed but allows even more crazy + things than this library provides by default. +*/ +#ifdef NK_UINT_DRAW_INDEX +typedef nk_uint nk_draw_index; +#else +typedef nk_ushort nk_draw_index; +#endif +enum nk_draw_list_stroke { + NK_STROKE_OPEN = nk_false, + /* build up path has no connection back to the beginning */ + NK_STROKE_CLOSED = nk_true + /* build up path has a connection back to the beginning */ +}; + +enum nk_draw_vertex_layout_attribute { + NK_VERTEX_POSITION, + NK_VERTEX_COLOR, + NK_VERTEX_TEXCOORD, + NK_VERTEX_ATTRIBUTE_COUNT +}; + +enum nk_draw_vertex_layout_format { + NK_FORMAT_SCHAR, + NK_FORMAT_SSHORT, + NK_FORMAT_SINT, + NK_FORMAT_UCHAR, + NK_FORMAT_USHORT, + NK_FORMAT_UINT, + NK_FORMAT_FLOAT, + NK_FORMAT_DOUBLE, + +NK_FORMAT_COLOR_BEGIN, + NK_FORMAT_R8G8B8 = NK_FORMAT_COLOR_BEGIN, + NK_FORMAT_R16G15B16, + NK_FORMAT_R32G32B32, + + NK_FORMAT_R8G8B8A8, + NK_FORMAT_B8G8R8A8, + NK_FORMAT_R16G15B16A16, + NK_FORMAT_R32G32B32A32, + NK_FORMAT_R32G32B32A32_FLOAT, + NK_FORMAT_R32G32B32A32_DOUBLE, + + NK_FORMAT_RGB32, + NK_FORMAT_RGBA32, +NK_FORMAT_COLOR_END = NK_FORMAT_RGBA32, + NK_FORMAT_COUNT +}; + +#define NK_VERTEX_LAYOUT_END NK_VERTEX_ATTRIBUTE_COUNT,NK_FORMAT_COUNT,0 +struct nk_draw_vertex_layout_element { + enum nk_draw_vertex_layout_attribute attribute; + enum nk_draw_vertex_layout_format format; + nk_size offset; +}; + +struct nk_draw_command { + unsigned int elem_count; + /* number of elements in the current draw batch */ + struct nk_rect clip_rect; + /* current screen clipping rectangle */ + nk_handle texture; + /* current texture to set */ +#ifdef NK_INCLUDE_COMMAND_USERDATA + nk_handle userdata; +#endif +}; + +struct nk_draw_list { + struct nk_rect clip_rect; + struct nk_vec2 circle_vtx[12]; + struct nk_convert_config config; + + struct nk_buffer *buffer; + struct nk_buffer *vertices; + struct nk_buffer *elements; + + unsigned int element_count; + unsigned int vertex_count; + unsigned int cmd_count; + nk_size cmd_offset; + + unsigned int path_count; + unsigned int path_offset; + + enum nk_anti_aliasing line_AA; + enum nk_anti_aliasing shape_AA; + +#ifdef NK_INCLUDE_COMMAND_USERDATA + nk_handle userdata; +#endif +}; + +/* draw list */ +NK_API void nk_draw_list_init(struct nk_draw_list*); +NK_API void nk_draw_list_setup(struct nk_draw_list*, const struct nk_convert_config*, struct nk_buffer *cmds, struct nk_buffer *vertices, struct nk_buffer *elements, enum nk_anti_aliasing line_aa,enum nk_anti_aliasing shape_aa); + +/* drawing */ +#define nk_draw_list_foreach(cmd, can, b) for((cmd)=nk__draw_list_begin(can, b); (cmd)!=0; (cmd)=nk__draw_list_next(cmd, b, can)) +NK_API const struct nk_draw_command* nk__draw_list_begin(const struct nk_draw_list*, const struct nk_buffer*); +NK_API const struct nk_draw_command* nk__draw_list_next(const struct nk_draw_command*, const struct nk_buffer*, const struct nk_draw_list*); +NK_API const struct nk_draw_command* nk__draw_list_end(const struct nk_draw_list*, const struct nk_buffer*); + +/* path */ +NK_API void nk_draw_list_path_clear(struct nk_draw_list*); +NK_API void nk_draw_list_path_line_to(struct nk_draw_list*, struct nk_vec2 pos); +NK_API void nk_draw_list_path_arc_to_fast(struct nk_draw_list*, struct nk_vec2 center, float radius, int a_min, int a_max); +NK_API void nk_draw_list_path_arc_to(struct nk_draw_list*, struct nk_vec2 center, float radius, float a_min, float a_max, unsigned int segments); +NK_API void nk_draw_list_path_rect_to(struct nk_draw_list*, struct nk_vec2 a, struct nk_vec2 b, float rounding); +NK_API void nk_draw_list_path_curve_to(struct nk_draw_list*, struct nk_vec2 p2, struct nk_vec2 p3, struct nk_vec2 p4, unsigned int num_segments); +NK_API void nk_draw_list_path_fill(struct nk_draw_list*, struct nk_color); +NK_API void nk_draw_list_path_stroke(struct nk_draw_list*, struct nk_color, enum nk_draw_list_stroke closed, float thickness); + +/* stroke */ +NK_API void nk_draw_list_stroke_line(struct nk_draw_list*, struct nk_vec2 a, struct nk_vec2 b, struct nk_color, float thickness); +NK_API void nk_draw_list_stroke_rect(struct nk_draw_list*, struct nk_rect rect, struct nk_color, float rounding, float thickness); +NK_API void nk_draw_list_stroke_triangle(struct nk_draw_list*, struct nk_vec2 a, struct nk_vec2 b, struct nk_vec2 c, struct nk_color, float thickness); +NK_API void nk_draw_list_stroke_circle(struct nk_draw_list*, struct nk_vec2 center, float radius, struct nk_color, unsigned int segs, float thickness); +NK_API void nk_draw_list_stroke_curve(struct nk_draw_list*, struct nk_vec2 p0, struct nk_vec2 cp0, struct nk_vec2 cp1, struct nk_vec2 p1, struct nk_color, unsigned int segments, float thickness); +NK_API void nk_draw_list_stroke_poly_line(struct nk_draw_list*, const struct nk_vec2 *pnts, const unsigned int cnt, struct nk_color, enum nk_draw_list_stroke, float thickness, enum nk_anti_aliasing); + +/* fill */ +NK_API void nk_draw_list_fill_rect(struct nk_draw_list*, struct nk_rect rect, struct nk_color, float rounding); +NK_API void nk_draw_list_fill_rect_multi_color(struct nk_draw_list*, struct nk_rect rect, struct nk_color left, struct nk_color top, struct nk_color right, struct nk_color bottom); +NK_API void nk_draw_list_fill_triangle(struct nk_draw_list*, struct nk_vec2 a, struct nk_vec2 b, struct nk_vec2 c, struct nk_color); +NK_API void nk_draw_list_fill_circle(struct nk_draw_list*, struct nk_vec2 center, float radius, struct nk_color col, unsigned int segs); +NK_API void nk_draw_list_fill_poly_convex(struct nk_draw_list*, const struct nk_vec2 *points, const unsigned int count, struct nk_color, enum nk_anti_aliasing); + +/* misc */ +NK_API void nk_draw_list_add_image(struct nk_draw_list*, struct nk_image texture, struct nk_rect rect, struct nk_color); +NK_API void nk_draw_list_add_text(struct nk_draw_list*, const struct nk_user_font*, struct nk_rect, const char *text, int len, float font_height, struct nk_color); +#ifdef NK_INCLUDE_COMMAND_USERDATA +NK_API void nk_draw_list_push_userdata(struct nk_draw_list*, nk_handle userdata); +#endif + +#endif + +/* =============================================================== + * + * GUI + * + * ===============================================================*/ +enum nk_style_item_type { + NK_STYLE_ITEM_COLOR, + NK_STYLE_ITEM_IMAGE +}; + +union nk_style_item_data { + struct nk_image image; + struct nk_color color; +}; + +struct nk_style_item { + enum nk_style_item_type type; + union nk_style_item_data data; +}; + +struct nk_style_text { + struct nk_color color; + struct nk_vec2 padding; +}; + +struct nk_style_button { + /* background */ + struct nk_style_item normal; + struct nk_style_item hover; + struct nk_style_item active; + struct nk_color border_color; + + /* text */ + struct nk_color text_background; + struct nk_color text_normal; + struct nk_color text_hover; + struct nk_color text_active; + nk_flags text_alignment; + + /* properties */ + float border; + float rounding; + struct nk_vec2 padding; + struct nk_vec2 image_padding; + struct nk_vec2 touch_padding; + + /* optional user callbacks */ + nk_handle userdata; + void(*draw_begin)(struct nk_command_buffer*, nk_handle userdata); + void(*draw_end)(struct nk_command_buffer*, nk_handle userdata); +}; + +struct nk_style_toggle { + /* background */ + struct nk_style_item normal; + struct nk_style_item hover; + struct nk_style_item active; + struct nk_color border_color; + + /* cursor */ + struct nk_style_item cursor_normal; + struct nk_style_item cursor_hover; + + /* text */ + struct nk_color text_normal; + struct nk_color text_hover; + struct nk_color text_active; + struct nk_color text_background; + nk_flags text_alignment; + + /* properties */ + struct nk_vec2 padding; + struct nk_vec2 touch_padding; + float spacing; + float border; + + /* optional user callbacks */ + nk_handle userdata; + void(*draw_begin)(struct nk_command_buffer*, nk_handle); + void(*draw_end)(struct nk_command_buffer*, nk_handle); +}; + +struct nk_style_selectable { + /* background (inactive) */ + struct nk_style_item normal; + struct nk_style_item hover; + struct nk_style_item pressed; + + /* background (active) */ + struct nk_style_item normal_active; + struct nk_style_item hover_active; + struct nk_style_item pressed_active; + + /* text color (inactive) */ + struct nk_color text_normal; + struct nk_color text_hover; + struct nk_color text_pressed; + + /* text color (active) */ + struct nk_color text_normal_active; + struct nk_color text_hover_active; + struct nk_color text_pressed_active; + struct nk_color text_background; + nk_flags text_alignment; + + /* properties */ + float rounding; + struct nk_vec2 padding; + struct nk_vec2 touch_padding; + struct nk_vec2 image_padding; + + /* optional user callbacks */ + nk_handle userdata; + void(*draw_begin)(struct nk_command_buffer*, nk_handle); + void(*draw_end)(struct nk_command_buffer*, nk_handle); +}; + +struct nk_style_slider { + /* background */ + struct nk_style_item normal; + struct nk_style_item hover; + struct nk_style_item active; + struct nk_color border_color; + + /* background bar */ + struct nk_color bar_normal; + struct nk_color bar_hover; + struct nk_color bar_active; + struct nk_color bar_filled; + + /* cursor */ + struct nk_style_item cursor_normal; + struct nk_style_item cursor_hover; + struct nk_style_item cursor_active; + + /* properties */ + float border; + float rounding; + float bar_height; + struct nk_vec2 padding; + struct nk_vec2 spacing; + struct nk_vec2 cursor_size; + + /* optional buttons */ + int show_buttons; + struct nk_style_button inc_button; + struct nk_style_button dec_button; + enum nk_symbol_type inc_symbol; + enum nk_symbol_type dec_symbol; + + /* optional user callbacks */ + nk_handle userdata; + void(*draw_begin)(struct nk_command_buffer*, nk_handle); + void(*draw_end)(struct nk_command_buffer*, nk_handle); +}; + +struct nk_style_progress { + /* background */ + struct nk_style_item normal; + struct nk_style_item hover; + struct nk_style_item active; + struct nk_color border_color; + + /* cursor */ + struct nk_style_item cursor_normal; + struct nk_style_item cursor_hover; + struct nk_style_item cursor_active; + struct nk_color cursor_border_color; + + /* properties */ + float rounding; + float border; + float cursor_border; + float cursor_rounding; + struct nk_vec2 padding; + + /* optional user callbacks */ + nk_handle userdata; + void(*draw_begin)(struct nk_command_buffer*, nk_handle); + void(*draw_end)(struct nk_command_buffer*, nk_handle); +}; + +struct nk_style_scrollbar { + /* background */ + struct nk_style_item normal; + struct nk_style_item hover; + struct nk_style_item active; + struct nk_color border_color; + + /* cursor */ + struct nk_style_item cursor_normal; + struct nk_style_item cursor_hover; + struct nk_style_item cursor_active; + struct nk_color cursor_border_color; + + /* properties */ + float border; + float rounding; + float border_cursor; + float rounding_cursor; + struct nk_vec2 padding; + + /* optional buttons */ + int show_buttons; + struct nk_style_button inc_button; + struct nk_style_button dec_button; + enum nk_symbol_type inc_symbol; + enum nk_symbol_type dec_symbol; + + /* optional user callbacks */ + nk_handle userdata; + void(*draw_begin)(struct nk_command_buffer*, nk_handle); + void(*draw_end)(struct nk_command_buffer*, nk_handle); +}; + +struct nk_style_edit { + /* background */ + struct nk_style_item normal; + struct nk_style_item hover; + struct nk_style_item active; + struct nk_color border_color; + struct nk_style_scrollbar scrollbar; + + /* cursor */ + struct nk_color cursor_normal; + struct nk_color cursor_hover; + struct nk_color cursor_text_normal; + struct nk_color cursor_text_hover; + + /* text (unselected) */ + struct nk_color text_normal; + struct nk_color text_hover; + struct nk_color text_active; + + /* text (selected) */ + struct nk_color selected_normal; + struct nk_color selected_hover; + struct nk_color selected_text_normal; + struct nk_color selected_text_hover; + + /* properties */ + float border; + float rounding; + float cursor_size; + struct nk_vec2 scrollbar_size; + struct nk_vec2 padding; + float row_padding; +}; + +struct nk_style_property { + /* background */ + struct nk_style_item normal; + struct nk_style_item hover; + struct nk_style_item active; + struct nk_color border_color; + + /* text */ + struct nk_color label_normal; + struct nk_color label_hover; + struct nk_color label_active; + + /* symbols */ + enum nk_symbol_type sym_left; + enum nk_symbol_type sym_right; + + /* properties */ + float border; + float rounding; + struct nk_vec2 padding; + + struct nk_style_edit edit; + struct nk_style_button inc_button; + struct nk_style_button dec_button; + + /* optional user callbacks */ + nk_handle userdata; + void(*draw_begin)(struct nk_command_buffer*, nk_handle); + void(*draw_end)(struct nk_command_buffer*, nk_handle); +}; + +struct nk_style_chart { + /* colors */ + struct nk_style_item background; + struct nk_color border_color; + struct nk_color selected_color; + struct nk_color color; + + /* properties */ + float border; + float rounding; + struct nk_vec2 padding; +}; + +struct nk_style_combo { + /* background */ + struct nk_style_item normal; + struct nk_style_item hover; + struct nk_style_item active; + struct nk_color border_color; + + /* label */ + struct nk_color label_normal; + struct nk_color label_hover; + struct nk_color label_active; + + /* symbol */ + struct nk_color symbol_normal; + struct nk_color symbol_hover; + struct nk_color symbol_active; + + /* button */ + struct nk_style_button button; + enum nk_symbol_type sym_normal; + enum nk_symbol_type sym_hover; + enum nk_symbol_type sym_active; + + /* properties */ + float border; + float rounding; + struct nk_vec2 content_padding; + struct nk_vec2 button_padding; + struct nk_vec2 spacing; +}; + +struct nk_style_tab { + /* background */ + struct nk_style_item background; + struct nk_color border_color; + struct nk_color text; + + /* button */ + struct nk_style_button tab_maximize_button; + struct nk_style_button tab_minimize_button; + struct nk_style_button node_maximize_button; + struct nk_style_button node_minimize_button; + enum nk_symbol_type sym_minimize; + enum nk_symbol_type sym_maximize; + + /* properties */ + float border; + float rounding; + float indent; + struct nk_vec2 padding; + struct nk_vec2 spacing; +}; + +enum nk_style_header_align { + NK_HEADER_LEFT, + NK_HEADER_RIGHT +}; +struct nk_style_window_header { + /* background */ + struct nk_style_item normal; + struct nk_style_item hover; + struct nk_style_item active; + + /* button */ + struct nk_style_button close_button; + struct nk_style_button minimize_button; + enum nk_symbol_type close_symbol; + enum nk_symbol_type minimize_symbol; + enum nk_symbol_type maximize_symbol; + + /* title */ + struct nk_color label_normal; + struct nk_color label_hover; + struct nk_color label_active; + + /* properties */ + enum nk_style_header_align align; + struct nk_vec2 padding; + struct nk_vec2 label_padding; + struct nk_vec2 spacing; +}; + +struct nk_style_window { + struct nk_style_window_header header; + struct nk_style_item fixed_background; + struct nk_color background; + + struct nk_color border_color; + struct nk_color popup_border_color; + struct nk_color combo_border_color; + struct nk_color contextual_border_color; + struct nk_color menu_border_color; + struct nk_color group_border_color; + struct nk_color tooltip_border_color; + struct nk_style_item scaler; + + float border; + float combo_border; + float contextual_border; + float menu_border; + float group_border; + float tooltip_border; + float popup_border; + float min_row_height_padding; + + float rounding; + struct nk_vec2 spacing; + struct nk_vec2 scrollbar_size; + struct nk_vec2 min_size; + + struct nk_vec2 padding; + struct nk_vec2 group_padding; + struct nk_vec2 popup_padding; + struct nk_vec2 combo_padding; + struct nk_vec2 contextual_padding; + struct nk_vec2 menu_padding; + struct nk_vec2 tooltip_padding; +}; + +struct nk_style { + const struct nk_user_font *font; + const struct nk_cursor *cursors[NK_CURSOR_COUNT]; + const struct nk_cursor *cursor_active; + struct nk_cursor *cursor_last; + int cursor_visible; + + struct nk_style_text text; + struct nk_style_button button; + struct nk_style_button contextual_button; + struct nk_style_button menu_button; + struct nk_style_toggle option; + struct nk_style_toggle checkbox; + struct nk_style_selectable selectable; + struct nk_style_slider slider; + struct nk_style_progress progress; + struct nk_style_property property; + struct nk_style_edit edit; + struct nk_style_chart chart; + struct nk_style_scrollbar scrollh; + struct nk_style_scrollbar scrollv; + struct nk_style_tab tab; + struct nk_style_combo combo; + struct nk_style_window window; +}; + +NK_API struct nk_style_item nk_style_item_image(struct nk_image img); +NK_API struct nk_style_item nk_style_item_color(struct nk_color); +NK_API struct nk_style_item nk_style_item_hide(void); + +/*============================================================== + * PANEL + * =============================================================*/ +#ifndef NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS +#define NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS 16 +#endif +#ifndef NK_CHART_MAX_SLOT +#define NK_CHART_MAX_SLOT 4 +#endif + +enum nk_panel_type { + NK_PANEL_NONE = 0, + NK_PANEL_WINDOW = NK_FLAG(0), + NK_PANEL_GROUP = NK_FLAG(1), + NK_PANEL_POPUP = NK_FLAG(2), + NK_PANEL_CONTEXTUAL = NK_FLAG(4), + NK_PANEL_COMBO = NK_FLAG(5), + NK_PANEL_MENU = NK_FLAG(6), + NK_PANEL_TOOLTIP = NK_FLAG(7) +}; +enum nk_panel_set { + NK_PANEL_SET_NONBLOCK = NK_PANEL_CONTEXTUAL|NK_PANEL_COMBO|NK_PANEL_MENU|NK_PANEL_TOOLTIP, + NK_PANEL_SET_POPUP = NK_PANEL_SET_NONBLOCK|NK_PANEL_POPUP, + NK_PANEL_SET_SUB = NK_PANEL_SET_POPUP|NK_PANEL_GROUP +}; + +struct nk_chart_slot { + enum nk_chart_type type; + struct nk_color color; + struct nk_color highlight; + float min, max, range; + int count; + struct nk_vec2 last; + int index; +}; + +struct nk_chart { + int slot; + float x, y, w, h; + struct nk_chart_slot slots[NK_CHART_MAX_SLOT]; +}; + +enum nk_panel_row_layout_type { + NK_LAYOUT_DYNAMIC_FIXED = 0, + NK_LAYOUT_DYNAMIC_ROW, + NK_LAYOUT_DYNAMIC_FREE, + NK_LAYOUT_DYNAMIC, + NK_LAYOUT_STATIC_FIXED, + NK_LAYOUT_STATIC_ROW, + NK_LAYOUT_STATIC_FREE, + NK_LAYOUT_STATIC, + NK_LAYOUT_TEMPLATE, + NK_LAYOUT_COUNT +}; +struct nk_row_layout { + enum nk_panel_row_layout_type type; + int index; + float height; + float min_height; + int columns; + const float *ratio; + float item_width; + float item_height; + float item_offset; + float filled; + struct nk_rect item; + int tree_depth; + float templates[NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS]; +}; + +struct nk_popup_buffer { + nk_size begin; + nk_size parent; + nk_size last; + nk_size end; + nk_bool active; +}; + +struct nk_menu_state { + float x, y, w, h; + struct nk_scroll offset; +}; + +struct nk_panel { + enum nk_panel_type type; + nk_flags flags; + struct nk_rect bounds; + nk_uint *offset_x; + nk_uint *offset_y; + float at_x, at_y, max_x; + float footer_height; + float header_height; + float border; + unsigned int has_scrolling; + struct nk_rect clip; + struct nk_menu_state menu; + struct nk_row_layout row; + struct nk_chart chart; + struct nk_command_buffer *buffer; + struct nk_panel *parent; +}; + +/*============================================================== + * WINDOW + * =============================================================*/ +#ifndef NK_WINDOW_MAX_NAME +#define NK_WINDOW_MAX_NAME 64 +#endif + +struct nk_table; +enum nk_window_flags { + NK_WINDOW_PRIVATE = NK_FLAG(11), + NK_WINDOW_DYNAMIC = NK_WINDOW_PRIVATE, + /* special window type growing up in height while being filled to a certain maximum height */ + NK_WINDOW_ROM = NK_FLAG(12), + /* sets window widgets into a read only mode and does not allow input changes */ + NK_WINDOW_NOT_INTERACTIVE = NK_WINDOW_ROM|NK_WINDOW_NO_INPUT, + /* prevents all interaction caused by input to either window or widgets inside */ + NK_WINDOW_HIDDEN = NK_FLAG(13), + /* Hides window and stops any window interaction and drawing */ + NK_WINDOW_CLOSED = NK_FLAG(14), + /* Directly closes and frees the window at the end of the frame */ + NK_WINDOW_MINIMIZED = NK_FLAG(15), + /* marks the window as minimized */ + NK_WINDOW_REMOVE_ROM = NK_FLAG(16) + /* Removes read only mode at the end of the window */ +}; + +struct nk_popup_state { + struct nk_window *win; + enum nk_panel_type type; + struct nk_popup_buffer buf; + nk_hash name; + nk_bool active; + unsigned combo_count; + unsigned con_count, con_old; + unsigned active_con; + struct nk_rect header; +}; + +struct nk_edit_state { + nk_hash name; + unsigned int seq; + unsigned int old; + int active, prev; + int cursor; + int sel_start; + int sel_end; + struct nk_scroll scrollbar; + unsigned char mode; + unsigned char single_line; +}; + +struct nk_property_state { + int active, prev; + char buffer[NK_MAX_NUMBER_BUFFER]; + int length; + int cursor; + int select_start; + int select_end; + nk_hash name; + unsigned int seq; + unsigned int old; + int state; +}; + +struct nk_window { + unsigned int seq; + nk_hash name; + char name_string[NK_WINDOW_MAX_NAME]; + nk_flags flags; + + struct nk_rect bounds; + struct nk_scroll scrollbar; + struct nk_command_buffer buffer; + struct nk_panel *layout; + float scrollbar_hiding_timer; + + /* persistent widget state */ + struct nk_property_state property; + struct nk_popup_state popup; + struct nk_edit_state edit; + unsigned int scrolled; + + struct nk_table *tables; + unsigned int table_count; + + /* window list hooks */ + struct nk_window *next; + struct nk_window *prev; + struct nk_window *parent; +}; + +/*============================================================== + * STACK + * =============================================================*/ +/* The style modifier stack can be used to temporarily change a + * property inside `nk_style`. For example if you want a special + * red button you can temporarily push the old button color onto a stack + * draw the button with a red color and then you just pop the old color + * back from the stack: + * + * nk_style_push_style_item(ctx, &ctx->style.button.normal, nk_style_item_color(nk_rgb(255,0,0))); + * nk_style_push_style_item(ctx, &ctx->style.button.hover, nk_style_item_color(nk_rgb(255,0,0))); + * nk_style_push_style_item(ctx, &ctx->style.button.active, nk_style_item_color(nk_rgb(255,0,0))); + * nk_style_push_vec2(ctx, &cx->style.button.padding, nk_vec2(2,2)); + * + * nk_button(...); + * + * nk_style_pop_style_item(ctx); + * nk_style_pop_style_item(ctx); + * nk_style_pop_style_item(ctx); + * nk_style_pop_vec2(ctx); + * + * Nuklear has a stack for style_items, float properties, vector properties, + * flags, colors, fonts and for button_behavior. Each has it's own fixed size stack + * which can be changed at compile time. + */ +#ifndef NK_BUTTON_BEHAVIOR_STACK_SIZE +#define NK_BUTTON_BEHAVIOR_STACK_SIZE 8 +#endif + +#ifndef NK_FONT_STACK_SIZE +#define NK_FONT_STACK_SIZE 8 +#endif + +#ifndef NK_STYLE_ITEM_STACK_SIZE +#define NK_STYLE_ITEM_STACK_SIZE 16 +#endif + +#ifndef NK_FLOAT_STACK_SIZE +#define NK_FLOAT_STACK_SIZE 32 +#endif + +#ifndef NK_VECTOR_STACK_SIZE +#define NK_VECTOR_STACK_SIZE 16 +#endif + +#ifndef NK_FLAGS_STACK_SIZE +#define NK_FLAGS_STACK_SIZE 32 +#endif + +#ifndef NK_COLOR_STACK_SIZE +#define NK_COLOR_STACK_SIZE 32 +#endif + +#define NK_CONFIGURATION_STACK_TYPE(prefix, name, type)\ + struct nk_config_stack_##name##_element {\ + prefix##_##type *address;\ + prefix##_##type old_value;\ + } +#define NK_CONFIG_STACK(type,size)\ + struct nk_config_stack_##type {\ + int head;\ + struct nk_config_stack_##type##_element elements[size];\ + } + +#define nk_float float +NK_CONFIGURATION_STACK_TYPE(struct nk, style_item, style_item); +NK_CONFIGURATION_STACK_TYPE(nk ,float, float); +NK_CONFIGURATION_STACK_TYPE(struct nk, vec2, vec2); +NK_CONFIGURATION_STACK_TYPE(nk ,flags, flags); +NK_CONFIGURATION_STACK_TYPE(struct nk, color, color); +NK_CONFIGURATION_STACK_TYPE(const struct nk, user_font, user_font*); +NK_CONFIGURATION_STACK_TYPE(enum nk, button_behavior, button_behavior); + +NK_CONFIG_STACK(style_item, NK_STYLE_ITEM_STACK_SIZE); +NK_CONFIG_STACK(float, NK_FLOAT_STACK_SIZE); +NK_CONFIG_STACK(vec2, NK_VECTOR_STACK_SIZE); +NK_CONFIG_STACK(flags, NK_FLAGS_STACK_SIZE); +NK_CONFIG_STACK(color, NK_COLOR_STACK_SIZE); +NK_CONFIG_STACK(user_font, NK_FONT_STACK_SIZE); +NK_CONFIG_STACK(button_behavior, NK_BUTTON_BEHAVIOR_STACK_SIZE); + +struct nk_configuration_stacks { + struct nk_config_stack_style_item style_items; + struct nk_config_stack_float floats; + struct nk_config_stack_vec2 vectors; + struct nk_config_stack_flags flags; + struct nk_config_stack_color colors; + struct nk_config_stack_user_font fonts; + struct nk_config_stack_button_behavior button_behaviors; +}; + +/*============================================================== + * CONTEXT + * =============================================================*/ +#define NK_VALUE_PAGE_CAPACITY \ + (((NK_MAX(sizeof(struct nk_window),sizeof(struct nk_panel)) / sizeof(nk_uint))) / 2) + +struct nk_table { + unsigned int seq; + unsigned int size; + nk_hash keys[NK_VALUE_PAGE_CAPACITY]; + nk_uint values[NK_VALUE_PAGE_CAPACITY]; + struct nk_table *next, *prev; +}; + +union nk_page_data { + struct nk_table tbl; + struct nk_panel pan; + struct nk_window win; +}; + +struct nk_page_element { + union nk_page_data data; + struct nk_page_element *next; + struct nk_page_element *prev; +}; + +struct nk_page { + unsigned int size; + struct nk_page *next; + struct nk_page_element win[1]; +}; + +struct nk_pool { + struct nk_allocator alloc; + enum nk_allocation_type type; + unsigned int page_count; + struct nk_page *pages; + struct nk_page_element *freelist; + unsigned capacity; + nk_size size; + nk_size cap; +}; + +struct nk_context { +/* public: can be accessed freely */ + struct nk_input input; + struct nk_style style; + struct nk_buffer memory; + struct nk_clipboard clip; + nk_flags last_widget_state; + enum nk_button_behavior button_behavior; + struct nk_configuration_stacks stacks; + float delta_time_seconds; + +/* private: + should only be accessed if you + know what you are doing */ +#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT + struct nk_draw_list draw_list; +#endif +#ifdef NK_INCLUDE_COMMAND_USERDATA + nk_handle userdata; +#endif + /* text editor objects are quite big because of an internal + * undo/redo stack. Therefore it does not make sense to have one for + * each window for temporary use cases, so I only provide *one* instance + * for all windows. This works because the content is cleared anyway */ + struct nk_text_edit text_edit; + /* draw buffer used for overlay drawing operation like cursor */ + struct nk_command_buffer overlay; + + /* windows */ + int build; + int use_pool; + struct nk_pool pool; + struct nk_window *begin; + struct nk_window *end; + struct nk_window *active; + struct nk_window *current; + struct nk_page_element *freelist; + unsigned int count; + unsigned int seq; +}; + +/* ============================================================== + * MATH + * =============================================================== */ +#define NK_PI 3.141592654f +#define NK_UTF_INVALID 0xFFFD +#define NK_MAX_FLOAT_PRECISION 2 + +#define NK_UNUSED(x) ((void)(x)) +#define NK_SATURATE(x) (NK_MAX(0, NK_MIN(1.0f, x))) +#define NK_LEN(a) (sizeof(a)/sizeof(a)[0]) +#define NK_ABS(a) (((a) < 0) ? -(a) : (a)) +#define NK_BETWEEN(x, a, b) ((a) <= (x) && (x) < (b)) +#define NK_INBOX(px, py, x, y, w, h)\ + (NK_BETWEEN(px,x,x+w) && NK_BETWEEN(py,y,y+h)) +#define NK_INTERSECT(x0, y0, w0, h0, x1, y1, w1, h1) \ + ((x1 < (x0 + w0)) && (x0 < (x1 + w1)) && \ + (y1 < (y0 + h0)) && (y0 < (y1 + h1))) +#define NK_CONTAINS(x, y, w, h, bx, by, bw, bh)\ + (NK_INBOX(x,y, bx, by, bw, bh) && NK_INBOX(x+w,y+h, bx, by, bw, bh)) + +#define nk_vec2_sub(a, b) nk_vec2((a).x - (b).x, (a).y - (b).y) +#define nk_vec2_add(a, b) nk_vec2((a).x + (b).x, (a).y + (b).y) +#define nk_vec2_len_sqr(a) ((a).x*(a).x+(a).y*(a).y) +#define nk_vec2_muls(a, t) nk_vec2((a).x * (t), (a).y * (t)) + +#define nk_ptr_add(t, p, i) ((t*)((void*)((nk_byte*)(p) + (i)))) +#define nk_ptr_add_const(t, p, i) ((const t*)((const void*)((const nk_byte*)(p) + (i)))) +#define nk_zero_struct(s) nk_zero(&s, sizeof(s)) + +/* ============================================================== + * ALIGNMENT + * =============================================================== */ +/* Pointer to Integer type conversion for pointer alignment */ +#if defined(__PTRDIFF_TYPE__) /* This case should work for GCC*/ +# define NK_UINT_TO_PTR(x) ((void*)(__PTRDIFF_TYPE__)(x)) +# define NK_PTR_TO_UINT(x) ((nk_size)(__PTRDIFF_TYPE__)(x)) +#elif !defined(__GNUC__) /* works for compilers other than LLVM */ +# define NK_UINT_TO_PTR(x) ((void*)&((char*)0)[x]) +# define NK_PTR_TO_UINT(x) ((nk_size)(((char*)x)-(char*)0)) +#elif defined(NK_USE_FIXED_TYPES) /* used if we have */ +# define NK_UINT_TO_PTR(x) ((void*)(uintptr_t)(x)) +# define NK_PTR_TO_UINT(x) ((uintptr_t)(x)) +#else /* generates warning but works */ +# define NK_UINT_TO_PTR(x) ((void*)(x)) +# define NK_PTR_TO_UINT(x) ((nk_size)(x)) +#endif + +#define NK_ALIGN_PTR(x, mask)\ + (NK_UINT_TO_PTR((NK_PTR_TO_UINT((nk_byte*)(x) + (mask-1)) & ~(mask-1)))) +#define NK_ALIGN_PTR_BACK(x, mask)\ + (NK_UINT_TO_PTR((NK_PTR_TO_UINT((nk_byte*)(x)) & ~(mask-1)))) + +#define NK_OFFSETOF(st,m) ((nk_ptr)&(((st*)0)->m)) +#define NK_CONTAINER_OF(ptr,type,member)\ + (type*)((void*)((char*)(1 ? (ptr): &((type*)0)->member) - NK_OFFSETOF(type, member))) + +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus +template struct nk_alignof; +template struct nk_helper{enum {value = size_diff};}; +template struct nk_helper{enum {value = nk_alignof::value};}; +template struct nk_alignof{struct Big {T x; char c;}; enum { + diff = sizeof(Big) - sizeof(T), value = nk_helper::value};}; +#define NK_ALIGNOF(t) (nk_alignof::value) +#elif defined(_MSC_VER) +#define NK_ALIGNOF(t) (__alignof(t)) +#else +#define NK_ALIGNOF(t) ((char*)(&((struct {char c; t _h;}*)0)->_h) - (char*)0) +#endif + +#endif /* NK_NUKLEAR_H_ */ + +#ifdef NK_IMPLEMENTATION + +#ifndef NK_INTERNAL_H +#define NK_INTERNAL_H + +#ifndef NK_POOL_DEFAULT_CAPACITY +#define NK_POOL_DEFAULT_CAPACITY 16 +#endif + +#ifndef NK_DEFAULT_COMMAND_BUFFER_SIZE +#define NK_DEFAULT_COMMAND_BUFFER_SIZE (4*1024) +#endif + +#ifndef NK_BUFFER_DEFAULT_INITIAL_SIZE +#define NK_BUFFER_DEFAULT_INITIAL_SIZE (4*1024) +#endif + +/* standard library headers */ +#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR +#include /* malloc, free */ +#endif +#ifdef NK_INCLUDE_STANDARD_IO +#include /* fopen, fclose,... */ +#endif +#ifdef NK_INCLUDE_STANDARD_VARARGS +#include /* valist, va_start, va_end, ... */ +#endif +#ifndef NK_ASSERT +#include +#define NK_ASSERT(expr) assert(expr) +#endif + +#define NK_DEFAULT (-1) + +#ifndef NK_VSNPRINTF +/* If your compiler does support `vsnprintf` I would highly recommend + * defining this to vsnprintf instead since `vsprintf` is basically + * unbelievable unsafe and should *NEVER* be used. But I have to support + * it since C89 only provides this unsafe version. */ + #if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) ||\ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + (defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L)) ||\ + (defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500)) ||\ + defined(_ISOC99_SOURCE) || defined(_BSD_SOURCE) + #define NK_VSNPRINTF(s,n,f,a) vsnprintf(s,n,f,a) + #else + #define NK_VSNPRINTF(s,n,f,a) vsprintf(s,f,a) + #endif +#endif + +#define NK_SCHAR_MIN (-127) +#define NK_SCHAR_MAX 127 +#define NK_UCHAR_MIN 0 +#define NK_UCHAR_MAX 256 +#define NK_SSHORT_MIN (-32767) +#define NK_SSHORT_MAX 32767 +#define NK_USHORT_MIN 0 +#define NK_USHORT_MAX 65535 +#define NK_SINT_MIN (-2147483647) +#define NK_SINT_MAX 2147483647 +#define NK_UINT_MIN 0 +#define NK_UINT_MAX 4294967295u + +/* Make sure correct type size: + * This will fire with a negative subscript error if the type sizes + * are set incorrectly by the compiler, and compile out if not */ +NK_STATIC_ASSERT(sizeof(nk_size) >= sizeof(void*)); +NK_STATIC_ASSERT(sizeof(nk_ptr) == sizeof(void*)); +NK_STATIC_ASSERT(sizeof(nk_flags) >= 4); +NK_STATIC_ASSERT(sizeof(nk_rune) >= 4); +NK_STATIC_ASSERT(sizeof(nk_ushort) == 2); +NK_STATIC_ASSERT(sizeof(nk_short) == 2); +NK_STATIC_ASSERT(sizeof(nk_uint) == 4); +NK_STATIC_ASSERT(sizeof(nk_int) == 4); +NK_STATIC_ASSERT(sizeof(nk_byte) == 1); +#ifdef NK_INCLUDE_STANDARD_BOOL +NK_STATIC_ASSERT(sizeof(nk_bool) == sizeof(bool)); +#else +NK_STATIC_ASSERT(sizeof(nk_bool) == 4); +#endif + +NK_GLOBAL const struct nk_rect nk_null_rect = {-8192.0f, -8192.0f, 16384, 16384}; +#define NK_FLOAT_PRECISION 0.00000000000001 + +NK_GLOBAL const struct nk_color nk_red = {255,0,0,255}; +NK_GLOBAL const struct nk_color nk_green = {0,255,0,255}; +NK_GLOBAL const struct nk_color nk_blue = {0,0,255,255}; +NK_GLOBAL const struct nk_color nk_white = {255,255,255,255}; +NK_GLOBAL const struct nk_color nk_black = {0,0,0,255}; +NK_GLOBAL const struct nk_color nk_yellow = {255,255,0,255}; + +/* widget */ +#define nk_widget_state_reset(s)\ + if ((*(s)) & NK_WIDGET_STATE_MODIFIED)\ + (*(s)) = NK_WIDGET_STATE_INACTIVE|NK_WIDGET_STATE_MODIFIED;\ + else (*(s)) = NK_WIDGET_STATE_INACTIVE; + +/* math */ +NK_LIB float nk_inv_sqrt(float n); +#ifndef NK_SIN +NK_LIB float nk_sin(float x); +#endif +#ifndef NK_COS +NK_LIB float nk_cos(float x); +#endif +NK_LIB nk_uint nk_round_up_pow2(nk_uint v); +NK_LIB struct nk_rect nk_shrink_rect(struct nk_rect r, float amount); +NK_LIB struct nk_rect nk_pad_rect(struct nk_rect r, struct nk_vec2 pad); +NK_LIB void nk_unify(struct nk_rect *clip, const struct nk_rect *a, float x0, float y0, float x1, float y1); +NK_LIB double nk_pow(double x, int n); +NK_LIB int nk_ifloord(double x); +NK_LIB int nk_ifloorf(float x); +NK_LIB int nk_iceilf(float x); +NK_LIB int nk_log10(double n); + +/* util */ +enum {NK_DO_NOT_STOP_ON_NEW_LINE, NK_STOP_ON_NEW_LINE}; +NK_LIB nk_bool nk_is_lower(int c); +NK_LIB nk_bool nk_is_upper(int c); +NK_LIB int nk_to_upper(int c); +NK_LIB int nk_to_lower(int c); + +#ifndef NK_MEMCPY +NK_LIB void* nk_memcopy(void *dst, const void *src, nk_size n); +#endif +#ifndef NK_MEMSET +NK_LIB void nk_memset(void *ptr, int c0, nk_size size); +#endif +NK_LIB void nk_zero(void *ptr, nk_size size); +NK_LIB char *nk_itoa(char *s, long n); +NK_LIB int nk_string_float_limit(char *string, int prec); +#ifndef NK_DTOA +NK_LIB char *nk_dtoa(char *s, double n); +#endif +NK_LIB int nk_text_clamp(const struct nk_user_font *font, const char *text, int text_len, float space, int *glyphs, float *text_width, nk_rune *sep_list, int sep_count); +NK_LIB struct nk_vec2 nk_text_calculate_text_bounds(const struct nk_user_font *font, const char *begin, int byte_len, float row_height, const char **remaining, struct nk_vec2 *out_offset, int *glyphs, int op); +#ifdef NK_INCLUDE_STANDARD_VARARGS +NK_LIB int nk_strfmt(char *buf, int buf_size, const char *fmt, va_list args); +#endif +#ifdef NK_INCLUDE_STANDARD_IO +NK_LIB char *nk_file_load(const char* path, nk_size* siz, struct nk_allocator *alloc); +#endif + +/* buffer */ +#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR +NK_LIB void* nk_malloc(nk_handle unused, void *old,nk_size size); +NK_LIB void nk_mfree(nk_handle unused, void *ptr); +#endif +NK_LIB void* nk_buffer_align(void *unaligned, nk_size align, nk_size *alignment, enum nk_buffer_allocation_type type); +NK_LIB void* nk_buffer_alloc(struct nk_buffer *b, enum nk_buffer_allocation_type type, nk_size size, nk_size align); +NK_LIB void* nk_buffer_realloc(struct nk_buffer *b, nk_size capacity, nk_size *size); + +/* draw */ +NK_LIB void nk_command_buffer_init(struct nk_command_buffer *cb, struct nk_buffer *b, enum nk_command_clipping clip); +NK_LIB void nk_command_buffer_reset(struct nk_command_buffer *b); +NK_LIB void* nk_command_buffer_push(struct nk_command_buffer* b, enum nk_command_type t, nk_size size); +NK_LIB void nk_draw_symbol(struct nk_command_buffer *out, enum nk_symbol_type type, struct nk_rect content, struct nk_color background, struct nk_color foreground, float border_width, const struct nk_user_font *font); + +/* buffering */ +NK_LIB void nk_start_buffer(struct nk_context *ctx, struct nk_command_buffer *b); +NK_LIB void nk_start(struct nk_context *ctx, struct nk_window *win); +NK_LIB void nk_start_popup(struct nk_context *ctx, struct nk_window *win); +NK_LIB void nk_finish_popup(struct nk_context *ctx, struct nk_window*); +NK_LIB void nk_finish_buffer(struct nk_context *ctx, struct nk_command_buffer *b); +NK_LIB void nk_finish(struct nk_context *ctx, struct nk_window *w); +NK_LIB void nk_build(struct nk_context *ctx); + +/* text editor */ +NK_LIB void nk_textedit_clear_state(struct nk_text_edit *state, enum nk_text_edit_type type, nk_plugin_filter filter); +NK_LIB void nk_textedit_click(struct nk_text_edit *state, float x, float y, const struct nk_user_font *font, float row_height); +NK_LIB void nk_textedit_drag(struct nk_text_edit *state, float x, float y, const struct nk_user_font *font, float row_height); +NK_LIB void nk_textedit_key(struct nk_text_edit *state, enum nk_keys key, int shift_mod, const struct nk_user_font *font, float row_height); + +/* window */ +enum nk_window_insert_location { + NK_INSERT_BACK, /* inserts window into the back of list (front of screen) */ + NK_INSERT_FRONT /* inserts window into the front of list (back of screen) */ +}; +NK_LIB void *nk_create_window(struct nk_context *ctx); +NK_LIB void nk_remove_window(struct nk_context*, struct nk_window*); +NK_LIB void nk_free_window(struct nk_context *ctx, struct nk_window *win); +NK_LIB struct nk_window *nk_find_window(struct nk_context *ctx, nk_hash hash, const char *name); +NK_LIB void nk_insert_window(struct nk_context *ctx, struct nk_window *win, enum nk_window_insert_location loc); + +/* pool */ +NK_LIB void nk_pool_init(struct nk_pool *pool, struct nk_allocator *alloc, unsigned int capacity); +NK_LIB void nk_pool_free(struct nk_pool *pool); +NK_LIB void nk_pool_init_fixed(struct nk_pool *pool, void *memory, nk_size size); +NK_LIB struct nk_page_element *nk_pool_alloc(struct nk_pool *pool); + +/* page-element */ +NK_LIB struct nk_page_element* nk_create_page_element(struct nk_context *ctx); +NK_LIB void nk_link_page_element_into_freelist(struct nk_context *ctx, struct nk_page_element *elem); +NK_LIB void nk_free_page_element(struct nk_context *ctx, struct nk_page_element *elem); + +/* table */ +NK_LIB struct nk_table* nk_create_table(struct nk_context *ctx); +NK_LIB void nk_remove_table(struct nk_window *win, struct nk_table *tbl); +NK_LIB void nk_free_table(struct nk_context *ctx, struct nk_table *tbl); +NK_LIB void nk_push_table(struct nk_window *win, struct nk_table *tbl); +NK_LIB nk_uint *nk_add_value(struct nk_context *ctx, struct nk_window *win, nk_hash name, nk_uint value); +NK_LIB nk_uint *nk_find_value(struct nk_window *win, nk_hash name); + +/* panel */ +NK_LIB void *nk_create_panel(struct nk_context *ctx); +NK_LIB void nk_free_panel(struct nk_context*, struct nk_panel *pan); +NK_LIB nk_bool nk_panel_has_header(nk_flags flags, const char *title); +NK_LIB struct nk_vec2 nk_panel_get_padding(const struct nk_style *style, enum nk_panel_type type); +NK_LIB float nk_panel_get_border(const struct nk_style *style, nk_flags flags, enum nk_panel_type type); +NK_LIB struct nk_color nk_panel_get_border_color(const struct nk_style *style, enum nk_panel_type type); +NK_LIB nk_bool nk_panel_is_sub(enum nk_panel_type type); +NK_LIB nk_bool nk_panel_is_nonblock(enum nk_panel_type type); +NK_LIB nk_bool nk_panel_begin(struct nk_context *ctx, const char *title, enum nk_panel_type panel_type); +NK_LIB void nk_panel_end(struct nk_context *ctx); + +/* layout */ +NK_LIB float nk_layout_row_calculate_usable_space(const struct nk_style *style, enum nk_panel_type type, float total_space, int columns); +NK_LIB void nk_panel_layout(const struct nk_context *ctx, struct nk_window *win, float height, int cols); +NK_LIB void nk_row_layout(struct nk_context *ctx, enum nk_layout_format fmt, float height, int cols, int width); +NK_LIB void nk_panel_alloc_row(const struct nk_context *ctx, struct nk_window *win); +NK_LIB void nk_layout_widget_space(struct nk_rect *bounds, const struct nk_context *ctx, struct nk_window *win, int modify); +NK_LIB void nk_panel_alloc_space(struct nk_rect *bounds, const struct nk_context *ctx); +NK_LIB void nk_layout_peek(struct nk_rect *bounds, struct nk_context *ctx); + +/* popup */ +NK_LIB nk_bool nk_nonblock_begin(struct nk_context *ctx, nk_flags flags, struct nk_rect body, struct nk_rect header, enum nk_panel_type panel_type); + +/* text */ +struct nk_text { + struct nk_vec2 padding; + struct nk_color background; + struct nk_color text; +}; +NK_LIB void nk_widget_text(struct nk_command_buffer *o, struct nk_rect b, const char *string, int len, const struct nk_text *t, nk_flags a, const struct nk_user_font *f); +NK_LIB void nk_widget_text_wrap(struct nk_command_buffer *o, struct nk_rect b, const char *string, int len, const struct nk_text *t, const struct nk_user_font *f); + +/* button */ +NK_LIB nk_bool nk_button_behavior(nk_flags *state, struct nk_rect r, const struct nk_input *i, enum nk_button_behavior behavior); +NK_LIB const struct nk_style_item* nk_draw_button(struct nk_command_buffer *out, const struct nk_rect *bounds, nk_flags state, const struct nk_style_button *style); +NK_LIB nk_bool nk_do_button(nk_flags *state, struct nk_command_buffer *out, struct nk_rect r, const struct nk_style_button *style, const struct nk_input *in, enum nk_button_behavior behavior, struct nk_rect *content); +NK_LIB void nk_draw_button_text(struct nk_command_buffer *out, const struct nk_rect *bounds, const struct nk_rect *content, nk_flags state, const struct nk_style_button *style, const char *txt, int len, nk_flags text_alignment, const struct nk_user_font *font); +NK_LIB nk_bool nk_do_button_text(nk_flags *state, struct nk_command_buffer *out, struct nk_rect bounds, const char *string, int len, nk_flags align, enum nk_button_behavior behavior, const struct nk_style_button *style, const struct nk_input *in, const struct nk_user_font *font); +NK_LIB void nk_draw_button_symbol(struct nk_command_buffer *out, const struct nk_rect *bounds, const struct nk_rect *content, nk_flags state, const struct nk_style_button *style, enum nk_symbol_type type, const struct nk_user_font *font); +NK_LIB nk_bool nk_do_button_symbol(nk_flags *state, struct nk_command_buffer *out, struct nk_rect bounds, enum nk_symbol_type symbol, enum nk_button_behavior behavior, const struct nk_style_button *style, const struct nk_input *in, const struct nk_user_font *font); +NK_LIB void nk_draw_button_image(struct nk_command_buffer *out, const struct nk_rect *bounds, const struct nk_rect *content, nk_flags state, const struct nk_style_button *style, const struct nk_image *img); +NK_LIB nk_bool nk_do_button_image(nk_flags *state, struct nk_command_buffer *out, struct nk_rect bounds, struct nk_image img, enum nk_button_behavior b, const struct nk_style_button *style, const struct nk_input *in); +NK_LIB void nk_draw_button_text_symbol(struct nk_command_buffer *out, const struct nk_rect *bounds, const struct nk_rect *label, const struct nk_rect *symbol, nk_flags state, const struct nk_style_button *style, const char *str, int len, enum nk_symbol_type type, const struct nk_user_font *font); +NK_LIB nk_bool nk_do_button_text_symbol(nk_flags *state, struct nk_command_buffer *out, struct nk_rect bounds, enum nk_symbol_type symbol, const char *str, int len, nk_flags align, enum nk_button_behavior behavior, const struct nk_style_button *style, const struct nk_user_font *font, const struct nk_input *in); +NK_LIB void nk_draw_button_text_image(struct nk_command_buffer *out, const struct nk_rect *bounds, const struct nk_rect *label, const struct nk_rect *image, nk_flags state, const struct nk_style_button *style, const char *str, int len, const struct nk_user_font *font, const struct nk_image *img); +NK_LIB nk_bool nk_do_button_text_image(nk_flags *state, struct nk_command_buffer *out, struct nk_rect bounds, struct nk_image img, const char* str, int len, nk_flags align, enum nk_button_behavior behavior, const struct nk_style_button *style, const struct nk_user_font *font, const struct nk_input *in); + +/* toggle */ +enum nk_toggle_type { + NK_TOGGLE_CHECK, + NK_TOGGLE_OPTION +}; +NK_LIB nk_bool nk_toggle_behavior(const struct nk_input *in, struct nk_rect select, nk_flags *state, nk_bool active); +NK_LIB void nk_draw_checkbox(struct nk_command_buffer *out, nk_flags state, const struct nk_style_toggle *style, nk_bool active, const struct nk_rect *label, const struct nk_rect *selector, const struct nk_rect *cursors, const char *string, int len, const struct nk_user_font *font); +NK_LIB void nk_draw_option(struct nk_command_buffer *out, nk_flags state, const struct nk_style_toggle *style, nk_bool active, const struct nk_rect *label, const struct nk_rect *selector, const struct nk_rect *cursors, const char *string, int len, const struct nk_user_font *font); +NK_LIB nk_bool nk_do_toggle(nk_flags *state, struct nk_command_buffer *out, struct nk_rect r, nk_bool *active, const char *str, int len, enum nk_toggle_type type, const struct nk_style_toggle *style, const struct nk_input *in, const struct nk_user_font *font); + +/* progress */ +NK_LIB nk_size nk_progress_behavior(nk_flags *state, struct nk_input *in, struct nk_rect r, struct nk_rect cursor, nk_size max, nk_size value, nk_bool modifiable); +NK_LIB void nk_draw_progress(struct nk_command_buffer *out, nk_flags state, const struct nk_style_progress *style, const struct nk_rect *bounds, const struct nk_rect *scursor, nk_size value, nk_size max); +NK_LIB nk_size nk_do_progress(nk_flags *state, struct nk_command_buffer *out, struct nk_rect bounds, nk_size value, nk_size max, nk_bool modifiable, const struct nk_style_progress *style, struct nk_input *in); + +/* slider */ +NK_LIB float nk_slider_behavior(nk_flags *state, struct nk_rect *logical_cursor, struct nk_rect *visual_cursor, struct nk_input *in, struct nk_rect bounds, float slider_min, float slider_max, float slider_value, float slider_step, float slider_steps); +NK_LIB void nk_draw_slider(struct nk_command_buffer *out, nk_flags state, const struct nk_style_slider *style, const struct nk_rect *bounds, const struct nk_rect *visual_cursor, float min, float value, float max); +NK_LIB float nk_do_slider(nk_flags *state, struct nk_command_buffer *out, struct nk_rect bounds, float min, float val, float max, float step, const struct nk_style_slider *style, struct nk_input *in, const struct nk_user_font *font); + +/* scrollbar */ +NK_LIB float nk_scrollbar_behavior(nk_flags *state, struct nk_input *in, int has_scrolling, const struct nk_rect *scroll, const struct nk_rect *cursor, const struct nk_rect *empty0, const struct nk_rect *empty1, float scroll_offset, float target, float scroll_step, enum nk_orientation o); +NK_LIB void nk_draw_scrollbar(struct nk_command_buffer *out, nk_flags state, const struct nk_style_scrollbar *style, const struct nk_rect *bounds, const struct nk_rect *scroll); +NK_LIB float nk_do_scrollbarv(nk_flags *state, struct nk_command_buffer *out, struct nk_rect scroll, int has_scrolling, float offset, float target, float step, float button_pixel_inc, const struct nk_style_scrollbar *style, struct nk_input *in, const struct nk_user_font *font); +NK_LIB float nk_do_scrollbarh(nk_flags *state, struct nk_command_buffer *out, struct nk_rect scroll, int has_scrolling, float offset, float target, float step, float button_pixel_inc, const struct nk_style_scrollbar *style, struct nk_input *in, const struct nk_user_font *font); + +/* selectable */ +NK_LIB void nk_draw_selectable(struct nk_command_buffer *out, nk_flags state, const struct nk_style_selectable *style, nk_bool active, const struct nk_rect *bounds, const struct nk_rect *icon, const struct nk_image *img, enum nk_symbol_type sym, const char *string, int len, nk_flags align, const struct nk_user_font *font); +NK_LIB nk_bool nk_do_selectable(nk_flags *state, struct nk_command_buffer *out, struct nk_rect bounds, const char *str, int len, nk_flags align, nk_bool *value, const struct nk_style_selectable *style, const struct nk_input *in, const struct nk_user_font *font); +NK_LIB nk_bool nk_do_selectable_image(nk_flags *state, struct nk_command_buffer *out, struct nk_rect bounds, const char *str, int len, nk_flags align, nk_bool *value, const struct nk_image *img, const struct nk_style_selectable *style, const struct nk_input *in, const struct nk_user_font *font); + +/* edit */ +NK_LIB void nk_edit_draw_text(struct nk_command_buffer *out, const struct nk_style_edit *style, float pos_x, float pos_y, float x_offset, const char *text, int byte_len, float row_height, const struct nk_user_font *font, struct nk_color background, struct nk_color foreground, nk_bool is_selected); +NK_LIB nk_flags nk_do_edit(nk_flags *state, struct nk_command_buffer *out, struct nk_rect bounds, nk_flags flags, nk_plugin_filter filter, struct nk_text_edit *edit, const struct nk_style_edit *style, struct nk_input *in, const struct nk_user_font *font); + +/* color-picker */ +NK_LIB nk_bool nk_color_picker_behavior(nk_flags *state, const struct nk_rect *bounds, const struct nk_rect *matrix, const struct nk_rect *hue_bar, const struct nk_rect *alpha_bar, struct nk_colorf *color, const struct nk_input *in); +NK_LIB void nk_draw_color_picker(struct nk_command_buffer *o, const struct nk_rect *matrix, const struct nk_rect *hue_bar, const struct nk_rect *alpha_bar, struct nk_colorf col); +NK_LIB nk_bool nk_do_color_picker(nk_flags *state, struct nk_command_buffer *out, struct nk_colorf *col, enum nk_color_format fmt, struct nk_rect bounds, struct nk_vec2 padding, const struct nk_input *in, const struct nk_user_font *font); + +/* property */ +enum nk_property_status { + NK_PROPERTY_DEFAULT, + NK_PROPERTY_EDIT, + NK_PROPERTY_DRAG +}; +enum nk_property_filter { + NK_FILTER_INT, + NK_FILTER_FLOAT +}; +enum nk_property_kind { + NK_PROPERTY_INT, + NK_PROPERTY_FLOAT, + NK_PROPERTY_DOUBLE +}; +union nk_property { + int i; + float f; + double d; +}; +struct nk_property_variant { + enum nk_property_kind kind; + union nk_property value; + union nk_property min_value; + union nk_property max_value; + union nk_property step; +}; +NK_LIB struct nk_property_variant nk_property_variant_int(int value, int min_value, int max_value, int step); +NK_LIB struct nk_property_variant nk_property_variant_float(float value, float min_value, float max_value, float step); +NK_LIB struct nk_property_variant nk_property_variant_double(double value, double min_value, double max_value, double step); + +NK_LIB void nk_drag_behavior(nk_flags *state, const struct nk_input *in, struct nk_rect drag, struct nk_property_variant *variant, float inc_per_pixel); +NK_LIB void nk_property_behavior(nk_flags *ws, const struct nk_input *in, struct nk_rect property, struct nk_rect label, struct nk_rect edit, struct nk_rect empty, int *state, struct nk_property_variant *variant, float inc_per_pixel); +NK_LIB void nk_draw_property(struct nk_command_buffer *out, const struct nk_style_property *style, const struct nk_rect *bounds, const struct nk_rect *label, nk_flags state, const char *name, int len, const struct nk_user_font *font); +NK_LIB void nk_do_property(nk_flags *ws, struct nk_command_buffer *out, struct nk_rect property, const char *name, struct nk_property_variant *variant, float inc_per_pixel, char *buffer, int *len, int *state, int *cursor, int *select_begin, int *select_end, const struct nk_style_property *style, enum nk_property_filter filter, struct nk_input *in, const struct nk_user_font *font, struct nk_text_edit *text_edit, enum nk_button_behavior behavior); +NK_LIB void nk_property(struct nk_context *ctx, const char *name, struct nk_property_variant *variant, float inc_per_pixel, const enum nk_property_filter filter); + +#ifdef NK_INCLUDE_FONT_BAKING + +#define STB_RECT_PACK_IMPLEMENTATION +#define STB_TRUETYPE_IMPLEMENTATION + +/* Allow consumer to define own STBTT_malloc/STBTT_free, and use the font atlas' allocator otherwise */ +#ifndef STBTT_malloc +static void* +nk_stbtt_malloc(nk_size size, void *user_data) { + struct nk_allocator *alloc = (struct nk_allocator *) user_data; + return alloc->alloc(alloc->userdata, 0, size); +} + +static void +nk_stbtt_free(void *ptr, void *user_data) { + struct nk_allocator *alloc = (struct nk_allocator *) user_data; + alloc->free(alloc->userdata, ptr); +} + +#define STBTT_malloc(x,u) nk_stbtt_malloc(x,u) +#define STBTT_free(x,u) nk_stbtt_free(x,u) + +#endif /* STBTT_malloc */ + +#endif /* NK_INCLUDE_FONT_BAKING */ + +#endif + + + + + +/* =============================================================== + * + * MATH + * + * ===============================================================*/ +/* Since nuklear is supposed to work on all systems providing floating point + math without any dependencies I also had to implement my own math functions + for sqrt, sin and cos. Since the actual highly accurate implementations for + the standard library functions are quite complex and I do not need high + precision for my use cases I use approximations. + + Sqrt + ---- + For square root nuklear uses the famous fast inverse square root: + https://en.wikipedia.org/wiki/Fast_inverse_square_root with + slightly tweaked magic constant. While on today's hardware it is + probably not faster it is still fast and accurate enough for + nuklear's use cases. IMPORTANT: this requires float format IEEE 754 + + Sine/Cosine + ----------- + All constants inside both function are generated Remez's minimax + approximations for value range 0...2*PI. The reason why I decided to + approximate exactly that range is that nuklear only needs sine and + cosine to generate circles which only requires that exact range. + In addition I used Remez instead of Taylor for additional precision: + www.lolengine.net/blog/2011/12/21/better-function-approximations. + + The tool I used to generate constants for both sine and cosine + (it can actually approximate a lot more functions) can be + found here: www.lolengine.net/wiki/oss/lolremez +*/ +NK_LIB float +nk_inv_sqrt(float n) +{ + float x2; + const float threehalfs = 1.5f; + union {nk_uint i; float f;} conv = {0}; + conv.f = n; + x2 = n * 0.5f; + conv.i = 0x5f375A84 - (conv.i >> 1); + conv.f = conv.f * (threehalfs - (x2 * conv.f * conv.f)); + return conv.f; +} +#ifndef NK_SIN +#define NK_SIN nk_sin +NK_LIB float +nk_sin(float x) +{ + NK_STORAGE const float a0 = +1.91059300966915117e-31f; + NK_STORAGE const float a1 = +1.00086760103908896f; + NK_STORAGE const float a2 = -1.21276126894734565e-2f; + NK_STORAGE const float a3 = -1.38078780785773762e-1f; + NK_STORAGE const float a4 = -2.67353392911981221e-2f; + NK_STORAGE const float a5 = +2.08026600266304389e-2f; + NK_STORAGE const float a6 = -3.03996055049204407e-3f; + NK_STORAGE const float a7 = +1.38235642404333740e-4f; + return a0 + x*(a1 + x*(a2 + x*(a3 + x*(a4 + x*(a5 + x*(a6 + x*a7)))))); +} +#endif +#ifndef NK_COS +#define NK_COS nk_cos +NK_LIB float +nk_cos(float x) +{ + /* New implementation. Also generated using lolremez. */ + /* Old version significantly deviated from expected results. */ + NK_STORAGE const float a0 = 9.9995999154986614e-1f; + NK_STORAGE const float a1 = 1.2548995793001028e-3f; + NK_STORAGE const float a2 = -5.0648546280678015e-1f; + NK_STORAGE const float a3 = 1.2942246466519995e-2f; + NK_STORAGE const float a4 = 2.8668384702547972e-2f; + NK_STORAGE const float a5 = 7.3726485210586547e-3f; + NK_STORAGE const float a6 = -3.8510875386947414e-3f; + NK_STORAGE const float a7 = 4.7196604604366623e-4f; + NK_STORAGE const float a8 = -1.8776444013090451e-5f; + return a0 + x*(a1 + x*(a2 + x*(a3 + x*(a4 + x*(a5 + x*(a6 + x*(a7 + x*a8))))))); +} +#endif +NK_LIB nk_uint +nk_round_up_pow2(nk_uint v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; +} +NK_LIB double +nk_pow(double x, int n) +{ + /* check the sign of n */ + double r = 1; + int plus = n >= 0; + n = (plus) ? n : -n; + while (n > 0) { + if ((n & 1) == 1) + r *= x; + n /= 2; + x *= x; + } + return plus ? r : 1.0 / r; +} +NK_LIB int +nk_ifloord(double x) +{ + x = (double)((int)x - ((x < 0.0) ? 1 : 0)); + return (int)x; +} +NK_LIB int +nk_ifloorf(float x) +{ + x = (float)((int)x - ((x < 0.0f) ? 1 : 0)); + return (int)x; +} +NK_LIB int +nk_iceilf(float x) +{ + if (x >= 0) { + int i = (int)x; + return (x > i) ? i+1: i; + } else { + int t = (int)x; + float r = x - (float)t; + return (r > 0.0f) ? t+1: t; + } +} +NK_LIB int +nk_log10(double n) +{ + int neg; + int ret; + int exp = 0; + + neg = (n < 0) ? 1 : 0; + ret = (neg) ? (int)-n : (int)n; + while ((ret / 10) > 0) { + ret /= 10; + exp++; + } + if (neg) exp = -exp; + return exp; +} +NK_API struct nk_rect +nk_get_null_rect(void) +{ + return nk_null_rect; +} +NK_API struct nk_rect +nk_rect(float x, float y, float w, float h) +{ + struct nk_rect r; + r.x = x; r.y = y; + r.w = w; r.h = h; + return r; +} +NK_API struct nk_rect +nk_recti(int x, int y, int w, int h) +{ + struct nk_rect r; + r.x = (float)x; + r.y = (float)y; + r.w = (float)w; + r.h = (float)h; + return r; +} +NK_API struct nk_rect +nk_recta(struct nk_vec2 pos, struct nk_vec2 size) +{ + return nk_rect(pos.x, pos.y, size.x, size.y); +} +NK_API struct nk_rect +nk_rectv(const float *r) +{ + return nk_rect(r[0], r[1], r[2], r[3]); +} +NK_API struct nk_rect +nk_rectiv(const int *r) +{ + return nk_recti(r[0], r[1], r[2], r[3]); +} +NK_API struct nk_vec2 +nk_rect_pos(struct nk_rect r) +{ + struct nk_vec2 ret; + ret.x = r.x; ret.y = r.y; + return ret; +} +NK_API struct nk_vec2 +nk_rect_size(struct nk_rect r) +{ + struct nk_vec2 ret; + ret.x = r.w; ret.y = r.h; + return ret; +} +NK_LIB struct nk_rect +nk_shrink_rect(struct nk_rect r, float amount) +{ + struct nk_rect res; + r.w = NK_MAX(r.w, 2 * amount); + r.h = NK_MAX(r.h, 2 * amount); + res.x = r.x + amount; + res.y = r.y + amount; + res.w = r.w - 2 * amount; + res.h = r.h - 2 * amount; + return res; +} +NK_LIB struct nk_rect +nk_pad_rect(struct nk_rect r, struct nk_vec2 pad) +{ + r.w = NK_MAX(r.w, 2 * pad.x); + r.h = NK_MAX(r.h, 2 * pad.y); + r.x += pad.x; r.y += pad.y; + r.w -= 2 * pad.x; + r.h -= 2 * pad.y; + return r; +} +NK_API struct nk_vec2 +nk_vec2(float x, float y) +{ + struct nk_vec2 ret; + ret.x = x; ret.y = y; + return ret; +} +NK_API struct nk_vec2 +nk_vec2i(int x, int y) +{ + struct nk_vec2 ret; + ret.x = (float)x; + ret.y = (float)y; + return ret; +} +NK_API struct nk_vec2 +nk_vec2v(const float *v) +{ + return nk_vec2(v[0], v[1]); +} +NK_API struct nk_vec2 +nk_vec2iv(const int *v) +{ + return nk_vec2i(v[0], v[1]); +} +NK_LIB void +nk_unify(struct nk_rect *clip, const struct nk_rect *a, float x0, float y0, + float x1, float y1) +{ + NK_ASSERT(a); + NK_ASSERT(clip); + clip->x = NK_MAX(a->x, x0); + clip->y = NK_MAX(a->y, y0); + clip->w = NK_MIN(a->x + a->w, x1) - clip->x; + clip->h = NK_MIN(a->y + a->h, y1) - clip->y; + clip->w = NK_MAX(0, clip->w); + clip->h = NK_MAX(0, clip->h); +} + +NK_API void +nk_triangle_from_direction(struct nk_vec2 *result, struct nk_rect r, + float pad_x, float pad_y, enum nk_heading direction) +{ + float w_half, h_half; + NK_ASSERT(result); + + r.w = NK_MAX(2 * pad_x, r.w); + r.h = NK_MAX(2 * pad_y, r.h); + r.w = r.w - 2 * pad_x; + r.h = r.h - 2 * pad_y; + + r.x = r.x + pad_x; + r.y = r.y + pad_y; + + w_half = r.w / 2.0f; + h_half = r.h / 2.0f; + + if (direction == NK_UP) { + result[0] = nk_vec2(r.x + w_half, r.y); + result[1] = nk_vec2(r.x + r.w, r.y + r.h); + result[2] = nk_vec2(r.x, r.y + r.h); + } else if (direction == NK_RIGHT) { + result[0] = nk_vec2(r.x, r.y); + result[1] = nk_vec2(r.x + r.w, r.y + h_half); + result[2] = nk_vec2(r.x, r.y + r.h); + } else if (direction == NK_DOWN) { + result[0] = nk_vec2(r.x, r.y); + result[1] = nk_vec2(r.x + r.w, r.y); + result[2] = nk_vec2(r.x + w_half, r.y + r.h); + } else { + result[0] = nk_vec2(r.x, r.y + h_half); + result[1] = nk_vec2(r.x + r.w, r.y); + result[2] = nk_vec2(r.x + r.w, r.y + r.h); + } +} + + + + + +/* =============================================================== + * + * UTIL + * + * ===============================================================*/ +NK_INTERN int nk_str_match_here(const char *regexp, const char *text); +NK_INTERN int nk_str_match_star(int c, const char *regexp, const char *text); +NK_LIB nk_bool nk_is_lower(int c) {return (c >= 'a' && c <= 'z') || (c >= 0xE0 && c <= 0xFF);} +NK_LIB nk_bool nk_is_upper(int c){return (c >= 'A' && c <= 'Z') || (c >= 0xC0 && c <= 0xDF);} +NK_LIB int nk_to_upper(int c) {return (c >= 'a' && c <= 'z') ? (c - ('a' - 'A')) : c;} +NK_LIB int nk_to_lower(int c) {return (c >= 'A' && c <= 'Z') ? (c - ('a' + 'A')) : c;} + +#ifndef NK_MEMCPY +#define NK_MEMCPY nk_memcopy +NK_LIB void* +nk_memcopy(void *dst0, const void *src0, nk_size length) +{ + nk_ptr t; + char *dst = (char*)dst0; + const char *src = (const char*)src0; + if (length == 0 || dst == src) + goto done; + + #define nk_word int + #define nk_wsize sizeof(nk_word) + #define nk_wmask (nk_wsize-1) + #define NK_TLOOP(s) if (t) NK_TLOOP1(s) + #define NK_TLOOP1(s) do { s; } while (--t) + + if (dst < src) { + t = (nk_ptr)src; /* only need low bits */ + if ((t | (nk_ptr)dst) & nk_wmask) { + if ((t ^ (nk_ptr)dst) & nk_wmask || length < nk_wsize) + t = length; + else + t = nk_wsize - (t & nk_wmask); + length -= t; + NK_TLOOP1(*dst++ = *src++); + } + t = length / nk_wsize; + NK_TLOOP(*(nk_word*)(void*)dst = *(const nk_word*)(const void*)src; + src += nk_wsize; dst += nk_wsize); + t = length & nk_wmask; + NK_TLOOP(*dst++ = *src++); + } else { + src += length; + dst += length; + t = (nk_ptr)src; + if ((t | (nk_ptr)dst) & nk_wmask) { + if ((t ^ (nk_ptr)dst) & nk_wmask || length <= nk_wsize) + t = length; + else + t &= nk_wmask; + length -= t; + NK_TLOOP1(*--dst = *--src); + } + t = length / nk_wsize; + NK_TLOOP(src -= nk_wsize; dst -= nk_wsize; + *(nk_word*)(void*)dst = *(const nk_word*)(const void*)src); + t = length & nk_wmask; + NK_TLOOP(*--dst = *--src); + } + #undef nk_word + #undef nk_wsize + #undef nk_wmask + #undef NK_TLOOP + #undef NK_TLOOP1 +done: + return (dst0); +} +#endif +#ifndef NK_MEMSET +#define NK_MEMSET nk_memset +NK_LIB void +nk_memset(void *ptr, int c0, nk_size size) +{ + #define nk_word unsigned + #define nk_wsize sizeof(nk_word) + #define nk_wmask (nk_wsize - 1) + nk_byte *dst = (nk_byte*)ptr; + unsigned c = 0; + nk_size t = 0; + + if ((c = (nk_byte)c0) != 0) { + c = (c << 8) | c; /* at least 16-bits */ + if (sizeof(unsigned int) > 2) + c = (c << 16) | c; /* at least 32-bits*/ + } + + /* too small of a word count */ + dst = (nk_byte*)ptr; + if (size < 3 * nk_wsize) { + while (size--) *dst++ = (nk_byte)c0; + return; + } + + /* align destination */ + if ((t = NK_PTR_TO_UINT(dst) & nk_wmask) != 0) { + t = nk_wsize -t; + size -= t; + do { + *dst++ = (nk_byte)c0; + } while (--t != 0); + } + + /* fill word */ + t = size / nk_wsize; + do { + *(nk_word*)((void*)dst) = c; + dst += nk_wsize; + } while (--t != 0); + + /* fill trailing bytes */ + t = (size & nk_wmask); + if (t != 0) { + do { + *dst++ = (nk_byte)c0; + } while (--t != 0); + } + + #undef nk_word + #undef nk_wsize + #undef nk_wmask +} +#endif +NK_LIB void +nk_zero(void *ptr, nk_size size) +{ + NK_ASSERT(ptr); + NK_MEMSET(ptr, 0, size); +} +NK_API int +nk_strlen(const char *str) +{ + int siz = 0; + NK_ASSERT(str); + while (str && *str++ != '\0') siz++; + return siz; +} +NK_API int +nk_strtoi(const char *str, const char **endptr) +{ + int neg = 1; + const char *p = str; + int value = 0; + + NK_ASSERT(str); + if (!str) return 0; + + /* skip whitespace */ + while (*p == ' ') p++; + if (*p == '-') { + neg = -1; + p++; + } + while (*p && *p >= '0' && *p <= '9') { + value = value * 10 + (int) (*p - '0'); + p++; + } + if (endptr) + *endptr = p; + return neg*value; +} +NK_API double +nk_strtod(const char *str, const char **endptr) +{ + double m; + double neg = 1.0; + const char *p = str; + double value = 0; + double number = 0; + + NK_ASSERT(str); + if (!str) return 0; + + /* skip whitespace */ + while (*p == ' ') p++; + if (*p == '-') { + neg = -1.0; + p++; + } + + while (*p && *p != '.' && *p != 'e') { + value = value * 10.0 + (double) (*p - '0'); + p++; + } + + if (*p == '.') { + p++; + for(m = 0.1; *p && *p != 'e'; p++ ) { + value = value + (double) (*p - '0') * m; + m *= 0.1; + } + } + if (*p == 'e') { + int i, pow, div; + p++; + if (*p == '-') { + div = nk_true; + p++; + } else if (*p == '+') { + div = nk_false; + p++; + } else div = nk_false; + + for (pow = 0; *p; p++) + pow = pow * 10 + (int) (*p - '0'); + + for (m = 1.0, i = 0; i < pow; i++) + m *= 10.0; + + if (div) + value /= m; + else value *= m; + } + number = value * neg; + if (endptr) + *endptr = p; + return number; +} +NK_API float +nk_strtof(const char *str, const char **endptr) +{ + float float_value; + double double_value; + double_value = NK_STRTOD(str, endptr); + float_value = (float)double_value; + return float_value; +} +NK_API int +nk_stricmp(const char *s1, const char *s2) +{ + nk_int c1,c2,d; + do { + c1 = *s1++; + c2 = *s2++; + d = c1 - c2; + while (d) { + if (c1 <= 'Z' && c1 >= 'A') { + d += ('a' - 'A'); + if (!d) break; + } + if (c2 <= 'Z' && c2 >= 'A') { + d -= ('a' - 'A'); + if (!d) break; + } + return ((d >= 0) << 1) - 1; + } + } while (c1); + return 0; +} +NK_API int +nk_stricmpn(const char *s1, const char *s2, int n) +{ + int c1,c2,d; + NK_ASSERT(n >= 0); + do { + c1 = *s1++; + c2 = *s2++; + if (!n--) return 0; + + d = c1 - c2; + while (d) { + if (c1 <= 'Z' && c1 >= 'A') { + d += ('a' - 'A'); + if (!d) break; + } + if (c2 <= 'Z' && c2 >= 'A') { + d -= ('a' - 'A'); + if (!d) break; + } + return ((d >= 0) << 1) - 1; + } + } while (c1); + return 0; +} +NK_INTERN int +nk_str_match_here(const char *regexp, const char *text) +{ + if (regexp[0] == '\0') + return 1; + if (regexp[1] == '*') + return nk_str_match_star(regexp[0], regexp+2, text); + if (regexp[0] == '$' && regexp[1] == '\0') + return *text == '\0'; + if (*text!='\0' && (regexp[0]=='.' || regexp[0]==*text)) + return nk_str_match_here(regexp+1, text+1); + return 0; +} +NK_INTERN int +nk_str_match_star(int c, const char *regexp, const char *text) +{ + do {/* a '* matches zero or more instances */ + if (nk_str_match_here(regexp, text)) + return 1; + } while (*text != '\0' && (*text++ == c || c == '.')); + return 0; +} +NK_API int +nk_strfilter(const char *text, const char *regexp) +{ + /* + c matches any literal character c + . matches any single character + ^ matches the beginning of the input string + $ matches the end of the input string + * matches zero or more occurrences of the previous character*/ + if (regexp[0] == '^') + return nk_str_match_here(regexp+1, text); + do { /* must look even if string is empty */ + if (nk_str_match_here(regexp, text)) + return 1; + } while (*text++ != '\0'); + return 0; +} +NK_API int +nk_strmatch_fuzzy_text(const char *str, int str_len, + const char *pattern, int *out_score) +{ + /* Returns true if each character in pattern is found sequentially within str + * if found then out_score is also set. Score value has no intrinsic meaning. + * Range varies with pattern. Can only compare scores with same search pattern. */ + + /* bonus for adjacent matches */ + #define NK_ADJACENCY_BONUS 5 + /* bonus if match occurs after a separator */ + #define NK_SEPARATOR_BONUS 10 + /* bonus if match is uppercase and prev is lower */ + #define NK_CAMEL_BONUS 10 + /* penalty applied for every letter in str before the first match */ + #define NK_LEADING_LETTER_PENALTY (-3) + /* maximum penalty for leading letters */ + #define NK_MAX_LEADING_LETTER_PENALTY (-9) + /* penalty for every letter that doesn't matter */ + #define NK_UNMATCHED_LETTER_PENALTY (-1) + + /* loop variables */ + int score = 0; + char const * pattern_iter = pattern; + int str_iter = 0; + int prev_matched = nk_false; + int prev_lower = nk_false; + /* true so if first letter match gets separator bonus*/ + int prev_separator = nk_true; + + /* use "best" matched letter if multiple string letters match the pattern */ + char const * best_letter = 0; + int best_letter_score = 0; + + /* loop over strings */ + NK_ASSERT(str); + NK_ASSERT(pattern); + if (!str || !str_len || !pattern) return 0; + while (str_iter < str_len) + { + const char pattern_letter = *pattern_iter; + const char str_letter = str[str_iter]; + + int next_match = *pattern_iter != '\0' && + nk_to_lower(pattern_letter) == nk_to_lower(str_letter); + int rematch = best_letter && nk_to_upper(*best_letter) == nk_to_upper(str_letter); + + int advanced = next_match && best_letter; + int pattern_repeat = best_letter && *pattern_iter != '\0'; + pattern_repeat = pattern_repeat && + nk_to_lower(*best_letter) == nk_to_lower(pattern_letter); + + if (advanced || pattern_repeat) { + score += best_letter_score; + best_letter = 0; + best_letter_score = 0; + } + + if (next_match || rematch) + { + int new_score = 0; + /* Apply penalty for each letter before the first pattern match */ + if (pattern_iter == pattern) { + int count = (int)(&str[str_iter] - str); + int penalty = NK_LEADING_LETTER_PENALTY * count; + if (penalty < NK_MAX_LEADING_LETTER_PENALTY) + penalty = NK_MAX_LEADING_LETTER_PENALTY; + + score += penalty; + } + + /* apply bonus for consecutive bonuses */ + if (prev_matched) + new_score += NK_ADJACENCY_BONUS; + + /* apply bonus for matches after a separator */ + if (prev_separator) + new_score += NK_SEPARATOR_BONUS; + + /* apply bonus across camel case boundaries */ + if (prev_lower && nk_is_upper(str_letter)) + new_score += NK_CAMEL_BONUS; + + /* update pattern iter IFF the next pattern letter was matched */ + if (next_match) + ++pattern_iter; + + /* update best letter in str which may be for a "next" letter or a rematch */ + if (new_score >= best_letter_score) { + /* apply penalty for now skipped letter */ + if (best_letter != 0) + score += NK_UNMATCHED_LETTER_PENALTY; + + best_letter = &str[str_iter]; + best_letter_score = new_score; + } + prev_matched = nk_true; + } else { + score += NK_UNMATCHED_LETTER_PENALTY; + prev_matched = nk_false; + } + + /* separators should be more easily defined */ + prev_lower = nk_is_lower(str_letter) != 0; + prev_separator = str_letter == '_' || str_letter == ' '; + + ++str_iter; + } + + /* apply score for last match */ + if (best_letter) + score += best_letter_score; + + /* did not match full pattern */ + if (*pattern_iter != '\0') + return nk_false; + + if (out_score) + *out_score = score; + return nk_true; +} +NK_API int +nk_strmatch_fuzzy_string(char const *str, char const *pattern, int *out_score) +{ + return nk_strmatch_fuzzy_text(str, nk_strlen(str), pattern, out_score); +} +NK_LIB int +nk_string_float_limit(char *string, int prec) +{ + int dot = 0; + char *c = string; + while (*c) { + if (*c == '.') { + dot = 1; + c++; + continue; + } + if (dot == (prec+1)) { + *c = 0; + break; + } + if (dot > 0) dot++; + c++; + } + return (int)(c - string); +} +NK_INTERN void +nk_strrev_ascii(char *s) +{ + int len = nk_strlen(s); + int end = len / 2; + int i = 0; + char t; + for (; i < end; ++i) { + t = s[i]; + s[i] = s[len - 1 - i]; + s[len -1 - i] = t; + } +} +NK_LIB char* +nk_itoa(char *s, long n) +{ + long i = 0; + if (n == 0) { + s[i++] = '0'; + s[i] = 0; + return s; + } + if (n < 0) { + s[i++] = '-'; + n = -n; + } + while (n > 0) { + s[i++] = (char)('0' + (n % 10)); + n /= 10; + } + s[i] = 0; + if (s[0] == '-') + ++s; + + nk_strrev_ascii(s); + return s; +} +#ifndef NK_DTOA +#define NK_DTOA nk_dtoa +NK_LIB char* +nk_dtoa(char *s, double n) +{ + int useExp = 0; + int digit = 0, m = 0, m1 = 0; + char *c = s; + int neg = 0; + + NK_ASSERT(s); + if (!s) return 0; + + if (n == 0.0) { + s[0] = '0'; s[1] = '\0'; + return s; + } + + neg = (n < 0); + if (neg) n = -n; + + /* calculate magnitude */ + m = nk_log10(n); + useExp = (m >= 14 || (neg && m >= 9) || m <= -9); + if (neg) *(c++) = '-'; + + /* set up for scientific notation */ + if (useExp) { + if (m < 0) + m -= 1; + n = n / (double)nk_pow(10.0, m); + m1 = m; + m = 0; + } + if (m < 1.0) { + m = 0; + } + + /* convert the number */ + while (n > NK_FLOAT_PRECISION || m >= 0) { + double weight = nk_pow(10.0, m); + if (weight > 0) { + double t = (double)n / weight; + digit = nk_ifloord(t); + n -= ((double)digit * weight); + *(c++) = (char)('0' + (char)digit); + } + if (m == 0 && n > 0) + *(c++) = '.'; + m--; + } + + if (useExp) { + /* convert the exponent */ + int i, j; + *(c++) = 'e'; + if (m1 > 0) { + *(c++) = '+'; + } else { + *(c++) = '-'; + m1 = -m1; + } + m = 0; + while (m1 > 0) { + *(c++) = (char)('0' + (char)(m1 % 10)); + m1 /= 10; + m++; + } + c -= m; + for (i = 0, j = m-1; i= buf_size) break; + iter++; + + /* flag arguments */ + while (*iter) { + if (*iter == '-') flag |= NK_ARG_FLAG_LEFT; + else if (*iter == '+') flag |= NK_ARG_FLAG_PLUS; + else if (*iter == ' ') flag |= NK_ARG_FLAG_SPACE; + else if (*iter == '#') flag |= NK_ARG_FLAG_NUM; + else if (*iter == '0') flag |= NK_ARG_FLAG_ZERO; + else break; + iter++; + } + + /* width argument */ + width = NK_DEFAULT; + if (*iter >= '1' && *iter <= '9') { + const char *end; + width = nk_strtoi(iter, &end); + if (end == iter) + width = -1; + else iter = end; + } else if (*iter == '*') { + width = va_arg(args, int); + iter++; + } + + /* precision argument */ + precision = NK_DEFAULT; + if (*iter == '.') { + iter++; + if (*iter == '*') { + precision = va_arg(args, int); + iter++; + } else { + const char *end; + precision = nk_strtoi(iter, &end); + if (end == iter) + precision = -1; + else iter = end; + } + } + + /* length modifier */ + if (*iter == 'h') { + if (*(iter+1) == 'h') { + arg_type = NK_ARG_TYPE_CHAR; + iter++; + } else arg_type = NK_ARG_TYPE_SHORT; + iter++; + } else if (*iter == 'l') { + arg_type = NK_ARG_TYPE_LONG; + iter++; + } else arg_type = NK_ARG_TYPE_DEFAULT; + + /* specifier */ + if (*iter == '%') { + NK_ASSERT(arg_type == NK_ARG_TYPE_DEFAULT); + NK_ASSERT(precision == NK_DEFAULT); + NK_ASSERT(width == NK_DEFAULT); + if (len < buf_size) + buf[len++] = '%'; + } else if (*iter == 's') { + /* string */ + const char *str = va_arg(args, const char*); + NK_ASSERT(str != buf && "buffer and argument are not allowed to overlap!"); + NK_ASSERT(arg_type == NK_ARG_TYPE_DEFAULT); + NK_ASSERT(precision == NK_DEFAULT); + NK_ASSERT(width == NK_DEFAULT); + if (str == buf) return -1; + while (str && *str && len < buf_size) + buf[len++] = *str++; + } else if (*iter == 'n') { + /* current length callback */ + signed int *n = va_arg(args, int*); + NK_ASSERT(arg_type == NK_ARG_TYPE_DEFAULT); + NK_ASSERT(precision == NK_DEFAULT); + NK_ASSERT(width == NK_DEFAULT); + if (n) *n = len; + } else if (*iter == 'c' || *iter == 'i' || *iter == 'd') { + /* signed integer */ + long value = 0; + const char *num_iter; + int num_len, num_print, padding; + int cur_precision = NK_MAX(precision, 1); + int cur_width = NK_MAX(width, 0); + + /* retrieve correct value type */ + if (arg_type == NK_ARG_TYPE_CHAR) + value = (signed char)va_arg(args, int); + else if (arg_type == NK_ARG_TYPE_SHORT) + value = (signed short)va_arg(args, int); + else if (arg_type == NK_ARG_TYPE_LONG) + value = va_arg(args, signed long); + else if (*iter == 'c') + value = (unsigned char)va_arg(args, int); + else value = va_arg(args, signed int); + + /* convert number to string */ + nk_itoa(number_buffer, value); + num_len = nk_strlen(number_buffer); + padding = NK_MAX(cur_width - NK_MAX(cur_precision, num_len), 0); + if ((flag & NK_ARG_FLAG_PLUS) || (flag & NK_ARG_FLAG_SPACE)) + padding = NK_MAX(padding-1, 0); + + /* fill left padding up to a total of `width` characters */ + if (!(flag & NK_ARG_FLAG_LEFT)) { + while (padding-- > 0 && (len < buf_size)) { + if ((flag & NK_ARG_FLAG_ZERO) && (precision == NK_DEFAULT)) + buf[len++] = '0'; + else buf[len++] = ' '; + } + } + + /* copy string value representation into buffer */ + if ((flag & NK_ARG_FLAG_PLUS) && value >= 0 && len < buf_size) + buf[len++] = '+'; + else if ((flag & NK_ARG_FLAG_SPACE) && value >= 0 && len < buf_size) + buf[len++] = ' '; + + /* fill up to precision number of digits with '0' */ + num_print = NK_MAX(cur_precision, num_len); + while (precision && (num_print > num_len) && (len < buf_size)) { + buf[len++] = '0'; + num_print--; + } + + /* copy string value representation into buffer */ + num_iter = number_buffer; + while (precision && *num_iter && len < buf_size) + buf[len++] = *num_iter++; + + /* fill right padding up to width characters */ + if (flag & NK_ARG_FLAG_LEFT) { + while ((padding-- > 0) && (len < buf_size)) + buf[len++] = ' '; + } + } else if (*iter == 'o' || *iter == 'x' || *iter == 'X' || *iter == 'u') { + /* unsigned integer */ + unsigned long value = 0; + int num_len = 0, num_print, padding = 0; + int cur_precision = NK_MAX(precision, 1); + int cur_width = NK_MAX(width, 0); + unsigned int base = (*iter == 'o') ? 8: (*iter == 'u')? 10: 16; + + /* print oct/hex/dec value */ + const char *upper_output_format = "0123456789ABCDEF"; + const char *lower_output_format = "0123456789abcdef"; + const char *output_format = (*iter == 'x') ? + lower_output_format: upper_output_format; + + /* retrieve correct value type */ + if (arg_type == NK_ARG_TYPE_CHAR) + value = (unsigned char)va_arg(args, int); + else if (arg_type == NK_ARG_TYPE_SHORT) + value = (unsigned short)va_arg(args, int); + else if (arg_type == NK_ARG_TYPE_LONG) + value = va_arg(args, unsigned long); + else value = va_arg(args, unsigned int); + + do { + /* convert decimal number into hex/oct number */ + int digit = output_format[value % base]; + if (num_len < NK_MAX_NUMBER_BUFFER) + number_buffer[num_len++] = (char)digit; + value /= base; + } while (value > 0); + + num_print = NK_MAX(cur_precision, num_len); + padding = NK_MAX(cur_width - NK_MAX(cur_precision, num_len), 0); + if (flag & NK_ARG_FLAG_NUM) + padding = NK_MAX(padding-1, 0); + + /* fill left padding up to a total of `width` characters */ + if (!(flag & NK_ARG_FLAG_LEFT)) { + while ((padding-- > 0) && (len < buf_size)) { + if ((flag & NK_ARG_FLAG_ZERO) && (precision == NK_DEFAULT)) + buf[len++] = '0'; + else buf[len++] = ' '; + } + } + + /* fill up to precision number of digits */ + if (num_print && (flag & NK_ARG_FLAG_NUM)) { + if ((*iter == 'o') && (len < buf_size)) { + buf[len++] = '0'; + } else if ((*iter == 'x') && ((len+1) < buf_size)) { + buf[len++] = '0'; + buf[len++] = 'x'; + } else if ((*iter == 'X') && ((len+1) < buf_size)) { + buf[len++] = '0'; + buf[len++] = 'X'; + } + } + while (precision && (num_print > num_len) && (len < buf_size)) { + buf[len++] = '0'; + num_print--; + } + + /* reverse number direction */ + while (num_len > 0) { + if (precision && (len < buf_size)) + buf[len++] = number_buffer[num_len-1]; + num_len--; + } + + /* fill right padding up to width characters */ + if (flag & NK_ARG_FLAG_LEFT) { + while ((padding-- > 0) && (len < buf_size)) + buf[len++] = ' '; + } + } else if (*iter == 'f') { + /* floating point */ + const char *num_iter; + int cur_precision = (precision < 0) ? 6: precision; + int prefix, cur_width = NK_MAX(width, 0); + double value = va_arg(args, double); + int num_len = 0, frac_len = 0, dot = 0; + int padding = 0; + + NK_ASSERT(arg_type == NK_ARG_TYPE_DEFAULT); + NK_DTOA(number_buffer, value); + num_len = nk_strlen(number_buffer); + + /* calculate padding */ + num_iter = number_buffer; + while (*num_iter && *num_iter != '.') + num_iter++; + + prefix = (*num_iter == '.')?(int)(num_iter - number_buffer)+1:0; + padding = NK_MAX(cur_width - (prefix + NK_MIN(cur_precision, num_len - prefix)) , 0); + if ((flag & NK_ARG_FLAG_PLUS) || (flag & NK_ARG_FLAG_SPACE)) + padding = NK_MAX(padding-1, 0); + + /* fill left padding up to a total of `width` characters */ + if (!(flag & NK_ARG_FLAG_LEFT)) { + while (padding-- > 0 && (len < buf_size)) { + if (flag & NK_ARG_FLAG_ZERO) + buf[len++] = '0'; + else buf[len++] = ' '; + } + } + + /* copy string value representation into buffer */ + num_iter = number_buffer; + if ((flag & NK_ARG_FLAG_PLUS) && (value >= 0) && (len < buf_size)) + buf[len++] = '+'; + else if ((flag & NK_ARG_FLAG_SPACE) && (value >= 0) && (len < buf_size)) + buf[len++] = ' '; + while (*num_iter) { + if (dot) frac_len++; + if (len < buf_size) + buf[len++] = *num_iter; + if (*num_iter == '.') dot = 1; + if (frac_len >= cur_precision) break; + num_iter++; + } + + /* fill number up to precision */ + while (frac_len < cur_precision) { + if (!dot && len < buf_size) { + buf[len++] = '.'; + dot = 1; + } + if (len < buf_size) + buf[len++] = '0'; + frac_len++; + } + + /* fill right padding up to width characters */ + if (flag & NK_ARG_FLAG_LEFT) { + while ((padding-- > 0) && (len < buf_size)) + buf[len++] = ' '; + } + } else { + /* Specifier not supported: g,G,e,E,p,z */ + NK_ASSERT(0 && "specifier is not supported!"); + return result; + } + } + buf[(len >= buf_size)?(buf_size-1):len] = 0; + result = (len >= buf_size)?-1:len; + return result; +} +#endif +NK_LIB int +nk_strfmt(char *buf, int buf_size, const char *fmt, va_list args) +{ + int result = -1; + NK_ASSERT(buf); + NK_ASSERT(buf_size); + if (!buf || !buf_size || !fmt) return 0; +#ifdef NK_INCLUDE_STANDARD_IO + result = NK_VSNPRINTF(buf, (nk_size)buf_size, fmt, args); + result = (result >= buf_size) ? -1: result; + buf[buf_size-1] = 0; +#else + result = nk_vsnprintf(buf, buf_size, fmt, args); +#endif + return result; +} +#endif +NK_API nk_hash +nk_murmur_hash(const void * key, int len, nk_hash seed) +{ + /* 32-Bit MurmurHash3: https://code.google.com/p/smhasher/wiki/MurmurHash3*/ + #define NK_ROTL(x,r) ((x) << (r) | ((x) >> (32 - r))) + + nk_uint h1 = seed; + nk_uint k1; + const nk_byte *data = (const nk_byte*)key; + const nk_byte *keyptr = data; + nk_byte *k1ptr; + const int bsize = sizeof(k1); + const int nblocks = len/4; + + const nk_uint c1 = 0xcc9e2d51; + const nk_uint c2 = 0x1b873593; + const nk_byte *tail; + int i; + + /* body */ + if (!key) return 0; + for (i = 0; i < nblocks; ++i, keyptr += bsize) { + k1ptr = (nk_byte*)&k1; + k1ptr[0] = keyptr[0]; + k1ptr[1] = keyptr[1]; + k1ptr[2] = keyptr[2]; + k1ptr[3] = keyptr[3]; + + k1 *= c1; + k1 = NK_ROTL(k1,15); + k1 *= c2; + + h1 ^= k1; + h1 = NK_ROTL(h1,13); + h1 = h1*5+0xe6546b64; + } + + /* tail */ + tail = (const nk_byte*)(data + nblocks*4); + k1 = 0; + switch (len & 3) { + case 3: k1 ^= (nk_uint)(tail[2] << 16); /* fallthrough */ + case 2: k1 ^= (nk_uint)(tail[1] << 8u); /* fallthrough */ + case 1: k1 ^= tail[0]; + k1 *= c1; + k1 = NK_ROTL(k1,15); + k1 *= c2; + h1 ^= k1; + break; + default: break; + } + + /* finalization */ + h1 ^= (nk_uint)len; + /* fmix32 */ + h1 ^= h1 >> 16; + h1 *= 0x85ebca6b; + h1 ^= h1 >> 13; + h1 *= 0xc2b2ae35; + h1 ^= h1 >> 16; + + #undef NK_ROTL + return h1; +} +#ifdef NK_INCLUDE_STANDARD_IO +NK_LIB char* +nk_file_load(const char* path, nk_size* siz, struct nk_allocator *alloc) +{ + char *buf; + FILE *fd; + long ret; + + NK_ASSERT(path); + NK_ASSERT(siz); + NK_ASSERT(alloc); + if (!path || !siz || !alloc) + return 0; + + fd = fopen(path, "rb"); + if (!fd) return 0; + fseek(fd, 0, SEEK_END); + ret = ftell(fd); + if (ret < 0) { + fclose(fd); + return 0; + } + *siz = (nk_size)ret; + fseek(fd, 0, SEEK_SET); + buf = (char*)alloc->alloc(alloc->userdata,0, *siz); + NK_ASSERT(buf); + if (!buf) { + fclose(fd); + return 0; + } + *siz = (nk_size)fread(buf, 1,*siz, fd); + fclose(fd); + return buf; +} +#endif +NK_LIB int +nk_text_clamp(const struct nk_user_font *font, const char *text, + int text_len, float space, int *glyphs, float *text_width, + nk_rune *sep_list, int sep_count) +{ + int i = 0; + int glyph_len = 0; + float last_width = 0; + nk_rune unicode = 0; + float width = 0; + int len = 0; + int g = 0; + float s; + + int sep_len = 0; + int sep_g = 0; + float sep_width = 0; + sep_count = NK_MAX(sep_count,0); + + glyph_len = nk_utf_decode(text, &unicode, text_len); + while (glyph_len && (width < space) && (len < text_len)) { + len += glyph_len; + s = font->width(font->userdata, font->height, text, len); + for (i = 0; i < sep_count; ++i) { + if (unicode != sep_list[i]) continue; + sep_width = last_width = width; + sep_g = g+1; + sep_len = len; + break; + } + if (i == sep_count){ + last_width = sep_width = width; + sep_g = g+1; + } + width = s; + glyph_len = nk_utf_decode(&text[len], &unicode, text_len - len); + g++; + } + if (len >= text_len) { + *glyphs = g; + *text_width = last_width; + return len; + } else { + *glyphs = sep_g; + *text_width = sep_width; + return (!sep_len) ? len: sep_len; + } +} +NK_LIB struct nk_vec2 +nk_text_calculate_text_bounds(const struct nk_user_font *font, + const char *begin, int byte_len, float row_height, const char **remaining, + struct nk_vec2 *out_offset, int *glyphs, int op) +{ + float line_height = row_height; + struct nk_vec2 text_size = nk_vec2(0,0); + float line_width = 0.0f; + + float glyph_width; + int glyph_len = 0; + nk_rune unicode = 0; + int text_len = 0; + if (!begin || byte_len <= 0 || !font) + return nk_vec2(0,row_height); + + glyph_len = nk_utf_decode(begin, &unicode, byte_len); + if (!glyph_len) return text_size; + glyph_width = font->width(font->userdata, font->height, begin, glyph_len); + + *glyphs = 0; + while ((text_len < byte_len) && glyph_len) { + if (unicode == '\n') { + text_size.x = NK_MAX(text_size.x, line_width); + text_size.y += line_height; + line_width = 0; + *glyphs+=1; + if (op == NK_STOP_ON_NEW_LINE) + break; + + text_len++; + glyph_len = nk_utf_decode(begin + text_len, &unicode, byte_len-text_len); + continue; + } + + if (unicode == '\r') { + text_len++; + *glyphs+=1; + glyph_len = nk_utf_decode(begin + text_len, &unicode, byte_len-text_len); + continue; + } + + *glyphs = *glyphs + 1; + text_len += glyph_len; + line_width += (float)glyph_width; + glyph_len = nk_utf_decode(begin + text_len, &unicode, byte_len-text_len); + glyph_width = font->width(font->userdata, font->height, begin+text_len, glyph_len); + continue; + } + + if (text_size.x < line_width) + text_size.x = line_width; + if (out_offset) + *out_offset = nk_vec2(line_width, text_size.y + line_height); + if (line_width > 0 || text_size.y == 0.0f) + text_size.y += line_height; + if (remaining) + *remaining = begin+text_len; + return text_size; +} + + + + + +/* ============================================================== + * + * COLOR + * + * ===============================================================*/ +NK_INTERN int +nk_parse_hex(const char *p, int length) +{ + int i = 0; + int len = 0; + while (len < length) { + i <<= 4; + if (p[len] >= 'a' && p[len] <= 'f') + i += ((p[len] - 'a') + 10); + else if (p[len] >= 'A' && p[len] <= 'F') + i += ((p[len] - 'A') + 10); + else i += (p[len] - '0'); + len++; + } + return i; +} +NK_API struct nk_color +nk_rgba(int r, int g, int b, int a) +{ + struct nk_color ret; + ret.r = (nk_byte)NK_CLAMP(0, r, 255); + ret.g = (nk_byte)NK_CLAMP(0, g, 255); + ret.b = (nk_byte)NK_CLAMP(0, b, 255); + ret.a = (nk_byte)NK_CLAMP(0, a, 255); + return ret; +} +NK_API struct nk_color +nk_rgb_hex(const char *rgb) +{ + struct nk_color col; + const char *c = rgb; + if (*c == '#') c++; + col.r = (nk_byte)nk_parse_hex(c, 2); + col.g = (nk_byte)nk_parse_hex(c+2, 2); + col.b = (nk_byte)nk_parse_hex(c+4, 2); + col.a = 255; + return col; +} +NK_API struct nk_color +nk_rgba_hex(const char *rgb) +{ + struct nk_color col; + const char *c = rgb; + if (*c == '#') c++; + col.r = (nk_byte)nk_parse_hex(c, 2); + col.g = (nk_byte)nk_parse_hex(c+2, 2); + col.b = (nk_byte)nk_parse_hex(c+4, 2); + col.a = (nk_byte)nk_parse_hex(c+6, 2); + return col; +} +NK_API void +nk_color_hex_rgba(char *output, struct nk_color col) +{ + #define NK_TO_HEX(i) ((i) <= 9 ? '0' + (i): 'A' - 10 + (i)) + output[0] = (char)NK_TO_HEX((col.r & 0xF0) >> 4); + output[1] = (char)NK_TO_HEX((col.r & 0x0F)); + output[2] = (char)NK_TO_HEX((col.g & 0xF0) >> 4); + output[3] = (char)NK_TO_HEX((col.g & 0x0F)); + output[4] = (char)NK_TO_HEX((col.b & 0xF0) >> 4); + output[5] = (char)NK_TO_HEX((col.b & 0x0F)); + output[6] = (char)NK_TO_HEX((col.a & 0xF0) >> 4); + output[7] = (char)NK_TO_HEX((col.a & 0x0F)); + output[8] = '\0'; + #undef NK_TO_HEX +} +NK_API void +nk_color_hex_rgb(char *output, struct nk_color col) +{ + #define NK_TO_HEX(i) ((i) <= 9 ? '0' + (i): 'A' - 10 + (i)) + output[0] = (char)NK_TO_HEX((col.r & 0xF0) >> 4); + output[1] = (char)NK_TO_HEX((col.r & 0x0F)); + output[2] = (char)NK_TO_HEX((col.g & 0xF0) >> 4); + output[3] = (char)NK_TO_HEX((col.g & 0x0F)); + output[4] = (char)NK_TO_HEX((col.b & 0xF0) >> 4); + output[5] = (char)NK_TO_HEX((col.b & 0x0F)); + output[6] = '\0'; + #undef NK_TO_HEX +} +NK_API struct nk_color +nk_rgba_iv(const int *c) +{ + return nk_rgba(c[0], c[1], c[2], c[3]); +} +NK_API struct nk_color +nk_rgba_bv(const nk_byte *c) +{ + return nk_rgba(c[0], c[1], c[2], c[3]); +} +NK_API struct nk_color +nk_rgb(int r, int g, int b) +{ + struct nk_color ret; + ret.r = (nk_byte)NK_CLAMP(0, r, 255); + ret.g = (nk_byte)NK_CLAMP(0, g, 255); + ret.b = (nk_byte)NK_CLAMP(0, b, 255); + ret.a = (nk_byte)255; + return ret; +} +NK_API struct nk_color +nk_rgb_iv(const int *c) +{ + return nk_rgb(c[0], c[1], c[2]); +} +NK_API struct nk_color +nk_rgb_bv(const nk_byte* c) +{ + return nk_rgb(c[0], c[1], c[2]); +} +NK_API struct nk_color +nk_rgba_u32(nk_uint in) +{ + struct nk_color ret; + ret.r = (in & 0xFF); + ret.g = ((in >> 8) & 0xFF); + ret.b = ((in >> 16) & 0xFF); + ret.a = (nk_byte)((in >> 24) & 0xFF); + return ret; +} +NK_API struct nk_color +nk_rgba_f(float r, float g, float b, float a) +{ + struct nk_color ret; + ret.r = (nk_byte)(NK_SATURATE(r) * 255.0f); + ret.g = (nk_byte)(NK_SATURATE(g) * 255.0f); + ret.b = (nk_byte)(NK_SATURATE(b) * 255.0f); + ret.a = (nk_byte)(NK_SATURATE(a) * 255.0f); + return ret; +} +NK_API struct nk_color +nk_rgba_fv(const float *c) +{ + return nk_rgba_f(c[0], c[1], c[2], c[3]); +} +NK_API struct nk_color +nk_rgba_cf(struct nk_colorf c) +{ + return nk_rgba_f(c.r, c.g, c.b, c.a); +} +NK_API struct nk_color +nk_rgb_f(float r, float g, float b) +{ + struct nk_color ret; + ret.r = (nk_byte)(NK_SATURATE(r) * 255.0f); + ret.g = (nk_byte)(NK_SATURATE(g) * 255.0f); + ret.b = (nk_byte)(NK_SATURATE(b) * 255.0f); + ret.a = 255; + return ret; +} +NK_API struct nk_color +nk_rgb_fv(const float *c) +{ + return nk_rgb_f(c[0], c[1], c[2]); +} +NK_API struct nk_color +nk_rgb_cf(struct nk_colorf c) +{ + return nk_rgb_f(c.r, c.g, c.b); +} +NK_API struct nk_color +nk_hsv(int h, int s, int v) +{ + return nk_hsva(h, s, v, 255); +} +NK_API struct nk_color +nk_hsv_iv(const int *c) +{ + return nk_hsv(c[0], c[1], c[2]); +} +NK_API struct nk_color +nk_hsv_bv(const nk_byte *c) +{ + return nk_hsv(c[0], c[1], c[2]); +} +NK_API struct nk_color +nk_hsv_f(float h, float s, float v) +{ + return nk_hsva_f(h, s, v, 1.0f); +} +NK_API struct nk_color +nk_hsv_fv(const float *c) +{ + return nk_hsv_f(c[0], c[1], c[2]); +} +NK_API struct nk_color +nk_hsva(int h, int s, int v, int a) +{ + float hf = ((float)NK_CLAMP(0, h, 255)) / 255.0f; + float sf = ((float)NK_CLAMP(0, s, 255)) / 255.0f; + float vf = ((float)NK_CLAMP(0, v, 255)) / 255.0f; + float af = ((float)NK_CLAMP(0, a, 255)) / 255.0f; + return nk_hsva_f(hf, sf, vf, af); +} +NK_API struct nk_color +nk_hsva_iv(const int *c) +{ + return nk_hsva(c[0], c[1], c[2], c[3]); +} +NK_API struct nk_color +nk_hsva_bv(const nk_byte *c) +{ + return nk_hsva(c[0], c[1], c[2], c[3]); +} +NK_API struct nk_colorf +nk_hsva_colorf(float h, float s, float v, float a) +{ + int i; + float p, q, t, f; + struct nk_colorf out = {0,0,0,0}; + if (s <= 0.0f) { + out.r = v; out.g = v; out.b = v; out.a = a; + return out; + } + h = h / (60.0f/360.0f); + i = (int)h; + f = h - (float)i; + p = v * (1.0f - s); + q = v * (1.0f - (s * f)); + t = v * (1.0f - s * (1.0f - f)); + + switch (i) { + case 0: default: out.r = v; out.g = t; out.b = p; break; + case 1: out.r = q; out.g = v; out.b = p; break; + case 2: out.r = p; out.g = v; out.b = t; break; + case 3: out.r = p; out.g = q; out.b = v; break; + case 4: out.r = t; out.g = p; out.b = v; break; + case 5: out.r = v; out.g = p; out.b = q; break;} + out.a = a; + return out; +} +NK_API struct nk_colorf +nk_hsva_colorfv(float *c) +{ + return nk_hsva_colorf(c[0], c[1], c[2], c[3]); +} +NK_API struct nk_color +nk_hsva_f(float h, float s, float v, float a) +{ + struct nk_colorf c = nk_hsva_colorf(h, s, v, a); + return nk_rgba_f(c.r, c.g, c.b, c.a); +} +NK_API struct nk_color +nk_hsva_fv(const float *c) +{ + return nk_hsva_f(c[0], c[1], c[2], c[3]); +} +NK_API nk_uint +nk_color_u32(struct nk_color in) +{ + nk_uint out = (nk_uint)in.r; + out |= ((nk_uint)in.g << 8); + out |= ((nk_uint)in.b << 16); + out |= ((nk_uint)in.a << 24); + return out; +} +NK_API void +nk_color_f(float *r, float *g, float *b, float *a, struct nk_color in) +{ + NK_STORAGE const float s = 1.0f/255.0f; + *r = (float)in.r * s; + *g = (float)in.g * s; + *b = (float)in.b * s; + *a = (float)in.a * s; +} +NK_API void +nk_color_fv(float *c, struct nk_color in) +{ + nk_color_f(&c[0], &c[1], &c[2], &c[3], in); +} +NK_API struct nk_colorf +nk_color_cf(struct nk_color in) +{ + struct nk_colorf o; + nk_color_f(&o.r, &o.g, &o.b, &o.a, in); + return o; +} +NK_API void +nk_color_d(double *r, double *g, double *b, double *a, struct nk_color in) +{ + NK_STORAGE const double s = 1.0/255.0; + *r = (double)in.r * s; + *g = (double)in.g * s; + *b = (double)in.b * s; + *a = (double)in.a * s; +} +NK_API void +nk_color_dv(double *c, struct nk_color in) +{ + nk_color_d(&c[0], &c[1], &c[2], &c[3], in); +} +NK_API void +nk_color_hsv_f(float *out_h, float *out_s, float *out_v, struct nk_color in) +{ + float a; + nk_color_hsva_f(out_h, out_s, out_v, &a, in); +} +NK_API void +nk_color_hsv_fv(float *out, struct nk_color in) +{ + float a; + nk_color_hsva_f(&out[0], &out[1], &out[2], &a, in); +} +NK_API void +nk_colorf_hsva_f(float *out_h, float *out_s, + float *out_v, float *out_a, struct nk_colorf in) +{ + float chroma; + float K = 0.0f; + if (in.g < in.b) { + const float t = in.g; in.g = in.b; in.b = t; + K = -1.f; + } + if (in.r < in.g) { + const float t = in.r; in.r = in.g; in.g = t; + K = -2.f/6.0f - K; + } + chroma = in.r - ((in.g < in.b) ? in.g: in.b); + *out_h = NK_ABS(K + (in.g - in.b)/(6.0f * chroma + 1e-20f)); + *out_s = chroma / (in.r + 1e-20f); + *out_v = in.r; + *out_a = in.a; + +} +NK_API void +nk_colorf_hsva_fv(float *hsva, struct nk_colorf in) +{ + nk_colorf_hsva_f(&hsva[0], &hsva[1], &hsva[2], &hsva[3], in); +} +NK_API void +nk_color_hsva_f(float *out_h, float *out_s, + float *out_v, float *out_a, struct nk_color in) +{ + struct nk_colorf col; + nk_color_f(&col.r,&col.g,&col.b,&col.a, in); + nk_colorf_hsva_f(out_h, out_s, out_v, out_a, col); +} +NK_API void +nk_color_hsva_fv(float *out, struct nk_color in) +{ + nk_color_hsva_f(&out[0], &out[1], &out[2], &out[3], in); +} +NK_API void +nk_color_hsva_i(int *out_h, int *out_s, int *out_v, + int *out_a, struct nk_color in) +{ + float h,s,v,a; + nk_color_hsva_f(&h, &s, &v, &a, in); + *out_h = (nk_byte)(h * 255.0f); + *out_s = (nk_byte)(s * 255.0f); + *out_v = (nk_byte)(v * 255.0f); + *out_a = (nk_byte)(a * 255.0f); +} +NK_API void +nk_color_hsva_iv(int *out, struct nk_color in) +{ + nk_color_hsva_i(&out[0], &out[1], &out[2], &out[3], in); +} +NK_API void +nk_color_hsva_bv(nk_byte *out, struct nk_color in) +{ + int tmp[4]; + nk_color_hsva_i(&tmp[0], &tmp[1], &tmp[2], &tmp[3], in); + out[0] = (nk_byte)tmp[0]; + out[1] = (nk_byte)tmp[1]; + out[2] = (nk_byte)tmp[2]; + out[3] = (nk_byte)tmp[3]; +} +NK_API void +nk_color_hsva_b(nk_byte *h, nk_byte *s, nk_byte *v, nk_byte *a, struct nk_color in) +{ + int tmp[4]; + nk_color_hsva_i(&tmp[0], &tmp[1], &tmp[2], &tmp[3], in); + *h = (nk_byte)tmp[0]; + *s = (nk_byte)tmp[1]; + *v = (nk_byte)tmp[2]; + *a = (nk_byte)tmp[3]; +} +NK_API void +nk_color_hsv_i(int *out_h, int *out_s, int *out_v, struct nk_color in) +{ + int a; + nk_color_hsva_i(out_h, out_s, out_v, &a, in); +} +NK_API void +nk_color_hsv_b(nk_byte *out_h, nk_byte *out_s, nk_byte *out_v, struct nk_color in) +{ + int tmp[4]; + nk_color_hsva_i(&tmp[0], &tmp[1], &tmp[2], &tmp[3], in); + *out_h = (nk_byte)tmp[0]; + *out_s = (nk_byte)tmp[1]; + *out_v = (nk_byte)tmp[2]; +} +NK_API void +nk_color_hsv_iv(int *out, struct nk_color in) +{ + nk_color_hsv_i(&out[0], &out[1], &out[2], in); +} +NK_API void +nk_color_hsv_bv(nk_byte *out, struct nk_color in) +{ + int tmp[4]; + nk_color_hsv_i(&tmp[0], &tmp[1], &tmp[2], in); + out[0] = (nk_byte)tmp[0]; + out[1] = (nk_byte)tmp[1]; + out[2] = (nk_byte)tmp[2]; +} + + + + + +/* =============================================================== + * + * UTF-8 + * + * ===============================================================*/ +NK_GLOBAL const nk_byte nk_utfbyte[NK_UTF_SIZE+1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +NK_GLOBAL const nk_byte nk_utfmask[NK_UTF_SIZE+1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +NK_GLOBAL const nk_uint nk_utfmin[NK_UTF_SIZE+1] = {0, 0, 0x80, 0x800, 0x10000}; +NK_GLOBAL const nk_uint nk_utfmax[NK_UTF_SIZE+1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +NK_INTERN int +nk_utf_validate(nk_rune *u, int i) +{ + NK_ASSERT(u); + if (!u) return 0; + if (!NK_BETWEEN(*u, nk_utfmin[i], nk_utfmax[i]) || + NK_BETWEEN(*u, 0xD800, 0xDFFF)) + *u = NK_UTF_INVALID; + for (i = 1; *u > nk_utfmax[i]; ++i); + return i; +} +NK_INTERN nk_rune +nk_utf_decode_byte(char c, int *i) +{ + NK_ASSERT(i); + if (!i) return 0; + for(*i = 0; *i < (int)NK_LEN(nk_utfmask); ++(*i)) { + if (((nk_byte)c & nk_utfmask[*i]) == nk_utfbyte[*i]) + return (nk_byte)(c & ~nk_utfmask[*i]); + } + return 0; +} +NK_API int +nk_utf_decode(const char *c, nk_rune *u, int clen) +{ + int i, j, len, type=0; + nk_rune udecoded; + + NK_ASSERT(c); + NK_ASSERT(u); + + if (!c || !u) return 0; + if (!clen) return 0; + *u = NK_UTF_INVALID; + + udecoded = nk_utf_decode_byte(c[0], &len); + if (!NK_BETWEEN(len, 1, NK_UTF_SIZE)) + return 1; + + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | nk_utf_decode_byte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + nk_utf_validate(u, len); + return len; +} +NK_INTERN char +nk_utf_encode_byte(nk_rune u, int i) +{ + return (char)((nk_utfbyte[i]) | ((nk_byte)u & ~nk_utfmask[i])); +} +NK_API int +nk_utf_encode(nk_rune u, char *c, int clen) +{ + int len, i; + len = nk_utf_validate(&u, 0); + if (clen < len || !len || len > NK_UTF_SIZE) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = nk_utf_encode_byte(u, 0); + u >>= 6; + } + c[0] = nk_utf_encode_byte(u, len); + return len; +} +NK_API int +nk_utf_len(const char *str, int len) +{ + const char *text; + int glyphs = 0; + int text_len; + int glyph_len; + int src_len = 0; + nk_rune unicode; + + NK_ASSERT(str); + if (!str || !len) return 0; + + text = str; + text_len = len; + glyph_len = nk_utf_decode(text, &unicode, text_len); + while (glyph_len && src_len < len) { + glyphs++; + src_len = src_len + glyph_len; + glyph_len = nk_utf_decode(text + src_len, &unicode, text_len - src_len); + } + return glyphs; +} +NK_API const char* +nk_utf_at(const char *buffer, int length, int index, + nk_rune *unicode, int *len) +{ + int i = 0; + int src_len = 0; + int glyph_len = 0; + const char *text; + int text_len; + + NK_ASSERT(buffer); + NK_ASSERT(unicode); + NK_ASSERT(len); + + if (!buffer || !unicode || !len) return 0; + if (index < 0) { + *unicode = NK_UTF_INVALID; + *len = 0; + return 0; + } + + text = buffer; + text_len = length; + glyph_len = nk_utf_decode(text, unicode, text_len); + while (glyph_len) { + if (i == index) { + *len = glyph_len; + break; + } + + i++; + src_len = src_len + glyph_len; + glyph_len = nk_utf_decode(text + src_len, unicode, text_len - src_len); + } + if (i != index) return 0; + return buffer + src_len; +} + + + + + +/* ============================================================== + * + * BUFFER + * + * ===============================================================*/ +#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR +NK_LIB void* +nk_malloc(nk_handle unused, void *old,nk_size size) +{ + NK_UNUSED(unused); + NK_UNUSED(old); + return malloc(size); +} +NK_LIB void +nk_mfree(nk_handle unused, void *ptr) +{ + NK_UNUSED(unused); + free(ptr); +} +NK_API void +nk_buffer_init_default(struct nk_buffer *buffer) +{ + struct nk_allocator alloc; + alloc.userdata.ptr = 0; + alloc.alloc = nk_malloc; + alloc.free = nk_mfree; + nk_buffer_init(buffer, &alloc, NK_BUFFER_DEFAULT_INITIAL_SIZE); +} +#endif + +NK_API void +nk_buffer_init(struct nk_buffer *b, const struct nk_allocator *a, + nk_size initial_size) +{ + NK_ASSERT(b); + NK_ASSERT(a); + NK_ASSERT(initial_size); + if (!b || !a || !initial_size) return; + + nk_zero(b, sizeof(*b)); + b->type = NK_BUFFER_DYNAMIC; + b->memory.ptr = a->alloc(a->userdata,0, initial_size); + b->memory.size = initial_size; + b->size = initial_size; + b->grow_factor = 2.0f; + b->pool = *a; +} +NK_API void +nk_buffer_init_fixed(struct nk_buffer *b, void *m, nk_size size) +{ + NK_ASSERT(b); + NK_ASSERT(m); + NK_ASSERT(size); + if (!b || !m || !size) return; + + nk_zero(b, sizeof(*b)); + b->type = NK_BUFFER_FIXED; + b->memory.ptr = m; + b->memory.size = size; + b->size = size; +} +NK_LIB void* +nk_buffer_align(void *unaligned, + nk_size align, nk_size *alignment, + enum nk_buffer_allocation_type type) +{ + void *memory = 0; + switch (type) { + default: + case NK_BUFFER_MAX: + case NK_BUFFER_FRONT: + if (align) { + memory = NK_ALIGN_PTR(unaligned, align); + *alignment = (nk_size)((nk_byte*)memory - (nk_byte*)unaligned); + } else { + memory = unaligned; + *alignment = 0; + } + break; + case NK_BUFFER_BACK: + if (align) { + memory = NK_ALIGN_PTR_BACK(unaligned, align); + *alignment = (nk_size)((nk_byte*)unaligned - (nk_byte*)memory); + } else { + memory = unaligned; + *alignment = 0; + } + break; + } + return memory; +} +NK_LIB void* +nk_buffer_realloc(struct nk_buffer *b, nk_size capacity, nk_size *size) +{ + void *temp; + nk_size buffer_size; + + NK_ASSERT(b); + NK_ASSERT(size); + if (!b || !size || !b->pool.alloc || !b->pool.free) + return 0; + + buffer_size = b->memory.size; + temp = b->pool.alloc(b->pool.userdata, b->memory.ptr, capacity); + NK_ASSERT(temp); + if (!temp) return 0; + + *size = capacity; + if (temp != b->memory.ptr) { + NK_MEMCPY(temp, b->memory.ptr, buffer_size); + b->pool.free(b->pool.userdata, b->memory.ptr); + } + + if (b->size == buffer_size) { + /* no back buffer so just set correct size */ + b->size = capacity; + return temp; + } else { + /* copy back buffer to the end of the new buffer */ + void *dst, *src; + nk_size back_size; + back_size = buffer_size - b->size; + dst = nk_ptr_add(void, temp, capacity - back_size); + src = nk_ptr_add(void, temp, b->size); + NK_MEMCPY(dst, src, back_size); + b->size = capacity - back_size; + } + return temp; +} +NK_LIB void* +nk_buffer_alloc(struct nk_buffer *b, enum nk_buffer_allocation_type type, + nk_size size, nk_size align) +{ + int full; + nk_size alignment; + void *unaligned; + void *memory; + + NK_ASSERT(b); + NK_ASSERT(size); + if (!b || !size) return 0; + b->needed += size; + + /* calculate total size with needed alignment + size */ + if (type == NK_BUFFER_FRONT) + unaligned = nk_ptr_add(void, b->memory.ptr, b->allocated); + else unaligned = nk_ptr_add(void, b->memory.ptr, b->size - size); + memory = nk_buffer_align(unaligned, align, &alignment, type); + + /* check if buffer has enough memory*/ + if (type == NK_BUFFER_FRONT) + full = ((b->allocated + size + alignment) > b->size); + else full = ((b->size - NK_MIN(b->size,(size + alignment))) <= b->allocated); + + if (full) { + nk_size capacity; + if (b->type != NK_BUFFER_DYNAMIC) + return 0; + NK_ASSERT(b->pool.alloc && b->pool.free); + if (b->type != NK_BUFFER_DYNAMIC || !b->pool.alloc || !b->pool.free) + return 0; + + /* buffer is full so allocate bigger buffer if dynamic */ + capacity = (nk_size)((float)b->memory.size * b->grow_factor); + capacity = NK_MAX(capacity, nk_round_up_pow2((nk_uint)(b->allocated + size))); + b->memory.ptr = nk_buffer_realloc(b, capacity, &b->memory.size); + if (!b->memory.ptr) return 0; + + /* align newly allocated pointer */ + if (type == NK_BUFFER_FRONT) + unaligned = nk_ptr_add(void, b->memory.ptr, b->allocated); + else unaligned = nk_ptr_add(void, b->memory.ptr, b->size - size); + memory = nk_buffer_align(unaligned, align, &alignment, type); + } + if (type == NK_BUFFER_FRONT) + b->allocated += size + alignment; + else b->size -= (size + alignment); + b->needed += alignment; + b->calls++; + return memory; +} +NK_API void +nk_buffer_push(struct nk_buffer *b, enum nk_buffer_allocation_type type, + const void *memory, nk_size size, nk_size align) +{ + void *mem = nk_buffer_alloc(b, type, size, align); + if (!mem) return; + NK_MEMCPY(mem, memory, size); +} +NK_API void +nk_buffer_mark(struct nk_buffer *buffer, enum nk_buffer_allocation_type type) +{ + NK_ASSERT(buffer); + if (!buffer) return; + buffer->marker[type].active = nk_true; + if (type == NK_BUFFER_BACK) + buffer->marker[type].offset = buffer->size; + else buffer->marker[type].offset = buffer->allocated; +} +NK_API void +nk_buffer_reset(struct nk_buffer *buffer, enum nk_buffer_allocation_type type) +{ + NK_ASSERT(buffer); + if (!buffer) return; + if (type == NK_BUFFER_BACK) { + /* reset back buffer either back to marker or empty */ + buffer->needed -= (buffer->memory.size - buffer->marker[type].offset); + if (buffer->marker[type].active) + buffer->size = buffer->marker[type].offset; + else buffer->size = buffer->memory.size; + buffer->marker[type].active = nk_false; + } else { + /* reset front buffer either back to back marker or empty */ + buffer->needed -= (buffer->allocated - buffer->marker[type].offset); + if (buffer->marker[type].active) + buffer->allocated = buffer->marker[type].offset; + else buffer->allocated = 0; + buffer->marker[type].active = nk_false; + } +} +NK_API void +nk_buffer_clear(struct nk_buffer *b) +{ + NK_ASSERT(b); + if (!b) return; + b->allocated = 0; + b->size = b->memory.size; + b->calls = 0; + b->needed = 0; +} +NK_API void +nk_buffer_free(struct nk_buffer *b) +{ + NK_ASSERT(b); + if (!b || !b->memory.ptr) return; + if (b->type == NK_BUFFER_FIXED) return; + if (!b->pool.free) return; + NK_ASSERT(b->pool.free); + b->pool.free(b->pool.userdata, b->memory.ptr); +} +NK_API void +nk_buffer_info(struct nk_memory_status *s, struct nk_buffer *b) +{ + NK_ASSERT(b); + NK_ASSERT(s); + if (!s || !b) return; + s->allocated = b->allocated; + s->size = b->memory.size; + s->needed = b->needed; + s->memory = b->memory.ptr; + s->calls = b->calls; +} +NK_API void* +nk_buffer_memory(struct nk_buffer *buffer) +{ + NK_ASSERT(buffer); + if (!buffer) return 0; + return buffer->memory.ptr; +} +NK_API const void* +nk_buffer_memory_const(const struct nk_buffer *buffer) +{ + NK_ASSERT(buffer); + if (!buffer) return 0; + return buffer->memory.ptr; +} +NK_API nk_size +nk_buffer_total(struct nk_buffer *buffer) +{ + NK_ASSERT(buffer); + if (!buffer) return 0; + return buffer->memory.size; +} + + + + + +/* =============================================================== + * + * STRING + * + * ===============================================================*/ +#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR +NK_API void +nk_str_init_default(struct nk_str *str) +{ + struct nk_allocator alloc; + alloc.userdata.ptr = 0; + alloc.alloc = nk_malloc; + alloc.free = nk_mfree; + nk_buffer_init(&str->buffer, &alloc, 32); + str->len = 0; +} +#endif + +NK_API void +nk_str_init(struct nk_str *str, const struct nk_allocator *alloc, nk_size size) +{ + nk_buffer_init(&str->buffer, alloc, size); + str->len = 0; +} +NK_API void +nk_str_init_fixed(struct nk_str *str, void *memory, nk_size size) +{ + nk_buffer_init_fixed(&str->buffer, memory, size); + str->len = 0; +} +NK_API int +nk_str_append_text_char(struct nk_str *s, const char *str, int len) +{ + char *mem; + NK_ASSERT(s); + NK_ASSERT(str); + if (!s || !str || !len) return 0; + mem = (char*)nk_buffer_alloc(&s->buffer, NK_BUFFER_FRONT, (nk_size)len * sizeof(char), 0); + if (!mem) return 0; + NK_MEMCPY(mem, str, (nk_size)len * sizeof(char)); + s->len += nk_utf_len(str, len); + return len; +} +NK_API int +nk_str_append_str_char(struct nk_str *s, const char *str) +{ + return nk_str_append_text_char(s, str, nk_strlen(str)); +} +NK_API int +nk_str_append_text_utf8(struct nk_str *str, const char *text, int len) +{ + int i = 0; + int byte_len = 0; + nk_rune unicode; + if (!str || !text || !len) return 0; + for (i = 0; i < len; ++i) + byte_len += nk_utf_decode(text+byte_len, &unicode, 4); + nk_str_append_text_char(str, text, byte_len); + return len; +} +NK_API int +nk_str_append_str_utf8(struct nk_str *str, const char *text) +{ + int runes = 0; + int byte_len = 0; + int num_runes = 0; + int glyph_len = 0; + nk_rune unicode; + if (!str || !text) return 0; + + glyph_len = byte_len = nk_utf_decode(text+byte_len, &unicode, 4); + while (unicode != '\0' && glyph_len) { + glyph_len = nk_utf_decode(text+byte_len, &unicode, 4); + byte_len += glyph_len; + num_runes++; + } + nk_str_append_text_char(str, text, byte_len); + return runes; +} +NK_API int +nk_str_append_text_runes(struct nk_str *str, const nk_rune *text, int len) +{ + int i = 0; + int byte_len = 0; + nk_glyph glyph; + + NK_ASSERT(str); + if (!str || !text || !len) return 0; + for (i = 0; i < len; ++i) { + byte_len = nk_utf_encode(text[i], glyph, NK_UTF_SIZE); + if (!byte_len) break; + nk_str_append_text_char(str, glyph, byte_len); + } + return len; +} +NK_API int +nk_str_append_str_runes(struct nk_str *str, const nk_rune *runes) +{ + int i = 0; + nk_glyph glyph; + int byte_len; + NK_ASSERT(str); + if (!str || !runes) return 0; + while (runes[i] != '\0') { + byte_len = nk_utf_encode(runes[i], glyph, NK_UTF_SIZE); + nk_str_append_text_char(str, glyph, byte_len); + i++; + } + return i; +} +NK_API int +nk_str_insert_at_char(struct nk_str *s, int pos, const char *str, int len) +{ + int i; + void *mem; + char *src; + char *dst; + + int copylen; + NK_ASSERT(s); + NK_ASSERT(str); + NK_ASSERT(len >= 0); + if (!s || !str || !len || (nk_size)pos > s->buffer.allocated) return 0; + if ((s->buffer.allocated + (nk_size)len >= s->buffer.memory.size) && + (s->buffer.type == NK_BUFFER_FIXED)) return 0; + + copylen = (int)s->buffer.allocated - pos; + if (!copylen) { + nk_str_append_text_char(s, str, len); + return 1; + } + mem = nk_buffer_alloc(&s->buffer, NK_BUFFER_FRONT, (nk_size)len * sizeof(char), 0); + if (!mem) return 0; + + /* memmove */ + NK_ASSERT(((int)pos + (int)len + ((int)copylen - 1)) >= 0); + NK_ASSERT(((int)pos + ((int)copylen - 1)) >= 0); + dst = nk_ptr_add(char, s->buffer.memory.ptr, pos + len + (copylen - 1)); + src = nk_ptr_add(char, s->buffer.memory.ptr, pos + (copylen-1)); + for (i = 0; i < copylen; ++i) *dst-- = *src--; + mem = nk_ptr_add(void, s->buffer.memory.ptr, pos); + NK_MEMCPY(mem, str, (nk_size)len * sizeof(char)); + s->len = nk_utf_len((char *)s->buffer.memory.ptr, (int)s->buffer.allocated); + return 1; +} +NK_API int +nk_str_insert_at_rune(struct nk_str *str, int pos, const char *cstr, int len) +{ + int glyph_len; + nk_rune unicode; + const char *begin; + const char *buffer; + + NK_ASSERT(str); + NK_ASSERT(cstr); + NK_ASSERT(len); + if (!str || !cstr || !len) return 0; + begin = nk_str_at_rune(str, pos, &unicode, &glyph_len); + if (!str->len) + return nk_str_append_text_char(str, cstr, len); + buffer = nk_str_get_const(str); + if (!begin) return 0; + return nk_str_insert_at_char(str, (int)(begin - buffer), cstr, len); +} +NK_API int +nk_str_insert_text_char(struct nk_str *str, int pos, const char *text, int len) +{ + return nk_str_insert_text_utf8(str, pos, text, len); +} +NK_API int +nk_str_insert_str_char(struct nk_str *str, int pos, const char *text) +{ + return nk_str_insert_text_utf8(str, pos, text, nk_strlen(text)); +} +NK_API int +nk_str_insert_text_utf8(struct nk_str *str, int pos, const char *text, int len) +{ + int i = 0; + int byte_len = 0; + nk_rune unicode; + + NK_ASSERT(str); + NK_ASSERT(text); + if (!str || !text || !len) return 0; + for (i = 0; i < len; ++i) + byte_len += nk_utf_decode(text+byte_len, &unicode, 4); + nk_str_insert_at_rune(str, pos, text, byte_len); + return len; +} +NK_API int +nk_str_insert_str_utf8(struct nk_str *str, int pos, const char *text) +{ + int runes = 0; + int byte_len = 0; + int num_runes = 0; + int glyph_len = 0; + nk_rune unicode; + if (!str || !text) return 0; + + glyph_len = byte_len = nk_utf_decode(text+byte_len, &unicode, 4); + while (unicode != '\0' && glyph_len) { + glyph_len = nk_utf_decode(text+byte_len, &unicode, 4); + byte_len += glyph_len; + num_runes++; + } + nk_str_insert_at_rune(str, pos, text, byte_len); + return runes; +} +NK_API int +nk_str_insert_text_runes(struct nk_str *str, int pos, const nk_rune *runes, int len) +{ + int i = 0; + int byte_len = 0; + nk_glyph glyph; + + NK_ASSERT(str); + if (!str || !runes || !len) return 0; + for (i = 0; i < len; ++i) { + byte_len = nk_utf_encode(runes[i], glyph, NK_UTF_SIZE); + if (!byte_len) break; + nk_str_insert_at_rune(str, pos+i, glyph, byte_len); + } + return len; +} +NK_API int +nk_str_insert_str_runes(struct nk_str *str, int pos, const nk_rune *runes) +{ + int i = 0; + nk_glyph glyph; + int byte_len; + NK_ASSERT(str); + if (!str || !runes) return 0; + while (runes[i] != '\0') { + byte_len = nk_utf_encode(runes[i], glyph, NK_UTF_SIZE); + nk_str_insert_at_rune(str, pos+i, glyph, byte_len); + i++; + } + return i; +} +NK_API void +nk_str_remove_chars(struct nk_str *s, int len) +{ + NK_ASSERT(s); + NK_ASSERT(len >= 0); + if (!s || len < 0 || (nk_size)len > s->buffer.allocated) return; + NK_ASSERT(((int)s->buffer.allocated - (int)len) >= 0); + s->buffer.allocated -= (nk_size)len; + s->len = nk_utf_len((char *)s->buffer.memory.ptr, (int)s->buffer.allocated); +} +NK_API void +nk_str_remove_runes(struct nk_str *str, int len) +{ + int index; + const char *begin; + const char *end; + nk_rune unicode; + + NK_ASSERT(str); + NK_ASSERT(len >= 0); + if (!str || len < 0) return; + if (len >= str->len) { + str->len = 0; + return; + } + + index = str->len - len; + begin = nk_str_at_rune(str, index, &unicode, &len); + end = (const char*)str->buffer.memory.ptr + str->buffer.allocated; + nk_str_remove_chars(str, (int)(end-begin)+1); +} +NK_API void +nk_str_delete_chars(struct nk_str *s, int pos, int len) +{ + NK_ASSERT(s); + if (!s || !len || (nk_size)pos > s->buffer.allocated || + (nk_size)(pos + len) > s->buffer.allocated) return; + + if ((nk_size)(pos + len) < s->buffer.allocated) { + /* memmove */ + char *dst = nk_ptr_add(char, s->buffer.memory.ptr, pos); + char *src = nk_ptr_add(char, s->buffer.memory.ptr, pos + len); + NK_MEMCPY(dst, src, s->buffer.allocated - (nk_size)(pos + len)); + NK_ASSERT(((int)s->buffer.allocated - (int)len) >= 0); + s->buffer.allocated -= (nk_size)len; + } else nk_str_remove_chars(s, len); + s->len = nk_utf_len((char *)s->buffer.memory.ptr, (int)s->buffer.allocated); +} +NK_API void +nk_str_delete_runes(struct nk_str *s, int pos, int len) +{ + char *temp; + nk_rune unicode; + char *begin; + char *end; + int unused; + + NK_ASSERT(s); + NK_ASSERT(s->len >= pos + len); + if (s->len < pos + len) + len = NK_CLAMP(0, (s->len - pos), s->len); + if (!len) return; + + temp = (char *)s->buffer.memory.ptr; + begin = nk_str_at_rune(s, pos, &unicode, &unused); + if (!begin) return; + s->buffer.memory.ptr = begin; + end = nk_str_at_rune(s, len, &unicode, &unused); + s->buffer.memory.ptr = temp; + if (!end) return; + nk_str_delete_chars(s, (int)(begin - temp), (int)(end - begin)); +} +NK_API char* +nk_str_at_char(struct nk_str *s, int pos) +{ + NK_ASSERT(s); + if (!s || pos > (int)s->buffer.allocated) return 0; + return nk_ptr_add(char, s->buffer.memory.ptr, pos); +} +NK_API char* +nk_str_at_rune(struct nk_str *str, int pos, nk_rune *unicode, int *len) +{ + int i = 0; + int src_len = 0; + int glyph_len = 0; + char *text; + int text_len; + + NK_ASSERT(str); + NK_ASSERT(unicode); + NK_ASSERT(len); + + if (!str || !unicode || !len) return 0; + if (pos < 0) { + *unicode = 0; + *len = 0; + return 0; + } + + text = (char*)str->buffer.memory.ptr; + text_len = (int)str->buffer.allocated; + glyph_len = nk_utf_decode(text, unicode, text_len); + while (glyph_len) { + if (i == pos) { + *len = glyph_len; + break; + } + + i++; + src_len = src_len + glyph_len; + glyph_len = nk_utf_decode(text + src_len, unicode, text_len - src_len); + } + if (i != pos) return 0; + return text + src_len; +} +NK_API const char* +nk_str_at_char_const(const struct nk_str *s, int pos) +{ + NK_ASSERT(s); + if (!s || pos > (int)s->buffer.allocated) return 0; + return nk_ptr_add(char, s->buffer.memory.ptr, pos); +} +NK_API const char* +nk_str_at_const(const struct nk_str *str, int pos, nk_rune *unicode, int *len) +{ + int i = 0; + int src_len = 0; + int glyph_len = 0; + char *text; + int text_len; + + NK_ASSERT(str); + NK_ASSERT(unicode); + NK_ASSERT(len); + + if (!str || !unicode || !len) return 0; + if (pos < 0) { + *unicode = 0; + *len = 0; + return 0; + } + + text = (char*)str->buffer.memory.ptr; + text_len = (int)str->buffer.allocated; + glyph_len = nk_utf_decode(text, unicode, text_len); + while (glyph_len) { + if (i == pos) { + *len = glyph_len; + break; + } + + i++; + src_len = src_len + glyph_len; + glyph_len = nk_utf_decode(text + src_len, unicode, text_len - src_len); + } + if (i != pos) return 0; + return text + src_len; +} +NK_API nk_rune +nk_str_rune_at(const struct nk_str *str, int pos) +{ + int len; + nk_rune unicode = 0; + nk_str_at_const(str, pos, &unicode, &len); + return unicode; +} +NK_API char* +nk_str_get(struct nk_str *s) +{ + NK_ASSERT(s); + if (!s || !s->len || !s->buffer.allocated) return 0; + return (char*)s->buffer.memory.ptr; +} +NK_API const char* +nk_str_get_const(const struct nk_str *s) +{ + NK_ASSERT(s); + if (!s || !s->len || !s->buffer.allocated) return 0; + return (const char*)s->buffer.memory.ptr; +} +NK_API int +nk_str_len(struct nk_str *s) +{ + NK_ASSERT(s); + if (!s || !s->len || !s->buffer.allocated) return 0; + return s->len; +} +NK_API int +nk_str_len_char(struct nk_str *s) +{ + NK_ASSERT(s); + if (!s || !s->len || !s->buffer.allocated) return 0; + return (int)s->buffer.allocated; +} +NK_API void +nk_str_clear(struct nk_str *str) +{ + NK_ASSERT(str); + nk_buffer_clear(&str->buffer); + str->len = 0; +} +NK_API void +nk_str_free(struct nk_str *str) +{ + NK_ASSERT(str); + nk_buffer_free(&str->buffer); + str->len = 0; +} + + + + + +/* ============================================================== + * + * DRAW + * + * ===============================================================*/ +NK_LIB void +nk_command_buffer_init(struct nk_command_buffer *cb, + struct nk_buffer *b, enum nk_command_clipping clip) +{ + NK_ASSERT(cb); + NK_ASSERT(b); + if (!cb || !b) return; + cb->base = b; + cb->use_clipping = (int)clip; + cb->begin = b->allocated; + cb->end = b->allocated; + cb->last = b->allocated; +} +NK_LIB void +nk_command_buffer_reset(struct nk_command_buffer *b) +{ + NK_ASSERT(b); + if (!b) return; + b->begin = 0; + b->end = 0; + b->last = 0; + b->clip = nk_null_rect; +#ifdef NK_INCLUDE_COMMAND_USERDATA + b->userdata.ptr = 0; +#endif +} +NK_LIB void* +nk_command_buffer_push(struct nk_command_buffer* b, + enum nk_command_type t, nk_size size) +{ + NK_STORAGE const nk_size align = NK_ALIGNOF(struct nk_command); + struct nk_command *cmd; + nk_size alignment; + void *unaligned; + void *memory; + + NK_ASSERT(b); + NK_ASSERT(b->base); + if (!b) return 0; + cmd = (struct nk_command*)nk_buffer_alloc(b->base,NK_BUFFER_FRONT,size,align); + if (!cmd) return 0; + + /* make sure the offset to the next command is aligned */ + b->last = (nk_size)((nk_byte*)cmd - (nk_byte*)b->base->memory.ptr); + unaligned = (nk_byte*)cmd + size; + memory = NK_ALIGN_PTR(unaligned, align); + alignment = (nk_size)((nk_byte*)memory - (nk_byte*)unaligned); +#ifdef NK_ZERO_COMMAND_MEMORY + NK_MEMSET(cmd, 0, size + alignment); +#endif + + cmd->type = t; + cmd->next = b->base->allocated + alignment; +#ifdef NK_INCLUDE_COMMAND_USERDATA + cmd->userdata = b->userdata; +#endif + b->end = cmd->next; + return cmd; +} +NK_API void +nk_push_scissor(struct nk_command_buffer *b, struct nk_rect r) +{ + struct nk_command_scissor *cmd; + NK_ASSERT(b); + if (!b) return; + + b->clip.x = r.x; + b->clip.y = r.y; + b->clip.w = r.w; + b->clip.h = r.h; + cmd = (struct nk_command_scissor*) + nk_command_buffer_push(b, NK_COMMAND_SCISSOR, sizeof(*cmd)); + + if (!cmd) return; + cmd->x = (short)r.x; + cmd->y = (short)r.y; + cmd->w = (unsigned short)NK_MAX(0, r.w); + cmd->h = (unsigned short)NK_MAX(0, r.h); +} +NK_API void +nk_stroke_line(struct nk_command_buffer *b, float x0, float y0, + float x1, float y1, float line_thickness, struct nk_color c) +{ + struct nk_command_line *cmd; + NK_ASSERT(b); + if (!b || line_thickness <= 0) return; + cmd = (struct nk_command_line*) + nk_command_buffer_push(b, NK_COMMAND_LINE, sizeof(*cmd)); + if (!cmd) return; + cmd->line_thickness = (unsigned short)line_thickness; + cmd->begin.x = (short)x0; + cmd->begin.y = (short)y0; + cmd->end.x = (short)x1; + cmd->end.y = (short)y1; + cmd->color = c; +} +NK_API void +nk_stroke_curve(struct nk_command_buffer *b, float ax, float ay, + float ctrl0x, float ctrl0y, float ctrl1x, float ctrl1y, + float bx, float by, float line_thickness, struct nk_color col) +{ + struct nk_command_curve *cmd; + NK_ASSERT(b); + if (!b || col.a == 0 || line_thickness <= 0) return; + + cmd = (struct nk_command_curve*) + nk_command_buffer_push(b, NK_COMMAND_CURVE, sizeof(*cmd)); + if (!cmd) return; + cmd->line_thickness = (unsigned short)line_thickness; + cmd->begin.x = (short)ax; + cmd->begin.y = (short)ay; + cmd->ctrl[0].x = (short)ctrl0x; + cmd->ctrl[0].y = (short)ctrl0y; + cmd->ctrl[1].x = (short)ctrl1x; + cmd->ctrl[1].y = (short)ctrl1y; + cmd->end.x = (short)bx; + cmd->end.y = (short)by; + cmd->color = col; +} +NK_API void +nk_stroke_rect(struct nk_command_buffer *b, struct nk_rect rect, + float rounding, float line_thickness, struct nk_color c) +{ + struct nk_command_rect *cmd; + NK_ASSERT(b); + if (!b || c.a == 0 || rect.w == 0 || rect.h == 0 || line_thickness <= 0) return; + if (b->use_clipping) { + const struct nk_rect *clip = &b->clip; + if (!NK_INTERSECT(rect.x, rect.y, rect.w, rect.h, + clip->x, clip->y, clip->w, clip->h)) return; + } + cmd = (struct nk_command_rect*) + nk_command_buffer_push(b, NK_COMMAND_RECT, sizeof(*cmd)); + if (!cmd) return; + cmd->rounding = (unsigned short)rounding; + cmd->line_thickness = (unsigned short)line_thickness; + cmd->x = (short)rect.x; + cmd->y = (short)rect.y; + cmd->w = (unsigned short)NK_MAX(0, rect.w); + cmd->h = (unsigned short)NK_MAX(0, rect.h); + cmd->color = c; +} +NK_API void +nk_fill_rect(struct nk_command_buffer *b, struct nk_rect rect, + float rounding, struct nk_color c) +{ + struct nk_command_rect_filled *cmd; + NK_ASSERT(b); + if (!b || c.a == 0 || rect.w == 0 || rect.h == 0) return; + if (b->use_clipping) { + const struct nk_rect *clip = &b->clip; + if (!NK_INTERSECT(rect.x, rect.y, rect.w, rect.h, + clip->x, clip->y, clip->w, clip->h)) return; + } + + cmd = (struct nk_command_rect_filled*) + nk_command_buffer_push(b, NK_COMMAND_RECT_FILLED, sizeof(*cmd)); + if (!cmd) return; + cmd->rounding = (unsigned short)rounding; + cmd->x = (short)rect.x; + cmd->y = (short)rect.y; + cmd->w = (unsigned short)NK_MAX(0, rect.w); + cmd->h = (unsigned short)NK_MAX(0, rect.h); + cmd->color = c; +} +NK_API void +nk_fill_rect_multi_color(struct nk_command_buffer *b, struct nk_rect rect, + struct nk_color left, struct nk_color top, struct nk_color right, + struct nk_color bottom) +{ + struct nk_command_rect_multi_color *cmd; + NK_ASSERT(b); + if (!b || rect.w == 0 || rect.h == 0) return; + if (b->use_clipping) { + const struct nk_rect *clip = &b->clip; + if (!NK_INTERSECT(rect.x, rect.y, rect.w, rect.h, + clip->x, clip->y, clip->w, clip->h)) return; + } + + cmd = (struct nk_command_rect_multi_color*) + nk_command_buffer_push(b, NK_COMMAND_RECT_MULTI_COLOR, sizeof(*cmd)); + if (!cmd) return; + cmd->x = (short)rect.x; + cmd->y = (short)rect.y; + cmd->w = (unsigned short)NK_MAX(0, rect.w); + cmd->h = (unsigned short)NK_MAX(0, rect.h); + cmd->left = left; + cmd->top = top; + cmd->right = right; + cmd->bottom = bottom; +} +NK_API void +nk_stroke_circle(struct nk_command_buffer *b, struct nk_rect r, + float line_thickness, struct nk_color c) +{ + struct nk_command_circle *cmd; + if (!b || r.w == 0 || r.h == 0 || line_thickness <= 0) return; + if (b->use_clipping) { + const struct nk_rect *clip = &b->clip; + if (!NK_INTERSECT(r.x, r.y, r.w, r.h, clip->x, clip->y, clip->w, clip->h)) + return; + } + + cmd = (struct nk_command_circle*) + nk_command_buffer_push(b, NK_COMMAND_CIRCLE, sizeof(*cmd)); + if (!cmd) return; + cmd->line_thickness = (unsigned short)line_thickness; + cmd->x = (short)r.x; + cmd->y = (short)r.y; + cmd->w = (unsigned short)NK_MAX(r.w, 0); + cmd->h = (unsigned short)NK_MAX(r.h, 0); + cmd->color = c; +} +NK_API void +nk_fill_circle(struct nk_command_buffer *b, struct nk_rect r, struct nk_color c) +{ + struct nk_command_circle_filled *cmd; + NK_ASSERT(b); + if (!b || c.a == 0 || r.w == 0 || r.h == 0) return; + if (b->use_clipping) { + const struct nk_rect *clip = &b->clip; + if (!NK_INTERSECT(r.x, r.y, r.w, r.h, clip->x, clip->y, clip->w, clip->h)) + return; + } + + cmd = (struct nk_command_circle_filled*) + nk_command_buffer_push(b, NK_COMMAND_CIRCLE_FILLED, sizeof(*cmd)); + if (!cmd) return; + cmd->x = (short)r.x; + cmd->y = (short)r.y; + cmd->w = (unsigned short)NK_MAX(r.w, 0); + cmd->h = (unsigned short)NK_MAX(r.h, 0); + cmd->color = c; +} +NK_API void +nk_stroke_arc(struct nk_command_buffer *b, float cx, float cy, float radius, + float a_min, float a_max, float line_thickness, struct nk_color c) +{ + struct nk_command_arc *cmd; + if (!b || c.a == 0 || line_thickness <= 0) return; + cmd = (struct nk_command_arc*) + nk_command_buffer_push(b, NK_COMMAND_ARC, sizeof(*cmd)); + if (!cmd) return; + cmd->line_thickness = (unsigned short)line_thickness; + cmd->cx = (short)cx; + cmd->cy = (short)cy; + cmd->r = (unsigned short)radius; + cmd->a[0] = a_min; + cmd->a[1] = a_max; + cmd->color = c; +} +NK_API void +nk_fill_arc(struct nk_command_buffer *b, float cx, float cy, float radius, + float a_min, float a_max, struct nk_color c) +{ + struct nk_command_arc_filled *cmd; + NK_ASSERT(b); + if (!b || c.a == 0) return; + cmd = (struct nk_command_arc_filled*) + nk_command_buffer_push(b, NK_COMMAND_ARC_FILLED, sizeof(*cmd)); + if (!cmd) return; + cmd->cx = (short)cx; + cmd->cy = (short)cy; + cmd->r = (unsigned short)radius; + cmd->a[0] = a_min; + cmd->a[1] = a_max; + cmd->color = c; +} +NK_API void +nk_stroke_triangle(struct nk_command_buffer *b, float x0, float y0, float x1, + float y1, float x2, float y2, float line_thickness, struct nk_color c) +{ + struct nk_command_triangle *cmd; + NK_ASSERT(b); + if (!b || c.a == 0 || line_thickness <= 0) return; + if (b->use_clipping) { + const struct nk_rect *clip = &b->clip; + if (!NK_INBOX(x0, y0, clip->x, clip->y, clip->w, clip->h) && + !NK_INBOX(x1, y1, clip->x, clip->y, clip->w, clip->h) && + !NK_INBOX(x2, y2, clip->x, clip->y, clip->w, clip->h)) + return; + } + + cmd = (struct nk_command_triangle*) + nk_command_buffer_push(b, NK_COMMAND_TRIANGLE, sizeof(*cmd)); + if (!cmd) return; + cmd->line_thickness = (unsigned short)line_thickness; + cmd->a.x = (short)x0; + cmd->a.y = (short)y0; + cmd->b.x = (short)x1; + cmd->b.y = (short)y1; + cmd->c.x = (short)x2; + cmd->c.y = (short)y2; + cmd->color = c; +} +NK_API void +nk_fill_triangle(struct nk_command_buffer *b, float x0, float y0, float x1, + float y1, float x2, float y2, struct nk_color c) +{ + struct nk_command_triangle_filled *cmd; + NK_ASSERT(b); + if (!b || c.a == 0) return; + if (!b) return; + if (b->use_clipping) { + const struct nk_rect *clip = &b->clip; + if (!NK_INBOX(x0, y0, clip->x, clip->y, clip->w, clip->h) && + !NK_INBOX(x1, y1, clip->x, clip->y, clip->w, clip->h) && + !NK_INBOX(x2, y2, clip->x, clip->y, clip->w, clip->h)) + return; + } + + cmd = (struct nk_command_triangle_filled*) + nk_command_buffer_push(b, NK_COMMAND_TRIANGLE_FILLED, sizeof(*cmd)); + if (!cmd) return; + cmd->a.x = (short)x0; + cmd->a.y = (short)y0; + cmd->b.x = (short)x1; + cmd->b.y = (short)y1; + cmd->c.x = (short)x2; + cmd->c.y = (short)y2; + cmd->color = c; +} +NK_API void +nk_stroke_polygon(struct nk_command_buffer *b, float *points, int point_count, + float line_thickness, struct nk_color col) +{ + int i; + nk_size size = 0; + struct nk_command_polygon *cmd; + + NK_ASSERT(b); + if (!b || col.a == 0 || line_thickness <= 0) return; + size = sizeof(*cmd) + sizeof(short) * 2 * (nk_size)point_count; + cmd = (struct nk_command_polygon*) nk_command_buffer_push(b, NK_COMMAND_POLYGON, size); + if (!cmd) return; + cmd->color = col; + cmd->line_thickness = (unsigned short)line_thickness; + cmd->point_count = (unsigned short)point_count; + for (i = 0; i < point_count; ++i) { + cmd->points[i].x = (short)points[i*2]; + cmd->points[i].y = (short)points[i*2+1]; + } +} +NK_API void +nk_fill_polygon(struct nk_command_buffer *b, float *points, int point_count, + struct nk_color col) +{ + int i; + nk_size size = 0; + struct nk_command_polygon_filled *cmd; + + NK_ASSERT(b); + if (!b || col.a == 0) return; + size = sizeof(*cmd) + sizeof(short) * 2 * (nk_size)point_count; + cmd = (struct nk_command_polygon_filled*) + nk_command_buffer_push(b, NK_COMMAND_POLYGON_FILLED, size); + if (!cmd) return; + cmd->color = col; + cmd->point_count = (unsigned short)point_count; + for (i = 0; i < point_count; ++i) { + cmd->points[i].x = (short)points[i*2+0]; + cmd->points[i].y = (short)points[i*2+1]; + } +} +NK_API void +nk_stroke_polyline(struct nk_command_buffer *b, float *points, int point_count, + float line_thickness, struct nk_color col) +{ + int i; + nk_size size = 0; + struct nk_command_polyline *cmd; + + NK_ASSERT(b); + if (!b || col.a == 0 || line_thickness <= 0) return; + size = sizeof(*cmd) + sizeof(short) * 2 * (nk_size)point_count; + cmd = (struct nk_command_polyline*) nk_command_buffer_push(b, NK_COMMAND_POLYLINE, size); + if (!cmd) return; + cmd->color = col; + cmd->point_count = (unsigned short)point_count; + cmd->line_thickness = (unsigned short)line_thickness; + for (i = 0; i < point_count; ++i) { + cmd->points[i].x = (short)points[i*2]; + cmd->points[i].y = (short)points[i*2+1]; + } +} +NK_API void +nk_draw_image(struct nk_command_buffer *b, struct nk_rect r, + const struct nk_image *img, struct nk_color col) +{ + struct nk_command_image *cmd; + NK_ASSERT(b); + if (!b) return; + if (b->use_clipping) { + const struct nk_rect *c = &b->clip; + if (c->w == 0 || c->h == 0 || !NK_INTERSECT(r.x, r.y, r.w, r.h, c->x, c->y, c->w, c->h)) + return; + } + + cmd = (struct nk_command_image*) + nk_command_buffer_push(b, NK_COMMAND_IMAGE, sizeof(*cmd)); + if (!cmd) return; + cmd->x = (short)r.x; + cmd->y = (short)r.y; + cmd->w = (unsigned short)NK_MAX(0, r.w); + cmd->h = (unsigned short)NK_MAX(0, r.h); + cmd->img = *img; + cmd->col = col; +} +NK_API void +nk_push_custom(struct nk_command_buffer *b, struct nk_rect r, + nk_command_custom_callback cb, nk_handle usr) +{ + struct nk_command_custom *cmd; + NK_ASSERT(b); + if (!b) return; + if (b->use_clipping) { + const struct nk_rect *c = &b->clip; + if (c->w == 0 || c->h == 0 || !NK_INTERSECT(r.x, r.y, r.w, r.h, c->x, c->y, c->w, c->h)) + return; + } + + cmd = (struct nk_command_custom*) + nk_command_buffer_push(b, NK_COMMAND_CUSTOM, sizeof(*cmd)); + if (!cmd) return; + cmd->x = (short)r.x; + cmd->y = (short)r.y; + cmd->w = (unsigned short)NK_MAX(0, r.w); + cmd->h = (unsigned short)NK_MAX(0, r.h); + cmd->callback_data = usr; + cmd->callback = cb; +} +NK_API void +nk_draw_text(struct nk_command_buffer *b, struct nk_rect r, + const char *string, int length, const struct nk_user_font *font, + struct nk_color bg, struct nk_color fg) +{ + float text_width = 0; + struct nk_command_text *cmd; + + NK_ASSERT(b); + NK_ASSERT(font); + if (!b || !string || !length || (bg.a == 0 && fg.a == 0)) return; + if (b->use_clipping) { + const struct nk_rect *c = &b->clip; + if (c->w == 0 || c->h == 0 || !NK_INTERSECT(r.x, r.y, r.w, r.h, c->x, c->y, c->w, c->h)) + return; + } + + /* make sure text fits inside bounds */ + text_width = font->width(font->userdata, font->height, string, length); + if (text_width > r.w){ + int glyphs = 0; + float txt_width = (float)text_width; + length = nk_text_clamp(font, string, length, r.w, &glyphs, &txt_width, 0,0); + } + + if (!length) return; + cmd = (struct nk_command_text*) + nk_command_buffer_push(b, NK_COMMAND_TEXT, sizeof(*cmd) + (nk_size)(length + 1)); + if (!cmd) return; + cmd->x = (short)r.x; + cmd->y = (short)r.y; + cmd->w = (unsigned short)r.w; + cmd->h = (unsigned short)r.h; + cmd->background = bg; + cmd->foreground = fg; + cmd->font = font; + cmd->length = length; + cmd->height = font->height; + NK_MEMCPY(cmd->string, string, (nk_size)length); + cmd->string[length] = '\0'; +} + + + + + +/* =============================================================== + * + * VERTEX + * + * ===============================================================*/ +#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT +NK_API void +nk_draw_list_init(struct nk_draw_list *list) +{ + nk_size i = 0; + NK_ASSERT(list); + if (!list) return; + nk_zero(list, sizeof(*list)); + for (i = 0; i < NK_LEN(list->circle_vtx); ++i) { + const float a = ((float)i / (float)NK_LEN(list->circle_vtx)) * 2 * NK_PI; + list->circle_vtx[i].x = (float)NK_COS(a); + list->circle_vtx[i].y = (float)NK_SIN(a); + } +} +NK_API void +nk_draw_list_setup(struct nk_draw_list *canvas, const struct nk_convert_config *config, + struct nk_buffer *cmds, struct nk_buffer *vertices, struct nk_buffer *elements, + enum nk_anti_aliasing line_aa, enum nk_anti_aliasing shape_aa) +{ + NK_ASSERT(canvas); + NK_ASSERT(config); + NK_ASSERT(cmds); + NK_ASSERT(vertices); + NK_ASSERT(elements); + if (!canvas || !config || !cmds || !vertices || !elements) + return; + + canvas->buffer = cmds; + canvas->config = *config; + canvas->elements = elements; + canvas->vertices = vertices; + canvas->line_AA = line_aa; + canvas->shape_AA = shape_aa; + canvas->clip_rect = nk_null_rect; + + canvas->cmd_offset = 0; + canvas->element_count = 0; + canvas->vertex_count = 0; + canvas->cmd_offset = 0; + canvas->cmd_count = 0; + canvas->path_count = 0; +} +NK_API const struct nk_draw_command* +nk__draw_list_begin(const struct nk_draw_list *canvas, const struct nk_buffer *buffer) +{ + nk_byte *memory; + nk_size offset; + const struct nk_draw_command *cmd; + + NK_ASSERT(buffer); + if (!buffer || !buffer->size || !canvas->cmd_count) + return 0; + + memory = (nk_byte*)buffer->memory.ptr; + offset = buffer->memory.size - canvas->cmd_offset; + cmd = nk_ptr_add(const struct nk_draw_command, memory, offset); + return cmd; +} +NK_API const struct nk_draw_command* +nk__draw_list_end(const struct nk_draw_list *canvas, const struct nk_buffer *buffer) +{ + nk_size size; + nk_size offset; + nk_byte *memory; + const struct nk_draw_command *end; + + NK_ASSERT(buffer); + NK_ASSERT(canvas); + if (!buffer || !canvas) + return 0; + + memory = (nk_byte*)buffer->memory.ptr; + size = buffer->memory.size; + offset = size - canvas->cmd_offset; + end = nk_ptr_add(const struct nk_draw_command, memory, offset); + end -= (canvas->cmd_count-1); + return end; +} +NK_API const struct nk_draw_command* +nk__draw_list_next(const struct nk_draw_command *cmd, + const struct nk_buffer *buffer, const struct nk_draw_list *canvas) +{ + const struct nk_draw_command *end; + NK_ASSERT(buffer); + NK_ASSERT(canvas); + if (!cmd || !buffer || !canvas) + return 0; + + end = nk__draw_list_end(canvas, buffer); + if (cmd <= end) return 0; + return (cmd-1); +} +NK_INTERN struct nk_vec2* +nk_draw_list_alloc_path(struct nk_draw_list *list, int count) +{ + struct nk_vec2 *points; + NK_STORAGE const nk_size point_align = NK_ALIGNOF(struct nk_vec2); + NK_STORAGE const nk_size point_size = sizeof(struct nk_vec2); + points = (struct nk_vec2*) + nk_buffer_alloc(list->buffer, NK_BUFFER_FRONT, + point_size * (nk_size)count, point_align); + + if (!points) return 0; + if (!list->path_offset) { + void *memory = nk_buffer_memory(list->buffer); + list->path_offset = (unsigned int)((nk_byte*)points - (nk_byte*)memory); + } + list->path_count += (unsigned int)count; + return points; +} +NK_INTERN struct nk_vec2 +nk_draw_list_path_last(struct nk_draw_list *list) +{ + void *memory; + struct nk_vec2 *point; + NK_ASSERT(list->path_count); + memory = nk_buffer_memory(list->buffer); + point = nk_ptr_add(struct nk_vec2, memory, list->path_offset); + point += (list->path_count-1); + return *point; +} +NK_INTERN struct nk_draw_command* +nk_draw_list_push_command(struct nk_draw_list *list, struct nk_rect clip, + nk_handle texture) +{ + NK_STORAGE const nk_size cmd_align = NK_ALIGNOF(struct nk_draw_command); + NK_STORAGE const nk_size cmd_size = sizeof(struct nk_draw_command); + struct nk_draw_command *cmd; + + NK_ASSERT(list); + cmd = (struct nk_draw_command*) + nk_buffer_alloc(list->buffer, NK_BUFFER_BACK, cmd_size, cmd_align); + + if (!cmd) return 0; + if (!list->cmd_count) { + nk_byte *memory = (nk_byte*)nk_buffer_memory(list->buffer); + nk_size total = nk_buffer_total(list->buffer); + memory = nk_ptr_add(nk_byte, memory, total); + list->cmd_offset = (nk_size)(memory - (nk_byte*)cmd); + } + + cmd->elem_count = 0; + cmd->clip_rect = clip; + cmd->texture = texture; +#ifdef NK_INCLUDE_COMMAND_USERDATA + cmd->userdata = list->userdata; +#endif + + list->cmd_count++; + list->clip_rect = clip; + return cmd; +} +NK_INTERN struct nk_draw_command* +nk_draw_list_command_last(struct nk_draw_list *list) +{ + void *memory; + nk_size size; + struct nk_draw_command *cmd; + NK_ASSERT(list->cmd_count); + + memory = nk_buffer_memory(list->buffer); + size = nk_buffer_total(list->buffer); + cmd = nk_ptr_add(struct nk_draw_command, memory, size - list->cmd_offset); + return (cmd - (list->cmd_count-1)); +} +NK_INTERN void +nk_draw_list_add_clip(struct nk_draw_list *list, struct nk_rect rect) +{ + NK_ASSERT(list); + if (!list) return; + if (!list->cmd_count) { + nk_draw_list_push_command(list, rect, list->config.null.texture); + } else { + struct nk_draw_command *prev = nk_draw_list_command_last(list); + if (prev->elem_count == 0) + prev->clip_rect = rect; + nk_draw_list_push_command(list, rect, prev->texture); + } +} +NK_INTERN void +nk_draw_list_push_image(struct nk_draw_list *list, nk_handle texture) +{ + NK_ASSERT(list); + if (!list) return; + if (!list->cmd_count) { + nk_draw_list_push_command(list, nk_null_rect, texture); + } else { + struct nk_draw_command *prev = nk_draw_list_command_last(list); + if (prev->elem_count == 0) { + prev->texture = texture; + #ifdef NK_INCLUDE_COMMAND_USERDATA + prev->userdata = list->userdata; + #endif + } else if (prev->texture.id != texture.id + #ifdef NK_INCLUDE_COMMAND_USERDATA + || prev->userdata.id != list->userdata.id + #endif + ) nk_draw_list_push_command(list, prev->clip_rect, texture); + } +} +#ifdef NK_INCLUDE_COMMAND_USERDATA +NK_API void +nk_draw_list_push_userdata(struct nk_draw_list *list, nk_handle userdata) +{ + list->userdata = userdata; +} +#endif +NK_INTERN void* +nk_draw_list_alloc_vertices(struct nk_draw_list *list, nk_size count) +{ + void *vtx; + NK_ASSERT(list); + if (!list) return 0; + vtx = nk_buffer_alloc(list->vertices, NK_BUFFER_FRONT, + list->config.vertex_size*count, list->config.vertex_alignment); + if (!vtx) return 0; + list->vertex_count += (unsigned int)count; + + /* This assert triggers because your are drawing a lot of stuff and nuklear + * defined `nk_draw_index` as `nk_ushort` to safe space be default. + * + * So you reached the maximum number of indicies or rather vertexes. + * To solve this issue please change typdef `nk_draw_index` to `nk_uint` + * and don't forget to specify the new element size in your drawing + * backend (OpenGL, DirectX, ...). For example in OpenGL for `glDrawElements` + * instead of specifing `GL_UNSIGNED_SHORT` you have to define `GL_UNSIGNED_INT`. + * Sorry for the inconvenience. */ + if(sizeof(nk_draw_index)==2) NK_ASSERT((list->vertex_count < NK_USHORT_MAX && + "To many verticies for 16-bit vertex indicies. Please read comment above on how to solve this problem")); + return vtx; +} +NK_INTERN nk_draw_index* +nk_draw_list_alloc_elements(struct nk_draw_list *list, nk_size count) +{ + nk_draw_index *ids; + struct nk_draw_command *cmd; + NK_STORAGE const nk_size elem_align = NK_ALIGNOF(nk_draw_index); + NK_STORAGE const nk_size elem_size = sizeof(nk_draw_index); + NK_ASSERT(list); + if (!list) return 0; + + ids = (nk_draw_index*) + nk_buffer_alloc(list->elements, NK_BUFFER_FRONT, elem_size*count, elem_align); + if (!ids) return 0; + cmd = nk_draw_list_command_last(list); + list->element_count += (unsigned int)count; + cmd->elem_count += (unsigned int)count; + return ids; +} +NK_INTERN int +nk_draw_vertex_layout_element_is_end_of_layout( + const struct nk_draw_vertex_layout_element *element) +{ + return (element->attribute == NK_VERTEX_ATTRIBUTE_COUNT || + element->format == NK_FORMAT_COUNT); +} +NK_INTERN void +nk_draw_vertex_color(void *attr, const float *vals, + enum nk_draw_vertex_layout_format format) +{ + /* if this triggers you tried to provide a value format for a color */ + float val[4]; + NK_ASSERT(format >= NK_FORMAT_COLOR_BEGIN); + NK_ASSERT(format <= NK_FORMAT_COLOR_END); + if (format < NK_FORMAT_COLOR_BEGIN || format > NK_FORMAT_COLOR_END) return; + + val[0] = NK_SATURATE(vals[0]); + val[1] = NK_SATURATE(vals[1]); + val[2] = NK_SATURATE(vals[2]); + val[3] = NK_SATURATE(vals[3]); + + switch (format) { + default: NK_ASSERT(0 && "Invalid vertex layout color format"); break; + case NK_FORMAT_R8G8B8A8: + case NK_FORMAT_R8G8B8: { + struct nk_color col = nk_rgba_fv(val); + NK_MEMCPY(attr, &col.r, sizeof(col)); + } break; + case NK_FORMAT_B8G8R8A8: { + struct nk_color col = nk_rgba_fv(val); + struct nk_color bgra = nk_rgba(col.b, col.g, col.r, col.a); + NK_MEMCPY(attr, &bgra, sizeof(bgra)); + } break; + case NK_FORMAT_R16G15B16: { + nk_ushort col[3]; + col[0] = (nk_ushort)(val[0]*(float)NK_USHORT_MAX); + col[1] = (nk_ushort)(val[1]*(float)NK_USHORT_MAX); + col[2] = (nk_ushort)(val[2]*(float)NK_USHORT_MAX); + NK_MEMCPY(attr, col, sizeof(col)); + } break; + case NK_FORMAT_R16G15B16A16: { + nk_ushort col[4]; + col[0] = (nk_ushort)(val[0]*(float)NK_USHORT_MAX); + col[1] = (nk_ushort)(val[1]*(float)NK_USHORT_MAX); + col[2] = (nk_ushort)(val[2]*(float)NK_USHORT_MAX); + col[3] = (nk_ushort)(val[3]*(float)NK_USHORT_MAX); + NK_MEMCPY(attr, col, sizeof(col)); + } break; + case NK_FORMAT_R32G32B32: { + nk_uint col[3]; + col[0] = (nk_uint)(val[0]*(float)NK_UINT_MAX); + col[1] = (nk_uint)(val[1]*(float)NK_UINT_MAX); + col[2] = (nk_uint)(val[2]*(float)NK_UINT_MAX); + NK_MEMCPY(attr, col, sizeof(col)); + } break; + case NK_FORMAT_R32G32B32A32: { + nk_uint col[4]; + col[0] = (nk_uint)(val[0]*(float)NK_UINT_MAX); + col[1] = (nk_uint)(val[1]*(float)NK_UINT_MAX); + col[2] = (nk_uint)(val[2]*(float)NK_UINT_MAX); + col[3] = (nk_uint)(val[3]*(float)NK_UINT_MAX); + NK_MEMCPY(attr, col, sizeof(col)); + } break; + case NK_FORMAT_R32G32B32A32_FLOAT: + NK_MEMCPY(attr, val, sizeof(float)*4); + break; + case NK_FORMAT_R32G32B32A32_DOUBLE: { + double col[4]; + col[0] = (double)val[0]; + col[1] = (double)val[1]; + col[2] = (double)val[2]; + col[3] = (double)val[3]; + NK_MEMCPY(attr, col, sizeof(col)); + } break; + case NK_FORMAT_RGB32: + case NK_FORMAT_RGBA32: { + struct nk_color col = nk_rgba_fv(val); + nk_uint color = nk_color_u32(col); + NK_MEMCPY(attr, &color, sizeof(color)); + } break; } +} +NK_INTERN void +nk_draw_vertex_element(void *dst, const float *values, int value_count, + enum nk_draw_vertex_layout_format format) +{ + int value_index; + void *attribute = dst; + /* if this triggers you tried to provide a color format for a value */ + NK_ASSERT(format < NK_FORMAT_COLOR_BEGIN); + if (format >= NK_FORMAT_COLOR_BEGIN && format <= NK_FORMAT_COLOR_END) return; + for (value_index = 0; value_index < value_count; ++value_index) { + switch (format) { + default: NK_ASSERT(0 && "invalid vertex layout format"); break; + case NK_FORMAT_SCHAR: { + char value = (char)NK_CLAMP((float)NK_SCHAR_MIN, values[value_index], (float)NK_SCHAR_MAX); + NK_MEMCPY(attribute, &value, sizeof(value)); + attribute = (void*)((char*)attribute + sizeof(char)); + } break; + case NK_FORMAT_SSHORT: { + nk_short value = (nk_short)NK_CLAMP((float)NK_SSHORT_MIN, values[value_index], (float)NK_SSHORT_MAX); + NK_MEMCPY(attribute, &value, sizeof(value)); + attribute = (void*)((char*)attribute + sizeof(value)); + } break; + case NK_FORMAT_SINT: { + nk_int value = (nk_int)NK_CLAMP((float)NK_SINT_MIN, values[value_index], (float)NK_SINT_MAX); + NK_MEMCPY(attribute, &value, sizeof(value)); + attribute = (void*)((char*)attribute + sizeof(nk_int)); + } break; + case NK_FORMAT_UCHAR: { + unsigned char value = (unsigned char)NK_CLAMP((float)NK_UCHAR_MIN, values[value_index], (float)NK_UCHAR_MAX); + NK_MEMCPY(attribute, &value, sizeof(value)); + attribute = (void*)((char*)attribute + sizeof(unsigned char)); + } break; + case NK_FORMAT_USHORT: { + nk_ushort value = (nk_ushort)NK_CLAMP((float)NK_USHORT_MIN, values[value_index], (float)NK_USHORT_MAX); + NK_MEMCPY(attribute, &value, sizeof(value)); + attribute = (void*)((char*)attribute + sizeof(value)); + } break; + case NK_FORMAT_UINT: { + nk_uint value = (nk_uint)NK_CLAMP((float)NK_UINT_MIN, values[value_index], (float)NK_UINT_MAX); + NK_MEMCPY(attribute, &value, sizeof(value)); + attribute = (void*)((char*)attribute + sizeof(nk_uint)); + } break; + case NK_FORMAT_FLOAT: + NK_MEMCPY(attribute, &values[value_index], sizeof(values[value_index])); + attribute = (void*)((char*)attribute + sizeof(float)); + break; + case NK_FORMAT_DOUBLE: { + double value = (double)values[value_index]; + NK_MEMCPY(attribute, &value, sizeof(value)); + attribute = (void*)((char*)attribute + sizeof(double)); + } break; + } + } +} +NK_INTERN void* +nk_draw_vertex(void *dst, const struct nk_convert_config *config, + struct nk_vec2 pos, struct nk_vec2 uv, struct nk_colorf color) +{ + void *result = (void*)((char*)dst + config->vertex_size); + const struct nk_draw_vertex_layout_element *elem_iter = config->vertex_layout; + while (!nk_draw_vertex_layout_element_is_end_of_layout(elem_iter)) { + void *address = (void*)((char*)dst + elem_iter->offset); + switch (elem_iter->attribute) { + case NK_VERTEX_ATTRIBUTE_COUNT: + default: NK_ASSERT(0 && "wrong element attribute"); break; + case NK_VERTEX_POSITION: nk_draw_vertex_element(address, &pos.x, 2, elem_iter->format); break; + case NK_VERTEX_TEXCOORD: nk_draw_vertex_element(address, &uv.x, 2, elem_iter->format); break; + case NK_VERTEX_COLOR: nk_draw_vertex_color(address, &color.r, elem_iter->format); break; + } + elem_iter++; + } + return result; +} +NK_API void +nk_draw_list_stroke_poly_line(struct nk_draw_list *list, const struct nk_vec2 *points, + const unsigned int points_count, struct nk_color color, enum nk_draw_list_stroke closed, + float thickness, enum nk_anti_aliasing aliasing) +{ + nk_size count; + int thick_line; + struct nk_colorf col; + struct nk_colorf col_trans; + NK_ASSERT(list); + if (!list || points_count < 2) return; + + color.a = (nk_byte)((float)color.a * list->config.global_alpha); + count = points_count; + if (!closed) count = points_count-1; + thick_line = thickness > 1.0f; + +#ifdef NK_INCLUDE_COMMAND_USERDATA + nk_draw_list_push_userdata(list, list->userdata); +#endif + + color.a = (nk_byte)((float)color.a * list->config.global_alpha); + nk_color_fv(&col.r, color); + col_trans = col; + col_trans.a = 0; + + if (aliasing == NK_ANTI_ALIASING_ON) { + /* ANTI-ALIASED STROKE */ + const float AA_SIZE = 1.0f; + NK_STORAGE const nk_size pnt_align = NK_ALIGNOF(struct nk_vec2); + NK_STORAGE const nk_size pnt_size = sizeof(struct nk_vec2); + + /* allocate vertices and elements */ + nk_size i1 = 0; + nk_size vertex_offset; + nk_size index = list->vertex_count; + + const nk_size idx_count = (thick_line) ? (count * 18) : (count * 12); + const nk_size vtx_count = (thick_line) ? (points_count * 4): (points_count *3); + + void *vtx = nk_draw_list_alloc_vertices(list, vtx_count); + nk_draw_index *ids = nk_draw_list_alloc_elements(list, idx_count); + + nk_size size; + struct nk_vec2 *normals, *temp; + if (!vtx || !ids) return; + + /* temporary allocate normals + points */ + vertex_offset = (nk_size)((nk_byte*)vtx - (nk_byte*)list->vertices->memory.ptr); + nk_buffer_mark(list->vertices, NK_BUFFER_FRONT); + size = pnt_size * ((thick_line) ? 5 : 3) * points_count; + normals = (struct nk_vec2*) nk_buffer_alloc(list->vertices, NK_BUFFER_FRONT, size, pnt_align); + if (!normals) return; + temp = normals + points_count; + + /* make sure vertex pointer is still correct */ + vtx = (void*)((nk_byte*)list->vertices->memory.ptr + vertex_offset); + + /* calculate normals */ + for (i1 = 0; i1 < count; ++i1) { + const nk_size i2 = ((i1 + 1) == points_count) ? 0 : (i1 + 1); + struct nk_vec2 diff = nk_vec2_sub(points[i2], points[i1]); + float len; + + /* vec2 inverted length */ + len = nk_vec2_len_sqr(diff); + if (len != 0.0f) + len = nk_inv_sqrt(len); + else len = 1.0f; + + diff = nk_vec2_muls(diff, len); + normals[i1].x = diff.y; + normals[i1].y = -diff.x; + } + + if (!closed) + normals[points_count-1] = normals[points_count-2]; + + if (!thick_line) { + nk_size idx1, i; + if (!closed) { + struct nk_vec2 d; + temp[0] = nk_vec2_add(points[0], nk_vec2_muls(normals[0], AA_SIZE)); + temp[1] = nk_vec2_sub(points[0], nk_vec2_muls(normals[0], AA_SIZE)); + d = nk_vec2_muls(normals[points_count-1], AA_SIZE); + temp[(points_count-1) * 2 + 0] = nk_vec2_add(points[points_count-1], d); + temp[(points_count-1) * 2 + 1] = nk_vec2_sub(points[points_count-1], d); + } + + /* fill elements */ + idx1 = index; + for (i1 = 0; i1 < count; i1++) { + struct nk_vec2 dm; + float dmr2; + nk_size i2 = ((i1 + 1) == points_count) ? 0 : (i1 + 1); + nk_size idx2 = ((i1+1) == points_count) ? index: (idx1 + 3); + + /* average normals */ + dm = nk_vec2_muls(nk_vec2_add(normals[i1], normals[i2]), 0.5f); + dmr2 = dm.x * dm.x + dm.y* dm.y; + if (dmr2 > 0.000001f) { + float scale = 1.0f/dmr2; + scale = NK_MIN(100.0f, scale); + dm = nk_vec2_muls(dm, scale); + } + + dm = nk_vec2_muls(dm, AA_SIZE); + temp[i2*2+0] = nk_vec2_add(points[i2], dm); + temp[i2*2+1] = nk_vec2_sub(points[i2], dm); + + ids[0] = (nk_draw_index)(idx2 + 0); ids[1] = (nk_draw_index)(idx1+0); + ids[2] = (nk_draw_index)(idx1 + 2); ids[3] = (nk_draw_index)(idx1+2); + ids[4] = (nk_draw_index)(idx2 + 2); ids[5] = (nk_draw_index)(idx2+0); + ids[6] = (nk_draw_index)(idx2 + 1); ids[7] = (nk_draw_index)(idx1+1); + ids[8] = (nk_draw_index)(idx1 + 0); ids[9] = (nk_draw_index)(idx1+0); + ids[10]= (nk_draw_index)(idx2 + 0); ids[11]= (nk_draw_index)(idx2+1); + ids += 12; + idx1 = idx2; + } + + /* fill vertices */ + for (i = 0; i < points_count; ++i) { + const struct nk_vec2 uv = list->config.null.uv; + vtx = nk_draw_vertex(vtx, &list->config, points[i], uv, col); + vtx = nk_draw_vertex(vtx, &list->config, temp[i*2+0], uv, col_trans); + vtx = nk_draw_vertex(vtx, &list->config, temp[i*2+1], uv, col_trans); + } + } else { + nk_size idx1, i; + const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f; + if (!closed) { + struct nk_vec2 d1 = nk_vec2_muls(normals[0], half_inner_thickness + AA_SIZE); + struct nk_vec2 d2 = nk_vec2_muls(normals[0], half_inner_thickness); + + temp[0] = nk_vec2_add(points[0], d1); + temp[1] = nk_vec2_add(points[0], d2); + temp[2] = nk_vec2_sub(points[0], d2); + temp[3] = nk_vec2_sub(points[0], d1); + + d1 = nk_vec2_muls(normals[points_count-1], half_inner_thickness + AA_SIZE); + d2 = nk_vec2_muls(normals[points_count-1], half_inner_thickness); + + temp[(points_count-1)*4+0] = nk_vec2_add(points[points_count-1], d1); + temp[(points_count-1)*4+1] = nk_vec2_add(points[points_count-1], d2); + temp[(points_count-1)*4+2] = nk_vec2_sub(points[points_count-1], d2); + temp[(points_count-1)*4+3] = nk_vec2_sub(points[points_count-1], d1); + } + + /* add all elements */ + idx1 = index; + for (i1 = 0; i1 < count; ++i1) { + struct nk_vec2 dm_out, dm_in; + const nk_size i2 = ((i1+1) == points_count) ? 0: (i1 + 1); + nk_size idx2 = ((i1+1) == points_count) ? index: (idx1 + 4); + + /* average normals */ + struct nk_vec2 dm = nk_vec2_muls(nk_vec2_add(normals[i1], normals[i2]), 0.5f); + float dmr2 = dm.x * dm.x + dm.y* dm.y; + if (dmr2 > 0.000001f) { + float scale = 1.0f/dmr2; + scale = NK_MIN(100.0f, scale); + dm = nk_vec2_muls(dm, scale); + } + + dm_out = nk_vec2_muls(dm, ((half_inner_thickness) + AA_SIZE)); + dm_in = nk_vec2_muls(dm, half_inner_thickness); + temp[i2*4+0] = nk_vec2_add(points[i2], dm_out); + temp[i2*4+1] = nk_vec2_add(points[i2], dm_in); + temp[i2*4+2] = nk_vec2_sub(points[i2], dm_in); + temp[i2*4+3] = nk_vec2_sub(points[i2], dm_out); + + /* add indexes */ + ids[0] = (nk_draw_index)(idx2 + 1); ids[1] = (nk_draw_index)(idx1+1); + ids[2] = (nk_draw_index)(idx1 + 2); ids[3] = (nk_draw_index)(idx1+2); + ids[4] = (nk_draw_index)(idx2 + 2); ids[5] = (nk_draw_index)(idx2+1); + ids[6] = (nk_draw_index)(idx2 + 1); ids[7] = (nk_draw_index)(idx1+1); + ids[8] = (nk_draw_index)(idx1 + 0); ids[9] = (nk_draw_index)(idx1+0); + ids[10]= (nk_draw_index)(idx2 + 0); ids[11] = (nk_draw_index)(idx2+1); + ids[12]= (nk_draw_index)(idx2 + 2); ids[13] = (nk_draw_index)(idx1+2); + ids[14]= (nk_draw_index)(idx1 + 3); ids[15] = (nk_draw_index)(idx1+3); + ids[16]= (nk_draw_index)(idx2 + 3); ids[17] = (nk_draw_index)(idx2+2); + ids += 18; + idx1 = idx2; + } + + /* add vertices */ + for (i = 0; i < points_count; ++i) { + const struct nk_vec2 uv = list->config.null.uv; + vtx = nk_draw_vertex(vtx, &list->config, temp[i*4+0], uv, col_trans); + vtx = nk_draw_vertex(vtx, &list->config, temp[i*4+1], uv, col); + vtx = nk_draw_vertex(vtx, &list->config, temp[i*4+2], uv, col); + vtx = nk_draw_vertex(vtx, &list->config, temp[i*4+3], uv, col_trans); + } + } + /* free temporary normals + points */ + nk_buffer_reset(list->vertices, NK_BUFFER_FRONT); + } else { + /* NON ANTI-ALIASED STROKE */ + nk_size i1 = 0; + nk_size idx = list->vertex_count; + const nk_size idx_count = count * 6; + const nk_size vtx_count = count * 4; + void *vtx = nk_draw_list_alloc_vertices(list, vtx_count); + nk_draw_index *ids = nk_draw_list_alloc_elements(list, idx_count); + if (!vtx || !ids) return; + + for (i1 = 0; i1 < count; ++i1) { + float dx, dy; + const struct nk_vec2 uv = list->config.null.uv; + const nk_size i2 = ((i1+1) == points_count) ? 0 : i1 + 1; + const struct nk_vec2 p1 = points[i1]; + const struct nk_vec2 p2 = points[i2]; + struct nk_vec2 diff = nk_vec2_sub(p2, p1); + float len; + + /* vec2 inverted length */ + len = nk_vec2_len_sqr(diff); + if (len != 0.0f) + len = nk_inv_sqrt(len); + else len = 1.0f; + diff = nk_vec2_muls(diff, len); + + /* add vertices */ + dx = diff.x * (thickness * 0.5f); + dy = diff.y * (thickness * 0.5f); + + vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(p1.x + dy, p1.y - dx), uv, col); + vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(p2.x + dy, p2.y - dx), uv, col); + vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(p2.x - dy, p2.y + dx), uv, col); + vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(p1.x - dy, p1.y + dx), uv, col); + + ids[0] = (nk_draw_index)(idx+0); ids[1] = (nk_draw_index)(idx+1); + ids[2] = (nk_draw_index)(idx+2); ids[3] = (nk_draw_index)(idx+0); + ids[4] = (nk_draw_index)(idx+2); ids[5] = (nk_draw_index)(idx+3); + + ids += 6; + idx += 4; + } + } +} +NK_API void +nk_draw_list_fill_poly_convex(struct nk_draw_list *list, + const struct nk_vec2 *points, const unsigned int points_count, + struct nk_color color, enum nk_anti_aliasing aliasing) +{ + struct nk_colorf col; + struct nk_colorf col_trans; + + NK_STORAGE const nk_size pnt_align = NK_ALIGNOF(struct nk_vec2); + NK_STORAGE const nk_size pnt_size = sizeof(struct nk_vec2); + NK_ASSERT(list); + if (!list || points_count < 3) return; + +#ifdef NK_INCLUDE_COMMAND_USERDATA + nk_draw_list_push_userdata(list, list->userdata); +#endif + + color.a = (nk_byte)((float)color.a * list->config.global_alpha); + nk_color_fv(&col.r, color); + col_trans = col; + col_trans.a = 0; + + if (aliasing == NK_ANTI_ALIASING_ON) { + nk_size i = 0; + nk_size i0 = 0; + nk_size i1 = 0; + + const float AA_SIZE = 1.0f; + nk_size vertex_offset = 0; + nk_size index = list->vertex_count; + + const nk_size idx_count = (points_count-2)*3 + points_count*6; + const nk_size vtx_count = (points_count*2); + + void *vtx = nk_draw_list_alloc_vertices(list, vtx_count); + nk_draw_index *ids = nk_draw_list_alloc_elements(list, idx_count); + + nk_size size = 0; + struct nk_vec2 *normals = 0; + unsigned int vtx_inner_idx = (unsigned int)(index + 0); + unsigned int vtx_outer_idx = (unsigned int)(index + 1); + if (!vtx || !ids) return; + + /* temporary allocate normals */ + vertex_offset = (nk_size)((nk_byte*)vtx - (nk_byte*)list->vertices->memory.ptr); + nk_buffer_mark(list->vertices, NK_BUFFER_FRONT); + size = pnt_size * points_count; + normals = (struct nk_vec2*) nk_buffer_alloc(list->vertices, NK_BUFFER_FRONT, size, pnt_align); + if (!normals) return; + vtx = (void*)((nk_byte*)list->vertices->memory.ptr + vertex_offset); + + /* add elements */ + for (i = 2; i < points_count; i++) { + ids[0] = (nk_draw_index)(vtx_inner_idx); + ids[1] = (nk_draw_index)(vtx_inner_idx + ((i-1) << 1)); + ids[2] = (nk_draw_index)(vtx_inner_idx + (i << 1)); + ids += 3; + } + + /* compute normals */ + for (i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) { + struct nk_vec2 p0 = points[i0]; + struct nk_vec2 p1 = points[i1]; + struct nk_vec2 diff = nk_vec2_sub(p1, p0); + + /* vec2 inverted length */ + float len = nk_vec2_len_sqr(diff); + if (len != 0.0f) + len = nk_inv_sqrt(len); + else len = 1.0f; + diff = nk_vec2_muls(diff, len); + + normals[i0].x = diff.y; + normals[i0].y = -diff.x; + } + + /* add vertices + indexes */ + for (i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) { + const struct nk_vec2 uv = list->config.null.uv; + struct nk_vec2 n0 = normals[i0]; + struct nk_vec2 n1 = normals[i1]; + struct nk_vec2 dm = nk_vec2_muls(nk_vec2_add(n0, n1), 0.5f); + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) { + float scale = 1.0f / dmr2; + scale = NK_MIN(scale, 100.0f); + dm = nk_vec2_muls(dm, scale); + } + dm = nk_vec2_muls(dm, AA_SIZE * 0.5f); + + /* add vertices */ + vtx = nk_draw_vertex(vtx, &list->config, nk_vec2_sub(points[i1], dm), uv, col); + vtx = nk_draw_vertex(vtx, &list->config, nk_vec2_add(points[i1], dm), uv, col_trans); + + /* add indexes */ + ids[0] = (nk_draw_index)(vtx_inner_idx+(i1<<1)); + ids[1] = (nk_draw_index)(vtx_inner_idx+(i0<<1)); + ids[2] = (nk_draw_index)(vtx_outer_idx+(i0<<1)); + ids[3] = (nk_draw_index)(vtx_outer_idx+(i0<<1)); + ids[4] = (nk_draw_index)(vtx_outer_idx+(i1<<1)); + ids[5] = (nk_draw_index)(vtx_inner_idx+(i1<<1)); + ids += 6; + } + /* free temporary normals + points */ + nk_buffer_reset(list->vertices, NK_BUFFER_FRONT); + } else { + nk_size i = 0; + nk_size index = list->vertex_count; + const nk_size idx_count = (points_count-2)*3; + const nk_size vtx_count = points_count; + void *vtx = nk_draw_list_alloc_vertices(list, vtx_count); + nk_draw_index *ids = nk_draw_list_alloc_elements(list, idx_count); + + if (!vtx || !ids) return; + for (i = 0; i < vtx_count; ++i) + vtx = nk_draw_vertex(vtx, &list->config, points[i], list->config.null.uv, col); + for (i = 2; i < points_count; ++i) { + ids[0] = (nk_draw_index)index; + ids[1] = (nk_draw_index)(index+ i - 1); + ids[2] = (nk_draw_index)(index+i); + ids += 3; + } + } +} +NK_API void +nk_draw_list_path_clear(struct nk_draw_list *list) +{ + NK_ASSERT(list); + if (!list) return; + nk_buffer_reset(list->buffer, NK_BUFFER_FRONT); + list->path_count = 0; + list->path_offset = 0; +} +NK_API void +nk_draw_list_path_line_to(struct nk_draw_list *list, struct nk_vec2 pos) +{ + struct nk_vec2 *points = 0; + struct nk_draw_command *cmd = 0; + NK_ASSERT(list); + if (!list) return; + if (!list->cmd_count) + nk_draw_list_add_clip(list, nk_null_rect); + + cmd = nk_draw_list_command_last(list); + if (cmd && cmd->texture.ptr != list->config.null.texture.ptr) + nk_draw_list_push_image(list, list->config.null.texture); + + points = nk_draw_list_alloc_path(list, 1); + if (!points) return; + points[0] = pos; +} +NK_API void +nk_draw_list_path_arc_to_fast(struct nk_draw_list *list, struct nk_vec2 center, + float radius, int a_min, int a_max) +{ + int a = 0; + NK_ASSERT(list); + if (!list) return; + if (a_min <= a_max) { + for (a = a_min; a <= a_max; a++) { + const struct nk_vec2 c = list->circle_vtx[(nk_size)a % NK_LEN(list->circle_vtx)]; + const float x = center.x + c.x * radius; + const float y = center.y + c.y * radius; + nk_draw_list_path_line_to(list, nk_vec2(x, y)); + } + } +} +NK_API void +nk_draw_list_path_arc_to(struct nk_draw_list *list, struct nk_vec2 center, + float radius, float a_min, float a_max, unsigned int segments) +{ + unsigned int i = 0; + NK_ASSERT(list); + if (!list) return; + if (radius == 0.0f) return; + + /* This algorithm for arc drawing relies on these two trigonometric identities[1]: + sin(a + b) = sin(a) * cos(b) + cos(a) * sin(b) + cos(a + b) = cos(a) * cos(b) - sin(a) * sin(b) + + Two coordinates (x, y) of a point on a circle centered on + the origin can be written in polar form as: + x = r * cos(a) + y = r * sin(a) + where r is the radius of the circle, + a is the angle between (x, y) and the origin. + + This allows us to rotate the coordinates around the + origin by an angle b using the following transformation: + x' = r * cos(a + b) = x * cos(b) - y * sin(b) + y' = r * sin(a + b) = y * cos(b) + x * sin(b) + + [1] https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Angle_sum_and_difference_identities + */ + {const float d_angle = (a_max - a_min) / (float)segments; + const float sin_d = (float)NK_SIN(d_angle); + const float cos_d = (float)NK_COS(d_angle); + + float cx = (float)NK_COS(a_min) * radius; + float cy = (float)NK_SIN(a_min) * radius; + for(i = 0; i <= segments; ++i) { + float new_cx, new_cy; + const float x = center.x + cx; + const float y = center.y + cy; + nk_draw_list_path_line_to(list, nk_vec2(x, y)); + + new_cx = cx * cos_d - cy * sin_d; + new_cy = cy * cos_d + cx * sin_d; + cx = new_cx; + cy = new_cy; + }} +} +NK_API void +nk_draw_list_path_rect_to(struct nk_draw_list *list, struct nk_vec2 a, + struct nk_vec2 b, float rounding) +{ + float r; + NK_ASSERT(list); + if (!list) return; + r = rounding; + r = NK_MIN(r, ((b.x-a.x) < 0) ? -(b.x-a.x): (b.x-a.x)); + r = NK_MIN(r, ((b.y-a.y) < 0) ? -(b.y-a.y): (b.y-a.y)); + + if (r == 0.0f) { + nk_draw_list_path_line_to(list, a); + nk_draw_list_path_line_to(list, nk_vec2(b.x,a.y)); + nk_draw_list_path_line_to(list, b); + nk_draw_list_path_line_to(list, nk_vec2(a.x,b.y)); + } else { + nk_draw_list_path_arc_to_fast(list, nk_vec2(a.x + r, a.y + r), r, 6, 9); + nk_draw_list_path_arc_to_fast(list, nk_vec2(b.x - r, a.y + r), r, 9, 12); + nk_draw_list_path_arc_to_fast(list, nk_vec2(b.x - r, b.y - r), r, 0, 3); + nk_draw_list_path_arc_to_fast(list, nk_vec2(a.x + r, b.y - r), r, 3, 6); + } +} +NK_API void +nk_draw_list_path_curve_to(struct nk_draw_list *list, struct nk_vec2 p2, + struct nk_vec2 p3, struct nk_vec2 p4, unsigned int num_segments) +{ + float t_step; + unsigned int i_step; + struct nk_vec2 p1; + + NK_ASSERT(list); + NK_ASSERT(list->path_count); + if (!list || !list->path_count) return; + num_segments = NK_MAX(num_segments, 1); + + p1 = nk_draw_list_path_last(list); + t_step = 1.0f/(float)num_segments; + for (i_step = 1; i_step <= num_segments; ++i_step) { + float t = t_step * (float)i_step; + float u = 1.0f - t; + float w1 = u*u*u; + float w2 = 3*u*u*t; + float w3 = 3*u*t*t; + float w4 = t * t *t; + float x = w1 * p1.x + w2 * p2.x + w3 * p3.x + w4 * p4.x; + float y = w1 * p1.y + w2 * p2.y + w3 * p3.y + w4 * p4.y; + nk_draw_list_path_line_to(list, nk_vec2(x,y)); + } +} +NK_API void +nk_draw_list_path_fill(struct nk_draw_list *list, struct nk_color color) +{ + struct nk_vec2 *points; + NK_ASSERT(list); + if (!list) return; + points = (struct nk_vec2*)nk_buffer_memory(list->buffer); + nk_draw_list_fill_poly_convex(list, points, list->path_count, color, list->config.shape_AA); + nk_draw_list_path_clear(list); +} +NK_API void +nk_draw_list_path_stroke(struct nk_draw_list *list, struct nk_color color, + enum nk_draw_list_stroke closed, float thickness) +{ + struct nk_vec2 *points; + NK_ASSERT(list); + if (!list) return; + points = (struct nk_vec2*)nk_buffer_memory(list->buffer); + nk_draw_list_stroke_poly_line(list, points, list->path_count, color, + closed, thickness, list->config.line_AA); + nk_draw_list_path_clear(list); +} +NK_API void +nk_draw_list_stroke_line(struct nk_draw_list *list, struct nk_vec2 a, + struct nk_vec2 b, struct nk_color col, float thickness) +{ + NK_ASSERT(list); + if (!list || !col.a) return; + if (list->line_AA == NK_ANTI_ALIASING_ON) { + nk_draw_list_path_line_to(list, a); + nk_draw_list_path_line_to(list, b); + } else { + nk_draw_list_path_line_to(list, nk_vec2_sub(a,nk_vec2(0.5f,0.5f))); + nk_draw_list_path_line_to(list, nk_vec2_sub(b,nk_vec2(0.5f,0.5f))); + } + nk_draw_list_path_stroke(list, col, NK_STROKE_OPEN, thickness); +} +NK_API void +nk_draw_list_fill_rect(struct nk_draw_list *list, struct nk_rect rect, + struct nk_color col, float rounding) +{ + NK_ASSERT(list); + if (!list || !col.a) return; + + if (list->line_AA == NK_ANTI_ALIASING_ON) { + nk_draw_list_path_rect_to(list, nk_vec2(rect.x, rect.y), + nk_vec2(rect.x + rect.w, rect.y + rect.h), rounding); + } else { + nk_draw_list_path_rect_to(list, nk_vec2(rect.x-0.5f, rect.y-0.5f), + nk_vec2(rect.x + rect.w, rect.y + rect.h), rounding); + } nk_draw_list_path_fill(list, col); +} +NK_API void +nk_draw_list_stroke_rect(struct nk_draw_list *list, struct nk_rect rect, + struct nk_color col, float rounding, float thickness) +{ + NK_ASSERT(list); + if (!list || !col.a) return; + if (list->line_AA == NK_ANTI_ALIASING_ON) { + nk_draw_list_path_rect_to(list, nk_vec2(rect.x, rect.y), + nk_vec2(rect.x + rect.w, rect.y + rect.h), rounding); + } else { + nk_draw_list_path_rect_to(list, nk_vec2(rect.x-0.5f, rect.y-0.5f), + nk_vec2(rect.x + rect.w, rect.y + rect.h), rounding); + } nk_draw_list_path_stroke(list, col, NK_STROKE_CLOSED, thickness); +} +NK_API void +nk_draw_list_fill_rect_multi_color(struct nk_draw_list *list, struct nk_rect rect, + struct nk_color left, struct nk_color top, struct nk_color right, + struct nk_color bottom) +{ + void *vtx; + struct nk_colorf col_left, col_top; + struct nk_colorf col_right, col_bottom; + nk_draw_index *idx; + nk_draw_index index; + + nk_color_fv(&col_left.r, left); + nk_color_fv(&col_right.r, right); + nk_color_fv(&col_top.r, top); + nk_color_fv(&col_bottom.r, bottom); + + NK_ASSERT(list); + if (!list) return; + + nk_draw_list_push_image(list, list->config.null.texture); + index = (nk_draw_index)list->vertex_count; + vtx = nk_draw_list_alloc_vertices(list, 4); + idx = nk_draw_list_alloc_elements(list, 6); + if (!vtx || !idx) return; + + idx[0] = (nk_draw_index)(index+0); idx[1] = (nk_draw_index)(index+1); + idx[2] = (nk_draw_index)(index+2); idx[3] = (nk_draw_index)(index+0); + idx[4] = (nk_draw_index)(index+2); idx[5] = (nk_draw_index)(index+3); + + vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(rect.x, rect.y), list->config.null.uv, col_left); + vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(rect.x + rect.w, rect.y), list->config.null.uv, col_top); + vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(rect.x + rect.w, rect.y + rect.h), list->config.null.uv, col_right); + vtx = nk_draw_vertex(vtx, &list->config, nk_vec2(rect.x, rect.y + rect.h), list->config.null.uv, col_bottom); +} +NK_API void +nk_draw_list_fill_triangle(struct nk_draw_list *list, struct nk_vec2 a, + struct nk_vec2 b, struct nk_vec2 c, struct nk_color col) +{ + NK_ASSERT(list); + if (!list || !col.a) return; + nk_draw_list_path_line_to(list, a); + nk_draw_list_path_line_to(list, b); + nk_draw_list_path_line_to(list, c); + nk_draw_list_path_fill(list, col); +} +NK_API void +nk_draw_list_stroke_triangle(struct nk_draw_list *list, struct nk_vec2 a, + struct nk_vec2 b, struct nk_vec2 c, struct nk_color col, float thickness) +{ + NK_ASSERT(list); + if (!list || !col.a) return; + nk_draw_list_path_line_to(list, a); + nk_draw_list_path_line_to(list, b); + nk_draw_list_path_line_to(list, c); + nk_draw_list_path_stroke(list, col, NK_STROKE_CLOSED, thickness); +} +NK_API void +nk_draw_list_fill_circle(struct nk_draw_list *list, struct nk_vec2 center, + float radius, struct nk_color col, unsigned int segs) +{ + float a_max; + NK_ASSERT(list); + if (!list || !col.a) return; + a_max = NK_PI * 2.0f * ((float)segs - 1.0f) / (float)segs; + nk_draw_list_path_arc_to(list, center, radius, 0.0f, a_max, segs); + nk_draw_list_path_fill(list, col); +} +NK_API void +nk_draw_list_stroke_circle(struct nk_draw_list *list, struct nk_vec2 center, + float radius, struct nk_color col, unsigned int segs, float thickness) +{ + float a_max; + NK_ASSERT(list); + if (!list || !col.a) return; + a_max = NK_PI * 2.0f * ((float)segs - 1.0f) / (float)segs; + nk_draw_list_path_arc_to(list, center, radius, 0.0f, a_max, segs); + nk_draw_list_path_stroke(list, col, NK_STROKE_CLOSED, thickness); +} +NK_API void +nk_draw_list_stroke_curve(struct nk_draw_list *list, struct nk_vec2 p0, + struct nk_vec2 cp0, struct nk_vec2 cp1, struct nk_vec2 p1, + struct nk_color col, unsigned int segments, float thickness) +{ + NK_ASSERT(list); + if (!list || !col.a) return; + nk_draw_list_path_line_to(list, p0); + nk_draw_list_path_curve_to(list, cp0, cp1, p1, segments); + nk_draw_list_path_stroke(list, col, NK_STROKE_OPEN, thickness); +} +NK_INTERN void +nk_draw_list_push_rect_uv(struct nk_draw_list *list, struct nk_vec2 a, + struct nk_vec2 c, struct nk_vec2 uva, struct nk_vec2 uvc, + struct nk_color color) +{ + void *vtx; + struct nk_vec2 uvb; + struct nk_vec2 uvd; + struct nk_vec2 b; + struct nk_vec2 d; + + struct nk_colorf col; + nk_draw_index *idx; + nk_draw_index index; + NK_ASSERT(list); + if (!list) return; + + nk_color_fv(&col.r, color); + uvb = nk_vec2(uvc.x, uva.y); + uvd = nk_vec2(uva.x, uvc.y); + b = nk_vec2(c.x, a.y); + d = nk_vec2(a.x, c.y); + + index = (nk_draw_index)list->vertex_count; + vtx = nk_draw_list_alloc_vertices(list, 4); + idx = nk_draw_list_alloc_elements(list, 6); + if (!vtx || !idx) return; + + idx[0] = (nk_draw_index)(index+0); idx[1] = (nk_draw_index)(index+1); + idx[2] = (nk_draw_index)(index+2); idx[3] = (nk_draw_index)(index+0); + idx[4] = (nk_draw_index)(index+2); idx[5] = (nk_draw_index)(index+3); + + vtx = nk_draw_vertex(vtx, &list->config, a, uva, col); + vtx = nk_draw_vertex(vtx, &list->config, b, uvb, col); + vtx = nk_draw_vertex(vtx, &list->config, c, uvc, col); + vtx = nk_draw_vertex(vtx, &list->config, d, uvd, col); +} +NK_API void +nk_draw_list_add_image(struct nk_draw_list *list, struct nk_image texture, + struct nk_rect rect, struct nk_color color) +{ + NK_ASSERT(list); + if (!list) return; + /* push new command with given texture */ + nk_draw_list_push_image(list, texture.handle); + if (nk_image_is_subimage(&texture)) { + /* add region inside of the texture */ + struct nk_vec2 uv[2]; + uv[0].x = (float)texture.region[0]/(float)texture.w; + uv[0].y = (float)texture.region[1]/(float)texture.h; + uv[1].x = (float)(texture.region[0] + texture.region[2])/(float)texture.w; + uv[1].y = (float)(texture.region[1] + texture.region[3])/(float)texture.h; + nk_draw_list_push_rect_uv(list, nk_vec2(rect.x, rect.y), + nk_vec2(rect.x + rect.w, rect.y + rect.h), uv[0], uv[1], color); + } else nk_draw_list_push_rect_uv(list, nk_vec2(rect.x, rect.y), + nk_vec2(rect.x + rect.w, rect.y + rect.h), + nk_vec2(0.0f, 0.0f), nk_vec2(1.0f, 1.0f),color); +} +NK_API void +nk_draw_list_add_text(struct nk_draw_list *list, const struct nk_user_font *font, + struct nk_rect rect, const char *text, int len, float font_height, + struct nk_color fg) +{ + float x = 0; + int text_len = 0; + nk_rune unicode = 0; + nk_rune next = 0; + int glyph_len = 0; + int next_glyph_len = 0; + struct nk_user_font_glyph g; + + NK_ASSERT(list); + if (!list || !len || !text) return; + if (!NK_INTERSECT(rect.x, rect.y, rect.w, rect.h, + list->clip_rect.x, list->clip_rect.y, list->clip_rect.w, list->clip_rect.h)) return; + + nk_draw_list_push_image(list, font->texture); + x = rect.x; + glyph_len = nk_utf_decode(text, &unicode, len); + if (!glyph_len) return; + + /* draw every glyph image */ + fg.a = (nk_byte)((float)fg.a * list->config.global_alpha); + while (text_len < len && glyph_len) { + float gx, gy, gh, gw; + float char_width = 0; + if (unicode == NK_UTF_INVALID) break; + + /* query currently drawn glyph information */ + next_glyph_len = nk_utf_decode(text + text_len + glyph_len, &next, (int)len - text_len); + font->query(font->userdata, font_height, &g, unicode, + (next == NK_UTF_INVALID) ? '\0' : next); + + /* calculate and draw glyph drawing rectangle and image */ + gx = x + g.offset.x; + gy = rect.y + g.offset.y; + gw = g.width; gh = g.height; + char_width = g.xadvance; + nk_draw_list_push_rect_uv(list, nk_vec2(gx,gy), nk_vec2(gx + gw, gy+ gh), + g.uv[0], g.uv[1], fg); + + /* offset next glyph */ + text_len += glyph_len; + x += char_width; + glyph_len = next_glyph_len; + unicode = next; + } +} +NK_API nk_flags +nk_convert(struct nk_context *ctx, struct nk_buffer *cmds, + struct nk_buffer *vertices, struct nk_buffer *elements, + const struct nk_convert_config *config) +{ + nk_flags res = NK_CONVERT_SUCCESS; + const struct nk_command *cmd; + NK_ASSERT(ctx); + NK_ASSERT(cmds); + NK_ASSERT(vertices); + NK_ASSERT(elements); + NK_ASSERT(config); + NK_ASSERT(config->vertex_layout); + NK_ASSERT(config->vertex_size); + if (!ctx || !cmds || !vertices || !elements || !config || !config->vertex_layout) + return NK_CONVERT_INVALID_PARAM; + + nk_draw_list_setup(&ctx->draw_list, config, cmds, vertices, elements, + config->line_AA, config->shape_AA); + nk_foreach(cmd, ctx) + { +#ifdef NK_INCLUDE_COMMAND_USERDATA + ctx->draw_list.userdata = cmd->userdata; +#endif + switch (cmd->type) { + case NK_COMMAND_NOP: break; + case NK_COMMAND_SCISSOR: { + const struct nk_command_scissor *s = (const struct nk_command_scissor*)cmd; + nk_draw_list_add_clip(&ctx->draw_list, nk_rect(s->x, s->y, s->w, s->h)); + } break; + case NK_COMMAND_LINE: { + const struct nk_command_line *l = (const struct nk_command_line*)cmd; + nk_draw_list_stroke_line(&ctx->draw_list, nk_vec2(l->begin.x, l->begin.y), + nk_vec2(l->end.x, l->end.y), l->color, l->line_thickness); + } break; + case NK_COMMAND_CURVE: { + const struct nk_command_curve *q = (const struct nk_command_curve*)cmd; + nk_draw_list_stroke_curve(&ctx->draw_list, nk_vec2(q->begin.x, q->begin.y), + nk_vec2(q->ctrl[0].x, q->ctrl[0].y), nk_vec2(q->ctrl[1].x, + q->ctrl[1].y), nk_vec2(q->end.x, q->end.y), q->color, + config->curve_segment_count, q->line_thickness); + } break; + case NK_COMMAND_RECT: { + const struct nk_command_rect *r = (const struct nk_command_rect*)cmd; + nk_draw_list_stroke_rect(&ctx->draw_list, nk_rect(r->x, r->y, r->w, r->h), + r->color, (float)r->rounding, r->line_thickness); + } break; + case NK_COMMAND_RECT_FILLED: { + const struct nk_command_rect_filled *r = (const struct nk_command_rect_filled*)cmd; + nk_draw_list_fill_rect(&ctx->draw_list, nk_rect(r->x, r->y, r->w, r->h), + r->color, (float)r->rounding); + } break; + case NK_COMMAND_RECT_MULTI_COLOR: { + const struct nk_command_rect_multi_color *r = (const struct nk_command_rect_multi_color*)cmd; + nk_draw_list_fill_rect_multi_color(&ctx->draw_list, nk_rect(r->x, r->y, r->w, r->h), + r->left, r->top, r->right, r->bottom); + } break; + case NK_COMMAND_CIRCLE: { + const struct nk_command_circle *c = (const struct nk_command_circle*)cmd; + nk_draw_list_stroke_circle(&ctx->draw_list, nk_vec2((float)c->x + (float)c->w/2, + (float)c->y + (float)c->h/2), (float)c->w/2, c->color, + config->circle_segment_count, c->line_thickness); + } break; + case NK_COMMAND_CIRCLE_FILLED: { + const struct nk_command_circle_filled *c = (const struct nk_command_circle_filled *)cmd; + nk_draw_list_fill_circle(&ctx->draw_list, nk_vec2((float)c->x + (float)c->w/2, + (float)c->y + (float)c->h/2), (float)c->w/2, c->color, + config->circle_segment_count); + } break; + case NK_COMMAND_ARC: { + const struct nk_command_arc *c = (const struct nk_command_arc*)cmd; + nk_draw_list_path_line_to(&ctx->draw_list, nk_vec2(c->cx, c->cy)); + nk_draw_list_path_arc_to(&ctx->draw_list, nk_vec2(c->cx, c->cy), c->r, + c->a[0], c->a[1], config->arc_segment_count); + nk_draw_list_path_stroke(&ctx->draw_list, c->color, NK_STROKE_CLOSED, c->line_thickness); + } break; + case NK_COMMAND_ARC_FILLED: { + const struct nk_command_arc_filled *c = (const struct nk_command_arc_filled*)cmd; + nk_draw_list_path_line_to(&ctx->draw_list, nk_vec2(c->cx, c->cy)); + nk_draw_list_path_arc_to(&ctx->draw_list, nk_vec2(c->cx, c->cy), c->r, + c->a[0], c->a[1], config->arc_segment_count); + nk_draw_list_path_fill(&ctx->draw_list, c->color); + } break; + case NK_COMMAND_TRIANGLE: { + const struct nk_command_triangle *t = (const struct nk_command_triangle*)cmd; + nk_draw_list_stroke_triangle(&ctx->draw_list, nk_vec2(t->a.x, t->a.y), + nk_vec2(t->b.x, t->b.y), nk_vec2(t->c.x, t->c.y), t->color, + t->line_thickness); + } break; + case NK_COMMAND_TRIANGLE_FILLED: { + const struct nk_command_triangle_filled *t = (const struct nk_command_triangle_filled*)cmd; + nk_draw_list_fill_triangle(&ctx->draw_list, nk_vec2(t->a.x, t->a.y), + nk_vec2(t->b.x, t->b.y), nk_vec2(t->c.x, t->c.y), t->color); + } break; + case NK_COMMAND_POLYGON: { + int i; + const struct nk_command_polygon*p = (const struct nk_command_polygon*)cmd; + for (i = 0; i < p->point_count; ++i) { + struct nk_vec2 pnt = nk_vec2((float)p->points[i].x, (float)p->points[i].y); + nk_draw_list_path_line_to(&ctx->draw_list, pnt); + } + nk_draw_list_path_stroke(&ctx->draw_list, p->color, NK_STROKE_CLOSED, p->line_thickness); + } break; + case NK_COMMAND_POLYGON_FILLED: { + int i; + const struct nk_command_polygon_filled *p = (const struct nk_command_polygon_filled*)cmd; + for (i = 0; i < p->point_count; ++i) { + struct nk_vec2 pnt = nk_vec2((float)p->points[i].x, (float)p->points[i].y); + nk_draw_list_path_line_to(&ctx->draw_list, pnt); + } + nk_draw_list_path_fill(&ctx->draw_list, p->color); + } break; + case NK_COMMAND_POLYLINE: { + int i; + const struct nk_command_polyline *p = (const struct nk_command_polyline*)cmd; + for (i = 0; i < p->point_count; ++i) { + struct nk_vec2 pnt = nk_vec2((float)p->points[i].x, (float)p->points[i].y); + nk_draw_list_path_line_to(&ctx->draw_list, pnt); + } + nk_draw_list_path_stroke(&ctx->draw_list, p->color, NK_STROKE_OPEN, p->line_thickness); + } break; + case NK_COMMAND_TEXT: { + const struct nk_command_text *t = (const struct nk_command_text*)cmd; + nk_draw_list_add_text(&ctx->draw_list, t->font, nk_rect(t->x, t->y, t->w, t->h), + t->string, t->length, t->height, t->foreground); + } break; + case NK_COMMAND_IMAGE: { + const struct nk_command_image *i = (const struct nk_command_image*)cmd; + nk_draw_list_add_image(&ctx->draw_list, i->img, nk_rect(i->x, i->y, i->w, i->h), i->col); + } break; + case NK_COMMAND_CUSTOM: { + const struct nk_command_custom *c = (const struct nk_command_custom*)cmd; + c->callback(&ctx->draw_list, c->x, c->y, c->w, c->h, c->callback_data); + } break; + default: break; + } + } + res |= (cmds->needed > cmds->allocated + (cmds->memory.size - cmds->size)) ? NK_CONVERT_COMMAND_BUFFER_FULL: 0; + res |= (vertices->needed > vertices->allocated) ? NK_CONVERT_VERTEX_BUFFER_FULL: 0; + res |= (elements->needed > elements->allocated) ? NK_CONVERT_ELEMENT_BUFFER_FULL: 0; + return res; +} +NK_API const struct nk_draw_command* +nk__draw_begin(const struct nk_context *ctx, + const struct nk_buffer *buffer) +{ + return nk__draw_list_begin(&ctx->draw_list, buffer); +} +NK_API const struct nk_draw_command* +nk__draw_end(const struct nk_context *ctx, const struct nk_buffer *buffer) +{ + return nk__draw_list_end(&ctx->draw_list, buffer); +} +NK_API const struct nk_draw_command* +nk__draw_next(const struct nk_draw_command *cmd, + const struct nk_buffer *buffer, const struct nk_context *ctx) +{ + return nk__draw_list_next(cmd, buffer, &ctx->draw_list); +} +#endif + + +/* stb_rect_pack.h - v1.00 - public domain - rectangle packing */ +/* Sean Barrett 2014 */ +/* */ +/* Useful for e.g. packing rectangular textures into an atlas. */ +/* Does not do rotation. */ +/* */ +/* Not necessarily the awesomest packing method, but better than */ +/* the totally naive one in stb_truetype (which is primarily what */ +/* this is meant to replace). */ +/* */ +/* Has only had a few tests run, may have issues. */ +/* */ +/* More docs to come. */ +/* */ +/* No memory allocations; uses qsort() and assert() from stdlib. */ +/* Can override those by defining STBRP_SORT and STBRP_ASSERT. */ +/* */ +/* This library currently uses the Skyline Bottom-Left algorithm. */ +/* */ +/* Please note: better rectangle packers are welcome! Please */ +/* implement them to the same API, but with a different init */ +/* function. */ +/* */ +/* Credits */ +/* */ +/* Library */ +/* Sean Barrett */ +/* Minor features */ +/* Martins Mozeiko */ +/* github:IntellectualKitty */ +/* */ +/* Bugfixes / warning fixes */ +/* Jeremy Jaussaud */ +/* Fabian Giesen */ +/* */ +/* Version history: */ +/* */ +/* 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles */ +/* 0.99 (2019-02-07) warning fixes */ +/* 0.11 (2017-03-03) return packing success/fail result */ +/* 0.10 (2016-10-25) remove cast-away-const to avoid warnings */ +/* 0.09 (2016-08-27) fix compiler warnings */ +/* 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) */ +/* 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) */ +/* 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort */ +/* 0.05: added STBRP_ASSERT to allow replacing assert */ +/* 0.04: fixed minor bug in STBRP_LARGE_RECTS support */ +/* 0.01: initial release */ +/* */ +/* LICENSE */ +/* */ +/* See end of file for license information. */ + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* INCLUDE SECTION */ +/* */ + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +#ifdef STBRP_LARGE_RECTS +typedef int stbrp_coord; +#else +typedef unsigned short stbrp_coord; +#endif + +STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +/* Assign packed locations to rectangles. The rectangles are of type */ +/* 'stbrp_rect' defined below, stored in the array 'rects', and there */ +/* are 'num_rects' many of them. */ +/* */ +/* Rectangles which are successfully packed have the 'was_packed' flag */ +/* set to a non-zero value and 'x' and 'y' store the minimum location */ +/* on each axis (i.e. bottom-left in cartesian coordinates, top-left */ +/* if you imagine y increasing downwards). Rectangles which do not fit */ +/* have the 'was_packed' flag set to 0. */ +/* */ +/* You should not try to access the 'rects' array from another thread */ +/* while this function is running, as the function temporarily reorders */ +/* the array while it executes. */ +/* */ +/* To pack into another rectangle, you need to call stbrp_init_target */ +/* again. To continue packing into the same rectangle, you can call */ +/* this function again. Calling this multiple times with multiple rect */ +/* arrays will probably produce worse packing results than calling it */ +/* a single time with the full rectangle array, but the option is */ +/* available. */ +/* */ +/* The function returns 1 if all of the rectangles were successfully */ +/* packed and 0 otherwise. */ + +struct stbrp_rect +{ + /* reserved for your use: */ + int id; + + /* input: */ + stbrp_coord w, h; + + /* output: */ + stbrp_coord x, y; + int was_packed; /* non-zero if valid packing */ + +}; /* 16 bytes, nominally */ + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +/* Initialize a rectangle packer to: */ +/* pack a rectangle that is 'width' by 'height' in dimensions */ +/* using temporary storage provided by the array 'nodes', which is 'num_nodes' long */ +/* */ +/* You must call this function every time you start packing into a new target. */ +/* */ +/* There is no "shutdown" function. The 'nodes' memory must stay valid for */ +/* the following stbrp_pack_rects() call (or calls), but can be freed after */ +/* the call (or calls) finish. */ +/* */ +/* Note: to guarantee best results, either: */ +/* 1. make sure 'num_nodes' >= 'width' */ +/* or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' */ +/* */ +/* If you don't do either of the above things, widths will be quantized to multiples */ +/* of small integers to guarantee the algorithm doesn't run out of temporary storage. */ +/* */ +/* If you do #2, then the non-quantized algorithm will be used, but the algorithm */ +/* may run out of temporary storage and be unable to pack some rectangles. */ + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +/* Optionally call this function after init but before doing any packing to */ +/* change the handling of the out-of-temp-memory scenario, described above. */ +/* If you call init again, this will be reset to the default (false). */ + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +/* Optionally select which packing heuristic the library should use. Different */ +/* heuristics will produce better/worse results for different data sets. */ +/* If you call init again, this will be reset to the default. */ + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* the details of the following structures don't matter to you, but they must */ +/* be visible so you can handle the memory allocations for them */ + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; /* we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' */ +}; + +#ifdef __cplusplus +} +#endif + +#endif + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* IMPLEMENTATION SECTION */ +/* */ + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + /* if it's ok to run out of memory, then don't bother aligning them; */ + /* this gives better packing, but may fail due to OOM (even though */ + /* the rectangles easily fit). @TODO a smarter approach would be to only */ + /* quantize once we've hit OOM, then we could get rid of this parameter. */ + context->align = 1; + else { + /* if it's not ok to run out of memory, then quantize the widths */ + /* so that num_nodes is always enough nodes. */ + /* */ + /* I.e. num_nodes * align >= width */ + /* align >= width / num_nodes */ + /* align = ceil(width/num_nodes) */ + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + /* node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) */ + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +/* find minimum y position if it starts at x1 */ +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + /* skip in case we're past the node */ + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); /* we ended up handling this in the caller for efficiency */ + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + /* raise min_y higher. */ + /* we've accounted for all waste up to min_y, */ + /* but we'll now add more waste for everything we've visted */ + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + /* the first time through, visited_width might be reduced */ + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + /* add waste area */ + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + /* align to multiple of c->align */ + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + /* if it can't possibly fit, bail immediately */ + if (width > c->width || height > c->height) { + fr.prev_link = NULL; + fr.x = fr.y = 0; + return fr; + } + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { /* actually just want to test BL */ + /* bottom left */ + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + /* best-fit */ + if (y + height <= c->height) { + /* can only use it if it first vertically */ + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + /* if doing best-fit (BF), we also have to try aligning right edge to each node position */ + /* */ + /* e.g, if fitting */ + /* */ + /* ____________________ */ + /* |____________________| */ + /* */ + /* into */ + /* */ + /* | | */ + /* | ____________| */ + /* |____________| */ + /* */ + /* then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned */ + /* */ + /* This makes BF take about 2x the time */ + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + /* find first node that's admissible */ + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + /* find the left position that matches this */ + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height <= c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + /* find best position according to heuristic */ + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + /* bail if: */ + /* 1. it failed */ + /* 2. the best node doesn't fit (we don't always check this) */ + /* 3. we're out of memory */ + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + /* on success, create new node */ + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + /* insert the new node into the right starting point, and */ + /* let 'cur' point to the remaining nodes needing to be */ + /* stiched back in */ + + cur = *res.prev_link; + if (cur->x < res.x) { + /* preserve the existing one, so start testing with the next one */ + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + /* from here, traverse cur and free the nodes, until we get to one */ + /* that shouldn't be freed */ + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + /* move the current node to the free list */ + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + /* stitch the list back in */ + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count=0; + cur = context->active_head; + while (cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i, all_rects_packed = 1; + + /* we use the 'was_packed' field internally to allow sorting/unsorting */ + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + } + + /* sort according to heuristic */ + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; /* empty rect needs no space */ + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + /* unsort */ + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + /* set was_packed flags and all_rects_packed status */ + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) + all_rects_packed = 0; + } + + /* return the all_rects_packed status */ + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ + +/* stb_truetype.h - v1.24 - public domain */ +/* authored from 2009-2020 by Sean Barrett / RAD Game Tools */ +/* */ +/* ======================================================================= */ +/* */ +/* NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES */ +/* */ +/* This library does no range checking of the offsets found in the file, */ +/* meaning an attacker can use it to read arbitrary memory. */ +/* */ +/* ======================================================================= */ +/* */ +/* This library processes TrueType files: */ +/* parse files */ +/* extract glyph metrics */ +/* extract glyph shapes */ +/* render glyphs to one-channel bitmaps with antialiasing (box filter) */ +/* render glyphs to one-channel SDF bitmaps (signed-distance field/function) */ +/* */ +/* Todo: */ +/* non-MS cmaps */ +/* crashproof on bad data */ +/* hinting? (no longer patented) */ +/* cleartype-style AA? */ +/* optimize: use simple memory allocator for intermediates */ +/* optimize: build edge-list directly from curves */ +/* optimize: rasterize directly from curves? */ +/* */ +/* ADDITIONAL CONTRIBUTORS */ +/* */ +/* Mikko Mononen: compound shape support, more cmap formats */ +/* Tor Andersson: kerning, subpixel rendering */ +/* Dougall Johnson: OpenType / Type 2 font handling */ +/* Daniel Ribeiro Maciel: basic GPOS-based kerning */ +/* */ +/* Misc other: */ +/* Ryan Gordon */ +/* Simon Glass */ +/* github:IntellectualKitty */ +/* Imanol Celaya */ +/* Daniel Ribeiro Maciel */ +/* */ +/* Bug/warning reports/fixes: */ +/* "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe */ +/* Cass Everitt Martins Mozeiko github:aloucks */ +/* stoiko (Haemimont Games) Cap Petschulat github:oyvindjam */ +/* Brian Hook Omar Cornut github:vassvik */ +/* Walter van Niftrik Ryan Griege */ +/* David Gow Peter LaValle */ +/* David Given Sergey Popov */ +/* Ivan-Assen Ivanov Giumo X. Clanjor */ +/* Anthony Pesch Higor Euripedes */ +/* Johan Duparc Thomas Fields */ +/* Hou Qiming Derek Vinyard */ +/* Rob Loach Cort Stratton */ +/* Kenney Phillis Jr. Brian Costabile */ +/* Ken Voskuil (kaesve) */ +/* */ +/* VERSION HISTORY */ +/* */ +/* 1.24 (2020-02-05) fix warning */ +/* 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) */ +/* 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined */ +/* 1.21 (2019-02-25) fix warning */ +/* 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() */ +/* 1.19 (2018-02-11) GPOS kerning, STBTT_fmod */ +/* 1.18 (2018-01-29) add missing function */ +/* 1.17 (2017-07-23) make more arguments const; doc fix */ +/* 1.16 (2017-07-12) SDF support */ +/* 1.15 (2017-03-03) make more arguments const */ +/* 1.14 (2017-01-16) num-fonts-in-TTC function */ +/* 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts */ +/* 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual */ +/* 1.11 (2016-04-02) fix unused-variable warning */ +/* 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef */ +/* 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly */ +/* 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges */ +/* 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; */ +/* variant PackFontRanges to pack and render in separate phases; */ +/* fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); */ +/* fixed an assert() bug in the new rasterizer */ +/* replace assert() with STBTT_assert() in new rasterizer */ +/* */ +/* Full history can be found at the end of this file. */ +/* */ +/* LICENSE */ +/* */ +/* See end of file for license information. */ +/* */ +/* USAGE */ +/* */ +/* Include this file in whatever places need to refer to it. In ONE C/C++ */ +/* file, write: */ +/* #define STB_TRUETYPE_IMPLEMENTATION */ +/* before the #include of this file. This expands out the actual */ +/* implementation into that C/C++ file. */ +/* */ +/* To make the implementation private to the file that generates the implementation, */ +/* #define STBTT_STATIC */ +/* */ +/* Simple 3D API (don't ship this, but it's fine for tools and quick start) */ +/* stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture */ +/* stbtt_GetBakedQuad() -- compute quad to draw for a given char */ +/* */ +/* Improved 3D API (more shippable): */ +/* #include "stb_rect_pack.h" -- optional, but you really want it */ +/* stbtt_PackBegin() */ +/* stbtt_PackSetOversampling() -- for improved quality on small fonts */ +/* stbtt_PackFontRanges() -- pack and renders */ +/* stbtt_PackEnd() */ +/* stbtt_GetPackedQuad() */ +/* */ +/* "Load" a font file from a memory buffer (you have to keep the buffer loaded) */ +/* stbtt_InitFont() */ +/* stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections */ +/* stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections */ +/* */ +/* Render a unicode codepoint to a bitmap */ +/* stbtt_GetCodepointBitmap() -- allocates and returns a bitmap */ +/* stbtt_MakeCodepointBitmap() -- renders into bitmap you provide */ +/* stbtt_GetCodepointBitmapBox() -- how big the bitmap must be */ +/* */ +/* Character advance/positioning */ +/* stbtt_GetCodepointHMetrics() */ +/* stbtt_GetFontVMetrics() */ +/* stbtt_GetFontVMetricsOS2() */ +/* stbtt_GetCodepointKernAdvance() */ +/* */ +/* Starting with version 1.06, the rasterizer was replaced with a new, */ +/* faster and generally-more-precise rasterizer. The new rasterizer more */ +/* accurately measures pixel coverage for anti-aliasing, except in the case */ +/* where multiple shapes overlap, in which case it overestimates the AA pixel */ +/* coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If */ +/* this turns out to be a problem, you can re-enable the old rasterizer with */ +/* #define STBTT_RASTERIZER_VERSION 1 */ +/* which will incur about a 15% speed hit. */ +/* */ +/* ADDITIONAL DOCUMENTATION */ +/* */ +/* Immediately after this block comment are a series of sample programs. */ +/* */ +/* After the sample programs is the "header file" section. This section */ +/* includes documentation for each API function. */ +/* */ +/* Some important concepts to understand to use this library: */ +/* */ +/* Codepoint */ +/* Characters are defined by unicode codepoints, e.g. 65 is */ +/* uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is */ +/* the hiragana for "ma". */ +/* */ +/* Glyph */ +/* A visual character shape (every codepoint is rendered as */ +/* some glyph) */ +/* */ +/* Glyph index */ +/* A font-specific integer ID representing a glyph */ +/* */ +/* Baseline */ +/* Glyph shapes are defined relative to a baseline, which is the */ +/* bottom of uppercase characters. Characters extend both above */ +/* and below the baseline. */ +/* */ +/* Current Point */ +/* As you draw text to the screen, you keep track of a "current point" */ +/* which is the origin of each character. The current point's vertical */ +/* position is the baseline. Even "baked fonts" use this model. */ +/* */ +/* Vertical Font Metrics */ +/* The vertical qualities of the font, used to vertically position */ +/* and space the characters. See docs for stbtt_GetFontVMetrics. */ +/* */ +/* Font Size in Pixels or Points */ +/* The preferred interface for specifying font sizes in stb_truetype */ +/* is to specify how tall the font's vertical extent should be in pixels. */ +/* If that sounds good enough, skip the next paragraph. */ +/* */ +/* Most font APIs instead use "points", which are a common typographic */ +/* measurement for describing font size, defined as 72 points per inch. */ +/* stb_truetype provides a point API for compatibility. However, true */ +/* "per inch" conventions don't make much sense on computer displays */ +/* since different monitors have different number of pixels per */ +/* inch. For example, Windows traditionally uses a convention that */ +/* there are 96 pixels per inch, thus making 'inch' measurements have */ +/* nothing to do with inches, and thus effectively defining a point to */ +/* be 1.333 pixels. Additionally, the TrueType font data provides */ +/* an explicit scale factor to scale a given font's glyphs to points, */ +/* but the author has observed that this scale factor is often wrong */ +/* for non-commercial fonts, thus making fonts scaled in points */ +/* according to the TrueType spec incoherently sized in practice. */ +/* */ +/* DETAILED USAGE: */ +/* */ +/* Scale: */ +/* Select how high you want the font to be, in points or pixels. */ +/* Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute */ +/* a scale factor SF that will be used by all other functions. */ +/* */ +/* Baseline: */ +/* You need to select a y-coordinate that is the baseline of where */ +/* your text will appear. Call GetFontBoundingBox to get the baseline-relative */ +/* bounding box for all characters. SF*-y0 will be the distance in pixels */ +/* that the worst-case character could extend above the baseline, so if */ +/* you want the top edge of characters to appear at the top of the */ +/* screen where y=0, then you would set the baseline to SF*-y0. */ +/* */ +/* Current point: */ +/* Set the current point where the first character will appear. The */ +/* first character could extend left of the current point; this is font */ +/* dependent. You can either choose a current point that is the leftmost */ +/* point and hope, or add some padding, or check the bounding box or */ +/* left-side-bearing of the first character to be displayed and set */ +/* the current point based on that. */ +/* */ +/* Displaying a character: */ +/* Compute the bounding box of the character. It will contain signed values */ +/* relative to . I.e. if it returns x0,y0,x1,y1, */ +/* then the character should be displayed in the rectangle from */ +/* to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);/* 1=opengl & d3d10+,0=d3d9 */ + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +/* */ +/* */ +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* Complete program (this compiles): get a single bitmap, print as ASCII art */ +/* */ +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION /* force following include to generate implementation */ +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +/* */ +/* Output: */ +/* */ +/* .ii. */ +/* @@@@@@. */ +/* V@Mio@@o */ +/* :i. V@V */ +/* :oM@@M */ +/* :@@@MM@M */ +/* @@o o@M */ +/* :@@. M@M */ +/* @@@o@@@@ */ +/* :M@@V:@@. */ +/* */ +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* Complete program: print "Hello World!" banner, with bugs */ +/* */ +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; /* leave a little padding in case the character extends left */ + char *text = "Heljo World!"; /* intentionally misspelled to show 'lj' brokenness */ + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + /* note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong */ + /* because this API is really for baking character bitmaps into textures. if you want to render */ + /* a sequence of characters, you really need to render each bitmap to a temp buffer, then */ + /* "alpha blend" that into the working buffer */ + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +/* //////////////////////////////////////////////////////////////////////////// */ +/* //////////////////////////////////////////////////////////////////////////// */ +/* // */ +/* // INTEGRATION WITH YOUR CODEBASE */ +/* // */ +/* // The following sections allow you to supply alternate definitions */ +/* // of C library functions used by stb_truetype, e.g. if you don't */ +/* // link with the C runtime library. */ + +#ifdef STB_TRUETYPE_IMPLEMENTATION + /* #define your own (u)stbtt_int8/16/32 before including to override this */ + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + /* e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h */ + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + /* #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h */ + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/* ///////////////////////////////////////////////////////////////////////////// */ +/* ///////////////////////////////////////////////////////////////////////////// */ +/* // */ +/* // INTERFACE */ +/* // */ +/* // */ + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* private structure */ +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* TEXTURE BAKING API */ +/* */ +/* If you use this API, you only have to call two functions ever. */ +/* */ + +typedef struct +{ + unsigned short x0,y0,x1,y1; /* coordinates of bbox in bitmap */ + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, /* font location (use offset=0 for plain .ttf) */ + float pixel_height, /* height of font in pixels */ + unsigned char *pixels, int pw, int ph, /* bitmap to be filled in */ + int first_char, int num_chars, /* characters to bake */ + stbtt_bakedchar *chardata); /* you allocate this, it's num_chars long */ +/* if return is positive, the first unused row of the bitmap */ +/* if return is negative, returns the negative of the number of characters that fit */ +/* if return is 0, no characters fit and no rows were used */ +/* This uses a very crappy packing. */ + +typedef struct +{ + float x0,y0,s0,t0; /* top-left */ + float x1,y1,s1,t1; /* bottom-right */ +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, /* same data as above */ + int char_index, /* character to display */ + float *xpos, float *ypos, /* pointers to current position in screen pixel space */ + stbtt_aligned_quad *q, /* output: quad to draw */ + int opengl_fillrule); /* true if opengl fill rule; false if DX9 or earlier */ +/* Call GetBakedQuad with char_index = 'character - first_char', and it */ +/* creates the quad you need to draw and advances the current position. */ +/* */ +/* The coordinate system used assumes y increases downwards. */ +/* */ +/* Characters will extend both above and below the current position; */ +/* see discussion of "BASELINE" above. */ +/* */ +/* It's inefficient; you might want to c&p it and optimize it. */ + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +/* Query the font vertical metrics without having to create a font first. */ + + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* NEW TEXTURE BAKING API */ +/* */ +/* This provides options for packing multiple fonts into one atlas, not */ +/* perfectly but better than nothing. */ + +typedef struct +{ + unsigned short x0,y0,x1,y1; /* coordinates of bbox in bitmap */ + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +/* Initializes a packing context stored in the passed-in stbtt_pack_context. */ +/* Future calls using this context will pack characters into the bitmap passed */ +/* in here: a 1-channel bitmap that is width * height. stride_in_bytes is */ +/* the distance from one row to the next (or 0 to mean they are packed tightly */ +/* together). "padding" is the amount of padding to leave between each */ +/* character (normally you want '1' for bitmaps you'll use as textures with */ +/* bilinear filtering). */ +/* */ +/* Returns 0 on failure, 1 on success. */ + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +/* Cleans up the packing context and frees all memory. */ + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +/* Creates character bitmaps from the font_index'th font found in fontdata (use */ +/* font_index=0 if you don't know what that is). It creates num_chars_in_range */ +/* bitmaps for characters with unicode values starting at first_unicode_char_in_range */ +/* and increasing. Data for how to render them is stored in chardata_for_range; */ +/* pass these to stbtt_GetPackedQuad to get back renderable quads. */ +/* */ +/* font_size is the full height of the character from ascender to descender, */ +/* as computed by stbtt_ScaleForPixelHeight. To use a point size as computed */ +/* by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() */ +/* and pass that result as 'font_size': */ +/* ..., 20 , ... // font max minus min y is 20 pixels tall */ +/* ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall */ + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; /* if non-zero, then the chars are continuous, and this is the first codepoint */ + int *array_of_unicode_codepoints; /* if non-zero, then this is an array of unicode codepoints */ + int num_chars; + stbtt_packedchar *chardata_for_range; /* output */ + unsigned char h_oversample, v_oversample; /* don't set these, they're used internally */ +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +/* Creates character bitmaps from multiple ranges of characters stored in */ +/* ranges. This will usually create a better-packed bitmap than multiple */ +/* calls to stbtt_PackFontRange. Note that you can call this multiple */ +/* times within a single PackBegin/PackEnd. */ + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +/* Oversampling a font increases the quality by allowing higher-quality subpixel */ +/* positioning, and is especially valuable at smaller text sizes. */ +/* */ +/* This function sets the amount of oversampling for all following calls to */ +/* stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given */ +/* pack context. The default (no oversampling) is achieved by h_oversample=1 */ +/* and v_oversample=1. The total number of pixels required is */ +/* h_oversample*v_oversample larger than the default; for example, 2x2 */ +/* oversampling requires 4x the storage of 1x1. For best results, render */ +/* oversampled textures with bilinear filtering. Look at the readme in */ +/* stb/tests/oversample for information about oversampled fonts */ +/* */ +/* To use with PackFontRangesGather etc., you must set it before calls */ +/* call to PackFontRangesGatherRects. */ + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +/* If skip != 0, this tells stb_truetype to skip any codepoints for which */ +/* there is no corresponding glyph. If skip=0, which is the default, then */ +/* codepoints without a glyph recived the font's "missing character" glyph, */ +/* typically an empty box by convention. */ + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, /* same data as above */ + int char_index, /* character to display */ + float *xpos, float *ypos, /* pointers to current position in screen pixel space */ + stbtt_aligned_quad *q, /* output: quad to draw */ + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +/* Calling these functions in sequence is roughly equivalent to calling */ +/* stbtt_PackFontRanges(). If you more control over the packing of multiple */ +/* fonts, or if you want to pack custom data into a font texture, take a look */ +/* at the source to of stbtt_PackFontRanges() and create a custom version */ +/* using these functions, e.g. call GatherRects multiple times, */ +/* building up a single array of rects, then call PackRects once, */ +/* then call RenderIntoRects repeatedly. This may result in a */ +/* better packing than calling PackFontRanges multiple times */ +/* (or it may not). */ + +/* this is an opaque structure that you shouldn't mess with which holds */ +/* all the context needed from PackBegin to PackEnd. */ +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* FONT LOADING */ +/* */ +/* */ + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +/* This function will determine the number of fonts in a font file. TrueType */ +/* collection (.ttc) files may contain multiple fonts, while TrueType font */ +/* (.ttf) files only contain one font. The number of fonts can be used for */ +/* indexing with the previous function where the index is between zero and one */ +/* less than the total fonts. If an error occurs, -1 is returned. */ + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +/* Each .ttf/.ttc file may have more than one font. Each font has a sequential */ +/* index number starting from 0. Call this function to get the font offset for */ +/* a given index; it returns -1 if the index is out of range. A regular .ttf */ +/* file will only define one font and it always be at offset 0, so it will */ +/* return '0' for index 0, and -1 for all other indices. */ + +/* The following structure is defined publicly so you can declare one on */ +/* the stack or as a global or etc, but you should treat it as opaque. */ +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; /* pointer to .ttf file */ + int fontstart; /* offset of start of font */ + + int numGlyphs; /* number of glyphs, needed for range checking */ + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; /* table locations as offset from start of .ttf */ + int index_map; /* a cmap mapping for our chosen character encoding */ + int indexToLocFormat; /* format needed to map from glyph index to glyph */ + + stbtt__buf cff; /* cff font data */ + stbtt__buf charstrings; /* the charstring index */ + stbtt__buf gsubrs; /* global charstring subroutines index */ + stbtt__buf subrs; /* private charstring subroutines index */ + stbtt__buf fontdicts; /* array of font dicts */ + stbtt__buf fdselect; /* map from glyph to fontdict */ +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +/* Given an offset into the file that defines a font, this function builds */ +/* the necessary cached info for the rest of the system. You must allocate */ +/* the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't */ +/* need to do anything special to free it, because the contents are pure */ +/* value data with no additional data structures. Returns 0 on failure. */ + + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* CHARACTER TO GLYPH-INDEX CONVERSIOn */ + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +/* If you're going to perform multiple operations on the same character */ +/* and you want a speed-up, call this function with the character you're */ +/* going to process, then use glyph-based functions instead of the */ +/* codepoint-based functions. */ +/* Returns 0 if the character codepoint is not defined in the font. */ + + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* CHARACTER PROPERTIES */ +/* */ + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +/* computes a scale factor to produce a font whose "height" is 'pixels' tall. */ +/* Height is measured as the distance from the highest ascender to the lowest */ +/* descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics */ +/* and computing: */ +/* scale = pixels / (ascent - descent) */ +/* so if you prefer to measure height by the ascent only, use a similar calculation. */ + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +/* computes a scale factor to produce a font whose EM size is mapped to */ +/* 'pixels' tall. This is probably what traditional APIs compute, but */ +/* I'm not positive. */ + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +/* ascent is the coordinate above the baseline the font extends; descent */ +/* is the coordinate below the baseline the font extends (i.e. it is typically negative) */ +/* lineGap is the spacing between one row's descent and the next row's ascent... */ +/* so you should advance the vertical position by "*ascent - *descent + *lineGap" */ +/* these are expressed in unscaled coordinates, so you must multiply by */ +/* the scale factor for a given size */ + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +/* analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 */ +/* table (specific to MS/Windows TTF files). */ +/* */ +/* Returns 1 on success (table present), 0 on failure. */ + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +/* the bounding box around all possible characters */ + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +/* leftSideBearing is the offset from the current horizontal position to the left edge of the character */ +/* advanceWidth is the offset from the current horizontal position to the next horizontal position */ +/* these are expressed in unscaled coordinates */ + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +/* an additional amount to add to the 'advance' value between ch1 and ch2 */ + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +/* Gets the bounding box of the visible part of the glyph, in unscaled coordinates */ + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +/* as above, but takes one or more glyph indices for greater efficiency */ + +typedef struct stbtt_kerningentry +{ + int glyph1; /* use stbtt_FindGlyphIndex */ + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +/* Retrieves a complete list of all of the kerning pairs provided by the font */ +/* stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. */ +/* The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) */ + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* GLYPH SHAPES (you probably don't need these, but they have to go before */ +/* the bitmaps for C declaration-order reasons) */ +/* */ + +#ifndef STBTT_vmove /* you can predefine these to use different values (but why?) */ + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex /* you can predefine this to use different values */ + /* (we share this with other code at RAD) */ + #define stbtt_vertex_type short /* can't use stbtt_int16 because that's not visible in the header file */ + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +/* returns non-zero if nothing is drawn for this glyph */ + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +/* returns # of vertices and fills *vertices with the pointer to them */ +/* these are expressed in "unscaled" coordinates */ +/* */ +/* The shape is a series of contours. Each one starts with */ +/* a STBTT_moveto, then consists of a series of mixed */ +/* STBTT_lineto and STBTT_curveto segments. A lineto */ +/* draws a line from previous endpoint to its x,y; a curveto */ +/* draws a quadratic bezier from previous endpoint to */ +/* its x,y, using cx,cy as the bezier control point. */ + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +/* frees the data allocated above */ + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +/* fills svg with the character's SVG data. */ +/* returns data size or 0 if SVG not found. */ + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* BITMAP RENDERING */ +/* */ + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +/* frees the bitmap allocated below */ + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +/* allocates a large-enough single-channel 8bpp bitmap and renders the */ +/* specified character/glyph at the specified scale into it, with */ +/* antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). */ +/* *width & *height are filled out with the width & height of the bitmap, */ +/* which is stored left-to-right, top-to-bottom. */ +/* */ +/* xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap */ + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +/* the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel */ +/* shift for the character */ + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +/* the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap */ +/* in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap */ +/* is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the */ +/* width and height and positioning info for it first. */ + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +/* same as stbtt_MakeCodepointBitmap, but you can specify a subpixel */ +/* shift for the character */ + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +/* same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering */ +/* is performed (see stbtt_PackSetOversampling) */ + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +/* get the bbox of the bitmap centered around the glyph origin; so the */ +/* bitmap width is ix1-ix0, height is iy1-iy0, and location to place */ +/* the bitmap top left is (leftSideBearing*scale,iy0). */ +/* (Note that the bitmap uses y-increases-down, but the shape uses */ +/* y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) */ + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +/* same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel */ +/* shift for the character */ + +/* the following functions are equivalent to the above functions, but operate */ +/* on glyph indices instead of Unicode codepoints (for efficiency) */ +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +/* @TODO: don't expose this structure */ +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +/* rasterize a shape with quadratic beziers into a bitmap */ +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, /* 1-channel bitmap to draw into */ + float flatness_in_pixels, /* allowable error of curve in pixels */ + stbtt_vertex *vertices, /* array of vertices defining shape */ + int num_verts, /* number of vertices in above array */ + float scale_x, float scale_y, /* scale applied to input vertices */ + float shift_x, float shift_y, /* translation applied to input vertices */ + int x_off, int y_off, /* another translation applied to input */ + int invert, /* if non-zero, vertically flip shape */ + void *userdata); /* context for to STBTT_MALLOC */ + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* Signed Distance Function (or Field) rendering */ + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +/* frees the SDF bitmap allocated below */ + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +/* These functions compute a discretized SDF field for a single character, suitable for storing */ +/* in a single-channel texture, sampling with bilinear filtering, and testing against */ +/* larger than some threshold to produce scalable fonts. */ +/* info -- the font */ +/* scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap */ +/* glyph/codepoint -- the character to generate the SDF for */ +/* padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), */ +/* which allows effects like bit outlines */ +/* onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) */ +/* pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) */ +/* if positive, > onedge_value is inside; if negative, < onedge_value is inside */ +/* width,height -- output height & width of the SDF bitmap (including padding) */ +/* xoff,yoff -- output origin of the character */ +/* return value -- a 2D array of bytes 0..255, width*height in size */ +/* */ +/* pixel_dist_scale & onedge_value are a scale & bias that allows you to make */ +/* optimal use of the limited 0..255 for your application, trading off precision */ +/* and special effects. SDF values outside the range 0..255 are clamped to 0..255. */ +/* */ +/* Example: */ +/* scale = stbtt_ScaleForPixelHeight(22) */ +/* padding = 5 */ +/* onedge_value = 180 */ +/* pixel_dist_scale = 180/5.0 = 36.0 */ +/* */ +/* This will create an SDF bitmap in which the character is about 22 pixels */ +/* high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled */ +/* shape, sample the SDF at each pixel and fill the pixel if the SDF value */ +/* is greater than or equal to 180/255. (You'll actually want to antialias, */ +/* which is beyond the scope of this example.) Additionally, you can compute */ +/* offset outlines (e.g. to stroke the character border inside & outside, */ +/* or only outside). For example, to fill outside the character up to 3 SDF */ +/* pixels, you would compare against (180-36.0*3)/255 = 72/255. The above */ +/* choice of variables maps a range from 5 pixels outside the shape to */ +/* 2 pixels inside the shape to 0..255; this is intended primarily for apply */ +/* outside effects only (the interior range is needed to allow proper */ +/* antialiasing of the font at *smaller* sizes) */ +/* */ +/* The function computes the SDF analytically at each SDF pixel, not by e.g. */ +/* building a higher-res bitmap and approximating it. In theory the quality */ +/* should be as high as possible for an SDF of this size & representation, but */ +/* unclear if this is true in practice (perhaps building a higher-res bitmap */ +/* and computing from that can allow drop-out prevention). */ +/* */ +/* The algorithm has not been optimized at all, so expect it to be slow */ +/* if computing lots of characters or very large sizes. */ + + + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* Finding the right font... */ +/* */ +/* You should really just solve this offline, keep your own tables */ +/* of what font is what, and don't try to get it out of the .ttf file. */ +/* That's because getting it out of the .ttf file is really hard, because */ +/* the names in the file can appear in many possible encodings, in many */ +/* possible languages, and e.g. if you need a case-insensitive comparison, */ +/* the details of that depend on the encoding & language in a complex way */ +/* (actually underspecified in truetype, but also gigantic). */ +/* */ +/* But you can use the provided functions in two possible ways: */ +/* stbtt_FindMatchingFont() will use *case-sensitive* comparisons on */ +/* unicode-encoded names to try to find the font you want; */ +/* you can run this before calling stbtt_InitFont() */ +/* */ +/* stbtt_GetFontNameString() lets you get any of the various strings */ +/* from the file yourself and do your own comparisons on them. */ +/* You have to have called stbtt_InitFont() first. */ + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +/* returns the offset (not index) of the font that matches, or -1 if none */ +/* if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". */ +/* if you use any other flag, use a font name like "Arial"; this checks */ +/* the 'macStyle' header field; i don't know if fonts set this consistently */ +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 /* <= not same as 0, this makes us check the bitfield is 0 */ + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +/* returns 1/0 whether the first string interpreted as utf8 is identical to */ +/* the second string interpreted as big-endian utf16... useful for strings from next func */ + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +/* returns the string (which may be big-endian double byte, e.g. for unicode) */ +/* and puts the length in bytes in *length. */ +/* */ +/* some of the values for the IDs are below; for more see the truetype spec: */ +/* http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html */ +/* http://www.microsoft.com/typography/otspec/name.htm */ + +enum { /* platformID */ + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { /* encodingID for STBTT_PLATFORM_ID_UNICODE */ + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { /* encodingID for STBTT_PLATFORM_ID_MICROSOFT */ + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { /* encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes */ + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { /* languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... */ + /* problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs */ + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { /* languageID for STBTT_PLATFORM_ID_MAC */ + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __STB_INCLUDE_STB_TRUETYPE_H__ */ + +/* ///////////////////////////////////////////////////////////////////////////// */ +/* ///////////////////////////////////////////////////////////////////////////// */ +/* // */ +/* // IMPLEMENTATION */ +/* // */ +/* // */ + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +/* //////////////////////////////////////////////////////////////////////// */ +/* */ +/* stbtt__buf helpers to parse data from file */ +/* */ + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +/* //////////////////////////////////////////////////////////////////////// */ +/* */ +/* accessors to parse data from file */ +/* */ + +/* on platforms that don't allow misaligned reads, if we want to allow */ +/* truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE */ + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + /* check the version number */ + if (stbtt_tag4(font, '1',0,0,0)) return 1; /* TrueType 1 */ + if (stbtt_tag(font, "typ1")) return 1; /* TrueType with type 1 font -- we don't support this! */ + if (stbtt_tag(font, "OTTO")) return 1; /* OpenType with CFF */ + if (stbtt_tag4(font, 0,1,0,0)) return 1; /* OpenType 1.0 */ + if (stbtt_tag(font, "true")) return 1; /* Apple specification for TrueType fonts */ + return 0; +} + +/* @OPTIMIZE: binary search */ +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + /* if it's just a font, there's only one valid index */ + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + /* check if it's a TTC */ + if (stbtt_tag(font_collection, "ttcf")) { + /* version 1? */ + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + /* if it's just a font, there's only one valid font */ + if (stbtt__isfont(font_collection)) + return 1; + + /* check if it's a TTC */ + if (stbtt_tag(font_collection, "ttcf")) { + /* version 1? */ + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +/* since most people won't use this, find this table the first time it's needed */ +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); /* required */ + info->loca = stbtt__find_table(data, fontstart, "loca"); /* required */ + info->head = stbtt__find_table(data, fontstart, "head"); /* required */ + info->glyf = stbtt__find_table(data, fontstart, "glyf"); /* required */ + info->hhea = stbtt__find_table(data, fontstart, "hhea"); /* required */ + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); /* required */ + info->kern = stbtt__find_table(data, fontstart, "kern"); /* not required */ + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); /* not required */ + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + /* required for truetype */ + if (!info->loca) return 0; + } else { + /* initialization for CFF / Type2 fonts (OTF) */ + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + /* @TODO this should use size from table (not 512MB) */ + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + /* read the header */ + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); /* hdrsize */ + + /* @TODO the name INDEX could list multiple fonts, */ + /* but we just use the first one. */ + stbtt__cff_get_index(&b); /* name INDEX */ + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); /* string INDEX */ + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + /* we only support Type 2 charstrings */ + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + /* looks like a CID font */ + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + /* find a cmap encoding table we understand *now* to avoid searching */ + /* later. (todo: could make this installable) */ + /* the same regardless of glyph. */ + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + /* find an encoding we understand: */ + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + /* MS/Unicode */ + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + /* Mac/iOS has these */ + /* all the encodingIDs are unicode, so we don't bother to check it */ + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { /* apple byte encoding */ + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); /* @TODO: high-byte mapping for japanese/chinese/korean */ + return 0; + } else if (format == 4) { /* standard mapping for windows fonts: binary search collection of ranges */ + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + /* do a binary search of the segments */ + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + /* they lie from endCount .. endCount + segCount */ + /* but searchRange is the nearest power of two, so... */ + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + /* now decrement to bias correctly to find smallest */ + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + /* Binary search the right group. */ + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); /* rounds down, so low <= mid < high */ + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else /* format == 13 */ + return start_glyph; + } + } + return 0; /* not found */ + } + /* @TODO */ + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; /* glyph index out of range */ + if (info->indexToLocFormat >= 2) return -1; /* unknown index->glyph map format */ + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; /* if length is 0, return -1 */ +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; /* a loose bound on how many vertices we might need */ + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + /* in first pass, we load uninterpreted data into the allocated array */ + /* above, shifted to the end of the array so we won't overwrite it when */ + /* we create our final data starting from the front */ + + off = m - n; /* starting offset for uninterpreted data, regardless of how m ends up being calculated */ + + /* first load flags */ + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + /* now load x coordinates */ + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; /* ??? */ + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + /* now load y coordinates */ + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; /* ??? */ + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + /* now convert them to our format */ + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + /* now start the new one */ + start_off = !(flags & 1); + if (start_off) { + /* if we start off with an off-curve point, then when we need to find a point on the curve */ + /* where we can start, and we need to save some state for when we wraparound. */ + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + /* next point is also a curve point, so interpolate an on-point curve */ + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + /* otherwise just use the next point as our start point */ + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; /* we're using point i+1 as the starting point, so skip it */ + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { /* if it's a curve */ + if (was_off) /* two off-curve control points in a row means interpolate an on-curve midpoint */ + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + /* Compound shapes. */ + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { /* XY values */ + if (flags & 1) { /* shorts */ + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + /* @TODO handle matching point */ + STBTT_assert(0); + } + if (flags & (1<<3)) { /* WE_HAVE_A_SCALE */ + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { /* WE_HAVE_AN_X_AND_YSCALE */ + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { /* WE_HAVE_A_TWO_BY_TWO */ + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + /* Find transformation scales. */ + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + /* Get indexed glyph. */ + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + /* Transform vertices. */ + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + /* Append vertices. */ + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + /* More components ? */ + more = flags & (1<<5); + } + } else { + /* numberOfCounters == 0, do nothing */ + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + /* untested */ + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + /* this currently ignores the initial width value, which isn't needed if we have hmtx */ + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + /* @TODO implement hinting */ + case 0x13: /* hintmask */ + case 0x14: /* cntrmask */ + if (in_header) + maskbits += (sp / 2); /* implicit "vstem" */ + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: /* hstem */ + case 0x03: /* vstem */ + case 0x12: /* hstemhm */ + case 0x17: /* vstemhm */ + maskbits += (sp / 2); + break; + + case 0x15: /* rmoveto */ + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: /* vmoveto */ + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: /* hmoveto */ + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: /* rlineto */ + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + /* hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical */ + /* starting from a different place. */ + + case 0x07: /* vlineto */ + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: /* hlineto */ + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: /* hvcurveto */ + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: /* vhcurveto */ + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: /* rrcurveto */ + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: /* rcurveline */ + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: /* rlinecurve */ + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: /* vvcurveto */ + case 0x1B: /* hhcurveto */ + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: /* callsubr */ + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + /* fallthrough */ + case 0x1D: /* callgsubr */ + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: /* return */ + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: /* endchar */ + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { /* two-byte escape */ + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + /* @TODO These "flex" implementations ignore the flex-depth and resolution, */ + /* and always draw beziers. */ + case 0x22: /* hflex */ + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: /* flex */ + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + /* fd is s[12] */ + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: /* hflex1 */ + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: /* flex1 */ + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + /* push immediate */ + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + /* runs the charstring twice, once to count and once to output (to avoid realloc) */ + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + /* we only look at the first table. it must be 'horizontal' and format 0. */ + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) /* number of tables, need at least 1 */ + return 0; + if (ttUSHORT(data+8) != 1) /* horizontal flag must be set in format */ + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + /* we only look at the first table. it must be 'horizontal' and format 0. */ + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) /* number of tables, need at least 1 */ + return 0; + if (ttUSHORT(data+8) != 1) /* horizontal flag must be set in format */ + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + /* we only look at the first table. it must be 'horizontal' and format 0. */ + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) /* number of tables, need at least 1 */ + return 0; + if (ttUSHORT(data+8) != 1) /* horizontal flag must be set in format */ + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); /* note: unaligned read */ + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + /* Binary search. */ + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + /* Binary search. */ + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + /* There are no other cases. */ + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + /* Binary search. */ + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + /* There are no other cases. */ + STBTT_assert(0); + } break; + } + + return -1; +} + +/* Define to STBTT_assert(x) if you want to break on unimplemented formats. */ +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; /* Major version 1 */ + if (ttUSHORT(data+2) != 0) return 0; /* Minor version 0 */ + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + /* TODO: Support more formats. */ + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + /* There are no other cases. */ + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + /* TODO: Implement other stuff. */ + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) /* if no kerning table, don't waste time looking up both codepoint->glyphs */ + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* antialiasing software rasterizer */ +/* */ + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; /* =0 suppresses compiler warning */ + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + /* e.g. space character */ + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + /* move to integral bboxes (treating pixels as little squares, what pixels get touched)? */ + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* Rasterizer */ + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + /* round dx down to avoid overshooting */ + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); /* use z->dx so when we offset later it's by the same amount */ + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + /* STBTT_assert(e->y0 <= start_point); */ + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +/* note: this routine clips fills that extend off the edges... ideally this */ +/* wouldn't happen, but it could happen if the truetype glyph bounding boxes */ +/* are wrong, or if the user supplies a too-small bitmap */ +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + /* non-zero winding fill */ + int x0=0, w=0; + + while (e) { + if (w == 0) { + /* if we're currently at zero, we need to record the edge start point */ + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + /* if we went to zero, we need to draw */ + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + /* x0,x1 are the same pixel, so compute combined coverage */ + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) /* add antialiasing for x0 */ + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; /* clip */ + + if (j < len) /* add antialiasing for x1 */ + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; /* clip */ + + for (++i; i < j; ++i) /* fill pixels between x0 and x1 */ + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); /* weight per vertical scanline */ + int s; /* vertical subsample index */ + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + /* find center of pixel for this scanline */ + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + /* update all active edges; */ + /* remove all active edges that terminate before the center of this scanline */ + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; /* delete from list */ + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; /* advance to position for current scanline */ + step = &((*step)->next); /* advance through list */ + } + } + + /* resort the list if needed */ + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + /* insert all edges that start before the center of this scanline -- omit ones that also end on this scanline */ + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + /* find insertion point */ + if (active == NULL) + active = z; + else if (z->x < active->x) { + /* insert at front */ + z->next = active; + active = z; + } else { + /* find thing to insert AFTER */ + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + /* at this point, p->next->x is NOT < z->x */ + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + /* now process all active edges in XOR fashion */ + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +/* the edge passed in here does not cross the vertical line at x or the vertical line at x+1 */ +/* (i.e. it has already been clipped to those) */ +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); /* coverage = 1 - average x position */ + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + /* brute force every pixel */ + + /* compute intersection points with top & bottom */ + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + /* compute endpoints of line segment clipped to this scanline (if the */ + /* line segment starts on this scanline. x0 is the intersection of the */ + /* line with y_top, but that may be off the line segment. */ + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + /* from here on, we don't have to range check x values */ + + if ((int) x_top == (int) x_bottom) { + float height; + /* simple case, only spans one pixel */ + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; /* everything right of this pixel is filled */ + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + /* covers 2+ pixels */ + if (x_top > x_bottom) { + /* flip scanline vertically; signed area is the same */ + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + /* compute intersection with y axis at x1+1 */ + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + /* area of the rectangle covered from y0..y_crossing */ + area = sign * (y_crossing-sy0); + /* area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) */ + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + /* if edge goes outside of box we're drawing, we require */ + /* clipping logic. since this does not match the intended use */ + /* of this library, we use a different, very slow brute */ + /* force implementation */ + int x; + for (x=0; x < len; ++x) { + /* cases: */ + /* */ + /* there can be up to two intersections with the pixel. any intersection */ + /* with left or right edges can be handled by splitting into two (or three) */ + /* regions. intersections with top & bottom do not necessitate case-wise logic. */ + /* */ + /* the old way of doing this found the intersections with the left & right edges, */ + /* then used some simple logic to produce up to three segments in sorted order */ + /* from top-to-bottom. however, this had a problem: if an x edge was epsilon */ + /* across the x border, then the corresponding y position might not be distinct */ + /* from the other y segment, and it might ignored as an empty segment. to avoid */ + /* that, we need to explicitly produce segments based on x positions. */ + + /* rename variables to clearly-defined pairs */ + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + /* x = e->x + e->dx * (y-y_top) */ + /* (y-y_top) = (x - e->x) / e->dx */ + /* y = (x - e->x) / e->dx + y_top */ + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { /* three segments descending down-right */ + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { /* three segments descending down-left */ + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { /* two segments across x, down-right */ + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { /* two segments across x, down-left */ + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { /* two segments across x+1, down-right */ + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { /* two segments across x+1, down-left */ + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { /* one segment */ + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +/* directly AA rasterize edges w/o supersampling */ +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + /* find center of pixel for this scanline */ + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + /* update all active edges; */ + /* remove all active edges that terminate before the top of this scanline */ + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; /* delete from list */ + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); /* advance through list */ + } + } + + /* insert all edges that start before the bottom of this scanline */ + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + /* this can happen due to subpixel positioning and some kind of fp rounding error i think */ + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); /* if we get really unlucky a tiny bit of an edge can be out of bounds */ + /* insert at front */ + z->next = active; + active = z; + } + } + ++e; + } + + /* now process all active edges */ + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + /* advance all the edges */ + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; /* advance to position for current scanline */ + step = &((*step)->next); /* advance through list */ + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + /* vsubsample should divide 255 evenly; otherwise we won't reach full opacity */ + + /* now we have to blow out the windings into explicit edge lists */ + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); /* add an extra one as a sentinel */ + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + /* skip the edge if horizontal */ + if (p[j].y == p[k].y) + continue; + /* add edge from j to k to the list */ + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + /* now sort the edges by their highest point (should snap to integer, and then by x) */ + /* STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); */ + stbtt__sort_edges(e, n); + + /* now, traverse the scanlines and find the intersections on each scanline, use xor winding rule */ + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; /* during first pass, it's unallocated */ + points[n].x = x; + points[n].y = y; +} + +/* tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching */ +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + /* midpoint */ + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + /* versus directly drawn line */ + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) /* 65536 segments on one curve better be enough! */ + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { /* half-pixel error allowed... need to be smaller if AA */ + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + /* @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough */ + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) /* 65536 segments on one curve better be enough! */ + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +/* returns number of contours */ +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + /* count how many "moves" there are to get the contour count */ + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + /* make two passes through the points so we don't need to realloc */ + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + /* start the next contour */ + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + /* now we get the size */ + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; /* in case we error */ + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* bitmap baking */ +/* */ +/* This is SUPER-CRAPPY packing to keep source code small */ + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, /* font location (use offset=0 for plain .ttf) */ + float pixel_height, /* height of font in pixels */ + unsigned char *pixels, int pw, int ph, /* bitmap to be filled in */ + int first_char, int num_chars, /* characters to bake */ + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); /* background of 0 around pixels */ + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; /* advance to next row */ + if (y + gh + 1 >= ph) /* check if it fits vertically AFTER potentially moving to next row */ + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* rectangle packing replacement routines if you don't have stb_rect_pack.h */ +/* */ + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +/* ////////////////////////////////////////////////////////////////////////////////// */ +/* // */ +/* // */ +/* COMPILER WARNING ?!?!? // */ +/* // */ +/* // */ +/* if you get a compile warning due to these symbols being defined more than // */ +/* once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // */ +/* // */ +/* ////////////////////////////////////////////////////////////////////////////////// */ + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* bitmap baking */ +/* */ +/* This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If */ +/* stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. */ + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); /* background of 0 around pixels */ + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); /* suppress bogus warning from VS2013 -analyze */ + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + /* make kernel_width a constant in common cases so compiler can optimize out the divide */ + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); /* suppress bogus warning from VS2013 -analyze */ + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + /* make kernel_width a constant in common cases so compiler can optimize out the divide */ + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + /* The prefilter is a box filter of width "oversample", */ + /* which shifts phase by (oversample - 1)/2 pixels in */ + /* oversampled space. We want to shift in the opposite */ + /* direction to counter this. */ + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +/* rects array must be big enough to accommodate all characters in the given ranges */ +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +/* rects array must be big enough to accommodate all characters in the given ranges */ +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + /* save current values */ + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + /* pad on left and top */ + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; /* if any fail, report failure */ + } + + ++k; + } + } + + /* restore original values */ + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + /* stbrp_context *context = (stbrp_context *) spc->pack_info; */ + stbrp_rect *rects; + + /* flag all characters as NOT packed */ + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* sdf computation */ +/* */ + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + /* 2*b*s + c = 0 */ + /* s = -c / (2*b) */ + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + /* make sure y never passes through a vertex of the shape */ + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + /* test a ray from (-infinity,y) to (x,y) */ + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +/* x^3 + c*x^2 + b*x + a = 0 */ +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; /* p3 must be negative, since d is negative */ + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + /* STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? */ + /* STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); */ + /* STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); */ + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + /* if empty, return NULL */ + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + /* invert for y-downwards bitmaps */ + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); /* @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path */ + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + /* check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve */ + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + /* coarse culling against bbox */ + /* if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && */ + /* sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) */ + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + /* check position along line */ + /* x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) */ + /* minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) */ + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + /* minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy */ + /* derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve */ + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + /* coarse culling against bbox to avoid computing cubic unnecessarily */ + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { /* if a_inv is 0, it's 2nd degree so use quadratic formula */ + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { /* if a is 0, it's linear */ + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; /* don't bother distinguishing 1-solution case, as code below will still work */ + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; /* could precompute this as it doesn't depend on sample point */ + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; /* if outside the shape, value is negative */ + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +/* //////////////////////////////////////////////////////////////////////////// */ +/* */ +/* font name matching -- recommended not to use this */ +/* */ + +/* check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string */ +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + /* convert utf16 to utf8 and compare the results while converting */ + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; /* plus another 2 below */ + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +/* returns results in whatever encoding you request... but note that 2-byte encodings */ +/* will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare */ +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + /* find the encoding */ + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + /* is this a Unicode encoding? */ + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + /* check if there's a prefix match */ + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + /* check for target_id+1 immediately following, with same encoding & language */ + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + /* if nothing immediately following */ + if (matchlen == nlen) + return 1; + } + } + } + + /* @TODO handle other encodings */ + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + /* check italics/bold/underline flags in macStyle... */ + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + /* if we checked the macStyle flags, then just check the family and ignore the subfamily */ + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif /* STB_TRUETYPE_IMPLEMENTATION */ + + +/* FULL VERSION HISTORY */ +/* */ +/* 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod */ +/* 1.18 (2018-01-29) add missing function */ +/* 1.17 (2017-07-23) make more arguments const; doc fix */ +/* 1.16 (2017-07-12) SDF support */ +/* 1.15 (2017-03-03) make more arguments const */ +/* 1.14 (2017-01-16) num-fonts-in-TTC function */ +/* 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts */ +/* 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual */ +/* 1.11 (2016-04-02) fix unused-variable warning */ +/* 1.10 (2016-04-02) allow user-defined fabs() replacement */ +/* fix memory leak if fontsize=0.0 */ +/* fix warning from duplicate typedef */ +/* 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges */ +/* 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges */ +/* 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; */ +/* allow PackFontRanges to pack and render in separate phases; */ +/* fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); */ +/* fixed an assert() bug in the new rasterizer */ +/* replace assert() with STBTT_assert() in new rasterizer */ +/* 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) */ +/* also more precise AA rasterizer, except if shapes overlap */ +/* remove need for STBTT_sort */ +/* 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC */ +/* 1.04 (2015-04-15) typo in example */ +/* 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes */ +/* 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ */ +/* 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match */ +/* non-oversampled; STBTT_POINT_SIZE for packed case only */ +/* 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling */ +/* 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) */ +/* 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID */ +/* 0.8b (2014-07-07) fix a warning */ +/* 0.8 (2014-05-25) fix a few more warnings */ +/* 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back */ +/* 0.6c (2012-07-24) improve documentation */ +/* 0.6b (2012-07-20) fix a few more warnings */ +/* 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, */ +/* stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty */ +/* 0.5 (2011-12-09) bugfixes: */ +/* subpixel glyph renderer computed wrong bounding box */ +/* first vertex of shape can be off-curve (FreeSans) */ +/* 0.4b (2011-12-03) fixed an error in the font baking example */ +/* 0.4 (2011-12-01) kerning, subpixel rendering (tor) */ +/* bugfixes for: */ +/* codepoint-to-glyph conversion using table fmt=12 */ +/* codepoint-to-glyph conversion using table fmt=4 */ +/* stbtt_GetBakedQuad with non-square texture (Zer) */ +/* updated Hello World! sample to use kerning and subpixel */ +/* fixed some warnings */ +/* 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) */ +/* userdata, malloc-from-userdata, non-zero fill (stb) */ +/* 0.2 (2009-03-11) Fix unsigned/signed char warnings */ +/* 0.1 (2009-03-09) First public release */ +/* */ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ + + + + +#ifdef NK_INCLUDE_FONT_BAKING +/* ------------------------------------------------------------- + * + * RECT PACK + * + * --------------------------------------------------------------*/ + + + +/* + * ============================================================== + * + * TRUETYPE + * + * =============================================================== + */ +#define STBTT_MAX_OVERSAMPLE 8 + + +/* ------------------------------------------------------------- + * + * FONT BAKING + * + * --------------------------------------------------------------*/ +struct nk_font_bake_data { + struct stbtt_fontinfo info; + struct stbrp_rect *rects; + stbtt_pack_range *ranges; + nk_rune range_count; +}; + +struct nk_font_baker { + struct nk_allocator alloc; + struct stbtt_pack_context spc; + struct nk_font_bake_data *build; + stbtt_packedchar *packed_chars; + struct stbrp_rect *rects; + stbtt_pack_range *ranges; +}; + +NK_GLOBAL const nk_size nk_rect_align = NK_ALIGNOF(struct stbrp_rect); +NK_GLOBAL const nk_size nk_range_align = NK_ALIGNOF(stbtt_pack_range); +NK_GLOBAL const nk_size nk_char_align = NK_ALIGNOF(stbtt_packedchar); +NK_GLOBAL const nk_size nk_build_align = NK_ALIGNOF(struct nk_font_bake_data); +NK_GLOBAL const nk_size nk_baker_align = NK_ALIGNOF(struct nk_font_baker); + +NK_INTERN int +nk_range_count(const nk_rune *range) +{ + const nk_rune *iter = range; + NK_ASSERT(range); + if (!range) return 0; + while (*(iter++) != 0); + return (iter == range) ? 0 : (int)((iter - range)/2); +} +NK_INTERN int +nk_range_glyph_count(const nk_rune *range, int count) +{ + int i = 0; + int total_glyphs = 0; + for (i = 0; i < count; ++i) { + int diff; + nk_rune f = range[(i*2)+0]; + nk_rune t = range[(i*2)+1]; + NK_ASSERT(t >= f); + diff = (int)((t - f) + 1); + total_glyphs += diff; + } + return total_glyphs; +} +NK_API const nk_rune* +nk_font_default_glyph_ranges(void) +{ + NK_STORAGE const nk_rune ranges[] = {0x0020, 0x00FF, 0}; + return ranges; +} +NK_API const nk_rune* +nk_font_chinese_glyph_ranges(void) +{ + NK_STORAGE const nk_rune ranges[] = { + 0x0020, 0x00FF, + 0x3000, 0x30FF, + 0x31F0, 0x31FF, + 0xFF00, 0xFFEF, + 0x4e00, 0x9FAF, + 0 + }; + return ranges; +} +NK_API const nk_rune* +nk_font_cyrillic_glyph_ranges(void) +{ + NK_STORAGE const nk_rune ranges[] = { + 0x0020, 0x00FF, + 0x0400, 0x052F, + 0x2DE0, 0x2DFF, + 0xA640, 0xA69F, + 0 + }; + return ranges; +} +NK_API const nk_rune* +nk_font_korean_glyph_ranges(void) +{ + NK_STORAGE const nk_rune ranges[] = { + 0x0020, 0x00FF, + 0x3131, 0x3163, + 0xAC00, 0xD79D, + 0 + }; + return ranges; +} +NK_INTERN void +nk_font_baker_memory(nk_size *temp, int *glyph_count, + struct nk_font_config *config_list, int count) +{ + int range_count = 0; + int total_range_count = 0; + struct nk_font_config *iter, *i; + + NK_ASSERT(config_list); + NK_ASSERT(glyph_count); + if (!config_list) { + *temp = 0; + *glyph_count = 0; + return; + } + *glyph_count = 0; + for (iter = config_list; iter; iter = iter->next) { + i = iter; + do {if (!i->range) iter->range = nk_font_default_glyph_ranges(); + range_count = nk_range_count(i->range); + total_range_count += range_count; + *glyph_count += nk_range_glyph_count(i->range, range_count); + } while ((i = i->n) != iter); + } + *temp = (nk_size)*glyph_count * sizeof(struct stbrp_rect); + *temp += (nk_size)total_range_count * sizeof(stbtt_pack_range); + *temp += (nk_size)*glyph_count * sizeof(stbtt_packedchar); + *temp += (nk_size)count * sizeof(struct nk_font_bake_data); + *temp += sizeof(struct nk_font_baker); + *temp += nk_rect_align + nk_range_align + nk_char_align; + *temp += nk_build_align + nk_baker_align; +} +NK_INTERN struct nk_font_baker* +nk_font_baker(void *memory, int glyph_count, int count, struct nk_allocator *alloc) +{ + struct nk_font_baker *baker; + if (!memory) return 0; + /* setup baker inside a memory block */ + baker = (struct nk_font_baker*)NK_ALIGN_PTR(memory, nk_baker_align); + baker->build = (struct nk_font_bake_data*)NK_ALIGN_PTR((baker + 1), nk_build_align); + baker->packed_chars = (stbtt_packedchar*)NK_ALIGN_PTR((baker->build + count), nk_char_align); + baker->rects = (struct stbrp_rect*)NK_ALIGN_PTR((baker->packed_chars + glyph_count), nk_rect_align); + baker->ranges = (stbtt_pack_range*)NK_ALIGN_PTR((baker->rects + glyph_count), nk_range_align); + baker->alloc = *alloc; + return baker; +} +NK_INTERN int +nk_font_bake_pack(struct nk_font_baker *baker, + nk_size *image_memory, int *width, int *height, struct nk_recti *custom, + const struct nk_font_config *config_list, int count, + struct nk_allocator *alloc) +{ + NK_STORAGE const nk_size max_height = 1024 * 32; + const struct nk_font_config *config_iter, *it; + int total_glyph_count = 0; + int total_range_count = 0; + int range_count = 0; + int i = 0; + + NK_ASSERT(image_memory); + NK_ASSERT(width); + NK_ASSERT(height); + NK_ASSERT(config_list); + NK_ASSERT(count); + NK_ASSERT(alloc); + + if (!image_memory || !width || !height || !config_list || !count) return nk_false; + for (config_iter = config_list; config_iter; config_iter = config_iter->next) { + it = config_iter; + do {range_count = nk_range_count(it->range); + total_range_count += range_count; + total_glyph_count += nk_range_glyph_count(it->range, range_count); + } while ((it = it->n) != config_iter); + } + /* setup font baker from temporary memory */ + for (config_iter = config_list; config_iter; config_iter = config_iter->next) { + struct stbtt_fontinfo *font_info = &baker->build[i++].info; + it = config_iter; + font_info->userdata = alloc; + do {if (!stbtt_InitFont(font_info, (const unsigned char*)it->ttf_blob, 0)) + return nk_false; + } while ((it = it->n) != config_iter); + } + *height = 0; + *width = (total_glyph_count > 1000) ? 1024 : 512; + stbtt_PackBegin(&baker->spc, 0, (int)*width, (int)max_height, 0, 1, alloc); + { + int input_i = 0; + int range_n = 0; + int rect_n = 0; + int char_n = 0; + + if (custom) { + /* pack custom user data first so it will be in the upper left corner*/ + struct stbrp_rect custom_space; + nk_zero(&custom_space, sizeof(custom_space)); + custom_space.w = (stbrp_coord)(custom->w); + custom_space.h = (stbrp_coord)(custom->h); + + stbtt_PackSetOversampling(&baker->spc, 1, 1); + stbrp_pack_rects((struct stbrp_context*)baker->spc.pack_info, &custom_space, 1); + *height = NK_MAX(*height, (int)(custom_space.y + custom_space.h)); + + custom->x = (short)custom_space.x; + custom->y = (short)custom_space.y; + custom->w = (short)custom_space.w; + custom->h = (short)custom_space.h; + } + + /* first font pass: pack all glyphs */ + for (input_i = 0, config_iter = config_list; input_i < count && config_iter; + config_iter = config_iter->next) { + it = config_iter; + do {int n = 0; + int glyph_count; + const nk_rune *in_range; + const struct nk_font_config *cfg = it; + struct nk_font_bake_data *tmp = &baker->build[input_i++]; + + /* count glyphs + ranges in current font */ + glyph_count = 0; range_count = 0; + for (in_range = cfg->range; in_range[0] && in_range[1]; in_range += 2) { + glyph_count += (int)(in_range[1] - in_range[0]) + 1; + range_count++; + } + + /* setup ranges */ + tmp->ranges = baker->ranges + range_n; + tmp->range_count = (nk_rune)range_count; + range_n += range_count; + for (i = 0; i < range_count; ++i) { + in_range = &cfg->range[i * 2]; + tmp->ranges[i].font_size = cfg->size; + tmp->ranges[i].first_unicode_codepoint_in_range = (int)in_range[0]; + tmp->ranges[i].num_chars = (int)(in_range[1]- in_range[0]) + 1; + tmp->ranges[i].chardata_for_range = baker->packed_chars + char_n; + char_n += tmp->ranges[i].num_chars; + } + + /* pack */ + tmp->rects = baker->rects + rect_n; + rect_n += glyph_count; + stbtt_PackSetOversampling(&baker->spc, cfg->oversample_h, cfg->oversample_v); + n = stbtt_PackFontRangesGatherRects(&baker->spc, &tmp->info, + tmp->ranges, (int)tmp->range_count, tmp->rects); + stbrp_pack_rects((struct stbrp_context*)baker->spc.pack_info, tmp->rects, (int)n); + + /* texture height */ + for (i = 0; i < n; ++i) { + if (tmp->rects[i].was_packed) + *height = NK_MAX(*height, tmp->rects[i].y + tmp->rects[i].h); + } + } while ((it = it->n) != config_iter); + } + NK_ASSERT(rect_n == total_glyph_count); + NK_ASSERT(char_n == total_glyph_count); + NK_ASSERT(range_n == total_range_count); + } + *height = (int)nk_round_up_pow2((nk_uint)*height); + *image_memory = (nk_size)(*width) * (nk_size)(*height); + return nk_true; +} +NK_INTERN void +nk_font_bake(struct nk_font_baker *baker, void *image_memory, int width, int height, + struct nk_font_glyph *glyphs, int glyphs_count, + const struct nk_font_config *config_list, int font_count) +{ + int input_i = 0; + nk_rune glyph_n = 0; + const struct nk_font_config *config_iter; + const struct nk_font_config *it; + + NK_ASSERT(image_memory); + NK_ASSERT(width); + NK_ASSERT(height); + NK_ASSERT(config_list); + NK_ASSERT(baker); + NK_ASSERT(font_count); + NK_ASSERT(glyphs_count); + if (!image_memory || !width || !height || !config_list || + !font_count || !glyphs || !glyphs_count) + return; + + /* second font pass: render glyphs */ + nk_zero(image_memory, (nk_size)((nk_size)width * (nk_size)height)); + baker->spc.pixels = (unsigned char*)image_memory; + baker->spc.height = (int)height; + for (input_i = 0, config_iter = config_list; input_i < font_count && config_iter; + config_iter = config_iter->next) { + it = config_iter; + do {const struct nk_font_config *cfg = it; + struct nk_font_bake_data *tmp = &baker->build[input_i++]; + stbtt_PackSetOversampling(&baker->spc, cfg->oversample_h, cfg->oversample_v); + stbtt_PackFontRangesRenderIntoRects(&baker->spc, &tmp->info, tmp->ranges, (int)tmp->range_count, tmp->rects); + } while ((it = it->n) != config_iter); + } stbtt_PackEnd(&baker->spc); + + /* third pass: setup font and glyphs */ + for (input_i = 0, config_iter = config_list; input_i < font_count && config_iter; + config_iter = config_iter->next) { + it = config_iter; + do {nk_size i = 0; + int char_idx = 0; + nk_rune glyph_count = 0; + const struct nk_font_config *cfg = it; + struct nk_font_bake_data *tmp = &baker->build[input_i++]; + struct nk_baked_font *dst_font = cfg->font; + + float font_scale = stbtt_ScaleForPixelHeight(&tmp->info, cfg->size); + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&tmp->info, &unscaled_ascent, &unscaled_descent, + &unscaled_line_gap); + + /* fill baked font */ + if (!cfg->merge_mode) { + dst_font->ranges = cfg->range; + dst_font->height = cfg->size; + dst_font->ascent = ((float)unscaled_ascent * font_scale); + dst_font->descent = ((float)unscaled_descent * font_scale); + dst_font->glyph_offset = glyph_n; + /* + Need to zero this, or it will carry over from a previous + bake, and cause a segfault when accessing glyphs[]. + */ + dst_font->glyph_count = 0; + } + + /* fill own baked font glyph array */ + for (i = 0; i < tmp->range_count; ++i) { + stbtt_pack_range *range = &tmp->ranges[i]; + for (char_idx = 0; char_idx < range->num_chars; char_idx++) + { + nk_rune codepoint = 0; + float dummy_x = 0, dummy_y = 0; + stbtt_aligned_quad q; + struct nk_font_glyph *glyph; + + /* query glyph bounds from stb_truetype */ + const stbtt_packedchar *pc = &range->chardata_for_range[char_idx]; + if (!pc->x0 && !pc->x1 && !pc->y0 && !pc->y1) continue; + codepoint = (nk_rune)(range->first_unicode_codepoint_in_range + char_idx); + stbtt_GetPackedQuad(range->chardata_for_range, (int)width, + (int)height, char_idx, &dummy_x, &dummy_y, &q, 0); + + /* fill own glyph type with data */ + glyph = &glyphs[dst_font->glyph_offset + dst_font->glyph_count + (unsigned int)glyph_count]; + glyph->codepoint = codepoint; + glyph->x0 = q.x0; glyph->y0 = q.y0; + glyph->x1 = q.x1; glyph->y1 = q.y1; + glyph->y0 += (dst_font->ascent + 0.5f); + glyph->y1 += (dst_font->ascent + 0.5f); + glyph->w = glyph->x1 - glyph->x0 + 0.5f; + glyph->h = glyph->y1 - glyph->y0; + + if (cfg->coord_type == NK_COORD_PIXEL) { + glyph->u0 = q.s0 * (float)width; + glyph->v0 = q.t0 * (float)height; + glyph->u1 = q.s1 * (float)width; + glyph->v1 = q.t1 * (float)height; + } else { + glyph->u0 = q.s0; + glyph->v0 = q.t0; + glyph->u1 = q.s1; + glyph->v1 = q.t1; + } + glyph->xadvance = (pc->xadvance + cfg->spacing.x); + if (cfg->pixel_snap) + glyph->xadvance = (float)(int)(glyph->xadvance + 0.5f); + glyph_count++; + } + } + dst_font->glyph_count += glyph_count; + glyph_n += glyph_count; + } while ((it = it->n) != config_iter); + } +} +NK_INTERN void +nk_font_bake_custom_data(void *img_memory, int img_width, int img_height, + struct nk_recti img_dst, const char *texture_data_mask, int tex_width, + int tex_height, char white, char black) +{ + nk_byte *pixels; + int y = 0; + int x = 0; + int n = 0; + + NK_ASSERT(img_memory); + NK_ASSERT(img_width); + NK_ASSERT(img_height); + NK_ASSERT(texture_data_mask); + NK_UNUSED(tex_height); + if (!img_memory || !img_width || !img_height || !texture_data_mask) + return; + + pixels = (nk_byte*)img_memory; + for (y = 0, n = 0; y < tex_height; ++y) { + for (x = 0; x < tex_width; ++x, ++n) { + const int off0 = ((img_dst.x + x) + (img_dst.y + y) * img_width); + const int off1 = off0 + 1 + tex_width; + pixels[off0] = (texture_data_mask[n] == white) ? 0xFF : 0x00; + pixels[off1] = (texture_data_mask[n] == black) ? 0xFF : 0x00; + } + } +} +NK_INTERN void +nk_font_bake_convert(void *out_memory, int img_width, int img_height, + const void *in_memory) +{ + int n = 0; + nk_rune *dst; + const nk_byte *src; + + NK_ASSERT(out_memory); + NK_ASSERT(in_memory); + NK_ASSERT(img_width); + NK_ASSERT(img_height); + if (!out_memory || !in_memory || !img_height || !img_width) return; + + dst = (nk_rune*)out_memory; + src = (const nk_byte*)in_memory; + for (n = (int)(img_width * img_height); n > 0; n--) + *dst++ = ((nk_rune)(*src++) << 24) | 0x00FFFFFF; +} + +/* ------------------------------------------------------------- + * + * FONT + * + * --------------------------------------------------------------*/ +NK_INTERN float +nk_font_text_width(nk_handle handle, float height, const char *text, int len) +{ + nk_rune unicode; + int text_len = 0; + float text_width = 0; + int glyph_len = 0; + float scale = 0; + + struct nk_font *font = (struct nk_font*)handle.ptr; + NK_ASSERT(font); + NK_ASSERT(font->glyphs); + if (!font || !text || !len) + return 0; + + scale = height/font->info.height; + glyph_len = text_len = nk_utf_decode(text, &unicode, (int)len); + if (!glyph_len) return 0; + while (text_len <= (int)len && glyph_len) { + const struct nk_font_glyph *g; + if (unicode == NK_UTF_INVALID) break; + + /* query currently drawn glyph information */ + g = nk_font_find_glyph(font, unicode); + text_width += g->xadvance * scale; + + /* offset next glyph */ + glyph_len = nk_utf_decode(text + text_len, &unicode, (int)len - text_len); + text_len += glyph_len; + } + return text_width; +} +#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT +NK_INTERN void +nk_font_query_font_glyph(nk_handle handle, float height, + struct nk_user_font_glyph *glyph, nk_rune codepoint, nk_rune next_codepoint) +{ + float scale; + const struct nk_font_glyph *g; + struct nk_font *font; + + NK_ASSERT(glyph); + NK_UNUSED(next_codepoint); + + font = (struct nk_font*)handle.ptr; + NK_ASSERT(font); + NK_ASSERT(font->glyphs); + if (!font || !glyph) + return; + + scale = height/font->info.height; + g = nk_font_find_glyph(font, codepoint); + glyph->width = (g->x1 - g->x0) * scale; + glyph->height = (g->y1 - g->y0) * scale; + glyph->offset = nk_vec2(g->x0 * scale, g->y0 * scale); + glyph->xadvance = (g->xadvance * scale); + glyph->uv[0] = nk_vec2(g->u0, g->v0); + glyph->uv[1] = nk_vec2(g->u1, g->v1); +} +#endif +NK_API const struct nk_font_glyph* +nk_font_find_glyph(struct nk_font *font, nk_rune unicode) +{ + int i = 0; + int count; + int total_glyphs = 0; + const struct nk_font_glyph *glyph = 0; + const struct nk_font_config *iter = 0; + + NK_ASSERT(font); + NK_ASSERT(font->glyphs); + NK_ASSERT(font->info.ranges); + if (!font || !font->glyphs) return 0; + + glyph = font->fallback; + iter = font->config; + do {count = nk_range_count(iter->range); + for (i = 0; i < count; ++i) { + nk_rune f = iter->range[(i*2)+0]; + nk_rune t = iter->range[(i*2)+1]; + int diff = (int)((t - f) + 1); + if (unicode >= f && unicode <= t) + return &font->glyphs[((nk_rune)total_glyphs + (unicode - f))]; + total_glyphs += diff; + } + } while ((iter = iter->n) != font->config); + return glyph; +} +NK_INTERN void +nk_font_init(struct nk_font *font, float pixel_height, + nk_rune fallback_codepoint, struct nk_font_glyph *glyphs, + const struct nk_baked_font *baked_font, nk_handle atlas) +{ + struct nk_baked_font baked; + NK_ASSERT(font); + NK_ASSERT(glyphs); + NK_ASSERT(baked_font); + if (!font || !glyphs || !baked_font) + return; + + baked = *baked_font; + font->fallback = 0; + font->info = baked; + font->scale = (float)pixel_height / (float)font->info.height; + font->glyphs = &glyphs[baked_font->glyph_offset]; + font->texture = atlas; + font->fallback_codepoint = fallback_codepoint; + font->fallback = nk_font_find_glyph(font, fallback_codepoint); + + font->handle.height = font->info.height * font->scale; + font->handle.width = nk_font_text_width; + font->handle.userdata.ptr = font; +#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT + font->handle.query = nk_font_query_font_glyph; + font->handle.texture = font->texture; +#endif +} + +/* --------------------------------------------------------------------------- + * + * DEFAULT FONT + * + * ProggyClean.ttf + * Copyright (c) 2004, 2005 Tristan Grimmer + * MIT license (see License.txt in http://www.upperbounds.net/download/ProggyClean.ttf.zip) + * Download and more information at http://upperbounds.net + *-----------------------------------------------------------------------------*/ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Woverlength-strings" +#elif defined(__GNUC__) || defined(__GNUG__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Woverlength-strings" +#endif + +#ifdef NK_INCLUDE_DEFAULT_FONT + +NK_GLOBAL const char nk_proggy_clean_ttf_compressed_data_base85[11980+1] = + "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/" + "2*>]b(MC;$jPfY.;h^`IWM9Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1=Ke$$'5F%)]0^#0X@U.a$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;--VsM.M0rJfLH2eTM`*oJMHRC`N" + "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`�j@'DbG&#^$PG.Ll+DNa&VZ>1i%h1S9u5o@YaaW$e+bROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc." + "x]Ip.PH^'/aqUO/$1WxLoW0[iLAw=4h(9.`G" + "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?Ggv:[7MI2k).'2($5FNP&EQ(,)" + "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#" + "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM" + "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu" + "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/" + "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[Ket`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO" + "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%" + "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$MhLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]" + "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et" + "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:" + "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VBpqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<-+k?'(^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M" + "D?@f&1'BW-)Ju#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX(" + "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs" + "bIu)'Z,*[>br5fX^:FPAWr-m2KgLQ_nN6'8uTGT5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q" + "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aege0jT6'N#(q%.O=?2S]u*(m<-" + "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i" + "sZ88+dKQ)W6>J%CL`.d*(B`-n8D9oK-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P r+$%CE=68>K8r0=dSC%%(@p7" + ".m7jilQ02'0-VWAgTlGW'b)Tq7VT9q^*^$$.:&N@@" + "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*" + "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u" + "@-W$U%VEQ/,,>>#)D#%8cY#YZ?=,`Wdxu/ae&#" + "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$so8lKN%5/$(vdfq7+ebA#" + "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoFDoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8" + "6e%B/:=>)N4xeW.*wft-;$'58-ESqr#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#" + "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjLV#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#SfD07&6D@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5" + "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%" + "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;" + "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmLq9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:" + "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3$U4O]GKx'm9)b@p7YsvK3w^YR-" + "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*" + "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdFTi1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IXSsDiWP,##P`%/L-" + "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdFl*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj" + "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#$(>.Z-I&J(Q0Hd5Q%7Co-b`-cP)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8WlA2);Sa" + ">gXm8YB`1d@K#n]76-a$U,mF%Ul:#/'xoFM9QX-$.QN'>" + "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I" + "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-uW%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)" + "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo" + "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P" + "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*'IAO" + "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#" + ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T" + "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4" + "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#" + "/QHC#3^ZC#7jmC#;v)D#?,)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP" + "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp" + "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#"; + +#endif /* NK_INCLUDE_DEFAULT_FONT */ + +#define NK_CURSOR_DATA_W 90 +#define NK_CURSOR_DATA_H 27 +NK_GLOBAL const char nk_custom_cursor_data[NK_CURSOR_DATA_W * NK_CURSOR_DATA_H + 1] = +{ + "..- -XXXXXXX- X - X -XXXXXXX - XXXXXXX" + "..- -X.....X- X.X - X.X -X.....X - X.....X" + "--- -XXX.XXX- X...X - X...X -X....X - X....X" + "X - X.X - X.....X - X.....X -X...X - X...X" + "XX - X.X -X.......X- X.......X -X..X.X - X.X..X" + "X.X - X.X -XXXX.XXXX- XXXX.XXXX -X.X X.X - X.X X.X" + "X..X - X.X - X.X - X.X -XX X.X - X.X XX" + "X...X - X.X - X.X - XX X.X XX - X.X - X.X " + "X....X - X.X - X.X - X.X X.X X.X - X.X - X.X " + "X.....X - X.X - X.X - X..X X.X X..X - X.X - X.X " + "X......X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X XX-XX X.X " + "X.......X - X.X - X.X -X.....................X- X.X X.X-X.X X.X " + "X........X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X..X-X..X.X " + "X.........X -XXX.XXX- X.X - X..X X.X X..X - X...X-X...X " + "X..........X-X.....X- X.X - X.X X.X X.X - X....X-X....X " + "X......XXXXX-XXXXXXX- X.X - XX X.X XX - X.....X-X.....X " + "X...X..X --------- X.X - X.X - XXXXXXX-XXXXXXX " + "X..X X..X - -XXXX.XXXX- XXXX.XXXX ------------------------------------" + "X.X X..X - -X.......X- X.......X - XX XX - " + "XX X..X - - X.....X - X.....X - X.X X.X - " + " X..X - X...X - X...X - X..X X..X - " + " XX - X.X - X.X - X...XXXXXXXXXXXXX...X - " + "------------ - X - X -X.....................X- " + " ----------------------------------- X...XXXXXXXXXXXXX...X - " + " - X..X X..X - " + " - X.X X.X - " + " - XX XX - " +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#elif defined(__GNUC__) || defined(__GNUG__) +#pragma GCC diagnostic pop +#endif + +NK_GLOBAL unsigned char *nk__barrier; +NK_GLOBAL unsigned char *nk__barrier2; +NK_GLOBAL unsigned char *nk__barrier3; +NK_GLOBAL unsigned char *nk__barrier4; +NK_GLOBAL unsigned char *nk__dout; + +NK_INTERN unsigned int +nk_decompress_length(unsigned char *input) +{ + return (unsigned int)((input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11]); +} +NK_INTERN void +nk__match(unsigned char *data, unsigned int length) +{ + /* INVERSE of memmove... write each byte before copying the next...*/ + NK_ASSERT (nk__dout + length <= nk__barrier); + if (nk__dout + length > nk__barrier) { nk__dout += length; return; } + if (data < nk__barrier4) { nk__dout = nk__barrier+1; return; } + while (length--) *nk__dout++ = *data++; +} +NK_INTERN void +nk__lit(unsigned char *data, unsigned int length) +{ + NK_ASSERT (nk__dout + length <= nk__barrier); + if (nk__dout + length > nk__barrier) { nk__dout += length; return; } + if (data < nk__barrier2) { nk__dout = nk__barrier+1; return; } + NK_MEMCPY(nk__dout, data, length); + nk__dout += length; +} +NK_INTERN unsigned char* +nk_decompress_token(unsigned char *i) +{ + #define nk__in2(x) ((i[x] << 8) + i[(x)+1]) + #define nk__in3(x) ((i[x] << 16) + nk__in2((x)+1)) + #define nk__in4(x) ((i[x] << 24) + nk__in3((x)+1)) + + if (*i >= 0x20) { /* use fewer if's for cases that expand small */ + if (*i >= 0x80) nk__match(nk__dout-i[1]-1, (unsigned int)i[0] - 0x80 + 1), i += 2; + else if (*i >= 0x40) nk__match(nk__dout-(nk__in2(0) - 0x4000 + 1), (unsigned int)i[2]+1), i += 3; + else /* *i >= 0x20 */ nk__lit(i+1, (unsigned int)i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1); + } else { /* more ifs for cases that expand large, since overhead is amortized */ + if (*i >= 0x18) nk__match(nk__dout-(unsigned int)(nk__in3(0) - 0x180000 + 1), (unsigned int)i[3]+1), i += 4; + else if (*i >= 0x10) nk__match(nk__dout-(unsigned int)(nk__in3(0) - 0x100000 + 1), (unsigned int)nk__in2(3)+1), i += 5; + else if (*i >= 0x08) nk__lit(i+2, (unsigned int)nk__in2(0) - 0x0800 + 1), i += 2 + (nk__in2(0) - 0x0800 + 1); + else if (*i == 0x07) nk__lit(i+3, (unsigned int)nk__in2(1) + 1), i += 3 + (nk__in2(1) + 1); + else if (*i == 0x06) nk__match(nk__dout-(unsigned int)(nk__in3(1)+1), i[4]+1u), i += 5; + else if (*i == 0x04) nk__match(nk__dout-(unsigned int)(nk__in3(1)+1), (unsigned int)nk__in2(4)+1u), i += 6; + } + return i; +} +NK_INTERN unsigned int +nk_adler32(unsigned int adler32, unsigned char *buffer, unsigned int buflen) +{ + const unsigned long ADLER_MOD = 65521; + unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16; + unsigned long blocklen, i; + + blocklen = buflen % 5552; + while (buflen) { + for (i=0; i + 7 < blocklen; i += 8) { + s1 += buffer[0]; s2 += s1; + s1 += buffer[1]; s2 += s1; + s1 += buffer[2]; s2 += s1; + s1 += buffer[3]; s2 += s1; + s1 += buffer[4]; s2 += s1; + s1 += buffer[5]; s2 += s1; + s1 += buffer[6]; s2 += s1; + s1 += buffer[7]; s2 += s1; + buffer += 8; + } + for (; i < blocklen; ++i) { + s1 += *buffer++; s2 += s1; + } + + s1 %= ADLER_MOD; s2 %= ADLER_MOD; + buflen -= (unsigned int)blocklen; + blocklen = 5552; + } + return (unsigned int)(s2 << 16) + (unsigned int)s1; +} +NK_INTERN unsigned int +nk_decompress(unsigned char *output, unsigned char *i, unsigned int length) +{ + unsigned int olen; + if (nk__in4(0) != 0x57bC0000) return 0; + if (nk__in4(4) != 0) return 0; /* error! stream is > 4GB */ + olen = nk_decompress_length(i); + nk__barrier2 = i; + nk__barrier3 = i+length; + nk__barrier = output + olen; + nk__barrier4 = output; + i += 16; + + nk__dout = output; + for (;;) { + unsigned char *old_i = i; + i = nk_decompress_token(i); + if (i == old_i) { + if (*i == 0x05 && i[1] == 0xfa) { + NK_ASSERT(nk__dout == output + olen); + if (nk__dout != output + olen) return 0; + if (nk_adler32(1, output, olen) != (unsigned int) nk__in4(2)) + return 0; + return olen; + } else { + NK_ASSERT(0); /* NOTREACHED */ + return 0; + } + } + NK_ASSERT(nk__dout <= output + olen); + if (nk__dout > output + olen) + return 0; + } +} +NK_INTERN unsigned int +nk_decode_85_byte(char c) +{ + return (unsigned int)((c >= '\\') ? c-36 : c-35); +} +NK_INTERN void +nk_decode_85(unsigned char* dst, const unsigned char* src) +{ + while (*src) + { + unsigned int tmp = + nk_decode_85_byte((char)src[0]) + + 85 * (nk_decode_85_byte((char)src[1]) + + 85 * (nk_decode_85_byte((char)src[2]) + + 85 * (nk_decode_85_byte((char)src[3]) + + 85 * nk_decode_85_byte((char)src[4])))); + + /* we can't assume little-endianess. */ + dst[0] = (unsigned char)((tmp >> 0) & 0xFF); + dst[1] = (unsigned char)((tmp >> 8) & 0xFF); + dst[2] = (unsigned char)((tmp >> 16) & 0xFF); + dst[3] = (unsigned char)((tmp >> 24) & 0xFF); + + src += 5; + dst += 4; + } +} + +/* ------------------------------------------------------------- + * + * FONT ATLAS + * + * --------------------------------------------------------------*/ +NK_API struct nk_font_config +nk_font_config(float pixel_height) +{ + struct nk_font_config cfg; + nk_zero_struct(cfg); + cfg.ttf_blob = 0; + cfg.ttf_size = 0; + cfg.ttf_data_owned_by_atlas = 0; + cfg.size = pixel_height; + cfg.oversample_h = 3; + cfg.oversample_v = 1; + cfg.pixel_snap = 0; + cfg.coord_type = NK_COORD_UV; + cfg.spacing = nk_vec2(0,0); + cfg.range = nk_font_default_glyph_ranges(); + cfg.merge_mode = 0; + cfg.fallback_glyph = '?'; + cfg.font = 0; + cfg.n = 0; + return cfg; +} +#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR +NK_API void +nk_font_atlas_init_default(struct nk_font_atlas *atlas) +{ + NK_ASSERT(atlas); + if (!atlas) return; + nk_zero_struct(*atlas); + atlas->temporary.userdata.ptr = 0; + atlas->temporary.alloc = nk_malloc; + atlas->temporary.free = nk_mfree; + atlas->permanent.userdata.ptr = 0; + atlas->permanent.alloc = nk_malloc; + atlas->permanent.free = nk_mfree; +} +#endif +NK_API void +nk_font_atlas_init(struct nk_font_atlas *atlas, struct nk_allocator *alloc) +{ + NK_ASSERT(atlas); + NK_ASSERT(alloc); + if (!atlas || !alloc) return; + nk_zero_struct(*atlas); + atlas->permanent = *alloc; + atlas->temporary = *alloc; +} +NK_API void +nk_font_atlas_init_custom(struct nk_font_atlas *atlas, + struct nk_allocator *permanent, struct nk_allocator *temporary) +{ + NK_ASSERT(atlas); + NK_ASSERT(permanent); + NK_ASSERT(temporary); + if (!atlas || !permanent || !temporary) return; + nk_zero_struct(*atlas); + atlas->permanent = *permanent; + atlas->temporary = *temporary; +} +NK_API void +nk_font_atlas_begin(struct nk_font_atlas *atlas) +{ + NK_ASSERT(atlas); + NK_ASSERT(atlas->temporary.alloc && atlas->temporary.free); + NK_ASSERT(atlas->permanent.alloc && atlas->permanent.free); + if (!atlas || !atlas->permanent.alloc || !atlas->permanent.free || + !atlas->temporary.alloc || !atlas->temporary.free) return; + if (atlas->glyphs) { + atlas->permanent.free(atlas->permanent.userdata, atlas->glyphs); + atlas->glyphs = 0; + } + if (atlas->pixel) { + atlas->permanent.free(atlas->permanent.userdata, atlas->pixel); + atlas->pixel = 0; + } +} +NK_API struct nk_font* +nk_font_atlas_add(struct nk_font_atlas *atlas, const struct nk_font_config *config) +{ + struct nk_font *font = 0; + struct nk_font_config *cfg; + + NK_ASSERT(atlas); + NK_ASSERT(atlas->permanent.alloc); + NK_ASSERT(atlas->permanent.free); + NK_ASSERT(atlas->temporary.alloc); + NK_ASSERT(atlas->temporary.free); + + NK_ASSERT(config); + NK_ASSERT(config->ttf_blob); + NK_ASSERT(config->ttf_size); + NK_ASSERT(config->size > 0.0f); + + if (!atlas || !config || !config->ttf_blob || !config->ttf_size || config->size <= 0.0f|| + !atlas->permanent.alloc || !atlas->permanent.free || + !atlas->temporary.alloc || !atlas->temporary.free) + return 0; + + /* allocate font config */ + cfg = (struct nk_font_config*) + atlas->permanent.alloc(atlas->permanent.userdata,0, sizeof(struct nk_font_config)); + NK_MEMCPY(cfg, config, sizeof(*config)); + cfg->n = cfg; + cfg->p = cfg; + + if (!config->merge_mode) { + /* insert font config into list */ + if (!atlas->config) { + atlas->config = cfg; + cfg->next = 0; + } else { + struct nk_font_config *i = atlas->config; + while (i->next) i = i->next; + i->next = cfg; + cfg->next = 0; + } + /* allocate new font */ + font = (struct nk_font*) + atlas->permanent.alloc(atlas->permanent.userdata,0, sizeof(struct nk_font)); + NK_ASSERT(font); + nk_zero(font, sizeof(*font)); + if (!font) return 0; + font->config = cfg; + + /* insert font into list */ + if (!atlas->fonts) { + atlas->fonts = font; + font->next = 0; + } else { + struct nk_font *i = atlas->fonts; + while (i->next) i = i->next; + i->next = font; + font->next = 0; + } + cfg->font = &font->info; + } else { + /* extend previously added font */ + struct nk_font *f = 0; + struct nk_font_config *c = 0; + NK_ASSERT(atlas->font_num); + f = atlas->fonts; + c = f->config; + cfg->font = &f->info; + + cfg->n = c; + cfg->p = c->p; + c->p->n = cfg; + c->p = cfg; + } + /* create own copy of .TTF font blob */ + if (!config->ttf_data_owned_by_atlas) { + cfg->ttf_blob = atlas->permanent.alloc(atlas->permanent.userdata,0, cfg->ttf_size); + NK_ASSERT(cfg->ttf_blob); + if (!cfg->ttf_blob) { + atlas->font_num++; + return 0; + } + NK_MEMCPY(cfg->ttf_blob, config->ttf_blob, cfg->ttf_size); + cfg->ttf_data_owned_by_atlas = 1; + } + atlas->font_num++; + return font; +} +NK_API struct nk_font* +nk_font_atlas_add_from_memory(struct nk_font_atlas *atlas, void *memory, + nk_size size, float height, const struct nk_font_config *config) +{ + struct nk_font_config cfg; + NK_ASSERT(memory); + NK_ASSERT(size); + + NK_ASSERT(atlas); + NK_ASSERT(atlas->temporary.alloc); + NK_ASSERT(atlas->temporary.free); + NK_ASSERT(atlas->permanent.alloc); + NK_ASSERT(atlas->permanent.free); + if (!atlas || !atlas->temporary.alloc || !atlas->temporary.free || !memory || !size || + !atlas->permanent.alloc || !atlas->permanent.free) + return 0; + + cfg = (config) ? *config: nk_font_config(height); + cfg.ttf_blob = memory; + cfg.ttf_size = size; + cfg.size = height; + cfg.ttf_data_owned_by_atlas = 0; + return nk_font_atlas_add(atlas, &cfg); +} +#ifdef NK_INCLUDE_STANDARD_IO +NK_API struct nk_font* +nk_font_atlas_add_from_file(struct nk_font_atlas *atlas, const char *file_path, + float height, const struct nk_font_config *config) +{ + nk_size size; + char *memory; + struct nk_font_config cfg; + + NK_ASSERT(atlas); + NK_ASSERT(atlas->temporary.alloc); + NK_ASSERT(atlas->temporary.free); + NK_ASSERT(atlas->permanent.alloc); + NK_ASSERT(atlas->permanent.free); + + if (!atlas || !file_path) return 0; + memory = nk_file_load(file_path, &size, &atlas->permanent); + if (!memory) return 0; + + cfg = (config) ? *config: nk_font_config(height); + cfg.ttf_blob = memory; + cfg.ttf_size = size; + cfg.size = height; + cfg.ttf_data_owned_by_atlas = 1; + return nk_font_atlas_add(atlas, &cfg); +} +#endif +NK_API struct nk_font* +nk_font_atlas_add_compressed(struct nk_font_atlas *atlas, + void *compressed_data, nk_size compressed_size, float height, + const struct nk_font_config *config) +{ + unsigned int decompressed_size; + void *decompressed_data; + struct nk_font_config cfg; + + NK_ASSERT(atlas); + NK_ASSERT(atlas->temporary.alloc); + NK_ASSERT(atlas->temporary.free); + NK_ASSERT(atlas->permanent.alloc); + NK_ASSERT(atlas->permanent.free); + + NK_ASSERT(compressed_data); + NK_ASSERT(compressed_size); + if (!atlas || !compressed_data || !atlas->temporary.alloc || !atlas->temporary.free || + !atlas->permanent.alloc || !atlas->permanent.free) + return 0; + + decompressed_size = nk_decompress_length((unsigned char*)compressed_data); + decompressed_data = atlas->permanent.alloc(atlas->permanent.userdata,0,decompressed_size); + NK_ASSERT(decompressed_data); + if (!decompressed_data) return 0; + nk_decompress((unsigned char*)decompressed_data, (unsigned char*)compressed_data, + (unsigned int)compressed_size); + + cfg = (config) ? *config: nk_font_config(height); + cfg.ttf_blob = decompressed_data; + cfg.ttf_size = decompressed_size; + cfg.size = height; + cfg.ttf_data_owned_by_atlas = 1; + return nk_font_atlas_add(atlas, &cfg); +} +NK_API struct nk_font* +nk_font_atlas_add_compressed_base85(struct nk_font_atlas *atlas, + const char *data_base85, float height, const struct nk_font_config *config) +{ + int compressed_size; + void *compressed_data; + struct nk_font *font; + + NK_ASSERT(atlas); + NK_ASSERT(atlas->temporary.alloc); + NK_ASSERT(atlas->temporary.free); + NK_ASSERT(atlas->permanent.alloc); + NK_ASSERT(atlas->permanent.free); + + NK_ASSERT(data_base85); + if (!atlas || !data_base85 || !atlas->temporary.alloc || !atlas->temporary.free || + !atlas->permanent.alloc || !atlas->permanent.free) + return 0; + + compressed_size = (((int)nk_strlen(data_base85) + 4) / 5) * 4; + compressed_data = atlas->temporary.alloc(atlas->temporary.userdata,0, (nk_size)compressed_size); + NK_ASSERT(compressed_data); + if (!compressed_data) return 0; + nk_decode_85((unsigned char*)compressed_data, (const unsigned char*)data_base85); + font = nk_font_atlas_add_compressed(atlas, compressed_data, + (nk_size)compressed_size, height, config); + atlas->temporary.free(atlas->temporary.userdata, compressed_data); + return font; +} + +#ifdef NK_INCLUDE_DEFAULT_FONT +NK_API struct nk_font* +nk_font_atlas_add_default(struct nk_font_atlas *atlas, + float pixel_height, const struct nk_font_config *config) +{ + NK_ASSERT(atlas); + NK_ASSERT(atlas->temporary.alloc); + NK_ASSERT(atlas->temporary.free); + NK_ASSERT(atlas->permanent.alloc); + NK_ASSERT(atlas->permanent.free); + return nk_font_atlas_add_compressed_base85(atlas, + nk_proggy_clean_ttf_compressed_data_base85, pixel_height, config); +} +#endif +NK_API const void* +nk_font_atlas_bake(struct nk_font_atlas *atlas, int *width, int *height, + enum nk_font_atlas_format fmt) +{ + int i = 0; + void *tmp = 0; + nk_size tmp_size, img_size; + struct nk_font *font_iter; + struct nk_font_baker *baker; + + NK_ASSERT(atlas); + NK_ASSERT(atlas->temporary.alloc); + NK_ASSERT(atlas->temporary.free); + NK_ASSERT(atlas->permanent.alloc); + NK_ASSERT(atlas->permanent.free); + + NK_ASSERT(width); + NK_ASSERT(height); + if (!atlas || !width || !height || + !atlas->temporary.alloc || !atlas->temporary.free || + !atlas->permanent.alloc || !atlas->permanent.free) + return 0; + +#ifdef NK_INCLUDE_DEFAULT_FONT + /* no font added so just use default font */ + if (!atlas->font_num) + atlas->default_font = nk_font_atlas_add_default(atlas, 13.0f, 0); +#endif + NK_ASSERT(atlas->font_num); + if (!atlas->font_num) return 0; + + /* allocate temporary baker memory required for the baking process */ + nk_font_baker_memory(&tmp_size, &atlas->glyph_count, atlas->config, atlas->font_num); + tmp = atlas->temporary.alloc(atlas->temporary.userdata,0, tmp_size); + NK_ASSERT(tmp); + if (!tmp) goto failed; + memset(tmp,0,tmp_size); + + /* allocate glyph memory for all fonts */ + baker = nk_font_baker(tmp, atlas->glyph_count, atlas->font_num, &atlas->temporary); + atlas->glyphs = (struct nk_font_glyph*)atlas->permanent.alloc( + atlas->permanent.userdata,0, sizeof(struct nk_font_glyph)*(nk_size)atlas->glyph_count); + NK_ASSERT(atlas->glyphs); + if (!atlas->glyphs) + goto failed; + + /* pack all glyphs into a tight fit space */ + atlas->custom.w = (NK_CURSOR_DATA_W*2)+1; + atlas->custom.h = NK_CURSOR_DATA_H + 1; + if (!nk_font_bake_pack(baker, &img_size, width, height, &atlas->custom, + atlas->config, atlas->font_num, &atlas->temporary)) + goto failed; + + /* allocate memory for the baked image font atlas */ + atlas->pixel = atlas->temporary.alloc(atlas->temporary.userdata,0, img_size); + NK_ASSERT(atlas->pixel); + if (!atlas->pixel) + goto failed; + + /* bake glyphs and custom white pixel into image */ + nk_font_bake(baker, atlas->pixel, *width, *height, + atlas->glyphs, atlas->glyph_count, atlas->config, atlas->font_num); + nk_font_bake_custom_data(atlas->pixel, *width, *height, atlas->custom, + nk_custom_cursor_data, NK_CURSOR_DATA_W, NK_CURSOR_DATA_H, '.', 'X'); + + if (fmt == NK_FONT_ATLAS_RGBA32) { + /* convert alpha8 image into rgba32 image */ + void *img_rgba = atlas->temporary.alloc(atlas->temporary.userdata,0, + (nk_size)(*width * *height * 4)); + NK_ASSERT(img_rgba); + if (!img_rgba) goto failed; + nk_font_bake_convert(img_rgba, *width, *height, atlas->pixel); + atlas->temporary.free(atlas->temporary.userdata, atlas->pixel); + atlas->pixel = img_rgba; + } + atlas->tex_width = *width; + atlas->tex_height = *height; + + /* initialize each font */ + for (font_iter = atlas->fonts; font_iter; font_iter = font_iter->next) { + struct nk_font *font = font_iter; + struct nk_font_config *config = font->config; + nk_font_init(font, config->size, config->fallback_glyph, atlas->glyphs, + config->font, nk_handle_ptr(0)); + } + + /* initialize each cursor */ + {NK_STORAGE const struct nk_vec2 nk_cursor_data[NK_CURSOR_COUNT][3] = { + /* Pos Size Offset */ + {{ 0, 3}, {12,19}, { 0, 0}}, + {{13, 0}, { 7,16}, { 4, 8}}, + {{31, 0}, {23,23}, {11,11}}, + {{21, 0}, { 9, 23}, { 5,11}}, + {{55,18}, {23, 9}, {11, 5}}, + {{73, 0}, {17,17}, { 9, 9}}, + {{55, 0}, {17,17}, { 9, 9}} + }; + for (i = 0; i < NK_CURSOR_COUNT; ++i) { + struct nk_cursor *cursor = &atlas->cursors[i]; + cursor->img.w = (unsigned short)*width; + cursor->img.h = (unsigned short)*height; + cursor->img.region[0] = (unsigned short)(atlas->custom.x + nk_cursor_data[i][0].x); + cursor->img.region[1] = (unsigned short)(atlas->custom.y + nk_cursor_data[i][0].y); + cursor->img.region[2] = (unsigned short)nk_cursor_data[i][1].x; + cursor->img.region[3] = (unsigned short)nk_cursor_data[i][1].y; + cursor->size = nk_cursor_data[i][1]; + cursor->offset = nk_cursor_data[i][2]; + }} + /* free temporary memory */ + atlas->temporary.free(atlas->temporary.userdata, tmp); + return atlas->pixel; + +failed: + /* error so cleanup all memory */ + if (tmp) atlas->temporary.free(atlas->temporary.userdata, tmp); + if (atlas->glyphs) { + atlas->permanent.free(atlas->permanent.userdata, atlas->glyphs); + atlas->glyphs = 0; + } + if (atlas->pixel) { + atlas->temporary.free(atlas->temporary.userdata, atlas->pixel); + atlas->pixel = 0; + } + return 0; +} +NK_API void +nk_font_atlas_end(struct nk_font_atlas *atlas, nk_handle texture, + struct nk_draw_null_texture *null) +{ + int i = 0; + struct nk_font *font_iter; + NK_ASSERT(atlas); + if (!atlas) { + if (!null) return; + null->texture = texture; + null->uv = nk_vec2(0.5f,0.5f); + } + if (null) { + null->texture = texture; + null->uv.x = (atlas->custom.x + 0.5f)/(float)atlas->tex_width; + null->uv.y = (atlas->custom.y + 0.5f)/(float)atlas->tex_height; + } + for (font_iter = atlas->fonts; font_iter; font_iter = font_iter->next) { + font_iter->texture = texture; +#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT + font_iter->handle.texture = texture; +#endif + } + for (i = 0; i < NK_CURSOR_COUNT; ++i) + atlas->cursors[i].img.handle = texture; + + atlas->temporary.free(atlas->temporary.userdata, atlas->pixel); + atlas->pixel = 0; + atlas->tex_width = 0; + atlas->tex_height = 0; + atlas->custom.x = 0; + atlas->custom.y = 0; + atlas->custom.w = 0; + atlas->custom.h = 0; +} +NK_API void +nk_font_atlas_cleanup(struct nk_font_atlas *atlas) +{ + NK_ASSERT(atlas); + NK_ASSERT(atlas->temporary.alloc); + NK_ASSERT(atlas->temporary.free); + NK_ASSERT(atlas->permanent.alloc); + NK_ASSERT(atlas->permanent.free); + if (!atlas || !atlas->permanent.alloc || !atlas->permanent.free) return; + if (atlas->config) { + struct nk_font_config *iter; + for (iter = atlas->config; iter; iter = iter->next) { + struct nk_font_config *i; + for (i = iter->n; i != iter; i = i->n) { + atlas->permanent.free(atlas->permanent.userdata, i->ttf_blob); + i->ttf_blob = 0; + } + atlas->permanent.free(atlas->permanent.userdata, iter->ttf_blob); + iter->ttf_blob = 0; + } + } +} +NK_API void +nk_font_atlas_clear(struct nk_font_atlas *atlas) +{ + NK_ASSERT(atlas); + NK_ASSERT(atlas->temporary.alloc); + NK_ASSERT(atlas->temporary.free); + NK_ASSERT(atlas->permanent.alloc); + NK_ASSERT(atlas->permanent.free); + if (!atlas || !atlas->permanent.alloc || !atlas->permanent.free) return; + + if (atlas->config) { + struct nk_font_config *iter, *next; + for (iter = atlas->config; iter; iter = next) { + struct nk_font_config *i, *n; + for (i = iter->n; i != iter; i = n) { + n = i->n; + if (i->ttf_blob) + atlas->permanent.free(atlas->permanent.userdata, i->ttf_blob); + atlas->permanent.free(atlas->permanent.userdata, i); + } + next = iter->next; + if (i->ttf_blob) + atlas->permanent.free(atlas->permanent.userdata, iter->ttf_blob); + atlas->permanent.free(atlas->permanent.userdata, iter); + } + atlas->config = 0; + } + if (atlas->fonts) { + struct nk_font *iter, *next; + for (iter = atlas->fonts; iter; iter = next) { + next = iter->next; + atlas->permanent.free(atlas->permanent.userdata, iter); + } + atlas->fonts = 0; + } + if (atlas->glyphs) + atlas->permanent.free(atlas->permanent.userdata, atlas->glyphs); + nk_zero_struct(*atlas); +} +#endif + + + + + +/* =============================================================== + * + * INPUT + * + * ===============================================================*/ +NK_API void +nk_input_begin(struct nk_context *ctx) +{ + int i; + struct nk_input *in; + NK_ASSERT(ctx); + if (!ctx) return; + in = &ctx->input; + for (i = 0; i < NK_BUTTON_MAX; ++i) + in->mouse.buttons[i].clicked = 0; + + in->keyboard.text_len = 0; + in->mouse.scroll_delta = nk_vec2(0,0); + in->mouse.prev.x = in->mouse.pos.x; + in->mouse.prev.y = in->mouse.pos.y; + in->mouse.delta.x = 0; + in->mouse.delta.y = 0; + for (i = 0; i < NK_KEY_MAX; i++) + in->keyboard.keys[i].clicked = 0; +} +NK_API void +nk_input_end(struct nk_context *ctx) +{ + struct nk_input *in; + NK_ASSERT(ctx); + if (!ctx) return; + in = &ctx->input; + if (in->mouse.grab) + in->mouse.grab = 0; + if (in->mouse.ungrab) { + in->mouse.grabbed = 0; + in->mouse.ungrab = 0; + in->mouse.grab = 0; + } +} +NK_API void +nk_input_motion(struct nk_context *ctx, int x, int y) +{ + struct nk_input *in; + NK_ASSERT(ctx); + if (!ctx) return; + in = &ctx->input; + in->mouse.pos.x = (float)x; + in->mouse.pos.y = (float)y; + in->mouse.delta.x = in->mouse.pos.x - in->mouse.prev.x; + in->mouse.delta.y = in->mouse.pos.y - in->mouse.prev.y; +} +NK_API void +nk_input_key(struct nk_context *ctx, enum nk_keys key, nk_bool down) +{ + struct nk_input *in; + NK_ASSERT(ctx); + if (!ctx) return; + in = &ctx->input; +#ifdef NK_KEYSTATE_BASED_INPUT + if (in->keyboard.keys[key].down != down) + in->keyboard.keys[key].clicked++; +#else + in->keyboard.keys[key].clicked++; +#endif + in->keyboard.keys[key].down = down; +} +NK_API void +nk_input_button(struct nk_context *ctx, enum nk_buttons id, int x, int y, nk_bool down) +{ + struct nk_mouse_button *btn; + struct nk_input *in; + NK_ASSERT(ctx); + if (!ctx) return; + in = &ctx->input; + if (in->mouse.buttons[id].down == down) return; + + btn = &in->mouse.buttons[id]; + btn->clicked_pos.x = (float)x; + btn->clicked_pos.y = (float)y; + btn->down = down; + btn->clicked++; +} +NK_API void +nk_input_scroll(struct nk_context *ctx, struct nk_vec2 val) +{ + NK_ASSERT(ctx); + if (!ctx) return; + ctx->input.mouse.scroll_delta.x += val.x; + ctx->input.mouse.scroll_delta.y += val.y; +} +NK_API void +nk_input_glyph(struct nk_context *ctx, const nk_glyph glyph) +{ + int len = 0; + nk_rune unicode; + struct nk_input *in; + + NK_ASSERT(ctx); + if (!ctx) return; + in = &ctx->input; + + len = nk_utf_decode(glyph, &unicode, NK_UTF_SIZE); + if (len && ((in->keyboard.text_len + len) < NK_INPUT_MAX)) { + nk_utf_encode(unicode, &in->keyboard.text[in->keyboard.text_len], + NK_INPUT_MAX - in->keyboard.text_len); + in->keyboard.text_len += len; + } +} +NK_API void +nk_input_char(struct nk_context *ctx, char c) +{ + nk_glyph glyph; + NK_ASSERT(ctx); + if (!ctx) return; + glyph[0] = c; + nk_input_glyph(ctx, glyph); +} +NK_API void +nk_input_unicode(struct nk_context *ctx, nk_rune unicode) +{ + nk_glyph rune; + NK_ASSERT(ctx); + if (!ctx) return; + nk_utf_encode(unicode, rune, NK_UTF_SIZE); + nk_input_glyph(ctx, rune); +} +NK_API nk_bool +nk_input_has_mouse_click(const struct nk_input *i, enum nk_buttons id) +{ + const struct nk_mouse_button *btn; + if (!i) return nk_false; + btn = &i->mouse.buttons[id]; + return (btn->clicked && btn->down == nk_false) ? nk_true : nk_false; +} +NK_API nk_bool +nk_input_has_mouse_click_in_rect(const struct nk_input *i, enum nk_buttons id, + struct nk_rect b) +{ + const struct nk_mouse_button *btn; + if (!i) return nk_false; + btn = &i->mouse.buttons[id]; + if (!NK_INBOX(btn->clicked_pos.x,btn->clicked_pos.y,b.x,b.y,b.w,b.h)) + return nk_false; + return nk_true; +} +NK_API nk_bool +nk_input_has_mouse_click_down_in_rect(const struct nk_input *i, enum nk_buttons id, + struct nk_rect b, nk_bool down) +{ + const struct nk_mouse_button *btn; + if (!i) return nk_false; + btn = &i->mouse.buttons[id]; + return nk_input_has_mouse_click_in_rect(i, id, b) && (btn->down == down); +} +NK_API nk_bool +nk_input_is_mouse_click_in_rect(const struct nk_input *i, enum nk_buttons id, + struct nk_rect b) +{ + const struct nk_mouse_button *btn; + if (!i) return nk_false; + btn = &i->mouse.buttons[id]; + return (nk_input_has_mouse_click_down_in_rect(i, id, b, nk_false) && + btn->clicked) ? nk_true : nk_false; +} +NK_API nk_bool +nk_input_is_mouse_click_down_in_rect(const struct nk_input *i, enum nk_buttons id, + struct nk_rect b, nk_bool down) +{ + const struct nk_mouse_button *btn; + if (!i) return nk_false; + btn = &i->mouse.buttons[id]; + return (nk_input_has_mouse_click_down_in_rect(i, id, b, down) && + btn->clicked) ? nk_true : nk_false; +} +NK_API nk_bool +nk_input_any_mouse_click_in_rect(const struct nk_input *in, struct nk_rect b) +{ + int i, down = 0; + for (i = 0; i < NK_BUTTON_MAX; ++i) + down = down || nk_input_is_mouse_click_in_rect(in, (enum nk_buttons)i, b); + return down; +} +NK_API nk_bool +nk_input_is_mouse_hovering_rect(const struct nk_input *i, struct nk_rect rect) +{ + if (!i) return nk_false; + return NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h); +} +NK_API nk_bool +nk_input_is_mouse_prev_hovering_rect(const struct nk_input *i, struct nk_rect rect) +{ + if (!i) return nk_false; + return NK_INBOX(i->mouse.prev.x, i->mouse.prev.y, rect.x, rect.y, rect.w, rect.h); +} +NK_API nk_bool +nk_input_mouse_clicked(const struct nk_input *i, enum nk_buttons id, struct nk_rect rect) +{ + if (!i) return nk_false; + if (!nk_input_is_mouse_hovering_rect(i, rect)) return nk_false; + return nk_input_is_mouse_click_in_rect(i, id, rect); +} +NK_API nk_bool +nk_input_is_mouse_down(const struct nk_input *i, enum nk_buttons id) +{ + if (!i) return nk_false; + return i->mouse.buttons[id].down; +} +NK_API nk_bool +nk_input_is_mouse_pressed(const struct nk_input *i, enum nk_buttons id) +{ + const struct nk_mouse_button *b; + if (!i) return nk_false; + b = &i->mouse.buttons[id]; + if (b->down && b->clicked) + return nk_true; + return nk_false; +} +NK_API nk_bool +nk_input_is_mouse_released(const struct nk_input *i, enum nk_buttons id) +{ + if (!i) return nk_false; + return (!i->mouse.buttons[id].down && i->mouse.buttons[id].clicked); +} +NK_API nk_bool +nk_input_is_key_pressed(const struct nk_input *i, enum nk_keys key) +{ + const struct nk_key *k; + if (!i) return nk_false; + k = &i->keyboard.keys[key]; + if ((k->down && k->clicked) || (!k->down && k->clicked >= 2)) + return nk_true; + return nk_false; +} +NK_API nk_bool +nk_input_is_key_released(const struct nk_input *i, enum nk_keys key) +{ + const struct nk_key *k; + if (!i) return nk_false; + k = &i->keyboard.keys[key]; + if ((!k->down && k->clicked) || (k->down && k->clicked >= 2)) + return nk_true; + return nk_false; +} +NK_API nk_bool +nk_input_is_key_down(const struct nk_input *i, enum nk_keys key) +{ + const struct nk_key *k; + if (!i) return nk_false; + k = &i->keyboard.keys[key]; + if (k->down) return nk_true; + return nk_false; +} + + + + + +/* =============================================================== + * + * STYLE + * + * ===============================================================*/ +NK_API void nk_style_default(struct nk_context *ctx){nk_style_from_table(ctx, 0);} +#define NK_COLOR_MAP(NK_COLOR)\ + NK_COLOR(NK_COLOR_TEXT, 175,175,175,255) \ + NK_COLOR(NK_COLOR_WINDOW, 45, 45, 45, 255) \ + NK_COLOR(NK_COLOR_HEADER, 40, 40, 40, 255) \ + NK_COLOR(NK_COLOR_BORDER, 65, 65, 65, 255) \ + NK_COLOR(NK_COLOR_BUTTON, 50, 50, 50, 255) \ + NK_COLOR(NK_COLOR_BUTTON_HOVER, 40, 40, 40, 255) \ + NK_COLOR(NK_COLOR_BUTTON_ACTIVE, 35, 35, 35, 255) \ + NK_COLOR(NK_COLOR_TOGGLE, 100,100,100,255) \ + NK_COLOR(NK_COLOR_TOGGLE_HOVER, 120,120,120,255) \ + NK_COLOR(NK_COLOR_TOGGLE_CURSOR, 45, 45, 45, 255) \ + NK_COLOR(NK_COLOR_SELECT, 45, 45, 45, 255) \ + NK_COLOR(NK_COLOR_SELECT_ACTIVE, 35, 35, 35,255) \ + NK_COLOR(NK_COLOR_SLIDER, 38, 38, 38, 255) \ + NK_COLOR(NK_COLOR_SLIDER_CURSOR, 100,100,100,255) \ + NK_COLOR(NK_COLOR_SLIDER_CURSOR_HOVER, 120,120,120,255) \ + NK_COLOR(NK_COLOR_SLIDER_CURSOR_ACTIVE, 150,150,150,255) \ + NK_COLOR(NK_COLOR_PROPERTY, 38, 38, 38, 255) \ + NK_COLOR(NK_COLOR_EDIT, 38, 38, 38, 255) \ + NK_COLOR(NK_COLOR_EDIT_CURSOR, 175,175,175,255) \ + NK_COLOR(NK_COLOR_COMBO, 45, 45, 45, 255) \ + NK_COLOR(NK_COLOR_CHART, 120,120,120,255) \ + NK_COLOR(NK_COLOR_CHART_COLOR, 45, 45, 45, 255) \ + NK_COLOR(NK_COLOR_CHART_COLOR_HIGHLIGHT, 255, 0, 0, 255) \ + NK_COLOR(NK_COLOR_SCROLLBAR, 40, 40, 40, 255) \ + NK_COLOR(NK_COLOR_SCROLLBAR_CURSOR, 100,100,100,255) \ + NK_COLOR(NK_COLOR_SCROLLBAR_CURSOR_HOVER, 120,120,120,255) \ + NK_COLOR(NK_COLOR_SCROLLBAR_CURSOR_ACTIVE, 150,150,150,255) \ + NK_COLOR(NK_COLOR_TAB_HEADER, 40, 40, 40,255) + +NK_GLOBAL const struct nk_color +nk_default_color_style[NK_COLOR_COUNT] = { +#define NK_COLOR(a,b,c,d,e) {b,c,d,e}, + NK_COLOR_MAP(NK_COLOR) +#undef NK_COLOR +}; +NK_GLOBAL const char *nk_color_names[NK_COLOR_COUNT] = { +#define NK_COLOR(a,b,c,d,e) #a, + NK_COLOR_MAP(NK_COLOR) +#undef NK_COLOR +}; + +NK_API const char* +nk_style_get_color_by_name(enum nk_style_colors c) +{ + return nk_color_names[c]; +} +NK_API struct nk_style_item +nk_style_item_image(struct nk_image img) +{ + struct nk_style_item i; + i.type = NK_STYLE_ITEM_IMAGE; + i.data.image = img; + return i; +} +NK_API struct nk_style_item +nk_style_item_color(struct nk_color col) +{ + struct nk_style_item i; + i.type = NK_STYLE_ITEM_COLOR; + i.data.color = col; + return i; +} +NK_API struct nk_style_item +nk_style_item_hide(void) +{ + struct nk_style_item i; + i.type = NK_STYLE_ITEM_COLOR; + i.data.color = nk_rgba(0,0,0,0); + return i; +} +NK_API void +nk_style_from_table(struct nk_context *ctx, const struct nk_color *table) +{ + struct nk_style *style; + struct nk_style_text *text; + struct nk_style_button *button; + struct nk_style_toggle *toggle; + struct nk_style_selectable *select; + struct nk_style_slider *slider; + struct nk_style_progress *prog; + struct nk_style_scrollbar *scroll; + struct nk_style_edit *edit; + struct nk_style_property *property; + struct nk_style_combo *combo; + struct nk_style_chart *chart; + struct nk_style_tab *tab; + struct nk_style_window *win; + + NK_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + table = (!table) ? nk_default_color_style: table; + + /* default text */ + text = &style->text; + text->color = table[NK_COLOR_TEXT]; + text->padding = nk_vec2(0,0); + + /* default button */ + button = &style->button; + nk_zero_struct(*button); + button->normal = nk_style_item_color(table[NK_COLOR_BUTTON]); + button->hover = nk_style_item_color(table[NK_COLOR_BUTTON_HOVER]); + button->active = nk_style_item_color(table[NK_COLOR_BUTTON_ACTIVE]); + button->border_color = table[NK_COLOR_BORDER]; + button->text_background = table[NK_COLOR_BUTTON]; + button->text_normal = table[NK_COLOR_TEXT]; + button->text_hover = table[NK_COLOR_TEXT]; + button->text_active = table[NK_COLOR_TEXT]; + button->padding = nk_vec2(2.0f,2.0f); + button->image_padding = nk_vec2(0.0f,0.0f); + button->touch_padding = nk_vec2(0.0f, 0.0f); + button->userdata = nk_handle_ptr(0); + button->text_alignment = NK_TEXT_CENTERED; + button->border = 1.0f; + button->rounding = 4.0f; + button->draw_begin = 0; + button->draw_end = 0; + + /* contextual button */ + button = &style->contextual_button; + nk_zero_struct(*button); + button->normal = nk_style_item_color(table[NK_COLOR_WINDOW]); + button->hover = nk_style_item_color(table[NK_COLOR_BUTTON_HOVER]); + button->active = nk_style_item_color(table[NK_COLOR_BUTTON_ACTIVE]); + button->border_color = table[NK_COLOR_WINDOW]; + button->text_background = table[NK_COLOR_WINDOW]; + button->text_normal = table[NK_COLOR_TEXT]; + button->text_hover = table[NK_COLOR_TEXT]; + button->text_active = table[NK_COLOR_TEXT]; + button->padding = nk_vec2(2.0f,2.0f); + button->touch_padding = nk_vec2(0.0f,0.0f); + button->userdata = nk_handle_ptr(0); + button->text_alignment = NK_TEXT_CENTERED; + button->border = 0.0f; + button->rounding = 0.0f; + button->draw_begin = 0; + button->draw_end = 0; + + /* menu button */ + button = &style->menu_button; + nk_zero_struct(*button); + button->normal = nk_style_item_color(table[NK_COLOR_WINDOW]); + button->hover = nk_style_item_color(table[NK_COLOR_WINDOW]); + button->active = nk_style_item_color(table[NK_COLOR_WINDOW]); + button->border_color = table[NK_COLOR_WINDOW]; + button->text_background = table[NK_COLOR_WINDOW]; + button->text_normal = table[NK_COLOR_TEXT]; + button->text_hover = table[NK_COLOR_TEXT]; + button->text_active = table[NK_COLOR_TEXT]; + button->padding = nk_vec2(2.0f,2.0f); + button->touch_padding = nk_vec2(0.0f,0.0f); + button->userdata = nk_handle_ptr(0); + button->text_alignment = NK_TEXT_CENTERED; + button->border = 0.0f; + button->rounding = 1.0f; + button->draw_begin = 0; + button->draw_end = 0; + + /* checkbox toggle */ + toggle = &style->checkbox; + nk_zero_struct(*toggle); + toggle->normal = nk_style_item_color(table[NK_COLOR_TOGGLE]); + toggle->hover = nk_style_item_color(table[NK_COLOR_TOGGLE_HOVER]); + toggle->active = nk_style_item_color(table[NK_COLOR_TOGGLE_HOVER]); + toggle->cursor_normal = nk_style_item_color(table[NK_COLOR_TOGGLE_CURSOR]); + toggle->cursor_hover = nk_style_item_color(table[NK_COLOR_TOGGLE_CURSOR]); + toggle->userdata = nk_handle_ptr(0); + toggle->text_background = table[NK_COLOR_WINDOW]; + toggle->text_normal = table[NK_COLOR_TEXT]; + toggle->text_hover = table[NK_COLOR_TEXT]; + toggle->text_active = table[NK_COLOR_TEXT]; + toggle->padding = nk_vec2(2.0f, 2.0f); + toggle->touch_padding = nk_vec2(0,0); + toggle->border_color = nk_rgba(0,0,0,0); + toggle->border = 0.0f; + toggle->spacing = 4; + + /* option toggle */ + toggle = &style->option; + nk_zero_struct(*toggle); + toggle->normal = nk_style_item_color(table[NK_COLOR_TOGGLE]); + toggle->hover = nk_style_item_color(table[NK_COLOR_TOGGLE_HOVER]); + toggle->active = nk_style_item_color(table[NK_COLOR_TOGGLE_HOVER]); + toggle->cursor_normal = nk_style_item_color(table[NK_COLOR_TOGGLE_CURSOR]); + toggle->cursor_hover = nk_style_item_color(table[NK_COLOR_TOGGLE_CURSOR]); + toggle->userdata = nk_handle_ptr(0); + toggle->text_background = table[NK_COLOR_WINDOW]; + toggle->text_normal = table[NK_COLOR_TEXT]; + toggle->text_hover = table[NK_COLOR_TEXT]; + toggle->text_active = table[NK_COLOR_TEXT]; + toggle->padding = nk_vec2(3.0f, 3.0f); + toggle->touch_padding = nk_vec2(0,0); + toggle->border_color = nk_rgba(0,0,0,0); + toggle->border = 0.0f; + toggle->spacing = 4; + + /* selectable */ + select = &style->selectable; + nk_zero_struct(*select); + select->normal = nk_style_item_color(table[NK_COLOR_SELECT]); + select->hover = nk_style_item_color(table[NK_COLOR_SELECT]); + select->pressed = nk_style_item_color(table[NK_COLOR_SELECT]); + select->normal_active = nk_style_item_color(table[NK_COLOR_SELECT_ACTIVE]); + select->hover_active = nk_style_item_color(table[NK_COLOR_SELECT_ACTIVE]); + select->pressed_active = nk_style_item_color(table[NK_COLOR_SELECT_ACTIVE]); + select->text_normal = table[NK_COLOR_TEXT]; + select->text_hover = table[NK_COLOR_TEXT]; + select->text_pressed = table[NK_COLOR_TEXT]; + select->text_normal_active = table[NK_COLOR_TEXT]; + select->text_hover_active = table[NK_COLOR_TEXT]; + select->text_pressed_active = table[NK_COLOR_TEXT]; + select->padding = nk_vec2(2.0f,2.0f); + select->image_padding = nk_vec2(2.0f,2.0f); + select->touch_padding = nk_vec2(0,0); + select->userdata = nk_handle_ptr(0); + select->rounding = 0.0f; + select->draw_begin = 0; + select->draw_end = 0; + + /* slider */ + slider = &style->slider; + nk_zero_struct(*slider); + slider->normal = nk_style_item_hide(); + slider->hover = nk_style_item_hide(); + slider->active = nk_style_item_hide(); + slider->bar_normal = table[NK_COLOR_SLIDER]; + slider->bar_hover = table[NK_COLOR_SLIDER]; + slider->bar_active = table[NK_COLOR_SLIDER]; + slider->bar_filled = table[NK_COLOR_SLIDER_CURSOR]; + slider->cursor_normal = nk_style_item_color(table[NK_COLOR_SLIDER_CURSOR]); + slider->cursor_hover = nk_style_item_color(table[NK_COLOR_SLIDER_CURSOR_HOVER]); + slider->cursor_active = nk_style_item_color(table[NK_COLOR_SLIDER_CURSOR_ACTIVE]); + slider->inc_symbol = NK_SYMBOL_TRIANGLE_RIGHT; + slider->dec_symbol = NK_SYMBOL_TRIANGLE_LEFT; + slider->cursor_size = nk_vec2(16,16); + slider->padding = nk_vec2(2,2); + slider->spacing = nk_vec2(2,2); + slider->userdata = nk_handle_ptr(0); + slider->show_buttons = nk_false; + slider->bar_height = 8; + slider->rounding = 0; + slider->draw_begin = 0; + slider->draw_end = 0; + + /* slider buttons */ + button = &style->slider.inc_button; + button->normal = nk_style_item_color(nk_rgb(40,40,40)); + button->hover = nk_style_item_color(nk_rgb(42,42,42)); + button->active = nk_style_item_color(nk_rgb(44,44,44)); + button->border_color = nk_rgb(65,65,65); + button->text_background = nk_rgb(40,40,40); + button->text_normal = nk_rgb(175,175,175); + button->text_hover = nk_rgb(175,175,175); + button->text_active = nk_rgb(175,175,175); + button->padding = nk_vec2(8.0f,8.0f); + button->touch_padding = nk_vec2(0.0f,0.0f); + button->userdata = nk_handle_ptr(0); + button->text_alignment = NK_TEXT_CENTERED; + button->border = 1.0f; + button->rounding = 0.0f; + button->draw_begin = 0; + button->draw_end = 0; + style->slider.dec_button = style->slider.inc_button; + + /* progressbar */ + prog = &style->progress; + nk_zero_struct(*prog); + prog->normal = nk_style_item_color(table[NK_COLOR_SLIDER]); + prog->hover = nk_style_item_color(table[NK_COLOR_SLIDER]); + prog->active = nk_style_item_color(table[NK_COLOR_SLIDER]); + prog->cursor_normal = nk_style_item_color(table[NK_COLOR_SLIDER_CURSOR]); + prog->cursor_hover = nk_style_item_color(table[NK_COLOR_SLIDER_CURSOR_HOVER]); + prog->cursor_active = nk_style_item_color(table[NK_COLOR_SLIDER_CURSOR_ACTIVE]); + prog->border_color = nk_rgba(0,0,0,0); + prog->cursor_border_color = nk_rgba(0,0,0,0); + prog->userdata = nk_handle_ptr(0); + prog->padding = nk_vec2(4,4); + prog->rounding = 0; + prog->border = 0; + prog->cursor_rounding = 0; + prog->cursor_border = 0; + prog->draw_begin = 0; + prog->draw_end = 0; + + /* scrollbars */ + scroll = &style->scrollh; + nk_zero_struct(*scroll); + scroll->normal = nk_style_item_color(table[NK_COLOR_SCROLLBAR]); + scroll->hover = nk_style_item_color(table[NK_COLOR_SCROLLBAR]); + scroll->active = nk_style_item_color(table[NK_COLOR_SCROLLBAR]); + scroll->cursor_normal = nk_style_item_color(table[NK_COLOR_SCROLLBAR_CURSOR]); + scroll->cursor_hover = nk_style_item_color(table[NK_COLOR_SCROLLBAR_CURSOR_HOVER]); + scroll->cursor_active = nk_style_item_color(table[NK_COLOR_SCROLLBAR_CURSOR_ACTIVE]); + scroll->dec_symbol = NK_SYMBOL_CIRCLE_SOLID; + scroll->inc_symbol = NK_SYMBOL_CIRCLE_SOLID; + scroll->userdata = nk_handle_ptr(0); + scroll->border_color = table[NK_COLOR_SCROLLBAR]; + scroll->cursor_border_color = table[NK_COLOR_SCROLLBAR]; + scroll->padding = nk_vec2(0,0); + scroll->show_buttons = nk_false; + scroll->border = 0; + scroll->rounding = 0; + scroll->border_cursor = 0; + scroll->rounding_cursor = 0; + scroll->draw_begin = 0; + scroll->draw_end = 0; + style->scrollv = style->scrollh; + + /* scrollbars buttons */ + button = &style->scrollh.inc_button; + button->normal = nk_style_item_color(nk_rgb(40,40,40)); + button->hover = nk_style_item_color(nk_rgb(42,42,42)); + button->active = nk_style_item_color(nk_rgb(44,44,44)); + button->border_color = nk_rgb(65,65,65); + button->text_background = nk_rgb(40,40,40); + button->text_normal = nk_rgb(175,175,175); + button->text_hover = nk_rgb(175,175,175); + button->text_active = nk_rgb(175,175,175); + button->padding = nk_vec2(4.0f,4.0f); + button->touch_padding = nk_vec2(0.0f,0.0f); + button->userdata = nk_handle_ptr(0); + button->text_alignment = NK_TEXT_CENTERED; + button->border = 1.0f; + button->rounding = 0.0f; + button->draw_begin = 0; + button->draw_end = 0; + style->scrollh.dec_button = style->scrollh.inc_button; + style->scrollv.inc_button = style->scrollh.inc_button; + style->scrollv.dec_button = style->scrollh.inc_button; + + /* edit */ + edit = &style->edit; + nk_zero_struct(*edit); + edit->normal = nk_style_item_color(table[NK_COLOR_EDIT]); + edit->hover = nk_style_item_color(table[NK_COLOR_EDIT]); + edit->active = nk_style_item_color(table[NK_COLOR_EDIT]); + edit->cursor_normal = table[NK_COLOR_TEXT]; + edit->cursor_hover = table[NK_COLOR_TEXT]; + edit->cursor_text_normal= table[NK_COLOR_EDIT]; + edit->cursor_text_hover = table[NK_COLOR_EDIT]; + edit->border_color = table[NK_COLOR_BORDER]; + edit->text_normal = table[NK_COLOR_TEXT]; + edit->text_hover = table[NK_COLOR_TEXT]; + edit->text_active = table[NK_COLOR_TEXT]; + edit->selected_normal = table[NK_COLOR_TEXT]; + edit->selected_hover = table[NK_COLOR_TEXT]; + edit->selected_text_normal = table[NK_COLOR_EDIT]; + edit->selected_text_hover = table[NK_COLOR_EDIT]; + edit->scrollbar_size = nk_vec2(10,10); + edit->scrollbar = style->scrollv; + edit->padding = nk_vec2(4,4); + edit->row_padding = 2; + edit->cursor_size = 4; + edit->border = 1; + edit->rounding = 0; + + /* property */ + property = &style->property; + nk_zero_struct(*property); + property->normal = nk_style_item_color(table[NK_COLOR_PROPERTY]); + property->hover = nk_style_item_color(table[NK_COLOR_PROPERTY]); + property->active = nk_style_item_color(table[NK_COLOR_PROPERTY]); + property->border_color = table[NK_COLOR_BORDER]; + property->label_normal = table[NK_COLOR_TEXT]; + property->label_hover = table[NK_COLOR_TEXT]; + property->label_active = table[NK_COLOR_TEXT]; + property->sym_left = NK_SYMBOL_TRIANGLE_LEFT; + property->sym_right = NK_SYMBOL_TRIANGLE_RIGHT; + property->userdata = nk_handle_ptr(0); + property->padding = nk_vec2(4,4); + property->border = 1; + property->rounding = 10; + property->draw_begin = 0; + property->draw_end = 0; + + /* property buttons */ + button = &style->property.dec_button; + nk_zero_struct(*button); + button->normal = nk_style_item_color(table[NK_COLOR_PROPERTY]); + button->hover = nk_style_item_color(table[NK_COLOR_PROPERTY]); + button->active = nk_style_item_color(table[NK_COLOR_PROPERTY]); + button->border_color = nk_rgba(0,0,0,0); + button->text_background = table[NK_COLOR_PROPERTY]; + button->text_normal = table[NK_COLOR_TEXT]; + button->text_hover = table[NK_COLOR_TEXT]; + button->text_active = table[NK_COLOR_TEXT]; + button->padding = nk_vec2(0.0f,0.0f); + button->touch_padding = nk_vec2(0.0f,0.0f); + button->userdata = nk_handle_ptr(0); + button->text_alignment = NK_TEXT_CENTERED; + button->border = 0.0f; + button->rounding = 0.0f; + button->draw_begin = 0; + button->draw_end = 0; + style->property.inc_button = style->property.dec_button; + + /* property edit */ + edit = &style->property.edit; + nk_zero_struct(*edit); + edit->normal = nk_style_item_color(table[NK_COLOR_PROPERTY]); + edit->hover = nk_style_item_color(table[NK_COLOR_PROPERTY]); + edit->active = nk_style_item_color(table[NK_COLOR_PROPERTY]); + edit->border_color = nk_rgba(0,0,0,0); + edit->cursor_normal = table[NK_COLOR_TEXT]; + edit->cursor_hover = table[NK_COLOR_TEXT]; + edit->cursor_text_normal= table[NK_COLOR_EDIT]; + edit->cursor_text_hover = table[NK_COLOR_EDIT]; + edit->text_normal = table[NK_COLOR_TEXT]; + edit->text_hover = table[NK_COLOR_TEXT]; + edit->text_active = table[NK_COLOR_TEXT]; + edit->selected_normal = table[NK_COLOR_TEXT]; + edit->selected_hover = table[NK_COLOR_TEXT]; + edit->selected_text_normal = table[NK_COLOR_EDIT]; + edit->selected_text_hover = table[NK_COLOR_EDIT]; + edit->padding = nk_vec2(0,0); + edit->cursor_size = 8; + edit->border = 0; + edit->rounding = 0; + + /* chart */ + chart = &style->chart; + nk_zero_struct(*chart); + chart->background = nk_style_item_color(table[NK_COLOR_CHART]); + chart->border_color = table[NK_COLOR_BORDER]; + chart->selected_color = table[NK_COLOR_CHART_COLOR_HIGHLIGHT]; + chart->color = table[NK_COLOR_CHART_COLOR]; + chart->padding = nk_vec2(4,4); + chart->border = 0; + chart->rounding = 0; + + /* combo */ + combo = &style->combo; + combo->normal = nk_style_item_color(table[NK_COLOR_COMBO]); + combo->hover = nk_style_item_color(table[NK_COLOR_COMBO]); + combo->active = nk_style_item_color(table[NK_COLOR_COMBO]); + combo->border_color = table[NK_COLOR_BORDER]; + combo->label_normal = table[NK_COLOR_TEXT]; + combo->label_hover = table[NK_COLOR_TEXT]; + combo->label_active = table[NK_COLOR_TEXT]; + combo->sym_normal = NK_SYMBOL_TRIANGLE_DOWN; + combo->sym_hover = NK_SYMBOL_TRIANGLE_DOWN; + combo->sym_active = NK_SYMBOL_TRIANGLE_DOWN; + combo->content_padding = nk_vec2(4,4); + combo->button_padding = nk_vec2(0,4); + combo->spacing = nk_vec2(4,0); + combo->border = 1; + combo->rounding = 0; + + /* combo button */ + button = &style->combo.button; + nk_zero_struct(*button); + button->normal = nk_style_item_color(table[NK_COLOR_COMBO]); + button->hover = nk_style_item_color(table[NK_COLOR_COMBO]); + button->active = nk_style_item_color(table[NK_COLOR_COMBO]); + button->border_color = nk_rgba(0,0,0,0); + button->text_background = table[NK_COLOR_COMBO]; + button->text_normal = table[NK_COLOR_TEXT]; + button->text_hover = table[NK_COLOR_TEXT]; + button->text_active = table[NK_COLOR_TEXT]; + button->padding = nk_vec2(2.0f,2.0f); + button->touch_padding = nk_vec2(0.0f,0.0f); + button->userdata = nk_handle_ptr(0); + button->text_alignment = NK_TEXT_CENTERED; + button->border = 0.0f; + button->rounding = 0.0f; + button->draw_begin = 0; + button->draw_end = 0; + + /* tab */ + tab = &style->tab; + tab->background = nk_style_item_color(table[NK_COLOR_TAB_HEADER]); + tab->border_color = table[NK_COLOR_BORDER]; + tab->text = table[NK_COLOR_TEXT]; + tab->sym_minimize = NK_SYMBOL_TRIANGLE_RIGHT; + tab->sym_maximize = NK_SYMBOL_TRIANGLE_DOWN; + tab->padding = nk_vec2(4,4); + tab->spacing = nk_vec2(4,4); + tab->indent = 10.0f; + tab->border = 1; + tab->rounding = 0; + + /* tab button */ + button = &style->tab.tab_minimize_button; + nk_zero_struct(*button); + button->normal = nk_style_item_color(table[NK_COLOR_TAB_HEADER]); + button->hover = nk_style_item_color(table[NK_COLOR_TAB_HEADER]); + button->active = nk_style_item_color(table[NK_COLOR_TAB_HEADER]); + button->border_color = nk_rgba(0,0,0,0); + button->text_background = table[NK_COLOR_TAB_HEADER]; + button->text_normal = table[NK_COLOR_TEXT]; + button->text_hover = table[NK_COLOR_TEXT]; + button->text_active = table[NK_COLOR_TEXT]; + button->padding = nk_vec2(2.0f,2.0f); + button->touch_padding = nk_vec2(0.0f,0.0f); + button->userdata = nk_handle_ptr(0); + button->text_alignment = NK_TEXT_CENTERED; + button->border = 0.0f; + button->rounding = 0.0f; + button->draw_begin = 0; + button->draw_end = 0; + style->tab.tab_maximize_button =*button; + + /* node button */ + button = &style->tab.node_minimize_button; + nk_zero_struct(*button); + button->normal = nk_style_item_color(table[NK_COLOR_WINDOW]); + button->hover = nk_style_item_color(table[NK_COLOR_WINDOW]); + button->active = nk_style_item_color(table[NK_COLOR_WINDOW]); + button->border_color = nk_rgba(0,0,0,0); + button->text_background = table[NK_COLOR_TAB_HEADER]; + button->text_normal = table[NK_COLOR_TEXT]; + button->text_hover = table[NK_COLOR_TEXT]; + button->text_active = table[NK_COLOR_TEXT]; + button->padding = nk_vec2(2.0f,2.0f); + button->touch_padding = nk_vec2(0.0f,0.0f); + button->userdata = nk_handle_ptr(0); + button->text_alignment = NK_TEXT_CENTERED; + button->border = 0.0f; + button->rounding = 0.0f; + button->draw_begin = 0; + button->draw_end = 0; + style->tab.node_maximize_button =*button; + + /* window header */ + win = &style->window; + win->header.align = NK_HEADER_RIGHT; + win->header.close_symbol = NK_SYMBOL_X; + win->header.minimize_symbol = NK_SYMBOL_MINUS; + win->header.maximize_symbol = NK_SYMBOL_PLUS; + win->header.normal = nk_style_item_color(table[NK_COLOR_HEADER]); + win->header.hover = nk_style_item_color(table[NK_COLOR_HEADER]); + win->header.active = nk_style_item_color(table[NK_COLOR_HEADER]); + win->header.label_normal = table[NK_COLOR_TEXT]; + win->header.label_hover = table[NK_COLOR_TEXT]; + win->header.label_active = table[NK_COLOR_TEXT]; + win->header.label_padding = nk_vec2(4,4); + win->header.padding = nk_vec2(4,4); + win->header.spacing = nk_vec2(0,0); + + /* window header close button */ + button = &style->window.header.close_button; + nk_zero_struct(*button); + button->normal = nk_style_item_color(table[NK_COLOR_HEADER]); + button->hover = nk_style_item_color(table[NK_COLOR_HEADER]); + button->active = nk_style_item_color(table[NK_COLOR_HEADER]); + button->border_color = nk_rgba(0,0,0,0); + button->text_background = table[NK_COLOR_HEADER]; + button->text_normal = table[NK_COLOR_TEXT]; + button->text_hover = table[NK_COLOR_TEXT]; + button->text_active = table[NK_COLOR_TEXT]; + button->padding = nk_vec2(0.0f,0.0f); + button->touch_padding = nk_vec2(0.0f,0.0f); + button->userdata = nk_handle_ptr(0); + button->text_alignment = NK_TEXT_CENTERED; + button->border = 0.0f; + button->rounding = 0.0f; + button->draw_begin = 0; + button->draw_end = 0; + + /* window header minimize button */ + button = &style->window.header.minimize_button; + nk_zero_struct(*button); + button->normal = nk_style_item_color(table[NK_COLOR_HEADER]); + button->hover = nk_style_item_color(table[NK_COLOR_HEADER]); + button->active = nk_style_item_color(table[NK_COLOR_HEADER]); + button->border_color = nk_rgba(0,0,0,0); + button->text_background = table[NK_COLOR_HEADER]; + button->text_normal = table[NK_COLOR_TEXT]; + button->text_hover = table[NK_COLOR_TEXT]; + button->text_active = table[NK_COLOR_TEXT]; + button->padding = nk_vec2(0.0f,0.0f); + button->touch_padding = nk_vec2(0.0f,0.0f); + button->userdata = nk_handle_ptr(0); + button->text_alignment = NK_TEXT_CENTERED; + button->border = 0.0f; + button->rounding = 0.0f; + button->draw_begin = 0; + button->draw_end = 0; + + /* window */ + win->background = table[NK_COLOR_WINDOW]; + win->fixed_background = nk_style_item_color(table[NK_COLOR_WINDOW]); + win->border_color = table[NK_COLOR_BORDER]; + win->popup_border_color = table[NK_COLOR_BORDER]; + win->combo_border_color = table[NK_COLOR_BORDER]; + win->contextual_border_color = table[NK_COLOR_BORDER]; + win->menu_border_color = table[NK_COLOR_BORDER]; + win->group_border_color = table[NK_COLOR_BORDER]; + win->tooltip_border_color = table[NK_COLOR_BORDER]; + win->scaler = nk_style_item_color(table[NK_COLOR_TEXT]); + + win->rounding = 0.0f; + win->spacing = nk_vec2(4,4); + win->scrollbar_size = nk_vec2(10,10); + win->min_size = nk_vec2(64,64); + + win->combo_border = 1.0f; + win->contextual_border = 1.0f; + win->menu_border = 1.0f; + win->group_border = 1.0f; + win->tooltip_border = 1.0f; + win->popup_border = 1.0f; + win->border = 2.0f; + win->min_row_height_padding = 8; + + win->padding = nk_vec2(4,4); + win->group_padding = nk_vec2(4,4); + win->popup_padding = nk_vec2(4,4); + win->combo_padding = nk_vec2(4,4); + win->contextual_padding = nk_vec2(4,4); + win->menu_padding = nk_vec2(4,4); + win->tooltip_padding = nk_vec2(4,4); +} +NK_API void +nk_style_set_font(struct nk_context *ctx, const struct nk_user_font *font) +{ + struct nk_style *style; + NK_ASSERT(ctx); + + if (!ctx) return; + style = &ctx->style; + style->font = font; + ctx->stacks.fonts.head = 0; + if (ctx->current) + nk_layout_reset_min_row_height(ctx); +} +NK_API nk_bool +nk_style_push_font(struct nk_context *ctx, const struct nk_user_font *font) +{ + struct nk_config_stack_user_font *font_stack; + struct nk_config_stack_user_font_element *element; + + NK_ASSERT(ctx); + if (!ctx) return 0; + + font_stack = &ctx->stacks.fonts; + NK_ASSERT(font_stack->head < (int)NK_LEN(font_stack->elements)); + if (font_stack->head >= (int)NK_LEN(font_stack->elements)) + return 0; + + element = &font_stack->elements[font_stack->head++]; + element->address = &ctx->style.font; + element->old_value = ctx->style.font; + ctx->style.font = font; + return 1; +} +NK_API nk_bool +nk_style_pop_font(struct nk_context *ctx) +{ + struct nk_config_stack_user_font *font_stack; + struct nk_config_stack_user_font_element *element; + + NK_ASSERT(ctx); + if (!ctx) return 0; + + font_stack = &ctx->stacks.fonts; + NK_ASSERT(font_stack->head > 0); + if (font_stack->head < 1) + return 0; + + element = &font_stack->elements[--font_stack->head]; + *element->address = element->old_value; + return 1; +} +#define NK_STYLE_PUSH_IMPLEMENATION(prefix, type, stack) \ +nk_style_push_##type(struct nk_context *ctx, prefix##_##type *address, prefix##_##type value)\ +{\ + struct nk_config_stack_##type * type_stack;\ + struct nk_config_stack_##type##_element *element;\ + NK_ASSERT(ctx);\ + if (!ctx) return 0;\ + type_stack = &ctx->stacks.stack;\ + NK_ASSERT(type_stack->head < (int)NK_LEN(type_stack->elements));\ + if (type_stack->head >= (int)NK_LEN(type_stack->elements))\ + return 0;\ + element = &type_stack->elements[type_stack->head++];\ + element->address = address;\ + element->old_value = *address;\ + *address = value;\ + return 1;\ +} +#define NK_STYLE_POP_IMPLEMENATION(type, stack) \ +nk_style_pop_##type(struct nk_context *ctx)\ +{\ + struct nk_config_stack_##type *type_stack;\ + struct nk_config_stack_##type##_element *element;\ + NK_ASSERT(ctx);\ + if (!ctx) return 0;\ + type_stack = &ctx->stacks.stack;\ + NK_ASSERT(type_stack->head > 0);\ + if (type_stack->head < 1)\ + return 0;\ + element = &type_stack->elements[--type_stack->head];\ + *element->address = element->old_value;\ + return 1;\ +} +NK_API nk_bool NK_STYLE_PUSH_IMPLEMENATION(struct nk, style_item, style_items) +NK_API nk_bool NK_STYLE_PUSH_IMPLEMENATION(nk,float, floats) +NK_API nk_bool NK_STYLE_PUSH_IMPLEMENATION(struct nk, vec2, vectors) +NK_API nk_bool NK_STYLE_PUSH_IMPLEMENATION(nk,flags, flags) +NK_API nk_bool NK_STYLE_PUSH_IMPLEMENATION(struct nk,color, colors) + +NK_API nk_bool NK_STYLE_POP_IMPLEMENATION(style_item, style_items) +NK_API nk_bool NK_STYLE_POP_IMPLEMENATION(float,floats) +NK_API nk_bool NK_STYLE_POP_IMPLEMENATION(vec2, vectors) +NK_API nk_bool NK_STYLE_POP_IMPLEMENATION(flags,flags) +NK_API nk_bool NK_STYLE_POP_IMPLEMENATION(color,colors) + +NK_API nk_bool +nk_style_set_cursor(struct nk_context *ctx, enum nk_style_cursor c) +{ + struct nk_style *style; + NK_ASSERT(ctx); + if (!ctx) return 0; + style = &ctx->style; + if (style->cursors[c]) { + style->cursor_active = style->cursors[c]; + return 1; + } + return 0; +} +NK_API void +nk_style_show_cursor(struct nk_context *ctx) +{ + ctx->style.cursor_visible = nk_true; +} +NK_API void +nk_style_hide_cursor(struct nk_context *ctx) +{ + ctx->style.cursor_visible = nk_false; +} +NK_API void +nk_style_load_cursor(struct nk_context *ctx, enum nk_style_cursor cursor, + const struct nk_cursor *c) +{ + struct nk_style *style; + NK_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + style->cursors[cursor] = c; +} +NK_API void +nk_style_load_all_cursors(struct nk_context *ctx, struct nk_cursor *cursors) +{ + int i = 0; + struct nk_style *style; + NK_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + for (i = 0; i < NK_CURSOR_COUNT; ++i) + style->cursors[i] = &cursors[i]; + style->cursor_visible = nk_true; +} + + + + + +/* ============================================================== + * + * CONTEXT + * + * ===============================================================*/ +NK_INTERN void +nk_setup(struct nk_context *ctx, const struct nk_user_font *font) +{ + NK_ASSERT(ctx); + if (!ctx) return; + nk_zero_struct(*ctx); + nk_style_default(ctx); + ctx->seq = 1; + if (font) ctx->style.font = font; +#ifdef NK_INCLUDE_VERTEX_BUFFER_OUTPUT + nk_draw_list_init(&ctx->draw_list); +#endif +} +#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR +NK_API nk_bool +nk_init_default(struct nk_context *ctx, const struct nk_user_font *font) +{ + struct nk_allocator alloc; + alloc.userdata.ptr = 0; + alloc.alloc = nk_malloc; + alloc.free = nk_mfree; + return nk_init(ctx, &alloc, font); +} +#endif +NK_API nk_bool +nk_init_fixed(struct nk_context *ctx, void *memory, nk_size size, + const struct nk_user_font *font) +{ + NK_ASSERT(memory); + if (!memory) return 0; + nk_setup(ctx, font); + nk_buffer_init_fixed(&ctx->memory, memory, size); + ctx->use_pool = nk_false; + return 1; +} +NK_API nk_bool +nk_init_custom(struct nk_context *ctx, struct nk_buffer *cmds, + struct nk_buffer *pool, const struct nk_user_font *font) +{ + NK_ASSERT(cmds); + NK_ASSERT(pool); + if (!cmds || !pool) return 0; + + nk_setup(ctx, font); + ctx->memory = *cmds; + if (pool->type == NK_BUFFER_FIXED) { + /* take memory from buffer and alloc fixed pool */ + nk_pool_init_fixed(&ctx->pool, pool->memory.ptr, pool->memory.size); + } else { + /* create dynamic pool from buffer allocator */ + struct nk_allocator *alloc = &pool->pool; + nk_pool_init(&ctx->pool, alloc, NK_POOL_DEFAULT_CAPACITY); + } + ctx->use_pool = nk_true; + return 1; +} +NK_API nk_bool +nk_init(struct nk_context *ctx, struct nk_allocator *alloc, + const struct nk_user_font *font) +{ + NK_ASSERT(alloc); + if (!alloc) return 0; + nk_setup(ctx, font); + nk_buffer_init(&ctx->memory, alloc, NK_DEFAULT_COMMAND_BUFFER_SIZE); + nk_pool_init(&ctx->pool, alloc, NK_POOL_DEFAULT_CAPACITY); + ctx->use_pool = nk_true; + return 1; +} +#ifdef NK_INCLUDE_COMMAND_USERDATA +NK_API void +nk_set_user_data(struct nk_context *ctx, nk_handle handle) +{ + if (!ctx) return; + ctx->userdata = handle; + if (ctx->current) + ctx->current->buffer.userdata = handle; +} +#endif +NK_API void +nk_free(struct nk_context *ctx) +{ + NK_ASSERT(ctx); + if (!ctx) return; + nk_buffer_free(&ctx->memory); + if (ctx->use_pool) + nk_pool_free(&ctx->pool); + + nk_zero(&ctx->input, sizeof(ctx->input)); + nk_zero(&ctx->style, sizeof(ctx->style)); + nk_zero(&ctx->memory, sizeof(ctx->memory)); + + ctx->seq = 0; + ctx->build = 0; + ctx->begin = 0; + ctx->end = 0; + ctx->active = 0; + ctx->current = 0; + ctx->freelist = 0; + ctx->count = 0; +} +NK_API void +nk_clear(struct nk_context *ctx) +{ + struct nk_window *iter; + struct nk_window *next; + NK_ASSERT(ctx); + + if (!ctx) return; + if (ctx->use_pool) + nk_buffer_clear(&ctx->memory); + else nk_buffer_reset(&ctx->memory, NK_BUFFER_FRONT); + + ctx->build = 0; + ctx->memory.calls = 0; + ctx->last_widget_state = 0; + ctx->style.cursor_active = ctx->style.cursors[NK_CURSOR_ARROW]; + NK_MEMSET(&ctx->overlay, 0, sizeof(ctx->overlay)); + + /* garbage collector */ + iter = ctx->begin; + while (iter) { + /* make sure valid minimized windows do not get removed */ + if ((iter->flags & NK_WINDOW_MINIMIZED) && + !(iter->flags & NK_WINDOW_CLOSED) && + iter->seq == ctx->seq) { + iter = iter->next; + continue; + } + /* remove hotness from hidden or closed windows*/ + if (((iter->flags & NK_WINDOW_HIDDEN) || + (iter->flags & NK_WINDOW_CLOSED)) && + iter == ctx->active) { + ctx->active = iter->prev; + ctx->end = iter->prev; + if (!ctx->end) + ctx->begin = 0; + if (ctx->active) + ctx->active->flags &= ~(unsigned)NK_WINDOW_ROM; + } + /* free unused popup windows */ + if (iter->popup.win && iter->popup.win->seq != ctx->seq) { + nk_free_window(ctx, iter->popup.win); + iter->popup.win = 0; + } + /* remove unused window state tables */ + {struct nk_table *n, *it = iter->tables; + while (it) { + n = it->next; + if (it->seq != ctx->seq) { + nk_remove_table(iter, it); + nk_zero(it, sizeof(union nk_page_data)); + nk_free_table(ctx, it); + if (it == iter->tables) + iter->tables = n; + } it = n; + }} + /* window itself is not used anymore so free */ + if (iter->seq != ctx->seq || iter->flags & NK_WINDOW_CLOSED) { + next = iter->next; + nk_remove_window(ctx, iter); + nk_free_window(ctx, iter); + iter = next; + } else iter = iter->next; + } + ctx->seq++; +} +NK_LIB void +nk_start_buffer(struct nk_context *ctx, struct nk_command_buffer *buffer) +{ + NK_ASSERT(ctx); + NK_ASSERT(buffer); + if (!ctx || !buffer) return; + buffer->begin = ctx->memory.allocated; + buffer->end = buffer->begin; + buffer->last = buffer->begin; + buffer->clip = nk_null_rect; +} +NK_LIB void +nk_start(struct nk_context *ctx, struct nk_window *win) +{ + NK_ASSERT(ctx); + NK_ASSERT(win); + nk_start_buffer(ctx, &win->buffer); +} +NK_LIB void +nk_start_popup(struct nk_context *ctx, struct nk_window *win) +{ + struct nk_popup_buffer *buf; + NK_ASSERT(ctx); + NK_ASSERT(win); + if (!ctx || !win) return; + + /* save buffer fill state for popup */ + buf = &win->popup.buf; + buf->begin = win->buffer.end; + buf->end = win->buffer.end; + buf->parent = win->buffer.last; + buf->last = buf->begin; + buf->active = nk_true; +} +NK_LIB void +nk_finish_popup(struct nk_context *ctx, struct nk_window *win) +{ + struct nk_popup_buffer *buf; + NK_ASSERT(ctx); + NK_ASSERT(win); + if (!ctx || !win) return; + + buf = &win->popup.buf; + buf->last = win->buffer.last; + buf->end = win->buffer.end; +} +NK_LIB void +nk_finish_buffer(struct nk_context *ctx, struct nk_command_buffer *buffer) +{ + NK_ASSERT(ctx); + NK_ASSERT(buffer); + if (!ctx || !buffer) return; + buffer->end = ctx->memory.allocated; +} +NK_LIB void +nk_finish(struct nk_context *ctx, struct nk_window *win) +{ + struct nk_popup_buffer *buf; + struct nk_command *parent_last; + void *memory; + + NK_ASSERT(ctx); + NK_ASSERT(win); + if (!ctx || !win) return; + nk_finish_buffer(ctx, &win->buffer); + if (!win->popup.buf.active) return; + + buf = &win->popup.buf; + memory = ctx->memory.memory.ptr; + parent_last = nk_ptr_add(struct nk_command, memory, buf->parent); + parent_last->next = buf->end; +} +NK_LIB void +nk_build(struct nk_context *ctx) +{ + struct nk_window *it = 0; + struct nk_command *cmd = 0; + nk_byte *buffer = 0; + + /* draw cursor overlay */ + if (!ctx->style.cursor_active) + ctx->style.cursor_active = ctx->style.cursors[NK_CURSOR_ARROW]; + if (ctx->style.cursor_active && !ctx->input.mouse.grabbed && ctx->style.cursor_visible) { + struct nk_rect mouse_bounds; + const struct nk_cursor *cursor = ctx->style.cursor_active; + nk_command_buffer_init(&ctx->overlay, &ctx->memory, NK_CLIPPING_OFF); + nk_start_buffer(ctx, &ctx->overlay); + + mouse_bounds.x = ctx->input.mouse.pos.x - cursor->offset.x; + mouse_bounds.y = ctx->input.mouse.pos.y - cursor->offset.y; + mouse_bounds.w = cursor->size.x; + mouse_bounds.h = cursor->size.y; + + nk_draw_image(&ctx->overlay, mouse_bounds, &cursor->img, nk_white); + nk_finish_buffer(ctx, &ctx->overlay); + } + /* build one big draw command list out of all window buffers */ + it = ctx->begin; + buffer = (nk_byte*)ctx->memory.memory.ptr; + while (it != 0) { + struct nk_window *next = it->next; + if (it->buffer.last == it->buffer.begin || (it->flags & NK_WINDOW_HIDDEN)|| + it->seq != ctx->seq) + goto cont; + + cmd = nk_ptr_add(struct nk_command, buffer, it->buffer.last); + while (next && ((next->buffer.last == next->buffer.begin) || + (next->flags & NK_WINDOW_HIDDEN) || next->seq != ctx->seq)) + next = next->next; /* skip empty command buffers */ + + if (next) cmd->next = next->buffer.begin; + cont: it = next; + } + /* append all popup draw commands into lists */ + it = ctx->begin; + while (it != 0) { + struct nk_window *next = it->next; + struct nk_popup_buffer *buf; + if (!it->popup.buf.active) + goto skip; + + buf = &it->popup.buf; + cmd->next = buf->begin; + cmd = nk_ptr_add(struct nk_command, buffer, buf->last); + buf->active = nk_false; + skip: it = next; + } + if (cmd) { + /* append overlay commands */ + if (ctx->overlay.end != ctx->overlay.begin) + cmd->next = ctx->overlay.begin; + else cmd->next = ctx->memory.allocated; + } +} +NK_API const struct nk_command* +nk__begin(struct nk_context *ctx) +{ + struct nk_window *iter; + nk_byte *buffer; + NK_ASSERT(ctx); + if (!ctx) return 0; + if (!ctx->count) return 0; + + buffer = (nk_byte*)ctx->memory.memory.ptr; + if (!ctx->build) { + nk_build(ctx); + ctx->build = nk_true; + } + iter = ctx->begin; + while (iter && ((iter->buffer.begin == iter->buffer.end) || + (iter->flags & NK_WINDOW_HIDDEN) || iter->seq != ctx->seq)) + iter = iter->next; + if (!iter) return 0; + return nk_ptr_add_const(struct nk_command, buffer, iter->buffer.begin); +} + +NK_API const struct nk_command* +nk__next(struct nk_context *ctx, const struct nk_command *cmd) +{ + nk_byte *buffer; + const struct nk_command *next; + NK_ASSERT(ctx); + if (!ctx || !cmd || !ctx->count) return 0; + if (cmd->next >= ctx->memory.allocated) return 0; + buffer = (nk_byte*)ctx->memory.memory.ptr; + next = nk_ptr_add_const(struct nk_command, buffer, cmd->next); + return next; +} + + + + + + +/* =============================================================== + * + * POOL + * + * ===============================================================*/ +NK_LIB void +nk_pool_init(struct nk_pool *pool, struct nk_allocator *alloc, + unsigned int capacity) +{ + NK_ASSERT(capacity >= 1); + nk_zero(pool, sizeof(*pool)); + pool->alloc = *alloc; + pool->capacity = capacity; + pool->type = NK_BUFFER_DYNAMIC; + pool->pages = 0; +} +NK_LIB void +nk_pool_free(struct nk_pool *pool) +{ + struct nk_page *iter; + if (!pool) return; + iter = pool->pages; + if (pool->type == NK_BUFFER_FIXED) return; + while (iter) { + struct nk_page *next = iter->next; + pool->alloc.free(pool->alloc.userdata, iter); + iter = next; + } +} +NK_LIB void +nk_pool_init_fixed(struct nk_pool *pool, void *memory, nk_size size) +{ + nk_zero(pool, sizeof(*pool)); + NK_ASSERT(size >= sizeof(struct nk_page)); + if (size < sizeof(struct nk_page)) return; + /* first nk_page_element is embedded in nk_page, additional elements follow in adjacent space */ + pool->capacity = 1 + (unsigned)(size - sizeof(struct nk_page)) / sizeof(struct nk_page_element); + pool->pages = (struct nk_page*)memory; + pool->type = NK_BUFFER_FIXED; + pool->size = size; +} +NK_LIB struct nk_page_element* +nk_pool_alloc(struct nk_pool *pool) +{ + if (!pool->pages || pool->pages->size >= pool->capacity) { + /* allocate new page */ + struct nk_page *page; + if (pool->type == NK_BUFFER_FIXED) { + NK_ASSERT(pool->pages); + if (!pool->pages) return 0; + NK_ASSERT(pool->pages->size < pool->capacity); + return 0; + } else { + nk_size size = sizeof(struct nk_page); + size += (pool->capacity - 1) * sizeof(struct nk_page_element); + page = (struct nk_page*)pool->alloc.alloc(pool->alloc.userdata,0, size); + page->next = pool->pages; + pool->pages = page; + page->size = 0; + } + } return &pool->pages->win[pool->pages->size++]; +} + + + + + +/* =============================================================== + * + * PAGE ELEMENT + * + * ===============================================================*/ +NK_LIB struct nk_page_element* +nk_create_page_element(struct nk_context *ctx) +{ + struct nk_page_element *elem; + if (ctx->freelist) { + /* unlink page element from free list */ + elem = ctx->freelist; + ctx->freelist = elem->next; + } else if (ctx->use_pool) { + /* allocate page element from memory pool */ + elem = nk_pool_alloc(&ctx->pool); + NK_ASSERT(elem); + if (!elem) return 0; + } else { + /* allocate new page element from back of fixed size memory buffer */ + NK_STORAGE const nk_size size = sizeof(struct nk_page_element); + NK_STORAGE const nk_size align = NK_ALIGNOF(struct nk_page_element); + elem = (struct nk_page_element*)nk_buffer_alloc(&ctx->memory, NK_BUFFER_BACK, size, align); + NK_ASSERT(elem); + if (!elem) return 0; + } + nk_zero_struct(*elem); + elem->next = 0; + elem->prev = 0; + return elem; +} +NK_LIB void +nk_link_page_element_into_freelist(struct nk_context *ctx, + struct nk_page_element *elem) +{ + /* link table into freelist */ + if (!ctx->freelist) { + ctx->freelist = elem; + } else { + elem->next = ctx->freelist; + ctx->freelist = elem; + } +} +NK_LIB void +nk_free_page_element(struct nk_context *ctx, struct nk_page_element *elem) +{ + /* we have a pool so just add to free list */ + if (ctx->use_pool) { + nk_link_page_element_into_freelist(ctx, elem); + return; + } + /* if possible remove last element from back of fixed memory buffer */ + {void *elem_end = (void*)(elem + 1); + void *buffer_end = (nk_byte*)ctx->memory.memory.ptr + ctx->memory.size; + if (elem_end == buffer_end) + ctx->memory.size -= sizeof(struct nk_page_element); + else nk_link_page_element_into_freelist(ctx, elem);} +} + + + + + +/* =============================================================== + * + * TABLE + * + * ===============================================================*/ +NK_LIB struct nk_table* +nk_create_table(struct nk_context *ctx) +{ + struct nk_page_element *elem; + elem = nk_create_page_element(ctx); + if (!elem) return 0; + nk_zero_struct(*elem); + return &elem->data.tbl; +} +NK_LIB void +nk_free_table(struct nk_context *ctx, struct nk_table *tbl) +{ + union nk_page_data *pd = NK_CONTAINER_OF(tbl, union nk_page_data, tbl); + struct nk_page_element *pe = NK_CONTAINER_OF(pd, struct nk_page_element, data); + nk_free_page_element(ctx, pe); +} +NK_LIB void +nk_push_table(struct nk_window *win, struct nk_table *tbl) +{ + if (!win->tables) { + win->tables = tbl; + tbl->next = 0; + tbl->prev = 0; + tbl->size = 0; + win->table_count = 1; + return; + } + win->tables->prev = tbl; + tbl->next = win->tables; + tbl->prev = 0; + tbl->size = 0; + win->tables = tbl; + win->table_count++; +} +NK_LIB void +nk_remove_table(struct nk_window *win, struct nk_table *tbl) +{ + if (win->tables == tbl) + win->tables = tbl->next; + if (tbl->next) + tbl->next->prev = tbl->prev; + if (tbl->prev) + tbl->prev->next = tbl->next; + tbl->next = 0; + tbl->prev = 0; +} +NK_LIB nk_uint* +nk_add_value(struct nk_context *ctx, struct nk_window *win, + nk_hash name, nk_uint value) +{ + NK_ASSERT(ctx); + NK_ASSERT(win); + if (!win || !ctx) return 0; + if (!win->tables || win->tables->size >= NK_VALUE_PAGE_CAPACITY) { + struct nk_table *tbl = nk_create_table(ctx); + NK_ASSERT(tbl); + if (!tbl) return 0; + nk_push_table(win, tbl); + } + win->tables->seq = win->seq; + win->tables->keys[win->tables->size] = name; + win->tables->values[win->tables->size] = value; + return &win->tables->values[win->tables->size++]; +} +NK_LIB nk_uint* +nk_find_value(struct nk_window *win, nk_hash name) +{ + struct nk_table *iter = win->tables; + while (iter) { + unsigned int i = 0; + unsigned int size = iter->size; + for (i = 0; i < size; ++i) { + if (iter->keys[i] == name) { + iter->seq = win->seq; + return &iter->values[i]; + } + } size = NK_VALUE_PAGE_CAPACITY; + iter = iter->next; + } + return 0; +} + + + + + +/* =============================================================== + * + * PANEL + * + * ===============================================================*/ +NK_LIB void* +nk_create_panel(struct nk_context *ctx) +{ + struct nk_page_element *elem; + elem = nk_create_page_element(ctx); + if (!elem) return 0; + nk_zero_struct(*elem); + return &elem->data.pan; +} +NK_LIB void +nk_free_panel(struct nk_context *ctx, struct nk_panel *pan) +{ + union nk_page_data *pd = NK_CONTAINER_OF(pan, union nk_page_data, pan); + struct nk_page_element *pe = NK_CONTAINER_OF(pd, struct nk_page_element, data); + nk_free_page_element(ctx, pe); +} +NK_LIB nk_bool +nk_panel_has_header(nk_flags flags, const char *title) +{ + nk_bool active = 0; + active = (flags & (NK_WINDOW_CLOSABLE|NK_WINDOW_MINIMIZABLE)); + active = active || (flags & NK_WINDOW_TITLE); + active = active && !(flags & NK_WINDOW_HIDDEN) && title; + return active; +} +NK_LIB struct nk_vec2 +nk_panel_get_padding(const struct nk_style *style, enum nk_panel_type type) +{ + switch (type) { + default: + case NK_PANEL_WINDOW: return style->window.padding; + case NK_PANEL_GROUP: return style->window.group_padding; + case NK_PANEL_POPUP: return style->window.popup_padding; + case NK_PANEL_CONTEXTUAL: return style->window.contextual_padding; + case NK_PANEL_COMBO: return style->window.combo_padding; + case NK_PANEL_MENU: return style->window.menu_padding; + case NK_PANEL_TOOLTIP: return style->window.menu_padding;} +} +NK_LIB float +nk_panel_get_border(const struct nk_style *style, nk_flags flags, + enum nk_panel_type type) +{ + if (flags & NK_WINDOW_BORDER) { + switch (type) { + default: + case NK_PANEL_WINDOW: return style->window.border; + case NK_PANEL_GROUP: return style->window.group_border; + case NK_PANEL_POPUP: return style->window.popup_border; + case NK_PANEL_CONTEXTUAL: return style->window.contextual_border; + case NK_PANEL_COMBO: return style->window.combo_border; + case NK_PANEL_MENU: return style->window.menu_border; + case NK_PANEL_TOOLTIP: return style->window.menu_border; + }} else return 0; +} +NK_LIB struct nk_color +nk_panel_get_border_color(const struct nk_style *style, enum nk_panel_type type) +{ + switch (type) { + default: + case NK_PANEL_WINDOW: return style->window.border_color; + case NK_PANEL_GROUP: return style->window.group_border_color; + case NK_PANEL_POPUP: return style->window.popup_border_color; + case NK_PANEL_CONTEXTUAL: return style->window.contextual_border_color; + case NK_PANEL_COMBO: return style->window.combo_border_color; + case NK_PANEL_MENU: return style->window.menu_border_color; + case NK_PANEL_TOOLTIP: return style->window.menu_border_color;} +} +NK_LIB nk_bool +nk_panel_is_sub(enum nk_panel_type type) +{ + return (type & NK_PANEL_SET_SUB)?1:0; +} +NK_LIB nk_bool +nk_panel_is_nonblock(enum nk_panel_type type) +{ + return (type & NK_PANEL_SET_NONBLOCK)?1:0; +} +NK_LIB nk_bool +nk_panel_begin(struct nk_context *ctx, const char *title, enum nk_panel_type panel_type) +{ + struct nk_input *in; + struct nk_window *win; + struct nk_panel *layout; + struct nk_command_buffer *out; + const struct nk_style *style; + const struct nk_user_font *font; + + struct nk_vec2 scrollbar_size; + struct nk_vec2 panel_padding; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) return 0; + nk_zero(ctx->current->layout, sizeof(*ctx->current->layout)); + if ((ctx->current->flags & NK_WINDOW_HIDDEN) || (ctx->current->flags & NK_WINDOW_CLOSED)) { + nk_zero(ctx->current->layout, sizeof(struct nk_panel)); + ctx->current->layout->type = panel_type; + return 0; + } + /* pull state into local stack */ + style = &ctx->style; + font = style->font; + win = ctx->current; + layout = win->layout; + out = &win->buffer; + in = (win->flags & NK_WINDOW_NO_INPUT) ? 0: &ctx->input; +#ifdef NK_INCLUDE_COMMAND_USERDATA + win->buffer.userdata = ctx->userdata; +#endif + /* pull style configuration into local stack */ + scrollbar_size = style->window.scrollbar_size; + panel_padding = nk_panel_get_padding(style, panel_type); + + /* window movement */ + if ((win->flags & NK_WINDOW_MOVABLE) && !(win->flags & NK_WINDOW_ROM)) { + int left_mouse_down; + int left_mouse_clicked; + int left_mouse_click_in_cursor; + + /* calculate draggable window space */ + struct nk_rect header; + header.x = win->bounds.x; + header.y = win->bounds.y; + header.w = win->bounds.w; + if (nk_panel_has_header(win->flags, title)) { + header.h = font->height + 2.0f * style->window.header.padding.y; + header.h += 2.0f * style->window.header.label_padding.y; + } else header.h = panel_padding.y; + + /* window movement by dragging */ + left_mouse_down = in->mouse.buttons[NK_BUTTON_LEFT].down; + left_mouse_clicked = (int)in->mouse.buttons[NK_BUTTON_LEFT].clicked; + left_mouse_click_in_cursor = nk_input_has_mouse_click_down_in_rect(in, + NK_BUTTON_LEFT, header, nk_true); + if (left_mouse_down && left_mouse_click_in_cursor && !left_mouse_clicked) { + win->bounds.x = win->bounds.x + in->mouse.delta.x; + win->bounds.y = win->bounds.y + in->mouse.delta.y; + in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.x += in->mouse.delta.x; + in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.y += in->mouse.delta.y; + ctx->style.cursor_active = ctx->style.cursors[NK_CURSOR_MOVE]; + } + } + + /* setup panel */ + layout->type = panel_type; + layout->flags = win->flags; + layout->bounds = win->bounds; + layout->bounds.x += panel_padding.x; + layout->bounds.w -= 2*panel_padding.x; + if (win->flags & NK_WINDOW_BORDER) { + layout->border = nk_panel_get_border(style, win->flags, panel_type); + layout->bounds = nk_shrink_rect(layout->bounds, layout->border); + } else layout->border = 0; + layout->at_y = layout->bounds.y; + layout->at_x = layout->bounds.x; + layout->max_x = 0; + layout->header_height = 0; + layout->footer_height = 0; + nk_layout_reset_min_row_height(ctx); + layout->row.index = 0; + layout->row.columns = 0; + layout->row.ratio = 0; + layout->row.item_width = 0; + layout->row.tree_depth = 0; + layout->row.height = panel_padding.y; + layout->has_scrolling = nk_true; + if (!(win->flags & NK_WINDOW_NO_SCROLLBAR)) + layout->bounds.w -= scrollbar_size.x; + if (!nk_panel_is_nonblock(panel_type)) { + layout->footer_height = 0; + if (!(win->flags & NK_WINDOW_NO_SCROLLBAR) || win->flags & NK_WINDOW_SCALABLE) + layout->footer_height = scrollbar_size.y; + layout->bounds.h -= layout->footer_height; + } + + /* panel header */ + if (nk_panel_has_header(win->flags, title)) + { + struct nk_text text; + struct nk_rect header; + const struct nk_style_item *background = 0; + + /* calculate header bounds */ + header.x = win->bounds.x; + header.y = win->bounds.y; + header.w = win->bounds.w; + header.h = font->height + 2.0f * style->window.header.padding.y; + header.h += (2.0f * style->window.header.label_padding.y); + + /* shrink panel by header */ + layout->header_height = header.h; + layout->bounds.y += header.h; + layout->bounds.h -= header.h; + layout->at_y += header.h; + + /* select correct header background and text color */ + if (ctx->active == win) { + background = &style->window.header.active; + text.text = style->window.header.label_active; + } else if (nk_input_is_mouse_hovering_rect(&ctx->input, header)) { + background = &style->window.header.hover; + text.text = style->window.header.label_hover; + } else { + background = &style->window.header.normal; + text.text = style->window.header.label_normal; + } + + /* draw header background */ + header.h += 1.0f; + if (background->type == NK_STYLE_ITEM_IMAGE) { + text.background = nk_rgba(0,0,0,0); + nk_draw_image(&win->buffer, header, &background->data.image, nk_white); + } else { + text.background = background->data.color; + nk_fill_rect(out, header, 0, background->data.color); + } + + /* window close button */ + {struct nk_rect button; + button.y = header.y + style->window.header.padding.y; + button.h = header.h - 2 * style->window.header.padding.y; + button.w = button.h; + if (win->flags & NK_WINDOW_CLOSABLE) { + nk_flags ws = 0; + if (style->window.header.align == NK_HEADER_RIGHT) { + button.x = (header.w + header.x) - (button.w + style->window.header.padding.x); + header.w -= button.w + style->window.header.spacing.x + style->window.header.padding.x; + } else { + button.x = header.x + style->window.header.padding.x; + header.x += button.w + style->window.header.spacing.x + style->window.header.padding.x; + } + + if (nk_do_button_symbol(&ws, &win->buffer, button, + style->window.header.close_symbol, NK_BUTTON_DEFAULT, + &style->window.header.close_button, in, style->font) && !(win->flags & NK_WINDOW_ROM)) + { + layout->flags |= NK_WINDOW_HIDDEN; + layout->flags &= (nk_flags)~NK_WINDOW_MINIMIZED; + } + } + + /* window minimize button */ + if (win->flags & NK_WINDOW_MINIMIZABLE) { + nk_flags ws = 0; + if (style->window.header.align == NK_HEADER_RIGHT) { + button.x = (header.w + header.x) - button.w; + if (!(win->flags & NK_WINDOW_CLOSABLE)) { + button.x -= style->window.header.padding.x; + header.w -= style->window.header.padding.x; + } + header.w -= button.w + style->window.header.spacing.x; + } else { + button.x = header.x; + header.x += button.w + style->window.header.spacing.x + style->window.header.padding.x; + } + if (nk_do_button_symbol(&ws, &win->buffer, button, (layout->flags & NK_WINDOW_MINIMIZED)? + style->window.header.maximize_symbol: style->window.header.minimize_symbol, + NK_BUTTON_DEFAULT, &style->window.header.minimize_button, in, style->font) && !(win->flags & NK_WINDOW_ROM)) + layout->flags = (layout->flags & NK_WINDOW_MINIMIZED) ? + layout->flags & (nk_flags)~NK_WINDOW_MINIMIZED: + layout->flags | NK_WINDOW_MINIMIZED; + }} + + {/* window header title */ + int text_len = nk_strlen(title); + struct nk_rect label = {0,0,0,0}; + float t = font->width(font->userdata, font->height, title, text_len); + text.padding = nk_vec2(0,0); + + label.x = header.x + style->window.header.padding.x; + label.x += style->window.header.label_padding.x; + label.y = header.y + style->window.header.label_padding.y; + label.h = font->height + 2 * style->window.header.label_padding.y; + label.w = t + 2 * style->window.header.spacing.x; + label.w = NK_CLAMP(0, label.w, header.x + header.w - label.x); + nk_widget_text(out, label,(const char*)title, text_len, &text, NK_TEXT_LEFT, font);} + } + + /* draw window background */ + if (!(layout->flags & NK_WINDOW_MINIMIZED) && !(layout->flags & NK_WINDOW_DYNAMIC)) { + struct nk_rect body; + body.x = win->bounds.x; + body.w = win->bounds.w; + body.y = (win->bounds.y + layout->header_height); + body.h = (win->bounds.h - layout->header_height); + if (style->window.fixed_background.type == NK_STYLE_ITEM_IMAGE) + nk_draw_image(out, body, &style->window.fixed_background.data.image, nk_white); + else nk_fill_rect(out, body, 0, style->window.fixed_background.data.color); + } + + /* set clipping rectangle */ + {struct nk_rect clip; + layout->clip = layout->bounds; + nk_unify(&clip, &win->buffer.clip, layout->clip.x, layout->clip.y, + layout->clip.x + layout->clip.w, layout->clip.y + layout->clip.h); + nk_push_scissor(out, clip); + layout->clip = clip;} + return !(layout->flags & NK_WINDOW_HIDDEN) && !(layout->flags & NK_WINDOW_MINIMIZED); +} +NK_LIB void +nk_panel_end(struct nk_context *ctx) +{ + struct nk_input *in; + struct nk_window *window; + struct nk_panel *layout; + const struct nk_style *style; + struct nk_command_buffer *out; + + struct nk_vec2 scrollbar_size; + struct nk_vec2 panel_padding; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + window = ctx->current; + layout = window->layout; + style = &ctx->style; + out = &window->buffer; + in = (layout->flags & NK_WINDOW_ROM || layout->flags & NK_WINDOW_NO_INPUT) ? 0 :&ctx->input; + if (!nk_panel_is_sub(layout->type)) + nk_push_scissor(out, nk_null_rect); + + /* cache configuration data */ + scrollbar_size = style->window.scrollbar_size; + panel_padding = nk_panel_get_padding(style, layout->type); + + /* update the current cursor Y-position to point over the last added widget */ + layout->at_y += layout->row.height; + + /* dynamic panels */ + if (layout->flags & NK_WINDOW_DYNAMIC && !(layout->flags & NK_WINDOW_MINIMIZED)) + { + /* update panel height to fit dynamic growth */ + struct nk_rect empty_space; + if (layout->at_y < (layout->bounds.y + layout->bounds.h)) + layout->bounds.h = layout->at_y - layout->bounds.y; + + /* fill top empty space */ + empty_space.x = window->bounds.x; + empty_space.y = layout->bounds.y; + empty_space.h = panel_padding.y; + empty_space.w = window->bounds.w; + nk_fill_rect(out, empty_space, 0, style->window.background); + + /* fill left empty space */ + empty_space.x = window->bounds.x; + empty_space.y = layout->bounds.y; + empty_space.w = panel_padding.x + layout->border; + empty_space.h = layout->bounds.h; + nk_fill_rect(out, empty_space, 0, style->window.background); + + /* fill right empty space */ + empty_space.x = layout->bounds.x + layout->bounds.w; + empty_space.y = layout->bounds.y; + empty_space.w = panel_padding.x + layout->border; + empty_space.h = layout->bounds.h; + if (*layout->offset_y == 0 && !(layout->flags & NK_WINDOW_NO_SCROLLBAR)) + empty_space.w += scrollbar_size.x; + nk_fill_rect(out, empty_space, 0, style->window.background); + + /* fill bottom empty space */ + if (layout->footer_height > 0) { + empty_space.x = window->bounds.x; + empty_space.y = layout->bounds.y + layout->bounds.h; + empty_space.w = window->bounds.w; + empty_space.h = layout->footer_height; + nk_fill_rect(out, empty_space, 0, style->window.background); + } + } + + /* scrollbars */ + if (!(layout->flags & NK_WINDOW_NO_SCROLLBAR) && + !(layout->flags & NK_WINDOW_MINIMIZED) && + window->scrollbar_hiding_timer < NK_SCROLLBAR_HIDING_TIMEOUT) + { + struct nk_rect scroll; + int scroll_has_scrolling; + float scroll_target; + float scroll_offset; + float scroll_step; + float scroll_inc; + + /* mouse wheel scrolling */ + if (nk_panel_is_sub(layout->type)) + { + /* sub-window mouse wheel scrolling */ + struct nk_window *root_window = window; + struct nk_panel *root_panel = window->layout; + while (root_panel->parent) + root_panel = root_panel->parent; + while (root_window->parent) + root_window = root_window->parent; + + /* only allow scrolling if parent window is active */ + scroll_has_scrolling = 0; + if ((root_window == ctx->active) && layout->has_scrolling) { + /* and panel is being hovered and inside clip rect*/ + if (nk_input_is_mouse_hovering_rect(in, layout->bounds) && + NK_INTERSECT(layout->bounds.x, layout->bounds.y, layout->bounds.w, layout->bounds.h, + root_panel->clip.x, root_panel->clip.y, root_panel->clip.w, root_panel->clip.h)) + { + /* deactivate all parent scrolling */ + root_panel = window->layout; + while (root_panel->parent) { + root_panel->has_scrolling = nk_false; + root_panel = root_panel->parent; + } + root_panel->has_scrolling = nk_false; + scroll_has_scrolling = nk_true; + } + } + } else if (!nk_panel_is_sub(layout->type)) { + /* window mouse wheel scrolling */ + scroll_has_scrolling = (window == ctx->active) && layout->has_scrolling; + if (in && (in->mouse.scroll_delta.y > 0 || in->mouse.scroll_delta.x > 0) && scroll_has_scrolling) + window->scrolled = nk_true; + else window->scrolled = nk_false; + } else scroll_has_scrolling = nk_false; + + { + /* vertical scrollbar */ + nk_flags state = 0; + scroll.x = layout->bounds.x + layout->bounds.w + panel_padding.x; + scroll.y = layout->bounds.y; + scroll.w = scrollbar_size.x; + scroll.h = layout->bounds.h; + + scroll_offset = (float)*layout->offset_y; + scroll_step = scroll.h * 0.10f; + scroll_inc = scroll.h * 0.01f; + scroll_target = (float)(int)(layout->at_y - scroll.y); + scroll_offset = nk_do_scrollbarv(&state, out, scroll, scroll_has_scrolling, + scroll_offset, scroll_target, scroll_step, scroll_inc, + &ctx->style.scrollv, in, style->font); + *layout->offset_y = (nk_uint)scroll_offset; + if (in && scroll_has_scrolling) + in->mouse.scroll_delta.y = 0; + } + { + /* horizontal scrollbar */ + nk_flags state = 0; + scroll.x = layout->bounds.x; + scroll.y = layout->bounds.y + layout->bounds.h; + scroll.w = layout->bounds.w; + scroll.h = scrollbar_size.y; + + scroll_offset = (float)*layout->offset_x; + scroll_target = (float)(int)(layout->max_x - scroll.x); + scroll_step = layout->max_x * 0.05f; + scroll_inc = layout->max_x * 0.005f; + scroll_offset = nk_do_scrollbarh(&state, out, scroll, scroll_has_scrolling, + scroll_offset, scroll_target, scroll_step, scroll_inc, + &ctx->style.scrollh, in, style->font); + *layout->offset_x = (nk_uint)scroll_offset; + } + } + + /* hide scroll if no user input */ + if (window->flags & NK_WINDOW_SCROLL_AUTO_HIDE) { + int has_input = ctx->input.mouse.delta.x != 0 || ctx->input.mouse.delta.y != 0 || ctx->input.mouse.scroll_delta.y != 0; + int is_window_hovered = nk_window_is_hovered(ctx); + int any_item_active = (ctx->last_widget_state & NK_WIDGET_STATE_MODIFIED); + if ((!has_input && is_window_hovered) || (!is_window_hovered && !any_item_active)) + window->scrollbar_hiding_timer += ctx->delta_time_seconds; + else window->scrollbar_hiding_timer = 0; + } else window->scrollbar_hiding_timer = 0; + + /* window border */ + if (layout->flags & NK_WINDOW_BORDER) + { + struct nk_color border_color = nk_panel_get_border_color(style, layout->type); + const float padding_y = (layout->flags & NK_WINDOW_MINIMIZED) + ? (style->window.border + window->bounds.y + layout->header_height) + : ((layout->flags & NK_WINDOW_DYNAMIC) + ? (layout->bounds.y + layout->bounds.h + layout->footer_height) + : (window->bounds.y + window->bounds.h)); + struct nk_rect b = window->bounds; + b.h = padding_y - window->bounds.y; + nk_stroke_rect(out, b, 0, layout->border, border_color); + } + + /* scaler */ + if ((layout->flags & NK_WINDOW_SCALABLE) && in && !(layout->flags & NK_WINDOW_MINIMIZED)) + { + /* calculate scaler bounds */ + struct nk_rect scaler; + scaler.w = scrollbar_size.x; + scaler.h = scrollbar_size.y; + scaler.y = layout->bounds.y + layout->bounds.h; + if (layout->flags & NK_WINDOW_SCALE_LEFT) + scaler.x = layout->bounds.x - panel_padding.x * 0.5f; + else scaler.x = layout->bounds.x + layout->bounds.w + panel_padding.x; + if (layout->flags & NK_WINDOW_NO_SCROLLBAR) + scaler.x -= scaler.w; + + /* draw scaler */ + {const struct nk_style_item *item = &style->window.scaler; + if (item->type == NK_STYLE_ITEM_IMAGE) + nk_draw_image(out, scaler, &item->data.image, nk_white); + else { + if (layout->flags & NK_WINDOW_SCALE_LEFT) { + nk_fill_triangle(out, scaler.x, scaler.y, scaler.x, + scaler.y + scaler.h, scaler.x + scaler.w, + scaler.y + scaler.h, item->data.color); + } else { + nk_fill_triangle(out, scaler.x + scaler.w, scaler.y, scaler.x + scaler.w, + scaler.y + scaler.h, scaler.x, scaler.y + scaler.h, item->data.color); + } + }} + + /* do window scaling */ + if (!(window->flags & NK_WINDOW_ROM)) { + struct nk_vec2 window_size = style->window.min_size; + int left_mouse_down = in->mouse.buttons[NK_BUTTON_LEFT].down; + int left_mouse_click_in_scaler = nk_input_has_mouse_click_down_in_rect(in, + NK_BUTTON_LEFT, scaler, nk_true); + + if (left_mouse_down && left_mouse_click_in_scaler) { + float delta_x = in->mouse.delta.x; + if (layout->flags & NK_WINDOW_SCALE_LEFT) { + delta_x = -delta_x; + window->bounds.x += in->mouse.delta.x; + } + /* dragging in x-direction */ + if (window->bounds.w + delta_x >= window_size.x) { + if ((delta_x < 0) || (delta_x > 0 && in->mouse.pos.x >= scaler.x)) { + window->bounds.w = window->bounds.w + delta_x; + scaler.x += in->mouse.delta.x; + } + } + /* dragging in y-direction (only possible if static window) */ + if (!(layout->flags & NK_WINDOW_DYNAMIC)) { + if (window_size.y < window->bounds.h + in->mouse.delta.y) { + if ((in->mouse.delta.y < 0) || (in->mouse.delta.y > 0 && in->mouse.pos.y >= scaler.y)) { + window->bounds.h = window->bounds.h + in->mouse.delta.y; + scaler.y += in->mouse.delta.y; + } + } + } + ctx->style.cursor_active = ctx->style.cursors[NK_CURSOR_RESIZE_TOP_RIGHT_DOWN_LEFT]; + in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.x = scaler.x + scaler.w/2.0f; + in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.y = scaler.y + scaler.h/2.0f; + } + } + } + if (!nk_panel_is_sub(layout->type)) { + /* window is hidden so clear command buffer */ + if (layout->flags & NK_WINDOW_HIDDEN) + nk_command_buffer_reset(&window->buffer); + /* window is visible and not tab */ + else nk_finish(ctx, window); + } + + /* NK_WINDOW_REMOVE_ROM flag was set so remove NK_WINDOW_ROM */ + if (layout->flags & NK_WINDOW_REMOVE_ROM) { + layout->flags &= ~(nk_flags)NK_WINDOW_ROM; + layout->flags &= ~(nk_flags)NK_WINDOW_REMOVE_ROM; + } + window->flags = layout->flags; + + /* property garbage collector */ + if (window->property.active && window->property.old != window->property.seq && + window->property.active == window->property.prev) { + nk_zero(&window->property, sizeof(window->property)); + } else { + window->property.old = window->property.seq; + window->property.prev = window->property.active; + window->property.seq = 0; + } + /* edit garbage collector */ + if (window->edit.active && window->edit.old != window->edit.seq && + window->edit.active == window->edit.prev) { + nk_zero(&window->edit, sizeof(window->edit)); + } else { + window->edit.old = window->edit.seq; + window->edit.prev = window->edit.active; + window->edit.seq = 0; + } + /* contextual garbage collector */ + if (window->popup.active_con && window->popup.con_old != window->popup.con_count) { + window->popup.con_count = 0; + window->popup.con_old = 0; + window->popup.active_con = 0; + } else { + window->popup.con_old = window->popup.con_count; + window->popup.con_count = 0; + } + window->popup.combo_count = 0; + /* helper to make sure you have a 'nk_tree_push' for every 'nk_tree_pop' */ + NK_ASSERT(!layout->row.tree_depth); +} + + + + + +/* =============================================================== + * + * WINDOW + * + * ===============================================================*/ +NK_LIB void* +nk_create_window(struct nk_context *ctx) +{ + struct nk_page_element *elem; + elem = nk_create_page_element(ctx); + if (!elem) return 0; + elem->data.win.seq = ctx->seq; + return &elem->data.win; +} +NK_LIB void +nk_free_window(struct nk_context *ctx, struct nk_window *win) +{ + /* unlink windows from list */ + struct nk_table *it = win->tables; + if (win->popup.win) { + nk_free_window(ctx, win->popup.win); + win->popup.win = 0; + } + win->next = 0; + win->prev = 0; + + while (it) { + /*free window state tables */ + struct nk_table *n = it->next; + nk_remove_table(win, it); + nk_free_table(ctx, it); + if (it == win->tables) + win->tables = n; + it = n; + } + + /* link windows into freelist */ + {union nk_page_data *pd = NK_CONTAINER_OF(win, union nk_page_data, win); + struct nk_page_element *pe = NK_CONTAINER_OF(pd, struct nk_page_element, data); + nk_free_page_element(ctx, pe);} +} +NK_LIB struct nk_window* +nk_find_window(struct nk_context *ctx, nk_hash hash, const char *name) +{ + struct nk_window *iter; + iter = ctx->begin; + while (iter) { + NK_ASSERT(iter != iter->next); + if (iter->name == hash) { + int max_len = nk_strlen(iter->name_string); + if (!nk_stricmpn(iter->name_string, name, max_len)) + return iter; + } + iter = iter->next; + } + return 0; +} +NK_LIB void +nk_insert_window(struct nk_context *ctx, struct nk_window *win, + enum nk_window_insert_location loc) +{ + const struct nk_window *iter; + NK_ASSERT(ctx); + NK_ASSERT(win); + if (!win || !ctx) return; + + iter = ctx->begin; + while (iter) { + NK_ASSERT(iter != iter->next); + NK_ASSERT(iter != win); + if (iter == win) return; + iter = iter->next; + } + + if (!ctx->begin) { + win->next = 0; + win->prev = 0; + ctx->begin = win; + ctx->end = win; + ctx->count = 1; + return; + } + if (loc == NK_INSERT_BACK) { + struct nk_window *end; + end = ctx->end; + end->flags |= NK_WINDOW_ROM; + end->next = win; + win->prev = ctx->end; + win->next = 0; + ctx->end = win; + ctx->active = ctx->end; + ctx->end->flags &= ~(nk_flags)NK_WINDOW_ROM; + } else { + /*ctx->end->flags |= NK_WINDOW_ROM;*/ + ctx->begin->prev = win; + win->next = ctx->begin; + win->prev = 0; + ctx->begin = win; + ctx->begin->flags &= ~(nk_flags)NK_WINDOW_ROM; + } + ctx->count++; +} +NK_LIB void +nk_remove_window(struct nk_context *ctx, struct nk_window *win) +{ + if (win == ctx->begin || win == ctx->end) { + if (win == ctx->begin) { + ctx->begin = win->next; + if (win->next) + win->next->prev = 0; + } + if (win == ctx->end) { + ctx->end = win->prev; + if (win->prev) + win->prev->next = 0; + } + } else { + if (win->next) + win->next->prev = win->prev; + if (win->prev) + win->prev->next = win->next; + } + if (win == ctx->active || !ctx->active) { + ctx->active = ctx->end; + if (ctx->end) + ctx->end->flags &= ~(nk_flags)NK_WINDOW_ROM; + } + win->next = 0; + win->prev = 0; + ctx->count--; +} +NK_API nk_bool +nk_begin(struct nk_context *ctx, const char *title, + struct nk_rect bounds, nk_flags flags) +{ + return nk_begin_titled(ctx, title, title, bounds, flags); +} +NK_API nk_bool +nk_begin_titled(struct nk_context *ctx, const char *name, const char *title, + struct nk_rect bounds, nk_flags flags) +{ + struct nk_window *win; + struct nk_style *style; + nk_hash name_hash; + int name_len; + int ret = 0; + + NK_ASSERT(ctx); + NK_ASSERT(name); + NK_ASSERT(title); + NK_ASSERT(ctx->style.font && ctx->style.font->width && "if this triggers you forgot to add a font"); + NK_ASSERT(!ctx->current && "if this triggers you missed a `nk_end` call"); + if (!ctx || ctx->current || !title || !name) + return 0; + + /* find or create window */ + style = &ctx->style; + name_len = (int)nk_strlen(name); + name_hash = nk_murmur_hash(name, (int)name_len, NK_WINDOW_TITLE); + win = nk_find_window(ctx, name_hash, name); + if (!win) { + /* create new window */ + nk_size name_length = (nk_size)name_len; + win = (struct nk_window*)nk_create_window(ctx); + NK_ASSERT(win); + if (!win) return 0; + + if (flags & NK_WINDOW_BACKGROUND) + nk_insert_window(ctx, win, NK_INSERT_FRONT); + else nk_insert_window(ctx, win, NK_INSERT_BACK); + nk_command_buffer_init(&win->buffer, &ctx->memory, NK_CLIPPING_ON); + + win->flags = flags; + win->bounds = bounds; + win->name = name_hash; + name_length = NK_MIN(name_length, NK_WINDOW_MAX_NAME-1); + NK_MEMCPY(win->name_string, name, name_length); + win->name_string[name_length] = 0; + win->popup.win = 0; + if (!ctx->active) + ctx->active = win; + } else { + /* update window */ + win->flags &= ~(nk_flags)(NK_WINDOW_PRIVATE-1); + win->flags |= flags; + if (!(win->flags & (NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE))) + win->bounds = bounds; + /* If this assert triggers you either: + * + * I.) Have more than one window with the same name or + * II.) You forgot to actually draw the window. + * More specific you did not call `nk_clear` (nk_clear will be + * automatically called for you if you are using one of the + * provided demo backends). */ + NK_ASSERT(win->seq != ctx->seq); + win->seq = ctx->seq; + if (!ctx->active && !(win->flags & NK_WINDOW_HIDDEN)) { + ctx->active = win; + ctx->end = win; + } + } + if (win->flags & NK_WINDOW_HIDDEN) { + ctx->current = win; + win->layout = 0; + return 0; + } else nk_start(ctx, win); + + /* window overlapping */ + if (!(win->flags & NK_WINDOW_HIDDEN) && !(win->flags & NK_WINDOW_NO_INPUT)) + { + int inpanel, ishovered; + struct nk_window *iter = win; + float h = ctx->style.font->height + 2.0f * style->window.header.padding.y + + (2.0f * style->window.header.label_padding.y); + struct nk_rect win_bounds = (!(win->flags & NK_WINDOW_MINIMIZED))? + win->bounds: nk_rect(win->bounds.x, win->bounds.y, win->bounds.w, h); + + /* activate window if hovered and no other window is overlapping this window */ + inpanel = nk_input_has_mouse_click_down_in_rect(&ctx->input, NK_BUTTON_LEFT, win_bounds, nk_true); + inpanel = inpanel && ctx->input.mouse.buttons[NK_BUTTON_LEFT].clicked; + ishovered = nk_input_is_mouse_hovering_rect(&ctx->input, win_bounds); + if ((win != ctx->active) && ishovered && !ctx->input.mouse.buttons[NK_BUTTON_LEFT].down) { + iter = win->next; + while (iter) { + struct nk_rect iter_bounds = (!(iter->flags & NK_WINDOW_MINIMIZED))? + iter->bounds: nk_rect(iter->bounds.x, iter->bounds.y, iter->bounds.w, h); + if (NK_INTERSECT(win_bounds.x, win_bounds.y, win_bounds.w, win_bounds.h, + iter_bounds.x, iter_bounds.y, iter_bounds.w, iter_bounds.h) && + (!(iter->flags & NK_WINDOW_HIDDEN))) + break; + + if (iter->popup.win && iter->popup.active && !(iter->flags & NK_WINDOW_HIDDEN) && + NK_INTERSECT(win->bounds.x, win_bounds.y, win_bounds.w, win_bounds.h, + iter->popup.win->bounds.x, iter->popup.win->bounds.y, + iter->popup.win->bounds.w, iter->popup.win->bounds.h)) + break; + iter = iter->next; + } + } + + /* activate window if clicked */ + if (iter && inpanel && (win != ctx->end)) { + iter = win->next; + while (iter) { + /* try to find a panel with higher priority in the same position */ + struct nk_rect iter_bounds = (!(iter->flags & NK_WINDOW_MINIMIZED))? + iter->bounds: nk_rect(iter->bounds.x, iter->bounds.y, iter->bounds.w, h); + if (NK_INBOX(ctx->input.mouse.pos.x, ctx->input.mouse.pos.y, + iter_bounds.x, iter_bounds.y, iter_bounds.w, iter_bounds.h) && + !(iter->flags & NK_WINDOW_HIDDEN)) + break; + if (iter->popup.win && iter->popup.active && !(iter->flags & NK_WINDOW_HIDDEN) && + NK_INTERSECT(win_bounds.x, win_bounds.y, win_bounds.w, win_bounds.h, + iter->popup.win->bounds.x, iter->popup.win->bounds.y, + iter->popup.win->bounds.w, iter->popup.win->bounds.h)) + break; + iter = iter->next; + } + } + if (iter && !(win->flags & NK_WINDOW_ROM) && (win->flags & NK_WINDOW_BACKGROUND)) { + win->flags |= (nk_flags)NK_WINDOW_ROM; + iter->flags &= ~(nk_flags)NK_WINDOW_ROM; + ctx->active = iter; + if (!(iter->flags & NK_WINDOW_BACKGROUND)) { + /* current window is active in that position so transfer to top + * at the highest priority in stack */ + nk_remove_window(ctx, iter); + nk_insert_window(ctx, iter, NK_INSERT_BACK); + } + } else { + if (!iter && ctx->end != win) { + if (!(win->flags & NK_WINDOW_BACKGROUND)) { + /* current window is active in that position so transfer to top + * at the highest priority in stack */ + nk_remove_window(ctx, win); + nk_insert_window(ctx, win, NK_INSERT_BACK); + } + win->flags &= ~(nk_flags)NK_WINDOW_ROM; + ctx->active = win; + } + if (ctx->end != win && !(win->flags & NK_WINDOW_BACKGROUND)) + win->flags |= NK_WINDOW_ROM; + } + } + win->layout = (struct nk_panel*)nk_create_panel(ctx); + ctx->current = win; + ret = nk_panel_begin(ctx, title, NK_PANEL_WINDOW); + win->layout->offset_x = &win->scrollbar.x; + win->layout->offset_y = &win->scrollbar.y; + return ret; +} +NK_API void +nk_end(struct nk_context *ctx) +{ + struct nk_panel *layout; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current && "if this triggers you forgot to call `nk_begin`"); + if (!ctx || !ctx->current) + return; + + layout = ctx->current->layout; + if (!layout || (layout->type == NK_PANEL_WINDOW && (ctx->current->flags & NK_WINDOW_HIDDEN))) { + ctx->current = 0; + return; + } + nk_panel_end(ctx); + nk_free_panel(ctx, ctx->current->layout); + ctx->current = 0; +} +NK_API struct nk_rect +nk_window_get_bounds(const struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) return nk_rect(0,0,0,0); + return ctx->current->bounds; +} +NK_API struct nk_vec2 +nk_window_get_position(const struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) return nk_vec2(0,0); + return nk_vec2(ctx->current->bounds.x, ctx->current->bounds.y); +} +NK_API struct nk_vec2 +nk_window_get_size(const struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) return nk_vec2(0,0); + return nk_vec2(ctx->current->bounds.w, ctx->current->bounds.h); +} +NK_API float +nk_window_get_width(const struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) return 0; + return ctx->current->bounds.w; +} +NK_API float +nk_window_get_height(const struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) return 0; + return ctx->current->bounds.h; +} +NK_API struct nk_rect +nk_window_get_content_region(struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) return nk_rect(0,0,0,0); + return ctx->current->layout->clip; +} +NK_API struct nk_vec2 +nk_window_get_content_region_min(struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current) return nk_vec2(0,0); + return nk_vec2(ctx->current->layout->clip.x, ctx->current->layout->clip.y); +} +NK_API struct nk_vec2 +nk_window_get_content_region_max(struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current) return nk_vec2(0,0); + return nk_vec2(ctx->current->layout->clip.x + ctx->current->layout->clip.w, + ctx->current->layout->clip.y + ctx->current->layout->clip.h); +} +NK_API struct nk_vec2 +nk_window_get_content_region_size(struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current) return nk_vec2(0,0); + return nk_vec2(ctx->current->layout->clip.w, ctx->current->layout->clip.h); +} +NK_API struct nk_command_buffer* +nk_window_get_canvas(struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current) return 0; + return &ctx->current->buffer; +} +NK_API struct nk_panel* +nk_window_get_panel(struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) return 0; + return ctx->current->layout; +} +NK_API void +nk_window_get_scroll(struct nk_context *ctx, nk_uint *offset_x, nk_uint *offset_y) +{ + struct nk_window *win; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) + return ; + win = ctx->current; + if (offset_x) + *offset_x = win->scrollbar.x; + if (offset_y) + *offset_y = win->scrollbar.y; +} +NK_API nk_bool +nk_window_has_focus(const struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current) return 0; + return ctx->current == ctx->active; +} +NK_API nk_bool +nk_window_is_hovered(struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) return 0; + if(ctx->current->flags & NK_WINDOW_HIDDEN) + return 0; + return nk_input_is_mouse_hovering_rect(&ctx->input, ctx->current->bounds); +} +NK_API nk_bool +nk_window_is_any_hovered(struct nk_context *ctx) +{ + struct nk_window *iter; + NK_ASSERT(ctx); + if (!ctx) return 0; + iter = ctx->begin; + while (iter) { + /* check if window is being hovered */ + if(!(iter->flags & NK_WINDOW_HIDDEN)) { + /* check if window popup is being hovered */ + if (iter->popup.active && iter->popup.win && nk_input_is_mouse_hovering_rect(&ctx->input, iter->popup.win->bounds)) + return 1; + + if (iter->flags & NK_WINDOW_MINIMIZED) { + struct nk_rect header = iter->bounds; + header.h = ctx->style.font->height + 2 * ctx->style.window.header.padding.y; + if (nk_input_is_mouse_hovering_rect(&ctx->input, header)) + return 1; + } else if (nk_input_is_mouse_hovering_rect(&ctx->input, iter->bounds)) { + return 1; + } + } + iter = iter->next; + } + return 0; +} +NK_API nk_bool +nk_item_is_any_active(struct nk_context *ctx) +{ + int any_hovered = nk_window_is_any_hovered(ctx); + int any_active = (ctx->last_widget_state & NK_WIDGET_STATE_MODIFIED); + return any_hovered || any_active; +} +NK_API nk_bool +nk_window_is_collapsed(struct nk_context *ctx, const char *name) +{ + int title_len; + nk_hash title_hash; + struct nk_window *win; + NK_ASSERT(ctx); + if (!ctx) return 0; + + title_len = (int)nk_strlen(name); + title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE); + win = nk_find_window(ctx, title_hash, name); + if (!win) return 0; + return win->flags & NK_WINDOW_MINIMIZED; +} +NK_API nk_bool +nk_window_is_closed(struct nk_context *ctx, const char *name) +{ + int title_len; + nk_hash title_hash; + struct nk_window *win; + NK_ASSERT(ctx); + if (!ctx) return 1; + + title_len = (int)nk_strlen(name); + title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE); + win = nk_find_window(ctx, title_hash, name); + if (!win) return 1; + return (win->flags & NK_WINDOW_CLOSED); +} +NK_API nk_bool +nk_window_is_hidden(struct nk_context *ctx, const char *name) +{ + int title_len; + nk_hash title_hash; + struct nk_window *win; + NK_ASSERT(ctx); + if (!ctx) return 1; + + title_len = (int)nk_strlen(name); + title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE); + win = nk_find_window(ctx, title_hash, name); + if (!win) return 1; + return (win->flags & NK_WINDOW_HIDDEN); +} +NK_API nk_bool +nk_window_is_active(struct nk_context *ctx, const char *name) +{ + int title_len; + nk_hash title_hash; + struct nk_window *win; + NK_ASSERT(ctx); + if (!ctx) return 0; + + title_len = (int)nk_strlen(name); + title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE); + win = nk_find_window(ctx, title_hash, name); + if (!win) return 0; + return win == ctx->active; +} +NK_API struct nk_window* +nk_window_find(struct nk_context *ctx, const char *name) +{ + int title_len; + nk_hash title_hash; + title_len = (int)nk_strlen(name); + title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE); + return nk_find_window(ctx, title_hash, name); +} +NK_API void +nk_window_close(struct nk_context *ctx, const char *name) +{ + struct nk_window *win; + NK_ASSERT(ctx); + if (!ctx) return; + win = nk_window_find(ctx, name); + if (!win) return; + NK_ASSERT(ctx->current != win && "You cannot close a currently active window"); + if (ctx->current == win) return; + win->flags |= NK_WINDOW_HIDDEN; + win->flags |= NK_WINDOW_CLOSED; +} +NK_API void +nk_window_set_bounds(struct nk_context *ctx, + const char *name, struct nk_rect bounds) +{ + struct nk_window *win; + NK_ASSERT(ctx); + if (!ctx) return; + win = nk_window_find(ctx, name); + if (!win) return; + NK_ASSERT(ctx->current != win && "You cannot update a currently in procecss window"); + win->bounds = bounds; +} +NK_API void +nk_window_set_position(struct nk_context *ctx, + const char *name, struct nk_vec2 pos) +{ + struct nk_window *win = nk_window_find(ctx, name); + if (!win) return; + win->bounds.x = pos.x; + win->bounds.y = pos.y; +} +NK_API void +nk_window_set_size(struct nk_context *ctx, + const char *name, struct nk_vec2 size) +{ + struct nk_window *win = nk_window_find(ctx, name); + if (!win) return; + win->bounds.w = size.x; + win->bounds.h = size.y; +} +NK_API void +nk_window_set_scroll(struct nk_context *ctx, nk_uint offset_x, nk_uint offset_y) +{ + struct nk_window *win; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) + return; + win = ctx->current; + win->scrollbar.x = offset_x; + win->scrollbar.y = offset_y; +} +NK_API void +nk_window_collapse(struct nk_context *ctx, const char *name, + enum nk_collapse_states c) +{ + int title_len; + nk_hash title_hash; + struct nk_window *win; + NK_ASSERT(ctx); + if (!ctx) return; + + title_len = (int)nk_strlen(name); + title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE); + win = nk_find_window(ctx, title_hash, name); + if (!win) return; + if (c == NK_MINIMIZED) + win->flags |= NK_WINDOW_MINIMIZED; + else win->flags &= ~(nk_flags)NK_WINDOW_MINIMIZED; +} +NK_API void +nk_window_collapse_if(struct nk_context *ctx, const char *name, + enum nk_collapse_states c, int cond) +{ + NK_ASSERT(ctx); + if (!ctx || !cond) return; + nk_window_collapse(ctx, name, c); +} +NK_API void +nk_window_show(struct nk_context *ctx, const char *name, enum nk_show_states s) +{ + int title_len; + nk_hash title_hash; + struct nk_window *win; + NK_ASSERT(ctx); + if (!ctx) return; + + title_len = (int)nk_strlen(name); + title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE); + win = nk_find_window(ctx, title_hash, name); + if (!win) return; + if (s == NK_HIDDEN) { + win->flags |= NK_WINDOW_HIDDEN; + } else win->flags &= ~(nk_flags)NK_WINDOW_HIDDEN; +} +NK_API void +nk_window_show_if(struct nk_context *ctx, const char *name, + enum nk_show_states s, int cond) +{ + NK_ASSERT(ctx); + if (!ctx || !cond) return; + nk_window_show(ctx, name, s); +} + +NK_API void +nk_window_set_focus(struct nk_context *ctx, const char *name) +{ + int title_len; + nk_hash title_hash; + struct nk_window *win; + NK_ASSERT(ctx); + if (!ctx) return; + + title_len = (int)nk_strlen(name); + title_hash = nk_murmur_hash(name, (int)title_len, NK_WINDOW_TITLE); + win = nk_find_window(ctx, title_hash, name); + if (win && ctx->end != win) { + nk_remove_window(ctx, win); + nk_insert_window(ctx, win, NK_INSERT_BACK); + } + ctx->active = win; +} + + + + +/* =============================================================== + * + * POPUP + * + * ===============================================================*/ +NK_API nk_bool +nk_popup_begin(struct nk_context *ctx, enum nk_popup_type type, + const char *title, nk_flags flags, struct nk_rect rect) +{ + struct nk_window *popup; + struct nk_window *win; + struct nk_panel *panel; + + int title_len; + nk_hash title_hash; + nk_size allocated; + + NK_ASSERT(ctx); + NK_ASSERT(title); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + panel = win->layout; + NK_ASSERT(!(panel->type & NK_PANEL_SET_POPUP) && "popups are not allowed to have popups"); + (void)panel; + title_len = (int)nk_strlen(title); + title_hash = nk_murmur_hash(title, (int)title_len, NK_PANEL_POPUP); + + popup = win->popup.win; + if (!popup) { + popup = (struct nk_window*)nk_create_window(ctx); + popup->parent = win; + win->popup.win = popup; + win->popup.active = 0; + win->popup.type = NK_PANEL_POPUP; + } + + /* make sure we have correct popup */ + if (win->popup.name != title_hash) { + if (!win->popup.active) { + nk_zero(popup, sizeof(*popup)); + win->popup.name = title_hash; + win->popup.active = 1; + win->popup.type = NK_PANEL_POPUP; + } else return 0; + } + + /* popup position is local to window */ + ctx->current = popup; + rect.x += win->layout->clip.x; + rect.y += win->layout->clip.y; + + /* setup popup data */ + popup->parent = win; + popup->bounds = rect; + popup->seq = ctx->seq; + popup->layout = (struct nk_panel*)nk_create_panel(ctx); + popup->flags = flags; + popup->flags |= NK_WINDOW_BORDER; + if (type == NK_POPUP_DYNAMIC) + popup->flags |= NK_WINDOW_DYNAMIC; + + popup->buffer = win->buffer; + nk_start_popup(ctx, win); + allocated = ctx->memory.allocated; + nk_push_scissor(&popup->buffer, nk_null_rect); + + if (nk_panel_begin(ctx, title, NK_PANEL_POPUP)) { + /* popup is running therefore invalidate parent panels */ + struct nk_panel *root; + root = win->layout; + while (root) { + root->flags |= NK_WINDOW_ROM; + root->flags &= ~(nk_flags)NK_WINDOW_REMOVE_ROM; + root = root->parent; + } + win->popup.active = 1; + popup->layout->offset_x = &popup->scrollbar.x; + popup->layout->offset_y = &popup->scrollbar.y; + popup->layout->parent = win->layout; + return 1; + } else { + /* popup was closed/is invalid so cleanup */ + struct nk_panel *root; + root = win->layout; + while (root) { + root->flags |= NK_WINDOW_REMOVE_ROM; + root = root->parent; + } + win->popup.buf.active = 0; + win->popup.active = 0; + ctx->memory.allocated = allocated; + ctx->current = win; + nk_free_panel(ctx, popup->layout); + popup->layout = 0; + return 0; + } +} +NK_LIB nk_bool +nk_nonblock_begin(struct nk_context *ctx, + nk_flags flags, struct nk_rect body, struct nk_rect header, + enum nk_panel_type panel_type) +{ + struct nk_window *popup; + struct nk_window *win; + struct nk_panel *panel; + int is_active = nk_true; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + /* popups cannot have popups */ + win = ctx->current; + panel = win->layout; + NK_ASSERT(!(panel->type & NK_PANEL_SET_POPUP)); + (void)panel; + popup = win->popup.win; + if (!popup) { + /* create window for nonblocking popup */ + popup = (struct nk_window*)nk_create_window(ctx); + popup->parent = win; + win->popup.win = popup; + win->popup.type = panel_type; + nk_command_buffer_init(&popup->buffer, &ctx->memory, NK_CLIPPING_ON); + } else { + /* close the popup if user pressed outside or in the header */ + int pressed, in_body, in_header; +#ifdef NK_BUTTON_TRIGGER_ON_RELEASE + pressed = nk_input_is_mouse_released(&ctx->input, NK_BUTTON_LEFT); +#else + pressed = nk_input_is_mouse_pressed(&ctx->input, NK_BUTTON_LEFT); +#endif + in_body = nk_input_is_mouse_hovering_rect(&ctx->input, body); + in_header = nk_input_is_mouse_hovering_rect(&ctx->input, header); + if (pressed && (!in_body || in_header)) + is_active = nk_false; + } + win->popup.header = header; + + if (!is_active) { + /* remove read only mode from all parent panels */ + struct nk_panel *root = win->layout; + while (root) { + root->flags |= NK_WINDOW_REMOVE_ROM; + root = root->parent; + } + return is_active; + } + popup->bounds = body; + popup->parent = win; + popup->layout = (struct nk_panel*)nk_create_panel(ctx); + popup->flags = flags; + popup->flags |= NK_WINDOW_BORDER; + popup->flags |= NK_WINDOW_DYNAMIC; + popup->seq = ctx->seq; + win->popup.active = 1; + NK_ASSERT(popup->layout); + + nk_start_popup(ctx, win); + popup->buffer = win->buffer; + nk_push_scissor(&popup->buffer, nk_null_rect); + ctx->current = popup; + + nk_panel_begin(ctx, 0, panel_type); + win->buffer = popup->buffer; + popup->layout->parent = win->layout; + popup->layout->offset_x = &popup->scrollbar.x; + popup->layout->offset_y = &popup->scrollbar.y; + + /* set read only mode to all parent panels */ + {struct nk_panel *root; + root = win->layout; + while (root) { + root->flags |= NK_WINDOW_ROM; + root = root->parent; + }} + return is_active; +} +NK_API void +nk_popup_close(struct nk_context *ctx) +{ + struct nk_window *popup; + NK_ASSERT(ctx); + if (!ctx || !ctx->current) return; + + popup = ctx->current; + NK_ASSERT(popup->parent); + NK_ASSERT(popup->layout->type & NK_PANEL_SET_POPUP); + popup->flags |= NK_WINDOW_HIDDEN; +} +NK_API void +nk_popup_end(struct nk_context *ctx) +{ + struct nk_window *win; + struct nk_window *popup; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + popup = ctx->current; + if (!popup->parent) return; + win = popup->parent; + if (popup->flags & NK_WINDOW_HIDDEN) { + struct nk_panel *root; + root = win->layout; + while (root) { + root->flags |= NK_WINDOW_REMOVE_ROM; + root = root->parent; + } + win->popup.active = 0; + } + nk_push_scissor(&popup->buffer, nk_null_rect); + nk_end(ctx); + + win->buffer = popup->buffer; + nk_finish_popup(ctx, win); + ctx->current = win; + nk_push_scissor(&win->buffer, win->layout->clip); +} +NK_API void +nk_popup_get_scroll(struct nk_context *ctx, nk_uint *offset_x, nk_uint *offset_y) +{ + struct nk_window *popup; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + popup = ctx->current; + if (offset_x) + *offset_x = popup->scrollbar.x; + if (offset_y) + *offset_y = popup->scrollbar.y; +} +NK_API void +nk_popup_set_scroll(struct nk_context *ctx, nk_uint offset_x, nk_uint offset_y) +{ + struct nk_window *popup; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + popup = ctx->current; + popup->scrollbar.x = offset_x; + popup->scrollbar.y = offset_y; +} + + + + +/* ============================================================== + * + * CONTEXTUAL + * + * ===============================================================*/ +NK_API nk_bool +nk_contextual_begin(struct nk_context *ctx, nk_flags flags, struct nk_vec2 size, + struct nk_rect trigger_bounds) +{ + struct nk_window *win; + struct nk_window *popup; + struct nk_rect body; + + NK_STORAGE const struct nk_rect null_rect = {-1,-1,0,0}; + int is_clicked = 0; + int is_open = 0; + int ret = 0; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + ++win->popup.con_count; + if (ctx->current != ctx->active) + return 0; + + /* check if currently active contextual is active */ + popup = win->popup.win; + is_open = (popup && win->popup.type == NK_PANEL_CONTEXTUAL); + is_clicked = nk_input_mouse_clicked(&ctx->input, NK_BUTTON_RIGHT, trigger_bounds); + if (win->popup.active_con && win->popup.con_count != win->popup.active_con) + return 0; + if (!is_open && win->popup.active_con) + win->popup.active_con = 0; + if ((!is_open && !is_clicked)) + return 0; + + /* calculate contextual position on click */ + win->popup.active_con = win->popup.con_count; + if (is_clicked) { + body.x = ctx->input.mouse.pos.x; + body.y = ctx->input.mouse.pos.y; + } else { + body.x = popup->bounds.x; + body.y = popup->bounds.y; + } + body.w = size.x; + body.h = size.y; + + /* start nonblocking contextual popup */ + ret = nk_nonblock_begin(ctx, flags|NK_WINDOW_NO_SCROLLBAR, body, + null_rect, NK_PANEL_CONTEXTUAL); + if (ret) win->popup.type = NK_PANEL_CONTEXTUAL; + else { + win->popup.active_con = 0; + win->popup.type = NK_PANEL_NONE; + if (win->popup.win) + win->popup.win->flags = 0; + } + return ret; +} +NK_API nk_bool +nk_contextual_item_text(struct nk_context *ctx, const char *text, int len, + nk_flags alignment) +{ + struct nk_window *win; + const struct nk_input *in; + const struct nk_style *style; + + struct nk_rect bounds; + enum nk_widget_layout_states state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + style = &ctx->style; + state = nk_widget_fitting(&bounds, ctx, style->contextual_button.padding); + if (!state) return nk_false; + + in = (state == NK_WIDGET_ROM || win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + if (nk_do_button_text(&ctx->last_widget_state, &win->buffer, bounds, + text, len, alignment, NK_BUTTON_DEFAULT, &style->contextual_button, in, style->font)) { + nk_contextual_close(ctx); + return nk_true; + } + return nk_false; +} +NK_API nk_bool +nk_contextual_item_label(struct nk_context *ctx, const char *label, nk_flags align) +{ + return nk_contextual_item_text(ctx, label, nk_strlen(label), align); +} +NK_API nk_bool +nk_contextual_item_image_text(struct nk_context *ctx, struct nk_image img, + const char *text, int len, nk_flags align) +{ + struct nk_window *win; + const struct nk_input *in; + const struct nk_style *style; + + struct nk_rect bounds; + enum nk_widget_layout_states state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + style = &ctx->style; + state = nk_widget_fitting(&bounds, ctx, style->contextual_button.padding); + if (!state) return nk_false; + + in = (state == NK_WIDGET_ROM || win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + if (nk_do_button_text_image(&ctx->last_widget_state, &win->buffer, bounds, + img, text, len, align, NK_BUTTON_DEFAULT, &style->contextual_button, style->font, in)){ + nk_contextual_close(ctx); + return nk_true; + } + return nk_false; +} +NK_API nk_bool +nk_contextual_item_image_label(struct nk_context *ctx, struct nk_image img, + const char *label, nk_flags align) +{ + return nk_contextual_item_image_text(ctx, img, label, nk_strlen(label), align); +} +NK_API nk_bool +nk_contextual_item_symbol_text(struct nk_context *ctx, enum nk_symbol_type symbol, + const char *text, int len, nk_flags align) +{ + struct nk_window *win; + const struct nk_input *in; + const struct nk_style *style; + + struct nk_rect bounds; + enum nk_widget_layout_states state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + style = &ctx->style; + state = nk_widget_fitting(&bounds, ctx, style->contextual_button.padding); + if (!state) return nk_false; + + in = (state == NK_WIDGET_ROM || win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + if (nk_do_button_text_symbol(&ctx->last_widget_state, &win->buffer, bounds, + symbol, text, len, align, NK_BUTTON_DEFAULT, &style->contextual_button, style->font, in)) { + nk_contextual_close(ctx); + return nk_true; + } + return nk_false; +} +NK_API nk_bool +nk_contextual_item_symbol_label(struct nk_context *ctx, enum nk_symbol_type symbol, + const char *text, nk_flags align) +{ + return nk_contextual_item_symbol_text(ctx, symbol, text, nk_strlen(text), align); +} +NK_API void +nk_contextual_close(struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) return; + nk_popup_close(ctx); +} +NK_API void +nk_contextual_end(struct nk_context *ctx) +{ + struct nk_window *popup; + struct nk_panel *panel; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) return; + + popup = ctx->current; + panel = popup->layout; + NK_ASSERT(popup->parent); + NK_ASSERT(panel->type & NK_PANEL_SET_POPUP); + if (panel->flags & NK_WINDOW_DYNAMIC) { + /* Close behavior + This is a bit of a hack solution since we do not know before we end our popup + how big it will be. We therefore do not directly know when a + click outside the non-blocking popup must close it at that direct frame. + Instead it will be closed in the next frame.*/ + struct nk_rect body = {0,0,0,0}; + if (panel->at_y < (panel->bounds.y + panel->bounds.h)) { + struct nk_vec2 padding = nk_panel_get_padding(&ctx->style, panel->type); + body = panel->bounds; + body.y = (panel->at_y + panel->footer_height + panel->border + padding.y + panel->row.height); + body.h = (panel->bounds.y + panel->bounds.h) - body.y; + } + {int pressed = nk_input_is_mouse_pressed(&ctx->input, NK_BUTTON_LEFT); + int in_body = nk_input_is_mouse_hovering_rect(&ctx->input, body); + if (pressed && in_body) + popup->flags |= NK_WINDOW_HIDDEN; + } + } + if (popup->flags & NK_WINDOW_HIDDEN) + popup->seq = 0; + nk_popup_end(ctx); + return; +} + + + + + +/* =============================================================== + * + * MENU + * + * ===============================================================*/ +NK_API void +nk_menubar_begin(struct nk_context *ctx) +{ + struct nk_panel *layout; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + layout = ctx->current->layout; + NK_ASSERT(layout->at_y == layout->bounds.y); + /* if this assert triggers you allocated space between nk_begin and nk_menubar_begin. + If you want a menubar the first nuklear function after `nk_begin` has to be a + `nk_menubar_begin` call. Inside the menubar you then have to allocate space for + widgets (also supports multiple rows). + Example: + if (nk_begin(...)) { + nk_menubar_begin(...); + nk_layout_xxxx(...); + nk_button(...); + nk_layout_xxxx(...); + nk_button(...); + nk_menubar_end(...); + } + nk_end(...); + */ + if (layout->flags & NK_WINDOW_HIDDEN || layout->flags & NK_WINDOW_MINIMIZED) + return; + + layout->menu.x = layout->at_x; + layout->menu.y = layout->at_y + layout->row.height; + layout->menu.w = layout->bounds.w; + layout->menu.offset.x = *layout->offset_x; + layout->menu.offset.y = *layout->offset_y; + *layout->offset_y = 0; +} +NK_API void +nk_menubar_end(struct nk_context *ctx) +{ + struct nk_window *win; + struct nk_panel *layout; + struct nk_command_buffer *out; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + out = &win->buffer; + layout = win->layout; + if (layout->flags & NK_WINDOW_HIDDEN || layout->flags & NK_WINDOW_MINIMIZED) + return; + + layout->menu.h = layout->at_y - layout->menu.y; + layout->menu.h += layout->row.height + ctx->style.window.spacing.y; + + layout->bounds.y += layout->menu.h; + layout->bounds.h -= layout->menu.h; + + *layout->offset_x = layout->menu.offset.x; + *layout->offset_y = layout->menu.offset.y; + layout->at_y = layout->bounds.y - layout->row.height; + + layout->clip.y = layout->bounds.y; + layout->clip.h = layout->bounds.h; + nk_push_scissor(out, layout->clip); +} +NK_INTERN int +nk_menu_begin(struct nk_context *ctx, struct nk_window *win, + const char *id, int is_clicked, struct nk_rect header, struct nk_vec2 size) +{ + int is_open = 0; + int is_active = 0; + struct nk_rect body; + struct nk_window *popup; + nk_hash hash = nk_murmur_hash(id, (int)nk_strlen(id), NK_PANEL_MENU); + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + body.x = header.x; + body.w = size.x; + body.y = header.y + header.h; + body.h = size.y; + + popup = win->popup.win; + is_open = popup ? nk_true : nk_false; + is_active = (popup && (win->popup.name == hash) && win->popup.type == NK_PANEL_MENU); + if ((is_clicked && is_open && !is_active) || (is_open && !is_active) || + (!is_open && !is_active && !is_clicked)) return 0; + if (!nk_nonblock_begin(ctx, NK_WINDOW_NO_SCROLLBAR, body, header, NK_PANEL_MENU)) + return 0; + + win->popup.type = NK_PANEL_MENU; + win->popup.name = hash; + return 1; +} +NK_API nk_bool +nk_menu_begin_text(struct nk_context *ctx, const char *title, int len, + nk_flags align, struct nk_vec2 size) +{ + struct nk_window *win; + const struct nk_input *in; + struct nk_rect header; + int is_clicked = nk_false; + nk_flags state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + state = nk_widget(&header, ctx); + if (!state) return 0; + in = (state == NK_WIDGET_ROM || win->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + if (nk_do_button_text(&ctx->last_widget_state, &win->buffer, header, + title, len, align, NK_BUTTON_DEFAULT, &ctx->style.menu_button, in, ctx->style.font)) + is_clicked = nk_true; + return nk_menu_begin(ctx, win, title, is_clicked, header, size); +} +NK_API nk_bool nk_menu_begin_label(struct nk_context *ctx, + const char *text, nk_flags align, struct nk_vec2 size) +{ + return nk_menu_begin_text(ctx, text, nk_strlen(text), align, size); +} +NK_API nk_bool +nk_menu_begin_image(struct nk_context *ctx, const char *id, struct nk_image img, + struct nk_vec2 size) +{ + struct nk_window *win; + struct nk_rect header; + const struct nk_input *in; + int is_clicked = nk_false; + nk_flags state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + state = nk_widget(&header, ctx); + if (!state) return 0; + in = (state == NK_WIDGET_ROM || win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + if (nk_do_button_image(&ctx->last_widget_state, &win->buffer, header, + img, NK_BUTTON_DEFAULT, &ctx->style.menu_button, in)) + is_clicked = nk_true; + return nk_menu_begin(ctx, win, id, is_clicked, header, size); +} +NK_API nk_bool +nk_menu_begin_symbol(struct nk_context *ctx, const char *id, + enum nk_symbol_type sym, struct nk_vec2 size) +{ + struct nk_window *win; + const struct nk_input *in; + struct nk_rect header; + int is_clicked = nk_false; + nk_flags state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + state = nk_widget(&header, ctx); + if (!state) return 0; + in = (state == NK_WIDGET_ROM || win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + if (nk_do_button_symbol(&ctx->last_widget_state, &win->buffer, header, + sym, NK_BUTTON_DEFAULT, &ctx->style.menu_button, in, ctx->style.font)) + is_clicked = nk_true; + return nk_menu_begin(ctx, win, id, is_clicked, header, size); +} +NK_API nk_bool +nk_menu_begin_image_text(struct nk_context *ctx, const char *title, int len, + nk_flags align, struct nk_image img, struct nk_vec2 size) +{ + struct nk_window *win; + struct nk_rect header; + const struct nk_input *in; + int is_clicked = nk_false; + nk_flags state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + state = nk_widget(&header, ctx); + if (!state) return 0; + in = (state == NK_WIDGET_ROM || win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + if (nk_do_button_text_image(&ctx->last_widget_state, &win->buffer, + header, img, title, len, align, NK_BUTTON_DEFAULT, &ctx->style.menu_button, + ctx->style.font, in)) + is_clicked = nk_true; + return nk_menu_begin(ctx, win, title, is_clicked, header, size); +} +NK_API nk_bool +nk_menu_begin_image_label(struct nk_context *ctx, + const char *title, nk_flags align, struct nk_image img, struct nk_vec2 size) +{ + return nk_menu_begin_image_text(ctx, title, nk_strlen(title), align, img, size); +} +NK_API nk_bool +nk_menu_begin_symbol_text(struct nk_context *ctx, const char *title, int len, + nk_flags align, enum nk_symbol_type sym, struct nk_vec2 size) +{ + struct nk_window *win; + struct nk_rect header; + const struct nk_input *in; + int is_clicked = nk_false; + nk_flags state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + state = nk_widget(&header, ctx); + if (!state) return 0; + + in = (state == NK_WIDGET_ROM || win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + if (nk_do_button_text_symbol(&ctx->last_widget_state, &win->buffer, + header, sym, title, len, align, NK_BUTTON_DEFAULT, &ctx->style.menu_button, + ctx->style.font, in)) is_clicked = nk_true; + return nk_menu_begin(ctx, win, title, is_clicked, header, size); +} +NK_API nk_bool +nk_menu_begin_symbol_label(struct nk_context *ctx, + const char *title, nk_flags align, enum nk_symbol_type sym, struct nk_vec2 size ) +{ + return nk_menu_begin_symbol_text(ctx, title, nk_strlen(title), align,sym,size); +} +NK_API nk_bool +nk_menu_item_text(struct nk_context *ctx, const char *title, int len, nk_flags align) +{ + return nk_contextual_item_text(ctx, title, len, align); +} +NK_API nk_bool +nk_menu_item_label(struct nk_context *ctx, const char *label, nk_flags align) +{ + return nk_contextual_item_label(ctx, label, align); +} +NK_API nk_bool +nk_menu_item_image_label(struct nk_context *ctx, struct nk_image img, + const char *label, nk_flags align) +{ + return nk_contextual_item_image_label(ctx, img, label, align); +} +NK_API nk_bool +nk_menu_item_image_text(struct nk_context *ctx, struct nk_image img, + const char *text, int len, nk_flags align) +{ + return nk_contextual_item_image_text(ctx, img, text, len, align); +} +NK_API nk_bool nk_menu_item_symbol_text(struct nk_context *ctx, enum nk_symbol_type sym, + const char *text, int len, nk_flags align) +{ + return nk_contextual_item_symbol_text(ctx, sym, text, len, align); +} +NK_API nk_bool nk_menu_item_symbol_label(struct nk_context *ctx, enum nk_symbol_type sym, + const char *label, nk_flags align) +{ + return nk_contextual_item_symbol_label(ctx, sym, label, align); +} +NK_API void nk_menu_close(struct nk_context *ctx) +{ + nk_contextual_close(ctx); +} +NK_API void +nk_menu_end(struct nk_context *ctx) +{ + nk_contextual_end(ctx); +} + + + + + +/* =============================================================== + * + * LAYOUT + * + * ===============================================================*/ +NK_API void +nk_layout_set_min_row_height(struct nk_context *ctx, float height) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + layout->row.min_height = height; +} +NK_API void +nk_layout_reset_min_row_height(struct nk_context *ctx) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + layout->row.min_height = ctx->style.font->height; + layout->row.min_height += ctx->style.text.padding.y*2; + layout->row.min_height += ctx->style.window.min_row_height_padding*2; +} +NK_LIB float +nk_layout_row_calculate_usable_space(const struct nk_style *style, enum nk_panel_type type, + float total_space, int columns) +{ + float panel_spacing; + float panel_space; + + struct nk_vec2 spacing; + + spacing = style->window.spacing; + + /* calculate the usable panel space */ + panel_spacing = (float)NK_MAX(columns - 1, 0) * spacing.x; + panel_space = total_space - panel_spacing; + return panel_space; +} +NK_LIB void +nk_panel_layout(const struct nk_context *ctx, struct nk_window *win, + float height, int cols) +{ + struct nk_panel *layout; + const struct nk_style *style; + struct nk_command_buffer *out; + + struct nk_vec2 item_spacing; + struct nk_color color; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + /* prefetch some configuration data */ + layout = win->layout; + style = &ctx->style; + out = &win->buffer; + color = style->window.background; + item_spacing = style->window.spacing; + + /* if one of these triggers you forgot to add an `if` condition around either + a window, group, popup, combobox or contextual menu `begin` and `end` block. + Example: + if (nk_begin(...) {...} nk_end(...); or + if (nk_group_begin(...) { nk_group_end(...);} */ + NK_ASSERT(!(layout->flags & NK_WINDOW_MINIMIZED)); + NK_ASSERT(!(layout->flags & NK_WINDOW_HIDDEN)); + NK_ASSERT(!(layout->flags & NK_WINDOW_CLOSED)); + + /* update the current row and set the current row layout */ + layout->row.index = 0; + layout->at_y += layout->row.height; + layout->row.columns = cols; + if (height == 0.0f) + layout->row.height = NK_MAX(height, layout->row.min_height) + item_spacing.y; + else layout->row.height = height + item_spacing.y; + + layout->row.item_offset = 0; + if (layout->flags & NK_WINDOW_DYNAMIC) { + /* draw background for dynamic panels */ + struct nk_rect background; + background.x = win->bounds.x; + background.w = win->bounds.w; + background.y = layout->at_y - 1.0f; + background.h = layout->row.height + 1.0f; + nk_fill_rect(out, background, 0, color); + } +} +NK_LIB void +nk_row_layout(struct nk_context *ctx, enum nk_layout_format fmt, + float height, int cols, int width) +{ + /* update the current row and set the current row layout */ + struct nk_window *win; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + nk_panel_layout(ctx, win, height, cols); + if (fmt == NK_DYNAMIC) + win->layout->row.type = NK_LAYOUT_DYNAMIC_FIXED; + else win->layout->row.type = NK_LAYOUT_STATIC_FIXED; + + win->layout->row.ratio = 0; + win->layout->row.filled = 0; + win->layout->row.item_offset = 0; + win->layout->row.item_width = (float)width; +} +NK_API float +nk_layout_ratio_from_pixel(struct nk_context *ctx, float pixel_width) +{ + struct nk_window *win; + NK_ASSERT(ctx); + NK_ASSERT(pixel_width); + if (!ctx || !ctx->current || !ctx->current->layout) return 0; + win = ctx->current; + return NK_CLAMP(0.0f, pixel_width/win->bounds.x, 1.0f); +} +NK_API void +nk_layout_row_dynamic(struct nk_context *ctx, float height, int cols) +{ + nk_row_layout(ctx, NK_DYNAMIC, height, cols, 0); +} +NK_API void +nk_layout_row_static(struct nk_context *ctx, float height, int item_width, int cols) +{ + nk_row_layout(ctx, NK_STATIC, height, cols, item_width); +} +NK_API void +nk_layout_row_begin(struct nk_context *ctx, enum nk_layout_format fmt, + float row_height, int cols) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + nk_panel_layout(ctx, win, row_height, cols); + if (fmt == NK_DYNAMIC) + layout->row.type = NK_LAYOUT_DYNAMIC_ROW; + else layout->row.type = NK_LAYOUT_STATIC_ROW; + + layout->row.ratio = 0; + layout->row.filled = 0; + layout->row.item_width = 0; + layout->row.item_offset = 0; + layout->row.columns = cols; +} +NK_API void +nk_layout_row_push(struct nk_context *ctx, float ratio_or_width) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + NK_ASSERT(layout->row.type == NK_LAYOUT_STATIC_ROW || layout->row.type == NK_LAYOUT_DYNAMIC_ROW); + if (layout->row.type != NK_LAYOUT_STATIC_ROW && layout->row.type != NK_LAYOUT_DYNAMIC_ROW) + return; + + if (layout->row.type == NK_LAYOUT_DYNAMIC_ROW) { + float ratio = ratio_or_width; + if ((ratio + layout->row.filled) > 1.0f) return; + if (ratio > 0.0f) + layout->row.item_width = NK_SATURATE(ratio); + else layout->row.item_width = 1.0f - layout->row.filled; + } else layout->row.item_width = ratio_or_width; +} +NK_API void +nk_layout_row_end(struct nk_context *ctx) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + NK_ASSERT(layout->row.type == NK_LAYOUT_STATIC_ROW || layout->row.type == NK_LAYOUT_DYNAMIC_ROW); + if (layout->row.type != NK_LAYOUT_STATIC_ROW && layout->row.type != NK_LAYOUT_DYNAMIC_ROW) + return; + layout->row.item_width = 0; + layout->row.item_offset = 0; +} +NK_API void +nk_layout_row(struct nk_context *ctx, enum nk_layout_format fmt, + float height, int cols, const float *ratio) +{ + int i; + int n_undef = 0; + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + nk_panel_layout(ctx, win, height, cols); + if (fmt == NK_DYNAMIC) { + /* calculate width of undefined widget ratios */ + float r = 0; + layout->row.ratio = ratio; + for (i = 0; i < cols; ++i) { + if (ratio[i] < 0.0f) + n_undef++; + else r += ratio[i]; + } + r = NK_SATURATE(1.0f - r); + layout->row.type = NK_LAYOUT_DYNAMIC; + layout->row.item_width = (r > 0 && n_undef > 0) ? (r / (float)n_undef):0; + } else { + layout->row.ratio = ratio; + layout->row.type = NK_LAYOUT_STATIC; + layout->row.item_width = 0; + layout->row.item_offset = 0; + } + layout->row.item_offset = 0; + layout->row.filled = 0; +} +NK_API void +nk_layout_row_template_begin(struct nk_context *ctx, float height) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + nk_panel_layout(ctx, win, height, 1); + layout->row.type = NK_LAYOUT_TEMPLATE; + layout->row.columns = 0; + layout->row.ratio = 0; + layout->row.item_width = 0; + layout->row.item_height = 0; + layout->row.item_offset = 0; + layout->row.filled = 0; + layout->row.item.x = 0; + layout->row.item.y = 0; + layout->row.item.w = 0; + layout->row.item.h = 0; +} +NK_API void +nk_layout_row_template_push_dynamic(struct nk_context *ctx) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + NK_ASSERT(layout->row.type == NK_LAYOUT_TEMPLATE); + NK_ASSERT(layout->row.columns < NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS); + if (layout->row.type != NK_LAYOUT_TEMPLATE) return; + if (layout->row.columns >= NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS) return; + layout->row.templates[layout->row.columns++] = -1.0f; +} +NK_API void +nk_layout_row_template_push_variable(struct nk_context *ctx, float min_width) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + NK_ASSERT(layout->row.type == NK_LAYOUT_TEMPLATE); + NK_ASSERT(layout->row.columns < NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS); + if (layout->row.type != NK_LAYOUT_TEMPLATE) return; + if (layout->row.columns >= NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS) return; + layout->row.templates[layout->row.columns++] = -min_width; +} +NK_API void +nk_layout_row_template_push_static(struct nk_context *ctx, float width) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + NK_ASSERT(layout->row.type == NK_LAYOUT_TEMPLATE); + NK_ASSERT(layout->row.columns < NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS); + if (layout->row.type != NK_LAYOUT_TEMPLATE) return; + if (layout->row.columns >= NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS) return; + layout->row.templates[layout->row.columns++] = width; +} +NK_API void +nk_layout_row_template_end(struct nk_context *ctx) +{ + struct nk_window *win; + struct nk_panel *layout; + + int i = 0; + int variable_count = 0; + int min_variable_count = 0; + float min_fixed_width = 0.0f; + float total_fixed_width = 0.0f; + float max_variable_width = 0.0f; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + NK_ASSERT(layout->row.type == NK_LAYOUT_TEMPLATE); + if (layout->row.type != NK_LAYOUT_TEMPLATE) return; + for (i = 0; i < layout->row.columns; ++i) { + float width = layout->row.templates[i]; + if (width >= 0.0f) { + total_fixed_width += width; + min_fixed_width += width; + } else if (width < -1.0f) { + width = -width; + total_fixed_width += width; + max_variable_width = NK_MAX(max_variable_width, width); + variable_count++; + } else { + min_variable_count++; + variable_count++; + } + } + if (variable_count) { + float space = nk_layout_row_calculate_usable_space(&ctx->style, layout->type, + layout->bounds.w, layout->row.columns); + float var_width = (NK_MAX(space-min_fixed_width,0.0f)) / (float)variable_count; + int enough_space = var_width >= max_variable_width; + if (!enough_space) + var_width = (NK_MAX(space-total_fixed_width,0)) / (float)min_variable_count; + for (i = 0; i < layout->row.columns; ++i) { + float *width = &layout->row.templates[i]; + *width = (*width >= 0.0f)? *width: (*width < -1.0f && !enough_space)? -(*width): var_width; + } + } +} +NK_API void +nk_layout_space_begin(struct nk_context *ctx, enum nk_layout_format fmt, + float height, int widget_count) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + nk_panel_layout(ctx, win, height, widget_count); + if (fmt == NK_STATIC) + layout->row.type = NK_LAYOUT_STATIC_FREE; + else layout->row.type = NK_LAYOUT_DYNAMIC_FREE; + + layout->row.ratio = 0; + layout->row.filled = 0; + layout->row.item_width = 0; + layout->row.item_offset = 0; +} +NK_API void +nk_layout_space_end(struct nk_context *ctx) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + layout->row.item_width = 0; + layout->row.item_height = 0; + layout->row.item_offset = 0; + nk_zero(&layout->row.item, sizeof(layout->row.item)); +} +NK_API void +nk_layout_space_push(struct nk_context *ctx, struct nk_rect rect) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + layout->row.item = rect; +} +NK_API struct nk_rect +nk_layout_space_bounds(struct nk_context *ctx) +{ + struct nk_rect ret; + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + win = ctx->current; + layout = win->layout; + + ret.x = layout->clip.x; + ret.y = layout->clip.y; + ret.w = layout->clip.w; + ret.h = layout->row.height; + return ret; +} +NK_API struct nk_rect +nk_layout_widget_bounds(struct nk_context *ctx) +{ + struct nk_rect ret; + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + win = ctx->current; + layout = win->layout; + + ret.x = layout->at_x; + ret.y = layout->at_y; + ret.w = layout->bounds.w - NK_MAX(layout->at_x - layout->bounds.x,0); + ret.h = layout->row.height; + return ret; +} +NK_API struct nk_vec2 +nk_layout_space_to_screen(struct nk_context *ctx, struct nk_vec2 ret) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + win = ctx->current; + layout = win->layout; + + ret.x += layout->at_x - (float)*layout->offset_x; + ret.y += layout->at_y - (float)*layout->offset_y; + return ret; +} +NK_API struct nk_vec2 +nk_layout_space_to_local(struct nk_context *ctx, struct nk_vec2 ret) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + win = ctx->current; + layout = win->layout; + + ret.x += -layout->at_x + (float)*layout->offset_x; + ret.y += -layout->at_y + (float)*layout->offset_y; + return ret; +} +NK_API struct nk_rect +nk_layout_space_rect_to_screen(struct nk_context *ctx, struct nk_rect ret) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + win = ctx->current; + layout = win->layout; + + ret.x += layout->at_x - (float)*layout->offset_x; + ret.y += layout->at_y - (float)*layout->offset_y; + return ret; +} +NK_API struct nk_rect +nk_layout_space_rect_to_local(struct nk_context *ctx, struct nk_rect ret) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + win = ctx->current; + layout = win->layout; + + ret.x += -layout->at_x + (float)*layout->offset_x; + ret.y += -layout->at_y + (float)*layout->offset_y; + return ret; +} +NK_LIB void +nk_panel_alloc_row(const struct nk_context *ctx, struct nk_window *win) +{ + struct nk_panel *layout = win->layout; + struct nk_vec2 spacing = ctx->style.window.spacing; + const float row_height = layout->row.height - spacing.y; + nk_panel_layout(ctx, win, row_height, layout->row.columns); +} +NK_LIB void +nk_layout_widget_space(struct nk_rect *bounds, const struct nk_context *ctx, + struct nk_window *win, int modify) +{ + struct nk_panel *layout; + const struct nk_style *style; + + struct nk_vec2 spacing; + + float item_offset = 0; + float item_width = 0; + float item_spacing = 0; + float panel_space = 0; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + style = &ctx->style; + NK_ASSERT(bounds); + + spacing = style->window.spacing; + panel_space = nk_layout_row_calculate_usable_space(&ctx->style, layout->type, + layout->bounds.w, layout->row.columns); + + #define NK_FRAC(x) (x - (int)x) /* will be used to remove fookin gaps */ + /* calculate the width of one item inside the current layout space */ + switch (layout->row.type) { + case NK_LAYOUT_DYNAMIC_FIXED: { + /* scaling fixed size widgets item width */ + float w = NK_MAX(1.0f,panel_space) / (float)layout->row.columns; + item_offset = (float)layout->row.index * w; + item_width = w + NK_FRAC(item_offset); + item_spacing = (float)layout->row.index * spacing.x; + } break; + case NK_LAYOUT_DYNAMIC_ROW: { + /* scaling single ratio widget width */ + float w = layout->row.item_width * panel_space; + item_offset = layout->row.item_offset; + item_width = w + NK_FRAC(item_offset); + item_spacing = 0; + + if (modify) { + layout->row.item_offset += w + spacing.x; + layout->row.filled += layout->row.item_width; + layout->row.index = 0; + } + } break; + case NK_LAYOUT_DYNAMIC_FREE: { + /* panel width depended free widget placing */ + bounds->x = layout->at_x + (layout->bounds.w * layout->row.item.x); + bounds->x -= (float)*layout->offset_x; + bounds->y = layout->at_y + (layout->row.height * layout->row.item.y); + bounds->y -= (float)*layout->offset_y; + bounds->w = layout->bounds.w * layout->row.item.w + NK_FRAC(bounds->x); + bounds->h = layout->row.height * layout->row.item.h + NK_FRAC(bounds->y); + return; + } + case NK_LAYOUT_DYNAMIC: { + /* scaling arrays of panel width ratios for every widget */ + float ratio, w; + NK_ASSERT(layout->row.ratio); + ratio = (layout->row.ratio[layout->row.index] < 0) ? + layout->row.item_width : layout->row.ratio[layout->row.index]; + + w = (ratio * panel_space); + item_spacing = (float)layout->row.index * spacing.x; + item_offset = layout->row.item_offset; + item_width = w + NK_FRAC(item_offset); + + if (modify) { + layout->row.item_offset += w; + layout->row.filled += ratio; + } + } break; + case NK_LAYOUT_STATIC_FIXED: { + /* non-scaling fixed widgets item width */ + item_width = layout->row.item_width; + item_offset = (float)layout->row.index * item_width; + item_spacing = (float)layout->row.index * spacing.x; + } break; + case NK_LAYOUT_STATIC_ROW: { + /* scaling single ratio widget width */ + item_width = layout->row.item_width; + item_offset = layout->row.item_offset; + item_spacing = (float)layout->row.index * spacing.x; + if (modify) layout->row.item_offset += item_width; + } break; + case NK_LAYOUT_STATIC_FREE: { + /* free widget placing */ + bounds->x = layout->at_x + layout->row.item.x; + bounds->w = layout->row.item.w; + if (((bounds->x + bounds->w) > layout->max_x) && modify) + layout->max_x = (bounds->x + bounds->w); + bounds->x -= (float)*layout->offset_x; + bounds->y = layout->at_y + layout->row.item.y; + bounds->y -= (float)*layout->offset_y; + bounds->h = layout->row.item.h; + return; + } + case NK_LAYOUT_STATIC: { + /* non-scaling array of panel pixel width for every widget */ + item_spacing = (float)layout->row.index * spacing.x; + item_width = layout->row.ratio[layout->row.index]; + item_offset = layout->row.item_offset; + if (modify) layout->row.item_offset += item_width; + } break; + case NK_LAYOUT_TEMPLATE: { + /* stretchy row layout with combined dynamic/static widget width*/ + float w; + NK_ASSERT(layout->row.index < layout->row.columns); + NK_ASSERT(layout->row.index < NK_MAX_LAYOUT_ROW_TEMPLATE_COLUMNS); + w = layout->row.templates[layout->row.index]; + item_offset = layout->row.item_offset; + item_width = w + NK_FRAC(item_offset); + item_spacing = (float)layout->row.index * spacing.x; + if (modify) layout->row.item_offset += w; + } break; + #undef NK_FRAC + default: NK_ASSERT(0); break; + }; + + /* set the bounds of the newly allocated widget */ + bounds->w = item_width; + bounds->h = layout->row.height - spacing.y; + bounds->y = layout->at_y - (float)*layout->offset_y; + bounds->x = layout->at_x + item_offset + item_spacing; + if (((bounds->x + bounds->w) > layout->max_x) && modify) + layout->max_x = bounds->x + bounds->w; + bounds->x -= (float)*layout->offset_x; +} +NK_LIB void +nk_panel_alloc_space(struct nk_rect *bounds, const struct nk_context *ctx) +{ + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + /* check if the end of the row has been hit and begin new row if so */ + win = ctx->current; + layout = win->layout; + if (layout->row.index >= layout->row.columns) + nk_panel_alloc_row(ctx, win); + + /* calculate widget position and size */ + nk_layout_widget_space(bounds, ctx, win, nk_true); + layout->row.index++; +} +NK_LIB void +nk_layout_peek(struct nk_rect *bounds, struct nk_context *ctx) +{ + float y; + int index; + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + y = layout->at_y; + index = layout->row.index; + if (layout->row.index >= layout->row.columns) { + layout->at_y += layout->row.height; + layout->row.index = 0; + } + nk_layout_widget_space(bounds, ctx, win, nk_false); + if (!layout->row.index) { + bounds->x -= layout->row.item_offset; + } + layout->at_y = y; + layout->row.index = index; +} + + + + + +/* =============================================================== + * + * TREE + * + * ===============================================================*/ +NK_INTERN int +nk_tree_state_base(struct nk_context *ctx, enum nk_tree_type type, + struct nk_image *img, const char *title, enum nk_collapse_states *state) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_style *style; + struct nk_command_buffer *out; + const struct nk_input *in; + const struct nk_style_button *button; + enum nk_symbol_type symbol; + float row_height; + + struct nk_vec2 item_spacing; + struct nk_rect header = {0,0,0,0}; + struct nk_rect sym = {0,0,0,0}; + struct nk_text text; + + nk_flags ws = 0; + enum nk_widget_layout_states widget_state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + /* cache some data */ + win = ctx->current; + layout = win->layout; + out = &win->buffer; + style = &ctx->style; + item_spacing = style->window.spacing; + + /* calculate header bounds and draw background */ + row_height = style->font->height + 2 * style->tab.padding.y; + nk_layout_set_min_row_height(ctx, row_height); + nk_layout_row_dynamic(ctx, row_height, 1); + nk_layout_reset_min_row_height(ctx); + + widget_state = nk_widget(&header, ctx); + if (type == NK_TREE_TAB) { + const struct nk_style_item *background = &style->tab.background; + if (background->type == NK_STYLE_ITEM_IMAGE) { + nk_draw_image(out, header, &background->data.image, nk_white); + text.background = nk_rgba(0,0,0,0); + } else { + text.background = background->data.color; + nk_fill_rect(out, header, 0, style->tab.border_color); + nk_fill_rect(out, nk_shrink_rect(header, style->tab.border), + style->tab.rounding, background->data.color); + } + } else text.background = style->window.background; + + /* update node state */ + in = (!(layout->flags & NK_WINDOW_ROM)) ? &ctx->input: 0; + in = (in && widget_state == NK_WIDGET_VALID) ? &ctx->input : 0; + if (nk_button_behavior(&ws, header, in, NK_BUTTON_DEFAULT)) + *state = (*state == NK_MAXIMIZED) ? NK_MINIMIZED : NK_MAXIMIZED; + + /* select correct button style */ + if (*state == NK_MAXIMIZED) { + symbol = style->tab.sym_maximize; + if (type == NK_TREE_TAB) + button = &style->tab.tab_maximize_button; + else button = &style->tab.node_maximize_button; + } else { + symbol = style->tab.sym_minimize; + if (type == NK_TREE_TAB) + button = &style->tab.tab_minimize_button; + else button = &style->tab.node_minimize_button; + } + + {/* draw triangle button */ + sym.w = sym.h = style->font->height; + sym.y = header.y + style->tab.padding.y; + sym.x = header.x + style->tab.padding.x; + nk_do_button_symbol(&ws, &win->buffer, sym, symbol, NK_BUTTON_DEFAULT, + button, 0, style->font); + + if (img) { + /* draw optional image icon */ + sym.x = sym.x + sym.w + 4 * item_spacing.x; + nk_draw_image(&win->buffer, sym, img, nk_white); + sym.w = style->font->height + style->tab.spacing.x;} + } + + {/* draw label */ + struct nk_rect label; + header.w = NK_MAX(header.w, sym.w + item_spacing.x); + label.x = sym.x + sym.w + item_spacing.x; + label.y = sym.y; + label.w = header.w - (sym.w + item_spacing.y + style->tab.indent); + label.h = style->font->height; + text.text = style->tab.text; + text.padding = nk_vec2(0,0); + nk_widget_text(out, label, title, nk_strlen(title), &text, + NK_TEXT_LEFT, style->font);} + + /* increase x-axis cursor widget position pointer */ + if (*state == NK_MAXIMIZED) { + layout->at_x = header.x + (float)*layout->offset_x + style->tab.indent; + layout->bounds.w = NK_MAX(layout->bounds.w, style->tab.indent); + layout->bounds.w -= (style->tab.indent + style->window.padding.x); + layout->row.tree_depth++; + return nk_true; + } else return nk_false; +} +NK_INTERN int +nk_tree_base(struct nk_context *ctx, enum nk_tree_type type, + struct nk_image *img, const char *title, enum nk_collapse_states initial_state, + const char *hash, int len, int line) +{ + struct nk_window *win = ctx->current; + int title_len = 0; + nk_hash tree_hash = 0; + nk_uint *state = 0; + + /* retrieve tree state from internal widget state tables */ + if (!hash) { + title_len = (int)nk_strlen(title); + tree_hash = nk_murmur_hash(title, (int)title_len, (nk_hash)line); + } else tree_hash = nk_murmur_hash(hash, len, (nk_hash)line); + state = nk_find_value(win, tree_hash); + if (!state) { + state = nk_add_value(ctx, win, tree_hash, 0); + *state = initial_state; + } + return nk_tree_state_base(ctx, type, img, title, (enum nk_collapse_states*)state); +} +NK_API nk_bool +nk_tree_state_push(struct nk_context *ctx, enum nk_tree_type type, + const char *title, enum nk_collapse_states *state) +{ + return nk_tree_state_base(ctx, type, 0, title, state); +} +NK_API nk_bool +nk_tree_state_image_push(struct nk_context *ctx, enum nk_tree_type type, + struct nk_image img, const char *title, enum nk_collapse_states *state) +{ + return nk_tree_state_base(ctx, type, &img, title, state); +} +NK_API void +nk_tree_state_pop(struct nk_context *ctx) +{ + struct nk_window *win = 0; + struct nk_panel *layout = 0; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + layout->at_x -= ctx->style.tab.indent + (float)*layout->offset_x; + layout->bounds.w += ctx->style.tab.indent + ctx->style.window.padding.x; + NK_ASSERT(layout->row.tree_depth); + layout->row.tree_depth--; +} +NK_API nk_bool +nk_tree_push_hashed(struct nk_context *ctx, enum nk_tree_type type, + const char *title, enum nk_collapse_states initial_state, + const char *hash, int len, int line) +{ + return nk_tree_base(ctx, type, 0, title, initial_state, hash, len, line); +} +NK_API nk_bool +nk_tree_image_push_hashed(struct nk_context *ctx, enum nk_tree_type type, + struct nk_image img, const char *title, enum nk_collapse_states initial_state, + const char *hash, int len,int seed) +{ + return nk_tree_base(ctx, type, &img, title, initial_state, hash, len, seed); +} +NK_API void +nk_tree_pop(struct nk_context *ctx) +{ + nk_tree_state_pop(ctx); +} +NK_INTERN int +nk_tree_element_image_push_hashed_base(struct nk_context *ctx, enum nk_tree_type type, + struct nk_image *img, const char *title, int title_len, + enum nk_collapse_states *state, nk_bool *selected) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_style *style; + struct nk_command_buffer *out; + const struct nk_input *in; + const struct nk_style_button *button; + enum nk_symbol_type symbol; + float row_height; + struct nk_vec2 padding; + + int text_len; + float text_width; + + struct nk_vec2 item_spacing; + struct nk_rect header = {0,0,0,0}; + struct nk_rect sym = {0,0,0,0}; + + nk_flags ws = 0; + enum nk_widget_layout_states widget_state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + /* cache some data */ + win = ctx->current; + layout = win->layout; + out = &win->buffer; + style = &ctx->style; + item_spacing = style->window.spacing; + padding = style->selectable.padding; + + /* calculate header bounds and draw background */ + row_height = style->font->height + 2 * style->tab.padding.y; + nk_layout_set_min_row_height(ctx, row_height); + nk_layout_row_dynamic(ctx, row_height, 1); + nk_layout_reset_min_row_height(ctx); + + widget_state = nk_widget(&header, ctx); + if (type == NK_TREE_TAB) { + const struct nk_style_item *background = &style->tab.background; + if (background->type == NK_STYLE_ITEM_IMAGE) { + nk_draw_image(out, header, &background->data.image, nk_white); + } else { + nk_fill_rect(out, header, 0, style->tab.border_color); + nk_fill_rect(out, nk_shrink_rect(header, style->tab.border), + style->tab.rounding, background->data.color); + } + } + + in = (!(layout->flags & NK_WINDOW_ROM)) ? &ctx->input: 0; + in = (in && widget_state == NK_WIDGET_VALID) ? &ctx->input : 0; + + /* select correct button style */ + if (*state == NK_MAXIMIZED) { + symbol = style->tab.sym_maximize; + if (type == NK_TREE_TAB) + button = &style->tab.tab_maximize_button; + else button = &style->tab.node_maximize_button; + } else { + symbol = style->tab.sym_minimize; + if (type == NK_TREE_TAB) + button = &style->tab.tab_minimize_button; + else button = &style->tab.node_minimize_button; + } + {/* draw triangle button */ + sym.w = sym.h = style->font->height; + sym.y = header.y + style->tab.padding.y; + sym.x = header.x + style->tab.padding.x; + if (nk_do_button_symbol(&ws, &win->buffer, sym, symbol, NK_BUTTON_DEFAULT, button, in, style->font)) + *state = (*state == NK_MAXIMIZED) ? NK_MINIMIZED : NK_MAXIMIZED;} + + /* draw label */ + {nk_flags dummy = 0; + struct nk_rect label; + /* calculate size of the text and tooltip */ + text_len = nk_strlen(title); + text_width = style->font->width(style->font->userdata, style->font->height, title, text_len); + text_width += (4 * padding.x); + + header.w = NK_MAX(header.w, sym.w + item_spacing.x); + label.x = sym.x + sym.w + item_spacing.x; + label.y = sym.y; + label.w = NK_MIN(header.w - (sym.w + item_spacing.y + style->tab.indent), text_width); + label.h = style->font->height; + + if (img) { + nk_do_selectable_image(&dummy, &win->buffer, label, title, title_len, NK_TEXT_LEFT, + selected, img, &style->selectable, in, style->font); + } else nk_do_selectable(&dummy, &win->buffer, label, title, title_len, NK_TEXT_LEFT, + selected, &style->selectable, in, style->font); + } + /* increase x-axis cursor widget position pointer */ + if (*state == NK_MAXIMIZED) { + layout->at_x = header.x + (float)*layout->offset_x + style->tab.indent; + layout->bounds.w = NK_MAX(layout->bounds.w, style->tab.indent); + layout->bounds.w -= (style->tab.indent + style->window.padding.x); + layout->row.tree_depth++; + return nk_true; + } else return nk_false; +} +NK_INTERN int +nk_tree_element_base(struct nk_context *ctx, enum nk_tree_type type, + struct nk_image *img, const char *title, enum nk_collapse_states initial_state, + nk_bool *selected, const char *hash, int len, int line) +{ + struct nk_window *win = ctx->current; + int title_len = 0; + nk_hash tree_hash = 0; + nk_uint *state = 0; + + /* retrieve tree state from internal widget state tables */ + if (!hash) { + title_len = (int)nk_strlen(title); + tree_hash = nk_murmur_hash(title, (int)title_len, (nk_hash)line); + } else tree_hash = nk_murmur_hash(hash, len, (nk_hash)line); + state = nk_find_value(win, tree_hash); + if (!state) { + state = nk_add_value(ctx, win, tree_hash, 0); + *state = initial_state; + } return nk_tree_element_image_push_hashed_base(ctx, type, img, title, + nk_strlen(title), (enum nk_collapse_states*)state, selected); +} +NK_API nk_bool +nk_tree_element_push_hashed(struct nk_context *ctx, enum nk_tree_type type, + const char *title, enum nk_collapse_states initial_state, + nk_bool *selected, const char *hash, int len, int seed) +{ + return nk_tree_element_base(ctx, type, 0, title, initial_state, selected, hash, len, seed); +} +NK_API nk_bool +nk_tree_element_image_push_hashed(struct nk_context *ctx, enum nk_tree_type type, + struct nk_image img, const char *title, enum nk_collapse_states initial_state, + nk_bool *selected, const char *hash, int len,int seed) +{ + return nk_tree_element_base(ctx, type, &img, title, initial_state, selected, hash, len, seed); +} +NK_API void +nk_tree_element_pop(struct nk_context *ctx) +{ + nk_tree_state_pop(ctx); +} + + + + + +/* =============================================================== + * + * GROUP + * + * ===============================================================*/ +NK_API nk_bool +nk_group_scrolled_offset_begin(struct nk_context *ctx, + nk_uint *x_offset, nk_uint *y_offset, const char *title, nk_flags flags) +{ + struct nk_rect bounds; + struct nk_window panel; + struct nk_window *win; + + win = ctx->current; + nk_panel_alloc_space(&bounds, ctx); + {const struct nk_rect *c = &win->layout->clip; + if (!NK_INTERSECT(c->x, c->y, c->w, c->h, bounds.x, bounds.y, bounds.w, bounds.h) && + !(flags & NK_WINDOW_MOVABLE)) { + return 0; + }} + if (win->flags & NK_WINDOW_ROM) + flags |= NK_WINDOW_ROM; + + /* initialize a fake window to create the panel from */ + nk_zero(&panel, sizeof(panel)); + panel.bounds = bounds; + panel.flags = flags; + panel.scrollbar.x = *x_offset; + panel.scrollbar.y = *y_offset; + panel.buffer = win->buffer; + panel.layout = (struct nk_panel*)nk_create_panel(ctx); + ctx->current = &panel; + nk_panel_begin(ctx, (flags & NK_WINDOW_TITLE) ? title: 0, NK_PANEL_GROUP); + + win->buffer = panel.buffer; + win->buffer.clip = panel.layout->clip; + panel.layout->offset_x = x_offset; + panel.layout->offset_y = y_offset; + panel.layout->parent = win->layout; + win->layout = panel.layout; + + ctx->current = win; + if ((panel.layout->flags & NK_WINDOW_CLOSED) || + (panel.layout->flags & NK_WINDOW_MINIMIZED)) + { + nk_flags f = panel.layout->flags; + nk_group_scrolled_end(ctx); + if (f & NK_WINDOW_CLOSED) + return NK_WINDOW_CLOSED; + if (f & NK_WINDOW_MINIMIZED) + return NK_WINDOW_MINIMIZED; + } + return 1; +} +NK_API void +nk_group_scrolled_end(struct nk_context *ctx) +{ + struct nk_window *win; + struct nk_panel *parent; + struct nk_panel *g; + + struct nk_rect clip; + struct nk_window pan; + struct nk_vec2 panel_padding; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) + return; + + /* make sure nk_group_begin was called correctly */ + NK_ASSERT(ctx->current); + win = ctx->current; + NK_ASSERT(win->layout); + g = win->layout; + NK_ASSERT(g->parent); + parent = g->parent; + + /* dummy window */ + nk_zero_struct(pan); + panel_padding = nk_panel_get_padding(&ctx->style, NK_PANEL_GROUP); + pan.bounds.y = g->bounds.y - (g->header_height + g->menu.h); + pan.bounds.x = g->bounds.x - panel_padding.x; + pan.bounds.w = g->bounds.w + 2 * panel_padding.x; + pan.bounds.h = g->bounds.h + g->header_height + g->menu.h; + if (g->flags & NK_WINDOW_BORDER) { + pan.bounds.x -= g->border; + pan.bounds.y -= g->border; + pan.bounds.w += 2*g->border; + pan.bounds.h += 2*g->border; + } + if (!(g->flags & NK_WINDOW_NO_SCROLLBAR)) { + pan.bounds.w += ctx->style.window.scrollbar_size.x; + pan.bounds.h += ctx->style.window.scrollbar_size.y; + } + pan.scrollbar.x = *g->offset_x; + pan.scrollbar.y = *g->offset_y; + pan.flags = g->flags; + pan.buffer = win->buffer; + pan.layout = g; + pan.parent = win; + ctx->current = &pan; + + /* make sure group has correct clipping rectangle */ + nk_unify(&clip, &parent->clip, pan.bounds.x, pan.bounds.y, + pan.bounds.x + pan.bounds.w, pan.bounds.y + pan.bounds.h + panel_padding.x); + nk_push_scissor(&pan.buffer, clip); + nk_end(ctx); + + win->buffer = pan.buffer; + nk_push_scissor(&win->buffer, parent->clip); + ctx->current = win; + win->layout = parent; + g->bounds = pan.bounds; + return; +} +NK_API nk_bool +nk_group_scrolled_begin(struct nk_context *ctx, + struct nk_scroll *scroll, const char *title, nk_flags flags) +{ + return nk_group_scrolled_offset_begin(ctx, &scroll->x, &scroll->y, title, flags); +} +NK_API nk_bool +nk_group_begin_titled(struct nk_context *ctx, const char *id, + const char *title, nk_flags flags) +{ + int id_len; + nk_hash id_hash; + struct nk_window *win; + nk_uint *x_offset; + nk_uint *y_offset; + + NK_ASSERT(ctx); + NK_ASSERT(id); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout || !id) + return 0; + + /* find persistent group scrollbar value */ + win = ctx->current; + id_len = (int)nk_strlen(id); + id_hash = nk_murmur_hash(id, (int)id_len, NK_PANEL_GROUP); + x_offset = nk_find_value(win, id_hash); + if (!x_offset) { + x_offset = nk_add_value(ctx, win, id_hash, 0); + y_offset = nk_add_value(ctx, win, id_hash+1, 0); + + NK_ASSERT(x_offset); + NK_ASSERT(y_offset); + if (!x_offset || !y_offset) return 0; + *x_offset = *y_offset = 0; + } else y_offset = nk_find_value(win, id_hash+1); + return nk_group_scrolled_offset_begin(ctx, x_offset, y_offset, title, flags); +} +NK_API nk_bool +nk_group_begin(struct nk_context *ctx, const char *title, nk_flags flags) +{ + return nk_group_begin_titled(ctx, title, title, flags); +} +NK_API void +nk_group_end(struct nk_context *ctx) +{ + nk_group_scrolled_end(ctx); +} +NK_API void +nk_group_get_scroll(struct nk_context *ctx, const char *id, nk_uint *x_offset, nk_uint *y_offset) +{ + int id_len; + nk_hash id_hash; + struct nk_window *win; + nk_uint *x_offset_ptr; + nk_uint *y_offset_ptr; + + NK_ASSERT(ctx); + NK_ASSERT(id); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout || !id) + return; + + /* find persistent group scrollbar value */ + win = ctx->current; + id_len = (int)nk_strlen(id); + id_hash = nk_murmur_hash(id, (int)id_len, NK_PANEL_GROUP); + x_offset_ptr = nk_find_value(win, id_hash); + if (!x_offset_ptr) { + x_offset_ptr = nk_add_value(ctx, win, id_hash, 0); + y_offset_ptr = nk_add_value(ctx, win, id_hash+1, 0); + + NK_ASSERT(x_offset_ptr); + NK_ASSERT(y_offset_ptr); + if (!x_offset_ptr || !y_offset_ptr) return; + *x_offset_ptr = *y_offset_ptr = 0; + } else y_offset_ptr = nk_find_value(win, id_hash+1); + if (x_offset) + *x_offset = *x_offset_ptr; + if (y_offset) + *y_offset = *y_offset_ptr; +} +NK_API void +nk_group_set_scroll(struct nk_context *ctx, const char *id, nk_uint x_offset, nk_uint y_offset) +{ + int id_len; + nk_hash id_hash; + struct nk_window *win; + nk_uint *x_offset_ptr; + nk_uint *y_offset_ptr; + + NK_ASSERT(ctx); + NK_ASSERT(id); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout || !id) + return; + + /* find persistent group scrollbar value */ + win = ctx->current; + id_len = (int)nk_strlen(id); + id_hash = nk_murmur_hash(id, (int)id_len, NK_PANEL_GROUP); + x_offset_ptr = nk_find_value(win, id_hash); + if (!x_offset_ptr) { + x_offset_ptr = nk_add_value(ctx, win, id_hash, 0); + y_offset_ptr = nk_add_value(ctx, win, id_hash+1, 0); + + NK_ASSERT(x_offset_ptr); + NK_ASSERT(y_offset_ptr); + if (!x_offset_ptr || !y_offset_ptr) return; + *x_offset_ptr = *y_offset_ptr = 0; + } else y_offset_ptr = nk_find_value(win, id_hash+1); + *x_offset_ptr = x_offset; + *y_offset_ptr = y_offset; +} + + + + +/* =============================================================== + * + * LIST VIEW + * + * ===============================================================*/ +NK_API nk_bool +nk_list_view_begin(struct nk_context *ctx, struct nk_list_view *view, + const char *title, nk_flags flags, int row_height, int row_count) +{ + int title_len; + nk_hash title_hash; + nk_uint *x_offset; + nk_uint *y_offset; + + int result; + struct nk_window *win; + struct nk_panel *layout; + const struct nk_style *style; + struct nk_vec2 item_spacing; + + NK_ASSERT(ctx); + NK_ASSERT(view); + NK_ASSERT(title); + if (!ctx || !view || !title) return 0; + + win = ctx->current; + style = &ctx->style; + item_spacing = style->window.spacing; + row_height += NK_MAX(0, (int)item_spacing.y); + + /* find persistent list view scrollbar offset */ + title_len = (int)nk_strlen(title); + title_hash = nk_murmur_hash(title, (int)title_len, NK_PANEL_GROUP); + x_offset = nk_find_value(win, title_hash); + if (!x_offset) { + x_offset = nk_add_value(ctx, win, title_hash, 0); + y_offset = nk_add_value(ctx, win, title_hash+1, 0); + + NK_ASSERT(x_offset); + NK_ASSERT(y_offset); + if (!x_offset || !y_offset) return 0; + *x_offset = *y_offset = 0; + } else y_offset = nk_find_value(win, title_hash+1); + view->scroll_value = *y_offset; + view->scroll_pointer = y_offset; + + *y_offset = 0; + result = nk_group_scrolled_offset_begin(ctx, x_offset, y_offset, title, flags); + win = ctx->current; + layout = win->layout; + + view->total_height = row_height * NK_MAX(row_count,1); + view->begin = (int)NK_MAX(((float)view->scroll_value / (float)row_height), 0.0f); + view->count = (int)NK_MAX(nk_iceilf((layout->clip.h)/(float)row_height),0); + view->count = NK_MIN(view->count, row_count - view->begin); + view->end = view->begin + view->count; + view->ctx = ctx; + return result; +} +NK_API void +nk_list_view_end(struct nk_list_view *view) +{ + struct nk_context *ctx; + struct nk_window *win; + struct nk_panel *layout; + + NK_ASSERT(view); + NK_ASSERT(view->ctx); + NK_ASSERT(view->scroll_pointer); + if (!view || !view->ctx) return; + + ctx = view->ctx; + win = ctx->current; + layout = win->layout; + layout->at_y = layout->bounds.y + (float)view->total_height; + *view->scroll_pointer = *view->scroll_pointer + view->scroll_value; + nk_group_end(view->ctx); +} + + + + + +/* =============================================================== + * + * WIDGET + * + * ===============================================================*/ +NK_API struct nk_rect +nk_widget_bounds(struct nk_context *ctx) +{ + struct nk_rect bounds; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) + return nk_rect(0,0,0,0); + nk_layout_peek(&bounds, ctx); + return bounds; +} +NK_API struct nk_vec2 +nk_widget_position(struct nk_context *ctx) +{ + struct nk_rect bounds; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) + return nk_vec2(0,0); + + nk_layout_peek(&bounds, ctx); + return nk_vec2(bounds.x, bounds.y); +} +NK_API struct nk_vec2 +nk_widget_size(struct nk_context *ctx) +{ + struct nk_rect bounds; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) + return nk_vec2(0,0); + + nk_layout_peek(&bounds, ctx); + return nk_vec2(bounds.w, bounds.h); +} +NK_API float +nk_widget_width(struct nk_context *ctx) +{ + struct nk_rect bounds; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) + return 0; + + nk_layout_peek(&bounds, ctx); + return bounds.w; +} +NK_API float +nk_widget_height(struct nk_context *ctx) +{ + struct nk_rect bounds; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) + return 0; + + nk_layout_peek(&bounds, ctx); + return bounds.h; +} +NK_API nk_bool +nk_widget_is_hovered(struct nk_context *ctx) +{ + struct nk_rect c, v; + struct nk_rect bounds; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current || ctx->active != ctx->current) + return 0; + + c = ctx->current->layout->clip; + c.x = (float)((int)c.x); + c.y = (float)((int)c.y); + c.w = (float)((int)c.w); + c.h = (float)((int)c.h); + + nk_layout_peek(&bounds, ctx); + nk_unify(&v, &c, bounds.x, bounds.y, bounds.x + bounds.w, bounds.y + bounds.h); + if (!NK_INTERSECT(c.x, c.y, c.w, c.h, bounds.x, bounds.y, bounds.w, bounds.h)) + return 0; + return nk_input_is_mouse_hovering_rect(&ctx->input, bounds); +} +NK_API nk_bool +nk_widget_is_mouse_clicked(struct nk_context *ctx, enum nk_buttons btn) +{ + struct nk_rect c, v; + struct nk_rect bounds; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current || ctx->active != ctx->current) + return 0; + + c = ctx->current->layout->clip; + c.x = (float)((int)c.x); + c.y = (float)((int)c.y); + c.w = (float)((int)c.w); + c.h = (float)((int)c.h); + + nk_layout_peek(&bounds, ctx); + nk_unify(&v, &c, bounds.x, bounds.y, bounds.x + bounds.w, bounds.y + bounds.h); + if (!NK_INTERSECT(c.x, c.y, c.w, c.h, bounds.x, bounds.y, bounds.w, bounds.h)) + return 0; + return nk_input_mouse_clicked(&ctx->input, btn, bounds); +} +NK_API nk_bool +nk_widget_has_mouse_click_down(struct nk_context *ctx, enum nk_buttons btn, nk_bool down) +{ + struct nk_rect c, v; + struct nk_rect bounds; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current || ctx->active != ctx->current) + return 0; + + c = ctx->current->layout->clip; + c.x = (float)((int)c.x); + c.y = (float)((int)c.y); + c.w = (float)((int)c.w); + c.h = (float)((int)c.h); + + nk_layout_peek(&bounds, ctx); + nk_unify(&v, &c, bounds.x, bounds.y, bounds.x + bounds.w, bounds.y + bounds.h); + if (!NK_INTERSECT(c.x, c.y, c.w, c.h, bounds.x, bounds.y, bounds.w, bounds.h)) + return 0; + return nk_input_has_mouse_click_down_in_rect(&ctx->input, btn, bounds, down); +} +NK_API enum nk_widget_layout_states +nk_widget(struct nk_rect *bounds, const struct nk_context *ctx) +{ + struct nk_rect c, v; + struct nk_window *win; + struct nk_panel *layout; + const struct nk_input *in; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return NK_WIDGET_INVALID; + + /* allocate space and check if the widget needs to be updated and drawn */ + nk_panel_alloc_space(bounds, ctx); + win = ctx->current; + layout = win->layout; + in = &ctx->input; + c = layout->clip; + + /* if one of these triggers you forgot to add an `if` condition around either + a window, group, popup, combobox or contextual menu `begin` and `end` block. + Example: + if (nk_begin(...) {...} nk_end(...); or + if (nk_group_begin(...) { nk_group_end(...);} */ + NK_ASSERT(!(layout->flags & NK_WINDOW_MINIMIZED)); + NK_ASSERT(!(layout->flags & NK_WINDOW_HIDDEN)); + NK_ASSERT(!(layout->flags & NK_WINDOW_CLOSED)); + + /* need to convert to int here to remove floating point errors */ + bounds->x = (float)((int)bounds->x); + bounds->y = (float)((int)bounds->y); + bounds->w = (float)((int)bounds->w); + bounds->h = (float)((int)bounds->h); + + c.x = (float)((int)c.x); + c.y = (float)((int)c.y); + c.w = (float)((int)c.w); + c.h = (float)((int)c.h); + + nk_unify(&v, &c, bounds->x, bounds->y, bounds->x + bounds->w, bounds->y + bounds->h); + if (!NK_INTERSECT(c.x, c.y, c.w, c.h, bounds->x, bounds->y, bounds->w, bounds->h)) + return NK_WIDGET_INVALID; + if (!NK_INBOX(in->mouse.pos.x, in->mouse.pos.y, v.x, v.y, v.w, v.h)) + return NK_WIDGET_ROM; + return NK_WIDGET_VALID; +} +NK_API enum nk_widget_layout_states +nk_widget_fitting(struct nk_rect *bounds, struct nk_context *ctx, + struct nk_vec2 item_padding) +{ + /* update the bounds to stand without padding */ + enum nk_widget_layout_states state; + NK_UNUSED(item_padding); + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return NK_WIDGET_INVALID; + + state = nk_widget(bounds, ctx); + return state; +} +NK_API void +nk_spacing(struct nk_context *ctx, int cols) +{ + struct nk_window *win; + struct nk_panel *layout; + struct nk_rect none; + int i, index, rows; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + /* spacing over row boundaries */ + win = ctx->current; + layout = win->layout; + index = (layout->row.index + cols) % layout->row.columns; + rows = (layout->row.index + cols) / layout->row.columns; + if (rows) { + for (i = 0; i < rows; ++i) + nk_panel_alloc_row(ctx, win); + cols = index; + } + /* non table layout need to allocate space */ + if (layout->row.type != NK_LAYOUT_DYNAMIC_FIXED && + layout->row.type != NK_LAYOUT_STATIC_FIXED) { + for (i = 0; i < cols; ++i) + nk_panel_alloc_space(&none, ctx); + } layout->row.index = index; +} + + + + + +/* =============================================================== + * + * TEXT + * + * ===============================================================*/ +NK_LIB void +nk_widget_text(struct nk_command_buffer *o, struct nk_rect b, + const char *string, int len, const struct nk_text *t, + nk_flags a, const struct nk_user_font *f) +{ + struct nk_rect label; + float text_width; + + NK_ASSERT(o); + NK_ASSERT(t); + if (!o || !t) return; + + b.h = NK_MAX(b.h, 2 * t->padding.y); + label.x = 0; label.w = 0; + label.y = b.y + t->padding.y; + label.h = NK_MIN(f->height, b.h - 2 * t->padding.y); + + text_width = f->width(f->userdata, f->height, (const char*)string, len); + text_width += (2.0f * t->padding.x); + + /* align in x-axis */ + if (a & NK_TEXT_ALIGN_LEFT) { + label.x = b.x + t->padding.x; + label.w = NK_MAX(0, b.w - 2 * t->padding.x); + } else if (a & NK_TEXT_ALIGN_CENTERED) { + label.w = NK_MAX(1, 2 * t->padding.x + (float)text_width); + label.x = (b.x + t->padding.x + ((b.w - 2 * t->padding.x) - label.w) / 2); + label.x = NK_MAX(b.x + t->padding.x, label.x); + label.w = NK_MIN(b.x + b.w, label.x + label.w); + if (label.w >= label.x) label.w -= label.x; + } else if (a & NK_TEXT_ALIGN_RIGHT) { + label.x = NK_MAX(b.x + t->padding.x, (b.x + b.w) - (2 * t->padding.x + (float)text_width)); + label.w = (float)text_width + 2 * t->padding.x; + } else return; + + /* align in y-axis */ + if (a & NK_TEXT_ALIGN_MIDDLE) { + label.y = b.y + b.h/2.0f - (float)f->height/2.0f; + label.h = NK_MAX(b.h/2.0f, b.h - (b.h/2.0f + f->height/2.0f)); + } else if (a & NK_TEXT_ALIGN_BOTTOM) { + label.y = b.y + b.h - f->height; + label.h = f->height; + } + nk_draw_text(o, label, (const char*)string, len, f, t->background, t->text); +} +NK_LIB void +nk_widget_text_wrap(struct nk_command_buffer *o, struct nk_rect b, + const char *string, int len, const struct nk_text *t, + const struct nk_user_font *f) +{ + float width; + int glyphs = 0; + int fitting = 0; + int done = 0; + struct nk_rect line; + struct nk_text text; + NK_INTERN nk_rune seperator[] = {' '}; + + NK_ASSERT(o); + NK_ASSERT(t); + if (!o || !t) return; + + text.padding = nk_vec2(0,0); + text.background = t->background; + text.text = t->text; + + b.w = NK_MAX(b.w, 2 * t->padding.x); + b.h = NK_MAX(b.h, 2 * t->padding.y); + b.h = b.h - 2 * t->padding.y; + + line.x = b.x + t->padding.x; + line.y = b.y + t->padding.y; + line.w = b.w - 2 * t->padding.x; + line.h = 2 * t->padding.y + f->height; + + fitting = nk_text_clamp(f, string, len, line.w, &glyphs, &width, seperator,NK_LEN(seperator)); + while (done < len) { + if (!fitting || line.y + line.h >= (b.y + b.h)) break; + nk_widget_text(o, line, &string[done], fitting, &text, NK_TEXT_LEFT, f); + done += fitting; + line.y += f->height + 2 * t->padding.y; + fitting = nk_text_clamp(f, &string[done], len - done, line.w, &glyphs, &width, seperator,NK_LEN(seperator)); + } +} +NK_API void +nk_text_colored(struct nk_context *ctx, const char *str, int len, + nk_flags alignment, struct nk_color color) +{ + struct nk_window *win; + const struct nk_style *style; + + struct nk_vec2 item_padding; + struct nk_rect bounds; + struct nk_text text; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) return; + + win = ctx->current; + style = &ctx->style; + nk_panel_alloc_space(&bounds, ctx); + item_padding = style->text.padding; + + text.padding.x = item_padding.x; + text.padding.y = item_padding.y; + text.background = style->window.background; + text.text = color; + nk_widget_text(&win->buffer, bounds, str, len, &text, alignment, style->font); +} +NK_API void +nk_text_wrap_colored(struct nk_context *ctx, const char *str, + int len, struct nk_color color) +{ + struct nk_window *win; + const struct nk_style *style; + + struct nk_vec2 item_padding; + struct nk_rect bounds; + struct nk_text text; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) return; + + win = ctx->current; + style = &ctx->style; + nk_panel_alloc_space(&bounds, ctx); + item_padding = style->text.padding; + + text.padding.x = item_padding.x; + text.padding.y = item_padding.y; + text.background = style->window.background; + text.text = color; + nk_widget_text_wrap(&win->buffer, bounds, str, len, &text, style->font); +} +#ifdef NK_INCLUDE_STANDARD_VARARGS +NK_API void +nk_labelf_colored(struct nk_context *ctx, nk_flags flags, + struct nk_color color, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + nk_labelfv_colored(ctx, flags, color, fmt, args); + va_end(args); +} +NK_API void +nk_labelf_colored_wrap(struct nk_context *ctx, struct nk_color color, + const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + nk_labelfv_colored_wrap(ctx, color, fmt, args); + va_end(args); +} +NK_API void +nk_labelf(struct nk_context *ctx, nk_flags flags, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + nk_labelfv(ctx, flags, fmt, args); + va_end(args); +} +NK_API void +nk_labelf_wrap(struct nk_context *ctx, const char *fmt,...) +{ + va_list args; + va_start(args, fmt); + nk_labelfv_wrap(ctx, fmt, args); + va_end(args); +} +NK_API void +nk_labelfv_colored(struct nk_context *ctx, nk_flags flags, + struct nk_color color, const char *fmt, va_list args) +{ + char buf[256]; + nk_strfmt(buf, NK_LEN(buf), fmt, args); + nk_label_colored(ctx, buf, flags, color); +} + +NK_API void +nk_labelfv_colored_wrap(struct nk_context *ctx, struct nk_color color, + const char *fmt, va_list args) +{ + char buf[256]; + nk_strfmt(buf, NK_LEN(buf), fmt, args); + nk_label_colored_wrap(ctx, buf, color); +} + +NK_API void +nk_labelfv(struct nk_context *ctx, nk_flags flags, const char *fmt, va_list args) +{ + char buf[256]; + nk_strfmt(buf, NK_LEN(buf), fmt, args); + nk_label(ctx, buf, flags); +} + +NK_API void +nk_labelfv_wrap(struct nk_context *ctx, const char *fmt, va_list args) +{ + char buf[256]; + nk_strfmt(buf, NK_LEN(buf), fmt, args); + nk_label_wrap(ctx, buf); +} + +NK_API void +nk_value_bool(struct nk_context *ctx, const char *prefix, int value) +{ + nk_labelf(ctx, NK_TEXT_LEFT, "%s: %s", prefix, ((value) ? "true": "false")); +} +NK_API void +nk_value_int(struct nk_context *ctx, const char *prefix, int value) +{ + nk_labelf(ctx, NK_TEXT_LEFT, "%s: %d", prefix, value); +} +NK_API void +nk_value_uint(struct nk_context *ctx, const char *prefix, unsigned int value) +{ + nk_labelf(ctx, NK_TEXT_LEFT, "%s: %u", prefix, value); +} +NK_API void +nk_value_float(struct nk_context *ctx, const char *prefix, float value) +{ + double double_value = (double)value; + nk_labelf(ctx, NK_TEXT_LEFT, "%s: %.3f", prefix, double_value); +} +NK_API void +nk_value_color_byte(struct nk_context *ctx, const char *p, struct nk_color c) +{ + nk_labelf(ctx, NK_TEXT_LEFT, "%s: (%d, %d, %d, %d)", p, c.r, c.g, c.b, c.a); +} +NK_API void +nk_value_color_float(struct nk_context *ctx, const char *p, struct nk_color color) +{ + double c[4]; nk_color_dv(c, color); + nk_labelf(ctx, NK_TEXT_LEFT, "%s: (%.2f, %.2f, %.2f, %.2f)", + p, c[0], c[1], c[2], c[3]); +} +NK_API void +nk_value_color_hex(struct nk_context *ctx, const char *prefix, struct nk_color color) +{ + char hex[16]; + nk_color_hex_rgba(hex, color); + nk_labelf(ctx, NK_TEXT_LEFT, "%s: %s", prefix, hex); +} +#endif +NK_API void +nk_text(struct nk_context *ctx, const char *str, int len, nk_flags alignment) +{ + NK_ASSERT(ctx); + if (!ctx) return; + nk_text_colored(ctx, str, len, alignment, ctx->style.text.color); +} +NK_API void +nk_text_wrap(struct nk_context *ctx, const char *str, int len) +{ + NK_ASSERT(ctx); + if (!ctx) return; + nk_text_wrap_colored(ctx, str, len, ctx->style.text.color); +} +NK_API void +nk_label(struct nk_context *ctx, const char *str, nk_flags alignment) +{ + nk_text(ctx, str, nk_strlen(str), alignment); +} +NK_API void +nk_label_colored(struct nk_context *ctx, const char *str, nk_flags align, + struct nk_color color) +{ + nk_text_colored(ctx, str, nk_strlen(str), align, color); +} +NK_API void +nk_label_wrap(struct nk_context *ctx, const char *str) +{ + nk_text_wrap(ctx, str, nk_strlen(str)); +} +NK_API void +nk_label_colored_wrap(struct nk_context *ctx, const char *str, struct nk_color color) +{ + nk_text_wrap_colored(ctx, str, nk_strlen(str), color); +} + + + + + +/* =============================================================== + * + * IMAGE + * + * ===============================================================*/ +NK_API nk_handle +nk_handle_ptr(void *ptr) +{ + nk_handle handle = {0}; + handle.ptr = ptr; + return handle; +} +NK_API nk_handle +nk_handle_id(int id) +{ + nk_handle handle; + nk_zero_struct(handle); + handle.id = id; + return handle; +} +NK_API struct nk_image +nk_subimage_ptr(void *ptr, unsigned short w, unsigned short h, struct nk_rect r) +{ + struct nk_image s; + nk_zero(&s, sizeof(s)); + s.handle.ptr = ptr; + s.w = w; s.h = h; + s.region[0] = (unsigned short)r.x; + s.region[1] = (unsigned short)r.y; + s.region[2] = (unsigned short)r.w; + s.region[3] = (unsigned short)r.h; + return s; +} +NK_API struct nk_image +nk_subimage_id(int id, unsigned short w, unsigned short h, struct nk_rect r) +{ + struct nk_image s; + nk_zero(&s, sizeof(s)); + s.handle.id = id; + s.w = w; s.h = h; + s.region[0] = (unsigned short)r.x; + s.region[1] = (unsigned short)r.y; + s.region[2] = (unsigned short)r.w; + s.region[3] = (unsigned short)r.h; + return s; +} +NK_API struct nk_image +nk_subimage_handle(nk_handle handle, unsigned short w, unsigned short h, + struct nk_rect r) +{ + struct nk_image s; + nk_zero(&s, sizeof(s)); + s.handle = handle; + s.w = w; s.h = h; + s.region[0] = (unsigned short)r.x; + s.region[1] = (unsigned short)r.y; + s.region[2] = (unsigned short)r.w; + s.region[3] = (unsigned short)r.h; + return s; +} +NK_API struct nk_image +nk_image_handle(nk_handle handle) +{ + struct nk_image s; + nk_zero(&s, sizeof(s)); + s.handle = handle; + s.w = 0; s.h = 0; + s.region[0] = 0; + s.region[1] = 0; + s.region[2] = 0; + s.region[3] = 0; + return s; +} +NK_API struct nk_image +nk_image_ptr(void *ptr) +{ + struct nk_image s; + nk_zero(&s, sizeof(s)); + NK_ASSERT(ptr); + s.handle.ptr = ptr; + s.w = 0; s.h = 0; + s.region[0] = 0; + s.region[1] = 0; + s.region[2] = 0; + s.region[3] = 0; + return s; +} +NK_API struct nk_image +nk_image_id(int id) +{ + struct nk_image s; + nk_zero(&s, sizeof(s)); + s.handle.id = id; + s.w = 0; s.h = 0; + s.region[0] = 0; + s.region[1] = 0; + s.region[2] = 0; + s.region[3] = 0; + return s; +} +NK_API nk_bool +nk_image_is_subimage(const struct nk_image* img) +{ + NK_ASSERT(img); + return !(img->w == 0 && img->h == 0); +} +NK_API void +nk_image(struct nk_context *ctx, struct nk_image img) +{ + struct nk_window *win; + struct nk_rect bounds; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) return; + + win = ctx->current; + if (!nk_widget(&bounds, ctx)) return; + nk_draw_image(&win->buffer, bounds, &img, nk_white); +} +NK_API void +nk_image_color(struct nk_context *ctx, struct nk_image img, struct nk_color col) +{ + struct nk_window *win; + struct nk_rect bounds; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) return; + + win = ctx->current; + if (!nk_widget(&bounds, ctx)) return; + nk_draw_image(&win->buffer, bounds, &img, col); +} + + + + + +/* ============================================================== + * + * BUTTON + * + * ===============================================================*/ +NK_LIB void +nk_draw_symbol(struct nk_command_buffer *out, enum nk_symbol_type type, + struct nk_rect content, struct nk_color background, struct nk_color foreground, + float border_width, const struct nk_user_font *font) +{ + switch (type) { + case NK_SYMBOL_X: + case NK_SYMBOL_UNDERSCORE: + case NK_SYMBOL_PLUS: + case NK_SYMBOL_MINUS: { + /* single character text symbol */ + const char *X = (type == NK_SYMBOL_X) ? "x": + (type == NK_SYMBOL_UNDERSCORE) ? "_": + (type == NK_SYMBOL_PLUS) ? "+": "-"; + struct nk_text text; + text.padding = nk_vec2(0,0); + text.background = background; + text.text = foreground; + nk_widget_text(out, content, X, 1, &text, NK_TEXT_CENTERED, font); + } break; + case NK_SYMBOL_CIRCLE_SOLID: + case NK_SYMBOL_CIRCLE_OUTLINE: + case NK_SYMBOL_RECT_SOLID: + case NK_SYMBOL_RECT_OUTLINE: { + /* simple empty/filled shapes */ + if (type == NK_SYMBOL_RECT_SOLID || type == NK_SYMBOL_RECT_OUTLINE) { + nk_fill_rect(out, content, 0, foreground); + if (type == NK_SYMBOL_RECT_OUTLINE) + nk_fill_rect(out, nk_shrink_rect(content, border_width), 0, background); + } else { + nk_fill_circle(out, content, foreground); + if (type == NK_SYMBOL_CIRCLE_OUTLINE) + nk_fill_circle(out, nk_shrink_rect(content, 1), background); + } + } break; + case NK_SYMBOL_TRIANGLE_UP: + case NK_SYMBOL_TRIANGLE_DOWN: + case NK_SYMBOL_TRIANGLE_LEFT: + case NK_SYMBOL_TRIANGLE_RIGHT: { + enum nk_heading heading; + struct nk_vec2 points[3]; + heading = (type == NK_SYMBOL_TRIANGLE_RIGHT) ? NK_RIGHT : + (type == NK_SYMBOL_TRIANGLE_LEFT) ? NK_LEFT: + (type == NK_SYMBOL_TRIANGLE_UP) ? NK_UP: NK_DOWN; + nk_triangle_from_direction(points, content, 0, 0, heading); + nk_fill_triangle(out, points[0].x, points[0].y, points[1].x, points[1].y, + points[2].x, points[2].y, foreground); + } break; + default: + case NK_SYMBOL_NONE: + case NK_SYMBOL_MAX: break; + } +} +NK_LIB nk_bool +nk_button_behavior(nk_flags *state, struct nk_rect r, + const struct nk_input *i, enum nk_button_behavior behavior) +{ + int ret = 0; + nk_widget_state_reset(state); + if (!i) return 0; + if (nk_input_is_mouse_hovering_rect(i, r)) { + *state = NK_WIDGET_STATE_HOVERED; + if (nk_input_is_mouse_down(i, NK_BUTTON_LEFT)) + *state = NK_WIDGET_STATE_ACTIVE; + if (nk_input_has_mouse_click_in_rect(i, NK_BUTTON_LEFT, r)) { + ret = (behavior != NK_BUTTON_DEFAULT) ? + nk_input_is_mouse_down(i, NK_BUTTON_LEFT): +#ifdef NK_BUTTON_TRIGGER_ON_RELEASE + nk_input_is_mouse_released(i, NK_BUTTON_LEFT); +#else + nk_input_is_mouse_pressed(i, NK_BUTTON_LEFT); +#endif + } + } + if (*state & NK_WIDGET_STATE_HOVER && !nk_input_is_mouse_prev_hovering_rect(i, r)) + *state |= NK_WIDGET_STATE_ENTERED; + else if (nk_input_is_mouse_prev_hovering_rect(i, r)) + *state |= NK_WIDGET_STATE_LEFT; + return ret; +} +NK_LIB const struct nk_style_item* +nk_draw_button(struct nk_command_buffer *out, + const struct nk_rect *bounds, nk_flags state, + const struct nk_style_button *style) +{ + const struct nk_style_item *background; + if (state & NK_WIDGET_STATE_HOVER) + background = &style->hover; + else if (state & NK_WIDGET_STATE_ACTIVED) + background = &style->active; + else background = &style->normal; + + if (background->type == NK_STYLE_ITEM_IMAGE) { + nk_draw_image(out, *bounds, &background->data.image, nk_white); + } else { + nk_fill_rect(out, *bounds, style->rounding, background->data.color); + nk_stroke_rect(out, *bounds, style->rounding, style->border, style->border_color); + } + return background; +} +NK_LIB nk_bool +nk_do_button(nk_flags *state, struct nk_command_buffer *out, struct nk_rect r, + const struct nk_style_button *style, const struct nk_input *in, + enum nk_button_behavior behavior, struct nk_rect *content) +{ + struct nk_rect bounds; + NK_ASSERT(style); + NK_ASSERT(state); + NK_ASSERT(out); + if (!out || !style) + return nk_false; + + /* calculate button content space */ + content->x = r.x + style->padding.x + style->border + style->rounding; + content->y = r.y + style->padding.y + style->border + style->rounding; + content->w = r.w - (2 * style->padding.x + style->border + style->rounding*2); + content->h = r.h - (2 * style->padding.y + style->border + style->rounding*2); + + /* execute button behavior */ + bounds.x = r.x - style->touch_padding.x; + bounds.y = r.y - style->touch_padding.y; + bounds.w = r.w + 2 * style->touch_padding.x; + bounds.h = r.h + 2 * style->touch_padding.y; + return nk_button_behavior(state, bounds, in, behavior); +} +NK_LIB void +nk_draw_button_text(struct nk_command_buffer *out, + const struct nk_rect *bounds, const struct nk_rect *content, nk_flags state, + const struct nk_style_button *style, const char *txt, int len, + nk_flags text_alignment, const struct nk_user_font *font) +{ + struct nk_text text; + const struct nk_style_item *background; + background = nk_draw_button(out, bounds, state, style); + + /* select correct colors/images */ + if (background->type == NK_STYLE_ITEM_COLOR) + text.background = background->data.color; + else text.background = style->text_background; + if (state & NK_WIDGET_STATE_HOVER) + text.text = style->text_hover; + else if (state & NK_WIDGET_STATE_ACTIVED) + text.text = style->text_active; + else text.text = style->text_normal; + + text.padding = nk_vec2(0,0); + nk_widget_text(out, *content, txt, len, &text, text_alignment, font); +} +NK_LIB nk_bool +nk_do_button_text(nk_flags *state, + struct nk_command_buffer *out, struct nk_rect bounds, + const char *string, int len, nk_flags align, enum nk_button_behavior behavior, + const struct nk_style_button *style, const struct nk_input *in, + const struct nk_user_font *font) +{ + struct nk_rect content; + int ret = nk_false; + + NK_ASSERT(state); + NK_ASSERT(style); + NK_ASSERT(out); + NK_ASSERT(string); + NK_ASSERT(font); + if (!out || !style || !font || !string) + return nk_false; + + ret = nk_do_button(state, out, bounds, style, in, behavior, &content); + if (style->draw_begin) style->draw_begin(out, style->userdata); + nk_draw_button_text(out, &bounds, &content, *state, style, string, len, align, font); + if (style->draw_end) style->draw_end(out, style->userdata); + return ret; +} +NK_LIB void +nk_draw_button_symbol(struct nk_command_buffer *out, + const struct nk_rect *bounds, const struct nk_rect *content, + nk_flags state, const struct nk_style_button *style, + enum nk_symbol_type type, const struct nk_user_font *font) +{ + struct nk_color sym, bg; + const struct nk_style_item *background; + + /* select correct colors/images */ + background = nk_draw_button(out, bounds, state, style); + if (background->type == NK_STYLE_ITEM_COLOR) + bg = background->data.color; + else bg = style->text_background; + + if (state & NK_WIDGET_STATE_HOVER) + sym = style->text_hover; + else if (state & NK_WIDGET_STATE_ACTIVED) + sym = style->text_active; + else sym = style->text_normal; + nk_draw_symbol(out, type, *content, bg, sym, 1, font); +} +NK_LIB nk_bool +nk_do_button_symbol(nk_flags *state, + struct nk_command_buffer *out, struct nk_rect bounds, + enum nk_symbol_type symbol, enum nk_button_behavior behavior, + const struct nk_style_button *style, const struct nk_input *in, + const struct nk_user_font *font) +{ + int ret; + struct nk_rect content; + + NK_ASSERT(state); + NK_ASSERT(style); + NK_ASSERT(font); + NK_ASSERT(out); + if (!out || !style || !font || !state) + return nk_false; + + ret = nk_do_button(state, out, bounds, style, in, behavior, &content); + if (style->draw_begin) style->draw_begin(out, style->userdata); + nk_draw_button_symbol(out, &bounds, &content, *state, style, symbol, font); + if (style->draw_end) style->draw_end(out, style->userdata); + return ret; +} +NK_LIB void +nk_draw_button_image(struct nk_command_buffer *out, + const struct nk_rect *bounds, const struct nk_rect *content, + nk_flags state, const struct nk_style_button *style, const struct nk_image *img) +{ + nk_draw_button(out, bounds, state, style); + nk_draw_image(out, *content, img, nk_white); +} +NK_LIB nk_bool +nk_do_button_image(nk_flags *state, + struct nk_command_buffer *out, struct nk_rect bounds, + struct nk_image img, enum nk_button_behavior b, + const struct nk_style_button *style, const struct nk_input *in) +{ + int ret; + struct nk_rect content; + + NK_ASSERT(state); + NK_ASSERT(style); + NK_ASSERT(out); + if (!out || !style || !state) + return nk_false; + + ret = nk_do_button(state, out, bounds, style, in, b, &content); + content.x += style->image_padding.x; + content.y += style->image_padding.y; + content.w -= 2 * style->image_padding.x; + content.h -= 2 * style->image_padding.y; + + if (style->draw_begin) style->draw_begin(out, style->userdata); + nk_draw_button_image(out, &bounds, &content, *state, style, &img); + if (style->draw_end) style->draw_end(out, style->userdata); + return ret; +} +NK_LIB void +nk_draw_button_text_symbol(struct nk_command_buffer *out, + const struct nk_rect *bounds, const struct nk_rect *label, + const struct nk_rect *symbol, nk_flags state, const struct nk_style_button *style, + const char *str, int len, enum nk_symbol_type type, + const struct nk_user_font *font) +{ + struct nk_color sym; + struct nk_text text; + const struct nk_style_item *background; + + /* select correct background colors/images */ + background = nk_draw_button(out, bounds, state, style); + if (background->type == NK_STYLE_ITEM_COLOR) + text.background = background->data.color; + else text.background = style->text_background; + + /* select correct text colors */ + if (state & NK_WIDGET_STATE_HOVER) { + sym = style->text_hover; + text.text = style->text_hover; + } else if (state & NK_WIDGET_STATE_ACTIVED) { + sym = style->text_active; + text.text = style->text_active; + } else { + sym = style->text_normal; + text.text = style->text_normal; + } + + text.padding = nk_vec2(0,0); + nk_draw_symbol(out, type, *symbol, style->text_background, sym, 0, font); + nk_widget_text(out, *label, str, len, &text, NK_TEXT_CENTERED, font); +} +NK_LIB nk_bool +nk_do_button_text_symbol(nk_flags *state, + struct nk_command_buffer *out, struct nk_rect bounds, + enum nk_symbol_type symbol, const char *str, int len, nk_flags align, + enum nk_button_behavior behavior, const struct nk_style_button *style, + const struct nk_user_font *font, const struct nk_input *in) +{ + int ret; + struct nk_rect tri = {0,0,0,0}; + struct nk_rect content; + + NK_ASSERT(style); + NK_ASSERT(out); + NK_ASSERT(font); + if (!out || !style || !font) + return nk_false; + + ret = nk_do_button(state, out, bounds, style, in, behavior, &content); + tri.y = content.y + (content.h/2) - font->height/2; + tri.w = font->height; tri.h = font->height; + if (align & NK_TEXT_ALIGN_LEFT) { + tri.x = (content.x + content.w) - (2 * style->padding.x + tri.w); + tri.x = NK_MAX(tri.x, 0); + } else tri.x = content.x + 2 * style->padding.x; + + /* draw button */ + if (style->draw_begin) style->draw_begin(out, style->userdata); + nk_draw_button_text_symbol(out, &bounds, &content, &tri, + *state, style, str, len, symbol, font); + if (style->draw_end) style->draw_end(out, style->userdata); + return ret; +} +NK_LIB void +nk_draw_button_text_image(struct nk_command_buffer *out, + const struct nk_rect *bounds, const struct nk_rect *label, + const struct nk_rect *image, nk_flags state, const struct nk_style_button *style, + const char *str, int len, const struct nk_user_font *font, + const struct nk_image *img) +{ + struct nk_text text; + const struct nk_style_item *background; + background = nk_draw_button(out, bounds, state, style); + + /* select correct colors */ + if (background->type == NK_STYLE_ITEM_COLOR) + text.background = background->data.color; + else text.background = style->text_background; + if (state & NK_WIDGET_STATE_HOVER) + text.text = style->text_hover; + else if (state & NK_WIDGET_STATE_ACTIVED) + text.text = style->text_active; + else text.text = style->text_normal; + + text.padding = nk_vec2(0,0); + nk_widget_text(out, *label, str, len, &text, NK_TEXT_CENTERED, font); + nk_draw_image(out, *image, img, nk_white); +} +NK_LIB nk_bool +nk_do_button_text_image(nk_flags *state, + struct nk_command_buffer *out, struct nk_rect bounds, + struct nk_image img, const char* str, int len, nk_flags align, + enum nk_button_behavior behavior, const struct nk_style_button *style, + const struct nk_user_font *font, const struct nk_input *in) +{ + int ret; + struct nk_rect icon; + struct nk_rect content; + + NK_ASSERT(style); + NK_ASSERT(state); + NK_ASSERT(font); + NK_ASSERT(out); + if (!out || !font || !style || !str) + return nk_false; + + ret = nk_do_button(state, out, bounds, style, in, behavior, &content); + icon.y = bounds.y + style->padding.y; + icon.w = icon.h = bounds.h - 2 * style->padding.y; + if (align & NK_TEXT_ALIGN_LEFT) { + icon.x = (bounds.x + bounds.w) - (2 * style->padding.x + icon.w); + icon.x = NK_MAX(icon.x, 0); + } else icon.x = bounds.x + 2 * style->padding.x; + + icon.x += style->image_padding.x; + icon.y += style->image_padding.y; + icon.w -= 2 * style->image_padding.x; + icon.h -= 2 * style->image_padding.y; + + if (style->draw_begin) style->draw_begin(out, style->userdata); + nk_draw_button_text_image(out, &bounds, &content, &icon, *state, style, str, len, font, &img); + if (style->draw_end) style->draw_end(out, style->userdata); + return ret; +} +NK_API void +nk_button_set_behavior(struct nk_context *ctx, enum nk_button_behavior behavior) +{ + NK_ASSERT(ctx); + if (!ctx) return; + ctx->button_behavior = behavior; +} +NK_API nk_bool +nk_button_push_behavior(struct nk_context *ctx, enum nk_button_behavior behavior) +{ + struct nk_config_stack_button_behavior *button_stack; + struct nk_config_stack_button_behavior_element *element; + + NK_ASSERT(ctx); + if (!ctx) return 0; + + button_stack = &ctx->stacks.button_behaviors; + NK_ASSERT(button_stack->head < (int)NK_LEN(button_stack->elements)); + if (button_stack->head >= (int)NK_LEN(button_stack->elements)) + return 0; + + element = &button_stack->elements[button_stack->head++]; + element->address = &ctx->button_behavior; + element->old_value = ctx->button_behavior; + ctx->button_behavior = behavior; + return 1; +} +NK_API nk_bool +nk_button_pop_behavior(struct nk_context *ctx) +{ + struct nk_config_stack_button_behavior *button_stack; + struct nk_config_stack_button_behavior_element *element; + + NK_ASSERT(ctx); + if (!ctx) return 0; + + button_stack = &ctx->stacks.button_behaviors; + NK_ASSERT(button_stack->head > 0); + if (button_stack->head < 1) + return 0; + + element = &button_stack->elements[--button_stack->head]; + *element->address = element->old_value; + return 1; +} +NK_API nk_bool +nk_button_text_styled(struct nk_context *ctx, + const struct nk_style_button *style, const char *title, int len) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_input *in; + + struct nk_rect bounds; + enum nk_widget_layout_states state; + + NK_ASSERT(ctx); + NK_ASSERT(style); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!style || !ctx || !ctx->current || !ctx->current->layout) return 0; + + win = ctx->current; + layout = win->layout; + state = nk_widget(&bounds, ctx); + + if (!state) return 0; + in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + return nk_do_button_text(&ctx->last_widget_state, &win->buffer, bounds, + title, len, style->text_alignment, ctx->button_behavior, + style, in, ctx->style.font); +} +NK_API nk_bool +nk_button_text(struct nk_context *ctx, const char *title, int len) +{ + NK_ASSERT(ctx); + if (!ctx) return 0; + return nk_button_text_styled(ctx, &ctx->style.button, title, len); +} +NK_API nk_bool nk_button_label_styled(struct nk_context *ctx, + const struct nk_style_button *style, const char *title) +{ + return nk_button_text_styled(ctx, style, title, nk_strlen(title)); +} +NK_API nk_bool nk_button_label(struct nk_context *ctx, const char *title) +{ + return nk_button_text(ctx, title, nk_strlen(title)); +} +NK_API nk_bool +nk_button_color(struct nk_context *ctx, struct nk_color color) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_input *in; + struct nk_style_button button; + + int ret = 0; + struct nk_rect bounds; + struct nk_rect content; + enum nk_widget_layout_states state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + layout = win->layout; + + state = nk_widget(&bounds, ctx); + if (!state) return 0; + in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + + button = ctx->style.button; + button.normal = nk_style_item_color(color); + button.hover = nk_style_item_color(color); + button.active = nk_style_item_color(color); + ret = nk_do_button(&ctx->last_widget_state, &win->buffer, bounds, + &button, in, ctx->button_behavior, &content); + nk_draw_button(&win->buffer, &bounds, ctx->last_widget_state, &button); + return ret; +} +NK_API nk_bool +nk_button_symbol_styled(struct nk_context *ctx, + const struct nk_style_button *style, enum nk_symbol_type symbol) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_input *in; + + struct nk_rect bounds; + enum nk_widget_layout_states state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + layout = win->layout; + state = nk_widget(&bounds, ctx); + if (!state) return 0; + in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + return nk_do_button_symbol(&ctx->last_widget_state, &win->buffer, bounds, + symbol, ctx->button_behavior, style, in, ctx->style.font); +} +NK_API nk_bool +nk_button_symbol(struct nk_context *ctx, enum nk_symbol_type symbol) +{ + NK_ASSERT(ctx); + if (!ctx) return 0; + return nk_button_symbol_styled(ctx, &ctx->style.button, symbol); +} +NK_API nk_bool +nk_button_image_styled(struct nk_context *ctx, const struct nk_style_button *style, + struct nk_image img) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_input *in; + + struct nk_rect bounds; + enum nk_widget_layout_states state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + layout = win->layout; + + state = nk_widget(&bounds, ctx); + if (!state) return 0; + in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + return nk_do_button_image(&ctx->last_widget_state, &win->buffer, bounds, + img, ctx->button_behavior, style, in); +} +NK_API nk_bool +nk_button_image(struct nk_context *ctx, struct nk_image img) +{ + NK_ASSERT(ctx); + if (!ctx) return 0; + return nk_button_image_styled(ctx, &ctx->style.button, img); +} +NK_API nk_bool +nk_button_symbol_text_styled(struct nk_context *ctx, + const struct nk_style_button *style, enum nk_symbol_type symbol, + const char *text, int len, nk_flags align) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_input *in; + + struct nk_rect bounds; + enum nk_widget_layout_states state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + layout = win->layout; + + state = nk_widget(&bounds, ctx); + if (!state) return 0; + in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + return nk_do_button_text_symbol(&ctx->last_widget_state, &win->buffer, bounds, + symbol, text, len, align, ctx->button_behavior, + style, ctx->style.font, in); +} +NK_API nk_bool +nk_button_symbol_text(struct nk_context *ctx, enum nk_symbol_type symbol, + const char* text, int len, nk_flags align) +{ + NK_ASSERT(ctx); + if (!ctx) return 0; + return nk_button_symbol_text_styled(ctx, &ctx->style.button, symbol, text, len, align); +} +NK_API nk_bool nk_button_symbol_label(struct nk_context *ctx, enum nk_symbol_type symbol, + const char *label, nk_flags align) +{ + return nk_button_symbol_text(ctx, symbol, label, nk_strlen(label), align); +} +NK_API nk_bool nk_button_symbol_label_styled(struct nk_context *ctx, + const struct nk_style_button *style, enum nk_symbol_type symbol, + const char *title, nk_flags align) +{ + return nk_button_symbol_text_styled(ctx, style, symbol, title, nk_strlen(title), align); +} +NK_API nk_bool +nk_button_image_text_styled(struct nk_context *ctx, + const struct nk_style_button *style, struct nk_image img, const char *text, + int len, nk_flags align) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_input *in; + + struct nk_rect bounds; + enum nk_widget_layout_states state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + layout = win->layout; + + state = nk_widget(&bounds, ctx); + if (!state) return 0; + in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + return nk_do_button_text_image(&ctx->last_widget_state, &win->buffer, + bounds, img, text, len, align, ctx->button_behavior, + style, ctx->style.font, in); +} +NK_API nk_bool +nk_button_image_text(struct nk_context *ctx, struct nk_image img, + const char *text, int len, nk_flags align) +{ + return nk_button_image_text_styled(ctx, &ctx->style.button,img, text, len, align); +} +NK_API nk_bool nk_button_image_label(struct nk_context *ctx, struct nk_image img, + const char *label, nk_flags align) +{ + return nk_button_image_text(ctx, img, label, nk_strlen(label), align); +} +NK_API nk_bool nk_button_image_label_styled(struct nk_context *ctx, + const struct nk_style_button *style, struct nk_image img, + const char *label, nk_flags text_alignment) +{ + return nk_button_image_text_styled(ctx, style, img, label, nk_strlen(label), text_alignment); +} + + + + + +/* =============================================================== + * + * TOGGLE + * + * ===============================================================*/ +NK_LIB nk_bool +nk_toggle_behavior(const struct nk_input *in, struct nk_rect select, + nk_flags *state, nk_bool active) +{ + nk_widget_state_reset(state); + if (nk_button_behavior(state, select, in, NK_BUTTON_DEFAULT)) { + *state = NK_WIDGET_STATE_ACTIVE; + active = !active; + } + if (*state & NK_WIDGET_STATE_HOVER && !nk_input_is_mouse_prev_hovering_rect(in, select)) + *state |= NK_WIDGET_STATE_ENTERED; + else if (nk_input_is_mouse_prev_hovering_rect(in, select)) + *state |= NK_WIDGET_STATE_LEFT; + return active; +} +NK_LIB void +nk_draw_checkbox(struct nk_command_buffer *out, + nk_flags state, const struct nk_style_toggle *style, nk_bool active, + const struct nk_rect *label, const struct nk_rect *selector, + const struct nk_rect *cursors, const char *string, int len, + const struct nk_user_font *font) +{ + const struct nk_style_item *background; + const struct nk_style_item *cursor; + struct nk_text text; + + /* select correct colors/images */ + if (state & NK_WIDGET_STATE_HOVER) { + background = &style->hover; + cursor = &style->cursor_hover; + text.text = style->text_hover; + } else if (state & NK_WIDGET_STATE_ACTIVED) { + background = &style->hover; + cursor = &style->cursor_hover; + text.text = style->text_active; + } else { + background = &style->normal; + cursor = &style->cursor_normal; + text.text = style->text_normal; + } + + /* draw background and cursor */ + if (background->type == NK_STYLE_ITEM_COLOR) { + nk_fill_rect(out, *selector, 0, style->border_color); + nk_fill_rect(out, nk_shrink_rect(*selector, style->border), 0, background->data.color); + } else nk_draw_image(out, *selector, &background->data.image, nk_white); + if (active) { + if (cursor->type == NK_STYLE_ITEM_IMAGE) + nk_draw_image(out, *cursors, &cursor->data.image, nk_white); + else nk_fill_rect(out, *cursors, 0, cursor->data.color); + } + + text.padding.x = 0; + text.padding.y = 0; + text.background = style->text_background; + nk_widget_text(out, *label, string, len, &text, NK_TEXT_LEFT, font); +} +NK_LIB void +nk_draw_option(struct nk_command_buffer *out, + nk_flags state, const struct nk_style_toggle *style, nk_bool active, + const struct nk_rect *label, const struct nk_rect *selector, + const struct nk_rect *cursors, const char *string, int len, + const struct nk_user_font *font) +{ + const struct nk_style_item *background; + const struct nk_style_item *cursor; + struct nk_text text; + + /* select correct colors/images */ + if (state & NK_WIDGET_STATE_HOVER) { + background = &style->hover; + cursor = &style->cursor_hover; + text.text = style->text_hover; + } else if (state & NK_WIDGET_STATE_ACTIVED) { + background = &style->hover; + cursor = &style->cursor_hover; + text.text = style->text_active; + } else { + background = &style->normal; + cursor = &style->cursor_normal; + text.text = style->text_normal; + } + + /* draw background and cursor */ + if (background->type == NK_STYLE_ITEM_COLOR) { + nk_fill_circle(out, *selector, style->border_color); + nk_fill_circle(out, nk_shrink_rect(*selector, style->border), background->data.color); + } else nk_draw_image(out, *selector, &background->data.image, nk_white); + if (active) { + if (cursor->type == NK_STYLE_ITEM_IMAGE) + nk_draw_image(out, *cursors, &cursor->data.image, nk_white); + else nk_fill_circle(out, *cursors, cursor->data.color); + } + + text.padding.x = 0; + text.padding.y = 0; + text.background = style->text_background; + nk_widget_text(out, *label, string, len, &text, NK_TEXT_LEFT, font); +} +NK_LIB nk_bool +nk_do_toggle(nk_flags *state, + struct nk_command_buffer *out, struct nk_rect r, + nk_bool *active, const char *str, int len, enum nk_toggle_type type, + const struct nk_style_toggle *style, const struct nk_input *in, + const struct nk_user_font *font) +{ + int was_active; + struct nk_rect bounds; + struct nk_rect select; + struct nk_rect cursor; + struct nk_rect label; + + NK_ASSERT(style); + NK_ASSERT(out); + NK_ASSERT(font); + if (!out || !style || !font || !active) + return 0; + + r.w = NK_MAX(r.w, font->height + 2 * style->padding.x); + r.h = NK_MAX(r.h, font->height + 2 * style->padding.y); + + /* add additional touch padding for touch screen devices */ + bounds.x = r.x - style->touch_padding.x; + bounds.y = r.y - style->touch_padding.y; + bounds.w = r.w + 2 * style->touch_padding.x; + bounds.h = r.h + 2 * style->touch_padding.y; + + /* calculate the selector space */ + select.w = font->height; + select.h = select.w; + select.y = r.y + r.h/2.0f - select.h/2.0f; + select.x = r.x; + + /* calculate the bounds of the cursor inside the selector */ + cursor.x = select.x + style->padding.x + style->border; + cursor.y = select.y + style->padding.y + style->border; + cursor.w = select.w - (2 * style->padding.x + 2 * style->border); + cursor.h = select.h - (2 * style->padding.y + 2 * style->border); + + /* label behind the selector */ + label.x = select.x + select.w + style->spacing; + label.y = select.y; + label.w = NK_MAX(r.x + r.w, label.x) - label.x; + label.h = select.w; + + /* update selector */ + was_active = *active; + *active = nk_toggle_behavior(in, bounds, state, *active); + + /* draw selector */ + if (style->draw_begin) + style->draw_begin(out, style->userdata); + if (type == NK_TOGGLE_CHECK) { + nk_draw_checkbox(out, *state, style, *active, &label, &select, &cursor, str, len, font); + } else { + nk_draw_option(out, *state, style, *active, &label, &select, &cursor, str, len, font); + } + if (style->draw_end) + style->draw_end(out, style->userdata); + return (was_active != *active); +} +/*---------------------------------------------------------------- + * + * CHECKBOX + * + * --------------------------------------------------------------*/ +NK_API nk_bool +nk_check_text(struct nk_context *ctx, const char *text, int len, nk_bool active) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_input *in; + const struct nk_style *style; + + struct nk_rect bounds; + enum nk_widget_layout_states state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return active; + + win = ctx->current; + style = &ctx->style; + layout = win->layout; + + state = nk_widget(&bounds, ctx); + if (!state) return active; + in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + nk_do_toggle(&ctx->last_widget_state, &win->buffer, bounds, &active, + text, len, NK_TOGGLE_CHECK, &style->checkbox, in, style->font); + return active; +} +NK_API unsigned int +nk_check_flags_text(struct nk_context *ctx, const char *text, int len, + unsigned int flags, unsigned int value) +{ + int old_active; + NK_ASSERT(ctx); + NK_ASSERT(text); + if (!ctx || !text) return flags; + old_active = (int)((flags & value) & value); + if (nk_check_text(ctx, text, len, old_active)) + flags |= value; + else flags &= ~value; + return flags; +} +NK_API nk_bool +nk_checkbox_text(struct nk_context *ctx, const char *text, int len, nk_bool *active) +{ + int old_val; + NK_ASSERT(ctx); + NK_ASSERT(text); + NK_ASSERT(active); + if (!ctx || !text || !active) return 0; + old_val = *active; + *active = nk_check_text(ctx, text, len, *active); + return old_val != *active; +} +NK_API nk_bool +nk_checkbox_flags_text(struct nk_context *ctx, const char *text, int len, + unsigned int *flags, unsigned int value) +{ + nk_bool active; + NK_ASSERT(ctx); + NK_ASSERT(text); + NK_ASSERT(flags); + if (!ctx || !text || !flags) return 0; + + active = (int)((*flags & value) & value); + if (nk_checkbox_text(ctx, text, len, &active)) { + if (active) *flags |= value; + else *flags &= ~value; + return 1; + } + return 0; +} +NK_API nk_bool nk_check_label(struct nk_context *ctx, const char *label, nk_bool active) +{ + return nk_check_text(ctx, label, nk_strlen(label), active); +} +NK_API unsigned int nk_check_flags_label(struct nk_context *ctx, const char *label, + unsigned int flags, unsigned int value) +{ + return nk_check_flags_text(ctx, label, nk_strlen(label), flags, value); +} +NK_API nk_bool nk_checkbox_label(struct nk_context *ctx, const char *label, nk_bool *active) +{ + return nk_checkbox_text(ctx, label, nk_strlen(label), active); +} +NK_API nk_bool nk_checkbox_flags_label(struct nk_context *ctx, const char *label, + unsigned int *flags, unsigned int value) +{ + return nk_checkbox_flags_text(ctx, label, nk_strlen(label), flags, value); +} +/*---------------------------------------------------------------- + * + * OPTION + * + * --------------------------------------------------------------*/ +NK_API nk_bool +nk_option_text(struct nk_context *ctx, const char *text, int len, nk_bool is_active) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_input *in; + const struct nk_style *style; + + struct nk_rect bounds; + enum nk_widget_layout_states state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return is_active; + + win = ctx->current; + style = &ctx->style; + layout = win->layout; + + state = nk_widget(&bounds, ctx); + if (!state) return (int)state; + in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + nk_do_toggle(&ctx->last_widget_state, &win->buffer, bounds, &is_active, + text, len, NK_TOGGLE_OPTION, &style->option, in, style->font); + return is_active; +} +NK_API nk_bool +nk_radio_text(struct nk_context *ctx, const char *text, int len, nk_bool *active) +{ + int old_value; + NK_ASSERT(ctx); + NK_ASSERT(text); + NK_ASSERT(active); + if (!ctx || !text || !active) return 0; + old_value = *active; + *active = nk_option_text(ctx, text, len, old_value); + return old_value != *active; +} +NK_API nk_bool +nk_option_label(struct nk_context *ctx, const char *label, nk_bool active) +{ + return nk_option_text(ctx, label, nk_strlen(label), active); +} +NK_API nk_bool +nk_radio_label(struct nk_context *ctx, const char *label, nk_bool *active) +{ + return nk_radio_text(ctx, label, nk_strlen(label), active); +} + + + + + +/* =============================================================== + * + * SELECTABLE + * + * ===============================================================*/ +NK_LIB void +nk_draw_selectable(struct nk_command_buffer *out, + nk_flags state, const struct nk_style_selectable *style, nk_bool active, + const struct nk_rect *bounds, + const struct nk_rect *icon, const struct nk_image *img, enum nk_symbol_type sym, + const char *string, int len, nk_flags align, const struct nk_user_font *font) +{ + const struct nk_style_item *background; + struct nk_text text; + text.padding = style->padding; + + /* select correct colors/images */ + if (!active) { + if (state & NK_WIDGET_STATE_ACTIVED) { + background = &style->pressed; + text.text = style->text_pressed; + } else if (state & NK_WIDGET_STATE_HOVER) { + background = &style->hover; + text.text = style->text_hover; + } else { + background = &style->normal; + text.text = style->text_normal; + } + } else { + if (state & NK_WIDGET_STATE_ACTIVED) { + background = &style->pressed_active; + text.text = style->text_pressed_active; + } else if (state & NK_WIDGET_STATE_HOVER) { + background = &style->hover_active; + text.text = style->text_hover_active; + } else { + background = &style->normal_active; + text.text = style->text_normal_active; + } + } + /* draw selectable background and text */ + if (background->type == NK_STYLE_ITEM_IMAGE) { + nk_draw_image(out, *bounds, &background->data.image, nk_white); + text.background = nk_rgba(0,0,0,0); + } else { + nk_fill_rect(out, *bounds, style->rounding, background->data.color); + text.background = background->data.color; + } + if (icon) { + if (img) nk_draw_image(out, *icon, img, nk_white); + else nk_draw_symbol(out, sym, *icon, text.background, text.text, 1, font); + } + nk_widget_text(out, *bounds, string, len, &text, align, font); +} +NK_LIB nk_bool +nk_do_selectable(nk_flags *state, struct nk_command_buffer *out, + struct nk_rect bounds, const char *str, int len, nk_flags align, nk_bool *value, + const struct nk_style_selectable *style, const struct nk_input *in, + const struct nk_user_font *font) +{ + int old_value; + struct nk_rect touch; + + NK_ASSERT(state); + NK_ASSERT(out); + NK_ASSERT(str); + NK_ASSERT(len); + NK_ASSERT(value); + NK_ASSERT(style); + NK_ASSERT(font); + + if (!state || !out || !str || !len || !value || !style || !font) return 0; + old_value = *value; + + /* remove padding */ + touch.x = bounds.x - style->touch_padding.x; + touch.y = bounds.y - style->touch_padding.y; + touch.w = bounds.w + style->touch_padding.x * 2; + touch.h = bounds.h + style->touch_padding.y * 2; + + /* update button */ + if (nk_button_behavior(state, touch, in, NK_BUTTON_DEFAULT)) + *value = !(*value); + + /* draw selectable */ + if (style->draw_begin) style->draw_begin(out, style->userdata); + nk_draw_selectable(out, *state, style, *value, &bounds, 0,0,NK_SYMBOL_NONE, str, len, align, font); + if (style->draw_end) style->draw_end(out, style->userdata); + return old_value != *value; +} +NK_LIB nk_bool +nk_do_selectable_image(nk_flags *state, struct nk_command_buffer *out, + struct nk_rect bounds, const char *str, int len, nk_flags align, nk_bool *value, + const struct nk_image *img, const struct nk_style_selectable *style, + const struct nk_input *in, const struct nk_user_font *font) +{ + nk_bool old_value; + struct nk_rect touch; + struct nk_rect icon; + + NK_ASSERT(state); + NK_ASSERT(out); + NK_ASSERT(str); + NK_ASSERT(len); + NK_ASSERT(value); + NK_ASSERT(style); + NK_ASSERT(font); + + if (!state || !out || !str || !len || !value || !style || !font) return 0; + old_value = *value; + + /* toggle behavior */ + touch.x = bounds.x - style->touch_padding.x; + touch.y = bounds.y - style->touch_padding.y; + touch.w = bounds.w + style->touch_padding.x * 2; + touch.h = bounds.h + style->touch_padding.y * 2; + if (nk_button_behavior(state, touch, in, NK_BUTTON_DEFAULT)) + *value = !(*value); + + icon.y = bounds.y + style->padding.y; + icon.w = icon.h = bounds.h - 2 * style->padding.y; + if (align & NK_TEXT_ALIGN_LEFT) { + icon.x = (bounds.x + bounds.w) - (2 * style->padding.x + icon.w); + icon.x = NK_MAX(icon.x, 0); + } else icon.x = bounds.x + 2 * style->padding.x; + + icon.x += style->image_padding.x; + icon.y += style->image_padding.y; + icon.w -= 2 * style->image_padding.x; + icon.h -= 2 * style->image_padding.y; + + /* draw selectable */ + if (style->draw_begin) style->draw_begin(out, style->userdata); + nk_draw_selectable(out, *state, style, *value, &bounds, &icon, img, NK_SYMBOL_NONE, str, len, align, font); + if (style->draw_end) style->draw_end(out, style->userdata); + return old_value != *value; +} +NK_LIB nk_bool +nk_do_selectable_symbol(nk_flags *state, struct nk_command_buffer *out, + struct nk_rect bounds, const char *str, int len, nk_flags align, nk_bool *value, + enum nk_symbol_type sym, const struct nk_style_selectable *style, + const struct nk_input *in, const struct nk_user_font *font) +{ + int old_value; + struct nk_rect touch; + struct nk_rect icon; + + NK_ASSERT(state); + NK_ASSERT(out); + NK_ASSERT(str); + NK_ASSERT(len); + NK_ASSERT(value); + NK_ASSERT(style); + NK_ASSERT(font); + + if (!state || !out || !str || !len || !value || !style || !font) return 0; + old_value = *value; + + /* toggle behavior */ + touch.x = bounds.x - style->touch_padding.x; + touch.y = bounds.y - style->touch_padding.y; + touch.w = bounds.w + style->touch_padding.x * 2; + touch.h = bounds.h + style->touch_padding.y * 2; + if (nk_button_behavior(state, touch, in, NK_BUTTON_DEFAULT)) + *value = !(*value); + + icon.y = bounds.y + style->padding.y; + icon.w = icon.h = bounds.h - 2 * style->padding.y; + if (align & NK_TEXT_ALIGN_LEFT) { + icon.x = (bounds.x + bounds.w) - (2 * style->padding.x + icon.w); + icon.x = NK_MAX(icon.x, 0); + } else icon.x = bounds.x + 2 * style->padding.x; + + icon.x += style->image_padding.x; + icon.y += style->image_padding.y; + icon.w -= 2 * style->image_padding.x; + icon.h -= 2 * style->image_padding.y; + + /* draw selectable */ + if (style->draw_begin) style->draw_begin(out, style->userdata); + nk_draw_selectable(out, *state, style, *value, &bounds, &icon, 0, sym, str, len, align, font); + if (style->draw_end) style->draw_end(out, style->userdata); + return old_value != *value; +} + +NK_API nk_bool +nk_selectable_text(struct nk_context *ctx, const char *str, int len, + nk_flags align, nk_bool *value) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_input *in; + const struct nk_style *style; + + enum nk_widget_layout_states state; + struct nk_rect bounds; + + NK_ASSERT(ctx); + NK_ASSERT(value); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout || !value) + return 0; + + win = ctx->current; + layout = win->layout; + style = &ctx->style; + + state = nk_widget(&bounds, ctx); + if (!state) return 0; + in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + return nk_do_selectable(&ctx->last_widget_state, &win->buffer, bounds, + str, len, align, value, &style->selectable, in, style->font); +} +NK_API nk_bool +nk_selectable_image_text(struct nk_context *ctx, struct nk_image img, + const char *str, int len, nk_flags align, nk_bool *value) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_input *in; + const struct nk_style *style; + + enum nk_widget_layout_states state; + struct nk_rect bounds; + + NK_ASSERT(ctx); + NK_ASSERT(value); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout || !value) + return 0; + + win = ctx->current; + layout = win->layout; + style = &ctx->style; + + state = nk_widget(&bounds, ctx); + if (!state) return 0; + in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + return nk_do_selectable_image(&ctx->last_widget_state, &win->buffer, bounds, + str, len, align, value, &img, &style->selectable, in, style->font); +} +NK_API nk_bool +nk_selectable_symbol_text(struct nk_context *ctx, enum nk_symbol_type sym, + const char *str, int len, nk_flags align, nk_bool *value) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_input *in; + const struct nk_style *style; + + enum nk_widget_layout_states state; + struct nk_rect bounds; + + NK_ASSERT(ctx); + NK_ASSERT(value); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout || !value) + return 0; + + win = ctx->current; + layout = win->layout; + style = &ctx->style; + + state = nk_widget(&bounds, ctx); + if (!state) return 0; + in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + return nk_do_selectable_symbol(&ctx->last_widget_state, &win->buffer, bounds, + str, len, align, value, sym, &style->selectable, in, style->font); +} +NK_API nk_bool +nk_selectable_symbol_label(struct nk_context *ctx, enum nk_symbol_type sym, + const char *title, nk_flags align, nk_bool *value) +{ + return nk_selectable_symbol_text(ctx, sym, title, nk_strlen(title), align, value); +} +NK_API nk_bool nk_select_text(struct nk_context *ctx, const char *str, int len, + nk_flags align, nk_bool value) +{ + nk_selectable_text(ctx, str, len, align, &value);return value; +} +NK_API nk_bool nk_selectable_label(struct nk_context *ctx, const char *str, nk_flags align, nk_bool *value) +{ + return nk_selectable_text(ctx, str, nk_strlen(str), align, value); +} +NK_API nk_bool nk_selectable_image_label(struct nk_context *ctx,struct nk_image img, + const char *str, nk_flags align, nk_bool *value) +{ + return nk_selectable_image_text(ctx, img, str, nk_strlen(str), align, value); +} +NK_API nk_bool nk_select_label(struct nk_context *ctx, const char *str, nk_flags align, nk_bool value) +{ + nk_selectable_text(ctx, str, nk_strlen(str), align, &value);return value; +} +NK_API nk_bool nk_select_image_label(struct nk_context *ctx, struct nk_image img, + const char *str, nk_flags align, nk_bool value) +{ + nk_selectable_image_text(ctx, img, str, nk_strlen(str), align, &value);return value; +} +NK_API nk_bool nk_select_image_text(struct nk_context *ctx, struct nk_image img, + const char *str, int len, nk_flags align, nk_bool value) +{ + nk_selectable_image_text(ctx, img, str, len, align, &value);return value; +} +NK_API nk_bool +nk_select_symbol_text(struct nk_context *ctx, enum nk_symbol_type sym, + const char *title, int title_len, nk_flags align, nk_bool value) +{ + nk_selectable_symbol_text(ctx, sym, title, title_len, align, &value);return value; +} +NK_API nk_bool +nk_select_symbol_label(struct nk_context *ctx, enum nk_symbol_type sym, + const char *title, nk_flags align, nk_bool value) +{ + return nk_select_symbol_text(ctx, sym, title, nk_strlen(title), align, value); +} + + + + + +/* =============================================================== + * + * SLIDER + * + * ===============================================================*/ +NK_LIB float +nk_slider_behavior(nk_flags *state, struct nk_rect *logical_cursor, + struct nk_rect *visual_cursor, struct nk_input *in, + struct nk_rect bounds, float slider_min, float slider_max, float slider_value, + float slider_step, float slider_steps) +{ + int left_mouse_down; + int left_mouse_click_in_cursor; + + /* check if visual cursor is being dragged */ + nk_widget_state_reset(state); + left_mouse_down = in && in->mouse.buttons[NK_BUTTON_LEFT].down; + left_mouse_click_in_cursor = in && nk_input_has_mouse_click_down_in_rect(in, + NK_BUTTON_LEFT, *visual_cursor, nk_true); + + if (left_mouse_down && left_mouse_click_in_cursor) { + float ratio = 0; + const float d = in->mouse.pos.x - (visual_cursor->x+visual_cursor->w*0.5f); + const float pxstep = bounds.w / slider_steps; + + /* only update value if the next slider step is reached */ + *state = NK_WIDGET_STATE_ACTIVE; + if (NK_ABS(d) >= pxstep) { + const float steps = (float)((int)(NK_ABS(d) / pxstep)); + slider_value += (d > 0) ? (slider_step*steps) : -(slider_step*steps); + slider_value = NK_CLAMP(slider_min, slider_value, slider_max); + ratio = (slider_value - slider_min)/slider_step; + logical_cursor->x = bounds.x + (logical_cursor->w * ratio); + in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.x = logical_cursor->x; + } + } + + /* slider widget state */ + if (nk_input_is_mouse_hovering_rect(in, bounds)) + *state = NK_WIDGET_STATE_HOVERED; + if (*state & NK_WIDGET_STATE_HOVER && + !nk_input_is_mouse_prev_hovering_rect(in, bounds)) + *state |= NK_WIDGET_STATE_ENTERED; + else if (nk_input_is_mouse_prev_hovering_rect(in, bounds)) + *state |= NK_WIDGET_STATE_LEFT; + return slider_value; +} +NK_LIB void +nk_draw_slider(struct nk_command_buffer *out, nk_flags state, + const struct nk_style_slider *style, const struct nk_rect *bounds, + const struct nk_rect *visual_cursor, float min, float value, float max) +{ + struct nk_rect fill; + struct nk_rect bar; + const struct nk_style_item *background; + + /* select correct slider images/colors */ + struct nk_color bar_color; + const struct nk_style_item *cursor; + + NK_UNUSED(min); + NK_UNUSED(max); + NK_UNUSED(value); + + if (state & NK_WIDGET_STATE_ACTIVED) { + background = &style->active; + bar_color = style->bar_active; + cursor = &style->cursor_active; + } else if (state & NK_WIDGET_STATE_HOVER) { + background = &style->hover; + bar_color = style->bar_hover; + cursor = &style->cursor_hover; + } else { + background = &style->normal; + bar_color = style->bar_normal; + cursor = &style->cursor_normal; + } + /* calculate slider background bar */ + bar.x = bounds->x; + bar.y = (visual_cursor->y + visual_cursor->h/2) - bounds->h/12; + bar.w = bounds->w; + bar.h = bounds->h/6; + + /* filled background bar style */ + fill.w = (visual_cursor->x + (visual_cursor->w/2.0f)) - bar.x; + fill.x = bar.x; + fill.y = bar.y; + fill.h = bar.h; + + /* draw background */ + if (background->type == NK_STYLE_ITEM_IMAGE) { + nk_draw_image(out, *bounds, &background->data.image, nk_white); + } else { + nk_fill_rect(out, *bounds, style->rounding, background->data.color); + nk_stroke_rect(out, *bounds, style->rounding, style->border, style->border_color); + } + + /* draw slider bar */ + nk_fill_rect(out, bar, style->rounding, bar_color); + nk_fill_rect(out, fill, style->rounding, style->bar_filled); + + /* draw cursor */ + if (cursor->type == NK_STYLE_ITEM_IMAGE) + nk_draw_image(out, *visual_cursor, &cursor->data.image, nk_white); + else nk_fill_circle(out, *visual_cursor, cursor->data.color); +} +NK_LIB float +nk_do_slider(nk_flags *state, + struct nk_command_buffer *out, struct nk_rect bounds, + float min, float val, float max, float step, + const struct nk_style_slider *style, struct nk_input *in, + const struct nk_user_font *font) +{ + float slider_range; + float slider_min; + float slider_max; + float slider_value; + float slider_steps; + float cursor_offset; + + struct nk_rect visual_cursor; + struct nk_rect logical_cursor; + + NK_ASSERT(style); + NK_ASSERT(out); + if (!out || !style) + return 0; + + /* remove padding from slider bounds */ + bounds.x = bounds.x + style->padding.x; + bounds.y = bounds.y + style->padding.y; + bounds.h = NK_MAX(bounds.h, 2*style->padding.y); + bounds.w = NK_MAX(bounds.w, 2*style->padding.x + style->cursor_size.x); + bounds.w -= 2 * style->padding.x; + bounds.h -= 2 * style->padding.y; + + /* optional buttons */ + if (style->show_buttons) { + nk_flags ws; + struct nk_rect button; + button.y = bounds.y; + button.w = bounds.h; + button.h = bounds.h; + + /* decrement button */ + button.x = bounds.x; + if (nk_do_button_symbol(&ws, out, button, style->dec_symbol, NK_BUTTON_DEFAULT, + &style->dec_button, in, font)) + val -= step; + + /* increment button */ + button.x = (bounds.x + bounds.w) - button.w; + if (nk_do_button_symbol(&ws, out, button, style->inc_symbol, NK_BUTTON_DEFAULT, + &style->inc_button, in, font)) + val += step; + + bounds.x = bounds.x + button.w + style->spacing.x; + bounds.w = bounds.w - (2*button.w + 2*style->spacing.x); + } + + /* remove one cursor size to support visual cursor */ + bounds.x += style->cursor_size.x*0.5f; + bounds.w -= style->cursor_size.x; + + /* make sure the provided values are correct */ + slider_max = NK_MAX(min, max); + slider_min = NK_MIN(min, max); + slider_value = NK_CLAMP(slider_min, val, slider_max); + slider_range = slider_max - slider_min; + slider_steps = slider_range / step; + cursor_offset = (slider_value - slider_min) / step; + + /* calculate cursor + Basically you have two cursors. One for visual representation and interaction + and one for updating the actual cursor value. */ + logical_cursor.h = bounds.h; + logical_cursor.w = bounds.w / slider_steps; + logical_cursor.x = bounds.x + (logical_cursor.w * cursor_offset); + logical_cursor.y = bounds.y; + + visual_cursor.h = style->cursor_size.y; + visual_cursor.w = style->cursor_size.x; + visual_cursor.y = (bounds.y + bounds.h*0.5f) - visual_cursor.h*0.5f; + visual_cursor.x = logical_cursor.x - visual_cursor.w*0.5f; + + slider_value = nk_slider_behavior(state, &logical_cursor, &visual_cursor, + in, bounds, slider_min, slider_max, slider_value, step, slider_steps); + visual_cursor.x = logical_cursor.x - visual_cursor.w*0.5f; + + /* draw slider */ + if (style->draw_begin) style->draw_begin(out, style->userdata); + nk_draw_slider(out, *state, style, &bounds, &visual_cursor, slider_min, slider_value, slider_max); + if (style->draw_end) style->draw_end(out, style->userdata); + return slider_value; +} +NK_API nk_bool +nk_slider_float(struct nk_context *ctx, float min_value, float *value, float max_value, + float value_step) +{ + struct nk_window *win; + struct nk_panel *layout; + struct nk_input *in; + const struct nk_style *style; + + int ret = 0; + float old_value; + struct nk_rect bounds; + enum nk_widget_layout_states state; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + NK_ASSERT(value); + if (!ctx || !ctx->current || !ctx->current->layout || !value) + return ret; + + win = ctx->current; + style = &ctx->style; + layout = win->layout; + + state = nk_widget(&bounds, ctx); + if (!state) return ret; + in = (/*state == NK_WIDGET_ROM || */ layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + + old_value = *value; + *value = nk_do_slider(&ctx->last_widget_state, &win->buffer, bounds, min_value, + old_value, max_value, value_step, &style->slider, in, style->font); + return (old_value > *value || old_value < *value); +} +NK_API float +nk_slide_float(struct nk_context *ctx, float min, float val, float max, float step) +{ + nk_slider_float(ctx, min, &val, max, step); return val; +} +NK_API int +nk_slide_int(struct nk_context *ctx, int min, int val, int max, int step) +{ + float value = (float)val; + nk_slider_float(ctx, (float)min, &value, (float)max, (float)step); + return (int)value; +} +NK_API nk_bool +nk_slider_int(struct nk_context *ctx, int min, int *val, int max, int step) +{ + int ret; + float value = (float)*val; + ret = nk_slider_float(ctx, (float)min, &value, (float)max, (float)step); + *val = (int)value; + return ret; +} + + + + + +/* =============================================================== + * + * PROGRESS + * + * ===============================================================*/ +NK_LIB nk_size +nk_progress_behavior(nk_flags *state, struct nk_input *in, + struct nk_rect r, struct nk_rect cursor, nk_size max, nk_size value, nk_bool modifiable) +{ + int left_mouse_down = 0; + int left_mouse_click_in_cursor = 0; + + nk_widget_state_reset(state); + if (!in || !modifiable) return value; + left_mouse_down = in && in->mouse.buttons[NK_BUTTON_LEFT].down; + left_mouse_click_in_cursor = in && nk_input_has_mouse_click_down_in_rect(in, + NK_BUTTON_LEFT, cursor, nk_true); + if (nk_input_is_mouse_hovering_rect(in, r)) + *state = NK_WIDGET_STATE_HOVERED; + + if (in && left_mouse_down && left_mouse_click_in_cursor) { + if (left_mouse_down && left_mouse_click_in_cursor) { + float ratio = NK_MAX(0, (float)(in->mouse.pos.x - cursor.x)) / (float)cursor.w; + value = (nk_size)NK_CLAMP(0, (float)max * ratio, (float)max); + in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.x = cursor.x + cursor.w/2.0f; + *state |= NK_WIDGET_STATE_ACTIVE; + } + } + /* set progressbar widget state */ + if (*state & NK_WIDGET_STATE_HOVER && !nk_input_is_mouse_prev_hovering_rect(in, r)) + *state |= NK_WIDGET_STATE_ENTERED; + else if (nk_input_is_mouse_prev_hovering_rect(in, r)) + *state |= NK_WIDGET_STATE_LEFT; + return value; +} +NK_LIB void +nk_draw_progress(struct nk_command_buffer *out, nk_flags state, + const struct nk_style_progress *style, const struct nk_rect *bounds, + const struct nk_rect *scursor, nk_size value, nk_size max) +{ + const struct nk_style_item *background; + const struct nk_style_item *cursor; + + NK_UNUSED(max); + NK_UNUSED(value); + + /* select correct colors/images to draw */ + if (state & NK_WIDGET_STATE_ACTIVED) { + background = &style->active; + cursor = &style->cursor_active; + } else if (state & NK_WIDGET_STATE_HOVER){ + background = &style->hover; + cursor = &style->cursor_hover; + } else { + background = &style->normal; + cursor = &style->cursor_normal; + } + + /* draw background */ + if (background->type == NK_STYLE_ITEM_COLOR) { + nk_fill_rect(out, *bounds, style->rounding, background->data.color); + nk_stroke_rect(out, *bounds, style->rounding, style->border, style->border_color); + } else nk_draw_image(out, *bounds, &background->data.image, nk_white); + + /* draw cursor */ + if (cursor->type == NK_STYLE_ITEM_COLOR) { + nk_fill_rect(out, *scursor, style->rounding, cursor->data.color); + nk_stroke_rect(out, *scursor, style->rounding, style->border, style->border_color); + } else nk_draw_image(out, *scursor, &cursor->data.image, nk_white); +} +NK_LIB nk_size +nk_do_progress(nk_flags *state, + struct nk_command_buffer *out, struct nk_rect bounds, + nk_size value, nk_size max, nk_bool modifiable, + const struct nk_style_progress *style, struct nk_input *in) +{ + float prog_scale; + nk_size prog_value; + struct nk_rect cursor; + + NK_ASSERT(style); + NK_ASSERT(out); + if (!out || !style) return 0; + + /* calculate progressbar cursor */ + cursor.w = NK_MAX(bounds.w, 2 * style->padding.x + 2 * style->border); + cursor.h = NK_MAX(bounds.h, 2 * style->padding.y + 2 * style->border); + cursor = nk_pad_rect(bounds, nk_vec2(style->padding.x + style->border, style->padding.y + style->border)); + prog_scale = (float)value / (float)max; + + /* update progressbar */ + prog_value = NK_MIN(value, max); + prog_value = nk_progress_behavior(state, in, bounds, cursor,max, prog_value, modifiable); + cursor.w = cursor.w * prog_scale; + + /* draw progressbar */ + if (style->draw_begin) style->draw_begin(out, style->userdata); + nk_draw_progress(out, *state, style, &bounds, &cursor, value, max); + if (style->draw_end) style->draw_end(out, style->userdata); + return prog_value; +} +NK_API nk_bool +nk_progress(struct nk_context *ctx, nk_size *cur, nk_size max, nk_bool is_modifyable) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_style *style; + struct nk_input *in; + + struct nk_rect bounds; + enum nk_widget_layout_states state; + nk_size old_value; + + NK_ASSERT(ctx); + NK_ASSERT(cur); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout || !cur) + return 0; + + win = ctx->current; + style = &ctx->style; + layout = win->layout; + state = nk_widget(&bounds, ctx); + if (!state) return 0; + + in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + old_value = *cur; + *cur = nk_do_progress(&ctx->last_widget_state, &win->buffer, bounds, + *cur, max, is_modifyable, &style->progress, in); + return (*cur != old_value); +} +NK_API nk_size +nk_prog(struct nk_context *ctx, nk_size cur, nk_size max, nk_bool modifyable) +{ + nk_progress(ctx, &cur, max, modifyable); + return cur; +} + + + + + +/* =============================================================== + * + * SCROLLBAR + * + * ===============================================================*/ +NK_LIB float +nk_scrollbar_behavior(nk_flags *state, struct nk_input *in, + int has_scrolling, const struct nk_rect *scroll, + const struct nk_rect *cursor, const struct nk_rect *empty0, + const struct nk_rect *empty1, float scroll_offset, + float target, float scroll_step, enum nk_orientation o) +{ + nk_flags ws = 0; + int left_mouse_down; + int left_mouse_clicked; + int left_mouse_click_in_cursor; + float scroll_delta; + + nk_widget_state_reset(state); + if (!in) return scroll_offset; + + left_mouse_down = in->mouse.buttons[NK_BUTTON_LEFT].down; + left_mouse_clicked = in->mouse.buttons[NK_BUTTON_LEFT].clicked; + left_mouse_click_in_cursor = nk_input_has_mouse_click_down_in_rect(in, + NK_BUTTON_LEFT, *cursor, nk_true); + if (nk_input_is_mouse_hovering_rect(in, *scroll)) + *state = NK_WIDGET_STATE_HOVERED; + + scroll_delta = (o == NK_VERTICAL) ? in->mouse.scroll_delta.y: in->mouse.scroll_delta.x; + if (left_mouse_down && left_mouse_click_in_cursor && !left_mouse_clicked) { + /* update cursor by mouse dragging */ + float pixel, delta; + *state = NK_WIDGET_STATE_ACTIVE; + if (o == NK_VERTICAL) { + float cursor_y; + pixel = in->mouse.delta.y; + delta = (pixel / scroll->h) * target; + scroll_offset = NK_CLAMP(0, scroll_offset + delta, target - scroll->h); + cursor_y = scroll->y + ((scroll_offset/target) * scroll->h); + in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.y = cursor_y + cursor->h/2.0f; + } else { + float cursor_x; + pixel = in->mouse.delta.x; + delta = (pixel / scroll->w) * target; + scroll_offset = NK_CLAMP(0, scroll_offset + delta, target - scroll->w); + cursor_x = scroll->x + ((scroll_offset/target) * scroll->w); + in->mouse.buttons[NK_BUTTON_LEFT].clicked_pos.x = cursor_x + cursor->w/2.0f; + } + } else if ((nk_input_is_key_pressed(in, NK_KEY_SCROLL_UP) && o == NK_VERTICAL && has_scrolling)|| + nk_button_behavior(&ws, *empty0, in, NK_BUTTON_DEFAULT)) { + /* scroll page up by click on empty space or shortcut */ + if (o == NK_VERTICAL) + scroll_offset = NK_MAX(0, scroll_offset - scroll->h); + else scroll_offset = NK_MAX(0, scroll_offset - scroll->w); + } else if ((nk_input_is_key_pressed(in, NK_KEY_SCROLL_DOWN) && o == NK_VERTICAL && has_scrolling) || + nk_button_behavior(&ws, *empty1, in, NK_BUTTON_DEFAULT)) { + /* scroll page down by click on empty space or shortcut */ + if (o == NK_VERTICAL) + scroll_offset = NK_MIN(scroll_offset + scroll->h, target - scroll->h); + else scroll_offset = NK_MIN(scroll_offset + scroll->w, target - scroll->w); + } else if (has_scrolling) { + if ((scroll_delta < 0 || (scroll_delta > 0))) { + /* update cursor by mouse scrolling */ + scroll_offset = scroll_offset + scroll_step * (-scroll_delta); + if (o == NK_VERTICAL) + scroll_offset = NK_CLAMP(0, scroll_offset, target - scroll->h); + else scroll_offset = NK_CLAMP(0, scroll_offset, target - scroll->w); + } else if (nk_input_is_key_pressed(in, NK_KEY_SCROLL_START)) { + /* update cursor to the beginning */ + if (o == NK_VERTICAL) scroll_offset = 0; + } else if (nk_input_is_key_pressed(in, NK_KEY_SCROLL_END)) { + /* update cursor to the end */ + if (o == NK_VERTICAL) scroll_offset = target - scroll->h; + } + } + if (*state & NK_WIDGET_STATE_HOVER && !nk_input_is_mouse_prev_hovering_rect(in, *scroll)) + *state |= NK_WIDGET_STATE_ENTERED; + else if (nk_input_is_mouse_prev_hovering_rect(in, *scroll)) + *state |= NK_WIDGET_STATE_LEFT; + return scroll_offset; +} +NK_LIB void +nk_draw_scrollbar(struct nk_command_buffer *out, nk_flags state, + const struct nk_style_scrollbar *style, const struct nk_rect *bounds, + const struct nk_rect *scroll) +{ + const struct nk_style_item *background; + const struct nk_style_item *cursor; + + /* select correct colors/images to draw */ + if (state & NK_WIDGET_STATE_ACTIVED) { + background = &style->active; + cursor = &style->cursor_active; + } else if (state & NK_WIDGET_STATE_HOVER) { + background = &style->hover; + cursor = &style->cursor_hover; + } else { + background = &style->normal; + cursor = &style->cursor_normal; + } + + /* draw background */ + if (background->type == NK_STYLE_ITEM_COLOR) { + nk_fill_rect(out, *bounds, style->rounding, background->data.color); + nk_stroke_rect(out, *bounds, style->rounding, style->border, style->border_color); + } else { + nk_draw_image(out, *bounds, &background->data.image, nk_white); + } + + /* draw cursor */ + if (cursor->type == NK_STYLE_ITEM_COLOR) { + nk_fill_rect(out, *scroll, style->rounding_cursor, cursor->data.color); + nk_stroke_rect(out, *scroll, style->rounding_cursor, style->border_cursor, style->cursor_border_color); + } else nk_draw_image(out, *scroll, &cursor->data.image, nk_white); +} +NK_LIB float +nk_do_scrollbarv(nk_flags *state, + struct nk_command_buffer *out, struct nk_rect scroll, int has_scrolling, + float offset, float target, float step, float button_pixel_inc, + const struct nk_style_scrollbar *style, struct nk_input *in, + const struct nk_user_font *font) +{ + struct nk_rect empty_north; + struct nk_rect empty_south; + struct nk_rect cursor; + + float scroll_step; + float scroll_offset; + float scroll_off; + float scroll_ratio; + + NK_ASSERT(out); + NK_ASSERT(style); + NK_ASSERT(state); + if (!out || !style) return 0; + + scroll.w = NK_MAX(scroll.w, 1); + scroll.h = NK_MAX(scroll.h, 0); + if (target <= scroll.h) return 0; + + /* optional scrollbar buttons */ + if (style->show_buttons) { + nk_flags ws; + float scroll_h; + struct nk_rect button; + + button.x = scroll.x; + button.w = scroll.w; + button.h = scroll.w; + + scroll_h = NK_MAX(scroll.h - 2 * button.h,0); + scroll_step = NK_MIN(step, button_pixel_inc); + + /* decrement button */ + button.y = scroll.y; + if (nk_do_button_symbol(&ws, out, button, style->dec_symbol, + NK_BUTTON_REPEATER, &style->dec_button, in, font)) + offset = offset - scroll_step; + + /* increment button */ + button.y = scroll.y + scroll.h - button.h; + if (nk_do_button_symbol(&ws, out, button, style->inc_symbol, + NK_BUTTON_REPEATER, &style->inc_button, in, font)) + offset = offset + scroll_step; + + scroll.y = scroll.y + button.h; + scroll.h = scroll_h; + } + + /* calculate scrollbar constants */ + scroll_step = NK_MIN(step, scroll.h); + scroll_offset = NK_CLAMP(0, offset, target - scroll.h); + scroll_ratio = scroll.h / target; + scroll_off = scroll_offset / target; + + /* calculate scrollbar cursor bounds */ + cursor.h = NK_MAX((scroll_ratio * scroll.h) - (2*style->border + 2*style->padding.y), 0); + cursor.y = scroll.y + (scroll_off * scroll.h) + style->border + style->padding.y; + cursor.w = scroll.w - (2 * style->border + 2 * style->padding.x); + cursor.x = scroll.x + style->border + style->padding.x; + + /* calculate empty space around cursor */ + empty_north.x = scroll.x; + empty_north.y = scroll.y; + empty_north.w = scroll.w; + empty_north.h = NK_MAX(cursor.y - scroll.y, 0); + + empty_south.x = scroll.x; + empty_south.y = cursor.y + cursor.h; + empty_south.w = scroll.w; + empty_south.h = NK_MAX((scroll.y + scroll.h) - (cursor.y + cursor.h), 0); + + /* update scrollbar */ + scroll_offset = nk_scrollbar_behavior(state, in, has_scrolling, &scroll, &cursor, + &empty_north, &empty_south, scroll_offset, target, scroll_step, NK_VERTICAL); + scroll_off = scroll_offset / target; + cursor.y = scroll.y + (scroll_off * scroll.h) + style->border_cursor + style->padding.y; + + /* draw scrollbar */ + if (style->draw_begin) style->draw_begin(out, style->userdata); + nk_draw_scrollbar(out, *state, style, &scroll, &cursor); + if (style->draw_end) style->draw_end(out, style->userdata); + return scroll_offset; +} +NK_LIB float +nk_do_scrollbarh(nk_flags *state, + struct nk_command_buffer *out, struct nk_rect scroll, int has_scrolling, + float offset, float target, float step, float button_pixel_inc, + const struct nk_style_scrollbar *style, struct nk_input *in, + const struct nk_user_font *font) +{ + struct nk_rect cursor; + struct nk_rect empty_west; + struct nk_rect empty_east; + + float scroll_step; + float scroll_offset; + float scroll_off; + float scroll_ratio; + + NK_ASSERT(out); + NK_ASSERT(style); + if (!out || !style) return 0; + + /* scrollbar background */ + scroll.h = NK_MAX(scroll.h, 1); + scroll.w = NK_MAX(scroll.w, 2 * scroll.h); + if (target <= scroll.w) return 0; + + /* optional scrollbar buttons */ + if (style->show_buttons) { + nk_flags ws; + float scroll_w; + struct nk_rect button; + button.y = scroll.y; + button.w = scroll.h; + button.h = scroll.h; + + scroll_w = scroll.w - 2 * button.w; + scroll_step = NK_MIN(step, button_pixel_inc); + + /* decrement button */ + button.x = scroll.x; + if (nk_do_button_symbol(&ws, out, button, style->dec_symbol, + NK_BUTTON_REPEATER, &style->dec_button, in, font)) + offset = offset - scroll_step; + + /* increment button */ + button.x = scroll.x + scroll.w - button.w; + if (nk_do_button_symbol(&ws, out, button, style->inc_symbol, + NK_BUTTON_REPEATER, &style->inc_button, in, font)) + offset = offset + scroll_step; + + scroll.x = scroll.x + button.w; + scroll.w = scroll_w; + } + + /* calculate scrollbar constants */ + scroll_step = NK_MIN(step, scroll.w); + scroll_offset = NK_CLAMP(0, offset, target - scroll.w); + scroll_ratio = scroll.w / target; + scroll_off = scroll_offset / target; + + /* calculate cursor bounds */ + cursor.w = (scroll_ratio * scroll.w) - (2*style->border + 2*style->padding.x); + cursor.x = scroll.x + (scroll_off * scroll.w) + style->border + style->padding.x; + cursor.h = scroll.h - (2 * style->border + 2 * style->padding.y); + cursor.y = scroll.y + style->border + style->padding.y; + + /* calculate empty space around cursor */ + empty_west.x = scroll.x; + empty_west.y = scroll.y; + empty_west.w = cursor.x - scroll.x; + empty_west.h = scroll.h; + + empty_east.x = cursor.x + cursor.w; + empty_east.y = scroll.y; + empty_east.w = (scroll.x + scroll.w) - (cursor.x + cursor.w); + empty_east.h = scroll.h; + + /* update scrollbar */ + scroll_offset = nk_scrollbar_behavior(state, in, has_scrolling, &scroll, &cursor, + &empty_west, &empty_east, scroll_offset, target, scroll_step, NK_HORIZONTAL); + scroll_off = scroll_offset / target; + cursor.x = scroll.x + (scroll_off * scroll.w); + + /* draw scrollbar */ + if (style->draw_begin) style->draw_begin(out, style->userdata); + nk_draw_scrollbar(out, *state, style, &scroll, &cursor); + if (style->draw_end) style->draw_end(out, style->userdata); + return scroll_offset; +} + + + + + +/* =============================================================== + * + * TEXT EDITOR + * + * ===============================================================*/ +/* stb_textedit.h - v1.8 - public domain - Sean Barrett */ +struct nk_text_find { + float x,y; /* position of n'th character */ + float height; /* height of line */ + int first_char, length; /* first char of row, and length */ + int prev_first; /*_ first char of previous row */ +}; + +struct nk_text_edit_row { + float x0,x1; + /* starting x location, end x location (allows for align=right, etc) */ + float baseline_y_delta; + /* position of baseline relative to previous row's baseline*/ + float ymin,ymax; + /* height of row above and below baseline */ + int num_chars; +}; + +/* forward declarations */ +NK_INTERN void nk_textedit_makeundo_delete(struct nk_text_edit*, int, int); +NK_INTERN void nk_textedit_makeundo_insert(struct nk_text_edit*, int, int); +NK_INTERN void nk_textedit_makeundo_replace(struct nk_text_edit*, int, int, int); +#define NK_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) + +NK_INTERN float +nk_textedit_get_width(const struct nk_text_edit *edit, int line_start, int char_id, + const struct nk_user_font *font) +{ + int len = 0; + nk_rune unicode = 0; + const char *str = nk_str_at_const(&edit->string, line_start + char_id, &unicode, &len); + return font->width(font->userdata, font->height, str, len); +} +NK_INTERN void +nk_textedit_layout_row(struct nk_text_edit_row *r, struct nk_text_edit *edit, + int line_start_id, float row_height, const struct nk_user_font *font) +{ + int l; + int glyphs = 0; + nk_rune unicode; + const char *remaining; + int len = nk_str_len_char(&edit->string); + const char *end = nk_str_get_const(&edit->string) + len; + const char *text = nk_str_at_const(&edit->string, line_start_id, &unicode, &l); + const struct nk_vec2 size = nk_text_calculate_text_bounds(font, + text, (int)(end - text), row_height, &remaining, 0, &glyphs, NK_STOP_ON_NEW_LINE); + + r->x0 = 0.0f; + r->x1 = size.x; + r->baseline_y_delta = size.y; + r->ymin = 0.0f; + r->ymax = size.y; + r->num_chars = glyphs; +} +NK_INTERN int +nk_textedit_locate_coord(struct nk_text_edit *edit, float x, float y, + const struct nk_user_font *font, float row_height) +{ + struct nk_text_edit_row r; + int n = edit->string.len; + float base_y = 0, prev_x; + int i=0, k; + + r.x0 = r.x1 = 0; + r.ymin = r.ymax = 0; + r.num_chars = 0; + + /* search rows to find one that straddles 'y' */ + while (i < n) { + nk_textedit_layout_row(&r, edit, i, row_height, font); + if (r.num_chars <= 0) + return n; + + if (i==0 && y < base_y + r.ymin) + return 0; + + if (y < base_y + r.ymax) + break; + + i += r.num_chars; + base_y += r.baseline_y_delta; + } + + /* below all text, return 'after' last character */ + if (i >= n) + return n; + + /* check if it's before the beginning of the line */ + if (x < r.x0) + return i; + + /* check if it's before the end of the line */ + if (x < r.x1) { + /* search characters in row for one that straddles 'x' */ + k = i; + prev_x = r.x0; + for (i=0; i < r.num_chars; ++i) { + float w = nk_textedit_get_width(edit, k, i, font); + if (x < prev_x+w) { + if (x < prev_x+w/2) + return k+i; + else return k+i+1; + } + prev_x += w; + } + /* shouldn't happen, but if it does, fall through to end-of-line case */ + } + + /* if the last character is a newline, return that. + * otherwise return 'after' the last character */ + if (nk_str_rune_at(&edit->string, i+r.num_chars-1) == '\n') + return i+r.num_chars-1; + else return i+r.num_chars; +} +NK_LIB void +nk_textedit_click(struct nk_text_edit *state, float x, float y, + const struct nk_user_font *font, float row_height) +{ + /* API click: on mouse down, move the cursor to the clicked location, + * and reset the selection */ + state->cursor = nk_textedit_locate_coord(state, x, y, font, row_height); + state->select_start = state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; +} +NK_LIB void +nk_textedit_drag(struct nk_text_edit *state, float x, float y, + const struct nk_user_font *font, float row_height) +{ + /* API drag: on mouse drag, move the cursor and selection endpoint + * to the clicked location */ + int p = nk_textedit_locate_coord(state, x, y, font, row_height); + if (state->select_start == state->select_end) + state->select_start = state->cursor; + state->cursor = state->select_end = p; +} +NK_INTERN void +nk_textedit_find_charpos(struct nk_text_find *find, struct nk_text_edit *state, + int n, int single_line, const struct nk_user_font *font, float row_height) +{ + /* find the x/y location of a character, and remember info about the previous + * row in case we get a move-up event (for page up, we'll have to rescan) */ + struct nk_text_edit_row r; + int prev_start = 0; + int z = state->string.len; + int i=0, first; + + nk_zero_struct(r); + if (n == z) { + /* if it's at the end, then find the last line -- simpler than trying to + explicitly handle this case in the regular code */ + nk_textedit_layout_row(&r, state, 0, row_height, font); + if (single_line) { + find->first_char = 0; + find->length = z; + } else { + while (i < z) { + prev_start = i; + i += r.num_chars; + nk_textedit_layout_row(&r, state, i, row_height, font); + } + + find->first_char = i; + find->length = r.num_chars; + } + find->x = r.x1; + find->y = r.ymin; + find->height = r.ymax - r.ymin; + find->prev_first = prev_start; + return; + } + + /* search rows to find the one that straddles character n */ + find->y = 0; + + for(;;) { + nk_textedit_layout_row(&r, state, i, row_height, font); + if (n < i + r.num_chars) break; + prev_start = i; + i += r.num_chars; + find->y += r.baseline_y_delta; + } + + find->first_char = first = i; + find->length = r.num_chars; + find->height = r.ymax - r.ymin; + find->prev_first = prev_start; + + /* now scan to find xpos */ + find->x = r.x0; + for (i=0; first+i < n; ++i) + find->x += nk_textedit_get_width(state, first, i, font); +} +NK_INTERN void +nk_textedit_clamp(struct nk_text_edit *state) +{ + /* make the selection/cursor state valid if client altered the string */ + int n = state->string.len; + if (NK_TEXT_HAS_SELECTION(state)) { + if (state->select_start > n) state->select_start = n; + if (state->select_end > n) state->select_end = n; + /* if clamping forced them to be equal, move the cursor to match */ + if (state->select_start == state->select_end) + state->cursor = state->select_start; + } + if (state->cursor > n) state->cursor = n; +} +NK_API void +nk_textedit_delete(struct nk_text_edit *state, int where, int len) +{ + /* delete characters while updating undo */ + nk_textedit_makeundo_delete(state, where, len); + nk_str_delete_runes(&state->string, where, len); + state->has_preferred_x = 0; +} +NK_API void +nk_textedit_delete_selection(struct nk_text_edit *state) +{ + /* delete the section */ + nk_textedit_clamp(state); + if (NK_TEXT_HAS_SELECTION(state)) { + if (state->select_start < state->select_end) { + nk_textedit_delete(state, state->select_start, + state->select_end - state->select_start); + state->select_end = state->cursor = state->select_start; + } else { + nk_textedit_delete(state, state->select_end, + state->select_start - state->select_end); + state->select_start = state->cursor = state->select_end; + } + state->has_preferred_x = 0; + } +} +NK_INTERN void +nk_textedit_sortselection(struct nk_text_edit *state) +{ + /* canonicalize the selection so start <= end */ + if (state->select_end < state->select_start) { + int temp = state->select_end; + state->select_end = state->select_start; + state->select_start = temp; + } +} +NK_INTERN void +nk_textedit_move_to_first(struct nk_text_edit *state) +{ + /* move cursor to first character of selection */ + if (NK_TEXT_HAS_SELECTION(state)) { + nk_textedit_sortselection(state); + state->cursor = state->select_start; + state->select_end = state->select_start; + state->has_preferred_x = 0; + } +} +NK_INTERN void +nk_textedit_move_to_last(struct nk_text_edit *state) +{ + /* move cursor to last character of selection */ + if (NK_TEXT_HAS_SELECTION(state)) { + nk_textedit_sortselection(state); + nk_textedit_clamp(state); + state->cursor = state->select_end; + state->select_start = state->select_end; + state->has_preferred_x = 0; + } +} +NK_INTERN int +nk_is_word_boundary( struct nk_text_edit *state, int idx) +{ + int len; + nk_rune c; + if (idx <= 0) return 1; + if (!nk_str_at_rune(&state->string, idx, &c, &len)) return 1; + return (c == ' ' || c == '\t' ||c == 0x3000 || c == ',' || c == ';' || + c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c == ']' || + c == '|'); +} +NK_INTERN int +nk_textedit_move_to_word_previous(struct nk_text_edit *state) +{ + int c = state->cursor - 1; + while( c >= 0 && !nk_is_word_boundary(state, c)) + --c; + + if( c < 0 ) + c = 0; + + return c; +} +NK_INTERN int +nk_textedit_move_to_word_next(struct nk_text_edit *state) +{ + const int len = state->string.len; + int c = state->cursor+1; + while( c < len && !nk_is_word_boundary(state, c)) + ++c; + + if( c > len ) + c = len; + + return c; +} +NK_INTERN void +nk_textedit_prep_selection_at_cursor(struct nk_text_edit *state) +{ + /* update selection and cursor to match each other */ + if (!NK_TEXT_HAS_SELECTION(state)) + state->select_start = state->select_end = state->cursor; + else state->cursor = state->select_end; +} +NK_API nk_bool +nk_textedit_cut(struct nk_text_edit *state) +{ + /* API cut: delete selection */ + if (state->mode == NK_TEXT_EDIT_MODE_VIEW) + return 0; + if (NK_TEXT_HAS_SELECTION(state)) { + nk_textedit_delete_selection(state); /* implicitly clamps */ + state->has_preferred_x = 0; + return 1; + } + return 0; +} +NK_API nk_bool +nk_textedit_paste(struct nk_text_edit *state, char const *ctext, int len) +{ + /* API paste: replace existing selection with passed-in text */ + int glyphs; + const char *text = (const char *) ctext; + if (state->mode == NK_TEXT_EDIT_MODE_VIEW) return 0; + + /* if there's a selection, the paste should delete it */ + nk_textedit_clamp(state); + nk_textedit_delete_selection(state); + + /* try to insert the characters */ + glyphs = nk_utf_len(ctext, len); + if (nk_str_insert_text_char(&state->string, state->cursor, text, len)) { + nk_textedit_makeundo_insert(state, state->cursor, glyphs); + state->cursor += len; + state->has_preferred_x = 0; + return 1; + } + /* remove the undo since we didn't actually insert the characters */ + if (state->undo.undo_point) + --state->undo.undo_point; + return 0; +} +NK_API void +nk_textedit_text(struct nk_text_edit *state, const char *text, int total_len) +{ + nk_rune unicode; + int glyph_len; + int text_len = 0; + + NK_ASSERT(state); + NK_ASSERT(text); + if (!text || !total_len || state->mode == NK_TEXT_EDIT_MODE_VIEW) return; + + glyph_len = nk_utf_decode(text, &unicode, total_len); + while ((text_len < total_len) && glyph_len) + { + /* don't insert a backward delete, just process the event */ + if (unicode == 127) goto next; + /* can't add newline in single-line mode */ + if (unicode == '\n' && state->single_line) goto next; + /* filter incoming text */ + if (state->filter && !state->filter(state, unicode)) goto next; + + if (!NK_TEXT_HAS_SELECTION(state) && + state->cursor < state->string.len) + { + if (state->mode == NK_TEXT_EDIT_MODE_REPLACE) { + nk_textedit_makeundo_replace(state, state->cursor, 1, 1); + nk_str_delete_runes(&state->string, state->cursor, 1); + } + if (nk_str_insert_text_utf8(&state->string, state->cursor, + text+text_len, 1)) + { + ++state->cursor; + state->has_preferred_x = 0; + } + } else { + nk_textedit_delete_selection(state); /* implicitly clamps */ + if (nk_str_insert_text_utf8(&state->string, state->cursor, + text+text_len, 1)) + { + nk_textedit_makeundo_insert(state, state->cursor, 1); + ++state->cursor; + state->has_preferred_x = 0; + } + } + next: + text_len += glyph_len; + glyph_len = nk_utf_decode(text + text_len, &unicode, total_len-text_len); + } +} +NK_LIB void +nk_textedit_key(struct nk_text_edit *state, enum nk_keys key, int shift_mod, + const struct nk_user_font *font, float row_height) +{ +retry: + switch (key) + { + case NK_KEY_NONE: + case NK_KEY_CTRL: + case NK_KEY_ENTER: + case NK_KEY_SHIFT: + case NK_KEY_TAB: + case NK_KEY_COPY: + case NK_KEY_CUT: + case NK_KEY_PASTE: + case NK_KEY_MAX: + default: break; + case NK_KEY_TEXT_UNDO: + nk_textedit_undo(state); + state->has_preferred_x = 0; + break; + + case NK_KEY_TEXT_REDO: + nk_textedit_redo(state); + state->has_preferred_x = 0; + break; + + case NK_KEY_TEXT_SELECT_ALL: + nk_textedit_select_all(state); + state->has_preferred_x = 0; + break; + + case NK_KEY_TEXT_INSERT_MODE: + if (state->mode == NK_TEXT_EDIT_MODE_VIEW) + state->mode = NK_TEXT_EDIT_MODE_INSERT; + break; + case NK_KEY_TEXT_REPLACE_MODE: + if (state->mode == NK_TEXT_EDIT_MODE_VIEW) + state->mode = NK_TEXT_EDIT_MODE_REPLACE; + break; + case NK_KEY_TEXT_RESET_MODE: + if (state->mode == NK_TEXT_EDIT_MODE_INSERT || + state->mode == NK_TEXT_EDIT_MODE_REPLACE) + state->mode = NK_TEXT_EDIT_MODE_VIEW; + break; + + case NK_KEY_LEFT: + if (shift_mod) { + nk_textedit_clamp(state); + nk_textedit_prep_selection_at_cursor(state); + /* move selection left */ + if (state->select_end > 0) + --state->select_end; + state->cursor = state->select_end; + state->has_preferred_x = 0; + } else { + /* if currently there's a selection, + * move cursor to start of selection */ + if (NK_TEXT_HAS_SELECTION(state)) + nk_textedit_move_to_first(state); + else if (state->cursor > 0) + --state->cursor; + state->has_preferred_x = 0; + } break; + + case NK_KEY_RIGHT: + if (shift_mod) { + nk_textedit_prep_selection_at_cursor(state); + /* move selection right */ + ++state->select_end; + nk_textedit_clamp(state); + state->cursor = state->select_end; + state->has_preferred_x = 0; + } else { + /* if currently there's a selection, + * move cursor to end of selection */ + if (NK_TEXT_HAS_SELECTION(state)) + nk_textedit_move_to_last(state); + else ++state->cursor; + nk_textedit_clamp(state); + state->has_preferred_x = 0; + } break; + + case NK_KEY_TEXT_WORD_LEFT: + if (shift_mod) { + if( !NK_TEXT_HAS_SELECTION( state ) ) + nk_textedit_prep_selection_at_cursor(state); + state->cursor = nk_textedit_move_to_word_previous(state); + state->select_end = state->cursor; + nk_textedit_clamp(state ); + } else { + if (NK_TEXT_HAS_SELECTION(state)) + nk_textedit_move_to_first(state); + else { + state->cursor = nk_textedit_move_to_word_previous(state); + nk_textedit_clamp(state ); + } + } break; + + case NK_KEY_TEXT_WORD_RIGHT: + if (shift_mod) { + if( !NK_TEXT_HAS_SELECTION( state ) ) + nk_textedit_prep_selection_at_cursor(state); + state->cursor = nk_textedit_move_to_word_next(state); + state->select_end = state->cursor; + nk_textedit_clamp(state); + } else { + if (NK_TEXT_HAS_SELECTION(state)) + nk_textedit_move_to_last(state); + else { + state->cursor = nk_textedit_move_to_word_next(state); + nk_textedit_clamp(state ); + } + } break; + + case NK_KEY_DOWN: { + struct nk_text_find find; + struct nk_text_edit_row row; + int i, sel = shift_mod; + + if (state->single_line) { + /* on windows, up&down in single-line behave like left&right */ + key = NK_KEY_RIGHT; + goto retry; + } + + if (sel) + nk_textedit_prep_selection_at_cursor(state); + else if (NK_TEXT_HAS_SELECTION(state)) + nk_textedit_move_to_last(state); + + /* compute current position of cursor point */ + nk_textedit_clamp(state); + nk_textedit_find_charpos(&find, state, state->cursor, state->single_line, + font, row_height); + + /* now find character position down a row */ + if (find.length) + { + float x; + float goal_x = state->has_preferred_x ? state->preferred_x : find.x; + int start = find.first_char + find.length; + + state->cursor = start; + nk_textedit_layout_row(&row, state, state->cursor, row_height, font); + x = row.x0; + + for (i=0; i < row.num_chars && x < row.x1; ++i) { + float dx = nk_textedit_get_width(state, start, i, font); + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + nk_textedit_clamp(state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + if (sel) + state->select_end = state->cursor; + } + } break; + + case NK_KEY_UP: { + struct nk_text_find find; + struct nk_text_edit_row row; + int i, sel = shift_mod; + + if (state->single_line) { + /* on windows, up&down become left&right */ + key = NK_KEY_LEFT; + goto retry; + } + + if (sel) + nk_textedit_prep_selection_at_cursor(state); + else if (NK_TEXT_HAS_SELECTION(state)) + nk_textedit_move_to_first(state); + + /* compute current position of cursor point */ + nk_textedit_clamp(state); + nk_textedit_find_charpos(&find, state, state->cursor, state->single_line, + font, row_height); + + /* can only go up if there's a previous row */ + if (find.prev_first != find.first_char) { + /* now find character position up a row */ + float x; + float goal_x = state->has_preferred_x ? state->preferred_x : find.x; + + state->cursor = find.prev_first; + nk_textedit_layout_row(&row, state, state->cursor, row_height, font); + x = row.x0; + + for (i=0; i < row.num_chars && x < row.x1; ++i) { + float dx = nk_textedit_get_width(state, find.prev_first, i, font); + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + nk_textedit_clamp(state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + if (sel) state->select_end = state->cursor; + } + } break; + + case NK_KEY_DEL: + if (state->mode == NK_TEXT_EDIT_MODE_VIEW) + break; + if (NK_TEXT_HAS_SELECTION(state)) + nk_textedit_delete_selection(state); + else { + int n = state->string.len; + if (state->cursor < n) + nk_textedit_delete(state, state->cursor, 1); + } + state->has_preferred_x = 0; + break; + + case NK_KEY_BACKSPACE: + if (state->mode == NK_TEXT_EDIT_MODE_VIEW) + break; + if (NK_TEXT_HAS_SELECTION(state)) + nk_textedit_delete_selection(state); + else { + nk_textedit_clamp(state); + if (state->cursor > 0) { + nk_textedit_delete(state, state->cursor-1, 1); + --state->cursor; + } + } + state->has_preferred_x = 0; + break; + + case NK_KEY_TEXT_START: + if (shift_mod) { + nk_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = 0; + state->has_preferred_x = 0; + } else { + state->cursor = state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + } + break; + + case NK_KEY_TEXT_END: + if (shift_mod) { + nk_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = state->string.len; + state->has_preferred_x = 0; + } else { + state->cursor = state->string.len; + state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + } + break; + + case NK_KEY_TEXT_LINE_START: { + if (shift_mod) { + struct nk_text_find find; + nk_textedit_clamp(state); + nk_textedit_prep_selection_at_cursor(state); + if (state->string.len && state->cursor == state->string.len) + --state->cursor; + nk_textedit_find_charpos(&find, state,state->cursor, state->single_line, + font, row_height); + state->cursor = state->select_end = find.first_char; + state->has_preferred_x = 0; + } else { + struct nk_text_find find; + if (state->string.len && state->cursor == state->string.len) + --state->cursor; + nk_textedit_clamp(state); + nk_textedit_move_to_first(state); + nk_textedit_find_charpos(&find, state, state->cursor, state->single_line, + font, row_height); + state->cursor = find.first_char; + state->has_preferred_x = 0; + } + } break; + + case NK_KEY_TEXT_LINE_END: { + if (shift_mod) { + struct nk_text_find find; + nk_textedit_clamp(state); + nk_textedit_prep_selection_at_cursor(state); + nk_textedit_find_charpos(&find, state, state->cursor, state->single_line, + font, row_height); + state->has_preferred_x = 0; + state->cursor = find.first_char + find.length; + if (find.length > 0 && nk_str_rune_at(&state->string, state->cursor-1) == '\n') + --state->cursor; + state->select_end = state->cursor; + } else { + struct nk_text_find find; + nk_textedit_clamp(state); + nk_textedit_move_to_first(state); + nk_textedit_find_charpos(&find, state, state->cursor, state->single_line, + font, row_height); + + state->has_preferred_x = 0; + state->cursor = find.first_char + find.length; + if (find.length > 0 && nk_str_rune_at(&state->string, state->cursor-1) == '\n') + --state->cursor; + }} break; + } +} +NK_INTERN void +nk_textedit_flush_redo(struct nk_text_undo_state *state) +{ + state->redo_point = NK_TEXTEDIT_UNDOSTATECOUNT; + state->redo_char_point = NK_TEXTEDIT_UNDOCHARCOUNT; +} +NK_INTERN void +nk_textedit_discard_undo(struct nk_text_undo_state *state) +{ + /* discard the oldest entry in the undo list */ + if (state->undo_point > 0) { + /* if the 0th undo state has characters, clean those up */ + if (state->undo_rec[0].char_storage >= 0) { + int n = state->undo_rec[0].insert_length, i; + /* delete n characters from all other records */ + state->undo_char_point = (short)(state->undo_char_point - n); + NK_MEMCPY(state->undo_char, state->undo_char + n, + (nk_size)state->undo_char_point*sizeof(nk_rune)); + for (i=0; i < state->undo_point; ++i) { + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage = (short) + (state->undo_rec[i].char_storage - n); + } + } + --state->undo_point; + NK_MEMCPY(state->undo_rec, state->undo_rec+1, + (nk_size)((nk_size)state->undo_point * sizeof(state->undo_rec[0]))); + } +} +NK_INTERN void +nk_textedit_discard_redo(struct nk_text_undo_state *state) +{ +/* discard the oldest entry in the redo list--it's bad if this + ever happens, but because undo & redo have to store the actual + characters in different cases, the redo character buffer can + fill up even though the undo buffer didn't */ + nk_size num; + int k = NK_TEXTEDIT_UNDOSTATECOUNT-1; + if (state->redo_point <= k) { + /* if the k'th undo state has characters, clean those up */ + if (state->undo_rec[k].char_storage >= 0) { + int n = state->undo_rec[k].insert_length, i; + /* delete n characters from all other records */ + state->redo_char_point = (short)(state->redo_char_point + n); + num = (nk_size)(NK_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point); + NK_MEMCPY(state->undo_char + state->redo_char_point, + state->undo_char + state->redo_char_point-n, num * sizeof(char)); + for (i = state->redo_point; i < k; ++i) { + if (state->undo_rec[i].char_storage >= 0) { + state->undo_rec[i].char_storage = (short) + (state->undo_rec[i].char_storage + n); + } + } + } + ++state->redo_point; + num = (nk_size)(NK_TEXTEDIT_UNDOSTATECOUNT - state->redo_point); + if (num) NK_MEMCPY(state->undo_rec + state->redo_point-1, + state->undo_rec + state->redo_point, num * sizeof(state->undo_rec[0])); + } +} +NK_INTERN struct nk_text_undo_record* +nk_textedit_create_undo_record(struct nk_text_undo_state *state, int numchars) +{ + /* any time we create a new undo record, we discard redo*/ + nk_textedit_flush_redo(state); + + /* if we have no free records, we have to make room, + * by sliding the existing records down */ + if (state->undo_point == NK_TEXTEDIT_UNDOSTATECOUNT) + nk_textedit_discard_undo(state); + + /* if the characters to store won't possibly fit in the buffer, + * we can't undo */ + if (numchars > NK_TEXTEDIT_UNDOCHARCOUNT) { + state->undo_point = 0; + state->undo_char_point = 0; + return 0; + } + + /* if we don't have enough free characters in the buffer, + * we have to make room */ + while (state->undo_char_point + numchars > NK_TEXTEDIT_UNDOCHARCOUNT) + nk_textedit_discard_undo(state); + return &state->undo_rec[state->undo_point++]; +} +NK_INTERN nk_rune* +nk_textedit_createundo(struct nk_text_undo_state *state, int pos, + int insert_len, int delete_len) +{ + struct nk_text_undo_record *r = nk_textedit_create_undo_record(state, insert_len); + if (r == 0) + return 0; + + r->where = pos; + r->insert_length = (short) insert_len; + r->delete_length = (short) delete_len; + + if (insert_len == 0) { + r->char_storage = -1; + return 0; + } else { + r->char_storage = state->undo_char_point; + state->undo_char_point = (short)(state->undo_char_point + insert_len); + return &state->undo_char[r->char_storage]; + } +} +NK_API void +nk_textedit_undo(struct nk_text_edit *state) +{ + struct nk_text_undo_state *s = &state->undo; + struct nk_text_undo_record u, *r; + if (s->undo_point == 0) + return; + + /* we need to do two things: apply the undo record, and create a redo record */ + u = s->undo_rec[s->undo_point-1]; + r = &s->undo_rec[s->redo_point-1]; + r->char_storage = -1; + + r->insert_length = u.delete_length; + r->delete_length = u.insert_length; + r->where = u.where; + + if (u.delete_length) + { + /* if the undo record says to delete characters, then the redo record will + need to re-insert the characters that get deleted, so we need to store + them. + there are three cases: + - there's enough room to store the characters + - characters stored for *redoing* don't leave room for redo + - characters stored for *undoing* don't leave room for redo + if the last is true, we have to bail */ + if (s->undo_char_point + u.delete_length >= NK_TEXTEDIT_UNDOCHARCOUNT) { + /* the undo records take up too much character space; there's no space + * to store the redo characters */ + r->insert_length = 0; + } else { + int i; + /* there's definitely room to store the characters eventually */ + while (s->undo_char_point + u.delete_length > s->redo_char_point) { + /* there's currently not enough room, so discard a redo record */ + nk_textedit_discard_redo(s); + /* should never happen: */ + if (s->redo_point == NK_TEXTEDIT_UNDOSTATECOUNT) + return; + } + + r = &s->undo_rec[s->redo_point-1]; + r->char_storage = (short)(s->redo_char_point - u.delete_length); + s->redo_char_point = (short)(s->redo_char_point - u.delete_length); + + /* now save the characters */ + for (i=0; i < u.delete_length; ++i) + s->undo_char[r->char_storage + i] = + nk_str_rune_at(&state->string, u.where + i); + } + /* now we can carry out the deletion */ + nk_str_delete_runes(&state->string, u.where, u.delete_length); + } + + /* check type of recorded action: */ + if (u.insert_length) { + /* easy case: was a deletion, so we need to insert n characters */ + nk_str_insert_text_runes(&state->string, u.where, + &s->undo_char[u.char_storage], u.insert_length); + s->undo_char_point = (short)(s->undo_char_point - u.insert_length); + } + state->cursor = (short)(u.where + u.insert_length); + + s->undo_point--; + s->redo_point--; +} +NK_API void +nk_textedit_redo(struct nk_text_edit *state) +{ + struct nk_text_undo_state *s = &state->undo; + struct nk_text_undo_record *u, r; + if (s->redo_point == NK_TEXTEDIT_UNDOSTATECOUNT) + return; + + /* we need to do two things: apply the redo record, and create an undo record */ + u = &s->undo_rec[s->undo_point]; + r = s->undo_rec[s->redo_point]; + + /* we KNOW there must be room for the undo record, because the redo record + was derived from an undo record */ + u->delete_length = r.insert_length; + u->insert_length = r.delete_length; + u->where = r.where; + u->char_storage = -1; + + if (r.delete_length) { + /* the redo record requires us to delete characters, so the undo record + needs to store the characters */ + if (s->undo_char_point + u->insert_length > s->redo_char_point) { + u->insert_length = 0; + u->delete_length = 0; + } else { + int i; + u->char_storage = s->undo_char_point; + s->undo_char_point = (short)(s->undo_char_point + u->insert_length); + + /* now save the characters */ + for (i=0; i < u->insert_length; ++i) { + s->undo_char[u->char_storage + i] = + nk_str_rune_at(&state->string, u->where + i); + } + } + nk_str_delete_runes(&state->string, r.where, r.delete_length); + } + + if (r.insert_length) { + /* easy case: need to insert n characters */ + nk_str_insert_text_runes(&state->string, r.where, + &s->undo_char[r.char_storage], r.insert_length); + } + state->cursor = r.where + r.insert_length; + + s->undo_point++; + s->redo_point++; +} +NK_INTERN void +nk_textedit_makeundo_insert(struct nk_text_edit *state, int where, int length) +{ + nk_textedit_createundo(&state->undo, where, 0, length); +} +NK_INTERN void +nk_textedit_makeundo_delete(struct nk_text_edit *state, int where, int length) +{ + int i; + nk_rune *p = nk_textedit_createundo(&state->undo, where, length, 0); + if (p) { + for (i=0; i < length; ++i) + p[i] = nk_str_rune_at(&state->string, where+i); + } +} +NK_INTERN void +nk_textedit_makeundo_replace(struct nk_text_edit *state, int where, + int old_length, int new_length) +{ + int i; + nk_rune *p = nk_textedit_createundo(&state->undo, where, old_length, new_length); + if (p) { + for (i=0; i < old_length; ++i) + p[i] = nk_str_rune_at(&state->string, where+i); + } +} +NK_LIB void +nk_textedit_clear_state(struct nk_text_edit *state, enum nk_text_edit_type type, + nk_plugin_filter filter) +{ + /* reset the state to default */ + state->undo.undo_point = 0; + state->undo.undo_char_point = 0; + state->undo.redo_point = NK_TEXTEDIT_UNDOSTATECOUNT; + state->undo.redo_char_point = NK_TEXTEDIT_UNDOCHARCOUNT; + state->select_end = state->select_start = 0; + state->cursor = 0; + state->has_preferred_x = 0; + state->preferred_x = 0; + state->cursor_at_end_of_line = 0; + state->initialized = 1; + state->single_line = (unsigned char)(type == NK_TEXT_EDIT_SINGLE_LINE); + state->mode = NK_TEXT_EDIT_MODE_VIEW; + state->filter = filter; + state->scrollbar = nk_vec2(0,0); +} +NK_API void +nk_textedit_init_fixed(struct nk_text_edit *state, void *memory, nk_size size) +{ + NK_ASSERT(state); + NK_ASSERT(memory); + if (!state || !memory || !size) return; + NK_MEMSET(state, 0, sizeof(struct nk_text_edit)); + nk_textedit_clear_state(state, NK_TEXT_EDIT_SINGLE_LINE, 0); + nk_str_init_fixed(&state->string, memory, size); +} +NK_API void +nk_textedit_init(struct nk_text_edit *state, struct nk_allocator *alloc, nk_size size) +{ + NK_ASSERT(state); + NK_ASSERT(alloc); + if (!state || !alloc) return; + NK_MEMSET(state, 0, sizeof(struct nk_text_edit)); + nk_textedit_clear_state(state, NK_TEXT_EDIT_SINGLE_LINE, 0); + nk_str_init(&state->string, alloc, size); +} +#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR +NK_API void +nk_textedit_init_default(struct nk_text_edit *state) +{ + NK_ASSERT(state); + if (!state) return; + NK_MEMSET(state, 0, sizeof(struct nk_text_edit)); + nk_textedit_clear_state(state, NK_TEXT_EDIT_SINGLE_LINE, 0); + nk_str_init_default(&state->string); +} +#endif +NK_API void +nk_textedit_select_all(struct nk_text_edit *state) +{ + NK_ASSERT(state); + state->select_start = 0; + state->select_end = state->string.len; +} +NK_API void +nk_textedit_free(struct nk_text_edit *state) +{ + NK_ASSERT(state); + if (!state) return; + nk_str_free(&state->string); +} + + + + + +/* =============================================================== + * + * FILTER + * + * ===============================================================*/ +NK_API nk_bool +nk_filter_default(const struct nk_text_edit *box, nk_rune unicode) +{ + NK_UNUSED(unicode); + NK_UNUSED(box); + return nk_true; +} +NK_API nk_bool +nk_filter_ascii(const struct nk_text_edit *box, nk_rune unicode) +{ + NK_UNUSED(box); + if (unicode > 128) return nk_false; + else return nk_true; +} +NK_API nk_bool +nk_filter_float(const struct nk_text_edit *box, nk_rune unicode) +{ + NK_UNUSED(box); + if ((unicode < '0' || unicode > '9') && unicode != '.' && unicode != '-') + return nk_false; + else return nk_true; +} +NK_API nk_bool +nk_filter_decimal(const struct nk_text_edit *box, nk_rune unicode) +{ + NK_UNUSED(box); + if ((unicode < '0' || unicode > '9') && unicode != '-') + return nk_false; + else return nk_true; +} +NK_API nk_bool +nk_filter_hex(const struct nk_text_edit *box, nk_rune unicode) +{ + NK_UNUSED(box); + if ((unicode < '0' || unicode > '9') && + (unicode < 'a' || unicode > 'f') && + (unicode < 'A' || unicode > 'F')) + return nk_false; + else return nk_true; +} +NK_API nk_bool +nk_filter_oct(const struct nk_text_edit *box, nk_rune unicode) +{ + NK_UNUSED(box); + if (unicode < '0' || unicode > '7') + return nk_false; + else return nk_true; +} +NK_API nk_bool +nk_filter_binary(const struct nk_text_edit *box, nk_rune unicode) +{ + NK_UNUSED(box); + if (unicode != '0' && unicode != '1') + return nk_false; + else return nk_true; +} + +/* =============================================================== + * + * EDIT + * + * ===============================================================*/ +NK_LIB void +nk_edit_draw_text(struct nk_command_buffer *out, + const struct nk_style_edit *style, float pos_x, float pos_y, + float x_offset, const char *text, int byte_len, float row_height, + const struct nk_user_font *font, struct nk_color background, + struct nk_color foreground, nk_bool is_selected) +{ + NK_ASSERT(out); + NK_ASSERT(font); + NK_ASSERT(style); + if (!text || !byte_len || !out || !style) return; + + {int glyph_len = 0; + nk_rune unicode = 0; + int text_len = 0; + float line_width = 0; + float glyph_width; + const char *line = text; + float line_offset = 0; + int line_count = 0; + + struct nk_text txt; + txt.padding = nk_vec2(0,0); + txt.background = background; + txt.text = foreground; + + glyph_len = nk_utf_decode(text+text_len, &unicode, byte_len-text_len); + if (!glyph_len) return; + while ((text_len < byte_len) && glyph_len) + { + if (unicode == '\n') { + /* new line separator so draw previous line */ + struct nk_rect label; + label.y = pos_y + line_offset; + label.h = row_height; + label.w = line_width; + label.x = pos_x; + if (!line_count) + label.x += x_offset; + + if (is_selected) /* selection needs to draw different background color */ + nk_fill_rect(out, label, 0, background); + nk_widget_text(out, label, line, (int)((text + text_len) - line), + &txt, NK_TEXT_CENTERED, font); + + text_len++; + line_count++; + line_width = 0; + line = text + text_len; + line_offset += row_height; + glyph_len = nk_utf_decode(text + text_len, &unicode, (int)(byte_len-text_len)); + continue; + } + if (unicode == '\r') { + text_len++; + glyph_len = nk_utf_decode(text + text_len, &unicode, byte_len-text_len); + continue; + } + glyph_width = font->width(font->userdata, font->height, text+text_len, glyph_len); + line_width += (float)glyph_width; + text_len += glyph_len; + glyph_len = nk_utf_decode(text + text_len, &unicode, byte_len-text_len); + continue; + } + if (line_width > 0) { + /* draw last line */ + struct nk_rect label; + label.y = pos_y + line_offset; + label.h = row_height; + label.w = line_width; + label.x = pos_x; + if (!line_count) + label.x += x_offset; + + if (is_selected) + nk_fill_rect(out, label, 0, background); + nk_widget_text(out, label, line, (int)((text + text_len) - line), + &txt, NK_TEXT_LEFT, font); + }} +} +NK_LIB nk_flags +nk_do_edit(nk_flags *state, struct nk_command_buffer *out, + struct nk_rect bounds, nk_flags flags, nk_plugin_filter filter, + struct nk_text_edit *edit, const struct nk_style_edit *style, + struct nk_input *in, const struct nk_user_font *font) +{ + struct nk_rect area; + nk_flags ret = 0; + float row_height; + char prev_state = 0; + char is_hovered = 0; + char select_all = 0; + char cursor_follow = 0; + struct nk_rect old_clip; + struct nk_rect clip; + + NK_ASSERT(state); + NK_ASSERT(out); + NK_ASSERT(style); + if (!state || !out || !style) + return ret; + + /* visible text area calculation */ + area.x = bounds.x + style->padding.x + style->border; + area.y = bounds.y + style->padding.y + style->border; + area.w = bounds.w - (2.0f * style->padding.x + 2 * style->border); + area.h = bounds.h - (2.0f * style->padding.y + 2 * style->border); + if (flags & NK_EDIT_MULTILINE) + area.w = NK_MAX(0, area.w - style->scrollbar_size.x); + row_height = (flags & NK_EDIT_MULTILINE)? font->height + style->row_padding: area.h; + + /* calculate clipping rectangle */ + old_clip = out->clip; + nk_unify(&clip, &old_clip, area.x, area.y, area.x + area.w, area.y + area.h); + + /* update edit state */ + prev_state = (char)edit->active; + is_hovered = (char)nk_input_is_mouse_hovering_rect(in, bounds); + if (in && in->mouse.buttons[NK_BUTTON_LEFT].clicked && in->mouse.buttons[NK_BUTTON_LEFT].down) { + edit->active = NK_INBOX(in->mouse.pos.x, in->mouse.pos.y, + bounds.x, bounds.y, bounds.w, bounds.h); + } + + /* (de)activate text editor */ + if (!prev_state && edit->active) { + const enum nk_text_edit_type type = (flags & NK_EDIT_MULTILINE) ? + NK_TEXT_EDIT_MULTI_LINE: NK_TEXT_EDIT_SINGLE_LINE; + /* keep scroll position when re-activating edit widget */ + struct nk_vec2 oldscrollbar = edit->scrollbar; + nk_textedit_clear_state(edit, type, filter); + edit->scrollbar = oldscrollbar; + if (flags & NK_EDIT_AUTO_SELECT) + select_all = nk_true; + if (flags & NK_EDIT_GOTO_END_ON_ACTIVATE) { + edit->cursor = edit->string.len; + in = 0; + } + } else if (!edit->active) edit->mode = NK_TEXT_EDIT_MODE_VIEW; + if (flags & NK_EDIT_READ_ONLY) + edit->mode = NK_TEXT_EDIT_MODE_VIEW; + else if (flags & NK_EDIT_ALWAYS_INSERT_MODE) + edit->mode = NK_TEXT_EDIT_MODE_INSERT; + + ret = (edit->active) ? NK_EDIT_ACTIVE: NK_EDIT_INACTIVE; + if (prev_state != edit->active) + ret |= (edit->active) ? NK_EDIT_ACTIVATED: NK_EDIT_DEACTIVATED; + + /* handle user input */ + if (edit->active && in) + { + int shift_mod = in->keyboard.keys[NK_KEY_SHIFT].down; + const float mouse_x = (in->mouse.pos.x - area.x) + edit->scrollbar.x; + const float mouse_y = (in->mouse.pos.y - area.y) + edit->scrollbar.y; + + /* mouse click handler */ + is_hovered = (char)nk_input_is_mouse_hovering_rect(in, area); + if (select_all) { + nk_textedit_select_all(edit); + } else if (is_hovered && in->mouse.buttons[NK_BUTTON_LEFT].down && + in->mouse.buttons[NK_BUTTON_LEFT].clicked) { + nk_textedit_click(edit, mouse_x, mouse_y, font, row_height); + } else if (is_hovered && in->mouse.buttons[NK_BUTTON_LEFT].down && + (in->mouse.delta.x != 0.0f || in->mouse.delta.y != 0.0f)) { + nk_textedit_drag(edit, mouse_x, mouse_y, font, row_height); + cursor_follow = nk_true; + } else if (is_hovered && in->mouse.buttons[NK_BUTTON_RIGHT].clicked && + in->mouse.buttons[NK_BUTTON_RIGHT].down) { + nk_textedit_key(edit, NK_KEY_TEXT_WORD_LEFT, nk_false, font, row_height); + nk_textedit_key(edit, NK_KEY_TEXT_WORD_RIGHT, nk_true, font, row_height); + cursor_follow = nk_true; + } + + {int i; /* keyboard input */ + int old_mode = edit->mode; + for (i = 0; i < NK_KEY_MAX; ++i) { + if (i == NK_KEY_ENTER || i == NK_KEY_TAB) continue; /* special case */ + if (nk_input_is_key_pressed(in, (enum nk_keys)i)) { + nk_textedit_key(edit, (enum nk_keys)i, shift_mod, font, row_height); + cursor_follow = nk_true; + } + } + if (old_mode != edit->mode) { + in->keyboard.text_len = 0; + }} + + /* text input */ + edit->filter = filter; + if (in->keyboard.text_len) { + nk_textedit_text(edit, in->keyboard.text, in->keyboard.text_len); + cursor_follow = nk_true; + in->keyboard.text_len = 0; + } + + /* enter key handler */ + if (nk_input_is_key_pressed(in, NK_KEY_ENTER)) { + cursor_follow = nk_true; + if (flags & NK_EDIT_CTRL_ENTER_NEWLINE && shift_mod) + nk_textedit_text(edit, "\n", 1); + else if (flags & NK_EDIT_SIG_ENTER) + ret |= NK_EDIT_COMMITED; + else nk_textedit_text(edit, "\n", 1); + } + + /* cut & copy handler */ + {int copy= nk_input_is_key_pressed(in, NK_KEY_COPY); + int cut = nk_input_is_key_pressed(in, NK_KEY_CUT); + if ((copy || cut) && (flags & NK_EDIT_CLIPBOARD)) + { + int glyph_len; + nk_rune unicode; + const char *text; + int b = edit->select_start; + int e = edit->select_end; + + int begin = NK_MIN(b, e); + int end = NK_MAX(b, e); + text = nk_str_at_const(&edit->string, begin, &unicode, &glyph_len); + if (edit->clip.copy) + edit->clip.copy(edit->clip.userdata, text, end - begin); + if (cut && !(flags & NK_EDIT_READ_ONLY)){ + nk_textedit_cut(edit); + cursor_follow = nk_true; + } + }} + + /* paste handler */ + {int paste = nk_input_is_key_pressed(in, NK_KEY_PASTE); + if (paste && (flags & NK_EDIT_CLIPBOARD) && edit->clip.paste) { + edit->clip.paste(edit->clip.userdata, edit); + cursor_follow = nk_true; + }} + + /* tab handler */ + {int tab = nk_input_is_key_pressed(in, NK_KEY_TAB); + if (tab && (flags & NK_EDIT_ALLOW_TAB)) { + nk_textedit_text(edit, " ", 4); + cursor_follow = nk_true; + }} + } + + /* set widget state */ + if (edit->active) + *state = NK_WIDGET_STATE_ACTIVE; + else nk_widget_state_reset(state); + + if (is_hovered) + *state |= NK_WIDGET_STATE_HOVERED; + + /* DRAW EDIT */ + {const char *text = nk_str_get_const(&edit->string); + int len = nk_str_len_char(&edit->string); + + {/* select background colors/images */ + const struct nk_style_item *background; + if (*state & NK_WIDGET_STATE_ACTIVED) + background = &style->active; + else if (*state & NK_WIDGET_STATE_HOVER) + background = &style->hover; + else background = &style->normal; + + /* draw background frame */ + if (background->type == NK_STYLE_ITEM_COLOR) { + nk_fill_rect(out, bounds, style->rounding, background->data.color); + nk_stroke_rect(out, bounds, style->rounding, style->border, style->border_color); + } else nk_draw_image(out, bounds, &background->data.image, nk_white);} + + area.w = NK_MAX(0, area.w - style->cursor_size); + if (edit->active) + { + int total_lines = 1; + struct nk_vec2 text_size = nk_vec2(0,0); + + /* text pointer positions */ + const char *cursor_ptr = 0; + const char *select_begin_ptr = 0; + const char *select_end_ptr = 0; + + /* 2D pixel positions */ + struct nk_vec2 cursor_pos = nk_vec2(0,0); + struct nk_vec2 selection_offset_start = nk_vec2(0,0); + struct nk_vec2 selection_offset_end = nk_vec2(0,0); + + int selection_begin = NK_MIN(edit->select_start, edit->select_end); + int selection_end = NK_MAX(edit->select_start, edit->select_end); + + /* calculate total line count + total space + cursor/selection position */ + float line_width = 0.0f; + if (text && len) + { + /* utf8 encoding */ + float glyph_width; + int glyph_len = 0; + nk_rune unicode = 0; + int text_len = 0; + int glyphs = 0; + int row_begin = 0; + + glyph_len = nk_utf_decode(text, &unicode, len); + glyph_width = font->width(font->userdata, font->height, text, glyph_len); + line_width = 0; + + /* iterate all lines */ + while ((text_len < len) && glyph_len) + { + /* set cursor 2D position and line */ + if (!cursor_ptr && glyphs == edit->cursor) + { + int glyph_offset; + struct nk_vec2 out_offset; + struct nk_vec2 row_size; + const char *remaining; + + /* calculate 2d position */ + cursor_pos.y = (float)(total_lines-1) * row_height; + row_size = nk_text_calculate_text_bounds(font, text+row_begin, + text_len-row_begin, row_height, &remaining, + &out_offset, &glyph_offset, NK_STOP_ON_NEW_LINE); + cursor_pos.x = row_size.x; + cursor_ptr = text + text_len; + } + + /* set start selection 2D position and line */ + if (!select_begin_ptr && edit->select_start != edit->select_end && + glyphs == selection_begin) + { + int glyph_offset; + struct nk_vec2 out_offset; + struct nk_vec2 row_size; + const char *remaining; + + /* calculate 2d position */ + selection_offset_start.y = (float)(NK_MAX(total_lines-1,0)) * row_height; + row_size = nk_text_calculate_text_bounds(font, text+row_begin, + text_len-row_begin, row_height, &remaining, + &out_offset, &glyph_offset, NK_STOP_ON_NEW_LINE); + selection_offset_start.x = row_size.x; + select_begin_ptr = text + text_len; + } + + /* set end selection 2D position and line */ + if (!select_end_ptr && edit->select_start != edit->select_end && + glyphs == selection_end) + { + int glyph_offset; + struct nk_vec2 out_offset; + struct nk_vec2 row_size; + const char *remaining; + + /* calculate 2d position */ + selection_offset_end.y = (float)(total_lines-1) * row_height; + row_size = nk_text_calculate_text_bounds(font, text+row_begin, + text_len-row_begin, row_height, &remaining, + &out_offset, &glyph_offset, NK_STOP_ON_NEW_LINE); + selection_offset_end.x = row_size.x; + select_end_ptr = text + text_len; + } + if (unicode == '\n') { + text_size.x = NK_MAX(text_size.x, line_width); + total_lines++; + line_width = 0; + text_len++; + glyphs++; + row_begin = text_len; + glyph_len = nk_utf_decode(text + text_len, &unicode, len-text_len); + glyph_width = font->width(font->userdata, font->height, text+text_len, glyph_len); + continue; + } + + glyphs++; + text_len += glyph_len; + line_width += (float)glyph_width; + + glyph_len = nk_utf_decode(text + text_len, &unicode, len-text_len); + glyph_width = font->width(font->userdata, font->height, + text+text_len, glyph_len); + continue; + } + text_size.y = (float)total_lines * row_height; + + /* handle case when cursor is at end of text buffer */ + if (!cursor_ptr && edit->cursor == edit->string.len) { + cursor_pos.x = line_width; + cursor_pos.y = text_size.y - row_height; + } + } + { + /* scrollbar */ + if (cursor_follow) + { + /* update scrollbar to follow cursor */ + if (!(flags & NK_EDIT_NO_HORIZONTAL_SCROLL)) { + /* horizontal scroll */ + const float scroll_increment = area.w * 0.25f; + if (cursor_pos.x < edit->scrollbar.x) + edit->scrollbar.x = (float)(int)NK_MAX(0.0f, cursor_pos.x - scroll_increment); + if (cursor_pos.x >= edit->scrollbar.x + area.w) + edit->scrollbar.x = (float)(int)NK_MAX(0.0f, cursor_pos.x - area.w + scroll_increment); + } else edit->scrollbar.x = 0; + + if (flags & NK_EDIT_MULTILINE) { + /* vertical scroll */ + if (cursor_pos.y < edit->scrollbar.y) + edit->scrollbar.y = NK_MAX(0.0f, cursor_pos.y - row_height); + if (cursor_pos.y >= edit->scrollbar.y + area.h) + edit->scrollbar.y = edit->scrollbar.y + row_height; + } else edit->scrollbar.y = 0; + } + + /* scrollbar widget */ + if (flags & NK_EDIT_MULTILINE) + { + nk_flags ws; + struct nk_rect scroll; + float scroll_target; + float scroll_offset; + float scroll_step; + float scroll_inc; + + scroll = area; + scroll.x = (bounds.x + bounds.w - style->border) - style->scrollbar_size.x; + scroll.w = style->scrollbar_size.x; + + scroll_offset = edit->scrollbar.y; + scroll_step = scroll.h * 0.10f; + scroll_inc = scroll.h * 0.01f; + scroll_target = text_size.y; + edit->scrollbar.y = nk_do_scrollbarv(&ws, out, scroll, 0, + scroll_offset, scroll_target, scroll_step, scroll_inc, + &style->scrollbar, in, font); + } + } + + /* draw text */ + {struct nk_color background_color; + struct nk_color text_color; + struct nk_color sel_background_color; + struct nk_color sel_text_color; + struct nk_color cursor_color; + struct nk_color cursor_text_color; + const struct nk_style_item *background; + nk_push_scissor(out, clip); + + /* select correct colors to draw */ + if (*state & NK_WIDGET_STATE_ACTIVED) { + background = &style->active; + text_color = style->text_active; + sel_text_color = style->selected_text_hover; + sel_background_color = style->selected_hover; + cursor_color = style->cursor_hover; + cursor_text_color = style->cursor_text_hover; + } else if (*state & NK_WIDGET_STATE_HOVER) { + background = &style->hover; + text_color = style->text_hover; + sel_text_color = style->selected_text_hover; + sel_background_color = style->selected_hover; + cursor_text_color = style->cursor_text_hover; + cursor_color = style->cursor_hover; + } else { + background = &style->normal; + text_color = style->text_normal; + sel_text_color = style->selected_text_normal; + sel_background_color = style->selected_normal; + cursor_color = style->cursor_normal; + cursor_text_color = style->cursor_text_normal; + } + if (background->type == NK_STYLE_ITEM_IMAGE) + background_color = nk_rgba(0,0,0,0); + else background_color = background->data.color; + + + if (edit->select_start == edit->select_end) { + /* no selection so just draw the complete text */ + const char *begin = nk_str_get_const(&edit->string); + int l = nk_str_len_char(&edit->string); + nk_edit_draw_text(out, style, area.x - edit->scrollbar.x, + area.y - edit->scrollbar.y, 0, begin, l, row_height, font, + background_color, text_color, nk_false); + } else { + /* edit has selection so draw 1-3 text chunks */ + if (edit->select_start != edit->select_end && selection_begin > 0){ + /* draw unselected text before selection */ + const char *begin = nk_str_get_const(&edit->string); + NK_ASSERT(select_begin_ptr); + nk_edit_draw_text(out, style, area.x - edit->scrollbar.x, + area.y - edit->scrollbar.y, 0, begin, (int)(select_begin_ptr - begin), + row_height, font, background_color, text_color, nk_false); + } + if (edit->select_start != edit->select_end) { + /* draw selected text */ + NK_ASSERT(select_begin_ptr); + if (!select_end_ptr) { + const char *begin = nk_str_get_const(&edit->string); + select_end_ptr = begin + nk_str_len_char(&edit->string); + } + nk_edit_draw_text(out, style, + area.x - edit->scrollbar.x, + area.y + selection_offset_start.y - edit->scrollbar.y, + selection_offset_start.x, + select_begin_ptr, (int)(select_end_ptr - select_begin_ptr), + row_height, font, sel_background_color, sel_text_color, nk_true); + } + if ((edit->select_start != edit->select_end && + selection_end < edit->string.len)) + { + /* draw unselected text after selected text */ + const char *begin = select_end_ptr; + const char *end = nk_str_get_const(&edit->string) + + nk_str_len_char(&edit->string); + NK_ASSERT(select_end_ptr); + nk_edit_draw_text(out, style, + area.x - edit->scrollbar.x, + area.y + selection_offset_end.y - edit->scrollbar.y, + selection_offset_end.x, + begin, (int)(end - begin), row_height, font, + background_color, text_color, nk_true); + } + } + + /* cursor */ + if (edit->select_start == edit->select_end) + { + if (edit->cursor >= nk_str_len(&edit->string) || + (cursor_ptr && *cursor_ptr == '\n')) { + /* draw cursor at end of line */ + struct nk_rect cursor; + cursor.w = style->cursor_size; + cursor.h = font->height; + cursor.x = area.x + cursor_pos.x - edit->scrollbar.x; + cursor.y = area.y + cursor_pos.y + row_height/2.0f - cursor.h/2.0f; + cursor.y -= edit->scrollbar.y; + nk_fill_rect(out, cursor, 0, cursor_color); + } else { + /* draw cursor inside text */ + int glyph_len; + struct nk_rect label; + struct nk_text txt; + + nk_rune unicode; + NK_ASSERT(cursor_ptr); + glyph_len = nk_utf_decode(cursor_ptr, &unicode, 4); + + label.x = area.x + cursor_pos.x - edit->scrollbar.x; + label.y = area.y + cursor_pos.y - edit->scrollbar.y; + label.w = font->width(font->userdata, font->height, cursor_ptr, glyph_len); + label.h = row_height; + + txt.padding = nk_vec2(0,0); + txt.background = cursor_color;; + txt.text = cursor_text_color; + nk_fill_rect(out, label, 0, cursor_color); + nk_widget_text(out, label, cursor_ptr, glyph_len, &txt, NK_TEXT_LEFT, font); + } + }} + } else { + /* not active so just draw text */ + int l = nk_str_len_char(&edit->string); + const char *begin = nk_str_get_const(&edit->string); + + const struct nk_style_item *background; + struct nk_color background_color; + struct nk_color text_color; + nk_push_scissor(out, clip); + if (*state & NK_WIDGET_STATE_ACTIVED) { + background = &style->active; + text_color = style->text_active; + } else if (*state & NK_WIDGET_STATE_HOVER) { + background = &style->hover; + text_color = style->text_hover; + } else { + background = &style->normal; + text_color = style->text_normal; + } + if (background->type == NK_STYLE_ITEM_IMAGE) + background_color = nk_rgba(0,0,0,0); + else background_color = background->data.color; + nk_edit_draw_text(out, style, area.x - edit->scrollbar.x, + area.y - edit->scrollbar.y, 0, begin, l, row_height, font, + background_color, text_color, nk_false); + } + nk_push_scissor(out, old_clip);} + return ret; +} +NK_API void +nk_edit_focus(struct nk_context *ctx, nk_flags flags) +{ + nk_hash hash; + struct nk_window *win; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) return; + + win = ctx->current; + hash = win->edit.seq; + win->edit.active = nk_true; + win->edit.name = hash; + if (flags & NK_EDIT_ALWAYS_INSERT_MODE) + win->edit.mode = NK_TEXT_EDIT_MODE_INSERT; +} +NK_API void +nk_edit_unfocus(struct nk_context *ctx) +{ + struct nk_window *win; + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) return; + + win = ctx->current; + win->edit.active = nk_false; + win->edit.name = 0; +} +NK_API nk_flags +nk_edit_string(struct nk_context *ctx, nk_flags flags, + char *memory, int *len, int max, nk_plugin_filter filter) +{ + nk_hash hash; + nk_flags state; + struct nk_text_edit *edit; + struct nk_window *win; + + NK_ASSERT(ctx); + NK_ASSERT(memory); + NK_ASSERT(len); + if (!ctx || !memory || !len) + return 0; + + filter = (!filter) ? nk_filter_default: filter; + win = ctx->current; + hash = win->edit.seq; + edit = &ctx->text_edit; + nk_textedit_clear_state(&ctx->text_edit, (flags & NK_EDIT_MULTILINE)? + NK_TEXT_EDIT_MULTI_LINE: NK_TEXT_EDIT_SINGLE_LINE, filter); + + if (win->edit.active && hash == win->edit.name) { + if (flags & NK_EDIT_NO_CURSOR) + edit->cursor = nk_utf_len(memory, *len); + else edit->cursor = win->edit.cursor; + if (!(flags & NK_EDIT_SELECTABLE)) { + edit->select_start = win->edit.cursor; + edit->select_end = win->edit.cursor; + } else { + edit->select_start = win->edit.sel_start; + edit->select_end = win->edit.sel_end; + } + edit->mode = win->edit.mode; + edit->scrollbar.x = (float)win->edit.scrollbar.x; + edit->scrollbar.y = (float)win->edit.scrollbar.y; + edit->active = nk_true; + } else edit->active = nk_false; + + max = NK_MAX(1, max); + *len = NK_MIN(*len, max-1); + nk_str_init_fixed(&edit->string, memory, (nk_size)max); + edit->string.buffer.allocated = (nk_size)*len; + edit->string.len = nk_utf_len(memory, *len); + state = nk_edit_buffer(ctx, flags, edit, filter); + *len = (int)edit->string.buffer.allocated; + + if (edit->active) { + win->edit.cursor = edit->cursor; + win->edit.sel_start = edit->select_start; + win->edit.sel_end = edit->select_end; + win->edit.mode = edit->mode; + win->edit.scrollbar.x = (nk_uint)edit->scrollbar.x; + win->edit.scrollbar.y = (nk_uint)edit->scrollbar.y; + } return state; +} +NK_API nk_flags +nk_edit_buffer(struct nk_context *ctx, nk_flags flags, + struct nk_text_edit *edit, nk_plugin_filter filter) +{ + struct nk_window *win; + struct nk_style *style; + struct nk_input *in; + + enum nk_widget_layout_states state; + struct nk_rect bounds; + + nk_flags ret_flags = 0; + unsigned char prev_state; + nk_hash hash; + + /* make sure correct values */ + NK_ASSERT(ctx); + NK_ASSERT(edit); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + style = &ctx->style; + state = nk_widget(&bounds, ctx); + if (!state) return state; + in = (win->layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + + /* check if edit is currently hot item */ + hash = win->edit.seq++; + if (win->edit.active && hash == win->edit.name) { + if (flags & NK_EDIT_NO_CURSOR) + edit->cursor = edit->string.len; + if (!(flags & NK_EDIT_SELECTABLE)) { + edit->select_start = edit->cursor; + edit->select_end = edit->cursor; + } + if (flags & NK_EDIT_CLIPBOARD) + edit->clip = ctx->clip; + edit->active = (unsigned char)win->edit.active; + } else edit->active = nk_false; + edit->mode = win->edit.mode; + + filter = (!filter) ? nk_filter_default: filter; + prev_state = (unsigned char)edit->active; + in = (flags & NK_EDIT_READ_ONLY) ? 0: in; + ret_flags = nk_do_edit(&ctx->last_widget_state, &win->buffer, bounds, flags, + filter, edit, &style->edit, in, style->font); + + if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) + ctx->style.cursor_active = ctx->style.cursors[NK_CURSOR_TEXT]; + if (edit->active && prev_state != edit->active) { + /* current edit is now hot */ + win->edit.active = nk_true; + win->edit.name = hash; + } else if (prev_state && !edit->active) { + /* current edit is now cold */ + win->edit.active = nk_false; + } return ret_flags; +} +NK_API nk_flags +nk_edit_string_zero_terminated(struct nk_context *ctx, nk_flags flags, + char *buffer, int max, nk_plugin_filter filter) +{ + nk_flags result; + int len = nk_strlen(buffer); + result = nk_edit_string(ctx, flags, buffer, &len, max, filter); + buffer[NK_MIN(NK_MAX(max-1,0), len)] = '\0'; + return result; +} + + + + + +/* =============================================================== + * + * PROPERTY + * + * ===============================================================*/ +NK_LIB void +nk_drag_behavior(nk_flags *state, const struct nk_input *in, + struct nk_rect drag, struct nk_property_variant *variant, + float inc_per_pixel) +{ + int left_mouse_down = in && in->mouse.buttons[NK_BUTTON_LEFT].down; + int left_mouse_click_in_cursor = in && + nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, drag, nk_true); + + nk_widget_state_reset(state); + if (nk_input_is_mouse_hovering_rect(in, drag)) + *state = NK_WIDGET_STATE_HOVERED; + + if (left_mouse_down && left_mouse_click_in_cursor) { + float delta, pixels; + pixels = in->mouse.delta.x; + delta = pixels * inc_per_pixel; + switch (variant->kind) { + default: break; + case NK_PROPERTY_INT: + variant->value.i = variant->value.i + (int)delta; + variant->value.i = NK_CLAMP(variant->min_value.i, variant->value.i, variant->max_value.i); + break; + case NK_PROPERTY_FLOAT: + variant->value.f = variant->value.f + (float)delta; + variant->value.f = NK_CLAMP(variant->min_value.f, variant->value.f, variant->max_value.f); + break; + case NK_PROPERTY_DOUBLE: + variant->value.d = variant->value.d + (double)delta; + variant->value.d = NK_CLAMP(variant->min_value.d, variant->value.d, variant->max_value.d); + break; + } + *state = NK_WIDGET_STATE_ACTIVE; + } + if (*state & NK_WIDGET_STATE_HOVER && !nk_input_is_mouse_prev_hovering_rect(in, drag)) + *state |= NK_WIDGET_STATE_ENTERED; + else if (nk_input_is_mouse_prev_hovering_rect(in, drag)) + *state |= NK_WIDGET_STATE_LEFT; +} +NK_LIB void +nk_property_behavior(nk_flags *ws, const struct nk_input *in, + struct nk_rect property, struct nk_rect label, struct nk_rect edit, + struct nk_rect empty, int *state, struct nk_property_variant *variant, + float inc_per_pixel) +{ + if (in && *state == NK_PROPERTY_DEFAULT) { + if (nk_button_behavior(ws, edit, in, NK_BUTTON_DEFAULT)) + *state = NK_PROPERTY_EDIT; + else if (nk_input_is_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, label, nk_true)) + *state = NK_PROPERTY_DRAG; + else if (nk_input_is_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, empty, nk_true)) + *state = NK_PROPERTY_DRAG; + } + if (*state == NK_PROPERTY_DRAG) { + nk_drag_behavior(ws, in, property, variant, inc_per_pixel); + if (!(*ws & NK_WIDGET_STATE_ACTIVED)) *state = NK_PROPERTY_DEFAULT; + } +} +NK_LIB void +nk_draw_property(struct nk_command_buffer *out, const struct nk_style_property *style, + const struct nk_rect *bounds, const struct nk_rect *label, nk_flags state, + const char *name, int len, const struct nk_user_font *font) +{ + struct nk_text text; + const struct nk_style_item *background; + + /* select correct background and text color */ + if (state & NK_WIDGET_STATE_ACTIVED) { + background = &style->active; + text.text = style->label_active; + } else if (state & NK_WIDGET_STATE_HOVER) { + background = &style->hover; + text.text = style->label_hover; + } else { + background = &style->normal; + text.text = style->label_normal; + } + + /* draw background */ + if (background->type == NK_STYLE_ITEM_IMAGE) { + nk_draw_image(out, *bounds, &background->data.image, nk_white); + text.background = nk_rgba(0,0,0,0); + } else { + text.background = background->data.color; + nk_fill_rect(out, *bounds, style->rounding, background->data.color); + nk_stroke_rect(out, *bounds, style->rounding, style->border, background->data.color); + } + + /* draw label */ + text.padding = nk_vec2(0,0); + nk_widget_text(out, *label, name, len, &text, NK_TEXT_CENTERED, font); +} +NK_LIB void +nk_do_property(nk_flags *ws, + struct nk_command_buffer *out, struct nk_rect property, + const char *name, struct nk_property_variant *variant, + float inc_per_pixel, char *buffer, int *len, + int *state, int *cursor, int *select_begin, int *select_end, + const struct nk_style_property *style, + enum nk_property_filter filter, struct nk_input *in, + const struct nk_user_font *font, struct nk_text_edit *text_edit, + enum nk_button_behavior behavior) +{ + const nk_plugin_filter filters[] = { + nk_filter_decimal, + nk_filter_float + }; + nk_bool active, old; + int num_len, name_len; + char string[NK_MAX_NUMBER_BUFFER]; + float size; + + char *dst = 0; + int *length; + + struct nk_rect left; + struct nk_rect right; + struct nk_rect label; + struct nk_rect edit; + struct nk_rect empty; + + /* left decrement button */ + left.h = font->height/2; + left.w = left.h; + left.x = property.x + style->border + style->padding.x; + left.y = property.y + style->border + property.h/2.0f - left.h/2; + + /* text label */ + name_len = nk_strlen(name); + size = font->width(font->userdata, font->height, name, name_len); + label.x = left.x + left.w + style->padding.x; + label.w = (float)size + 2 * style->padding.x; + label.y = property.y + style->border + style->padding.y; + label.h = property.h - (2 * style->border + 2 * style->padding.y); + + /* right increment button */ + right.y = left.y; + right.w = left.w; + right.h = left.h; + right.x = property.x + property.w - (right.w + style->padding.x); + + /* edit */ + if (*state == NK_PROPERTY_EDIT) { + size = font->width(font->userdata, font->height, buffer, *len); + size += style->edit.cursor_size; + length = len; + dst = buffer; + } else { + switch (variant->kind) { + default: break; + case NK_PROPERTY_INT: + nk_itoa(string, variant->value.i); + num_len = nk_strlen(string); + break; + case NK_PROPERTY_FLOAT: + NK_DTOA(string, (double)variant->value.f); + num_len = nk_string_float_limit(string, NK_MAX_FLOAT_PRECISION); + break; + case NK_PROPERTY_DOUBLE: + NK_DTOA(string, variant->value.d); + num_len = nk_string_float_limit(string, NK_MAX_FLOAT_PRECISION); + break; + } + size = font->width(font->userdata, font->height, string, num_len); + dst = string; + length = &num_len; + } + + edit.w = (float)size + 2 * style->padding.x; + edit.w = NK_MIN(edit.w, right.x - (label.x + label.w)); + edit.x = right.x - (edit.w + style->padding.x); + edit.y = property.y + style->border; + edit.h = property.h - (2 * style->border); + + /* empty left space activator */ + empty.w = edit.x - (label.x + label.w); + empty.x = label.x + label.w; + empty.y = property.y; + empty.h = property.h; + + /* update property */ + old = (*state == NK_PROPERTY_EDIT); + nk_property_behavior(ws, in, property, label, edit, empty, state, variant, inc_per_pixel); + + /* draw property */ + if (style->draw_begin) style->draw_begin(out, style->userdata); + nk_draw_property(out, style, &property, &label, *ws, name, name_len, font); + if (style->draw_end) style->draw_end(out, style->userdata); + + /* execute right button */ + if (nk_do_button_symbol(ws, out, left, style->sym_left, behavior, &style->dec_button, in, font)) { + switch (variant->kind) { + default: break; + case NK_PROPERTY_INT: + variant->value.i = NK_CLAMP(variant->min_value.i, variant->value.i - variant->step.i, variant->max_value.i); break; + case NK_PROPERTY_FLOAT: + variant->value.f = NK_CLAMP(variant->min_value.f, variant->value.f - variant->step.f, variant->max_value.f); break; + case NK_PROPERTY_DOUBLE: + variant->value.d = NK_CLAMP(variant->min_value.d, variant->value.d - variant->step.d, variant->max_value.d); break; + } + } + /* execute left button */ + if (nk_do_button_symbol(ws, out, right, style->sym_right, behavior, &style->inc_button, in, font)) { + switch (variant->kind) { + default: break; + case NK_PROPERTY_INT: + variant->value.i = NK_CLAMP(variant->min_value.i, variant->value.i + variant->step.i, variant->max_value.i); break; + case NK_PROPERTY_FLOAT: + variant->value.f = NK_CLAMP(variant->min_value.f, variant->value.f + variant->step.f, variant->max_value.f); break; + case NK_PROPERTY_DOUBLE: + variant->value.d = NK_CLAMP(variant->min_value.d, variant->value.d + variant->step.d, variant->max_value.d); break; + } + } + if (old != NK_PROPERTY_EDIT && (*state == NK_PROPERTY_EDIT)) { + /* property has been activated so setup buffer */ + NK_MEMCPY(buffer, dst, (nk_size)*length); + *cursor = nk_utf_len(buffer, *length); + *len = *length; + length = len; + dst = buffer; + active = 0; + } else active = (*state == NK_PROPERTY_EDIT); + + /* execute and run text edit field */ + nk_textedit_clear_state(text_edit, NK_TEXT_EDIT_SINGLE_LINE, filters[filter]); + text_edit->active = (unsigned char)active; + text_edit->string.len = *length; + text_edit->cursor = NK_CLAMP(0, *cursor, *length); + text_edit->select_start = NK_CLAMP(0,*select_begin, *length); + text_edit->select_end = NK_CLAMP(0,*select_end, *length); + text_edit->string.buffer.allocated = (nk_size)*length; + text_edit->string.buffer.memory.size = NK_MAX_NUMBER_BUFFER; + text_edit->string.buffer.memory.ptr = dst; + text_edit->string.buffer.size = NK_MAX_NUMBER_BUFFER; + text_edit->mode = NK_TEXT_EDIT_MODE_INSERT; + nk_do_edit(ws, out, edit, NK_EDIT_FIELD|NK_EDIT_AUTO_SELECT, + filters[filter], text_edit, &style->edit, (*state == NK_PROPERTY_EDIT) ? in: 0, font); + + *length = text_edit->string.len; + *cursor = text_edit->cursor; + *select_begin = text_edit->select_start; + *select_end = text_edit->select_end; + if (text_edit->active && nk_input_is_key_pressed(in, NK_KEY_ENTER)) + text_edit->active = nk_false; + + if (active && !text_edit->active) { + /* property is now not active so convert edit text to value*/ + *state = NK_PROPERTY_DEFAULT; + buffer[*len] = '\0'; + switch (variant->kind) { + default: break; + case NK_PROPERTY_INT: + variant->value.i = nk_strtoi(buffer, 0); + variant->value.i = NK_CLAMP(variant->min_value.i, variant->value.i, variant->max_value.i); + break; + case NK_PROPERTY_FLOAT: + nk_string_float_limit(buffer, NK_MAX_FLOAT_PRECISION); + variant->value.f = nk_strtof(buffer, 0); + variant->value.f = NK_CLAMP(variant->min_value.f, variant->value.f, variant->max_value.f); + break; + case NK_PROPERTY_DOUBLE: + nk_string_float_limit(buffer, NK_MAX_FLOAT_PRECISION); + variant->value.d = nk_strtod(buffer, 0); + variant->value.d = NK_CLAMP(variant->min_value.d, variant->value.d, variant->max_value.d); + break; + } + } +} +NK_LIB struct nk_property_variant +nk_property_variant_int(int value, int min_value, int max_value, int step) +{ + struct nk_property_variant result; + result.kind = NK_PROPERTY_INT; + result.value.i = value; + result.min_value.i = min_value; + result.max_value.i = max_value; + result.step.i = step; + return result; +} +NK_LIB struct nk_property_variant +nk_property_variant_float(float value, float min_value, float max_value, float step) +{ + struct nk_property_variant result; + result.kind = NK_PROPERTY_FLOAT; + result.value.f = value; + result.min_value.f = min_value; + result.max_value.f = max_value; + result.step.f = step; + return result; +} +NK_LIB struct nk_property_variant +nk_property_variant_double(double value, double min_value, double max_value, + double step) +{ + struct nk_property_variant result; + result.kind = NK_PROPERTY_DOUBLE; + result.value.d = value; + result.min_value.d = min_value; + result.max_value.d = max_value; + result.step.d = step; + return result; +} +NK_LIB void +nk_property(struct nk_context *ctx, const char *name, struct nk_property_variant *variant, + float inc_per_pixel, const enum nk_property_filter filter) +{ + struct nk_window *win; + struct nk_panel *layout; + struct nk_input *in; + const struct nk_style *style; + + struct nk_rect bounds; + enum nk_widget_layout_states s; + + int *state = 0; + nk_hash hash = 0; + char *buffer = 0; + int *len = 0; + int *cursor = 0; + int *select_begin = 0; + int *select_end = 0; + int old_state; + + char dummy_buffer[NK_MAX_NUMBER_BUFFER]; + int dummy_state = NK_PROPERTY_DEFAULT; + int dummy_length = 0; + int dummy_cursor = 0; + int dummy_select_begin = 0; + int dummy_select_end = 0; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + style = &ctx->style; + s = nk_widget(&bounds, ctx); + if (!s) return; + + /* calculate hash from name */ + if (name[0] == '#') { + hash = nk_murmur_hash(name, (int)nk_strlen(name), win->property.seq++); + name++; /* special number hash */ + } else hash = nk_murmur_hash(name, (int)nk_strlen(name), 42); + + /* check if property is currently hot item */ + if (win->property.active && hash == win->property.name) { + buffer = win->property.buffer; + len = &win->property.length; + cursor = &win->property.cursor; + state = &win->property.state; + select_begin = &win->property.select_start; + select_end = &win->property.select_end; + } else { + buffer = dummy_buffer; + len = &dummy_length; + cursor = &dummy_cursor; + state = &dummy_state; + select_begin = &dummy_select_begin; + select_end = &dummy_select_end; + } + + /* execute property widget */ + old_state = *state; + ctx->text_edit.clip = ctx->clip; + in = ((s == NK_WIDGET_ROM && !win->property.active) || + layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + nk_do_property(&ctx->last_widget_state, &win->buffer, bounds, name, + variant, inc_per_pixel, buffer, len, state, cursor, select_begin, + select_end, &style->property, filter, in, style->font, &ctx->text_edit, + ctx->button_behavior); + + if (in && *state != NK_PROPERTY_DEFAULT && !win->property.active) { + /* current property is now hot */ + win->property.active = 1; + NK_MEMCPY(win->property.buffer, buffer, (nk_size)*len); + win->property.length = *len; + win->property.cursor = *cursor; + win->property.state = *state; + win->property.name = hash; + win->property.select_start = *select_begin; + win->property.select_end = *select_end; + if (*state == NK_PROPERTY_DRAG) { + ctx->input.mouse.grab = nk_true; + ctx->input.mouse.grabbed = nk_true; + } + } + /* check if previously active property is now inactive */ + if (*state == NK_PROPERTY_DEFAULT && old_state != NK_PROPERTY_DEFAULT) { + if (old_state == NK_PROPERTY_DRAG) { + ctx->input.mouse.grab = nk_false; + ctx->input.mouse.grabbed = nk_false; + ctx->input.mouse.ungrab = nk_true; + } + win->property.select_start = 0; + win->property.select_end = 0; + win->property.active = 0; + } +} +NK_API void +nk_property_int(struct nk_context *ctx, const char *name, + int min, int *val, int max, int step, float inc_per_pixel) +{ + struct nk_property_variant variant; + NK_ASSERT(ctx); + NK_ASSERT(name); + NK_ASSERT(val); + + if (!ctx || !ctx->current || !name || !val) return; + variant = nk_property_variant_int(*val, min, max, step); + nk_property(ctx, name, &variant, inc_per_pixel, NK_FILTER_INT); + *val = variant.value.i; +} +NK_API void +nk_property_float(struct nk_context *ctx, const char *name, + float min, float *val, float max, float step, float inc_per_pixel) +{ + struct nk_property_variant variant; + NK_ASSERT(ctx); + NK_ASSERT(name); + NK_ASSERT(val); + + if (!ctx || !ctx->current || !name || !val) return; + variant = nk_property_variant_float(*val, min, max, step); + nk_property(ctx, name, &variant, inc_per_pixel, NK_FILTER_FLOAT); + *val = variant.value.f; +} +NK_API void +nk_property_double(struct nk_context *ctx, const char *name, + double min, double *val, double max, double step, float inc_per_pixel) +{ + struct nk_property_variant variant; + NK_ASSERT(ctx); + NK_ASSERT(name); + NK_ASSERT(val); + + if (!ctx || !ctx->current || !name || !val) return; + variant = nk_property_variant_double(*val, min, max, step); + nk_property(ctx, name, &variant, inc_per_pixel, NK_FILTER_FLOAT); + *val = variant.value.d; +} +NK_API nk_bool +nk_propertyi(struct nk_context *ctx, const char *name, int min, int val, + int max, int step, float inc_per_pixel) +{ + struct nk_property_variant variant; + NK_ASSERT(ctx); + NK_ASSERT(name); + + if (!ctx || !ctx->current || !name) return val; + variant = nk_property_variant_int(val, min, max, step); + nk_property(ctx, name, &variant, inc_per_pixel, NK_FILTER_INT); + val = variant.value.i; + return val; +} +NK_API float +nk_propertyf(struct nk_context *ctx, const char *name, float min, + float val, float max, float step, float inc_per_pixel) +{ + struct nk_property_variant variant; + NK_ASSERT(ctx); + NK_ASSERT(name); + + if (!ctx || !ctx->current || !name) return val; + variant = nk_property_variant_float(val, min, max, step); + nk_property(ctx, name, &variant, inc_per_pixel, NK_FILTER_FLOAT); + val = variant.value.f; + return val; +} +NK_API double +nk_propertyd(struct nk_context *ctx, const char *name, double min, + double val, double max, double step, float inc_per_pixel) +{ + struct nk_property_variant variant; + NK_ASSERT(ctx); + NK_ASSERT(name); + + if (!ctx || !ctx->current || !name) return val; + variant = nk_property_variant_double(val, min, max, step); + nk_property(ctx, name, &variant, inc_per_pixel, NK_FILTER_FLOAT); + val = variant.value.d; + return val; +} + + + + + +/* ============================================================== + * + * CHART + * + * ===============================================================*/ +NK_API nk_bool +nk_chart_begin_colored(struct nk_context *ctx, enum nk_chart_type type, + struct nk_color color, struct nk_color highlight, + int count, float min_value, float max_value) +{ + struct nk_window *win; + struct nk_chart *chart; + const struct nk_style *config; + const struct nk_style_chart *style; + + const struct nk_style_item *background; + struct nk_rect bounds = {0, 0, 0, 0}; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + + if (!ctx || !ctx->current || !ctx->current->layout) return 0; + if (!nk_widget(&bounds, ctx)) { + chart = &ctx->current->layout->chart; + nk_zero(chart, sizeof(*chart)); + return 0; + } + + win = ctx->current; + config = &ctx->style; + chart = &win->layout->chart; + style = &config->chart; + + /* setup basic generic chart */ + nk_zero(chart, sizeof(*chart)); + chart->x = bounds.x + style->padding.x; + chart->y = bounds.y + style->padding.y; + chart->w = bounds.w - 2 * style->padding.x; + chart->h = bounds.h - 2 * style->padding.y; + chart->w = NK_MAX(chart->w, 2 * style->padding.x); + chart->h = NK_MAX(chart->h, 2 * style->padding.y); + + /* add first slot into chart */ + {struct nk_chart_slot *slot = &chart->slots[chart->slot++]; + slot->type = type; + slot->count = count; + slot->color = color; + slot->highlight = highlight; + slot->min = NK_MIN(min_value, max_value); + slot->max = NK_MAX(min_value, max_value); + slot->range = slot->max - slot->min;} + + /* draw chart background */ + background = &style->background; + if (background->type == NK_STYLE_ITEM_IMAGE) { + nk_draw_image(&win->buffer, bounds, &background->data.image, nk_white); + } else { + nk_fill_rect(&win->buffer, bounds, style->rounding, style->border_color); + nk_fill_rect(&win->buffer, nk_shrink_rect(bounds, style->border), + style->rounding, style->background.data.color); + } + return 1; +} +NK_API nk_bool +nk_chart_begin(struct nk_context *ctx, const enum nk_chart_type type, + int count, float min_value, float max_value) +{ + return nk_chart_begin_colored(ctx, type, ctx->style.chart.color, + ctx->style.chart.selected_color, count, min_value, max_value); +} +NK_API void +nk_chart_add_slot_colored(struct nk_context *ctx, const enum nk_chart_type type, + struct nk_color color, struct nk_color highlight, + int count, float min_value, float max_value) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + NK_ASSERT(ctx->current->layout->chart.slot < NK_CHART_MAX_SLOT); + if (!ctx || !ctx->current || !ctx->current->layout) return; + if (ctx->current->layout->chart.slot >= NK_CHART_MAX_SLOT) return; + + /* add another slot into the graph */ + {struct nk_chart *chart = &ctx->current->layout->chart; + struct nk_chart_slot *slot = &chart->slots[chart->slot++]; + slot->type = type; + slot->count = count; + slot->color = color; + slot->highlight = highlight; + slot->min = NK_MIN(min_value, max_value); + slot->max = NK_MAX(min_value, max_value); + slot->range = slot->max - slot->min;} +} +NK_API void +nk_chart_add_slot(struct nk_context *ctx, const enum nk_chart_type type, + int count, float min_value, float max_value) +{ + nk_chart_add_slot_colored(ctx, type, ctx->style.chart.color, + ctx->style.chart.selected_color, count, min_value, max_value); +} +NK_INTERN nk_flags +nk_chart_push_line(struct nk_context *ctx, struct nk_window *win, + struct nk_chart *g, float value, int slot) +{ + struct nk_panel *layout = win->layout; + const struct nk_input *i = &ctx->input; + struct nk_command_buffer *out = &win->buffer; + + nk_flags ret = 0; + struct nk_vec2 cur; + struct nk_rect bounds; + struct nk_color color; + float step; + float range; + float ratio; + + NK_ASSERT(slot >= 0 && slot < NK_CHART_MAX_SLOT); + step = g->w / (float)g->slots[slot].count; + range = g->slots[slot].max - g->slots[slot].min; + ratio = (value - g->slots[slot].min) / range; + + if (g->slots[slot].index == 0) { + /* first data point does not have a connection */ + g->slots[slot].last.x = g->x; + g->slots[slot].last.y = (g->y + g->h) - ratio * (float)g->h; + + bounds.x = g->slots[slot].last.x - 2; + bounds.y = g->slots[slot].last.y - 2; + bounds.w = bounds.h = 4; + + color = g->slots[slot].color; + if (!(layout->flags & NK_WINDOW_ROM) && + NK_INBOX(i->mouse.pos.x,i->mouse.pos.y, g->slots[slot].last.x-3, g->slots[slot].last.y-3, 6, 6)){ + ret = nk_input_is_mouse_hovering_rect(i, bounds) ? NK_CHART_HOVERING : 0; + ret |= (i->mouse.buttons[NK_BUTTON_LEFT].down && + i->mouse.buttons[NK_BUTTON_LEFT].clicked) ? NK_CHART_CLICKED: 0; + color = g->slots[slot].highlight; + } + nk_fill_rect(out, bounds, 0, color); + g->slots[slot].index += 1; + return ret; + } + + /* draw a line between the last data point and the new one */ + color = g->slots[slot].color; + cur.x = g->x + (float)(step * (float)g->slots[slot].index); + cur.y = (g->y + g->h) - (ratio * (float)g->h); + nk_stroke_line(out, g->slots[slot].last.x, g->slots[slot].last.y, cur.x, cur.y, 1.0f, color); + + bounds.x = cur.x - 3; + bounds.y = cur.y - 3; + bounds.w = bounds.h = 6; + + /* user selection of current data point */ + if (!(layout->flags & NK_WINDOW_ROM)) { + if (nk_input_is_mouse_hovering_rect(i, bounds)) { + ret = NK_CHART_HOVERING; + ret |= (!i->mouse.buttons[NK_BUTTON_LEFT].down && + i->mouse.buttons[NK_BUTTON_LEFT].clicked) ? NK_CHART_CLICKED: 0; + color = g->slots[slot].highlight; + } + } + nk_fill_rect(out, nk_rect(cur.x - 2, cur.y - 2, 4, 4), 0, color); + + /* save current data point position */ + g->slots[slot].last.x = cur.x; + g->slots[slot].last.y = cur.y; + g->slots[slot].index += 1; + return ret; +} +NK_INTERN nk_flags +nk_chart_push_column(const struct nk_context *ctx, struct nk_window *win, + struct nk_chart *chart, float value, int slot) +{ + struct nk_command_buffer *out = &win->buffer; + const struct nk_input *in = &ctx->input; + struct nk_panel *layout = win->layout; + + float ratio; + nk_flags ret = 0; + struct nk_color color; + struct nk_rect item = {0,0,0,0}; + + NK_ASSERT(slot >= 0 && slot < NK_CHART_MAX_SLOT); + if (chart->slots[slot].index >= chart->slots[slot].count) + return nk_false; + if (chart->slots[slot].count) { + float padding = (float)(chart->slots[slot].count-1); + item.w = (chart->w - padding) / (float)(chart->slots[slot].count); + } + + /* calculate bounds of current bar chart entry */ + color = chart->slots[slot].color;; + item.h = chart->h * NK_ABS((value/chart->slots[slot].range)); + if (value >= 0) { + ratio = (value + NK_ABS(chart->slots[slot].min)) / NK_ABS(chart->slots[slot].range); + item.y = (chart->y + chart->h) - chart->h * ratio; + } else { + ratio = (value - chart->slots[slot].max) / chart->slots[slot].range; + item.y = chart->y + (chart->h * NK_ABS(ratio)) - item.h; + } + item.x = chart->x + ((float)chart->slots[slot].index * item.w); + item.x = item.x + ((float)chart->slots[slot].index); + + /* user chart bar selection */ + if (!(layout->flags & NK_WINDOW_ROM) && + NK_INBOX(in->mouse.pos.x,in->mouse.pos.y,item.x,item.y,item.w,item.h)) { + ret = NK_CHART_HOVERING; + ret |= (!in->mouse.buttons[NK_BUTTON_LEFT].down && + in->mouse.buttons[NK_BUTTON_LEFT].clicked) ? NK_CHART_CLICKED: 0; + color = chart->slots[slot].highlight; + } + nk_fill_rect(out, item, 0, color); + chart->slots[slot].index += 1; + return ret; +} +NK_API nk_flags +nk_chart_push_slot(struct nk_context *ctx, float value, int slot) +{ + nk_flags flags; + struct nk_window *win; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(slot >= 0 && slot < NK_CHART_MAX_SLOT); + NK_ASSERT(slot < ctx->current->layout->chart.slot); + if (!ctx || !ctx->current || slot >= NK_CHART_MAX_SLOT) return nk_false; + if (slot >= ctx->current->layout->chart.slot) return nk_false; + + win = ctx->current; + if (win->layout->chart.slot < slot) return nk_false; + switch (win->layout->chart.slots[slot].type) { + case NK_CHART_LINES: + flags = nk_chart_push_line(ctx, win, &win->layout->chart, value, slot); break; + case NK_CHART_COLUMN: + flags = nk_chart_push_column(ctx, win, &win->layout->chart, value, slot); break; + default: + case NK_CHART_MAX: + flags = 0; + } + return flags; +} +NK_API nk_flags +nk_chart_push(struct nk_context *ctx, float value) +{ + return nk_chart_push_slot(ctx, value, 0); +} +NK_API void +nk_chart_end(struct nk_context *ctx) +{ + struct nk_window *win; + struct nk_chart *chart; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) + return; + + win = ctx->current; + chart = &win->layout->chart; + NK_MEMSET(chart, 0, sizeof(*chart)); + return; +} +NK_API void +nk_plot(struct nk_context *ctx, enum nk_chart_type type, const float *values, + int count, int offset) +{ + int i = 0; + float min_value; + float max_value; + + NK_ASSERT(ctx); + NK_ASSERT(values); + if (!ctx || !values || !count) return; + + min_value = values[offset]; + max_value = values[offset]; + for (i = 0; i < count; ++i) { + min_value = NK_MIN(values[i + offset], min_value); + max_value = NK_MAX(values[i + offset], max_value); + } + + if (nk_chart_begin(ctx, type, count, min_value, max_value)) { + for (i = 0; i < count; ++i) + nk_chart_push(ctx, values[i + offset]); + nk_chart_end(ctx); + } +} +NK_API void +nk_plot_function(struct nk_context *ctx, enum nk_chart_type type, void *userdata, + float(*value_getter)(void* user, int index), int count, int offset) +{ + int i = 0; + float min_value; + float max_value; + + NK_ASSERT(ctx); + NK_ASSERT(value_getter); + if (!ctx || !value_getter || !count) return; + + max_value = min_value = value_getter(userdata, offset); + for (i = 0; i < count; ++i) { + float value = value_getter(userdata, i + offset); + min_value = NK_MIN(value, min_value); + max_value = NK_MAX(value, max_value); + } + + if (nk_chart_begin(ctx, type, count, min_value, max_value)) { + for (i = 0; i < count; ++i) + nk_chart_push(ctx, value_getter(userdata, i + offset)); + nk_chart_end(ctx); + } +} + + + + + +/* ============================================================== + * + * COLOR PICKER + * + * ===============================================================*/ +NK_LIB nk_bool +nk_color_picker_behavior(nk_flags *state, + const struct nk_rect *bounds, const struct nk_rect *matrix, + const struct nk_rect *hue_bar, const struct nk_rect *alpha_bar, + struct nk_colorf *color, const struct nk_input *in) +{ + float hsva[4]; + nk_bool value_changed = 0; + nk_bool hsv_changed = 0; + + NK_ASSERT(state); + NK_ASSERT(matrix); + NK_ASSERT(hue_bar); + NK_ASSERT(color); + + /* color matrix */ + nk_colorf_hsva_fv(hsva, *color); + if (nk_button_behavior(state, *matrix, in, NK_BUTTON_REPEATER)) { + hsva[1] = NK_SATURATE((in->mouse.pos.x - matrix->x) / (matrix->w-1)); + hsva[2] = 1.0f - NK_SATURATE((in->mouse.pos.y - matrix->y) / (matrix->h-1)); + value_changed = hsv_changed = 1; + } + /* hue bar */ + if (nk_button_behavior(state, *hue_bar, in, NK_BUTTON_REPEATER)) { + hsva[0] = NK_SATURATE((in->mouse.pos.y - hue_bar->y) / (hue_bar->h-1)); + value_changed = hsv_changed = 1; + } + /* alpha bar */ + if (alpha_bar) { + if (nk_button_behavior(state, *alpha_bar, in, NK_BUTTON_REPEATER)) { + hsva[3] = 1.0f - NK_SATURATE((in->mouse.pos.y - alpha_bar->y) / (alpha_bar->h-1)); + value_changed = 1; + } + } + nk_widget_state_reset(state); + if (hsv_changed) { + *color = nk_hsva_colorfv(hsva); + *state = NK_WIDGET_STATE_ACTIVE; + } + if (value_changed) { + color->a = hsva[3]; + *state = NK_WIDGET_STATE_ACTIVE; + } + /* set color picker widget state */ + if (nk_input_is_mouse_hovering_rect(in, *bounds)) + *state = NK_WIDGET_STATE_HOVERED; + if (*state & NK_WIDGET_STATE_HOVER && !nk_input_is_mouse_prev_hovering_rect(in, *bounds)) + *state |= NK_WIDGET_STATE_ENTERED; + else if (nk_input_is_mouse_prev_hovering_rect(in, *bounds)) + *state |= NK_WIDGET_STATE_LEFT; + return value_changed; +} +NK_LIB void +nk_draw_color_picker(struct nk_command_buffer *o, const struct nk_rect *matrix, + const struct nk_rect *hue_bar, const struct nk_rect *alpha_bar, + struct nk_colorf col) +{ + NK_STORAGE const struct nk_color black = {0,0,0,255}; + NK_STORAGE const struct nk_color white = {255, 255, 255, 255}; + NK_STORAGE const struct nk_color black_trans = {0,0,0,0}; + + const float crosshair_size = 7.0f; + struct nk_color temp; + float hsva[4]; + float line_y; + int i; + + NK_ASSERT(o); + NK_ASSERT(matrix); + NK_ASSERT(hue_bar); + + /* draw hue bar */ + nk_colorf_hsva_fv(hsva, col); + for (i = 0; i < 6; ++i) { + NK_GLOBAL const struct nk_color hue_colors[] = { + {255, 0, 0, 255}, {255,255,0,255}, {0,255,0,255}, {0, 255,255,255}, + {0,0,255,255}, {255, 0, 255, 255}, {255, 0, 0, 255} + }; + nk_fill_rect_multi_color(o, + nk_rect(hue_bar->x, hue_bar->y + (float)i * (hue_bar->h/6.0f) + 0.5f, + hue_bar->w, (hue_bar->h/6.0f) + 0.5f), hue_colors[i], hue_colors[i], + hue_colors[i+1], hue_colors[i+1]); + } + line_y = (float)(int)(hue_bar->y + hsva[0] * matrix->h + 0.5f); + nk_stroke_line(o, hue_bar->x-1, line_y, hue_bar->x + hue_bar->w + 2, + line_y, 1, nk_rgb(255,255,255)); + + /* draw alpha bar */ + if (alpha_bar) { + float alpha = NK_SATURATE(col.a); + line_y = (float)(int)(alpha_bar->y + (1.0f - alpha) * matrix->h + 0.5f); + + nk_fill_rect_multi_color(o, *alpha_bar, white, white, black, black); + nk_stroke_line(o, alpha_bar->x-1, line_y, alpha_bar->x + alpha_bar->w + 2, + line_y, 1, nk_rgb(255,255,255)); + } + + /* draw color matrix */ + temp = nk_hsv_f(hsva[0], 1.0f, 1.0f); + nk_fill_rect_multi_color(o, *matrix, white, temp, temp, white); + nk_fill_rect_multi_color(o, *matrix, black_trans, black_trans, black, black); + + /* draw cross-hair */ + {struct nk_vec2 p; float S = hsva[1]; float V = hsva[2]; + p.x = (float)(int)(matrix->x + S * matrix->w); + p.y = (float)(int)(matrix->y + (1.0f - V) * matrix->h); + nk_stroke_line(o, p.x - crosshair_size, p.y, p.x-2, p.y, 1.0f, white); + nk_stroke_line(o, p.x + crosshair_size + 1, p.y, p.x+3, p.y, 1.0f, white); + nk_stroke_line(o, p.x, p.y + crosshair_size + 1, p.x, p.y+3, 1.0f, white); + nk_stroke_line(o, p.x, p.y - crosshair_size, p.x, p.y-2, 1.0f, white);} +} +NK_LIB nk_bool +nk_do_color_picker(nk_flags *state, + struct nk_command_buffer *out, struct nk_colorf *col, + enum nk_color_format fmt, struct nk_rect bounds, + struct nk_vec2 padding, const struct nk_input *in, + const struct nk_user_font *font) +{ + int ret = 0; + struct nk_rect matrix; + struct nk_rect hue_bar; + struct nk_rect alpha_bar; + float bar_w; + + NK_ASSERT(out); + NK_ASSERT(col); + NK_ASSERT(state); + NK_ASSERT(font); + if (!out || !col || !state || !font) + return ret; + + bar_w = font->height; + bounds.x += padding.x; + bounds.y += padding.x; + bounds.w -= 2 * padding.x; + bounds.h -= 2 * padding.y; + + matrix.x = bounds.x; + matrix.y = bounds.y; + matrix.h = bounds.h; + matrix.w = bounds.w - (3 * padding.x + 2 * bar_w); + + hue_bar.w = bar_w; + hue_bar.y = bounds.y; + hue_bar.h = matrix.h; + hue_bar.x = matrix.x + matrix.w + padding.x; + + alpha_bar.x = hue_bar.x + hue_bar.w + padding.x; + alpha_bar.y = bounds.y; + alpha_bar.w = bar_w; + alpha_bar.h = matrix.h; + + ret = nk_color_picker_behavior(state, &bounds, &matrix, &hue_bar, + (fmt == NK_RGBA) ? &alpha_bar:0, col, in); + nk_draw_color_picker(out, &matrix, &hue_bar, (fmt == NK_RGBA) ? &alpha_bar:0, *col); + return ret; +} +NK_API nk_bool +nk_color_pick(struct nk_context * ctx, struct nk_colorf *color, + enum nk_color_format fmt) +{ + struct nk_window *win; + struct nk_panel *layout; + const struct nk_style *config; + const struct nk_input *in; + + enum nk_widget_layout_states state; + struct nk_rect bounds; + + NK_ASSERT(ctx); + NK_ASSERT(color); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout || !color) + return 0; + + win = ctx->current; + config = &ctx->style; + layout = win->layout; + state = nk_widget(&bounds, ctx); + if (!state) return 0; + in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input; + return nk_do_color_picker(&ctx->last_widget_state, &win->buffer, color, fmt, bounds, + nk_vec2(0,0), in, config->font); +} +NK_API struct nk_colorf +nk_color_picker(struct nk_context *ctx, struct nk_colorf color, + enum nk_color_format fmt) +{ + nk_color_pick(ctx, &color, fmt); + return color; +} + + + + + +/* ============================================================== + * + * COMBO + * + * ===============================================================*/ +NK_INTERN nk_bool +nk_combo_begin(struct nk_context *ctx, struct nk_window *win, + struct nk_vec2 size, nk_bool is_clicked, struct nk_rect header) +{ + struct nk_window *popup; + int is_open = 0; + int is_active = 0; + struct nk_rect body; + nk_hash hash; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + popup = win->popup.win; + body.x = header.x; + body.w = size.x; + body.y = header.y + header.h-ctx->style.window.combo_border; + body.h = size.y; + + hash = win->popup.combo_count++; + is_open = (popup) ? nk_true:nk_false; + is_active = (popup && (win->popup.name == hash) && win->popup.type == NK_PANEL_COMBO); + if ((is_clicked && is_open && !is_active) || (is_open && !is_active) || + (!is_open && !is_active && !is_clicked)) return 0; + if (!nk_nonblock_begin(ctx, 0, body, + (is_clicked && is_open)?nk_rect(0,0,0,0):header, NK_PANEL_COMBO)) return 0; + + win->popup.type = NK_PANEL_COMBO; + win->popup.name = hash; + return 1; +} +NK_API nk_bool +nk_combo_begin_text(struct nk_context *ctx, const char *selected, int len, + struct nk_vec2 size) +{ + const struct nk_input *in; + struct nk_window *win; + struct nk_style *style; + + enum nk_widget_layout_states s; + int is_clicked = nk_false; + struct nk_rect header; + const struct nk_style_item *background; + struct nk_text text; + + NK_ASSERT(ctx); + NK_ASSERT(selected); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout || !selected) + return 0; + + win = ctx->current; + style = &ctx->style; + s = nk_widget(&header, ctx); + if (s == NK_WIDGET_INVALID) + return 0; + + in = (win->layout->flags & NK_WINDOW_ROM || s == NK_WIDGET_ROM)? 0: &ctx->input; + if (nk_button_behavior(&ctx->last_widget_state, header, in, NK_BUTTON_DEFAULT)) + is_clicked = nk_true; + + /* draw combo box header background and border */ + if (ctx->last_widget_state & NK_WIDGET_STATE_ACTIVED) { + background = &style->combo.active; + text.text = style->combo.label_active; + } else if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) { + background = &style->combo.hover; + text.text = style->combo.label_hover; + } else { + background = &style->combo.normal; + text.text = style->combo.label_normal; + } + if (background->type == NK_STYLE_ITEM_IMAGE) { + text.background = nk_rgba(0,0,0,0); + nk_draw_image(&win->buffer, header, &background->data.image, nk_white); + } else { + text.background = background->data.color; + nk_fill_rect(&win->buffer, header, style->combo.rounding, background->data.color); + nk_stroke_rect(&win->buffer, header, style->combo.rounding, style->combo.border, style->combo.border_color); + } + { + /* print currently selected text item */ + struct nk_rect label; + struct nk_rect button; + struct nk_rect content; + int draw_button_symbol; + + enum nk_symbol_type sym; + if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) + sym = style->combo.sym_hover; + else if (is_clicked) + sym = style->combo.sym_active; + else + sym = style->combo.sym_normal; + + /* represents whether or not the combo's button symbol should be drawn */ + draw_button_symbol = sym != NK_SYMBOL_NONE; + + /* calculate button */ + button.w = header.h - 2 * style->combo.button_padding.y; + button.x = (header.x + header.w - header.h) - style->combo.button_padding.x; + button.y = header.y + style->combo.button_padding.y; + button.h = button.w; + + content.x = button.x + style->combo.button.padding.x; + content.y = button.y + style->combo.button.padding.y; + content.w = button.w - 2 * style->combo.button.padding.x; + content.h = button.h - 2 * style->combo.button.padding.y; + + /* draw selected label */ + text.padding = nk_vec2(0,0); + label.x = header.x + style->combo.content_padding.x; + label.y = header.y + style->combo.content_padding.y; + label.h = header.h - 2 * style->combo.content_padding.y; + if (draw_button_symbol) + label.w = button.x - (style->combo.content_padding.x + style->combo.spacing.x) - label.x; + else + label.w = header.w - 2 * style->combo.content_padding.x; + nk_widget_text(&win->buffer, label, selected, len, &text, + NK_TEXT_LEFT, ctx->style.font); + + /* draw open/close button */ + if (draw_button_symbol) + nk_draw_button_symbol(&win->buffer, &button, &content, ctx->last_widget_state, + &ctx->style.combo.button, sym, style->font); + } + return nk_combo_begin(ctx, win, size, is_clicked, header); +} +NK_API nk_bool +nk_combo_begin_label(struct nk_context *ctx, const char *selected, struct nk_vec2 size) +{ + return nk_combo_begin_text(ctx, selected, nk_strlen(selected), size); +} +NK_API nk_bool +nk_combo_begin_color(struct nk_context *ctx, struct nk_color color, struct nk_vec2 size) +{ + struct nk_window *win; + struct nk_style *style; + const struct nk_input *in; + + struct nk_rect header; + int is_clicked = nk_false; + enum nk_widget_layout_states s; + const struct nk_style_item *background; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + style = &ctx->style; + s = nk_widget(&header, ctx); + if (s == NK_WIDGET_INVALID) + return 0; + + in = (win->layout->flags & NK_WINDOW_ROM || s == NK_WIDGET_ROM)? 0: &ctx->input; + if (nk_button_behavior(&ctx->last_widget_state, header, in, NK_BUTTON_DEFAULT)) + is_clicked = nk_true; + + /* draw combo box header background and border */ + if (ctx->last_widget_state & NK_WIDGET_STATE_ACTIVED) + background = &style->combo.active; + else if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) + background = &style->combo.hover; + else background = &style->combo.normal; + + if (background->type == NK_STYLE_ITEM_IMAGE) { + nk_draw_image(&win->buffer, header, &background->data.image,nk_white); + } else { + nk_fill_rect(&win->buffer, header, style->combo.rounding, background->data.color); + nk_stroke_rect(&win->buffer, header, style->combo.rounding, style->combo.border, style->combo.border_color); + } + { + struct nk_rect content; + struct nk_rect button; + struct nk_rect bounds; + int draw_button_symbol; + + enum nk_symbol_type sym; + if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) + sym = style->combo.sym_hover; + else if (is_clicked) + sym = style->combo.sym_active; + else sym = style->combo.sym_normal; + + /* represents whether or not the combo's button symbol should be drawn */ + draw_button_symbol = sym != NK_SYMBOL_NONE; + + /* calculate button */ + button.w = header.h - 2 * style->combo.button_padding.y; + button.x = (header.x + header.w - header.h) - style->combo.button_padding.x; + button.y = header.y + style->combo.button_padding.y; + button.h = button.w; + + content.x = button.x + style->combo.button.padding.x; + content.y = button.y + style->combo.button.padding.y; + content.w = button.w - 2 * style->combo.button.padding.x; + content.h = button.h - 2 * style->combo.button.padding.y; + + /* draw color */ + bounds.h = header.h - 4 * style->combo.content_padding.y; + bounds.y = header.y + 2 * style->combo.content_padding.y; + bounds.x = header.x + 2 * style->combo.content_padding.x; + if (draw_button_symbol) + bounds.w = (button.x - (style->combo.content_padding.x + style->combo.spacing.x)) - bounds.x; + else + bounds.w = header.w - 4 * style->combo.content_padding.x; + nk_fill_rect(&win->buffer, bounds, 0, color); + + /* draw open/close button */ + if (draw_button_symbol) + nk_draw_button_symbol(&win->buffer, &button, &content, ctx->last_widget_state, + &ctx->style.combo.button, sym, style->font); + } + return nk_combo_begin(ctx, win, size, is_clicked, header); +} +NK_API nk_bool +nk_combo_begin_symbol(struct nk_context *ctx, enum nk_symbol_type symbol, struct nk_vec2 size) +{ + struct nk_window *win; + struct nk_style *style; + const struct nk_input *in; + + struct nk_rect header; + int is_clicked = nk_false; + enum nk_widget_layout_states s; + const struct nk_style_item *background; + struct nk_color sym_background; + struct nk_color symbol_color; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + style = &ctx->style; + s = nk_widget(&header, ctx); + if (s == NK_WIDGET_INVALID) + return 0; + + in = (win->layout->flags & NK_WINDOW_ROM || s == NK_WIDGET_ROM)? 0: &ctx->input; + if (nk_button_behavior(&ctx->last_widget_state, header, in, NK_BUTTON_DEFAULT)) + is_clicked = nk_true; + + /* draw combo box header background and border */ + if (ctx->last_widget_state & NK_WIDGET_STATE_ACTIVED) { + background = &style->combo.active; + symbol_color = style->combo.symbol_active; + } else if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) { + background = &style->combo.hover; + symbol_color = style->combo.symbol_hover; + } else { + background = &style->combo.normal; + symbol_color = style->combo.symbol_hover; + } + + if (background->type == NK_STYLE_ITEM_IMAGE) { + sym_background = nk_rgba(0,0,0,0); + nk_draw_image(&win->buffer, header, &background->data.image, nk_white); + } else { + sym_background = background->data.color; + nk_fill_rect(&win->buffer, header, style->combo.rounding, background->data.color); + nk_stroke_rect(&win->buffer, header, style->combo.rounding, style->combo.border, style->combo.border_color); + } + { + struct nk_rect bounds = {0,0,0,0}; + struct nk_rect content; + struct nk_rect button; + + enum nk_symbol_type sym; + if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) + sym = style->combo.sym_hover; + else if (is_clicked) + sym = style->combo.sym_active; + else sym = style->combo.sym_normal; + + /* calculate button */ + button.w = header.h - 2 * style->combo.button_padding.y; + button.x = (header.x + header.w - header.h) - style->combo.button_padding.y; + button.y = header.y + style->combo.button_padding.y; + button.h = button.w; + + content.x = button.x + style->combo.button.padding.x; + content.y = button.y + style->combo.button.padding.y; + content.w = button.w - 2 * style->combo.button.padding.x; + content.h = button.h - 2 * style->combo.button.padding.y; + + /* draw symbol */ + bounds.h = header.h - 2 * style->combo.content_padding.y; + bounds.y = header.y + style->combo.content_padding.y; + bounds.x = header.x + style->combo.content_padding.x; + bounds.w = (button.x - style->combo.content_padding.y) - bounds.x; + nk_draw_symbol(&win->buffer, symbol, bounds, sym_background, symbol_color, + 1.0f, style->font); + + /* draw open/close button */ + nk_draw_button_symbol(&win->buffer, &bounds, &content, ctx->last_widget_state, + &ctx->style.combo.button, sym, style->font); + } + return nk_combo_begin(ctx, win, size, is_clicked, header); +} +NK_API nk_bool +nk_combo_begin_symbol_text(struct nk_context *ctx, const char *selected, int len, + enum nk_symbol_type symbol, struct nk_vec2 size) +{ + struct nk_window *win; + struct nk_style *style; + struct nk_input *in; + + struct nk_rect header; + int is_clicked = nk_false; + enum nk_widget_layout_states s; + const struct nk_style_item *background; + struct nk_color symbol_color; + struct nk_text text; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + style = &ctx->style; + s = nk_widget(&header, ctx); + if (!s) return 0; + + in = (win->layout->flags & NK_WINDOW_ROM || s == NK_WIDGET_ROM)? 0: &ctx->input; + if (nk_button_behavior(&ctx->last_widget_state, header, in, NK_BUTTON_DEFAULT)) + is_clicked = nk_true; + + /* draw combo box header background and border */ + if (ctx->last_widget_state & NK_WIDGET_STATE_ACTIVED) { + background = &style->combo.active; + symbol_color = style->combo.symbol_active; + text.text = style->combo.label_active; + } else if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) { + background = &style->combo.hover; + symbol_color = style->combo.symbol_hover; + text.text = style->combo.label_hover; + } else { + background = &style->combo.normal; + symbol_color = style->combo.symbol_normal; + text.text = style->combo.label_normal; + } + if (background->type == NK_STYLE_ITEM_IMAGE) { + text.background = nk_rgba(0,0,0,0); + nk_draw_image(&win->buffer, header, &background->data.image, nk_white); + } else { + text.background = background->data.color; + nk_fill_rect(&win->buffer, header, style->combo.rounding, background->data.color); + nk_stroke_rect(&win->buffer, header, style->combo.rounding, style->combo.border, style->combo.border_color); + } + { + struct nk_rect content; + struct nk_rect button; + struct nk_rect label; + struct nk_rect image; + + enum nk_symbol_type sym; + if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) + sym = style->combo.sym_hover; + else if (is_clicked) + sym = style->combo.sym_active; + else sym = style->combo.sym_normal; + + /* calculate button */ + button.w = header.h - 2 * style->combo.button_padding.y; + button.x = (header.x + header.w - header.h) - style->combo.button_padding.x; + button.y = header.y + style->combo.button_padding.y; + button.h = button.w; + + content.x = button.x + style->combo.button.padding.x; + content.y = button.y + style->combo.button.padding.y; + content.w = button.w - 2 * style->combo.button.padding.x; + content.h = button.h - 2 * style->combo.button.padding.y; + nk_draw_button_symbol(&win->buffer, &button, &content, ctx->last_widget_state, + &ctx->style.combo.button, sym, style->font); + + /* draw symbol */ + image.x = header.x + style->combo.content_padding.x; + image.y = header.y + style->combo.content_padding.y; + image.h = header.h - 2 * style->combo.content_padding.y; + image.w = image.h; + nk_draw_symbol(&win->buffer, symbol, image, text.background, symbol_color, + 1.0f, style->font); + + /* draw label */ + text.padding = nk_vec2(0,0); + label.x = image.x + image.w + style->combo.spacing.x + style->combo.content_padding.x; + label.y = header.y + style->combo.content_padding.y; + label.w = (button.x - style->combo.content_padding.x) - label.x; + label.h = header.h - 2 * style->combo.content_padding.y; + nk_widget_text(&win->buffer, label, selected, len, &text, NK_TEXT_LEFT, style->font); + } + return nk_combo_begin(ctx, win, size, is_clicked, header); +} +NK_API nk_bool +nk_combo_begin_image(struct nk_context *ctx, struct nk_image img, struct nk_vec2 size) +{ + struct nk_window *win; + struct nk_style *style; + const struct nk_input *in; + + struct nk_rect header; + int is_clicked = nk_false; + enum nk_widget_layout_states s; + const struct nk_style_item *background; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + style = &ctx->style; + s = nk_widget(&header, ctx); + if (s == NK_WIDGET_INVALID) + return 0; + + in = (win->layout->flags & NK_WINDOW_ROM || s == NK_WIDGET_ROM)? 0: &ctx->input; + if (nk_button_behavior(&ctx->last_widget_state, header, in, NK_BUTTON_DEFAULT)) + is_clicked = nk_true; + + /* draw combo box header background and border */ + if (ctx->last_widget_state & NK_WIDGET_STATE_ACTIVED) + background = &style->combo.active; + else if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) + background = &style->combo.hover; + else background = &style->combo.normal; + + if (background->type == NK_STYLE_ITEM_IMAGE) { + nk_draw_image(&win->buffer, header, &background->data.image, nk_white); + } else { + nk_fill_rect(&win->buffer, header, style->combo.rounding, background->data.color); + nk_stroke_rect(&win->buffer, header, style->combo.rounding, style->combo.border, style->combo.border_color); + } + { + struct nk_rect bounds = {0,0,0,0}; + struct nk_rect content; + struct nk_rect button; + int draw_button_symbol; + + enum nk_symbol_type sym; + if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) + sym = style->combo.sym_hover; + else if (is_clicked) + sym = style->combo.sym_active; + else sym = style->combo.sym_normal; + + /* represents whether or not the combo's button symbol should be drawn */ + draw_button_symbol = sym != NK_SYMBOL_NONE; + + /* calculate button */ + button.w = header.h - 2 * style->combo.button_padding.y; + button.x = (header.x + header.w - header.h) - style->combo.button_padding.y; + button.y = header.y + style->combo.button_padding.y; + button.h = button.w; + + content.x = button.x + style->combo.button.padding.x; + content.y = button.y + style->combo.button.padding.y; + content.w = button.w - 2 * style->combo.button.padding.x; + content.h = button.h - 2 * style->combo.button.padding.y; + + /* draw image */ + bounds.h = header.h - 2 * style->combo.content_padding.y; + bounds.y = header.y + style->combo.content_padding.y; + bounds.x = header.x + style->combo.content_padding.x; + if (draw_button_symbol) + bounds.w = (button.x - style->combo.content_padding.y) - bounds.x; + else + bounds.w = header.w - 2 * style->combo.content_padding.x; + nk_draw_image(&win->buffer, bounds, &img, nk_white); + + /* draw open/close button */ + if (draw_button_symbol) + nk_draw_button_symbol(&win->buffer, &bounds, &content, ctx->last_widget_state, + &ctx->style.combo.button, sym, style->font); + } + return nk_combo_begin(ctx, win, size, is_clicked, header); +} +NK_API nk_bool +nk_combo_begin_image_text(struct nk_context *ctx, const char *selected, int len, + struct nk_image img, struct nk_vec2 size) +{ + struct nk_window *win; + struct nk_style *style; + struct nk_input *in; + + struct nk_rect header; + int is_clicked = nk_false; + enum nk_widget_layout_states s; + const struct nk_style_item *background; + struct nk_text text; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + style = &ctx->style; + s = nk_widget(&header, ctx); + if (!s) return 0; + + in = (win->layout->flags & NK_WINDOW_ROM || s == NK_WIDGET_ROM)? 0: &ctx->input; + if (nk_button_behavior(&ctx->last_widget_state, header, in, NK_BUTTON_DEFAULT)) + is_clicked = nk_true; + + /* draw combo box header background and border */ + if (ctx->last_widget_state & NK_WIDGET_STATE_ACTIVED) { + background = &style->combo.active; + text.text = style->combo.label_active; + } else if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) { + background = &style->combo.hover; + text.text = style->combo.label_hover; + } else { + background = &style->combo.normal; + text.text = style->combo.label_normal; + } + if (background->type == NK_STYLE_ITEM_IMAGE) { + text.background = nk_rgba(0,0,0,0); + nk_draw_image(&win->buffer, header, &background->data.image, nk_white); + } else { + text.background = background->data.color; + nk_fill_rect(&win->buffer, header, style->combo.rounding, background->data.color); + nk_stroke_rect(&win->buffer, header, style->combo.rounding, style->combo.border, style->combo.border_color); + } + { + struct nk_rect content; + struct nk_rect button; + struct nk_rect label; + struct nk_rect image; + int draw_button_symbol; + + enum nk_symbol_type sym; + if (ctx->last_widget_state & NK_WIDGET_STATE_HOVER) + sym = style->combo.sym_hover; + else if (is_clicked) + sym = style->combo.sym_active; + else sym = style->combo.sym_normal; + + /* represents whether or not the combo's button symbol should be drawn */ + draw_button_symbol = sym != NK_SYMBOL_NONE; + + /* calculate button */ + button.w = header.h - 2 * style->combo.button_padding.y; + button.x = (header.x + header.w - header.h) - style->combo.button_padding.x; + button.y = header.y + style->combo.button_padding.y; + button.h = button.w; + + content.x = button.x + style->combo.button.padding.x; + content.y = button.y + style->combo.button.padding.y; + content.w = button.w - 2 * style->combo.button.padding.x; + content.h = button.h - 2 * style->combo.button.padding.y; + if (draw_button_symbol) + nk_draw_button_symbol(&win->buffer, &button, &content, ctx->last_widget_state, + &ctx->style.combo.button, sym, style->font); + + /* draw image */ + image.x = header.x + style->combo.content_padding.x; + image.y = header.y + style->combo.content_padding.y; + image.h = header.h - 2 * style->combo.content_padding.y; + image.w = image.h; + nk_draw_image(&win->buffer, image, &img, nk_white); + + /* draw label */ + text.padding = nk_vec2(0,0); + label.x = image.x + image.w + style->combo.spacing.x + style->combo.content_padding.x; + label.y = header.y + style->combo.content_padding.y; + label.h = header.h - 2 * style->combo.content_padding.y; + if (draw_button_symbol) + label.w = (button.x - style->combo.content_padding.x) - label.x; + else + label.w = (header.x + header.w - style->combo.content_padding.x) - label.x; + nk_widget_text(&win->buffer, label, selected, len, &text, NK_TEXT_LEFT, style->font); + } + return nk_combo_begin(ctx, win, size, is_clicked, header); +} +NK_API nk_bool +nk_combo_begin_symbol_label(struct nk_context *ctx, + const char *selected, enum nk_symbol_type type, struct nk_vec2 size) +{ + return nk_combo_begin_symbol_text(ctx, selected, nk_strlen(selected), type, size); +} +NK_API nk_bool +nk_combo_begin_image_label(struct nk_context *ctx, + const char *selected, struct nk_image img, struct nk_vec2 size) +{ + return nk_combo_begin_image_text(ctx, selected, nk_strlen(selected), img, size); +} +NK_API nk_bool +nk_combo_item_text(struct nk_context *ctx, const char *text, int len,nk_flags align) +{ + return nk_contextual_item_text(ctx, text, len, align); +} +NK_API nk_bool +nk_combo_item_label(struct nk_context *ctx, const char *label, nk_flags align) +{ + return nk_contextual_item_label(ctx, label, align); +} +NK_API nk_bool +nk_combo_item_image_text(struct nk_context *ctx, struct nk_image img, const char *text, + int len, nk_flags alignment) +{ + return nk_contextual_item_image_text(ctx, img, text, len, alignment); +} +NK_API nk_bool +nk_combo_item_image_label(struct nk_context *ctx, struct nk_image img, + const char *text, nk_flags alignment) +{ + return nk_contextual_item_image_label(ctx, img, text, alignment); +} +NK_API nk_bool +nk_combo_item_symbol_text(struct nk_context *ctx, enum nk_symbol_type sym, + const char *text, int len, nk_flags alignment) +{ + return nk_contextual_item_symbol_text(ctx, sym, text, len, alignment); +} +NK_API nk_bool +nk_combo_item_symbol_label(struct nk_context *ctx, enum nk_symbol_type sym, + const char *label, nk_flags alignment) +{ + return nk_contextual_item_symbol_label(ctx, sym, label, alignment); +} +NK_API void nk_combo_end(struct nk_context *ctx) +{ + nk_contextual_end(ctx); +} +NK_API void nk_combo_close(struct nk_context *ctx) +{ + nk_contextual_close(ctx); +} +NK_API int +nk_combo(struct nk_context *ctx, const char **items, int count, + int selected, int item_height, struct nk_vec2 size) +{ + int i = 0; + int max_height; + struct nk_vec2 item_spacing; + struct nk_vec2 window_padding; + + NK_ASSERT(ctx); + NK_ASSERT(items); + NK_ASSERT(ctx->current); + if (!ctx || !items ||!count) + return selected; + + item_spacing = ctx->style.window.spacing; + window_padding = nk_panel_get_padding(&ctx->style, ctx->current->layout->type); + max_height = count * item_height + count * (int)item_spacing.y; + max_height += (int)item_spacing.y * 2 + (int)window_padding.y * 2; + size.y = NK_MIN(size.y, (float)max_height); + if (nk_combo_begin_label(ctx, items[selected], size)) { + nk_layout_row_dynamic(ctx, (float)item_height, 1); + for (i = 0; i < count; ++i) { + if (nk_combo_item_label(ctx, items[i], NK_TEXT_LEFT)) + selected = i; + } + nk_combo_end(ctx); + } + return selected; +} +NK_API int +nk_combo_separator(struct nk_context *ctx, const char *items_separated_by_separator, + int separator, int selected, int count, int item_height, struct nk_vec2 size) +{ + int i; + int max_height; + struct nk_vec2 item_spacing; + struct nk_vec2 window_padding; + const char *current_item; + const char *iter; + int length = 0; + + NK_ASSERT(ctx); + NK_ASSERT(items_separated_by_separator); + if (!ctx || !items_separated_by_separator) + return selected; + + /* calculate popup window */ + item_spacing = ctx->style.window.spacing; + window_padding = nk_panel_get_padding(&ctx->style, ctx->current->layout->type); + max_height = count * item_height + count * (int)item_spacing.y; + max_height += (int)item_spacing.y * 2 + (int)window_padding.y * 2; + size.y = NK_MIN(size.y, (float)max_height); + + /* find selected item */ + current_item = items_separated_by_separator; + for (i = 0; i < count; ++i) { + iter = current_item; + while (*iter && *iter != separator) iter++; + length = (int)(iter - current_item); + if (i == selected) break; + current_item = iter + 1; + } + + if (nk_combo_begin_text(ctx, current_item, length, size)) { + current_item = items_separated_by_separator; + nk_layout_row_dynamic(ctx, (float)item_height, 1); + for (i = 0; i < count; ++i) { + iter = current_item; + while (*iter && *iter != separator) iter++; + length = (int)(iter - current_item); + if (nk_combo_item_text(ctx, current_item, length, NK_TEXT_LEFT)) + selected = i; + current_item = current_item + length + 1; + } + nk_combo_end(ctx); + } + return selected; +} +NK_API int +nk_combo_string(struct nk_context *ctx, const char *items_separated_by_zeros, + int selected, int count, int item_height, struct nk_vec2 size) +{ + return nk_combo_separator(ctx, items_separated_by_zeros, '\0', selected, count, item_height, size); +} +NK_API int +nk_combo_callback(struct nk_context *ctx, void(*item_getter)(void*, int, const char**), + void *userdata, int selected, int count, int item_height, struct nk_vec2 size) +{ + int i; + int max_height; + struct nk_vec2 item_spacing; + struct nk_vec2 window_padding; + const char *item; + + NK_ASSERT(ctx); + NK_ASSERT(item_getter); + if (!ctx || !item_getter) + return selected; + + /* calculate popup window */ + item_spacing = ctx->style.window.spacing; + window_padding = nk_panel_get_padding(&ctx->style, ctx->current->layout->type); + max_height = count * item_height + count * (int)item_spacing.y; + max_height += (int)item_spacing.y * 2 + (int)window_padding.y * 2; + size.y = NK_MIN(size.y, (float)max_height); + + item_getter(userdata, selected, &item); + if (nk_combo_begin_label(ctx, item, size)) { + nk_layout_row_dynamic(ctx, (float)item_height, 1); + for (i = 0; i < count; ++i) { + item_getter(userdata, i, &item); + if (nk_combo_item_label(ctx, item, NK_TEXT_LEFT)) + selected = i; + } + nk_combo_end(ctx); + } return selected; +} +NK_API void +nk_combobox(struct nk_context *ctx, const char **items, int count, + int *selected, int item_height, struct nk_vec2 size) +{ + *selected = nk_combo(ctx, items, count, *selected, item_height, size); +} +NK_API void +nk_combobox_string(struct nk_context *ctx, const char *items_separated_by_zeros, + int *selected, int count, int item_height, struct nk_vec2 size) +{ + *selected = nk_combo_string(ctx, items_separated_by_zeros, *selected, count, item_height, size); +} +NK_API void +nk_combobox_separator(struct nk_context *ctx, const char *items_separated_by_separator, + int separator, int *selected, int count, int item_height, struct nk_vec2 size) +{ + *selected = nk_combo_separator(ctx, items_separated_by_separator, separator, + *selected, count, item_height, size); +} +NK_API void +nk_combobox_callback(struct nk_context *ctx, + void(*item_getter)(void* data, int id, const char **out_text), + void *userdata, int *selected, int count, int item_height, struct nk_vec2 size) +{ + *selected = nk_combo_callback(ctx, item_getter, userdata, *selected, count, item_height, size); +} + + + + + +/* =============================================================== + * + * TOOLTIP + * + * ===============================================================*/ +NK_API nk_bool +nk_tooltip_begin(struct nk_context *ctx, float width) +{ + int x,y,w,h; + struct nk_window *win; + const struct nk_input *in; + struct nk_rect bounds; + int ret; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + /* make sure that no nonblocking popup is currently active */ + win = ctx->current; + in = &ctx->input; + if (win->popup.win && (win->popup.type & NK_PANEL_SET_NONBLOCK)) + return 0; + + w = nk_iceilf(width); + h = nk_iceilf(nk_null_rect.h); + x = nk_ifloorf(in->mouse.pos.x + 1) - (int)win->layout->clip.x; + y = nk_ifloorf(in->mouse.pos.y + 1) - (int)win->layout->clip.y; + + bounds.x = (float)x; + bounds.y = (float)y; + bounds.w = (float)w; + bounds.h = (float)h; + + ret = nk_popup_begin(ctx, NK_POPUP_DYNAMIC, + "__##Tooltip##__", NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER, bounds); + if (ret) win->layout->flags &= ~(nk_flags)NK_WINDOW_ROM; + win->popup.type = NK_PANEL_TOOLTIP; + ctx->current->layout->type = NK_PANEL_TOOLTIP; + return ret; +} + +NK_API void +nk_tooltip_end(struct nk_context *ctx) +{ + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + if (!ctx || !ctx->current) return; + ctx->current->seq--; + nk_popup_close(ctx); + nk_popup_end(ctx); +} +NK_API void +nk_tooltip(struct nk_context *ctx, const char *text) +{ + const struct nk_style *style; + struct nk_vec2 padding; + + int text_len; + float text_width; + float text_height; + + NK_ASSERT(ctx); + NK_ASSERT(ctx->current); + NK_ASSERT(ctx->current->layout); + NK_ASSERT(text); + if (!ctx || !ctx->current || !ctx->current->layout || !text) + return; + + /* fetch configuration data */ + style = &ctx->style; + padding = style->window.padding; + + /* calculate size of the text and tooltip */ + text_len = nk_strlen(text); + text_width = style->font->width(style->font->userdata, + style->font->height, text, text_len); + text_width += (4 * padding.x); + text_height = (style->font->height + 2 * padding.y); + + /* execute tooltip and fill with text */ + if (nk_tooltip_begin(ctx, (float)text_width)) { + nk_layout_row_dynamic(ctx, (float)text_height, 1); + nk_text(ctx, text, text_len, NK_TEXT_LEFT); + nk_tooltip_end(ctx); + } +} +#ifdef NK_INCLUDE_STANDARD_VARARGS +NK_API void +nk_tooltipf(struct nk_context *ctx, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + nk_tooltipfv(ctx, fmt, args); + va_end(args); +} +NK_API void +nk_tooltipfv(struct nk_context *ctx, const char *fmt, va_list args) +{ + char buf[256]; + nk_strfmt(buf, NK_LEN(buf), fmt, args); + nk_tooltip(ctx, buf); +} +#endif + + + +#endif /* NK_IMPLEMENTATION */ + +/* +/// ## License +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~none +/// ------------------------------------------------------------------------------ +/// This software is available under 2 licenses -- choose whichever you prefer. +/// ------------------------------------------------------------------------------ +/// ALTERNATIVE A - MIT License +/// Copyright (c) 2016-2018 Micha Mettke +/// Permission is hereby granted, free of charge, to any person obtaining a copy of +/// this software and associated documentation files (the "Software"), to deal in +/// the Software without restriction, including without limitation the rights to +/// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +/// of the Software, and to permit persons to whom the Software is furnished to do +/// so, subject to the following conditions: +/// The above copyright notice and this permission notice shall be included in all +/// copies or substantial portions of the Software. +/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +/// SOFTWARE. +/// ------------------------------------------------------------------------------ +/// ALTERNATIVE B - Public Domain (www.unlicense.org) +/// This is free and unencumbered software released into the public domain. +/// Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +/// software, either in source code form or as a compiled binary, for any purpose, +/// commercial or non-commercial, and by any means. +/// In jurisdictions that recognize copyright laws, the author or authors of this +/// software dedicate any and all copyright interest in the software to the public +/// domain. We make this dedication for the benefit of the public at large and to +/// the detriment of our heirs and successors. We intend this dedication to be an +/// overt act of relinquishment in perpetuity of all present and future rights to +/// this software under copyright law. +/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +/// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +/// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +/// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +/// ------------------------------------------------------------------------------ +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +/// ## Changelog +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~none +/// [date][x.yy.zz]-[description] +/// -[date]: date on which the change has been pushed +/// -[x.yy.zz]: Numerical version string representation. Each version number on the right +/// resets back to zero if version on the left is incremented. +/// - [x]: Major version with API and library breaking changes +/// - [yy]: Minor version with non-breaking API and library changes +/// - [zz]: Bug fix version with no direct changes to API +/// +/// - 2020/10/11 (4.06.1) - Fix C++ style comments which are not allowed in ISO C90. +/// - 2020/10/07 (4.06.0) - Fix nk_combo return type wrongly changed to nk_bool +/// - 2020/09/05 (4.05.0) - Use the nk_font_atlas allocator for stb_truetype memory management. +/// - 2020/09/04 (4.04.1) - Replace every boolean int by nk_bool +/// - 2020/09/04 (4.04.0) - Add nk_bool with NK_INCLUDE_STANDARD_BOOL +/// - 2020/06/13 (4.03.1) - Fix nk_pool allocation sizes. +/// - 2020/06/04 (4.03.0) - Made nk_combo header symbols optional. +/// - 2020/05/27 (4.02.5) - Fix nk_do_edit: Keep scroll position when re-activating edit widget. +/// - 2020/05/09 (4.02.4) - Fix nk_menubar height calculation bug +/// - 2020/05/08 (4.02.3) - Fix missing stdarg.h with NK_INCLUDE_STANDARD_VARARGS +/// - 2020/04/30 (4.02.2) - Fix nk_edit border drawing bug +/// - 2020/04/09 (4.02.1) - Removed unused nk_sqrt function to fix compiler warnings +/// - Fixed compiler warnings if you bring your own methods for +/// nk_cos/nk_sin/nk_strtod/nk_memset/nk_memcopy/nk_dtoa +/// - 2020/04/06 (4.01.10) - Fix bug: Do not use pool before checking for NULL +/// - 2020/03/22 (4.01.9) - Fix bug where layout state wasn't restored correctly after +/// popping a tree. +/// - 2020/03/11 (4.01.8) - Fix bug where padding is subtracted from widget +/// - 2020/03/06 (4.01.7) - Fix bug where width padding was applied twice +/// - 2020/02/06 (4.01.6) - Update stb_truetype.h and stb_rect_pack.h and separate them +/// - 2019/12/10 (4.01.5) - Fix off-by-one error in NK_INTERSECT +/// - 2019/10/09 (4.01.4) - Fix bug for autoscrolling in nk_do_edit +/// - 2019/09/20 (4.01.3) - Fixed a bug wherein combobox cannot be closed by clicking the header +/// when NK_BUTTON_TRIGGER_ON_RELEASE is defined. +/// - 2019/09/10 (4.01.2) - Fixed the nk_cos function, which deviated significantly. +/// - 2019/09/08 (4.01.1) - Fixed a bug wherein re-baking of fonts caused a segmentation +/// fault due to dst_font->glyph_count not being zeroed on subsequent +/// bakes of the same set of fonts. +/// - 2019/06/23 (4.01.0) - Added nk_***_get_scroll and nk_***_set_scroll for groups, windows, and popups. +/// - 2019/06/12 (4.00.3) - Fix panel background drawing bug. +/// - 2018/10/31 (4.00.2) - Added NK_KEYSTATE_BASED_INPUT to "fix" state based backends +/// like GLFW without breaking key repeat behavior on event based. +/// - 2018/04/01 (4.00.1) - Fixed calling `nk_convert` multiple time per single frame. +/// - 2018/04/01 (4.00.0) - BREAKING CHANGE: nk_draw_list_clear no longer tries to +/// clear provided buffers. So make sure to either free +/// or clear each passed buffer after calling nk_convert. +/// - 2018/02/23 (3.00.6) - Fixed slider dragging behavior. +/// - 2018/01/31 (3.00.5) - Fixed overcalculation of cursor data in font baking process. +/// - 2018/01/31 (3.00.4) - Removed name collision with stb_truetype. +/// - 2018/01/28 (3.00.3) - Fixed panel window border drawing bug. +/// - 2018/01/12 (3.00.2) - Added `nk_group_begin_titled` for separed group identifier and title. +/// - 2018/01/07 (3.00.1) - Started to change documentation style. +/// - 2018/01/05 (3.00.0) - BREAKING CHANGE: The previous color picker API was broken +/// because of conversions between float and byte color representation. +/// Color pickers now use floating point values to represent +/// HSV values. To get back the old behavior I added some additional +/// color conversion functions to cast between nk_color and +/// nk_colorf. +/// - 2017/12/23 (2.00.7) - Fixed small warning. +/// - 2017/12/23 (2.00.7) - Fixed `nk_edit_buffer` behavior if activated to allow input. +/// - 2017/12/23 (2.00.7) - Fixed modifyable progressbar dragging visuals and input behavior. +/// - 2017/12/04 (2.00.6) - Added formated string tooltip widget. +/// - 2017/11/18 (2.00.5) - Fixed window becoming hidden with flag `NK_WINDOW_NO_INPUT`. +/// - 2017/11/15 (2.00.4) - Fixed font merging. +/// - 2017/11/07 (2.00.3) - Fixed window size and position modifier functions. +/// - 2017/09/14 (2.00.2) - Fixed `nk_edit_buffer` and `nk_edit_focus` behavior. +/// - 2017/09/14 (2.00.1) - Fixed window closing behavior. +/// - 2017/09/14 (2.00.0) - BREAKING CHANGE: Modifing window position and size funtions now +/// require the name of the window and must happen outside the window +/// building process (between function call nk_begin and nk_end). +/// - 2017/09/11 (1.40.9) - Fixed window background flag if background window is declared last. +/// - 2017/08/27 (1.40.8) - Fixed `nk_item_is_any_active` for hidden windows. +/// - 2017/08/27 (1.40.7) - Fixed window background flag. +/// - 2017/07/07 (1.40.6) - Fixed missing clipping rect check for hovering/clicked +/// query for widgets. +/// - 2017/07/07 (1.40.5) - Fixed drawing bug for vertex output for lines and stroked +/// and filled rectangles. +/// - 2017/07/07 (1.40.4) - Fixed bug in nk_convert trying to add windows that are in +/// process of being destroyed. +/// - 2017/07/07 (1.40.3) - Fixed table internal bug caused by storing table size in +/// window instead of directly in table. +/// - 2017/06/30 (1.40.2) - Removed unneeded semicolon in C++ NK_ALIGNOF macro. +/// - 2017/06/30 (1.40.1) - Fixed drawing lines smaller or equal zero. +/// - 2017/06/08 (1.40.0) - Removed the breaking part of last commit. Auto layout now only +/// comes in effect if you pass in zero was row height argument. +/// - 2017/06/08 (1.40.0) - BREAKING CHANGE: while not directly API breaking it will change +/// how layouting works. From now there will be an internal minimum +/// row height derived from font height. If you need a row smaller than +/// that you can directly set it by `nk_layout_set_min_row_height` and +/// reset the value back by calling `nk_layout_reset_min_row_height. +/// - 2017/06/08 (1.39.1) - Fixed property text edit handling bug caused by past `nk_widget` fix. +/// - 2017/06/08 (1.39.0) - Added function to retrieve window space without calling a `nk_layout_xxx` function. +/// - 2017/06/06 (1.38.5) - Fixed `nk_convert` return flag for command buffer. +/// - 2017/05/23 (1.38.4) - Fixed activation behavior for widgets partially clipped. +/// - 2017/05/10 (1.38.3) - Fixed wrong min window size mouse scaling over boundries. +/// - 2017/05/09 (1.38.2) - Fixed vertical scrollbar drawing with not enough space. +/// - 2017/05/09 (1.38.1) - Fixed scaler dragging behavior if window size hits minimum size. +/// - 2017/05/06 (1.38.0) - Added platform double-click support. +/// - 2017/04/20 (1.37.1) - Fixed key repeat found inside glfw demo backends. +/// - 2017/04/20 (1.37.0) - Extended properties with selection and clipboard support. +/// - 2017/04/20 (1.36.2) - Fixed #405 overlapping rows with zero padding and spacing. +/// - 2017/04/09 (1.36.1) - Fixed #403 with another widget float error. +/// - 2017/04/09 (1.36.0) - Added window `NK_WINDOW_NO_INPUT` and `NK_WINDOW_NOT_INTERACTIVE` flags. +/// - 2017/04/09 (1.35.3) - Fixed buffer heap corruption. +/// - 2017/03/25 (1.35.2) - Fixed popup overlapping for `NK_WINDOW_BACKGROUND` windows. +/// - 2017/03/25 (1.35.1) - Fixed windows closing behavior. +/// - 2017/03/18 (1.35.0) - Added horizontal scroll requested in #377. +/// - 2017/03/18 (1.34.3) - Fixed long window header titles. +/// - 2017/03/04 (1.34.2) - Fixed text edit filtering. +/// - 2017/03/04 (1.34.1) - Fixed group closable flag. +/// - 2017/02/25 (1.34.0) - Added custom draw command for better language binding support. +/// - 2017/01/24 (1.33.0) - Added programatic way of remove edit focus. +/// - 2017/01/24 (1.32.3) - Fixed wrong define for basic type definitions for windows. +/// - 2017/01/21 (1.32.2) - Fixed input capture from hidden or closed windows. +/// - 2017/01/21 (1.32.1) - Fixed slider behavior and drawing. +/// - 2017/01/13 (1.32.0) - Added flag to put scaler into the bottom left corner. +/// - 2017/01/13 (1.31.0) - Added additional row layouting method to combine both +/// dynamic and static widgets. +/// - 2016/12/31 (1.30.0) - Extended scrollbar offset from 16-bit to 32-bit. +/// - 2016/12/31 (1.29.2) - Fixed closing window bug of minimized windows. +/// - 2016/12/03 (1.29.1) - Fixed wrapped text with no seperator and C89 error. +/// - 2016/12/03 (1.29.0) - Changed text wrapping to process words not characters. +/// - 2016/11/22 (1.28.6) - Fixed window minimized closing bug. +/// - 2016/11/19 (1.28.5) - Fixed abstract combo box closing behavior. +/// - 2016/11/19 (1.28.4) - Fixed tooltip flickering. +/// - 2016/11/19 (1.28.3) - Fixed memory leak caused by popup repeated closing. +/// - 2016/11/18 (1.28.2) - Fixed memory leak caused by popup panel allocation. +/// - 2016/11/10 (1.28.1) - Fixed some warnings and C++ error. +/// - 2016/11/10 (1.28.0) - Added additional `nk_button` versions which allows to directly +/// pass in a style struct to change buttons visual. +/// - 2016/11/10 (1.27.0) - Added additional `nk_tree` versions to support external state +/// storage. Just like last the `nk_group` commit the main +/// advantage is that you optionally can minimize nuklears runtime +/// memory consumption or handle hash collisions. +/// - 2016/11/09 (1.26.0) - Added additional `nk_group` version to support external scrollbar +/// offset storage. Main advantage is that you can externalize +/// the memory management for the offset. It could also be helpful +/// if you have a hash collision in `nk_group_begin` but really +/// want the name. In addition I added `nk_list_view` which allows +/// to draw big lists inside a group without actually having to +/// commit the whole list to nuklear (issue #269). +/// - 2016/10/30 (1.25.1) - Fixed clipping rectangle bug inside `nk_draw_list`. +/// - 2016/10/29 (1.25.0) - Pulled `nk_panel` memory management into nuklear and out of +/// the hands of the user. From now on users don't have to care +/// about panels unless they care about some information. If you +/// still need the panel just call `nk_window_get_panel`. +/// - 2016/10/21 (1.24.0) - Changed widget border drawing to stroked rectangle from filled +/// rectangle for less overdraw and widget background transparency. +/// - 2016/10/18 (1.23.0) - Added `nk_edit_focus` for manually edit widget focus control. +/// - 2016/09/29 (1.22.7) - Fixed deduction of basic type in non `` compilation. +/// - 2016/09/29 (1.22.6) - Fixed edit widget UTF-8 text cursor drawing bug. +/// - 2016/09/28 (1.22.5) - Fixed edit widget UTF-8 text appending/inserting/removing. +/// - 2016/09/28 (1.22.4) - Fixed drawing bug inside edit widgets which offset all text +/// text in every edit widget if one of them is scrolled. +/// - 2016/09/28 (1.22.3) - Fixed small bug in edit widgets if not active. The wrong +/// text length is passed. It should have been in bytes but +/// was passed as glyphes. +/// - 2016/09/20 (1.22.2) - Fixed color button size calculation. +/// - 2016/09/20 (1.22.1) - Fixed some `nk_vsnprintf` behavior bugs and removed `` +/// again from `NK_INCLUDE_STANDARD_VARARGS`. +/// - 2016/09/18 (1.22.0) - C89 does not support vsnprintf only C99 and newer as well +/// as C++11 and newer. In addition to use vsnprintf you have +/// to include . So just defining `NK_INCLUDE_STD_VAR_ARGS` +/// is not enough. That behavior is now fixed. By default if +/// both varargs as well as stdio is selected I try to use +/// vsnprintf if not possible I will revert to vsprintf. If +/// varargs but not stdio was defined I will use my own function. +/// - 2016/09/15 (1.21.2) - Fixed panel `close` behavior for deeper panel levels. +/// - 2016/09/15 (1.21.1) - Fixed C++ errors and wrong argument to `nk_panel_get_xxxx`. +/// - 2016/09/13 (1.21.0) - !BREAKING! Fixed nonblocking popup behavior in menu, combo, +/// and contextual which prevented closing in y-direction if +/// popup did not reach max height. +/// In addition the height parameter was changed into vec2 +/// for width and height to have more control over the popup size. +/// - 2016/09/13 (1.20.3) - Cleaned up and extended type selection. +/// - 2016/09/13 (1.20.2) - Fixed slider behavior hopefully for the last time. This time +/// all calculation are correct so no more hackery. +/// - 2016/09/13 (1.20.1) - Internal change to divide window/panel flags into panel flags and types. +/// Suprisinly spend years in C and still happened to confuse types +/// with flags. Probably something to take note. +/// - 2016/09/08 (1.20.0) - Added additional helper function to make it easier to just +/// take the produced buffers from `nk_convert` and unplug the +/// iteration process from `nk_context`. So now you can +/// just use the vertex,element and command buffer + two pointer +/// inside the command buffer retrieved by calls `nk__draw_begin` +/// and `nk__draw_end` and macro `nk_draw_foreach_bounded`. +/// - 2016/09/08 (1.19.0) - Added additional asserts to make sure every `nk_xxx_begin` call +/// for windows, popups, combobox, menu and contextual is guarded by +/// `if` condition and does not produce false drawing output. +/// - 2016/09/08 (1.18.0) - Changed confusing name for `NK_SYMBOL_RECT_FILLED`, `NK_SYMBOL_RECT` +/// to hopefully easier to understand `NK_SYMBOL_RECT_FILLED` and +/// `NK_SYMBOL_RECT_OUTLINE`. +/// - 2016/09/08 (1.17.0) - Changed confusing name for `NK_SYMBOL_CIRLCE_FILLED`, `NK_SYMBOL_CIRCLE` +/// to hopefully easier to understand `NK_SYMBOL_CIRCLE_FILLED` and +/// `NK_SYMBOL_CIRCLE_OUTLINE`. +/// - 2016/09/08 (1.16.0) - Added additional checks to select correct types if `NK_INCLUDE_FIXED_TYPES` +/// is not defined by supporting the biggest compiler GCC, clang and MSVC. +/// - 2016/09/07 (1.15.3) - Fixed `NK_INCLUDE_COMMAND_USERDATA` define to not cause an error. +/// - 2016/09/04 (1.15.2) - Fixed wrong combobox height calculation. +/// - 2016/09/03 (1.15.1) - Fixed gaps inside combo boxes in OpenGL. +/// - 2016/09/02 (1.15.0) - Changed nuklear to not have any default vertex layout and +/// instead made it user provided. The range of types to convert +/// to is quite limited at the moment, but I would be more than +/// happy to accept PRs to add additional. +/// - 2016/08/30 (1.14.2) - Removed unused variables. +/// - 2016/08/30 (1.14.1) - Fixed C++ build errors. +/// - 2016/08/30 (1.14.0) - Removed mouse dragging from SDL demo since it does not work correctly. +/// - 2016/08/30 (1.13.4) - Tweaked some default styling variables. +/// - 2016/08/30 (1.13.3) - Hopefully fixed drawing bug in slider, in general I would +/// refrain from using slider with a big number of steps. +/// - 2016/08/30 (1.13.2) - Fixed close and minimize button which would fire even if the +/// window was in Read Only Mode. +/// - 2016/08/30 (1.13.1) - Fixed popup panel padding handling which was previously just +/// a hack for combo box and menu. +/// - 2016/08/30 (1.13.0) - Removed `NK_WINDOW_DYNAMIC` flag from public API since +/// it is bugged and causes issues in window selection. +/// - 2016/08/30 (1.12.0) - Removed scaler size. The size of the scaler is now +/// determined by the scrollbar size. +/// - 2016/08/30 (1.11.2) - Fixed some drawing bugs caused by changes from 1.11.0. +/// - 2016/08/30 (1.11.1) - Fixed overlapping minimized window selection. +/// - 2016/08/30 (1.11.0) - Removed some internal complexity and overly complex code +/// handling panel padding and panel border. +/// - 2016/08/29 (1.10.0) - Added additional height parameter to `nk_combobox_xxx`. +/// - 2016/08/29 (1.10.0) - Fixed drawing bug in dynamic popups. +/// - 2016/08/29 (1.10.0) - Added experimental mouse scrolling to popups, menus and comboboxes. +/// - 2016/08/26 (1.10.0) - Added window name string prepresentation to account for +/// hash collisions. Currently limited to `NK_WINDOW_MAX_NAME` +/// which in term can be redefined if not big enough. +/// - 2016/08/26 (1.10.0) - Added stacks for temporary style/UI changes in code. +/// - 2016/08/25 (1.10.0) - Changed `nk_input_is_key_pressed` and 'nk_input_is_key_released' +/// to account for key press and release happening in one frame. +/// - 2016/08/25 (1.10.0) - Added additional nk_edit flag to directly jump to the end on activate. +/// - 2016/08/17 (1.09.6) - Removed invalid check for value zero in `nk_propertyx`. +/// - 2016/08/16 (1.09.5) - Fixed ROM mode for deeper levels of popup windows parents. +/// - 2016/08/15 (1.09.4) - Editbox are now still active if enter was pressed with flag +/// `NK_EDIT_SIG_ENTER`. Main reasoning is to be able to keep +/// typing after commiting. +/// - 2016/08/15 (1.09.4) - Removed redundant code. +/// - 2016/08/15 (1.09.4) - Fixed negative numbers in `nk_strtoi` and remove unused variable. +/// - 2016/08/15 (1.09.3) - Fixed `NK_WINDOW_BACKGROUND` flag behavior to select a background +/// window only as selected by hovering and not by clicking. +/// - 2016/08/14 (1.09.2) - Fixed a bug in font atlas which caused wrong loading +/// of glyphes for font with multiple ranges. +/// - 2016/08/12 (1.09.1) - Added additional function to check if window is currently +/// hidden and therefore not visible. +/// - 2016/08/12 (1.09.1) - nk_window_is_closed now queries the correct flag `NK_WINDOW_CLOSED` +/// instead of the old flag `NK_WINDOW_HIDDEN`. +/// - 2016/08/09 (1.09.0) - Added additional double version to nk_property and changed +/// the underlying implementation to not cast to float and instead +/// work directly on the given values. +/// - 2016/08/09 (1.08.0) - Added additional define to overwrite library internal +/// floating pointer number to string conversion for additional +/// precision. +/// - 2016/08/09 (1.08.0) - Added additional define to overwrite library internal +/// string to floating point number conversion for additional +/// precision. +/// - 2016/08/08 (1.07.2) - Fixed compiling error without define `NK_INCLUDE_FIXED_TYPE`. +/// - 2016/08/08 (1.07.1) - Fixed possible floating point error inside `nk_widget` leading +/// to wrong wiget width calculation which results in widgets falsly +/// becomming tagged as not inside window and cannot be accessed. +/// - 2016/08/08 (1.07.0) - Nuklear now differentiates between hiding a window (NK_WINDOW_HIDDEN) and +/// closing a window (NK_WINDOW_CLOSED). A window can be hidden/shown +/// by using `nk_window_show` and closed by either clicking the close +/// icon in a window or by calling `nk_window_close`. Only closed +/// windows get removed at the end of the frame while hidden windows +/// remain. +/// - 2016/08/08 (1.06.0) - Added `nk_edit_string_zero_terminated` as a second option to +/// `nk_edit_string` which takes, edits and outputs a '\0' terminated string. +/// - 2016/08/08 (1.05.4) - Fixed scrollbar auto hiding behavior. +/// - 2016/08/08 (1.05.3) - Fixed wrong panel padding selection in `nk_layout_widget_space`. +/// - 2016/08/07 (1.05.2) - Fixed old bug in dynamic immediate mode layout API, calculating +/// wrong item spacing and panel width. +/// - 2016/08/07 (1.05.1) - Hopefully finally fixed combobox popup drawing bug. +/// - 2016/08/07 (1.05.0) - Split varargs away from `NK_INCLUDE_STANDARD_IO` into own +/// define `NK_INCLUDE_STANDARD_VARARGS` to allow more fine +/// grained controlled over library includes. +/// - 2016/08/06 (1.04.5) - Changed memset calls to `NK_MEMSET`. +/// - 2016/08/04 (1.04.4) - Fixed fast window scaling behavior. +/// - 2016/08/04 (1.04.3) - Fixed window scaling, movement bug which appears if you +/// move/scale a window and another window is behind it. +/// If you are fast enough then the window behind gets activated +/// and the operation is blocked. I now require activating +/// by hovering only if mouse is not pressed. +/// - 2016/08/04 (1.04.2) - Fixed changing fonts. +/// - 2016/08/03 (1.04.1) - Fixed `NK_WINDOW_BACKGROUND` behavior. +/// - 2016/08/03 (1.04.0) - Added color parameter to `nk_draw_image`. +/// - 2016/08/03 (1.04.0) - Added additional window padding style attributes for +/// sub windows (combo, menu, ...). +/// - 2016/08/03 (1.04.0) - Added functions to show/hide software cursor. +/// - 2016/08/03 (1.04.0) - Added `NK_WINDOW_BACKGROUND` flag to force a window +/// to be always in the background of the screen. +/// - 2016/08/03 (1.03.2) - Removed invalid assert macro for NK_RGB color picker. +/// - 2016/08/01 (1.03.1) - Added helper macros into header include guard. +/// - 2016/07/29 (1.03.0) - Moved the window/table pool into the header part to +/// simplify memory management by removing the need to +/// allocate the pool. +/// - 2016/07/29 (1.02.0) - Added auto scrollbar hiding window flag which if enabled +/// will hide the window scrollbar after NK_SCROLLBAR_HIDING_TIMEOUT +/// seconds without window interaction. To make it work +/// you have to also set a delta time inside the `nk_context`. +/// - 2016/07/25 (1.01.1) - Fixed small panel and panel border drawing bugs. +/// - 2016/07/15 (1.01.0) - Added software cursor to `nk_style` and `nk_context`. +/// - 2016/07/15 (1.01.0) - Added const correctness to `nk_buffer_push' data argument. +/// - 2016/07/15 (1.01.0) - Removed internal font baking API and simplified +/// font atlas memory management by converting pointer +/// arrays for fonts and font configurations to lists. +/// - 2016/07/15 (1.00.0) - Changed button API to use context dependend button +/// behavior instead of passing it for every function call. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// ## Gallery +/// ![Figure [blue]: Feature overview with blue color styling](https://cloud.githubusercontent.com/assets/8057201/13538240/acd96876-e249-11e5-9547-5ac0b19667a0.png) +/// ![Figure [red]: Feature overview with red color styling](https://cloud.githubusercontent.com/assets/8057201/13538243/b04acd4c-e249-11e5-8fd2-ad7744a5b446.png) +/// ![Figure [widgets]: Widget overview](https://cloud.githubusercontent.com/assets/8057201/11282359/3325e3c6-8eff-11e5-86cb-cf02b0596087.png) +/// ![Figure [blackwhite]: Black and white](https://cloud.githubusercontent.com/assets/8057201/11033668/59ab5d04-86e5-11e5-8091-c56f16411565.png) +/// ![Figure [filexp]: File explorer](https://cloud.githubusercontent.com/assets/8057201/10718115/02a9ba08-7b6b-11e5-950f-adacdd637739.png) +/// ![Figure [opengl]: OpenGL Editor](https://cloud.githubusercontent.com/assets/8057201/12779619/2a20d72c-ca69-11e5-95fe-4edecf820d5c.png) +/// ![Figure [nodedit]: Node Editor](https://cloud.githubusercontent.com/assets/8057201/9976995/e81ac04a-5ef7-11e5-872b-acd54fbeee03.gif) +/// ![Figure [skinning]: Using skinning in Nuklear](https://cloud.githubusercontent.com/assets/8057201/15991632/76494854-30b8-11e6-9555-a69840d0d50b.png) +/// ![Figure [bf]: Heavy modified version](https://cloud.githubusercontent.com/assets/8057201/14902576/339926a8-0d9c-11e6-9fee-a8b73af04473.png) +/// +/// ## Credits +/// Developed by Micha Mettke and every direct or indirect github contributor.

    +/// +/// Embeds [stb_texedit](https://github.com/nothings/stb/blob/master/stb_textedit.h), [stb_truetype](https://github.com/nothings/stb/blob/master/stb_truetype.h) and [stb_rectpack](https://github.com/nothings/stb/blob/master/stb_rect_pack.h) by Sean Barret (public domain)
    +/// Uses [stddoc.c](https://github.com/r-lyeh/stddoc.c) from r-lyeh@github.com for documentation generation

    +/// Embeds ProggyClean.ttf font by Tristan Grimmer (MIT license).
    +/// +/// Big thank you to Omar Cornut (ocornut@github) for his [imgui library](https://github.com/ocornut/imgui) and +/// giving me the inspiration for this library, Casey Muratori for handmade hero +/// and his original immediate mode graphical user interface idea and Sean +/// Barret for his amazing single header libraries which restored my faith +/// in libraries and brought me to create some of my own. Finally Apoorva Joshi +/// for his single header file packer. +*/ + diff --git a/source/engine/thirdparty/sokol/tests/ext/stb_truetype.h b/source/engine/thirdparty/sokol/tests/ext/stb_truetype.h new file mode 100644 index 0000000..98a12c9 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/ext/stb_truetype.h @@ -0,0 +1,4548 @@ +// stb_truetype.h - v1.16 - public domain +// authored from 2009-2016 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// David Gow +// David Given +// Ivan-Assen Ivanov +// Anthony Pesch +// Johan Duparc +// Hou Qiming +// Fabian "ryg" Giesen +// Martins Mozeiko +// Cap Petschulat +// Omar Cornut +// github:aloucks +// Peter LaValle +// Sergey Popov +// Giumo X. Clanjor +// Higor Euripedes +// Thomas Fields +// Derek Vinyard +// Cort Stratton +// +// VERSION HISTORY +// +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversample() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since they different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.6c, 2050 LOC) +// +// Documentation & header file 520 LOC \___ 660 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 620 LOC ---- 620 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 +// +// +// PERFORMANCE MEASUREMENTS FOR 1.06: +// +// 32-bit 64-bit +// Previous release: 8.83 s 7.68 s +// Pool allocations: 7.72 s 6.34 s +// Inline sort : 6.54 s 5.65 s +// New rasterizer : 5.63 s 5.00 s + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLuint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +// NOTE sokol-samples: declared static but never defined: T_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshhold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + assert(b->data); + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) { + *x0 = r ? c.min_x : 0; + *y0 = r ? c.min_y : 0; + *x1 = r ? c.max_x : 0; + *y1 = r ? c.max_y : 0; + } + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + // SOKOL FIXME: static analyzer fix 'Value stored to 'dx' is never read' + //dx = -dx; + dy = -dy; + // SOKOL FIXME: static analyzer fix 'Although the value stored to 'xb' is used in the enclosing expression, the value is never actually read from 'xb'' + /*t = x0,*/x0 = xb/*xb = t*/; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + STBTT_assert(z->ey >= scan_y_top); + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshhold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + // if one scale is 0, use same scale for both + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; // if both scales are 0, return NULL + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/sokol/tests/functional/CMakeLists.txt b/source/engine/thirdparty/sokol/tests/functional/CMakeLists.txt new file mode 100644 index 0000000..9a73c08 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/CMakeLists.txt @@ -0,0 +1,23 @@ +if (NOT ANDROID) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/assets/comsi.s3m DESTINATION ${CMAKE_BINARY_DIR}) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/assets/comsi.s3m DESTINATION ${CMAKE_BINARY_DIR}/Debug) + +set(c_sources + sokol_log_test.c + sokol_args_test.c + sokol_audio_test.c + sokol_debugtext_test.c + sokol_fetch_test.c + sokol_gfx_test.c + sokol_gl_test.c + sokol_shape_test.c + sokol_color_test.c + sokol_spine_test.c + sokol_test.c +) +add_executable(sokol-test ${c_sources}) +target_link_libraries(sokol-test PUBLIC spine) +configure_c(sokol-test) + +endif() diff --git a/source/engine/thirdparty/sokol/tests/functional/README.txt b/source/engine/thirdparty/sokol/tests/functional/README.txt new file mode 100644 index 0000000..28fd594 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/README.txt @@ -0,0 +1 @@ +Test the public API behaviour with dummy backends. diff --git a/source/engine/thirdparty/sokol/tests/functional/assets/comsi.s3m b/source/engine/thirdparty/sokol/tests/functional/assets/comsi.s3m new file mode 100644 index 0000000000000000000000000000000000000000..a33c90ab5efdb9dd1b64981eba5f83b19e142f1b GIT binary patch literal 409482 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 XO7P3V_7$P8wftI=iz~DEBJsZg^jsV7 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/sokol/tests/functional/assets/readme.txt b/source/engine/thirdparty/sokol/tests/functional/assets/readme.txt new file mode 100644 index 0000000..568e95c --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/assets/readme.txt @@ -0,0 +1,3 @@ +Spine examples taken from: + +https://github.com/EsotericSoftware/spine-runtimes/tree/4.1/examples diff --git a/source/engine/thirdparty/sokol/tests/functional/force_dummy_backend.h b/source/engine/thirdparty/sokol/tests/functional/force_dummy_backend.h new file mode 100644 index 0000000..22390e7 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/force_dummy_backend.h @@ -0,0 +1,18 @@ +#if defined(SOKOL_GLES3) +#undef SOKOL_GLES3 +#endif +#if defined(SOKOL_GLCORE33) +#undef SOKOL_GLCORE33 +#endif +#if defined(SOKOL_METAL) +#undef SOKOL_METAL +#endif +#if defined(SOKOL_D3D11) +#undef SOKOL_D3D11 +#endif +#if defined(SOKOL_WGPU) +#undef SOKOL_WGPU +#endif +#ifndef SOKOL_DUMMY_BACKEND +#define SOKOL_DUMMY_BACKEND +#endif diff --git a/source/engine/thirdparty/sokol/tests/functional/sokol_args_test.c b/source/engine/thirdparty/sokol/tests/functional/sokol_args_test.c new file mode 100644 index 0000000..e2928d1 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/sokol_args_test.c @@ -0,0 +1,302 @@ +//------------------------------------------------------------------------------ +// sokol-args-test.c +//------------------------------------------------------------------------------ +#define SOKOL_IMPL +#include "sokol_args.h" +#include "utest.h" + +#define T(b) EXPECT_TRUE(b) +#define TSTR(s0, s1) EXPECT_TRUE(0 == strcmp(s0,s1)) +#define NUM_ARGS(x) (sizeof(x)/sizeof(void*)) + +static char* argv_0[] = { "exe_name " }; +UTEST(sokol_args, init_shutdown) { + sargs_setup(&(sargs_desc){0}); + T(sargs_isvalid()); + T(_sargs.max_args == _SARGS_MAX_ARGS_DEF); + T(_sargs.args); + T(_sargs.buf_size == _SARGS_BUF_SIZE_DEF); + T(_sargs.buf_pos == 1); + T(_sargs.buf); + T(sargs_num_args() == 0); + TSTR(sargs_key_at(0), ""); + TSTR(sargs_value_at(0), ""); + sargs_shutdown(); + T(!sargs_isvalid()); + T(0 == _sargs.args); + T(0 == _sargs.buf); +} + +UTEST(sokol_args, no_args) { + sargs_setup(&(sargs_desc){ + .argc = NUM_ARGS(argv_0), + .argv = argv_0 + }); + T(sargs_isvalid()); + T(sargs_num_args() == 0); + TSTR(sargs_key_at(0), ""); + TSTR(sargs_value_at(0), ""); + T(-1 == sargs_find("bla")); + T(!sargs_exists("bla")); + TSTR(sargs_value("bla"), ""); + TSTR(sargs_value_def("bla", "blub"), "blub"); + sargs_shutdown(); +} + +static char* argv_1[] = { "exe_name", "kvp0=val0", "kvp1=val1", "kvp2=val2" }; +UTEST(sokol_args, simple_args) { + sargs_setup(&(sargs_desc){ + .argc = NUM_ARGS(argv_1), + .argv = argv_1, + }); + T(sargs_isvalid()); + T(sargs_num_args() == 3); + T(0 == sargs_find("kvp0")); + TSTR(sargs_value("kvp0"), "val0"); + TSTR(sargs_key_at(0), "kvp0"); + TSTR(sargs_value_at(0), "val0"); + T(1 == sargs_find("kvp1")); + TSTR(sargs_value("kvp1"), "val1"); + TSTR(sargs_key_at(1), "kvp1"); + TSTR(sargs_value_at(1), "val1"); + T(2 == sargs_find("kvp2")); + TSTR(sargs_value("kvp2"), "val2"); + TSTR(sargs_key_at(2), "kvp2"); + TSTR(sargs_value_at(2), "val2"); + T(_sargs.buf_pos == 31); + sargs_shutdown(); +} + +static char* argv_2[] = { "exe_name", "kvp0 = val0 ", " \tkvp1= val1", "kvp2 = val2 "}; +UTEST(sokol_args, simple_whitespace) { + sargs_setup(&(sargs_desc){ + .argc = NUM_ARGS(argv_2), + .argv = argv_2 + }); + T(sargs_isvalid()); + T(sargs_num_args() == 3); + T(0 == sargs_find("kvp0")); + TSTR(sargs_value("kvp0"), "val0"); + TSTR(sargs_key_at(0), "kvp0"); + TSTR(sargs_value_at(0), "val0"); + T(1 == sargs_find("kvp1")); + TSTR(sargs_value("kvp1"), "val1"); + TSTR(sargs_key_at(1), "kvp1"); + TSTR(sargs_value_at(1), "val1"); + T(2 == sargs_find("kvp2")); + TSTR(sargs_value("kvp2"), "val2"); + TSTR(sargs_key_at(2), "kvp2"); + TSTR(sargs_value_at(2), "val2"); + T(_sargs.buf_pos == 31); + sargs_shutdown(); +} + +static char* argv_4[] = { "exe_name", "kvp0 ", "=val0 ", " kvp1", "=", "val1", "kvp2 \t", "= val2 "}; +UTEST(sokol_args, standalone_separator) { + sargs_setup(&(sargs_desc){ + .argc = NUM_ARGS(argv_4), + .argv = argv_4 + }); + T(sargs_isvalid()); + T(sargs_num_args() == 3); + T(0 == sargs_find("kvp0")); + TSTR(sargs_value("kvp0"), "val0"); + TSTR(sargs_key_at(0), "kvp0"); + TSTR(sargs_value_at(0), "val0"); + T(1 == sargs_find("kvp1")); + TSTR(sargs_value("kvp1"), "val1"); + TSTR(sargs_key_at(1), "kvp1"); + TSTR(sargs_value_at(1), "val1"); + T(2 == sargs_find("kvp2")); + TSTR(sargs_value("kvp2"), "val2"); + TSTR(sargs_key_at(2), "kvp2"); + TSTR(sargs_value_at(2), "val2"); + T(_sargs.buf_pos == 31); + sargs_shutdown(); +} + +static char* argv_5[] = { "exe_name", "kvp0='bla bla'", "kvp1=' blub blub'", "kvp2='blob blob '"}; +UTEST(sokol_args, single_quotes) { + sargs_setup(&(sargs_desc){ + .argc = NUM_ARGS(argv_5), + .argv = argv_5 + }); + T(sargs_isvalid()); + T(sargs_num_args() == 3); + T(0 == sargs_find("kvp0")); + TSTR(sargs_value("kvp0"), "bla bla"); + TSTR(sargs_key_at(0), "kvp0"); + TSTR(sargs_value_at(0), "bla bla"); + T(1 == sargs_find("kvp1")); + TSTR(sargs_value("kvp1"), " blub blub"); + TSTR(sargs_key_at(1), "kvp1"); + TSTR(sargs_value_at(1), " blub blub"); + T(2 == sargs_find("kvp2")); + TSTR(sargs_value("kvp2"), "blob blob "); + TSTR(sargs_key_at(2), "kvp2"); + TSTR(sargs_value_at(2), "blob blob "); + sargs_shutdown(); +} + +static char* argv_6[] = { "exe_name", "kvp0=\"bla bla\"", "kvp1=\" blub blub\"", "kvp2=\"blob blob \""}; +UTEST(sokol_args, double_quotes) { + sargs_setup(&(sargs_desc){ + .argc = NUM_ARGS(argv_6), + .argv = argv_6 + }); + T(sargs_isvalid()); + T(sargs_num_args() == 3); + T(0 == sargs_find("kvp0")); + TSTR(sargs_value("kvp0"), "bla bla"); + TSTR(sargs_key_at(0), "kvp0"); + TSTR(sargs_value_at(0), "bla bla"); + T(1 == sargs_find("kvp1")); + TSTR(sargs_value("kvp1"), " blub blub"); + TSTR(sargs_key_at(1), "kvp1"); + TSTR(sargs_value_at(1), " blub blub"); + T(2 == sargs_find("kvp2")); + TSTR(sargs_value("kvp2"), "blob blob "); + TSTR(sargs_key_at(2), "kvp2"); + TSTR(sargs_value_at(2), "blob blob "); + sargs_shutdown(); +} + +static char* argv_7[] = { "exe_name", "kvp0='bla \"bla\"'", "kvp1=' \"blub blub\"'", "kvp2='blob \"blob\" '"}; +UTEST(sokol_args, double_in_single_quotes) { + sargs_setup(&(sargs_desc){ + .argc = NUM_ARGS(argv_7), + .argv = argv_7 + }); + T(sargs_isvalid()); + T(sargs_num_args() == 3); + T(0 == sargs_find("kvp0")); + TSTR(sargs_value("kvp0"), "bla \"bla\""); + TSTR(sargs_key_at(0), "kvp0"); + TSTR(sargs_value_at(0), "bla \"bla\""); + T(1 == sargs_find("kvp1")); + TSTR(sargs_value("kvp1"), " \"blub blub\""); + TSTR(sargs_key_at(1), "kvp1"); + TSTR(sargs_value_at(1), " \"blub blub\""); + T(2 == sargs_find("kvp2")); + TSTR(sargs_value("kvp2"), "blob \"blob\" "); + TSTR(sargs_key_at(2), "kvp2"); + TSTR(sargs_value_at(2), "blob \"blob\" "); + sargs_shutdown(); +} + +static char* argv_8[] = { "exe_name", "kvp0=\"bla 'bla'\"", "kvp1=\" 'blub blub'\"", "kvp2=\"blob 'blob' \""}; +UTEST(sokol_args, single_in_double_quotes) { + sargs_setup(&(sargs_desc){ + .argc = NUM_ARGS(argv_8), + .argv = argv_8 + }); + T(sargs_isvalid()); + T(sargs_num_args() == 3); + T(0 == sargs_find("kvp0")); + TSTR(sargs_value("kvp0"), "bla 'bla'"); + TSTR(sargs_key_at(0), "kvp0"); + TSTR(sargs_value_at(0), "bla 'bla'"); + T(1 == sargs_find("kvp1")); + TSTR(sargs_value("kvp1"), " 'blub blub'"); + TSTR(sargs_key_at(1), "kvp1"); + TSTR(sargs_value_at(1), " 'blub blub'"); + T(2 == sargs_find("kvp2")); + TSTR(sargs_value("kvp2"), "blob 'blob' "); + TSTR(sargs_key_at(2), "kvp2"); + TSTR(sargs_value_at(2), "blob 'blob' "); + sargs_shutdown(); +} + +static char* argv_9[] = { "exe_name", "kvp0='bla ", "bla'", "kvp1= ' blub", " blub'", "kvp2='blob blob '"}; +UTEST(sokol_args, test_split_quotes) { + sargs_setup(&(sargs_desc){ + .argc = NUM_ARGS(argv_9), + .argv = argv_9 + }); + T(sargs_isvalid()); + T(sargs_num_args() == 3); + T(0 == sargs_find("kvp0")); + TSTR(sargs_value("kvp0"), "bla bla"); + TSTR(sargs_key_at(0), "kvp0"); + TSTR(sargs_value_at(0), "bla bla"); + T(1 == sargs_find("kvp1")); + TSTR(sargs_value("kvp1"), " blub blub"); + TSTR(sargs_key_at(1), "kvp1"); + TSTR(sargs_value_at(1), " blub blub"); + T(2 == sargs_find("kvp2")); + TSTR(sargs_value("kvp2"), "blob blob "); + TSTR(sargs_key_at(2), "kvp2"); + TSTR(sargs_value_at(2), "blob blob "); + sargs_shutdown(); +} + +static char* argv_10[] = { "exe_name", "kvp0=\\\\val0\\nval1", "kvp1=val1\\rval2", "kvp2='val2\\tval3'" }; +UTEST(sokol_args, escape_sequence) { + sargs_setup(&(sargs_desc){ + .argc = NUM_ARGS(argv_10), + .argv = argv_10, + }); + T(sargs_isvalid()); + T(sargs_num_args() == 3); + T(0 == sargs_find("kvp0")); + TSTR(sargs_value("kvp0"), "\\val0\nval1"); + TSTR(sargs_key_at(0), "kvp0"); + TSTR(sargs_value_at(0), "\\val0\nval1"); + T(1 == sargs_find("kvp1")); + TSTR(sargs_value("kvp1"), "val1\rval2"); + TSTR(sargs_key_at(1), "kvp1"); + TSTR(sargs_value_at(1), "val1\rval2"); + T(2 == sargs_find("kvp2")); + TSTR(sargs_value("kvp2"), "val2\tval3"); + TSTR(sargs_key_at(2), "kvp2"); + TSTR(sargs_value_at(2), "val2\tval3"); + sargs_shutdown(); +} + +static char* argv_11[] = { "exe_name", "kvp0 kvp1", "kvp2 = val2", "kvp3", "kvp4=val4" }; +UTEST(sokol_args, key_only_args) { + sargs_setup(&(sargs_desc){ + .argc = NUM_ARGS(argv_11), + .argv = argv_11, + }); + T(sargs_isvalid()); + T(sargs_num_args() == 5); + T(0 == sargs_find("kvp0")); + T(1 == sargs_find("kvp1")); + T(2 == sargs_find("kvp2")); + T(3 == sargs_find("kvp3")); + T(4 == sargs_find("kvp4")) + T(-1 == sargs_find("kvp5")); + T(-1 == sargs_find("val2")); + T(-1 == sargs_find("val4")); + T(sargs_exists("kvp0")); + T(sargs_exists("kvp1")); + T(sargs_exists("kvp2")); + T(sargs_exists("kvp3")); + T(sargs_exists("kvp4")); + T(!sargs_exists("kvp5")); + TSTR(sargs_value("kvp0"), ""); + TSTR(sargs_value("kvp1"), ""); + TSTR(sargs_value("kvp2"), "val2"); + TSTR(sargs_value("kvp3"), ""); + TSTR(sargs_value("kvp4"), "val4"); + TSTR(sargs_value("kvp5"), ""); + TSTR(sargs_value_def("kvp0", "bla0"), "bla0"); + TSTR(sargs_value_def("kvp1", "bla1"), "bla1"); + TSTR(sargs_value_def("kvp2", "bla2"), "val2"); + TSTR(sargs_value_def("kvp3", "bla3"), "bla3"); + TSTR(sargs_value_def("kvp4", "bla4"), "val4"); + TSTR(sargs_value_def("kvp5", "bla5"), "bla5"); + TSTR(sargs_key_at(0), "kvp0"); + TSTR(sargs_key_at(1), "kvp1"); + TSTR(sargs_key_at(2), "kvp2"); + TSTR(sargs_key_at(3), "kvp3"); + TSTR(sargs_key_at(4), "kvp4"); + TSTR(sargs_key_at(5), ""); + TSTR(sargs_value_at(0), ""); + TSTR(sargs_value_at(1), ""); + TSTR(sargs_value_at(2), "val2"); + TSTR(sargs_value_at(3), ""); + TSTR(sargs_value_at(4), "val4"); + TSTR(sargs_value_at(5), ""); +} diff --git a/source/engine/thirdparty/sokol/tests/functional/sokol_audio_test.c b/source/engine/thirdparty/sokol/tests/functional/sokol_audio_test.c new file mode 100644 index 0000000..a607d29 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/sokol_audio_test.c @@ -0,0 +1,99 @@ +//------------------------------------------------------------------------------ +// sokol_audio_test.c +//------------------------------------------------------------------------------ +#define SOKOL_IMPL +#ifndef SOKOL_DUMMY_BACKEND +#define SOKOL_DUMMY_BACKEND +#endif +#include "sokol_audio.h" +#include "utest.h" + +#define T(b) EXPECT_TRUE(b) + +UTEST(sokol_audio, ring_buffer) { + _saudio_ring_t rb; + _saudio_ring_init(&rb, 4); + T(0 == rb.head); + T(0 == rb.tail); + T(5 == rb.num); + T(!_saudio_ring_full(&rb)); + T(_saudio_ring_empty(&rb)); + T(0 == _saudio_ring_count(&rb)); + + _saudio_ring_enqueue(&rb, 23); + T(1 == rb.head); + T(0 == rb.tail); + T(!_saudio_ring_full(&rb)); + T(!_saudio_ring_empty(&rb)); + T(1 == _saudio_ring_count(&rb)); + + T(23 == _saudio_ring_dequeue(&rb)); + T(1 == rb.head); + T(1 == rb.tail); + T(!_saudio_ring_full(&rb)); + T(_saudio_ring_empty(&rb)); + T(0 == _saudio_ring_count(&rb)); + + _saudio_ring_enqueue(&rb, 23); + _saudio_ring_enqueue(&rb, 46); + T(3 == rb.head); + T(1 == rb.tail); + T(!_saudio_ring_full(&rb)); + T(!_saudio_ring_empty(&rb)); + T(2 == _saudio_ring_count(&rb)); + T(23 == _saudio_ring_dequeue(&rb)); + T(46 == _saudio_ring_dequeue(&rb)); + T(3 == rb.head); + T(3 == rb.tail); + T(!_saudio_ring_full(&rb)); + T(_saudio_ring_empty(&rb)); + T(0 == _saudio_ring_count(&rb)); + + _saudio_ring_enqueue(&rb, 12); + _saudio_ring_enqueue(&rb, 34); + _saudio_ring_enqueue(&rb, 56); + _saudio_ring_enqueue(&rb, 78); + T(2 == rb.head); + T(3 == rb.tail); + T(_saudio_ring_full(&rb)); + T(!_saudio_ring_empty(&rb)); + T(4 == _saudio_ring_count(&rb)); + T(12 == _saudio_ring_dequeue(&rb)); + T(2 == rb.head); + T(4 == rb.tail); + T(!_saudio_ring_full(&rb)); + T(!_saudio_ring_empty(&rb)); + T(3 == _saudio_ring_count(&rb)); + _saudio_ring_enqueue(&rb, 90); + T(3 == rb.head); + T(4 == rb.tail); + T(_saudio_ring_full(&rb)); + T(!_saudio_ring_empty(&rb)); + T(4 == _saudio_ring_count(&rb)); + T(34 == _saudio_ring_dequeue(&rb)); + T(56 == _saudio_ring_dequeue(&rb)); + T(78 == _saudio_ring_dequeue(&rb)); + T(90 == _saudio_ring_dequeue(&rb)); + T(!_saudio_ring_full(&rb)); + T(_saudio_ring_empty(&rb)); + T(3 == rb.head); + T(3 == rb.tail); +} + +UTEST(saudio, api_test) { + saudio_setup(&(saudio_desc){ + .sample_rate = 22050, + .num_channels = 2, + .buffer_frames = 8192, + .num_packets = 128, + .packet_frames = 8192 / 128, + .user_data = (void*)12345 + }); + T(saudio_isvalid()); + T(saudio_query_desc().sample_rate == 22050); + T(saudio_userdata() == (void*)12345); + T(saudio_sample_rate() == 22050); + T(saudio_channels() == 2); + T(saudio_expect() == 8192); + T(saudio_buffer_frames() == 8192); +} diff --git a/source/engine/thirdparty/sokol/tests/functional/sokol_color_test.c b/source/engine/thirdparty/sokol/tests/functional/sokol_color_test.c new file mode 100644 index 0000000..c3c1345 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/sokol_color_test.c @@ -0,0 +1,70 @@ +//------------------------------------------------------------------------------ +// sokol_color_test.c +//------------------------------------------------------------------------------ +#include "sokol_gfx.h" +#define SOKOL_COLOR_IMPL +#include "sokol_color.h" +#include "utest.h" +#include + +#define T(b) EXPECT_TRUE(b) +#define TFLT(f0,f1,epsilon) {T(fabs((f0)-(f1))<=(epsilon));} + +UTEST(sokol_color, make_color) { + const sg_color c0 = sg_make_color_4b(255, 127, 0, 255); + TFLT(c0.r, 1.0f, 0.01f); + TFLT(c0.g, 0.5f, 0.01f); + TFLT(c0.b, 0.0f, 0.01f); + TFLT(c0.a, 1.0f, 0.01f); + const sg_color c1 = sg_make_color_1i(SG_BLACK_RGBA32); + TFLT(c1.r, 0.0f, 0.01f); + TFLT(c1.g, 0.0f, 0.01f); + TFLT(c1.b, 0.0f, 0.01f); + TFLT(c1.a, 1.0f, 0.01f); + const sg_color c2 = sg_make_color_1i(SG_GREEN_RGBA32); + TFLT(c2.r, 0.0f, 0.01f); + TFLT(c2.g, 1.0f, 0.01f); + TFLT(c2.b, 0.0f, 0.01f); + TFLT(c2.a, 1.0f, 0.01f); + const sg_color c3 = sg_make_color_1i(SG_RED_RGBA32); + TFLT(c3.r, 1.0f, 0.01f); + TFLT(c3.g, 0.0f, 0.01f); + TFLT(c3.b, 0.0f, 0.01f); + TFLT(c3.a, 1.0f, 0.01f); + const sg_color c4 = sg_make_color_1i(SG_BLUE_RGBA32); + TFLT(c4.r, 0.0f, 0.01f); + TFLT(c4.g, 0.0f, 0.01f); + TFLT(c4.b, 1.0f, 0.01f); + TFLT(c4.a, 1.0f, 0.01f); +} + +UTEST(sokol_color, lerp) { + const sg_color c0 = sg_color_lerp(&sg_red, &sg_green, 0.5f); + TFLT(c0.r, 0.5f, 0.001f); + TFLT(c0.g, 0.5f, 0.001f); + TFLT(c0.b, 0.0f, 0.001f); + TFLT(c0.a, 1.0f, 0.001f); + const sg_color c1 = sg_color_lerp_precise(&sg_red, &sg_green, 0.5f); + TFLT(c1.r, 0.5f, 0.001f); + TFLT(c1.g, 0.5f, 0.001f); + TFLT(c1.b, 0.0f, 0.001f); + TFLT(c1.a, 1.0f, 0.001f); +} + +UTEST(sokol_color, multiply) { + const sg_color c0 = sg_color_multiply(&sg_red, 0.5f); + TFLT(c0.r, 0.5f, 0.001f); + TFLT(c0.g, 0.0f, 0.001f); + TFLT(c0.b, 0.0f, 0.001f); + TFLT(c0.a, 0.5f, 0.001f); + const sg_color c1 = sg_color_multiply(&sg_green, 0.5f); + TFLT(c1.r, 0.0f, 0.001f); + TFLT(c1.g, 0.5f, 0.001f); + TFLT(c1.b, 0.0f, 0.001f); + TFLT(c1.a, 0.5f, 0.001f); + const sg_color c2 = sg_color_multiply(&sg_blue, 0.5f); + TFLT(c2.r, 0.0f, 0.001f); + TFLT(c2.g, 0.0f, 0.001f); + TFLT(c2.b, 0.5f, 0.001f); + TFLT(c2.a, 0.5f, 0.001f); +} diff --git a/source/engine/thirdparty/sokol/tests/functional/sokol_debugtext_test.c b/source/engine/thirdparty/sokol/tests/functional/sokol_debugtext_test.c new file mode 100644 index 0000000..205fd53 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/sokol_debugtext_test.c @@ -0,0 +1,520 @@ +//------------------------------------------------------------------------------ +// sokol-debugtext-test.c +// For best results, run with ASAN and UBSAN. +//------------------------------------------------------------------------------ +#include "sokol_gfx.h" +#include "sokol_log.h" +#define SOKOL_DEBUGTEXT_IMPL +#include "sokol_debugtext.h" +#include "utest.h" + +#define T(b) EXPECT_TRUE(b) +#define TFLT(f0,f1) {T(fabs((f0)-(f1))<=(0.000001));} + +static void init(void) { + sg_setup(&(sg_desc){ .logger = { .func = slog_func }}); + sdtx_setup(&(sdtx_desc_t){ .logger = { .func = slog_func }}); +} + +static void init_with(const sdtx_desc_t* desc) { + sg_setup(&(sg_desc){0}); + sdtx_setup(desc); +} + +static void shutdown(void) { + sdtx_shutdown(); + sg_shutdown(); +} + +UTEST(sokol_debugtext, default_init_shutdown) { + init(); + T(_sdtx.init_cookie == _SDTX_INIT_COOKIE); + T(_sdtx.desc.context_pool_size == _SDTX_DEFAULT_CONTEXT_POOL_SIZE); + T(_sdtx.desc.printf_buf_size == _SDTX_DEFAULT_PRINTF_BUF_SIZE); + T(_sdtx.desc.context.char_buf_size == _SDTX_DEFAULT_CHAR_BUF_SIZE); + T(_sdtx.desc.context.canvas_width == _SDTX_DEFAULT_CANVAS_WIDTH); + T(_sdtx.desc.context.canvas_height == _SDTX_DEFAULT_CANVAS_HEIGHT); + T(_sdtx.desc.context.tab_width == _SDTX_DEFAULT_TAB_WIDTH); + T(_sdtx.desc.context.color_format == 0); + T(_sdtx.desc.context.depth_format == 0); + T(_sdtx.desc.context.sample_count == 0); + for (int i = 0; i < SDTX_MAX_FONTS; i++) { + T(_sdtx.desc.fonts[i].data.ptr == 0); + T(_sdtx.desc.fonts[i].data.size == 0); + T(_sdtx.desc.fonts[i].first_char == 0); + T(_sdtx.desc.fonts[i].last_char == 0); + } + T(_sdtx.font_img.id != SG_INVALID_ID); + T(_sdtx.shader.id != SG_INVALID_ID); + T(_sdtx.fmt_buf_size == (_SDTX_DEFAULT_CHAR_BUF_SIZE + 1)); + T(_sdtx.fmt_buf); + T(_sdtx.def_ctx_id.id != 0); + T(_sdtx.def_ctx_id.id == _sdtx.cur_ctx_id.id); + T(_sdtx.cur_ctx == _sdtx_lookup_context(_sdtx.cur_ctx_id.id)); + T(_sdtx.cur_ctx->desc.char_buf_size == _sdtx.desc.context.char_buf_size); + T(_sdtx.cur_ctx->desc.canvas_width == _sdtx.desc.context.canvas_width); + T(_sdtx.cur_ctx->desc.canvas_height == _sdtx.desc.context.canvas_height); + T(_sdtx.cur_ctx->desc.tab_width == _sdtx.desc.context.tab_width); + T(_sdtx.cur_ctx->desc.color_format == 0); + T(_sdtx.cur_ctx->desc.depth_format == 0); + T(_sdtx.cur_ctx->desc.sample_count == 0); + T(_sdtx.cur_ctx->vertices.cap == _SDTX_DEFAULT_CHAR_BUF_SIZE * 6); + T(_sdtx.cur_ctx->vertices.next == 0); + T(_sdtx.cur_ctx->vertices.ptr); + T(_sdtx.cur_ctx->commands.cap == _SDTX_DEFAULT_MAX_COMMANDS); + T(_sdtx.cur_ctx->commands.next == 1); + T(_sdtx.cur_ctx->commands.ptr); + T(_sdtx.cur_ctx->vbuf.id != 0); + T(_sdtx.cur_ctx->pip.id != 0); + TFLT(_sdtx.cur_ctx->canvas_size.x, 640.0f); + TFLT(_sdtx.cur_ctx->canvas_size.y, 480.0f); + TFLT(_sdtx.cur_ctx->glyph_size.x, 8.0f / 640.0f); + TFLT(_sdtx.cur_ctx->glyph_size.y, 8.0f / 480.0f); + TFLT(_sdtx.cur_ctx->origin.x, 0.0f); + TFLT(_sdtx.cur_ctx->origin.y, 0.0f); + TFLT(_sdtx.cur_ctx->pos.x, 0.0f); + TFLT(_sdtx.cur_ctx->pos.y, 0.0f); + TFLT(_sdtx.cur_ctx->tab_width, 4.0f); + T(_sdtx.cur_ctx->color == _SDTX_DEFAULT_COLOR); + T(_sdtx.context_pool.contexts); + T(_sdtx.context_pool.pool.size == (_SDTX_DEFAULT_CONTEXT_POOL_SIZE + 1)); + shutdown(); + T(_sdtx.init_cookie == 0); +} + +UTEST(sokol_debugtext, init_with_params) { + init_with(&(sdtx_desc_t){ + .context_pool_size = 2, + .printf_buf_size = 128, + .context = { + .char_buf_size = 256, + .canvas_width = 320, + .canvas_height = 200, + .tab_width = 8, + .color_format = SG_PIXELFORMAT_RGBA8, + .depth_format = SG_PIXELFORMAT_DEPTH_STENCIL, + .sample_count = 4, + } + }); + T(_sdtx.init_cookie == _SDTX_INIT_COOKIE); + T(_sdtx.desc.context_pool_size == 2); + T(_sdtx.desc.printf_buf_size == 128); + T(_sdtx.desc.context.char_buf_size == 256); + T(_sdtx.desc.context.canvas_width == 320); + T(_sdtx.desc.context.canvas_height == 200); + T(_sdtx.desc.context.tab_width == 8); + T(_sdtx.desc.context.color_format == SG_PIXELFORMAT_RGBA8); + T(_sdtx.desc.context.depth_format == SG_PIXELFORMAT_DEPTH_STENCIL); + T(_sdtx.desc.context.sample_count == 4); + T(_sdtx.fmt_buf_size == 129); + T(_sdtx.cur_ctx->desc.char_buf_size == _sdtx.desc.context.char_buf_size); + T(_sdtx.cur_ctx->desc.canvas_width == _sdtx.desc.context.canvas_width); + T(_sdtx.cur_ctx->desc.canvas_height == _sdtx.desc.context.canvas_height); + T(_sdtx.cur_ctx->desc.tab_width == _sdtx.desc.context.tab_width); + T(_sdtx.cur_ctx->desc.color_format == SG_PIXELFORMAT_RGBA8); + T(_sdtx.cur_ctx->desc.depth_format == SG_PIXELFORMAT_DEPTH_STENCIL); + T(_sdtx.cur_ctx->desc.sample_count == 4); + T(_sdtx.cur_ctx->vertices.cap == (256 * 6)); + TFLT(_sdtx.cur_ctx->canvas_size.x, 320.0f); + TFLT(_sdtx.cur_ctx->canvas_size.y, 200.0f); + TFLT(_sdtx.cur_ctx->glyph_size.x, 8.0f / 320.0f); + TFLT(_sdtx.cur_ctx->glyph_size.y, 8.0f / 200.0f); + TFLT(_sdtx.cur_ctx->tab_width, 8.0f); + T(_sdtx.context_pool.pool.size == 3); + shutdown(); +} + +UTEST(sokol_debugtext, make_destroy_context) { + init(); + sdtx_context ctx_id = sdtx_make_context(&(sdtx_context_desc_t){ + .char_buf_size = 64, + .canvas_width = 1024, + .canvas_height = 768, + .tab_width = 3, + .color_format = SG_PIXELFORMAT_RGBA32F, + .sample_count = 2 + }); + T(ctx_id.id != 0); + T(ctx_id.id != _sdtx.cur_ctx_id.id); + _sdtx_context_t* ctx = _sdtx_lookup_context(ctx_id.id); + T(ctx); + T(ctx != _sdtx.cur_ctx); + T(ctx->desc.char_buf_size == 64); + T(ctx->desc.canvas_width == 1024); + T(ctx->desc.canvas_height == 768); + T(ctx->desc.tab_width == 3); + T(ctx->desc.color_format == SG_PIXELFORMAT_RGBA32F); + T(ctx->desc.depth_format == 0); + T(ctx->desc.sample_count == 2); + T(ctx->vertices.ptr); + T(ctx->vertices.next == 0); + T(ctx->vertices.cap == (64 * 6)); + TFLT(ctx->canvas_size.x, 1024.0f); + TFLT(ctx->canvas_size.y, 768.0f); + TFLT(ctx->glyph_size.x, 8.0f / 1024.0f); + TFLT(ctx->glyph_size.y, 8.0f / 768.0f); + TFLT(ctx->tab_width, 3.0f); + sdtx_destroy_context(ctx_id); + T(0 == _sdtx_lookup_context(ctx_id.id)); + T(ctx->desc.char_buf_size == 0); + T(ctx->vertices.ptr == 0); + shutdown(); +} + +UTEST(sokol_debugtext, get_default_context) { + // getting the default context must always return SDTX_DEFAULT_CONTEXT + init(); + T(sdtx_get_context().id == SDTX_DEFAULT_CONTEXT.id); + shutdown(); +} + +UTEST(sokol_debugtext, set_get_context) { + init(); + sdtx_context ctx_id = sdtx_make_context(&(sdtx_context_desc_t){ 0 }); + T(ctx_id.id != 0); + T(ctx_id.id != _sdtx.cur_ctx_id.id); + sdtx_set_context(ctx_id); + T(sdtx_get_context().id == ctx_id.id); + T(ctx_id.id == _sdtx.cur_ctx_id.id); + const _sdtx_context_t* ctx = _sdtx_lookup_context(ctx_id.id); + T(ctx == _sdtx.cur_ctx); + sdtx_set_context(SDTX_DEFAULT_CONTEXT); + T(sdtx_get_context().id == SDTX_DEFAULT_CONTEXT.id); + T(_sdtx.cur_ctx); + T(ctx != _sdtx.cur_ctx); + T(_sdtx.cur_ctx == _sdtx_lookup_context(_sdtx.def_ctx_id.id)); + shutdown(); +} + +UTEST(sokol_debugtext, destroy_default_context) { + // destroying the default context is not allowed + init(); + sdtx_context def_ctx_id = _sdtx.def_ctx_id; + T(def_ctx_id.id == _sdtx.cur_ctx_id.id); + sdtx_destroy_context(def_ctx_id); + T(def_ctx_id.id == _sdtx.def_ctx_id.id); + T(def_ctx_id.id == _sdtx.cur_ctx_id.id); + T(_sdtx.cur_ctx); + shutdown(); +} + +UTEST(sokol_debugtext, destroy_current_context) { + // destroying the current context has the same effect + // as setting a current context with an invalid context handle + init(); + sdtx_context ctx_id = sdtx_make_context(&(sdtx_context_desc_t){ 0 }); + sdtx_set_context(ctx_id); + T(sdtx_get_context().id == ctx_id.id); + T(ctx_id.id == _sdtx.cur_ctx_id.id); + T(_sdtx_lookup_context(ctx_id.id) == _sdtx.cur_ctx); + sdtx_destroy_context(ctx_id); + T(_sdtx.cur_ctx_id.id == ctx_id.id); + T(_sdtx.cur_ctx == 0); + T(sdtx_get_context().id == ctx_id.id); + shutdown(); +} + +UTEST(sokol_debugtext, ignore_invalid_context_handle) { + // trying to render with an invalid context handle must not crash, + // instead ignore all operations + init(); + sdtx_context ctx_id = sdtx_make_context(&(sdtx_context_desc_t){ 0 }); + sdtx_set_context(ctx_id); + sdtx_destroy_context(ctx_id); + T(0 == _sdtx.cur_ctx); + T(sdtx_get_context().id == ctx_id.id); + sdtx_font(0); + sdtx_canvas(100.0f, 200.0f); + sdtx_origin(10.0f, 10.0f); + sdtx_home(); + sdtx_pos(1.0f, 2.0f); + sdtx_pos_x(1.0f); + sdtx_pos_y(2.0f); + sdtx_move(2.0f, 3.0f); + sdtx_move_x(2.0f); + sdtx_move_y(3.0f); + sdtx_crlf(); + sdtx_color3b(255, 255, 255); + sdtx_color3f(1.0f, 1.0f, 1.0f); + sdtx_color4b(255, 255, 255, 255); + sdtx_color4f(1.0f, 1.0f, 1.0f, 1.0f); + sdtx_color1i(0xFFFFFFFF); + sdtx_putc('A'); + sdtx_puts("Hello World!"); + sdtx_putr("Hello World!", 5); + sdtx_printf("Hello World %d %d %d", 1, 2, 3); + shutdown(); +} + +UTEST(sokol_debugtext, set_font) { + init(); + T(0 == _sdtx.cur_ctx->cur_font); + sdtx_font(1); + T(1 == _sdtx.cur_ctx->cur_font); + sdtx_font(2); + T(2 == _sdtx.cur_ctx->cur_font); + shutdown(); +} + +UTEST(sokol_debugtext, set_canvas) { + init(); + sdtx_origin(10.0f, 11.0f); + sdtx_pos(1.0f, 2.0f); + sdtx_canvas(320.0f, 200.0f); + TFLT(_sdtx.cur_ctx->canvas_size.x, 320.0f); + TFLT(_sdtx.cur_ctx->canvas_size.y, 200.0f); + TFLT(_sdtx.cur_ctx->glyph_size.x, 8.0f / 320.0f); + TFLT(_sdtx.cur_ctx->glyph_size.y, 8.0f / 200.0f); + // origin and pos must be reset to 0 when canvas is set + TFLT(_sdtx.cur_ctx->origin.x, 0.0f); + TFLT(_sdtx.cur_ctx->origin.y, 0.0f); + TFLT(_sdtx.cur_ctx->pos.x, 0.0f); + TFLT(_sdtx.cur_ctx->pos.y, 0.0f); + shutdown(); +} + +UTEST(sokol_debugtext, set_origin) { + init(); + sdtx_origin(10.0f, 20.0f); + TFLT(_sdtx.cur_ctx->origin.x, 10.0f); + TFLT(_sdtx.cur_ctx->origin.y, 20.0f); + shutdown(); +} + +UTEST(sokol_debugtext, cursor_movement) { + init(); + sdtx_pos(1.0f, 2.0f); + TFLT(_sdtx.cur_ctx->pos.x, 1.0f); + TFLT(_sdtx.cur_ctx->pos.y, 2.0f); + sdtx_pos_x(5.0f); + TFLT(_sdtx.cur_ctx->pos.x, 5.0f); + TFLT(_sdtx.cur_ctx->pos.y, 2.0f); + sdtx_pos_y(6.0f); + TFLT(_sdtx.cur_ctx->pos.x, 5.0f); + TFLT(_sdtx.cur_ctx->pos.y, 6.0f); + sdtx_move(-1.0f, -3.0f); + TFLT(_sdtx.cur_ctx->pos.x, 4.0f); + TFLT(_sdtx.cur_ctx->pos.y, 3.0f); + sdtx_move_x(+1.0f); + TFLT(_sdtx.cur_ctx->pos.x, 5.0f); + TFLT(_sdtx.cur_ctx->pos.y, 3.0f); + sdtx_move_y(+3.0f); + TFLT(_sdtx.cur_ctx->pos.x, 5.0f); + TFLT(_sdtx.cur_ctx->pos.y, 6.0f); + sdtx_crlf(); + TFLT(_sdtx.cur_ctx->pos.x, 0.0f); + TFLT(_sdtx.cur_ctx->pos.y, 7.0f); + sdtx_pos(20.0f, 30.0f); + sdtx_home(); + TFLT(_sdtx.cur_ctx->pos.x, 0.0f); + TFLT(_sdtx.cur_ctx->pos.y, 0.0f); + shutdown(); +} + +UTEST(sokol_debugtext, set_color) { + init(); + T(_sdtx.cur_ctx->color == _SDTX_DEFAULT_COLOR); + sdtx_color3b(255, 127, 0); + T(_sdtx.cur_ctx->color == 0xFF007FFF); + sdtx_color4b(0, 127, 255, 255); + T(_sdtx.cur_ctx->color == 0xFFFF7F00); + sdtx_color3f(1.0f, 0.5f, 0.0f); + T(_sdtx.cur_ctx->color == 0xFF007FFF); + sdtx_color4f(0.0f, 0.5f, 1.0f, 1.0f); + T(_sdtx.cur_ctx->color == 0xFFFF7F00); + sdtx_color1i(0xFF000000); + T(_sdtx.cur_ctx->color == 0xFF000000); + shutdown(); +} + +UTEST(sokol_debugtext, vertex_overflow) { + // overflowing the vertex buffer must not crash + init_with(&(sdtx_desc_t){ + .context.char_buf_size = 8, + }); + for (int i = 0; i < 32; i++) { + sdtx_putc('A'); + } + sdtx_puts("1234567890"); + sdtx_putr("1234567890", 5); + sdtx_printf("Hello World %d!\n", 12); + T(_sdtx.cur_ctx->vertices.next == _sdtx.cur_ctx->vertices.cap); + shutdown(); +} + +UTEST(sokol_debugtext, context_overflow) { + // creating too many contexts should not crash + init_with(&(sdtx_desc_t){ + .context_pool_size = 4, + }); + T(_sdtx.context_pool.pool.size == 5); + // one slot is taken by the default context + sdtx_context ctx[4]; + for (int i = 0; i < 4; i++) { + ctx[i] = sdtx_make_context(&(sdtx_context_desc_t){ 0 }); + if (i < 3) { + T(ctx[i].id != 0); + } + else { + T(ctx[i].id == 0); + } + } + // destroying an invalid context should not crash + for (int i = 0; i < 4; i++) { + sdtx_destroy_context(ctx[i]); + } + shutdown(); +} + +UTEST(sokol_debugtext, printf_overflow) { + // overflowing the printf formatting buffer should not crash + init_with(&(sdtx_desc_t){ + .printf_buf_size = 8 + }); + T(9 == _sdtx.fmt_buf_size); + T(16 == sdtx_printf("Hello %d\n", 123456789)); + T('H' == _sdtx.fmt_buf[0]) + T('e' == _sdtx.fmt_buf[1]) + T('l' == _sdtx.fmt_buf[2]) + T('l' == _sdtx.fmt_buf[3]) + T('o' == _sdtx.fmt_buf[4]) + T(' ' == _sdtx.fmt_buf[5]) + T('1' == _sdtx.fmt_buf[6]) + T('2' == _sdtx.fmt_buf[7]) + T(0 == _sdtx.fmt_buf[8]) + shutdown(); +} + +UTEST(sokol_debugtext, rewind_after_draw) { + // calling sdtx_draw() must rewind the cursor position, font and + // vertex pointer, to keep canvas size and origin as is + init(); + sdtx_canvas(256, 128); + TFLT(_sdtx.cur_ctx->canvas_size.x, 256); + TFLT(_sdtx.cur_ctx->canvas_size.y, 128); + sdtx_origin(5, 5); + TFLT(_sdtx.cur_ctx->origin.x, 5); + TFLT(_sdtx.cur_ctx->origin.y, 5); + sdtx_pos(10, 20); + TFLT(_sdtx.cur_ctx->pos.x, 10); + TFLT(_sdtx.cur_ctx->pos.y, 20); + sdtx_font(3); + T(_sdtx.cur_ctx->cur_font == 3); + sdtx_printf("Hello World!\n"); + T(_sdtx.cur_ctx->vertices.next != 0); + sg_begin_pass(&(sg_pass){ + .swapchain = { + .width = 256, + .height = 256, + .sample_count = 1, + .color_format = SG_PIXELFORMAT_RGBA8, + .depth_format = SG_PIXELFORMAT_DEPTH_STENCIL, + } + }); + sdtx_draw(); + sg_end_pass(); + sg_commit(); + TFLT(_sdtx.cur_ctx->canvas_size.x, 256); + TFLT(_sdtx.cur_ctx->canvas_size.y, 128); + TFLT(_sdtx.cur_ctx->origin.x, 5); + TFLT(_sdtx.cur_ctx->origin.y, 5); + TFLT(_sdtx.cur_ctx->pos.x, 0); + TFLT(_sdtx.cur_ctx->pos.x, 0); + T(_sdtx.cur_ctx->cur_font == 0); + T(_sdtx.cur_ctx->vertices.next == 0); + shutdown(); +} + +UTEST(sokol_debugtext, putr) { + // test if sdtx_putr() draws the right amount of characters + init(); + int start_index = _sdtx.cur_ctx->vertices.next; + sdtx_putr("Hello World!", 5); + T((5 * 6) == (_sdtx.cur_ctx->vertices.next - start_index)); + + start_index = _sdtx.cur_ctx->vertices.next; + sdtx_putr("Hello!\n\n\n\n\n\n\n\n\n\n\n", 10); + // NOTE: the \n's don't result in rendered vertices + T((6 * 6) == (_sdtx.cur_ctx->vertices.next - start_index)); + shutdown(); +} + +UTEST(sokol_debugtext, default_context) { + init(); + T(sdtx_default_context().id == SDTX_DEFAULT_CONTEXT.id); + shutdown(); +} + +// switching layers without any text inbetween should not advance the current draw command +UTEST(sokol_debug_text, empty_layers) { + init(); + T(_sdtx.cur_ctx->commands.next == 1); + T(_sdtx.cur_ctx->commands.ptr[0].layer_id == 0); + sdtx_layer(1); + T(_sdtx.cur_ctx->commands.next == 1); + T(_sdtx.cur_ctx->commands.ptr[0].layer_id == 1); + sdtx_layer(2); + T(_sdtx.cur_ctx->commands.next == 1); + T(_sdtx.cur_ctx->commands.ptr[0].layer_id == 2); + sdtx_layer(0); + T(_sdtx.cur_ctx->commands.next == 1); + T(_sdtx.cur_ctx->commands.ptr[0].layer_id == 0); + shutdown(); +} + +// switching layers with text inbetween should advance the current draw command +UTEST(sokol_debug_text, non_empty_layers) { + init(); + T(_sdtx.cur_ctx->commands.next == 1); + T(_sdtx.cur_ctx->commands.ptr[0].layer_id == 0); + T(_sdtx.cur_ctx->commands.ptr[0].first_vertex == 0); + T(_sdtx.cur_ctx->commands.ptr[0].num_vertices == 0); + sdtx_puts("123"); + T(_sdtx.cur_ctx->commands.next == 1); + T(_sdtx.cur_ctx->commands.ptr[0].layer_id == 0); + T(_sdtx.cur_ctx->commands.ptr[0].first_vertex == 0); + T(_sdtx.cur_ctx->commands.ptr[0].num_vertices == (3 * 6)); + sdtx_layer(1); + sdtx_puts("1234"); + T(_sdtx.cur_ctx->commands.next == 2); + T(_sdtx.cur_ctx->commands.ptr[1].layer_id == 1); + T(_sdtx.cur_ctx->commands.ptr[1].first_vertex == (3 * 6)); + T(_sdtx.cur_ctx->commands.ptr[1].num_vertices == (4 * 6)); + // switching to same layer should not start a new draw commands + sdtx_layer(1); + sdtx_puts("12345"); + T(_sdtx.cur_ctx->commands.next == 2); + T(_sdtx.cur_ctx->commands.ptr[1].layer_id == 1); + T(_sdtx.cur_ctx->commands.ptr[1].first_vertex == (3 * 6)); + T(_sdtx.cur_ctx->commands.ptr[1].num_vertices == (9 * 6)); + sdtx_layer(0); + sdtx_puts("123456"); + T(_sdtx.cur_ctx->commands.next == 3); + T(_sdtx.cur_ctx->commands.ptr[2].layer_id == 0); + T(_sdtx.cur_ctx->commands.ptr[2].first_vertex == (12 * 6)); + T(_sdtx.cur_ctx->commands.ptr[2].num_vertices == (6 * 6)); + shutdown(); +} + +UTEST(sokol_debug_text, command_buffer_overflow) { + init_with(&(sdtx_desc_t){ + .context = { + .max_commands = 4 + } + }); + sdtx_puts("0"); + T(_sdtx.cur_ctx->commands.next == 1); + sdtx_layer(1); + sdtx_puts("1"); + T(_sdtx.cur_ctx->commands.next == 2); + sdtx_layer(2); + sdtx_puts("2"); + T(_sdtx.cur_ctx->commands.next == 3); + sdtx_layer(3); + sdtx_puts("3"); + T(_sdtx.cur_ctx->commands.next == 4); + // from here on should fail + sdtx_layer(4); + sdtx_puts("4"); + T(_sdtx.cur_ctx->commands.next == 4); +} \ No newline at end of file diff --git a/source/engine/thirdparty/sokol/tests/functional/sokol_fetch_test.c b/source/engine/thirdparty/sokol/tests/functional/sokol_fetch_test.c new file mode 100644 index 0000000..8318a9e --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/sokol_fetch_test.c @@ -0,0 +1,862 @@ +//------------------------------------------------------------------------------ +// sokol-fetch-test.c +// +// FIXME: simulate allocation errors +//------------------------------------------------------------------------------ +#define SOKOL_IMPL +#define SFETCH_MAX_USERDATA_UINT64 (8) +#define SFETCH_MAX_PATH (32) +#include "sokol_fetch.h" +#include "utest.h" + +#define T(b) EXPECT_TRUE(b) +#define TSTR(s0, s1) EXPECT_TRUE(0 == strcmp(s0,s1)) + +static uint8_t load_file_buf[500000]; +static const uint64_t combatsignal_file_size = 409482; + +typedef struct { + int a, b, c; +} userdata_t; + +static const _sfetch_item_t zeroed_item = {0}; + +#ifdef _WIN32 +#include +static void sleep_ms(int ms) { + Sleep((DWORD)ms); +} +#else +#include +static void sleep_ms(uint32_t ms) { + usleep(ms * 1000); +} +#endif + +/* private implementation functions */ +UTEST(sokol_fetch, path_make) { + const char* str31 = "1234567890123456789012345678901"; + const char* str32 = "12345678901234567890123456789012"; + // max allowed string length (MAX_PATH - 1) + _sfetch_path_t p31 = _sfetch_path_make(str31); + TSTR(p31.buf, str31); + // overflow + _sfetch_path_t p32 = _sfetch_path_make(str32); + T(p32.buf[0] == 0); +} + +UTEST(sokol_fetch, make_id) { + uint32_t slot_id = _sfetch_make_id(123, 456); + T(slot_id == ((456<<16)|123)); + T(_sfetch_slot_index(slot_id) == 123); +} + +UTEST(sokol_fetch, item_init_discard) { + userdata_t user_data = { + .a = 123, + .b = 456, + .c = 789 + }; + sfetch_request_t request = { + .channel = 4, + .path = "hello_world.txt", + .chunk_size = 128, + .user_data = SFETCH_RANGE(user_data) + }; + _sfetch_item_t item = zeroed_item; + uint32_t slot_id = _sfetch_make_id(1, 1); + _sfetch_item_init(&item, slot_id, &request); + T(item.handle.id == slot_id); + T(item.channel == 4); + T(item.lane == _SFETCH_INVALID_LANE); + T(item.chunk_size == 128); + T(item.state == _SFETCH_STATE_INITIAL); + TSTR(item.path.buf, request.path); + T(item.user.user_data_size == sizeof(userdata_t)); + const userdata_t* ud = (const userdata_t*) item.user.user_data; + T((((uintptr_t)ud) & 0x7) == 0); // check alignment + T(ud->a == 123); + T(ud->b == 456); + T(ud->c == 789); + + item.state = _SFETCH_STATE_FETCHING; + _sfetch_item_discard(&item); + T(item.handle.id == 0); + T(item.path.buf[0] == 0); + T(item.state == _SFETCH_STATE_INITIAL); + T(item.user.user_data_size == 0); + T(item.user.user_data[0] == 0); +} + +UTEST(sokol_fetch, item_init_path_overflow) { + sfetch_request_t request = { + .path = "012345678901234567890123456789012", + }; + _sfetch_item_t item = zeroed_item; + _sfetch_item_init(&item, _sfetch_make_id(1, 1), &request); + T(item.path.buf[0] == 0); +} + +UTEST(sokol_fetch, item_init_userdata_overflow) { + uint8_t big_data[128] = { 0xFF }; + sfetch_request_t request = { + .path = "hello_world.txt", + .user_data = SFETCH_RANGE(big_data), + }; + _sfetch_item_t item = zeroed_item; + _sfetch_item_init(&item, _sfetch_make_id(1, 1), &request); + T(item.user.user_data_size == 0); + T(item.user.user_data[0] == 0); +} + +UTEST(sokol_fetch, pool_init_discard) { + sfetch_setup(&(sfetch_desc_t){0}); + _sfetch_pool_t pool = {0}; + const uint32_t num_items = 127; + T(_sfetch_pool_init(&pool, num_items)); + T(pool.valid); + T(pool.size == 128); + T(pool.free_top == 127); + T(pool.free_slots[0] == 127); + T(pool.free_slots[1] == 126); + T(pool.free_slots[126] == 1); + _sfetch_pool_discard(&pool); + T(!pool.valid); + T(pool.free_slots == 0); + T(pool.items == 0); + sfetch_shutdown(); +} + +UTEST(sokol_fetch, pool_alloc_free) { + sfetch_setup(&(sfetch_desc_t){0}); + uint8_t buf[32]; + _sfetch_pool_t pool = {0}; + const uint32_t num_items = 16; + _sfetch_pool_init(&pool, num_items); + uint32_t slot_id = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ + .path = "hello_world.txt", + .buffer = SFETCH_RANGE(buf), + }); + T(slot_id == 0x00010001); + T(pool.items[1].state == _SFETCH_STATE_ALLOCATED); + T(pool.items[1].handle.id == slot_id); + TSTR(pool.items[1].path.buf, "hello_world.txt"); + T(pool.items[1].buffer.ptr == buf); + T(pool.items[1].buffer.size == sizeof(buf)); + T(pool.free_top == 15); + _sfetch_pool_item_free(&pool, slot_id); + T(pool.items[1].handle.id == 0); + T(pool.items[1].state == _SFETCH_STATE_INITIAL); + T(pool.items[1].path.buf[0] == 0); + T(pool.items[1].buffer.ptr == 0); + T(pool.items[1].buffer.size == 0); + T(pool.free_top == 16); + _sfetch_pool_discard(&pool); + sfetch_shutdown(); +} + +UTEST(sokol_fetch, pool_overflow) { + sfetch_setup(&(sfetch_desc_t){0}); + _sfetch_pool_t pool = {0}; + _sfetch_pool_init(&pool, 4); + uint32_t id0 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path0" }); + uint32_t id1 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path1" }); + uint32_t id2 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path2" }); + uint32_t id3 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path3" }); + // next alloc should fail + uint32_t id4 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path4" }); + T(id0 == 0x00010001); + T(id1 == 0x00010002); + T(id2 == 0x00010003); + T(id3 == 0x00010004); + T(id4 == 0); + T(pool.items[1].handle.id == id0); + T(pool.items[2].handle.id == id1); + T(pool.items[3].handle.id == id2); + T(pool.items[4].handle.id == id3); + // free one item, alloc should work now + _sfetch_pool_item_free(&pool, id0); + uint32_t id5 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path5" }); + T(id5 == 0x00020001); + T(pool.items[1].handle.id == id5); + TSTR(pool.items[1].path.buf, "path5"); + _sfetch_pool_discard(&pool); + sfetch_shutdown(); +} + +UTEST(sokol_fetch, lookup_item) { + sfetch_setup(&(sfetch_desc_t){0}); + _sfetch_pool_t pool = {0}; + _sfetch_pool_init(&pool, 4); + uint32_t id0 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path0" }); + uint32_t id1 = _sfetch_pool_item_alloc(&pool, &(sfetch_request_t){ .path="path1" }); + const _sfetch_item_t* item0 = _sfetch_pool_item_lookup(&pool, id0); + const _sfetch_item_t* item1 = _sfetch_pool_item_lookup(&pool, id1); + T(item0 == &pool.items[1]); + T(item1 == &pool.items[2]); + /* invalid handle always returns 0-ptr */ + T(0 == _sfetch_pool_item_lookup(&pool, _sfetch_make_id(0, 0))); + /* free an item and make sure it's detected as dangling */ + _sfetch_pool_item_free(&pool, id0); + T(0 == _sfetch_pool_item_lookup(&pool, id0)); + _sfetch_pool_discard(&pool); + sfetch_shutdown(); +} + +UTEST(sokol_fetch, ring_init_discard) { + sfetch_setup(&(sfetch_desc_t){0}); + _sfetch_ring_t ring = {0}; + const uint32_t num_slots = 4; + T(_sfetch_ring_init(&ring, num_slots)); + T(ring.head == 0); + T(ring.tail == 0); + T(ring.num == (num_slots + 1)); + T(ring.buf); + _sfetch_ring_discard(&ring); + T(ring.head == 0); + T(ring.tail == 0); + T(ring.num == 0); + T(ring.buf == 0); + sfetch_shutdown(); +} + +UTEST(sokol_fetch, ring_enqueue_dequeue) { + sfetch_setup(&(sfetch_desc_t){0}); + _sfetch_ring_t ring = {0}; + const uint32_t num_slots = 4; + _sfetch_ring_init(&ring, num_slots); + T(_sfetch_ring_count(&ring) == 0); + T(_sfetch_ring_empty(&ring)); + T(!_sfetch_ring_full(&ring)); + for (uint32_t i = 0; i < num_slots; i++) { + T(!_sfetch_ring_full(&ring)); + _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1)); + T(_sfetch_ring_count(&ring) == (i+1)); + T(!_sfetch_ring_empty(&ring)); + } + T(_sfetch_ring_count(&ring) == 4); + T(!_sfetch_ring_empty(&ring)); + T(_sfetch_ring_full(&ring)); + for (uint32_t i = 0; i < num_slots; i++) { + T(_sfetch_ring_peek(&ring, i) == _sfetch_make_id(1, i+1)); + } + for (uint32_t i = 0; i < num_slots; i++) { + T(!_sfetch_ring_empty(&ring)); + const uint32_t slot_id = _sfetch_ring_dequeue(&ring); + T(slot_id == _sfetch_make_id(1, i+1)); + T(!_sfetch_ring_full(&ring)); + } + T(_sfetch_ring_count(&ring) == 0); + T(_sfetch_ring_empty(&ring)); + T(!_sfetch_ring_full(&ring)); + _sfetch_ring_discard(&ring); + sfetch_shutdown(); +} + +UTEST(sokol_fetch, ring_wrap_around) { + sfetch_setup(&(sfetch_desc_t){0}); + _sfetch_ring_t ring = {0}; + _sfetch_ring_init(&ring, 4); + uint32_t i = 0; + for (i = 0; i < 4; i++) { + _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1)); + } + T(_sfetch_ring_full(&ring)); + for (; i < 64; i++) { + T(_sfetch_ring_full(&ring)); + T(_sfetch_ring_dequeue(&ring) == _sfetch_make_id(1, (i - 3))); + T(!_sfetch_ring_full(&ring)); + _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1)); + } + T(_sfetch_ring_full(&ring)); + for (i = 0; i < 4; i++) { + T(_sfetch_ring_dequeue(&ring) == _sfetch_make_id(1, (i + 61))); + } + T(_sfetch_ring_empty(&ring)); + _sfetch_ring_discard(&ring); + sfetch_shutdown(); +} + +UTEST(sokol_fetch, ring_wrap_count) { + sfetch_setup(&(sfetch_desc_t){0}); + _sfetch_ring_t ring = {0}; + _sfetch_ring_init(&ring, 8); + // add and remove 4 items to move tail to the middle + for (uint32_t i = 0; i < 4; i++) { + _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1)); + _sfetch_ring_dequeue(&ring); + T(_sfetch_ring_empty(&ring)); + } + // add another 8 items + for (uint32_t i = 0; i < 8; i++) { + _sfetch_ring_enqueue(&ring, _sfetch_make_id(1, i+1)); + } + // now test, dequeue and test... + T(_sfetch_ring_full(&ring)); + for (uint32_t i = 0; i < 8; i++) { + T(_sfetch_ring_count(&ring) == (8 - i)); + _sfetch_ring_dequeue(&ring); + } + T(_sfetch_ring_count(&ring) == 0); + T(_sfetch_ring_empty(&ring)); + _sfetch_ring_discard(&ring); + sfetch_shutdown(); +} + +/* NOTE: channel_worker is called from a thread */ +static int num_processed_items = 0; +static void channel_worker(_sfetch_t* ctx, uint32_t slot_id) { + (void)ctx; + (void)slot_id; + num_processed_items++; +} + +UTEST(sokol_fetch, channel_init_discard) { + sfetch_setup(&(sfetch_desc_t){0}); + num_processed_items = 0; + _sfetch_channel_t chn = {0}; + const uint32_t num_slots = 12; + const uint32_t num_lanes = 64; + _sfetch_channel_init(&chn, 0, num_slots, num_lanes, channel_worker); + T(chn.valid); + T(_sfetch_ring_full(&chn.free_lanes)); + T(_sfetch_ring_empty(&chn.user_sent)); + T(_sfetch_ring_empty(&chn.user_incoming)); + #if !defined(__EMSCRIPTEN__) + T(_sfetch_ring_empty(&chn.thread_incoming)); + T(_sfetch_ring_empty(&chn.thread_outgoing)); + #endif + T(_sfetch_ring_empty(&chn.user_outgoing)); + _sfetch_channel_discard(&chn); + T(!chn.valid); + sfetch_shutdown(); +} + +/* public API functions */ +UTEST(sokol_fetch, setup_shutdown) { + sfetch_setup(&(sfetch_desc_t){0}); + T(sfetch_valid()); + // check default values + T(sfetch_desc().max_requests == 128); + T(sfetch_desc().num_channels == 1); + T(sfetch_desc().num_lanes == 1); + sfetch_shutdown(); + T(!sfetch_valid()); +} + +UTEST(sokol_fetch, setup_too_many_channels) { + /* try to initialize with too many channels, this should clamp to + SFETCH_MAX_CHANNELS + */ + sfetch_setup(&(sfetch_desc_t){ + .num_channels = 64 + }); + T(sfetch_valid()); + T(sfetch_desc().num_channels == SFETCH_MAX_CHANNELS); + sfetch_shutdown(); +} + +UTEST(sokol_fetch, max_path) { + T(sfetch_max_path() == SFETCH_MAX_PATH); +} + +UTEST(sokol_fetch, max_userdata) { + T(sfetch_max_userdata_bytes() == (SFETCH_MAX_USERDATA_UINT64 * sizeof(uint64_t))); +} + +static uint8_t fail_open_buffer[128]; +static bool fail_open_passed; +static void fail_open_callback(const sfetch_response_t* response) { + /* if opening a file fails, it will immediate switch into CLOSED state */ + if ((response->failed) && (response->error_code == SFETCH_ERROR_FILE_NOT_FOUND)) { + fail_open_passed = true; + } +} + +UTEST(sokol_fetch, fail_open) { + sfetch_setup(&(sfetch_desc_t){0}); + sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ + .path = "non_existing_file.txt", + .callback = fail_open_callback, + .buffer = SFETCH_RANGE(fail_open_buffer), + }); + fail_open_passed = false; + int frame_count = 0; + const int max_frames = 10000; + while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) { + sfetch_dowork(); + sleep_ms(1); + } + T(frame_count < max_frames); + T(fail_open_passed); + sfetch_shutdown(); +} + +static bool load_file_fixed_buffer_passed; + +// The file callback is called from the "current user thread" (the same +// thread where the sfetch_send() for this request was called). Note that you +// can call sfetch_setup/shutdown() on multiple threads, each thread will +// get its own thread-local "sokol-fetch instance" and its own set of +// IO-channel threads. +static void load_file_fixed_buffer_callback(const sfetch_response_t* response) { + // when loading the whole file at once, the fetched state + // is the best place to grab/process the data + if (response->fetched) { + if ((response->data_offset == 0) && + (response->data.ptr == load_file_buf) && + (response->data.size == combatsignal_file_size) && + (response->buffer.ptr == load_file_buf) && + (response->buffer.size == sizeof(load_file_buf)) && + response->finished) + { + load_file_fixed_buffer_passed = true; + } + } +} + +UTEST(sokol_fetch, load_file_fixed_buffer) { + memset(load_file_buf, 0, sizeof(load_file_buf)); + sfetch_setup(&(sfetch_desc_t){0}); + // send a load-request for a file where we know the max size upfront, + // so we can provide a buffer right in the fetch request (otherwise + // the buffer needs to be provided in the callback when the request + // is in OPENED state, since only then the file size will be known). + sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ + .path = "comsi.s3m", + .callback = load_file_fixed_buffer_callback, + .buffer = SFETCH_RANGE(load_file_buf), + }); + // simulate a frame-loop for as long as the request is in flight, normally + // the sfetch_dowork() function is just called somewhere in the frame + // to pump messages in and out of the IO threads, and invoke user-callbacks + int frame_count = 0; + const int max_frames = 10000; + while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) { + sfetch_dowork(); + sleep_ms(1); + } + T(frame_count < max_frames); + T(load_file_fixed_buffer_passed); + sfetch_shutdown(); +} + +/* tests whether files with unknown size are processed correctly */ +static bool load_file_unknown_size_opened_passed; +static bool load_file_unknown_size_fetched_passed; +static void load_file_unknown_size_callback(const sfetch_response_t* response) { + if (response->dispatched) { + if ((response->data_offset == 0) && + (response->data.ptr == 0) && + (response->data.size == 0) && + (response->buffer.ptr == 0) && + (response->buffer.size == 0) && + !response->finished) + { + load_file_unknown_size_opened_passed = true; + sfetch_bind_buffer(response->handle, SFETCH_RANGE(load_file_buf)); + } + } + else if (response->fetched) { + if ((response->data_offset == 0) && + (response->data.ptr == load_file_buf) && + (response->data.size == combatsignal_file_size) && + (response->buffer.ptr == load_file_buf) && + (response->buffer.size == sizeof(load_file_buf)) && + response->finished) + { + load_file_unknown_size_fetched_passed = true; + } + } +} + +UTEST(sokol_fetch, load_file_unknown_size) { + memset(load_file_buf, 0, sizeof(load_file_buf)); + sfetch_setup(&(sfetch_desc_t){0}); + sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ + .path = "comsi.s3m", + .callback = load_file_unknown_size_callback + }); + int frame_count = 0; + const int max_frames = 10000; + while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) { + sfetch_dowork(); + sleep_ms(1); + } + T(frame_count < max_frames); + T(load_file_unknown_size_opened_passed); + T(load_file_unknown_size_fetched_passed); + sfetch_shutdown(); +} + +/* tests whether not providing a buffer in OPENED properly fails */ +static bool load_file_no_buffer_opened_passed; +static bool load_file_no_buffer_failed_passed; +static void load_file_no_buffer_callback(const sfetch_response_t* response) { + if (response->dispatched) { + if ((response->data_offset == 0) && + (response->data.ptr == 0) && + (response->data.size == 0) && + (response->buffer.ptr == 0) && + (response->buffer.size == 0) && + !response->finished) + { + /* DO NOT provide a buffer here, see if that properly fails */ + load_file_no_buffer_opened_passed = true; + } + } + else if ((response->failed) && (response->error_code == SFETCH_ERROR_NO_BUFFER)) { + if (load_file_no_buffer_opened_passed) { + load_file_no_buffer_failed_passed = true; + } + } +} + +UTEST(sokol_fetch, load_file_no_buffer) { + memset(load_file_buf, 0, sizeof(load_file_buf)); + sfetch_setup(&(sfetch_desc_t){0}); + sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ + .path = "comsi.s3m", + .callback = load_file_no_buffer_callback + }); + int frame_count = 0; + const int max_frames = 10000; + while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) { + sfetch_dowork(); + sleep_ms(1); + } + T(frame_count < max_frames); + T(load_file_no_buffer_opened_passed); + T(load_file_no_buffer_failed_passed); + sfetch_shutdown(); +} + +static bool load_file_too_small_passed; +static uint8_t load_file_too_small_buf[8192]; +static void load_file_too_small_callback(const sfetch_response_t* response) { + if (response->failed && (response->error_code == SFETCH_ERROR_BUFFER_TOO_SMALL)) { + load_file_too_small_passed = true; + } +} + +UTEST(sokol_fetch, load_file_too_small_buffer) { + memset(load_file_buf, 0, sizeof(load_file_buf)); + sfetch_setup(&(sfetch_desc_t){0}); + sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ + .path = "comsi.s3m", + .callback = load_file_too_small_callback, + .buffer = SFETCH_RANGE(load_file_too_small_buf), + }); + int frame_count = 0; + const int max_frames = 10000; + while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) { + sfetch_dowork(); + sleep_ms(1); + } + T(frame_count < max_frames); + T(load_file_too_small_passed); + sfetch_shutdown(); +} + + +/* test loading a big file via a small chunk-buffer, the callback will + be called multiple times with the FETCHED state until the entire file + is loaded +*/ +static bool load_file_chunked_passed; +static uint8_t load_chunk_buf[8192]; +static uint8_t load_file_chunked_content[500000]; +static void load_file_chunked_callback(const sfetch_response_t* response) { + if (response->fetched) { + uint8_t* dst = &load_file_chunked_content[response->data_offset]; + const uint8_t* src = response->data.ptr; + size_t num_bytes = response->data.size; + memcpy(dst, src, num_bytes); + if (response->finished) { + load_file_chunked_passed = true; + } + } +} + +UTEST(sokol_fetch, load_file_chunked) { + memset(load_file_buf, 0, sizeof(load_file_buf)); + memset(load_chunk_buf, 0, sizeof(load_chunk_buf)); + memset(load_file_chunked_content, 0, sizeof(load_file_chunked_content)); + load_file_fixed_buffer_passed = false; + sfetch_setup(&(sfetch_desc_t){0}); + // request for chunked-loading + sfetch_handle_t h0 = sfetch_send(&(sfetch_request_t){ + .path = "comsi.s3m", + .callback = load_file_chunked_callback, + .buffer = SFETCH_RANGE(load_chunk_buf), + .chunk_size = sizeof(load_chunk_buf) + }); + // request for all-in-one loading for comparing with the chunked buffer + sfetch_handle_t h1 = sfetch_send(&(sfetch_request_t){ + .path = "comsi.s3m", + .callback = load_file_fixed_buffer_callback, + .buffer = SFETCH_RANGE(load_file_buf), + }); + int frame_count = 0; + const int max_frames = 10000; + while ((sfetch_handle_valid(h0) || sfetch_handle_valid(h1)) && (frame_count++ < max_frames)) { + sfetch_dowork(); + sleep_ms(1); + } + T(frame_count < max_frames); + T(load_file_chunked_passed); + T(0 == memcmp(load_file_chunked_content, load_file_buf, combatsignal_file_size)); + sfetch_shutdown(); +} + +/* load N big files in small chunks interleaved on the same channel via lanes */ +#define LOAD_FILE_LANES_NUM_LANES (4) + +static uint8_t load_file_lanes_chunk_buf[LOAD_FILE_LANES_NUM_LANES][8192]; +static uint8_t load_file_lanes_content[LOAD_FILE_LANES_NUM_LANES][500000]; +static int load_file_lanes_passed[LOAD_FILE_LANES_NUM_LANES]; +static void load_file_lanes_callback(const sfetch_response_t* response) { + assert((response->channel == 0) && (response->lane < LOAD_FILE_LANES_NUM_LANES)); + if (response->fetched) { + uint8_t* dst = &load_file_lanes_content[response->lane][response->data_offset]; + const uint8_t* src = response->data.ptr; + size_t num_bytes = response->data.size; + memcpy(dst, src, num_bytes); + if (response->finished) { + load_file_lanes_passed[response->lane]++; + } + } +} + +UTEST(sokol_fetch, load_file_lanes) { + for (int i = 0; i < LOAD_FILE_LANES_NUM_LANES; i++) { + memset(load_file_lanes_content[i], i, sizeof(load_file_lanes_content[i])); + } + sfetch_setup(&(sfetch_desc_t){ + .num_channels = 1, + .num_lanes = LOAD_FILE_LANES_NUM_LANES, + }); + sfetch_handle_t h[LOAD_FILE_LANES_NUM_LANES]; + for (int lane = 0; lane < LOAD_FILE_LANES_NUM_LANES; lane++) { + h[lane] = sfetch_send(&(sfetch_request_t){ + .path = "comsi.s3m", + .callback = load_file_lanes_callback, + .buffer = { .ptr = load_file_lanes_chunk_buf[lane], .size = sizeof(load_file_lanes_chunk_buf[0]) }, + .chunk_size = sizeof(load_file_lanes_chunk_buf[0]) + }); + } + bool done = false; + int frame_count = 0; + const int max_frames = 10000; + while (!done && (frame_count++ < max_frames)) { + done = true; + for (int i = 0; i < LOAD_FILE_LANES_NUM_LANES; i++) { + done &= !sfetch_handle_valid(h[i]); + } + sfetch_dowork(); + sleep_ms(1); + } + T(frame_count < max_frames); + for (int i = 0; i < LOAD_FILE_LANES_NUM_LANES; i++) { + T(1 == load_file_lanes_passed[i]); + T(0 == memcmp(load_file_lanes_content[0], load_file_lanes_content[i], combatsignal_file_size)); + } + sfetch_shutdown(); +} + +/* same as above, but issue more requests than available lanes to test if rate-limiting works */ +#define LOAD_FILE_THROTTLE_NUM_LANES (4) +#define LOAD_FILE_THROTTLE_NUM_PASSES (3) +#define LOAD_FILE_THROTTLE_NUM_REQUESTS (12) // lanes * passes + +static uint8_t load_file_throttle_chunk_buf[LOAD_FILE_THROTTLE_NUM_LANES][128000]; +static uint8_t load_file_throttle_content[LOAD_FILE_THROTTLE_NUM_PASSES][LOAD_FILE_THROTTLE_NUM_LANES][500000]; +static int load_file_throttle_passed[LOAD_FILE_THROTTLE_NUM_LANES]; + +static void load_file_throttle_callback(const sfetch_response_t* response) { + assert((response->channel == 0) && (response->lane < LOAD_FILE_LANES_NUM_LANES)); + if (response->fetched) { + assert(load_file_throttle_passed[response->lane] < LOAD_FILE_THROTTLE_NUM_PASSES); + uint8_t* dst = &load_file_throttle_content[load_file_throttle_passed[response->lane]][response->lane][response->data_offset]; + const uint8_t* src = response->data.ptr; + size_t num_bytes = response->data.size; + memcpy(dst, src, num_bytes); + if (response->finished) { + load_file_throttle_passed[response->lane]++; + } + } +} + +UTEST(sokol_fetch, load_file_throttle) { + for (int pass = 0; pass < LOAD_FILE_THROTTLE_NUM_PASSES; pass++) { + for (int lane = 0; lane < LOAD_FILE_THROTTLE_NUM_LANES; lane++) { + memset(load_file_throttle_content[pass][lane], 10*pass+lane, sizeof(load_file_throttle_content[pass][lane])); + } + } + sfetch_setup(&(sfetch_desc_t){ + .num_channels = 1, + .num_lanes = LOAD_FILE_THROTTLE_NUM_LANES, + }); + sfetch_handle_t h[LOAD_FILE_THROTTLE_NUM_REQUESTS]; + for (int i = 0; i < LOAD_FILE_THROTTLE_NUM_REQUESTS; i++) { + h[i] = sfetch_send(&(sfetch_request_t){ + .path = "comsi.s3m", + .callback = load_file_throttle_callback, + .buffer = { + .ptr = load_file_throttle_chunk_buf[i % LOAD_FILE_THROTTLE_NUM_LANES], + .size = sizeof(load_file_throttle_chunk_buf[0]), + }, + .chunk_size = sizeof(load_file_throttle_chunk_buf[0]) + }); + T(sfetch_handle_valid(h[i])); + } + bool done = false; + int frame_count = 0; + const int max_frames = 10000; + while (!done && (frame_count++ < max_frames)) { + done = true; + for (int i = 0; i < LOAD_FILE_THROTTLE_NUM_REQUESTS; i++) { + done &= !sfetch_handle_valid(h[i]); + } + sfetch_dowork(); + sleep_ms(1); + } + T(frame_count < max_frames); + for (int lane = 0; lane < LOAD_FILE_THROTTLE_NUM_LANES; lane++) { + T(LOAD_FILE_THROTTLE_NUM_PASSES == load_file_throttle_passed[lane]); + for (int pass = 0; pass < LOAD_FILE_THROTTLE_NUM_PASSES; pass++) { + T(0 == memcmp(load_file_throttle_content[0][0], load_file_throttle_content[pass][lane], combatsignal_file_size)); + } + } + sfetch_shutdown(); +} + +/* test parallel fetches on multiple channels */ +#define LOAD_CHANNEL_NUM_CHANNELS (16) +static uint8_t load_channel_buf[LOAD_CHANNEL_NUM_CHANNELS][500000]; +static bool load_channel_passed[LOAD_CHANNEL_NUM_CHANNELS]; + +void load_channel_callback(const sfetch_response_t* response) { + assert(response->channel < LOAD_CHANNEL_NUM_CHANNELS); + assert(!load_channel_passed[response->channel]); + if (response->fetched) { + if ((response->data.size == combatsignal_file_size) && response->finished) { + load_channel_passed[response->channel] = true; + } + } +} + +UTEST(sokol_fetch, load_channel) { + for (int chn = 0; chn < LOAD_CHANNEL_NUM_CHANNELS; chn++) { + memset(load_channel_buf[chn], chn, sizeof(load_channel_buf[chn])); + } + sfetch_setup(&(sfetch_desc_t){ + .num_channels = LOAD_CHANNEL_NUM_CHANNELS + }); + sfetch_handle_t h[LOAD_CHANNEL_NUM_CHANNELS]; + for (uint32_t chn = 0; chn < LOAD_CHANNEL_NUM_CHANNELS; chn++) { + h[chn] = sfetch_send(&(sfetch_request_t){ + .path = "comsi.s3m", + .channel = chn, + .callback = load_channel_callback, + .buffer = SFETCH_RANGE(load_channel_buf[chn]), + }); + } + bool done = false; + int frame_count = 0; + const int max_frames = 100000; + while (!done && (frame_count++ < max_frames)) { + done = true; + for (int i = 0; i < LOAD_CHANNEL_NUM_CHANNELS; i++) { + done &= !sfetch_handle_valid(h[i]); + } + sfetch_dowork(); + sleep_ms(1); + } + T(frame_count < max_frames); + for (int chn = 0; chn < LOAD_CHANNEL_NUM_CHANNELS; chn++) { + T(load_channel_passed[chn]); + T(0 == memcmp(load_channel_buf[0], load_channel_buf[chn], combatsignal_file_size)); + } + sfetch_shutdown(); +} + +static bool load_file_cancel_passed = false; +static void load_file_cancel_callback(const sfetch_response_t* response) { + if (response->dispatched) { + sfetch_cancel(response->handle); + } + if (response->cancelled && response->finished && response->failed && (response->error_code == SFETCH_ERROR_CANCELLED)) { + load_file_cancel_passed = true; + } +} + +UTEST(sokol_fetch, load_file_cancel) { + sfetch_setup(&(sfetch_desc_t){ + .num_channels = 1 + }); + sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ + .path = "comsi.s3m", + .callback = load_file_cancel_callback, + }); + int frame_count = 0; + const int max_frames = 10000; + while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) { + sfetch_dowork(); + sleep_ms(1); + } + T(frame_count < max_frames); + T(load_file_cancel_passed); + sfetch_shutdown(); +} + +static bool load_file_cancel_before_dispatch_passed = false; +static void load_file_cancel_before_dispatch_callback(const sfetch_response_t* response) { + // cancelled, finished, failed and error code must all be set + if (response->cancelled && response->finished && response->failed && (response->error_code == SFETCH_ERROR_CANCELLED)) { + load_file_cancel_before_dispatch_passed = true; + } +} + +UTEST(sokol_fetch, load_file_cancel_before_dispatch) { + sfetch_setup(&(sfetch_desc_t){ + .num_channels = 1, + }); + sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ + .path = "comsi.s3m", + .callback = load_file_cancel_before_dispatch_callback, + }); + sfetch_cancel(h); + sfetch_dowork(); + T(load_file_cancel_before_dispatch_passed); + sfetch_shutdown(); +} + +static bool load_file_cancel_after_dispatch_passed = false; +static void load_file_cancel_after_dispatch_callback(const sfetch_response_t* response) { + // when cancelled, then finished, failed and error code must all be set + if (response->cancelled && response->finished && response->failed && (response->error_code == SFETCH_ERROR_CANCELLED)) { + load_file_cancel_after_dispatch_passed = true; + } +} + +UTEST(sokol_fetch, load_file_cancel_after_dispatch) { + sfetch_setup(&(sfetch_desc_t){ + .num_channels = 1, + }); + sfetch_handle_t h = sfetch_send(&(sfetch_request_t){ + .path = "comsi.s3m", + .callback = load_file_cancel_after_dispatch_callback, + .buffer = SFETCH_RANGE(load_file_buf), + }); + int frame_count = 0; + const int max_frames = 10000; + while (sfetch_handle_valid(h) && (frame_count++ < max_frames)) { + sfetch_dowork(); + sfetch_cancel(h); + sleep_ms(1); + } + T(frame_count < max_frames); + T(load_file_cancel_after_dispatch_passed); + sfetch_shutdown(); +} diff --git a/source/engine/thirdparty/sokol/tests/functional/sokol_gfx_test.c b/source/engine/thirdparty/sokol/tests/functional/sokol_gfx_test.c new file mode 100644 index 0000000..7535605 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/sokol_gfx_test.c @@ -0,0 +1,2975 @@ +//------------------------------------------------------------------------------ +// sokol-gfx-test.c +// NOTE: this is not only testing the public API behaviour, but also +// accesses private functions and data. It may make sense to split +// these into two separate tests. +//------------------------------------------------------------------------------ +#include "force_dummy_backend.h" +#define SOKOL_IMPL +#include "sokol_gfx.h" +#include "utest.h" + +#define T(b) EXPECT_TRUE(b) + +#define MAX_LOGITEMS (32) +static int num_log_called = 0; +static sg_log_item log_items[MAX_LOGITEMS]; + +static void test_logger(const char* tag, uint32_t log_level, uint32_t log_item_id, const char* message_or_null, uint32_t line_nr, const char* filename_or_null, void* user_data) { + (void)tag; + (void)log_level; + (void)message_or_null; + (void)line_nr; + (void)filename_or_null; + (void)user_data; + if (num_log_called < MAX_LOGITEMS) { + log_items[num_log_called++] = log_item_id; + } + if (message_or_null) { + printf("%s\n", message_or_null); + } +} + +static void reset_log_items(void) { + num_log_called = 0; + memset(log_items, 0, sizeof(log_items)); +} + +static void setup(const sg_desc* desc) { + reset_log_items(); + sg_desc desc_with_logger = *desc; + desc_with_logger.logger.func = test_logger; + sg_setup(&desc_with_logger); +} + +static sg_buffer create_buffer(void) { + static const float data[] = { 1, 2, 3, 4 }; + return sg_make_buffer(&(sg_buffer_desc){ .data = SG_RANGE(data) }); +} + +static sg_image create_image(void) { + return sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 128 + }); +} + +static sg_shader create_shader(void) { + return sg_make_shader(&(sg_shader_desc){0}); +} + +static sg_pipeline create_pipeline(void) { + return sg_make_pipeline(&(sg_pipeline_desc){ + .layout = { + .attrs[0].format = SG_VERTEXFORMAT_FLOAT3 + }, + .shader = sg_make_shader(&(sg_shader_desc){0}) + }); +} + +static sg_attachments create_attachments(void) { + sg_image_desc img_desc = { + .render_target = true, + .width = 128, + .height = 128, + }; + return sg_make_attachments(&(sg_attachments_desc){ + .colors = { + [0].image = sg_make_image(&img_desc), + [1].image = sg_make_image(&img_desc), + [2].image = sg_make_image(&img_desc) + }, + }); +} + +UTEST(sokol_gfx, init_shutdown) { + setup(&(sg_desc){0}); + T(sg_isvalid()); + sg_shutdown(); + T(!sg_isvalid()); +} + +UTEST(sokol_gfx, query_desc) { + setup(&(sg_desc){ + .buffer_pool_size = 1024, + .sampler_pool_size = 8, + .shader_pool_size = 128, + .attachments_pool_size = 64, + }); + const sg_desc desc = sg_query_desc(); + T(desc.buffer_pool_size == 1024); + T(desc.image_pool_size == _SG_DEFAULT_IMAGE_POOL_SIZE); + T(desc.sampler_pool_size == 8); + T(desc.shader_pool_size == 128); + T(desc.pipeline_pool_size == _SG_DEFAULT_PIPELINE_POOL_SIZE); + T(desc.attachments_pool_size == 64); + T(desc.uniform_buffer_size == _SG_DEFAULT_UB_SIZE); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_backend) { + setup(&(sg_desc){0}); + T(sg_query_backend() == SG_BACKEND_DUMMY); + sg_shutdown(); +} + +UTEST(sokol_gfx, pool_size) { + setup(&(sg_desc){ + .buffer_pool_size = 1024, + .image_pool_size = 2048, + .shader_pool_size = 128, + .pipeline_pool_size = 256, + .attachments_pool_size = 64, + }); + T(sg_isvalid()); + /* pool slot 0 is reserved (this is the "invalid slot") */ + T(_sg.pools.buffer_pool.size == 1025); + T(_sg.pools.image_pool.size == 2049); + T(_sg.pools.shader_pool.size == 129); + T(_sg.pools.pipeline_pool.size == 257); + T(_sg.pools.attachments_pool.size == 65); + T(_sg.pools.buffer_pool.queue_top == 1024); + T(_sg.pools.image_pool.queue_top == 2048); + T(_sg.pools.shader_pool.queue_top == 128); + T(_sg.pools.pipeline_pool.queue_top == 256); + T(_sg.pools.attachments_pool.queue_top == 64); + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_fail_destroy_buffers) { + setup(&(sg_desc){ + .buffer_pool_size = 3 + }); + T(sg_isvalid()); + + sg_buffer buf[3] = { {0} }; + for (int i = 0; i < 3; i++) { + buf[i] = sg_alloc_buffer(); + T(buf[i].id != SG_INVALID_ID); + T((2-i) == _sg.pools.buffer_pool.queue_top); + T(sg_query_buffer_state(buf[i]) == SG_RESOURCESTATE_ALLOC); + } + /* the next alloc will fail because the pool is exhausted */ + sg_buffer b3 = sg_alloc_buffer(); + T(b3.id == SG_INVALID_ID); + T(sg_query_buffer_state(b3) == SG_RESOURCESTATE_INVALID); + + /* before destroying, the resources must be either in valid or failed state */ + for (int i = 0; i < 3; i++) { + sg_fail_buffer(buf[i]); + T(sg_query_buffer_state(buf[i]) == SG_RESOURCESTATE_FAILED); + } + for (int i = 0; i < 3; i++) { + sg_destroy_buffer(buf[i]); + T(sg_query_buffer_state(buf[i]) == SG_RESOURCESTATE_INVALID); + T((i+1) == _sg.pools.buffer_pool.queue_top); + } + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_fail_destroy_images) { + setup(&(sg_desc){ + .image_pool_size = 3 + }); + T(sg_isvalid()); + + sg_image img[3] = { {0} }; + for (int i = 0; i < 3; i++) { + img[i] = sg_alloc_image(); + T(img[i].id != SG_INVALID_ID); + T((2-i) == _sg.pools.image_pool.queue_top); + T(sg_query_image_state(img[i]) == SG_RESOURCESTATE_ALLOC); + } + /* the next alloc will fail because the pool is exhausted */ + sg_image i3 = sg_alloc_image(); + T(i3.id == SG_INVALID_ID); + T(sg_query_image_state(i3) == SG_RESOURCESTATE_INVALID); + + /* before destroying, the resources must be either in valid or failed state */ + for (int i = 0; i < 3; i++) { + sg_fail_image(img[i]); + T(sg_query_image_state(img[i]) == SG_RESOURCESTATE_FAILED); + } + for (int i = 0; i < 3; i++) { + sg_destroy_image(img[i]); + T(sg_query_image_state(img[i]) == SG_RESOURCESTATE_INVALID); + T((i+1) == _sg.pools.image_pool.queue_top); + } + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_fail_destroy_samplers) { + setup(&(sg_desc){ + .sampler_pool_size = 3, + }); + + sg_sampler smp[3] = { {0} }; + for (int i = 0; i < 3; i++) { + smp[i] = sg_alloc_sampler(); + T(smp[i].id != SG_INVALID_ID); + T((2-i) == _sg.pools.sampler_pool.queue_top); + T(sg_query_sampler_state(smp[i]) == SG_RESOURCESTATE_ALLOC); + } + // the next alloc will fail because the pool is exhausted + sg_sampler s3 = sg_alloc_sampler(); + T(s3.id == SG_INVALID_ID); + T(sg_query_sampler_state(s3) == SG_RESOURCESTATE_INVALID); + + // before destroying, the resources must be either in valid or failed state + for (int i = 0; i < 3; i++) { + sg_fail_sampler(smp[i]); + T(sg_query_sampler_state(smp[i]) == SG_RESOURCESTATE_FAILED); + } + for (int i = 0; i < 3; i++) { + sg_destroy_sampler(smp[i]); + T(sg_query_sampler_state(smp[i]) == SG_RESOURCESTATE_INVALID); + T((i+1) == _sg.pools.sampler_pool.queue_top); + } + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_fail_destroy_shaders) { + setup(&(sg_desc){ + .shader_pool_size = 3 + }); + T(sg_isvalid()); + + sg_shader shd[3] = { {0} }; + for (int i = 0; i < 3; i++) { + shd[i] = sg_alloc_shader(); + T(shd[i].id != SG_INVALID_ID); + T((2-i) == _sg.pools.shader_pool.queue_top); + T(sg_query_shader_state(shd[i]) == SG_RESOURCESTATE_ALLOC); + } + /* the next alloc will fail because the pool is exhausted */ + sg_shader s3 = sg_alloc_shader(); + T(s3.id == SG_INVALID_ID); + T(sg_query_shader_state(s3) == SG_RESOURCESTATE_INVALID); + + /* before destroying, the resources must be either in valid or failed state */ + for (int i = 0; i < 3; i++) { + sg_fail_shader(shd[i]); + T(sg_query_shader_state(shd[i]) == SG_RESOURCESTATE_FAILED); + } + for (int i = 0; i < 3; i++) { + sg_destroy_shader(shd[i]); + T(sg_query_shader_state(shd[i]) == SG_RESOURCESTATE_INVALID); + T((i+1) == _sg.pools.shader_pool.queue_top); + } + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_fail_destroy_pipelines) { + setup(&(sg_desc){ + .pipeline_pool_size = 3 + }); + T(sg_isvalid()); + + sg_pipeline pip[3] = { {0} }; + for (int i = 0; i < 3; i++) { + pip[i] = sg_alloc_pipeline(); + T(pip[i].id != SG_INVALID_ID); + T((2-i) == _sg.pools.pipeline_pool.queue_top); + T(sg_query_pipeline_state(pip[i]) == SG_RESOURCESTATE_ALLOC); + } + + /* the next alloc will fail because the pool is exhausted */ + sg_pipeline p3 = sg_alloc_pipeline(); + T(p3.id == SG_INVALID_ID); + T(sg_query_pipeline_state(p3) == SG_RESOURCESTATE_INVALID); + + /* before destroying, the resources must be either in valid or failed state */ + for (int i = 0; i < 3; i++) { + sg_fail_pipeline(pip[i]); + T(sg_query_pipeline_state(pip[i]) == SG_RESOURCESTATE_FAILED); + } + for (int i = 0; i < 3; i++) { + sg_destroy_pipeline(pip[i]); + T(sg_query_pipeline_state(pip[i]) == SG_RESOURCESTATE_INVALID); + T((i+1) == _sg.pools.pipeline_pool.queue_top); + } + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_fail_destroy_attachments) { + setup(&(sg_desc){ + .attachments_pool_size = 3 + }); + T(sg_isvalid()); + + sg_attachments atts[3] = { {0} }; + for (int i = 0; i < 3; i++) { + atts[i] = sg_alloc_attachments(); + T(atts[i].id != SG_INVALID_ID); + T((2-i) == _sg.pools.attachments_pool.queue_top); + T(sg_query_attachments_state(atts[i]) == SG_RESOURCESTATE_ALLOC); + } + /* the next alloc will fail because the pool is exhausted */ + sg_attachments a3 = sg_alloc_attachments(); + T(a3.id == SG_INVALID_ID); + T(sg_query_attachments_state(a3) == SG_RESOURCESTATE_INVALID); + + /* before destroying, the resources must be either in valid or failed state */ + for (int i = 0; i < 3; i++) { + sg_fail_attachments(atts[i]); + T(sg_query_attachments_state(atts[i]) == SG_RESOURCESTATE_FAILED); + } + for (int i = 0; i < 3; i++) { + sg_destroy_attachments(atts[i]); + T(sg_query_attachments_state(atts[i]) == SG_RESOURCESTATE_INVALID); + T((i+1) == _sg.pools.attachments_pool.queue_top); + } + sg_shutdown(); +} + +UTEST(sokol_gfx, make_destroy_buffers) { + setup(&(sg_desc){ + .buffer_pool_size = 3 + }); + T(sg_isvalid()); + + float data[] = { 1.0f, 2.0f, 3.0f, 4.0f }; + + sg_buffer buf[3] = { {0} }; + sg_buffer_desc desc = { .data = SG_RANGE(data) }; + for (int i = 0; i < 3; i++) { + buf[i] = sg_make_buffer(&desc); + T(buf[i].id != SG_INVALID_ID); + T((2-i) == _sg.pools.buffer_pool.queue_top); + T(sg_query_buffer_state(buf[i]) == SG_RESOURCESTATE_VALID); + const _sg_buffer_t* bufptr = _sg_lookup_buffer(&_sg.pools, buf[i].id); + T(bufptr); + T(bufptr->slot.id == buf[i].id); + T(bufptr->slot.state == SG_RESOURCESTATE_VALID); + T(bufptr->cmn.size == sizeof(data)); + T(bufptr->cmn.append_pos == 0); + T(!bufptr->cmn.append_overflow); + T(bufptr->cmn.type == SG_BUFFERTYPE_VERTEXBUFFER); + T(bufptr->cmn.usage == SG_USAGE_IMMUTABLE); + T(bufptr->cmn.update_frame_index == 0); + T(bufptr->cmn.append_frame_index == 0); + T(bufptr->cmn.num_slots == 1); + T(bufptr->cmn.active_slot == 0); + } + /* trying to create another one fails because pool is exhausted */ + T(sg_make_buffer(&desc).id == SG_INVALID_ID); + + for (int i = 0; i < 3; i++) { + sg_destroy_buffer(buf[i]); + T(sg_query_buffer_state(buf[i]) == SG_RESOURCESTATE_INVALID); + T((i+1) == _sg.pools.buffer_pool.queue_top); + } + sg_shutdown(); +} + +UTEST(sokol_gfx, make_destroy_images) { + setup(&(sg_desc){ + .image_pool_size = 3 + }); + T(sg_isvalid()); + + uint32_t data[8*8] = { 0 }; + + sg_image img[3] = { {0} }; + sg_image_desc desc = { + .width = 8, + .height = 8, + .data.subimage[0][0] = SG_RANGE(data) + }; + for (int i = 0; i < 3; i++) { + img[i] = sg_make_image(&desc); + T(img[i].id != SG_INVALID_ID); + T((2-i) == _sg.pools.image_pool.queue_top); + T(sg_query_image_state(img[i]) == SG_RESOURCESTATE_VALID); + const _sg_image_t* imgptr = _sg_lookup_image(&_sg.pools, img[i].id); + T(imgptr); + T(imgptr->slot.id == img[i].id); + T(imgptr->slot.state == SG_RESOURCESTATE_VALID); + T(imgptr->cmn.type == SG_IMAGETYPE_2D); + T(!imgptr->cmn.render_target); + T(imgptr->cmn.width == 8); + T(imgptr->cmn.height == 8); + T(imgptr->cmn.num_slices == 1); + T(imgptr->cmn.num_mipmaps == 1); + T(imgptr->cmn.usage == SG_USAGE_IMMUTABLE); + T(imgptr->cmn.pixel_format == SG_PIXELFORMAT_RGBA8); + T(imgptr->cmn.sample_count == 1); + T(imgptr->cmn.upd_frame_index == 0); + T(imgptr->cmn.num_slots == 1); + T(imgptr->cmn.active_slot == 0); + } + // trying to create another one fails because pool is exhausted + T(sg_make_image(&desc).id == SG_INVALID_ID); + + for (int i = 0; i < 3; i++) { + sg_destroy_image(img[i]); + T(sg_query_image_state(img[i]) == SG_RESOURCESTATE_INVALID); + T((i+1) == _sg.pools.image_pool.queue_top); + } + sg_shutdown(); +} + +UTEST(sokol_gfx, make_destroy_samplers) { + setup(&(sg_desc){ + .sampler_pool_size = 3 + }); + T(sg_isvalid()); + + sg_sampler smp[3] = { {0} }; + sg_sampler_desc desc = { 0 }; + for (int i = 0; i < 3; i++) { + smp[i] = sg_make_sampler(&desc); + T(smp[i].id != SG_INVALID_ID); + T((2-i) == _sg.pools.sampler_pool.queue_top); + T(sg_query_sampler_state(smp[i]) == SG_RESOURCESTATE_VALID); + const _sg_sampler_t* smpptr = _sg_lookup_sampler(&_sg.pools, smp[i].id); + T(smpptr); + T(smpptr->slot.id == smp[i].id); + T(smpptr->slot.state == SG_RESOURCESTATE_VALID); + T(smpptr->cmn.min_filter == SG_FILTER_NEAREST); + T(smpptr->cmn.mag_filter == SG_FILTER_NEAREST); + T(smpptr->cmn.mipmap_filter == SG_FILTER_NONE); + T(smpptr->cmn.wrap_u == SG_WRAP_REPEAT); + T(smpptr->cmn.wrap_v == SG_WRAP_REPEAT); + T(smpptr->cmn.wrap_w == SG_WRAP_REPEAT); + T(smpptr->cmn.min_lod == 0.0f); + T(smpptr->cmn.max_lod == FLT_MAX); + T(smpptr->cmn.border_color == SG_BORDERCOLOR_OPAQUE_BLACK); + T(smpptr->cmn.compare == SG_COMPAREFUNC_NEVER); + T(smpptr->cmn.max_anisotropy == 1); + } + // trying to create another one fails because pool is exhausted + T(sg_make_sampler(&desc).id == SG_INVALID_ID); + + for (int i = 0; i < 3; i++) { + sg_destroy_sampler(smp[i]); + T(sg_query_sampler_state(smp[i]) == SG_RESOURCESTATE_INVALID); + T((i+1) == _sg.pools.sampler_pool.queue_top); + } + sg_shutdown(); +} + +UTEST(sokol_gfx, make_destroy_shaders) { + setup(&(sg_desc){ + .shader_pool_size = 3 + }); + T(sg_isvalid()); + + sg_shader shd[3] = { {0} }; + sg_shader_desc desc = { + .vs.uniform_blocks[0] = { + .size = 16 + } + }; + for (int i = 0; i < 3; i++) { + shd[i] = sg_make_shader(&desc); + T(shd[i].id != SG_INVALID_ID); + T((2-i) == _sg.pools.shader_pool.queue_top); + T(sg_query_shader_state(shd[i]) == SG_RESOURCESTATE_VALID); + const _sg_shader_t* shdptr = _sg_lookup_shader(&_sg.pools, shd[i].id); + T(shdptr); + T(shdptr->slot.id == shd[i].id); + T(shdptr->slot.state == SG_RESOURCESTATE_VALID); + T(shdptr->cmn.stage[SG_SHADERSTAGE_VS].num_uniform_blocks == 1); + T(shdptr->cmn.stage[SG_SHADERSTAGE_VS].num_images == 0); + T(shdptr->cmn.stage[SG_SHADERSTAGE_VS].uniform_blocks[0].size == 16); + T(shdptr->cmn.stage[SG_SHADERSTAGE_FS].num_uniform_blocks == 0); + T(shdptr->cmn.stage[SG_SHADERSTAGE_FS].num_images == 0); + } + /* trying to create another one fails because pool is exhausted */ + T(sg_make_shader(&desc).id == SG_INVALID_ID); + + for (int i = 0; i < 3; i++) { + sg_destroy_shader(shd[i]); + T(sg_query_shader_state(shd[i]) == SG_RESOURCESTATE_INVALID); + T((i+1) == _sg.pools.shader_pool.queue_top); + } + sg_shutdown(); +} + +UTEST(sokol_gfx, make_destroy_pipelines) { + setup(&(sg_desc){ + .pipeline_pool_size = 3 + }); + T(sg_isvalid()); + + sg_pipeline pip[3] = { {0} }; + sg_pipeline_desc desc = { + .shader = sg_make_shader(&(sg_shader_desc){ 0 }), + .layout = { + .attrs = { + [0] = { .format=SG_VERTEXFORMAT_FLOAT3 }, + [1] = { .format=SG_VERTEXFORMAT_FLOAT4 } + } + }, + }; + for (int i = 0; i < 3; i++) { + pip[i] = sg_make_pipeline(&desc); + T(pip[i].id != SG_INVALID_ID); + T((2-i) == _sg.pools.pipeline_pool.queue_top); + T(sg_query_pipeline_state(pip[i]) == SG_RESOURCESTATE_VALID); + const _sg_pipeline_t* pipptr = _sg_lookup_pipeline(&_sg.pools, pip[i].id); + T(pipptr); + T(pipptr->slot.id == pip[i].id); + T(pipptr->slot.state == SG_RESOURCESTATE_VALID); + T(pipptr->shader == _sg_lookup_shader(&_sg.pools, desc.shader.id)); + T(pipptr->cmn.shader_id.id == desc.shader.id); + T(pipptr->cmn.color_count == 1); + T(pipptr->cmn.colors[0].pixel_format == SG_PIXELFORMAT_RGBA8); + T(pipptr->cmn.depth.pixel_format == SG_PIXELFORMAT_DEPTH_STENCIL); + T(pipptr->cmn.sample_count == 1); + T(pipptr->cmn.index_type == SG_INDEXTYPE_NONE); + T(pipptr->cmn.vertex_buffer_layout_active[0]); + T(!pipptr->cmn.vertex_buffer_layout_active[1]); + } + /* trying to create another one fails because pool is exhausted */ + T(sg_make_pipeline(&desc).id == SG_INVALID_ID); + + for (int i = 0; i < 3; i++) { + sg_destroy_pipeline(pip[i]); + T(sg_query_pipeline_state(pip[i]) == SG_RESOURCESTATE_INVALID); + T((i+1) == _sg.pools.pipeline_pool.queue_top); + } + sg_shutdown(); +} + +UTEST(sokol_gfx, make_destroy_attachments) { + setup(&(sg_desc){ + .attachments_pool_size = 3 + }); + T(sg_isvalid()); + + sg_attachments atts[3] = { {0} }; + + sg_image_desc img_desc = { + .render_target = true, + .width = 128, + .height = 128, + }; + sg_attachments_desc atts_desc = (sg_attachments_desc){ + .colors = { + [0].image = sg_make_image(&img_desc), + [1].image = sg_make_image(&img_desc), + [2].image = sg_make_image(&img_desc) + }, + }; + for (int i = 0; i < 3; i++) { + atts[i] = sg_make_attachments(&atts_desc); + T(atts[i].id != SG_INVALID_ID); + T((2-i) == _sg.pools.attachments_pool.queue_top); + T(sg_query_attachments_state(atts[i]) == SG_RESOURCESTATE_VALID); + const _sg_attachments_t* attsptr = _sg_lookup_attachments(&_sg.pools, atts[i].id); + T(attsptr); + T(attsptr->slot.id == atts[i].id); + T(attsptr->slot.state == SG_RESOURCESTATE_VALID); + T(attsptr->cmn.num_colors == 3); + for (int ai = 0; ai < 3; ai++) { + const _sg_image_t* img = _sg_attachments_color_image(attsptr, ai); + T(img == _sg_lookup_image(&_sg.pools, atts_desc.colors[ai].image.id)); + T(attsptr->cmn.colors[ai].image_id.id == atts_desc.colors[ai].image.id); + } + } + /* trying to create another one fails because pool is exhausted */ + T(sg_make_attachments(&atts_desc).id == SG_INVALID_ID); + + for (int i = 0; i < 3; i++) { + sg_destroy_attachments(atts[i]); + T(sg_query_attachments_state(atts[i]) == SG_RESOURCESTATE_INVALID); + T((i+1) == _sg.pools.attachments_pool.queue_top); + } + sg_shutdown(); +} + +UTEST(sokol_gfx, generation_counter) { + setup(&(sg_desc){ + .buffer_pool_size = 1, + }); + + static float data[] = { 1.0f, 2.0f, 3.0f, 4.0f }; + for (int i = 0; i < 64; i++) { + sg_buffer buf = sg_make_buffer(&(sg_buffer_desc){ .data = SG_RANGE(data) }); + T(buf.id != SG_INVALID_ID); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_VALID); + T((buf.id >> 16) == (uint32_t)(i + 1)); /* this is the generation counter */ + T(_sg_slot_index(buf.id) == 1); /* slot index should remain the same */ + sg_destroy_buffer(buf); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_INVALID); + } + sg_shutdown(); +} + +UTEST(sokol_gfx, query_buffer_defaults) { + setup(&(sg_desc){0}); + sg_buffer_desc desc; + desc = sg_query_buffer_defaults(&(sg_buffer_desc){0}); + T(desc.type == SG_BUFFERTYPE_VERTEXBUFFER); + T(desc.usage == SG_USAGE_IMMUTABLE); + desc = sg_query_buffer_defaults(&(sg_buffer_desc){ + .type = SG_BUFFERTYPE_INDEXBUFFER, + }); + T(desc.type == SG_BUFFERTYPE_INDEXBUFFER); + T(desc.usage == SG_USAGE_IMMUTABLE); + desc = sg_query_buffer_defaults(&(sg_buffer_desc){ + .usage = SG_USAGE_DYNAMIC + }); + T(desc.type == SG_BUFFERTYPE_VERTEXBUFFER); + T(desc.usage == SG_USAGE_DYNAMIC); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_image_defaults) { + setup(&(sg_desc){0}); + const sg_image_desc desc = sg_query_image_defaults(&(sg_image_desc){0}); + T(desc.type == SG_IMAGETYPE_2D); + T(!desc.render_target); + T(desc.num_mipmaps == 1); + T(desc.usage == SG_USAGE_IMMUTABLE); + T(desc.pixel_format == SG_PIXELFORMAT_RGBA8); + T(desc.sample_count == 1); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_sampler_defaults) { + setup(&(sg_desc){0}); + const sg_sampler_desc desc = sg_query_sampler_defaults(&(sg_sampler_desc){0}); + T(desc.min_filter == SG_FILTER_NEAREST); + T(desc.mag_filter == SG_FILTER_NEAREST); + T(desc.mipmap_filter == SG_FILTER_NONE); + T(desc.wrap_u == SG_WRAP_REPEAT); + T(desc.wrap_v == SG_WRAP_REPEAT); + T(desc.wrap_w == SG_WRAP_REPEAT); + T(desc.min_lod == 0.0f); + T(desc.max_lod == FLT_MAX); + T(desc.border_color == SG_BORDERCOLOR_OPAQUE_BLACK); + T(desc.compare == SG_COMPAREFUNC_NEVER); + T(desc.max_anisotropy == 1); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_shader_defaults) { + setup(&(sg_desc){0}); + const sg_shader_desc desc = sg_query_shader_defaults(&(sg_shader_desc){0}); + T(0 == strcmp(desc.vs.entry, "main")); + T(0 == strcmp(desc.fs.entry, "main")); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_pipeline_defaults) { + setup(&(sg_desc){0}); + const sg_pipeline_desc desc = sg_query_pipeline_defaults(&(sg_pipeline_desc){ + .layout.attrs = { + [0].format = SG_VERTEXFORMAT_FLOAT3, + [1].format = SG_VERTEXFORMAT_FLOAT4 + } + }); + T(desc.layout.buffers[0].stride == 28); + T(desc.layout.buffers[0].step_rate == 1); + T(desc.layout.buffers[0].step_func == SG_VERTEXSTEP_PER_VERTEX); + T(desc.layout.attrs[0].offset == 0); + T(desc.layout.attrs[0].buffer_index == 0); + T(desc.layout.attrs[0].format == SG_VERTEXFORMAT_FLOAT3); + T(desc.layout.attrs[1].offset == 12); + T(desc.layout.attrs[1].buffer_index == 0); + T(desc.layout.attrs[1].format == SG_VERTEXFORMAT_FLOAT4); + T(desc.stencil.front.fail_op == SG_STENCILOP_KEEP); + T(desc.stencil.front.depth_fail_op == SG_STENCILOP_KEEP); + T(desc.stencil.front.pass_op == SG_STENCILOP_KEEP); + T(desc.stencil.front.compare == SG_COMPAREFUNC_ALWAYS); + T(desc.stencil.back.fail_op == SG_STENCILOP_KEEP); + T(desc.stencil.back.depth_fail_op == SG_STENCILOP_KEEP); + T(desc.stencil.back.pass_op == SG_STENCILOP_KEEP); + T(desc.stencil.back.compare == SG_COMPAREFUNC_ALWAYS); + T(desc.stencil.enabled == false); + T(desc.stencil.read_mask == 0); + T(desc.stencil.write_mask == 0); + T(desc.stencil.ref == 0); + T(desc.depth.pixel_format == SG_PIXELFORMAT_DEPTH_STENCIL); + T(desc.depth.compare == SG_COMPAREFUNC_ALWAYS); + T(desc.depth.write_enabled == false); + T(desc.depth.bias == 0); + T(desc.depth.bias_slope_scale == 0); + T(desc.depth.bias_clamp == 0); + T(desc.color_count == 1); + T(desc.colors[0].pixel_format == SG_PIXELFORMAT_RGBA8); + T(desc.colors[0].write_mask == 0xF); + T(desc.colors[0].blend.enabled == false); + T(desc.colors[0].blend.src_factor_rgb == SG_BLENDFACTOR_ONE); + T(desc.colors[0].blend.dst_factor_rgb == SG_BLENDFACTOR_ZERO); + T(desc.colors[0].blend.op_rgb == SG_BLENDOP_ADD); + T(desc.colors[0].blend.src_factor_alpha == SG_BLENDFACTOR_ONE); + T(desc.colors[0].blend.dst_factor_alpha == SG_BLENDFACTOR_ZERO); + T(desc.colors[0].blend.op_alpha == SG_BLENDOP_ADD); + T(desc.alpha_to_coverage_enabled == false); + T(desc.primitive_type == SG_PRIMITIVETYPE_TRIANGLES); + T(desc.index_type == SG_INDEXTYPE_NONE); + T(desc.cull_mode == SG_CULLMODE_NONE); + T(desc.face_winding == SG_FACEWINDING_CW); + T(desc.sample_count == 1); + sg_shutdown(); +} + +// test that color attachment defaults are set in all attachments +UTEST(sokol_gfx, query_mrt_pipeline_defaults) { + setup(&(sg_desc){0}); + const sg_pipeline_desc desc = sg_query_pipeline_defaults(&(sg_pipeline_desc){ + .color_count = 3, + }); + T(desc.color_count == 3); + for (int i = 0; i < desc.color_count; i++) { + T(desc.colors[i].pixel_format == SG_PIXELFORMAT_RGBA8); + T(desc.colors[i].write_mask == 0xF); + T(desc.colors[i].blend.enabled == false); + T(desc.colors[i].blend.src_factor_rgb == SG_BLENDFACTOR_ONE); + T(desc.colors[i].blend.dst_factor_rgb == SG_BLENDFACTOR_ZERO); + T(desc.colors[i].blend.op_rgb == SG_BLENDOP_ADD); + T(desc.colors[i].blend.src_factor_alpha == SG_BLENDFACTOR_ONE); + T(desc.colors[i].blend.dst_factor_alpha == SG_BLENDFACTOR_ZERO); + T(desc.colors[i].blend.op_alpha == SG_BLENDOP_ADD); + }; + sg_shutdown(); +} + +// test that first color attachment values are duplicated to other attachments +UTEST(sokol_gfx, multiple_color_state) { + setup(&(sg_desc){0}); + const sg_pipeline_desc desc = sg_query_pipeline_defaults(&(sg_pipeline_desc){ + .color_count = 3, + .colors = { + [0] = { + .pixel_format = SG_PIXELFORMAT_R8, + .write_mask = SG_COLORMASK_BA, + .blend = { + .enabled = true, + .src_factor_rgb = SG_BLENDFACTOR_SRC_COLOR, + .dst_factor_rgb = SG_BLENDFACTOR_DST_COLOR, + .op_rgb = SG_BLENDOP_SUBTRACT, + .src_factor_alpha = SG_BLENDFACTOR_SRC_ALPHA, + .dst_factor_alpha = SG_BLENDFACTOR_DST_ALPHA, + .op_alpha = SG_BLENDOP_REVERSE_SUBTRACT + } + }, + [2] = { + .pixel_format = SG_PIXELFORMAT_RG8, + .write_mask = SG_COLORMASK_GA, + .blend = { + .enabled = true, + .src_factor_rgb = SG_BLENDFACTOR_DST_COLOR, + .dst_factor_rgb = SG_BLENDFACTOR_SRC_COLOR, + .op_rgb = SG_BLENDOP_REVERSE_SUBTRACT, + .src_factor_alpha = SG_BLENDFACTOR_DST_ALPHA, + .dst_factor_alpha = SG_BLENDFACTOR_SRC_ALPHA, + .op_alpha = SG_BLENDOP_SUBTRACT + } + }, + } + }); + T(desc.color_count == 3); + + T(desc.colors[0].pixel_format == SG_PIXELFORMAT_R8); + T(desc.colors[0].write_mask == SG_COLORMASK_BA); + T(desc.colors[0].blend.enabled == true); + T(desc.colors[0].blend.src_factor_rgb == SG_BLENDFACTOR_SRC_COLOR); + T(desc.colors[0].blend.dst_factor_rgb == SG_BLENDFACTOR_DST_COLOR); + T(desc.colors[0].blend.op_rgb == SG_BLENDOP_SUBTRACT); + T(desc.colors[0].blend.src_factor_alpha == SG_BLENDFACTOR_SRC_ALPHA); + T(desc.colors[0].blend.dst_factor_alpha == SG_BLENDFACTOR_DST_ALPHA); + T(desc.colors[0].blend.op_alpha == SG_BLENDOP_REVERSE_SUBTRACT); + + T(desc.colors[1].pixel_format == SG_PIXELFORMAT_RGBA8); + T(desc.colors[1].write_mask == SG_COLORMASK_RGBA); + T(desc.colors[1].blend.enabled == false); + T(desc.colors[1].blend.src_factor_rgb == SG_BLENDFACTOR_ONE); + T(desc.colors[1].blend.dst_factor_rgb == SG_BLENDFACTOR_ZERO); + T(desc.colors[1].blend.op_rgb == SG_BLENDOP_ADD); + T(desc.colors[1].blend.src_factor_alpha == SG_BLENDFACTOR_ONE); + T(desc.colors[1].blend.dst_factor_alpha == SG_BLENDFACTOR_ZERO); + T(desc.colors[1].blend.op_alpha == SG_BLENDOP_ADD); + + T(desc.colors[2].pixel_format == SG_PIXELFORMAT_RG8); + T(desc.colors[2].write_mask == SG_COLORMASK_GA); + T(desc.colors[2].blend.enabled == true); + T(desc.colors[2].blend.src_factor_rgb == SG_BLENDFACTOR_DST_COLOR); + T(desc.colors[2].blend.dst_factor_rgb == SG_BLENDFACTOR_SRC_COLOR); + T(desc.colors[2].blend.op_rgb == SG_BLENDOP_REVERSE_SUBTRACT); + T(desc.colors[2].blend.src_factor_alpha == SG_BLENDFACTOR_DST_ALPHA); + T(desc.colors[2].blend.dst_factor_alpha == SG_BLENDFACTOR_SRC_ALPHA); + T(desc.colors[2].blend.op_alpha == SG_BLENDOP_SUBTRACT); + + sg_shutdown(); +} + +UTEST(sokol_gfx, query_attachments_defaults) { + setup(&(sg_desc){0}); + /* sg_attachments_desc doesn't actually have any meaningful default values */ + const sg_attachments_desc desc = sg_query_attachments_defaults(&(sg_attachments_desc){0}); + T(desc.colors[0].image.id == SG_INVALID_ID); + T(desc.colors[0].mip_level == 0); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_buffer_info) { + setup(&(sg_desc){0}); + sg_buffer buf = sg_make_buffer(&(sg_buffer_desc){ + .size = 256, + .type = SG_BUFFERTYPE_VERTEXBUFFER, + .usage = SG_USAGE_STREAM + }); + T(buf.id != SG_INVALID_ID); + const sg_buffer_info info = sg_query_buffer_info(buf); + T(info.slot.state == SG_RESOURCESTATE_VALID); + T(info.slot.res_id == buf.id); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_image_info) { + setup(&(sg_desc){0}); + sg_image img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 256, + .height = 128 + }); + T(img.id != SG_INVALID_ID); + const sg_image_info info = sg_query_image_info(img); + T(info.slot.state == SG_RESOURCESTATE_VALID); + T(info.slot.res_id == img.id); + T(info.num_slots == 1); + sg_shutdown(); +} + +UTEST(sokoL_gfx, query_sampler_info) { + setup(&(sg_desc){0}); + sg_sampler smp = sg_make_sampler(&(sg_sampler_desc){ 0 }); + T(smp.id != SG_INVALID_ID); + const sg_sampler_info info = sg_query_sampler_info(smp); + T(info.slot.state == SG_RESOURCESTATE_VALID); + T(info.slot.res_id == smp.id); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_shader_info) { + setup(&(sg_desc){0}); + sg_shader shd = sg_make_shader(&(sg_shader_desc){ + .attrs = { + [0] = { .name = "pos" } + }, + .vs.source = "bla", + .fs.source = "blub" + }); + const sg_shader_info info = sg_query_shader_info(shd); + T(info.slot.state == SG_RESOURCESTATE_VALID); + T(info.slot.res_id == shd.id); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_pipeline_info) { + setup(&(sg_desc){0}); + sg_pipeline pip = sg_make_pipeline(&(sg_pipeline_desc){ + .layout = { + .attrs[0].format = SG_VERTEXFORMAT_FLOAT3 + }, + .shader = sg_make_shader(&(sg_shader_desc){ + .attrs = { + [0] = { .name = "pos" } + }, + .vs.source = "bla", + .fs.source = "blub" + }) + }); + const sg_pipeline_info info = sg_query_pipeline_info(pip); + T(info.slot.state == SG_RESOURCESTATE_VALID); + T(info.slot.res_id == pip.id); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_attachments_info) { + setup(&(sg_desc){0}); + sg_image_desc img_desc = { + .render_target = true, + .width = 128, + .height = 128, + }; + sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors = { + [0].image = sg_make_image(&img_desc), + [1].image = sg_make_image(&img_desc), + [2].image = sg_make_image(&img_desc) + }, + }); + const sg_attachments_info info = sg_query_attachments_info(atts); + T(info.slot.state == SG_RESOURCESTATE_VALID); + T(info.slot.res_id == atts.id); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_buffer_desc) { + setup(&(sg_desc){0}); + + sg_buffer b0 = sg_make_buffer(&(sg_buffer_desc){ + .size = 32, + .usage = SG_USAGE_STREAM, + .label = "bla", + }); + const sg_buffer_desc b0_desc = sg_query_buffer_desc(b0); + T(b0_desc.size == 32); + T(b0_desc.type == SG_BUFFERTYPE_VERTEXBUFFER); + T(b0_desc.usage == SG_USAGE_STREAM); + T(b0_desc.data.ptr == 0); + T(b0_desc.data.size == 0); + T(b0_desc.gl_buffers[0] == 0); + T(b0_desc.mtl_buffers[0] == 0); + T(b0_desc.d3d11_buffer == 0); + T(b0_desc.wgpu_buffer == 0); + + float vtx_data[16]; + sg_buffer b1 = sg_make_buffer(&(sg_buffer_desc){ + .data = SG_RANGE(vtx_data) + }); + const sg_buffer_desc b1_desc = sg_query_buffer_desc(b1); + T(b1_desc.size == sizeof(vtx_data)); + T(b1_desc.type == SG_BUFFERTYPE_VERTEXBUFFER); + T(b1_desc.usage == SG_USAGE_IMMUTABLE); + T(b1_desc.data.ptr == 0); + T(b1_desc.data.size == 0); + + uint16_t idx_data[8]; + sg_buffer b2 = sg_make_buffer(&(sg_buffer_desc){ + .type = SG_BUFFERTYPE_INDEXBUFFER, + .data = SG_RANGE(idx_data), + }); + const sg_buffer_desc b2_desc = sg_query_buffer_desc(b2); + T(b2_desc.size == sizeof(idx_data)); + T(b2_desc.type == SG_BUFFERTYPE_INDEXBUFFER); + T(b2_desc.usage == SG_USAGE_IMMUTABLE); + T(b2_desc.data.ptr == 0); + T(b2_desc.data.size == 0); + + // invalid buffer (returns zeroed desc) + sg_buffer b3 = sg_make_buffer(&(sg_buffer_desc){ + .size = 32, + .usage = SG_USAGE_STREAM, + .label = "bla", + }); + sg_destroy_buffer(b3); + const sg_buffer_desc b3_desc = sg_query_buffer_desc(b3); + T(b3_desc.size == 0); + T(b3_desc.type == 0); + T(b3_desc.usage == 0); + + sg_shutdown(); +} + +UTEST(sokol_gfx, query_image_desc) { + setup(&(sg_desc){0}); + + sg_image i0 = sg_make_image(&(sg_image_desc){ + .width = 256, + .height = 512, + .pixel_format = SG_PIXELFORMAT_R8, + .usage = SG_USAGE_DYNAMIC, + }); + const sg_image_desc i0_desc = sg_query_image_desc(i0); + T(i0_desc.type == SG_IMAGETYPE_2D); + T(i0_desc.render_target == false); + T(i0_desc.width == 256); + T(i0_desc.height == 512); + T(i0_desc.num_slices == 1); + T(i0_desc.num_mipmaps == 1); + T(i0_desc.usage == SG_USAGE_DYNAMIC); + T(i0_desc.pixel_format == SG_PIXELFORMAT_R8); + T(i0_desc.sample_count == 1); + T(i0_desc.data.subimage[0][0].ptr == 0); + T(i0_desc.data.subimage[0][0].size == 0); + T(i0_desc.gl_textures[0] == 0); + T(i0_desc.gl_texture_target == 0); + T(i0_desc.mtl_textures[0] == 0); + T(i0_desc.d3d11_texture == 0); + T(i0_desc.d3d11_shader_resource_view == 0); + T(i0_desc.wgpu_texture == 0); + + sg_destroy_image(i0); + const sg_image_desc i0_desc_x = sg_query_image_desc(i0); + T(i0_desc_x.type == 0); + T(i0_desc_x.render_target == false); + T(i0_desc_x.width == 0); + T(i0_desc_x.height == 0); + T(i0_desc_x.num_slices == 0); + T(i0_desc_x.num_mipmaps == 0); + T(i0_desc_x.usage == 0); + T(i0_desc_x.pixel_format == 0); + T(i0_desc_x.sample_count == 0); + + sg_shutdown(); +} + +UTEST(sokol_gfx, query_sampler_desc) { + setup(&(sg_desc){0}); + sg_sampler s0 = sg_make_sampler(&(sg_sampler_desc){ + .min_filter = SG_FILTER_LINEAR, + .mag_filter = SG_FILTER_LINEAR, + .mipmap_filter = SG_FILTER_LINEAR, + .wrap_v = SG_WRAP_MIRRORED_REPEAT, + .max_anisotropy = 8, + .border_color = SG_BORDERCOLOR_TRANSPARENT_BLACK, + .compare = SG_COMPAREFUNC_GREATER, + }); + const sg_sampler_desc s0_desc = sg_query_sampler_desc(s0); + T(s0_desc.min_filter == SG_FILTER_LINEAR); + T(s0_desc.mag_filter == SG_FILTER_LINEAR); + T(s0_desc.mipmap_filter == SG_FILTER_LINEAR); + T(s0_desc.wrap_u == SG_WRAP_REPEAT); + T(s0_desc.wrap_v == SG_WRAP_MIRRORED_REPEAT); + T(s0_desc.wrap_w == SG_WRAP_REPEAT); + T(s0_desc.min_lod == 0.0f); + T(s0_desc.max_lod == FLT_MAX); + T(s0_desc.border_color == SG_BORDERCOLOR_TRANSPARENT_BLACK); + T(s0_desc.compare == SG_COMPAREFUNC_GREATER); + T(s0_desc.max_anisotropy == 8); + + sg_destroy_sampler(s0); + const sg_sampler_desc s0_desc_x = sg_query_sampler_desc(s0); + T(s0_desc_x.min_filter == 0); + T(s0_desc_x.compare == 0); + + sg_shutdown(); +} + +UTEST(sokol_gfx, query_shader_desc) { + setup(&(sg_desc){0}); + + sg_shader s0 = sg_make_shader(&(sg_shader_desc){ + .attrs = { + [0] = { .name = "pos", .sem_name = "POS", .sem_index = 1 }, + }, + .vs = { + .source = "vs_source", + .uniform_blocks = { + [0] = { + .size = 128, + .layout = SG_UNIFORMLAYOUT_STD140, + .uniforms = { + [0] = { .name = "blub", .type = SG_UNIFORMTYPE_FLOAT4, .array_count = 1 }, + [1] = { .name = "blob", .type = SG_UNIFORMTYPE_FLOAT2, .array_count = 1 }, + } + } + }, + .images[0] = { .used = true, .image_type = SG_IMAGETYPE_2D, .sample_type = SG_IMAGESAMPLETYPE_FLOAT, .multisampled = true }, + .images[1] = { .used = true, .image_type = SG_IMAGETYPE_3D, .sample_type = SG_IMAGESAMPLETYPE_DEPTH }, + .samplers[0] = { .used = true, .sampler_type = SG_SAMPLERTYPE_FILTERING }, + .samplers[1] = { .used = true, .sampler_type = SG_SAMPLERTYPE_COMPARISON }, + .image_sampler_pairs[0] = { .used = true, .image_slot = 0, .sampler_slot = 0, .glsl_name = "img0" }, + .image_sampler_pairs[1] = { .used = true, .image_slot = 1, .sampler_slot = 1, .glsl_name = "img1" }, + }, + .fs = { + .source = "fs_source", + .images[0] = { .used = true, .image_type = SG_IMAGETYPE_ARRAY, .sample_type = SG_IMAGESAMPLETYPE_DEPTH }, + .images[1] = { .used = true, .image_type = SG_IMAGETYPE_CUBE, .sample_type = SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT }, + .samplers[0] = { .used = true, .sampler_type = SG_SAMPLERTYPE_COMPARISON }, + .samplers[1] = { .used = true, .sampler_type = SG_SAMPLERTYPE_NONFILTERING }, + .image_sampler_pairs[0] = { .used = true, .image_slot = 0, .sampler_slot = 0, .glsl_name = "img3" }, + .image_sampler_pairs[1] = { .used = true, .image_slot = 1, .sampler_slot = 1, .glsl_name = "img4" }, + }, + .label = "label", + }); + const sg_shader_desc s0_desc = sg_query_shader_desc(s0); + T(s0_desc.attrs[0].name == 0); + T(s0_desc.attrs[0].sem_name == 0); + T(s0_desc.attrs[0].sem_index == 0); + T(s0_desc.vs.source == 0); + T(s0_desc.vs.uniform_blocks[0].size == 128); + T(s0_desc.vs.uniform_blocks[0].layout == 0); + T(s0_desc.vs.uniform_blocks[0].uniforms[0].name == 0); + T(s0_desc.vs.uniform_blocks[0].uniforms[0].type == 0); + T(s0_desc.vs.uniform_blocks[0].uniforms[0].array_count == 0); + T(s0_desc.vs.images[0].used); + T(s0_desc.vs.images[0].image_type == SG_IMAGETYPE_2D); + T(s0_desc.vs.images[0].sample_type == SG_IMAGESAMPLETYPE_FLOAT); + T(s0_desc.vs.images[0].multisampled); + T(s0_desc.vs.images[1].used); + T(s0_desc.vs.images[1].image_type == SG_IMAGETYPE_3D); + T(s0_desc.vs.images[1].sample_type == SG_IMAGESAMPLETYPE_DEPTH); + T(s0_desc.vs.images[1].multisampled == false); + T(s0_desc.vs.samplers[0].used); + T(s0_desc.vs.samplers[0].sampler_type == SG_SAMPLERTYPE_FILTERING); + T(s0_desc.vs.samplers[1].used); + T(s0_desc.vs.samplers[1].sampler_type == SG_SAMPLERTYPE_COMPARISON); + T(s0_desc.vs.image_sampler_pairs[0].used); + T(s0_desc.vs.image_sampler_pairs[0].image_slot == 0); + T(s0_desc.vs.image_sampler_pairs[0].sampler_slot == 0); + T(s0_desc.vs.image_sampler_pairs[0].glsl_name == 0); + T(s0_desc.vs.image_sampler_pairs[1].used); + T(s0_desc.vs.image_sampler_pairs[1].image_slot == 1); + T(s0_desc.vs.image_sampler_pairs[1].sampler_slot == 1); + T(s0_desc.vs.image_sampler_pairs[1].glsl_name == 0); + T(s0_desc.fs.source == 0); + T(s0_desc.fs.uniform_blocks[0].size == 0); + T(s0_desc.fs.uniform_blocks[0].layout == 0); + T(s0_desc.fs.uniform_blocks[0].uniforms[0].name == 0); + T(s0_desc.fs.uniform_blocks[0].uniforms[0].type == 0); + T(s0_desc.fs.uniform_blocks[0].uniforms[0].array_count == 0); + T(s0_desc.fs.images[0].used); + T(s0_desc.fs.images[0].image_type == SG_IMAGETYPE_ARRAY); + T(s0_desc.fs.images[0].sample_type == SG_IMAGESAMPLETYPE_DEPTH); + T(s0_desc.fs.images[0].multisampled == false); + T(s0_desc.fs.images[1].used); + T(s0_desc.fs.images[1].image_type == SG_IMAGETYPE_CUBE); + T(s0_desc.fs.images[1].sample_type == SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT); + T(s0_desc.fs.images[1].multisampled == false); + T(s0_desc.fs.samplers[0].used); + T(s0_desc.fs.samplers[0].sampler_type == SG_SAMPLERTYPE_COMPARISON); + T(s0_desc.fs.samplers[1].used); + T(s0_desc.fs.samplers[1].sampler_type == SG_SAMPLERTYPE_NONFILTERING); + T(s0_desc.fs.image_sampler_pairs[0].used); + T(s0_desc.fs.image_sampler_pairs[0].image_slot == 0); + T(s0_desc.fs.image_sampler_pairs[0].sampler_slot == 0); + T(s0_desc.fs.image_sampler_pairs[0].glsl_name == 0); + T(s0_desc.fs.image_sampler_pairs[1].used); + T(s0_desc.fs.image_sampler_pairs[1].image_slot == 1); + T(s0_desc.fs.image_sampler_pairs[1].sampler_slot == 1); + T(s0_desc.fs.image_sampler_pairs[1].glsl_name == 0); + + sg_shutdown(); +} + +UTEST(sokol_gfx, query_pipeline_desc) { + setup(&(sg_desc){0}); + + sg_shader shd = sg_make_shader(&(sg_shader_desc){0}); + sg_pipeline p0 = sg_make_pipeline(&(sg_pipeline_desc){ + .shader = shd, + .layout = { + .attrs = { + [0] = { .format = SG_VERTEXFORMAT_FLOAT4 }, + [1] = { .format = SG_VERTEXFORMAT_FLOAT2 }, + } + }, + .label = "p0", + }); + + const sg_pipeline_desc p0_desc = sg_query_pipeline_desc(p0); + T(p0_desc.shader.id == shd.id); + T(p0_desc.layout.buffers[0].stride == 24); + T(p0_desc.layout.buffers[0].step_func == SG_VERTEXSTEP_PER_VERTEX); + T(p0_desc.layout.buffers[0].step_rate == 1); + T(p0_desc.layout.buffers[1].stride == 0); + T(p0_desc.layout.buffers[1].step_func == 0); + T(p0_desc.layout.buffers[1].step_rate == 0); + T(p0_desc.layout.attrs[0].format == SG_VERTEXFORMAT_FLOAT4); + T(p0_desc.layout.attrs[0].offset == 0); + T(p0_desc.layout.attrs[0].buffer_index == 0); + T(p0_desc.layout.attrs[1].format == SG_VERTEXFORMAT_FLOAT2); + T(p0_desc.layout.attrs[1].offset == 16); + T(p0_desc.layout.attrs[1].buffer_index == 0); + T(p0_desc.layout.attrs[2].format == 0); + T(p0_desc.layout.attrs[2].offset == 0); + T(p0_desc.layout.attrs[2].buffer_index == 0); + T(p0_desc.depth.pixel_format == SG_PIXELFORMAT_DEPTH_STENCIL); + T(p0_desc.depth.compare == SG_COMPAREFUNC_ALWAYS); + T(p0_desc.depth.write_enabled == false); + T(p0_desc.depth.bias == 0.0f); + T(p0_desc.depth.bias_slope_scale == 0.0f); + T(p0_desc.depth.bias_clamp == 0.0f); + T(p0_desc.stencil.enabled == false); + T(p0_desc.stencil.front.compare == SG_COMPAREFUNC_ALWAYS); + T(p0_desc.stencil.front.fail_op == SG_STENCILOP_KEEP); + T(p0_desc.stencil.front.depth_fail_op == SG_STENCILOP_KEEP); + T(p0_desc.stencil.front.pass_op == SG_STENCILOP_KEEP); + T(p0_desc.stencil.back.compare == SG_COMPAREFUNC_ALWAYS); + T(p0_desc.stencil.back.fail_op == SG_STENCILOP_KEEP); + T(p0_desc.stencil.back.depth_fail_op == SG_STENCILOP_KEEP); + T(p0_desc.stencil.back.pass_op == SG_STENCILOP_KEEP); + T(p0_desc.stencil.read_mask == 0); + T(p0_desc.stencil.write_mask == 0); + T(p0_desc.stencil.ref == 0); + T(p0_desc.color_count == 1); + T(p0_desc.colors[0].pixel_format == SG_PIXELFORMAT_RGBA8); + T(p0_desc.colors[0].write_mask == SG_COLORMASK_RGBA); + T(p0_desc.colors[0].blend.enabled == false); + T(p0_desc.colors[0].blend.src_factor_rgb == SG_BLENDFACTOR_ONE); + T(p0_desc.colors[0].blend.dst_factor_rgb == SG_BLENDFACTOR_ZERO); + T(p0_desc.colors[0].blend.op_rgb == SG_BLENDOP_ADD); + T(p0_desc.colors[0].blend.src_factor_alpha == SG_BLENDFACTOR_ONE); + T(p0_desc.colors[0].blend.dst_factor_alpha == SG_BLENDFACTOR_ZERO); + T(p0_desc.colors[0].blend.op_alpha == SG_BLENDOP_ADD); + T(p0_desc.primitive_type == SG_PRIMITIVETYPE_TRIANGLES); + T(p0_desc.index_type == SG_INDEXTYPE_NONE); + T(p0_desc.cull_mode == SG_CULLMODE_NONE); + T(p0_desc.face_winding == SG_FACEWINDING_CW); + T(p0_desc.sample_count == 1); + T(p0_desc.blend_color.r == 0.0f); + T(p0_desc.blend_color.g == 0.0f); + T(p0_desc.blend_color.b == 0.0f); + T(p0_desc.blend_color.a == 0.0f); + T(p0_desc.alpha_to_coverage_enabled == false); + T(p0_desc.label == 0); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_attachments_desc) { + setup(&(sg_desc){0}); + + const sg_image_desc color_img_desc = { + .render_target = true, + .width = 128, + .height = 128, + }; + const sg_image_desc depth_img_desc = { + .render_target = true, + .width = 128, + .height = 128, + .pixel_format = SG_PIXELFORMAT_DEPTH, + }; + sg_image color_img_0 = sg_make_image(&color_img_desc); + sg_image color_img_1 = sg_make_image(&color_img_desc); + sg_image color_img_2 = sg_make_image(&color_img_desc); + sg_image depth_img = sg_make_image(&depth_img_desc); + + sg_attachments a0 = sg_make_attachments(&(sg_attachments_desc){ + .colors = { + [0].image = color_img_0, + [1].image = color_img_1, + [2].image = color_img_2, + }, + .depth_stencil.image = depth_img, + }); + const sg_attachments_desc a0_desc = sg_query_attachments_desc(a0); + T(a0_desc.colors[0].image.id == color_img_0.id); + T(a0_desc.colors[0].mip_level == 0); + T(a0_desc.colors[0].slice == 0); + T(a0_desc.colors[1].image.id == color_img_1.id); + T(a0_desc.colors[1].mip_level == 0); + T(a0_desc.colors[1].slice == 0); + T(a0_desc.colors[2].image.id == color_img_2.id); + T(a0_desc.colors[2].mip_level == 0); + T(a0_desc.colors[2].slice == 0); + T(a0_desc.depth_stencil.image.id == depth_img.id); + T(a0_desc.depth_stencil.mip_level == 0); + T(a0_desc.depth_stencil.slice == 0); + + sg_shutdown(); +} + +UTEST(sokol_gfx, buffer_resource_states) { + setup(&(sg_desc){0}); + sg_buffer buf = sg_alloc_buffer(); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_ALLOC); + sg_init_buffer(buf, &(sg_buffer_desc){ .usage = SG_USAGE_STREAM, .size = 128 }); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_VALID); + sg_uninit_buffer(buf); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_ALLOC); + sg_dealloc_buffer(buf); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, image_resource_states) { + setup(&(sg_desc){0}); + sg_image img = sg_alloc_image(); + T(sg_query_image_state(img) == SG_RESOURCESTATE_ALLOC); + sg_init_image(img, &(sg_image_desc){ .render_target = true, .width = 16, .height = 16 }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_VALID); + sg_uninit_image(img); + T(sg_query_image_state(img) == SG_RESOURCESTATE_ALLOC); + sg_dealloc_image(img); + T(sg_query_image_state(img) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, sampler_resource_states) { + setup(&(sg_desc){0}); + sg_sampler smp = sg_alloc_sampler(); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_ALLOC); + sg_init_sampler(smp, &(sg_sampler_desc){ .min_filter = SG_FILTER_LINEAR, .mag_filter = SG_FILTER_LINEAR }); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_VALID); + sg_uninit_sampler(smp); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_ALLOC); + sg_dealloc_sampler(smp); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, shader_resource_states) { + setup(&(sg_desc){0}); + sg_shader shd = sg_alloc_shader(); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_ALLOC); + sg_init_shader(shd, &(sg_shader_desc){0}); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_VALID); + sg_uninit_shader(shd); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_ALLOC); + sg_dealloc_shader(shd); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, pipeline_resource_states) { + setup(&(sg_desc){0}); + sg_pipeline pip = sg_alloc_pipeline(); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_ALLOC); + sg_init_pipeline(pip, &(sg_pipeline_desc){ + .shader = sg_make_shader(&(sg_shader_desc){0}), + .layout.attrs[0].format = SG_VERTEXFORMAT_FLOAT3 + }); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_VALID); + sg_uninit_pipeline(pip); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_ALLOC); + sg_dealloc_pipeline(pip); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, attachments_resource_states) { + setup(&(sg_desc){0}); + sg_attachments atts = sg_alloc_attachments(); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_ALLOC); + sg_init_attachments(atts, &(sg_attachments_desc){ + .colors[0].image = sg_make_image(&(sg_image_desc){ .render_target=true, .width=16, .height=16}) + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_VALID); + sg_uninit_attachments(atts); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_ALLOC); + sg_dealloc_attachments(atts); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_buffer_will_overflow) { + setup(&(sg_desc){0}); + sg_buffer buf = sg_make_buffer(&(sg_buffer_desc){ + .size = 64, + .usage = SG_USAGE_STREAM + }); + T(!sg_query_buffer_will_overflow(buf, 32)); + T(!sg_query_buffer_will_overflow(buf, 64)); + T(sg_query_buffer_will_overflow(buf, 65)); + static const uint8_t data[32] = {0}; + sg_append_buffer(buf, &SG_RANGE(data)); + T(!sg_query_buffer_will_overflow(buf, 32)); + T(sg_query_buffer_will_overflow(buf, 33)); + sg_shutdown(); +} + +static struct { + uintptr_t userdata; + int num_called; +} commit_listener; +static void reset_commit_listener(void) { + commit_listener.userdata = 0; + commit_listener.num_called = 0; +} +static void commit_listener_func(void* ud) { + commit_listener.userdata = (uintptr_t)ud; + commit_listener.num_called++; +} + +UTEST(sokol_gfx, commit_listener_called) { + reset_commit_listener(); + setup(&(sg_desc){0}); + const bool added = sg_add_commit_listener((sg_commit_listener){ + .func = commit_listener_func, + .user_data = (void*)23, + }); + T(added); + T(_sg.commit_listeners.upper == 1); + sg_commit(); + T(23 == commit_listener.userdata); + T(1 == commit_listener.num_called); + sg_shutdown(); +} + +UTEST(sokol_gfx, commit_listener_add_twice) { + reset_commit_listener(); + setup(&(sg_desc){0}); + const sg_commit_listener listener = { + .func = commit_listener_func, + .user_data = (void*)23, + }; + T(sg_add_commit_listener(listener)); + T(_sg.commit_listeners.upper == 1); + T(!sg_add_commit_listener(listener)); + T(_sg.commit_listeners.upper == 1); + sg_commit(); + T(23 == commit_listener.userdata); + T(1 == commit_listener.num_called); + sg_shutdown(); +} + +UTEST(sokol_gfx, commit_listener_same_func_diff_ud) { + reset_commit_listener(); + setup(&(sg_desc){0}); + T(sg_add_commit_listener((sg_commit_listener){ + .func = commit_listener_func, + .user_data = (void*)23, + })); + T(_sg.commit_listeners.upper == 1); + T(sg_add_commit_listener((sg_commit_listener){ + .func = commit_listener_func, + .user_data = (void*)25, + })); + T(_sg.commit_listeners.upper == 2); + sg_commit(); + T(2 == commit_listener.num_called); + sg_shutdown(); +} + +UTEST(sokol_gfx, commit_listener_add_remove_add) { + reset_commit_listener(); + setup(&(sg_desc){0}); + const sg_commit_listener listener = { + .func = commit_listener_func, + .user_data = (void*)23, + }; + T(sg_add_commit_listener(listener)); + T(_sg.commit_listeners.upper == 1); + T(sg_remove_commit_listener(listener)); + T(_sg.commit_listeners.upper == 1); + sg_commit(); + T(0 == commit_listener.num_called); + T(sg_add_commit_listener(listener)); + T(_sg.commit_listeners.upper == 1); + sg_commit(); + T(1 == commit_listener.num_called); + T(23 == commit_listener.userdata); + sg_shutdown(); +} + +UTEST(sokol_gfx, commit_listener_remove_non_existent) { + reset_commit_listener(); + setup(&(sg_desc){0}); + const sg_commit_listener l0 = { + .func = commit_listener_func, + .user_data = (void*)23, + }; + const sg_commit_listener l1 = { + .func = commit_listener_func, + .user_data = (void*)46, + }; + const sg_commit_listener l2 = { + .func = commit_listener_func, + .user_data = (void*)256, + }; + T(sg_add_commit_listener(l0)); + T(sg_add_commit_listener(l1)); + T(_sg.commit_listeners.upper == 2); + T(!sg_remove_commit_listener(l2)); + T(_sg.commit_listeners.upper == 2); + sg_shutdown(); +} + +UTEST(sokol_gfx, commit_listener_multi_add_remove) { + reset_commit_listener(); + setup(&(sg_desc){0}); + const sg_commit_listener l0 = { + .func = commit_listener_func, + .user_data = (void*)23, + }; + const sg_commit_listener l1 = { + .func = commit_listener_func, + .user_data = (void*)46, + }; + T(sg_add_commit_listener(l0)); + T(sg_add_commit_listener(l1)); + T(_sg.commit_listeners.upper == 2); + // removing the first listener will just clear its slot + T(sg_remove_commit_listener(l0)); + T(_sg.commit_listeners.upper == 2); + sg_commit(); + T(commit_listener.num_called == 1); + T(commit_listener.userdata == 46); + commit_listener.num_called = 0; + // adding the first listener back will fill that same slot again + T(sg_add_commit_listener(l0)); + T(_sg.commit_listeners.upper == 2); + sg_commit(); + T(commit_listener.num_called == 2); + T(commit_listener.userdata == 46); + commit_listener.num_called = 0; + // removing the second listener will decrement the upper bound + T(sg_remove_commit_listener(l1)); + T(_sg.commit_listeners.upper == 2); + sg_commit(); + T(commit_listener.num_called == 1); + T(commit_listener.userdata == 23); + commit_listener.num_called = 0; + // and finally remove the first listener too + T(sg_remove_commit_listener(l0)); + T(_sg.commit_listeners.upper == 2); + sg_commit(); + T(commit_listener.num_called == 0); + // removing the same listener twice just returns false + T(!sg_remove_commit_listener(l0)); + T(!sg_remove_commit_listener(l1)); + sg_shutdown(); +} + +UTEST(sokol_gfx, commit_listener_array_full) { + reset_commit_listener(); + setup(&(sg_desc){ + .max_commit_listeners = 3, + }); + const sg_commit_listener l0 = { + .func = commit_listener_func, + .user_data = (void*)23, + }; + const sg_commit_listener l1 = { + .func = commit_listener_func, + .user_data = (void*)46, + }; + const sg_commit_listener l2 = { + .func = commit_listener_func, + .user_data = (void*)128, + }; + const sg_commit_listener l3 = { + .func = commit_listener_func, + .user_data = (void*)256, + }; + T(sg_add_commit_listener(l0)); + T(sg_add_commit_listener(l1)); + T(sg_add_commit_listener(l2)); + T(_sg.commit_listeners.upper == 3); + // overflow! + T(!sg_add_commit_listener(l3)); + T(_sg.commit_listeners.upper == 3); + sg_commit(); + T(commit_listener.num_called == 3); + T(commit_listener.userdata == 128); + sg_shutdown(); +} + +UTEST(sokol_gfx, buffer_double_destroy_is_ok) { + setup(&(sg_desc){0}); + sg_buffer buf = create_buffer(); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_VALID); + sg_destroy_buffer(buf); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_INVALID); + sg_destroy_buffer(buf); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, image_double_destroy_is_ok) { + setup(&(sg_desc){0}); + sg_image img = create_image(); + T(sg_query_image_state(img) == SG_RESOURCESTATE_VALID); + sg_destroy_image(img); + T(sg_query_image_state(img) == SG_RESOURCESTATE_INVALID); + sg_destroy_image(img); + T(sg_query_image_state(img) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, sampler_double_destroy_is_ok) { + setup(&(sg_desc){0}); + sg_sampler smp = sg_make_sampler(&(sg_sampler_desc){0}); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_VALID); + sg_destroy_sampler(smp); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_INVALID); + sg_destroy_sampler(smp); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, shader_double_destroy_is_ok) { + setup(&(sg_desc){0}); + sg_shader shd = create_shader(); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_VALID); + sg_destroy_shader(shd); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_INVALID); + sg_destroy_shader(shd); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, pipeline_double_destroy_is_ok) { + setup(&(sg_desc){0}); + sg_pipeline pip = create_pipeline(); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_VALID); + sg_destroy_pipeline(pip); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_INVALID); + sg_destroy_pipeline(pip); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokoL_gfx, attachments_double_destroy_is_ok) { + setup(&(sg_desc){0}); + sg_attachments atts = create_attachments(); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_VALID); + sg_destroy_attachments(atts); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_INVALID); + sg_destroy_attachments(atts); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_dealloc_buffer_warns) { + setup(&(sg_desc){0}); + sg_buffer buf = create_buffer(); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_VALID); + sg_dealloc_buffer(buf); + T(log_items[0] == SG_LOGITEM_DEALLOC_BUFFER_INVALID_STATE); + T(num_log_called == 1); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_VALID); + sg_destroy_buffer(buf); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_dealloc_image_warns) { + setup(&(sg_desc){0}); + sg_image img = create_image(); + T(sg_query_image_state(img) == SG_RESOURCESTATE_VALID); + sg_dealloc_image(img); + T(log_items[0] == SG_LOGITEM_DEALLOC_IMAGE_INVALID_STATE); + T(num_log_called == 1); + T(sg_query_image_state(img) == SG_RESOURCESTATE_VALID); + sg_destroy_image(img); + T(sg_query_image_state(img) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_dealloc_sampler_warns) { + setup(&(sg_desc){0}); + sg_sampler smp = sg_make_sampler(&(sg_sampler_desc){0}); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_VALID); + sg_dealloc_sampler(smp); + T(log_items[0] == SG_LOGITEM_DEALLOC_SAMPLER_INVALID_STATE); + T(num_log_called == 1); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_VALID); + sg_destroy_sampler(smp); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_dealloc_shader_warns) { + setup(&(sg_desc){0}); + sg_shader shd = create_shader(); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_VALID); + sg_dealloc_shader(shd); + T(log_items[0] == SG_LOGITEM_DEALLOC_SHADER_INVALID_STATE); + T(num_log_called == 1); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_VALID); + sg_destroy_shader(shd); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_dealloc_pipeline_warns) { + setup(&(sg_desc){0}); + sg_pipeline pip = create_pipeline(); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_VALID); + sg_dealloc_pipeline(pip); + T(log_items[0] == SG_LOGITEM_DEALLOC_PIPELINE_INVALID_STATE); + T(num_log_called == 1); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_VALID); + sg_destroy_pipeline(pip); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_dealloc_attachments_warns) { + setup(&(sg_desc){0}); + sg_attachments atts = create_attachments(); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_VALID); + sg_dealloc_attachments(atts); + T(log_items[0] == SG_LOGITEM_DEALLOC_ATTACHMENTS_INVALID_STATE); + T(num_log_called == 1); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_VALID); + sg_destroy_attachments(atts); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_uninit_buffer_warns) { + setup(&(sg_desc){0}); + sg_buffer buf = sg_alloc_buffer(); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_ALLOC); + sg_uninit_buffer(buf); + T(log_items[0] == SG_LOGITEM_UNINIT_BUFFER_INVALID_STATE); + T(num_log_called == 1); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_ALLOC); + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_uninit_image_warns) { + setup(&(sg_desc){0}); + sg_image img = sg_alloc_image(); + T(sg_query_image_state(img) == SG_RESOURCESTATE_ALLOC); + sg_uninit_image(img); + T(log_items[0] == SG_LOGITEM_UNINIT_IMAGE_INVALID_STATE); + T(num_log_called == 1); + T(sg_query_image_state(img) == SG_RESOURCESTATE_ALLOC); + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_uninit_sampler_warns) { + setup(&(sg_desc){0}); + sg_sampler smp = sg_alloc_sampler(); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_ALLOC); + sg_uninit_sampler(smp); + T(log_items[0] == SG_LOGITEM_UNINIT_SAMPLER_INVALID_STATE); + T(num_log_called == 1); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_ALLOC); + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_uninit_shader_warns) { + setup(&(sg_desc){0}); + sg_shader shd = sg_alloc_shader(); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_ALLOC); + sg_uninit_shader(shd); + T(log_items[0] == SG_LOGITEM_UNINIT_SHADER_INVALID_STATE); + T(num_log_called == 1); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_ALLOC); + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_uninit_pipeline_warns) { + setup(&(sg_desc){0}); + sg_pipeline pip = sg_alloc_pipeline(); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_ALLOC); + sg_uninit_pipeline(pip); + T(log_items[0] == SG_LOGITEM_UNINIT_PIPELINE_INVALID_STATE); + T(num_log_called == 1); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_ALLOC); + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_uninit_attachments_warns) { + setup(&(sg_desc){0}); + sg_attachments atts = sg_alloc_attachments(); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_ALLOC); + sg_uninit_attachments(atts); + T(log_items[0] == SG_LOGITEM_UNINIT_ATTACHMENTS_INVALID_STATE); + T(num_log_called == 1); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_ALLOC); + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_destroy_buffer_is_ok) { + setup(&(sg_desc){0}); + sg_buffer buf = sg_alloc_buffer(); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_ALLOC); + sg_destroy_buffer(buf); + T(num_log_called == 0); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_destroy_image_is_ok) { + setup(&(sg_desc){0}); + sg_image img = sg_alloc_image(); + T(sg_query_image_state(img) == SG_RESOURCESTATE_ALLOC); + sg_destroy_image(img); + T(num_log_called == 0); + T(sg_query_image_state(img) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_destroy_sampler_is_ok) { + setup(&(sg_desc){0}); + sg_sampler smp = sg_alloc_sampler(); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_ALLOC); + sg_destroy_sampler(smp); + T(num_log_called == 0); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); + +} + +UTEST(sokol_gfx, alloc_destroy_shader_is_ok) { + setup(&(sg_desc){0}); + sg_shader shd = sg_alloc_shader(); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_ALLOC); + sg_destroy_shader(shd); + T(num_log_called == 0); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_destroy_pipeline_is_ok) { + setup(&(sg_desc){0}); + sg_pipeline pip = sg_alloc_pipeline(); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_ALLOC); + sg_destroy_pipeline(pip); + T(num_log_called == 0); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, alloc_destroy_attachments_is_ok) { + setup(&(sg_desc){0}); + sg_attachments atts = sg_alloc_attachments(); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_ALLOC); + sg_destroy_attachments(atts); + T(num_log_called == 0); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_pipeline_with_nonvalid_shader) { + setup(&(sg_desc){ + .disable_validation = true, + }); + sg_shader shd = sg_alloc_shader(); + T(sg_query_shader_state(shd) == SG_RESOURCESTATE_ALLOC); + sg_pipeline pip = sg_make_pipeline(&(sg_pipeline_desc){ + .shader = shd, + .layout = { + .attrs[0].format = SG_VERTEXFORMAT_FLOAT3 + }, + }); + T(sg_query_pipeline_state(pip) == SG_RESOURCESTATE_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_with_nonvalid_color_images) { + setup(&(sg_desc){ + .disable_validation = true, + }); + sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors = { + [0].image = sg_alloc_image(), + [1].image = sg_alloc_image(), + }, + .depth_stencil = { + .image = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 128, + .height = 128 + }) + } + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + sg_destroy_attachments(atts); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_INVALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_without_color_attachments) { + setup(&(sg_desc){0}); + sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .depth_stencil.image = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .pixel_format = SG_PIXELFORMAT_DEPTH, + }) + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_VALID); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_buffer_validate_start_canary) { + setup(&(sg_desc){0}); + const uint32_t data[32] = {0}; + sg_buffer buf = sg_make_buffer(&(sg_buffer_desc){ + ._start_canary = 1234, + .data = SG_RANGE(data), + }); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_BUFFERDESC_CANARY); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_buffer_validate_end_canary) { + setup(&(sg_desc){0}); + const uint32_t data[32] = {0}; + sg_buffer buf = sg_make_buffer(&(sg_buffer_desc){ + .data = SG_RANGE(data), + ._end_canary = 1234, + }); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_BUFFERDESC_CANARY); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_buffer_validate_immutable_nodata) { + setup(&(sg_desc){0}); + sg_buffer buf = sg_make_buffer(&(sg_buffer_desc){ 0 }); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_BUFFERDESC_SIZE); + T(log_items[1] == SG_LOGITEM_VALIDATE_BUFFERDESC_DATA); + T(log_items[2] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_buffer_validate_size_mismatch) { + setup(&(sg_desc){0}); + const uint32_t data[16] = { 0 }; + sg_buffer buf = sg_make_buffer(&(sg_buffer_desc){ + .size = 15 * sizeof(uint32_t), + .data = SG_RANGE(data), + }); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_BUFFERDESC_DATA_SIZE); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_buffer_validate_data_ptr_but_no_size) { + setup(&(sg_desc){0}); + const uint32_t data[16] = {0}; + sg_buffer buf = sg_make_buffer(&(sg_buffer_desc){ + .data.ptr = data, + }); + T(sg_query_buffer_state(buf) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_BUFFERDESC_SIZE); + T(log_items[1] == SG_LOGITEM_VALIDATE_BUFFERDESC_DATA); + T(log_items[2] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_image_validate_start_canary) { + setup(&(sg_desc){0}); + const uint32_t pixels[8][8] = {0}; + sg_image img = sg_make_image(&(sg_image_desc){ + ._start_canary = 1234, + .width = 8, + .height = 8, + .data.subimage[0][0] = SG_RANGE(pixels), + }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_IMAGEDESC_CANARY); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_image_validate_end_canary) { + setup(&(sg_desc){0}); + const uint32_t pixels[8][8] = {0}; + sg_image img = sg_make_image(&(sg_image_desc){ + .width = 8, + .height = 8, + .data.subimage[0][0] = SG_RANGE(pixels), + ._end_canary = 1234, + }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_IMAGEDESC_CANARY); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_image_zero_width_height) { + setup(&(sg_desc){0}); + const uint32_t pixels[8][8] = {0}; + sg_image img = sg_make_image(&(sg_image_desc){ + .width = 0, + .height = 0, + .data.subimage[0][0] = SG_RANGE(pixels), + }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_IMAGEDESC_WIDTH); + T(log_items[1] == SG_LOGITEM_VALIDATE_IMAGEDESC_HEIGHT); + T(log_items[2] == SG_LOGITEM_VALIDATE_IMAGEDATA_DATA_SIZE); + T(log_items[3] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_image_validate_msaa_no_rt) { + setup(&(sg_desc){0}); + const uint32_t pixels[8][8] = {0}; + sg_image img = sg_make_image(&(sg_image_desc){ + .width = 8, + .height = 8, + .sample_count = 4, + .data.subimage[0][0] = SG_RANGE(pixels), + }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_IMAGEDESC_MSAA_BUT_NO_RT); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_image_validate_msaa_num_mipmaps) { + setup(&(sg_desc){0}); + sg_image img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 4, + .num_mipmaps = 2, + }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_IMAGEDESC_MSAA_NUM_MIPMAPS); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_image_validate_msaa_3d_image) { + setup(&(sg_desc){0}); + sg_image img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .type = SG_IMAGETYPE_3D, + .width = 32, + .height = 32, + .num_slices = 32, + .sample_count = 4, + }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_IMAGEDESC_MSAA_3D_IMAGE); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_image_validate_depth_3d_image_with_depth_format) { + setup(&(sg_desc){0}); + sg_image img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .type = SG_IMAGETYPE_3D, + .width = 8, + .height = 8, + .num_slices = 8, + .pixel_format = SG_PIXELFORMAT_DEPTH, + }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_IMAGEDESC_DEPTH_3D_IMAGE); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_image_validate_rt_immutable) { + setup(&(sg_desc){0}); + sg_image img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .usage = SG_USAGE_DYNAMIC, + .width = 8, + .height = 8, + }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_IMAGEDESC_RT_IMMUTABLE); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_image_validate_dynamic_no_data) { + setup(&(sg_desc){0}); + uint32_t pixels[8][8] = {0}; + sg_image img = sg_make_image(&(sg_image_desc){ + .width = 8, + .height = 8, + .usage = SG_USAGE_DYNAMIC, + .data.subimage[0][0] = SG_RANGE(pixels), + }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_IMAGEDESC_DYNAMIC_NO_DATA); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_image_valiate_compressed_immutable) { + setup(&(sg_desc){0}); + sg_image img = sg_make_image(&(sg_image_desc){ + .width = 8, + .height = 8, + .pixel_format = SG_PIXELFORMAT_BC1_RGBA, + .usage = SG_USAGE_DYNAMIC, + }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_IMAGEDESC_COMPRESSED_IMMUTABLE); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_image_validate_nodata) { + setup(&(sg_desc){0}); + sg_image img = sg_make_image(&(sg_image_desc){ + .width = 8, + .height = 8, + }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_IMAGEDATA_NODATA); + T(log_items[1] == SG_LOGITEM_VALIDATE_IMAGEDATA_DATA_SIZE); + T(log_items[2] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_image_validate_data_size) { + setup(&(sg_desc){0}); + uint32_t pixels[4][4] = {0}; + sg_image img = sg_make_image(&(sg_image_desc){ + .width = 8, + .height = 8, + .data.subimage[0][0] = SG_RANGE(pixels), + }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_IMAGEDATA_DATA_SIZE); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_image_validate_missing_mipdata) { + setup(&(sg_desc){0}); + uint32_t mip0[8][8] = {0}; + uint32_t mip1[4][4] = {0}; + uint32_t mip2[2][2] = {0}; + sg_image img = sg_make_image(&(sg_image_desc){ + .width = 8, + .height = 8, + .num_mipmaps = 4, + .data.subimage[0][0] = SG_RANGE(mip0), + .data.subimage[0][1] = SG_RANGE(mip1), + .data.subimage[0][2] = SG_RANGE(mip2), + }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_IMAGEDATA_NODATA); + T(log_items[1] == SG_LOGITEM_VALIDATE_IMAGEDATA_DATA_SIZE); + T(log_items[2] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_image_validate_wrong_mipsize) { + setup(&(sg_desc){0}); + uint32_t mip0[8][8] = {0}; + uint32_t mip1[4][4] = {0}; + uint32_t mip2[2][2] = {0}; + uint32_t mip3[1][1] = {0}; + sg_image img = sg_make_image(&(sg_image_desc){ + .width = 8, + .height = 8, + .num_mipmaps = 4, + .data.subimage[0][0] = SG_RANGE(mip0), + .data.subimage[0][1] = SG_RANGE(mip2), + .data.subimage[0][2] = SG_RANGE(mip1), + .data.subimage[0][3] = SG_RANGE(mip3) + }); + T(sg_query_image_state(img) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_IMAGEDATA_DATA_SIZE); + T(log_items[1] == SG_LOGITEM_VALIDATE_IMAGEDATA_DATA_SIZE); + T(log_items[2] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_sampler_validate_start_canary) { + setup(&(sg_desc){0}); + sg_sampler smp = sg_make_sampler(&(sg_sampler_desc){ + ._start_canary = 1234, + }); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_SAMPLERDESC_CANARY); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_sampler_validate_minfilter_none) { + setup(&(sg_desc){0}); + sg_sampler smp = sg_make_sampler(&(sg_sampler_desc){ + .min_filter = SG_FILTER_NONE, + }); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_SAMPLERDESC_MINFILTER_NONE); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_sampler_validate_magfilter_none) { + setup(&(sg_desc){0}); + sg_sampler smp = sg_make_sampler(&(sg_sampler_desc){ + .mag_filter = SG_FILTER_NONE, + }); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_SAMPLERDESC_MAGFILTER_NONE); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_sampler_validate_anistropic_requires_linear_filtering) { + setup(&(sg_desc){0}); + sg_sampler smp; + + smp = sg_make_sampler(&(sg_sampler_desc){ + .max_anisotropy = 2, + .min_filter = SG_FILTER_LINEAR, + .mag_filter = SG_FILTER_LINEAR, + .mipmap_filter = SG_FILTER_NONE, + }); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_SAMPLERDESC_ANISTROPIC_REQUIRES_LINEAR_FILTERING); + + reset_log_items(); + smp = sg_make_sampler(&(sg_sampler_desc){ + .max_anisotropy = 2, + .min_filter = SG_FILTER_LINEAR, + .mag_filter = SG_FILTER_LINEAR, + .mipmap_filter = SG_FILTER_NEAREST, + }); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_SAMPLERDESC_ANISTROPIC_REQUIRES_LINEAR_FILTERING); + + reset_log_items(); + smp = sg_make_sampler(&(sg_sampler_desc){ + .max_anisotropy = 2, + .min_filter = SG_FILTER_NEAREST, + .mag_filter = SG_FILTER_LINEAR, + .mipmap_filter = SG_FILTER_LINEAR, + }); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_SAMPLERDESC_ANISTROPIC_REQUIRES_LINEAR_FILTERING); + + reset_log_items(); + smp = sg_make_sampler(&(sg_sampler_desc){ + .max_anisotropy = 2, + .min_filter = SG_FILTER_LINEAR, + .mag_filter = SG_FILTER_NEAREST, + .mipmap_filter = SG_FILTER_LINEAR, + }); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_SAMPLERDESC_ANISTROPIC_REQUIRES_LINEAR_FILTERING); + + reset_log_items(); + smp = sg_make_sampler(&(sg_sampler_desc){ + .max_anisotropy = 2, + .min_filter = SG_FILTER_LINEAR, + .mag_filter = SG_FILTER_LINEAR, + .mipmap_filter = SG_FILTER_LINEAR, + }); + T(sg_query_sampler_state(smp) == SG_RESOURCESTATE_VALID); + + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_start_canary) { + setup(&(sg_desc){0}); + sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + ._start_canary = 1234, + .colors[0].image = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + }), + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_CANARY); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_end_canary) { + setup(&(sg_desc){0}); + sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0].image = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + }), + ._end_canary = 1234, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_CANARY); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_no_cont_color_atts1) { + setup(&(sg_desc){0}); + const sg_image_desc img_desc = { .render_target = true, .width = 64, .height = 64 }; + sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors = { + [0].image = sg_make_image(&img_desc), + [2].image = sg_make_image(&img_desc), + } + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_NO_CONT_COLOR_ATTS); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_image) { + setup(&(sg_desc){0}); + const sg_image_desc img_desc = { .render_target = true, .width = 64, .height = 64 }; + const sg_image img0 = sg_make_image(&img_desc); + const sg_image img1 = sg_make_image(&img_desc); + sg_destroy_image(img1); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors = { + [0].image = img0, + [1].image = img1, + } + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_IMAGE); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_miplevel) { + setup(&(sg_desc){0}); + const sg_image img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 16, + .height = 16, + .num_mipmaps = 4, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = img, .mip_level = 4 } + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_MIPLEVEL); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_face) { + setup(&(sg_desc){0}); + const sg_image img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .type = SG_IMAGETYPE_CUBE, + .width = 64, + .height = 64, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = img, .slice = 6 } + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_FACE); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_layer) { + setup(&(sg_desc){0}); + const sg_image img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .type = SG_IMAGETYPE_ARRAY, + .width = 64, + .height = 64, + .num_slices = 4, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = img, .slice = 5 }, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_LAYER); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_slice) { + setup(&(sg_desc){0}); + const sg_image img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .type = SG_IMAGETYPE_3D, + .width = 64, + .height = 64, + .num_slices = 4, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = img, .slice = 5 }, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_SLICE); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_image_no_rt) { + setup(&(sg_desc){0}); + const sg_image img = sg_make_image(&(sg_image_desc){ + .width = 8, + .height = 8, + .usage = SG_USAGE_DYNAMIC, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0].image = img, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_IMAGE_NO_RT); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_color_inv_pixelformat) { + setup(&(sg_desc){0}); + const sg_image_desc img_desc = { + .render_target = true, + .width = 8, + .height = 8, + .pixel_format = SG_PIXELFORMAT_DEPTH, + }; + reset_log_items(); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0].image = sg_make_image(&img_desc), + .depth_stencil.image = sg_make_image(&img_desc), + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_COLOR_INV_PIXELFORMAT); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_depth_inv_pixelformat) { + setup(&(sg_desc){0}); + const sg_image_desc img_desc = { + .render_target = true, + .width = 8, + .height = 8, + }; + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0].image = sg_make_image(&img_desc), + .depth_stencil.image = sg_make_image(&img_desc), + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_DEPTH_INV_PIXELFORMAT); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_image_sizes) { + setup(&(sg_desc){0}); + const sg_image img0 = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + }); + const sg_image img1 = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 32, + .height = 32, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors = { + [0].image = img0, + [1].image = img1, + } + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_IMAGE_SIZES); + T(log_items[1] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_IMAGE_SIZES); + T(log_items[2] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_image_sample_counts) { + setup(&(sg_desc){0}); + const sg_image img0 = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 4, + }); + const sg_image img1 = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 2, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors = { + [0].image = img0, + [1].image = img1, + } + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_IMAGE_SAMPLE_COUNTS); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_resolve_color_image_msaa) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 1, + }); + const sg_image resolve_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 1, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0].image = color_img, + .resolves[0].image = resolve_img, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_RESOLVE_COLOR_IMAGE_MSAA); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_resolve_image) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 4, + }); + const sg_image resolve_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 1, + }); + sg_destroy_image(resolve_img); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0].image = color_img, + .resolves[0].image = resolve_img, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_resolve_sample_count) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 4, + }); + const sg_image resolve_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 4, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0].image = color_img, + .resolves[0].image = resolve_img, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_RESOLVE_SAMPLE_COUNT); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_resolve_miplevel) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 4, + }); + const sg_image resolve_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 1, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = color_img }, + .resolves[0] = { .image = resolve_img, .mip_level = 1 }, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_RESOLVE_MIPLEVEL); + // FIXME: these are confusing + T(log_items[1] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_SIZES); + T(log_items[2] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_SIZES); + T(log_items[3] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_resolve_face) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 4, + }); + const sg_image resolve_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .type = SG_IMAGETYPE_CUBE, + .width = 64, + .height = 64, + .sample_count = 1, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = color_img }, + .resolves[0] = { .image = resolve_img, .slice = 6 }, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_RESOLVE_FACE); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_resolve_layer) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 4, + }); + const sg_image resolve_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .type = SG_IMAGETYPE_ARRAY, + .width = 64, + .height = 64, + .num_slices = 4, + .sample_count = 1, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = color_img }, + .resolves[0] = { .image = resolve_img, .slice = 4 }, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_RESOLVE_LAYER); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_resolve_slice) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 4, + }); + const sg_image resolve_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .type = SG_IMAGETYPE_3D, + .width = 64, + .height = 64, + .num_slices = 4, + .sample_count = 1, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = color_img }, + .resolves[0] = { .image = resolve_img, .slice = 4 }, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_RESOLVE_SLICE); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_resolve_image_no_rt) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 4, + }); + const sg_image resolve_img = sg_make_image(&(sg_image_desc){ + .width = 64, + .height = 64, + .usage = SG_USAGE_DYNAMIC, + .sample_count = 1, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = color_img }, + .resolves[0] = { .image = resolve_img }, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_NO_RT); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_resolve_image_sizes) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 4, + }); + const sg_image resolve_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 32, + .height = 32, + .sample_count = 1, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = color_img }, + .resolves[0] = { .image = resolve_img }, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_SIZES); + T(log_items[1] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_SIZES); + T(log_items[2] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_resolve_image_format) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 4, + }); + const sg_image resolve_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .pixel_format = SG_PIXELFORMAT_R8, + .sample_count = 1, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = color_img }, + .resolves[0] = { .image = resolve_img }, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_RESOLVE_IMAGE_FORMAT); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_depth_image) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + }); + const sg_image depth_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .pixel_format = SG_PIXELFORMAT_DEPTH, + }); + sg_destroy_image(depth_img); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = color_img }, + .depth_stencil.image = depth_img, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_depth_miplevel) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + }); + const sg_image depth_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .pixel_format = SG_PIXELFORMAT_DEPTH, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = color_img }, + .depth_stencil = { .image = depth_img, .mip_level = 1 }, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_DEPTH_MIPLEVEL); + // FIXME: these additional validation errors are confusing + T(log_items[1] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SIZES); + T(log_items[2] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SIZES); + T(log_items[3] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_depth_face) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + }); + const sg_image depth_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .type = SG_IMAGETYPE_CUBE, + .width = 64, + .height = 64, + .pixel_format = SG_PIXELFORMAT_DEPTH, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = color_img }, + .depth_stencil = { .image = depth_img, .slice = 6 }, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_DEPTH_FACE); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_depth_layer) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + }); + const sg_image depth_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .type = SG_IMAGETYPE_ARRAY, + .width = 64, + .height = 64, + .num_slices = 4, + .pixel_format = SG_PIXELFORMAT_DEPTH, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = color_img }, + .depth_stencil = { .image = depth_img, .slice = 4 }, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_DEPTH_LAYER); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +// NOTE: VALIDATE_PASSDESC_DEPTH_SLICE can't actually happen because VALIDATE_IMAGEDESC_DEPTH_3D_IMAGE + +// NOTE: VALIDATE_DEPTH_IMAGE_NO_RT can't actually happen because VALIDATE_IMAGEDESC_NONRT_PIXELFORMAT + +UTEST(sokol_gfx, make_attachments_validate_depth_image_sizes) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + }); + const sg_image depth_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 32, + .height = 32, + .pixel_format = SG_PIXELFORMAT_DEPTH, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = color_img }, + .depth_stencil = { .image = depth_img }, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SIZES); + T(log_items[1] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SIZES); + T(log_items[2] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, make_attachments_validate_depth_image_sample_count) { + setup(&(sg_desc){0}); + const sg_image color_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .sample_count = 4, + }); + const sg_image depth_img = sg_make_image(&(sg_image_desc){ + .render_target = true, + .width = 64, + .height = 64, + .pixel_format = SG_PIXELFORMAT_DEPTH, + .sample_count = 2, + }); + const sg_attachments atts = sg_make_attachments(&(sg_attachments_desc){ + .colors[0] = { .image = color_img }, + .depth_stencil = { .image = depth_img }, + }); + T(sg_query_attachments_state(atts) == SG_RESOURCESTATE_FAILED); + T(log_items[0] == SG_LOGITEM_VALIDATE_ATTACHMENTSDESC_DEPTH_IMAGE_SAMPLE_COUNT); + T(log_items[1] == SG_LOGITEM_VALIDATION_FAILED); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_pixelformat_bytesperpixel) { + setup(&(sg_desc){0}); + T(sg_query_pixelformat(SG_PIXELFORMAT_R8).bytes_per_pixel == 1); + T(sg_query_pixelformat(SG_PIXELFORMAT_R8SN).bytes_per_pixel == 1); + T(sg_query_pixelformat(SG_PIXELFORMAT_R8UI).bytes_per_pixel == 1); + T(sg_query_pixelformat(SG_PIXELFORMAT_R8SI).bytes_per_pixel == 1); + T(sg_query_pixelformat(SG_PIXELFORMAT_R16).bytes_per_pixel == 2); + T(sg_query_pixelformat(SG_PIXELFORMAT_R16SN).bytes_per_pixel == 2); + T(sg_query_pixelformat(SG_PIXELFORMAT_R16UI).bytes_per_pixel == 2); + T(sg_query_pixelformat(SG_PIXELFORMAT_R16SI).bytes_per_pixel == 2); + T(sg_query_pixelformat(SG_PIXELFORMAT_R16F).bytes_per_pixel == 2); + T(sg_query_pixelformat(SG_PIXELFORMAT_RG8).bytes_per_pixel == 2); + T(sg_query_pixelformat(SG_PIXELFORMAT_RG8SN).bytes_per_pixel == 2); + T(sg_query_pixelformat(SG_PIXELFORMAT_RG8UI).bytes_per_pixel == 2); + T(sg_query_pixelformat(SG_PIXELFORMAT_RG8SI).bytes_per_pixel == 2); + T(sg_query_pixelformat(SG_PIXELFORMAT_R32UI).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_R32SI).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_R32F).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_RG16).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_RG16SN).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_RG16UI).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_RG16SI).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_RG16F).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_RGBA8).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_SRGB8A8).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_RGBA8SN).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_RGBA8UI).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_RGBA8SI).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_BGRA8).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_RGB10A2).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_RG11B10F).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_RGB9E5).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_RG32UI).bytes_per_pixel == 8); + T(sg_query_pixelformat(SG_PIXELFORMAT_RG32SI).bytes_per_pixel == 8); + T(sg_query_pixelformat(SG_PIXELFORMAT_RG32F).bytes_per_pixel == 8); + T(sg_query_pixelformat(SG_PIXELFORMAT_RGBA16).bytes_per_pixel == 8); + T(sg_query_pixelformat(SG_PIXELFORMAT_RGBA16SN).bytes_per_pixel == 8); + T(sg_query_pixelformat(SG_PIXELFORMAT_RGBA16UI).bytes_per_pixel == 8); + T(sg_query_pixelformat(SG_PIXELFORMAT_RGBA16SI).bytes_per_pixel == 8); + T(sg_query_pixelformat(SG_PIXELFORMAT_RGBA16F).bytes_per_pixel == 8); + T(sg_query_pixelformat(SG_PIXELFORMAT_RGBA32UI).bytes_per_pixel == 16); + T(sg_query_pixelformat(SG_PIXELFORMAT_RGBA32SI).bytes_per_pixel == 16); + T(sg_query_pixelformat(SG_PIXELFORMAT_RGBA32F).bytes_per_pixel == 16); + T(sg_query_pixelformat(SG_PIXELFORMAT_DEPTH).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_DEPTH_STENCIL).bytes_per_pixel == 4); + T(sg_query_pixelformat(SG_PIXELFORMAT_BC1_RGBA).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_BC2_RGBA).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_BC3_RGBA).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_BC4_R).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_BC4_RSN).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_BC5_RG).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_BC5_RGSN).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_BC6H_RGBF).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_BC6H_RGBUF).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_BC7_RGBA).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_PVRTC_RGB_2BPP).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_PVRTC_RGB_4BPP).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_PVRTC_RGBA_2BPP).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_PVRTC_RGBA_4BPP).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_ETC2_RGB8).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_ETC2_RGB8A1).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_ETC2_RGBA8).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_ETC2_RG11).bytes_per_pixel == 0); + T(sg_query_pixelformat(SG_PIXELFORMAT_ETC2_RG11SN).bytes_per_pixel == 0); + sg_shutdown(); +} + +UTEST(sokol_gfx, query_pixelformat_compressed) { + setup(&(sg_desc){0}); + int i = SG_PIXELFORMAT_NONE + 1; + for (; i < SG_PIXELFORMAT_BC1_RGBA; i++) { + T(sg_query_pixelformat((sg_pixel_format)i).compressed == false); + } + for (; i < _SG_PIXELFORMAT_NUM; i++) { + T(sg_query_pixelformat((sg_pixel_format)i).compressed == true); + } + sg_shutdown(); +} + +UTEST(sokol_gfx, query_row_pitch) { + setup(&(sg_desc){0}); + T(sg_query_row_pitch(SG_PIXELFORMAT_R8, 13, 1) == 13); + T(sg_query_row_pitch(SG_PIXELFORMAT_R8, 13, 32) == 32); + T(sg_query_row_pitch(SG_PIXELFORMAT_RG8SN, 256, 16) == 512); + T(sg_query_row_pitch(SG_PIXELFORMAT_RGBA8, 256, 16) == 1024); + T(sg_query_row_pitch(SG_PIXELFORMAT_BC1_RGBA, 1024, 1) == 2048); + T(sg_query_row_pitch(SG_PIXELFORMAT_BC1_RGBA, 1, 1) == 8); + T(sg_query_row_pitch(SG_PIXELFORMAT_DEPTH, 256, 4) == 1024); + T(sg_query_row_pitch(SG_PIXELFORMAT_DEPTH_STENCIL, 256, 4) == 1024); + sg_shutdown(); +} + +UTEST(sokol_gfx, sg_query_surface_pitch) { + setup(&(sg_desc){0}); + T(sg_query_surface_pitch(SG_PIXELFORMAT_R8, 256, 256, 1) == (256 * 256)); + T(sg_query_surface_pitch(SG_PIXELFORMAT_R8, 256, 256, 1024) == (256 * 1024)); + T(sg_query_surface_pitch(SG_PIXELFORMAT_RG8, 1, 1, 1) == 2); + T(sg_query_surface_pitch(SG_PIXELFORMAT_RG8, 256, 256, 4) == (256 * 256 * 2)); + T(sg_query_surface_pitch(SG_PIXELFORMAT_RGBA32F, 256, 256, 1) == (256 * 256 * 16)); + T(sg_query_surface_pitch(SG_PIXELFORMAT_BC1_RGBA, 256, 256, 1) == (256 * 2 * 64)); + T(sg_query_surface_pitch(SG_PIXELFORMAT_BC1_RGBA, 256, 1, 1) == (256 * 2)); + T(sg_query_surface_pitch(SG_PIXELFORMAT_BC1_RGBA, 256, 2, 1) == (256 * 2)); + T(sg_query_surface_pitch(SG_PIXELFORMAT_BC1_RGBA, 256, 3, 1) == (256 * 2)); + T(sg_query_surface_pitch(SG_PIXELFORMAT_BC1_RGBA, 256, 4, 1) == (256 * 2)); + T(sg_query_surface_pitch(SG_PIXELFORMAT_BC1_RGBA, 256, 5, 1) == (256 * 2 * 2)); + sg_shutdown(); +} diff --git a/source/engine/thirdparty/sokol/tests/functional/sokol_gl_test.c b/source/engine/thirdparty/sokol/tests/functional/sokol_gl_test.c new file mode 100644 index 0000000..4fe4f45 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/sokol_gl_test.c @@ -0,0 +1,323 @@ +//------------------------------------------------------------------------------ +// sokol-gl-test.c +//------------------------------------------------------------------------------ +#include "sokol_gfx.h" +#define SOKOL_GL_IMPL +#include "sokol_gl.h" +#include "utest.h" +#include + +#define T(b) EXPECT_TRUE(b) +#define TFLT(f0,f1,epsilon) {T(fabs((f0)-(f1))<=(epsilon));} + +static void init(void) { + sg_setup(&(sg_desc){0}); + sgl_setup(&(sgl_desc_t){0}); +} + +static void shutdown(void) { + sgl_shutdown(); + sg_shutdown(); +} + +UTEST(sokol_gl, default_init_shutdown) { + init(); + T(_sgl.init_cookie == _SGL_INIT_COOKIE); + T(_sgl.def_ctx_id.id == SGL_DEFAULT_CONTEXT.id); + T(_sgl.cur_ctx_id.id == _sgl.def_ctx_id.id); + T(_sgl.cur_ctx); + T(_sgl.cur_ctx->vertices.cap == 65536); + T(_sgl.cur_ctx->commands.cap == 16384); + T(_sgl.cur_ctx->uniforms.cap == 16384); + T(_sgl.cur_ctx->vertices.next == 0); + T(_sgl.cur_ctx->commands.next == 0); + T(_sgl.cur_ctx->uniforms.next == 0); + T(_sgl.cur_ctx->vertices.ptr != 0); + T(_sgl.cur_ctx->uniforms.ptr != 0); + T(_sgl.cur_ctx->commands.ptr != 0); + T(_sgl.cur_ctx->error == SGL_NO_ERROR); + T(!_sgl.cur_ctx->in_begin); + T(_sgl.cur_ctx->def_pip.id != SG_INVALID_ID); + T(_sgl.pip_pool.pool.size == (_SGL_DEFAULT_PIPELINE_POOL_SIZE + 1)); + TFLT(_sgl.cur_ctx->u, 0.0f, FLT_MIN); + TFLT(_sgl.cur_ctx->v, 0.0f, FLT_MIN); + T(_sgl.cur_ctx->rgba == 0xFFFFFFFF); + T(_sgl.cur_ctx->cur_img.id == _sgl.def_img.id); + shutdown(); +} + +UTEST(sokol_gl, viewport) { + init(); + sgl_viewport(1, 2, 3, 4, true); + T(_sgl.cur_ctx->commands.next == 1); + T(_sgl.cur_ctx->commands.ptr[0].cmd == SGL_COMMAND_VIEWPORT); + T(_sgl.cur_ctx->commands.ptr[0].args.viewport.x == 1); + T(_sgl.cur_ctx->commands.ptr[0].args.viewport.y == 2); + T(_sgl.cur_ctx->commands.ptr[0].args.viewport.w == 3); + T(_sgl.cur_ctx->commands.ptr[0].args.viewport.h == 4); + T(_sgl.cur_ctx->commands.ptr[0].args.viewport.origin_top_left); + sgl_viewport(5, 6, 7, 8, false); + T(_sgl.cur_ctx->commands.next == 2); + T(_sgl.cur_ctx->commands.ptr[1].cmd == SGL_COMMAND_VIEWPORT); + T(_sgl.cur_ctx->commands.ptr[1].args.viewport.x == 5); + T(_sgl.cur_ctx->commands.ptr[1].args.viewport.y == 6); + T(_sgl.cur_ctx->commands.ptr[1].args.viewport.w == 7); + T(_sgl.cur_ctx->commands.ptr[1].args.viewport.h == 8); + T(!_sgl.cur_ctx->commands.ptr[1].args.viewport.origin_top_left); + shutdown(); +} + +UTEST(sokol_gl, scissor_rect) { + init(); + sgl_scissor_rect(10, 20, 30, 40, true); + T(_sgl.cur_ctx->commands.next == 1); + T(_sgl.cur_ctx->commands.ptr[0].cmd == SGL_COMMAND_SCISSOR_RECT); + T(_sgl.cur_ctx->commands.ptr[0].args.scissor_rect.x == 10); + T(_sgl.cur_ctx->commands.ptr[0].args.scissor_rect.y == 20); + T(_sgl.cur_ctx->commands.ptr[0].args.scissor_rect.w == 30); + T(_sgl.cur_ctx->commands.ptr[0].args.scissor_rect.h == 40); + T(_sgl.cur_ctx->commands.ptr[0].args.scissor_rect.origin_top_left); + sgl_scissor_rect(50, 60, 70, 80, false); + T(_sgl.cur_ctx->commands.next == 2); + T(_sgl.cur_ctx->commands.ptr[1].cmd == SGL_COMMAND_SCISSOR_RECT); + T(_sgl.cur_ctx->commands.ptr[1].args.scissor_rect.x == 50); + T(_sgl.cur_ctx->commands.ptr[1].args.scissor_rect.y == 60); + T(_sgl.cur_ctx->commands.ptr[1].args.scissor_rect.w == 70); + T(_sgl.cur_ctx->commands.ptr[1].args.scissor_rect.h == 80); + T(!_sgl.cur_ctx->commands.ptr[1].args.scissor_rect.origin_top_left); + shutdown(); +} + +UTEST(sokol_gl, texture) { + init(); + T(_sgl.cur_ctx->cur_img.id == _sgl.def_img.id); + uint32_t pixels[64] = { 0 }; + sg_image img = sg_make_image(&(sg_image_desc){ + .type = SG_IMAGETYPE_2D, + .width = 8, + .height = 8, + .data.subimage[0][0] = SG_RANGE(pixels), + }); + sg_sampler smp = sg_make_sampler(&(sg_sampler_desc){0}); + sgl_texture(img, smp); + T(_sgl.cur_ctx->cur_img.id == img.id); + T(_sgl.cur_ctx->cur_smp.id == smp.id); + shutdown(); +} + +UTEST(sokol_gl, texture_image_nosampler) { + init(); + T(_sgl.cur_ctx->cur_img.id == _sgl.def_img.id); + uint32_t pixels[64] = { 0 }; + sg_image img = sg_make_image(&(sg_image_desc){ + .type = SG_IMAGETYPE_2D, + .width = 8, + .height = 8, + .data.subimage[0][0] = SG_RANGE(pixels), + }); + sgl_texture(img, (sg_sampler){0}); + T(_sgl.cur_ctx->cur_img.id == img.id); + T(_sgl.cur_ctx->cur_smp.id == _sgl.def_smp.id); + shutdown(); +} + +UTEST(sokol_gl, texture_noimage_sampler) { + init(); + T(_sgl.cur_ctx->cur_img.id == _sgl.def_img.id); + sg_sampler smp = sg_make_sampler(&(sg_sampler_desc){0}); + sgl_texture((sg_image){0}, smp); + T(_sgl.cur_ctx->cur_img.id == _sgl.def_img.id); + T(_sgl.cur_ctx->cur_smp.id == smp.id); + shutdown(); +} + +UTEST(sokol_gl, texture_noimage_nosampler) { + init(); + T(_sgl.cur_ctx->cur_img.id == _sgl.def_img.id); + sgl_texture((sg_image){0}, (sg_sampler){0}); + T(_sgl.cur_ctx->cur_img.id == _sgl.def_img.id); + T(_sgl.cur_ctx->cur_smp.id == _sgl.def_smp.id); + shutdown(); +} +UTEST(sokol_gl, begin_end) { + init(); + sgl_begin_triangles(); + sgl_v3f(1.0f, 2.0f, 3.0f); + sgl_v3f(4.0f, 5.0f, 6.0f); + sgl_v3f(7.0f, 8.0f, 9.0f); + sgl_end(); + T(_sgl.cur_ctx->base_vertex == 0); + T(_sgl.cur_ctx->vertices.next == 3); + T(_sgl.cur_ctx->commands.next == 1); + T(_sgl.cur_ctx->uniforms.next == 1); + T(_sgl.cur_ctx->commands.ptr[0].cmd == SGL_COMMAND_DRAW); + T(_sgl.cur_ctx->commands.ptr[0].args.draw.pip.id == _sgl_pipeline_at(_sgl.cur_ctx->def_pip.id)->pip[SGL_PRIMITIVETYPE_TRIANGLES].id); + T(_sgl.cur_ctx->commands.ptr[0].args.draw.base_vertex == 0); + T(_sgl.cur_ctx->commands.ptr[0].args.draw.num_vertices == 3); + T(_sgl.cur_ctx->commands.ptr[0].args.draw.uniform_index == 0); + shutdown(); +} + +UTEST(sokol_gl, matrix_mode) { + init(); + sgl_matrix_mode_modelview(); T(_sgl.cur_ctx->cur_matrix_mode == SGL_MATRIXMODE_MODELVIEW); + sgl_matrix_mode_projection(); T(_sgl.cur_ctx->cur_matrix_mode == SGL_MATRIXMODE_PROJECTION); + sgl_matrix_mode_texture(); T(_sgl.cur_ctx->cur_matrix_mode == SGL_MATRIXMODE_TEXTURE); + shutdown(); +} + +UTEST(sokol_gl, load_identity) { + init(); + sgl_load_identity(); + const _sgl_matrix_t* m = _sgl_matrix_modelview(_sgl.cur_ctx); + TFLT(m->v[0][0], 1.0f, FLT_MIN); TFLT(m->v[0][1], 0.0f, FLT_MIN); TFLT(m->v[0][2], 0.0f, FLT_MIN); TFLT(m->v[0][3], 0.0f, FLT_MIN); + TFLT(m->v[1][0], 0.0f, FLT_MIN); TFLT(m->v[1][1], 1.0f, FLT_MIN); TFLT(m->v[1][2], 0.0f, FLT_MIN); TFLT(m->v[1][3], 0.0f, FLT_MIN); + TFLT(m->v[2][0], 0.0f, FLT_MIN); TFLT(m->v[2][1], 0.0f, FLT_MIN); TFLT(m->v[2][2], 1.0f, FLT_MIN); TFLT(m->v[2][3], 0.0f, FLT_MIN); + TFLT(m->v[3][0], 0.0f, FLT_MIN); TFLT(m->v[3][1], 0.0f, FLT_MIN); TFLT(m->v[3][2], 0.0f, FLT_MIN); TFLT(m->v[3][3], 1.0f, FLT_MIN); + shutdown(); +} + +UTEST(sokol_gl, load_matrix) { + init(); + const float m[16] = { + 0.5f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.5f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + 2.0f, 3.0f, 4.0f, 1.0f + }; + sgl_load_matrix(m); + const _sgl_matrix_t* m0 = _sgl_matrix_modelview(_sgl.cur_ctx); + TFLT(m0->v[0][0], 0.5f, FLT_MIN); + TFLT(m0->v[1][1], 0.5f, FLT_MIN); + TFLT(m0->v[2][2], 0.5f, FLT_MIN); + TFLT(m0->v[3][0], 2.0f, FLT_MIN); + TFLT(m0->v[3][1], 3.0f, FLT_MIN); + TFLT(m0->v[3][2], 4.0f, FLT_MIN); + TFLT(m0->v[3][3], 1.0f, FLT_MIN); + sgl_load_transpose_matrix(m); + const _sgl_matrix_t* m1 = _sgl_matrix_modelview(_sgl.cur_ctx); + TFLT(m1->v[0][0], 0.5f, FLT_MIN); + TFLT(m1->v[1][1], 0.5f, FLT_MIN); + TFLT(m1->v[2][2], 0.5f, FLT_MIN); + TFLT(m1->v[0][3], 2.0f, FLT_MIN); + TFLT(m1->v[1][3], 3.0f, FLT_MIN); + TFLT(m1->v[2][3], 4.0f, FLT_MIN); + TFLT(m1->v[3][3], 1.0f, FLT_MIN); + shutdown(); +} + +UTEST(sokol_gl, make_destroy_pipelines) { + sg_setup(&(sg_desc){0}); + sgl_setup(&(sgl_desc_t){ + /* one pool slot is used by soko-gl itself */ + .pipeline_pool_size = 4 + }); + + sgl_pipeline pip[3] = { {0} }; + sg_pipeline_desc desc = { + .depth = { + .write_enabled = true, + .compare = SG_COMPAREFUNC_LESS_EQUAL + } + }; + for (int i = 0; i < 3; i++) { + pip[i] = sgl_make_pipeline(&desc); + T(pip[i].id != SG_INVALID_ID); + T((2-i) == _sgl.pip_pool.pool.queue_top); + const _sgl_pipeline_t* pip_ptr = _sgl_lookup_pipeline(pip[i].id); + T(pip_ptr); + T(pip_ptr->slot.id == pip[i].id); + T(pip_ptr->slot.state == SG_RESOURCESTATE_VALID); + } + /* trying to create another one fails because buffer is exhausted */ + T(sgl_make_pipeline(&desc).id == SG_INVALID_ID); + + for (int i = 0; i < 3; i++) { + sgl_destroy_pipeline(pip[i]); + T(0 == _sgl_lookup_pipeline(pip[i].id)); + const _sgl_pipeline_t* pip_ptr = _sgl_pipeline_at(pip[i].id); + T(pip_ptr); + T(pip_ptr->slot.id == SG_INVALID_ID); + T(pip_ptr->slot.state == SG_RESOURCESTATE_INITIAL); + T((i+1) == _sgl.pip_pool.pool.queue_top); + } + sgl_shutdown(); + sg_shutdown(); +} + +UTEST(sokol_gl, make_destroy_contexts) { + init(); + sgl_context ctx = sgl_make_context(&(sgl_context_desc_t){ + .max_vertices = 1024, + .max_commands = 256, + .color_format = SG_PIXELFORMAT_RG8, + .depth_format = SG_PIXELFORMAT_NONE, + .sample_count = 4, + }); + T(ctx.id != SG_INVALID_ID); + T(ctx.id != SGL_DEFAULT_CONTEXT.id); + // creating a context should not change the current context + T(ctx.id != _sgl.cur_ctx_id.id); + sgl_set_context(ctx); + T(_sgl.cur_ctx->vertices.cap == 1024); + T(_sgl.cur_ctx->commands.cap == 256); + T(_sgl.cur_ctx->uniforms.cap == 256); + T(ctx.id == _sgl.cur_ctx_id.id); + T(sgl_get_context().id == ctx.id); + sgl_set_context(SGL_DEFAULT_CONTEXT); + T(sgl_get_context().id == SGL_DEFAULT_CONTEXT.id); + sgl_destroy_context(ctx); + shutdown(); +} + +UTEST(sokol_gl, destroy_active_context) { + init(); + sgl_context ctx = sgl_make_context(&(sgl_context_desc_t){ + .max_vertices = 1024, + .max_commands = 256, + .color_format = SG_PIXELFORMAT_RG8, + .depth_format = SG_PIXELFORMAT_NONE, + .sample_count = 4, + }); + sgl_set_context(ctx); + sgl_destroy_context(ctx); + T(_sgl.cur_ctx == 0); + T(sgl_error() == SGL_ERROR_NO_CONTEXT); + shutdown(); +} + +UTEST(sokol_gl, context_pipeline) { + init(); + sgl_context ctx1 = sgl_make_context(&(sgl_context_desc_t){ + .max_vertices = 1024, + .max_commands = 256, + .color_format = SG_PIXELFORMAT_R8, + .depth_format = SG_PIXELFORMAT_NONE, + .sample_count = 4, + }); + sgl_context ctx2 = sgl_make_context(&(sgl_context_desc_t){ + .max_vertices = 1024, + .max_commands = 256, + .color_format = SG_PIXELFORMAT_RG8, + .depth_format = SG_PIXELFORMAT_NONE, + .sample_count = 2, + }); + sgl_set_context(ctx1); + sgl_pipeline pip1 = sgl_make_pipeline(&(sg_pipeline_desc){ + .colors[0].blend.enabled = true, + }); + T(pip1.id != SG_INVALID_ID); + // FIXME: currently sg_query_pipeline_info() doesn't provide enough information + + sgl_pipeline pip2 = sgl_context_make_pipeline(ctx2, &(sg_pipeline_desc){ + .alpha_to_coverage_enabled = true, + }); + T(pip2.id != SG_INVALID_ID); + shutdown(); +} + +UTEST(sokol_gl, default_context) { + init(); + T(sgl_default_context().id == SGL_DEFAULT_CONTEXT.id); + shutdown(); +} diff --git a/source/engine/thirdparty/sokol/tests/functional/sokol_log_test.c b/source/engine/thirdparty/sokol/tests/functional/sokol_log_test.c new file mode 100644 index 0000000..1ed21ef --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/sokol_log_test.c @@ -0,0 +1,2 @@ +#define SOKOL_LOG_IMPL +#include "sokol_log.h" diff --git a/source/engine/thirdparty/sokol/tests/functional/sokol_shape_test.c b/source/engine/thirdparty/sokol/tests/functional/sokol_shape_test.c new file mode 100644 index 0000000..8fc84b1 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/sokol_shape_test.c @@ -0,0 +1,421 @@ +//------------------------------------------------------------------------------ +// sokol-shape-test.c +//------------------------------------------------------------------------------ +#include "sokol_gfx.h" +#define SOKOL_SHAPE_IMPL +#include "sokol_shape.h" +#include "utest.h" + +#define T(b) EXPECT_TRUE(b) +#define TFLT(f0,f1,epsilon) {T(fabs((f0)-(f1))<=(epsilon));} + +UTEST(sokol_shape, color4f) { + T(sshape_color_4f(1.0f, 0.0f, 0.0f, 0.0f) == 0x000000FF); + T(sshape_color_4f(0.0f, 1.0f, 0.0f, 0.0f) == 0x0000FF00); + T(sshape_color_4f(0.0f, 0.0f, 1.0f, 0.0f) == 0x00FF0000); + T(sshape_color_4f(0.0f, 0.0f, 0.0f, 1.0f) == 0xFF000000); +} + +UTEST(sokol_shape, color3f) { + T(sshape_color_3f(1.0f, 0.0f, 0.0f) == 0xFF0000FF); + T(sshape_color_3f(0.0f, 1.0f, 0.0f) == 0xFF00FF00); + T(sshape_color_3f(0.0f, 0.0f, 1.0f) == 0xFFFF0000); +} + +UTEST(sokol_shape, color4b) { + T(sshape_color_4b(255, 0, 0, 0) == 0x000000FF); + T(sshape_color_4b(0, 255, 0, 0) == 0x0000FF00); + T(sshape_color_4b(0, 0, 255, 0) == 0x00FF0000); + T(sshape_color_4b(0, 0, 0, 255) == 0xFF000000); +} + +UTEST(sokol_shape, color3b) { + T(sshape_color_3b(255, 0, 0) == 0xFF0000FF); + T(sshape_color_3b(0, 255, 0) == 0xFF00FF00); + T(sshape_color_3b(0, 0, 255) == 0xFFFF0000); +} + +UTEST(sokol_shape, mat4) { + float values[16] = { + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f + }; + sshape_mat4_t m = sshape_mat4(values); + T(m.m[0][0] == 1.0f); + T(m.m[0][1] == 2.0f); + T(m.m[0][2] == 3.0f); + T(m.m[0][3] == 4.0f); + T(m.m[1][0] == 5.0f); + T(m.m[1][1] == 6.0f); + T(m.m[1][2] == 7.0f); + T(m.m[1][3] == 8.0f); + T(m.m[2][0] == 9.0f); + T(m.m[2][1] == 10.0f); + T(m.m[2][2] == 11.0f); + T(m.m[2][3] == 12.0f); + T(m.m[3][0] == 13.0f); + T(m.m[3][1] == 14.0f); + T(m.m[3][2] == 15.0f); + T(m.m[3][3] == 16.0f); +} + +UTEST(sokol_shape, mat4_transpose) { + float values[16] = { + 1.0f, 2.0f, 3.0f, 4.0f, + 5.0f, 6.0f, 7.0f, 8.0f, + 9.0f, 10.0f, 11.0f, 12.0f, + 13.0f, 14.0f, 15.0f, 16.0f + }; + sshape_mat4_t m = sshape_mat4_transpose(values); + T(m.m[0][0] == 1.0f); + T(m.m[1][0] == 2.0f); + T(m.m[2][0] == 3.0f); + T(m.m[3][0] == 4.0f); + T(m.m[0][1] == 5.0f); + T(m.m[1][1] == 6.0f); + T(m.m[2][1] == 7.0f); + T(m.m[3][1] == 8.0f); + T(m.m[0][2] == 9.0f); + T(m.m[1][2] == 10.0f); + T(m.m[2][2] == 11.0f); + T(m.m[3][2] == 12.0f); + T(m.m[0][3] == 13.0f); + T(m.m[1][3] == 14.0f); + T(m.m[2][3] == 15.0f); + T(m.m[3][3] == 16.0f); +} + +UTEST(sokol_shape, plane_buffer_sizes) { + sshape_sizes_t res; + + res = sshape_plane_sizes(1); + T(4 == res.vertices.num); + T(6 == res.indices.num); + T(res.vertices.num * sizeof(sshape_vertex_t) == res.vertices.size); + T(res.indices.num * sizeof(uint16_t) == res.indices.size); + + res = sshape_plane_sizes(2); + T(9 == res.vertices.num); + T(24 == res.indices.num); + T(res.vertices.num * sizeof(sshape_vertex_t) == res.vertices.size); + T(res.indices.num * sizeof(uint16_t) == res.indices.size); +} + +UTEST(sokol_shape, box_buffer_sizes) { + sshape_sizes_t res; + + res = sshape_box_sizes(1); + T(24 == res.vertices.num); + T(36 == res.indices.num); + T(res.vertices.num * sizeof(sshape_vertex_t) == res.vertices.size); + T(res.indices.num * sizeof(uint16_t) == res.indices.size); + + res = sshape_box_sizes(2); + T(54 == res.vertices.num); + T(144 == res.indices.num); + T(res.vertices.num * sizeof(sshape_vertex_t) == res.vertices.size); + T(res.indices.num * sizeof(uint16_t) == res.indices.size); +} + +UTEST(sokol_shape, sphere_buffer_sizes) { + sshape_sizes_t res; + + res = sshape_sphere_sizes(3, 2); + T(12 == res.vertices.num); + T(18 == res.indices.num); + T(res.vertices.num * sizeof(sshape_vertex_t) == res.vertices.size); + T(res.indices.num * sizeof(uint16_t) == res.indices.size); + + res = sshape_sphere_sizes(36, 12); + T(481 == res.vertices.num); + T(2376 == res.indices.num); + T(res.vertices.num * sizeof(sshape_vertex_t) == res.vertices.size); + T(res.indices.num * sizeof(uint16_t) == res.indices.size); +} + +UTEST(sokol_shape, cylinder_buffer_sizes) { + sshape_sizes_t res; + + res = sshape_cylinder_sizes(3, 1); + T(24 == res.vertices.num); + T(36 == res.indices.num); + T(res.vertices.num * sizeof(sshape_vertex_t) == res.vertices.size); + T(res.indices.num * sizeof(uint16_t) == res.indices.size); + + res = sshape_cylinder_sizes(5, 2); + T(42 == res.vertices.num); + T(90 == res.indices.num); + T(res.vertices.num * sizeof(sshape_vertex_t) == res.vertices.size); + T(res.indices.num * sizeof(uint16_t) == res.indices.size); +} + +UTEST(sokol_shape, torus_buffer_sizes) { + sshape_sizes_t res; + + res = sshape_torus_sizes(3, 3); + T(16 == res.vertices.num); + T(54 == res.indices.num); + T(res.vertices.num * sizeof(sshape_vertex_t) == res.vertices.size); + T(res.indices.num * sizeof(uint16_t) == res.indices.size); + + res = sshape_torus_sizes(4, 5); + T(30 == res.vertices.num); + T(120 == res.indices.num); + T(res.vertices.num * sizeof(sshape_vertex_t) == res.vertices.size); + T(res.indices.num * sizeof(uint16_t) == res.indices.size); +} + +UTEST(sokol_shape, buffer_layout_desc) { + const sg_vertex_buffer_layout_state l_state = sshape_vertex_buffer_layout_state(); + T(sizeof(sshape_vertex_t) == l_state.stride); + T(0 == l_state.step_func); + T(0 == l_state.step_rate); +} + +UTEST(sokol_shape, attr_descs) { + { + const sg_vertex_attr_state a_state = sshape_position_vertex_attr_state(); + T(offsetof(sshape_vertex_t, x) == a_state.offset); + T(SG_VERTEXFORMAT_FLOAT3 == a_state.format); + T(0 == a_state.buffer_index); + } + { + const sg_vertex_attr_state a_state = sshape_normal_vertex_attr_state(); + T(offsetof(sshape_vertex_t, normal) == a_state.offset); + T(SG_VERTEXFORMAT_BYTE4N == a_state.format); + T(0 == a_state.buffer_index); + } + { + const sg_vertex_attr_state a_state = sshape_texcoord_vertex_attr_state(); + T(offsetof(sshape_vertex_t, u) == a_state.offset); + T(SG_VERTEXFORMAT_USHORT2N == a_state.format); + T(0 == a_state.buffer_index); + } + { + const sg_vertex_attr_state a_state = sshape_color_vertex_attr_state(); + T(offsetof(sshape_vertex_t, color) == a_state.offset); + T(SG_VERTEXFORMAT_UBYTE4N == a_state.format); + T(0 == a_state.buffer_index); + } +} + +UTEST(sokol_shape, buffer_descs_elm_range) { + sshape_vertex_t vx[128] = { 0 }; + uint16_t ix[128] = { 0 }; + sshape_buffer_t buf = { + .vertices.buffer = SSHAPE_RANGE(vx), + .indices.buffer = SSHAPE_RANGE(ix), + }; + + // build a box... + { + buf = sshape_build_box(&buf, &(sshape_box_t) {0}); + const sg_buffer_desc vbuf_desc = sshape_vertex_buffer_desc(&buf); + const sg_buffer_desc ibuf_desc = sshape_index_buffer_desc(&buf); + const sshape_element_range_t elm_range = sshape_element_range(&buf); + T(vbuf_desc.size == 0); + T(vbuf_desc.type == SG_BUFFERTYPE_VERTEXBUFFER); + T(vbuf_desc.usage == SG_USAGE_IMMUTABLE); + T(vbuf_desc.data.ptr == vx); + T(vbuf_desc.data.size == 24 * sizeof(sshape_vertex_t)); + T(ibuf_desc.size == 0); + T(ibuf_desc.type == SG_BUFFERTYPE_INDEXBUFFER); + T(ibuf_desc.usage == SG_USAGE_IMMUTABLE); + T(ibuf_desc.data.ptr == ix); + T(ibuf_desc.data.size == 36 * sizeof(uint16_t)); + T(elm_range.base_element == 0); + T(elm_range.num_elements == 36); + } + + // append a plane... + { + buf = sshape_build_plane(&buf, &(sshape_plane_t) {0}); + const sg_buffer_desc vbuf_desc = sshape_vertex_buffer_desc(&buf); + const sg_buffer_desc ibuf_desc = sshape_index_buffer_desc(&buf); + const sshape_element_range_t elm_range = sshape_element_range(&buf); + T(vbuf_desc.size == 0); + T(vbuf_desc.type == SG_BUFFERTYPE_VERTEXBUFFER); + T(vbuf_desc.usage == SG_USAGE_IMMUTABLE); + T(vbuf_desc.data.ptr == vx); + T(vbuf_desc.data.size == 28 * sizeof(sshape_vertex_t)); + T(ibuf_desc.size == 0); + T(ibuf_desc.type == SG_BUFFERTYPE_INDEXBUFFER); + T(ibuf_desc.usage == SG_USAGE_IMMUTABLE); + T(ibuf_desc.data.ptr == ix); + T(ibuf_desc.data.size == 42 * sizeof(uint16_t)); + T(elm_range.base_element == 36); + T(elm_range.num_elements == 6); + } +} + +UTEST(sokol_shape, build_plane_defaults) { + sshape_vertex_t vx[64] = { 0 }; + uint16_t ix[64] = { 0 }; + + sshape_buffer_t buf = { + .vertices.buffer = SSHAPE_RANGE(vx), + .indices.buffer = SSHAPE_RANGE(ix), + }; + buf = sshape_build_plane(&buf, &(sshape_plane_t) { 0 }); + + T(buf.valid); + T(0 == buf.vertices.shape_offset); + T(4 * sizeof(sshape_vertex_t) == buf.vertices.data_size); + T(0 == buf.indices.shape_offset); + T(6 * sizeof(uint16_t) == buf.indices.data_size); + for (int i = 0; i < 4; i++) { + T(vx[i].color == 0xFFFFFFFF); + } + T(ix[0] == 0); + T(ix[1] == 1); + T(ix[2] == 3); + T(ix[3] == 0); + T(ix[4] == 3); + T(ix[5] == 2); +} + +UTEST(sokol_shape, build_plane_validate) { + sshape_vertex_t vx[64] = { 0 }; + uint16_t ix[64] = { 0 }; + const sshape_plane_t params = { 0 }; + + // vertex buffer too small + { + sshape_buffer_t buf = { + .vertices.buffer = { .ptr = vx, .size = 3 * sizeof(sshape_vertex_t) }, + .indices.buffer = SSHAPE_RANGE(ix), + }; + T(!sshape_build_plane(&buf, ¶ms).valid); + } + + // index buffer too small + { + sshape_buffer_t buf = { + .vertices.buffer = SSHAPE_RANGE(vx), + .indices.buffer = { .ptr = ix, .size = 5 * sizeof(uint16_t) } + }; + T(!sshape_build_plane(&buf, ¶ms).valid); + } + // just the right size + { + sshape_buffer_t buf = { + .vertices.buffer = { .ptr = vx, .size = 4 * sizeof(sshape_vertex_t) }, + .indices.buffer = { .ptr = ix, .size = 6 * sizeof(uint16_t) } + }; + T(sshape_build_plane(&buf, ¶ms).valid); + } + + // too small for two planes + { + sshape_buffer_t buf = { + .vertices.buffer = { .ptr = vx, .size = 5 * sizeof(sshape_vertex_t) }, + .indices.buffer = { .ptr = ix, .size = 7 * sizeof(uint16_t) } + }; + buf = sshape_build_plane(&buf, ¶ms); + T(buf.valid); + buf = sshape_build_plane(&buf, ¶ms); + T(!buf.valid); + } + + // just the right size for two planes + { + sshape_buffer_t buf = { + .vertices.buffer = { .ptr = vx, .size = 8 * sizeof(sshape_vertex_t) }, + .indices.buffer = { .ptr = ix, .size = 12 * sizeof(uint16_t) } + }; + buf = sshape_build_plane(&buf, ¶ms); + T(buf.valid); + T(buf.vertices.shape_offset == 0); + T(buf.vertices.data_size == 4 * sizeof(sshape_vertex_t)); + T(buf.indices.shape_offset == 0); + T(buf.indices.data_size == 6 * sizeof(uint16_t)); + buf = sshape_build_plane(&buf, ¶ms); + T(buf.valid); + T(buf.vertices.shape_offset == 4 * sizeof(sshape_vertex_t)); + T(buf.vertices.data_size == 8 * sizeof(sshape_vertex_t)); + T(buf.indices.shape_offset == 6 * sizeof(uint16_t)); + T(buf.indices.data_size == 12 * sizeof(uint16_t)); + } +} + +UTEST(sokol_shape, build_box_defaults) { + sshape_vertex_t vx[128] = { 0 }; + uint16_t ix[128] = { 0 }; + + sshape_buffer_t buf = { + .vertices.buffer = SSHAPE_RANGE(vx), + .indices.buffer = SSHAPE_RANGE(ix), + }; + buf = sshape_build_box(&buf, &(sshape_box_t) { .color = 0xFF0000FF }); + T(buf.valid); + T(buf.vertices.buffer.ptr == vx); + T(buf.vertices.buffer.size == sizeof(vx)); + T(buf.indices.buffer.ptr == ix); + T(buf.indices.buffer.size == sizeof(ix)); + T(buf.vertices.shape_offset == 0); + T(buf.vertices.data_size == 24 * sizeof(sshape_vertex_t)); + T(buf.indices.shape_offset == 0); + T(buf.indices.data_size == 36 * sizeof(uint16_t)); +} + +UTEST(sokol_shape, build_sphere_defaults) { + sshape_vertex_t vx[128] = { 0 }; + uint16_t ix[128] = { 0 }; + + sshape_buffer_t buf = { + .vertices.buffer = SSHAPE_RANGE(vx), + .indices.buffer = SSHAPE_RANGE(ix), + }; + buf = sshape_build_sphere(&buf, &(sshape_sphere_t) { .color = 0xFF0000FF }); + T(buf.valid); + T(buf.vertices.buffer.ptr == vx); + T(buf.vertices.buffer.size == sizeof(vx)); + T(buf.indices.buffer.ptr == ix); + T(buf.indices.buffer.size == sizeof(ix)); + T(buf.vertices.shape_offset == 0); + T(buf.vertices.data_size == 30 * sizeof(sshape_vertex_t)); + T(buf.indices.shape_offset == 0); + T(buf.indices.data_size == 90 * sizeof(uint16_t)); +} + +UTEST(sokol_shape, build_cylinder_defaults) { + sshape_vertex_t vx[128] = { 0 }; + uint16_t ix[128] = { 0 }; + + sshape_buffer_t buf = { + .vertices.buffer = SSHAPE_RANGE(vx), + .indices.buffer = SSHAPE_RANGE(ix) + }; + buf = sshape_build_cylinder(&buf, &(sshape_cylinder_t) { .color = 0xFF0000FF }); + T(buf.valid); + T(buf.vertices.buffer.ptr == vx); + T(buf.vertices.buffer.size == sizeof(vx)); + T(buf.indices.buffer.ptr == ix); + T(buf.indices.buffer.size == sizeof(ix)); + T(buf.vertices.shape_offset == 0); + T(buf.vertices.data_size == 36 * sizeof(sshape_vertex_t)); + T(buf.indices.shape_offset == 0); + T(buf.indices.data_size == 60 * sizeof(uint16_t)); +} + +UTEST(sokol_shape, build_torus_defaults) { + sshape_vertex_t vx[128] = { 0 }; + uint16_t ix[256] = { 0 }; + + sshape_buffer_t buf = { + .vertices.buffer = SSHAPE_RANGE(vx), + .indices.buffer = SSHAPE_RANGE(ix), + }; + buf = sshape_build_torus(&buf, &(sshape_torus_t) { .color = 0xFF0000FF }); + T(buf.valid); + T(buf.vertices.buffer.ptr == vx); + T(buf.vertices.buffer.size == sizeof(vx)); + T(buf.indices.buffer.ptr == ix); + T(buf.indices.buffer.size == sizeof(ix)); + T(buf.vertices.shape_offset == 0); + T(buf.vertices.data_size == 36 * sizeof(sshape_vertex_t)); + T(buf.indices.shape_offset == 0); + T(buf.indices.data_size == 150 * sizeof(uint16_t)); +} diff --git a/source/engine/thirdparty/sokol/tests/functional/sokol_spine_test.c b/source/engine/thirdparty/sokol/tests/functional/sokol_spine_test.c new file mode 100644 index 0000000..435b32b --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/sokol_spine_test.c @@ -0,0 +1,1197 @@ +//------------------------------------------------------------------------------ +// sokol_spine_test.c +//------------------------------------------------------------------------------ +#include "sokol_gfx.h" +#define SOKOL_SPINE_IMPL +#include "spine/spine.h" +#include "sokol_spine.h" +#include "utest.h" + +#define T(b) EXPECT_TRUE(b) + +static sspine_log_item last_logitem = SSPINE_LOGITEM_OK; +static void log_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) { + (void)tag; (void)log_level; (void)message; (void)line_nr; (void)filename; (void)user_data; + last_logitem = log_item; +} + +static void init(void) { + last_logitem = SSPINE_LOGITEM_OK; + sg_setup(&(sg_desc){0}); + sspine_setup(&(sspine_desc){ .logger = { .func = log_func } }); +} + +static void init_with_desc(const sspine_desc* desc) { + last_logitem = SSPINE_LOGITEM_OK; + sspine_desc desc1 = *desc; + desc1.logger.func = log_func; + sg_setup(&(sg_desc){0}); + sspine_setup(&desc1); +} + +static void shutdown(void) { + sspine_shutdown(); + sg_shutdown(); +} + +// NOTE: this guarantees that the data is zero terminated because the loaded data +// might either be binary or text (the zero sentinel is NOT counted in the returned size) +static sspine_range load_data(const char* path) { + assert(path); + FILE* fp = fopen(path, "rb"); + assert(fp); + fseek(fp, 0, SEEK_END); + const size_t size = (size_t)ftell(fp); + fseek(fp, 0, SEEK_SET); + // room for terminating zero + const size_t alloc_size = size + 1; + uint8_t* ptr = (uint8_t*)malloc(alloc_size); + memset(ptr, 0, alloc_size); + // NOTE: GCC warns if result of fread() is ignored + size_t num_bytes = fread(ptr, size, 1, fp); + (void)num_bytes; + fclose(fp); + return (sspine_range) { .ptr = ptr, .size = size }; +} + +static void free_data(sspine_range r) { + free((void*)r.ptr); +} + +static sspine_atlas create_atlas(void) { + sspine_range atlas_data = load_data("spineboy.atlas"); + sspine_atlas atlas = sspine_make_atlas(&(sspine_atlas_desc){ + .data = atlas_data + }); + free_data(atlas_data); + return atlas; +} + +static sspine_skeleton create_skeleton_json(sspine_atlas atlas) { + sspine_range skeleton_json_data = load_data("spineboy-pro.json"); + sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){ + .atlas = atlas, + .json_data = (const char*)skeleton_json_data.ptr + }); + free_data(skeleton_json_data); + return skeleton; +} + +static sspine_skeleton create_skeleton_binary(sspine_atlas atlas) { + sspine_range skeleton_binary_data = load_data("spineboy-pro.skel"); + sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){ + .atlas = atlas, + .binary_data = skeleton_binary_data + }); + free_data(skeleton_binary_data); + return skeleton; +} + +static sspine_skeleton create_skeleton(void) { + return create_skeleton_json(create_atlas()); +} + +static sspine_instance create_instance(void) { + return sspine_make_instance(&(sspine_instance_desc){ + .skeleton = create_skeleton(), + }); +} + +UTEST(sokol_spine, default_init_shutdown) { + // FIXME! + T(true); +} + +UTEST(sokol_spine, atlas_pool_exhausted) { + init_with_desc(&(sspine_desc){ + .atlas_pool_size = 4, + }); + for (int i = 0; i < 4; i++) { + sspine_atlas atlas = sspine_make_atlas(&(sspine_atlas_desc){0}); + T(sspine_get_atlas_resource_state(atlas) == SSPINE_RESOURCESTATE_FAILED); + T(last_logitem == SSPINE_LOGITEM_ATLAS_DESC_NO_DATA); + } + sspine_atlas atlas = sspine_make_atlas(&(sspine_atlas_desc){0}); + T(SSPINE_INVALID_ID == atlas.id); + T(sspine_get_atlas_resource_state(atlas) == SSPINE_RESOURCESTATE_INVALID); + T(last_logitem == SSPINE_LOGITEM_ATLAS_POOL_EXHAUSTED); + shutdown(); +} + +UTEST(sokol_spine, make_destroy_atlas_ok) { + init(); + sspine_atlas atlas = create_atlas(); + T(sspine_get_atlas_resource_state(atlas) == SSPINE_RESOURCESTATE_VALID); + T(sspine_atlas_valid(atlas)); + sspine_destroy_atlas(atlas); + T(sspine_get_atlas_resource_state(atlas) == SSPINE_RESOURCESTATE_INVALID); + T(!sspine_atlas_valid(atlas)) + shutdown(); +} + +UTEST(sokol_spine, make_atlas_fail_no_data) { + init(); + sspine_atlas atlas = sspine_make_atlas(&(sspine_atlas_desc){0}); + T(atlas.id != SSPINE_INVALID_ID); + T(last_logitem == SSPINE_LOGITEM_ATLAS_DESC_NO_DATA); + T(sspine_get_atlas_resource_state(atlas) == SSPINE_RESOURCESTATE_FAILED); + T(!sspine_atlas_valid(atlas)); + shutdown(); +} + +// an invalid atlas must return zero number of images +UTEST(sokol_spine, failed_atlas_no_images) { + init(); + sspine_atlas atlas = sspine_make_atlas(&(sspine_atlas_desc){0}); + T(last_logitem == SSPINE_LOGITEM_ATLAS_DESC_NO_DATA); + T(atlas.id != SSPINE_INVALID_ID); + T(!sspine_atlas_valid(atlas)); + T(sspine_num_images(atlas) == 0); + shutdown(); + +} + +// NOTE: spine-c doesn't detect wrong/corrupt atlas file data, so we can't test for that + +UTEST(sokol_spine, image_valid) { + init(); + sspine_atlas atlas = create_atlas(); + T(sspine_image_valid(sspine_image_by_index(atlas, 0))); + T(!sspine_image_valid(sspine_image_by_index(atlas, 1))); + T(!sspine_image_valid(sspine_image_by_index(atlas, -1))); + sspine_destroy_atlas(atlas); + T(!sspine_image_valid(sspine_image_by_index(atlas, 0))); + shutdown(); +} + +UTEST(sokol_spine, atlas_image_info) { + init(); + sspine_atlas atlas = create_atlas(); + T(sspine_atlas_valid(atlas)); + T(sspine_num_images(atlas) == 1); + const sspine_image_info img_info = sspine_get_image_info(sspine_image_by_index(atlas, 0)); + T(img_info.valid); + T(img_info.sgimage.id != SG_INVALID_ID); + T(sg_query_image_state(img_info.sgimage) == SG_RESOURCESTATE_ALLOC); + T(strcmp(img_info.filename.cstr, "spineboy.png") == 0); + T(img_info.min_filter == SG_FILTER_LINEAR); + T(img_info.mag_filter == SG_FILTER_LINEAR); + T(img_info.wrap_u == SG_WRAP_CLAMP_TO_EDGE); + T(img_info.wrap_v == SG_WRAP_CLAMP_TO_EDGE); + T(img_info.width == 1024); + T(img_info.height == 256); + T(img_info.premul_alpha == false); + shutdown(); +} + +UTEST(sokol_spine, atlas_with_overrides) { + init(); + sspine_range atlas_data = load_data("spineboy.atlas"); + sspine_atlas atlas = sspine_make_atlas(&(sspine_atlas_desc){ + .data = atlas_data, + .override = { + .min_filter = SG_FILTER_NEAREST, + .mag_filter = SG_FILTER_NEAREST, + .mipmap_filter = SG_FILTER_LINEAR, + .wrap_u = SG_WRAP_REPEAT, + .wrap_v = SG_WRAP_CLAMP_TO_EDGE, + .premul_alpha_enabled = true, + } + }); + T(sspine_atlas_valid(atlas)); + T(sspine_num_images(atlas) == 1); + const sspine_image_info img_info = sspine_get_image_info(sspine_image_by_index(atlas, 0)); + T(img_info.valid); + T(img_info.sgimage.id != SG_INVALID_ID); + T(sg_query_image_state(img_info.sgimage) == SG_RESOURCESTATE_ALLOC); + T(strcmp(img_info.filename.cstr, "spineboy.png") == 0); + T(img_info.min_filter == SG_FILTER_NEAREST); + T(img_info.mag_filter == SG_FILTER_NEAREST); + T(img_info.mipmap_filter == SG_FILTER_LINEAR); + T(img_info.wrap_u == SG_WRAP_REPEAT); + T(img_info.wrap_v == SG_WRAP_CLAMP_TO_EDGE); + T(img_info.width == 1024); + T(img_info.height == 256); + T(img_info.premul_alpha == true); + shutdown(); +} + +UTEST(sokol_spine, skeleton_pool_exhausted) { + init_with_desc(&(sspine_desc){ + .skeleton_pool_size = 4 + }); + for (int i = 0; i < 4; i++) { + sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){0}); + T(sspine_get_skeleton_resource_state(skeleton) == SSPINE_RESOURCESTATE_FAILED); + T(last_logitem == SSPINE_LOGITEM_SKELETON_DESC_NO_DATA); + } + sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){0}); + T(SSPINE_INVALID_ID == skeleton.id); + T(sspine_get_skeleton_resource_state(skeleton) == SSPINE_RESOURCESTATE_INVALID); + T(last_logitem == SSPINE_LOGITEM_SKELETON_POOL_EXHAUSTED); + shutdown(); +} + +UTEST(sokol_spine, make_destroy_skeleton_json_ok) { + init(); + sspine_skeleton skeleton = create_skeleton_json(create_atlas()); + T(sspine_get_skeleton_resource_state(skeleton) == SSPINE_RESOURCESTATE_VALID); + T(sspine_skeleton_valid(skeleton)); + sspine_destroy_skeleton(skeleton); + T(sspine_get_skeleton_resource_state(skeleton) == SSPINE_RESOURCESTATE_INVALID); + T(!sspine_skeleton_valid(skeleton)); + shutdown(); +} + +UTEST(sokol_spine, make_destroy_skeleton_binary_ok) { + init(); + sspine_skeleton skeleton = create_skeleton_binary(create_atlas()); + T(sspine_get_skeleton_resource_state(skeleton) == SSPINE_RESOURCESTATE_VALID); + T(sspine_skeleton_valid(skeleton)); + sspine_destroy_skeleton(skeleton); + T(sspine_get_skeleton_resource_state(skeleton) == SSPINE_RESOURCESTATE_INVALID); + T(!sspine_skeleton_valid(skeleton)); + shutdown(); +} + +UTEST(sokol_spine, make_skeleton_fail_no_data) { + init(); + sspine_atlas atlas = create_atlas(); + sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){ + .atlas = atlas + }); + T(sspine_get_skeleton_resource_state(skeleton) == SSPINE_RESOURCESTATE_FAILED); + T(!sspine_skeleton_valid(skeleton)); + T(last_logitem == SSPINE_LOGITEM_SKELETON_DESC_NO_DATA); + shutdown(); +} + +UTEST(sokol_spine, make_skeleton_fail_no_atlas) { + init(); + sspine_range skeleton_json_data = load_data("spineboy-pro.json"); + sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){ + .json_data = (const char*)skeleton_json_data.ptr + }); + free_data(skeleton_json_data); + T(sspine_get_skeleton_resource_state(skeleton) == SSPINE_RESOURCESTATE_FAILED); + T(!sspine_skeleton_valid(skeleton)); + T(last_logitem == SSPINE_LOGITEM_SKELETON_DESC_NO_ATLAS); + shutdown(); +} + +UTEST(sokol_spine, make_skeleton_fail_with_failed_atlas) { + init(); + sspine_atlas atlas = sspine_make_atlas(&(sspine_atlas_desc){0}); + T(last_logitem == SSPINE_LOGITEM_ATLAS_DESC_NO_DATA); + T(sspine_get_atlas_resource_state(atlas) == SSPINE_RESOURCESTATE_FAILED); + sspine_skeleton skeleton = create_skeleton_json(atlas); + T(sspine_get_skeleton_resource_state(skeleton) == SSPINE_RESOURCESTATE_FAILED); + T(!sspine_skeleton_valid(skeleton)); + T(last_logitem == SSPINE_LOGITEM_SKELETON_ATLAS_NOT_VALID); + shutdown(); +} + +UTEST(sokol_spine, make_skeleton_json_fail_corrupt_data) { + init(); + sspine_atlas atlas = create_atlas(); + const char* invalid_json_data = "This is not valid JSON!"; + sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){ + .atlas = atlas, + .json_data = (const char*)invalid_json_data, + }); + T(sspine_get_skeleton_resource_state(skeleton) == SSPINE_RESOURCESTATE_FAILED); + T(last_logitem == SSPINE_LOGITEM_CREATE_SKELETON_DATA_FROM_JSON_FAILED); + sspine_destroy_skeleton(skeleton); + T(sspine_get_skeleton_resource_state(skeleton) == SSPINE_RESOURCESTATE_INVALID); + shutdown(); +} + +// FIXME: this crashes the spine-c runtime +/* +UTEST(sokol_spine, make_skeleton_binary_fail_corrupt_data) { + init(); + sspine_atlas atlas = create_atlas(); + uint8_t invalid_binary_data[] = { 0x23, 0x63, 0x11, 0xFF }; + sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){ + .atlas = atlas, + .binary_data = { .ptr = invalid_binary_data, .size = sizeof(invalid_binary_data) } + }); + T(sspine_get_skeleton_resource_state(skeleton) == SSPINE_RESOURCESTATE_FAILED); + sspine_destroy_skeleton(skeleton); + T(sspine_get_skeleton_resource_state(skeleton) == SSPINE_RESOURCESTATE_INVALID); + shutdown(); +} +*/ + +UTEST(sokol_spine, instance_pool_exhausted) { + init_with_desc(&(sspine_desc){ + .instance_pool_size = 4 + }); + for (int i = 0; i < 4; i++) { + sspine_instance instance = sspine_make_instance(&(sspine_instance_desc){0}); + T(sspine_get_instance_resource_state(instance) == SSPINE_RESOURCESTATE_FAILED); + T(last_logitem == SSPINE_LOGITEM_INSTANCE_DESC_NO_SKELETON); + } + sspine_instance instance = sspine_make_instance(&(sspine_instance_desc){0}); + T(SSPINE_INVALID_ID == instance.id); + T(sspine_get_instance_resource_state(instance) == SSPINE_RESOURCESTATE_INVALID); + T(last_logitem == SSPINE_LOGITEM_INSTANCE_POOL_EXHAUSTED); + shutdown(); +} + +UTEST(sokol_spine, make_destroy_instance_ok) { + init(); + sspine_instance instance = sspine_make_instance(&(sspine_instance_desc){ + .skeleton = create_skeleton_json(create_atlas()) + }); + T(sspine_get_instance_resource_state(instance) == SSPINE_RESOURCESTATE_VALID); + T(sspine_instance_valid(instance)); + sspine_destroy_instance(instance); + T(sspine_get_instance_resource_state(instance) == SSPINE_RESOURCESTATE_INVALID); + T(!sspine_instance_valid(instance)); + shutdown(); +} + +UTEST(sokol_spine, make_instance_fail_no_skeleton) { + init(); + sspine_instance instance = sspine_make_instance(&(sspine_instance_desc){0}); + T(sspine_get_instance_resource_state(instance) == SSPINE_RESOURCESTATE_FAILED); + T(last_logitem == SSPINE_LOGITEM_INSTANCE_DESC_NO_SKELETON); + sspine_destroy_instance(instance); + T(sspine_get_instance_resource_state(instance) == SSPINE_RESOURCESTATE_INVALID); + shutdown(); +} + +UTEST(sokol_spine, make_instance_fail_with_failed_skeleton) { + init(); + sspine_skeleton failed_skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){0}); + T(last_logitem == SSPINE_LOGITEM_SKELETON_DESC_NO_DATA); + T(sspine_get_skeleton_resource_state(failed_skeleton) == SSPINE_RESOURCESTATE_FAILED); + sspine_instance instance = sspine_make_instance(&(sspine_instance_desc){ + .skeleton = failed_skeleton + }); + T(sspine_get_instance_resource_state(instance) == SSPINE_RESOURCESTATE_FAILED); + T(last_logitem == SSPINE_LOGITEM_INSTANCE_SKELETON_NOT_VALID); + shutdown(); +} + +UTEST(sokol_spine, make_instance_fail_with_destroyed_atlas) { + init(); + sspine_atlas atlas = create_atlas(); + T(sspine_atlas_valid(atlas)); + sspine_skeleton skeleton = create_skeleton_json(atlas); + T(sspine_skeleton_valid(skeleton)); + sspine_destroy_atlas(atlas); + T(!sspine_atlas_valid(atlas)); + sspine_instance instance = sspine_make_instance(&(sspine_instance_desc){ + .skeleton = skeleton + }); + T(sspine_get_instance_resource_state(instance) == SSPINE_RESOURCESTATE_FAILED); + T(last_logitem == SSPINE_LOGITEM_INSTANCE_ATLAS_NOT_VALID); + shutdown(); +} + +UTEST(sokol_spine, get_skeleton_atlas) { + init(); + sspine_atlas atlas = create_atlas(); + sspine_skeleton skeleton = create_skeleton_json(atlas); + T(sspine_get_skeleton_atlas(skeleton).id == atlas.id); + sspine_destroy_skeleton(skeleton); + T(sspine_get_skeleton_atlas(skeleton).id == SSPINE_INVALID_ID); + shutdown(); +} + +UTEST(sokol_spine, get_instance_skeleton) { + init(); + sspine_atlas atlas = create_atlas(); + sspine_skeleton skeleton = create_skeleton_json(atlas); + sspine_instance instance = sspine_make_instance(&(sspine_instance_desc){ + .skeleton = skeleton + }); + T(sspine_get_instance_skeleton(instance).id == skeleton.id); + sspine_destroy_instance(instance); + T(sspine_get_instance_skeleton(instance).id == SSPINE_INVALID_ID); + shutdown(); +} + +UTEST(sokol_spine, set_get_position) { + init(); + sspine_instance instance = create_instance(); + sspine_set_position(instance, (sspine_vec2){ .x=1.0f, .y=2.0f }); + const sspine_vec2 pos = sspine_get_position(instance); + T(pos.x == 1.0f); + T(pos.y == 2.0f); + shutdown(); +} + +UTEST(sokol_spine, set_get_position_destroyed_instance) { + init(); + sspine_instance instance = create_instance(); + sspine_set_position(instance, (sspine_vec2){ .x=1.0f, .y=2.0f }); + sspine_destroy_instance(instance); + const sspine_vec2 pos = sspine_get_position(instance); + T(pos.x == 0.0f); + T(pos.y == 0.0f); + shutdown(); +} + +UTEST(sokol_spine, set_get_scale) { + init(); + sspine_instance instance = create_instance(); + sspine_set_scale(instance, (sspine_vec2){ .x=2.0f, .y=3.0f }); + const sspine_vec2 scale = sspine_get_scale(instance); + T(scale.x == 2.0f); + T(scale.y == 3.0f); + shutdown(); +} + +UTEST(sokol_spine, set_get_scale_destroyed_instance) { + init(); + sspine_instance instance = create_instance(); + sspine_set_scale(instance, (sspine_vec2){ .x=2.0f, .y=3.0f }); + sspine_destroy_instance(instance); + const sspine_vec2 scale = sspine_get_scale(instance); + T(scale.x == 0.0f); + T(scale.y == 0.0f); + shutdown(); +} + +UTEST(sokol_spine, set_get_color) { + init(); + sspine_instance instance = create_instance(); + sspine_set_color(instance, (sspine_color) { .r=1.0f, .g=2.0f, .b=3.0f, .a=4.0f }); + const sspine_color color = sspine_get_color(instance); + T(color.r == 1.0f); + T(color.g == 2.0f); + T(color.b == 3.0f); + T(color.a == 4.0f); + shutdown(); +} + +UTEST(sokol_spine, set_get_color_destroyed_instance) { + init(); + sspine_instance instance = create_instance(); + sspine_set_color(instance, (sspine_color) { .r=1.0f, .g=2.0f, .b=3.0f, .a=4.0f }); + sspine_destroy_instance(instance); + const sspine_color color = sspine_get_color(instance); + T(color.r == 0.0f); + T(color.g == 0.0f); + T(color.b == 0.0f); + T(color.a == 0.0f); + shutdown(); +} + +UTEST(sokol_spine, anim_by_name) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_anim a0 = sspine_anim_by_name(skeleton, "hoverboard"); + T((a0.skeleton_id == skeleton.id) && (a0.index == 2)); + sspine_anim a1 = sspine_anim_by_name(skeleton, "bla"); + T((a1.skeleton_id == 0) && (a1.index == 0)); + shutdown(); +} + +UTEST(sokol_spine, anim_by_name_destroyed_instance) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_destroy_skeleton(skeleton); + sspine_anim a0 = sspine_anim_by_name(skeleton, "hoverboard"); + T((a0.skeleton_id == 0) && (a0.index == 0)); + shutdown(); +} + +UTEST(sokol_spine, anim_valid) { + init(); + sspine_skeleton skeleton = create_skeleton(); + T(sspine_anim_valid(sspine_anim_by_index(skeleton, 0))); + T(sspine_anim_valid(sspine_anim_by_index(skeleton, 10))); + T(!sspine_anim_valid(sspine_anim_by_index(skeleton, -1))); + T(!sspine_anim_valid(sspine_anim_by_index(skeleton, 11))); + sspine_destroy_skeleton(skeleton); + T(!sspine_anim_valid(sspine_anim_by_index(skeleton, 0))); + shutdown(); +} + +UTEST(sokol_spine, anim_equal) { + init(); + T(sspine_anim_equal((sspine_anim){ 1, 2 }, (sspine_anim){ 1, 2 })); + T(!sspine_anim_equal((sspine_anim){ 2, 2 }, (sspine_anim){ 1, 2 })); + T(!sspine_anim_equal((sspine_anim){ 1, 3 }, (sspine_anim){ 1, 2 })); + shutdown(); +} + +UTEST(sokol_spine, num_anims) { + init(); + sspine_skeleton skeleton = create_skeleton(); + T(sspine_num_anims(skeleton) == 11); + sspine_destroy_skeleton(skeleton); + T(sspine_num_anims(skeleton) == 0); + shutdown(); +} + +UTEST(sokol_spine, get_anim_info) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_anim anim = sspine_anim_by_name(skeleton, "hoverboard"); + const sspine_anim_info info = sspine_get_anim_info(anim); + T(info.valid); + T(info.index == 2); + T(strcmp(info.name.cstr, "hoverboard") == 0); + T(info.duration == 1.0f); + shutdown(); +} + +UTEST(sokol_spine, get_anim_info_destroyed_skeleton) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_anim anim = sspine_anim_by_name(skeleton, "hoverboard"); + sspine_destroy_skeleton(skeleton); + const sspine_anim_info info = sspine_get_anim_info(anim); + T(!info.valid); + shutdown(); +} + +UTEST(sokol_spine, get_anim_info_invalid_index) { + init(); + sspine_skeleton skeleton = create_skeleton(); + const sspine_anim_info i0 = sspine_get_anim_info(sspine_anim_by_index(skeleton, -1)); + T(!i0.valid); + T(!i0.name.valid); + const sspine_anim_info i1 = sspine_get_anim_info(sspine_anim_by_index(skeleton, 1234)); + T(!i1.valid); + T(!i1.name.valid); + shutdown(); +} + +UTEST(sokol_spine, atlas_page_valid) { + init(); + sspine_atlas atlas = create_atlas(); + T(sspine_atlas_page_valid(sspine_atlas_page_by_index(atlas, 0))); + T(!sspine_atlas_page_valid(sspine_atlas_page_by_index(atlas, -1))); + T(!sspine_atlas_page_valid(sspine_atlas_page_by_index(atlas, 1))); + sspine_destroy_atlas(atlas); + T(!sspine_atlas_page_valid(sspine_atlas_page_by_index(atlas, 0))); + shutdown(); +} + +UTEST(sokol_spine, num_atlas_pages) { + init(); + sspine_atlas atlas = create_atlas(); + T(sspine_num_atlas_pages(atlas) == 1); + sspine_destroy_atlas(atlas); + T(sspine_num_atlas_pages(atlas) == 0); + shutdown(); +} + +UTEST(sokol_spine, get_atlas_page_info) { + init(); + sspine_atlas atlas = create_atlas(); + const sspine_atlas_page_info info = sspine_get_atlas_page_info(sspine_atlas_page_by_index(atlas, 0)); + T(info.valid); + T(info.atlas.id == atlas.id); + T(info.image.valid); + T(info.image.sgimage.id != SG_INVALID_ID); + T(sg_query_image_state(info.image.sgimage) == SG_RESOURCESTATE_ALLOC); + T(strcmp(info.image.filename.cstr, "spineboy.png") == 0); + T(info.image.min_filter == SG_FILTER_LINEAR); + T(info.image.mag_filter == SG_FILTER_LINEAR); + T(info.image.wrap_u == SG_WRAP_CLAMP_TO_EDGE); + T(info.image.wrap_v == SG_WRAP_CLAMP_TO_EDGE); + T(info.image.width == 1024); + T(info.image.height == 256); + T(info.image.premul_alpha == false); + T(info.overrides.min_filter == _SG_FILTER_DEFAULT); + T(info.overrides.mag_filter == _SG_FILTER_DEFAULT); + T(info.overrides.wrap_u == _SG_WRAP_DEFAULT); + T(info.overrides.wrap_v == _SG_WRAP_DEFAULT); + T(!info.overrides.premul_alpha_enabled); + T(!info.overrides.premul_alpha_disabled); + shutdown(); +} + +UTEST(sokol_spine, get_atlas_page_info_destroyed_atlas) { + init(); + sspine_atlas atlas = create_atlas(); + sspine_destroy_atlas(atlas); + const sspine_atlas_page_info info = sspine_get_atlas_page_info(sspine_atlas_page_by_index(atlas, 0)); + T(!info.valid); + T(info.atlas.id == SSPINE_INVALID_ID); + shutdown(); +} + +UTEST(sokol_spine, get_atlas_page_info_invalid_index) { + init(); + sspine_atlas atlas = create_atlas(); + sspine_destroy_atlas(atlas); + const sspine_atlas_page_info i0 = sspine_get_atlas_page_info(sspine_atlas_page_by_index(atlas, -1)); + T(!i0.valid); + T(i0.atlas.id == SSPINE_INVALID_ID); + const sspine_atlas_page_info i1 = sspine_get_atlas_page_info(sspine_atlas_page_by_index(atlas, 1234)); + T(!i0.valid); + T(i1.atlas.id == SSPINE_INVALID_ID); + shutdown(); +} + +UTEST(sokol_spine, atlas_get_atlas_page_info_with_overrides) { + init(); + sspine_range atlas_data = load_data("spineboy.atlas"); + sspine_atlas atlas = sspine_make_atlas(&(sspine_atlas_desc){ + .data = atlas_data, + .override = { + .min_filter = SG_FILTER_NEAREST, + .mag_filter = SG_FILTER_NEAREST, + .mipmap_filter = SG_FILTER_NEAREST, + .wrap_u = SG_WRAP_REPEAT, + .wrap_v = SG_WRAP_CLAMP_TO_EDGE, + .premul_alpha_enabled = true, + } + }); + const sspine_atlas_page_info info = sspine_get_atlas_page_info(sspine_atlas_page_by_index(atlas, 0)); + T(info.valid); + T(info.atlas.id == atlas.id); + T(info.image.valid); + T(info.image.sgimage.id != SG_INVALID_ID); + T(sg_query_image_state(info.image.sgimage) == SG_RESOURCESTATE_ALLOC); + T(strcmp(info.image.filename.cstr, "spineboy.png") == 0); + T(info.image.min_filter == SG_FILTER_LINEAR); + T(info.image.mag_filter == SG_FILTER_LINEAR); + T(info.image.mipmap_filter == SG_FILTER_NONE); + T(info.image.wrap_u == SG_WRAP_CLAMP_TO_EDGE); + T(info.image.wrap_v == SG_WRAP_CLAMP_TO_EDGE); + T(info.image.width == 1024); + T(info.image.height == 256); + T(info.image.premul_alpha == true); // FIXME: hmm, this is actually inconsistent + T(info.overrides.min_filter == SG_FILTER_NEAREST); + T(info.overrides.mag_filter == SG_FILTER_NEAREST); + T(info.overrides.mipmap_filter == SG_FILTER_NEAREST); + T(info.overrides.wrap_u == SG_WRAP_REPEAT); + T(info.overrides.wrap_v == SG_WRAP_CLAMP_TO_EDGE); + T(info.overrides.premul_alpha_enabled); + T(!info.overrides.premul_alpha_disabled); + shutdown(); +} + +UTEST(sokol_spine, bone_by_name) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_bone b0 = sspine_bone_by_name(skeleton, "crosshair"); + T((b0.skeleton_id == skeleton.id) && (b0.index == 2)); + sspine_bone b1 = sspine_bone_by_name(skeleton, "blablub"); + T((b1.skeleton_id == 0) && (b1.index == 0)); + shutdown(); +} + +UTEST(sokol_spine, bone_by_name_destroyed_skeleton) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_destroy_skeleton(skeleton); + sspine_bone b0 = sspine_bone_by_name(skeleton, "crosshair"); + T((b0.skeleton_id == 0) && (b0.index == 0)); + shutdown(); +} + +UTEST(sokol_spine, bone_valid) { + init(); + sspine_skeleton skeleton = create_skeleton(); + T(sspine_bone_valid(sspine_bone_by_index(skeleton, 0))); + T(sspine_bone_valid(sspine_bone_by_index(skeleton, 66))); + T(!sspine_bone_valid(sspine_bone_by_index(skeleton, -1))); + T(!sspine_bone_valid(sspine_bone_by_index(skeleton, 67))); + sspine_destroy_skeleton(skeleton); + T(!sspine_bone_valid(sspine_bone_by_index(skeleton, 0))); + shutdown(); +} + +UTEST(sokol_spine, bone_equal) { + init(); + T(sspine_bone_equal((sspine_bone){ 1, 2 }, (sspine_bone){ 1, 2 })); + T(!sspine_bone_equal((sspine_bone){ 2, 2 }, (sspine_bone){ 1, 2 })); + T(!sspine_bone_equal((sspine_bone){ 1, 3 }, (sspine_bone){ 1, 2 })); + shutdown(); +} + +UTEST(sokol_spine, num_bones) { + init(); + sspine_skeleton skeleton = create_skeleton(); + T(sspine_num_bones(skeleton) == 67); + sspine_destroy_skeleton(skeleton); + T(sspine_num_bones(skeleton) == 0); + shutdown(); +} + +UTEST(sokol_spine, get_bone_info_root) { + init(); + sspine_skeleton skeleton = create_skeleton(); + const sspine_bone_info info = sspine_get_bone_info(sspine_bone_by_name(skeleton, "root")); + T(info.valid); + T(info.index == 0); + T((info.parent_bone.skeleton_id == 0) && (info.parent_bone.index == 0)); + T(strcmp(info.name.cstr, "root") == 0); + T(info.length == 0.0f); + T(info.pose.position.x == 0.0f); + T(info.pose.position.y == 0.0f); + T(info.pose.rotation == 0.05f); + T(info.pose.scale.x == 1.0f); + T(info.pose.scale.y == 1.0f); + T(info.pose.shear.x == 0.0f); + T(info.pose.shear.y == 0.0f); + shutdown(); +} + +UTEST(sokol_spine, get_bone_info_parent_bone) { + init(); + sspine_skeleton skeleton = create_skeleton(); + const sspine_bone_info info = sspine_get_bone_info(sspine_bone_by_name(skeleton, "rear-shin")); + T(info.valid); + T(info.index == 7); + T((info.parent_bone.skeleton_id == skeleton.id) && (info.parent_bone.index == 6)); + shutdown(); +} + +UTEST(sokol_spine, get_bone_info_destroyed_skeleton) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_bone bone = sspine_bone_by_name(skeleton, "root"); + sspine_destroy_skeleton(skeleton); + const sspine_bone_info info = sspine_get_bone_info(bone); + T(!info.valid); + T(!info.name.valid); + shutdown(); +} + +UTEST(sokol_spine, get_bone_info_invalid_index) { + init(); + sspine_skeleton skeleton = create_skeleton(); + const sspine_bone_info i0 = sspine_get_bone_info(sspine_bone_by_index(skeleton, -1)); + T(!i0.valid); + T(!i0.name.valid); + const sspine_bone_info i1 = sspine_get_bone_info(sspine_bone_by_index(skeleton, 1234)); + T(!i1.valid); + T(!i1.name.valid); + shutdown(); +} + +UTEST(sokol_spine, set_get_bone_transform) { + init(); + sspine_instance instance = create_instance(); + sspine_skeleton skeleton = sspine_get_instance_skeleton(instance); + sspine_bone bone = sspine_bone_by_name(skeleton, "root"); + sspine_set_bone_transform(instance, bone, &(sspine_bone_transform){ + .position = { 1.0f, 2.0f }, + .rotation = 3.0f, + .scale = { 4.0f, 5.0f }, + .shear = { 6.0f, 7.0f } + }); + const sspine_bone_transform tform = sspine_get_bone_transform(instance, bone); + T(tform.position.x == 1.0f); + T(tform.position.y == 2.0f); + T(tform.rotation == 3.0f); + T(tform.scale.x == 4.0f); + T(tform.scale.y == 5.0f); + T(tform.shear.x == 6.0f); + T(tform.shear.y == 7.0f); + shutdown(); +} + +UTEST(sokol_spine, set_get_bone_transform_destroyed_instance) { + init(); + sspine_instance instance = create_instance(); + sspine_skeleton skeleton = sspine_get_instance_skeleton(instance); + sspine_bone bone = sspine_bone_by_name(skeleton, "root"); + sspine_destroy_instance(instance); + sspine_set_bone_transform(instance, bone, &(sspine_bone_transform){ + .position = { 1.0f, 2.0f }, + .rotation = 3.0f, + .scale = { 4.0f, 5.0f }, + .shear = { 6.0f, 7.0f } + }); + const sspine_bone_transform tform = sspine_get_bone_transform(instance, bone); + T(tform.position.x == 0.0f); + T(tform.position.y == 0.0f); + T(tform.rotation == 0.0f); + T(tform.scale.x == 0.0f); + T(tform.scale.y == 0.0f); + T(tform.shear.x == 0.0f); + T(tform.shear.y == 0.0f); + shutdown(); +} + +UTEST(sokol_spine, set_get_bone_position) { + init(); + sspine_instance instance = create_instance(); + sspine_skeleton skeleton = sspine_get_instance_skeleton(instance); + sspine_bone bone = sspine_bone_by_name(skeleton, "root"); + sspine_set_bone_position(instance, bone, (sspine_vec2){ 1.0f, 2.0f }); + const sspine_vec2 p0 = sspine_get_bone_position(instance, bone); + T(p0.x == 1.0f); + T(p0.y == 2.0f); + sspine_destroy_instance(instance); + const sspine_vec2 p1 = sspine_get_bone_position(instance, bone); + T(p1.x == 0.0f); + T(p1.y == 0.0f); + shutdown(); +} + +UTEST(sokol_spine, set_get_bone_rotation) { + init(); + sspine_instance instance = create_instance(); + sspine_skeleton skeleton = sspine_get_instance_skeleton(instance); + sspine_bone bone = sspine_bone_by_name(skeleton, "root"); + sspine_set_bone_rotation(instance, bone, 5.0f); + T(sspine_get_bone_rotation(instance, bone) == 5.0f); + sspine_destroy_instance(instance); + T(sspine_get_bone_rotation(instance, bone) == 0.0f); + shutdown(); +} + +UTEST(sokol_spine, set_get_bone_scale) { + init(); + sspine_instance instance = create_instance(); + sspine_skeleton skeleton = sspine_get_instance_skeleton(instance); + sspine_bone bone = sspine_bone_by_name(skeleton, "root"); + sspine_set_bone_scale(instance, bone, (sspine_vec2){ 1.0f, 2.0f }); + const sspine_vec2 s0 = sspine_get_bone_scale(instance, bone); + T(s0.x == 1.0f); + T(s0.y == 2.0f); + sspine_destroy_instance(instance); + const sspine_vec2 s1 = sspine_get_bone_scale(instance, bone); + T(s1.x == 0.0f); + T(s1.y == 0.0f); + shutdown(); +} + +UTEST(sokol_spine, set_get_bone_shear) { + init(); + sspine_instance instance = create_instance(); + sspine_skeleton skeleton = sspine_get_instance_skeleton(instance); + sspine_bone bone = sspine_bone_by_name(skeleton, "root"); + sspine_set_bone_shear(instance, bone, (sspine_vec2){ 1.0f, 2.0f }); + const sspine_vec2 s0 = sspine_get_bone_shear(instance, bone); + T(s0.x == 1.0f); + T(s0.y == 2.0f); + sspine_destroy_instance(instance); + const sspine_vec2 s1 = sspine_get_bone_shear(instance, bone); + T(s1.x == 0.0f); + T(s1.y == 0.0f); + shutdown(); +} + +UTEST(sokol_spine, slot_by_name) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_slot s0 = sspine_slot_by_name(skeleton, "portal-streaks1"); + T((s0.skeleton_id == skeleton.id) && (s0.index == 3)); + sspine_slot s1 = sspine_slot_by_name(skeleton, "blablub"); + T((s1.skeleton_id == 0) && (s1.index == 0)); + shutdown(); +} + +UTEST(sokol_spine, slot_by_name_destroyed_skeleton) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_destroy_skeleton(skeleton); + sspine_slot s0 = sspine_slot_by_name(skeleton, "portal-streaks1"); + T((s0.skeleton_id == 0) && (s0.index == 0)); + shutdown(); +} + +UTEST(sokol_spine, num_slots) { + init(); + sspine_skeleton skeleton = create_skeleton(); + T(sspine_num_slots(skeleton) == 52); + sspine_destroy_skeleton(skeleton); + T(sspine_num_slots(skeleton) == 0); + shutdown(); +} + +UTEST(sokol_spine, slot_valid) { + init(); + sspine_skeleton skeleton = create_skeleton(); + T(sspine_slot_valid(sspine_slot_by_index(skeleton, 0))); + T(sspine_slot_valid(sspine_slot_by_index(skeleton, 51))); + T(!sspine_slot_valid(sspine_slot_by_index(skeleton, -1))); + T(!sspine_slot_valid(sspine_slot_by_index(skeleton, 52))); + sspine_destroy_skeleton(skeleton); + T(!sspine_slot_valid(sspine_slot_by_index(skeleton, 0))); + shutdown(); +} + +UTEST(sokol_spine, slot_equal) { + init(); + T(sspine_slot_equal((sspine_slot){ 1, 2 }, (sspine_slot){ 1, 2 })); + T(!sspine_slot_equal((sspine_slot){ 2, 2 }, (sspine_slot){ 1, 2 })); + T(!sspine_slot_equal((sspine_slot){ 1, 3 }, (sspine_slot){ 1, 2 })); + shutdown(); +} + +UTEST(sokol_spine, get_slot_info) { + init(); + sspine_skeleton skeleton = create_skeleton(); + const sspine_slot_info info = sspine_get_slot_info(sspine_slot_by_name(skeleton, "portal-streaks1")); + T(info.valid); + T(info.index == 3); + T(strcmp(info.name.cstr, "portal-streaks1") == 0); + T(!info.attachment_name.valid); + T((info.bone.skeleton_id == skeleton.id) && (info.bone.index == 62)); + T(info.color.r == 1.0f); + T(info.color.g == 1.0f); + T(info.color.b == 1.0f); + T(info.color.a == 1.0f); + shutdown(); +} + +UTEST(sokol_spine, get_slot_info_destroyed_skeleton) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_slot slot = sspine_slot_by_name(skeleton, "portal-streaks1"); + sspine_destroy_skeleton(skeleton); + const sspine_slot_info info = sspine_get_slot_info(slot); + T(!info.valid); + T(!info.name.valid); + shutdown(); +} + +UTEST(sokol_spine, get_slot_info_invalid_index) { + init(); + sspine_skeleton skeleton = create_skeleton(); + const sspine_slot_info i0 = sspine_get_slot_info(sspine_slot_by_index(skeleton, -1)); + T(!i0.valid); + T(!i0.name.valid); + const sspine_slot_info i1 = sspine_get_slot_info(sspine_slot_by_index(skeleton, 1234)); + T(!i1.valid); + T(!i1.name.valid); + shutdown(); +} + +UTEST(sokol_spine, set_get_slot_color) { + init(); + sspine_instance instance = create_instance(); + sspine_skeleton skeleton = sspine_get_instance_skeleton(instance); + sspine_slot slot = sspine_slot_by_name(skeleton, "portal-streaks1"); + sspine_set_slot_color(instance, slot, (sspine_color){ 1.0f, 2.0f, 3.0f, 4.0f }); + const sspine_color color = sspine_get_slot_color(instance, slot); + T(color.r == 1.0f); + T(color.g == 2.0f); + T(color.b == 3.0f); + T(color.a == 4.0f); + const sspine_slot_info info = sspine_get_slot_info(slot); + T(info.color.r == 1.0f); + T(info.color.g == 1.0f); + T(info.color.b == 1.0f); + T(info.color.a == 1.0f); + shutdown(); +} + +UTEST(sokol_spine, event_by_name) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_event e0 = sspine_event_by_name(skeleton, "footstep"); + T((e0.skeleton_id == skeleton.id) && (e0.index == 0)); + sspine_event e1 = sspine_event_by_name(skeleton, "bla"); + T((e1.skeleton_id == 0) && (e1.index == 0)); + shutdown(); +} + +UTEST(sokol_spine, event_by_name_destroyed_skeleton) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_destroy_skeleton(skeleton); + sspine_event e0 = sspine_event_by_name(skeleton, "footstep"); + T((e0.skeleton_id == 0) && (e0.index == 0)); + shutdown(); +} + +UTEST(sokol_spine, event_valid) { + init(); + sspine_skeleton skeleton = create_skeleton(); + T(sspine_event_valid(sspine_event_by_index(skeleton, 0))); + T(!sspine_event_valid(sspine_event_by_index(skeleton, 1))); + T(!sspine_event_valid(sspine_event_by_index(skeleton, -1))); + sspine_destroy_skeleton(skeleton); + T(!sspine_event_valid(sspine_event_by_index(skeleton, 0))); + shutdown(); +} + +UTEST(sokol_spine, event_equal) { + init(); + T(sspine_event_equal((sspine_event){ 1, 2 }, (sspine_event){ 1, 2 })); + T(!sspine_event_equal((sspine_event){ 2, 2 }, (sspine_event){ 1, 2 })); + T(!sspine_event_equal((sspine_event){ 1, 3 }, (sspine_event){ 1, 2 })); + shutdown(); +} + +UTEST(sokol_spine, num_events) { + init(); + sspine_skeleton skeleton = create_skeleton(); + T(sspine_num_events(skeleton) == 1); + sspine_destroy_skeleton(skeleton); + T(sspine_num_events(skeleton) == 0); + shutdown(); +} + +UTEST(sokol_spine, get_event_info) { + init(); + sspine_skeleton skeleton = create_skeleton(); + const sspine_event_info info = sspine_get_event_info(sspine_event_by_index(skeleton, 0)); + T(info.valid); + T(0 == strcmp(info.name.cstr, "footstep")); + T(0 == info.index); + T(0 == info.int_value); + T(0.0f == info.float_value); + T(!info.string_value.valid); + T(!info.audio_path.valid); + T(0.0f == info.volume); + T(0.0f == info.balance); + shutdown(); +} + +UTEST(sokol_spine, get_event_info_destroyed_skeleton) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_destroy_skeleton(skeleton); + const sspine_event_info info = sspine_get_event_info(sspine_event_by_index(skeleton, 0)); + T(!info.valid); + T(!info.name.valid); + shutdown(); +} + +UTEST(sokol_spine, iktarget_by_name) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_iktarget ik0 = sspine_iktarget_by_name(skeleton, "board-ik"); + T((ik0.skeleton_id == skeleton.id) && (ik0.index == 2)); + sspine_iktarget ik1 = sspine_iktarget_by_name(skeleton, "bla"); + T((ik1.skeleton_id == 0) && (ik1.index == 0)); + shutdown(); +} + +UTEST(sokol_spine, iktarget_by_name_destroyed_skeleton) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_destroy_skeleton(skeleton); + sspine_iktarget ik0 = sspine_iktarget_by_name(skeleton, "board-ik"); + T((ik0.skeleton_id == 0) && (ik0.index == 0)); + shutdown(); +} + +UTEST(sokol_spine, iktarget_valid) { + init(); + sspine_skeleton skeleton = create_skeleton(); + T(sspine_iktarget_valid(sspine_iktarget_by_index(skeleton, 0))); + T(sspine_iktarget_valid(sspine_iktarget_by_index(skeleton, 6))); + T(!sspine_iktarget_valid(sspine_iktarget_by_index(skeleton, -1))); + T(!sspine_iktarget_valid(sspine_iktarget_by_index(skeleton, 7))); + sspine_destroy_skeleton(skeleton); + T(!sspine_iktarget_valid(sspine_iktarget_by_index(skeleton, 0))); + shutdown(); +} + +UTEST(sokol_spine, iktarget_equal) { + init(); + T(sspine_iktarget_equal((sspine_iktarget){ 1, 2 }, (sspine_iktarget){ 1, 2 })); + T(!sspine_iktarget_equal((sspine_iktarget){ 2, 2 }, (sspine_iktarget){ 1, 2 })); + T(!sspine_iktarget_equal((sspine_iktarget){ 1, 3 }, (sspine_iktarget){ 1, 2 })); + shutdown(); +} + +UTEST(sokol_spine, num_iktargets) { + init(); + sspine_skeleton skeleton = create_skeleton(); + T(sspine_num_iktargets(skeleton) == 7); + sspine_destroy_skeleton(skeleton); + T(sspine_num_iktargets(skeleton) == 0); + shutdown(); +} + +UTEST(sokol_spine, get_iktarget_info) { + init(); + sspine_skeleton skeleton = create_skeleton(); + const sspine_iktarget_info info = sspine_get_iktarget_info(sspine_iktarget_by_index(skeleton, 1)); + T(info.valid); + T(1 == info.index); + T(0 == strcmp(info.name.cstr, "aim-torso-ik")); + T((info.target_bone.skeleton_id == skeleton.id) && (info.target_bone.index == 2)); + shutdown(); +} + +UTEST(sokol_spine, get_iktarget_info_destroyed_skeleton) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_destroy_skeleton(skeleton); + const sspine_iktarget_info info = sspine_get_iktarget_info(sspine_iktarget_by_index(skeleton, 1)); + T(!info.valid); + T(!info.name.valid); + shutdown(); +} + +UTEST(sokol_spine, get_iktarget_info_out_of_bounds) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_destroy_skeleton(skeleton); + const sspine_iktarget_info info0 = sspine_get_iktarget_info(sspine_iktarget_by_index(skeleton, -1)); + T(!info0.name.valid); + const sspine_iktarget_info info1 = sspine_get_iktarget_info(sspine_iktarget_by_index(skeleton, 7)); + T(!info1.name.valid); + shutdown(); +} + +UTEST(sokol_spine, skin_by_name) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_skin s0 = sspine_skin_by_name(skeleton, "default"); + T((s0.skeleton_id == skeleton.id) && (s0.index == 0)); + sspine_skin s1 = sspine_skin_by_name(skeleton, "bla"); + T((s1.skeleton_id == 0) && (s1.index == 0)); + sspine_destroy_skeleton(skeleton); + sspine_skin s2 = sspine_skin_by_name(skeleton, "default"); + T((s2.skeleton_id == 0) && (s2.index == 0)); + shutdown(); +} + +UTEST(sokol_spine, skin_valid) { + init(); + sspine_skeleton skeleton = create_skeleton(); + T(sspine_skin_valid(sspine_skin_by_index(skeleton, 0))); + T(!sspine_skin_valid(sspine_skin_by_index(skeleton, -1))); + T(!sspine_skin_valid(sspine_skin_by_index(skeleton, 1))); + sspine_destroy_skeleton(skeleton); + T(!sspine_skin_valid(sspine_skin_by_index(skeleton, 0))); + shutdown(); +} + +UTEST(sokol_spine, skin_equal) { + init(); + T(sspine_skin_equal((sspine_skin){ 1, 2 }, (sspine_skin){ 1, 2 })); + T(!sspine_skin_equal((sspine_skin){ 2, 2 }, (sspine_skin){ 1, 2 })); + T(!sspine_skin_equal((sspine_skin){ 1, 3 }, (sspine_skin){ 1, 2 })); + shutdown(); +} + +UTEST(sokol_spine, num_skins) { + init(); + sspine_skeleton skeleton = create_skeleton(); + T(sspine_num_skins(skeleton) == 1); + sspine_destroy_skeleton(skeleton); + T(sspine_num_skins(skeleton) == 0); + shutdown(); +} + +UTEST(sokol_spine, get_skin_info) { + init(); + sspine_skeleton skeleton = create_skeleton(); + const sspine_skin_info info = sspine_get_skin_info(sspine_skin_by_index(skeleton, 0)); + T(info.valid); + T(0 == info.index); + T(0 == strcmp(info.name.cstr, "default")); + shutdown(); +} + +UTEST(sokol_spine, get_skin_info_destroyed_skeleton) { + init(); + sspine_skeleton skeleton = create_skeleton(); + sspine_destroy_skeleton(skeleton); + const sspine_skin_info info = sspine_get_skin_info(sspine_skin_by_index(skeleton, 0)); + T(!info.valid); + T(!info.name.valid); + shutdown(); +} diff --git a/source/engine/thirdparty/sokol/tests/functional/sokol_test.c b/source/engine/thirdparty/sokol/tests/functional/sokol_test.c new file mode 100644 index 0000000..29528b6 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/sokol_test.c @@ -0,0 +1,7 @@ +//------------------------------------------------------------------------------ +// sokol-test.c +// Sokol headers main test source. +//------------------------------------------------------------------------------ +#include "utest.h" + +UTEST_MAIN(); diff --git a/source/engine/thirdparty/sokol/tests/functional/utest.h b/source/engine/thirdparty/sokol/tests/functional/utest.h new file mode 100644 index 0000000..0fe3023 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/functional/utest.h @@ -0,0 +1,859 @@ +/* + The latest version of this library is available on GitHub; + https://github.com/sheredom/utest.h +*/ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +#ifndef SHEREDOM_UTEST_H_INCLUDED +#define SHEREDOM_UTEST_H_INCLUDED + +#ifdef _MSC_VER +/* + Disable warning about not inlining 'inline' functions. + TODO: We'll fix this later by not using fprintf within our macros, and + instead use snprintf to a realloc'ed buffer. +*/ +#pragma warning(disable : 4710) + +/* + Disable warning about inlining functions that are not marked 'inline'. + TODO: add a UTEST_NOINLINE onto the macro generated functions to fix this. +*/ +#pragma warning(disable : 4711) +#pragma warning(push, 1) +#endif + +#if defined(_MSC_VER) +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif + +#include +#include +#include +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(_MSC_VER) +#if defined(_M_IX86) +#define _X86_ +#endif + +#if defined(_M_AMD64) +#define _AMD64_ +#endif + +#pragma warning(push, 1) +#include +#include +#pragma warning(pop) + +#elif defined(__linux__) + +/* + slightly obscure include here - we need to include glibc's features.h, but + we don't want to just include a header that might not be defined for other + c libraries like musl. Instead we include limits.h, which we know on all + glibc distributions includes features.h +*/ +#include + +#if defined(__GLIBC__) && defined(__GLIBC_MINOR__) +#include + +#if ((2 < __GLIBC__) || ((2 == __GLIBC__) && (17 <= __GLIBC_MINOR__))) +/* glibc is version 2.17 or above, so we can just use clock_gettime */ +#define UTEST_USE_CLOCKGETTIME +#else +#include +#include +#endif +#endif + +#elif defined(__APPLE__) +#include +#endif + +#if defined(_MSC_VER) +#define UTEST_PRId64 "I64d" +#define UTEST_PRIu64 "I64u" +#define UTEST_INLINE __forceinline + +#pragma section(".CRT$XCU", read) +#define UTEST_INITIALIZER(f) \ + static void __cdecl f(void); \ + __declspec(allocate(".CRT$XCU")) void(__cdecl * f##_)(void) = f; \ + static void __cdecl f(void) +#else +#if defined(__linux__) +#if defined(__clang__) +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#endif +#endif + +#define __STDC_FORMAT_MACROS 1 + +#if defined(__clang__) +#if __has_warning("-Wreserved-id-macro") +#pragma clang diagnostic pop +#endif +#endif +#endif + +#include + +#define UTEST_PRId64 PRId64 +#define UTEST_PRIu64 PRIu64 +#define UTEST_INLINE inline + +#define UTEST_INITIALIZER(f) \ + static void f(void) __attribute__((constructor)); \ + static void f(void) +#endif + +#if defined(__cplusplus) +#define UTEST_CAST(type, x) static_cast(x) +#define UTEST_PTR_CAST(type, x) reinterpret_cast(x) +#define UTEST_EXTERN extern "C" +#else +#define UTEST_CAST(type, x) ((type)x) +#define UTEST_PTR_CAST(type, x) ((type)x) +#define UTEST_EXTERN extern +#endif + +#ifdef _MSC_VER +/* + io.h contains definitions for some structures with natural padding. This is + uninteresting, but for some reason MSVC's behaviour is to warn about + including this system header. That *is* interesting +*/ +#pragma warning(disable : 4820) +#pragma warning(push, 1) +#include +#pragma warning(pop) +#define UTEST_COLOUR_OUTPUT() (_isatty(_fileno(stdout))) +#else +#include +#define UTEST_COLOUR_OUTPUT() (isatty(STDOUT_FILENO)) +#endif + +static UTEST_INLINE int64_t utest_ns(void) { +#ifdef _MSC_VER + LARGE_INTEGER counter; + LARGE_INTEGER frequency; + QueryPerformanceCounter(&counter); + QueryPerformanceFrequency(&frequency); + return UTEST_CAST(int64_t, + (counter.QuadPart * 1000000000) / frequency.QuadPart); +#elif defined(__linux) + struct timespec ts; + const clockid_t cid = CLOCK_REALTIME; +#if defined(UTEST_USE_CLOCKGETTIME) + clock_gettime(cid, &ts); +#else + syscall(SYS_clock_gettime, cid, &ts); +#endif + return UTEST_CAST(int64_t, ts.tv_sec) * 1000 * 1000 * 1000 + ts.tv_nsec; +#elif __APPLE__ + return UTEST_CAST(int64_t, mach_absolute_time()); +#else + /* hack to prevent warning on unsupported platforms */ + return 1; +#endif +} + +typedef void (*utest_testcase_t)(int *, size_t); + +struct utest_test_state_s { + utest_testcase_t func; + size_t index; + char *name; +}; + +struct utest_state_s { + struct utest_test_state_s *tests; + size_t tests_length; + FILE *output; +}; + +/* extern to the global state utest needs to execute */ +UTEST_EXTERN struct utest_state_s utest_state; + +#if defined(_MSC_VER) +#define UTEST_WEAK __forceinline +#else +#define UTEST_WEAK __attribute__((weak)) +#endif + +#if defined(_MSC_VER) +#define UTEST_UNUSED +#else +#define UTEST_UNUSED __attribute__((unused)) +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wvariadic-macros" +#endif +#define UTEST_PRINTF(...) \ + if (utest_state.output) { \ + fprintf(utest_state.output, __VA_ARGS__); \ + } \ + printf(__VA_ARGS__) +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _MSC_VER +#define UTEST_SNPRINTF(BUFFER, N, ...) _snprintf_s(BUFFER, N, N, __VA_ARGS__) +#else +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wvariadic-macros" +#endif +#define UTEST_SNPRINTF(...) snprintf(__VA_ARGS__) +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#endif + +#if defined(__cplusplus) +/* if we are using c++ we can use overloaded methods (its in the language) */ +#define UTEST_OVERLOADABLE +#elif defined(__clang__) +/* otherwise, if we are using clang with c - use the overloadable attribute */ +#define UTEST_OVERLOADABLE __attribute__((overloadable)) +#endif + +#if defined(UTEST_OVERLOADABLE) +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(float f); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(float f) { + UTEST_PRINTF("%f", UTEST_CAST(double, f)); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(double d); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(double d) { + UTEST_PRINTF("%f", d); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long double d); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long double d) { + UTEST_PRINTF("%Lf", d); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(int i) { + UTEST_PRINTF("%d", i); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned int i) { + UTEST_PRINTF("%u", i); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long int i) { + UTEST_PRINTF("%ld", i); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long unsigned int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long unsigned int i) { + UTEST_PRINTF("%lu", i); +} + +/* + long long is a c++11 extension + TODO: grok for c++11 version here +*/ +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long long int i); +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long long int i) { + UTEST_PRINTF("%lld", i); +} + +UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long long unsigned int i); +UTEST_WEAK UTEST_OVERLOADABLE void +utest_type_printer(long long unsigned int i) { + UTEST_PRINTF("%llu", i); +} +#endif +#else +/* + we don't have the ability to print the values we got, so we create a macro + to tell our users we can't do anything fancy +*/ +#define utest_type_printer(...) UTEST_PRINTF("undef") +#endif + +#if defined(__clang__) +#define UTEST_EXPECT(x, y, cond) \ + { \ + _Pragma("clang diagnostic push") _Pragma( \ + "clang diagnostic ignored \"-Wlanguage-extension-token\"") typeof(y) \ + xEval = (x); \ + typeof(y) yEval = (y); \ + _Pragma("clang diagnostic pop") if (!((xEval)cond(yEval))) { \ + UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : "); \ + utest_type_printer(xEval); \ + UTEST_PRINTF("\n"); \ + UTEST_PRINTF(" Actual : "); \ + utest_type_printer(yEval); \ + UTEST_PRINTF("\n"); \ + *utest_result = 1; \ + } \ + } +#elif defined(__GNUC__) +#define UTEST_EXPECT(x, y, cond) \ + { \ + typeof(y) xEval = (x); \ + typeof(y) yEval = (y); \ + if (!((xEval)cond(yEval))) { \ + UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : "); \ + utest_type_printer(xEval); \ + UTEST_PRINTF("\n"); \ + UTEST_PRINTF(" Actual : "); \ + utest_type_printer(yEval); \ + UTEST_PRINTF("\n"); \ + *utest_result = 1; \ + } \ + } +#else +#define UTEST_EXPECT(x, y, cond) \ + { \ + if (!((x)cond(y))) { \ + UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + *utest_result = 1; \ + } \ + } +#endif + +#define EXPECT_TRUE(x) \ + if (!(x)) { \ + UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : true\n"); \ + UTEST_PRINTF(" Actual : %s\n", (x) ? "true" : "false"); \ + *utest_result = 1; \ + } + +#define EXPECT_FALSE(x) \ + if (x) { \ + UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : false\n"); \ + UTEST_PRINTF(" Actual : %s\n", (x) ? "true" : "false"); \ + *utest_result = 1; \ + } + +#define EXPECT_EQ(x, y) UTEST_EXPECT(x, y, ==) +#define EXPECT_NE(x, y) UTEST_EXPECT(x, y, !=) +#define EXPECT_LT(x, y) UTEST_EXPECT(x, y, <) +#define EXPECT_LE(x, y) UTEST_EXPECT(x, y, <=) +#define EXPECT_GT(x, y) UTEST_EXPECT(x, y, >) +#define EXPECT_GE(x, y) UTEST_EXPECT(x, y, >=) + +#define EXPECT_STREQ(x, y) \ + if (0 != strcmp(x, y)) { \ + UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%s\"\n", x); \ + UTEST_PRINTF(" Actual : \"%s\"\n", y); \ + *utest_result = 1; \ + } + +#define EXPECT_STRNE(x, y) \ + if (0 == strcmp(x, y)) { \ + UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%s\"\n", x); \ + UTEST_PRINTF(" Actual : \"%s\"\n", y); \ + *utest_result = 1; \ + } + +#if defined(__clang__) +#define UTEST_ASSERT(x, y, cond) \ + { \ + _Pragma("clang diagnostic push") _Pragma( \ + "clang diagnostic ignored \"-Wlanguage-extension-token\"") typeof(y) \ + xEval = (x); \ + typeof(y) yEval = (y); \ + _Pragma("clang diagnostic pop") if (!((xEval)cond(yEval))) { \ + UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : "); \ + utest_type_printer(xEval); \ + UTEST_PRINTF("\n"); \ + UTEST_PRINTF(" Actual : "); \ + utest_type_printer(yEval); \ + UTEST_PRINTF("\n"); \ + *utest_result = 1; \ + return; \ + } \ + } +#elif defined(__GNUC__) +#define UTEST_ASSERT(x, y, cond) \ + { \ + typeof(y) xEval = (x); \ + typeof(y) yEval = (y); \ + if (!((xEval)cond(yEval))) { \ + UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : "); \ + utest_type_printer(xEval); \ + UTEST_PRINTF("\n"); \ + UTEST_PRINTF(" Actual : "); \ + utest_type_printer(yEval); \ + UTEST_PRINTF("\n"); \ + *utest_result = 1; \ + return; \ + } \ + } +#else +#define UTEST_ASSERT(x, y, cond) \ + { \ + if (!((x)cond(y))) { \ + UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + *utest_result = 1; \ + return; \ + } \ + } +#endif + +#define ASSERT_TRUE(x) \ + if (!(x)) { \ + UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : true\n"); \ + UTEST_PRINTF(" Actual : %s\n", (x) ? "true" : "false"); \ + *utest_result = 1; \ + return; \ + } + +#define ASSERT_FALSE(x) \ + if (x) { \ + UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : false\n"); \ + UTEST_PRINTF(" Actual : %s\n", (x) ? "true" : "false"); \ + *utest_result = 1; \ + return; \ + } + +#define ASSERT_EQ(x, y) UTEST_ASSERT(x, y, ==) +#define ASSERT_NE(x, y) UTEST_ASSERT(x, y, !=) +#define ASSERT_LT(x, y) UTEST_ASSERT(x, y, <) +#define ASSERT_LE(x, y) UTEST_ASSERT(x, y, <=) +#define ASSERT_GT(x, y) UTEST_ASSERT(x, y, >) +#define ASSERT_GE(x, y) UTEST_ASSERT(x, y, >=) + +#define ASSERT_STREQ(x, y) \ + EXPECT_STREQ(x, y); \ + if (0 != strcmp(x, y)) { \ + UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%s\"\n", x); \ + UTEST_PRINTF(" Actual : \"%s\"\n", y); \ + *utest_result = 1; \ + return; \ + } + +#define ASSERT_STRNE(x, y) \ + EXPECT_STRNE(x, y); \ + if (0 == strcmp(x, y)) { \ + UTEST_PRINTF("%s:%u: Failure\n", __FILE__, __LINE__); \ + UTEST_PRINTF(" Expected : \"%s\"\n", x); \ + UTEST_PRINTF(" Actual : \"%s\"\n", y); \ + *utest_result = 1; \ + return; \ + } + +#define UTEST(SET, NAME) \ + UTEST_EXTERN struct utest_state_s utest_state; \ + static void utest_run_##SET##_##NAME(int *utest_result); \ + static void utest_##SET##_##NAME(int *utest_result, size_t utest_index) { \ + (void)utest_index; \ + utest_run_##SET##_##NAME(utest_result); \ + } \ + UTEST_INITIALIZER(utest_register_##SET##_##NAME) { \ + const size_t index = utest_state.tests_length++; \ + const char *name_part = #SET "." #NAME; \ + const size_t name_size = strlen(name_part) + 1; \ + char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \ + utest_state.tests = \ + UTEST_PTR_CAST(struct utest_test_state_s *, \ + realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ + sizeof(struct utest_test_state_s) * \ + utest_state.tests_length)); \ + utest_state.tests[index].func = &utest_##SET##_##NAME; \ + utest_state.tests[index].name = name; \ + UTEST_SNPRINTF(name, name_size, "%s", name_part); \ + } \ + void utest_run_##SET##_##NAME(int *utest_result) + +#define UTEST_F_SETUP(FIXTURE) \ + static void utest_f_setup_##FIXTURE(int *utest_result, \ + struct FIXTURE *utest_fixture) + +#define UTEST_F_TEARDOWN(FIXTURE) \ + static void utest_f_teardown_##FIXTURE(int *utest_result, \ + struct FIXTURE *utest_fixture) + +#define UTEST_F(FIXTURE, NAME) \ + UTEST_EXTERN struct utest_state_s utest_state; \ + static void utest_f_setup_##FIXTURE(int *, struct FIXTURE *); \ + static void utest_f_teardown_##FIXTURE(int *, struct FIXTURE *); \ + static void utest_run_##FIXTURE##_##NAME(int *, struct FIXTURE *); \ + static void utest_f_##FIXTURE##_##NAME(int *utest_result, \ + size_t utest_index) { \ + struct FIXTURE fixture; \ + (void)utest_index; \ + memset(&fixture, 0, sizeof(fixture)); \ + utest_f_setup_##FIXTURE(utest_result, &fixture); \ + if (0 != *utest_result) { \ + return; \ + } \ + utest_run_##FIXTURE##_##NAME(utest_result, &fixture); \ + utest_f_teardown_##FIXTURE(utest_result, &fixture); \ + } \ + UTEST_INITIALIZER(utest_register_##FIXTURE##_##NAME) { \ + const size_t index = utest_state.tests_length++; \ + const char *name_part = #FIXTURE "." #NAME; \ + const size_t name_size = strlen(name_part) + 1; \ + char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \ + utest_state.tests = \ + UTEST_PTR_CAST(struct utest_test_state_s *, \ + realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ + sizeof(struct utest_test_state_s) * \ + utest_state.tests_length)); \ + utest_state.tests[index].func = &utest_f_##FIXTURE##_##NAME; \ + utest_state.tests[index].name = name; \ + UTEST_SNPRINTF(name, name_size, "%s", name_part); \ + } \ + void utest_run_##FIXTURE##_##NAME(int *utest_result, \ + struct FIXTURE *utest_fixture) + +#define UTEST_I_SETUP(FIXTURE) \ + static void utest_i_setup_##FIXTURE( \ + int *utest_result, struct FIXTURE *utest_fixture, size_t utest_index) + +#define UTEST_I_TEARDOWN(FIXTURE) \ + static void utest_i_teardown_##FIXTURE( \ + int *utest_result, struct FIXTURE *utest_fixture, size_t utest_index) + +#define UTEST_I(FIXTURE, NAME, INDEX) \ + UTEST_EXTERN struct utest_state_s utest_state; \ + static void utest_run_##FIXTURE##_##NAME##_##INDEX(int *, struct FIXTURE *); \ + static void utest_i_##FIXTURE##_##NAME##_##INDEX(int *utest_result, \ + size_t index) { \ + struct FIXTURE fixture; \ + memset(&fixture, 0, sizeof(fixture)); \ + utest_i_setup_##FIXTURE(utest_result, &fixture, index); \ + if (0 != *utest_result) { \ + return; \ + } \ + utest_run_##FIXTURE##_##NAME##_##INDEX(utest_result, &fixture); \ + utest_i_teardown_##FIXTURE(utest_result, &fixture, index); \ + } \ + UTEST_INITIALIZER(utest_register_##FIXTURE##_##NAME##_##INDEX) { \ + size_t i; \ + uint64_t iUp; \ + for (i = 0; i < (INDEX); i++) { \ + const size_t index = utest_state.tests_length++; \ + const char *name_part = #FIXTURE "." #NAME; \ + const size_t name_size = strlen(name_part) + 32; \ + char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \ + utest_state.tests = \ + UTEST_PTR_CAST(struct utest_test_state_s *, \ + realloc(UTEST_PTR_CAST(void *, utest_state.tests), \ + sizeof(struct utest_test_state_s) * \ + utest_state.tests_length)); \ + utest_state.tests[index].func = &utest_i_##FIXTURE##_##NAME##_##INDEX; \ + utest_state.tests[index].index = i; \ + utest_state.tests[index].name = name; \ + iUp = UTEST_CAST(uint64_t, i); \ + UTEST_SNPRINTF(name, name_size, "%s/%" UTEST_PRIu64, name_part, iUp); \ + } \ + } \ + void utest_run_##FIXTURE##_##NAME##_##INDEX(int *utest_result, \ + struct FIXTURE *utest_fixture) + +UTEST_WEAK +int utest_should_filter_test(const char *filter, const char *testcase); +UTEST_WEAK int utest_should_filter_test(const char *filter, + const char *testcase) { + if (filter) { + const char *filter_cur = filter; + const char *testcase_cur = testcase; + const char *filter_wildcard = 0; + + while (('\0' != *filter_cur) && ('\0' != *testcase_cur)) { + if ('*' == *filter_cur) { + /* store the position of the wildcard */ + filter_wildcard = filter_cur; + + /* skip the wildcard character */ + filter_cur++; + + while (('\0' != *filter_cur) && ('\0' != *testcase_cur)) { + if ('*' == *filter_cur) { + /* + we found another wildcard (filter is something like *foo*) so we + exit the current loop, and return to the parent loop to handle + the wildcard case + */ + break; + } else if (*filter_cur != *testcase_cur) { + /* otherwise our filter didn't match, so reset it */ + filter_cur = filter_wildcard; + } + + /* move testcase along */ + testcase_cur++; + + /* move filter along */ + filter_cur++; + } + + if (('\0' == *filter_cur) && ('\0' == *testcase_cur)) { + return 0; + } + + /* if the testcase has been exhausted, we don't have a match! */ + if ('\0' == *testcase_cur) { + return 1; + } + } else { + if (*testcase_cur != *filter_cur) { + /* test case doesn't match filter */ + return 1; + } else { + /* move our filter and testcase forward */ + testcase_cur++; + filter_cur++; + } + } + } + + if (('\0' != *filter_cur) || + (('\0' != *testcase_cur) && + ((filter == filter_cur) || ('*' != filter_cur[-1])))) { + /* we have a mismatch! */ + return 1; + } + } + + return 0; +} + +static UTEST_INLINE int utest_strncmp(const char *a, const char *b, size_t n) { + /* strncmp breaks on Wall / Werror on gcc/clang, so we avoid using it */ + unsigned i; + + for (i = 0; i < n; i++) { + if (a[i] < b[i]) { + return -1; + } else if (a[i] > b[i]) { + return 1; + } + } + + return 0; +} + +static UTEST_INLINE FILE *utest_fopen(const char *filename, const char *mode) { +#ifdef _MSC_VER + FILE *file; + if (0 == fopen_s(&file, filename, mode)) { + return file; + } else { + return 0; + } +#else + return fopen(filename, mode); +#endif +} + +UTEST_WEAK int utest_main(int argc, const char *const argv[]); +UTEST_WEAK int utest_main(int argc, const char *const argv[]) { + uint64_t failed = 0; + size_t index = 0; + size_t *failed_testcases = 0; + size_t failed_testcases_length = 0; + const char *filter = 0; + uint64_t ran_tests = 0; + + enum colours { RESET, GREEN, RED }; + + const int use_colours = UTEST_COLOUR_OUTPUT(); + const char *colours[] = {"\033[0m", "\033[32m", "\033[31m"}; + if (!use_colours) { + for (index = 0; index < sizeof colours / sizeof colours[0]; index++) { + colours[index] = ""; + } + } + /* loop through all arguments looking for our options */ + for (index = 1; index < UTEST_CAST(size_t, argc); index++) { + const char help_str[] = "--help"; + const char filter_str[] = "--filter="; + const char output_str[] = "--output="; + + if (0 == utest_strncmp(argv[index], help_str, strlen(help_str))) { + printf("utest.h - the single file unit testing solution for C/C++!\n" + "Command line Options:\n" + " --help Show this message and exit.\n" + " --filter= Filter the test cases to run (EG. MyTest*.a " + "would run MyTestCase.a but not MyTestCase.b).\n" + " --output= Output an xunit XML file to the file " + "specified in .\n"); + goto cleanup; + } else if (0 == + utest_strncmp(argv[index], filter_str, strlen(filter_str))) { + /* user wants to filter what test cases run! */ + filter = argv[index] + strlen(filter_str); + } else if (0 == + utest_strncmp(argv[index], output_str, strlen(output_str))) { + utest_state.output = utest_fopen(argv[index] + strlen(output_str), "w+"); + } + } + + for (index = 0; index < utest_state.tests_length; index++) { + if (utest_should_filter_test(filter, utest_state.tests[index].name)) { + continue; + } + + ran_tests++; + } + + printf("%s[==========]%s Running %" UTEST_PRIu64 " test cases.\n", + colours[GREEN], colours[RESET], UTEST_CAST(uint64_t, ran_tests)); + + if (utest_state.output) { + fprintf(utest_state.output, "\n"); + fprintf(utest_state.output, + "\n", + UTEST_CAST(uint64_t, ran_tests)); + fprintf(utest_state.output, + "\n", + UTEST_CAST(uint64_t, ran_tests)); + } + + for (index = 0; index < utest_state.tests_length; index++) { + int result = 0; + int64_t ns = 0; + + if (utest_should_filter_test(filter, utest_state.tests[index].name)) { + continue; + } + + printf("%s[ RUN ]%s %s\n", colours[GREEN], colours[RESET], + utest_state.tests[index].name); + + if (utest_state.output) { + fprintf(utest_state.output, "", + utest_state.tests[index].name); + } + + ns = utest_ns(); + utest_state.tests[index].func(&result, utest_state.tests[index].index); + ns = utest_ns() - ns; + + if (utest_state.output) { + fprintf(utest_state.output, "\n"); + } + + if (0 != result) { + const size_t failed_testcase_index = failed_testcases_length++; + failed_testcases = UTEST_PTR_CAST( + size_t *, realloc(UTEST_PTR_CAST(void *, failed_testcases), + sizeof(size_t) * failed_testcases_length)); + failed_testcases[failed_testcase_index] = index; + failed++; + printf("%s[ FAILED ]%s %s (%" UTEST_PRId64 "ns)\n", colours[RED], + colours[RESET], utest_state.tests[index].name, ns); + } else { + printf("%s[ OK ]%s %s (%" UTEST_PRId64 "ns)\n", colours[GREEN], + colours[RESET], utest_state.tests[index].name, ns); + } + } + + printf("%s[==========]%s %" UTEST_PRIu64 " test cases ran.\n", colours[GREEN], + colours[RESET], ran_tests); + printf("%s[ PASSED ]%s %" UTEST_PRIu64 " tests.\n", colours[GREEN], + colours[RESET], ran_tests - failed); + + if (0 != failed) { + printf("%s[ FAILED ]%s %" UTEST_PRIu64 " tests, listed below:\n", + colours[RED], colours[RESET], failed); + for (index = 0; index < failed_testcases_length; index++) { + printf("%s[ FAILED ]%s %s\n", colours[RED], colours[RESET], + utest_state.tests[failed_testcases[index]].name); + } + } + + if (utest_state.output) { + fprintf(utest_state.output, "\n\n"); + } + +cleanup: + for (index = 0; index < utest_state.tests_length; index++) { + free(UTEST_PTR_CAST(void *, utest_state.tests[index].name)); + } + + free(UTEST_PTR_CAST(void *, failed_testcases)); + free(UTEST_PTR_CAST(void *, utest_state.tests)); + + if (utest_state.output) { + fclose(utest_state.output); + } + + return UTEST_CAST(int, failed); +} + +/* + we need, in exactly one source file, define the global struct that will hold + the data we need to run utest. This macro allows the user to declare the + data without having to use the UTEST_MAIN macro, thus allowing them to write + their own main() function. +*/ +#define UTEST_STATE() struct utest_state_s utest_state = {0, 0, 0} + +/* + define a main() function to call into utest.h and start executing tests! A + user can optionally not use this macro, and instead define their own main() + function and manually call utest_main. The user must, in exactly one source + file, use the UTEST_STATE macro to declare a global struct variable that + utest requires. +*/ +#define UTEST_MAIN() \ + UTEST_STATE(); \ + int main(int argc, const char *const argv[]) { \ + return utest_main(argc, argv); \ + } + +#endif /* SHEREDOM_UTEST_H_INCLUDED */ diff --git a/source/engine/thirdparty/sokol/tests/test_android.sh b/source/engine/thirdparty/sokol/tests/test_android.sh new file mode 100755 index 0000000..88fb1c9 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/test_android.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -e +source test_common.sh +setup_android +build android_debug android_debug +build android_release android_release +build android_sles_debug android_sles_debug +build android_sles_release android_sles_release diff --git a/source/engine/thirdparty/sokol/tests/test_common.sh b/source/engine/thirdparty/sokol/tests/test_common.sh new file mode 100644 index 0000000..43f056b --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/test_common.sh @@ -0,0 +1,50 @@ +setup_emsdk() { + if [ ! -d "build/emsdk" ] ; then + mkdir -p build && cd build + git clone https://github.com/emscripten-core/emsdk.git + cd emsdk + ./emsdk install latest + ./emsdk activate latest + cd ../.. + fi + source build/emsdk/emsdk_env.sh +} + +setup_android() { + if [ ! -d "build/android_sdk" ] ; then + mkdir -p build/android_sdk && cd build/android_sdk + sdk_file="sdk-tools-linux-3859397.zip" + wget --no-verbose https://dl.google.com/android/repository/$sdk_file + unzip -q $sdk_file + cd tools/bin + yes | ./sdkmanager "platforms;android-28" >/dev/null + yes | ./sdkmanager "build-tools;29.0.3" >/dev/null + yes | ./sdkmanager "platform-tools" >/dev/null + yes | ./sdkmanager "ndk-bundle" >/dev/null + cd ../../../.. + fi +} + +build() { + gen_preset=$1 + build_preset=$2 + cmake --preset $gen_preset + cmake --build --preset $build_preset +} + +analyze() { + cfg=$1 + backend=$2 + mode=$3 + mkdir -p build/$cfg && cd build/$cfg + cmake -GNinja -DSOKOL_BACKEND=$backend -DCMAKE_BUILD_TYPE=$mode -DUSE_ANALYZER=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ../.. + cmake --build . + cd ../.. +} + +runtest() { + cfg=$1 + cd build/$cfg + ./sokol-test + cd ../../.. +} diff --git a/source/engine/thirdparty/sokol/tests/test_emscripten.sh b/source/engine/thirdparty/sokol/tests/test_emscripten.sh new file mode 100755 index 0000000..aff4b08 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/test_emscripten.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -e +source test_common.sh +setup_emsdk +build emsc_webgl2_debug emsc_webgl2_debug +build emsc_webgl2_release emsc_webgl2_release +build emsc_wgpu_debug emsc_wgpu_debug +build emsc_wgpu_release emsc_wgpu_release diff --git a/source/engine/thirdparty/sokol/tests/test_ios.sh b/source/engine/thirdparty/sokol/tests/test_ios.sh new file mode 100755 index 0000000..39e8868 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/test_ios.sh @@ -0,0 +1,10 @@ +set -e +source test_common.sh +build ios_gl ios_gl_debug +build ios_gl ios_gl_release +build ios_metal ios_metal_debug +build ios_metal ios_metal_release +build ios_arc_gl ios_arc_gl_debug +build ios_arc_gl ios_arc_gl_release +build ios_arc_metal ios_arc_metal_debug +build ios_arc_metal ios_arc_metal_release diff --git a/source/engine/thirdparty/sokol/tests/test_linux.sh b/source/engine/thirdparty/sokol/tests/test_linux.sh new file mode 100755 index 0000000..ae35e8d --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/test_linux.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -e +source test_common.sh +build linux_gl_debug linux_gl_debug +build linux_gl_release linux_gl_release +build linux_gles3_debug linux_gles3_debug +build linux_gles3_release linux_gles3_release +build linux_gl_egl_debug linux_gl_egl_debug +build linux_gl_egl_release linux_gl_egl_release +runtest linux_gl_debug diff --git a/source/engine/thirdparty/sokol/tests/test_macos.sh b/source/engine/thirdparty/sokol/tests/test_macos.sh new file mode 100755 index 0000000..0c0299a --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/test_macos.sh @@ -0,0 +1,11 @@ +set -e +source test_common.sh +build macos_gl_debug macos_gl_debug +build macos_gl_release macos_gl_release +build macos_metal_debug macos_metal_debug +build macos_metal_release macos_metal_release +build macos_arc_gl_debug macos_arc_gl_debug +build macos_arc_gl_release macos_arc_gl_release +build macos_arc_metal_debug macos_arc_metal_debug +build macos_arc_metal_release macos_arc_metal_release +runtest macos_gl_debug diff --git a/source/engine/thirdparty/sokol/tests/test_win.cmd b/source/engine/thirdparty/sokol/tests/test_win.cmd new file mode 100644 index 0000000..d4c16a7 --- /dev/null +++ b/source/engine/thirdparty/sokol/tests/test_win.cmd @@ -0,0 +1,11 @@ +cmake --preset win_gl || exit /b 10 +cmake --build --preset win_gl_debug || exit /b 10 +cmake --build --preset win_gl_release || exit /b 10 + +cmake --preset win_d3d11 || exit /b 10 +cmake --build --preset win_d3d11_debug || exit /b 10 +cmake --build --preset win_d3d11_release || exit /b 10 + +cd build\win_d3d11\Debug +sokol-test.exe || exit /b 10 +cd ..\..\.. diff --git a/source/engine/thirdparty/sokol/util/gen_sokol_color.py b/source/engine/thirdparty/sokol/util/gen_sokol_color.py new file mode 100644 index 0000000..f19a2ba --- /dev/null +++ b/source/engine/thirdparty/sokol/util/gen_sokol_color.py @@ -0,0 +1,500 @@ +#------------------------------------------------------------------------------- +# Generate the sokol_color.h header from a predefined palette +#------------------------------------------------------------------------------- +# LICENSE +# ======= +# +# zlib/libpng license +# +# Copyright (c) 2020 Stuart Adams +# +# 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. + +colors = [ +("Alice Blue", 0xF0F8FFFF), +("Antique White", 0xFAEBD7FF), +("Aqua", 0x00FFFFFF), +("Aquamarine", 0x7FFFD4FF), +("Azure", 0xF0FFFFFF), +("Beige", 0xF5F5DCFF), +("Bisque", 0xFFE4C4FF), +("Black", 0x000000FF), +("Blanched Almond", 0xFFEBCDFF), +("Blue", 0x0000FFFF), +("Blue Violet", 0x8A2BE2FF), +("Brown", 0xA52A2AFF), +("Burlywood", 0xDEB887FF), +("Cadet Blue", 0x5F9EA0FF), +("Chartreuse", 0x7FFF00FF), +("Chocolate", 0xD2691EFF), +("Coral", 0xFF7F50FF), +("Cornflower Blue", 0x6495EDFF), +("Cornsilk", 0xFFF8DCFF), +("Crimson", 0xDC143CFF), +("Cyan", 0x00FFFFFF), +("Dark Blue", 0x00008BFF), +("Dark Cyan", 0x008B8BFF), +("Dark Goldenrod", 0xB8860BFF), +("Dark Gray", 0xA9A9A9FF), +("Dark Green", 0x006400FF), +("Dark Khaki", 0xBDB76BFF), +("Dark Magenta", 0x8B008BFF), +("Dark Olive Green", 0x556B2FFF), +("Dark Orange", 0xFF8C00FF), +("Dark Orchid", 0x9932CCFF), +("Dark Red", 0x8B0000FF), +("Dark Salmon", 0xE9967AFF), +("Dark Sea Green", 0x8FBC8FFF), +("Dark Slate Blue", 0x483D8BFF), +("Dark Slate Gray", 0x2F4F4FFF), +("Dark Turquoise", 0x00CED1FF), +("Dark Violet", 0x9400D3FF), +("Deep Pink", 0xFF1493FF), +("Deep Sky Blue", 0x00BFFFFF), +("Dim Gray", 0x696969FF), +("Dodger Blue", 0x1E90FFFF), +("Firebrick", 0xB22222FF), +("Floral White", 0xFFFAF0FF), +("Forest Green", 0x228B22FF), +("Fuchsia", 0xFF00FFFF), +("Gainsboro", 0xDCDCDCFF), +("Ghost White", 0xF8F8FFFF), +("Gold", 0xFFD700FF), +("Goldenrod", 0xDAA520FF), +("Gray", 0xBEBEBEFF), +("Web Gray", 0x808080FF), +("Green", 0x00FF00FF), +("Web Green", 0x008000FF), +("Green Yellow", 0xADFF2FFF), +("Honeydew", 0xF0FFF0FF), +("Hot Pink", 0xFF69B4FF), +("Indian Red", 0xCD5C5CFF), +("Indigo", 0x4B0082FF), +("Ivory", 0xFFFFF0FF), +("Khaki", 0xF0E68CFF), +("Lavender", 0xE6E6FAFF), +("Lavender Blush", 0xFFF0F5FF), +("Lawn Green", 0x7CFC00FF), +("Lemon Chiffon", 0xFFFACDFF), +("Light Blue", 0xADD8E6FF), +("Light Coral", 0xF08080FF), +("Light Cyan", 0xE0FFFFFF), +("Light Goldenrod", 0xFAFAD2FF), +("Light Gray", 0xD3D3D3FF), +("Light Green", 0x90EE90FF), +("Light Pink", 0xFFB6C1FF), +("Light Salmon", 0xFFA07AFF), +("Light Sea Green", 0x20B2AAFF), +("Light Sky Blue", 0x87CEFAFF), +("Light Slate Gray", 0x778899FF), +("Light Steel Blue", 0xB0C4DEFF), +("Light Yellow", 0xFFFFE0FF), +("Lime", 0x00FF00FF), +("Lime Green", 0x32CD32FF), +("Linen", 0xFAF0E6FF), +("Magenta", 0xFF00FFFF), +("Maroon", 0xB03060FF), +("Web Maroon", 0x800000FF), +("Medium Aquamarine", 0x66CDAAFF), +("Medium Blue", 0x0000CDFF), +("Medium Orchid", 0xBA55D3FF), +("Medium Purple", 0x9370DBFF), +("Medium Sea Green", 0x3CB371FF), +("Medium Slate Blue", 0x7B68EEFF), +("Medium Spring Green", 0x00FA9AFF), +("Medium Turquoise", 0x48D1CCFF), +("Medium Violet Red", 0xC71585FF), +("Midnight Blue", 0x191970FF), +("Mint Cream", 0xF5FFFAFF), +("Misty Rose", 0xFFE4E1FF), +("Moccasin", 0xFFE4B5FF), +("Navajo White", 0xFFDEADFF), +("Navy Blue", 0x000080FF), +("Old Lace", 0xFDF5E6FF), +("Olive", 0x808000FF), +("Olive Drab", 0x6B8E23FF), +("Orange", 0xFFA500FF), +("Orange Red", 0xFF4500FF), +("Orchid", 0xDA70D6FF), +("Pale Goldenrod", 0xEEE8AAFF), +("Pale Green", 0x98FB98FF), +("Pale Turquoise", 0xAFEEEEFF), +("Pale Violet Red", 0xDB7093FF), +("Papaya Whip", 0xFFEFD5FF), +("Peach Puff", 0xFFDAB9FF), +("Peru", 0xCD853FFF), +("Pink", 0xFFC0CBFF), +("Plum", 0xDDA0DDFF), +("Powder Blue", 0xB0E0E6FF), +("Purple", 0xA020F0FF), +("Web Purple", 0x800080FF), +("Rebecca Purple", 0x663399FF), +("Red", 0xFF0000FF), +("Rosy Brown", 0xBC8F8FFF), +("Royal Blue", 0x4169E1FF), +("Saddle Brown", 0x8B4513FF), +("Salmon", 0xFA8072FF), +("Sandy Brown", 0xF4A460FF), +("Sea Green", 0x2E8B57FF), +("Seashell", 0xFFF5EEFF), +("Sienna", 0xA0522DFF), +("Silver", 0xC0C0C0FF), +("Sky Blue", 0x87CEEBFF), +("Slate Blue", 0x6A5ACDFF), +("Slate Gray", 0x708090FF), +("Snow", 0xFFFAFAFF), +("Spring Green", 0x00FF7FFF), +("Steel Blue", 0x4682B4FF), +("Tan", 0xD2B48CFF), +("Teal", 0x008080FF), +("Thistle", 0xD8BFD8FF), +("Tomato", 0xFF6347FF), +("Transparent", 0x00000000), +("Turquoise", 0x40E0D0FF), +("Violet", 0xEE82EEFF), +("Wheat", 0xF5DEB3FF), +("White", 0xFFFFFFFF), +("White Smoke", 0xF5F5F5FF), +("Yellow", 0xFFFF00FF), +("Yellow Green", 0x9ACD32FF) +] + +header = open("sokol_color.h", "w") + +header.write("""#if defined(SOKOL_IMPL) && !defined(SOKOL_COLOR_IMPL) +#define SOKOL_COLOR_IMPL +#endif +#ifndef SOKOL_COLOR_INCLUDED +/* + sokol_color.h -- sg_color utilities + + This header was generated by gen_sokol_color.py. Do not modify it. + + Project URL: https://github.com/floooh/sokol + + Include the following headers before including sokol_color.h: + + sokol_gfx.h + + FEATURE OVERVIEW + ================ + sokol_color.h defines preset colors based on the X11 color names, + alongside utility functions to create and modify sg_color objects. + + The predefined colors are based on the X11 color names: + + https://en.wikipedia.org/wiki/X11_color_names + + This palette is useful for prototyping - lots of programmers are familiar with + these colours due to their use in X11, web development and XNA / MonoGame. They + are also handy when you want to reference a familiar color, but don't want to + write it out by hand. + + COLORS + ====== + The palette is defined using static const (or constexpr if you are using a + C++ compiler) objects. These objects use lowercase names: + + static SOKOL_COLOR_CONSTEXPR sg_color sg_red = SG_RED; + static SOKOL_COLOR_CONSTEXPR sg_color sg_green = SG_GREEN; + static SOKOL_COLOR_CONSTEXPR sg_color sg_blue = SG_BLUE; + + An sg_color preset object like sg_red can be used to initialize + an sg_pass_action: + + sg_pass_action pass_action = { + .colors[0] = { .action=SG_ACTION_CLEAR, .value = sg_red } + }; + + Initializing an object with static storage duration is more complicated + because of C language rules. Technically, a static const is not a + compile-time constant in C. To work around this, the palette is also + defined as a series of brace-enclosed list macro definitions. These + definitions use uppercase names: + + #define SG_RED { 1.0f, 0.0f, 0.0f, 1.0f } + #define SG_GREEN { 0.0f, 1.0f, 0.0f, 1.0f } + #define SG_BLUE { 0.0f, 0.0f, 1.0f, 1.0f } + + A preset macro like SG_RED can be used to initialize objects with static + storage duration: + + static struct { + sg_pass_action pass_action; + } state = { + .pass_action = { + .colors[0] = { .action = SG_ACTION_CLEAR, .value = SG_RED } + } + }; + + A second set of macro definitions exists for colors packed as 32 bit integer + values. These definitions are also uppercase, but use the _RGBA32 suffix: + + #define SG_RED_RGBA32 0xFF0000FF + #define SG_GREEN_RGBA32 0x00FF00FF + #define SG_BLUE_RGBA32 0x0000FFFF + + This is useful if your code makes use of packed colors, as sokol_gl.h does for its + internal vertex format: + + sgl_begin_triangles(); + sgl_v2f_c1i( 0.0f, 0.5f, SG_RED_RGBA32); + sgl_v2f_c1i( 0.5f, -0.5f, SG_GREEN_RGBA32); + sgl_v2f_c1i(-0.5f, -0.5f, SG_BLUE_RGBA32); + sgl_end(); + + UTILITY FUNCTIONS + ================= + + Utility functions for creating colours are provided: + + - sg_make_color_4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + Create a sg_color object from separate R, G, B, A bytes. + + - sg_make_color_1i(uint32_t rgba) + Create a sg_color object from RGBA bytes packed into a 32-bit unsigned integer. + + - sg_color_lerp(const sg_color* color_a, const sg_color* color_b, float amount) + Linearly interpolate a color. + + - sg_color_lerp_precise(const sg_color* color_a, const sg_color* color_b, float amount) + Linearly interpolate a color. Less efficient but more precise than sg_color_lerp. + + - sg_color_multiply(const sg_color* color, float scale) + Multiply each color component by the scale factor. + + LICENSE + ======= + + zlib/libpng license + + Copyright (c) 2020 Stuart Adams + + 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_COLOR_INCLUDED (1) + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_color.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_GL_API_DECL) +#define SOKOL_COLOR_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_COLOR_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_COLOR_IMPL) +#define SOKOL_COLOR_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_COLOR_API_DECL __declspec(dllimport) +#else +#define SOKOL_COLOR_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +#define SOKOL_COLOR_CONSTEXPR constexpr +extern "C" { +#else +#define SOKOL_COLOR_CONSTEXPR const +#endif + +SOKOL_COLOR_API_DECL sg_color sg_make_color_4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_COLOR_API_DECL sg_color sg_make_color_1i(uint32_t rgba); +SOKOL_COLOR_API_DECL sg_color sg_color_lerp(const sg_color* color_a, const sg_color* color_b, float amount); +SOKOL_COLOR_API_DECL sg_color sg_color_lerp_precise(const sg_color* color_a, const sg_color* color_b, float amount); +SOKOL_COLOR_API_DECL sg_color sg_color_multiply(const sg_color* color, float scale); + +""") + +def unpack_rgba(color): + red = (color & 0xFF000000) >> 24 + green = (color & 0xFF0000) >> 16 + blue = (color & 0xFF00) >> 8 + alpha = (color & 0xFF) + return (red, green, blue, alpha) + +def add_documentation(color): + documentation = "/* {name} color {{ R:{r}, G:{g}, B:{b}, A:{a} }} */\n" + rgba = unpack_rgba(color[1]) + header.write(documentation.format( + name = color[0], r = rgba[0], g = rgba[1], b = rgba[2], a = rgba[3])) + +for color in colors: + add_documentation(color) + init_color = "SG_" + color[0].upper().replace(" ", "_") + init_color_definition = "#define {name} {{ {r}f, {g}f, {b}f, {a}f }}\n" + rgba = unpack_rgba(color[1]) + r = rgba[0] / 255 + g = rgba[1] / 255 + b = rgba[2] / 255 + a = rgba[3] / 255 + r_text = "{:.1f}".format(r) if r.is_integer() else "{:.9g}".format(r) + g_text = "{:.1f}".format(g) if g.is_integer() else "{:.9g}".format(g) + b_text = "{:.1f}".format(b) if b.is_integer() else "{:.9g}".format(b) + a_text = "{:.1f}".format(a) if a.is_integer() else "{:.9g}".format(a) + header.write(init_color_definition.format( + name = init_color, r = r_text, g = g_text, b = b_text, a = a_text)) + +header.write("\n") + +for color in colors: + add_documentation(color) + init_color = "sg_" + color[0].lower().replace(" ", "_") + init_color_definition = "static SOKOL_COLOR_CONSTEXPR sg_color {name} = {init};\n" + init_color_name = "SG_" + color[0].upper().replace(" ", "_") + header.write(init_color_definition.format(name = init_color, init = init_color_name)) + +header.write("\n") + +for color in colors: + add_documentation(color) + hex_color = "0x{0:08X}".format(color[1]) + packed_color = "SG_" + color[0].upper().replace(" ", "_") + "_RGBA32" + packed_color_definition = "#define {name} {rgba}\n" + header.write(packed_color_definition.format(name = packed_color, rgba = hex_color)) + +header.write(""" +#ifdef __cplusplus +} /* extern "C" */ + +inline sg_color sg_make_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return sg_make_color_4b(r, g, b, a); +} + +inline sg_color sg_make_color(uint32_t rgba) { + return sg_make_color_1i(rgba); +} + +inline sg_color sg_color_lerp(const sg_color& color_a, const sg_color& color_b, float amount) { + return sg_color_lerp(&color_a, &color_b, amount); +} + +inline sg_color sg_color_lerp_precise(const sg_color& color_a, const sg_color& color_b, float amount) { + return sg_color_lerp_precise(&color_a, &color_b, amount); +} + +inline sg_color sg_color_multiply(const sg_color& color, float scale) { + return sg_color_multiply(&color, scale); +} + +#endif /* __cplusplus */ + +#endif /* SOKOL_COLOR_INCLUDED */ + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_COLOR_IMPL +#define SOKOL_COLOR_IMPL_INCLUDED (1) + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif + +static inline float _sg_color_clamp(float v, float low, float high) { + if (v < low) { + return low; + } else if (v > high) { + return high; + } + return v; +} + +static inline float _sg_color_lerp(float a, float b, float amount) { + return a + (b - a) * amount; +} + +static inline float _sg_color_lerp_precise(float a, float b, float amount) { + return ((1.0f - amount) * a) + (b * amount); +} + +SOKOL_API_IMPL sg_color sg_make_color_4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + sg_color result; + result.r = r / 255.0f; + result.g = g / 255.0f; + result.b = b / 255.0f; + result.a = a / 255.0f; + return result; +} + +SOKOL_API_IMPL sg_color sg_make_color_1i(uint32_t rgba) { + return sg_make_color_4b( + (uint8_t)(rgba >> 24), + (uint8_t)(rgba >> 16), + (uint8_t)(rgba >> 8), + (uint8_t)(rgba >> 0) + ); +} + +SOKOL_API_IMPL sg_color sg_color_lerp(const sg_color* color_a, const sg_color* color_b, float amount) { + SOKOL_ASSERT(color_a); + SOKOL_ASSERT(color_b); + amount = _sg_color_clamp(amount, 0.0f, 1.0f); + sg_color result; + result.r = _sg_color_lerp(color_a->r, color_b->r, amount); + result.g = _sg_color_lerp(color_a->g, color_b->g, amount); + result.b = _sg_color_lerp(color_a->b, color_b->b, amount); + result.a = _sg_color_lerp(color_a->a, color_b->a, amount); + return result; +} + +SOKOL_API_IMPL sg_color sg_color_lerp_precise(const sg_color* color_a, const sg_color* color_b, float amount) { + SOKOL_ASSERT(color_a); + SOKOL_ASSERT(color_b); + amount = _sg_color_clamp(amount, 0.0f, 1.0f); + sg_color result; + result.r = _sg_color_lerp_precise(color_a->r, color_b->r, amount); + result.g = _sg_color_lerp_precise(color_a->g, color_b->g, amount); + result.b = _sg_color_lerp_precise(color_a->b, color_b->b, amount); + result.a = _sg_color_lerp_precise(color_a->a, color_b->a, amount); + return result; +} + +SOKOL_API_IMPL sg_color sg_color_multiply(const sg_color* color, float scale) { + SOKOL_ASSERT(color); + sg_color result; + result.r = color->r * scale; + result.g = color->g * scale; + result.b = color->b * scale; + result.a = color->a * scale; + return result; +} + +#endif /* SOKOL_COLOR_IMPL */ +""") diff --git a/source/engine/thirdparty/sokol/util/sokol_color.h b/source/engine/thirdparty/sokol/util/sokol_color.h new file mode 100644 index 0000000..cc31b44 --- /dev/null +++ b/source/engine/thirdparty/sokol/util/sokol_color.h @@ -0,0 +1,1148 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_COLOR_IMPL) +#define SOKOL_COLOR_IMPL +#endif +#ifndef SOKOL_COLOR_INCLUDED +/* + sokol_color.h -- sg_color utilities + + This header was generated by gen_sokol_color.py. Do not modify it. + + Project URL: https://github.com/floooh/sokol + + Include the following headers before including sokol_color.h: + + sokol_gfx.h + + FEATURE OVERVIEW + ================ + sokol_color.h defines preset colors based on the X11 color names, + alongside utility functions to create and modify sg_color objects. + + The predefined colors are based on the X11 color names: + + https://en.wikipedia.org/wiki/X11_color_names + + This palette is useful for prototyping - lots of programmers are familiar with + these colours due to their use in X11, web development and XNA / MonoGame. They + are also handy when you want to reference a familiar color, but don't want to + write it out by hand. + + COLORS + ====== + The palette is defined using static const (or constexpr if you are using a + C++ compiler) objects. These objects use lowercase names: + + static SOKOL_COLOR_CONSTEXPR sg_color sg_red = SG_RED; + static SOKOL_COLOR_CONSTEXPR sg_color sg_green = SG_GREEN; + static SOKOL_COLOR_CONSTEXPR sg_color sg_blue = SG_BLUE; + + An sg_color preset object like sg_red can be used to initialize + an sg_pass_action: + + sg_pass_action pass_action = { + .colors[0] = { .action=SG_ACTION_CLEAR, .value = sg_red } + }; + + Initializing an object with static storage duration is more complicated + because of C language rules. Technically, a static const is not a + compile-time constant in C. To work around this, the palette is also + defined as a series of brace-enclosed list macro definitions. These + definitions use uppercase names: + + #define SG_RED { 1.0f, 0.0f, 0.0f, 1.0f } + #define SG_GREEN { 0.0f, 1.0f, 0.0f, 1.0f } + #define SG_BLUE { 0.0f, 0.0f, 1.0f, 1.0f } + + A preset macro like SG_RED can be used to initialize objects with static + storage duration: + + static struct { + sg_pass_action pass_action; + } state = { + .pass_action = { + .colors[0] = { .action = SG_ACTION_CLEAR, .value = SG_RED } + } + }; + + A second set of macro definitions exists for colors packed as 32 bit integer + values. These definitions are also uppercase, but use the _RGBA32 suffix: + + #define SG_RED_RGBA32 0xFF0000FF + #define SG_GREEN_RGBA32 0x00FF00FF + #define SG_BLUE_RGBA32 0x0000FFFF + + This is useful if your code makes use of packed colors, as sokol_gl.h does for its + internal vertex format: + + sgl_begin_triangles(); + sgl_v2f_c1i( 0.0f, 0.5f, SG_RED_RGBA32); + sgl_v2f_c1i( 0.5f, -0.5f, SG_GREEN_RGBA32); + sgl_v2f_c1i(-0.5f, -0.5f, SG_BLUE_RGBA32); + sgl_end(); + + UTILITY FUNCTIONS + ================= + + Utility functions for creating colours are provided: + + - sg_make_color_4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + Create a sg_color object from separate R, G, B, A bytes. + + - sg_make_color_1i(uint32_t rgba) + Create a sg_color object from RGBA bytes packed into a 32-bit unsigned integer. + + - sg_color_lerp(const sg_color* color_a, const sg_color* color_b, float amount) + Linearly interpolate a color. + + - sg_color_lerp_precise(const sg_color* color_a, const sg_color* color_b, float amount) + Linearly interpolate a color. Less efficient but more precise than sg_color_lerp. + + - sg_color_multiply(const sg_color* color, float scale) + Multiply each color component by the scale factor. + + LICENSE + ======= + + zlib/libpng license + + Copyright (c) 2020 Stuart Adams + + 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_COLOR_INCLUDED (1) + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_color.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_GL_API_DECL) +#define SOKOL_COLOR_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_COLOR_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_COLOR_IMPL) +#define SOKOL_COLOR_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_COLOR_API_DECL __declspec(dllimport) +#else +#define SOKOL_COLOR_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +#define SOKOL_COLOR_CONSTEXPR constexpr +extern "C" { +#else +#define SOKOL_COLOR_CONSTEXPR const +#endif + +SOKOL_COLOR_API_DECL sg_color sg_make_color_4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a); +SOKOL_COLOR_API_DECL sg_color sg_make_color_1i(uint32_t rgba); +SOKOL_COLOR_API_DECL sg_color sg_color_lerp(const sg_color* color_a, const sg_color* color_b, float amount); +SOKOL_COLOR_API_DECL sg_color sg_color_lerp_precise(const sg_color* color_a, const sg_color* color_b, float amount); +SOKOL_COLOR_API_DECL sg_color sg_color_multiply(const sg_color* color, float scale); + +/* Alice Blue color { R:240, G:248, B:255, A:255 } */ +#define SG_ALICE_BLUE { 0.941176471f, 0.97254902f, 1.0f, 1.0f } +/* Antique White color { R:250, G:235, B:215, A:255 } */ +#define SG_ANTIQUE_WHITE { 0.980392157f, 0.921568627f, 0.843137255f, 1.0f } +/* Aqua color { R:0, G:255, B:255, A:255 } */ +#define SG_AQUA { 0.0f, 1.0f, 1.0f, 1.0f } +/* Aquamarine color { R:127, G:255, B:212, A:255 } */ +#define SG_AQUAMARINE { 0.498039216f, 1.0f, 0.831372549f, 1.0f } +/* Azure color { R:240, G:255, B:255, A:255 } */ +#define SG_AZURE { 0.941176471f, 1.0f, 1.0f, 1.0f } +/* Beige color { R:245, G:245, B:220, A:255 } */ +#define SG_BEIGE { 0.960784314f, 0.960784314f, 0.862745098f, 1.0f } +/* Bisque color { R:255, G:228, B:196, A:255 } */ +#define SG_BISQUE { 1.0f, 0.894117647f, 0.768627451f, 1.0f } +/* Black color { R:0, G:0, B:0, A:255 } */ +#define SG_BLACK { 0.0f, 0.0f, 0.0f, 1.0f } +/* Blanched Almond color { R:255, G:235, B:205, A:255 } */ +#define SG_BLANCHED_ALMOND { 1.0f, 0.921568627f, 0.803921569f, 1.0f } +/* Blue color { R:0, G:0, B:255, A:255 } */ +#define SG_BLUE { 0.0f, 0.0f, 1.0f, 1.0f } +/* Blue Violet color { R:138, G:43, B:226, A:255 } */ +#define SG_BLUE_VIOLET { 0.541176471f, 0.168627451f, 0.88627451f, 1.0f } +/* Brown color { R:165, G:42, B:42, A:255 } */ +#define SG_BROWN { 0.647058824f, 0.164705882f, 0.164705882f, 1.0f } +/* Burlywood color { R:222, G:184, B:135, A:255 } */ +#define SG_BURLYWOOD { 0.870588235f, 0.721568627f, 0.529411765f, 1.0f } +/* Cadet Blue color { R:95, G:158, B:160, A:255 } */ +#define SG_CADET_BLUE { 0.37254902f, 0.619607843f, 0.62745098f, 1.0f } +/* Chartreuse color { R:127, G:255, B:0, A:255 } */ +#define SG_CHARTREUSE { 0.498039216f, 1.0f, 0.0f, 1.0f } +/* Chocolate color { R:210, G:105, B:30, A:255 } */ +#define SG_CHOCOLATE { 0.823529412f, 0.411764706f, 0.117647059f, 1.0f } +/* Coral color { R:255, G:127, B:80, A:255 } */ +#define SG_CORAL { 1.0f, 0.498039216f, 0.31372549f, 1.0f } +/* Cornflower Blue color { R:100, G:149, B:237, A:255 } */ +#define SG_CORNFLOWER_BLUE { 0.392156863f, 0.584313725f, 0.929411765f, 1.0f } +/* Cornsilk color { R:255, G:248, B:220, A:255 } */ +#define SG_CORNSILK { 1.0f, 0.97254902f, 0.862745098f, 1.0f } +/* Crimson color { R:220, G:20, B:60, A:255 } */ +#define SG_CRIMSON { 0.862745098f, 0.0784313725f, 0.235294118f, 1.0f } +/* Cyan color { R:0, G:255, B:255, A:255 } */ +#define SG_CYAN { 0.0f, 1.0f, 1.0f, 1.0f } +/* Dark Blue color { R:0, G:0, B:139, A:255 } */ +#define SG_DARK_BLUE { 0.0f, 0.0f, 0.545098039f, 1.0f } +/* Dark Cyan color { R:0, G:139, B:139, A:255 } */ +#define SG_DARK_CYAN { 0.0f, 0.545098039f, 0.545098039f, 1.0f } +/* Dark Goldenrod color { R:184, G:134, B:11, A:255 } */ +#define SG_DARK_GOLDENROD { 0.721568627f, 0.525490196f, 0.0431372549f, 1.0f } +/* Dark Gray color { R:169, G:169, B:169, A:255 } */ +#define SG_DARK_GRAY { 0.662745098f, 0.662745098f, 0.662745098f, 1.0f } +/* Dark Green color { R:0, G:100, B:0, A:255 } */ +#define SG_DARK_GREEN { 0.0f, 0.392156863f, 0.0f, 1.0f } +/* Dark Khaki color { R:189, G:183, B:107, A:255 } */ +#define SG_DARK_KHAKI { 0.741176471f, 0.717647059f, 0.419607843f, 1.0f } +/* Dark Magenta color { R:139, G:0, B:139, A:255 } */ +#define SG_DARK_MAGENTA { 0.545098039f, 0.0f, 0.545098039f, 1.0f } +/* Dark Olive Green color { R:85, G:107, B:47, A:255 } */ +#define SG_DARK_OLIVE_GREEN { 0.333333333f, 0.419607843f, 0.184313725f, 1.0f } +/* Dark Orange color { R:255, G:140, B:0, A:255 } */ +#define SG_DARK_ORANGE { 1.0f, 0.549019608f, 0.0f, 1.0f } +/* Dark Orchid color { R:153, G:50, B:204, A:255 } */ +#define SG_DARK_ORCHID { 0.6f, 0.196078431f, 0.8f, 1.0f } +/* Dark Red color { R:139, G:0, B:0, A:255 } */ +#define SG_DARK_RED { 0.545098039f, 0.0f, 0.0f, 1.0f } +/* Dark Salmon color { R:233, G:150, B:122, A:255 } */ +#define SG_DARK_SALMON { 0.91372549f, 0.588235294f, 0.478431373f, 1.0f } +/* Dark Sea Green color { R:143, G:188, B:143, A:255 } */ +#define SG_DARK_SEA_GREEN { 0.560784314f, 0.737254902f, 0.560784314f, 1.0f } +/* Dark Slate Blue color { R:72, G:61, B:139, A:255 } */ +#define SG_DARK_SLATE_BLUE { 0.282352941f, 0.239215686f, 0.545098039f, 1.0f } +/* Dark Slate Gray color { R:47, G:79, B:79, A:255 } */ +#define SG_DARK_SLATE_GRAY { 0.184313725f, 0.309803922f, 0.309803922f, 1.0f } +/* Dark Turquoise color { R:0, G:206, B:209, A:255 } */ +#define SG_DARK_TURQUOISE { 0.0f, 0.807843137f, 0.819607843f, 1.0f } +/* Dark Violet color { R:148, G:0, B:211, A:255 } */ +#define SG_DARK_VIOLET { 0.580392157f, 0.0f, 0.82745098f, 1.0f } +/* Deep Pink color { R:255, G:20, B:147, A:255 } */ +#define SG_DEEP_PINK { 1.0f, 0.0784313725f, 0.576470588f, 1.0f } +/* Deep Sky Blue color { R:0, G:191, B:255, A:255 } */ +#define SG_DEEP_SKY_BLUE { 0.0f, 0.749019608f, 1.0f, 1.0f } +/* Dim Gray color { R:105, G:105, B:105, A:255 } */ +#define SG_DIM_GRAY { 0.411764706f, 0.411764706f, 0.411764706f, 1.0f } +/* Dodger Blue color { R:30, G:144, B:255, A:255 } */ +#define SG_DODGER_BLUE { 0.117647059f, 0.564705882f, 1.0f, 1.0f } +/* Firebrick color { R:178, G:34, B:34, A:255 } */ +#define SG_FIREBRICK { 0.698039216f, 0.133333333f, 0.133333333f, 1.0f } +/* Floral White color { R:255, G:250, B:240, A:255 } */ +#define SG_FLORAL_WHITE { 1.0f, 0.980392157f, 0.941176471f, 1.0f } +/* Forest Green color { R:34, G:139, B:34, A:255 } */ +#define SG_FOREST_GREEN { 0.133333333f, 0.545098039f, 0.133333333f, 1.0f } +/* Fuchsia color { R:255, G:0, B:255, A:255 } */ +#define SG_FUCHSIA { 1.0f, 0.0f, 1.0f, 1.0f } +/* Gainsboro color { R:220, G:220, B:220, A:255 } */ +#define SG_GAINSBORO { 0.862745098f, 0.862745098f, 0.862745098f, 1.0f } +/* Ghost White color { R:248, G:248, B:255, A:255 } */ +#define SG_GHOST_WHITE { 0.97254902f, 0.97254902f, 1.0f, 1.0f } +/* Gold color { R:255, G:215, B:0, A:255 } */ +#define SG_GOLD { 1.0f, 0.843137255f, 0.0f, 1.0f } +/* Goldenrod color { R:218, G:165, B:32, A:255 } */ +#define SG_GOLDENROD { 0.854901961f, 0.647058824f, 0.125490196f, 1.0f } +/* Gray color { R:190, G:190, B:190, A:255 } */ +#define SG_GRAY { 0.745098039f, 0.745098039f, 0.745098039f, 1.0f } +/* Web Gray color { R:128, G:128, B:128, A:255 } */ +#define SG_WEB_GRAY { 0.501960784f, 0.501960784f, 0.501960784f, 1.0f } +/* Green color { R:0, G:255, B:0, A:255 } */ +#define SG_GREEN { 0.0f, 1.0f, 0.0f, 1.0f } +/* Web Green color { R:0, G:128, B:0, A:255 } */ +#define SG_WEB_GREEN { 0.0f, 0.501960784f, 0.0f, 1.0f } +/* Green Yellow color { R:173, G:255, B:47, A:255 } */ +#define SG_GREEN_YELLOW { 0.678431373f, 1.0f, 0.184313725f, 1.0f } +/* Honeydew color { R:240, G:255, B:240, A:255 } */ +#define SG_HONEYDEW { 0.941176471f, 1.0f, 0.941176471f, 1.0f } +/* Hot Pink color { R:255, G:105, B:180, A:255 } */ +#define SG_HOT_PINK { 1.0f, 0.411764706f, 0.705882353f, 1.0f } +/* Indian Red color { R:205, G:92, B:92, A:255 } */ +#define SG_INDIAN_RED { 0.803921569f, 0.360784314f, 0.360784314f, 1.0f } +/* Indigo color { R:75, G:0, B:130, A:255 } */ +#define SG_INDIGO { 0.294117647f, 0.0f, 0.509803922f, 1.0f } +/* Ivory color { R:255, G:255, B:240, A:255 } */ +#define SG_IVORY { 1.0f, 1.0f, 0.941176471f, 1.0f } +/* Khaki color { R:240, G:230, B:140, A:255 } */ +#define SG_KHAKI { 0.941176471f, 0.901960784f, 0.549019608f, 1.0f } +/* Lavender color { R:230, G:230, B:250, A:255 } */ +#define SG_LAVENDER { 0.901960784f, 0.901960784f, 0.980392157f, 1.0f } +/* Lavender Blush color { R:255, G:240, B:245, A:255 } */ +#define SG_LAVENDER_BLUSH { 1.0f, 0.941176471f, 0.960784314f, 1.0f } +/* Lawn Green color { R:124, G:252, B:0, A:255 } */ +#define SG_LAWN_GREEN { 0.48627451f, 0.988235294f, 0.0f, 1.0f } +/* Lemon Chiffon color { R:255, G:250, B:205, A:255 } */ +#define SG_LEMON_CHIFFON { 1.0f, 0.980392157f, 0.803921569f, 1.0f } +/* Light Blue color { R:173, G:216, B:230, A:255 } */ +#define SG_LIGHT_BLUE { 0.678431373f, 0.847058824f, 0.901960784f, 1.0f } +/* Light Coral color { R:240, G:128, B:128, A:255 } */ +#define SG_LIGHT_CORAL { 0.941176471f, 0.501960784f, 0.501960784f, 1.0f } +/* Light Cyan color { R:224, G:255, B:255, A:255 } */ +#define SG_LIGHT_CYAN { 0.878431373f, 1.0f, 1.0f, 1.0f } +/* Light Goldenrod color { R:250, G:250, B:210, A:255 } */ +#define SG_LIGHT_GOLDENROD { 0.980392157f, 0.980392157f, 0.823529412f, 1.0f } +/* Light Gray color { R:211, G:211, B:211, A:255 } */ +#define SG_LIGHT_GRAY { 0.82745098f, 0.82745098f, 0.82745098f, 1.0f } +/* Light Green color { R:144, G:238, B:144, A:255 } */ +#define SG_LIGHT_GREEN { 0.564705882f, 0.933333333f, 0.564705882f, 1.0f } +/* Light Pink color { R:255, G:182, B:193, A:255 } */ +#define SG_LIGHT_PINK { 1.0f, 0.71372549f, 0.756862745f, 1.0f } +/* Light Salmon color { R:255, G:160, B:122, A:255 } */ +#define SG_LIGHT_SALMON { 1.0f, 0.62745098f, 0.478431373f, 1.0f } +/* Light Sea Green color { R:32, G:178, B:170, A:255 } */ +#define SG_LIGHT_SEA_GREEN { 0.125490196f, 0.698039216f, 0.666666667f, 1.0f } +/* Light Sky Blue color { R:135, G:206, B:250, A:255 } */ +#define SG_LIGHT_SKY_BLUE { 0.529411765f, 0.807843137f, 0.980392157f, 1.0f } +/* Light Slate Gray color { R:119, G:136, B:153, A:255 } */ +#define SG_LIGHT_SLATE_GRAY { 0.466666667f, 0.533333333f, 0.6f, 1.0f } +/* Light Steel Blue color { R:176, G:196, B:222, A:255 } */ +#define SG_LIGHT_STEEL_BLUE { 0.690196078f, 0.768627451f, 0.870588235f, 1.0f } +/* Light Yellow color { R:255, G:255, B:224, A:255 } */ +#define SG_LIGHT_YELLOW { 1.0f, 1.0f, 0.878431373f, 1.0f } +/* Lime color { R:0, G:255, B:0, A:255 } */ +#define SG_LIME { 0.0f, 1.0f, 0.0f, 1.0f } +/* Lime Green color { R:50, G:205, B:50, A:255 } */ +#define SG_LIME_GREEN { 0.196078431f, 0.803921569f, 0.196078431f, 1.0f } +/* Linen color { R:250, G:240, B:230, A:255 } */ +#define SG_LINEN { 0.980392157f, 0.941176471f, 0.901960784f, 1.0f } +/* Magenta color { R:255, G:0, B:255, A:255 } */ +#define SG_MAGENTA { 1.0f, 0.0f, 1.0f, 1.0f } +/* Maroon color { R:176, G:48, B:96, A:255 } */ +#define SG_MAROON { 0.690196078f, 0.188235294f, 0.376470588f, 1.0f } +/* Web Maroon color { R:128, G:0, B:0, A:255 } */ +#define SG_WEB_MAROON { 0.501960784f, 0.0f, 0.0f, 1.0f } +/* Medium Aquamarine color { R:102, G:205, B:170, A:255 } */ +#define SG_MEDIUM_AQUAMARINE { 0.4f, 0.803921569f, 0.666666667f, 1.0f } +/* Medium Blue color { R:0, G:0, B:205, A:255 } */ +#define SG_MEDIUM_BLUE { 0.0f, 0.0f, 0.803921569f, 1.0f } +/* Medium Orchid color { R:186, G:85, B:211, A:255 } */ +#define SG_MEDIUM_ORCHID { 0.729411765f, 0.333333333f, 0.82745098f, 1.0f } +/* Medium Purple color { R:147, G:112, B:219, A:255 } */ +#define SG_MEDIUM_PURPLE { 0.576470588f, 0.439215686f, 0.858823529f, 1.0f } +/* Medium Sea Green color { R:60, G:179, B:113, A:255 } */ +#define SG_MEDIUM_SEA_GREEN { 0.235294118f, 0.701960784f, 0.443137255f, 1.0f } +/* Medium Slate Blue color { R:123, G:104, B:238, A:255 } */ +#define SG_MEDIUM_SLATE_BLUE { 0.482352941f, 0.407843137f, 0.933333333f, 1.0f } +/* Medium Spring Green color { R:0, G:250, B:154, A:255 } */ +#define SG_MEDIUM_SPRING_GREEN { 0.0f, 0.980392157f, 0.603921569f, 1.0f } +/* Medium Turquoise color { R:72, G:209, B:204, A:255 } */ +#define SG_MEDIUM_TURQUOISE { 0.282352941f, 0.819607843f, 0.8f, 1.0f } +/* Medium Violet Red color { R:199, G:21, B:133, A:255 } */ +#define SG_MEDIUM_VIOLET_RED { 0.780392157f, 0.0823529412f, 0.521568627f, 1.0f } +/* Midnight Blue color { R:25, G:25, B:112, A:255 } */ +#define SG_MIDNIGHT_BLUE { 0.0980392157f, 0.0980392157f, 0.439215686f, 1.0f } +/* Mint Cream color { R:245, G:255, B:250, A:255 } */ +#define SG_MINT_CREAM { 0.960784314f, 1.0f, 0.980392157f, 1.0f } +/* Misty Rose color { R:255, G:228, B:225, A:255 } */ +#define SG_MISTY_ROSE { 1.0f, 0.894117647f, 0.882352941f, 1.0f } +/* Moccasin color { R:255, G:228, B:181, A:255 } */ +#define SG_MOCCASIN { 1.0f, 0.894117647f, 0.709803922f, 1.0f } +/* Navajo White color { R:255, G:222, B:173, A:255 } */ +#define SG_NAVAJO_WHITE { 1.0f, 0.870588235f, 0.678431373f, 1.0f } +/* Navy Blue color { R:0, G:0, B:128, A:255 } */ +#define SG_NAVY_BLUE { 0.0f, 0.0f, 0.501960784f, 1.0f } +/* Old Lace color { R:253, G:245, B:230, A:255 } */ +#define SG_OLD_LACE { 0.992156863f, 0.960784314f, 0.901960784f, 1.0f } +/* Olive color { R:128, G:128, B:0, A:255 } */ +#define SG_OLIVE { 0.501960784f, 0.501960784f, 0.0f, 1.0f } +/* Olive Drab color { R:107, G:142, B:35, A:255 } */ +#define SG_OLIVE_DRAB { 0.419607843f, 0.556862745f, 0.137254902f, 1.0f } +/* Orange color { R:255, G:165, B:0, A:255 } */ +#define SG_ORANGE { 1.0f, 0.647058824f, 0.0f, 1.0f } +/* Orange Red color { R:255, G:69, B:0, A:255 } */ +#define SG_ORANGE_RED { 1.0f, 0.270588235f, 0.0f, 1.0f } +/* Orchid color { R:218, G:112, B:214, A:255 } */ +#define SG_ORCHID { 0.854901961f, 0.439215686f, 0.839215686f, 1.0f } +/* Pale Goldenrod color { R:238, G:232, B:170, A:255 } */ +#define SG_PALE_GOLDENROD { 0.933333333f, 0.909803922f, 0.666666667f, 1.0f } +/* Pale Green color { R:152, G:251, B:152, A:255 } */ +#define SG_PALE_GREEN { 0.596078431f, 0.984313725f, 0.596078431f, 1.0f } +/* Pale Turquoise color { R:175, G:238, B:238, A:255 } */ +#define SG_PALE_TURQUOISE { 0.68627451f, 0.933333333f, 0.933333333f, 1.0f } +/* Pale Violet Red color { R:219, G:112, B:147, A:255 } */ +#define SG_PALE_VIOLET_RED { 0.858823529f, 0.439215686f, 0.576470588f, 1.0f } +/* Papaya Whip color { R:255, G:239, B:213, A:255 } */ +#define SG_PAPAYA_WHIP { 1.0f, 0.937254902f, 0.835294118f, 1.0f } +/* Peach Puff color { R:255, G:218, B:185, A:255 } */ +#define SG_PEACH_PUFF { 1.0f, 0.854901961f, 0.725490196f, 1.0f } +/* Peru color { R:205, G:133, B:63, A:255 } */ +#define SG_PERU { 0.803921569f, 0.521568627f, 0.247058824f, 1.0f } +/* Pink color { R:255, G:192, B:203, A:255 } */ +#define SG_PINK { 1.0f, 0.752941176f, 0.796078431f, 1.0f } +/* Plum color { R:221, G:160, B:221, A:255 } */ +#define SG_PLUM { 0.866666667f, 0.62745098f, 0.866666667f, 1.0f } +/* Powder Blue color { R:176, G:224, B:230, A:255 } */ +#define SG_POWDER_BLUE { 0.690196078f, 0.878431373f, 0.901960784f, 1.0f } +/* Purple color { R:160, G:32, B:240, A:255 } */ +#define SG_PURPLE { 0.62745098f, 0.125490196f, 0.941176471f, 1.0f } +/* Web Purple color { R:128, G:0, B:128, A:255 } */ +#define SG_WEB_PURPLE { 0.501960784f, 0.0f, 0.501960784f, 1.0f } +/* Rebecca Purple color { R:102, G:51, B:153, A:255 } */ +#define SG_REBECCA_PURPLE { 0.4f, 0.2f, 0.6f, 1.0f } +/* Red color { R:255, G:0, B:0, A:255 } */ +#define SG_RED { 1.0f, 0.0f, 0.0f, 1.0f } +/* Rosy Brown color { R:188, G:143, B:143, A:255 } */ +#define SG_ROSY_BROWN { 0.737254902f, 0.560784314f, 0.560784314f, 1.0f } +/* Royal Blue color { R:65, G:105, B:225, A:255 } */ +#define SG_ROYAL_BLUE { 0.254901961f, 0.411764706f, 0.882352941f, 1.0f } +/* Saddle Brown color { R:139, G:69, B:19, A:255 } */ +#define SG_SADDLE_BROWN { 0.545098039f, 0.270588235f, 0.0745098039f, 1.0f } +/* Salmon color { R:250, G:128, B:114, A:255 } */ +#define SG_SALMON { 0.980392157f, 0.501960784f, 0.447058824f, 1.0f } +/* Sandy Brown color { R:244, G:164, B:96, A:255 } */ +#define SG_SANDY_BROWN { 0.956862745f, 0.643137255f, 0.376470588f, 1.0f } +/* Sea Green color { R:46, G:139, B:87, A:255 } */ +#define SG_SEA_GREEN { 0.180392157f, 0.545098039f, 0.341176471f, 1.0f } +/* Seashell color { R:255, G:245, B:238, A:255 } */ +#define SG_SEASHELL { 1.0f, 0.960784314f, 0.933333333f, 1.0f } +/* Sienna color { R:160, G:82, B:45, A:255 } */ +#define SG_SIENNA { 0.62745098f, 0.321568627f, 0.176470588f, 1.0f } +/* Silver color { R:192, G:192, B:192, A:255 } */ +#define SG_SILVER { 0.752941176f, 0.752941176f, 0.752941176f, 1.0f } +/* Sky Blue color { R:135, G:206, B:235, A:255 } */ +#define SG_SKY_BLUE { 0.529411765f, 0.807843137f, 0.921568627f, 1.0f } +/* Slate Blue color { R:106, G:90, B:205, A:255 } */ +#define SG_SLATE_BLUE { 0.415686275f, 0.352941176f, 0.803921569f, 1.0f } +/* Slate Gray color { R:112, G:128, B:144, A:255 } */ +#define SG_SLATE_GRAY { 0.439215686f, 0.501960784f, 0.564705882f, 1.0f } +/* Snow color { R:255, G:250, B:250, A:255 } */ +#define SG_SNOW { 1.0f, 0.980392157f, 0.980392157f, 1.0f } +/* Spring Green color { R:0, G:255, B:127, A:255 } */ +#define SG_SPRING_GREEN { 0.0f, 1.0f, 0.498039216f, 1.0f } +/* Steel Blue color { R:70, G:130, B:180, A:255 } */ +#define SG_STEEL_BLUE { 0.274509804f, 0.509803922f, 0.705882353f, 1.0f } +/* Tan color { R:210, G:180, B:140, A:255 } */ +#define SG_TAN { 0.823529412f, 0.705882353f, 0.549019608f, 1.0f } +/* Teal color { R:0, G:128, B:128, A:255 } */ +#define SG_TEAL { 0.0f, 0.501960784f, 0.501960784f, 1.0f } +/* Thistle color { R:216, G:191, B:216, A:255 } */ +#define SG_THISTLE { 0.847058824f, 0.749019608f, 0.847058824f, 1.0f } +/* Tomato color { R:255, G:99, B:71, A:255 } */ +#define SG_TOMATO { 1.0f, 0.388235294f, 0.278431373f, 1.0f } +/* Transparent color { R:0, G:0, B:0, A:0 } */ +#define SG_TRANSPARENT { 0.0f, 0.0f, 0.0f, 0.0f } +/* Turquoise color { R:64, G:224, B:208, A:255 } */ +#define SG_TURQUOISE { 0.250980392f, 0.878431373f, 0.815686275f, 1.0f } +/* Violet color { R:238, G:130, B:238, A:255 } */ +#define SG_VIOLET { 0.933333333f, 0.509803922f, 0.933333333f, 1.0f } +/* Wheat color { R:245, G:222, B:179, A:255 } */ +#define SG_WHEAT { 0.960784314f, 0.870588235f, 0.701960784f, 1.0f } +/* White color { R:255, G:255, B:255, A:255 } */ +#define SG_WHITE { 1.0f, 1.0f, 1.0f, 1.0f } +/* White Smoke color { R:245, G:245, B:245, A:255 } */ +#define SG_WHITE_SMOKE { 0.960784314f, 0.960784314f, 0.960784314f, 1.0f } +/* Yellow color { R:255, G:255, B:0, A:255 } */ +#define SG_YELLOW { 1.0f, 1.0f, 0.0f, 1.0f } +/* Yellow Green color { R:154, G:205, B:50, A:255 } */ +#define SG_YELLOW_GREEN { 0.603921569f, 0.803921569f, 0.196078431f, 1.0f } + +/* Alice Blue color { R:240, G:248, B:255, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_alice_blue = SG_ALICE_BLUE; +/* Antique White color { R:250, G:235, B:215, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_antique_white = SG_ANTIQUE_WHITE; +/* Aqua color { R:0, G:255, B:255, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_aqua = SG_AQUA; +/* Aquamarine color { R:127, G:255, B:212, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_aquamarine = SG_AQUAMARINE; +/* Azure color { R:240, G:255, B:255, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_azure = SG_AZURE; +/* Beige color { R:245, G:245, B:220, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_beige = SG_BEIGE; +/* Bisque color { R:255, G:228, B:196, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_bisque = SG_BISQUE; +/* Black color { R:0, G:0, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_black = SG_BLACK; +/* Blanched Almond color { R:255, G:235, B:205, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_blanched_almond = SG_BLANCHED_ALMOND; +/* Blue color { R:0, G:0, B:255, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_blue = SG_BLUE; +/* Blue Violet color { R:138, G:43, B:226, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_blue_violet = SG_BLUE_VIOLET; +/* Brown color { R:165, G:42, B:42, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_brown = SG_BROWN; +/* Burlywood color { R:222, G:184, B:135, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_burlywood = SG_BURLYWOOD; +/* Cadet Blue color { R:95, G:158, B:160, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_cadet_blue = SG_CADET_BLUE; +/* Chartreuse color { R:127, G:255, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_chartreuse = SG_CHARTREUSE; +/* Chocolate color { R:210, G:105, B:30, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_chocolate = SG_CHOCOLATE; +/* Coral color { R:255, G:127, B:80, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_coral = SG_CORAL; +/* Cornflower Blue color { R:100, G:149, B:237, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_cornflower_blue = SG_CORNFLOWER_BLUE; +/* Cornsilk color { R:255, G:248, B:220, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_cornsilk = SG_CORNSILK; +/* Crimson color { R:220, G:20, B:60, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_crimson = SG_CRIMSON; +/* Cyan color { R:0, G:255, B:255, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_cyan = SG_CYAN; +/* Dark Blue color { R:0, G:0, B:139, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_blue = SG_DARK_BLUE; +/* Dark Cyan color { R:0, G:139, B:139, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_cyan = SG_DARK_CYAN; +/* Dark Goldenrod color { R:184, G:134, B:11, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_goldenrod = SG_DARK_GOLDENROD; +/* Dark Gray color { R:169, G:169, B:169, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_gray = SG_DARK_GRAY; +/* Dark Green color { R:0, G:100, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_green = SG_DARK_GREEN; +/* Dark Khaki color { R:189, G:183, B:107, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_khaki = SG_DARK_KHAKI; +/* Dark Magenta color { R:139, G:0, B:139, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_magenta = SG_DARK_MAGENTA; +/* Dark Olive Green color { R:85, G:107, B:47, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_olive_green = SG_DARK_OLIVE_GREEN; +/* Dark Orange color { R:255, G:140, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_orange = SG_DARK_ORANGE; +/* Dark Orchid color { R:153, G:50, B:204, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_orchid = SG_DARK_ORCHID; +/* Dark Red color { R:139, G:0, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_red = SG_DARK_RED; +/* Dark Salmon color { R:233, G:150, B:122, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_salmon = SG_DARK_SALMON; +/* Dark Sea Green color { R:143, G:188, B:143, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_sea_green = SG_DARK_SEA_GREEN; +/* Dark Slate Blue color { R:72, G:61, B:139, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_slate_blue = SG_DARK_SLATE_BLUE; +/* Dark Slate Gray color { R:47, G:79, B:79, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_slate_gray = SG_DARK_SLATE_GRAY; +/* Dark Turquoise color { R:0, G:206, B:209, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_turquoise = SG_DARK_TURQUOISE; +/* Dark Violet color { R:148, G:0, B:211, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dark_violet = SG_DARK_VIOLET; +/* Deep Pink color { R:255, G:20, B:147, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_deep_pink = SG_DEEP_PINK; +/* Deep Sky Blue color { R:0, G:191, B:255, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_deep_sky_blue = SG_DEEP_SKY_BLUE; +/* Dim Gray color { R:105, G:105, B:105, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dim_gray = SG_DIM_GRAY; +/* Dodger Blue color { R:30, G:144, B:255, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_dodger_blue = SG_DODGER_BLUE; +/* Firebrick color { R:178, G:34, B:34, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_firebrick = SG_FIREBRICK; +/* Floral White color { R:255, G:250, B:240, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_floral_white = SG_FLORAL_WHITE; +/* Forest Green color { R:34, G:139, B:34, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_forest_green = SG_FOREST_GREEN; +/* Fuchsia color { R:255, G:0, B:255, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_fuchsia = SG_FUCHSIA; +/* Gainsboro color { R:220, G:220, B:220, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_gainsboro = SG_GAINSBORO; +/* Ghost White color { R:248, G:248, B:255, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_ghost_white = SG_GHOST_WHITE; +/* Gold color { R:255, G:215, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_gold = SG_GOLD; +/* Goldenrod color { R:218, G:165, B:32, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_goldenrod = SG_GOLDENROD; +/* Gray color { R:190, G:190, B:190, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_gray = SG_GRAY; +/* Web Gray color { R:128, G:128, B:128, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_web_gray = SG_WEB_GRAY; +/* Green color { R:0, G:255, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_green = SG_GREEN; +/* Web Green color { R:0, G:128, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_web_green = SG_WEB_GREEN; +/* Green Yellow color { R:173, G:255, B:47, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_green_yellow = SG_GREEN_YELLOW; +/* Honeydew color { R:240, G:255, B:240, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_honeydew = SG_HONEYDEW; +/* Hot Pink color { R:255, G:105, B:180, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_hot_pink = SG_HOT_PINK; +/* Indian Red color { R:205, G:92, B:92, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_indian_red = SG_INDIAN_RED; +/* Indigo color { R:75, G:0, B:130, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_indigo = SG_INDIGO; +/* Ivory color { R:255, G:255, B:240, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_ivory = SG_IVORY; +/* Khaki color { R:240, G:230, B:140, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_khaki = SG_KHAKI; +/* Lavender color { R:230, G:230, B:250, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_lavender = SG_LAVENDER; +/* Lavender Blush color { R:255, G:240, B:245, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_lavender_blush = SG_LAVENDER_BLUSH; +/* Lawn Green color { R:124, G:252, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_lawn_green = SG_LAWN_GREEN; +/* Lemon Chiffon color { R:255, G:250, B:205, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_lemon_chiffon = SG_LEMON_CHIFFON; +/* Light Blue color { R:173, G:216, B:230, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_light_blue = SG_LIGHT_BLUE; +/* Light Coral color { R:240, G:128, B:128, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_light_coral = SG_LIGHT_CORAL; +/* Light Cyan color { R:224, G:255, B:255, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_light_cyan = SG_LIGHT_CYAN; +/* Light Goldenrod color { R:250, G:250, B:210, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_light_goldenrod = SG_LIGHT_GOLDENROD; +/* Light Gray color { R:211, G:211, B:211, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_light_gray = SG_LIGHT_GRAY; +/* Light Green color { R:144, G:238, B:144, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_light_green = SG_LIGHT_GREEN; +/* Light Pink color { R:255, G:182, B:193, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_light_pink = SG_LIGHT_PINK; +/* Light Salmon color { R:255, G:160, B:122, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_light_salmon = SG_LIGHT_SALMON; +/* Light Sea Green color { R:32, G:178, B:170, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_light_sea_green = SG_LIGHT_SEA_GREEN; +/* Light Sky Blue color { R:135, G:206, B:250, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_light_sky_blue = SG_LIGHT_SKY_BLUE; +/* Light Slate Gray color { R:119, G:136, B:153, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_light_slate_gray = SG_LIGHT_SLATE_GRAY; +/* Light Steel Blue color { R:176, G:196, B:222, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_light_steel_blue = SG_LIGHT_STEEL_BLUE; +/* Light Yellow color { R:255, G:255, B:224, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_light_yellow = SG_LIGHT_YELLOW; +/* Lime color { R:0, G:255, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_lime = SG_LIME; +/* Lime Green color { R:50, G:205, B:50, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_lime_green = SG_LIME_GREEN; +/* Linen color { R:250, G:240, B:230, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_linen = SG_LINEN; +/* Magenta color { R:255, G:0, B:255, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_magenta = SG_MAGENTA; +/* Maroon color { R:176, G:48, B:96, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_maroon = SG_MAROON; +/* Web Maroon color { R:128, G:0, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_web_maroon = SG_WEB_MAROON; +/* Medium Aquamarine color { R:102, G:205, B:170, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_medium_aquamarine = SG_MEDIUM_AQUAMARINE; +/* Medium Blue color { R:0, G:0, B:205, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_medium_blue = SG_MEDIUM_BLUE; +/* Medium Orchid color { R:186, G:85, B:211, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_medium_orchid = SG_MEDIUM_ORCHID; +/* Medium Purple color { R:147, G:112, B:219, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_medium_purple = SG_MEDIUM_PURPLE; +/* Medium Sea Green color { R:60, G:179, B:113, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_medium_sea_green = SG_MEDIUM_SEA_GREEN; +/* Medium Slate Blue color { R:123, G:104, B:238, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_medium_slate_blue = SG_MEDIUM_SLATE_BLUE; +/* Medium Spring Green color { R:0, G:250, B:154, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_medium_spring_green = SG_MEDIUM_SPRING_GREEN; +/* Medium Turquoise color { R:72, G:209, B:204, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_medium_turquoise = SG_MEDIUM_TURQUOISE; +/* Medium Violet Red color { R:199, G:21, B:133, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_medium_violet_red = SG_MEDIUM_VIOLET_RED; +/* Midnight Blue color { R:25, G:25, B:112, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_midnight_blue = SG_MIDNIGHT_BLUE; +/* Mint Cream color { R:245, G:255, B:250, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_mint_cream = SG_MINT_CREAM; +/* Misty Rose color { R:255, G:228, B:225, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_misty_rose = SG_MISTY_ROSE; +/* Moccasin color { R:255, G:228, B:181, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_moccasin = SG_MOCCASIN; +/* Navajo White color { R:255, G:222, B:173, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_navajo_white = SG_NAVAJO_WHITE; +/* Navy Blue color { R:0, G:0, B:128, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_navy_blue = SG_NAVY_BLUE; +/* Old Lace color { R:253, G:245, B:230, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_old_lace = SG_OLD_LACE; +/* Olive color { R:128, G:128, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_olive = SG_OLIVE; +/* Olive Drab color { R:107, G:142, B:35, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_olive_drab = SG_OLIVE_DRAB; +/* Orange color { R:255, G:165, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_orange = SG_ORANGE; +/* Orange Red color { R:255, G:69, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_orange_red = SG_ORANGE_RED; +/* Orchid color { R:218, G:112, B:214, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_orchid = SG_ORCHID; +/* Pale Goldenrod color { R:238, G:232, B:170, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_pale_goldenrod = SG_PALE_GOLDENROD; +/* Pale Green color { R:152, G:251, B:152, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_pale_green = SG_PALE_GREEN; +/* Pale Turquoise color { R:175, G:238, B:238, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_pale_turquoise = SG_PALE_TURQUOISE; +/* Pale Violet Red color { R:219, G:112, B:147, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_pale_violet_red = SG_PALE_VIOLET_RED; +/* Papaya Whip color { R:255, G:239, B:213, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_papaya_whip = SG_PAPAYA_WHIP; +/* Peach Puff color { R:255, G:218, B:185, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_peach_puff = SG_PEACH_PUFF; +/* Peru color { R:205, G:133, B:63, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_peru = SG_PERU; +/* Pink color { R:255, G:192, B:203, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_pink = SG_PINK; +/* Plum color { R:221, G:160, B:221, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_plum = SG_PLUM; +/* Powder Blue color { R:176, G:224, B:230, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_powder_blue = SG_POWDER_BLUE; +/* Purple color { R:160, G:32, B:240, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_purple = SG_PURPLE; +/* Web Purple color { R:128, G:0, B:128, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_web_purple = SG_WEB_PURPLE; +/* Rebecca Purple color { R:102, G:51, B:153, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_rebecca_purple = SG_REBECCA_PURPLE; +/* Red color { R:255, G:0, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_red = SG_RED; +/* Rosy Brown color { R:188, G:143, B:143, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_rosy_brown = SG_ROSY_BROWN; +/* Royal Blue color { R:65, G:105, B:225, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_royal_blue = SG_ROYAL_BLUE; +/* Saddle Brown color { R:139, G:69, B:19, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_saddle_brown = SG_SADDLE_BROWN; +/* Salmon color { R:250, G:128, B:114, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_salmon = SG_SALMON; +/* Sandy Brown color { R:244, G:164, B:96, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_sandy_brown = SG_SANDY_BROWN; +/* Sea Green color { R:46, G:139, B:87, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_sea_green = SG_SEA_GREEN; +/* Seashell color { R:255, G:245, B:238, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_seashell = SG_SEASHELL; +/* Sienna color { R:160, G:82, B:45, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_sienna = SG_SIENNA; +/* Silver color { R:192, G:192, B:192, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_silver = SG_SILVER; +/* Sky Blue color { R:135, G:206, B:235, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_sky_blue = SG_SKY_BLUE; +/* Slate Blue color { R:106, G:90, B:205, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_slate_blue = SG_SLATE_BLUE; +/* Slate Gray color { R:112, G:128, B:144, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_slate_gray = SG_SLATE_GRAY; +/* Snow color { R:255, G:250, B:250, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_snow = SG_SNOW; +/* Spring Green color { R:0, G:255, B:127, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_spring_green = SG_SPRING_GREEN; +/* Steel Blue color { R:70, G:130, B:180, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_steel_blue = SG_STEEL_BLUE; +/* Tan color { R:210, G:180, B:140, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_tan = SG_TAN; +/* Teal color { R:0, G:128, B:128, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_teal = SG_TEAL; +/* Thistle color { R:216, G:191, B:216, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_thistle = SG_THISTLE; +/* Tomato color { R:255, G:99, B:71, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_tomato = SG_TOMATO; +/* Transparent color { R:0, G:0, B:0, A:0 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_transparent = SG_TRANSPARENT; +/* Turquoise color { R:64, G:224, B:208, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_turquoise = SG_TURQUOISE; +/* Violet color { R:238, G:130, B:238, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_violet = SG_VIOLET; +/* Wheat color { R:245, G:222, B:179, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_wheat = SG_WHEAT; +/* White color { R:255, G:255, B:255, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_white = SG_WHITE; +/* White Smoke color { R:245, G:245, B:245, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_white_smoke = SG_WHITE_SMOKE; +/* Yellow color { R:255, G:255, B:0, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_yellow = SG_YELLOW; +/* Yellow Green color { R:154, G:205, B:50, A:255 } */ +static SOKOL_COLOR_CONSTEXPR sg_color sg_yellow_green = SG_YELLOW_GREEN; + +/* Alice Blue color { R:240, G:248, B:255, A:255 } */ +#define SG_ALICE_BLUE_RGBA32 0xF0F8FFFF +/* Antique White color { R:250, G:235, B:215, A:255 } */ +#define SG_ANTIQUE_WHITE_RGBA32 0xFAEBD7FF +/* Aqua color { R:0, G:255, B:255, A:255 } */ +#define SG_AQUA_RGBA32 0x00FFFFFF +/* Aquamarine color { R:127, G:255, B:212, A:255 } */ +#define SG_AQUAMARINE_RGBA32 0x7FFFD4FF +/* Azure color { R:240, G:255, B:255, A:255 } */ +#define SG_AZURE_RGBA32 0xF0FFFFFF +/* Beige color { R:245, G:245, B:220, A:255 } */ +#define SG_BEIGE_RGBA32 0xF5F5DCFF +/* Bisque color { R:255, G:228, B:196, A:255 } */ +#define SG_BISQUE_RGBA32 0xFFE4C4FF +/* Black color { R:0, G:0, B:0, A:255 } */ +#define SG_BLACK_RGBA32 0x000000FF +/* Blanched Almond color { R:255, G:235, B:205, A:255 } */ +#define SG_BLANCHED_ALMOND_RGBA32 0xFFEBCDFF +/* Blue color { R:0, G:0, B:255, A:255 } */ +#define SG_BLUE_RGBA32 0x0000FFFF +/* Blue Violet color { R:138, G:43, B:226, A:255 } */ +#define SG_BLUE_VIOLET_RGBA32 0x8A2BE2FF +/* Brown color { R:165, G:42, B:42, A:255 } */ +#define SG_BROWN_RGBA32 0xA52A2AFF +/* Burlywood color { R:222, G:184, B:135, A:255 } */ +#define SG_BURLYWOOD_RGBA32 0xDEB887FF +/* Cadet Blue color { R:95, G:158, B:160, A:255 } */ +#define SG_CADET_BLUE_RGBA32 0x5F9EA0FF +/* Chartreuse color { R:127, G:255, B:0, A:255 } */ +#define SG_CHARTREUSE_RGBA32 0x7FFF00FF +/* Chocolate color { R:210, G:105, B:30, A:255 } */ +#define SG_CHOCOLATE_RGBA32 0xD2691EFF +/* Coral color { R:255, G:127, B:80, A:255 } */ +#define SG_CORAL_RGBA32 0xFF7F50FF +/* Cornflower Blue color { R:100, G:149, B:237, A:255 } */ +#define SG_CORNFLOWER_BLUE_RGBA32 0x6495EDFF +/* Cornsilk color { R:255, G:248, B:220, A:255 } */ +#define SG_CORNSILK_RGBA32 0xFFF8DCFF +/* Crimson color { R:220, G:20, B:60, A:255 } */ +#define SG_CRIMSON_RGBA32 0xDC143CFF +/* Cyan color { R:0, G:255, B:255, A:255 } */ +#define SG_CYAN_RGBA32 0x00FFFFFF +/* Dark Blue color { R:0, G:0, B:139, A:255 } */ +#define SG_DARK_BLUE_RGBA32 0x00008BFF +/* Dark Cyan color { R:0, G:139, B:139, A:255 } */ +#define SG_DARK_CYAN_RGBA32 0x008B8BFF +/* Dark Goldenrod color { R:184, G:134, B:11, A:255 } */ +#define SG_DARK_GOLDENROD_RGBA32 0xB8860BFF +/* Dark Gray color { R:169, G:169, B:169, A:255 } */ +#define SG_DARK_GRAY_RGBA32 0xA9A9A9FF +/* Dark Green color { R:0, G:100, B:0, A:255 } */ +#define SG_DARK_GREEN_RGBA32 0x006400FF +/* Dark Khaki color { R:189, G:183, B:107, A:255 } */ +#define SG_DARK_KHAKI_RGBA32 0xBDB76BFF +/* Dark Magenta color { R:139, G:0, B:139, A:255 } */ +#define SG_DARK_MAGENTA_RGBA32 0x8B008BFF +/* Dark Olive Green color { R:85, G:107, B:47, A:255 } */ +#define SG_DARK_OLIVE_GREEN_RGBA32 0x556B2FFF +/* Dark Orange color { R:255, G:140, B:0, A:255 } */ +#define SG_DARK_ORANGE_RGBA32 0xFF8C00FF +/* Dark Orchid color { R:153, G:50, B:204, A:255 } */ +#define SG_DARK_ORCHID_RGBA32 0x9932CCFF +/* Dark Red color { R:139, G:0, B:0, A:255 } */ +#define SG_DARK_RED_RGBA32 0x8B0000FF +/* Dark Salmon color { R:233, G:150, B:122, A:255 } */ +#define SG_DARK_SALMON_RGBA32 0xE9967AFF +/* Dark Sea Green color { R:143, G:188, B:143, A:255 } */ +#define SG_DARK_SEA_GREEN_RGBA32 0x8FBC8FFF +/* Dark Slate Blue color { R:72, G:61, B:139, A:255 } */ +#define SG_DARK_SLATE_BLUE_RGBA32 0x483D8BFF +/* Dark Slate Gray color { R:47, G:79, B:79, A:255 } */ +#define SG_DARK_SLATE_GRAY_RGBA32 0x2F4F4FFF +/* Dark Turquoise color { R:0, G:206, B:209, A:255 } */ +#define SG_DARK_TURQUOISE_RGBA32 0x00CED1FF +/* Dark Violet color { R:148, G:0, B:211, A:255 } */ +#define SG_DARK_VIOLET_RGBA32 0x9400D3FF +/* Deep Pink color { R:255, G:20, B:147, A:255 } */ +#define SG_DEEP_PINK_RGBA32 0xFF1493FF +/* Deep Sky Blue color { R:0, G:191, B:255, A:255 } */ +#define SG_DEEP_SKY_BLUE_RGBA32 0x00BFFFFF +/* Dim Gray color { R:105, G:105, B:105, A:255 } */ +#define SG_DIM_GRAY_RGBA32 0x696969FF +/* Dodger Blue color { R:30, G:144, B:255, A:255 } */ +#define SG_DODGER_BLUE_RGBA32 0x1E90FFFF +/* Firebrick color { R:178, G:34, B:34, A:255 } */ +#define SG_FIREBRICK_RGBA32 0xB22222FF +/* Floral White color { R:255, G:250, B:240, A:255 } */ +#define SG_FLORAL_WHITE_RGBA32 0xFFFAF0FF +/* Forest Green color { R:34, G:139, B:34, A:255 } */ +#define SG_FOREST_GREEN_RGBA32 0x228B22FF +/* Fuchsia color { R:255, G:0, B:255, A:255 } */ +#define SG_FUCHSIA_RGBA32 0xFF00FFFF +/* Gainsboro color { R:220, G:220, B:220, A:255 } */ +#define SG_GAINSBORO_RGBA32 0xDCDCDCFF +/* Ghost White color { R:248, G:248, B:255, A:255 } */ +#define SG_GHOST_WHITE_RGBA32 0xF8F8FFFF +/* Gold color { R:255, G:215, B:0, A:255 } */ +#define SG_GOLD_RGBA32 0xFFD700FF +/* Goldenrod color { R:218, G:165, B:32, A:255 } */ +#define SG_GOLDENROD_RGBA32 0xDAA520FF +/* Gray color { R:190, G:190, B:190, A:255 } */ +#define SG_GRAY_RGBA32 0xBEBEBEFF +/* Web Gray color { R:128, G:128, B:128, A:255 } */ +#define SG_WEB_GRAY_RGBA32 0x808080FF +/* Green color { R:0, G:255, B:0, A:255 } */ +#define SG_GREEN_RGBA32 0x00FF00FF +/* Web Green color { R:0, G:128, B:0, A:255 } */ +#define SG_WEB_GREEN_RGBA32 0x008000FF +/* Green Yellow color { R:173, G:255, B:47, A:255 } */ +#define SG_GREEN_YELLOW_RGBA32 0xADFF2FFF +/* Honeydew color { R:240, G:255, B:240, A:255 } */ +#define SG_HONEYDEW_RGBA32 0xF0FFF0FF +/* Hot Pink color { R:255, G:105, B:180, A:255 } */ +#define SG_HOT_PINK_RGBA32 0xFF69B4FF +/* Indian Red color { R:205, G:92, B:92, A:255 } */ +#define SG_INDIAN_RED_RGBA32 0xCD5C5CFF +/* Indigo color { R:75, G:0, B:130, A:255 } */ +#define SG_INDIGO_RGBA32 0x4B0082FF +/* Ivory color { R:255, G:255, B:240, A:255 } */ +#define SG_IVORY_RGBA32 0xFFFFF0FF +/* Khaki color { R:240, G:230, B:140, A:255 } */ +#define SG_KHAKI_RGBA32 0xF0E68CFF +/* Lavender color { R:230, G:230, B:250, A:255 } */ +#define SG_LAVENDER_RGBA32 0xE6E6FAFF +/* Lavender Blush color { R:255, G:240, B:245, A:255 } */ +#define SG_LAVENDER_BLUSH_RGBA32 0xFFF0F5FF +/* Lawn Green color { R:124, G:252, B:0, A:255 } */ +#define SG_LAWN_GREEN_RGBA32 0x7CFC00FF +/* Lemon Chiffon color { R:255, G:250, B:205, A:255 } */ +#define SG_LEMON_CHIFFON_RGBA32 0xFFFACDFF +/* Light Blue color { R:173, G:216, B:230, A:255 } */ +#define SG_LIGHT_BLUE_RGBA32 0xADD8E6FF +/* Light Coral color { R:240, G:128, B:128, A:255 } */ +#define SG_LIGHT_CORAL_RGBA32 0xF08080FF +/* Light Cyan color { R:224, G:255, B:255, A:255 } */ +#define SG_LIGHT_CYAN_RGBA32 0xE0FFFFFF +/* Light Goldenrod color { R:250, G:250, B:210, A:255 } */ +#define SG_LIGHT_GOLDENROD_RGBA32 0xFAFAD2FF +/* Light Gray color { R:211, G:211, B:211, A:255 } */ +#define SG_LIGHT_GRAY_RGBA32 0xD3D3D3FF +/* Light Green color { R:144, G:238, B:144, A:255 } */ +#define SG_LIGHT_GREEN_RGBA32 0x90EE90FF +/* Light Pink color { R:255, G:182, B:193, A:255 } */ +#define SG_LIGHT_PINK_RGBA32 0xFFB6C1FF +/* Light Salmon color { R:255, G:160, B:122, A:255 } */ +#define SG_LIGHT_SALMON_RGBA32 0xFFA07AFF +/* Light Sea Green color { R:32, G:178, B:170, A:255 } */ +#define SG_LIGHT_SEA_GREEN_RGBA32 0x20B2AAFF +/* Light Sky Blue color { R:135, G:206, B:250, A:255 } */ +#define SG_LIGHT_SKY_BLUE_RGBA32 0x87CEFAFF +/* Light Slate Gray color { R:119, G:136, B:153, A:255 } */ +#define SG_LIGHT_SLATE_GRAY_RGBA32 0x778899FF +/* Light Steel Blue color { R:176, G:196, B:222, A:255 } */ +#define SG_LIGHT_STEEL_BLUE_RGBA32 0xB0C4DEFF +/* Light Yellow color { R:255, G:255, B:224, A:255 } */ +#define SG_LIGHT_YELLOW_RGBA32 0xFFFFE0FF +/* Lime color { R:0, G:255, B:0, A:255 } */ +#define SG_LIME_RGBA32 0x00FF00FF +/* Lime Green color { R:50, G:205, B:50, A:255 } */ +#define SG_LIME_GREEN_RGBA32 0x32CD32FF +/* Linen color { R:250, G:240, B:230, A:255 } */ +#define SG_LINEN_RGBA32 0xFAF0E6FF +/* Magenta color { R:255, G:0, B:255, A:255 } */ +#define SG_MAGENTA_RGBA32 0xFF00FFFF +/* Maroon color { R:176, G:48, B:96, A:255 } */ +#define SG_MAROON_RGBA32 0xB03060FF +/* Web Maroon color { R:128, G:0, B:0, A:255 } */ +#define SG_WEB_MAROON_RGBA32 0x800000FF +/* Medium Aquamarine color { R:102, G:205, B:170, A:255 } */ +#define SG_MEDIUM_AQUAMARINE_RGBA32 0x66CDAAFF +/* Medium Blue color { R:0, G:0, B:205, A:255 } */ +#define SG_MEDIUM_BLUE_RGBA32 0x0000CDFF +/* Medium Orchid color { R:186, G:85, B:211, A:255 } */ +#define SG_MEDIUM_ORCHID_RGBA32 0xBA55D3FF +/* Medium Purple color { R:147, G:112, B:219, A:255 } */ +#define SG_MEDIUM_PURPLE_RGBA32 0x9370DBFF +/* Medium Sea Green color { R:60, G:179, B:113, A:255 } */ +#define SG_MEDIUM_SEA_GREEN_RGBA32 0x3CB371FF +/* Medium Slate Blue color { R:123, G:104, B:238, A:255 } */ +#define SG_MEDIUM_SLATE_BLUE_RGBA32 0x7B68EEFF +/* Medium Spring Green color { R:0, G:250, B:154, A:255 } */ +#define SG_MEDIUM_SPRING_GREEN_RGBA32 0x00FA9AFF +/* Medium Turquoise color { R:72, G:209, B:204, A:255 } */ +#define SG_MEDIUM_TURQUOISE_RGBA32 0x48D1CCFF +/* Medium Violet Red color { R:199, G:21, B:133, A:255 } */ +#define SG_MEDIUM_VIOLET_RED_RGBA32 0xC71585FF +/* Midnight Blue color { R:25, G:25, B:112, A:255 } */ +#define SG_MIDNIGHT_BLUE_RGBA32 0x191970FF +/* Mint Cream color { R:245, G:255, B:250, A:255 } */ +#define SG_MINT_CREAM_RGBA32 0xF5FFFAFF +/* Misty Rose color { R:255, G:228, B:225, A:255 } */ +#define SG_MISTY_ROSE_RGBA32 0xFFE4E1FF +/* Moccasin color { R:255, G:228, B:181, A:255 } */ +#define SG_MOCCASIN_RGBA32 0xFFE4B5FF +/* Navajo White color { R:255, G:222, B:173, A:255 } */ +#define SG_NAVAJO_WHITE_RGBA32 0xFFDEADFF +/* Navy Blue color { R:0, G:0, B:128, A:255 } */ +#define SG_NAVY_BLUE_RGBA32 0x000080FF +/* Old Lace color { R:253, G:245, B:230, A:255 } */ +#define SG_OLD_LACE_RGBA32 0xFDF5E6FF +/* Olive color { R:128, G:128, B:0, A:255 } */ +#define SG_OLIVE_RGBA32 0x808000FF +/* Olive Drab color { R:107, G:142, B:35, A:255 } */ +#define SG_OLIVE_DRAB_RGBA32 0x6B8E23FF +/* Orange color { R:255, G:165, B:0, A:255 } */ +#define SG_ORANGE_RGBA32 0xFFA500FF +/* Orange Red color { R:255, G:69, B:0, A:255 } */ +#define SG_ORANGE_RED_RGBA32 0xFF4500FF +/* Orchid color { R:218, G:112, B:214, A:255 } */ +#define SG_ORCHID_RGBA32 0xDA70D6FF +/* Pale Goldenrod color { R:238, G:232, B:170, A:255 } */ +#define SG_PALE_GOLDENROD_RGBA32 0xEEE8AAFF +/* Pale Green color { R:152, G:251, B:152, A:255 } */ +#define SG_PALE_GREEN_RGBA32 0x98FB98FF +/* Pale Turquoise color { R:175, G:238, B:238, A:255 } */ +#define SG_PALE_TURQUOISE_RGBA32 0xAFEEEEFF +/* Pale Violet Red color { R:219, G:112, B:147, A:255 } */ +#define SG_PALE_VIOLET_RED_RGBA32 0xDB7093FF +/* Papaya Whip color { R:255, G:239, B:213, A:255 } */ +#define SG_PAPAYA_WHIP_RGBA32 0xFFEFD5FF +/* Peach Puff color { R:255, G:218, B:185, A:255 } */ +#define SG_PEACH_PUFF_RGBA32 0xFFDAB9FF +/* Peru color { R:205, G:133, B:63, A:255 } */ +#define SG_PERU_RGBA32 0xCD853FFF +/* Pink color { R:255, G:192, B:203, A:255 } */ +#define SG_PINK_RGBA32 0xFFC0CBFF +/* Plum color { R:221, G:160, B:221, A:255 } */ +#define SG_PLUM_RGBA32 0xDDA0DDFF +/* Powder Blue color { R:176, G:224, B:230, A:255 } */ +#define SG_POWDER_BLUE_RGBA32 0xB0E0E6FF +/* Purple color { R:160, G:32, B:240, A:255 } */ +#define SG_PURPLE_RGBA32 0xA020F0FF +/* Web Purple color { R:128, G:0, B:128, A:255 } */ +#define SG_WEB_PURPLE_RGBA32 0x800080FF +/* Rebecca Purple color { R:102, G:51, B:153, A:255 } */ +#define SG_REBECCA_PURPLE_RGBA32 0x663399FF +/* Red color { R:255, G:0, B:0, A:255 } */ +#define SG_RED_RGBA32 0xFF0000FF +/* Rosy Brown color { R:188, G:143, B:143, A:255 } */ +#define SG_ROSY_BROWN_RGBA32 0xBC8F8FFF +/* Royal Blue color { R:65, G:105, B:225, A:255 } */ +#define SG_ROYAL_BLUE_RGBA32 0x4169E1FF +/* Saddle Brown color { R:139, G:69, B:19, A:255 } */ +#define SG_SADDLE_BROWN_RGBA32 0x8B4513FF +/* Salmon color { R:250, G:128, B:114, A:255 } */ +#define SG_SALMON_RGBA32 0xFA8072FF +/* Sandy Brown color { R:244, G:164, B:96, A:255 } */ +#define SG_SANDY_BROWN_RGBA32 0xF4A460FF +/* Sea Green color { R:46, G:139, B:87, A:255 } */ +#define SG_SEA_GREEN_RGBA32 0x2E8B57FF +/* Seashell color { R:255, G:245, B:238, A:255 } */ +#define SG_SEASHELL_RGBA32 0xFFF5EEFF +/* Sienna color { R:160, G:82, B:45, A:255 } */ +#define SG_SIENNA_RGBA32 0xA0522DFF +/* Silver color { R:192, G:192, B:192, A:255 } */ +#define SG_SILVER_RGBA32 0xC0C0C0FF +/* Sky Blue color { R:135, G:206, B:235, A:255 } */ +#define SG_SKY_BLUE_RGBA32 0x87CEEBFF +/* Slate Blue color { R:106, G:90, B:205, A:255 } */ +#define SG_SLATE_BLUE_RGBA32 0x6A5ACDFF +/* Slate Gray color { R:112, G:128, B:144, A:255 } */ +#define SG_SLATE_GRAY_RGBA32 0x708090FF +/* Snow color { R:255, G:250, B:250, A:255 } */ +#define SG_SNOW_RGBA32 0xFFFAFAFF +/* Spring Green color { R:0, G:255, B:127, A:255 } */ +#define SG_SPRING_GREEN_RGBA32 0x00FF7FFF +/* Steel Blue color { R:70, G:130, B:180, A:255 } */ +#define SG_STEEL_BLUE_RGBA32 0x4682B4FF +/* Tan color { R:210, G:180, B:140, A:255 } */ +#define SG_TAN_RGBA32 0xD2B48CFF +/* Teal color { R:0, G:128, B:128, A:255 } */ +#define SG_TEAL_RGBA32 0x008080FF +/* Thistle color { R:216, G:191, B:216, A:255 } */ +#define SG_THISTLE_RGBA32 0xD8BFD8FF +/* Tomato color { R:255, G:99, B:71, A:255 } */ +#define SG_TOMATO_RGBA32 0xFF6347FF +/* Transparent color { R:0, G:0, B:0, A:0 } */ +#define SG_TRANSPARENT_RGBA32 0x00000000 +/* Turquoise color { R:64, G:224, B:208, A:255 } */ +#define SG_TURQUOISE_RGBA32 0x40E0D0FF +/* Violet color { R:238, G:130, B:238, A:255 } */ +#define SG_VIOLET_RGBA32 0xEE82EEFF +/* Wheat color { R:245, G:222, B:179, A:255 } */ +#define SG_WHEAT_RGBA32 0xF5DEB3FF +/* White color { R:255, G:255, B:255, A:255 } */ +#define SG_WHITE_RGBA32 0xFFFFFFFF +/* White Smoke color { R:245, G:245, B:245, A:255 } */ +#define SG_WHITE_SMOKE_RGBA32 0xF5F5F5FF +/* Yellow color { R:255, G:255, B:0, A:255 } */ +#define SG_YELLOW_RGBA32 0xFFFF00FF +/* Yellow Green color { R:154, G:205, B:50, A:255 } */ +#define SG_YELLOW_GREEN_RGBA32 0x9ACD32FF + +#ifdef __cplusplus +} /* extern "C" */ + +inline sg_color sg_make_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return sg_make_color_4b(r, g, b, a); +} + +inline sg_color sg_make_color(uint32_t rgba) { + return sg_make_color_1i(rgba); +} + +inline sg_color sg_color_lerp(const sg_color& color_a, const sg_color& color_b, float amount) { + return sg_color_lerp(&color_a, &color_b, amount); +} + +inline sg_color sg_color_lerp_precise(const sg_color& color_a, const sg_color& color_b, float amount) { + return sg_color_lerp_precise(&color_a, &color_b, amount); +} + +inline sg_color sg_color_multiply(const sg_color& color, float scale) { + return sg_color_multiply(&color, scale); +} + +#endif /* __cplusplus */ + +#endif /* SOKOL_COLOR_INCLUDED */ + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_COLOR_IMPL +#define SOKOL_COLOR_IMPL_INCLUDED (1) + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif + +static inline float _sg_color_clamp(float v, float low, float high) { + if (v < low) { + return low; + } else if (v > high) { + return high; + } + return v; +} + +static inline float _sg_color_lerp(float a, float b, float amount) { + return a + (b - a) * amount; +} + +static inline float _sg_color_lerp_precise(float a, float b, float amount) { + return ((1.0f - amount) * a) + (b * amount); +} + +SOKOL_API_IMPL sg_color sg_make_color_4b(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + sg_color result; + result.r = r / 255.0f; + result.g = g / 255.0f; + result.b = b / 255.0f; + result.a = a / 255.0f; + return result; +} + +SOKOL_API_IMPL sg_color sg_make_color_1i(uint32_t rgba) { + return sg_make_color_4b( + (uint8_t)(rgba >> 24), + (uint8_t)(rgba >> 16), + (uint8_t)(rgba >> 8), + (uint8_t)(rgba >> 0) + ); +} + +SOKOL_API_IMPL sg_color sg_color_lerp(const sg_color* color_a, const sg_color* color_b, float amount) { + SOKOL_ASSERT(color_a); + SOKOL_ASSERT(color_b); + amount = _sg_color_clamp(amount, 0.0f, 1.0f); + sg_color result; + result.r = _sg_color_lerp(color_a->r, color_b->r, amount); + result.g = _sg_color_lerp(color_a->g, color_b->g, amount); + result.b = _sg_color_lerp(color_a->b, color_b->b, amount); + result.a = _sg_color_lerp(color_a->a, color_b->a, amount); + return result; +} + +SOKOL_API_IMPL sg_color sg_color_lerp_precise(const sg_color* color_a, const sg_color* color_b, float amount) { + SOKOL_ASSERT(color_a); + SOKOL_ASSERT(color_b); + amount = _sg_color_clamp(amount, 0.0f, 1.0f); + sg_color result; + result.r = _sg_color_lerp_precise(color_a->r, color_b->r, amount); + result.g = _sg_color_lerp_precise(color_a->g, color_b->g, amount); + result.b = _sg_color_lerp_precise(color_a->b, color_b->b, amount); + result.a = _sg_color_lerp_precise(color_a->a, color_b->a, amount); + return result; +} + +SOKOL_API_IMPL sg_color sg_color_multiply(const sg_color* color, float scale) { + SOKOL_ASSERT(color); + sg_color result; + result.r = color->r * scale; + result.g = color->g * scale; + result.b = color->b * scale; + result.a = color->a * scale; + return result; +} + +#endif /* SOKOL_COLOR_IMPL */ diff --git a/source/engine/thirdparty/sokol/util/sokol_debugtext.h b/source/engine/thirdparty/sokol/util/sokol_debugtext.h new file mode 100644 index 0000000..cec0694 --- /dev/null +++ b/source/engine/thirdparty/sokol/util/sokol_debugtext.h @@ -0,0 +1,4561 @@ +#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_GLCORE33 + 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 glsl330: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 + uniform texture2D tex; + 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_GLCORE33) +static const char _sdtx_vs_source_glsl330[298] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,0x30,0x0a,0x0a,0x6c,0x61, + 0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,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, +}; +static const char _sdtx_fs_source_glsl330[182] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,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,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a, + 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) +static const char _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, +}; +static const char _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) +static const uint8_t _sdtx_vs_bytecode_metal_macos[2796] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xec,0x0a,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, + 0xe0,0x09,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,0xfe,0xa3,0xdd,0x3f,0xa3,0x19,0x66, + 0x48,0xdb,0x53,0x17,0xfa,0x47,0xab,0xcd,0x1c,0x32,0x10,0x34,0x47,0xde,0x1e,0x11, + 0x5c,0xfd,0x36,0x7a,0xb2,0xbe,0x26,0x50,0xa3,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,0xcc,0x09,0x00,0x00,0xff,0xff,0xff,0xff, + 0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0x70,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,0x82,0x00,0x00,0x00,0x1b,0xc8,0x25,0xf8,0xff,0xff,0xff,0xff, + 0x01,0x90,0x00,0x8a,0x18,0x87,0x77,0x90,0x07,0x79,0x28,0x87,0x71,0xa0,0x07,0x76, + 0xc8,0x87,0x36,0x90,0x87,0x77,0xa8,0x07,0x77,0x20,0x87,0x72,0x20,0x87,0x36,0x20, + 0x87,0x74,0xb0,0x87,0x74,0x20,0x87,0x72,0x68,0x83,0x79,0x88,0x07,0x79,0xa0,0x87, + 0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0xc0,0x1c,0xc2,0x81, + 0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c,0xd2,0x61,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c, + 0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8,0xa1,0x0d,0xc6,0x21,0x1c,0xd8,0x81,0x1d,0xe6, + 0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07,0x80,0x60,0x87,0x72,0x98,0x87,0x79, + 0x68,0x03,0x78,0x90,0x87,0x72,0x18,0x87,0x74,0x98,0x87,0x72,0x68,0x03,0x73,0x80, + 0x87,0x76,0x08,0x07,0x72,0x00,0xcc,0x21,0x1c,0xd8,0x61,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,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x06,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,0x68,0x03,0x77,0x78,0x07,0x77,0x68,0x03,0x76,0x28, + 0x87,0x70,0x30,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,0x40,0x1d,0xea,0xa1,0x1d,0xe0,0xa1,0x0d, + 0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87, + 0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x70,0x87,0x70,0x70,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,0xe6,0x21,0x1d,0xce,0xc1,0x1d,0xca,0x81,0x1c,0xda, + 0x40,0x1f,0xca,0x41,0x1e,0xde,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, + 0x03,0x7a,0x90,0x87,0x70,0x80,0x07,0x78,0x48,0x07,0x77,0x38,0x87,0x36,0x68,0x87, + 0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0x62,0x1e,0xe8,0x21, + 0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e,0xe4,0xe1,0x1d,0xe8,0xa1,0x1c,0xc6,0x81,0x1e, + 0xde,0x41,0x1e,0xda,0x40,0x1c,0xea,0xc1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x0d,0xe6, + 0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c,0x00,0x88,0x7a,0x70,0x87,0x79,0x08,0x07,0x73, + 0x28,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e, + 0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea,0x61,0x1e,0xca,0xa1,0x0d,0xe6,0xe1,0x1d,0xcc, + 0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1,0x1d,0xc2,0x81,0x1e,0x00,0x73,0x08,0x07,0x76, + 0x98,0x87,0x72,0x00,0x36,0x18,0xc2,0xff,0xff,0xff,0xff,0x0f,0x80,0x04,0x50,0x00, + 0x49,0x18,0x00,0x00,0x02,0x00,0x00,0x00,0x13,0x82,0x60,0x42,0x20,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,0x26,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,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4, + 0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06, + 0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac, + 0x65,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45,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,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8, + 0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83, + 0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,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,0x86,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, +}; +static const uint8_t _sdtx_fs_bytecode_metal_macos[2825] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x09,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, + 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,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0xf6,0x61,0xa5,0x24,0x2e,0x8e,0x25, + 0x56,0xa9,0xb9,0xe4,0x35,0x58,0x0f,0xd8,0xb4,0x32,0x8b,0xbc,0x73,0xb0,0x70,0xbe, + 0x5d,0x66,0xf0,0xf4,0x12,0x93,0x26,0xe5,0xdd,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,0x1c,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0x84,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,0x89,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07,0x7a,0x60,0x87,0x7c, + 0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72,0x68,0x03,0x72,0x48, + 0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90,0x07,0x7a,0x68,0x03, + 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc,0x21,0x1c,0xd8,0x61, + 0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81,0x1d,0xca,0xa1,0x0d, + 0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d,0xd8,0x61,0x1e,0x00, + 0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87,0x79,0x98,0x87,0x36, + 0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,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,0x00,0x7a,0x90, + 0x87,0x7a,0x28,0x07,0x60,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,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36,0x60,0x87,0x72,0x08, + 0x07,0x73,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,0xd4,0xa1,0x1e,0xda,0x01,0x1e,0xda,0x80,0x1e, + 0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,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,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c,0xc8,0xa1,0x0d,0xf4, + 0xa1,0x1c,0xe4,0xe1,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,0xa0, + 0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68,0x83,0x76,0x08,0x07, + 0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6,0x81,0x1e,0xc2,0x61, + 0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d, + 0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e,0xda,0x60,0x1e,0xd2, + 0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87,0x70,0x30,0x87,0x72, + 0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e, + 0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e,0xde,0xc1,0x1c,0xe8, + 0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 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,0x05,0x49,0x18,0x00,0x00,0x03,0x00,0x00, + 0x00,0x13,0x86,0x40,0x18,0x26,0x0c,0x44,0x61,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,0xa1,0x09,0x00,0x00,0x79,0x18,0x00,0x00,0xb9,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,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c, + 0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26, + 0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac, + 0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45,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,0xc1,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,0x98,0x70,0x48,0x07, + 0x79,0x70,0x03,0x73,0x90,0x87,0x70,0x38,0x87,0x76,0x28,0x07,0x77,0xa0,0x87,0x29, + 0xc1,0x1a,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c, + 0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c, + 0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,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,0x0f,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,0x8a, + 0x10,0x4c,0x09,0xb2,0x10,0x46,0x11,0x0c,0x32,0x04,0x03,0x62,0x01,0x23,0x9f,0xd9, + 0x06,0x23,0x00,0x32,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sdtx_vs_bytecode_metal_ios[2796] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xec,0x0a,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, + 0xe0,0x09,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,0xba,0x9f,0x15,0xc2,0x1c,0x90,0x81, + 0x14,0x52,0x1d,0xff,0x02,0xbe,0x84,0x58,0x4b,0x16,0xdb,0x77,0xe8,0xfc,0xb2,0x67, + 0xb1,0x23,0xf1,0x84,0xb0,0x8d,0xed,0xb3,0x81,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,0xc4,0x09,0x00,0x00,0xff,0xff,0xff,0xff, + 0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0x6e,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,0x82,0x00,0x00,0x00,0x1b,0xc8,0x25,0xf8,0xff,0xff,0xff,0xff, + 0x01,0x90,0x00,0x8a,0x18,0x87,0x77,0x90,0x07,0x79,0x28,0x87,0x71,0xa0,0x07,0x76, + 0xc8,0x87,0x36,0x90,0x87,0x77,0xa8,0x07,0x77,0x20,0x87,0x72,0x20,0x87,0x36,0x20, + 0x87,0x74,0xb0,0x87,0x74,0x20,0x87,0x72,0x68,0x83,0x79,0x88,0x07,0x79,0xa0,0x87, + 0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0xc0,0x1c,0xc2,0x81, + 0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c,0xd2,0x61,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c, + 0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8,0xa1,0x0d,0xc6,0x21,0x1c,0xd8,0x81,0x1d,0xe6, + 0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07,0x80,0x60,0x87,0x72,0x98,0x87,0x79, + 0x68,0x03,0x78,0x90,0x87,0x72,0x18,0x87,0x74,0x98,0x87,0x72,0x68,0x03,0x73,0x80, + 0x87,0x76,0x08,0x07,0x72,0x00,0xcc,0x21,0x1c,0xd8,0x61,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,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x06,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,0x68,0x03,0x77,0x78,0x07,0x77,0x68,0x03,0x76,0x28, + 0x87,0x70,0x30,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,0x40,0x1d,0xea,0xa1,0x1d,0xe0,0xa1,0x0d, + 0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87, + 0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x70,0x87,0x70,0x70,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,0xe6,0x21,0x1d,0xce,0xc1,0x1d,0xca,0x81,0x1c,0xda, + 0x40,0x1f,0xca,0x41,0x1e,0xde,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, + 0x03,0x7a,0x90,0x87,0x70,0x80,0x07,0x78,0x48,0x07,0x77,0x38,0x87,0x36,0x68,0x87, + 0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0x62,0x1e,0xe8,0x21, + 0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e,0xe4,0xe1,0x1d,0xe8,0xa1,0x1c,0xc6,0x81,0x1e, + 0xde,0x41,0x1e,0xda,0x40,0x1c,0xea,0xc1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x0d,0xe6, + 0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c,0x00,0x88,0x7a,0x70,0x87,0x79,0x08,0x07,0x73, + 0x28,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e, + 0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea,0x61,0x1e,0xca,0xa1,0x0d,0xe6,0xe1,0x1d,0xcc, + 0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1,0x1d,0xc2,0x81,0x1e,0x00,0x73,0x08,0x07,0x76, + 0x98,0x87,0x72,0x00,0x36,0x18,0xc2,0xff,0xff,0xff,0xff,0x0f,0x80,0x04,0x50,0x00, + 0x49,0x18,0x00,0x00,0x02,0x00,0x00,0x00,0x13,0x82,0x60,0x42,0x20,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,0x48,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, + 0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c, + 0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06,0x04, + 0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6, + 0xe6,0xc6,0x45,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,0x79,0x18,0x00,0x00, + 0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d, + 0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,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,0x86,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, +}; +static const uint8_t _sdtx_fs_bytecode_metal_ios[2825] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x09,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, + 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,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0x5c,0x3b,0x31,0x10,0xb3,0x9b,0xe2, + 0x6d,0x48,0xbf,0xdd,0x8e,0xac,0x5d,0xcc,0x7d,0x7b,0xba,0xe9,0xfb,0xe5,0xd0,0xdd, + 0xf7,0xec,0x82,0x0c,0xb9,0x6a,0xd2,0x30,0x4d,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,0x14,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0x82,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,0x89,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07,0x7a,0x60,0x87,0x7c, + 0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72,0x68,0x03,0x72,0x48, + 0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90,0x07,0x7a,0x68,0x03, + 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc,0x21,0x1c,0xd8,0x61, + 0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81,0x1d,0xca,0xa1,0x0d, + 0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d,0xd8,0x61,0x1e,0x00, + 0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87,0x79,0x98,0x87,0x36, + 0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,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,0x00,0x7a,0x90, + 0x87,0x7a,0x28,0x07,0x60,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,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36,0x60,0x87,0x72,0x08, + 0x07,0x73,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,0xd4,0xa1,0x1e,0xda,0x01,0x1e,0xda,0x80,0x1e, + 0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,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,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c,0xc8,0xa1,0x0d,0xf4, + 0xa1,0x1c,0xe4,0xe1,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,0xa0, + 0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68,0x83,0x76,0x08,0x07, + 0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6,0x81,0x1e,0xc2,0x61, + 0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d, + 0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e,0xda,0x60,0x1e,0xd2, + 0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87,0x70,0x30,0x87,0x72, + 0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e, + 0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e,0xde,0xc1,0x1c,0xe8, + 0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 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,0x05,0x49,0x18,0x00,0x00,0x03,0x00,0x00, + 0x00,0x13,0x86,0x40,0x18,0x26,0x0c,0x44,0x61,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,0x01,0x12,0x00, + 0x00,0x79,0x18,0x00,0x00,0xb9,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,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae, + 0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06, + 0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5, + 0xc6,0xe6,0xc6,0x45,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,0xc1,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,0x98,0x70,0x48,0x07,0x79,0x70,0x03,0x73,0x90,0x87,0x70,0x38,0x87, + 0x76,0x28,0x07,0x77,0xa0,0x87,0x29,0xc1,0x1a,0x00,0x00,0x00,0x00,0x79,0x18,0x00, + 0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03, + 0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c, + 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,0x0f,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,0x8a,0x10,0x4c,0x09,0xb2,0x10,0x46,0x11,0x0c, + 0x32,0x04,0x03,0x62,0x01,0x23,0x9f,0xd9,0x06,0x23,0x00,0x32,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const char _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, +}; +static const char _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 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, +}; +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) +static const char _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, +}; +static const char _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,0x33,0x32, + 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,0x34,0x38,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_GLCORE33, 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, 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_ZERO; + pip_desc.colors[0].blend.dst_factor_alpha = SG_BLENDFACTOR_ONE; + 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].name = "position"; + shd_desc.attrs[1].name = "texcoord0"; + shd_desc.attrs[2].name = "color0"; + shd_desc.attrs[0].sem_name = "TEXCOORD"; + shd_desc.attrs[0].sem_index = 0; + shd_desc.attrs[1].sem_name = "TEXCOORD"; + shd_desc.attrs[1].sem_index = 1; + shd_desc.attrs[2].sem_name = "TEXCOORD"; + shd_desc.attrs[2].sem_index = 2; + shd_desc.fs.images[0].used = true; + shd_desc.fs.images[0].image_type = SG_IMAGETYPE_2D; + shd_desc.fs.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + shd_desc.fs.samplers[0].used = true; + shd_desc.fs.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + shd_desc.fs.image_sampler_pairs[0].used = true; + shd_desc.fs.image_sampler_pairs[0].image_slot = 0; + shd_desc.fs.image_sampler_pairs[0].sampler_slot = 0; + shd_desc.fs.image_sampler_pairs[0].glsl_name = "tex_smp"; + #if defined(SOKOL_GLCORE33) + shd_desc.vs.source = _sdtx_vs_source_glsl330; + shd_desc.fs.source = _sdtx_fs_source_glsl330; + #elif defined(SOKOL_GLES3) + shd_desc.vs.source = _sdtx_vs_source_glsl300es; + shd_desc.fs.source = _sdtx_fs_source_glsl300es; + #elif defined(SOKOL_METAL) + shd_desc.vs.entry = "main0"; + shd_desc.fs.entry = "main0"; + switch (sg_query_backend()) { + case SG_BACKEND_METAL_MACOS: + shd_desc.vs.bytecode = SG_RANGE(_sdtx_vs_bytecode_metal_macos); + shd_desc.fs.bytecode = SG_RANGE(_sdtx_fs_bytecode_metal_macos); + break; + case SG_BACKEND_METAL_IOS: + shd_desc.vs.bytecode = SG_RANGE(_sdtx_vs_bytecode_metal_ios); + shd_desc.fs.bytecode = SG_RANGE(_sdtx_fs_bytecode_metal_ios); + break; + default: + shd_desc.vs.source = _sdtx_vs_source_metal_sim; + shd_desc.fs.source = _sdtx_fs_source_metal_sim; + break; + } + #elif defined(SOKOL_D3D11) + shd_desc.vs.bytecode = SG_RANGE(_sdtx_vs_bytecode_hlsl4); + shd_desc.fs.bytecode = SG_RANGE(_sdtx_fs_bytecode_hlsl4); + #elif defined(SOKOL_WGPU) + shd_desc.vs.source = _sdtx_vs_source_wgsl; + shd_desc.fs.source = _sdtx_fs_source_wgsl; + #else + shd_desc.vs.source = _sdtx_vs_src_dummy; + shd_desc.fs.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.fs.images[0] = _sdtx.font_img; + bindings.fs.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/source/engine/thirdparty/sokol/util/sokol_fontstash.h b/source/engine/thirdparty/sokol/util/sokol_fontstash.h new file mode 100644 index 0000000..048c821 --- /dev/null +++ b/source/engine/thirdparty/sokol/util/sokol_fontstash.h @@ -0,0 +1,1784 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_FONTSTASH_IMPL) +#define SOKOL_FONTSTASH_IMPL +#endif +#ifndef SOKOL_FONTSTASH_INCLUDED +/* + sokol_fontstash.h -- renderer for https://github.com/memononen/fontstash + on top of sokol_gl.h + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_FONTSTASH_IMPL + + before you include this file in *one* C or C++ file to create the + implementation. + + The following defines are used by the implementation to select the + platform-specific embedded shader code (these are the same defines as + used by sokol_gfx.h and sokol_app.h): + + SOKOL_GLCORE33 + SOKOL_GLES3 + SOKOL_D3D11 + SOKOL_METAL + + ...optionally provide the following macros to override defaults: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_FONTSTASH_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_FONTSTASH_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + + Include the following headers before including sokol_fontstash.h: + + sokol_gfx.h + + Additionally include the following headers for including the sokol_fontstash.h + implementation: + + sokol_gl.h + + HOW TO + ====== + --- First initialize sokol-gfx and sokol-gl as usual: + + sg_setup(&(sg_desc){...}); + sgl_setup(&(sgl_desc){...}); + + --- Create at least one fontstash context with sfons_create() (this replaces + glfonsCreate() from fontstash.h's example GL renderer: + + FONScontext* ctx = sfons_create(&(sfons_desc_t){ + .width = atlas_width, + .height = atlas_height, + }); + + Each FONScontext manages one font atlas texture which can hold rasterized + glyphs for multiple fonts. + + --- From here on, use fontstash.h's functions "as usual" to add TTF + font data and draw text. Note that (just like with sokol-gl), text + rendering can happen anywhere in the frame, not only inside + a sokol-gfx rendering pass. + + --- You can use the helper function + + uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + + To convert a 0..255 RGBA color into a packed uint32_t color value + expected by fontstash.h. + + --- Once per frame before calling sgl_draw(), call: + + sfons_flush(FONScontext* ctx) + + ...this will update the dynamic sokol-gfx texture with the latest font + atlas content. + + --- To actually render the text (and any other sokol-gl draw commands), + call sgl_draw() inside a sokol-gfx frame. + + --- NOTE that you can mix fontstash.h calls with sokol-gl calls to mix + text rendering with sokol-gl rendering. You can also use + sokol-gl's matrix stack to position fontstash.h text in 3D. + + --- finally on application shutdown, call: + + sfons_destroy(FONScontext* ctx) + + before sgl_shutdown() and sg_shutdown() + + + WHAT HAPPENS UNDER THE HOOD: + ============================ + + FONScontext* sfons_create(const sfons_desc_t* desc) + - creates a sokol-gfx shader compatible with sokol-gl + - creates an sgl_pipeline object with alpha-blending using + this shader + - creates a 1-byte-per-pixel font atlas texture via sokol-gfx + (pixel format SG_PIXELFORMAT_R8) + + fonsDrawText(): + - this will call the following sequence of sokol-gl functions: + + sgl_enable_texture(); + sgl_texture(...); + sgl_push_pipeline(); + sgl_load_pipeline(...); + sgl_begin_triangles(); + for each vertex: + sgl_v2f_t2f_c1i(...); + sgl_end(); + sgl_pop_pipeline(); + sgl_disable_texture(); + + - note that sokol-gl will merge several sgl_*_begin/sgl_end pairs + into a single draw call if no relevant state has changed, typically + all calls to fonsDrawText() will be merged into a single draw call + as long as all calls use the same FONScontext + + sfons_flush(FONScontext* ctx): + - this will call sg_update_image() on the font atlas texture + if fontstash.h has added any rasterized glyphs since the last + frame + + sfons_destroy(FONScontext* ctx): + - destroy the font atlas texture, sgl_pipeline and sg_shader objects + + + MEMORY ALLOCATION OVERRIDE + ========================== + You can override the memory allocation functions at initialization time + like this: + + void* my_alloc(size_t size, void* user_data) { + return malloc(size); + } + + void my_free(void* ptr, void* user_data) { + free(ptr); + } + + ... + FONScontext* fons_context = sfons_create(&(sfons_desc_t){ + ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + } + }); + ... + + If no overrides are provided, malloc and free will be used. Please + note that this doesn't affect any memory allocation performed + in fontstash.h (unfortunately those are hardwired to malloc/free). + + LICENSE + ======= + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + This software is provided 'as-is', without any express or implied warranty. + In no event will the authors be held liable for any damages arising from the + use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software in a + product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#define SOKOL_FONTSTASH_INCLUDED (1) +#include +#include +#include // size_t + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_fontstash.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_FONTSTASH_API_DECL) +#define SOKOL_FONTSTASH_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_FONTSTASH_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_FONTSTASH_IMPL) +#define SOKOL_FONTSTASH_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_FONTSTASH_API_DECL __declspec(dllimport) +#else +#define SOKOL_FONTSTASH_API_DECL extern +#endif +#endif +#ifdef __cplusplus +extern "C" { +#endif + +/* + sfonst_allocator_t + + Used in sfons_desc_t to provide custom memory-alloc and -free functions + to sokol_fontstash.h. If memory management should be overridden, both the + alloc_fn and free_fn function must be provided (e.g. it's not valid to + override one function but not the other). + + NOTE that this does not affect memory allocation calls inside + fontstash.h +*/ +typedef struct sfons_allocator_t { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sfons_allocator_t; + +typedef struct sfons_desc_t { + int width; // initial width of font atlas texture (default: 512, must be power of 2) + int height; // initial height of font atlas texture (default: 512, must be power of 2) + sfons_allocator_t allocator; // optional memory allocation overrides +} sfons_desc_t; + +SOKOL_FONTSTASH_API_DECL FONScontext* sfons_create(const sfons_desc_t* desc); +SOKOL_FONTSTASH_API_DECL void sfons_destroy(FONScontext* ctx); +SOKOL_FONTSTASH_API_DECL void sfons_flush(FONScontext* ctx); +SOKOL_FONTSTASH_API_DECL uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* SOKOL_FONTSTASH_INCLUDED */ + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_FONTSTASH_IMPL +#define SOKOL_FONTSTASH_IMPL_INCLUDED (1) + +#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE) +#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sfons_desc_t.allocator to override memory allocation functions" +#endif + +#include // memset, memcpy +#include // malloc, free + +#if !defined(SOKOL_GL_INCLUDED) +#error "Please include sokol_gl.h before sokol_fontstash.h" +#endif +#if !defined(FONS_H) +#error "Please include fontstash.h before sokol_fontstash.h" +#endif + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef SOKOL_UNREACHABLE + #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) +#endif +#ifndef _SOKOL_UNUSED + #define _SOKOL_UNUSED(x) (void)(x) +#endif + +#if defined(SOKOL_GLCORE33) +/* + Embedded source code compiled with: + + sokol-shdc -i sfons.glsl -o sfons.h -l glsl330: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 + uniform vs_params { + uniform mat4 mvp; + uniform mat4 tm; + }; + in vec4 position; + in vec2 texcoord0; + in vec4 color0; + in float psize; + out vec4 uv; + out vec4 color; + void main() { + gl_Position = mvp * position; + #ifndef SOKOL_WGSL + gl_PointSize = psize; + #endif + uv = tm * vec4(texcoord0, 0.0, 1.0); + color = color0; + } + @end + + @fs fs + uniform texture2D tex; + uniform sampler smp; + in vec4 uv; + in vec4 color; + out vec4 frag_color; + void main() { + frag_color = vec4(1.0, 1.0, 1.0, texture(sampler2D(tex, smp), uv.xy).r) * color; + } + @end + + @program sfontstash vs fs +*/ +static const char _sfons_vs_source_glsl330[478] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,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,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, +}; +static const char _sfons_fs_source_glsl330[203] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,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,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x75,0x76,0x3b,0x0a, + 0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a, + 0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76, + 0x65,0x63,0x34,0x28,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e, + 0x30,0x2c,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x5f,0x73, + 0x6d,0x70,0x2c,0x20,0x75,0x76,0x2e,0x78,0x79,0x29,0x2e,0x78,0x29,0x20,0x2a,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_GLES3) +static const char _sfons_vs_source_glsl300es[481] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x38,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f, + 0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29, + 0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69, + 0x6f,0x6e,0x20,0x3d,0x20,0x33,0x29,0x20,0x69,0x6e,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x20,0x70,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34, + 0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, + 0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x6f,0x75,0x74, + 0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79, + 0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32, + 0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b, + 0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d, + 0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x33,0x5d,0x29,0x20, + 0x2a,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69,0x7a,0x65,0x20,0x3d,0x20,0x70, + 0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x6d, + 0x61,0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d, + 0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x35,0x5d,0x2c,0x20, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x36,0x5d,0x2c,0x20,0x76,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x37,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65, + 0x63,0x34,0x28,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x2c,0x20,0x30,0x2e, + 0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a, + 0x00, +}; +static const char _sfons_fs_source_glsl300es[276] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, + 0x70,0x72,0x65,0x63,0x69,0x73,0x69,0x6f,0x6e,0x20,0x6d,0x65,0x64,0x69,0x75,0x6d, + 0x70,0x20,0x66,0x6c,0x6f,0x61,0x74,0x3b,0x0a,0x70,0x72,0x65,0x63,0x69,0x73,0x69, + 0x6f,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x75, + 0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x73,0x61,0x6d, + 0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x3b,0x0a, + 0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x68,0x69,0x67,0x68,0x70,0x20, + 0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b, + 0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x75, + 0x76,0x3b,0x0a,0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61, + 0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x31,0x2e,0x30, + 0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76,0x2e, + 0x78,0x79,0x29,0x2e,0x78,0x29,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_METAL) +static const uint8_t _sfons_vs_bytecode_metal_macos[3317] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf5,0x0c,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, + 0xe0,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,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x76,0x25,0x5f,0x37,0x22,0xd0,0x3f, + 0x64,0xef,0xff,0xc3,0x45,0x1a,0x3d,0xb7,0x5e,0x83,0x13,0x96,0xd3,0x09,0xec,0x53, + 0x25,0xd5,0x7e,0x0c,0xed,0xb9,0x58,0x34,0x02,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,0xc4,0x0b,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00, + 0x00,0xee,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,0x81,0x00,0x00, + 0x00,0x1b,0xc8,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x90,0x80,0x8a,0x18,0x87,0x77, + 0x90,0x07,0x79,0x28,0x87,0x71,0xa0,0x07,0x76,0xc8,0x87,0x36,0x90,0x87,0x77,0xa8, + 0x07,0x77,0x20,0x87,0x72,0x20,0x87,0x36,0x20,0x87,0x74,0xb0,0x87,0x74,0x20,0x87, + 0x72,0x68,0x83,0x79,0x88,0x07,0x79,0xa0,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76, + 0x08,0x07,0x7a,0x40,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c, + 0xd2,0x61,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8, + 0xa1,0x0d,0xc6,0x21,0x1c,0xd8,0x81,0x1d,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 0x28,0x07,0x80,0x60,0x87,0x72,0x98,0x87,0x79,0x68,0x03,0x78,0x90,0x87,0x72,0x18, + 0x87,0x74,0x98,0x87,0x72,0x68,0x03,0x73,0x80,0x87,0x76,0x08,0x07,0x72,0x00,0xcc, + 0x21,0x1c,0xd8,0x61,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,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x06, + 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,0x68, + 0x03,0x77,0x78,0x07,0x77,0x68,0x03,0x76,0x28,0x87,0x70,0x30,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,0x40,0x1d,0xea,0xa1,0x1d,0xe0,0xa1,0x0d,0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca, + 0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36, + 0x70,0x87,0x70,0x70,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,0xe6, + 0x21,0x1d,0xce,0xc1,0x1d,0xca,0x81,0x1c,0xda,0x40,0x1f,0xca,0x41,0x1e,0xde,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,0x03,0x7a,0x90,0x87,0x70,0x80,0x07, + 0x78,0x48,0x07,0x77,0x38,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41, + 0x1e,0xea,0xa1,0x1c,0x00,0x62,0x1e,0xe8,0x21,0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e, + 0xe4,0xe1,0x1d,0xe8,0xa1,0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0x40,0x1c,0xea, + 0xc1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x0d,0xe6,0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c, + 0x00,0x88,0x7a,0x70,0x87,0x79,0x08,0x07,0x73,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea, + 0x61,0x1e,0xca,0xa1,0x0d,0xe6,0xe1,0x1d,0xcc,0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1, + 0x1d,0xc2,0x81,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x36,0x18,0x42, + 0x01,0x2c,0x40,0x05,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,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,0x34, + 0x01,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x02,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,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e, + 0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6, + 0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06, + 0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45,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,0x00,0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c, + 0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c, + 0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,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,0xa8,0x95,0x40, + 0x19,0x14,0x01,0xbd,0x11,0x00,0x1a,0x33,0x00,0x24,0x66,0x00,0x28,0xcc,0x00,0x00, + 0x00,0xe3,0x15,0x4b,0x94,0x65,0x11,0x05,0x65,0x90,0x21,0x1a,0x0c,0x13,0x02,0xf9, + 0x8c,0x57,0x3c,0x55,0xd7,0x2d,0x14,0x94,0x41,0x86,0xea,0x70,0x4c,0x08,0xe4,0x63, + 0x41,0x01,0x9f,0xf1,0x0a,0x4a,0x13,0x03,0x31,0x70,0x28,0x28,0x83,0x0c,0x1a,0x43, + 0x99,0x10,0xc8,0xc7,0x8a,0x00,0x3e,0xe3,0x15,0xd9,0x77,0x06,0x67,0x40,0x51,0x50, + 0x06,0x19,0xbe,0x48,0x33,0x21,0x90,0x8f,0x15,0x01,0x7c,0xc6,0x2b,0x3c,0x32,0x68, + 0x03,0x36,0x20,0x03,0x0a,0xca,0x20,0xc3,0x18,0x60,0x99,0x09,0x81,0x7c,0xc6,0x2b, + 0xc4,0x00,0x0d,0xe2,0x00,0x0e,0x3c,0x0a,0xca,0x20,0xc3,0x19,0x70,0x61,0x60,0x42, + 0x20,0x1f,0x0b,0x0a,0xf8,0x8c,0x57,0x9c,0x41,0x1b,0xd8,0x41,0x1d,0x88,0x01,0x05, + 0xc5,0x86,0x00,0x3e,0xb3,0x0d,0x61,0x10,0x00,0xb3,0x0d,0x41,0x1b,0x04,0xb3,0x0d, + 0xc1,0x23,0xcc,0x36,0x04,0x6e,0x30,0x64,0x10,0x10,0x03,0x00,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, +}; +static const uint8_t _sfons_fs_bytecode_metal_macos[2857] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x29,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, + 0x50,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,0x38,0x3c,0x07,0x7d,0xe0,0x21,0x6b, + 0x04,0x03,0x36,0x12,0x1d,0xbc,0x24,0x12,0xc4,0x27,0xab,0xef,0x84,0x17,0xbe,0x12, + 0x8d,0xc7,0xbc,0x06,0xbc,0x98,0x53,0xdb,0x0b,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,0x30,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0x89,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,0x89,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07,0x7a,0x60,0x87,0x7c, + 0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72,0x68,0x03,0x72,0x48, + 0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90,0x07,0x7a,0x68,0x03, + 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc,0x21,0x1c,0xd8,0x61, + 0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81,0x1d,0xca,0xa1,0x0d, + 0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d,0xd8,0x61,0x1e,0x00, + 0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87,0x79,0x98,0x87,0x36, + 0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,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,0x00,0x7a,0x90, + 0x87,0x7a,0x28,0x07,0x60,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,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36,0x60,0x87,0x72,0x08, + 0x07,0x73,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,0xd4,0xa1,0x1e,0xda,0x01,0x1e,0xda,0x80,0x1e, + 0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,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,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c,0xc8,0xa1,0x0d,0xf4, + 0xa1,0x1c,0xe4,0xe1,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,0xa0, + 0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68,0x83,0x76,0x08,0x07, + 0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6,0x81,0x1e,0xc2,0x61, + 0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d, + 0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e,0xda,0x60,0x1e,0xd2, + 0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87,0x70,0x30,0x87,0x72, + 0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e, + 0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e,0xde,0xc1,0x1c,0xe8, + 0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 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,0x05,0x49,0x18,0x00,0x00,0x03,0x00,0x00, + 0x00,0x13,0x86,0x40,0x18,0x26,0x0c,0x44,0x61,0x00,0x00,0x00,0x00,0x89,0x20,0x00, + 0x00,0x1e,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x4c,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x70,0x94,0x34,0x45,0x94,0x30, + 0xf9,0xff,0x44,0x5c,0x13,0x15,0x11,0xbf,0x3d,0xfc,0xd3,0x18,0x01,0x30,0x88,0x30, + 0x04,0x17,0x49,0x53,0x44,0x09,0x93,0xff,0x4b,0x00,0xf3,0x2c,0x44,0xf4,0x4f,0x63, + 0x04,0xc0,0x20,0x42,0x21,0x94,0x42,0x84,0x40,0x0c,0x9d,0x61,0x04,0x01,0x98,0x23, + 0x08,0xe6,0x08,0xc0,0x60,0x18,0x41,0x58,0x0a,0x12,0x88,0x49,0x8a,0x29,0x40,0x6d, + 0x20,0x20,0x05,0xd6,0x30,0x02,0xb1,0x8c,0x00,0x00,0x00,0x00,0x00,0x13,0xb2,0x70, + 0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68, + 0x83,0x76,0x08,0x87,0x71,0x78,0x87,0x79,0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83, + 0x38,0x70,0x03,0x38,0xd8,0x70,0x1b,0xe5,0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07, + 0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78, + 0xa0,0x07,0x78,0xd0,0x06,0xe9,0x80,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90, + 0x0e,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e, + 0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76, + 0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20, + 0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07, + 0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a, + 0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30, + 0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07, + 0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74, + 0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0, + 0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x79,0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07, + 0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71, + 0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20, + 0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f, + 0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76, + 0xd0,0x06,0xf6,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50, + 0x07,0x71,0x20,0x07,0x6d,0x60,0x0f,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07, + 0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78, + 0xa0,0x07,0x71,0x60,0x07,0x7a,0x30,0x07,0x72,0x30,0x84,0x41,0x00,0x00,0x08,0x00, + 0x00,0x00,0x00,0x00,0x18,0xc2,0x38,0x40,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x64, + 0x81,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c, + 0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50,0x04,0x85,0x50, + 0x10,0x65,0x40,0x70,0x2c,0xa1,0x09,0x00,0x00,0x79,0x18,0x00,0x00,0xb7,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,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c, + 0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26, + 0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac, + 0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45,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,0x83,0x11,0x11,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b, + 0xb4,0xc3,0x3b,0x90,0x43,0x3d,0xb0,0x43,0x39,0xb8,0x81,0x39,0xb0,0x43,0x38,0x9c, + 0xc3,0x3c,0x4c,0x11,0x82,0x61,0x84,0xc2,0x0e,0xec,0x60,0x0f,0xed,0xe0,0x06,0xe9, + 0x40,0x0e,0xe5,0xe0,0x0e,0xf4,0x30,0x25,0x28,0x46,0x2c,0xe1,0x90,0x0e,0xf2,0xe0, + 0x06,0xf6,0x50,0x0e,0xf2,0x30,0x0f,0xe9,0xf0,0x0e,0xee,0x30,0x25,0x30,0x46,0x50, + 0xe1,0x90,0x0e,0xf2,0xe0,0x06,0xec,0x10,0x0e,0xee,0x70,0x0e,0xf5,0x10,0x0e,0xe7, + 0x50,0x0e,0xbf,0x60,0x0f,0xe5,0x20,0x0f,0xf3,0x90,0x0e,0xef,0xe0,0x0e,0x53,0x02, + 0x64,0xc4,0x14,0x0e,0xe9,0x20,0x0f,0x6e,0x30,0x0e,0xef,0xd0,0x0e,0xf0,0x90,0x0e, + 0xec,0x50,0x0e,0xbf,0xf0,0x0e,0xf0,0x40,0x0f,0xe9,0xf0,0x0e,0xee,0x30,0x0f,0x53, + 0x06,0x85,0x71,0x46,0x30,0xe1,0x90,0x0e,0xf2,0xe0,0x06,0xe6,0x20,0x0f,0xe1,0x70, + 0x0e,0xed,0x50,0x0e,0xee,0x40,0x0f,0x53,0x02,0x35,0x00,0x00,0x00,0x79,0x18,0x00, + 0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03, + 0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c, + 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,0x16,0x00,0x00, + 0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x14,0xc7,0x22, + 0x80,0x40,0x20,0x88,0x8d,0x00,0x8c,0x25,0x00,0x01,0xa9,0x11,0x80,0x1a,0x20,0x31, + 0x03,0x40,0x61,0x0e,0xe2,0xba,0xae,0x6a,0x06,0x80,0xc0,0x0c,0xc0,0x08,0xc0,0x18, + 0x01,0x08,0x82,0x20,0xfe,0x01,0x00,0x00,0x00,0x83,0x0c,0x0f,0x91,0x8c,0x18,0x28, + 0x42,0x80,0x39,0x4d,0x80,0x2c,0xc9,0x30,0xc8,0x70,0x04,0x8d,0x05,0x91,0x7c,0x66, + 0x1b,0x94,0x00,0xc8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sfons_vs_bytecode_metal_ios[3317] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf5,0x0c,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, + 0xe0,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,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x17,0x11,0x57,0x16,0x94,0x42,0x52, + 0xfb,0x1e,0xd0,0x32,0xfd,0x87,0x16,0xb0,0xa4,0xd0,0xc2,0x43,0xbe,0x93,0x8c,0xe0, + 0x2d,0x7a,0x5c,0x3e,0x06,0x4c,0x57,0xeb,0x4b,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,0xc0,0x0b,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00, + 0x00,0xed,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,0x82,0x00,0x00, + 0x00,0x1b,0xc8,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x90,0x80,0x8a,0x18,0x87,0x77, + 0x90,0x07,0x79,0x28,0x87,0x71,0xa0,0x07,0x76,0xc8,0x87,0x36,0x90,0x87,0x77,0xa8, + 0x07,0x77,0x20,0x87,0x72,0x20,0x87,0x36,0x20,0x87,0x74,0xb0,0x87,0x74,0x20,0x87, + 0x72,0x68,0x83,0x79,0x88,0x07,0x79,0xa0,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76, + 0x08,0x07,0x7a,0x40,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c, + 0xd2,0x61,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8, + 0xa1,0x0d,0xc6,0x21,0x1c,0xd8,0x81,0x1d,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 0x28,0x07,0x80,0x60,0x87,0x72,0x98,0x87,0x79,0x68,0x03,0x78,0x90,0x87,0x72,0x18, + 0x87,0x74,0x98,0x87,0x72,0x68,0x03,0x73,0x80,0x87,0x76,0x08,0x07,0x72,0x00,0xcc, + 0x21,0x1c,0xd8,0x61,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,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x06, + 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,0x68, + 0x03,0x77,0x78,0x07,0x77,0x68,0x03,0x76,0x28,0x87,0x70,0x30,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,0x40,0x1d,0xea,0xa1,0x1d,0xe0,0xa1,0x0d,0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca, + 0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36, + 0x70,0x87,0x70,0x70,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,0xe6, + 0x21,0x1d,0xce,0xc1,0x1d,0xca,0x81,0x1c,0xda,0x40,0x1f,0xca,0x41,0x1e,0xde,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,0x03,0x7a,0x90,0x87,0x70,0x80,0x07, + 0x78,0x48,0x07,0x77,0x38,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41, + 0x1e,0xea,0xa1,0x1c,0x00,0x62,0x1e,0xe8,0x21,0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e, + 0xe4,0xe1,0x1d,0xe8,0xa1,0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0x40,0x1c,0xea, + 0xc1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x0d,0xe6,0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c, + 0x00,0x88,0x7a,0x70,0x87,0x79,0x08,0x07,0x73,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea, + 0x61,0x1e,0xca,0xa1,0x0d,0xe6,0xe1,0x1d,0xcc,0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1, + 0x1d,0xc2,0x81,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x36,0x20,0x42, + 0x01,0x24,0xc0,0x02,0x54,0x00,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,0x40,0x02,0x00,0x00,0x00, + 0x00,0x79,0x18,0x00,0x00,0x02,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,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae, + 0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06, + 0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5, + 0xc6,0xe6,0xc6,0x45,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,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,0x00,0x00, + 0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4, + 0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43, + 0x3d,0xc0,0xc3,0x3c,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,0xa8,0x95,0x40,0x19,0x14,0x01,0xbd, + 0x11,0x00,0x1a,0x33,0x00,0x24,0x66,0x00,0x28,0xcc,0x00,0x00,0x00,0xe3,0x15,0x4b, + 0x94,0x65,0x11,0x05,0x65,0x90,0x21,0x1a,0x0c,0x13,0x02,0xf9,0x8c,0x57,0x3c,0x55, + 0xd7,0x2d,0x14,0x94,0x41,0x86,0xea,0x70,0x4c,0x08,0xe4,0x63,0x41,0x01,0x9f,0xf1, + 0x0a,0x4a,0x13,0x03,0x31,0x70,0x28,0x28,0x83,0x0c,0x1a,0x43,0x99,0x10,0xc8,0xc7, + 0x8a,0x00,0x3e,0xe3,0x15,0xd9,0x77,0x06,0x67,0x40,0x51,0x50,0x06,0x19,0xbe,0x48, + 0x33,0x21,0x90,0x8f,0x15,0x01,0x7c,0xc6,0x2b,0x3c,0x32,0x68,0x03,0x36,0x20,0x03, + 0x0a,0xca,0x20,0xc3,0x18,0x60,0x99,0x09,0x81,0x7c,0xc6,0x2b,0xc4,0x00,0x0d,0xe2, + 0x00,0x0e,0x3c,0x0a,0xca,0x20,0xc3,0x19,0x70,0x61,0x60,0x42,0x20,0x1f,0x0b,0x0a, + 0xf8,0x8c,0x57,0x9c,0x41,0x1b,0xd8,0x41,0x1d,0x88,0x01,0x05,0xc5,0x86,0x00,0x3e, + 0xb3,0x0d,0x61,0x10,0x00,0xb3,0x0d,0x41,0x1b,0x04,0xb3,0x0d,0xc1,0x23,0xcc,0x36, + 0x04,0x6e,0x30,0x64,0x10,0x10,0x03,0x00,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,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sfons_fs_bytecode_metal_ios[2841] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x19,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, + 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,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0x7c,0x59,0x61,0x85,0x8c,0x08,0x62, + 0xf6,0xd0,0x45,0x0a,0x3a,0xcb,0xa8,0xa5,0x3c,0x2d,0x6c,0x6b,0x9c,0x3d,0xf0,0xf5, + 0xc4,0xdc,0xb5,0x90,0xdc,0xee,0x5a,0x9f,0x63,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,0x28,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0x87,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,0x89,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07,0x7a,0x60,0x87,0x7c, + 0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72,0x68,0x03,0x72,0x48, + 0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90,0x07,0x7a,0x68,0x03, + 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc,0x21,0x1c,0xd8,0x61, + 0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81,0x1d,0xca,0xa1,0x0d, + 0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d,0xd8,0x61,0x1e,0x00, + 0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87,0x79,0x98,0x87,0x36, + 0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,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,0x00,0x7a,0x90, + 0x87,0x7a,0x28,0x07,0x60,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,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36,0x60,0x87,0x72,0x08, + 0x07,0x73,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,0xd4,0xa1,0x1e,0xda,0x01,0x1e,0xda,0x80,0x1e, + 0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,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,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c,0xc8,0xa1,0x0d,0xf4, + 0xa1,0x1c,0xe4,0xe1,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,0xa0, + 0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68,0x83,0x76,0x08,0x07, + 0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6,0x81,0x1e,0xc2,0x61, + 0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d, + 0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e,0xda,0x60,0x1e,0xd2, + 0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87,0x70,0x30,0x87,0x72, + 0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e, + 0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e,0xde,0xc1,0x1c,0xe8, + 0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 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,0x05,0x49,0x18,0x00,0x00,0x03,0x00,0x00, + 0x00,0x13,0x86,0x40,0x18,0x26,0x0c,0x44,0x61,0x00,0x00,0x00,0x00,0x89,0x20,0x00, + 0x00,0x1e,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4, + 0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4, + 0x4c,0x10,0x4c,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x70,0x94,0x34,0x45,0x94,0x30, + 0xf9,0xff,0x44,0x5c,0x13,0x15,0x11,0xbf,0x3d,0xfc,0xd3,0x18,0x01,0x30,0x88,0x30, + 0x04,0x17,0x49,0x53,0x44,0x09,0x93,0xff,0x4b,0x00,0xf3,0x2c,0x44,0xf4,0x4f,0x63, + 0x04,0xc0,0x20,0x42,0x21,0x94,0x42,0x84,0x40,0x0c,0x9d,0x61,0x04,0x01,0x98,0x23, + 0x08,0xe6,0x08,0xc0,0x60,0x18,0x41,0x58,0x0a,0x12,0x88,0x49,0x8a,0x29,0x40,0x6d, + 0x20,0x20,0x05,0xd6,0x30,0x02,0xb1,0x8c,0x00,0x00,0x00,0x00,0x00,0x13,0xa8,0x70, + 0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68, + 0x83,0x74,0x78,0x87,0x79,0xc8,0x03,0x37,0x80,0x03,0x37,0x80,0x83,0x0d,0xb7,0x51, + 0x0e,0x6d,0x00,0x0f,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07, + 0x74,0xd0,0x06,0xe9,0x10,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78, + 0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9,0x10,0x07,0x76,0xa0,0x07,0x71,0x60, + 0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xe9,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07, + 0x7a,0x30,0x07,0x72,0xd0,0x06,0xe9,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a, + 0x60,0x07,0x74,0xd0,0x06,0xe6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30, + 0x07,0x72,0xd0,0x06,0xe6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07, + 0x74,0xd0,0x06,0xf6,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76, + 0xd0,0x06,0xf6,0x20,0x07,0x74,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0, + 0x06,0xf6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06, + 0xf6,0x40,0x07,0x78,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6, + 0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x90, + 0x07,0x76,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06, + 0xf6,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72, + 0x80,0x07,0x6d,0x60,0x0f,0x71,0x90,0x07,0x72,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0, + 0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07, + 0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x75,0x10,0x07,0x72, + 0xa0,0x07,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xd0,0x06,0xf6,0x10, + 0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07, + 0x70,0x20,0x07,0x74,0xd0,0x06,0xee,0x80,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x73, + 0x20,0x07,0x43,0x18,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x21,0x8c,0x03, + 0x04,0x80,0x00,0x00,0x00,0x00,0x00,0x40,0x16,0x08,0x00,0x00,0x00,0x08,0x00,0x00, + 0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43, + 0x5a,0x25,0x30,0x02,0x50,0x04,0x85,0x50,0x10,0x65,0x40,0x70,0x2c,0x01,0x12,0x00, + 0x00,0x79,0x18,0x00,0x00,0xb7,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,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae, + 0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06, + 0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5, + 0xc6,0xe6,0xc6,0x45,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,0x83,0x11,0x11, + 0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0xb4,0xc3,0x3b,0x90,0x43,0x3d,0xb0,0x43,0x39, + 0xb8,0x81,0x39,0xb0,0x43,0x38,0x9c,0xc3,0x3c,0x4c,0x11,0x82,0x61,0x84,0xc2,0x0e, + 0xec,0x60,0x0f,0xed,0xe0,0x06,0xe9,0x40,0x0e,0xe5,0xe0,0x0e,0xf4,0x30,0x25,0x28, + 0x46,0x2c,0xe1,0x90,0x0e,0xf2,0xe0,0x06,0xf6,0x50,0x0e,0xf2,0x30,0x0f,0xe9,0xf0, + 0x0e,0xee,0x30,0x25,0x30,0x46,0x50,0xe1,0x90,0x0e,0xf2,0xe0,0x06,0xec,0x10,0x0e, + 0xee,0x70,0x0e,0xf5,0x10,0x0e,0xe7,0x50,0x0e,0xbf,0x60,0x0f,0xe5,0x20,0x0f,0xf3, + 0x90,0x0e,0xef,0xe0,0x0e,0x53,0x02,0x64,0xc4,0x14,0x0e,0xe9,0x20,0x0f,0x6e,0x30, + 0x0e,0xef,0xd0,0x0e,0xf0,0x90,0x0e,0xec,0x50,0x0e,0xbf,0xf0,0x0e,0xf0,0x40,0x0f, + 0xe9,0xf0,0x0e,0xee,0x30,0x0f,0x53,0x06,0x85,0x71,0x46,0x30,0xe1,0x90,0x0e,0xf2, + 0xe0,0x06,0xe6,0x20,0x0f,0xe1,0x70,0x0e,0xed,0x50,0x0e,0xee,0x40,0x0f,0x53,0x02, + 0x35,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c, + 0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c, + 0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,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,0x16,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00, + 0x00,0x0b,0x00,0x00,0x00,0x14,0xc7,0x22,0x80,0x40,0x20,0x88,0x8d,0x00,0x8c,0x25, + 0x00,0x01,0xa9,0x11,0x80,0x1a,0x20,0x31,0x03,0x40,0x61,0x0e,0xe2,0xba,0xae,0x6a, + 0x06,0x80,0xc0,0x0c,0xc0,0x08,0xc0,0x18,0x01,0x08,0x82,0x20,0xfe,0x01,0x00,0x00, + 0x00,0x83,0x0c,0x0f,0x91,0x8c,0x18,0x28,0x42,0x80,0x39,0x4d,0x80,0x2c,0xc9,0x30, + 0xc8,0x70,0x04,0x8d,0x05,0x91,0x7c,0x66,0x1b,0x94,0x00,0xc8,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const char _sfons_vs_source_metal_sim[756] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x76, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x74,0x6d,0x3b,0x0a,0x7d,0x3b, + 0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f, + 0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, + 0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69,0x7a, + 0x65,0x20,0x5b,0x5b,0x70,0x6f,0x69,0x6e,0x74,0x5f,0x73,0x69,0x7a,0x65,0x5d,0x5d, + 0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x34,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x61,0x74, + 0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72, + 0x64,0x30,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31, + 0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75, + 0x74,0x65,0x28,0x32,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x20,0x70,0x73,0x69,0x7a,0x65,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69, + 0x62,0x75,0x74,0x65,0x28,0x33,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x76, + 0x65,0x72,0x74,0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20, + 0x6d,0x61,0x69,0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69, + 0x6e,0x20,0x5b,0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20, + 0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x26,0x20,0x5f,0x31,0x39,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72, + 0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b, + 0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70,0x20,0x2a, + 0x20,0x69,0x6e,0x2e,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a,0x20,0x20, + 0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f,0x50,0x6f,0x69,0x6e,0x74,0x53,0x69, + 0x7a,0x65,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x70,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20, + 0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x75,0x76,0x20,0x3d,0x20,0x5f,0x31,0x39,0x2e, + 0x74,0x6d,0x20,0x2a,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x74, + 0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31, + 0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, +}; +static const char _sfons_fs_source_metal_sim[464] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d, + 0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x7d, + 0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, + 0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29, + 0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e, + 0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x66,0x72,0x61,0x67,0x6d,0x65, + 0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69, + 0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b, + 0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x74,0x65,0x78, + 0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x3e,0x20,0x74,0x65, + 0x78,0x20,0x5b,0x5b,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5d,0x5d, + 0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x73,0x6d,0x70,0x20,0x5b,0x5b, + 0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a, + 0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75, + 0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e, + 0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x34,0x28,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e, + 0x30,0x2c,0x20,0x74,0x65,0x78,0x2e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x28,0x73,0x6d, + 0x70,0x2c,0x20,0x69,0x6e,0x2e,0x75,0x76,0x2e,0x78,0x79,0x29,0x2e,0x78,0x29,0x20, + 0x2a,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20, + 0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, + +}; +#elif defined(SOKOL_D3D11) +static const uint8_t _sfons_vs_bytecode_hlsl4[1032] = { + 0x44,0x58,0x42,0x43,0x74,0x7f,0x01,0xd9,0xf4,0xd5,0xed,0x1d,0x74,0xc1,0x30,0x27, + 0xd8,0xe9,0x9d,0x50,0x01,0x00,0x00,0x00,0x08,0x04,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0x14,0x01,0x00,0x00,0x90,0x01,0x00,0x00,0x00,0x02,0x00,0x00, + 0x8c,0x03,0x00,0x00,0x52,0x44,0x45,0x46,0xd8,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x48,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xfe,0xff, + 0x10,0x81,0x00,0x00,0xaf,0x00,0x00,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, + 0x73,0x00,0xab,0xab,0x3c,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x60,0x00,0x00,0x00, + 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x98,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xa8,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x40,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x98,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5f,0x31,0x39,0x5f, + 0x6d,0x76,0x70,0x00,0x02,0x00,0x03,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x5f,0x31,0x39,0x5f,0x74,0x6d,0x00,0x4d,0x69,0x63,0x72,0x6f, + 0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c,0x53,0x4c,0x20,0x53,0x68, + 0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x72,0x20,0x31,0x30, + 0x2e,0x31,0x00,0xab,0x49,0x53,0x47,0x4e,0x74,0x00,0x00,0x00,0x04,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x68,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x03,0x03,0x00,0x00,0x68,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x68,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x00,0xab,0xab,0xab, + 0x4f,0x53,0x47,0x4e,0x68,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00, + 0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0f,0x00,0x00,0x00, + 0x59,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44, + 0x00,0x53,0x56,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0xab,0xab,0xab, + 0x53,0x48,0x44,0x52,0x84,0x01,0x00,0x00,0x40,0x00,0x01,0x00,0x61,0x00,0x00,0x00, + 0x59,0x00,0x00,0x04,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00, + 0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x5f,0x00,0x00,0x03, + 0x32,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00, + 0x02,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00, + 0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00,0x67,0x00,0x00,0x04, + 0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x68,0x00,0x00,0x02, + 0x01,0x00,0x00,0x00,0x38,0x00,0x00,0x08,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x56,0x15,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, + 0x05,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x06,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, + 0x04,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08, + 0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, + 0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x36,0x00,0x00,0x05, + 0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x1e,0x10,0x00,0x02,0x00,0x00,0x00, + 0x38,0x00,0x00,0x08,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x56,0x15,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x06,0x10,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00, + 0x00,0x00,0x00,0x00,0xa6,0x1a,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, + 0x32,0x00,0x00,0x0a,0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,0xf6,0x1f,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54, + 0x74,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x06,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sfons_fs_bytecode_hlsl4[628] = { + 0x44,0x58,0x42,0x43,0xb6,0x66,0xf0,0xfc,0x09,0x54,0x2a,0x35,0x84,0x1d,0x27,0xd2, + 0xff,0xb3,0x2e,0xdb,0x01,0x00,0x00,0x00,0x74,0x02,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0xc8,0x00,0x00,0x00,0x14,0x01,0x00,0x00,0x48,0x01,0x00,0x00, + 0xf8,0x01,0x00,0x00,0x52,0x44,0x45,0x46,0x8c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xff,0xff, + 0x10,0x81,0x00,0x00,0x64,0x00,0x00,0x00,0x5c,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x02,0x00,0x00,0x00, + 0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x73,0x6d,0x70,0x00,0x74,0x65,0x78,0x00, + 0x4d,0x69,0x63,0x72,0x6f,0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c, + 0x53,0x4c,0x20,0x53,0x68,0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c, + 0x65,0x72,0x20,0x31,0x30,0x2e,0x31,0x00,0x49,0x53,0x47,0x4e,0x44,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x03,0x00,0x00, + 0x38,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44, + 0x00,0xab,0xab,0xab,0x4f,0x53,0x47,0x4e,0x2c,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x53,0x56,0x5f,0x54, + 0x61,0x72,0x67,0x65,0x74,0x00,0xab,0xab,0x53,0x48,0x44,0x52,0xa8,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x2a,0x00,0x00,0x00,0x5a,0x00,0x00,0x03,0x00,0x60,0x10,0x00, + 0x00,0x00,0x00,0x00,0x58,0x18,0x00,0x04,0x00,0x70,0x10,0x00,0x00,0x00,0x00,0x00, + 0x55,0x55,0x00,0x00,0x62,0x10,0x00,0x03,0x32,0x10,0x10,0x00,0x00,0x00,0x00,0x00, + 0x62,0x10,0x00,0x03,0xf2,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x65,0x00,0x00,0x03, + 0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x68,0x00,0x00,0x02,0x01,0x00,0x00,0x00, + 0x45,0x00,0x00,0x09,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00, + 0x00,0x00,0x00,0x00,0x96,0x73,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x10,0x00, + 0x00,0x00,0x00,0x00,0x36,0x00,0x00,0x05,0x12,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x01,0x40,0x00,0x00,0x00,0x00,0x80,0x3f,0x38,0x00,0x00,0x07,0xf2,0x20,0x10,0x00, + 0x00,0x00,0x00,0x00,0x06,0x0c,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x1e,0x10,0x00, + 0x01,0x00,0x00,0x00,0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00, + 0x04,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, +}; +#elif defined(SOKOL_WGPU) +static const char _sfons_vs_source_wgsl[1162] = { + 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, + 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x20,0x7b,0x0a,0x20,0x20,0x2f,0x2a, + 0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x30,0x29,0x20,0x2a,0x2f,0x0a,0x20, + 0x20,0x6d,0x76,0x70,0x20,0x3a,0x20,0x6d,0x61,0x74,0x34,0x78,0x34,0x66,0x2c,0x0a, + 0x20,0x20,0x2f,0x2a,0x20,0x40,0x6f,0x66,0x66,0x73,0x65,0x74,0x28,0x36,0x34,0x29, + 0x20,0x2a,0x2f,0x0a,0x20,0x20,0x74,0x6d,0x20,0x3a,0x20,0x6d,0x61,0x74,0x34,0x78, + 0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75,0x70,0x28,0x30,0x29, + 0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x30,0x29,0x20,0x76,0x61,0x72, + 0x3c,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x3e,0x20,0x78,0x5f,0x31,0x39,0x20,0x3a, + 0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x3b,0x0a,0x0a,0x76,0x61,0x72, + 0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x70,0x6f,0x73,0x69,0x74,0x69, + 0x6f,0x6e,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76, + 0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x75,0x76,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72, + 0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61, + 0x74,0x65,0x3e,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x34,0x66,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65, + 0x3e,0x20,0x70,0x73,0x69,0x7a,0x65,0x20,0x3a,0x20,0x66,0x33,0x32,0x3b,0x0a,0x0a, + 0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x3b,0x0a,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b, + 0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x32,0x20,0x3a,0x20,0x6d,0x61, + 0x74,0x34,0x78,0x34,0x66,0x20,0x3d,0x20,0x78,0x5f,0x31,0x39,0x2e,0x6d,0x76,0x70, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x35,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f, + 0x31,0x3b,0x0a,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x20,0x3d,0x20,0x28,0x78,0x5f,0x32,0x32,0x20,0x2a,0x20,0x78,0x5f,0x32,0x35,0x29, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x32,0x20,0x3a,0x20,0x6d, + 0x61,0x74,0x34,0x78,0x34,0x66,0x20,0x3d,0x20,0x78,0x5f,0x31,0x39,0x2e,0x74,0x6d, + 0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x36,0x20,0x3a,0x20,0x76, + 0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30, + 0x3b,0x0a,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x28,0x78,0x5f,0x33,0x32,0x20,0x2a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x28,0x78,0x5f,0x33,0x36,0x2e,0x78,0x2c,0x20,0x78, + 0x5f,0x33,0x36,0x2e,0x79,0x2c,0x20,0x30,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30, + 0x66,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x35,0x20, + 0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x3b,0x0a,0x20,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x78,0x5f,0x34,0x35, + 0x3b,0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a,0x7d,0x0a,0x0a,0x73, + 0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b, + 0x0a,0x20,0x20,0x40,0x62,0x75,0x69,0x6c,0x74,0x69,0x6e,0x28,0x70,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x29,0x0a,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74, + 0x69,0x6f,0x6e,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x20,0x20,0x40, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x0a,0x20,0x20,0x75,0x76, + 0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x20,0x20,0x40,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x0a,0x20,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x5f,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a, + 0x0a,0x40,0x76,0x65,0x72,0x74,0x65,0x78,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e, + 0x28,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x20,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a, + 0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f, + 0x6e,0x28,0x31,0x29,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x5f,0x70, + 0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x2c,0x20,0x40,0x6c, + 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x32,0x29,0x20,0x63,0x6f,0x6c,0x6f,0x72, + 0x30,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c, + 0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x33,0x29,0x20,0x70,0x73, + 0x69,0x7a,0x65,0x5f,0x70,0x61,0x72,0x61,0x6d,0x20,0x3a,0x20,0x66,0x33,0x32,0x29, + 0x20,0x2d,0x3e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20, + 0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x20,0x3d,0x20,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x5f,0x31,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a, + 0x20,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20,0x3d,0x20,0x74,0x65, + 0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x70,0x73,0x69,0x7a,0x65,0x20, + 0x3d,0x20,0x70,0x73,0x69,0x7a,0x65,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20, + 0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x3b,0x0a,0x20,0x20,0x72,0x65,0x74, + 0x75,0x72,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x28,0x67,0x6c,0x5f, + 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x75,0x76,0x2c,0x20,0x63,0x6f, + 0x6c,0x6f,0x72,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +static const char _sfons_fs_source_wgsl[674] = { + 0x64,0x69,0x61,0x67,0x6e,0x6f,0x73,0x74,0x69,0x63,0x28,0x6f,0x66,0x66,0x2c,0x20, + 0x64,0x65,0x72,0x69,0x76,0x61,0x74,0x69,0x76,0x65,0x5f,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x69,0x74,0x79,0x29,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72, + 0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a,0x40,0x67,0x72,0x6f,0x75, + 0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x33,0x32, + 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,0x34,0x38,0x29,0x20,0x76,0x61,0x72,0x20,0x73,0x6d,0x70,0x20,0x3a,0x20,0x73, + 0x61,0x6d,0x70,0x6c,0x65,0x72,0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69, + 0x76,0x61,0x74,0x65,0x3e,0x20,0x75,0x76,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x3b,0x0a,0x0a,0x76,0x61,0x72,0x3c,0x70,0x72,0x69,0x76,0x61,0x74,0x65,0x3e,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x0a, + 0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b,0x0a,0x20,0x20, + 0x6c,0x65,0x74,0x20,0x78,0x5f,0x32,0x34,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66, + 0x20,0x3d,0x20,0x75,0x76,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x32, + 0x36,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x53,0x61,0x6d,0x70,0x6c,0x65,0x28,0x74,0x65,0x78,0x2c,0x20,0x73, + 0x6d,0x70,0x2c,0x20,0x76,0x65,0x63,0x32,0x66,0x28,0x78,0x5f,0x32,0x34,0x2e,0x78, + 0x2c,0x20,0x78,0x5f,0x32,0x34,0x2e,0x79,0x29,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65, + 0x74,0x20,0x78,0x5f,0x33,0x32,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d, + 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63, + 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x28,0x76,0x65,0x63,0x34,0x66,0x28,0x31,0x2e, + 0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x2c,0x20, + 0x78,0x5f,0x32,0x36,0x2e,0x78,0x29,0x20,0x2a,0x20,0x78,0x5f,0x33,0x32,0x29,0x3b, + 0x0a,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x3b,0x0a,0x7d,0x0a,0x0a,0x73,0x74, + 0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a, + 0x20,0x20,0x40,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x0a,0x20, + 0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x5f,0x31,0x20,0x3a,0x20, + 0x76,0x65,0x63,0x34,0x66,0x2c,0x0a,0x7d,0x0a,0x0a,0x40,0x66,0x72,0x61,0x67,0x6d, + 0x65,0x6e,0x74,0x0a,0x66,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x28,0x40,0x6c,0x6f,0x63, + 0x61,0x74,0x69,0x6f,0x6e,0x28,0x30,0x29,0x20,0x75,0x76,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x2c,0x20,0x40,0x6c,0x6f,0x63,0x61, + 0x74,0x69,0x6f,0x6e,0x28,0x31,0x29,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x29,0x20,0x2d,0x3e,0x20, + 0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74,0x20,0x7b,0x0a,0x20,0x20,0x75,0x76,0x20, + 0x3d,0x20,0x75,0x76,0x5f,0x70,0x61,0x72,0x61,0x6d,0x3b,0x0a,0x20,0x20,0x63,0x6f, + 0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x3b,0x0a,0x20,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x3b,0x0a,0x20, + 0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6d,0x61,0x69,0x6e,0x5f,0x6f,0x75,0x74, + 0x28,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x29,0x3b,0x0a,0x7d,0x0a, + 0x0a,0x00, +}; +#elif defined(SOKOL_DUMMY_BACKEND) +static const char* _sfons_vs_source_dummy = ""; +static const char* _sfons_fs_source_dummy = ""; +#else +#error "Please define one of SOKOL_GLCORE33, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND!" +#endif + +typedef struct _sfons_t { + sfons_desc_t desc; + sg_shader shd; + sgl_pipeline pip; + sg_image img; + sg_sampler smp; + int cur_width, cur_height; + bool img_dirty; +} _sfons_t; + +static void _sfons_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +static void* _sfons_malloc(const sfons_allocator_t* allocator, size_t size) { + SOKOL_ASSERT(allocator && (size > 0)); + void* ptr; + if (allocator->alloc_fn) { + ptr = allocator->alloc_fn(size, allocator->user_data); + } else { + ptr = malloc(size); + } + SOKOL_ASSERT(ptr); + return ptr; +} + +static void* _sfons_malloc_clear(const sfons_allocator_t* allocator, size_t size) { + void* ptr = _sfons_malloc(allocator, size); + _sfons_clear(ptr, size); + return ptr; +} + +static void _sfons_free(const sfons_allocator_t* allocator, void* ptr) { + SOKOL_ASSERT(allocator); + if (allocator->free_fn) { + allocator->free_fn(ptr, allocator->user_data); + } else { + free(ptr); + } +} + +static int _sfons_render_create(void* user_ptr, int width, int height) { + SOKOL_ASSERT(user_ptr && (width > 8) && (height > 8)); + _sfons_t* sfons = (_sfons_t*) user_ptr; + + // sokol-gl compatible shader which treats RED channel as alpha + if (sfons->shd.id == SG_INVALID_ID) { + sg_shader_desc shd_desc; + _sfons_clear(&shd_desc, sizeof(shd_desc)); + shd_desc.attrs[0].name = "position"; + shd_desc.attrs[1].name = "texcoord0"; + shd_desc.attrs[2].name = "color0"; + shd_desc.attrs[3].name = "psize"; + shd_desc.attrs[0].sem_name = "TEXCOORD"; + shd_desc.attrs[0].sem_index = 0; + shd_desc.attrs[1].sem_name = "TEXCOORD"; + shd_desc.attrs[1].sem_index = 1; + shd_desc.attrs[2].sem_name = "TEXCOORD"; + shd_desc.attrs[2].sem_index = 2; + shd_desc.attrs[3].sem_name = "TEXCOORD"; + shd_desc.attrs[3].sem_index = 3; + sg_shader_uniform_block_desc* ub = &shd_desc.vs.uniform_blocks[0]; + ub->size = 128; + ub->uniforms[0].name = "vs_params"; + ub->uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; + ub->uniforms[0].array_count = 8; + shd_desc.fs.images[0].used = true; + shd_desc.fs.images[0].image_type = SG_IMAGETYPE_2D; + shd_desc.fs.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + shd_desc.fs.samplers[0].used = true; + shd_desc.fs.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + shd_desc.fs.image_sampler_pairs[0].used = true; + shd_desc.fs.image_sampler_pairs[0].glsl_name = "tex_smp"; + shd_desc.fs.image_sampler_pairs[0].image_slot = 0; + shd_desc.fs.image_sampler_pairs[0].sampler_slot = 0; + shd_desc.label = "sokol-fontstash-shader"; + #if defined(SOKOL_GLCORE33) + shd_desc.vs.source = _sfons_vs_source_glsl330; + shd_desc.fs.source = _sfons_fs_source_glsl330; + #elif defined(SOKOL_GLES3) + shd_desc.vs.source = _sfons_vs_source_glsl300es; + shd_desc.fs.source = _sfons_fs_source_glsl300es; + #elif defined(SOKOL_METAL) + shd_desc.vs.entry = "main0"; + shd_desc.fs.entry = "main0"; + switch (sg_query_backend()) { + case SG_BACKEND_METAL_MACOS: + shd_desc.vs.bytecode = SG_RANGE(_sfons_vs_bytecode_metal_macos); + shd_desc.fs.bytecode = SG_RANGE(_sfons_fs_bytecode_metal_macos); + break; + case SG_BACKEND_METAL_IOS: + shd_desc.vs.bytecode = SG_RANGE(_sfons_vs_bytecode_metal_ios); + shd_desc.fs.bytecode = SG_RANGE(_sfons_fs_bytecode_metal_ios); + break; + default: + shd_desc.vs.source = _sfons_vs_source_metal_sim; + shd_desc.fs.source = _sfons_fs_source_metal_sim; + break; + } + #elif defined(SOKOL_D3D11) + shd_desc.vs.bytecode = SG_RANGE(_sfons_vs_bytecode_hlsl4); + shd_desc.fs.bytecode = SG_RANGE(_sfons_fs_bytecode_hlsl4); + #elif defined(SOKOL_WGPU) + shd_desc.vs.source = _sfons_vs_source_wgsl; + shd_desc.fs.source = _sfons_fs_source_wgsl; + #else + shd_desc.vs.source = _sfons_vs_source_dummy; + shd_desc.fs.source = _sfons_fs_source_dummy; + #endif + shd_desc.label = "sfons-shader"; + sfons->shd = sg_make_shader(&shd_desc); + } + + // sokol-gl pipeline object + if (sfons->pip.id == SG_INVALID_ID) { + sg_pipeline_desc pip_desc; + _sfons_clear(&pip_desc, sizeof(pip_desc)); + pip_desc.shader = sfons->shd; + pip_desc.colors[0].blend.enabled = true; + pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA; + pip_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + sfons->pip = sgl_make_pipeline(&pip_desc); + } + + // a sampler object + if (sfons->smp.id == SG_INVALID_ID) { + sg_sampler_desc smp_desc; + _sfons_clear(&smp_desc, sizeof(smp_desc)); + smp_desc.min_filter = SG_FILTER_LINEAR; + smp_desc.mag_filter = SG_FILTER_LINEAR; + smp_desc.mipmap_filter = SG_FILTER_NONE; + sfons->smp = sg_make_sampler(&smp_desc); + } + + // create or re-create font atlas texture + if (sfons->img.id != SG_INVALID_ID) { + sg_destroy_image(sfons->img); + sfons->img.id = SG_INVALID_ID; + } + sfons->cur_width = width; + sfons->cur_height = height; + + SOKOL_ASSERT(sfons->img.id == SG_INVALID_ID); + sg_image_desc img_desc; + _sfons_clear(&img_desc, sizeof(img_desc)); + img_desc.width = sfons->cur_width; + img_desc.height = sfons->cur_height; + img_desc.usage = SG_USAGE_DYNAMIC; + img_desc.pixel_format = SG_PIXELFORMAT_R8; + sfons->img = sg_make_image(&img_desc); + return 1; +} + +static int _sfons_render_resize(void* user_ptr, int width, int height) { + return _sfons_render_create(user_ptr, width, height); +} + +static void _sfons_render_update(void* user_ptr, int* rect, const unsigned char* data) { + SOKOL_ASSERT(user_ptr && rect && data); + _SOKOL_UNUSED(rect); + _SOKOL_UNUSED(data); + _sfons_t* sfons = (_sfons_t*) user_ptr; + sfons->img_dirty = true; +} + +static void _sfons_render_draw(void* user_ptr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts) { + SOKOL_ASSERT(user_ptr && verts && tcoords && colors && (nverts > 0)); + _sfons_t* sfons = (_sfons_t*) user_ptr; + sgl_enable_texture(); + sgl_texture(sfons->img, sfons->smp); + sgl_push_pipeline(); + sgl_load_pipeline(sfons->pip); + sgl_begin_triangles(); + for (int i = 0; i < nverts; i++) { + sgl_v2f_t2f_c1i(verts[2*i+0], verts[2*i+1], tcoords[2*i+0], tcoords[2*i+1], colors[i]); + } + sgl_end(); + sgl_pop_pipeline(); + sgl_disable_texture(); +} + +static void _sfons_render_delete(void* user_ptr) { + SOKOL_ASSERT(user_ptr); + _sfons_t* sfons = (_sfons_t*) user_ptr; + if (sfons->img.id != SG_INVALID_ID) { + sg_destroy_image(sfons->img); + sfons->img.id = SG_INVALID_ID; + } + if (sfons->smp.id != SG_INVALID_ID) { + sg_destroy_sampler(sfons->smp); + sfons->smp.id = SG_INVALID_ID; + } + if (sfons->pip.id != SG_INVALID_ID) { + sgl_destroy_pipeline(sfons->pip); + sfons->pip.id = SG_INVALID_ID; + } + if (sfons->shd.id != SG_INVALID_ID) { + sg_destroy_shader(sfons->shd); + sfons->shd.id = SG_INVALID_ID; + } +} + +#define _sfons_def(val, def) (((val) == 0) ? (def) : (val)) + +static sfons_desc_t _sfons_desc_defaults(const sfons_desc_t* desc) { + SOKOL_ASSERT(desc); + sfons_desc_t res = *desc; + res.width = _sfons_def(res.width, 512); + res.height = _sfons_def(res.height, 512); + return res; +} + +SOKOL_API_IMPL FONScontext* sfons_create(const sfons_desc_t* desc) { + SOKOL_ASSERT(desc); + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + _sfons_t* sfons = (_sfons_t*) _sfons_malloc_clear(&desc->allocator, sizeof(_sfons_t)); + sfons->desc = _sfons_desc_defaults(desc); + FONSparams params; + _sfons_clear(¶ms, sizeof(params)); + params.width = sfons->desc.width; + params.height = sfons->desc.height; + params.flags = FONS_ZERO_TOPLEFT; + params.renderCreate = _sfons_render_create; + params.renderResize = _sfons_render_resize; + params.renderUpdate = _sfons_render_update; + params.renderDraw = _sfons_render_draw; + params.renderDelete = _sfons_render_delete; + params.userPtr = sfons; + return fonsCreateInternal(¶ms); +} + +SOKOL_API_IMPL void sfons_destroy(FONScontext* ctx) { + SOKOL_ASSERT(ctx); + _sfons_t* sfons = (_sfons_t*) ctx->params.userPtr; + fonsDeleteInternal(ctx); + const sfons_allocator_t allocator = sfons->desc.allocator; + _sfons_free(&allocator, sfons); +} + +SOKOL_API_IMPL void sfons_flush(FONScontext* ctx) { + SOKOL_ASSERT(ctx && ctx->params.userPtr); + _sfons_t* sfons = (_sfons_t*) ctx->params.userPtr; + if (sfons->img_dirty) { + sfons->img_dirty = false; + sg_image_data data; + _sfons_clear(&data, sizeof(data)); + data.subimage[0][0].ptr = ctx->texData; + data.subimage[0][0].size = (size_t) (sfons->cur_width * sfons->cur_height); + sg_update_image(sfons->img, &data); + } +} + +SOKOL_API_IMPL uint32_t sfons_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return ((uint32_t)r) | ((uint32_t)g<<8) | ((uint32_t)b<<16) | ((uint32_t)a<<24); +} + +#endif // SOKOL_FONTSTASH_IMPL diff --git a/source/engine/thirdparty/sokol/util/sokol_gfx_imgui.h b/source/engine/thirdparty/sokol/util/sokol_gfx_imgui.h new file mode 100644 index 0000000..eed45e3 --- /dev/null +++ b/source/engine/thirdparty/sokol/util/sokol_gfx_imgui.h @@ -0,0 +1,4720 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_GFX_IMGUI_IMPL) +#define SOKOL_GFX_IMGUI_IMPL +#endif +#ifndef SOKOL_GFX_IMGUI_INCLUDED +/* + sokol_gfx_imgui.h -- debug-inspection UI for sokol_gfx.h using Dear ImGui + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_GFX_IMGUI_IMPL + + before you include this file in *one* C or C++ file to create the + implementation. + + NOTE that the implementation can be compiled either as C++ or as C. + When compiled as C++, sokol_gfx_imgui.h will directly call into the + Dear ImGui C++ API. When compiled as C, sokol_gfx_imgui.h will call + cimgui.h functions instead. + + Include the following file(s) before including sokol_gfx_imgui.h: + + sokol_gfx.h + + Additionally, include the following headers before including the + implementation: + + If the implementation is compiled as C++: + imgui.h + + If the implementation is compiled as C: + cimgui.h + + The sokol_gfx.h implementation must be compiled with debug trace hooks + enabled by defining: + + SOKOL_TRACE_HOOKS + + ...before including the sokol_gfx.h implementation. + + Before including the sokol_gfx_imgui.h implementation, optionally + override the following macros: + + SOKOL_ASSERT(c) -- your own assert macro, default: assert(c) + SOKOL_UNREACHABLE -- your own macro to annotate unreachable code, + default: SOKOL_ASSERT(false) + SOKOL_GFX_IMGUI_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_GFX_IMGUI_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_gfx_imgui.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_IMGUI_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + STEP BY STEP: + ============= + --- create an sgimgui_t struct (which must be preserved between frames) + and initialize it with: + + sgimgui_init(&sgimgui, &(sgimgui_desc_t){ 0 }); + + Note that from C++ you can't inline the desc structure initialization: + + const sgimgui_desc_t desc = { }; + sgimgui_init(&sgimgui, &desc); + + Provide optional memory allocator override functions (compatible with malloc/free) like this: + + sgimgui_init(&sgimgui, &(sgimgui_desc_t){ + .allocator = { + .alloc_fn = my_malloc, + .free_fn = my_free, + } + }); + + --- somewhere in the per-frame code call: + + sgimgui_draw(&sgimgui) + + this won't draw anything yet, since no windows are open. + + --- call the convenience function sgimgui_draw_menu(ctx, title) + to render a menu which allows to open/close the provided debug windows + + sgimgui_draw_menu(&sgimgui, "sokol-gfx"); + + --- alternative, open and close windows directly by setting the following public + booleans in the sgimgui_t struct: + + sgimgui.caps_window.open = true; + sgimgui.frame_stats_window.open = true; + sgimgui.buffer_window.open = true; + sgimgui.image_window.open = true; + sgimgui.sampler_window.open = true; + sgimgui.shader_window.open = true; + sgimgui.pipeline_window.open = true; + sgimgui.attachments_window.open = true; + sgimgui.capture_window.open = true; + sgimgui.frame_stats_window.open = true; + + ...for instance, to control the window visibility through + menu items, the following code can be used: + + if (ImGui::BeginMainMenuBar()) { + if (ImGui::BeginMenu("sokol-gfx")) { + ImGui::MenuItem("Capabilities", 0, &sgimgui.caps_window.open); + ImGui::MenuItem("Frame Stats", 0, &sgimgui.frame_stats_window.open); + ImGui::MenuItem("Buffers", 0, &sgimgui.buffer_window.open); + ImGui::MenuItem("Images", 0, &sgimgui.image_window.open); + ImGui::MenuItem("Samplers", 0, &sgimgui.sampler_window.open); + ImGui::MenuItem("Shaders", 0, &sgimgui.shader_window.open); + ImGui::MenuItem("Pipelines", 0, &sgimgui.pipeline_window.open); + ImGui::MenuItem("Attachments", 0, &sgimgui.attachments_window.open); + ImGui::MenuItem("Calls", 0, &sgimgui.capture_window.open); + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } + + --- before application shutdown, call: + + sgimgui_discard(&sgimgui); + + ...this is not strictly necessary because the application exits + anyway, but not doing this may trigger memory leak detection tools. + + --- finally, your application needs an ImGui renderer, you can either + provide your own, or drop in the sokol_imgui.h utility header + + ALTERNATIVE DRAWING FUNCTIONS: + ============================== + Instead of the convenient, but all-in-one sgimgui_draw() function, + you can also use the following granular functions which might allow + better integration with your existing UI: + + The following functions only render the window *content* (so you + can integrate the UI into you own windows): + + void sgimgui_draw_buffer_window_content(sgimgui_t* ctx); + void sgimgui_draw_image_window_content(sgimgui_t* ctx); + void sgimgui_draw_sampler_window_content(sgimgui_t* ctx); + void sgimgui_draw_shader_window_content(sgimgui_t* ctx); + void sgimgui_draw_pipeline_window_content(sgimgui_t* ctx); + void sgimgui_draw_attachments_window_content(sgimgui_t* ctx); + void sgimgui_draw_capture_window_content(sgimgui_t* ctx); + + And these are the 'full window' drawing functions: + + void sgimgui_draw_buffer_window(sgimgui_t* ctx); + void sgimgui_draw_image_window(sgimgui_t* ctx); + void sgimgui_draw_sampler_window(sgimgui_t* ctx); + void sgimgui_draw_shader_window(sgimgui_t* ctx); + void sgimgui_draw_pipeline_window(sgimgui_t* ctx); + void sgimgui_draw_attachments_window(sgimgui_t* ctx); + void sgimgui_draw_capture_window(sgimgui_t* ctx); + + Finer-grained drawing functions may be moved to the public API + in the future as needed. + + 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); + } + + ... + sgimgui_init(&(&ctx, &(sgimgui_desc_t){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ...; + } + }); + ... + + This only affects memory allocation calls done by sokol_gfx_imgui.h + itself though, not any allocations in OS libraries. + + + 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_IMGUI_INCLUDED (1) +#include +#include +#include // size_t + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_gfx_imgui.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_GFX_IMGUI_API_DECL) +#define SOKOL_GFX_IMGUI_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_GFX_IMGUI_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_GFX_IMGUI_IMPL) +#define SOKOL_GFX_IMGUI_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_GFX_IMGUI_API_DECL __declspec(dllimport) +#else +#define SOKOL_GFX_IMGUI_API_DECL extern +#endif +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +#define sgimgui_STRBUF_LEN (96) +/* max number of captured calls per frame */ +#define sgimgui_MAX_FRAMECAPTURE_ITEMS (4096) + +typedef struct sgimgui_str_t { + char buf[sgimgui_STRBUF_LEN]; +} sgimgui_str_t; + +typedef struct sgimgui_buffer_t { + sg_buffer res_id; + sgimgui_str_t label; + sg_buffer_desc desc; +} sgimgui_buffer_t; + +typedef struct sgimgui_image_t { + sg_image res_id; + float ui_scale; + sgimgui_str_t label; + sg_image_desc desc; + simgui_image_t simgui_img; +} sgimgui_image_t; + +typedef struct sgimgui_sampler_t { + sg_sampler res_id; + sgimgui_str_t label; + sg_sampler_desc desc; +} sgimgui_sampler_t; + +typedef struct sgimgui_shader_t { + sg_shader res_id; + sgimgui_str_t label; + sgimgui_str_t vs_entry; + sgimgui_str_t vs_d3d11_target; + sgimgui_str_t vs_image_sampler_name[SG_MAX_SHADERSTAGE_IMAGESAMPLERPAIRS]; + sgimgui_str_t vs_uniform_name[SG_MAX_SHADERSTAGE_UBS][SG_MAX_UB_MEMBERS]; + sgimgui_str_t fs_entry; + sgimgui_str_t fs_d3d11_target; + sgimgui_str_t fs_image_sampler_name[SG_MAX_SHADERSTAGE_IMAGESAMPLERPAIRS]; + sgimgui_str_t fs_uniform_name[SG_MAX_SHADERSTAGE_UBS][SG_MAX_UB_MEMBERS]; + sgimgui_str_t attr_name[SG_MAX_VERTEX_ATTRIBUTES]; + sgimgui_str_t attr_sem_name[SG_MAX_VERTEX_ATTRIBUTES]; + sg_shader_desc desc; +} sgimgui_shader_t; + +typedef struct sgimgui_pipeline_t { + sg_pipeline res_id; + sgimgui_str_t label; + sg_pipeline_desc desc; +} sgimgui_pipeline_t; + +typedef struct sgimgui_attachments_t { + sg_attachments res_id; + sgimgui_str_t label; + float color_image_scale[SG_MAX_COLOR_ATTACHMENTS]; + float resolve_image_scale[SG_MAX_COLOR_ATTACHMENTS]; + float ds_image_scale; + sg_attachments_desc desc; +} sgimgui_attachments_t; + +typedef struct sgimgui_buffer_window_t { + bool open; + int num_slots; + sg_buffer sel_buf; + sgimgui_buffer_t* slots; +} sgimgui_buffer_window_t; + +typedef struct sgimgui_image_window_t { + bool open; + int num_slots; + sg_image sel_img; + sgimgui_image_t* slots; +} sgimgui_image_window_t; + +typedef struct sgimgui_sampler_window_t { + bool open; + int num_slots; + sg_sampler sel_smp; + sgimgui_sampler_t* slots; +} sgimgui_sampler_window_t; + +typedef struct sgimgui_shader_window_t { + bool open; + int num_slots; + sg_shader sel_shd; + sgimgui_shader_t* slots; +} sgimgui_shader_window_t; + +typedef struct sgimgui_pipeline_window_t { + bool open; + int num_slots; + sg_pipeline sel_pip; + sgimgui_pipeline_t* slots; +} sgimgui_pipeline_window_t; + +typedef struct sgimgui_attachments_window_t { + bool open; + int num_slots; + sg_attachments sel_atts; + sgimgui_attachments_t* slots; +} sgimgui_attachments_window_t; + +typedef enum sgimgui_cmd_t { + SGIMGUI_CMD_INVALID, + SGIMGUI_CMD_RESET_STATE_CACHE, + SGIMGUI_CMD_MAKE_BUFFER, + SGIMGUI_CMD_MAKE_IMAGE, + SGIMGUI_CMD_MAKE_SAMPLER, + SGIMGUI_CMD_MAKE_SHADER, + SGIMGUI_CMD_MAKE_PIPELINE, + SGIMGUI_CMD_MAKE_ATTACHMENTS, + SGIMGUI_CMD_DESTROY_BUFFER, + SGIMGUI_CMD_DESTROY_IMAGE, + SGIMGUI_CMD_DESTROY_SAMPLER, + SGIMGUI_CMD_DESTROY_SHADER, + SGIMGUI_CMD_DESTROY_PIPELINE, + SGIMGUI_CMD_DESTROY_ATTACHMENTS, + SGIMGUI_CMD_UPDATE_BUFFER, + SGIMGUI_CMD_UPDATE_IMAGE, + SGIMGUI_CMD_APPEND_BUFFER, + SGIMGUI_CMD_BEGIN_PASS, + SGIMGUI_CMD_APPLY_VIEWPORT, + SGIMGUI_CMD_APPLY_SCISSOR_RECT, + SGIMGUI_CMD_APPLY_PIPELINE, + SGIMGUI_CMD_APPLY_BINDINGS, + SGIMGUI_CMD_APPLY_UNIFORMS, + SGIMGUI_CMD_DRAW, + SGIMGUI_CMD_END_PASS, + SGIMGUI_CMD_COMMIT, + SGIMGUI_CMD_ALLOC_BUFFER, + SGIMGUI_CMD_ALLOC_IMAGE, + SGIMGUI_CMD_ALLOC_SAMPLER, + SGIMGUI_CMD_ALLOC_SHADER, + SGIMGUI_CMD_ALLOC_PIPELINE, + SGIMGUI_CMD_ALLOC_ATTACHMENTS, + SGIMGUI_CMD_DEALLOC_BUFFER, + SGIMGUI_CMD_DEALLOC_IMAGE, + SGIMGUI_CMD_DEALLOC_SAMPLER, + SGIMGUI_CMD_DEALLOC_SHADER, + SGIMGUI_CMD_DEALLOC_PIPELINE, + SGIMGUI_CMD_DEALLOC_ATTACHMENTS, + SGIMGUI_CMD_INIT_BUFFER, + SGIMGUI_CMD_INIT_IMAGE, + SGIMGUI_CMD_INIT_SAMPLER, + SGIMGUI_CMD_INIT_SHADER, + SGIMGUI_CMD_INIT_PIPELINE, + SGIMGUI_CMD_INIT_ATTACHMENTS, + SGIMGUI_CMD_UNINIT_BUFFER, + SGIMGUI_CMD_UNINIT_IMAGE, + SGIMGUI_CMD_UNINIT_SAMPLER, + SGIMGUI_CMD_UNINIT_SHADER, + SGIMGUI_CMD_UNINIT_PIPELINE, + SGIMGUI_CMD_UNINIT_ATTACHMENTS, + SGIMGUI_CMD_FAIL_BUFFER, + SGIMGUI_CMD_FAIL_IMAGE, + SGIMGUI_CMD_FAIL_SAMPLER, + SGIMGUI_CMD_FAIL_SHADER, + SGIMGUI_CMD_FAIL_PIPELINE, + SGIMGUI_CMD_FAIL_ATTACHMENTS, + SGIMGUI_CMD_PUSH_DEBUG_GROUP, + SGIMGUI_CMD_POP_DEBUG_GROUP, +} sgimgui_cmd_t; + +typedef struct sgimgui_args_make_buffer_t { + sg_buffer result; +} sgimgui_args_make_buffer_t; + +typedef struct sgimgui_args_make_image_t { + sg_image result; +} sgimgui_args_make_image_t; + +typedef struct sgimgui_args_make_sampler_t { + sg_sampler result; +} sgimgui_args_make_sampler_t; + +typedef struct sgimgui_args_make_shader_t { + sg_shader result; +} sgimgui_args_make_shader_t; + +typedef struct sgimgui_args_make_pipeline_t { + sg_pipeline result; +} sgimgui_args_make_pipeline_t; + +typedef struct sgimgui_args_make_attachments_t { + sg_attachments result; +} sgimgui_args_make_attachments_t; + +typedef struct sgimgui_args_destroy_buffer_t { + sg_buffer buffer; +} sgimgui_args_destroy_buffer_t; + +typedef struct sgimgui_args_destroy_image_t { + sg_image image; +} sgimgui_args_destroy_image_t; + +typedef struct sgimgui_args_destroy_sampler_t { + sg_sampler sampler; +} sgimgui_args_destroy_sampler_t; + +typedef struct sgimgui_args_destroy_shader_t { + sg_shader shader; +} sgimgui_args_destroy_shader_t; + +typedef struct sgimgui_args_destroy_pipeline_t { + sg_pipeline pipeline; +} sgimgui_args_destroy_pipeline_t; + +typedef struct sgimgui_args_destroy_attachments_t { + sg_attachments attachments; +} sgimgui_args_destroy_attachments_t; + +typedef struct sgimgui_args_update_buffer_t { + sg_buffer buffer; + size_t data_size; +} sgimgui_args_update_buffer_t; + +typedef struct sgimgui_args_update_image_t { + sg_image image; +} sgimgui_args_update_image_t; + +typedef struct sgimgui_args_append_buffer_t { + sg_buffer buffer; + size_t data_size; + int result; +} sgimgui_args_append_buffer_t; + +typedef struct sgimgui_args_begin_pass_t { + sg_pass pass; +} sgimgui_args_begin_pass_t; + +typedef struct sgimgui_args_apply_viewport_t { + int x, y, width, height; + bool origin_top_left; +} sgimgui_args_apply_viewport_t; + +typedef struct sgimgui_args_apply_scissor_rect_t { + int x, y, width, height; + bool origin_top_left; +} sgimgui_args_apply_scissor_rect_t; + +typedef struct sgimgui_args_apply_pipeline_t { + sg_pipeline pipeline; +} sgimgui_args_apply_pipeline_t; + +typedef struct sgimgui_args_apply_bindings_t { + sg_bindings bindings; +} sgimgui_args_apply_bindings_t; + +typedef struct sgimgui_args_apply_uniforms_t { + sg_shader_stage stage; + int ub_index; + size_t data_size; + sg_pipeline pipeline; /* the pipeline which was active at this call */ + size_t ubuf_pos; /* start of copied data in capture buffer */ +} sgimgui_args_apply_uniforms_t; + +typedef struct sgimgui_args_draw_t { + int base_element; + int num_elements; + int num_instances; +} sgimgui_args_draw_t; + +typedef struct sgimgui_args_alloc_buffer_t { + sg_buffer result; +} sgimgui_args_alloc_buffer_t; + +typedef struct sgimgui_args_alloc_image_t { + sg_image result; +} sgimgui_args_alloc_image_t; + +typedef struct sgimgui_args_alloc_sampler_t { + sg_sampler result; +} sgimgui_args_alloc_sampler_t; + +typedef struct sgimgui_args_alloc_shader_t { + sg_shader result; +} sgimgui_args_alloc_shader_t; + +typedef struct sgimgui_args_alloc_pipeline_t { + sg_pipeline result; +} sgimgui_args_alloc_pipeline_t; + +typedef struct sgimgui_args_alloc_attachments_t { + sg_attachments result; +} sgimgui_args_alloc_attachments_t; + +typedef struct sgimgui_args_dealloc_buffer_t { + sg_buffer buffer; +} sgimgui_args_dealloc_buffer_t; + +typedef struct sgimgui_args_dealloc_image_t { + sg_image image; +} sgimgui_args_dealloc_image_t; + +typedef struct sgimgui_args_dealloc_sampler_t { + sg_sampler sampler; +} sgimgui_args_dealloc_sampler_t; + +typedef struct sgimgui_args_dealloc_shader_t { + sg_shader shader; +} sgimgui_args_dealloc_shader_t; + +typedef struct sgimgui_args_dealloc_pipeline_t { + sg_pipeline pipeline; +} sgimgui_args_dealloc_pipeline_t; + +typedef struct sgimgui_args_dealloc_attachments_t { + sg_attachments attachments; +} sgimgui_args_dealloc_attachments_t; + +typedef struct sgimgui_args_init_buffer_t { + sg_buffer buffer; +} sgimgui_args_init_buffer_t; + +typedef struct sgimgui_args_init_image_t { + sg_image image; +} sgimgui_args_init_image_t; + +typedef struct sgimgui_args_init_sampler_t { + sg_sampler sampler; +} sgimgui_args_init_sampler_t; + +typedef struct sgimgui_args_init_shader_t { + sg_shader shader; +} sgimgui_args_init_shader_t; + +typedef struct sgimgui_args_init_pipeline_t { + sg_pipeline pipeline; +} sgimgui_args_init_pipeline_t; + +typedef struct sgimgui_args_init_attachments_t { + sg_attachments attachments; +} sgimgui_args_init_attachments_t; + +typedef struct sgimgui_args_uninit_buffer_t { + sg_buffer buffer; +} sgimgui_args_uninit_buffer_t; + +typedef struct sgimgui_args_uninit_image_t { + sg_image image; +} sgimgui_args_uninit_image_t; + +typedef struct sgimgui_args_uninit_sampler_t { + sg_sampler sampler; +} sgimgui_args_uninit_sampler_t; + +typedef struct sgimgui_args_uninit_shader_t { + sg_shader shader; +} sgimgui_args_uninit_shader_t; + +typedef struct sgimgui_args_uninit_pipeline_t { + sg_pipeline pipeline; +} sgimgui_args_uninit_pipeline_t; + +typedef struct sgimgui_args_uninit_attachments_t { + sg_attachments attachments; +} sgimgui_args_uninit_attachments_t; + +typedef struct sgimgui_args_fail_buffer_t { + sg_buffer buffer; +} sgimgui_args_fail_buffer_t; + +typedef struct sgimgui_args_fail_image_t { + sg_image image; +} sgimgui_args_fail_image_t; + +typedef struct sgimgui_args_fail_sampler_t { + sg_sampler sampler; +} sgimgui_args_fail_sampler_t; + +typedef struct sgimgui_args_fail_shader_t { + sg_shader shader; +} sgimgui_args_fail_shader_t; + +typedef struct sgimgui_args_fail_pipeline_t { + sg_pipeline pipeline; +} sgimgui_args_fail_pipeline_t; + +typedef struct sgimgui_args_fail_attachments_t { + sg_attachments attachments; +} sgimgui_args_fail_attachments_t; + +typedef struct sgimgui_args_push_debug_group_t { + sgimgui_str_t name; +} sgimgui_args_push_debug_group_t; + +typedef union sgimgui_args_t { + sgimgui_args_make_buffer_t make_buffer; + sgimgui_args_make_image_t make_image; + sgimgui_args_make_sampler_t make_sampler; + sgimgui_args_make_shader_t make_shader; + sgimgui_args_make_pipeline_t make_pipeline; + sgimgui_args_make_attachments_t make_attachments; + sgimgui_args_destroy_buffer_t destroy_buffer; + sgimgui_args_destroy_image_t destroy_image; + sgimgui_args_destroy_sampler_t destroy_sampler; + sgimgui_args_destroy_shader_t destroy_shader; + sgimgui_args_destroy_pipeline_t destroy_pipeline; + sgimgui_args_destroy_attachments_t destroy_attachments; + sgimgui_args_update_buffer_t update_buffer; + sgimgui_args_update_image_t update_image; + sgimgui_args_append_buffer_t append_buffer; + sgimgui_args_begin_pass_t begin_pass; + sgimgui_args_apply_viewport_t apply_viewport; + sgimgui_args_apply_scissor_rect_t apply_scissor_rect; + sgimgui_args_apply_pipeline_t apply_pipeline; + sgimgui_args_apply_bindings_t apply_bindings; + sgimgui_args_apply_uniforms_t apply_uniforms; + sgimgui_args_draw_t draw; + sgimgui_args_alloc_buffer_t alloc_buffer; + sgimgui_args_alloc_image_t alloc_image; + sgimgui_args_alloc_sampler_t alloc_sampler; + sgimgui_args_alloc_shader_t alloc_shader; + sgimgui_args_alloc_pipeline_t alloc_pipeline; + sgimgui_args_alloc_attachments_t alloc_attachments; + sgimgui_args_dealloc_buffer_t dealloc_buffer; + sgimgui_args_dealloc_image_t dealloc_image; + sgimgui_args_dealloc_sampler_t dealloc_sampler; + sgimgui_args_dealloc_shader_t dealloc_shader; + sgimgui_args_dealloc_pipeline_t dealloc_pipeline; + sgimgui_args_dealloc_attachments_t dealloc_attachments; + sgimgui_args_init_buffer_t init_buffer; + sgimgui_args_init_image_t init_image; + sgimgui_args_init_sampler_t init_sampler; + sgimgui_args_init_shader_t init_shader; + sgimgui_args_init_pipeline_t init_pipeline; + sgimgui_args_init_attachments_t init_attachments; + sgimgui_args_uninit_buffer_t uninit_buffer; + sgimgui_args_uninit_image_t uninit_image; + sgimgui_args_uninit_sampler_t uninit_sampler; + sgimgui_args_uninit_shader_t uninit_shader; + sgimgui_args_uninit_pipeline_t uninit_pipeline; + sgimgui_args_uninit_attachments_t uninit_attachments; + sgimgui_args_fail_buffer_t fail_buffer; + sgimgui_args_fail_image_t fail_image; + sgimgui_args_fail_sampler_t fail_sampler; + sgimgui_args_fail_shader_t fail_shader; + sgimgui_args_fail_pipeline_t fail_pipeline; + sgimgui_args_fail_attachments_t fail_attachments; + sgimgui_args_push_debug_group_t push_debug_group; +} sgimgui_args_t; + +typedef struct sgimgui_capture_item_t { + sgimgui_cmd_t cmd; + uint32_t color; + sgimgui_args_t args; +} sgimgui_capture_item_t; + +typedef struct sgimgui_capture_bucket_t { + size_t ubuf_size; /* size of uniform capture buffer in bytes */ + size_t ubuf_pos; /* current uniform buffer pos */ + uint8_t* ubuf; /* buffer for capturing uniform updates */ + int num_items; + sgimgui_capture_item_t items[sgimgui_MAX_FRAMECAPTURE_ITEMS]; +} sgimgui_capture_bucket_t; + +/* double-buffered call-capture buckets, one bucket is currently recorded, + the previous bucket is displayed +*/ +typedef struct sgimgui_capture_window_t { + bool open; + int bucket_index; /* which bucket to record to, 0 or 1 */ + int sel_item; /* currently selected capture item by index */ + sgimgui_capture_bucket_t bucket[2]; +} sgimgui_capture_window_t; + +typedef struct sgimgui_caps_window_t { + bool open; +} sgimgui_caps_window_t; + +typedef struct sgimgui_frame_stats_window_t { + bool open; + bool disable_sokol_imgui_stats; + bool in_sokol_imgui; + sg_frame_stats stats; + // FIXME: add a ringbuffer for a stats history here +} sgimgui_frame_stats_window_t; + +/* + sgimgui_allocator_t + + Used in sgimgui_desc_t to provide custom memory-alloc and -free functions + to sokol_gfx_imgui.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 sgimgui_allocator_t { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sgimgui_allocator_t; + +/* + sgimgui_desc_t + + Initialization options for sgimgui_init(). +*/ +typedef struct sgimgui_desc_t { + sgimgui_allocator_t allocator; // optional memory allocation overrides (default: malloc/free) +} sgimgui_desc_t; + +typedef struct sgimgui_t { + uint32_t init_tag; + sgimgui_desc_t desc; + sgimgui_buffer_window_t buffer_window; + sgimgui_image_window_t image_window; + sgimgui_sampler_window_t sampler_window; + sgimgui_shader_window_t shader_window; + sgimgui_pipeline_window_t pipeline_window; + sgimgui_attachments_window_t attachments_window; + sgimgui_capture_window_t capture_window; + sgimgui_caps_window_t caps_window; + sgimgui_frame_stats_window_t frame_stats_window; + sg_pipeline cur_pipeline; + sg_trace_hooks hooks; +} sgimgui_t; + +SOKOL_GFX_IMGUI_API_DECL void sgimgui_init(sgimgui_t* ctx, const sgimgui_desc_t* desc); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_discard(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw(sgimgui_t* ctx); + +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_menu(sgimgui_t* ctx, const char* title); + +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_buffer_window_content(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_image_window_content(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_sampler_window_content(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_shader_window_content(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_pipeline_window_content(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_attachments_window_content(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_capture_window_content(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_capabilities_window_content(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_frame_stats_window_content(sgimgui_t* ctx); + +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_buffer_window(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_image_window(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_sampler_window(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_shader_window(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_pipeline_window(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_attachments_window(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_capture_window(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_capabilities_window(sgimgui_t* ctx); +SOKOL_GFX_IMGUI_API_DECL void sgimgui_draw_frame_stats_window(sgimgui_t* ctx); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif +#endif /* SOKOL_GFX_IMGUI_INCLUDED */ + +/*=== IMPLEMENTATION =========================================================*/ +#ifdef SOKOL_GFX_IMGUI_IMPL +#define SOKOL_GFX_IMGUI_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 sgimgui_desc_t.allocator to override memory allocation functions" +#endif + +#if defined(__cplusplus) + #if !defined(IMGUI_VERSION) + #error "Please include imgui.h before the sokol_imgui.h implementation" + #endif +#else + #if !defined(CIMGUI_INCLUDED) + #error "Please include cimgui.h before the sokol_imgui.h implementation" + #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 +#ifndef SOKOL_API_IMPL +#define SOKOL_API_IMPL +#endif + +#include +#include // snprintf +#include // malloc, free + +#define _SGIMGUI_SLOT_MASK (0xFFFF) +#define _SGIMGUI_LIST_WIDTH (192) +#define _SGIMGUI_COLOR_OTHER 0xFFCCCCCC +#define _SGIMGUI_COLOR_RSRC 0xFF00FFFF +#define _SGIMGUI_COLOR_PASS 0xFFFFFF00 +#define _SGIMGUI_COLOR_APPLY 0xFFCCCC00 +#define _SGIMGUI_COLOR_DRAW 0xFF00FF00 +#define _SGIMGUI_COLOR_ERR 0xFF8888FF + +/*--- C => C++ layer ---------------------------------------------------------*/ +#if defined(__cplusplus) +#define IMVEC2(x,y) ImVec2(x,y) +#define IMVEC4(x,y,z,w) ImVec4(x,y,z,w) +_SOKOL_PRIVATE void igText(const char* fmt,...) { + va_list args; + va_start(args, fmt); + ImGui::TextV(fmt, args); + va_end(args); +} +_SOKOL_PRIVATE void igSeparator() { + return ImGui::Separator(); +} +_SOKOL_PRIVATE void igSameLine(float offset_from_start_x, float spacing) { + return ImGui::SameLine(offset_from_start_x,spacing); +} +_SOKOL_PRIVATE void igPushID_Int(int int_id) { + return ImGui::PushID(int_id); +} +_SOKOL_PRIVATE void igPopID() { + return ImGui::PopID(); +} +_SOKOL_PRIVATE bool igSelectable_Bool(const char* label,bool selected,ImGuiSelectableFlags flags,const ImVec2 size) { + return ImGui::Selectable(label,selected,flags,size); +} +_SOKOL_PRIVATE bool igSmallButton(const char* label) { + return ImGui::SmallButton(label); +} +_SOKOL_PRIVATE bool igBeginChild_Str(const char* str_id,const ImVec2 size,bool border,ImGuiWindowFlags flags) { + return ImGui::BeginChild(str_id,size,border,flags); +} +_SOKOL_PRIVATE void igEndChild() { + return ImGui::EndChild(); +} +_SOKOL_PRIVATE void igPushStyleColor_U32(ImGuiCol idx, ImU32 col) { + return ImGui::PushStyleColor(idx,col); +} +_SOKOL_PRIVATE void igPopStyleColor(int count) { + return ImGui::PopStyleColor(count); +} +_SOKOL_PRIVATE bool igTreeNode_StrStr(const char* str_id,const char* fmt,...) { + va_list args; + va_start(args, fmt); + bool ret = ImGui::TreeNodeV(str_id,fmt,args); + va_end(args); + return ret; +} +_SOKOL_PRIVATE bool igTreeNode_Str(const char* label) { + return ImGui::TreeNode(label); +} +_SOKOL_PRIVATE void igTreePop() { + return ImGui::TreePop(); +} +_SOKOL_PRIVATE bool igIsItemHovered(ImGuiHoveredFlags flags) { + return ImGui::IsItemHovered(flags); +} +_SOKOL_PRIVATE void igSetTooltip(const char* fmt,...) { + va_list args; + va_start(args, fmt); + ImGui::SetTooltipV(fmt,args); + va_end(args); +} +_SOKOL_PRIVATE bool igSliderFloat(const char* label,float* v,float v_min,float v_max,const char* format,ImGuiSliderFlags flags) { + return ImGui::SliderFloat(label,v,v_min,v_max,format,flags); +} +_SOKOL_PRIVATE void igImage(ImTextureID user_texture_id,const ImVec2 size,const ImVec2 uv0,const ImVec2 uv1,const ImVec4 tint_col,const ImVec4 border_col) { + return ImGui::Image(user_texture_id,size,uv0,uv1,tint_col,border_col); +} +_SOKOL_PRIVATE void igSetNextWindowSize(const ImVec2 size,ImGuiCond cond) { + return ImGui::SetNextWindowSize(size,cond); +} +_SOKOL_PRIVATE bool igBegin(const char* name,bool* p_open,ImGuiWindowFlags flags) { + return ImGui::Begin(name,p_open,flags); +} +_SOKOL_PRIVATE void igEnd() { + return ImGui::End(); +} +_SOKOL_PRIVATE bool igBeginMenu(const char* label, bool enabled) { + return ImGui::BeginMenu(label, enabled); +} +_SOKOL_PRIVATE void igEndMenu(void) { + ImGui::EndMenu(); +} +_SOKOL_PRIVATE bool igMenuItem_BoolPtr(const char* label, const char* shortcut, bool* p_selected, bool enabled) { + return ImGui::MenuItem(label, shortcut, p_selected, enabled); +} +_SOKOL_PRIVATE bool igBeginTable(const char* str_id, int column, ImGuiTableFlags flags, const ImVec2 outer_size, float inner_width) { + return ImGui::BeginTable(str_id, column, flags, outer_size, inner_width); +} +_SOKOL_PRIVATE void igEndTable(void) { + ImGui::EndTable(); +} +_SOKOL_PRIVATE void igTableSetupScrollFreeze(int cols, int rows) { + ImGui::TableSetupScrollFreeze(cols, rows); +} +_SOKOL_PRIVATE void igTableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id) { + ImGui::TableSetupColumn(label, flags, init_width_or_weight, user_id); +} +_SOKOL_PRIVATE void igTableHeadersRow(void) { + ImGui::TableHeadersRow(); +} +_SOKOL_PRIVATE void igTableNextRow(ImGuiTableRowFlags row_flags, float min_row_height) { + ImGui::TableNextRow(row_flags, min_row_height); +} +_SOKOL_PRIVATE bool igTableSetColumnIndex(int column_n) { + return ImGui::TableSetColumnIndex(column_n); +} +_SOKOL_PRIVATE bool igCheckbox(const char* label, bool* v) { + return ImGui::Checkbox(label, v); +} +#else +#define IMVEC2(x,y) (ImVec2){x,y} +#define IMVEC4(x,y,z,w) (ImVec4){x,y,z,w} +#endif + +/*--- UTILS ------------------------------------------------------------------*/ +_SOKOL_PRIVATE void _sgimgui_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +_SOKOL_PRIVATE void* _sgimgui_malloc(const sgimgui_allocator_t* allocator, size_t size) { + SOKOL_ASSERT(allocator && (size > 0)); + void* ptr; + if (allocator->alloc_fn) { + ptr = allocator->alloc_fn(size, allocator->user_data); + } else { + ptr = malloc(size); + } + SOKOL_ASSERT(ptr); + return ptr; +} + +_SOKOL_PRIVATE void* _sgimgui_malloc_clear(const sgimgui_allocator_t* allocator, size_t size) { + void* ptr = _sgimgui_malloc(allocator, size); + _sgimgui_clear(ptr, size); + return ptr; +} + +_SOKOL_PRIVATE void _sgimgui_free(const sgimgui_allocator_t* allocator, void* ptr) { + SOKOL_ASSERT(allocator); + if (allocator->free_fn) { + allocator->free_fn(ptr, allocator->user_data); + } else { + free(ptr); + } +} + + _SOKOL_PRIVATE void* _sgimgui_realloc(const sgimgui_allocator_t* allocator, void* old_ptr, size_t old_size, size_t new_size) { + SOKOL_ASSERT(allocator && (new_size > 0) && (new_size > old_size)); + void* new_ptr = _sgimgui_malloc(allocator, new_size); + if (old_ptr) { + if (old_size > 0) { + memcpy(new_ptr, old_ptr, old_size); + } + _sgimgui_free(allocator, old_ptr); + } + return new_ptr; +} + +_SOKOL_PRIVATE int _sgimgui_slot_index(uint32_t id) { + int slot_index = (int) (id & _SGIMGUI_SLOT_MASK); + SOKOL_ASSERT(0 != slot_index); + return slot_index; +} + +_SOKOL_PRIVATE uint32_t _sgimgui_align_u32(uint32_t val, uint32_t align) { + SOKOL_ASSERT((align > 0) && ((align & (align - 1)) == 0)); + return (val + (align - 1)) & ~(align - 1); +} + +_SOKOL_PRIVATE uint32_t _sgimgui_std140_uniform_alignment(sg_uniform_type type, int array_count) { + 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 _sgimgui_std140_uniform_size(sg_uniform_type type, int array_count) { + 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 { + 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 void _sgimgui_strcpy(sgimgui_str_t* dst, const char* src) { + SOKOL_ASSERT(dst); + if (src) { + #if defined(_MSC_VER) + strncpy_s(dst->buf, sgimgui_STRBUF_LEN, src, (sgimgui_STRBUF_LEN-1)); + #else + strncpy(dst->buf, src, sgimgui_STRBUF_LEN); + #endif + dst->buf[sgimgui_STRBUF_LEN-1] = 0; + } else { + _sgimgui_clear(dst->buf, sgimgui_STRBUF_LEN); + } +} + +_SOKOL_PRIVATE sgimgui_str_t _sgimgui_make_str(const char* str) { + sgimgui_str_t res; + _sgimgui_strcpy(&res, str); + return res; +} + +_SOKOL_PRIVATE const char* _sgimgui_str_dup(const sgimgui_allocator_t* allocator, const char* src) { + SOKOL_ASSERT(allocator && src); + size_t len = strlen(src) + 1; + char* dst = (char*) _sgimgui_malloc(allocator, len); + memcpy(dst, src, len); + return (const char*) dst; +} + +_SOKOL_PRIVATE const void* _sgimgui_bin_dup(const sgimgui_allocator_t* allocator, const void* src, size_t num_bytes) { + SOKOL_ASSERT(allocator && src && (num_bytes > 0)); + void* dst = _sgimgui_malloc(allocator, num_bytes); + memcpy(dst, src, num_bytes); + return (const void*) dst; +} + +_SOKOL_PRIVATE void _sgimgui_snprintf(sgimgui_str_t* dst, const char* fmt, ...) { + SOKOL_ASSERT(dst); + va_list args; + va_start(args, fmt); + vsnprintf(dst->buf, sizeof(dst->buf), fmt, args); + dst->buf[sizeof(dst->buf)-1] = 0; + va_end(args); +} + +/*--- STRING CONVERSION ------------------------------------------------------*/ +_SOKOL_PRIVATE const char* _sgimgui_resourcestate_string(sg_resource_state s) { + switch (s) { + case SG_RESOURCESTATE_INITIAL: return "SG_RESOURCESTATE_INITIAL"; + case SG_RESOURCESTATE_ALLOC: return "SG_RESOURCESTATE_ALLOC"; + case SG_RESOURCESTATE_VALID: return "SG_RESOURCESTATE_VALID"; + case SG_RESOURCESTATE_FAILED: return "SG_RESOURCESTATE_FAILED"; + default: return "SG_RESOURCESTATE_INVALID"; + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_resource_slot(const sg_slot_info* slot) { + igText("ResId: %08X", slot->res_id); + igText("State: %s", _sgimgui_resourcestate_string(slot->state)); +} + +_SOKOL_PRIVATE const char* _sgimgui_backend_string(sg_backend b) { + switch (b) { + case SG_BACKEND_GLCORE33: return "SG_BACKEND_GLCORE33"; + case SG_BACKEND_GLES3: return "SG_BACKEND_GLES3"; + case SG_BACKEND_D3D11: return "SG_BACKEND_D3D11"; + case SG_BACKEND_METAL_IOS: return "SG_BACKEND_METAL_IOS"; + case SG_BACKEND_METAL_MACOS: return "SG_BACKEND_METAL_MACOS"; + case SG_BACKEND_METAL_SIMULATOR: return "SG_BACKEND_METAL_SIMULATOR"; + case SG_BACKEND_WGPU: return "SG_BACKEND_WGPU"; + case SG_BACKEND_DUMMY: return "SG_BACKEND_DUMMY"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_buffertype_string(sg_buffer_type t) { + switch (t) { + case SG_BUFFERTYPE_VERTEXBUFFER: return "SG_BUFFERTYPE_VERTEXBUFFER"; + case SG_BUFFERTYPE_INDEXBUFFER: return "SG_BUFFERTYPE_INDEXBUFFER"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_usage_string(sg_usage u) { + switch (u) { + case SG_USAGE_IMMUTABLE: return "SG_USAGE_IMMUTABLE"; + case SG_USAGE_DYNAMIC: return "SG_USAGE_DYNAMIC"; + case SG_USAGE_STREAM: return "SG_USAGE_STREAM"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_imagetype_string(sg_image_type t) { + switch (t) { + case SG_IMAGETYPE_2D: return "SG_IMAGETYPE_2D"; + case SG_IMAGETYPE_CUBE: return "SG_IMAGETYPE_CUBE"; + case SG_IMAGETYPE_3D: return "SG_IMAGETYPE_3D"; + case SG_IMAGETYPE_ARRAY: return "SG_IMAGETYPE_ARRAY"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_imagesampletype_string(sg_image_sample_type t) { + switch (t) { + case SG_IMAGESAMPLETYPE_FLOAT: return "SG_IMAGESAMPLETYPE_FLOAT"; + case SG_IMAGESAMPLETYPE_DEPTH: return "SG_IMAGESAMPLETYPE_DEPTH"; + case SG_IMAGESAMPLETYPE_SINT: return "SG_IMAGESAMPLETYPE_SINT"; + case SG_IMAGESAMPLETYPE_UINT: return "SG_IMAGESAMPLETYPE_UINT"; + case SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT: return "SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_samplertype_string(sg_sampler_type t) { + switch (t) { + case SG_SAMPLERTYPE_FILTERING: return "SG_SAMPLERTYPE_FILTERING"; + case SG_SAMPLERTYPE_COMPARISON: return "SG_SAMPLERTYPE_COMPARISON"; + case SG_SAMPLERTYPE_NONFILTERING: return "SG_SAMPLERTYPE_NONFILTERING"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_uniformlayout_string(sg_uniform_layout l) { + switch (l) { + case SG_UNIFORMLAYOUT_NATIVE: return "SG_UNIFORMLAYOUT_NATIVE"; + case SG_UNIFORMLAYOUT_STD140: return "SG_UNIFORMLAYOUT_STD140"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_pixelformat_string(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_NONE: return "SG_PIXELFORMAT_NONE"; + case SG_PIXELFORMAT_R8: return "SG_PIXELFORMAT_R8"; + case SG_PIXELFORMAT_R8SN: return "SG_PIXELFORMAT_R8SN"; + case SG_PIXELFORMAT_R8UI: return "SG_PIXELFORMAT_R8UI"; + case SG_PIXELFORMAT_R8SI: return "SG_PIXELFORMAT_R8SI"; + case SG_PIXELFORMAT_R16: return "SG_PIXELFORMAT_R16"; + case SG_PIXELFORMAT_R16SN: return "SG_PIXELFORMAT_R16SN"; + case SG_PIXELFORMAT_R16UI: return "SG_PIXELFORMAT_R16UI"; + case SG_PIXELFORMAT_R16SI: return "SG_PIXELFORMAT_R16SI"; + case SG_PIXELFORMAT_R16F: return "SG_PIXELFORMAT_R16F"; + case SG_PIXELFORMAT_RG8: return "SG_PIXELFORMAT_RG8"; + case SG_PIXELFORMAT_RG8SN: return "SG_PIXELFORMAT_RG8SN"; + case SG_PIXELFORMAT_RG8UI: return "SG_PIXELFORMAT_RG8UI"; + case SG_PIXELFORMAT_RG8SI: return "SG_PIXELFORMAT_RG8SI"; + case SG_PIXELFORMAT_R32UI: return "SG_PIXELFORMAT_R32UI"; + case SG_PIXELFORMAT_R32SI: return "SG_PIXELFORMAT_R32SI"; + case SG_PIXELFORMAT_R32F: return "SG_PIXELFORMAT_R32F"; + case SG_PIXELFORMAT_RG16: return "SG_PIXELFORMAT_RG16"; + case SG_PIXELFORMAT_RG16SN: return "SG_PIXELFORMAT_RG16SN"; + case SG_PIXELFORMAT_RG16UI: return "SG_PIXELFORMAT_RG16UI"; + case SG_PIXELFORMAT_RG16SI: return "SG_PIXELFORMAT_RG16SI"; + case SG_PIXELFORMAT_RG16F: return "SG_PIXELFORMAT_RG16F"; + case SG_PIXELFORMAT_RGBA8: return "SG_PIXELFORMAT_RGBA8"; + case SG_PIXELFORMAT_SRGB8A8: return "SG_PIXELFORMAT_SRGB8A8"; + case SG_PIXELFORMAT_RGBA8SN: return "SG_PIXELFORMAT_RGBA8SN"; + case SG_PIXELFORMAT_RGBA8UI: return "SG_PIXELFORMAT_RGBA8UI"; + case SG_PIXELFORMAT_RGBA8SI: return "SG_PIXELFORMAT_RGBA8SI"; + case SG_PIXELFORMAT_BGRA8: return "SG_PIXELFORMAT_BGRA8"; + case SG_PIXELFORMAT_RGB10A2: return "SG_PIXELFORMAT_RGB10A2"; + case SG_PIXELFORMAT_RG11B10F: return "SG_PIXELFORMAT_RG11B10F"; + case SG_PIXELFORMAT_RG32UI: return "SG_PIXELFORMAT_RG32UI"; + case SG_PIXELFORMAT_RG32SI: return "SG_PIXELFORMAT_RG32SI"; + case SG_PIXELFORMAT_RG32F: return "SG_PIXELFORMAT_RG32F"; + case SG_PIXELFORMAT_RGBA16: return "SG_PIXELFORMAT_RGBA16"; + case SG_PIXELFORMAT_RGBA16SN: return "SG_PIXELFORMAT_RGBA16SN"; + case SG_PIXELFORMAT_RGBA16UI: return "SG_PIXELFORMAT_RGBA16UI"; + case SG_PIXELFORMAT_RGBA16SI: return "SG_PIXELFORMAT_RGBA16SI"; + case SG_PIXELFORMAT_RGBA16F: return "SG_PIXELFORMAT_RGBA16F"; + case SG_PIXELFORMAT_RGBA32UI: return "SG_PIXELFORMAT_RGBA32UI"; + case SG_PIXELFORMAT_RGBA32SI: return "SG_PIXELFORMAT_RGBA32SI"; + case SG_PIXELFORMAT_RGBA32F: return "SG_PIXELFORMAT_RGBA32F"; + case SG_PIXELFORMAT_DEPTH: return "SG_PIXELFORMAT_DEPTH"; + case SG_PIXELFORMAT_DEPTH_STENCIL: return "SG_PIXELFORMAT_DEPTH_STENCIL"; + case SG_PIXELFORMAT_BC1_RGBA: return "SG_PIXELFORMAT_BC1_RGBA"; + case SG_PIXELFORMAT_BC2_RGBA: return "SG_PIXELFORMAT_BC2_RGBA"; + case SG_PIXELFORMAT_BC3_RGBA: return "SG_PIXELFORMAT_BC3_RGBA"; + case SG_PIXELFORMAT_BC4_R: return "SG_PIXELFORMAT_BC4_R"; + case SG_PIXELFORMAT_BC4_RSN: return "SG_PIXELFORMAT_BC4_RSN"; + case SG_PIXELFORMAT_BC5_RG: return "SG_PIXELFORMAT_BC5_RG"; + case SG_PIXELFORMAT_BC5_RGSN: return "SG_PIXELFORMAT_BC5_RGSN"; + case SG_PIXELFORMAT_BC6H_RGBF: return "SG_PIXELFORMAT_BC6H_RGBF"; + case SG_PIXELFORMAT_BC6H_RGBUF: return "SG_PIXELFORMAT_BC6H_RGBUF"; + case SG_PIXELFORMAT_BC7_RGBA: return "SG_PIXELFORMAT_BC7_RGBA"; + case SG_PIXELFORMAT_PVRTC_RGB_2BPP: return "SG_PIXELFORMAT_PVRTC_RGB_2BPP"; + case SG_PIXELFORMAT_PVRTC_RGB_4BPP: return "SG_PIXELFORMAT_PVRTC_RGB_4BPP"; + case SG_PIXELFORMAT_PVRTC_RGBA_2BPP: return "SG_PIXELFORMAT_PVRTC_RGBA_2BPP"; + case SG_PIXELFORMAT_PVRTC_RGBA_4BPP: return "SG_PIXELFORMAT_PVRTC_RGBA_4BPP"; + case SG_PIXELFORMAT_ETC2_RGB8: return "SG_PIXELFORMAT_ETC2_RGB8"; + case SG_PIXELFORMAT_ETC2_RGB8A1: return "SG_PIXELFORMAT_ETC2_RGB8A1"; + case SG_PIXELFORMAT_ETC2_RGBA8: return "SG_PIXELFORMAT_ETC2_RGBA8"; + case SG_PIXELFORMAT_ETC2_RG11: return "SG_PIXELFORMAT_ETC2_RG11"; + case SG_PIXELFORMAT_ETC2_RG11SN: return "SG_PIXELFORMAT_ETC2_RG11SN"; + case SG_PIXELFORMAT_RGB9E5: return "SG_PIXELFORMAT_RGB9E5"; + case SG_PIXELFORMAT_BC3_SRGBA: return "SG_PIXELFORMAT_BC3_SRGBA"; + case SG_PIXELFORMAT_BC7_SRGBA: return "SG_PIXELFORMAT_BC7_SRGBA"; + case SG_PIXELFORMAT_ETC2_SRGB8: return "SG_PIXELFORMAT_ETC2_SRGB8"; + case SG_PIXELFORMAT_ETC2_SRGB8A8: return "SG_PIXELFORMAT_ETC2_SRGB8A8"; + case SG_PIXELFORMAT_ASTC_4x4_RGBA: return "SG_PIXELFORMAT_ASTC_4x4_RGBA"; + case SG_PIXELFORMAT_ASTC_4x4_SRGBA: return "SG_PIXELFORMAT_ASTC_4x4_SRGBA"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_filter_string(sg_filter f) { + switch (f) { + case SG_FILTER_NONE: return "SG_FILTER_NONE"; + case SG_FILTER_NEAREST: return "SG_FILTER_NEAREST"; + case SG_FILTER_LINEAR: return "SG_FILTER_LINEAR"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_wrap_string(sg_wrap w) { + switch (w) { + case SG_WRAP_REPEAT: return "SG_WRAP_REPEAT"; + case SG_WRAP_CLAMP_TO_EDGE: return "SG_WRAP_CLAMP_TO_EDGE"; + case SG_WRAP_CLAMP_TO_BORDER: return "SG_WRAP_CLAMP_TO_BORDER"; + case SG_WRAP_MIRRORED_REPEAT: return "SG_WRAP_MIRRORED_REPEAT"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_bordercolor_string(sg_border_color bc) { + switch (bc) { + case SG_BORDERCOLOR_TRANSPARENT_BLACK: return "SG_BORDERCOLOR_TRANSPARENT_BLACK"; + case SG_BORDERCOLOR_OPAQUE_BLACK: return "SG_BORDERCOLOR_OPAQUE_BLACK"; + case SG_BORDERCOLOR_OPAQUE_WHITE: return "SG_BORDERCOLOR_OPAQUE_WHITE"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_uniformtype_string(sg_uniform_type t) { + switch (t) { + case SG_UNIFORMTYPE_FLOAT: return "SG_UNIFORMTYPE_FLOAT"; + case SG_UNIFORMTYPE_FLOAT2: return "SG_UNIFORMTYPE_FLOAT2"; + case SG_UNIFORMTYPE_FLOAT3: return "SG_UNIFORMTYPE_FLOAT3"; + case SG_UNIFORMTYPE_FLOAT4: return "SG_UNIFORMTYPE_FLOAT4"; + case SG_UNIFORMTYPE_INT: return "SG_UNIFORMTYPE_INT"; + case SG_UNIFORMTYPE_INT2: return "SG_UNIFORMTYPE_INT2"; + case SG_UNIFORMTYPE_INT3: return "SG_UNIFORMTYPE_INT3"; + case SG_UNIFORMTYPE_INT4: return "SG_UNIFORMTYPE_INT4"; + case SG_UNIFORMTYPE_MAT4: return "SG_UNIFORMTYPE_MAT4"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_vertexstep_string(sg_vertex_step s) { + switch (s) { + case SG_VERTEXSTEP_PER_VERTEX: return "SG_VERTEXSTEP_PER_VERTEX"; + case SG_VERTEXSTEP_PER_INSTANCE: return "SG_VERTEXSTEP_PER_INSTANCE"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_vertexformat_string(sg_vertex_format f) { + switch (f) { + case SG_VERTEXFORMAT_FLOAT: return "SG_VERTEXFORMAT_FLOAT"; + case SG_VERTEXFORMAT_FLOAT2: return "SG_VERTEXFORMAT_FLOAT2"; + case SG_VERTEXFORMAT_FLOAT3: return "SG_VERTEXFORMAT_FLOAT3"; + case SG_VERTEXFORMAT_FLOAT4: return "SG_VERTEXFORMAT_FLOAT4"; + case SG_VERTEXFORMAT_BYTE4: return "SG_VERTEXFORMAT_BYTE4"; + case SG_VERTEXFORMAT_BYTE4N: return "SG_VERTEXFORMAT_BYTE4N"; + case SG_VERTEXFORMAT_UBYTE4: return "SG_VERTEXFORMAT_UBYTE4"; + case SG_VERTEXFORMAT_UBYTE4N: return "SG_VERTEXFORMAT_UBYTE4N"; + case SG_VERTEXFORMAT_SHORT2: return "SG_VERTEXFORMAT_SHORT2"; + case SG_VERTEXFORMAT_SHORT2N: return "SG_VERTEXFORMAT_SHORT2N"; + case SG_VERTEXFORMAT_USHORT2N: return "SG_VERTEXFORMAT_USHORT2N"; + case SG_VERTEXFORMAT_SHORT4: return "SG_VERTEXFORMAT_SHORT4"; + case SG_VERTEXFORMAT_SHORT4N: return "SG_VERTEXFORMAT_SHORT4N"; + case SG_VERTEXFORMAT_USHORT4N: return "SG_VERTEXFORMAT_USHORT4N"; + case SG_VERTEXFORMAT_UINT10_N2: return "SG_VERTEXFORMAT_UINT10_N2"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_primitivetype_string(sg_primitive_type t) { + switch (t) { + case SG_PRIMITIVETYPE_POINTS: return "SG_PRIMITIVETYPE_POINTS"; + case SG_PRIMITIVETYPE_LINES: return "SG_PRIMITIVETYPE_LINES"; + case SG_PRIMITIVETYPE_LINE_STRIP: return "SG_PRIMITIVETYPE_LINE_STRIP"; + case SG_PRIMITIVETYPE_TRIANGLES: return "SG_PRIMITIVETYPE_TRIANGLES"; + case SG_PRIMITIVETYPE_TRIANGLE_STRIP: return "SG_PRIMITIVETYPE_TRIANGLE_STRIP"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_indextype_string(sg_index_type t) { + switch (t) { + case SG_INDEXTYPE_NONE: return "SG_INDEXTYPE_NONE"; + case SG_INDEXTYPE_UINT16: return "SG_INDEXTYPE_UINT16"; + case SG_INDEXTYPE_UINT32: return "SG_INDEXTYPE_UINT32"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_stencilop_string(sg_stencil_op op) { + switch (op) { + case SG_STENCILOP_KEEP: return "SG_STENCILOP_KEEP"; + case SG_STENCILOP_ZERO: return "SG_STENCILOP_ZERO"; + case SG_STENCILOP_REPLACE: return "SG_STENCILOP_REPLACE"; + case SG_STENCILOP_INCR_CLAMP: return "SG_STENCILOP_INCR_CLAMP"; + case SG_STENCILOP_DECR_CLAMP: return "SG_STENCILOP_DECR_CLAMP"; + case SG_STENCILOP_INVERT: return "SG_STENCILOP_INVERT"; + case SG_STENCILOP_INCR_WRAP: return "SG_STENCILOP_INCR_WRAP"; + case SG_STENCILOP_DECR_WRAP: return "SG_STENCILOP_DECR_WRAP"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_comparefunc_string(sg_compare_func f) { + switch (f) { + case SG_COMPAREFUNC_NEVER: return "SG_COMPAREFUNC_NEVER"; + case SG_COMPAREFUNC_LESS: return "SG_COMPAREFUNC_LESS"; + case SG_COMPAREFUNC_EQUAL: return "SG_COMPAREFUNC_EQUAL"; + case SG_COMPAREFUNC_LESS_EQUAL: return "SG_COMPAREFUNC_LESS_EQUAL"; + case SG_COMPAREFUNC_GREATER: return "SG_COMPAREFUNC_GREATER"; + case SG_COMPAREFUNC_NOT_EQUAL: return "SG_COMPAREFUNC_NOT_EQUAL"; + case SG_COMPAREFUNC_GREATER_EQUAL: return "SG_COMPAREFUNC_GREATER_EQUAL"; + case SG_COMPAREFUNC_ALWAYS: return "SG_COMPAREFUNC_ALWAYS"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_blendfactor_string(sg_blend_factor f) { + switch (f) { + case SG_BLENDFACTOR_ZERO: return "SG_BLENDFACTOR_ZERO"; + case SG_BLENDFACTOR_ONE: return "SG_BLENDFACTOR_ONE"; + case SG_BLENDFACTOR_SRC_COLOR: return "SG_BLENDFACTOR_SRC_COLOR"; + case SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return "SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR"; + case SG_BLENDFACTOR_SRC_ALPHA: return "SG_BLENDFACTOR_SRC_ALPHA"; + case SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return "SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA"; + case SG_BLENDFACTOR_DST_COLOR: return "SG_BLENDFACTOR_DST_COLOR"; + case SG_BLENDFACTOR_ONE_MINUS_DST_COLOR: return "SG_BLENDFACTOR_ONE_MINUS_DST_COLOR"; + case SG_BLENDFACTOR_DST_ALPHA: return "SG_BLENDFACTOR_DST_ALPHA"; + case SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return "SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA"; + case SG_BLENDFACTOR_SRC_ALPHA_SATURATED: return "SG_BLENDFACTOR_SRC_ALPHA_SATURATED"; + case SG_BLENDFACTOR_BLEND_COLOR: return "SG_BLENDFACTOR_BLEND_COLOR"; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR: return "SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR"; + case SG_BLENDFACTOR_BLEND_ALPHA: return "SG_BLENDFACTOR_BLEND_ALPHA"; + case SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA: return "SG_BLENDFACTOR_ONE_MINUS_BLEND_ALPHA"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_blendop_string(sg_blend_op op) { + switch (op) { + case SG_BLENDOP_ADD: return "SG_BLENDOP_ADD"; + case SG_BLENDOP_SUBTRACT: return "SG_BLENDOP_SUBTRACT"; + case SG_BLENDOP_REVERSE_SUBTRACT: return "SG_BLENDOP_REVERSE_SUBTRACT"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_colormask_string(sg_color_mask m) { + static const char* str[] = { + "NONE", + "R", + "G", + "RG", + "B", + "RB", + "GB", + "RGB", + "A", + "RA", + "GA", + "RGA", + "BA", + "RBA", + "GBA", + "RGBA", + }; + return str[m & 0xF]; +} + +_SOKOL_PRIVATE const char* _sgimgui_cullmode_string(sg_cull_mode cm) { + switch (cm) { + case SG_CULLMODE_NONE: return "SG_CULLMODE_NONE"; + case SG_CULLMODE_FRONT: return "SG_CULLMODE_FRONT"; + case SG_CULLMODE_BACK: return "SG_CULLMODE_BACK"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_facewinding_string(sg_face_winding fw) { + switch (fw) { + case SG_FACEWINDING_CCW: return "SG_FACEWINDING_CCW"; + case SG_FACEWINDING_CW: return "SG_FACEWINDING_CW"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_shaderstage_string(sg_shader_stage stage) { + switch (stage) { + case SG_SHADERSTAGE_VS: return "SG_SHADERSTAGE_VS"; + case SG_SHADERSTAGE_FS: return "SG_SHADERSTAGE_FS"; + default: return "???"; + } +} + +_SOKOL_PRIVATE const char* _sgimgui_bool_string(bool b) { + return b ? "true" : "false"; +} + +_SOKOL_PRIVATE const char* _sgimgui_color_string(sgimgui_str_t* dst_str, sg_color color) { + _sgimgui_snprintf(dst_str, "%.3f %.3f %.3f %.3f", color.r, color.g, color.b, color.a); + return dst_str->buf; +} + +_SOKOL_PRIVATE sgimgui_str_t _sgimgui_res_id_string(uint32_t res_id, const char* label) { + SOKOL_ASSERT(label); + sgimgui_str_t res; + if (label[0]) { + _sgimgui_snprintf(&res, "'%s'", label); + } else { + _sgimgui_snprintf(&res, "0x%08X", res_id); + } + return res; +} + +_SOKOL_PRIVATE sgimgui_str_t _sgimgui_buffer_id_string(sgimgui_t* ctx, sg_buffer buf_id) { + if (buf_id.id != SG_INVALID_ID) { + const sgimgui_buffer_t* buf_ui = &ctx->buffer_window.slots[_sgimgui_slot_index(buf_id.id)]; + return _sgimgui_res_id_string(buf_id.id, buf_ui->label.buf); + } else { + return _sgimgui_make_str(""); + } +} + +_SOKOL_PRIVATE sgimgui_str_t _sgimgui_image_id_string(sgimgui_t* ctx, sg_image img_id) { + if (img_id.id != SG_INVALID_ID) { + const sgimgui_image_t* img_ui = &ctx->image_window.slots[_sgimgui_slot_index(img_id.id)]; + return _sgimgui_res_id_string(img_id.id, img_ui->label.buf); + } else { + return _sgimgui_make_str(""); + } +} + +_SOKOL_PRIVATE sgimgui_str_t _sgimgui_sampler_id_string(sgimgui_t* ctx, sg_sampler smp_id) { + if (smp_id.id != SG_INVALID_ID) { + const sgimgui_sampler_t* smp_ui = &ctx->sampler_window.slots[_sgimgui_slot_index(smp_id.id)]; + return _sgimgui_res_id_string(smp_id.id, smp_ui->label.buf); + } else { + return _sgimgui_make_str(""); + } +} + +_SOKOL_PRIVATE sgimgui_str_t _sgimgui_shader_id_string(sgimgui_t* ctx, sg_shader shd_id) { + if (shd_id.id != SG_INVALID_ID) { + const sgimgui_shader_t* shd_ui = &ctx->shader_window.slots[_sgimgui_slot_index(shd_id.id)]; + return _sgimgui_res_id_string(shd_id.id, shd_ui->label.buf); + } else { + return _sgimgui_make_str(""); + } +} + +_SOKOL_PRIVATE sgimgui_str_t _sgimgui_pipeline_id_string(sgimgui_t* ctx, sg_pipeline pip_id) { + if (pip_id.id != SG_INVALID_ID) { + const sgimgui_pipeline_t* pip_ui = &ctx->pipeline_window.slots[_sgimgui_slot_index(pip_id.id)]; + return _sgimgui_res_id_string(pip_id.id, pip_ui->label.buf); + } else { + return _sgimgui_make_str(""); + } +} + +_SOKOL_PRIVATE sgimgui_str_t _sgimgui_attachments_id_string(sgimgui_t* ctx, sg_attachments atts_id) { + if (atts_id.id != SG_INVALID_ID) { + const sgimgui_attachments_t* atts_ui = &ctx->attachments_window.slots[_sgimgui_slot_index(atts_id.id)]; + return _sgimgui_res_id_string(atts_id.id, atts_ui->label.buf); + } else { + return _sgimgui_make_str(""); + } +} + +/*--- RESOURCE HELPERS -------------------------------------------------------*/ +_SOKOL_PRIVATE void _sgimgui_buffer_created(sgimgui_t* ctx, sg_buffer res_id, int slot_index, const sg_buffer_desc* desc) { + SOKOL_ASSERT((slot_index > 0) && (slot_index < ctx->buffer_window.num_slots)); + sgimgui_buffer_t* buf = &ctx->buffer_window.slots[slot_index]; + buf->res_id = res_id; + buf->desc = *desc; + buf->label = _sgimgui_make_str(desc->label); +} + +_SOKOL_PRIVATE void _sgimgui_buffer_destroyed(sgimgui_t* ctx, int slot_index) { + SOKOL_ASSERT((slot_index > 0) && (slot_index < ctx->buffer_window.num_slots)); + sgimgui_buffer_t* buf = &ctx->buffer_window.slots[slot_index]; + buf->res_id.id = SG_INVALID_ID; +} + +_SOKOL_PRIVATE void _sgimgui_image_created(sgimgui_t* ctx, sg_image res_id, int slot_index, const sg_image_desc* desc) { + SOKOL_ASSERT((slot_index > 0) && (slot_index < ctx->image_window.num_slots)); + sgimgui_image_t* img = &ctx->image_window.slots[slot_index]; + img->res_id = res_id; + img->desc = *desc; + img->ui_scale = 1.0f; + img->label = _sgimgui_make_str(desc->label); + simgui_image_desc_t simgui_img_desc; + _sgimgui_clear(&simgui_img_desc, sizeof(simgui_img_desc)); + simgui_img_desc.image = res_id; + // keep sampler at default, which will use sokol_imgui.h's default nearest-filtering sampler + img->simgui_img = simgui_make_image(&simgui_img_desc); +} + +_SOKOL_PRIVATE void _sgimgui_image_destroyed(sgimgui_t* ctx, int slot_index) { + SOKOL_ASSERT((slot_index > 0) && (slot_index < ctx->image_window.num_slots)); + sgimgui_image_t* img = &ctx->image_window.slots[slot_index]; + img->res_id.id = SG_INVALID_ID; + simgui_destroy_image(img->simgui_img); +} + +_SOKOL_PRIVATE void _sgimgui_sampler_created(sgimgui_t* ctx, sg_sampler res_id, int slot_index, const sg_sampler_desc* desc) { + SOKOL_ASSERT((slot_index > 0) && (slot_index < ctx->sampler_window.num_slots)); + sgimgui_sampler_t* smp = &ctx->sampler_window.slots[slot_index]; + smp->res_id = res_id; + smp->desc = *desc; + smp->label = _sgimgui_make_str(desc->label); +} + +_SOKOL_PRIVATE void _sgimgui_sampler_destroyed(sgimgui_t* ctx, int slot_index) { + SOKOL_ASSERT((slot_index > 0) && (slot_index < ctx->sampler_window.num_slots)); + sgimgui_sampler_t* smp = &ctx->sampler_window.slots[slot_index]; + smp->res_id.id = SG_INVALID_ID; +} + +_SOKOL_PRIVATE void _sgimgui_shader_created(sgimgui_t* ctx, sg_shader res_id, int slot_index, const sg_shader_desc* desc) { + SOKOL_ASSERT((slot_index > 0) && (slot_index < ctx->shader_window.num_slots)); + sgimgui_shader_t* shd = &ctx->shader_window.slots[slot_index]; + shd->res_id = res_id; + shd->desc = *desc; + shd->label = _sgimgui_make_str(desc->label); + if (shd->desc.vs.entry) { + shd->vs_entry = _sgimgui_make_str(shd->desc.vs.entry); + shd->desc.vs.entry = shd->vs_entry.buf; + } + if (shd->desc.fs.entry) { + shd->fs_entry = _sgimgui_make_str(shd->desc.fs.entry); + shd->desc.fs.entry = shd->fs_entry.buf; + } + if (shd->desc.vs.d3d11_target) { + shd->vs_d3d11_target = _sgimgui_make_str(shd->desc.vs.d3d11_target); + shd->desc.fs.d3d11_target = shd->vs_d3d11_target.buf; + } + if (shd->desc.fs.d3d11_target) { + shd->fs_d3d11_target = _sgimgui_make_str(shd->desc.fs.d3d11_target); + shd->desc.fs.d3d11_target = shd->fs_d3d11_target.buf; + } + for (int i = 0; i < SG_MAX_SHADERSTAGE_UBS; i++) { + for (int j = 0; j < SG_MAX_UB_MEMBERS; j++) { + sg_shader_uniform_desc* ud = &shd->desc.vs.uniform_blocks[i].uniforms[j]; + if (ud->name) { + shd->vs_uniform_name[i][j] = _sgimgui_make_str(ud->name); + ud->name = shd->vs_uniform_name[i][j].buf; + } + } + } + for (int i = 0; i < SG_MAX_SHADERSTAGE_UBS; i++) { + for (int j = 0; j < SG_MAX_UB_MEMBERS; j++) { + sg_shader_uniform_desc* ud = &shd->desc.fs.uniform_blocks[i].uniforms[j]; + if (ud->name) { + shd->fs_uniform_name[i][j] = _sgimgui_make_str(ud->name); + ud->name = shd->fs_uniform_name[i][j].buf; + } + } + } + for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGESAMPLERPAIRS; i++) { + if (shd->desc.vs.image_sampler_pairs[i].glsl_name) { + shd->vs_image_sampler_name[i] = _sgimgui_make_str(shd->desc.vs.image_sampler_pairs[i].glsl_name); + shd->desc.vs.image_sampler_pairs[i].glsl_name = shd->vs_image_sampler_name[i].buf; + } + } + for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGESAMPLERPAIRS; i++) { + if (shd->desc.fs.image_sampler_pairs[i].glsl_name) { + shd->fs_image_sampler_name[i] = _sgimgui_make_str(shd->desc.fs.image_sampler_pairs[i].glsl_name); + shd->desc.fs.image_sampler_pairs[i].glsl_name = shd->fs_image_sampler_name[i].buf; + } + } + if (shd->desc.vs.source) { + shd->desc.vs.source = _sgimgui_str_dup(&ctx->desc.allocator, shd->desc.vs.source); + } + if (shd->desc.vs.bytecode.ptr) { + shd->desc.vs.bytecode.ptr = _sgimgui_bin_dup(&ctx->desc.allocator, shd->desc.vs.bytecode.ptr, shd->desc.vs.bytecode.size); + } + if (shd->desc.fs.source) { + shd->desc.fs.source = _sgimgui_str_dup(&ctx->desc.allocator, shd->desc.fs.source); + } + if (shd->desc.fs.bytecode.ptr) { + shd->desc.fs.bytecode.ptr = _sgimgui_bin_dup(&ctx->desc.allocator, shd->desc.fs.bytecode.ptr, shd->desc.fs.bytecode.size); + } + for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + sg_shader_attr_desc* ad = &shd->desc.attrs[i]; + if (ad->name) { + shd->attr_name[i] = _sgimgui_make_str(ad->name); + ad->name = shd->attr_name[i].buf; + } + if (ad->sem_name) { + shd->attr_sem_name[i] = _sgimgui_make_str(ad->sem_name); + ad->sem_name = shd->attr_sem_name[i].buf; + } + } +} + +_SOKOL_PRIVATE void _sgimgui_shader_destroyed(sgimgui_t* ctx, int slot_index) { + SOKOL_ASSERT((slot_index > 0) && (slot_index < ctx->shader_window.num_slots)); + sgimgui_shader_t* shd = &ctx->shader_window.slots[slot_index]; + shd->res_id.id = SG_INVALID_ID; + if (shd->desc.vs.source) { + _sgimgui_free(&ctx->desc.allocator, (void*)shd->desc.vs.source); + shd->desc.vs.source = 0; + } + if (shd->desc.vs.bytecode.ptr) { + _sgimgui_free(&ctx->desc.allocator, (void*)shd->desc.vs.bytecode.ptr); + shd->desc.vs.bytecode.ptr = 0; + } + if (shd->desc.fs.source) { + _sgimgui_free(&ctx->desc.allocator, (void*)shd->desc.fs.source); + shd->desc.fs.source = 0; + } + if (shd->desc.fs.bytecode.ptr) { + _sgimgui_free(&ctx->desc.allocator, (void*)shd->desc.fs.bytecode.ptr); + shd->desc.fs.bytecode.ptr = 0; + } +} + +_SOKOL_PRIVATE void _sgimgui_pipeline_created(sgimgui_t* ctx, sg_pipeline res_id, int slot_index, const sg_pipeline_desc* desc) { + SOKOL_ASSERT((slot_index > 0) && (slot_index < ctx->pipeline_window.num_slots)); + sgimgui_pipeline_t* pip = &ctx->pipeline_window.slots[slot_index]; + pip->res_id = res_id; + pip->label = _sgimgui_make_str(desc->label); + pip->desc = *desc; + +} + +_SOKOL_PRIVATE void _sgimgui_pipeline_destroyed(sgimgui_t* ctx, int slot_index) { + SOKOL_ASSERT((slot_index > 0) && (slot_index < ctx->pipeline_window.num_slots)); + sgimgui_pipeline_t* pip = &ctx->pipeline_window.slots[slot_index]; + pip->res_id.id = SG_INVALID_ID; +} + +_SOKOL_PRIVATE void _sgimgui_attachments_created(sgimgui_t* ctx, sg_attachments res_id, int slot_index, const sg_attachments_desc* desc) { + SOKOL_ASSERT((slot_index > 0) && (slot_index < ctx->attachments_window.num_slots)); + sgimgui_attachments_t* atts = &ctx->attachments_window.slots[slot_index]; + atts->res_id = res_id; + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + atts->color_image_scale[i] = 0.25f; + atts->resolve_image_scale[i] = 0.25f; + } + atts->ds_image_scale = 0.25f; + atts->label = _sgimgui_make_str(desc->label); + atts->desc = *desc; +} + +_SOKOL_PRIVATE void _sgimgui_attachments_destroyed(sgimgui_t* ctx, int slot_index) { + SOKOL_ASSERT((slot_index > 0) && (slot_index < ctx->attachments_window.num_slots)); + sgimgui_attachments_t* atts = &ctx->attachments_window.slots[slot_index]; + atts->res_id.id = SG_INVALID_ID; +} + +/*--- COMMAND CAPTURING ------------------------------------------------------*/ +_SOKOL_PRIVATE void _sgimgui_capture_init(sgimgui_t* ctx) { + const size_t ubuf_initial_size = 256 * 1024; + for (int i = 0; i < 2; i++) { + sgimgui_capture_bucket_t* bucket = &ctx->capture_window.bucket[i]; + bucket->ubuf_size = ubuf_initial_size; + bucket->ubuf = (uint8_t*) _sgimgui_malloc(&ctx->desc.allocator, bucket->ubuf_size); + } +} + +_SOKOL_PRIVATE void _sgimgui_capture_discard(sgimgui_t* ctx) { + for (int i = 0; i < 2; i++) { + sgimgui_capture_bucket_t* bucket = &ctx->capture_window.bucket[i]; + SOKOL_ASSERT(bucket->ubuf); + _sgimgui_free(&ctx->desc.allocator, bucket->ubuf); + bucket->ubuf = 0; + } +} + +_SOKOL_PRIVATE sgimgui_capture_bucket_t* _sgimgui_capture_get_write_bucket(sgimgui_t* ctx) { + return &ctx->capture_window.bucket[ctx->capture_window.bucket_index & 1]; +} + +_SOKOL_PRIVATE sgimgui_capture_bucket_t* _sgimgui_capture_get_read_bucket(sgimgui_t* ctx) { + return &ctx->capture_window.bucket[(ctx->capture_window.bucket_index + 1) & 1]; +} + +_SOKOL_PRIVATE void _sgimgui_capture_next_frame(sgimgui_t* ctx) { + ctx->capture_window.bucket_index = (ctx->capture_window.bucket_index + 1) & 1; + sgimgui_capture_bucket_t* bucket = &ctx->capture_window.bucket[ctx->capture_window.bucket_index]; + bucket->num_items = 0; + bucket->ubuf_pos = 0; +} + +_SOKOL_PRIVATE void _sgimgui_capture_grow_ubuf(sgimgui_t* ctx, size_t required_size) { + sgimgui_capture_bucket_t* bucket = _sgimgui_capture_get_write_bucket(ctx); + SOKOL_ASSERT(required_size > bucket->ubuf_size); + size_t old_size = bucket->ubuf_size; + size_t new_size = required_size + (required_size>>1); /* allocate a bit ahead */ + bucket->ubuf_size = new_size; + bucket->ubuf = (uint8_t*) _sgimgui_realloc(&ctx->desc.allocator, bucket->ubuf, old_size, new_size); +} + +_SOKOL_PRIVATE sgimgui_capture_item_t* _sgimgui_capture_next_write_item(sgimgui_t* ctx) { + sgimgui_capture_bucket_t* bucket = _sgimgui_capture_get_write_bucket(ctx); + if (bucket->num_items < sgimgui_MAX_FRAMECAPTURE_ITEMS) { + sgimgui_capture_item_t* item = &bucket->items[bucket->num_items++]; + return item; + } else { + return 0; + } +} + +_SOKOL_PRIVATE int _sgimgui_capture_num_read_items(sgimgui_t* ctx) { + sgimgui_capture_bucket_t* bucket = _sgimgui_capture_get_read_bucket(ctx); + return bucket->num_items; +} + +_SOKOL_PRIVATE sgimgui_capture_item_t* _sgimgui_capture_read_item_at(sgimgui_t* ctx, int index) { + sgimgui_capture_bucket_t* bucket = _sgimgui_capture_get_read_bucket(ctx); + SOKOL_ASSERT(index < bucket->num_items); + return &bucket->items[index]; +} + +_SOKOL_PRIVATE size_t _sgimgui_capture_uniforms(sgimgui_t* ctx, const sg_range* data) { + sgimgui_capture_bucket_t* bucket = _sgimgui_capture_get_write_bucket(ctx); + const size_t required_size = bucket->ubuf_pos + data->size; + if (required_size > bucket->ubuf_size) { + _sgimgui_capture_grow_ubuf(ctx, required_size); + } + SOKOL_ASSERT(required_size <= bucket->ubuf_size); + memcpy(bucket->ubuf + bucket->ubuf_pos, data->ptr, data->size); + const size_t pos = bucket->ubuf_pos; + bucket->ubuf_pos += data->size; + SOKOL_ASSERT(bucket->ubuf_pos <= bucket->ubuf_size); + return pos; +} + +_SOKOL_PRIVATE sgimgui_str_t _sgimgui_capture_item_string(sgimgui_t* ctx, int index, const sgimgui_capture_item_t* item) { + sgimgui_str_t str = _sgimgui_make_str(0); + switch (item->cmd) { + case SGIMGUI_CMD_RESET_STATE_CACHE: + _sgimgui_snprintf(&str, "%d: sg_reset_state_cache()", index); + break; + + case SGIMGUI_CMD_MAKE_BUFFER: + { + sgimgui_str_t res_id = _sgimgui_buffer_id_string(ctx, item->args.make_buffer.result); + _sgimgui_snprintf(&str, "%d: sg_make_buffer(desc=..) => %s", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_MAKE_IMAGE: + { + sgimgui_str_t res_id = _sgimgui_image_id_string(ctx, item->args.make_image.result); + _sgimgui_snprintf(&str, "%d: sg_make_image(desc=..) => %s", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_MAKE_SAMPLER: + { + sgimgui_str_t res_id = _sgimgui_sampler_id_string(ctx, item->args.make_sampler.result); + _sgimgui_snprintf(&str, "%d: sg_make_sampler(desc=..) => %s", index, res_id.buf); + } + break; + case SGIMGUI_CMD_MAKE_SHADER: + { + sgimgui_str_t res_id = _sgimgui_shader_id_string(ctx, item->args.make_shader.result); + _sgimgui_snprintf(&str, "%d: sg_make_shader(desc=..) => %s", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_MAKE_PIPELINE: + { + sgimgui_str_t res_id = _sgimgui_pipeline_id_string(ctx, item->args.make_pipeline.result); + _sgimgui_snprintf(&str, "%d: sg_make_pipeline(desc=..) => %s", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_MAKE_ATTACHMENTS: + { + sgimgui_str_t res_id = _sgimgui_attachments_id_string(ctx, item->args.make_attachments.result); + _sgimgui_snprintf(&str, "%d: sg_make_attachments(desc=..) => %s", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_DESTROY_BUFFER: + { + sgimgui_str_t res_id = _sgimgui_buffer_id_string(ctx, item->args.destroy_buffer.buffer); + _sgimgui_snprintf(&str, "%d: sg_destroy_buffer(buf=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_DESTROY_IMAGE: + { + sgimgui_str_t res_id = _sgimgui_image_id_string(ctx, item->args.destroy_image.image); + _sgimgui_snprintf(&str, "%d: sg_destroy_image(img=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_DESTROY_SAMPLER: + { + sgimgui_str_t res_id = _sgimgui_sampler_id_string(ctx, item->args.destroy_sampler.sampler); + _sgimgui_snprintf(&str, "%d: sg_destroy_sampler(smp=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_DESTROY_SHADER: + { + sgimgui_str_t res_id = _sgimgui_shader_id_string(ctx, item->args.destroy_shader.shader); + _sgimgui_snprintf(&str, "%d: sg_destroy_shader(shd=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_DESTROY_PIPELINE: + { + sgimgui_str_t res_id = _sgimgui_pipeline_id_string(ctx, item->args.destroy_pipeline.pipeline); + _sgimgui_snprintf(&str, "%d: sg_destroy_pipeline(pip=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_DESTROY_ATTACHMENTS: + { + sgimgui_str_t res_id = _sgimgui_attachments_id_string(ctx, item->args.destroy_attachments.attachments); + _sgimgui_snprintf(&str, "%d: sg_destroy_attachments(atts=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_UPDATE_BUFFER: + { + sgimgui_str_t res_id = _sgimgui_buffer_id_string(ctx, item->args.update_buffer.buffer); + _sgimgui_snprintf(&str, "%d: sg_update_buffer(buf=%s, data.size=%d)", + index, res_id.buf, + item->args.update_buffer.data_size); + } + break; + + case SGIMGUI_CMD_UPDATE_IMAGE: + { + sgimgui_str_t res_id = _sgimgui_image_id_string(ctx, item->args.update_image.image); + _sgimgui_snprintf(&str, "%d: sg_update_image(img=%s, data=..)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_APPEND_BUFFER: + { + sgimgui_str_t res_id = _sgimgui_buffer_id_string(ctx, item->args.append_buffer.buffer); + _sgimgui_snprintf(&str, "%d: sg_append_buffer(buf=%s, data.size=%d) => %d", + index, res_id.buf, + item->args.append_buffer.data_size, + item->args.append_buffer.result); + } + break; + + case SGIMGUI_CMD_BEGIN_PASS: + { + _sgimgui_snprintf(&str, "%d: sg_begin_pass(pass=...)", index); + } + break; + + case SGIMGUI_CMD_APPLY_VIEWPORT: + _sgimgui_snprintf(&str, "%d: sg_apply_viewport(x=%d, y=%d, width=%d, height=%d, origin_top_left=%s)", + index, + item->args.apply_viewport.x, + item->args.apply_viewport.y, + item->args.apply_viewport.width, + item->args.apply_viewport.height, + _sgimgui_bool_string(item->args.apply_viewport.origin_top_left)); + break; + + case SGIMGUI_CMD_APPLY_SCISSOR_RECT: + _sgimgui_snprintf(&str, "%d: sg_apply_scissor_rect(x=%d, y=%d, width=%d, height=%d, origin_top_left=%s)", + index, + item->args.apply_scissor_rect.x, + item->args.apply_scissor_rect.y, + item->args.apply_scissor_rect.width, + item->args.apply_scissor_rect.height, + _sgimgui_bool_string(item->args.apply_scissor_rect.origin_top_left)); + break; + + case SGIMGUI_CMD_APPLY_PIPELINE: + { + sgimgui_str_t res_id = _sgimgui_pipeline_id_string(ctx, item->args.apply_pipeline.pipeline); + _sgimgui_snprintf(&str, "%d: sg_apply_pipeline(pip=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_APPLY_BINDINGS: + _sgimgui_snprintf(&str, "%d: sg_apply_bindings(bindings=..)", index); + break; + + case SGIMGUI_CMD_APPLY_UNIFORMS: + _sgimgui_snprintf(&str, "%d: sg_apply_uniforms(stage=%s, ub_index=%d, data.size=%d)", + index, + _sgimgui_shaderstage_string(item->args.apply_uniforms.stage), + item->args.apply_uniforms.ub_index, + item->args.apply_uniforms.data_size); + break; + + case SGIMGUI_CMD_DRAW: + _sgimgui_snprintf(&str, "%d: sg_draw(base_element=%d, num_elements=%d, num_instances=%d)", + index, + item->args.draw.base_element, + item->args.draw.num_elements, + item->args.draw.num_instances); + break; + + case SGIMGUI_CMD_END_PASS: + _sgimgui_snprintf(&str, "%d: sg_end_pass()", index); + break; + + case SGIMGUI_CMD_COMMIT: + _sgimgui_snprintf(&str, "%d: sg_commit()", index); + break; + + case SGIMGUI_CMD_ALLOC_BUFFER: + { + sgimgui_str_t res_id = _sgimgui_buffer_id_string(ctx, item->args.alloc_buffer.result); + _sgimgui_snprintf(&str, "%d: sg_alloc_buffer() => %s", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_ALLOC_IMAGE: + { + sgimgui_str_t res_id = _sgimgui_image_id_string(ctx, item->args.alloc_image.result); + _sgimgui_snprintf(&str, "%d: sg_alloc_image() => %s", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_ALLOC_SAMPLER: + { + sgimgui_str_t res_id = _sgimgui_sampler_id_string(ctx, item->args.alloc_sampler.result); + _sgimgui_snprintf(&str, "%d: sg_alloc_sampler() => %s", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_ALLOC_SHADER: + { + sgimgui_str_t res_id = _sgimgui_shader_id_string(ctx, item->args.alloc_shader.result); + _sgimgui_snprintf(&str, "%d: sg_alloc_shader() => %s", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_ALLOC_PIPELINE: + { + sgimgui_str_t res_id = _sgimgui_pipeline_id_string(ctx, item->args.alloc_pipeline.result); + _sgimgui_snprintf(&str, "%d: sg_alloc_pipeline() => %s", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_ALLOC_ATTACHMENTS: + { + sgimgui_str_t res_id = _sgimgui_attachments_id_string(ctx, item->args.alloc_attachments.result); + _sgimgui_snprintf(&str, "%d: sg_alloc_attachments() => %s", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_DEALLOC_BUFFER: + { + sgimgui_str_t res_id = _sgimgui_buffer_id_string(ctx, item->args.dealloc_buffer.buffer); + _sgimgui_snprintf(&str, "%d: sg_dealloc_buffer(buf=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_DEALLOC_IMAGE: + { + sgimgui_str_t res_id = _sgimgui_image_id_string(ctx, item->args.dealloc_image.image); + _sgimgui_snprintf(&str, "%d: sg_dealloc_image(img=%d)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_DEALLOC_SAMPLER: + { + sgimgui_str_t res_id = _sgimgui_sampler_id_string(ctx, item->args.dealloc_sampler.sampler); + _sgimgui_snprintf(&str, "%d: sg_dealloc_sampler(smp=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_DEALLOC_SHADER: + { + sgimgui_str_t res_id = _sgimgui_shader_id_string(ctx, item->args.dealloc_shader.shader); + _sgimgui_snprintf(&str, "%d: sg_dealloc_shader(shd=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_DEALLOC_PIPELINE: + { + sgimgui_str_t res_id = _sgimgui_pipeline_id_string(ctx, item->args.dealloc_pipeline.pipeline); + _sgimgui_snprintf(&str, "%d: sg_dealloc_pipeline(pip=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_DEALLOC_ATTACHMENTS: + { + sgimgui_str_t res_id = _sgimgui_attachments_id_string(ctx, item->args.dealloc_attachments.attachments); + _sgimgui_snprintf(&str, "%d: sg_dealloc_attachments(atts=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_INIT_BUFFER: + { + sgimgui_str_t res_id = _sgimgui_buffer_id_string(ctx, item->args.init_buffer.buffer); + _sgimgui_snprintf(&str, "%d: sg_init_buffer(buf=%s, desc=..)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_INIT_IMAGE: + { + sgimgui_str_t res_id = _sgimgui_image_id_string(ctx, item->args.init_image.image); + _sgimgui_snprintf(&str, "%d: sg_init_image(img=%s, desc=..)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_INIT_SAMPLER: + { + sgimgui_str_t res_id = _sgimgui_sampler_id_string(ctx, item->args.init_sampler.sampler); + _sgimgui_snprintf(&str, "%d: sg_init_sampler(smp=%s, desc=..)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_INIT_SHADER: + { + sgimgui_str_t res_id = _sgimgui_shader_id_string(ctx, item->args.init_shader.shader); + _sgimgui_snprintf(&str, "%d: sg_init_shader(shd=%s, desc=..)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_INIT_PIPELINE: + { + sgimgui_str_t res_id = _sgimgui_pipeline_id_string(ctx, item->args.init_pipeline.pipeline); + _sgimgui_snprintf(&str, "%d: sg_init_pipeline(pip=%s, desc=..)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_INIT_ATTACHMENTS: + { + sgimgui_str_t res_id = _sgimgui_attachments_id_string(ctx, item->args.init_attachments.attachments); + _sgimgui_snprintf(&str, "%d: sg_init_attachments(atts=%s, desc=..)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_UNINIT_BUFFER: + { + sgimgui_str_t res_id = _sgimgui_buffer_id_string(ctx, item->args.uninit_buffer.buffer); + _sgimgui_snprintf(&str, "%d: sg_uninit_buffer(buf=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_UNINIT_IMAGE: + { + sgimgui_str_t res_id = _sgimgui_image_id_string(ctx, item->args.uninit_image.image); + _sgimgui_snprintf(&str, "%d: sg_uninit_image(img=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_UNINIT_SAMPLER: + { + sgimgui_str_t res_id = _sgimgui_sampler_id_string(ctx, item->args.uninit_sampler.sampler); + _sgimgui_snprintf(&str, "%d: sg_uninit_sampler(smp=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_UNINIT_SHADER: + { + sgimgui_str_t res_id = _sgimgui_shader_id_string(ctx, item->args.uninit_shader.shader); + _sgimgui_snprintf(&str, "%d: sg_uninit_shader(shd=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_UNINIT_PIPELINE: + { + sgimgui_str_t res_id = _sgimgui_pipeline_id_string(ctx, item->args.uninit_pipeline.pipeline); + _sgimgui_snprintf(&str, "%d: sg_uninit_pipeline(pip=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_UNINIT_ATTACHMENTS: + { + sgimgui_str_t res_id = _sgimgui_attachments_id_string(ctx, item->args.uninit_attachments.attachments); + _sgimgui_snprintf(&str, "%d: sg_uninit_attachemnts(atts=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_FAIL_BUFFER: + { + sgimgui_str_t res_id = _sgimgui_buffer_id_string(ctx, item->args.fail_buffer.buffer); + _sgimgui_snprintf(&str, "%d: sg_fail_buffer(buf=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_FAIL_IMAGE: + { + sgimgui_str_t res_id = _sgimgui_image_id_string(ctx, item->args.fail_image.image); + _sgimgui_snprintf(&str, "%d: sg_fail_image(img=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_FAIL_SAMPLER: + { + sgimgui_str_t res_id = _sgimgui_sampler_id_string(ctx, item->args.fail_sampler.sampler); + _sgimgui_snprintf(&str, "%d: sg_fail_sampler(smp=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_FAIL_SHADER: + { + sgimgui_str_t res_id = _sgimgui_shader_id_string(ctx, item->args.fail_shader.shader); + _sgimgui_snprintf(&str, "%d: sg_fail_shader(shd=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_FAIL_PIPELINE: + { + sgimgui_str_t res_id = _sgimgui_pipeline_id_string(ctx, item->args.fail_pipeline.pipeline); + _sgimgui_snprintf(&str, "%d: sg_fail_pipeline(shd=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_FAIL_ATTACHMENTS: + { + sgimgui_str_t res_id = _sgimgui_attachments_id_string(ctx, item->args.fail_attachments.attachments); + _sgimgui_snprintf(&str, "%d: sg_fail_attachments(atts=%s)", index, res_id.buf); + } + break; + + case SGIMGUI_CMD_PUSH_DEBUG_GROUP: + _sgimgui_snprintf(&str, "%d: sg_push_debug_group(name=%s)", index, + item->args.push_debug_group.name.buf); + break; + + case SGIMGUI_CMD_POP_DEBUG_GROUP: + _sgimgui_snprintf(&str, "%d: sg_pop_debug_group()", index); + break; + + default: + _sgimgui_snprintf(&str, "%d: ???", index); + break; + } + return str; +} + +/*--- CAPTURE CALLBACKS ------------------------------------------------------*/ +_SOKOL_PRIVATE void _sgimgui_reset_state_cache(void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_RESET_STATE_CACHE; + item->color = _SGIMGUI_COLOR_OTHER; + } + if (ctx->hooks.reset_state_cache) { + ctx->hooks.reset_state_cache(ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_make_buffer(const sg_buffer_desc* desc, sg_buffer buf_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_MAKE_BUFFER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.make_buffer.result = buf_id; + } + if (ctx->hooks.make_buffer) { + ctx->hooks.make_buffer(desc, buf_id, ctx->hooks.user_data); + } + if (buf_id.id != SG_INVALID_ID) { + _sgimgui_buffer_created(ctx, buf_id, _sgimgui_slot_index(buf_id.id), desc); + } +} + +_SOKOL_PRIVATE void _sgimgui_make_image(const sg_image_desc* desc, sg_image img_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_MAKE_IMAGE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.make_image.result = img_id; + } + if (ctx->hooks.make_image) { + ctx->hooks.make_image(desc, img_id, ctx->hooks.user_data); + } + if (img_id.id != SG_INVALID_ID) { + _sgimgui_image_created(ctx, img_id, _sgimgui_slot_index(img_id.id), desc); + } +} + +_SOKOL_PRIVATE void _sgimgui_make_sampler(const sg_sampler_desc* desc, sg_sampler smp_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_MAKE_SAMPLER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.make_sampler.result = smp_id; + } + if (ctx->hooks.make_sampler) { + ctx->hooks.make_sampler(desc, smp_id, ctx->hooks.user_data); + } + if (smp_id.id != SG_INVALID_ID) { + _sgimgui_sampler_created(ctx, smp_id, _sgimgui_slot_index(smp_id.id), desc); + } +} + +_SOKOL_PRIVATE void _sgimgui_make_shader(const sg_shader_desc* desc, sg_shader shd_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_MAKE_SHADER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.make_shader.result = shd_id; + } + if (ctx->hooks.make_shader) { + ctx->hooks.make_shader(desc, shd_id, ctx->hooks.user_data); + } + if (shd_id.id != SG_INVALID_ID) { + _sgimgui_shader_created(ctx, shd_id, _sgimgui_slot_index(shd_id.id), desc); + } +} + +_SOKOL_PRIVATE void _sgimgui_make_pipeline(const sg_pipeline_desc* desc, sg_pipeline pip_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_MAKE_PIPELINE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.make_pipeline.result = pip_id; + } + if (ctx->hooks.make_pipeline) { + ctx->hooks.make_pipeline(desc, pip_id, ctx->hooks.user_data); + } + if (pip_id.id != SG_INVALID_ID) { + _sgimgui_pipeline_created(ctx, pip_id, _sgimgui_slot_index(pip_id.id), desc); + } +} + +_SOKOL_PRIVATE void _sgimgui_make_attachments(const sg_attachments_desc* desc, sg_attachments atts_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_MAKE_ATTACHMENTS; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.make_attachments.result = atts_id; + } + if (ctx->hooks.make_attachments) { + ctx->hooks.make_attachments(desc, atts_id, ctx->hooks.user_data); + } + if (atts_id.id != SG_INVALID_ID) { + _sgimgui_attachments_created(ctx, atts_id, _sgimgui_slot_index(atts_id.id), desc); + } +} + +_SOKOL_PRIVATE void _sgimgui_destroy_buffer(sg_buffer buf, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_DESTROY_BUFFER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.destroy_buffer.buffer = buf; + } + if (ctx->hooks.destroy_buffer) { + ctx->hooks.destroy_buffer(buf, ctx->hooks.user_data); + } + if (buf.id != SG_INVALID_ID) { + _sgimgui_buffer_destroyed(ctx, _sgimgui_slot_index(buf.id)); + } +} + +_SOKOL_PRIVATE void _sgimgui_destroy_image(sg_image img, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_DESTROY_IMAGE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.destroy_image.image = img; + } + if (ctx->hooks.destroy_image) { + ctx->hooks.destroy_image(img, ctx->hooks.user_data); + } + if (img.id != SG_INVALID_ID) { + _sgimgui_image_destroyed(ctx, _sgimgui_slot_index(img.id)); + } +} + +_SOKOL_PRIVATE void _sgimgui_destroy_sampler(sg_sampler smp, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_DESTROY_SAMPLER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.destroy_sampler.sampler = smp; + } + if (ctx->hooks.destroy_sampler) { + ctx->hooks.destroy_sampler(smp, ctx->hooks.user_data); + } + if (smp.id != SG_INVALID_ID) { + _sgimgui_sampler_destroyed(ctx, _sgimgui_slot_index(smp.id)); + } +} + +_SOKOL_PRIVATE void _sgimgui_destroy_shader(sg_shader shd, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_DESTROY_SHADER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.destroy_shader.shader = shd; + } + if (ctx->hooks.destroy_shader) { + ctx->hooks.destroy_shader(shd, ctx->hooks.user_data); + } + if (shd.id != SG_INVALID_ID) { + _sgimgui_shader_destroyed(ctx, _sgimgui_slot_index(shd.id)); + } +} + +_SOKOL_PRIVATE void _sgimgui_destroy_pipeline(sg_pipeline pip, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_DESTROY_PIPELINE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.destroy_pipeline.pipeline = pip; + } + if (ctx->hooks.destroy_pipeline) { + ctx->hooks.destroy_pipeline(pip, ctx->hooks.user_data); + } + if (pip.id != SG_INVALID_ID) { + _sgimgui_pipeline_destroyed(ctx, _sgimgui_slot_index(pip.id)); + } +} + +_SOKOL_PRIVATE void _sgimgui_destroy_attachments(sg_attachments atts, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_DESTROY_ATTACHMENTS; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.destroy_attachments.attachments = atts; + } + if (ctx->hooks.destroy_attachments) { + ctx->hooks.destroy_attachments(atts, ctx->hooks.user_data); + } + if (atts.id != SG_INVALID_ID) { + _sgimgui_attachments_destroyed(ctx, _sgimgui_slot_index(atts.id)); + } +} + +_SOKOL_PRIVATE void _sgimgui_update_buffer(sg_buffer buf, const sg_range* data, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_UPDATE_BUFFER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.update_buffer.buffer = buf; + item->args.update_buffer.data_size = data->size; + } + if (ctx->hooks.update_buffer) { + ctx->hooks.update_buffer(buf, data, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_update_image(sg_image img, const sg_image_data* data, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_UPDATE_IMAGE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.update_image.image = img; + } + if (ctx->hooks.update_image) { + ctx->hooks.update_image(img, data, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_append_buffer(sg_buffer buf, const sg_range* data, int result, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_APPEND_BUFFER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.append_buffer.buffer = buf; + item->args.append_buffer.data_size = data->size; + item->args.append_buffer.result = result; + } + if (ctx->hooks.append_buffer) { + ctx->hooks.append_buffer(buf, data, result, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_begin_pass(const sg_pass* pass, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + SOKOL_ASSERT(pass); + item->cmd = SGIMGUI_CMD_BEGIN_PASS; + item->color = _SGIMGUI_COLOR_PASS; + item->args.begin_pass.pass = *pass; + } + if (ctx->hooks.begin_pass) { + ctx->hooks.begin_pass(pass, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_apply_viewport(int x, int y, int width, int height, bool origin_top_left, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_APPLY_VIEWPORT; + item->color = _SGIMGUI_COLOR_APPLY; + item->args.apply_viewport.x = x; + item->args.apply_viewport.y = y; + item->args.apply_viewport.width = width; + item->args.apply_viewport.height = height; + item->args.apply_viewport.origin_top_left = origin_top_left; + } + if (ctx->hooks.apply_viewport) { + ctx->hooks.apply_viewport(x, y, width, height, origin_top_left, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_apply_scissor_rect(int x, int y, int width, int height, bool origin_top_left, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_APPLY_SCISSOR_RECT; + item->color = _SGIMGUI_COLOR_APPLY; + item->args.apply_scissor_rect.x = x; + item->args.apply_scissor_rect.y = y; + item->args.apply_scissor_rect.width = width; + item->args.apply_scissor_rect.height = height; + item->args.apply_scissor_rect.origin_top_left = origin_top_left; + } + if (ctx->hooks.apply_scissor_rect) { + ctx->hooks.apply_scissor_rect(x, y, width, height, origin_top_left, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_apply_pipeline(sg_pipeline pip, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + ctx->cur_pipeline = pip; /* stored for _sgimgui_apply_uniforms */ + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_APPLY_PIPELINE; + item->color = _SGIMGUI_COLOR_APPLY; + item->args.apply_pipeline.pipeline = pip; + } + if (ctx->hooks.apply_pipeline) { + ctx->hooks.apply_pipeline(pip, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_apply_bindings(const sg_bindings* bindings, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + SOKOL_ASSERT(bindings); + item->cmd = SGIMGUI_CMD_APPLY_BINDINGS; + item->color = _SGIMGUI_COLOR_APPLY; + item->args.apply_bindings.bindings = *bindings; + } + if (ctx->hooks.apply_bindings) { + ctx->hooks.apply_bindings(bindings, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_apply_uniforms(sg_shader_stage stage, int ub_index, const sg_range* data, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + SOKOL_ASSERT(data); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_APPLY_UNIFORMS; + item->color = _SGIMGUI_COLOR_APPLY; + sgimgui_args_apply_uniforms_t* args = &item->args.apply_uniforms; + args->stage = stage; + args->ub_index = ub_index; + args->data_size = data->size; + args->pipeline = ctx->cur_pipeline; + args->ubuf_pos = _sgimgui_capture_uniforms(ctx, data); + } + if (ctx->hooks.apply_uniforms) { + ctx->hooks.apply_uniforms(stage, ub_index, data, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_draw(int base_element, int num_elements, int num_instances, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_DRAW; + item->color = _SGIMGUI_COLOR_DRAW; + item->args.draw.base_element = base_element; + item->args.draw.num_elements = num_elements; + item->args.draw.num_instances = num_instances; + } + if (ctx->hooks.draw) { + ctx->hooks.draw(base_element, num_elements, num_instances, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_end_pass(void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + ctx->cur_pipeline.id = SG_INVALID_ID; + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_END_PASS; + item->color = _SGIMGUI_COLOR_PASS; + } + if (ctx->hooks.end_pass) { + ctx->hooks.end_pass(ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_commit(void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_COMMIT; + item->color = _SGIMGUI_COLOR_OTHER; + } + _sgimgui_capture_next_frame(ctx); + if (ctx->hooks.commit) { + ctx->hooks.commit(ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_alloc_buffer(sg_buffer result, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_ALLOC_BUFFER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.alloc_buffer.result = result; + } + if (ctx->hooks.alloc_buffer) { + ctx->hooks.alloc_buffer(result, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_alloc_image(sg_image result, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_ALLOC_IMAGE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.alloc_image.result = result; + } + if (ctx->hooks.alloc_image) { + ctx->hooks.alloc_image(result, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_alloc_sampler(sg_sampler result, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_ALLOC_SAMPLER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.alloc_sampler.result = result; + } + if (ctx->hooks.alloc_sampler) { + ctx->hooks.alloc_sampler(result, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_alloc_shader(sg_shader result, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_ALLOC_SHADER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.alloc_shader.result = result; + } + if (ctx->hooks.alloc_shader) { + ctx->hooks.alloc_shader(result, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_alloc_pipeline(sg_pipeline result, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_ALLOC_PIPELINE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.alloc_pipeline.result = result; + } + if (ctx->hooks.alloc_pipeline) { + ctx->hooks.alloc_pipeline(result, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_alloc_attachments(sg_attachments result, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_ALLOC_ATTACHMENTS; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.alloc_attachments.result = result; + } + if (ctx->hooks.alloc_attachments) { + ctx->hooks.alloc_attachments(result, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_dealloc_buffer(sg_buffer buf_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_DEALLOC_BUFFER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.dealloc_buffer.buffer = buf_id; + } + if (ctx->hooks.dealloc_buffer) { + ctx->hooks.dealloc_buffer(buf_id, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_dealloc_image(sg_image img_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_DEALLOC_IMAGE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.dealloc_image.image = img_id; + } + if (ctx->hooks.dealloc_image) { + ctx->hooks.dealloc_image(img_id, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_dealloc_sampler(sg_sampler smp_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_DEALLOC_SAMPLER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.dealloc_sampler.sampler = smp_id; + } + if (ctx->hooks.dealloc_sampler) { + ctx->hooks.dealloc_sampler(smp_id, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_dealloc_shader(sg_shader shd_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_DEALLOC_SHADER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.dealloc_shader.shader = shd_id; + } + if (ctx->hooks.dealloc_shader) { + ctx->hooks.dealloc_shader(shd_id, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_dealloc_pipeline(sg_pipeline pip_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_DEALLOC_PIPELINE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.dealloc_pipeline.pipeline = pip_id; + } + if (ctx->hooks.dealloc_pipeline) { + ctx->hooks.dealloc_pipeline(pip_id, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_dealloc_attachments(sg_attachments atts_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_DEALLOC_ATTACHMENTS; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.dealloc_attachments.attachments = atts_id; + } + if (ctx->hooks.dealloc_attachments) { + ctx->hooks.dealloc_attachments(atts_id, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_init_buffer(sg_buffer buf_id, const sg_buffer_desc* desc, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_INIT_BUFFER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.init_buffer.buffer = buf_id; + } + if (ctx->hooks.init_buffer) { + ctx->hooks.init_buffer(buf_id, desc, ctx->hooks.user_data); + } + if (buf_id.id != SG_INVALID_ID) { + _sgimgui_buffer_created(ctx, buf_id, _sgimgui_slot_index(buf_id.id), desc); + } +} + +_SOKOL_PRIVATE void _sgimgui_init_image(sg_image img_id, const sg_image_desc* desc, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_INIT_IMAGE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.init_image.image = img_id; + } + if (ctx->hooks.init_image) { + ctx->hooks.init_image(img_id, desc, ctx->hooks.user_data); + } + if (img_id.id != SG_INVALID_ID) { + _sgimgui_image_created(ctx, img_id, _sgimgui_slot_index(img_id.id), desc); + } +} + +_SOKOL_PRIVATE void _sgimgui_init_sampler(sg_sampler smp_id, const sg_sampler_desc* desc, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_INIT_SAMPLER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.init_sampler.sampler = smp_id; + } + if (ctx->hooks.init_sampler) { + ctx->hooks.init_sampler(smp_id, desc, ctx->hooks.user_data); + } + if (smp_id.id != SG_INVALID_ID) { + _sgimgui_sampler_created(ctx, smp_id, _sgimgui_slot_index(smp_id.id), desc); + } +} + +_SOKOL_PRIVATE void _sgimgui_init_shader(sg_shader shd_id, const sg_shader_desc* desc, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_INIT_SHADER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.init_shader.shader = shd_id; + } + if (ctx->hooks.init_shader) { + ctx->hooks.init_shader(shd_id, desc, ctx->hooks.user_data); + } + if (shd_id.id != SG_INVALID_ID) { + _sgimgui_shader_created(ctx, shd_id, _sgimgui_slot_index(shd_id.id), desc); + } +} + +_SOKOL_PRIVATE void _sgimgui_init_pipeline(sg_pipeline pip_id, const sg_pipeline_desc* desc, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_INIT_PIPELINE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.init_pipeline.pipeline = pip_id; + } + if (ctx->hooks.init_pipeline) { + ctx->hooks.init_pipeline(pip_id, desc, ctx->hooks.user_data); + } + if (pip_id.id != SG_INVALID_ID) { + _sgimgui_pipeline_created(ctx, pip_id, _sgimgui_slot_index(pip_id.id), desc); + } +} + +_SOKOL_PRIVATE void _sgimgui_init_attachments(sg_attachments atts_id, const sg_attachments_desc* desc, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_INIT_ATTACHMENTS; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.init_attachments.attachments = atts_id; + } + if (ctx->hooks.init_attachments) { + ctx->hooks.init_attachments(atts_id, desc, ctx->hooks.user_data); + } + if (atts_id.id != SG_INVALID_ID) { + _sgimgui_attachments_created(ctx, atts_id, _sgimgui_slot_index(atts_id.id), desc); + } +} + +_SOKOL_PRIVATE void _sgimgui_uninit_buffer(sg_buffer buf, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_UNINIT_BUFFER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.uninit_buffer.buffer = buf; + } + if (ctx->hooks.uninit_buffer) { + ctx->hooks.uninit_buffer(buf, ctx->hooks.user_data); + } + if (buf.id != SG_INVALID_ID) { + _sgimgui_buffer_destroyed(ctx, _sgimgui_slot_index(buf.id)); + } +} + +_SOKOL_PRIVATE void _sgimgui_uninit_image(sg_image img, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_UNINIT_IMAGE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.uninit_image.image = img; + } + if (ctx->hooks.uninit_image) { + ctx->hooks.uninit_image(img, ctx->hooks.user_data); + } + if (img.id != SG_INVALID_ID) { + _sgimgui_image_destroyed(ctx, _sgimgui_slot_index(img.id)); + } +} + +_SOKOL_PRIVATE void _sgimgui_uninit_sampler(sg_sampler smp, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_UNINIT_SAMPLER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.uninit_sampler.sampler = smp; + } + if (ctx->hooks.uninit_sampler) { + ctx->hooks.uninit_sampler(smp, ctx->hooks.user_data); + } + if (smp.id != SG_INVALID_ID) { + _sgimgui_sampler_destroyed(ctx, _sgimgui_slot_index(smp.id)); + } +} + +_SOKOL_PRIVATE void _sgimgui_uninit_shader(sg_shader shd, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_UNINIT_SHADER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.uninit_shader.shader = shd; + } + if (ctx->hooks.uninit_shader) { + ctx->hooks.uninit_shader(shd, ctx->hooks.user_data); + } + if (shd.id != SG_INVALID_ID) { + _sgimgui_shader_destroyed(ctx, _sgimgui_slot_index(shd.id)); + } +} + +_SOKOL_PRIVATE void _sgimgui_uninit_pipeline(sg_pipeline pip, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_UNINIT_PIPELINE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.uninit_pipeline.pipeline = pip; + } + if (ctx->hooks.uninit_pipeline) { + ctx->hooks.uninit_pipeline(pip, ctx->hooks.user_data); + } + if (pip.id != SG_INVALID_ID) { + _sgimgui_pipeline_destroyed(ctx, _sgimgui_slot_index(pip.id)); + } +} + +_SOKOL_PRIVATE void _sgimgui_uninit_attachments(sg_attachments atts, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_UNINIT_PIPELINE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.uninit_attachments.attachments = atts; + } + if (ctx->hooks.uninit_attachments) { + ctx->hooks.uninit_attachments(atts, ctx->hooks.user_data); + } + if (atts.id != SG_INVALID_ID) { + _sgimgui_attachments_destroyed(ctx, _sgimgui_slot_index(atts.id)); + } +} + +_SOKOL_PRIVATE void _sgimgui_fail_buffer(sg_buffer buf_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_FAIL_BUFFER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.fail_buffer.buffer = buf_id; + } + if (ctx->hooks.fail_buffer) { + ctx->hooks.fail_buffer(buf_id, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_fail_image(sg_image img_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_FAIL_IMAGE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.fail_image.image = img_id; + } + if (ctx->hooks.fail_image) { + ctx->hooks.fail_image(img_id, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_fail_sampler(sg_sampler smp_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_FAIL_SAMPLER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.fail_sampler.sampler = smp_id; + } + if (ctx->hooks.fail_sampler) { + ctx->hooks.fail_sampler(smp_id, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_fail_shader(sg_shader shd_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_FAIL_SHADER; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.fail_shader.shader = shd_id; + } + if (ctx->hooks.fail_shader) { + ctx->hooks.fail_shader(shd_id, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_fail_pipeline(sg_pipeline pip_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_FAIL_PIPELINE; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.fail_pipeline.pipeline = pip_id; + } + if (ctx->hooks.fail_pipeline) { + ctx->hooks.fail_pipeline(pip_id, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_fail_attachments(sg_attachments atts_id, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_FAIL_ATTACHMENTS; + item->color = _SGIMGUI_COLOR_RSRC; + item->args.fail_attachments.attachments = atts_id; + } + if (ctx->hooks.fail_attachments) { + ctx->hooks.fail_attachments(atts_id, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_push_debug_group(const char* name, void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + if (0 == strcmp(name, "sokol-imgui")) { + ctx->frame_stats_window.in_sokol_imgui = true; + if (ctx->frame_stats_window.disable_sokol_imgui_stats) { + sg_disable_frame_stats(); + } + } + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_PUSH_DEBUG_GROUP; + item->color = _SGIMGUI_COLOR_OTHER; + item->args.push_debug_group.name = _sgimgui_make_str(name); + } + if (ctx->hooks.push_debug_group) { + ctx->hooks.push_debug_group(name, ctx->hooks.user_data); + } +} + +_SOKOL_PRIVATE void _sgimgui_pop_debug_group(void* user_data) { + sgimgui_t* ctx = (sgimgui_t*) user_data; + SOKOL_ASSERT(ctx); + if (ctx->frame_stats_window.in_sokol_imgui) { + ctx->frame_stats_window.in_sokol_imgui = false; + if (ctx->frame_stats_window.disable_sokol_imgui_stats) { + sg_enable_frame_stats(); + } + } + sgimgui_capture_item_t* item = _sgimgui_capture_next_write_item(ctx); + if (item) { + item->cmd = SGIMGUI_CMD_POP_DEBUG_GROUP; + item->color = _SGIMGUI_COLOR_OTHER; + } + if (ctx->hooks.pop_debug_group) { + ctx->hooks.pop_debug_group(ctx->hooks.user_data); + } +} + +/*--- IMGUI HELPERS ----------------------------------------------------------*/ +_SOKOL_PRIVATE bool _sgimgui_draw_resid_list_item(uint32_t res_id, const char* label, bool selected) { + igPushID_Int((int)res_id); + bool res; + if (label[0]) { + res = igSelectable_Bool(label, selected, 0, IMVEC2(0,0)); + } else { + sgimgui_str_t str; + _sgimgui_snprintf(&str, "0x%08X", res_id); + res = igSelectable_Bool(str.buf, selected, 0, IMVEC2(0,0)); + } + igPopID(); + return res; +} + +_SOKOL_PRIVATE bool _sgimgui_draw_resid_link(uint32_t res_type, uint32_t res_id, const char* label) { + SOKOL_ASSERT(label); + sgimgui_str_t str_buf; + const char* str; + if (label[0]) { + str = label; + } else { + _sgimgui_snprintf(&str_buf, "0x%08X", res_id); + str = str_buf.buf; + } + igPushID_Int((int)((res_type<<24)|res_id)); + bool res = igSmallButton(str); + igPopID(); + return res; +} + +_SOKOL_PRIVATE bool _sgimgui_draw_buffer_link(sgimgui_t* ctx, sg_buffer buf) { + bool retval = false; + if (buf.id != SG_INVALID_ID) { + const sgimgui_buffer_t* buf_ui = &ctx->buffer_window.slots[_sgimgui_slot_index(buf.id)]; + retval = _sgimgui_draw_resid_link(1, buf.id, buf_ui->label.buf); + } + return retval; +} + +_SOKOL_PRIVATE bool _sgimgui_draw_image_link(sgimgui_t* ctx, sg_image img) { + bool retval = false; + if (img.id != SG_INVALID_ID) { + const sgimgui_image_t* img_ui = &ctx->image_window.slots[_sgimgui_slot_index(img.id)]; + retval = _sgimgui_draw_resid_link(2, img.id, img_ui->label.buf); + } + return retval; +} + +_SOKOL_PRIVATE bool _sgimgui_draw_sampler_link(sgimgui_t* ctx, sg_sampler smp) { + bool retval = false; + if (smp.id != SG_INVALID_ID) { + const sgimgui_sampler_t* smp_ui = &ctx->sampler_window.slots[_sgimgui_slot_index(smp.id)]; + retval = _sgimgui_draw_resid_link(2, smp.id, smp_ui->label.buf); + } + return retval; +} + +_SOKOL_PRIVATE bool _sgimgui_draw_shader_link(sgimgui_t* ctx, sg_shader shd) { + bool retval = false; + if (shd.id != SG_INVALID_ID) { + const sgimgui_shader_t* shd_ui = &ctx->shader_window.slots[_sgimgui_slot_index(shd.id)]; + retval = _sgimgui_draw_resid_link(3, shd.id, shd_ui->label.buf); + } + return retval; +} + +_SOKOL_PRIVATE void _sgimgui_show_buffer(sgimgui_t* ctx, sg_buffer buf) { + ctx->buffer_window.open = true; + ctx->buffer_window.sel_buf = buf; +} + +_SOKOL_PRIVATE void _sgimgui_show_image(sgimgui_t* ctx, sg_image img) { + ctx->image_window.open = true; + ctx->image_window.sel_img = img; +} + +_SOKOL_PRIVATE void _sgimgui_show_sampler(sgimgui_t* ctx, sg_sampler smp) { + ctx->sampler_window.open = true; + ctx->sampler_window.sel_smp = smp; +} + +_SOKOL_PRIVATE void _sgimgui_show_shader(sgimgui_t* ctx, sg_shader shd) { + ctx->shader_window.open = true; + ctx->shader_window.sel_shd = shd; +} + +_SOKOL_PRIVATE void _sgimgui_draw_buffer_list(sgimgui_t* ctx) { + igBeginChild_Str("buffer_list", IMVEC2(_SGIMGUI_LIST_WIDTH,0), true, 0); + for (int i = 0; i < ctx->buffer_window.num_slots; i++) { + sg_buffer buf = ctx->buffer_window.slots[i].res_id; + sg_resource_state state = sg_query_buffer_state(buf); + if ((state != SG_RESOURCESTATE_INVALID) && (state != SG_RESOURCESTATE_INITIAL)) { + bool selected = ctx->buffer_window.sel_buf.id == buf.id; + if (_sgimgui_draw_resid_list_item(buf.id, ctx->buffer_window.slots[i].label.buf, selected)) { + ctx->buffer_window.sel_buf.id = buf.id; + } + } + } + igEndChild(); +} + +_SOKOL_PRIVATE void _sgimgui_draw_image_list(sgimgui_t* ctx) { + igBeginChild_Str("image_list", IMVEC2(_SGIMGUI_LIST_WIDTH,0), true, 0); + for (int i = 0; i < ctx->image_window.num_slots; i++) { + sg_image img = ctx->image_window.slots[i].res_id; + sg_resource_state state = sg_query_image_state(img); + if ((state != SG_RESOURCESTATE_INVALID) && (state != SG_RESOURCESTATE_INITIAL)) { + bool selected = ctx->image_window.sel_img.id == img.id; + if (_sgimgui_draw_resid_list_item(img.id, ctx->image_window.slots[i].label.buf, selected)) { + ctx->image_window.sel_img.id = img.id; + } + } + } + igEndChild(); +} + +_SOKOL_PRIVATE void _sgimgui_draw_sampler_list(sgimgui_t* ctx) { + igBeginChild_Str("sampler_list", IMVEC2(_SGIMGUI_LIST_WIDTH,0), true, 0); + for (int i = 0; i < ctx->sampler_window.num_slots; i++) { + sg_sampler smp = ctx->sampler_window.slots[i].res_id; + sg_resource_state state = sg_query_sampler_state(smp); + if ((state != SG_RESOURCESTATE_INVALID) && (state != SG_RESOURCESTATE_INITIAL)) { + bool selected = ctx->sampler_window.sel_smp.id == smp.id; + if (_sgimgui_draw_resid_list_item(smp.id, ctx->sampler_window.slots[i].label.buf, selected)) { + ctx->sampler_window.sel_smp.id = smp.id; + } + } + } + igEndChild(); +} + +_SOKOL_PRIVATE void _sgimgui_draw_shader_list(sgimgui_t* ctx) { + igBeginChild_Str("shader_list", IMVEC2(_SGIMGUI_LIST_WIDTH,0), true, 0); + for (int i = 0; i < ctx->shader_window.num_slots; i++) { + sg_shader shd = ctx->shader_window.slots[i].res_id; + sg_resource_state state = sg_query_shader_state(shd); + if ((state != SG_RESOURCESTATE_INVALID) && (state != SG_RESOURCESTATE_INITIAL)) { + bool selected = ctx->shader_window.sel_shd.id == shd.id; + if (_sgimgui_draw_resid_list_item(shd.id, ctx->shader_window.slots[i].label.buf, selected)) { + ctx->shader_window.sel_shd.id = shd.id; + } + } + } + igEndChild(); +} + +_SOKOL_PRIVATE void _sgimgui_draw_pipeline_list(sgimgui_t* ctx) { + igBeginChild_Str("pipeline_list", IMVEC2(_SGIMGUI_LIST_WIDTH,0), true, 0); + for (int i = 1; i < ctx->pipeline_window.num_slots; i++) { + sg_pipeline pip = ctx->pipeline_window.slots[i].res_id; + sg_resource_state state = sg_query_pipeline_state(pip); + if ((state != SG_RESOURCESTATE_INVALID) && (state != SG_RESOURCESTATE_INITIAL)) { + bool selected = ctx->pipeline_window.sel_pip.id == pip.id; + if (_sgimgui_draw_resid_list_item(pip.id, ctx->pipeline_window.slots[i].label.buf, selected)) { + ctx->pipeline_window.sel_pip.id = pip.id; + } + } + } + igEndChild(); +} + +_SOKOL_PRIVATE void _sgimgui_draw_attachments_list(sgimgui_t* ctx) { + igBeginChild_Str("pass_list", IMVEC2(_SGIMGUI_LIST_WIDTH,0), true, 0); + for (int i = 1; i < ctx->attachments_window.num_slots; i++) { + sg_attachments atts = ctx->attachments_window.slots[i].res_id; + sg_resource_state state = sg_query_attachments_state(atts); + if ((state != SG_RESOURCESTATE_INVALID) && (state != SG_RESOURCESTATE_INITIAL)) { + bool selected = ctx->attachments_window.sel_atts.id == atts.id; + if (_sgimgui_draw_resid_list_item(atts.id, ctx->attachments_window.slots[i].label.buf, selected)) { + ctx->attachments_window.sel_atts.id = atts.id; + } + } + } + igEndChild(); +} + +_SOKOL_PRIVATE void _sgimgui_draw_capture_list(sgimgui_t* ctx) { + igBeginChild_Str("capture_list", IMVEC2(_SGIMGUI_LIST_WIDTH,0), true, 0); + const int num_items = _sgimgui_capture_num_read_items(ctx); + uint64_t group_stack = 1; /* bit set: group unfolded, cleared: folded */ + for (int i = 0; i < num_items; i++) { + const sgimgui_capture_item_t* item = _sgimgui_capture_read_item_at(ctx, i); + sgimgui_str_t item_string = _sgimgui_capture_item_string(ctx, i, item); + igPushStyleColor_U32(ImGuiCol_Text, item->color); + igPushID_Int(i); + if (item->cmd == SGIMGUI_CMD_PUSH_DEBUG_GROUP) { + if (group_stack & 1) { + group_stack <<= 1; + const char* group_name = item->args.push_debug_group.name.buf; + if (igTreeNode_StrStr(group_name, "Group: %s", group_name)) { + group_stack |= 1; + } + } else { + group_stack <<= 1; + } + } else if (item->cmd == SGIMGUI_CMD_POP_DEBUG_GROUP) { + if (group_stack & 1) { + igTreePop(); + } + group_stack >>= 1; + } else if (group_stack & 1) { + if (igSelectable_Bool(item_string.buf, ctx->capture_window.sel_item == i, 0, IMVEC2(0,0))) { + ctx->capture_window.sel_item = i; + } + if (igIsItemHovered(0)) { + igSetTooltip("%s", item_string.buf); + } + } + igPopID(); + igPopStyleColor(1); + } + igEndChild(); +} + +_SOKOL_PRIVATE void _sgimgui_draw_buffer_panel(sgimgui_t* ctx, sg_buffer buf) { + if (buf.id != SG_INVALID_ID) { + igBeginChild_Str("buffer", IMVEC2(0,0), false, 0); + sg_buffer_info info = sg_query_buffer_info(buf); + if (info.slot.state == SG_RESOURCESTATE_VALID) { + const sgimgui_buffer_t* buf_ui = &ctx->buffer_window.slots[_sgimgui_slot_index(buf.id)]; + igText("Label: %s", buf_ui->label.buf[0] ? buf_ui->label.buf : "---"); + _sgimgui_draw_resource_slot(&info.slot); + igSeparator(); + igText("Type: %s", _sgimgui_buffertype_string(buf_ui->desc.type)); + igText("Usage: %s", _sgimgui_usage_string(buf_ui->desc.usage)); + igText("Size: %d", buf_ui->desc.size); + if (buf_ui->desc.usage != SG_USAGE_IMMUTABLE) { + igSeparator(); + igText("Num Slots: %d", info.num_slots); + igText("Active Slot: %d", info.active_slot); + igText("Update Frame Index: %d", info.update_frame_index); + igText("Append Frame Index: %d", info.append_frame_index); + igText("Append Pos: %d", info.append_pos); + igText("Append Overflow: %s", _sgimgui_bool_string(info.append_overflow)); + } + } else { + igText("Buffer 0x%08X not valid.", buf.id); + } + igEndChild(); + } +} + +_SOKOL_PRIVATE bool _sgimgui_image_renderable(sg_image_type type, sg_pixel_format fmt, int sample_count) { + return (type == SG_IMAGETYPE_2D) + && sg_query_pixelformat(fmt).sample + && sample_count == 1; +} + +_SOKOL_PRIVATE void _sgimgui_draw_embedded_image(sgimgui_t* ctx, sg_image img, float* scale) { + if (sg_query_image_state(img) == SG_RESOURCESTATE_VALID) { + sgimgui_image_t* img_ui = &ctx->image_window.slots[_sgimgui_slot_index(img.id)]; + if (_sgimgui_image_renderable(img_ui->desc.type, img_ui->desc.pixel_format, img_ui->desc.sample_count)) { + igPushID_Int((int)img.id); + igSliderFloat("Scale", scale, 0.125f, 8.0f, "%.3f", ImGuiSliderFlags_Logarithmic); + float w = (float)img_ui->desc.width * (*scale); + float h = (float)img_ui->desc.height * (*scale); + igImage(simgui_imtextureid(img_ui->simgui_img), IMVEC2(w, h), IMVEC2(0,0), IMVEC2(1,1), IMVEC4(1,1,1,1), IMVEC4(0,0,0,0)); + igPopID(); + } else { + igText("Image not renderable."); + } + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_image_panel(sgimgui_t* ctx, sg_image img) { + if (img.id != SG_INVALID_ID) { + igBeginChild_Str("image", IMVEC2(0,0), false, 0); + sg_image_info info = sg_query_image_info(img); + if (info.slot.state == SG_RESOURCESTATE_VALID) { + sgimgui_image_t* img_ui = &ctx->image_window.slots[_sgimgui_slot_index(img.id)]; + const sg_image_desc* desc = &img_ui->desc; + igText("Label: %s", img_ui->label.buf[0] ? img_ui->label.buf : "---"); + _sgimgui_draw_resource_slot(&info.slot); + igSeparator(); + _sgimgui_draw_embedded_image(ctx, img, &img_ui->ui_scale); + igSeparator(); + igText("Type: %s", _sgimgui_imagetype_string(desc->type)); + igText("Usage: %s", _sgimgui_usage_string(desc->usage)); + igText("Render Target: %s", _sgimgui_bool_string(desc->render_target)); + igText("Width: %d", desc->width); + igText("Height: %d", desc->height); + igText("Num Slices: %d", desc->num_slices); + igText("Num Mipmaps: %d", desc->num_mipmaps); + igText("Pixel Format: %s", _sgimgui_pixelformat_string(desc->pixel_format)); + igText("Sample Count: %d", desc->sample_count); + if (desc->usage != SG_USAGE_IMMUTABLE) { + igSeparator(); + igText("Num Slots: %d", info.num_slots); + igText("Active Slot: %d", info.active_slot); + igText("Update Frame Index: %d", info.upd_frame_index); + } + } else { + igText("Image 0x%08X not valid.", img.id); + } + igEndChild(); + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_sampler_panel(sgimgui_t* ctx, sg_sampler smp) { + if (smp.id != SG_INVALID_ID) { + igBeginChild_Str("sampler", IMVEC2(0,0), false, 0); + sg_sampler_info info = sg_query_sampler_info(smp); + if (info.slot.state == SG_RESOURCESTATE_VALID) { + sgimgui_sampler_t* smp_ui = &ctx->sampler_window.slots[_sgimgui_slot_index(smp.id)]; + const sg_sampler_desc* desc = &smp_ui->desc; + igText("Label: %s", smp_ui->label.buf[0] ? smp_ui->label.buf : "---"); + _sgimgui_draw_resource_slot(&info.slot); + igSeparator(); + igText("Min Filter: %s", _sgimgui_filter_string(desc->min_filter)); + igText("Mag Filter: %s", _sgimgui_filter_string(desc->mag_filter)); + igText("Mipmap Filter: %s", _sgimgui_filter_string(desc->mipmap_filter)); + igText("Wrap U: %s", _sgimgui_wrap_string(desc->wrap_u)); + igText("Wrap V: %s", _sgimgui_wrap_string(desc->wrap_v)); + igText("Wrap W: %s", _sgimgui_wrap_string(desc->wrap_w)); + igText("Min LOD: %.3f", desc->min_lod); + igText("Max LOD: %.3f", desc->max_lod); + igText("Border Color: %s", _sgimgui_bordercolor_string(desc->border_color)); + igText("Compare: %s", _sgimgui_comparefunc_string(desc->compare)); + igText("Max Anisotropy: %d", desc->max_anisotropy); + } else { + igText("Sampler 0x%08X not valid.", smp.id); + } + igEndChild(); + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_shader_stage(const sg_shader_stage_desc* stage) { + int num_valid_ubs = 0; + for (int i = 0; i < SG_MAX_SHADERSTAGE_UBS; i++) { + const sg_shader_uniform_block_desc* ub = &stage->uniform_blocks[i]; + for (int j = 0; j < SG_MAX_UB_MEMBERS; j++) { + const sg_shader_uniform_desc* u = &ub->uniforms[j]; + if (SG_UNIFORMTYPE_INVALID != u->type) { + num_valid_ubs++; + break; + } + } + } + int num_valid_images = 0; + for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { + if (stage->images[i].used) { + num_valid_images++; + } else { + break; + } + } + int num_valid_samplers = 0; + for (int i = 0; i < SG_MAX_SHADERSTAGE_SAMPLERS; i++) { + if (stage->samplers[i].used) { + num_valid_samplers++; + } else { + break; + } + } + int num_valid_image_sampler_pairs = 0; + for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGESAMPLERPAIRS; i++) { + if (stage->image_sampler_pairs[i].used) { + num_valid_image_sampler_pairs++; + } else { + break; + } + } + if (num_valid_ubs > 0) { + if (igTreeNode_Str("Uniform Blocks")) { + for (int i = 0; i < num_valid_ubs; i++) { + const sg_shader_uniform_block_desc* ub = &stage->uniform_blocks[i]; + igText("#%d: (size: %d layout: %s)\n", i, ub->size, _sgimgui_uniformlayout_string(ub->layout)); + for (int j = 0; j < SG_MAX_UB_MEMBERS; j++) { + const sg_shader_uniform_desc* u = &ub->uniforms[j]; + if (SG_UNIFORMTYPE_INVALID != u->type) { + if (u->array_count <= 1) { + igText(" %s %s", _sgimgui_uniformtype_string(u->type), u->name ? u->name : ""); + } else { + igText(" %s[%d] %s", _sgimgui_uniformtype_string(u->type), u->array_count, u->name ? u->name : ""); + } + } + } + } + igTreePop(); + } + } + if (num_valid_images > 0) { + if (igTreeNode_Str("Images")) { + for (int i = 0; i < num_valid_images; i++) { + const sg_shader_image_desc* sid = &stage->images[i]; + igText("slot: %d\n multisampled: %s\n image_type: %s\n sample_type: %s", + i, + sid->multisampled ? "true" : "false", + _sgimgui_imagetype_string(sid->image_type), + _sgimgui_imagesampletype_string(sid->sample_type)); + } + igTreePop(); + } + } + if (num_valid_samplers > 0) { + if (igTreeNode_Str("Samplers")) { + for (int i = 0; i < num_valid_samplers; i++) { + const sg_shader_sampler_desc* ssd = &stage->samplers[i]; + igText("slot: %d\n sampler_type: %s", i, _sgimgui_samplertype_string(ssd->sampler_type)); + } + igTreePop(); + } + } + if (num_valid_image_sampler_pairs > 0) { + if (igTreeNode_Str("Image Sampler Pairs")) { + for (int i = 0; i < num_valid_image_sampler_pairs; i++) { + const sg_shader_image_sampler_pair_desc* sispd = &stage->image_sampler_pairs[i]; + igText("slot: %d\n image_slot: %d\n sampler_slot: %d\n glsl_name: %s\n", + i, + sispd->image_slot, + sispd->sampler_slot, + sispd->glsl_name ? sispd->glsl_name : "---"); + } + igTreePop(); + } + } + if (stage->entry) { + igText("Entry: %s", stage->entry); + } + if (stage->d3d11_target) { + igText("D3D11 Target: %s", stage->d3d11_target); + } + if (stage->source) { + if (igTreeNode_Str("Source")) { + igText("%s", stage->source); + igTreePop(); + } + } else if (stage->bytecode.ptr) { + if (igTreeNode_Str("Byte Code")) { + igText("Byte-code display currently not supported."); + igTreePop(); + } + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_shader_panel(sgimgui_t* ctx, sg_shader shd) { + if (shd.id != SG_INVALID_ID) { + igBeginChild_Str("shader", IMVEC2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar); + sg_shader_info info = sg_query_shader_info(shd); + if (info.slot.state == SG_RESOURCESTATE_VALID) { + const sgimgui_shader_t* shd_ui = &ctx->shader_window.slots[_sgimgui_slot_index(shd.id)]; + igText("Label: %s", shd_ui->label.buf[0] ? shd_ui->label.buf : "---"); + _sgimgui_draw_resource_slot(&info.slot); + igSeparator(); + if (igTreeNode_Str("Attrs")) { + for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + const sg_shader_attr_desc* a_desc = &shd_ui->desc.attrs[i]; + if (a_desc->name || a_desc->sem_index) { + igText("#%d:", i); + igText(" Name: %s", a_desc->name ? a_desc->name : "---"); + igText(" Sem Name: %s", a_desc->sem_name ? a_desc->sem_name : "---"); + igText(" Sem Index: %d", a_desc->sem_index); + } + } + igTreePop(); + } + if (igTreeNode_Str("Vertex Shader Stage")) { + _sgimgui_draw_shader_stage(&shd_ui->desc.vs); + igTreePop(); + } + if (igTreeNode_Str("Fragment Shader Stage")) { + _sgimgui_draw_shader_stage(&shd_ui->desc.fs); + igTreePop(); + } + } else { + igText("Shader 0x%08X not valid!", shd.id); + } + igEndChild(); + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_vertex_layout_state(const sg_vertex_layout_state* layout) { + if (igTreeNode_Str("Buffers")) { + for (int i = 0; i < SG_MAX_VERTEX_BUFFERS; i++) { + const sg_vertex_buffer_layout_state* l_state = &layout->buffers[i]; + if (l_state->stride > 0) { + igText("#%d:", i); + igText(" Stride: %d", l_state->stride); + igText(" Step Func: %s", _sgimgui_vertexstep_string(l_state->step_func)); + igText(" Step Rate: %d", l_state->step_rate); + } + } + igTreePop(); + } + if (igTreeNode_Str("Attrs")) { + for (int i = 0; i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + const sg_vertex_attr_state* a_state = &layout->attrs[i]; + if (a_state->format != SG_VERTEXFORMAT_INVALID) { + igText("#%d:", i); + igText(" Format: %s", _sgimgui_vertexformat_string(a_state->format)); + igText(" Offset: %d", a_state->offset); + igText(" Buffer Index: %d", a_state->buffer_index); + } + } + igTreePop(); + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_stencil_face_state(const sg_stencil_face_state* sfs) { + igText("Fail Op: %s", _sgimgui_stencilop_string(sfs->fail_op)); + igText("Depth Fail Op: %s", _sgimgui_stencilop_string(sfs->depth_fail_op)); + igText("Pass Op: %s", _sgimgui_stencilop_string(sfs->pass_op)); + igText("Compare: %s", _sgimgui_comparefunc_string(sfs->compare)); +} + +_SOKOL_PRIVATE void _sgimgui_draw_stencil_state(const sg_stencil_state* ss) { + igText("Enabled: %s", _sgimgui_bool_string(ss->enabled)); + igText("Read Mask: 0x%02X", ss->read_mask); + igText("Write Mask: 0x%02X", ss->write_mask); + igText("Ref: 0x%02X", ss->ref); + if (igTreeNode_Str("Front")) { + _sgimgui_draw_stencil_face_state(&ss->front); + igTreePop(); + } + if (igTreeNode_Str("Back")) { + _sgimgui_draw_stencil_face_state(&ss->back); + igTreePop(); + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_depth_state(const sg_depth_state* ds) { + igText("Pixel Format: %s", _sgimgui_pixelformat_string(ds->pixel_format)); + igText("Compare: %s", _sgimgui_comparefunc_string(ds->compare)); + igText("Write Enabled: %s", _sgimgui_bool_string(ds->write_enabled)); + igText("Bias: %f", ds->bias); + igText("Bias Slope: %f", ds->bias_slope_scale); + igText("Bias Clamp: %f", ds->bias_clamp); +} + +_SOKOL_PRIVATE void _sgimgui_draw_blend_state(const sg_blend_state* bs) { + igText("Blend Enabled: %s", _sgimgui_bool_string(bs->enabled)); + igText("Src Factor RGB: %s", _sgimgui_blendfactor_string(bs->src_factor_rgb)); + igText("Dst Factor RGB: %s", _sgimgui_blendfactor_string(bs->dst_factor_rgb)); + igText("Op RGB: %s", _sgimgui_blendop_string(bs->op_rgb)); + igText("Src Factor Alpha: %s", _sgimgui_blendfactor_string(bs->src_factor_alpha)); + igText("Dst Factor Alpha: %s", _sgimgui_blendfactor_string(bs->dst_factor_alpha)); + igText("Op Alpha: %s", _sgimgui_blendop_string(bs->op_alpha)); +} + +_SOKOL_PRIVATE void _sgimgui_draw_color_target_state(const sg_color_target_state* cs) { + igText("Pixel Format: %s", _sgimgui_pixelformat_string(cs->pixel_format)); + igText("Write Mask: %s", _sgimgui_colormask_string(cs->write_mask)); + if (igTreeNode_Str("Blend State:")) { + _sgimgui_draw_blend_state(&cs->blend); + igTreePop(); + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_pipeline_panel(sgimgui_t* ctx, sg_pipeline pip) { + if (pip.id != SG_INVALID_ID) { + igBeginChild_Str("pipeline", IMVEC2(0,0), false, 0); + sg_pipeline_info info = sg_query_pipeline_info(pip); + if (info.slot.state == SG_RESOURCESTATE_VALID) { + const sgimgui_pipeline_t* pip_ui = &ctx->pipeline_window.slots[_sgimgui_slot_index(pip.id)]; + igText("Label: %s", pip_ui->label.buf[0] ? pip_ui->label.buf : "---"); + _sgimgui_draw_resource_slot(&info.slot); + igSeparator(); + igText("Shader: "); igSameLine(0,-1); + if (_sgimgui_draw_shader_link(ctx, pip_ui->desc.shader)) { + _sgimgui_show_shader(ctx, pip_ui->desc.shader); + } + if (igTreeNode_Str("Vertex Layout State")) { + _sgimgui_draw_vertex_layout_state(&pip_ui->desc.layout); + igTreePop(); + } + if (igTreeNode_Str("Depth State")) { + _sgimgui_draw_depth_state(&pip_ui->desc.depth); + igTreePop(); + } + if (igTreeNode_Str("Stencil State")) { + _sgimgui_draw_stencil_state(&pip_ui->desc.stencil); + igTreePop(); + } + igText("Color Count: %d", pip_ui->desc.color_count); + for (int i = 0; i < pip_ui->desc.color_count; i++) { + sgimgui_str_t str; + _sgimgui_snprintf(&str, "Color Target %d", i); + if (igTreeNode_Str(str.buf)) { + _sgimgui_draw_color_target_state(&pip_ui->desc.colors[i]); + igTreePop(); + } + } + igText("Prim Type: %s", _sgimgui_primitivetype_string(pip_ui->desc.primitive_type)); + igText("Index Type: %s", _sgimgui_indextype_string(pip_ui->desc.index_type)); + igText("Cull Mode: %s", _sgimgui_cullmode_string(pip_ui->desc.cull_mode)); + igText("Face Winding: %s", _sgimgui_facewinding_string(pip_ui->desc.face_winding)); + igText("Sample Count: %d", pip_ui->desc.sample_count); + sgimgui_str_t blend_color_str; + igText("Blend Color: %.3f %.3f %.3f %.3f", _sgimgui_color_string(&blend_color_str, pip_ui->desc.blend_color)); + igText("Alpha To Coverage: %s", _sgimgui_bool_string(pip_ui->desc.alpha_to_coverage_enabled)); + } else { + igText("Pipeline 0x%08X not valid.", pip.id); + } + igEndChild(); + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_attachment(sgimgui_t* ctx, const sg_attachment_desc* att, float* img_scale) { + igText(" Image: "); igSameLine(0,-1); + if (_sgimgui_draw_image_link(ctx, att->image)) { + _sgimgui_show_image(ctx, att->image); + } + igText(" Mip Level: %d", att->mip_level); + igText(" Slice: %d", att->slice); + _sgimgui_draw_embedded_image(ctx, att->image, img_scale); +} + +_SOKOL_PRIVATE void _sgimgui_draw_attachments_panel(sgimgui_t* ctx, sg_attachments atts) { + if (atts.id != SG_INVALID_ID) { + igBeginChild_Str("attachments", IMVEC2(0,0), false, 0); + sg_attachments_info info = sg_query_attachments_info(atts); + if (info.slot.state == SG_RESOURCESTATE_VALID) { + sgimgui_attachments_t* atts_ui = &ctx->attachments_window.slots[_sgimgui_slot_index(atts.id)]; + igText("Label: %s", atts_ui->label.buf[0] ? atts_ui->label.buf : "---"); + _sgimgui_draw_resource_slot(&info.slot); + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (atts_ui->desc.colors[i].image.id == SG_INVALID_ID) { + break; + } + igSeparator(); + igText("Color Image #%d:", i); + _sgimgui_draw_attachment(ctx, &atts_ui->desc.colors[i], &atts_ui->color_image_scale[i]); + } + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (atts_ui->desc.resolves[i].image.id == SG_INVALID_ID) { + break; + } + igSeparator(); + igText("Resolve Image #%d:", i); + _sgimgui_draw_attachment(ctx, &atts_ui->desc.resolves[i], &atts_ui->resolve_image_scale[i]); + } + if (atts_ui->desc.depth_stencil.image.id != SG_INVALID_ID) { + igSeparator(); + igText("Depth-Stencil Image:"); + _sgimgui_draw_attachment(ctx, &atts_ui->desc.depth_stencil, &atts_ui->ds_image_scale); + } + } else { + igText("Attachments 0x%08X not valid.", atts.id); + } + igEndChild(); + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_bindings_panel(sgimgui_t* ctx, const sg_bindings* bnd) { + for (int i = 0; i < SG_MAX_VERTEX_BUFFERS; i++) { + sg_buffer buf = bnd->vertex_buffers[i]; + if (buf.id != SG_INVALID_ID) { + igSeparator(); + igText("Vertex Buffer Slot #%d:", i); + igText(" Buffer: "); igSameLine(0,-1); + if (_sgimgui_draw_buffer_link(ctx, buf)) { + _sgimgui_show_buffer(ctx, buf); + } + igText(" Offset: %d", bnd->vertex_buffer_offsets[i]); + } else { + break; + } + } + if (bnd->index_buffer.id != SG_INVALID_ID) { + sg_buffer buf = bnd->index_buffer; + if (buf.id != SG_INVALID_ID) { + igSeparator(); + igText("Index Buffer Slot:"); + igText(" Buffer: "); igSameLine(0,-1); + if (_sgimgui_draw_buffer_link(ctx, buf)) { + _sgimgui_show_buffer(ctx, buf); + } + igText(" Offset: %d", bnd->index_buffer_offset); + } + } + for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { + sg_image img = bnd->vs.images[i]; + if (img.id != SG_INVALID_ID) { + igSeparator(); + igText("Vertex Stage Image Slot #%d:", i); + igText(" Image: "); igSameLine(0,-1); + if (_sgimgui_draw_image_link(ctx, img)) { + _sgimgui_show_image(ctx, img); + } + } else { + break; + } + } + for (int i = 0; i < SG_MAX_SHADERSTAGE_SAMPLERS; i++) { + sg_sampler smp = bnd->vs.samplers[i]; + if (smp.id != SG_INVALID_ID) { + igSeparator(); + igText("Vertex Stage Sampler Slot #%d:", i); + igText(" Sampler: "); igSameLine(0,-1); + if (_sgimgui_draw_sampler_link(ctx, smp)) { + _sgimgui_show_sampler(ctx, smp); + } + } else { + break; + } + } + for (int i = 0; i < SG_MAX_SHADERSTAGE_IMAGES; i++) { + sg_image img = bnd->fs.images[i]; + if (img.id != SG_INVALID_ID) { + igSeparator(); + igText("Fragment Stage Image Slot #%d:", i); + igText(" Image: "); igSameLine(0,-1); + if (_sgimgui_draw_image_link(ctx, img)) { + _sgimgui_show_image(ctx, img); + } + } + } + for (int i = 0; i < SG_MAX_SHADERSTAGE_SAMPLERS; i++) { + sg_sampler smp = bnd->fs.samplers[i]; + if (smp.id != SG_INVALID_ID) { + igSeparator(); + igText("Fragment Stage Sampler Slot #%d:", i); + igText(" Sampler: "); igSameLine(0,-1); + if (_sgimgui_draw_sampler_link(ctx, smp)) { + _sgimgui_show_sampler(ctx, smp); + } + } + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_uniforms_panel(sgimgui_t* ctx, const sgimgui_args_apply_uniforms_t* args) { + SOKOL_ASSERT(args->ub_index < SG_MAX_VERTEX_BUFFERS); + + /* check if all the required information for drawing the structured uniform block content + is available, otherwise just render a generic hexdump + */ + if (sg_query_pipeline_state(args->pipeline) != SG_RESOURCESTATE_VALID) { + igText("Pipeline object not valid!"); + return; + } + sgimgui_pipeline_t* pip_ui = &ctx->pipeline_window.slots[_sgimgui_slot_index(args->pipeline.id)]; + if (sg_query_shader_state(pip_ui->desc.shader) != SG_RESOURCESTATE_VALID) { + igText("Shader object not valid!"); + return; + } + sgimgui_shader_t* shd_ui = &ctx->shader_window.slots[_sgimgui_slot_index(pip_ui->desc.shader.id)]; + SOKOL_ASSERT(shd_ui->res_id.id == pip_ui->desc.shader.id); + const sg_shader_uniform_block_desc* ub_desc = (args->stage == SG_SHADERSTAGE_VS) ? + &shd_ui->desc.vs.uniform_blocks[args->ub_index] : + &shd_ui->desc.fs.uniform_blocks[args->ub_index]; + SOKOL_ASSERT(args->data_size <= ub_desc->size); + bool draw_dump = false; + if (ub_desc->uniforms[0].type == SG_UNIFORMTYPE_INVALID) { + draw_dump = true; + } + + sgimgui_capture_bucket_t* bucket = _sgimgui_capture_get_read_bucket(ctx); + SOKOL_ASSERT((args->ubuf_pos + args->data_size) <= bucket->ubuf_size); + const float* uptrf = (const float*) (bucket->ubuf + args->ubuf_pos); + const int32_t* uptri32 = (const int32_t*) uptrf; + if (!draw_dump) { + uint32_t u_off = 0; + for (int i = 0; i < SG_MAX_UB_MEMBERS; i++) { + const sg_shader_uniform_desc* ud = &ub_desc->uniforms[i]; + if (ud->type == SG_UNIFORMTYPE_INVALID) { + break; + } + int num_items = (ud->array_count > 1) ? ud->array_count : 1; + if (num_items > 1) { + igText("%d: %s %s[%d] =", i, _sgimgui_uniformtype_string(ud->type), ud->name?ud->name:"", ud->array_count); + } else { + igText("%d: %s %s =", i, _sgimgui_uniformtype_string(ud->type), ud->name?ud->name:""); + } + for (int item_index = 0; item_index < num_items; item_index++) { + const uint32_t u_size = _sgimgui_std140_uniform_size(ud->type, ud->array_count) / 4; + const uint32_t u_align = _sgimgui_std140_uniform_alignment(ud->type, ud->array_count) / 4; + u_off = _sgimgui_align_u32(u_off, u_align); + switch (ud->type) { + case SG_UNIFORMTYPE_FLOAT: + igText(" %.3f", uptrf[u_off]); + break; + case SG_UNIFORMTYPE_INT: + igText(" %d", uptri32[u_off]); + break; + case SG_UNIFORMTYPE_FLOAT2: + igText(" %.3f, %.3f", uptrf[u_off], uptrf[u_off+1]); + break; + case SG_UNIFORMTYPE_INT2: + igText(" %d, %d", uptri32[u_off], uptri32[u_off+1]); + break; + case SG_UNIFORMTYPE_FLOAT3: + igText(" %.3f, %.3f, %.3f", uptrf[u_off], uptrf[u_off+1], uptrf[u_off+2]); + break; + case SG_UNIFORMTYPE_INT3: + igText(" %d, %d, %d", uptri32[u_off], uptri32[u_off+1], uptri32[u_off+2]); + break; + case SG_UNIFORMTYPE_FLOAT4: + igText(" %.3f, %.3f, %.3f, %.3f", uptrf[u_off], uptrf[u_off+1], uptrf[u_off+2], uptrf[u_off+3]); + break; + case SG_UNIFORMTYPE_INT4: + igText(" %d, %d, %d, %d", uptri32[u_off], uptri32[u_off+1], uptri32[u_off+2], uptri32[u_off+3]); + break; + case SG_UNIFORMTYPE_MAT4: + igText(" %.3f, %.3f, %.3f, %.3f\n" + " %.3f, %.3f, %.3f, %.3f\n" + " %.3f, %.3f, %.3f, %.3f\n" + " %.3f, %.3f, %.3f, %.3f", + uptrf[u_off+0], uptrf[u_off+1], uptrf[u_off+2], uptrf[u_off+3], + uptrf[u_off+4], uptrf[u_off+5], uptrf[u_off+6], uptrf[u_off+7], + uptrf[u_off+8], uptrf[u_off+9], uptrf[u_off+10], uptrf[u_off+11], + uptrf[u_off+12], uptrf[u_off+13], uptrf[u_off+14], uptrf[u_off+15]); + break; + default: + igText("???"); + break; + } + u_off += u_size; + } + } + } else { + // FIXME: float vs int + const size_t num_floats = ub_desc->size / sizeof(float); + for (uint32_t i = 0; i < num_floats; i++) { + igText("%.3f, ", uptrf[i]); + if (((i + 1) % 4) != 0) { + igSameLine(0,-1); + } + } + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_passaction_panel(sgimgui_t* ctx, sg_attachments atts, const sg_pass_action* action) { + /* determine number of valid color attachments */ + int num_color_atts = 0; + if (SG_INVALID_ID == atts.id) { + /* a swapchain pass: one color attachment */ + num_color_atts = 1; + } else { + const sgimgui_attachments_t* atts_ui = &ctx->attachments_window.slots[_sgimgui_slot_index(atts.id)]; + for (int i = 0; i < SG_MAX_COLOR_ATTACHMENTS; i++) { + if (atts_ui->desc.colors[i].image.id != SG_INVALID_ID) { + num_color_atts++; + } + } + } + + igText("Pass Action: "); + for (int i = 0; i < num_color_atts; i++) { + const sg_color_attachment_action* c_att = &action->colors[i]; + igText(" Color Attachment %d:", i); + sgimgui_str_t color_str; + switch (c_att->load_action) { + case SG_LOADACTION_LOAD: igText(" SG_LOADACTION_LOAD"); break; + case SG_LOADACTION_DONTCARE: igText(" SG_LOADACTION_DONTCARE"); break; + case SG_LOADACTION_CLEAR: + igText(" SG_LOADACTION_CLEAR: %s", _sgimgui_color_string(&color_str, c_att->clear_value)); + break; + default: igText(" ???"); break; + } + switch (c_att->store_action) { + case SG_STOREACTION_STORE: igText(" SG_STOREACTION_STORE"); break; + case SG_STOREACTION_DONTCARE: igText(" SG_STOREACTION_DONTCARE"); break; + default: igText(" ???"); break; + } + } + const sg_depth_attachment_action* d_att = &action->depth; + igText(" Depth Attachment:"); + switch (d_att->load_action) { + case SG_LOADACTION_LOAD: igText(" SG_LOADACTION_LOAD"); break; + case SG_LOADACTION_DONTCARE: igText(" SG_LOADACTION_DONTCARE"); break; + case SG_LOADACTION_CLEAR: igText(" SG_LOADACTION_CLEAR: %.3f", d_att->clear_value); break; + default: igText(" ???"); break; + } + switch (d_att->store_action) { + case SG_STOREACTION_STORE: igText(" SG_STOREACTION_STORE"); break; + case SG_STOREACTION_DONTCARE: igText(" SG_STOREACTION_DONTCARE"); break; + default: igText(" ???"); break; + } + const sg_stencil_attachment_action* s_att = &action->stencil; + igText(" Stencil Attachment"); + switch (s_att->load_action) { + case SG_LOADACTION_LOAD: igText(" SG_LOADACTION_LOAD"); break; + case SG_LOADACTION_DONTCARE: igText(" SG_LOADACTION_DONTCARE"); break; + case SG_LOADACTION_CLEAR: igText(" SG_LOADACTION_CLEAR: 0x%02X", s_att->clear_value); break; + default: igText(" ???"); break; + } + switch (d_att->store_action) { + case SG_STOREACTION_STORE: igText(" SG_STOREACTION_STORE"); break; + case SG_STOREACTION_DONTCARE: igText(" SG_STOREACTION_DONTCARE"); break; + default: igText(" ???"); break; + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_swapchain_panel(sg_swapchain* swapchain) { + igText("Swapchain"); + igText(" Width: %d", swapchain->width); + igText(" Height: %d", swapchain->height); + igText(" Sample Count: %d", swapchain->sample_count); + igText(" Color Format: %s", _sgimgui_pixelformat_string(swapchain->color_format)); + igText(" Depth Format: %s", _sgimgui_pixelformat_string(swapchain->depth_format)); + igSeparator(); + switch (sg_query_backend()) { + case SG_BACKEND_D3D11: + igText("D3D11 Objects:"); + igText(" Render View: %p", swapchain->d3d11.render_view); + igText(" Resolve View: %p", swapchain->d3d11.resolve_view); + igText(" Depth Stencil View: %p", swapchain->d3d11.depth_stencil_view); + break; + case SG_BACKEND_WGPU: + igText("WGPU Objects:"); + igText(" Render View: %p", swapchain->wgpu.render_view); + igText(" Resolve View: %p", swapchain->wgpu.resolve_view); + igText(" Depth Stencil View: %p", swapchain->wgpu.depth_stencil_view); + break; + case SG_BACKEND_METAL_MACOS: + case SG_BACKEND_METAL_IOS: + case SG_BACKEND_METAL_SIMULATOR: + igText("Metal Objects:"); + igText(" Current Drawable: %p", swapchain->metal.current_drawable); + igText(" Depth Stencil Texture: %p", swapchain->metal.depth_stencil_texture); + igText(" MSAA Color Texture: %p", swapchain->metal.msaa_color_texture); + break; + case SG_BACKEND_GLCORE33: + case SG_BACKEND_GLES3: + igText("GL Objects:"); + igText(" Framebuffer: %d", swapchain->gl.framebuffer); + break; + default: + igText(" UNKNOWN BACKEND!"); + break; + } +} + +_SOKOL_PRIVATE void _sgimgui_draw_capture_panel(sgimgui_t* ctx) { + int sel_item_index = ctx->capture_window.sel_item; + if (sel_item_index >= _sgimgui_capture_num_read_items(ctx)) { + return; + } + sgimgui_capture_item_t* item = _sgimgui_capture_read_item_at(ctx, sel_item_index); + igBeginChild_Str("capture_item", IMVEC2(0, 0), false, 0); + igPushStyleColor_U32(ImGuiCol_Text, item->color); + igText("%s", _sgimgui_capture_item_string(ctx, sel_item_index, item).buf); + igPopStyleColor(1); + igSeparator(); + switch (item->cmd) { + case SGIMGUI_CMD_RESET_STATE_CACHE: + break; + case SGIMGUI_CMD_MAKE_BUFFER: + _sgimgui_draw_buffer_panel(ctx, item->args.make_buffer.result); + break; + case SGIMGUI_CMD_MAKE_IMAGE: + _sgimgui_draw_image_panel(ctx, item->args.make_image.result); + break; + case SGIMGUI_CMD_MAKE_SAMPLER: + _sgimgui_draw_sampler_panel(ctx, item->args.make_sampler.result); + break; + case SGIMGUI_CMD_MAKE_SHADER: + _sgimgui_draw_shader_panel(ctx, item->args.make_shader.result); + break; + case SGIMGUI_CMD_MAKE_PIPELINE: + _sgimgui_draw_pipeline_panel(ctx, item->args.make_pipeline.result); + break; + case SGIMGUI_CMD_MAKE_ATTACHMENTS: + _sgimgui_draw_attachments_panel(ctx, item->args.make_attachments.result); + break; + case SGIMGUI_CMD_DESTROY_BUFFER: + _sgimgui_draw_buffer_panel(ctx, item->args.destroy_buffer.buffer); + break; + case SGIMGUI_CMD_DESTROY_IMAGE: + _sgimgui_draw_image_panel(ctx, item->args.destroy_image.image); + break; + case SGIMGUI_CMD_DESTROY_SAMPLER: + _sgimgui_draw_sampler_panel(ctx, item->args.destroy_sampler.sampler); + break; + case SGIMGUI_CMD_DESTROY_SHADER: + _sgimgui_draw_shader_panel(ctx, item->args.destroy_shader.shader); + break; + case SGIMGUI_CMD_DESTROY_PIPELINE: + _sgimgui_draw_pipeline_panel(ctx, item->args.destroy_pipeline.pipeline); + break; + case SGIMGUI_CMD_DESTROY_ATTACHMENTS: + _sgimgui_draw_attachments_panel(ctx, item->args.destroy_attachments.attachments); + break; + case SGIMGUI_CMD_UPDATE_BUFFER: + _sgimgui_draw_buffer_panel(ctx, item->args.update_buffer.buffer); + break; + case SGIMGUI_CMD_UPDATE_IMAGE: + _sgimgui_draw_image_panel(ctx, item->args.update_image.image); + break; + case SGIMGUI_CMD_APPEND_BUFFER: + _sgimgui_draw_buffer_panel(ctx, item->args.update_buffer.buffer); + break; + case SGIMGUI_CMD_BEGIN_PASS: + _sgimgui_draw_passaction_panel(ctx, item->args.begin_pass.pass.attachments, &item->args.begin_pass.pass.action); + igSeparator(); + if (item->args.begin_pass.pass.attachments.id != SG_INVALID_ID) { + _sgimgui_draw_attachments_panel(ctx, item->args.begin_pass.pass.attachments); + } else { + _sgimgui_draw_swapchain_panel(&item->args.begin_pass.pass.swapchain); + } + break; + case SGIMGUI_CMD_APPLY_VIEWPORT: + case SGIMGUI_CMD_APPLY_SCISSOR_RECT: + break; + case SGIMGUI_CMD_APPLY_PIPELINE: + _sgimgui_draw_pipeline_panel(ctx, item->args.apply_pipeline.pipeline); + break; + case SGIMGUI_CMD_APPLY_BINDINGS: + _sgimgui_draw_bindings_panel(ctx, &item->args.apply_bindings.bindings); + break; + case SGIMGUI_CMD_APPLY_UNIFORMS: + _sgimgui_draw_uniforms_panel(ctx, &item->args.apply_uniforms); + break; + case SGIMGUI_CMD_DRAW: + case SGIMGUI_CMD_END_PASS: + case SGIMGUI_CMD_COMMIT: + break; + case SGIMGUI_CMD_ALLOC_BUFFER: + _sgimgui_draw_buffer_panel(ctx, item->args.alloc_buffer.result); + break; + case SGIMGUI_CMD_ALLOC_IMAGE: + _sgimgui_draw_image_panel(ctx, item->args.alloc_image.result); + break; + case SGIMGUI_CMD_ALLOC_SAMPLER: + _sgimgui_draw_sampler_panel(ctx, item->args.alloc_sampler.result); + break; + case SGIMGUI_CMD_ALLOC_SHADER: + _sgimgui_draw_shader_panel(ctx, item->args.alloc_shader.result); + break; + case SGIMGUI_CMD_ALLOC_PIPELINE: + _sgimgui_draw_pipeline_panel(ctx, item->args.alloc_pipeline.result); + break; + case SGIMGUI_CMD_ALLOC_ATTACHMENTS: + _sgimgui_draw_attachments_panel(ctx, item->args.alloc_attachments.result); + break; + case SGIMGUI_CMD_INIT_BUFFER: + _sgimgui_draw_buffer_panel(ctx, item->args.init_buffer.buffer); + break; + case SGIMGUI_CMD_INIT_IMAGE: + _sgimgui_draw_image_panel(ctx, item->args.init_image.image); + break; + case SGIMGUI_CMD_INIT_SAMPLER: + _sgimgui_draw_sampler_panel(ctx, item->args.init_sampler.sampler); + break; + case SGIMGUI_CMD_INIT_SHADER: + _sgimgui_draw_shader_panel(ctx, item->args.init_shader.shader); + break; + case SGIMGUI_CMD_INIT_PIPELINE: + _sgimgui_draw_pipeline_panel(ctx, item->args.init_pipeline.pipeline); + break; + case SGIMGUI_CMD_INIT_ATTACHMENTS: + _sgimgui_draw_attachments_panel(ctx, item->args.init_attachments.attachments); + break; + case SGIMGUI_CMD_FAIL_BUFFER: + _sgimgui_draw_buffer_panel(ctx, item->args.fail_buffer.buffer); + break; + case SGIMGUI_CMD_FAIL_IMAGE: + _sgimgui_draw_image_panel(ctx, item->args.fail_image.image); + break; + case SGIMGUI_CMD_FAIL_SAMPLER: + _sgimgui_draw_sampler_panel(ctx, item->args.fail_sampler.sampler); + break; + case SGIMGUI_CMD_FAIL_SHADER: + _sgimgui_draw_shader_panel(ctx, item->args.fail_shader.shader); + break; + case SGIMGUI_CMD_FAIL_PIPELINE: + _sgimgui_draw_pipeline_panel(ctx, item->args.fail_pipeline.pipeline); + break; + case SGIMGUI_CMD_FAIL_ATTACHMENTS: + _sgimgui_draw_attachments_panel(ctx, item->args.fail_attachments.attachments); + break; + default: + break; + } + igEndChild(); +} + +_SOKOL_PRIVATE void _sgimgui_draw_caps_panel(void) { + igText("Backend: %s\n\n", _sgimgui_backend_string(sg_query_backend())); + sg_features f = sg_query_features(); + igText("Features:"); + igText(" origin_top_left: %s", _sgimgui_bool_string(f.origin_top_left)); + igText(" image_clamp_to_border: %s", _sgimgui_bool_string(f.image_clamp_to_border)); + igText(" mrt_independent_blend_state: %s", _sgimgui_bool_string(f.mrt_independent_blend_state)); + igText(" mrt_independent_write_mask: %s", _sgimgui_bool_string(f.mrt_independent_write_mask)); + sg_limits l = sg_query_limits(); + igText("\nLimits:\n"); + igText(" max_image_size_2d: %d", l.max_image_size_2d); + igText(" max_image_size_cube: %d", l.max_image_size_cube); + igText(" max_image_size_3d: %d", l.max_image_size_3d); + igText(" max_image_size_array: %d", l.max_image_size_array); + igText(" max_image_array_layers: %d", l.max_image_array_layers); + igText(" max_vertex_attrs: %d", l.max_vertex_attrs); + igText(" gl_max_vertex_uniform_components: %d", l.gl_max_vertex_uniform_components); + igText(" gl_max_combined_texture_image_units: %d", l.gl_max_combined_texture_image_units); + igText("\nUsable Pixelformats:"); + for (int i = (int)(SG_PIXELFORMAT_NONE+1); i < (int)_SG_PIXELFORMAT_NUM; i++) { + sg_pixel_format fmt = (sg_pixel_format)i; + sg_pixelformat_info info = sg_query_pixelformat(fmt); + if (info.sample) { + igText(" %s: %s%s%s%s%s%s", + _sgimgui_pixelformat_string(fmt), + info.sample ? "SAMPLE ":"", + info.filter ? "FILTER ":"", + info.blend ? "BLEND ":"", + info.render ? "RENDER ":"", + info.msaa ? "MSAA ":"", + info.depth ? "DEPTH ":""); + } + } +} + +_SOKOL_PRIVATE void _sgimgui_frame_add_stats_row(const char* key, uint32_t value) { + igTableNextRow(0, 0.0f); + igTableSetColumnIndex(0); + igText(key); + igTableSetColumnIndex(1); + igText("%d", value); +} + +#define _sgimgui_frame_stats(key) _sgimgui_frame_add_stats_row(#key, stats->key) + +_SOKOL_PRIVATE void _sgimgui_draw_frame_stats_panel(sgimgui_t* ctx) { + _SOKOL_UNUSED(ctx); + igCheckbox("Ignore sokol_imgui.h", &ctx->frame_stats_window.disable_sokol_imgui_stats); + const sg_frame_stats* stats = &ctx->frame_stats_window.stats; + const ImGuiTableFlags flags = + ImGuiTableFlags_Resizable | + ImGuiTableFlags_ScrollY | + ImGuiTableFlags_SizingFixedFit | + ImGuiTableFlags_Borders; + if (igBeginTable("##frame_stats_table", 2, flags, IMVEC2(0, 0), 0)) { + igTableSetupScrollFreeze(0, 2); + igTableSetupColumn("key", ImGuiTableColumnFlags_None, 0, 0); + igTableSetupColumn("value", ImGuiTableColumnFlags_None, 0, 0); + igTableHeadersRow(); + _sgimgui_frame_stats(frame_index); + _sgimgui_frame_stats(num_passes); + _sgimgui_frame_stats(num_apply_viewport); + _sgimgui_frame_stats(num_apply_scissor_rect); + _sgimgui_frame_stats(num_apply_pipeline); + _sgimgui_frame_stats(num_apply_bindings); + _sgimgui_frame_stats(num_apply_uniforms); + _sgimgui_frame_stats(num_draw); + _sgimgui_frame_stats(num_update_buffer); + _sgimgui_frame_stats(num_append_buffer); + _sgimgui_frame_stats(num_update_image); + _sgimgui_frame_stats(size_apply_uniforms); + _sgimgui_frame_stats(size_update_buffer); + _sgimgui_frame_stats(size_append_buffer); + _sgimgui_frame_stats(size_update_image); + switch (sg_query_backend()) { + case SG_BACKEND_GLCORE33: + case SG_BACKEND_GLES3: + _sgimgui_frame_stats(gl.num_bind_buffer); + _sgimgui_frame_stats(gl.num_active_texture); + _sgimgui_frame_stats(gl.num_bind_texture); + _sgimgui_frame_stats(gl.num_bind_sampler); + _sgimgui_frame_stats(gl.num_use_program); + _sgimgui_frame_stats(gl.num_render_state); + _sgimgui_frame_stats(gl.num_vertex_attrib_pointer); + _sgimgui_frame_stats(gl.num_vertex_attrib_divisor); + _sgimgui_frame_stats(gl.num_enable_vertex_attrib_array); + _sgimgui_frame_stats(gl.num_disable_vertex_attrib_array); + _sgimgui_frame_stats(gl.num_uniform); + break; + case SG_BACKEND_WGPU: + _sgimgui_frame_stats(wgpu.uniforms.num_set_bindgroup); + _sgimgui_frame_stats(wgpu.uniforms.size_write_buffer); + _sgimgui_frame_stats(wgpu.bindings.num_set_vertex_buffer); + _sgimgui_frame_stats(wgpu.bindings.num_skip_redundant_vertex_buffer); + _sgimgui_frame_stats(wgpu.bindings.num_set_index_buffer); + _sgimgui_frame_stats(wgpu.bindings.num_skip_redundant_index_buffer); + _sgimgui_frame_stats(wgpu.bindings.num_create_bindgroup); + _sgimgui_frame_stats(wgpu.bindings.num_discard_bindgroup); + _sgimgui_frame_stats(wgpu.bindings.num_set_bindgroup); + _sgimgui_frame_stats(wgpu.bindings.num_skip_redundant_bindgroup); + _sgimgui_frame_stats(wgpu.bindings.num_bindgroup_cache_hits); + _sgimgui_frame_stats(wgpu.bindings.num_bindgroup_cache_misses); + _sgimgui_frame_stats(wgpu.bindings.num_bindgroup_cache_collisions); + _sgimgui_frame_stats(wgpu.bindings.num_bindgroup_cache_hash_vs_key_mismatch); + break; + case SG_BACKEND_METAL_MACOS: + case SG_BACKEND_METAL_IOS: + case SG_BACKEND_METAL_SIMULATOR: + _sgimgui_frame_stats(metal.idpool.num_added); + _sgimgui_frame_stats(metal.idpool.num_released); + _sgimgui_frame_stats(metal.idpool.num_garbage_collected); + _sgimgui_frame_stats(metal.pipeline.num_set_blend_color); + _sgimgui_frame_stats(metal.pipeline.num_set_cull_mode); + _sgimgui_frame_stats(metal.pipeline.num_set_front_facing_winding); + _sgimgui_frame_stats(metal.pipeline.num_set_stencil_reference_value); + _sgimgui_frame_stats(metal.pipeline.num_set_depth_bias); + _sgimgui_frame_stats(metal.pipeline.num_set_render_pipeline_state); + _sgimgui_frame_stats(metal.pipeline.num_set_depth_stencil_state); + _sgimgui_frame_stats(metal.bindings.num_set_vertex_buffer); + _sgimgui_frame_stats(metal.bindings.num_set_vertex_texture); + _sgimgui_frame_stats(metal.bindings.num_set_vertex_sampler_state); + _sgimgui_frame_stats(metal.bindings.num_set_fragment_texture); + _sgimgui_frame_stats(metal.bindings.num_set_fragment_sampler_state); + _sgimgui_frame_stats(metal.uniforms.num_set_vertex_buffer_offset); + _sgimgui_frame_stats(metal.uniforms.num_set_fragment_buffer_offset); + break; + case SG_BACKEND_D3D11: + _sgimgui_frame_stats(d3d11.pass.num_om_set_render_targets); + _sgimgui_frame_stats(d3d11.pass.num_clear_render_target_view); + _sgimgui_frame_stats(d3d11.pass.num_clear_depth_stencil_view); + _sgimgui_frame_stats(d3d11.pass.num_resolve_subresource); + _sgimgui_frame_stats(d3d11.pipeline.num_rs_set_state); + _sgimgui_frame_stats(d3d11.pipeline.num_om_set_depth_stencil_state); + _sgimgui_frame_stats(d3d11.pipeline.num_om_set_blend_state); + _sgimgui_frame_stats(d3d11.pipeline.num_ia_set_primitive_topology); + _sgimgui_frame_stats(d3d11.pipeline.num_ia_set_input_layout); + _sgimgui_frame_stats(d3d11.pipeline.num_vs_set_shader); + _sgimgui_frame_stats(d3d11.pipeline.num_vs_set_constant_buffers); + _sgimgui_frame_stats(d3d11.pipeline.num_ps_set_shader); + _sgimgui_frame_stats(d3d11.pipeline.num_ps_set_constant_buffers); + _sgimgui_frame_stats(d3d11.bindings.num_ia_set_vertex_buffers); + _sgimgui_frame_stats(d3d11.bindings.num_ia_set_index_buffer); + _sgimgui_frame_stats(d3d11.bindings.num_vs_set_shader_resources); + _sgimgui_frame_stats(d3d11.bindings.num_ps_set_shader_resources); + _sgimgui_frame_stats(d3d11.bindings.num_vs_set_samplers); + _sgimgui_frame_stats(d3d11.bindings.num_ps_set_samplers); + _sgimgui_frame_stats(d3d11.uniforms.num_update_subresource); + _sgimgui_frame_stats(d3d11.draw.num_draw_indexed_instanced); + _sgimgui_frame_stats(d3d11.draw.num_draw_indexed); + _sgimgui_frame_stats(d3d11.draw.num_draw_instanced); + _sgimgui_frame_stats(d3d11.draw.num_draw); + _sgimgui_frame_stats(d3d11.num_map); + _sgimgui_frame_stats(d3d11.num_unmap); + break; + default: break; + } + igEndTable(); + } +} + +#define _sgimgui_def(val, def) (((val) == 0) ? (def) : (val)) + +_SOKOL_PRIVATE sgimgui_desc_t _sgimgui_desc_defaults(const sgimgui_desc_t* desc) { + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + sgimgui_desc_t res = *desc; + // FIXME: any additional default overrides would go here + return res; +} + +/*--- PUBLIC FUNCTIONS -------------------------------------------------------*/ +SOKOL_API_IMPL void sgimgui_init(sgimgui_t* ctx, const sgimgui_desc_t* desc) { + SOKOL_ASSERT(ctx && desc); + _sgimgui_clear(ctx, sizeof(sgimgui_t)); + ctx->init_tag = 0xABCDABCD; + ctx->desc = _sgimgui_desc_defaults(desc); + _sgimgui_capture_init(ctx); + + /* hook into sokol_gfx functions */ + sg_trace_hooks hooks; + _sgimgui_clear(&hooks, sizeof(hooks)); + hooks.user_data = (void*) ctx; + hooks.reset_state_cache = _sgimgui_reset_state_cache; + hooks.make_buffer = _sgimgui_make_buffer; + hooks.make_image = _sgimgui_make_image; + hooks.make_sampler = _sgimgui_make_sampler; + hooks.make_shader = _sgimgui_make_shader; + hooks.make_pipeline = _sgimgui_make_pipeline; + hooks.make_attachments = _sgimgui_make_attachments; + hooks.destroy_buffer = _sgimgui_destroy_buffer; + hooks.destroy_image = _sgimgui_destroy_image; + hooks.destroy_sampler = _sgimgui_destroy_sampler; + hooks.destroy_shader = _sgimgui_destroy_shader; + hooks.destroy_pipeline = _sgimgui_destroy_pipeline; + hooks.destroy_attachments = _sgimgui_destroy_attachments; + hooks.update_buffer = _sgimgui_update_buffer; + hooks.update_image = _sgimgui_update_image; + hooks.append_buffer = _sgimgui_append_buffer; + hooks.begin_pass = _sgimgui_begin_pass; + hooks.apply_viewport = _sgimgui_apply_viewport; + hooks.apply_scissor_rect = _sgimgui_apply_scissor_rect; + hooks.apply_pipeline = _sgimgui_apply_pipeline; + hooks.apply_bindings = _sgimgui_apply_bindings; + hooks.apply_uniforms = _sgimgui_apply_uniforms; + hooks.draw = _sgimgui_draw; + hooks.end_pass = _sgimgui_end_pass; + hooks.commit = _sgimgui_commit; + hooks.alloc_buffer = _sgimgui_alloc_buffer; + hooks.alloc_image = _sgimgui_alloc_image; + hooks.alloc_sampler = _sgimgui_alloc_sampler; + hooks.alloc_shader = _sgimgui_alloc_shader; + hooks.alloc_pipeline = _sgimgui_alloc_pipeline; + hooks.alloc_attachments = _sgimgui_alloc_attachments; + hooks.dealloc_buffer = _sgimgui_dealloc_buffer; + hooks.dealloc_image = _sgimgui_dealloc_image; + hooks.dealloc_sampler = _sgimgui_dealloc_sampler; + hooks.dealloc_shader = _sgimgui_dealloc_shader; + hooks.dealloc_pipeline = _sgimgui_dealloc_pipeline; + hooks.dealloc_attachments = _sgimgui_dealloc_attachments; + hooks.init_buffer = _sgimgui_init_buffer; + hooks.init_image = _sgimgui_init_image; + hooks.init_sampler = _sgimgui_init_sampler; + hooks.init_shader = _sgimgui_init_shader; + hooks.init_pipeline = _sgimgui_init_pipeline; + hooks.init_attachments = _sgimgui_init_attachments; + hooks.uninit_buffer = _sgimgui_uninit_buffer; + hooks.uninit_image = _sgimgui_uninit_image; + hooks.uninit_sampler = _sgimgui_uninit_sampler; + hooks.uninit_shader = _sgimgui_uninit_shader; + hooks.uninit_pipeline = _sgimgui_uninit_pipeline; + hooks.uninit_attachments = _sgimgui_uninit_attachments; + hooks.fail_buffer = _sgimgui_fail_buffer; + hooks.fail_image = _sgimgui_fail_image; + hooks.fail_sampler = _sgimgui_fail_sampler; + hooks.fail_shader = _sgimgui_fail_shader; + hooks.fail_pipeline = _sgimgui_fail_pipeline; + hooks.fail_attachments = _sgimgui_fail_attachments; + hooks.push_debug_group = _sgimgui_push_debug_group; + hooks.pop_debug_group = _sgimgui_pop_debug_group; + ctx->hooks = sg_install_trace_hooks(&hooks); + + /* allocate resource debug-info slots */ + const sg_desc sgdesc = sg_query_desc(); + ctx->buffer_window.num_slots = sgdesc.buffer_pool_size; + ctx->image_window.num_slots = sgdesc.image_pool_size; + ctx->sampler_window.num_slots = sgdesc.sampler_pool_size; + ctx->shader_window.num_slots = sgdesc.shader_pool_size; + ctx->pipeline_window.num_slots = sgdesc.pipeline_pool_size; + ctx->attachments_window.num_slots = sgdesc.attachments_pool_size; + + const size_t buffer_pool_size = (size_t)ctx->buffer_window.num_slots * sizeof(sgimgui_buffer_t); + ctx->buffer_window.slots = (sgimgui_buffer_t*) _sgimgui_malloc_clear(&ctx->desc.allocator, buffer_pool_size); + + const size_t image_pool_size = (size_t)ctx->image_window.num_slots * sizeof(sgimgui_image_t); + ctx->image_window.slots = (sgimgui_image_t*) _sgimgui_malloc_clear(&ctx->desc.allocator, image_pool_size); + + const size_t sampler_pool_size = (size_t)ctx->sampler_window.num_slots * sizeof(sgimgui_sampler_t); + ctx->sampler_window.slots = (sgimgui_sampler_t*) _sgimgui_malloc_clear(&ctx->desc.allocator, sampler_pool_size); + + const size_t shader_pool_size = (size_t)ctx->shader_window.num_slots * sizeof(sgimgui_shader_t); + ctx->shader_window.slots = (sgimgui_shader_t*) _sgimgui_malloc_clear(&ctx->desc.allocator, shader_pool_size); + + const size_t pipeline_pool_size = (size_t)ctx->pipeline_window.num_slots * sizeof(sgimgui_pipeline_t); + ctx->pipeline_window.slots = (sgimgui_pipeline_t*) _sgimgui_malloc_clear(&ctx->desc.allocator, pipeline_pool_size); + + const size_t attachments_pool_size = (size_t)ctx->attachments_window.num_slots * sizeof(sgimgui_attachments_t); + ctx->attachments_window.slots = (sgimgui_attachments_t*) _sgimgui_malloc_clear(&ctx->desc.allocator, attachments_pool_size); +} + +SOKOL_API_IMPL void sgimgui_discard(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + /* restore original trace hooks */ + sg_install_trace_hooks(&ctx->hooks); + ctx->init_tag = 0; + _sgimgui_capture_discard(ctx); + if (ctx->buffer_window.slots) { + for (int i = 0; i < ctx->buffer_window.num_slots; i++) { + if (ctx->buffer_window.slots[i].res_id.id != SG_INVALID_ID) { + _sgimgui_buffer_destroyed(ctx, i); + } + } + _sgimgui_free(&ctx->desc.allocator, (void*)ctx->buffer_window.slots); + ctx->buffer_window.slots = 0; + } + if (ctx->image_window.slots) { + for (int i = 0; i < ctx->image_window.num_slots; i++) { + if (ctx->image_window.slots[i].res_id.id != SG_INVALID_ID) { + _sgimgui_image_destroyed(ctx, i); + } + } + _sgimgui_free(&ctx->desc.allocator, (void*)ctx->image_window.slots); + ctx->image_window.slots = 0; + } + if (ctx->sampler_window.slots) { + for (int i = 0; i < ctx->sampler_window.num_slots; i++) { + if (ctx->sampler_window.slots[i].res_id.id != SG_INVALID_ID) { + _sgimgui_sampler_destroyed(ctx, i); + } + } + _sgimgui_free(&ctx->desc.allocator, (void*)ctx->sampler_window.slots); + ctx->sampler_window.slots = 0; + } + if (ctx->shader_window.slots) { + for (int i = 0; i < ctx->shader_window.num_slots; i++) { + if (ctx->shader_window.slots[i].res_id.id != SG_INVALID_ID) { + _sgimgui_shader_destroyed(ctx, i); + } + } + _sgimgui_free(&ctx->desc.allocator, (void*)ctx->shader_window.slots); + ctx->shader_window.slots = 0; + } + if (ctx->pipeline_window.slots) { + for (int i = 0; i < ctx->pipeline_window.num_slots; i++) { + if (ctx->pipeline_window.slots[i].res_id.id != SG_INVALID_ID) { + _sgimgui_pipeline_destroyed(ctx, i); + } + } + _sgimgui_free(&ctx->desc.allocator, (void*)ctx->pipeline_window.slots); + ctx->pipeline_window.slots = 0; + } + if (ctx->attachments_window.slots) { + for (int i = 0; i < ctx->attachments_window.num_slots; i++) { + if (ctx->attachments_window.slots[i].res_id.id != SG_INVALID_ID) { + _sgimgui_attachments_destroyed(ctx, i); + } + } + _sgimgui_free(&ctx->desc.allocator, (void*)ctx->attachments_window.slots); + ctx->attachments_window.slots = 0; + } +} + +SOKOL_API_IMPL void sgimgui_draw(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + sgimgui_draw_buffer_window(ctx); + sgimgui_draw_image_window(ctx); + sgimgui_draw_sampler_window(ctx); + sgimgui_draw_shader_window(ctx); + sgimgui_draw_pipeline_window(ctx); + sgimgui_draw_attachments_window(ctx); + sgimgui_draw_capture_window(ctx); + sgimgui_draw_capabilities_window(ctx); + sgimgui_draw_frame_stats_window(ctx); +} + +SOKOL_API_IMPL void sgimgui_draw_menu(sgimgui_t* ctx, const char* title) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + SOKOL_ASSERT(title); + if (igBeginMenu(title, true)) { + igMenuItem_BoolPtr("Capabilities", 0, &ctx->caps_window.open, true); + igMenuItem_BoolPtr("Frame Stats", 0, &ctx->frame_stats_window.open, true); + igMenuItem_BoolPtr("Buffers", 0, &ctx->buffer_window.open, true); + igMenuItem_BoolPtr("Images", 0, &ctx->image_window.open, true); + igMenuItem_BoolPtr("Samplers", 0, &ctx->sampler_window.open, true); + igMenuItem_BoolPtr("Shaders", 0, &ctx->shader_window.open, true); + igMenuItem_BoolPtr("Pipelines", 0, &ctx->pipeline_window.open, true); + igMenuItem_BoolPtr("Attachments", 0, &ctx->attachments_window.open, true); + igMenuItem_BoolPtr("Calls", 0, &ctx->capture_window.open, true); + igEndMenu(); + } +} + +SOKOL_API_IMPL void sgimgui_draw_buffer_window(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + if (!ctx->buffer_window.open) { + return; + } + igSetNextWindowSize(IMVEC2(440, 280), ImGuiCond_Once); + if (igBegin("Buffers", &ctx->buffer_window.open, 0)) { + sgimgui_draw_buffer_window_content(ctx); + } + igEnd(); +} + +SOKOL_API_IMPL void sgimgui_draw_image_window(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + if (!ctx->image_window.open) { + return; + } + igSetNextWindowSize(IMVEC2(440, 400), ImGuiCond_Once); + if (igBegin("Images", &ctx->image_window.open, 0)) { + sgimgui_draw_image_window_content(ctx); + } + igEnd(); +} + +SOKOL_API_IMPL void sgimgui_draw_sampler_window(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + if (!ctx->sampler_window.open) { + return; + } + igSetNextWindowSize(IMVEC2(440, 400), ImGuiCond_Once); + if (igBegin("Samplers", &ctx->sampler_window.open, 0)) { + sgimgui_draw_sampler_window_content(ctx); + } + igEnd(); +} + +SOKOL_API_IMPL void sgimgui_draw_shader_window(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + if (!ctx->shader_window.open) { + return; + } + igSetNextWindowSize(IMVEC2(440, 400), ImGuiCond_Once); + if (igBegin("Shaders", &ctx->shader_window.open, 0)) { + sgimgui_draw_shader_window_content(ctx); + } + igEnd(); +} + +SOKOL_API_IMPL void sgimgui_draw_pipeline_window(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + if (!ctx->pipeline_window.open) { + return; + } + igSetNextWindowSize(IMVEC2(540, 400), ImGuiCond_Once); + if (igBegin("Pipelines", &ctx->pipeline_window.open, 0)) { + sgimgui_draw_pipeline_window_content(ctx); + } + igEnd(); +} + +SOKOL_API_IMPL void sgimgui_draw_attachments_window(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + if (!ctx->attachments_window.open) { + return; + } + igSetNextWindowSize(IMVEC2(440, 400), ImGuiCond_Once); + if (igBegin("Attachments", &ctx->attachments_window.open, 0)) { + sgimgui_draw_attachments_window_content(ctx); + } + igEnd(); +} + +SOKOL_API_IMPL void sgimgui_draw_capture_window(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + if (!ctx->capture_window.open) { + return; + } + igSetNextWindowSize(IMVEC2(640, 400), ImGuiCond_Once); + if (igBegin("Frame Capture", &ctx->capture_window.open, 0)) { + sgimgui_draw_capture_window_content(ctx); + } + igEnd(); +} + +SOKOL_API_IMPL void sgimgui_draw_capabilities_window(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + if (!ctx->caps_window.open) { + return; + } + igSetNextWindowSize(IMVEC2(440, 400), ImGuiCond_Once); + if (igBegin("Capabilities", &ctx->caps_window.open, 0)) { + sgimgui_draw_capabilities_window_content(ctx); + } + igEnd(); +} + +SOKOL_API_IMPL void sgimgui_draw_frame_stats_window(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + if (!ctx->frame_stats_window.open) { + return; + } + igSetNextWindowSize(IMVEC2(512, 400), ImGuiCond_Once); + if (igBegin("Frame Stats", &ctx->frame_stats_window.open, 0)) { + sgimgui_draw_frame_stats_window_content(ctx); + } + igEnd(); +} + +SOKOL_API_IMPL void sgimgui_draw_buffer_window_content(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + _sgimgui_draw_buffer_list(ctx); + igSameLine(0,-1); + _sgimgui_draw_buffer_panel(ctx, ctx->buffer_window.sel_buf); +} + +SOKOL_API_IMPL void sgimgui_draw_image_window_content(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + _sgimgui_draw_image_list(ctx); + igSameLine(0,-1); + _sgimgui_draw_image_panel(ctx, ctx->image_window.sel_img); +} + +SOKOL_API_IMPL void sgimgui_draw_sampler_window_content(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + _sgimgui_draw_sampler_list(ctx); + igSameLine(0,-1); + _sgimgui_draw_sampler_panel(ctx, ctx->sampler_window.sel_smp); +} + +SOKOL_API_IMPL void sgimgui_draw_shader_window_content(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + _sgimgui_draw_shader_list(ctx); + igSameLine(0,-1); + _sgimgui_draw_shader_panel(ctx, ctx->shader_window.sel_shd); +} + +SOKOL_API_IMPL void sgimgui_draw_pipeline_window_content(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + _sgimgui_draw_pipeline_list(ctx); + igSameLine(0,-1); + _sgimgui_draw_pipeline_panel(ctx, ctx->pipeline_window.sel_pip); +} + +SOKOL_API_IMPL void sgimgui_draw_attachments_window_content(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + _sgimgui_draw_attachments_list(ctx); + igSameLine(0,-1); + _sgimgui_draw_attachments_panel(ctx, ctx->attachments_window.sel_atts); +} + +SOKOL_API_IMPL void sgimgui_draw_capture_window_content(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + _sgimgui_draw_capture_list(ctx); + igSameLine(0,-1); + _sgimgui_draw_capture_panel(ctx); +} + +SOKOL_API_IMPL void sgimgui_draw_capabilities_window_content(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + _SOKOL_UNUSED(ctx); + _sgimgui_draw_caps_panel(); +} + +SOKOL_API_IMPL void sgimgui_draw_frame_stats_window_content(sgimgui_t* ctx) { + SOKOL_ASSERT(ctx && (ctx->init_tag == 0xABCDABCD)); + ctx->frame_stats_window.stats = sg_query_frame_stats(); + _sgimgui_draw_frame_stats_panel(ctx); +} + +#endif /* SOKOL_GFX_IMGUI_IMPL */ diff --git a/source/engine/thirdparty/sokol/util/sokol_gl.h b/source/engine/thirdparty/sokol/util/sokol_gl.h new file mode 100644 index 0000000..997ddea --- /dev/null +++ b/source/engine/thirdparty/sokol/util/sokol_gl.h @@ -0,0 +1,4254 @@ +#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_GLCORE33 + 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 an internal error code, to query the + current error code for the currently active context call: + + sgl_error_t sgl_error() + + ...alternatively with an explicit context argument: + + sgl_error_t sgl_context_error(ctx); + + ...which can return the following error codes: + + SGL_NO_ERROR - all OK, no error occurred since last sgl_draw() + SGL_ERROR_VERTICES_FULL - internal vertex buffer is full (checked in sgl_end()) + SGL_ERROR_UNIFORMS_FULL - the internal uniforms buffer is full (checked in sgl_end()) + SGL_ERROR_COMMANDS_FULL - the internal command buffer is full (checked in sgl_end()) + SGL_ERROR_STACK_OVERFLOW - matrix- or pipeline-stack overflow + SGL_ERROR_STACK_UNDERFLOW - matrix- or pipeline-stack underflow + SGL_ERROR_NO_CONTEXT - the active context no longer exists + + ...if sokol-gl is in an error-state, sgl_draw() will skip any rendering, + and reset the error code to SGL_NO_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 enum sgl_error_t { + SGL_NO_ERROR = 0, + SGL_ERROR_VERTICES_FULL, + SGL_ERROR_UNIFORMS_FULL, + SGL_ERROR_COMMANDS_FULL, + SGL_ERROR_STACK_OVERFLOW, + SGL_ERROR_STACK_UNDERFLOW, + SGL_ERROR_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); + +/* 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 glsl330:glsl300es:hlsl4:metal_macos:metal_ios:metal_sim:wgpu -b + + (not that for Metal and D3D11 byte code, sokol-shdc must be run + on macOS and Windows) + + @vs vs + 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 + uniform texture2D tex; + 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_GLCORE33) +static const char _sgl_vs_source_glsl330[478] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,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,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, +}; +static const char _sgl_fs_source_glsl330[180] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,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,0x69,0x6e,0x20,0x76,0x65,0x63,0x34,0x20,0x75,0x76,0x3b,0x0a, + 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) +static const char _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, +}; +static const char _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) +static const uint8_t _sgl_vs_bytecode_metal_macos[3317] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf5,0x0c,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, + 0xe0,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,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x76,0x25,0x5f,0x37,0x22,0xd0,0x3f, + 0x64,0xef,0xff,0xc3,0x45,0x1a,0x3d,0xb7,0x5e,0x83,0x13,0x96,0xd3,0x09,0xec,0x53, + 0x25,0xd5,0x7e,0x0c,0xed,0xb9,0x58,0x34,0x02,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,0xc4,0x0b,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00, + 0x00,0xee,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,0x81,0x00,0x00, + 0x00,0x1b,0xc8,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x90,0x80,0x8a,0x18,0x87,0x77, + 0x90,0x07,0x79,0x28,0x87,0x71,0xa0,0x07,0x76,0xc8,0x87,0x36,0x90,0x87,0x77,0xa8, + 0x07,0x77,0x20,0x87,0x72,0x20,0x87,0x36,0x20,0x87,0x74,0xb0,0x87,0x74,0x20,0x87, + 0x72,0x68,0x83,0x79,0x88,0x07,0x79,0xa0,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76, + 0x08,0x07,0x7a,0x40,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c, + 0xd2,0x61,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8, + 0xa1,0x0d,0xc6,0x21,0x1c,0xd8,0x81,0x1d,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 0x28,0x07,0x80,0x60,0x87,0x72,0x98,0x87,0x79,0x68,0x03,0x78,0x90,0x87,0x72,0x18, + 0x87,0x74,0x98,0x87,0x72,0x68,0x03,0x73,0x80,0x87,0x76,0x08,0x07,0x72,0x00,0xcc, + 0x21,0x1c,0xd8,0x61,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,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x06, + 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,0x68, + 0x03,0x77,0x78,0x07,0x77,0x68,0x03,0x76,0x28,0x87,0x70,0x30,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,0x40,0x1d,0xea,0xa1,0x1d,0xe0,0xa1,0x0d,0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca, + 0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36, + 0x70,0x87,0x70,0x70,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,0xe6, + 0x21,0x1d,0xce,0xc1,0x1d,0xca,0x81,0x1c,0xda,0x40,0x1f,0xca,0x41,0x1e,0xde,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,0x03,0x7a,0x90,0x87,0x70,0x80,0x07, + 0x78,0x48,0x07,0x77,0x38,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41, + 0x1e,0xea,0xa1,0x1c,0x00,0x62,0x1e,0xe8,0x21,0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e, + 0xe4,0xe1,0x1d,0xe8,0xa1,0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0x40,0x1c,0xea, + 0xc1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x0d,0xe6,0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c, + 0x00,0x88,0x7a,0x70,0x87,0x79,0x08,0x07,0x73,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea, + 0x61,0x1e,0xca,0xa1,0x0d,0xe6,0xe1,0x1d,0xcc,0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1, + 0x1d,0xc2,0x81,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x36,0x18,0x42, + 0x01,0x2c,0x40,0x05,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,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,0x34, + 0x01,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x02,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,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e, + 0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6, + 0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06, + 0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45,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,0x00,0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c, + 0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c, + 0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,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,0xa8,0x95,0x40, + 0x19,0x14,0x01,0xbd,0x11,0x00,0x1a,0x33,0x00,0x24,0x66,0x00,0x28,0xcc,0x00,0x00, + 0x00,0xe3,0x15,0x4b,0x94,0x65,0x11,0x05,0x65,0x90,0x21,0x1a,0x0c,0x13,0x02,0xf9, + 0x8c,0x57,0x3c,0x55,0xd7,0x2d,0x14,0x94,0x41,0x86,0xea,0x70,0x4c,0x08,0xe4,0x63, + 0x41,0x01,0x9f,0xf1,0x0a,0x4a,0x13,0x03,0x31,0x70,0x28,0x28,0x83,0x0c,0x1a,0x43, + 0x99,0x10,0xc8,0xc7,0x8a,0x00,0x3e,0xe3,0x15,0xd9,0x77,0x06,0x67,0x40,0x51,0x50, + 0x06,0x19,0xbe,0x48,0x33,0x21,0x90,0x8f,0x15,0x01,0x7c,0xc6,0x2b,0x3c,0x32,0x68, + 0x03,0x36,0x20,0x03,0x0a,0xca,0x20,0xc3,0x18,0x60,0x99,0x09,0x81,0x7c,0xc6,0x2b, + 0xc4,0x00,0x0d,0xe2,0x00,0x0e,0x3c,0x0a,0xca,0x20,0xc3,0x19,0x70,0x61,0x60,0x42, + 0x20,0x1f,0x0b,0x0a,0xf8,0x8c,0x57,0x9c,0x41,0x1b,0xd8,0x41,0x1d,0x88,0x01,0x05, + 0xc5,0x86,0x00,0x3e,0xb3,0x0d,0x61,0x10,0x00,0xb3,0x0d,0x41,0x1b,0x04,0xb3,0x0d, + 0xc1,0x23,0xcc,0x36,0x04,0x6e,0x30,0x64,0x10,0x10,0x03,0x00,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, +}; +static const uint8_t _sgl_fs_bytecode_metal_macos[2825] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x09,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, + 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,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0xb9,0x65,0x80,0x88,0xa2,0xc8,0x18, + 0x8d,0x2a,0x38,0xc1,0x87,0xa4,0x7f,0x35,0x83,0x69,0xdc,0x8c,0xcb,0x7b,0x11,0x67, + 0x89,0xc7,0xf0,0x8a,0x99,0x36,0x06,0xc5,0x90,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,0x10,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0x81,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,0x89,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07,0x7a,0x60,0x87,0x7c, + 0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72,0x68,0x03,0x72,0x48, + 0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90,0x07,0x7a,0x68,0x03, + 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc,0x21,0x1c,0xd8,0x61, + 0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81,0x1d,0xca,0xa1,0x0d, + 0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d,0xd8,0x61,0x1e,0x00, + 0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87,0x79,0x98,0x87,0x36, + 0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,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,0x00,0x7a,0x90, + 0x87,0x7a,0x28,0x07,0x60,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,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36,0x60,0x87,0x72,0x08, + 0x07,0x73,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,0xd4,0xa1,0x1e,0xda,0x01,0x1e,0xda,0x80,0x1e, + 0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,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,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c,0xc8,0xa1,0x0d,0xf4, + 0xa1,0x1c,0xe4,0xe1,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,0xa0, + 0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68,0x83,0x76,0x08,0x07, + 0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6,0x81,0x1e,0xc2,0x61, + 0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d, + 0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e,0xda,0x60,0x1e,0xd2, + 0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87,0x70,0x30,0x87,0x72, + 0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e, + 0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e,0xde,0xc1,0x1c,0xe8, + 0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 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,0x05,0x49,0x18,0x00,0x00,0x03,0x00,0x00, + 0x00,0x13,0x86,0x40,0x18,0x26,0x0c,0x44,0x61,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,0xa1,0x09,0x00,0x00,0x79,0x18,0x00,0x00,0xb7,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,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e, + 0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6, + 0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06, + 0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45,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,0x83,0x11,0x11,0x3b,0xb0,0x83,0x3d,0xb4,0x83,0x1b,0xb4,0xc3,0x3b,0x90, + 0x43,0x3d,0xb0,0x43,0x39,0xb8,0x81,0x39,0xb0,0x43,0x38,0x9c,0xc3,0x3c,0x4c,0x11, + 0x82,0x61,0x84,0xc2,0x0e,0xec,0x60,0x0f,0xed,0xe0,0x06,0xe9,0x40,0x0e,0xe5,0xe0, + 0x0e,0xf4,0x30,0x25,0x28,0x46,0x2c,0xe1,0x90,0x0e,0xf2,0xe0,0x06,0xf6,0x50,0x0e, + 0xf2,0x30,0x0f,0xe9,0xf0,0x0e,0xee,0x30,0x25,0x30,0x46,0x50,0xe1,0x90,0x0e,0xf2, + 0xe0,0x06,0xec,0x10,0x0e,0xee,0x70,0x0e,0xf5,0x10,0x0e,0xe7,0x50,0x0e,0xbf,0x60, + 0x0f,0xe5,0x20,0x0f,0xf3,0x90,0x0e,0xef,0xe0,0x0e,0x53,0x02,0x64,0xc4,0x14,0x0e, + 0xe9,0x20,0x0f,0x6e,0x30,0x0e,0xef,0xd0,0x0e,0xf0,0x90,0x0e,0xec,0x50,0x0e,0xbf, + 0xf0,0x0e,0xf0,0x40,0x0f,0xe9,0xf0,0x0e,0xee,0x30,0x0f,0x53,0x06,0x85,0x71,0x46, + 0x30,0xe1,0x90,0x0e,0xf2,0xe0,0x06,0xe6,0x20,0x0f,0xe1,0x70,0x0e,0xed,0x50,0x0e, + 0xee,0x40,0x0f,0x53,0x02,0x35,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59, + 0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b, + 0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,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,0x0f,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,0x42,0x40,0x29,0x49,0x50,0x20,0x86,0x60, + 0x01,0x23,0x9f,0xd9,0x06,0x23,0x00,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sgl_vs_bytecode_metal_ios[3317] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf5,0x0c,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, + 0xe0,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,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x17,0x11,0x57,0x16,0x94,0x42,0x52, + 0xfb,0x1e,0xd0,0x32,0xfd,0x87,0x16,0xb0,0xa4,0xd0,0xc2,0x43,0xbe,0x93,0x8c,0xe0, + 0x2d,0x7a,0x5c,0x3e,0x06,0x4c,0x57,0xeb,0x4b,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,0xc0,0x0b,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0,0xde,0x21,0x0c,0x00, + 0x00,0xed,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,0x82,0x00,0x00, + 0x00,0x1b,0xc8,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x90,0x80,0x8a,0x18,0x87,0x77, + 0x90,0x07,0x79,0x28,0x87,0x71,0xa0,0x07,0x76,0xc8,0x87,0x36,0x90,0x87,0x77,0xa8, + 0x07,0x77,0x20,0x87,0x72,0x20,0x87,0x36,0x20,0x87,0x74,0xb0,0x87,0x74,0x20,0x87, + 0x72,0x68,0x83,0x79,0x88,0x07,0x79,0xa0,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76, + 0x08,0x07,0x7a,0x40,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c, + 0xd2,0x61,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8, + 0xa1,0x0d,0xc6,0x21,0x1c,0xd8,0x81,0x1d,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 0x28,0x07,0x80,0x60,0x87,0x72,0x98,0x87,0x79,0x68,0x03,0x78,0x90,0x87,0x72,0x18, + 0x87,0x74,0x98,0x87,0x72,0x68,0x03,0x73,0x80,0x87,0x76,0x08,0x07,0x72,0x00,0xcc, + 0x21,0x1c,0xd8,0x61,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,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x06, + 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,0x68, + 0x03,0x77,0x78,0x07,0x77,0x68,0x03,0x76,0x28,0x87,0x70,0x30,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,0x40,0x1d,0xea,0xa1,0x1d,0xe0,0xa1,0x0d,0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca, + 0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36, + 0x70,0x87,0x70,0x70,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,0xe6, + 0x21,0x1d,0xce,0xc1,0x1d,0xca,0x81,0x1c,0xda,0x40,0x1f,0xca,0x41,0x1e,0xde,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,0x03,0x7a,0x90,0x87,0x70,0x80,0x07, + 0x78,0x48,0x07,0x77,0x38,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41, + 0x1e,0xea,0xa1,0x1c,0x00,0x62,0x1e,0xe8,0x21,0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e, + 0xe4,0xe1,0x1d,0xe8,0xa1,0x1c,0xc6,0x81,0x1e,0xde,0x41,0x1e,0xda,0x40,0x1c,0xea, + 0xc1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x0d,0xe6,0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c, + 0x00,0x88,0x7a,0x70,0x87,0x79,0x08,0x07,0x73,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea, + 0x61,0x1e,0xca,0xa1,0x0d,0xe6,0xe1,0x1d,0xcc,0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1, + 0x1d,0xc2,0x81,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x36,0x20,0x42, + 0x01,0x24,0xc0,0x02,0x54,0x00,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,0x40,0x02,0x00,0x00,0x00, + 0x00,0x79,0x18,0x00,0x00,0x02,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,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae, + 0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06, + 0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5, + 0xc6,0xe6,0xc6,0x45,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,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,0x00,0x00, + 0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4, + 0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43, + 0x3d,0xc0,0xc3,0x3c,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,0xa8,0x95,0x40,0x19,0x14,0x01,0xbd, + 0x11,0x00,0x1a,0x33,0x00,0x24,0x66,0x00,0x28,0xcc,0x00,0x00,0x00,0xe3,0x15,0x4b, + 0x94,0x65,0x11,0x05,0x65,0x90,0x21,0x1a,0x0c,0x13,0x02,0xf9,0x8c,0x57,0x3c,0x55, + 0xd7,0x2d,0x14,0x94,0x41,0x86,0xea,0x70,0x4c,0x08,0xe4,0x63,0x41,0x01,0x9f,0xf1, + 0x0a,0x4a,0x13,0x03,0x31,0x70,0x28,0x28,0x83,0x0c,0x1a,0x43,0x99,0x10,0xc8,0xc7, + 0x8a,0x00,0x3e,0xe3,0x15,0xd9,0x77,0x06,0x67,0x40,0x51,0x50,0x06,0x19,0xbe,0x48, + 0x33,0x21,0x90,0x8f,0x15,0x01,0x7c,0xc6,0x2b,0x3c,0x32,0x68,0x03,0x36,0x20,0x03, + 0x0a,0xca,0x20,0xc3,0x18,0x60,0x99,0x09,0x81,0x7c,0xc6,0x2b,0xc4,0x00,0x0d,0xe2, + 0x00,0x0e,0x3c,0x0a,0xca,0x20,0xc3,0x19,0x70,0x61,0x60,0x42,0x20,0x1f,0x0b,0x0a, + 0xf8,0x8c,0x57,0x9c,0x41,0x1b,0xd8,0x41,0x1d,0x88,0x01,0x05,0xc5,0x86,0x00,0x3e, + 0xb3,0x0d,0x61,0x10,0x00,0xb3,0x0d,0x41,0x1b,0x04,0xb3,0x0d,0xc1,0x23,0xcc,0x36, + 0x04,0x6e,0x30,0x64,0x10,0x10,0x03,0x00,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,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sgl_fs_bytecode_metal_ios[2809] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf9,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x20,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,0xc1,0x98,0x28,0x2b,0x2b,0x1f,0x36, + 0x7c,0x5c,0xb0,0x69,0x3f,0xc3,0xd1,0x80,0x4b,0xcf,0xa1,0x10,0xfc,0x19,0x31,0x58, + 0xad,0x45,0xd3,0x5a,0x81,0x72,0x0d,0x8f,0x85,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,0x08,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0x7f,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,0x89,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07,0x7a,0x60,0x87,0x7c, + 0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72,0x68,0x03,0x72,0x48, + 0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90,0x07,0x7a,0x68,0x03, + 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc,0x21,0x1c,0xd8,0x61, + 0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81,0x1d,0xca,0xa1,0x0d, + 0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d,0xd8,0x61,0x1e,0x00, + 0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87,0x79,0x98,0x87,0x36, + 0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,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,0x00,0x7a,0x90, + 0x87,0x7a,0x28,0x07,0x60,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,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36,0x60,0x87,0x72,0x08, + 0x07,0x73,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,0xd4,0xa1,0x1e,0xda,0x01,0x1e,0xda,0x80,0x1e, + 0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,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,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c,0xc8,0xa1,0x0d,0xf4, + 0xa1,0x1c,0xe4,0xe1,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,0xa0, + 0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68,0x83,0x76,0x08,0x07, + 0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6,0x81,0x1e,0xc2,0x61, + 0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d, + 0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e,0xda,0x60,0x1e,0xd2, + 0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87,0x70,0x30,0x87,0x72, + 0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e, + 0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e,0xde,0xc1,0x1c,0xe8, + 0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 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,0x05,0x49,0x18,0x00,0x00,0x03,0x00,0x00, + 0x00,0x13,0x86,0x40,0x18,0x26,0x0c,0x44,0x61,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,0x01,0x12,0x00,0x00,0x79,0x18,0x00, + 0x00,0xb7,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,0xe7,0x20,0x08, + 0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed, + 0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c, + 0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45, + 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,0x83,0x11,0x11,0x3b,0xb0,0x83,0x3d, + 0xb4,0x83,0x1b,0xb4,0xc3,0x3b,0x90,0x43,0x3d,0xb0,0x43,0x39,0xb8,0x81,0x39,0xb0, + 0x43,0x38,0x9c,0xc3,0x3c,0x4c,0x11,0x82,0x61,0x84,0xc2,0x0e,0xec,0x60,0x0f,0xed, + 0xe0,0x06,0xe9,0x40,0x0e,0xe5,0xe0,0x0e,0xf4,0x30,0x25,0x28,0x46,0x2c,0xe1,0x90, + 0x0e,0xf2,0xe0,0x06,0xf6,0x50,0x0e,0xf2,0x30,0x0f,0xe9,0xf0,0x0e,0xee,0x30,0x25, + 0x30,0x46,0x50,0xe1,0x90,0x0e,0xf2,0xe0,0x06,0xec,0x10,0x0e,0xee,0x70,0x0e,0xf5, + 0x10,0x0e,0xe7,0x50,0x0e,0xbf,0x60,0x0f,0xe5,0x20,0x0f,0xf3,0x90,0x0e,0xef,0xe0, + 0x0e,0x53,0x02,0x64,0xc4,0x14,0x0e,0xe9,0x20,0x0f,0x6e,0x30,0x0e,0xef,0xd0,0x0e, + 0xf0,0x90,0x0e,0xec,0x50,0x0e,0xbf,0xf0,0x0e,0xf0,0x40,0x0f,0xe9,0xf0,0x0e,0xee, + 0x30,0x0f,0x53,0x06,0x85,0x71,0x46,0x30,0xe1,0x90,0x0e,0xf2,0xe0,0x06,0xe6,0x20, + 0x0f,0xe1,0x70,0x0e,0xed,0x50,0x0e,0xee,0x40,0x0f,0x53,0x02,0x35,0x00,0x00,0x00, + 0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4, + 0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43, + 0x3d,0xc0,0xc3,0x3c,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,0x0f,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, + 0x42,0x40,0x29,0x49,0x50,0x20,0x86,0x60,0x01,0x23,0x9f,0xd9,0x06,0x23,0x00,0x32, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const char _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, +}; +static const char _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) +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, +}; +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) +static const char _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, +}; +static const char _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,0x33,0x32, + 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,0x34,0x38,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_GLCORE33, 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 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, 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 void _sgl_begin(_sgl_context_t* ctx, _sgl_primitive_type_t mode) { + ctx->in_begin = true; + ctx->base_vertex = ctx->vertices.next; + ctx->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_NO_ERROR; + 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 = SGL_ERROR_VERTICES_FULL; + 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 = SGL_ERROR_UNIFORMS_FULL; + 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 = SGL_ERROR_COMMANDS_FULL; + 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->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->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]]; +} + +// return sg_context_desc_t with patched defaults +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].name = "position"; + shd_desc.attrs[1].name = "texcoord0"; + shd_desc.attrs[2].name = "color0"; + shd_desc.attrs[3].name = "psize"; + shd_desc.attrs[0].sem_name = "TEXCOORD"; + shd_desc.attrs[0].sem_index = 0; + shd_desc.attrs[1].sem_name = "TEXCOORD"; + shd_desc.attrs[1].sem_index = 1; + shd_desc.attrs[2].sem_name = "TEXCOORD"; + shd_desc.attrs[2].sem_index = 2; + shd_desc.attrs[3].sem_name = "TEXCOORD"; + shd_desc.attrs[3].sem_index = 3; + sg_shader_uniform_block_desc* ub = &shd_desc.vs.uniform_blocks[0]; + ub->size = sizeof(_sgl_uniform_t); + ub->uniforms[0].name = "vs_params"; + ub->uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; + ub->uniforms[0].array_count = 8; + shd_desc.fs.images[0].used = true; + shd_desc.fs.images[0].image_type = SG_IMAGETYPE_2D; + shd_desc.fs.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + shd_desc.fs.samplers[0].used = true; + shd_desc.fs.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + shd_desc.fs.image_sampler_pairs[0].used = true; + shd_desc.fs.image_sampler_pairs[0].image_slot = 0; + shd_desc.fs.image_sampler_pairs[0].sampler_slot = 0; + shd_desc.fs.image_sampler_pairs[0].glsl_name = "tex_smp"; + shd_desc.label = "sgl-shader"; + #if defined(SOKOL_GLCORE33) + shd_desc.vs.source = _sgl_vs_source_glsl330; + shd_desc.fs.source = _sgl_fs_source_glsl330; + #elif defined(SOKOL_GLES3) + shd_desc.vs.source = _sgl_vs_source_glsl300es; + shd_desc.fs.source = _sgl_fs_source_glsl300es; + #elif defined(SOKOL_METAL) + shd_desc.vs.entry = "main0"; + shd_desc.fs.entry = "main0"; + switch (sg_query_backend()) { + case SG_BACKEND_METAL_MACOS: + shd_desc.vs.bytecode = SG_RANGE(_sgl_vs_bytecode_metal_macos); + shd_desc.fs.bytecode = SG_RANGE(_sgl_fs_bytecode_metal_macos); + break; + case SG_BACKEND_METAL_IOS: + shd_desc.vs.bytecode = SG_RANGE(_sgl_vs_bytecode_metal_ios); + shd_desc.fs.bytecode = SG_RANGE(_sgl_fs_bytecode_metal_ios); + break; + default: + shd_desc.vs.source = _sgl_vs_source_metal_sim; + shd_desc.fs.source = _sgl_fs_source_metal_sim; + break; + } + #elif defined(SOKOL_D3D11) + shd_desc.vs.bytecode = SG_RANGE(_sgl_vs_bytecode_hlsl4); + shd_desc.fs.bytecode = SG_RANGE(_sgl_fs_bytecode_hlsl4); + #elif defined(SOKOL_WGPU) + shd_desc.vs.source = _sgl_vs_source_wgsl; + shd_desc.fs.source = _sgl_fs_source_wgsl; + #else + shd_desc.vs.source = _sgl_vs_source_dummy; + shd_desc.fs.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->error == SGL_NO_ERROR) && (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); + } + + 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.fs.images[0] = args->img; + ctx->bind.fs.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(SG_SHADERSTAGE_VS, 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 { + return SGL_ERROR_NO_CONTEXT; + } +} + +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 { + return SGL_ERROR_NO_CONTEXT; + } +} + +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 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 = SGL_ERROR_STACK_OVERFLOW; + } +} + +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 = SGL_ERROR_STACK_UNDERFLOW; + } +} + +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); + } + } + // 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 = SGL_ERROR_STACK_OVERFLOW; + } +} + +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 = SGL_ERROR_STACK_UNDERFLOW; + } +} + +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/source/engine/thirdparty/sokol/util/sokol_imgui.h b/source/engine/thirdparty/sokol/util/sokol_imgui.h new file mode 100644 index 0000000..9c041ea --- /dev/null +++ b/source/engine/thirdparty/sokol/util/sokol_imgui.h @@ -0,0 +1,3141 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_IMGUI_IMPL) +#define SOKOL_IMGUI_IMPL +#endif +#ifndef SOKOL_IMGUI_INCLUDED +/* + sokol_imgui.h -- drop-in Dear ImGui renderer/event-handler for sokol_gfx.h + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_IMGUI_IMPL + + before you include this file in *one* C or C++ file to create the + implementation. + + NOTE that the implementation can be compiled either as C++ or as C. + When compiled as C++, sokol_imgui.h will directly call into the + Dear ImGui C++ API. When compiled as C, sokol_imgui.h will call + cimgui.h functions instead. + + NOTE that the formerly separate header sokol_cimgui.h has been + merged into sokol_imgui.h + + 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_GLCORE33 + SOKOL_GLES3 + SOKOL_D3D11 + SOKOL_METAL + SOKOL_WGPU + + Optionally provide the following configuration define both before including the + the declaration and implementation: + + SOKOL_IMGUI_NO_SOKOL_APP - don't depend on sokol_app.h (see below for details) + + Optionally provide the following macros before including the implementation + to override defaults: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_IMGUI_API_DECL- public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_IMGUI_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_imgui.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_IMGUI_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + Include the following headers before sokol_imgui.h (both before including + the declaration and implementation): + + sokol_gfx.h + sokol_app.h (except SOKOL_IMGUI_NO_SOKOL_APP) + + Additionally, include the following headers before including the + implementation: + + If the implementation is compiled as C++: + imgui.h + + If the implementation is compiled as C: + cimgui.h + + + FEATURE OVERVIEW: + ================= + sokol_imgui.h implements the initialization, rendering and event-handling + code for Dear ImGui (https://github.com/ocornut/imgui) on top of + sokol_gfx.h and (optionally) sokol_app.h. + + The sokol_app.h dependency is optional and used for input event handling. + If you only use sokol_gfx.h but not sokol_app.h in your application, + define SOKOL_IMGUI_NO_SOKOL_APP before including the implementation + of sokol_imgui.h, this will remove any dependency to sokol_app.h, but + you must feed input events into Dear ImGui yourself. + + sokol_imgui.h is not thread-safe, all calls must be made from the + same thread where sokol_gfx.h is running. + + HOWTO: + ====== + + --- To initialize sokol-imgui, call: + + simgui_setup(const simgui_desc_t* desc) + + This will initialize Dear ImGui and create sokol-gfx resources + (two buffers for vertices and indices, a font texture and a pipeline- + state-object). + + Use the following simgui_desc_t members to configure behaviour: + + int max_vertices + The maximum number of vertices used for UI rendering, default is 65536. + sokol-imgui will use this to compute the size of the vertex- + and index-buffers allocated via sokol_gfx.h + + int image_pool_size + Number of simgui_image_t objects which can be alive at the same time. + The default is 256. + + sg_pixel_format color_format + The color pixel format of the render pass where the UI + will be rendered. The default (0) matches sokoL_gfx.h's + default pass. + + sg_pixel_format depth_format + The depth-buffer pixel format of the render pass where + the UI will be rendered. The default (0) matches + sokol_gfx.h's default pass depth format. + + int sample_count + The MSAA sample-count of the render pass where the UI + will be rendered. The default (0) matches sokol_gfx.h's + default pass sample count. + + const char* ini_filename + Sets this path as ImGui::GetIO().IniFilename where ImGui will store + and load UI persistency data. By default this is 0, so that Dear ImGui + will not preserve state between sessions (and also won't do + any filesystem calls). Also see the ImGui functions: + - LoadIniSettingsFromMemory() + - SaveIniSettingsFromMemory() + These functions give you explicit control over loading and saving + UI state while using your own filesystem wrapper functions (in this + case keep simgui_desc.ini_filename zero) + + bool no_default_font + Set this to true if you don't want to use ImGui's default + font. In this case you need to initialize the font + yourself after simgui_setup() is called. + + bool disable_paste_override + If set to true, sokol_imgui.h will not 'emulate' a Dear Imgui + clipboard paste action on SAPP_EVENTTYPE_CLIPBOARD_PASTED event. + This is mainly a hack/workaround to allow external workarounds + for making copy/paste work on the web platform. In general, + copy/paste support isn't properly fleshed out in sokol_imgui.h yet. + + bool disable_set_mouse_cursor + If true, sokol_imgui.h will not control the mouse cursor type + by calling sapp_set_mouse_cursor(). + + bool disable_windows_resize_from_edges + If true, windows can only be resized from the bottom right corner. + The default is false, meaning windows can be resized from edges. + + bool write_alpha_channel + Set this to true if you want alpha values written to the + framebuffer. By default this behavior is disabled to prevent + undesired behavior on platforms like the web where the canvas is + always alpha-blended with the background. + + simgui_allocator_t allocator + Used to override memory allocation functions. See further below + for details. + + simgui_logger_t logger + A user-provided logging callback. Note that without logging + callback, sokol-imgui will be completely silent! + See the section about ERROR REPORTING AND LOGGING below + for more details. + + --- At the start of a frame, call: + + simgui_new_frame(&(simgui_frame_desc_t){ + .width = ..., + .height = ..., + .delta_time = ..., + .dpi_scale = ... + }); + + 'width' and 'height' are the dimensions of the rendering surface, + passed to ImGui::GetIO().DisplaySize. + + 'delta_time' is the frame duration passed to ImGui::GetIO().DeltaTime. + + 'dpi_scale' is the current DPI scale factor, if this is left zero-initialized, + 1.0f will be used instead. Typical values for dpi_scale are >= 1.0f. + + For example, if you're using sokol_app.h and render to the default framebuffer: + + simgui_new_frame(&(simgui_frame_desc_t){ + .width = sapp_width(), + .height = sapp_height(), + .delta_time = sapp_frame_duration(), + .dpi_scale = sapp_dpi_scale() + }); + + --- at the end of the frame, before the sg_end_pass() where you + want to render the UI, call: + + simgui_render() + + This will first call ImGui::Render(), and then render ImGui's draw list + through sokol_gfx.h + + --- if you're using sokol_app.h, from inside the sokol_app.h event callback, + call: + + bool simgui_handle_event(const sapp_event* ev); + + The return value is the value of ImGui::GetIO().WantCaptureKeyboard, + if this is true, you might want to skip keyboard input handling + in your own event handler. + + If you want to use the ImGui functions for checking if a key is pressed + (e.g. ImGui::IsKeyPressed()) the following helper function to map + an sapp_keycode to an ImGuiKey value may be useful: + + int simgui_map_keycode(sapp_keycode c); + + Note that simgui_map_keycode() can be called outside simgui_setup()/simgui_shutdown(). + + --- finally, on application shutdown, call + + simgui_shutdown() + + + ON USER-PROVIDED IMAGES AND SAMPLERS + ==================================== + To render your own images via ImGui::Image(), first create an simgui_image_t + object from a sokol-gfx image and sampler object. + + // create a sokol-imgui image object which associates an sg_image with an sg_sampler + simgui_image_t simgui_img = simgui_make_image(&(simgui_image_desc_t){ + .image = sg_make_image(...), + .sampler = sg_make_sampler(...), + }); + + // convert the returned image handle into a ImTextureID handle + ImTextureID tex_id = simgui_imtextureid(simgui_img); + + // use the ImTextureID handle in Dear ImGui calls: + ImGui::Image(tex_id, ...); + + simgui_image_t objects are small and cheap (literally just the image and sampler + handle). + + You can omit the sampler handle in the simgui_make_image() call, in this case a + default sampler will be used with nearest-filtering and clamp-to-edge. + + Trying to render with an invalid simgui_image_t handle will render a small 8x8 + white default texture instead. + + To destroy a sokol-imgui image object, call + + simgui_destroy_image(simgui_img); + + But please be aware that the image object needs to be around until simgui_render() is called + in a frame (if this turns out to be too much of a hassle we could introduce some sort + of garbage collection where destroyed simgui_image_t objects are kept around until + the simgui_render() call). + + You can call: + + simgui_image_desc_t desc = simgui_query_image_desc(img) + + ...to get the original desc struct, useful if you need to get the sokol-gfx image + and sampler handle of the simgui_image_t object. + + You can convert an ImTextureID back into an simgui_image_t handle: + + simgui_image_t img = simgui_image_from_imtextureid(tex_id); + + + 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); + } + + ... + simgui_setup(&(simgui_desc_t){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ...; + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_imgui.h + itself though, not any allocations in Dear ImGui. + + + 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" + + simgui_setup(&(simgui_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. 'simgui' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SIMGUI_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_imgui.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-imgui like this: + + simgui_setup(&(simgui_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. + + + IMGUI EVENT HANDLING + ==================== + You can call these functions from your platform's events to handle ImGui events + when SOKOL_IMGUI_NO_SOKOL_APP is defined. + + E.g. mouse position events can be dispatched like this: + + simgui_add_mouse_pos_event(100, 200); + + Key events require a mapping function to convert your platform's key values to ImGuiKey's: + + int map_keycode(int keycode) { + // Your mapping logic here... + } + simgui_add_key_event(map_keycode, keycode, true); + + Take note that modifiers (shift, ctrl, etc.) must be updated manually. + + If sokol_app is being used, ImGui events are handled for you. + + + 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_IMGUI_INCLUDED (1) +#include +#include +#include // size_t + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_imgui.h" +#endif +#if !defined(SOKOL_IMGUI_NO_SOKOL_APP) && !defined(SOKOL_APP_INCLUDED) +#error "Please include sokol_app.h before sokol_imgui.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_IMGUI_API_DECL) +#define SOKOL_IMGUI_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_IMGUI_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_IMGUI_IMPL) +#define SOKOL_IMGUI_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_IMGUI_API_DECL __declspec(dllimport) +#else +#define SOKOL_IMGUI_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + SIMGUI_INVALID_ID = 0, +}; + +/* + simgui_image_t + + A combined image-sampler pair used to inject custom images and samplers into Dear ImGui. + + Create with simgui_make_image(), and convert to an ImTextureID handle via + simgui_imtextureid(). +*/ +typedef struct simgui_image_t { uint32_t id; } simgui_image_t; + +/* + simgui_image_desc_t + + Descriptor struct for simgui_make_image(). You must provide + at least an sg_image handle. Keeping the sg_sampler handle + zero-initialized will select the builtin default sampler + which uses linear filtering. +*/ +typedef struct simgui_image_desc_t { + sg_image image; + sg_sampler sampler; +} simgui_image_desc_t; + +/* + simgui_log_item + + An enum with a unique item for each log message, warning, error + and validation layer message. +*/ +#define _SIMGUI_LOG_ITEMS \ + _SIMGUI_LOGITEM_XMACRO(OK, "Ok") \ + _SIMGUI_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SIMGUI_LOGITEM_XMACRO(IMAGE_POOL_EXHAUSTED, "image pool exhausted") \ + +#define _SIMGUI_LOGITEM_XMACRO(item,msg) SIMGUI_LOGITEM_##item, +typedef enum simgui_log_item_t { + _SIMGUI_LOG_ITEMS +} simgui_log_item_t; +#undef _SIMGUI_LOGITEM_XMACRO + +/* + simgui_allocator_t + + Used in simgui_desc_t to provide custom memory-alloc and -free functions + to sokol_imgui.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 simgui_allocator_t { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} simgui_allocator_t; + +/* + simgui_logger + + Used in simgui_desc_t to provide a logging function. Please be aware + that without logging function, sokol-imgui 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 install + a logger (for instance the standard logging function from sokol_log.h). +*/ +typedef struct simgui_logger_t { + void (*func)( + const char* tag, // always "simgui" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SIMGUI_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_imgui.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} simgui_logger_t; + +typedef struct simgui_desc_t { + int max_vertices; // default: 65536 + int image_pool_size; // default: 256 + sg_pixel_format color_format; + sg_pixel_format depth_format; + int sample_count; + const char* ini_filename; + bool no_default_font; + bool disable_paste_override; // if true, don't send Ctrl-V on EVENTTYPE_CLIPBOARD_PASTED + bool disable_set_mouse_cursor; // if true, don't control the mouse cursor type via sapp_set_mouse_cursor() + bool disable_windows_resize_from_edges; // if true, only resize edges from the bottom right corner + bool write_alpha_channel; // if true, alpha values get written into the framebuffer + simgui_allocator_t allocator; // optional memory allocation overrides (default: malloc/free) + simgui_logger_t logger; // optional log function override +} simgui_desc_t; + +typedef struct simgui_frame_desc_t { + int width; + int height; + double delta_time; + float dpi_scale; +} simgui_frame_desc_t; + +typedef struct simgui_font_tex_desc_t { + sg_filter min_filter; + sg_filter mag_filter; +} simgui_font_tex_desc_t; + +SOKOL_IMGUI_API_DECL void simgui_setup(const simgui_desc_t* desc); +SOKOL_IMGUI_API_DECL void simgui_new_frame(const simgui_frame_desc_t* desc); +SOKOL_IMGUI_API_DECL void simgui_render(void); +SOKOL_IMGUI_API_DECL simgui_image_t simgui_make_image(const simgui_image_desc_t* desc); +SOKOL_IMGUI_API_DECL void simgui_destroy_image(simgui_image_t img); +SOKOL_IMGUI_API_DECL simgui_image_desc_t simgui_query_image_desc(simgui_image_t img); +SOKOL_IMGUI_API_DECL void* simgui_imtextureid(simgui_image_t img); +SOKOL_IMGUI_API_DECL simgui_image_t simgui_image_from_imtextureid(void* imtextureid); +SOKOL_IMGUI_API_DECL void simgui_add_focus_event(bool focus); +SOKOL_IMGUI_API_DECL void simgui_add_mouse_pos_event(float x, float y); +SOKOL_IMGUI_API_DECL void simgui_add_touch_pos_event(float x, float y); +SOKOL_IMGUI_API_DECL void simgui_add_mouse_button_event(int mouse_button, bool down); +SOKOL_IMGUI_API_DECL void simgui_add_mouse_wheel_event(float wheel_x, float wheel_y); +SOKOL_IMGUI_API_DECL void simgui_add_key_event(int (*map_keycode)(int), int keycode, bool down); +SOKOL_IMGUI_API_DECL void simgui_add_input_character(uint32_t c); +SOKOL_IMGUI_API_DECL void simgui_add_input_characters_utf8(const char* c); +SOKOL_IMGUI_API_DECL void simgui_add_touch_button_event(int mouse_button, bool down); +#if !defined(SOKOL_IMGUI_NO_SOKOL_APP) +SOKOL_IMGUI_API_DECL bool simgui_handle_event(const sapp_event* ev); +SOKOL_IMGUI_API_DECL int simgui_map_keycode(sapp_keycode keycode); // returns ImGuiKey_* +#endif +SOKOL_IMGUI_API_DECL void simgui_shutdown(void); +SOKOL_IMGUI_API_DECL void simgui_create_fonts_texture(const simgui_font_tex_desc_t* desc); +SOKOL_IMGUI_API_DECL void simgui_destroy_fonts_texture(void); + +#ifdef __cplusplus +} // extern "C" + +// reference-based equivalents for C++ +inline void simgui_setup(const simgui_desc_t& desc) { return simgui_setup(&desc); } +inline simgui_image_t simgui_make_image(const simgui_image_desc_t& desc) { return simgui_make_image(&desc); } +inline void simgui_new_frame(const simgui_frame_desc_t& desc) { return simgui_new_frame(&desc); } +inline void simgui_create_fonts_texture(const simgui_font_tex_desc_t& desc) { return simgui_create_fonts_texture(&desc); } + +#endif +#endif /* SOKOL_IMGUI_INCLUDED */ + +//-- IMPLEMENTATION ------------------------------------------------------------ +#ifdef SOKOL_IMGUI_IMPL +#define SOKOL_IMGUI_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 simgui_desc_t.allocator to override memory allocation functions" +#endif + +#if defined(__cplusplus) + #if !defined(IMGUI_VERSION) + #error "Please include imgui.h before the sokol_imgui.h implementation" + #endif +#else + #if !defined(CIMGUI_INCLUDED) + #error "Please include cimgui.h before the sokol_imgui.h implementation" + #endif +#endif + +#include // memset +#include // malloc/free + +#if defined(__EMSCRIPTEN__) && !defined(SOKOL_DUMMY_BACKEND) +#include +#endif + +#ifndef SOKOL_API_IMPL +#define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif + +#define _SIMGUI_INIT_COOKIE (0xBABEBABE) +#define _SIMGUI_INVALID_SLOT_INDEX (0) +#define _SIMGUI_SLOT_SHIFT (16) +#define _SIMGUI_MAX_POOL_SIZE (1<<_SIMGUI_SLOT_SHIFT) +#define _SIMGUI_SLOT_MASK (_SIMGUI_MAX_POOL_SIZE-1) + +// helper macros and constants +#define _simgui_def(val, def) (((val) == 0) ? (def) : (val)) + +// workaround for missing ImDrawCallback_ResetRenderState in cimgui.h +// see: https://github.com/cimgui/cimgui/issues/261 +#ifndef ImDrawCallback_ResetRenderState +#define ImDrawCallback_ResetRenderState (ImDrawCallback)(-8) +#endif + +typedef struct { + ImVec2 disp_size; + uint8_t _pad_8[8]; +} _simgui_vs_params_t; + +typedef enum { + _SIMGUI_RESOURCESTATE_INITIAL, + _SIMGUI_RESOURCESTATE_ALLOC, + _SIMGUI_RESOURCESTATE_VALID, + _SIMGUI_RESOURCESTATE_FAILED, + _SIMGUI_RESOURCESTATE_INVALID, + _SIMGUI_RESOURCESTATE_FORCE_U32 = 0x7FFFFFFF +} _simgui_resource_state; + +typedef struct { + uint32_t id; + _simgui_resource_state state; +} _simgui_slot_t; + +typedef struct { + int size; + int queue_top; + uint32_t* gen_ctrs; + int* free_queue; +} _simgui_pool_t; + +typedef struct { + _simgui_slot_t slot; + sg_image image; + sg_sampler sampler; + sg_pipeline pip; // this will either be _simgui.def_pip or _simgui.pip_unfilterable +} _simgui_image_t; + +typedef struct { + _simgui_pool_t pool; + _simgui_image_t* items; +} _simgui_image_pool_t; + +typedef struct { + uint32_t init_cookie; + simgui_desc_t desc; + float cur_dpi_scale; + sg_buffer vbuf; + sg_buffer ibuf; + sg_image font_img; + sg_sampler font_smp; + simgui_image_t default_font; + sg_image def_img; // used as default image for user images + sg_sampler def_smp; // used as default sampler for user images + sg_shader def_shd; + sg_pipeline def_pip; + // separate shader and pipeline for unfilterable user images + sg_shader shd_unfilterable; + sg_pipeline pip_unfilterable; + sg_range vertices; + sg_range indices; + bool is_osx; + _simgui_image_pool_t image_pool; +} _simgui_state_t; +static _simgui_state_t _simgui; + +/* + Embedded source code compiled with: + + sokol-shdc -i simgui.glsl -o simgui.h -l glsl330:glsl300es:hlsl4:metal_macos:metal_ios:metal_sim:wgpu -b + + (not that for Metal and D3D11 byte code, sokol-shdc must be run + on macOS and Windows) + + @vs vs + uniform vs_params { + vec2 disp_size; + }; + in vec2 position; + in vec2 texcoord0; + in vec4 color0; + out vec2 uv; + out vec4 color; + void main() { + gl_Position = vec4(((position/disp_size)-0.5)*vec2(2.0,-2.0), 0.5, 1.0); + uv = texcoord0; + color = color0; + } + @end + + @fs fs + uniform texture2D tex; + uniform sampler smp; + in vec2 uv; + in vec4 color; + out vec4 frag_color; + void main() { + frag_color = texture(sampler2D(tex, smp), uv) * color; + } + @end + + @program simgui vs fs +*/ +#if defined(SOKOL_GLCORE33) +static const char _simgui_vs_source_glsl330[341] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,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,0x31,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,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, + 0x28,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x2f,0x20,0x76,0x73,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,0x79,0x29,0x20,0x2d,0x20, + 0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x35,0x29,0x29,0x20,0x2a,0x20,0x76,0x65,0x63, + 0x32,0x28,0x32,0x2e,0x30,0x2c,0x20,0x2d,0x32,0x2e,0x30,0x29,0x2c,0x20,0x30,0x2e, + 0x35,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, +}; +static const char _simgui_fs_source_glsl330[177] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,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,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a, + 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,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a, + 0x00, +}; +#elif defined(SOKOL_GLES3) +static const char _simgui_vs_source_glsl300es[344] = { + 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,0x31,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f, + 0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29, + 0x20,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,0x28,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x2f,0x20, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,0x79,0x29, + 0x20,0x2d,0x20,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x35,0x29,0x29,0x20,0x2a,0x20, + 0x76,0x65,0x63,0x32,0x28,0x32,0x2e,0x30,0x2c,0x20,0x2d,0x32,0x2e,0x30,0x29,0x2c, + 0x20,0x30,0x2e,0x35,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, +}; +static const char _simgui_fs_source_glsl300es[250] = { + 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,0x20,0x2a,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_METAL) +static const uint8_t _simgui_vs_bytecode_metal_macos[3052] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xec,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, + 0xe0,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,0x7b,0x12,0x23,0x17,0xd9,0x25,0x1c, + 0x1b,0x42,0x42,0x9f,0xbf,0x31,0xd2,0x2c,0x3a,0x55,0x22,0x1d,0x40,0xd8,0xc4,0xf8, + 0x20,0x49,0x60,0x6d,0x3c,0xea,0x4e,0x1c,0x34,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,0xc8,0x0a,0x00,0x00,0xff,0xff,0xff,0xff, + 0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0xaf,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,0x81,0x00,0x00,0x00,0x1b,0xc8,0x25,0xf8,0xff,0xff,0xff,0xff, + 0x01,0x90,0x80,0x8a,0x18,0x87,0x77,0x90,0x07,0x79,0x28,0x87,0x71,0xa0,0x07,0x76, + 0xc8,0x87,0x36,0x90,0x87,0x77,0xa8,0x07,0x77,0x20,0x87,0x72,0x20,0x87,0x36,0x20, + 0x87,0x74,0xb0,0x87,0x74,0x20,0x87,0x72,0x68,0x83,0x79,0x88,0x07,0x79,0xa0,0x87, + 0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0xc0,0x1c,0xc2,0x81, + 0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c,0xd2,0x61,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c, + 0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8,0xa1,0x0d,0xc6,0x21,0x1c,0xd8,0x81,0x1d,0xe6, + 0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07,0x80,0x60,0x87,0x72,0x98,0x87,0x79, + 0x68,0x03,0x78,0x90,0x87,0x72,0x18,0x87,0x74,0x98,0x87,0x72,0x68,0x03,0x73,0x80, + 0x87,0x76,0x08,0x07,0x72,0x00,0xcc,0x21,0x1c,0xd8,0x61,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,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x06,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,0x68,0x03,0x77,0x78,0x07,0x77,0x68,0x03,0x76,0x28, + 0x87,0x70,0x30,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,0x40,0x1d,0xea,0xa1,0x1d,0xe0,0xa1,0x0d, + 0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87, + 0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x70,0x87,0x70,0x70,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,0xe6,0x21,0x1d,0xce,0xc1,0x1d,0xca,0x81,0x1c,0xda, + 0x40,0x1f,0xca,0x41,0x1e,0xde,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, + 0x03,0x7a,0x90,0x87,0x70,0x80,0x07,0x78,0x48,0x07,0x77,0x38,0x87,0x36,0x68,0x87, + 0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0x62,0x1e,0xe8,0x21, + 0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e,0xe4,0xe1,0x1d,0xe8,0xa1,0x1c,0xc6,0x81,0x1e, + 0xde,0x41,0x1e,0xda,0x40,0x1c,0xea,0xc1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x0d,0xe6, + 0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c,0x00,0x88,0x7a,0x70,0x87,0x79,0x08,0x07,0x73, + 0x28,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e, + 0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea,0x61,0x1e,0xca,0xa1,0x0d,0xe6,0xe1,0x1d,0xcc, + 0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1,0x1d,0xc2,0x81,0x1e,0x00,0x73,0x08,0x07,0x76, + 0x98,0x87,0x72,0x00,0x36,0x18,0x02,0x01,0x2c,0x40,0x05,0x00,0x49,0x18,0x00,0x00, + 0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00,0x00,0x16,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,0x3c,0x33,0x00, + 0xc3,0x08,0x02,0x30,0x8c,0x40,0x00,0x76,0x08,0x91,0x83,0xa4,0x29,0xa2,0x84,0xc9, + 0xaf,0xa4,0xff,0x01,0x22,0x80,0x91,0x50,0x10,0x83,0x08,0x84,0x50,0x8a,0x89,0x90, + 0x22,0x1b,0x08,0x98,0x23,0x00,0x83,0x14,0xc8,0x39,0x02,0x50,0x18,0x44,0x08,0x84, + 0x61,0x04,0x22,0x19,0x01,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,0x39,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0xc8, + 0x02,0x01,0x00,0x00,0x09,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90, + 0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0xca,0x12,0x18,0x01,0x28,0x88,0x22,0x28,0x84, + 0x32,0xa0,0x1d,0x01,0x20,0x1d,0x4b,0x68,0x02,0x00,0x00,0x00,0x79,0x18,0x00,0x00, + 0xea,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9, + 0xb9,0xb4,0x37,0xb7,0x21,0x46,0x42,0x20,0x80,0x82,0x50,0xb9,0x1b,0x43,0x0b,0x93, + 0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x24,0x01,0x22,0x24,0x05,0xe7,0x20,0x08,0x0e, + 0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd, + 0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e, + 0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45,0x26, + 0x65,0x88,0x80,0x10,0x43,0x8c,0x24,0x48,0x86,0x44,0x60,0xd1,0x54,0x46,0x17,0xc6, + 0x36,0x04,0x41,0x8e,0x24,0x48,0x82,0x44,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,0x44,0x40,0x3c,0xe4,0x4b,0x84,0x24,0x40,0xc0,0x00,0x89,0x10,0x09,0x99,0x90, + 0x30,0x60,0x42,0x57,0x86,0x37,0xf6,0xf6,0x26,0x47,0x06,0x33,0x84,0x4a,0x02,0xc4, + 0x43,0xbe,0x24,0x48,0x02,0x04,0x0c,0x90,0x08,0x91,0x90,0x09,0x19,0x03,0x1a,0x63, + 0x6f,0x6c,0x6f,0x72,0x30,0x43,0xa8,0x84,0x40,0x3c,0xe4,0x4b,0x88,0x24,0x40,0xc0, + 0x00,0x89,0x90,0x0b,0x99,0x90,0x32,0xa0,0x12,0x96,0x26,0xe7,0x22,0x56,0x67,0x66, + 0x56,0x26,0xc7,0x27,0x2c,0x4d,0xce,0x45,0xac,0xce,0xcc,0xac,0x4c,0xee,0x6b,0x2e, + 0x4d,0xaf,0x8c,0x48,0x58,0x9a,0x9c,0x8b,0x5c,0x59,0x18,0x19,0xa9,0xb0,0x34,0x39, + 0x97,0x39,0x3a,0xb9,0xba,0x31,0xba,0x2f,0xba,0x3c,0xb8,0xb2,0xaf,0x34,0x37,0xb3, + 0x37,0x26,0x64,0x69,0x73,0x70,0x5f,0x73,0x69,0x7a,0x65,0x43,0x94,0x44,0x48,0x86, + 0x44,0x40,0x24,0x64,0x0d,0x18,0x85,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xd1,0xe5, + 0xc1,0x95,0x7d,0xcd,0xa5,0xe9,0x95,0xf1,0x0a,0x4b,0x93,0x73,0x09,0x93,0x3b,0xfb, + 0xa2,0xcb,0x83,0x2b,0xfb,0x0a,0x63,0x4b,0x3b,0x73,0xfb,0x9a,0x4b,0xd3,0x2b,0x63, + 0x62,0x37,0xf7,0x05,0x17,0x26,0x17,0xd6,0x36,0xc7,0xe1,0x4b,0x46,0x66,0x08,0x19, + 0x24,0x06,0x72,0x06,0x08,0x1a,0x24,0x03,0xf2,0x25,0x42,0x12,0x20,0x69,0x80,0xa8, + 0x01,0xc2,0x06,0x48,0x1b,0x24,0x03,0xe2,0x06,0xc9,0x80,0x44,0xc8,0x1b,0x20,0x13, + 0x02,0x07,0x43,0x10,0x44,0x0c,0x10,0x32,0x40,0xcc,0x00,0x89,0x83,0x21,0xc6,0x01, + 0x20,0x1d,0x22,0x07,0x7c,0xde,0xda,0xdc,0xd2,0xe0,0xde,0xe8,0xca,0xdc,0xe8,0x40, + 0xc6,0xd0,0xc2,0xe4,0xf8,0x4c,0xa5,0xb5,0xc1,0xb1,0x95,0x81,0x0c,0xad,0xac,0x80, + 0x50,0x09,0x05,0x05,0x0d,0x11,0x90,0x3a,0x18,0x62,0x20,0x74,0x80,0xd8,0xc1,0x72, + 0x0c,0x31,0x90,0x3b,0x40,0xee,0x60,0x39,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,0xe6,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00, + 0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d, + 0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,0x00, + 0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00,0x06,0x50,0x30,0x00,0xd2,0xd0,0x00,0x00, + 0x61,0x20,0x00,0x00,0x23,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00, + 0x11,0x00,0x00,0x00,0xd4,0x63,0x11,0x40,0x60,0x1c,0x73,0x10,0x42,0xf0,0x3c,0x94, + 0x33,0x00,0x14,0x63,0x09,0x20,0x08,0x82,0xf0,0x2f,0x80,0x20,0x08,0xc2,0xbf,0x30, + 0x96,0x00,0x82,0x20,0x08,0x82,0x01,0x08,0x82,0x20,0x08,0x0e,0x33,0x00,0x24,0x73, + 0x10,0xd7,0x65,0x55,0x34,0x33,0x00,0x04,0x63,0x04,0x20,0x08,0x82,0xf8,0x37,0x46, + 0x00,0x82,0x20,0x08,0x7f,0x33,0x00,0x00,0xe3,0x0d,0x4c,0x64,0x51,0x40,0x2c,0x0a, + 0xe8,0x63,0xc1,0x02,0x1f,0x0b,0x16,0xf9,0x0c,0x32,0x04,0xcb,0x33,0xc8,0x10,0x2c, + 0xd1,0x6c,0xc3,0x52,0x01,0xb3,0x0d,0x41,0x15,0xcc,0x36,0x04,0x83,0x90,0x41,0x40, + 0x0c,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x5b,0x86,0x20,0xc0,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _simgui_fs_bytecode_metal_macos[2809] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf9,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x20,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,0xa1,0xce,0x6b,0xd1,0x1f,0x32,0x9e, + 0x8d,0x8d,0x1c,0xcc,0x19,0xcb,0xd3,0xb6,0x21,0x99,0x0b,0xb6,0x46,0x8b,0x87,0x98, + 0x8e,0x2d,0xb5,0x98,0x92,0x0a,0x81,0x7d,0xf3,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,0x0c,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0x80,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,0x89,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07,0x7a,0x60,0x87,0x7c, + 0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72,0x68,0x03,0x72,0x48, + 0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90,0x07,0x7a,0x68,0x03, + 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc,0x21,0x1c,0xd8,0x61, + 0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81,0x1d,0xca,0xa1,0x0d, + 0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d,0xd8,0x61,0x1e,0x00, + 0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87,0x79,0x98,0x87,0x36, + 0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,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,0x00,0x7a,0x90, + 0x87,0x7a,0x28,0x07,0x60,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,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36,0x60,0x87,0x72,0x08, + 0x07,0x73,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,0xd4,0xa1,0x1e,0xda,0x01,0x1e,0xda,0x80,0x1e, + 0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,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,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c,0xc8,0xa1,0x0d,0xf4, + 0xa1,0x1c,0xe4,0xe1,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,0xa0, + 0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68,0x83,0x76,0x08,0x07, + 0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6,0x81,0x1e,0xc2,0x61, + 0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d, + 0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e,0xda,0x60,0x1e,0xd2, + 0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87,0x70,0x30,0x87,0x72, + 0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e, + 0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e,0xde,0xc1,0x1c,0xe8, + 0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 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,0x05,0x49,0x18,0x00,0x00,0x03,0x00,0x00, + 0x00,0x13,0x86,0x40,0x18,0x26,0x0c,0x44,0x61,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,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,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, + 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,0xa1,0x09,0x00,0x00,0x79,0x18,0x00,0x00,0xb9,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,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e, + 0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6, + 0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06, + 0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45,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,0xc1,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,0x98,0x70,0x48,0x07,0x79,0x70,0x03,0x73, + 0x90,0x87,0x70,0x38,0x87,0x76,0x28,0x07,0x77,0xa0,0x87,0x29,0xc1,0x1a,0x00,0x00, + 0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4, + 0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43, + 0x3d,0xc0,0xc3,0x3c,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,0x0c,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x04,0x00,0x00, + 0x00,0xc4,0x46,0x00,0x48,0x8d,0x00,0xd4,0x00,0x89,0x19,0x00,0x02,0x23,0x00,0x00, + 0x00,0x23,0x06,0x8a,0x10,0x44,0x87,0x91,0x0c,0x05,0x11,0x58,0x90,0xc8,0x67,0xb6, + 0x81,0x08,0x80,0x0c,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _simgui_vs_bytecode_metal_ios[3052] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xec,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, + 0xe0,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,0x69,0x97,0x6b,0xac,0xd8,0xa2,0x51, + 0x33,0x7c,0x5f,0x96,0xb2,0xb1,0x06,0x06,0x7c,0xbb,0x5f,0x88,0xa0,0xeb,0x9f,0xea, + 0x6e,0x6b,0x70,0xa9,0x6e,0xef,0xe6,0xa4,0xea,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,0xc0,0x0a,0x00,0x00,0xff,0xff,0xff,0xff, + 0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0xad,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,0x82,0x00,0x00,0x00,0x1b,0xc8,0x25,0xf8,0xff,0xff,0xff,0xff, + 0x01,0x90,0x80,0x8a,0x18,0x87,0x77,0x90,0x07,0x79,0x28,0x87,0x71,0xa0,0x07,0x76, + 0xc8,0x87,0x36,0x90,0x87,0x77,0xa8,0x07,0x77,0x20,0x87,0x72,0x20,0x87,0x36,0x20, + 0x87,0x74,0xb0,0x87,0x74,0x20,0x87,0x72,0x68,0x83,0x79,0x88,0x07,0x79,0xa0,0x87, + 0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0xc0,0x1c,0xc2,0x81, + 0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c,0xd2,0x61,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c, + 0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8,0xa1,0x0d,0xc6,0x21,0x1c,0xd8,0x81,0x1d,0xe6, + 0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07,0x80,0x60,0x87,0x72,0x98,0x87,0x79, + 0x68,0x03,0x78,0x90,0x87,0x72,0x18,0x87,0x74,0x98,0x87,0x72,0x68,0x03,0x73,0x80, + 0x87,0x76,0x08,0x07,0x72,0x00,0xcc,0x21,0x1c,0xd8,0x61,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,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x06,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,0x68,0x03,0x77,0x78,0x07,0x77,0x68,0x03,0x76,0x28, + 0x87,0x70,0x30,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,0x40,0x1d,0xea,0xa1,0x1d,0xe0,0xa1,0x0d, + 0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87, + 0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x70,0x87,0x70,0x70,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,0xe6,0x21,0x1d,0xce,0xc1,0x1d,0xca,0x81,0x1c,0xda, + 0x40,0x1f,0xca,0x41,0x1e,0xde,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, + 0x03,0x7a,0x90,0x87,0x70,0x80,0x07,0x78,0x48,0x07,0x77,0x38,0x87,0x36,0x68,0x87, + 0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0x62,0x1e,0xe8,0x21, + 0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e,0xe4,0xe1,0x1d,0xe8,0xa1,0x1c,0xc6,0x81,0x1e, + 0xde,0x41,0x1e,0xda,0x40,0x1c,0xea,0xc1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x0d,0xe6, + 0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c,0x00,0x88,0x7a,0x70,0x87,0x79,0x08,0x07,0x73, + 0x28,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e, + 0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea,0x61,0x1e,0xca,0xa1,0x0d,0xe6,0xe1,0x1d,0xcc, + 0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1,0x1d,0xc2,0x81,0x1e,0x00,0x73,0x08,0x07,0x76, + 0x98,0x87,0x72,0x00,0x36,0x20,0x02,0x01,0x24,0xc0,0x02,0x54,0x00,0x00,0x00,0x00, + 0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00,0x00, + 0x16,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,0x3c,0x33,0x00,0xc3,0x08,0x02,0x30,0x8c,0x40,0x00,0x76,0x08,0x91,0x83,0xa4, + 0x29,0xa2,0x84,0xc9,0xaf,0xa4,0xff,0x01,0x22,0x80,0x91,0x50,0x10,0x83,0x08,0x84, + 0x50,0x8a,0x89,0x90,0x22,0x1b,0x08,0x98,0x23,0x00,0x83,0x14,0xc8,0x39,0x02,0x50, + 0x18,0x44,0x08,0x84,0x61,0x04,0x22,0x19,0x01,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,0x03,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x2c,0x10,0x00,0x00, + 0x09,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47, + 0xc6,0x04,0x43,0xca,0x12,0x18,0x01,0x28,0x88,0x22,0x28,0x84,0x32,0xa0,0x1d,0x01, + 0x20,0x1d,0x4b,0x80,0x04,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xe9,0x00,0x00,0x00, + 0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7, + 0x21,0x46,0x42,0x20,0x80,0x82,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3, + 0x2b,0x1b,0x62,0x24,0x01,0x22,0x24,0x05,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4, + 0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06, + 0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac, + 0x65,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45,0x26,0x65,0x88,0x80,0x10, + 0x43,0x8c,0x24,0x48,0x86,0x44,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x41,0x8e, + 0x24,0x48,0x82,0x44,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,0x44,0x40,0x3c,0xe4, + 0x4b,0x84,0x24,0x40,0xc0,0x00,0x89,0x10,0x09,0x99,0x90,0x30,0x60,0x42,0x57,0x86, + 0x37,0xf6,0xf6,0x26,0x47,0x06,0x33,0x84,0x4a,0x02,0xc4,0x43,0xbe,0x24,0x48,0x02, + 0x04,0x0c,0x90,0x08,0x91,0x90,0x09,0x19,0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x43,0xa8,0x84,0x40,0x3c,0xe4,0x4b,0x88,0x24,0x40,0xc0,0x00,0x89,0x90,0x0b,0x99, + 0x90,0x32,0xa0,0x12,0x96,0x26,0xe7,0x22,0x56,0x67,0x66,0x56,0x26,0xc7,0x27,0x2c, + 0x4d,0xce,0x45,0xac,0xce,0xcc,0xac,0x4c,0xee,0x6b,0x2e,0x4d,0xaf,0x8c,0x48,0x58, + 0x9a,0x9c,0x8b,0x5c,0x59,0x18,0x19,0xa9,0xb0,0x34,0x39,0x97,0x39,0x3a,0xb9,0xba, + 0x31,0xba,0x2f,0xba,0x3c,0xb8,0xb2,0xaf,0x34,0x37,0xb3,0x37,0x26,0x64,0x69,0x73, + 0x70,0x5f,0x73,0x69,0x7a,0x65,0x43,0x94,0x44,0x48,0x86,0x44,0x40,0x24,0x64,0x0d, + 0x18,0x85,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xd1,0xe5,0xc1,0x95,0x7d,0xcd,0xa5, + 0xe9,0x95,0xf1,0x0a,0x4b,0x93,0x73,0x09,0x93,0x3b,0xfb,0xa2,0xcb,0x83,0x2b,0xfb, + 0x0a,0x63,0x4b,0x3b,0x73,0xfb,0x9a,0x4b,0xd3,0x2b,0x63,0x62,0x37,0xf7,0x05,0x17, + 0x26,0x17,0xd6,0x36,0xc7,0xe1,0x4b,0x46,0x66,0x08,0x19,0x24,0x06,0x72,0x06,0x08, + 0x1a,0x24,0x03,0xf2,0x25,0x42,0x12,0x20,0x69,0x80,0xa8,0x01,0xc2,0x06,0x48,0x1b, + 0x24,0x03,0xe2,0x06,0xc9,0x80,0x44,0xc8,0x1b,0x20,0x13,0x02,0x07,0x43,0x10,0x44, + 0x0c,0x10,0x32,0x40,0xcc,0x00,0x89,0x83,0x21,0xc6,0x01,0x20,0x1d,0x22,0x07,0x7c, + 0xde,0xda,0xdc,0xd2,0xe0,0xde,0xe8,0xca,0xdc,0xe8,0x40,0xc6,0xd0,0xc2,0xe4,0xf8, + 0x4c,0xa5,0xb5,0xc1,0xb1,0x95,0x81,0x0c,0xad,0xac,0x80,0x50,0x09,0x05,0x05,0x0d, + 0x11,0x90,0x3a,0x18,0x62,0x20,0x74,0x80,0xd8,0xc1,0x72,0x0c,0x31,0x90,0x3b,0x40, + 0xee,0x60,0x39,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,0xe6,0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8, + 0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83, + 0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00, + 0x06,0x50,0x30,0x00,0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x23,0x00,0x00,0x00, + 0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0xd4,0x63,0x11,0x40, + 0x60,0x1c,0x73,0x10,0x42,0xf0,0x3c,0x94,0x33,0x00,0x14,0x63,0x09,0x20,0x08,0x82, + 0xf0,0x2f,0x80,0x20,0x08,0xc2,0xbf,0x30,0x96,0x00,0x82,0x20,0x08,0x82,0x01,0x08, + 0x82,0x20,0x08,0x0e,0x33,0x00,0x24,0x73,0x10,0xd7,0x65,0x55,0x34,0x33,0x00,0x04, + 0x63,0x04,0x20,0x08,0x82,0xf8,0x37,0x46,0x00,0x82,0x20,0x08,0x7f,0x33,0x00,0x00, + 0xe3,0x0d,0x4c,0x64,0x51,0x40,0x2c,0x0a,0xe8,0x63,0xc1,0x02,0x1f,0x0b,0x16,0xf9, + 0x0c,0x32,0x04,0xcb,0x33,0xc8,0x10,0x2c,0xd1,0x6c,0xc3,0x52,0x01,0xb3,0x0d,0x41, + 0x15,0xcc,0x36,0x04,0x83,0x90,0x41,0x40,0x0c,0x00,0x00,0x00,0x02,0x00,0x00,0x00, + 0x5b,0x86,0x20,0xc0,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _simgui_fs_bytecode_metal_ios[2809] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf9,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x20,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,0xf0,0xa4,0xb3,0x95,0x4b,0xab,0x64, + 0x94,0xe7,0xa9,0x8a,0x69,0x27,0x6d,0x28,0x77,0x84,0x8d,0x3f,0xaf,0x7d,0x3c,0x39, + 0x31,0xc4,0xcb,0x53,0x6d,0xc0,0x0d,0xdf,0x08,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,0x04,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0x7e,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,0x89,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07,0x7a,0x60,0x87,0x7c, + 0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72,0x68,0x03,0x72,0x48, + 0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90,0x07,0x7a,0x68,0x03, + 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc,0x21,0x1c,0xd8,0x61, + 0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81,0x1d,0xca,0xa1,0x0d, + 0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d,0xd8,0x61,0x1e,0x00, + 0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87,0x79,0x98,0x87,0x36, + 0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,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,0x00,0x7a,0x90, + 0x87,0x7a,0x28,0x07,0x60,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,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36,0x60,0x87,0x72,0x08, + 0x07,0x73,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,0xd4,0xa1,0x1e,0xda,0x01,0x1e,0xda,0x80,0x1e, + 0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,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,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c,0xc8,0xa1,0x0d,0xf4, + 0xa1,0x1c,0xe4,0xe1,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,0xa0, + 0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68,0x83,0x76,0x08,0x07, + 0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6,0x81,0x1e,0xc2,0x61, + 0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d, + 0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e,0xda,0x60,0x1e,0xd2, + 0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87,0x70,0x30,0x87,0x72, + 0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e, + 0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e,0xde,0xc1,0x1c,0xe8, + 0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 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,0x05,0x49,0x18,0x00,0x00,0x03,0x00,0x00, + 0x00,0x13,0x86,0x40,0x18,0x26,0x0c,0x44,0x61,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,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,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,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,0x01,0x12,0x00,0x00,0x79,0x18,0x00, + 0x00,0xb9,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,0xe7,0x20,0x08, + 0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed, + 0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c, + 0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45, + 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,0xc1,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,0x98, + 0x70,0x48,0x07,0x79,0x70,0x03,0x73,0x90,0x87,0x70,0x38,0x87,0x76,0x28,0x07,0x77, + 0xa0,0x87,0x29,0xc1,0x1a,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59, + 0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b, + 0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,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,0x0c,0x00,0x00,0x00,0x13,0x04,0x41, + 0x2c,0x10,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0xc4,0x46,0x00,0x48,0x8d,0x00,0xd4, + 0x00,0x89,0x19,0x00,0x02,0x23,0x00,0x00,0x00,0x23,0x06,0x8a,0x10,0x44,0x87,0x91, + 0x0c,0x05,0x11,0x58,0x90,0xc8,0x67,0xb6,0x81,0x08,0x80,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const char _simgui_vs_source_metal_sim[672] = { + 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,0x32,0x20,0x64,0x69,0x73,0x70,0x5f,0x73,0x69,0x7a,0x65,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,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,0x2c,0x20,0x63,0x6f,0x6e,0x73,0x74,0x61, + 0x6e,0x74,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x26,0x20,0x5f,0x32, + 0x32,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,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x28,0x28,0x69,0x6e,0x2e,0x70,0x6f,0x73, + 0x69,0x74,0x69,0x6f,0x6e,0x20,0x2f,0x20,0x5f,0x32,0x32,0x2e,0x64,0x69,0x73,0x70, + 0x5f,0x73,0x69,0x7a,0x65,0x29,0x20,0x2d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28, + 0x30,0x2e,0x35,0x29,0x29,0x20,0x2a,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x32, + 0x2e,0x30,0x2c,0x20,0x2d,0x32,0x2e,0x30,0x29,0x2c,0x20,0x30,0x2e,0x35,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, + +}; +static const char _simgui_fs_source_metal_sim[436] = { + 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,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 const uint8_t _simgui_vs_bytecode_hlsl4[892] = { + 0x44,0x58,0x42,0x43,0x0d,0xbd,0x9e,0x9e,0x7d,0xc0,0x2b,0x54,0x88,0xf9,0xca,0x89, + 0x32,0xe4,0x0c,0x59,0x01,0x00,0x00,0x00,0x7c,0x03,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0xfc,0x00,0x00,0x00,0x60,0x01,0x00,0x00,0xd0,0x01,0x00,0x00, + 0x00,0x03,0x00,0x00,0x52,0x44,0x45,0x46,0xc0,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,0x98,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,0x01,0x00,0x00,0x00,0x60,0x00,0x00,0x00, + 0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x88,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x5f,0x32,0x32,0x5f,0x64,0x69,0x73,0x70,0x5f,0x73,0x69,0x7a, + 0x65,0x00,0xab,0xab,0x01,0x00,0x03,0x00,0x01,0x00,0x02,0x00,0x00,0x00,0x00,0x00, + 0x00,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,0x28,0x01,0x00,0x00,0x40,0x00,0x01,0x00,0x4a,0x00,0x00,0x00, + 0x59,0x00,0x00,0x04,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x01,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,0x68,0x00,0x00,0x02, + 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,0x0e,0x00,0x00,0x08, + 0x32,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00,0x00,0x00,0x00,0x00, + 0x46,0x80,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a, + 0x32,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x02,0x40,0x00,0x00,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x38,0x00,0x00,0x0a,0x32,0x20,0x10,0x00,0x02,0x00,0x00,0x00, + 0x46,0x00,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,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,0x3f,0x00,0x00,0x80,0x3f,0x3e,0x00,0x00,0x01, + 0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x03,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, +}; +static const uint8_t _simgui_fs_bytecode_hlsl4[608] = { + 0x44,0x58,0x42,0x43,0x3a,0xa7,0x41,0x21,0xb4,0x2d,0xa7,0x6e,0xfe,0x31,0xb0,0xe0, + 0x14,0xe0,0xdf,0x5a,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, + 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) +static const char _simgui_vs_source_wgsl[1083] = { + 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,0x64,0x69,0x73,0x70,0x5f,0x73,0x69,0x7a,0x65,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x32,0x66,0x2c,0x0a,0x7d,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,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,0x32,0x32, + 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,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, + 0x35,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x78,0x5f,0x32,0x32, + 0x2e,0x64,0x69,0x73,0x70,0x5f,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x6c,0x65, + 0x74,0x20,0x78,0x5f,0x33,0x33,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d, + 0x20,0x28,0x28,0x28,0x78,0x5f,0x31,0x39,0x20,0x2f,0x20,0x78,0x5f,0x32,0x35,0x29, + 0x20,0x2d,0x20,0x76,0x65,0x63,0x32,0x66,0x28,0x30,0x2e,0x35,0x66,0x2c,0x20,0x30, + 0x2e,0x35,0x66,0x29,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x32,0x66,0x28,0x32,0x2e, + 0x30,0x66,0x2c,0x20,0x2d,0x32,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,0x33,0x33,0x2e,0x78,0x2c,0x20,0x78,0x5f,0x33,0x33,0x2e, + 0x79,0x2c,0x20,0x30,0x2e,0x35,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x3b,0x0a, + 0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x33,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,0x34,0x33,0x3b,0x0a,0x20,0x20,0x6c, + 0x65,0x74,0x20,0x78,0x5f,0x34,0x37,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,0x37,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, +}; +static const char _simgui_fs_source_wgsl[630] = { + 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,0x33,0x32, + 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,0x34,0x38,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,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,0x34,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,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* _simgui_vs_source_dummy = ""; +static const char* _simgui_fs_source_dummy = ""; +#else +#error "Please define one of SOKOL_GLCORE33, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND!" +#endif + +#if !defined(SOKOL_IMGUI_NO_SOKOL_APP) +static void _simgui_set_clipboard(void* user_data, const char* text) { + (void)user_data; + sapp_set_clipboard_string(text); +} + +static const char* _simgui_get_clipboard(void* user_data) { + (void)user_data; + return sapp_get_clipboard_string(); +} +#endif + +#if defined(__EMSCRIPTEN__) && !defined(SOKOL_DUMMY_BACKEND) +EM_JS(int, simgui_js_is_osx, (void), { + if (navigator.userAgent.includes('Macintosh')) { + return 1; + } else { + return 0; + } +}); +#endif + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SIMGUI_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _simgui_log_messages[] = { + _SIMGUI_LOG_ITEMS +}; +#undef _SIMGUI_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SIMGUI_PANIC(code) _simgui_log(SIMGUI_LOGITEM_ ##code, 0, 0, __LINE__) +#define _SIMGUI_ERROR(code) _simgui_log(SIMGUI_LOGITEM_ ##code, 1, 0, __LINE__) +#define _SIMGUI_WARN(code) _simgui_log(SIMGUI_LOGITEM_ ##code, 2, 0, __LINE__) +#define _SIMGUI_INFO(code) _simgui_log(SIMGUI_LOGITEM_ ##code, 3, 0, __LINE__) +#define _SIMGUI_LOGMSG(code,msg) _simgui_log(SIMGUI_LOGITEM_ ##code, 3, msg, __LINE__) + +static void _simgui_log(simgui_log_item_t log_item, uint32_t log_level, const char* msg, uint32_t line_nr) { + if (_simgui.desc.logger.func) { + const char* filename = 0; + #if defined(SOKOL_DEBUG) + filename = __FILE__; + if (0 == msg) { + msg = _simgui_log_messages[log_item]; + } + #endif + _simgui.desc.logger.func("simgui", log_level, log_item, msg, line_nr, filename, _simgui.desc.logger.user_data); + } else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory +static void _simgui_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +static void* _simgui_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_simgui.desc.allocator.alloc_fn) { + ptr = _simgui.desc.allocator.alloc_fn(size, _simgui.desc.allocator.user_data); + } else { + ptr = malloc(size); + } + if (0 == ptr) { + _SIMGUI_PANIC(MALLOC_FAILED); + } + return ptr; +} + +static void* _simgui_malloc_clear(size_t size) { + void* ptr = _simgui_malloc(size); + _simgui_clear(ptr, size); + return ptr; +} + +static void _simgui_free(void* ptr) { + if (_simgui.desc.allocator.free_fn) { + _simgui.desc.allocator.free_fn(ptr, _simgui.desc.allocator.user_data); + } else { + free(ptr); + } +} + +// ██████ ██████ ██████ ██ +// ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ +// +// >>pool +static void _simgui_init_pool(_simgui_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*) _simgui_malloc_clear(gen_ctrs_size); + // it's not a bug to only reserve 'num' here + pool->free_queue = (int*) _simgui_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 _simgui_discard_pool(_simgui_pool_t* pool) { + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + _simgui_free(pool->free_queue); + pool->free_queue = 0; + SOKOL_ASSERT(pool->gen_ctrs); + _simgui_free(pool->gen_ctrs); + pool->gen_ctrs = 0; + pool->size = 0; + pool->queue_top = 0; +} + +static int _simgui_pool_alloc_index(_simgui_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 _SIMGUI_INVALID_SLOT_INDEX; + } +} + +static void _simgui_pool_free_index(_simgui_pool_t* pool, int slot_index) { + SOKOL_ASSERT((slot_index > _SIMGUI_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)); +} + +/* initialize a pool slot: + - 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 handle id +*/ +static uint32_t _simgui_slot_init(_simgui_pool_t* pool, _simgui_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 > _SIMGUI_INVALID_SLOT_INDEX) && (slot_index < pool->size)); + SOKOL_ASSERT((slot->state == _SIMGUI_RESOURCESTATE_INITIAL) && (slot->id == SIMGUI_INVALID_ID)); + uint32_t ctr = ++pool->gen_ctrs[slot_index]; + slot->id = (ctr<<_SIMGUI_SLOT_SHIFT)|(slot_index & _SIMGUI_SLOT_MASK); + slot->state = _SIMGUI_RESOURCESTATE_ALLOC; + return slot->id; +} + +// extract slot index from id +static int _simgui_slot_index(uint32_t id) { + int slot_index = (int) (id & _SIMGUI_SLOT_MASK); + SOKOL_ASSERT(_SIMGUI_INVALID_SLOT_INDEX != slot_index); + return slot_index; +} + +static void _simgui_init_item_pool(_simgui_pool_t* pool, int pool_size, void** items_ptr, size_t item_size_bytes) { + // NOTE: the pools will have an additional item, since slot 0 is reserved + SOKOL_ASSERT(pool && (pool->size == 0)); + SOKOL_ASSERT((pool_size > 0) && (pool_size < _SIMGUI_MAX_POOL_SIZE)); + SOKOL_ASSERT(items_ptr && (*items_ptr == 0)); + SOKOL_ASSERT(item_size_bytes > 0); + _simgui_init_pool(pool, pool_size); + const size_t pool_size_bytes = item_size_bytes * (size_t)pool->size; + *items_ptr = _simgui_malloc_clear(pool_size_bytes); +} + +static void _simgui_discard_item_pool(_simgui_pool_t* pool, void** items_ptr) { + SOKOL_ASSERT(pool && (pool->size != 0)); + SOKOL_ASSERT(items_ptr && (*items_ptr != 0)); + _simgui_free(*items_ptr); *items_ptr = 0; + _simgui_discard_pool(pool); +} + +static void _simgui_setup_image_pool(int pool_size) { + _simgui_image_pool_t* p = &_simgui.image_pool; + _simgui_init_item_pool(&p->pool, pool_size, (void**)&p->items, sizeof(_simgui_image_t)); +} + +static void _simgui_discard_image_pool(void) { + _simgui_image_pool_t* p = &_simgui.image_pool; + _simgui_discard_item_pool(&p->pool, (void**)&p->items); +} + +static simgui_image_t _simgui_make_image_handle(uint32_t id) { + simgui_image_t handle = { id }; + return handle; +} + +static _simgui_image_t* _simgui_image_at(uint32_t id) { + SOKOL_ASSERT(SIMGUI_INVALID_ID != id); + const _simgui_image_pool_t* p = &_simgui.image_pool; + int slot_index = _simgui_slot_index(id); + SOKOL_ASSERT((slot_index > _SIMGUI_INVALID_SLOT_INDEX) && (slot_index < p->pool.size)); + return &p->items[slot_index]; +} + +static _simgui_image_t* _simgui_lookup_image(uint32_t id) { + if (SIMGUI_INVALID_ID != id) { + _simgui_image_t* img = _simgui_image_at(id); + if (img->slot.id == id) { + return img; + } + } + return 0; +} + +static simgui_image_t _simgui_alloc_image(void) { + _simgui_image_pool_t* p = &_simgui.image_pool; + int slot_index = _simgui_pool_alloc_index(&p->pool); + if (_SIMGUI_INVALID_SLOT_INDEX != slot_index) { + uint32_t id = _simgui_slot_init(&p->pool, &p->items[slot_index].slot, slot_index); + return _simgui_make_image_handle(id); + } else { + // pool exhausted + return _simgui_make_image_handle(SIMGUI_INVALID_ID); + } +} + +static _simgui_resource_state _simgui_init_image(_simgui_image_t* img, const simgui_image_desc_t* desc) { + SOKOL_ASSERT(img && (img->slot.state == _SIMGUI_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + SOKOL_ASSERT(_simgui.def_pip.id != SIMGUI_INVALID_ID); + SOKOL_ASSERT(_simgui.pip_unfilterable.id != SIMGUI_INVALID_ID); + img->image = desc->image; + img->sampler = desc->sampler; + if (sg_query_pixelformat(sg_query_image_desc(desc->image).pixel_format).filter) { + img->pip = _simgui.def_pip; + } else { + img->pip = _simgui.pip_unfilterable; + } + return _SIMGUI_RESOURCESTATE_VALID; +} + +static void _simgui_deinit_image(_simgui_image_t* img) { + SOKOL_ASSERT(img); + img->image.id = SIMGUI_INVALID_ID; + img->sampler.id = SIMGUI_INVALID_ID; + img->pip.id = SIMGUI_INVALID_ID; +} + +static void _simgui_destroy_image(simgui_image_t img_id) { + _simgui_image_t* img = _simgui_lookup_image(img_id.id); + if (img) { + _simgui_deinit_image(img); + _simgui_image_pool_t* p = &_simgui.image_pool; + _simgui_clear(img, sizeof(_simgui_image_t)); + _simgui_pool_free_index(&p->pool, _simgui_slot_index(img_id.id)); + } +} + +static void _simgui_destroy_all_images(void) { + _simgui_image_pool_t* p = &_simgui.image_pool; + for (int i = 0; i < p->pool.size; i++) { + _simgui_image_t* img = &p->items[i]; + _simgui_destroy_image(_simgui_make_image_handle(img->slot.id)); + } +} + +static simgui_image_desc_t _simgui_image_desc_defaults(const simgui_image_desc_t* desc) { + SOKOL_ASSERT(desc); + simgui_image_desc_t res = *desc; + res.image.id = _simgui_def(res.image.id, _simgui.def_img.id); + res.sampler.id = _simgui_def(res.sampler.id, _simgui.def_smp.id); + return res; +} + +static bool _simgui_is_osx(void) { + #if defined(SOKOL_DUMMY_BACKEND) + return false; + #elif defined(__EMSCRIPTEN__) + return simgui_js_is_osx(); + #elif defined(__APPLE__) + return true; + #else + return false; + #endif +} + +static simgui_desc_t _simgui_desc_defaults(const simgui_desc_t* desc) { + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + simgui_desc_t res = *desc; + res.max_vertices = _simgui_def(res.max_vertices, 65536); + res.image_pool_size = _simgui_def(res.image_pool_size, 256); + return res; +} + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +SOKOL_API_IMPL void simgui_setup(const simgui_desc_t* desc) { + SOKOL_ASSERT(desc); + _simgui_clear(&_simgui, sizeof(_simgui)); + _simgui.init_cookie = _SIMGUI_INIT_COOKIE; + _simgui.desc = _simgui_desc_defaults(desc); + _simgui.cur_dpi_scale = 1.0f; + #if !defined(SOKOL_IMGUI_NO_SOKOL_APP) + _simgui.is_osx = _simgui_is_osx(); + #endif + // can keep color_format, depth_format and sample_count as is, + // since sokol_gfx.h will do its own default-value handling + + // setup image pool + _simgui_setup_image_pool(_simgui.desc.image_pool_size); + + // allocate an intermediate vertex- and index-buffer + SOKOL_ASSERT(_simgui.desc.max_vertices > 0); + _simgui.vertices.size = (size_t)_simgui.desc.max_vertices * sizeof(ImDrawVert); + _simgui.vertices.ptr = _simgui_malloc(_simgui.vertices.size); + _simgui.indices.size = (size_t)_simgui.desc.max_vertices * 3 * sizeof(ImDrawIdx); + _simgui.indices.ptr = _simgui_malloc(_simgui.indices.size); + + // initialize Dear ImGui + #if defined(__cplusplus) + ImGui::CreateContext(); + ImGui::StyleColorsDark(); + ImGuiIO* io = &ImGui::GetIO(); + if (!_simgui.desc.no_default_font) { + io->Fonts->AddFontDefault(); + } + #else + igCreateContext(NULL); + igStyleColorsDark(igGetStyle()); + ImGuiIO* io = igGetIO(); + if (!_simgui.desc.no_default_font) { + ImFontAtlas_AddFontDefault(io->Fonts, NULL); + } + #endif + io->IniFilename = _simgui.desc.ini_filename; + io->ConfigMacOSXBehaviors = _simgui_is_osx(); + io->BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; + #if !defined(SOKOL_IMGUI_NO_SOKOL_APP) + if (!_simgui.desc.disable_set_mouse_cursor) { + io->BackendFlags |= ImGuiBackendFlags_HasMouseCursors; + } + io->SetClipboardTextFn = _simgui_set_clipboard; + io->GetClipboardTextFn = _simgui_get_clipboard; + #endif + io->ConfigWindowsResizeFromEdges = !_simgui.desc.disable_windows_resize_from_edges; + + // create sokol-gfx resources + sg_push_debug_group("sokol-imgui"); + + // shader object for using the embedded shader source (or bytecode) + sg_shader_desc shd_desc; + _simgui_clear(&shd_desc, sizeof(shd_desc)); + shd_desc.attrs[0].name = "position"; + shd_desc.attrs[1].name = "texcoord0"; + shd_desc.attrs[2].name = "color0"; + shd_desc.attrs[0].sem_name = "TEXCOORD"; + shd_desc.attrs[0].sem_index = 0; + shd_desc.attrs[1].sem_name = "TEXCOORD"; + shd_desc.attrs[1].sem_index = 1; + shd_desc.attrs[2].sem_name = "TEXCOORD"; + shd_desc.attrs[2].sem_index = 2; + sg_shader_uniform_block_desc* ub = &shd_desc.vs.uniform_blocks[0]; + ub->size = sizeof(_simgui_vs_params_t); + ub->uniforms[0].name = "vs_params"; + ub->uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; + ub->uniforms[0].array_count = 1; + shd_desc.fs.images[0].used = true; + shd_desc.fs.images[0].image_type = SG_IMAGETYPE_2D; + shd_desc.fs.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + shd_desc.fs.samplers[0].used = true; + shd_desc.fs.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + shd_desc.fs.image_sampler_pairs[0].used = true; + shd_desc.fs.image_sampler_pairs[0].image_slot = 0; + shd_desc.fs.image_sampler_pairs[0].sampler_slot = 0; + shd_desc.fs.image_sampler_pairs[0].glsl_name = "tex_smp"; + shd_desc.label = "sokol-imgui-shader"; + #if defined(SOKOL_GLCORE33) + shd_desc.vs.source = _simgui_vs_source_glsl330; + shd_desc.fs.source = _simgui_fs_source_glsl330; + #elif defined(SOKOL_GLES3) + shd_desc.vs.source = _simgui_vs_source_glsl300es; + shd_desc.fs.source = _simgui_fs_source_glsl300es; + #elif defined(SOKOL_METAL) + shd_desc.vs.entry = "main0"; + shd_desc.fs.entry = "main0"; + switch (sg_query_backend()) { + case SG_BACKEND_METAL_MACOS: + shd_desc.vs.bytecode = SG_RANGE(_simgui_vs_bytecode_metal_macos); + shd_desc.fs.bytecode = SG_RANGE(_simgui_fs_bytecode_metal_macos); + break; + case SG_BACKEND_METAL_IOS: + shd_desc.vs.bytecode = SG_RANGE(_simgui_vs_bytecode_metal_ios); + shd_desc.fs.bytecode = SG_RANGE(_simgui_fs_bytecode_metal_ios); + break; + default: + shd_desc.vs.source = _simgui_vs_source_metal_sim; + shd_desc.fs.source = _simgui_fs_source_metal_sim; + break; + } + #elif defined(SOKOL_D3D11) + shd_desc.vs.bytecode = SG_RANGE(_simgui_vs_bytecode_hlsl4); + shd_desc.fs.bytecode = SG_RANGE(_simgui_fs_bytecode_hlsl4); + #elif defined(SOKOL_WGPU) + shd_desc.vs.source = _simgui_vs_source_wgsl; + shd_desc.fs.source = _simgui_fs_source_wgsl; + #else + shd_desc.vs.source = _simgui_vs_source_dummy; + shd_desc.fs.source = _simgui_fs_source_dummy; + #endif + _simgui.def_shd = sg_make_shader(&shd_desc); + + // pipeline object for imgui rendering + sg_pipeline_desc pip_desc; + _simgui_clear(&pip_desc, sizeof(pip_desc)); + pip_desc.layout.buffers[0].stride = sizeof(ImDrawVert); + { + sg_vertex_attr_state* attr = &pip_desc.layout.attrs[0]; + attr->offset = offsetof(ImDrawVert, pos); + attr->format = SG_VERTEXFORMAT_FLOAT2; + } + { + sg_vertex_attr_state* attr = &pip_desc.layout.attrs[1]; + attr->offset = offsetof(ImDrawVert, uv); + attr->format = SG_VERTEXFORMAT_FLOAT2; + } + { + sg_vertex_attr_state* attr = &pip_desc.layout.attrs[2]; + attr->offset = offsetof(ImDrawVert, col); + attr->format = SG_VERTEXFORMAT_UBYTE4N; + } + pip_desc.shader = _simgui.def_shd; + pip_desc.index_type = SG_INDEXTYPE_UINT16; + pip_desc.sample_count = _simgui.desc.sample_count; + pip_desc.depth.pixel_format = _simgui.desc.depth_format; + pip_desc.colors[0].pixel_format = _simgui.desc.color_format; + pip_desc.colors[0].write_mask = _simgui.desc.write_alpha_channel ? SG_COLORMASK_RGBA : SG_COLORMASK_RGB; + 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; + if (_simgui.desc.write_alpha_channel) { + pip_desc.colors[0].blend.src_factor_alpha = SG_BLENDFACTOR_ONE; + pip_desc.colors[0].blend.dst_factor_alpha = SG_BLENDFACTOR_ONE; + } + pip_desc.label = "sokol-imgui-pipeline"; + _simgui.def_pip = sg_make_pipeline(&pip_desc); + + // create a unfilterable/nonfiltering variants of the shader and pipeline + shd_desc.fs.images[0].sample_type = SG_IMAGESAMPLETYPE_UNFILTERABLE_FLOAT; + shd_desc.fs.samplers[0].sampler_type = SG_SAMPLERTYPE_NONFILTERING; + shd_desc.label = "sokol-imgui-shader-unfilterable"; + _simgui.shd_unfilterable = sg_make_shader(&shd_desc); + pip_desc.shader = _simgui.shd_unfilterable; + pip_desc.label = "sokol-imgui-pipeline-unfilterable"; + _simgui.pip_unfilterable = sg_make_pipeline(&pip_desc); + + // NOTE: since we're in C++ mode here we can't use C99 designated init + sg_buffer_desc vb_desc; + _simgui_clear(&vb_desc, sizeof(vb_desc)); + vb_desc.usage = SG_USAGE_STREAM; + vb_desc.size = _simgui.vertices.size; + vb_desc.label = "sokol-imgui-vertices"; + _simgui.vbuf = sg_make_buffer(&vb_desc); + + sg_buffer_desc ib_desc; + _simgui_clear(&ib_desc, sizeof(ib_desc)); + ib_desc.type = SG_BUFFERTYPE_INDEXBUFFER; + ib_desc.usage = SG_USAGE_STREAM; + ib_desc.size = _simgui.indices.size; + ib_desc.label = "sokol-imgui-indices"; + _simgui.ibuf = sg_make_buffer(&ib_desc); + + // a default user-image sampler + sg_sampler_desc def_sampler_desc; + _simgui_clear(&def_sampler_desc, sizeof(def_sampler_desc)); + def_sampler_desc.min_filter = SG_FILTER_NEAREST; + def_sampler_desc.mag_filter = SG_FILTER_NEAREST; + def_sampler_desc.wrap_u = SG_WRAP_CLAMP_TO_EDGE; + def_sampler_desc.wrap_v = SG_WRAP_CLAMP_TO_EDGE; + def_sampler_desc.label = "sokol-imgui-default-sampler"; + _simgui.def_smp = sg_make_sampler(&def_sampler_desc); + + // a default user image + static uint32_t def_pixels[64]; + memset(def_pixels, 0xFF, sizeof(def_pixels)); + sg_image_desc def_image_desc; + _simgui_clear(&def_image_desc, sizeof(def_image_desc)); + def_image_desc.width = 8; + def_image_desc.height = 8; + def_image_desc.pixel_format = SG_PIXELFORMAT_RGBA8; + def_image_desc.data.subimage[0][0].ptr = def_pixels; + def_image_desc.data.subimage[0][0].size = sizeof(def_pixels); + def_image_desc.label = "sokol-imgui-default-image"; + _simgui.def_img = sg_make_image(&def_image_desc); + + // default font texture + if (!_simgui.desc.no_default_font) { + simgui_font_tex_desc_t simgui_font_smp_desc; + _simgui_clear(&simgui_font_smp_desc, sizeof(simgui_font_smp_desc)); + simgui_create_fonts_texture(&simgui_font_smp_desc); + } + + sg_pop_debug_group(); +} + +SOKOL_API_IMPL void simgui_create_fonts_texture(const simgui_font_tex_desc_t* desc) { + SOKOL_ASSERT(desc); + + #if defined(__cplusplus) + ImGuiIO* io = &ImGui::GetIO(); + #else + ImGuiIO* io = igGetIO(); + #endif + + // a default font sampler + sg_sampler_desc font_smp_desc; + _simgui_clear(&font_smp_desc, sizeof(font_smp_desc)); + font_smp_desc.wrap_u = SG_WRAP_CLAMP_TO_EDGE; + font_smp_desc.wrap_v = SG_WRAP_CLAMP_TO_EDGE; + font_smp_desc.min_filter = desc->min_filter; + font_smp_desc.mag_filter = desc->mag_filter; + font_smp_desc.mipmap_filter = SG_FILTER_NONE; + font_smp_desc.label = "sokol-imgui-font-sampler"; + _simgui.font_smp = sg_make_sampler(&font_smp_desc); + + unsigned char* font_pixels; + int font_width, font_height; + #if defined(__cplusplus) + io->Fonts->GetTexDataAsRGBA32(&font_pixels, &font_width, &font_height); + #else + int bytes_per_pixel; + ImFontAtlas_GetTexDataAsRGBA32(io->Fonts, &font_pixels, &font_width, &font_height, &bytes_per_pixel); + #endif + sg_image_desc font_img_desc; + _simgui_clear(&font_img_desc, sizeof(font_img_desc)); + font_img_desc.width = font_width; + font_img_desc.height = font_height; + font_img_desc.pixel_format = SG_PIXELFORMAT_RGBA8; + font_img_desc.data.subimage[0][0].ptr = font_pixels; + font_img_desc.data.subimage[0][0].size = (size_t)(font_width * font_height) * sizeof(uint32_t); + font_img_desc.label = "sokol-imgui-font-image"; + _simgui.font_img = sg_make_image(&font_img_desc); + + simgui_image_desc_t img_desc; + _simgui_clear(&img_desc, sizeof(img_desc)); + img_desc.image = _simgui.font_img; + img_desc.sampler = _simgui.font_smp; + _simgui.default_font = simgui_make_image(&img_desc); + io->Fonts->TexID = simgui_imtextureid(_simgui.default_font); +} + +SOKOL_API_IMPL void simgui_destroy_fonts_texture(void) { + // NOTE: it's valid to call the destroy funcs with SG_INVALID_ID + sg_destroy_sampler(_simgui.font_smp); + sg_destroy_image(_simgui.font_img); + simgui_destroy_image(_simgui.default_font); +} + +SOKOL_API_IMPL void simgui_shutdown(void) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + #if defined(__cplusplus) + ImGui::DestroyContext(); + #else + igDestroyContext(0); + #endif + // NOTE: it's valid to call the destroy funcs with SG_INVALID_ID + sg_destroy_pipeline(_simgui.pip_unfilterable); + sg_destroy_shader(_simgui.shd_unfilterable); + sg_destroy_pipeline(_simgui.def_pip); + sg_destroy_shader(_simgui.def_shd); + sg_destroy_sampler(_simgui.font_smp); + sg_destroy_image(_simgui.font_img); + sg_destroy_sampler(_simgui.def_smp); + sg_destroy_image(_simgui.def_img); + sg_destroy_buffer(_simgui.ibuf); + sg_destroy_buffer(_simgui.vbuf); + sg_pop_debug_group(); + sg_push_debug_group("sokol-imgui"); + _simgui_destroy_all_images(); + _simgui_discard_image_pool(); + SOKOL_ASSERT(_simgui.vertices.ptr); + _simgui_free((void*)_simgui.vertices.ptr); + SOKOL_ASSERT(_simgui.indices.ptr); + _simgui_free((void*)_simgui.indices.ptr); + _simgui.init_cookie = 0; +} + +SOKOL_API_IMPL simgui_image_t simgui_make_image(const simgui_image_desc_t* desc) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + SOKOL_ASSERT(desc); + const simgui_image_desc_t desc_def = _simgui_image_desc_defaults(desc); + simgui_image_t img_id = _simgui_alloc_image(); + _simgui_image_t* img = _simgui_lookup_image(img_id.id); + if (img) { + img->slot.state = _simgui_init_image(img, &desc_def); + SOKOL_ASSERT((img->slot.state == _SIMGUI_RESOURCESTATE_VALID) || (img->slot.state == _SIMGUI_RESOURCESTATE_FAILED)); + } else { + _SIMGUI_ERROR(IMAGE_POOL_EXHAUSTED); + } + return img_id; +} + +SOKOL_API_IMPL void simgui_destroy_image(simgui_image_t img_id) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + _simgui_destroy_image(img_id); +} + +SOKOL_API_IMPL simgui_image_desc_t simgui_query_image_desc(simgui_image_t img_id) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + _simgui_image_t* img = _simgui_lookup_image(img_id.id); + simgui_image_desc_t desc; + _simgui_clear(&desc, sizeof(desc)); + if (img) { + desc.image = img->image; + desc.sampler = img->sampler; + } + return desc; +} + +SOKOL_API_IMPL void* simgui_imtextureid(simgui_image_t img) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + return (void*)(uintptr_t)img.id; +} + +SOKOL_API_IMPL simgui_image_t simgui_image_from_imtextureid(void* imtextureid) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + simgui_image_t img = { (uint32_t)(uintptr_t) imtextureid }; + return img; +} + +SOKOL_API_IMPL void simgui_new_frame(const simgui_frame_desc_t* desc) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + SOKOL_ASSERT(desc); + SOKOL_ASSERT(desc->width > 0); + SOKOL_ASSERT(desc->height > 0); + _simgui.cur_dpi_scale = _simgui_def(desc->dpi_scale, 1.0f); + #if defined(__cplusplus) + ImGuiIO* io = &ImGui::GetIO(); + #else + ImGuiIO* io = igGetIO(); + #endif + io->DisplaySize.x = ((float)desc->width) / _simgui.cur_dpi_scale; + io->DisplaySize.y = ((float)desc->height) / _simgui.cur_dpi_scale; + io->DeltaTime = (float)desc->delta_time; + #if !defined(SOKOL_IMGUI_NO_SOKOL_APP) + if (io->WantTextInput && !sapp_keyboard_shown()) { + sapp_show_keyboard(true); + } + if (!io->WantTextInput && sapp_keyboard_shown()) { + sapp_show_keyboard(false); + } + if (!_simgui.desc.disable_set_mouse_cursor) { + #if defined(__cplusplus) + ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); + #else + ImGuiMouseCursor imgui_cursor = igGetMouseCursor(); + #endif + sapp_mouse_cursor cursor = sapp_get_mouse_cursor(); + switch (imgui_cursor) { + case ImGuiMouseCursor_Arrow: cursor = SAPP_MOUSECURSOR_ARROW; break; + case ImGuiMouseCursor_TextInput: cursor = SAPP_MOUSECURSOR_IBEAM; break; + case ImGuiMouseCursor_ResizeAll: cursor = SAPP_MOUSECURSOR_RESIZE_ALL; break; + case ImGuiMouseCursor_ResizeNS: cursor = SAPP_MOUSECURSOR_RESIZE_NS; break; + case ImGuiMouseCursor_ResizeEW: cursor = SAPP_MOUSECURSOR_RESIZE_EW; break; + case ImGuiMouseCursor_ResizeNESW: cursor = SAPP_MOUSECURSOR_RESIZE_NESW; break; + case ImGuiMouseCursor_ResizeNWSE: cursor = SAPP_MOUSECURSOR_RESIZE_NWSE; break; + case ImGuiMouseCursor_Hand: cursor = SAPP_MOUSECURSOR_POINTING_HAND; break; + case ImGuiMouseCursor_NotAllowed: cursor = SAPP_MOUSECURSOR_NOT_ALLOWED; break; + default: break; + } + sapp_set_mouse_cursor(cursor); + } + #endif + #if defined(__cplusplus) + ImGui::NewFrame(); + #else + igNewFrame(); + #endif +} + +static const _simgui_image_t* _simgui_bind_image_sampler(sg_bindings* bindings, ImTextureID tex_id) { + const _simgui_image_t* img = _simgui_lookup_image((uint32_t)(uintptr_t)tex_id); + if (img) { + bindings->fs.images[0] = img->image; + bindings->fs.samplers[0] = img->sampler; + } else { + bindings->fs.images[0] = _simgui.def_img; + bindings->fs.samplers[0] = _simgui.def_smp; + } + return img; +} + +static ImDrawList* _simgui_imdrawlist_at(ImDrawData* draw_data, int cl_index) { + #if defined(__cplusplus) + return draw_data->CmdLists[cl_index]; + #else + return draw_data->CmdLists.Data[cl_index]; + #endif +} + +SOKOL_API_IMPL void simgui_render(void) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + #if defined(__cplusplus) + ImGui::Render(); + ImDrawData* draw_data = ImGui::GetDrawData(); + ImGuiIO* io = &ImGui::GetIO(); + #else + igRender(); + ImDrawData* draw_data = igGetDrawData(); + ImGuiIO* io = igGetIO(); + #endif + if (0 == draw_data) { + return; + } + if (draw_data->CmdListsCount == 0) { + return; + } + /* copy vertices and indices into an intermediate buffer so that + they can be updated with a single sg_update_buffer() call each + (sg_append_buffer() has performance problems on some GL platforms), + also keep track of valid number of command lists in case of a + buffer overflow + */ + size_t all_vtx_size = 0; + size_t all_idx_size = 0; + int cmd_list_count = 0; + for (int cl_index = 0; cl_index < draw_data->CmdListsCount; cl_index++, cmd_list_count++) { + ImDrawList* cl = _simgui_imdrawlist_at(draw_data, cl_index); + const size_t vtx_size = (size_t)cl->VtxBuffer.Size * sizeof(ImDrawVert); + const size_t idx_size = (size_t)cl->IdxBuffer.Size * sizeof(ImDrawIdx); + + // check for buffer overflow + if (((all_vtx_size + vtx_size) > _simgui.vertices.size) || + ((all_idx_size + idx_size) > _simgui.indices.size)) + { + break; + } + + // copy vertices and indices into common buffers + if (vtx_size > 0) { + const ImDrawVert* src_vtx_ptr = cl->VtxBuffer.Data; + void* dst_vtx_ptr = (void*) (((uint8_t*)_simgui.vertices.ptr) + all_vtx_size); + memcpy(dst_vtx_ptr, src_vtx_ptr, vtx_size); + } + if (idx_size > 0) { + const ImDrawIdx* src_idx_ptr = cl->IdxBuffer.Data; + void* dst_idx_ptr = (void*) (((uint8_t*)_simgui.indices.ptr) + all_idx_size); + memcpy(dst_idx_ptr, src_idx_ptr, idx_size); + } + all_vtx_size += vtx_size; + all_idx_size += idx_size; + } + if (0 == cmd_list_count) { + return; + } + + // update the sokol-gfx vertex- and index-buffer + sg_push_debug_group("sokol-imgui"); + if (all_vtx_size > 0) { + sg_range vtx_data = _simgui.vertices; + vtx_data.size = all_vtx_size; + sg_update_buffer(_simgui.vbuf, &vtx_data); + } + if (all_idx_size > 0) { + sg_range idx_data = _simgui.indices; + idx_data.size = all_idx_size; + sg_update_buffer(_simgui.ibuf, &idx_data); + } + + // render the ImGui command list + const float dpi_scale = _simgui.cur_dpi_scale; + const int fb_width = (int) (io->DisplaySize.x * dpi_scale); + const int fb_height = (int) (io->DisplaySize.y * dpi_scale); + sg_apply_viewport(0, 0, fb_width, fb_height, true); + sg_apply_scissor_rect(0, 0, fb_width, fb_height, true); + + sg_apply_pipeline(_simgui.def_pip); + _simgui_vs_params_t vs_params; + _simgui_clear((void*)&vs_params, sizeof(vs_params)); + vs_params.disp_size.x = io->DisplaySize.x; + vs_params.disp_size.y = io->DisplaySize.y; + sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(vs_params)); + sg_bindings bind; + _simgui_clear((void*)&bind, sizeof(bind)); + bind.vertex_buffers[0] = _simgui.vbuf; + bind.index_buffer = _simgui.ibuf; + ImTextureID tex_id = io->Fonts->TexID; + _simgui_bind_image_sampler(&bind, tex_id); + int vb_offset = 0; + int ib_offset = 0; + for (int cl_index = 0; cl_index < cmd_list_count; cl_index++) { + ImDrawList* cl = _simgui_imdrawlist_at(draw_data, cl_index); + + bind.vertex_buffer_offsets[0] = vb_offset; + bind.index_buffer_offset = ib_offset; + sg_apply_bindings(&bind); + + #if defined(__cplusplus) + const int num_cmds = cl->CmdBuffer.size(); + #else + const int num_cmds = cl->CmdBuffer.Size; + #endif + uint32_t vtx_offset = 0; + for (int cmd_index = 0; cmd_index < num_cmds; cmd_index++) { + ImDrawCmd* pcmd = &cl->CmdBuffer.Data[cmd_index]; + if (pcmd->UserCallback != 0) { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) + if (pcmd->UserCallback != ImDrawCallback_ResetRenderState) { + pcmd->UserCallback(cl, pcmd); + // need to re-apply all state after calling a user callback + sg_reset_state_cache(); + sg_apply_viewport(0, 0, fb_width, fb_height, true); + sg_apply_pipeline(_simgui.def_pip); + sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(vs_params)); + sg_apply_bindings(&bind); + } + } else { + if ((tex_id != pcmd->TextureId) || (vtx_offset != pcmd->VtxOffset)) { + tex_id = pcmd->TextureId; + vtx_offset = pcmd->VtxOffset; + const _simgui_image_t* img = _simgui_bind_image_sampler(&bind, tex_id); + if (img) { + sg_apply_pipeline(img->pip); + } else { + sg_apply_pipeline(_simgui.def_pip); + } + sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(vs_params)); + bind.vertex_buffer_offsets[0] = vb_offset + (int)(pcmd->VtxOffset * sizeof(ImDrawVert)); + sg_apply_bindings(&bind); + } + const int scissor_x = (int) (pcmd->ClipRect.x * dpi_scale); + const int scissor_y = (int) (pcmd->ClipRect.y * dpi_scale); + const int scissor_w = (int) ((pcmd->ClipRect.z - pcmd->ClipRect.x) * dpi_scale); + const int scissor_h = (int) ((pcmd->ClipRect.w - pcmd->ClipRect.y) * dpi_scale); + sg_apply_scissor_rect(scissor_x, scissor_y, scissor_w, scissor_h, true); + sg_draw((int)pcmd->IdxOffset, (int)pcmd->ElemCount, 1); + } + } + #if defined(__cplusplus) + const size_t vtx_size = (size_t)cl->VtxBuffer.size() * sizeof(ImDrawVert); + const size_t idx_size = (size_t)cl->IdxBuffer.size() * sizeof(ImDrawIdx); + #else + const size_t vtx_size = (size_t)cl->VtxBuffer.Size * sizeof(ImDrawVert); + const size_t idx_size = (size_t)cl->IdxBuffer.Size * sizeof(ImDrawIdx); + #endif + vb_offset += (int)vtx_size; + ib_offset += (int)idx_size; + } + sg_apply_viewport(0, 0, fb_width, fb_height, true); + sg_apply_scissor_rect(0, 0, fb_width, fb_height, true); + sg_pop_debug_group(); +} + +SOKOL_API_IMPL void simgui_add_focus_event(bool focus) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + #if defined(__cplusplus) + ImGuiIO* io = &ImGui::GetIO(); + io->AddFocusEvent(focus); + #else + ImGuiIO* io = igGetIO(); + ImGuiIO_AddFocusEvent(io, focus); + #endif +} + +SOKOL_API_IMPL void simgui_add_mouse_pos_event(float x, float y) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + #if defined(__cplusplus) + ImGuiIO* io = &ImGui::GetIO(); + #if (IMGUI_VERSION_NUM >= 18950) + io->AddMouseSourceEvent(ImGuiMouseSource_Mouse); + #endif + io->AddMousePosEvent(x, y); + #else + ImGuiIO* io = igGetIO(); + #if (IMGUI_VERSION_NUM >= 18950) + ImGuiIO_AddMouseSourceEvent(io, ImGuiMouseSource_Mouse); + #endif + ImGuiIO_AddMousePosEvent(io, x, y); + #endif +} + +SOKOL_API_IMPL void simgui_add_touch_pos_event(float x, float y) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + #if defined(__cplusplus) + ImGuiIO* io = &ImGui::GetIO(); + #if (IMGUI_VERSION_NUM >= 18950) + io->AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); + #endif + io->AddMousePosEvent(x, y); + #else + ImGuiIO* io = igGetIO(); + #if (IMGUI_VERSION_NUM >= 18950) + ImGuiIO_AddMouseSourceEvent(io, ImGuiMouseSource_TouchScreen); + #endif + ImGuiIO_AddMousePosEvent(io, x, y); + #endif +} + +SOKOL_API_IMPL void simgui_add_mouse_button_event(int mouse_button, bool down) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + #if defined(__cplusplus) + ImGuiIO* io = &ImGui::GetIO(); + #if (IMGUI_VERSION_NUM >= 18950) + io->AddMouseSourceEvent(ImGuiMouseSource_Mouse); + #endif + io->AddMouseButtonEvent(mouse_button, down); + #else + ImGuiIO* io = igGetIO(); + #if (IMGUI_VERSION_NUM >= 18950) + ImGuiIO_AddMouseSourceEvent(io, ImGuiMouseSource_Mouse); + #endif + ImGuiIO_AddMouseButtonEvent(io, mouse_button, down); + #endif +} + +SOKOL_API_IMPL void simgui_add_touch_button_event(int mouse_button, bool down) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + #if defined(__cplusplus) + ImGuiIO* io = &ImGui::GetIO(); + #if (IMGUI_VERSION_NUM >= 18950) + io->AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); + #endif + io->AddMouseButtonEvent(mouse_button, down); + #else + ImGuiIO* io = igGetIO(); + #if (IMGUI_VERSION_NUM >= 18950) + ImGuiIO_AddMouseSourceEvent(io, ImGuiMouseSource_TouchScreen); + #endif + ImGuiIO_AddMouseButtonEvent(io, mouse_button, down); + #endif +} + +SOKOL_API_IMPL void simgui_add_mouse_wheel_event(float wheel_x, float wheel_y) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + #if defined(__cplusplus) + ImGuiIO* io = &ImGui::GetIO(); + #if (IMGUI_VERSION_NUM >= 18950) + io->AddMouseSourceEvent(ImGuiMouseSource_Mouse); + #endif + io->AddMouseWheelEvent(wheel_x, wheel_y); + #else + ImGuiIO* io = igGetIO(); + #if (IMGUI_VERSION_NUM >= 18950) + ImGuiIO_AddMouseSourceEvent(io, ImGuiMouseSource_Mouse); + #endif + ImGuiIO_AddMouseWheelEvent(io, wheel_x, wheel_y); + #endif +} + +SOKOL_API_IMPL void simgui_add_key_event(int (*map_keycode)(int), int keycode, bool down) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + const ImGuiKey imgui_key = (ImGuiKey)map_keycode(keycode); + #if defined(__cplusplus) + ImGuiIO* io = &ImGui::GetIO(); + io->AddKeyEvent(imgui_key, down); + io->SetKeyEventNativeData(imgui_key, keycode, 0, -1); + #else + ImGuiIO* io = igGetIO(); + ImGuiIO_AddKeyEvent(io, imgui_key, down); + ImGuiIO_SetKeyEventNativeData(io, imgui_key, keycode, 0, -1); + #endif +} + +SOKOL_API_IMPL void simgui_add_input_character(uint32_t c) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + #if defined(__cplusplus) + ImGuiIO* io = &ImGui::GetIO(); + io->AddInputCharacter(c); + #else + ImGuiIO* io = igGetIO(); + ImGuiIO_AddInputCharacter(io, c); + #endif +} + +SOKOL_API_IMPL void simgui_add_input_characters_utf8(const char* c) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + #if defined(__cplusplus) + ImGuiIO* io = &ImGui::GetIO(); + io->AddInputCharactersUTF8(c); + #else + ImGuiIO* io = igGetIO(); + ImGuiIO_AddInputCharactersUTF8(io, c); + #endif +} + +#if !defined(SOKOL_IMGUI_NO_SOKOL_APP) +_SOKOL_PRIVATE bool _simgui_is_ctrl(uint32_t modifiers) { + if (_simgui.is_osx) { + return 0 != (modifiers & SAPP_MODIFIER_SUPER); + } else { + return 0 != (modifiers & SAPP_MODIFIER_CTRL); + } +} + +_SOKOL_PRIVATE ImGuiKey _simgui_map_keycode(sapp_keycode key) { + switch (key) { + case SAPP_KEYCODE_SPACE: return ImGuiKey_Space; + case SAPP_KEYCODE_APOSTROPHE: return ImGuiKey_Apostrophe; + case SAPP_KEYCODE_COMMA: return ImGuiKey_Comma; + case SAPP_KEYCODE_MINUS: return ImGuiKey_Minus; + case SAPP_KEYCODE_PERIOD: return ImGuiKey_Apostrophe; + case SAPP_KEYCODE_SLASH: return ImGuiKey_Slash; + case SAPP_KEYCODE_0: return ImGuiKey_0; + case SAPP_KEYCODE_1: return ImGuiKey_1; + case SAPP_KEYCODE_2: return ImGuiKey_2; + case SAPP_KEYCODE_3: return ImGuiKey_3; + case SAPP_KEYCODE_4: return ImGuiKey_4; + case SAPP_KEYCODE_5: return ImGuiKey_5; + case SAPP_KEYCODE_6: return ImGuiKey_6; + case SAPP_KEYCODE_7: return ImGuiKey_7; + case SAPP_KEYCODE_8: return ImGuiKey_8; + case SAPP_KEYCODE_9: return ImGuiKey_9; + case SAPP_KEYCODE_SEMICOLON: return ImGuiKey_Semicolon; + case SAPP_KEYCODE_EQUAL: return ImGuiKey_Equal; + case SAPP_KEYCODE_A: return ImGuiKey_A; + case SAPP_KEYCODE_B: return ImGuiKey_B; + case SAPP_KEYCODE_C: return ImGuiKey_C; + case SAPP_KEYCODE_D: return ImGuiKey_D; + case SAPP_KEYCODE_E: return ImGuiKey_E; + case SAPP_KEYCODE_F: return ImGuiKey_F; + case SAPP_KEYCODE_G: return ImGuiKey_G; + case SAPP_KEYCODE_H: return ImGuiKey_H; + case SAPP_KEYCODE_I: return ImGuiKey_I; + case SAPP_KEYCODE_J: return ImGuiKey_J; + case SAPP_KEYCODE_K: return ImGuiKey_K; + case SAPP_KEYCODE_L: return ImGuiKey_L; + case SAPP_KEYCODE_M: return ImGuiKey_M; + case SAPP_KEYCODE_N: return ImGuiKey_N; + case SAPP_KEYCODE_O: return ImGuiKey_O; + case SAPP_KEYCODE_P: return ImGuiKey_P; + case SAPP_KEYCODE_Q: return ImGuiKey_Q; + case SAPP_KEYCODE_R: return ImGuiKey_R; + case SAPP_KEYCODE_S: return ImGuiKey_S; + case SAPP_KEYCODE_T: return ImGuiKey_T; + case SAPP_KEYCODE_U: return ImGuiKey_U; + case SAPP_KEYCODE_V: return ImGuiKey_V; + case SAPP_KEYCODE_W: return ImGuiKey_W; + case SAPP_KEYCODE_X: return ImGuiKey_X; + case SAPP_KEYCODE_Y: return ImGuiKey_Y; + case SAPP_KEYCODE_Z: return ImGuiKey_Z; + case SAPP_KEYCODE_LEFT_BRACKET: return ImGuiKey_LeftBracket; + case SAPP_KEYCODE_BACKSLASH: return ImGuiKey_Backslash; + case SAPP_KEYCODE_RIGHT_BRACKET:return ImGuiKey_RightBracket; + case SAPP_KEYCODE_GRAVE_ACCENT: return ImGuiKey_GraveAccent; + case SAPP_KEYCODE_ESCAPE: return ImGuiKey_Escape; + case SAPP_KEYCODE_ENTER: return ImGuiKey_Enter; + case SAPP_KEYCODE_TAB: return ImGuiKey_Tab; + case SAPP_KEYCODE_BACKSPACE: return ImGuiKey_Backspace; + case SAPP_KEYCODE_INSERT: return ImGuiKey_Insert; + case SAPP_KEYCODE_DELETE: return ImGuiKey_Delete; + case SAPP_KEYCODE_RIGHT: return ImGuiKey_RightArrow; + case SAPP_KEYCODE_LEFT: return ImGuiKey_LeftArrow; + case SAPP_KEYCODE_DOWN: return ImGuiKey_DownArrow; + case SAPP_KEYCODE_UP: return ImGuiKey_UpArrow; + case SAPP_KEYCODE_PAGE_UP: return ImGuiKey_PageUp; + case SAPP_KEYCODE_PAGE_DOWN: return ImGuiKey_PageDown; + case SAPP_KEYCODE_HOME: return ImGuiKey_Home; + case SAPP_KEYCODE_END: return ImGuiKey_End; + case SAPP_KEYCODE_CAPS_LOCK: return ImGuiKey_CapsLock; + case SAPP_KEYCODE_SCROLL_LOCK: return ImGuiKey_ScrollLock; + case SAPP_KEYCODE_NUM_LOCK: return ImGuiKey_NumLock; + case SAPP_KEYCODE_PRINT_SCREEN: return ImGuiKey_PrintScreen; + case SAPP_KEYCODE_PAUSE: return ImGuiKey_Pause; + case SAPP_KEYCODE_F1: return ImGuiKey_F1; + case SAPP_KEYCODE_F2: return ImGuiKey_F2; + case SAPP_KEYCODE_F3: return ImGuiKey_F3; + case SAPP_KEYCODE_F4: return ImGuiKey_F4; + case SAPP_KEYCODE_F5: return ImGuiKey_F5; + case SAPP_KEYCODE_F6: return ImGuiKey_F6; + case SAPP_KEYCODE_F7: return ImGuiKey_F7; + case SAPP_KEYCODE_F8: return ImGuiKey_F8; + case SAPP_KEYCODE_F9: return ImGuiKey_F9; + case SAPP_KEYCODE_F10: return ImGuiKey_F10; + case SAPP_KEYCODE_F11: return ImGuiKey_F11; + case SAPP_KEYCODE_F12: return ImGuiKey_F12; + case SAPP_KEYCODE_KP_0: return ImGuiKey_Keypad0; + case SAPP_KEYCODE_KP_1: return ImGuiKey_Keypad1; + case SAPP_KEYCODE_KP_2: return ImGuiKey_Keypad2; + case SAPP_KEYCODE_KP_3: return ImGuiKey_Keypad3; + case SAPP_KEYCODE_KP_4: return ImGuiKey_Keypad4; + case SAPP_KEYCODE_KP_5: return ImGuiKey_Keypad5; + case SAPP_KEYCODE_KP_6: return ImGuiKey_Keypad6; + case SAPP_KEYCODE_KP_7: return ImGuiKey_Keypad7; + case SAPP_KEYCODE_KP_8: return ImGuiKey_Keypad8; + case SAPP_KEYCODE_KP_9: return ImGuiKey_Keypad9; + case SAPP_KEYCODE_KP_DECIMAL: return ImGuiKey_KeypadDecimal; + case SAPP_KEYCODE_KP_DIVIDE: return ImGuiKey_KeypadDivide; + case SAPP_KEYCODE_KP_MULTIPLY: return ImGuiKey_KeypadMultiply; + case SAPP_KEYCODE_KP_SUBTRACT: return ImGuiKey_KeypadSubtract; + case SAPP_KEYCODE_KP_ADD: return ImGuiKey_KeypadAdd; + case SAPP_KEYCODE_KP_ENTER: return ImGuiKey_KeypadEnter; + case SAPP_KEYCODE_KP_EQUAL: return ImGuiKey_KeypadEqual; + case SAPP_KEYCODE_LEFT_SHIFT: return ImGuiKey_LeftShift; + case SAPP_KEYCODE_LEFT_CONTROL: return ImGuiKey_LeftCtrl; + case SAPP_KEYCODE_LEFT_ALT: return ImGuiKey_LeftAlt; + case SAPP_KEYCODE_LEFT_SUPER: return ImGuiKey_LeftSuper; + case SAPP_KEYCODE_RIGHT_SHIFT: return ImGuiKey_RightShift; + case SAPP_KEYCODE_RIGHT_CONTROL:return ImGuiKey_RightCtrl; + case SAPP_KEYCODE_RIGHT_ALT: return ImGuiKey_RightAlt; + case SAPP_KEYCODE_RIGHT_SUPER: return ImGuiKey_RightSuper; + case SAPP_KEYCODE_MENU: return ImGuiKey_Menu; + default: return ImGuiKey_None; + } +} + +_SOKOL_PRIVATE void _simgui_add_sapp_key_event(ImGuiIO* io, sapp_keycode sapp_key, bool down) { + const ImGuiKey imgui_key = _simgui_map_keycode(sapp_key); + #if defined(__cplusplus) + io->AddKeyEvent(imgui_key, down); + io->SetKeyEventNativeData(imgui_key, (int)sapp_key, 0, -1); + #else + ImGuiIO_AddKeyEvent(io, imgui_key, down); + ImGuiIO_SetKeyEventNativeData(io, imgui_key, (int)sapp_key, 0, -1); + #endif +} + +_SOKOL_PRIVATE void _simgui_add_imgui_key_event(ImGuiIO* io, ImGuiKey imgui_key, bool down) { + #if defined(__cplusplus) + io->AddKeyEvent(imgui_key, down); + #else + ImGuiIO_AddKeyEvent(io, imgui_key, down); + #endif +} + +_SOKOL_PRIVATE void _simgui_update_modifiers(ImGuiIO* io, uint32_t mods) { + _simgui_add_imgui_key_event(io, ImGuiMod_Ctrl, (mods & SAPP_MODIFIER_CTRL) != 0); + _simgui_add_imgui_key_event(io, ImGuiMod_Shift, (mods & SAPP_MODIFIER_SHIFT) != 0); + _simgui_add_imgui_key_event(io, ImGuiMod_Alt, (mods & SAPP_MODIFIER_ALT) != 0); + _simgui_add_imgui_key_event(io, ImGuiMod_Super, (mods & SAPP_MODIFIER_SUPER) != 0); +} + +// returns Ctrl or Super, depending on platform +_SOKOL_PRIVATE ImGuiKey _simgui_copypaste_modifier(void) { + return _simgui.is_osx ? ImGuiMod_Super : ImGuiMod_Ctrl; +} + +SOKOL_API_IMPL int simgui_map_keycode(sapp_keycode keycode) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + return (int)_simgui_map_keycode(keycode); +} + +SOKOL_API_IMPL bool simgui_handle_event(const sapp_event* ev) { + SOKOL_ASSERT(_SIMGUI_INIT_COOKIE == _simgui.init_cookie); + const float dpi_scale = _simgui.cur_dpi_scale; + #if defined(__cplusplus) + ImGuiIO* io = &ImGui::GetIO(); + #else + ImGuiIO* io = igGetIO(); + #endif + switch (ev->type) { + case SAPP_EVENTTYPE_FOCUSED: + simgui_add_focus_event(true); + break; + case SAPP_EVENTTYPE_UNFOCUSED: + simgui_add_focus_event(false); + break; + case SAPP_EVENTTYPE_MOUSE_DOWN: + simgui_add_mouse_pos_event(ev->mouse_x / dpi_scale, ev->mouse_y / dpi_scale); + simgui_add_mouse_button_event((int)ev->mouse_button, true); + _simgui_update_modifiers(io, ev->modifiers); + break; + case SAPP_EVENTTYPE_MOUSE_UP: + simgui_add_mouse_pos_event(ev->mouse_x / dpi_scale, ev->mouse_y / dpi_scale); + simgui_add_mouse_button_event((int)ev->mouse_button, false); + _simgui_update_modifiers(io, ev->modifiers); + break; + case SAPP_EVENTTYPE_MOUSE_MOVE: + simgui_add_mouse_pos_event(ev->mouse_x / dpi_scale, ev->mouse_y / dpi_scale); + break; + case SAPP_EVENTTYPE_MOUSE_ENTER: + case SAPP_EVENTTYPE_MOUSE_LEAVE: + // FIXME: since the sokol_app.h emscripten backend doesn't support + // mouse capture, mouse buttons must be released when the mouse leaves the + // browser window, so that they don't "stick" when released outside the window. + // A cleaner solution would be a new sokol_app.h function to query + // "platform behaviour flags". + #if defined(__EMSCRIPTEN__) + for (int i = 0; i < SAPP_MAX_MOUSEBUTTONS; i++) { + simgui_add_mouse_button_event(i, false); + } + #endif + break; + case SAPP_EVENTTYPE_MOUSE_SCROLL: + simgui_add_mouse_wheel_event(ev->scroll_x, ev->scroll_y); + break; + case SAPP_EVENTTYPE_TOUCHES_BEGAN: + simgui_add_touch_pos_event(ev->touches[0].pos_x / dpi_scale, ev->touches[0].pos_y / dpi_scale); + simgui_add_touch_button_event(0, true); + break; + case SAPP_EVENTTYPE_TOUCHES_MOVED: + simgui_add_touch_pos_event(ev->touches[0].pos_x / dpi_scale, ev->touches[0].pos_y / dpi_scale); + break; + case SAPP_EVENTTYPE_TOUCHES_ENDED: + simgui_add_touch_pos_event(ev->touches[0].pos_x / dpi_scale, ev->touches[0].pos_y / dpi_scale); + simgui_add_touch_button_event(0, false); + break; + case SAPP_EVENTTYPE_TOUCHES_CANCELLED: + simgui_add_touch_button_event(0, false); + break; + case SAPP_EVENTTYPE_KEY_DOWN: + _simgui_update_modifiers(io, ev->modifiers); + // intercept Ctrl-V, this is handled via EVENTTYPE_CLIPBOARD_PASTED + if (!_simgui.desc.disable_paste_override) { + if (_simgui_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_V)) { + break; + } + } + // on web platform, don't forward Ctrl-X, Ctrl-V to the browser + if (_simgui_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_X)) { + sapp_consume_event(); + } + if (_simgui_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_C)) { + sapp_consume_event(); + } + // it's ok to add ImGuiKey_None key events + _simgui_add_sapp_key_event(io, ev->key_code, true); + break; + case SAPP_EVENTTYPE_KEY_UP: + _simgui_update_modifiers(io, ev->modifiers); + // intercept Ctrl-V, this is handled via EVENTTYPE_CLIPBOARD_PASTED + if (_simgui_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_V)) { + break; + } + // on web platform, don't forward Ctrl-X, Ctrl-V to the browser + if (_simgui_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_X)) { + sapp_consume_event(); + } + if (_simgui_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_C)) { + sapp_consume_event(); + } + // it's ok to add ImGuiKey_None key events + _simgui_add_sapp_key_event(io, ev->key_code, false); + break; + case SAPP_EVENTTYPE_CHAR: + /* on some platforms, special keys may be reported as + characters, which may confuse some ImGui widgets, + drop those, also don't forward characters if some + modifiers have been pressed + */ + _simgui_update_modifiers(io, ev->modifiers); + if ((ev->char_code >= 32) && + (ev->char_code != 127) && + (0 == (ev->modifiers & (SAPP_MODIFIER_ALT|SAPP_MODIFIER_CTRL|SAPP_MODIFIER_SUPER)))) + { + simgui_add_input_character(ev->char_code); + } + break; + case SAPP_EVENTTYPE_CLIPBOARD_PASTED: + // simulate a Ctrl-V key down/up + if (!_simgui.desc.disable_paste_override) { + _simgui_add_imgui_key_event(io, _simgui_copypaste_modifier(), true); + _simgui_add_imgui_key_event(io, ImGuiKey_V, true); + _simgui_add_imgui_key_event(io, ImGuiKey_V, false); + _simgui_add_imgui_key_event(io, _simgui_copypaste_modifier(), false); + } + break; + default: + break; + } + return io->WantCaptureKeyboard || io->WantCaptureMouse; +} +#endif // SOKOL_IMGUI_NO_SOKOL_APP + +#endif // SOKOL_IMPL diff --git a/source/engine/thirdparty/sokol/util/sokol_memtrack.h b/source/engine/thirdparty/sokol/util/sokol_memtrack.h new file mode 100644 index 0000000..47f70fc --- /dev/null +++ b/source/engine/thirdparty/sokol/util/sokol_memtrack.h @@ -0,0 +1,167 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_MEMTRACK_IMPL) +#define SOKOL_MEMTRACK_IMPL +#endif +#ifndef SOKOL_MEMTRACK_INCLUDED +/* + sokol_memtrack.h -- memory allocation wrapper to track memory usage + of sokol libraries + + Project URL: https://github.com/floooh/sokol + + Optionally provide the following defines with your own implementations: + + SOKOL_MEMTRACK_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_MEMTRACK_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_memtrack.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + USAGE + ===== + Just plug the malloc/free wrapper functions into the desc.allocator + struct provided by most sokol header setup functions: + + sg_setup(&(sg_desc){ + //... + .allocator = { + .alloc_fn = smemtrack_alloc, + .free_fn = smemtrack_free, + } + }); + + Then call smemtrack_info() to get information about current number + of allocations and overall allocation size: + + const smemtrack_info_t info = smemtrack_info(); + const int num_allocs = info.num_allocs; + const int num_bytes = info.num_bytes; + + Note the sokol_memtrack.h can only track allocations issued by + the sokol headers, not allocations that happen under the hood + in system libraries. + + 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_MEMTRACK_INCLUDED (1) +#include +#include // size_t + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_MEMTRACK_API_DECL) +#define SOKOL_MEMTRACK_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_MEMTRACK_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_MEMTRACK_IMPL) +#define SOKOL_MEMTRACK_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_MEMTRACK_API_DECL __declspec(dllimport) +#else +#define SOKOL_MEMTRACK_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct smemtrack_info_t { + int num_allocs; + int num_bytes; +} smemtrack_info_t; + +SOKOL_MEMTRACK_API_DECL smemtrack_info_t smemtrack_info(void); +SOKOL_MEMTRACK_API_DECL void* smemtrack_alloc(size_t size, void* user_data); +SOKOL_MEMTRACK_API_DECL void smemtrack_free(void* ptr, void* user_data); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* SOKOL_MEMTRACK_INCLUDED */ + +/*=== IMPLEMENTATION =========================================================*/ +#ifdef SOKOL_MEMTRACK_IMPL +#define SOKOL_MEMTRACK_IMPL_INCLUDED (1) +#include // malloc, free +#include // memset + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif + +// per-allocation header used to keep track of the allocation size +#define _SMEMTRACK_HEADER_SIZE (16) + +static struct { + smemtrack_info_t state; +} _smemtrack; + +SOKOL_API_IMPL void* smemtrack_alloc(size_t size, void* user_data) { + (void)user_data; + uint8_t* ptr = (uint8_t*) malloc(size + _SMEMTRACK_HEADER_SIZE); + if (ptr) { + // store allocation size (for allocation size tracking) + *(size_t*)ptr = size; + _smemtrack.state.num_allocs++; + _smemtrack.state.num_bytes += (int) size; + return ptr + _SMEMTRACK_HEADER_SIZE; + } + else { + // allocation failed, return null pointer + return ptr; + } +} + +SOKOL_API_IMPL void smemtrack_free(void* ptr, void* user_data) { + (void)user_data; + if (ptr) { + uint8_t* alloc_ptr = ((uint8_t*)ptr) - _SMEMTRACK_HEADER_SIZE; + size_t size = *(size_t*)alloc_ptr; + _smemtrack.state.num_allocs--; + _smemtrack.state.num_bytes -= (int) size; + free(alloc_ptr); + } +} + +SOKOL_API_IMPL smemtrack_info_t smemtrack_info(void) { + return _smemtrack.state; +} + +#endif /* SOKOL_MEMTRACK_IMPL */ diff --git a/source/engine/thirdparty/sokol/util/sokol_nuklear.h b/source/engine/thirdparty/sokol/util/sokol_nuklear.h new file mode 100644 index 0000000..04e73ea --- /dev/null +++ b/source/engine/thirdparty/sokol/util/sokol_nuklear.h @@ -0,0 +1,2667 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_NUKLEAR_IMPL) +#define SOKOL_NUKLEAR_IMPL +#endif +#ifndef SOKOL_NUKLEAR_INCLUDED +/* + sokol_nuklear.h -- drop-in Nuklear renderer/event-handler for sokol_gfx.h + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_NUKLEAR_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_GLCORE33 + SOKOL_GLES3 + SOKOL_D3D11 + SOKOL_METAL + SOKOL_WGPU + + Optionally provide the following configuration defines before including the + implementation: + + SOKOL_NUKLEAR_NO_SOKOL_APP - don't depend on sokol_app.h (see below for details) + + Optionally provide the following macros before including the implementation + to override defaults: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_NUKLEAR_API_DECL- public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_NUKLEAR_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_nuklear.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_NUKLEAR_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + Include the following headers before sokol_nuklear.h (both before including + the declaration and implementation): + + sokol_gfx.h + sokol_app.h (except SOKOL_NUKLEAR_NO_SOKOL_APP) + nuklear.h + + NOTE: Unlike most other sokol-headers, the implementation must be compiled + as C, compiling as C++ isn't supported. The interface is callable + from C++ of course. + + + FEATURE OVERVIEW: + ================= + sokol_nuklear.h implements the initialization, rendering and event-handling + code for Nuklear (https://github.com/Immediate-Mode-UI/Nuklear) on top of + sokol_gfx.h and (optionally) sokol_app.h. + + The sokol_app.h dependency is optional and used for input event handling. + If you only use sokol_gfx.h but not sokol_app.h in your application, + define SOKOL_NUKLEAR_NO_SOKOL_APP before including the implementation + of sokol_nuklear.h, this will remove any dependency to sokol_app.h, but + you must feed input events into Nuklear yourself. + + sokol_nuklear.h is not thread-safe, all calls must be made from the + same thread where sokol_gfx.h is running. + + HOWTO: + ====== + + --- To initialize sokol-nuklear, call: + + snk_setup(const snk_desc_t* desc) + + This will initialize Nuklear and create sokol-gfx resources + (two buffers for vertices and indices, a font texture and a pipeline- + state-object). + + Use the following snk_desc_t members to configure behaviour: + + int max_vertices + The maximum number of vertices used for UI rendering, default is 65536. + sokol-nuklear will use this to compute the size of the vertex- + and index-buffers allocated via sokol_gfx.h + + int image_pool_size + Number of snk_image_t objects which can be alive at the same time. + The default is 256. + + sg_pixel_format color_format + The color pixel format of the render pass where the UI + will be rendered. The default is SG_PIXELFORMAT_RGBA8 + + sg_pixel_format depth_format + The depth-buffer pixel format of the render pass where + the UI will be rendered. The default is SG_PIXELFORMAT_DEPTHSTENCIL. + + int sample_count + The MSAA sample-count of the render pass where the UI + will be rendered. The default is 1. + + float dpi_scale + DPI scaling factor. Set this to the result of sapp_dpi_scale(). + To render in high resolution on a Retina Mac this would + typically be 2.0. The default value is 1.0 + + bool no_default_font + Set this to true if you don't want to use Nuklear's default + font. In this case you need to initialize the font + yourself after snk_setup() is called. + + --- At the start of a frame, call: + + struct nk_context *snk_new_frame() + + This updates Nuklear's event handling state and then returns + a Nuklear context pointer which you use to build the UI. For + example: + + struct nk_context *ctx = snk_new_frame(); + if (nk_begin(ctx, "Demo", nk_rect(50, 50, 200, 200), ... + + + --- at the end of the frame, before the sg_end_pass() where you + want to render the UI, call: + + snk_render(int width, int height) + + where 'width' and 'height' are the dimensions of the rendering surface. + For example, if you're using sokol_app.h and render to the default + framebuffer: + + snk_render(sapp_width(), sapp_height()); + + This will convert Nuklear's command list into a vertex and index buffer, + and then render that through sokol_gfx.h + + --- if you're using sokol_app.h, from inside the sokol_app.h event callback, + call: + + bool snk_handle_event(const sapp_event* ev); + + This will feed the event into Nuklear's event handling code, and return + true if the event was handled by Nuklear, or false if the event should + be handled by the application. + + --- finally, on application shutdown, call + + snk_shutdown() + + --- Note that for touch-based systems, like iOS, there is a wrapper around + nk_edit_string(...), called snk_edit_string(...) which will show + and hide the onscreen keyboard as required. + + + ON USER-PROVIDED IMAGES AND SAMPLERS + ==================================== + To render your own images via nk_image(), first create an snk_image_t + object from a sokol-gfx image and sampler object. + + // create a sokol-nuklear image object which associates an sg_image with an sg_sampler + snk_image_t snk_img = snk_make_image(&(snk_image_desc_t){ + .image = sg_make_image(...), + .sampler = sg_make_sampler(...), + }); + + // convert the returned image handle into an nk_handle object + struct nk_handle nk_hnd = snk_nkhandle(snk_img); + + // create a nuklear image from the generic handle (note that there a different helper functions for this) + struct nk_image nk_img = nk_image_handle(nk_hnd); + + // finally specify a Nuklear image UI object + nk_image(ctx, nk_img); + + snk_image_t objects are small and cheap (literally just the image and sampler + handle). + + You can omit the sampler handle in the snk_make_image() call, in this case a + default sampler will be used with nearest-filtering and clamp-to-edge. + + Trying to render with an invalid snk_image_t handle will render a small 8x8 + white default texture instead. + + To destroy a sokol-nuklear image object, call + + snk_destroy_image(snk_img); + + But please be aware that the image object needs to be around until snk_render() is called + in a frame (if this turns out to be too much of a hassle we could introduce some sort + of garbage collection where destroyed snk_image_t objects are kept around until + the snk_render() call). + + You can call: + + snk_image_desc_t desc = snk_query_image_desc(img) + + ...to get the original desc struct, useful if you need to get the sokol-gfx image + and sampler handle of the snk_image_t object. + + You can convert an nk_handle back into an snk_image_t handle: + + snk_image_t img = snk_image_from_nkhandle(nk_hnd); + + + 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); + } + + ... + snk_setup(&(snk_desc_t){ + // ... + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ...; + } + }); + ... + + If no overrides are provided, malloc and free will be used. + + This only affects memory allocation calls done by sokol_nuklear.h + itself though, not any allocations in Nuklear. + + + 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" + + snk_setup(&(snk_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. 'snk' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SNK_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_nuklear.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-nuklear like this: + + snk_setup(&(snk_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 Warren Merrifield + + 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_NUKLEAR_INCLUDED (1) +#include +#include +#include + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_nuklear.h" +#endif +#if !defined(SOKOL_NUKLEAR_NO_SOKOL_APP) && !defined(SOKOL_APP_INCLUDED) +#error "Please include sokol_app.h before sokol_nuklear.h" +#endif +#if !defined(NK_UNDEFINED) +#error "Please include nuklear.h before sokol_nuklear.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_NUKLEAR_API_DECL) +#define SOKOL_NUKLEAR_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_NUKLEAR_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_NUKLEAR_IMPL) +#define SOKOL_NUKLEAR_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_NUKLEAR_API_DECL __declspec(dllimport) +#else +#define SOKOL_NUKLEAR_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + SNK_INVALID_ID = 0, +}; + +/* + snk_image_t + + A combined image-sampler pair used to inject custom images and samplers into Nuklear + + Create with snk_make_image(), and convert to an nk_handle via snk_nkhandle(). +*/ +typedef struct snk_image_t { uint32_t id; } snk_image_t; + +/* + snk_image_desc_t + + Descriptor struct for snk_make_image(). You must provide + at least an sg_image handle. Keeping the sg_sampler handle + zero-initialized will select the builtin default sampler + which uses linear filtering. +*/ +typedef struct snk_image_desc_t { + sg_image image; + sg_sampler sampler; +} snk_image_desc_t; + +/* + snk_log_item + + An enum with a unique item for each log message, warning, error + and validation layer message. +*/ +#define _SNK_LOG_ITEMS \ + _SNK_LOGITEM_XMACRO(OK, "Ok") \ + _SNK_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \ + _SNK_LOGITEM_XMACRO(IMAGE_POOL_EXHAUSTED, "image pool exhausted") \ + +#define _SNK_LOGITEM_XMACRO(item,msg) SNK_LOGITEM_##item, +typedef enum snk_log_item_t { + _SNK_LOG_ITEMS +} snk_log_item_t; +#undef _SNK_LOGITEM_XMACRO + +/* + snk_allocator_t + + Used in snk_desc_t to provide custom memory-alloc and -free functions + to sokol_nuklear.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 snk_allocator_t { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} snk_allocator_t; + +/* + snk_logger + + Used in snk_desc_t to provide a logging function. Please be aware + that without logging function, sokol-nuklear 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 install + a logger (for instance the standard logging function from sokol_log.h). +*/ +typedef struct snk_logger_t { + void (*func)( + const char* tag, // always "snk" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SNK_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_imgui.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} snk_logger_t; + + +typedef struct snk_desc_t { + int max_vertices; // default: 65536 + int image_pool_size; // default: 256 + sg_pixel_format color_format; + sg_pixel_format depth_format; + int sample_count; + float dpi_scale; + bool no_default_font; + snk_allocator_t allocator; // optional memory allocation overrides (default: malloc/free) + snk_logger_t logger; // optional log function override +} snk_desc_t; + +SOKOL_NUKLEAR_API_DECL void snk_setup(const snk_desc_t* desc); +SOKOL_NUKLEAR_API_DECL struct nk_context* snk_new_frame(void); +SOKOL_NUKLEAR_API_DECL void snk_render(int width, int height); +SOKOL_NUKLEAR_API_DECL snk_image_t snk_make_image(const snk_image_desc_t* desc); +SOKOL_NUKLEAR_API_DECL void snk_destroy_image(snk_image_t img); +SOKOL_NUKLEAR_API_DECL snk_image_desc_t snk_query_image_desc(snk_image_t img); +SOKOL_NUKLEAR_API_DECL nk_handle snk_nkhandle(snk_image_t img); +SOKOL_NUKLEAR_API_DECL snk_image_t snk_image_from_nkhandle(nk_handle handle); +#if !defined(SOKOL_NUKLEAR_NO_SOKOL_APP) +SOKOL_NUKLEAR_API_DECL bool snk_handle_event(const sapp_event* ev); +SOKOL_NUKLEAR_API_DECL nk_flags snk_edit_string(struct nk_context *ctx, nk_flags flags, char *memory, int *len, int max, nk_plugin_filter filter); +#endif +SOKOL_NUKLEAR_API_DECL void snk_shutdown(void); + +#ifdef __cplusplus +} /* extern "C" */ + +/* reference-based equivalents for C++ */ +inline void snk_setup(const snk_desc_t& desc) { return snk_setup(&desc); } +inline snk_image_t snk_make_image(const snk_image_desc_t& desc) { return snk_make_image(&desc); } + +#endif +#endif /* SOKOL_NUKLEAR_INCLUDED */ + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_NUKLEAR_IMPL +#define SOKOL_NUKLEAR_IMPL_INCLUDED (1) + +#if !defined(NK_INCLUDE_VERTEX_BUFFER_OUTPUT) +#error "Please ensure that NK_INCLUDE_VERTEX_BUFFER_OUTPUT is #defined before including nuklear.h" +#endif + +#ifdef __cplusplus +#error "The sokol_nuklear.h implementation must be compiled as C." +#endif + +#include +#include // memset + +#if defined(__EMSCRIPTEN__) && !defined(SOKOL_NUKLEAR_NO_SOKOL_APP) && !defined(SOKOL_DUMMY_BACKEND) +#include +#endif + +#ifndef SOKOL_API_IMPL +#define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG + #endif +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif + +#define _SNK_INIT_COOKIE (0xBABEBABE) +#define _SNK_INVALID_SLOT_INDEX (0) +#define _SNK_SLOT_SHIFT (16) +#define _SNK_MAX_POOL_SIZE (1<<_SNK_SLOT_SHIFT) +#define _SNK_SLOT_MASK (_SNK_MAX_POOL_SIZE-1) + +// helper macros +#define _snk_def(val, def) (((val) == 0) ? (def) : (val)) + +typedef struct _snk_vertex_t { + float pos[2]; + float uv[2]; + uint8_t col[4]; +} _snk_vertex_t; + +typedef struct _snk_vs_params_t { + float disp_size[2]; + uint8_t _pad_8[8]; +} _snk_vs_params_t; + +typedef enum { + _SNK_RESOURCESTATE_INITIAL, + _SNK_RESOURCESTATE_ALLOC, + _SNK_RESOURCESTATE_VALID, + _SNK_RESOURCESTATE_FAILED, + _SNK_RESOURCESTATE_INVALID, + _SNK_RESOURCESTATE_FORCE_U32 = 0x7FFFFFFF +} _snk_resource_state; + +typedef struct { + uint32_t id; + _snk_resource_state state; +} _snk_slot_t; + +typedef struct { + int size; + int queue_top; + uint32_t* gen_ctrs; + int* free_queue; +} _snk_pool_t; + +typedef struct { + _snk_slot_t slot; + sg_image image; + sg_sampler sampler; +} _snk_image_t; + +typedef struct { + _snk_pool_t pool; + _snk_image_t* items; +} _snk_image_pool_t; + +typedef struct { + uint32_t init_cookie; + snk_desc_t desc; + struct nk_context ctx; + struct nk_font_atlas atlas; + _snk_vs_params_t vs_params; + size_t vertex_buffer_size; + size_t index_buffer_size; + sg_buffer vbuf; + sg_buffer ibuf; + sg_image font_img; + sg_sampler font_smp; + snk_image_t default_font; + sg_image def_img; + sg_sampler def_smp; + sg_shader shd; + sg_pipeline pip; + bool is_osx; // true if running on OSX (or HTML5 OSX), needed for copy/paste + _snk_image_pool_t image_pool; + #if !defined(SOKOL_NUKLEAR_NO_SOKOL_APP) + int mouse_pos[2]; + float mouse_scroll[2]; + bool mouse_did_move; + bool mouse_did_scroll; + bool btn_down[NK_BUTTON_MAX]; + bool btn_up[NK_BUTTON_MAX]; + char char_buffer[NK_INPUT_MAX]; + bool keys_down[NK_KEY_MAX]; + bool keys_up[NK_KEY_MAX]; + #endif +} _snk_state_t; +static _snk_state_t _snuklear; + +/* + Embedded source code compiled with: + + sokol-shdc -i snuk.glsl -o snuk.h -l glsl330: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 + uniform vs_params { + vec2 disp_size; + }; + in vec2 position; + in vec2 texcoord0; + in vec4 color0; + out vec2 uv; + out vec4 color; + void main() { + gl_Position = vec4(((position/disp_size)-0.5)*vec2(2.0,-2.0), 0.5, 1.0); + uv = texcoord0; + color = color0; + } + @end + + @fs fs + uniform texture2D tex; + uniform sampler smp; + in vec2 uv; + in vec4 color; + out vec4 frag_color; + void main() { + frag_color = texture(sampler2D(tex, smp), uv) * color; + } + @end + + @program snuk vs fs +*/ +#if defined(SOKOL_GLCORE33) +static const char _snk_vs_source_glsl330[341] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,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,0x31,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,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, + 0x28,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x2f,0x20,0x76,0x73,0x5f, + 0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,0x79,0x29,0x20,0x2d,0x20, + 0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x35,0x29,0x29,0x20,0x2a,0x20,0x76,0x65,0x63, + 0x32,0x28,0x32,0x2e,0x30,0x2c,0x20,0x2d,0x32,0x2e,0x30,0x29,0x2c,0x20,0x30,0x2e, + 0x35,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, +}; +static const char _snk_fs_source_glsl330[177] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,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,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a, + 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,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a, + 0x00, +}; +#elif defined(SOKOL_GLES3) +static const char _snk_vs_source_glsl300es[344] = { + 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,0x31,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f, + 0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29, + 0x20,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,0x28,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x2f,0x20, + 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,0x79,0x29, + 0x20,0x2d,0x20,0x76,0x65,0x63,0x32,0x28,0x30,0x2e,0x35,0x29,0x29,0x20,0x2a,0x20, + 0x76,0x65,0x63,0x32,0x28,0x32,0x2e,0x30,0x2c,0x20,0x2d,0x32,0x2e,0x30,0x29,0x2c, + 0x20,0x30,0x2e,0x35,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, +}; +static const char _snk_fs_source_glsl300es[250] = { + 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,0x20,0x2a,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_METAL) +static const uint8_t _snk_vs_bytecode_metal_macos[3052] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xec,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, + 0xe0,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,0x7b,0x12,0x23,0x17,0xd9,0x25,0x1c, + 0x1b,0x42,0x42,0x9f,0xbf,0x31,0xd2,0x2c,0x3a,0x55,0x22,0x1d,0x40,0xd8,0xc4,0xf8, + 0x20,0x49,0x60,0x6d,0x3c,0xea,0x4e,0x1c,0x34,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,0xc8,0x0a,0x00,0x00,0xff,0xff,0xff,0xff, + 0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0xaf,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,0x81,0x00,0x00,0x00,0x1b,0xc8,0x25,0xf8,0xff,0xff,0xff,0xff, + 0x01,0x90,0x80,0x8a,0x18,0x87,0x77,0x90,0x07,0x79,0x28,0x87,0x71,0xa0,0x07,0x76, + 0xc8,0x87,0x36,0x90,0x87,0x77,0xa8,0x07,0x77,0x20,0x87,0x72,0x20,0x87,0x36,0x20, + 0x87,0x74,0xb0,0x87,0x74,0x20,0x87,0x72,0x68,0x83,0x79,0x88,0x07,0x79,0xa0,0x87, + 0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0xc0,0x1c,0xc2,0x81, + 0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c,0xd2,0x61,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c, + 0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8,0xa1,0x0d,0xc6,0x21,0x1c,0xd8,0x81,0x1d,0xe6, + 0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07,0x80,0x60,0x87,0x72,0x98,0x87,0x79, + 0x68,0x03,0x78,0x90,0x87,0x72,0x18,0x87,0x74,0x98,0x87,0x72,0x68,0x03,0x73,0x80, + 0x87,0x76,0x08,0x07,0x72,0x00,0xcc,0x21,0x1c,0xd8,0x61,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,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x06,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,0x68,0x03,0x77,0x78,0x07,0x77,0x68,0x03,0x76,0x28, + 0x87,0x70,0x30,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,0x40,0x1d,0xea,0xa1,0x1d,0xe0,0xa1,0x0d, + 0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87, + 0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x70,0x87,0x70,0x70,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,0xe6,0x21,0x1d,0xce,0xc1,0x1d,0xca,0x81,0x1c,0xda, + 0x40,0x1f,0xca,0x41,0x1e,0xde,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, + 0x03,0x7a,0x90,0x87,0x70,0x80,0x07,0x78,0x48,0x07,0x77,0x38,0x87,0x36,0x68,0x87, + 0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0x62,0x1e,0xe8,0x21, + 0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e,0xe4,0xe1,0x1d,0xe8,0xa1,0x1c,0xc6,0x81,0x1e, + 0xde,0x41,0x1e,0xda,0x40,0x1c,0xea,0xc1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x0d,0xe6, + 0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c,0x00,0x88,0x7a,0x70,0x87,0x79,0x08,0x07,0x73, + 0x28,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e, + 0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea,0x61,0x1e,0xca,0xa1,0x0d,0xe6,0xe1,0x1d,0xcc, + 0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1,0x1d,0xc2,0x81,0x1e,0x00,0x73,0x08,0x07,0x76, + 0x98,0x87,0x72,0x00,0x36,0x18,0x02,0x01,0x2c,0x40,0x05,0x00,0x49,0x18,0x00,0x00, + 0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00,0x00,0x16,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,0x3c,0x33,0x00, + 0xc3,0x08,0x02,0x30,0x8c,0x40,0x00,0x76,0x08,0x91,0x83,0xa4,0x29,0xa2,0x84,0xc9, + 0xaf,0xa4,0xff,0x01,0x22,0x80,0x91,0x50,0x10,0x83,0x08,0x84,0x50,0x8a,0x89,0x90, + 0x22,0x1b,0x08,0x98,0x23,0x00,0x83,0x14,0xc8,0x39,0x02,0x50,0x18,0x44,0x08,0x84, + 0x61,0x04,0x22,0x19,0x01,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,0x39,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0xc8, + 0x02,0x01,0x00,0x00,0x09,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90, + 0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0xca,0x12,0x18,0x01,0x28,0x88,0x22,0x28,0x84, + 0x32,0xa0,0x1d,0x01,0x20,0x1d,0x4b,0x68,0x02,0x00,0x00,0x00,0x79,0x18,0x00,0x00, + 0xea,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9, + 0xb9,0xb4,0x37,0xb7,0x21,0x46,0x42,0x20,0x80,0x82,0x50,0xb9,0x1b,0x43,0x0b,0x93, + 0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x24,0x01,0x22,0x24,0x05,0xe7,0x20,0x08,0x0e, + 0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd, + 0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e, + 0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45,0x26, + 0x65,0x88,0x80,0x10,0x43,0x8c,0x24,0x48,0x86,0x44,0x60,0xd1,0x54,0x46,0x17,0xc6, + 0x36,0x04,0x41,0x8e,0x24,0x48,0x82,0x44,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,0x44,0x40,0x3c,0xe4,0x4b,0x84,0x24,0x40,0xc0,0x00,0x89,0x10,0x09,0x99,0x90, + 0x30,0x60,0x42,0x57,0x86,0x37,0xf6,0xf6,0x26,0x47,0x06,0x33,0x84,0x4a,0x02,0xc4, + 0x43,0xbe,0x24,0x48,0x02,0x04,0x0c,0x90,0x08,0x91,0x90,0x09,0x19,0x03,0x1a,0x63, + 0x6f,0x6c,0x6f,0x72,0x30,0x43,0xa8,0x84,0x40,0x3c,0xe4,0x4b,0x88,0x24,0x40,0xc0, + 0x00,0x89,0x90,0x0b,0x99,0x90,0x32,0xa0,0x12,0x96,0x26,0xe7,0x22,0x56,0x67,0x66, + 0x56,0x26,0xc7,0x27,0x2c,0x4d,0xce,0x45,0xac,0xce,0xcc,0xac,0x4c,0xee,0x6b,0x2e, + 0x4d,0xaf,0x8c,0x48,0x58,0x9a,0x9c,0x8b,0x5c,0x59,0x18,0x19,0xa9,0xb0,0x34,0x39, + 0x97,0x39,0x3a,0xb9,0xba,0x31,0xba,0x2f,0xba,0x3c,0xb8,0xb2,0xaf,0x34,0x37,0xb3, + 0x37,0x26,0x64,0x69,0x73,0x70,0x5f,0x73,0x69,0x7a,0x65,0x43,0x94,0x44,0x48,0x86, + 0x44,0x40,0x24,0x64,0x0d,0x18,0x85,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xd1,0xe5, + 0xc1,0x95,0x7d,0xcd,0xa5,0xe9,0x95,0xf1,0x0a,0x4b,0x93,0x73,0x09,0x93,0x3b,0xfb, + 0xa2,0xcb,0x83,0x2b,0xfb,0x0a,0x63,0x4b,0x3b,0x73,0xfb,0x9a,0x4b,0xd3,0x2b,0x63, + 0x62,0x37,0xf7,0x05,0x17,0x26,0x17,0xd6,0x36,0xc7,0xe1,0x4b,0x46,0x66,0x08,0x19, + 0x24,0x06,0x72,0x06,0x08,0x1a,0x24,0x03,0xf2,0x25,0x42,0x12,0x20,0x69,0x80,0xa8, + 0x01,0xc2,0x06,0x48,0x1b,0x24,0x03,0xe2,0x06,0xc9,0x80,0x44,0xc8,0x1b,0x20,0x13, + 0x02,0x07,0x43,0x10,0x44,0x0c,0x10,0x32,0x40,0xcc,0x00,0x89,0x83,0x21,0xc6,0x01, + 0x20,0x1d,0x22,0x07,0x7c,0xde,0xda,0xdc,0xd2,0xe0,0xde,0xe8,0xca,0xdc,0xe8,0x40, + 0xc6,0xd0,0xc2,0xe4,0xf8,0x4c,0xa5,0xb5,0xc1,0xb1,0x95,0x81,0x0c,0xad,0xac,0x80, + 0x50,0x09,0x05,0x05,0x0d,0x11,0x90,0x3a,0x18,0x62,0x20,0x74,0x80,0xd8,0xc1,0x72, + 0x0c,0x31,0x90,0x3b,0x40,0xee,0x60,0x39,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,0xe6,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00, + 0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d, + 0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,0x00, + 0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00,0x06,0x50,0x30,0x00,0xd2,0xd0,0x00,0x00, + 0x61,0x20,0x00,0x00,0x23,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00, + 0x11,0x00,0x00,0x00,0xd4,0x63,0x11,0x40,0x60,0x1c,0x73,0x10,0x42,0xf0,0x3c,0x94, + 0x33,0x00,0x14,0x63,0x09,0x20,0x08,0x82,0xf0,0x2f,0x80,0x20,0x08,0xc2,0xbf,0x30, + 0x96,0x00,0x82,0x20,0x08,0x82,0x01,0x08,0x82,0x20,0x08,0x0e,0x33,0x00,0x24,0x73, + 0x10,0xd7,0x65,0x55,0x34,0x33,0x00,0x04,0x63,0x04,0x20,0x08,0x82,0xf8,0x37,0x46, + 0x00,0x82,0x20,0x08,0x7f,0x33,0x00,0x00,0xe3,0x0d,0x4c,0x64,0x51,0x40,0x2c,0x0a, + 0xe8,0x63,0xc1,0x02,0x1f,0x0b,0x16,0xf9,0x0c,0x32,0x04,0xcb,0x33,0xc8,0x10,0x2c, + 0xd1,0x6c,0xc3,0x52,0x01,0xb3,0x0d,0x41,0x15,0xcc,0x36,0x04,0x83,0x90,0x41,0x40, + 0x0c,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x5b,0x86,0x20,0xc0,0x03,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _snk_fs_bytecode_metal_macos[2809] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf9,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x20,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,0xa1,0xce,0x6b,0xd1,0x1f,0x32,0x9e, + 0x8d,0x8d,0x1c,0xcc,0x19,0xcb,0xd3,0xb6,0x21,0x99,0x0b,0xb6,0x46,0x8b,0x87,0x98, + 0x8e,0x2d,0xb5,0x98,0x92,0x0a,0x81,0x7d,0xf3,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,0x0c,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0x80,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,0x89,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07,0x7a,0x60,0x87,0x7c, + 0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72,0x68,0x03,0x72,0x48, + 0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90,0x07,0x7a,0x68,0x03, + 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc,0x21,0x1c,0xd8,0x61, + 0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81,0x1d,0xca,0xa1,0x0d, + 0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d,0xd8,0x61,0x1e,0x00, + 0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87,0x79,0x98,0x87,0x36, + 0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,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,0x00,0x7a,0x90, + 0x87,0x7a,0x28,0x07,0x60,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,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36,0x60,0x87,0x72,0x08, + 0x07,0x73,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,0xd4,0xa1,0x1e,0xda,0x01,0x1e,0xda,0x80,0x1e, + 0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,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,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c,0xc8,0xa1,0x0d,0xf4, + 0xa1,0x1c,0xe4,0xe1,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,0xa0, + 0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68,0x83,0x76,0x08,0x07, + 0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6,0x81,0x1e,0xc2,0x61, + 0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d, + 0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e,0xda,0x60,0x1e,0xd2, + 0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87,0x70,0x30,0x87,0x72, + 0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e, + 0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e,0xde,0xc1,0x1c,0xe8, + 0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 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,0x05,0x49,0x18,0x00,0x00,0x03,0x00,0x00, + 0x00,0x13,0x86,0x40,0x18,0x26,0x0c,0x44,0x61,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,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,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, + 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,0xa1,0x09,0x00,0x00,0x79,0x18,0x00,0x00,0xb9,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,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e, + 0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6, + 0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06, + 0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45,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,0xc1,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,0x98,0x70,0x48,0x07,0x79,0x70,0x03,0x73, + 0x90,0x87,0x70,0x38,0x87,0x76,0x28,0x07,0x77,0xa0,0x87,0x29,0xc1,0x1a,0x00,0x00, + 0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4, + 0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43, + 0x3d,0xc0,0xc3,0x3c,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,0x0c,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x04,0x00,0x00, + 0x00,0xc4,0x46,0x00,0x48,0x8d,0x00,0xd4,0x00,0x89,0x19,0x00,0x02,0x23,0x00,0x00, + 0x00,0x23,0x06,0x8a,0x10,0x44,0x87,0x91,0x0c,0x05,0x11,0x58,0x90,0xc8,0x67,0xb6, + 0x81,0x08,0x80,0x0c,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _snk_vs_bytecode_metal_ios[3052] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xec,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, + 0xe0,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,0x69,0x97,0x6b,0xac,0xd8,0xa2,0x51, + 0x33,0x7c,0x5f,0x96,0xb2,0xb1,0x06,0x06,0x7c,0xbb,0x5f,0x88,0xa0,0xeb,0x9f,0xea, + 0x6e,0x6b,0x70,0xa9,0x6e,0xef,0xe6,0xa4,0xea,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,0xc0,0x0a,0x00,0x00,0xff,0xff,0xff,0xff, + 0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0xad,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,0x82,0x00,0x00,0x00,0x1b,0xc8,0x25,0xf8,0xff,0xff,0xff,0xff, + 0x01,0x90,0x80,0x8a,0x18,0x87,0x77,0x90,0x07,0x79,0x28,0x87,0x71,0xa0,0x07,0x76, + 0xc8,0x87,0x36,0x90,0x87,0x77,0xa8,0x07,0x77,0x20,0x87,0x72,0x20,0x87,0x36,0x20, + 0x87,0x74,0xb0,0x87,0x74,0x20,0x87,0x72,0x68,0x83,0x79,0x88,0x07,0x79,0xa0,0x87, + 0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0xc0,0x1c,0xc2,0x81, + 0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c,0xd2,0x61,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c, + 0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8,0xa1,0x0d,0xc6,0x21,0x1c,0xd8,0x81,0x1d,0xe6, + 0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07,0x80,0x60,0x87,0x72,0x98,0x87,0x79, + 0x68,0x03,0x78,0x90,0x87,0x72,0x18,0x87,0x74,0x98,0x87,0x72,0x68,0x03,0x73,0x80, + 0x87,0x76,0x08,0x07,0x72,0x00,0xcc,0x21,0x1c,0xd8,0x61,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,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x06,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,0x68,0x03,0x77,0x78,0x07,0x77,0x68,0x03,0x76,0x28, + 0x87,0x70,0x30,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,0x40,0x1d,0xea,0xa1,0x1d,0xe0,0xa1,0x0d, + 0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87, + 0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x70,0x87,0x70,0x70,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,0xe6,0x21,0x1d,0xce,0xc1,0x1d,0xca,0x81,0x1c,0xda, + 0x40,0x1f,0xca,0x41,0x1e,0xde,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, + 0x03,0x7a,0x90,0x87,0x70,0x80,0x07,0x78,0x48,0x07,0x77,0x38,0x87,0x36,0x68,0x87, + 0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0x62,0x1e,0xe8,0x21, + 0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e,0xe4,0xe1,0x1d,0xe8,0xa1,0x1c,0xc6,0x81,0x1e, + 0xde,0x41,0x1e,0xda,0x40,0x1c,0xea,0xc1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x0d,0xe6, + 0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c,0x00,0x88,0x7a,0x70,0x87,0x79,0x08,0x07,0x73, + 0x28,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e, + 0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea,0x61,0x1e,0xca,0xa1,0x0d,0xe6,0xe1,0x1d,0xcc, + 0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1,0x1d,0xc2,0x81,0x1e,0x00,0x73,0x08,0x07,0x76, + 0x98,0x87,0x72,0x00,0x36,0x20,0x02,0x01,0x24,0xc0,0x02,0x54,0x00,0x00,0x00,0x00, + 0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00,0x00, + 0x16,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,0x3c,0x33,0x00,0xc3,0x08,0x02,0x30,0x8c,0x40,0x00,0x76,0x08,0x91,0x83,0xa4, + 0x29,0xa2,0x84,0xc9,0xaf,0xa4,0xff,0x01,0x22,0x80,0x91,0x50,0x10,0x83,0x08,0x84, + 0x50,0x8a,0x89,0x90,0x22,0x1b,0x08,0x98,0x23,0x00,0x83,0x14,0xc8,0x39,0x02,0x50, + 0x18,0x44,0x08,0x84,0x61,0x04,0x22,0x19,0x01,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,0x03,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x80,0x2c,0x10,0x00,0x00, + 0x09,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47, + 0xc6,0x04,0x43,0xca,0x12,0x18,0x01,0x28,0x88,0x22,0x28,0x84,0x32,0xa0,0x1d,0x01, + 0x20,0x1d,0x4b,0x80,0x04,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xe9,0x00,0x00,0x00, + 0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7, + 0x21,0x46,0x42,0x20,0x80,0x82,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3, + 0x2b,0x1b,0x62,0x24,0x01,0x22,0x24,0x05,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4, + 0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06, + 0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac, + 0x65,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45,0x26,0x65,0x88,0x80,0x10, + 0x43,0x8c,0x24,0x48,0x86,0x44,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x41,0x8e, + 0x24,0x48,0x82,0x44,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,0x44,0x40,0x3c,0xe4, + 0x4b,0x84,0x24,0x40,0xc0,0x00,0x89,0x10,0x09,0x99,0x90,0x30,0x60,0x42,0x57,0x86, + 0x37,0xf6,0xf6,0x26,0x47,0x06,0x33,0x84,0x4a,0x02,0xc4,0x43,0xbe,0x24,0x48,0x02, + 0x04,0x0c,0x90,0x08,0x91,0x90,0x09,0x19,0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30, + 0x43,0xa8,0x84,0x40,0x3c,0xe4,0x4b,0x88,0x24,0x40,0xc0,0x00,0x89,0x90,0x0b,0x99, + 0x90,0x32,0xa0,0x12,0x96,0x26,0xe7,0x22,0x56,0x67,0x66,0x56,0x26,0xc7,0x27,0x2c, + 0x4d,0xce,0x45,0xac,0xce,0xcc,0xac,0x4c,0xee,0x6b,0x2e,0x4d,0xaf,0x8c,0x48,0x58, + 0x9a,0x9c,0x8b,0x5c,0x59,0x18,0x19,0xa9,0xb0,0x34,0x39,0x97,0x39,0x3a,0xb9,0xba, + 0x31,0xba,0x2f,0xba,0x3c,0xb8,0xb2,0xaf,0x34,0x37,0xb3,0x37,0x26,0x64,0x69,0x73, + 0x70,0x5f,0x73,0x69,0x7a,0x65,0x43,0x94,0x44,0x48,0x86,0x44,0x40,0x24,0x64,0x0d, + 0x18,0x85,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xd1,0xe5,0xc1,0x95,0x7d,0xcd,0xa5, + 0xe9,0x95,0xf1,0x0a,0x4b,0x93,0x73,0x09,0x93,0x3b,0xfb,0xa2,0xcb,0x83,0x2b,0xfb, + 0x0a,0x63,0x4b,0x3b,0x73,0xfb,0x9a,0x4b,0xd3,0x2b,0x63,0x62,0x37,0xf7,0x05,0x17, + 0x26,0x17,0xd6,0x36,0xc7,0xe1,0x4b,0x46,0x66,0x08,0x19,0x24,0x06,0x72,0x06,0x08, + 0x1a,0x24,0x03,0xf2,0x25,0x42,0x12,0x20,0x69,0x80,0xa8,0x01,0xc2,0x06,0x48,0x1b, + 0x24,0x03,0xe2,0x06,0xc9,0x80,0x44,0xc8,0x1b,0x20,0x13,0x02,0x07,0x43,0x10,0x44, + 0x0c,0x10,0x32,0x40,0xcc,0x00,0x89,0x83,0x21,0xc6,0x01,0x20,0x1d,0x22,0x07,0x7c, + 0xde,0xda,0xdc,0xd2,0xe0,0xde,0xe8,0xca,0xdc,0xe8,0x40,0xc6,0xd0,0xc2,0xe4,0xf8, + 0x4c,0xa5,0xb5,0xc1,0xb1,0x95,0x81,0x0c,0xad,0xac,0x80,0x50,0x09,0x05,0x05,0x0d, + 0x11,0x90,0x3a,0x18,0x62,0x20,0x74,0x80,0xd8,0xc1,0x72,0x0c,0x31,0x90,0x3b,0x40, + 0xee,0x60,0x39,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,0xe6,0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8, + 0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83, + 0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00, + 0x06,0x50,0x30,0x00,0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x23,0x00,0x00,0x00, + 0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0xd4,0x63,0x11,0x40, + 0x60,0x1c,0x73,0x10,0x42,0xf0,0x3c,0x94,0x33,0x00,0x14,0x63,0x09,0x20,0x08,0x82, + 0xf0,0x2f,0x80,0x20,0x08,0xc2,0xbf,0x30,0x96,0x00,0x82,0x20,0x08,0x82,0x01,0x08, + 0x82,0x20,0x08,0x0e,0x33,0x00,0x24,0x73,0x10,0xd7,0x65,0x55,0x34,0x33,0x00,0x04, + 0x63,0x04,0x20,0x08,0x82,0xf8,0x37,0x46,0x00,0x82,0x20,0x08,0x7f,0x33,0x00,0x00, + 0xe3,0x0d,0x4c,0x64,0x51,0x40,0x2c,0x0a,0xe8,0x63,0xc1,0x02,0x1f,0x0b,0x16,0xf9, + 0x0c,0x32,0x04,0xcb,0x33,0xc8,0x10,0x2c,0xd1,0x6c,0xc3,0x52,0x01,0xb3,0x0d,0x41, + 0x15,0xcc,0x36,0x04,0x83,0x90,0x41,0x40,0x0c,0x00,0x00,0x00,0x02,0x00,0x00,0x00, + 0x5b,0x86,0x20,0xc0,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _snk_fs_bytecode_metal_ios[2809] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf9,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x20,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,0xf0,0xa4,0xb3,0x95,0x4b,0xab,0x64, + 0x94,0xe7,0xa9,0x8a,0x69,0x27,0x6d,0x28,0x77,0x84,0x8d,0x3f,0xaf,0x7d,0x3c,0x39, + 0x31,0xc4,0xcb,0x53,0x6d,0xc0,0x0d,0xdf,0x08,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,0x04,0x0a,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0x7e,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,0x89,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07,0x7a,0x60,0x87,0x7c, + 0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72,0x68,0x03,0x72,0x48, + 0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90,0x07,0x7a,0x68,0x03, + 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc,0x21,0x1c,0xd8,0x61, + 0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81,0x1d,0xca,0xa1,0x0d, + 0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d,0xd8,0x61,0x1e,0x00, + 0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87,0x79,0x98,0x87,0x36, + 0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,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,0x00,0x7a,0x90, + 0x87,0x7a,0x28,0x07,0x60,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,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36,0x60,0x87,0x72,0x08, + 0x07,0x73,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,0xd4,0xa1,0x1e,0xda,0x01,0x1e,0xda,0x80,0x1e, + 0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,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,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c,0xc8,0xa1,0x0d,0xf4, + 0xa1,0x1c,0xe4,0xe1,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,0xa0, + 0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68,0x83,0x76,0x08,0x07, + 0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6,0x81,0x1e,0xc2,0x61, + 0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d, + 0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e,0xda,0x60,0x1e,0xd2, + 0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87,0x70,0x30,0x87,0x72, + 0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e, + 0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e,0xde,0xc1,0x1c,0xe8, + 0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 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,0x05,0x49,0x18,0x00,0x00,0x03,0x00,0x00, + 0x00,0x13,0x86,0x40,0x18,0x26,0x0c,0x44,0x61,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,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,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,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,0x01,0x12,0x00,0x00,0x79,0x18,0x00, + 0x00,0xb9,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,0xe7,0x20,0x08, + 0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed, + 0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c, + 0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45, + 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,0xc1,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,0x98, + 0x70,0x48,0x07,0x79,0x70,0x03,0x73,0x90,0x87,0x70,0x38,0x87,0x76,0x28,0x07,0x77, + 0xa0,0x87,0x29,0xc1,0x1a,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59, + 0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b, + 0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,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,0x0c,0x00,0x00,0x00,0x13,0x04,0x41, + 0x2c,0x10,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0xc4,0x46,0x00,0x48,0x8d,0x00,0xd4, + 0x00,0x89,0x19,0x00,0x02,0x23,0x00,0x00,0x00,0x23,0x06,0x8a,0x10,0x44,0x87,0x91, + 0x0c,0x05,0x11,0x58,0x90,0xc8,0x67,0xb6,0x81,0x08,0x80,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const char _snk_vs_source_metal_sim[672] = { + 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,0x32,0x20,0x64,0x69,0x73,0x70,0x5f,0x73,0x69,0x7a,0x65,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,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,0x2c,0x20,0x63,0x6f,0x6e,0x73,0x74,0x61, + 0x6e,0x74,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x26,0x20,0x5f,0x32, + 0x32,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,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x28,0x28,0x69,0x6e,0x2e,0x70,0x6f,0x73, + 0x69,0x74,0x69,0x6f,0x6e,0x20,0x2f,0x20,0x5f,0x32,0x32,0x2e,0x64,0x69,0x73,0x70, + 0x5f,0x73,0x69,0x7a,0x65,0x29,0x20,0x2d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28, + 0x30,0x2e,0x35,0x29,0x29,0x20,0x2a,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x28,0x32, + 0x2e,0x30,0x2c,0x20,0x2d,0x32,0x2e,0x30,0x29,0x2c,0x20,0x30,0x2e,0x35,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, + +}; +static const char _snk_fs_source_metal_sim[436] = { + 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,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 const uint8_t _snk_vs_bytecode_hlsl4[892] = { + 0x44,0x58,0x42,0x43,0x0d,0xbd,0x9e,0x9e,0x7d,0xc0,0x2b,0x54,0x88,0xf9,0xca,0x89, + 0x32,0xe4,0x0c,0x59,0x01,0x00,0x00,0x00,0x7c,0x03,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0xfc,0x00,0x00,0x00,0x60,0x01,0x00,0x00,0xd0,0x01,0x00,0x00, + 0x00,0x03,0x00,0x00,0x52,0x44,0x45,0x46,0xc0,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,0x98,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,0x01,0x00,0x00,0x00,0x60,0x00,0x00,0x00, + 0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x88,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x5f,0x32,0x32,0x5f,0x64,0x69,0x73,0x70,0x5f,0x73,0x69,0x7a, + 0x65,0x00,0xab,0xab,0x01,0x00,0x03,0x00,0x01,0x00,0x02,0x00,0x00,0x00,0x00,0x00, + 0x00,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,0x28,0x01,0x00,0x00,0x40,0x00,0x01,0x00,0x4a,0x00,0x00,0x00, + 0x59,0x00,0x00,0x04,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x01,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,0x68,0x00,0x00,0x02, + 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,0x0e,0x00,0x00,0x08, + 0x32,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00,0x00,0x00,0x00,0x00, + 0x46,0x80,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a, + 0x32,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x00,0x10,0x00,0x00,0x00,0x00,0x00, + 0x02,0x40,0x00,0x00,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x38,0x00,0x00,0x0a,0x32,0x20,0x10,0x00,0x02,0x00,0x00,0x00, + 0x46,0x00,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,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,0x3f,0x00,0x00,0x80,0x3f,0x3e,0x00,0x00,0x01, + 0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x03,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, +}; +static const uint8_t _snk_fs_bytecode_hlsl4[608] = { + 0x44,0x58,0x42,0x43,0x3a,0xa7,0x41,0x21,0xb4,0x2d,0xa7,0x6e,0xfe,0x31,0xb0,0xe0, + 0x14,0xe0,0xdf,0x5a,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, + 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) +static const char _snk_vs_source_wgsl[1083] = { + 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,0x64,0x69,0x73,0x70,0x5f,0x73,0x69,0x7a,0x65,0x20,0x3a,0x20,0x76,0x65,0x63, + 0x32,0x66,0x2c,0x0a,0x7d,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,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,0x32,0x32, + 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,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, + 0x35,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d,0x20,0x78,0x5f,0x32,0x32, + 0x2e,0x64,0x69,0x73,0x70,0x5f,0x73,0x69,0x7a,0x65,0x3b,0x0a,0x20,0x20,0x6c,0x65, + 0x74,0x20,0x78,0x5f,0x33,0x33,0x20,0x3a,0x20,0x76,0x65,0x63,0x32,0x66,0x20,0x3d, + 0x20,0x28,0x28,0x28,0x78,0x5f,0x31,0x39,0x20,0x2f,0x20,0x78,0x5f,0x32,0x35,0x29, + 0x20,0x2d,0x20,0x76,0x65,0x63,0x32,0x66,0x28,0x30,0x2e,0x35,0x66,0x2c,0x20,0x30, + 0x2e,0x35,0x66,0x29,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x32,0x66,0x28,0x32,0x2e, + 0x30,0x66,0x2c,0x20,0x2d,0x32,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,0x33,0x33,0x2e,0x78,0x2c,0x20,0x78,0x5f,0x33,0x33,0x2e, + 0x79,0x2c,0x20,0x30,0x2e,0x35,0x66,0x2c,0x20,0x31,0x2e,0x30,0x66,0x29,0x3b,0x0a, + 0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x33,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,0x34,0x33,0x3b,0x0a,0x20,0x20,0x6c, + 0x65,0x74,0x20,0x78,0x5f,0x34,0x37,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,0x37,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, +}; +static const char _snk_fs_source_wgsl[630] = { + 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,0x33,0x32, + 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,0x34,0x38,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,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,0x34,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,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* _snk_vs_source_dummy = ""; +static const char* _snk_fs_source_dummy = ""; +#else +#error "Please define one of SOKOL_GLCORE33, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND!" +#endif + +#if !defined(SOKOL_NUKLEAR_NO_SOKOL_APP) +static void _snk_clipboard_copy(nk_handle usr, const char *text, int len) { + (void)usr; + if (len == 0) { + return; + } + sapp_set_clipboard_string(text); +} + +static void _snk_clipboard_paste(nk_handle usr, struct nk_text_edit *edit) { + const char *text = sapp_get_clipboard_string(); + if (text) { + nk_textedit_paste(edit, text, nk_strlen(text)); + } + (void)usr; +} + +#if defined(__EMSCRIPTEN__) && !defined(SOKOL_DUMMY_BACKEND) +EM_JS(int, snk_js_is_osx, (void), { + if (navigator.userAgent.includes('Macintosh')) { + return 1; + } else { + return 0; + } +}); +#endif + +static bool _snk_is_osx(void) { + #if defined(SOKOL_DUMMY_BACKEND) + return false; + #elif defined(__EMSCRIPTEN__) + return snk_js_is_osx(); + #elif defined(__APPLE__) + return true; + #else + return false; + #endif +} +#endif // !SOKOL_NUKLEAR_NO_SOKOL_APP + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SNK_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _snk_log_messages[] = { + _SNK_LOG_ITEMS +}; +#undef _SNK_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SNK_PANIC(code) _snk_log(SNK_LOGITEM_ ##code, 0, 0, __LINE__) +#define _SNK_ERROR(code) _snk_log(SNK_LOGITEM_ ##code, 1, 0, __LINE__) +#define _SNK_WARN(code) _snk_log(SNK_LOGITEM_ ##code, 2, 0, __LINE__) +#define _SNK_INFO(code) _snk_log(SNK_LOGITEM_ ##code, 3, 0, __LINE__) +#define _SNK_LOGMSG(code,msg) _snk_log(SNK_LOGITEM_ ##code, 3, msg, __LINE__) + +static void _snk_log(snk_log_item_t log_item, uint32_t log_level, const char* msg, uint32_t line_nr) { + if (_snuklear.desc.logger.func) { + const char* filename = 0; + #if defined(SOKOL_DEBUG) + filename = __FILE__; + if (0 == msg) { + msg = _snk_log_messages[log_item]; + } + #endif + _snuklear.desc.logger.func("snk", log_level, log_item, msg, line_nr, filename, _snuklear.desc.logger.user_data); + } else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory +static void _snk_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +static void* _snk_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_snuklear.desc.allocator.alloc_fn) { + ptr = _snuklear.desc.allocator.alloc_fn(size, _snuklear.desc.allocator.user_data); + } else { + ptr = malloc(size); + } + if (0 == ptr) { + _SNK_PANIC(MALLOC_FAILED); + } + return ptr; +} + +static void* _snk_malloc_clear(size_t size) { + void* ptr = _snk_malloc(size); + _snk_clear(ptr, size); + return ptr; +} + +static void _snk_free(void* ptr) { + if (_snuklear.desc.allocator.free_fn) { + _snuklear.desc.allocator.free_fn(ptr, _snuklear.desc.allocator.user_data); + } else { + free(ptr); + } +} + +// ██████ ██████ ██████ ██ +// ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ +// +// >>pool +static void _snk_init_pool(_snk_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*) _snk_malloc_clear(gen_ctrs_size); + // it's not a bug to only reserve 'num' here + pool->free_queue = (int*) _snk_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 _snk_discard_pool(_snk_pool_t* pool) { + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + _snk_free(pool->free_queue); + pool->free_queue = 0; + SOKOL_ASSERT(pool->gen_ctrs); + _snk_free(pool->gen_ctrs); + pool->gen_ctrs = 0; + pool->size = 0; + pool->queue_top = 0; +} + +static int _snk_pool_alloc_index(_snk_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 _SNK_INVALID_SLOT_INDEX; + } +} + +static void _snk_pool_free_index(_snk_pool_t* pool, int slot_index) { + SOKOL_ASSERT((slot_index > _SNK_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)); +} + +/* initialize a pool slot: + - 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 handle id +*/ +static uint32_t _snk_slot_init(_snk_pool_t* pool, _snk_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 > _SNK_INVALID_SLOT_INDEX) && (slot_index < pool->size)); + SOKOL_ASSERT((slot->state == _SNK_RESOURCESTATE_INITIAL) && (slot->id == SNK_INVALID_ID)); + uint32_t ctr = ++pool->gen_ctrs[slot_index]; + slot->id = (ctr<<_SNK_SLOT_SHIFT)|(slot_index & _SNK_SLOT_MASK); + slot->state = _SNK_RESOURCESTATE_ALLOC; + return slot->id; +} + +// extract slot index from id +static int _snk_slot_index(uint32_t id) { + int slot_index = (int) (id & _SNK_SLOT_MASK); + SOKOL_ASSERT(_SNK_INVALID_SLOT_INDEX != slot_index); + return slot_index; +} + +static void _snk_init_item_pool(_snk_pool_t* pool, int pool_size, void** items_ptr, size_t item_size_bytes) { + // NOTE: the pools will have an additional item, since slot 0 is reserved + SOKOL_ASSERT(pool && (pool->size == 0)); + SOKOL_ASSERT((pool_size > 0) && (pool_size < _SNK_MAX_POOL_SIZE)); + SOKOL_ASSERT(items_ptr && (*items_ptr == 0)); + SOKOL_ASSERT(item_size_bytes > 0); + _snk_init_pool(pool, pool_size); + const size_t pool_size_bytes = item_size_bytes * (size_t)pool->size; + *items_ptr = _snk_malloc_clear(pool_size_bytes); +} + +static void _snk_discard_item_pool(_snk_pool_t* pool, void** items_ptr) { + SOKOL_ASSERT(pool && (pool->size != 0)); + SOKOL_ASSERT(items_ptr && (*items_ptr != 0)); + _snk_free(*items_ptr); *items_ptr = 0; + _snk_discard_pool(pool); +} + +static void _snk_setup_image_pool(int pool_size) { + _snk_image_pool_t* p = &_snuklear.image_pool; + _snk_init_item_pool(&p->pool, pool_size, (void**)&p->items, sizeof(_snk_image_t)); +} + +static void _snk_discard_image_pool(void) { + _snk_image_pool_t* p = &_snuklear.image_pool; + _snk_discard_item_pool(&p->pool, (void**)&p->items); +} + +static snk_image_t _snk_make_image_handle(uint32_t id) { + snk_image_t handle = { id }; + return handle; +} + +static _snk_image_t* _snk_image_at(uint32_t id) { + SOKOL_ASSERT(SNK_INVALID_ID != id); + const _snk_image_pool_t* p = &_snuklear.image_pool; + int slot_index = _snk_slot_index(id); + SOKOL_ASSERT((slot_index > _SNK_INVALID_SLOT_INDEX) && (slot_index < p->pool.size)); + return &p->items[slot_index]; +} + +static _snk_image_t* _snk_lookup_image(uint32_t id) { + if (SNK_INVALID_ID != id) { + _snk_image_t* img = _snk_image_at(id); + if (img->slot.id == id) { + return img; + } + } + return 0; +} + +static snk_image_t _snk_alloc_image(void) { + _snk_image_pool_t* p = &_snuklear.image_pool; + int slot_index = _snk_pool_alloc_index(&p->pool); + if (_SNK_INVALID_SLOT_INDEX != slot_index) { + uint32_t id = _snk_slot_init(&p->pool, &p->items[slot_index].slot, slot_index); + return _snk_make_image_handle(id); + } else { + // pool exhausted + return _snk_make_image_handle(SNK_INVALID_ID); + } +} + +static _snk_resource_state _snk_init_image(_snk_image_t* img, const snk_image_desc_t* desc) { + SOKOL_ASSERT(img && (img->slot.state == _SNK_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + img->image = desc->image; + img->sampler = desc->sampler; + return _SNK_RESOURCESTATE_VALID; +} + +static void _snk_deinit_image(_snk_image_t* img) { + SOKOL_ASSERT(img); + img->image.id = SNK_INVALID_ID; + img->sampler.id = SNK_INVALID_ID; +} + +static void _snk_destroy_image(snk_image_t img_id) { + _snk_image_t* img = _snk_lookup_image(img_id.id); + if (img) { + _snk_deinit_image(img); + _snk_image_pool_t* p = &_snuklear.image_pool; + _snk_clear(img, sizeof(_snk_image_t)); + _snk_pool_free_index(&p->pool, _snk_slot_index(img_id.id)); + } +} + +static void _snk_destroy_all_images(void) { + _snk_image_pool_t* p = &_snuklear.image_pool; + for (int i = 0; i < p->pool.size; i++) { + _snk_image_t* img = &p->items[i]; + _snk_destroy_image(_snk_make_image_handle(img->slot.id)); + } +} + +static snk_image_desc_t _snk_image_desc_defaults(const snk_image_desc_t* desc) { + SOKOL_ASSERT(desc); + snk_image_desc_t res = *desc; + res.image.id = _snk_def(res.image.id, _snuklear.def_img.id); + res.sampler.id = _snk_def(res.sampler.id, _snuklear.def_smp.id); + return res; +} + +static snk_desc_t _snk_desc_defaults(const snk_desc_t* desc) { + SOKOL_ASSERT(desc); + snk_desc_t res = *desc; + res.max_vertices = _snk_def(res.max_vertices, 65536); + res.dpi_scale = _snk_def(res.dpi_scale, 1.0f); + res.image_pool_size = _snk_def(res.image_pool_size, 256); + return res; +} + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +SOKOL_API_IMPL void snk_setup(const snk_desc_t* desc) { + SOKOL_ASSERT(desc); + _snk_clear(&_snuklear, sizeof(_snuklear)); + _snuklear.init_cookie = _SNK_INIT_COOKIE; + _snuklear.desc = _snk_desc_defaults(desc); + #if !defined(SOKOL_NUKLEAR_NO_SOKOL_APP) + _snuklear.is_osx = _snk_is_osx(); + #endif + // can keep color_format, depth_format and sample_count as is, + // since sokol_gfx.h will do its own default-value handling + + _snk_setup_image_pool(_snuklear.desc.image_pool_size); + + // initialize Nuklear + nk_bool init_res = nk_init_default(&_snuklear.ctx, 0); + SOKOL_ASSERT(1 == init_res); (void)init_res; // silence unused warning in release mode +#if !defined(SOKOL_NUKLEAR_NO_SOKOL_APP) + _snuklear.ctx.clip.copy = _snk_clipboard_copy; + _snuklear.ctx.clip.paste = _snk_clipboard_paste; +#endif + + // create sokol-gfx resources + sg_push_debug_group("sokol-nuklear"); + + // vertex buffer + _snuklear.vertex_buffer_size = (size_t)_snuklear.desc.max_vertices * sizeof(_snk_vertex_t); + _snuklear.vbuf = sg_make_buffer(&(sg_buffer_desc){ + .usage = SG_USAGE_STREAM, + .size = _snuklear.vertex_buffer_size, + .label = "sokol-nuklear-vertices" + }); + + // index buffer + _snuklear.index_buffer_size = (size_t)_snuklear.desc.max_vertices * 3 * sizeof(uint16_t); + _snuklear.ibuf = sg_make_buffer(&(sg_buffer_desc){ + .type = SG_BUFFERTYPE_INDEXBUFFER, + .usage = SG_USAGE_STREAM, + .size = _snuklear.index_buffer_size, + .label = "sokol-nuklear-indices" + }); + + // default font sampler + _snuklear.font_smp = sg_make_sampler(&(sg_sampler_desc){ + .min_filter = SG_FILTER_LINEAR, + .mag_filter = SG_FILTER_LINEAR, + .wrap_u = SG_WRAP_CLAMP_TO_EDGE, + .wrap_v = SG_WRAP_CLAMP_TO_EDGE, + .label = "sokol-nuklear-font-sampler", + }); + + // default user-image sampler + _snuklear.def_smp = sg_make_sampler(&(sg_sampler_desc){ + .min_filter = SG_FILTER_NEAREST, + .mag_filter = SG_FILTER_NEAREST, + .wrap_u = SG_WRAP_CLAMP_TO_EDGE, + .wrap_v = SG_WRAP_CLAMP_TO_EDGE, + .label = "sokol-nuklear-default-sampler", + }); + + // default font texture + if (!_snuklear.desc.no_default_font) { + nk_font_atlas_init_default(&_snuklear.atlas); + nk_font_atlas_begin(&_snuklear.atlas); + int font_width = 0, font_height = 0; + const void* pixels = nk_font_atlas_bake(&_snuklear.atlas, &font_width, &font_height, NK_FONT_ATLAS_RGBA32); + SOKOL_ASSERT((font_width > 0) && (font_height > 0)); + _snuklear.font_img = sg_make_image(&(sg_image_desc){ + .width = font_width, + .height = font_height, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .data.subimage[0][0] = { + .ptr = pixels, + .size = (size_t)(font_width * font_height) * sizeof(uint32_t) + }, + .label = "sokol-nuklear-font" + }); + _snuklear.default_font = snk_make_image(&(snk_image_desc_t){ + .image = _snuklear.font_img, + .sampler = _snuklear.font_smp, + }); + nk_font_atlas_end(&_snuklear.atlas, snk_nkhandle(_snuklear.default_font), 0); + nk_font_atlas_cleanup(&_snuklear.atlas); + if (_snuklear.atlas.default_font) { + nk_style_set_font(&_snuklear.ctx, &_snuklear.atlas.default_font->handle); + } + } + + // default user image + static uint32_t def_pixels[64]; + memset(def_pixels, 0xFF, sizeof(def_pixels)); + _snuklear.def_img = sg_make_image(&(sg_image_desc){ + .width = 8, + .height = 8, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .data.subimage[0][0] = SG_RANGE(def_pixels), + .label = "sokol-nuklear-default-image", + }); + + // shader + #if defined SOKOL_METAL + const char* vs_entry = "main0"; + const char* fs_entry = "main0"; + #else + const char* vs_entry = "main"; + const char* fs_entry = "main"; + #endif + sg_range vs_bytecode = { .ptr = 0, .size = 0 }; + sg_range fs_bytecode = { .ptr = 0, .size = 0 }; + const char* vs_source = 0; + const char* fs_source = 0; + #if defined(SOKOL_GLCORE33) + vs_source = _snk_vs_source_glsl330; + fs_source = _snk_fs_source_glsl330; + #elif defined(SOKOL_GLES3) + vs_source = _snk_vs_source_glsl300es; + fs_source = _snk_fs_source_glsl300es; + #elif defined(SOKOL_METAL) + switch (sg_query_backend()) { + case SG_BACKEND_METAL_MACOS: + vs_bytecode = SG_RANGE(_snk_vs_bytecode_metal_macos); + fs_bytecode = SG_RANGE(_snk_fs_bytecode_metal_macos); + break; + case SG_BACKEND_METAL_IOS: + vs_bytecode = SG_RANGE(_snk_vs_bytecode_metal_ios); + fs_bytecode = SG_RANGE(_snk_fs_bytecode_metal_ios); + break; + default: + vs_source = _snk_vs_source_metal_sim; + fs_source = _snk_fs_source_metal_sim; + break; + } + #elif defined(SOKOL_D3D11) + vs_bytecode = SG_RANGE(_snk_vs_bytecode_hlsl4); + fs_bytecode = SG_RANGE(_snk_fs_bytecode_hlsl4); + #elif defined(SOKOL_WGPU) + vs_source = _snk_vs_source_wgsl; + fs_source = _snk_fs_source_wgsl; + #else + vs_source = _snk_vs_source_dummy; + fs_source = _snk_fs_source_dummy; + #endif + _snuklear.shd = sg_make_shader(&(sg_shader_desc){ + .attrs = { + [0] = { .name = "position", .sem_name = "TEXCOORD", .sem_index = 0 }, + [1] = { .name = "texcoord0", .sem_name = "TEXCOORD", .sem_index = 1 }, + [2] = { .name = "color0", .sem_name = "TEXCOORD", .sem_index = 2 }, + }, + .vs = { + .source = vs_source, + .bytecode = vs_bytecode, + .entry = vs_entry, + .d3d11_target = "vs_4_0", + .uniform_blocks[0] = { + .size = sizeof(_snk_vs_params_t), + .uniforms[0] = { + .name = "vs_params", + .type = SG_UNIFORMTYPE_FLOAT4, + .array_count = 1, + } + }, + }, + .fs = { + .source = fs_source, + .bytecode = fs_bytecode, + .entry = fs_entry, + .d3d11_target = "ps_4_0", + .images[0] = { .used = true, .image_type = SG_IMAGETYPE_2D, .sample_type = SG_IMAGESAMPLETYPE_FLOAT }, + .samplers[0] = { .used = true, .sampler_type = SG_SAMPLERTYPE_FILTERING }, + .image_sampler_pairs[0] = { .used = true, .glsl_name = "tex_smp", .image_slot = 0, .sampler_slot = 0 }, + }, + .label = "sokol-nuklear-shader" + }); + + // pipeline object + _snuklear.pip = sg_make_pipeline(&(sg_pipeline_desc){ + .layout = { + .attrs = { + [0] = { .offset = offsetof(_snk_vertex_t, pos), .format=SG_VERTEXFORMAT_FLOAT2 }, + [1] = { .offset = offsetof(_snk_vertex_t, uv), .format=SG_VERTEXFORMAT_FLOAT2 }, + [2] = { .offset = offsetof(_snk_vertex_t, col), .format=SG_VERTEXFORMAT_UBYTE4N } + } + }, + .shader = _snuklear.shd, + .index_type = SG_INDEXTYPE_UINT16, + .sample_count = _snuklear.desc.sample_count, + .depth.pixel_format = _snuklear.desc.depth_format, + .colors[0] = { + .pixel_format = _snuklear.desc.color_format, + .write_mask = SG_COLORMASK_RGB, + .blend = { + .enabled = true, + .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, + .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + } + }, + .label = "sokol-nuklear-pipeline" + }); + + sg_pop_debug_group(); +} + +SOKOL_API_IMPL void snk_shutdown(void) { + SOKOL_ASSERT(_SNK_INIT_COOKIE == _snuklear.init_cookie); + nk_free(&_snuklear.ctx); + nk_font_atlas_clear(&_snuklear.atlas); + + // NOTE: it's valid to call the destroy funcs with SG_INVALID_ID + sg_push_debug_group("sokol-nuklear"); + sg_destroy_pipeline(_snuklear.pip); + sg_destroy_shader(_snuklear.shd); + sg_destroy_sampler(_snuklear.font_smp); + sg_destroy_image(_snuklear.font_img); + sg_destroy_sampler(_snuklear.def_smp); + sg_destroy_image(_snuklear.def_img); + sg_destroy_buffer(_snuklear.ibuf); + sg_destroy_buffer(_snuklear.vbuf); + sg_pop_debug_group(); + _snk_destroy_all_images(); + _snk_discard_image_pool(); + _snuklear.init_cookie = 0; +} + +SOKOL_API_IMPL struct nk_context* snk_new_frame(void) { + SOKOL_ASSERT(_SNK_INIT_COOKIE == _snuklear.init_cookie); + #if !defined(SOKOL_NUKLEAR_NO_SOKOL_APP) + nk_input_begin(&_snuklear.ctx); + if (_snuklear.mouse_did_move) { + nk_input_motion(&_snuklear.ctx, _snuklear.mouse_pos[0], _snuklear.mouse_pos[1]); + _snuklear.mouse_did_move = false; + } + if (_snuklear.mouse_did_scroll) { + nk_input_scroll(&_snuklear.ctx, nk_vec2(_snuklear.mouse_scroll[0], _snuklear.mouse_scroll[1])); + _snuklear.mouse_did_scroll = false; + } + for (int i = 0; i < NK_BUTTON_MAX; i++) { + if (_snuklear.btn_down[i]) { + _snuklear.btn_down[i] = false; + nk_input_button(&_snuklear.ctx, (enum nk_buttons)i, _snuklear.mouse_pos[0], _snuklear.mouse_pos[1], 1); + } else if (_snuklear.btn_up[i]) { + _snuklear.btn_up[i] = false; + nk_input_button(&_snuklear.ctx, (enum nk_buttons)i, _snuklear.mouse_pos[0], _snuklear.mouse_pos[1], 0); + } + } + const size_t char_buffer_len = strlen(_snuklear.char_buffer); + if (char_buffer_len > 0) { + for (size_t i = 0; i < char_buffer_len; i++) { + nk_input_char(&_snuklear.ctx, _snuklear.char_buffer[i]); + } + _snk_clear(_snuklear.char_buffer, NK_INPUT_MAX); + } + for (int i = 0; i < NK_KEY_MAX; i++) { + if (_snuklear.keys_down[i]) { + nk_input_key(&_snuklear.ctx, (enum nk_keys)i, true); + _snuklear.keys_down[i] = 0; + } + if (_snuklear.keys_up[i]) { + nk_input_key(&_snuklear.ctx, (enum nk_keys)i, false); + _snuklear.keys_up[i] = 0; + } + } + nk_input_end(&_snuklear.ctx); + #endif + + nk_clear(&_snuklear.ctx); + return &_snuklear.ctx; +} + +SOKOL_API_IMPL snk_image_t snk_make_image(const snk_image_desc_t* desc) { + SOKOL_ASSERT(_SNK_INIT_COOKIE == _snuklear.init_cookie); + SOKOL_ASSERT(desc); + const snk_image_desc_t desc_def = _snk_image_desc_defaults(desc); + snk_image_t img_id = _snk_alloc_image(); + _snk_image_t* img = _snk_lookup_image(img_id.id); + if (img) { + img->slot.state = _snk_init_image(img, &desc_def); + SOKOL_ASSERT((img->slot.state == _SNK_RESOURCESTATE_VALID) || (img->slot.state == _SNK_RESOURCESTATE_FAILED)); + } else { + _SNK_ERROR(IMAGE_POOL_EXHAUSTED); + } + return img_id; +} + +SOKOL_API_IMPL void snk_destroy_image(snk_image_t img_id) { + SOKOL_ASSERT(_SNK_INIT_COOKIE == _snuklear.init_cookie); + _snk_destroy_image(img_id); +} + +SOKOL_API_IMPL snk_image_desc_t snk_query_image_desc(snk_image_t img_id) { + SOKOL_ASSERT(_SNK_INIT_COOKIE == _snuklear.init_cookie); + _snk_image_t* img = _snk_lookup_image(img_id.id); + if (img) { + return (snk_image_desc_t){ + .image = img->image, + .sampler = img->sampler, + }; + } else { + return (snk_image_desc_t){0}; + } +} + +SOKOL_API_IMPL nk_handle snk_nkhandle(snk_image_t img) { + SOKOL_ASSERT(_SNK_INIT_COOKIE == _snuklear.init_cookie); + return (nk_handle) { .id = (int)img.id }; +} + +SOKOL_API_IMPL snk_image_t snk_image_from_nkhandle(nk_handle h) { + SOKOL_ASSERT(_SNK_INIT_COOKIE == _snuklear.init_cookie); + return (snk_image_t){ .id = (uint32_t) h.id }; +} + +static void _snk_bind_image_sampler(sg_bindings* bindings, nk_handle h) { + _snk_image_t* img = _snk_lookup_image((uint32_t)h.id); + if (img) { + bindings->fs.images[0] = img->image; + bindings->fs.samplers[0] = img->sampler; + } else { + bindings->fs.images[0] = _snuklear.def_img; + bindings->fs.samplers[0] = _snuklear.def_smp; + } +} + +SOKOL_API_IMPL void snk_render(int width, int height) { + SOKOL_ASSERT(_SNK_INIT_COOKIE == _snuklear.init_cookie); + static const struct nk_draw_vertex_layout_element vertex_layout[] = { + {NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(struct _snk_vertex_t, pos)}, + {NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(struct _snk_vertex_t, uv)}, + {NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(struct _snk_vertex_t, col)}, + {NK_VERTEX_LAYOUT_END} + }; + struct nk_convert_config cfg = { + .shape_AA = NK_ANTI_ALIASING_ON, + .line_AA = NK_ANTI_ALIASING_ON, + .vertex_layout = vertex_layout, + .vertex_size = sizeof(_snk_vertex_t), + .vertex_alignment = 4, + .circle_segment_count = 22, + .curve_segment_count = 22, + .arc_segment_count = 22, + .global_alpha = 1.0f + }; + + _snuklear.vs_params.disp_size[0] = (float)width; + _snuklear.vs_params.disp_size[1] = (float)height; + + // Setup vert/index buffers and convert + struct nk_buffer cmds, verts, idx; + nk_buffer_init_default(&cmds); + nk_buffer_init_default(&verts); + nk_buffer_init_default(&idx); + nk_convert(&_snuklear.ctx, &cmds, &verts, &idx, &cfg); + + // Check for vertex- and index-buffer overflow, assert in debug-mode, + // otherwise silently skip rendering + const bool vertex_buffer_overflow = nk_buffer_total(&verts) > _snuklear.vertex_buffer_size; + const bool index_buffer_overflow = nk_buffer_total(&idx) > _snuklear.index_buffer_size; + SOKOL_ASSERT(!vertex_buffer_overflow && !index_buffer_overflow); + if (!vertex_buffer_overflow && !index_buffer_overflow) { + + // Setup rendering + sg_update_buffer(_snuklear.vbuf, &(sg_range){ nk_buffer_memory_const(&verts), nk_buffer_total(&verts) }); + sg_update_buffer(_snuklear.ibuf, &(sg_range){ nk_buffer_memory_const(&idx), nk_buffer_total(&idx) }); + const float dpi_scale = _snuklear.desc.dpi_scale; + const int fb_width = (int)(_snuklear.vs_params.disp_size[0] * dpi_scale); + const int fb_height = (int)(_snuklear.vs_params.disp_size[1] * dpi_scale); + sg_apply_viewport(0, 0, fb_width, fb_height, true); + sg_apply_scissor_rect(0, 0, fb_width, fb_height, true); + sg_apply_pipeline(_snuklear.pip); + sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, &SG_RANGE(_snuklear.vs_params)); + + // Iterate through the command list, rendering each one + const struct nk_draw_command* cmd = NULL; + int idx_offset = 0; + sg_bindings bindings = { + .vertex_buffers[0] = _snuklear.vbuf, + .index_buffer = _snuklear.ibuf, + .index_buffer_offset = idx_offset + }; + nk_draw_foreach(cmd, &_snuklear.ctx, &cmds) { + if (cmd->elem_count > 0) { + _snk_bind_image_sampler(&bindings, cmd->texture); + sg_apply_bindings(&bindings); + sg_apply_scissor_rectf(cmd->clip_rect.x * dpi_scale, + cmd->clip_rect.y * dpi_scale, + cmd->clip_rect.w * dpi_scale, + cmd->clip_rect.h * dpi_scale, + true); + sg_draw(0, (int)cmd->elem_count, 1); + bindings.index_buffer_offset += (int)cmd->elem_count * (int)sizeof(uint16_t); + } + } + sg_apply_scissor_rect(0, 0, fb_width, fb_height, true); + } + + // Cleanup + nk_buffer_free(&cmds); + nk_buffer_free(&verts); + nk_buffer_free(&idx); +} + +#if !defined(SOKOL_NUKLEAR_NO_SOKOL_APP) +_SOKOL_PRIVATE bool _snk_is_ctrl(uint32_t modifiers) { + if (_snuklear.is_osx) { + return 0 != (modifiers & SAPP_MODIFIER_SUPER); + } else { + return 0 != (modifiers & SAPP_MODIFIER_CTRL); + } +} + +_SOKOL_PRIVATE void _snk_append_char(uint32_t char_code) { + size_t idx = strlen(_snuklear.char_buffer); + if (idxkey_code) { + case SAPP_KEYCODE_C: + if (_snk_is_ctrl(ev->modifiers)) { + return NK_KEY_COPY; + } else { + return NK_KEY_NONE; + } + break; + case SAPP_KEYCODE_X: + if (_snk_is_ctrl(ev->modifiers)) { + return NK_KEY_CUT; + } else { + return NK_KEY_NONE; + } + break; + case SAPP_KEYCODE_A: + if (_snk_is_ctrl(ev->modifiers)) { + return NK_KEY_TEXT_SELECT_ALL; + } else { + return NK_KEY_NONE; + } + break; + case SAPP_KEYCODE_Z: + if (_snk_is_ctrl(ev->modifiers)) { + if (ev->modifiers & SAPP_MODIFIER_SHIFT) { + return NK_KEY_TEXT_REDO; + } else { + return NK_KEY_TEXT_UNDO; + } + } else { + return NK_KEY_NONE; + } + break; + case SAPP_KEYCODE_DELETE: return NK_KEY_DEL; + case SAPP_KEYCODE_ENTER: return NK_KEY_ENTER; + case SAPP_KEYCODE_TAB: return NK_KEY_TAB; + case SAPP_KEYCODE_BACKSPACE: return NK_KEY_BACKSPACE; + case SAPP_KEYCODE_UP: return NK_KEY_UP; + case SAPP_KEYCODE_DOWN: return NK_KEY_DOWN; + case SAPP_KEYCODE_LEFT: return NK_KEY_LEFT; + case SAPP_KEYCODE_RIGHT: return NK_KEY_RIGHT; + case SAPP_KEYCODE_LEFT_SHIFT: return NK_KEY_SHIFT; + case SAPP_KEYCODE_RIGHT_SHIFT: return NK_KEY_SHIFT; + case SAPP_KEYCODE_LEFT_CONTROL: return NK_KEY_CTRL; + case SAPP_KEYCODE_RIGHT_CONTROL: return NK_KEY_CTRL; + default: + return NK_KEY_NONE; + } +} + +SOKOL_API_IMPL bool snk_handle_event(const sapp_event* ev) { + SOKOL_ASSERT(_SNK_INIT_COOKIE == _snuklear.init_cookie); + const float dpi_scale = _snuklear.desc.dpi_scale; + switch (ev->type) { + case SAPP_EVENTTYPE_MOUSE_DOWN: + _snuklear.mouse_pos[0] = (int) (ev->mouse_x / dpi_scale); + _snuklear.mouse_pos[1] = (int) (ev->mouse_y / dpi_scale); + switch (ev->mouse_button) { + case SAPP_MOUSEBUTTON_LEFT: + _snuklear.btn_down[NK_BUTTON_LEFT] = true; + break; + case SAPP_MOUSEBUTTON_RIGHT: + _snuklear.btn_down[NK_BUTTON_RIGHT] = true; + break; + case SAPP_MOUSEBUTTON_MIDDLE: + _snuklear.btn_down[NK_BUTTON_MIDDLE] = true; + break; + default: + break; + } + break; + case SAPP_EVENTTYPE_MOUSE_UP: + _snuklear.mouse_pos[0] = (int) (ev->mouse_x / dpi_scale); + _snuklear.mouse_pos[1] = (int) (ev->mouse_y / dpi_scale); + switch (ev->mouse_button) { + case SAPP_MOUSEBUTTON_LEFT: + _snuklear.btn_up[NK_BUTTON_LEFT] = true; + break; + case SAPP_MOUSEBUTTON_RIGHT: + _snuklear.btn_up[NK_BUTTON_RIGHT] = true; + break; + case SAPP_MOUSEBUTTON_MIDDLE: + _snuklear.btn_up[NK_BUTTON_MIDDLE] = true; + break; + default: + break; + } + break; + case SAPP_EVENTTYPE_MOUSE_MOVE: + _snuklear.mouse_pos[0] = (int) (ev->mouse_x / dpi_scale); + _snuklear.mouse_pos[1] = (int) (ev->mouse_y / dpi_scale); + _snuklear.mouse_did_move = true; + break; + case SAPP_EVENTTYPE_MOUSE_ENTER: + case SAPP_EVENTTYPE_MOUSE_LEAVE: + for (int i = 0; i < NK_BUTTON_MAX; i++) { + _snuklear.btn_down[i] = false; + _snuklear.btn_up[i] = false; + } + break; + case SAPP_EVENTTYPE_MOUSE_SCROLL: + _snuklear.mouse_scroll[0] = ev->scroll_x; + _snuklear.mouse_scroll[1] = ev->scroll_y; + _snuklear.mouse_did_scroll = true; + break; + case SAPP_EVENTTYPE_TOUCHES_BEGAN: + _snuklear.btn_down[NK_BUTTON_LEFT] = true; + _snuklear.mouse_pos[0] = (int) (ev->touches[0].pos_x / dpi_scale); + _snuklear.mouse_pos[1] = (int) (ev->touches[0].pos_y / dpi_scale); + _snuklear.mouse_did_move = true; + break; + case SAPP_EVENTTYPE_TOUCHES_MOVED: + _snuklear.mouse_pos[0] = (int) (ev->touches[0].pos_x / dpi_scale); + _snuklear.mouse_pos[1] = (int) (ev->touches[0].pos_y / dpi_scale); + _snuklear.mouse_did_move = true; + break; + case SAPP_EVENTTYPE_TOUCHES_ENDED: + _snuklear.btn_up[NK_BUTTON_LEFT] = true; + _snuklear.mouse_pos[0] = (int) (ev->touches[0].pos_x / dpi_scale); + _snuklear.mouse_pos[1] = (int) (ev->touches[0].pos_y / dpi_scale); + _snuklear.mouse_did_move = true; + break; + case SAPP_EVENTTYPE_TOUCHES_CANCELLED: + _snuklear.btn_up[NK_BUTTON_LEFT] = false; + _snuklear.btn_down[NK_BUTTON_LEFT] = false; + break; + case SAPP_EVENTTYPE_KEY_DOWN: + /* intercept Ctrl-V, this is handled via EVENTTYPE_CLIPBOARD_PASTED */ + if (_snk_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_V)) { + break; + } + /* on web platform, don't forward Ctrl-X, Ctrl-V to the browser */ + if (_snk_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_X)) { + sapp_consume_event(); + } + if (_snk_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_C)) { + sapp_consume_event(); + } + _snuklear.keys_down[_snk_event_to_nuklearkey(ev)] = true; + break; + case SAPP_EVENTTYPE_KEY_UP: + /* intercept Ctrl-V, this is handled via EVENTTYPE_CLIPBOARD_PASTED */ + if (_snk_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_V)) { + break; + } + /* on web platform, don't forward Ctrl-X, Ctrl-V to the browser */ + if (_snk_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_X)) { + sapp_consume_event(); + } + if (_snk_is_ctrl(ev->modifiers) && (ev->key_code == SAPP_KEYCODE_C)) { + sapp_consume_event(); + } + _snuklear.keys_up[_snk_event_to_nuklearkey(ev)] = true; + break; + case SAPP_EVENTTYPE_CHAR: + if ((ev->char_code >= 32) && + (ev->char_code != 127) && + (0 == (ev->modifiers & (SAPP_MODIFIER_ALT|SAPP_MODIFIER_CTRL|SAPP_MODIFIER_SUPER)))) + { + _snk_append_char(ev->char_code); + } + break; + case SAPP_EVENTTYPE_CLIPBOARD_PASTED: + _snuklear.keys_down[NK_KEY_PASTE] = _snuklear.keys_up[NK_KEY_PASTE] = true; + break; + default: + break; + } + return nk_item_is_any_active(&_snuklear.ctx); +} + +SOKOL_API_IMPL nk_flags snk_edit_string(struct nk_context *ctx, nk_flags flags, char *memory, int *len, int max, nk_plugin_filter filter) { + SOKOL_ASSERT(_SNK_INIT_COOKIE == _snuklear.init_cookie); + nk_flags event = nk_edit_string(ctx, flags, memory, len, max, filter); + if ((event & NK_EDIT_ACTIVATED) && !sapp_keyboard_shown()) { + sapp_show_keyboard(true); + } + if ((event & NK_EDIT_DEACTIVATED) && sapp_keyboard_shown()) { + sapp_show_keyboard(false); + } + return event; +} +#endif // SOKOL_NUKLEAR_NO_SOKOL_APP + +#endif // SOKOL_IMPL diff --git a/source/engine/thirdparty/sokol/util/sokol_shape.h b/source/engine/thirdparty/sokol/util/sokol_shape.h new file mode 100644 index 0000000..d1676dd --- /dev/null +++ b/source/engine/thirdparty/sokol/util/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/source/engine/thirdparty/sokol/util/sokol_spine.h b/source/engine/thirdparty/sokol/util/sokol_spine.h new file mode 100644 index 0000000..f76d2bf --- /dev/null +++ b/source/engine/thirdparty/sokol/util/sokol_spine.h @@ -0,0 +1,5941 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_SPINE_IMPL) +#define SOKOL_SPINE_IMPL +#endif +#ifndef SOKOL_SPINE_INCLUDED +/* + sokol_spine.h -- a sokol-gfx renderer for the spine-c runtime + (see https://github.com/EsotericSoftware/spine-runtimes/tree/4.1/spine-c) + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_SPINE_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_GLCORE33 + SOKOL_GLES3 + SOKOL_D3D11 + SOKOL_METAL + + ...optionally provide the following macros to override defaults: + + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_SPINE_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_SPINE_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) + + If sokol_spine.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_SPINE_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + Include the following headers before including sokol_spine.h: + + sokol_gfx.h + + Include the following headers before include the sokol_spine.h *IMPLEMENTATION*: + + spine/spine.h + + You'll also need to compile and link with the spine-c runtime: + + https://github.com/EsotericSoftware/spine-runtimes/tree/4.1/spine-c/spine-c + + + FEATURE OVERVIEW + ================ + sokol_spine.h is a sokol-gfx renderer and 'handle wrapper' for Spine + (http://en.esotericsoftware.com/spine-in-depth) on top of the + spine-c runtime: http://en.esotericsoftware.com/spine-c (source code: + https://github.com/EsotericSoftware/spine-runtimes/tree/4.1/spine-c/spine-c). + + The sokol-gfx renderer allows to manage multiple contexts for rendering + Spine scenes into different sokol-gfx render passes (similar to sokol-gl and + sokol-debugtext), allows to split rendering into layers to mix Spine + rendering with other rendering operations, and it automatically batches + adjacent draw calls for Spine objects that use the same texture and in the + same layer. + + Sokol-spine wraps 'raw' spine-c objects with tagged index handles. This + eliminates the risk of memory corruption via dangling pointers. Any + API calls involving invalid objects either result in a no-op, or + in a proper error. + + The sokol-spine API exposes four 'base object types', and a number of + 'subobject types' which are owned by base objects. + + Base object types are: + + - sspine_atlas: A wrapper around a spine-c spAtlas object, each spAtlas + object owns at least one spAtlasPage object, and each spAtlasPage object + owns exactly one sokol-gfx image object. + + - sspine_skeleton: A skeleton object requires an atlas object for creation, + and is a wrapper around one spine-c spSkeletonData and one + spAnimationStateData object. both contain the shared static data for + individual spine instances + + - sspine_instance: Instance objects are created from skeleton objects. + Instances are the objects that are actually getting rendered. Each instance + tracks its own transformation and animation state, but otherwise just + references shared data of the skeleton object it was created from. An + sspine_instance object is a wrapper around one spine-c spSkeleton, + spAnimationState and spSkeletonClipping object each. + + - sspine_skinset: Skin-set objects are collections of skins which define + the look of an instance. Some Spine scenes consist of combinable skins + (for instance a human character could offer different skins for different + types of clothing, hats, scarfs, shirts, pants, and so on..., and a skin + set would represent a specific outfit). + + Subobject types allow to inspect and manipulate Spine objects in more detail: + + - sspine_anim: Each skeleton object usually offers animations which can + then be scheduled and mixed on an instance. + + - sspine_bone: Bone objects are the hierarchical transform nodes of + a skeleton. The sokol-spine API allows both to inspect the shared + static bone attributes of an sspine_skeleton object, as well as + inspecting and manipulating the per-instance bone attributes + on an sspine_instance object. + + - sspine_event: A running Spine animation may fire 'events' at certain + positions in time (for instance a 'footstep' event whenever a foot + hits the ground). Events can be used to play sound effects (or visual + effects) at the right time. + + - sspine_iktarget: Allows to set the target position for a group of + bones controlled by inverse kinematics. + + There's a couple of other subobject types which are mostly useful to + inspect the interior structure of skeletons. Those will be explained + in detail further down. + + MINIMAL API USAGE OVERVIEW + ========================== + During initialization: + + - call sspine_setup() after initializing sokol-gfx + - create an atlas object from a Spine atlas file with sspine_make_atlas() + - load and initialize the sokol-gfx image objects referenced by the atlas + - create a skeleton object from a Spine skeleton file with sspine_make_skeleton() + - create at least one instance object with sspine_make_instance() + + In the frame loop, outside of sokol-gfx render passes: + + - if needed, move instances around with sspine_set_position() + - if needed, schedule new animations with sspine_set_animation() and sspine_add_animation() + - each frame, advance the current instance animation state with sspine_update_instance() + - each frame, render instances with sspine_draw_instance_in_layer(), this just records + vertices, indices and draw commands into internal buffers, but does no actual + sokol-gfx rendering + + In the frame loop, inside a sokol-gfx render pass: + + - call sspine_draw_layer() to draw all previously recorded instances in a specific layer + + On shutdown: + + - call sspine_shutdown(), ideally before shutting down sokol-gfx + + QUICKSTART STEP BY STEP + ======================= + For a simple demo program using sokol_app.h, sokol_gfx.h and sokol_fetch.h, + see here: [TODO: add link to spine-simple-sapp wasm demo]. + + - sokol_spine.h must be included after sokol_gfx.h (this is true both + for the declaration and implementation): + + #include "sokol_gfx.h" + #include "sokol_spine.h" + + - ...and sokol_gfx.h must be initialized before sokol_spine.h: + + sg_setup(&(sg_desc){ ... }); + sspine_setup(&(sspine_desc){ ... }); + + - You should always provide a logging callback to sokol-spine, otherwise + no warning or errors will be logged. The easiest way is to use sokol_log.h + for this: + + #include "sokol_log.h" + + sspine_setup(&(sspine_desc){ + .logger = { + .func = slog_func + } + }); + + - You can tweak the memory usage of sokol-spine by limiting or expanding the + maximum number of vertices, draw commands and pool sizes: + + sspine_setup(&(sspine_desc){ + .max_vertices = 1024, // default: (1<<16) = 65536 + .max_commands = 128, // default: (1<<14) = 16384 + .context_pool_size = 1, // default: 4 + .atlas_pool_size = 1, // default: 64 + .skeleton_pool_size = 1, // default: 64 + .skinset_pool_size = 1, // default: 64 + .instance_pool_size = 16, // default: 1024 + .logger = { + .func = slog_func, + } + }); + + Sokol-spine uses 32-bit vertex-indices for rendering + (SG_INDEXTYPE_UINT32), so that the maximum number of Spine vertices + in a frame isn't limited to (1<<16). + + - You can override memory allocation and logging with your own + functions, this is explained in detail further down: + + sspine_setup(&(sspine_desc){ + .allocator = { + .alloc_fn = my_alloc, + .free_fn = my_free, + .user_data = ..., + }, + .logger = { + .log_func = my_log_func, + .user_data = ..., + } + }); + + - After initialization, the first thing you need is an sspine_atlas object. + Sokol-spine doesn't concern itself with file IO, it expects all external + data to be provided as pointer/size pairs: + + sspine_atlas atlas = sspine_make_atlas(&(sspine_atlas_desc){ + .data = { + .ptr = ..., // pointer to Spine atlas file data in memory + .size = ..., // atlas file data size in bytes + } + }); + assert(sspine_atlas_valid(atlas)); + + If you load the atlas data asynchronously, you can still run your + per-frame rendering code without waiting for the atlas data to be loaded + and the atlas to be created. This works because calling sokol-spine + functions with 'invalid' object handles is a valid no-op. + + - Optionally you can override some or all of the atlas texture creation parameters: + + sspine_atlas atlas = sspine_make_atlas(&(sspine_atlas_desc){ + .data = { ... }, + .overrides = { + .min_filter = SG_FILTER_NEAREST, + .mag_filter = SG_FILTER_NEAREST, + .mipmap_filter = SG_FILTER_NONE, + .wrap_u = SG_WRAP_MIRROR, + .wrap_v = SG_WRAP_MIRROR, + .premul_alpha_enabled = ..., + .premul_alpha_disabled = ..., + } + }); + + - The atlas file itself doesn't contain any texture data, it only contains + filenames of the required textures. Sokol-spine has already allocated + a sokol-gfx sg_image and sg_sample handle for each required texture, but the + actual loading and initialization must be performed by user code: + + // iterate over atlas textures and initialize sokol-gfx image objects + // with existing handles + const int num = sspine_num_images(atlas); + for (int i = 0; i < num; i++) { + const sspine_image img = sspine_image_by_index(atlas, i); + const sspine_image_info img_info = sspine_get_image_info(img); + assert(img_info.valid); + assert(!img_info.filename.truncated); + + // the filename is now in img_info.filename.cstr, 'somehow' + // load and decode the image data into memory, and then + // initialize the sokol-gfx image from the existing sg_image handle + // in img_info.sgimage: + sg_init_image(img_info.sgimage, &(sg_image_desc){ + .width = ..., + .height = ..., + .pixel_format = ..., + .data.subimage[0][0] = { + .ptr = ..., // pointer to decoded image pixel data + .size = ..., // size of decoded image pixel data in bytes + } + }); + + // ...and same procedure for the sampler object + sg_init_sampler(img_info.sgsampler, &(sg_image_desc){ + .min_filter = img_info.min_filter, + .mag_filter = img_info.mag_filter, + .mipmap_filter = img_info.mipmap_filter, + .wrap_u = img_info.wrap_u, + .wrap_v = img_info.wrap_v, + }); + } + + If you load the image data asynchronously, you can still simply start rendering + before the image data is loaded. This works because sokol-gfx will silently drop + any rendering operations that involve 'incomplete' objects. + + - Once an atlas object has been created (independently from loading any image data), + an sspine_skeleton object is needed next. This requires a valid atlas object + handle as input, and a pointer to the Spine skeleton file data loaded into memory. + + Spine skeleton files come in two flavours: binary or json, for binary data, + a ptr/size pair must be provided: + + sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){ + .atlas = atlas, // atlas must be a valid sspine_atlas handle + .binary_data = { + .ptr = ..., // pointer to binary skeleton data in memory + .size = ..., // size of binary skeleton data in bytes + } + }); + assert(sspine_skeleton_valid(skeleton)); + + For JSON skeleton file data, the data must be provided as a zero-terminated C string: + + sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){ + .atlas = atlas, + .json_data = ..., // JSON skeleton data as zero-terminated(!) C-string + }); + + Like with all sokol-spine objects, if you load the skeleton data asynchronously + and only then create a skeleton object, you can already start rendering before + the data is loaded and the Spine objects have been created. Any operations + involving 'incomplete' handles will be dropped. + + - You can pre-scale the Spine scene size, and you can provide a default cross-fade + duration for animation mixing: + + sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){ + .atlas = atlas, + .binary_data = { ... }, + .prescale = 0.5f, // scale to half-size + .anim_default_mix = 0.2f, // default anim mixing cross-fade duration 0.2 seconds + }); + + - Once the skeleton object has been created, it's finally time to create one or many instance objects. + If you want to independently render and animate the 'same' Spine object many times in a frame, + you should only create one sspine_skeleton object, and then as many sspine_instance object + as needed from the shared skeleton object: + + sspine_instance instance = sspine_make_instance(&(sspine_instance_desc){ + .skeleton = skeleton, // must be a valid skeleton handle + }); + assert(sspine_instance_valid(instance)); + + After creation, the sspine_instance will have a 'default skin' set as its appearance. + + - To set the position of an instance: + + sspine_set_position(inst, (sspine_vec2){ .x=..., .y=... }); + + Sokol-spine doesn't define a specific unit (like pixels or meters), instead the + rendering coordinate system is defined later at 'render time'. + + - To schedule an initial looping animation by its name: + + // first lookup up the animation by name on the skeleton: + sspine_anim anim = sspine_anim_by_name(skeleton, "walk"); + assert(sspine_anim_valid(anim)); + + // then schedule the animation on the instance, on mixer track 0, as looping: + sspine_set_animation(instance, anim, 0, true); + + Scheduling and mixing animations will be explained in more detail further down. + + - To advance and mix instance animations: + + sspine_update_instance(instance, delta_time_in_seconds); + + Usually you'd call this each frame for each active instance with the + frame duration in seconds. + + - Now it's finally time to 'render' the instance at its current position and + animation state: + + sspine_draw_instance_in_layer(instance, 0); + + Instances are generally rendered into numbered virtual 'render layers' (in this + case, layer 0). Layers are useful for interleaving sokol-spine rendering + with other rendering commands (like background and foreground tile maps, + sprites or text). + + - It's important to note that no actual sokol-gfx rendering happens in + sspine_draw_instance_in_layer(), instead only vertices, indices and + draw commands are recorded into internal memory buffes. + + - The only sokol-spine function which *must* (and should) be called inside + a sokol-gfx rendering pass is sspine_draw_layer(). + + This renders all draw commands that have been recorded previously in a + specific layer via sspine_draw_instance_in_layer(). + + const sspine_layer_transform tform = { ... }; + + sg_begin_default_pass(...); + sspine_draw_layer(0, tform); + sg_end_pass(); + sg_commit(); + + IMPORTANT: DO *NOT* MIX any calls to sspine_draw_instance_in_layer() + with sspine_draw_layer(), as this will confuse the internal draw command + recording. Ideally, move all sokol-gfx pass rendering (including all + sspine_draw_layer() calls) towards the end of the frame, separate from + any other sokol-spine calls. + + The sspine_layer_transform struct defines the layer's screen space coordinate + system. For instance to map Spine coordinates to framebuffer pixels, with the + origin in the screen center, you'd setup the layer transform like this: + + const float width = sapp_widthf(); + const float height = sapp_heightf(); + const sspine_layer_transform tform = { + .size = { .x = width, .y = height }, + .origin = { .x = width * 0.5f, .y = height * 0.5f }, + }; + + With this pixel mapping, the Spine scene would *not* scale with window size, + which often is not very useful. Instead it might make more sense to render + to a fixed 'virtual' resolution, for instance 1024 * 768: + + const sspine_layer_transform tform = { + .size = { .x = 1024.0f, .y = 768.0f }, + .origin = { .x = 512.0f, .y = 384.0f }, + }; + + How to configure a virtual resolution with a fixed aspect ratio is + left as an exercise to the reader ;) + + - That's it for basic sokol-spine setup and rendering. Any existing objects + will automatically be cleaned up when calling sspine_shutdown(), this + should be called before shutting down sokol-gfx, but this is not required: + + sspine_shutdown(); + sg_shutdown(); + + - You can explicitly destroy the base object types if you don't need them + any longer. This will cause the underlying spine-c objects to be + freed and the memory to be returned to the operating system: + + sspine_destroy_instance(instance); + sspine_destroy_skinset(skinset); + sspine_destroy_skeleton(skeleton); + sspine_destroy_atlas(atlas); + + You can destroy these objects in any order without causing memory corruption + issues. Instead any dependent object handles will simply become invalid (e.g. + if you destroy an atlas object, all skeletons and instances created from + this atlas will 'technically' still exist, but their handles will resolve to + 'invalid' and all sokol-spine calls involving these handles will silently fail). + + For instance: + + // create an atlas, skeleton and instance + sspine_atlas atlas = sspine_make_atlas(&(sspine_atlas_desc){ ... }); + assert(sspine_atlas_valid(atlas)); + + sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){ + .atlas = atlas, + ... + }); + assert(sspine_skeleton_valid(skeleton)); + + sspine_instance instance = sspine_make_instance(&(sspine_instance_desc){ + .skeleton = skeleton, + }); + assert(sspine_instance_valid(instance)); + + // destroy the atlas object: + sspine_destroy_atlas(atlas); + + // the skeleton and instance handle should now be invalid, but + // otherwise, nothing bad will happen: + if (!sspine_skeleton_valid(skeleton)) { + ... + } + if (!sspine_instance_valid(instance)) { + ... + } + + RENDERER DETAILS + ================ + Any rendering related work happens in the functions sspine_draw_instance_in_layer() and + sspine_draw_layer(). + + sspine_draw_instance_in_layer() will result in vertices, indices and internal + draw commands which will be recorded into internal memory buffers (e.g. + no sokol-gfx functions will be called here). + + If possible, batching will be performed by merging a new draw command with + the previously recorded draw command. For two draw commands to be merged, + the following conditions must be tru: + + - rendering needs to go into the same layer + - the same atlas texture must be used + - the blend mode must be compatible (the Spine blending modes + 'normal' and 'additive' can be merged, but not 'multiply') + - the same premultiplied alpha mode must be used + + To make the most out of batching: + + - use Spine objects which only have a single atlas texture + and blend mode across all slots + - group sspine_draw_instance_in_layer() calls by layer + + After all instances have been 'rendered' (or rather: recorded) into layers, + the actually rendering happens inside a sokol-gfx pass by calling the + function sspine_draw_layer() for each layer in 'z order' (e.g. the layer + index doesn't matter for z-ordering, only the order how sspine_draw_layer() is + called). + + Only the first call to sspine_draw_layer() in a frame will copy the recorded + vertices and indices into sokol-gfx buffers. + + Each call to sspine_draw_layer() will iterate over all recorded (and + hopefully well-batched) draw commands, skip any draw commands with a + non-matching layer index, and draw only those with a matching layer by + calling: + + - if the pipeline object has changed: + - sg_apply_pipeline() + - sg_apply_uniforms() for the vertex stage + - if the atlas texture has changed: + - sg_apply_bindings() + - if the premultiplied-alpha mode has changed: + - sg_apply_uniforms() for the fragment stage + - and finally sg_draw() + + The main purpose of render layers is to mix Spine rendering with other + render operations. In the not too distant future, the same render layer idea + will also be implemented at least for sokol-gl and sokol-debugtext. + + FIXME: does this section need more details about layer transforms? + + RENDERING WITH CONTEXTS + ======================= + At first glance, render contexts may look like more heavy-weight + render layers, but they serve a different purpose: they are useful + if Spine rendering needs to happen in different sokol-gfx render passes + with different pixel formats and MSAA sample counts. + + All Spine rendering happens within a context, even you don't call any + of the context API functions, in this case, an internal 'default context' + will be used. + + Each context has its own internal vertex-, index- and command buffer and + all context state is completely independent from any other contexts. + + To create a new context object, call: + + sspine_context ctx = sspine_make_context(&(sspine_context_desc){ + .max_vertices = ..., + .max_commands = ..., + .color_format = SG_PIXELFORMAT_..., + .depth_format = SG_PIXELFORMAT_..., + .sample_count = ..., + .color_write_mask = SG_COLORMASK_..., + }); + + The color_format, depth_format and sample_count items must be compatible + with the sokol-gfx render pass you're going to render into. + + If you omit the color_format, depth_format and sample_count designators, + the new context will be compatible with the sokol-gfx default pass + (which is most likely not what you want, unless your offscreen render passes + exactly match the default pass attributes). + + Once a context has been created, it can be made active with: + + sspine_set_context(ctx); + + To set the default context again: + + sspine_set_contxt(sspine_default_context()); + + ...and to get the currently active context: + + sspine_context cur_ctx = sspine_get_context(); + + The currently active context only matter for two functions: + + - sspine_draw_instance_in_layer() + - sspine_draw_layer() + + Alternatively you can bypass the currently set context with these + alternative functions: + + - sspine_context_draw_layer_in_instance(ctx, ...) + - sspine_context_draw_layer(ctx, ...) + + These explicitly take a context argument, completely ignore + and don't change the active context. + + You can query some information about a context with the function: + + sspine_context_info info = ssgpine_get_context_info(ctx); + + This returns the current number of recorded vertices, indices + and draw commands. + + RESOURCE STATES: + ================ + Similar to sokol-gfx, you can query the current 'resource state' of Spine + objects: + + sspine_resource_state sspine_get_atlas_resource_state(sspine_atlas atlas); + sspine_resource_state sspine_get_skeleton_resource_state(sspine_atlas atlas); + sspine_resource_state sspine_get_instance_resource_state(sspine_atlas atlas); + sspine_resource_state sspine_get_skinset_resource_state(sspine_atlas atlas); + sspine_resource_state sspine_get_context_resource_state(sspine_atlas atlas); + + This returns one of + + - SSPINE_RESOURCE_VALID: the object is valid and ready to use + - SSPINE_RESOURCE_FAILED: the object creation has failed + - SSPINE_RESOURCE_INVALID: the object or one of its dependencies is + invalid, it either no longer exists, or the handle hasn't been + initialized with a call to one of the object creation functions + + MISC HELPER FUNCTIONS: + ====================== + There's a couple of helper functions which don't fit into a big enough category + of their own: + + You can ask a skeleton for the atlas it has been created from: + + sspine_atlas atlas = sspine_get_skeleton_atlas(skeleton); + + ...and likewise, ask an instance for the skeleton it has been created from: + + sspine_skeleton skeleton = sspine_get_instance_skeleton(instance); + + ...and finally you can convert a layer transform struct into a 4x4 projection + matrix that's memory-layout compatible with sokol-gl: + + const sspine_layer_transform tform = { ... }; + const sspine_mat4 proj = sspine_layer_transform_to_mat4(&tform); + sgl_matrix_mode_projection(); + sgl_load_matrix(proj.m); + + ANIMATIONS + ========== + Animations have their own handle type sspine_anim. A valid sspine_anim + handle is either obtained by looking up an animation by name from a skeleton: + + sspine_anim anim = sspine_anim_by_name(skeleton, "walk"); + + ...or by index: + + sspine_anim anim = sspine_anim_by_index(skeleton, 0); + + The returned anim handle will be invalid if an animation of that name doesn't + exist, or the provided index is out-of-range: + + if (!sspine_anim_is_valid(anim)) { + // animation handle is not valid + } + + An animation handle will also become invalid when the skeleton object it was + created is destroyed, or otherwise becomes invalid. + + You can iterate over all animations in a skeleton: + + const int num_anims = sspine_num_anims(skeleton); + for (int anim_index = 0; anim_index < num_anims; anim_index++) { + sspine_anim anim = sspine_anim_by_index(skeleton, anim_index); + ... + } + + Since sspine_anim is a 'fat handle' (it houses a skeleton handle and an index), + there's a helper function which checks if two anim handles are equal: + + if (sspine_anim_equal(anim0, anim1)) { + ... + } + + To query information about an animation: + + const sspine_anim_info info = sspine_get_anim_info(anim); + if (info.valid) { + printf("index: %d, duration: %f, name: %s", info.index, info.duration, info.name.cstr); + } + + Scheduling and mixing animations is controlled through the following functions: + + void sspine_clear_animation_tracks(sspine_instance instance); + void sspine_clear_animation_track(sspine_instance instance, int track_index); + void sspine_set_animation(sspine_instance instance, sspine_anim anim, int track_index, bool loop); + void sspine_add_animation(sspine_instance instance, sspine_anim anim, int track_index, bool loop, float delay); + void sspine_set_empty_animation(sspine_instance instance, int track_index, float mix_duration); + void sspine_add_empty_animation(sspine_instance instance, int track_index, float mix_duration, float delay); + + Please refer to the spine-c documentation to get an idea what these functions do: + + http://en.esotericsoftware.com/spine-c#Applying-animations + + EVENTS + ====== + For a general idea of Spine events, see here: http://esotericsoftware.com/spine-events + + After calling sspine_update_instance() to advance the currently configured animations, + you can poll for triggered events like this: + + const int num_triggered_events = sspine_num_triggered_events(instance); + for (int i = 0; i < num_triggered_events; i++) { + const sspine_triggered_event_info info = sspine_get_triggered_event_info(instance, i); + if (info.valid) { + ... + } + } + + The returned sspine_triggered_event_info struct gives you the current runtime properties + of the event (in case the event has keyed properties). For the actual list of event + properties please see the actual sspine_triggered_event_info struct declaration. + + It's also possible to inspect the static event definition on a skeleton, this works + the same as iterating through animations. You can lookup an event by name, + get the number of events, lookup an event by its index, and get detailed + information about an event: + + int sspine_num_events(sspine_skeleton skeleton); + sspine_event sspine_event_by_name(sspine_skeleton skeleton, const char* name); + sspine_event sspine_event_by_index(sspine_skeleton skeleton, int index); + bool sspine_event_valid(sspine_event event); + bool sspine_event_equal(sspine_event first, sspine_event second); + sspine_event_info sspine_get_event_info(sspine_event event); + + (FIXME: shouldn't the event info struct contains an sspine_anim handle?) + + IK TARGETS + ========== + The IK target function group allows to iterate over the IK targets that have been + defined on a skeleton, find an IK target by name, get detailed information about + an IK target, and most importantly, set the world space position of an IK target + which updates the position of all bones influenced by the IK target: + + int sspine_num_iktargets(sspine_skeleton skeleton); + sspine_iktarget sspine_iktarget_by_name(sspine_skeleton skeleton, const char* name); + sspine_iktarget sspine_iktarget_by_index(sspine_skeleton skeleton, int index); + bool sspine_iktarget_valid(sspine_iktarget iktarget); + bool sspine_iktarget_equal(sspine_iktarget first, sspine_iktarget second); + sspine_iktarget_info sspine_get_iktarget_info(sspine_iktarget iktarget); + void sspine_set_iktarget_world_pos(sspine_instance instance, sspine_iktarget iktarget, sspine_vec2 world_pos); + + BONES + ===== + Skeleton bones are wrapped with an sspine_bone handle which can be created from + a skeleton handle, and either a bone name: + + sspine_bone bone = sspine_bone_by_name(skeleton, "root"); + assert(sspine_bone_valid(bone)); + + ...or a bone index: + + sspine_bone bone = sspine_bone_by_index(skeleton, 0); + assert(sspine_bone_valid(bone)); + + ...to iterate over all bones of a skeleton and query information about each + bone: + + const int num_bones = sspine_num_bones(skeleton); + for (int bone_index = 0; bone_index < num_bones; bone_index++) { + sspine_bone bone = sspine_bone_by_index(skeleton, bone_index); + const sspine_bone_info info = sspine_get_bone_info(skeleton, bone); + if (info.valid) { + ... + } + } + + The sspine_bone_info struct provides the shared, static bone state in the skeleton (like + the name, a parent bone handle, bone length, pose transform and a color attribute), + but doesn't contain any dynamic information of per-instance bones. + + To manipulate the per-instance bone attributes use the following setter functions: + + void sspine_set_bone_transform(sspine_instance instance, sspine_bone bone, const sspine_bone_transform* transform); + void sspine_set_bone_position(sspine_instance instance, sspine_bone bone, sspine_vec2 position); + void sspine_set_bone_rotation(sspine_instance instance, sspine_bone bone, float rotation); + void sspine_set_bone_scale(sspine_instance instance, sspine_bone bone, sspine_vec2 scale); + void sspine_set_bone_shear(sspine_instance instance, sspine_bone bone, sspine_vec2 shear); + + ...and to query the per-instance bone attributes, the following getters: + + sspine_bone_transform sspine_get_bone_transform(sspine_instance instance, sspine_bone bone); + sspine_vec2 sspine_get_bone_position(sspine_instance instance, sspine_bone bone); + float sspine_get_bone_rotation(sspine_instance instance, sspine_bone bone); + sspine_vec2 sspine_get_bone_scale(sspine_instance instance, sspine_bone bone); + sspine_vec2 sspine_get_bone_shear(sspine_instance instance, sspine_bone bone); + + These functions all work in the local bone coordinate system (relative to a bone's parent bone). + + To transform positions between bone-local and global space use the following helper functions: + + sspine_vec2 sspine_bone_local_to_world(sspine_instance instance, sspine_bone bone, sspine_vec2 local_pos); + sspine_vec2 sspine_bone_world_to_local(sspine_instance instance, sspine_bone bone, sspine_vec2 world_pos); + + ...and as a convenience, there's a helper function which obtains the bone position in global space + directly: + + sspine_vec2 sspine_get_bone_world_position(sspine_instance instance, sspine_bone bone); + + SKINS AND SKINSETS + ================== + Skins are named pieces of geometry which can be turned on and off, what makes Spine skins a bit + confusing is that they are hierarchical. A skin can itself be a collection of other skins. Setting + the 'root skin' will also make all 'child skins' visible. In sokol-spine collections of skins are + managed through dedicated 'skin set' objects. Under the hood they create a 'root skin' where the + skins of the skin set are attached to, but from the outside it just looks like a 'flat' collection + of skins without the tricky hierarchical management. + + Like other 'subobjects', skin handles can be obtained by the skin name from a skeleton handle: + + sspine_skin skin = sspine_skin_by_name(skeleton, "jacket"); + assert(sspine_skin_valid(skin)); + + ...or by a skin index: + + sspine_skin skin = sspine_skin_by_index(skeleton, 0); + assert(sspine_skin_valid(skin)); + + ...you can iterate over all skins of a skeleton and query some information about the skin: + + const int num_skins = sspine_num_skins(skeleton); + for (int skin_index = 0; skin_index < num_skins; skin_index++) { + sspine_skin skin = sspine_skin_by_index(skin_index); + sspine_skin_info info = sspine_get_skin_info(skin); + if (info.valid) { + ... + } + } + + Currently, the only useful query item is the skin name though. + + To make a skin visible on an instance, just call: + + sspine_set_skin(instance, skin); + + ...this will first deactivate the previous skin before setting a new skin. + + A more powerful way to configure the skin visibility is through 'skin sets'. Skin + sets are simply flat collections of skins which should be made visible at once. + A new skin set is created like this: + + sspine_skinset skinset = sspine_make_skinset(&(sspine_skinset_desc){ + .skeleton = skeleton, + .skins = { + sspine_skin_by_name(skeleton, "blue-jacket"), + sspine_skin_by_name(skeleton, "green-pants"), + sspine_skin_by_name(skeleton, "blonde-hair"), + ... + } + }); + assert(sspine_skinset_valid(skinset)) + + ...then simply set the skinset on an instance to reconfigure the appearance + of the instance: + + sspine_set_skinset(instance, skinset); + + The functions sspine_set_skinset() and sspine_set_skin() will cancel each other. + Calling sspine_set_skinset() deactivates the effect of sspine_set_skin() and + vice versa. + + + 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" + + sspine_setup(&(sspine_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. 'sspine' + uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info + uint32_t log_item_id, // SSPINE_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_spine.h + const char* filename_or_null, // source filename, may be nullptr in release mode + void* user_data) + { + ... + } + + ...and then setup sokol-spine like this: + + sspine_setup(&(sspine_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. + + + 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); + } + + ... + sspine_setup(&(sspine_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. + + + LICENSE + ======= + 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. +*/ +#define SOKOL_SPINE_INCLUDED (1) +#include +#include +#include // size_t + +#if !defined(SOKOL_GFX_INCLUDED) +#error "Please include sokol_gfx.h before sokol_spine.h" +#endif + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_SPINE_API_DECL) +#define SOKOL_SPINE_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_SPINE_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_SPINE_IMPL) +#define SOKOL_SPINE_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_SPINE_API_DECL __declspec(dllimport) +#else +#define SOKOL_SPINE_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + SSPINE_INVALID_ID = 0, + SSPINE_MAX_SKINSET_SKINS = 32, + SSPINE_MAX_STRING_SIZE = 61, // see sspine_string struct +}; + +typedef struct sspine_context { uint32_t id; } sspine_context; +typedef struct sspine_atlas { uint32_t id; } sspine_atlas; +typedef struct sspine_skeleton { uint32_t id; } sspine_skeleton; +typedef struct sspine_instance { uint32_t id; } sspine_instance; +typedef struct sspine_skinset { uint32_t id; } sspine_skinset; + +typedef struct sspine_image { uint32_t atlas_id; int index; } sspine_image; +typedef struct sspine_atlas_page { uint32_t atlas_id; int index; } sspine_atlas_page; +typedef struct sspine_anim { uint32_t skeleton_id; int index; } sspine_anim; +typedef struct sspine_bone { uint32_t skeleton_id; int index; } sspine_bone; +typedef struct sspine_slot { uint32_t skeleton_id; int index; } sspine_slot; +typedef struct sspine_event { uint32_t skeleton_id; int index; } sspine_event; +typedef struct sspine_iktarget { uint32_t skeleton_id; int index; } sspine_iktarget; +typedef struct sspine_skin { uint32_t skeleton_id; int index; } sspine_skin; + +typedef struct sspine_range { const void* ptr; size_t size; } sspine_range; +typedef struct sspine_vec2 { float x, y; } sspine_vec2; +typedef struct sspine_mat4 { float m[16]; } sspine_mat4; +typedef sg_color sspine_color; + +typedef struct sspine_string { + bool valid; + bool truncated; + uint8_t len; + char cstr[SSPINE_MAX_STRING_SIZE]; +} sspine_string; + +typedef enum sspine_resource_state { + SSPINE_RESOURCESTATE_INITIAL, + SSPINE_RESOURCESTATE_ALLOC, + SSPINE_RESOURCESTATE_VALID, + SSPINE_RESOURCESTATE_FAILED, + SSPINE_RESOURCESTATE_INVALID, + _SSPINE_RESOURCESTATE_FORCE_U32 = 0x7FFFFFFF +} sspine_resource_state; + +// log item codes via x-macro magic +#define _SSPINE_LOG_ITEMS \ + _SSPINE_LOGITEM_XMACRO(OK, "Ok")\ + _SSPINE_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed")\ + _SSPINE_LOGITEM_XMACRO(CONTEXT_POOL_EXHAUSTED, "context pool exhausted (adjust via sspine_desc.context_pool_size)")\ + _SSPINE_LOGITEM_XMACRO(ATLAS_POOL_EXHAUSTED, "atlas pool exhausted (adjust via sspine_desc.atlas_pool_size)")\ + _SSPINE_LOGITEM_XMACRO(SKELETON_POOL_EXHAUSTED, "skeleton pool exhausted (adjust via sspine_desc.skeleton_pool_size)")\ + _SSPINE_LOGITEM_XMACRO(SKINSET_POOL_EXHAUSTED, "skinset pool exhausted (adjust via sspine_desc.skinset_pool_size)")\ + _SSPINE_LOGITEM_XMACRO(INSTANCE_POOL_EXHAUSTED, "instance pool exhausted (adjust via sspine_desc.instance_pool_size)")\ + _SSPINE_LOGITEM_XMACRO(CANNOT_DESTROY_DEFAULT_CONTEXT, "cannot destroy default context")\ + _SSPINE_LOGITEM_XMACRO(ATLAS_DESC_NO_DATA, "no data provided in sspine_atlas_desc.data")\ + _SSPINE_LOGITEM_XMACRO(SPINE_ATLAS_CREATION_FAILED, "spAtlas_create() failed")\ + _SSPINE_LOGITEM_XMACRO(SG_ALLOC_IMAGE_FAILED, "sg_alloc_image() failed")\ + _SSPINE_LOGITEM_XMACRO(SG_ALLOC_SAMPLER_FAILED, "sg_alloc_sampler() failed")\ + _SSPINE_LOGITEM_XMACRO(SKELETON_DESC_NO_DATA, "no data provided in sspine_skeleton_desc.json_data or .binary_data")\ + _SSPINE_LOGITEM_XMACRO(SKELETON_DESC_NO_ATLAS, "no atlas object provided in sspine_skeleton_desc.atlas")\ + _SSPINE_LOGITEM_XMACRO(SKELETON_ATLAS_NOT_VALID, "sspine_skeleton_desc.atlas is not in valid state")\ + _SSPINE_LOGITEM_XMACRO(CREATE_SKELETON_DATA_FROM_JSON_FAILED, "spSkeletonJson_readSkeletonData() failed")\ + _SSPINE_LOGITEM_XMACRO(CREATE_SKELETON_DATA_FROM_BINARY_FAILED, "spSkeletonBinary_readSkeletonData() failed")\ + _SSPINE_LOGITEM_XMACRO(SKINSET_DESC_NO_SKELETON, "no skeleton object provided in sspine_skinset_desc.skeleton")\ + _SSPINE_LOGITEM_XMACRO(SKINSET_SKELETON_NOT_VALID, "sspine_skinset_desc.skeleton is not in valid state")\ + _SSPINE_LOGITEM_XMACRO(SKINSET_INVALID_SKIN_HANDLE, "invalid skin handle in sspine_skinset_desc.skins[]")\ + _SSPINE_LOGITEM_XMACRO(INSTANCE_DESC_NO_SKELETON, "no skeleton object provided in sspine_instance_desc.skeleton")\ + _SSPINE_LOGITEM_XMACRO(INSTANCE_SKELETON_NOT_VALID, "sspine_instance_desc.skeleton is not in valid state")\ + _SSPINE_LOGITEM_XMACRO(INSTANCE_ATLAS_NOT_VALID, "skeleton's atlas object no longer valid via sspine_instance_desc.skeleton")\ + _SSPINE_LOGITEM_XMACRO(SPINE_SKELETON_CREATION_FAILED, "spSkeleton_create() failed")\ + _SSPINE_LOGITEM_XMACRO(SPINE_ANIMATIONSTATE_CREATION_FAILED, "spAnimationState_create() failed")\ + _SSPINE_LOGITEM_XMACRO(SPINE_SKELETONCLIPPING_CREATION_FAILED, "spSkeletonClipping_create() failed")\ + _SSPINE_LOGITEM_XMACRO(COMMAND_BUFFER_FULL, "command buffer full (adjust via sspine_desc.max_commands)")\ + _SSPINE_LOGITEM_XMACRO(VERTEX_BUFFER_FULL, "vertex buffer (adjust via sspine_desc.max_vertices)")\ + _SSPINE_LOGITEM_XMACRO(INDEX_BUFFER_FULL, "index buffer full (adjust via sspine_desc.max_vertices)")\ + _SSPINE_LOGITEM_XMACRO(STRING_TRUNCATED, "a string has been truncated")\ + _SSPINE_LOGITEM_XMACRO(ADD_COMMIT_LISTENER_FAILED, "sg_add_commit_listener() failed")\ + +#define _SSPINE_LOGITEM_XMACRO(item,msg) SSPINE_LOGITEM_##item, +typedef enum sspine_log_item { + _SSPINE_LOG_ITEMS +} sspine_log_item; +#undef _SSPINE_LOGITEM_XMACRO + +typedef struct sspine_layer_transform { + sspine_vec2 size; + sspine_vec2 origin; +} sspine_layer_transform; + +typedef struct sspine_bone_transform { + sspine_vec2 position; + float rotation; // in degrees + sspine_vec2 scale; + sspine_vec2 shear; // in degrees +} sspine_bone_transform; + +typedef struct sspine_context_desc { + int max_vertices; + int max_commands; + sg_pixel_format color_format; + sg_pixel_format depth_format; + int sample_count; + sg_color_mask color_write_mask; +} sspine_context_desc; + +typedef struct sspine_context_info { + int num_vertices; // current number of vertices + int num_indices; // current number of indices + int num_commands; // current number of commands +} sspine_context_info; + +typedef struct sspine_image_info { + bool valid; + sg_image sgimage; + sg_sampler sgsampler; + sg_filter min_filter; + sg_filter mag_filter; + sg_filter mipmap_filter; + sg_wrap wrap_u; + sg_wrap wrap_v; + int width; + int height; + bool premul_alpha; + sspine_string filename; +} sspine_image_info; + +typedef struct sspine_atlas_overrides { + sg_filter min_filter; + sg_filter mag_filter; + sg_filter mipmap_filter; + sg_wrap wrap_u; + sg_wrap wrap_v; + bool premul_alpha_enabled; + bool premul_alpha_disabled; +} sspine_atlas_overrides; + +typedef struct sspine_atlas_desc { + sspine_range data; + sspine_atlas_overrides override; +} sspine_atlas_desc; + +typedef struct sspine_atlas_page_info { + bool valid; + sspine_atlas atlas; + sspine_image_info image; + sspine_atlas_overrides overrides; +} sspine_atlas_page_info; + +typedef struct sspine_skeleton_desc { + sspine_atlas atlas; + float prescale; + float anim_default_mix; + const char* json_data; + sspine_range binary_data; +} sspine_skeleton_desc; + +typedef struct sspine_skinset_desc { + sspine_skeleton skeleton; + sspine_skin skins[SSPINE_MAX_SKINSET_SKINS]; +} sspine_skinset_desc; + +typedef struct sspine_anim_info { + bool valid; + int index; + float duration; + sspine_string name; +} sspine_anim_info; + +typedef struct sspine_bone_info { + bool valid; + int index; + sspine_bone parent_bone; + float length; + sspine_bone_transform pose; + sspine_color color; + sspine_string name; +} sspine_bone_info; + +typedef struct sspine_slot_info { + bool valid; + int index; + sspine_bone bone; + sspine_color color; + sspine_string attachment_name; + sspine_string name; +} sspine_slot_info; + +typedef struct sspine_iktarget_info { + bool valid; + int index; + sspine_bone target_bone; + sspine_string name; +} sspine_iktarget_info; + +typedef struct sspine_skin_info { + bool valid; + int index; + sspine_string name; +} sspine_skin_info; + +typedef struct sspine_event_info { + bool valid; + int index; + int int_value; + float float_value; + float volume; + float balance; + sspine_string name; + sspine_string string_value; + sspine_string audio_path; +} sspine_event_info; + +typedef struct sspine_triggered_event_info { + bool valid; + sspine_event event; + float time; + int int_value; + float float_value; + float volume; + float balance; + sspine_string string_value; +} sspine_triggered_event_info; + +typedef struct sspine_instance_desc { + sspine_skeleton skeleton; +} sspine_instance_desc; + +typedef struct sspine_allocator { + void* (*alloc_fn)(size_t size, void* user_data); + void (*free_fn)(void* ptr, void* user_data); + void* user_data; +} sspine_allocator; + +typedef struct sspine_logger { + void (*func)( + const char* tag, // always "sspine" + uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info + uint32_t log_item_id, // SSPINE_LOGITEM_* + const char* message_or_null, // a message string, may be nullptr in release mode + uint32_t line_nr, // line number in sokol_spine.h + const char* filename_or_null, // the source filename, may be nullptr in release mode + void* user_data); + void* user_data; +} sspine_logger; + +typedef struct sspine_desc { + int max_vertices; + int max_commands; + int context_pool_size; + int atlas_pool_size; + int skeleton_pool_size; + int skinset_pool_size; + int instance_pool_size; + sg_pixel_format color_format; + sg_pixel_format depth_format; + int sample_count; + sg_color_mask color_write_mask; + sspine_allocator allocator; // optional allocation override functions (default: malloc/free) + sspine_logger logger; // optional logging function (default: NO LOGGING!) +} sspine_desc; + +// setup/shutdown +SOKOL_SPINE_API_DECL void sspine_setup(const sspine_desc* desc); +SOKOL_SPINE_API_DECL void sspine_shutdown(void); + +// context functions +SOKOL_SPINE_API_DECL sspine_context sspine_make_context(const sspine_context_desc* desc); +SOKOL_SPINE_API_DECL void sspine_destroy_context(sspine_context ctx); +SOKOL_SPINE_API_DECL void sspine_set_context(sspine_context ctx); +SOKOL_SPINE_API_DECL sspine_context sspine_get_context(void); +SOKOL_SPINE_API_DECL sspine_context sspine_default_context(void); +SOKOL_SPINE_API_DECL sspine_context_info sspine_get_context_info(sspine_context ctx); + +// create and destroy spine objects +SOKOL_SPINE_API_DECL sspine_atlas sspine_make_atlas(const sspine_atlas_desc* desc); +SOKOL_SPINE_API_DECL sspine_skeleton sspine_make_skeleton(const sspine_skeleton_desc* desc); +SOKOL_SPINE_API_DECL sspine_skinset sspine_make_skinset(const sspine_skinset_desc* desc); +SOKOL_SPINE_API_DECL sspine_instance sspine_make_instance(const sspine_instance_desc* desc); +SOKOL_SPINE_API_DECL void sspine_destroy_atlas(sspine_atlas atlas); +SOKOL_SPINE_API_DECL void sspine_destroy_skeleton(sspine_skeleton skeleton); +SOKOL_SPINE_API_DECL void sspine_destroy_skinset(sspine_skinset skinset); +SOKOL_SPINE_API_DECL void sspine_destroy_instance(sspine_instance instance); + +// configure instance appearance via skinsets +SOKOL_SPINE_API_DECL void sspine_set_skinset(sspine_instance instance, sspine_skinset skinset); + +// update instance animations before drawing +SOKOL_SPINE_API_DECL void sspine_update_instance(sspine_instance instance, float delta_time); + +// iterate over triggered events after updating an instance +SOKOL_SPINE_API_DECL int sspine_num_triggered_events(sspine_instance instance); +SOKOL_SPINE_API_DECL sspine_triggered_event_info sspine_get_triggered_event_info(sspine_instance instance, int triggered_event_index); + +// draw instance into current or explicit context +SOKOL_SPINE_API_DECL void sspine_draw_instance_in_layer(sspine_instance instance, int layer); +SOKOL_SPINE_API_DECL void sspine_context_draw_instance_in_layer(sspine_context ctx, sspine_instance instance, int layer); + +// helper function to convert sspine_layer_transform into projection matrix +SOKOL_SPINE_API_DECL sspine_mat4 sspine_layer_transform_to_mat4(const sspine_layer_transform* tform); + +// draw a layer in current context or explicit context (call once per context and frame in sokol-gfx pass) +SOKOL_SPINE_API_DECL void sspine_draw_layer(int layer, const sspine_layer_transform* tform); +SOKOL_SPINE_API_DECL void sspine_context_draw_layer(sspine_context ctx, int layer, const sspine_layer_transform* tform); + +// get current resource state (INITIAL, ALLOC, VALID, FAILED, INVALID) +SOKOL_SPINE_API_DECL sspine_resource_state sspine_get_context_resource_state(sspine_context context); +SOKOL_SPINE_API_DECL sspine_resource_state sspine_get_atlas_resource_state(sspine_atlas atlas); +SOKOL_SPINE_API_DECL sspine_resource_state sspine_get_skeleton_resource_state(sspine_skeleton skeleton); +SOKOL_SPINE_API_DECL sspine_resource_state sspine_get_skinset_resource_state(sspine_skinset skinset); +SOKOL_SPINE_API_DECL sspine_resource_state sspine_get_instance_resource_state(sspine_instance instance); + +// shortcut for sspine_get_*_state() == SSPINE_RESOURCESTATE_VALID +SOKOL_SPINE_API_DECL bool sspine_context_valid(sspine_context context); +SOKOL_SPINE_API_DECL bool sspine_atlas_valid(sspine_atlas atlas); +SOKOL_SPINE_API_DECL bool sspine_skeleton_valid(sspine_skeleton skeleton); +SOKOL_SPINE_API_DECL bool sspine_instance_valid(sspine_instance instance); +SOKOL_SPINE_API_DECL bool sspine_skinset_valid(sspine_skinset skinset); + +// get dependency objects +SOKOL_SPINE_API_DECL sspine_atlas sspine_get_skeleton_atlas(sspine_skeleton skeleton); +SOKOL_SPINE_API_DECL sspine_skeleton sspine_get_instance_skeleton(sspine_instance instance); + +// atlas images +SOKOL_SPINE_API_DECL int sspine_num_images(sspine_atlas atlas); +SOKOL_SPINE_API_DECL sspine_image sspine_image_by_index(sspine_atlas atlas, int index); +SOKOL_SPINE_API_DECL bool sspine_image_valid(sspine_image image); +SOKOL_SPINE_API_DECL bool sspine_image_equal(sspine_image first, sspine_image second); +SOKOL_SPINE_API_DECL sspine_image_info sspine_get_image_info(sspine_image image); + +// atlas page functions +SOKOL_SPINE_API_DECL int sspine_num_atlas_pages(sspine_atlas atlas); +SOKOL_SPINE_API_DECL sspine_atlas_page sspine_atlas_page_by_index(sspine_atlas atlas, int index); +SOKOL_SPINE_API_DECL bool sspine_atlas_page_valid(sspine_atlas_page page); +SOKOL_SPINE_API_DECL bool sspine_atlas_page_equal(sspine_atlas_page first, sspine_atlas_page second); +SOKOL_SPINE_API_DECL sspine_atlas_page_info sspine_get_atlas_page_info(sspine_atlas_page page); + +// instance transform functions +SOKOL_SPINE_API_DECL void sspine_set_position(sspine_instance instance, sspine_vec2 position); +SOKOL_SPINE_API_DECL void sspine_set_scale(sspine_instance instance, sspine_vec2 scale); +SOKOL_SPINE_API_DECL void sspine_set_color(sspine_instance instance, sspine_color color); +SOKOL_SPINE_API_DECL sspine_vec2 sspine_get_position(sspine_instance instance); +SOKOL_SPINE_API_DECL sspine_vec2 sspine_get_scale(sspine_instance instance); +SOKOL_SPINE_API_DECL sspine_color sspine_get_color(sspine_instance instance); + +// instance animation functions +SOKOL_SPINE_API_DECL int sspine_num_anims(sspine_skeleton skeleton); +SOKOL_SPINE_API_DECL sspine_anim sspine_anim_by_name(sspine_skeleton skeleton, const char* name); +SOKOL_SPINE_API_DECL sspine_anim sspine_anim_by_index(sspine_skeleton skeleton, int index); +SOKOL_SPINE_API_DECL bool sspine_anim_valid(sspine_anim anim); +SOKOL_SPINE_API_DECL bool sspine_anim_equal(sspine_anim first, sspine_anim second); +SOKOL_SPINE_API_DECL sspine_anim_info sspine_get_anim_info(sspine_anim anim); +SOKOL_SPINE_API_DECL void sspine_clear_animation_tracks(sspine_instance instance); +SOKOL_SPINE_API_DECL void sspine_clear_animation_track(sspine_instance instance, int track_index); +SOKOL_SPINE_API_DECL void sspine_set_animation(sspine_instance instance, sspine_anim anim, int track_index, bool loop); +SOKOL_SPINE_API_DECL void sspine_add_animation(sspine_instance instance, sspine_anim anim, int track_index, bool loop, float delay); +SOKOL_SPINE_API_DECL void sspine_set_empty_animation(sspine_instance instance, int track_index, float mix_duration); +SOKOL_SPINE_API_DECL void sspine_add_empty_animation(sspine_instance instance, int track_index, float mix_duration, float delay); + +// bone functions +SOKOL_SPINE_API_DECL int sspine_num_bones(sspine_skeleton skeleton); +SOKOL_SPINE_API_DECL sspine_bone sspine_bone_by_name(sspine_skeleton skeleton, const char* name); +SOKOL_SPINE_API_DECL sspine_bone sspine_bone_by_index(sspine_skeleton skeleton, int index); +SOKOL_SPINE_API_DECL bool sspine_bone_valid(sspine_bone bone); +SOKOL_SPINE_API_DECL bool sspine_bone_equal(sspine_bone first, sspine_bone second); +SOKOL_SPINE_API_DECL sspine_bone_info sspine_get_bone_info(sspine_bone bone); +SOKOL_SPINE_API_DECL void sspine_set_bone_transform(sspine_instance instance, sspine_bone bone, const sspine_bone_transform* transform); +SOKOL_SPINE_API_DECL void sspine_set_bone_position(sspine_instance instance, sspine_bone bone, sspine_vec2 position); +SOKOL_SPINE_API_DECL void sspine_set_bone_rotation(sspine_instance instance, sspine_bone bone, float rotation); +SOKOL_SPINE_API_DECL void sspine_set_bone_scale(sspine_instance instance, sspine_bone bone, sspine_vec2 scale); +SOKOL_SPINE_API_DECL void sspine_set_bone_shear(sspine_instance instance, sspine_bone bone, sspine_vec2 shear); +SOKOL_SPINE_API_DECL sspine_bone_transform sspine_get_bone_transform(sspine_instance instance, sspine_bone bone); +SOKOL_SPINE_API_DECL sspine_vec2 sspine_get_bone_position(sspine_instance instance, sspine_bone bone); +SOKOL_SPINE_API_DECL float sspine_get_bone_rotation(sspine_instance instance, sspine_bone bone); +SOKOL_SPINE_API_DECL sspine_vec2 sspine_get_bone_scale(sspine_instance instance, sspine_bone bone); +SOKOL_SPINE_API_DECL sspine_vec2 sspine_get_bone_shear(sspine_instance instance, sspine_bone bone); +SOKOL_SPINE_API_DECL sspine_vec2 sspine_get_bone_world_position(sspine_instance instance, sspine_bone bone); +SOKOL_SPINE_API_DECL sspine_vec2 sspine_bone_local_to_world(sspine_instance instance, sspine_bone bone, sspine_vec2 local_pos); +SOKOL_SPINE_API_DECL sspine_vec2 sspine_bone_world_to_local(sspine_instance instance, sspine_bone bone, sspine_vec2 world_pos); + +// slot functions +SOKOL_SPINE_API_DECL int sspine_num_slots(sspine_skeleton skeleton); +SOKOL_SPINE_API_DECL sspine_slot sspine_slot_by_name(sspine_skeleton skeleton, const char* name); +SOKOL_SPINE_API_DECL sspine_slot sspine_slot_by_index(sspine_skeleton skeleton, int index); +SOKOL_SPINE_API_DECL bool sspine_slot_valid(sspine_slot slot); +SOKOL_SPINE_API_DECL bool sspine_slot_equal(sspine_slot first, sspine_slot second); +SOKOL_SPINE_API_DECL sspine_slot_info sspine_get_slot_info(sspine_slot slot); +SOKOL_SPINE_API_DECL void sspine_set_slot_color(sspine_instance instance, sspine_slot slot, sspine_color color); +SOKOL_SPINE_API_DECL sspine_color sspine_get_slot_color(sspine_instance instance, sspine_slot slot); + +// event functions +SOKOL_SPINE_API_DECL int sspine_num_events(sspine_skeleton skeleton); +SOKOL_SPINE_API_DECL sspine_event sspine_event_by_name(sspine_skeleton skeleton, const char* name); +SOKOL_SPINE_API_DECL sspine_event sspine_event_by_index(sspine_skeleton skeleton, int index); +SOKOL_SPINE_API_DECL bool sspine_event_valid(sspine_event event); +SOKOL_SPINE_API_DECL bool sspine_event_equal(sspine_event first, sspine_event second); +SOKOL_SPINE_API_DECL sspine_event_info sspine_get_event_info(sspine_event event); + +// ik target functions +SOKOL_SPINE_API_DECL int sspine_num_iktargets(sspine_skeleton skeleton); +SOKOL_SPINE_API_DECL sspine_iktarget sspine_iktarget_by_name(sspine_skeleton skeleton, const char* name); +SOKOL_SPINE_API_DECL sspine_iktarget sspine_iktarget_by_index(sspine_skeleton skeleton, int index); +SOKOL_SPINE_API_DECL bool sspine_iktarget_valid(sspine_iktarget iktarget); +SOKOL_SPINE_API_DECL bool sspine_iktarget_equal(sspine_iktarget first, sspine_iktarget second); +SOKOL_SPINE_API_DECL sspine_iktarget_info sspine_get_iktarget_info(sspine_iktarget iktarget); +SOKOL_SPINE_API_DECL void sspine_set_iktarget_world_pos(sspine_instance instance, sspine_iktarget iktarget, sspine_vec2 world_pos); + +// skin functions +SOKOL_SPINE_API_DECL int sspine_num_skins(sspine_skeleton skeleton); +SOKOL_SPINE_API_DECL sspine_skin sspine_skin_by_name(sspine_skeleton skeleton, const char* name); +SOKOL_SPINE_API_DECL sspine_skin sspine_skin_by_index(sspine_skeleton skeleton, int index); +SOKOL_SPINE_API_DECL bool sspine_skin_valid(sspine_skin skin); +SOKOL_SPINE_API_DECL bool sspine_skin_equal(sspine_skin first, sspine_skin second); +SOKOL_SPINE_API_DECL sspine_skin_info sspine_get_skin_info(sspine_skin skin); +SOKOL_SPINE_API_DECL void sspine_set_skin(sspine_instance instance, sspine_skin skin); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ +// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ +// +// >>implementation +#ifdef SOKOL_SPINE_IMPL +#define SOKOL_SPINE_IMPL_INCLUDED (1) + +#if !defined(SPINE_SPINE_H_) +#error "Please include spine/spine.h before the sokol_spine.h implementation" +#endif + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_DEBUG + #ifndef NDEBUG + #define SOKOL_DEBUG (1) + #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 + +#include // malloc/free +#include // memset, strcmp + +// ███████╗██╗ ██╗ █████╗ ██████╗ ███████╗██████╗ ███████╗ +// ██╔════╝██║ ██║██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔════╝ +// ███████╗███████║███████║██║ ██║█████╗ ██████╔╝███████╗ +// ╚════██║██╔══██║██╔══██║██║ ██║██╔══╝ ██╔══██╗╚════██║ +// ███████║██║ ██║██║ ██║██████╔╝███████╗██║ ██║███████║ +// ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝╚══════╝ +// +// >>shaders +/* + Embedded source compiled with: + + sokol-shdc -i sspine.glsl -o sspine.h -l glsl330:glsl300es:hlsl4:metal_macos:metal_ios:metal_sim:wgsl -b + + @vs vs + uniform vs_params { + mat4 mvp; + }; + in vec2 position; + in vec2 texcoord0; + in vec4 color0; + out vec2 uv; + out vec4 color; + void main() { + gl_Position = mvp * vec4(position, 0.0, 1.0); + uv = texcoord0; + color = color0; + } + @end + + @fs fs + uniform texture2D tex; + uniform sampler smp; + uniform fs_params { + float pma; + }; + in vec2 uv; + in vec4 color; + out vec4 frag_color; + void main() { + vec4 c0 = texture(sampler2D(tex, smp), uv) * color; + vec4 c1 = vec4(c0.rgb * c0.a, c0.a) * color; + frag_color = mix(c0, c1, pma); + } + @end + + @program sspine vs fs +*/ +#if defined(SOKOL_GLCORE33) +static const char _sspine_vs_source_glsl330[352] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28, + 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e, + 0x20,0x76,0x65,0x63,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,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,0x76,0x65,0x63,0x34,0x28,0x70, + 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,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, + +}; +static const char _sspine_fs_source_glsl330[308] = { + 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,0x30,0x0a,0x0a,0x75,0x6e, + 0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x73,0x5f,0x70,0x61, + 0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x3b,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,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b, + 0x0a,0x69,0x6e,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,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72, + 0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20, + 0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,0x63, + 0x34,0x20,0x5f,0x32,0x38,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28, + 0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76,0x29,0x20,0x2a,0x20,0x63, + 0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20, + 0x5f,0x33,0x37,0x20,0x3d,0x20,0x5f,0x32,0x38,0x2e,0x77,0x3b,0x0a,0x20,0x20,0x20, + 0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x6d,0x69, + 0x78,0x28,0x5f,0x32,0x38,0x2c,0x20,0x76,0x65,0x63,0x34,0x28,0x5f,0x32,0x38,0x2e, + 0x78,0x79,0x7a,0x20,0x2a,0x20,0x5f,0x33,0x37,0x2c,0x20,0x5f,0x33,0x37,0x29,0x20, + 0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x2c,0x20,0x76,0x65,0x63,0x34,0x28,0x66,0x73, + 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,0x29,0x29,0x3b,0x0a, + 0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_GLES3) +static const char _sspine_vs_source_glsl300es[355] = { + 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,0x34,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f, + 0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29, + 0x20,0x69,0x6e,0x20,0x76,0x65,0x63,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,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,0x76,0x65,0x63, + 0x34,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,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, +}; +static const char _sspine_fs_source_glsl300es[399] = { + 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,0x76,0x65,0x63, + 0x34,0x20,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x3b,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,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,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,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d, + 0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x68,0x69,0x67,0x68, + 0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x5f,0x32,0x38,0x20,0x3d,0x20,0x74,0x65,0x78, + 0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x5f,0x73,0x6d,0x70,0x2c,0x20,0x75,0x76, + 0x29,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x68, + 0x69,0x67,0x68,0x70,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x33,0x37,0x20,0x3d, + 0x20,0x5f,0x32,0x38,0x2e,0x77,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67, + 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x5f,0x32,0x38, + 0x2c,0x20,0x76,0x65,0x63,0x34,0x28,0x5f,0x32,0x38,0x2e,0x78,0x79,0x7a,0x20,0x2a, + 0x20,0x5f,0x33,0x37,0x2c,0x20,0x5f,0x33,0x37,0x29,0x20,0x2a,0x20,0x63,0x6f,0x6c, + 0x6f,0x72,0x2c,0x20,0x76,0x65,0x63,0x34,0x28,0x66,0x73,0x5f,0x70,0x61,0x72,0x61, + 0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,0x29,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, +}; +#elif defined(SOKOL_D3D11) +static const uint8_t _sspine_vs_bytecode_hlsl4[844] = { + 0x44,0x58,0x42,0x43,0x2a,0xc9,0x57,0x52,0x4b,0x3d,0x3e,0x89,0x00,0xf4,0xfa,0x41, + 0xfd,0xd7,0x63,0xc3,0x01,0x00,0x00,0x00,0x4c,0x03,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0xf4,0x00,0x00,0x00,0x58,0x01,0x00,0x00,0xc8,0x01,0x00,0x00, + 0xd0,0x02,0x00,0x00,0x52,0x44,0x45,0x46,0xb8,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,0x90,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,0x01,0x00,0x00,0x00,0x60,0x00,0x00,0x00, + 0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x80,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,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,0x00,0x01,0x00,0x00, + 0x40,0x00,0x01,0x00,0x40,0x00,0x00,0x00,0x59,0x00,0x00,0x04,0x46,0x8e,0x20,0x00, + 0x00,0x00,0x00,0x00,0x04,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,0x68,0x00,0x00,0x02,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,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,0x00,0x00,0x00,0x08, + 0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, + 0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x3e,0x00,0x00,0x01, + 0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x03,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,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sspine_fs_bytecode_hlsl4[868] = { + 0x44,0x58,0x42,0x43,0xfb,0x9d,0xdb,0x19,0x85,0xbc,0x50,0x5d,0x6b,0x03,0xd4,0xc8, + 0x1b,0xea,0x59,0xf5,0x01,0x00,0x00,0x00,0x64,0x03,0x00,0x00,0x05,0x00,0x00,0x00, + 0x34,0x00,0x00,0x00,0x3c,0x01,0x00,0x00,0x88,0x01,0x00,0x00,0xbc,0x01,0x00,0x00, + 0xe8,0x02,0x00,0x00,0x52,0x44,0x45,0x46,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x00, + 0x90,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xff,0xff, + 0x10,0x81,0x00,0x00,0xd8,0x00,0x00,0x00,0x7c,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,0x80,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,0x84,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,0x73,0x6d,0x70,0x00,0x74,0x65,0x78,0x00, + 0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x00,0xab,0xab,0x84,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0xa8,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00, + 0x02,0x00,0x00,0x00,0xc8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5f,0x35,0x33,0x5f, + 0x70,0x6d,0x61,0x00,0x00,0x00,0x03,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,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, + 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, + 0x24,0x01,0x00,0x00,0x40,0x00,0x00,0x00,0x49,0x00,0x00,0x00,0x59,0x00,0x00,0x04, + 0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x01,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, + 0x02,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,0x00,0x10,0x00, + 0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x1e,0x10,0x00, + 0x01,0x00,0x00,0x00,0x38,0x00,0x00,0x07,0x72,0x00,0x10,0x00,0x01,0x00,0x00,0x00, + 0xf6,0x0f,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x02,0x10,0x00,0x00,0x00,0x00,0x00, + 0x36,0x00,0x00,0x05,0x82,0x00,0x10,0x00,0x01,0x00,0x00,0x00,0x3a,0x00,0x10,0x00, + 0x00,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x01,0x00,0x00,0x00, + 0x46,0x0e,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x1e,0x10,0x00,0x01,0x00,0x00,0x00, + 0x46,0x0e,0x10,0x80,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x32,0x00,0x00,0x0a, + 0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x06,0x80,0x20,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x0e,0x10,0x00, + 0x00,0x00,0x00,0x00,0x3e,0x00,0x00,0x01,0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00, + 0x07,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, + 0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, +}; +#elif defined(SOKOL_METAL) +static const uint8_t _sspine_vs_bytecode_metal_macos[3084] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,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, + 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,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x1d,0x26,0xf7,0xce,0x5d,0x78,0x21, + 0x2f,0xa7,0x97,0xe1,0xbd,0x15,0x30,0xfb,0xa6,0x98,0xcc,0x1d,0xba,0x1d,0x0d,0x92, + 0xaa,0x4e,0x3d,0x75,0xee,0x73,0x36,0x7c,0x99,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,0xe0,0x0a,0x00,0x00,0xff,0xff,0xff,0xff, + 0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0xb5,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,0x81,0x00,0x00,0x00,0x1b,0xc8,0x25,0xf8,0xff,0xff,0xff,0xff, + 0x01,0x90,0x80,0x8a,0x18,0x87,0x77,0x90,0x07,0x79,0x28,0x87,0x71,0xa0,0x07,0x76, + 0xc8,0x87,0x36,0x90,0x87,0x77,0xa8,0x07,0x77,0x20,0x87,0x72,0x20,0x87,0x36,0x20, + 0x87,0x74,0xb0,0x87,0x74,0x20,0x87,0x72,0x68,0x83,0x79,0x88,0x07,0x79,0xa0,0x87, + 0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0xc0,0x1c,0xc2,0x81, + 0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c,0xd2,0x61,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c, + 0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8,0xa1,0x0d,0xc6,0x21,0x1c,0xd8,0x81,0x1d,0xe6, + 0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07,0x80,0x60,0x87,0x72,0x98,0x87,0x79, + 0x68,0x03,0x78,0x90,0x87,0x72,0x18,0x87,0x74,0x98,0x87,0x72,0x68,0x03,0x73,0x80, + 0x87,0x76,0x08,0x07,0x72,0x00,0xcc,0x21,0x1c,0xd8,0x61,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,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x06,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,0x68,0x03,0x77,0x78,0x07,0x77,0x68,0x03,0x76,0x28, + 0x87,0x70,0x30,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,0x40,0x1d,0xea,0xa1,0x1d,0xe0,0xa1,0x0d, + 0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87, + 0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x70,0x87,0x70,0x70,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,0xe6,0x21,0x1d,0xce,0xc1,0x1d,0xca,0x81,0x1c,0xda, + 0x40,0x1f,0xca,0x41,0x1e,0xde,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, + 0x03,0x7a,0x90,0x87,0x70,0x80,0x07,0x78,0x48,0x07,0x77,0x38,0x87,0x36,0x68,0x87, + 0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0x62,0x1e,0xe8,0x21, + 0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e,0xe4,0xe1,0x1d,0xe8,0xa1,0x1c,0xc6,0x81,0x1e, + 0xde,0x41,0x1e,0xda,0x40,0x1c,0xea,0xc1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x0d,0xe6, + 0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c,0x00,0x88,0x7a,0x70,0x87,0x79,0x08,0x07,0x73, + 0x28,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e, + 0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea,0x61,0x1e,0xca,0xa1,0x0d,0xe6,0xe1,0x1d,0xcc, + 0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1,0x1d,0xc2,0x81,0x1e,0x00,0x73,0x08,0x07,0x76, + 0x98,0x87,0x72,0x00,0x36,0x18,0x02,0x01,0x2c,0x40,0x05,0x00,0x49,0x18,0x00,0x00, + 0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00,0x00,0x1f,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,0x02,0x30,0x8c,0x40,0x00,0x76,0x08,0x42,0x24,0x81,0x98,0x89,0x9a,0x07, + 0x7a,0x90,0x87,0x7a,0x18,0x07,0x7a,0x70,0x83,0x76,0x28,0x07,0x7a,0x08,0x07,0x76, + 0xd0,0x03,0x3d,0x68,0x87,0x70,0xa0,0x07,0x79,0x48,0x07,0x7c,0x40,0x01,0x39,0x48, + 0x9a,0x22,0x4a,0x98,0xfc,0x4a,0xfa,0x1f,0x20,0x02,0x18,0x09,0x05,0x65,0x10,0xc1, + 0x10,0x4a,0x31,0x42,0x10,0x87,0xd0,0x40,0xc0,0x1c,0x01,0x18,0xa4,0xc0,0x9a,0x23, + 0x00,0x85,0x41,0x04,0x41,0x18,0x46,0x20,0x96,0x11,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,0x0a,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,0x05,0x51,0x06,0x05,0x42,0x6d,0x04,0x80,0xd8,0x58,0x42, + 0x13,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xeb,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10, + 0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x32,0x28, + 0x00,0xa3,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c, + 0x81,0x22,0x2c,0x05,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c, + 0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xc5, + 0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06, + 0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45,0x26,0x65,0x88,0xa0,0x10,0x43,0x8c,0x25,0x58, + 0x8c,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,0x64,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,0xa1,0x61,0xc6,0xf6,0x16,0x46,0x47, + 0xc3,0x62,0xec,0x8d,0xed,0x4d,0x6e,0x08,0xa3,0x3c,0x8a,0xa5,0x44,0xca,0xa5,0x4c, + 0x0a,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,0x45,0xd1,0x94,0x48, + 0xb9,0x94,0x49,0xd9,0x86,0x18,0x4a,0xa5,0x64,0x0a,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,0x45,0x50,0x3c,0xe5,0x5b,0x84,0x25, + 0x50,0xc0,0x40,0x89,0x14,0x49,0x99,0x94,0x30,0x60,0x42,0x57,0x86,0x37,0xf6,0xf6, + 0x26,0x47,0x06,0x33,0x84,0x5a,0x02,0xc5,0x53,0xbe,0x25,0x58,0x02,0x05,0x0c,0x94, + 0x48,0x91,0x94,0x49,0x19,0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x43,0xa8,0x65, + 0x50,0x3c,0xe5,0x5b,0x86,0x25,0x50,0xc0,0x40,0x89,0x94,0x4b,0x99,0x94,0x32,0xa0, + 0x12,0x96,0x26,0xe7,0x22,0x56,0x67,0x66,0x56,0x26,0xc7,0x27,0x2c,0x4d,0xce,0x45, + 0xac,0xce,0xcc,0xac,0x4c,0xee,0x6b,0x2e,0x4d,0xaf,0x8c,0x48,0x58,0x9a,0x9c,0x8b, + 0x5c,0x59,0x18,0x19,0xa9,0xb0,0x34,0x39,0x97,0x39,0x3a,0xb9,0xba,0x31,0xba,0x2f, + 0xba,0x3c,0xb8,0xb2,0xaf,0x34,0x37,0xb3,0x37,0x22,0x66,0x6c,0x6f,0x61,0x74,0x34, + 0x78,0x34,0x1c,0xda,0xec,0xe0,0x86,0x28,0x8b,0xb0,0x10,0x8b,0xa0,0xac,0x81,0xc2, + 0x06,0x8c,0xc2,0xd2,0xe4,0x5c,0xc2,0xe4,0xce,0xbe,0xe8,0xf2,0xe0,0xca,0xbe,0xe6, + 0xd2,0xf4,0xca,0x78,0x85,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xd1,0xe5,0xc1,0x95, + 0x7d,0x85,0xb1,0xa5,0x9d,0xb9,0x7d,0xcd,0xa5,0xe9,0x95,0x31,0xb1,0x9b,0xfb,0x82, + 0x0b,0x93,0x0b,0x6b,0x9b,0xe3,0xf0,0x15,0x93,0x33,0x84,0x0c,0x96,0x43,0x39,0x03, + 0x05,0x0d,0x16,0x42,0xf9,0x16,0x61,0x09,0x94,0x34,0x50,0xd4,0x40,0x69,0x03,0xc5, + 0x0d,0x16,0x42,0x79,0x83,0x05,0x51,0x22,0x05,0x0e,0x94,0x49,0x89,0x83,0x21,0x88, + 0x22,0x06,0x0a,0x19,0x28,0x66,0xa0,0xc8,0xc1,0x10,0x23,0x01,0x94,0x4e,0x99,0x03, + 0x3e,0x6f,0x6d,0x6e,0x69,0x70,0x6f,0x74,0x65,0x6e,0x74,0x20,0x63,0x68,0x61,0x72, + 0x7c,0xa6,0xd2,0xda,0xe0,0xd8,0xca,0x40,0x86,0x56,0x56,0x40,0xa8,0x84,0x82,0x82, + 0x86,0x08,0x8a,0x1d,0x0c,0x31,0x94,0x3a,0x50,0xee,0xa0,0x49,0x86,0x18,0x0a,0x1e, + 0x28,0x78,0xd0,0x24,0x23,0x22,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x77, + 0x20,0x87,0x7a,0x60,0x87,0x72,0x70,0x03,0x73,0x60,0x87,0x70,0x38,0x87,0x79,0x98, + 0x22,0x04,0xc3,0x08,0x85,0x1d,0xd8,0xc1,0x1e,0xda,0xc1,0x0d,0xd2,0x81,0x1c,0xca, + 0xc1,0x1d,0xe8,0x61,0x4a,0x50,0x8c,0x58,0xc2,0x21,0x1d,0xe4,0xc1,0x0d,0xec,0xa1, + 0x1c,0xe4,0x61,0x1e,0xd2,0xe1,0x1d,0xdc,0x61,0x4a,0x60,0x8c,0xa0,0xc2,0x21,0x1d, + 0xe4,0xc1,0x0d,0xd8,0x21,0x1c,0xdc,0xe1,0x1c,0xea,0x21,0x1c,0xce,0xa1,0x1c,0x7e, + 0xc1,0x1e,0xca,0x41,0x1e,0xe6,0x21,0x1d,0xde,0xc1,0x1d,0xa6,0x04,0xc8,0x88,0x29, + 0x1c,0xd2,0x41,0x1e,0xdc,0x60,0x1c,0xde,0xa1,0x1d,0xe0,0x21,0x1d,0xd8,0xa1,0x1c, + 0x7e,0xe1,0x1d,0xe0,0x81,0x1e,0xd2,0xe1,0x1d,0xdc,0x61,0x1e,0xa6,0x0c,0x0a,0xe3, + 0x8c,0x50,0xc2,0x21,0x1d,0xe4,0xc1,0x0d,0xec,0xa1,0x1c,0xe4,0x81,0x1e,0xca,0x01, + 0x1f,0xa6,0x04,0x74,0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38, + 0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8, + 0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,0x00,0x71,0x20,0x00,0x00, + 0x02,0x00,0x00,0x00,0x06,0x50,0x30,0x00,0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00, + 0x1e,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x05,0x00,0x00,0x00, + 0xf4,0xc6,0x22,0x82,0x20,0x08,0x46,0x00,0xa8,0x95,0x40,0x19,0xd0,0x98,0x01,0xa0, + 0x30,0x03,0x00,0x00,0xe3,0x15,0x07,0x33,0x4d,0x0c,0x05,0x65,0x90,0x81,0x19,0x0e, + 0x13,0x02,0xf9,0x8c,0x57,0x2c,0xd0,0x75,0x21,0x14,0x94,0x41,0x06,0xe8,0x60,0x4c, + 0x08,0xe4,0x63,0x41,0x01,0x9f,0xf1,0x0a,0xa8,0xe2,0x38,0x86,0x82,0x62,0x43,0x00, + 0x9f,0xd9,0x06,0xa7,0x02,0x66,0x1b,0x82,0x2a,0x98,0x6d,0x08,0x06,0x21,0x83,0x80, + 0x18,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x5b,0x86,0x20,0xc8,0x83,0x2d,0x43,0x11, + 0xe4,0xc1,0x96,0x41,0x09,0xf2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sspine_fs_bytecode_metal_macos[3257] = { + 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xb9,0x0c,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, + 0xe0,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,0x47,0xc7,0x1e,0x49,0x53,0x1c,0xe5, + 0x90,0x01,0xa5,0x57,0x9a,0x30,0x01,0x5c,0x39,0x85,0x9f,0xb2,0x71,0x51,0xe1,0x73, + 0x0c,0xa4,0x4d,0xb1,0x81,0x33,0x8d,0x17,0x1e,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,0xc4,0x0b,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0xee,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,0x8e,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07,0x7a,0x60,0x87,0x7c, + 0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72,0x68,0x03,0x72,0x48, + 0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90,0x07,0x7a,0x68,0x03, + 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc,0x21,0x1c,0xd8,0x61, + 0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81,0x1d,0xca,0xa1,0x0d, + 0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d,0xd8,0x61,0x1e,0x00, + 0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87,0x79,0x98,0x87,0x36, + 0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,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,0x00,0x7a,0x90, + 0x87,0x7a,0x28,0x07,0x60,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,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36,0x60,0x87,0x72,0x08, + 0x07,0x73,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,0xd4,0xa1,0x1e,0xda,0x01,0x1e,0xda,0x80,0x1e, + 0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,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,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c,0xc8,0xa1,0x0d,0xf4, + 0xa1,0x1c,0xe4,0xe1,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,0xa0, + 0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68,0x83,0x76,0x08,0x07, + 0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6,0x81,0x1e,0xc2,0x61, + 0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d, + 0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e,0xda,0x60,0x1e,0xd2, + 0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87,0x70,0x30,0x87,0x72, + 0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e, + 0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e,0xde,0xc1,0x1c,0xe8, + 0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 0x28,0x07,0x60,0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81,0x00,0x16,0xa0,0xda, + 0x60,0x10,0x05,0xb0,0x00,0xd5,0x06,0xa3,0xf8,0xff,0xff,0xff,0xff,0x01,0x90,0x00, + 0x6a,0x03,0x62,0xfc,0xff,0xff,0xff,0xff,0x00,0x30,0x80,0x04,0x54,0x1b,0x8c,0x23, + 0x00,0x16,0xa0,0xda,0x60,0x20,0x02,0xb0,0x00,0x15,0x00,0x00,0x00,0x49,0x18,0x00, + 0x00,0x03,0x00,0x00,0x00,0x13,0x88,0x40,0x18,0x88,0x09,0x41,0x31,0x61,0x30,0x0e, + 0x04,0x89,0x20,0x00,0x00,0x27,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,0x6c,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x30,0x8c, + 0x20,0x00,0x07,0x49,0x53,0x44,0x09,0x93,0x5f,0x48,0xff,0x03,0x44,0x00,0x23,0xa1, + 0x00,0x0c,0x22,0x10,0xc2,0x51,0xd2,0x14,0x51,0xc2,0xe4,0xff,0x13,0x71,0x4d,0x54, + 0x44,0xfc,0xf6,0xf0,0x4f,0x63,0x04,0xc0,0x20,0x82,0x11,0x5c,0x24,0x4d,0x11,0x25, + 0x4c,0xfe,0x2f,0x01,0xcc,0xb3,0x10,0xd1,0x3f,0x8d,0x11,0x00,0x83,0x08,0x88,0x50, + 0x0c,0x31,0x42,0x39,0x89,0x54,0x21,0x42,0x08,0x81,0xd8,0x1c,0x41,0x30,0x47,0x00, + 0x06,0xc3,0x08,0xc2,0x53,0x90,0x70,0xd2,0x70,0xd0,0x01,0x8a,0x03,0x01,0x29,0xf0, + 0x86,0x11,0x86,0x67,0x18,0x61,0x00,0x86,0x11,0x88,0x67,0x8e,0x00,0x14,0x06,0x11, + 0x00,0x61,0x04,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,0x59,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x18,0xc2,0x34,0x40, + 0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x0c,0x61,0x24,0x20,0x00,0x06,0x00,0x00,0x00, + 0x00,0x00,0xb2,0x40,0x00,0x09,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c, + 0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x7a,0x23,0x00,0x25,0x50,0x08,0x45,0x50, + 0x10,0x65,0x40,0x78,0x04,0x80,0xe8,0x58,0x42,0x13,0x00,0x00,0x00,0x79,0x18,0x00, + 0x00,0xf9,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32, + 0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x63,0x4c,0x00,0xa5,0x50,0xb9,0x1b,0x43,0x0b, + 0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x3c,0xc4,0x24,0x3c,0x05,0xe7,0x20,0x08, + 0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed, + 0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c, + 0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45, + 0x26,0x65,0x88,0x30,0x11,0x43,0x8c,0x87,0x78,0x8e,0x67,0x60,0xd1,0x54,0x46,0x17, + 0xc6,0x36,0x04,0x99,0x8e,0x87,0x78,0x88,0x67,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,0x98,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,0x69,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,0xa6,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,0xa6,0xe7,0x19,0x26,0x68,0x8a,0x26, + 0x69,0x9a,0x86,0x08,0x13,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,0x9e,0x61,0xb2,0xa6, + 0x6b,0xc2,0xa6,0x6c,0x82,0x26,0x6d,0x92,0xa6,0x8d,0x4b,0xdd,0x5c,0x99,0x1c,0x0a, + 0xdb,0xdb,0x98,0x5b,0x4c,0x0a,0x8b,0xb1,0x37,0xb6,0x37,0xb9,0x21,0xd2,0x43,0x4c, + 0xd6,0xd4,0x4d,0xd8,0x94,0x4d,0xd0,0x14,0x4d,0xd2,0xe4,0x51,0x09,0x4b,0x93,0x73, + 0x11,0xab,0x33,0x33,0x2b,0x93,0xe3,0x13,0x96,0x26,0xe7,0x22,0x56,0x67,0x66,0x56, + 0x26,0xf7,0x35,0x97,0xa6,0x57,0x46,0x29,0x2c,0x4d,0xce,0x85,0xed,0x6d,0x2c,0x8c, + 0x2e,0xed,0xcd,0xed,0x2b,0xcd,0x8d,0xac,0x0c,0x8f,0x48,0x58,0x9a,0x9c,0x8b,0x5c, + 0x59,0x18,0x19,0xa9,0xb0,0x34,0x39,0x97,0x39,0x3a,0xb9,0xba,0x31,0xba,0x2f,0xba, + 0x3c,0xb8,0xb2,0xaf,0x34,0x37,0xb3,0x37,0x16,0x66,0x6c,0x6f,0x61,0x74,0x1c,0xe0, + 0xda,0xc2,0x86,0x28,0xcf,0xf0,0x14,0xcf,0x30,0x95,0xc1,0x64,0x06,0x8c,0xc2,0xd2, + 0xe4,0x5c,0xc2,0xe4,0xce,0xbe,0xe8,0xf2,0xe0,0xca,0xbe,0xe6,0xd2,0xf4,0xca,0x78, + 0x85,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xd1,0xe5,0xc1,0x95,0x7d,0x85,0xb1,0xa5, + 0x9d,0xb9,0x7d,0xcd,0xa5,0xe9,0x95,0x31,0x31,0x9b,0xfb,0x82,0x0b,0x93,0x0b,0x6b, + 0x9b,0xe3,0xf0,0x55,0x33,0x33,0x84,0x0c,0x1e,0x63,0x02,0x83,0x29,0x0c,0x9e,0x62, + 0x12,0x83,0x67,0x78,0x88,0x69,0x0c,0x26,0x32,0x98,0xce,0x60,0x42,0x83,0xa7,0x98, + 0xd2,0xe0,0x29,0x26,0x68,0x52,0x83,0x49,0x9a,0xd6,0x80,0x4b,0x58,0x9a,0x9c,0x0b, + 0x5d,0x19,0x1e,0x5d,0x9d,0x5c,0x19,0x95,0xb0,0x34,0x39,0x97,0xb9,0xb0,0x36,0x38, + 0xb6,0x32,0x62,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f, + 0x61,0x74,0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x3e,0x1c,0xe8,0xca,0xf0,0x86, + 0x50,0x0f,0x32,0xb5,0xc1,0x24,0x06,0xcf,0xf0,0x10,0x93,0x1b,0x4c,0xd0,0xf4,0x06, + 0x93,0x34,0xc1,0x01,0x97,0xb0,0x34,0x39,0x97,0xb9,0xb0,0x36,0x38,0xb6,0x32,0x39, + 0x1e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x1c,0xe6,0xda,0xe0,0x86,0x48,0x4f,0x31, + 0xc9,0xc1,0x24,0x06,0xcf,0xf0,0x10,0x13,0x34,0xcd,0xc1,0x24,0x4d,0x74,0x30,0x44, + 0x99,0xb8,0xe9,0x9b,0xd8,0x60,0x8a,0x83,0xa9,0x0e,0x86,0x18,0x0b,0x30,0x55,0x93, + 0x1d,0xd0,0xf9,0xd2,0xa2,0x9a,0xca,0x31,0x9b,0xfb,0x82,0x0b,0x93,0x0b,0x6b,0x9b, + 0xe3,0xf3,0xd6,0xe6,0x96,0x06,0xf7,0x46,0x57,0xe6,0x46,0x07,0x32,0x86,0x16,0x26, + 0xc7,0x67,0x2a,0xad,0x0d,0x8e,0xad,0x0c,0x64,0x68,0x65,0x05,0x84,0x4a,0x28,0x28, + 0x68,0x88,0x30,0xe9,0xc1,0x10,0x63,0xca,0x83,0x69,0x0f,0xb0,0x64,0x88,0x31,0x95, + 0xc1,0xc4,0x07,0x58,0x32,0xc4,0x98,0xf0,0x60,0xea,0x03,0x2c,0x19,0x62,0x4c,0x7e, + 0x30,0xf5,0x01,0x96,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,0xdc,0x01,0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59, + 0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b, + 0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,0x00,0x71,0x20,0x00, + 0x00,0x0b,0x00,0x00,0x00,0x26,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,0x6d,0x01,0x0d,0x80,0x44,0x3e,0x83,0x5c,0x7e,0x85,0x17,0xb7,0x0d,0x00,0x00, + 0x00,0x61,0x20,0x00,0x00,0x25,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00, + 0x00,0x0c,0x00,0x00,0x00,0x74,0x47,0x00,0xc6,0x22,0x80,0x40,0x38,0xe6,0x20,0x06, + 0xc2,0xa8,0xc8,0xd5,0xc0,0x08,0x00,0xbd,0x19,0x00,0x82,0x23,0x00,0x54,0xc7,0x1a, + 0x80,0x40,0x18,0x6b,0x18,0x86,0x81,0xec,0x0c,0x00,0x89,0x19,0x00,0x0a,0x33,0x00, + 0x04,0x46,0x00,0x00,0x00,0x23,0x06,0xca,0x10,0x6c,0x8f,0x23,0x29,0x47,0x12,0x58, + 0x20,0xc9,0x67,0x90,0x21,0x20,0x90,0x41,0x06,0xa1,0x40,0x4c,0x08,0xe4,0x33,0xc8, + 0x10,0x24,0xd0,0x20,0x43,0x50,0x48,0x16,0x60,0xf2,0x19,0x6f,0xc0,0x38,0x31,0xa0, + 0x60,0xcc,0x31,0x30,0x01,0x19,0x0c,0x32,0x04,0x0d,0x36,0x62,0x60,0x08,0x01,0x1a, + 0x2c,0x45,0x30,0xdb,0x00,0x05,0x40,0x06,0x01,0x31,0x00,0x00,0x00,0x02,0x00,0x00, + 0x00,0x5b,0x86,0x24,0xf8,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sspine_vs_bytecode_metal_ios[3068] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,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, + 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,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x93,0xd6,0x91,0x34,0xa6,0x5a,0xef, + 0xf4,0xa9,0x2b,0xc7,0x55,0x75,0x4a,0x7f,0xc5,0x46,0xc0,0x95,0x92,0x61,0x00,0x3e, + 0x6d,0x53,0x68,0xee,0xb6,0x8e,0xc8,0x26,0x6c,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,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,0x82,0x00,0x00,0x00,0x1b,0xc8,0x25,0xf8,0xff,0xff,0xff,0xff, + 0x01,0x90,0x80,0x8a,0x18,0x87,0x77,0x90,0x07,0x79,0x28,0x87,0x71,0xa0,0x07,0x76, + 0xc8,0x87,0x36,0x90,0x87,0x77,0xa8,0x07,0x77,0x20,0x87,0x72,0x20,0x87,0x36,0x20, + 0x87,0x74,0xb0,0x87,0x74,0x20,0x87,0x72,0x68,0x83,0x79,0x88,0x07,0x79,0xa0,0x87, + 0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0xc0,0x1c,0xc2,0x81, + 0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c,0xd2,0x61,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c, + 0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8,0xa1,0x0d,0xc6,0x21,0x1c,0xd8,0x81,0x1d,0xe6, + 0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07,0x80,0x60,0x87,0x72,0x98,0x87,0x79, + 0x68,0x03,0x78,0x90,0x87,0x72,0x18,0x87,0x74,0x98,0x87,0x72,0x68,0x03,0x73,0x80, + 0x87,0x76,0x08,0x07,0x72,0x00,0xcc,0x21,0x1c,0xd8,0x61,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,0x01,0xa0, + 0x07,0x79,0xa8,0x87,0x72,0x00,0x06,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,0x68,0x03,0x77,0x78,0x07,0x77,0x68,0x03,0x76,0x28, + 0x87,0x70,0x30,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,0x40,0x1d,0xea,0xa1,0x1d,0xe0,0xa1,0x0d, + 0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87, + 0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x70,0x87,0x70,0x70,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,0xe6,0x21,0x1d,0xce,0xc1,0x1d,0xca,0x81,0x1c,0xda, + 0x40,0x1f,0xca,0x41,0x1e,0xde,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, + 0x03,0x7a,0x90,0x87,0x70,0x80,0x07,0x78,0x48,0x07,0x77,0x38,0x87,0x36,0x68,0x87, + 0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0x62,0x1e,0xe8,0x21, + 0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e,0xe4,0xe1,0x1d,0xe8,0xa1,0x1c,0xc6,0x81,0x1e, + 0xde,0x41,0x1e,0xda,0x40,0x1c,0xea,0xc1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x0d,0xe6, + 0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c,0x00,0x88,0x7a,0x70,0x87,0x79,0x08,0x07,0x73, + 0x28,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e, + 0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea,0x61,0x1e,0xca,0xa1,0x0d,0xe6,0xe1,0x1d,0xcc, + 0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1,0x1d,0xc2,0x81,0x1e,0x00,0x73,0x08,0x07,0x76, + 0x98,0x87,0x72,0x00,0x36,0x20,0x02,0x01,0x24,0xc0,0x02,0x54,0x00,0x00,0x00,0x00, + 0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00,0x00, + 0x1f,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,0x02,0x30,0x8c,0x40,0x00,0x76,0x08,0x42,0x24,0x81, + 0x98,0x89,0x9a,0x07,0x7a,0x90,0x87,0x7a,0x18,0x07,0x7a,0x70,0x83,0x76,0x28,0x07, + 0x7a,0x08,0x07,0x76,0xd0,0x03,0x3d,0x68,0x87,0x70,0xa0,0x07,0x79,0x48,0x07,0x7c, + 0x40,0x01,0x39,0x48,0x9a,0x22,0x4a,0x98,0xfc,0x4a,0xfa,0x1f,0x20,0x02,0x18,0x09, + 0x05,0x65,0x10,0xc1,0x10,0x4a,0x31,0x42,0x10,0x87,0xd0,0x40,0xc0,0x1c,0x01,0x18, + 0xa4,0xc0,0x9a,0x23,0x00,0x85,0x41,0x04,0x41,0x18,0x46,0x20,0x96,0x11,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,0x0a,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,0x05,0x51,0x06,0x05,0x42,0x6d,0x04,0x80,0xd8,0x58,0x02,0x24,0x00,0x00,0x00, + 0x79,0x18,0x00,0x00,0xea,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25, + 0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x32,0x28,0x00,0xa3,0x50,0xb9, + 0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0x81,0x22,0x2c,0x05, + 0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c, + 0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6,0xe6,0x06,0x04, + 0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5,0xc6, + 0xe6,0xc6,0x45,0x26,0x65,0x88,0xa0,0x10,0x43,0x8c,0x25,0x58,0x8c,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,0x64,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,0xa1,0x61,0xc6,0xf6,0x16,0x46,0x47,0xc3,0x62,0xec,0x8d,0xed, + 0x4d,0x6e,0x08,0xa3,0x3c,0x8a,0xa5,0x44,0xca,0xa5,0x4c,0x0a,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,0x45,0xd1,0x94,0x48,0xb9,0x94,0x49,0xd9,0x86, + 0x18,0x4a,0xa5,0x64,0x0a,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,0x45,0x50,0x3c,0xe5,0x5b,0x84,0x25,0x50,0xc0,0x40,0x89,0x14, + 0x49,0x99,0x94,0x30,0x60,0x42,0x57,0x86,0x37,0xf6,0xf6,0x26,0x47,0x06,0x33,0x84, + 0x5a,0x02,0xc5,0x53,0xbe,0x25,0x58,0x02,0x05,0x0c,0x94,0x48,0x91,0x94,0x49,0x19, + 0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x43,0xa8,0x65,0x50,0x3c,0xe5,0x5b,0x86, + 0x25,0x50,0xc0,0x40,0x89,0x94,0x4b,0x99,0x94,0x32,0xa0,0x12,0x96,0x26,0xe7,0x22, + 0x56,0x67,0x66,0x56,0x26,0xc7,0x27,0x2c,0x4d,0xce,0x45,0xac,0xce,0xcc,0xac,0x4c, + 0xee,0x6b,0x2e,0x4d,0xaf,0x8c,0x48,0x58,0x9a,0x9c,0x8b,0x5c,0x59,0x18,0x19,0xa9, + 0xb0,0x34,0x39,0x97,0x39,0x3a,0xb9,0xba,0x31,0xba,0x2f,0xba,0x3c,0xb8,0xb2,0xaf, + 0x34,0x37,0xb3,0x37,0x22,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x1c,0xda,0xec, + 0xe0,0x86,0x28,0x8b,0xb0,0x10,0x8b,0xa0,0xac,0x81,0xc2,0x06,0x8c,0xc2,0xd2,0xe4, + 0x5c,0xc2,0xe4,0xce,0xbe,0xe8,0xf2,0xe0,0xca,0xbe,0xe6,0xd2,0xf4,0xca,0x78,0x85, + 0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xd1,0xe5,0xc1,0x95,0x7d,0x85,0xb1,0xa5,0x9d, + 0xb9,0x7d,0xcd,0xa5,0xe9,0x95,0x31,0xb1,0x9b,0xfb,0x82,0x0b,0x93,0x0b,0x6b,0x9b, + 0xe3,0xf0,0x15,0x93,0x33,0x84,0x0c,0x96,0x43,0x39,0x03,0x05,0x0d,0x16,0x42,0xf9, + 0x16,0x61,0x09,0x94,0x34,0x50,0xd4,0x40,0x69,0x03,0xc5,0x0d,0x16,0x42,0x79,0x83, + 0x05,0x51,0x22,0x05,0x0e,0x94,0x49,0x89,0x83,0x21,0x88,0x22,0x06,0x0a,0x19,0x28, + 0x66,0xa0,0xc8,0xc1,0x10,0x23,0x01,0x94,0x4e,0x99,0x03,0x3e,0x6f,0x6d,0x6e,0x69, + 0x70,0x6f,0x74,0x65,0x6e,0x74,0x20,0x63,0x68,0x61,0x72,0x7c,0xa6,0xd2,0xda,0xe0, + 0xd8,0xca,0x40,0x86,0x56,0x56,0x40,0xa8,0x84,0x82,0x82,0x86,0x08,0x8a,0x1d,0x0c, + 0x31,0x94,0x3a,0x50,0xee,0xa0,0x49,0x86,0x18,0x0a,0x1e,0x28,0x78,0xd0,0x24,0x23, + 0x22,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x77,0x20,0x87,0x7a,0x60,0x87, + 0x72,0x70,0x03,0x73,0x60,0x87,0x70,0x38,0x87,0x79,0x98,0x22,0x04,0xc3,0x08,0x85, + 0x1d,0xd8,0xc1,0x1e,0xda,0xc1,0x0d,0xd2,0x81,0x1c,0xca,0xc1,0x1d,0xe8,0x61,0x4a, + 0x50,0x8c,0x58,0xc2,0x21,0x1d,0xe4,0xc1,0x0d,0xec,0xa1,0x1c,0xe4,0x61,0x1e,0xd2, + 0xe1,0x1d,0xdc,0x61,0x4a,0x60,0x8c,0xa0,0xc2,0x21,0x1d,0xe4,0xc1,0x0d,0xd8,0x21, + 0x1c,0xdc,0xe1,0x1c,0xea,0x21,0x1c,0xce,0xa1,0x1c,0x7e,0xc1,0x1e,0xca,0x41,0x1e, + 0xe6,0x21,0x1d,0xde,0xc1,0x1d,0xa6,0x04,0xc8,0x88,0x29,0x1c,0xd2,0x41,0x1e,0xdc, + 0x60,0x1c,0xde,0xa1,0x1d,0xe0,0x21,0x1d,0xd8,0xa1,0x1c,0x7e,0xe1,0x1d,0xe0,0x81, + 0x1e,0xd2,0xe1,0x1d,0xdc,0x61,0x1e,0xa6,0x0c,0x0a,0xe3,0x8c,0x50,0xc2,0x21,0x1d, + 0xe4,0xc1,0x0d,0xec,0xa1,0x1c,0xe4,0x81,0x1e,0xca,0x01,0x1f,0xa6,0x04,0x74,0x00, + 0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4,0x83, + 0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43,0x3d, + 0xc0,0xc3,0x3c,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00,0x06,0x50,0x30,0x00, + 0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x1e,0x00,0x00,0x00,0x13,0x04,0x41,0x2c, + 0x10,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0xf4,0xc6,0x22,0x82,0x20,0x08,0x46,0x00, + 0xa8,0x95,0x40,0x19,0xd0,0x98,0x01,0xa0,0x30,0x03,0x00,0x00,0xe3,0x15,0x07,0x33, + 0x4d,0x0c,0x05,0x65,0x90,0x81,0x19,0x0e,0x13,0x02,0xf9,0x8c,0x57,0x2c,0xd0,0x75, + 0x21,0x14,0x94,0x41,0x06,0xe8,0x60,0x4c,0x08,0xe4,0x63,0x41,0x01,0x9f,0xf1,0x0a, + 0xa8,0xe2,0x38,0x86,0x82,0x62,0x43,0x00,0x9f,0xd9,0x06,0xa7,0x02,0x66,0x1b,0x82, + 0x2a,0x98,0x6d,0x08,0x06,0x21,0x83,0x80,0x18,0x00,0x00,0x00,0x04,0x00,0x00,0x00, + 0x5b,0x86,0x20,0xc8,0x83,0x2d,0x43,0x11,0xe4,0xc1,0x96,0x41,0x09,0xf2,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const uint8_t _sspine_fs_bytecode_metal_ios[3241] = { + 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xa9,0x0c,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, + 0xd0,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,0xf2,0xd8,0x54,0xd8,0x76,0x28,0x3c, + 0xc1,0x24,0xd6,0xe7,0xb5,0xc4,0xbc,0x0f,0x0f,0x5a,0x55,0xdc,0x0c,0x24,0xa5,0x96, + 0x04,0x74,0x34,0x10,0x4e,0xdf,0x2e,0x1d,0xee,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,0xbc,0x0b,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, + 0xde,0x21,0x0c,0x00,0x00,0xec,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,0x8e,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, + 0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07,0x7a,0x60,0x87,0x7c, + 0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72,0x68,0x03,0x72,0x48, + 0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90,0x07,0x7a,0x68,0x03, + 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc,0x21,0x1c,0xd8,0x61, + 0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81,0x1d,0xca,0xa1,0x0d, + 0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d,0xd8,0x61,0x1e,0x00, + 0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87,0x79,0x98,0x87,0x36, + 0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36,0x30,0x07,0x78,0x68, + 0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,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,0x00,0x7a,0x90, + 0x87,0x7a,0x28,0x07,0x60,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,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36,0x60,0x87,0x72,0x08, + 0x07,0x73,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,0xd4,0xa1,0x1e,0xda,0x01,0x1e,0xda,0x80,0x1e, + 0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07, + 0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,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,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c,0xc8,0xa1,0x0d,0xf4, + 0xa1,0x1c,0xe4,0xe1,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,0xa0, + 0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68,0x83,0x76,0x08,0x07, + 0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6,0x81,0x1e,0xc2,0x61, + 0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d, + 0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e,0xda,0x60,0x1e,0xd2, + 0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87,0x70,0x30,0x87,0x72, + 0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e, + 0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e,0xde,0xc1,0x1c,0xe8, + 0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87,0x70,0x60,0x87,0x79, + 0x28,0x07,0x60,0x03,0x22,0x0c,0x40,0x02,0x2c,0x40,0xb5,0xc1,0x18,0x08,0x60,0x01, + 0xaa,0x0d,0x06,0x51,0x00,0x0b,0x50,0x6d,0x30,0x8a,0xff,0xff,0xff,0xff,0x1f,0x00, + 0x09,0xa0,0x36,0x20,0xc6,0xff,0xff,0xff,0xff,0x0f,0x00,0x03,0x48,0x40,0xb5,0xc1, + 0x38,0x02,0x60,0x01,0xaa,0x0d,0x06,0x22,0x00,0x0b,0x50,0x01,0x00,0x49,0x18,0x00, + 0x00,0x03,0x00,0x00,0x00,0x13,0x88,0x40,0x18,0x88,0x09,0x41,0x31,0x61,0x30,0x0e, + 0x04,0x89,0x20,0x00,0x00,0x27,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,0x6c,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x30,0x8c, + 0x20,0x00,0x07,0x49,0x53,0x44,0x09,0x93,0x5f,0x48,0xff,0x03,0x44,0x00,0x23,0xa1, + 0x00,0x0c,0x22,0x10,0xc2,0x51,0xd2,0x14,0x51,0xc2,0xe4,0xff,0x13,0x71,0x4d,0x54, + 0x44,0xfc,0xf6,0xf0,0x4f,0x63,0x04,0xc0,0x20,0x82,0x11,0x5c,0x24,0x4d,0x11,0x25, + 0x4c,0xfe,0x2f,0x01,0xcc,0xb3,0x10,0xd1,0x3f,0x8d,0x11,0x00,0x83,0x08,0x88,0x50, + 0x0c,0x31,0x42,0x39,0x89,0x54,0x21,0x42,0x08,0x81,0xd8,0x1c,0x41,0x30,0x47,0x00, + 0x06,0xc3,0x08,0xc2,0x53,0x90,0x70,0xd2,0x70,0xd0,0x01,0x8a,0x03,0x01,0x29,0xf0, + 0x86,0x11,0x86,0x67,0x18,0x61,0x00,0x86,0x11,0x88,0x67,0x8e,0x00,0x14,0x06,0x11, + 0x00,0x61,0x04,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,0x05,0x00,0x80,0x00, + 0x00,0x00,0x00,0x00,0x80,0x21,0x4c,0x03,0x04,0x80,0x00,0x00,0x00,0x00,0x00,0xc0, + 0x10,0x46,0x02,0x02,0x60,0x00,0x00,0x00,0x00,0x00,0x20,0x0b,0x04,0x09,0x00,0x00, + 0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43, + 0x7a,0x23,0x00,0x25,0x50,0x08,0x45,0x50,0x10,0x65,0x40,0x78,0x04,0x80,0xe8,0x58, + 0x02,0x24,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xf9,0x00,0x00,0x00,0x1a,0x03,0x4c, + 0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x63, + 0x4c,0x00,0xa5,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62, + 0x3c,0xc4,0x24,0x3c,0x05,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e, + 0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6, + 0xc5,0xc6,0xe6,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06, + 0x06,0x26,0xc6,0xc5,0xc6,0xe6,0xc6,0x45,0x26,0x65,0x88,0x30,0x11,0x43,0x8c,0x87, + 0x78,0x8e,0x67,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x99,0x8e,0x87,0x78,0x88, + 0x67,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,0x98, + 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, + 0x69,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,0xa6,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, + 0xa6,0xe7,0x19,0x26,0x68,0x8a,0x26,0x69,0x9a,0x86,0x08,0x13,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,0x9e,0x61,0xb2,0xa6,0x6b,0xc2,0xa6,0x6c,0x82,0x26,0x6d,0x92,0xa6, + 0x8d,0x4b,0xdd,0x5c,0x99,0x1c,0x0a,0xdb,0xdb,0x98,0x5b,0x4c,0x0a,0x8b,0xb1,0x37, + 0xb6,0x37,0xb9,0x21,0xd2,0x43,0x4c,0xd6,0xd4,0x4d,0xd8,0x94,0x4d,0xd0,0x14,0x4d, + 0xd2,0xe4,0x51,0x09,0x4b,0x93,0x73,0x11,0xab,0x33,0x33,0x2b,0x93,0xe3,0x13,0x96, + 0x26,0xe7,0x22,0x56,0x67,0x66,0x56,0x26,0xf7,0x35,0x97,0xa6,0x57,0x46,0x29,0x2c, + 0x4d,0xce,0x85,0xed,0x6d,0x2c,0x8c,0x2e,0xed,0xcd,0xed,0x2b,0xcd,0x8d,0xac,0x0c, + 0x8f,0x48,0x58,0x9a,0x9c,0x8b,0x5c,0x59,0x18,0x19,0xa9,0xb0,0x34,0x39,0x97,0x39, + 0x3a,0xb9,0xba,0x31,0xba,0x2f,0xba,0x3c,0xb8,0xb2,0xaf,0x34,0x37,0xb3,0x37,0x16, + 0x66,0x6c,0x6f,0x61,0x74,0x1c,0xe0,0xda,0xc2,0x86,0x28,0xcf,0xf0,0x14,0xcf,0x30, + 0x95,0xc1,0x64,0x06,0x8c,0xc2,0xd2,0xe4,0x5c,0xc2,0xe4,0xce,0xbe,0xe8,0xf2,0xe0, + 0xca,0xbe,0xe6,0xd2,0xf4,0xca,0x78,0x85,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xd1, + 0xe5,0xc1,0x95,0x7d,0x85,0xb1,0xa5,0x9d,0xb9,0x7d,0xcd,0xa5,0xe9,0x95,0x31,0x31, + 0x9b,0xfb,0x82,0x0b,0x93,0x0b,0x6b,0x9b,0xe3,0xf0,0x55,0x33,0x33,0x84,0x0c,0x1e, + 0x63,0x02,0x83,0x29,0x0c,0x9e,0x62,0x12,0x83,0x67,0x78,0x88,0x69,0x0c,0x26,0x32, + 0x98,0xce,0x60,0x42,0x83,0xa7,0x98,0xd2,0xe0,0x29,0x26,0x68,0x52,0x83,0x49,0x9a, + 0xd6,0x80,0x4b,0x58,0x9a,0x9c,0x0b,0x5d,0x19,0x1e,0x5d,0x9d,0x5c,0x19,0x95,0xb0, + 0x34,0x39,0x97,0xb9,0xb0,0x36,0x38,0xb6,0x32,0x62,0x74,0x65,0x78,0x74,0x75,0x72, + 0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c, + 0x65,0x3e,0x1c,0xe8,0xca,0xf0,0x86,0x50,0x0f,0x32,0xb5,0xc1,0x24,0x06,0xcf,0xf0, + 0x10,0x93,0x1b,0x4c,0xd0,0xf4,0x06,0x93,0x34,0xc1,0x01,0x97,0xb0,0x34,0x39,0x97, + 0xb9,0xb0,0x36,0x38,0xb6,0x32,0x39,0x1e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x1c, + 0xe6,0xda,0xe0,0x86,0x48,0x4f,0x31,0xc9,0xc1,0x24,0x06,0xcf,0xf0,0x10,0x13,0x34, + 0xcd,0xc1,0x24,0x4d,0x74,0x30,0x44,0x99,0xb8,0xe9,0x9b,0xd8,0x60,0x8a,0x83,0xa9, + 0x0e,0x86,0x18,0x0b,0x30,0x55,0x93,0x1d,0xd0,0xf9,0xd2,0xa2,0x9a,0xca,0x31,0x9b, + 0xfb,0x82,0x0b,0x93,0x0b,0x6b,0x9b,0xe3,0xf3,0xd6,0xe6,0x96,0x06,0xf7,0x46,0x57, + 0xe6,0x46,0x07,0x32,0x86,0x16,0x26,0xc7,0x67,0x2a,0xad,0x0d,0x8e,0xad,0x0c,0x64, + 0x68,0x65,0x05,0x84,0x4a,0x28,0x28,0x68,0x88,0x30,0xe9,0xc1,0x10,0x63,0xca,0x83, + 0x69,0x0f,0xb0,0x64,0x88,0x31,0x95,0xc1,0xc4,0x07,0x58,0x32,0xc4,0x98,0xf0,0x60, + 0xea,0x03,0x2c,0x19,0x62,0x4c,0x7e,0x30,0xf5,0x01,0x96,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,0xdc,0x01,0x00, + 0x00,0x79,0x18,0x00,0x00,0x7b,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,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4, + 0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43, + 0x3d,0xc0,0xc3,0x3c,0x00,0x71,0x20,0x00,0x00,0x0b,0x00,0x00,0x00,0x26,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,0x6d,0x01,0x0d,0x80,0x44,0x3e,0x83, + 0x5c,0x7e,0x85,0x17,0xb7,0x0d,0x00,0x00,0x00,0x61,0x20,0x00,0x00,0x25,0x00,0x00, + 0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x74,0x47,0x00, + 0xc6,0x22,0x80,0x40,0x38,0xe6,0x20,0x06,0xc2,0xa8,0xc8,0xd5,0xc0,0x08,0x00,0xbd, + 0x19,0x00,0x82,0x23,0x00,0x54,0xc7,0x1a,0x80,0x40,0x18,0x6b,0x18,0x86,0x81,0xec, + 0x0c,0x00,0x89,0x19,0x00,0x0a,0x33,0x00,0x04,0x46,0x00,0x00,0x00,0x23,0x06,0xca, + 0x10,0x6c,0x8f,0x23,0x29,0x47,0x12,0x58,0x20,0xc9,0x67,0x90,0x21,0x20,0x90,0x41, + 0x06,0xa1,0x40,0x4c,0x08,0xe4,0x33,0xc8,0x10,0x24,0xd0,0x20,0x43,0x50,0x48,0x16, + 0x60,0xf2,0x19,0x6f,0xc0,0x38,0x31,0xa0,0x60,0xcc,0x31,0x30,0x01,0x19,0x0c,0x32, + 0x04,0x0d,0x36,0x62,0x60,0x08,0x01,0x1a,0x2c,0x45,0x30,0xdb,0x00,0x05,0x40,0x06, + 0x01,0x31,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x5b,0x86,0x24,0xf8,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; +static const char _sspine_vs_source_metal_sim[624] = { + 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, + 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, + 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, + 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, + 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x76, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x7d,0x3b,0x0a, + 0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75, + 0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,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,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,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e, + 0x2e,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,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, + +}; +static const char _sspine_fs_source_metal_sim[619] = { + 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,0x66, + 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x20,0x70,0x6d,0x61,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74, + 0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b, + 0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67, + 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30, + 0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,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,0x63,0x6f,0x6e,0x73,0x74,0x61,0x6e,0x74,0x20,0x66,0x73,0x5f,0x70, + 0x61,0x72,0x61,0x6d,0x73,0x26,0x20,0x5f,0x35,0x33,0x20,0x5b,0x5b,0x62,0x75,0x66, + 0x66,0x65,0x72,0x28,0x30,0x29,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,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, + 0x5f,0x32,0x38,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,0x20,0x2a,0x20,0x69, + 0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f, + 0x61,0x74,0x20,0x5f,0x33,0x37,0x20,0x3d,0x20,0x5f,0x32,0x38,0x2e,0x77,0x3b,0x0a, + 0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c, + 0x6f,0x72,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x5f,0x32,0x38,0x2c,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x34,0x28,0x5f,0x32,0x38,0x2e,0x78,0x79,0x7a,0x20,0x2a,0x20,0x5f, + 0x33,0x37,0x2c,0x20,0x5f,0x33,0x37,0x29,0x20,0x2a,0x20,0x69,0x6e,0x2e,0x63,0x6f, + 0x6c,0x6f,0x72,0x2c,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x5f,0x35,0x33,0x2e, + 0x70,0x6d,0x61,0x29,0x29,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_WGPU) +static const char _sspine_vs_source_wgsl[1003] = { + 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, + 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,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,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,0x36,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,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x20,0x3d,0x20,0x28,0x78,0x5f,0x32,0x32,0x20,0x2a,0x20,0x76,0x65,0x63,0x34, + 0x66,0x28,0x78,0x5f,0x32,0x36,0x2e,0x78,0x2c,0x20,0x78,0x5f,0x32,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,0x33,0x38,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,0x38,0x3b,0x0a,0x20,0x20,0x6c, + 0x65,0x74,0x20,0x78,0x5f,0x34,0x32,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,0x32,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, +}; +static const char _sspine_fs_source_wgsl[1125] = { + 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, + 0x66,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,0x70,0x6d,0x61,0x20,0x3a,0x20,0x66,0x33,0x32,0x2c,0x0a,0x7d,0x0a,0x0a,0x40, + 0x67,0x72,0x6f,0x75,0x70,0x28,0x31,0x29,0x20,0x40,0x62,0x69,0x6e,0x64,0x69,0x6e, + 0x67,0x28,0x33,0x32,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,0x34,0x38,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,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,0x30,0x29,0x20, + 0x40,0x62,0x69,0x6e,0x64,0x69,0x6e,0x67,0x28,0x34,0x29,0x20,0x76,0x61,0x72,0x3c, + 0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x3e,0x20,0x78,0x5f,0x35,0x33,0x20,0x3a,0x20, + 0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x3b,0x0a,0x0a,0x66,0x6e,0x20,0x6d, + 0x61,0x69,0x6e,0x5f,0x31,0x28,0x29,0x20,0x7b,0x0a,0x20,0x20,0x76,0x61,0x72,0x20, + 0x63,0x30,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,0x0a,0x20,0x20,0x76,0x61, + 0x72,0x20,0x63,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x3b,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,0x37,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20, + 0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20,0x63,0x30,0x20,0x3d,0x20,0x28,0x78, + 0x5f,0x32,0x34,0x20,0x2a,0x20,0x78,0x5f,0x32,0x37,0x29,0x3b,0x0a,0x20,0x20,0x6c, + 0x65,0x74,0x20,0x78,0x5f,0x33,0x31,0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20, + 0x3d,0x20,0x63,0x30,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x37, + 0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x63,0x30,0x2e,0x77,0x3b,0x0a,0x20, + 0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x33,0x38,0x20,0x3a,0x20,0x76,0x65,0x63,0x33, + 0x66,0x20,0x3d,0x20,0x28,0x76,0x65,0x63,0x33,0x66,0x28,0x78,0x5f,0x33,0x31,0x2e, + 0x78,0x2c,0x20,0x78,0x5f,0x33,0x31,0x2e,0x79,0x2c,0x20,0x78,0x5f,0x33,0x31,0x2e, + 0x7a,0x29,0x20,0x2a,0x20,0x78,0x5f,0x33,0x37,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65, + 0x74,0x20,0x78,0x5f,0x34,0x30,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x63, + 0x30,0x2e,0x77,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,0x3b, + 0x0a,0x20,0x20,0x63,0x31,0x20,0x3d,0x20,0x28,0x76,0x65,0x63,0x34,0x66,0x28,0x78, + 0x5f,0x33,0x38,0x2e,0x78,0x2c,0x20,0x78,0x5f,0x33,0x38,0x2e,0x79,0x2c,0x20,0x78, + 0x5f,0x33,0x38,0x2e,0x7a,0x2c,0x20,0x78,0x5f,0x34,0x30,0x29,0x20,0x2a,0x20,0x78, + 0x5f,0x34,0x35,0x29,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x34,0x39, + 0x20,0x3a,0x20,0x76,0x65,0x63,0x34,0x66,0x20,0x3d,0x20,0x63,0x30,0x3b,0x0a,0x20, + 0x20,0x6c,0x65,0x74,0x20,0x78,0x5f,0x35,0x30,0x20,0x3a,0x20,0x76,0x65,0x63,0x34, + 0x66,0x20,0x3d,0x20,0x63,0x31,0x3b,0x0a,0x20,0x20,0x6c,0x65,0x74,0x20,0x78,0x5f, + 0x35,0x38,0x20,0x3a,0x20,0x66,0x33,0x32,0x20,0x3d,0x20,0x78,0x5f,0x35,0x33,0x2e, + 0x70,0x6d,0x61,0x3b,0x0a,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f, + 0x72,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x78,0x5f,0x34,0x39,0x2c,0x20,0x78,0x5f, + 0x35,0x30,0x2c,0x20,0x76,0x65,0x63,0x34,0x66,0x28,0x78,0x5f,0x35,0x38,0x2c,0x20, + 0x78,0x5f,0x35,0x38,0x2c,0x20,0x78,0x5f,0x35,0x38,0x2c,0x20,0x78,0x5f,0x35,0x38, + 0x29,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* _sspine_vs_source_dummy = ""; +static const char* _sspine_fs_source_dummy = ""; +#else +#error "Please define one of SOKOL_GLCORE33, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND!" +#endif + +// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██████ ██ ██ ██ ██ ███████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██████ ██████ ██ ███████ +// +// >>structs + +#define _sspine_def(val, def) (((val) == 0) ? (def) : (val)) +#define _SSPINE_INIT_COOKIE (0xABBAABBA) +#define _SSPINE_INVALID_SLOT_INDEX (0) +#define _SSPINE_DEFAULT_CONTEXT_POOL_SIZE (4) +#define _SSPINE_DEFAULT_ATLAS_POOL_SIZE (64) +#define _SSPINE_DEFAULT_SKELETON_POOL_SIZE (64) +#define _SSPINE_DEFAULT_SKINSET_POOL_SIZE (64) +#define _SSPINE_DEFAULT_INSTANCE_POOL_SIZE (1024) +#define _SSPINE_DEFAULT_MAX_VERTICES (1<<16) +#define _SSPINE_DEFAULT_MAX_COMMANDS (1<<14) +#define _SSPINE_MAX_TRIGGERED_EVENTS (16) +#define _SSPINE_SLOT_SHIFT (16) +#define _SSPINE_MAX_POOL_SIZE (1<<_SSPINE_SLOT_SHIFT) +#define _SSPINE_SLOT_MASK (_SSPINE_MAX_POOL_SIZE-1) + +typedef struct { + float mvp[16]; +} _sspine_vsparams_t; + +typedef struct { + float pma; + uint8_t _pad[12]; +} _sspine_fsparams_t; + +typedef struct { + uint32_t id; + sspine_resource_state state; +} _sspine_slot_t; + +typedef struct { + int size; + int queue_top; + uint32_t* gen_ctrs; + int* free_queue; +} _sspine_pool_t; + +typedef struct { + _sspine_slot_t slot; + sspine_atlas_overrides overrides; + spAtlas* sp_atlas; + int num_pages; +} _sspine_atlas_t; + +typedef struct { + _sspine_pool_t pool; + _sspine_atlas_t* items; +} _sspine_atlas_pool_t; + +typedef struct { + uint32_t id; + _sspine_atlas_t* ptr; +} _sspine_atlas_ref_t; + +typedef struct { + sg_image img; + sg_sampler smp; +} _sspine_image_sampler_pair_t; + +typedef struct { + _sspine_slot_t slot; + _sspine_atlas_ref_t atlas; + spSkeletonData* sp_skel_data; + spAnimationStateData* sp_anim_data; + struct { + int cap; + sspine_vec2* ptr; + } tform_buf; +} _sspine_skeleton_t; + +typedef struct { + _sspine_pool_t pool; + _sspine_skeleton_t* items; +} _sspine_skeleton_pool_t; + +typedef struct { + uint32_t id; + _sspine_skeleton_t* ptr; +} _sspine_skeleton_ref_t; + +typedef struct { + _sspine_slot_t slot; + _sspine_skeleton_ref_t skel; + spSkin* sp_skin; +} _sspine_skinset_t; + +typedef struct { + _sspine_pool_t pool; + _sspine_skinset_t* items; +} _sspine_skinset_pool_t; + +typedef struct { + uint32_t id; + _sspine_skinset_t* ptr; +} _sspine_skinset_ref_t; + +typedef struct { + _sspine_slot_t slot; + _sspine_atlas_ref_t atlas; + _sspine_skeleton_ref_t skel; + _sspine_skinset_ref_t skinset; + spSkeleton* sp_skel; + spAnimationState* sp_anim_state; + spSkeletonClipping* sp_clip; + int cur_triggered_event_index; + sspine_triggered_event_info triggered_events[_SSPINE_MAX_TRIGGERED_EVENTS]; +} _sspine_instance_t; + +typedef struct { + _sspine_pool_t pool; + _sspine_instance_t* items; +} _sspine_instance_pool_t; + +typedef struct { + sspine_vec2 pos; + sspine_vec2 uv; + uint32_t color; +} _sspine_vertex_t; + +typedef struct { + _sspine_vertex_t* ptr; + int index; +} _sspine_alloc_vertices_result_t; + +typedef struct { + uint32_t* ptr; + int index; +} _sspine_alloc_indices_result_t; + +typedef struct { + int layer; + sg_pipeline pip; + sg_image img; + sg_sampler smp; + float pma; // pma = 0.0: use texture color as is, pma = 1.0: multiply texture rgb by texture alpha in fragment shader + int base_element; + int num_elements; +} _sspine_command_t; + +typedef struct { + _sspine_slot_t slot; + float transform[16]; + struct { + int cap; + int next; + uint32_t rewind_frame_id; + _sspine_vertex_t* ptr; + } vertices; + struct { + int cap; + int next; + uint32_t rewind_frame_id; + uint32_t* ptr; + } indices; + struct { + int cap; + int next; + uint32_t rewind_frame_id; + _sspine_command_t* ptr; + } commands; + uint32_t update_frame_id; + sg_buffer vbuf; + sg_buffer ibuf; + struct { + sg_pipeline normal_additive; + sg_pipeline multiply; + } pip; + sg_bindings bind; +} _sspine_context_t; + +typedef struct { + _sspine_pool_t pool; + _sspine_context_t* items; +} _sspine_context_pool_t; + +typedef struct { + uint32_t init_cookie; + uint32_t frame_id; + sspine_desc desc; + sspine_context def_ctx_id; + sspine_context cur_ctx_id; + _sspine_context_t* cur_ctx; // may be 0! + sg_shader shd; + _sspine_context_pool_t context_pool; + _sspine_atlas_pool_t atlas_pool; + _sspine_skeleton_pool_t skeleton_pool; + _sspine_skinset_pool_t skinset_pool; + _sspine_instance_pool_t instance_pool; +} _sspine_t; +static _sspine_t _sspine; + +// dummy spine-c platform implementation functions +#if defined(__cplusplus) +extern "C" { +#endif +void _spAtlasPage_createTexture(spAtlasPage* self, const char* path) { + // nothing to do here + (void)self; (void)path; +} + +static void _sspine_delete_image_sampler_pair(const _sspine_image_sampler_pair_t* isp); +void _spAtlasPage_disposeTexture(spAtlasPage* self) { + _sspine_delete_image_sampler_pair((const _sspine_image_sampler_pair_t*) self->rendererObject); +} + +char* _spUtil_readFile(const char* path, int* length) { + (void)path; + *length = 0; + return 0; +} +#if defined(__cplusplus) +} // extern "C" +#endif + +// ██ ██████ ██████ ██████ ██ ███ ██ ██████ +// ██ ██ ██ ██ ██ ██ ████ ██ ██ +// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██████ ██████ ██████ ██ ██ ████ ██████ +// +// >>logging +#if defined(SOKOL_DEBUG) +#define _SSPINE_LOGITEM_XMACRO(item,msg) #item ": " msg, +static const char* _sspine_log_messages[] = { + _SSPINE_LOG_ITEMS +}; +#undef _SSPINE_LOGITEM_XMACRO +#endif // SOKOL_DEBUG + +#define _SSPINE_PANIC(code) _sspine_log(SSPINE_LOGITEM_ ##code, 0, __LINE__) +#define _SSPINE_ERROR(code) _sspine_log(SSPINE_LOGITEM_ ##code, 1, __LINE__) +#define _SSPINE_WARN(code) _sspine_log(SSPINE_LOGITEM_ ##code, 2, __LINE__) +#define _SSPINE_INFO(code) _sspine_log(SSPINE_LOGITEM_ ##code, 3, __LINE__) + +static void _sspine_log(sspine_log_item log_item, uint32_t log_level, uint32_t line_nr) { + if (_sspine.desc.logger.func) { + #if defined(SOKOL_DEBUG) + const char* filename = __FILE__; + const char* message = _sspine_log_messages[log_item]; + #else + const char* filename = 0; + const char* message = 0; + #endif + _sspine.desc.logger.func("sspine", log_level, log_item, message, line_nr, filename, _sspine.desc.logger.user_data); + } else { + // for log level PANIC it would be 'undefined behaviour' to continue + if (log_level == 0) { + abort(); + } + } +} + +// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ +// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ +// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ██ ██████ ██ ██ ██ +// +// >>memory +static void _sspine_clear(void* ptr, size_t size) { + SOKOL_ASSERT(ptr && (size > 0)); + memset(ptr, 0, size); +} + +/* 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. +*/ +static bool _sspine_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; + } +} + +static sspine_string _sspine_string(const char* cstr) { + sspine_string res; + _sspine_clear(&res, sizeof(res)); + if (cstr) { + res.valid = true; + res.truncated = !_sspine_strcpy(cstr, res.cstr, sizeof(res.cstr)); + res.len = (uint8_t)strlen(res.cstr); + } + return res; +} + +static void* _sspine_malloc(size_t size) { + SOKOL_ASSERT(size > 0); + void* ptr; + if (_sspine.desc.allocator.alloc_fn) { + ptr = _sspine.desc.allocator.alloc_fn(size, _sspine.desc.allocator.user_data); + } else { + ptr = malloc(size); + } + if (0 == ptr) { + _SSPINE_PANIC(MALLOC_FAILED); + } + return ptr; +} + +static void* _sspine_malloc_clear(size_t size) { + void* ptr = _sspine_malloc(size); + _sspine_clear(ptr, size); + return ptr; +} + +static void _sspine_free(void* ptr) { + if (_sspine.desc.allocator.free_fn) { + _sspine.desc.allocator.free_fn(ptr, _sspine.desc.allocator.user_data); + } else { + free(ptr); + } +} + +// ██████ ███████ ███████ ███████ +// ██ ██ ██ ██ ██ +// ██████ █████ █████ ███████ +// ██ ██ ██ ██ ██ +// ██ ██ ███████ ██ ███████ +// +// >>refs +static bool _sspine_atlas_ref_valid(const _sspine_atlas_ref_t* ref) { + return ref->ptr && (ref->ptr->slot.id == ref->id); +} + +static bool _sspine_skeleton_ref_valid(const _sspine_skeleton_ref_t* ref) { + return ref->ptr && (ref->ptr->slot.id == ref->id); +} + +static bool _sspine_skinset_ref_valid(const _sspine_skinset_ref_t* ref) { + return ref->ptr && (ref->ptr->slot.id == ref->id); +} + +static bool _sspine_skeleton_and_deps_valid(_sspine_skeleton_t* skeleton) { + return skeleton && _sspine_atlas_ref_valid(&skeleton->atlas); +} + +static bool _sspine_skinset_and_deps_valid(_sspine_skinset_t* skinset) { + return skinset && _sspine_skeleton_ref_valid(&skinset->skel); +} + +static bool _sspine_instance_and_deps_valid(_sspine_instance_t* instance) { + return instance && + _sspine_atlas_ref_valid(&instance->atlas) && + _sspine_skeleton_ref_valid(&instance->skel) && + ((instance->skinset.id == SSPINE_INVALID_ID) || _sspine_skinset_ref_valid(&instance->skinset)); +} + +static sspine_image _sspine_image(uint32_t atlas_id, int index) { + sspine_image img = { atlas_id, index }; + return img; +} + +static sspine_atlas_page _sspine_atlas_page(uint32_t atlas_id, int index) { + sspine_atlas_page page = { atlas_id, index }; + return page; +} + +static sspine_anim _sspine_anim(uint32_t skeleton_id, int index) { + sspine_anim anim = { skeleton_id, index }; + return anim; +} + +static sspine_bone _sspine_bone(uint32_t skeleton_id, int index) { + sspine_bone bone = { skeleton_id, index }; + return bone; +} + +static sspine_slot _sspine_slot(uint32_t skeleton_id, int index) { + sspine_slot slot = { skeleton_id, index }; + return slot; +} + +static sspine_event _sspine_event(uint32_t skeleton_id, int index) { + sspine_event event = { skeleton_id, index }; + return event; +} + +static sspine_iktarget _sspine_iktarget(uint32_t skeleton_id, int index) { + sspine_iktarget iktarget = { skeleton_id, index }; + return iktarget; +} + +static sspine_skin _sspine_skin(uint32_t skeleton_id, int index) { + sspine_skin skin = { skeleton_id, index }; + return skin; +} + +// ██████ ██████ ██████ ██ +// ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ +// +// >>pool +static void _sspine_init_pool(_sspine_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*) _sspine_malloc_clear(gen_ctrs_size); + // it's not a bug to only reserve 'num' here + pool->free_queue = (int*) _sspine_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 _sspine_discard_pool(_sspine_pool_t* pool) { + SOKOL_ASSERT(pool); + SOKOL_ASSERT(pool->free_queue); + _sspine_free(pool->free_queue); + pool->free_queue = 0; + SOKOL_ASSERT(pool->gen_ctrs); + _sspine_free(pool->gen_ctrs); + pool->gen_ctrs = 0; + pool->size = 0; + pool->queue_top = 0; +} + +static int _sspine_pool_alloc_index(_sspine_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 _SSPINE_INVALID_SLOT_INDEX; + } +} + +static void _sspine_pool_free_index(_sspine_pool_t* pool, int slot_index) { + SOKOL_ASSERT((slot_index > _SSPINE_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)); +} + +/* initialize a pool slot: + - 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 handle id +*/ +static uint32_t _sspine_slot_init(_sspine_pool_t* pool, _sspine_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 > _SSPINE_INVALID_SLOT_INDEX) && (slot_index < pool->size)); + SOKOL_ASSERT((slot->state == SSPINE_RESOURCESTATE_INITIAL) && (slot->id == SSPINE_INVALID_ID)); + uint32_t ctr = ++pool->gen_ctrs[slot_index]; + slot->id = (ctr<<_SSPINE_SLOT_SHIFT)|(slot_index & _SSPINE_SLOT_MASK); + slot->state = SSPINE_RESOURCESTATE_ALLOC; + return slot->id; +} + +// extract slot index from id +static int _sspine_slot_index(uint32_t id) { + int slot_index = (int) (id & _SSPINE_SLOT_MASK); + SOKOL_ASSERT(_SSPINE_INVALID_SLOT_INDEX != slot_index); + return slot_index; +} + +static void _sspine_init_item_pool(_sspine_pool_t* pool, int pool_size, void** items_ptr, size_t item_size_bytes) { + // NOTE: the pools will have an additional item, since slot 0 is reserved + SOKOL_ASSERT(pool && (pool->size == 0)); + SOKOL_ASSERT((pool_size > 0) && (pool_size < _SSPINE_MAX_POOL_SIZE)); + SOKOL_ASSERT(items_ptr && (*items_ptr == 0)); + SOKOL_ASSERT(item_size_bytes > 0); + _sspine_init_pool(pool, pool_size); + const size_t pool_size_bytes = item_size_bytes * (size_t)pool->size; + *items_ptr = _sspine_malloc_clear(pool_size_bytes); +} + +static void _sspine_discard_item_pool(_sspine_pool_t* pool, void** items_ptr) { + SOKOL_ASSERT(pool && (pool->size != 0)); + SOKOL_ASSERT(items_ptr && (*items_ptr != 0)); + _sspine_free(*items_ptr); *items_ptr = 0; + _sspine_discard_pool(pool); +} + +// ██████ ██████ ███ ██ ████████ ███████ ██ ██ ████████ +// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ █████ ███ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██████ ██ ████ ██ ███████ ██ ██ ██ +// +// >>context +static void _sspine_setup_context_pool(int pool_size) { + _sspine_context_pool_t* p = &_sspine.context_pool; + _sspine_init_item_pool(&p->pool, pool_size, (void**)&p->items, sizeof(_sspine_context_t)); +} + +static void _sspine_discard_context_pool(void) { + _sspine_context_pool_t* p = &_sspine.context_pool; + _sspine_discard_item_pool(&p->pool, (void**)&p->items); +} + +static sspine_context _sspine_make_context_handle(uint32_t id) { + sspine_context handle = { id }; + return handle; +} + +static _sspine_context_t* _sspine_context_at(uint32_t id) { + SOKOL_ASSERT(SSPINE_INVALID_ID != id); + const _sspine_context_pool_t* p = &_sspine.context_pool; + int slot_index = _sspine_slot_index(id); + SOKOL_ASSERT((slot_index > _SSPINE_INVALID_SLOT_INDEX) && (slot_index < p->pool.size)); + return &p->items[slot_index]; +} + +static _sspine_context_t* _sspine_lookup_context(uint32_t id) { + if (SSPINE_INVALID_ID != id) { + _sspine_context_t* ctx = _sspine_context_at(id); + if (ctx->slot.id == id) { + return ctx; + } + } + return 0; +} + +static sspine_context _sspine_alloc_context(void) { + _sspine_context_pool_t* p = &_sspine.context_pool; + int slot_index = _sspine_pool_alloc_index(&p->pool); + if (_SSPINE_INVALID_SLOT_INDEX != slot_index) { + uint32_t id = _sspine_slot_init(&p->pool, &p->items[slot_index].slot, slot_index); + return _sspine_make_context_handle(id); + } else { + // pool exhausted + return _sspine_make_context_handle(SSPINE_INVALID_ID); + } +} + +static sspine_resource_state _sspine_init_context(_sspine_context_t* ctx, const sspine_context_desc* desc) { + SOKOL_ASSERT(ctx && (ctx->slot.state == SSPINE_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + + // setup vertex, index and command storage + ctx->vertices.cap = desc->max_vertices; + ctx->indices.cap = ctx->vertices.cap * 3; + ctx->commands.cap = desc->max_commands; + + const size_t vbuf_size = (size_t)ctx->vertices.cap * sizeof(_sspine_vertex_t); + const size_t ibuf_size = (size_t)ctx->indices.cap * sizeof(uint32_t); + const size_t cbuf_size = (size_t)ctx->commands.cap * sizeof(_sspine_command_t); + + ctx->vertices.ptr = (_sspine_vertex_t*) _sspine_malloc(vbuf_size); + ctx->indices.ptr = (uint32_t*) _sspine_malloc(ibuf_size); + ctx->commands.ptr = (_sspine_command_t*) _sspine_malloc(cbuf_size); + + sg_buffer_desc vbuf_desc; + _sspine_clear(&vbuf_desc, sizeof(vbuf_desc)); + vbuf_desc.type = SG_BUFFERTYPE_VERTEXBUFFER; + vbuf_desc.usage = SG_USAGE_STREAM; + vbuf_desc.size = vbuf_size; + vbuf_desc.label = "sspine-vbuf"; + ctx->vbuf = sg_make_buffer(&vbuf_desc); + ctx->bind.vertex_buffers[0] = ctx->vbuf; + + sg_buffer_desc ibuf_desc; + _sspine_clear(&ibuf_desc, sizeof(ibuf_desc)); + ibuf_desc.type = SG_BUFFERTYPE_INDEXBUFFER; + ibuf_desc.usage = SG_USAGE_STREAM; + ibuf_desc.size = ibuf_size; + ibuf_desc.label = "sspine-ibuf"; + ctx->ibuf = sg_make_buffer(&ibuf_desc); + ctx->bind.index_buffer = ctx->ibuf; + + // for blend modes, see: https://wiki.libsdl.org/SDL_BlendMode + // + // NOTE: we're configuring the blend mode for premultiplied alpha, + // and then do the premultiplication in the fragment shader + // if needed + sg_pipeline_desc pip_desc; + _sspine_clear(&pip_desc, sizeof(pip_desc)); + pip_desc.shader = _sspine.shd; + pip_desc.layout.buffers[0].stride = sizeof(_sspine_vertex_t); + pip_desc.layout.attrs[0].format = SG_VERTEXFORMAT_FLOAT2; + pip_desc.layout.attrs[1].format = SG_VERTEXFORMAT_FLOAT2; + pip_desc.layout.attrs[2].format = SG_VERTEXFORMAT_UBYTE4N; + pip_desc.index_type = SG_INDEXTYPE_UINT32; + pip_desc.sample_count = desc->sample_count; + pip_desc.depth.pixel_format = desc->depth_format; + pip_desc.colors[0].pixel_format = desc->color_format; + pip_desc.colors[0].write_mask = desc->color_write_mask; + pip_desc.colors[0].blend.enabled = true; + pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_ONE; + 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_ONE_MINUS_SRC_ALPHA; + pip_desc.label = "sspine-pip-normal/additive"; + ctx->pip.normal_additive = sg_make_pipeline(&pip_desc); + + pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_ZERO; + pip_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_SRC_COLOR; + pip_desc.colors[0].blend.src_factor_alpha = SG_BLENDFACTOR_ZERO; + pip_desc.colors[0].blend.dst_factor_alpha = SG_BLENDFACTOR_ONE; + pip_desc.label = "sspine-pip-multiply"; + ctx->pip.multiply = sg_make_pipeline(&pip_desc); + + return SSPINE_RESOURCESTATE_VALID; +} + +static void _sspine_deinit_context(_sspine_context_t* ctx) { + // NOTE: it's ok to call sg_destroy functions with invalid handles + sg_destroy_pipeline(ctx->pip.normal_additive); + sg_destroy_pipeline(ctx->pip.multiply); + sg_destroy_buffer(ctx->ibuf); + sg_destroy_buffer(ctx->vbuf); + if (ctx->commands.ptr) { + _sspine_free(ctx->commands.ptr); + ctx->commands.ptr = 0; + } + if (ctx->indices.ptr) { + _sspine_free(ctx->indices.ptr); + ctx->indices.ptr = 0; + } + if (ctx->vertices.ptr) { + _sspine_free(ctx->vertices.ptr); + ctx->vertices.ptr = 0; + } +} + +static void _sspine_destroy_context(sspine_context ctx_id) { + _sspine_context_t* ctx = _sspine_lookup_context(ctx_id.id); + if (ctx) { + _sspine_deinit_context(ctx); + _sspine_context_pool_t* p = &_sspine.context_pool; + _sspine_clear(ctx, sizeof(_sspine_context_t)); + _sspine_pool_free_index(&p->pool, _sspine_slot_index(ctx_id.id)); + } +} + +static void _sspine_destroy_all_contexts(void) { + _sspine_context_pool_t* p = &_sspine.context_pool; + for (int i = 0; i < p->pool.size; i++) { + _sspine_context_t* ctx = &p->items[i]; + _sspine_destroy_context(_sspine_make_context_handle(ctx->slot.id)); + } +} + +static sspine_context_desc _sspine_context_desc_defaults(const sspine_context_desc* desc) { + sspine_context_desc res = *desc; + res.max_vertices = _sspine_def(desc->max_vertices, _SSPINE_DEFAULT_MAX_VERTICES); + res.max_commands = _sspine_def(desc->max_commands, _SSPINE_DEFAULT_MAX_COMMANDS); + return res; +} + +static bool _sspine_is_default_context(sspine_context ctx_id) { + return ctx_id.id == 0x00010001; +} + +// █████ ████████ ██ █████ ███████ +// ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ███████ ███████ +// ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ███████ ██ ██ ███████ +// +// >>atlas +static void _sspine_setup_atlas_pool(int pool_size) { + _sspine_atlas_pool_t* p = &_sspine.atlas_pool; + _sspine_init_item_pool(&p->pool, pool_size, (void**)&p->items, sizeof(_sspine_atlas_t)); +} + +static void _sspine_discard_atlas_pool(void) { + _sspine_atlas_pool_t* p = &_sspine.atlas_pool; + _sspine_discard_item_pool(&p->pool, (void**)&p->items); +} + +static sspine_atlas _sspine_make_atlas_handle(uint32_t id) { + sspine_atlas handle = { id }; + return handle; +} + +static _sspine_atlas_t* _sspine_atlas_at(uint32_t id) { + SOKOL_ASSERT(SSPINE_INVALID_ID != id); + const _sspine_atlas_pool_t* p = &_sspine.atlas_pool; + int slot_index = _sspine_slot_index(id); + SOKOL_ASSERT((slot_index > _SSPINE_INVALID_SLOT_INDEX) && (slot_index < p->pool.size)); + return &p->items[slot_index]; +} + +static _sspine_atlas_t* _sspine_lookup_atlas(uint32_t id) { + if (SSPINE_INVALID_ID != id) { + _sspine_atlas_t* atlas = _sspine_atlas_at(id); + if (atlas->slot.id == id) { + return atlas; + } + } + return 0; +} + +static sspine_atlas _sspine_alloc_atlas(void) { + _sspine_atlas_pool_t* p = &_sspine.atlas_pool; + int slot_index = _sspine_pool_alloc_index(&p->pool); + if (_SSPINE_INVALID_SLOT_INDEX != slot_index) { + uint32_t id = _sspine_slot_init(&p->pool, &p->items[slot_index].slot, slot_index); + return _sspine_make_atlas_handle(id); + } else { + // pool exhausted + return _sspine_make_atlas_handle(SSPINE_INVALID_ID); + } +} + +static const _sspine_image_sampler_pair_t* _sspine_new_image_sampler_pair(sg_image img, sg_sampler smp) { + _sspine_image_sampler_pair_t* isp = (_sspine_image_sampler_pair_t*) _sspine_malloc_clear(sizeof(_sspine_image_sampler_pair_t)); + SOKOL_ASSERT(isp); + isp->img = img; + isp->smp = smp; + return isp; +} + +static void _sspine_delete_image_sampler_pair(const _sspine_image_sampler_pair_t* isp) { + if (isp) { + sg_destroy_sampler(isp->smp); + sg_destroy_image(isp->img); + _sspine_free((void*)isp); + } +} + +static sg_image _sspine_image_from_renderer_object(void* renderer_object) { + SOKOL_ASSERT(renderer_object); + const _sspine_image_sampler_pair_t* isp = (const _sspine_image_sampler_pair_t*)renderer_object; + return isp->img; +} + +static sg_sampler _sspine_sampler_from_renderer_object(void* renderer_object) { + SOKOL_ASSERT(renderer_object); + const _sspine_image_sampler_pair_t* isp = (const _sspine_image_sampler_pair_t*)renderer_object; + return isp->smp; +} + +static sspine_resource_state _sspine_init_atlas(_sspine_atlas_t* atlas, const sspine_atlas_desc* desc) { + SOKOL_ASSERT(atlas && (atlas->slot.state == SSPINE_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + SOKOL_ASSERT(atlas->sp_atlas == 0); + + if ((0 == desc->data.ptr) || (0 == desc->data.size)) { + _SSPINE_ERROR(ATLAS_DESC_NO_DATA); + return SSPINE_RESOURCESTATE_FAILED; + } + atlas->overrides = desc->override; + + // NOTE: Spine doesn't detect when invalid or corrupt data is passed here, + // not much we can do about this... + atlas->sp_atlas = spAtlas_create((const char*)desc->data.ptr, (int)desc->data.size, "", 0); + if (0 == atlas->sp_atlas) { + _SSPINE_ERROR(SPINE_ATLAS_CREATION_FAILED); + return SSPINE_RESOURCESTATE_FAILED; + } + + // allocate a sokol-gfx image and sampler object for each page, but the actual + // initialization needs to be delegated to the application + for (spAtlasPage* page = atlas->sp_atlas->pages; page != 0; page = page->next) { + atlas->num_pages++; + const sg_image img = sg_alloc_image(); + if (sg_query_image_state(img) != SG_RESOURCESTATE_ALLOC) { + _SSPINE_ERROR(SG_ALLOC_IMAGE_FAILED); + return SSPINE_RESOURCESTATE_FAILED; + } + const sg_sampler smp = sg_alloc_sampler(); + if (sg_query_sampler_state(smp) != SG_RESOURCESTATE_ALLOC) { + _SSPINE_ERROR(SG_ALLOC_SAMPLER_FAILED); + return SSPINE_RESOURCESTATE_FAILED; + } + + // need to put the image and sampler handle into a heap-alloc unfortunately, + // because a void* isn't big enough to stash two 32-bit handles into + // it directly on platforms with 32-bit pointers (like wasm) + page->rendererObject = (void*) _sspine_new_image_sampler_pair(img, smp); + + if (desc->override.premul_alpha_enabled) { + // NOTE: -1 is spine-c convention for 'true' + page->pma = -1; + } else if (desc->override.premul_alpha_disabled) { + page->pma = 0; + } + } + return SSPINE_RESOURCESTATE_VALID; +} + +static void _sspine_deinit_atlas(_sspine_atlas_t* atlas) { + if (atlas->sp_atlas) { + spAtlas_dispose(atlas->sp_atlas); + atlas->sp_atlas = 0; + } +} + +static void _sspine_destroy_atlas(sspine_atlas atlas_id) { + _sspine_atlas_t* atlas = _sspine_lookup_atlas(atlas_id.id); + if (atlas) { + _sspine_deinit_atlas(atlas); + _sspine_atlas_pool_t* p = &_sspine.atlas_pool; + _sspine_clear(atlas, sizeof(_sspine_atlas_t)); + _sspine_pool_free_index(&p->pool, _sspine_slot_index(atlas_id.id)); + } +} + +static void _sspine_destroy_all_atlases(void) { + _sspine_atlas_pool_t* p = &_sspine.atlas_pool; + for (int i = 0; i < p->pool.size; i++) { + _sspine_atlas_t* atlas = &p->items[i]; + _sspine_destroy_atlas(_sspine_make_atlas_handle(atlas->slot.id)); + } +} + +static sspine_atlas_desc _sspine_atlas_desc_defaults(const sspine_atlas_desc* desc) { + sspine_atlas_desc res = *desc; + return res; +} + +static spAtlasPage* _sspine_lookup_atlas_page(uint32_t atlas_id, int page_index) { + _sspine_atlas_t* atlas = _sspine_lookup_atlas(atlas_id); + if (atlas) { + if ((page_index >= 0) && (page_index < atlas->num_pages)) { + int i = 0; + for (spAtlasPage* page = atlas->sp_atlas->pages; page != 0; page = page->next, i++) { + if (i == page_index) { + return page; + } + } + } + } + return 0; +} + +// ███████ ██ ██ ███████ ██ ███████ ████████ ██████ ███ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ +// ███████ █████ █████ ██ █████ ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ███████ ███████ ███████ ██ ██████ ██ ████ +// +// >>skeleton +static void _sspine_setup_skeleton_pool(int pool_size) { + _sspine_skeleton_pool_t* p = &_sspine.skeleton_pool; + _sspine_init_item_pool(&p->pool, pool_size, (void**)&p->items, sizeof(_sspine_skeleton_t)); +} + +static void _sspine_discard_skeleton_pool(void) { + _sspine_skeleton_pool_t* p = &_sspine.skeleton_pool; + _sspine_discard_item_pool(&p->pool, (void**)&p->items); +} + +static sspine_skeleton _sspine_make_skeleton_handle(uint32_t id) { + sspine_skeleton handle = { id }; + return handle; +} + +static _sspine_skeleton_t* _sspine_skeleton_at(uint32_t id) { + SOKOL_ASSERT(SSPINE_INVALID_ID != id); + const _sspine_skeleton_pool_t* p = &_sspine.skeleton_pool; + int slot_index = _sspine_slot_index(id); + SOKOL_ASSERT((slot_index > _SSPINE_INVALID_SLOT_INDEX) && (slot_index < p->pool.size)); + return &p->items[slot_index]; +} + +static _sspine_skeleton_t* _sspine_lookup_skeleton(uint32_t id) { + if (SSPINE_INVALID_ID != id) { + _sspine_skeleton_t* skeleton = _sspine_skeleton_at(id); + if (skeleton->slot.id == id) { + return skeleton; + } + } + return 0; +} + +static sspine_skeleton _sspine_alloc_skeleton(void) { + _sspine_skeleton_pool_t* p = &_sspine.skeleton_pool; + int slot_index = _sspine_pool_alloc_index(&p->pool); + if (_SSPINE_INVALID_SLOT_INDEX != slot_index) { + uint32_t id = _sspine_slot_init(&p->pool, &p->items[slot_index].slot, slot_index); + return _sspine_make_skeleton_handle(id); + } else { + // pool exhausted + return _sspine_make_skeleton_handle(SSPINE_INVALID_ID); + } +} + +static sspine_resource_state _sspine_init_skeleton(_sspine_skeleton_t* skeleton, const sspine_skeleton_desc* desc) { + SOKOL_ASSERT(skeleton && (skeleton->slot.state == SSPINE_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + + if ((0 == desc->json_data) && ((0 == desc->binary_data.ptr) || (0 == desc->binary_data.size))) { + _SSPINE_ERROR(SKELETON_DESC_NO_DATA); + return SSPINE_RESOURCESTATE_FAILED; + } + if (desc->atlas.id == SSPINE_INVALID_ID) { + _SSPINE_ERROR(SKELETON_DESC_NO_ATLAS); + return SSPINE_RESOURCESTATE_FAILED; + } + + skeleton->atlas.id = desc->atlas.id; + skeleton->atlas.ptr = _sspine_lookup_atlas(skeleton->atlas.id); + if (!_sspine_atlas_ref_valid(&skeleton->atlas)) { + _SSPINE_ERROR(SKELETON_ATLAS_NOT_VALID); + return SSPINE_RESOURCESTATE_FAILED; + } + _sspine_atlas_t* atlas = skeleton->atlas.ptr; + if (SSPINE_RESOURCESTATE_VALID != atlas->slot.state) { + _SSPINE_ERROR(SKELETON_ATLAS_NOT_VALID); + return SSPINE_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT(atlas->sp_atlas); + + if (desc->json_data) { + spSkeletonJson* skel_json = spSkeletonJson_create(atlas->sp_atlas); + SOKOL_ASSERT(skel_json); + skel_json->scale = desc->prescale; + skeleton->sp_skel_data = spSkeletonJson_readSkeletonData(skel_json, desc->json_data); + spSkeletonJson_dispose(skel_json); skel_json = 0; + if (0 == skeleton->sp_skel_data) { + _SSPINE_ERROR(CREATE_SKELETON_DATA_FROM_JSON_FAILED); + return SSPINE_RESOURCESTATE_FAILED; + } + } else { + spSkeletonBinary* skel_bin = spSkeletonBinary_create(atlas->sp_atlas); + SOKOL_ASSERT(skel_bin); + skel_bin->scale = desc->prescale; + skeleton->sp_skel_data = spSkeletonBinary_readSkeletonData(skel_bin, (const unsigned char*)desc->binary_data.ptr, (int)desc->binary_data.size); + spSkeletonBinary_dispose(skel_bin); skel_bin = 0; + if (0 == skeleton->sp_skel_data) { + _SSPINE_ERROR(CREATE_SKELETON_DATA_FROM_BINARY_FAILED); + return SSPINE_RESOURCESTATE_FAILED; + } + } + SOKOL_ASSERT(skeleton->sp_skel_data); + + skeleton->sp_anim_data = spAnimationStateData_create(skeleton->sp_skel_data); + SOKOL_ASSERT(skeleton->sp_anim_data); + skeleton->sp_anim_data->defaultMix = desc->anim_default_mix; + + // get the max number of vertices in any mesh attachment + int max_vertex_count = 4; // number of vertices in a 'region attachment' (a 2-triangle quad) + const spSkeletonData* sp_skel_data = skeleton->sp_skel_data; + for (int skinIndex = 0; skinIndex < sp_skel_data->skinsCount; skinIndex++) { + const spSkin* sp_skin = sp_skel_data->skins[skinIndex]; + const spSkinEntry* skin_entry = spSkin_getAttachments(sp_skin); + if (skin_entry) do { + if (skin_entry->attachment) { + if (skin_entry->attachment->type == SP_ATTACHMENT_MESH) { + const spMeshAttachment* mesh_attachment = (spMeshAttachment*)skin_entry->attachment; + // worldVerticesLength is number of floats + SOKOL_ASSERT((mesh_attachment->super.worldVerticesLength & 1) == 0); + const int num_vertices = mesh_attachment->super.worldVerticesLength / 2; + if (num_vertices > max_vertex_count) { + max_vertex_count = num_vertices; + } + } + } + } while ((skin_entry = skin_entry->next) != 0); + } + + // allocate a shared vertex transform buffer (big enough to hold vertices for biggest mesh attachment) + skeleton->tform_buf.cap = max_vertex_count; + skeleton->tform_buf.ptr = (sspine_vec2*) _sspine_malloc((size_t)skeleton->tform_buf.cap * sizeof(sspine_vec2)); + + return SSPINE_RESOURCESTATE_VALID; +} + +static void _sspine_deinit_skeleton(_sspine_skeleton_t* skeleton) { + if (skeleton->tform_buf.ptr) { + _sspine_free(skeleton->tform_buf.ptr); + skeleton->tform_buf.ptr = 0; + } + if (skeleton->sp_anim_data) { + spAnimationStateData_dispose(skeleton->sp_anim_data); + skeleton->sp_anim_data = 0; + } + if (skeleton->sp_skel_data) { + spSkeletonData_dispose(skeleton->sp_skel_data); + skeleton->sp_skel_data = 0; + } +} + +static void _sspine_destroy_skeleton(sspine_skeleton skeleton_id) { + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (skeleton) { + _sspine_deinit_skeleton(skeleton); + _sspine_skeleton_pool_t* p = &_sspine.skeleton_pool; + _sspine_clear(skeleton, sizeof(_sspine_skeleton_t)); + _sspine_pool_free_index(&p->pool, _sspine_slot_index(skeleton_id.id)); + } +} + +static void _sspine_destroy_all_skeletons(void) { + _sspine_skeleton_pool_t* p = &_sspine.skeleton_pool; + for (int i = 0; i < p->pool.size; i++) { + _sspine_skeleton_t* skeleton = &p->items[i]; + _sspine_destroy_skeleton(_sspine_make_skeleton_handle(skeleton->slot.id)); + } +} + +static sspine_skeleton_desc _sspine_skeleton_desc_defaults(const sspine_skeleton_desc* desc) { + sspine_skeleton_desc res = *desc; + res.prescale = _sspine_def(desc->prescale, 1.0f); + res.anim_default_mix = _sspine_def(desc->anim_default_mix, 0.2f); + return res; +} + +static spBoneData* _sspine_lookup_bone_data(uint32_t skeleton_id, int bone_index) { + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data && skeleton->sp_skel_data->bones); + if ((bone_index >= 0) && (bone_index <= skeleton->sp_skel_data->bonesCount)) { + return skeleton->sp_skel_data->bones[bone_index]; + } + } + return 0; +} + +static spSlotData* _sspine_lookup_slot_data(uint32_t skeleton_id, int slot_index) { + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data && skeleton->sp_skel_data->slots); + if ((slot_index >= 0) && (slot_index <= skeleton->sp_skel_data->slotsCount)) { + return skeleton->sp_skel_data->slots[slot_index]; + } + } + return 0; +} + +static spEventData* _sspine_lookup_event_data(uint32_t skeleton_id, int event_index) { + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data && skeleton->sp_skel_data->events); + if ((event_index >= 0) && (event_index < skeleton->sp_skel_data->eventsCount)) { + return skeleton->sp_skel_data->events[event_index]; + } + } + return 0; +} + +static spIkConstraintData* _sspine_lookup_ikconstraint_data(uint32_t skeleton_id, int iktarget_index) { + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data && skeleton->sp_skel_data->ikConstraints); + if ((iktarget_index >= 0) && (iktarget_index < skeleton->sp_skel_data->ikConstraintsCount)) { + return skeleton->sp_skel_data->ikConstraints[iktarget_index]; + } + } + return 0; +} + +static spSkin* _sspine_lookup_skin(uint32_t skeleton_id, int skin_index) { + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data && skeleton->sp_skel_data->skins); + if ((skin_index >= 0) && (skin_index < skeleton->sp_skel_data->skinsCount)) { + return skeleton->sp_skel_data->skins[skin_index]; + } + } + return 0; +} + +// ███████ ██ ██ ██ ███ ██ ███████ ███████ ████████ +// ██ ██ ██ ██ ████ ██ ██ ██ ██ +// ███████ █████ ██ ██ ██ ██ ███████ █████ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ███████ ██ ██ ██ ██ ████ ███████ ███████ ██ +// +// >>skinset +static void _sspine_setup_skinset_pool(int pool_size) { + _sspine_skinset_pool_t* p = &_sspine.skinset_pool; + _sspine_init_item_pool(&p->pool, pool_size, (void**)&p->items, sizeof(_sspine_skinset_t)); +} + +static void _sspine_discard_skinset_pool(void) { + _sspine_skinset_pool_t* p = &_sspine.skinset_pool; + _sspine_discard_item_pool(&p->pool, (void**)&p->items); +} + +static sspine_skinset _sspine_make_skinset_handle(uint32_t id) { + sspine_skinset handle = { id }; + return handle; +} + +static _sspine_skinset_t* _sspine_skinset_at(uint32_t id) { + SOKOL_ASSERT(SSPINE_INVALID_ID != id); + const _sspine_skinset_pool_t* p = &_sspine.skinset_pool; + int slot_index = _sspine_slot_index(id); + SOKOL_ASSERT((slot_index > _SSPINE_INVALID_SLOT_INDEX) && (slot_index < p->pool.size)); + return &p->items[slot_index]; +} + +static _sspine_skinset_t* _sspine_lookup_skinset(uint32_t id) { + if (SSPINE_INVALID_ID != id) { + _sspine_skinset_t* skinset = _sspine_skinset_at(id); + if (skinset->slot.id == id) { + return skinset; + } + } + return 0; +} + +static sspine_skinset _sspine_alloc_skinset(void) { + _sspine_skinset_pool_t* p = &_sspine.skinset_pool; + int slot_index = _sspine_pool_alloc_index(&p->pool); + if (_SSPINE_INVALID_SLOT_INDEX != slot_index) { + uint32_t id = _sspine_slot_init(&p->pool, &p->items[slot_index].slot, slot_index); + return _sspine_make_skinset_handle(id); + } else { + // pool exhausted + return _sspine_make_skinset_handle(SSPINE_INVALID_ID); + } +} + +static sspine_resource_state _sspine_init_skinset(_sspine_skinset_t* skinset, const sspine_skinset_desc* desc) { + SOKOL_ASSERT(skinset && (skinset->slot.state == SSPINE_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + + if (desc->skeleton.id == SSPINE_INVALID_ID) { + _SSPINE_ERROR(SKINSET_DESC_NO_SKELETON); + return SSPINE_RESOURCESTATE_FAILED; + } + skinset->skel.id = desc->skeleton.id; + skinset->skel.ptr = _sspine_lookup_skeleton(desc->skeleton.id); + if (!_sspine_skeleton_ref_valid(&skinset->skel)) { + _SSPINE_ERROR(SKINSET_SKELETON_NOT_VALID); + return SSPINE_RESOURCESTATE_FAILED; + } + _sspine_skeleton_t* skel = skinset->skel.ptr; + if (SSPINE_RESOURCESTATE_VALID != skel->slot.state) { + _SSPINE_ERROR(SKINSET_SKELETON_NOT_VALID); + return SSPINE_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT(skel->sp_skel_data); + skinset->sp_skin = spSkin_create("skinset"); + for (int i = 0; i < SSPINE_MAX_SKINSET_SKINS; i++) { + if (desc->skins[i].skeleton_id != SSPINE_INVALID_ID) { + spSkin* skin = _sspine_lookup_skin(desc->skins[i].skeleton_id, desc->skins[i].index); + if (0 == skin) { + _SSPINE_ERROR(SKINSET_INVALID_SKIN_HANDLE); + return SSPINE_RESOURCESTATE_FAILED; + } + spSkin_addSkin(skinset->sp_skin, skin); + } + } + return SSPINE_RESOURCESTATE_VALID; +} + +static void _sspine_deinit_skinset(_sspine_skinset_t* skinset) { + if (skinset->sp_skin) { + spSkin_clear(skinset->sp_skin); + spSkin_dispose(skinset->sp_skin); + skinset->sp_skin = 0; + } +} + +static void _sspine_destroy_skinset(sspine_skinset skinset_id) { + _sspine_skinset_t* skinset = _sspine_lookup_skinset(skinset_id.id); + if (skinset) { + _sspine_deinit_skinset(skinset); + _sspine_skinset_pool_t* p = &_sspine.skinset_pool; + _sspine_clear(skinset, sizeof(_sspine_skinset_t)); + _sspine_pool_free_index(&p->pool, _sspine_slot_index(skinset_id.id)); + } +} + +static void _sspine_destroy_all_skinsets(void) { + _sspine_skinset_pool_t* p = &_sspine.skinset_pool; + for (int i = 0; i < p->pool.size; i++) { + _sspine_skinset_t* skinset = &p->items[i]; + _sspine_destroy_skinset(_sspine_make_skinset_handle(skinset->slot.id)); + } +} + +static sspine_skinset_desc _sspine_skinset_desc_defaults(const sspine_skinset_desc* desc) { + sspine_skinset_desc res = *desc; + return res; +} + +// ██ ███ ██ ███████ ████████ █████ ███ ██ ██████ ███████ +// ██ ████ ██ ██ ██ ██ ██ ████ ██ ██ ██ +// ██ ██ ██ ██ ███████ ██ ███████ ██ ██ ██ ██ █████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ████ ███████ ██ ██ ██ ██ ████ ██████ ███████ +// +// >>instance +static void _sspine_setup_instance_pool(int pool_size) { + _sspine_instance_pool_t* p = &_sspine.instance_pool; + _sspine_init_item_pool(&p->pool, pool_size, (void**)&p->items, sizeof(_sspine_instance_t)); +} + +static void _sspine_discard_instance_pool(void) { + _sspine_instance_pool_t* p = &_sspine.instance_pool; + _sspine_discard_item_pool(&p->pool, (void**)&p->items); +} + +static sspine_instance _sspine_make_instance_handle(uint32_t id) { + sspine_instance handle = { id }; + return handle; +} + +static _sspine_instance_t* _sspine_instance_at(uint32_t id) { + SOKOL_ASSERT(SSPINE_INVALID_ID != id); + const _sspine_instance_pool_t* p = &_sspine.instance_pool; + int slot_index = _sspine_slot_index(id); + SOKOL_ASSERT((slot_index > _SSPINE_INVALID_SLOT_INDEX) && (slot_index < p->pool.size)); + return &p->items[slot_index]; +} + +static _sspine_instance_t* _sspine_lookup_instance(uint32_t id) { + if (SSPINE_INVALID_ID != id) { + _sspine_instance_t* instance = _sspine_instance_at(id); + if (instance->slot.id == id) { + return instance; + } + } + return 0; +} + +static sspine_instance _sspine_alloc_instance(void) { + _sspine_instance_pool_t* p = &_sspine.instance_pool; + sspine_instance res; + int slot_index = _sspine_pool_alloc_index(&p->pool); + if (_SSPINE_INVALID_SLOT_INDEX != slot_index) { + uint32_t id = _sspine_slot_init(&p->pool, &p->items[slot_index].slot, slot_index); + res = _sspine_make_instance_handle(id); + } else { + // pool exhausted + res = _sspine_make_instance_handle(SSPINE_INVALID_ID); + } + return res; +} + +static void _sspine_rewind_triggered_events(_sspine_instance_t* instance) { + instance->cur_triggered_event_index = 0; + _sspine_clear(instance->triggered_events, sizeof(instance->triggered_events)); +} + +static sspine_triggered_event_info* _sspine_next_triggered_event_info(_sspine_instance_t* instance) { + if (instance->cur_triggered_event_index < _SSPINE_MAX_TRIGGERED_EVENTS) { + return &instance->triggered_events[instance->cur_triggered_event_index++]; + } else { + return 0; + } +} + +static void _sspine_event_listener(spAnimationState* sp_anim_state, spEventType sp_event_type, spTrackEntry* sp_track_entry, spEvent* sp_event) { + if (sp_event_type == SP_ANIMATION_EVENT) { + SOKOL_ASSERT(sp_anim_state && sp_track_entry && sp_event); (void)sp_track_entry; + SOKOL_ASSERT(sp_event->data && sp_event->data->name); + _sspine_instance_t* instance = _sspine_lookup_instance((uint32_t)(uintptr_t)sp_anim_state->userData); + if (_sspine_instance_and_deps_valid(instance)) { + sspine_triggered_event_info* info = _sspine_next_triggered_event_info(instance); + if (info) { + // FIXME: this sucks, but we really need the event index + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(instance->skel.id); + SOKOL_ASSERT(skeleton && skeleton->sp_skel_data->events); + const spEventData* sp_event_data = sp_event->data; + for (int i = 0; i < skeleton->sp_skel_data->eventsCount; i++) { + if (sp_event_data == skeleton->sp_skel_data->events[i]) { + info->event = _sspine_event(skeleton->slot.id, i); + break; + } + } + SOKOL_ASSERT(info->event.skeleton_id != SSPINE_INVALID_ID); + info->valid = true; + info->time = sp_event->time; + info->int_value = sp_event->intValue; + info->float_value = sp_event->floatValue; + info->volume = sp_event->volume; + info->balance = sp_event->balance; + info->string_value = _sspine_string(sp_event->stringValue); + if (info->string_value.truncated) { + _SSPINE_WARN(STRING_TRUNCATED); + } + } + } + } +} + +static sspine_resource_state _sspine_init_instance(_sspine_instance_t* instance, const sspine_instance_desc* desc) { + SOKOL_ASSERT(instance && (instance->slot.state == SSPINE_RESOURCESTATE_ALLOC)); + SOKOL_ASSERT(desc); + + if (desc->skeleton.id == SSPINE_INVALID_ID) { + _SSPINE_ERROR(INSTANCE_DESC_NO_SKELETON); + return SSPINE_RESOURCESTATE_FAILED; + } + instance->skel.id = desc->skeleton.id; + instance->skel.ptr = _sspine_lookup_skeleton(instance->skel.id); + if (!_sspine_skeleton_ref_valid(&instance->skel)) { + _SSPINE_ERROR(INSTANCE_SKELETON_NOT_VALID); + return SSPINE_RESOURCESTATE_FAILED; + } + _sspine_skeleton_t* skel = instance->skel.ptr; + if (SSPINE_RESOURCESTATE_VALID != skel->slot.state) { + _SSPINE_ERROR(INSTANCE_SKELETON_NOT_VALID); + return SSPINE_RESOURCESTATE_FAILED; + } + instance->atlas = skel->atlas; + if (!_sspine_atlas_ref_valid(&instance->atlas)) { + _SSPINE_ERROR(INSTANCE_ATLAS_NOT_VALID); + return SSPINE_RESOURCESTATE_FAILED; + } + if (SSPINE_RESOURCESTATE_VALID != instance->atlas.ptr->slot.state) { + _SSPINE_ERROR(INSTANCE_ATLAS_NOT_VALID); + return SSPINE_RESOURCESTATE_FAILED; + } + SOKOL_ASSERT(skel->sp_skel_data); + SOKOL_ASSERT(skel->sp_anim_data); + + instance->sp_skel = spSkeleton_create(skel->sp_skel_data); + if (0 == instance->sp_skel) { + _SSPINE_ERROR(SPINE_SKELETON_CREATION_FAILED); + return SSPINE_RESOURCESTATE_FAILED; + } + instance->sp_anim_state = spAnimationState_create(skel->sp_anim_data); + if (0 == instance->sp_anim_state) { + _SSPINE_ERROR(SPINE_ANIMATIONSTATE_CREATION_FAILED); + return SSPINE_RESOURCESTATE_FAILED; + } + instance->sp_clip = spSkeletonClipping_create(); + if (0 == instance->sp_clip) { + _SSPINE_ERROR(SPINE_SKELETONCLIPPING_CREATION_FAILED); + return SSPINE_RESOURCESTATE_FAILED; + } + + instance->sp_anim_state->userData = (void*)(uintptr_t)instance->slot.id; + instance->sp_anim_state->listener = _sspine_event_listener; + + spSkeleton_setToSetupPose(instance->sp_skel); + spAnimationState_update(instance->sp_anim_state, 0.0f); + spAnimationState_apply(instance->sp_anim_state, instance->sp_skel); + spSkeleton_updateWorldTransform(instance->sp_skel); + + return SSPINE_RESOURCESTATE_VALID; +} + +static void _sspine_deinit_instance(_sspine_instance_t* instance) { + if (instance->sp_clip) { + spSkeletonClipping_dispose(instance->sp_clip); + instance->sp_clip = 0; + } + if (instance->sp_anim_state) { + spAnimationState_dispose(instance->sp_anim_state); + instance->sp_anim_state = 0; + } + if (instance->sp_skel) { + spSkeleton_dispose(instance->sp_skel); + instance->sp_skel = 0; + } +} + +static void _sspine_destroy_instance(sspine_instance instance_id) { + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (instance) { + _sspine_deinit_instance(instance); + _sspine_instance_pool_t* p = &_sspine.instance_pool; + _sspine_clear(instance, sizeof(_sspine_instance_t)); + _sspine_pool_free_index(&p->pool, _sspine_slot_index(instance_id.id)); + } +} + +static void _sspine_destroy_all_instances(void) { + _sspine_instance_pool_t* p = &_sspine.instance_pool; + for (int i = 0; i < p->pool.size; i++) { + _sspine_instance_t* instance = &p->items[i]; + _sspine_destroy_instance(_sspine_make_instance_handle(instance->slot.id)); + } +} + +static spAnimation* _sspine_lookup_skeleton_anim(uint32_t skeleton_id, int anim_index) { + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + if ((anim_index >= 0) && (anim_index < skeleton->sp_skel_data->animationsCount)) { + return skeleton->sp_skel_data->animations[anim_index]; + } + } + return 0; +} + +static spAnimation* _sspine_lookup_instance_anim(uint32_t instance_id, uint32_t skeleton_id, int anim_index) { + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id); + if (_sspine_instance_and_deps_valid(instance) && (instance->skel.id == skeleton_id)) { + SOKOL_ASSERT(instance->sp_skel && instance->sp_skel->data); + if ((anim_index >= 0) && (anim_index < instance->sp_skel->data->animationsCount)) { + return instance->sp_skel->data->animations[anim_index]; + } + } + return 0; +} + +static spBone* _sspine_lookup_bone(uint32_t instance_id, uint32_t skeleton_id, int bone_index) { + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id); + if (_sspine_instance_and_deps_valid(instance) && (instance->skel.id == skeleton_id)) { + SOKOL_ASSERT(instance->sp_skel && instance->sp_skel->bones); + if ((bone_index >= 0) && (bone_index <= instance->sp_skel->bonesCount)) { + return instance->sp_skel->bones[bone_index]; + } + } + return 0; +} + +static spSlot* _sspine_lookup_slot(uint32_t instance_id, uint32_t skeleton_id, int slot_index) { + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id); + if (_sspine_instance_and_deps_valid(instance) && (instance->skel.id == skeleton_id)) { + SOKOL_ASSERT(instance->sp_skel && instance->sp_skel->slots); + if ((slot_index >= 0) && (slot_index <= instance->sp_skel->slotsCount)) { + return instance->sp_skel->slots[slot_index]; + } + } + return 0; +} + +static spIkConstraint* _sspine_lookup_ikconstraint(uint32_t instance_id, uint32_t skeleton_id, int iktarget_index) { + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id); + if (_sspine_instance_and_deps_valid(instance) && (instance->skel.id == skeleton_id)) { + SOKOL_ASSERT(instance->sp_skel && instance->sp_skel->ikConstraints); + if ((iktarget_index >= 0) && (iktarget_index < instance->sp_skel->ikConstraintsCount)) { + return instance->sp_skel->ikConstraints[iktarget_index]; + } + } + return 0; +} + +static sspine_instance_desc _sspine_instance_desc_defaults(const sspine_instance_desc* desc) { + sspine_instance_desc res = *desc; + return res; +} + +// ██████ ██████ █████ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██ ██████ ███████ ██ █ ██ +// ██ ██ ██ ██ ██ ██ ██ ███ ██ +// ██████ ██ ██ ██ ██ ███ ███ +// +// >>draw +static void _sspine_check_rewind_commands(_sspine_context_t* ctx) { + if (_sspine.frame_id != ctx->commands.rewind_frame_id) { + ctx->commands.next = 0; + ctx->commands.rewind_frame_id = _sspine.frame_id; + } +} + +static _sspine_command_t* _sspine_next_command(_sspine_context_t* ctx) { + _sspine_check_rewind_commands(ctx); + if (ctx->commands.next < ctx->commands.cap) { + return &(ctx->commands.ptr[ctx->commands.next++]); + } else { + _SSPINE_ERROR(COMMAND_BUFFER_FULL); + return 0; + } +} + +static _sspine_command_t* _sspine_cur_command(_sspine_context_t* ctx) { + _sspine_check_rewind_commands(ctx); + if (ctx->commands.next > 0) { + return &ctx->commands.ptr[ctx->commands.next - 1]; + } else { + return 0; + } +} + +static void _sspine_check_rewind_vertices(_sspine_context_t* ctx) { + if (_sspine.frame_id != ctx->vertices.rewind_frame_id) { + ctx->vertices.next = 0; + ctx->vertices.rewind_frame_id = _sspine.frame_id; + } +} + +static _sspine_alloc_vertices_result_t _sspine_alloc_vertices(_sspine_context_t* ctx, int num) { + _sspine_check_rewind_vertices(ctx); + _sspine_alloc_vertices_result_t res; + _sspine_clear(&res, sizeof(res)); + if ((ctx->vertices.next + num) <= ctx->vertices.cap) { + res.ptr = &(ctx->vertices.ptr[ctx->vertices.next]); + res.index = ctx->vertices.next; + ctx->vertices.next += num; + } else { + _SSPINE_ERROR(VERTEX_BUFFER_FULL); + } + return res; +} + +static void _sspine_check_rewind_indices(_sspine_context_t* ctx) { + if (_sspine.frame_id != ctx->indices.rewind_frame_id) { + ctx->indices.next = 0; + ctx->indices.rewind_frame_id = _sspine.frame_id; + } +} + +static _sspine_alloc_indices_result_t _sspine_alloc_indices(_sspine_context_t* ctx, int num) { + _sspine_check_rewind_indices(ctx); + _sspine_alloc_indices_result_t res; + _sspine_clear(&res, sizeof(res)); + if ((ctx->indices.next + num) <= ctx->indices.cap) { + res.ptr = &(ctx->indices.ptr[ctx->indices.next]); + res.index = ctx->indices.next; + ctx->indices.next += num; + } else { + _SSPINE_ERROR(INDEX_BUFFER_FULL); + } + return res; +} + +static void _sspine_draw_instance(_sspine_context_t* ctx, _sspine_instance_t* instance, int layer) { + SOKOL_ASSERT(_sspine_instance_and_deps_valid(instance)); + SOKOL_ASSERT(instance->sp_skel); + SOKOL_ASSERT(instance->sp_anim_state); + SOKOL_ASSERT(instance->sp_clip); + + // see: https://github.com/EsotericSoftware/spine-runtimes/blob/4.1/spine-sdl/src/spine-sdl-c.c + const spSkeleton* sp_skel = instance->sp_skel; + float* tform_buf = (float*)instance->skel.ptr->tform_buf.ptr; + const int max_tform_buf_verts = instance->skel.ptr->tform_buf.cap; + SOKOL_UNUSED(max_tform_buf_verts); // only used in asserts + const int tform_buf_stride = 2; // each element is 2 floats + spSkeletonClipping* sp_clip = instance->sp_clip; + for (int slot_index = 0; slot_index < sp_skel->slotsCount; slot_index++) { + spSlot* sp_slot = sp_skel->drawOrder[slot_index]; + if (!sp_slot->attachment) { + spSkeletonClipping_clipEnd(sp_clip, sp_slot); + continue; + } + + // early out if the slot alpha is 0 or the bone is not active + // FIXME: does alpha 0 actually mean 'invisible' for all blend modes? + if ((sp_slot->color.a == 0) || (!sp_slot->bone->active)) { + spSkeletonClipping_clipEnd(sp_clip, sp_slot); + continue; + } + + int num_vertices = 0; + float* uvs = 0; + float* vertices = 0; + int num_indices = 0; + const uint16_t* indices = 0; + const spColor* att_color = 0; + sg_image img = { SG_INVALID_ID }; + sg_sampler smp = { SG_INVALID_ID }; + bool premul_alpha = false; + if (sp_slot->attachment->type == SP_ATTACHMENT_REGION) { + static const uint16_t quad_indices[] = { 0, 1, 2, 2, 3, 0 }; + spRegionAttachment* region = (spRegionAttachment*)sp_slot->attachment; + att_color = ®ion->color; + // FIXME(?) early out if the slot alpha is 0 + if (att_color->a == 0) { + spSkeletonClipping_clipEnd(sp_clip, sp_slot); + continue; + } + spRegionAttachment_computeWorldVertices(region, sp_slot, tform_buf, 0, tform_buf_stride); + vertices = tform_buf; + num_vertices = 4; + indices = &quad_indices[0]; + num_indices = 6; + uvs = region->uvs; + const spAtlasPage* sp_page = ((spAtlasRegion*)region->rendererObject)->page; + img = _sspine_image_from_renderer_object(sp_page->rendererObject); + smp = _sspine_sampler_from_renderer_object(sp_page->rendererObject); + premul_alpha = sp_page->pma != 0; + } else if (sp_slot->attachment->type == SP_ATTACHMENT_MESH) { + spMeshAttachment* mesh = (spMeshAttachment*)sp_slot->attachment; + att_color = &mesh->color; + // FIXME(?) early out if the slot alpha is 0 + if (att_color->a == 0) { + spSkeletonClipping_clipEnd(sp_clip, sp_slot); + continue; + } + const int num_floats = mesh->super.worldVerticesLength; + num_vertices = num_floats / 2; + SOKOL_ASSERT(num_vertices <= max_tform_buf_verts); + spVertexAttachment_computeWorldVertices(&mesh->super, sp_slot, 0, num_floats, tform_buf, 0, tform_buf_stride); + vertices = tform_buf; + indices = mesh->triangles; + num_indices = mesh->trianglesCount; // actually indicesCount??? + uvs = mesh->uvs; + const spAtlasPage* sp_page = ((spAtlasRegion*)mesh->rendererObject)->page; + img = _sspine_image_from_renderer_object(sp_page->rendererObject); + smp = _sspine_sampler_from_renderer_object(sp_page->rendererObject); + premul_alpha = sp_page->pma != 0; + } else if (sp_slot->attachment->type == SP_ATTACHMENT_CLIPPING) { + spClippingAttachment* clip_attachment = (spClippingAttachment*) sp_slot->attachment; + spSkeletonClipping_clipStart(sp_clip, sp_slot, clip_attachment); + continue; + } else { + spSkeletonClipping_clipEnd(sp_clip, sp_slot); + continue; + } + SOKOL_ASSERT(vertices && (num_vertices > 0)); + SOKOL_ASSERT(indices && (num_indices > 0)); + SOKOL_ASSERT(uvs); + SOKOL_ASSERT(img.id != SG_INVALID_ID); + SOKOL_ASSERT(smp.id != SG_INVALID_ID); + + if (spSkeletonClipping_isClipping(sp_clip)) { + spSkeletonClipping_clipTriangles(sp_clip, tform_buf, num_vertices * 2, (uint16_t*)indices, num_indices, uvs, tform_buf_stride); + vertices = sp_clip->clippedVertices->items; + num_vertices = sp_clip->clippedVertices->size / 2; + uvs = sp_clip->clippedUVs->items; + indices = sp_clip->clippedTriangles->items; + num_indices = sp_clip->clippedTriangles->size; + } + SOKOL_ASSERT(vertices); + SOKOL_ASSERT(indices); + SOKOL_ASSERT(uvs); + + // there might be no geometry to render after clipping + if ((0 == num_vertices) || (0 == num_indices)) { + spSkeletonClipping_clipEnd(sp_clip, sp_slot); + continue; + } + + const _sspine_alloc_vertices_result_t dst_vertices = _sspine_alloc_vertices(ctx, num_vertices); + const _sspine_alloc_indices_result_t dst_indices = _sspine_alloc_indices(ctx, num_indices); + if ((0 == dst_vertices.ptr) || (0 == dst_indices.ptr)) { + spSkeletonClipping_clipEnd(sp_clip, sp_slot); + continue; + } + + // write transformed and potentially clipped vertices and indices + const uint8_t r = (uint8_t)(sp_skel->color.r * sp_slot->color.r * att_color->r * 255.0f); + const uint8_t g = (uint8_t)(sp_skel->color.g * sp_slot->color.g * att_color->g * 255.0f); + const uint8_t b = (uint8_t)(sp_skel->color.b * sp_slot->color.b * att_color->b * 255.0f); + const uint8_t a = (uint8_t)(sp_skel->color.a * sp_slot->color.a * att_color->a * 255.0f); + const uint32_t color = (((uint32_t)a<<24) | ((uint32_t)b<<16) | ((uint32_t)g<<8) | (uint32_t)r); + for (int vi = 0; vi < num_vertices; vi++) { + dst_vertices.ptr[vi].pos.x = vertices[vi*2]; + dst_vertices.ptr[vi].pos.y = vertices[vi*2 + 1]; + dst_vertices.ptr[vi].color = color; + dst_vertices.ptr[vi].uv.x = uvs[vi*2]; + dst_vertices.ptr[vi].uv.y = uvs[vi*2 + 1]; + } + for (int ii = 0; ii < num_indices; ii++) { + dst_indices.ptr[ii] = (uint32_t)indices[ii] + (uint32_t)dst_vertices.index; + } + + sg_pipeline pip = { SG_INVALID_ID }; + // NOTE: pma == 0.0: use color from texture as is + // pma == 1.0: multiply texture rgb by texture alpha in fragment shader + float pma = 0.0f; + switch (sp_slot->data->blendMode) { + case SP_BLEND_MODE_NORMAL: + case SP_BLEND_MODE_ADDITIVE: + case SP_BLEND_MODE_SCREEN: + pip = ctx->pip.normal_additive; + pma = premul_alpha ? 0.0f : 1.0f; // NOT A BUG + break; + case SP_BLEND_MODE_MULTIPLY: + pip = ctx->pip.multiply; + pma = 0.0f; // always use texture color as is + break; + } + + // write new draw command, or merge with current draw command + _sspine_command_t* cur_cmd = _sspine_cur_command(ctx); + if (cur_cmd + && (cur_cmd->layer == layer) + && (cur_cmd->pip.id == pip.id) + && (cur_cmd->img.id == img.id) + && (cur_cmd->smp.id == smp.id) + && (cur_cmd->pma == pma)) + { + // merge with current command + cur_cmd->num_elements += num_indices; + } else { + // record a new command + _sspine_command_t* cmd_ptr = _sspine_next_command(ctx); + if (cmd_ptr) { + cmd_ptr->layer = layer; + cmd_ptr->pip = pip; + cmd_ptr->img = img; + cmd_ptr->smp = smp; + cmd_ptr->pma = pma; + cmd_ptr->base_element = dst_indices.index; + cmd_ptr->num_elements = num_indices; + } + } + spSkeletonClipping_clipEnd(sp_clip, sp_slot); + } + spSkeletonClipping_clipEnd2(sp_clip); +} + +// compute orthographic projection matrix +static void _sspine_layer_transform_to_proj(const sspine_layer_transform* tform, float* res) { + const float left = -tform->origin.x; + const float right = tform->size.x - tform->origin.x; + const float top = -tform->origin.y; + const float bottom = tform->size.y - tform->origin.y; + const float znear = -1.0f; + const float zfar = 1.0f; + res[0] = 2.0f / (right - left); + res[1] = 0.0f; + res[2] = 0.0f; + res[3] = 0.0f; + res[4] = 0.0f; + res[5] = 2.0f / (top - bottom); + res[6] = 0.0f; + res[7] = 0.0f; + res[8] = 0.0f; + res[9] = 0.0f; + res[10] = -2.0f / (zfar - znear); + res[11] = 0.0f; + res[12] = -(right + left) / (right - left); + res[13] = -(top + bottom) / (top - bottom); + res[14] = -(zfar + znear) / (zfar - znear); + res[15] = 1.0f; +} + +static _sspine_vsparams_t _sspine_compute_vsparams(const sspine_layer_transform* tform) { + _sspine_vsparams_t p; + _sspine_clear(&p, sizeof(p)); + _sspine_layer_transform_to_proj(tform, p.mvp); + return p; +} + +static void _sspine_draw_layer(_sspine_context_t* ctx, int layer, const sspine_layer_transform* tform) { + if ((ctx->vertices.next > 0) && (ctx->commands.next > 0)) { + sg_push_debug_group("sokol-spine"); + + if (ctx->update_frame_id != _sspine.frame_id) { + ctx->update_frame_id = _sspine.frame_id; + const sg_range vtx_range = { ctx->vertices.ptr, (size_t)ctx->vertices.next * sizeof(_sspine_vertex_t) }; + sg_update_buffer(ctx->vbuf, &vtx_range); + const sg_range idx_range = { ctx->indices.ptr, (size_t)ctx->indices.next * sizeof(uint32_t) }; + sg_update_buffer(ctx->ibuf, &idx_range); + } + + _sspine_vsparams_t vsparams = _sspine_compute_vsparams(tform); + const sg_range vsparams_range = { &vsparams, sizeof(vsparams) }; + _sspine_fsparams_t fsparams; + _sspine_clear(&fsparams, sizeof(fsparams)); + const sg_range fsparams_range = { &fsparams, sizeof(fsparams) }; + + uint32_t cur_pip_id = SG_INVALID_ID; + uint32_t cur_img_id = SG_INVALID_ID; + uint32_t cur_smp_id = SG_INVALID_ID; + float cur_pma = -1.0f; + for (int i = 0; i < ctx->commands.next; i++) { + const _sspine_command_t* cmd = &ctx->commands.ptr[i]; + const bool img_valid = sg_query_image_state(cmd->img) == SG_RESOURCESTATE_VALID; + const bool smp_valid = sg_query_sampler_state(cmd->smp) == SG_RESOURCESTATE_VALID; + if ((layer == cmd->layer) && img_valid && smp_valid) { + if (cur_pip_id != cmd->pip.id) { + sg_apply_pipeline(cmd->pip); + cur_pip_id = cmd->pip.id; + sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, &vsparams_range); + cur_img_id = SG_INVALID_ID; + } + if ((cur_img_id != cmd->img.id) || (cur_smp_id != cmd->smp.id)) { + ctx->bind.fs.images[0] = cmd->img; + ctx->bind.fs.samplers[0] = cmd->smp; + sg_apply_bindings(&ctx->bind); + cur_img_id = cmd->img.id; + cur_smp_id = cmd->smp.id; + } + if (cur_pma != cmd->pma) { + fsparams.pma = cmd->pma; + sg_apply_uniforms(SG_SHADERSTAGE_FS, 0, &fsparams_range); + cur_pma = cmd->pma; + } + if (cmd->num_elements > 0) { + sg_draw(cmd->base_element, cmd->num_elements, 1); + } + } + } + sg_pop_debug_group(); + } +} + +// ███ ███ ██ ███████ ██████ +// ████ ████ ██ ██ ██ +// ██ ████ ██ ██ ███████ ██ +// ██ ██ ██ ██ ██ ██ +// ██ ██ ██ ███████ ██████ +// +// >>misc + +// return sspine_desc with patched defaults +static sspine_desc _sspine_desc_defaults(const sspine_desc* desc) { + SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn)); + sspine_desc res = *desc; + res.max_vertices = _sspine_def(desc->max_vertices, _SSPINE_DEFAULT_MAX_VERTICES); + res.max_commands = _sspine_def(desc->max_commands, _SSPINE_DEFAULT_MAX_COMMANDS); + res.context_pool_size = _sspine_def(desc->context_pool_size, _SSPINE_DEFAULT_CONTEXT_POOL_SIZE); + res.atlas_pool_size = _sspine_def(desc->atlas_pool_size, _SSPINE_DEFAULT_ATLAS_POOL_SIZE); + res.skeleton_pool_size = _sspine_def(desc->skeleton_pool_size, _SSPINE_DEFAULT_SKELETON_POOL_SIZE); + res.skinset_pool_size = _sspine_def(desc->skinset_pool_size, _SSPINE_DEFAULT_SKINSET_POOL_SIZE); + res.instance_pool_size = _sspine_def(desc->instance_pool_size, _SSPINE_DEFAULT_INSTANCE_POOL_SIZE); + return res; +} + +static sspine_context_desc _sspine_as_context_desc(const sspine_desc* desc) { + sspine_context_desc ctx_desc; + _sspine_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; + ctx_desc.color_write_mask = desc->color_write_mask; + return ctx_desc; +} + +static sg_filter _sspine_as_sampler_filter(spAtlasFilter filter) { + switch (filter) { + case SP_ATLAS_UNKNOWN_FILTER: return _SG_FILTER_DEFAULT; + case SP_ATLAS_NEAREST: return SG_FILTER_NEAREST; + case SP_ATLAS_LINEAR: return SG_FILTER_LINEAR; + case SP_ATLAS_MIPMAP: return SG_FILTER_LINEAR; + case SP_ATLAS_MIPMAP_NEAREST_NEAREST: return SG_FILTER_NEAREST; + case SP_ATLAS_MIPMAP_LINEAR_NEAREST: return SG_FILTER_LINEAR; + case SP_ATLAS_MIPMAP_NEAREST_LINEAR: return SG_FILTER_NEAREST; + case SP_ATLAS_MIPMAP_LINEAR_LINEAR: return SG_FILTER_LINEAR; + default: return SG_FILTER_LINEAR; + } +} + +static sg_filter _sspine_as_sampler_mipmap_filter(spAtlasFilter filter) { + switch (filter) { + case SP_ATLAS_UNKNOWN_FILTER: return _SG_FILTER_DEFAULT; + case SP_ATLAS_NEAREST: return SG_FILTER_NONE; + case SP_ATLAS_LINEAR: return SG_FILTER_NONE; + case SP_ATLAS_MIPMAP: return SG_FILTER_NEAREST; + case SP_ATLAS_MIPMAP_NEAREST_NEAREST: return SG_FILTER_NEAREST; + case SP_ATLAS_MIPMAP_LINEAR_NEAREST: return SG_FILTER_NEAREST; + case SP_ATLAS_MIPMAP_NEAREST_LINEAR: return SG_FILTER_LINEAR; + case SP_ATLAS_MIPMAP_LINEAR_LINEAR: return SG_FILTER_LINEAR; + default: return SG_FILTER_NEAREST; + } +} + +static sg_wrap _sspine_as_sampler_wrap(spAtlasWrap wrap) { + switch (wrap) { + case SP_ATLAS_MIRROREDREPEAT: return SG_WRAP_MIRRORED_REPEAT; + case SP_ATLAS_CLAMPTOEDGE: return SG_WRAP_CLAMP_TO_EDGE; + case SP_ATLAS_REPEAT: return SG_WRAP_REPEAT; + default: return _SG_WRAP_DEFAULT; + } +} + +static void _sspine_init_image_info(const _sspine_atlas_t* atlas, int index, sspine_image_info* info, bool with_overrides) { + spAtlasPage* page = _sspine_lookup_atlas_page(atlas->slot.id, index); + SOKOL_ASSERT(page); + SOKOL_ASSERT(page->name); + info->valid = true; + info->sgimage = _sspine_image_from_renderer_object(page->rendererObject); + info->sgsampler = _sspine_sampler_from_renderer_object(page->rendererObject); + if (with_overrides && (atlas->overrides.min_filter != _SG_FILTER_DEFAULT)) { + info->min_filter = atlas->overrides.min_filter; + } else { + info->min_filter = _sspine_as_sampler_filter(page->minFilter); + } + if (with_overrides && (atlas->overrides.mag_filter != _SG_FILTER_DEFAULT)) { + info->mag_filter = atlas->overrides.mag_filter; + } else { + info->mag_filter = _sspine_as_sampler_filter(page->magFilter); + } + if (with_overrides && (atlas->overrides.mipmap_filter != _SG_FILTER_DEFAULT)) { + info->mipmap_filter = atlas->overrides.mipmap_filter; + } else { + info->mipmap_filter = _sspine_as_sampler_mipmap_filter(page->minFilter); + } + if (with_overrides && (atlas->overrides.wrap_u != _SG_WRAP_DEFAULT)) { + info->wrap_u = atlas->overrides.wrap_u; + } else { + info->wrap_u = _sspine_as_sampler_wrap(page->uWrap); + } + if (with_overrides && (atlas->overrides.wrap_v != _SG_WRAP_DEFAULT)) { + info->wrap_v = atlas->overrides.wrap_v; + } else { + info->wrap_v = _sspine_as_sampler_wrap(page->vWrap); + } + info->width = page->width; + info->height = page->height; + // NOTE: override already happened in atlas init + info->premul_alpha = page->pma != 0; + info->filename = _sspine_string(page->name); + if (info->filename.truncated) { + _SSPINE_WARN(STRING_TRUNCATED); + } +} + +static void _sspine_init_shared(void) { + sg_shader_desc shd_desc; + _sspine_clear(&shd_desc, sizeof(shd_desc)); + shd_desc.attrs[0].name = "position"; + shd_desc.attrs[1].name = "texcoord0"; + shd_desc.attrs[2].name = "color0"; + shd_desc.attrs[0].sem_name = "TEXCOORD"; + shd_desc.attrs[0].sem_index = 0; + shd_desc.attrs[1].sem_name = "TEXCOORD"; + shd_desc.attrs[1].sem_index = 1; + shd_desc.attrs[2].sem_name = "TEXCOORD"; + shd_desc.attrs[2].sem_index = 2; + shd_desc.vs.uniform_blocks[0].size = sizeof(_sspine_vsparams_t); + shd_desc.vs.uniform_blocks[0].layout = SG_UNIFORMLAYOUT_STD140; + shd_desc.vs.uniform_blocks[0].uniforms[0].name = "vs_params"; + shd_desc.vs.uniform_blocks[0].uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; + shd_desc.vs.uniform_blocks[0].uniforms[0].array_count = 4; + shd_desc.fs.uniform_blocks[0].size = 16; + shd_desc.fs.uniform_blocks[0].layout = SG_UNIFORMLAYOUT_STD140; + shd_desc.fs.uniform_blocks[0].uniforms[0].name = "fs_params"; + shd_desc.fs.uniform_blocks[0].uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; + shd_desc.fs.uniform_blocks[0].uniforms[0].array_count = 1; + shd_desc.fs.images[0].used = true; + shd_desc.fs.images[0].image_type = SG_IMAGETYPE_2D; + shd_desc.fs.images[0].sample_type = SG_IMAGESAMPLETYPE_FLOAT; + shd_desc.fs.samplers[0].used = true; + shd_desc.fs.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING; + shd_desc.fs.image_sampler_pairs[0].used = true; + shd_desc.fs.image_sampler_pairs[0].image_slot = 0; + shd_desc.fs.image_sampler_pairs[0].sampler_slot = 0; + shd_desc.fs.image_sampler_pairs[0].glsl_name = "tex_smp"; + shd_desc.label = "sspine-shader"; + #if defined(SOKOL_GLCORE33) + shd_desc.vs.source = _sspine_vs_source_glsl330; + shd_desc.fs.source = _sspine_fs_source_glsl330; + #elif defined(SOKOL_GLES3) + shd_desc.vs.source = _sspine_vs_source_glsl300es; + shd_desc.fs.source = _sspine_fs_source_glsl300es; + #elif defined(SOKOL_METAL) + shd_desc.vs.entry = "main0"; + shd_desc.fs.entry = "main0"; + switch (sg_query_backend()) { + case SG_BACKEND_METAL_MACOS: + shd_desc.vs.bytecode = SG_RANGE(_sspine_vs_bytecode_metal_macos); + shd_desc.fs.bytecode = SG_RANGE(_sspine_fs_bytecode_metal_macos); + break; + case SG_BACKEND_METAL_IOS: + shd_desc.vs.bytecode = SG_RANGE(_sspine_vs_bytecode_metal_ios); + shd_desc.fs.bytecode = SG_RANGE(_sspine_fs_bytecode_metal_ios); + break; + default: + shd_desc.vs.source = _sspine_vs_source_metal_sim; + shd_desc.fs.source = _sspine_fs_source_metal_sim; + break; + } + #elif defined(SOKOL_D3D11) + shd_desc.vs.bytecode = SG_RANGE(_sspine_vs_bytecode_hlsl4); + shd_desc.fs.bytecode = SG_RANGE(_sspine_fs_bytecode_hlsl4); + #elif defined(SOKOL_WGPU) + shd_desc.vs.source = _sspine_vs_source_wgsl; + shd_desc.fs.source = _sspine_fs_source_wgsl; + #else + shd_desc.vs.source = _sspine_vs_source_dummy; + shd_desc.fs.source = _sspine_fs_source_dummy; + #endif + _sspine.shd = sg_make_shader(&shd_desc); +} + +static void _sspine_destroy_shared(void) { + sg_destroy_shader(_sspine.shd); +} + +// called from inside sokol-gfx sg_commit() +static void _sspine_commit_listener_func(void* userdata) { + (void)userdata; + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine.frame_id++; +} + +static sg_commit_listener _sspine_make_commit_listener(void) { + sg_commit_listener commit_listener = { _sspine_commit_listener_func, 0 }; + return commit_listener; +} + +// ██████ ██ ██ ██████ ██ ██ ██████ +// ██ ██ ██ ██ ██ ██ ██ ██ ██ +// ██████ ██ ██ ██████ ██ ██ ██ +// ██ ██ ██ ██ ██ ██ ██ ██ +// ██ ██████ ██████ ███████ ██ ██████ +// +// >>public +SOKOL_API_IMPL void sspine_setup(const sspine_desc* desc) { + SOKOL_ASSERT(desc); + spBone_setYDown(1); + _sspine_clear(&_sspine, sizeof(_sspine)); + _sspine.init_cookie = _SSPINE_INIT_COOKIE; + _sspine.desc = _sspine_desc_defaults(desc); + _sspine_init_shared(); + // important, need to setup the frame id with a non-zero value, + // otherwise updates won't trigger in the first frame + _sspine.frame_id = 1; + _sspine_setup_context_pool(_sspine.desc.context_pool_size); + _sspine_setup_atlas_pool(_sspine.desc.atlas_pool_size); + _sspine_setup_skeleton_pool(_sspine.desc.skeleton_pool_size); + _sspine_setup_skinset_pool(_sspine.desc.skinset_pool_size); + _sspine_setup_instance_pool(_sspine.desc.instance_pool_size); + const sspine_context_desc ctx_desc = _sspine_as_context_desc(&_sspine.desc); + _sspine.def_ctx_id = sspine_make_context(&ctx_desc); + SOKOL_ASSERT(_sspine_is_default_context(_sspine.def_ctx_id)); + sspine_set_context(_sspine.def_ctx_id); + if (!sg_add_commit_listener(_sspine_make_commit_listener())) { + _SSPINE_ERROR(ADD_COMMIT_LISTENER_FAILED); + } +} + +SOKOL_API_IMPL void sspine_shutdown(void) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sg_remove_commit_listener(_sspine_make_commit_listener()); + _sspine_destroy_all_instances(); + _sspine_destroy_all_skinsets(); + _sspine_destroy_all_skeletons(); + _sspine_destroy_all_atlases(); + _sspine_destroy_all_contexts(); + _sspine_discard_instance_pool(); + _sspine_discard_skinset_pool(); + _sspine_discard_skeleton_pool(); + _sspine_discard_atlas_pool(); + _sspine_discard_context_pool(); + _sspine_destroy_shared(); + _sspine.init_cookie = 0; +} + +SOKOL_API_IMPL sspine_context sspine_make_context(const sspine_context_desc* desc) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + SOKOL_ASSERT(desc); + const sspine_context_desc desc_def = _sspine_context_desc_defaults(desc); + sspine_context ctx_id = _sspine_alloc_context(); + _sspine_context_t* ctx = _sspine_lookup_context(ctx_id.id); + if (ctx) { + ctx->slot.state = _sspine_init_context(ctx, &desc_def); + SOKOL_ASSERT((ctx->slot.state == SSPINE_RESOURCESTATE_VALID) || (ctx->slot.state == SSPINE_RESOURCESTATE_FAILED)); + if (ctx->slot.state == SSPINE_RESOURCESTATE_FAILED) { + _sspine_deinit_context(ctx); + } + } else { + ctx->slot.state = SSPINE_RESOURCESTATE_FAILED; + _SSPINE_ERROR(CONTEXT_POOL_EXHAUSTED); + } + return ctx_id; +} + +SOKOL_API_IMPL void sspine_destroy_context(sspine_context ctx_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + if (_sspine_is_default_context(ctx_id)) { + _SSPINE_ERROR(CANNOT_DESTROY_DEFAULT_CONTEXT); + return; + } + _sspine_destroy_context(ctx_id); + // re-validate the current context pointer (this will return a nullptr + // if we just destroyed the current context) + _sspine.cur_ctx = _sspine_lookup_context(_sspine.cur_ctx_id.id); +} + +SOKOL_API_IMPL void sspine_set_context(sspine_context ctx_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + if (_sspine_is_default_context(ctx_id)) { + _sspine.cur_ctx_id = _sspine.def_ctx_id; + } else { + _sspine.cur_ctx_id = ctx_id; + } + // this will return null if the handle isn't valid + _sspine.cur_ctx = _sspine_lookup_context(_sspine.cur_ctx_id.id); +} + +SOKOL_API_IMPL sspine_context sspine_get_context(void) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + return _sspine.cur_ctx_id; +} + +SOKOL_API_IMPL sspine_context sspine_default_context(void) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + return _sspine_make_context_handle(0x00010001); +} + +SOKOL_API_IMPL sspine_context_info sspine_get_context_info(sspine_context ctx_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_context_info res; + _sspine_clear(&res, sizeof(res)); + const _sspine_context_t* ctx = _sspine_lookup_context(ctx_id.id); + if (ctx) { + res.num_vertices = ctx->vertices.next; + res.num_indices = ctx->indices.next; + res.num_commands = ctx->commands.next; + } + return res; +} + +SOKOL_API_IMPL void sspine_set_skinset(sspine_instance instance_id, sspine_skinset skinset_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + _sspine_skinset_t* skinset = _sspine_lookup_skinset(skinset_id.id); + if (_sspine_instance_and_deps_valid(instance) && _sspine_skinset_and_deps_valid(skinset) && (instance->skel.id == skinset->skel.id)) { + SOKOL_ASSERT(instance->sp_skel); + SOKOL_ASSERT(instance->sp_anim_state); + SOKOL_ASSERT(skinset->sp_skin); + spSkeleton_setSkin(instance->sp_skel, 0); + spSkeleton_setSkin(instance->sp_skel, skinset->sp_skin); + spSkeleton_setSlotsToSetupPose(instance->sp_skel); + spAnimationState_apply(instance->sp_anim_state, instance->sp_skel); + } +} + +SOKOL_API_IMPL void sspine_update_instance(sspine_instance instance_id, float delta_time) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (_sspine_instance_and_deps_valid(instance)) { + SOKOL_ASSERT(instance->sp_skel); + SOKOL_ASSERT(instance->sp_anim_state); + _sspine_rewind_triggered_events(instance); + spAnimationState_update(instance->sp_anim_state, delta_time); + spAnimationState_apply(instance->sp_anim_state, instance->sp_skel); + spSkeleton_updateWorldTransform(instance->sp_skel); + } +} + +SOKOL_API_IMPL int sspine_num_triggered_events(sspine_instance instance_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (_sspine_instance_and_deps_valid(instance)) { + SOKOL_ASSERT((instance->cur_triggered_event_index >= 0) && (instance->cur_triggered_event_index <= _SSPINE_MAX_TRIGGERED_EVENTS)); + return instance->cur_triggered_event_index; + } + return 0; +} + +SOKOL_API_IMPL sspine_triggered_event_info sspine_get_triggered_event_info(sspine_instance instance_id, int triggered_event_index) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + sspine_triggered_event_info res; + _sspine_clear(&res, sizeof(res)); + if (_sspine_instance_and_deps_valid(instance)) { + if ((triggered_event_index >= 0) && (triggered_event_index < instance->cur_triggered_event_index)) { + res = instance->triggered_events[triggered_event_index]; + } + } + return res; +} + +SOKOL_API_IMPL void sspine_draw_instance_in_layer(sspine_instance instance_id, int layer) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_context_t* ctx = _sspine.cur_ctx; + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (ctx && _sspine_instance_and_deps_valid(instance)) { + _sspine_draw_instance(ctx, instance, layer); + } +} + +SOKOL_API_IMPL sspine_mat4 sspine_layer_transform_to_mat4(const sspine_layer_transform* tform) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_mat4 res; + _sspine_layer_transform_to_proj(tform, res.m); + return res; +} + +SOKOL_API_IMPL void sspine_context_draw_instance_in_layer(sspine_context ctx_id, sspine_instance instance_id, int layer) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_context_t* ctx = _sspine_lookup_context(ctx_id.id); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (ctx && _sspine_instance_and_deps_valid(instance)) { + _sspine_draw_instance(ctx, instance, layer); + } +} + +SOKOL_API_IMPL void sspine_draw_layer(int layer, const sspine_layer_transform* tform) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + SOKOL_ASSERT(tform); + _sspine_context_t* ctx = _sspine.cur_ctx; + if (ctx) { + _sspine_draw_layer(ctx, layer, tform); + } +} + +SOKOL_API_IMPL void sspine_context_draw_layer(sspine_context ctx_id, int layer, const sspine_layer_transform* tform) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + SOKOL_ASSERT(tform); + _sspine_context_t* ctx = _sspine_lookup_context(ctx_id.id); + if (ctx) { + _sspine_draw_layer(ctx, layer, tform); + } +} + +SOKOL_API_IMPL sspine_atlas sspine_make_atlas(const sspine_atlas_desc* desc) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + SOKOL_ASSERT(desc); + const sspine_atlas_desc desc_def = _sspine_atlas_desc_defaults(desc); + sspine_atlas atlas_id = _sspine_alloc_atlas(); + _sspine_atlas_t* atlas = _sspine_lookup_atlas(atlas_id.id); + if (atlas) { + atlas->slot.state = _sspine_init_atlas(atlas, &desc_def); + SOKOL_ASSERT((atlas->slot.state == SSPINE_RESOURCESTATE_VALID) || (atlas->slot.state == SSPINE_RESOURCESTATE_FAILED)); + if (atlas->slot.state == SSPINE_RESOURCESTATE_FAILED) { + _sspine_deinit_atlas(atlas); + } + } else { + _SSPINE_ERROR(ATLAS_POOL_EXHAUSTED); + } + return atlas_id; +} + +SOKOL_API_IMPL void sspine_destroy_atlas(sspine_atlas atlas_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_destroy_atlas(atlas_id); +} + +SOKOL_API_IMPL sspine_skeleton sspine_make_skeleton(const sspine_skeleton_desc* desc) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + SOKOL_ASSERT(desc); + const sspine_skeleton_desc desc_def = _sspine_skeleton_desc_defaults(desc); + sspine_skeleton skeleton_id = _sspine_alloc_skeleton(); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (skeleton) { + skeleton->slot.state = _sspine_init_skeleton(skeleton, &desc_def); + SOKOL_ASSERT((skeleton->slot.state == SSPINE_RESOURCESTATE_VALID) || (skeleton->slot.state == SSPINE_RESOURCESTATE_FAILED)); + if (skeleton->slot.state == SSPINE_RESOURCESTATE_FAILED) { + _sspine_deinit_skeleton(skeleton); + } + } else { + _SSPINE_ERROR(SKELETON_POOL_EXHAUSTED); + } + return skeleton_id; +} + +SOKOL_API_IMPL void sspine_destroy_skeleton(sspine_skeleton skeleton_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_destroy_skeleton(skeleton_id); +} + +SOKOL_API_IMPL sspine_skinset sspine_make_skinset(const sspine_skinset_desc* desc) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + SOKOL_ASSERT(desc); + const sspine_skinset_desc desc_def = _sspine_skinset_desc_defaults(desc); + sspine_skinset skinset_id = _sspine_alloc_skinset(); + _sspine_skinset_t* skinset = _sspine_lookup_skinset(skinset_id.id); + if (skinset) { + skinset->slot.state = _sspine_init_skinset(skinset, &desc_def); + SOKOL_ASSERT((skinset->slot.state == SSPINE_RESOURCESTATE_VALID) || (skinset->slot.state == SSPINE_RESOURCESTATE_FAILED)); + if (skinset->slot.state == SSPINE_RESOURCESTATE_FAILED) { + _sspine_deinit_skinset(skinset); + } + } else { + _SSPINE_ERROR(SKINSET_POOL_EXHAUSTED); + } + return skinset_id; +} + +SOKOL_API_IMPL void sspine_destroy_skinset(sspine_skinset skinset_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_destroy_skinset(skinset_id); +} + +SOKOL_API_IMPL sspine_instance sspine_make_instance(const sspine_instance_desc* desc) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + SOKOL_ASSERT(desc); + const sspine_instance_desc desc_def = _sspine_instance_desc_defaults(desc); + sspine_instance instance_id = _sspine_alloc_instance(); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (instance) { + instance->slot.state = _sspine_init_instance(instance, &desc_def); + SOKOL_ASSERT((instance->slot.state == SSPINE_RESOURCESTATE_VALID) || (instance->slot.state == SSPINE_RESOURCESTATE_FAILED)); + if (instance->slot.state == SSPINE_RESOURCESTATE_FAILED) { + _sspine_deinit_instance(instance); + } + } else { + _SSPINE_ERROR(INSTANCE_POOL_EXHAUSTED); + } + return instance_id; +} + +SOKOL_API_IMPL void sspine_destroy_instance(sspine_instance instance_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_destroy_instance(instance_id); +} + +SOKOL_API_IMPL sspine_resource_state sspine_get_context_resource_state(sspine_context ctx_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + const _sspine_context_t* ctx = _sspine_lookup_context(ctx_id.id); + if (ctx) { + return ctx->slot.state; + } else { + return SSPINE_RESOURCESTATE_INVALID; + } +} + +SOKOL_API_IMPL sspine_resource_state sspine_get_atlas_resource_state(sspine_atlas atlas_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + const _sspine_atlas_t* atlas = _sspine_lookup_atlas(atlas_id.id); + if (atlas) { + return atlas->slot.state; + } else { + return SSPINE_RESOURCESTATE_INVALID; + } +} + +SOKOL_API_IMPL sspine_resource_state sspine_get_skeleton_resource_state(sspine_skeleton skeleton_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + const _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (skeleton) { + return skeleton->slot.state; + } else { + return SSPINE_RESOURCESTATE_INVALID; + } +} + +SOKOL_API_IMPL sspine_resource_state sspine_get_skinset_resource_state(sspine_skinset skinset_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + const _sspine_skinset_t* skinset = _sspine_lookup_skinset(skinset_id.id); + if (skinset) { + return skinset->slot.state; + } else { + return SSPINE_RESOURCESTATE_INVALID; + } +} + +SOKOL_API_IMPL sspine_resource_state sspine_get_instance_resource_state(sspine_instance instance_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + const _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (instance) { + return instance->slot.state; + } else { + return SSPINE_RESOURCESTATE_INVALID; + } +} + +SOKOL_API_IMPL bool sspine_context_valid(sspine_context ctx_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + return sspine_get_context_resource_state(ctx_id) == SSPINE_RESOURCESTATE_VALID; +} + +SOKOL_API_IMPL bool sspine_atlas_valid(sspine_atlas atlas_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + return sspine_get_atlas_resource_state(atlas_id) == SSPINE_RESOURCESTATE_VALID; +} + +SOKOL_API_IMPL bool sspine_skeleton_valid(sspine_skeleton skeleton_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + return sspine_get_skeleton_resource_state(skeleton_id) == SSPINE_RESOURCESTATE_VALID; +} + +SOKOL_API_IMPL bool sspine_skinset_valid(sspine_skinset skinset_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + return sspine_get_skinset_resource_state(skinset_id) == SSPINE_RESOURCESTATE_VALID; +} + +SOKOL_API_IMPL bool sspine_instance_valid(sspine_instance instance_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + return sspine_get_instance_resource_state(instance_id) == SSPINE_RESOURCESTATE_VALID; +} + +SOKOL_API_IMPL sspine_atlas sspine_get_skeleton_atlas(sspine_skeleton skeleton_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + sspine_atlas res; + _sspine_clear(&res, sizeof(res)); + if (skeleton) { + res.id = skeleton->atlas.id; + } + return res; +} + +SOKOL_API_IMPL sspine_skeleton sspine_get_instance_skeleton(sspine_instance instance_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + sspine_skeleton res; + _sspine_clear(&res, sizeof(res)); + if (instance) { + res.id = instance->skel.id; + } + return res; +} + +SOKOL_API_IMPL int sspine_num_images(sspine_atlas atlas_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_atlas_t* atlas = _sspine_lookup_atlas(atlas_id.id); + if (atlas) { + return atlas->num_pages; + } + return 0; +} + +SOKOL_API_IMPL sspine_image sspine_image_by_index(sspine_atlas atlas_id, int index) { + return _sspine_image(atlas_id.id, index); +} + +SOKOL_API_IMPL bool sspine_image_valid(sspine_image image) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_atlas_t* atlas = _sspine_lookup_atlas(image.atlas_id); + return atlas && (image.index >= 0) && (image.index < atlas->num_pages); +} + +SOKOL_API_IMPL bool sspine_image_equal(sspine_image first, sspine_image second) { + return (first.atlas_id == second.atlas_id) && (first.index == second.index); +} + +SOKOL_API_IMPL sspine_image_info sspine_get_image_info(sspine_image image) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_atlas_t* atlas = _sspine_lookup_atlas(image.atlas_id); + sspine_image_info img_info; + _sspine_clear(&img_info, sizeof(img_info)); + if (atlas && (image.index >= 0) && (image.index < atlas->num_pages)) { + _sspine_init_image_info(atlas, image.index, &img_info, true); + } + return img_info; +} + +SOKOL_API_IMPL int sspine_num_atlas_pages(sspine_atlas atlas_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_atlas_t* atlas = _sspine_lookup_atlas(atlas_id.id); + if (atlas) { + return atlas->num_pages; + } else { + return 0; + } +} + +SOKOL_API_IMPL sspine_atlas_page sspine_atlas_page_by_index(sspine_atlas atlas_id, int index) { + return _sspine_atlas_page(atlas_id.id, index); +} + +SOKOL_API_IMPL bool sspine_atlas_page_valid(sspine_atlas_page page) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_atlas_t* atlas = _sspine_lookup_atlas(page.atlas_id); + if (atlas) { + return (page.index >= 0) && (page.index < atlas->num_pages); + } + return false; +} + +SOKOL_API_IMPL bool sspine_atlas_page_equal(sspine_atlas_page first, sspine_atlas_page second) { + return (first.atlas_id == second.atlas_id) && (first.index == second.index); +} + +SOKOL_API_IMPL sspine_atlas_page_info sspine_get_atlas_page_info(sspine_atlas_page page) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_atlas_page_info res; + _sspine_clear(&res, sizeof(res)); + const spAtlasPage* sp_page = _sspine_lookup_atlas_page(page.atlas_id, page.index); + if (sp_page) { + // at this point, atlas is guaranteed to be valid + const _sspine_atlas_t* atlas = _sspine_lookup_atlas(page.atlas_id); + res.valid = true; + res.atlas.id = page.atlas_id; + // write image info without overrides + _sspine_init_image_info(atlas, page.index, &res.image, false); + // ...and provide the overrides separately + res.overrides = atlas->overrides; + } + return res; +} + +SOKOL_API_IMPL void sspine_set_position(sspine_instance instance_id, sspine_vec2 position) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (_sspine_instance_and_deps_valid(instance)) { + SOKOL_ASSERT(instance->sp_skel); + instance->sp_skel->x = position.x; + instance->sp_skel->y = position.y; + } +} + +SOKOL_API_IMPL void sspine_set_scale(sspine_instance instance_id, sspine_vec2 scale) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (instance) { + SOKOL_ASSERT(instance->sp_skel); + instance->sp_skel->scaleX = scale.x; + instance->sp_skel->scaleY = scale.y; + } +} + +SOKOL_API_IMPL void sspine_set_color(sspine_instance instance_id, sspine_color color) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (instance) { + SOKOL_ASSERT(instance->sp_skel); + instance->sp_skel->color.r = color.r; + instance->sp_skel->color.g = color.g; + instance->sp_skel->color.b = color.b; + instance->sp_skel->color.a = color.a; + } +} + +SOKOL_API_IMPL sspine_vec2 sspine_get_position(sspine_instance instance_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + sspine_vec2 v = { 0.0f, 0.0f }; + if (instance) { + SOKOL_ASSERT(instance->sp_skel); + v.x = instance->sp_skel->x; + v.y = instance->sp_skel->y; + } + return v; +} + +SOKOL_API_IMPL sspine_vec2 sspine_get_scale(sspine_instance instance_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + sspine_vec2 v = { 0.0f, 0.0f }; + if (instance) { + SOKOL_ASSERT(instance->sp_skel); + v.x = instance->sp_skel->scaleX; + v.y = instance->sp_skel->scaleY; + } + return v; +} + +SOKOL_API_IMPL sspine_color sspine_get_color(sspine_instance instance_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + sspine_color c = { 0.0f, 0.0f, 0.0f, 0.0f }; + if (instance) { + SOKOL_ASSERT(instance->sp_skel); + c.r = instance->sp_skel->color.r; + c.g = instance->sp_skel->color.g; + c.b = instance->sp_skel->color.b; + c.a = instance->sp_skel->color.a; + } + return c; +} + +SOKOL_API_IMPL int sspine_num_anims(sspine_skeleton skeleton_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + return skeleton->sp_skel_data->animationsCount; + } + return 0; +} + +SOKOL_API_IMPL sspine_anim sspine_anim_by_name(sspine_skeleton skeleton_id, const char* name) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + SOKOL_ASSERT(name); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + // NOTE: there's a spSkeletonData_findAnimation function, but that doesn't + // give us access to the index, so we'll need to do the loop ourselves + SOKOL_ASSERT(skeleton->sp_skel_data); + const spSkeletonData* sp_skel_data = skeleton->sp_skel_data; + const int num_anims = sp_skel_data->animationsCount; + SOKOL_ASSERT(sp_skel_data->animations); + for (int i = 0; i < num_anims; i++) { + SOKOL_ASSERT(sp_skel_data->animations[i]); + SOKOL_ASSERT(sp_skel_data->animations[i]->name); + if (0 == strcmp(sp_skel_data->animations[i]->name, name)) { + return _sspine_anim(skeleton_id.id, i); + } + } + } + return _sspine_anim(0, 0); +} + +SOKOL_API_IMPL sspine_anim sspine_anim_by_index(sspine_skeleton skeleton_id, int index) { + return _sspine_anim(skeleton_id.id, index); +} + +SOKOL_API_IMPL bool sspine_anim_valid(sspine_anim anim) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(anim.skeleton_id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + return (anim.index >= 0) && (anim.index < skeleton->sp_skel_data->animationsCount); + } + return false; +} + +SOKOL_API_IMPL bool sspine_anim_equal(sspine_anim first, sspine_anim second) { + return (first.skeleton_id == second.skeleton_id) && (first.index == second.index); +} + +SOKOL_API_IMPL sspine_anim_info sspine_get_anim_info(sspine_anim anim) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_anim_info res; + _sspine_clear(&res, sizeof(res)); + const spAnimation* sp_anim = _sspine_lookup_skeleton_anim(anim.skeleton_id, anim.index); + if (sp_anim) { + res.valid = true; + res.index = anim.index; + res.duration = sp_anim->duration; + res.name = _sspine_string(sp_anim->name); + if (res.name.truncated) { + _SSPINE_WARN(STRING_TRUNCATED); + } + } + return res; +} + +SOKOL_API_IMPL void sspine_clear_animation_tracks(sspine_instance instance_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (_sspine_instance_and_deps_valid(instance)) { + SOKOL_ASSERT(instance->sp_anim_state); + spAnimationState_clearTracks(instance->sp_anim_state); + } +} + +SOKOL_API_IMPL void sspine_clear_animation_track(sspine_instance instance_id, int track_index) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (_sspine_instance_and_deps_valid(instance)) { + SOKOL_ASSERT(instance->sp_anim_state); + spAnimationState_clearTrack(instance->sp_anim_state, track_index); + } +} + +SOKOL_API_IMPL void sspine_set_animation(sspine_instance instance_id, sspine_anim anim, int track_index, bool loop) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + spAnimation* sp_anim = _sspine_lookup_instance_anim(instance_id.id, anim.skeleton_id, anim.index); + if (sp_anim) { + // NOTE: at this point, instance is guaranteed to be valid + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + SOKOL_ASSERT(instance); + spAnimationState_setAnimation(instance->sp_anim_state, track_index, sp_anim, loop?1:0); + } +} + +SOKOL_API_IMPL void sspine_add_animation(sspine_instance instance_id, sspine_anim anim, int track_index, bool loop, float delay) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + spAnimation* sp_anim = _sspine_lookup_instance_anim(instance_id.id, anim.skeleton_id, anim.index); + if (sp_anim) { + // NOTE: at this point, instance is guaranteed to be valid + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + SOKOL_ASSERT(instance); + SOKOL_ASSERT(instance->sp_anim_state); + spAnimationState_addAnimation(instance->sp_anim_state, track_index, sp_anim, loop?1:0, delay); + } +} + +SOKOL_API_IMPL void sspine_set_empty_animation(sspine_instance instance_id, int track_index, float mix_duration) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (_sspine_instance_and_deps_valid(instance)) { + SOKOL_ASSERT(instance->sp_anim_state); + spAnimationState_setEmptyAnimation(instance->sp_anim_state, track_index, mix_duration); + } +} + +SOKOL_API_IMPL void sspine_add_empty_animation(sspine_instance instance_id, int track_index, float mix_duration, float delay) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (_sspine_instance_and_deps_valid(instance)) { + SOKOL_ASSERT(instance->sp_anim_state); + spAnimationState_addEmptyAnimation(instance->sp_anim_state, track_index, mix_duration, delay); + } +} + +SOKOL_API_IMPL int sspine_num_bones(sspine_skeleton skeleton_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + return skeleton->sp_skel_data->bonesCount; + } + return 0; +} + +SOKOL_API_IMPL sspine_bone sspine_bone_by_name(sspine_skeleton skeleton_id, const char* name) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + SOKOL_ASSERT(name); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + spBoneData* sp_bone_data = spSkeletonData_findBone(skeleton->sp_skel_data, name); + if (sp_bone_data) { + return _sspine_bone(skeleton_id.id, sp_bone_data->index); + } + } + return _sspine_bone(0, 0); +} + +SOKOL_API_IMPL sspine_bone sspine_bone_by_index(sspine_skeleton skeleton_id, int index) { + return _sspine_bone(skeleton_id.id, index); +} + +SOKOL_API_IMPL bool sspine_bone_valid(sspine_bone bone) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(bone.skeleton_id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + return (bone.index >= 0) && (bone.index < skeleton->sp_skel_data->bonesCount); + } + return false; +} + +SOKOL_API_IMPL bool sspine_bone_equal(sspine_bone first, sspine_bone second) { + return (first.skeleton_id == second.skeleton_id) && (first.index == second.index); +} + +SOKOL_API_IMPL sspine_bone_info sspine_get_bone_info(sspine_bone bone) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_bone_info res; + _sspine_clear(&res, sizeof(res)); + const spBoneData* sp_bone_data = _sspine_lookup_bone_data(bone.skeleton_id, bone.index); + if (sp_bone_data) { + SOKOL_ASSERT(sp_bone_data->index == bone.index); + SOKOL_ASSERT(sp_bone_data->name); + res.valid = true; + res.index = sp_bone_data->index; + if (sp_bone_data->parent) { + res.parent_bone = _sspine_bone(bone.skeleton_id, sp_bone_data->parent->index); + } + res.length = sp_bone_data->length; + res.pose.position.x = sp_bone_data->x; + res.pose.position.y = sp_bone_data->y; + res.pose.rotation = sp_bone_data->rotation; + res.pose.scale.x = sp_bone_data->scaleX; + res.pose.scale.y = sp_bone_data->scaleY; + res.pose.shear.x = sp_bone_data->shearX; + res.pose.shear.y = sp_bone_data->shearY; + res.color.r = sp_bone_data->color.r; + res.color.g = sp_bone_data->color.g; + res.color.b = sp_bone_data->color.b; + res.color.a = sp_bone_data->color.a; + res.name = _sspine_string(sp_bone_data->name); + if (res.name.truncated) { + _SSPINE_WARN(STRING_TRUNCATED); + } + } + return res; +} + +SOKOL_API_IMPL void sspine_set_bone_transform(sspine_instance instance_id, sspine_bone bone, const sspine_bone_transform* transform) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + SOKOL_ASSERT(transform); + spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); + if (sp_bone) { + sp_bone->x = transform->position.x; + sp_bone->y = transform->position.y; + sp_bone->rotation = transform->rotation; + sp_bone->scaleX = transform->scale.x; + sp_bone->scaleY = transform->scale.y; + sp_bone->shearX = transform->shear.x; + sp_bone->shearY = transform->shear.y; + } +} + +SOKOL_API_IMPL void sspine_set_bone_position(sspine_instance instance_id, sspine_bone bone, sspine_vec2 position) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); + if (sp_bone) { + sp_bone->x = position.x; + sp_bone->y = position.y; + } +} + +SOKOL_API_IMPL void sspine_set_bone_rotation(sspine_instance instance_id, sspine_bone bone, float rotation) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); + if (sp_bone) { + sp_bone->rotation = rotation; + } +} + +SOKOL_API_IMPL void sspine_set_bone_scale(sspine_instance instance_id, sspine_bone bone, sspine_vec2 scale) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); + if (sp_bone) { + sp_bone->scaleX = scale.x; + sp_bone->scaleY = scale.y; + } +} + +SOKOL_API_IMPL void sspine_set_bone_shear(sspine_instance instance_id, sspine_bone bone, sspine_vec2 shear) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); + if (sp_bone) { + sp_bone->shearX = shear.x; + sp_bone->shearY = shear.y; + } +} + +SOKOL_API_IMPL sspine_bone_transform sspine_get_bone_transform(sspine_instance instance_id, sspine_bone bone) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_bone_transform res; + _sspine_clear(&res, sizeof(res)); + spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); + if (sp_bone) { + res.position.x = sp_bone->x; + res.position.y = sp_bone->y; + res.rotation = sp_bone->rotation; + res.scale.x = sp_bone->scaleX; + res.scale.y = sp_bone->scaleY; + res.shear.x = sp_bone->shearX; + res.shear.y = sp_bone->shearY; + } + return res; +} + +SOKOL_API_IMPL sspine_vec2 sspine_get_bone_position(sspine_instance instance_id, sspine_bone bone) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_vec2 res; + _sspine_clear(&res, sizeof(res)); + spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); + if (sp_bone) { + res.x = sp_bone->x; + res.y = sp_bone->y; + } + return res; +} + +SOKOL_API_IMPL float sspine_get_bone_rotation(sspine_instance instance_id, sspine_bone bone) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); + if (sp_bone) { + return sp_bone->rotation; + } else { + return 0.0f; + } +} + +SOKOL_API_IMPL sspine_vec2 sspine_get_bone_scale(sspine_instance instance_id, sspine_bone bone) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_vec2 res; + _sspine_clear(&res, sizeof(res)); + spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); + if (sp_bone) { + res.x = sp_bone->scaleX; + res.y = sp_bone->scaleY; + } + return res; +} + +SOKOL_API_IMPL sspine_vec2 sspine_get_bone_shear(sspine_instance instance_id, sspine_bone bone) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_vec2 res; + _sspine_clear(&res, sizeof(res)); + spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); + if (sp_bone) { + res.x = sp_bone->shearX; + res.y = sp_bone->shearY; + } + return res; +} + +SOKOL_API_IMPL sspine_vec2 sspine_get_bone_world_position(sspine_instance instance_id, sspine_bone bone) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_vec2 res; + _sspine_clear(&res, sizeof(res)); + spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); + if (sp_bone) { + res.x = sp_bone->worldX; + res.y = sp_bone->worldY; + } + return res; +} + +SOKOL_API_IMPL sspine_vec2 sspine_bone_local_to_world(sspine_instance instance_id, sspine_bone bone, sspine_vec2 local_pos) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_vec2 res; + _sspine_clear(&res, sizeof(res)); + spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); + if (sp_bone) { + spBone_localToWorld(sp_bone, local_pos.x, local_pos.y, &res.x, &res.y); + } + return res; +} + +SOKOL_API_IMPL sspine_vec2 sspine_bone_world_to_local(sspine_instance instance_id, sspine_bone bone, sspine_vec2 world_pos) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_vec2 res; + _sspine_clear(&res, sizeof(res)); + spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); + if (sp_bone) { + spBone_worldToLocal(sp_bone, world_pos.x, world_pos.y, &res.x, &res.y); + } + return res; +} + +SOKOL_API_IMPL int sspine_num_slots(sspine_skeleton skeleton_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + return skeleton->sp_skel_data->slotsCount; + } + return 0; +} + +SOKOL_API_IMPL sspine_slot sspine_slot_by_name(sspine_skeleton skeleton_id, const char* name) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + SOKOL_ASSERT(name); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + spSlotData* sp_slot_data = spSkeletonData_findSlot(skeleton->sp_skel_data, name); + if (sp_slot_data) { + return _sspine_slot(skeleton_id.id, sp_slot_data->index); + } + } + return _sspine_slot(0, 0); +} + +SOKOL_API_IMPL sspine_slot sspine_slot_by_index(sspine_skeleton skeleton_id, int index) { + return _sspine_slot(skeleton_id.id, index); +} + +SOKOL_API_IMPL bool sspine_slot_valid(sspine_slot slot) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(slot.skeleton_id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + return (slot.index >= 0) && (slot.index < skeleton->sp_skel_data->slotsCount); + } + return false; +} + +SOKOL_API_IMPL bool sspine_slot_equal(sspine_slot first, sspine_slot second) { + return (first.skeleton_id == second.skeleton_id) && (first.index == second.index); +} + +SOKOL_API_IMPL sspine_slot_info sspine_get_slot_info(sspine_slot slot) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_slot_info res; + _sspine_clear(&res, sizeof(res)); + const spSlotData* sp_slot_data = _sspine_lookup_slot_data(slot.skeleton_id, slot.index); + if (sp_slot_data) { + SOKOL_ASSERT(sp_slot_data->index == slot.index); + SOKOL_ASSERT(sp_slot_data->name); + SOKOL_ASSERT(sp_slot_data->boneData); + res.valid = true; + res.index = sp_slot_data->index; + res.bone = _sspine_bone(slot.skeleton_id, sp_slot_data->boneData->index); + res.color.r = sp_slot_data->color.r; + res.color.g = sp_slot_data->color.g; + res.color.b = sp_slot_data->color.b; + res.color.a = sp_slot_data->color.a; + res.attachment_name = _sspine_string(sp_slot_data->attachmentName); + if (res.attachment_name.truncated) { + _SSPINE_WARN(STRING_TRUNCATED); + } + res.name = _sspine_string(sp_slot_data->name); + if (res.name.truncated) { + _SSPINE_WARN(STRING_TRUNCATED); + } + } + return res; +} + +SOKOL_API_IMPL void sspine_set_slot_color(sspine_instance instance_id, sspine_slot slot, sspine_color color) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + spSlot* sp_slot = _sspine_lookup_slot(instance_id.id, slot.skeleton_id, slot.index); + if (sp_slot) { + sp_slot->color.r = color.r; + sp_slot->color.g = color.g; + sp_slot->color.b = color.b; + sp_slot->color.a = color.a; + } +} + +SOKOL_API_IMPL sspine_color sspine_get_slot_color(sspine_instance instance_id, sspine_slot slot) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_color color; + _sspine_clear(&color, sizeof(color)); + spSlot* sp_slot = _sspine_lookup_slot(instance_id.id, slot.skeleton_id, slot.index); + if (sp_slot) { + color.r = sp_slot->color.r; + color.g = sp_slot->color.g; + color.b = sp_slot->color.b; + color.a = sp_slot->color.a; + } + return color; +} + +SOKOL_API_IMPL int sspine_num_events(sspine_skeleton skeleton_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + return skeleton->sp_skel_data->eventsCount; + } + return 0; +} + +SOKOL_API_IMPL sspine_event sspine_event_by_name(sspine_skeleton skeleton_id, const char* name) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + SOKOL_ASSERT(name); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + SOKOL_ASSERT(skeleton->sp_skel_data->events); + // spEventData has no embedded index, so we need to loop over the events + for (int i = 0; i < skeleton->sp_skel_data->eventsCount; i++) { + SOKOL_ASSERT(skeleton->sp_skel_data->events[i]); + SOKOL_ASSERT(skeleton->sp_skel_data->events[i]->name); + if (0 == strcmp(skeleton->sp_skel_data->events[i]->name, name)) { + return _sspine_event(skeleton_id.id, i); + } + } + } + return _sspine_event(0, 0); +} + +SOKOL_API_IMPL sspine_event sspine_event_by_index(sspine_skeleton skeleton_id, int index) { + return _sspine_event(skeleton_id.id, index); +} + +SOKOL_API_IMPL bool sspine_event_valid(sspine_event event) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(event.skeleton_id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + return (event.index >= 0) && (event.index < skeleton->sp_skel_data->eventsCount); + } + return false; +} + +SOKOL_API_IMPL bool sspine_event_equal(sspine_event first, sspine_event second) { + return (first.skeleton_id == second.skeleton_id) && (first.index == second.index); +} + +SOKOL_API_IMPL sspine_event_info sspine_get_event_info(sspine_event event) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_event_info res; + _sspine_clear(&res, sizeof(res)); + const spEventData* sp_event_data = _sspine_lookup_event_data(event.skeleton_id, event.index); + if (sp_event_data) { + res.valid = true; + res.index = event.index; + res.int_value = sp_event_data->intValue; + res.float_value = sp_event_data->floatValue; + res.volume = sp_event_data->volume; + res.balance = sp_event_data->balance; + res.name = _sspine_string(sp_event_data->name); + if (res.name.truncated) { + _SSPINE_WARN(STRING_TRUNCATED); + } + res.string_value = _sspine_string(sp_event_data->stringValue); + if (res.string_value.truncated) { + _SSPINE_WARN(STRING_TRUNCATED); + } + res.audio_path = _sspine_string(sp_event_data->audioPath); + if (res.audio_path.truncated) { + _SSPINE_WARN(STRING_TRUNCATED); + } + } + return res; +} + +SOKOL_API_IMPL int sspine_num_iktargets(sspine_skeleton skeleton_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + return skeleton->sp_skel_data->ikConstraintsCount; + } + return 0; +} + +SOKOL_API_IMPL sspine_iktarget sspine_iktarget_by_name(sspine_skeleton skeleton_id, const char* name) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + SOKOL_ASSERT(name); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + SOKOL_ASSERT(skeleton->sp_skel_data->ikConstraints); + // spIkConstraintData has no embedded index, so we need to loop over the events + for (int i = 0; i < skeleton->sp_skel_data->ikConstraintsCount; i++) { + SOKOL_ASSERT(skeleton->sp_skel_data->ikConstraints[i]); + SOKOL_ASSERT(skeleton->sp_skel_data->ikConstraints[i]->name); + if (0 == strcmp(skeleton->sp_skel_data->ikConstraints[i]->name, name)) { + return _sspine_iktarget(skeleton_id.id, i); + } + } + } + return _sspine_iktarget(0, 0); +} + +SOKOL_API_IMPL sspine_iktarget sspine_iktarget_by_index(sspine_skeleton skeleton_id, int index) { + return _sspine_iktarget(skeleton_id.id, index); +} + +SOKOL_API_IMPL bool sspine_iktarget_valid(sspine_iktarget iktarget) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(iktarget.skeleton_id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + return (iktarget.index >= 0) && (iktarget.index < skeleton->sp_skel_data->ikConstraintsCount); + } + return false; +} + +SOKOL_API_IMPL bool sspine_iktarget_equal(sspine_iktarget first, sspine_iktarget second) { + return (first.skeleton_id == second.skeleton_id) && (first.index == second.index); +} + +SOKOL_API_IMPL sspine_iktarget_info sspine_get_iktarget_info(sspine_iktarget iktarget) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_iktarget_info res; + _sspine_clear(&res, sizeof(res)); + const spIkConstraintData* ik_data = _sspine_lookup_ikconstraint_data(iktarget.skeleton_id, iktarget.index); + if (ik_data) { + res.valid = true; + res.index = iktarget.index; + res.target_bone = _sspine_bone(iktarget.skeleton_id, ik_data->target->index); + res.name = _sspine_string(ik_data->name); + if (res.name.truncated) { + _SSPINE_WARN(STRING_TRUNCATED); + } + } + return res; +} + +SOKOL_API_IMPL void sspine_set_iktarget_world_pos(sspine_instance instance_id, sspine_iktarget iktarget, sspine_vec2 world_pos) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + spIkConstraint* ik_data = _sspine_lookup_ikconstraint(instance_id.id, iktarget.skeleton_id, iktarget.index); + if (ik_data) { + spBone* bone = ik_data->target; + spBone* parent_bone = bone->parent; + if (parent_bone) { + spBone_worldToLocal(parent_bone, world_pos.x, world_pos.y, &bone->x, &bone->y); + } + } +} + +SOKOL_API_IMPL int sspine_num_skins(sspine_skeleton skeleton_id) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + return skeleton->sp_skel_data->skinsCount; + } + return 0; +} + +SOKOL_API_IMPL sspine_skin sspine_skin_by_name(sspine_skeleton skeleton_id, const char* name) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + SOKOL_ASSERT(name); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + SOKOL_ASSERT(skeleton->sp_skel_data->skins); + // spSkin has no embedded index, so we need to loop over the skins + for (int i = 0; i < skeleton->sp_skel_data->skinsCount; i++) { + SOKOL_ASSERT(skeleton->sp_skel_data->skins[i]); + SOKOL_ASSERT(skeleton->sp_skel_data->skins[i]->name); + if (0 == strcmp(skeleton->sp_skel_data->skins[i]->name, name)) { + return _sspine_skin(skeleton_id.id, i); + } + } + } + return _sspine_skin(0, 0); +} + +SOKOL_API_IMPL sspine_skin sspine_skin_by_index(sspine_skeleton skeleton_id, int index) { + return _sspine_skin(skeleton_id.id, index); +} + +SOKOL_API_IMPL bool sspine_skin_valid(sspine_skin skin) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skin.skeleton_id); + if (_sspine_skeleton_and_deps_valid(skeleton)) { + SOKOL_ASSERT(skeleton->sp_skel_data); + return (skin.index >= 0) && (skin.index < skeleton->sp_skel_data->skinsCount); + } + return false; +} + +SOKOL_API_IMPL bool sspine_skin_equal(sspine_skin first, sspine_skin second) { + return (first.skeleton_id == second.skeleton_id) && (first.index == second.index); +} + +SOKOL_API_IMPL sspine_skin_info sspine_get_skin_info(sspine_skin skin) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + sspine_skin_info res; + _sspine_clear(&res, sizeof(res)); + const spSkin* sp_skin = _sspine_lookup_skin(skin.skeleton_id, skin.index); + if (sp_skin) { + res.valid = true; + res.index = skin.index; + res.name = _sspine_string(sp_skin->name); + if (res.name.truncated) { + _SSPINE_WARN(STRING_TRUNCATED); + } + } + return res; +} + +SOKOL_SPINE_API_DECL void sspine_set_skin(sspine_instance instance_id, sspine_skin skin) { + SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); + _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); + if (_sspine_instance_and_deps_valid(instance) && (instance->skel.id == skin.skeleton_id)) { + SOKOL_ASSERT(instance->sp_skel); + SOKOL_ASSERT(instance->sp_anim_state); + // clear any currently set skinset + instance->skinset.id = SSPINE_INVALID_ID; + instance->skinset.ptr = 0; + spSkin* sp_skin = _sspine_lookup_skin(skin.skeleton_id, skin.index); + if (sp_skin) { + spSkeleton_setSkin(instance->sp_skel, 0); + spSkeleton_setSkin(instance->sp_skel, sp_skin); + spSkeleton_setSlotsToSetupPose(instance->sp_skel); + spAnimationState_apply(instance->sp_anim_state, instance->sp_skel); + } + } +} + +#endif // SOKOL_SPINE_IMPL +#endif // SOKOL_SPINE_INCLUDED diff --git a/source/engine/thirdparty/stb b/source/engine/thirdparty/stb deleted file mode 160000 index ae721c5..0000000 --- a/source/engine/thirdparty/stb +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ae721c50eaf761660b4f90cc590453cdb0c2acd0 diff --git a/source/engine/thirdparty/stb/.github/CONTRIBUTING.md b/source/engine/thirdparty/stb/.github/CONTRIBUTING.md new file mode 100644 index 0000000..54e3543 --- /dev/null +++ b/source/engine/thirdparty/stb/.github/CONTRIBUTING.md @@ -0,0 +1,32 @@ +Pull Requests and Issues are both welcome. + +# Responsiveness + +General priority order is: + +* Crashes +* Security issues in stb_image +* Bugs +* Security concerns in other libs +* Warnings +* Enhancements (new features, performance improvement, etc) + +Pull requests get priority over Issues. Some pull requests I take +as written; some I modify myself; some I will request changes before +accepting them. Because I've ended up supporting a lot of libraries +(20 as I write this, with more on the way), I am somewhat slow to +address things. Many issues have been around for a long time. + +# Pull requests + +* Make sure you're using a special branch just for this pull request. (Sometimes people unknowingly use a default branch, then later update that branch, which updates the pull request with the other changes if it hasn't been merged yet.) +* Do NOT update the version number in the file. (This just causes conflicts.) +* Do add your name to the list of contributors. (Don't worry about the formatting.) I'll try to remember to add it if you don't, but I sometimes forget as it's an extra step. +* Your change needs to compile as both C and C++. Pre-C99 compilers should be supported (e.g. declare at start of block) + +# Specific libraries + +I generally do not want new file formats for stb_image because +we are trying to improve its security, so increasing its attack +surface is counter-productive. + diff --git a/source/engine/thirdparty/stb/.github/ISSUE_TEMPLATE/bug_report.md b/source/engine/thirdparty/stb/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..a5cb26f --- /dev/null +++ b/source/engine/thirdparty/stb/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,24 @@ +--- +name: Bug report +about: if you're having trouble using a library, try the support forum instead +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. diff --git a/source/engine/thirdparty/stb/.github/ISSUE_TEMPLATE/config.yml b/source/engine/thirdparty/stb/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..85cbf6e --- /dev/null +++ b/source/engine/thirdparty/stb/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: support forum + url: https://github.com/nothings/stb/discussions/categories/q-a + about: having trouble using an stb library? don't create an issue, post in the forum diff --git a/source/engine/thirdparty/stb/.github/ISSUE_TEMPLATE/feature_request.md b/source/engine/thirdparty/stb/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..71c8763 --- /dev/null +++ b/source/engine/thirdparty/stb/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: 4 enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/source/engine/thirdparty/stb/.github/PULL_REQUEST_TEMPLATE.md b/source/engine/thirdparty/stb/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..2b10daa --- /dev/null +++ b/source/engine/thirdparty/stb/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,6 @@ +* Delete this list before clicking CREATE PULL REQUEST +* Make sure you're using a special branch just for this pull request. (Sometimes people unknowingly use a default branch, then later update that branch, which updates the pull request with the other changes if it hasn't been merged yet.) +* Do NOT update the version number in the file. (This just causes conflicts.) +* Do add your name to the list of contributors. (Don't worry about the formatting.) I'll try to remember to add it if you don't, but I sometimes forget as it's an extra step. + +If you get something above wrong, don't fret it, it's not the end of the world. diff --git a/source/engine/thirdparty/stb/.github/workflows/ci-fuzz.yml b/source/engine/thirdparty/stb/.github/workflows/ci-fuzz.yml new file mode 100644 index 0000000..332fca9 --- /dev/null +++ b/source/engine/thirdparty/stb/.github/workflows/ci-fuzz.yml @@ -0,0 +1,23 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'stb' + dry-run: false + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'stb' + fuzz-seconds: 900 + dry-run: false + - name: Upload Crash + uses: actions/upload-artifact@v1 + if: failure() + with: + name: artifacts + path: ./out/artifacts diff --git a/source/engine/thirdparty/stb/.gitignore b/source/engine/thirdparty/stb/.gitignore new file mode 100644 index 0000000..8cc774f --- /dev/null +++ b/source/engine/thirdparty/stb/.gitignore @@ -0,0 +1,3 @@ +*.o +*.obj +*.exe diff --git a/source/engine/thirdparty/stb/.travis.yml b/source/engine/thirdparty/stb/.travis.yml new file mode 100644 index 0000000..c2ad947 --- /dev/null +++ b/source/engine/thirdparty/stb/.travis.yml @@ -0,0 +1,8 @@ +language: C +arch: + - AMD64 + - ppc64le +install: true +script: + - cd tests + - make all diff --git a/source/engine/thirdparty/stb/LICENSE b/source/engine/thirdparty/stb/LICENSE new file mode 100644 index 0000000..a77ae91 --- /dev/null +++ b/source/engine/thirdparty/stb/LICENSE @@ -0,0 +1,37 @@ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/source/engine/thirdparty/stb/README.md b/source/engine/thirdparty/stb/README.md new file mode 100644 index 0000000..90f6d38 --- /dev/null +++ b/source/engine/thirdparty/stb/README.md @@ -0,0 +1,184 @@ + + +stb +=== + +single-file public domain (or MIT licensed) libraries for C/C++ + +# This project discusses security-relevant bugs in public in Github Issues and Pull Requests, and it may take significant time for security fixes to be implemented or merged. If this poses an unreasonable risk to your project, do not use stb libraries. + +Noteworthy: + +* image loader: [stb_image.h](stb_image.h) +* image writer: [stb_image_write.h](stb_image_write.h) +* image resizer: [stb_image_resize2.h](stb_image_resize2.h) +* font text rasterizer: [stb_truetype.h](stb_truetype.h) +* typesafe containers: [stb_ds.h](stb_ds.h) + +Most libraries by stb, except: stb_dxt by Fabian "ryg" Giesen, original stb_image_resize +by Jorge L. "VinoBS" Rodriguez, and stb_image_resize2 and stb_sprintf by Jeff Roberts. + + + +library | lastest version | category | LoC | description +--------------------- | ---- | -------- | --- | -------------------------------- +**[stb_vorbis.c](stb_vorbis.c)** | 1.22 | audio | 5584 | decode ogg vorbis files from file/memory to float/16-bit signed output +**[stb_hexwave.h](stb_hexwave.h)** | 0.5 | audio | 680 | audio waveform synthesizer +**[stb_image.h](stb_image.h)** | 2.29 | graphics | 7985 | image loading/decoding from file/memory: JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC +**[stb_truetype.h](stb_truetype.h)** | 1.26 | graphics | 5077 | parse, decode, and rasterize characters from truetype fonts +**[stb_image_write.h](stb_image_write.h)** | 1.16 | graphics | 1724 | image writing to disk: PNG, TGA, BMP +**[stb_image_resize2.h](stb_image_resize2.h)** | 2.04 | graphics | 10325 | resize images larger/smaller with good quality +**[stb_rect_pack.h](stb_rect_pack.h)** | 1.01 | graphics | 623 | simple 2D rectangle packer with decent quality +**[stb_perlin.h](stb_perlin.h)** | 0.5 | graphics | 428 | perlin's revised simplex noise w/ different seeds +**[stb_ds.h](stb_ds.h)** | 0.67 | utility | 1895 | typesafe dynamic array and hash tables for C, will compile in C++ +**[stb_sprintf.h](stb_sprintf.h)** | 1.10 | utility | 1906 | fast sprintf, snprintf for C/C++ +**[stb_textedit.h](stb_textedit.h)** | 1.14 | user interface | 1429 | guts of a text editor for games etc implementing them from scratch +**[stb_voxel_render.h](stb_voxel_render.h)** | 0.89 | 3D graphics | 3807 | Minecraft-esque voxel rendering "engine" with many more features +**[stb_dxt.h](stb_dxt.h)** | 1.12 | 3D graphics | 719 | Fabian "ryg" Giesen's real-time DXT compressor +**[stb_easy_font.h](stb_easy_font.h)** | 1.1 | 3D graphics | 305 | quick-and-dirty easy-to-deploy bitmap font for printing frame rate, etc +**[stb_tilemap_editor.h](stb_tilemap_editor.h)** | 0.42 | game dev | 4187 | embeddable tilemap editor +**[stb_herringbone_wa...](stb_herringbone_wang_tile.h)** | 0.7 | game dev | 1221 | herringbone Wang tile map generator +**[stb_c_lexer.h](stb_c_lexer.h)** | 0.12 | parsing | 940 | simplify writing parsers for C-like languages +**[stb_divide.h](stb_divide.h)** | 0.94 | math | 433 | more useful 32-bit modulus e.g. "euclidean divide" +**[stb_connected_comp...](stb_connected_components.h)** | 0.96 | misc | 1049 | incrementally compute reachability on grids +**[stb_leakcheck.h](stb_leakcheck.h)** | 0.6 | misc | 194 | quick-and-dirty malloc/free leak-checking +**[stb_include.h](stb_include.h)** | 0.02 | misc | 295 | implement recursive #include support, particularly for GLSL + +Total libraries: 21 +Total lines of C code: 50806 + + +FAQ +--- + +#### What's the license? + +These libraries are in the public domain. You can do anything you +want with them. You have no legal obligation +to do anything else, although I appreciate attribution. + +They are also licensed under the MIT open source license, if you have lawyers +who are unhappy with public domain. Every source file includes an explicit +dual-license for you to choose from. + +#### How do I use these libraries? + +The idea behind single-header file libraries is that they're easy to distribute and deploy +because all the code is contained in a single file. By default, the .h files in here act as +their own header files, i.e. they declare the functions contained in the file but don't +actually result in any code getting compiled. + +So in addition, you should select _exactly one_ C/C++ source file that actually instantiates +the code, preferably a file you're not editing frequently. This file should define a +specific macro (this is documented per-library) to actually enable the function definitions. +For example, to use stb_image, you should have exactly one C/C++ file that doesn't +include stb_image.h regularly, but instead does + + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + +The right macro to define is pointed out right at the top of each of these libraries. + +#### Are there other single-file public-domain/open source libraries with minimal dependencies out there? + +[Yes.](https://github.com/nothings/single_file_libs) + +#### If I wrap an stb library in a new library, does the new library have to be public domain/MIT? + +No, because it's public domain you can freely relicense it to whatever license your new +library wants to be. + +#### What's the deal with SSE support in GCC-based compilers? + +stb_image will either use SSE2 (if you compile with -msse2) or +will not use any SIMD at all, rather than trying to detect the +processor at runtime and handle it correctly. As I understand it, +the approved path in GCC for runtime-detection require +you to use multiple source files, one for each CPU configuration. +Because stb_image is a header-file library that compiles in only +one source file, there's no approved way to build both an +SSE-enabled and a non-SSE-enabled variation. + +While we've tried to work around it, we've had multiple issues over +the years due to specific versions of gcc breaking what we're doing, +so we've given up on it. See https://github.com/nothings/stb/issues/280 +and https://github.com/nothings/stb/issues/410 for examples. + +#### Some of these libraries seem redundant to existing open source libraries. Are they better somehow? + +Generally they're only better in that they're easier to integrate, +easier to use, and easier to release (single file; good API; no +attribution requirement). They may be less featureful, slower, +and/or use more memory. If you're already using an equivalent +library, there's probably no good reason to switch. + +#### Can I link directly to the table of stb libraries? + +You can use [this URL](https://github.com/nothings/stb#stb_libs) to link directly to that list. + +#### Why do you list "lines of code"? It's a terrible metric. + +Just to give you some idea of the internal complexity of the library, +to help you manage your expectations, or to let you know what you're +getting into. While not all the libraries are written in the same +style, they're certainly similar styles, and so comparisons between +the libraries are probably still meaningful. + +Note though that the lines do include both the implementation, the +part that corresponds to a header file, and the documentation. + +#### Why single-file headers? + +Windows doesn't have standard directories where libraries +live. That makes deploying libraries in Windows a lot more +painful than open source developers on Unix-derivates generally +realize. (It also makes library dependencies a lot worse in Windows.) + +There's also a common problem in Windows where a library was built +against a different version of the runtime library, which causes +link conflicts and confusion. Shipping the libs as headers means +you normally just compile them straight into your project without +making libraries, thus sidestepping that problem. + +Making them a single file makes it very easy to just +drop them into a project that needs them. (Of course you can +still put them in a proper shared library tree if you want.) + +Why not two files, one a header and one an implementation? +The difference between 10 files and 9 files is not a big deal, +but the difference between 2 files and 1 file is a big deal. +You don't need to zip or tar the files up, you don't have to +remember to attach *two* files, etc. + +#### Why "stb"? Is this something to do with Set-Top Boxes? + +No, they are just the initials for my name, Sean T. Barrett. +This was not chosen out of egomania, but as a moderately sane +way of namespacing the filenames and source function names. + +#### Will you add more image types to stb_image.h? + +No. As stb_image use has grown, it has become more important +for us to focus on security of the codebase. Adding new image +formats increases the amount of code we need to secure, so it +is no longer worth adding new formats. + +#### Do you have any advice on how to create my own single-file library? + +Yes. https://github.com/nothings/stb/blob/master/docs/stb_howto.txt + +#### Why public domain? + +I prefer it over GPL, LGPL, BSD, zlib, etc. for many reasons. +Some of them are listed here: +https://github.com/nothings/stb/blob/master/docs/why_public_domain.md + +#### Why C? + +Primarily, because I use C, not C++. But it does also make it easier +for other people to use them from other languages. + +#### Why not C99? stdint.h, declare-anywhere, etc. + +I still use MSVC 6 (1998) as my IDE because it has better human factors +for me than later versions of MSVC. diff --git a/source/engine/thirdparty/stb/data/atari_8bit_font_revised.png b/source/engine/thirdparty/stb/data/atari_8bit_font_revised.png new file mode 100644 index 0000000000000000000000000000000000000000..91c553c6387fd5eb6c09db3b1aa05ede45d4ba82 GIT binary patch literal 1769 zcmVU8P*7-ZbZ>KLZ*U+lnSp_Ufq@}0xwybFAi#%#fq@|}KQEO56)-X|e7nZL z$iTqBa9P*U#mSX{G{Bl%P*lRez;J+pfx##xwK$o9f#C}S14DXwNkIt%17i#W1A|CX zc0maP17iUL1A|C*NRTrF17iyV0~1e4YDEbH0|SF|enDkXW_m`6f}y3QrGjHhep0GJ zaAk2xYHqQDXI^rCQ9*uDVo7QW0|Nup4h9AW240u^5(W3f%sd4n162kpgNVo|1qcff zJ_s=cNG>fZg9jx8g8+j9g8_pBLjXe}Lp{R+hNBE`7{wV~7)u#fFy3PlV+vxLz;uCG zm^qSpA@ds+OO_6nTdaDlt*rOhEZL^9ePa)2-_4=K(Z%tFGm-NGmm}8}ZcXk5JW@PU zd4+f<@d@)yL(o<5icqT158+-B6_LH7;i6x}CW#w~Uy-Pgl#@Irl`kzV zeL|*8R$ca%T%Wv){2zs_iiJvgN^h0dsuZZ2sQy$tsNSU!s;Q*;LF<6_B%M@UD?LHI zSNcZ`78uqV#TeU~$eS{ozBIdFzSClfs*^S+dw;4dus<{M;#|MXC)T}S9v!D zcV!QCPhBq)ZyO(X-(bH4|NMaZz==UigLj2o41F2S6d@OB6%`R(5i>J(Puzn9wnW{e zu;hl6HK{k#IWjCVGqdJqU(99Cv(K+6*i`tgSi2;vbXD1#3jNBGs$DgVwO(~o>mN4i zHPtkqZIx>)Y(Ls5-Br|mx>vQYvH$Kwn@O`L|D75??eGkZnfg$5<;Xeg_o%+-I&+-3%01W^SH2RkDT>t<8AY({UO#lFTB>(_`g8%^e z{{R4h=>PzAFaQARU;qF*m;eA5Z<1fdMgRZ9Ya zfbQPS&6I9gN+2Pe@ZLVg#M7Yp7n=!*KBhs3V0@#wJYRvu4-EIh0eFD(WQ_!C0MS1yUwmi$ zJOuHM4kldFpro=@Axc~(JETEnh|ZpJz-E{swgX>4b&wnoTOZ>V&NW)JLl@EyagL5* z<%k38M}Yb>cl!Y3=nD{U?_0&j{5?*Mi=jpZqlp3NKM;{arC$))B zMejr(NF3})kH|P*J0!7;eV@V{PN3FAk#h8PVjiYvxZBBribS1Mmc9dOO0dyfO4tnn zK8E_624=rWe)H>v@3q07AGsTI9`c06a zBi|D*8`IJkCmwYDgqD_3QJpK^xBahAKCuD6l`Ca?g-)%{ag9C=&Yhx@1%%J?d6lf4 zQZU{emJ$p<+{ovqV1Jg+TUjyW^UBSQ#9pzkz|;~WyACtFJ3t(Z*0X<@>#=RhmhJALb# zqPNH3e6XUIB`hrGNSf;NlZCE6p9hSX3v7oSaOL0nxvA6QVph1qgEw(*3Jb)2oKG=j zlo+eWDSQqKaae^lrurOPvD>l+CW8l8QnXBWR~D(o-ap}$g0GlL20-|dP~QmIdiX|tuH~m;D>P3r z)#qd&2P>y<+ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/data/easy_font_raw.png b/source/engine/thirdparty/stb/data/easy_font_raw.png new file mode 100644 index 0000000000000000000000000000000000000000..2f08148b4614413c1c975ab264ca563fdf667c65 GIT binary patch literal 645 zcmV;00($+4P)dL_t(|UhS5FYQr!LMDHp4|8E|o2WL&hWbGJiRCUf$ zY{`)?s-q7{A4XlE+q@aq`w+kGn&`JeMBR@DHueUS=)8LYGtsDfr|vv4(Bu9ac}tzY z2j~Pid+-f}c9Mx;I0Ir^(F{}YpnIgUhRPP+y~S(gUW5JS8hGbs@i?eVmxG< zJ&U8a3qQ>wGB?wcgM&3m^G!?mg2nQbyDTC%*XSClo@CL6^-_J4ML!?v%VtfoaY#^W znS)E8gA7`nI0*SZ(J1c-;$Xg7DEkJiEFwCKsm1k0O2VcY07-K1DonO=GB}YFGT?j@ zK58ut#5*G(gM{aV#rn#kAtP2*m88~rsW<17E1PM#de0a^AIOrMG=c2FU{NHj4V5(% zNiEtp4=!zMG3(}PB}4w0ttA4okW~Qg)M9OFQJN9_^K?xXt19&t@?u$bEW!ngXv<=X zrqZ&j9_V*aMQ f1)BOYi~B&Iu%ieWP969(00000NkvXXu0mjfRf{XR literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/data/herringbone/license.txt b/source/engine/thirdparty/stb/data/herringbone/license.txt new file mode 100644 index 0000000..11ffc42 --- /dev/null +++ b/source/engine/thirdparty/stb/data/herringbone/license.txt @@ -0,0 +1,4 @@ +All files in this directory are in the public domain. Where +a public domain declaration is not recognized, you are granted +a license to freely use, modify, and redistribute them in +any way you choose. \ No newline at end of file diff --git a/source/engine/thirdparty/stb/data/herringbone/template_caves_limit_connectivity.png b/source/engine/thirdparty/stb/data/herringbone/template_caves_limit_connectivity.png new file mode 100644 index 0000000000000000000000000000000000000000..1c286e7ce44665d478b9bf4c767826793bab28b4 GIT binary patch literal 10818 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ⅇhl(t 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 M07*qoM6N<$f^o<=cK`qY literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/data/herringbone/template_caves_tiny_corridors.png b/source/engine/thirdparty/stb/data/herringbone/template_caves_tiny_corridors.png new file mode 100644 index 0000000000000000000000000000000000000000..e9b0d444eade835c71554eb09e253d0d4c541fb1 GIT binary patch literal 4904 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 a@$UfWm`XT>Heh}L0000OtuM9NCZ-^ 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/data/herringbone/template_horizontal_corridors_v3.png b/source/engine/thirdparty/stb/data/herringbone/template_horizontal_corridors_v3.png new file mode 100644 index 0000000000000000000000000000000000000000..f921807255481904cd8800be3adff4cd7138acb1 GIT binary patch literal 6766 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 Qk literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/data/herringbone/template_limit_connectivity_fat.png b/source/engine/thirdparty/stb/data/herringbone/template_limit_connectivity_fat.png new file mode 100644 index 0000000000000000000000000000000000000000..dda13023c77749a9f5dfe7d36eb9d5c62404e70b GIT binary patch literal 6655 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/data/herringbone/template_limited_connectivity.png b/source/engine/thirdparty/stb/data/herringbone/template_limited_connectivity.png new file mode 100644 index 0000000000000000000000000000000000000000..d9f97c9c6463b3ccba096d946e86412eb57e8846 GIT binary patch literal 6728 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/data/herringbone/template_maze_plus_2_wide.png b/source/engine/thirdparty/stb/data/herringbone/template_maze_plus_2_wide.png new file mode 100644 index 0000000000000000000000000000000000000000..d27f7bd8762461db38869c8397ca335f7619de3d GIT binary patch literal 13141 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 }>*>vQfd-W65-M^3{ff6BuI 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%0DA5(W={doe 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 G^8W$t>>vFA literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/data/herringbone/template_rooms_and_corridors.png b/source/engine/thirdparty/stb/data/herringbone/template_rooms_and_corridors.png new file mode 100644 index 0000000000000000000000000000000000000000..c0467f3e92b00086f4d74ca4498ed4f1b6156d64 GIT binary patch literal 4736 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�Ea(pn}$By;{m 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 Y)$SCh)qml~ifuP#?FV;25@0Cg$+ng9R* literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/data/herringbone/template_round_rooms_diagonal_corridors.png b/source/engine/thirdparty/stb/data/herringbone/template_round_rooms_diagonal_corridors.png new file mode 100644 index 0000000000000000000000000000000000000000..2073f988906f634b1c0f884470df9f10c8bac369 GIT binary patch literal 8172 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 bwErIfH*5$lY+ElB00000NkvXXu0mjfWy4L8 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/data/herringbone/template_simple_caves_2_wide.png b/source/engine/thirdparty/stb/data/herringbone/template_simple_caves_2_wide.png new file mode 100644 index 0000000000000000000000000000000000000000..321727146615e7644ced4a1c96157cae96cf76be GIT binary patch literal 15712 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 UDo4cI6&8SkjH+~%)Ti(N2Vn*JQ~&?~ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/data/herringbone/template_square_rooms_with_random_rects.png b/source/engine/thirdparty/stb/data/herringbone/template_square_rooms_with_random_rects.png new file mode 100644 index 0000000000000000000000000000000000000000..0d7e82e560c996d89533869f71aa66aed4e9dbf4 GIT binary patch literal 4772 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/deprecated/rrsprintf.h b/source/engine/thirdparty/stb/deprecated/rrsprintf.h new file mode 100644 index 0000000..62962e3 --- /dev/null +++ b/source/engine/thirdparty/stb/deprecated/rrsprintf.h @@ -0,0 +1,1055 @@ +#ifndef RR_SPRINTF_H_INCLUDE +#define RR_SPRINTF_H_INCLUDE + +/* +Single file sprintf replacement. + +Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. +Hereby placed in public domain. + +This is a full sprintf replacement that supports everything that +the C runtime sprintfs support, including float/double, 64-bit integers, +hex floats, field parameters (%*.*d stuff), length reads backs, etc. + +Why would you need this if sprintf already exists? Well, first off, +it's *much* faster (see below). It's also much smaller than the CRT +versions code-space-wise. We've also added some simple improvements +that are super handy (commas in thousands, callbacks at buffer full, +for example). Finally, the format strings for MSVC and GCC differ +for 64-bit integers (among other small things), so this lets you use +the same format strings in cross platform code. + +It uses the standard single file trick of being both the header file +and the source itself. If you just include it normally, you just get +the header file function definitions. To get the code, you include +it from a C or C++ file and define RR_SPRINTF_IMPLEMENTATION first. + +It only uses va_args macros from the C runtime to do it's work. It +does cast doubles to S64s and shifts and divides U64s, which does +drag in CRT code on most platforms. + +It compiles to roughly 8K with float support, and 4K without. +As a comparison, when using MSVC static libs, calling sprintf drags +in 16K. + +API: +==== +int rrsprintf( char * buf, char const * fmt, ... ) +int rrsnprintf( char * buf, int count, char const * fmt, ... ) + Convert an arg list into a buffer. rrsnprintf always returns + a zero-terminated string (unlike regular snprintf). + +int rrvsprintf( char * buf, char const * fmt, va_list va ) +int rrvsnprintf( char * buf, int count, char const * fmt, va_list va ) + Convert a va_list arg list into a buffer. rrvsnprintf always returns + a zero-terminated string (unlike regular snprintf). + +int rrvsprintfcb( RRSPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) + typedef char * RRSPRINTFCB( char const * buf, void * user, int len ); + Convert into a buffer, calling back every RR_SPRINTF_MIN chars. + Your callback can then copy the chars out, print them or whatever. + This function is actually the workhorse for everything else. + The buffer you pass in must hold at least RR_SPRINTF_MIN characters. + // you return the next buffer to use or 0 to stop converting + +void rrsetseparators( char comma, char period ) + Set the comma and period characters to use. + +FLOATS/DOUBLES: +=============== +This code uses a internal float->ascii conversion method that uses +doubles with error correction (double-doubles, for ~105 bits of +precision). This conversion is round-trip perfect - that is, an atof +of the values output here will give you the bit-exact double back. + +One difference is that our insignificant digits will be different than +with MSVC or GCC (but they don't match each other either). We also +don't attempt to find the minimum length matching float (pre-MSVC15 +doesn't either). + +If you don't need float or doubles at all, define RR_SPRINTF_NOFLOAT +and you'll save 4K of code space. + +64-BIT INTS: +============ +This library also supports 64-bit integers and you can use MSVC style or +GCC style indicators (%I64d or %lld). It supports the C99 specifiers +for size_t and ptr_diff_t (%jd %zd) as well. + +EXTRAS: +======= +Like some GCCs, for integers and floats, you can use a ' (single quote) +specifier and commas will be inserted on the thousands: "%'d" on 12345 +would print 12,345. + +For integers and floats, you can use a "$" specifier and the number +will be converted to float and then divided to get kilo, mega, giga or +tera and then printed, so "%$d" 1024 is "1.0 k", "%$.2d" 2536000 is +"2.42 m", etc. + +In addition to octal and hexadecimal conversions, you can print +integers in binary: "%b" for 256 would print 100. + +PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): +=================================================================== +"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) +"%24d" across all 32-bit ints (4.5x/4.2x faster) +"%x" across all 32-bit ints (4.5x/3.8x faster) +"%08x" across all 32-bit ints (4.3x/3.8x faster) +"%f" across e-10 to e+10 floats (7.3x/6.0x faster) +"%e" across e-10 to e+10 floats (8.1x/6.0x faster) +"%g" across e-10 to e+10 floats (10.0x/7.1x faster) +"%f" for values near e-300 (7.9x/6.5x faster) +"%f" for values near e+300 (10.0x/9.1x faster) +"%e" for values near e-300 (10.1x/7.0x faster) +"%e" for values near e+300 (9.2x/6.0x faster) +"%.320f" for values near e-300 (12.6x/11.2x faster) +"%a" for random values (8.6x/4.3x faster) +"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) +"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) +"%s%s%s" for 64 char strings (7.1x/7.3x faster) +"...512 char string..." ( 35.0x/32.5x faster!) +*/ + +#ifdef RR_SPRINTF_STATIC +#define RRPUBLIC_DEC static +#define RRPUBLIC_DEF static +#else +#ifdef __cplusplus +#define RRPUBLIC_DEC extern "C" +#define RRPUBLIC_DEF extern "C" +#else +#define RRPUBLIC_DEC extern +#define RRPUBLIC_DEF +#endif +#endif + +#include // for va_list() + +#ifndef RR_SPRINTF_MIN +#define RR_SPRINTF_MIN 512 // how many characters per callback +#endif +typedef char * RRSPRINTFCB( char * buf, void * user, int len ); + +#ifndef RR_SPRINTF_DECORATE +#define RR_SPRINTF_DECORATE(name) rr##name // define this before including if you want to change the names +#endif + +#ifndef RR_SPRINTF_IMPLEMENTATION + +RRPUBLIC_DEF int RR_SPRINTF_DECORATE( vsprintf )( char * buf, char const * fmt, va_list va ); +RRPUBLIC_DEF int RR_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ); +RRPUBLIC_DEF int RR_SPRINTF_DECORATE( sprintf ) ( char * buf, char const * fmt, ... ); +RRPUBLIC_DEF int RR_SPRINTF_DECORATE( snprintf )( char * buf, int count, char const * fmt, ... ); + +RRPUBLIC_DEF int RR_SPRINTF_DECORATE( vsprintfcb )( RRSPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ); +RRPUBLIC_DEF void RR_SPRINTF_DECORATE( setseparators )( char comma, char period ); + +#else + +#include // for va_arg() + +#define rU32 unsigned int +#define rS32 signed int + +#ifdef _MSC_VER +#define rU64 unsigned __int64 +#define rS64 signed __int64 +#else +#define rU64 unsigned long long +#define rS64 signed long long +#endif +#define rU16 unsigned short + +#ifndef rUINTa +#if defined(__ppc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) +#define rUINTa rU64 +#else +#define rUINTa rU32 +#endif +#endif + +#ifndef RR_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) +#if defined(_MSC_VER) && (_MSC_VER<1900) +#define RR_SPRINTF_MSVC_MODE +#endif +#endif + +#ifdef RR_SPRINTF_NOUNALIGNED // define this before inclusion to force rrsprint to always use aligned accesses +#define RR_UNALIGNED(code) +#else +#define RR_UNALIGNED(code) code +#endif + +#ifndef RR_SPRINTF_NOFLOAT +// internal float utility functions +static rS32 rrreal_to_str( char const * * start, rU32 * len, char *out, rS32 * decimal_pos, double value, rU32 frac_digits ); +static rS32 rrreal_to_parts( rS64 * bits, rS32 * expo, double value ); +#define RRSPECIAL 0x7000 +#endif + +static char RRperiod='.'; +static char RRcomma=','; +static char rrdiglookup[201]="00010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899"; + +RRPUBLIC_DEF void RR_SPRINTF_DECORATE( setseparators )( char pcomma, char pperiod ) +{ + RRperiod=pperiod; + RRcomma=pcomma; +} + +RRPUBLIC_DEF int RR_SPRINTF_DECORATE( vsprintfcb )( RRSPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) +{ + static char hex[]="0123456789abcdefxp"; + static char hexu[]="0123456789ABCDEFXP"; + char * bf; + char const * f; + int tlen = 0; + + bf = buf; + f = fmt; + for(;;) + { + rS32 fw,pr,tz; rU32 fl; + + #define LJ 1 + #define LP 2 + #define LS 4 + #define LX 8 + #define LZ 16 + #define BI 32 + #define CS 64 + #define NG 128 + #define KI 256 + #define HW 512 + + // macros for the callback buffer stuff + #define chk_cb_bufL(bytes) { int len = (int)(bf-buf); if ((len+(bytes))>=RR_SPRINTF_MIN) { tlen+=len; if (0==(bf=buf=callback(buf,user,len))) goto done; } } + #define chk_cb_buf(bytes) { if ( callback ) { chk_cb_bufL(bytes); } } + #define flush_cb() { chk_cb_bufL(RR_SPRINTF_MIN-1); } //flush if there is even one byte in the buffer + #define cb_buf_clamp(cl,v) cl = v; if ( callback ) { int lg = RR_SPRINTF_MIN-(int)(bf-buf); if (cl>lg) cl=lg; } + + // fast copy everything up to the next % (or end of string) + for(;;) + { + while (((rUINTa)f)&3) + { + schk1: if (f[0]=='%') goto scandd; + schk2: if (f[0]==0) goto endfmt; + chk_cb_buf(1); *bf++=f[0]; ++f; + } + for(;;) + { + rU32 v,c; + v=*(rU32*)f; c=(~v)&0x80808080; + if ((v-0x26262626)&c) goto schk1; + if ((v-0x01010101)&c) goto schk2; + if (callback) if ((RR_SPRINTF_MIN-(int)(bf-buf))<4) goto schk1; + *(rU32*)bf=v; bf+=4; f+=4; + } + } scandd: + + ++f; + + // ok, we have a percent, read the modifiers first + fw = 0; pr = -1; fl = 0; tz = 0; + + // flags + for(;;) + { + switch(f[0]) + { + // if we have left just + case '-': fl|=LJ; ++f; continue; + // if we have leading plus + case '+': fl|=LP; ++f; continue; + // if we have leading space + case ' ': fl|=LS; ++f; continue; + // if we have leading 0x + case '#': fl|=LX; ++f; continue; + // if we have thousand commas + case '\'': fl|=CS; ++f; continue; + // if we have kilo marker + case '$': fl|=KI; ++f; continue; + // if we have leading zero + case '0': fl|=LZ; ++f; goto flags_done; + default: goto flags_done; + } + } + flags_done: + + // get the field width + if ( f[0] == '*' ) {fw = va_arg(va,rU32); ++f;} else { while (( f[0] >= '0' ) && ( f[0] <= '9' )) { fw = fw * 10 + f[0] - '0'; f++; } } + // get the precision + if ( f[0]=='.' ) { ++f; if ( f[0] == '*' ) {pr = va_arg(va,rU32); ++f;} else { pr = 0; while (( f[0] >= '0' ) && ( f[0] <= '9' )) { pr = pr * 10 + f[0] - '0'; f++; } } } + + // handle integer size overrides + switch(f[0]) + { + // are we halfwidth? + case 'h': fl|=HW; ++f; break; + // are we 64-bit (unix style) + case 'l': ++f; if ( f[0]=='l') { fl|=BI; ++f; } break; + // are we 64-bit on intmax? (c99) + case 'j': fl|=BI; ++f; break; + // are we 64-bit on size_t or ptrdiff_t? (c99) + case 'z': case 't': fl|=((sizeof(char*)==8)?BI:0); ++f; break; + // are we 64-bit (msft style) + case 'I': if ( ( f[1]=='6') && ( f[2]=='4') ) { fl|=BI; f+=3; } else if ( ( f[1]=='3') && ( f[2]=='2') ) { f+=3; } else { fl|=((sizeof(void*)==8)?BI:0); ++f; } break; + default: break; + } + + // handle each replacement + switch( f[0] ) + { + #define NUMSZ 512 // big enough for e308 (with commas) or e-307 + char num[NUMSZ]; + char lead[8]; + char tail[8]; + char *s; + char const *h; + rU32 l,n,cs; + rU64 n64; + #ifndef RR_SPRINTF_NOFLOAT + double fv; + #endif + rS32 dp; char const * sn; + + case 's': + // get the string + s = va_arg(va,char*); if (s==0) s = (char*)"null"; + // get the length + sn = s; + for(;;) + { + if ((((rUINTa)sn)&3)==0) break; + lchk: + if (sn[0]==0) goto ld; + ++sn; + } + n = 0xffffffff; + if (pr>=0) { n=(rU32)(sn-s); if (n>=(rU32)pr) goto ld; n=((rU32)(pr-n))>>2; } + while(n) + { + rU32 v=*(rU32*)sn; + if ((v-0x01010101)&(~v)&0x80808080UL) goto lchk; + sn+=4; + --n; + } + goto lchk; + ld: + + l = (rU32) ( sn - s ); + // clamp to precision + if ( l > (rU32)pr ) l = pr; + lead[0]=0; tail[0]=0; pr = 0; dp = 0; cs = 0; + // copy the string in + goto scopy; + + case 'c': // char + // get the character + s = num + NUMSZ -1; *s = (char)va_arg(va,int); + l = 1; + lead[0]=0; tail[0]=0; pr = 0; dp = 0; cs = 0; + goto scopy; + + case 'n': // weird write-bytes specifier + { int * d = va_arg(va,int*); + *d = tlen + (int)( bf - buf ); } + break; + +#ifdef RR_SPRINTF_NOFLOAT + case 'A': // float + case 'a': // hex float + case 'G': // float + case 'g': // float + case 'E': // float + case 'e': // float + case 'f': // float + va_arg(va,double); // eat it + s = (char*)"No float"; + l = 8; + lead[0]=0; tail[0]=0; pr = 0; dp = 0; cs = 0; + goto scopy; +#else + case 'A': // float + h=hexu; + goto hexfloat; + + case 'a': // hex float + h=hex; + hexfloat: + fv = va_arg(va,double); + if (pr==-1) pr=6; // default is 6 + // read the double into a string + if ( rrreal_to_parts( (rS64*)&n64, &dp, fv ) ) + fl |= NG; + + s = num+64; + + // sign + lead[0]=0; if (fl&NG) { lead[0]=1; lead[1]='-'; } else if (fl&LS) { lead[0]=1; lead[1]=' '; } else if (fl&LP) { lead[0]=1; lead[1]='+'; }; + + if (dp==-1023) dp=(n64)?-1022:0; else n64|=(((rU64)1)<<52); + n64<<=(64-56); + if (pr<15) n64+=((((rU64)8)<<56)>>(pr*4)); + // add leading chars + + #ifdef RR_SPRINTF_MSVC_MODE + *s++='0';*s++='x'; + #else + lead[1+lead[0]]='0'; lead[2+lead[0]]='x'; lead[0]+=2; + #endif + *s++=h[(n64>>60)&15]; n64<<=4; + if ( pr ) *s++=RRperiod; + sn = s; + + // print the bits + n = pr; if (n>13) n = 13; if (pr>(rS32)n) tz=pr-n; pr = 0; + while(n--) { *s++=h[(n64>>60)&15]; n64<<=4; } + + // print the expo + tail[1]=h[17]; + if (dp<0) { tail[2]='-'; dp=-dp;} else tail[2]='+'; + n = (dp>=1000)?6:((dp>=100)?5:((dp>=10)?4:3)); + tail[0]=(char)n; + for(;;) { tail[n]='0'+dp%10; if (n<=3) break; --n; dp/=10; } + + dp = (int)(s-sn); + l = (int)(s-(num+64)); + s = num+64; + cs = 1 + (3<<24); + goto scopy; + + case 'G': // float + h=hexu; + goto dosmallfloat; + + case 'g': // float + h=hex; + dosmallfloat: + fv = va_arg(va,double); + if (pr==-1) pr=6; else if (pr==0) pr = 1; // default is 6 + // read the double into a string + if ( rrreal_to_str( &sn, &l, num, &dp, fv, (pr-1)|0x80000000 ) ) + fl |= NG; + + // clamp the precision and delete extra zeros after clamp + n = pr; + if ( l > (rU32)pr ) l = pr; while ((l>1)&&(pr)&&(sn[l-1]=='0')) { --pr; --l; } + + // should we use %e + if ((dp<=-4)||(dp>(rS32)n)) + { + if ( pr > (rS32)l ) pr = l-1; else if ( pr ) --pr; // when using %e, there is one digit before the decimal + goto doexpfromg; + } + // this is the insane action to get the pr to match %g sematics for %f + if(dp>0) { pr=(dp<(rS32)l)?l-dp:0; } else { pr = -dp+((pr>(rS32)l)?l:pr); } + goto dofloatfromg; + + case 'E': // float + h=hexu; + goto doexp; + + case 'e': // float + h=hex; + doexp: + fv = va_arg(va,double); + if (pr==-1) pr=6; // default is 6 + // read the double into a string + if ( rrreal_to_str( &sn, &l, num, &dp, fv, pr|0x80000000 ) ) + fl |= NG; + doexpfromg: + tail[0]=0; + lead[0]=0; if (fl&NG) { lead[0]=1; lead[1]='-'; } else if (fl&LS) { lead[0]=1; lead[1]=' '; } else if (fl&LP) { lead[0]=1; lead[1]='+'; }; + if ( dp == RRSPECIAL ) { s=(char*)sn; cs=0; pr=0; goto scopy; } + s=num+64; + // handle leading chars + *s++=sn[0]; + + if (pr) *s++=RRperiod; + + // handle after decimal + if ((l-1)>(rU32)pr) l=pr+1; + for(n=1;n=100)?5:4; + #endif + tail[0]=(char)n; + for(;;) { tail[n]='0'+dp%10; if (n<=3) break; --n; dp/=10; } + cs = 1 + (3<<24); // how many tens + goto flt_lead; + + case 'f': // float + fv = va_arg(va,double); + doafloat: + // do kilos + if (fl&KI) {while(fl<0x4000000) { if ((fv<1024.0) && (fv>-1024.0)) break; fv/=1024.0; fl+=0x1000000; }} + if (pr==-1) pr=6; // default is 6 + // read the double into a string + if ( rrreal_to_str( &sn, &l, num, &dp, fv, pr ) ) + fl |= NG; + dofloatfromg: + tail[0]=0; + // sign + lead[0]=0; if (fl&NG) { lead[0]=1; lead[1]='-'; } else if (fl&LS) { lead[0]=1; lead[1]=' '; } else if (fl&LP) { lead[0]=1; lead[1]='+'; }; + if ( dp == RRSPECIAL ) { s=(char*)sn; cs=0; pr=0; goto scopy; } + s=num+64; + + // handle the three decimal varieties + if (dp<=0) + { + rS32 i; + // handle 0.000*000xxxx + *s++='0'; if (pr) *s++=RRperiod; + n=-dp; if((rS32)n>pr) n=pr; i=n; while(i) { if ((((rUINTa)s)&3)==0) break; *s++='0'; --i; } while(i>=4) { *(rU32*)s=0x30303030; s+=4; i-=4; } while(i) { *s++='0'; --i; } + if ((rS32)(l+n)>pr) l=pr-n; i=l; while(i) { *s++=*sn++; --i; } + tz = pr-(n+l); + cs = 1 + (3<<24); // how many tens did we write (for commas below) + } + else + { + cs = (fl&CS)?((600-(rU32)dp)%3):0; + if ((rU32)dp>=l) + { + // handle xxxx000*000.0 + n=0; for(;;) { if ((fl&CS) && (++cs==4)) { cs = 0; *s++=RRcomma; } else { *s++=sn[n]; ++n; if (n>=l) break; } } + if (n<(rU32)dp) + { + n = dp - n; + if ((fl&CS)==0) { while(n) { if ((((rUINTa)s)&3)==0) break; *s++='0'; --n; } while(n>=4) { *(rU32*)s=0x30303030; s+=4; n-=4; } } + while(n) { if ((fl&CS) && (++cs==4)) { cs = 0; *s++=RRcomma; } else { *s++='0'; --n; } } + } + cs = (int)(s-(num+64)) + (3<<24); // cs is how many tens + if (pr) { *s++=RRperiod; tz=pr;} + } + else + { + // handle xxxxx.xxxx000*000 + n=0; for(;;) { if ((fl&CS) && (++cs==4)) { cs = 0; *s++=RRcomma; } else { *s++=sn[n]; ++n; if (n>=(rU32)dp) break; } } + cs = (int)(s-(num+64)) + (3<<24); // cs is how many tens + if (pr) *s++=RRperiod; + if ((l-dp)>(rU32)pr) l=pr+dp; + while(n>24) { tail[2]="_kmgt"[fl>>24]; tail[0]=2; } } }; + + flt_lead: + // get the length that we copied + l = (rU32) ( s-(num+64) ); + s=num+64; + goto scopy; +#endif + + case 'B': // upper binary + h = hexu; + goto binary; + + case 'b': // lower binary + h = hex; + binary: + lead[0]=0; + if (fl&LX) { lead[0]=2;lead[1]='0';lead[2]=h[0xb]; } + l=(8<<4)|(1<<8); + goto radixnum; + + case 'o': // octal + h = hexu; + lead[0]=0; + if (fl&LX) { lead[0]=1;lead[1]='0'; } + l=(3<<4)|(3<<8); + goto radixnum; + + case 'p': // pointer + fl |= (sizeof(void*)==8)?BI:0; + pr = sizeof(void*)*2; + fl &= ~LZ; // 'p' only prints the pointer with zeros + // drop through to X + + case 'X': // upper binary + h = hexu; + goto dohexb; + + case 'x': // lower binary + h = hex; dohexb: + l=(4<<4)|(4<<8); + lead[0]=0; + if (fl&LX) { lead[0]=2;lead[1]='0';lead[2]=h[16]; } + radixnum: + // get the number + if ( fl&BI ) + n64 = va_arg(va,rU64); + else + n64 = va_arg(va,rU32); + + s = num + NUMSZ; dp = 0; + // clear tail, and clear leading if value is zero + tail[0]=0; if (n64==0) { lead[0]=0; if (pr==0) { l=0; cs = ( ((l>>4)&15)) << 24; goto scopy; } } + // convert to string + for(;;) { *--s = h[n64&((1<<(l>>8))-1)]; n64>>=(l>>8); if ( ! ( (n64) || ((rS32) ( (num+NUMSZ) - s ) < pr ) ) ) break; if ( fl&CS) { ++l; if ((l&15)==((l>>4)&15)) { l&=~15; *--s=RRcomma; } } }; + // get the tens and the comma pos + cs = (rU32) ( (num+NUMSZ) - s ) + ( ( ((l>>4)&15)) << 24 ); + // get the length that we copied + l = (rU32) ( (num+NUMSZ) - s ); + // copy it + goto scopy; + + case 'u': // unsigned + case 'i': + case 'd': // integer + // get the integer and abs it + if ( fl&BI ) + { + rS64 i64 = va_arg(va,rS64); n64 = (rU64)i64; if ((f[0]!='u') && (i64<0)) { n64=(rU64)-i64; fl|=NG; } + } + else + { + rS32 i = va_arg(va,rS32); n64 = (rU32)i; if ((f[0]!='u') && (i<0)) { n64=(rU32)-i; fl|=NG; } + } + + #ifndef RR_SPRINTF_NOFLOAT + if (fl&KI) { if (n64<1024) pr=0; else if (pr==-1) pr=1; fv=(double)(rS64)n64; goto doafloat; } + #endif + + // convert to string + s = num+NUMSZ; l=0; + + for(;;) + { + // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) + char * o=s-8; + if (n64>=100000000) { n = (rU32)( n64 % 100000000); n64 /= 100000000; } else {n = (rU32)n64; n64 = 0; } + if((fl&CS)==0) { while(n) { s-=2; *(rU16*)s=*(rU16*)&rrdiglookup[(n%100)*2]; n/=100; } } + while (n) { if ( ( fl&CS) && (l++==3) ) { l=0; *--s=RRcomma; --o; } else { *--s=(char)(n%10)+'0'; n/=10; } } + if (n64==0) { if ((s[0]=='0') && (s!=(num+NUMSZ))) ++s; break; } + while (s!=o) if ( ( fl&CS) && (l++==3) ) { l=0; *--s=RRcomma; --o; } else { *--s='0'; } + } + + tail[0]=0; + // sign + lead[0]=0; if (fl&NG) { lead[0]=1; lead[1]='-'; } else if (fl&LS) { lead[0]=1; lead[1]=' '; } else if (fl&LP) { lead[0]=1; lead[1]='+'; }; + + // get the length that we copied + l = (rU32) ( (num+NUMSZ) - s ); if ( l == 0 ) { *--s='0'; l = 1; } + cs = l + (3<<24); + if (pr<0) pr = 0; + + scopy: + // get fw=leading/trailing space, pr=leading zeros + if (pr<(rS32)l) pr = l; + n = pr + lead[0] + tail[0] + tz; + if (fw<(rS32)n) fw = n; + fw -= n; + pr -= l; + + // handle right justify and leading zeros + if ( (fl&LJ)==0 ) + { + if (fl&LZ) // if leading zeros, everything is in pr + { + pr = (fw>pr)?fw:pr; + fw = 0; + } + else + { + fl &= ~CS; // if no leading zeros, then no commas + } + } + + // copy the spaces and/or zeros + if (fw+pr) + { + rS32 i; rU32 c; + + // copy leading spaces (or when doing %8.4d stuff) + if ( (fl&LJ)==0 ) while(fw>0) { cb_buf_clamp(i,fw); fw -= i; while(i) { if ((((rUINTa)bf)&3)==0) break; *bf++=' '; --i; } while(i>=4) { *(rU32*)bf=0x20202020; bf+=4; i-=4; } while (i) {*bf++=' '; --i;} chk_cb_buf(1); } + + // copy leader + sn=lead+1; while(lead[0]) { cb_buf_clamp(i,lead[0]); lead[0] -= (char)i; while (i) {*bf++=*sn++; --i;} chk_cb_buf(1); } + + // copy leading zeros + c = cs >> 24; cs &= 0xffffff; + cs = (fl&CS)?((rU32)(c-((pr+cs)%(c+1)))):0; + while(pr>0) { cb_buf_clamp(i,pr); pr -= i; if((fl&CS)==0) { while(i) { if ((((rUINTa)bf)&3)==0) break; *bf++='0'; --i; } while(i>=4) { *(rU32*)bf=0x30303030; bf+=4; i-=4; } } while (i) { if((fl&CS) && (cs++==c)) { cs = 0; *bf++=RRcomma; } else *bf++='0'; --i; } chk_cb_buf(1); } + } + + // copy leader if there is still one + sn=lead+1; while(lead[0]) { rS32 i; cb_buf_clamp(i,lead[0]); lead[0] -= (char)i; while (i) {*bf++=*sn++; --i;} chk_cb_buf(1); } + + // copy the string + n = l; while (n) { rS32 i; cb_buf_clamp(i,n); n-=i; RR_UNALIGNED( while(i>=4) { *(rU32*)bf=*(rU32*)s; bf+=4; s+=4; i-=4; } ) while (i) {*bf++=*s++; --i;} chk_cb_buf(1); } + + // copy trailing zeros + while(tz) { rS32 i; cb_buf_clamp(i,tz); tz -= i; while(i) { if ((((rUINTa)bf)&3)==0) break; *bf++='0'; --i; } while(i>=4) { *(rU32*)bf=0x30303030; bf+=4; i-=4; } while (i) {*bf++='0'; --i;} chk_cb_buf(1); } + + // copy tail if there is one + sn=tail+1; while(tail[0]) { rS32 i; cb_buf_clamp(i,tail[0]); tail[0] -= (char)i; while (i) {*bf++=*sn++; --i;} chk_cb_buf(1); } + + // handle the left justify + if (fl&LJ) if (fw>0) { while (fw) { rS32 i; cb_buf_clamp(i,fw); fw-=i; while(i) { if ((((rUINTa)bf)&3)==0) break; *bf++=' '; --i; } while(i>=4) { *(rU32*)bf=0x20202020; bf+=4; i-=4; } while (i--) *bf++=' '; chk_cb_buf(1); } } + break; + + default: // unknown, just copy code + s = num + NUMSZ -1; *s = f[0]; + l = 1; + fw=pr=fl=0; + lead[0]=0; tail[0]=0; pr = 0; dp = 0; cs = 0; + goto scopy; + } + ++f; + } + endfmt: + + if (!callback) + *bf = 0; + else + flush_cb(); + + done: + return tlen + (int)(bf-buf); +} + +// cleanup +#undef LJ +#undef LP +#undef LS +#undef LX +#undef LZ +#undef BI +#undef CS +#undef NG +#undef KI +#undef NUMSZ +#undef chk_cb_bufL +#undef chk_cb_buf +#undef flush_cb +#undef cb_buf_clamp + +// ============================================================================ +// wrapper functions + +RRPUBLIC_DEF int RR_SPRINTF_DECORATE( sprintf )( char * buf, char const * fmt, ... ) +{ + va_list va; + va_start( va, fmt ); + return RR_SPRINTF_DECORATE( vsprintfcb )( 0, 0, buf, fmt, va ); +} + +typedef struct RRCCS +{ + char * buf; + int count; + char tmp[ RR_SPRINTF_MIN ]; +} RRCCS; + +static char * rrclampcallback( char * buf, void * user, int len ) +{ + RRCCS * c = (RRCCS*)user; + + if ( len > c->count ) len = c->count; + + if (len) + { + if ( buf != c->buf ) + { + char * s, * d, * se; + d = c->buf; s = buf; se = buf+len; + do{ *d++ = *s++; } while (sbuf += len; + c->count -= len; + } + + if ( c->count <= 0 ) return 0; + return ( c->count >= RR_SPRINTF_MIN ) ? c->buf : c->tmp; // go direct into buffer if you can +} + +RRPUBLIC_DEF int RR_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) +{ + RRCCS c; + int l; + + if ( count == 0 ) + return 0; + + c.buf = buf; + c.count = count; + + RR_SPRINTF_DECORATE( vsprintfcb )( rrclampcallback, &c, rrclampcallback(0,&c,0), fmt, va ); + + // zero-terminate + l = (int)( c.buf - buf ); + if ( l >= count ) // should never be greater, only equal (or less) than count + l = count - 1; + buf[l] = 0; + + return l; +} + +RRPUBLIC_DEF int RR_SPRINTF_DECORATE( snprintf )( char * buf, int count, char const * fmt, ... ) +{ + va_list va; + va_start( va, fmt ); + + return RR_SPRINTF_DECORATE( vsnprintf )( buf, count, fmt, va ); +} + +RRPUBLIC_DEF int RR_SPRINTF_DECORATE( vsprintf )( char * buf, char const * fmt, va_list va ) +{ + return RR_SPRINTF_DECORATE( vsprintfcb )( 0, 0, buf, fmt, va ); +} + +// ======================================================================= +// low level float utility functions + +#ifndef RR_SPRINTF_NOFLOAT + + // copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) + #define RRCOPYFP(dest,src) { int cn; for(cn=0;cn<8;cn++) ((char*)&dest)[cn]=((char*)&src)[cn]; } + +// get float info +static rS32 rrreal_to_parts( rS64 * bits, rS32 * expo, double value ) +{ + double d; + rS64 b = 0; + + // load value and round at the frac_digits + d = value; + + RRCOPYFP( b, d ); + + *bits = b & ((((rU64)1)<<52)-1); + *expo = ((b >> 52) & 2047)-1023; + + return (rS32)(b >> 63); +} + +static double const rrbot[23]={1e+000,1e+001,1e+002,1e+003,1e+004,1e+005,1e+006,1e+007,1e+008,1e+009,1e+010,1e+011,1e+012,1e+013,1e+014,1e+015,1e+016,1e+017,1e+018,1e+019,1e+020,1e+021,1e+022}; +static double const rrnegbot[22]={1e-001,1e-002,1e-003,1e-004,1e-005,1e-006,1e-007,1e-008,1e-009,1e-010,1e-011,1e-012,1e-013,1e-014,1e-015,1e-016,1e-017,1e-018,1e-019,1e-020,1e-021,1e-022}; +static double const rrnegboterr[22]={-5.551115123125783e-018,-2.0816681711721684e-019,-2.0816681711721686e-020,-4.7921736023859299e-021,-8.1803053914031305e-022,4.5251888174113741e-023,4.5251888174113739e-024,-2.0922560830128471e-025,-6.2281591457779853e-026,-3.6432197315497743e-027,6.0503030718060191e-028,2.0113352370744385e-029,-3.0373745563400371e-030,1.1806906454401013e-032,-7.7705399876661076e-032,2.0902213275965398e-033,-7.1542424054621921e-034,-7.1542424054621926e-035,2.4754073164739869e-036,5.4846728545790429e-037,9.2462547772103625e-038,-4.8596774326570872e-039}; +static double const rrtop[13]={1e+023,1e+046,1e+069,1e+092,1e+115,1e+138,1e+161,1e+184,1e+207,1e+230,1e+253,1e+276,1e+299}; +static double const rrnegtop[13]={1e-023,1e-046,1e-069,1e-092,1e-115,1e-138,1e-161,1e-184,1e-207,1e-230,1e-253,1e-276,1e-299}; +static double const rrtoperr[13]={8388608,6.8601809640529717e+028,-7.253143638152921e+052,-4.3377296974619174e+075,-1.5559416129466825e+098,-3.2841562489204913e+121,-3.7745893248228135e+144,-1.7356668416969134e+167,-3.8893577551088374e+190,-9.9566444326005119e+213,6.3641293062232429e+236,-5.2069140800249813e+259,-5.2504760255204387e+282}; +static double const rrnegtoperr[13]={3.9565301985100693e-040,-2.299904345391321e-063,3.6506201437945798e-086,1.1875228833981544e-109,-5.0644902316928607e-132,-6.7156837247865426e-155,-2.812077463003139e-178,-5.7778912386589953e-201,7.4997100559334532e-224,-4.6439668915134491e-247,-6.3691100762962136e-270,-9.436808465446358e-293,8.0970921678014997e-317}; + +#if defined(_MSC_VER) && (_MSC_VER<=1200) +static rU64 const rrpot[20]={1,10,100,1000, 10000,100000,1000000,10000000, 100000000,1000000000,10000000000,100000000000, 1000000000000,10000000000000,100000000000000,1000000000000000, 10000000000000000,100000000000000000,1000000000000000000,10000000000000000000U }; +#define rrtento19th ((rU64)1000000000000000000) +#else +static rU64 const rrpot[20]={1,10,100,1000, 10000,100000,1000000,10000000, 100000000,1000000000,10000000000ULL,100000000000ULL, 1000000000000ULL,10000000000000ULL,100000000000000ULL,1000000000000000ULL, 10000000000000000ULL,100000000000000000ULL,1000000000000000000ULL,10000000000000000000ULL }; +#define rrtento19th (1000000000000000000ULL) +#endif + +#define rrddmulthi(oh,ol,xh,yh) \ +{ \ + double ahi=0,alo,bhi=0,blo; \ + rS64 bt; \ + oh = xh * yh; \ + RRCOPYFP(bt,xh); bt&=((~(rU64)0)<<27); RRCOPYFP(ahi,bt); alo = xh-ahi; \ + RRCOPYFP(bt,yh); bt&=((~(rU64)0)<<27); RRCOPYFP(bhi,bt); blo = yh-bhi; \ + ol = ((ahi*bhi-oh)+ahi*blo+alo*bhi)+alo*blo; \ +} + +#define rrddtoS64(ob,xh,xl) \ +{ \ + double ahi=0,alo,vh,t;\ + ob = (rS64)ph;\ + vh=(double)ob;\ + ahi = ( xh - vh );\ + t = ( ahi - xh );\ + alo = (xh-(ahi-t))-(vh+t);\ + ob += (rS64)(ahi+alo+xl);\ +} + + +#define rrddrenorm(oh,ol) { double s; s=oh+ol; ol=ol-(s-oh); oh=s; } + +#define rrddmultlo(oh,ol,xh,xl,yh,yl) \ + ol = ol + ( xh*yl + xl*yh ); \ + +#define rrddmultlos(oh,ol,xh,yl) \ + ol = ol + ( xh*yl ); \ + +static void rrraise_to_power10( double *ohi, double *olo, double d, rS32 power ) // power can be -323 to +350 +{ + double ph, pl; + if ((power>=0) && (power<=22)) + { + rrddmulthi(ph,pl,d,rrbot[power]); + } + else + { + rS32 e,et,eb; + double p2h,p2l; + + e=power; if (power<0) e=-e; + et = (e*0x2c9)>>14;/* %23 */ if (et>13) et=13; eb = e-(et*23); + + ph = d; pl = 0.0; + if (power<0) + { + if (eb) { --eb; rrddmulthi(ph,pl,d,rrnegbot[eb]); rrddmultlos(ph,pl,d,rrnegboterr[eb]); } + if (et) + { + rrddrenorm(ph,pl); + --et; rrddmulthi(p2h,p2l,ph,rrnegtop[et]); rrddmultlo(p2h,p2l,ph,pl,rrnegtop[et],rrnegtoperr[et]); ph=p2h;pl=p2l; + } + } + else + { + if (eb) + { + e = eb; if (eb>22) eb=22; e -= eb; + rrddmulthi(ph,pl,d,rrbot[eb]); + if ( e ) { rrddrenorm(ph,pl); rrddmulthi(p2h,p2l,ph,rrbot[e]); rrddmultlos(p2h,p2l,rrbot[e],pl); ph=p2h;pl=p2l; } + } + if (et) + { + rrddrenorm(ph,pl); + --et; rrddmulthi(p2h,p2l,ph,rrtop[et]); rrddmultlo(p2h,p2l,ph,pl,rrtop[et],rrtoperr[et]); ph=p2h;pl=p2l; + } + } + } + rrddrenorm(ph,pl); + *ohi = ph; *olo = pl; +} + +// given a float value, returns the significant bits in bits, and the position of the +// decimal point in decimal_pos. +/-INF and NAN are specified by special values +// returned in the decimal_pos parameter. +// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 +static rS32 rrreal_to_str( char const * * start, rU32 * len, char *out, rS32 * decimal_pos, double value, rU32 frac_digits ) +{ + double d; + rS64 bits = 0; + rS32 expo, e, ng, tens; + + d = value; + RRCOPYFP(bits,d); + expo = (bits >> 52) & 2047; + ng = (rS32)(bits >> 63); + if (ng) d=-d; + + if ( expo == 2047 ) // is nan or inf? + { + *start = (bits&((((rU64)1)<<52)-1)) ? "NaN" : "Inf"; + *decimal_pos = RRSPECIAL; + *len = 3; + return ng; + } + + if ( expo == 0 ) // is zero or denormal + { + if ((bits<<1)==0) // do zero + { + *decimal_pos = 1; + *start = out; + out[0] = '0'; *len = 1; + return ng; + } + // find the right expo for denormals + { + rS64 v = ((rU64)1)<<51; + while ((bits&v)==0) { --expo; v >>= 1; } + } + } + + // find the decimal exponent as well as the decimal bits of the value + { + double ph,pl; + + // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 + tens=expo-1023; tens = (tens<0)?((tens*617)/2048):(((tens*1233)/4096)+1); + + // move the significant bits into position and stick them into an int + rrraise_to_power10( &ph, &pl, d, 18-tens ); + + // get full as much precision from double-double as possible + rrddtoS64( bits, ph,pl ); + + // check if we undershot + if ( ((rU64)bits) >= rrtento19th ) ++tens; + } + + // now do the rounding in integer land + frac_digits = ( frac_digits & 0x80000000 ) ? ( (frac_digits&0x7ffffff) + 1 ) : ( tens + frac_digits ); + if ( ( frac_digits < 24 ) ) + { + rU32 dg = 1; if ((rU64)bits >= rrpot[9] ) dg=10; while( (rU64)bits >= rrpot[dg] ) { ++dg; if (dg==20) goto noround; } + if ( frac_digits < dg ) + { + rU64 r; + // add 0.5 at the right position and round + e = dg - frac_digits; + if ( (rU32)e >= 24 ) goto noround; + r = rrpot[e]; + bits = bits + (r/2); + if ( (rU64)bits >= rrpot[dg] ) ++tens; + bits /= r; + } + noround:; + } + + // kill long trailing runs of zeros + if ( bits ) + { + rU32 n; for(;;) { if ( bits<=0xffffffff ) break; if (bits%1000) goto donez; bits/=1000; } n = (rU32)bits; while ((n%1000)==0) n/=1000; bits=n; donez:; + } + + // convert to string + out += 64; + e = 0; + for(;;) + { + rU32 n; + char * o = out-8; + // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) + if (bits>=100000000) { n = (rU32)( bits % 100000000); bits /= 100000000; } else {n = (rU32)bits; bits = 0; } + while(n) { out-=2; *(rU16*)out=*(rU16*)&rrdiglookup[(n%100)*2]; n/=100; e+=2; } + if (bits==0) { if ((e) && (out[0]=='0')) { ++out; --e; } break; } + while( out!=o ) { *--out ='0'; ++e; } + } + + *decimal_pos = tens; + *start = out; + *len = e; + return ng; +} + +#undef rrddmulthi +#undef rrddrenorm +#undef rrddmultlo +#undef rrddmultlos +#undef RRSPECIAL +#undef RRCOPYFP + +#endif + +// clean up +#undef rU16 +#undef rU32 +#undef rS32 +#undef rU64 +#undef rS64 +#undef RRPUBLIC_DEC +#undef RRPUBLIC_DEF +#undef RR_SPRINTF_DECORATE +#undef RR_UNALIGNED + +#endif + +#endif diff --git a/source/engine/thirdparty/stb/deprecated/stb.h b/source/engine/thirdparty/stb/deprecated/stb.h new file mode 100644 index 0000000..1633c3b --- /dev/null +++ b/source/engine/thirdparty/stb/deprecated/stb.h @@ -0,0 +1,13111 @@ +/* stb.h - v2.37 - Sean's Tool Box -- public domain -- http://nothings.org/stb.h + no warranty is offered or implied; use this code at your own risk + + This is a single header file with a bunch of useful utilities + for getting stuff done in C/C++. + + Documentation: http://nothings.org/stb/stb_h.html + Unit tests: http://nothings.org/stb/stb.c + + ============================================================================ + You MUST + + #define STB_DEFINE + + in EXACTLY _one_ C or C++ file that includes this header, BEFORE the + include, like this: + + #define STB_DEFINE + #include "stb.h" + + All other files should just #include "stb.h" without the #define. + ============================================================================ + +Version History + + 2.36 various fixes + 2.35 fix clang-cl issues with swprintf + 2.34 fix warnings + 2.33 more fixes to random numbers + 2.32 stb_intcmprev, stb_uidict, fix random numbers on Linux + 2.31 stb_ucharcmp + 2.30 MinGW fix + 2.29 attempt to fix use of swprintf() + 2.28 various new functionality + 2.27 test _WIN32 not WIN32 in STB_THREADS + 2.26 various warning & bugfixes + 2.25 various warning & bugfixes + 2.24 various warning & bugfixes + 2.23 fix 2.22 + 2.22 64-bit fixes from '!='; fix stb_sdict_copy() to have preferred name + 2.21 utf-8 decoder rejects "overlong" encodings; attempted 64-bit improvements + 2.20 fix to hash "copy" function--reported by someone with handle "!=" + 2.19 ??? + 2.18 stb_readdir_subdirs_mask + 2.17 stb_cfg_dir + 2.16 fix stb_bgio_, add stb_bgio_stat(); begin a streaming wrapper + 2.15 upgraded hash table template to allow: + - aggregate keys (explicit comparison func for EMPTY and DEL keys) + - "static" implementations (so they can be culled if unused) + 2.14 stb_mprintf + 2.13 reduce identifiable strings in STB_NO_STB_STRINGS + 2.12 fix STB_ONLY -- lots of uint32s, TRUE/FALSE things had crept in + 2.11 fix bug in stb_dirtree_get() which caused "c://path" sorts of stuff + 2.10 STB_F(), STB_I() inline constants (also KI,KU,KF,KD) + 2.09 stb_box_face_vertex_axis_side + 2.08 bugfix stb_trimwhite() + 2.07 colored printing in windows (why are we in 1985?) + 2.06 comparison functions are now functions-that-return-functions and + accept a struct-offset as a parameter (not thread-safe) + 2.05 compile and pass tests under Linux (but no threads); thread cleanup + 2.04 stb_cubic_bezier_1d, smoothstep, avoid dependency on registry + 2.03 ? + 2.02 remove integrated documentation + 2.01 integrate various fixes; stb_force_uniprocessor + 2.00 revised stb_dupe to use multiple hashes + 1.99 stb_charcmp + 1.98 stb_arr_deleten, stb_arr_insertn + 1.97 fix stb_newell_normal() + 1.96 stb_hash_number() + 1.95 hack stb__rec_max; clean up recursion code to use new functions + 1.94 stb_dirtree; rename stb_extra to stb_ptrmap + 1.93 stb_sem_new() API cleanup (no blockflag-starts blocked; use 'extra') + 1.92 stb_threadqueue--multi reader/writer queue, fixed size or resizeable + 1.91 stb_bgio_* for reading disk asynchronously + 1.90 stb_mutex uses CRITICAL_REGION; new stb_sync primitive for thread + joining; workqueue supports stb_sync instead of stb_semaphore + 1.89 support ';' in constant-string wildcards; stb_mutex wrapper (can + implement with EnterCriticalRegion eventually) + 1.88 portable threading API (only for win32 so far); worker thread queue + 1.87 fix wildcard handling in stb_readdir_recursive + 1.86 support ';' in wildcards + 1.85 make stb_regex work with non-constant strings; + beginnings of stb_introspect() + 1.84 (forgot to make notes) + 1.83 whoops, stb_keep_if_different wasn't deleting the temp file + 1.82 bring back stb_compress from stb_file.h for cmirror + 1.81 various bugfixes, STB_FASTMALLOC_INIT inits FASTMALLOC in release + 1.80 stb_readdir returns utf8; write own utf8-utf16 because lib was wrong + 1.79 stb_write + 1.78 calloc() support for malloc wrapper, STB_FASTMALLOC + 1.77 STB_FASTMALLOC + 1.76 STB_STUA - Lua-like language; (stb_image, stb_csample, stb_bilinear) + 1.75 alloc/free array of blocks; stb_hheap bug; a few stb_ps_ funcs; + hash*getkey, hash*copy; stb_bitset; stb_strnicmp; bugfix stb_bst + 1.74 stb_replaceinplace; use stdlib C function to convert utf8 to UTF-16 + 1.73 fix performance bug & leak in stb_ischar (C++ port lost a 'static') + 1.72 remove stb_block, stb_block_manager, stb_decompress (to stb_file.h) + 1.71 stb_trimwhite, stb_tokens_nested, etc. + 1.70 back out 1.69 because it might problemize mixed builds; stb_filec() + 1.69 (stb_file returns 'char *' in C++) + 1.68 add a special 'tree root' data type for stb_bst; stb_arr_end + 1.67 full C++ port. (stb_block_manager) + 1.66 stb_newell_normal + 1.65 stb_lex_item_wild -- allow wildcard items which MUST match entirely + 1.64 stb_data + 1.63 stb_log_name + 1.62 stb_define_sort; C++ cleanup + 1.61 stb_hash_fast -- Paul Hsieh's hash function (beats Bob Jenkins'?) + 1.60 stb_delete_directory_recursive + 1.59 stb_readdir_recursive + 1.58 stb_bst variant with parent pointer for O(1) iteration, not O(log N) + 1.57 replace LCG random with Mersenne Twister (found a public domain one) + 1.56 stb_perfect_hash, stb_ischar, stb_regex + 1.55 new stb_bst API allows multiple BSTs per node (e.g. secondary keys) + 1.54 bugfix: stb_define_hash, stb_wildmatch, regexp + 1.53 stb_define_hash; recoded stb_extra, stb_sdict use it + 1.52 stb_rand_define, stb_bst, stb_reverse + 1.51 fix 'stb_arr_setlen(NULL, 0)' + 1.50 stb_wordwrap + 1.49 minor improvements to enable the scripting language + 1.48 better approach for stb_arr using stb_malloc; more invasive, clearer + 1.47 stb_lex (lexes stb.h at 1.5ML/s on 3Ghz P4; 60/70% of optimal/flex) + 1.46 stb_wrapper_*, STB_MALLOC_WRAPPER + 1.45 lightly tested DFA acceleration of regexp searching + 1.44 wildcard matching & searching; regexp matching & searching + 1.43 stb_temp + 1.42 allow stb_arr to use stb_malloc/realloc; note this is global + 1.41 make it compile in C++; (disable stb_arr in C++) + 1.40 stb_dupe tweak; stb_swap; stb_substr + 1.39 stb_dupe; improve stb_file_max to be less stupid + 1.38 stb_sha1_file: generate sha1 for file, even > 4GB + 1.37 stb_file_max; partial support for utf8 filenames in Windows + 1.36 remove STB__NO_PREFIX - poor interaction with IDE, not worth it + streamline stb_arr to make it separately publishable + 1.35 bugfixes for stb_sdict, stb_malloc(0), stristr + 1.34 (streaming interfaces for stb_compress) + 1.33 stb_alloc; bug in stb_getopt; remove stb_overflow + 1.32 (stb_compress returns, smaller&faster; encode window & 64-bit len) + 1.31 stb_prefix_count + 1.30 (STB__NO_PREFIX - remove stb_ prefixes for personal projects) + 1.29 stb_fput_varlen64, etc. + 1.28 stb_sha1 + 1.27 ? + 1.26 stb_extra + 1.25 ? + 1.24 stb_copyfile + 1.23 stb_readdir + 1.22 ? + 1.21 ? + 1.20 ? + 1.19 ? + 1.18 ? + 1.17 ? + 1.16 ? + 1.15 stb_fixpath, stb_splitpath, stb_strchr2 + 1.14 stb_arr + 1.13 ?stb, stb_log, stb_fatal + 1.12 ?stb_hash2 + 1.11 miniML + 1.10 stb_crc32, stb_adler32 + 1.09 stb_sdict + 1.08 stb_bitreverse, stb_ispow2, stb_big32 + stb_fopen, stb_fput_varlen, stb_fput_ranged + stb_fcmp, stb_feq + 1.07 (stb_encompress) + 1.06 stb_compress + 1.05 stb_tokens, (stb_hheap) + 1.04 stb_rand + 1.03 ?(s-strings) + 1.02 ?stb_filelen, stb_tokens + 1.01 stb_tolower + 1.00 stb_hash, stb_intcmp + stb_file, stb_stringfile, stb_fgets + stb_prefix, stb_strlower, stb_strtok + stb_image + (stb_array), (stb_arena) + +Parenthesized items have since been removed. + +LICENSE + + See end of file for license information. + +CREDITS + + Written by Sean Barrett. + + Fixes: + Philipp Wiesemann + Robert Nix + r-lyeh + blackpawn + github:Mojofreem + Ryan Whitworth + Vincent Isambart + Mike Sartain + Eugene Opalev + Tim Sjostrand + github:infatum + Dave Butler (Croepha) + Ethan Lee (flibitijibibo) + Brian Collins + Kyle Langley +*/ + +#include + +#ifndef STB__INCLUDE_STB_H +#define STB__INCLUDE_STB_H + +#define STB_VERSION 1 + +#ifdef STB_INTROSPECT + #define STB_DEFINE +#endif + +#ifdef STB_DEFINE_THREADS + #ifndef STB_DEFINE + #define STB_DEFINE + #endif + #ifndef STB_THREADS + #define STB_THREADS + #endif +#endif + +#if defined(_WIN32) && !defined(__MINGW32__) + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif + #ifndef _CRT_NON_CONFORMING_SWPRINTFS + #define _CRT_NON_CONFORMING_SWPRINTFS + #endif + #if !defined(_MSC_VER) || _MSC_VER > 1700 + #include // _BitScanReverse + #endif +#endif + +#include // stdlib could have min/max +#include // need FILE +#include // stb_define_hash needs memcpy/memset +#include // stb_dirtree +#ifdef __MINGW32__ + #include // O_RDWR +#endif + +#ifdef STB_PERSONAL + typedef int Bool; + #define False 0 + #define True 1 +#endif + +#ifdef STB_MALLOC_WRAPPER_PAGED + #define STB_MALLOC_WRAPPER_DEBUG +#endif +#ifdef STB_MALLOC_WRAPPER_DEBUG + #define STB_MALLOC_WRAPPER +#endif +#ifdef STB_MALLOC_WRAPPER_FASTMALLOC + #define STB_FASTMALLOC + #define STB_MALLOC_WRAPPER +#endif + +#ifdef STB_FASTMALLOC + #ifndef _WIN32 + #undef STB_FASTMALLOC + #endif +#endif + +#ifdef STB_DEFINE + #include + #include + #include + #include + #include + #ifndef _WIN32 + #include + #else + #include // _mktemp + #include // _rmdir + #endif + #include // stat()/_stat() + #include // stat()/_stat() +#endif + +#define stb_min(a,b) ((a) < (b) ? (a) : (b)) +#define stb_max(a,b) ((a) > (b) ? (a) : (b)) + +#ifndef STB_ONLY + #if !defined(__cplusplus) && !defined(min) && !defined(max) + #define min(x,y) stb_min(x,y) + #define max(x,y) stb_max(x,y) + #endif + + #ifndef M_PI + #define M_PI 3.14159265358979323846f + #endif + + #ifndef TRUE + #define TRUE 1 + #define FALSE 0 + #endif + + #ifndef deg2rad + #define deg2rad(a) ((a)*(M_PI/180)) + #endif + #ifndef rad2deg + #define rad2deg(a) ((a)*(180/M_PI)) + #endif + + #ifndef swap + #ifndef __cplusplus + #define swap(TYPE,a,b) \ + do { TYPE stb__t; stb__t = (a); (a) = (b); (b) = stb__t; } while (0) + #endif + #endif + + typedef unsigned char uint8 ; + typedef signed char int8 ; + typedef unsigned short uint16; + typedef signed short int16; + #if defined(STB_USE_LONG_FOR_32_BIT_INT) || defined(STB_LONG32) + typedef unsigned long uint32; + typedef signed long int32; + #else + typedef unsigned int uint32; + typedef signed int int32; + #endif + + typedef unsigned char uchar ; + typedef unsigned short ushort; + typedef unsigned int uint ; + typedef unsigned long ulong ; + + // produce compile errors if the sizes aren't right + typedef char stb__testsize16[sizeof(int16)==2]; + typedef char stb__testsize32[sizeof(int32)==4]; +#endif + +#ifndef STB_TRUE + #define STB_TRUE 1 + #define STB_FALSE 0 +#endif + +// if we're STB_ONLY, can't rely on uint32 or even uint, so all the +// variables we'll use herein need typenames prefixed with 'stb': +typedef unsigned char stb_uchar; +typedef unsigned char stb_uint8; +typedef unsigned int stb_uint; +typedef unsigned short stb_uint16; +typedef short stb_int16; +typedef signed char stb_int8; +#if defined(STB_USE_LONG_FOR_32_BIT_INT) || defined(STB_LONG32) + typedef unsigned long stb_uint32; + typedef long stb_int32; +#else + typedef unsigned int stb_uint32; + typedef int stb_int32; +#endif +typedef char stb__testsize2_16[sizeof(stb_uint16)==2 ? 1 : -1]; +typedef char stb__testsize2_32[sizeof(stb_uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER + typedef unsigned __int64 stb_uint64; + typedef __int64 stb_int64; + #define STB_IMM_UINT64(literalui64) (literalui64##ui64) + #define STB_IMM_INT64(literali64) (literali64##i64) +#else + // ?? + typedef unsigned long long stb_uint64; + typedef long long stb_int64; + #define STB_IMM_UINT64(literalui64) (literalui64##ULL) + #define STB_IMM_INT64(literali64) (literali64##LL) +#endif +typedef char stb__testsize2_64[sizeof(stb_uint64)==8 ? 1 : -1]; + +// add platform-specific ways of checking for sizeof(char*) == 8, +// and make those define STB_PTR64 +#if defined(_WIN64) || defined(__x86_64__) || defined(__ia64__) || defined(__LP64__) + #define STB_PTR64 +#endif + +#ifdef STB_PTR64 +typedef char stb__testsize2_ptr[sizeof(char *) == 8]; +typedef stb_uint64 stb_uinta; +typedef stb_int64 stb_inta; +#else +typedef char stb__testsize2_ptr[sizeof(char *) == 4]; +typedef stb_uint32 stb_uinta; +typedef stb_int32 stb_inta; +#endif +typedef char stb__testsize2_uinta[sizeof(stb_uinta)==sizeof(char*) ? 1 : -1]; + +// if so, we should define an int type that is the pointer size. until then, +// we'll have to make do with this (which is not the same at all!) + +typedef union +{ + unsigned int i; + void * p; +} stb_uintptr; + + +#ifdef __cplusplus + #define STB_EXTERN extern "C" +#else + #define STB_EXTERN extern +#endif + +// check for well-known debug defines +#if defined(DEBUG) || defined(_DEBUG) || defined(DBG) + #ifndef NDEBUG + #define STB_DEBUG + #endif +#endif + +#ifdef STB_DEBUG + #include +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// C library function platform handling +// + +#ifdef STB_DEFINE + +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) +static FILE * stb_p_fopen(const char *filename, const char *mode) +{ + FILE *f; + if (0 == fopen_s(&f, filename, mode)) + return f; + else + return NULL; +} +static FILE * stb_p_wfopen(const wchar_t *filename, const wchar_t *mode) +{ + FILE *f; + if (0 == _wfopen_s(&f, filename, mode)) + return f; + else + return NULL; +} +static char *stb_p_strcpy_s(char *a, size_t size, const char *b) +{ + strcpy_s(a,size,b); + return a; +} +static char *stb_p_strncpy_s(char *a, size_t size, const char *b, size_t count) +{ + strncpy_s(a,size,b,count); + return a; +} +#define stb_p_mktemp(s) (_mktemp_s(s, strlen(s)+1) == 0) +#define stb_p_sprintf sprintf_s +#define stb_p_size(x) ,(x) +#else +#define stb_p_fopen fopen +#define stb_p_wfopen _wfopen +#define stb_p_strcpy_s(a,s,b) strcpy(a,b) +#define stb_p_strncpy_s(a,s,b,c) strncpy(a,b,c) +#define stb_p_mktemp(s) (mktemp(s) != NULL) + +#define stb_p_sprintf sprintf +#define stb_p_size(x) +#endif + +#if defined(_WIN32) +#define stb_p_vsnprintf _vsnprintf +#else +#define stb_p_vsnprintf vsnprintf +#endif +#endif // STB_DEFINE + +#if defined(_WIN32) && (_MSC_VER >= 1300) +#define stb_p_stricmp _stricmp +#define stb_p_strnicmp _strnicmp +#define stb_p_strdup _strdup +#else +#define stb_p_strdup strdup +#define stb_p_stricmp stricmp +#define stb_p_strnicmp strnicmp +#endif + +STB_EXTERN void stb_wrapper_malloc(void *newp, size_t sz, char *file, int line); +STB_EXTERN void stb_wrapper_free(void *oldp, char *file, int line); +STB_EXTERN void stb_wrapper_realloc(void *oldp, void *newp, size_t sz, char *file, int line); +STB_EXTERN void stb_wrapper_calloc(size_t num, size_t sz, char *file, int line); +STB_EXTERN void stb_wrapper_listall(void (*func)(void *ptr, size_t sz, char *file, int line)); +STB_EXTERN void stb_wrapper_dump(char *filename); +STB_EXTERN size_t stb_wrapper_allocsize(void *oldp); +STB_EXTERN void stb_wrapper_check(void *oldp); + +#ifdef STB_DEFINE +// this is a special function used inside malloc wrapper +// to do allocations that aren't tracked (to avoid +// reentrancy). Of course if someone _else_ wraps realloc, +// this breaks, but if they're doing that AND the malloc +// wrapper they need to explicitly check for reentrancy. +// +// only define realloc_raw() and we do realloc(NULL,sz) +// for malloc() and realloc(p,0) for free(). +static void * stb__realloc_raw(void *p, int sz) +{ + if (p == NULL) return malloc(sz); + if (sz == 0) { free(p); return NULL; } + return realloc(p,sz); +} +#endif + +#ifdef _WIN32 +STB_EXTERN void * stb_smalloc(size_t sz); +STB_EXTERN void stb_sfree(void *p); +STB_EXTERN void * stb_srealloc(void *p, size_t sz); +STB_EXTERN void * stb_scalloc(size_t n, size_t sz); +STB_EXTERN char * stb_sstrdup(char *s); +#endif + +#ifdef STB_FASTMALLOC +#define malloc stb_smalloc +#define free stb_sfree +#define realloc stb_srealloc +#define strdup stb_sstrdup +#define calloc stb_scalloc +#endif + +#ifndef STB_MALLOC_ALLCHECK + #define stb__check(p) 1 +#else + #ifndef STB_MALLOC_WRAPPER + #error STB_MALLOC_ALLCHECK requires STB_MALLOC_WRAPPER + #else + #define stb__check(p) stb_mcheck(p) + #endif +#endif + +#ifdef STB_MALLOC_WRAPPER + STB_EXTERN void * stb__malloc(size_t, char *, int); + STB_EXTERN void * stb__realloc(void *, size_t, char *, int); + STB_EXTERN void * stb__calloc(size_t n, size_t s, char *, int); + STB_EXTERN void stb__free(void *, char *file, int); + STB_EXTERN char * stb__strdup(char *s, char *file, int); + STB_EXTERN void stb_malloc_checkall(void); + STB_EXTERN void stb_malloc_check_counter(int init_delay, int rep_delay); + #ifndef STB_MALLOC_WRAPPER_DEBUG + #define stb_mcheck(p) 1 + #else + STB_EXTERN int stb_mcheck(void *); + #endif + + + #ifdef STB_DEFINE + + #ifdef STB_MALLOC_WRAPPER_DEBUG + #define STB__PAD 32 + #define STB__BIAS 16 + #define STB__SIG 0x51b01234 + #define STB__FIXSIZE(sz) (((sz+3) & ~3) + STB__PAD) + #define STB__ptr(x,y) ((char *) (x) + (y)) + #else + #define STB__ptr(x,y) (x) + #define STB__FIXSIZE(sz) (sz) + #endif + + #ifdef STB_MALLOC_WRAPPER_DEBUG + int stb_mcheck(void *p) + { + unsigned int sz; + if (p == NULL) return 1; + p = ((char *) p) - STB__BIAS; + sz = * (unsigned int *) p; + assert(* (unsigned int *) STB__ptr(p,4) == STB__SIG); + assert(* (unsigned int *) STB__ptr(p,8) == STB__SIG); + assert(* (unsigned int *) STB__ptr(p,12) == STB__SIG); + assert(* (unsigned int *) STB__ptr(p,sz-4) == STB__SIG+1); + assert(* (unsigned int *) STB__ptr(p,sz-8) == STB__SIG+1); + assert(* (unsigned int *) STB__ptr(p,sz-12) == STB__SIG+1); + assert(* (unsigned int *) STB__ptr(p,sz-16) == STB__SIG+1); + stb_wrapper_check(STB__ptr(p, STB__BIAS)); + return 1; + } + + static void stb__check2(void *p, size_t sz, char *file, int line) + { + stb_mcheck(p); + } + + void stb_malloc_checkall(void) + { + stb_wrapper_listall(stb__check2); + } + #else + void stb_malloc_checkall(void) { } + #endif + + static int stb__malloc_wait=(1 << 30), stb__malloc_next_wait = (1 << 30), stb__malloc_iter; + void stb_malloc_check_counter(int init_delay, int rep_delay) + { + stb__malloc_wait = init_delay; + stb__malloc_next_wait = rep_delay; + } + + void stb_mcheck_all(void) + { + #ifdef STB_MALLOC_WRAPPER_DEBUG + ++stb__malloc_iter; + if (--stb__malloc_wait <= 0) { + stb_malloc_checkall(); + stb__malloc_wait = stb__malloc_next_wait; + } + #endif + } + + #ifdef STB_MALLOC_WRAPPER_PAGED + #define STB__WINDOWS_PAGE (1 << 12) + #ifndef _WINDOWS_ + STB_EXTERN __declspec(dllimport) void * __stdcall VirtualAlloc(void *p, unsigned long size, unsigned long type, unsigned long protect); + STB_EXTERN __declspec(dllimport) int __stdcall VirtualFree(void *p, unsigned long size, unsigned long freetype); + #endif + #endif + + static void *stb__malloc_final(size_t sz) + { + #ifdef STB_MALLOC_WRAPPER_PAGED + size_t aligned = (sz + STB__WINDOWS_PAGE - 1) & ~(STB__WINDOWS_PAGE-1); + char *p = VirtualAlloc(NULL, aligned + STB__WINDOWS_PAGE, 0x2000, 0x04); // RESERVE, READWRITE + if (p == NULL) return p; + VirtualAlloc(p, aligned, 0x1000, 0x04); // COMMIT, READWRITE + return p; + #else + return malloc(sz); + #endif + } + + static void stb__free_final(void *p) + { + #ifdef STB_MALLOC_WRAPPER_PAGED + VirtualFree(p, 0, 0x8000); // RELEASE + #else + free(p); + #endif + } + + int stb__malloc_failure; + #ifdef STB_MALLOC_WRAPPER_PAGED + static void *stb__realloc_final(void *p, size_t sz, size_t old_sz) + { + void *q = stb__malloc_final(sz); + if (q == NULL) + return ++stb__malloc_failure, q; + // @TODO: deal with p being smaller! + memcpy(q, p, sz < old_sz ? sz : old_sz); + stb__free_final(p); + return q; + } + #endif + + void stb__free(void *p, char *file, int line) + { + stb_mcheck_all(); + if (!p) return; + #ifdef STB_MALLOC_WRAPPER_DEBUG + stb_mcheck(p); + #endif + stb_wrapper_free(p,file,line); + #ifdef STB_MALLOC_WRAPPER_DEBUG + p = STB__ptr(p,-STB__BIAS); + * (unsigned int *) STB__ptr(p,0) = 0xdeadbeef; + * (unsigned int *) STB__ptr(p,4) = 0xdeadbeef; + * (unsigned int *) STB__ptr(p,8) = 0xdeadbeef; + * (unsigned int *) STB__ptr(p,12) = 0xdeadbeef; + #endif + stb__free_final(p); + } + + void * stb__malloc(size_t sz, char *file, int line) + { + void *p; + stb_mcheck_all(); + if (sz == 0) return NULL; + p = stb__malloc_final(STB__FIXSIZE(sz)); + if (p == NULL) p = stb__malloc_final(STB__FIXSIZE(sz)); + if (p == NULL) p = stb__malloc_final(STB__FIXSIZE(sz)); + if (p == NULL) { + ++stb__malloc_failure; + #ifdef STB_MALLOC_WRAPPER_DEBUG + stb_malloc_checkall(); + #endif + return p; + } + #ifdef STB_MALLOC_WRAPPER_DEBUG + * (int *) STB__ptr(p,0) = STB__FIXSIZE(sz); + * (unsigned int *) STB__ptr(p,4) = STB__SIG; + * (unsigned int *) STB__ptr(p,8) = STB__SIG; + * (unsigned int *) STB__ptr(p,12) = STB__SIG; + * (unsigned int *) STB__ptr(p,STB__FIXSIZE(sz)-4) = STB__SIG+1; + * (unsigned int *) STB__ptr(p,STB__FIXSIZE(sz)-8) = STB__SIG+1; + * (unsigned int *) STB__ptr(p,STB__FIXSIZE(sz)-12) = STB__SIG+1; + * (unsigned int *) STB__ptr(p,STB__FIXSIZE(sz)-16) = STB__SIG+1; + p = STB__ptr(p, STB__BIAS); + #endif + stb_wrapper_malloc(p,sz,file,line); + return p; + } + + void * stb__realloc(void *p, size_t sz, char *file, int line) + { + void *q; + + stb_mcheck_all(); + if (p == NULL) return stb__malloc(sz,file,line); + if (sz == 0 ) { stb__free(p,file,line); return NULL; } + + #ifdef STB_MALLOC_WRAPPER_DEBUG + stb_mcheck(p); + p = STB__ptr(p,-STB__BIAS); + #endif + #ifdef STB_MALLOC_WRAPPER_PAGED + { + size_t n = stb_wrapper_allocsize(STB__ptr(p,STB__BIAS)); + if (!n) + stb_wrapper_check(STB__ptr(p,STB__BIAS)); + q = stb__realloc_final(p, STB__FIXSIZE(sz), STB__FIXSIZE(n)); + } + #else + q = realloc(p, STB__FIXSIZE(sz)); + #endif + if (q == NULL) + return ++stb__malloc_failure, q; + #ifdef STB_MALLOC_WRAPPER_DEBUG + * (int *) STB__ptr(q,0) = STB__FIXSIZE(sz); + * (unsigned int *) STB__ptr(q,4) = STB__SIG; + * (unsigned int *) STB__ptr(q,8) = STB__SIG; + * (unsigned int *) STB__ptr(q,12) = STB__SIG; + * (unsigned int *) STB__ptr(q,STB__FIXSIZE(sz)-4) = STB__SIG+1; + * (unsigned int *) STB__ptr(q,STB__FIXSIZE(sz)-8) = STB__SIG+1; + * (unsigned int *) STB__ptr(q,STB__FIXSIZE(sz)-12) = STB__SIG+1; + * (unsigned int *) STB__ptr(q,STB__FIXSIZE(sz)-16) = STB__SIG+1; + + q = STB__ptr(q, STB__BIAS); + p = STB__ptr(p, STB__BIAS); + #endif + stb_wrapper_realloc(p,q,sz,file,line); + return q; + } + + STB_EXTERN int stb_log2_ceil(size_t); + static void *stb__calloc(size_t n, size_t sz, char *file, int line) + { + void *q; + stb_mcheck_all(); + if (n == 0 || sz == 0) return NULL; + if (stb_log2_ceil(n) + stb_log2_ceil(sz) >= 32) return NULL; + q = stb__malloc(n*sz, file, line); + if (q) memset(q, 0, n*sz); + return q; + } + + char * stb__strdup(char *s, char *file, int line) + { + char *p; + stb_mcheck_all(); + p = stb__malloc(strlen(s)+1, file, line); + if (!p) return p; + stb_p_strcpy_s(p, strlen(s)+1, s); + return p; + } + #endif // STB_DEFINE + + #ifdef STB_FASTMALLOC + #undef malloc + #undef realloc + #undef free + #undef strdup + #undef calloc + #endif + + // include everything that might define these, BEFORE making macros + #include + #include + #include + + #define malloc(s) stb__malloc ( s, __FILE__, __LINE__) + #define realloc(p,s) stb__realloc(p,s, __FILE__, __LINE__) + #define calloc(n,s) stb__calloc (n,s, __FILE__, __LINE__) + #define free(p) stb__free (p, __FILE__, __LINE__) + #define strdup(p) stb__strdup (p, __FILE__, __LINE__) +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Windows pretty display +// + +STB_EXTERN void stbprint(const char *fmt, ...); +STB_EXTERN char *stb_sprintf(const char *fmt, ...); +STB_EXTERN char *stb_mprintf(const char *fmt, ...); +STB_EXTERN int stb_snprintf(char *s, size_t n, const char *fmt, ...); +STB_EXTERN int stb_vsnprintf(char *s, size_t n, const char *fmt, va_list v); + +#ifdef STB_DEFINE +int stb_vsnprintf(char *s, size_t n, const char *fmt, va_list v) +{ + int res; + #ifdef _WIN32 + #ifdef __STDC_WANT_SECURE_LIB__ + res = _vsnprintf_s(s, n, _TRUNCATE, fmt, v); + #else + res = stb_p_vsnprintf(s,n,fmt,v); + #endif + #else + res = vsnprintf(s,n,fmt,v); + #endif + if (n) s[n-1] = 0; + // Unix returns length output would require, Windows returns negative when truncated. + return (res >= (int) n || res < 0) ? -1 : res; +} + +int stb_snprintf(char *s, size_t n, const char *fmt, ...) +{ + int res; + va_list v; + va_start(v,fmt); + res = stb_vsnprintf(s, n, fmt, v); + va_end(v); + return res; +} + +char *stb_sprintf(const char *fmt, ...) +{ + static char buffer[1024]; + va_list v; + va_start(v,fmt); + stb_vsnprintf(buffer,1024,fmt,v); + va_end(v); + return buffer; +} + +char *stb_mprintf(const char *fmt, ...) +{ + static char buffer[1024]; + va_list v; + va_start(v,fmt); + stb_vsnprintf(buffer,1024,fmt,v); + va_end(v); + return stb_p_strdup(buffer); +} + +#ifdef _WIN32 + +#ifndef _WINDOWS_ +STB_EXTERN __declspec(dllimport) int __stdcall WriteConsoleA(void *, const void *, unsigned int, unsigned int *, void *); +STB_EXTERN __declspec(dllimport) void * __stdcall GetStdHandle(unsigned int); +STB_EXTERN __declspec(dllimport) int __stdcall SetConsoleTextAttribute(void *, unsigned short); +#endif + +static void stb__print_one(void *handle, char *s, ptrdiff_t len) +{ + if (len) + if (0==WriteConsoleA(handle, s, (unsigned) len, NULL,NULL)) + // if it fails, maybe redirected, so output normally... + // but it's supriously reporting failure now on Win7 and later + {}//fwrite(s, 1, (unsigned) len, stdout); +} + +static void stb__print(char *s) +{ + void *handle = GetStdHandle((unsigned int) -11); // STD_OUTPUT_HANDLE + int pad=0; // number of padding characters to add + + char *t = s; + while (*s) { + int lpad; + while (*s && *s != '{') { + if (pad) { + if (*s == '\r' || *s == '\n') + pad = 0; + else if (s[0] == ' ' && s[1] == ' ') { + stb__print_one(handle, t, s-t); + t = s; + while (pad) { + stb__print_one(handle, t, 1); + --pad; + } + } + } + ++s; + } + if (!*s) break; + stb__print_one(handle, t, s-t); + if (s[1] == '{') { + ++s; + continue; + } + + if (s[1] == '#') { + t = s+3; + if (isxdigit(s[2])) + if (isdigit(s[2])) + SetConsoleTextAttribute(handle, s[2] - '0'); + else + SetConsoleTextAttribute(handle, tolower(s[2]) - 'a' + 10); + else { + SetConsoleTextAttribute(handle, 0x0f); + t=s+2; + } + } else if (s[1] == '!') { + SetConsoleTextAttribute(handle, 0x0c); + t = s+2; + } else if (s[1] == '@') { + SetConsoleTextAttribute(handle, 0x09); + t = s+2; + } else if (s[1] == '$') { + SetConsoleTextAttribute(handle, 0x0a); + t = s+2; + } else { + SetConsoleTextAttribute(handle, 0x08); // 0,7,8,15 => shades of grey + t = s+1; + } + + lpad = (int) (t-s); + s = t; + while (*s && *s != '}') ++s; + if (!*s) break; + stb__print_one(handle, t, s-t); + if (s[1] == '}') { + t = s+2; + } else { + pad += 1+lpad; + t = s+1; + } + s=t; + SetConsoleTextAttribute(handle, 0x07); + } + stb__print_one(handle, t, s-t); + SetConsoleTextAttribute(handle, 0x07); +} + +void stbprint(const char *fmt, ...) +{ + int res; + char buffer[1024]; + char *tbuf = buffer; + va_list v; + + va_start(v,fmt); + res = stb_vsnprintf(buffer, sizeof(buffer), fmt, v); + va_end(v); + + if (res < 0) { + tbuf = (char *) malloc(16384); + va_start(v,fmt); + res = stb_vsnprintf(tbuf,16384, fmt, v); + va_end(v); + tbuf[16383] = 0; + } + + stb__print(tbuf); + + if (tbuf != buffer) + free(tbuf); +} + +#else // _WIN32 +void stbprint(const char *fmt, ...) +{ + va_list v; + va_start(v,fmt); + vprintf(fmt,v); + va_end(v); +} +#endif // _WIN32 +#endif // STB_DEFINE + + + +////////////////////////////////////////////////////////////////////////////// +// +// Windows UTF8 filename handling +// +// Windows stupidly treats 8-bit filenames as some dopey code page, +// rather than utf-8. If we want to use utf8 filenames, we have to +// convert them to WCHAR explicitly and call WCHAR versions of the +// file functions. So, ok, we do. + + +#ifdef _WIN32 + #define stb__fopen(x,y) stb_p_wfopen((const wchar_t *)stb__from_utf8(x), (const wchar_t *)stb__from_utf8_alt(y)) + #define stb__windows(x,y) x +#else + #define stb__fopen(x,y) stb_p_fopen(x,y) + #define stb__windows(x,y) y +#endif + + +typedef unsigned short stb__wchar; + +STB_EXTERN stb__wchar * stb_from_utf8(stb__wchar *buffer, const char *str, int n); +STB_EXTERN char * stb_to_utf8 (char *buffer, const stb__wchar *str, int n); + +STB_EXTERN stb__wchar *stb__from_utf8(const char *str); +STB_EXTERN stb__wchar *stb__from_utf8_alt(const char *str); +STB_EXTERN char *stb__to_utf8(const stb__wchar *str); + + +#ifdef STB_DEFINE +stb__wchar * stb_from_utf8(stb__wchar *buffer, const char *ostr, int n) +{ + unsigned char *str = (unsigned char *) ostr; + stb_uint32 c; + int i=0; + --n; + while (*str) { + if (i >= n) + return NULL; + if (!(*str & 0x80)) + buffer[i++] = *str++; + else if ((*str & 0xe0) == 0xc0) { + if (*str < 0xc2) return NULL; + c = (*str++ & 0x1f) << 6; + if ((*str & 0xc0) != 0x80) return NULL; + buffer[i++] = c + (*str++ & 0x3f); + } else if ((*str & 0xf0) == 0xe0) { + if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return NULL; + if (*str == 0xed && str[1] > 0x9f) return NULL; // str[1] < 0x80 is checked below + c = (*str++ & 0x0f) << 12; + if ((*str & 0xc0) != 0x80) return NULL; + c += (*str++ & 0x3f) << 6; + if ((*str & 0xc0) != 0x80) return NULL; + buffer[i++] = c + (*str++ & 0x3f); + } else if ((*str & 0xf8) == 0xf0) { + if (*str > 0xf4) return NULL; + if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return NULL; + if (*str == 0xf4 && str[1] > 0x8f) return NULL; // str[1] < 0x80 is checked below + c = (*str++ & 0x07) << 18; + if ((*str & 0xc0) != 0x80) return NULL; + c += (*str++ & 0x3f) << 12; + if ((*str & 0xc0) != 0x80) return NULL; + c += (*str++ & 0x3f) << 6; + if ((*str & 0xc0) != 0x80) return NULL; + c += (*str++ & 0x3f); + // utf-8 encodings of values used in surrogate pairs are invalid + if ((c & 0xFFFFF800) == 0xD800) return NULL; + if (c >= 0x10000) { + c -= 0x10000; + if (i + 2 > n) return NULL; + buffer[i++] = 0xD800 | (0x3ff & (c >> 10)); + buffer[i++] = 0xDC00 | (0x3ff & (c )); + } + } else + return NULL; + } + buffer[i] = 0; + return buffer; +} + +char * stb_to_utf8(char *buffer, const stb__wchar *str, int n) +{ + int i=0; + --n; + while (*str) { + if (*str < 0x80) { + if (i+1 > n) return NULL; + buffer[i++] = (char) *str++; + } else if (*str < 0x800) { + if (i+2 > n) return NULL; + buffer[i++] = 0xc0 + (*str >> 6); + buffer[i++] = 0x80 + (*str & 0x3f); + str += 1; + } else if (*str >= 0xd800 && *str < 0xdc00) { + stb_uint32 c; + if (i+4 > n) return NULL; + c = ((str[0] - 0xd800) << 10) + ((str[1]) - 0xdc00) + 0x10000; + buffer[i++] = 0xf0 + (c >> 18); + buffer[i++] = 0x80 + ((c >> 12) & 0x3f); + buffer[i++] = 0x80 + ((c >> 6) & 0x3f); + buffer[i++] = 0x80 + ((c ) & 0x3f); + str += 2; + } else if (*str >= 0xdc00 && *str < 0xe000) { + return NULL; + } else { + if (i+3 > n) return NULL; + buffer[i++] = 0xe0 + (*str >> 12); + buffer[i++] = 0x80 + ((*str >> 6) & 0x3f); + buffer[i++] = 0x80 + ((*str ) & 0x3f); + str += 1; + } + } + buffer[i] = 0; + return buffer; +} + +stb__wchar *stb__from_utf8(const char *str) +{ + static stb__wchar buffer[4096]; + return stb_from_utf8(buffer, str, 4096); +} + +stb__wchar *stb__from_utf8_alt(const char *str) +{ + static stb__wchar buffer[4096]; + return stb_from_utf8(buffer, str, 4096); +} + +char *stb__to_utf8(const stb__wchar *str) +{ + static char buffer[4096]; + return stb_to_utf8(buffer, str, 4096); +} + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Miscellany +// + +STB_EXTERN void stb_fatal(const char *fmt, ...); +STB_EXTERN void stb_(char *fmt, ...); +STB_EXTERN void stb_append_to_file(char *file, char *fmt, ...); +STB_EXTERN void stb_log(int active); +STB_EXTERN void stb_log_fileline(int active); +STB_EXTERN void stb_log_name(char *filename); + +STB_EXTERN void stb_swap(void *p, void *q, size_t sz); +STB_EXTERN void *stb_copy(void *p, size_t sz); +STB_EXTERN void stb_pointer_array_free(void *p, int len); +STB_EXTERN void **stb_array_block_alloc(int count, int blocksize); + +#define stb_arrcount(x) (sizeof(x)/sizeof((x)[0])) + + +STB_EXTERN int stb__record_fileline(const char *f, int n); + +#ifdef STB_DEFINE + +static char *stb__file; +static int stb__line; + +int stb__record_fileline(const char *f, int n) +{ + stb__file = (char*) f; + stb__line = n; + return 0; +} + +void stb_fatal(const char *s, ...) +{ + va_list a; + if (stb__file) + fprintf(stderr, "[%s:%d] ", stb__file, stb__line); + va_start(a,s); + fputs("Fatal error: ", stderr); + vfprintf(stderr, s, a); + va_end(a); + fputs("\n", stderr); + #ifdef STB_DEBUG + #ifdef _MSC_VER + #ifndef STB_PTR64 + __asm int 3; // trap to debugger! + #else + __debugbreak(); + #endif + #else + __builtin_trap(); + #endif + #endif + exit(1); +} + +static int stb__log_active=1, stb__log_fileline=1; + +void stb_log(int active) +{ + stb__log_active = active; +} + +void stb_log_fileline(int active) +{ + stb__log_fileline = active; +} + +#ifdef STB_NO_STB_STRINGS +const char *stb__log_filename = "temp.log"; +#else +const char *stb__log_filename = "stb.log"; +#endif + +void stb_log_name(char *s) +{ + stb__log_filename = s; +} + +void stb_(char *s, ...) +{ + if (stb__log_active) { + FILE *f = stb_p_fopen(stb__log_filename, "a"); + if (f) { + va_list a; + if (stb__log_fileline && stb__file) + fprintf(f, "[%s:%4d] ", stb__file, stb__line); + va_start(a,s); + vfprintf(f, s, a); + va_end(a); + fputs("\n", f); + fclose(f); + } + } +} + +void stb_append_to_file(char *filename, char *s, ...) +{ + FILE *f = stb_p_fopen(filename, "a"); + if (f) { + va_list a; + va_start(a,s); + vfprintf(f, s, a); + va_end(a); + fputs("\n", f); + fclose(f); + } +} + + +typedef struct { char d[4]; } stb__4; +typedef struct { char d[8]; } stb__8; + +// optimize the small cases, though you shouldn't be calling this for those! +void stb_swap(void *p, void *q, size_t sz) +{ + char buffer[256]; + if (p == q) return; + if (sz == 4) { + stb__4 temp = * ( stb__4 *) p; + * (stb__4 *) p = * ( stb__4 *) q; + * (stb__4 *) q = temp; + return; + } else if (sz == 8) { + stb__8 temp = * ( stb__8 *) p; + * (stb__8 *) p = * ( stb__8 *) q; + * (stb__8 *) q = temp; + return; + } + + while (sz > sizeof(buffer)) { + stb_swap(p, q, sizeof(buffer)); + p = (char *) p + sizeof(buffer); + q = (char *) q + sizeof(buffer); + sz -= sizeof(buffer); + } + + memcpy(buffer, p , sz); + memcpy(p , q , sz); + memcpy(q , buffer, sz); +} + +void *stb_copy(void *p, size_t sz) +{ + void *q = malloc(sz); + memcpy(q, p, sz); + return q; +} + +void stb_pointer_array_free(void *q, int len) +{ + void **p = (void **) q; + int i; + for (i=0; i < len; ++i) + free(p[i]); +} + +void **stb_array_block_alloc(int count, int blocksize) +{ + int i; + char *p = (char *) malloc(sizeof(void *) * count + count * blocksize); + void **q; + if (p == NULL) return NULL; + q = (void **) p; + p += sizeof(void *) * count; + for (i=0; i < count; ++i) + q[i] = p + i * blocksize; + return q; +} +#endif + +#ifdef STB_DEBUG + // tricky hack to allow recording FILE,LINE even in varargs functions + #define STB__RECORD_FILE(x) (stb__record_fileline(__FILE__, __LINE__),(x)) + #define stb_log STB__RECORD_FILE(stb_log) + #define stb_ STB__RECORD_FILE(stb_) + #ifndef STB_FATAL_CLEAN + #define stb_fatal STB__RECORD_FILE(stb_fatal) + #endif + #define STB__DEBUG(x) x +#else + #define STB__DEBUG(x) +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// stb_temp +// + +#define stb_temp(block, sz) stb__temp(block, sizeof(block), (sz)) + +STB_EXTERN void * stb__temp(void *b, int b_sz, int want_sz); +STB_EXTERN void stb_tempfree(void *block, void *ptr); + +#ifdef STB_DEFINE + +void * stb__temp(void *b, int b_sz, int want_sz) +{ + if (b_sz >= want_sz) + return b; + else + return malloc(want_sz); +} + +void stb_tempfree(void *b, void *p) +{ + if (p != b) + free(p); +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// math/sampling operations +// + + +#define stb_lerp(t,a,b) ( (a) + (t) * (float) ((b)-(a)) ) +#define stb_unlerp(t,a,b) ( ((t) - (a)) / (float) ((b) - (a)) ) + +#define stb_clamp(x,xmin,xmax) ((x) < (xmin) ? (xmin) : (x) > (xmax) ? (xmax) : (x)) + +STB_EXTERN void stb_newell_normal(float *normal, int num_vert, float **vert, int normalize); +STB_EXTERN int stb_box_face_vertex_axis_side(int face_number, int vertex_number, int axis); +STB_EXTERN void stb_linear_controller(float *curpos, float target_pos, float acc, float deacc, float dt); + +STB_EXTERN int stb_float_eq(float x, float y, float delta, int max_ulps); +STB_EXTERN int stb_is_prime(unsigned int m); +STB_EXTERN unsigned int stb_power_of_two_nearest_prime(int n); + +STB_EXTERN float stb_smoothstep(float t); +STB_EXTERN float stb_cubic_bezier_1d(float t, float p0, float p1, float p2, float p3); + +STB_EXTERN double stb_linear_remap(double x, double a, double b, + double c, double d); + +#ifdef STB_DEFINE +float stb_smoothstep(float t) +{ + return (3 - 2*t)*(t*t); +} + +float stb_cubic_bezier_1d(float t, float p0, float p1, float p2, float p3) +{ + float it = 1-t; + return it*it*it*p0 + 3*it*it*t*p1 + 3*it*t*t*p2 + t*t*t*p3; +} + +void stb_newell_normal(float *normal, int num_vert, float **vert, int normalize) +{ + int i,j; + float p; + normal[0] = normal[1] = normal[2] = 0; + for (i=num_vert-1,j=0; j < num_vert; i=j++) { + float *u = vert[i]; + float *v = vert[j]; + normal[0] += (u[1] - v[1]) * (u[2] + v[2]); + normal[1] += (u[2] - v[2]) * (u[0] + v[0]); + normal[2] += (u[0] - v[0]) * (u[1] + v[1]); + } + if (normalize) { + p = normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]; + p = (float) (1.0 / sqrt(p)); + normal[0] *= p; + normal[1] *= p; + normal[2] *= p; + } +} + +int stb_box_face_vertex_axis_side(int face_number, int vertex_number, int axis) +{ + static int box_vertices[6][4][3] = + { + { { 1,1,1 }, { 1,0,1 }, { 1,0,0 }, { 1,1,0 } }, + { { 0,0,0 }, { 0,0,1 }, { 0,1,1 }, { 0,1,0 } }, + { { 0,0,0 }, { 0,1,0 }, { 1,1,0 }, { 1,0,0 } }, + { { 0,0,0 }, { 1,0,0 }, { 1,0,1 }, { 0,0,1 } }, + { { 1,1,1 }, { 0,1,1 }, { 0,0,1 }, { 1,0,1 } }, + { { 1,1,1 }, { 1,1,0 }, { 0,1,0 }, { 0,1,1 } }, + }; + assert(face_number >= 0 && face_number < 6); + assert(vertex_number >= 0 && vertex_number < 4); + assert(axis >= 0 && axis < 3); + return box_vertices[face_number][vertex_number][axis]; +} + +void stb_linear_controller(float *curpos, float target_pos, float acc, float deacc, float dt) +{ + float sign = 1, p, cp = *curpos; + if (cp == target_pos) return; + if (target_pos < cp) { + target_pos = -target_pos; + cp = -cp; + sign = -1; + } + // first decelerate + if (cp < 0) { + p = cp + deacc * dt; + if (p > 0) { + p = 0; + dt = dt - cp / deacc; + if (dt < 0) dt = 0; + } else { + dt = 0; + } + cp = p; + } + // now accelerate + p = cp + acc*dt; + if (p > target_pos) p = target_pos; + *curpos = p * sign; + // @TODO: testing +} + +float stb_quadratic_controller(float target_pos, float curpos, float maxvel, float maxacc, float dt, float *curvel) +{ + return 0; // @TODO +} + +int stb_float_eq(float x, float y, float delta, int max_ulps) +{ + if (fabs(x-y) <= delta) return 1; + if (abs(*(int *)&x - *(int *)&y) <= max_ulps) return 1; + return 0; +} + +int stb_is_prime(unsigned int m) +{ + unsigned int i,j; + if (m < 2) return 0; + if (m == 2) return 1; + if (!(m & 1)) return 0; + if (m % 3 == 0) return (m == 3); + for (i=5; (j=i*i), j <= m && j > i; i += 6) { + if (m % i == 0) return 0; + if (m % (i+2) == 0) return 0; + } + return 1; +} + +unsigned int stb_power_of_two_nearest_prime(int n) +{ + static signed char tab[32] = { 0,0,0,0,1,0,-1,0,1,-1,-1,3,-1,0,-1,2,1, + 0,2,0,-1,-4,-1,5,-1,18,-2,15,2,-1,2,0 }; + if (!tab[0]) { + int i; + for (i=0; i < 32; ++i) + tab[i] = (1 << i) + 2*tab[i] - 1; + tab[1] = 2; + tab[0] = 1; + } + if (n >= 32) return 0xfffffffb; + return tab[n]; +} + +double stb_linear_remap(double x, double x_min, double x_max, + double out_min, double out_max) +{ + return stb_lerp(stb_unlerp(x,x_min,x_max),out_min,out_max); +} +#endif + +// create a macro so it's faster, but you can get at the function pointer +#define stb_linear_remap(t,a,b,c,d) stb_lerp(stb_unlerp(t,a,b),c,d) + + +////////////////////////////////////////////////////////////////////////////// +// +// bit operations +// + +#define stb_big32(c) (((c)[0]<<24) + (c)[1]*65536 + (c)[2]*256 + (c)[3]) +#define stb_little32(c) (((c)[3]<<24) + (c)[2]*65536 + (c)[1]*256 + (c)[0]) +#define stb_big16(c) ((c)[0]*256 + (c)[1]) +#define stb_little16(c) ((c)[1]*256 + (c)[0]) + +STB_EXTERN int stb_bitcount(unsigned int a); +STB_EXTERN unsigned int stb_bitreverse8(unsigned char n); +STB_EXTERN unsigned int stb_bitreverse(unsigned int n); + +STB_EXTERN int stb_is_pow2(size_t); +STB_EXTERN int stb_log2_ceil(size_t); +STB_EXTERN int stb_log2_floor(size_t); + +STB_EXTERN int stb_lowbit8(unsigned int n); +STB_EXTERN int stb_highbit8(unsigned int n); + +#ifdef STB_DEFINE +int stb_bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +unsigned int stb_bitreverse8(unsigned char n) +{ + n = ((n & 0xAA) >> 1) + ((n & 0x55) << 1); + n = ((n & 0xCC) >> 2) + ((n & 0x33) << 2); + return (unsigned char) ((n >> 4) + (n << 4)); +} + +unsigned int stb_bitreverse(unsigned int n) +{ + n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1); + n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2); + n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4); + n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8); + return (n >> 16) | (n << 16); +} + +int stb_is_pow2(size_t n) +{ + return (n & (n-1)) == 0; +} + +// tricky use of 4-bit table to identify 5 bit positions (note the '-1') +// 3-bit table would require another tree level; 5-bit table wouldn't save one +#if defined(_WIN32) && !defined(__MINGW32__) +#pragma warning(push) +#pragma warning(disable: 4035) // disable warning about no return value +int stb_log2_floor(size_t n) +{ + #if _MSC_VER > 1700 + unsigned long i; + #ifdef STB_PTR64 + _BitScanReverse64(&i, n); + #else + _BitScanReverse(&i, n); + #endif + return i != 0 ? i : -1; + #else + __asm { + bsr eax,n + jnz done + mov eax,-1 + } + done:; + #endif +} +#pragma warning(pop) +#else +int stb_log2_floor(size_t n) +{ + static signed char log2_4[16] = { -1,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3 }; + +#ifdef STB_PTR64 + if (n >= ((size_t) 1u << 32)) + return stb_log2_floor(n >> 32); +#endif + + // 2 compares if n < 16, 3 compares otherwise + if (n < (1U << 14)) + if (n < (1U << 4)) return 0 + log2_4[n ]; + else if (n < (1U << 9)) return 5 + log2_4[n >> 5]; + else return 10 + log2_4[n >> 10]; + else if (n < (1U << 24)) + if (n < (1U << 19)) return 15 + log2_4[n >> 15]; + else return 20 + log2_4[n >> 20]; + else if (n < (1U << 29)) return 25 + log2_4[n >> 25]; + else return 30 + log2_4[n >> 30]; +} +#endif + +// define ceil from floor +int stb_log2_ceil(size_t n) +{ + if (stb_is_pow2(n)) return stb_log2_floor(n); + else return 1 + stb_log2_floor(n); +} + +int stb_highbit8(unsigned int n) +{ + return stb_log2_ceil(n&255); +} + +int stb_lowbit8(unsigned int n) +{ + static signed char lowbit4[16] = { -1,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0 }; + int k = lowbit4[n & 15]; + if (k >= 0) return k; + k = lowbit4[(n >> 4) & 15]; + if (k >= 0) return k+4; + return k; +} +#endif + + + +////////////////////////////////////////////////////////////////////////////// +// +// qsort Compare Routines +// + +#ifdef _WIN32 + #define stb_stricmp(a,b) stb_p_stricmp(a,b) + #define stb_strnicmp(a,b,n) stb_p_strnicmp(a,b,n) +#else + #define stb_stricmp(a,b) strcasecmp(a,b) + #define stb_strnicmp(a,b,n) strncasecmp(a,b,n) +#endif + + +STB_EXTERN int (*stb_intcmp(int offset))(const void *a, const void *b); +STB_EXTERN int (*stb_intcmprev(int offset))(const void *a, const void *b); +STB_EXTERN int (*stb_qsort_strcmp(int offset))(const void *a, const void *b); +STB_EXTERN int (*stb_qsort_stricmp(int offset))(const void *a, const void *b); +STB_EXTERN int (*stb_floatcmp(int offset))(const void *a, const void *b); +STB_EXTERN int (*stb_doublecmp(int offset))(const void *a, const void *b); +STB_EXTERN int (*stb_charcmp(int offset))(const void *a, const void *b); + +#ifdef STB_DEFINE +static int stb__intcmpoffset, stb__ucharcmpoffset, stb__strcmpoffset; +static int stb__floatcmpoffset, stb__doublecmpoffset; +static int stb__memcmpoffset, stb__memcmpsize; + +int stb__intcmp(const void *a, const void *b) +{ + const int p = *(const int *) ((const char *) a + stb__intcmpoffset); + const int q = *(const int *) ((const char *) b + stb__intcmpoffset); + return p < q ? -1 : p > q; +} + +int stb__intcmprev(const void *a, const void *b) +{ + const int p = *(const int *) ((const char *) a + stb__intcmpoffset); + const int q = *(const int *) ((const char *) b + stb__intcmpoffset); + return q < p ? -1 : q > p; +} + +int stb__ucharcmp(const void *a, const void *b) +{ + const int p = *(const unsigned char *) ((const char *) a + stb__ucharcmpoffset); + const int q = *(const unsigned char *) ((const char *) b + stb__ucharcmpoffset); + return p < q ? -1 : p > q; +} + +int stb__floatcmp(const void *a, const void *b) +{ + const float p = *(const float *) ((const char *) a + stb__floatcmpoffset); + const float q = *(const float *) ((const char *) b + stb__floatcmpoffset); + return p < q ? -1 : p > q; +} + +int stb__doublecmp(const void *a, const void *b) +{ + const double p = *(const double *) ((const char *) a + stb__doublecmpoffset); + const double q = *(const double *) ((const char *) b + stb__doublecmpoffset); + return p < q ? -1 : p > q; +} + +int stb__qsort_strcmp(const void *a, const void *b) +{ + const char *p = *(const char **) ((const char *) a + stb__strcmpoffset); + const char *q = *(const char **) ((const char *) b + stb__strcmpoffset); + return strcmp(p,q); +} + +int stb__qsort_stricmp(const void *a, const void *b) +{ + const char *p = *(const char **) ((const char *) a + stb__strcmpoffset); + const char *q = *(const char **) ((const char *) b + stb__strcmpoffset); + return stb_stricmp(p,q); +} + +int stb__memcmp(const void *a, const void *b) +{ + return memcmp((char *) a + stb__memcmpoffset, (char *) b + stb__memcmpoffset, stb__memcmpsize); +} + +int (*stb_intcmp(int offset))(const void *, const void *) +{ + stb__intcmpoffset = offset; + return &stb__intcmp; +} + +int (*stb_intcmprev(int offset))(const void *, const void *) +{ + stb__intcmpoffset = offset; + return &stb__intcmprev; +} + +int (*stb_ucharcmp(int offset))(const void *, const void *) +{ + stb__ucharcmpoffset = offset; + return &stb__ucharcmp; +} + +int (*stb_qsort_strcmp(int offset))(const void *, const void *) +{ + stb__strcmpoffset = offset; + return &stb__qsort_strcmp; +} + +int (*stb_qsort_stricmp(int offset))(const void *, const void *) +{ + stb__strcmpoffset = offset; + return &stb__qsort_stricmp; +} + +int (*stb_floatcmp(int offset))(const void *, const void *) +{ + stb__floatcmpoffset = offset; + return &stb__floatcmp; +} + +int (*stb_doublecmp(int offset))(const void *, const void *) +{ + stb__doublecmpoffset = offset; + return &stb__doublecmp; +} + +int (*stb_memcmp(int offset, int size))(const void *, const void *) +{ + stb__memcmpoffset = offset; + stb__memcmpsize = size; + return &stb__memcmp; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Binary Search Toolkit +// + +typedef struct +{ + int minval, maxval, guess; + int mode, step; +} stb_search; + +STB_EXTERN int stb_search_binary(stb_search *s, int minv, int maxv, int find_smallest); +STB_EXTERN int stb_search_open(stb_search *s, int minv, int find_smallest); +STB_EXTERN int stb_probe(stb_search *s, int compare, int *result); // return 0 when done + +#ifdef STB_DEFINE +enum +{ + STB_probe_binary_smallest, + STB_probe_binary_largest, + STB_probe_open_smallest, + STB_probe_open_largest, +}; + +static int stb_probe_guess(stb_search *s, int *result) +{ + switch(s->mode) { + case STB_probe_binary_largest: + if (s->minval == s->maxval) { + *result = s->minval; + return 0; + } + assert(s->minval < s->maxval); + // if a < b, then a < p <= b + s->guess = s->minval + (((unsigned) s->maxval - s->minval + 1) >> 1); + break; + + case STB_probe_binary_smallest: + if (s->minval == s->maxval) { + *result = s->minval; + return 0; + } + assert(s->minval < s->maxval); + // if a < b, then a <= p < b + s->guess = s->minval + (((unsigned) s->maxval - s->minval) >> 1); + break; + case STB_probe_open_smallest: + case STB_probe_open_largest: + s->guess = s->maxval; // guess the current maxval + break; + } + *result = s->guess; + return 1; +} + +int stb_probe(stb_search *s, int compare, int *result) +{ + switch(s->mode) { + case STB_probe_open_smallest: + case STB_probe_open_largest: { + if (compare <= 0) { + // then it lies within minval & maxval + if (s->mode == STB_probe_open_smallest) + s->mode = STB_probe_binary_smallest; + else + s->mode = STB_probe_binary_largest; + } else { + // otherwise, we need to probe larger + s->minval = s->maxval + 1; + s->maxval = s->minval + s->step; + s->step += s->step; + } + break; + } + case STB_probe_binary_smallest: { + // if compare < 0, then s->minval <= a < p + // if compare = 0, then s->minval <= a <= p + // if compare > 0, then p < a <= s->maxval + if (compare <= 0) + s->maxval = s->guess; + else + s->minval = s->guess+1; + break; + } + case STB_probe_binary_largest: { + // if compare < 0, then s->minval <= a < p + // if compare = 0, then p <= a <= s->maxval + // if compare > 0, then p < a <= s->maxval + if (compare < 0) + s->maxval = s->guess-1; + else + s->minval = s->guess; + break; + } + } + return stb_probe_guess(s, result); +} + +int stb_search_binary(stb_search *s, int minv, int maxv, int find_smallest) +{ + int r; + if (maxv < minv) return minv-1; + s->minval = minv; + s->maxval = maxv; + s->mode = find_smallest ? STB_probe_binary_smallest : STB_probe_binary_largest; + stb_probe_guess(s, &r); + return r; +} + +int stb_search_open(stb_search *s, int minv, int find_smallest) +{ + int r; + s->step = 4; + s->minval = minv; + s->maxval = minv+s->step; + s->mode = find_smallest ? STB_probe_open_smallest : STB_probe_open_largest; + stb_probe_guess(s, &r); + return r; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// String Processing +// + +#define stb_prefixi(s,t) (0==stb_strnicmp((s),(t),strlen(t))) + +enum stb_splitpath_flag +{ + STB_PATH = 1, + STB_FILE = 2, + STB_EXT = 4, + STB_PATH_FILE = STB_PATH + STB_FILE, + STB_FILE_EXT = STB_FILE + STB_EXT, + STB_EXT_NO_PERIOD = 8, +}; + +STB_EXTERN char * stb_skipwhite(char *s); +STB_EXTERN char * stb_trimwhite(char *s); +STB_EXTERN char * stb_skipnewline(char *s); +STB_EXTERN char * stb_strncpy(char *s, char *t, int n); +STB_EXTERN char * stb_substr(char *t, int n); +STB_EXTERN char * stb_duplower(char *s); +STB_EXTERN void stb_tolower (char *s); +STB_EXTERN char * stb_strchr2 (char *s, char p1, char p2); +STB_EXTERN char * stb_strrchr2(char *s, char p1, char p2); +STB_EXTERN char * stb_strtok(char *output, char *src, char *delimit); +STB_EXTERN char * stb_strtok_keep(char *output, char *src, char *delimit); +STB_EXTERN char * stb_strtok_invert(char *output, char *src, char *allowed); +STB_EXTERN char * stb_dupreplace(char *s, char *find, char *replace); +STB_EXTERN void stb_replaceinplace(char *s, char *find, char *replace); +STB_EXTERN char * stb_splitpath(char *output, char *src, int flag); +STB_EXTERN char * stb_splitpathdup(char *src, int flag); +STB_EXTERN char * stb_replacedir(char *output, char *src, char *dir); +STB_EXTERN char * stb_replaceext(char *output, char *src, char *ext); +STB_EXTERN void stb_fixpath(char *path); +STB_EXTERN char * stb_shorten_path_readable(char *path, int max_len); +STB_EXTERN int stb_suffix (char *s, char *t); +STB_EXTERN int stb_suffixi(char *s, char *t); +STB_EXTERN int stb_prefix (char *s, char *t); +STB_EXTERN char * stb_strichr(char *s, char t); +STB_EXTERN char * stb_stristr(char *s, char *t); +STB_EXTERN int stb_prefix_count(char *s, char *t); +STB_EXTERN const char * stb_plural(int n); // "s" or "" +STB_EXTERN size_t stb_strscpy(char *d, const char *s, size_t n); + +STB_EXTERN char **stb_tokens(char *src, char *delimit, int *count); +STB_EXTERN char **stb_tokens_nested(char *src, char *delimit, int *count, char *nest_in, char *nest_out); +STB_EXTERN char **stb_tokens_nested_empty(char *src, char *delimit, int *count, char *nest_in, char *nest_out); +STB_EXTERN char **stb_tokens_allowempty(char *src, char *delimit, int *count); +STB_EXTERN char **stb_tokens_stripwhite(char *src, char *delimit, int *count); +STB_EXTERN char **stb_tokens_withdelim(char *src, char *delimit, int *count); +STB_EXTERN char **stb_tokens_quoted(char *src, char *delimit, int *count); +// with 'quoted', allow delimiters to appear inside quotation marks, and don't +// strip whitespace inside them (and we delete the quotation marks unless they +// appear back to back, in which case they're considered escaped) + +#ifdef STB_DEFINE + +size_t stb_strscpy(char *d, const char *s, size_t n) +{ + size_t len = strlen(s); + if (len >= n) { + if (n) d[0] = 0; + return 0; + } + stb_p_strcpy_s(d,n,s); + return len; +} + +const char *stb_plural(int n) +{ + return n == 1 ? "" : "s"; +} + +int stb_prefix(char *s, char *t) +{ + while (*t) + if (*s++ != *t++) + return STB_FALSE; + return STB_TRUE; +} + +int stb_prefix_count(char *s, char *t) +{ + int c=0; + while (*t) { + if (*s++ != *t++) + break; + ++c; + } + return c; +} + +int stb_suffix(char *s, char *t) +{ + size_t n = strlen(s); + size_t m = strlen(t); + if (m <= n) + return 0 == strcmp(s+n-m, t); + else + return 0; +} + +int stb_suffixi(char *s, char *t) +{ + size_t n = strlen(s); + size_t m = strlen(t); + if (m <= n) + return 0 == stb_stricmp(s+n-m, t); + else + return 0; +} + +// originally I was using this table so that I could create known sentinel +// values--e.g. change whitetable[0] to be true if I was scanning for whitespace, +// and false if I was scanning for nonwhite. I don't appear to be using that +// functionality anymore (I do for tokentable, though), so just replace it +// with isspace() +char *stb_skipwhite(char *s) +{ + while (isspace((unsigned char) *s)) ++s; + return s; +} + +char *stb_skipnewline(char *s) +{ + if (s[0] == '\r' || s[0] == '\n') { + if (s[0]+s[1] == '\r' + '\n') ++s; + ++s; + } + return s; +} + +char *stb_trimwhite(char *s) +{ + int i,n; + s = stb_skipwhite(s); + n = (int) strlen(s); + for (i=n-1; i >= 0; --i) + if (!isspace(s[i])) + break; + s[i+1] = 0; + return s; +} + +char *stb_strncpy(char *s, char *t, int n) +{ + stb_p_strncpy_s(s,n+1,t,n); + s[n] = 0; + return s; +} + +char *stb_substr(char *t, int n) +{ + char *a; + int z = (int) strlen(t); + if (z < n) n = z; + a = (char *) malloc(n+1); + stb_p_strncpy_s(a,n+1,t,n); + a[n] = 0; + return a; +} + +char *stb_duplower(char *s) +{ + char *p = stb_p_strdup(s), *q = p; + while (*q) { + *q = tolower(*q); + ++q; + } + return p; +} + +void stb_tolower(char *s) +{ + while (*s) { + *s = tolower(*s); + ++s; + } +} + +char *stb_strchr2(char *s, char x, char y) +{ + for(; *s; ++s) + if (*s == x || *s == y) + return s; + return NULL; +} + +char *stb_strrchr2(char *s, char x, char y) +{ + char *r = NULL; + for(; *s; ++s) + if (*s == x || *s == y) + r = s; + return r; +} + +char *stb_strichr(char *s, char t) +{ + if (tolower(t) == toupper(t)) + return strchr(s,t); + return stb_strchr2(s, (char) tolower(t), (char) toupper(t)); +} + +char *stb_stristr(char *s, char *t) +{ + size_t n = strlen(t); + char *z; + if (n==0) return s; + while ((z = stb_strichr(s, *t)) != NULL) { + if (0==stb_strnicmp(z, t, n)) + return z; + s = z+1; + } + return NULL; +} + +static char *stb_strtok_raw(char *output, char *src, char *delimit, int keep, int invert) +{ + if (invert) { + while (*src && strchr(delimit, *src) != NULL) { + *output++ = *src++; + } + } else { + while (*src && strchr(delimit, *src) == NULL) { + *output++ = *src++; + } + } + *output = 0; + if (keep) + return src; + else + return *src ? src+1 : src; +} + +char *stb_strtok(char *output, char *src, char *delimit) +{ + return stb_strtok_raw(output, src, delimit, 0, 0); +} + +char *stb_strtok_keep(char *output, char *src, char *delimit) +{ + return stb_strtok_raw(output, src, delimit, 1, 0); +} + +char *stb_strtok_invert(char *output, char *src, char *delimit) +{ + return stb_strtok_raw(output, src, delimit, 1,1); +} + +static char **stb_tokens_raw(char *src_, char *delimit, int *count, + int stripwhite, int allow_empty, char *start, char *end) +{ + int nested = 0; + unsigned char *src = (unsigned char *) src_; + static char stb_tokentable[256]; // rely on static initializion to 0 + static char stable[256],etable[256]; + char *out; + char **result; + int num=0; + unsigned char *s; + + s = (unsigned char *) delimit; while (*s) stb_tokentable[*s++] = 1; + if (start) { + s = (unsigned char *) start; while (*s) stable[*s++] = 1; + s = (unsigned char *) end; if (s) while (*s) stable[*s++] = 1; + s = (unsigned char *) end; if (s) while (*s) etable[*s++] = 1; + } + stable[0] = 1; + + // two passes through: the first time, counting how many + s = (unsigned char *) src; + while (*s) { + // state: just found delimiter + // skip further delimiters + if (!allow_empty) { + stb_tokentable[0] = 0; + while (stb_tokentable[*s]) + ++s; + if (!*s) break; + } + ++num; + // skip further non-delimiters + stb_tokentable[0] = 1; + if (stripwhite == 2) { // quoted strings + while (!stb_tokentable[*s]) { + if (*s != '"') + ++s; + else { + ++s; + if (*s == '"') + ++s; // "" -> ", not start a string + else { + // begin a string + while (*s) { + if (s[0] == '"') { + if (s[1] == '"') s += 2; // "" -> " + else { ++s; break; } // terminating " + } else + ++s; + } + } + } + } + } else + while (nested || !stb_tokentable[*s]) { + if (stable[*s]) { + if (!*s) break; + if (end ? etable[*s] : nested) + --nested; + else + ++nested; + } + ++s; + } + if (allow_empty) { + if (*s) ++s; + } + } + // now num has the actual count... malloc our output structure + // need space for all the strings: strings won't be any longer than + // original input, since for every '\0' there's at least one delimiter + result = (char **) malloc(sizeof(*result) * (num+1) + (s-src+1)); + if (result == NULL) return result; + out = (char *) (result + (num+1)); + // second pass: copy out the data + s = (unsigned char *) src; + num = 0; + nested = 0; + while (*s) { + char *last_nonwhite; + // state: just found delimiter + // skip further delimiters + if (!allow_empty) { + stb_tokentable[0] = 0; + if (stripwhite) + while (stb_tokentable[*s] || isspace(*s)) + ++s; + else + while (stb_tokentable[*s]) + ++s; + } else if (stripwhite) { + while (isspace(*s)) ++s; + } + if (!*s) break; + // we're past any leading delimiters and whitespace + result[num] = out; + ++num; + // copy non-delimiters + stb_tokentable[0] = 1; + last_nonwhite = out-1; + if (stripwhite == 2) { + while (!stb_tokentable[*s]) { + if (*s != '"') { + if (!isspace(*s)) last_nonwhite = out; + *out++ = *s++; + } else { + ++s; + if (*s == '"') { + if (!isspace(*s)) last_nonwhite = out; + *out++ = *s++; // "" -> ", not start string + } else { + // begin a quoted string + while (*s) { + if (s[0] == '"') { + if (s[1] == '"') { *out++ = *s; s += 2; } + else { ++s; break; } // terminating " + } else + *out++ = *s++; + } + last_nonwhite = out-1; // all in quotes counts as non-white + } + } + } + } else { + while (nested || !stb_tokentable[*s]) { + if (!isspace(*s)) last_nonwhite = out; + if (stable[*s]) { + if (!*s) break; + if (end ? etable[*s] : nested) + --nested; + else + ++nested; + } + *out++ = *s++; + } + } + + if (stripwhite) // rewind to last non-whitespace char + out = last_nonwhite+1; + *out++ = '\0'; + + if (*s) ++s; // skip delimiter + } + s = (unsigned char *) delimit; while (*s) stb_tokentable[*s++] = 0; + if (start) { + s = (unsigned char *) start; while (*s) stable[*s++] = 1; + s = (unsigned char *) end; if (s) while (*s) stable[*s++] = 1; + s = (unsigned char *) end; if (s) while (*s) etable[*s++] = 1; + } + if (count != NULL) *count = num; + result[num] = 0; + return result; +} + +char **stb_tokens(char *src, char *delimit, int *count) +{ + return stb_tokens_raw(src,delimit,count,0,0,0,0); +} + +char **stb_tokens_nested(char *src, char *delimit, int *count, char *nest_in, char *nest_out) +{ + return stb_tokens_raw(src,delimit,count,0,0,nest_in,nest_out); +} + +char **stb_tokens_nested_empty(char *src, char *delimit, int *count, char *nest_in, char *nest_out) +{ + return stb_tokens_raw(src,delimit,count,0,1,nest_in,nest_out); +} + +char **stb_tokens_allowempty(char *src, char *delimit, int *count) +{ + return stb_tokens_raw(src,delimit,count,0,1,0,0); +} + +char **stb_tokens_stripwhite(char *src, char *delimit, int *count) +{ + return stb_tokens_raw(src,delimit,count,1,1,0,0); +} + +char **stb_tokens_quoted(char *src, char *delimit, int *count) +{ + return stb_tokens_raw(src,delimit,count,2,1,0,0); +} + +char *stb_dupreplace(char *src, char *find, char *replace) +{ + size_t len_find = strlen(find); + size_t len_replace = strlen(replace); + int count = 0; + + char *s,*p,*q; + + s = strstr(src, find); + if (s == NULL) return stb_p_strdup(src); + do { + ++count; + s = strstr(s + len_find, find); + } while (s != NULL); + + p = (char *) malloc(strlen(src) + count * (len_replace - len_find) + 1); + if (p == NULL) return p; + q = p; + s = src; + for (;;) { + char *t = strstr(s, find); + if (t == NULL) { + stb_p_strcpy_s(q,strlen(src)+count*(len_replace-len_find)+1,s); + assert(strlen(p) == strlen(src) + count*(len_replace-len_find)); + return p; + } + memcpy(q, s, t-s); + q += t-s; + memcpy(q, replace, len_replace); + q += len_replace; + s = t + len_find; + } +} + +void stb_replaceinplace(char *src, char *find, char *replace) +{ + size_t len_find = strlen(find); + size_t len_replace = strlen(replace); + int delta; + + char *s,*p,*q; + + delta = (int) (len_replace - len_find); + assert(delta <= 0); + if (delta > 0) return; + + p = strstr(src, find); + if (p == NULL) return; + + s = q = p; + while (*s) { + memcpy(q, replace, len_replace); + p += len_find; + q += len_replace; + s = strstr(p, find); + if (s == NULL) s = p + strlen(p); + memmove(q, p, s-p); + q += s-p; + p = s; + } + *q = 0; +} + +void stb_fixpath(char *path) +{ + for(; *path; ++path) + if (*path == '\\') + *path = '/'; +} + +void stb__add_section(char *buffer, char *data, ptrdiff_t curlen, ptrdiff_t newlen) +{ + if (newlen < curlen) { + ptrdiff_t z1 = newlen >> 1, z2 = newlen-z1; + memcpy(buffer, data, z1-1); + buffer[z1-1] = '.'; + buffer[z1-0] = '.'; + memcpy(buffer+z1+1, data+curlen-z2+1, z2-1); + } else + memcpy(buffer, data, curlen); +} + +char * stb_shorten_path_readable(char *path, int len) +{ + static char buffer[1024]; + ptrdiff_t n = strlen(path),n1,n2,r1,r2; + char *s; + if (n <= len) return path; + if (len > 1024) return path; + s = stb_strrchr2(path, '/', '\\'); + if (s) { + n1 = s - path + 1; + n2 = n - n1; + ++s; + } else { + n1 = 0; + n2 = n; + s = path; + } + // now we need to reduce r1 and r2 so that they fit in len + if (n1 < len>>1) { + r1 = n1; + r2 = len - r1; + } else if (n2 < len >> 1) { + r2 = n2; + r1 = len - r2; + } else { + r1 = n1 * len / n; + r2 = n2 * len / n; + if (r1 < len>>2) r1 = len>>2, r2 = len-r1; + if (r2 < len>>2) r2 = len>>2, r1 = len-r2; + } + assert(r1 <= n1 && r2 <= n2); + if (n1) + stb__add_section(buffer, path, n1, r1); + stb__add_section(buffer+r1, s, n2, r2); + buffer[len] = 0; + return buffer; +} + +static char *stb__splitpath_raw(char *buffer, char *path, int flag) +{ + ptrdiff_t len=0,x,y, n = (int) strlen(path), f1,f2; + char *s = stb_strrchr2(path, '/', '\\'); + char *t = strrchr(path, '.'); + if (s && t && t < s) t = NULL; + + if (!s) { + // check for drive + if (isalpha(path[0]) && path[1] == ':') + s = &path[1]; + } + if (s) ++s; + + if (flag == STB_EXT_NO_PERIOD) + flag |= STB_EXT; + + if (!(flag & (STB_PATH | STB_FILE | STB_EXT))) return NULL; + + f1 = s == NULL ? 0 : s-path; // start of filename + f2 = t == NULL ? n : t-path; // just past end of filename + + if (flag & STB_PATH) { + x = 0; if (f1 == 0 && flag == STB_PATH) len=2; + } else if (flag & STB_FILE) { + x = f1; + } else { + x = f2; + if (flag & STB_EXT_NO_PERIOD) + if (path[x] == '.') + ++x; + } + + if (flag & STB_EXT) + y = n; + else if (flag & STB_FILE) + y = f2; + else + y = f1; + + if (buffer == NULL) { + buffer = (char *) malloc(y-x + len + 1); + if (!buffer) return NULL; + } + + if (len) { stb_p_strcpy_s(buffer, 3, "./"); return buffer; } + stb_strncpy(buffer, path+(int)x, (int)(y-x)); + return buffer; +} + +char *stb_splitpath(char *output, char *src, int flag) +{ + return stb__splitpath_raw(output, src, flag); +} + +char *stb_splitpathdup(char *src, int flag) +{ + return stb__splitpath_raw(NULL, src, flag); +} + +char *stb_replacedir(char *output, char *src, char *dir) +{ + char buffer[4096]; + stb_splitpath(buffer, src, STB_FILE | STB_EXT); + if (dir) + stb_p_sprintf(output stb_p_size(9999), "%s/%s", dir, buffer); + else + stb_p_strcpy_s(output, sizeof(buffer), buffer); // @UNSAFE + return output; +} + +char *stb_replaceext(char *output, char *src, char *ext) +{ + char buffer[4096]; + stb_splitpath(buffer, src, STB_PATH | STB_FILE); + if (ext) + stb_p_sprintf(output stb_p_size(9999), "%s.%s", buffer, ext[0] == '.' ? ext+1 : ext); + else + stb_p_strcpy_s(output, sizeof(buffer), buffer); // @UNSAFE + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// stb_alloc - hierarchical allocator +// +// inspired by http://swapped.cc/halloc +// +// +// When you alloc a given block through stb_alloc, you have these choices: +// +// 1. does it have a parent? +// 2. can it have children? +// 3. can it be freed directly? +// 4. is it transferrable? +// 5. what is its alignment? +// +// Here are interesting combinations of those: +// +// children free transfer alignment +// arena Y Y N n/a +// no-overhead, chunked N N N normal +// string pool alloc N N N 1 +// parent-ptr, chunked Y N N normal +// low-overhead, unchunked N Y Y normal +// general purpose alloc Y Y Y normal +// +// Unchunked allocations will probably return 16-aligned pointers. If +// we 16-align the results, we have room for 4 pointers. For smaller +// allocations that allow finer alignment, we can reduce the pointers. +// +// The strategy is that given a pointer, assuming it has a header (only +// the no-overhead allocations have no header), we can determine the +// type of the header fields, and the number of them, by stepping backwards +// through memory and looking at the tags in the bottom bits. +// +// Implementation strategy: +// chunked allocations come from the middle of chunks, and can't +// be freed. thefore they do not need to be on a sibling chain. +// they may need child pointers if they have children. +// +// chunked, with-children +// void *parent; +// +// unchunked, no-children -- reduced storage +// void *next_sibling; +// void *prev_sibling_nextp; +// +// unchunked, general +// void *first_child; +// void *next_sibling; +// void *prev_sibling_nextp; +// void *chunks; +// +// so, if we code each of these fields with different bit patterns +// (actually same one for next/prev/child), then we can identify which +// each one is from the last field. + +STB_EXTERN void stb_free(void *p); +STB_EXTERN void *stb_malloc_global(size_t size); +STB_EXTERN void *stb_malloc(void *context, size_t size); +STB_EXTERN void *stb_malloc_nofree(void *context, size_t size); +STB_EXTERN void *stb_malloc_leaf(void *context, size_t size); +STB_EXTERN void *stb_malloc_raw(void *context, size_t size); +STB_EXTERN void *stb_realloc(void *ptr, size_t newsize); + +STB_EXTERN void stb_reassign(void *new_context, void *ptr); +STB_EXTERN void stb_malloc_validate(void *p, void *parent); + +extern int stb_alloc_chunk_size ; +extern int stb_alloc_count_free ; +extern int stb_alloc_count_alloc; +extern int stb_alloc_alignment ; + +#ifdef STB_DEFINE + +int stb_alloc_chunk_size = 65536; +int stb_alloc_count_free = 0; +int stb_alloc_count_alloc = 0; +int stb_alloc_alignment = -16; + +typedef struct stb__chunk +{ + struct stb__chunk *next; + int data_left; + int alloc; +} stb__chunk; + +typedef struct +{ + void * next; + void ** prevn; +} stb__nochildren; + +typedef struct +{ + void ** prevn; + void * child; + void * next; + stb__chunk *chunks; +} stb__alloc; + +typedef struct +{ + stb__alloc *parent; +} stb__chunked; + +#define STB__PARENT 1 +#define STB__CHUNKS 2 + +typedef enum +{ + STB__nochildren = 0, + STB__chunked = STB__PARENT, + STB__alloc = STB__CHUNKS, + + STB__chunk_raw = 4, +} stb__alloc_type; + +// these functions set the bottom bits of a pointer efficiently +#define STB__DECODE(x,v) ((void *) ((char *) (x) - (v))) +#define STB__ENCODE(x,v) ((void *) ((char *) (x) + (v))) + +#define stb__parent(z) (stb__alloc *) STB__DECODE((z)->parent, STB__PARENT) +#define stb__chunks(z) (stb__chunk *) STB__DECODE((z)->chunks, STB__CHUNKS) + +#define stb__setparent(z,p) (z)->parent = (stb__alloc *) STB__ENCODE((p), STB__PARENT) +#define stb__setchunks(z,c) (z)->chunks = (stb__chunk *) STB__ENCODE((c), STB__CHUNKS) + +static stb__alloc stb__alloc_global = +{ + NULL, + NULL, + NULL, + (stb__chunk *) STB__ENCODE(NULL, STB__CHUNKS) +}; + +static stb__alloc_type stb__identify(void *p) +{ + void **q = (void **) p; + return (stb__alloc_type) ((stb_uinta) q[-1] & 3); +} + +static void *** stb__prevn(void *p) +{ + if (stb__identify(p) == STB__alloc) { + stb__alloc *s = (stb__alloc *) p - 1; + return &s->prevn; + } else { + stb__nochildren *s = (stb__nochildren *) p - 1; + return &s->prevn; + } +} + +void stb_free(void *p) +{ + if (p == NULL) return; + + // count frees so that unit tests can see what's happening + ++stb_alloc_count_free; + + switch(stb__identify(p)) { + case STB__chunked: + // freeing a chunked-block with children does nothing; + // they only get freed when the parent does + // surely this is wrong, and it should free them immediately? + // otherwise how are they getting put on the right chain? + return; + case STB__nochildren: { + stb__nochildren *s = (stb__nochildren *) p - 1; + // unlink from sibling chain + *(s->prevn) = s->next; + if (s->next) + *stb__prevn(s->next) = s->prevn; + free(s); + return; + } + case STB__alloc: { + stb__alloc *s = (stb__alloc *) p - 1; + stb__chunk *c, *n; + void *q; + + // unlink from sibling chain, if any + *(s->prevn) = s->next; + if (s->next) + *stb__prevn(s->next) = s->prevn; + + // first free chunks + c = (stb__chunk *) stb__chunks(s); + while (c != NULL) { + n = c->next; + stb_alloc_count_free += c->alloc; + free(c); + c = n; + } + + // validating + stb__setchunks(s,NULL); + s->prevn = NULL; + s->next = NULL; + + // now free children + while ((q = s->child) != NULL) { + stb_free(q); + } + + // now free self + free(s); + return; + } + default: + assert(0); /* NOTREACHED */ + } +} + +void stb_malloc_validate(void *p, void *parent) +{ + if (p == NULL) return; + + switch(stb__identify(p)) { + case STB__chunked: + return; + case STB__nochildren: { + stb__nochildren *n = (stb__nochildren *) p - 1; + if (n->prevn) + assert(*n->prevn == p); + if (n->next) { + assert(*stb__prevn(n->next) == &n->next); + stb_malloc_validate(n, parent); + } + return; + } + case STB__alloc: { + stb__alloc *s = (stb__alloc *) p - 1; + + if (s->prevn) + assert(*s->prevn == p); + + if (s->child) { + assert(*stb__prevn(s->child) == &s->child); + stb_malloc_validate(s->child, p); + } + + if (s->next) { + assert(*stb__prevn(s->next) == &s->next); + stb_malloc_validate(s->next, parent); + } + return; + } + default: + assert(0); /* NOTREACHED */ + } +} + +static void * stb__try_chunk(stb__chunk *c, int size, int align, int pre_align) +{ + char *memblock = (char *) (c+1), *q; + stb_inta iq; + int start_offset; + + // we going to allocate at the end of the chunk, not the start. confusing, + // but it means we don't need both a 'limit' and a 'cur', just a 'cur'. + // the block ends at: p + c->data_left + // then we move back by size + start_offset = c->data_left - size; + + // now we need to check the alignment of that + q = memblock + start_offset; + iq = (stb_inta) q; + assert(sizeof(q) == sizeof(iq)); + + // suppose align = 2 + // then we need to retreat iq far enough that (iq & (2-1)) == 0 + // to get (iq & (align-1)) = 0 requires subtracting (iq & (align-1)) + + start_offset -= iq & (align-1); + assert(((stb_uinta) (memblock+start_offset) & (align-1)) == 0); + + // now, if that + pre_align works, go for it! + start_offset -= pre_align; + + if (start_offset >= 0) { + c->data_left = start_offset; + return memblock + start_offset; + } + + return NULL; +} + +static void stb__sort_chunks(stb__alloc *src) +{ + // of the first two chunks, put the chunk with more data left in it first + stb__chunk *c = stb__chunks(src), *d; + if (c == NULL) return; + d = c->next; + if (d == NULL) return; + if (c->data_left > d->data_left) return; + + c->next = d->next; + d->next = c; + stb__setchunks(src, d); +} + +static void * stb__alloc_chunk(stb__alloc *src, int size, int align, int pre_align) +{ + void *p; + stb__chunk *c = stb__chunks(src); + + if (c && size <= stb_alloc_chunk_size) { + + p = stb__try_chunk(c, size, align, pre_align); + if (p) { ++c->alloc; return p; } + + // try a second chunk to reduce wastage + if (c->next) { + p = stb__try_chunk(c->next, size, align, pre_align); + if (p) { ++c->alloc; return p; } + + // put the bigger chunk first, since the second will get buried + // the upshot of this is that, until it gets allocated from, chunk #2 + // is always the largest remaining chunk. (could formalize + // this with a heap!) + stb__sort_chunks(src); + c = stb__chunks(src); + } + } + + // allocate a new chunk + { + stb__chunk *n; + + int chunk_size = stb_alloc_chunk_size; + // we're going to allocate a new chunk to put this in + if (size > chunk_size) + chunk_size = size; + + assert(sizeof(*n) + pre_align <= 16); + + // loop trying to allocate a large enough chunk + // the loop is because the alignment may cause problems if it's big... + // and we don't know what our chunk alignment is going to be + while (1) { + n = (stb__chunk *) malloc(16 + chunk_size); + if (n == NULL) return NULL; + + n->data_left = chunk_size - sizeof(*n); + + p = stb__try_chunk(n, size, align, pre_align); + if (p != NULL) { + n->next = c; + stb__setchunks(src, n); + + // if we just used up the whole block immediately, + // move the following chunk up + n->alloc = 1; + if (size == chunk_size) + stb__sort_chunks(src); + + return p; + } + + free(n); + chunk_size += 16+align; + } + } +} + +static stb__alloc * stb__get_context(void *context) +{ + if (context == NULL) { + return &stb__alloc_global; + } else { + int u = stb__identify(context); + // if context is chunked, grab parent + if (u == STB__chunked) { + stb__chunked *s = (stb__chunked *) context - 1; + return stb__parent(s); + } else { + return (stb__alloc *) context - 1; + } + } +} + +static void stb__insert_alloc(stb__alloc *src, stb__alloc *s) +{ + s->prevn = &src->child; + s->next = src->child; + src->child = s+1; + if (s->next) + *stb__prevn(s->next) = &s->next; +} + +static void stb__insert_nochild(stb__alloc *src, stb__nochildren *s) +{ + s->prevn = &src->child; + s->next = src->child; + src->child = s+1; + if (s->next) + *stb__prevn(s->next) = &s->next; +} + +static void * malloc_base(void *context, size_t size, stb__alloc_type t, int align) +{ + void *p; + + stb__alloc *src = stb__get_context(context); + + if (align <= 0) { + // compute worst-case C packed alignment + // e.g. a 24-byte struct is 8-aligned + int align_proposed = 1 << stb_lowbit8((unsigned int) size); + + if (align_proposed < 0) + align_proposed = 4; + + if (align_proposed == 0) { + if (size == 0) + align_proposed = 1; + else + align_proposed = 256; + } + + // a negative alignment means 'don't align any larger + // than this'; so -16 means we align 1,2,4,8, or 16 + + if (align < 0) { + if (align_proposed > -align) + align_proposed = -align; + } + + align = align_proposed; + } + + assert(stb_is_pow2(align)); + + // don't cause misalignment when allocating nochildren + if (t == STB__nochildren && align > 8) + t = STB__alloc; + + switch (t) { + case STB__alloc: { + stb__alloc *s = (stb__alloc *) malloc(size + sizeof(*s)); + if (s == NULL) return NULL; + p = s+1; + s->child = NULL; + stb__insert_alloc(src, s); + + stb__setchunks(s,NULL); + break; + } + + case STB__nochildren: { + stb__nochildren *s = (stb__nochildren *) malloc(size + sizeof(*s)); + if (s == NULL) return NULL; + p = s+1; + stb__insert_nochild(src, s); + break; + } + + case STB__chunk_raw: { + p = stb__alloc_chunk(src, (int) size, align, 0); + if (p == NULL) return NULL; + break; + } + + case STB__chunked: { + stb__chunked *s; + if (align < sizeof(stb_uintptr)) align = sizeof(stb_uintptr); + s = (stb__chunked *) stb__alloc_chunk(src, (int) size, align, sizeof(*s)); + if (s == NULL) return NULL; + stb__setparent(s, src); + p = s+1; + break; + } + + default: p = NULL; assert(0); /* NOTREACHED */ + } + + ++stb_alloc_count_alloc; + return p; +} + +void *stb_malloc_global(size_t size) +{ + return malloc_base(NULL, size, STB__alloc, stb_alloc_alignment); +} + +void *stb_malloc(void *context, size_t size) +{ + return malloc_base(context, size, STB__alloc, stb_alloc_alignment); +} + +void *stb_malloc_nofree(void *context, size_t size) +{ + return malloc_base(context, size, STB__chunked, stb_alloc_alignment); +} + +void *stb_malloc_leaf(void *context, size_t size) +{ + return malloc_base(context, size, STB__nochildren, stb_alloc_alignment); +} + +void *stb_malloc_raw(void *context, size_t size) +{ + return malloc_base(context, size, STB__chunk_raw, stb_alloc_alignment); +} + +char *stb_malloc_string(void *context, size_t size) +{ + return (char *) malloc_base(context, size, STB__chunk_raw, 1); +} + +void *stb_realloc(void *ptr, size_t newsize) +{ + stb__alloc_type t; + + if (ptr == NULL) return stb_malloc(NULL, newsize); + if (newsize == 0) { stb_free(ptr); return NULL; } + + t = stb__identify(ptr); + assert(t == STB__alloc || t == STB__nochildren); + + if (t == STB__alloc) { + stb__alloc *s = (stb__alloc *) ptr - 1; + + s = (stb__alloc *) realloc(s, newsize + sizeof(*s)); + if (s == NULL) return NULL; + + ptr = s+1; + + // update pointers + (*s->prevn) = ptr; + if (s->next) + *stb__prevn(s->next) = &s->next; + + if (s->child) + *stb__prevn(s->child) = &s->child; + + return ptr; + } else { + stb__nochildren *s = (stb__nochildren *) ptr - 1; + + s = (stb__nochildren *) realloc(ptr, newsize + sizeof(s)); + if (s == NULL) return NULL; + + // update pointers + (*s->prevn) = s+1; + if (s->next) + *stb__prevn(s->next) = &s->next; + + return s+1; + } +} + +void *stb_realloc_c(void *context, void *ptr, size_t newsize) +{ + if (ptr == NULL) return stb_malloc(context, newsize); + if (newsize == 0) { stb_free(ptr); return NULL; } + // @TODO: verify you haven't changed contexts + return stb_realloc(ptr, newsize); +} + +void stb_reassign(void *new_context, void *ptr) +{ + stb__alloc *src = stb__get_context(new_context); + + stb__alloc_type t = stb__identify(ptr); + assert(t == STB__alloc || t == STB__nochildren); + + if (t == STB__alloc) { + stb__alloc *s = (stb__alloc *) ptr - 1; + + // unlink from old + *(s->prevn) = s->next; + if (s->next) + *stb__prevn(s->next) = s->prevn; + + stb__insert_alloc(src, s); + } else { + stb__nochildren *s = (stb__nochildren *) ptr - 1; + + // unlink from old + *(s->prevn) = s->next; + if (s->next) + *stb__prevn(s->next) = s->prevn; + + stb__insert_nochild(src, s); + } +} + +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// stb_arr +// +// An stb_arr is directly useable as a pointer (use the actual type in your +// definition), but when it resizes, it returns a new pointer and you can't +// use the old one, so you have to be careful to copy-in-out as necessary. +// +// Use a NULL pointer as a 0-length array. +// +// float *my_array = NULL, *temp; +// +// // add elements on the end one at a time +// stb_arr_push(my_array, 0.0f); +// stb_arr_push(my_array, 1.0f); +// stb_arr_push(my_array, 2.0f); +// +// assert(my_array[1] == 2.0f); +// +// // add an uninitialized element at the end, then assign it +// *stb_arr_add(my_array) = 3.0f; +// +// // add three uninitialized elements at the end +// temp = stb_arr_addn(my_array,3); +// temp[0] = 4.0f; +// temp[1] = 5.0f; +// temp[2] = 6.0f; +// +// assert(my_array[5] == 5.0f); +// +// // remove the last one +// stb_arr_pop(my_array); +// +// assert(stb_arr_len(my_array) == 6); + + +#ifdef STB_MALLOC_WRAPPER + #define STB__PARAMS , char *file, int line + #define STB__ARGS , file, line +#else + #define STB__PARAMS + #define STB__ARGS +#endif + +// calling this function allocates an empty stb_arr attached to p +// (whereas NULL isn't attached to anything) +STB_EXTERN void stb_arr_malloc(void **target, void *context); + +// call this function with a non-NULL value to have all successive +// stbs that are created be attached to the associated parent. Note +// that once a given stb_arr is non-empty, it stays attached to its +// current parent, even if you call this function again. +// it turns the previous value, so you can restore it +STB_EXTERN void* stb_arr_malloc_parent(void *p); + +// simple functions written on top of other functions +#define stb_arr_empty(a) ( stb_arr_len(a) == 0 ) +#define stb_arr_add(a) ( stb_arr_addn((a),1) ) +#define stb_arr_push(a,v) ( *stb_arr_add(a)=(v) ) + +typedef struct +{ + int len, limit; + int stb_malloc; + unsigned int signature; +} stb__arr; + +#define stb_arr_signature 0x51bada7b // ends with 0123 in decimal + +// access the header block stored before the data +#define stb_arrhead(a) /*lint --e(826)*/ (((stb__arr *) (a)) - 1) +#define stb_arrhead2(a) /*lint --e(826)*/ (((stb__arr *) (a)) - 1) + +#ifdef STB_DEBUG +#define stb_arr_check(a) assert(!a || stb_arrhead(a)->signature == stb_arr_signature) +#define stb_arr_check2(a) assert(!a || stb_arrhead2(a)->signature == stb_arr_signature) +#else +#define stb_arr_check(a) ((void) 0) +#define stb_arr_check2(a) ((void) 0) +#endif + +// ARRAY LENGTH + +// get the array length; special case if pointer is NULL +#define stb_arr_len(a) (a ? stb_arrhead(a)->len : 0) +#define stb_arr_len2(a) ((stb__arr *) (a) ? stb_arrhead2(a)->len : 0) +#define stb_arr_lastn(a) (stb_arr_len(a)-1) + +// check whether a given index is valid -- tests 0 <= i < stb_arr_len(a) +#define stb_arr_valid(a,i) (a ? (int) (i) < stb_arrhead(a)->len : 0) + +// change the array length so is is exactly N entries long, creating +// uninitialized entries as needed +#define stb_arr_setlen(a,n) \ + (stb__arr_setlen((void **) &(a), sizeof(a[0]), (n))) + +// change the array length so that N is a valid index (that is, so +// it is at least N entries long), creating uninitialized entries as needed +#define stb_arr_makevalid(a,n) \ + (stb_arr_len(a) < (n)+1 ? stb_arr_setlen(a,(n)+1),(a) : (a)) + +// remove the last element of the array, returning it +#define stb_arr_pop(a) ((stb_arr_check(a), (a))[--stb_arrhead(a)->len]) + +// access the last element in the array +#define stb_arr_last(a) ((stb_arr_check(a), (a))[stb_arr_len(a)-1]) + +// is iterator at end of list? +#define stb_arr_end(a,i) ((i) >= &(a)[stb_arr_len(a)]) + +// (internal) change the allocated length of the array +#define stb_arr__grow(a,n) (stb_arr_check(a), stb_arrhead(a)->len += (n)) + +// add N new uninitialized elements to the end of the array +#define stb_arr__addn(a,n) /*lint --e(826)*/ \ + ((stb_arr_len(a)+(n) > stb_arrcurmax(a)) \ + ? (stb__arr_addlen((void **) &(a),sizeof(*a),(n)),0) \ + : ((stb_arr__grow(a,n), 0))) + +// add N new uninitialized elements to the end of the array, and return +// a pointer to the first new one +#define stb_arr_addn(a,n) (stb_arr__addn((a),n),(a)+stb_arr_len(a)-(n)) + +// add N new uninitialized elements starting at index 'i' +#define stb_arr_insertn(a,i,n) (stb__arr_insertn((void **) &(a), sizeof(*a), (i), (n))) + +// insert an element at i +#define stb_arr_insert(a,i,v) (stb__arr_insertn((void **) &(a), sizeof(*a), (i), (1)), ((a)[i] = v)) + +// delete N elements from the middle starting at index 'i' +#define stb_arr_deleten(a,i,n) (stb__arr_deleten((void **) &(a), sizeof(*a), (i), (n))) + +// delete the i'th element +#define stb_arr_delete(a,i) stb_arr_deleten(a,i,1) + +// delete the i'th element, swapping down from the end +#define stb_arr_fastdelete(a,i) \ + (stb_swap(&a[i], &a[stb_arrhead(a)->len-1], sizeof(*a)), stb_arr_pop(a)) + + +// ARRAY STORAGE + +// get the array maximum storage; special case if NULL +#define stb_arrcurmax(a) (a ? stb_arrhead(a)->limit : 0) +#define stb_arrcurmax2(a) (a ? stb_arrhead2(a)->limit : 0) + +// set the maxlength of the array to n in anticipation of further growth +#define stb_arr_setsize(a,n) (stb_arr_check(a), stb__arr_setsize((void **) &(a),sizeof((a)[0]),n)) + +// make sure maxlength is large enough for at least N new allocations +#define stb_arr_atleast(a,n) (stb_arr_len(a)+(n) > stb_arrcurmax(a) \ + ? stb_arr_setsize((a), (n)) : 0) + +// make a copy of a given array (copies contents via 'memcpy'!) +#define stb_arr_copy(a) stb__arr_copy(a, sizeof((a)[0])) + +// compute the storage needed to store all the elements of the array +#define stb_arr_storage(a) (stb_arr_len(a) * sizeof((a)[0])) + +#define stb_arr_for(v,arr) for((v)=(arr); (v) < (arr)+stb_arr_len(arr); ++(v)) + +// IMPLEMENTATION + +STB_EXTERN void stb_arr_free_(void **p); +STB_EXTERN void *stb__arr_copy_(void *p, int elem_size); +STB_EXTERN void stb__arr_setsize_(void **p, int size, int limit STB__PARAMS); +STB_EXTERN void stb__arr_setlen_(void **p, int size, int newlen STB__PARAMS); +STB_EXTERN void stb__arr_addlen_(void **p, int size, int addlen STB__PARAMS); +STB_EXTERN void stb__arr_deleten_(void **p, int size, int loc, int n STB__PARAMS); +STB_EXTERN void stb__arr_insertn_(void **p, int size, int loc, int n STB__PARAMS); + +#define stb_arr_free(p) stb_arr_free_((void **) &(p)) +#define stb__arr_copy stb__arr_copy_ + +#ifndef STB_MALLOC_WRAPPER + #define stb__arr_setsize stb__arr_setsize_ + #define stb__arr_setlen stb__arr_setlen_ + #define stb__arr_addlen stb__arr_addlen_ + #define stb__arr_deleten stb__arr_deleten_ + #define stb__arr_insertn stb__arr_insertn_ +#else + #define stb__arr_addlen(p,s,n) stb__arr_addlen_(p,s,n,__FILE__,__LINE__) + #define stb__arr_setlen(p,s,n) stb__arr_setlen_(p,s,n,__FILE__,__LINE__) + #define stb__arr_setsize(p,s,n) stb__arr_setsize_(p,s,n,__FILE__,__LINE__) + #define stb__arr_deleten(p,s,i,n) stb__arr_deleten_(p,s,i,n,__FILE__,__LINE__) + #define stb__arr_insertn(p,s,i,n) stb__arr_insertn_(p,s,i,n,__FILE__,__LINE__) +#endif + +#ifdef STB_DEFINE +static void *stb__arr_context; + +void *stb_arr_malloc_parent(void *p) +{ + void *q = stb__arr_context; + stb__arr_context = p; + return q; +} + +void stb_arr_malloc(void **target, void *context) +{ + stb__arr *q = (stb__arr *) stb_malloc(context, sizeof(*q)); + q->len = q->limit = 0; + q->stb_malloc = 1; + q->signature = stb_arr_signature; + *target = (void *) (q+1); +} + +static void * stb__arr_malloc(int size) +{ + if (stb__arr_context) + return stb_malloc(stb__arr_context, size); + return malloc(size); +} + +void * stb__arr_copy_(void *p, int elem_size) +{ + stb__arr *q; + if (p == NULL) return p; + q = (stb__arr *) stb__arr_malloc(sizeof(*q) + elem_size * stb_arrhead2(p)->limit); + stb_arr_check2(p); + memcpy(q, stb_arrhead2(p), sizeof(*q) + elem_size * stb_arrhead2(p)->len); + q->stb_malloc = !!stb__arr_context; + return q+1; +} + +void stb_arr_free_(void **pp) +{ + void *p = *pp; + stb_arr_check2(p); + if (p) { + stb__arr *q = stb_arrhead2(p); + if (q->stb_malloc) + stb_free(q); + else + free(q); + } + *pp = NULL; +} + +static void stb__arrsize_(void **pp, int size, int limit, int len STB__PARAMS) +{ + void *p = *pp; + stb__arr *a; + stb_arr_check2(p); + if (p == NULL) { + if (len == 0 && size == 0) return; + a = (stb__arr *) stb__arr_malloc(sizeof(*a) + size*limit); + a->limit = limit; + a->len = len; + a->stb_malloc = !!stb__arr_context; + a->signature = stb_arr_signature; + } else { + a = stb_arrhead2(p); + a->len = len; + if (a->limit < limit) { + void *p; + if (a->limit >= 4 && limit < a->limit * 2) + limit = a->limit * 2; + if (a->stb_malloc) + p = stb_realloc(a, sizeof(*a) + limit*size); + else + #ifdef STB_MALLOC_WRAPPER + p = stb__realloc(a, sizeof(*a) + limit*size, file, line); + #else + p = realloc(a, sizeof(*a) + limit*size); + #endif + if (p) { + a = (stb__arr *) p; + a->limit = limit; + } else { + // throw an error! + } + } + } + a->len = stb_min(a->len, a->limit); + *pp = a+1; +} + +void stb__arr_setsize_(void **pp, int size, int limit STB__PARAMS) +{ + void *p = *pp; + stb_arr_check2(p); + stb__arrsize_(pp, size, limit, stb_arr_len2(p) STB__ARGS); +} + +void stb__arr_setlen_(void **pp, int size, int newlen STB__PARAMS) +{ + void *p = *pp; + stb_arr_check2(p); + if (stb_arrcurmax2(p) < newlen || p == NULL) { + stb__arrsize_(pp, size, newlen, newlen STB__ARGS); + } else { + stb_arrhead2(p)->len = newlen; + } +} + +void stb__arr_addlen_(void **p, int size, int addlen STB__PARAMS) +{ + stb__arr_setlen_(p, size, stb_arr_len2(*p) + addlen STB__ARGS); +} + +void stb__arr_insertn_(void **pp, int size, int i, int n STB__PARAMS) +{ + void *p = *pp; + if (n) { + int z; + + if (p == NULL) { + stb__arr_addlen_(pp, size, n STB__ARGS); + return; + } + + z = stb_arr_len2(p); + stb__arr_addlen_(&p, size, n STB__ARGS); + memmove((char *) p + (i+n)*size, (char *) p + i*size, size * (z-i)); + } + *pp = p; +} + +void stb__arr_deleten_(void **pp, int size, int i, int n STB__PARAMS) +{ + void *p = *pp; + if (n) { + memmove((char *) p + i*size, (char *) p + (i+n)*size, size * (stb_arr_len2(p)-(i+n))); + stb_arrhead2(p)->len -= n; + } + *pp = p; +} + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Hashing +// +// typical use for this is to make a power-of-two hash table. +// +// let N = size of table (2^n) +// let H = stb_hash(str) +// let S = stb_rehash(H) | 1 +// +// then hash probe sequence P(i) for i=0..N-1 +// P(i) = (H + S*i) & (N-1) +// +// the idea is that H has 32 bits of hash information, but the +// table has only, say, 2^20 entries so only uses 20 of the bits. +// then by rehashing the original H we get 2^12 different probe +// sequences for a given initial probe location. (So it's optimal +// for 64K tables and its optimality decreases past that.) +// +// ok, so I've added something that generates _two separate_ +// 32-bit hashes simultaneously which should scale better to +// very large tables. + + +STB_EXTERN unsigned int stb_hash(char *str); +STB_EXTERN unsigned int stb_hashptr(void *p); +STB_EXTERN unsigned int stb_hashlen(char *str, int len); +STB_EXTERN unsigned int stb_rehash_improved(unsigned int v); +STB_EXTERN unsigned int stb_hash_fast(void *p, int len); +STB_EXTERN unsigned int stb_hash2(char *str, unsigned int *hash2_ptr); +STB_EXTERN unsigned int stb_hash_number(unsigned int hash); + +#define stb_rehash(x) ((x) + ((x) >> 6) + ((x) >> 19)) + +#ifdef STB_DEFINE +unsigned int stb_hash(char *str) +{ + unsigned int hash = 0; + while (*str) + hash = (hash << 7) + (hash >> 25) + *str++; + return hash + (hash >> 16); +} + +unsigned int stb_hashlen(char *str, int len) +{ + unsigned int hash = 0; + while (len-- > 0 && *str) + hash = (hash << 7) + (hash >> 25) + *str++; + return hash + (hash >> 16); +} + +unsigned int stb_hashptr(void *p) +{ + unsigned int x = (unsigned int)(size_t) p; + + // typically lacking in low bits and high bits + x = stb_rehash(x); + x += x << 16; + + // pearson's shuffle + x ^= x << 3; + x += x >> 5; + x ^= x << 2; + x += x >> 15; + x ^= x << 10; + return stb_rehash(x); +} + +unsigned int stb_rehash_improved(unsigned int v) +{ + return stb_hashptr((void *)(size_t) v); +} + +unsigned int stb_hash2(char *str, unsigned int *hash2_ptr) +{ + unsigned int hash1 = 0x3141592c; + unsigned int hash2 = 0x77f044ed; + while (*str) { + hash1 = (hash1 << 7) + (hash1 >> 25) + *str; + hash2 = (hash2 << 11) + (hash2 >> 21) + *str; + ++str; + } + *hash2_ptr = hash2 + (hash1 >> 16); + return hash1 + (hash2 >> 16); +} + +// Paul Hsieh hash +#define stb__get16(p) ((p)[0] | ((p)[1] << 8)) + +unsigned int stb_hash_fast(void *p, int len) +{ + unsigned char *q = (unsigned char *) p; + unsigned int hash = len; + + if (len <= 0 || q == NULL) return 0; + + /* Main loop */ + for (;len > 3; len -= 4) { + unsigned int val; + hash += stb__get16(q); + val = (stb__get16(q+2) << 11); + hash = (hash << 16) ^ hash ^ val; + q += 4; + hash += hash >> 11; + } + + /* Handle end cases */ + switch (len) { + case 3: hash += stb__get16(q); + hash ^= hash << 16; + hash ^= q[2] << 18; + hash += hash >> 11; + break; + case 2: hash += stb__get16(q); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: hash += q[0]; + hash ^= hash << 10; + hash += hash >> 1; + break; + case 0: break; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; +} + +unsigned int stb_hash_number(unsigned int hash) +{ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#endif + +#ifdef STB_PERFECT_HASH +////////////////////////////////////////////////////////////////////////////// +// +// Perfect hashing for ints/pointers +// +// This is mainly useful for making faster pointer-indexed tables +// that don't change frequently. E.g. for stb_ischar(). +// + +typedef struct +{ + stb_uint32 addend; + stb_uint multiplicand; + stb_uint b_mask; + stb_uint8 small_bmap[16]; + stb_uint16 *large_bmap; + + stb_uint table_mask; + stb_uint32 *table; +} stb_perfect; + +STB_EXTERN int stb_perfect_create(stb_perfect *,unsigned int*,int n); +STB_EXTERN void stb_perfect_destroy(stb_perfect *); +STB_EXTERN int stb_perfect_hash(stb_perfect *, unsigned int x); +extern int stb_perfect_hash_max_failures; + +#ifdef STB_DEFINE + +int stb_perfect_hash_max_failures; + +int stb_perfect_hash(stb_perfect *p, unsigned int x) +{ + stb_uint m = x * p->multiplicand; + stb_uint y = x >> 16; + stb_uint bv = (m >> 24) + y; + stb_uint av = (m + y) >> 12; + if (p->table == NULL) return -1; // uninitialized table fails + bv &= p->b_mask; + av &= p->table_mask; + if (p->large_bmap) + av ^= p->large_bmap[bv]; + else + av ^= p->small_bmap[bv]; + return p->table[av] == x ? av : -1; +} + +static void stb__perfect_prehash(stb_perfect *p, stb_uint x, stb_uint16 *a, stb_uint16 *b) +{ + stb_uint m = x * p->multiplicand; + stb_uint y = x >> 16; + stb_uint bv = (m >> 24) + y; + stb_uint av = (m + y) >> 12; + bv &= p->b_mask; + av &= p->table_mask; + *b = bv; + *a = av; +} + +static unsigned long stb__perfect_rand(void) +{ + static unsigned long stb__rand; + stb__rand = stb__rand * 2147001325 + 715136305; + return 0x31415926 ^ ((stb__rand >> 16) + (stb__rand << 16)); +} + +typedef struct { + unsigned short count; + unsigned short b; + unsigned short map; + unsigned short *entries; +} stb__slot; + +static int stb__slot_compare(const void *p, const void *q) +{ + stb__slot *a = (stb__slot *) p; + stb__slot *b = (stb__slot *) q; + return a->count > b->count ? -1 : a->count < b->count; // sort large to small +} + +int stb_perfect_create(stb_perfect *p, unsigned int *v, int n) +{ + unsigned int buffer1[64], buffer2[64], buffer3[64], buffer4[64], buffer5[32]; + unsigned short *as = (unsigned short *) stb_temp(buffer1, sizeof(*v)*n); + unsigned short *bs = (unsigned short *) stb_temp(buffer2, sizeof(*v)*n); + unsigned short *entries = (unsigned short *) stb_temp(buffer4, sizeof(*entries) * n); + int size = 1 << stb_log2_ceil(n), bsize=8; + int failure = 0,i,j,k; + + assert(n <= 32768); + p->large_bmap = NULL; + + for(;;) { + stb__slot *bcount = (stb__slot *) stb_temp(buffer3, sizeof(*bcount) * bsize); + unsigned short *bloc = (unsigned short *) stb_temp(buffer5, sizeof(*bloc) * bsize); + unsigned short *e; + int bad=0; + + p->addend = stb__perfect_rand(); + p->multiplicand = stb__perfect_rand() | 1; + p->table_mask = size-1; + p->b_mask = bsize-1; + p->table = (stb_uint32 *) malloc(size * sizeof(*p->table)); + + for (i=0; i < bsize; ++i) { + bcount[i].b = i; + bcount[i].count = 0; + bcount[i].map = 0; + } + for (i=0; i < n; ++i) { + stb__perfect_prehash(p, v[i], as+i, bs+i); + ++bcount[bs[i]].count; + } + qsort(bcount, bsize, sizeof(*bcount), stb__slot_compare); + e = entries; // now setup up their entries index + for (i=0; i < bsize; ++i) { + bcount[i].entries = e; + e += bcount[i].count; + bcount[i].count = 0; + bloc[bcount[i].b] = i; + } + // now fill them out + for (i=0; i < n; ++i) { + int b = bs[i]; + int w = bloc[b]; + bcount[w].entries[bcount[w].count++] = i; + } + stb_tempfree(buffer5,bloc); + // verify + for (i=0; i < bsize; ++i) + for (j=0; j < bcount[i].count; ++j) + assert(bs[bcount[i].entries[j]] == bcount[i].b); + memset(p->table, 0, size*sizeof(*p->table)); + + // check if any b has duplicate a + for (i=0; i < bsize; ++i) { + if (bcount[i].count > 1) { + for (j=0; j < bcount[i].count; ++j) { + if (p->table[as[bcount[i].entries[j]]]) + bad = 1; + p->table[as[bcount[i].entries[j]]] = 1; + } + for (j=0; j < bcount[i].count; ++j) { + p->table[as[bcount[i].entries[j]]] = 0; + } + if (bad) break; + } + } + + if (!bad) { + // go through the bs and populate the table, first fit + for (i=0; i < bsize; ++i) { + if (bcount[i].count) { + // go through the candidate table[b] values + for (j=0; j < size; ++j) { + // go through the a values and see if they fit + for (k=0; k < bcount[i].count; ++k) { + int a = as[bcount[i].entries[k]]; + if (p->table[(a^j)&p->table_mask]) { + break; // fails + } + } + // if succeeded, accept + if (k == bcount[i].count) { + bcount[i].map = j; + for (k=0; k < bcount[i].count; ++k) { + int a = as[bcount[i].entries[k]]; + p->table[(a^j)&p->table_mask] = 1; + } + break; + } + } + if (j == size) + break; // no match for i'th entry, so break out in failure + } + } + if (i == bsize) { + // success... fill out map + if (bsize <= 16 && size <= 256) { + p->large_bmap = NULL; + for (i=0; i < bsize; ++i) + p->small_bmap[bcount[i].b] = (stb_uint8) bcount[i].map; + } else { + p->large_bmap = (unsigned short *) malloc(sizeof(*p->large_bmap) * bsize); + for (i=0; i < bsize; ++i) + p->large_bmap[bcount[i].b] = bcount[i].map; + } + + // initialize table to v[0], so empty slots will fail + for (i=0; i < size; ++i) + p->table[i] = v[0]; + + for (i=0; i < n; ++i) + if (p->large_bmap) + p->table[as[i] ^ p->large_bmap[bs[i]]] = v[i]; + else + p->table[as[i] ^ p->small_bmap[bs[i]]] = v[i]; + + // and now validate that none of them collided + for (i=0; i < n; ++i) + assert(stb_perfect_hash(p, v[i]) >= 0); + + stb_tempfree(buffer3, bcount); + break; + } + } + free(p->table); + p->table = NULL; + stb_tempfree(buffer3, bcount); + + ++failure; + if (failure >= 4 && bsize < size) bsize *= 2; + if (failure >= 8 && (failure & 3) == 0 && size < 4*n) { + size *= 2; + bsize *= 2; + } + if (failure == 6) { + // make sure the input data is unique, so we don't infinite loop + unsigned int *data = (unsigned int *) stb_temp(buffer3, n * sizeof(*data)); + memcpy(data, v, sizeof(*data) * n); + qsort(data, n, sizeof(*data), stb_intcmp(0)); + for (i=1; i < n; ++i) { + if (data[i] == data[i-1]) + size = 0; // size is return value, so 0 it + } + stb_tempfree(buffer3, data); + if (!size) break; + } + } + + if (failure > stb_perfect_hash_max_failures) + stb_perfect_hash_max_failures = failure; + + stb_tempfree(buffer1, as); + stb_tempfree(buffer2, bs); + stb_tempfree(buffer4, entries); + + return size; +} + +void stb_perfect_destroy(stb_perfect *p) +{ + if (p->large_bmap) free(p->large_bmap); + if (p->table ) free(p->table); + p->large_bmap = NULL; + p->table = NULL; + p->b_mask = 0; + p->table_mask = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Perfect hash clients + +STB_EXTERN int stb_ischar(char s, char *set); + +#ifdef STB_DEFINE + +int stb_ischar(char c, char *set) +{ + static unsigned char bit[8] = { 1,2,4,8,16,32,64,128 }; + static stb_perfect p; + static unsigned char (*tables)[256]; + static char ** sets = NULL; + + int z = stb_perfect_hash(&p, (int)(size_t) set); + if (z < 0) { + int i,k,n,j,f; + // special code that means free all existing data + if (set == NULL) { + stb_arr_free(sets); + free(tables); + tables = NULL; + stb_perfect_destroy(&p); + return 0; + } + stb_arr_push(sets, set); + stb_perfect_destroy(&p); + n = stb_perfect_create(&p, (unsigned int *) (char **) sets, stb_arr_len(sets)); + assert(n != 0); + k = (n+7) >> 3; + tables = (unsigned char (*)[256]) realloc(tables, sizeof(*tables) * k); + memset(tables, 0, sizeof(*tables) * k); + for (i=0; i < stb_arr_len(sets); ++i) { + k = stb_perfect_hash(&p, (int)(size_t) sets[i]); + assert(k >= 0); + n = k >> 3; + f = bit[k&7]; + for (j=0; !j || sets[i][j]; ++j) { + tables[n][(unsigned char) sets[i][j]] |= f; + } + } + z = stb_perfect_hash(&p, (int)(size_t) set); + } + return tables[z >> 3][(unsigned char) c] & bit[z & 7]; +} + +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Instantiated data structures +// +// This is an attempt to implement a templated data structure. +// +// Hash table: call stb_define_hash(TYPE,N,KEY,K1,K2,HASH,VALUE) +// TYPE -- will define a structure type containing the hash table +// N -- the name, will prefix functions named: +// N create +// N destroy +// N get +// N set, N add, N update, +// N remove +// KEY -- the type of the key. 'x == y' must be valid +// K1,K2 -- keys never used by the app, used as flags in the hashtable +// HASH -- a piece of code ending with 'return' that hashes key 'k' +// VALUE -- the type of the value. 'x = y' must be valid +// +// Note that stb_define_hash_base can be used to define more sophisticated +// hash tables, e.g. those that make copies of the key or use special +// comparisons (e.g. strcmp). + +#define STB_(prefix,name) stb__##prefix##name +#define STB__(prefix,name) prefix##name +#define STB__use(x) x +#define STB__skip(x) + +#define stb_declare_hash(PREFIX,TYPE,N,KEY,VALUE) \ + typedef struct stb__st_##TYPE TYPE;\ + PREFIX int STB__(N, init)(TYPE *h, int count);\ + PREFIX int STB__(N, memory_usage)(TYPE *h);\ + PREFIX TYPE * STB__(N, create)(void);\ + PREFIX TYPE * STB__(N, copy)(TYPE *h);\ + PREFIX void STB__(N, destroy)(TYPE *h);\ + PREFIX int STB__(N,get_flag)(TYPE *a, KEY k, VALUE *v);\ + PREFIX VALUE STB__(N,get)(TYPE *a, KEY k);\ + PREFIX int STB__(N, set)(TYPE *a, KEY k, VALUE v);\ + PREFIX int STB__(N, add)(TYPE *a, KEY k, VALUE v);\ + PREFIX int STB__(N, update)(TYPE*a,KEY k,VALUE v);\ + PREFIX int STB__(N, remove)(TYPE *a, KEY k, VALUE *v); + +#define STB_nocopy(x) (x) +#define STB_nodelete(x) 0 +#define STB_nofields +#define STB_nonullvalue(x) +#define STB_nullvalue(x) x +#define STB_safecompare(x) x +#define STB_nosafe(x) +#define STB_noprefix + +#ifdef __GNUC__ +#define STB__nogcc(x) +#else +#define STB__nogcc(x) x +#endif + +#define stb_define_hash_base(PREFIX,TYPE,FIELDS,N,NC,LOAD_FACTOR, \ + KEY,EMPTY,DEL,COPY,DISPOSE,SAFE, \ + VCOMPARE,CCOMPARE,HASH, \ + VALUE,HASVNULL,VNULL) \ + \ +typedef struct \ +{ \ + KEY k; \ + VALUE v; \ +} STB_(N,_hashpair); \ + \ +STB__nogcc( typedef struct stb__st_##TYPE TYPE; ) \ +struct stb__st_##TYPE { \ + FIELDS \ + STB_(N,_hashpair) *table; \ + unsigned int mask; \ + int count, limit; \ + int deleted; \ + \ + int delete_threshhold; \ + int grow_threshhold; \ + int shrink_threshhold; \ + unsigned char alloced, has_empty, has_del; \ + VALUE ev; VALUE dv; \ +}; \ + \ +static unsigned int STB_(N, hash)(KEY k) \ +{ \ + HASH \ +} \ + \ +PREFIX int STB__(N, init)(TYPE *h, int count) \ +{ \ + int i; \ + if (count < 4) count = 4; \ + h->limit = count; \ + h->count = 0; \ + h->mask = count-1; \ + h->deleted = 0; \ + h->grow_threshhold = (int) (count * LOAD_FACTOR); \ + h->has_empty = h->has_del = 0; \ + h->alloced = 0; \ + if (count <= 64) \ + h->shrink_threshhold = 0; \ + else \ + h->shrink_threshhold = (int) (count * (LOAD_FACTOR/2.25)); \ + h->delete_threshhold = (int) (count * (1-LOAD_FACTOR)/2); \ + h->table = (STB_(N,_hashpair)*) malloc(sizeof(h->table[0]) * count); \ + if (h->table == NULL) return 0; \ + /* ideally this gets turned into a memset32 automatically */ \ + for (i=0; i < count; ++i) \ + h->table[i].k = EMPTY; \ + return 1; \ +} \ + \ +PREFIX int STB__(N, memory_usage)(TYPE *h) \ +{ \ + return sizeof(*h) + h->limit * sizeof(h->table[0]); \ +} \ + \ +PREFIX TYPE * STB__(N, create)(void) \ +{ \ + TYPE *h = (TYPE *) malloc(sizeof(*h)); \ + if (h) { \ + if (STB__(N, init)(h, 16)) \ + h->alloced = 1; \ + else { free(h); h=NULL; } \ + } \ + return h; \ +} \ + \ +PREFIX void STB__(N, destroy)(TYPE *a) \ +{ \ + int i; \ + for (i=0; i < a->limit; ++i) \ + if (!CCOMPARE(a->table[i].k,EMPTY) && !CCOMPARE(a->table[i].k, DEL)) \ + DISPOSE(a->table[i].k); \ + free(a->table); \ + if (a->alloced) \ + free(a); \ +} \ + \ +static void STB_(N, rehash)(TYPE *a, int count); \ + \ +PREFIX int STB__(N,get_flag)(TYPE *a, KEY k, VALUE *v) \ +{ \ + unsigned int h = STB_(N, hash)(k); \ + unsigned int n = h & a->mask, s; \ + if (CCOMPARE(k,EMPTY)){ if (a->has_empty) *v = a->ev; return a->has_empty;}\ + if (CCOMPARE(k,DEL)) { if (a->has_del ) *v = a->dv; return a->has_del; }\ + if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \ + SAFE(if (!CCOMPARE(a->table[n].k,DEL))) \ + if (VCOMPARE(a->table[n].k,k)) { *v = a->table[n].v; return 1; } \ + s = stb_rehash(h) | 1; \ + for(;;) { \ + n = (n + s) & a->mask; \ + if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \ + SAFE(if (CCOMPARE(a->table[n].k,DEL)) continue;) \ + if (VCOMPARE(a->table[n].k,k)) \ + { *v = a->table[n].v; return 1; } \ + } \ +} \ + \ +HASVNULL( \ + PREFIX VALUE STB__(N,get)(TYPE *a, KEY k) \ + { \ + VALUE v; \ + if (STB__(N,get_flag)(a,k,&v)) return v; \ + else return VNULL; \ + } \ +) \ + \ +PREFIX int STB__(N,getkey)(TYPE *a, KEY k, KEY *kout) \ +{ \ + unsigned int h = STB_(N, hash)(k); \ + unsigned int n = h & a->mask, s; \ + if (CCOMPARE(k,EMPTY)||CCOMPARE(k,DEL)) return 0; \ + if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \ + SAFE(if (!CCOMPARE(a->table[n].k,DEL))) \ + if (VCOMPARE(a->table[n].k,k)) { *kout = a->table[n].k; return 1; } \ + s = stb_rehash(h) | 1; \ + for(;;) { \ + n = (n + s) & a->mask; \ + if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \ + SAFE(if (CCOMPARE(a->table[n].k,DEL)) continue;) \ + if (VCOMPARE(a->table[n].k,k)) \ + { *kout = a->table[n].k; return 1; } \ + } \ +} \ + \ +static int STB_(N,addset)(TYPE *a, KEY k, VALUE v, \ + int allow_new, int allow_old, int copy) \ +{ \ + unsigned int h = STB_(N, hash)(k); \ + unsigned int n = h & a->mask; \ + int b = -1; \ + if (CCOMPARE(k,EMPTY)) { \ + if (a->has_empty ? allow_old : allow_new) { \ + n=a->has_empty; a->ev = v; a->has_empty = 1; return !n; \ + } else return 0; \ + } \ + if (CCOMPARE(k,DEL)) { \ + if (a->has_del ? allow_old : allow_new) { \ + n=a->has_del; a->dv = v; a->has_del = 1; return !n; \ + } else return 0; \ + } \ + if (!CCOMPARE(a->table[n].k, EMPTY)) { \ + unsigned int s; \ + if (CCOMPARE(a->table[n].k, DEL)) \ + b = n; \ + else if (VCOMPARE(a->table[n].k,k)) { \ + if (allow_old) \ + a->table[n].v = v; \ + return !allow_new; \ + } \ + s = stb_rehash(h) | 1; \ + for(;;) { \ + n = (n + s) & a->mask; \ + if (CCOMPARE(a->table[n].k, EMPTY)) break; \ + if (CCOMPARE(a->table[n].k, DEL)) { \ + if (b < 0) b = n; \ + } else if (VCOMPARE(a->table[n].k,k)) { \ + if (allow_old) \ + a->table[n].v = v; \ + return !allow_new; \ + } \ + } \ + } \ + if (!allow_new) return 0; \ + if (b < 0) b = n; else --a->deleted; \ + a->table[b].k = copy ? COPY(k) : k; \ + a->table[b].v = v; \ + ++a->count; \ + if (a->count > a->grow_threshhold) \ + STB_(N,rehash)(a, a->limit*2); \ + return 1; \ +} \ + \ +PREFIX int STB__(N, set)(TYPE *a, KEY k, VALUE v){return STB_(N,addset)(a,k,v,1,1,1);}\ +PREFIX int STB__(N, add)(TYPE *a, KEY k, VALUE v){return STB_(N,addset)(a,k,v,1,0,1);}\ +PREFIX int STB__(N, update)(TYPE*a,KEY k,VALUE v){return STB_(N,addset)(a,k,v,0,1,1);}\ + \ +PREFIX int STB__(N, remove)(TYPE *a, KEY k, VALUE *v) \ +{ \ + unsigned int h = STB_(N, hash)(k); \ + unsigned int n = h & a->mask, s; \ + if (CCOMPARE(k,EMPTY)) { if (a->has_empty) { if(v)*v = a->ev; a->has_empty=0; return 1; } return 0; } \ + if (CCOMPARE(k,DEL)) { if (a->has_del ) { if(v)*v = a->dv; a->has_del =0; return 1; } return 0; } \ + if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \ + if (SAFE(CCOMPARE(a->table[n].k,DEL) || ) !VCOMPARE(a->table[n].k,k)) { \ + s = stb_rehash(h) | 1; \ + for(;;) { \ + n = (n + s) & a->mask; \ + if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \ + SAFE(if (CCOMPARE(a->table[n].k, DEL)) continue;) \ + if (VCOMPARE(a->table[n].k,k)) break; \ + } \ + } \ + DISPOSE(a->table[n].k); \ + a->table[n].k = DEL; \ + --a->count; \ + ++a->deleted; \ + if (v != NULL) \ + *v = a->table[n].v; \ + if (a->count < a->shrink_threshhold) \ + STB_(N, rehash)(a, a->limit >> 1); \ + else if (a->deleted > a->delete_threshhold) \ + STB_(N, rehash)(a, a->limit); \ + return 1; \ +} \ + \ +PREFIX TYPE * STB__(NC, copy)(TYPE *a) \ +{ \ + int i; \ + TYPE *h = (TYPE *) malloc(sizeof(*h)); \ + if (!h) return NULL; \ + if (!STB__(N, init)(h, a->limit)) { free(h); return NULL; } \ + h->count = a->count; \ + h->deleted = a->deleted; \ + h->alloced = 1; \ + h->ev = a->ev; h->dv = a->dv; \ + h->has_empty = a->has_empty; h->has_del = a->has_del; \ + memcpy(h->table, a->table, h->limit * sizeof(h->table[0])); \ + for (i=0; i < a->limit; ++i) \ + if (!CCOMPARE(h->table[i].k,EMPTY) && !CCOMPARE(h->table[i].k,DEL)) \ + h->table[i].k = COPY(h->table[i].k); \ + return h; \ +} \ + \ +static void STB_(N, rehash)(TYPE *a, int count) \ +{ \ + int i; \ + TYPE b; \ + STB__(N, init)(&b, count); \ + for (i=0; i < a->limit; ++i) \ + if (!CCOMPARE(a->table[i].k,EMPTY) && !CCOMPARE(a->table[i].k,DEL)) \ + STB_(N,addset)(&b, a->table[i].k, a->table[i].v,1,1,0); \ + free(a->table); \ + a->table = b.table; \ + a->mask = b.mask; \ + a->count = b.count; \ + a->limit = b.limit; \ + a->deleted = b.deleted; \ + a->delete_threshhold = b.delete_threshhold; \ + a->grow_threshhold = b.grow_threshhold; \ + a->shrink_threshhold = b.shrink_threshhold; \ +} + +#define STB_equal(a,b) ((a) == (b)) + +#define stb_define_hash(TYPE,N,KEY,EMPTY,DEL,HASH,VALUE) \ + stb_define_hash_base(STB_noprefix, TYPE,STB_nofields,N,NC,0.85f, \ + KEY,EMPTY,DEL,STB_nocopy,STB_nodelete,STB_nosafe, \ + STB_equal,STB_equal,HASH, \ + VALUE,STB_nonullvalue,0) + +#define stb_define_hash_vnull(TYPE,N,KEY,EMPTY,DEL,HASH,VALUE,VNULL) \ + stb_define_hash_base(STB_noprefix, TYPE,STB_nofields,N,NC,0.85f, \ + KEY,EMPTY,DEL,STB_nocopy,STB_nodelete,STB_nosafe, \ + STB_equal,STB_equal,HASH, \ + VALUE,STB_nullvalue,VNULL) + +////////////////////////////////////////////////////////////////////////////// +// +// stb_ptrmap +// +// An stb_ptrmap data structure is an O(1) hash table between pointers. One +// application is to let you store "extra" data associated with pointers, +// which is why it was originally called stb_extra. + +stb_declare_hash(STB_EXTERN, stb_ptrmap, stb_ptrmap_, void *, void *) +stb_declare_hash(STB_EXTERN, stb_idict, stb_idict_, stb_int32, stb_int32) +stb_declare_hash(STB_EXTERN, stb_uidict, stbi_uidict_, stb_uint32, stb_uint32) + +STB_EXTERN void stb_ptrmap_delete(stb_ptrmap *e, void (*free_func)(void *)); +STB_EXTERN stb_ptrmap *stb_ptrmap_new(void); + +STB_EXTERN stb_idict * stb_idict_new_size(int size); +STB_EXTERN void stb_idict_remove_all(stb_idict *e); +STB_EXTERN void stb_uidict_reset(stb_uidict *e); + +#ifdef STB_DEFINE + +#define STB_EMPTY ((void *) 2) +#define STB_EDEL ((void *) 6) + +stb_define_hash_base(STB_noprefix,stb_ptrmap, STB_nofields, stb_ptrmap_,stb_ptrmap_,0.85f, + void *,STB_EMPTY,STB_EDEL,STB_nocopy,STB_nodelete,STB_nosafe, + STB_equal,STB_equal,return stb_hashptr(k);, + void *,STB_nullvalue,NULL) + +stb_ptrmap *stb_ptrmap_new(void) +{ + return stb_ptrmap_create(); +} + +void stb_ptrmap_delete(stb_ptrmap *e, void (*free_func)(void *)) +{ + int i; + if (free_func) + for (i=0; i < e->limit; ++i) + if (e->table[i].k != STB_EMPTY && e->table[i].k != STB_EDEL) { + if (free_func == free) + free(e->table[i].v); // allow STB_MALLOC_WRAPPER to operate + else + free_func(e->table[i].v); + } + stb_ptrmap_destroy(e); +} + +// extra fields needed for stua_dict +#define STB_IEMPTY ((int) 1) +#define STB_IDEL ((int) 3) +stb_define_hash_base(STB_noprefix, stb_idict, short type; short gc; STB_nofields, stb_idict_,stb_idict_,0.95f, + stb_int32,STB_IEMPTY,STB_IDEL,STB_nocopy,STB_nodelete,STB_nosafe, + STB_equal,STB_equal, + return stb_rehash_improved(k);,stb_int32,STB_nonullvalue,0) + +stb_idict * stb_idict_new_size(int size) +{ + stb_idict *e = (stb_idict *) malloc(sizeof(*e)); + if (e) { + if (!stb_is_pow2(size)) + size = 1 << stb_log2_ceil(size); + stb_idict_init(e, size); + e->alloced = 1; + } + return e; +} + +void stb_idict_remove_all(stb_idict *e) +{ + int n; + for (n=0; n < e->limit; ++n) + e->table[n].k = STB_IEMPTY; + e->has_empty = e->has_del = 0; + e->count = 0; + e->deleted = 0; +} + +stb_define_hash_base(STB_noprefix, stb_uidict, STB_nofields, stb_uidict_,stb_uidict_,0.85f, + stb_int32,0xffffffff,0xfffffffe,STB_nocopy,STB_nodelete,STB_nosafe, + STB_equal,STB_equal, + return stb_rehash_improved(k);,stb_uint32,STB_nonullvalue,0) + +void stb_uidict_reset(stb_uidict *e) +{ + int n; + for (n=0; n < e->limit; ++n) + e->table[n].k = 0xffffffff; + e->has_empty = e->has_del = 0; + e->count = 0; + e->deleted = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// stb_sparse_ptr_matrix +// +// An stb_ptrmap data structure is an O(1) hash table storing an arbitrary +// block of data for a given pair of pointers. +// +// If create=0, returns + +typedef struct stb__st_stb_spmatrix stb_spmatrix; + +STB_EXTERN stb_spmatrix * stb_sparse_ptr_matrix_new(int val_size); +STB_EXTERN void stb_sparse_ptr_matrix_free(stb_spmatrix *z); +STB_EXTERN void * stb_sparse_ptr_matrix_get(stb_spmatrix *z, void *a, void *b, int create); + +#ifdef STB_DEFINE +typedef struct +{ + void *a; + void *b; +} stb__ptrpair; + +static stb__ptrpair stb__ptrpair_empty = { (void *) 1, (void *) 1 }; +static stb__ptrpair stb__ptrpair_del = { (void *) 2, (void *) 2 }; + +#define STB__equal_ptrpair(x,y) ((x).a == (y).a && (x).b == (y).b) + +stb_define_hash_base(STB_noprefix, stb_spmatrix, int val_size; void *arena;, stb__spmatrix_,stb__spmatrix_, 0.85, + stb__ptrpair, stb__ptrpair_empty, stb__ptrpair_del, + STB_nocopy, STB_nodelete, STB_nosafe, + STB__equal_ptrpair, STB__equal_ptrpair, return stb_rehash(stb_hashptr(k.a))+stb_hashptr(k.b);, + void *, STB_nullvalue, 0) + +stb_spmatrix *stb_sparse_ptr_matrix_new(int val_size) +{ + stb_spmatrix *m = stb__spmatrix_create(); + if (m) m->val_size = val_size; + if (m) m->arena = stb_malloc_global(1); + return m; +} + +void stb_sparse_ptr_matrix_free(stb_spmatrix *z) +{ + if (z->arena) stb_free(z->arena); + stb__spmatrix_destroy(z); +} + +void *stb_sparse_ptr_matrix_get(stb_spmatrix *z, void *a, void *b, int create) +{ + stb__ptrpair t = { a,b }; + void *data = stb__spmatrix_get(z, t); + if (!data && create) { + data = stb_malloc_raw(z->arena, z->val_size); + if (!data) return NULL; + memset(data, 0, z->val_size); + stb__spmatrix_add(z, t, data); + } + return data; +} +#endif + + + +////////////////////////////////////////////////////////////////////////////// +// +// SDICT: Hash Table for Strings (symbol table) +// +// if "use_arena=1", then strings will be copied +// into blocks and never freed until the sdict is freed; +// otherwise they're malloc()ed and free()d on the fly. +// (specify use_arena=1 if you never stb_sdict_remove) + +stb_declare_hash(STB_EXTERN, stb_sdict, stb_sdict_, char *, void *) + +STB_EXTERN stb_sdict * stb_sdict_new(int use_arena); +STB_EXTERN stb_sdict * stb_sdict_copy(stb_sdict*); +STB_EXTERN void stb_sdict_delete(stb_sdict *); +STB_EXTERN void * stb_sdict_change(stb_sdict *, char *str, void *p); +STB_EXTERN int stb_sdict_count(stb_sdict *d); + +STB_EXTERN int stb_sdict_internal_limit(stb_sdict *d); +STB_EXTERN char * stb_sdict_internal_key(stb_sdict *d, int n); +STB_EXTERN void * stb_sdict_internal_value(stb_sdict *d, int n); + +#define stb_sdict_for(d,i,q,z) \ + for(i=0; i < stb_sdict_internal_limit(d) ? (q=stb_sdict_internal_key(d,i),z=stb_sdict_internal_value(d,i),1) : 0; ++i) \ + if (q==NULL||q==(void *) 1);else // reversed makes macro friendly + +#ifdef STB_DEFINE + +// if in same translation unit, for speed, don't call accessors +#undef stb_sdict_for +#define stb_sdict_for(d,i,q,z) \ + for(i=0; i < (d)->limit ? (q=(d)->table[i].k,z=(d)->table[i].v,1) : 0; ++i) \ + if (q==NULL||q==(void *) 1);else // reversed makes macro friendly + +#define STB_DEL ((void *) 1) +#define STB_SDEL ((char *) 1) + +#define stb_sdict__copy(x) \ + stb_p_strcpy_s(a->arena ? stb_malloc_string(a->arena, strlen(x)+1) \ + : (char *) malloc(strlen(x)+1), strlen(x)+1, x) + +#define stb_sdict__dispose(x) if (!a->arena) free(x) + +stb_define_hash_base(STB_noprefix, stb_sdict, void*arena;, stb_sdict_,stb_sdictinternal_, 0.85f, + char *, NULL, STB_SDEL, stb_sdict__copy, stb_sdict__dispose, + STB_safecompare, !strcmp, STB_equal, return stb_hash(k);, + void *, STB_nullvalue, NULL) + +int stb_sdict_count(stb_sdict *a) +{ + return a->count; +} + +int stb_sdict_internal_limit(stb_sdict *a) +{ + return a->limit; +} +char* stb_sdict_internal_key(stb_sdict *a, int n) +{ + return a->table[n].k; +} +void* stb_sdict_internal_value(stb_sdict *a, int n) +{ + return a->table[n].v; +} + +stb_sdict * stb_sdict_new(int use_arena) +{ + stb_sdict *d = stb_sdict_create(); + if (d == NULL) return NULL; + d->arena = use_arena ? stb_malloc_global(1) : NULL; + return d; +} + +stb_sdict* stb_sdict_copy(stb_sdict *old) +{ + stb_sdict *n; + void *old_arena = old->arena; + void *new_arena = old_arena ? stb_malloc_global(1) : NULL; + old->arena = new_arena; + n = stb_sdictinternal_copy(old); + old->arena = old_arena; + if (n) + n->arena = new_arena; + else if (new_arena) + stb_free(new_arena); + return n; +} + + +void stb_sdict_delete(stb_sdict *d) +{ + if (d->arena) + stb_free(d->arena); + stb_sdict_destroy(d); +} + +void * stb_sdict_change(stb_sdict *d, char *str, void *p) +{ + void *q = stb_sdict_get(d, str); + stb_sdict_set(d, str, p); + return q; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Instantiated data structures +// +// This is an attempt to implement a templated data structure. +// What you do is define a struct foo, and then include several +// pointer fields to struct foo in your struct. Then you call +// the instantiator, which creates the functions that implement +// the data structure. This requires massive undebuggable #defines, +// so we limit the cases where we do this. +// +// AA tree is an encoding of a 2-3 tree whereas RB trees encode a 2-3-4 tree; +// much simpler code due to fewer cases. + +#define stb__bst_parent(x) x +#define stb__bst_noparent(x) + +#define stb_bst_fields(N) \ + *STB_(N,left), *STB_(N,right); \ + unsigned char STB_(N,level) + +#define stb_bst_fields_parent(N) \ + *STB_(N,left), *STB_(N,right), *STB_(N,parent); \ + unsigned char STB_(N,level) + +#define STB__level(N,x) ((x) ? (x)->STB_(N,level) : 0) + +#define stb_bst_base(TYPE, N, TREE, M, compare, PAR) \ + \ +static int STB_(N,_compare)(TYPE *p, TYPE *q) \ +{ \ + compare \ +} \ + \ +static void STB_(N,setleft)(TYPE *q, TYPE *v) \ +{ \ + q->STB_(N,left) = v; \ + PAR(if (v) v->STB_(N,parent) = q;) \ +} \ + \ +static void STB_(N,setright)(TYPE *q, TYPE *v) \ +{ \ + q->STB_(N,right) = v; \ + PAR(if (v) v->STB_(N,parent) = q;) \ +} \ + \ +static TYPE *STB_(N,skew)(TYPE *q) \ +{ \ + if (q == NULL) return q; \ + if (q->STB_(N,left) \ + && q->STB_(N,left)->STB_(N,level) == q->STB_(N,level)) { \ + TYPE *p = q->STB_(N,left); \ + STB_(N,setleft)(q, p->STB_(N,right)); \ + STB_(N,setright)(p, q); \ + return p; \ + } \ + return q; \ +} \ + \ +static TYPE *STB_(N,split)(TYPE *p) \ +{ \ + TYPE *q = p->STB_(N,right); \ + if (q && q->STB_(N,right) \ + && q->STB_(N,right)->STB_(N,level) == p->STB_(N,level)) { \ + STB_(N,setright)(p, q->STB_(N,left)); \ + STB_(N,setleft)(q,p); \ + ++q->STB_(N,level); \ + return q; \ + } \ + return p; \ +} \ + \ +TYPE *STB__(N,insert)(TYPE *tree, TYPE *item) \ +{ \ + int c; \ + if (tree == NULL) { \ + item->STB_(N,left) = NULL; \ + item->STB_(N,right) = NULL; \ + item->STB_(N,level) = 1; \ + PAR(item->STB_(N,parent) = NULL;) \ + return item; \ + } \ + c = STB_(N,_compare)(item,tree); \ + if (c == 0) { \ + if (item != tree) { \ + STB_(N,setleft)(item, tree->STB_(N,left)); \ + STB_(N,setright)(item, tree->STB_(N,right)); \ + item->STB_(N,level) = tree->STB_(N,level); \ + PAR(item->STB_(N,parent) = NULL;) \ + } \ + return item; \ + } \ + if (c < 0) \ + STB_(N,setleft )(tree, STB__(N,insert)(tree->STB_(N,left), item)); \ + else \ + STB_(N,setright)(tree, STB__(N,insert)(tree->STB_(N,right), item)); \ + tree = STB_(N,skew)(tree); \ + tree = STB_(N,split)(tree); \ + PAR(tree->STB_(N,parent) = NULL;) \ + return tree; \ +} \ + \ +TYPE *STB__(N,remove)(TYPE *tree, TYPE *item) \ +{ \ + static TYPE *delnode, *leaf, *restore; \ + if (tree == NULL) return NULL; \ + leaf = tree; \ + if (STB_(N,_compare)(item, tree) < 0) { \ + STB_(N,setleft)(tree, STB__(N,remove)(tree->STB_(N,left), item)); \ + } else { \ + TYPE *r; \ + delnode = tree; \ + r = STB__(N,remove)(tree->STB_(N,right), item); \ + /* maybe move 'leaf' up to this location */ \ + if (restore == tree) { tree = leaf; leaf = restore = NULL; } \ + STB_(N,setright)(tree,r); \ + assert(tree->STB_(N,right) != tree); \ + } \ + if (tree == leaf) { \ + if (delnode == item) { \ + tree = tree->STB_(N,right); \ + assert(leaf->STB_(N,left) == NULL); \ + /* move leaf (the right sibling) up to delnode */ \ + STB_(N,setleft )(leaf, item->STB_(N,left )); \ + STB_(N,setright)(leaf, item->STB_(N,right)); \ + leaf->STB_(N,level) = item->STB_(N,level); \ + if (leaf != item) \ + restore = delnode; \ + } \ + delnode = NULL; \ + } else { \ + if (STB__level(N,tree->STB_(N,left) ) < tree->STB_(N,level)-1 || \ + STB__level(N,tree->STB_(N,right)) < tree->STB_(N,level)-1) { \ + --tree->STB_(N,level); \ + if (STB__level(N,tree->STB_(N,right)) > tree->STB_(N,level)) \ + tree->STB_(N,right)->STB_(N,level) = tree->STB_(N,level); \ + tree = STB_(N,skew)(tree); \ + STB_(N,setright)(tree, STB_(N,skew)(tree->STB_(N,right))); \ + if (tree->STB_(N,right)) \ + STB_(N,setright)(tree->STB_(N,right), \ + STB_(N,skew)(tree->STB_(N,right)->STB_(N,right))); \ + tree = STB_(N,split)(tree); \ + if (tree->STB_(N,right)) \ + STB_(N,setright)(tree, STB_(N,split)(tree->STB_(N,right))); \ + } \ + } \ + PAR(if (tree) tree->STB_(N,parent) = NULL;) \ + return tree; \ +} \ + \ +TYPE *STB__(N,last)(TYPE *tree) \ +{ \ + if (tree) \ + while (tree->STB_(N,right)) tree = tree->STB_(N,right); \ + return tree; \ +} \ + \ +TYPE *STB__(N,first)(TYPE *tree) \ +{ \ + if (tree) \ + while (tree->STB_(N,left)) tree = tree->STB_(N,left); \ + return tree; \ +} \ + \ +TYPE *STB__(N,next)(TYPE *tree, TYPE *item) \ +{ \ + TYPE *next = NULL; \ + if (item->STB_(N,right)) \ + return STB__(N,first)(item->STB_(N,right)); \ + PAR( \ + while(item->STB_(N,parent)) { \ + TYPE *up = item->STB_(N,parent); \ + if (up->STB_(N,left) == item) return up; \ + item = up; \ + } \ + return NULL; \ + ) \ + while (tree != item) { \ + if (STB_(N,_compare)(item, tree) < 0) { \ + next = tree; \ + tree = tree->STB_(N,left); \ + } else { \ + tree = tree->STB_(N,right); \ + } \ + } \ + return next; \ +} \ + \ +TYPE *STB__(N,prev)(TYPE *tree, TYPE *item) \ +{ \ + TYPE *next = NULL; \ + if (item->STB_(N,left)) \ + return STB__(N,last)(item->STB_(N,left)); \ + PAR( \ + while(item->STB_(N,parent)) { \ + TYPE *up = item->STB_(N,parent); \ + if (up->STB_(N,right) == item) return up; \ + item = up; \ + } \ + return NULL; \ + ) \ + while (tree != item) { \ + if (STB_(N,_compare)(item, tree) < 0) { \ + tree = tree->STB_(N,left); \ + } else { \ + next = tree; \ + tree = tree->STB_(N,right); \ + } \ + } \ + return next; \ +} \ + \ +STB__DEBUG( \ + void STB__(N,_validate)(TYPE *tree, int root) \ + { \ + if (tree == NULL) return; \ + PAR(if(root) assert(tree->STB_(N,parent) == NULL);) \ + assert(STB__level(N,tree->STB_(N,left) ) == tree->STB_(N,level)-1); \ + assert(STB__level(N,tree->STB_(N,right)) <= tree->STB_(N,level)); \ + assert(STB__level(N,tree->STB_(N,right)) >= tree->STB_(N,level)-1); \ + if (tree->STB_(N,right)) { \ + assert(STB__level(N,tree->STB_(N,right)->STB_(N,right)) \ + != tree->STB_(N,level)); \ + PAR(assert(tree->STB_(N,right)->STB_(N,parent) == tree);) \ + } \ + PAR(if(tree->STB_(N,left)) assert(tree->STB_(N,left)->STB_(N,parent) == tree);) \ + STB__(N,_validate)(tree->STB_(N,left) ,0); \ + STB__(N,_validate)(tree->STB_(N,right),0); \ + } \ +) \ + \ +typedef struct \ +{ \ + TYPE *root; \ +} TREE; \ + \ +void STB__(M,Insert)(TREE *tree, TYPE *item) \ +{ tree->root = STB__(N,insert)(tree->root, item); } \ +void STB__(M,Remove)(TREE *tree, TYPE *item) \ +{ tree->root = STB__(N,remove)(tree->root, item); } \ +TYPE *STB__(M,Next)(TREE *tree, TYPE *item) \ +{ return STB__(N,next)(tree->root, item); } \ +TYPE *STB__(M,Prev)(TREE *tree, TYPE *item) \ +{ return STB__(N,prev)(tree->root, item); } \ +TYPE *STB__(M,First)(TREE *tree) { return STB__(N,first)(tree->root); } \ +TYPE *STB__(M,Last) (TREE *tree) { return STB__(N,last) (tree->root); } \ +void STB__(M,Init)(TREE *tree) { tree->root = NULL; } + + +#define stb_bst_find(N,tree,fcompare) \ +{ \ + int c; \ + while (tree != NULL) { \ + fcompare \ + if (c == 0) return tree; \ + if (c < 0) tree = tree->STB_(N,left); \ + else tree = tree->STB_(N,right); \ + } \ + return NULL; \ +} + +#define stb_bst_raw(TYPE,N,TREE,M,vfield,VTYPE,compare,PAR) \ + stb_bst_base(TYPE,N,TREE,M, \ + VTYPE a = p->vfield; VTYPE b = q->vfield; return (compare);, PAR ) \ + \ +TYPE *STB__(N,find)(TYPE *tree, VTYPE a) \ + stb_bst_find(N,tree,VTYPE b = tree->vfield; c = (compare);) \ +TYPE *STB__(M,Find)(TREE *tree, VTYPE a) \ +{ return STB__(N,find)(tree->root, a); } + +#define stb_bst(TYPE,N,TREE,M,vfield,VTYPE,compare) \ + stb_bst_raw(TYPE,N,TREE,M,vfield,VTYPE,compare,stb__bst_noparent) +#define stb_bst_parent(TYPE,N,TREE,M,vfield,VTYPE,compare) \ + stb_bst_raw(TYPE,N,TREE,M,vfield,VTYPE,compare,stb__bst_parent) + + + +////////////////////////////////////////////////////////////////////////////// +// +// Pointer Nulling +// +// This lets you automatically NULL dangling pointers to "registered" +// objects. Note that you have to make sure you call the appropriate +// functions when you free or realloc blocks of memory that contain +// pointers or pointer targets. stb.h can automatically do this for +// stb_arr, or for all frees/reallocs if it's wrapping them. +// + +#ifdef STB_NPTR + +STB_EXTERN void stb_nptr_set(void *address_of_pointer, void *value_to_write); +STB_EXTERN void stb_nptr_didset(void *address_of_pointer); + +STB_EXTERN void stb_nptr_didfree(void *address_being_freed, int len); +STB_EXTERN void stb_nptr_free(void *address_being_freed, int len); + +STB_EXTERN void stb_nptr_didrealloc(void *new_address, void *old_address, int len); +STB_EXTERN void stb_nptr_recache(void); // recache all known pointers + // do this after pointer sets outside your control, slow + +#ifdef STB_DEFINE +// for fast updating on free/realloc, we need to be able to find +// all the objects (pointers and targets) within a given block; +// this precludes hashing + +// we use a three-level hierarchy of memory to minimize storage: +// level 1: 65536 pointers to stb__memory_node (always uses 256 KB) +// level 2: each stb__memory_node represents a 64K block of memory +// with 256 stb__memory_leafs (worst case 64MB) +// level 3: each stb__memory_leaf represents 256 bytes of memory +// using a list of target locations and a list of pointers +// (which are hopefully fairly short normally!) + +// this approach won't work in 64-bit, which has a much larger address +// space. need to redesign + +#define STB__NPTR_ROOT_LOG2 16 +#define STB__NPTR_ROOT_NUM (1 << STB__NPTR_ROOT_LOG2) +#define STB__NPTR_ROOT_SHIFT (32 - STB__NPTR_ROOT_LOG2) + +#define STB__NPTR_NODE_LOG2 5 +#define STB__NPTR_NODE_NUM (1 << STB__NPTR_NODE_LOG2) +#define STB__NPTR_NODE_MASK (STB__NPTR_NODE_NUM-1) +#define STB__NPTR_NODE_SHIFT (STB__NPTR_ROOT_SHIFT - STB__NPTR_NODE_LOG2) +#define STB__NPTR_NODE_OFFSET(x) (((x) >> STB__NPTR_NODE_SHIFT) & STB__NPTR_NODE_MASK) + +typedef struct stb__st_nptr +{ + void *ptr; // address of actual pointer + struct stb__st_nptr *next; // next pointer with same target + struct stb__st_nptr **prev; // prev pointer with same target, address of 'next' field (or first) + struct stb__st_nptr *next_in_block; +} stb__nptr; + +typedef struct stb__st_nptr_target +{ + void *ptr; // address of target + stb__nptr *first; // address of first nptr pointing to this + struct stb__st_nptr_target *next_in_block; +} stb__nptr_target; + +typedef struct +{ + stb__nptr *pointers; + stb__nptr_target *targets; +} stb__memory_leaf; + +typedef struct +{ + stb__memory_leaf *children[STB__NPTR_NODE_NUM]; +} stb__memory_node; + +stb__memory_node *stb__memtab_root[STB__NPTR_ROOT_NUM]; + +static stb__memory_leaf *stb__nptr_find_leaf(void *mem) +{ + stb_uint32 address = (stb_uint32) mem; + stb__memory_node *z = stb__memtab_root[address >> STB__NPTR_ROOT_SHIFT]; + if (z) + return z->children[STB__NPTR_NODE_OFFSET(address)]; + else + return NULL; +} + +static void * stb__nptr_alloc(int size) +{ + return stb__realloc_raw(0,size); +} + +static void stb__nptr_free(void *p) +{ + stb__realloc_raw(p,0); +} + +static stb__memory_leaf *stb__nptr_make_leaf(void *mem) +{ + stb_uint32 address = (stb_uint32) mem; + stb__memory_node *z = stb__memtab_root[address >> STB__NPTR_ROOT_SHIFT]; + stb__memory_leaf *f; + if (!z) { + int i; + z = (stb__memory_node *) stb__nptr_alloc(sizeof(*stb__memtab_root[0])); + stb__memtab_root[address >> STB__NPTR_ROOT_SHIFT] = z; + for (i=0; i < 256; ++i) + z->children[i] = 0; + } + f = (stb__memory_leaf *) stb__nptr_alloc(sizeof(*f)); + z->children[STB__NPTR_NODE_OFFSET(address)] = f; + f->pointers = NULL; + f->targets = NULL; + return f; +} + +static stb__nptr_target *stb__nptr_find_target(void *target, int force) +{ + stb__memory_leaf *p = stb__nptr_find_leaf(target); + if (p) { + stb__nptr_target *t = p->targets; + while (t) { + if (t->ptr == target) + return t; + t = t->next_in_block; + } + } + if (force) { + stb__nptr_target *t = (stb__nptr_target*) stb__nptr_alloc(sizeof(*t)); + if (!p) p = stb__nptr_make_leaf(target); + t->ptr = target; + t->first = NULL; + t->next_in_block = p->targets; + p->targets = t; + return t; + } else + return NULL; +} + +static stb__nptr *stb__nptr_find_pointer(void *ptr, int force) +{ + stb__memory_leaf *p = stb__nptr_find_leaf(ptr); + if (p) { + stb__nptr *t = p->pointers; + while (t) { + if (t->ptr == ptr) + return t; + t = t->next_in_block; + } + } + if (force) { + stb__nptr *t = (stb__nptr *) stb__nptr_alloc(sizeof(*t)); + if (!p) p = stb__nptr_make_leaf(ptr); + t->ptr = ptr; + t->next = NULL; + t->prev = NULL; + t->next_in_block = p->pointers; + p->pointers = t; + return t; + } else + return NULL; +} + +void stb_nptr_set(void *address_of_pointer, void *value_to_write) +{ + if (*(void **)address_of_pointer != value_to_write) { + *(void **) address_of_pointer = value_to_write; + stb_nptr_didset(address_of_pointer); + } +} + +void stb_nptr_didset(void *address_of_pointer) +{ + // first unlink from old chain + void *new_address; + stb__nptr *p = stb__nptr_find_pointer(address_of_pointer, 1); // force building if doesn't exist + if (p->prev) { // if p->prev is NULL, we just built it, or it was NULL + *(p->prev) = p->next; + if (p->next) p->next->prev = p->prev; + } + // now add to new chain + new_address = *(void **)address_of_pointer; + if (new_address != NULL) { + stb__nptr_target *t = stb__nptr_find_target(new_address, 1); + p->next = t->first; + if (p->next) p->next->prev = &p->next; + p->prev = &t->first; + t->first = p; + } else { + p->prev = NULL; + p->next = NULL; + } +} + +void stb__nptr_block(void *address, int len, void (*function)(stb__memory_leaf *f, int datum, void *start, void *end), int datum) +{ + void *end_address = (void *) ((char *) address + len - 1); + stb__memory_node *n; + stb_uint32 start = (stb_uint32) address; + stb_uint32 end = start + len - 1; + + int b0 = start >> STB__NPTR_ROOT_SHIFT; + int b1 = end >> STB__NPTR_ROOT_SHIFT; + int b=b0,i,e0,e1; + + e0 = STB__NPTR_NODE_OFFSET(start); + + if (datum <= 0) { + // first block + n = stb__memtab_root[b0]; + if (n) { + if (b0 != b1) + e1 = STB__NPTR_NODE_NUM-1; + else + e1 = STB__NPTR_NODE_OFFSET(end); + for (i=e0; i <= e1; ++i) + if (n->children[i]) + function(n->children[i], datum, address, end_address); + } + if (b1 > b0) { + // blocks other than the first and last block + for (b=b0+1; b < b1; ++b) { + n = stb__memtab_root[b]; + if (n) + for (i=0; i <= STB__NPTR_NODE_NUM-1; ++i) + if (n->children[i]) + function(n->children[i], datum, address, end_address); + } + // last block + n = stb__memtab_root[b1]; + if (n) { + e1 = STB__NPTR_NODE_OFFSET(end); + for (i=0; i <= e1; ++i) + if (n->children[i]) + function(n->children[i], datum, address, end_address); + } + } + } else { + if (b1 > b0) { + // last block + n = stb__memtab_root[b1]; + if (n) { + e1 = STB__NPTR_NODE_OFFSET(end); + for (i=e1; i >= 0; --i) + if (n->children[i]) + function(n->children[i], datum, address, end_address); + } + // blocks other than the first and last block + for (b=b1-1; b > b0; --b) { + n = stb__memtab_root[b]; + if (n) + for (i=STB__NPTR_NODE_NUM-1; i >= 0; --i) + if (n->children[i]) + function(n->children[i], datum, address, end_address); + } + } + // first block + n = stb__memtab_root[b0]; + if (n) { + if (b0 != b1) + e1 = STB__NPTR_NODE_NUM-1; + else + e1 = STB__NPTR_NODE_OFFSET(end); + for (i=e1; i >= e0; --i) + if (n->children[i]) + function(n->children[i], datum, address, end_address); + } + } +} + +static void stb__nptr_delete_pointers(stb__memory_leaf *f, int offset, void *start, void *end) +{ + stb__nptr **p = &f->pointers; + while (*p) { + stb__nptr *n = *p; + if (n->ptr >= start && n->ptr <= end) { + // unlink + if (n->prev) { + *(n->prev) = n->next; + if (n->next) n->next->prev = n->prev; + } + *p = n->next_in_block; + stb__nptr_free(n); + } else + p = &(n->next_in_block); + } +} + +static void stb__nptr_delete_targets(stb__memory_leaf *f, int offset, void *start, void *end) +{ + stb__nptr_target **p = &f->targets; + while (*p) { + stb__nptr_target *n = *p; + if (n->ptr >= start && n->ptr <= end) { + // null pointers + stb__nptr *z = n->first; + while (z) { + stb__nptr *y = z->next; + z->prev = NULL; + z->next = NULL; + *(void **) z->ptr = NULL; + z = y; + } + // unlink this target + *p = n->next_in_block; + stb__nptr_free(n); + } else + p = &(n->next_in_block); + } +} + +void stb_nptr_didfree(void *address_being_freed, int len) +{ + // step one: delete all pointers in this block + stb__nptr_block(address_being_freed, len, stb__nptr_delete_pointers, 0); + // step two: NULL all pointers to this block; do this second to avoid NULLing deleted pointers + stb__nptr_block(address_being_freed, len, stb__nptr_delete_targets, 0); +} + +void stb_nptr_free(void *address_being_freed, int len) +{ + free(address_being_freed); + stb_nptr_didfree(address_being_freed, len); +} + +static void stb__nptr_move_targets(stb__memory_leaf *f, int offset, void *start, void *end) +{ + stb__nptr_target **t = &f->targets; + while (*t) { + stb__nptr_target *n = *t; + if (n->ptr >= start && n->ptr <= end) { + stb__nptr *z; + stb__memory_leaf *f; + // unlink n + *t = n->next_in_block; + // update n to new address + n->ptr = (void *) ((char *) n->ptr + offset); + f = stb__nptr_find_leaf(n->ptr); + if (!f) f = stb__nptr_make_leaf(n->ptr); + n->next_in_block = f->targets; + f->targets = n; + // now go through all pointers and make them point here + z = n->first; + while (z) { + *(void**) z->ptr = n->ptr; + z = z->next; + } + } else + t = &(n->next_in_block); + } +} + +static void stb__nptr_move_pointers(stb__memory_leaf *f, int offset, void *start, void *end) +{ + stb__nptr **p = &f->pointers; + while (*p) { + stb__nptr *n = *p; + if (n->ptr >= start && n->ptr <= end) { + // unlink + *p = n->next_in_block; + n->ptr = (void *) ((int) n->ptr + offset); + // move to new block + f = stb__nptr_find_leaf(n->ptr); + if (!f) f = stb__nptr_make_leaf(n->ptr); + n->next_in_block = f->pointers; + f->pointers = n; + } else + p = &(n->next_in_block); + } +} + +void stb_nptr_realloc(void *new_address, void *old_address, int len) +{ + if (new_address == old_address) return; + + // have to move the pointers first, because moving the targets + // requires writing to the pointers-to-the-targets, and if some of those moved too, + // we need to make sure we don't write to the old memory + + // step one: move all pointers within the block + stb__nptr_block(old_address, len, stb__nptr_move_pointers, (char *) new_address - (char *) old_address); + // step two: move all targets within the block + stb__nptr_block(old_address, len, stb__nptr_move_targets, (char *) new_address - (char *) old_address); +} + +void stb_nptr_move(void *new_address, void *old_address) +{ + stb_nptr_realloc(new_address, old_address, 1); +} + +void stb_nptr_recache(void) +{ + int i,j; + for (i=0; i < STB__NPTR_ROOT_NUM; ++i) + if (stb__memtab_root[i]) + for (j=0; j < STB__NPTR_NODE_NUM; ++j) + if (stb__memtab_root[i]->children[j]) { + stb__nptr *p = stb__memtab_root[i]->children[j]->pointers; + while (p) { + stb_nptr_didset(p->ptr); + p = p->next_in_block; + } + } +} + +#endif // STB_DEFINE +#endif // STB_NPTR + + +////////////////////////////////////////////////////////////////////////////// +// +// File Processing +// + + +#ifdef _WIN32 + #define stb_rename(x,y) _wrename((const wchar_t *)stb__from_utf8(x), (const wchar_t *)stb__from_utf8_alt(y)) +#else + #define stb_rename rename +#endif + +STB_EXTERN void stb_fput_varlen64(FILE *f, stb_uint64 v); +STB_EXTERN stb_uint64 stb_fget_varlen64(FILE *f); +STB_EXTERN int stb_size_varlen64(stb_uint64 v); + + +#define stb_filec (char *) stb_file +#define stb_fileu (unsigned char *) stb_file +STB_EXTERN void * stb_file(char *filename, size_t *length); +STB_EXTERN void * stb_file_max(char *filename, size_t *length); +STB_EXTERN size_t stb_filelen(FILE *f); +STB_EXTERN int stb_filewrite(char *filename, void *data, size_t length); +STB_EXTERN int stb_filewritestr(char *filename, char *data); +STB_EXTERN char ** stb_stringfile(char *filename, int *len); +STB_EXTERN char ** stb_stringfile_trimmed(char *name, int *len, char comm); +STB_EXTERN char * stb_fgets(char *buffer, int buflen, FILE *f); +STB_EXTERN char * stb_fgets_malloc(FILE *f); +STB_EXTERN int stb_fexists(char *filename); +STB_EXTERN int stb_fcmp(char *s1, char *s2); +STB_EXTERN int stb_feq(char *s1, char *s2); +STB_EXTERN time_t stb_ftimestamp(char *filename); + +STB_EXTERN int stb_fullpath(char *abs, int abs_size, char *rel); +STB_EXTERN FILE * stb_fopen(char *filename, const char *mode); +STB_EXTERN int stb_fclose(FILE *f, int keep); + +enum +{ + stb_keep_no = 0, + stb_keep_yes = 1, + stb_keep_if_different = 2, +}; + +STB_EXTERN int stb_copyfile(char *src, char *dest); + +STB_EXTERN void stb_fput_varlen64(FILE *f, stb_uint64 v); +STB_EXTERN stb_uint64 stb_fget_varlen64(FILE *f); +STB_EXTERN int stb_size_varlen64(stb_uint64 v); + +STB_EXTERN void stb_fwrite32(FILE *f, stb_uint32 datum); +STB_EXTERN void stb_fput_varlen (FILE *f, int v); +STB_EXTERN void stb_fput_varlenu(FILE *f, unsigned int v); +STB_EXTERN int stb_fget_varlen (FILE *f); +STB_EXTERN stb_uint stb_fget_varlenu(FILE *f); +STB_EXTERN void stb_fput_ranged (FILE *f, int v, int b, stb_uint n); +STB_EXTERN int stb_fget_ranged (FILE *f, int b, stb_uint n); +STB_EXTERN int stb_size_varlen (int v); +STB_EXTERN int stb_size_varlenu(unsigned int v); +STB_EXTERN int stb_size_ranged (int b, stb_uint n); + +STB_EXTERN int stb_fread(void *data, size_t len, size_t count, void *f); +STB_EXTERN int stb_fwrite(void *data, size_t len, size_t count, void *f); + +#if 0 +typedef struct +{ + FILE *base_file; + char *buffer; + int buffer_size; + int buffer_off; + int buffer_left; +} STBF; + +STB_EXTERN STBF *stb_tfopen(char *filename, char *mode); +STB_EXTERN int stb_tfread(void *data, size_t len, size_t count, STBF *f); +STB_EXTERN int stb_tfwrite(void *data, size_t len, size_t count, STBF *f); +#endif + +#ifdef STB_DEFINE + +#if 0 +STBF *stb_tfopen(char *filename, char *mode) +{ + STBF *z; + FILE *f = stb_p_fopen(filename, mode); + if (!f) return NULL; + z = (STBF *) malloc(sizeof(*z)); + if (!z) { fclose(f); return NULL; } + z->base_file = f; + if (!strcmp(mode, "rb") || !strcmp(mode, "wb")) { + z->buffer_size = 4096; + z->buffer_off = z->buffer_size; + z->buffer_left = 0; + z->buffer = malloc(z->buffer_size); + if (!z->buffer) { free(z); fclose(f); return NULL; } + } else { + z->buffer = 0; + z->buffer_size = 0; + z->buffer_left = 0; + } + return z; +} + +int stb_tfread(void *data, size_t len, size_t count, STBF *f) +{ + int total = len*count, done=0; + if (!total) return 0; + if (total <= z->buffer_left) { + memcpy(data, z->buffer + z->buffer_off, total); + z->buffer_off += total; + z->buffer_left -= total; + return count; + } else { + char *out = (char *) data; + + // consume all buffered data + memcpy(data, z->buffer + z->buffer_off, z->buffer_left); + done = z->buffer_left; + out += z->buffer_left; + z->buffer_left=0; + + if (total-done > (z->buffer_size >> 1)) { + done += fread(out + } + } +} +#endif + +void stb_fwrite32(FILE *f, stb_uint32 x) +{ + fwrite(&x, 4, 1, f); +} + +#if defined(_WIN32) + #define stb__stat _stat +#else + #define stb__stat stat +#endif + +int stb_fexists(char *filename) +{ + struct stb__stat buf; + return stb__windows( + _wstat((const wchar_t *)stb__from_utf8(filename), &buf), + stat(filename,&buf) + ) == 0; +} + +time_t stb_ftimestamp(char *filename) +{ + struct stb__stat buf; + if (stb__windows( + _wstat((const wchar_t *)stb__from_utf8(filename), &buf), + stat(filename,&buf) + ) == 0) + { + return buf.st_mtime; + } else { + return 0; + } +} + +size_t stb_filelen(FILE *f) +{ + long len, pos; + pos = ftell(f); + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, pos, SEEK_SET); + return (size_t) len; +} + +void *stb_file(char *filename, size_t *length) +{ + FILE *f = stb__fopen(filename, "rb"); + char *buffer; + size_t len, len2; + if (!f) return NULL; + len = stb_filelen(f); + buffer = (char *) malloc(len+2); // nul + extra + len2 = fread(buffer, 1, len, f); + if (len2 == len) { + if (length) *length = len; + buffer[len] = 0; + } else { + free(buffer); + buffer = NULL; + } + fclose(f); + return buffer; +} + +int stb_filewrite(char *filename, void *data, size_t length) +{ + FILE *f = stb_fopen(filename, "wb"); + if (f) { + unsigned char *data_ptr = (unsigned char *) data; + size_t remaining = length; + while (remaining > 0) { + size_t len2 = remaining > 65536 ? 65536 : remaining; + size_t len3 = fwrite(data_ptr, 1, len2, f); + if (len2 != len3) { + fprintf(stderr, "Failed while writing %s\n", filename); + break; + } + remaining -= len2; + data_ptr += len2; + } + stb_fclose(f, stb_keep_if_different); + } + return f != NULL; +} + +int stb_filewritestr(char *filename, char *data) +{ + return stb_filewrite(filename, data, strlen(data)); +} + +void * stb_file_max(char *filename, size_t *length) +{ + FILE *f = stb__fopen(filename, "rb"); + char *buffer; + size_t len, maxlen; + if (!f) return NULL; + maxlen = *length; + buffer = (char *) malloc(maxlen+1); + len = fread(buffer, 1, maxlen, f); + buffer[len] = 0; + fclose(f); + *length = len; + return buffer; +} + +char ** stb_stringfile(char *filename, int *plen) +{ + FILE *f = stb__fopen(filename, "rb"); + char *buffer, **list=NULL, *s; + size_t len, count, i; + + if (!f) return NULL; + len = stb_filelen(f); + buffer = (char *) malloc(len+1); + len = fread(buffer, 1, len, f); + buffer[len] = 0; + fclose(f); + + // two passes through: first time count lines, second time set them + for (i=0; i < 2; ++i) { + s = buffer; + if (i == 1) + list[0] = s; + count = 1; + while (*s) { + if (*s == '\n' || *s == '\r') { + // detect if both cr & lf are together + int crlf = (s[0] + s[1]) == ('\n' + '\r'); + if (i == 1) *s = 0; + if (crlf) ++s; + if (s[1]) { // it's not over yet + if (i == 1) list[count] = s+1; + ++count; + } + } + ++s; + } + if (i == 0) { + list = (char **) malloc(sizeof(*list) * (count+1) + len+1); + if (!list) return NULL; + list[count] = 0; + // recopy the file so there's just a single allocation to free + memcpy(&list[count+1], buffer, len+1); + free(buffer); + buffer = (char *) &list[count+1]; + if (plen) *plen = (int) count; + } + } + return list; +} + +char ** stb_stringfile_trimmed(char *name, int *len, char comment) +{ + int i,n,o=0; + char **s = stb_stringfile(name, &n); + if (s == NULL) return NULL; + for (i=0; i < n; ++i) { + char *p = stb_skipwhite(s[i]); + if (*p && *p != comment) + s[o++] = p; + } + s[o] = NULL; + if (len) *len = o; + return s; +} + +char * stb_fgets(char *buffer, int buflen, FILE *f) +{ + char *p; + buffer[0] = 0; + p = fgets(buffer, buflen, f); + if (p) { + int n = (int) (strlen(p)-1); + if (n >= 0) + if (p[n] == '\n') + p[n] = 0; + } + return p; +} + +char * stb_fgets_malloc(FILE *f) +{ + // avoid reallocing for small strings + char quick_buffer[800]; + quick_buffer[sizeof(quick_buffer)-2] = 0; + if (!fgets(quick_buffer, sizeof(quick_buffer), f)) + return NULL; + + if (quick_buffer[sizeof(quick_buffer)-2] == 0) { + size_t n = strlen(quick_buffer); + if (n > 0 && quick_buffer[n-1] == '\n') + quick_buffer[n-1] = 0; + return stb_p_strdup(quick_buffer); + } else { + char *p; + char *a = stb_p_strdup(quick_buffer); + size_t len = sizeof(quick_buffer)-1; + + while (!feof(f)) { + if (a[len-1] == '\n') break; + a = (char *) realloc(a, len*2); + p = &a[len]; + p[len-2] = 0; + if (!fgets(p, (int) len, f)) + break; + if (p[len-2] == 0) { + len += strlen(p); + break; + } + len = len + (len-1); + } + if (a[len-1] == '\n') + a[len-1] = 0; + return a; + } +} + +int stb_fullpath(char *abs, int abs_size, char *rel) +{ + #ifdef _WIN32 + return _fullpath(abs, rel, abs_size) != NULL; + #else + if (rel[0] == '/' || rel[0] == '~') { + if ((int) strlen(rel) >= abs_size) + return 0; + stb_p_strcpy_s(abs,65536,rel); + return STB_TRUE; + } else { + int n; + getcwd(abs, abs_size); + n = strlen(abs); + if (n+(int) strlen(rel)+2 <= abs_size) { + abs[n] = '/'; + stb_p_strcpy_s(abs+n+1, 65536,rel); + return STB_TRUE; + } else { + return STB_FALSE; + } + } + #endif +} + +static int stb_fcmp_core(FILE *f, FILE *g) +{ + char buf1[1024],buf2[1024]; + int n1,n2, res=0; + + while (1) { + n1 = (int) fread(buf1, 1, sizeof(buf1), f); + n2 = (int) fread(buf2, 1, sizeof(buf2), g); + res = memcmp(buf1,buf2,stb_min(n1,n2)); + if (res) + break; + if (n1 != n2) { + res = n1 < n2 ? -1 : 1; + break; + } + if (n1 == 0) + break; + } + + fclose(f); + fclose(g); + return res; +} + +int stb_fcmp(char *s1, char *s2) +{ + FILE *f = stb__fopen(s1, "rb"); + FILE *g = stb__fopen(s2, "rb"); + + if (f == NULL || g == NULL) { + if (f) fclose(f); + if (g) { + fclose(g); + return STB_TRUE; + } + return f != NULL; + } + + return stb_fcmp_core(f,g); +} + +int stb_feq(char *s1, char *s2) +{ + FILE *f = stb__fopen(s1, "rb"); + FILE *g = stb__fopen(s2, "rb"); + + if (f == NULL || g == NULL) { + if (f) fclose(f); + if (g) fclose(g); + return f == g; + } + + // feq is faster because it shortcuts if they're different length + if (stb_filelen(f) != stb_filelen(g)) { + fclose(f); + fclose(g); + return 0; + } + + return !stb_fcmp_core(f,g); +} + +static stb_ptrmap *stb__files; + +typedef struct +{ + char *temp_name; + char *name; + int errors; +} stb__file_data; + +static FILE *stb__open_temp_file(char *temp_name, char *src_name, const char *mode) +{ + size_t p; +#ifdef _MSC_VER + int j; +#endif + FILE *f; + // try to generate a temporary file in the same directory + p = strlen(src_name)-1; + while (p > 0 && src_name[p] != '/' && src_name[p] != '\\' + && src_name[p] != ':' && src_name[p] != '~') + --p; + ++p; + + memcpy(temp_name, src_name, p); + + #ifdef _MSC_VER + // try multiple times to make a temp file... just in + // case some other process makes the name first + for (j=0; j < 32; ++j) { + stb_p_strcpy_s(temp_name+p, 65536, "stmpXXXXXX"); + if (!stb_p_mktemp(temp_name)) + return 0; + + f = stb_p_fopen(temp_name, mode); + if (f != NULL) + break; + } + #else + { + stb_p_strcpy_s(temp_name+p, 65536, "stmpXXXXXX"); + #ifdef __MINGW32__ + int fd = open(stb_p_mktemp(temp_name), O_RDWR); + #else + int fd = mkstemp(temp_name); + #endif + if (fd == -1) return NULL; + f = fdopen(fd, mode); + if (f == NULL) { + unlink(temp_name); + close(fd); + return NULL; + } + } + #endif + return f; +} + + +FILE * stb_fopen(char *filename, const char *mode) +{ + FILE *f; + char name_full[4096]; + char temp_full[sizeof(name_full) + 12]; + + // @TODO: if the file doesn't exist, we can also use the fastpath here + if (mode[0] != 'w' && !strchr(mode, '+')) + return stb__fopen(filename, mode); + + // save away the full path to the file so if the program + // changes the cwd everything still works right! unix has + // better ways to do this, but we have to work in windows + name_full[0] = '\0'; // stb_fullpath reads name_full[0] + if (stb_fullpath(name_full, sizeof(name_full), filename)==0) + return 0; + + f = stb__open_temp_file(temp_full, name_full, mode); + if (f != NULL) { + stb__file_data *d = (stb__file_data *) malloc(sizeof(*d)); + if (!d) { assert(0); /* NOTREACHED */fclose(f); return NULL; } + if (stb__files == NULL) stb__files = stb_ptrmap_create(); + d->temp_name = stb_p_strdup(temp_full); + d->name = stb_p_strdup(name_full); + d->errors = 0; + stb_ptrmap_add(stb__files, f, d); + return f; + } + + return NULL; +} + +int stb_fclose(FILE *f, int keep) +{ + stb__file_data *d; + + int ok = STB_FALSE; + if (f == NULL) return 0; + + if (ferror(f)) + keep = stb_keep_no; + + fclose(f); + + if (stb__files && stb_ptrmap_remove(stb__files, f, (void **) &d)) { + if (stb__files->count == 0) { + stb_ptrmap_destroy(stb__files); + stb__files = NULL; + } + } else + return STB_TRUE; // not special + + if (keep == stb_keep_if_different) { + // check if the files are identical + if (stb_feq(d->name, d->temp_name)) { + keep = stb_keep_no; + ok = STB_TRUE; // report success if no change + } + } + + if (keep == stb_keep_no) { + remove(d->temp_name); + } else { + if (!stb_fexists(d->name)) { + // old file doesn't exist, so just move the new file over it + stb_rename(d->temp_name, d->name); + } else { + // don't delete the old file yet in case there are troubles! First rename it! + char preserved_old_file[4096]; + + // generate a temp filename in the same directory (also creates it, which we don't need) + FILE *dummy = stb__open_temp_file(preserved_old_file, d->name, "wb"); + if (dummy != NULL) { + // we don't actually want the open file + fclose(dummy); + + // discard what we just created + remove(preserved_old_file); // if this fails, there's nothing we can do, and following logic handles it as best as possible anyway + + // move the existing file to the preserved name + if (0 != stb_rename(d->name, preserved_old_file)) { // 0 on success + // failed, state is: + // filename -> old file + // tempname -> new file + // keep tempname around so we don't lose data + } else { + // state is: + // preserved -> old file + // tempname -> new file + // move the new file to the old name + if (0 == stb_rename(d->temp_name, d->name)) { + // state is: + // preserved -> old file + // filename -> new file + ok = STB_TRUE; + + // 'filename -> new file' has always been the goal, so clean up + remove(preserved_old_file); // nothing to be done if it fails + } else { + // couldn't rename, so try renaming preserved file back + + // state is: + // preserved -> old file + // tempname -> new file + stb_rename(preserved_old_file, d->name); + // if the rename failed, there's nothing more we can do + } + } + } else { + // we couldn't get a temp filename. do this the naive way; the worst case failure here + // leaves the filename pointing to nothing and the new file as a tempfile + remove(d->name); + stb_rename(d->temp_name, d->name); + } + } + } + + free(d->temp_name); + free(d->name); + free(d); + + return ok; +} + +int stb_copyfile(char *src, char *dest) +{ + char raw_buffer[1024]; + char *buffer; + int buf_size = 65536; + + FILE *f, *g; + + // if file already exists at destination, do nothing + if (stb_feq(src, dest)) return STB_TRUE; + + // open file + f = stb__fopen(src, "rb"); + if (f == NULL) return STB_FALSE; + + // open file for writing + g = stb__fopen(dest, "wb"); + if (g == NULL) { + fclose(f); + return STB_FALSE; + } + + buffer = (char *) malloc(buf_size); + if (buffer == NULL) { + buffer = raw_buffer; + buf_size = sizeof(raw_buffer); + } + + while (!feof(f)) { + size_t n = fread(buffer, 1, buf_size, f); + if (n != 0) + fwrite(buffer, 1, n, g); + } + + fclose(f); + if (buffer != raw_buffer) + free(buffer); + + fclose(g); + return STB_TRUE; +} + +// varlen: +// v' = (v >> 31) + (v < 0 ? ~v : v)<<1; // small abs(v) => small v' +// output v as big endian v'+k for v' <= k: +// 1 byte : v' <= 0x00000080 ( -64 <= v < 64) 7 bits +// 2 bytes: v' <= 0x00004000 (-8192 <= v < 8192) 14 bits +// 3 bytes: v' <= 0x00200000 21 bits +// 4 bytes: v' <= 0x10000000 28 bits +// the number of most significant 1-bits in the first byte +// equals the number of bytes after the first + +#define stb__varlen_xform(v) (v<0 ? (~v << 1)+1 : (v << 1)) + +int stb_size_varlen(int v) { return stb_size_varlenu(stb__varlen_xform(v)); } +int stb_size_varlenu(unsigned int v) +{ + if (v < 0x00000080) return 1; + if (v < 0x00004000) return 2; + if (v < 0x00200000) return 3; + if (v < 0x10000000) return 4; + return 5; +} + +void stb_fput_varlen(FILE *f, int v) { stb_fput_varlenu(f, stb__varlen_xform(v)); } + +void stb_fput_varlenu(FILE *f, unsigned int z) +{ + if (z >= 0x10000000) fputc(0xF0,f); + if (z >= 0x00200000) fputc((z < 0x10000000 ? 0xE0 : 0)+(z>>24),f); + if (z >= 0x00004000) fputc((z < 0x00200000 ? 0xC0 : 0)+(z>>16),f); + if (z >= 0x00000080) fputc((z < 0x00004000 ? 0x80 : 0)+(z>> 8),f); + fputc(z,f); +} + +#define stb_fgetc(f) ((unsigned char) fgetc(f)) + +int stb_fget_varlen(FILE *f) +{ + unsigned int z = stb_fget_varlenu(f); + return (z & 1) ? ~(z>>1) : (z>>1); +} + +unsigned int stb_fget_varlenu(FILE *f) +{ + unsigned int z; + unsigned char d; + d = stb_fgetc(f); + + if (d >= 0x80) { + if (d >= 0xc0) { + if (d >= 0xe0) { + if (d == 0xf0) z = stb_fgetc(f) << 24; + else z = (d - 0xe0) << 24; + z += stb_fgetc(f) << 16; + } + else + z = (d - 0xc0) << 16; + z += stb_fgetc(f) << 8; + } else + z = (d - 0x80) << 8; + z += stb_fgetc(f); + } else + z = d; + return z; +} + +stb_uint64 stb_fget_varlen64(FILE *f) +{ + stb_uint64 z; + unsigned char d; + d = stb_fgetc(f); + + if (d >= 0x80) { + if (d >= 0xc0) { + if (d >= 0xe0) { + if (d >= 0xf0) { + if (d >= 0xf8) { + if (d >= 0xfc) { + if (d >= 0xfe) { + if (d >= 0xff) + z = (stb_uint64) stb_fgetc(f) << 56; + else + z = (stb_uint64) (d - 0xfe) << 56; + z |= (stb_uint64) stb_fgetc(f) << 48; + } else z = (stb_uint64) (d - 0xfc) << 48; + z |= (stb_uint64) stb_fgetc(f) << 40; + } else z = (stb_uint64) (d - 0xf8) << 40; + z |= (stb_uint64) stb_fgetc(f) << 32; + } else z = (stb_uint64) (d - 0xf0) << 32; + z |= (stb_uint) stb_fgetc(f) << 24; + } else z = (stb_uint) (d - 0xe0) << 24; + z |= (stb_uint) stb_fgetc(f) << 16; + } else z = (stb_uint) (d - 0xc0) << 16; + z |= (stb_uint) stb_fgetc(f) << 8; + } else z = (stb_uint) (d - 0x80) << 8; + z |= stb_fgetc(f); + } else + z = d; + + return (z & 1) ? ~(z >> 1) : (z >> 1); +} + +int stb_size_varlen64(stb_uint64 v) +{ + if (v < 0x00000080) return 1; + if (v < 0x00004000) return 2; + if (v < 0x00200000) return 3; + if (v < 0x10000000) return 4; + if (v < STB_IMM_UINT64(0x0000000800000000)) return 5; + if (v < STB_IMM_UINT64(0x0000040000000000)) return 6; + if (v < STB_IMM_UINT64(0x0002000000000000)) return 7; + if (v < STB_IMM_UINT64(0x0100000000000000)) return 8; + return 9; +} + +void stb_fput_varlen64(FILE *f, stb_uint64 v) +{ + stb_uint64 z = stb__varlen_xform(v); + int first=1; + if (z >= STB_IMM_UINT64(0x100000000000000)) { + fputc(0xff,f); + first=0; + } + if (z >= STB_IMM_UINT64(0x02000000000000)) fputc((first ? 0xFE : 0)+(char)(z>>56),f), first=0; + if (z >= STB_IMM_UINT64(0x00040000000000)) fputc((first ? 0xFC : 0)+(char)(z>>48),f), first=0; + if (z >= STB_IMM_UINT64(0x00000800000000)) fputc((first ? 0xF8 : 0)+(char)(z>>40),f), first=0; + if (z >= STB_IMM_UINT64(0x00000010000000)) fputc((first ? 0xF0 : 0)+(char)(z>>32),f), first=0; + if (z >= STB_IMM_UINT64(0x00000000200000)) fputc((first ? 0xE0 : 0)+(char)(z>>24),f), first=0; + if (z >= STB_IMM_UINT64(0x00000000004000)) fputc((first ? 0xC0 : 0)+(char)(z>>16),f), first=0; + if (z >= STB_IMM_UINT64(0x00000000000080)) fputc((first ? 0x80 : 0)+(char)(z>> 8),f), first=0; + fputc((char)z,f); +} + +void stb_fput_ranged(FILE *f, int v, int b, stb_uint n) +{ + v -= b; + if (n <= (1 << 31)) + assert((stb_uint) v < n); + if (n > (1 << 24)) fputc(v >> 24, f); + if (n > (1 << 16)) fputc(v >> 16, f); + if (n > (1 << 8)) fputc(v >> 8, f); + fputc(v,f); +} + +int stb_fget_ranged(FILE *f, int b, stb_uint n) +{ + unsigned int v=0; + if (n > (1 << 24)) v += stb_fgetc(f) << 24; + if (n > (1 << 16)) v += stb_fgetc(f) << 16; + if (n > (1 << 8)) v += stb_fgetc(f) << 8; + v += stb_fgetc(f); + return b+v; +} + +int stb_size_ranged(int b, stb_uint n) +{ + if (n > (1 << 24)) return 4; + if (n > (1 << 16)) return 3; + if (n > (1 << 8)) return 2; + return 1; +} + +void stb_fput_string(FILE *f, char *s) +{ + size_t len = strlen(s); + stb_fput_varlenu(f, (unsigned int) len); + fwrite(s, 1, len, f); +} + +// inverse of the above algorithm +char *stb_fget_string(FILE *f, void *p) +{ + char *s; + int len = stb_fget_varlenu(f); + if (len > 4096) return NULL; + s = p ? stb_malloc_string(p, len+1) : (char *) malloc(len+1); + fread(s, 1, len, f); + s[len] = 0; + return s; +} + +char *stb_strdup(char *str, void *pool) +{ + size_t len = strlen(str); + char *p = stb_malloc_string(pool, len+1); + stb_p_strcpy_s(p, len+1, str); + return p; +} + +// strip the trailing '/' or '\\' from a directory so we can refer to it +// as a file for _stat() +char *stb_strip_final_slash(char *t) +{ + if (t[0]) { + char *z = t + strlen(t) - 1; + // *z is the last character + if (*z == '\\' || *z == '/') + if (z != t+2 || t[1] != ':') // but don't strip it if it's e.g. "c:/" + *z = 0; + if (*z == '\\') + *z = '/'; // canonicalize to make sure it matches db + } + return t; +} + +char *stb_strip_final_slash_regardless(char *t) +{ + if (t[0]) { + char *z = t + strlen(t) - 1; + // *z is the last character + if (*z == '\\' || *z == '/') + *z = 0; + if (*z == '\\') + *z = '/'; // canonicalize to make sure it matches db + } + return t; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Options parsing +// + +STB_EXTERN char **stb_getopt_param(int *argc, char **argv, char *param); +STB_EXTERN char **stb_getopt(int *argc, char **argv); +STB_EXTERN void stb_getopt_free(char **opts); + +#ifdef STB_DEFINE + +void stb_getopt_free(char **opts) +{ + int i; + char ** o2 = opts; + for (i=0; i < stb_arr_len(o2); ++i) + free(o2[i]); + stb_arr_free(o2); +} + +char **stb_getopt(int *argc, char **argv) +{ + return stb_getopt_param(argc, argv, (char*) ""); +} + +char **stb_getopt_param(int *argc, char **argv, char *param) +{ + char ** opts=NULL; + int i,j=1; + for (i=1; i < *argc; ++i) { + if (argv[i][0] != '-') { + argv[j++] = argv[i]; + } else { + if (argv[i][1] == 0) { // plain - == don't parse further options + ++i; + while (i < *argc) + argv[j++] = argv[i++]; + break; + } else if (argv[i][1] == '-') { + // copy argument through including initial '-' for clarity + stb_arr_push(opts, stb_p_strdup(argv[i])); + } else { + int k; + char *q = argv[i]; // traverse options list + for (k=1; q[k]; ++k) { + char *s; + if (strchr(param, q[k])) { // does it take a parameter? + char *t = &q[k+1], z = q[k]; + size_t len=0; + if (*t == 0) { + if (i == *argc-1) { // takes a parameter, but none found + *argc = 0; + stb_getopt_free(opts); + return NULL; + } + t = argv[++i]; + } else + k += (int) strlen(t); + len = strlen(t); + s = (char *) malloc(len+2); + if (!s) return NULL; + s[0] = z; + stb_p_strcpy_s(s+1, len+2, t); + } else { + // no parameter + s = (char *) malloc(2); + if (!s) return NULL; + s[0] = q[k]; + s[1] = 0; + } + stb_arr_push(opts, s); + } + } + } + } + stb_arr_push(opts, NULL); + *argc = j; + return opts; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// Portable directory reading +// + +STB_EXTERN char **stb_readdir_files (char *dir); +STB_EXTERN char **stb_readdir_files_mask(char *dir, char *wild); +STB_EXTERN char **stb_readdir_subdirs(char *dir); +STB_EXTERN char **stb_readdir_subdirs_mask(char *dir, char *wild); +STB_EXTERN void stb_readdir_free (char **files); +STB_EXTERN char **stb_readdir_recursive(char *dir, char *filespec); +STB_EXTERN void stb_delete_directory_recursive(char *dir); + +#ifdef STB_DEFINE + +#ifdef _MSC_VER +#include +#else +#include +#include +#endif + +void stb_readdir_free(char **files) +{ + char **f2 = files; + int i; + for (i=0; i < stb_arr_len(f2); ++i) + free(f2[i]); + stb_arr_free(f2); +} + +static int isdotdirname(char *name) +{ + if (name[0] == '.') + return (name[1] == '.') ? !name[2] : !name[1]; + return 0; +} + +STB_EXTERN int stb_wildmatchi(char *expr, char *candidate); +static char **readdir_raw(char *dir, int return_subdirs, char *mask) +{ + char **results = NULL; + char buffer[4096], with_slash[4096]; + size_t n; + + #ifdef WIN32 + stb__wchar *ws; + struct _wfinddata_t data; + #ifdef _WIN64 + const intptr_t none = -1; + intptr_t z; + #else + const long none = -1; + long z; + #endif + #else // !WIN32 + const DIR *none = NULL; + DIR *z; + #endif + + n = stb_strscpy(buffer,dir,sizeof(buffer)); + if (!n || n >= sizeof(buffer)) + return NULL; + stb_fixpath(buffer); + + if (n > 0 && (buffer[n-1] != '/')) { + buffer[n++] = '/'; + } + buffer[n] = 0; + if (!stb_strscpy(with_slash,buffer,sizeof(with_slash))) + return NULL; + + #ifdef WIN32 + if (!stb_strscpy(buffer+n,"*.*",sizeof(buffer)-n)) + return NULL; + ws = stb__from_utf8(buffer); + z = _wfindfirst((wchar_t *)ws, &data); + #else + z = opendir(dir); + #endif + + if (z != none) { + int nonempty = STB_TRUE; + #ifndef WIN32 + struct dirent *data = readdir(z); + nonempty = (data != NULL); + #endif + + if (nonempty) { + + do { + int is_subdir; + #ifdef WIN32 + char *name = stb__to_utf8((stb__wchar *)data.name); + if (name == NULL) { + fprintf(stderr, "%s to convert '%S' to %s!\n", "Unable", data.name, "utf8"); + continue; + } + is_subdir = !!(data.attrib & _A_SUBDIR); + #else + char *name = data->d_name; + if (!stb_strscpy(buffer+n,name,sizeof(buffer)-n)) + break; + // Could follow DT_LNK, but would need to check for recursive links. + is_subdir = !!(data->d_type & DT_DIR); + #endif + + if (is_subdir == return_subdirs) { + if (!is_subdir || !isdotdirname(name)) { + if (!mask || stb_wildmatchi(mask, name)) { + char buffer[4096],*p=buffer; + if ( stb_snprintf(buffer, sizeof(buffer), "%s%s", with_slash, name) < 0 ) + break; + if (buffer[0] == '.' && buffer[1] == '/') + p = buffer+2; + stb_arr_push(results, stb_p_strdup(p)); + } + } + } + } + #ifdef WIN32 + while (0 == _wfindnext(z, &data)); + #else + while ((data = readdir(z)) != NULL); + #endif + } + #ifdef WIN32 + _findclose(z); + #else + closedir(z); + #endif + } + return results; +} + +char **stb_readdir_files (char *dir) { return readdir_raw(dir, 0, NULL); } +char **stb_readdir_subdirs(char *dir) { return readdir_raw(dir, 1, NULL); } +char **stb_readdir_files_mask(char *dir, char *wild) { return readdir_raw(dir, 0, wild); } +char **stb_readdir_subdirs_mask(char *dir, char *wild) { return readdir_raw(dir, 1, wild); } + +int stb__rec_max=0x7fffffff; +static char **stb_readdir_rec(char **sofar, char *dir, char *filespec) +{ + char **files; + char ** dirs; + char **p; + + if (stb_arr_len(sofar) >= stb__rec_max) return sofar; + + files = stb_readdir_files_mask(dir, filespec); + stb_arr_for(p, files) { + stb_arr_push(sofar, stb_p_strdup(*p)); + if (stb_arr_len(sofar) >= stb__rec_max) break; + } + stb_readdir_free(files); + if (stb_arr_len(sofar) >= stb__rec_max) return sofar; + + dirs = stb_readdir_subdirs(dir); + stb_arr_for(p, dirs) + sofar = stb_readdir_rec(sofar, *p, filespec); + stb_readdir_free(dirs); + return sofar; +} + +char **stb_readdir_recursive(char *dir, char *filespec) +{ + return stb_readdir_rec(NULL, dir, filespec); +} + +void stb_delete_directory_recursive(char *dir) +{ + char **list = stb_readdir_subdirs(dir); + int i; + for (i=0; i < stb_arr_len(list); ++i) + stb_delete_directory_recursive(list[i]); + stb_arr_free(list); + list = stb_readdir_files(dir); + for (i=0; i < stb_arr_len(list); ++i) + if (!remove(list[i])) { + // on windows, try again after making it writeable; don't ALWAYS + // do this first since that would be slow in the normal case + #ifdef _MSC_VER + _chmod(list[i], _S_IWRITE); + remove(list[i]); + #endif + } + stb_arr_free(list); + stb__windows(_rmdir,rmdir)(dir); +} + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// construct trees from filenames; useful for cmirror summaries + +typedef struct stb_dirtree2 stb_dirtree2; + +struct stb_dirtree2 +{ + stb_dirtree2 **subdirs; + + // make convenient for stb_summarize_tree + int num_subdir; + float weight; + + // actual data + char *fullpath; + char *relpath; + char **files; +}; + +STB_EXTERN stb_dirtree2 *stb_dirtree2_from_files_relative(char *src, char **filelist, int count); +STB_EXTERN stb_dirtree2 *stb_dirtree2_from_files(char **filelist, int count); +STB_EXTERN int stb_dir_is_prefix(char *dir, int dirlen, char *file); + +#ifdef STB_DEFINE + +int stb_dir_is_prefix(char *dir, int dirlen, char *file) +{ + if (dirlen == 0) return STB_TRUE; + if (stb_strnicmp(dir, file, dirlen)) return STB_FALSE; + if (file[dirlen] == '/' || file[dirlen] == '\\') return STB_TRUE; + return STB_FALSE; +} + +stb_dirtree2 *stb_dirtree2_from_files_relative(char *src, char **filelist, int count) +{ + char buffer1[1024]; + int i; + int dlen = (int) strlen(src), elen; + stb_dirtree2 *d; + char ** descendents = NULL; + char ** files = NULL; + char *s; + if (!count) return NULL; + // first find all the ones that belong here... note this is will take O(NM) with N files and M subdirs + for (i=0; i < count; ++i) { + if (stb_dir_is_prefix(src, dlen, filelist[i])) { + stb_arr_push(descendents, filelist[i]); + } + } + if (descendents == NULL) + return NULL; + elen = dlen; + // skip a leading slash + if (elen == 0 && (descendents[0][0] == '/' || descendents[0][0] == '\\')) + ++elen; + else if (elen) + ++elen; + // now extract all the ones that have their root here + for (i=0; i < stb_arr_len(descendents);) { + if (!stb_strchr2(descendents[i]+elen, '/', '\\')) { + stb_arr_push(files, descendents[i]); + descendents[i] = descendents[stb_arr_len(descendents)-1]; + stb_arr_pop(descendents); + } else + ++i; + } + // now create a record + d = (stb_dirtree2 *) malloc(sizeof(*d)); + d->files = files; + d->subdirs = NULL; + d->fullpath = stb_p_strdup(src); + s = stb_strrchr2(d->fullpath, '/', '\\'); + if (s) + ++s; + else + s = d->fullpath; + d->relpath = s; + // now create the children + qsort(descendents, stb_arr_len(descendents), sizeof(char *), stb_qsort_stricmp(0)); + buffer1[0] = 0; + for (i=0; i < stb_arr_len(descendents); ++i) { + char buffer2[1024]; + char *s = descendents[i] + elen, *t; + t = stb_strchr2(s, '/', '\\'); + assert(t); + stb_strncpy(buffer2, descendents[i], (int) (t-descendents[i]+1)); + if (stb_stricmp(buffer1, buffer2)) { + stb_dirtree2 *t = stb_dirtree2_from_files_relative(buffer2, descendents, stb_arr_len(descendents)); + assert(t != NULL); + stb_p_strcpy_s(buffer1, sizeof(buffer1), buffer2); + stb_arr_push(d->subdirs, t); + } + } + d->num_subdir = stb_arr_len(d->subdirs); + d->weight = 0; + return d; +} + +stb_dirtree2 *stb_dirtree2_from_files(char **filelist, int count) +{ + return stb_dirtree2_from_files_relative((char*) "", filelist, count); +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Checksums: CRC-32, ADLER32, SHA-1 +// +// CRC-32 and ADLER32 allow streaming blocks +// SHA-1 requires either a complete buffer, max size 2^32 - 73 +// or it can checksum directly from a file, max 2^61 + +#define STB_ADLER32_SEED 1 +#define STB_CRC32_SEED 0 // note that we logical NOT this in the code + +STB_EXTERN stb_uint + stb_adler32(stb_uint adler32, stb_uchar *buffer, stb_uint buflen); +STB_EXTERN stb_uint + stb_crc32_block(stb_uint crc32, stb_uchar *buffer, stb_uint len); +STB_EXTERN stb_uint stb_crc32(unsigned char *buffer, stb_uint len); + +STB_EXTERN void stb_sha1( + unsigned char output[20], unsigned char *buffer, unsigned int len); +STB_EXTERN int stb_sha1_file(unsigned char output[20], char *file); + +STB_EXTERN void stb_sha1_readable(char display[27], unsigned char sha[20]); + +#ifdef STB_DEFINE +stb_uint stb_crc32_block(stb_uint crc, unsigned char *buffer, stb_uint len) +{ + static stb_uint crc_table[256]; + stb_uint i,j,s; + crc = ~crc; + + if (crc_table[1] == 0) + for(i=0; i < 256; i++) { + for (s=i, j=0; j < 8; ++j) + s = (s >> 1) ^ (s & 1 ? 0xedb88320 : 0); + crc_table[i] = s; + } + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +} + +stb_uint stb_crc32(unsigned char *buffer, stb_uint len) +{ + return stb_crc32_block(0, buffer, len); +} + +stb_uint stb_adler32(stb_uint adler32, stb_uchar *buffer, stb_uint buflen) +{ + const unsigned long ADLER_MOD = 65521; + unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16; + unsigned long blocklen, i; + + blocklen = buflen % 5552; + while (buflen) { + for (i=0; i + 7 < blocklen; i += 8) { + s1 += buffer[0], s2 += s1; + s1 += buffer[1], s2 += s1; + s1 += buffer[2], s2 += s1; + s1 += buffer[3], s2 += s1; + s1 += buffer[4], s2 += s1; + s1 += buffer[5], s2 += s1; + s1 += buffer[6], s2 += s1; + s1 += buffer[7], s2 += s1; + + buffer += 8; + } + + for (; i < blocklen; ++i) + s1 += *buffer++, s2 += s1; + + s1 %= ADLER_MOD, s2 %= ADLER_MOD; + buflen -= blocklen; + blocklen = 5552; + } + return (s2 << 16) + s1; +} + +static void stb__sha1(stb_uchar *chunk, stb_uint h[5]) +{ + int i; + stb_uint a,b,c,d,e; + stb_uint w[80]; + + for (i=0; i < 16; ++i) + w[i] = stb_big32(&chunk[i*4]); + for (i=16; i < 80; ++i) { + stb_uint t; + t = w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]; + w[i] = (t + t) | (t >> 31); + } + + a = h[0]; + b = h[1]; + c = h[2]; + d = h[3]; + e = h[4]; + + #define STB__SHA1(k,f) \ + { \ + stb_uint temp = (a << 5) + (a >> 27) + (f) + e + (k) + w[i]; \ + e = d; \ + d = c; \ + c = (b << 30) + (b >> 2); \ + b = a; \ + a = temp; \ + } + + i=0; + for (; i < 20; ++i) STB__SHA1(0x5a827999, d ^ (b & (c ^ d)) ); + for (; i < 40; ++i) STB__SHA1(0x6ed9eba1, b ^ c ^ d ); + for (; i < 60; ++i) STB__SHA1(0x8f1bbcdc, (b & c) + (d & (b ^ c)) ); + for (; i < 80; ++i) STB__SHA1(0xca62c1d6, b ^ c ^ d ); + + #undef STB__SHA1 + + h[0] += a; + h[1] += b; + h[2] += c; + h[3] += d; + h[4] += e; +} + +void stb_sha1(stb_uchar output[20], stb_uchar *buffer, stb_uint len) +{ + unsigned char final_block[128]; + stb_uint end_start, final_len, j; + int i; + + stb_uint h[5]; + + h[0] = 0x67452301; + h[1] = 0xefcdab89; + h[2] = 0x98badcfe; + h[3] = 0x10325476; + h[4] = 0xc3d2e1f0; + + // we need to write padding to the last one or two + // blocks, so build those first into 'final_block' + + // we have to write one special byte, plus the 8-byte length + + // compute the block where the data runs out + end_start = len & ~63; + + // compute the earliest we can encode the length + if (((len+9) & ~63) == end_start) { + // it all fits in one block, so fill a second-to-last block + end_start -= 64; + } + + final_len = end_start + 128; + + // now we need to copy the data in + assert(end_start + 128 >= len+9); + assert(end_start < len || len < 64-9); + + j = 0; + if (end_start > len) + j = (stb_uint) - (int) end_start; + + for (; end_start + j < len; ++j) + final_block[j] = buffer[end_start + j]; + final_block[j++] = 0x80; + while (j < 128-5) // 5 byte length, so write 4 extra padding bytes + final_block[j++] = 0; + // big-endian size + final_block[j++] = len >> 29; + final_block[j++] = len >> 21; + final_block[j++] = len >> 13; + final_block[j++] = len >> 5; + final_block[j++] = len << 3; + assert(j == 128 && end_start + j == final_len); + + for (j=0; j < final_len; j += 64) { // 512-bit chunks + if (j+64 >= end_start+64) + stb__sha1(&final_block[j - end_start], h); + else + stb__sha1(&buffer[j], h); + } + + for (i=0; i < 5; ++i) { + output[i*4 + 0] = h[i] >> 24; + output[i*4 + 1] = h[i] >> 16; + output[i*4 + 2] = h[i] >> 8; + output[i*4 + 3] = h[i] >> 0; + } +} + +#ifdef _MSC_VER +int stb_sha1_file(stb_uchar output[20], char *file) +{ + int i; + stb_uint64 length=0; + unsigned char buffer[128]; + + FILE *f = stb__fopen(file, "rb"); + stb_uint h[5]; + + if (f == NULL) return 0; // file not found + + h[0] = 0x67452301; + h[1] = 0xefcdab89; + h[2] = 0x98badcfe; + h[3] = 0x10325476; + h[4] = 0xc3d2e1f0; + + for(;;) { + size_t n = fread(buffer, 1, 64, f); + if (n == 64) { + stb__sha1(buffer, h); + length += n; + } else { + int block = 64; + + length += n; + + buffer[n++] = 0x80; + + // if there isn't enough room for the length, double the block + if (n + 8 > 64) + block = 128; + + // pad to end + memset(buffer+n, 0, block-8-n); + + i = block - 8; + buffer[i++] = (stb_uchar) (length >> 53); + buffer[i++] = (stb_uchar) (length >> 45); + buffer[i++] = (stb_uchar) (length >> 37); + buffer[i++] = (stb_uchar) (length >> 29); + buffer[i++] = (stb_uchar) (length >> 21); + buffer[i++] = (stb_uchar) (length >> 13); + buffer[i++] = (stb_uchar) (length >> 5); + buffer[i++] = (stb_uchar) (length << 3); + assert(i == block); + stb__sha1(buffer, h); + if (block == 128) + stb__sha1(buffer+64, h); + else + assert(block == 64); + break; + } + } + fclose(f); + + for (i=0; i < 5; ++i) { + output[i*4 + 0] = h[i] >> 24; + output[i*4 + 1] = h[i] >> 16; + output[i*4 + 2] = h[i] >> 8; + output[i*4 + 3] = h[i] >> 0; + } + + return 1; +} +#endif // _MSC_VER + +// client can truncate this wherever they like +void stb_sha1_readable(char display[27], unsigned char sha[20]) +{ + char encoding[65] = "0123456789abcdefghijklmnopqrstuv" + "wxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%$"; + int num_bits = 0, acc=0; + int i=0,o=0; + while (o < 26) { + int v; + // expand the accumulator + if (num_bits < 6) { + assert(i != 20); + acc += sha[i++] << num_bits; + num_bits += 8; + } + v = acc & ((1 << 6) - 1); + display[o++] = encoding[v]; + acc >>= 6; + num_bits -= 6; + } + assert(num_bits == 20*8 - 26*6); + display[o++] = encoding[acc]; +} + +#endif // STB_DEFINE + +/////////////////////////////////////////////////////////// +// +// simplified WINDOWS registry interface... hopefully +// we'll never actually use this? + +#if defined(_WIN32) + +STB_EXTERN void * stb_reg_open(const char *mode, const char *where); // mode: "rHKLM" or "rHKCU" or "w.." +STB_EXTERN void stb_reg_close(void *reg); +STB_EXTERN int stb_reg_read(void *zreg, const char *str, void *data, unsigned long len); +STB_EXTERN int stb_reg_read_string(void *zreg, const char *str, char *data, int len); +STB_EXTERN void stb_reg_write(void *zreg, const char *str, const void *data, unsigned long len); +STB_EXTERN void stb_reg_write_string(void *zreg, const char *str, const char *data); + +#if defined(STB_DEFINE) && !defined(STB_NO_REGISTRY) + +#define STB_HAS_REGISTRY + +#ifndef _WINDOWS_ + +#define HKEY void * + +STB_EXTERN __declspec(dllimport) long __stdcall RegCloseKey ( HKEY hKey ); +STB_EXTERN __declspec(dllimport) long __stdcall RegCreateKeyExA ( HKEY hKey, const char * lpSubKey, + int Reserved, char * lpClass, int dwOptions, + int samDesired, void *lpSecurityAttributes, HKEY * phkResult, int * lpdwDisposition ); +STB_EXTERN __declspec(dllimport) long __stdcall RegDeleteKeyA ( HKEY hKey, const char * lpSubKey ); +STB_EXTERN __declspec(dllimport) long __stdcall RegQueryValueExA ( HKEY hKey, const char * lpValueName, + int * lpReserved, unsigned long * lpType, unsigned char * lpData, unsigned long * lpcbData ); +STB_EXTERN __declspec(dllimport) long __stdcall RegSetValueExA ( HKEY hKey, const char * lpValueName, + int Reserved, int dwType, const unsigned char* lpData, int cbData ); +STB_EXTERN __declspec(dllimport) long __stdcall RegOpenKeyExA ( HKEY hKey, const char * lpSubKey, + int ulOptions, int samDesired, HKEY * phkResult ); + +#endif // _WINDOWS_ + +#define STB__REG_OPTION_NON_VOLATILE 0 +#define STB__REG_KEY_ALL_ACCESS 0x000f003f +#define STB__REG_KEY_READ 0x00020019 + +#ifdef _M_AMD64 +#define STB__HKEY_CURRENT_USER 0x80000001ull +#define STB__HKEY_LOCAL_MACHINE 0x80000002ull +#else +#define STB__HKEY_CURRENT_USER 0x80000001 +#define STB__HKEY_LOCAL_MACHINE 0x80000002 +#endif + +void *stb_reg_open(const char *mode, const char *where) +{ + long res; + HKEY base; + HKEY zreg; + if (!stb_stricmp(mode+1, "cu") || !stb_stricmp(mode+1, "hkcu")) + base = (HKEY) STB__HKEY_CURRENT_USER; + else if (!stb_stricmp(mode+1, "lm") || !stb_stricmp(mode+1, "hklm")) + base = (HKEY) STB__HKEY_LOCAL_MACHINE; + else + return NULL; + + if (mode[0] == 'r') + res = RegOpenKeyExA(base, where, 0, STB__REG_KEY_READ, &zreg); + else if (mode[0] == 'w') + res = RegCreateKeyExA(base, where, 0, NULL, STB__REG_OPTION_NON_VOLATILE, STB__REG_KEY_ALL_ACCESS, NULL, &zreg, NULL); + else + return NULL; + + return res ? NULL : zreg; +} + +void stb_reg_close(void *reg) +{ + RegCloseKey((HKEY) reg); +} + +#define STB__REG_SZ 1 +#define STB__REG_BINARY 3 +#define STB__REG_DWORD 4 + +int stb_reg_read(void *zreg, const char *str, void *data, unsigned long len) +{ + unsigned long type; + unsigned long alen = len; + if (0 == RegQueryValueExA((HKEY) zreg, str, 0, &type, (unsigned char *) data, &len)) + if (type == STB__REG_BINARY || type == STB__REG_SZ || type == STB__REG_DWORD) { + if (len < alen) + *((char *) data + len) = 0; + return 1; + } + return 0; +} + +void stb_reg_write(void *zreg, const char *str, const void *data, unsigned long len) +{ + if (zreg) + RegSetValueExA((HKEY) zreg, str, 0, STB__REG_BINARY, (const unsigned char *) data, len); +} + +int stb_reg_read_string(void *zreg, const char *str, char *data, int len) +{ + if (!stb_reg_read(zreg, str, data, len)) return 0; + data[len-1] = 0; // force a 0 at the end of the string no matter what + return 1; +} + +void stb_reg_write_string(void *zreg, const char *str, const char *data) +{ + if (zreg) + RegSetValueExA((HKEY) zreg, str, 0, STB__REG_SZ, (const unsigned char *) data, (int) strlen(data)+1); +} +#endif // STB_DEFINE +#endif // _WIN32 + + +////////////////////////////////////////////////////////////////////////////// +// +// stb_cfg - This is like the registry, but the config info +// is all stored in plain old files where we can +// backup and restore them easily. The LOCATION of +// the config files is gotten from... the registry! + +#ifndef STB_NO_STB_STRINGS +typedef struct stb_cfg_st stb_cfg; + +STB_EXTERN stb_cfg * stb_cfg_open(char *config, const char *mode); // mode = "r", "w" +STB_EXTERN void stb_cfg_close(stb_cfg *cfg); +STB_EXTERN int stb_cfg_read(stb_cfg *cfg, char *key, void *value, int len); +STB_EXTERN void stb_cfg_write(stb_cfg *cfg, char *key, void *value, int len); +STB_EXTERN int stb_cfg_read_string(stb_cfg *cfg, char *key, char *value, int len); +STB_EXTERN void stb_cfg_write_string(stb_cfg *cfg, char *key, char *value); +STB_EXTERN int stb_cfg_delete(stb_cfg *cfg, char *key); +STB_EXTERN void stb_cfg_set_directory(char *dir); + +#ifdef STB_DEFINE + +typedef struct +{ + char *key; + void *value; + int value_len; +} stb__cfg_item; + +struct stb_cfg_st +{ + stb__cfg_item *data; + char *loaded_file; // this needs to be freed + FILE *f; // write the data to this file on close +}; + +static const char *stb__cfg_sig = "sTbCoNfIg!\0\0"; +static char stb__cfg_dir[512]; +STB_EXTERN void stb_cfg_set_directory(char *dir) +{ + stb_p_strcpy_s(stb__cfg_dir, sizeof(stb__cfg_dir), dir); +} + +STB_EXTERN stb_cfg * stb_cfg_open(char *config, const char *mode) +{ + size_t len; + stb_cfg *z; + char file[512]; + if (mode[0] != 'r' && mode[0] != 'w') return NULL; + + if (!stb__cfg_dir[0]) { + #ifdef _WIN32 + stb_p_strcpy_s(stb__cfg_dir, sizeof(stb__cfg_dir), "c:/stb"); + #else + strcpy(stb__cfg_dir, "~/.stbconfig"); + #endif + + #ifdef STB_HAS_REGISTRY + { + void *reg = stb_reg_open("rHKLM", "Software\\SilverSpaceship\\stb"); + if (reg) { + stb_reg_read_string(reg, "config_dir", stb__cfg_dir, sizeof(stb__cfg_dir)); + stb_reg_close(reg); + } + } + #endif + } + + stb_p_sprintf(file stb_p_size(sizeof(file)), "%s/%s.cfg", stb__cfg_dir, config); + + z = (stb_cfg *) stb_malloc(0, sizeof(*z)); + z->data = NULL; + + z->loaded_file = stb_filec(file, &len); + if (z->loaded_file) { + char *s = z->loaded_file; + if (!memcmp(s, stb__cfg_sig, 12)) { + char *s = z->loaded_file + 12; + while (s < z->loaded_file + len) { + stb__cfg_item a; + int n = *(stb_int16 *) s; + a.key = s+2; + s = s+2 + n; + a.value_len = *(int *) s; + s += 4; + a.value = s; + s += a.value_len; + stb_arr_push(z->data, a); + } + assert(s == z->loaded_file + len); + } + } + + if (mode[0] == 'w') + z->f = stb_p_fopen(file, "wb"); + else + z->f = NULL; + + return z; +} + +void stb_cfg_close(stb_cfg *z) +{ + if (z->f) { + int i; + // write the file out + fwrite(stb__cfg_sig, 12, 1, z->f); + for (i=0; i < stb_arr_len(z->data); ++i) { + stb_int16 n = (stb_int16) strlen(z->data[i].key)+1; + fwrite(&n, 2, 1, z->f); + fwrite(z->data[i].key, n, 1, z->f); + fwrite(&z->data[i].value_len, 4, 1, z->f); + fwrite(z->data[i].value, z->data[i].value_len, 1, z->f); + } + fclose(z->f); + } + stb_arr_free(z->data); + stb_free(z); +} + +int stb_cfg_read(stb_cfg *z, char *key, void *value, int len) +{ + int i; + for (i=0; i < stb_arr_len(z->data); ++i) { + if (!stb_stricmp(z->data[i].key, key)) { + int n = stb_min(len, z->data[i].value_len); + memcpy(value, z->data[i].value, n); + if (n < len) + *((char *) value + n) = 0; + return 1; + } + } + return 0; +} + +void stb_cfg_write(stb_cfg *z, char *key, void *value, int len) +{ + int i; + for (i=0; i < stb_arr_len(z->data); ++i) + if (!stb_stricmp(z->data[i].key, key)) + break; + if (i == stb_arr_len(z->data)) { + stb__cfg_item p; + p.key = stb_strdup(key, z); + p.value = NULL; + p.value_len = 0; + stb_arr_push(z->data, p); + } + z->data[i].value = stb_malloc(z, len); + z->data[i].value_len = len; + memcpy(z->data[i].value, value, len); +} + +int stb_cfg_delete(stb_cfg *z, char *key) +{ + int i; + for (i=0; i < stb_arr_len(z->data); ++i) + if (!stb_stricmp(z->data[i].key, key)) { + stb_arr_fastdelete(z->data, i); + return 1; + } + return 0; +} + +int stb_cfg_read_string(stb_cfg *z, char *key, char *value, int len) +{ + if (!stb_cfg_read(z, key, value, len)) return 0; + value[len-1] = 0; + return 1; +} + +void stb_cfg_write_string(stb_cfg *z, char *key, char *value) +{ + stb_cfg_write(z, key, value, (int) strlen(value)+1); +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// stb_dirtree - load a description of a directory tree +// uses a cache and stat()s the directories for changes +// MUCH faster on NTFS, _wrong_ on FAT32, so should +// ignore the db on FAT32 + +#ifdef _WIN32 + +typedef struct +{ + char * path; // full path from passed-in root + time_t last_modified; + int num_files; + int flag; +} stb_dirtree_dir; + +typedef struct +{ + char *name; // name relative to path + int dir; // index into dirs[] array + stb_int64 size; // size, max 4GB + time_t last_modified; + int flag; +} stb_dirtree_file; + +typedef struct +{ + stb_dirtree_dir *dirs; + stb_dirtree_file *files; + + // internal use + void * string_pool; // used to free data en masse +} stb_dirtree; + +extern void stb_dirtree_free ( stb_dirtree *d ); +extern stb_dirtree *stb_dirtree_get ( char *dir); +extern stb_dirtree *stb_dirtree_get_dir ( char *dir, char *cache_dir); +extern stb_dirtree *stb_dirtree_get_with_file ( char *dir, char *cache_file); + +// get a list of all the files recursively underneath 'dir' +// +// cache_file is used to store a copy of the directory tree to speed up +// later calls. It must be unique to 'dir' and the current working +// directory! Otherwise who knows what will happen (a good solution +// is to put it _in_ dir, but this API doesn't force that). +// +// Also, it might be possible to break this if you have two different processes +// do a call to stb_dirtree_get() with the same cache file at about the same +// time, but I _think_ it might just work. + +// i needed to build an identical data structure representing the state of +// a mirrored copy WITHOUT bothering to rescan it (i.e. we're mirroring to +// it WITHOUT scanning it, e.g. it's over the net), so this requires access +// to all of the innards. +extern void stb_dirtree_db_add_dir(stb_dirtree *active, char *path, time_t last); +extern void stb_dirtree_db_add_file(stb_dirtree *active, char *name, int dir, stb_int64 size, time_t last); +extern void stb_dirtree_db_read(stb_dirtree *target, char *filename, char *dir); +extern void stb_dirtree_db_write(stb_dirtree *target, char *filename, char *dir); + +#ifdef STB_DEFINE +static void stb__dirtree_add_dir(char *path, time_t last, stb_dirtree *active) +{ + stb_dirtree_dir d; + d.last_modified = last; + d.num_files = 0; + d.path = stb_strdup(path, active->string_pool); + stb_arr_push(active->dirs, d); +} + +static void stb__dirtree_add_file(char *name, int dir, stb_int64 size, time_t last, stb_dirtree *active) +{ + stb_dirtree_file f; + f.dir = dir; + f.size = size; + f.last_modified = last; + f.name = stb_strdup(name, active->string_pool); + ++active->dirs[dir].num_files; + stb_arr_push(active->files, f); +} + +// version 02 supports > 4GB files +static char stb__signature[12] = { 's', 'T', 'b', 'D', 'i', 'R', 't', 'R', 'e', 'E', '0', '2' }; + +static void stb__dirtree_save_db(char *filename, stb_dirtree *data, char *root) +{ + int i, num_dirs_final=0, num_files_final; + char *info = root ? root : (char*)""; + int *remap; + FILE *f = stb_p_fopen(filename, "wb"); + if (!f) return; + + fwrite(stb__signature, sizeof(stb__signature), 1, f); + fwrite(info, strlen(info)+1, 1, f); + // need to be slightly tricky and not write out NULLed directories, nor the root + + // build remapping table of all dirs we'll be writing out + remap = (int *) malloc(sizeof(remap[0]) * stb_arr_len(data->dirs)); + for (i=0; i < stb_arr_len(data->dirs); ++i) { + if (data->dirs[i].path == NULL || (root && 0==stb_stricmp(data->dirs[i].path, root))) { + remap[i] = -1; + } else { + remap[i] = num_dirs_final++; + } + } + + fwrite(&num_dirs_final, 4, 1, f); + for (i=0; i < stb_arr_len(data->dirs); ++i) { + if (remap[i] >= 0) { + fwrite(&data->dirs[i].last_modified, 4, 1, f); + stb_fput_string(f, data->dirs[i].path); + } + } + + num_files_final = 0; + for (i=0; i < stb_arr_len(data->files); ++i) + if (remap[data->files[i].dir] >= 0 && data->files[i].name) + ++num_files_final; + + fwrite(&num_files_final, 4, 1, f); + for (i=0; i < stb_arr_len(data->files); ++i) { + if (remap[data->files[i].dir] >= 0 && data->files[i].name) { + stb_fput_ranged(f, remap[data->files[i].dir], 0, num_dirs_final); + stb_fput_varlen64(f, data->files[i].size); + fwrite(&data->files[i].last_modified, 4, 1, f); + stb_fput_string(f, data->files[i].name); + } + } + + fclose(f); +} + +// note: stomps any existing data, rather than appending +static void stb__dirtree_load_db(char *filename, stb_dirtree *data, char *dir) +{ + char sig[2048]; + int i,n; + FILE *f = stb_p_fopen(filename, "rb"); + + if (!f) return; + + data->string_pool = stb_malloc(0,1); + + fread(sig, sizeof(stb__signature), 1, f); + if (memcmp(stb__signature, sig, sizeof(stb__signature))) { fclose(f); return; } + if (!fread(sig, strlen(dir)+1, 1, f)) { fclose(f); return; } + if (stb_stricmp(sig,dir)) { fclose(f); return; } + + // we can just read them straight in, because they're guaranteed to be valid + fread(&n, 4, 1, f); + stb_arr_setlen(data->dirs, n); + for(i=0; i < stb_arr_len(data->dirs); ++i) { + fread(&data->dirs[i].last_modified, 4, 1, f); + data->dirs[i].path = stb_fget_string(f, data->string_pool); + if (data->dirs[i].path == NULL) goto bail; + } + fread(&n, 4, 1, f); + stb_arr_setlen(data->files, n); + for (i=0; i < stb_arr_len(data->files); ++i) { + data->files[i].dir = stb_fget_ranged(f, 0, stb_arr_len(data->dirs)); + data->files[i].size = stb_fget_varlen64(f); + fread(&data->files[i].last_modified, 4, 1, f); + data->files[i].name = stb_fget_string(f, data->string_pool); + if (data->files[i].name == NULL) goto bail; + } + + if (0) { + bail: + stb_arr_free(data->dirs); + stb_arr_free(data->files); + } + fclose(f); +} + +FILE *hlog; + +static int stb__dircount, stb__dircount_mask, stb__showfile; +static void stb__dirtree_scandir(char *path, time_t last_time, stb_dirtree *active) +{ + // this is dumb depth first; theoretically it might be faster + // to fully traverse each directory before visiting its children, + // but it's complicated and didn't seem like a gain in the test app + + int n; + + struct _wfinddatai64_t c_file; + long hFile; + stb__wchar full_path[1024]; + int has_slash; + if (stb__showfile) printf("<"); + + has_slash = (path[0] && path[strlen(path)-1] == '/'); + + // @TODO: do this concatenation without using swprintf to avoid this mess: +#if (defined(_MSC_VER) && _MSC_VER < 1400) // || (defined(__clang__)) + // confusingly, Windows Kits\10 needs to go down this path?!? + // except now it doesn't, I don't know what changed + if (has_slash) + swprintf(full_path, L"%s*", stb__from_utf8(path)); + else + swprintf(full_path, L"%s/*", stb__from_utf8(path)); +#else + if (has_slash) + swprintf((wchar_t *) full_path, (size_t) 1024, L"%s*", (wchar_t *) stb__from_utf8(path)); + else + swprintf((wchar_t *) full_path, (size_t) 1024, L"%s/*", (wchar_t *) stb__from_utf8(path)); +#endif + + // it's possible this directory is already present: that means it was in the + // cache, but its parent wasn't... in that case, we're done with it + if (stb__showfile) printf("C[%d]", stb_arr_len(active->dirs)); + for (n=0; n < stb_arr_len(active->dirs); ++n) + if (0 == stb_stricmp(active->dirs[n].path, path)) { + if (stb__showfile) printf("D"); + return; + } + if (stb__showfile) printf("E"); + + // otherwise, we need to add it + stb__dirtree_add_dir(path, last_time, active); + n = stb_arr_lastn(active->dirs); + + if (stb__showfile) printf("["); + if( (hFile = (long) _wfindfirsti64( (wchar_t *) full_path, &c_file )) != -1L ) { + do { + if (stb__showfile) printf(")"); + if (c_file.attrib & _A_SUBDIR) { + // ignore subdirectories starting with '.', e.g. "." and ".." + if (c_file.name[0] != '.') { + char *new_path = (char *) full_path; + char *temp = stb__to_utf8((stb__wchar *) c_file.name); + + if (has_slash) + stb_p_sprintf(new_path stb_p_size(sizeof(full_path)), "%s%s", path, temp); + else + stb_p_sprintf(new_path stb_p_size(sizeof(full_path)), "%s/%s", path, temp); + + if (stb__dircount_mask) { + ++stb__dircount; + if (!(stb__dircount & stb__dircount_mask)) { + char dummy_path[128], *pad; + stb_strncpy(dummy_path, new_path, sizeof(dummy_path)-1); + if (strlen(dummy_path) > 96) { + stb_p_strcpy_s(dummy_path+96/2-1,128, "..."); + stb_p_strcpy_s(dummy_path+96/2+2,128, new_path + strlen(new_path)-96/2+2); + } + pad = dummy_path + strlen(dummy_path); + while (pad < dummy_path+98) + *pad++ = ' '; + *pad = 0; + printf("%s\r", dummy_path); + #if 0 + if (hlog == 0) { + hlog = stb_p_fopen("c:/x/temp.log", "w"); + fprintf(hlog, "%s\n", dummy_path); + } + #endif + } + } + + stb__dirtree_scandir(new_path, c_file.time_write, active); + } + } else { + char *temp = stb__to_utf8((stb__wchar *) c_file.name); + stb__dirtree_add_file(temp, n, c_file.size, c_file.time_write, active); + } + if (stb__showfile) printf("("); + } while( _wfindnexti64( hFile, &c_file ) == 0 ); + if (stb__showfile) printf("]"); + _findclose( hFile ); + } + if (stb__showfile) printf(">\n"); +} + +// scan the database and see if it's all valid +static int stb__dirtree_update_db(stb_dirtree *db, stb_dirtree *active) +{ + int changes_detected = STB_FALSE; + int i; + int *remap; + int *rescan=NULL; + remap = (int *) malloc(sizeof(remap[0]) * stb_arr_len(db->dirs)); + memset(remap, 0, sizeof(remap[0]) * stb_arr_len(db->dirs)); + rescan = NULL; + + for (i=0; i < stb_arr_len(db->dirs); ++i) { + struct _stat info; + if (stb__dircount_mask) { + ++stb__dircount; + if (!(stb__dircount & stb__dircount_mask)) { + printf("."); + } + } + if (0 == _stat(db->dirs[i].path, &info)) { + if (info.st_mode & _S_IFDIR) { + // it's still a directory, as expected + int n = abs((int) (info.st_mtime - db->dirs[i].last_modified)); + if (n > 1 && n != 3600) { // the 3600 is a hack because sometimes this jumps for no apparent reason, even when no time zone or DST issues are at play + // it's changed! force a rescan + // we don't want to scan it until we've stat()d its + // subdirs, though, so we queue it + if (stb__showfile) printf("Changed: %s - %08x:%08x\n", db->dirs[i].path, (unsigned int) db->dirs[i].last_modified, (unsigned int) info.st_mtime); + stb_arr_push(rescan, i); + // update the last_mod time + db->dirs[i].last_modified = info.st_mtime; + // ignore existing files in this dir + remap[i] = -1; + changes_detected = STB_TRUE; + } else { + // it hasn't changed, just copy it through unchanged + stb__dirtree_add_dir(db->dirs[i].path, db->dirs[i].last_modified, active); + remap[i] = stb_arr_lastn(active->dirs); + } + } else { + // this path used to refer to a directory, but now it's a file! + // assume that the parent directory is going to be forced to rescan anyway + goto delete_entry; + } + } else { + delete_entry: + // directory no longer exists, so don't copy it + // we don't free it because it's in the string pool now + db->dirs[i].path = NULL; + remap[i] = -1; + changes_detected = STB_TRUE; + } + } + + // at this point, we have: + // + // holds a list of directory indices that need to be scanned due to being out of date + // holds the directory index in for each dir in , if it exists; -1 if not + // directories in are not in yet + + // so we can go ahead and remap all the known files right now + for (i=0; i < stb_arr_len(db->files); ++i) { + int dir = db->files[i].dir; + if (remap[dir] >= 0) { + stb__dirtree_add_file(db->files[i].name, remap[dir], db->files[i].size, db->files[i].last_modified, active); + } + } + + // at this point we're done with db->files, and done with remap + free(remap); + + // now scan those directories using the standard scan + for (i=0; i < stb_arr_len(rescan); ++i) { + int z = rescan[i]; + stb__dirtree_scandir(db->dirs[z].path, db->dirs[z].last_modified, active); + } + stb_arr_free(rescan); + + return changes_detected; +} + +static void stb__dirtree_free_raw(stb_dirtree *d) +{ + stb_free(d->string_pool); + stb_arr_free(d->dirs); + stb_arr_free(d->files); +} + +stb_dirtree *stb_dirtree_get_with_file(char *dir, char *cache_file) +{ + stb_dirtree *output = (stb_dirtree *) malloc(sizeof(*output)); + stb_dirtree db,active; + int prev_dir_count, cache_mismatch; + + char *stripped_dir; // store the directory name without a trailing '/' or '\\' + + // load the database of last-known state on disk + db.string_pool = NULL; + db.files = NULL; + db.dirs = NULL; + + stripped_dir = stb_strip_final_slash(stb_p_strdup(dir)); + + if (cache_file != NULL) + stb__dirtree_load_db(cache_file, &db, stripped_dir); + else if (stb__showfile) + printf("No cache file\n"); + + active.files = NULL; + active.dirs = NULL; + active.string_pool = stb_malloc(0,1); // @TODO: share string pools between both? + + // check all the directories in the database; make note if + // anything we scanned had changed, and rescan those things + cache_mismatch = stb__dirtree_update_db(&db, &active); + + // check the root tree + prev_dir_count = stb_arr_len(active.dirs); // record how many directories we've seen + + stb__dirtree_scandir(stripped_dir, 0, &active); // no last_modified time available for root + + if (stb__dircount_mask) + printf(" \r"); + + // done with the DB; write it back out if any changes, i.e. either + // 1. any inconsistency found between cached information and actual disk + // or 2. if scanning the root found any new directories--which we detect because + // more than one directory got added to the active db during that scan + if (cache_mismatch || stb_arr_len(active.dirs) > prev_dir_count+1) + stb__dirtree_save_db(cache_file, &active, stripped_dir); + + free(stripped_dir); + + stb__dirtree_free_raw(&db); + + *output = active; + return output; +} + +stb_dirtree *stb_dirtree_get_dir(char *dir, char *cache_dir) +{ + int i; + stb_uint8 sha[20]; + char dir_lower[1024]; + char cache_file[1024],*s; + if (cache_dir == NULL) + return stb_dirtree_get_with_file(dir, NULL); + stb_p_strcpy_s(dir_lower, sizeof(dir_lower), dir); + stb_tolower(dir_lower); + stb_sha1(sha, (unsigned char *) dir_lower, (unsigned int) strlen(dir_lower)); + stb_p_strcpy_s(cache_file, sizeof(cache_file), cache_dir); + s = cache_file + strlen(cache_file); + if (s[-1] != '/' && s[-1] != '\\') *s++ = '/'; + stb_p_strcpy_s(s, sizeof(cache_file), "dirtree_"); + s += strlen(s); + for (i=0; i < 8; ++i) { + char *hex = (char*)"0123456789abcdef"; + stb_uint z = sha[i]; + *s++ = hex[z >> 4]; + *s++ = hex[z & 15]; + } + stb_p_strcpy_s(s, sizeof(cache_file), ".bin"); + return stb_dirtree_get_with_file(dir, cache_file); +} + +stb_dirtree *stb_dirtree_get(char *dir) +{ + char cache_dir[256]; + stb_p_strcpy_s(cache_dir, sizeof(cache_dir), "c:/bindata"); + #ifdef STB_HAS_REGISTRY + { + void *reg = stb_reg_open("rHKLM", "Software\\SilverSpaceship\\stb"); + if (reg) { + stb_reg_read(reg, "dirtree", cache_dir, sizeof(cache_dir)); + stb_reg_close(reg); + } + } + #endif + return stb_dirtree_get_dir(dir, cache_dir); +} + +void stb_dirtree_free(stb_dirtree *d) +{ + stb__dirtree_free_raw(d); + free(d); +} + +void stb_dirtree_db_add_dir(stb_dirtree *active, char *path, time_t last) +{ + stb__dirtree_add_dir(path, last, active); +} + +void stb_dirtree_db_add_file(stb_dirtree *active, char *name, int dir, stb_int64 size, time_t last) +{ + stb__dirtree_add_file(name, dir, size, last, active); +} + +void stb_dirtree_db_read(stb_dirtree *target, char *filename, char *dir) +{ + char *s = stb_strip_final_slash(stb_p_strdup(dir)); + target->dirs = 0; + target->files = 0; + target->string_pool = 0; + stb__dirtree_load_db(filename, target, s); + free(s); +} + +void stb_dirtree_db_write(stb_dirtree *target, char *filename, char *dir) +{ + stb__dirtree_save_db(filename, target, 0); // don't strip out any directories +} + +#endif // STB_DEFINE + +#endif // _WIN32 +#endif // STB_NO_STB_STRINGS + +////////////////////////////////////////////////////////////////////////////// +// +// STB_MALLOC_WRAPPER +// +// you can use the wrapper functions with your own malloc wrapper, +// or define STB_MALLOC_WRAPPER project-wide to have +// malloc/free/realloc/strdup all get vectored to it + +// this has too many very specific error messages you could google for and find in stb.h, +// so don't use it if they don't want any stb.h-identifiable strings +#if defined(STB_DEFINE) && !defined(STB_NO_STB_STRINGS) + +typedef struct +{ + void *p; + char *file; + int line; + size_t size; +} stb_malloc_record; + +#ifndef STB_MALLOC_HISTORY_COUNT +#define STB_MALLOC_HISTORY_COUNT 50 // 800 bytes +#endif + +stb_malloc_record *stb__allocations; +static int stb__alloc_size, stb__alloc_limit, stb__alloc_mask; +int stb__alloc_count; + +stb_malloc_record stb__alloc_history[STB_MALLOC_HISTORY_COUNT]; +int stb__history_pos; + +static int stb__hashfind(void *p) +{ + stb_uint32 h = stb_hashptr(p); + int s,n = h & stb__alloc_mask; + if (stb__allocations[n].p == p) + return n; + s = stb_rehash(h)|1; + for(;;) { + if (stb__allocations[n].p == NULL) + return -1; + n = (n+s) & stb__alloc_mask; + if (stb__allocations[n].p == p) + return n; + } +} + +size_t stb_wrapper_allocsize(void *p) +{ + int n = stb__hashfind(p); + if (n < 0) return 0; + return stb__allocations[n].size; +} + +static int stb__historyfind(void *p) +{ + int n = stb__history_pos; + int i; + for (i=0; i < STB_MALLOC_HISTORY_COUNT; ++i) { + if (--n < 0) n = STB_MALLOC_HISTORY_COUNT-1; + if (stb__alloc_history[n].p == p) + return n; + } + return -1; +} + +static void stb__add_alloc(void *p, size_t sz, char *file, int line); +static void stb__grow_alloc(void) +{ + int i,old_num = stb__alloc_size; + stb_malloc_record *old = stb__allocations; + if (stb__alloc_size == 0) + stb__alloc_size = 64; + else + stb__alloc_size *= 2; + + stb__allocations = (stb_malloc_record *) stb__realloc_raw(NULL, stb__alloc_size * sizeof(stb__allocations[0])); + if (stb__allocations == NULL) + stb_fatal("Internal error: couldn't grow malloc wrapper table"); + memset(stb__allocations, 0, stb__alloc_size * sizeof(stb__allocations[0])); + stb__alloc_limit = (stb__alloc_size*3)>>2; + stb__alloc_mask = stb__alloc_size-1; + + stb__alloc_count = 0; + + for (i=0; i < old_num; ++i) + if (old[i].p > STB_DEL) { + stb__add_alloc(old[i].p, old[i].size, old[i].file, old[i].line); + assert(stb__hashfind(old[i].p) >= 0); + } + for (i=0; i < old_num; ++i) + if (old[i].p > STB_DEL) + assert(stb__hashfind(old[i].p) >= 0); + stb__realloc_raw(old, 0); +} + +static void stb__add_alloc(void *p, size_t sz, char *file, int line) +{ + stb_uint32 h; + int n; + if (stb__alloc_count >= stb__alloc_limit) + stb__grow_alloc(); + h = stb_hashptr(p); + n = h & stb__alloc_mask; + if (stb__allocations[n].p > STB_DEL) { + int s = stb_rehash(h)|1; + do { + n = (n+s) & stb__alloc_mask; + } while (stb__allocations[n].p > STB_DEL); + } + assert(stb__allocations[n].p == NULL || stb__allocations[n].p == STB_DEL); + stb__allocations[n].p = p; + stb__allocations[n].size = sz; + stb__allocations[n].line = line; + stb__allocations[n].file = file; + ++stb__alloc_count; +} + +static void stb__remove_alloc(int n, char *file, int line) +{ + stb__alloc_history[stb__history_pos] = stb__allocations[n]; + stb__alloc_history[stb__history_pos].file = file; + stb__alloc_history[stb__history_pos].line = line; + if (++stb__history_pos == STB_MALLOC_HISTORY_COUNT) + stb__history_pos = 0; + stb__allocations[n].p = STB_DEL; + --stb__alloc_count; +} + +void stb_wrapper_malloc(void *p, size_t sz, char *file, int line) +{ + if (!p) return; + stb__add_alloc(p,sz,file,line); +} + +void stb_wrapper_free(void *p, char *file, int line) +{ + int n; + + if (p == NULL) return; + + n = stb__hashfind(p); + + if (n >= 0) + stb__remove_alloc(n, file, line); + else { + // tried to free something we hadn't allocated! + n = stb__historyfind(p); + assert(0); /* NOTREACHED */ + if (n >= 0) + stb_fatal("Attempted to free %d-byte block %p at %s:%d previously freed/realloced at %s:%d", + stb__alloc_history[n].size, p, + file, line, + stb__alloc_history[n].file, stb__alloc_history[n].line); + else + stb_fatal("Attempted to free unknown block %p at %s:%d", p, file,line); + } +} + +void stb_wrapper_check(void *p) +{ + int n; + + if (p == NULL) return; + + n = stb__hashfind(p); + + if (n >= 0) return; + + for (n=0; n < stb__alloc_size; ++n) + if (stb__allocations[n].p == p) + stb_fatal("Internal error: pointer %p was allocated, but hash search failed", p); + + // tried to free something that wasn't allocated! + n = stb__historyfind(p); + if (n >= 0) + stb_fatal("Checked %d-byte block %p previously freed/realloced at %s:%d", + stb__alloc_history[n].size, p, + stb__alloc_history[n].file, stb__alloc_history[n].line); + stb_fatal("Checked unknown block %p"); +} + +void stb_wrapper_realloc(void *p, void *q, size_t sz, char *file, int line) +{ + int n; + if (p == NULL) { stb_wrapper_malloc(q, sz, file, line); return; } + if (q == NULL) return; // nothing happened + + n = stb__hashfind(p); + if (n == -1) { + // tried to free something we hadn't allocated! + // this is weird, though, because we got past the realloc! + n = stb__historyfind(p); + assert(0); /* NOTREACHED */ + if (n >= 0) + stb_fatal("Attempted to realloc %d-byte block %p at %s:%d previously freed/realloced at %s:%d", + stb__alloc_history[n].size, p, + file, line, + stb__alloc_history[n].file, stb__alloc_history[n].line); + else + stb_fatal("Attempted to realloc unknown block %p at %s:%d", p, file,line); + } else { + if (q == p) { + stb__allocations[n].size = sz; + stb__allocations[n].file = file; + stb__allocations[n].line = line; + } else { + stb__remove_alloc(n, file, line); + stb__add_alloc(q,sz,file,line); + } + } +} + +void stb_wrapper_listall(void (*func)(void *ptr, size_t sz, char *file, int line)) +{ + int i; + for (i=0; i < stb__alloc_size; ++i) + if (stb__allocations[i].p > STB_DEL) + func(stb__allocations[i].p , stb__allocations[i].size, + stb__allocations[i].file, stb__allocations[i].line); +} + +void stb_wrapper_dump(char *filename) +{ + int i; + FILE *f = stb_p_fopen(filename, "w"); + if (!f) return; + for (i=0; i < stb__alloc_size; ++i) + if (stb__allocations[i].p > STB_DEL) + fprintf(f, "%p %7d - %4d %s\n", + stb__allocations[i].p , (int) stb__allocations[i].size, + stb__allocations[i].line, stb__allocations[i].file); +} +#endif // STB_DEFINE + + +////////////////////////////////////////////////////////////////////////////// +// +// stb_pointer_set +// +// +// For data structures that support querying by key, data structure +// classes always hand-wave away the issue of what to do if two entries +// have the same key: basically, store a linked list of all the nodes +// which have the same key (a LISP-style list). +// +// The thing is, it's not that trivial. If you have an O(log n) +// lookup data structure, but then n/4 items have the same value, +// you don't want to spend O(n) time scanning that list when +// deleting an item if you already have a pointer to the item. +// (You have to spend O(n) time enumerating all the items with +// a given key, sure, and you can't accelerate deleting a particular +// item if you only have the key, not a pointer to the item.) +// +// I'm going to call this data structure, whatever it turns out to +// be, a "pointer set", because we don't store any associated data for +// items in this data structure, we just answer the question of +// whether an item is in it or not (it's effectively one bit per pointer). +// Technically they don't have to be pointers; you could cast ints +// to (void *) if you want, but you can't store 0 or 1 because of the +// hash table. +// +// Since the fastest data structure we might want to add support for +// identical-keys to is a hash table with O(1)-ish lookup time, +// that means that the conceptual "linked list of all items with +// the same indexed value" that we build needs to have the same +// performance; that way when we index a table we think is arbitrary +// ints, but in fact half of them are 0, we don't get screwed. +// +// Therefore, it needs to be a hash table, at least when it gets +// large. On the other hand, when the data has totally arbitrary ints +// or floats, there won't be many collisions, and we'll have tons of +// 1-item bitmaps. That will be grossly inefficient as hash tables; +// trade-off; the hash table is reasonably efficient per-item when +// it's large, but not when it's small. So we need to do something +// Judy-like and use different strategies depending on the size. +// +// Like Judy, we'll use the bottom bit to encode the strategy: +// +// bottom bits: +// 00 - direct pointer +// 01 - 4-item bucket (16 bytes, no length, NULLs) +// 10 - N-item array +// 11 - hash table + +typedef struct stb_ps stb_ps; + +STB_EXTERN int stb_ps_find (stb_ps *ps, void *value); +STB_EXTERN stb_ps * stb_ps_add (stb_ps *ps, void *value); +STB_EXTERN stb_ps * stb_ps_remove(stb_ps *ps, void *value); +STB_EXTERN stb_ps * stb_ps_remove_any(stb_ps *ps, void **value); +STB_EXTERN void stb_ps_delete(stb_ps *ps); +STB_EXTERN int stb_ps_count (stb_ps *ps); + +STB_EXTERN stb_ps * stb_ps_copy (stb_ps *ps); +STB_EXTERN int stb_ps_subset(stb_ps *bigger, stb_ps *smaller); +STB_EXTERN int stb_ps_eq (stb_ps *p0, stb_ps *p1); + +STB_EXTERN void ** stb_ps_getlist (stb_ps *ps, int *count); +STB_EXTERN int stb_ps_writelist(stb_ps *ps, void **list, int size ); + +// enum and fastlist don't allocate storage, but you must consume the +// list before there's any chance the data structure gets screwed up; +STB_EXTERN int stb_ps_enum (stb_ps *ps, void *data, + int (*func)(void *value, void*data) ); +STB_EXTERN void ** stb_ps_fastlist(stb_ps *ps, int *count); +// result: +// returns a list, *count is the length of that list, +// but some entries of the list may be invalid; +// test with 'stb_ps_fastlist_valid(x)' + +#define stb_ps_fastlist_valid(x) ((stb_uinta) (x) > 1) + +#ifdef STB_DEFINE + +enum +{ + STB_ps_direct = 0, + STB_ps_bucket = 1, + STB_ps_array = 2, + STB_ps_hash = 3, +}; + +#define STB_BUCKET_SIZE 4 + +typedef struct +{ + void *p[STB_BUCKET_SIZE]; +} stb_ps_bucket; +#define GetBucket(p) ((stb_ps_bucket *) ((char *) (p) - STB_ps_bucket)) +#define EncodeBucket(p) ((stb_ps *) ((char *) (p) + STB_ps_bucket)) + +static void stb_bucket_free(stb_ps_bucket *b) +{ + free(b); +} + +static stb_ps_bucket *stb_bucket_create2(void *v0, void *v1) +{ + stb_ps_bucket *b = (stb_ps_bucket*) malloc(sizeof(*b)); + b->p[0] = v0; + b->p[1] = v1; + b->p[2] = NULL; + b->p[3] = NULL; + return b; +} + +static stb_ps_bucket * stb_bucket_create3(void **v) +{ + stb_ps_bucket *b = (stb_ps_bucket*) malloc(sizeof(*b)); + b->p[0] = v[0]; + b->p[1] = v[1]; + b->p[2] = v[2]; + b->p[3] = NULL; + return b; +} + + +// could use stb_arr, but this will save us memory +typedef struct +{ + int count; + void *p[1]; +} stb_ps_array; +#define GetArray(p) ((stb_ps_array *) ((char *) (p) - STB_ps_array)) +#define EncodeArray(p) ((stb_ps *) ((char *) (p) + STB_ps_array)) + +static int stb_ps_array_max = 13; + +typedef struct +{ + int size, mask; + int count, count_deletes; + int grow_threshhold; + int shrink_threshhold; + int rehash_threshhold; + int any_offset; + void *table[1]; +} stb_ps_hash; +#define GetHash(p) ((stb_ps_hash *) ((char *) (p) - STB_ps_hash)) +#define EncodeHash(p) ((stb_ps *) ((char *) (p) + STB_ps_hash)) + +#define stb_ps_empty(v) (((stb_uint32) v) <= 1) + +static stb_ps_hash *stb_ps_makehash(int size, int old_size, void **old_data) +{ + int i; + stb_ps_hash *h = (stb_ps_hash *) malloc(sizeof(*h) + (size-1) * sizeof(h->table[0])); + assert(stb_is_pow2(size)); + h->size = size; + h->mask = size-1; + h->shrink_threshhold = (int) (0.3f * size); + h-> grow_threshhold = (int) (0.8f * size); + h->rehash_threshhold = (int) (0.9f * size); + h->count = 0; + h->count_deletes = 0; + h->any_offset = 0; + memset(h->table, 0, size * sizeof(h->table[0])); + for (i=0; i < old_size; ++i) + if (!stb_ps_empty((size_t)old_data[i])) + stb_ps_add(EncodeHash(h), old_data[i]); + return h; +} + +void stb_ps_delete(stb_ps *ps) +{ + switch (3 & (int)(size_t) ps) { + case STB_ps_direct: break; + case STB_ps_bucket: stb_bucket_free(GetBucket(ps)); break; + case STB_ps_array : free(GetArray(ps)); break; + case STB_ps_hash : free(GetHash(ps)); break; + } +} + +stb_ps *stb_ps_copy(stb_ps *ps) +{ + int i; + // not a switch: order based on expected performance/power-law distribution + switch (3 & (int)(size_t) ps) { + case STB_ps_direct: return ps; + case STB_ps_bucket: { + stb_ps_bucket *n = (stb_ps_bucket *) malloc(sizeof(*n)); + *n = *GetBucket(ps); + return EncodeBucket(n); + } + case STB_ps_array: { + stb_ps_array *a = GetArray(ps); + stb_ps_array *n = (stb_ps_array *) malloc(sizeof(*n) + stb_ps_array_max * sizeof(n->p[0])); + n->count = a->count; + for (i=0; i < a->count; ++i) + n->p[i] = a->p[i]; + return EncodeArray(n); + } + case STB_ps_hash: { + stb_ps_hash *h = GetHash(ps); + stb_ps_hash *n = stb_ps_makehash(h->size, h->size, h->table); + return EncodeHash(n); + } + } + assert(0); /* NOTREACHED */ + return NULL; +} + +int stb_ps_find(stb_ps *ps, void *value) +{ + int i, code = 3 & (int)(size_t) ps; + assert((3 & (int)(size_t) value) == STB_ps_direct); + assert(stb_ps_fastlist_valid(value)); + // not a switch: order based on expected performance/power-law distribution + if (code == STB_ps_direct) + return value == ps; + if (code == STB_ps_bucket) { + stb_ps_bucket *b = GetBucket(ps); + assert(STB_BUCKET_SIZE == 4); + if (b->p[0] == value || b->p[1] == value || + b->p[2] == value || b->p[3] == value) + return STB_TRUE; + return STB_FALSE; + } + if (code == STB_ps_array) { + stb_ps_array *a = GetArray(ps); + for (i=0; i < a->count; ++i) + if (a->p[i] == value) + return STB_TRUE; + return STB_FALSE; + } else { + stb_ps_hash *h = GetHash(ps); + stb_uint32 hash = stb_hashptr(value); + stb_uint32 s, n = hash & h->mask; + void **t = h->table; + if (t[n] == value) return STB_TRUE; + if (t[n] == NULL) return STB_FALSE; + s = stb_rehash(hash) | 1; + do { + n = (n + s) & h->mask; + if (t[n] == value) return STB_TRUE; + } while (t[n] != NULL); + return STB_FALSE; + } +} + +stb_ps * stb_ps_add (stb_ps *ps, void *value) +{ + #ifdef STB_DEBUG + assert(!stb_ps_find(ps,value)); + #endif + if (value == NULL) return ps; // ignore NULL adds to avoid bad breakage + assert((3 & (int)(size_t) value) == STB_ps_direct); + assert(stb_ps_fastlist_valid(value)); + assert(value != STB_DEL); // STB_DEL is less likely + + switch (3 & (int)(size_t) ps) { + case STB_ps_direct: + if (ps == NULL) return (stb_ps *) value; + return EncodeBucket(stb_bucket_create2(ps,value)); + + case STB_ps_bucket: { + stb_ps_bucket *b = GetBucket(ps); + stb_ps_array *a; + assert(STB_BUCKET_SIZE == 4); + if (b->p[0] == NULL) { b->p[0] = value; return ps; } + if (b->p[1] == NULL) { b->p[1] = value; return ps; } + if (b->p[2] == NULL) { b->p[2] = value; return ps; } + if (b->p[3] == NULL) { b->p[3] = value; return ps; } + a = (stb_ps_array *) malloc(sizeof(*a) + 7 * sizeof(a->p[0])); // 8 slots, must be 2^k + memcpy(a->p, b, sizeof(*b)); + a->p[4] = value; + a->count = 5; + stb_bucket_free(b); + return EncodeArray(a); + } + + case STB_ps_array: { + stb_ps_array *a = GetArray(ps); + if (a->count == stb_ps_array_max) { + // promote from array to hash + stb_ps_hash *h = stb_ps_makehash(2 << stb_log2_ceil(a->count), a->count, a->p); + free(a); + return stb_ps_add(EncodeHash(h), value); + } + // do we need to resize the array? the array doubles in size when it + // crosses a power-of-two + if ((a->count & (a->count-1))==0) { + int newsize = a->count*2; + // clamp newsize to max if: + // 1. it's larger than max + // 2. newsize*1.5 is larger than max (to avoid extra resizing) + if (newsize + a->count > stb_ps_array_max) + newsize = stb_ps_array_max; + a = (stb_ps_array *) realloc(a, sizeof(*a) + (newsize-1) * sizeof(a->p[0])); + } + a->p[a->count++] = value; + return EncodeArray(a); + } + case STB_ps_hash: { + stb_ps_hash *h = GetHash(ps); + stb_uint32 hash = stb_hashptr(value); + stb_uint32 n = hash & h->mask; + void **t = h->table; + // find first NULL or STB_DEL entry + if (!stb_ps_empty((size_t)t[n])) { + stb_uint32 s = stb_rehash(hash) | 1; + do { + n = (n + s) & h->mask; + } while (!stb_ps_empty((size_t)t[n])); + } + if (t[n] == STB_DEL) + -- h->count_deletes; + t[n] = value; + ++ h->count; + if (h->count == h->grow_threshhold) { + stb_ps_hash *h2 = stb_ps_makehash(h->size*2, h->size, t); + free(h); + return EncodeHash(h2); + } + if (h->count + h->count_deletes == h->rehash_threshhold) { + stb_ps_hash *h2 = stb_ps_makehash(h->size, h->size, t); + free(h); + return EncodeHash(h2); + } + return ps; + } + } + return NULL; /* NOTREACHED */ +} + +stb_ps *stb_ps_remove(stb_ps *ps, void *value) +{ + #ifdef STB_DEBUG + assert(stb_ps_find(ps, value)); + #endif + assert((3 & (int)(size_t) value) == STB_ps_direct); + if (value == NULL) return ps; // ignore NULL removes to avoid bad breakage + switch (3 & (int)(size_t) ps) { + case STB_ps_direct: + return ps == value ? NULL : ps; + case STB_ps_bucket: { + stb_ps_bucket *b = GetBucket(ps); + int count=0; + assert(STB_BUCKET_SIZE == 4); + if (b->p[0] == value) b->p[0] = NULL; else count += (b->p[0] != NULL); + if (b->p[1] == value) b->p[1] = NULL; else count += (b->p[1] != NULL); + if (b->p[2] == value) b->p[2] = NULL; else count += (b->p[2] != NULL); + if (b->p[3] == value) b->p[3] = NULL; else count += (b->p[3] != NULL); + if (count == 1) { // shrink bucket at size 1 + value = b->p[0]; + if (value == NULL) value = b->p[1]; + if (value == NULL) value = b->p[2]; + if (value == NULL) value = b->p[3]; + assert(value != NULL); + stb_bucket_free(b); + return (stb_ps *) value; // return STB_ps_direct of value + } + return ps; + } + case STB_ps_array: { + stb_ps_array *a = GetArray(ps); + int i; + for (i=0; i < a->count; ++i) { + if (a->p[i] == value) { + a->p[i] = a->p[--a->count]; + if (a->count == 3) { // shrink to bucket! + stb_ps_bucket *b = stb_bucket_create3(a->p); + free(a); + return EncodeBucket(b); + } + return ps; + } + } + return ps; + } + case STB_ps_hash: { + stb_ps_hash *h = GetHash(ps); + stb_uint32 hash = stb_hashptr(value); + stb_uint32 s, n = hash & h->mask; + void **t = h->table; + if (t[n] != value) { + s = stb_rehash(hash) | 1; + do { + n = (n + s) & h->mask; + } while (t[n] != value); + } + t[n] = STB_DEL; + -- h->count; + ++ h->count_deletes; + // should we shrink down to an array? + if (h->count < stb_ps_array_max) { + int n = 1 << stb_log2_floor(stb_ps_array_max); + if (h->count < n) { + stb_ps_array *a = (stb_ps_array *) malloc(sizeof(*a) + (n-1) * sizeof(a->p[0])); + int i,j=0; + for (i=0; i < h->size; ++i) + if (!stb_ps_empty((size_t)t[i])) + a->p[j++] = t[i]; + assert(j == h->count); + a->count = j; + free(h); + return EncodeArray(a); + } + } + if (h->count == h->shrink_threshhold) { + stb_ps_hash *h2 = stb_ps_makehash(h->size >> 1, h->size, t); + free(h); + return EncodeHash(h2); + } + return ps; + } + } + return ps; /* NOTREACHED */ +} + +stb_ps *stb_ps_remove_any(stb_ps *ps, void **value) +{ + assert(ps != NULL); + switch (3 & (int)(size_t) ps) { + case STB_ps_direct: + *value = ps; + return NULL; + case STB_ps_bucket: { + stb_ps_bucket *b = GetBucket(ps); + int count=0, slast=0, last=0; + assert(STB_BUCKET_SIZE == 4); + if (b->p[0]) { ++count; last = 0; } + if (b->p[1]) { ++count; slast = last; last = 1; } + if (b->p[2]) { ++count; slast = last; last = 2; } + if (b->p[3]) { ++count; slast = last; last = 3; } + *value = b->p[last]; + b->p[last] = 0; + if (count == 2) { + void *leftover = b->p[slast]; // second to last + stb_bucket_free(b); + return (stb_ps *) leftover; + } + return ps; + } + case STB_ps_array: { + stb_ps_array *a = GetArray(ps); + *value = a->p[a->count-1]; + if (a->count == 4) + return stb_ps_remove(ps, *value); + --a->count; + return ps; + } + case STB_ps_hash: { + stb_ps_hash *h = GetHash(ps); + void **t = h->table; + stb_uint32 n = h->any_offset; + while (stb_ps_empty((size_t)t[n])) + n = (n + 1) & h->mask; + *value = t[n]; + h->any_offset = (n+1) & h->mask; + // check if we need to skip down to the previous type + if (h->count-1 < stb_ps_array_max || h->count-1 == h->shrink_threshhold) + return stb_ps_remove(ps, *value); + t[n] = STB_DEL; + -- h->count; + ++ h->count_deletes; + return ps; + } + } + return ps; /* NOTREACHED */ +} + + +void ** stb_ps_getlist(stb_ps *ps, int *count) +{ + int i,n=0; + void **p = NULL; + switch (3 & (int)(size_t) ps) { + case STB_ps_direct: + if (ps == NULL) { *count = 0; return NULL; } + p = (void **) malloc(sizeof(*p) * 1); + p[0] = ps; + *count = 1; + return p; + case STB_ps_bucket: { + stb_ps_bucket *b = GetBucket(ps); + p = (void **) malloc(sizeof(*p) * STB_BUCKET_SIZE); + for (i=0; i < STB_BUCKET_SIZE; ++i) + if (b->p[i] != NULL) + p[n++] = b->p[i]; + break; + } + case STB_ps_array: { + stb_ps_array *a = GetArray(ps); + p = (void **) malloc(sizeof(*p) * a->count); + memcpy(p, a->p, sizeof(*p) * a->count); + *count = a->count; + return p; + } + case STB_ps_hash: { + stb_ps_hash *h = GetHash(ps); + p = (void **) malloc(sizeof(*p) * h->count); + for (i=0; i < h->size; ++i) + if (!stb_ps_empty((size_t)h->table[i])) + p[n++] = h->table[i]; + break; + } + } + *count = n; + return p; +} + +int stb_ps_writelist(stb_ps *ps, void **list, int size ) +{ + int i,n=0; + switch (3 & (int)(size_t) ps) { + case STB_ps_direct: + if (ps == NULL || size <= 0) return 0; + list[0] = ps; + return 1; + case STB_ps_bucket: { + stb_ps_bucket *b = GetBucket(ps); + for (i=0; i < STB_BUCKET_SIZE; ++i) + if (b->p[i] != NULL && n < size) + list[n++] = b->p[i]; + return n; + } + case STB_ps_array: { + stb_ps_array *a = GetArray(ps); + n = stb_min(size, a->count); + memcpy(list, a->p, sizeof(*list) * n); + return n; + } + case STB_ps_hash: { + stb_ps_hash *h = GetHash(ps); + if (size <= 0) return 0; + for (i=0; i < h->count; ++i) { + if (!stb_ps_empty((size_t)h->table[i])) { + list[n++] = h->table[i]; + if (n == size) break; + } + } + return n; + } + } + return 0; /* NOTREACHED */ +} + +int stb_ps_enum(stb_ps *ps, void *data, int (*func)(void *value, void *data)) +{ + int i; + switch (3 & (int)(size_t) ps) { + case STB_ps_direct: + if (ps == NULL) return STB_TRUE; + return func(ps, data); + case STB_ps_bucket: { + stb_ps_bucket *b = GetBucket(ps); + for (i=0; i < STB_BUCKET_SIZE; ++i) + if (b->p[i] != NULL) + if (!func(b->p[i], data)) + return STB_FALSE; + return STB_TRUE; + } + case STB_ps_array: { + stb_ps_array *a = GetArray(ps); + for (i=0; i < a->count; ++i) + if (!func(a->p[i], data)) + return STB_FALSE; + return STB_TRUE; + } + case STB_ps_hash: { + stb_ps_hash *h = GetHash(ps); + for (i=0; i < h->count; ++i) + if (!stb_ps_empty((size_t)h->table[i])) + if (!func(h->table[i], data)) + return STB_FALSE; + return STB_TRUE; + } + } + return STB_TRUE; /* NOTREACHED */ +} + +int stb_ps_count (stb_ps *ps) +{ + switch (3 & (int)(size_t) ps) { + case STB_ps_direct: + return ps != NULL; + case STB_ps_bucket: { + stb_ps_bucket *b = GetBucket(ps); + return (b->p[0] != NULL) + (b->p[1] != NULL) + + (b->p[2] != NULL) + (b->p[3] != NULL); + } + case STB_ps_array: { + stb_ps_array *a = GetArray(ps); + return a->count; + } + case STB_ps_hash: { + stb_ps_hash *h = GetHash(ps); + return h->count; + } + } + return 0; +} + +void ** stb_ps_fastlist(stb_ps *ps, int *count) +{ + static void *storage; + + switch (3 & (int)(size_t) ps) { + case STB_ps_direct: + if (ps == NULL) { *count = 0; return NULL; } + storage = ps; + *count = 1; + return &storage; + case STB_ps_bucket: { + stb_ps_bucket *b = GetBucket(ps); + *count = STB_BUCKET_SIZE; + return b->p; + } + case STB_ps_array: { + stb_ps_array *a = GetArray(ps); + *count = a->count; + return a->p; + } + case STB_ps_hash: { + stb_ps_hash *h = GetHash(ps); + *count = h->size; + return h->table; + } + } + return NULL; /* NOTREACHED */ +} + +int stb_ps_subset(stb_ps *bigger, stb_ps *smaller) +{ + int i, listlen; + void **list = stb_ps_fastlist(smaller, &listlen); + for(i=0; i < listlen; ++i) + if (stb_ps_fastlist_valid(list[i])) + if (!stb_ps_find(bigger, list[i])) + return 0; + return 1; +} + +int stb_ps_eq(stb_ps *p0, stb_ps *p1) +{ + if (stb_ps_count(p0) != stb_ps_count(p1)) + return 0; + return stb_ps_subset(p0, p1); +} + +#undef GetBucket +#undef GetArray +#undef GetHash + +#undef EncodeBucket +#undef EncodeArray +#undef EncodeHash + +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// Random Numbers via Meresenne Twister or LCG +// + +STB_EXTERN unsigned int stb_srandLCG(unsigned int seed); +STB_EXTERN unsigned int stb_randLCG(void); +STB_EXTERN double stb_frandLCG(void); + +STB_EXTERN void stb_srand(unsigned int seed); +STB_EXTERN unsigned int stb_rand(void); +STB_EXTERN double stb_frand(void); +STB_EXTERN void stb_shuffle(void *p, size_t n, size_t sz, + unsigned int seed); +STB_EXTERN void stb_reverse(void *p, size_t n, size_t sz); + +STB_EXTERN unsigned int stb_randLCG_explicit(unsigned int seed); + +#define stb_rand_define(x,y) \ + \ + unsigned int x(void) \ + { \ + static unsigned int stb__rand = y; \ + stb__rand = stb__rand * 2147001325 + 715136305; /* BCPL */ \ + return 0x31415926 ^ ((stb__rand >> 16) + (stb__rand << 16)); \ + } + +#ifdef STB_DEFINE +unsigned int stb_randLCG_explicit(unsigned int seed) +{ + return seed * 2147001325 + 715136305; +} + +static unsigned int stb__rand_seed=0; + +unsigned int stb_srandLCG(unsigned int seed) +{ + unsigned int previous = stb__rand_seed; + stb__rand_seed = seed; + return previous; +} + +unsigned int stb_randLCG(void) +{ + stb__rand_seed = stb__rand_seed * 2147001325 + 715136305; // BCPL generator + // shuffle non-random bits to the middle, and xor to decorrelate with seed + return 0x31415926 ^ ((stb__rand_seed >> 16) + (stb__rand_seed << 16)); +} + +double stb_frandLCG(void) +{ + return stb_randLCG() / ((double) (1 << 16) * (1 << 16)); +} + +void stb_shuffle(void *p, size_t n, size_t sz, unsigned int seed) +{ + char *a; + unsigned int old_seed; + int i; + if (seed) + old_seed = stb_srandLCG(seed); + a = (char *) p + (n-1) * sz; + + for (i=(int) n; i > 1; --i) { + int j = stb_randLCG() % i; + stb_swap(a, (char *) p + j * sz, sz); + a -= sz; + } + if (seed) + stb_srandLCG(old_seed); +} + +void stb_reverse(void *p, size_t n, size_t sz) +{ + size_t i,j = n-1; + for (i=0; i < j; ++i,--j) { + stb_swap((char *) p + i * sz, (char *) p + j * sz, sz); + } +} + +// public domain Mersenne Twister by Michael Brundage +#define STB__MT_LEN 624 + +int stb__mt_index = STB__MT_LEN*sizeof(int)+1; +unsigned int stb__mt_buffer[STB__MT_LEN]; + +void stb_srand(unsigned int seed) +{ + int i; + stb__mt_buffer[0]= seed & 0xffffffffUL; + for (i=1 ; i < STB__MT_LEN; ++i) + stb__mt_buffer[i] = (1812433253UL * (stb__mt_buffer[i-1] ^ (stb__mt_buffer[i-1] >> 30)) + i); + stb__mt_index = STB__MT_LEN*sizeof(unsigned int); +} + +#define STB__MT_IA 397 +#define STB__MT_IB (STB__MT_LEN - STB__MT_IA) +#define STB__UPPER_MASK 0x80000000 +#define STB__LOWER_MASK 0x7FFFFFFF +#define STB__MATRIX_A 0x9908B0DF +#define STB__TWIST(b,i,j) ((b)[i] & STB__UPPER_MASK) | ((b)[j] & STB__LOWER_MASK) +#define STB__MAGIC(s) (((s)&1)*STB__MATRIX_A) + +unsigned int stb_rand() +{ + unsigned int * b = stb__mt_buffer; + int idx = stb__mt_index; + unsigned int s,r; + int i; + + if (idx >= STB__MT_LEN*sizeof(unsigned int)) { + if (idx > STB__MT_LEN*sizeof(unsigned int)) + stb_srand(0); + idx = 0; + i = 0; + for (; i < STB__MT_IB; i++) { + s = STB__TWIST(b, i, i+1); + b[i] = b[i + STB__MT_IA] ^ (s >> 1) ^ STB__MAGIC(s); + } + for (; i < STB__MT_LEN-1; i++) { + s = STB__TWIST(b, i, i+1); + b[i] = b[i - STB__MT_IB] ^ (s >> 1) ^ STB__MAGIC(s); + } + + s = STB__TWIST(b, STB__MT_LEN-1, 0); + b[STB__MT_LEN-1] = b[STB__MT_IA-1] ^ (s >> 1) ^ STB__MAGIC(s); + } + stb__mt_index = idx + sizeof(unsigned int); + + r = *(unsigned int *)((unsigned char *)b + idx); + + r ^= (r >> 11); + r ^= (r << 7) & 0x9D2C5680; + r ^= (r << 15) & 0xEFC60000; + r ^= (r >> 18); + + return r; +} + +double stb_frand(void) +{ + return stb_rand() / ((double) (1 << 16) * (1 << 16)); +} + +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// stb_dupe +// +// stb_dupe is a duplicate-finding system for very, very large data +// structures--large enough that sorting is too slow, but not so large +// that we can't keep all the data in memory. using it works as follows: +// +// 1. create an stb_dupe: +// provide a hash function +// provide an equality function +// provide an estimate for the size +// optionally provide a comparison function +// +// 2. traverse your data, 'adding' pointers to the stb_dupe +// +// 3. finish and ask for duplicates +// +// the stb_dupe will discard its intermediate data and build +// a collection of sorted lists of duplicates, with non-duplicate +// entries omitted entirely +// +// +// Implementation strategy: +// +// while collecting the N items, we keep a hash table of approximate +// size sqrt(N). (if you tell use the N up front, the hash table is +// just that size exactly) +// +// each entry in the hash table is just an stb__arr of pointers (no need +// to use stb_ps, because we don't need to delete from these) +// +// for step 3, for each entry in the hash table, we apply stb_dupe to it +// recursively. once the size gets small enough (or doesn't decrease +// significantly), we switch to either using qsort() on the comparison +// function, or else we just do the icky N^2 gather + + +typedef struct stb_dupe stb_dupe; + +typedef int (*stb_compare_func)(void *a, void *b); +typedef int (*stb_hash_func)(void *a, unsigned int seed); + +STB_EXTERN void stb_dupe_free(stb_dupe *sd); +STB_EXTERN stb_dupe *stb_dupe_create(stb_hash_func hash, + stb_compare_func eq, int size, stb_compare_func ineq); +STB_EXTERN void stb_dupe_add(stb_dupe *sd, void *item); +STB_EXTERN void stb_dupe_finish(stb_dupe *sd); +STB_EXTERN int stb_dupe_numsets(stb_dupe *sd); +STB_EXTERN void **stb_dupe_set(stb_dupe *sd, int num); +STB_EXTERN int stb_dupe_set_count(stb_dupe *sd, int num); + +struct stb_dupe +{ + void ***hash_table; + int hash_size; + int size_log2; + int population; + + int hash_shift; + stb_hash_func hash; + + stb_compare_func eq; + stb_compare_func ineq; + + void ***dupes; +}; + +#ifdef STB_DEFINE + +int stb_dupe_numsets(stb_dupe *sd) +{ + assert(sd->hash_table == NULL); + return stb_arr_len(sd->dupes); +} + +void **stb_dupe_set(stb_dupe *sd, int num) +{ + assert(sd->hash_table == NULL); + return sd->dupes[num]; +} + +int stb_dupe_set_count(stb_dupe *sd, int num) +{ + assert(sd->hash_table == NULL); + return stb_arr_len(sd->dupes[num]); +} + +stb_dupe *stb_dupe_create(stb_hash_func hash, stb_compare_func eq, int size, + stb_compare_func ineq) +{ + int i, hsize; + stb_dupe *sd = (stb_dupe *) malloc(sizeof(*sd)); + + sd->size_log2 = 4; + hsize = 1 << sd->size_log2; + while (hsize * hsize < size) { + ++sd->size_log2; + hsize *= 2; + } + + sd->hash = hash; + sd->eq = eq; + sd->ineq = ineq; + sd->hash_shift = 0; + + sd->population = 0; + sd->hash_size = hsize; + sd->hash_table = (void ***) malloc(sizeof(*sd->hash_table) * hsize); + for (i=0; i < hsize; ++i) + sd->hash_table[i] = NULL; + + sd->dupes = NULL; + + return sd; +} + +void stb_dupe_add(stb_dupe *sd, void *item) +{ + stb_uint32 hash = sd->hash(item, sd->hash_shift); + int z = hash & (sd->hash_size-1); + stb_arr_push(sd->hash_table[z], item); + ++sd->population; +} + +void stb_dupe_free(stb_dupe *sd) +{ + int i; + for (i=0; i < stb_arr_len(sd->dupes); ++i) + if (sd->dupes[i]) + stb_arr_free(sd->dupes[i]); + stb_arr_free(sd->dupes); + free(sd); +} + +static stb_compare_func stb__compare; + +static int stb__dupe_compare(const void *a, const void *b) +{ + void *p = *(void **) a; + void *q = *(void **) b; + + return stb__compare(p,q); +} + +void stb_dupe_finish(stb_dupe *sd) +{ + int i,j,k; + assert(sd->dupes == NULL); + for (i=0; i < sd->hash_size; ++i) { + void ** list = sd->hash_table[i]; + if (list != NULL) { + int n = stb_arr_len(list); + // @TODO: measure to find good numbers instead of just making them up! + int thresh = (sd->ineq ? 200 : 20); + // if n is large enough to be worth it, and n is smaller than + // before (so we can guarantee we'll use a smaller hash table); + // and there are enough hash bits left, assuming full 32-bit hash + if (n > thresh && n < (sd->population >> 3) && sd->hash_shift + sd->size_log2*2 < 32) { + + // recursively process this row using stb_dupe, O(N log log N) + + stb_dupe *d = stb_dupe_create(sd->hash, sd->eq, n, sd->ineq); + d->hash_shift = stb_randLCG_explicit(sd->hash_shift); + for (j=0; j < n; ++j) + stb_dupe_add(d, list[j]); + stb_arr_free(sd->hash_table[i]); + stb_dupe_finish(d); + for (j=0; j < stb_arr_len(d->dupes); ++j) { + stb_arr_push(sd->dupes, d->dupes[j]); + d->dupes[j] = NULL; // take over ownership + } + stb_dupe_free(d); + + } else if (sd->ineq) { + + // process this row using qsort(), O(N log N) + stb__compare = sd->ineq; + qsort(list, n, sizeof(list[0]), stb__dupe_compare); + + // find equal subsequences of the list + for (j=0; j < n-1; ) { + // find a subsequence from j..k + for (k=j; k < n; ++k) + // only use ineq so eq can be left undefined + if (sd->ineq(list[j], list[k])) + break; + // k is the first one not in the subsequence + if (k-j > 1) { + void **mylist = NULL; + stb_arr_setlen(mylist, k-j); + memcpy(mylist, list+j, sizeof(list[j]) * (k-j)); + stb_arr_push(sd->dupes, mylist); + } + j = k; + } + stb_arr_free(sd->hash_table[i]); + } else { + + // process this row using eq(), O(N^2) + for (j=0; j < n; ++j) { + if (list[j] != NULL) { + void **output = NULL; + for (k=j+1; k < n; ++k) { + if (sd->eq(list[j], list[k])) { + if (output == NULL) + stb_arr_push(output, list[j]); + stb_arr_push(output, list[k]); + list[k] = NULL; + } + } + list[j] = NULL; + if (output) + stb_arr_push(sd->dupes, output); + } + } + stb_arr_free(sd->hash_table[i]); + } + } + } + free(sd->hash_table); + sd->hash_table = NULL; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// templatized Sort routine +// +// This is an attempt to implement a templated sorting algorithm. +// To use it, you have to explicitly instantiate it as a _function_, +// then you call that function. This allows the comparison to be inlined, +// giving the sort similar performance to C++ sorts. +// +// It implements quicksort with three-way-median partitioning (generally +// well-behaved), with a final insertion sort pass. +// +// When you define the compare expression, you should assume you have +// elements of your array pointed to by 'a' and 'b', and perform the comparison +// on those. OR you can use one or more statements; first say '0;', then +// write whatever code you want, and compute the result into a variable 'c'. + +#define stb_declare_sort(FUNCNAME, TYPE) \ + void FUNCNAME(TYPE *p, int n) +#define stb_define_sort(FUNCNAME,TYPE,COMPARE) \ + stb__define_sort( void, FUNCNAME,TYPE,COMPARE) +#define stb_define_sort_static(FUNCNAME,TYPE,COMPARE) \ + stb__define_sort(static void, FUNCNAME,TYPE,COMPARE) + +#define stb__define_sort(MODE, FUNCNAME, TYPE, COMPARE) \ + \ +static void STB_(FUNCNAME,_ins_sort)(TYPE *p, int n) \ +{ \ + int i,j; \ + for (i=1; i < n; ++i) { \ + TYPE t = p[i], *a = &t; \ + j = i; \ + while (j > 0) { \ + TYPE *b = &p[j-1]; \ + int c = COMPARE; \ + if (!c) break; \ + p[j] = p[j-1]; \ + --j; \ + } \ + if (i != j) \ + p[j] = t; \ + } \ +} \ + \ +static void STB_(FUNCNAME,_quicksort)(TYPE *p, int n) \ +{ \ + /* threshold for transitioning to insertion sort */ \ + while (n > 12) { \ + TYPE *a,*b,t; \ + int c01,c12,c,m,i,j; \ + \ + /* compute median of three */ \ + m = n >> 1; \ + a = &p[0]; \ + b = &p[m]; \ + c = COMPARE; \ + c01 = c; \ + a = &p[m]; \ + b = &p[n-1]; \ + c = COMPARE; \ + c12 = c; \ + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ \ + if (c01 != c12) { \ + /* otherwise, we'll need to swap something else to middle */ \ + int z; \ + a = &p[0]; \ + b = &p[n-1]; \ + c = COMPARE; \ + /* 0>mid && midn => n; 0 0 */ \ + /* 0n: 0>n => 0; 0 n */ \ + z = (c == c12) ? 0 : n-1; \ + t = p[z]; \ + p[z] = p[m]; \ + p[m] = t; \ + } \ + /* now p[m] is the median-of-three */ \ + /* swap it to the beginning so it won't move around */ \ + t = p[0]; \ + p[0] = p[m]; \ + p[m] = t; \ + \ + /* partition loop */ \ + i=1; \ + j=n-1; \ + for(;;) { \ + /* handling of equality is crucial here */ \ + /* for sentinels & efficiency with duplicates */ \ + b = &p[0]; \ + for (;;++i) { \ + a=&p[i]; \ + c = COMPARE; \ + if (!c) break; \ + } \ + a = &p[0]; \ + for (;;--j) { \ + b=&p[j]; \ + c = COMPARE; \ + if (!c) break; \ + } \ + /* make sure we haven't crossed */ \ + if (i >= j) break; \ + t = p[i]; \ + p[i] = p[j]; \ + p[j] = t; \ + \ + ++i; \ + --j; \ + } \ + /* recurse on smaller side, iterate on larger */ \ + if (j < (n-i)) { \ + STB_(FUNCNAME,_quicksort)(p,j); \ + p = p+i; \ + n = n-i; \ + } else { \ + STB_(FUNCNAME,_quicksort)(p+i, n-i); \ + n = j; \ + } \ + } \ +} \ + \ +MODE FUNCNAME(TYPE *p, int n) \ +{ \ + STB_(FUNCNAME, _quicksort)(p, n); \ + STB_(FUNCNAME, _ins_sort)(p, n); \ +} \ + + +////////////////////////////////////////////////////////////////////////////// +// +// stb_bitset an array of booleans indexed by integers +// + +typedef stb_uint32 stb_bitset; + +STB_EXTERN stb_bitset *stb_bitset_new(int value, int len); + +#define stb_bitset_clearall(arr,len) (memset(arr, 0, 4 * (len))) +#define stb_bitset_setall(arr,len) (memset(arr, 255, 4 * (len))) + +#define stb_bitset_setbit(arr,n) ((arr)[(n) >> 5] |= (1 << (n & 31))) +#define stb_bitset_clearbit(arr,n) ((arr)[(n) >> 5] &= ~(1 << (n & 31))) +#define stb_bitset_testbit(arr,n) ((arr)[(n) >> 5] & (1 << (n & 31))) + +STB_EXTERN stb_bitset *stb_bitset_union(stb_bitset *p0, stb_bitset *p1, int len); + +STB_EXTERN int *stb_bitset_getlist(stb_bitset *out, int start, int end); + +STB_EXTERN int stb_bitset_eq(stb_bitset *p0, stb_bitset *p1, int len); +STB_EXTERN int stb_bitset_disjoint(stb_bitset *p0, stb_bitset *p1, int len); +STB_EXTERN int stb_bitset_disjoint_0(stb_bitset *p0, stb_bitset *p1, int len); +STB_EXTERN int stb_bitset_subset(stb_bitset *bigger, stb_bitset *smaller, int len); +STB_EXTERN int stb_bitset_unioneq_changed(stb_bitset *p0, stb_bitset *p1, int len); + +#ifdef STB_DEFINE +int stb_bitset_eq(stb_bitset *p0, stb_bitset *p1, int len) +{ + int i; + for (i=0; i < len; ++i) + if (p0[i] != p1[i]) return 0; + return 1; +} + +int stb_bitset_disjoint(stb_bitset *p0, stb_bitset *p1, int len) +{ + int i; + for (i=0; i < len; ++i) + if (p0[i] & p1[i]) return 0; + return 1; +} + +int stb_bitset_disjoint_0(stb_bitset *p0, stb_bitset *p1, int len) +{ + int i; + for (i=0; i < len; ++i) + if ((p0[i] | p1[i]) != 0xffffffff) return 0; + return 1; +} + +int stb_bitset_subset(stb_bitset *bigger, stb_bitset *smaller, int len) +{ + int i; + for (i=0; i < len; ++i) + if ((bigger[i] & smaller[i]) != smaller[i]) return 0; + return 1; +} + +stb_bitset *stb_bitset_union(stb_bitset *p0, stb_bitset *p1, int len) +{ + int i; + stb_bitset *d = (stb_bitset *) malloc(sizeof(*d) * len); + for (i=0; i < len; ++i) d[i] = p0[i] | p1[i]; + return d; +} + +int stb_bitset_unioneq_changed(stb_bitset *p0, stb_bitset *p1, int len) +{ + int i, changed=0; + for (i=0; i < len; ++i) { + stb_bitset d = p0[i] | p1[i]; + if (d != p0[i]) { + p0[i] = d; + changed = 1; + } + } + return changed; +} + +stb_bitset *stb_bitset_new(int value, int len) +{ + int i; + stb_bitset *d = (stb_bitset *) malloc(sizeof(*d) * len); + if (value) value = 0xffffffff; + for (i=0; i < len; ++i) d[i] = value; + return d; +} + +int *stb_bitset_getlist(stb_bitset *out, int start, int end) +{ + int *list = NULL; + int i; + for (i=start; i < end; ++i) + if (stb_bitset_testbit(out, i)) + stb_arr_push(list, i); + return list; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// stb_wordwrap quality word-wrapping for fixed-width fonts +// + +STB_EXTERN int stb_wordwrap(int *pairs, int pair_max, int count, char *str); +STB_EXTERN int *stb_wordwrapalloc(int count, char *str); + +#ifdef STB_DEFINE + +int stb_wordwrap(int *pairs, int pair_max, int count, char *str) +{ + int n=0,i=0, start=0,nonwhite=0; + if (pairs == NULL) pair_max = 0x7ffffff0; + else pair_max *= 2; + // parse + for(;;) { + int s=i; // first whitespace char; last nonwhite+1 + int w; // word start + // accept whitespace + while (isspace(str[i])) { + if (str[i] == '\n' || str[i] == '\r') { + if (str[i] + str[i+1] == '\n' + '\r') ++i; + if (n >= pair_max) return -1; + if (pairs) pairs[n] = start, pairs[n+1] = s-start; + n += 2; + nonwhite=0; + start = i+1; + s = start; + } + ++i; + } + if (i >= start+count) { + // we've gone off the end using whitespace + if (nonwhite) { + if (n >= pair_max) return -1; + if (pairs) pairs[n] = start, pairs[n+1] = s-start; + n += 2; + start = s = i; + nonwhite=0; + } else { + // output all the whitespace + while (i >= start+count) { + if (n >= pair_max) return -1; + if (pairs) pairs[n] = start, pairs[n+1] = count; + n += 2; + start += count; + } + s = start; + } + } + + if (str[i] == 0) break; + // now scan out a word and see if it fits + w = i; + while (str[i] && !isspace(str[i])) { + ++i; + } + // wrapped? + if (i > start + count) { + // huge? + if (i-s <= count) { + if (n >= pair_max) return -1; + if (pairs) pairs[n] = start, pairs[n+1] = s-start; + n += 2; + start = w; + } else { + // This word is longer than one line. If we wrap it onto N lines + // there are leftover chars. do those chars fit on the cur line? + // But if we have leading whitespace, we force it to start here. + if ((w-start) + ((i-w) % count) <= count || !nonwhite) { + // output a full line + if (n >= pair_max) return -1; + if (pairs) pairs[n] = start, pairs[n+1] = count; + n += 2; + start += count; + w = start; + } else { + // output a partial line, trimming trailing whitespace + if (s != start) { + if (n >= pair_max) return -1; + if (pairs) pairs[n] = start, pairs[n+1] = s-start; + n += 2; + start = w; + } + } + // now output full lines as needed + while (start + count <= i) { + if (n >= pair_max) return -1; + if (pairs) pairs[n] = start, pairs[n+1] = count; + n += 2; + start += count; + } + } + } + nonwhite=1; + } + if (start < i) { + if (n >= pair_max) return -1; + if (pairs) pairs[n] = start, pairs[n+1] = i-start; + n += 2; + } + return n>>1; +} + +int *stb_wordwrapalloc(int count, char *str) +{ + int n = stb_wordwrap(NULL,0,count,str); + int *z = NULL; + stb_arr_setlen(z, n*2); + stb_wordwrap(z, n, count, str); + return z; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// stb_match: wildcards and regexping +// + +STB_EXTERN int stb_wildmatch (char *expr, char *candidate); +STB_EXTERN int stb_wildmatchi(char *expr, char *candidate); +STB_EXTERN int stb_wildfind (char *expr, char *candidate); +STB_EXTERN int stb_wildfindi (char *expr, char *candidate); + +STB_EXTERN int stb_regex(char *regex, char *candidate); + +typedef struct stb_matcher stb_matcher; + +STB_EXTERN stb_matcher *stb_regex_matcher(char *regex); +STB_EXTERN int stb_matcher_match(stb_matcher *m, char *str); +STB_EXTERN int stb_matcher_find(stb_matcher *m, char *str); +STB_EXTERN void stb_matcher_free(stb_matcher *f); + +STB_EXTERN stb_matcher *stb_lex_matcher(void); +STB_EXTERN int stb_lex_item(stb_matcher *m, const char *str, int result); +STB_EXTERN int stb_lex_item_wild(stb_matcher *matcher, const char *regex, int result); +STB_EXTERN int stb_lex(stb_matcher *m, char *str, int *len); + + + +#ifdef STB_DEFINE + +static int stb__match_qstring(char *candidate, char *qstring, int qlen, int insensitive) +{ + int i; + if (insensitive) { + for (i=0; i < qlen; ++i) + if (qstring[i] == '?') { + if (!candidate[i]) return 0; + } else + if (tolower(qstring[i]) != tolower(candidate[i])) + return 0; + } else { + for (i=0; i < qlen; ++i) + if (qstring[i] == '?') { + if (!candidate[i]) return 0; + } else + if (qstring[i] != candidate[i]) + return 0; + } + return 1; +} + +static int stb__find_qstring(char *candidate, char *qstring, int qlen, int insensitive) +{ + char c; + + int offset=0; + while (*qstring == '?') { + ++qstring; + --qlen; + ++candidate; + if (qlen == 0) return 0; + if (*candidate == 0) return -1; + } + + c = *qstring++; + --qlen; + if (insensitive) c = tolower(c); + + while (candidate[offset]) { + if (c == (insensitive ? tolower(candidate[offset]) : candidate[offset])) + if (stb__match_qstring(candidate+offset+1, qstring, qlen, insensitive)) + return offset; + ++offset; + } + + return -1; +} + +int stb__wildmatch_raw2(char *expr, char *candidate, int search, int insensitive) +{ + int where=0; + int start = -1; + + if (!search) { + // parse to first '*' + if (*expr != '*') + start = 0; + while (*expr != '*') { + if (!*expr) + return *candidate == 0 ? 0 : -1; + if (*expr == '?') { + if (!*candidate) return -1; + } else { + if (insensitive) { + if (tolower(*candidate) != tolower(*expr)) + return -1; + } else + if (*candidate != *expr) + return -1; + } + ++candidate, ++expr, ++where; + } + } else { + // 0-length search string + if (!*expr) + return 0; + } + + assert(search || *expr == '*'); + if (!search) + ++expr; + + // implicit '*' at this point + + while (*expr) { + int o=0; + // combine redundant * characters + while (expr[0] == '*') ++expr; + + // ok, at this point, expr[-1] == '*', + // and expr[0] != '*' + + if (!expr[0]) return start >= 0 ? start : 0; + + // now find next '*' + o = 0; + while (expr[o] != '*') { + if (expr[o] == 0) + break; + ++o; + } + // if no '*', scan to end, then match at end + if (expr[o] == 0 && !search) { + int z; + for (z=0; z < o; ++z) + if (candidate[z] == 0) + return -1; + while (candidate[z]) + ++z; + // ok, now check if they match + if (stb__match_qstring(candidate+z-o, expr, o, insensitive)) + return start >= 0 ? start : 0; + return -1; + } else { + // if yes '*', then do stb__find_qmatch on the intervening chars + int n = stb__find_qstring(candidate, expr, o, insensitive); + if (n < 0) + return -1; + if (start < 0) + start = where + n; + expr += o; + candidate += n+o; + } + + if (*expr == 0) { + assert(search); + return start; + } + + assert(*expr == '*'); + ++expr; + } + + return start >= 0 ? start : 0; +} + +int stb__wildmatch_raw(char *expr, char *candidate, int search, int insensitive) +{ + char buffer[256]; + // handle multiple search strings + char *s = strchr(expr, ';'); + char *last = expr; + while (s) { + int z; + // need to allow for non-writeable strings... assume they're small + if (s - last < 256) { + stb_strncpy(buffer, last, (int) (s-last+1)); + buffer[s-last] = 0; + z = stb__wildmatch_raw2(buffer, candidate, search, insensitive); + } else { + *s = 0; + z = stb__wildmatch_raw2(last, candidate, search, insensitive); + *s = ';'; + } + if (z >= 0) return z; + last = s+1; + s = strchr(last, ';'); + } + return stb__wildmatch_raw2(last, candidate, search, insensitive); +} + +int stb_wildmatch(char *expr, char *candidate) +{ + return stb__wildmatch_raw(expr, candidate, 0,0) >= 0; +} + +int stb_wildmatchi(char *expr, char *candidate) +{ + return stb__wildmatch_raw(expr, candidate, 0,1) >= 0; +} + +int stb_wildfind(char *expr, char *candidate) +{ + return stb__wildmatch_raw(expr, candidate, 1,0); +} + +int stb_wildfindi(char *expr, char *candidate) +{ + return stb__wildmatch_raw(expr, candidate, 1,1); +} + +typedef struct +{ + stb_int16 transition[256]; +} stb_dfa; + +// an NFA node represents a state you're in; it then has +// an arbitrary number of edges dangling off of it +// note this isn't utf8-y +typedef struct +{ + stb_int16 match; // character/set to match + stb_uint16 node; // output node to go to +} stb_nfa_edge; + +typedef struct +{ + stb_int16 goal; // does reaching this win the prize? + stb_uint8 active; // is this in the active list + stb_nfa_edge *out; + stb_uint16 *eps; // list of epsilon closures +} stb_nfa_node; + +#define STB__DFA_UNDEF -1 +#define STB__DFA_GOAL -2 +#define STB__DFA_END -3 +#define STB__DFA_MGOAL -4 +#define STB__DFA_VALID 0 + +#define STB__NFA_STOP_GOAL -1 + +// compiled regexp +struct stb_matcher +{ + stb_uint16 start_node; + stb_int16 dfa_start; + stb_uint32 *charset; + int num_charset; + int match_start; + stb_nfa_node *nodes; + int does_lex; + + // dfa matcher + stb_dfa * dfa; + stb_uint32 * dfa_mapping; + stb_int16 * dfa_result; + int num_words_per_dfa; +}; + +static int stb__add_node(stb_matcher *matcher) +{ + stb_nfa_node z; + z.active = 0; + z.eps = 0; + z.goal = 0; + z.out = 0; + stb_arr_push(matcher->nodes, z); + return stb_arr_len(matcher->nodes)-1; +} + +static void stb__add_epsilon(stb_matcher *matcher, int from, int to) +{ + assert(from != to); + if (matcher->nodes[from].eps == NULL) + stb_arr_malloc((void **) &matcher->nodes[from].eps, matcher); + stb_arr_push(matcher->nodes[from].eps, to); +} + +static void stb__add_edge(stb_matcher *matcher, int from, int to, int type) +{ + stb_nfa_edge z = { (stb_int16)type, (stb_uint16)to }; + if (matcher->nodes[from].out == NULL) + stb_arr_malloc((void **) &matcher->nodes[from].out, matcher); + stb_arr_push(matcher->nodes[from].out, z); +} + +static char *stb__reg_parse_alt(stb_matcher *m, int s, char *r, stb_uint16 *e); +static char *stb__reg_parse(stb_matcher *matcher, int start, char *regex, stb_uint16 *end) +{ + int n; + int last_start = -1; + stb_uint16 last_end = start; + + while (*regex) { + switch (*regex) { + case '(': + last_start = last_end; + regex = stb__reg_parse_alt(matcher, last_end, regex+1, &last_end); + if (regex == NULL || *regex != ')') + return NULL; + ++regex; + break; + + case '|': + case ')': + *end = last_end; + return regex; + + case '?': + if (last_start < 0) return NULL; + stb__add_epsilon(matcher, last_start, last_end); + ++regex; + break; + + case '*': + if (last_start < 0) return NULL; + stb__add_epsilon(matcher, last_start, last_end); + + // fall through + + case '+': + if (last_start < 0) return NULL; + stb__add_epsilon(matcher, last_end, last_start); + // prevent links back to last_end from chaining to last_start + n = stb__add_node(matcher); + stb__add_epsilon(matcher, last_end, n); + last_end = n; + ++regex; + break; + + case '{': // not supported! + // @TODO: given {n,m}, clone last_start to last_end m times, + // and include epsilons from start to first m-n blocks + return NULL; + + case '\\': + ++regex; + if (!*regex) return NULL; + + // fallthrough + default: // match exactly this character + n = stb__add_node(matcher); + stb__add_edge(matcher, last_end, n, *regex); + last_start = last_end; + last_end = n; + ++regex; + break; + + case '$': + n = stb__add_node(matcher); + stb__add_edge(matcher, last_end, n, '\n'); + last_start = last_end; + last_end = n; + ++regex; + break; + + case '.': + n = stb__add_node(matcher); + stb__add_edge(matcher, last_end, n, -1); + last_start = last_end; + last_end = n; + ++regex; + break; + + case '[': { + stb_uint8 flags[256]; + int invert = 0,z; + ++regex; + if (matcher->num_charset == 0) { + matcher->charset = (stb_uint *) stb_malloc(matcher, sizeof(*matcher->charset) * 256); + memset(matcher->charset, 0, sizeof(*matcher->charset) * 256); + } + + memset(flags,0,sizeof(flags)); + + // leading ^ is special + if (*regex == '^') + ++regex, invert = 1; + + // leading ] is special + if (*regex == ']') { + flags[(int) ']'] = 1; + ++regex; + } + while (*regex != ']') { + stb_uint a; + if (!*regex) return NULL; + a = *regex++; + if (regex[0] == '-' && regex[1] != ']') { + stb_uint i,b = regex[1]; + regex += 2; + if (b == 0) return NULL; + if (a > b) return NULL; + for (i=a; i <= b; ++i) + flags[i] = 1; + } else + flags[a] = 1; + } + ++regex; + if (invert) { + int i; + for (i=0; i < 256; ++i) + flags[i] = 1-flags[i]; + } + + // now check if any existing charset matches + for (z=0; z < matcher->num_charset; ++z) { + int i, k[2] = { 0, 1 << z}; + for (i=0; i < 256; ++i) { + unsigned int f = k[flags[i]]; + if ((matcher->charset[i] & k[1]) != f) + break; + } + if (i == 256) break; + } + + if (z == matcher->num_charset) { + int i; + ++matcher->num_charset; + if (matcher->num_charset > 32) { + assert(0); /* NOTREACHED */ + return NULL; // too many charsets, oops + } + for (i=0; i < 256; ++i) + if (flags[i]) + matcher->charset[i] |= (1 << z); + } + + n = stb__add_node(matcher); + stb__add_edge(matcher, last_end, n, -2 - z); + last_start = last_end; + last_end = n; + break; + } + } + } + *end = last_end; + return regex; +} + +static char *stb__reg_parse_alt(stb_matcher *matcher, int start, char *regex, stb_uint16 *end) +{ + stb_uint16 last_end = start; + stb_uint16 main_end; + + int head, tail; + + head = stb__add_node(matcher); + stb__add_epsilon(matcher, start, head); + + regex = stb__reg_parse(matcher, head, regex, &last_end); + if (regex == NULL) return NULL; + if (*regex == 0 || *regex == ')') { + *end = last_end; + return regex; + } + + main_end = last_end; + tail = stb__add_node(matcher); + + stb__add_epsilon(matcher, last_end, tail); + + // start alternatives from the same starting node; use epsilon + // transitions to combine their endings + while(*regex && *regex != ')') { + assert(*regex == '|'); + head = stb__add_node(matcher); + stb__add_epsilon(matcher, start, head); + regex = stb__reg_parse(matcher, head, regex+1, &last_end); + if (regex == NULL) + return NULL; + stb__add_epsilon(matcher, last_end, tail); + } + + *end = tail; + return regex; +} + +static char *stb__wild_parse(stb_matcher *matcher, int start, char *str, stb_uint16 *end) +{ + int n; + stb_uint16 last_end; + + last_end = stb__add_node(matcher); + stb__add_epsilon(matcher, start, last_end); + + while (*str) { + switch (*str) { + // fallthrough + default: // match exactly this character + n = stb__add_node(matcher); + if (toupper(*str) == tolower(*str)) { + stb__add_edge(matcher, last_end, n, *str); + } else { + stb__add_edge(matcher, last_end, n, tolower(*str)); + stb__add_edge(matcher, last_end, n, toupper(*str)); + } + last_end = n; + ++str; + break; + + case '?': + n = stb__add_node(matcher); + stb__add_edge(matcher, last_end, n, -1); + last_end = n; + ++str; + break; + + case '*': + n = stb__add_node(matcher); + stb__add_edge(matcher, last_end, n, -1); + stb__add_epsilon(matcher, last_end, n); + stb__add_epsilon(matcher, n, last_end); + last_end = n; + ++str; + break; + } + } + + // now require end of string to match + n = stb__add_node(matcher); + stb__add_edge(matcher, last_end, n, 0); + last_end = n; + + *end = last_end; + return str; +} + +static int stb__opt(stb_matcher *m, int n) +{ + for(;;) { + stb_nfa_node *p = &m->nodes[n]; + if (p->goal) return n; + if (stb_arr_len(p->out)) return n; + if (stb_arr_len(p->eps) != 1) return n; + n = p->eps[0]; + } +} + +static void stb__optimize(stb_matcher *m) +{ + // if the target of any edge is a node with exactly + // one out-epsilon, shorten it + int i,j; + for (i=0; i < stb_arr_len(m->nodes); ++i) { + stb_nfa_node *p = &m->nodes[i]; + for (j=0; j < stb_arr_len(p->out); ++j) + p->out[j].node = stb__opt(m,p->out[j].node); + for (j=0; j < stb_arr_len(p->eps); ++j) + p->eps[j] = stb__opt(m,p->eps[j] ); + } + m->start_node = stb__opt(m,m->start_node); +} + +void stb_matcher_free(stb_matcher *f) +{ + stb_free(f); +} + +static stb_matcher *stb__alloc_matcher(void) +{ + stb_matcher *matcher = (stb_matcher *) stb_malloc(0,sizeof(*matcher)); + + matcher->start_node = 0; + stb_arr_malloc((void **) &matcher->nodes, matcher); + matcher->num_charset = 0; + matcher->match_start = 0; + matcher->does_lex = 0; + + matcher->dfa_start = STB__DFA_UNDEF; + stb_arr_malloc((void **) &matcher->dfa, matcher); + stb_arr_malloc((void **) &matcher->dfa_mapping, matcher); + stb_arr_malloc((void **) &matcher->dfa_result, matcher); + + stb__add_node(matcher); + + return matcher; +} + +static void stb__lex_reset(stb_matcher *matcher) +{ + // flush cached dfa data + stb_arr_setlen(matcher->dfa, 0); + stb_arr_setlen(matcher->dfa_mapping, 0); + stb_arr_setlen(matcher->dfa_result, 0); + matcher->dfa_start = STB__DFA_UNDEF; +} + +stb_matcher *stb_regex_matcher(char *regex) +{ + char *z; + stb_uint16 end; + stb_matcher *matcher = stb__alloc_matcher(); + if (*regex == '^') { + matcher->match_start = 1; + ++regex; + } + + z = stb__reg_parse_alt(matcher, matcher->start_node, regex, &end); + + if (!z || *z) { + stb_free(matcher); + return NULL; + } + + ((matcher->nodes)[(int) end]).goal = STB__NFA_STOP_GOAL; + + return matcher; +} + +stb_matcher *stb_lex_matcher(void) +{ + stb_matcher *matcher = stb__alloc_matcher(); + + matcher->match_start = 1; + matcher->does_lex = 1; + + return matcher; +} + +int stb_lex_item(stb_matcher *matcher, const char *regex, int result) +{ + char *z; + stb_uint16 end; + + z = stb__reg_parse_alt(matcher, matcher->start_node, (char*) regex, &end); + + if (z == NULL) + return 0; + + stb__lex_reset(matcher); + + matcher->nodes[(int) end].goal = result; + return 1; +} + +int stb_lex_item_wild(stb_matcher *matcher, const char *regex, int result) +{ + char *z; + stb_uint16 end; + + z = stb__wild_parse(matcher, matcher->start_node, (char*) regex, &end); + + if (z == NULL) + return 0; + + stb__lex_reset(matcher); + + matcher->nodes[(int) end].goal = result; + return 1; +} + +static void stb__clear(stb_matcher *m, stb_uint16 *list) +{ + int i; + for (i=0; i < stb_arr_len(list); ++i) + m->nodes[(int) list[i]].active = 0; +} + +static int stb__clear_goalcheck(stb_matcher *m, stb_uint16 *list) +{ + int i, t=0; + for (i=0; i < stb_arr_len(list); ++i) { + t += m->nodes[(int) list[i]].goal; + m->nodes[(int) list[i]].active = 0; + } + return t; +} + +static stb_uint16 * stb__add_if_inactive(stb_matcher *m, stb_uint16 *list, int n) +{ + if (!m->nodes[n].active) { + stb_arr_push(list, n); + m->nodes[n].active = 1; + } + return list; +} + +static stb_uint16 * stb__eps_closure(stb_matcher *m, stb_uint16 *list) +{ + int i,n = stb_arr_len(list); + + for(i=0; i < n; ++i) { + stb_uint16 *e = m->nodes[(int) list[i]].eps; + if (e) { + int j,k = stb_arr_len(e); + for (j=0; j < k; ++j) + list = stb__add_if_inactive(m, list, e[j]); + n = stb_arr_len(list); + } + } + + return list; +} + +int stb_matcher_match(stb_matcher *m, char *str) +{ + int result = 0; + int i,j,y,z; + stb_uint16 *previous = NULL; + stb_uint16 *current = NULL; + stb_uint16 *temp; + + stb_arr_setsize(previous, 4); + stb_arr_setsize(current, 4); + + previous = stb__add_if_inactive(m, previous, m->start_node); + previous = stb__eps_closure(m,previous); + stb__clear(m, previous); + + while (*str && stb_arr_len(previous)) { + y = stb_arr_len(previous); + for (i=0; i < y; ++i) { + stb_nfa_node *n = &m->nodes[(int) previous[i]]; + z = stb_arr_len(n->out); + for (j=0; j < z; ++j) { + if (n->out[j].match >= 0) { + if (n->out[j].match == *str) + current = stb__add_if_inactive(m, current, n->out[j].node); + } else if (n->out[j].match == -1) { + if (*str != '\n') + current = stb__add_if_inactive(m, current, n->out[j].node); + } else if (n->out[j].match < -1) { + int z = -n->out[j].match - 2; + if (m->charset[(stb_uint8) *str] & (1 << z)) + current = stb__add_if_inactive(m, current, n->out[j].node); + } + } + } + stb_arr_setlen(previous, 0); + + temp = previous; + previous = current; + current = temp; + + previous = stb__eps_closure(m,previous); + stb__clear(m, previous); + + ++str; + } + + // transition to pick up a '$' at the end + y = stb_arr_len(previous); + for (i=0; i < y; ++i) + m->nodes[(int) previous[i]].active = 1; + + for (i=0; i < y; ++i) { + stb_nfa_node *n = &m->nodes[(int) previous[i]]; + z = stb_arr_len(n->out); + for (j=0; j < z; ++j) { + if (n->out[j].match == '\n') + current = stb__add_if_inactive(m, current, n->out[j].node); + } + } + + previous = stb__eps_closure(m,previous); + stb__clear(m, previous); + + y = stb_arr_len(previous); + for (i=0; i < y; ++i) + if (m->nodes[(int) previous[i]].goal) + result = 1; + + stb_arr_free(previous); + stb_arr_free(current); + + return result && *str == 0; +} + +stb_int16 stb__get_dfa_node(stb_matcher *m, stb_uint16 *list) +{ + stb_uint16 node; + stb_uint32 data[8], *state, *newstate; + int i,j,n; + + state = (stb_uint32 *) stb_temp(data, m->num_words_per_dfa * 4); + memset(state, 0, m->num_words_per_dfa*4); + + n = stb_arr_len(list); + for (i=0; i < n; ++i) { + int x = list[i]; + state[x >> 5] |= 1 << (x & 31); + } + + // @TODO use a hash table + n = stb_arr_len(m->dfa_mapping); + i=j=0; + for(; j < n; ++i, j += m->num_words_per_dfa) { + // @TODO special case for <= 32 + if (!memcmp(state, m->dfa_mapping + j, m->num_words_per_dfa*4)) { + node = i; + goto done; + } + } + + assert(stb_arr_len(m->dfa) == i); + node = i; + + newstate = stb_arr_addn(m->dfa_mapping, m->num_words_per_dfa); + memcpy(newstate, state, m->num_words_per_dfa*4); + + // set all transitions to 'unknown' + stb_arr_add(m->dfa); + memset(m->dfa[i].transition, -1, sizeof(m->dfa[i].transition)); + + if (m->does_lex) { + int result = -1; + n = stb_arr_len(list); + for (i=0; i < n; ++i) { + if (m->nodes[(int) list[i]].goal > result) + result = m->nodes[(int) list[i]].goal; + } + + stb_arr_push(m->dfa_result, result); + } + +done: + stb_tempfree(data, state); + return node; +} + +static int stb__matcher_dfa(stb_matcher *m, char *str_c, int *len) +{ + stb_uint8 *str = (stb_uint8 *) str_c; + stb_int16 node,prevnode; + stb_dfa *trans; + int match_length = 0; + stb_int16 match_result=0; + + if (m->dfa_start == STB__DFA_UNDEF) { + stb_uint16 *list; + + m->num_words_per_dfa = (stb_arr_len(m->nodes)+31) >> 5; + stb__optimize(m); + + list = stb__add_if_inactive(m, NULL, m->start_node); + list = stb__eps_closure(m,list); + if (m->does_lex) { + m->dfa_start = stb__get_dfa_node(m,list); + stb__clear(m, list); + // DON'T allow start state to be a goal state! + // this allows people to specify regexes that can match 0 + // characters without them actually matching (also we don't + // check _before_ advancing anyway + if (m->dfa_start <= STB__DFA_MGOAL) + m->dfa_start = -(m->dfa_start - STB__DFA_MGOAL); + } else { + if (stb__clear_goalcheck(m, list)) + m->dfa_start = STB__DFA_GOAL; + else + m->dfa_start = stb__get_dfa_node(m,list); + } + stb_arr_free(list); + } + + prevnode = STB__DFA_UNDEF; + node = m->dfa_start; + trans = m->dfa; + + if (m->dfa_start == STB__DFA_GOAL) + return 1; + + for(;;) { + assert(node >= STB__DFA_VALID); + + // fast inner DFA loop; especially if STB__DFA_VALID is 0 + + do { + prevnode = node; + node = trans[node].transition[*str++]; + } while (node >= STB__DFA_VALID); + + assert(node >= STB__DFA_MGOAL - stb_arr_len(m->dfa)); + assert(node < stb_arr_len(m->dfa)); + + // special case for lex: need _longest_ match, so notice goal + // state without stopping + if (node <= STB__DFA_MGOAL) { + match_length = (int) (str - (stb_uint8 *) str_c); + node = -(node - STB__DFA_MGOAL); + match_result = node; + continue; + } + + // slow NFA->DFA conversion + + // or we hit the goal or the end of the string, but those + // can only happen once per search... + + if (node == STB__DFA_UNDEF) { + // build a list -- @TODO special case <= 32 states + // heck, use a more compact data structure for <= 16 and <= 8 ?! + + // @TODO keep states/newstates around instead of reallocating them + stb_uint16 *states = NULL; + stb_uint16 *newstates = NULL; + int i,j,y,z; + stb_uint32 *flags = &m->dfa_mapping[prevnode * m->num_words_per_dfa]; + assert(prevnode != STB__DFA_UNDEF); + stb_arr_setsize(states, 4); + stb_arr_setsize(newstates,4); + for (j=0; j < m->num_words_per_dfa; ++j) { + for (i=0; i < 32; ++i) { + if (*flags & (1 << i)) + stb_arr_push(states, j*32+i); + } + ++flags; + } + // states is now the states we were in in the previous node; + // so now we can compute what node it transitions to on str[-1] + + y = stb_arr_len(states); + for (i=0; i < y; ++i) { + stb_nfa_node *n = &m->nodes[(int) states[i]]; + z = stb_arr_len(n->out); + for (j=0; j < z; ++j) { + if (n->out[j].match >= 0) { + if (n->out[j].match == str[-1] || (str[-1] == 0 && n->out[j].match == '\n')) + newstates = stb__add_if_inactive(m, newstates, n->out[j].node); + } else if (n->out[j].match == -1) { + if (str[-1] != '\n' && str[-1]) + newstates = stb__add_if_inactive(m, newstates, n->out[j].node); + } else if (n->out[j].match < -1) { + int z = -n->out[j].match - 2; + if (m->charset[str[-1]] & (1 << z)) + newstates = stb__add_if_inactive(m, newstates, n->out[j].node); + } + } + } + // AND add in the start state! + if (!m->match_start || (str[-1] == '\n' && !m->does_lex)) + newstates = stb__add_if_inactive(m, newstates, m->start_node); + // AND epsilon close it + newstates = stb__eps_closure(m, newstates); + // if it's a goal state, then that's all there is to it + if (stb__clear_goalcheck(m, newstates)) { + if (m->does_lex) { + match_length = (int) (str - (stb_uint8 *) str_c); + node = stb__get_dfa_node(m,newstates); + match_result = node; + node = -node + STB__DFA_MGOAL; + trans = m->dfa; // could have gotten realloc()ed + } else + node = STB__DFA_GOAL; + } else if (str[-1] == 0 || stb_arr_len(newstates) == 0) { + node = STB__DFA_END; + } else { + node = stb__get_dfa_node(m,newstates); + trans = m->dfa; // could have gotten realloc()ed + } + trans[prevnode].transition[str[-1]] = node; + if (node <= STB__DFA_MGOAL) + node = -(node - STB__DFA_MGOAL); + stb_arr_free(newstates); + stb_arr_free(states); + } + + if (node == STB__DFA_GOAL) { + return 1; + } + if (node == STB__DFA_END) { + if (m->does_lex) { + if (match_result) { + if (len) *len = match_length; + return m->dfa_result[(int) match_result]; + } + } + return 0; + } + + assert(node != STB__DFA_UNDEF); + } +} + +int stb_matcher_find(stb_matcher *m, char *str) +{ + assert(m->does_lex == 0); + return stb__matcher_dfa(m, str, NULL); +} + +int stb_lex(stb_matcher *m, char *str, int *len) +{ + assert(m->does_lex); + return stb__matcher_dfa(m, str, len); +} + +#ifdef STB_PERFECT_HASH +int stb_regex(char *regex, char *str) +{ + static stb_perfect p; + static stb_matcher ** matchers; + static char ** regexps; + static char ** regexp_cache; + static unsigned short *mapping; + int z = stb_perfect_hash(&p, (int)(size_t) regex); + if (z >= 0) { + if (strcmp(regex, regexp_cache[(int) mapping[z]])) { + int i = mapping[z]; + stb_matcher_free(matchers[i]); + free(regexp_cache[i]); + regexps[i] = regex; + regexp_cache[i] = stb_p_strdup(regex); + matchers[i] = stb_regex_matcher(regex); + } + } else { + int i,n; + if (regex == NULL) { + for (i=0; i < stb_arr_len(matchers); ++i) { + stb_matcher_free(matchers[i]); + free(regexp_cache[i]); + } + stb_arr_free(matchers); + stb_arr_free(regexps); + stb_arr_free(regexp_cache); + stb_perfect_destroy(&p); + free(mapping); mapping = NULL; + return -1; + } + stb_arr_push(regexps, regex); + stb_arr_push(regexp_cache, stb_p_strdup(regex)); + stb_arr_push(matchers, stb_regex_matcher(regex)); + stb_perfect_destroy(&p); + n = stb_perfect_create(&p, (unsigned int *) (char **) regexps, stb_arr_len(regexps)); + mapping = (unsigned short *) realloc(mapping, n * sizeof(*mapping)); + for (i=0; i < stb_arr_len(regexps); ++i) + mapping[stb_perfect_hash(&p, (int)(size_t) regexps[i])] = i; + z = stb_perfect_hash(&p, (int)(size_t) regex); + } + return stb_matcher_find(matchers[(int) mapping[z]], str); +} +#endif +#endif // STB_DEFINE + + +#if 0 +////////////////////////////////////////////////////////////////////////////// +// +// C source-code introspection +// + +// runtime structure +typedef struct +{ + char *name; + char *type; // base type + char *comment; // content of comment field + int size; // size of base type + int offset; // field offset + int arrcount[8]; // array sizes; -1 = pointer indirection; 0 = end of list +} stb_info_field; + +typedef struct +{ + char *structname; + int size; + int num_fields; + stb_info_field *fields; +} stb_info_struct; + +extern stb_info_struct stb_introspect_output[]; + +// + +STB_EXTERN void stb_introspect_precompiled(stb_info_struct *compiled); +STB_EXTERN void stb__introspect(char *path, char *file); + +#define stb_introspect_ship() stb__introspect(NULL, NULL, stb__introspect_output) + +#ifdef STB_SHIP +#define stb_introspect() stb_introspect_ship() +#define stb_introspect_path(p) stb_introspect_ship() +#else +// bootstrapping: define stb_introspect() (or 'path') the first time +#define stb_introspect() stb__introspect(NULL, __FILE__, NULL) +#define stb_introspect_auto() stb__introspect(NULL, __FILE__, stb__introspect_output) + +#define stb_introspect_path(p) stb__introspect(p, __FILE__, NULL) +#define stb_introspect_path(p) stb__introspect(p, __FILE__, NULL) +#endif + +#ifdef STB_DEFINE + +#ifndef STB_INTROSPECT_CPP + #ifdef __cplusplus + #define STB_INTROSPECT_CPP 1 + #else + #define STB_INTROSPECT_CPP 0 + #endif +#endif + +void stb_introspect_precompiled(stb_info_struct *compiled) +{ + +} + + +static void stb__introspect_filename(char *buffer, char *path) +{ + #if STB_INTROSPECT_CPP + stb_p_sprintf(buffer stb_p_size(9999), "%s/stb_introspect.cpp", path); + #else + stb_p_sprintf(buffer stb_p_size(9999), "%s/stb_introspect.c", path); + #endif +} + +static void stb__introspect_compute(char *path, char *file) +{ + int i; + char ** include_list = NULL; + char ** introspect_list = NULL; + FILE *f; + f = stb_p_fopen(file, "w"); + if (!f) return; + + fputs("// if you get compiler errors, change the following 0 to a 1:\n", f); + fputs("#define STB_INTROSPECT_INVALID 0\n\n", f); + fputs("// this will force the code to compile, and force the introspector\n", f); + fputs("// to run and then exit, allowing you to recompile\n\n\n", f); + fputs("#include \"stb.h\"\n\n",f ); + fputs("#if STB_INTROSPECT_INVALID\n", f); + fputs(" stb_info_struct stb__introspect_output[] = { (void *) 1 }\n", f); + fputs("#else\n\n", f); + for (i=0; i < stb_arr_len(include_list); ++i) + fprintf(f, " #include \"%s\"\n", include_list[i]); + + fputs(" stb_info_struct stb__introspect_output[] =\n{\n", f); + for (i=0; i < stb_arr_len(introspect_list); ++i) + fprintf(f, " stb_introspect_%s,\n", introspect_list[i]); + fputs(" };\n", f); + fputs("#endif\n", f); + fclose(f); +} + +static stb_info_struct *stb__introspect_info; + +#ifndef STB_SHIP + +#endif + +void stb__introspect(char *path, char *file, stb_info_struct *compiled) +{ + static int first=1; + if (!first) return; + first=0; + + stb__introspect_info = compiled; + + #ifndef STB_SHIP + if (path || file) { + int bail_flag = compiled && compiled[0].structname == (void *) 1; + int needs_building = bail_flag; + struct stb__stat st; + char buffer[1024], buffer2[1024]; + if (!path) { + stb_splitpath(buffer, file, STB_PATH); + path = buffer; + } + // bail if the source path doesn't exist + if (!stb_fexists(path)) return; + + stb__introspect_filename(buffer2, path); + + // get source/include files timestamps, compare to output-file timestamp; + // if mismatched, regenerate + + if (stb__stat(buffer2, &st)) + needs_building = STB_TRUE; + + { + // find any file that contains an introspection command and is newer + // if needs_building is already true, we don't need to do this test, + // but we still need these arrays, so go ahead and get them + char **all[3]; + all[0] = stb_readdir_files_mask(path, "*.h"); + all[1] = stb_readdir_files_mask(path, "*.c"); + all[2] = stb_readdir_files_mask(path, "*.cpp"); + int i,j; + if (needs_building) { + for (j=0; j < 3; ++j) { + for (i=0; i < stb_arr_len(all[j]); ++i) { + struct stb__stat st2; + if (!stb__stat(all[j][i], &st2)) { + if (st.st_mtime < st2.st_mtime) { + char *z = stb_filec(all[j][i], NULL); + int found=STB_FALSE; + while (y) { + y = strstr(y, "//si"); + if (y && isspace(y[4])) { + found = STB_TRUE; + break; + } + } + needs_building = STB_TRUE; + goto done; + } + } + } + } + done:; + } + char *z = stb_filec(all[i], NULL), *y = z; + int found=STB_FALSE; + while (y) { + y = strstr(y, "//si"); + if (y && isspace(y[4])) { + found = STB_TRUE; + break; + } + } + if (found) + stb_arr_push(introspect_h, stb_p_strdup(all[i])); + free(z); + } + } + stb_readdir_free(all); + if (!needs_building) { + for (i=0; i < stb_arr_len(introspect_h); ++i) { + struct stb__stat st2; + if (!stb__stat(introspect_h[i], &st2)) + if (st.st_mtime < st2.st_mtime) + needs_building = STB_TRUE; + } + } + + if (needs_building) { + stb__introspect_compute(path, buffer2); + } + } + } + #endif +} +#endif +#endif + +#ifdef STB_INTROSPECT +// compile-time code-generator +#define INTROSPECT(x) int main(int argc, char **argv) { stb__introspect(__FILE__); return 0; } +#define FILE(x) + +void stb__introspect(char *filename) +{ + char *file = stb_file(filename, NULL); + char *s = file, *t, **p; + char *out_name = "stb_introspect.c"; + char *out_path; + STB_ARR(char) filelist = NULL; + int i,n; + if (!file) stb_fatal("Couldn't open %s", filename); + + out_path = stb_splitpathdup(filename, STB_PATH); + + // search for the macros + while (*s) { + char buffer[256]; + while (*s && !isupper(*s)) ++s; + s = stb_strtok_invert(buffer, s, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + s = stb_skipwhite(s); + if (*s == '(') { + ++s; + t = strchr(s, ')'); + if (t == NULL) stb_fatal("Error parsing %s", filename); + + } + } +} + + + +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// STB-C sliding-window dictionary compression +// +// This uses a DEFLATE-style sliding window, but no bitwise entropy. +// Everything is on byte boundaries, so you could then apply a byte-wise +// entropy code, though that's nowhere near as effective. +// +// An STB-C stream begins with a 16-byte header: +// 4 bytes: 0x57 0xBC 0x00 0x00 +// 8 bytes: big-endian size of decompressed data, 64-bits +// 4 bytes: big-endian size of window (how far back decompressor may need) +// +// The following symbols appear in the stream (these were determined ad hoc, +// not by analysis): +// +// [dict] 00000100 yyyyyyyy yyyyyyyy yyyyyyyy xxxxxxxx xxxxxxxx +// [END] 00000101 11111010 cccccccc cccccccc cccccccc cccccccc +// [dict] 00000110 yyyyyyyy yyyyyyyy yyyyyyyy xxxxxxxx +// [literals] 00000111 zzzzzzzz zzzzzzzz +// [literals] 00001zzz zzzzzzzz +// [dict] 00010yyy yyyyyyyy yyyyyyyy xxxxxxxx xxxxxxxx +// [dict] 00011yyy yyyyyyyy yyyyyyyy xxxxxxxx +// [literals] 001zzzzz +// [dict] 01yyyyyy yyyyyyyy xxxxxxxx +// [dict] 1xxxxxxx yyyyyyyy +// +// xxxxxxxx: match length - 1 +// yyyyyyyy: backwards distance - 1 +// zzzzzzzz: num literals - 1 +// cccccccc: adler32 checksum of decompressed data +// (all big-endian) + + +STB_EXTERN stb_uint stb_decompress_length(stb_uchar *input); +STB_EXTERN stb_uint stb_decompress(stb_uchar *out,stb_uchar *in,stb_uint len); +STB_EXTERN stb_uint stb_compress (stb_uchar *out,stb_uchar *in,stb_uint len); +STB_EXTERN void stb_compress_window(int z); +STB_EXTERN void stb_compress_hashsize(unsigned int z); + +STB_EXTERN int stb_compress_tofile(char *filename, char *in, stb_uint len); +STB_EXTERN int stb_compress_intofile(FILE *f, char *input, stb_uint len); +STB_EXTERN char *stb_decompress_fromfile(char *filename, stb_uint *len); + +STB_EXTERN int stb_compress_stream_start(FILE *f); +STB_EXTERN void stb_compress_stream_end(int close); +STB_EXTERN void stb_write(char *data, int data_len); + +#ifdef STB_DEFINE + +stb_uint stb_decompress_length(stb_uchar *input) +{ + return (input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11]; +} + +//////////////////// decompressor /////////////////////// + +// simple implementation that just writes whole thing into big block + +static unsigned char *stb__barrier; +static unsigned char *stb__barrier2; +static unsigned char *stb__barrier3; +static unsigned char *stb__barrier4; + +static stb_uchar *stb__dout; +static void stb__match(stb_uchar *data, stb_uint length) +{ + // INVERSE of memmove... write each byte before copying the next... + assert (stb__dout + length <= stb__barrier); + if (stb__dout + length > stb__barrier) { stb__dout += length; return; } + if (data < stb__barrier4) { stb__dout = stb__barrier+1; return; } + while (length--) *stb__dout++ = *data++; +} + +static void stb__lit(stb_uchar *data, stb_uint length) +{ + assert (stb__dout + length <= stb__barrier); + if (stb__dout + length > stb__barrier) { stb__dout += length; return; } + if (data < stb__barrier2) { stb__dout = stb__barrier+1; return; } + memcpy(stb__dout, data, length); + stb__dout += length; +} + +#define stb__in2(x) ((i[x] << 8) + i[(x)+1]) +#define stb__in3(x) ((i[x] << 16) + stb__in2((x)+1)) +#define stb__in4(x) ((i[x] << 24) + stb__in3((x)+1)) + +static stb_uchar *stb_decompress_token(stb_uchar *i) +{ + if (*i >= 0x20) { // use fewer if's for cases that expand small + if (*i >= 0x80) stb__match(stb__dout-i[1]-1, i[0] - 0x80 + 1), i += 2; + else if (*i >= 0x40) stb__match(stb__dout-(stb__in2(0) - 0x4000 + 1), i[2]+1), i += 3; + else /* *i >= 0x20 */ stb__lit(i+1, i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1); + } else { // more ifs for cases that expand large, since overhead is amortized + if (*i >= 0x18) stb__match(stb__dout-(stb__in3(0) - 0x180000 + 1), i[3]+1), i += 4; + else if (*i >= 0x10) stb__match(stb__dout-(stb__in3(0) - 0x100000 + 1), stb__in2(3)+1), i += 5; + else if (*i >= 0x08) stb__lit(i+2, stb__in2(0) - 0x0800 + 1), i += 2 + (stb__in2(0) - 0x0800 + 1); + else if (*i == 0x07) stb__lit(i+3, stb__in2(1) + 1), i += 3 + (stb__in2(1) + 1); + else if (*i == 0x06) stb__match(stb__dout-(stb__in3(1)+1), i[4]+1), i += 5; + else if (*i == 0x04) stb__match(stb__dout-(stb__in3(1)+1), stb__in2(4)+1), i += 6; + } + return i; +} + +stb_uint stb_decompress(stb_uchar *output, stb_uchar *i, stb_uint length) +{ + stb_uint olen; + if (stb__in4(0) != 0x57bC0000) return 0; + if (stb__in4(4) != 0) return 0; // error! stream is > 4GB + olen = stb_decompress_length(i); + stb__barrier2 = i; + stb__barrier3 = i+length; + stb__barrier = output + olen; + stb__barrier4 = output; + i += 16; + + stb__dout = output; + while (1) { + stb_uchar *old_i = i; + i = stb_decompress_token(i); + if (i == old_i) { + if (*i == 0x05 && i[1] == 0xfa) { + assert(stb__dout == output + olen); + if (stb__dout != output + olen) return 0; + if (stb_adler32(1, output, olen) != (stb_uint) stb__in4(2)) + return 0; + return olen; + } else { + assert(0); /* NOTREACHED */ + return 0; + } + } + assert(stb__dout <= output + olen); + if (stb__dout > output + olen) + return 0; + } +} + +char *stb_decompress_fromfile(char *filename, unsigned int *len) +{ + unsigned int n; + char *q; + unsigned char *p; + FILE *f = stb_p_fopen(filename, "rb"); if (f == NULL) return NULL; + fseek(f, 0, SEEK_END); + n = ftell(f); + fseek(f, 0, SEEK_SET); + p = (unsigned char * ) malloc(n); if (p == NULL) return NULL; + fread(p, 1, n, f); + fclose(f); + if (p == NULL) return NULL; + if (p[0] != 0x57 || p[1] != 0xBc || p[2] || p[3]) { free(p); return NULL; } + q = (char *) malloc(stb_decompress_length(p)+1); + if (!q) { free(p); return NULL; } + *len = stb_decompress((unsigned char *) q, p, n); + if (*len) q[*len] = 0; + free(p); + return q; +} + +#if 0 +// streaming decompressor + +static struct +{ + stb__uchar *in_buffer; + stb__uchar *match; + + stb__uint pending_literals; + stb__uint pending_match; +} xx; + + + +static void stb__match(stb_uchar *data, stb_uint length) +{ + // INVERSE of memmove... write each byte before copying the next... + assert (stb__dout + length <= stb__barrier); + if (stb__dout + length > stb__barrier) { stb__dout += length; return; } + if (data < stb__barrier2) { stb__dout = stb__barrier+1; return; } + while (length--) *stb__dout++ = *data++; +} + +static void stb__lit(stb_uchar *data, stb_uint length) +{ + assert (stb__dout + length <= stb__barrier); + if (stb__dout + length > stb__barrier) { stb__dout += length; return; } + if (data < stb__barrier2) { stb__dout = stb__barrier+1; return; } + memcpy(stb__dout, data, length); + stb__dout += length; +} + +static void sx_match(stb_uchar *data, stb_uint length) +{ + xx.match = data; + xx.pending_match = length; +} + +static void sx_lit(stb_uchar *data, stb_uint length) +{ + xx.pending_lit = length; +} + +static int stb_decompress_token_state(void) +{ + stb__uchar *i = xx.in_buffer; + + if (*i >= 0x20) { // use fewer if's for cases that expand small + if (*i >= 0x80) sx_match(stb__dout-i[1]-1, i[0] - 0x80 + 1), i += 2; + else if (*i >= 0x40) sx_match(stb__dout-(stb__in2(0) - 0x4000 + 1), i[2]+1), i += 3; + else /* *i >= 0x20 */ sx_lit(i+1, i[0] - 0x20 + 1), i += 1; + } else { // more ifs for cases that expand large, since overhead is amortized + if (*i >= 0x18) sx_match(stb__dout-(stb__in3(0) - 0x180000 + 1), i[3]+1), i += 4; + else if (*i >= 0x10) sx_match(stb__dout-(stb__in3(0) - 0x100000 + 1), stb__in2(3)+1), i += 5; + else if (*i >= 0x08) sx_lit(i+2, stb__in2(0) - 0x0800 + 1), i += 2; + else if (*i == 0x07) sx_lit(i+3, stb__in2(1) + 1), i += 3; + else if (*i == 0x06) sx_match(stb__dout-(stb__in3(1)+1), i[4]+1), i += 5; + else if (*i == 0x04) sx_match(stb__dout-(stb__in3(1)+1), stb__in2(4)+1), i += 6; + else return 0; + } + xx.in_buffer = i; + return 1; +} +#endif + + + +//////////////////// compressor /////////////////////// + +static unsigned int stb_matchlen(stb_uchar *m1, stb_uchar *m2, stb_uint maxlen) +{ + stb_uint i; + for (i=0; i < maxlen; ++i) + if (m1[i] != m2[i]) return i; + return i; +} + +// simple implementation that just takes the source data in a big block + +static stb_uchar *stb__out; +static FILE *stb__outfile; +static stb_uint stb__outbytes; + +static void stb__write(unsigned char v) +{ + fputc(v, stb__outfile); + ++stb__outbytes; +} + +#define stb_out(v) (stb__out ? (void)(*stb__out++ = (stb_uchar) (v)) : stb__write((stb_uchar) (v))) + +static void stb_out2(stb_uint v) +{ + stb_out(v >> 8); + stb_out(v); +} + +static void stb_out3(stb_uint v) { stb_out(v >> 16); stb_out(v >> 8); stb_out(v); } +static void stb_out4(stb_uint v) { stb_out(v >> 24); stb_out(v >> 16); + stb_out(v >> 8 ); stb_out(v); } + +static void outliterals(stb_uchar *in, ptrdiff_t numlit) +{ + while (numlit > 65536) { + outliterals(in,65536); + in += 65536; + numlit -= 65536; + } + + if (numlit == 0) ; + else if (numlit <= 32) stb_out (0x000020 + (stb_uint) numlit-1); + else if (numlit <= 2048) stb_out2(0x000800 + (stb_uint) numlit-1); + else /* numlit <= 65536) */ stb_out3(0x070000 + (stb_uint) numlit-1); + + if (stb__out) { + memcpy(stb__out,in,numlit); + stb__out += numlit; + } else + fwrite(in, 1, numlit, stb__outfile); +} + +static int stb__window = 0x40000; // 256K +void stb_compress_window(int z) +{ + if (z >= 0x1000000) z = 0x1000000; // limit of implementation + if (z < 0x100) z = 0x100; // insanely small + stb__window = z; +} + +static int stb_not_crap(int best, int dist) +{ + return ((best > 2 && dist <= 0x00100) + || (best > 5 && dist <= 0x04000) + || (best > 7 && dist <= 0x80000)); +} + +static stb_uint stb__hashsize = 32768; +void stb_compress_hashsize(unsigned int y) +{ + unsigned int z = 1024; + while (z < y) z <<= 1; + stb__hashsize = z >> 2; // pass in bytes, store #pointers +} + +// note that you can play with the hashing functions all you +// want without needing to change the decompressor +#define stb__hc(q,h,c) (((h) << 7) + ((h) >> 25) + q[c]) +#define stb__hc2(q,h,c,d) (((h) << 14) + ((h) >> 18) + (q[c] << 7) + q[d]) +#define stb__hc3(q,c,d,e) ((q[c] << 14) + (q[d] << 7) + q[e]) + +static stb_uint32 stb__running_adler; + +static int stb_compress_chunk(stb_uchar *history, + stb_uchar *start, + stb_uchar *end, + int length, + int *pending_literals, + stb_uchar **chash, + stb_uint mask) +{ + int window = stb__window; + stb_uint match_max; + stb_uchar *lit_start = start - *pending_literals; + stb_uchar *q = start; + + #define STB__SCRAMBLE(h) (((h) + ((h) >> 16)) & mask) + + // stop short of the end so we don't scan off the end doing + // the hashing; this means we won't compress the last few bytes + // unless they were part of something longer + while (q < start+length && q+12 < end) { + int m; + stb_uint h1,h2,h3,h4, h; + stb_uchar *t; + int best = 2, dist=0; + + if (q+65536 > end) + match_max = (stb_uint) (end-q); + else + match_max = 65536u; + + #define stb__nc(b,d) ((d) <= window && ((b) > 9 || stb_not_crap(b,d))) + + #define STB__TRY(t,p) /* avoid retrying a match we already tried */ \ + if (p ? dist != (int) (q-t) : 1) \ + if ((m = (int) stb_matchlen(t, q, match_max)) > best)\ + if (stb__nc(m,(int) (q-(t)))) \ + best = m, dist = (int) (q - (t)) + + // rather than search for all matches, only try 4 candidate locations, + // chosen based on 4 different hash functions of different lengths. + // this strategy is inspired by LZO; hashing is unrolled here using the + // 'hc' macro + h = stb__hc3(q,0, 1, 2); h1 = STB__SCRAMBLE(h); + t = chash[h1]; if (t) STB__TRY(t,0); + h = stb__hc2(q,h, 3, 4); h2 = STB__SCRAMBLE(h); + h = stb__hc2(q,h, 5, 6); t = chash[h2]; if (t) STB__TRY(t,1); + h = stb__hc2(q,h, 7, 8); h3 = STB__SCRAMBLE(h); + h = stb__hc2(q,h, 9,10); t = chash[h3]; if (t) STB__TRY(t,1); + h = stb__hc2(q,h,11,12); h4 = STB__SCRAMBLE(h); + t = chash[h4]; if (t) STB__TRY(t,1); + + // because we use a shared hash table, can only update it + // _after_ we've probed all of them + chash[h1] = chash[h2] = chash[h3] = chash[h4] = q; + + if (best > 2) + assert(dist > 0); + + // see if our best match qualifies + if (best < 3) { // fast path literals + ++q; + } else if (best > 2 && best <= 0x80 && dist <= 0x100) { + outliterals(lit_start, q-lit_start); lit_start = (q += best); + stb_out(0x80 + best-1); + stb_out(dist-1); + } else if (best > 5 && best <= 0x100 && dist <= 0x4000) { + outliterals(lit_start, q-lit_start); lit_start = (q += best); + stb_out2(0x4000 + dist-1); + stb_out(best-1); + } else if (best > 7 && best <= 0x100 && dist <= 0x80000) { + outliterals(lit_start, q-lit_start); lit_start = (q += best); + stb_out3(0x180000 + dist-1); + stb_out(best-1); + } else if (best > 8 && best <= 0x10000 && dist <= 0x80000) { + outliterals(lit_start, q-lit_start); lit_start = (q += best); + stb_out3(0x100000 + dist-1); + stb_out2(best-1); + } else if (best > 9 && dist <= 0x1000000) { + if (best > 65536) best = 65536; + outliterals(lit_start, q-lit_start); lit_start = (q += best); + if (best <= 0x100) { + stb_out(0x06); + stb_out3(dist-1); + stb_out(best-1); + } else { + stb_out(0x04); + stb_out3(dist-1); + stb_out2(best-1); + } + } else { // fallback literals if no match was a balanced tradeoff + ++q; + } + } + + // if we didn't get all the way, add the rest to literals + if (q-start < length) + q = start+length; + + // the literals are everything from lit_start to q + *pending_literals = (int) (q - lit_start); + + stb__running_adler = stb_adler32(stb__running_adler, start, (int) (q - start)); + return (int) (q - start); +} + +static int stb_compress_inner(stb_uchar *input, stb_uint length) +{ + int literals = 0; + stb_uint len,i; + + stb_uchar **chash; + chash = (stb_uchar**) malloc(stb__hashsize * sizeof(stb_uchar*)); + if (chash == NULL) return 0; // failure + for (i=0; i < stb__hashsize; ++i) + chash[i] = NULL; + + // stream signature + stb_out(0x57); stb_out(0xbc); + stb_out2(0); + + stb_out4(0); // 64-bit length requires 32-bit leading 0 + stb_out4(length); + stb_out4(stb__window); + + stb__running_adler = 1; + + len = stb_compress_chunk(input, input, input+length, length, &literals, chash, stb__hashsize-1); + assert(len == length); + + outliterals(input+length - literals, literals); + + free(chash); + + stb_out2(0x05fa); // end opcode + + stb_out4(stb__running_adler); + + return 1; // success +} + +stb_uint stb_compress(stb_uchar *out, stb_uchar *input, stb_uint length) +{ + stb__out = out; + stb__outfile = NULL; + + stb_compress_inner(input, length); + + return (stb_uint) (stb__out - out); +} + +int stb_compress_tofile(char *filename, char *input, unsigned int length) +{ + //int maxlen = length + 512 + (length >> 2); // total guess + //char *buffer = (char *) malloc(maxlen); + //int blen = stb_compress((stb_uchar*)buffer, (stb_uchar*)input, length); + + stb__out = NULL; + stb__outfile = stb_p_fopen(filename, "wb"); + if (!stb__outfile) return 0; + + stb__outbytes = 0; + + if (!stb_compress_inner((stb_uchar*)input, length)) + return 0; + + fclose(stb__outfile); + + return stb__outbytes; +} + +int stb_compress_intofile(FILE *f, char *input, unsigned int length) +{ + //int maxlen = length + 512 + (length >> 2); // total guess + //char *buffer = (char*)malloc(maxlen); + //int blen = stb_compress((stb_uchar*)buffer, (stb_uchar*)input, length); + + stb__out = NULL; + stb__outfile = f; + if (!stb__outfile) return 0; + + stb__outbytes = 0; + + if (!stb_compress_inner((stb_uchar*)input, length)) + return 0; + + return stb__outbytes; +} + +////////////////////// streaming I/O version ///////////////////// + + +static size_t stb_out_backpatch_id(void) +{ + if (stb__out) + return (size_t) stb__out; + else + return ftell(stb__outfile); +} + +static void stb_out_backpatch(size_t id, stb_uint value) +{ + stb_uchar data[4] = { (stb_uchar)(value >> 24), (stb_uchar)(value >> 16), (stb_uchar)(value >> 8), (stb_uchar)(value) }; + if (stb__out) { + memcpy((void *) id, data, 4); + } else { + stb_uint where = ftell(stb__outfile); + fseek(stb__outfile, (long) id, SEEK_SET); + fwrite(data, 4, 1, stb__outfile); + fseek(stb__outfile, where, SEEK_SET); + } +} + +// ok, the wraparound buffer was a total failure. let's instead +// use a copying-in-place buffer, which lets us share the code. +// This is way less efficient but it'll do for now. + +static struct +{ + stb_uchar *buffer; + int size; // physical size of buffer in bytes + + int valid; // amount of valid data in bytes + int start; // bytes of data already output + + int window; + int fsize; + + int pending_literals; // bytes not-quite output but counted in start + int length_id; + + stb_uint total_bytes; + + stb_uchar **chash; + stb_uint hashmask; +} xtb; + +static int stb_compress_streaming_start(void) +{ + stb_uint i; + xtb.size = stb__window * 3; + xtb.buffer = (stb_uchar*)malloc(xtb.size); + if (!xtb.buffer) return 0; + + xtb.chash = (stb_uchar**)malloc(sizeof(*xtb.chash) * stb__hashsize); + if (!xtb.chash) { + free(xtb.buffer); + return 0; + } + + for (i=0; i < stb__hashsize; ++i) + xtb.chash[i] = NULL; + + xtb.hashmask = stb__hashsize-1; + + xtb.valid = 0; + xtb.start = 0; + xtb.window = stb__window; + xtb.fsize = stb__window; + xtb.pending_literals = 0; + xtb.total_bytes = 0; + + // stream signature + stb_out(0x57); stb_out(0xbc); stb_out2(0); + + stb_out4(0); // 64-bit length requires 32-bit leading 0 + + xtb.length_id = (int) stb_out_backpatch_id(); + stb_out4(0); // we don't know the output length yet + + stb_out4(stb__window); + + stb__running_adler = 1; + + return 1; +} + +static int stb_compress_streaming_end(void) +{ + // flush out any remaining data + stb_compress_chunk(xtb.buffer, xtb.buffer+xtb.start, xtb.buffer+xtb.valid, + xtb.valid-xtb.start, &xtb.pending_literals, xtb.chash, xtb.hashmask); + + // write out pending literals + outliterals(xtb.buffer + xtb.valid - xtb.pending_literals, xtb.pending_literals); + + stb_out2(0x05fa); // end opcode + stb_out4(stb__running_adler); + + stb_out_backpatch(xtb.length_id, xtb.total_bytes); + + free(xtb.buffer); + free(xtb.chash); + return 1; +} + +void stb_write(char *data, int data_len) +{ + stb_uint i; + + // @TODO: fast path for filling the buffer and doing nothing else + // if (xtb.valid + data_len < xtb.size) + + xtb.total_bytes += data_len; + + while (data_len) { + // fill buffer + if (xtb.valid < xtb.size) { + int amt = xtb.size - xtb.valid; + if (data_len < amt) amt = data_len; + memcpy(xtb.buffer + xtb.valid, data, amt); + data_len -= amt; + data += amt; + xtb.valid += amt; + } + if (xtb.valid < xtb.size) + return; + + // at this point, the buffer is full + + // if we can process some data, go for it; make sure + // we leave an 'fsize's worth of data, though + if (xtb.start + xtb.fsize < xtb.valid) { + int amount = (xtb.valid - xtb.fsize) - xtb.start; + int n; + assert(amount > 0); + n = stb_compress_chunk(xtb.buffer, xtb.buffer + xtb.start, xtb.buffer + xtb.valid, + amount, &xtb.pending_literals, xtb.chash, xtb.hashmask); + xtb.start += n; + } + + assert(xtb.start + xtb.fsize >= xtb.valid); + // at this point, our future size is too small, so we + // need to flush some history. we, in fact, flush exactly + // one window's worth of history + + { + int flush = xtb.window; + assert(xtb.start >= flush); + assert(xtb.valid >= flush); + + // if 'pending literals' extends back into the shift region, + // write them out + if (xtb.start - xtb.pending_literals < flush) { + outliterals(xtb.buffer + xtb.start - xtb.pending_literals, xtb.pending_literals); + xtb.pending_literals = 0; + } + + // now shift the window + memmove(xtb.buffer, xtb.buffer + flush, xtb.valid - flush); + xtb.start -= flush; + xtb.valid -= flush; + + for (i=0; i <= xtb.hashmask; ++i) + if (xtb.chash[i] < xtb.buffer + flush) + xtb.chash[i] = NULL; + else + xtb.chash[i] -= flush; + } + // and now that we've made room for more data, go back to the top + } +} + +int stb_compress_stream_start(FILE *f) +{ + stb__out = NULL; + stb__outfile = f; + + if (f == NULL) + return 0; + + if (!stb_compress_streaming_start()) + return 0; + + return 1; +} + +void stb_compress_stream_end(int close) +{ + stb_compress_streaming_end(); + if (close && stb__outfile) { + fclose(stb__outfile); + } +} + +#endif // STB_DEFINE + +////////////////////////////////////////////////////////////////////////////// +// +// File abstraction... tired of not having this... we can write +// compressors to be layers over these that auto-close their children. + + +typedef struct stbfile +{ + int (*getbyte)(struct stbfile *); // -1 on EOF + unsigned int (*getdata)(struct stbfile *, void *block, unsigned int len); + + int (*putbyte)(struct stbfile *, int byte); + unsigned int (*putdata)(struct stbfile *, void *block, unsigned int len); + + unsigned int (*size)(struct stbfile *); + + unsigned int (*tell)(struct stbfile *); + void (*backpatch)(struct stbfile *, unsigned int tell, void *block, unsigned int len); + + void (*close)(struct stbfile *); + + FILE *f; // file to fread/fwrite + unsigned char *buffer; // input/output buffer + unsigned char *indata, *inend; // input buffer + union { + int various; + void *ptr; + }; +} stbfile; + +STB_EXTERN unsigned int stb_getc(stbfile *f); // read +STB_EXTERN int stb_putc(stbfile *f, int ch); // write +STB_EXTERN unsigned int stb_getdata(stbfile *f, void *buffer, unsigned int len); // read +STB_EXTERN unsigned int stb_putdata(stbfile *f, void *buffer, unsigned int len); // write +STB_EXTERN unsigned int stb_tell(stbfile *f); // read +STB_EXTERN unsigned int stb_size(stbfile *f); // read/write +STB_EXTERN void stb_backpatch(stbfile *f, unsigned int tell, void *buffer, unsigned int len); // write + +#ifdef STB_DEFINE + +unsigned int stb_getc(stbfile *f) { return f->getbyte(f); } +int stb_putc(stbfile *f, int ch) { return f->putbyte(f, ch); } + +unsigned int stb_getdata(stbfile *f, void *buffer, unsigned int len) +{ + return f->getdata(f, buffer, len); +} +unsigned int stb_putdata(stbfile *f, void *buffer, unsigned int len) +{ + return f->putdata(f, buffer, len); +} +void stb_close(stbfile *f) +{ + f->close(f); + free(f); +} +unsigned int stb_tell(stbfile *f) { return f->tell(f); } +unsigned int stb_size(stbfile *f) { return f->size(f); } +void stb_backpatch(stbfile *f, unsigned int tell, void *buffer, unsigned int len) +{ + f->backpatch(f,tell,buffer,len); +} + +// FILE * implementation +static int stb__fgetbyte(stbfile *f) { return fgetc(f->f); } +static int stb__fputbyte(stbfile *f, int ch) { return fputc(ch, f->f)==0; } +static unsigned int stb__fgetdata(stbfile *f, void *buffer, unsigned int len) { return (unsigned int) fread(buffer,1,len,f->f); } +static unsigned int stb__fputdata(stbfile *f, void *buffer, unsigned int len) { return (unsigned int) fwrite(buffer,1,len,f->f); } +static unsigned int stb__fsize(stbfile *f) { return (unsigned int) stb_filelen(f->f); } +static unsigned int stb__ftell(stbfile *f) { return (unsigned int) ftell(f->f); } +static void stb__fbackpatch(stbfile *f, unsigned int where, void *buffer, unsigned int len) +{ + fseek(f->f, where, SEEK_SET); + fwrite(buffer, 1, len, f->f); + fseek(f->f, 0, SEEK_END); +} +static void stb__fclose(stbfile *f) { fclose(f->f); } + +stbfile *stb_openf(FILE *f) +{ + stbfile m = { stb__fgetbyte, stb__fgetdata, + stb__fputbyte, stb__fputdata, + stb__fsize, stb__ftell, stb__fbackpatch, stb__fclose, + 0,0,0, }; + stbfile *z = (stbfile *) malloc(sizeof(*z)); + if (z) { + *z = m; + z->f = f; + } + return z; +} + +static int stb__nogetbyte(stbfile *f) { assert(0); return -1; } +static unsigned int stb__nogetdata(stbfile *f, void *buffer, unsigned int len) { assert(0); return 0; } +static int stb__noputbyte(stbfile *f, int ch) { assert(0); return 0; } +static unsigned int stb__noputdata(stbfile *f, void *buffer, unsigned int len) { assert(0); return 0; } +static void stb__nobackpatch(stbfile *f, unsigned int where, void *buffer, unsigned int len) { assert(0); } + +static int stb__bgetbyte(stbfile *s) +{ + if (s->indata < s->inend) + return *s->indata++; + else + return -1; +} + +static unsigned int stb__bgetdata(stbfile *s, void *buffer, unsigned int len) +{ + if (s->indata + len > s->inend) + len = (unsigned int) (s->inend - s->indata); + memcpy(buffer, s->indata, len); + s->indata += len; + return len; +} +static unsigned int stb__bsize(stbfile *s) { return (unsigned int) (s->inend - s->buffer); } +static unsigned int stb__btell(stbfile *s) { return (unsigned int) (s->indata - s->buffer); } + +static void stb__bclose(stbfile *s) +{ + if (s->various) + free(s->buffer); +} + +stbfile *stb_open_inbuffer(void *buffer, unsigned int len) +{ + stbfile m = { stb__bgetbyte, stb__bgetdata, + stb__noputbyte, stb__noputdata, + stb__bsize, stb__btell, stb__nobackpatch, stb__bclose }; + stbfile *z = (stbfile *) malloc(sizeof(*z)); + if (z) { + *z = m; + z->buffer = (unsigned char *) buffer; + z->indata = z->buffer; + z->inend = z->indata + len; + } + return z; +} + +stbfile *stb_open_inbuffer_free(void *buffer, unsigned int len) +{ + stbfile *z = stb_open_inbuffer(buffer, len); + if (z) + z->various = 1; // free + return z; +} + +#ifndef STB_VERSION +// if we've been cut-and-pasted elsewhere, you get a limited +// version of stb_open, without the 'k' flag and utf8 support +static void stb__fclose2(stbfile *f) +{ + fclose(f->f); +} + +stbfile *stb_open(char *filename, char *mode) +{ + FILE *f = stb_p_fopen(filename, mode); + stbfile *s; + if (f == NULL) return NULL; + s = stb_openf(f); + if (s) + s->close = stb__fclose2; + return s; +} +#else +// the full version depends on some code in stb.h; this +// also includes the memory buffer output format implemented with stb_arr +static void stb__fclose2(stbfile *f) +{ + stb_fclose(f->f, f->various); +} + +stbfile *stb_open(char *filename, char *mode) +{ + FILE *f = stb_fopen(filename, mode[0] == 'k' ? mode+1 : mode); + stbfile *s; + if (f == NULL) return NULL; + s = stb_openf(f); + if (s) { + s->close = stb__fclose2; + s->various = mode[0] == 'k' ? stb_keep_if_different : stb_keep_yes; + } + return s; +} + +static int stb__aputbyte(stbfile *f, int ch) +{ + stb_arr_push(f->buffer, ch); + return 1; +} +static unsigned int stb__aputdata(stbfile *f, void *data, unsigned int len) +{ + memcpy(stb_arr_addn(f->buffer, (int) len), data, len); + return len; +} +static unsigned int stb__asize(stbfile *f) { return stb_arr_len(f->buffer); } +static void stb__abackpatch(stbfile *f, unsigned int where, void *data, unsigned int len) +{ + memcpy(f->buffer+where, data, len); +} +static void stb__aclose(stbfile *f) +{ + *(unsigned char **) f->ptr = f->buffer; +} + +stbfile *stb_open_outbuffer(unsigned char **update_on_close) +{ + stbfile m = { stb__nogetbyte, stb__nogetdata, + stb__aputbyte, stb__aputdata, + stb__asize, stb__asize, stb__abackpatch, stb__aclose }; + stbfile *z = (stbfile *) malloc(sizeof(*z)); + if (z) { + z->ptr = update_on_close; + *z = m; + } + return z; +} +#endif +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// Arithmetic coder... based on cbloom's notes on the subject, should be +// less code than a huffman code. + +typedef struct +{ + unsigned int range_low; + unsigned int range_high; + unsigned int code, range; // decode + int buffered_u8; + int pending_ffs; + stbfile *output; +} stb_arith; + +STB_EXTERN void stb_arith_init_encode(stb_arith *a, stbfile *out); +STB_EXTERN void stb_arith_init_decode(stb_arith *a, stbfile *in); +STB_EXTERN stbfile *stb_arith_encode_close(stb_arith *a); +STB_EXTERN stbfile *stb_arith_decode_close(stb_arith *a); + +STB_EXTERN void stb_arith_encode(stb_arith *a, unsigned int totalfreq, unsigned int freq, unsigned int cumfreq); +STB_EXTERN void stb_arith_encode_log2(stb_arith *a, unsigned int totalfreq2, unsigned int freq, unsigned int cumfreq); +STB_EXTERN unsigned int stb_arith_decode_value(stb_arith *a, unsigned int totalfreq); +STB_EXTERN void stb_arith_decode_advance(stb_arith *a, unsigned int totalfreq, unsigned int freq, unsigned int cumfreq); +STB_EXTERN unsigned int stb_arith_decode_value_log2(stb_arith *a, unsigned int totalfreq2); +STB_EXTERN void stb_arith_decode_advance_log2(stb_arith *a, unsigned int totalfreq2, unsigned int freq, unsigned int cumfreq); + +STB_EXTERN void stb_arith_encode_byte(stb_arith *a, int byte); +STB_EXTERN int stb_arith_decode_byte(stb_arith *a); + +// this is a memory-inefficient way of doing things, but it's +// fast(?) and simple +typedef struct +{ + unsigned short cumfreq; + unsigned short samples; +} stb_arith_symstate_item; + +typedef struct +{ + int num_sym; + unsigned int pow2; + int countdown; + stb_arith_symstate_item data[1]; +} stb_arith_symstate; + +#ifdef STB_DEFINE +void stb_arith_init_encode(stb_arith *a, stbfile *out) +{ + a->range_low = 0; + a->range_high = 0xffffffff; + a->pending_ffs = -1; // means no buffered character currently, to speed up normal case + a->output = out; +} + +static void stb__arith_carry(stb_arith *a) +{ + int i; + assert(a->pending_ffs != -1); // can't carry with no data + stb_putc(a->output, a->buffered_u8); + for (i=0; i < a->pending_ffs; ++i) + stb_putc(a->output, 0); +} + +static void stb__arith_putbyte(stb_arith *a, int byte) +{ + if (a->pending_ffs) { + if (a->pending_ffs == -1) { // means no buffered data; encoded for fast path efficiency + if (byte == 0xff) + stb_putc(a->output, byte); // just write it immediately + else { + a->buffered_u8 = byte; + a->pending_ffs = 0; + } + } else if (byte == 0xff) { + ++a->pending_ffs; + } else { + int i; + stb_putc(a->output, a->buffered_u8); + for (i=0; i < a->pending_ffs; ++i) + stb_putc(a->output, 0xff); + } + } else if (byte == 0xff) { + ++a->pending_ffs; + } else { + // fast path + stb_putc(a->output, a->buffered_u8); + a->buffered_u8 = byte; + } +} + +static void stb__arith_flush(stb_arith *a) +{ + if (a->pending_ffs >= 0) { + int i; + stb_putc(a->output, a->buffered_u8); + for (i=0; i < a->pending_ffs; ++i) + stb_putc(a->output, 0xff); + } +} + +static void stb__renorm_encoder(stb_arith *a) +{ + stb__arith_putbyte(a, a->range_low >> 24); + a->range_low <<= 8; + a->range_high = (a->range_high << 8) | 0xff; +} + +static void stb__renorm_decoder(stb_arith *a) +{ + int c = stb_getc(a->output); + a->code = (a->code << 8) + (c >= 0 ? c : 0); // if EOF, insert 0 +} + +void stb_arith_encode(stb_arith *a, unsigned int totalfreq, unsigned int freq, unsigned int cumfreq) +{ + unsigned int range = a->range_high - a->range_low; + unsigned int old = a->range_low; + range /= totalfreq; + a->range_low += range * cumfreq; + a->range_high = a->range_low + range*freq; + if (a->range_low < old) + stb__arith_carry(a); + while (a->range_high - a->range_low < 0x1000000) + stb__renorm_encoder(a); +} + +void stb_arith_encode_log2(stb_arith *a, unsigned int totalfreq2, unsigned int freq, unsigned int cumfreq) +{ + unsigned int range = a->range_high - a->range_low; + unsigned int old = a->range_low; + range >>= totalfreq2; + a->range_low += range * cumfreq; + a->range_high = a->range_low + range*freq; + if (a->range_low < old) + stb__arith_carry(a); + while (a->range_high - a->range_low < 0x1000000) + stb__renorm_encoder(a); +} + +unsigned int stb_arith_decode_value(stb_arith *a, unsigned int totalfreq) +{ + unsigned int freqsize = a->range / totalfreq; + unsigned int z = a->code / freqsize; + return z >= totalfreq ? totalfreq-1 : z; +} + +void stb_arith_decode_advance(stb_arith *a, unsigned int totalfreq, unsigned int freq, unsigned int cumfreq) +{ + unsigned int freqsize = a->range / totalfreq; // @OPTIMIZE, share with above divide somehow? + a->code -= freqsize * cumfreq; + a->range = freqsize * freq; + while (a->range < 0x1000000) + stb__renorm_decoder(a); +} + +unsigned int stb_arith_decode_value_log2(stb_arith *a, unsigned int totalfreq2) +{ + unsigned int freqsize = a->range >> totalfreq2; + unsigned int z = a->code / freqsize; + return z >= (1U<range >> totalfreq2; + a->code -= freqsize * cumfreq; + a->range = freqsize * freq; + while (a->range < 0x1000000) + stb__renorm_decoder(a); +} + +stbfile *stb_arith_encode_close(stb_arith *a) +{ + // put exactly as many bytes as we'll read, so we can turn on/off arithmetic coding in a stream + stb__arith_putbyte(a, a->range_low >> 24); + stb__arith_putbyte(a, a->range_low >> 16); + stb__arith_putbyte(a, a->range_low >> 8); + stb__arith_putbyte(a, a->range_low >> 0); + stb__arith_flush(a); + return a->output; +} + +stbfile *stb_arith_decode_close(stb_arith *a) +{ + return a->output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Threads +// + +#ifndef _WIN32 +#ifdef STB_THREADS +#error "threads not implemented except for Windows" +#endif +#endif + +// call this function to free any global variables for memory testing +STB_EXTERN void stb_thread_cleanup(void); + +typedef void * (*stb_thread_func)(void *); + +// do not rely on these types, this is an implementation detail. +// compare against STB_THREAD_NULL and ST_SEMAPHORE_NULL +typedef void *stb_thread; +typedef void *stb_semaphore; +typedef void *stb_mutex; +typedef struct stb__sync *stb_sync; + +#define STB_SEMAPHORE_NULL NULL +#define STB_THREAD_NULL NULL +#define STB_MUTEX_NULL NULL +#define STB_SYNC_NULL NULL + +// get the number of processors (limited to those in the affinity mask for this process). +STB_EXTERN int stb_processor_count(void); +// force to run on a single core -- needed for RDTSC to work, e.g. for iprof +STB_EXTERN void stb_force_uniprocessor(void); + +// stb_work functions: queue up work to be done by some worker threads + +// set number of threads to serve the queue; you can change this on the fly, +// but if you decrease it, it won't decrease until things currently on the +// queue are finished +STB_EXTERN void stb_work_numthreads(int n); +// set maximum number of units in the queue; you can only set this BEFORE running any work functions +STB_EXTERN int stb_work_maxunits(int n); +// enqueue some work to be done (can do this from any thread, or even from a piece of work); +// return value of f is stored in *return_code if non-NULL +STB_EXTERN int stb_work(stb_thread_func f, void *d, volatile void **return_code); +// as above, but stb_sync_reach is called on 'rel' after work is complete +STB_EXTERN int stb_work_reach(stb_thread_func f, void *d, volatile void **return_code, stb_sync rel); + + +// necessary to call this when using volatile to order writes/reads +STB_EXTERN void stb_barrier(void); + +// support for independent queues with their own threads + +typedef struct stb__workqueue stb_workqueue; + +STB_EXTERN stb_workqueue*stb_workq_new(int numthreads, int max_units); +STB_EXTERN stb_workqueue*stb_workq_new_flags(int numthreads, int max_units, int no_add_mutex, int no_remove_mutex); +STB_EXTERN void stb_workq_delete(stb_workqueue *q); +STB_EXTERN void stb_workq_numthreads(stb_workqueue *q, int n); +STB_EXTERN int stb_workq(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code); +STB_EXTERN int stb_workq_reach(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code, stb_sync rel); +STB_EXTERN int stb_workq_length(stb_workqueue *q); + +STB_EXTERN stb_thread stb_create_thread (stb_thread_func f, void *d); +STB_EXTERN stb_thread stb_create_thread2(stb_thread_func f, void *d, volatile void **return_code, stb_semaphore rel); +STB_EXTERN void stb_destroy_thread(stb_thread t); + +STB_EXTERN stb_semaphore stb_sem_new(int max_val); +STB_EXTERN stb_semaphore stb_sem_new_extra(int max_val, int start_val); +STB_EXTERN void stb_sem_delete (stb_semaphore s); +STB_EXTERN void stb_sem_waitfor(stb_semaphore s); +STB_EXTERN void stb_sem_release(stb_semaphore s); + +STB_EXTERN stb_mutex stb_mutex_new(void); +STB_EXTERN void stb_mutex_delete(stb_mutex m); +STB_EXTERN void stb_mutex_begin(stb_mutex m); +STB_EXTERN void stb_mutex_end(stb_mutex m); + +STB_EXTERN stb_sync stb_sync_new(void); +STB_EXTERN void stb_sync_delete(stb_sync s); +STB_EXTERN int stb_sync_set_target(stb_sync s, int count); +STB_EXTERN void stb_sync_reach_and_wait(stb_sync s); // wait for 'target' reachers +STB_EXTERN int stb_sync_reach(stb_sync s); + +typedef struct stb__threadqueue stb_threadqueue; +#define STB_THREADQ_DYNAMIC 0 +STB_EXTERN stb_threadqueue *stb_threadq_new(int item_size, int num_items, int many_add, int many_remove); +STB_EXTERN void stb_threadq_delete(stb_threadqueue *tq); +STB_EXTERN int stb_threadq_get(stb_threadqueue *tq, void *output); +STB_EXTERN void stb_threadq_get_block(stb_threadqueue *tq, void *output); +STB_EXTERN int stb_threadq_add(stb_threadqueue *tq, void *input); +// can return FALSE if STB_THREADQ_DYNAMIC and attempt to grow fails +STB_EXTERN int stb_threadq_add_block(stb_threadqueue *tq, void *input); + +#ifdef STB_THREADS +#ifdef STB_DEFINE + +typedef struct +{ + stb_thread_func f; + void *d; + volatile void **return_val; + stb_semaphore sem; +} stb__thread; + +// this is initialized along all possible paths to create threads, therefore +// it's always initialized before any other threads are create, therefore +// it's free of races AS LONG AS you only create threads through stb_* +static stb_mutex stb__threadmutex, stb__workmutex; + +static void stb__threadmutex_init(void) +{ + if (stb__threadmutex == STB_SEMAPHORE_NULL) { + stb__threadmutex = stb_mutex_new(); + stb__workmutex = stb_mutex_new(); + } +} + +#ifdef STB_THREAD_TEST +volatile float stb__t1=1, stb__t2; + +static void stb__wait(int n) +{ + float z = 0; + int i; + for (i=0; i < n; ++i) + z += 1 / (stb__t1+i); + stb__t2 = z; +} +#else +#define stb__wait(x) +#endif + +#ifdef _WIN32 + +// avoid including windows.h -- note that our definitions aren't +// exactly the same (we don't define the security descriptor struct) +// so if you want to include windows.h, make sure you do it first. +#include + +#ifndef _WINDOWS_ // check windows.h guard +#define STB__IMPORT STB_EXTERN __declspec(dllimport) +#define STB__DW unsigned long + +STB__IMPORT int __stdcall TerminateThread(void *, STB__DW); +STB__IMPORT void * __stdcall CreateSemaphoreA(void *sec, long,long,char*); +STB__IMPORT int __stdcall CloseHandle(void *); +STB__IMPORT STB__DW __stdcall WaitForSingleObject(void *, STB__DW); +STB__IMPORT int __stdcall ReleaseSemaphore(void *, long, long *); +STB__IMPORT void __stdcall Sleep(STB__DW); +#endif + +// necessary to call this when using volatile to order writes/reads +void stb_barrier(void) +{ + #ifdef MemoryBarrier + MemoryBarrier(); + #else + long temp; + __asm xchg temp,eax; + #endif +} + +static void stb__thread_run(void *t) +{ + void *res; + stb__thread info = * (stb__thread *) t; + free(t); + res = info.f(info.d); + if (info.return_val) + *info.return_val = res; + if (info.sem != STB_SEMAPHORE_NULL) + stb_sem_release(info.sem); +} + +static stb_thread stb_create_thread_raw(stb_thread_func f, void *d, volatile void **return_code, stb_semaphore rel) +{ +#ifdef _MT +#if defined(STB_FASTMALLOC) && !defined(STB_FASTMALLOC_ITS_OKAY_I_ONLY_MALLOC_IN_ONE_THREAD) + stb_fatal("Error! Cannot use STB_FASTMALLOC with threads.\n"); + return STB_THREAD_NULL; +#else + unsigned long id; + stb__thread *data = (stb__thread *) malloc(sizeof(*data)); + if (!data) return NULL; + stb__threadmutex_init(); + data->f = f; + data->d = d; + data->return_val = return_code; + data->sem = rel; + id = _beginthread(stb__thread_run, 0, data); + if (id == -1) return NULL; + return (void *) id; +#endif +#else +#ifdef STB_NO_STB_STRINGS + stb_fatal("Invalid compilation"); +#else + stb_fatal("Must compile mult-threaded to use stb_thread/stb_work."); +#endif + return NULL; +#endif +} + +// trivial win32 wrappers +void stb_destroy_thread(stb_thread t) { TerminateThread(t,0); } +stb_semaphore stb_sem_new(int maxv) {return CreateSemaphoreA(NULL,0,maxv,NULL); } +stb_semaphore stb_sem_new_extra(int maxv,int start){return CreateSemaphoreA(NULL,start,maxv,NULL); } +void stb_sem_delete(stb_semaphore s) { if (s != NULL) CloseHandle(s); } +void stb_sem_waitfor(stb_semaphore s) { WaitForSingleObject(s, 0xffffffff); } // INFINITE +void stb_sem_release(stb_semaphore s) { ReleaseSemaphore(s,1,NULL); } +static void stb__thread_sleep(int ms) { Sleep(ms); } + +#ifndef _WINDOWS_ +STB__IMPORT int __stdcall GetProcessAffinityMask(void *, STB__DW *, STB__DW *); +STB__IMPORT void * __stdcall GetCurrentProcess(void); +STB__IMPORT int __stdcall SetProcessAffinityMask(void *, STB__DW); +#endif + +int stb_processor_count(void) +{ + unsigned long proc,sys; + GetProcessAffinityMask(GetCurrentProcess(), &proc, &sys); + return stb_bitcount(proc); +} + +void stb_force_uniprocessor(void) +{ + unsigned long proc,sys; + GetProcessAffinityMask(GetCurrentProcess(), &proc, &sys); + if (stb_bitcount(proc) > 1) { + int z; + for (z=0; z < 32; ++z) + if (proc & (1 << z)) + break; + if (z < 32) { + proc = 1 << z; + SetProcessAffinityMask(GetCurrentProcess(), proc); + } + } +} + +#ifdef _WINDOWS_ +#define STB_MUTEX_NATIVE +void *stb_mutex_new(void) +{ + CRITICAL_SECTION *p = (CRITICAL_SECTION *) malloc(sizeof(*p)); + if (p) +#if _WIN32_WINNT >= 0x0500 + InitializeCriticalSectionAndSpinCount(p, 500); +#else + InitializeCriticalSection(p); +#endif + return p; +} + +void stb_mutex_delete(void *p) +{ + if (p) { + DeleteCriticalSection((CRITICAL_SECTION *) p); + free(p); + } +} + +void stb_mutex_begin(void *p) +{ + stb__wait(500); + if (p) + EnterCriticalSection((CRITICAL_SECTION *) p); +} + +void stb_mutex_end(void *p) +{ + if (p) + LeaveCriticalSection((CRITICAL_SECTION *) p); + stb__wait(500); +} +#endif // _WINDOWS_ + +#if 0 +// for future reference, +// InterlockedCompareExchange for x86: + int cas64_mp(void * dest, void * xcmp, void * xxchg) { + __asm + { + mov esi, [xxchg] ; exchange + mov ebx, [esi + 0] + mov ecx, [esi + 4] + + mov esi, [xcmp] ; comparand + mov eax, [esi + 0] + mov edx, [esi + 4] + + mov edi, [dest] ; destination + lock cmpxchg8b [edi] + jz yyyy; + + mov [esi + 0], eax; + mov [esi + 4], edx; + +yyyy: + xor eax, eax; + setz al; + }; + +inline unsigned __int64 _InterlockedCompareExchange64(volatile unsigned __int64 *dest + ,unsigned __int64 exchange + ,unsigned __int64 comperand) +{ + //value returned in eax::edx + __asm { + lea esi,comperand; + lea edi,exchange; + + mov eax,[esi]; + mov edx,4[esi]; + mov ebx,[edi]; + mov ecx,4[edi]; + mov esi,dest; + lock CMPXCHG8B [esi]; + } +#endif // #if 0 + +#endif // _WIN32 + +stb_thread stb_create_thread2(stb_thread_func f, void *d, volatile void **return_code, stb_semaphore rel) +{ + return stb_create_thread_raw(f,d,return_code,rel); +} + +stb_thread stb_create_thread(stb_thread_func f, void *d) +{ + return stb_create_thread2(f,d,NULL,STB_SEMAPHORE_NULL); +} + +// mutex implemented by wrapping semaphore +#ifndef STB_MUTEX_NATIVE +stb_mutex stb_mutex_new(void) { return stb_sem_new_extra(1,1); } +void stb_mutex_delete(stb_mutex m) { stb_sem_delete (m); } +void stb_mutex_begin(stb_mutex m) { stb__wait(500); if (m) stb_sem_waitfor(m); } +void stb_mutex_end(stb_mutex m) { if (m) stb_sem_release(m); stb__wait(500); } +#endif + +// thread merge operation +struct stb__sync +{ + int target; // target number of threads to hit it + int sofar; // total threads that hit it + int waiting; // total threads waiting + + stb_mutex start; // mutex to prevent starting again before finishing previous + stb_mutex mutex; // mutex while tweaking state + stb_semaphore release; // semaphore wake up waiting threads + // we have to wake them up one at a time, rather than using a single release + // call, because win32 semaphores don't let you dynamically change the max count! +}; + +stb_sync stb_sync_new(void) +{ + stb_sync s = (stb_sync) malloc(sizeof(*s)); + if (!s) return s; + + s->target = s->sofar = s->waiting = 0; + s->mutex = stb_mutex_new(); + s->start = stb_mutex_new(); + s->release = stb_sem_new(1); + if (s->mutex == STB_MUTEX_NULL || s->release == STB_SEMAPHORE_NULL || s->start == STB_MUTEX_NULL) { + stb_mutex_delete(s->mutex); + stb_mutex_delete(s->mutex); + stb_sem_delete(s->release); + free(s); + return NULL; + } + return s; +} + +void stb_sync_delete(stb_sync s) +{ + if (s->waiting) { + // it's bad to delete while there are threads waiting! + // shall we wait for them to reach, or just bail? just bail + assert(0); + } + stb_mutex_delete(s->mutex); + stb_mutex_delete(s->release); + free(s); +} + +int stb_sync_set_target(stb_sync s, int count) +{ + // don't allow setting a target until the last one is fully released; + // note that this can lead to inefficient pipelining, and maybe we'd + // be better off ping-ponging between two internal syncs? + // I tried seeing how often this happened using TryEnterCriticalSection + // and could _never_ get it to happen in imv(stb), even with more threads + // than processors. So who knows! + stb_mutex_begin(s->start); + + // this mutex is pointless, since it's not valid for threads + // to call reach() before anyone calls set_target() anyway + stb_mutex_begin(s->mutex); + + assert(s->target == 0); // enforced by start mutex + s->target = count; + s->sofar = 0; + s->waiting = 0; + stb_mutex_end(s->mutex); + return STB_TRUE; +} + +void stb__sync_release(stb_sync s) +{ + if (s->waiting) + stb_sem_release(s->release); + else { + s->target = 0; + stb_mutex_end(s->start); + } +} + +int stb_sync_reach(stb_sync s) +{ + int n; + stb_mutex_begin(s->mutex); + assert(s->sofar < s->target); + n = ++s->sofar; // record this value to avoid possible race if we did 'return s->sofar'; + if (s->sofar == s->target) + stb__sync_release(s); + stb_mutex_end(s->mutex); + return n; +} + +void stb_sync_reach_and_wait(stb_sync s) +{ + stb_mutex_begin(s->mutex); + assert(s->sofar < s->target); + ++s->sofar; + if (s->sofar == s->target) { + stb__sync_release(s); + stb_mutex_end(s->mutex); + } else { + ++s->waiting; // we're waiting, so one more waiter + stb_mutex_end(s->mutex); // release the mutex to other threads + + stb_sem_waitfor(s->release); // wait for merge completion + + stb_mutex_begin(s->mutex); // on merge completion, grab the mutex + --s->waiting; // we're done waiting + stb__sync_release(s); // restart the next waiter + stb_mutex_end(s->mutex); // and now we're done + // this ends the same as the first case, but it's a lot + // clearer to understand without sharing the code + } +} + +struct stb__threadqueue +{ + stb_mutex add, remove; + stb_semaphore nonempty, nonfull; + int head_blockers; // number of threads blocking--used to know whether to release(avail) + int tail_blockers; + int head, tail, array_size, growable; + int item_size; + char *data; +}; + +static int stb__tq_wrap(volatile stb_threadqueue *z, int p) +{ + if (p == z->array_size) + return p - z->array_size; + else + return p; +} + +int stb__threadq_get_raw(stb_threadqueue *tq2, void *output, int block) +{ + volatile stb_threadqueue *tq = (volatile stb_threadqueue *) tq2; + if (tq->head == tq->tail && !block) return 0; + + stb_mutex_begin(tq->remove); + + while (tq->head == tq->tail) { + if (!block) { + stb_mutex_end(tq->remove); + return 0; + } + ++tq->head_blockers; + stb_mutex_end(tq->remove); + + stb_sem_waitfor(tq->nonempty); + + stb_mutex_begin(tq->remove); + --tq->head_blockers; + } + + memcpy(output, tq->data + tq->head*tq->item_size, tq->item_size); + stb_barrier(); + tq->head = stb__tq_wrap(tq, tq->head+1); + + stb_sem_release(tq->nonfull); + if (tq->head_blockers) // can't check if actually non-empty due to race? + stb_sem_release(tq->nonempty); // if there are other blockers, wake one + + stb_mutex_end(tq->remove); + return STB_TRUE; +} + +int stb__threadq_grow(volatile stb_threadqueue *tq) +{ + int n; + char *p; + assert(tq->remove != STB_MUTEX_NULL); // must have this to allow growth! + stb_mutex_begin(tq->remove); + + n = tq->array_size * 2; + p = (char *) realloc(tq->data, n * tq->item_size); + if (p == NULL) { + stb_mutex_end(tq->remove); + stb_mutex_end(tq->add); + return STB_FALSE; + } + if (tq->tail < tq->head) { + memcpy(p + tq->array_size * tq->item_size, p, tq->tail * tq->item_size); + tq->tail += tq->array_size; + } + tq->data = p; + tq->array_size = n; + + stb_mutex_end(tq->remove); + return STB_TRUE; +} + +int stb__threadq_add_raw(stb_threadqueue *tq2, void *input, int block) +{ + int tail,pos; + volatile stb_threadqueue *tq = (volatile stb_threadqueue *) tq2; + stb_mutex_begin(tq->add); + for(;;) { + pos = tq->tail; + tail = stb__tq_wrap(tq, pos+1); + if (tail != tq->head) break; + + // full + if (tq->growable) { + if (!stb__threadq_grow(tq)) { + stb_mutex_end(tq->add); + return STB_FALSE; // out of memory + } + } else if (!block) { + stb_mutex_end(tq->add); + return STB_FALSE; + } else { + ++tq->tail_blockers; + stb_mutex_end(tq->add); + + stb_sem_waitfor(tq->nonfull); + + stb_mutex_begin(tq->add); + --tq->tail_blockers; + } + } + memcpy(tq->data + tq->item_size * pos, input, tq->item_size); + stb_barrier(); + tq->tail = tail; + stb_sem_release(tq->nonempty); + if (tq->tail_blockers) // can't check if actually non-full due to race? + stb_sem_release(tq->nonfull); + stb_mutex_end(tq->add); + return STB_TRUE; +} + +int stb_threadq_length(stb_threadqueue *tq2) +{ + int a,b,n; + volatile stb_threadqueue *tq = (volatile stb_threadqueue *) tq2; + stb_mutex_begin(tq->add); + a = tq->head; + b = tq->tail; + n = tq->array_size; + stb_mutex_end(tq->add); + if (a > b) b += n; + return b-a; +} + +int stb_threadq_get(stb_threadqueue *tq, void *output) +{ + return stb__threadq_get_raw(tq, output, STB_FALSE); +} + +void stb_threadq_get_block(stb_threadqueue *tq, void *output) +{ + stb__threadq_get_raw(tq, output, STB_TRUE); +} + +int stb_threadq_add(stb_threadqueue *tq, void *input) +{ + return stb__threadq_add_raw(tq, input, STB_FALSE); +} + +int stb_threadq_add_block(stb_threadqueue *tq, void *input) +{ + return stb__threadq_add_raw(tq, input, STB_TRUE); +} + +void stb_threadq_delete(stb_threadqueue *tq) +{ + if (tq) { + free(tq->data); + stb_mutex_delete(tq->add); + stb_mutex_delete(tq->remove); + stb_sem_delete(tq->nonempty); + stb_sem_delete(tq->nonfull); + free(tq); + } +} + +#define STB_THREADQUEUE_DYNAMIC 0 +stb_threadqueue *stb_threadq_new(int item_size, int num_items, int many_add, int many_remove) +{ + int error=0; + stb_threadqueue *tq = (stb_threadqueue *) malloc(sizeof(*tq)); + if (tq == NULL) return NULL; + + if (num_items == STB_THREADQUEUE_DYNAMIC) { + tq->growable = STB_TRUE; + num_items = 32; + } else + tq->growable = STB_FALSE; + + tq->item_size = item_size; + tq->array_size = num_items+1; + + tq->add = tq->remove = STB_MUTEX_NULL; + tq->nonempty = tq->nonfull = STB_SEMAPHORE_NULL; + tq->data = NULL; + if (many_add) + { tq->add = stb_mutex_new(); if (tq->add == STB_MUTEX_NULL) goto error; } + if (many_remove || tq->growable) + { tq->remove = stb_mutex_new(); if (tq->remove == STB_MUTEX_NULL) goto error; } + tq->nonempty = stb_sem_new(1); if (tq->nonempty == STB_SEMAPHORE_NULL) goto error; + tq->nonfull = stb_sem_new(1); if (tq->nonfull == STB_SEMAPHORE_NULL) goto error; + tq->data = (char *) malloc(tq->item_size * tq->array_size); + if (tq->data == NULL) goto error; + + tq->head = tq->tail = 0; + tq->head_blockers = tq->tail_blockers = 0; + + return tq; + +error: + stb_threadq_delete(tq); + return NULL; +} + +typedef struct +{ + stb_thread_func f; + void *d; + volatile void **retval; + stb_sync sync; +} stb__workinfo; + +//static volatile stb__workinfo *stb__work; + +struct stb__workqueue +{ + int numthreads; + stb_threadqueue *tq; +}; + +static stb_workqueue *stb__work_global; + +static void *stb__thread_workloop(void *p) +{ + volatile stb_workqueue *q = (volatile stb_workqueue *) p; + for(;;) { + void *z; + stb__workinfo w; + stb_threadq_get_block(q->tq, &w); + if (w.f == NULL) // null work is a signal to end the thread + return NULL; + z = w.f(w.d); + if (w.retval) { stb_barrier(); *w.retval = z; } + if (w.sync != STB_SYNC_NULL) stb_sync_reach(w.sync); + } +} + +stb_workqueue *stb_workq_new(int num_threads, int max_units) +{ + return stb_workq_new_flags(num_threads, max_units, 0,0); +} + +stb_workqueue *stb_workq_new_flags(int numthreads, int max_units, int no_add_mutex, int no_remove_mutex) +{ + stb_workqueue *q = (stb_workqueue *) malloc(sizeof(*q)); + if (q == NULL) return NULL; + q->tq = stb_threadq_new(sizeof(stb__workinfo), max_units, !no_add_mutex, !no_remove_mutex); + if (q->tq == NULL) { free(q); return NULL; } + q->numthreads = 0; + stb_workq_numthreads(q, numthreads); + return q; +} + +void stb_workq_delete(stb_workqueue *q) +{ + while (stb_workq_length(q) != 0) + stb__thread_sleep(1); + stb_threadq_delete(q->tq); + free(q); +} + +static int stb__work_maxitems = STB_THREADQUEUE_DYNAMIC; + +static void stb_work_init(int num_threads) +{ + if (stb__work_global == NULL) { + stb__threadmutex_init(); + stb_mutex_begin(stb__workmutex); + stb_barrier(); + if (*(stb_workqueue * volatile *) &stb__work_global == NULL) + stb__work_global = stb_workq_new(num_threads, stb__work_maxitems); + stb_mutex_end(stb__workmutex); + } +} + +static int stb__work_raw(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code, stb_sync rel) +{ + stb__workinfo w; + if (q == NULL) { + stb_work_init(1); + q = stb__work_global; + } + w.f = f; + w.d = d; + w.retval = return_code; + w.sync = rel; + return stb_threadq_add(q->tq, &w); +} + +int stb_workq_length(stb_workqueue *q) +{ + return stb_threadq_length(q->tq); +} + +int stb_workq(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code) +{ + if (f == NULL) return 0; + return stb_workq_reach(q, f, d, return_code, NULL); +} + +int stb_workq_reach(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code, stb_sync rel) +{ + if (f == NULL) return 0; + return stb__work_raw(q, f, d, return_code, rel); +} + +static void stb__workq_numthreads(stb_workqueue *q, int n) +{ + while (q->numthreads < n) { + stb_create_thread(stb__thread_workloop, q); + ++q->numthreads; + } + while (q->numthreads > n) { + stb__work_raw(q, NULL, NULL, NULL, NULL); + --q->numthreads; + } +} + +void stb_workq_numthreads(stb_workqueue *q, int n) +{ + stb_mutex_begin(stb__threadmutex); + stb__workq_numthreads(q,n); + stb_mutex_end(stb__threadmutex); +} + +int stb_work_maxunits(int n) +{ + if (stb__work_global == NULL) { + stb__work_maxitems = n; + stb_work_init(1); + } + return stb__work_maxitems; +} + +int stb_work(stb_thread_func f, void *d, volatile void **return_code) +{ + return stb_workq(stb__work_global, f,d,return_code); +} + +int stb_work_reach(stb_thread_func f, void *d, volatile void **return_code, stb_sync rel) +{ + return stb_workq_reach(stb__work_global, f,d,return_code,rel); +} + +void stb_work_numthreads(int n) +{ + if (stb__work_global == NULL) + stb_work_init(n); + else + stb_workq_numthreads(stb__work_global, n); +} +#endif // STB_DEFINE + + +////////////////////////////////////////////////////////////////////////////// +// +// Background disk I/O +// +// + +#define STB_BGIO_READ_ALL (-1) +STB_EXTERN int stb_bgio_read (char *filename, int offset, int len, stb_uchar **result, int *olen); +STB_EXTERN int stb_bgio_readf (FILE *f , int offset, int len, stb_uchar **result, int *olen); +STB_EXTERN int stb_bgio_read_to (char *filename, int offset, int len, stb_uchar *buffer, int *olen); +STB_EXTERN int stb_bgio_readf_to(FILE *f , int offset, int len, stb_uchar *buffer, int *olen); + +typedef struct +{ + int have_data; + int is_valid; + int is_dir; + time_t filetime; + stb_int64 filesize; +} stb_bgstat; + +STB_EXTERN int stb_bgio_stat (char *filename, stb_bgstat *result); + +#ifdef STB_DEFINE + +static stb_workqueue *stb__diskio; +static stb_mutex stb__diskio_mutex; + +void stb_thread_cleanup(void) +{ + if (stb__work_global) stb_workq_delete(stb__work_global); stb__work_global = NULL; + if (stb__threadmutex) stb_mutex_delete(stb__threadmutex); stb__threadmutex = NULL; + if (stb__workmutex) stb_mutex_delete(stb__workmutex); stb__workmutex = NULL; + if (stb__diskio) stb_workq_delete(stb__diskio); stb__diskio = NULL; + if (stb__diskio_mutex)stb_mutex_delete(stb__diskio_mutex);stb__diskio_mutex= NULL; +} + + +typedef struct +{ + char *filename; + FILE *f; + int offset; + int len; + + stb_bgstat *stat_out; + stb_uchar *output; + stb_uchar **result; + int *len_output; + int *flag; +} stb__disk_command; + +#define STB__MAX_DISK_COMMAND 100 +static stb__disk_command stb__dc_queue[STB__MAX_DISK_COMMAND]; +static int stb__dc_offset; + +void stb__io_init(void) +{ + if (!stb__diskio) { + stb__threadmutex_init(); + stb_mutex_begin(stb__threadmutex); + stb_barrier(); + if (*(stb_thread * volatile *) &stb__diskio == NULL) { + stb__diskio_mutex = stb_mutex_new(); + // use many threads so OS can try to schedule seeks + stb__diskio = stb_workq_new_flags(16,STB__MAX_DISK_COMMAND,STB_FALSE,STB_FALSE); + } + stb_mutex_end(stb__threadmutex); + } +} + +static void * stb__io_error(stb__disk_command *dc) +{ + if (dc->len_output) *dc->len_output = 0; + if (dc->result) *dc->result = NULL; + if (dc->flag) *dc->flag = -1; + return NULL; +} + +static void * stb__io_task(void *p) +{ + stb__disk_command *dc = (stb__disk_command *) p; + int len; + FILE *f; + stb_uchar *buf; + + if (dc->stat_out) { + struct _stati64 s; + if (!_stati64(dc->filename, &s)) { + dc->stat_out->filesize = s.st_size; + dc->stat_out->filetime = s.st_mtime; + dc->stat_out->is_dir = s.st_mode & _S_IFDIR; + dc->stat_out->is_valid = (s.st_mode & _S_IFREG) || dc->stat_out->is_dir; + } else + dc->stat_out->is_valid = 0; + stb_barrier(); + dc->stat_out->have_data = 1; + free(dc->filename); + return 0; + } + if (dc->f) { + #ifdef WIN32 + f = _fdopen(_dup(_fileno(dc->f)), "rb"); + #else + f = fdopen(dup(fileno(dc->f)), "rb"); + #endif + if (!f) + return stb__io_error(dc); + } else { + f = fopen(dc->filename, "rb"); + free(dc->filename); + if (!f) + return stb__io_error(dc); + } + + len = dc->len; + if (len < 0) { + fseek(f, 0, SEEK_END); + len = ftell(f) - dc->offset; + } + + if (fseek(f, dc->offset, SEEK_SET)) { + fclose(f); + return stb__io_error(dc); + } + + if (dc->output) + buf = dc->output; + else { + buf = (stb_uchar *) malloc(len); + if (buf == NULL) { + fclose(f); + return stb__io_error(dc); + } + } + + len = fread(buf, 1, len, f); + fclose(f); + if (dc->len_output) *dc->len_output = len; + if (dc->result) *dc->result = buf; + if (dc->flag) *dc->flag = 1; + + return NULL; +} + +int stb__io_add(char *fname, FILE *f, int off, int len, stb_uchar *out, stb_uchar **result, int *olen, int *flag, stb_bgstat *stat) +{ + int res; + stb__io_init(); + // do memory allocation outside of mutex + if (fname) fname = stb_p_strdup(fname); + stb_mutex_begin(stb__diskio_mutex); + { + stb__disk_command *dc = &stb__dc_queue[stb__dc_offset]; + dc->filename = fname; + dc->f = f; + dc->offset = off; + dc->len = len; + dc->output = out; + dc->result = result; + dc->len_output = olen; + dc->flag = flag; + dc->stat_out = stat; + res = stb_workq(stb__diskio, stb__io_task, dc, NULL); + if (res) + stb__dc_offset = (stb__dc_offset + 1 == STB__MAX_DISK_COMMAND ? 0 : stb__dc_offset+1); + } + stb_mutex_end(stb__diskio_mutex); + return res; +} + +int stb_bgio_read(char *filename, int offset, int len, stb_uchar **result, int *olen) +{ + return stb__io_add(filename,NULL,offset,len,NULL,result,olen,NULL,NULL); +} + +int stb_bgio_readf(FILE *f, int offset, int len, stb_uchar **result, int *olen) +{ + return stb__io_add(NULL,f,offset,len,NULL,result,olen,NULL,NULL); +} + +int stb_bgio_read_to(char *filename, int offset, int len, stb_uchar *buffer, int *olen) +{ + return stb__io_add(filename,NULL,offset,len,buffer,NULL,olen,NULL,NULL); +} + +int stb_bgio_readf_to(FILE *f, int offset, int len, stb_uchar *buffer, int *olen) +{ + return stb__io_add(NULL,f,offset,len,buffer,NULL,olen,NULL,NULL); +} + +STB_EXTERN int stb_bgio_stat (char *filename, stb_bgstat *result) +{ + result->have_data = 0; + return stb__io_add(filename,NULL,0,0,0,NULL,0,NULL, result); +} +#endif +#endif + + + +////////////////////////////////////////////////////////////////////////////// +// +// Fast malloc implementation +// +// This is a clone of TCMalloc, but without the thread support. +// 1. large objects are allocated directly, page-aligned +// 2. small objects are allocated in homogeonous heaps, 0 overhead +// +// We keep an allocation table for pages a la TCMalloc. This would +// require 4MB for the entire address space, but we only allocate +// the parts that are in use. The overhead from using homogenous heaps +// everywhere is 3MB. (That is, if you allocate 1 object of each size, +// you'll use 3MB.) + +#if defined(STB_DEFINE) && ((defined(_WIN32) && !defined(_M_AMD64)) || defined(STB_FASTMALLOC)) + +#ifdef _WIN32 + #ifndef _WINDOWS_ + #ifndef STB__IMPORT + #define STB__IMPORT STB_EXTERN __declspec(dllimport) + #define STB__DW unsigned long + #endif + STB__IMPORT void * __stdcall VirtualAlloc(void *p, unsigned long size, unsigned long type, unsigned long protect); + STB__IMPORT int __stdcall VirtualFree(void *p, unsigned long size, unsigned long freetype); + #endif + #define stb__alloc_pages_raw(x) (stb_uint32) VirtualAlloc(NULL, (x), 0x3000, 0x04) + #define stb__dealloc_pages_raw(p) VirtualFree((void *) p, 0, 0x8000) +#else + #error "Platform not currently supported" +#endif + +typedef struct stb__span +{ + int start, len; + struct stb__span *next, *prev; + void *first_free; + unsigned short list; // 1..256 free; 257..511 sizeclass; 0=large block + short allocations; // # outstanding allocations for sizeclass +} stb__span; // 24 + +static stb__span **stb__span_for_page; +static int stb__firstpage, stb__lastpage; +static void stb__update_page_range(int first, int last) +{ + stb__span **sfp; + int i, f,l; + if (first >= stb__firstpage && last <= stb__lastpage) return; + if (stb__span_for_page == NULL) { + f = first; + l = f+stb_max(last-f, 16384); + l = stb_min(l, 1<<20); + } else if (last > stb__lastpage) { + f = stb__firstpage; + l = f + (stb__lastpage - f) * 2; + l = stb_clamp(last, l,1<<20); + } else { + l = stb__lastpage; + f = l - (l - stb__firstpage) * 2; + f = stb_clamp(f, 0,first); + } + sfp = (stb__span **) stb__alloc_pages_raw(sizeof(void *) * (l-f)); + for (i=f; i < stb__firstpage; ++i) sfp[i - f] = NULL; + for ( ; i < stb__lastpage ; ++i) sfp[i - f] = stb__span_for_page[i - stb__firstpage]; + for ( ; i < l ; ++i) sfp[i - f] = NULL; + if (stb__span_for_page) stb__dealloc_pages_raw(stb__span_for_page); + stb__firstpage = f; + stb__lastpage = l; + stb__span_for_page = sfp; +} + +static stb__span *stb__span_free=NULL; +static stb__span *stb__span_first, *stb__span_end; +static stb__span *stb__span_alloc(void) +{ + stb__span *s = stb__span_free; + if (s) + stb__span_free = s->next; + else { + if (!stb__span_first) { + stb__span_first = (stb__span *) stb__alloc_pages_raw(65536); + if (stb__span_first == NULL) return NULL; + stb__span_end = stb__span_first + (65536 / sizeof(stb__span)); + } + s = stb__span_first++; + if (stb__span_first == stb__span_end) stb__span_first = NULL; + } + return s; +} + +static stb__span *stb__spanlist[512]; + +static void stb__spanlist_unlink(stb__span *s) +{ + if (s->prev) + s->prev->next = s->next; + else { + int n = s->list; + assert(stb__spanlist[n] == s); + stb__spanlist[n] = s->next; + } + if (s->next) + s->next->prev = s->prev; + s->next = s->prev = NULL; + s->list = 0; +} + +static void stb__spanlist_add(int n, stb__span *s) +{ + s->list = n; + s->next = stb__spanlist[n]; + s->prev = NULL; + stb__spanlist[n] = s; + if (s->next) s->next->prev = s; +} + +#define stb__page_shift 12 +#define stb__page_size (1 << stb__page_shift) +#define stb__page_number(x) ((x) >> stb__page_shift) +#define stb__page_address(x) ((x) << stb__page_shift) + +static void stb__set_span_for_page(stb__span *s) +{ + int i; + for (i=0; i < s->len; ++i) + stb__span_for_page[s->start + i - stb__firstpage] = s; +} + +static stb__span *stb__coalesce(stb__span *a, stb__span *b) +{ + assert(a->start + a->len == b->start); + if (a->list) stb__spanlist_unlink(a); + if (b->list) stb__spanlist_unlink(b); + a->len += b->len; + b->len = 0; + b->next = stb__span_free; + stb__span_free = b; + stb__set_span_for_page(a); + return a; +} + +static void stb__free_span(stb__span *s) +{ + stb__span *n = NULL; + if (s->start > stb__firstpage) { + n = stb__span_for_page[s->start-1 - stb__firstpage]; + if (n && n->allocations == -2 && n->start + n->len == s->start) s = stb__coalesce(n,s); + } + if (s->start + s->len < stb__lastpage) { + n = stb__span_for_page[s->start + s->len - stb__firstpage]; + if (n && n->allocations == -2 && s->start + s->len == n->start) s = stb__coalesce(s,n); + } + s->allocations = -2; + stb__spanlist_add(s->len > 256 ? 256 : s->len, s); +} + +static stb__span *stb__alloc_pages(int num) +{ + stb__span *s = stb__span_alloc(); + int p; + if (!s) return NULL; + p = stb__alloc_pages_raw(num << stb__page_shift); + if (p == 0) { s->next = stb__span_free; stb__span_free = s; return 0; } + assert(stb__page_address(stb__page_number(p)) == p); + p = stb__page_number(p); + stb__update_page_range(p, p+num); + s->start = p; + s->len = num; + s->next = NULL; + s->prev = NULL; + stb__set_span_for_page(s); + return s; +} + +static stb__span *stb__alloc_span(int pagecount) +{ + int i; + stb__span *p = NULL; + for(i=pagecount; i < 256; ++i) + if (stb__spanlist[i]) { + p = stb__spanlist[i]; + break; + } + if (!p) { + p = stb__spanlist[256]; + while (p && p->len < pagecount) + p = p->next; + } + if (!p) { + p = stb__alloc_pages(pagecount < 16 ? 16 : pagecount); + if (p == NULL) return 0; + } else + stb__spanlist_unlink(p); + + if (p->len > pagecount) { + stb__span *q = stb__span_alloc(); + if (q) { + q->start = p->start + pagecount; + q->len = p->len - pagecount; + p->len = pagecount; + for (i=0; i < q->len; ++i) + stb__span_for_page[q->start+i - stb__firstpage] = q; + stb__spanlist_add(q->len > 256 ? 256 : q->len, q); + } + } + return p; +} + +#define STB__MAX_SMALL_SIZE 32768 +#define STB__MAX_SIZE_CLASSES 256 + +static unsigned char stb__class_base[32]; +static unsigned char stb__class_shift[32]; +static unsigned char stb__pages_for_class[STB__MAX_SIZE_CLASSES]; +static int stb__size_for_class[STB__MAX_SIZE_CLASSES]; + +stb__span *stb__get_nonempty_sizeclass(int c) +{ + int s = c + 256, i, size, tsize; // remap to span-list index + char *z; + void *q; + stb__span *p = stb__spanlist[s]; + if (p) { + if (p->first_free) return p; // fast path: it's in the first one in list + for (p=p->next; p; p=p->next) + if (p->first_free) { + // move to front for future queries + stb__spanlist_unlink(p); + stb__spanlist_add(s, p); + return p; + } + } + // no non-empty ones, so allocate a new one + p = stb__alloc_span(stb__pages_for_class[c]); + if (!p) return NULL; + // create the free list up front + size = stb__size_for_class[c]; + tsize = stb__pages_for_class[c] << stb__page_shift; + i = 0; + z = (char *) stb__page_address(p->start); + q = NULL; + while (i + size <= tsize) { + * (void **) z = q; q = z; + z += size; + i += size; + } + p->first_free = q; + p->allocations = 0; + stb__spanlist_add(s,p); + return p; +} + +static int stb__sizeclass(size_t sz) +{ + int z = stb_log2_floor(sz); // -1 below to group e.g. 13,14,15,16 correctly + return stb__class_base[z] + ((sz-1) >> stb__class_shift[z]); +} + +static void stb__init_sizeclass(void) +{ + int i, size, overhead; + int align_shift = 2; // allow 4-byte and 12-byte blocks as well, vs. TCMalloc + int next_class = 1; + int last_log = 0; + + for (i = 0; i < align_shift; i++) { + stb__class_base [i] = next_class; + stb__class_shift[i] = align_shift; + } + + for (size = 1 << align_shift; size <= STB__MAX_SMALL_SIZE; size += 1 << align_shift) { + i = stb_log2_floor(size); + if (i > last_log) { + if (size == 16) ++align_shift; // switch from 4-byte to 8-byte alignment + else if (size >= 128 && align_shift < 8) ++align_shift; + stb__class_base[i] = next_class - ((size-1) >> align_shift); + stb__class_shift[i] = align_shift; + last_log = i; + } + stb__size_for_class[next_class++] = size; + } + + for (i=1; i <= STB__MAX_SMALL_SIZE; ++i) + assert(i <= stb__size_for_class[stb__sizeclass(i)]); + + overhead = 0; + for (i = 1; i < next_class; i++) { + int s = stb__size_for_class[i]; + size = stb__page_size; + while (size % s > size >> 3) + size += stb__page_size; + stb__pages_for_class[i] = (unsigned char) (size >> stb__page_shift); + overhead += size; + } + assert(overhead < (4 << 20)); // make sure it's under 4MB of overhead +} + +#ifdef STB_DEBUG +#define stb__smemset(a,b,c) memset((void *) a, b, c) +#elif defined(STB_FASTMALLOC_INIT) +#define stb__smemset(a,b,c) memset((void *) a, b, c) +#else +#define stb__smemset(a,b,c) +#endif +void *stb_smalloc(size_t sz) +{ + stb__span *s; + if (sz == 0) return NULL; + if (stb__size_for_class[1] == 0) stb__init_sizeclass(); + if (sz > STB__MAX_SMALL_SIZE) { + s = stb__alloc_span((sz + stb__page_size - 1) >> stb__page_shift); + if (s == NULL) return NULL; + s->list = 0; + s->next = s->prev = NULL; + s->allocations = -32767; + stb__smemset(stb__page_address(s->start), 0xcd, (sz+3)&~3); + return (void *) stb__page_address(s->start); + } else { + void *p; + int c = stb__sizeclass(sz); + s = stb__spanlist[256+c]; + if (!s || !s->first_free) + s = stb__get_nonempty_sizeclass(c); + if (s == NULL) return NULL; + p = s->first_free; + s->first_free = * (void **) p; + ++s->allocations; + stb__smemset(p,0xcd, sz); + return p; + } +} + +int stb_ssize(void *p) +{ + stb__span *s; + if (p == NULL) return 0; + s = stb__span_for_page[stb__page_number((stb_uint) p) - stb__firstpage]; + if (s->list >= 256) { + return stb__size_for_class[s->list - 256]; + } else { + assert(s->list == 0); + return s->len << stb__page_shift; + } +} + +void stb_sfree(void *p) +{ + stb__span *s; + if (p == NULL) return; + s = stb__span_for_page[stb__page_number((stb_uint) p) - stb__firstpage]; + if (s->list >= 256) { + stb__smemset(p, 0xfe, stb__size_for_class[s->list-256]); + * (void **) p = s->first_free; + s->first_free = p; + if (--s->allocations == 0) { + stb__spanlist_unlink(s); + stb__free_span(s); + } + } else { + assert(s->list == 0); + stb__smemset(p, 0xfe, stb_ssize(p)); + stb__free_span(s); + } +} + +void *stb_srealloc(void *p, size_t sz) +{ + size_t cur_size; + if (p == NULL) return stb_smalloc(sz); + if (sz == 0) { stb_sfree(p); return NULL; } + cur_size = stb_ssize(p); + if (sz > cur_size || sz <= (cur_size >> 1)) { + void *q; + if (sz > cur_size && sz < (cur_size << 1)) sz = cur_size << 1; + q = stb_smalloc(sz); if (q == NULL) return NULL; + memcpy(q, p, sz < cur_size ? sz : cur_size); + stb_sfree(p); + return q; + } + return p; +} + +void *stb_scalloc(size_t n, size_t sz) +{ + void *p; + if (n == 0 || sz == 0) return NULL; + if (stb_log2_ceil(n) + stb_log2_ceil(n) >= 32) return NULL; + p = stb_smalloc(n*sz); + if (p) memset(p, 0, n*sz); + return p; +} + +char *stb_sstrdup(char *s) +{ + int n = strlen(s); + char *p = (char *) stb_smalloc(n+1); + if (p) stb_p_strcpy_s(p,n+1,s); + return p; +} +#endif // STB_DEFINE + + + +////////////////////////////////////////////////////////////////////////////// +// +// Source code constants +// +// This is a trivial system to let you specify constants in source code, +// then while running you can change the constants. +// +// Note that you can't wrap the #defines, because we need to know their +// names. So we provide a pre-wrapped version without 'STB_' for convenience; +// to request it, #define STB_CONVENIENT_H, yielding: +// KI -- integer +// KU -- unsigned integer +// KF -- float +// KD -- double +// KS -- string constant +// +// Defaults to functioning in debug build, not in release builds. +// To force on, define STB_ALWAYS_H + +#ifdef STB_CONVENIENT_H +#define KI(x) STB_I(x) +#define KU(x) STB_UI(x) +#define KF(x) STB_F(x) +#define KD(x) STB_D(x) +#define KS(x) STB_S(x) +#endif + +STB_EXTERN void stb_source_path(char *str); +#ifdef STB_DEFINE +char *stb__source_path; +void stb_source_path(char *path) +{ + stb__source_path = path; +} + +char *stb__get_sourcefile_path(char *file) +{ + static char filebuf[512]; + if (stb__source_path) { + stb_p_sprintf(filebuf stb_p_size(sizeof(filebuf)), "%s/%s", stb__source_path, file); + if (stb_fexists(filebuf)) return filebuf; + } + + if (stb_fexists(file)) return file; + + stb_p_sprintf(filebuf stb_p_size(sizeof(filebuf)), "../%s", file); + if (!stb_fexists(filebuf)) return filebuf; + + return file; +} +#endif + +#define STB_F(x) ((float) STB_H(x)) +#define STB_UI(x) ((unsigned int) STB_I(x)) + +#if !defined(STB_DEBUG) && !defined(STB_ALWAYS_H) +#define STB_D(x) ((double) (x)) +#define STB_I(x) ((int) (x)) +#define STB_S(x) ((char *) (x)) +#else +#define STB_D(x) stb__double_constant(__FILE__, __LINE__-1, (x)) +#define STB_I(x) stb__int_constant(__FILE__, __LINE__-1, (x)) +#define STB_S(x) stb__string_constant(__FILE__, __LINE__-1, (x)) + +STB_EXTERN double stb__double_constant(char *file, int line, double x); +STB_EXTERN int stb__int_constant(char *file, int line, int x); +STB_EXTERN char * stb__string_constant(char *file, int line, char *str); + +#ifdef STB_DEFINE + +enum +{ + STB__CTYPE_int, + STB__CTYPE_uint, + STB__CTYPE_float, + STB__CTYPE_double, + STB__CTYPE_string, +}; + +typedef struct +{ + int line; + int type; + union { + int ival; + double dval; + char *sval; + }; +} stb__Entry; + +typedef struct +{ + stb__Entry *entries; + char *filename; + time_t timestamp; + char **file_data; + int file_len; + unsigned short *line_index; +} stb__FileEntry; + +static void stb__constant_parse(stb__FileEntry *f, int i) +{ + char *s; + int n; + if (!stb_arr_valid(f->entries, i)) return; + n = f->entries[i].line; + if (n >= f->file_len) return; + s = f->file_data[n]; + switch (f->entries[i].type) { + case STB__CTYPE_float: + while (*s) { + if (!strncmp(s, "STB_D(", 6)) { s+=6; goto matched_float; } + if (!strncmp(s, "STB_F(", 6)) { s+=6; goto matched_float; } + if (!strncmp(s, "KD(", 3)) { s+=3; goto matched_float; } + if (!strncmp(s, "KF(", 3)) { s+=3; goto matched_float; } + ++s; + } + break; + matched_float: + f->entries[i].dval = strtod(s, NULL); + break; + case STB__CTYPE_int: + while (*s) { + if (!strncmp(s, "STB_I(", 6)) { s+=6; goto matched_int; } + if (!strncmp(s, "STB_UI(", 7)) { s+=7; goto matched_int; } + if (!strncmp(s, "KI(", 3)) { s+=3; goto matched_int; } + if (!strncmp(s, "KU(", 3)) { s+=3; goto matched_int; } + ++s; + } + break; + matched_int: { + int neg=0; + s = stb_skipwhite(s); + while (*s == '-') { neg = !neg; s = stb_skipwhite(s+1); } // handle '- - 5', pointlessly + if (s[0] == '0' && tolower(s[1]) == 'x') + f->entries[i].ival = strtol(s, NULL, 16); + else if (s[0] == '0') + f->entries[i].ival = strtol(s, NULL, 8); + else + f->entries[i].ival = strtol(s, NULL, 10); + if (neg) f->entries[i].ival = -f->entries[i].ival; + break; + } + case STB__CTYPE_string: + // @TODO + break; + } +} + +static stb_sdict *stb__constant_file_hash; + +stb__Entry *stb__constant_get_entry(char *filename, int line, int type) +{ + int i; + stb__FileEntry *f; + if (stb__constant_file_hash == NULL) + stb__constant_file_hash = stb_sdict_new(STB_TRUE); + f = (stb__FileEntry*) stb_sdict_get(stb__constant_file_hash, filename); + if (f == NULL) { + char *s = stb__get_sourcefile_path(filename); + if (s == NULL || !stb_fexists(s)) return 0; + f = (stb__FileEntry *) malloc(sizeof(*f)); + f->timestamp = stb_ftimestamp(s); + f->file_data = stb_stringfile(s, &f->file_len); + f->filename = stb_p_strdup(s); // cache the full path + f->entries = NULL; + f->line_index = 0; + stb_arr_setlen(f->line_index, f->file_len); + memset(f->line_index, 0xff, stb_arr_storage(f->line_index)); + } else { + time_t t = stb_ftimestamp(f->filename); + if (f->timestamp != t) { + f->timestamp = t; + free(f->file_data); + f->file_data = stb_stringfile(f->filename, &f->file_len); + stb_arr_setlen(f->line_index, f->file_len); + for (i=0; i < stb_arr_len(f->entries); ++i) + stb__constant_parse(f, i); + } + } + + if (line >= f->file_len) return 0; + + if (f->line_index[line] >= stb_arr_len(f->entries)) { + // need a new entry + int n = stb_arr_len(f->entries); + stb__Entry e; + e.line = line; + if (line < f->file_len) + f->line_index[line] = n; + e.type = type; + stb_arr_push(f->entries, e); + stb__constant_parse(f, n); + } + return f->entries + f->line_index[line]; +} + +double stb__double_constant(char *file, int line, double x) +{ + stb__Entry *e = stb__constant_get_entry(file, line, STB__CTYPE_float); + if (!e) return x; + return e->dval; +} + +int stb__int_constant(char *file, int line, int x) +{ + stb__Entry *e = stb__constant_get_entry(file, line, STB__CTYPE_int); + if (!e) return x; + return e->ival; +} + +char * stb__string_constant(char *file, int line, char *x) +{ + stb__Entry *e = stb__constant_get_entry(file, line, STB__CTYPE_string); + if (!e) return x; + return e->sval; +} + +#endif // STB_DEFINE +#endif // !STB_DEBUG && !STB_ALWAYS_H + +#undef STB_EXTERN +#endif // STB_INCLUDE_STB_H + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/deprecated/stb_image.c b/source/engine/thirdparty/stb/deprecated/stb_image.c new file mode 100644 index 0000000..de0d935 --- /dev/null +++ b/source/engine/thirdparty/stb/deprecated/stb_image.c @@ -0,0 +1,4678 @@ +/* stb_image - v1.35 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c + when you control the images you're loading + no warranty implied; use at your own risk + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline (no JPEG progressive) + PNG 8-bit-per-channel only + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - overridable dequantizing-IDCT, YCbCr-to-RGB conversion (define STBI_SIMD) + + Latest revisions: + 1.35 (2014-05-27) warnings, bugfixes, TGA optimization, etc + 1.34 (unknown ) warning fix + 1.33 (2011-07-14) minor fixes suggested by Dave Moore + 1.32 (2011-07-13) info support for all filetypes (SpartanJ) + 1.31 (2011-06-19) a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) added ability to load files via io callbacks (Ben Wenger) + 1.29 (2010-08-16) various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) fix bug in GIF palette transparency (SpartanJ) + + See end of file for full revision history. + + TODO: + stbi_info support for BMP,PSD,HDR,PIC + + + ============================ Contributors ========================= + + Image formats Bug fixes & warning fixes + Sean Barrett (jpeg, png, bmp) Marc LeBlanc + Nicolas Schulz (hdr, psd) Christpher Lloyd + Jonathan Dummer (tga) Dave Moore + Jean-Marc Lienher (gif) Won Chun + Tom Seddon (pic) the Horde3D community + Thatcher Ulrich (psd) Janez Zemva + Jonathan Blow + Laurent Gomila + Extensions, features Aruelien Pocheville + Jetro Lauha (stbi_info) Ryamond Barbiero + James "moose2000" Brown (iPhone PNG) David Woo + Ben "Disch" Wenger (io callbacks) Roy Eltham + Martin "SpartanJ" Golini Luke Graham + Thomas Ruf + John Bartholomew + Optimizations & bugfixes Ken Hamada + Fabian "ryg" Giesen Cort Stratton + Arseny Kapoulkine Blazej Dariusz Roszkowski + Thibault Reuille + If your name should be here but Paul Du Bois + isn't let Sean know. Guillaume George + +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// To get a header file for this, either cut and paste the header, +// or create stb_image.h, #define STBI_HEADER_FILE_ONLY, and +// then include stb_image.c from it. + +//// begin header file //////////////////////////////////////////////////// +// +// Limitations: +// - no jpeg progressive support +// - non-HDR formats support 8-bit samples only (jpeg, png) +// - no delayed line count (jpeg) -- IJG doesn't support either +// - no 1-bit BMP +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *comp -- outputs # of image components in image file +// int req_comp -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. +// If req_comp is non-zero, *comp has the number of components that _would_ +// have been output otherwise. E.g. if you set req_comp to 4, you will always +// get RGBA output, but you can check *comp to easily see if it's opaque. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() +// can be queried for an extremely brief, end-user unfriendly explanation +// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid +// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB; nominally they +// would silently load as BGR, except the existing code should have just +// failed on such iPhone PNGs. But you can disable this conversion by +// by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through. +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). + + +#ifndef STBI_NO_STDIO + +#if defined(_MSC_VER) && _MSC_VER >= 1400 +#define _CRT_SECURE_NO_WARNINGS // suppress warnings about fopen() +#pragma warning(push) +#pragma warning(disable:4996) // suppress even more warnings about fopen() +#endif +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for req_comp + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +extern stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO +extern stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +extern stbi_uc *stbi_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_HDR + extern float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + + #ifndef STBI_NO_STDIO + extern float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); + extern float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); + #endif + + extern float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + + extern void stbi_hdr_to_ldr_gamma(float gamma); + extern void stbi_hdr_to_ldr_scale(float scale); + + extern void stbi_ldr_to_hdr_gamma(float gamma); + extern void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_HDR + +// stbi_is_hdr is always defined +extern int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +extern int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +extern int stbi_is_hdr (char const *filename); +extern int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// NOT THREADSAFE +extern const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +extern void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +extern int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +extern int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +extern int stbi_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); + +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +extern void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +extern void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + + +// ZLIB client - used by PNG, available for other purposes + +extern char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +extern char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +extern char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +extern int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +extern char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +extern int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +// define faster low-level operations (typically SIMD support) +#ifdef STBI_SIMD +typedef void (*stbi_idct_8x8)(stbi_uc *out, int out_stride, short data[64], unsigned short *dequantize); +// compute an integer IDCT on "input" +// input[x] = data[x] * dequantize[x] +// write results to 'out': 64 samples, each run of 8 spaced by 'out_stride' +// CLAMP results to 0..255 +typedef void (*stbi_YCbCr_to_RGB_run)(stbi_uc *output, stbi_uc const *y, stbi_uc const *cb, stbi_uc const *cr, int count, int step); +// compute a conversion from YCbCr to RGB +// 'count' pixels +// write pixels to 'output'; each pixel is 'step' bytes (either 3 or 4; if 4, write '255' as 4th), order R,G,B +// y: Y input channel +// cb: Cb input channel; scale/biased to be 0..255 +// cr: Cr input channel; scale/biased to be 0..255 + +extern void stbi_install_idct(stbi_idct_8x8 func); +extern void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func); +#endif // STBI_SIMD + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifndef STBI_HEADER_FILE_ONLY + +#ifndef STBI_NO_HDR +#include // ldexp +#include // strcmp, strtok +#endif + +#ifndef STBI_NO_STDIO +#include +#endif +#include +#include +#include +#include +#include // ptrdiff_t on osx + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + + +#ifdef _MSC_VER +typedef unsigned char stbi__uint8; +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint8_t stbi__uint8; +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +/////////////////////////////////////////////// +// +// stbi struct and start_xxx functions + +// stbi structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi__uint8 buffer_start[128]; + + stbi__uint8 *img_buffer, *img_buffer_end; + stbi__uint8 *img_buffer_original; +} stbi; + + +static void refill_buffer(stbi *s); + +// initialize a memory-decode context +static void start_mem(stbi *s, stbi__uint8 const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi__uint8 *) buffer; + s->img_buffer_end = (stbi__uint8 *) buffer+len; +} + +// initialize a callback-based context +static void start_callbacks(stbi *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + refill_buffer(s); +} + +#ifndef STBI_NO_STDIO + +static int stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stdio_skip(void *user, int n) +{ + fseek((FILE*) user, n, SEEK_CUR); +} + +static int stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi_stdio_callbacks = +{ + stdio_read, + stdio_skip, + stdio_eof, +}; + +static void start_file(stbi *s, FILE *f) +{ + start_callbacks(s, &stbi_stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi_rewind(stbi *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; +} + +static int stbi_jpeg_test(stbi *s); +static stbi_uc *stbi_jpeg_load(stbi *s, int *x, int *y, int *comp, int req_comp); +static int stbi_jpeg_info(stbi *s, int *x, int *y, int *comp); +static int stbi_png_test(stbi *s); +static stbi_uc *stbi_png_load(stbi *s, int *x, int *y, int *comp, int req_comp); +static int stbi_png_info(stbi *s, int *x, int *y, int *comp); +static int stbi_bmp_test(stbi *s); +static stbi_uc *stbi_bmp_load(stbi *s, int *x, int *y, int *comp, int req_comp); +static int stbi_tga_test(stbi *s); +static stbi_uc *stbi_tga_load(stbi *s, int *x, int *y, int *comp, int req_comp); +static int stbi_tga_info(stbi *s, int *x, int *y, int *comp); +static int stbi_psd_test(stbi *s); +static stbi_uc *stbi_psd_load(stbi *s, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_HDR +static int stbi_hdr_test(stbi *s); +static float *stbi_hdr_load(stbi *s, int *x, int *y, int *comp, int req_comp); +#endif +static int stbi_pic_test(stbi *s); +static stbi_uc *stbi_pic_load(stbi *s, int *x, int *y, int *comp, int req_comp); +static int stbi_gif_test(stbi *s); +static stbi_uc *stbi_gif_load(stbi *s, int *x, int *y, int *comp, int req_comp); +static int stbi_gif_info(stbi *s, int *x, int *y, int *comp); + + +// this is not threadsafe +static const char *failure_reason; + +const char *stbi_failure_reason(void) +{ + return failure_reason; +} + +static int e(const char *str) +{ + failure_reason = str; + return 0; +} + +// e - error +// epf - error returning pointer to float +// epuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define e(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define e(x,y) e(y) +#else + #define e(x,y) e(x) +#endif + +#define epf(x,y) ((float *) (e(x,y)?NULL:NULL)) +#define epuc(x,y) ((unsigned char *) (e(x,y)?NULL:NULL)) + +void stbi_image_free(void *retval_from_stbi_load) +{ + free(retval_from_stbi_load); +} + +#ifndef STBI_NO_HDR +static float *ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +static stbi_uc *hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static unsigned char *stbi_load_main(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + if (stbi_jpeg_test(s)) return stbi_jpeg_load(s,x,y,comp,req_comp); + if (stbi_png_test(s)) return stbi_png_load(s,x,y,comp,req_comp); + if (stbi_bmp_test(s)) return stbi_bmp_load(s,x,y,comp,req_comp); + if (stbi_gif_test(s)) return stbi_gif_load(s,x,y,comp,req_comp); + if (stbi_psd_test(s)) return stbi_psd_load(s,x,y,comp,req_comp); + if (stbi_pic_test(s)) return stbi_pic_load(s,x,y,comp,req_comp); + + #ifndef STBI_NO_HDR + if (stbi_hdr_test(s)) { + float *hdr = stbi_hdr_load(s, x,y,comp,req_comp); + return hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + // test tga last because it's a crappy test! + if (stbi_tga_test(s)) + return stbi_tga_load(s,x,y,comp,req_comp); + return epuc("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_STDIO +unsigned char *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = fopen(filename, "rb"); + unsigned char *result; + if (!f) return epuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +unsigned char *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi s; + start_file(&s,f); + result = stbi_load_main(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} +#endif //!STBI_NO_STDIO + +unsigned char *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s,buffer,len); + return stbi_load_main(&s,x,y,comp,req_comp); +} + +unsigned char *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi_load_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_HDR + +float *stbi_loadf_main(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi_hdr_test(s)) + return stbi_hdr_load(s,x,y,comp,req_comp); + #endif + data = stbi_load_main(s, x, y, comp, req_comp); + if (data) + return ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return epf("unknown image type", "Image not of any known type, or corrupt"); +} + +float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s,buffer,len); + return stbi_loadf_main(&s,x,y,comp,req_comp); +} + +float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi_loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = fopen(filename, "rb"); + float *result; + if (!f) return epf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_file(&s,f); + return stbi_loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_HDR + +// these is-hdr-or-not is defined independent of whether STBI_NO_HDR is +// defined, for API simplicity; if STBI_NO_HDR is defined, it always +// reports false! + +int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi s; + start_mem(&s,buffer,len); + return stbi_hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +extern int stbi_is_hdr (char const *filename) +{ + FILE *f = fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +extern int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + stbi s; + start_file(&s,f); + return stbi_hdr_test(&s); + #else + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +extern int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi s; + start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi_hdr_test(&s); + #else + return 0; + #endif +} + +#ifndef STBI_NO_HDR +static float h2l_gamma_i=1.0f/2.2f, h2l_scale_i=1.0f; +static float l2h_gamma=2.2f, l2h_scale=1.0f; + +void stbi_hdr_to_ldr_gamma(float gamma) { h2l_gamma_i = 1/gamma; } +void stbi_hdr_to_ldr_scale(float scale) { h2l_scale_i = 1/scale; } + +void stbi_ldr_to_hdr_gamma(float gamma) { l2h_gamma = gamma; } +void stbi_ldr_to_hdr_scale(float scale) { l2h_scale = scale; } +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + SCAN_load=0, + SCAN_type, + SCAN_header +}; + +static void refill_buffer(stbi *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static int get8(stbi *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int at_eof(stbi *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} + +stbi_inline static stbi__uint8 get8u(stbi *s) +{ + return (stbi__uint8) get8(s); +} + +static void skip(stbi *s, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} + +static int getn(stbi *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int get16(stbi *s) +{ + int z = get8(s); + return (z << 8) + get8(s); +} + +static stbi__uint32 get32(stbi *s) +{ + stbi__uint32 z = get16(s); + return (z << 16) + get16(s); +} + +static int get16le(stbi *s) +{ + int z = get8(s); + return z + (get8(s) << 8); +} + +static stbi__uint32 get32le(stbi *s) +{ + stbi__uint32 z = get16le(s); + return z + (get16le(s) << 16); +} + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi__uint8 compute_y(int r, int g, int b) +{ + return (stbi__uint8) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + assert(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) malloc(req_comp * x * y); + if (good == NULL) { + free(data); + return epuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define COMBO(a,b) ((a)*8+(b)) + #define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (COMBO(img_n, req_comp)) { + CASE(1,2) dest[0]=src[0], dest[1]=255; break; + CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; + CASE(2,1) dest[0]=src[0]; break; + CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; + CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; + CASE(3,1) dest[0]=compute_y(src[0],src[1],src[2]); break; + CASE(3,2) dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = 255; break; + CASE(4,1) dest[0]=compute_y(src[0],src[1],src[2]); break; + CASE(4,2) dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; + CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; + default: assert(0); + } + #undef CASE + } + + free(data); + return good; +} + +#ifndef STBI_NO_HDR +static float *ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output = (float *) malloc(x * y * comp * sizeof(float)); + if (output == NULL) { free(data); return epf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) pow(data[i*comp+k]/255.0f, l2h_gamma) * l2h_scale; + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + free(data); + return output; +} + +#define float2int(x) ((int) (x)) +static stbi_uc *hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output = (stbi_uc *) malloc(x * y * comp); + if (output == NULL) { free(data); return epuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*h2l_scale_i, h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi__uint8) float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi__uint8) float2int(z); + } + } + free(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder (not actually fully baseline implementation) +// +// simple implementation +// - channel subsampling of at most 2 in each dimension +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - uses a lot of intermediate memory, could cache poorly +// - load http://nothings.org/remote/anemones.jpg 3 times on 2.8Ghz P4 +// stb_jpeg: 1.34 seconds (MSVC6, default release build) +// stb_jpeg: 1.06 seconds (MSVC6, processor = Pentium Pro) +// IJL11.dll: 1.08 seconds (compiled by intel) +// IJG 1998: 0.98 seconds (MSVC6, makefile provided by IJG) +// IJG 1998: 0.95 seconds (MSVC6, makefile + proc=PPro) + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi__uint8 fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi__uint8 values[256]; + stbi__uint8 size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} huffman; + +typedef struct +{ + #ifdef STBI_SIMD + unsigned short dequant2[4][64]; + #endif + stbi *s; + huffman huff_dc[4]; + huffman huff_ac[4]; + stbi__uint8 dequant[4][64]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi__uint8 *data; + void *raw_data; + stbi__uint8 *linebuf; + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int scan_n, order[4]; + int restart_interval, todo; +} jpeg; + +static int build_huffman(huffman *h, int *count) +{ + int i,j,k=0,code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi__uint8) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1 << j)) return e("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi__uint8) i; + } + } + } + return 1; +} + +static void grow_buffer_unsafe(jpeg *j) +{ + do { + int b = j->nomore ? 0 : get8(j->s); + if (b == 0xff) { + int c = get8(j->s); + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static stbi__uint32 bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int decode(jpeg *j, huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & bmask[k]) + h->delta[k]; + assert((((j->code_buffer) >> (32 - h->size[c])) & bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// combined JPEG 'receive' and JPEG 'extend', since baseline +// always extends everything it receives. +stbi_inline static int extend_receive(jpeg *j, int n) +{ + unsigned int m = 1 << (n-1); + unsigned int k; + if (j->code_bits < n) grow_buffer_unsafe(j); + + #if 1 + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~bmask[n]; + k &= bmask[n]; + j->code_bits -= n; + #else + k = (j->code_buffer >> (32 - n)) & bmask[n]; + j->code_bits -= n; + j->code_buffer <<= n; + #endif + // the following test is probably a random branch that won't + // predict well. I tried to table accelerate it but failed. + // maybe it's compiling as a conditional move? + if (k < m) + return (-1 << n) + k + 1; + else + return k; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static stbi__uint8 dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int decode_block(jpeg *j, short data[64], huffman *hdc, huffman *hac, int b) +{ + int diff,dc,k; + int t = decode(j, hdc); + if (t < 0) return e("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) dc; + + // decode AC components, see JPEG spec + k = 1; + do { + int r,s; + int rs = decode(j, hac); + if (rs < 0) return e("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + data[dezigzag[k++]] = (short) extend_receive(j,s); + } + } while (k < 64); + return 1; +} + +// take a -128..127 value and clamp it and convert to 0..255 +stbi_inline static stbi__uint8 clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi__uint8) x; +} + +#define f2f(x) (int) (((x) * 4096 + 0.5)) +#define fsh(x) ((x) << 12) + +// derived from jidctint -- DCT_ISLOW +#define IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * f2f(0.5411961f); \ + t2 = p1 + p3*f2f(-1.847759065f); \ + t3 = p1 + p2*f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = fsh(p2+p3); \ + t1 = fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*f2f( 1.175875602f); \ + t0 = t0*f2f( 0.298631336f); \ + t1 = t1*f2f( 2.053119869f); \ + t2 = t2*f2f( 3.072711026f); \ + t3 = t3*f2f( 1.501321110f); \ + p1 = p5 + p1*f2f(-0.899976223f); \ + p2 = p5 + p2*f2f(-2.562915447f); \ + p3 = p3*f2f(-1.961570560f); \ + p4 = p4*f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +#ifdef STBI_SIMD +typedef unsigned short stbi_dequantize_t; +#else +typedef stbi__uint8 stbi_dequantize_t; +#endif + +// .344 seconds on 3*anemones.jpg +static void idct_block(stbi__uint8 *out, int out_stride, short data[64], stbi_dequantize_t *dequantize) +{ + int i,val[64],*v=val; + stbi_dequantize_t *dq = dequantize; + stbi__uint8 *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d,++dq, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0] * dq[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + IDCT_1D(d[ 0]*dq[ 0],d[ 8]*dq[ 8],d[16]*dq[16],d[24]*dq[24], + d[32]*dq[32],d[40]*dq[40],d[48]*dq[48],d[56]*dq[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = clamp((x0+t3) >> 17); + o[7] = clamp((x0-t3) >> 17); + o[1] = clamp((x1+t2) >> 17); + o[6] = clamp((x1-t2) >> 17); + o[2] = clamp((x2+t1) >> 17); + o[5] = clamp((x2-t1) >> 17); + o[3] = clamp((x3+t0) >> 17); + o[4] = clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SIMD +static stbi_idct_8x8 stbi_idct_installed = idct_block; + +void stbi_install_idct(stbi_idct_8x8 func) +{ + stbi_idct_installed = func; +} +#endif + +#define MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi__uint8 get_marker(jpeg *j) +{ + stbi__uint8 x; + if (j->marker != MARKER_none) { x = j->marker; j->marker = MARKER_none; return x; } + x = get8u(j->s); + if (x != 0xff) return MARKER_none; + while (x == 0xff) + x = get8u(j->s); + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, reset the entropy decoder and +// the dc prediction +static void reset(jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; + j->marker = MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int parse_entropy_coded_data(jpeg *z) +{ + reset(z); + if (z->scan_n == 1) { + int i,j; + #ifdef STBI_SIMD + __declspec(align(16)) + #endif + short data[64]; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + if (!decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; + #ifdef STBI_SIMD + stbi_idct_installed(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); + #else + idct_block(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); + #endif + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!RESTART(z->marker)) return 1; + reset(z); + } + } + } + } else { // interleaved! + int i,j,k,x,y; + short data[64]; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + if (!decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; + #ifdef STBI_SIMD + stbi_idct_installed(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); + #else + idct_block(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); + #endif + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!RESTART(z->marker)) return 1; + reset(z); + } + } + } + } + return 1; +} + +static int process_marker(jpeg *z, int m) +{ + int L; + switch (m) { + case MARKER_none: // no marker found + return e("expected marker","Corrupt JPEG"); + + case 0xC2: // SOF - progressive + return e("progressive jpeg","JPEG format not supported (progressive)"); + + case 0xDD: // DRI - specify restart interval + if (get16(z->s) != 4) return e("bad DRI len","Corrupt JPEG"); + z->restart_interval = get16(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = get16(z->s)-2; + while (L > 0) { + int q = get8(z->s); + int p = q >> 4; + int t = q & 15,i; + if (p != 0) return e("bad DQT type","Corrupt JPEG"); + if (t > 3) return e("bad DQT table","Corrupt JPEG"); + for (i=0; i < 64; ++i) + z->dequant[t][dezigzag[i]] = get8u(z->s); + #ifdef STBI_SIMD + for (i=0; i < 64; ++i) + z->dequant2[t][i] = z->dequant[t][i]; + #endif + L -= 65; + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = get16(z->s)-2; + while (L > 0) { + stbi__uint8 *v; + int sizes[16],i,n=0; + int q = get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return e("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = get8u(z->s); + L -= n; + } + return L==0; + } + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + skip(z->s, get16(z->s)-2); + return 1; + } + return 0; +} + +// after we see SOS +static int process_scan_header(jpeg *z) +{ + int i; + int Ls = get16(z->s); + z->scan_n = get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return e("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return e("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = get8(z->s), which; + int q = get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return e("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return e("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + if (get8(z->s) != 0) return e("bad SOS","Corrupt JPEG"); + get8(z->s); // should be 63, but might be 0 + if (get8(z->s) != 0) return e("bad SOS","Corrupt JPEG"); + + return 1; +} + +static int process_frame_header(jpeg *z, int scan) +{ + stbi *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = get16(s); if (Lf < 11) return e("bad SOF len","Corrupt JPEG"); // JPEG + p = get8(s); if (p != 8) return e("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = get16(s); if (s->img_y == 0) return e("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = get16(s); if (s->img_x == 0) return e("0 width","Corrupt JPEG"); // JPEG requires + c = get8(s); + if (c != 3 && c != 1) return e("bad component count","Corrupt JPEG"); // JFIF requires + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return e("bad SOF len","Corrupt JPEG"); + + for (i=0; i < s->img_n; ++i) { + z->img_comp[i].id = get8(s); + if (z->img_comp[i].id != i+1) // JFIF requires + if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files! + return e("bad component ID","Corrupt JPEG"); + q = get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return e("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return e("bad V","Corrupt JPEG"); + z->img_comp[i].tq = get8(s); if (z->img_comp[i].tq > 3) return e("bad TQ","Corrupt JPEG"); + } + + if (scan != SCAN_load) return 1; + + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return e("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].raw_data = malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); + if (z->img_comp[i].raw_data == NULL) { + for(--i; i >= 0; --i) { + free(z->img_comp[i].raw_data); + z->img_comp[i].data = NULL; + } + return e("outofmem", "Out of memory"); + } + // align blocks for installable-idct using mmx/sse + z->img_comp[i].data = (stbi__uint8*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + z->img_comp[i].linebuf = NULL; + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define DNL(x) ((x) == 0xdc) +#define SOI(x) ((x) == 0xd8) +#define EOI(x) ((x) == 0xd9) +#define SOF(x) ((x) == 0xc0 || (x) == 0xc1) +#define SOS(x) ((x) == 0xda) + +static int decode_jpeg_header(jpeg *z, int scan) +{ + int m; + z->marker = MARKER_none; // initialize cached marker to empty + m = get_marker(z); + if (!SOI(m)) return e("no SOI","Corrupt JPEG"); + if (scan == SCAN_type) return 1; + m = get_marker(z); + while (!SOF(m)) { + if (!process_marker(z,m)) return 0; + m = get_marker(z); + while (m == MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (at_eof(z->s)) return e("no SOF", "Corrupt JPEG"); + m = get_marker(z); + } + } + if (!process_frame_header(z, scan)) return 0; + return 1; +} + +static int decode_jpeg_image(jpeg *j) +{ + int m; + j->restart_interval = 0; + if (!decode_jpeg_header(j, SCAN_load)) return 0; + m = get_marker(j); + while (!EOI(m)) { + if (SOS(m)) { + if (!process_scan_header(j)) return 0; + if (!parse_entropy_coded_data(j)) return 0; + if (j->marker == MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!at_eof(j->s)) { + int x = get8(j->s); + if (x == 255) { + j->marker = get8u(j->s); + break; + } else if (x != 0) { + return 0; + } + } + // if we reach eof without hitting a marker, get_marker() below will fail and we'll eventually return 0 + } + } else { + if (!process_marker(j, m)) return 0; + } + m = get_marker(j); + } + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi__uint8 *(*resample_row_func)(stbi__uint8 *out, stbi__uint8 *in0, stbi__uint8 *in1, + int w, int hs); + +#define div4(x) ((stbi__uint8) ((x) >> 2)) + +static stbi__uint8 *resample_row_1(stbi__uint8 *out, stbi__uint8 *in_near, stbi__uint8 *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi__uint8* resample_row_v_2(stbi__uint8 *out, stbi__uint8 *in_near, stbi__uint8 *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi__uint8* resample_row_h_2(stbi__uint8 *out, stbi__uint8 *in_near, stbi__uint8 *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi__uint8 *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = div4(n+input[i-1]); + out[i*2+1] = div4(n+input[i+1]); + } + out[i*2+0] = div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define div16(x) ((stbi__uint8) ((x) >> 4)) + +static stbi__uint8 *resample_row_hv_2(stbi__uint8 *out, stbi__uint8 *in_near, stbi__uint8 *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = div16(3*t0 + t1 + 8); + out[i*2 ] = div16(3*t1 + t0 + 8); + } + out[w*2-1] = div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +static stbi__uint8 *resample_row_generic(stbi__uint8 *out, stbi__uint8 *in_near, stbi__uint8 *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) + +// 0.38 seconds on 3*anemones.jpg (0.25 with processor = Pro) +// VC6 without processor=Pro is generating multiple LEAs per multiply! +static void YCbCr_to_RGB_row(stbi__uint8 *out, const stbi__uint8 *y, const stbi__uint8 *pcb, const stbi__uint8 *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 16) + 32768; // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr*float2fixed(1.40200f); + g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); + b = y_fixed + cb*float2fixed(1.77200f); + r >>= 16; + g >>= 16; + b >>= 16; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi__uint8)r; + out[1] = (stbi__uint8)g; + out[2] = (stbi__uint8)b; + out[3] = 255; + out += step; + } +} + +#ifdef STBI_SIMD +static stbi_YCbCr_to_RGB_run stbi_YCbCr_installed = YCbCr_to_RGB_row; + +void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func) +{ + stbi_YCbCr_installed = func; +} +#endif + + +// clean up the temporary component buffers +static void cleanup_jpeg(jpeg *j) +{ + int i; + for (i=0; i < j->s->img_n; ++i) { + if (j->img_comp[i].data) { + free(j->img_comp[i].raw_data); + j->img_comp[i].data = NULL; + } + if (j->img_comp[i].linebuf) { + free(j->img_comp[i].linebuf); + j->img_comp[i].linebuf = NULL; + } + } +} + +typedef struct +{ + resample_row_func resample; + stbi__uint8 *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi_resample; + +static stbi__uint8 *load_jpeg_image(jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n; + // validate req_comp + if (req_comp < 0 || req_comp > 4) return epuc("bad req_comp", "Internal error"); + z->s->img_n = 0; + + // load a jpeg image from whichever source + if (!decode_jpeg_image(z)) { cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n; + + if (z->s->img_n == 3 && n < 3) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi__uint8 *output; + stbi__uint8 *coutput[4]; + + stbi_resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi_resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi__uint8 *) malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { cleanup_jpeg(z); return epuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = resample_row_hv_2; + else r->resample = resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi__uint8 *) malloc(n * z->s->img_x * z->s->img_y + 1); + if (!output) { cleanup_jpeg(z); return epuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi__uint8 *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi_resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi__uint8 *y = coutput[0]; + if (z->s->img_n == 3) { + #ifdef STBI_SIMD + stbi_YCbCr_installed(out, y, coutput[1], coutput[2], z->s->img_x, n); + #else + YCbCr_to_RGB_row(out, y, coutput[1], coutput[2], z->s->img_x, n); + #endif + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + stbi__uint8 *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n; // report original components, not output + return output; + } +} + +static unsigned char *stbi_jpeg_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + jpeg j; + j.s = s; + return load_jpeg_image(&j, x,y,comp,req_comp); +} + +static int stbi_jpeg_test(stbi *s) +{ + int r; + jpeg j; + j.s = s; + r = decode_jpeg_header(&j, SCAN_type); + stbi_rewind(s); + return r; +} + +static int stbi_jpeg_info_raw(jpeg *j, int *x, int *y, int *comp) +{ + if (!decode_jpeg_header(j, SCAN_header)) { + stbi_rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n; + return 1; +} + +static int stbi_jpeg_info(stbi *s, int *x, int *y, int *comp) +{ + jpeg j; + j.s = s; + return stbi_jpeg_info_raw(&j, x, y, comp); +} + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define ZFAST_BITS 9 // accelerate all cases in default tables +#define ZFAST_MASK ((1 << ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi__uint8 size[288]; + stbi__uint16 value[288]; +} zhuffman; + +stbi_inline static int bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int bit_reverse(int v, int bits) +{ + assert(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return bitreverse16(v) >> (16-bits); +} + +static int zbuild_huffman(zhuffman *z, stbi__uint8 *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 255, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + assert(sizes[i] <= (1 << i)); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return e("bad codelengths","Corrupt JPEG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + z->size[c] = (stbi__uint8)s; + z->value[c] = (stbi__uint16)i; + if (s <= ZFAST_BITS) { + int k = bit_reverse(next_code[s],s); + while (k < (1 << ZFAST_BITS)) { + z->fast[k] = (stbi__uint16) c; + k += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi__uint8 *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + zhuffman z_length, z_distance; +} zbuf; + +stbi_inline static int zget8(zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void fill_bits(zbuf *z) +{ + do { + assert(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int zreceive(zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +stbi_inline static int zhuffman_decode(zbuf *a, zhuffman *z) +{ + int b,s,k; + if (a->num_bits < 16) fill_bits(a); + b = z->fast[a->code_buffer & ZFAST_MASK]; + if (b < 0xffff) { + s = z->size[b]; + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; + } + + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = bit_reverse(a->code_buffer, 16); + for (s=ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + assert(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +static int expand(zbuf *z, int n) // need to make room for n bytes +{ + char *q; + int cur, limit; + if (!z->z_expandable) return e("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) realloc(z->zout_start, limit); + if (q == NULL) return e("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int length_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static int length_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static int dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static int dist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int parse_huffman_block(zbuf *a) +{ + for(;;) { + int z = zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return e("bad huffman code","Corrupt PNG"); // error in huffman codes + if (a->zout >= a->zout_end) if (!expand(a, 1)) return 0; + *a->zout++ = (char) z; + } else { + stbi__uint8 *p; + int len,dist; + if (z == 256) return 1; + z -= 257; + len = length_base[z]; + if (length_extra[z]) len += zreceive(a, length_extra[z]); + z = zhuffman_decode(a, &a->z_distance); + if (z < 0) return e("bad huffman code","Corrupt PNG"); + dist = dist_base[z]; + if (dist_extra[z]) dist += zreceive(a, dist_extra[z]); + if (a->zout - a->zout_start < dist) return e("bad dist","Corrupt PNG"); + if (a->zout + len > a->zout_end) if (!expand(a, len)) return 0; + p = (stbi__uint8 *) (a->zout - dist); + while (len--) + *a->zout++ = *p++; + } + } +} + +static int compute_huffman_codes(zbuf *a) +{ + static stbi__uint8 length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + zhuffman z_codelength; + stbi__uint8 lencodes[286+32+137];//padding for maximum single op + stbi__uint8 codelength_sizes[19]; + int i,n; + + int hlit = zreceive(a,5) + 257; + int hdist = zreceive(a,5) + 1; + int hclen = zreceive(a,4) + 4; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi__uint8) s; + } + if (!zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < hlit + hdist) { + int c = zhuffman_decode(a, &z_codelength); + assert(c >= 0 && c < 19); + if (c < 16) + lencodes[n++] = (stbi__uint8) c; + else if (c == 16) { + c = zreceive(a,2)+3; + memset(lencodes+n, lencodes[n-1], c); + n += c; + } else if (c == 17) { + c = zreceive(a,3)+3; + memset(lencodes+n, 0, c); + n += c; + } else { + assert(c == 18); + c = zreceive(a,7)+11; + memset(lencodes+n, 0, c); + n += c; + } + } + if (n != hlit+hdist) return e("bad codelengths","Corrupt PNG"); + if (!zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int parse_uncompressed_block(zbuf *a) +{ + stbi__uint8 header[4]; + int len,nlen,k; + if (a->num_bits & 7) + zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi__uint8) (a->code_buffer & 255); // wtf this warns? + a->code_buffer >>= 8; + a->num_bits -= 8; + } + assert(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = (stbi__uint8) zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return e("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return e("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!expand(a, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int parse_zlib_header(zbuf *a) +{ + int cmf = zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = zget8(a); + if ((cmf*256+flg) % 31 != 0) return e("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return e("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return e("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +// @TODO: should statically initialize these for optimal thread safety +static stbi__uint8 default_length[288], default_distance[32]; +static void init_defaults(void) +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) default_length[i] = 8; + for ( ; i <= 255; ++i) default_length[i] = 9; + for ( ; i <= 279; ++i) default_length[i] = 7; + for ( ; i <= 287; ++i) default_length[i] = 8; + + for (i=0; i <= 31; ++i) default_distance[i] = 5; +} + +int stbi_png_partial; // a quick hack to only allow decoding some of a PNG... I should implement real streaming support instead +static int parse_zlib(zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = zreceive(a,1); + type = zreceive(a,2); + if (type == 0) { + if (!parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!default_distance[31]) init_defaults(); + if (!zbuild_huffman(&a->z_length , default_length , 288)) return 0; + if (!zbuild_huffman(&a->z_distance, default_distance, 32)) return 0; + } else { + if (!compute_huffman_codes(a)) return 0; + } + if (!parse_huffman_block(a)) return 0; + } + if (stbi_png_partial && a->zout - a->zout_start > 65536) + break; + } while (!final); + return 1; +} + +static int do_zlib(zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return parse_zlib(a, parse_header); +} + +char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + zbuf a; + char *p = (char *) malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi__uint8 *) buffer; + a.zbuffer_end = (stbi__uint8 *) buffer + len; + if (do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + free(a.zout_start); + return NULL; + } +} + +char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + zbuf a; + char *p = (char *) malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi__uint8 *) buffer; + a.zbuffer_end = (stbi__uint8 *) buffer + len; + if (do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + free(a.zout_start); + return NULL; + } +} + +int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + zbuf a; + a.zbuffer = (stbi__uint8 *) ibuffer; + a.zbuffer_end = (stbi__uint8 *) ibuffer + ilen; + if (do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + zbuf a; + char *p = (char *) malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi__uint8 *) buffer; + a.zbuffer_end = (stbi__uint8 *) buffer+len; + if (do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + free(a.zout_start); + return NULL; + } +} + +int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + zbuf a; + a.zbuffer = (stbi__uint8 *) ibuffer; + a.zbuffer_end = (stbi__uint8 *) ibuffer + ilen; + if (do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + + +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} chunk; + +#define PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static chunk get_chunk_header(stbi *s) +{ + chunk c; + c.length = get32(s); + c.type = get32(s); + return c; +} + +static int check_png_header(stbi *s) +{ + static stbi__uint8 png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (get8u(s) != png_sig[i]) return e("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi *s; + stbi__uint8 *idata, *expanded, *out; +} png; + + +enum { + F_none=0, F_sub=1, F_up=2, F_avg=3, F_paeth=4, + F_avg_first, F_paeth_first +}; + +static stbi__uint8 first_row_filter[5] = +{ + F_none, F_sub, F_none, F_avg_first, F_paeth_first +}; + +static int paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +// create the png data from post-deflated data +static int create_png_image_raw(png *a, stbi__uint8 *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y) +{ + stbi *s = a->s; + stbi__uint32 i,j,stride = x*out_n; + int k; + int img_n = s->img_n; // copy it into a local for later + assert(out_n == s->img_n || out_n == s->img_n+1); + if (stbi_png_partial) y = 1; + a->out = (stbi__uint8 *) malloc(x * y * out_n); + if (!a->out) return e("outofmem", "Out of memory"); + if (!stbi_png_partial) { + if (s->img_x == x && s->img_y == y) { + if (raw_len != (img_n * x + 1) * y) return e("not enough pixels","Corrupt PNG"); + } else { // interlaced: + if (raw_len < (img_n * x + 1) * y) return e("not enough pixels","Corrupt PNG"); + } + } + for (j=0; j < y; ++j) { + stbi__uint8 *cur = a->out + stride*j; + stbi__uint8 *prior = cur - stride; + int filter = *raw++; + if (filter > 4) return e("invalid filter","Corrupt PNG"); + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + // handle first pixel explicitly + for (k=0; k < img_n; ++k) { + switch (filter) { + case F_none : cur[k] = raw[k]; break; + case F_sub : cur[k] = raw[k]; break; + case F_up : cur[k] = raw[k] + prior[k]; break; + case F_avg : cur[k] = raw[k] + (prior[k]>>1); break; + case F_paeth : cur[k] = (stbi__uint8) (raw[k] + paeth(0,prior[k],0)); break; + case F_avg_first : cur[k] = raw[k]; break; + case F_paeth_first: cur[k] = raw[k]; break; + } + } + if (img_n != out_n) cur[img_n] = 255; + raw += img_n; + cur += out_n; + prior += out_n; + // this is a little gross, so that we don't switch per-pixel or per-component + if (img_n == out_n) { + #define CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, raw+=img_n,cur+=img_n,prior+=img_n) \ + for (k=0; k < img_n; ++k) + switch (filter) { + CASE(F_none) cur[k] = raw[k]; break; + CASE(F_sub) cur[k] = raw[k] + cur[k-img_n]; break; + CASE(F_up) cur[k] = raw[k] + prior[k]; break; + CASE(F_avg) cur[k] = raw[k] + ((prior[k] + cur[k-img_n])>>1); break; + CASE(F_paeth) cur[k] = (stbi__uint8) (raw[k] + paeth(cur[k-img_n],prior[k],prior[k-img_n])); break; + CASE(F_avg_first) cur[k] = raw[k] + (cur[k-img_n] >> 1); break; + CASE(F_paeth_first) cur[k] = (stbi__uint8) (raw[k] + paeth(cur[k-img_n],0,0)); break; + } + #undef CASE + } else { + assert(img_n+1 == out_n); + #define CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \ + for (k=0; k < img_n; ++k) + switch (filter) { + CASE(F_none) cur[k] = raw[k]; break; + CASE(F_sub) cur[k] = raw[k] + cur[k-out_n]; break; + CASE(F_up) cur[k] = raw[k] + prior[k]; break; + CASE(F_avg) cur[k] = raw[k] + ((prior[k] + cur[k-out_n])>>1); break; + CASE(F_paeth) cur[k] = (stbi__uint8) (raw[k] + paeth(cur[k-out_n],prior[k],prior[k-out_n])); break; + CASE(F_avg_first) cur[k] = raw[k] + (cur[k-out_n] >> 1); break; + CASE(F_paeth_first) cur[k] = (stbi__uint8) (raw[k] + paeth(cur[k-out_n],0,0)); break; + } + #undef CASE + } + } + return 1; +} + +static int create_png_image(png *a, stbi__uint8 *raw, stbi__uint32 raw_len, int out_n, int interlaced) +{ + stbi__uint8 *final; + int p; + int save; + if (!interlaced) + return create_png_image_raw(a, raw, raw_len, out_n, a->s->img_x, a->s->img_y); + save = stbi_png_partial; + stbi_png_partial = 0; + + // de-interlacing + final = (stbi__uint8 *) malloc(a->s->img_x * a->s->img_y * out_n); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + if (!create_png_image_raw(a, raw, raw_len, out_n, x, y)) { + free(final); + return 0; + } + for (j=0; j < y; ++j) + for (i=0; i < x; ++i) + memcpy(final + (j*yspc[p]+yorig[p])*a->s->img_x*out_n + (i*xspc[p]+xorig[p])*out_n, + a->out + (j*x+i)*out_n, out_n); + free(a->out); + raw += (x*out_n+1)*y; + raw_len -= (x*out_n+1)*y; + } + } + a->out = final; + + stbi_png_partial = save; + return 1; +} + +static int compute_transparency(png *z, stbi__uint8 tc[3], int out_n) +{ + stbi *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint8 *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + assert(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int expand_palette(png *a, stbi__uint8 *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi__uint8 *p, *temp_out, *orig = a->out; + + p = (stbi__uint8 *) malloc(pixel_count * pal_img_n); + if (p == NULL) return e("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + free(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi_unpremultiply_on_load = 0; +static int stbi_de_iphone_flag = 0; + +void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi_unpremultiply_on_load = flag_true_if_should_unpremultiply; +} +void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi_de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi_de_iphone(png *z) +{ + stbi *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint8 *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi__uint8 t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + assert(s->img_out_n == 4); + if (stbi_unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi__uint8 a = p[3]; + stbi__uint8 t = p[0]; + if (a) { + p[0] = p[2] * 255 / a; + p[1] = p[1] * 255 / a; + p[2] = t * 255 / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi__uint8 t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +static int parse_png_file(png *z, int scan, int req_comp) +{ + stbi__uint8 palette[1024], pal_img_n=0; + stbi__uint8 has_trans=0, tc[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, iphone=0; + stbi *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!check_png_header(s)) return 0; + + if (scan == SCAN_type) return 1; + + for (;;) { + chunk c = get_chunk_header(s); + switch (c.type) { + case PNG_TYPE('C','g','B','I'): + iphone = stbi_de_iphone_flag; + skip(s, c.length); + break; + case PNG_TYPE('I','H','D','R'): { + int depth,color,comp,filter; + if (!first) return e("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return e("bad IHDR len","Corrupt PNG"); + s->img_x = get32(s); if (s->img_x > (1 << 24)) return e("too large","Very large image (corrupt?)"); + s->img_y = get32(s); if (s->img_y > (1 << 24)) return e("too large","Very large image (corrupt?)"); + depth = get8(s); if (depth != 8) return e("8bit only","PNG not supported: 8-bit only"); + color = get8(s); if (color > 6) return e("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return e("bad ctype","Corrupt PNG"); + comp = get8(s); if (comp) return e("bad comp method","Corrupt PNG"); + filter= get8(s); if (filter) return e("bad filter method","Corrupt PNG"); + interlace = get8(s); if (interlace>1) return e("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return e("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return e("too large", "Image too large to decode"); + if (scan == SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return e("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case PNG_TYPE('P','L','T','E'): { + if (first) return e("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return e("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return e("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = get8u(s); + palette[i*4+1] = get8u(s); + palette[i*4+2] = get8u(s); + palette[i*4+3] = 255; + } + break; + } + + case PNG_TYPE('t','R','N','S'): { + if (first) return e("first not IHDR", "Corrupt PNG"); + if (z->idata) return e("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return e("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return e("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = get8u(s); + } else { + if (!(s->img_n & 1)) return e("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return e("bad tRNS len","Corrupt PNG"); + has_trans = 1; + for (k=0; k < s->img_n; ++k) + tc[k] = (stbi__uint8) get16(s); // non 8-bit images will be larger + } + break; + } + + case PNG_TYPE('I','D','A','T'): { + if (first) return e("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return e("no PLTE","Corrupt PNG"); + if (scan == SCAN_header) { s->img_n = pal_img_n; return 1; } + if (ioff + c.length > idata_limit) { + stbi__uint8 *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + p = (stbi__uint8 *) realloc(z->idata, idata_limit); if (p == NULL) return e("outofmem", "Out of memory"); + z->idata = p; + } + if (!getn(s, z->idata+ioff,c.length)) return e("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len; + if (first) return e("first not IHDR", "Corrupt PNG"); + if (scan != SCAN_load) return 1; + if (z->idata == NULL) return e("no IDAT","Corrupt PNG"); + z->expanded = (stbi__uint8 *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, 16384, (int *) &raw_len, !iphone); + if (z->expanded == NULL) return 0; // zlib should set error + free(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!create_png_image(z, z->expanded, raw_len, s->img_out_n, interlace)) return 0; + if (has_trans) + if (!compute_transparency(z, tc, s->img_out_n)) return 0; + if (iphone && s->img_out_n > 2) + stbi_de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!expand_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } + free(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return e("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX chunk not known"; + invalid_chunk[0] = (stbi__uint8) (c.type >> 24); + invalid_chunk[1] = (stbi__uint8) (c.type >> 16); + invalid_chunk[2] = (stbi__uint8) (c.type >> 8); + invalid_chunk[3] = (stbi__uint8) (c.type >> 0); + #endif + return e(invalid_chunk, "PNG not supported: unknown chunk type"); + } + skip(s, c.length); + break; + } + // end of chunk, read and skip CRC + get32(s); + } +} + +static unsigned char *do_png(png *p, int *x, int *y, int *n, int req_comp) +{ + unsigned char *result=NULL; + if (req_comp < 0 || req_comp > 4) return epuc("bad req_comp", "Internal error"); + if (parse_png_file(p, SCAN_load, req_comp)) { + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + result = convert_format(result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + free(p->out); p->out = NULL; + free(p->expanded); p->expanded = NULL; + free(p->idata); p->idata = NULL; + + return result; +} + +static unsigned char *stbi_png_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + png p; + p.s = s; + return do_png(&p, x,y,comp,req_comp); +} + +static int stbi_png_test(stbi *s) +{ + int r; + r = check_png_header(s); + stbi_rewind(s); + return r; +} + +static int stbi_png_info_raw(png *p, int *x, int *y, int *comp) +{ + if (!parse_png_file(p, SCAN_header, 0)) { + stbi_rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi_png_info(stbi *s, int *x, int *y, int *comp) +{ + png p; + p.s = s; + return stbi_png_info_raw(&p, x, y, comp); +} + +// Microsoft/Windows BMP image + +static int bmp_test(stbi *s) +{ + int sz; + if (get8(s) != 'B') return 0; + if (get8(s) != 'M') return 0; + get32le(s); // discard filesize + get16le(s); // discard reserved + get16le(s); // discard reserved + get32le(s); // discard data offset + sz = get32le(s); + if (sz == 12 || sz == 40 || sz == 56 || sz == 108) return 1; + return 0; +} + +static int stbi_bmp_test(stbi *s) +{ + int r = bmp_test(s); + stbi_rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +static int shiftsigned(int v, int shift, int bits) +{ + int result; + int z=0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; +} + +static stbi_uc *bmp_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint8 *out; + unsigned int mr=0,mg=0,mb=0,ma=0, fake_a=0; + stbi_uc pal[256][4]; + int psize=0,i,j,compress=0,width; + int bpp, flip_vertically, pad, target, offset, hsz; + if (get8(s) != 'B' || get8(s) != 'M') return epuc("not BMP", "Corrupt BMP"); + get32le(s); // discard filesize + get16le(s); // discard reserved + get16le(s); // discard reserved + offset = get32le(s); + hsz = get32le(s); + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108) return epuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = get16le(s); + s->img_y = get16le(s); + } else { + s->img_x = get32le(s); + s->img_y = get32le(s); + } + if (get16le(s) != 1) return epuc("bad BMP", "bad BMP"); + bpp = get16le(s); + if (bpp == 1) return epuc("monochrome", "BMP type not supported: 1-bit"); + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + if (hsz == 12) { + if (bpp < 24) + psize = (offset - 14 - 24) / 3; + } else { + compress = get32le(s); + if (compress == 1 || compress == 2) return epuc("BMP RLE", "BMP type not supported: RLE"); + get32le(s); // discard sizeof + get32le(s); // discard hres + get32le(s); // discard vres + get32le(s); // discard colorsused + get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + get32le(s); + get32le(s); + get32le(s); + get32le(s); + } + if (bpp == 16 || bpp == 32) { + mr = mg = mb = 0; + if (compress == 0) { + if (bpp == 32) { + mr = 0xffu << 16; + mg = 0xffu << 8; + mb = 0xffu << 0; + ma = 0xffu << 24; + fake_a = 1; // @TODO: check for cases like alpha value is all 0 and switch it to 255 + STBI_NOTUSED(fake_a); + } else { + mr = 31u << 10; + mg = 31u << 5; + mb = 31u << 0; + } + } else if (compress == 3) { + mr = get32le(s); + mg = get32le(s); + mb = get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (mr == mg && mg == mb) { + // ?!?!? + return epuc("bad BMP", "bad BMP"); + } + } else + return epuc("bad BMP", "bad BMP"); + } + } else { + assert(hsz == 108); + mr = get32le(s); + mg = get32le(s); + mb = get32le(s); + ma = get32le(s); + get32le(s); // discard color space + for (i=0; i < 12; ++i) + get32le(s); // discard color space parameters + } + if (bpp < 16) + psize = (offset - 14 - hsz) >> 2; + } + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + out = (stbi_uc *) malloc(target * s->img_x * s->img_y); + if (!out) return epuc("outofmem", "Out of memory"); + if (bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { free(out); return epuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = get8u(s); + pal[i][1] = get8u(s); + pal[i][0] = get8u(s); + if (hsz != 12) get8(s); + pal[i][3] = 255; + } + skip(s, offset - 14 - hsz - psize * (hsz == 12 ? 3 : 4)); + if (bpp == 4) width = (s->img_x + 1) >> 1; + else if (bpp == 8) width = s->img_x; + else { free(out); return epuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=get8(s),v2=0; + if (bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (bpp == 8) ? get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + skip(s, pad); + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + skip(s, offset - 14 - hsz); + if (bpp == 24) width = 3 * s->img_x; + else if (bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (bpp == 24) { + easy = 1; + } else if (bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { free(out); return epuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = high_bit(mr)-7; rcount = bitcount(mr); + gshift = high_bit(mg)-7; gcount = bitcount(mg); + bshift = high_bit(mb)-7; bcount = bitcount(mb); + ashift = high_bit(ma)-7; acount = bitcount(ma); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + int a; + out[z+2] = get8u(s); + out[z+1] = get8u(s); + out[z+0] = get8u(s); + z += 3; + a = (easy == 2 ? get8(s) : 255); + if (target == 4) out[z++] = (stbi__uint8) a; + } + } else { + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (stbi__uint32) (bpp == 16 ? get16le(s) : get32le(s)); + int a; + out[z++] = (stbi__uint8) shiftsigned(v & mr, rshift, rcount); + out[z++] = (stbi__uint8) shiftsigned(v & mg, gshift, gcount); + out[z++] = (stbi__uint8) shiftsigned(v & mb, bshift, bcount); + a = (ma ? shiftsigned(v & ma, ashift, acount) : 255); + if (target == 4) out[z++] = (stbi__uint8) a; + } + } + skip(s, pad); + } + } + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} + +static stbi_uc *stbi_bmp_load(stbi *s,int *x, int *y, int *comp, int req_comp) +{ + return bmp_load(s, x,y,comp,req_comp); +} + + +// Targa Truevision - TGA +// by Jonathan Dummer + +static int tga_info(stbi *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp; + int sz; + get8u(s); // discard Offset + sz = get8u(s); // color type + if( sz > 1 ) { + stbi_rewind(s); + return 0; // only RGB or indexed allowed + } + sz = get8u(s); // image type + // only RGB or grey allowed, +/- RLE + if ((sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11)) return 0; + skip(s,9); + tga_w = get16le(s); + if( tga_w < 1 ) { + stbi_rewind(s); + return 0; // test width + } + tga_h = get16le(s); + if( tga_h < 1 ) { + stbi_rewind(s); + return 0; // test height + } + sz = get8(s); // bits per pixel + // only RGB or RGBA or grey allowed + if ((sz != 8) && (sz != 16) && (sz != 24) && (sz != 32)) { + stbi_rewind(s); + return 0; + } + tga_comp = sz; + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp / 8; + return 1; // seems to have passed everything +} + +int stbi_tga_info(stbi *s, int *x, int *y, int *comp) +{ + return tga_info(s, x, y, comp); +} + +static int tga_test(stbi *s) +{ + int sz; + get8u(s); // discard Offset + sz = get8u(s); // color type + if ( sz > 1 ) return 0; // only RGB or indexed allowed + sz = get8u(s); // image type + if ( (sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11) ) return 0; // only RGB or grey allowed, +/- RLE + get16(s); // discard palette start + get16(s); // discard palette length + get8(s); // discard bits per palette color entry + get16(s); // discard x origin + get16(s); // discard y origin + if ( get16(s) < 1 ) return 0; // test width + if ( get16(s) < 1 ) return 0; // test height + sz = get8(s); // bits per pixel + if ( (sz != 8) && (sz != 16) && (sz != 24) && (sz != 32) ) return 0; // only RGB or RGBA or grey allowed + return 1; // seems to have passed everything +} + +static int stbi_tga_test(stbi *s) +{ + int res = tga_test(s); + stbi_rewind(s); + return res; +} + +static stbi_uc *tga_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + // read in the TGA header stuff + int tga_offset = get8u(s); + int tga_indexed = get8u(s); + int tga_image_type = get8u(s); + int tga_is_RLE = 0; + int tga_palette_start = get16le(s); + int tga_palette_len = get16le(s); + int tga_palette_bits = get8u(s); + int tga_x_origin = get16le(s); + int tga_y_origin = get16le(s); + int tga_width = get16le(s); + int tga_height = get16le(s); + int tga_bits_per_pixel = get8u(s); + int tga_comp = tga_bits_per_pixel / 8; + int tga_inverted = get8u(s); + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4]; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + /* int tga_alpha_bits = tga_inverted & 15; */ + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // error check + if ( //(tga_indexed) || + (tga_width < 1) || (tga_height < 1) || + (tga_image_type < 1) || (tga_image_type > 3) || + ((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16) && + (tga_bits_per_pixel != 24) && (tga_bits_per_pixel != 32)) + ) + { + return NULL; // we don't report this as a bad TGA because we don't even know if it's TGA + } + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) + { + tga_comp = tga_palette_bits / 8; + } + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + tga_data = (unsigned char*)malloc( tga_width * tga_height * req_comp ); + if (!tga_data) return epuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE) { + for (i=0; i < tga_height; ++i) { + int y = tga_inverted ? tga_height -i - 1 : i; + stbi__uint8 *tga_row = tga_data + y*tga_width*tga_comp; + getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + // any data to skip? (offset usually = 0) + skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)malloc( tga_palette_len * tga_palette_bits / 8 ); + if (!tga_palette) { + free(tga_data); + return epuc("outofmem", "Out of memory"); + } + if (!getn(s, tga_palette, tga_palette_len * tga_palette_bits / 8 )) { + free(tga_data); + free(tga_palette); + return epuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE chunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = get8u(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in 1 byte, then perform the lookup + int pal_idx = get8u(s); + if ( pal_idx >= tga_palette_len ) + { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_bits_per_pixel / 8; + for (j = 0; j*8 < tga_bits_per_pixel; ++j) + { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else + { + // read in the data raw + for (j = 0; j*8 < tga_bits_per_pixel; ++j) + { + raw_data[j] = get8u(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * req_comp; + int index2 = (tga_height - 1 - j) * tga_width * req_comp; + for (i = tga_width * req_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + free( tga_palette ); + } + } + + // swap RGB + if (tga_comp >= 3) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} + +static stbi_uc *stbi_tga_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + return tga_load(s,x,y,comp,req_comp); +} + + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +static int psd_test(stbi *s) +{ + if (get32(s) != 0x38425053) return 0; // "8BPS" + else return 1; +} + +static int stbi_psd_test(stbi *s) +{ + int r = psd_test(s); + stbi_rewind(s); + return r; +} + +static stbi_uc *psd_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + int pixelCount; + int channelCount, compression; + int channel, i, count, len; + int w,h; + stbi__uint8 *out; + + // Check identifier + if (get32(s) != 0x38425053) // "8BPS" + return epuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (get16(s) != 1) + return epuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = get16(s); + if (channelCount < 0 || channelCount > 16) + return epuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = get32(s); + w = get32(s); + + // Make sure the depth is 8 bits. + if (get16(s) != 8) + return epuc("unsupported bit depth", "PSD bit depth is not 8 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (get16(s) != 3) + return epuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + skip(s,get32(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + skip(s, get32(s) ); + + // Skip the reserved data. + skip(s, get32(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = get16(s); + if (compression > 1) + return epuc("bad compression", "PSD has an unknown compression format"); + + // Create the destination image. + out = (stbi_uc *) malloc(4 * w*h); + if (!out) return epuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi__uint8 *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++) *p = (channel == 3 ? 255 : 0), p += 4; + } else { + // Read the RLE data. + count = 0; + while (count < pixelCount) { + len = get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + count += len; + while (len) { + *p = get8u(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi__uint8 val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len ^= 0x0FF; + len += 2; + val = get8u(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + stbi__uint8 *p; + + p = out + channel; + if (channel > channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++) *p = channel == 3 ? 255 : 0, p += 4; + } else { + // Read the data. + for (i = 0; i < pixelCount; i++) + *p = get8u(s), p += 4; + } + } + } + + if (req_comp && req_comp != 4) { + out = convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // convert_format frees input on failure + } + + if (comp) *comp = channelCount; + *y = h; + *x = w; + + return out; +} + +static stbi_uc *stbi_psd_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + return psd_load(s,x,y,comp,req_comp); +} + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +static int pic_is4(stbi *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int pic_test(stbi *s) +{ + int i; + + if (!pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + get8(s); + + if (!pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} pic_packet_t; + +static stbi_uc *pic_readval(stbi *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (at_eof(s)) return epuc("bad file","PIC file too short"); + dest[i]=get8u(s); + } + } + + return dest; +} + +static void pic_copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *pic_load2(stbi *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + pic_packet_t packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + pic_packet_t *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return epuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = get8(s); + packet->size = get8u(s); + packet->type = get8u(s); + packet->channel = get8u(s); + + act_comp |= packet->channel; + + if (at_eof(s)) return epuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return epuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return epuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=get8u(s); + if (at_eof(s)) return epuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi__uint8) left; + + if (!pic_readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = get8(s), i; + if (at_eof(s)) return epuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + int i; + + if (count==128) + count = get16(s); + else + count -= 127; + if (count > left) + return epuc("bad file","scanline overrun"); + + if (!pic_readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return epuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static stbi_uc *pic_load(stbi *s,int *px,int *py,int *comp,int req_comp) +{ + stbi_uc *result; + int i, x,y; + + for (i=0; i<92; ++i) + get8(s); + + x = get16(s); + y = get16(s); + if (at_eof(s)) return epuc("bad file","file too short (pic header)"); + if ((1 << 28) / x < y) return epuc("too large", "Image too large to decode"); + + get32(s); //skip `ratio' + get16(s); //skip `fields' + get16(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) malloc(x*y*4); + memset(result, 0xff, x*y*4); + + if (!pic_load2(s,x,y,comp, result)) { + free(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi_pic_test(stbi *s) +{ + int r = pic_test(s); + stbi_rewind(s); + return r; +} + +static stbi_uc *stbi_pic_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + return pic_load(s,x,y,comp,req_comp); +} + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb +typedef struct stbi_gif_lzw_struct { + stbi__int16 prefix; + stbi__uint8 first; + stbi__uint8 suffix; +} stbi_gif_lzw; + +typedef struct stbi_gif_struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + int flags, bgindex, ratio, transparent, eflags; + stbi__uint8 pal[256][4]; + stbi__uint8 lpal[256][4]; + stbi_gif_lzw codes[4096]; + stbi__uint8 *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; +} stbi_gif; + +static int gif_test(stbi *s) +{ + int sz; + if (get8(s) != 'G' || get8(s) != 'I' || get8(s) != 'F' || get8(s) != '8') return 0; + sz = get8(s); + if (sz != '9' && sz != '7') return 0; + if (get8(s) != 'a') return 0; + return 1; +} + +static int stbi_gif_test(stbi *s) +{ + int r = gif_test(s); + stbi_rewind(s); + return r; +} + +static void stbi_gif_parse_colortable(stbi *s, stbi__uint8 pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = get8u(s); + pal[i][1] = get8u(s); + pal[i][0] = get8u(s); + pal[i][3] = transp ? 0 : 255; + } +} + +static int stbi_gif_header(stbi *s, stbi_gif *g, int *comp, int is_info) +{ + stbi__uint8 version; + if (get8(s) != 'G' || get8(s) != 'I' || get8(s) != 'F' || get8(s) != '8') + return e("not GIF", "Corrupt GIF"); + + version = get8u(s); + if (version != '7' && version != '9') return e("not GIF", "Corrupt GIF"); + if (get8(s) != 'a') return e("not GIF", "Corrupt GIF"); + + failure_reason = ""; + g->w = get16le(s); + g->h = get16le(s); + g->flags = get8(s); + g->bgindex = get8(s); + g->ratio = get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi_gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi_gif_info_raw(stbi *s, int *x, int *y, int *comp) +{ + stbi_gif g; + if (!stbi_gif_header(s, &g, comp, 1)) { + stbi_rewind( s ); + return 0; + } + if (x) *x = g.w; + if (y) *y = g.h; + return 1; +} + +static void stbi_out_gif_code(stbi_gif *g, stbi__uint16 code) +{ + stbi__uint8 *p, *c; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi_out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + p = &g->out[g->cur_x + g->cur_y]; + c = &g->color_table[g->codes[code].suffix * 4]; + + if (c[3] >= 128) { + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi__uint8 *stbi_process_gif_raster(stbi *s, stbi_gif *g) +{ + stbi__uint8 lzw_cs; + stbi__int32 len, code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi_gif_lzw *p; + + lzw_cs = get8u(s); + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (code = 0; code < clear; code++) { + g->codes[code].prefix = -1; + g->codes[code].first = (stbi__uint8) code; + g->codes[code].suffix = (stbi__uint8) code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + skip(s, len); + while ((len = get8(s)) > 0) + skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) return epuc("no clear code", "Corrupt GIF"); + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 4096) return epuc("too many codes", "Corrupt GIF"); + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return epuc("illegal code in raster", "Corrupt GIF"); + + stbi_out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return epuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +static void stbi_fill_gif_background(stbi_gif *g) +{ + int i; + stbi__uint8 *c = g->pal[g->bgindex]; + // @OPTIMIZE: write a dword at a time + for (i = 0; i < g->w * g->h * 4; i += 4) { + stbi__uint8 *p = &g->out[i]; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +static stbi__uint8 *stbi_gif_load_next(stbi *s, stbi_gif *g, int *comp, int req_comp) +{ + int i; + stbi__uint8 *old_out = 0; + + if (g->out == 0) { + if (!stbi_gif_header(s, g, comp,0)) return 0; // failure_reason set by stbi_gif_header + g->out = (stbi__uint8 *) malloc(4 * g->w * g->h); + if (g->out == 0) return epuc("outofmem", "Out of memory"); + stbi_fill_gif_background(g); + } else { + // animated-gif-only path + if (((g->eflags & 0x1C) >> 2) == 3) { + old_out = g->out; + g->out = (stbi__uint8 *) malloc(4 * g->w * g->h); + if (g->out == 0) return epuc("outofmem", "Out of memory"); + memcpy(g->out, old_out, g->w*g->h*4); + } + } + + for (;;) { + switch (get8(s)) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi__uint8 *o; + + x = get16le(s); + y = get16le(s); + w = get16le(s); + h = get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return epuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + g->lflags = get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi_gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi__uint8 *) g->lpal; + } else if (g->flags & 0x80) { + for (i=0; i < 256; ++i) // @OPTIMIZE: reset only the previous transparent + g->pal[i][3] = 255; + if (g->transparent >= 0 && (g->eflags & 0x01)) + g->pal[g->transparent][3] = 0; + g->color_table = (stbi__uint8 *) g->pal; + } else + return epuc("missing color table", "Corrupt GIF"); + + o = stbi_process_gif_raster(s, g); + if (o == NULL) return NULL; + + if (req_comp && req_comp != 4) + o = convert_format(o, 4, req_comp, g->w, g->h); + return o; + } + + case 0x21: // Comment Extension. + { + int len; + if (get8(s) == 0xF9) { // Graphic Control Extension. + len = get8(s); + if (len == 4) { + g->eflags = get8(s); + get16le(s); // delay + g->transparent = get8(s); + } else { + skip(s, len); + break; + } + } + while ((len = get8(s)) != 0) + skip(s, len); + break; + } + + case 0x3B: // gif stream termination code + return (stbi__uint8 *) 1; + + default: + return epuc("unknown code", "Corrupt GIF"); + } + } +} + +static stbi_uc *stbi_gif_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint8 *u = 0; + stbi_gif g={0}; + + u = stbi_gif_load_next(s, &g, comp, req_comp); + if (u == (void *) 1) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + } + + return u; +} + +static int stbi_gif_info(stbi *s, int *x, int *y, int *comp) +{ + return stbi_gif_info_raw(s,x,y,comp); +} + + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int hdr_test(stbi *s) +{ + const char *signature = "#?RADIANCE\n"; + int i; + for (i=0; signature[i]; ++i) + if (get8(s) != signature[i]) + return 0; + return 1; +} + +static int stbi_hdr_test(stbi* s) +{ + int r = hdr_test(s); + stbi_rewind(s); + return r; +} + +#define HDR_BUFLEN 1024 +static char *hdr_gettoken(stbi *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) get8(z); + + while (!at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == HDR_BUFLEN-1) { + // flush to end of line + while (!at_eof(z) && get8(z) != '\n') + ; + break; + } + c = (char) get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *hdr_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + char buffer[HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + + + // Check identifier + if (strcmp(hdr_gettoken(s,buffer), "#?RADIANCE") != 0) + return epf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return epf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return epf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return epf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + *x = width; + *y = height; + + *comp = 3; + if (req_comp == 0) req_comp = 3; + + // Read data + hdr_data = (float *) malloc(height * width * req_comp * sizeof(float)); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + getn(s, rgbe, 4); + hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = get8(s); + c2 = get8(s); + len = get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi__uint8 rgbe[4]; + rgbe[0] = (stbi__uint8) c1; + rgbe[1] = (stbi__uint8) c2; + rgbe[2] = (stbi__uint8) len; + rgbe[3] = (stbi__uint8) get8u(s); + hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + free(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= get8(s); + if (len != width) { free(hdr_data); free(scanline); return epf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) scanline = (stbi_uc *) malloc(width * 4); + + for (k = 0; k < 4; ++k) { + i = 0; + while (i < width) { + count = get8u(s); + if (count > 128) { + // Run + value = get8u(s); + count -= 128; + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = get8u(s); + } + } + } + for (i=0; i < width; ++i) + hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + free(scanline); + } + + return hdr_data; +} + +static float *stbi_hdr_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + return hdr_load(s,x,y,comp,req_comp); +} + +static int stbi_hdr_info(stbi *s, int *x, int *y, int *comp) +{ + char buffer[HDR_BUFLEN]; + char *token; + int valid = 0; + + if (strcmp(hdr_gettoken(s,buffer), "#?RADIANCE") != 0) { + stbi_rewind( s ); + return 0; + } + + for(;;) { + token = hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi_rewind( s ); + return 0; + } + token = hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi_rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi_rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +static int stbi_bmp_info(stbi *s, int *x, int *y, int *comp) +{ + int hsz; + if (get8(s) != 'B' || get8(s) != 'M') { + stbi_rewind( s ); + return 0; + } + skip(s,12); + hsz = get32le(s); + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108) { + stbi_rewind( s ); + return 0; + } + if (hsz == 12) { + *x = get16le(s); + *y = get16le(s); + } else { + *x = get32le(s); + *y = get32le(s); + } + if (get16le(s) != 1) { + stbi_rewind( s ); + return 0; + } + *comp = get16le(s) / 8; + return 1; +} + +static int stbi_psd_info(stbi *s, int *x, int *y, int *comp) +{ + int channelCount; + if (get32(s) != 0x38425053) { + stbi_rewind( s ); + return 0; + } + if (get16(s) != 1) { + stbi_rewind( s ); + return 0; + } + skip(s, 6); + channelCount = get16(s); + if (channelCount < 0 || channelCount > 16) { + stbi_rewind( s ); + return 0; + } + *y = get32(s); + *x = get32(s); + if (get16(s) != 8) { + stbi_rewind( s ); + return 0; + } + if (get16(s) != 3) { + stbi_rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi_pic_info(stbi *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained; + pic_packet_t packets[10]; + + skip(s, 92); + + *x = get16(s); + *y = get16(s); + if (at_eof(s)) return 0; + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi_rewind( s ); + return 0; + } + + skip(s, 8); + + do { + pic_packet_t *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = get8(s); + packet->size = get8u(s); + packet->type = get8u(s); + packet->channel = get8u(s); + act_comp |= packet->channel; + + if (at_eof(s)) { + stbi_rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi_rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} + +static int stbi_info_main(stbi *s, int *x, int *y, int *comp) +{ + if (stbi_jpeg_info(s, x, y, comp)) + return 1; + if (stbi_png_info(s, x, y, comp)) + return 1; + if (stbi_gif_info(s, x, y, comp)) + return 1; + if (stbi_bmp_info(s, x, y, comp)) + return 1; + if (stbi_psd_info(s, x, y, comp)) + return 1; + if (stbi_pic_info(s, x, y, comp)) + return 1; + #ifndef STBI_NO_HDR + if (stbi_hdr_info(s, x, y, comp)) + return 1; + #endif + // test tga last because it's a crappy test! + if (stbi_tga_info(s, x, y, comp)) + return 1; + return e("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_STDIO +int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = fopen(filename, "rb"); + int result; + if (!f) return e("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi s; + long pos = ftell(f); + start_file(&s, f); + r = stbi_info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi s; + start_mem(&s,buffer,len); + return stbi_info_main(&s,x,y,comp); +} + +int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi s; + start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi_info_main(&s,x,y,comp); +} + +#endif // STBI_HEADER_FILE_ONLY + +#if !defined(STBI_NO_STDIO) && defined(_MSC_VER) && _MSC_VER >= 1400 +#pragma warning(pop) +#endif + + +/* + revision history: + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi__uint8 to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.e. Janez (U+017D)emva) + 1.21 fix use of 'stbi__uint8' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 2008-08-02 + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi_bmp_load() and stbi_tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 first released version +*/ diff --git a/source/engine/thirdparty/stb/deprecated/stb_image_resize.h b/source/engine/thirdparty/stb/deprecated/stb_image_resize.h new file mode 100644 index 0000000..ef9e6fe --- /dev/null +++ b/source/engine/thirdparty/stb/deprecated/stb_image_resize.h @@ -0,0 +1,2634 @@ +/* stb_image_resize - v0.97 - public domain image resizing + by Jorge L Rodriguez (@VinoBS) - 2014 + http://github.com/nothings/stb + + Written with emphasis on usability, portability, and efficiency. (No + SIMD or threads, so it be easily outperformed by libs that use those.) + Only scaling and translation is supported, no rotations or shears. + Easy API downsamples w/Mitchell filter, upsamples w/cubic interpolation. + + COMPILING & LINKING + In one C/C++ file that #includes this file, do this: + #define STB_IMAGE_RESIZE_IMPLEMENTATION + before the #include. That will create the implementation in that file. + + QUICKSTART + stbir_resize_uint8( input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, num_channels) + stbir_resize_float(...) + stbir_resize_uint8_srgb( input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, + num_channels , alpha_chan , 0) + stbir_resize_uint8_srgb_edgemode( + input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, + num_channels , alpha_chan , 0, STBIR_EDGE_CLAMP) + // WRAP/REFLECT/ZERO + + FULL API + See the "header file" section of the source for API documentation. + + ADDITIONAL DOCUMENTATION + + SRGB & FLOATING POINT REPRESENTATION + The sRGB functions presume IEEE floating point. If you do not have + IEEE floating point, define STBIR_NON_IEEE_FLOAT. This will use + a slower implementation. + + MEMORY ALLOCATION + The resize functions here perform a single memory allocation using + malloc. To control the memory allocation, before the #include that + triggers the implementation, do: + + #define STBIR_MALLOC(size,context) ... + #define STBIR_FREE(ptr,context) ... + + Each resize function makes exactly one call to malloc/free, so to use + temp memory, store the temp memory in the context and return that. + + ASSERT + Define STBIR_ASSERT(boolval) to override assert() and not use assert.h + + OPTIMIZATION + Define STBIR_SATURATE_INT to compute clamp values in-range using + integer operations instead of float operations. This may be faster + on some platforms. + + DEFAULT FILTERS + For functions which don't provide explicit control over what filters + to use, you can change the compile-time defaults with + + #define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_something + #define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_something + + See stbir_filter in the header-file section for the list of filters. + + NEW FILTERS + A number of 1D filter kernels are used. For a list of + supported filters see the stbir_filter enum. To add a new filter, + write a filter function and add it to stbir__filter_info_table. + + PROGRESS + For interactive use with slow resize operations, you can install + a progress-report callback: + + #define STBIR_PROGRESS_REPORT(val) some_func(val) + + The parameter val is a float which goes from 0 to 1 as progress is made. + + For example: + + static void my_progress_report(float progress); + #define STBIR_PROGRESS_REPORT(val) my_progress_report(val) + + #define STB_IMAGE_RESIZE_IMPLEMENTATION + #include "stb_image_resize.h" + + static void my_progress_report(float progress) + { + printf("Progress: %f%%\n", progress*100); + } + + MAX CHANNELS + If your image has more than 64 channels, define STBIR_MAX_CHANNELS + to the max you'll have. + + ALPHA CHANNEL + Most of the resizing functions provide the ability to control how + the alpha channel of an image is processed. The important things + to know about this: + + 1. The best mathematically-behaved version of alpha to use is + called "premultiplied alpha", in which the other color channels + have had the alpha value multiplied in. If you use premultiplied + alpha, linear filtering (such as image resampling done by this + library, or performed in texture units on GPUs) does the "right + thing". While premultiplied alpha is standard in the movie CGI + industry, it is still uncommon in the videogame/real-time world. + + If you linearly filter non-premultiplied alpha, strange effects + occur. (For example, the 50/50 average of 99% transparent bright green + and 1% transparent black produces 50% transparent dark green when + non-premultiplied, whereas premultiplied it produces 50% + transparent near-black. The former introduces green energy + that doesn't exist in the source image.) + + 2. Artists should not edit premultiplied-alpha images; artists + want non-premultiplied alpha images. Thus, art tools generally output + non-premultiplied alpha images. + + 3. You will get best results in most cases by converting images + to premultiplied alpha before processing them mathematically. + + 4. If you pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, the + resizer does not do anything special for the alpha channel; + it is resampled identically to other channels. This produces + the correct results for premultiplied-alpha images, but produces + less-than-ideal results for non-premultiplied-alpha images. + + 5. If you do not pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, + then the resizer weights the contribution of input pixels + based on their alpha values, or, equivalently, it multiplies + the alpha value into the color channels, resamples, then divides + by the resultant alpha value. Input pixels which have alpha=0 do + not contribute at all to output pixels unless _all_ of the input + pixels affecting that output pixel have alpha=0, in which case + the result for that pixel is the same as it would be without + STBIR_FLAG_ALPHA_PREMULTIPLIED. However, this is only true for + input images in integer formats. For input images in float format, + input pixels with alpha=0 have no effect, and output pixels + which have alpha=0 will be 0 in all channels. (For float images, + you can manually achieve the same result by adding a tiny epsilon + value to the alpha channel of every image, and then subtracting + or clamping it at the end.) + + 6. You can suppress the behavior described in #5 and make + all-0-alpha pixels have 0 in all channels by #defining + STBIR_NO_ALPHA_EPSILON. + + 7. You can separately control whether the alpha channel is + interpreted as linear or affected by the colorspace. By default + it is linear; you almost never want to apply the colorspace. + (For example, graphics hardware does not apply sRGB conversion + to the alpha channel.) + + CONTRIBUTORS + Jorge L Rodriguez: Implementation + Sean Barrett: API design, optimizations + Aras Pranckevicius: bugfix + Nathan Reed: warning fixes + + REVISIONS + 0.97 (2020-02-02) fixed warning + 0.96 (2019-03-04) fixed warnings + 0.95 (2017-07-23) fixed warnings + 0.94 (2017-03-18) fixed warnings + 0.93 (2017-03-03) fixed bug with certain combinations of heights + 0.92 (2017-01-02) fix integer overflow on large (>2GB) images + 0.91 (2016-04-02) fix warnings; fix handling of subpixel regions + 0.90 (2014-09-17) first released version + + LICENSE + See end of file for license information. + + TODO + Don't decode all of the image data when only processing a partial tile + Don't use full-width decode buffers when only processing a partial tile + When processing wide images, break processing into tiles so data fits in L1 cache + Installable filters? + Resize that respects alpha test coverage + (Reference code: FloatImage::alphaTestCoverage and FloatImage::scaleAlphaToCoverage: + https://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvimage/FloatImage.cpp ) +*/ + +#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE_H +#define STBIR_INCLUDE_STB_IMAGE_RESIZE_H + +#ifdef _MSC_VER +typedef unsigned char stbir_uint8; +typedef unsigned short stbir_uint16; +typedef unsigned int stbir_uint32; +#else +#include +typedef uint8_t stbir_uint8; +typedef uint16_t stbir_uint16; +typedef uint32_t stbir_uint32; +#endif + +#ifndef STBIRDEF +#ifdef STB_IMAGE_RESIZE_STATIC +#define STBIRDEF static +#else +#ifdef __cplusplus +#define STBIRDEF extern "C" +#else +#define STBIRDEF extern +#endif +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Easy-to-use API: +// +// * "input pixels" points to an array of image data with 'num_channels' channels (e.g. RGB=3, RGBA=4) +// * input_w is input image width (x-axis), input_h is input image height (y-axis) +// * stride is the offset between successive rows of image data in memory, in bytes. you can +// specify 0 to mean packed continuously in memory +// * alpha channel is treated identically to other channels. +// * colorspace is linear or sRGB as specified by function name +// * returned result is 1 for success or 0 in case of an error. +// #define STBIR_ASSERT() to trigger an assert on parameter validation errors. +// * Memory required grows approximately linearly with input and output size, but with +// discontinuities at input_w == output_w and input_h == output_h. +// * These functions use a "default" resampling filter defined at compile time. To change the filter, +// you can change the compile-time defaults by #defining STBIR_DEFAULT_FILTER_UPSAMPLE +// and STBIR_DEFAULT_FILTER_DOWNSAMPLE, or you can use the medium-complexity API. + +STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels); + +STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels); + + +// The following functions interpret image data as gamma-corrected sRGB. +// Specify STBIR_ALPHA_CHANNEL_NONE if you have no alpha channel, +// or otherwise provide the index of the alpha channel. Flags value +// of 0 will probably do the right thing if you're not sure what +// the flags mean. + +#define STBIR_ALPHA_CHANNEL_NONE -1 + +// Set this flag if your texture has premultiplied alpha. Otherwise, stbir will +// use alpha-weighted resampling (effectively premultiplying, resampling, +// then unpremultiplying). +#define STBIR_FLAG_ALPHA_PREMULTIPLIED (1 << 0) +// The specified alpha channel should be handled as gamma-corrected value even +// when doing sRGB operations. +#define STBIR_FLAG_ALPHA_USES_COLORSPACE (1 << 1) + +STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags); + + +typedef enum +{ + STBIR_EDGE_CLAMP = 1, + STBIR_EDGE_REFLECT = 2, + STBIR_EDGE_WRAP = 3, + STBIR_EDGE_ZERO = 4, +} stbir_edge; + +// This function adds the ability to specify how requests to sample off the edge of the image are handled. +STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode); + +////////////////////////////////////////////////////////////////////////////// +// +// Medium-complexity API +// +// This extends the easy-to-use API as follows: +// +// * Alpha-channel can be processed separately +// * If alpha_channel is not STBIR_ALPHA_CHANNEL_NONE +// * Alpha channel will not be gamma corrected (unless flags&STBIR_FLAG_GAMMA_CORRECT) +// * Filters will be weighted by alpha channel (unless flags&STBIR_FLAG_ALPHA_PREMULTIPLIED) +// * Filter can be selected explicitly +// * uint16 image type +// * sRGB colorspace available for all types +// * context parameter for passing to STBIR_MALLOC + +typedef enum +{ + STBIR_FILTER_DEFAULT = 0, // use same filter type that easy-to-use API chooses + STBIR_FILTER_BOX = 1, // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios + STBIR_FILTER_TRIANGLE = 2, // On upsampling, produces same results as bilinear texture filtering + STBIR_FILTER_CUBICBSPLINE = 3, // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque + STBIR_FILTER_CATMULLROM = 4, // An interpolating cubic spline + STBIR_FILTER_MITCHELL = 5, // Mitchell-Netrevalli filter with B=1/3, C=1/3 +} stbir_filter; + +typedef enum +{ + STBIR_COLORSPACE_LINEAR, + STBIR_COLORSPACE_SRGB, + + STBIR_MAX_COLORSPACES, +} stbir_colorspace; + +// The following functions are all identical except for the type of the image data + +STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + + + +////////////////////////////////////////////////////////////////////////////// +// +// Full-complexity API +// +// This extends the medium API as follows: +// +// * uint32 image type +// * not typesafe +// * separate filter types for each axis +// * separate edge modes for each axis +// * can specify scale explicitly for subpixel correctness +// * can specify image source tile using texture coordinates + +typedef enum +{ + STBIR_TYPE_UINT8 , + STBIR_TYPE_UINT16, + STBIR_TYPE_UINT32, + STBIR_TYPE_FLOAT , + + STBIR_MAX_TYPES +} stbir_datatype; + +STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context); + +STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float x_scale, float y_scale, + float x_offset, float y_offset); + +STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float s0, float t0, float s1, float t1); +// (s0, t0) & (s1, t1) are the top-left and bottom right corner (uv addressing style: [0, 1]x[0, 1]) of a region of the input image to use. + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE_H + + + + + +#ifdef STB_IMAGE_RESIZE_IMPLEMENTATION + +#ifndef STBIR_ASSERT +#include +#define STBIR_ASSERT(x) assert(x) +#endif + +// For memset +#include + +#include + +#ifndef STBIR_MALLOC +#include +// use comma operator to evaluate c, to avoid "unused parameter" warnings +#define STBIR_MALLOC(size,c) ((void)(c), malloc(size)) +#define STBIR_FREE(ptr,c) ((void)(c), free(ptr)) +#endif + +#ifndef _MSC_VER +#ifdef __cplusplus +#define stbir__inline inline +#else +#define stbir__inline +#endif +#else +#define stbir__inline __forceinline +#endif + + +// should produce compiler error if size is wrong +typedef unsigned char stbir__validate_uint32[sizeof(stbir_uint32) == 4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBIR__NOTUSED(v) (void)(v) +#else +#define STBIR__NOTUSED(v) (void)sizeof(v) +#endif + +#define STBIR__ARRAY_SIZE(a) (sizeof((a))/sizeof((a)[0])) + +#ifndef STBIR_DEFAULT_FILTER_UPSAMPLE +#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +#endif + +#ifndef STBIR_DEFAULT_FILTER_DOWNSAMPLE +#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL +#endif + +#ifndef STBIR_PROGRESS_REPORT +#define STBIR_PROGRESS_REPORT(float_0_to_1) +#endif + +#ifndef STBIR_MAX_CHANNELS +#define STBIR_MAX_CHANNELS 64 +#endif + +#if STBIR_MAX_CHANNELS > 65536 +#error "Too many channels; STBIR_MAX_CHANNELS must be no more than 65536." +// because we store the indices in 16-bit variables +#endif + +// This value is added to alpha just before premultiplication to avoid +// zeroing out color values. It is equivalent to 2^-80. If you don't want +// that behavior (it may interfere if you have floating point images with +// very small alpha values) then you can define STBIR_NO_ALPHA_EPSILON to +// disable it. +#ifndef STBIR_ALPHA_EPSILON +#define STBIR_ALPHA_EPSILON ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20)) +#endif + + + +#ifdef _MSC_VER +#define STBIR__UNUSED_PARAM(v) (void)(v) +#else +#define STBIR__UNUSED_PARAM(v) (void)sizeof(v) +#endif + +// must match stbir_datatype +static unsigned char stbir__type_size[] = { + 1, // STBIR_TYPE_UINT8 + 2, // STBIR_TYPE_UINT16 + 4, // STBIR_TYPE_UINT32 + 4, // STBIR_TYPE_FLOAT +}; + +// Kernel function centered at 0 +typedef float (stbir__kernel_fn)(float x, float scale); +typedef float (stbir__support_fn)(float scale); + +typedef struct +{ + stbir__kernel_fn* kernel; + stbir__support_fn* support; +} stbir__filter_info; + +// When upsampling, the contributors are which source pixels contribute. +// When downsampling, the contributors are which destination pixels are contributed to. +typedef struct +{ + int n0; // First contributing pixel + int n1; // Last contributing pixel +} stbir__contributors; + +typedef struct +{ + const void* input_data; + int input_w; + int input_h; + int input_stride_bytes; + + void* output_data; + int output_w; + int output_h; + int output_stride_bytes; + + float s0, t0, s1, t1; + + float horizontal_shift; // Units: output pixels + float vertical_shift; // Units: output pixels + float horizontal_scale; + float vertical_scale; + + int channels; + int alpha_channel; + stbir_uint32 flags; + stbir_datatype type; + stbir_filter horizontal_filter; + stbir_filter vertical_filter; + stbir_edge edge_horizontal; + stbir_edge edge_vertical; + stbir_colorspace colorspace; + + stbir__contributors* horizontal_contributors; + float* horizontal_coefficients; + + stbir__contributors* vertical_contributors; + float* vertical_coefficients; + + int decode_buffer_pixels; + float* decode_buffer; + + float* horizontal_buffer; + + // cache these because ceil/floor are inexplicably showing up in profile + int horizontal_coefficient_width; + int vertical_coefficient_width; + int horizontal_filter_pixel_width; + int vertical_filter_pixel_width; + int horizontal_filter_pixel_margin; + int vertical_filter_pixel_margin; + int horizontal_num_contributors; + int vertical_num_contributors; + + int ring_buffer_length_bytes; // The length of an individual entry in the ring buffer. The total number of ring buffers is stbir__get_filter_pixel_width(filter) + int ring_buffer_num_entries; // Total number of entries in the ring buffer. + int ring_buffer_first_scanline; + int ring_buffer_last_scanline; + int ring_buffer_begin_index; // first_scanline is at this index in the ring buffer + float* ring_buffer; + + float* encode_buffer; // A temporary buffer to store floats so we don't lose precision while we do multiply-adds. + + int horizontal_contributors_size; + int horizontal_coefficients_size; + int vertical_contributors_size; + int vertical_coefficients_size; + int decode_buffer_size; + int horizontal_buffer_size; + int ring_buffer_size; + int encode_buffer_size; +} stbir__info; + + +static const float stbir__max_uint8_as_float = 255.0f; +static const float stbir__max_uint16_as_float = 65535.0f; +static const double stbir__max_uint32_as_float = 4294967295.0; + + +static stbir__inline int stbir__min(int a, int b) +{ + return a < b ? a : b; +} + +static stbir__inline float stbir__saturate(float x) +{ + if (x < 0) + return 0; + + if (x > 1) + return 1; + + return x; +} + +#ifdef STBIR_SATURATE_INT +static stbir__inline stbir_uint8 stbir__saturate8(int x) +{ + if ((unsigned int) x <= 255) + return x; + + if (x < 0) + return 0; + + return 255; +} + +static stbir__inline stbir_uint16 stbir__saturate16(int x) +{ + if ((unsigned int) x <= 65535) + return x; + + if (x < 0) + return 0; + + return 65535; +} +#endif + +static float stbir__srgb_uchar_to_linear_float[256] = { + 0.000000f, 0.000304f, 0.000607f, 0.000911f, 0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f, + 0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f, 0.006049f, 0.006512f, 0.006995f, 0.007499f, + 0.008023f, 0.008568f, 0.009134f, 0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f, 0.014444f, + 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f, 0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f, + 0.025187f, 0.026241f, 0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f, 0.035601f, 0.036889f, + 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f, 0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f, + 0.054480f, 0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f, 0.068478f, 0.070360f, 0.072272f, + 0.074214f, 0.076185f, 0.078187f, 0.080220f, 0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f, + 0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f, 0.114435f, 0.116971f, 0.119538f, 0.122139f, + 0.124772f, 0.127438f, 0.130136f, 0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f, 0.152926f, + 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f, 0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f, + 0.191202f, 0.194618f, 0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f, 0.223228f, 0.226966f, + 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f, 0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f, + 0.274677f, 0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f, 0.309469f, 0.313989f, 0.318547f, + 0.323143f, 0.327778f, 0.332452f, 0.337164f, 0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f, + 0.376262f, 0.381326f, 0.386430f, 0.391573f, 0.396755f, 0.401978f, 0.407240f, 0.412543f, 0.417885f, 0.423268f, 0.428691f, + 0.434154f, 0.439657f, 0.445201f, 0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473532f, 0.479320f, 0.485150f, 0.491021f, + 0.496933f, 0.502887f, 0.508881f, 0.514918f, 0.520996f, 0.527115f, 0.533276f, 0.539480f, 0.545725f, 0.552011f, 0.558340f, + 0.564712f, 0.571125f, 0.577581f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f, 0.623960f, 0.630757f, + 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f, 0.672443f, 0.679543f, 0.686685f, 0.693872f, 0.701102f, 0.708376f, + 0.715694f, 0.723055f, 0.730461f, 0.737911f, 0.745404f, 0.752942f, 0.760525f, 0.768151f, 0.775822f, 0.783538f, 0.791298f, + 0.799103f, 0.806952f, 0.814847f, 0.822786f, 0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f, + 0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f, 0.947307f, 0.955974f, 0.964686f, 0.973445f, + 0.982251f, 0.991102f, 1.0f +}; + +static float stbir__srgb_to_linear(float f) +{ + if (f <= 0.04045f) + return f / 12.92f; + else + return (float)pow((f + 0.055f) / 1.055f, 2.4f); +} + +static float stbir__linear_to_srgb(float f) +{ + if (f <= 0.0031308f) + return f * 12.92f; + else + return 1.055f * (float)pow(f, 1 / 2.4f) - 0.055f; +} + +#ifndef STBIR_NON_IEEE_FLOAT +// From https://gist.github.com/rygorous/2203834 + +typedef union +{ + stbir_uint32 u; + float f; +} stbir__FP32; + +static const stbir_uint32 fp32_to_srgb8_tab4[104] = { + 0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d, + 0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a, + 0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033, + 0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067, + 0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5, + 0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2, + 0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143, + 0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af, + 0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240, + 0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300, + 0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401, + 0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559, + 0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723, +}; + +static stbir_uint8 stbir__linear_to_srgb_uchar(float in) +{ + static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps + static const stbir__FP32 minval = { (127-13) << 23 }; + stbir_uint32 tab,bias,scale,t; + stbir__FP32 f; + + // Clamp to [2^(-13), 1-eps]; these two values map to 0 and 1, respectively. + // The tests are carefully written so that NaNs map to 0, same as in the reference + // implementation. + if (!(in > minval.f)) // written this way to catch NaNs + in = minval.f; + if (in > almostone.f) + in = almostone.f; + + // Do the table lookup and unpack bias, scale + f.f = in; + tab = fp32_to_srgb8_tab4[(f.u - minval.u) >> 20]; + bias = (tab >> 16) << 9; + scale = tab & 0xffff; + + // Grab next-highest mantissa bits and perform linear interpolation + t = (f.u >> 12) & 0xff; + return (unsigned char) ((bias + scale*t) >> 16); +} + +#else +// sRGB transition values, scaled by 1<<28 +static int stbir__srgb_offset_to_linear_scaled[256] = +{ + 0, 40738, 122216, 203693, 285170, 366648, 448125, 529603, + 611080, 692557, 774035, 855852, 942009, 1033024, 1128971, 1229926, + 1335959, 1447142, 1563542, 1685229, 1812268, 1944725, 2082664, 2226148, + 2375238, 2529996, 2690481, 2856753, 3028870, 3206888, 3390865, 3580856, + 3776916, 3979100, 4187460, 4402049, 4622919, 4850123, 5083710, 5323731, + 5570236, 5823273, 6082892, 6349140, 6622065, 6901714, 7188133, 7481369, + 7781466, 8088471, 8402427, 8723380, 9051372, 9386448, 9728650, 10078021, + 10434603, 10798439, 11169569, 11548036, 11933879, 12327139, 12727857, 13136073, + 13551826, 13975156, 14406100, 14844697, 15290987, 15745007, 16206795, 16676389, + 17153826, 17639142, 18132374, 18633560, 19142734, 19659934, 20185196, 20718552, + 21260042, 21809696, 22367554, 22933648, 23508010, 24090680, 24681686, 25281066, + 25888850, 26505076, 27129772, 27762974, 28404716, 29055026, 29713942, 30381490, + 31057708, 31742624, 32436272, 33138682, 33849884, 34569912, 35298800, 36036568, + 36783260, 37538896, 38303512, 39077136, 39859796, 40651528, 41452360, 42262316, + 43081432, 43909732, 44747252, 45594016, 46450052, 47315392, 48190064, 49074096, + 49967516, 50870356, 51782636, 52704392, 53635648, 54576432, 55526772, 56486700, + 57456236, 58435408, 59424248, 60422780, 61431036, 62449032, 63476804, 64514376, + 65561776, 66619028, 67686160, 68763192, 69850160, 70947088, 72053992, 73170912, + 74297864, 75434880, 76581976, 77739184, 78906536, 80084040, 81271736, 82469648, + 83677792, 84896192, 86124888, 87363888, 88613232, 89872928, 91143016, 92423512, + 93714432, 95015816, 96327688, 97650056, 98982952, 100326408, 101680440, 103045072, + 104420320, 105806224, 107202800, 108610064, 110028048, 111456776, 112896264, 114346544, + 115807632, 117279552, 118762328, 120255976, 121760536, 123276016, 124802440, 126339832, + 127888216, 129447616, 131018048, 132599544, 134192112, 135795792, 137410592, 139036528, + 140673648, 142321952, 143981456, 145652208, 147334208, 149027488, 150732064, 152447968, + 154175200, 155913792, 157663776, 159425168, 161197984, 162982240, 164777968, 166585184, + 168403904, 170234160, 172075968, 173929344, 175794320, 177670896, 179559120, 181458992, + 183370528, 185293776, 187228736, 189175424, 191133888, 193104112, 195086128, 197079968, + 199085648, 201103184, 203132592, 205173888, 207227120, 209292272, 211369392, 213458480, + 215559568, 217672656, 219797792, 221934976, 224084240, 226245600, 228419056, 230604656, + 232802400, 235012320, 237234432, 239468736, 241715280, 243974080, 246245120, 248528464, + 250824112, 253132064, 255452368, 257785040, 260130080, 262487520, 264857376, 267239664, +}; + +static stbir_uint8 stbir__linear_to_srgb_uchar(float f) +{ + int x = (int) (f * (1 << 28)); // has headroom so you don't need to clamp + int v = 0; + int i; + + // Refine the guess with a short binary search. + i = v + 128; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 64; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 32; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 16; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 8; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 4; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 2; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 1; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + + return (stbir_uint8) v; +} +#endif + +static float stbir__filter_trapezoid(float x, float scale) +{ + float halfscale = scale / 2; + float t = 0.5f + halfscale; + STBIR_ASSERT(scale <= 1); + + x = (float)fabs(x); + + if (x >= t) + return 0; + else + { + float r = 0.5f - halfscale; + if (x <= r) + return 1; + else + return (t - x) / scale; + } +} + +static float stbir__support_trapezoid(float scale) +{ + STBIR_ASSERT(scale <= 1); + return 0.5f + scale / 2; +} + +static float stbir__filter_triangle(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x <= 1.0f) + return 1 - x; + else + return 0; +} + +static float stbir__filter_cubic(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return (4 + x*x*(3*x - 6))/6; + else if (x < 2.0f) + return (8 + x*(-12 + x*(6 - x)))/6; + + return (0.0f); +} + +static float stbir__filter_catmullrom(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return 1 - x*x*(2.5f - 1.5f*x); + else if (x < 2.0f) + return 2 - x*(4 + x*(0.5f*x - 2.5f)); + + return (0.0f); +} + +static float stbir__filter_mitchell(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return (16 + x*x*(21 * x - 36))/18; + else if (x < 2.0f) + return (32 + x*(-60 + x*(36 - 7*x)))/18; + + return (0.0f); +} + +static float stbir__support_zero(float s) +{ + STBIR__UNUSED_PARAM(s); + return 0; +} + +static float stbir__support_one(float s) +{ + STBIR__UNUSED_PARAM(s); + return 1; +} + +static float stbir__support_two(float s) +{ + STBIR__UNUSED_PARAM(s); + return 2; +} + +static stbir__filter_info stbir__filter_info_table[] = { + { NULL, stbir__support_zero }, + { stbir__filter_trapezoid, stbir__support_trapezoid }, + { stbir__filter_triangle, stbir__support_one }, + { stbir__filter_cubic, stbir__support_two }, + { stbir__filter_catmullrom, stbir__support_two }, + { stbir__filter_mitchell, stbir__support_two }, +}; + +stbir__inline static int stbir__use_upsampling(float ratio) +{ + return ratio > 1; +} + +stbir__inline static int stbir__use_width_upsampling(stbir__info* stbir_info) +{ + return stbir__use_upsampling(stbir_info->horizontal_scale); +} + +stbir__inline static int stbir__use_height_upsampling(stbir__info* stbir_info) +{ + return stbir__use_upsampling(stbir_info->vertical_scale); +} + +// This is the maximum number of input samples that can affect an output sample +// with the given filter +static int stbir__get_filter_pixel_width(stbir_filter filter, float scale) +{ + STBIR_ASSERT(filter != 0); + STBIR_ASSERT(filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + + if (stbir__use_upsampling(scale)) + return (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2); + else + return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2 / scale); +} + +// This is how much to expand buffers to account for filters seeking outside +// the image boundaries. +static int stbir__get_filter_pixel_margin(stbir_filter filter, float scale) +{ + return stbir__get_filter_pixel_width(filter, scale) / 2; +} + +static int stbir__get_coefficient_width(stbir_filter filter, float scale) +{ + if (stbir__use_upsampling(scale)) + return (int)ceil(stbir__filter_info_table[filter].support(1 / scale) * 2); + else + return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2); +} + +static int stbir__get_contributors(float scale, stbir_filter filter, int input_size, int output_size) +{ + if (stbir__use_upsampling(scale)) + return output_size; + else + return (input_size + stbir__get_filter_pixel_margin(filter, scale) * 2); +} + +static int stbir__get_total_horizontal_coefficients(stbir__info* info) +{ + return info->horizontal_num_contributors + * stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale); +} + +static int stbir__get_total_vertical_coefficients(stbir__info* info) +{ + return info->vertical_num_contributors + * stbir__get_coefficient_width (info->vertical_filter, info->vertical_scale); +} + +static stbir__contributors* stbir__get_contributor(stbir__contributors* contributors, int n) +{ + return &contributors[n]; +} + +// For perf reasons this code is duplicated in stbir__resample_horizontal_upsample/downsample, +// if you change it here change it there too. +static float* stbir__get_coefficient(float* coefficients, stbir_filter filter, float scale, int n, int c) +{ + int width = stbir__get_coefficient_width(filter, scale); + return &coefficients[width*n + c]; +} + +static int stbir__edge_wrap_slow(stbir_edge edge, int n, int max) +{ + switch (edge) + { + case STBIR_EDGE_ZERO: + return 0; // we'll decode the wrong pixel here, and then overwrite with 0s later + + case STBIR_EDGE_CLAMP: + if (n < 0) + return 0; + + if (n >= max) + return max - 1; + + return n; // NOTREACHED + + case STBIR_EDGE_REFLECT: + { + if (n < 0) + { + if (n < max) + return -n; + else + return max - 1; + } + + if (n >= max) + { + int max2 = max * 2; + if (n >= max2) + return 0; + else + return max2 - n - 1; + } + + return n; // NOTREACHED + } + + case STBIR_EDGE_WRAP: + if (n >= 0) + return (n % max); + else + { + int m = (-n) % max; + + if (m != 0) + m = max - m; + + return (m); + } + // NOTREACHED + + default: + STBIR_ASSERT(!"Unimplemented edge type"); + return 0; + } +} + +stbir__inline static int stbir__edge_wrap(stbir_edge edge, int n, int max) +{ + // avoid per-pixel switch + if (n >= 0 && n < max) + return n; + return stbir__edge_wrap_slow(edge, n, max); +} + +// What input pixels contribute to this output pixel? +static void stbir__calculate_sample_range_upsample(int n, float out_filter_radius, float scale_ratio, float out_shift, int* in_first_pixel, int* in_last_pixel, float* in_center_of_out) +{ + float out_pixel_center = (float)n + 0.5f; + float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius; + float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius; + + float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) / scale_ratio; + float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) / scale_ratio; + + *in_center_of_out = (out_pixel_center + out_shift) / scale_ratio; + *in_first_pixel = (int)(floor(in_pixel_influence_lowerbound + 0.5)); + *in_last_pixel = (int)(floor(in_pixel_influence_upperbound - 0.5)); +} + +// What output pixels does this input pixel contribute to? +static void stbir__calculate_sample_range_downsample(int n, float in_pixels_radius, float scale_ratio, float out_shift, int* out_first_pixel, int* out_last_pixel, float* out_center_of_in) +{ + float in_pixel_center = (float)n + 0.5f; + float in_pixel_influence_lowerbound = in_pixel_center - in_pixels_radius; + float in_pixel_influence_upperbound = in_pixel_center + in_pixels_radius; + + float out_pixel_influence_lowerbound = in_pixel_influence_lowerbound * scale_ratio - out_shift; + float out_pixel_influence_upperbound = in_pixel_influence_upperbound * scale_ratio - out_shift; + + *out_center_of_in = in_pixel_center * scale_ratio - out_shift; + *out_first_pixel = (int)(floor(out_pixel_influence_lowerbound + 0.5)); + *out_last_pixel = (int)(floor(out_pixel_influence_upperbound - 0.5)); +} + +static void stbir__calculate_coefficients_upsample(stbir_filter filter, float scale, int in_first_pixel, int in_last_pixel, float in_center_of_out, stbir__contributors* contributor, float* coefficient_group) +{ + int i; + float total_filter = 0; + float filter_scale; + + STBIR_ASSERT(in_last_pixel - in_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical. + + contributor->n0 = in_first_pixel; + contributor->n1 = in_last_pixel; + + STBIR_ASSERT(contributor->n1 >= contributor->n0); + + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + { + float in_pixel_center = (float)(i + in_first_pixel) + 0.5f; + coefficient_group[i] = stbir__filter_info_table[filter].kernel(in_center_of_out - in_pixel_center, 1 / scale); + + // If the coefficient is zero, skip it. (Don't do the <0 check here, we want the influence of those outside pixels.) + if (i == 0 && !coefficient_group[i]) + { + contributor->n0 = ++in_first_pixel; + i--; + continue; + } + + total_filter += coefficient_group[i]; + } + + // NOTE(fg): Not actually true in general, nor is there any reason to expect it should be. + // It would be true in exact math but is at best approximately true in floating-point math, + // and it would not make sense to try and put actual bounds on this here because it depends + // on the image aspect ratio which can get pretty extreme. + //STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(in_last_pixel + 1) + 0.5f - in_center_of_out, 1/scale) == 0); + + STBIR_ASSERT(total_filter > 0.9); + STBIR_ASSERT(total_filter < 1.1f); // Make sure it's not way off. + + // Make sure the sum of all coefficients is 1. + filter_scale = 1 / total_filter; + + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + coefficient_group[i] *= filter_scale; + + for (i = in_last_pixel - in_first_pixel; i >= 0; i--) + { + if (coefficient_group[i]) + break; + + // This line has no weight. We can skip it. + contributor->n1 = contributor->n0 + i - 1; + } +} + +static void stbir__calculate_coefficients_downsample(stbir_filter filter, float scale_ratio, int out_first_pixel, int out_last_pixel, float out_center_of_in, stbir__contributors* contributor, float* coefficient_group) +{ + int i; + + STBIR_ASSERT(out_last_pixel - out_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(scale_ratio) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical. + + contributor->n0 = out_first_pixel; + contributor->n1 = out_last_pixel; + + STBIR_ASSERT(contributor->n1 >= contributor->n0); + + for (i = 0; i <= out_last_pixel - out_first_pixel; i++) + { + float out_pixel_center = (float)(i + out_first_pixel) + 0.5f; + float x = out_pixel_center - out_center_of_in; + coefficient_group[i] = stbir__filter_info_table[filter].kernel(x, scale_ratio) * scale_ratio; + } + + // NOTE(fg): Not actually true in general, nor is there any reason to expect it should be. + // It would be true in exact math but is at best approximately true in floating-point math, + // and it would not make sense to try and put actual bounds on this here because it depends + // on the image aspect ratio which can get pretty extreme. + //STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(out_last_pixel + 1) + 0.5f - out_center_of_in, scale_ratio) == 0); + + for (i = out_last_pixel - out_first_pixel; i >= 0; i--) + { + if (coefficient_group[i]) + break; + + // This line has no weight. We can skip it. + contributor->n1 = contributor->n0 + i - 1; + } +} + +static void stbir__normalize_downsample_coefficients(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, int input_size, int output_size) +{ + int num_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size); + int num_coefficients = stbir__get_coefficient_width(filter, scale_ratio); + int i, j; + int skip; + + for (i = 0; i < output_size; i++) + { + float scale; + float total = 0; + + for (j = 0; j < num_contributors; j++) + { + if (i >= contributors[j].n0 && i <= contributors[j].n1) + { + float coefficient = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0); + total += coefficient; + } + else if (i < contributors[j].n0) + break; + } + + STBIR_ASSERT(total > 0.9f); + STBIR_ASSERT(total < 1.1f); + + scale = 1 / total; + + for (j = 0; j < num_contributors; j++) + { + if (i >= contributors[j].n0 && i <= contributors[j].n1) + *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0) *= scale; + else if (i < contributors[j].n0) + break; + } + } + + // Optimize: Skip zero coefficients and contributions outside of image bounds. + // Do this after normalizing because normalization depends on the n0/n1 values. + for (j = 0; j < num_contributors; j++) + { + int range, max, width; + + skip = 0; + while (*stbir__get_coefficient(coefficients, filter, scale_ratio, j, skip) == 0) + skip++; + + contributors[j].n0 += skip; + + while (contributors[j].n0 < 0) + { + contributors[j].n0++; + skip++; + } + + range = contributors[j].n1 - contributors[j].n0 + 1; + max = stbir__min(num_coefficients, range); + + width = stbir__get_coefficient_width(filter, scale_ratio); + for (i = 0; i < max; i++) + { + if (i + skip >= width) + break; + + *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i) = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i + skip); + } + + continue; + } + + // Using min to avoid writing into invalid pixels. + for (i = 0; i < num_contributors; i++) + contributors[i].n1 = stbir__min(contributors[i].n1, output_size - 1); +} + +// Each scan line uses the same kernel values so we should calculate the kernel +// values once and then we can use them for every scan line. +static void stbir__calculate_filters(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, float shift, int input_size, int output_size) +{ + int n; + int total_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size); + + if (stbir__use_upsampling(scale_ratio)) + { + float out_pixels_radius = stbir__filter_info_table[filter].support(1 / scale_ratio) * scale_ratio; + + // Looping through out pixels + for (n = 0; n < total_contributors; n++) + { + float in_center_of_out; // Center of the current out pixel in the in pixel space + int in_first_pixel, in_last_pixel; + + stbir__calculate_sample_range_upsample(n, out_pixels_radius, scale_ratio, shift, &in_first_pixel, &in_last_pixel, &in_center_of_out); + + stbir__calculate_coefficients_upsample(filter, scale_ratio, in_first_pixel, in_last_pixel, in_center_of_out, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0)); + } + } + else + { + float in_pixels_radius = stbir__filter_info_table[filter].support(scale_ratio) / scale_ratio; + + // Looping through in pixels + for (n = 0; n < total_contributors; n++) + { + float out_center_of_in; // Center of the current out pixel in the in pixel space + int out_first_pixel, out_last_pixel; + int n_adjusted = n - stbir__get_filter_pixel_margin(filter, scale_ratio); + + stbir__calculate_sample_range_downsample(n_adjusted, in_pixels_radius, scale_ratio, shift, &out_first_pixel, &out_last_pixel, &out_center_of_in); + + stbir__calculate_coefficients_downsample(filter, scale_ratio, out_first_pixel, out_last_pixel, out_center_of_in, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0)); + } + + stbir__normalize_downsample_coefficients(contributors, coefficients, filter, scale_ratio, input_size, output_size); + } +} + +static float* stbir__get_decode_buffer(stbir__info* stbir_info) +{ + // The 0 index of the decode buffer starts after the margin. This makes + // it okay to use negative indexes on the decode buffer. + return &stbir_info->decode_buffer[stbir_info->horizontal_filter_pixel_margin * stbir_info->channels]; +} + +#define STBIR__DECODE(type, colorspace) ((int)(type) * (STBIR_MAX_COLORSPACES) + (int)(colorspace)) + +static void stbir__decode_scanline(stbir__info* stbir_info, int n) +{ + int c; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int input_w = stbir_info->input_w; + size_t input_stride_bytes = stbir_info->input_stride_bytes; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir_edge edge_horizontal = stbir_info->edge_horizontal; + stbir_edge edge_vertical = stbir_info->edge_vertical; + size_t in_buffer_row_offset = stbir__edge_wrap(edge_vertical, n, stbir_info->input_h) * input_stride_bytes; + const void* input_data = (char *) stbir_info->input_data + in_buffer_row_offset; + int max_x = input_w + stbir_info->horizontal_filter_pixel_margin; + int decode = STBIR__DECODE(type, colorspace); + + int x = -stbir_info->horizontal_filter_pixel_margin; + + // special handling for STBIR_EDGE_ZERO because it needs to return an item that doesn't appear in the input, + // and we want to avoid paying overhead on every pixel if not STBIR_EDGE_ZERO + if (edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->input_h)) + { + for (; x < max_x; x++) + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + return; + } + + switch (decode) + { + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((float)((const unsigned char*)input_data)[input_pixel_index + c]) / stbir__max_uint8_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_uchar_to_linear_float[((const unsigned char*)input_data)[input_pixel_index + c]]; + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned char*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint8_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned short*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint16_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear((float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float)); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((const float*)input_data)[input_pixel_index + c]; + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((const float*)input_data)[input_pixel_index + c]); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((const float*)input_data)[input_pixel_index + alpha_channel]; + } + + break; + + default: + STBIR_ASSERT(!"Unknown type/colorspace/channels combination."); + break; + } + + if (!(stbir_info->flags & STBIR_FLAG_ALPHA_PREMULTIPLIED)) + { + for (x = -stbir_info->horizontal_filter_pixel_margin; x < max_x; x++) + { + int decode_pixel_index = x * channels; + + // If the alpha value is 0 it will clobber the color values. Make sure it's not. + float alpha = decode_buffer[decode_pixel_index + alpha_channel]; +#ifndef STBIR_NO_ALPHA_EPSILON + if (stbir_info->type != STBIR_TYPE_FLOAT) { + alpha += STBIR_ALPHA_EPSILON; + decode_buffer[decode_pixel_index + alpha_channel] = alpha; + } +#endif + for (c = 0; c < channels; c++) + { + if (c == alpha_channel) + continue; + + decode_buffer[decode_pixel_index + c] *= alpha; + } + } + } + + if (edge_horizontal == STBIR_EDGE_ZERO) + { + for (x = -stbir_info->horizontal_filter_pixel_margin; x < 0; x++) + { + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + } + for (x = input_w; x < max_x; x++) + { + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + } + } +} + +static float* stbir__get_ring_buffer_entry(float* ring_buffer, int index, int ring_buffer_length) +{ + return &ring_buffer[index * ring_buffer_length]; +} + +static float* stbir__add_empty_ring_buffer_entry(stbir__info* stbir_info, int n) +{ + int ring_buffer_index; + float* ring_buffer; + + stbir_info->ring_buffer_last_scanline = n; + + if (stbir_info->ring_buffer_begin_index < 0) + { + ring_buffer_index = stbir_info->ring_buffer_begin_index = 0; + stbir_info->ring_buffer_first_scanline = n; + } + else + { + ring_buffer_index = (stbir_info->ring_buffer_begin_index + (stbir_info->ring_buffer_last_scanline - stbir_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries; + STBIR_ASSERT(ring_buffer_index != stbir_info->ring_buffer_begin_index); + } + + ring_buffer = stbir__get_ring_buffer_entry(stbir_info->ring_buffer, ring_buffer_index, stbir_info->ring_buffer_length_bytes / sizeof(float)); + memset(ring_buffer, 0, stbir_info->ring_buffer_length_bytes); + + return ring_buffer; +} + + +static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, float* output_buffer) +{ + int x, k; + int output_w = stbir_info->output_w; + int channels = stbir_info->channels; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors; + float* horizontal_coefficients = stbir_info->horizontal_coefficients; + int coefficient_width = stbir_info->horizontal_coefficient_width; + + for (x = 0; x < output_w; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int out_pixel_index = x * channels; + int coefficient_group = coefficient_width * x; + int coefficient_counter = 0; + + STBIR_ASSERT(n1 >= n0); + STBIR_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n1 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin); + + switch (channels) { + case 1: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 1; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + } + break; + case 2: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 2; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + } + break; + case 3: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 3; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + } + break; + case 4: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 4; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient; + } + break; + default: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * channels; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + int c; + STBIR_ASSERT(coefficient != 0); + for (c = 0; c < channels; c++) + output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient; + } + break; + } + } +} + +static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, float* output_buffer) +{ + int x, k; + int input_w = stbir_info->input_w; + int channels = stbir_info->channels; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors; + float* horizontal_coefficients = stbir_info->horizontal_coefficients; + int coefficient_width = stbir_info->horizontal_coefficient_width; + int filter_pixel_margin = stbir_info->horizontal_filter_pixel_margin; + int max_x = input_w + filter_pixel_margin * 2; + + STBIR_ASSERT(!stbir__use_width_upsampling(stbir_info)); + + switch (channels) { + case 1: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 1; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 1; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + } + } + break; + + case 2: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 2; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 2; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + } + } + break; + + case 3: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 3; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 3; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + } + } + break; + + case 4: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 4; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 4; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient; + } + } + break; + + default: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * channels; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int c; + int out_pixel_index = k * channels; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + for (c = 0; c < channels; c++) + output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient; + } + } + break; + } +} + +static void stbir__decode_and_resample_upsample(stbir__info* stbir_info, int n) +{ + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline(stbir_info, n); + + // Now resample it into the ring buffer. + if (stbir__use_width_upsampling(stbir_info)) + stbir__resample_horizontal_upsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n)); + else + stbir__resample_horizontal_downsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n)); + + // Now it's sitting in the ring buffer ready to be used as source for the vertical sampling. +} + +static void stbir__decode_and_resample_downsample(stbir__info* stbir_info, int n) +{ + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline(stbir_info, n); + + memset(stbir_info->horizontal_buffer, 0, stbir_info->output_w * stbir_info->channels * sizeof(float)); + + // Now resample it into the horizontal buffer. + if (stbir__use_width_upsampling(stbir_info)) + stbir__resample_horizontal_upsample(stbir_info, stbir_info->horizontal_buffer); + else + stbir__resample_horizontal_downsample(stbir_info, stbir_info->horizontal_buffer); + + // Now it's sitting in the horizontal buffer ready to be distributed into the ring buffers. +} + +// Get the specified scan line from the ring buffer. +static float* stbir__get_ring_buffer_scanline(int get_scanline, float* ring_buffer, int begin_index, int first_scanline, int ring_buffer_num_entries, int ring_buffer_length) +{ + int ring_buffer_index = (begin_index + (get_scanline - first_scanline)) % ring_buffer_num_entries; + return stbir__get_ring_buffer_entry(ring_buffer, ring_buffer_index, ring_buffer_length); +} + + +static void stbir__encode_scanline(stbir__info* stbir_info, int num_pixels, void *output_buffer, float *encode_buffer, int channels, int alpha_channel, int decode) +{ + int x; + int n; + int num_nonalpha; + stbir_uint16 nonalpha[STBIR_MAX_CHANNELS]; + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) + { + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + float alpha = encode_buffer[pixel_index + alpha_channel]; + float reciprocal_alpha = alpha ? 1.0f / alpha : 0; + + // unrolling this produced a 1% slowdown upscaling a large RGBA linear-space image on my machine - stb + for (n = 0; n < channels; n++) + if (n != alpha_channel) + encode_buffer[pixel_index + n] *= reciprocal_alpha; + + // We added in a small epsilon to prevent the color channel from being deleted with zero alpha. + // Because we only add it for integer types, it will automatically be discarded on integer + // conversion, so we don't need to subtract it back out (which would be problematic for + // numeric precision reasons). + } + } + + // build a table of all channels that need colorspace correction, so + // we don't perform colorspace correction on channels that don't need it. + for (x = 0, num_nonalpha = 0; x < channels; ++x) + { + if (x != alpha_channel || (stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE)) + { + nonalpha[num_nonalpha++] = (stbir_uint16)x; + } + } + + #define STBIR__ROUND_INT(f) ((int) ((f)+0.5)) + #define STBIR__ROUND_UINT(f) ((stbir_uint32) ((f)+0.5)) + + #ifdef STBIR__SATURATE_INT + #define STBIR__ENCODE_LINEAR8(f) stbir__saturate8 (STBIR__ROUND_INT((f) * stbir__max_uint8_as_float )) + #define STBIR__ENCODE_LINEAR16(f) stbir__saturate16(STBIR__ROUND_INT((f) * stbir__max_uint16_as_float)) + #else + #define STBIR__ENCODE_LINEAR8(f) (unsigned char ) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint8_as_float ) + #define STBIR__ENCODE_LINEAR16(f) (unsigned short) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint16_as_float) + #endif + + switch (decode) + { + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned char*)output_buffer)[index] = STBIR__ENCODE_LINEAR8(encode_buffer[index]); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned char*)output_buffer)[index] = stbir__linear_to_srgb_uchar(encode_buffer[index]); + } + + if (!(stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned char *)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR8(encode_buffer[pixel_index+alpha_channel]); + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned short*)output_buffer)[index] = STBIR__ENCODE_LINEAR16(encode_buffer[index]); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned short*)output_buffer)[index] = (unsigned short)STBIR__ROUND_INT(stbir__linear_to_srgb(stbir__saturate(encode_buffer[index])) * stbir__max_uint16_as_float); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned short*)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR16(encode_buffer[pixel_index + alpha_channel]); + } + + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__saturate(encode_buffer[index])) * stbir__max_uint32_as_float); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__linear_to_srgb(stbir__saturate(encode_buffer[index]))) * stbir__max_uint32_as_float); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned int*)output_buffer)[pixel_index + alpha_channel] = (unsigned int)STBIR__ROUND_INT(((double)stbir__saturate(encode_buffer[pixel_index + alpha_channel])) * stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((float*)output_buffer)[index] = encode_buffer[index]; + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((float*)output_buffer)[index] = stbir__linear_to_srgb(encode_buffer[index]); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((float*)output_buffer)[pixel_index + alpha_channel] = encode_buffer[pixel_index + alpha_channel]; + } + break; + + default: + STBIR_ASSERT(!"Unknown type/colorspace/channels combination."); + break; + } +} + +static void stbir__resample_vertical_upsample(stbir__info* stbir_info, int n) +{ + int x, k; + int output_w = stbir_info->output_w; + stbir__contributors* vertical_contributors = stbir_info->vertical_contributors; + float* vertical_coefficients = stbir_info->vertical_coefficients; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int ring_buffer_entries = stbir_info->ring_buffer_num_entries; + void* output_data = stbir_info->output_data; + float* encode_buffer = stbir_info->encode_buffer; + int decode = STBIR__DECODE(type, colorspace); + int coefficient_width = stbir_info->vertical_coefficient_width; + int coefficient_counter; + int contributor = n; + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index; + int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + + int n0,n1, output_row_start; + int coefficient_group = coefficient_width * contributor; + + n0 = vertical_contributors[contributor].n0; + n1 = vertical_contributors[contributor].n1; + + output_row_start = n * stbir_info->output_stride_bytes; + + STBIR_ASSERT(stbir__use_height_upsampling(stbir_info)); + + memset(encode_buffer, 0, output_w * sizeof(float) * channels); + + // I tried reblocking this for better cache usage of encode_buffer + // (using x_outer, k, x_inner), but it lost speed. -- stb + + coefficient_counter = 0; + switch (channels) { + case 1: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 1; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + } + } + break; + case 2: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 2; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + } + } + break; + case 3: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 3; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient; + } + } + break; + case 4: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 4; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient; + encode_buffer[in_pixel_index + 3] += ring_buffer_entry[in_pixel_index + 3] * coefficient; + } + } + break; + default: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * channels; + int c; + for (c = 0; c < channels; c++) + encode_buffer[in_pixel_index + c] += ring_buffer_entry[in_pixel_index + c] * coefficient; + } + } + break; + } + stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, encode_buffer, channels, alpha_channel, decode); +} + +static void stbir__resample_vertical_downsample(stbir__info* stbir_info, int n) +{ + int x, k; + int output_w = stbir_info->output_w; + stbir__contributors* vertical_contributors = stbir_info->vertical_contributors; + float* vertical_coefficients = stbir_info->vertical_coefficients; + int channels = stbir_info->channels; + int ring_buffer_entries = stbir_info->ring_buffer_num_entries; + float* horizontal_buffer = stbir_info->horizontal_buffer; + int coefficient_width = stbir_info->vertical_coefficient_width; + int contributor = n + stbir_info->vertical_filter_pixel_margin; + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index; + int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + int n0,n1; + + n0 = vertical_contributors[contributor].n0; + n1 = vertical_contributors[contributor].n1; + + STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info)); + + for (k = n0; k <= n1; k++) + { + int coefficient_index = k - n0; + int coefficient_group = coefficient_width * contributor; + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + + switch (channels) { + case 1: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 1; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + } + break; + case 2: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 2; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + } + break; + case 3: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 3; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient; + } + break; + case 4: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 4; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient; + ring_buffer_entry[in_pixel_index + 3] += horizontal_buffer[in_pixel_index + 3] * coefficient; + } + break; + default: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * channels; + + int c; + for (c = 0; c < channels; c++) + ring_buffer_entry[in_pixel_index + c] += horizontal_buffer[in_pixel_index + c] * coefficient; + } + break; + } + } +} + +static void stbir__buffer_loop_upsample(stbir__info* stbir_info) +{ + int y; + float scale_ratio = stbir_info->vertical_scale; + float out_scanlines_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(1/scale_ratio) * scale_ratio; + + STBIR_ASSERT(stbir__use_height_upsampling(stbir_info)); + + for (y = 0; y < stbir_info->output_h; y++) + { + float in_center_of_out = 0; // Center of the current out scanline in the in scanline space + int in_first_scanline = 0, in_last_scanline = 0; + + stbir__calculate_sample_range_upsample(y, out_scanlines_radius, scale_ratio, stbir_info->vertical_shift, &in_first_scanline, &in_last_scanline, &in_center_of_out); + + STBIR_ASSERT(in_last_scanline - in_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if (stbir_info->ring_buffer_begin_index >= 0) + { + // Get rid of whatever we don't need anymore. + while (in_first_scanline > stbir_info->ring_buffer_first_scanline) + { + if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline) + { + // We just popped the last scanline off the ring buffer. + // Reset it to the empty state. + stbir_info->ring_buffer_begin_index = -1; + stbir_info->ring_buffer_first_scanline = 0; + stbir_info->ring_buffer_last_scanline = 0; + break; + } + else + { + stbir_info->ring_buffer_first_scanline++; + stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries; + } + } + } + + // Load in new ones. + if (stbir_info->ring_buffer_begin_index < 0) + stbir__decode_and_resample_upsample(stbir_info, in_first_scanline); + + while (in_last_scanline > stbir_info->ring_buffer_last_scanline) + stbir__decode_and_resample_upsample(stbir_info, stbir_info->ring_buffer_last_scanline + 1); + + // Now all buffers should be ready to write a row of vertical sampling. + stbir__resample_vertical_upsample(stbir_info, y); + + STBIR_PROGRESS_REPORT((float)y / stbir_info->output_h); + } +} + +static void stbir__empty_ring_buffer(stbir__info* stbir_info, int first_necessary_scanline) +{ + int output_stride_bytes = stbir_info->output_stride_bytes; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int output_w = stbir_info->output_w; + void* output_data = stbir_info->output_data; + int decode = STBIR__DECODE(type, colorspace); + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + + if (stbir_info->ring_buffer_begin_index >= 0) + { + // Get rid of whatever we don't need anymore. + while (first_necessary_scanline > stbir_info->ring_buffer_first_scanline) + { + if (stbir_info->ring_buffer_first_scanline >= 0 && stbir_info->ring_buffer_first_scanline < stbir_info->output_h) + { + int output_row_start = stbir_info->ring_buffer_first_scanline * output_stride_bytes; + float* ring_buffer_entry = stbir__get_ring_buffer_entry(ring_buffer, stbir_info->ring_buffer_begin_index, ring_buffer_length); + stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, ring_buffer_entry, channels, alpha_channel, decode); + STBIR_PROGRESS_REPORT((float)stbir_info->ring_buffer_first_scanline / stbir_info->output_h); + } + + if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline) + { + // We just popped the last scanline off the ring buffer. + // Reset it to the empty state. + stbir_info->ring_buffer_begin_index = -1; + stbir_info->ring_buffer_first_scanline = 0; + stbir_info->ring_buffer_last_scanline = 0; + break; + } + else + { + stbir_info->ring_buffer_first_scanline++; + stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries; + } + } + } +} + +static void stbir__buffer_loop_downsample(stbir__info* stbir_info) +{ + int y; + float scale_ratio = stbir_info->vertical_scale; + int output_h = stbir_info->output_h; + float in_pixels_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(scale_ratio) / scale_ratio; + int pixel_margin = stbir_info->vertical_filter_pixel_margin; + int max_y = stbir_info->input_h + pixel_margin; + + STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info)); + + for (y = -pixel_margin; y < max_y; y++) + { + float out_center_of_in; // Center of the current out scanline in the in scanline space + int out_first_scanline, out_last_scanline; + + stbir__calculate_sample_range_downsample(y, in_pixels_radius, scale_ratio, stbir_info->vertical_shift, &out_first_scanline, &out_last_scanline, &out_center_of_in); + + STBIR_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if (out_last_scanline < 0 || out_first_scanline >= output_h) + continue; + + stbir__empty_ring_buffer(stbir_info, out_first_scanline); + + stbir__decode_and_resample_downsample(stbir_info, y); + + // Load in new ones. + if (stbir_info->ring_buffer_begin_index < 0) + stbir__add_empty_ring_buffer_entry(stbir_info, out_first_scanline); + + while (out_last_scanline > stbir_info->ring_buffer_last_scanline) + stbir__add_empty_ring_buffer_entry(stbir_info, stbir_info->ring_buffer_last_scanline + 1); + + // Now the horizontal buffer is ready to write to all ring buffer rows. + stbir__resample_vertical_downsample(stbir_info, y); + } + + stbir__empty_ring_buffer(stbir_info, stbir_info->output_h); +} + +static void stbir__setup(stbir__info *info, int input_w, int input_h, int output_w, int output_h, int channels) +{ + info->input_w = input_w; + info->input_h = input_h; + info->output_w = output_w; + info->output_h = output_h; + info->channels = channels; +} + +static void stbir__calculate_transform(stbir__info *info, float s0, float t0, float s1, float t1, float *transform) +{ + info->s0 = s0; + info->t0 = t0; + info->s1 = s1; + info->t1 = t1; + + if (transform) + { + info->horizontal_scale = transform[0]; + info->vertical_scale = transform[1]; + info->horizontal_shift = transform[2]; + info->vertical_shift = transform[3]; + } + else + { + info->horizontal_scale = ((float)info->output_w / info->input_w) / (s1 - s0); + info->vertical_scale = ((float)info->output_h / info->input_h) / (t1 - t0); + + info->horizontal_shift = s0 * info->output_w / (s1 - s0); + info->vertical_shift = t0 * info->output_h / (t1 - t0); + } +} + +static void stbir__choose_filter(stbir__info *info, stbir_filter h_filter, stbir_filter v_filter) +{ + if (h_filter == 0) + h_filter = stbir__use_upsampling(info->horizontal_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE; + if (v_filter == 0) + v_filter = stbir__use_upsampling(info->vertical_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE; + info->horizontal_filter = h_filter; + info->vertical_filter = v_filter; +} + +static stbir_uint32 stbir__calculate_memory(stbir__info *info) +{ + int pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale); + int filter_height = stbir__get_filter_pixel_width(info->vertical_filter, info->vertical_scale); + + info->horizontal_num_contributors = stbir__get_contributors(info->horizontal_scale, info->horizontal_filter, info->input_w, info->output_w); + info->vertical_num_contributors = stbir__get_contributors(info->vertical_scale , info->vertical_filter , info->input_h, info->output_h); + + // One extra entry because floating point precision problems sometimes cause an extra to be necessary. + info->ring_buffer_num_entries = filter_height + 1; + + info->horizontal_contributors_size = info->horizontal_num_contributors * sizeof(stbir__contributors); + info->horizontal_coefficients_size = stbir__get_total_horizontal_coefficients(info) * sizeof(float); + info->vertical_contributors_size = info->vertical_num_contributors * sizeof(stbir__contributors); + info->vertical_coefficients_size = stbir__get_total_vertical_coefficients(info) * sizeof(float); + info->decode_buffer_size = (info->input_w + pixel_margin * 2) * info->channels * sizeof(float); + info->horizontal_buffer_size = info->output_w * info->channels * sizeof(float); + info->ring_buffer_size = info->output_w * info->channels * info->ring_buffer_num_entries * sizeof(float); + info->encode_buffer_size = info->output_w * info->channels * sizeof(float); + + STBIR_ASSERT(info->horizontal_filter != 0); + STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late + STBIR_ASSERT(info->vertical_filter != 0); + STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late + + if (stbir__use_height_upsampling(info)) + // The horizontal buffer is for when we're downsampling the height and we + // can't output the result of sampling the decode buffer directly into the + // ring buffers. + info->horizontal_buffer_size = 0; + else + // The encode buffer is to retain precision in the height upsampling method + // and isn't used when height downsampling. + info->encode_buffer_size = 0; + + return info->horizontal_contributors_size + info->horizontal_coefficients_size + + info->vertical_contributors_size + info->vertical_coefficients_size + + info->decode_buffer_size + info->horizontal_buffer_size + + info->ring_buffer_size + info->encode_buffer_size; +} + +static int stbir__resize_allocated(stbir__info *info, + const void* input_data, int input_stride_in_bytes, + void* output_data, int output_stride_in_bytes, + int alpha_channel, stbir_uint32 flags, stbir_datatype type, + stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace, + void* tempmem, size_t tempmem_size_in_bytes) +{ + size_t memory_required = stbir__calculate_memory(info); + + int width_stride_input = input_stride_in_bytes ? input_stride_in_bytes : info->channels * info->input_w * stbir__type_size[type]; + int width_stride_output = output_stride_in_bytes ? output_stride_in_bytes : info->channels * info->output_w * stbir__type_size[type]; + +#ifdef STBIR_DEBUG_OVERWRITE_TEST +#define OVERWRITE_ARRAY_SIZE 8 + unsigned char overwrite_output_before_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_tempmem_before_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_output_after_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_tempmem_after_pre[OVERWRITE_ARRAY_SIZE]; + + size_t begin_forbidden = width_stride_output * (info->output_h - 1) + info->output_w * info->channels * stbir__type_size[type]; + memcpy(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE); +#endif + + STBIR_ASSERT(info->channels >= 0); + STBIR_ASSERT(info->channels <= STBIR_MAX_CHANNELS); + + if (info->channels < 0 || info->channels > STBIR_MAX_CHANNELS) + return 0; + + STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + + if (info->horizontal_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table)) + return 0; + if (info->vertical_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table)) + return 0; + + if (alpha_channel < 0) + flags |= STBIR_FLAG_ALPHA_USES_COLORSPACE | STBIR_FLAG_ALPHA_PREMULTIPLIED; + + if (!(flags&STBIR_FLAG_ALPHA_USES_COLORSPACE) || !(flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) { + STBIR_ASSERT(alpha_channel >= 0 && alpha_channel < info->channels); + } + + if (alpha_channel >= info->channels) + return 0; + + STBIR_ASSERT(tempmem); + + if (!tempmem) + return 0; + + STBIR_ASSERT(tempmem_size_in_bytes >= memory_required); + + if (tempmem_size_in_bytes < memory_required) + return 0; + + memset(tempmem, 0, tempmem_size_in_bytes); + + info->input_data = input_data; + info->input_stride_bytes = width_stride_input; + + info->output_data = output_data; + info->output_stride_bytes = width_stride_output; + + info->alpha_channel = alpha_channel; + info->flags = flags; + info->type = type; + info->edge_horizontal = edge_horizontal; + info->edge_vertical = edge_vertical; + info->colorspace = colorspace; + + info->horizontal_coefficient_width = stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale); + info->vertical_coefficient_width = stbir__get_coefficient_width (info->vertical_filter , info->vertical_scale ); + info->horizontal_filter_pixel_width = stbir__get_filter_pixel_width (info->horizontal_filter, info->horizontal_scale); + info->vertical_filter_pixel_width = stbir__get_filter_pixel_width (info->vertical_filter , info->vertical_scale ); + info->horizontal_filter_pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale); + info->vertical_filter_pixel_margin = stbir__get_filter_pixel_margin(info->vertical_filter , info->vertical_scale ); + + info->ring_buffer_length_bytes = info->output_w * info->channels * sizeof(float); + info->decode_buffer_pixels = info->input_w + info->horizontal_filter_pixel_margin * 2; + +#define STBIR__NEXT_MEMPTR(current, newtype) (newtype*)(((unsigned char*)current) + current##_size) + + info->horizontal_contributors = (stbir__contributors *) tempmem; + info->horizontal_coefficients = STBIR__NEXT_MEMPTR(info->horizontal_contributors, float); + info->vertical_contributors = STBIR__NEXT_MEMPTR(info->horizontal_coefficients, stbir__contributors); + info->vertical_coefficients = STBIR__NEXT_MEMPTR(info->vertical_contributors, float); + info->decode_buffer = STBIR__NEXT_MEMPTR(info->vertical_coefficients, float); + + if (stbir__use_height_upsampling(info)) + { + info->horizontal_buffer = NULL; + info->ring_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float); + info->encode_buffer = STBIR__NEXT_MEMPTR(info->ring_buffer, float); + + STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->encode_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes); + } + else + { + info->horizontal_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float); + info->ring_buffer = STBIR__NEXT_MEMPTR(info->horizontal_buffer, float); + info->encode_buffer = NULL; + + STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->ring_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes); + } + +#undef STBIR__NEXT_MEMPTR + + // This signals that the ring buffer is empty + info->ring_buffer_begin_index = -1; + + stbir__calculate_filters(info->horizontal_contributors, info->horizontal_coefficients, info->horizontal_filter, info->horizontal_scale, info->horizontal_shift, info->input_w, info->output_w); + stbir__calculate_filters(info->vertical_contributors, info->vertical_coefficients, info->vertical_filter, info->vertical_scale, info->vertical_shift, info->input_h, info->output_h); + + STBIR_PROGRESS_REPORT(0); + + if (stbir__use_height_upsampling(info)) + stbir__buffer_loop_upsample(info); + else + stbir__buffer_loop_downsample(info); + + STBIR_PROGRESS_REPORT(1); + +#ifdef STBIR_DEBUG_OVERWRITE_TEST + STBIR_ASSERT(memcmp(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE) == 0); +#endif + + return 1; +} + + +static int stbir__resize_arbitrary( + void *alloc_context, + const void* input_data, int input_w, int input_h, int input_stride_in_bytes, + void* output_data, int output_w, int output_h, int output_stride_in_bytes, + float s0, float t0, float s1, float t1, float *transform, + int channels, int alpha_channel, stbir_uint32 flags, stbir_datatype type, + stbir_filter h_filter, stbir_filter v_filter, + stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace) +{ + stbir__info info; + int result; + size_t memory_required; + void* extra_memory; + + stbir__setup(&info, input_w, input_h, output_w, output_h, channels); + stbir__calculate_transform(&info, s0,t0,s1,t1,transform); + stbir__choose_filter(&info, h_filter, v_filter); + memory_required = stbir__calculate_memory(&info); + extra_memory = STBIR_MALLOC(memory_required, alloc_context); + + if (!extra_memory) + return 0; + + result = stbir__resize_allocated(&info, input_data, input_stride_in_bytes, + output_data, output_stride_in_bytes, + alpha_channel, flags, type, + edge_horizontal, edge_vertical, + colorspace, extra_memory, memory_required); + + STBIR_FREE(extra_memory, alloc_context); + + return result; +} + +STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR); +} + +STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_FLOAT, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR); +} + +STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB); +} + +STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + edge_wrap_mode, edge_wrap_mode, STBIR_COLORSPACE_SRGB); +} + +STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + +STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT16, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + + +STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_FLOAT, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + + +STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + + +STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float x_scale, float y_scale, + float x_offset, float y_offset) +{ + float transform[4]; + transform[0] = x_scale; + transform[1] = y_scale; + transform[2] = x_offset; + transform[3] = y_offset; + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,transform,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float s0, float t0, float s1, float t1) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + s0,t0,s1,t1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +#endif // STB_IMAGE_RESIZE_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/deprecated/stretch_test.c b/source/engine/thirdparty/stb/deprecated/stretch_test.c new file mode 100644 index 0000000..772237c --- /dev/null +++ b/source/engine/thirdparty/stb/deprecated/stretch_test.c @@ -0,0 +1,29 @@ +// check that stb_truetype compiles with no stb_rect_pack.h +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" + +#define STB_DS_IMPLEMENTATION +#include "stb_ds.h" +#include + +int main(int arg, char **argv) +{ + int i; + int *arr = NULL; + + for (i=0; i < 1000000; ++i) + arrput(arr, i); + + assert(arrlen(arr) == 1000000); + for (i=0; i < 1000000; ++i) + assert(arr[i] == i); + + arrfree(arr); + arr = NULL; + + for (i=0; i < 1000; ++i) + arrput(arr, 1000); + assert(arrlen(arr) == 1000000); + + return 0; +} \ No newline at end of file diff --git a/source/engine/thirdparty/stb/deprecated/stretchy_buffer.h b/source/engine/thirdparty/stb/deprecated/stretchy_buffer.h new file mode 100644 index 0000000..c0880ff --- /dev/null +++ b/source/engine/thirdparty/stb/deprecated/stretchy_buffer.h @@ -0,0 +1,263 @@ +// stretchy_buffer.h - v1.04 - public domain - nothings.org/stb +// a vector<>-like dynamic array for C +// +// version history: +// 1.04 - fix warning +// 1.03 - compile as C++ maybe +// 1.02 - tweaks to syntax for no good reason +// 1.01 - added a "common uses" documentation section +// 1.0 - fixed bug in the version I posted prematurely +// 0.9 - rewrite to try to avoid strict-aliasing optimization +// issues, but won't compile as C++ +// +// Will probably not work correctly with strict-aliasing optimizations. +// +// The idea: +// +// This implements an approximation to C++ vector<> for C, in that it +// provides a generic definition for dynamic arrays which you can +// still access in a typesafe way using arr[i] or *(arr+i). However, +// it is simply a convenience wrapper around the common idiom of +// of keeping a set of variables (in a struct or globals) which store +// - pointer to array +// - the length of the "in-use" part of the array +// - the current size of the allocated array +// +// I find it to be the single most useful non-built-in-structure when +// programming in C (hash tables a close second), but to be clear +// it lacks many of the capabilities of C++ vector<>: there is no +// range checking, the object address isn't stable (see next section +// for details), the set of methods available is small (although +// the file stb.h has another implementation of stretchy buffers +// called 'stb_arr' which provides more methods, e.g. for insertion +// and deletion). +// +// How to use: +// +// Unlike other stb header file libraries, there is no need to +// define an _IMPLEMENTATION symbol. Every #include creates as +// much implementation is needed. +// +// stretchy_buffer.h does not define any types, so you do not +// need to #include it to before defining data types that are +// stretchy buffers, only in files that *manipulate* stretchy +// buffers. +// +// If you want a stretchy buffer aka dynamic array containing +// objects of TYPE, declare such an array as: +// +// TYPE *myarray = NULL; +// +// (There is no typesafe way to distinguish between stretchy +// buffers and regular arrays/pointers; this is necessary to +// make ordinary array indexing work on these objects.) +// +// Unlike C++ vector<>, the stretchy_buffer has the same +// semantics as an object that you manually malloc and realloc. +// The pointer may relocate every time you add a new object +// to it, so you: +// +// 1. can't take long-term pointers to elements of the array +// 2. have to return the pointer from functions which might expand it +// (either as a return value or by storing it to a ptr-to-ptr) +// +// Now you can do the following things with this array: +// +// sb_free(TYPE *a) free the array +// sb_count(TYPE *a) the number of elements in the array +// sb_push(TYPE *a, TYPE v) adds v on the end of the array, a la push_back +// sb_add(TYPE *a, int n) adds n uninitialized elements at end of array & returns pointer to first added +// sb_last(TYPE *a) returns an lvalue of the last item in the array +// a[n] access the nth (counting from 0) element of the array +// +// #define STRETCHY_BUFFER_NO_SHORT_NAMES to only export +// names of the form 'stb_sb_' if you have a name that would +// otherwise collide. +// +// Note that these are all macros and many of them evaluate +// their arguments more than once, so the arguments should +// be side-effect-free. +// +// Note that 'TYPE *a' in sb_push and sb_add must be lvalues +// so that the library can overwrite the existing pointer if +// the object has to be reallocated. +// +// In an out-of-memory condition, the code will try to +// set up a null-pointer or otherwise-invalid-pointer +// exception to happen later. It's possible optimizing +// compilers could detect this write-to-null statically +// and optimize away some of the code, but it should only +// be along the failure path. Nevertheless, for more security +// in the face of such compilers, #define STRETCHY_BUFFER_OUT_OF_MEMORY +// to a statement such as assert(0) or exit(1) or something +// to force a failure when out-of-memory occurs. +// +// Common use: +// +// The main application for this is when building a list of +// things with an unknown quantity, either due to loading from +// a file or through a process which produces an unpredictable +// number. +// +// My most common idiom is something like: +// +// SomeStruct *arr = NULL; +// while (something) +// { +// SomeStruct new_one; +// new_one.whatever = whatever; +// new_one.whatup = whatup; +// new_one.foobar = barfoo; +// sb_push(arr, new_one); +// } +// +// and various closely-related factorings of that. For example, +// you might have several functions to create/init new SomeStructs, +// and if you use the above idiom, you might prefer to make them +// return structs rather than take non-const-pointers-to-structs, +// so you can do things like: +// +// SomeStruct *arr = NULL; +// while (something) +// { +// if (case_A) { +// sb_push(arr, some_func1()); +// } else if (case_B) { +// sb_push(arr, some_func2()); +// } else { +// sb_push(arr, some_func3()); +// } +// } +// +// Note that the above relies on the fact that sb_push doesn't +// evaluate its second argument more than once. The macros do +// evaluate the *array* argument multiple times, and numeric +// arguments may be evaluated multiple times, but you can rely +// on the second argument of sb_push being evaluated only once. +// +// Of course, you don't have to store bare objects in the array; +// if you need the objects to have stable pointers, store an array +// of pointers instead: +// +// SomeStruct **arr = NULL; +// while (something) +// { +// SomeStruct *new_one = malloc(sizeof(*new_one)); +// new_one->whatever = whatever; +// new_one->whatup = whatup; +// new_one->foobar = barfoo; +// sb_push(arr, new_one); +// } +// +// How it works: +// +// A long-standing tradition in things like malloc implementations +// is to store extra data before the beginning of the block returned +// to the user. The stretchy buffer implementation here uses the +// same trick; the current-count and current-allocation-size are +// stored before the beginning of the array returned to the user. +// (This means you can't directly free() the pointer, because the +// allocated pointer is different from the type-safe pointer provided +// to the user.) +// +// The details are trivial and implementation is straightforward; +// the main trick is in realizing in the first place that it's +// possible to do this in a generic, type-safe way in C. +// +// Contributors: +// +// Timothy Wright (github:ZenToad) +// +// LICENSE +// +// See end of file for license information. + +#ifndef STB_STRETCHY_BUFFER_H_INCLUDED +#define STB_STRETCHY_BUFFER_H_INCLUDED + +#ifndef NO_STRETCHY_BUFFER_SHORT_NAMES +#define sb_free stb_sb_free +#define sb_push stb_sb_push +#define sb_count stb_sb_count +#define sb_add stb_sb_add +#define sb_last stb_sb_last +#endif + +#define stb_sb_free(a) ((a) ? free(stb__sbraw(a)),0 : 0) +#define stb_sb_push(a,v) (stb__sbmaybegrow(a,1), (a)[stb__sbn(a)++] = (v)) +#define stb_sb_count(a) ((a) ? stb__sbn(a) : 0) +#define stb_sb_add(a,n) (stb__sbmaybegrow(a,n), stb__sbn(a)+=(n), &(a)[stb__sbn(a)-(n)]) +#define stb_sb_last(a) ((a)[stb__sbn(a)-1]) + +#define stb__sbraw(a) ((int *) (void *) (a) - 2) +#define stb__sbm(a) stb__sbraw(a)[0] +#define stb__sbn(a) stb__sbraw(a)[1] + +#define stb__sbneedgrow(a,n) ((a)==0 || stb__sbn(a)+(n) >= stb__sbm(a)) +#define stb__sbmaybegrow(a,n) (stb__sbneedgrow(a,(n)) ? stb__sbgrow(a,n) : 0) +#define stb__sbgrow(a,n) (*((void **)&(a)) = stb__sbgrowf((a), (n), sizeof(*(a)))) + +#include + +static void * stb__sbgrowf(void *arr, int increment, int itemsize) +{ + int dbl_cur = arr ? 2*stb__sbm(arr) : 0; + int min_needed = stb_sb_count(arr) + increment; + int m = dbl_cur > min_needed ? dbl_cur : min_needed; + int *p = (int *) realloc(arr ? stb__sbraw(arr) : 0, itemsize * m + sizeof(int)*2); + if (p) { + if (!arr) + p[1] = 0; + p[0] = m; + return p+2; + } else { + #ifdef STRETCHY_BUFFER_OUT_OF_MEMORY + STRETCHY_BUFFER_OUT_OF_MEMORY ; + #endif + return (void *) (2*sizeof(int)); // try to force a NULL pointer exception later + } +} +#endif // STB_STRETCHY_BUFFER_H_INCLUDED + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/deprecated/stretchy_buffer.txt b/source/engine/thirdparty/stb/deprecated/stretchy_buffer.txt new file mode 100644 index 0000000..dcd747e --- /dev/null +++ b/source/engine/thirdparty/stb/deprecated/stretchy_buffer.txt @@ -0,0 +1,28 @@ +// stretchy buffer // init: NULL // free: sbfree() // push_back: sbpush() // size: sbcount() // +#define sbfree(a) ((a) ? free(stb__sbraw(a)),0 : 0) +#define sbpush(a,v) (stb__sbmaybegrow(a,1), (a)[stb__sbn(a)++] = (v)) +#define sbcount(a) ((a) ? stb__sbn(a) : 0) +#define sbadd(a,n) (stb__sbmaybegrow(a,n), stb__sbn(a)+=(n), &(a)[stb__sbn(a)-(n)]) +#define sblast(a) ((a)[stb__sbn(a)-1]) + +#include +#define stb__sbraw(a) ((int *) (a) - 2) +#define stb__sbm(a) stb__sbraw(a)[0] +#define stb__sbn(a) stb__sbraw(a)[1] + +#define stb__sbneedgrow(a,n) ((a)==0 || stb__sbn(a)+n >= stb__sbm(a)) +#define stb__sbmaybegrow(a,n) (stb__sbneedgrow(a,(n)) ? stb__sbgrow(a,n) : 0) +#define stb__sbgrow(a,n) stb__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +static void stb__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stb__sbm(*arr)+increment : increment+1; + void *p = realloc(*arr ? stb__sbraw(*arr) : 0, itemsize * m + sizeof(int)*2); + assert(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stb__sbm(*arr) = m; + } +} + diff --git a/source/engine/thirdparty/stb/docs/other_libs.md b/source/engine/thirdparty/stb/docs/other_libs.md new file mode 100644 index 0000000..62f379c --- /dev/null +++ b/source/engine/thirdparty/stb/docs/other_libs.md @@ -0,0 +1 @@ +Moved to https://github.com/nothings/single_file_libs \ No newline at end of file diff --git a/source/engine/thirdparty/stb/docs/stb_howto.txt b/source/engine/thirdparty/stb/docs/stb_howto.txt new file mode 100644 index 0000000..a969b54 --- /dev/null +++ b/source/engine/thirdparty/stb/docs/stb_howto.txt @@ -0,0 +1,185 @@ +Lessons learned about how to make a header-file library +V1.0 +September 2013 Sean Barrett + +Things to do in an stb-style header-file library, +and rationales: + + +1. #define LIBRARYNAME_IMPLEMENTATION + +Use a symbol like the above to control creating +the implementation. (I used a far-less-clear name +in my first header-file library; it became +clear that was a mistake once I had multiple +libraries.) + +Include a "header-file" section with header-file +guards and declarations for all the functions, +but only guard the implementation with LIBRARYNAME_IMPLEMENTATION, +not the header-file guard. That way, if client's +header file X includes your header file for +declarations, they can still include header file X +in the source file that creates the implementation; +if you guard the implementation too, then the first +include (before the #define) creates the declarations, +and the second one (after the #define) does nothing. + + +2. AVOID DEPENDENCIES + +Don't rely on anything other than the C standard libraries. + +(If you're creating a library specifically to leverage/wrap +some other library, then obviously you can rely on that +library. But if that library is public domain, you might +be better off directly embedding the source, to reduce +dependencies for your clients. But of course now you have +to update whenever that library updates.) + +If you use stdlib, consider wrapping all stdlib calls in +macros, and then conditionally define those macros to the +stdlib function, allowing the user to replace them. + +For functions with side effects, like memory allocations, +consider letting the user pass in a context and pass +that in to the macros. (The stdlib versions will ignore +the parameter.) Otherwise, users may have to use global +or thread-local variables to achieve the same effect. + + +3. AVOID MALLOC + +You can't always do this, but when you can, embedded developers +will appreciate it. I almost never bother avoiding, as it's +too much work (and in some cases is pretty infeasible; +see http://nothings.org/gamedev/font_rendering_malloc.txt ). +But it's definitely something one of the things I've gotten +the most pushback on from potential users. + + +4. ALLOW STATIC IMPLEMENTATION + +Have a #define which makes function declarations and +function definitions static. This makes the implementation +private to the source file that creates it. This allows +people to use your library multiple times in their project +without collision. (This is only necessary if your library +has configuration macros or global state, or if your +library has multiple versions that are not backwards +compatible. I've run into both of those cases.) + + +5. MAKE ACCESSIBLE FROM C + +Making your code accessible from C instead of C++ (i.e. +either coding in C, or using extern "C") makes it more +straightforward to be used in C and in other languages, +which often only have support for C bindings, not C++. +(One of the earliest results I found in googling for +stb_image was a Haskell wrapper.) Otherwise, people +have to wrap it in another set of function calls, and +the whole point here is to make it convenient for people +to use, isn't it? (See below.) + +I prefer to code entirely in C, so the source file that +instantiates the implementation can be C itself, for +those crazy people out there who are programming in C. +But it's probably not a big hardship for a C programmer +to create a single C++ source file to instantiate your +library. + + +6. NAMESPACE PRIVATE FUNCTIONS + +Try to avoid having names in your source code that +will cause conflicts with identical names in client +code. You can do this either by namespacing in C++, +or prefixing with your library name in C. + +In C, generally, I use the same prefix for API +functions and private symbols, such as "stbtt_" +for stb_truetype; but private functions (and +static globals) use a second underscore as +in "stbtt__" to further minimize the chance of +additional collisions in the unlikely but not +impossible event that users write wrapper +functions that have names of the form "stbtt_". +(Consider the user that has used "stbtt_foo" +*successfully*, and then upgrades to a new +version of your library which has a new private +function named either "stbtt_foo" or "stbtt__foo".) + +Note that the double-underscore is reserved for +use by the compiler, but (1) there is nothing +reserved for "middleware", i.e. libraries +desiring to avoid conflicts with user symbols +have no other good options, and (2) in practice +no compilers use double-underscore in the middle +rather than the beginning/end. (Unfortunately, +there is at least one videogame-console compiler that +will warn about double-underscores by default.) + + +7. EASY-TO-COMPLY LICENSE + +I make my libraries public domain. You don't have to. +But my goal in releasing stb-style libraries is to +reduce friction for potential users as much as +possible. That means: + + a. easy to build (what this file is mostly about) + b. easy to invoke (which requires good API design) + c. easy to deploy (which is about licensing) + +I choose to place all my libraries in the public +domain, abjuring copyright, rather than license +the libraries. This has some benefits and some +drawbacks. + +Any license which is "viral" to modifications +causes worries for lawyers, even if their programmers +aren't modifying it. + +Any license which requires crediting in documentation +adds friction which can add up. Valve used to have +a page with a list of all of these on their web site, +and it was insane, and obviously nobody ever looked +at it so why would you care whether your credit appeared +there? + +Permissive licenses like zlib and BSD license are +perfectly reasonable, but they are very wordy and +have only two benefits over public domain: legally-mandated +attribution and liability-control. I do not believe these +are worth the excessive verbosity and user-unfriendliness +these licenses induce, especially in the single-file +case where those licenses tend to be at the top of +the file, the first thing you see. (To the specific +points, I have had no trouble receiving attribution +for my libraries; liability in the face of no explicit +disclaimer of liability is an open question.) + +However, public domain has frictions of its own, because +public domain declarations aren't necessary recognized +in the USA and some other locations. For that reason, +I recommend a declaration along these lines: + +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. + +I typically place this declaration at the end of the initial +comment block of the file and just say 'public domain' +at the top. + +I have had people say they couldn't use one of my +libraries because it was only "public domain" and didn't +have the additional fallback clause, who asked if +I could dual-license it under a traditional license. + +My answer: they can create a derivative work by +modifying one character, and then license that however +they like. (Indeed, *adding* the zlib or BSD license +would be such a modification!) Unfortunately, their +lawyers reportedly didn't like that answer. :( diff --git a/source/engine/thirdparty/stb/docs/stb_voxel_render_interview.md b/source/engine/thirdparty/stb/docs/stb_voxel_render_interview.md new file mode 100644 index 0000000..7071466 --- /dev/null +++ b/source/engine/thirdparty/stb/docs/stb_voxel_render_interview.md @@ -0,0 +1,173 @@ +# An interview with STB about stb_voxel_render.h + +**Q:** +I suppose you really like Minecraft? + +**A:** +Not really. I mean, I do own it and play it some, and +I do watch YouTube videos of other people playing it +once in a while, but I'm not saying it's that great. + +But I do love voxels. I've been playing with voxel rendering +since the mid-late 90's when we were still doing software +rendering and thinking maybe polygons weren't the answer. +Once GPUs came along that kind of died off, at least until +Minecraft brought it back to attention. + +**Q:** +Do you expect people will make a lot of Minecraft clones +with this? + +**A:** +I hope not! + +For one thing, it's a terrible idea for the +developer. Remember before Minecraft was on the Xbox 360, +there were a ton of "indie" clones (some maybe making +decent money even), but then the real Minecraft came out +and just crushed them (as far as I know). It's just not +something you really want to compete with. + +The reason I made this library is because I'd like +to see more games with Minecraft's *art style*, not +necessary its *gameplay*. + +I can understand the urge to clone the gameplay. When +you have a world made of voxels/blocks, there are a +few things that become incredibly easy to do that would +otherwise be very hard (at least for an indie) to do in 3D. +One thing is that procedural generation becomes much easier. +Another is that destructible environments are easy. Another +is that you have a world where your average user can build +stuff that they find satisfactory. + +Minecraft is at a sort of local maximum, a sweet spot, where +it leverages all of those easy-to-dos. And so I'm sure it's +hard to look at the space of 'games using voxels' and move +away from that local maximum, to give up some of that. +But I think that's what people should do. + +**Q:** +So what else can people do with stb_voxel_render? + +**A:** +All of those benefits I mentioned above are still valid even +if you stay away from the sweet spot. You can make a 3D roguelike +without player-creation/destruction that uses procedural generation. +You could make a shooter with pre-designed maps but destructible +environments. + +And I'm sure there are other possible benefits to using voxels/blocks. +Hopefully this will make it easier for people to explore the space. + +The library has a pretty wide range of features to allow +people to come up with some distinctive looks. For example, +the art style of Continue?9876543210 was one of the inspirations +for trying to make the multitexturing capabilities flexible. +I'm terrible at art, so this isn't really something I can +come up with myself, but I tried to put in flexible +technology that could be used multiple ways. + +One thing I did intentionally was try to make it possible to +make nicer looking ground terrain, using the half-height +slopes and "weird slopes". There are Minecraft mods with +drivable cars and they just go up these blocky slopes and, +like, what? So I wanted you to be able to make smoother +terrain, either just for the look, or for vehicles etc. +Also, you can spatially cross-fade between two ground textures for +that classic bad dirt/grass transition that has shipped +in plenty of professional games. Of course, you could +just use a separate non-voxel ground renderer for all of +this. But this way, you can seamlessly integrate everything +else with it. E.g. in your authoring tool (or procedural +generation) you can make smooth ground and then cut a +sharp-edged hole in it for a building's basement or whatever. + +Another thing you can do is work at a very different scale. +In Minecraft, a person is just under 2 blocks tall. In +Ace of Spades, a person is just under 3 blocks tall. Why +not 4 or 6? Well, partly because you just need a lot more +voxels; if a meter is 2 voxels in Mineraft and 4 voxels in +your game, and you draw the same number of voxels due to +hardware limits, then your game has half the view distance +of Minecraft. Since stb_voxel_render is designed to keep +the meshes small and render efficiently, you can push the +view distance out further than Minecraft--or use a similar +view distance and a higher voxel resolution. You could also +stop making infinite worlds and work at entirely different +scales; where Minecraft is 1 voxel per meter, you could +have 20 voxels per meter and make a small arena that's +50 meters wide and 5 meters tall. + +Back when the voxel game Voxatron was announced, the weekend +after the trailer came out I wrote my own little GPU-accelerated +version of the engine and thought that was pretty cool. I've +been tempted many times to extract that and release it +as a library, but +I don't want to steal Voxatron's thunder so I've avoided +it. You could use this engine to do the same kind of thing, +although it won't be as efficient as an engine dedicated to +that style of thing would be. + +**Q:** +What one thing would you really like to see somebody do? + +**A:** +Before Unity, 3D has seemed deeply problematic in the indie +space. Software like GameMaker has tried to support 3D but +it seems like little of note has been done with it. + +Minecraft has shown that people can build worlds with the +Minecraft toolset far more easily than we've ever seen from those +other tools. Obviously people have done great things with +Unity, but those people are much closer to professional +developers; typically they still need real 3D modelling +and all of that stuff. + +So what I'd really like to see is someone build some kind +of voxel-game-construction-set. Start with stb_voxel_render, +maybe expose all the flexibility of stb_voxel_render (so +people can do different things). Thrown in lua or something +else for scripting, make some kind of editor that feels +at least as good as Minecraft and Infinifactory, and see +where that gets you. + +**Q:** +Why'd you make this library? + +**A:** +Mainly as a way of releasing this technology I've been working +on since 2011 and seemed unlikely to ever ship myself. In 2011 +I was playing the voxel shooter Ace of Spades. One of the maps +that we played on was a partial port of Broville (which is the +first Minecraft map in stb_voxel_render release trailer). I'd +made a bunch of procedural level generators for the game, and +I started trying to make a city generator inspired by Broville. + +But I realized it would be a lot of work, and of very little +value (most of my maps didn't get much play because people +preferred to play on maps where they could charge straight +at the enemies and shoot them as fast as possible). So I +wrote my own voxel engine and started working on a procedural +city game. But I got bogged down after I finally got the road +generator working and never got anywhere with building +generation or gameplay. + +stb_voxel_render is actually a complete rewrite from scratch, +but it's based a lot on what I learned from that previous work. + +**Q:** +About the release video... how long did that take to edit? + +**A:** +About seven or eight hours. I had the first version done in +maybe six or seven hours, but then I realized I'd left out +one clip, and when I went back to add it I also gussied up +a couple other moments in the video. But there was something +basically identical to it that was done in around six. + +**Q:** +Ok, that's it. Thanks, me. + +**A:** +Thanks *me!* diff --git a/source/engine/thirdparty/stb/docs/why_public_domain.md b/source/engine/thirdparty/stb/docs/why_public_domain.md new file mode 100644 index 0000000..fd3f887 --- /dev/null +++ b/source/engine/thirdparty/stb/docs/why_public_domain.md @@ -0,0 +1,117 @@ +My collected rationales for placing these libraries +in the public domain: + +1. Public domain vs. viral licenses + + Why is this library public domain? + Because more people will use it. Because it's not viral, people are + not obligated to give back, so you could argue that it hurts the + development of it, and then because it doesn't develop as well it's + not as good, and then because it's not as good, in the long run + maybe fewer people will use it. I have total respect for that + opinion, but I just don't believe it myself for most software. + +2. Public domain vs. attribution-required licenses + + The primary difference between public domain and, say, a Creative Commons + commercial / non-share-alike / attribution license is solely the + requirement for attribution. (Similarly the BSD license and such.) + While I would *appreciate* acknowledgement and attribution, I believe + that it is foolish to place a legal encumberment (i.e. a license) on + the software *solely* to get attribution. + + In other words, I'm arguing that PD is superior to the BSD license and + the Creative Commons 'Attribution' license. If the license offers + anything besides attribution -- as does, e.g., CC NonCommercial-ShareAlike, + or the GPL -- that's a separate discussion. + +3. Other aspects of BSD-style licenses besides attribution + + Permissive licenses like zlib and BSD license are perfectly reasonable + in their requirements, but they are very wordy and + have only two benefits over public domain: legally-mandated + attribution and liability-control. I do not believe these + are worth the excessive verbosity and user-unfriendliness + these licenses induce, especially in the single-file + case where those licenses tend to be at the top of + the file, the first thing you see. + + To the specific points, I have had no trouble receiving + attribution for my libraries; liability in the face of + no explicit disclaimer of liability is an open question, + but one I have a lot of difficulty imagining there being + any actual doubt about in court. Sometimes I explicitly + note in my libraries that I make no guarantees about them + being fit for purpose, but it's pretty absurd to do this; + as a whole, it comes across as "here is a library to decode + vorbis audio files, but it may not actually work and if + you have problems it's not my fault, but also please + report bugs so I can fix them"--so dumb! + +4. full discussion from stb_howto.txt on what YOU should do for YOUR libs + +``` +EASY-TO-COMPLY LICENSE + +I make my libraries public domain. You don't have to. +But my goal in releasing stb-style libraries is to +reduce friction for potential users as much as +possible. That means: + + a. easy to build (what this file is mostly about) + b. easy to invoke (which requires good API design) + c. easy to deploy (which is about licensing) + +I choose to place all my libraries in the public +domain, abjuring copyright, rather than license +the libraries. This has some benefits and some +drawbacks. + +Any license which is "viral" to modifications +causes worries for lawyers, even if their programmers +aren't modifying it. + +Any license which requires crediting in documentation +adds friction which can add up. Valve has a huge list +(http://nothings.org/remote/ThirdPartyLegalNotices_steam_2019.html) +of all of these included in each game they ship, +and it's insane, and obviously nobody ever looks +at it so why would you care whether your credit +appeared there? + +Permissive licenses like zlib and BSD license are +perfectly reasonable, but they are very wordy and +have only two benefits over public domain: legally-mandated +attribution and liability-control. I do not believe these +are worth the excessive verbosity and user-unfriendliness +these licenses induce, especially in the single-file +case where those licenses tend to be at the top of +the file, the first thing you see. (To the specific +points, I have had no trouble receiving attribution +for my libraries; liability in the face of no explicit +disclaimer of liability is an open question.) + +However, public domain has frictions of its own, because +public domain declarations aren't necessary recognized +in the USA and some other locations. For that reason, +I recommend a declaration along these lines: + +// This software is dual-licensed to the public domain and under the following +// license: you are granted a perpetual, irrevocable license to copy, modify, +// publish, and distribute this file as you see fit. + +I typically place this declaration at the end of the initial +comment block of the file and just say 'public domain' +at the top. + +I have had people say they couldn't use one of my +libraries because it was only "public domain" and didn't +have the additional fallback clause, who asked if +I could dual-license it under a traditional license. + +My answer: they can create a derivative work by +modifying one character, and then license that however +they like. (Indeed, *adding* the zlib or BSD license +would be such a modification!) Unfortunately, their +lawyers reportedly didn't like that answer. :( +``` diff --git a/source/engine/thirdparty/stb/stb_c_lexer.h b/source/engine/thirdparty/stb/stb_c_lexer.h new file mode 100644 index 0000000..bf89dca --- /dev/null +++ b/source/engine/thirdparty/stb/stb_c_lexer.h @@ -0,0 +1,940 @@ +// stb_c_lexer.h - v0.12 - public domain Sean Barrett 2013 +// lexer for making little C-like languages with recursive-descent parsers +// +// This file provides both the interface and the implementation. +// To instantiate the implementation, +// #define STB_C_LEXER_IMPLEMENTATION +// in *ONE* source file, before #including this file. +// +// The default configuration is fairly close to a C lexer, although +// suffixes on integer constants are not handled (you can override this). +// +// History: +// 0.12 fix compilation bug for NUL support; better support separate inclusion +// 0.11 fix clang static analysis warning +// 0.10 fix warnings +// 0.09 hex floats, no-stdlib fixes +// 0.08 fix bad pointer comparison +// 0.07 fix mishandling of hexadecimal constants parsed by strtol +// 0.06 fix missing next character after ending quote mark (Andreas Fredriksson) +// 0.05 refixed get_location because github version had lost the fix +// 0.04 fix octal parsing bug +// 0.03 added STB_C_LEX_DISCARD_PREPROCESSOR option +// refactor API to simplify (only one struct instead of two) +// change literal enum names to have 'lit' at the end +// 0.02 first public release +// +// Status: +// - haven't tested compiling as C++ +// - haven't tested the float parsing path +// - haven't tested the non-default-config paths (e.g. non-stdlib) +// - only tested default-config paths by eyeballing output of self-parse +// +// - haven't implemented multiline strings +// - haven't implemented octal/hex character constants +// - haven't implemented support for unicode CLEX_char +// - need to expand error reporting so you don't just get "CLEX_parse_error" +// +// Contributors: +// Arpad Goretity (bugfix) +// Alan Hickman (hex floats) +// +// LICENSE +// +// See end of file for license information. + +#ifdef STB_C_LEXER_IMPLEMENTATION +#ifndef STB_C_LEXER_DEFINITIONS +// to change the default parsing rules, copy the following lines +// into your C/C++ file *before* including this, and then replace +// the Y's with N's for the ones you don't want. This needs to be +// set to the same values for every place in your program where +// stb_c_lexer.h is included. +// --BEGIN-- + +#if defined(Y) || defined(N) +#error "Can only use stb_c_lexer in contexts where the preprocessor symbols 'Y' and 'N' are not defined" +#endif + +#define STB_C_LEX_C_DECIMAL_INTS Y // "0|[1-9][0-9]*" CLEX_intlit +#define STB_C_LEX_C_HEX_INTS Y // "0x[0-9a-fA-F]+" CLEX_intlit +#define STB_C_LEX_C_OCTAL_INTS Y // "[0-7]+" CLEX_intlit +#define STB_C_LEX_C_DECIMAL_FLOATS Y // "[0-9]*(.[0-9]*([eE][-+]?[0-9]+)?) CLEX_floatlit +#define STB_C_LEX_C99_HEX_FLOATS N // "0x{hex}+(.{hex}*)?[pP][-+]?{hex}+ CLEX_floatlit +#define STB_C_LEX_C_IDENTIFIERS Y // "[_a-zA-Z][_a-zA-Z0-9]*" CLEX_id +#define STB_C_LEX_C_DQ_STRINGS Y // double-quote-delimited strings with escapes CLEX_dqstring +#define STB_C_LEX_C_SQ_STRINGS N // single-quote-delimited strings with escapes CLEX_ssstring +#define STB_C_LEX_C_CHARS Y // single-quote-delimited character with escape CLEX_charlits +#define STB_C_LEX_C_COMMENTS Y // "/* comment */" +#define STB_C_LEX_CPP_COMMENTS Y // "// comment to end of line\n" +#define STB_C_LEX_C_COMPARISONS Y // "==" CLEX_eq "!=" CLEX_noteq "<=" CLEX_lesseq ">=" CLEX_greatereq +#define STB_C_LEX_C_LOGICAL Y // "&&" CLEX_andand "||" CLEX_oror +#define STB_C_LEX_C_SHIFTS Y // "<<" CLEX_shl ">>" CLEX_shr +#define STB_C_LEX_C_INCREMENTS Y // "++" CLEX_plusplus "--" CLEX_minusminus +#define STB_C_LEX_C_ARROW Y // "->" CLEX_arrow +#define STB_C_LEX_EQUAL_ARROW N // "=>" CLEX_eqarrow +#define STB_C_LEX_C_BITWISEEQ Y // "&=" CLEX_andeq "|=" CLEX_oreq "^=" CLEX_xoreq +#define STB_C_LEX_C_ARITHEQ Y // "+=" CLEX_pluseq "-=" CLEX_minuseq + // "*=" CLEX_muleq "/=" CLEX_diveq "%=" CLEX_modeq + // if both STB_C_LEX_SHIFTS & STB_C_LEX_ARITHEQ: + // "<<=" CLEX_shleq ">>=" CLEX_shreq + +#define STB_C_LEX_PARSE_SUFFIXES N // letters after numbers are parsed as part of those numbers, and must be in suffix list below +#define STB_C_LEX_DECIMAL_SUFFIXES "" // decimal integer suffixes e.g. "uUlL" -- these are returned as-is in string storage +#define STB_C_LEX_HEX_SUFFIXES "" // e.g. "uUlL" +#define STB_C_LEX_OCTAL_SUFFIXES "" // e.g. "uUlL" +#define STB_C_LEX_FLOAT_SUFFIXES "" // + +#define STB_C_LEX_0_IS_EOF N // if Y, ends parsing at '\0'; if N, returns '\0' as token +#define STB_C_LEX_INTEGERS_AS_DOUBLES N // parses integers as doubles so they can be larger than 'int', but only if STB_C_LEX_STDLIB==N +#define STB_C_LEX_MULTILINE_DSTRINGS N // allow newlines in double-quoted strings +#define STB_C_LEX_MULTILINE_SSTRINGS N // allow newlines in single-quoted strings +#define STB_C_LEX_USE_STDLIB Y // use strtod,strtol for parsing #s; otherwise inaccurate hack +#define STB_C_LEX_DOLLAR_IDENTIFIER Y // allow $ as an identifier character +#define STB_C_LEX_FLOAT_NO_DECIMAL Y // allow floats that have no decimal point if they have an exponent + +#define STB_C_LEX_DEFINE_ALL_TOKEN_NAMES N // if Y, all CLEX_ token names are defined, even if never returned + // leaving it as N should help you catch config bugs + +#define STB_C_LEX_DISCARD_PREPROCESSOR Y // discard C-preprocessor directives (e.g. after prepocess + // still have #line, #pragma, etc) + +//#define STB_C_LEX_ISWHITE(str) ... // return length in bytes of whitespace characters if first char is whitespace + +#define STB_C_LEXER_DEFINITIONS // This line prevents the header file from replacing your definitions +// --END-- +#endif +#endif + +#ifndef INCLUDE_STB_C_LEXER_H +#define INCLUDE_STB_C_LEXER_H + +typedef struct +{ + // lexer variables + char *input_stream; + char *eof; + char *parse_point; + char *string_storage; + int string_storage_len; + + // lexer parse location for error messages + char *where_firstchar; + char *where_lastchar; + + // lexer token variables + long token; + double real_number; + long int_number; + char *string; + int string_len; +} stb_lexer; + +typedef struct +{ + int line_number; + int line_offset; +} stb_lex_location; + +#ifdef __cplusplus +extern "C" { +#endif + +extern void stb_c_lexer_init(stb_lexer *lexer, const char *input_stream, const char *input_stream_end, char *string_store, int store_length); +// this function initialize the 'lexer' structure +// Input: +// - input_stream points to the file to parse, loaded into memory +// - input_stream_end points to the end of the file, or NULL if you use 0-for-EOF +// - string_store is storage the lexer can use for storing parsed strings and identifiers +// - store_length is the length of that storage + +extern int stb_c_lexer_get_token(stb_lexer *lexer); +// this function returns non-zero if a token is parsed, or 0 if at EOF +// Output: +// - lexer->token is the token ID, which is unicode code point for a single-char token, < 0 for a multichar or eof or error +// - lexer->real_number is a double constant value for CLEX_floatlit, or CLEX_intlit if STB_C_LEX_INTEGERS_AS_DOUBLES +// - lexer->int_number is an integer constant for CLEX_intlit if !STB_C_LEX_INTEGERS_AS_DOUBLES, or character for CLEX_charlit +// - lexer->string is a 0-terminated string for CLEX_dqstring or CLEX_sqstring or CLEX_identifier +// - lexer->string_len is the byte length of lexer->string + +extern void stb_c_lexer_get_location(const stb_lexer *lexer, const char *where, stb_lex_location *loc); +// this inefficient function returns the line number and character offset of a +// given location in the file as returned by stb_lex_token. Because it's inefficient, +// you should only call it for errors, not for every token. +// For error messages of invalid tokens, you typically want the location of the start +// of the token (which caused the token to be invalid). For bugs involving legit +// tokens, you can report the first or the range. +// Output: +// - loc->line_number is the line number in the file, counting from 1, of the location +// - loc->line_offset is the char-offset in the line, counting from 0, of the location + + +#ifdef __cplusplus +} +#endif + +enum +{ + CLEX_eof = 256, + CLEX_parse_error, + CLEX_intlit , + CLEX_floatlit , + CLEX_id , + CLEX_dqstring , + CLEX_sqstring , + CLEX_charlit , + CLEX_eq , + CLEX_noteq , + CLEX_lesseq , + CLEX_greatereq , + CLEX_andand , + CLEX_oror , + CLEX_shl , + CLEX_shr , + CLEX_plusplus , + CLEX_minusminus , + CLEX_pluseq , + CLEX_minuseq , + CLEX_muleq , + CLEX_diveq , + CLEX_modeq , + CLEX_andeq , + CLEX_oreq , + CLEX_xoreq , + CLEX_arrow , + CLEX_eqarrow , + CLEX_shleq, CLEX_shreq, + + CLEX_first_unused_token + +}; +#endif // INCLUDE_STB_C_LEXER_H + +#ifdef STB_C_LEXER_IMPLEMENTATION + +// Hacky definitions so we can easily #if on them +#define Y(x) 1 +#define N(x) 0 + +#if STB_C_LEX_INTEGERS_AS_DOUBLES(x) +typedef double stb__clex_int; +#define intfield real_number +#define STB__clex_int_as_double +#else +typedef long stb__clex_int; +#define intfield int_number +#endif + +// Convert these config options to simple conditional #defines so we can more +// easily test them once we've change the meaning of Y/N + +#if STB_C_LEX_PARSE_SUFFIXES(x) +#define STB__clex_parse_suffixes +#endif + +#if STB_C_LEX_C99_HEX_FLOATS(x) +#define STB__clex_hex_floats +#endif + +#if STB_C_LEX_C_HEX_INTS(x) +#define STB__clex_hex_ints +#endif + +#if STB_C_LEX_C_DECIMAL_INTS(x) +#define STB__clex_decimal_ints +#endif + +#if STB_C_LEX_C_OCTAL_INTS(x) +#define STB__clex_octal_ints +#endif + +#if STB_C_LEX_C_DECIMAL_FLOATS(x) +#define STB__clex_decimal_floats +#endif + +#if STB_C_LEX_DISCARD_PREPROCESSOR(x) +#define STB__clex_discard_preprocessor +#endif + +#if STB_C_LEX_USE_STDLIB(x) && (!defined(STB__clex_hex_floats) || __STDC_VERSION__ >= 199901L) +#define STB__CLEX_use_stdlib +#include +#endif + +// Now for the rest of the file we'll use the basic definition where +// where Y expands to its contents and N expands to nothing +#undef Y +#define Y(a) a +#undef N +#define N(a) + +// API function +void stb_c_lexer_init(stb_lexer *lexer, const char *input_stream, const char *input_stream_end, char *string_store, int store_length) +{ + lexer->input_stream = (char *) input_stream; + lexer->eof = (char *) input_stream_end; + lexer->parse_point = (char *) input_stream; + lexer->string_storage = string_store; + lexer->string_storage_len = store_length; +} + +// API function +void stb_c_lexer_get_location(const stb_lexer *lexer, const char *where, stb_lex_location *loc) +{ + char *p = lexer->input_stream; + int line_number = 1; + int char_offset = 0; + while (*p && p < where) { + if (*p == '\n' || *p == '\r') { + p += (p[0]+p[1] == '\r'+'\n' ? 2 : 1); // skip newline + line_number += 1; + char_offset = 0; + } else { + ++p; + ++char_offset; + } + } + loc->line_number = line_number; + loc->line_offset = char_offset; +} + +// main helper function for returning a parsed token +static int stb__clex_token(stb_lexer *lexer, int token, char *start, char *end) +{ + lexer->token = token; + lexer->where_firstchar = start; + lexer->where_lastchar = end; + lexer->parse_point = end+1; + return 1; +} + +// helper function for returning eof +static int stb__clex_eof(stb_lexer *lexer) +{ + lexer->token = CLEX_eof; + return 0; +} + +static int stb__clex_iswhite(int x) +{ + return x == ' ' || x == '\t' || x == '\r' || x == '\n' || x == '\f'; +} + +static const char *stb__strchr(const char *str, int ch) +{ + for (; *str; ++str) + if (*str == ch) + return str; + return 0; +} + +// parse suffixes at the end of a number +static int stb__clex_parse_suffixes(stb_lexer *lexer, long tokenid, char *start, char *cur, const char *suffixes) +{ + #ifdef STB__clex_parse_suffixes + lexer->string = lexer->string_storage; + lexer->string_len = 0; + + while ((*cur >= 'a' && *cur <= 'z') || (*cur >= 'A' && *cur <= 'Z')) { + if (stb__strchr(suffixes, *cur) == 0) + return stb__clex_token(lexer, CLEX_parse_error, start, cur); + if (lexer->string_len+1 >= lexer->string_storage_len) + return stb__clex_token(lexer, CLEX_parse_error, start, cur); + lexer->string[lexer->string_len++] = *cur++; + } + #else + suffixes = suffixes; // attempt to suppress warnings + #endif + return stb__clex_token(lexer, tokenid, start, cur-1); +} + +#ifndef STB__CLEX_use_stdlib +static double stb__clex_pow(double base, unsigned int exponent) +{ + double value=1; + for ( ; exponent; exponent >>= 1) { + if (exponent & 1) + value *= base; + base *= base; + } + return value; +} + +static double stb__clex_parse_float(char *p, char **q) +{ + char *s = p; + double value=0; + int base=10; + int exponent=0; + +#ifdef STB__clex_hex_floats + if (*p == '0') { + if (p[1] == 'x' || p[1] == 'X') { + base=16; + p += 2; + } + } +#endif + + for (;;) { + if (*p >= '0' && *p <= '9') + value = value*base + (*p++ - '0'); +#ifdef STB__clex_hex_floats + else if (base == 16 && *p >= 'a' && *p <= 'f') + value = value*base + 10 + (*p++ - 'a'); + else if (base == 16 && *p >= 'A' && *p <= 'F') + value = value*base + 10 + (*p++ - 'A'); +#endif + else + break; + } + + if (*p == '.') { + double pow, addend = 0; + ++p; + for (pow=1; ; pow*=base) { + if (*p >= '0' && *p <= '9') + addend = addend*base + (*p++ - '0'); +#ifdef STB__clex_hex_floats + else if (base == 16 && *p >= 'a' && *p <= 'f') + addend = addend*base + 10 + (*p++ - 'a'); + else if (base == 16 && *p >= 'A' && *p <= 'F') + addend = addend*base + 10 + (*p++ - 'A'); +#endif + else + break; + } + value += addend / pow; + } +#ifdef STB__clex_hex_floats + if (base == 16) { + // exponent required for hex float literal + if (*p != 'p' && *p != 'P') { + *q = s; + return 0; + } + exponent = 1; + } else +#endif + exponent = (*p == 'e' || *p == 'E'); + + if (exponent) { + int sign = p[1] == '-'; + unsigned int exponent=0; + double power=1; + ++p; + if (*p == '-' || *p == '+') + ++p; + while (*p >= '0' && *p <= '9') + exponent = exponent*10 + (*p++ - '0'); + +#ifdef STB__clex_hex_floats + if (base == 16) + power = stb__clex_pow(2, exponent); + else +#endif + power = stb__clex_pow(10, exponent); + if (sign) + value /= power; + else + value *= power; + } + *q = p; + return value; +} +#endif + +static int stb__clex_parse_char(char *p, char **q) +{ + if (*p == '\\') { + *q = p+2; // tentatively guess we'll parse two characters + switch(p[1]) { + case '\\': return '\\'; + case '\'': return '\''; + case '"': return '"'; + case 't': return '\t'; + case 'f': return '\f'; + case 'n': return '\n'; + case 'r': return '\r'; + case '0': return '\0'; // @TODO ocatal constants + case 'x': case 'X': return -1; // @TODO hex constants + case 'u': return -1; // @TODO unicode constants + } + } + *q = p+1; + return (unsigned char) *p; +} + +static int stb__clex_parse_string(stb_lexer *lexer, char *p, int type) +{ + char *start = p; + char delim = *p++; // grab the " or ' for later matching + char *out = lexer->string_storage; + char *outend = lexer->string_storage + lexer->string_storage_len; + while (*p != delim) { + int n; + if (*p == '\\') { + char *q; + n = stb__clex_parse_char(p, &q); + if (n < 0) + return stb__clex_token(lexer, CLEX_parse_error, start, q); + p = q; + } else { + // @OPTIMIZE: could speed this up by looping-while-not-backslash + n = (unsigned char) *p++; + } + if (out+1 > outend) + return stb__clex_token(lexer, CLEX_parse_error, start, p); + // @TODO expand unicode escapes to UTF8 + *out++ = (char) n; + } + *out = 0; + lexer->string = lexer->string_storage; + lexer->string_len = (int) (out - lexer->string_storage); + return stb__clex_token(lexer, type, start, p); +} + +int stb_c_lexer_get_token(stb_lexer *lexer) +{ + char *p = lexer->parse_point; + + // skip whitespace and comments + for (;;) { + #ifdef STB_C_LEX_ISWHITE + while (p != lexer->stream_end) { + int n; + n = STB_C_LEX_ISWHITE(p); + if (n == 0) break; + if (lexer->eof && lexer->eof - lexer->parse_point < n) + return stb__clex_token(tok, CLEX_parse_error, p,lexer->eof-1); + p += n; + } + #else + while (p != lexer->eof && stb__clex_iswhite(*p)) + ++p; + #endif + + STB_C_LEX_CPP_COMMENTS( + if (p != lexer->eof && p[0] == '/' && p[1] == '/') { + while (p != lexer->eof && *p != '\r' && *p != '\n') + ++p; + continue; + } + ) + + STB_C_LEX_C_COMMENTS( + if (p != lexer->eof && p[0] == '/' && p[1] == '*') { + char *start = p; + p += 2; + while (p != lexer->eof && (p[0] != '*' || p[1] != '/')) + ++p; + if (p == lexer->eof) + return stb__clex_token(lexer, CLEX_parse_error, start, p-1); + p += 2; + continue; + } + ) + + #ifdef STB__clex_discard_preprocessor + // @TODO this discards everything after a '#', regardless + // of where in the line the # is, rather than requiring it + // be at the start. (because this parser doesn't otherwise + // check for line breaks!) + if (p != lexer->eof && p[0] == '#') { + while (p != lexer->eof && *p != '\r' && *p != '\n') + ++p; + continue; + } + #endif + + break; + } + + if (p == lexer->eof) + return stb__clex_eof(lexer); + + switch (*p) { + default: + if ( (*p >= 'a' && *p <= 'z') + || (*p >= 'A' && *p <= 'Z') + || *p == '_' || (unsigned char) *p >= 128 // >= 128 is UTF8 char + STB_C_LEX_DOLLAR_IDENTIFIER( || *p == '$' ) ) + { + int n = 0; + lexer->string = lexer->string_storage; + lexer->string_len = n; + do { + if (n+1 >= lexer->string_storage_len) + return stb__clex_token(lexer, CLEX_parse_error, p, p+n); + lexer->string[n] = p[n]; + ++n; + } while ( + (p[n] >= 'a' && p[n] <= 'z') + || (p[n] >= 'A' && p[n] <= 'Z') + || (p[n] >= '0' && p[n] <= '9') // allow digits in middle of identifier + || p[n] == '_' || (unsigned char) p[n] >= 128 + STB_C_LEX_DOLLAR_IDENTIFIER( || p[n] == '$' ) + ); + lexer->string[n] = 0; + return stb__clex_token(lexer, CLEX_id, p, p+n-1); + } + + // check for EOF + STB_C_LEX_0_IS_EOF( + if (*p == 0) + return stb__clex_eof(lexer); + ) + + single_char: + // not an identifier, return the character as itself + return stb__clex_token(lexer, *p, p, p); + + case '+': + if (p+1 != lexer->eof) { + STB_C_LEX_C_INCREMENTS(if (p[1] == '+') return stb__clex_token(lexer, CLEX_plusplus, p,p+1);) + STB_C_LEX_C_ARITHEQ( if (p[1] == '=') return stb__clex_token(lexer, CLEX_pluseq , p,p+1);) + } + goto single_char; + case '-': + if (p+1 != lexer->eof) { + STB_C_LEX_C_INCREMENTS(if (p[1] == '-') return stb__clex_token(lexer, CLEX_minusminus, p,p+1);) + STB_C_LEX_C_ARITHEQ( if (p[1] == '=') return stb__clex_token(lexer, CLEX_minuseq , p,p+1);) + STB_C_LEX_C_ARROW( if (p[1] == '>') return stb__clex_token(lexer, CLEX_arrow , p,p+1);) + } + goto single_char; + case '&': + if (p+1 != lexer->eof) { + STB_C_LEX_C_LOGICAL( if (p[1] == '&') return stb__clex_token(lexer, CLEX_andand, p,p+1);) + STB_C_LEX_C_BITWISEEQ(if (p[1] == '=') return stb__clex_token(lexer, CLEX_andeq , p,p+1);) + } + goto single_char; + case '|': + if (p+1 != lexer->eof) { + STB_C_LEX_C_LOGICAL( if (p[1] == '|') return stb__clex_token(lexer, CLEX_oror, p,p+1);) + STB_C_LEX_C_BITWISEEQ(if (p[1] == '=') return stb__clex_token(lexer, CLEX_oreq, p,p+1);) + } + goto single_char; + case '=': + if (p+1 != lexer->eof) { + STB_C_LEX_C_COMPARISONS(if (p[1] == '=') return stb__clex_token(lexer, CLEX_eq, p,p+1);) + STB_C_LEX_EQUAL_ARROW( if (p[1] == '>') return stb__clex_token(lexer, CLEX_eqarrow, p,p+1);) + } + goto single_char; + case '!': + STB_C_LEX_C_COMPARISONS(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_noteq, p,p+1);) + goto single_char; + case '^': + STB_C_LEX_C_BITWISEEQ(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_xoreq, p,p+1)); + goto single_char; + case '%': + STB_C_LEX_C_ARITHEQ(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_modeq, p,p+1)); + goto single_char; + case '*': + STB_C_LEX_C_ARITHEQ(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_muleq, p,p+1)); + goto single_char; + case '/': + STB_C_LEX_C_ARITHEQ(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_diveq, p,p+1)); + goto single_char; + case '<': + if (p+1 != lexer->eof) { + STB_C_LEX_C_COMPARISONS(if (p[1] == '=') return stb__clex_token(lexer, CLEX_lesseq, p,p+1);) + STB_C_LEX_C_SHIFTS( if (p[1] == '<') { + STB_C_LEX_C_ARITHEQ(if (p+2 != lexer->eof && p[2] == '=') + return stb__clex_token(lexer, CLEX_shleq, p,p+2);) + return stb__clex_token(lexer, CLEX_shl, p,p+1); + } + ) + } + goto single_char; + case '>': + if (p+1 != lexer->eof) { + STB_C_LEX_C_COMPARISONS(if (p[1] == '=') return stb__clex_token(lexer, CLEX_greatereq, p,p+1);) + STB_C_LEX_C_SHIFTS( if (p[1] == '>') { + STB_C_LEX_C_ARITHEQ(if (p+2 != lexer->eof && p[2] == '=') + return stb__clex_token(lexer, CLEX_shreq, p,p+2);) + return stb__clex_token(lexer, CLEX_shr, p,p+1); + } + ) + } + goto single_char; + + case '"': + STB_C_LEX_C_DQ_STRINGS(return stb__clex_parse_string(lexer, p, CLEX_dqstring);) + goto single_char; + case '\'': + STB_C_LEX_C_SQ_STRINGS(return stb__clex_parse_string(lexer, p, CLEX_sqstring);) + STB_C_LEX_C_CHARS( + { + char *start = p; + lexer->int_number = stb__clex_parse_char(p+1, &p); + if (lexer->int_number < 0) + return stb__clex_token(lexer, CLEX_parse_error, start,start); + if (p == lexer->eof || *p != '\'') + return stb__clex_token(lexer, CLEX_parse_error, start,p); + return stb__clex_token(lexer, CLEX_charlit, start, p+1); + }) + goto single_char; + + case '0': + #if defined(STB__clex_hex_ints) || defined(STB__clex_hex_floats) + if (p+1 != lexer->eof) { + if (p[1] == 'x' || p[1] == 'X') { + char *q; + + #ifdef STB__clex_hex_floats + for (q=p+2; + q != lexer->eof && ((*q >= '0' && *q <= '9') || (*q >= 'a' && *q <= 'f') || (*q >= 'A' && *q <= 'F')); + ++q); + if (q != lexer->eof) { + if (*q == '.' STB_C_LEX_FLOAT_NO_DECIMAL(|| *q == 'p' || *q == 'P')) { + #ifdef STB__CLEX_use_stdlib + lexer->real_number = strtod((char *) p, (char**) &q); + #else + lexer->real_number = stb__clex_parse_float(p, &q); + #endif + + if (p == q) + return stb__clex_token(lexer, CLEX_parse_error, p,q); + return stb__clex_parse_suffixes(lexer, CLEX_floatlit, p,q, STB_C_LEX_FLOAT_SUFFIXES); + + } + } + #endif // STB__CLEX_hex_floats + + #ifdef STB__clex_hex_ints + #ifdef STB__CLEX_use_stdlib + lexer->int_number = strtol((char *) p, (char **) &q, 16); + #else + { + stb__clex_int n=0; + for (q=p+2; q != lexer->eof; ++q) { + if (*q >= '0' && *q <= '9') + n = n*16 + (*q - '0'); + else if (*q >= 'a' && *q <= 'f') + n = n*16 + (*q - 'a') + 10; + else if (*q >= 'A' && *q <= 'F') + n = n*16 + (*q - 'A') + 10; + else + break; + } + lexer->int_number = n; + } + #endif + if (q == p+2) + return stb__clex_token(lexer, CLEX_parse_error, p-2,p-1); + return stb__clex_parse_suffixes(lexer, CLEX_intlit, p,q, STB_C_LEX_HEX_SUFFIXES); + #endif + } + } + #endif // defined(STB__clex_hex_ints) || defined(STB__clex_hex_floats) + // can't test for octal because we might parse '0.0' as float or as '0' '.' '0', + // so have to do float first + + /* FALL THROUGH */ + case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': + + #ifdef STB__clex_decimal_floats + { + char *q = p; + while (q != lexer->eof && (*q >= '0' && *q <= '9')) + ++q; + if (q != lexer->eof) { + if (*q == '.' STB_C_LEX_FLOAT_NO_DECIMAL(|| *q == 'e' || *q == 'E')) { + #ifdef STB__CLEX_use_stdlib + lexer->real_number = strtod((char *) p, (char**) &q); + #else + lexer->real_number = stb__clex_parse_float(p, &q); + #endif + + return stb__clex_parse_suffixes(lexer, CLEX_floatlit, p,q, STB_C_LEX_FLOAT_SUFFIXES); + + } + } + } + #endif // STB__clex_decimal_floats + + #ifdef STB__clex_octal_ints + if (p[0] == '0') { + char *q = p; + #ifdef STB__CLEX_use_stdlib + lexer->int_number = strtol((char *) p, (char **) &q, 8); + #else + stb__clex_int n=0; + while (q != lexer->eof) { + if (*q >= '0' && *q <= '7') + n = n*8 + (*q - '0'); + else + break; + ++q; + } + if (q != lexer->eof && (*q == '8' || *q=='9')) + return stb__clex_token(lexer, CLEX_parse_error, p, q); + lexer->int_number = n; + #endif + return stb__clex_parse_suffixes(lexer, CLEX_intlit, p,q, STB_C_LEX_OCTAL_SUFFIXES); + } + #endif // STB__clex_octal_ints + + #ifdef STB__clex_decimal_ints + { + char *q = p; + #ifdef STB__CLEX_use_stdlib + lexer->int_number = strtol((char *) p, (char **) &q, 10); + #else + stb__clex_int n=0; + while (q != lexer->eof) { + if (*q >= '0' && *q <= '9') + n = n*10 + (*q - '0'); + else + break; + ++q; + } + lexer->int_number = n; + #endif + return stb__clex_parse_suffixes(lexer, CLEX_intlit, p,q, STB_C_LEX_OCTAL_SUFFIXES); + } + #endif // STB__clex_decimal_ints + goto single_char; + } +} +#endif // STB_C_LEXER_IMPLEMENTATION + +#ifdef STB_C_LEXER_SELF_TEST +#define _CRT_SECURE_NO_WARNINGS +#include +#include + +static void print_token(stb_lexer *lexer) +{ + switch (lexer->token) { + case CLEX_id : printf("_%s", lexer->string); break; + case CLEX_eq : printf("=="); break; + case CLEX_noteq : printf("!="); break; + case CLEX_lesseq : printf("<="); break; + case CLEX_greatereq : printf(">="); break; + case CLEX_andand : printf("&&"); break; + case CLEX_oror : printf("||"); break; + case CLEX_shl : printf("<<"); break; + case CLEX_shr : printf(">>"); break; + case CLEX_plusplus : printf("++"); break; + case CLEX_minusminus: printf("--"); break; + case CLEX_arrow : printf("->"); break; + case CLEX_andeq : printf("&="); break; + case CLEX_oreq : printf("|="); break; + case CLEX_xoreq : printf("^="); break; + case CLEX_pluseq : printf("+="); break; + case CLEX_minuseq : printf("-="); break; + case CLEX_muleq : printf("*="); break; + case CLEX_diveq : printf("/="); break; + case CLEX_modeq : printf("%%="); break; + case CLEX_shleq : printf("<<="); break; + case CLEX_shreq : printf(">>="); break; + case CLEX_eqarrow : printf("=>"); break; + case CLEX_dqstring : printf("\"%s\"", lexer->string); break; + case CLEX_sqstring : printf("'\"%s\"'", lexer->string); break; + case CLEX_charlit : printf("'%s'", lexer->string); break; + #if defined(STB__clex_int_as_double) && !defined(STB__CLEX_use_stdlib) + case CLEX_intlit : printf("#%g", lexer->real_number); break; + #else + case CLEX_intlit : printf("#%ld", lexer->int_number); break; + #endif + case CLEX_floatlit : printf("%g", lexer->real_number); break; + default: + if (lexer->token >= 0 && lexer->token < 256) + printf("%c", (int) lexer->token); + else { + printf("<<>>\n", lexer->token); + } + break; + } +} + +/* Force a test +of parsing +multiline comments */ + +/*/ comment /*/ +/**/ extern /**/ + +void dummy(void) +{ + double some_floats[] = { + 1.0501, -10.4e12, 5E+10, +#if 0 // not supported in C++ or C-pre-99, so don't try to compile it, but let our parser test it + 0x1.0p+24, 0xff.FP-8, 0x1p-23, +#endif + 4. + }; + (void) sizeof(some_floats); + (void) some_floats[1]; + + printf("test %d",1); // https://github.com/nothings/stb/issues/13 +} + +int main(int argc, char **argv) +{ + FILE *f = fopen("stb_c_lexer.h","rb"); + char *text = (char *) malloc(1 << 20); + int len = f ? (int) fread(text, 1, 1<<20, f) : -1; + stb_lexer lex; + if (len < 0) { + fprintf(stderr, "Error opening file\n"); + free(text); + fclose(f); + return 1; + } + fclose(f); + + stb_c_lexer_init(&lex, text, text+len, (char *) malloc(0x10000), 0x10000); + while (stb_c_lexer_get_token(&lex)) { + if (lex.token == CLEX_parse_error) { + printf("\n<<>>\n"); + break; + } + print_token(&lex); + printf(" "); + } + return 0; +} +#endif +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_connected_components.h b/source/engine/thirdparty/stb/stb_connected_components.h new file mode 100644 index 0000000..f762f65 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_connected_components.h @@ -0,0 +1,1049 @@ +// stb_connected_components - v0.96 - public domain connected components on grids +// http://github.com/nothings/stb +// +// Finds connected components on 2D grids for testing reachability between +// two points, with fast updates when changing reachability (e.g. on one machine +// it was typically 0.2ms w/ 1024x1024 grid). Each grid square must be "open" or +// "closed" (traversable or untraversable), and grid squares are only connected +// to their orthogonal neighbors, not diagonally. +// +// In one source file, create the implementation by doing something like this: +// +// #define STBCC_GRID_COUNT_X_LOG2 10 +// #define STBCC_GRID_COUNT_Y_LOG2 10 +// #define STB_CONNECTED_COMPONENTS_IMPLEMENTATION +// #include "stb_connected_components.h" +// +// The above creates an implementation that can run on maps up to 1024x1024. +// Map sizes must be a multiple of (1<<(LOG2/2)) on each axis (e.g. 32 if LOG2=10, +// 16 if LOG2=8, etc.) (You can just pad your map with untraversable space.) +// +// MEMORY USAGE +// +// Uses about 6-7 bytes per grid square (e.g. 7MB for a 1024x1024 grid). +// Uses a single worst-case allocation which you pass in. +// +// PERFORMANCE +// +// On a core i7-2700K at 3.5 Ghz, for a particular 1024x1024 map (map_03.png): +// +// Creating map : 44.85 ms +// Making one square traversable: 0.27 ms (average over 29,448 calls) +// Making one square untraversable: 0.23 ms (average over 30,123 calls) +// Reachability query: 0.00001 ms (average over 4,000,000 calls) +// +// On non-degenerate maps update time is O(N^0.5), but on degenerate maps like +// checkerboards or 50% random, update time is O(N^0.75) (~2ms on above machine). +// +// CHANGELOG +// +// 0.96 (2019-03-04) Fix warnings +// 0.95 (2016-10-16) Bugfix if multiple clumps in one cluster connect to same clump in another +// 0.94 (2016-04-17) Bugfix & optimize worst case (checkerboard & random) +// 0.93 (2016-04-16) Reduce memory by 10x for 1Kx1K map; small speedup +// 0.92 (2016-04-16) Compute sqrt(N) cluster size by default +// 0.91 (2016-04-15) Initial release +// +// TODO: +// - better API documentation +// - more comments +// - try re-integrating naive algorithm & compare performance +// - more optimized batching (current approach still recomputes local clumps many times) +// - function for setting a grid of squares at once (just use batching) +// +// LICENSE +// +// See end of file for license information. +// +// ALGORITHM +// +// The NxN grid map is split into sqrt(N) x sqrt(N) blocks called +// "clusters". Each cluster independently computes a set of connected +// components within that cluster (ignoring all connectivity out of +// that cluster) using a union-find disjoint set forest. This produces a bunch +// of locally connected components called "clumps". Each clump is (a) connected +// within its cluster, (b) does not directly connect to any other clumps in the +// cluster (though it may connect to them by paths that lead outside the cluster, +// but those are ignored at this step), and (c) maintains an adjacency list of +// all clumps in adjacent clusters that it _is_ connected to. Then a second +// union-find disjoint set forest is used to compute connected clumps +// globally, across the whole map. Reachability is then computed by +// finding which clump each input point belongs to, and checking whether +// those clumps are in the same "global" connected component. +// +// The above data structure can be updated efficiently; on a change +// of a single grid square on the map, only one cluster changes its +// purely-local state, so only one cluster needs its clumps fully +// recomputed. Clumps in adjacent clusters need their adjacency lists +// updated: first to remove all references to the old clumps in the +// rebuilt cluster, then to add new references to the new clumps. Both +// of these operations can use the existing "find which clump each input +// point belongs to" query to compute that adjacency information rapidly. + +#ifndef INCLUDE_STB_CONNECTED_COMPONENTS_H +#define INCLUDE_STB_CONNECTED_COMPONENTS_H + +#include + +typedef struct st_stbcc_grid stbcc_grid; + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////////////////// +// +// initialization +// + +// you allocate the grid data structure to this size (note that it will be very big!!!) +extern size_t stbcc_grid_sizeof(void); + +// initialize the grid, value of map[] is 0 = traversable, non-0 is solid +extern void stbcc_init_grid(stbcc_grid *g, unsigned char *map, int w, int h); + + +////////////////////////////////////////////////////////////////////////////////////////// +// +// main functionality +// + +// update a grid square state, 0 = traversable, non-0 is solid +// i can add a batch-update if it's needed +extern void stbcc_update_grid(stbcc_grid *g, int x, int y, int solid); + +// query if two grid squares are reachable from each other +extern int stbcc_query_grid_node_connection(stbcc_grid *g, int x1, int y1, int x2, int y2); + + +////////////////////////////////////////////////////////////////////////////////////////// +// +// bonus functions +// + +// wrap multiple stbcc_update_grid calls in these function to compute +// multiple updates more efficiently; cannot make queries inside batch +extern void stbcc_update_batch_begin(stbcc_grid *g); +extern void stbcc_update_batch_end(stbcc_grid *g); + +// query the grid data structure for whether a given square is open or not +extern int stbcc_query_grid_open(stbcc_grid *g, int x, int y); + +// get a unique id for the connected component this is in; it's not necessarily +// small, you'll need a hash table or something to remap it (or just use +extern unsigned int stbcc_get_unique_id(stbcc_grid *g, int x, int y); +#define STBCC_NULL_UNIQUE_ID 0xffffffff // returned for closed map squares + +#ifdef __cplusplus +} +#endif + +#endif // INCLUDE_STB_CONNECTED_COMPONENTS_H + +#ifdef STB_CONNECTED_COMPONENTS_IMPLEMENTATION + +#include +#include // memset + +#if !defined(STBCC_GRID_COUNT_X_LOG2) || !defined(STBCC_GRID_COUNT_Y_LOG2) + #error "You must define STBCC_GRID_COUNT_X_LOG2 and STBCC_GRID_COUNT_Y_LOG2 to define the max grid supported." +#endif + +#define STBCC__GRID_COUNT_X (1 << STBCC_GRID_COUNT_X_LOG2) +#define STBCC__GRID_COUNT_Y (1 << STBCC_GRID_COUNT_Y_LOG2) + +#define STBCC__MAP_STRIDE (1 << (STBCC_GRID_COUNT_X_LOG2-3)) + +#ifndef STBCC_CLUSTER_SIZE_X_LOG2 + #define STBCC_CLUSTER_SIZE_X_LOG2 (STBCC_GRID_COUNT_X_LOG2/2) // log2(sqrt(2^N)) = 1/2 * log2(2^N)) = 1/2 * N + #if STBCC_CLUSTER_SIZE_X_LOG2 > 6 + #undef STBCC_CLUSTER_SIZE_X_LOG2 + #define STBCC_CLUSTER_SIZE_X_LOG2 6 + #endif +#endif + +#ifndef STBCC_CLUSTER_SIZE_Y_LOG2 + #define STBCC_CLUSTER_SIZE_Y_LOG2 (STBCC_GRID_COUNT_Y_LOG2/2) + #if STBCC_CLUSTER_SIZE_Y_LOG2 > 6 + #undef STBCC_CLUSTER_SIZE_Y_LOG2 + #define STBCC_CLUSTER_SIZE_Y_LOG2 6 + #endif +#endif + +#define STBCC__CLUSTER_SIZE_X (1 << STBCC_CLUSTER_SIZE_X_LOG2) +#define STBCC__CLUSTER_SIZE_Y (1 << STBCC_CLUSTER_SIZE_Y_LOG2) + +#define STBCC__CLUSTER_COUNT_X_LOG2 (STBCC_GRID_COUNT_X_LOG2 - STBCC_CLUSTER_SIZE_X_LOG2) +#define STBCC__CLUSTER_COUNT_Y_LOG2 (STBCC_GRID_COUNT_Y_LOG2 - STBCC_CLUSTER_SIZE_Y_LOG2) + +#define STBCC__CLUSTER_COUNT_X (1 << STBCC__CLUSTER_COUNT_X_LOG2) +#define STBCC__CLUSTER_COUNT_Y (1 << STBCC__CLUSTER_COUNT_Y_LOG2) + +#if STBCC__CLUSTER_SIZE_X >= STBCC__GRID_COUNT_X || STBCC__CLUSTER_SIZE_Y >= STBCC__GRID_COUNT_Y + #error "STBCC_CLUSTER_SIZE_X/Y_LOG2 must be smaller than STBCC_GRID_COUNT_X/Y_LOG2" +#endif + +// worst case # of clumps per cluster +#define STBCC__MAX_CLUMPS_PER_CLUSTER_LOG2 (STBCC_CLUSTER_SIZE_X_LOG2 + STBCC_CLUSTER_SIZE_Y_LOG2-1) +#define STBCC__MAX_CLUMPS_PER_CLUSTER (1 << STBCC__MAX_CLUMPS_PER_CLUSTER_LOG2) +#define STBCC__MAX_CLUMPS (STBCC__MAX_CLUMPS_PER_CLUSTER * STBCC__CLUSTER_COUNT_X * STBCC__CLUSTER_COUNT_Y) +#define STBCC__NULL_CLUMPID STBCC__MAX_CLUMPS_PER_CLUSTER + +#define STBCC__CLUSTER_X_FOR_COORD_X(x) ((x) >> STBCC_CLUSTER_SIZE_X_LOG2) +#define STBCC__CLUSTER_Y_FOR_COORD_Y(y) ((y) >> STBCC_CLUSTER_SIZE_Y_LOG2) + +#define STBCC__MAP_BYTE_MASK(x,y) (1 << ((x) & 7)) +#define STBCC__MAP_BYTE(g,x,y) ((g)->map[y][(x) >> 3]) +#define STBCC__MAP_OPEN(g,x,y) (STBCC__MAP_BYTE(g,x,y) & STBCC__MAP_BYTE_MASK(x,y)) + +typedef unsigned short stbcc__clumpid; +typedef unsigned char stbcc__verify_max_clumps[STBCC__MAX_CLUMPS_PER_CLUSTER < (1 << (8*sizeof(stbcc__clumpid))) ? 1 : -1]; + +#define STBCC__MAX_EXITS_PER_CLUSTER (STBCC__CLUSTER_SIZE_X + STBCC__CLUSTER_SIZE_Y) // 64 for 32x32 +#define STBCC__MAX_EXITS_PER_CLUMP (STBCC__CLUSTER_SIZE_X + STBCC__CLUSTER_SIZE_Y) // 64 for 32x32 +#define STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER (STBCC__MAX_EXITS_PER_CLUMP) + +// 2^19 * 2^6 => 2^25 exits => 2^26 => 64MB for 1024x1024 + +// Logic for above on 4x4 grid: +// +// Many clumps: One clump: +// + + + + +// +X.X. +XX.X+ +// .X.X+ .XXX +// +X.X. XXX. +// .X.X+ +X.XX+ +// + + + + +// +// 8 exits either way + +typedef unsigned char stbcc__verify_max_exits[STBCC__MAX_EXITS_PER_CLUMP <= 256]; + +typedef struct +{ + unsigned short clump_index:12; + signed short cluster_dx:2; + signed short cluster_dy:2; +} stbcc__relative_clumpid; + +typedef union +{ + struct { + unsigned int clump_index:12; + unsigned int cluster_x:10; + unsigned int cluster_y:10; + } f; + unsigned int c; +} stbcc__global_clumpid; + +// rebuilt cluster 3,4 + +// what changes in cluster 2,4 + +typedef struct +{ + stbcc__global_clumpid global_label; // 4 + unsigned char num_adjacent; // 1 + unsigned char max_adjacent; // 1 + unsigned char adjacent_clump_list_index; // 1 + unsigned char reserved; +} stbcc__clump; // 8 + +#define STBCC__CLUSTER_ADJACENCY_COUNT (STBCC__MAX_EXITS_PER_CLUSTER*2) +typedef struct +{ + short num_clumps; + unsigned char num_edge_clumps; + unsigned char rebuild_adjacency; + stbcc__clump clump[STBCC__MAX_CLUMPS_PER_CLUSTER]; // 8 * 2^9 = 4KB + stbcc__relative_clumpid adjacency_storage[STBCC__CLUSTER_ADJACENCY_COUNT]; // 256 bytes +} stbcc__cluster; + +struct st_stbcc_grid +{ + int w,h,cw,ch; + int in_batched_update; + //unsigned char cluster_dirty[STBCC__CLUSTER_COUNT_Y][STBCC__CLUSTER_COUNT_X]; // could bitpack, but: 1K x 1K => 1KB + unsigned char map[STBCC__GRID_COUNT_Y][STBCC__MAP_STRIDE]; // 1K x 1K => 1K x 128 => 128KB + stbcc__clumpid clump_for_node[STBCC__GRID_COUNT_Y][STBCC__GRID_COUNT_X]; // 1K x 1K x 2 = 2MB + stbcc__cluster cluster[STBCC__CLUSTER_COUNT_Y][STBCC__CLUSTER_COUNT_X]; // 1K x 4.5KB = 4.5MB +}; + +int stbcc_query_grid_node_connection(stbcc_grid *g, int x1, int y1, int x2, int y2) +{ + stbcc__global_clumpid label1, label2; + stbcc__clumpid c1 = g->clump_for_node[y1][x1]; + stbcc__clumpid c2 = g->clump_for_node[y2][x2]; + int cx1 = STBCC__CLUSTER_X_FOR_COORD_X(x1); + int cy1 = STBCC__CLUSTER_Y_FOR_COORD_Y(y1); + int cx2 = STBCC__CLUSTER_X_FOR_COORD_X(x2); + int cy2 = STBCC__CLUSTER_Y_FOR_COORD_Y(y2); + assert(!g->in_batched_update); + if (c1 == STBCC__NULL_CLUMPID || c2 == STBCC__NULL_CLUMPID) + return 0; + label1 = g->cluster[cy1][cx1].clump[c1].global_label; + label2 = g->cluster[cy2][cx2].clump[c2].global_label; + if (label1.c == label2.c) + return 1; + return 0; +} + +int stbcc_query_grid_open(stbcc_grid *g, int x, int y) +{ + return STBCC__MAP_OPEN(g, x, y) != 0; +} + +unsigned int stbcc_get_unique_id(stbcc_grid *g, int x, int y) +{ + stbcc__clumpid c = g->clump_for_node[y][x]; + int cx = STBCC__CLUSTER_X_FOR_COORD_X(x); + int cy = STBCC__CLUSTER_Y_FOR_COORD_Y(y); + assert(!g->in_batched_update); + if (c == STBCC__NULL_CLUMPID) return STBCC_NULL_UNIQUE_ID; + return g->cluster[cy][cx].clump[c].global_label.c; +} + +typedef struct +{ + unsigned char x,y; +} stbcc__tinypoint; + +typedef struct +{ + stbcc__tinypoint parent[STBCC__CLUSTER_SIZE_Y][STBCC__CLUSTER_SIZE_X]; // 32x32 => 2KB + stbcc__clumpid label[STBCC__CLUSTER_SIZE_Y][STBCC__CLUSTER_SIZE_X]; +} stbcc__cluster_build_info; + +static void stbcc__build_clumps_for_cluster(stbcc_grid *g, int cx, int cy); +static void stbcc__remove_connections_to_adjacent_cluster(stbcc_grid *g, int cx, int cy, int dx, int dy); +static void stbcc__add_connections_to_adjacent_cluster(stbcc_grid *g, int cx, int cy, int dx, int dy); + +static stbcc__global_clumpid stbcc__clump_find(stbcc_grid *g, stbcc__global_clumpid n) +{ + stbcc__global_clumpid q; + stbcc__clump *c = &g->cluster[n.f.cluster_y][n.f.cluster_x].clump[n.f.clump_index]; + + if (c->global_label.c == n.c) + return n; + + q = stbcc__clump_find(g, c->global_label); + c->global_label = q; + return q; +} + +typedef struct +{ + unsigned int cluster_x; + unsigned int cluster_y; + unsigned int clump_index; +} stbcc__unpacked_clumpid; + +static void stbcc__clump_union(stbcc_grid *g, stbcc__unpacked_clumpid m, int x, int y, int idx) +{ + stbcc__clump *mc = &g->cluster[m.cluster_y][m.cluster_x].clump[m.clump_index]; + stbcc__clump *nc = &g->cluster[y][x].clump[idx]; + stbcc__global_clumpid mp = stbcc__clump_find(g, mc->global_label); + stbcc__global_clumpid np = stbcc__clump_find(g, nc->global_label); + + if (mp.c == np.c) + return; + + g->cluster[mp.f.cluster_y][mp.f.cluster_x].clump[mp.f.clump_index].global_label = np; +} + +static void stbcc__build_connected_components_for_clumps(stbcc_grid *g) +{ + int i,j,k,h; + + for (j=0; j < STBCC__CLUSTER_COUNT_Y; ++j) { + for (i=0; i < STBCC__CLUSTER_COUNT_X; ++i) { + stbcc__cluster *cluster = &g->cluster[j][i]; + for (k=0; k < (int) cluster->num_edge_clumps; ++k) { + stbcc__global_clumpid m; + m.f.clump_index = k; + m.f.cluster_x = i; + m.f.cluster_y = j; + assert((int) m.f.clump_index == k && (int) m.f.cluster_x == i && (int) m.f.cluster_y == j); + cluster->clump[k].global_label = m; + } + } + } + + for (j=0; j < STBCC__CLUSTER_COUNT_Y; ++j) { + for (i=0; i < STBCC__CLUSTER_COUNT_X; ++i) { + stbcc__cluster *cluster = &g->cluster[j][i]; + for (k=0; k < (int) cluster->num_edge_clumps; ++k) { + stbcc__clump *clump = &cluster->clump[k]; + stbcc__unpacked_clumpid m; + stbcc__relative_clumpid *adj; + m.clump_index = k; + m.cluster_x = i; + m.cluster_y = j; + adj = &cluster->adjacency_storage[clump->adjacent_clump_list_index]; + for (h=0; h < clump->num_adjacent; ++h) { + unsigned int clump_index = adj[h].clump_index; + unsigned int x = adj[h].cluster_dx + i; + unsigned int y = adj[h].cluster_dy + j; + stbcc__clump_union(g, m, x, y, clump_index); + } + } + } + } + + for (j=0; j < STBCC__CLUSTER_COUNT_Y; ++j) { + for (i=0; i < STBCC__CLUSTER_COUNT_X; ++i) { + stbcc__cluster *cluster = &g->cluster[j][i]; + for (k=0; k < (int) cluster->num_edge_clumps; ++k) { + stbcc__global_clumpid m; + m.f.clump_index = k; + m.f.cluster_x = i; + m.f.cluster_y = j; + stbcc__clump_find(g, m); + } + } + } +} + +static void stbcc__build_all_connections_for_cluster(stbcc_grid *g, int cx, int cy) +{ + // in this particular case, we are fully non-incremental. that means we + // can discover the correct sizes for the arrays, but requires we build + // the data into temporary data structures, or just count the sizes, so + // for simplicity we do the latter + stbcc__cluster *cluster = &g->cluster[cy][cx]; + unsigned char connected[STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER][STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER/8]; // 64 x 8 => 1KB + unsigned char num_adj[STBCC__MAX_CLUMPS_PER_CLUSTER] = { 0 }; + int x = cx * STBCC__CLUSTER_SIZE_X; + int y = cy * STBCC__CLUSTER_SIZE_Y; + int step_x, step_y=0, i, j, k, n, m, dx, dy, total; + int extra; + + g->cluster[cy][cx].rebuild_adjacency = 0; + + total = 0; + for (m=0; m < 4; ++m) { + switch (m) { + case 0: + dx = 1, dy = 0; + step_x = 0, step_y = 1; + i = STBCC__CLUSTER_SIZE_X-1; + j = 0; + n = STBCC__CLUSTER_SIZE_Y; + break; + case 1: + dx = -1, dy = 0; + i = 0; + j = 0; + step_x = 0; + step_y = 1; + n = STBCC__CLUSTER_SIZE_Y; + break; + case 2: + dy = -1, dx = 0; + i = 0; + j = 0; + step_x = 1; + step_y = 0; + n = STBCC__CLUSTER_SIZE_X; + break; + case 3: + dy = 1, dx = 0; + i = 0; + j = STBCC__CLUSTER_SIZE_Y-1; + step_x = 1; + step_y = 0; + n = STBCC__CLUSTER_SIZE_X; + break; + } + + if (cx+dx < 0 || cx+dx >= g->cw || cy+dy < 0 || cy+dy >= g->ch) + continue; + + memset(connected, 0, sizeof(connected)); + for (k=0; k < n; ++k) { + if (STBCC__MAP_OPEN(g, x+i, y+j) && STBCC__MAP_OPEN(g, x+i+dx, y+j+dy)) { + stbcc__clumpid src = g->clump_for_node[y+j][x+i]; + stbcc__clumpid dest = g->clump_for_node[y+j+dy][x+i+dx]; + if (0 == (connected[src][dest>>3] & (1 << (dest & 7)))) { + connected[src][dest>>3] |= 1 << (dest & 7); + ++num_adj[src]; + ++total; + } + } + i += step_x; + j += step_y; + } + } + + assert(total <= STBCC__CLUSTER_ADJACENCY_COUNT); + + // decide how to apportion unused adjacency slots; only clumps that lie + // on the edges of the cluster need adjacency slots, so divide them up + // evenly between those clumps + + // we want: + // extra = (STBCC__CLUSTER_ADJACENCY_COUNT - total) / cluster->num_edge_clumps; + // but we efficiently approximate this without a divide, because + // ignoring edge-vs-non-edge with 'num_adj[i]*2' was faster than + // 'num_adj[i]+extra' with the divide + if (total + (cluster->num_edge_clumps<<2) <= STBCC__CLUSTER_ADJACENCY_COUNT) + extra = 4; + else if (total + (cluster->num_edge_clumps<<1) <= STBCC__CLUSTER_ADJACENCY_COUNT) + extra = 2; + else if (total + (cluster->num_edge_clumps<<0) <= STBCC__CLUSTER_ADJACENCY_COUNT) + extra = 1; + else + extra = 0; + + total = 0; + for (i=0; i < (int) cluster->num_edge_clumps; ++i) { + int alloc = num_adj[i]+extra; + if (alloc > STBCC__MAX_EXITS_PER_CLUSTER) + alloc = STBCC__MAX_EXITS_PER_CLUSTER; + assert(total < 256); // must fit in byte + cluster->clump[i].adjacent_clump_list_index = (unsigned char) total; + cluster->clump[i].max_adjacent = alloc; + cluster->clump[i].num_adjacent = 0; + total += alloc; + } + assert(total <= STBCC__CLUSTER_ADJACENCY_COUNT); + + stbcc__add_connections_to_adjacent_cluster(g, cx, cy, -1, 0); + stbcc__add_connections_to_adjacent_cluster(g, cx, cy, 1, 0); + stbcc__add_connections_to_adjacent_cluster(g, cx, cy, 0,-1); + stbcc__add_connections_to_adjacent_cluster(g, cx, cy, 0, 1); + // make sure all of the above succeeded. + assert(g->cluster[cy][cx].rebuild_adjacency == 0); +} + +static void stbcc__add_connections_to_adjacent_cluster_with_rebuild(stbcc_grid *g, int cx, int cy, int dx, int dy) +{ + if (cx >= 0 && cx < g->cw && cy >= 0 && cy < g->ch) { + stbcc__add_connections_to_adjacent_cluster(g, cx, cy, dx, dy); + if (g->cluster[cy][cx].rebuild_adjacency) + stbcc__build_all_connections_for_cluster(g, cx, cy); + } +} + +void stbcc_update_grid(stbcc_grid *g, int x, int y, int solid) +{ + int cx,cy; + + if (!solid) { + if (STBCC__MAP_OPEN(g,x,y)) + return; + } else { + if (!STBCC__MAP_OPEN(g,x,y)) + return; + } + + cx = STBCC__CLUSTER_X_FOR_COORD_X(x); + cy = STBCC__CLUSTER_Y_FOR_COORD_Y(y); + + stbcc__remove_connections_to_adjacent_cluster(g, cx-1, cy, 1, 0); + stbcc__remove_connections_to_adjacent_cluster(g, cx+1, cy, -1, 0); + stbcc__remove_connections_to_adjacent_cluster(g, cx, cy-1, 0, 1); + stbcc__remove_connections_to_adjacent_cluster(g, cx, cy+1, 0,-1); + + if (!solid) + STBCC__MAP_BYTE(g,x,y) |= STBCC__MAP_BYTE_MASK(x,y); + else + STBCC__MAP_BYTE(g,x,y) &= ~STBCC__MAP_BYTE_MASK(x,y); + + stbcc__build_clumps_for_cluster(g, cx, cy); + stbcc__build_all_connections_for_cluster(g, cx, cy); + + stbcc__add_connections_to_adjacent_cluster_with_rebuild(g, cx-1, cy, 1, 0); + stbcc__add_connections_to_adjacent_cluster_with_rebuild(g, cx+1, cy, -1, 0); + stbcc__add_connections_to_adjacent_cluster_with_rebuild(g, cx, cy-1, 0, 1); + stbcc__add_connections_to_adjacent_cluster_with_rebuild(g, cx, cy+1, 0,-1); + + if (!g->in_batched_update) + stbcc__build_connected_components_for_clumps(g); + #if 0 + else + g->cluster_dirty[cy][cx] = 1; + #endif +} + +void stbcc_update_batch_begin(stbcc_grid *g) +{ + assert(!g->in_batched_update); + g->in_batched_update = 1; +} + +void stbcc_update_batch_end(stbcc_grid *g) +{ + assert(g->in_batched_update); + g->in_batched_update = 0; + stbcc__build_connected_components_for_clumps(g); // @OPTIMIZE: only do this if update was non-empty +} + +size_t stbcc_grid_sizeof(void) +{ + return sizeof(stbcc_grid); +} + +void stbcc_init_grid(stbcc_grid *g, unsigned char *map, int w, int h) +{ + int i,j,k; + assert(w % STBCC__CLUSTER_SIZE_X == 0); + assert(h % STBCC__CLUSTER_SIZE_Y == 0); + assert(w % 8 == 0); + + g->w = w; + g->h = h; + g->cw = w >> STBCC_CLUSTER_SIZE_X_LOG2; + g->ch = h >> STBCC_CLUSTER_SIZE_Y_LOG2; + g->in_batched_update = 0; + + #if 0 + for (j=0; j < STBCC__CLUSTER_COUNT_Y; ++j) + for (i=0; i < STBCC__CLUSTER_COUNT_X; ++i) + g->cluster_dirty[j][i] = 0; + #endif + + for (j=0; j < h; ++j) { + for (i=0; i < w; i += 8) { + unsigned char c = 0; + for (k=0; k < 8; ++k) + if (map[j*w + (i+k)] == 0) + c |= (1 << k); + g->map[j][i>>3] = c; + } + } + + for (j=0; j < g->ch; ++j) + for (i=0; i < g->cw; ++i) + stbcc__build_clumps_for_cluster(g, i, j); + + for (j=0; j < g->ch; ++j) + for (i=0; i < g->cw; ++i) + stbcc__build_all_connections_for_cluster(g, i, j); + + stbcc__build_connected_components_for_clumps(g); + + for (j=0; j < g->h; ++j) + for (i=0; i < g->w; ++i) + assert(g->clump_for_node[j][i] <= STBCC__NULL_CLUMPID); +} + + +static void stbcc__add_clump_connection(stbcc_grid *g, int x1, int y1, int x2, int y2) +{ + stbcc__cluster *cluster; + stbcc__clump *clump; + + int cx1 = STBCC__CLUSTER_X_FOR_COORD_X(x1); + int cy1 = STBCC__CLUSTER_Y_FOR_COORD_Y(y1); + int cx2 = STBCC__CLUSTER_X_FOR_COORD_X(x2); + int cy2 = STBCC__CLUSTER_Y_FOR_COORD_Y(y2); + + stbcc__clumpid c1 = g->clump_for_node[y1][x1]; + stbcc__clumpid c2 = g->clump_for_node[y2][x2]; + + stbcc__relative_clumpid rc; + + assert(cx1 != cx2 || cy1 != cy2); + assert(abs(cx1-cx2) + abs(cy1-cy2) == 1); + + // add connection to c2 in c1 + + rc.clump_index = c2; + rc.cluster_dx = x2-x1; + rc.cluster_dy = y2-y1; + + cluster = &g->cluster[cy1][cx1]; + clump = &cluster->clump[c1]; + assert(clump->num_adjacent <= clump->max_adjacent); + if (clump->num_adjacent == clump->max_adjacent) + g->cluster[cy1][cx1].rebuild_adjacency = 1; + else { + stbcc__relative_clumpid *adj = &cluster->adjacency_storage[clump->adjacent_clump_list_index]; + assert(clump->num_adjacent < STBCC__MAX_EXITS_PER_CLUMP); + assert(clump->adjacent_clump_list_index + clump->num_adjacent <= STBCC__CLUSTER_ADJACENCY_COUNT); + adj[clump->num_adjacent++] = rc; + } +} + +static void stbcc__remove_clump_connection(stbcc_grid *g, int x1, int y1, int x2, int y2) +{ + stbcc__cluster *cluster; + stbcc__clump *clump; + stbcc__relative_clumpid *adj; + int i; + + int cx1 = STBCC__CLUSTER_X_FOR_COORD_X(x1); + int cy1 = STBCC__CLUSTER_Y_FOR_COORD_Y(y1); + int cx2 = STBCC__CLUSTER_X_FOR_COORD_X(x2); + int cy2 = STBCC__CLUSTER_Y_FOR_COORD_Y(y2); + + stbcc__clumpid c1 = g->clump_for_node[y1][x1]; + stbcc__clumpid c2 = g->clump_for_node[y2][x2]; + + stbcc__relative_clumpid rc; + + assert(cx1 != cx2 || cy1 != cy2); + assert(abs(cx1-cx2) + abs(cy1-cy2) == 1); + + // add connection to c2 in c1 + + rc.clump_index = c2; + rc.cluster_dx = x2-x1; + rc.cluster_dy = y2-y1; + + cluster = &g->cluster[cy1][cx1]; + clump = &cluster->clump[c1]; + adj = &cluster->adjacency_storage[clump->adjacent_clump_list_index]; + + for (i=0; i < clump->num_adjacent; ++i) + if (rc.clump_index == adj[i].clump_index && + rc.cluster_dx == adj[i].cluster_dx && + rc.cluster_dy == adj[i].cluster_dy) + break; + + if (i < clump->num_adjacent) + adj[i] = adj[--clump->num_adjacent]; + else + assert(0); +} + +static void stbcc__add_connections_to_adjacent_cluster(stbcc_grid *g, int cx, int cy, int dx, int dy) +{ + unsigned char connected[STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER][STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER/8] = { { 0 } }; + int x = cx * STBCC__CLUSTER_SIZE_X; + int y = cy * STBCC__CLUSTER_SIZE_Y; + int step_x, step_y=0, i, j, k, n; + + if (cx < 0 || cx >= g->cw || cy < 0 || cy >= g->ch) + return; + + if (cx+dx < 0 || cx+dx >= g->cw || cy+dy < 0 || cy+dy >= g->ch) + return; + + if (g->cluster[cy][cx].rebuild_adjacency) + return; + + assert(abs(dx) + abs(dy) == 1); + + if (dx == 1) { + i = STBCC__CLUSTER_SIZE_X-1; + j = 0; + step_x = 0; + step_y = 1; + n = STBCC__CLUSTER_SIZE_Y; + } else if (dx == -1) { + i = 0; + j = 0; + step_x = 0; + step_y = 1; + n = STBCC__CLUSTER_SIZE_Y; + } else if (dy == -1) { + i = 0; + j = 0; + step_x = 1; + step_y = 0; + n = STBCC__CLUSTER_SIZE_X; + } else if (dy == 1) { + i = 0; + j = STBCC__CLUSTER_SIZE_Y-1; + step_x = 1; + step_y = 0; + n = STBCC__CLUSTER_SIZE_X; + } else { + assert(0); + return; + } + + for (k=0; k < n; ++k) { + if (STBCC__MAP_OPEN(g, x+i, y+j) && STBCC__MAP_OPEN(g, x+i+dx, y+j+dy)) { + stbcc__clumpid src = g->clump_for_node[y+j][x+i]; + stbcc__clumpid dest = g->clump_for_node[y+j+dy][x+i+dx]; + if (0 == (connected[src][dest>>3] & (1 << (dest & 7)))) { + assert((dest>>3) < sizeof(connected)); + connected[src][dest>>3] |= 1 << (dest & 7); + stbcc__add_clump_connection(g, x+i, y+j, x+i+dx, y+j+dy); + if (g->cluster[cy][cx].rebuild_adjacency) + break; + } + } + i += step_x; + j += step_y; + } +} + +static void stbcc__remove_connections_to_adjacent_cluster(stbcc_grid *g, int cx, int cy, int dx, int dy) +{ + unsigned char disconnected[STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER][STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER/8] = { { 0 } }; + int x = cx * STBCC__CLUSTER_SIZE_X; + int y = cy * STBCC__CLUSTER_SIZE_Y; + int step_x, step_y=0, i, j, k, n; + + if (cx < 0 || cx >= g->cw || cy < 0 || cy >= g->ch) + return; + + if (cx+dx < 0 || cx+dx >= g->cw || cy+dy < 0 || cy+dy >= g->ch) + return; + + assert(abs(dx) + abs(dy) == 1); + + if (dx == 1) { + i = STBCC__CLUSTER_SIZE_X-1; + j = 0; + step_x = 0; + step_y = 1; + n = STBCC__CLUSTER_SIZE_Y; + } else if (dx == -1) { + i = 0; + j = 0; + step_x = 0; + step_y = 1; + n = STBCC__CLUSTER_SIZE_Y; + } else if (dy == -1) { + i = 0; + j = 0; + step_x = 1; + step_y = 0; + n = STBCC__CLUSTER_SIZE_X; + } else if (dy == 1) { + i = 0; + j = STBCC__CLUSTER_SIZE_Y-1; + step_x = 1; + step_y = 0; + n = STBCC__CLUSTER_SIZE_X; + } else { + assert(0); + return; + } + + for (k=0; k < n; ++k) { + if (STBCC__MAP_OPEN(g, x+i, y+j) && STBCC__MAP_OPEN(g, x+i+dx, y+j+dy)) { + stbcc__clumpid src = g->clump_for_node[y+j][x+i]; + stbcc__clumpid dest = g->clump_for_node[y+j+dy][x+i+dx]; + if (0 == (disconnected[src][dest>>3] & (1 << (dest & 7)))) { + disconnected[src][dest>>3] |= 1 << (dest & 7); + stbcc__remove_clump_connection(g, x+i, y+j, x+i+dx, y+j+dy); + } + } + i += step_x; + j += step_y; + } +} + +static stbcc__tinypoint stbcc__incluster_find(stbcc__cluster_build_info *cbi, int x, int y) +{ + stbcc__tinypoint p,q; + p = cbi->parent[y][x]; + if (p.x == x && p.y == y) + return p; + q = stbcc__incluster_find(cbi, p.x, p.y); + cbi->parent[y][x] = q; + return q; +} + +static void stbcc__incluster_union(stbcc__cluster_build_info *cbi, int x1, int y1, int x2, int y2) +{ + stbcc__tinypoint p = stbcc__incluster_find(cbi, x1,y1); + stbcc__tinypoint q = stbcc__incluster_find(cbi, x2,y2); + + if (p.x == q.x && p.y == q.y) + return; + + cbi->parent[p.y][p.x] = q; +} + +static void stbcc__switch_root(stbcc__cluster_build_info *cbi, int x, int y, stbcc__tinypoint p) +{ + cbi->parent[p.y][p.x].x = x; + cbi->parent[p.y][p.x].y = y; + cbi->parent[y][x].x = x; + cbi->parent[y][x].y = y; +} + +static void stbcc__build_clumps_for_cluster(stbcc_grid *g, int cx, int cy) +{ + stbcc__cluster *c; + stbcc__cluster_build_info cbi; + int label=0; + int i,j; + int x = cx * STBCC__CLUSTER_SIZE_X; + int y = cy * STBCC__CLUSTER_SIZE_Y; + + // set initial disjoint set forest state + for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) { + for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) { + cbi.parent[j][i].x = i; + cbi.parent[j][i].y = j; + } + } + + // join all sets that are connected + for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) { + // check down only if not on bottom row + if (j < STBCC__CLUSTER_SIZE_Y-1) + for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) + if (STBCC__MAP_OPEN(g,x+i,y+j) && STBCC__MAP_OPEN(g,x+i ,y+j+1)) + stbcc__incluster_union(&cbi, i,j, i,j+1); + // check right for everything but rightmost column + for (i=0; i < STBCC__CLUSTER_SIZE_X-1; ++i) + if (STBCC__MAP_OPEN(g,x+i,y+j) && STBCC__MAP_OPEN(g,x+i+1,y+j )) + stbcc__incluster_union(&cbi, i,j, i+1,j); + } + + // label all non-empty clumps along edges so that all edge clumps are first + // in list; this means in degenerate case we can skip traversing non-edge clumps. + // because in the first pass we only label leaders, we swap the leader to the + // edge first + + // first put solid labels on all the edges; these will get overwritten if they're open + for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) + cbi.label[j][0] = cbi.label[j][STBCC__CLUSTER_SIZE_X-1] = STBCC__NULL_CLUMPID; + for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) + cbi.label[0][i] = cbi.label[STBCC__CLUSTER_SIZE_Y-1][i] = STBCC__NULL_CLUMPID; + + for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) { + i = 0; + if (STBCC__MAP_OPEN(g, x+i, y+j)) { + stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j); + if (p.x == i && p.y == j) + // if this is the leader, give it a label + cbi.label[j][i] = label++; + else if (!(p.x == 0 || p.x == STBCC__CLUSTER_SIZE_X-1 || p.y == 0 || p.y == STBCC__CLUSTER_SIZE_Y-1)) { + // if leader is in interior, promote this edge node to leader and label + stbcc__switch_root(&cbi, i, j, p); + cbi.label[j][i] = label++; + } + // else if leader is on edge, do nothing (it'll get labelled when we reach it) + } + i = STBCC__CLUSTER_SIZE_X-1; + if (STBCC__MAP_OPEN(g, x+i, y+j)) { + stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j); + if (p.x == i && p.y == j) + cbi.label[j][i] = label++; + else if (!(p.x == 0 || p.x == STBCC__CLUSTER_SIZE_X-1 || p.y == 0 || p.y == STBCC__CLUSTER_SIZE_Y-1)) { + stbcc__switch_root(&cbi, i, j, p); + cbi.label[j][i] = label++; + } + } + } + + for (i=1; i < STBCC__CLUSTER_SIZE_Y-1; ++i) { + j = 0; + if (STBCC__MAP_OPEN(g, x+i, y+j)) { + stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j); + if (p.x == i && p.y == j) + cbi.label[j][i] = label++; + else if (!(p.x == 0 || p.x == STBCC__CLUSTER_SIZE_X-1 || p.y == 0 || p.y == STBCC__CLUSTER_SIZE_Y-1)) { + stbcc__switch_root(&cbi, i, j, p); + cbi.label[j][i] = label++; + } + } + j = STBCC__CLUSTER_SIZE_Y-1; + if (STBCC__MAP_OPEN(g, x+i, y+j)) { + stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j); + if (p.x == i && p.y == j) + cbi.label[j][i] = label++; + else if (!(p.x == 0 || p.x == STBCC__CLUSTER_SIZE_X-1 || p.y == 0 || p.y == STBCC__CLUSTER_SIZE_Y-1)) { + stbcc__switch_root(&cbi, i, j, p); + cbi.label[j][i] = label++; + } + } + } + + c = &g->cluster[cy][cx]; + c->num_edge_clumps = label; + + // label any internal clusters + for (j=1; j < STBCC__CLUSTER_SIZE_Y-1; ++j) { + for (i=1; i < STBCC__CLUSTER_SIZE_X-1; ++i) { + stbcc__tinypoint p = cbi.parent[j][i]; + if (p.x == i && p.y == j) { + if (STBCC__MAP_OPEN(g,x+i,y+j)) + cbi.label[j][i] = label++; + else + cbi.label[j][i] = STBCC__NULL_CLUMPID; + } + } + } + + // label all other nodes + for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) { + for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) { + stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j); + if (p.x != i || p.y != j) { + if (STBCC__MAP_OPEN(g,x+i,y+j)) + cbi.label[j][i] = cbi.label[p.y][p.x]; + } + if (STBCC__MAP_OPEN(g,x+i,y+j)) + assert(cbi.label[j][i] != STBCC__NULL_CLUMPID); + } + } + + c->num_clumps = label; + + for (i=0; i < label; ++i) { + c->clump[i].num_adjacent = 0; + c->clump[i].max_adjacent = 0; + } + + for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) + for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) { + g->clump_for_node[y+j][x+i] = cbi.label[j][i]; // @OPTIMIZE: remove cbi.label entirely + assert(g->clump_for_node[y+j][x+i] <= STBCC__NULL_CLUMPID); + } + + // set the global label for all interior clumps since they can't have connections, + // so we don't have to do this on the global pass (brings from O(N) to O(N^0.75)) + for (i=(int) c->num_edge_clumps; i < (int) c->num_clumps; ++i) { + stbcc__global_clumpid gc; + gc.f.cluster_x = cx; + gc.f.cluster_y = cy; + gc.f.clump_index = i; + c->clump[i].global_label = gc; + } + + c->rebuild_adjacency = 1; // flag that it has no valid adjacency data +} + +#endif // STB_CONNECTED_COMPONENTS_IMPLEMENTATION +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_divide.h b/source/engine/thirdparty/stb/stb_divide.h new file mode 100644 index 0000000..6a51e3f --- /dev/null +++ b/source/engine/thirdparty/stb/stb_divide.h @@ -0,0 +1,433 @@ +// stb_divide.h - v0.94 - public domain - Sean Barrett, Feb 2010 +// Three kinds of divide/modulus of signed integers. +// +// HISTORY +// +// v0.94 Fix integer overflow issues +// v0.93 2020-02-02 Write useful exit() value from main() +// v0.92 2019-02-25 Fix warning +// v0.91 2010-02-27 Fix euclidean division by INT_MIN for non-truncating C +// Check result with 64-bit math to catch such cases +// v0.90 2010-02-24 First public release +// +// USAGE +// +// In *ONE* source file, put: +// +// #define STB_DIVIDE_IMPLEMENTATION +// // #define C_INTEGER_DIVISION_TRUNCATES // see Note 1 +// // #define C_INTEGER_DIVISION_FLOORS // see Note 2 +// #include "stb_divide.h" +// +// Other source files should just include stb_divide.h +// +// Note 1: On platforms/compilers that you know signed C division +// truncates, you can #define C_INTEGER_DIVISION_TRUNCATES. +// +// Note 2: On platforms/compilers that you know signed C division +// floors (rounds to negative infinity), you can #define +// C_INTEGER_DIVISION_FLOORS. +// +// You can #define STB_DIVIDE_TEST in which case the implementation +// will generate a main() and compiling the result will create a +// program that tests the implementation. Run it with no arguments +// and any output indicates an error; run it with any argument and +// it will also print the test results. Define STB_DIVIDE_TEST_64 +// to a 64-bit integer type to avoid overflows in the result-checking +// which give false negatives. +// +// ABOUT +// +// This file provides three different consistent divide/mod pairs +// implemented on top of arbitrary C/C++ division, including correct +// handling of overflow of intermediate calculations: +// +// trunc: a/b truncates to 0, a%b has same sign as a +// floor: a/b truncates to -inf, a%b has same sign as b +// eucl: a/b truncates to sign(b)*inf, a%b is non-negative +// +// Not necessarily optimal; I tried to keep it generally efficient, +// but there may be better ways. +// +// Briefly, for those who are not familiar with the problem, we note +// the reason these divides exist and are interesting: +// +// 'trunc' is easy to implement in hardware (strip the signs, +// compute, reapply the signs), thus is commonly defined +// by many languages (including C99) +// +// 'floor' is simple to define and better behaved than trunc; +// for example it divides integers into fixed-size buckets +// without an extra-wide bucket at 0, and for a fixed +// divisor N there are only |N| possible moduli. +// +// 'eucl' guarantees fixed-sized buckets *and* a non-negative +// modulus and defines division to be whatever is needed +// to achieve that result. +// +// See "The Euclidean definition of the functions div and mod" +// by Raymond Boute (1992), or "Division and Modulus for Computer +// Scientists" by Daan Leijen (2001) +// +// We assume of the built-in C division: +// (a) modulus is the remainder for the corresponding division +// (b) a/b truncates if a and b are the same sign +// +// Property (a) requires (a/b)*b + (a%b)==a, and is required by C. +// Property (b) seems to be true of all hardware but is *not* satisfied +// by the euclidean division operator we define, so it's possibly not +// always true. If any such platform turns up, we can add more cases. +// (Possibly only stb_div_trunc currently relies on property (b).) +// +// LICENSE +// +// See end of file for license information. + + +#ifndef INCLUDE_STB_DIVIDE_H +#define INCLUDE_STB_DIVIDE_H + +#ifdef __cplusplus +extern "C" { +#endif + +extern int stb_div_trunc(int value_to_be_divided, int value_to_divide_by); +extern int stb_div_floor(int value_to_be_divided, int value_to_divide_by); +extern int stb_div_eucl (int value_to_be_divided, int value_to_divide_by); +extern int stb_mod_trunc(int value_to_be_divided, int value_to_divide_by); +extern int stb_mod_floor(int value_to_be_divided, int value_to_divide_by); +extern int stb_mod_eucl (int value_to_be_divided, int value_to_divide_by); + +#ifdef __cplusplus +} +#endif + +#ifdef STB_DIVIDE_IMPLEMENTATION + +#if defined(__STDC_VERSION) && __STDC_VERSION__ >= 19901 + #ifndef C_INTEGER_DIVISION_TRUNCATES + #define C_INTEGER_DIVISION_TRUNCATES + #endif +#endif + +#ifndef INT_MIN +#include // if you have no limits.h, #define INT_MIN yourself +#endif + +// the following macros are designed to allow testing +// other platforms by simulating them +#ifndef STB_DIVIDE_TEST_FLOOR + #define stb__div(a,b) ((a)/(b)) + #define stb__mod(a,b) ((a)%(b)) +#else + // implement floor-style divide on trunc platform + #ifndef C_INTEGER_DIVISION_TRUNCATES + #error "floor test requires truncating division" + #endif + #undef C_INTEGER_DIVISION_TRUNCATES + int stb__div(int v1, int v2) + { + int q = v1/v2, r = v1%v2; + if ((r > 0 && v2 < 0) || (r < 0 && v2 > 0)) + return q-1; + else + return q; + } + + int stb__mod(int v1, int v2) + { + int r = v1%v2; + if ((r > 0 && v2 < 0) || (r < 0 && v2 > 0)) + return r+v2; + else + return r; + } +#endif + +int stb_div_trunc(int v1, int v2) +{ + #ifdef C_INTEGER_DIVISION_TRUNCATES + return v1/v2; + #else + if (v1 >= 0 && v2 <= 0) + return -stb__div(-v1,v2); // both negative to avoid overflow + if (v1 <= 0 && v2 >= 0) + if (v1 != INT_MIN) + return -stb__div(v1,-v2); // both negative to avoid overflow + else + return -stb__div(v1+v2,-v2)-1; // push v1 away from wrap point + else + return v1/v2; // same sign, so expect truncation + #endif +} + +int stb_div_floor(int v1, int v2) +{ + #ifdef C_INTEGER_DIVISION_FLOORS + return v1/v2; + #else + if (v1 >= 0 && v2 < 0) { + if (v2 + 1 >= INT_MIN + v1) // check if increasing v1's magnitude overflows + return -stb__div((v2+1)-v1,v2); // nope, so just compute it + else + return -stb__div(-v1,v2) + ((-v1)%v2 ? -1 : 0); + } + if (v1 < 0 && v2 >= 0) { + if (v1 != INT_MIN) { + if (v1 + 1 >= INT_MIN + v2) // check if increasing v1's magnitude overflows + return -stb__div((v1+1)-v2,-v2); // nope, so just compute it + else + return -stb__div(-v1,v2) + (stb__mod(v1,-v2) ? -1 : 0); + } else // it must be possible to compute -(v1+v2) without overflowing + return -stb__div(-(v1+v2),v2) + (stb__mod(-(v1+v2),v2) ? -2 : -1); + } else + return v1/v2; // same sign, so expect truncation + #endif +} + +int stb_div_eucl(int v1, int v2) +{ + int q,r; + #ifdef C_INTEGER_DIVISION_TRUNCATES + q = v1/v2; + r = v1%v2; + #else + // handle every quadrant separately, since we can't rely on q and r flor + if (v1 >= 0) + if (v2 >= 0) + return stb__div(v1,v2); + else if (v2 != INT_MIN) + q = -stb__div(v1,-v2), r = stb__mod(v1,-v2); + else + q = 0, r = v1; + else if (v1 != INT_MIN) + if (v2 >= 0) + q = -stb__div(-v1,v2), r = -stb__mod(-v1,v2); + else if (v2 != INT_MIN) + q = stb__div(-v1,-v2), r = -stb__mod(-v1,-v2); + else // if v2 is INT_MIN, then we can't use -v2, but we can't divide by v2 + q = 1, r = v1-q*v2; + else // if v1 is INT_MIN, we have to move away from overflow place + if (v2 >= 0) + q = -stb__div(-(v1+v2),v2)-1, r = -stb__mod(-(v1+v2),v2); + else if (v2 != INT_MIN) + q = stb__div(-(v1-v2),-v2)+1, r = -stb__mod(-(v1-v2),-v2); + else // for INT_MIN / INT_MIN, we need to be extra-careful to avoid overflow + q = 1, r = 0; + #endif + if (r >= 0) + return q; + else + return q + (v2 > 0 ? -1 : 1); +} + +int stb_mod_trunc(int v1, int v2) +{ + #ifdef C_INTEGER_DIVISION_TRUNCATES + return v1%v2; + #else + if (v1 >= 0) { // modulus result should always be positive + int r = stb__mod(v1,v2); + if (r >= 0) + return r; + else + return r - (v2 < 0 ? v2 : -v2); + } else { // modulus result should always be negative + int r = stb__mod(v1,v2); + if (r <= 0) + return r; + else + return r + (v2 < 0 ? v2 : -v2); + } + #endif +} + +int stb_mod_floor(int v1, int v2) +{ + #ifdef C_INTEGER_DIVISION_FLOORS + return v1%v2; + #else + if (v2 >= 0) { // result should always be positive + int r = stb__mod(v1,v2); + if (r >= 0) + return r; + else + return r + v2; + } else { // result should always be negative + int r = stb__mod(v1,v2); + if (r <= 0) + return r; + else + return r + v2; + } + #endif +} + +int stb_mod_eucl(int v1, int v2) +{ + int r = stb__mod(v1,v2); + + if (r >= 0) + return r; + else + return r - (v2 < 0 ? v2 : -v2); // negative abs() [to avoid overflow] +} + +#ifdef STB_DIVIDE_TEST +#include +#include +#include + +int show=0; +int err=0; + +void stbdiv_check(int q, int r, int a, int b, char *type, int dir) +{ + if ((dir > 0 && r < 0) || (dir < 0 && r > 0)) { + fprintf(stderr, "FAILED: %s(%d,%d) remainder %d in wrong direction\n", type,a,b,r); + err++; + } else + if (b != INT_MIN) // can't compute abs(), but if b==INT_MIN all remainders are valid + if (r <= -abs(b) || r >= abs(b)) { + fprintf(stderr, "FAILED: %s(%d,%d) remainder %d out of range\n", type,a,b,r); + err++; + } + #ifdef STB_DIVIDE_TEST_64 + { + STB_DIVIDE_TEST_64 q64 = q, r64=r, a64=a, b64=b; + if (q64*b64+r64 != a64) { + fprintf(stderr, "FAILED: %s(%d,%d) remainder %d doesn't match quotient %d\n", type,a,b,r,q); + err++; + } + } + #else + if (q*b+r != a) { + fprintf(stderr, "FAILED: %s(%d,%d) remainder %d doesn't match quotient %d\n", type,a,b,r,q); + err++; + } + #endif +} + +void test(int a, int b) +{ + int q,r; + if (show) printf("(%+11d,%+d) | ", a,b); + q = stb_div_trunc(a,b), r = stb_mod_trunc(a,b); + if (show) printf("(%+11d,%+2d) ", q,r); stbdiv_check(q,r,a,b, "trunc",a); + q = stb_div_floor(a,b), r = stb_mod_floor(a,b); + if (show) printf("(%+11d,%+2d) ", q,r); stbdiv_check(q,r,a,b, "floor",b); + q = stb_div_eucl (a,b), r = stb_mod_eucl (a,b); + if (show) printf("(%+11d,%+2d)\n", q,r); stbdiv_check(q,r,a,b, "euclidean",1); +} + +void testh(int a, int b) +{ + int q,r; + if (show) printf("(%08x,%08x) |\n", a,b); + q = stb_div_trunc(a,b), r = stb_mod_trunc(a,b); stbdiv_check(q,r,a,b, "trunc",a); + if (show) printf(" (%08x,%08x)", q,r); + q = stb_div_floor(a,b), r = stb_mod_floor(a,b); stbdiv_check(q,r,a,b, "floor",b); + if (show) printf(" (%08x,%08x)", q,r); + q = stb_div_eucl (a,b), r = stb_mod_eucl (a,b); stbdiv_check(q,r,a,b, "euclidean",1); + if (show) printf(" (%08x,%08x)\n ", q,r); +} + +int main(int argc, char **argv) +{ + if (argc > 1) show=1; + + test(8,3); + test(8,-3); + test(-8,3); + test(-8,-3); + test(1,2); + test(1,-2); + test(-1,2); + test(-1,-2); + test(8,4); + test(8,-4); + test(-8,4); + test(-8,-4); + + test(INT_MAX,1); + test(INT_MIN,1); + test(INT_MIN+1,1); + test(INT_MAX,-1); + //test(INT_MIN,-1); // this traps in MSVC, so we leave it untested + test(INT_MIN+1,-1); + test(INT_MIN,-2); + test(INT_MIN+1,2); + test(INT_MIN+1,-2); + test(INT_MAX,2); + test(INT_MAX,-2); + test(INT_MIN+1,2); + test(INT_MIN+1,-2); + test(INT_MIN,2); + test(INT_MIN,-2); + test(INT_MIN,7); + test(INT_MIN,-7); + test(INT_MIN+1,4); + test(INT_MIN+1,-4); + + testh(-7, INT_MIN); + testh(-1, INT_MIN); + testh(1, INT_MIN); + testh(7, INT_MIN); + + testh(INT_MAX-1, INT_MIN); + testh(INT_MAX, INT_MIN); + testh(INT_MIN, INT_MIN); + testh(INT_MIN+1, INT_MIN); + + testh(INT_MAX-1, INT_MAX); + testh(INT_MAX , INT_MAX); + testh(INT_MIN , INT_MAX); + testh(INT_MIN+1, INT_MAX); + + return err > 0 ? 1 : 0; +} +#endif // STB_DIVIDE_TEST +#endif // STB_DIVIDE_IMPLEMENTATION +#endif // INCLUDE_STB_DIVIDE_H + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_ds.h b/source/engine/thirdparty/stb/stb_ds.h new file mode 100644 index 0000000..e84c82d --- /dev/null +++ b/source/engine/thirdparty/stb/stb_ds.h @@ -0,0 +1,1895 @@ +/* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019 + + This is a single-header-file library that provides easy-to-use + dynamic arrays and hash tables for C (also works in C++). + + For a gentle introduction: + http://nothings.org/stb_ds + + To use this library, do this in *one* C or C++ file: + #define STB_DS_IMPLEMENTATION + #include "stb_ds.h" + +TABLE OF CONTENTS + + Table of Contents + Compile-time options + License + Documentation + Notes + Notes - Dynamic arrays + Notes - Hash maps + Credits + +COMPILE-TIME OPTIONS + + #define STBDS_NO_SHORT_NAMES + + This flag needs to be set globally. + + By default stb_ds exposes shorter function names that are not qualified + with the "stbds_" prefix. If these names conflict with the names in your + code, define this flag. + + #define STBDS_SIPHASH_2_4 + + This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for + 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force + stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes + hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on + 64-byte keys, and 10% slower on 256-byte keys on my test computer. + + #define STBDS_REALLOC(context,ptr,size) better_realloc + #define STBDS_FREE(context,ptr) better_free + + These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds uses stdlib realloc() and free() for memory management. You can + substitute your own functions instead by defining these symbols. You must either + define both, or neither. Note that at the moment, 'context' will always be NULL. + @TODO add an array/hash initialization function that takes a memory context pointer. + + #define STBDS_UNIT_TESTS + + Defines a function stbds_unit_tests() that checks the functioning of the data structures. + + Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x' + (or equivalentally '-std=c++11') when using anonymous structures as seen on the web + page or in STBDS_UNIT_TESTS. + +LICENSE + + Placed in the public domain and also MIT licensed. + See end of file for detailed license information. + +DOCUMENTATION + + Dynamic Arrays + + Non-function interface: + + Declare an empty dynamic array of type T + T* foo = NULL; + + Access the i'th item of a dynamic array 'foo' of type T, T* foo: + foo[i] + + Functions (actually macros) + + arrfree: + void arrfree(T*); + Frees the array. + + arrlen: + ptrdiff_t arrlen(T*); + Returns the number of elements in the array. + + arrlenu: + size_t arrlenu(T*); + Returns the number of elements in the array as an unsigned type. + + arrpop: + T arrpop(T* a) + Removes the final element of the array and returns it. + + arrput: + T arrput(T* a, T b); + Appends the item b to the end of array a. Returns b. + + arrins: + T arrins(T* a, int p, T b); + Inserts the item b into the middle of array a, into a[p], + moving the rest of the array over. Returns b. + + arrinsn: + void arrinsn(T* a, int p, int n); + Inserts n uninitialized items into array a starting at a[p], + moving the rest of the array over. + + arraddnptr: + T* arraddnptr(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns a pointer to the first uninitialized item added. + + arraddnindex: + size_t arraddnindex(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns the index of the first uninitialized item added. + + arrdel: + void arrdel(T* a, int p); + Deletes the element at a[p], moving the rest of the array over. + + arrdeln: + void arrdeln(T* a, int p, int n); + Deletes n elements starting at a[p], moving the rest of the array over. + + arrdelswap: + void arrdelswap(T* a, int p); + Deletes the element at a[p], replacing it with the element from + the end of the array. O(1) performance. + + arrsetlen: + void arrsetlen(T* a, int n); + Changes the length of the array to n. Allocates uninitialized + slots at the end if necessary. + + arrsetcap: + size_t arrsetcap(T* a, int n); + Sets the length of allocated storage to at least n. It will not + change the length of the array. + + arrcap: + size_t arrcap(T* a); + Returns the number of total elements the array can contain without + needing to be reallocated. + + Hash maps & String hash maps + + Given T is a structure type: struct { TK key; TV value; }. Note that some + functions do not require TV value and can have other fields. For string + hash maps, TK must be 'char *'. + + Special interface: + + stbds_rand_seed: + void stbds_rand_seed(size_t seed); + For security against adversarially chosen data, you should seed the + library with a strong random number. Or at least seed it with time(). + + stbds_hash_string: + size_t stbds_hash_string(char *str, size_t seed); + Returns a hash value for a string. + + stbds_hash_bytes: + size_t stbds_hash_bytes(void *p, size_t len, size_t seed); + These functions hash an arbitrary number of bytes. The function + uses a custom hash for 4- and 8-byte data, and a weakened version + of SipHash for everything else. On 64-bit platforms you can get + specification-compliant SipHash-2-4 on all data by defining + STBDS_SIPHASH_2_4, at a significant cost in speed. + + Non-function interface: + + Declare an empty hash map of type T + T* foo = NULL; + + Access the i'th entry in a hash table T* foo: + foo[i] + + Function interface (actually macros): + + hmfree + shfree + void hmfree(T*); + void shfree(T*); + Frees the hashmap and sets the pointer to NULL. + + hmlen + shlen + ptrdiff_t hmlen(T*) + ptrdiff_t shlen(T*) + Returns the number of elements in the hashmap. + + hmlenu + shlenu + size_t hmlenu(T*) + size_t shlenu(T*) + Returns the number of elements in the hashmap. + + hmgeti + shgeti + hmgeti_ts + ptrdiff_t hmgeti(T*, TK key) + ptrdiff_t shgeti(T*, char* key) + ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) + Returns the index in the hashmap which has the key 'key', or -1 + if the key is not present. + + hmget + hmget_ts + shget + TV hmget(T*, TK key) + TV shget(T*, char* key) + TV hmget_ts(T*, TK key, ptrdiff_t tempvar) + Returns the value corresponding to 'key' in the hashmap. + The structure must have a 'value' field + + hmgets + shgets + T hmgets(T*, TK key) + T shgets(T*, char* key) + Returns the structure corresponding to 'key' in the hashmap. + + hmgetp + shgetp + hmgetp_ts + hmgetp_null + shgetp_null + T* hmgetp(T*, TK key) + T* shgetp(T*, char* key) + T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) + T* hmgetp_null(T*, TK key) + T* shgetp_null(T*, char *key) + Returns a pointer to the structure corresponding to 'key' in + the hashmap. Functions ending in "_null" return NULL if the key + is not present in the hashmap; the others return a pointer to a + structure holding the default value (but not the searched-for key). + + hmdefault + shdefault + TV hmdefault(T*, TV value) + TV shdefault(T*, TV value) + Sets the default value for the hashmap, the value which will be + returned by hmget/shget if the key is not present. + + hmdefaults + shdefaults + TV hmdefaults(T*, T item) + TV shdefaults(T*, T item) + Sets the default struct for the hashmap, the contents which will be + returned by hmgets/shgets if the key is not present. + + hmput + shput + TV hmput(T*, TK key, TV value) + TV shput(T*, char* key, TV value) + Inserts a pair into the hashmap. If the key is already + present in the hashmap, updates its value. + + hmputs + shputs + T hmputs(T*, T item) + T shputs(T*, T item) + Inserts a struct with T.key into the hashmap. If the struct is already + present in the hashmap, updates it. + + hmdel + shdel + int hmdel(T*, TK key) + int shdel(T*, char* key) + If 'key' is in the hashmap, deletes its entry and returns 1. + Otherwise returns 0. + + Function interface (actually macros) for strings only: + + sh_new_strdup + void sh_new_strdup(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate and free + each string key using realloc/free + + sh_new_arena + void sh_new_arena(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate each string + key to a string arena. Every string key ever used by this + hash table remains in the arena until the arena is freed. + Additionally, any key which is deleted and reinserted will + be allocated multiple times in the string arena. + +NOTES + + * These data structures are realloc'd when they grow, and the macro + "functions" write to the provided pointer. This means: (a) the pointer + must be an lvalue, and (b) the pointer to the data structure is not + stable, and you must maintain it the same as you would a realloc'd + pointer. For example, if you pass a pointer to a dynamic array to a + function which updates it, the function must return back the new + pointer to the caller. This is the price of trying to do this in C. + + * The following are the only functions that are thread-safe on a single data + structure, i.e. can be run in multiple threads simultaneously on the same + data structure + hmlen shlen + hmlenu shlenu + hmget_ts shget_ts + hmgeti_ts shgeti_ts + hmgets_ts shgets_ts + + * You iterate over the contents of a dynamic array and a hashmap in exactly + the same way, using arrlen/hmlen/shlen: + + for (i=0; i < arrlen(foo); ++i) + ... foo[i] ... + + * All operations except arrins/arrdel are O(1) amortized, but individual + operations can be slow, so these data structures may not be suitable + for real time use. Dynamic arrays double in capacity as needed, so + elements are copied an average of once. Hash tables double/halve + their size as needed, with appropriate hysteresis to maintain O(1) + performance. + +NOTES - DYNAMIC ARRAY + + * If you know how long a dynamic array is going to be in advance, you can avoid + extra memory allocations by using arrsetlen to allocate it to that length in + advance and use foo[n] while filling it out, or arrsetcap to allocate the memory + for that length and use arrput/arrpush as normal. + + * Unlike some other versions of the dynamic array, this version should + be safe to use with strict-aliasing optimizations. + +NOTES - HASH MAP + + * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel + and variants, the key must be an lvalue (so the macro can take the address of it). + Extensions are used that eliminate this requirement if you're using C99 and later + in GCC or clang, or if you're using C++ in GCC. But note that this can make your + code less portable. + + * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'. + + * The iteration order of your data in the hashmap is determined solely by the + order of insertions and deletions. In particular, if you never delete, new + keys are always added at the end of the array. This will be consistent + across all platforms and versions of the library. However, you should not + attempt to serialize the internal hash table, as the hash is not consistent + between different platforms, and may change with future versions of the library. + + * Use sh_new_arena() for string hashmaps that you never delete from. Initialize + with NULL if you're managing the memory for your strings, or your strings are + never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). + @TODO: make an arena variant that garbage collects the strings with a trivial + copy collector into a new arena whenever the table shrinks / rebuilds. Since + current arena recommendation is to only use arena if it never deletes, then + this can just replace current arena implementation. + + * If adversarial input is a serious concern and you're on a 64-bit platform, + enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass + a strong random number to stbds_rand_seed. + + * The default value for the hash table is stored in foo[-1], so if you + use code like 'hmget(T,k)->value = 5' you can accidentally overwrite + the value stored by hmdefault if 'k' is not present. + +CREDITS + + Sean Barrett -- library, idea for dynamic array API/implementation + Per Vognsen -- idea for hash table API/implementation + Rafael Sachetto -- arrpop() + github:HeroicKatora -- arraddn() reworking + + Bugfixes: + Andy Durdin + Shane Liesegang + Vinh Truong + Andreas Molzer + github:hashitaku + github:srdjanstipic + Macoy Madson + Andreas Vennstrom + Tobias Mansfield-Williams +*/ + +#ifdef STBDS_UNIT_TESTS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef INCLUDE_STB_DS_H +#define INCLUDE_STB_DS_H + +#include +#include + +#ifndef STBDS_NO_SHORT_NAMES +#define arrlen stbds_arrlen +#define arrlenu stbds_arrlenu +#define arrput stbds_arrput +#define arrpush stbds_arrput +#define arrpop stbds_arrpop +#define arrfree stbds_arrfree +#define arraddn stbds_arraddn // deprecated, use one of the following instead: +#define arraddnptr stbds_arraddnptr +#define arraddnindex stbds_arraddnindex +#define arrsetlen stbds_arrsetlen +#define arrlast stbds_arrlast +#define arrins stbds_arrins +#define arrinsn stbds_arrinsn +#define arrdel stbds_arrdel +#define arrdeln stbds_arrdeln +#define arrdelswap stbds_arrdelswap +#define arrcap stbds_arrcap +#define arrsetcap stbds_arrsetcap + +#define hmput stbds_hmput +#define hmputs stbds_hmputs +#define hmget stbds_hmget +#define hmget_ts stbds_hmget_ts +#define hmgets stbds_hmgets +#define hmgetp stbds_hmgetp +#define hmgetp_ts stbds_hmgetp_ts +#define hmgetp_null stbds_hmgetp_null +#define hmgeti stbds_hmgeti +#define hmgeti_ts stbds_hmgeti_ts +#define hmdel stbds_hmdel +#define hmlen stbds_hmlen +#define hmlenu stbds_hmlenu +#define hmfree stbds_hmfree +#define hmdefault stbds_hmdefault +#define hmdefaults stbds_hmdefaults + +#define shput stbds_shput +#define shputi stbds_shputi +#define shputs stbds_shputs +#define shget stbds_shget +#define shgeti stbds_shgeti +#define shgets stbds_shgets +#define shgetp stbds_shgetp +#define shgetp_null stbds_shgetp_null +#define shdel stbds_shdel +#define shlen stbds_shlen +#define shlenu stbds_shlenu +#define shfree stbds_shfree +#define shdefault stbds_shdefault +#define shdefaults stbds_shdefaults +#define sh_new_arena stbds_sh_new_arena +#define sh_new_strdup stbds_sh_new_strdup + +#define stralloc stbds_stralloc +#define strreset stbds_strreset +#endif + +#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE) +#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither." +#endif +#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE) +#include +#define STBDS_REALLOC(c,p,s) realloc(p,s) +#define STBDS_FREE(c,p) free(p) +#endif + +#ifdef _MSC_VER +#define STBDS_NOTUSED(v) (void)(v) +#else +#define STBDS_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// for security against attackers, seed the library with a random number, at least time() but stronger is better +extern void stbds_rand_seed(size_t seed); + +// these are the hash functions used internally if you want to test them or use them for other purposes +extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed); +extern size_t stbds_hash_string(char *str, size_t seed); + +// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'. +typedef struct stbds_string_arena stbds_string_arena; +extern char * stbds_stralloc(stbds_string_arena *a, char *str); +extern void stbds_strreset(stbds_string_arena *a); + +// have to #define STBDS_UNIT_TESTS to call this +extern void stbds_unit_tests(void); + +/////////////// +// +// Everything below here is implementation details +// + +extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap); +extern void stbds_arrfreef(void *a); +extern void stbds_hmfree_func(void *p, size_t elemsize); +extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode); +extern void * stbds_hmput_default(void *a, size_t elemsize); +extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode); +extern void * stbds_shmode_func(size_t elemsize, int mode); + +#ifdef __cplusplus +} +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define STBDS_HAS_TYPEOF +#ifdef __cplusplus +//#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang +#endif +#endif + +#if !defined(__cplusplus) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define STBDS_HAS_LITERAL_ARRAY +#endif +#endif + +// this macro takes the address of the argument, but on gcc/clang can accept rvalues +#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF) + #if __clang__ + #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value + #else + #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value + #endif +#else +#define STBDS_ADDRESSOF(typevar, value) &(value) +#endif + +#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var)) + +#define stbds_header(t) ((stbds_array_header *) (t) - 1) +#define stbds_temp(t) stbds_header(t)->temp +#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table) + +#define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n)) +#define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0) +#define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0) +#define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0) +#define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0) +#define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v)) +#define stbds_arrpush stbds_arrput // synonym +#define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length]) +#define stbds_arraddn(a,n) ((void)(stbds_arraddnindex(a, n))) // deprecated, use one of the following instead: +#define stbds_arraddnptr(a,n) (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a)) +#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a)) +#define stbds_arraddnoff stbds_arraddnindex +#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1]) +#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL) +#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1) +#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n)) +#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1) +#define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i)))) +#define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v)) + +#define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \ + ? (stbds_arrgrow(a,n,0),0) : 0) + +#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) + +#define stbds_hmput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ + (t)[stbds_temp((t)-1)].key = (k), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_hmputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ + (t)[stbds_temp((t)-1)] = (s)) + +#define stbds_hmgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ + stbds_temp((t)-1)) + +#define stbds_hmgeti_ts(t,k,temp) \ + ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ + (temp)) + +#define stbds_hmgetp(t, k) \ + ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_hmgetp_ts(t, k, temp) \ + ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) + +#define stbds_hmdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) + +#define stbds_hmdefault(t, v) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) + +#define stbds_hmdefaults(t, s) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) + +#define stbds_hmfree(p) \ + ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) + +#define stbds_hmgets(t, k) (*stbds_hmgetp(t,k)) +#define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value) +#define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value) +#define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0) +#define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0) +#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) + +#define stbds_shput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_shputi(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) + +#define stbds_shputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)] = (s), \ + (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally + +#define stbds_pshput(t, p) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ + (t)[stbds_temp((t)-1)] = (p)) + +#define stbds_shgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + stbds_temp((t)-1)) + +#define stbds_pshgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ + stbds_temp((t)-1)) + +#define stbds_shgetp(t, k) \ + ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_pshget(t, k) \ + ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) + +#define stbds_shdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) +#define stbds_pshdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) + +#define stbds_sh_new_arena(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) +#define stbds_sh_new_strdup(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) + +#define stbds_shdefault(t, v) stbds_hmdefault(t,v) +#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s) + +#define stbds_shfree stbds_hmfree +#define stbds_shlenu stbds_hmlenu + +#define stbds_shgets(t, k) (*stbds_shgetp(t,k)) +#define stbds_shget(t, k) (stbds_shgetp(t,k)->value) +#define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) +#define stbds_shlen stbds_hmlen + +typedef struct +{ + size_t length; + size_t capacity; + void * hash_table; + ptrdiff_t temp; +} stbds_array_header; + +typedef struct stbds_string_block +{ + struct stbds_string_block *next; + char storage[8]; +} stbds_string_block; + +struct stbds_string_arena +{ + stbds_string_block *storage; + size_t remaining; + unsigned char block; + unsigned char mode; // this isn't used by the string arena itself +}; + +#define STBDS_HM_BINARY 0 +#define STBDS_HM_STRING 1 + +enum +{ + STBDS_SH_NONE, + STBDS_SH_DEFAULT, + STBDS_SH_STRDUP, + STBDS_SH_ARENA +}; + +#ifdef __cplusplus +// in C we use implicit assignment from these void*-returning functions to T*. +// in C++ these templates make the same code work +template static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) { + return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap); +} +template static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) { + return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode); +} +template static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) { + return (T*)stbds_hmput_default((void *)a, elemsize); +} +template static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){ + return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode); +} +template static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) { + return (T*)stbds_shmode_func(elemsize, mode); +} +#else +#define stbds_arrgrowf_wrapper stbds_arrgrowf +#define stbds_hmget_key_wrapper stbds_hmget_key +#define stbds_hmget_key_ts_wrapper stbds_hmget_key_ts +#define stbds_hmput_default_wrapper stbds_hmput_default +#define stbds_hmput_key_wrapper stbds_hmput_key +#define stbds_hmdel_key_wrapper stbds_hmdel_key +#define stbds_shmode_func_wrapper(t,e,m) stbds_shmode_func(e,m) +#endif + +#endif // INCLUDE_STB_DS_H + + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// + +#ifdef STB_DS_IMPLEMENTATION +#include +#include + +#ifndef STBDS_ASSERT +#define STBDS_ASSERT_WAS_UNDEFINED +#define STBDS_ASSERT(x) ((void) 0) +#endif + +#ifdef STBDS_STATISTICS +#define STBDS_STATS(x) x +size_t stbds_array_grow; +size_t stbds_hash_grow; +size_t stbds_hash_shrink; +size_t stbds_hash_rebuild; +size_t stbds_hash_probes; +size_t stbds_hash_alloc; +size_t stbds_rehash_probes; +size_t stbds_rehash_items; +#else +#define STBDS_STATS(x) +#endif + +// +// stbds_arr implementation +// + +//int *prev_allocs[65536]; +//int num_prev; + +void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) +{ + stbds_array_header temp={0}; // force debugging + void *b; + size_t min_len = stbds_arrlen(a) + addlen; + (void) sizeof(temp); + + // compute the minimum capacity needed + if (min_len > min_cap) + min_cap = min_len; + + if (min_cap <= stbds_arrcap(a)) + return a; + + // increase needed capacity to guarantee O(1) amortized + if (min_cap < 2 * stbds_arrcap(a)) + min_cap = 2 * stbds_arrcap(a); + else if (min_cap < 4) + min_cap = 4; + + //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1); + //if (num_prev == 2201) + // num_prev = num_prev; + b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header)); + //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b; + b = (char *) b + sizeof(stbds_array_header); + if (a == NULL) { + stbds_header(b)->length = 0; + stbds_header(b)->hash_table = 0; + stbds_header(b)->temp = 0; + } else { + STBDS_STATS(++stbds_array_grow); + } + stbds_header(b)->capacity = min_cap; + + return b; +} + +void stbds_arrfreef(void *a) +{ + STBDS_FREE(NULL, stbds_header(a)); +} + +// +// stbds_hm hash table implementation +// + +#ifdef STBDS_INTERNAL_SMALL_BUCKET +#define STBDS_BUCKET_LENGTH 4 +#else +#define STBDS_BUCKET_LENGTH 8 +#endif + +#define STBDS_BUCKET_SHIFT (STBDS_BUCKET_LENGTH == 8 ? 3 : 2) +#define STBDS_BUCKET_MASK (STBDS_BUCKET_LENGTH-1) +#define STBDS_CACHE_LINE_SIZE 64 + +#define STBDS_ALIGN_FWD(n,a) (((n) + (a) - 1) & ~((a)-1)) + +typedef struct +{ + size_t hash [STBDS_BUCKET_LENGTH]; + ptrdiff_t index[STBDS_BUCKET_LENGTH]; +} stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line + +typedef struct +{ + char * temp_key; // this MUST be the first field of the hash table + size_t slot_count; + size_t used_count; + size_t used_count_threshold; + size_t used_count_shrink_threshold; + size_t tombstone_count; + size_t tombstone_count_threshold; + size_t seed; + size_t slot_count_log2; + stbds_string_arena string; + stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct +} stbds_hash_index; + +#define STBDS_INDEX_EMPTY -1 +#define STBDS_INDEX_DELETED -2 +#define STBDS_INDEX_IN_USE(x) ((x) >= 0) + +#define STBDS_HASH_EMPTY 0 +#define STBDS_HASH_DELETED 1 + +static size_t stbds_hash_seed=0x31415926; + +void stbds_rand_seed(size_t seed) +{ + stbds_hash_seed = seed; +} + +#define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo) \ + temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */ \ + var = v64_hi, var <<= 16, var <<= 16, /* discard if 32-bit */ \ + var ^= temp ^ v32 + +#define STBDS_SIZE_T_BITS ((sizeof (size_t)) * 8) + +static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2) +{ + size_t pos; + STBDS_NOTUSED(slot_log2); + pos = hash & (slot_count-1); + #ifdef STBDS_INTERNAL_BUCKET_START + pos &= ~STBDS_BUCKET_MASK; + #endif + return pos; +} + +static size_t stbds_log2(size_t slot_count) +{ + size_t n=0; + while (slot_count > 1) { + slot_count >>= 1; + ++n; + } + return n; +} + +static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot) +{ + stbds_hash_index *t; + t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1); + t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE); + t->slot_count = slot_count; + t->slot_count_log2 = stbds_log2(slot_count); + t->tombstone_count = 0; + t->used_count = 0; + + #if 0 // A1 + t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + #elif 1 // A2 + //t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + //t->tombstone_count_threshold = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild + //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + + // compute without overflowing + t->used_count_threshold = slot_count - (slot_count>>2); + t->tombstone_count_threshold = (slot_count>>3) + (slot_count>>4); + t->used_count_shrink_threshold = slot_count >> 2; + + #elif 0 // B1 + t->used_count_threshold = slot_count*13/16; // if 13/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink + #else // C1 + t->used_count_threshold = slot_count*14/16; // if 14/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // A1 A2 B1 C1 + // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table + // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table + // 14.48ms : 14.46ms : 10.63ms : 11.00ms : 200,000 inserts creating 200K table + // 195.74ms : 196.35ms : 203.69ms : 214.92ms : 2,000,000 inserts creating 2M table + // 2193.88ms : 2209.22ms : 2285.54ms : 2437.17ms : 20,000,000 inserts creating 20M table + // 65.27ms : 53.77ms : 65.33ms : 65.47ms : 500,000 inserts & deletes in 2K table + // 72.78ms : 62.45ms : 71.95ms : 72.85ms : 500,000 inserts & deletes in 20K table + // 89.47ms : 77.72ms : 96.49ms : 96.75ms : 500,000 inserts & deletes in 200K table + // 97.58ms : 98.14ms : 97.18ms : 97.53ms : 500,000 inserts & deletes in 2M table + // 118.61ms : 119.62ms : 120.16ms : 118.86ms : 500,000 inserts & deletes in 20M table + // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table + + if (slot_count <= STBDS_BUCKET_LENGTH) + t->used_count_shrink_threshold = 0; + // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes + STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count); + STBDS_STATS(++stbds_hash_alloc); + if (ot) { + t->string = ot->string; + // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing + t->seed = ot->seed; + } else { + size_t a,b,temp; + memset(&t->string, 0, sizeof(t->string)); + t->seed = stbds_hash_seed; + // LCG + // in 32-bit, a = 2147001325 b = 715136305 + // in 64-bit, a = 2862933555777941757 b = 3037000493 + stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); + stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); + stbds_hash_seed = stbds_hash_seed * a + b; + } + + { + size_t i,j; + for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *b = &t->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->hash[j] = STBDS_HASH_EMPTY; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->index[j] = STBDS_INDEX_EMPTY; + } + } + + // copy out the old data, if any + if (ot) { + size_t i,j; + t->used_count = ot->used_count; + for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *ob = &ot->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { + if (STBDS_INDEX_IN_USE(ob->index[j])) { + size_t hash = ob->hash[j]; + size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); + size_t step = STBDS_BUCKET_LENGTH; + STBDS_STATS(++stbds_rehash_items); + for (;;) { + size_t limit,z; + stbds_hash_bucket *bucket; + bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; + STBDS_STATS(++stbds_rehash_probes); + + for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + limit = pos & STBDS_BUCKET_MASK; + for (z = 0; z < limit; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + pos += step; // quadratic probing + step += STBDS_BUCKET_LENGTH; + pos &= (t->slot_count-1); + } + } + done: + ; + } + } + } + + return t; +} + +#define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n)))) +#define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n)))) + +size_t stbds_hash_string(char *str, size_t seed) +{ + size_t hash = seed; + while (*str) + hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; + + // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits + hash ^= seed; + hash = (~hash) + (hash << 18); + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31); + hash = hash * 21; + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11); + hash += (hash << 6); + hash ^= STBDS_ROTATE_RIGHT(hash,22); + return hash+seed; +} + +#ifdef STBDS_SIPHASH_2_4 +#define STBDS_SIPHASH_C_ROUNDS 2 +#define STBDS_SIPHASH_D_ROUNDS 4 +typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1]; +#endif + +#ifndef STBDS_SIPHASH_C_ROUNDS +#define STBDS_SIPHASH_C_ROUNDS 1 +#endif +#ifndef STBDS_SIPHASH_D_ROUNDS +#define STBDS_SIPHASH_D_ROUNDS 1 +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()== +#endif + +static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed) +{ + unsigned char *d = (unsigned char *) p; + size_t i,j; + size_t v0,v1,v2,v3, data; + + // hash that works on 32- or 64-bit registers without knowing which we have + // (computes different results on 32-bit and 64-bit platform) + // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit + v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^ seed; + v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed; + v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^ seed; + v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed; + + #ifdef STBDS_TEST_SIPHASH_2_4 + // hardcoded with key material in the siphash test vectors + v0 ^= 0x0706050403020100ull ^ seed; + v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + v2 ^= 0x0706050403020100ull ^ seed; + v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + #endif + + #define STBDS_SIPROUND() \ + do { \ + v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ + v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ + v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ + v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ + } while (0) + + for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) { + data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 + + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + } + data = len << (STBDS_SIZE_T_BITS-8); + switch (len - i) { + case 7: data |= ((size_t) d[6] << 24) << 24; // fall through + case 6: data |= ((size_t) d[5] << 20) << 20; // fall through + case 5: data |= ((size_t) d[4] << 16) << 16; // fall through + case 4: data |= (d[3] << 24); // fall through + case 3: data |= (d[2] << 16); // fall through + case 2: data |= (d[1] << 8); // fall through + case 1: data |= d[0]; // fall through + case 0: break; + } + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + v2 ^= 0xff; + for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j) + STBDS_SIPROUND(); + +#ifdef STBDS_SIPHASH_2_4 + return v0^v1^v2^v3; +#else + return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply +#endif +} + +size_t stbds_hash_bytes(void *p, size_t len, size_t seed) +{ +#ifdef STBDS_SIPHASH_2_4 + return stbds_siphash_bytes(p,len,seed); +#else + unsigned char *d = (unsigned char *) p; + + if (len == 4) { + unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + #if 0 + // HASH32-A Bob Jenkin's hash function w/o large constants + hash ^= seed; + hash -= (hash<<6); + hash ^= (hash>>17); + hash -= (hash<<9); + hash ^= seed; + hash ^= (hash<<4); + hash -= (hash<<3); + hash ^= (hash<<10); + hash ^= (hash>>15); + #elif 1 + // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. + // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm + // not really sure what's going on. + hash ^= seed; + hash = (hash ^ 61) ^ (hash >> 16); + hash = hash + (hash << 3); + hash = hash ^ (hash >> 4); + hash = hash * 0x27d4eb2d; + hash ^= seed; + hash = hash ^ (hash >> 15); + #else // HASH32-C - Murmur3 + hash ^= seed; + hash *= 0xcc9e2d51; + hash = (hash << 17) | (hash >> 15); + hash *= 0x1b873593; + hash ^= seed; + hash = (hash << 19) | (hash >> 13); + hash = hash*5 + 0xe6546b64; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= seed; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // HASH32-A // HASH32-BB // HASH32-C + // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table + // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table + // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table + // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table + // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table + // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table + // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table + // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table + // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table + // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table + // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table + // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing + + return (((size_t) hash << 16 << 16) | hash) ^ seed; + } else if (len == 8 && sizeof(size_t) == 8) { + size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 + hash ^= seed; + hash = (~hash) + (hash << 21); + hash ^= STBDS_ROTATE_RIGHT(hash,24); + hash *= 265; + hash ^= STBDS_ROTATE_RIGHT(hash,14); + hash ^= seed; + hash *= 21; + hash ^= STBDS_ROTATE_RIGHT(hash,28); + hash += (hash << 31); + hash = (~hash) + (hash << 18); + return hash; + } else { + return stbds_siphash_bytes(p,len,seed); + } +#endif +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i) +{ + if (mode >= STBDS_HM_STRING) + return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); + else + return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); +} + +#define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize)) +#define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize)) + +#define stbds_hash_table(a) ((stbds_hash_index *) stbds_header(a)->hash_table) + +void stbds_hmfree_func(void *a, size_t elemsize) +{ + if (a == NULL) return; + if (stbds_hash_table(a) != NULL) { + if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { + size_t i; + // skip 0th element, which is default + for (i=1; i < stbds_header(a)->length; ++i) + STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); + } + stbds_strreset(&stbds_hash_table(a)->string); + } + STBDS_FREE(NULL, stbds_header(a)->hash_table); + STBDS_FREE(NULL, stbds_header(a)); +} + +static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + stbds_hash_index *table = stbds_hash_table(raw_a); + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t limit,i; + size_t pos; + stbds_hash_bucket *bucket; + + if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + /* NOTREACHED */ +} + +void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) +{ + size_t keyoffset = 0; + if (a == NULL) { + // make it non-empty so we can return a temp + a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + *temp = STBDS_INDEX_EMPTY; + // adjust a to point after the default element + return STBDS_ARR_TO_HASH(a,elemsize); + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + // adjust a to point to the default element + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + if (table == 0) { + *temp = -1; + } else { + ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) { + *temp = STBDS_INDEX_EMPTY; + } else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + *temp = b->index[slot & STBDS_BUCKET_MASK]; + } + } + return a; + } +} + +void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + ptrdiff_t temp; + void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode); + stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp; + return p; +} + +void * stbds_hmput_default(void *a, size_t elemsize) +{ + // three cases: + // a is NULL <- allocate + // a has a hash table but no entries, because of shmode <- grow + // a has entries <- do nothing + if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) { + a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + a=STBDS_ARR_TO_HASH(a,elemsize); + } + return a; +} + +static char *stbds_strdup(char *str); + +void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + size_t keyoffset=0; + void *raw_a; + stbds_hash_index *table; + + if (a == NULL) { + a = stbds_arrgrowf(0, elemsize, 0, 1); + memset(a, 0, elemsize); + stbds_header(a)->length += 1; + // adjust a to point AFTER the default element + a = STBDS_ARR_TO_HASH(a,elemsize); + } + + // adjust a to point to the default element + raw_a = a; + a = STBDS_HASH_TO_ARR(a,elemsize); + + table = (stbds_hash_index *) stbds_header(a)->hash_table; + + if (table == NULL || table->used_count >= table->used_count_threshold) { + stbds_hash_index *nt; + size_t slot_count; + + slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; + nt = stbds_make_hash_index(slot_count, table); + if (table) + STBDS_FREE(NULL, table); + else + nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; + stbds_header(a)->hash_table = table = nt; + STBDS_STATS(++stbds_hash_grow); + } + + // we iterate hash table explicitly because we want to track if we saw a tombstone + { + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t pos; + ptrdiff_t tombstone = -1; + stbds_hash_bucket *bucket; + + // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly + if (hash < 2) hash += 2; + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + size_t limit, i; + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + if (mode >= STBDS_HM_STRING) + stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + found_empty_slot: + if (tombstone >= 0) { + pos = tombstone; + --table->tombstone_count; + } + ++table->used_count; + + { + ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); + // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type + if ((size_t) i+1 > stbds_arrcap(a)) + *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); + raw_a = STBDS_ARR_TO_HASH(a,elemsize); + + STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); + stbds_header(a)->length = i+1; + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + bucket->hash[pos & STBDS_BUCKET_MASK] = hash; + bucket->index[pos & STBDS_BUCKET_MASK] = i-1; + stbds_temp(a) = i-1; + + switch (table->string.mode) { + case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; + case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; + case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; + default: memcpy((char *) a + elemsize*i, key, keysize); break; + } + } + return STBDS_ARR_TO_HASH(a,elemsize); + } +} + +void * stbds_shmode_func(size_t elemsize, int mode) +{ + void *a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_hash_index *h; + memset(a, 0, elemsize); + stbds_header(a)->length = 1; + stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL); + h->string.mode = (unsigned char) mode; + return STBDS_ARR_TO_HASH(a,elemsize); +} + +void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + if (a == NULL) { + return 0; + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + stbds_temp(raw_a) = 0; + if (table == 0) { + return a; + } else { + ptrdiff_t slot; + slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) + return a; + else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + int i = slot & STBDS_BUCKET_MASK; + ptrdiff_t old_index = b->index[i]; + ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' + STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); + --table->used_count; + ++table->tombstone_count; + stbds_temp(raw_a) = 1; + STBDS_ASSERT(table->used_count >= 0); + //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); + b->hash[i] = STBDS_HASH_DELETED; + b->index[i] = STBDS_INDEX_DELETED; + + if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) + STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); + + // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip + if (old_index != final_index) { + // swap delete + memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); + + // now find the slot for the last element + if (mode == STBDS_HM_STRING) + slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); + else + slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); + STBDS_ASSERT(slot >= 0); + b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + i = slot & STBDS_BUCKET_MASK; + STBDS_ASSERT(b->index[i] == final_index); + b->index[i] = old_index; + } + stbds_header(raw_a)->length -= 1; + + if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_shrink); + } else if (table->tombstone_count > table->tombstone_count_threshold) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_rebuild); + } + + return a; + } + } + } + /* NOTREACHED */ +} + +static char *stbds_strdup(char *str) +{ + // to keep replaceable allocator simple, we don't want to use strdup. + // rolling our own also avoids problem of strdup vs _strdup + size_t len = strlen(str)+1; + char *p = (char*) STBDS_REALLOC(NULL, 0, len); + memmove(p, str, len); + return p; +} + +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN +#define STBDS_STRING_ARENA_BLOCKSIZE_MIN 512u +#endif +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX +#define STBDS_STRING_ARENA_BLOCKSIZE_MAX (1u<<20) +#endif + +char *stbds_stralloc(stbds_string_arena *a, char *str) +{ + char *p; + size_t len = strlen(str)+1; + if (len > a->remaining) { + // compute the next blocksize + size_t blocksize = a->block; + + // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that + // there are log(SIZE) allocations to free when we destroy the table + blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); + + // if size is under 1M, advance to next blocktype + if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) + ++a->block; + + if (len > blocksize) { + // if string is larger than blocksize, then just allocate the full size. + // note that we still advance string_block so block size will continue + // increasing, so e.g. if somebody only calls this with 1000-long strings, + // eventually the arena will start doubling and handling those as well + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); + memmove(sb->storage, str, len); + if (a->storage) { + // insert it after the first element, so that we don't waste the space there + sb->next = a->storage->next; + a->storage->next = sb; + } else { + sb->next = 0; + a->storage = sb; + a->remaining = 0; // this is redundant, but good for clarity + } + return sb->storage; + } else { + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); + sb->next = a->storage; + a->storage = sb; + a->remaining = blocksize; + } + } + + STBDS_ASSERT(len <= a->remaining); + p = a->storage->storage + a->remaining - len; + a->remaining -= len; + memmove(p, str, len); + return p; +} + +void stbds_strreset(stbds_string_arena *a) +{ + stbds_string_block *x,*y; + x = a->storage; + while (x) { + y = x->next; + STBDS_FREE(NULL, x); + x = y; + } + memset(a, 0, sizeof(*a)); +} + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// UNIT TESTS +// + +#ifdef STBDS_UNIT_TESTS +#include +#ifdef STBDS_ASSERT_WAS_UNDEFINED +#undef STBDS_ASSERT +#endif +#ifndef STBDS_ASSERT +#define STBDS_ASSERT assert +#include +#endif + +typedef struct { int key,b,c,d; } stbds_struct; +typedef struct { int key[2],b,c,d; } stbds_struct2; + +static char buffer[256]; +char *strkey(int n) +{ +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + sprintf_s(buffer, sizeof(buffer), "test_%d", n); +#else + sprintf(buffer, "test_%d", n); +#endif + return buffer; +} + +void stbds_unit_tests(void) +{ +#if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus) + // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing! + STBDS_ASSERT(0); +#else + const int testsize = 100000; + const int testsize2 = testsize/20; + int *arr=NULL; + struct { int key; int value; } *intmap = NULL; + struct { char *key; int value; } *strmap = NULL, s; + struct { stbds_struct key; int value; } *map = NULL; + stbds_struct *map2 = NULL; + stbds_struct2 *map3 = NULL; + stbds_string_arena sa = { 0 }; + int key3[2] = { 1,2 }; + ptrdiff_t temp; + + int i,j; + + STBDS_ASSERT(arrlen(arr)==0); + for (i=0; i < 20000; i += 50) { + for (j=0; j < i; ++j) + arrpush(arr,j); + arrfree(arr); + } + + for (i=0; i < 4; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdel(arr,i); + arrfree(arr); + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdelswap(arr,i); + arrfree(arr); + } + + for (i=0; i < 5; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + stbds_arrins(arr,i,5); + STBDS_ASSERT(arr[i] == 5); + if (i < 4) + STBDS_ASSERT(arr[4] == 4); + arrfree(arr); + } + + i = 1; + STBDS_ASSERT(hmgeti(intmap,i) == -1); + hmdefault(intmap, -2); + STBDS_ASSERT(hmgeti(intmap, i) == -1); + STBDS_ASSERT(hmget (intmap, i) == -2); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*5); + for (i=0; i < testsize; i+=1) { + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); + else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); + } + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=2; i < testsize; i+=4) + hmdel(intmap, i); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=0; i < testsize; i+=1) + hmdel(intmap, i); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(hmget(intmap, i) == -2 ); + hmfree(intmap); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + hmfree(intmap); + + #if defined(__clang__) || defined(__GNUC__) + #ifndef __cplusplus + intmap = NULL; + hmput(intmap, 15, 7); + hmput(intmap, 11, 3); + hmput(intmap, 9, 5); + STBDS_ASSERT(hmget(intmap, 9) == 5); + STBDS_ASSERT(hmget(intmap, 11) == 3); + STBDS_ASSERT(hmget(intmap, 15) == 7); + #endif + #endif + + for (i=0; i < testsize; ++i) + stralloc(&sa, strkey(i)); + strreset(&sa); + + { + s.key = "a", s.value = 1; + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key == s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_strdup(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_arena(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + for (j=0; j < 2; ++j) { + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + if (j == 0) + sh_new_strdup(strmap); + else + sh_new_arena(strmap); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + shdefault(strmap, -2); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + for (i=0; i < testsize; i+=2) + shput(strmap, strkey(i), i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=2; i < testsize; i+=4) + shdel(strmap, strkey(i)); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=0; i < testsize; i+=1) + shdel(strmap, strkey(i)); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + shfree(strmap); + } + + { + struct { char *key; char value; } *hash = NULL; + char name[4] = "jen"; + shput(hash, "bob" , 'h'); + shput(hash, "sally" , 'e'); + shput(hash, "fred" , 'l'); + shput(hash, "jen" , 'x'); + shput(hash, "doug" , 'o'); + + shput(hash, name , 'l'); + shfree(hash); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmput(map, s, i*5); + } + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3 ,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); + else STBDS_ASSERT(hmget(map, s) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); + else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); + //STBDS_ASSERT(hmget(map, t.key) == 0); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmputs(map2, s); + } + hmfree(map); + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); + else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); + //STBDS_ASSERT(hmgetp(map2, t.key) == 0); + } + hmfree(map2); + + for (i=0; i < testsize; i += 2) { + stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; + hmputs(map3, s); + } + for (i=0; i < testsize; i += 1) { + stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; + stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; + if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); + else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); + //STBDS_ASSERT(hmgetp(map3, t.key) == 0); + } +#endif +} +#endif + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2019 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_dxt.h b/source/engine/thirdparty/stb/stb_dxt.h new file mode 100644 index 0000000..6150a87 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_dxt.h @@ -0,0 +1,719 @@ +// stb_dxt.h - v1.12 - DXT1/DXT5 compressor - public domain +// original by fabian "ryg" giesen - ported to C by stb +// use '#define STB_DXT_IMPLEMENTATION' before including to create the implementation +// +// USAGE: +// call stb_compress_dxt_block() for every block (you must pad) +// source should be a 4x4 block of RGBA data in row-major order; +// Alpha channel is not stored if you specify alpha=0 (but you +// must supply some constant alpha in the alpha channel). +// You can turn on dithering and "high quality" using mode. +// +// version history: +// v1.12 - (ryg) fix bug in single-color table generator +// v1.11 - (ryg) avoid racy global init, better single-color tables, remove dither +// v1.10 - (i.c) various small quality improvements +// v1.09 - (stb) update documentation re: surprising alpha channel requirement +// v1.08 - (stb) fix bug in dxt-with-alpha block +// v1.07 - (stb) bc4; allow not using libc; add STB_DXT_STATIC +// v1.06 - (stb) fix to known-broken 1.05 +// v1.05 - (stb) support bc5/3dc (Arvids Kokins), use extern "C" in C++ (Pavel Krajcevski) +// v1.04 - (ryg) default to no rounding bias for lerped colors (as per S3TC/DX10 spec); +// single color match fix (allow for inexact color interpolation); +// optimal DXT5 index finder; "high quality" mode that runs multiple refinement steps. +// v1.03 - (stb) endianness support +// v1.02 - (stb) fix alpha encoding bug +// v1.01 - (stb) fix bug converting to RGB that messed up quality, thanks ryg & cbloom +// v1.00 - (stb) first release +// +// contributors: +// Rich Geldreich (more accurate index selection) +// Kevin Schmidt (#defines for "freestanding" compilation) +// github:ppiastucki (BC4 support) +// Ignacio Castano - improve DXT endpoint quantization +// Alan Hickman - static table initialization +// +// LICENSE +// +// See end of file for license information. + +#ifndef STB_INCLUDE_STB_DXT_H +#define STB_INCLUDE_STB_DXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_DXT_STATIC +#define STBDDEF static +#else +#define STBDDEF extern +#endif + +// compression mode (bitflags) +#define STB_DXT_NORMAL 0 +#define STB_DXT_DITHER 1 // use dithering. was always dubious, now deprecated. does nothing! +#define STB_DXT_HIGHQUAL 2 // high quality mode, does two refinement steps instead of 1. ~30-40% slower. + +STBDDEF void stb_compress_dxt_block(unsigned char *dest, const unsigned char *src_rgba_four_bytes_per_pixel, int alpha, int mode); +STBDDEF void stb_compress_bc4_block(unsigned char *dest, const unsigned char *src_r_one_byte_per_pixel); +STBDDEF void stb_compress_bc5_block(unsigned char *dest, const unsigned char *src_rg_two_byte_per_pixel); + +#define STB_COMPRESS_DXT_BLOCK + +#ifdef __cplusplus +} +#endif +#endif // STB_INCLUDE_STB_DXT_H + +#ifdef STB_DXT_IMPLEMENTATION + +// configuration options for DXT encoder. set them in the project/makefile or just define +// them at the top. + +// STB_DXT_USE_ROUNDING_BIAS +// use a rounding bias during color interpolation. this is closer to what "ideal" +// interpolation would do but doesn't match the S3TC/DX10 spec. old versions (pre-1.03) +// implicitly had this turned on. +// +// in case you're targeting a specific type of hardware (e.g. console programmers): +// NVidia and Intel GPUs (as of 2010) as well as DX9 ref use DXT decoders that are closer +// to STB_DXT_USE_ROUNDING_BIAS. AMD/ATI, S3 and DX10 ref are closer to rounding with no bias. +// you also see "(a*5 + b*3) / 8" on some old GPU designs. +// #define STB_DXT_USE_ROUNDING_BIAS + +#include + +#if !defined(STBD_FABS) +#include +#endif + +#ifndef STBD_FABS +#define STBD_FABS(x) fabs(x) +#endif + +static const unsigned char stb__OMatch5[256][2] = { + { 0, 0 }, { 0, 0 }, { 0, 1 }, { 0, 1 }, { 1, 0 }, { 1, 0 }, { 1, 0 }, { 1, 1 }, + { 1, 1 }, { 1, 1 }, { 1, 2 }, { 0, 4 }, { 2, 1 }, { 2, 1 }, { 2, 1 }, { 2, 2 }, + { 2, 2 }, { 2, 2 }, { 2, 3 }, { 1, 5 }, { 3, 2 }, { 3, 2 }, { 4, 0 }, { 3, 3 }, + { 3, 3 }, { 3, 3 }, { 3, 4 }, { 3, 4 }, { 3, 4 }, { 3, 5 }, { 4, 3 }, { 4, 3 }, + { 5, 2 }, { 4, 4 }, { 4, 4 }, { 4, 5 }, { 4, 5 }, { 5, 4 }, { 5, 4 }, { 5, 4 }, + { 6, 3 }, { 5, 5 }, { 5, 5 }, { 5, 6 }, { 4, 8 }, { 6, 5 }, { 6, 5 }, { 6, 5 }, + { 6, 6 }, { 6, 6 }, { 6, 6 }, { 6, 7 }, { 5, 9 }, { 7, 6 }, { 7, 6 }, { 8, 4 }, + { 7, 7 }, { 7, 7 }, { 7, 7 }, { 7, 8 }, { 7, 8 }, { 7, 8 }, { 7, 9 }, { 8, 7 }, + { 8, 7 }, { 9, 6 }, { 8, 8 }, { 8, 8 }, { 8, 9 }, { 8, 9 }, { 9, 8 }, { 9, 8 }, + { 9, 8 }, { 10, 7 }, { 9, 9 }, { 9, 9 }, { 9, 10 }, { 8, 12 }, { 10, 9 }, { 10, 9 }, + { 10, 9 }, { 10, 10 }, { 10, 10 }, { 10, 10 }, { 10, 11 }, { 9, 13 }, { 11, 10 }, { 11, 10 }, + { 12, 8 }, { 11, 11 }, { 11, 11 }, { 11, 11 }, { 11, 12 }, { 11, 12 }, { 11, 12 }, { 11, 13 }, + { 12, 11 }, { 12, 11 }, { 13, 10 }, { 12, 12 }, { 12, 12 }, { 12, 13 }, { 12, 13 }, { 13, 12 }, + { 13, 12 }, { 13, 12 }, { 14, 11 }, { 13, 13 }, { 13, 13 }, { 13, 14 }, { 12, 16 }, { 14, 13 }, + { 14, 13 }, { 14, 13 }, { 14, 14 }, { 14, 14 }, { 14, 14 }, { 14, 15 }, { 13, 17 }, { 15, 14 }, + { 15, 14 }, { 16, 12 }, { 15, 15 }, { 15, 15 }, { 15, 15 }, { 15, 16 }, { 15, 16 }, { 15, 16 }, + { 15, 17 }, { 16, 15 }, { 16, 15 }, { 17, 14 }, { 16, 16 }, { 16, 16 }, { 16, 17 }, { 16, 17 }, + { 17, 16 }, { 17, 16 }, { 17, 16 }, { 18, 15 }, { 17, 17 }, { 17, 17 }, { 17, 18 }, { 16, 20 }, + { 18, 17 }, { 18, 17 }, { 18, 17 }, { 18, 18 }, { 18, 18 }, { 18, 18 }, { 18, 19 }, { 17, 21 }, + { 19, 18 }, { 19, 18 }, { 20, 16 }, { 19, 19 }, { 19, 19 }, { 19, 19 }, { 19, 20 }, { 19, 20 }, + { 19, 20 }, { 19, 21 }, { 20, 19 }, { 20, 19 }, { 21, 18 }, { 20, 20 }, { 20, 20 }, { 20, 21 }, + { 20, 21 }, { 21, 20 }, { 21, 20 }, { 21, 20 }, { 22, 19 }, { 21, 21 }, { 21, 21 }, { 21, 22 }, + { 20, 24 }, { 22, 21 }, { 22, 21 }, { 22, 21 }, { 22, 22 }, { 22, 22 }, { 22, 22 }, { 22, 23 }, + { 21, 25 }, { 23, 22 }, { 23, 22 }, { 24, 20 }, { 23, 23 }, { 23, 23 }, { 23, 23 }, { 23, 24 }, + { 23, 24 }, { 23, 24 }, { 23, 25 }, { 24, 23 }, { 24, 23 }, { 25, 22 }, { 24, 24 }, { 24, 24 }, + { 24, 25 }, { 24, 25 }, { 25, 24 }, { 25, 24 }, { 25, 24 }, { 26, 23 }, { 25, 25 }, { 25, 25 }, + { 25, 26 }, { 24, 28 }, { 26, 25 }, { 26, 25 }, { 26, 25 }, { 26, 26 }, { 26, 26 }, { 26, 26 }, + { 26, 27 }, { 25, 29 }, { 27, 26 }, { 27, 26 }, { 28, 24 }, { 27, 27 }, { 27, 27 }, { 27, 27 }, + { 27, 28 }, { 27, 28 }, { 27, 28 }, { 27, 29 }, { 28, 27 }, { 28, 27 }, { 29, 26 }, { 28, 28 }, + { 28, 28 }, { 28, 29 }, { 28, 29 }, { 29, 28 }, { 29, 28 }, { 29, 28 }, { 30, 27 }, { 29, 29 }, + { 29, 29 }, { 29, 30 }, { 29, 30 }, { 30, 29 }, { 30, 29 }, { 30, 29 }, { 30, 30 }, { 30, 30 }, + { 30, 30 }, { 30, 31 }, { 30, 31 }, { 31, 30 }, { 31, 30 }, { 31, 30 }, { 31, 31 }, { 31, 31 }, +}; +static const unsigned char stb__OMatch6[256][2] = { + { 0, 0 }, { 0, 1 }, { 1, 0 }, { 1, 1 }, { 1, 1 }, { 1, 2 }, { 2, 1 }, { 2, 2 }, + { 2, 2 }, { 2, 3 }, { 3, 2 }, { 3, 3 }, { 3, 3 }, { 3, 4 }, { 4, 3 }, { 4, 4 }, + { 4, 4 }, { 4, 5 }, { 5, 4 }, { 5, 5 }, { 5, 5 }, { 5, 6 }, { 6, 5 }, { 6, 6 }, + { 6, 6 }, { 6, 7 }, { 7, 6 }, { 7, 7 }, { 7, 7 }, { 7, 8 }, { 8, 7 }, { 8, 8 }, + { 8, 8 }, { 8, 9 }, { 9, 8 }, { 9, 9 }, { 9, 9 }, { 9, 10 }, { 10, 9 }, { 10, 10 }, + { 10, 10 }, { 10, 11 }, { 11, 10 }, { 8, 16 }, { 11, 11 }, { 11, 12 }, { 12, 11 }, { 9, 17 }, + { 12, 12 }, { 12, 13 }, { 13, 12 }, { 11, 16 }, { 13, 13 }, { 13, 14 }, { 14, 13 }, { 12, 17 }, + { 14, 14 }, { 14, 15 }, { 15, 14 }, { 14, 16 }, { 15, 15 }, { 15, 16 }, { 16, 14 }, { 16, 15 }, + { 17, 14 }, { 16, 16 }, { 16, 17 }, { 17, 16 }, { 18, 15 }, { 17, 17 }, { 17, 18 }, { 18, 17 }, + { 20, 14 }, { 18, 18 }, { 18, 19 }, { 19, 18 }, { 21, 15 }, { 19, 19 }, { 19, 20 }, { 20, 19 }, + { 20, 20 }, { 20, 20 }, { 20, 21 }, { 21, 20 }, { 21, 21 }, { 21, 21 }, { 21, 22 }, { 22, 21 }, + { 22, 22 }, { 22, 22 }, { 22, 23 }, { 23, 22 }, { 23, 23 }, { 23, 23 }, { 23, 24 }, { 24, 23 }, + { 24, 24 }, { 24, 24 }, { 24, 25 }, { 25, 24 }, { 25, 25 }, { 25, 25 }, { 25, 26 }, { 26, 25 }, + { 26, 26 }, { 26, 26 }, { 26, 27 }, { 27, 26 }, { 24, 32 }, { 27, 27 }, { 27, 28 }, { 28, 27 }, + { 25, 33 }, { 28, 28 }, { 28, 29 }, { 29, 28 }, { 27, 32 }, { 29, 29 }, { 29, 30 }, { 30, 29 }, + { 28, 33 }, { 30, 30 }, { 30, 31 }, { 31, 30 }, { 30, 32 }, { 31, 31 }, { 31, 32 }, { 32, 30 }, + { 32, 31 }, { 33, 30 }, { 32, 32 }, { 32, 33 }, { 33, 32 }, { 34, 31 }, { 33, 33 }, { 33, 34 }, + { 34, 33 }, { 36, 30 }, { 34, 34 }, { 34, 35 }, { 35, 34 }, { 37, 31 }, { 35, 35 }, { 35, 36 }, + { 36, 35 }, { 36, 36 }, { 36, 36 }, { 36, 37 }, { 37, 36 }, { 37, 37 }, { 37, 37 }, { 37, 38 }, + { 38, 37 }, { 38, 38 }, { 38, 38 }, { 38, 39 }, { 39, 38 }, { 39, 39 }, { 39, 39 }, { 39, 40 }, + { 40, 39 }, { 40, 40 }, { 40, 40 }, { 40, 41 }, { 41, 40 }, { 41, 41 }, { 41, 41 }, { 41, 42 }, + { 42, 41 }, { 42, 42 }, { 42, 42 }, { 42, 43 }, { 43, 42 }, { 40, 48 }, { 43, 43 }, { 43, 44 }, + { 44, 43 }, { 41, 49 }, { 44, 44 }, { 44, 45 }, { 45, 44 }, { 43, 48 }, { 45, 45 }, { 45, 46 }, + { 46, 45 }, { 44, 49 }, { 46, 46 }, { 46, 47 }, { 47, 46 }, { 46, 48 }, { 47, 47 }, { 47, 48 }, + { 48, 46 }, { 48, 47 }, { 49, 46 }, { 48, 48 }, { 48, 49 }, { 49, 48 }, { 50, 47 }, { 49, 49 }, + { 49, 50 }, { 50, 49 }, { 52, 46 }, { 50, 50 }, { 50, 51 }, { 51, 50 }, { 53, 47 }, { 51, 51 }, + { 51, 52 }, { 52, 51 }, { 52, 52 }, { 52, 52 }, { 52, 53 }, { 53, 52 }, { 53, 53 }, { 53, 53 }, + { 53, 54 }, { 54, 53 }, { 54, 54 }, { 54, 54 }, { 54, 55 }, { 55, 54 }, { 55, 55 }, { 55, 55 }, + { 55, 56 }, { 56, 55 }, { 56, 56 }, { 56, 56 }, { 56, 57 }, { 57, 56 }, { 57, 57 }, { 57, 57 }, + { 57, 58 }, { 58, 57 }, { 58, 58 }, { 58, 58 }, { 58, 59 }, { 59, 58 }, { 59, 59 }, { 59, 59 }, + { 59, 60 }, { 60, 59 }, { 60, 60 }, { 60, 60 }, { 60, 61 }, { 61, 60 }, { 61, 61 }, { 61, 61 }, + { 61, 62 }, { 62, 61 }, { 62, 62 }, { 62, 62 }, { 62, 63 }, { 63, 62 }, { 63, 63 }, { 63, 63 }, +}; + +static int stb__Mul8Bit(int a, int b) +{ + int t = a*b + 128; + return (t + (t >> 8)) >> 8; +} + +static void stb__From16Bit(unsigned char *out, unsigned short v) +{ + int rv = (v & 0xf800) >> 11; + int gv = (v & 0x07e0) >> 5; + int bv = (v & 0x001f) >> 0; + + // expand to 8 bits via bit replication + out[0] = (rv * 33) >> 2; + out[1] = (gv * 65) >> 4; + out[2] = (bv * 33) >> 2; + out[3] = 0; +} + +static unsigned short stb__As16Bit(int r, int g, int b) +{ + return (unsigned short)((stb__Mul8Bit(r,31) << 11) + (stb__Mul8Bit(g,63) << 5) + stb__Mul8Bit(b,31)); +} + +// linear interpolation at 1/3 point between a and b, using desired rounding type +static int stb__Lerp13(int a, int b) +{ +#ifdef STB_DXT_USE_ROUNDING_BIAS + // with rounding bias + return a + stb__Mul8Bit(b-a, 0x55); +#else + // without rounding bias + // replace "/ 3" by "* 0xaaab) >> 17" if your compiler sucks or you really need every ounce of speed. + return (2*a + b) / 3; +#endif +} + +// lerp RGB color +static void stb__Lerp13RGB(unsigned char *out, unsigned char *p1, unsigned char *p2) +{ + out[0] = (unsigned char)stb__Lerp13(p1[0], p2[0]); + out[1] = (unsigned char)stb__Lerp13(p1[1], p2[1]); + out[2] = (unsigned char)stb__Lerp13(p1[2], p2[2]); +} + +/****************************************************************************/ + +static void stb__EvalColors(unsigned char *color,unsigned short c0,unsigned short c1) +{ + stb__From16Bit(color+ 0, c0); + stb__From16Bit(color+ 4, c1); + stb__Lerp13RGB(color+ 8, color+0, color+4); + stb__Lerp13RGB(color+12, color+4, color+0); +} + +// The color matching function +static unsigned int stb__MatchColorsBlock(unsigned char *block, unsigned char *color) +{ + unsigned int mask = 0; + int dirr = color[0*4+0] - color[1*4+0]; + int dirg = color[0*4+1] - color[1*4+1]; + int dirb = color[0*4+2] - color[1*4+2]; + int dots[16]; + int stops[4]; + int i; + int c0Point, halfPoint, c3Point; + + for(i=0;i<16;i++) + dots[i] = block[i*4+0]*dirr + block[i*4+1]*dirg + block[i*4+2]*dirb; + + for(i=0;i<4;i++) + stops[i] = color[i*4+0]*dirr + color[i*4+1]*dirg + color[i*4+2]*dirb; + + // think of the colors as arranged on a line; project point onto that line, then choose + // next color out of available ones. we compute the crossover points for "best color in top + // half"/"best in bottom half" and then the same inside that subinterval. + // + // relying on this 1d approximation isn't always optimal in terms of euclidean distance, + // but it's very close and a lot faster. + // http://cbloomrants.blogspot.com/2008/12/12-08-08-dxtc-summary.html + + c0Point = (stops[1] + stops[3]); + halfPoint = (stops[3] + stops[2]); + c3Point = (stops[2] + stops[0]); + + for (i=15;i>=0;i--) { + int dot = dots[i]*2; + mask <<= 2; + + if(dot < halfPoint) + mask |= (dot < c0Point) ? 1 : 3; + else + mask |= (dot < c3Point) ? 2 : 0; + } + + return mask; +} + +// The color optimization function. (Clever code, part 1) +static void stb__OptimizeColorsBlock(unsigned char *block, unsigned short *pmax16, unsigned short *pmin16) +{ + int mind,maxd; + unsigned char *minp, *maxp; + double magn; + int v_r,v_g,v_b; + static const int nIterPower = 4; + float covf[6],vfr,vfg,vfb; + + // determine color distribution + int cov[6]; + int mu[3],min[3],max[3]; + int ch,i,iter; + + for(ch=0;ch<3;ch++) + { + const unsigned char *bp = ((const unsigned char *) block) + ch; + int muv,minv,maxv; + + muv = minv = maxv = bp[0]; + for(i=4;i<64;i+=4) + { + muv += bp[i]; + if (bp[i] < minv) minv = bp[i]; + else if (bp[i] > maxv) maxv = bp[i]; + } + + mu[ch] = (muv + 8) >> 4; + min[ch] = minv; + max[ch] = maxv; + } + + // determine covariance matrix + for (i=0;i<6;i++) + cov[i] = 0; + + for (i=0;i<16;i++) + { + int r = block[i*4+0] - mu[0]; + int g = block[i*4+1] - mu[1]; + int b = block[i*4+2] - mu[2]; + + cov[0] += r*r; + cov[1] += r*g; + cov[2] += r*b; + cov[3] += g*g; + cov[4] += g*b; + cov[5] += b*b; + } + + // convert covariance matrix to float, find principal axis via power iter + for(i=0;i<6;i++) + covf[i] = cov[i] / 255.0f; + + vfr = (float) (max[0] - min[0]); + vfg = (float) (max[1] - min[1]); + vfb = (float) (max[2] - min[2]); + + for(iter=0;iter magn) magn = STBD_FABS(vfg); + if (STBD_FABS(vfb) > magn) magn = STBD_FABS(vfb); + + if(magn < 4.0f) { // too small, default to luminance + v_r = 299; // JPEG YCbCr luma coefs, scaled by 1000. + v_g = 587; + v_b = 114; + } else { + magn = 512.0 / magn; + v_r = (int) (vfr * magn); + v_g = (int) (vfg * magn); + v_b = (int) (vfb * magn); + } + + minp = maxp = block; + mind = maxd = block[0]*v_r + block[1]*v_g + block[2]*v_b; + // Pick colors at extreme points + for(i=1;i<16;i++) + { + int dot = block[i*4+0]*v_r + block[i*4+1]*v_g + block[i*4+2]*v_b; + + if (dot < mind) { + mind = dot; + minp = block+i*4; + } + + if (dot > maxd) { + maxd = dot; + maxp = block+i*4; + } + } + + *pmax16 = stb__As16Bit(maxp[0],maxp[1],maxp[2]); + *pmin16 = stb__As16Bit(minp[0],minp[1],minp[2]); +} + +static const float stb__midpoints5[32] = { + 0.015686f, 0.047059f, 0.078431f, 0.111765f, 0.145098f, 0.176471f, 0.207843f, 0.241176f, 0.274510f, 0.305882f, 0.337255f, 0.370588f, 0.403922f, 0.435294f, 0.466667f, 0.5f, + 0.533333f, 0.564706f, 0.596078f, 0.629412f, 0.662745f, 0.694118f, 0.725490f, 0.758824f, 0.792157f, 0.823529f, 0.854902f, 0.888235f, 0.921569f, 0.952941f, 0.984314f, 1.0f +}; + +static const float stb__midpoints6[64] = { + 0.007843f, 0.023529f, 0.039216f, 0.054902f, 0.070588f, 0.086275f, 0.101961f, 0.117647f, 0.133333f, 0.149020f, 0.164706f, 0.180392f, 0.196078f, 0.211765f, 0.227451f, 0.245098f, + 0.262745f, 0.278431f, 0.294118f, 0.309804f, 0.325490f, 0.341176f, 0.356863f, 0.372549f, 0.388235f, 0.403922f, 0.419608f, 0.435294f, 0.450980f, 0.466667f, 0.482353f, 0.500000f, + 0.517647f, 0.533333f, 0.549020f, 0.564706f, 0.580392f, 0.596078f, 0.611765f, 0.627451f, 0.643137f, 0.658824f, 0.674510f, 0.690196f, 0.705882f, 0.721569f, 0.737255f, 0.754902f, + 0.772549f, 0.788235f, 0.803922f, 0.819608f, 0.835294f, 0.850980f, 0.866667f, 0.882353f, 0.898039f, 0.913725f, 0.929412f, 0.945098f, 0.960784f, 0.976471f, 0.992157f, 1.0f +}; + +static unsigned short stb__Quantize5(float x) +{ + unsigned short q; + x = x < 0 ? 0 : x > 1 ? 1 : x; // saturate + q = (unsigned short)(x * 31); + q += (x > stb__midpoints5[q]); + return q; +} + +static unsigned short stb__Quantize6(float x) +{ + unsigned short q; + x = x < 0 ? 0 : x > 1 ? 1 : x; // saturate + q = (unsigned short)(x * 63); + q += (x > stb__midpoints6[q]); + return q; +} + +// The refinement function. (Clever code, part 2) +// Tries to optimize colors to suit block contents better. +// (By solving a least squares system via normal equations+Cramer's rule) +static int stb__RefineBlock(unsigned char *block, unsigned short *pmax16, unsigned short *pmin16, unsigned int mask) +{ + static const int w1Tab[4] = { 3,0,2,1 }; + static const int prods[4] = { 0x090000,0x000900,0x040102,0x010402 }; + // ^some magic to save a lot of multiplies in the accumulating loop... + // (precomputed products of weights for least squares system, accumulated inside one 32-bit register) + + float f; + unsigned short oldMin, oldMax, min16, max16; + int i, akku = 0, xx,xy,yy; + int At1_r,At1_g,At1_b; + int At2_r,At2_g,At2_b; + unsigned int cm = mask; + + oldMin = *pmin16; + oldMax = *pmax16; + + if((mask ^ (mask<<2)) < 4) // all pixels have the same index? + { + // yes, linear system would be singular; solve using optimal + // single-color match on average color + int r = 8, g = 8, b = 8; + for (i=0;i<16;++i) { + r += block[i*4+0]; + g += block[i*4+1]; + b += block[i*4+2]; + } + + r >>= 4; g >>= 4; b >>= 4; + + max16 = (stb__OMatch5[r][0]<<11) | (stb__OMatch6[g][0]<<5) | stb__OMatch5[b][0]; + min16 = (stb__OMatch5[r][1]<<11) | (stb__OMatch6[g][1]<<5) | stb__OMatch5[b][1]; + } else { + At1_r = At1_g = At1_b = 0; + At2_r = At2_g = At2_b = 0; + for (i=0;i<16;++i,cm>>=2) { + int step = cm&3; + int w1 = w1Tab[step]; + int r = block[i*4+0]; + int g = block[i*4+1]; + int b = block[i*4+2]; + + akku += prods[step]; + At1_r += w1*r; + At1_g += w1*g; + At1_b += w1*b; + At2_r += r; + At2_g += g; + At2_b += b; + } + + At2_r = 3*At2_r - At1_r; + At2_g = 3*At2_g - At1_g; + At2_b = 3*At2_b - At1_b; + + // extract solutions and decide solvability + xx = akku >> 16; + yy = (akku >> 8) & 0xff; + xy = (akku >> 0) & 0xff; + + f = 3.0f / 255.0f / (xx*yy - xy*xy); + + max16 = stb__Quantize5((At1_r*yy - At2_r * xy) * f) << 11; + max16 |= stb__Quantize6((At1_g*yy - At2_g * xy) * f) << 5; + max16 |= stb__Quantize5((At1_b*yy - At2_b * xy) * f) << 0; + + min16 = stb__Quantize5((At2_r*xx - At1_r * xy) * f) << 11; + min16 |= stb__Quantize6((At2_g*xx - At1_g * xy) * f) << 5; + min16 |= stb__Quantize5((At2_b*xx - At1_b * xy) * f) << 0; + } + + *pmin16 = min16; + *pmax16 = max16; + return oldMin != min16 || oldMax != max16; +} + +// Color block compression +static void stb__CompressColorBlock(unsigned char *dest, unsigned char *block, int mode) +{ + unsigned int mask; + int i; + int refinecount; + unsigned short max16, min16; + unsigned char color[4*4]; + + refinecount = (mode & STB_DXT_HIGHQUAL) ? 2 : 1; + + // check if block is constant + for (i=1;i<16;i++) + if (((unsigned int *) block)[i] != ((unsigned int *) block)[0]) + break; + + if(i == 16) { // constant color + int r = block[0], g = block[1], b = block[2]; + mask = 0xaaaaaaaa; + max16 = (stb__OMatch5[r][0]<<11) | (stb__OMatch6[g][0]<<5) | stb__OMatch5[b][0]; + min16 = (stb__OMatch5[r][1]<<11) | (stb__OMatch6[g][1]<<5) | stb__OMatch5[b][1]; + } else { + // first step: PCA+map along principal axis + stb__OptimizeColorsBlock(block,&max16,&min16); + if (max16 != min16) { + stb__EvalColors(color,max16,min16); + mask = stb__MatchColorsBlock(block,color); + } else + mask = 0; + + // third step: refine (multiple times if requested) + for (i=0;i> 8); + dest[2] = (unsigned char) (min16); + dest[3] = (unsigned char) (min16 >> 8); + dest[4] = (unsigned char) (mask); + dest[5] = (unsigned char) (mask >> 8); + dest[6] = (unsigned char) (mask >> 16); + dest[7] = (unsigned char) (mask >> 24); +} + +// Alpha block compression (this is easy for a change) +static void stb__CompressAlphaBlock(unsigned char *dest,unsigned char *src, int stride) +{ + int i,dist,bias,dist4,dist2,bits,mask; + + // find min/max color + int mn,mx; + mn = mx = src[0]; + + for (i=1;i<16;i++) + { + if (src[i*stride] < mn) mn = src[i*stride]; + else if (src[i*stride] > mx) mx = src[i*stride]; + } + + // encode them + dest[0] = (unsigned char)mx; + dest[1] = (unsigned char)mn; + dest += 2; + + // determine bias and emit color indices + // given the choice of mx/mn, these indices are optimal: + // http://fgiesen.wordpress.com/2009/12/15/dxt5-alpha-block-index-determination/ + dist = mx-mn; + dist4 = dist*4; + dist2 = dist*2; + bias = (dist < 8) ? (dist - 1) : (dist/2 + 2); + bias -= mn * 7; + bits = 0,mask=0; + + for (i=0;i<16;i++) { + int a = src[i*stride]*7 + bias; + int ind,t; + + // select index. this is a "linear scale" lerp factor between 0 (val=min) and 7 (val=max). + t = (a >= dist4) ? -1 : 0; ind = t & 4; a -= dist4 & t; + t = (a >= dist2) ? -1 : 0; ind += t & 2; a -= dist2 & t; + ind += (a >= dist); + + // turn linear scale into DXT index (0/1 are extremal pts) + ind = -ind & 7; + ind ^= (2 > ind); + + // write index + mask |= ind << bits; + if((bits += 3) >= 8) { + *dest++ = (unsigned char)mask; + mask >>= 8; + bits -= 8; + } + } +} + +void stb_compress_dxt_block(unsigned char *dest, const unsigned char *src, int alpha, int mode) +{ + unsigned char data[16][4]; + if (alpha) { + int i; + stb__CompressAlphaBlock(dest,(unsigned char*) src+3, 4); + dest += 8; + // make a new copy of the data in which alpha is opaque, + // because code uses a fast test for color constancy + memcpy(data, src, 4*16); + for (i=0; i < 16; ++i) + data[i][3] = 255; + src = &data[0][0]; + } + + stb__CompressColorBlock(dest,(unsigned char*) src,mode); +} + +void stb_compress_bc4_block(unsigned char *dest, const unsigned char *src) +{ + stb__CompressAlphaBlock(dest,(unsigned char*) src, 1); +} + +void stb_compress_bc5_block(unsigned char *dest, const unsigned char *src) +{ + stb__CompressAlphaBlock(dest,(unsigned char*) src,2); + stb__CompressAlphaBlock(dest + 8,(unsigned char*) src+1,2); +} +#endif // STB_DXT_IMPLEMENTATION + +// Compile with STB_DXT_IMPLEMENTATION and STB_DXT_GENERATE_TABLES +// defined to generate the tables above. +#ifdef STB_DXT_GENERATE_TABLES +#include + +int main() +{ + int i, j; + const char *omatch_names[] = { "stb__OMatch5", "stb__OMatch6" }; + int dequant_mults[2] = { 33*4, 65 }; // .4 fixed-point dequant multipliers + + // optimal endpoint tables + for (i = 0; i < 2; ++i) { + int dequant = dequant_mults[i]; + int size = i ? 64 : 32; + printf("static const unsigned char %s[256][2] = {\n", omatch_names[i]); + for (int j = 0; j < 256; ++j) { + int mn, mx; + int best_mn = 0, best_mx = 0; + int best_err = 256 * 100; + for (mn=0;mn> 4; + int maxe = (mx * dequant) >> 4; + int err = abs(stb__Lerp13(maxe, mine) - j) * 100; + + // DX10 spec says that interpolation must be within 3% of "correct" result, + // add this as error term. Normally we'd expect a random distribution of + // +-1.5% error, but nowhere in the spec does it say that the error has to be + // unbiased - better safe than sorry. + err += abs(maxe - mine) * 3; + + if(err < best_err) { + best_mn = mn; + best_mx = mx; + best_err = err; + } + } + } + if ((j % 8) == 0) printf(" "); // 2 spaces, third is done below + printf(" { %2d, %2d },", best_mx, best_mn); + if ((j % 8) == 7) printf("\n"); + } + printf("};\n"); + } + + return 0; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_easy_font.h b/source/engine/thirdparty/stb/stb_easy_font.h new file mode 100644 index 0000000..b663258 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_easy_font.h @@ -0,0 +1,305 @@ +// stb_easy_font.h - v1.1 - bitmap font for 3D rendering - public domain +// Sean Barrett, Feb 2015 +// +// Easy-to-deploy, +// reasonably compact, +// extremely inefficient performance-wise, +// crappy-looking, +// ASCII-only, +// bitmap font for use in 3D APIs. +// +// Intended for when you just want to get some text displaying +// in a 3D app as quickly as possible. +// +// Doesn't use any textures, instead builds characters out of quads. +// +// DOCUMENTATION: +// +// int stb_easy_font_width(char *text) +// int stb_easy_font_height(char *text) +// +// Takes a string and returns the horizontal size and the +// vertical size (which can vary if 'text' has newlines). +// +// int stb_easy_font_print(float x, float y, +// char *text, unsigned char color[4], +// void *vertex_buffer, int vbuf_size) +// +// Takes a string (which can contain '\n') and fills out a +// vertex buffer with renderable data to draw the string. +// Output data assumes increasing x is rightwards, increasing y +// is downwards. +// +// The vertex data is divided into quads, i.e. there are four +// vertices in the vertex buffer for each quad. +// +// The vertices are stored in an interleaved format: +// +// x:float +// y:float +// z:float +// color:uint8[4] +// +// You can ignore z and color if you get them from elsewhere +// This format was chosen in the hopes it would make it +// easier for you to reuse existing vertex-buffer-drawing code. +// +// If you pass in NULL for color, it becomes 255,255,255,255. +// +// Returns the number of quads. +// +// If the buffer isn't large enough, it will truncate. +// Expect it to use an average of ~270 bytes per character. +// +// If your API doesn't draw quads, build a reusable index +// list that allows you to render quads as indexed triangles. +// +// void stb_easy_font_spacing(float spacing) +// +// Use positive values to expand the space between characters, +// and small negative values (no smaller than -1.5) to contract +// the space between characters. +// +// E.g. spacing = 1 adds one "pixel" of spacing between the +// characters. spacing = -1 is reasonable but feels a bit too +// compact to me; -0.5 is a reasonable compromise as long as +// you're scaling the font up. +// +// LICENSE +// +// See end of file for license information. +// +// VERSION HISTORY +// +// (2020-02-02) 1.1 make everything static so can compile it in more than one src file +// (2017-01-15) 1.0 space character takes same space as numbers; fix bad spacing of 'f' +// (2016-01-22) 0.7 width() supports multiline text; add height() +// (2015-09-13) 0.6 #include ; updated license +// (2015-02-01) 0.5 First release +// +// CONTRIBUTORS +// +// github:vassvik -- bug report +// github:podsvirov -- fix multiple definition errors + +#if 0 +// SAMPLE CODE: +// +// Here's sample code for old OpenGL; it's a lot more complicated +// to make work on modern APIs, and that's your problem. +// +void print_string(float x, float y, char *text, float r, float g, float b) +{ + static char buffer[99999]; // ~500 chars + int num_quads; + + num_quads = stb_easy_font_print(x, y, text, NULL, buffer, sizeof(buffer)); + + glColor3f(r,g,b); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(2, GL_FLOAT, 16, buffer); + glDrawArrays(GL_QUADS, 0, num_quads*4); + glDisableClientState(GL_VERTEX_ARRAY); +} +#endif + +#ifndef INCLUDE_STB_EASY_FONT_H +#define INCLUDE_STB_EASY_FONT_H + +#include +#include + +static struct stb_easy_font_info_struct { + unsigned char advance; + unsigned char h_seg; + unsigned char v_seg; +} stb_easy_font_charinfo[96] = { + { 6, 0, 0 }, { 3, 0, 0 }, { 5, 1, 1 }, { 7, 1, 4 }, + { 7, 3, 7 }, { 7, 6, 12 }, { 7, 8, 19 }, { 4, 16, 21 }, + { 4, 17, 22 }, { 4, 19, 23 }, { 23, 21, 24 }, { 23, 22, 31 }, + { 20, 23, 34 }, { 22, 23, 36 }, { 19, 24, 36 }, { 21, 25, 36 }, + { 6, 25, 39 }, { 6, 27, 43 }, { 6, 28, 45 }, { 6, 30, 49 }, + { 6, 33, 53 }, { 6, 34, 57 }, { 6, 40, 58 }, { 6, 46, 59 }, + { 6, 47, 62 }, { 6, 55, 64 }, { 19, 57, 68 }, { 20, 59, 68 }, + { 21, 61, 69 }, { 22, 66, 69 }, { 21, 68, 69 }, { 7, 73, 69 }, + { 9, 75, 74 }, { 6, 78, 81 }, { 6, 80, 85 }, { 6, 83, 90 }, + { 6, 85, 91 }, { 6, 87, 95 }, { 6, 90, 96 }, { 7, 92, 97 }, + { 6, 96,102 }, { 5, 97,106 }, { 6, 99,107 }, { 6,100,110 }, + { 6,100,115 }, { 7,101,116 }, { 6,101,121 }, { 6,101,125 }, + { 6,102,129 }, { 7,103,133 }, { 6,104,140 }, { 6,105,145 }, + { 7,107,149 }, { 6,108,151 }, { 7,109,155 }, { 7,109,160 }, + { 7,109,165 }, { 7,118,167 }, { 6,118,172 }, { 4,120,176 }, + { 6,122,177 }, { 4,122,181 }, { 23,124,182 }, { 22,129,182 }, + { 4,130,182 }, { 22,131,183 }, { 6,133,187 }, { 22,135,191 }, + { 6,137,192 }, { 22,139,196 }, { 6,144,197 }, { 22,147,198 }, + { 6,150,202 }, { 19,151,206 }, { 21,152,207 }, { 6,155,209 }, + { 3,160,210 }, { 23,160,211 }, { 22,164,216 }, { 22,165,220 }, + { 22,167,224 }, { 22,169,228 }, { 21,171,232 }, { 21,173,233 }, + { 5,178,233 }, { 22,179,234 }, { 23,180,238 }, { 23,180,243 }, + { 23,180,248 }, { 22,189,248 }, { 22,191,252 }, { 5,196,252 }, + { 3,203,252 }, { 5,203,253 }, { 22,210,253 }, { 0,214,253 }, +}; + +static unsigned char stb_easy_font_hseg[214] = { + 97,37,69,84,28,51,2,18,10,49,98,41,65,25,81,105,33,9,97,1,97,37,37,36, + 81,10,98,107,3,100,3,99,58,51,4,99,58,8,73,81,10,50,98,8,73,81,4,10,50, + 98,8,25,33,65,81,10,50,17,65,97,25,33,25,49,9,65,20,68,1,65,25,49,41, + 11,105,13,101,76,10,50,10,50,98,11,99,10,98,11,50,99,11,50,11,99,8,57, + 58,3,99,99,107,10,10,11,10,99,11,5,100,41,65,57,41,65,9,17,81,97,3,107, + 9,97,1,97,33,25,9,25,41,100,41,26,82,42,98,27,83,42,98,26,51,82,8,41, + 35,8,10,26,82,114,42,1,114,8,9,73,57,81,41,97,18,8,8,25,26,26,82,26,82, + 26,82,41,25,33,82,26,49,73,35,90,17,81,41,65,57,41,65,25,81,90,114,20, + 84,73,57,41,49,25,33,65,81,9,97,1,97,25,33,65,81,57,33,25,41,25, +}; + +static unsigned char stb_easy_font_vseg[253] = { + 4,2,8,10,15,8,15,33,8,15,8,73,82,73,57,41,82,10,82,18,66,10,21,29,1,65, + 27,8,27,9,65,8,10,50,97,74,66,42,10,21,57,41,29,25,14,81,73,57,26,8,8, + 26,66,3,8,8,15,19,21,90,58,26,18,66,18,105,89,28,74,17,8,73,57,26,21, + 8,42,41,42,8,28,22,8,8,30,7,8,8,26,66,21,7,8,8,29,7,7,21,8,8,8,59,7,8, + 8,15,29,8,8,14,7,57,43,10,82,7,7,25,42,25,15,7,25,41,15,21,105,105,29, + 7,57,57,26,21,105,73,97,89,28,97,7,57,58,26,82,18,57,57,74,8,30,6,8,8, + 14,3,58,90,58,11,7,74,43,74,15,2,82,2,42,75,42,10,67,57,41,10,7,2,42, + 74,106,15,2,35,8,8,29,7,8,8,59,35,51,8,8,15,35,30,35,8,8,30,7,8,8,60, + 36,8,45,7,7,36,8,43,8,44,21,8,8,44,35,8,8,43,23,8,8,43,35,8,8,31,21,15, + 20,8,8,28,18,58,89,58,26,21,89,73,89,29,20,8,8,30,7, +}; + +typedef struct +{ + unsigned char c[4]; +} stb_easy_font_color; + +static int stb_easy_font_draw_segs(float x, float y, unsigned char *segs, int num_segs, int vertical, stb_easy_font_color c, char *vbuf, int vbuf_size, int offset) +{ + int i,j; + for (i=0; i < num_segs; ++i) { + int len = segs[i] & 7; + x += (float) ((segs[i] >> 3) & 1); + if (len && offset+64 <= vbuf_size) { + float y0 = y + (float) (segs[i]>>4); + for (j=0; j < 4; ++j) { + * (float *) (vbuf+offset+0) = x + (j==1 || j==2 ? (vertical ? 1 : len) : 0); + * (float *) (vbuf+offset+4) = y0 + ( j >= 2 ? (vertical ? len : 1) : 0); + * (float *) (vbuf+offset+8) = 0.f; + * (stb_easy_font_color *) (vbuf+offset+12) = c; + offset += 16; + } + } + } + return offset; +} + +static float stb_easy_font_spacing_val = 0; +static void stb_easy_font_spacing(float spacing) +{ + stb_easy_font_spacing_val = spacing; +} + +static int stb_easy_font_print(float x, float y, char *text, unsigned char color[4], void *vertex_buffer, int vbuf_size) +{ + char *vbuf = (char *) vertex_buffer; + float start_x = x; + int offset = 0; + + stb_easy_font_color c = { 255,255,255,255 }; // use structure copying to avoid needing depending on memcpy() + if (color) { c.c[0] = color[0]; c.c[1] = color[1]; c.c[2] = color[2]; c.c[3] = color[3]; } + + while (*text && offset < vbuf_size) { + if (*text == '\n') { + y += 12; + x = start_x; + } else { + unsigned char advance = stb_easy_font_charinfo[*text-32].advance; + float y_ch = advance & 16 ? y+1 : y; + int h_seg, v_seg, num_h, num_v; + h_seg = stb_easy_font_charinfo[*text-32 ].h_seg; + v_seg = stb_easy_font_charinfo[*text-32 ].v_seg; + num_h = stb_easy_font_charinfo[*text-32+1].h_seg - h_seg; + num_v = stb_easy_font_charinfo[*text-32+1].v_seg - v_seg; + offset = stb_easy_font_draw_segs(x, y_ch, &stb_easy_font_hseg[h_seg], num_h, 0, c, vbuf, vbuf_size, offset); + offset = stb_easy_font_draw_segs(x, y_ch, &stb_easy_font_vseg[v_seg], num_v, 1, c, vbuf, vbuf_size, offset); + x += advance & 15; + x += stb_easy_font_spacing_val; + } + ++text; + } + return (unsigned) offset/64; +} + +static int stb_easy_font_width(char *text) +{ + float len = 0; + float max_len = 0; + while (*text) { + if (*text == '\n') { + if (len > max_len) max_len = len; + len = 0; + } else { + len += stb_easy_font_charinfo[*text-32].advance & 15; + len += stb_easy_font_spacing_val; + } + ++text; + } + if (len > max_len) max_len = len; + return (int) ceil(max_len); +} + +static int stb_easy_font_height(char *text) +{ + float y = 0; + int nonempty_line=0; + while (*text) { + if (*text == '\n') { + y += 12; + nonempty_line = 0; + } else { + nonempty_line = 1; + } + ++text; + } + return (int) ceil(y + (nonempty_line ? 12 : 0)); +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_herringbone_wang_tile.h b/source/engine/thirdparty/stb/stb_herringbone_wang_tile.h new file mode 100644 index 0000000..5517941 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_herringbone_wang_tile.h @@ -0,0 +1,1221 @@ +/* stbhw - v0.7 - http://nothings.org/gamedev/herringbone + Herringbone Wang Tile Generator - Sean Barrett 2014 - public domain + +== LICENSE ============================== + +This software is dual-licensed to the public domain and under the following +license: you are granted a perpetual, irrevocable license to copy, modify, +publish, and distribute this file as you see fit. + +== WHAT IT IS =========================== + + This library is an SDK for Herringbone Wang Tile generation: + + http://nothings.org/gamedev/herringbone + + The core design is that you use this library offline to generate a + "template" of the tiles you'll create. You then edit those tiles, then + load the created tile image file back into this library and use it at + runtime to generate "maps". + + You cannot load arbitrary tile image files with this library; it is + only designed to load image files made from the template it created. + It stores a binary description of the tile sizes & constraints in a + few pixels, and uses those to recover the rules, rather than trying + to parse the tiles themselves. + + You *can* use this library to generate from arbitrary tile sets, but + only by loading the tile set and specifying the constraints explicitly + yourself. + +== COMPILING ============================ + + 1. #define STB_HERRINGBONE_WANG_TILE_IMPLEMENTATION before including this + header file in *one* source file to create the implementation + in that source file. + + 2. optionally #define STB_HBWANG_RAND() to be a random number + generator. if you don't define it, it will use rand(), + and you need to seed srand() yourself. + + 3. optionally #define STB_HBWANG_ASSERT(x), otherwise + it will use assert() + + 4. optionally #define STB_HBWANG_STATIC to force all symbols to be + static instead of public, so they are only accesible + in the source file that creates the implementation + + 5. optionally #define STB_HBWANG_NO_REPITITION_REDUCTION to disable + the code that tries to reduce having the same tile appear + adjacent to itself in wang-corner-tile mode (e.g. imagine + if you were doing something where 90% of things should be + the same grass tile, you need to disable this system) + + 6. optionally define STB_HBWANG_MAX_X and STB_HBWANG_MAX_Y + to be the max dimensions of the generated map in multiples + of the wang tile's short side's length (e.g. if you + have 20x10 wang tiles, so short_side_len=10, and you + have MAX_X is 17, then the largest map you can generate + is 170 pixels wide). The defaults are 100x100. This + is used to define static arrays which affect memory + usage. + +== USING ================================ + + To use the map generator, you need a tileset. You can download + some sample tilesets from http://nothings.org/gamedev/herringbone + + Then see the "sample application" below. + + You can also use this file to generate templates for + tilesets which you then hand-edit to create the data. + + +== MEMORY MANAGEMENT ==================== + + The tileset loader allocates memory with malloc(). The map + generator does no memory allocation, so e.g. you can load + tilesets at startup and never free them and never do any + further allocation. + + +== SAMPLE APPLICATION =================== + +#include +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" // http://nothings.org/stb_image.c + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" // http://nothings.org/stb/stb_image_write.h + +#define STB_HBWANG_IMPLEMENTATION +#include "stb_hbwang.h" + +int main(int argc, char **argv) +{ + unsigned char *data; + int xs,ys, w,h; + stbhw_tileset ts; + + if (argc != 4) { + fprintf(stderr, "Usage: mapgen {tile-file} {xsize} {ysize}\n" + "generates file named 'test_map.png'\n"); + exit(1); + } + data = stbi_load(argv[1], &w, &h, NULL, 3); + xs = atoi(argv[2]); + ys = atoi(argv[3]); + if (data == NULL) { + fprintf(stderr, "Error opening or parsing '%s' as an image file\n", argv[1]); + exit(1); + } + if (xs < 1 || xs > 1000) { + fprintf(stderr, "xsize invalid or out of range\n"); + exit(1); + } + if (ys < 1 || ys > 1000) { + fprintf(stderr, "ysize invalid or out of range\n"); + exit(1); + } + + stbhw_build_tileset_from_image(&ts, data, w*3, w, h); + free(data); + + // allocate a buffer to create the final image to + data = malloc(3 * xs * ys); + + srand(time(NULL)); + stbhw_generate_image(&ts, NULL, data, xs*3, xs, ys); + + stbi_write_png("test_map.png", xs, ys, 3, data, xs*3); + + stbhw_free_tileset(&ts); + free(data); + + return 0; +} + +== VERSION HISTORY =================== + + 0.7 2019-03-04 - fix warnings + 0.6 2014-08-17 - fix broken map-maker + 0.5 2014-07-07 - initial release + +*/ + +////////////////////////////////////////////////////////////////////////////// +// // +// HEADER FILE SECTION // +// // + +#ifndef INCLUDE_STB_HWANG_H +#define INCLUDE_STB_HWANG_H + +#ifdef STB_HBWANG_STATIC +#define STBHW_EXTERN static +#else +#ifdef __cplusplus +#define STBHW_EXTERN extern "C" +#else +#define STBHW_EXTERN extern +#endif +#endif + +typedef struct stbhw_tileset stbhw_tileset; + +// returns description of last error produced by any function (not thread-safe) +STBHW_EXTERN const char *stbhw_get_last_error(void); + +// build a tileset from an image that conforms to a template created by this +// library. (you allocate storage for stbhw_tileset and function fills it out; +// memory for individual tiles are malloc()ed). +// returns non-zero on success, 0 on error +STBHW_EXTERN int stbhw_build_tileset_from_image(stbhw_tileset *ts, + unsigned char *pixels, int stride_in_bytes, int w, int h); + +// free a tileset built by stbhw_build_tileset_from_image +STBHW_EXTERN void stbhw_free_tileset(stbhw_tileset *ts); + +// generate a map that is w * h pixels (3-bytes each) +// returns non-zero on success, 0 on error +// not thread-safe (uses a global data structure to avoid memory management) +// weighting should be NULL, as non-NULL weighting is currently untested +STBHW_EXTERN int stbhw_generate_image(stbhw_tileset *ts, int **weighting, + unsigned char *pixels, int stride_in_bytes, int w, int h); + +////////////////////////////////////// +// +// TILESET DATA STRUCTURE +// +// if you use the image-to-tileset system from this file, you +// don't need to worry about these data structures. but if you +// want to build/load a tileset yourself, you'll need to fill +// these out. + +typedef struct +{ + // the edge or vertex constraints, according to diagram below + signed char a,b,c,d,e,f; + + // The herringbone wang tile data; it is a bitmap which is either + // w=2*short_sidelen,h=short_sidelen, or w=short_sidelen,h=2*short_sidelen. + // it is always RGB, stored row-major, with no padding between rows. + // (allocate stbhw_tile structure to be large enough for the pixel data) + unsigned char pixels[1]; +} stbhw_tile; + +struct stbhw_tileset +{ + int is_corner; + int num_color[6]; // number of colors for each of 6 edge types or 4 corner types + int short_side_len; + stbhw_tile **h_tiles; + stbhw_tile **v_tiles; + int num_h_tiles, max_h_tiles; + int num_v_tiles, max_v_tiles; +}; + +/////////////// TEMPLATE GENERATOR ////////////////////////// + +// when requesting a template, you fill out this data +typedef struct +{ + int is_corner; // using corner colors or edge colors? + int short_side_len; // rectangles is 2n x n, n = short_side_len + int num_color[6]; // see below diagram for meaning of the index to this; + // 6 values if edge (!is_corner), 4 values if is_corner + // legal numbers: 1..8 if edge, 1..4 if is_corner + int num_vary_x; // additional number of variations along x axis in the template + int num_vary_y; // additional number of variations along y axis in the template + int corner_type_color_template[4][4]; + // if corner_type_color_template[s][t] is non-zero, then any + // corner of type s generated as color t will get a little + // corner sample markup in the template image data + +} stbhw_config; + +// computes the size needed for the template image +STBHW_EXTERN void stbhw_get_template_size(stbhw_config *c, int *w, int *h); + +// generates a template image, assuming data is 3*w*h bytes long, RGB format +STBHW_EXTERN int stbhw_make_template(stbhw_config *c, unsigned char *data, int w, int h, int stride_in_bytes); + +#endif//INCLUDE_STB_HWANG_H + + +// TILE CONSTRAINT TYPES +// +// there are 4 "types" of corners and 6 types of edges. +// you can configure the tileset to have different numbers +// of colors for each type of color or edge. +// +// corner types: +// +// 0---*---1---*---2---*---3 +// | | | +// * * * +// | | | +// 1---*---2---*---3 0---*---1---*---2 +// | | | +// * * * +// | | | +// 0---*---1---*---2---*---3 +// +// +// edge types: +// +// *---2---*---3---* *---0---* +// | | | | +// 1 4 5 1 +// | | | | +// *---0---*---2---* * * +// | | +// 4 5 +// | | +// *---3---* +// +// TILE CONSTRAINTS +// +// each corner/edge has a color; this shows the name +// of the variable containing the color +// +// corner constraints: +// +// a---*---d +// | | +// * * +// | | +// a---*---b---*---c b e +// | | | | +// * * * * +// | | | | +// d---*---e---*---f c---*---f +// +// +// edge constraints: +// +// *---a---*---b---* *---a---* +// | | | | +// c d b c +// | | | | +// *---e---*---f---* * * +// | | +// d e +// | | +// *---f---* +// + + +////////////////////////////////////////////////////////////////////////////// +// // +// IMPLEMENTATION SECTION // +// // + +#ifdef STB_HERRINGBONE_WANG_TILE_IMPLEMENTATION + + +#include // memcpy +#include // malloc + +#ifndef STB_HBWANG_RAND +#include +#define STB_HBWANG_RAND() (rand() >> 4) +#endif + +#ifndef STB_HBWANG_ASSERT +#include +#define STB_HBWANG_ASSERT(x) assert(x) +#endif + +// map size +#ifndef STB_HBWANG_MAX_X +#define STB_HBWANG_MAX_X 100 +#endif + +#ifndef STB_HBWANG_MAX_Y +#define STB_HBWANG_MAX_Y 100 +#endif + +// global variables for color assignments +// @MEMORY change these to just store last two/three rows +// and keep them on the stack +static signed char c_color[STB_HBWANG_MAX_Y+6][STB_HBWANG_MAX_X+6]; +static signed char v_color[STB_HBWANG_MAX_Y+6][STB_HBWANG_MAX_X+5]; +static signed char h_color[STB_HBWANG_MAX_Y+5][STB_HBWANG_MAX_X+6]; + +static const char *stbhw_error; +STBHW_EXTERN const char *stbhw_get_last_error(void) +{ + const char *temp = stbhw_error; + stbhw_error = 0; + return temp; +} + + + + +///////////////////////////////////////////////////////////// +// +// SHARED TEMPLATE-DESCRIPTION CODE +// +// Used by both template generator and tileset parser; by +// using the same code, they are locked in sync and we don't +// need to try to do more sophisticated parsing of edge color +// markup or something. + +typedef void stbhw__process_rect(struct stbhw__process *p, int xpos, int ypos, + int a, int b, int c, int d, int e, int f); + +typedef struct stbhw__process +{ + stbhw_tileset *ts; + stbhw_config *c; + stbhw__process_rect *process_h_rect; + stbhw__process_rect *process_v_rect; + unsigned char *data; + int stride,w,h; +} stbhw__process; + +static void stbhw__process_h_row(stbhw__process *p, + int xpos, int ypos, + int a0, int a1, + int b0, int b1, + int c0, int c1, + int d0, int d1, + int e0, int e1, + int f0, int f1, + int variants) +{ + int a,b,c,d,e,f,v; + + for (v=0; v < variants; ++v) + for (f=f0; f <= f1; ++f) + for (e=e0; e <= e1; ++e) + for (d=d0; d <= d1; ++d) + for (c=c0; c <= c1; ++c) + for (b=b0; b <= b1; ++b) + for (a=a0; a <= a1; ++a) { + p->process_h_rect(p, xpos, ypos, a,b,c,d,e,f); + xpos += 2*p->c->short_side_len + 3; + } +} + +static void stbhw__process_v_row(stbhw__process *p, + int xpos, int ypos, + int a0, int a1, + int b0, int b1, + int c0, int c1, + int d0, int d1, + int e0, int e1, + int f0, int f1, + int variants) +{ + int a,b,c,d,e,f,v; + + for (v=0; v < variants; ++v) + for (f=f0; f <= f1; ++f) + for (e=e0; e <= e1; ++e) + for (d=d0; d <= d1; ++d) + for (c=c0; c <= c1; ++c) + for (b=b0; b <= b1; ++b) + for (a=a0; a <= a1; ++a) { + p->process_v_rect(p, xpos, ypos, a,b,c,d,e,f); + xpos += p->c->short_side_len+3; + } +} + +static void stbhw__get_template_info(stbhw_config *c, int *w, int *h, int *h_count, int *v_count) +{ + int size_x,size_y; + int horz_count,vert_count; + + if (c->is_corner) { + int horz_w = c->num_color[1] * c->num_color[2] * c->num_color[3] * c->num_vary_x; + int horz_h = c->num_color[0] * c->num_color[1] * c->num_color[2] * c->num_vary_y; + + int vert_w = c->num_color[0] * c->num_color[3] * c->num_color[2] * c->num_vary_y; + int vert_h = c->num_color[1] * c->num_color[0] * c->num_color[3] * c->num_vary_x; + + int horz_x = horz_w * (2*c->short_side_len + 3); + int horz_y = horz_h * ( c->short_side_len + 3); + + int vert_x = vert_w * ( c->short_side_len + 3); + int vert_y = vert_h * (2*c->short_side_len + 3); + + horz_count = horz_w * horz_h; + vert_count = vert_w * vert_h; + + size_x = horz_x > vert_x ? horz_x : vert_x; + size_y = 2 + horz_y + 2 + vert_y; + } else { + int horz_w = c->num_color[0] * c->num_color[1] * c->num_color[2] * c->num_vary_x; + int horz_h = c->num_color[3] * c->num_color[4] * c->num_color[2] * c->num_vary_y; + + int vert_w = c->num_color[0] * c->num_color[5] * c->num_color[1] * c->num_vary_y; + int vert_h = c->num_color[3] * c->num_color[4] * c->num_color[5] * c->num_vary_x; + + int horz_x = horz_w * (2*c->short_side_len + 3); + int horz_y = horz_h * ( c->short_side_len + 3); + + int vert_x = vert_w * ( c->short_side_len + 3); + int vert_y = vert_h * (2*c->short_side_len + 3); + + horz_count = horz_w * horz_h; + vert_count = vert_w * vert_h; + + size_x = horz_x > vert_x ? horz_x : vert_x; + size_y = 2 + horz_y + 2 + vert_y; + } + if (w) *w = size_x; + if (h) *h = size_y; + if (h_count) *h_count = horz_count; + if (v_count) *v_count = vert_count; +} + +STBHW_EXTERN void stbhw_get_template_size(stbhw_config *c, int *w, int *h) +{ + stbhw__get_template_info(c, w, h, NULL, NULL); +} + +static int stbhw__process_template(stbhw__process *p) +{ + int i,j,k,q, ypos; + int size_x, size_y; + stbhw_config *c = p->c; + + stbhw__get_template_info(c, &size_x, &size_y, NULL, NULL); + + if (p->w < size_x || p->h < size_y) { + stbhw_error = "image too small for configuration"; + return 0; + } + + if (c->is_corner) { + ypos = 2; + for (k=0; k < c->num_color[2]; ++k) { + for (j=0; j < c->num_color[1]; ++j) { + for (i=0; i < c->num_color[0]; ++i) { + for (q=0; q < c->num_vary_y; ++q) { + stbhw__process_h_row(p, 0,ypos, + 0,c->num_color[1]-1, 0,c->num_color[2]-1, 0,c->num_color[3]-1, + i,i, j,j, k,k, + c->num_vary_x); + ypos += c->short_side_len + 3; + } + } + } + } + ypos += 2; + for (k=0; k < c->num_color[3]; ++k) { + for (j=0; j < c->num_color[0]; ++j) { + for (i=0; i < c->num_color[1]; ++i) { + for (q=0; q < c->num_vary_x; ++q) { + stbhw__process_v_row(p, 0,ypos, + 0,c->num_color[0]-1, 0,c->num_color[3]-1, 0,c->num_color[2]-1, + i,i, j,j, k,k, + c->num_vary_y); + ypos += (c->short_side_len*2) + 3; + } + } + } + } + assert(ypos == size_y); + } else { + ypos = 2; + for (k=0; k < c->num_color[3]; ++k) { + for (j=0; j < c->num_color[4]; ++j) { + for (i=0; i < c->num_color[2]; ++i) { + for (q=0; q < c->num_vary_y; ++q) { + stbhw__process_h_row(p, 0,ypos, + 0,c->num_color[2]-1, k,k, + 0,c->num_color[1]-1, j,j, + 0,c->num_color[0]-1, i,i, + c->num_vary_x); + ypos += c->short_side_len + 3; + } + } + } + } + ypos += 2; + for (k=0; k < c->num_color[3]; ++k) { + for (j=0; j < c->num_color[4]; ++j) { + for (i=0; i < c->num_color[5]; ++i) { + for (q=0; q < c->num_vary_x; ++q) { + stbhw__process_v_row(p, 0,ypos, + 0,c->num_color[0]-1, i,i, + 0,c->num_color[1]-1, j,j, + 0,c->num_color[5]-1, k,k, + c->num_vary_y); + ypos += (c->short_side_len*2) + 3; + } + } + } + } + assert(ypos == size_y); + } + return 1; +} + + +///////////////////////////////////////////////////////////// +// +// MAP GENERATOR +// + +static void stbhw__draw_pixel(unsigned char *output, int stride, int x, int y, unsigned char c[3]) +{ + memcpy(output + y*stride + x*3, c, 3); +} + +static void stbhw__draw_h_tile(unsigned char *output, int stride, int xmax, int ymax, int x, int y, stbhw_tile *h, int sz) +{ + int i,j; + for (j=0; j < sz; ++j) + if (y+j >= 0 && y+j < ymax) + for (i=0; i < sz*2; ++i) + if (x+i >= 0 && x+i < xmax) + stbhw__draw_pixel(output,stride, x+i,y+j, &h->pixels[(j*sz*2 + i)*3]); +} + +static void stbhw__draw_v_tile(unsigned char *output, int stride, int xmax, int ymax, int x, int y, stbhw_tile *h, int sz) +{ + int i,j; + for (j=0; j < sz*2; ++j) + if (y+j >= 0 && y+j < ymax) + for (i=0; i < sz; ++i) + if (x+i >= 0 && x+i < xmax) + stbhw__draw_pixel(output,stride, x+i,y+j, &h->pixels[(j*sz + i)*3]); +} + + +// randomly choose a tile that fits constraints for a given spot, and update the constraints +static stbhw_tile * stbhw__choose_tile(stbhw_tile **list, int numlist, + signed char *a, signed char *b, signed char *c, + signed char *d, signed char *e, signed char *f, + int **weighting) +{ + int i,n,m = 1<<30,pass; + for (pass=0; pass < 2; ++pass) { + n=0; + // pass #1: + // count number of variants that match this partial set of constraints + // pass #2: + // stop on randomly selected match + for (i=0; i < numlist; ++i) { + stbhw_tile *h = list[i]; + if ((*a < 0 || *a == h->a) && + (*b < 0 || *b == h->b) && + (*c < 0 || *c == h->c) && + (*d < 0 || *d == h->d) && + (*e < 0 || *e == h->e) && + (*f < 0 || *f == h->f)) { + if (weighting) + n += weighting[0][i]; + else + n += 1; + if (n > m) { + // use list[i] + // update constraints to reflect what we placed + *a = h->a; + *b = h->b; + *c = h->c; + *d = h->d; + *e = h->e; + *f = h->f; + return h; + } + } + } + if (n == 0) { + stbhw_error = "couldn't find tile matching constraints"; + return NULL; + } + m = STB_HBWANG_RAND() % n; + } + STB_HBWANG_ASSERT(0); + return NULL; +} + +static int stbhw__match(int x, int y) +{ + return c_color[y][x] == c_color[y+1][x+1]; +} + +static int stbhw__weighted(int num_options, int *weights) +{ + int k, total, choice; + total = 0; + for (k=0; k < num_options; ++k) + total += weights[k]; + choice = STB_HBWANG_RAND() % total; + total = 0; + for (k=0; k < num_options; ++k) { + total += weights[k]; + if (choice < total) + break; + } + STB_HBWANG_ASSERT(k < num_options); + return k; +} + +static int stbhw__change_color(int old_color, int num_options, int *weights) +{ + if (weights) { + int k, total, choice; + total = 0; + for (k=0; k < num_options; ++k) + if (k != old_color) + total += weights[k]; + choice = STB_HBWANG_RAND() % total; + total = 0; + for (k=0; k < num_options; ++k) { + if (k != old_color) { + total += weights[k]; + if (choice < total) + break; + } + } + STB_HBWANG_ASSERT(k < num_options); + return k; + } else { + int offset = 1+STB_HBWANG_RAND() % (num_options-1); + return (old_color+offset) % num_options; + } +} + + + +// generate a map that is w * h pixels (3-bytes each) +// returns 1 on success, 0 on error +STBHW_EXTERN int stbhw_generate_image(stbhw_tileset *ts, int **weighting, unsigned char *output, int stride, int w, int h) +{ + int sidelen = ts->short_side_len; + int xmax = (w / sidelen) + 6; + int ymax = (h / sidelen) + 6; + if (xmax > STB_HBWANG_MAX_X+6 || ymax > STB_HBWANG_MAX_Y+6) { + stbhw_error = "increase STB_HBWANG_MAX_X/Y"; + return 0; + } + + if (ts->is_corner) { + int i,j, ypos; + int *cc = ts->num_color; + + for (j=0; j < ymax; ++j) { + for (i=0; i < xmax; ++i) { + int p = (i-j+1)&3; // corner type + if (weighting==NULL || weighting[p]==0 || cc[p] == 1) + c_color[j][i] = STB_HBWANG_RAND() % cc[p]; + else + c_color[j][i] = stbhw__weighted(cc[p], weighting[p]); + } + } + #ifndef STB_HBWANG_NO_REPITITION_REDUCTION + // now go back through and make sure we don't have adjancent 3x2 vertices that are identical, + // to avoid really obvious repetition (which happens easily with extreme weights) + for (j=0; j < ymax-3; ++j) { + for (i=0; i < xmax-3; ++i) { + //int p = (i-j+1) & 3; // corner type // unused, not sure what the intent was so commenting it out + STB_HBWANG_ASSERT(i+3 < STB_HBWANG_MAX_X+6); + STB_HBWANG_ASSERT(j+3 < STB_HBWANG_MAX_Y+6); + if (stbhw__match(i,j) && stbhw__match(i,j+1) && stbhw__match(i,j+2) + && stbhw__match(i+1,j) && stbhw__match(i+1,j+1) && stbhw__match(i+1,j+2)) { + int p = ((i+1)-(j+1)+1) & 3; + if (cc[p] > 1) + c_color[j+1][i+1] = stbhw__change_color(c_color[j+1][i+1], cc[p], weighting ? weighting[p] : NULL); + } + if (stbhw__match(i,j) && stbhw__match(i+1,j) && stbhw__match(i+2,j) + && stbhw__match(i,j+1) && stbhw__match(i+1,j+1) && stbhw__match(i+2,j+1)) { + int p = ((i+2)-(j+1)+1) & 3; + if (cc[p] > 1) + c_color[j+1][i+2] = stbhw__change_color(c_color[j+1][i+2], cc[p], weighting ? weighting[p] : NULL); + } + } + } + #endif + + ypos = -1 * sidelen; + for (j = -1; ypos < h; ++j) { + // a general herringbone row consists of: + // horizontal left block, the bottom of a previous vertical, the top of a new vertical + int phase = (j & 3); + // displace horizontally according to pattern + if (phase == 0) { + i = 0; + } else { + i = phase-4; + } + for (;; i += 4) { + int xpos = i * sidelen; + if (xpos >= w) + break; + // horizontal left-block + if (xpos + sidelen*2 >= 0 && ypos >= 0) { + stbhw_tile *t = stbhw__choose_tile( + ts->h_tiles, ts->num_h_tiles, + &c_color[j+2][i+2], &c_color[j+2][i+3], &c_color[j+2][i+4], + &c_color[j+3][i+2], &c_color[j+3][i+3], &c_color[j+3][i+4], + weighting + ); + if (t == NULL) + return 0; + stbhw__draw_h_tile(output,stride,w,h, xpos, ypos, t, sidelen); + } + xpos += sidelen * 2; + // now we're at the end of a previous vertical one + xpos += sidelen; + // now we're at the start of a new vertical one + if (xpos < w) { + stbhw_tile *t = stbhw__choose_tile( + ts->v_tiles, ts->num_v_tiles, + &c_color[j+2][i+5], &c_color[j+3][i+5], &c_color[j+4][i+5], + &c_color[j+2][i+6], &c_color[j+3][i+6], &c_color[j+4][i+6], + weighting + ); + if (t == NULL) + return 0; + stbhw__draw_v_tile(output,stride,w,h, xpos, ypos, t, sidelen); + } + } + ypos += sidelen; + } + } else { + // @TODO edge-color repetition reduction + int i,j, ypos; + memset(v_color, -1, sizeof(v_color)); + memset(h_color, -1, sizeof(h_color)); + + ypos = -1 * sidelen; + for (j = -1; ypos= w) + break; + // horizontal left-block + if (xpos + sidelen*2 >= 0 && ypos >= 0) { + stbhw_tile *t = stbhw__choose_tile( + ts->h_tiles, ts->num_h_tiles, + &h_color[j+2][i+2], &h_color[j+2][i+3], + &v_color[j+2][i+2], &v_color[j+2][i+4], + &h_color[j+3][i+2], &h_color[j+3][i+3], + weighting + ); + if (t == NULL) return 0; + stbhw__draw_h_tile(output,stride,w,h, xpos, ypos, t, sidelen); + } + xpos += sidelen * 2; + // now we're at the end of a previous vertical one + xpos += sidelen; + // now we're at the start of a new vertical one + if (xpos < w) { + stbhw_tile *t = stbhw__choose_tile( + ts->v_tiles, ts->num_v_tiles, + &h_color[j+2][i+5], + &v_color[j+2][i+5], &v_color[j+2][i+6], + &v_color[j+3][i+5], &v_color[j+3][i+6], + &h_color[j+4][i+5], + weighting + ); + if (t == NULL) return 0; + stbhw__draw_v_tile(output,stride,w,h, xpos, ypos, t, sidelen); + } + } + ypos += sidelen; + } + } + return 1; +} + +static void stbhw__parse_h_rect(stbhw__process *p, int xpos, int ypos, + int a, int b, int c, int d, int e, int f) +{ + int len = p->c->short_side_len; + stbhw_tile *h = (stbhw_tile *) malloc(sizeof(*h)-1 + 3 * (len*2) * len); + int i,j; + ++xpos; + ++ypos; + h->a = a, h->b = b, h->c = c, h->d = d, h->e = e, h->f = f; + for (j=0; j < len; ++j) + for (i=0; i < len*2; ++i) + memcpy(h->pixels + j*(3*len*2) + i*3, p->data+(ypos+j)*p->stride+(xpos+i)*3, 3); + STB_HBWANG_ASSERT(p->ts->num_h_tiles < p->ts->max_h_tiles); + p->ts->h_tiles[p->ts->num_h_tiles++] = h; +} + +static void stbhw__parse_v_rect(stbhw__process *p, int xpos, int ypos, + int a, int b, int c, int d, int e, int f) +{ + int len = p->c->short_side_len; + stbhw_tile *h = (stbhw_tile *) malloc(sizeof(*h)-1 + 3 * (len*2) * len); + int i,j; + ++xpos; + ++ypos; + h->a = a, h->b = b, h->c = c, h->d = d, h->e = e, h->f = f; + for (j=0; j < len*2; ++j) + for (i=0; i < len; ++i) + memcpy(h->pixels + j*(3*len) + i*3, p->data+(ypos+j)*p->stride+(xpos+i)*3, 3); + STB_HBWANG_ASSERT(p->ts->num_v_tiles < p->ts->max_v_tiles); + p->ts->v_tiles[p->ts->num_v_tiles++] = h; +} + +STBHW_EXTERN int stbhw_build_tileset_from_image(stbhw_tileset *ts, unsigned char *data, int stride, int w, int h) +{ + int i, h_count, v_count; + unsigned char header[9]; + stbhw_config c = { 0 }; + stbhw__process p = { 0 }; + + // extract binary header + + // remove encoding that makes it more visually obvious it encodes actual data + for (i=0; i < 9; ++i) + header[i] = data[w*3 - 1 - i] ^ (i*55); + + // extract header info + if (header[7] == 0xc0) { + // corner-type + c.is_corner = 1; + for (i=0; i < 4; ++i) + c.num_color[i] = header[i]; + c.num_vary_x = header[4]; + c.num_vary_y = header[5]; + c.short_side_len = header[6]; + } else { + c.is_corner = 0; + // edge-type + for (i=0; i < 6; ++i) + c.num_color[i] = header[i]; + c.num_vary_x = header[6]; + c.num_vary_y = header[7]; + c.short_side_len = header[8]; + } + + if (c.num_vary_x < 0 || c.num_vary_x > 64 || c.num_vary_y < 0 || c.num_vary_y > 64) + return 0; + if (c.short_side_len == 0) + return 0; + if (c.num_color[0] > 32 || c.num_color[1] > 32 || c.num_color[2] > 32 || c.num_color[3] > 32) + return 0; + + stbhw__get_template_info(&c, NULL, NULL, &h_count, &v_count); + + ts->is_corner = c.is_corner; + ts->short_side_len = c.short_side_len; + memcpy(ts->num_color, c.num_color, sizeof(ts->num_color)); + + ts->max_h_tiles = h_count; + ts->max_v_tiles = v_count; + + ts->num_h_tiles = ts->num_v_tiles = 0; + + ts->h_tiles = (stbhw_tile **) malloc(sizeof(*ts->h_tiles) * h_count); + ts->v_tiles = (stbhw_tile **) malloc(sizeof(*ts->v_tiles) * v_count); + + p.ts = ts; + p.data = data; + p.stride = stride; + p.process_h_rect = stbhw__parse_h_rect; + p.process_v_rect = stbhw__parse_v_rect; + p.w = w; + p.h = h; + p.c = &c; + + // load all the tiles out of the image + return stbhw__process_template(&p); +} + +STBHW_EXTERN void stbhw_free_tileset(stbhw_tileset *ts) +{ + int i; + for (i=0; i < ts->num_h_tiles; ++i) + free(ts->h_tiles[i]); + for (i=0; i < ts->num_v_tiles; ++i) + free(ts->v_tiles[i]); + free(ts->h_tiles); + free(ts->v_tiles); + ts->h_tiles = NULL; + ts->v_tiles = NULL; + ts->num_h_tiles = ts->max_h_tiles = 0; + ts->num_v_tiles = ts->max_v_tiles = 0; +} + +////////////////////////////////////////////////////////////////////////////// +// +// GENERATOR +// +// + + +// shared code + +static void stbhw__set_pixel(unsigned char *data, int stride, int xpos, int ypos, unsigned char color[3]) +{ + memcpy(data + ypos*stride + xpos*3, color, 3); +} + +static void stbhw__stbhw__set_pixel_whiten(unsigned char *data, int stride, int xpos, int ypos, unsigned char color[3]) +{ + unsigned char c2[3]; + int i; + for (i=0; i < 3; ++i) + c2[i] = (color[i]*2 + 255)/3; + memcpy(data + ypos*stride + xpos*3, c2, 3); +} + + +static unsigned char stbhw__black[3] = { 0,0,0 }; + +// each edge set gets its own unique color variants +// used http://phrogz.net/css/distinct-colors.html to generate this set, +// but it's not very good and needs to be revised + +static unsigned char stbhw__color[7][8][3] = +{ + { {255,51,51} , {143,143,29}, {0,199,199}, {159,119,199}, {0,149,199} , {143, 0,143}, {255,128,0}, {64,255,0}, }, + { {235,255,30 }, {255,0,255}, {199,139,119}, {29,143, 57}, {143,0,71} , { 0,143,143}, {0,99,199}, {143,71,0}, }, + { {0,149,199} , {143, 0,143}, {255,128,0}, {64,255,0}, {255,191,0} , {51,255,153}, {0,0,143}, {199,119,159},}, + { {143,0,71} , { 0,143,143}, {0,99,199}, {143,71,0}, {255,190,153}, { 0,255,255}, {128,0,255}, {255,51,102},}, + { {255,191,0} , {51,255,153}, {0,0,143}, {199,119,159}, {255,51,51} , {143,143,29}, {0,199,199}, {159,119,199},}, + { {255,190,153}, { 0,255,255}, {128,0,255}, {255,51,102}, {235,255,30 }, {255,0,255}, {199,139,119}, {29,143, 57}, }, + + { {40,40,40 }, { 90,90,90 }, { 150,150,150 }, { 200,200,200 }, + { 255,90,90 }, { 160,160,80}, { 50,150,150 }, { 200,50,200 } }, +}; + +static void stbhw__draw_hline(unsigned char *data, int stride, int xpos, int ypos, int color, int len, int slot) +{ + int i; + int j = len * 6 / 16; + int k = len * 10 / 16; + for (i=0; i < len; ++i) + stbhw__set_pixel(data, stride, xpos+i, ypos, stbhw__black); + if (k-j < 2) { + j = len/2 - 1; + k = j+2; + if (len & 1) + ++k; + } + for (i=j; i < k; ++i) + stbhw__stbhw__set_pixel_whiten(data, stride, xpos+i, ypos, stbhw__color[slot][color]); +} + +static void stbhw__draw_vline(unsigned char *data, int stride, int xpos, int ypos, int color, int len, int slot) +{ + int i; + int j = len * 6 / 16; + int k = len * 10 / 16; + for (i=0; i < len; ++i) + stbhw__set_pixel(data, stride, xpos, ypos+i, stbhw__black); + if (k-j < 2) { + j = len/2 - 1; + k = j+2; + if (len & 1) + ++k; + } + for (i=j; i < k; ++i) + stbhw__stbhw__set_pixel_whiten(data, stride, xpos, ypos+i, stbhw__color[slot][color]); +} + +// 0--*--1--*--2--*--3 +// | | | +// * * * +// | | | +// 1--*--2--*--3 0--*--1--*--2 +// | | | +// * * * +// | | | +// 0--*--1--*--2--*--3 +// +// variables while enumerating (no correspondence between corners +// of the types is implied by these variables) +// +// a-----b-----c a-----d +// | | | | +// | | | | +// | | | | +// d-----e-----f b e +// | | +// | | +// | | +// c-----f +// + +unsigned char stbhw__corner_colors[4][4][3] = +{ + { { 255,0,0 }, { 200,200,200 }, { 100,100,200 }, { 255,200,150 }, }, + { { 0,0,255 }, { 255,255,0 }, { 100,200,100 }, { 150,255,200 }, }, + { { 255,0,255 }, { 80,80,80 }, { 200,100,100 }, { 200,150,255 }, }, + { { 0,255,255 }, { 0,255,0 }, { 200,120,200 }, { 255,200,200 }, }, +}; + +int stbhw__corner_colors_to_edge_color[4][4] = +{ + // 0 1 2 3 + { 0, 1, 4, 9, }, // 0 + { 2, 3, 5, 10, }, // 1 + { 6, 7, 8, 11, }, // 2 + { 12, 13, 14, 15, }, // 3 +}; + +#define stbhw__c2e stbhw__corner_colors_to_edge_color + +static void stbhw__draw_clipped_corner(unsigned char *data, int stride, int xpos, int ypos, int w, int h, int x, int y) +{ + static unsigned char template_color[3] = { 167,204,204 }; + int i,j; + for (j = -2; j <= 1; ++j) { + for (i = -2; i <= 1; ++i) { + if ((i == -2 || i == 1) && (j == -2 || j == 1)) + continue; + else { + if (x+i < 1 || x+i > w) continue; + if (y+j < 1 || y+j > h) continue; + stbhw__set_pixel(data, stride, xpos+x+i, ypos+y+j, template_color); + + } + } + } +} + +static void stbhw__edge_process_h_rect(stbhw__process *p, int xpos, int ypos, + int a, int b, int c, int d, int e, int f) +{ + int len = p->c->short_side_len; + stbhw__draw_hline(p->data, p->stride, xpos+1 , ypos , a, len, 2); + stbhw__draw_hline(p->data, p->stride, xpos+ len+1 , ypos , b, len, 3); + stbhw__draw_vline(p->data, p->stride, xpos , ypos+1 , c, len, 1); + stbhw__draw_vline(p->data, p->stride, xpos+2*len+1 , ypos+1 , d, len, 4); + stbhw__draw_hline(p->data, p->stride, xpos+1 , ypos + len+1, e, len, 0); + stbhw__draw_hline(p->data, p->stride, xpos + len+1 , ypos + len+1, f, len, 2); +} + +static void stbhw__edge_process_v_rect(stbhw__process *p, int xpos, int ypos, + int a, int b, int c, int d, int e, int f) +{ + int len = p->c->short_side_len; + stbhw__draw_hline(p->data, p->stride, xpos+1 , ypos , a, len, 0); + stbhw__draw_vline(p->data, p->stride, xpos , ypos+1 , b, len, 5); + stbhw__draw_vline(p->data, p->stride, xpos + len+1, ypos+1 , c, len, 1); + stbhw__draw_vline(p->data, p->stride, xpos , ypos + len+1, d, len, 4); + stbhw__draw_vline(p->data, p->stride, xpos + len+1, ypos + len+1, e, len, 5); + stbhw__draw_hline(p->data, p->stride, xpos+1 , ypos + 2*len+1, f, len, 3); +} + +static void stbhw__corner_process_h_rect(stbhw__process *p, int xpos, int ypos, + int a, int b, int c, int d, int e, int f) +{ + int len = p->c->short_side_len; + + stbhw__draw_hline(p->data, p->stride, xpos+1 , ypos , stbhw__c2e[a][b], len, 2); + stbhw__draw_hline(p->data, p->stride, xpos+ len+1 , ypos , stbhw__c2e[b][c], len, 3); + stbhw__draw_vline(p->data, p->stride, xpos , ypos+1 , stbhw__c2e[a][d], len, 1); + stbhw__draw_vline(p->data, p->stride, xpos+2*len+1 , ypos+1 , stbhw__c2e[c][f], len, 4); + stbhw__draw_hline(p->data, p->stride, xpos+1 , ypos + len+1, stbhw__c2e[d][e], len, 0); + stbhw__draw_hline(p->data, p->stride, xpos + len+1 , ypos + len+1, stbhw__c2e[e][f], len, 2); + + if (p->c->corner_type_color_template[1][a]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len*2,len, 1,1); + if (p->c->corner_type_color_template[2][b]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len*2,len, len+1,1); + if (p->c->corner_type_color_template[3][c]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len*2,len, len*2+1,1); + + if (p->c->corner_type_color_template[0][d]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len*2,len, 1,len+1); + if (p->c->corner_type_color_template[1][e]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len*2,len, len+1,len+1); + if (p->c->corner_type_color_template[2][f]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len*2,len, len*2+1,len+1); + + stbhw__set_pixel(p->data, p->stride, xpos , ypos, stbhw__corner_colors[1][a]); + stbhw__set_pixel(p->data, p->stride, xpos+len , ypos, stbhw__corner_colors[2][b]); + stbhw__set_pixel(p->data, p->stride, xpos+2*len+1, ypos, stbhw__corner_colors[3][c]); + stbhw__set_pixel(p->data, p->stride, xpos , ypos+len+1, stbhw__corner_colors[0][d]); + stbhw__set_pixel(p->data, p->stride, xpos+len , ypos+len+1, stbhw__corner_colors[1][e]); + stbhw__set_pixel(p->data, p->stride, xpos+2*len+1, ypos+len+1, stbhw__corner_colors[2][f]); +} + +static void stbhw__corner_process_v_rect(stbhw__process *p, int xpos, int ypos, + int a, int b, int c, int d, int e, int f) +{ + int len = p->c->short_side_len; + + stbhw__draw_hline(p->data, p->stride, xpos+1 , ypos , stbhw__c2e[a][d], len, 0); + stbhw__draw_vline(p->data, p->stride, xpos , ypos+1 , stbhw__c2e[a][b], len, 5); + stbhw__draw_vline(p->data, p->stride, xpos + len+1, ypos+1 , stbhw__c2e[d][e], len, 1); + stbhw__draw_vline(p->data, p->stride, xpos , ypos + len+1, stbhw__c2e[b][c], len, 4); + stbhw__draw_vline(p->data, p->stride, xpos + len+1, ypos + len+1, stbhw__c2e[e][f], len, 5); + stbhw__draw_hline(p->data, p->stride, xpos+1 , ypos + 2*len+1, stbhw__c2e[c][f], len, 3); + + if (p->c->corner_type_color_template[0][a]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len,len*2, 1,1); + if (p->c->corner_type_color_template[3][b]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len,len*2, 1,len+1); + if (p->c->corner_type_color_template[2][c]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len,len*2, 1,len*2+1); + + if (p->c->corner_type_color_template[1][d]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len,len*2, len+1,1); + if (p->c->corner_type_color_template[0][e]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len,len*2, len+1,len+1); + if (p->c->corner_type_color_template[3][f]) stbhw__draw_clipped_corner(p->data,p->stride, xpos,ypos, len,len*2, len+1,len*2+1); + + stbhw__set_pixel(p->data, p->stride, xpos , ypos , stbhw__corner_colors[0][a]); + stbhw__set_pixel(p->data, p->stride, xpos , ypos+len , stbhw__corner_colors[3][b]); + stbhw__set_pixel(p->data, p->stride, xpos , ypos+2*len+1, stbhw__corner_colors[2][c]); + stbhw__set_pixel(p->data, p->stride, xpos+len+1, ypos , stbhw__corner_colors[1][d]); + stbhw__set_pixel(p->data, p->stride, xpos+len+1, ypos+len , stbhw__corner_colors[0][e]); + stbhw__set_pixel(p->data, p->stride, xpos+len+1, ypos+2*len+1, stbhw__corner_colors[3][f]); +} + +// generates a template image, assuming data is 3*w*h bytes long, RGB format +STBHW_EXTERN int stbhw_make_template(stbhw_config *c, unsigned char *data, int w, int h, int stride_in_bytes) +{ + stbhw__process p; + int i; + + p.data = data; + p.w = w; + p.h = h; + p.stride = stride_in_bytes; + p.ts = 0; + p.c = c; + + if (c->is_corner) { + p.process_h_rect = stbhw__corner_process_h_rect; + p.process_v_rect = stbhw__corner_process_v_rect; + } else { + p.process_h_rect = stbhw__edge_process_h_rect; + p.process_v_rect = stbhw__edge_process_v_rect; + } + + for (i=0; i < p.h; ++i) + memset(p.data + i*p.stride, 255, 3*p.w); + + if (!stbhw__process_template(&p)) + return 0; + + if (c->is_corner) { + // write out binary information in first line of image + for (i=0; i < 4; ++i) + data[w*3-1-i] = c->num_color[i]; + data[w*3-1-i] = c->num_vary_x; + data[w*3-2-i] = c->num_vary_y; + data[w*3-3-i] = c->short_side_len; + data[w*3-4-i] = 0xc0; + } else { + for (i=0; i < 6; ++i) + data[w*3-1-i] = c->num_color[i]; + data[w*3-1-i] = c->num_vary_x; + data[w*3-2-i] = c->num_vary_y; + data[w*3-3-i] = c->short_side_len; + } + + // make it more obvious it encodes actual data + for (i=0; i < 9; ++i) + p.data[p.w*3 - 1 - i] ^= i*55; + + return 1; +} +#endif // STB_HBWANG_IMPLEMENTATION diff --git a/source/engine/thirdparty/stb/stb_hexwave.h b/source/engine/thirdparty/stb/stb_hexwave.h new file mode 100644 index 0000000..480ab1b --- /dev/null +++ b/source/engine/thirdparty/stb/stb_hexwave.h @@ -0,0 +1,680 @@ +// stb_hexwave - v0.5 - public domain, initial release 2021-04-01 +// +// A flexible anti-aliased (bandlimited) digital audio oscillator. +// +// This library generates waveforms of a variety of shapes made of +// line segments. It does not do envelopes, LFO effects, etc.; it +// merely tries to solve the problem of generating an artifact-free +// morphable digital waveform with a variety of spectra, and leaves +// it to the user to rescale the waveform and mix multiple voices, etc. +// +// Compiling: +// +// In one C/C++ file that #includes this file, do +// +// #define STB_HEXWAVE_IMPLEMENTATION +// #include "stb_hexwave.h" +// +// Optionally, #define STB_HEXWAVE_STATIC before including +// the header to cause the definitions to be private to the +// implementation file (i.e. to be "static" instead of "extern"). +// +// Notes: +// +// Optionally performs memory allocation during initialization, +// never allocates otherwise. +// +// License: +// +// See end of file for license information. +// +// Usage: +// +// Initialization: +// +// hexwave_init(32,16,NULL); // read "header section" for alternatives +// +// Create oscillator: +// +// HexWave *osc = malloc(sizeof(*osc)); // or "new HexWave", or declare globally or on stack +// hexwave_create(osc, reflect_flag, peak_time, half_height, zero_wait); +// see "Waveform shapes" below for the meaning of these parameters +// +// Generate audio: +// +// hexwave_generate_samples(output, number_of_samples, osc, oscillator_freq) +// where: +// output is a buffer where the library will store floating point audio samples +// number_of_samples is the number of audio samples to generate +// osc is a pointer to a Hexwave +// oscillator_freq is the frequency of the oscillator divided by the sample rate +// +// The output samples will continue from where the samples generated by the +// previous hexwave_generate_samples() on this oscillator ended. +// +// Change oscillator waveform: +// +// hexwave_change(osc, reflect_flag, peak_time, half_height, zero_wait); +// can call in between calls to hexwave_generate_samples +// +// Waveform shapes: +// +// All waveforms generated by hexwave are constructed from six line segments +// characterized by 3 parameters. +// +// See demonstration: https://www.youtube.com/watch?v=hsUCrAsDN-M +// +// reflect=0 reflect=1 +// +// 0-----P---1 0-----P---1 peak_time = P +// . 1 . 1 +// /\_ : /\_ : +// / \_ : / \_ : +// / \.H / \.H half_height = H +// / | : / | : +// _____/ |_:___ _____/ | : _____ +// . : \ | . | : / +// . : \ | . | : / +// . : \ _/ . \_: / +// . : \ _/ . :_ / +// . -1 \/ . -1 \/ +// 0 - Z - - - - 1 0 - Z - - - - 1 zero_wait = Z +// +// Classic waveforms: +// peak half zero +// reflect time height wait +// Sawtooth 1 0 0 0 +// Square 1 0 1 0 +// Triangle 1 0.5 0 0 +// +// Some waveforms can be produced in multiple ways, which is useful when morphing +// into other waveforms, and there are a few more notable shapes: +// +// peak half zero +// reflect time height wait +// Sawtooth 1 1 any 0 +// Sawtooth (8va) 1 0 -1 0 +// Triangle 1 0.5 0 0 +// Square 1 0 1 0 +// Square 0 0 1 0 +// Triangle 0 0.5 0 0 +// Triangle 0 0 -1 0 +// AlternatingSaw 0 0 0 0 +// AlternatingSaw 0 1 any 0 +// Stairs 0 0 1 0.5 +// +// The "Sawtooth (8va)" waveform is identical to a sawtooth wave with 2x the +// frequency, but when morphed with other values, it becomes an overtone of +// the base frequency. +// +// Morphing waveforms: +// +// Sweeping peak_time morphs the waveform while producing various spectra. +// Sweeping half_height effectively crossfades between two waveforms; useful, but less exciting. +// Sweeping zero_wait produces a similar effect no matter the reset of the waveform, +// a sort of high-pass/PWM effect where the wave becomes silent at zero_wait=1. +// +// You can trivially morph between any two waveforms from the above table +// which only differ in one column. +// +// Crossfade between classic waveforms: +// peak half zero +// Start End reflect time height wait +// ----- --- ------- ---- ------ ---- +// Triangle Square 0 0 -1..1 0 +// Saw Square 1 0 0..1 0 +// Triangle Saw 1 0.5 0..2 0 +// +// The last morph uses uses half-height values larger than 1, which means it will +// be louder and the output should be scaled down by half to compensate, or better +// by dynamically tracking the morph: volume_scale = 1 - half_height/4 +// +// Non-crossfade morph between classic waveforms, most require changing +// two parameters at the same time: +// peak half zero +// Start End reflect time height wait +// ----- --- ------- ---- ------ ---- +// Square Triangle any 0..0.5 1..0 0 +// Square Saw 1 0..1 1..any 0 +// Triangle Saw 1 0.5..1 0..-1 0 +// +// Other noteworthy morphs between simple shapes: +// peak half zero +// Start Halfway End reflect time height wait +// ----- --------- --- ------- ---- ------ ---- +// Saw (8va,neg) Saw (pos) 1 0..1 -1 0 +// Saw (neg) Saw (pos) 1 0..1 0 0 +// Triangle AlternatingSaw 0 0..1 -1 0 +// AlternatingSaw Triangle AlternatingSaw 0 0..1 0 0 +// Square AlternatingSaw 0 0..1 1 0 +// Triangle Triangle AlternatingSaw 0 0..1 -1..1 0 +// Square AlternatingSaw 0 0..1 1..0 0 +// Saw (8va) Triangle Saw 1 0..1 -1..1 0 +// Saw (neg) Saw (pos) 1 0..1 0..1 0 +// AlternatingSaw AlternatingSaw 0 0..1 0..any 0 +// +// The last entry is noteworthy because the morph from the halfway point to either +// endpoint sounds very different. For example, an LFO sweeping back and forth over +// the whole range will morph between the middle timbre and the AlternatingSaw +// timbre in two different ways, alternating. +// +// Entries with "any" for half_height are whole families of morphs, as you can pick +// any value you want as the endpoint for half_height. +// +// You can always morph between any two waveforms with the same value of 'reflect' +// by just sweeping the parameters simultaneously. There will never be artifacts +// and the result will always be useful, if not necessarily what you want. +// +// You can vary the sound of two-parameter morphs by ramping them differently, +// e.g. if the morph goes from t=0..1, then square-to-triangle looks like: +// peak_time = lerp(t, 0, 0.5) +// half_height = lerp(t, 1, 0 ) +// but you can also do things like: +// peak_time = lerp(smoothstep(t), 0, 0.5) +// half_height = cos(PI/2 * t) +// +// How it works: +// +// hexwave use BLEP to bandlimit discontinuities and BLAMP +// to bandlimit C1 discontinuities. This is not polyBLEP +// (polynomial BLEP), it is table-driven BLEP. It is +// also not minBLEP (minimum-phase BLEP), as that complicates +// things for little benefit once BLAMP is involved. +// +// The previous oscillator frequency is remembered, and when +// the frequency changes, a BLAMP is generated to remove the +// C1 discontinuity, which reduces artifacts for sweeps/LFO. +// +// Changes to an oscillator timbre using hexwave_change() actually +// wait until the oscillator finishes its current cycle. All +// waveforms with non-zero "zero_wait" settings pass through 0 +// and have 0-slope at the start of a cycle, which means changing +// the settings is artifact free at that time. (If zero_wait is 0, +// the code still treats it as passing through 0 with 0-slope; it'll +// apply the necessary fixups to make it artifact free as if it does +// transition to 0 with 0-slope vs. the waveform at the end of +// the cycle, then adds the fixups for a non-0 and non-0 slope +// at the start of the cycle, which cancels out if zero_wait is 0, +// and still does the right thing if zero_wait is 0 when the +// settings are updated.) +// +// BLEP/BLAMP normally requires overlapping buffers, but this +// is hidden from the user by generating the waveform to a +// temporary buffer and saving the overlap regions internally +// between calls. (It is slightly more complicated; see code.) +// +// By design all shapes have 0 DC offset; this is one reason +// hexwave uses zero_wait instead of standard PWM. +// +// The internals of hexwave could support any arbitrary shape +// made of line segments, but I chose not to expose this +// generality in favor of a simple, easy-to-use API. + +#ifndef STB_INCLUDE_STB_HEXWAVE_H +#define STB_INCLUDE_STB_HEXWAVE_H + +#ifndef STB_HEXWAVE_MAX_BLEP_LENGTH +#define STB_HEXWAVE_MAX_BLEP_LENGTH 64 // good enough for anybody +#endif + +#ifdef STB_HEXWAVE_STATIC +#define STB_HEXWAVE_DEF static +#else +#define STB_HEXWAVE_DEF extern +#endif + +typedef struct HexWave HexWave; + +STB_HEXWAVE_DEF void hexwave_init(int width, int oversample, float *user_buffer); +// width: size of BLEP, from 4..64, larger is slower & more memory but less aliasing +// oversample: 2+, number of subsample positions, larger uses more memory but less noise +// user_buffer: optional, if provided the library will perform no allocations. +// 16*width*(oversample+1) bytes, must stay allocated as long as library is used +// technically it only needs: 8*( width * (oversample + 1)) +// + 8*((width * oversample) + 1) bytes +// +// width can be larger than 64 if you define STB_HEXWAVE_MAX_BLEP_LENGTH to a larger value + +STB_HEXWAVE_DEF void hexwave_shutdown(float *user_buffer); +// user_buffer: pass in same parameter as passed to hexwave_init + +STB_HEXWAVE_DEF void hexwave_create(HexWave *hex, int reflect, float peak_time, float half_height, float zero_wait); +// see docs above for description +// +// reflect is tested as 0 or non-zero +// peak_time is clamped to 0..1 +// half_height is not clamped +// zero_wait is clamped to 0..1 + +STB_HEXWAVE_DEF void hexwave_change(HexWave *hex, int reflect, float peak_time, float half_height, float zero_wait); +// see docs + +STB_HEXWAVE_DEF void hexwave_generate_samples(float *output, int num_samples, HexWave *hex, float freq); +// output: buffer where the library will store generated floating point audio samples +// number_of_samples: the number of audio samples to generate +// osc: pointer to a Hexwave initialized with 'hexwave_create' +// oscillator_freq: frequency of the oscillator divided by the sample rate + +// private: +typedef struct +{ + int reflect; + float peak_time; + float zero_wait; + float half_height; +} HexWaveParameters; + +struct HexWave +{ + float t, prev_dt; + HexWaveParameters current, pending; + int have_pending; + float buffer[STB_HEXWAVE_MAX_BLEP_LENGTH]; +}; +#endif + +#ifdef STB_HEXWAVE_IMPLEMENTATION + +#ifndef STB_HEXWAVE_NO_ALLOCATION +#include // malloc,free +#endif + +#include // memset,memcpy,memmove +#include // sin,cos,fabs + +#define hexwave_clamp(v,a,b) ((v) < (a) ? (a) : (v) > (b) ? (b) : (v)) + +STB_HEXWAVE_DEF void hexwave_change(HexWave *hex, int reflect, float peak_time, float half_height, float zero_wait) +{ + hex->pending.reflect = reflect; + hex->pending.peak_time = hexwave_clamp(peak_time,0,1); + hex->pending.half_height = half_height; + hex->pending.zero_wait = hexwave_clamp(zero_wait,0,1); + // put a barrier here to allow changing from a different thread than the generator + hex->have_pending = 1; +} + +STB_HEXWAVE_DEF void hexwave_create(HexWave *hex, int reflect, float peak_time, float half_height, float zero_wait) +{ + memset(hex, 0, sizeof(*hex)); + hexwave_change(hex, reflect, peak_time, half_height, zero_wait); + hex->current = hex->pending; + hex->have_pending = 0; + hex->t = 0; + hex->prev_dt = 0; +} + +static struct +{ + int width; // width of fixup in samples + int oversample; // number of oversampled versions (there's actually one more to allow lerpign) + float *blep; + float *blamp; +} hexblep; + +static void hex_add_oversampled_bleplike(float *output, float time_since_transition, float scale, float *data) +{ + float *d1,*d2; + float lerpweight; + int i, bw = hexblep.width; + + int slot = (int) (time_since_transition * hexblep.oversample); + if (slot >= hexblep.oversample) + slot = hexblep.oversample-1; // clamp in case the floats overshoot + + d1 = &data[ slot *bw]; + d2 = &data[(slot+1)*bw]; + + lerpweight = time_since_transition * hexblep.oversample - slot; + for (i=0; i < bw; ++i) + output[i] += scale * (d1[i] + (d2[i]-d1[i])*lerpweight); +} + +static void hex_blep (float *output, float time_since_transition, float scale) +{ + hex_add_oversampled_bleplike(output, time_since_transition, scale, hexblep.blep); +} + +static void hex_blamp(float *output, float time_since_transition, float scale) +{ + hex_add_oversampled_bleplike(output, time_since_transition, scale, hexblep.blamp); +} + +typedef struct +{ + float t,v,s; // time, value, slope +} hexvert; + +// each half of the waveform needs 4 vertices to represent 3 line +// segments, plus 1 more for wraparound +static void hexwave_generate_linesegs(hexvert vert[9], HexWave *hex, float dt) +{ + int j; + float min_len = dt / 256.0f; + + vert[0].t = 0; + vert[0].v = 0; + vert[1].t = hex->current.zero_wait*0.5f; + vert[1].v = 0; + vert[2].t = 0.5f*hex->current.peak_time + vert[1].t*(1-hex->current.peak_time); + vert[2].v = 1; + vert[3].t = 0.5f; + vert[3].v = hex->current.half_height; + + if (hex->current.reflect) { + for (j=4; j <= 7; ++j) { + vert[j].t = 1 - vert[7-j].t; + vert[j].v = - vert[7-j].v; + } + } else { + for (j=4; j <= 7; ++j) { + vert[j].t = 0.5f + vert[j-4].t; + vert[j].v = - vert[j-4].v; + } + } + vert[8].t = 1; + vert[8].v = 0; + + for (j=0; j < 8; ++j) { + if (vert[j+1].t <= vert[j].t + min_len) { + // if change takes place over less than a fraction of a sample treat as discontinuity + // + // otherwise the slope computation can blow up to arbitrarily large and we + // try to generate a huge BLAMP and the result is wrong. + // + // why does this happen if the math is right? i believe if done perfectly, + // the two BLAMPs on either side of the slope would cancel out, but our + // BLAMPs have only limited sub-sample precision and limited integration + // accuracy. or maybe it's just the math blowing up w/ floating point precision + // limits as we try to make x * (1/x) cancel out + // + // min_len verified artifact-free even near nyquist with only oversample=4 + vert[j+1].t = vert[j].t; + } + } + + if (vert[8].t != 1.0f) { + // if the above fixup moved the endpoint away from 1.0, move it back, + // along with any other vertices that got moved to the same time + float t = vert[8].t; + for (j=5; j <= 8; ++j) + if (vert[j].t == t) + vert[j].t = 1.0f; + } + + // compute the exact slopes from the final fixed-up positions + for (j=0; j < 8; ++j) + if (vert[j+1].t == vert[j].t) + vert[j].s = 0; + else + vert[j].s = (vert[j+1].v - vert[j].v) / (vert[j+1].t - vert[j].t); + + // wraparound at end + vert[8].t = 1; + vert[8].v = vert[0].v; + vert[8].s = vert[0].s; +} + +STB_HEXWAVE_DEF void hexwave_generate_samples(float *output, int num_samples, HexWave *hex, float freq) +{ + hexvert vert[9]; + int pass,i,j; + float t = hex->t; + float temp_output[2*STB_HEXWAVE_MAX_BLEP_LENGTH]; + int buffered_length = sizeof(float)*hexblep.width; + float dt = (float) fabs(freq); + float recip_dt = (dt == 0.0f) ? 0.0f : 1.0f / dt; + + int halfw = hexblep.width/2; + // all sample times are biased by halfw to leave room for BLEP/BLAMP to go back in time + + if (num_samples <= 0) + return; + + // convert parameters to times and slopes + hexwave_generate_linesegs(vert, hex, dt); + + if (hex->prev_dt != dt) { + // if frequency changes, add a fixup at the derivative discontinuity starting at now + float slope; + for (j=1; j < 6; ++j) + if (t < vert[j].t) + break; + slope = vert[j].s; + if (slope != 0) + hex_blamp(output, 0, (dt - hex->prev_dt)*slope); + hex->prev_dt = dt; + } + + // copy the buffered data from last call and clear the rest of the output array + memset(output, 0, sizeof(float)*num_samples); + memset(temp_output, 0, 2*hexblep.width*sizeof(float)); + + if (num_samples >= hexblep.width) { + memcpy(output, hex->buffer, buffered_length); + } else { + // if the output is shorter than hexblep.width, we do all synthesis to temp_output + memcpy(temp_output, hex->buffer, buffered_length); + } + + for (pass=0; pass < 2; ++pass) { + int i0,i1; + float *out; + + // we want to simulate having one buffer that is num_output + hexblep.width + // samples long, without putting that requirement on the user, and without + // allocating a temp buffer that's as long as the whole thing. so we use two + // overlapping buffers, one the user's buffer and one a fixed-length temp + // buffer. + + if (pass == 0) { + if (num_samples < hexblep.width) + continue; + // run as far as we can without overwriting the end of the user's buffer + out = output; + i0 = 0; + i1 = num_samples - hexblep.width; + } else { + // generate the rest into a temp buffer + out = temp_output; + i0 = 0; + if (num_samples >= hexblep.width) + i1 = hexblep.width; + else + i1 = num_samples; + } + + // determine current segment + for (j=0; j < 8; ++j) + if (t < vert[j+1].t) + break; + + i = i0; + for(;;) { + while (t < vert[j+1].t) { + if (i == i1) + goto done; + out[i+halfw] += vert[j].v + vert[j].s*(t - vert[j].t); + t += dt; + ++i; + } + // transition from lineseg starting at j to lineseg starting at j+1 + + if (vert[j].t == vert[j+1].t) + hex_blep(out+i, recip_dt*(t-vert[j+1].t), (vert[j+1].v - vert[j].v)); + hex_blamp(out+i, recip_dt*(t-vert[j+1].t), dt*(vert[j+1].s - vert[j].s)); + ++j; + + if (j == 8) { + // change to different waveform if there's a change pending + j = 0; + t -= 1.0; // t was >= 1.f if j==8 + if (hex->have_pending) { + float prev_s0 = vert[j].s; + float prev_v0 = vert[j].v; + hex->current = hex->pending; + hex->have_pending = 0; + hexwave_generate_linesegs(vert, hex, dt); + // the following never occurs with this oscillator, but it makes + // the code work in more general cases + if (vert[j].v != prev_v0) + hex_blep (out+i, recip_dt*t, (vert[j].v - prev_v0)); + if (vert[j].s != prev_s0) + hex_blamp(out+i, recip_dt*t, dt*(vert[j].s - prev_s0)); + } + } + } + done: + ; + } + + // at this point, we've written output[] and temp_output[] + if (num_samples >= hexblep.width) { + // the first half of temp[] overlaps the end of output, the second half will be the new start overlap + for (i=0; i < hexblep.width; ++i) + output[num_samples-hexblep.width + i] += temp_output[i]; + memcpy(hex->buffer, temp_output+hexblep.width, buffered_length); + } else { + for (i=0; i < num_samples; ++i) + output[i] = temp_output[i]; + memcpy(hex->buffer, temp_output+num_samples, buffered_length); + } + + hex->t = t; +} + +STB_HEXWAVE_DEF void hexwave_shutdown(float *user_buffer) +{ + #ifndef STB_HEXWAVE_NO_ALLOCATION + if (user_buffer != 0) { + free(hexblep.blep); + free(hexblep.blamp); + } + #endif +} + +// buffer should be NULL or must be 4*(width*(oversample+1)*2 + +STB_HEXWAVE_DEF void hexwave_init(int width, int oversample, float *user_buffer) +{ + int halfwidth = width/2; + int half = halfwidth*oversample; + int blep_buffer_count = width*(oversample+1); + int n = 2*half+1; +#ifdef STB_HEXWAVE_NO_ALLOCATION + float *buffers = user_buffer; +#else + float *buffers = user_buffer ? user_buffer : (float *) malloc(sizeof(float) * n * 2); +#endif + float *step = buffers+0*n; + float *ramp = buffers+1*n; + float *blep_buffer, *blamp_buffer; + double integrate_impulse=0, integrate_step=0; + int i,j; + + if (width > STB_HEXWAVE_MAX_BLEP_LENGTH) + width = STB_HEXWAVE_MAX_BLEP_LENGTH; + + if (user_buffer == 0) { + #ifndef STB_HEXWAVE_NO_ALLOCATION + blep_buffer = (float *) malloc(sizeof(float)*blep_buffer_count); + blamp_buffer = (float *) malloc(sizeof(float)*blep_buffer_count); + #endif + } else { + blep_buffer = ramp+n; + blamp_buffer = blep_buffer + blep_buffer_count; + } + + // compute BLEP and BLAMP by integerating windowed sinc + for (i=0; i < n; ++i) { + for (j=0; j < 16; ++j) { + float sinc_t = 3.141592f* (i-half) / oversample; + float sinc = (i==half) ? 1.0f : (float) sin(sinc_t) / (sinc_t); + float wt = 2.0f*3.1415926f * i / (n-1); + float window = (float) (0.355768 - 0.487396*cos(wt) + 0.144232*cos(2*wt) - 0.012604*cos(3*wt)); // Nuttall + double value = window * sinc; + integrate_impulse += value/16; + integrate_step += integrate_impulse/16; + } + step[i] = (float) integrate_impulse; + ramp[i] = (float) integrate_step; + } + + // renormalize + for (i=0; i < n; ++i) { + step[i] = step[i] * (float) (1.0 / step[n-1]); // step needs to reach to 1.0 + ramp[i] = ramp[i] * (float) (halfwidth / ramp[n-1]); // ramp needs to become a slope of 1.0 after oversampling + } + + // deinterleave to allow efficient interpolation e.g. w/SIMD + for (j=0; j <= oversample; ++j) { + for (i=0; i < width; ++i) { + blep_buffer [j*width+i] = step[j+i*oversample]; + blamp_buffer[j*width+i] = ramp[j+i*oversample]; + } + } + + // subtract out the naive waveform; note we can't do this to the raw data + // above, because we want the discontinuity to be in a different locations + // for j=0 and j=oversample (which exists to provide something to interpolate against) + for (j=0; j <= oversample; ++j) { + // subtract step + for (i=halfwidth; i < width; ++i) + blep_buffer [j*width+i] -= 1.0f; + // subtract ramp + for (i=halfwidth; i < width; ++i) + blamp_buffer[j*width+i] -= (j+i*oversample-half)*(1.0f/oversample); + } + + hexblep.blep = blep_buffer; + hexblep.blamp = blamp_buffer; + hexblep.width = width; + hexblep.oversample = oversample; + + #ifndef STB_HEXWAVE_NO_ALLOCATION + if (user_buffer == 0) + free(buffers); + #endif +} +#endif // STB_HEXWAVE_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_image.h b/source/engine/thirdparty/stb/stb_image.h new file mode 100644 index 0000000..a632d54 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_image.h @@ -0,0 +1,7985 @@ +/* stb_image - v2.29 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.29 (2023-05-xx) optimizations + 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data); +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#if defined(_MSC_VER) || defined(__SYMBIAN32__) +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. +static int stbi__addints_valid(int a, int b) +{ + if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow + if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. + return a <= INT_MAX - b; +} + +// returns 1 if the product of two ints fits in a signed short, 0 on overflow. +static int stbi__mul2shorts_valid(int a, int b) +{ + if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow + if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid + if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN + return a >= SHRT_MIN / b; +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) { + for (j=0; j < count[i]; ++j) { + h->size[k++] = (stbi_uc) (i+1); + if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); + } + } + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + if(c < 0 || c >= 256) // symbol id out of bounds! + return -1; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +{ + // some JPEGs have junk at end, skip over it but if we find what looks + // like a valid marker, resume there + while (!stbi__at_eof(j->s)) { + stbi_uc x = stbi__get8(j->s); + while (x == 0xff) { // might be a marker + if (stbi__at_eof(j->s)) return STBI__MARKER_none; + x = stbi__get8(j->s); + if (x != 0x00 && x != 0xff) { + // not a stuffed zero or lead-in to another marker, looks + // like an actual marker, return it + return x; + } + // stuffed zero has x=0 now which ends the loop, meaning we go + // back to regular scan loop. + // repeated 0xff keeps trying to read the next byte of the marker. + } + } + return STBI__MARKER_none; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + j->marker = stbi__skip_jpeg_junk_at_end(j); + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + m = stbi__get_marker(j); + if (STBI__RESTART(m)) + m = stbi__get_marker(j); + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + m = stbi__get_marker(j); + } else { + if (!stbi__process_marker(j, m)) return 1; + m = stbi__get_marker(j); + } + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + int hit_zeof_once; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + if (!a->hit_zeof_once) { + // This is the first time we hit eof, insert 16 extra padding btis + // to allow us to keep going; if we actually consume any of them + // though, that is invalid data. This is caught later. + a->hit_zeof_once = 1; + a->num_bits += 16; // add 16 implicit zero bits + } else { + // We already inserted our extra 16 padding bits and are again + // out, this stream is actually prematurely terminated. + return -1; + } + } else { + stbi__fill_bits(a); + } + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + if (a->hit_zeof_once && a->num_bits < 16) { + // The first time we hit zeof, we inserted 16 extra zero bits into our bit + // buffer so the decoder can just do its speculative decoding. But if we + // actually consumed any of those bits (which is the case when num_bits < 16), + // the stream actually read past the end so it is malformed. + return stbi__err("unexpected end","Corrupt PNG"); + } + return 1; + } + if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (len > a->zout_end - zout) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + a->hit_zeof_once = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filter used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub +}; + +static int stbi__paeth(int a, int b, int c) +{ + // This formulation looks very different from the reference in the PNG spec, but is + // actually equivalent and has favorable data dependencies and admits straightforward + // generation of branch-free code, which helps performance significantly. + int thresh = c*3 - (a + b); + int lo = a < b ? a : b; + int hi = a < b ? b : a; + int t0 = (hi <= thresh) ? lo : c; + int t1 = (thresh <= lo) ? hi : t0; + return t1; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// adds an extra all-255 alpha channel +// dest == src is legal +// img_n must be 1 or 3 +static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n) +{ + int i; + // must process data backwards since we allow dest==src + if (img_n == 1) { + for (i=x-1; i >= 0; --i) { + dest[i*2+1] = 255; + dest[i*2+0] = src[i]; + } + } else { + STBI_ASSERT(img_n == 3); + for (i=x-1; i >= 0; --i) { + dest[i*4+3] = 255; + dest[i*4+2] = src[i*3+2]; + dest[i*4+1] = src[i*3+1]; + dest[i*4+0] = src[i*3+0]; + } + } +} + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16 ? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + stbi_uc *filter_buf; + int all_ok = 1; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + // note: error exits here don't need to clean up a->out individually, + // stbi__do_png always does on error. + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG"); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + // Allocate two scan lines worth of filter workspace buffer. + filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0); + if (!filter_buf) return stbi__err("outofmem", "Out of memory"); + + // Filtering for low-bit-depth images + if (depth < 8) { + filter_bytes = 1; + width = img_width_bytes; + } + + for (j=0; j < y; ++j) { + // cur/prior filter buffers alternate + stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes; + stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes; + stbi_uc *dest = a->out + stride*j; + int nk = width * filter_bytes; + int filter = *raw++; + + // check filter type + if (filter > 4) { + all_ok = stbi__err("invalid filter","Corrupt PNG"); + break; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // perform actual filtering + switch (filter) { + case STBI__F_none: + memcpy(cur, raw, nk); + break; + case STBI__F_sub: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); + break; + case STBI__F_up: + for (k = 0; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); + break; + case STBI__F_avg: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); + break; + case STBI__F_paeth: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes])); + break; + case STBI__F_avg_first: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); + break; + } + + raw += nk; + + // expand decoded bits in cur to dest, also adding an extra alpha channel if desired + if (depth < 8) { + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + stbi_uc *in = cur; + stbi_uc *out = dest; + stbi_uc inb = 0; + stbi__uint32 nsmp = x*img_n; + + // expand bits to bytes first + if (depth == 4) { + for (i=0; i < nsmp; ++i) { + if ((i & 1) == 0) inb = *in++; + *out++ = scale * (inb >> 4); + inb <<= 4; + } + } else if (depth == 2) { + for (i=0; i < nsmp; ++i) { + if ((i & 3) == 0) inb = *in++; + *out++ = scale * (inb >> 6); + inb <<= 2; + } + } else { + STBI_ASSERT(depth == 1); + for (i=0; i < nsmp; ++i) { + if ((i & 7) == 0) inb = *in++; + *out++ = scale * (inb >> 7); + inb <<= 1; + } + } + + // insert alpha=255 values if desired + if (img_n != out_n) + stbi__create_png_alpha_expand8(dest, dest, x, img_n); + } else if (depth == 8) { + if (img_n == out_n) + memcpy(dest, cur, x*img_n); + else + stbi__create_png_alpha_expand8(dest, cur, x, img_n); + } else if (depth == 16) { + // convert the image data from big-endian to platform-native + stbi__uint16 *dest16 = (stbi__uint16*)dest; + stbi__uint32 nsmp = x*img_n; + + if (img_n == out_n) { + for (i = 0; i < nsmp; ++i, ++dest16, cur += 2) + *dest16 = (cur[0] << 8) | cur[1]; + } else { + STBI_ASSERT(img_n+1 == out_n); + if (img_n == 1) { + for (i = 0; i < x; ++i, dest16 += 2, cur += 2) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = 0xffff; + } + } else { + STBI_ASSERT(img_n == 3); + for (i = 0; i < x; ++i, dest16 += 4, cur += 6) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = (cur[2] << 8) | cur[3]; + dest16[2] = (cur[4] << 8) | cur[5]; + dest16[3] = 0xffff; + } + } + } + } + } + + STBI_FREE(filter_buf); + if (!all_ok) return 0; + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + } + // even with SCAN_header, have to scan to see if we have a tRNS + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. + if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { + // header scan definitely stops at first IDAT + if (pal_img_n) + s->img_n = pal_img_n; + return 1; + } + if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + // accept some number of extra bytes after the header, but if the offset points either to before + // the header ends or implies a large amount of extra data, reject the file as malformed + int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); + int header_limit = 1024; // max we actually read is below 256 bytes currently. + int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. + if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { + return stbi__errpuc("bad header", "Corrupt BMP"); + } + // we established that bytes_read_so_far is positive and sensible. + // the first half of this test rejects offsets that are either too small positives, or + // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn + // ensures the number computed in the second half of the test can't overflow. + if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } else { + stbi__skip(s, info.offset - bytes_read_so_far); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { + STBI_FREE(out); + return stbi__errpuc("bad PNM", "PNM file truncated"); + } + + if (req_comp && req_comp != s->img_n) { + if (ri->bits_per_channel == 16) { + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); + } else { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + } + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + if((value > 214748364) || (value == 214748364 && *c > '7')) + return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + if(*x == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + if (*y == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_image_resize2.h b/source/engine/thirdparty/stb/stb_image_resize2.h new file mode 100644 index 0000000..1cd379a --- /dev/null +++ b/source/engine/thirdparty/stb/stb_image_resize2.h @@ -0,0 +1,10365 @@ +/* stb_image_resize2 - v2.06 - public domain image resizing + + by Jeff Roberts (v2) and Jorge L Rodriguez + http://github.com/nothings/stb + + Can be threaded with the extended API. SSE2, AVX, Neon and WASM SIMD support. Only + scaling and translation is supported, no rotations or shears. + + COMPILING & LINKING + In one C/C++ file that #includes this file, do this: + #define STB_IMAGE_RESIZE_IMPLEMENTATION + before the #include. That will create the implementation in that file. + + PORTING FROM VERSION 1 + + The API has changed. You can continue to use the old version of stb_image_resize.h, + which is available in the "deprecated/" directory. + + If you're using the old simple-to-use API, porting is straightforward. + (For more advanced APIs, read the documentation.) + + stbir_resize_uint8(): + - call `stbir_resize_uint8_linear`, cast channel count to `stbir_pixel_layout` + + stbir_resize_float(): + - call `stbir_resize_float_linear`, cast channel count to `stbir_pixel_layout` + + stbir_resize_uint8_srgb(): + - function name is unchanged + - cast channel count to `stbir_pixel_layout` + - above is sufficient unless your image has alpha and it's not RGBA/BGRA + - in that case, follow the below instructions for stbir_resize_uint8_srgb_edgemode + + stbir_resize_uint8_srgb_edgemode() + - switch to the "medium complexity" API + - stbir_resize(), very similar API but a few more parameters: + - pixel_layout: cast channel count to `stbir_pixel_layout` + - data_type: STBIR_TYPE_UINT8_SRGB + - edge: unchanged (STBIR_EDGE_WRAP, etc.) + - filter: STBIR_FILTER_DEFAULT + - which channel is alpha is specified in stbir_pixel_layout, see enum for details + + EASY API CALLS: + Easy API downsamples w/Mitchell filter, upsamples w/cubic interpolation, clamps to edge. + + stbir_resize_uint8_srgb( input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout_enum ) + + stbir_resize_uint8_linear( input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout_enum ) + + stbir_resize_float_linear( input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout_enum ) + + If you pass NULL or zero for the output_pixels, we will allocate the output buffer + for you and return it from the function (free with free() or STBIR_FREE). + As a special case, XX_stride_in_bytes of 0 means packed continuously in memory. + + API LEVELS + There are three levels of API - easy-to-use, medium-complexity and extended-complexity. + + See the "header file" section of the source for API documentation. + + ADDITIONAL DOCUMENTATION + + MEMORY ALLOCATION + By default, we use malloc and free for memory allocation. To override the + memory allocation, before the implementation #include, add a: + + #define STBIR_MALLOC(size,user_data) ... + #define STBIR_FREE(ptr,user_data) ... + + Each resize makes exactly one call to malloc/free (unless you use the + extended API where you can do one allocation for many resizes). Under + address sanitizer, we do separate allocations to find overread/writes. + + PERFORMANCE + This library was written with an emphasis on performance. When testing + stb_image_resize with RGBA, the fastest mode is STBIR_4CHANNEL with + STBIR_TYPE_UINT8 pixels and CLAMPed edges (which is what many other resize + libs do by default). Also, make sure SIMD is turned on of course (default + for 64-bit targets). Avoid WRAP edge mode if you want the fastest speed. + + This library also comes with profiling built-in. If you define STBIR_PROFILE, + you can use the advanced API and get low-level profiling information by + calling stbir_resize_extended_profile_info() or stbir_resize_split_profile_info() + after a resize. + + SIMD + Most of the routines have optimized SSE2, AVX, NEON and WASM versions. + + On Microsoft compilers, we automatically turn on SIMD for 64-bit x64 and + ARM; for 32-bit x86 and ARM, you select SIMD mode by defining STBIR_SSE2 or + STBIR_NEON. For AVX and AVX2, we auto-select it by detecting the /arch:AVX + or /arch:AVX2 switches. You can also always manually turn SSE2, AVX or AVX2 + support on by defining STBIR_SSE2, STBIR_AVX or STBIR_AVX2. + + On Linux, SSE2 and Neon is on by default for 64-bit x64 or ARM64. For 32-bit, + we select x86 SIMD mode by whether you have -msse2, -mavx or -mavx2 enabled + on the command line. For 32-bit ARM, you must pass -mfpu=neon-vfpv4 for both + clang and GCC, but GCC also requires an additional -mfp16-format=ieee to + automatically enable NEON. + + On x86 platforms, you can also define STBIR_FP16C to turn on FP16C instructions + for converting back and forth to half-floats. This is autoselected when we + are using AVX2. Clang and GCC also require the -mf16c switch. ARM always uses + the built-in half float hardware NEON instructions. + + You can also tell us to use multiply-add instructions with STBIR_USE_FMA. + Because x86 doesn't always have fma, we turn it off by default to maintain + determinism across all platforms. If you don't care about non-FMA determinism + and are willing to restrict yourself to more recent x86 CPUs (around the AVX + timeframe), then fma will give you around a 15% speedup. + + You can force off SIMD in all cases by defining STBIR_NO_SIMD. You can turn + off AVX or AVX2 specifically with STBIR_NO_AVX or STBIR_NO_AVX2. AVX is 10% + to 40% faster, and AVX2 is generally another 12%. + + ALPHA CHANNEL + Most of the resizing functions provide the ability to control how the alpha + channel of an image is processed. + + When alpha represents transparency, it is important that when combining + colors with filtering, the pixels should not be treated equally; they + should use a weighted average based on their alpha values. For example, + if a pixel is 1% opaque bright green and another pixel is 99% opaque + black and you average them, the average will be 50% opaque, but the + unweighted average and will be a middling green color, while the weighted + average will be nearly black. This means the unweighted version introduced + green energy that didn't exist in the source image. + + (If you want to know why this makes sense, you can work out the math for + the following: consider what happens if you alpha composite a source image + over a fixed color and then average the output, vs. if you average the + source image pixels and then composite that over the same fixed color. + Only the weighted average produces the same result as the ground truth + composite-then-average result.) + + Therefore, it is in general best to "alpha weight" the pixels when applying + filters to them. This essentially means multiplying the colors by the alpha + values before combining them, and then dividing by the alpha value at the + end. + + The computer graphics industry introduced a technique called "premultiplied + alpha" or "associated alpha" in which image colors are stored in image files + already multiplied by their alpha. This saves some math when compositing, + and also avoids the need to divide by the alpha at the end (which is quite + inefficient). However, while premultiplied alpha is common in the movie CGI + industry, it is not commonplace in other industries like videogames, and most + consumer file formats are generally expected to contain not-premultiplied + colors. For example, Photoshop saves PNG files "unpremultiplied", and web + browsers like Chrome and Firefox expect PNG images to be unpremultiplied. + + Note that there are three possibilities that might describe your image + and resize expectation: + + 1. images are not premultiplied, alpha weighting is desired + 2. images are not premultiplied, alpha weighting is not desired + 3. images are premultiplied + + Both case #2 and case #3 require the exact same math: no alpha weighting + should be applied or removed. Only case 1 requires extra math operations; + the other two cases can be handled identically. + + stb_image_resize expects case #1 by default, applying alpha weighting to + images, expecting the input images to be unpremultiplied. This is what the + COLOR+ALPHA buffer types tell the resizer to do. + + When you use the pixel layouts STBIR_RGBA, STBIR_BGRA, STBIR_ARGB, + STBIR_ABGR, STBIR_RX, or STBIR_XR you are telling us that the pixels are + non-premultiplied. In these cases, the resizer will alpha weight the colors + (effectively creating the premultiplied image), do the filtering, and then + convert back to non-premult on exit. + + When you use the pixel layouts STBIR_RGBA_PM, STBIR_RGBA_PM, STBIR_RGBA_PM, + STBIR_RGBA_PM, STBIR_RX_PM or STBIR_XR_PM, you are telling that the pixels + ARE premultiplied. In this case, the resizer doesn't have to do the + premultipling - it can filter directly on the input. This about twice as + fast as the non-premultiplied case, so it's the right option if your data is + already setup correctly. + + When you use the pixel layout STBIR_4CHANNEL or STBIR_2CHANNEL, you are + telling us that there is no channel that represents transparency; it may be + RGB and some unrelated fourth channel that has been stored in the alpha + channel, but it is actually not alpha. No special processing will be + performed. + + The difference between the generic 4 or 2 channel layouts, and the + specialized _PM versions is with the _PM versions you are telling us that + the data *is* alpha, just don't premultiply it. That's important when + using SRGB pixel formats, we need to know where the alpha is, because + it is converted linearly (rather than with the SRGB converters). + + Because alpha weighting produces the same effect as premultiplying, you + even have the option with non-premultiplied inputs to let the resizer + produce a premultiplied output. Because the intially computed alpha-weighted + output image is effectively premultiplied, this is actually more performant + than the normal path which un-premultiplies the output image as a final step. + + Finally, when converting both in and out of non-premulitplied space (for + example, when using STBIR_RGBA), we go to somewhat heroic measures to + ensure that areas with zero alpha value pixels get something reasonable + in the RGB values. If you don't care about the RGB values of zero alpha + pixels, you can call the stbir_set_non_pm_alpha_speed_over_quality() + function - this runs a premultiplied resize about 25% faster. That said, + when you really care about speed, using premultiplied pixels for both in + and out (STBIR_RGBA_PM, etc) much faster than both of these premultiplied + options. + + PIXEL LAYOUT CONVERSION + The resizer can convert from some pixel layouts to others. When using the + stbir_set_pixel_layouts(), you can, for example, specify STBIR_RGBA + on input, and STBIR_ARGB on output, and it will re-organize the channels + during the resize. Currently, you can only convert between two pixel + layouts with the same number of channels. + + DETERMINISM + We commit to being deterministic (from x64 to ARM to scalar to SIMD, etc). + This requires compiling with fast-math off (using at least /fp:precise). + Also, you must turn off fp-contracting (which turns mult+adds into fmas)! + We attempt to do this with pragmas, but with Clang, you usually want to add + -ffp-contract=off to the command line as well. + + For 32-bit x86, you must use SSE and SSE2 codegen for determinism. That is, + if the scalar x87 unit gets used at all, we immediately lose determinism. + On Microsoft Visual Studio 2008 and earlier, from what we can tell there is + no way to be deterministic in 32-bit x86 (some x87 always leaks in, even + with fp:strict). On 32-bit x86 GCC, determinism requires both -msse2 and + -fpmath=sse. + + Note that we will not be deterministic with float data containing NaNs - + the NaNs will propagate differently on different SIMD and platforms. + + If you turn on STBIR_USE_FMA, then we will be deterministic with other + fma targets, but we will differ from non-fma targets (this is unavoidable, + because a fma isn't simply an add with a mult - it also introduces a + rounding difference compared to non-fma instruction sequences. + + FLOAT PIXEL FORMAT RANGE + Any range of values can be used for the non-alpha float data that you pass + in (0 to 1, -1 to 1, whatever). However, if you are inputting float values + but *outputting* bytes or shorts, you must use a range of 0 to 1 so that we + scale back properly. The alpha channel must also be 0 to 1 for any format + that does premultiplication prior to resizing. + + Note also that with float output, using filters with negative lobes, the + output filtered values might go slightly out of range. You can define + STBIR_FLOAT_LOW_CLAMP and/or STBIR_FLOAT_HIGH_CLAMP to specify the range + to clamp to on output, if that's important. + + MAX/MIN SCALE FACTORS + The input pixel resolutions are in integers, and we do the internal pointer + resolution in size_t sized integers. However, the scale ratio from input + resolution to output resolution is calculated in float form. This means + the effective possible scale ratio is limited to 24 bits (or 16 million + to 1). As you get close to the size of the float resolution (again, 16 + million pixels wide or high), you might start seeing float inaccuracy + issues in general in the pipeline. If you have to do extreme resizes, + you can usually do this is multiple stages (using float intermediate + buffers). + + FLIPPED IMAGES + Stride is just the delta from one scanline to the next. This means you can + use a negative stride to handle inverted images (point to the final + scanline and use a negative stride). You can invert the input or output, + using negative strides. + + DEFAULT FILTERS + For functions which don't provide explicit control over what filters to + use, you can change the compile-time defaults with: + + #define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_something + #define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_something + + See stbir_filter in the header-file section for the list of filters. + + NEW FILTERS + A number of 1D filter kernels are supplied. For a list of supported + filters, see the stbir_filter enum. You can install your own filters by + using the stbir_set_filter_callbacks function. + + PROGRESS + For interactive use with slow resize operations, you can use the the + scanline callbacks in the extended API. It would have to be a *very* large + image resample to need progress though - we're very fast. + + CEIL and FLOOR + In scalar mode, the only functions we use from math.h are ceilf and floorf, + but if you have your own versions, you can define the STBIR_CEILF(v) and + STBIR_FLOORF(v) macros and we'll use them instead. In SIMD, we just use + our own versions. + + ASSERT + Define STBIR_ASSERT(boolval) to override assert() and not use assert.h + + FUTURE TODOS + * For polyphase integral filters, we just memcpy the coeffs to dupe + them, but we should indirect and use the same coeff memory. + * Add pixel layout conversions for sensible different channel counts + (maybe, 1->3/4, 3->4, 4->1, 3->1). + * For SIMD encode and decode scanline routines, do any pre-aligning + for bad input/output buffer alignments and pitch? + * For very wide scanlines, we should we do vertical strips to stay within + L2 cache. Maybe do chunks of 1K pixels at a time. There would be + some pixel reconversion, but probably dwarfed by things falling out + of cache. Probably also something possible with alternating between + scattering and gathering at high resize scales? + * Rewrite the coefficient generator to do many at once. + * AVX-512 vertical kernels - worried about downclocking here. + * Convert the reincludes to macros when we know they aren't changing. + * Experiment with pivoting the horizontal and always using the + vertical filters (which are faster, but perhaps not enough to overcome + the pivot cost and the extra memory touches). Need to buffer the whole + image so have to balance memory use. + * Most of our code is internally function pointers, should we compile + all the SIMD stuff always and dynamically dispatch? + + CONTRIBUTORS + Jeff Roberts: 2.0 implementation, optimizations, SIMD + Martins Mozeiko: NEON simd, WASM simd, clang and GCC whisperer. + Fabian Giesen: half float and srgb converters + Sean Barrett: API design, optimizations + Jorge L Rodriguez: Original 1.0 implementation + Aras Pranckevicius: bugfixes + Nathan Reed: warning fixes for 1.0 + + REVISIONS + 2.06 (2024-02-10) fix for indentical width/height 3x or more down-scaling + undersampling a single row on rare resize ratios (about 1%) + 2.05 (2024-02-07) fix for 2 pixel to 1 pixel resizes with wrap (thanks Aras) + fix for output callback (thanks Julien Koenen) + 2.04 (2023-11-17) fix for rare AVX bug, shadowed symbol (thanks Nikola Smiljanic). + 2.03 (2023-11-01) ASAN and TSAN warnings fixed, minor tweaks. + 2.00 (2023-10-10) mostly new source: new api, optimizations, simd, vertical-first, etc + (2x-5x faster without simd, 4x-12x faster with simd) + (in some cases, 20x to 40x faster - resizing to very small for example) + 0.96 (2019-03-04) fixed warnings + 0.95 (2017-07-23) fixed warnings + 0.94 (2017-03-18) fixed warnings + 0.93 (2017-03-03) fixed bug with certain combinations of heights + 0.92 (2017-01-02) fix integer overflow on large (>2GB) images + 0.91 (2016-04-02) fix warnings; fix handling of subpixel regions + 0.90 (2014-09-17) first released version + + LICENSE + See end of file for license information. +*/ + +#if !defined(STB_IMAGE_RESIZE_DO_HORIZONTALS) && !defined(STB_IMAGE_RESIZE_DO_VERTICALS) && !defined(STB_IMAGE_RESIZE_DO_CODERS) // for internal re-includes + +#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE2_H +#define STBIR_INCLUDE_STB_IMAGE_RESIZE2_H + +#include +#ifdef _MSC_VER +typedef unsigned char stbir_uint8; +typedef unsigned short stbir_uint16; +typedef unsigned int stbir_uint32; +typedef unsigned __int64 stbir_uint64; +#else +#include +typedef uint8_t stbir_uint8; +typedef uint16_t stbir_uint16; +typedef uint32_t stbir_uint32; +typedef uint64_t stbir_uint64; +#endif + +#ifdef _M_IX86_FP +#if ( _M_IX86_FP >= 1 ) +#ifndef STBIR_SSE +#define STBIR_SSE +#endif +#endif +#endif + +#if defined(_x86_64) || defined( __x86_64__ ) || defined( _M_X64 ) || defined(__x86_64) || defined(_M_AMD64) || defined(__SSE2__) || defined(STBIR_SSE) || defined(STBIR_SSE2) + #ifndef STBIR_SSE2 + #define STBIR_SSE2 + #endif + #if defined(__AVX__) || defined(STBIR_AVX2) + #ifndef STBIR_AVX + #ifndef STBIR_NO_AVX + #define STBIR_AVX + #endif + #endif + #endif + #if defined(__AVX2__) || defined(STBIR_AVX2) + #ifndef STBIR_NO_AVX2 + #ifndef STBIR_AVX2 + #define STBIR_AVX2 + #endif + #if defined( _MSC_VER ) && !defined(__clang__) + #ifndef STBIR_FP16C // FP16C instructions are on all AVX2 cpus, so we can autoselect it here on microsoft - clang needs -m16c + #define STBIR_FP16C + #endif + #endif + #endif + #endif + #ifdef __F16C__ + #ifndef STBIR_FP16C // turn on FP16C instructions if the define is set (for clang and gcc) + #define STBIR_FP16C + #endif + #endif +#endif + +#if defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || defined(_M_ARM) || (__ARM_NEON_FP & 4) != 0 && __ARM_FP16_FORMAT_IEEE != 0 +#ifndef STBIR_NEON +#define STBIR_NEON +#endif +#endif + +#if defined(_M_ARM) +#ifdef STBIR_USE_FMA +#undef STBIR_USE_FMA // no FMA for 32-bit arm on MSVC +#endif +#endif + +#if defined(__wasm__) && defined(__wasm_simd128__) +#ifndef STBIR_WASM +#define STBIR_WASM +#endif +#endif + +#ifndef STBIRDEF +#ifdef STB_IMAGE_RESIZE_STATIC +#define STBIRDEF static +#else +#ifdef __cplusplus +#define STBIRDEF extern "C" +#else +#define STBIRDEF extern +#endif +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +//// start "header file" /////////////////////////////////////////////////// +// +// Easy-to-use API: +// +// * stride is the offset between successive rows of image data +// in memory, in bytes. specify 0 for packed continuously in memory +// * colorspace is linear or sRGB as specified by function name +// * Uses the default filters +// * Uses edge mode clamped +// * returned result is 1 for success or 0 in case of an error. + + +// stbir_pixel_layout specifies: +// number of channels +// order of channels +// whether color is premultiplied by alpha +// for back compatibility, you can cast the old channel count to an stbir_pixel_layout +typedef enum +{ + STBIR_1CHANNEL = 1, + STBIR_2CHANNEL = 2, + STBIR_RGB = 3, // 3-chan, with order specified (for channel flipping) + STBIR_BGR = 0, // 3-chan, with order specified (for channel flipping) + STBIR_4CHANNEL = 5, + + STBIR_RGBA = 4, // alpha formats, where alpha is NOT premultiplied into color channels + STBIR_BGRA = 6, + STBIR_ARGB = 7, + STBIR_ABGR = 8, + STBIR_RA = 9, + STBIR_AR = 10, + + STBIR_RGBA_PM = 11, // alpha formats, where alpha is premultiplied into color channels + STBIR_BGRA_PM = 12, + STBIR_ARGB_PM = 13, + STBIR_ABGR_PM = 14, + STBIR_RA_PM = 15, + STBIR_AR_PM = 16, + + STBIR_RGBA_NO_AW = 11, // alpha formats, where NO alpha weighting is applied at all! + STBIR_BGRA_NO_AW = 12, // these are just synonyms for the _PM flags (which also do + STBIR_ARGB_NO_AW = 13, // no alpha weighting). These names just make it more clear + STBIR_ABGR_NO_AW = 14, // for some folks). + STBIR_RA_NO_AW = 15, + STBIR_AR_NO_AW = 16, + +} stbir_pixel_layout; + +//=============================================================== +// Simple-complexity API +// +// If output_pixels is NULL (0), then we will allocate the buffer and return it to you. +//-------------------------------- + +STBIRDEF unsigned char * stbir_resize_uint8_srgb( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_type ); + +STBIRDEF unsigned char * stbir_resize_uint8_linear( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_type ); + +STBIRDEF float * stbir_resize_float_linear( const float *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_type ); +//=============================================================== + +//=============================================================== +// Medium-complexity API +// +// This extends the easy-to-use API as follows: +// +// * Can specify the datatype - U8, U8_SRGB, U16, FLOAT, HALF_FLOAT +// * Edge wrap can selected explicitly +// * Filter can be selected explicitly +//-------------------------------- + +typedef enum +{ + STBIR_EDGE_CLAMP = 0, + STBIR_EDGE_REFLECT = 1, + STBIR_EDGE_WRAP = 2, // this edge mode is slower and uses more memory + STBIR_EDGE_ZERO = 3, +} stbir_edge; + +typedef enum +{ + STBIR_FILTER_DEFAULT = 0, // use same filter type that easy-to-use API chooses + STBIR_FILTER_BOX = 1, // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios + STBIR_FILTER_TRIANGLE = 2, // On upsampling, produces same results as bilinear texture filtering + STBIR_FILTER_CUBICBSPLINE = 3, // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque + STBIR_FILTER_CATMULLROM = 4, // An interpolating cubic spline + STBIR_FILTER_MITCHELL = 5, // Mitchell-Netrevalli filter with B=1/3, C=1/3 + STBIR_FILTER_POINT_SAMPLE = 6, // Simple point sampling + STBIR_FILTER_OTHER = 7, // User callback specified +} stbir_filter; + +typedef enum +{ + STBIR_TYPE_UINT8 = 0, + STBIR_TYPE_UINT8_SRGB = 1, + STBIR_TYPE_UINT8_SRGB_ALPHA = 2, // alpha channel, when present, should also be SRGB (this is very unusual) + STBIR_TYPE_UINT16 = 3, + STBIR_TYPE_FLOAT = 4, + STBIR_TYPE_HALF_FLOAT = 5 +} stbir_datatype; + +// medium api +STBIRDEF void * stbir_resize( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout, stbir_datatype data_type, + stbir_edge edge, stbir_filter filter ); +//=============================================================== + + + +//=============================================================== +// Extended-complexity API +// +// This API exposes all resize functionality. +// +// * Separate filter types for each axis +// * Separate edge modes for each axis +// * Separate input and output data types +// * Can specify regions with subpixel correctness +// * Can specify alpha flags +// * Can specify a memory callback +// * Can specify a callback data type for pixel input and output +// * Can be threaded for a single resize +// * Can be used to resize many frames without recalculating the sampler info +// +// Use this API as follows: +// 1) Call the stbir_resize_init function on a local STBIR_RESIZE structure +// 2) Call any of the stbir_set functions +// 3) Optionally call stbir_build_samplers() if you are going to resample multiple times +// with the same input and output dimensions (like resizing video frames) +// 4) Resample by calling stbir_resize_extended(). +// 5) Call stbir_free_samplers() if you called stbir_build_samplers() +//-------------------------------- + + +// Types: + +// INPUT CALLBACK: this callback is used for input scanlines +typedef void const * stbir_input_callback( void * optional_output, void const * input_ptr, int num_pixels, int x, int y, void * context ); + +// OUTPUT CALLBACK: this callback is used for output scanlines +typedef void stbir_output_callback( void const * output_ptr, int num_pixels, int y, void * context ); + +// callbacks for user installed filters +typedef float stbir__kernel_callback( float x, float scale, void * user_data ); // centered at zero +typedef float stbir__support_callback( float scale, void * user_data ); + +// internal structure with precomputed scaling +typedef struct stbir__info stbir__info; + +typedef struct STBIR_RESIZE // use the stbir_resize_init and stbir_override functions to set these values for future compatibility +{ + void * user_data; + void const * input_pixels; + int input_w, input_h; + double input_s0, input_t0, input_s1, input_t1; + stbir_input_callback * input_cb; + void * output_pixels; + int output_w, output_h; + int output_subx, output_suby, output_subw, output_subh; + stbir_output_callback * output_cb; + int input_stride_in_bytes; + int output_stride_in_bytes; + int splits; + int fast_alpha; + int needs_rebuild; + int called_alloc; + stbir_pixel_layout input_pixel_layout_public; + stbir_pixel_layout output_pixel_layout_public; + stbir_datatype input_data_type; + stbir_datatype output_data_type; + stbir_filter horizontal_filter, vertical_filter; + stbir_edge horizontal_edge, vertical_edge; + stbir__kernel_callback * horizontal_filter_kernel; stbir__support_callback * horizontal_filter_support; + stbir__kernel_callback * vertical_filter_kernel; stbir__support_callback * vertical_filter_support; + stbir__info * samplers; +} STBIR_RESIZE; + +// extended complexity api + + +// First off, you must ALWAYS call stbir_resize_init on your resize structure before any of the other calls! +STBIRDEF void stbir_resize_init( STBIR_RESIZE * resize, + const void *input_pixels, int input_w, int input_h, int input_stride_in_bytes, // stride can be zero + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, // stride can be zero + stbir_pixel_layout pixel_layout, stbir_datatype data_type ); + +//=============================================================== +// You can update these parameters any time after resize_init and there is no cost +//-------------------------------- + +STBIRDEF void stbir_set_datatypes( STBIR_RESIZE * resize, stbir_datatype input_type, stbir_datatype output_type ); +STBIRDEF void stbir_set_pixel_callbacks( STBIR_RESIZE * resize, stbir_input_callback * input_cb, stbir_output_callback * output_cb ); // no callbacks by default +STBIRDEF void stbir_set_user_data( STBIR_RESIZE * resize, void * user_data ); // pass back STBIR_RESIZE* by default +STBIRDEF void stbir_set_buffer_ptrs( STBIR_RESIZE * resize, const void * input_pixels, int input_stride_in_bytes, void * output_pixels, int output_stride_in_bytes ); + +//=============================================================== + + +//=============================================================== +// If you call any of these functions, you will trigger a sampler rebuild! +//-------------------------------- + +STBIRDEF int stbir_set_pixel_layouts( STBIR_RESIZE * resize, stbir_pixel_layout input_pixel_layout, stbir_pixel_layout output_pixel_layout ); // sets new buffer layouts +STBIRDEF int stbir_set_edgemodes( STBIR_RESIZE * resize, stbir_edge horizontal_edge, stbir_edge vertical_edge ); // CLAMP by default + +STBIRDEF int stbir_set_filters( STBIR_RESIZE * resize, stbir_filter horizontal_filter, stbir_filter vertical_filter ); // STBIR_DEFAULT_FILTER_UPSAMPLE/DOWNSAMPLE by default +STBIRDEF int stbir_set_filter_callbacks( STBIR_RESIZE * resize, stbir__kernel_callback * horizontal_filter, stbir__support_callback * horizontal_support, stbir__kernel_callback * vertical_filter, stbir__support_callback * vertical_support ); + +STBIRDEF int stbir_set_pixel_subrect( STBIR_RESIZE * resize, int subx, int suby, int subw, int subh ); // sets both sub-regions (full regions by default) +STBIRDEF int stbir_set_input_subrect( STBIR_RESIZE * resize, double s0, double t0, double s1, double t1 ); // sets input sub-region (full region by default) +STBIRDEF int stbir_set_output_pixel_subrect( STBIR_RESIZE * resize, int subx, int suby, int subw, int subh ); // sets output sub-region (full region by default) + +// when inputting AND outputting non-premultiplied alpha pixels, we use a slower but higher quality technique +// that fills the zero alpha pixel's RGB values with something plausible. If you don't care about areas of +// zero alpha, you can call this function to get about a 25% speed improvement for STBIR_RGBA to STBIR_RGBA +// types of resizes. +STBIRDEF int stbir_set_non_pm_alpha_speed_over_quality( STBIR_RESIZE * resize, int non_pma_alpha_speed_over_quality ); +//=============================================================== + + +//=============================================================== +// You can call build_samplers to prebuild all the internal data we need to resample. +// Then, if you call resize_extended many times with the same resize, you only pay the +// cost once. +// If you do call build_samplers, you MUST call free_samplers eventually. +//-------------------------------- + +// This builds the samplers and does one allocation +STBIRDEF int stbir_build_samplers( STBIR_RESIZE * resize ); + +// You MUST call this, if you call stbir_build_samplers or stbir_build_samplers_with_splits +STBIRDEF void stbir_free_samplers( STBIR_RESIZE * resize ); +//=============================================================== + + +// And this is the main function to perform the resize synchronously on one thread. +STBIRDEF int stbir_resize_extended( STBIR_RESIZE * resize ); + + +//=============================================================== +// Use these functions for multithreading. +// 1) You call stbir_build_samplers_with_splits first on the main thread +// 2) Then stbir_resize_with_split on each thread +// 3) stbir_free_samplers when done on the main thread +//-------------------------------- + +// This will build samplers for threading. +// You can pass in the number of threads you'd like to use (try_splits). +// It returns the number of splits (threads) that you can call it with. +/// It might be less if the image resize can't be split up that many ways. + +STBIRDEF int stbir_build_samplers_with_splits( STBIR_RESIZE * resize, int try_splits ); + +// This function does a split of the resizing (you call this fuction for each +// split, on multiple threads). A split is a piece of the output resize pixel space. + +// Note that you MUST call stbir_build_samplers_with_splits before stbir_resize_extended_split! + +// Usually, you will always call stbir_resize_split with split_start as the thread_index +// and "1" for the split_count. +// But, if you have a weird situation where you MIGHT want 8 threads, but sometimes +// only 4 threads, you can use 0,2,4,6 for the split_start's and use "2" for the +// split_count each time to turn in into a 4 thread resize. (This is unusual). + +STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start, int split_count ); +//=============================================================== + + +//=============================================================== +// Pixel Callbacks info: +//-------------------------------- + +// The input callback is super flexible - it calls you with the input address +// (based on the stride and base pointer), it gives you an optional_output +// pointer that you can fill, or you can just return your own pointer into +// your own data. +// +// You can also do conversion from non-supported data types if necessary - in +// this case, you ignore the input_ptr and just use the x and y parameters to +// calculate your own input_ptr based on the size of each non-supported pixel. +// (Something like the third example below.) +// +// You can also install just an input or just an output callback by setting the +// callback that you don't want to zero. +// +// First example, progress: (getting a callback that you can monitor the progress): +// void const * my_callback( void * optional_output, void const * input_ptr, int num_pixels, int x, int y, void * context ) +// { +// percentage_done = y / input_height; +// return input_ptr; // use buffer from call +// } +// +// Next example, copying: (copy from some other buffer or stream): +// void const * my_callback( void * optional_output, void const * input_ptr, int num_pixels, int x, int y, void * context ) +// { +// CopyOrStreamData( optional_output, other_data_src, num_pixels * pixel_width_in_bytes ); +// return optional_output; // return the optional buffer that we filled +// } +// +// Third example, input another buffer without copying: (zero-copy from other buffer): +// void const * my_callback( void * optional_output, void const * input_ptr, int num_pixels, int x, int y, void * context ) +// { +// void * pixels = ( (char*) other_image_base ) + ( y * other_image_stride ) + ( x * other_pixel_width_in_bytes ); +// return pixels; // return pointer to your data without copying +// } +// +// +// The output callback is considerably simpler - it just calls you so that you can dump +// out each scanline. You could even directly copy out to disk if you have a simple format +// like TGA or BMP. You can also convert to other output types here if you want. +// +// Simple example: +// void const * my_output( void * output_ptr, int num_pixels, int y, void * context ) +// { +// percentage_done = y / output_height; +// fwrite( output_ptr, pixel_width_in_bytes, num_pixels, output_file ); +// } +//=============================================================== + + + + +//=============================================================== +// optional built-in profiling API +//-------------------------------- + +#ifdef STBIR_PROFILE + +typedef struct STBIR_PROFILE_INFO +{ + stbir_uint64 total_clocks; + + // how many clocks spent (of total_clocks) in the various resize routines, along with a string description + // there are "resize_count" number of zones + stbir_uint64 clocks[ 8 ]; + char const ** descriptions; + + // count of clocks and descriptions + stbir_uint32 count; +} STBIR_PROFILE_INFO; + +// use after calling stbir_resize_extended (or stbir_build_samplers or stbir_build_samplers_with_splits) +STBIRDEF void stbir_resize_build_profile_info( STBIR_PROFILE_INFO * out_info, STBIR_RESIZE const * resize ); + +// use after calling stbir_resize_extended +STBIRDEF void stbir_resize_extended_profile_info( STBIR_PROFILE_INFO * out_info, STBIR_RESIZE const * resize ); + +// use after calling stbir_resize_extended_split +STBIRDEF void stbir_resize_split_profile_info( STBIR_PROFILE_INFO * out_info, STBIR_RESIZE const * resize, int split_start, int split_num ); + +//=============================================================== + +#endif + + +//// end header file ///////////////////////////////////////////////////// +#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE2_H + +#if defined(STB_IMAGE_RESIZE_IMPLEMENTATION) || defined(STB_IMAGE_RESIZE2_IMPLEMENTATION) + +#ifndef STBIR_ASSERT +#include +#define STBIR_ASSERT(x) assert(x) +#endif + +#ifndef STBIR_MALLOC +#include +#define STBIR_MALLOC(size,user_data) ((void)(user_data), malloc(size)) +#define STBIR_FREE(ptr,user_data) ((void)(user_data), free(ptr)) +// (we used the comma operator to evaluate user_data, to avoid "unused parameter" warnings) +#endif + +#ifdef _MSC_VER + +#define stbir__inline __forceinline + +#else + +#define stbir__inline __inline__ + +// Clang address sanitizer +#if defined(__has_feature) + #if __has_feature(address_sanitizer) || __has_feature(memory_sanitizer) + #ifndef STBIR__SEPARATE_ALLOCATIONS + #define STBIR__SEPARATE_ALLOCATIONS + #endif + #endif +#endif + +#endif + +// GCC and MSVC +#if defined(__SANITIZE_ADDRESS__) + #ifndef STBIR__SEPARATE_ALLOCATIONS + #define STBIR__SEPARATE_ALLOCATIONS + #endif +#endif + +// Always turn off automatic FMA use - use STBIR_USE_FMA if you want. +// Otherwise, this is a determinism disaster. +#ifndef STBIR_DONT_CHANGE_FP_CONTRACT // override in case you don't want this behavior +#if defined(_MSC_VER) && !defined(__clang__) +#if _MSC_VER > 1200 +#pragma fp_contract(off) +#endif +#elif defined(__GNUC__) && !defined(__clang__) +#pragma GCC optimize("fp-contract=off") +#else +#pragma STDC FP_CONTRACT OFF +#endif +#endif + +#ifdef _MSC_VER +#define STBIR__UNUSED(v) (void)(v) +#else +#define STBIR__UNUSED(v) (void)sizeof(v) +#endif + +#define STBIR__ARRAY_SIZE(a) (sizeof((a))/sizeof((a)[0])) + + +#ifndef STBIR_DEFAULT_FILTER_UPSAMPLE +#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +#endif + +#ifndef STBIR_DEFAULT_FILTER_DOWNSAMPLE +#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL +#endif + + +#ifndef STBIR__HEADER_FILENAME +#define STBIR__HEADER_FILENAME "stb_image_resize2.h" +#endif + +// the internal pixel layout enums are in a different order, so we can easily do range comparisons of types +// the public pixel layout is ordered in a way that if you cast num_channels (1-4) to the enum, you get something sensible +typedef enum +{ + STBIRI_1CHANNEL = 0, + STBIRI_2CHANNEL = 1, + STBIRI_RGB = 2, + STBIRI_BGR = 3, + STBIRI_4CHANNEL = 4, + + STBIRI_RGBA = 5, + STBIRI_BGRA = 6, + STBIRI_ARGB = 7, + STBIRI_ABGR = 8, + STBIRI_RA = 9, + STBIRI_AR = 10, + + STBIRI_RGBA_PM = 11, + STBIRI_BGRA_PM = 12, + STBIRI_ARGB_PM = 13, + STBIRI_ABGR_PM = 14, + STBIRI_RA_PM = 15, + STBIRI_AR_PM = 16, +} stbir_internal_pixel_layout; + +// define the public pixel layouts to not compile inside the implementation (to avoid accidental use) +#define STBIR_BGR bad_dont_use_in_implementation +#define STBIR_1CHANNEL STBIR_BGR +#define STBIR_2CHANNEL STBIR_BGR +#define STBIR_RGB STBIR_BGR +#define STBIR_RGBA STBIR_BGR +#define STBIR_4CHANNEL STBIR_BGR +#define STBIR_BGRA STBIR_BGR +#define STBIR_ARGB STBIR_BGR +#define STBIR_ABGR STBIR_BGR +#define STBIR_RA STBIR_BGR +#define STBIR_AR STBIR_BGR +#define STBIR_RGBA_PM STBIR_BGR +#define STBIR_BGRA_PM STBIR_BGR +#define STBIR_ARGB_PM STBIR_BGR +#define STBIR_ABGR_PM STBIR_BGR +#define STBIR_RA_PM STBIR_BGR +#define STBIR_AR_PM STBIR_BGR + +// must match stbir_datatype +static unsigned char stbir__type_size[] = { + 1,1,1,2,4,2 // STBIR_TYPE_UINT8,STBIR_TYPE_UINT8_SRGB,STBIR_TYPE_UINT8_SRGB_ALPHA,STBIR_TYPE_UINT16,STBIR_TYPE_FLOAT,STBIR_TYPE_HALF_FLOAT +}; + +// When gathering, the contributors are which source pixels contribute. +// When scattering, the contributors are which destination pixels are contributed to. +typedef struct +{ + int n0; // First contributing pixel + int n1; // Last contributing pixel +} stbir__contributors; + +typedef struct +{ + int lowest; // First sample index for whole filter + int highest; // Last sample index for whole filter + int widest; // widest single set of samples for an output +} stbir__filter_extent_info; + +typedef struct +{ + int n0; // First pixel of decode buffer to write to + int n1; // Last pixel of decode that will be written to + int pixel_offset_for_input; // Pixel offset into input_scanline +} stbir__span; + +typedef struct stbir__scale_info +{ + int input_full_size; + int output_sub_size; + float scale; + float inv_scale; + float pixel_shift; // starting shift in output pixel space (in pixels) + int scale_is_rational; + stbir_uint32 scale_numerator, scale_denominator; +} stbir__scale_info; + +typedef struct +{ + stbir__contributors * contributors; + float* coefficients; + stbir__contributors * gather_prescatter_contributors; + float * gather_prescatter_coefficients; + stbir__scale_info scale_info; + float support; + stbir_filter filter_enum; + stbir__kernel_callback * filter_kernel; + stbir__support_callback * filter_support; + stbir_edge edge; + int coefficient_width; + int filter_pixel_width; + int filter_pixel_margin; + int num_contributors; + int contributors_size; + int coefficients_size; + stbir__filter_extent_info extent_info; + int is_gather; // 0 = scatter, 1 = gather with scale >= 1, 2 = gather with scale < 1 + int gather_prescatter_num_contributors; + int gather_prescatter_coefficient_width; + int gather_prescatter_contributors_size; + int gather_prescatter_coefficients_size; +} stbir__sampler; + +typedef struct +{ + stbir__contributors conservative; + int edge_sizes[2]; // this can be less than filter_pixel_margin, if the filter and scaling falls off + stbir__span spans[2]; // can be two spans, if doing input subrect with clamp mode WRAP +} stbir__extents; + +typedef struct +{ +#ifdef STBIR_PROFILE + union + { + struct { stbir_uint64 total, looping, vertical, horizontal, decode, encode, alpha, unalpha; } named; + stbir_uint64 array[8]; + } profile; + stbir_uint64 * current_zone_excluded_ptr; +#endif + float* decode_buffer; + + int ring_buffer_first_scanline; + int ring_buffer_last_scanline; + int ring_buffer_begin_index; // first_scanline is at this index in the ring buffer + int start_output_y, end_output_y; + int start_input_y, end_input_y; // used in scatter only + + #ifdef STBIR__SEPARATE_ALLOCATIONS + float** ring_buffers; // one pointer for each ring buffer + #else + float* ring_buffer; // one big buffer that we index into + #endif + + float* vertical_buffer; + + char no_cache_straddle[64]; +} stbir__per_split_info; + +typedef void stbir__decode_pixels_func( float * decode, int width_times_channels, void const * input ); +typedef void stbir__alpha_weight_func( float * decode_buffer, int width_times_channels ); +typedef void stbir__horizontal_gather_channels_func( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, + stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ); +typedef void stbir__alpha_unweight_func(float * encode_buffer, int width_times_channels ); +typedef void stbir__encode_pixels_func( void * output, int width_times_channels, float const * encode ); + +struct stbir__info +{ +#ifdef STBIR_PROFILE + union + { + struct { stbir_uint64 total, build, alloc, horizontal, vertical, cleanup, pivot; } named; + stbir_uint64 array[7]; + } profile; + stbir_uint64 * current_zone_excluded_ptr; +#endif + stbir__sampler horizontal; + stbir__sampler vertical; + + void const * input_data; + void * output_data; + + int input_stride_bytes; + int output_stride_bytes; + int ring_buffer_length_bytes; // The length of an individual entry in the ring buffer. The total number of ring buffers is stbir__get_filter_pixel_width(filter) + int ring_buffer_num_entries; // Total number of entries in the ring buffer. + + stbir_datatype input_type; + stbir_datatype output_type; + + stbir_input_callback * in_pixels_cb; + void * user_data; + stbir_output_callback * out_pixels_cb; + + stbir__extents scanline_extents; + + void * alloced_mem; + stbir__per_split_info * split_info; // by default 1, but there will be N of these allocated based on the thread init you did + + stbir__decode_pixels_func * decode_pixels; + stbir__alpha_weight_func * alpha_weight; + stbir__horizontal_gather_channels_func * horizontal_gather_channels; + stbir__alpha_unweight_func * alpha_unweight; + stbir__encode_pixels_func * encode_pixels; + + int alloced_total; + int splits; // count of splits + + stbir_internal_pixel_layout input_pixel_layout_internal; + stbir_internal_pixel_layout output_pixel_layout_internal; + + int input_color_and_type; + int offset_x, offset_y; // offset within output_data + int vertical_first; + int channels; + int effective_channels; // same as channels, except on RGBA/ARGB (7), or XA/AX (3) + int alloc_ring_buffer_num_entries; // Number of entries in the ring buffer that will be allocated +}; + + +#define stbir__max_uint8_as_float 255.0f +#define stbir__max_uint16_as_float 65535.0f +#define stbir__max_uint8_as_float_inverted (1.0f/255.0f) +#define stbir__max_uint16_as_float_inverted (1.0f/65535.0f) +#define stbir__small_float ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20)) + +// min/max friendly +#define STBIR_CLAMP(x, xmin, xmax) do { \ + if ( (x) < (xmin) ) (x) = (xmin); \ + if ( (x) > (xmax) ) (x) = (xmax); \ +} while (0) + +static stbir__inline int stbir__min(int a, int b) +{ + return a < b ? a : b; +} + +static stbir__inline int stbir__max(int a, int b) +{ + return a > b ? a : b; +} + +static float stbir__srgb_uchar_to_linear_float[256] = { + 0.000000f, 0.000304f, 0.000607f, 0.000911f, 0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f, + 0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f, 0.006049f, 0.006512f, 0.006995f, 0.007499f, + 0.008023f, 0.008568f, 0.009134f, 0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f, 0.014444f, + 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f, 0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f, + 0.025187f, 0.026241f, 0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f, 0.035601f, 0.036889f, + 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f, 0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f, + 0.054480f, 0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f, 0.068478f, 0.070360f, 0.072272f, + 0.074214f, 0.076185f, 0.078187f, 0.080220f, 0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f, + 0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f, 0.114435f, 0.116971f, 0.119538f, 0.122139f, + 0.124772f, 0.127438f, 0.130136f, 0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f, 0.152926f, + 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f, 0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f, + 0.191202f, 0.194618f, 0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f, 0.223228f, 0.226966f, + 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f, 0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f, + 0.274677f, 0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f, 0.309469f, 0.313989f, 0.318547f, + 0.323143f, 0.327778f, 0.332452f, 0.337164f, 0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f, + 0.376262f, 0.381326f, 0.386430f, 0.391573f, 0.396755f, 0.401978f, 0.407240f, 0.412543f, 0.417885f, 0.423268f, 0.428691f, + 0.434154f, 0.439657f, 0.445201f, 0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473532f, 0.479320f, 0.485150f, 0.491021f, + 0.496933f, 0.502887f, 0.508881f, 0.514918f, 0.520996f, 0.527115f, 0.533276f, 0.539480f, 0.545725f, 0.552011f, 0.558340f, + 0.564712f, 0.571125f, 0.577581f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f, 0.623960f, 0.630757f, + 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f, 0.672443f, 0.679543f, 0.686685f, 0.693872f, 0.701102f, 0.708376f, + 0.715694f, 0.723055f, 0.730461f, 0.737911f, 0.745404f, 0.752942f, 0.760525f, 0.768151f, 0.775822f, 0.783538f, 0.791298f, + 0.799103f, 0.806952f, 0.814847f, 0.822786f, 0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f, + 0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f, 0.947307f, 0.955974f, 0.964686f, 0.973445f, + 0.982251f, 0.991102f, 1.0f +}; + +typedef union +{ + unsigned int u; + float f; +} stbir__FP32; + +// From https://gist.github.com/rygorous/2203834 + +static const stbir_uint32 fp32_to_srgb8_tab4[104] = { + 0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d, + 0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a, + 0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033, + 0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067, + 0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5, + 0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2, + 0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143, + 0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af, + 0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240, + 0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300, + 0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401, + 0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559, + 0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723, +}; + +static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) +{ + static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps + static const stbir__FP32 minval = { (127-13) << 23 }; + stbir_uint32 tab,bias,scale,t; + stbir__FP32 f; + + // Clamp to [2^(-13), 1-eps]; these two values map to 0 and 1, respectively. + // The tests are carefully written so that NaNs map to 0, same as in the reference + // implementation. + if (!(in > minval.f)) // written this way to catch NaNs + return 0; + if (in > almostone.f) + return 255; + + // Do the table lookup and unpack bias, scale + f.f = in; + tab = fp32_to_srgb8_tab4[(f.u - minval.u) >> 20]; + bias = (tab >> 16) << 9; + scale = tab & 0xffff; + + // Grab next-highest mantissa bits and perform linear interpolation + t = (f.u >> 12) & 0xff; + return (unsigned char) ((bias + scale*t) >> 16); +} + +#ifndef STBIR_FORCE_GATHER_FILTER_SCANLINES_AMOUNT +#define STBIR_FORCE_GATHER_FILTER_SCANLINES_AMOUNT 32 // when downsampling and <= 32 scanlines of buffering, use gather. gather used down to 1/8th scaling for 25% win. +#endif + +#ifndef STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS +#define STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS 4 // when threading, what is the minimum number of scanlines for a split? +#endif + +// restrict pointers for the output pointers +#if defined( _MSC_VER ) && !defined(__clang__) + #define STBIR_STREAMOUT_PTR( star ) star __restrict + #define STBIR_NO_UNROLL( ptr ) __assume(ptr) // this oddly keeps msvc from unrolling a loop +#elif defined( __clang__ ) + #define STBIR_STREAMOUT_PTR( star ) star __restrict__ + #define STBIR_NO_UNROLL( ptr ) __asm__ (""::"r"(ptr)) +#elif defined( __GNUC__ ) + #define STBIR_STREAMOUT_PTR( star ) star __restrict__ + #define STBIR_NO_UNROLL( ptr ) __asm__ (""::"r"(ptr)) +#else + #define STBIR_STREAMOUT_PTR( star ) star + #define STBIR_NO_UNROLL( ptr ) +#endif + +#ifdef STBIR_NO_SIMD // force simd off for whatever reason + +// force simd off overrides everything else, so clear it all + +#ifdef STBIR_SSE2 +#undef STBIR_SSE2 +#endif + +#ifdef STBIR_AVX +#undef STBIR_AVX +#endif + +#ifdef STBIR_NEON +#undef STBIR_NEON +#endif + +#ifdef STBIR_AVX2 +#undef STBIR_AVX2 +#endif + +#ifdef STBIR_FP16C +#undef STBIR_FP16C +#endif + +#ifdef STBIR_WASM +#undef STBIR_WASM +#endif + +#ifdef STBIR_SIMD +#undef STBIR_SIMD +#endif + +#else // STBIR_SIMD + +#ifdef STBIR_SSE2 + #include + + #define stbir__simdf __m128 + #define stbir__simdi __m128i + + #define stbir_simdi_castf( reg ) _mm_castps_si128(reg) + #define stbir_simdf_casti( reg ) _mm_castsi128_ps(reg) + + #define stbir__simdf_load( reg, ptr ) (reg) = _mm_loadu_ps( (float const*)(ptr) ) + #define stbir__simdi_load( reg, ptr ) (reg) = _mm_loadu_si128 ( (stbir__simdi const*)(ptr) ) + #define stbir__simdf_load1( out, ptr ) (out) = _mm_load_ss( (float const*)(ptr) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdi_load1( out, ptr ) (out) = _mm_castps_si128( _mm_load_ss( (float const*)(ptr) )) + #define stbir__simdf_load1z( out, ptr ) (out) = _mm_load_ss( (float const*)(ptr) ) // top values must be zero + #define stbir__simdf_frep4( fvar ) _mm_set_ps1( fvar ) + #define stbir__simdf_load1frep4( out, fvar ) (out) = _mm_set_ps1( fvar ) + #define stbir__simdf_load2( out, ptr ) (out) = _mm_castsi128_ps( _mm_loadl_epi64( (__m128i*)(ptr)) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdf_load2z( out, ptr ) (out) = _mm_castsi128_ps( _mm_loadl_epi64( (__m128i*)(ptr)) ) // top values must be zero + #define stbir__simdf_load2hmerge( out, reg, ptr ) (out) = _mm_castpd_ps(_mm_loadh_pd( _mm_castps_pd(reg), (double*)(ptr) )) + + #define stbir__simdf_zeroP() _mm_setzero_ps() + #define stbir__simdf_zero( reg ) (reg) = _mm_setzero_ps() + + #define stbir__simdf_store( ptr, reg ) _mm_storeu_ps( (float*)(ptr), reg ) + #define stbir__simdf_store1( ptr, reg ) _mm_store_ss( (float*)(ptr), reg ) + #define stbir__simdf_store2( ptr, reg ) _mm_storel_epi64( (__m128i*)(ptr), _mm_castps_si128(reg) ) + #define stbir__simdf_store2h( ptr, reg ) _mm_storeh_pd( (double*)(ptr), _mm_castps_pd(reg) ) + + #define stbir__simdi_store( ptr, reg ) _mm_storeu_si128( (__m128i*)(ptr), reg ) + #define stbir__simdi_store1( ptr, reg ) _mm_store_ss( (float*)(ptr), _mm_castsi128_ps(reg) ) + #define stbir__simdi_store2( ptr, reg ) _mm_storel_epi64( (__m128i*)(ptr), (reg) ) + + #define stbir__prefetch( ptr ) _mm_prefetch((char*)(ptr), _MM_HINT_T0 ) + + #define stbir__simdi_expand_u8_to_u32(out0,out1,out2,out3,ireg) \ + { \ + stbir__simdi zero = _mm_setzero_si128(); \ + out2 = _mm_unpacklo_epi8( ireg, zero ); \ + out3 = _mm_unpackhi_epi8( ireg, zero ); \ + out0 = _mm_unpacklo_epi16( out2, zero ); \ + out1 = _mm_unpackhi_epi16( out2, zero ); \ + out2 = _mm_unpacklo_epi16( out3, zero ); \ + out3 = _mm_unpackhi_epi16( out3, zero ); \ + } + +#define stbir__simdi_expand_u8_to_1u32(out,ireg) \ + { \ + stbir__simdi zero = _mm_setzero_si128(); \ + out = _mm_unpacklo_epi8( ireg, zero ); \ + out = _mm_unpacklo_epi16( out, zero ); \ + } + + #define stbir__simdi_expand_u16_to_u32(out0,out1,ireg) \ + { \ + stbir__simdi zero = _mm_setzero_si128(); \ + out0 = _mm_unpacklo_epi16( ireg, zero ); \ + out1 = _mm_unpackhi_epi16( ireg, zero ); \ + } + + #define stbir__simdf_convert_float_to_i32( i, f ) (i) = _mm_cvttps_epi32(f) + #define stbir__simdf_convert_float_to_int( f ) _mm_cvtt_ss2si(f) + #define stbir__simdf_convert_float_to_uint8( f ) ((unsigned char)_mm_cvtsi128_si32(_mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(f,STBIR__CONSTF(STBIR_max_uint8_as_float)),_mm_setzero_ps())))) + #define stbir__simdf_convert_float_to_short( f ) ((unsigned short)_mm_cvtsi128_si32(_mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(f,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps())))) + + #define stbir__simdi_to_int( i ) _mm_cvtsi128_si32(i) + #define stbir__simdi_convert_i32_to_float(out, ireg) (out) = _mm_cvtepi32_ps( ireg ) + #define stbir__simdf_add( out, reg0, reg1 ) (out) = _mm_add_ps( reg0, reg1 ) + #define stbir__simdf_mult( out, reg0, reg1 ) (out) = _mm_mul_ps( reg0, reg1 ) + #define stbir__simdf_mult_mem( out, reg, ptr ) (out) = _mm_mul_ps( reg, _mm_loadu_ps( (float const*)(ptr) ) ) + #define stbir__simdf_mult1_mem( out, reg, ptr ) (out) = _mm_mul_ss( reg, _mm_load_ss( (float const*)(ptr) ) ) + #define stbir__simdf_add_mem( out, reg, ptr ) (out) = _mm_add_ps( reg, _mm_loadu_ps( (float const*)(ptr) ) ) + #define stbir__simdf_add1_mem( out, reg, ptr ) (out) = _mm_add_ss( reg, _mm_load_ss( (float const*)(ptr) ) ) + + #ifdef STBIR_USE_FMA // not on by default to maintain bit identical simd to non-simd + #include + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = _mm_fmadd_ps( mul1, mul2, add ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = _mm_fmadd_ss( mul1, mul2, add ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = _mm_fmadd_ps( mul, _mm_loadu_ps( (float const*)(ptr) ), add ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = _mm_fmadd_ss( mul, _mm_load_ss( (float const*)(ptr) ), add ) + #else + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = _mm_add_ps( add, _mm_mul_ps( mul1, mul2 ) ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = _mm_add_ss( add, _mm_mul_ss( mul1, mul2 ) ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = _mm_add_ps( add, _mm_mul_ps( mul, _mm_loadu_ps( (float const*)(ptr) ) ) ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = _mm_add_ss( add, _mm_mul_ss( mul, _mm_load_ss( (float const*)(ptr) ) ) ) + #endif + + #define stbir__simdf_add1( out, reg0, reg1 ) (out) = _mm_add_ss( reg0, reg1 ) + #define stbir__simdf_mult1( out, reg0, reg1 ) (out) = _mm_mul_ss( reg0, reg1 ) + + #define stbir__simdf_and( out, reg0, reg1 ) (out) = _mm_and_ps( reg0, reg1 ) + #define stbir__simdf_or( out, reg0, reg1 ) (out) = _mm_or_ps( reg0, reg1 ) + + #define stbir__simdf_min( out, reg0, reg1 ) (out) = _mm_min_ps( reg0, reg1 ) + #define stbir__simdf_max( out, reg0, reg1 ) (out) = _mm_max_ps( reg0, reg1 ) + #define stbir__simdf_min1( out, reg0, reg1 ) (out) = _mm_min_ss( reg0, reg1 ) + #define stbir__simdf_max1( out, reg0, reg1 ) (out) = _mm_max_ss( reg0, reg1 ) + + #define stbir__simdf_0123ABCDto3ABx( out, reg0, reg1 ) (out)=_mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( _mm_shuffle_ps( reg1,reg0, (0<<0) + (1<<2) + (2<<4) + (3<<6) )), (3<<0) + (0<<2) + (1<<4) + (2<<6) ) ) + #define stbir__simdf_0123ABCDto23Ax( out, reg0, reg1 ) (out)=_mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( _mm_shuffle_ps( reg1,reg0, (0<<0) + (1<<2) + (2<<4) + (3<<6) )), (2<<0) + (3<<2) + (0<<4) + (1<<6) ) ) + + static const stbir__simdf STBIR_zeroones = { 0.0f,1.0f,0.0f,1.0f }; + static const stbir__simdf STBIR_onezeros = { 1.0f,0.0f,1.0f,0.0f }; + #define stbir__simdf_aaa1( out, alp, ones ) (out)=_mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( _mm_movehl_ps( ones, alp ) ), (1<<0) + (1<<2) + (1<<4) + (2<<6) ) ) + #define stbir__simdf_1aaa( out, alp, ones ) (out)=_mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( _mm_movelh_ps( ones, alp ) ), (0<<0) + (2<<2) + (2<<4) + (2<<6) ) ) + #define stbir__simdf_a1a1( out, alp, ones) (out) = _mm_or_ps( _mm_castsi128_ps( _mm_srli_epi64( _mm_castps_si128(alp), 32 ) ), STBIR_zeroones ) + #define stbir__simdf_1a1a( out, alp, ones) (out) = _mm_or_ps( _mm_castsi128_ps( _mm_slli_epi64( _mm_castps_si128(alp), 32 ) ), STBIR_onezeros ) + + #define stbir__simdf_swiz( reg, one, two, three, four ) _mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( reg ), (one<<0) + (two<<2) + (three<<4) + (four<<6) ) ) + + #define stbir__simdi_and( out, reg0, reg1 ) (out) = _mm_and_si128( reg0, reg1 ) + #define stbir__simdi_or( out, reg0, reg1 ) (out) = _mm_or_si128( reg0, reg1 ) + #define stbir__simdi_16madd( out, reg0, reg1 ) (out) = _mm_madd_epi16( reg0, reg1 ) + + #define stbir__simdf_pack_to_8bytes(out,aa,bb) \ + { \ + stbir__simdf af,bf; \ + stbir__simdi a,b; \ + af = _mm_min_ps( aa, STBIR_max_uint8_as_float ); \ + bf = _mm_min_ps( bb, STBIR_max_uint8_as_float ); \ + af = _mm_max_ps( af, _mm_setzero_ps() ); \ + bf = _mm_max_ps( bf, _mm_setzero_ps() ); \ + a = _mm_cvttps_epi32( af ); \ + b = _mm_cvttps_epi32( bf ); \ + a = _mm_packs_epi32( a, b ); \ + out = _mm_packus_epi16( a, a ); \ + } + + #define stbir__simdf_load4_transposed( o0, o1, o2, o3, ptr ) \ + stbir__simdf_load( o0, (ptr) ); \ + stbir__simdf_load( o1, (ptr)+4 ); \ + stbir__simdf_load( o2, (ptr)+8 ); \ + stbir__simdf_load( o3, (ptr)+12 ); \ + { \ + __m128 tmp0, tmp1, tmp2, tmp3; \ + tmp0 = _mm_unpacklo_ps(o0, o1); \ + tmp2 = _mm_unpacklo_ps(o2, o3); \ + tmp1 = _mm_unpackhi_ps(o0, o1); \ + tmp3 = _mm_unpackhi_ps(o2, o3); \ + o0 = _mm_movelh_ps(tmp0, tmp2); \ + o1 = _mm_movehl_ps(tmp2, tmp0); \ + o2 = _mm_movelh_ps(tmp1, tmp3); \ + o3 = _mm_movehl_ps(tmp3, tmp1); \ + } + + #define stbir__interleave_pack_and_store_16_u8( ptr, r0, r1, r2, r3 ) \ + r0 = _mm_packs_epi32( r0, r1 ); \ + r2 = _mm_packs_epi32( r2, r3 ); \ + r1 = _mm_unpacklo_epi16( r0, r2 ); \ + r3 = _mm_unpackhi_epi16( r0, r2 ); \ + r0 = _mm_unpacklo_epi16( r1, r3 ); \ + r2 = _mm_unpackhi_epi16( r1, r3 ); \ + r0 = _mm_packus_epi16( r0, r2 ); \ + stbir__simdi_store( ptr, r0 ); \ + + #define stbir__simdi_32shr( out, reg, imm ) out = _mm_srli_epi32( reg, imm ) + + #if defined(_MSC_VER) && !defined(__clang__) + // msvc inits with 8 bytes + #define STBIR__CONST_32_TO_8( v ) (char)(unsigned char)((v)&255),(char)(unsigned char)(((v)>>8)&255),(char)(unsigned char)(((v)>>16)&255),(char)(unsigned char)(((v)>>24)&255) + #define STBIR__CONST_4_32i( v ) STBIR__CONST_32_TO_8( v ), STBIR__CONST_32_TO_8( v ), STBIR__CONST_32_TO_8( v ), STBIR__CONST_32_TO_8( v ) + #define STBIR__CONST_4d_32i( v0, v1, v2, v3 ) STBIR__CONST_32_TO_8( v0 ), STBIR__CONST_32_TO_8( v1 ), STBIR__CONST_32_TO_8( v2 ), STBIR__CONST_32_TO_8( v3 ) + #else + // everything else inits with long long's + #define STBIR__CONST_4_32i( v ) (long long)((((stbir_uint64)(stbir_uint32)(v))<<32)|((stbir_uint64)(stbir_uint32)(v))),(long long)((((stbir_uint64)(stbir_uint32)(v))<<32)|((stbir_uint64)(stbir_uint32)(v))) + #define STBIR__CONST_4d_32i( v0, v1, v2, v3 ) (long long)((((stbir_uint64)(stbir_uint32)(v1))<<32)|((stbir_uint64)(stbir_uint32)(v0))),(long long)((((stbir_uint64)(stbir_uint32)(v3))<<32)|((stbir_uint64)(stbir_uint32)(v2))) + #endif + + #define STBIR__SIMDF_CONST(var, x) stbir__simdf var = { x, x, x, x } + #define STBIR__SIMDI_CONST(var, x) stbir__simdi var = { STBIR__CONST_4_32i(x) } + #define STBIR__CONSTF(var) (var) + #define STBIR__CONSTI(var) (var) + + #if defined(STBIR_AVX) || defined(__SSE4_1__) + #include + #define stbir__simdf_pack_to_8words(out,reg0,reg1) out = _mm_packus_epi32(_mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(reg0,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps())), _mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(reg1,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps()))) + #else + STBIR__SIMDI_CONST(stbir__s32_32768, 32768); + STBIR__SIMDI_CONST(stbir__s16_32768, ((32768<<16)|32768)); + + #define stbir__simdf_pack_to_8words(out,reg0,reg1) \ + { \ + stbir__simdi tmp0,tmp1; \ + tmp0 = _mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(reg0,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps())); \ + tmp1 = _mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(reg1,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps())); \ + tmp0 = _mm_sub_epi32( tmp0, stbir__s32_32768 ); \ + tmp1 = _mm_sub_epi32( tmp1, stbir__s32_32768 ); \ + out = _mm_packs_epi32( tmp0, tmp1 ); \ + out = _mm_sub_epi16( out, stbir__s16_32768 ); \ + } + + #endif + + #define STBIR_SIMD + + // if we detect AVX, set the simd8 defines + #ifdef STBIR_AVX + #include + #define STBIR_SIMD8 + #define stbir__simdf8 __m256 + #define stbir__simdi8 __m256i + #define stbir__simdf8_load( out, ptr ) (out) = _mm256_loadu_ps( (float const *)(ptr) ) + #define stbir__simdi8_load( out, ptr ) (out) = _mm256_loadu_si256( (__m256i const *)(ptr) ) + #define stbir__simdf8_mult( out, a, b ) (out) = _mm256_mul_ps( (a), (b) ) + #define stbir__simdf8_store( ptr, out ) _mm256_storeu_ps( (float*)(ptr), out ) + #define stbir__simdi8_store( ptr, reg ) _mm256_storeu_si256( (__m256i*)(ptr), reg ) + #define stbir__simdf8_frep8( fval ) _mm256_set1_ps( fval ) + + #define stbir__simdf8_min( out, reg0, reg1 ) (out) = _mm256_min_ps( reg0, reg1 ) + #define stbir__simdf8_max( out, reg0, reg1 ) (out) = _mm256_max_ps( reg0, reg1 ) + + #define stbir__simdf8_add4halves( out, bot4, top8 ) (out) = _mm_add_ps( bot4, _mm256_extractf128_ps( top8, 1 ) ) + #define stbir__simdf8_mult_mem( out, reg, ptr ) (out) = _mm256_mul_ps( reg, _mm256_loadu_ps( (float const*)(ptr) ) ) + #define stbir__simdf8_add_mem( out, reg, ptr ) (out) = _mm256_add_ps( reg, _mm256_loadu_ps( (float const*)(ptr) ) ) + #define stbir__simdf8_add( out, a, b ) (out) = _mm256_add_ps( a, b ) + #define stbir__simdf8_load1b( out, ptr ) (out) = _mm256_broadcast_ss( ptr ) + #define stbir__simdf_load1rep4( out, ptr ) (out) = _mm_broadcast_ss( ptr ) // avx load instruction + + #define stbir__simdi8_convert_i32_to_float(out, ireg) (out) = _mm256_cvtepi32_ps( ireg ) + #define stbir__simdf8_convert_float_to_i32( i, f ) (i) = _mm256_cvttps_epi32(f) + + #define stbir__simdf8_bot4s( out, a, b ) (out) = _mm256_permute2f128_ps(a,b, (0<<0)+(2<<4) ) + #define stbir__simdf8_top4s( out, a, b ) (out) = _mm256_permute2f128_ps(a,b, (1<<0)+(3<<4) ) + + #define stbir__simdf8_gettop4( reg ) _mm256_extractf128_ps(reg,1) + + #ifdef STBIR_AVX2 + + #define stbir__simdi8_expand_u8_to_u32(out0,out1,ireg) \ + { \ + stbir__simdi8 a, zero =_mm256_setzero_si256();\ + a = _mm256_permute4x64_epi64( _mm256_unpacklo_epi8( _mm256_permute4x64_epi64(_mm256_castsi128_si256(ireg),(0<<0)+(2<<2)+(1<<4)+(3<<6)), zero ),(0<<0)+(2<<2)+(1<<4)+(3<<6)); \ + out0 = _mm256_unpacklo_epi16( a, zero ); \ + out1 = _mm256_unpackhi_epi16( a, zero ); \ + } + + #define stbir__simdf8_pack_to_16bytes(out,aa,bb) \ + { \ + stbir__simdi8 t; \ + stbir__simdf8 af,bf; \ + stbir__simdi8 a,b; \ + af = _mm256_min_ps( aa, STBIR_max_uint8_as_floatX ); \ + bf = _mm256_min_ps( bb, STBIR_max_uint8_as_floatX ); \ + af = _mm256_max_ps( af, _mm256_setzero_ps() ); \ + bf = _mm256_max_ps( bf, _mm256_setzero_ps() ); \ + a = _mm256_cvttps_epi32( af ); \ + b = _mm256_cvttps_epi32( bf ); \ + t = _mm256_permute4x64_epi64( _mm256_packs_epi32( a, b ), (0<<0)+(2<<2)+(1<<4)+(3<<6) ); \ + out = _mm256_castsi256_si128( _mm256_permute4x64_epi64( _mm256_packus_epi16( t, t ), (0<<0)+(2<<2)+(1<<4)+(3<<6) ) ); \ + } + + #define stbir__simdi8_expand_u16_to_u32(out,ireg) out = _mm256_unpacklo_epi16( _mm256_permute4x64_epi64(_mm256_castsi128_si256(ireg),(0<<0)+(2<<2)+(1<<4)+(3<<6)), _mm256_setzero_si256() ); + + #define stbir__simdf8_pack_to_16words(out,aa,bb) \ + { \ + stbir__simdf8 af,bf; \ + stbir__simdi8 a,b; \ + af = _mm256_min_ps( aa, STBIR_max_uint16_as_floatX ); \ + bf = _mm256_min_ps( bb, STBIR_max_uint16_as_floatX ); \ + af = _mm256_max_ps( af, _mm256_setzero_ps() ); \ + bf = _mm256_max_ps( bf, _mm256_setzero_ps() ); \ + a = _mm256_cvttps_epi32( af ); \ + b = _mm256_cvttps_epi32( bf ); \ + (out) = _mm256_permute4x64_epi64( _mm256_packus_epi32(a, b), (0<<0)+(2<<2)+(1<<4)+(3<<6) ); \ + } + + #else + + #define stbir__simdi8_expand_u8_to_u32(out0,out1,ireg) \ + { \ + stbir__simdi a,zero = _mm_setzero_si128(); \ + a = _mm_unpacklo_epi8( ireg, zero ); \ + out0 = _mm256_setr_m128i( _mm_unpacklo_epi16( a, zero ), _mm_unpackhi_epi16( a, zero ) ); \ + a = _mm_unpackhi_epi8( ireg, zero ); \ + out1 = _mm256_setr_m128i( _mm_unpacklo_epi16( a, zero ), _mm_unpackhi_epi16( a, zero ) ); \ + } + + #define stbir__simdf8_pack_to_16bytes(out,aa,bb) \ + { \ + stbir__simdi t; \ + stbir__simdf8 af,bf; \ + stbir__simdi8 a,b; \ + af = _mm256_min_ps( aa, STBIR_max_uint8_as_floatX ); \ + bf = _mm256_min_ps( bb, STBIR_max_uint8_as_floatX ); \ + af = _mm256_max_ps( af, _mm256_setzero_ps() ); \ + bf = _mm256_max_ps( bf, _mm256_setzero_ps() ); \ + a = _mm256_cvttps_epi32( af ); \ + b = _mm256_cvttps_epi32( bf ); \ + out = _mm_packs_epi32( _mm256_castsi256_si128(a), _mm256_extractf128_si256( a, 1 ) ); \ + out = _mm_packus_epi16( out, out ); \ + t = _mm_packs_epi32( _mm256_castsi256_si128(b), _mm256_extractf128_si256( b, 1 ) ); \ + t = _mm_packus_epi16( t, t ); \ + out = _mm_castps_si128( _mm_shuffle_ps( _mm_castsi128_ps(out), _mm_castsi128_ps(t), (0<<0)+(1<<2)+(0<<4)+(1<<6) ) ); \ + } + + #define stbir__simdi8_expand_u16_to_u32(out,ireg) \ + { \ + stbir__simdi a,b,zero = _mm_setzero_si128(); \ + a = _mm_unpacklo_epi16( ireg, zero ); \ + b = _mm_unpackhi_epi16( ireg, zero ); \ + out = _mm256_insertf128_si256( _mm256_castsi128_si256( a ), b, 1 ); \ + } + + #define stbir__simdf8_pack_to_16words(out,aa,bb) \ + { \ + stbir__simdi t0,t1; \ + stbir__simdf8 af,bf; \ + stbir__simdi8 a,b; \ + af = _mm256_min_ps( aa, STBIR_max_uint16_as_floatX ); \ + bf = _mm256_min_ps( bb, STBIR_max_uint16_as_floatX ); \ + af = _mm256_max_ps( af, _mm256_setzero_ps() ); \ + bf = _mm256_max_ps( bf, _mm256_setzero_ps() ); \ + a = _mm256_cvttps_epi32( af ); \ + b = _mm256_cvttps_epi32( bf ); \ + t0 = _mm_packus_epi32( _mm256_castsi256_si128(a), _mm256_extractf128_si256( a, 1 ) ); \ + t1 = _mm_packus_epi32( _mm256_castsi256_si128(b), _mm256_extractf128_si256( b, 1 ) ); \ + out = _mm256_setr_m128i( t0, t1 ); \ + } + + #endif + + static __m256i stbir_00001111 = { STBIR__CONST_4d_32i( 0, 0, 0, 0 ), STBIR__CONST_4d_32i( 1, 1, 1, 1 ) }; + #define stbir__simdf8_0123to00001111( out, in ) (out) = _mm256_permutevar_ps ( in, stbir_00001111 ) + + static __m256i stbir_22223333 = { STBIR__CONST_4d_32i( 2, 2, 2, 2 ), STBIR__CONST_4d_32i( 3, 3, 3, 3 ) }; + #define stbir__simdf8_0123to22223333( out, in ) (out) = _mm256_permutevar_ps ( in, stbir_22223333 ) + + #define stbir__simdf8_0123to2222( out, in ) (out) = stbir__simdf_swiz(_mm256_castps256_ps128(in), 2,2,2,2 ) + + #define stbir__simdf8_load4b( out, ptr ) (out) = _mm256_broadcast_ps( (__m128 const *)(ptr) ) + + static __m256i stbir_00112233 = { STBIR__CONST_4d_32i( 0, 0, 1, 1 ), STBIR__CONST_4d_32i( 2, 2, 3, 3 ) }; + #define stbir__simdf8_0123to00112233( out, in ) (out) = _mm256_permutevar_ps ( in, stbir_00112233 ) + #define stbir__simdf8_add4( out, a8, b ) (out) = _mm256_add_ps( a8, _mm256_castps128_ps256( b ) ) + + static __m256i stbir_load6 = { STBIR__CONST_4_32i( 0x80000000 ), STBIR__CONST_4d_32i( 0x80000000, 0x80000000, 0, 0 ) }; + #define stbir__simdf8_load6z( out, ptr ) (out) = _mm256_maskload_ps( ptr, stbir_load6 ) + + #define stbir__simdf8_0123to00000000( out, in ) (out) = _mm256_shuffle_ps ( in, in, (0<<0)+(0<<2)+(0<<4)+(0<<6) ) + #define stbir__simdf8_0123to11111111( out, in ) (out) = _mm256_shuffle_ps ( in, in, (1<<0)+(1<<2)+(1<<4)+(1<<6) ) + #define stbir__simdf8_0123to22222222( out, in ) (out) = _mm256_shuffle_ps ( in, in, (2<<0)+(2<<2)+(2<<4)+(2<<6) ) + #define stbir__simdf8_0123to33333333( out, in ) (out) = _mm256_shuffle_ps ( in, in, (3<<0)+(3<<2)+(3<<4)+(3<<6) ) + #define stbir__simdf8_0123to21032103( out, in ) (out) = _mm256_shuffle_ps ( in, in, (2<<0)+(1<<2)+(0<<4)+(3<<6) ) + #define stbir__simdf8_0123to32103210( out, in ) (out) = _mm256_shuffle_ps ( in, in, (3<<0)+(2<<2)+(1<<4)+(0<<6) ) + #define stbir__simdf8_0123to12301230( out, in ) (out) = _mm256_shuffle_ps ( in, in, (1<<0)+(2<<2)+(3<<4)+(0<<6) ) + #define stbir__simdf8_0123to10321032( out, in ) (out) = _mm256_shuffle_ps ( in, in, (1<<0)+(0<<2)+(3<<4)+(2<<6) ) + #define stbir__simdf8_0123to30123012( out, in ) (out) = _mm256_shuffle_ps ( in, in, (3<<0)+(0<<2)+(1<<4)+(2<<6) ) + + #define stbir__simdf8_0123to11331133( out, in ) (out) = _mm256_shuffle_ps ( in, in, (1<<0)+(1<<2)+(3<<4)+(3<<6) ) + #define stbir__simdf8_0123to00220022( out, in ) (out) = _mm256_shuffle_ps ( in, in, (0<<0)+(0<<2)+(2<<4)+(2<<6) ) + + #define stbir__simdf8_aaa1( out, alp, ones ) (out) = _mm256_blend_ps( alp, ones, (1<<0)+(1<<1)+(1<<2)+(0<<3)+(1<<4)+(1<<5)+(1<<6)+(0<<7)); (out)=_mm256_shuffle_ps( out,out, (3<<0) + (3<<2) + (3<<4) + (0<<6) ) + #define stbir__simdf8_1aaa( out, alp, ones ) (out) = _mm256_blend_ps( alp, ones, (0<<0)+(1<<1)+(1<<2)+(1<<3)+(0<<4)+(1<<5)+(1<<6)+(1<<7)); (out)=_mm256_shuffle_ps( out,out, (1<<0) + (0<<2) + (0<<4) + (0<<6) ) + #define stbir__simdf8_a1a1( out, alp, ones) (out) = _mm256_blend_ps( alp, ones, (1<<0)+(0<<1)+(1<<2)+(0<<3)+(1<<4)+(0<<5)+(1<<6)+(0<<7)); (out)=_mm256_shuffle_ps( out,out, (1<<0) + (0<<2) + (3<<4) + (2<<6) ) + #define stbir__simdf8_1a1a( out, alp, ones) (out) = _mm256_blend_ps( alp, ones, (0<<0)+(1<<1)+(0<<2)+(1<<3)+(0<<4)+(1<<5)+(0<<6)+(1<<7)); (out)=_mm256_shuffle_ps( out,out, (1<<0) + (0<<2) + (3<<4) + (2<<6) ) + + #define stbir__simdf8_zero( reg ) (reg) = _mm256_setzero_ps() + + #ifdef STBIR_USE_FMA // not on by default to maintain bit identical simd to non-simd + #define stbir__simdf8_madd( out, add, mul1, mul2 ) (out) = _mm256_fmadd_ps( mul1, mul2, add ) + #define stbir__simdf8_madd_mem( out, add, mul, ptr ) (out) = _mm256_fmadd_ps( mul, _mm256_loadu_ps( (float const*)(ptr) ), add ) + #define stbir__simdf8_madd_mem4( out, add, mul, ptr )(out) = _mm256_fmadd_ps( _mm256_setr_m128( mul, _mm_setzero_ps() ), _mm256_setr_m128( _mm_loadu_ps( (float const*)(ptr) ), _mm_setzero_ps() ), add ) + #else + #define stbir__simdf8_madd( out, add, mul1, mul2 ) (out) = _mm256_add_ps( add, _mm256_mul_ps( mul1, mul2 ) ) + #define stbir__simdf8_madd_mem( out, add, mul, ptr ) (out) = _mm256_add_ps( add, _mm256_mul_ps( mul, _mm256_loadu_ps( (float const*)(ptr) ) ) ) + #define stbir__simdf8_madd_mem4( out, add, mul, ptr ) (out) = _mm256_add_ps( add, _mm256_setr_m128( _mm_mul_ps( mul, _mm_loadu_ps( (float const*)(ptr) ) ), _mm_setzero_ps() ) ) + #endif + #define stbir__if_simdf8_cast_to_simdf4( val ) _mm256_castps256_ps128( val ) + + #endif + + #ifdef STBIR_FLOORF + #undef STBIR_FLOORF + #endif + #define STBIR_FLOORF stbir_simd_floorf + static stbir__inline float stbir_simd_floorf(float x) // martins floorf + { + #if defined(STBIR_AVX) || defined(__SSE4_1__) || defined(STBIR_SSE41) + __m128 t = _mm_set_ss(x); + return _mm_cvtss_f32( _mm_floor_ss(t, t) ); + #else + __m128 f = _mm_set_ss(x); + __m128 t = _mm_cvtepi32_ps(_mm_cvttps_epi32(f)); + __m128 r = _mm_add_ss(t, _mm_and_ps(_mm_cmplt_ss(f, t), _mm_set_ss(-1.0f))); + return _mm_cvtss_f32(r); + #endif + } + + #ifdef STBIR_CEILF + #undef STBIR_CEILF + #endif + #define STBIR_CEILF stbir_simd_ceilf + static stbir__inline float stbir_simd_ceilf(float x) // martins ceilf + { + #if defined(STBIR_AVX) || defined(__SSE4_1__) || defined(STBIR_SSE41) + __m128 t = _mm_set_ss(x); + return _mm_cvtss_f32( _mm_ceil_ss(t, t) ); + #else + __m128 f = _mm_set_ss(x); + __m128 t = _mm_cvtepi32_ps(_mm_cvttps_epi32(f)); + __m128 r = _mm_add_ss(t, _mm_and_ps(_mm_cmplt_ss(t, f), _mm_set_ss(1.0f))); + return _mm_cvtss_f32(r); + #endif + } + +#elif defined(STBIR_NEON) + + #include + + #define stbir__simdf float32x4_t + #define stbir__simdi uint32x4_t + + #define stbir_simdi_castf( reg ) vreinterpretq_u32_f32(reg) + #define stbir_simdf_casti( reg ) vreinterpretq_f32_u32(reg) + + #define stbir__simdf_load( reg, ptr ) (reg) = vld1q_f32( (float const*)(ptr) ) + #define stbir__simdi_load( reg, ptr ) (reg) = vld1q_u32( (uint32_t const*)(ptr) ) + #define stbir__simdf_load1( out, ptr ) (out) = vld1q_dup_f32( (float const*)(ptr) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdi_load1( out, ptr ) (out) = vld1q_dup_u32( (uint32_t const*)(ptr) ) + #define stbir__simdf_load1z( out, ptr ) (out) = vld1q_lane_f32( (float const*)(ptr), vdupq_n_f32(0), 0 ) // top values must be zero + #define stbir__simdf_frep4( fvar ) vdupq_n_f32( fvar ) + #define stbir__simdf_load1frep4( out, fvar ) (out) = vdupq_n_f32( fvar ) + #define stbir__simdf_load2( out, ptr ) (out) = vcombine_f32( vld1_f32( (float const*)(ptr) ), vcreate_f32(0) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdf_load2z( out, ptr ) (out) = vcombine_f32( vld1_f32( (float const*)(ptr) ), vcreate_f32(0) ) // top values must be zero + #define stbir__simdf_load2hmerge( out, reg, ptr ) (out) = vcombine_f32( vget_low_f32(reg), vld1_f32( (float const*)(ptr) ) ) + + #define stbir__simdf_zeroP() vdupq_n_f32(0) + #define stbir__simdf_zero( reg ) (reg) = vdupq_n_f32(0) + + #define stbir__simdf_store( ptr, reg ) vst1q_f32( (float*)(ptr), reg ) + #define stbir__simdf_store1( ptr, reg ) vst1q_lane_f32( (float*)(ptr), reg, 0) + #define stbir__simdf_store2( ptr, reg ) vst1_f32( (float*)(ptr), vget_low_f32(reg) ) + #define stbir__simdf_store2h( ptr, reg ) vst1_f32( (float*)(ptr), vget_high_f32(reg) ) + + #define stbir__simdi_store( ptr, reg ) vst1q_u32( (uint32_t*)(ptr), reg ) + #define stbir__simdi_store1( ptr, reg ) vst1q_lane_u32( (uint32_t*)(ptr), reg, 0 ) + #define stbir__simdi_store2( ptr, reg ) vst1_u32( (uint32_t*)(ptr), vget_low_u32(reg) ) + + #define stbir__prefetch( ptr ) + + #define stbir__simdi_expand_u8_to_u32(out0,out1,out2,out3,ireg) \ + { \ + uint16x8_t l = vmovl_u8( vget_low_u8 ( vreinterpretq_u8_u32(ireg) ) ); \ + uint16x8_t h = vmovl_u8( vget_high_u8( vreinterpretq_u8_u32(ireg) ) ); \ + out0 = vmovl_u16( vget_low_u16 ( l ) ); \ + out1 = vmovl_u16( vget_high_u16( l ) ); \ + out2 = vmovl_u16( vget_low_u16 ( h ) ); \ + out3 = vmovl_u16( vget_high_u16( h ) ); \ + } + + #define stbir__simdi_expand_u8_to_1u32(out,ireg) \ + { \ + uint16x8_t tmp = vmovl_u8( vget_low_u8( vreinterpretq_u8_u32(ireg) ) ); \ + out = vmovl_u16( vget_low_u16( tmp ) ); \ + } + + #define stbir__simdi_expand_u16_to_u32(out0,out1,ireg) \ + { \ + uint16x8_t tmp = vreinterpretq_u16_u32(ireg); \ + out0 = vmovl_u16( vget_low_u16 ( tmp ) ); \ + out1 = vmovl_u16( vget_high_u16( tmp ) ); \ + } + + #define stbir__simdf_convert_float_to_i32( i, f ) (i) = vreinterpretq_u32_s32( vcvtq_s32_f32(f) ) + #define stbir__simdf_convert_float_to_int( f ) vgetq_lane_s32(vcvtq_s32_f32(f), 0) + #define stbir__simdi_to_int( i ) (int)vgetq_lane_u32(i, 0) + #define stbir__simdf_convert_float_to_uint8( f ) ((unsigned char)vgetq_lane_s32(vcvtq_s32_f32(vmaxq_f32(vminq_f32(f,STBIR__CONSTF(STBIR_max_uint8_as_float)),vdupq_n_f32(0))), 0)) + #define stbir__simdf_convert_float_to_short( f ) ((unsigned short)vgetq_lane_s32(vcvtq_s32_f32(vmaxq_f32(vminq_f32(f,STBIR__CONSTF(STBIR_max_uint16_as_float)),vdupq_n_f32(0))), 0)) + #define stbir__simdi_convert_i32_to_float(out, ireg) (out) = vcvtq_f32_s32( vreinterpretq_s32_u32(ireg) ) + #define stbir__simdf_add( out, reg0, reg1 ) (out) = vaddq_f32( reg0, reg1 ) + #define stbir__simdf_mult( out, reg0, reg1 ) (out) = vmulq_f32( reg0, reg1 ) + #define stbir__simdf_mult_mem( out, reg, ptr ) (out) = vmulq_f32( reg, vld1q_f32( (float const*)(ptr) ) ) + #define stbir__simdf_mult1_mem( out, reg, ptr ) (out) = vmulq_f32( reg, vld1q_dup_f32( (float const*)(ptr) ) ) + #define stbir__simdf_add_mem( out, reg, ptr ) (out) = vaddq_f32( reg, vld1q_f32( (float const*)(ptr) ) ) + #define stbir__simdf_add1_mem( out, reg, ptr ) (out) = vaddq_f32( reg, vld1q_dup_f32( (float const*)(ptr) ) ) + + #ifdef STBIR_USE_FMA // not on by default to maintain bit identical simd to non-simd (and also x64 no madd to arm madd) + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = vfmaq_f32( add, mul1, mul2 ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = vfmaq_f32( add, mul1, mul2 ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = vfmaq_f32( add, mul, vld1q_f32( (float const*)(ptr) ) ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = vfmaq_f32( add, mul, vld1q_dup_f32( (float const*)(ptr) ) ) + #else + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = vaddq_f32( add, vmulq_f32( mul1, mul2 ) ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = vaddq_f32( add, vmulq_f32( mul1, mul2 ) ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = vaddq_f32( add, vmulq_f32( mul, vld1q_f32( (float const*)(ptr) ) ) ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = vaddq_f32( add, vmulq_f32( mul, vld1q_dup_f32( (float const*)(ptr) ) ) ) + #endif + + #define stbir__simdf_add1( out, reg0, reg1 ) (out) = vaddq_f32( reg0, reg1 ) + #define stbir__simdf_mult1( out, reg0, reg1 ) (out) = vmulq_f32( reg0, reg1 ) + + #define stbir__simdf_and( out, reg0, reg1 ) (out) = vreinterpretq_f32_u32( vandq_u32( vreinterpretq_u32_f32(reg0), vreinterpretq_u32_f32(reg1) ) ) + #define stbir__simdf_or( out, reg0, reg1 ) (out) = vreinterpretq_f32_u32( vorrq_u32( vreinterpretq_u32_f32(reg0), vreinterpretq_u32_f32(reg1) ) ) + + #define stbir__simdf_min( out, reg0, reg1 ) (out) = vminq_f32( reg0, reg1 ) + #define stbir__simdf_max( out, reg0, reg1 ) (out) = vmaxq_f32( reg0, reg1 ) + #define stbir__simdf_min1( out, reg0, reg1 ) (out) = vminq_f32( reg0, reg1 ) + #define stbir__simdf_max1( out, reg0, reg1 ) (out) = vmaxq_f32( reg0, reg1 ) + + #define stbir__simdf_0123ABCDto3ABx( out, reg0, reg1 ) (out) = vextq_f32( reg0, reg1, 3 ) + #define stbir__simdf_0123ABCDto23Ax( out, reg0, reg1 ) (out) = vextq_f32( reg0, reg1, 2 ) + + #define stbir__simdf_a1a1( out, alp, ones ) (out) = vzipq_f32(vuzpq_f32(alp, alp).val[1], ones).val[0] + #define stbir__simdf_1a1a( out, alp, ones ) (out) = vzipq_f32(ones, vuzpq_f32(alp, alp).val[0]).val[0] + + #if defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) + + #define stbir__simdf_aaa1( out, alp, ones ) (out) = vcopyq_laneq_f32(vdupq_n_f32(vgetq_lane_f32(alp, 3)), 3, ones, 3) + #define stbir__simdf_1aaa( out, alp, ones ) (out) = vcopyq_laneq_f32(vdupq_n_f32(vgetq_lane_f32(alp, 0)), 0, ones, 0) + + #if defined( _MSC_VER ) && !defined(__clang__) + #define stbir_make16(a,b,c,d) vcombine_u8( \ + vcreate_u8( (4*a+0) | ((4*a+1)<<8) | ((4*a+2)<<16) | ((4*a+3)<<24) | \ + ((stbir_uint64)(4*b+0)<<32) | ((stbir_uint64)(4*b+1)<<40) | ((stbir_uint64)(4*b+2)<<48) | ((stbir_uint64)(4*b+3)<<56)), \ + vcreate_u8( (4*c+0) | ((4*c+1)<<8) | ((4*c+2)<<16) | ((4*c+3)<<24) | \ + ((stbir_uint64)(4*d+0)<<32) | ((stbir_uint64)(4*d+1)<<40) | ((stbir_uint64)(4*d+2)<<48) | ((stbir_uint64)(4*d+3)<<56) ) ) + #else + #define stbir_make16(a,b,c,d) (uint8x16_t){4*a+0,4*a+1,4*a+2,4*a+3,4*b+0,4*b+1,4*b+2,4*b+3,4*c+0,4*c+1,4*c+2,4*c+3,4*d+0,4*d+1,4*d+2,4*d+3} + #endif + + #define stbir__simdf_swiz( reg, one, two, three, four ) vreinterpretq_f32_u8( vqtbl1q_u8( vreinterpretq_u8_f32(reg), stbir_make16(one, two, three, four) ) ) + + #define stbir__simdi_16madd( out, reg0, reg1 ) \ + { \ + int16x8_t r0 = vreinterpretq_s16_u32(reg0); \ + int16x8_t r1 = vreinterpretq_s16_u32(reg1); \ + int32x4_t tmp0 = vmull_s16( vget_low_s16(r0), vget_low_s16(r1) ); \ + int32x4_t tmp1 = vmull_s16( vget_high_s16(r0), vget_high_s16(r1) ); \ + (out) = vreinterpretq_u32_s32( vpaddq_s32(tmp0, tmp1) ); \ + } + + #else + + #define stbir__simdf_aaa1( out, alp, ones ) (out) = vsetq_lane_f32(1.0f, vdupq_n_f32(vgetq_lane_f32(alp, 3)), 3) + #define stbir__simdf_1aaa( out, alp, ones ) (out) = vsetq_lane_f32(1.0f, vdupq_n_f32(vgetq_lane_f32(alp, 0)), 0) + + #if defined( _MSC_VER ) && !defined(__clang__) + static stbir__inline uint8x8x2_t stbir_make8x2(float32x4_t reg) + { + uint8x8x2_t r = { { vget_low_u8(vreinterpretq_u8_f32(reg)), vget_high_u8(vreinterpretq_u8_f32(reg)) } }; + return r; + } + #define stbir_make8(a,b) vcreate_u8( \ + (4*a+0) | ((4*a+1)<<8) | ((4*a+2)<<16) | ((4*a+3)<<24) | \ + ((stbir_uint64)(4*b+0)<<32) | ((stbir_uint64)(4*b+1)<<40) | ((stbir_uint64)(4*b+2)<<48) | ((stbir_uint64)(4*b+3)<<56) ) + #else + #define stbir_make8x2(reg) (uint8x8x2_t){ { vget_low_u8(vreinterpretq_u8_f32(reg)), vget_high_u8(vreinterpretq_u8_f32(reg)) } } + #define stbir_make8(a,b) (uint8x8_t){4*a+0,4*a+1,4*a+2,4*a+3,4*b+0,4*b+1,4*b+2,4*b+3} + #endif + + #define stbir__simdf_swiz( reg, one, two, three, four ) vreinterpretq_f32_u8( vcombine_u8( \ + vtbl2_u8( stbir_make8x2( reg ), stbir_make8( one, two ) ), \ + vtbl2_u8( stbir_make8x2( reg ), stbir_make8( three, four ) ) ) ) + + #define stbir__simdi_16madd( out, reg0, reg1 ) \ + { \ + int16x8_t r0 = vreinterpretq_s16_u32(reg0); \ + int16x8_t r1 = vreinterpretq_s16_u32(reg1); \ + int32x4_t tmp0 = vmull_s16( vget_low_s16(r0), vget_low_s16(r1) ); \ + int32x4_t tmp1 = vmull_s16( vget_high_s16(r0), vget_high_s16(r1) ); \ + int32x2_t out0 = vpadd_s32( vget_low_s32(tmp0), vget_high_s32(tmp0) ); \ + int32x2_t out1 = vpadd_s32( vget_low_s32(tmp1), vget_high_s32(tmp1) ); \ + (out) = vreinterpretq_u32_s32( vcombine_s32(out0, out1) ); \ + } + + #endif + + #define stbir__simdi_and( out, reg0, reg1 ) (out) = vandq_u32( reg0, reg1 ) + #define stbir__simdi_or( out, reg0, reg1 ) (out) = vorrq_u32( reg0, reg1 ) + + #define stbir__simdf_pack_to_8bytes(out,aa,bb) \ + { \ + float32x4_t af = vmaxq_f32( vminq_f32(aa,STBIR__CONSTF(STBIR_max_uint8_as_float) ), vdupq_n_f32(0) ); \ + float32x4_t bf = vmaxq_f32( vminq_f32(bb,STBIR__CONSTF(STBIR_max_uint8_as_float) ), vdupq_n_f32(0) ); \ + int16x4_t ai = vqmovn_s32( vcvtq_s32_f32( af ) ); \ + int16x4_t bi = vqmovn_s32( vcvtq_s32_f32( bf ) ); \ + uint8x8_t out8 = vqmovun_s16( vcombine_s16(ai, bi) ); \ + out = vreinterpretq_u32_u8( vcombine_u8(out8, out8) ); \ + } + + #define stbir__simdf_pack_to_8words(out,aa,bb) \ + { \ + float32x4_t af = vmaxq_f32( vminq_f32(aa,STBIR__CONSTF(STBIR_max_uint16_as_float) ), vdupq_n_f32(0) ); \ + float32x4_t bf = vmaxq_f32( vminq_f32(bb,STBIR__CONSTF(STBIR_max_uint16_as_float) ), vdupq_n_f32(0) ); \ + int32x4_t ai = vcvtq_s32_f32( af ); \ + int32x4_t bi = vcvtq_s32_f32( bf ); \ + out = vreinterpretq_u32_u16( vcombine_u16(vqmovun_s32(ai), vqmovun_s32(bi)) ); \ + } + + #define stbir__interleave_pack_and_store_16_u8( ptr, r0, r1, r2, r3 ) \ + { \ + int16x4x2_t tmp0 = vzip_s16( vqmovn_s32(vreinterpretq_s32_u32(r0)), vqmovn_s32(vreinterpretq_s32_u32(r2)) ); \ + int16x4x2_t tmp1 = vzip_s16( vqmovn_s32(vreinterpretq_s32_u32(r1)), vqmovn_s32(vreinterpretq_s32_u32(r3)) ); \ + uint8x8x2_t out = \ + { { \ + vqmovun_s16( vcombine_s16(tmp0.val[0], tmp0.val[1]) ), \ + vqmovun_s16( vcombine_s16(tmp1.val[0], tmp1.val[1]) ), \ + } }; \ + vst2_u8(ptr, out); \ + } + + #define stbir__simdf_load4_transposed( o0, o1, o2, o3, ptr ) \ + { \ + float32x4x4_t tmp = vld4q_f32(ptr); \ + o0 = tmp.val[0]; \ + o1 = tmp.val[1]; \ + o2 = tmp.val[2]; \ + o3 = tmp.val[3]; \ + } + + #define stbir__simdi_32shr( out, reg, imm ) out = vshrq_n_u32( reg, imm ) + + #if defined( _MSC_VER ) && !defined(__clang__) + #define STBIR__SIMDF_CONST(var, x) __declspec(align(8)) float var[] = { x, x, x, x } + #define STBIR__SIMDI_CONST(var, x) __declspec(align(8)) uint32_t var[] = { x, x, x, x } + #define STBIR__CONSTF(var) (*(const float32x4_t*)var) + #define STBIR__CONSTI(var) (*(const uint32x4_t*)var) + #else + #define STBIR__SIMDF_CONST(var, x) stbir__simdf var = { x, x, x, x } + #define STBIR__SIMDI_CONST(var, x) stbir__simdi var = { x, x, x, x } + #define STBIR__CONSTF(var) (var) + #define STBIR__CONSTI(var) (var) + #endif + + #ifdef STBIR_FLOORF + #undef STBIR_FLOORF + #endif + #define STBIR_FLOORF stbir_simd_floorf + static stbir__inline float stbir_simd_floorf(float x) + { + #if defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) + return vget_lane_f32( vrndm_f32( vdup_n_f32(x) ), 0); + #else + float32x2_t f = vdup_n_f32(x); + float32x2_t t = vcvt_f32_s32(vcvt_s32_f32(f)); + uint32x2_t a = vclt_f32(f, t); + uint32x2_t b = vreinterpret_u32_f32(vdup_n_f32(-1.0f)); + float32x2_t r = vadd_f32(t, vreinterpret_f32_u32(vand_u32(a, b))); + return vget_lane_f32(r, 0); + #endif + } + + #ifdef STBIR_CEILF + #undef STBIR_CEILF + #endif + #define STBIR_CEILF stbir_simd_ceilf + static stbir__inline float stbir_simd_ceilf(float x) + { + #if defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) + return vget_lane_f32( vrndp_f32( vdup_n_f32(x) ), 0); + #else + float32x2_t f = vdup_n_f32(x); + float32x2_t t = vcvt_f32_s32(vcvt_s32_f32(f)); + uint32x2_t a = vclt_f32(t, f); + uint32x2_t b = vreinterpret_u32_f32(vdup_n_f32(1.0f)); + float32x2_t r = vadd_f32(t, vreinterpret_f32_u32(vand_u32(a, b))); + return vget_lane_f32(r, 0); + #endif + } + + #define STBIR_SIMD + +#elif defined(STBIR_WASM) + + #include + + #define stbir__simdf v128_t + #define stbir__simdi v128_t + + #define stbir_simdi_castf( reg ) (reg) + #define stbir_simdf_casti( reg ) (reg) + + #define stbir__simdf_load( reg, ptr ) (reg) = wasm_v128_load( (void const*)(ptr) ) + #define stbir__simdi_load( reg, ptr ) (reg) = wasm_v128_load( (void const*)(ptr) ) + #define stbir__simdf_load1( out, ptr ) (out) = wasm_v128_load32_splat( (void const*)(ptr) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdi_load1( out, ptr ) (out) = wasm_v128_load32_splat( (void const*)(ptr) ) + #define stbir__simdf_load1z( out, ptr ) (out) = wasm_v128_load32_zero( (void const*)(ptr) ) // top values must be zero + #define stbir__simdf_frep4( fvar ) wasm_f32x4_splat( fvar ) + #define stbir__simdf_load1frep4( out, fvar ) (out) = wasm_f32x4_splat( fvar ) + #define stbir__simdf_load2( out, ptr ) (out) = wasm_v128_load64_splat( (void const*)(ptr) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdf_load2z( out, ptr ) (out) = wasm_v128_load64_zero( (void const*)(ptr) ) // top values must be zero + #define stbir__simdf_load2hmerge( out, reg, ptr ) (out) = wasm_v128_load64_lane( (void const*)(ptr), reg, 1 ) + + #define stbir__simdf_zeroP() wasm_f32x4_const_splat(0) + #define stbir__simdf_zero( reg ) (reg) = wasm_f32x4_const_splat(0) + + #define stbir__simdf_store( ptr, reg ) wasm_v128_store( (void*)(ptr), reg ) + #define stbir__simdf_store1( ptr, reg ) wasm_v128_store32_lane( (void*)(ptr), reg, 0 ) + #define stbir__simdf_store2( ptr, reg ) wasm_v128_store64_lane( (void*)(ptr), reg, 0 ) + #define stbir__simdf_store2h( ptr, reg ) wasm_v128_store64_lane( (void*)(ptr), reg, 1 ) + + #define stbir__simdi_store( ptr, reg ) wasm_v128_store( (void*)(ptr), reg ) + #define stbir__simdi_store1( ptr, reg ) wasm_v128_store32_lane( (void*)(ptr), reg, 0 ) + #define stbir__simdi_store2( ptr, reg ) wasm_v128_store64_lane( (void*)(ptr), reg, 0 ) + + #define stbir__prefetch( ptr ) + + #define stbir__simdi_expand_u8_to_u32(out0,out1,out2,out3,ireg) \ + { \ + v128_t l = wasm_u16x8_extend_low_u8x16 ( ireg ); \ + v128_t h = wasm_u16x8_extend_high_u8x16( ireg ); \ + out0 = wasm_u32x4_extend_low_u16x8 ( l ); \ + out1 = wasm_u32x4_extend_high_u16x8( l ); \ + out2 = wasm_u32x4_extend_low_u16x8 ( h ); \ + out3 = wasm_u32x4_extend_high_u16x8( h ); \ + } + + #define stbir__simdi_expand_u8_to_1u32(out,ireg) \ + { \ + v128_t tmp = wasm_u16x8_extend_low_u8x16(ireg); \ + out = wasm_u32x4_extend_low_u16x8(tmp); \ + } + + #define stbir__simdi_expand_u16_to_u32(out0,out1,ireg) \ + { \ + out0 = wasm_u32x4_extend_low_u16x8 ( ireg ); \ + out1 = wasm_u32x4_extend_high_u16x8( ireg ); \ + } + + #define stbir__simdf_convert_float_to_i32( i, f ) (i) = wasm_i32x4_trunc_sat_f32x4(f) + #define stbir__simdf_convert_float_to_int( f ) wasm_i32x4_extract_lane(wasm_i32x4_trunc_sat_f32x4(f), 0) + #define stbir__simdi_to_int( i ) wasm_i32x4_extract_lane(i, 0) + #define stbir__simdf_convert_float_to_uint8( f ) ((unsigned char)wasm_i32x4_extract_lane(wasm_i32x4_trunc_sat_f32x4(wasm_f32x4_max(wasm_f32x4_min(f,STBIR_max_uint8_as_float),wasm_f32x4_const_splat(0))), 0)) + #define stbir__simdf_convert_float_to_short( f ) ((unsigned short)wasm_i32x4_extract_lane(wasm_i32x4_trunc_sat_f32x4(wasm_f32x4_max(wasm_f32x4_min(f,STBIR_max_uint16_as_float),wasm_f32x4_const_splat(0))), 0)) + #define stbir__simdi_convert_i32_to_float(out, ireg) (out) = wasm_f32x4_convert_i32x4(ireg) + #define stbir__simdf_add( out, reg0, reg1 ) (out) = wasm_f32x4_add( reg0, reg1 ) + #define stbir__simdf_mult( out, reg0, reg1 ) (out) = wasm_f32x4_mul( reg0, reg1 ) + #define stbir__simdf_mult_mem( out, reg, ptr ) (out) = wasm_f32x4_mul( reg, wasm_v128_load( (void const*)(ptr) ) ) + #define stbir__simdf_mult1_mem( out, reg, ptr ) (out) = wasm_f32x4_mul( reg, wasm_v128_load32_splat( (void const*)(ptr) ) ) + #define stbir__simdf_add_mem( out, reg, ptr ) (out) = wasm_f32x4_add( reg, wasm_v128_load( (void const*)(ptr) ) ) + #define stbir__simdf_add1_mem( out, reg, ptr ) (out) = wasm_f32x4_add( reg, wasm_v128_load32_splat( (void const*)(ptr) ) ) + + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = wasm_f32x4_add( add, wasm_f32x4_mul( mul1, mul2 ) ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = wasm_f32x4_add( add, wasm_f32x4_mul( mul1, mul2 ) ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = wasm_f32x4_add( add, wasm_f32x4_mul( mul, wasm_v128_load( (void const*)(ptr) ) ) ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = wasm_f32x4_add( add, wasm_f32x4_mul( mul, wasm_v128_load32_splat( (void const*)(ptr) ) ) ) + + #define stbir__simdf_add1( out, reg0, reg1 ) (out) = wasm_f32x4_add( reg0, reg1 ) + #define stbir__simdf_mult1( out, reg0, reg1 ) (out) = wasm_f32x4_mul( reg0, reg1 ) + + #define stbir__simdf_and( out, reg0, reg1 ) (out) = wasm_v128_and( reg0, reg1 ) + #define stbir__simdf_or( out, reg0, reg1 ) (out) = wasm_v128_or( reg0, reg1 ) + + #define stbir__simdf_min( out, reg0, reg1 ) (out) = wasm_f32x4_min( reg0, reg1 ) + #define stbir__simdf_max( out, reg0, reg1 ) (out) = wasm_f32x4_max( reg0, reg1 ) + #define stbir__simdf_min1( out, reg0, reg1 ) (out) = wasm_f32x4_min( reg0, reg1 ) + #define stbir__simdf_max1( out, reg0, reg1 ) (out) = wasm_f32x4_max( reg0, reg1 ) + + #define stbir__simdf_0123ABCDto3ABx( out, reg0, reg1 ) (out) = wasm_i32x4_shuffle( reg0, reg1, 3, 4, 5, -1 ) + #define stbir__simdf_0123ABCDto23Ax( out, reg0, reg1 ) (out) = wasm_i32x4_shuffle( reg0, reg1, 2, 3, 4, -1 ) + + #define stbir__simdf_aaa1(out,alp,ones) (out) = wasm_i32x4_shuffle(alp, ones, 3, 3, 3, 4) + #define stbir__simdf_1aaa(out,alp,ones) (out) = wasm_i32x4_shuffle(alp, ones, 4, 0, 0, 0) + #define stbir__simdf_a1a1(out,alp,ones) (out) = wasm_i32x4_shuffle(alp, ones, 1, 4, 3, 4) + #define stbir__simdf_1a1a(out,alp,ones) (out) = wasm_i32x4_shuffle(alp, ones, 4, 0, 4, 2) + + #define stbir__simdf_swiz( reg, one, two, three, four ) wasm_i32x4_shuffle(reg, reg, one, two, three, four) + + #define stbir__simdi_and( out, reg0, reg1 ) (out) = wasm_v128_and( reg0, reg1 ) + #define stbir__simdi_or( out, reg0, reg1 ) (out) = wasm_v128_or( reg0, reg1 ) + #define stbir__simdi_16madd( out, reg0, reg1 ) (out) = wasm_i32x4_dot_i16x8( reg0, reg1 ) + + #define stbir__simdf_pack_to_8bytes(out,aa,bb) \ + { \ + v128_t af = wasm_f32x4_max( wasm_f32x4_min(aa, STBIR_max_uint8_as_float), wasm_f32x4_const_splat(0) ); \ + v128_t bf = wasm_f32x4_max( wasm_f32x4_min(bb, STBIR_max_uint8_as_float), wasm_f32x4_const_splat(0) ); \ + v128_t ai = wasm_i32x4_trunc_sat_f32x4( af ); \ + v128_t bi = wasm_i32x4_trunc_sat_f32x4( bf ); \ + v128_t out16 = wasm_i16x8_narrow_i32x4( ai, bi ); \ + out = wasm_u8x16_narrow_i16x8( out16, out16 ); \ + } + + #define stbir__simdf_pack_to_8words(out,aa,bb) \ + { \ + v128_t af = wasm_f32x4_max( wasm_f32x4_min(aa, STBIR_max_uint16_as_float), wasm_f32x4_const_splat(0)); \ + v128_t bf = wasm_f32x4_max( wasm_f32x4_min(bb, STBIR_max_uint16_as_float), wasm_f32x4_const_splat(0)); \ + v128_t ai = wasm_i32x4_trunc_sat_f32x4( af ); \ + v128_t bi = wasm_i32x4_trunc_sat_f32x4( bf ); \ + out = wasm_u16x8_narrow_i32x4( ai, bi ); \ + } + + #define stbir__interleave_pack_and_store_16_u8( ptr, r0, r1, r2, r3 ) \ + { \ + v128_t tmp0 = wasm_i16x8_narrow_i32x4(r0, r1); \ + v128_t tmp1 = wasm_i16x8_narrow_i32x4(r2, r3); \ + v128_t tmp = wasm_u8x16_narrow_i16x8(tmp0, tmp1); \ + tmp = wasm_i8x16_shuffle(tmp, tmp, 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15); \ + wasm_v128_store( (void*)(ptr), tmp); \ + } + + #define stbir__simdf_load4_transposed( o0, o1, o2, o3, ptr ) \ + { \ + v128_t t0 = wasm_v128_load( ptr ); \ + v128_t t1 = wasm_v128_load( ptr+4 ); \ + v128_t t2 = wasm_v128_load( ptr+8 ); \ + v128_t t3 = wasm_v128_load( ptr+12 ); \ + v128_t s0 = wasm_i32x4_shuffle(t0, t1, 0, 4, 2, 6); \ + v128_t s1 = wasm_i32x4_shuffle(t0, t1, 1, 5, 3, 7); \ + v128_t s2 = wasm_i32x4_shuffle(t2, t3, 0, 4, 2, 6); \ + v128_t s3 = wasm_i32x4_shuffle(t2, t3, 1, 5, 3, 7); \ + o0 = wasm_i32x4_shuffle(s0, s2, 0, 1, 4, 5); \ + o1 = wasm_i32x4_shuffle(s1, s3, 0, 1, 4, 5); \ + o2 = wasm_i32x4_shuffle(s0, s2, 2, 3, 6, 7); \ + o3 = wasm_i32x4_shuffle(s1, s3, 2, 3, 6, 7); \ + } + + #define stbir__simdi_32shr( out, reg, imm ) out = wasm_u32x4_shr( reg, imm ) + + typedef float stbir__f32x4 __attribute__((__vector_size__(16), __aligned__(16))); + #define STBIR__SIMDF_CONST(var, x) stbir__simdf var = (v128_t)(stbir__f32x4){ x, x, x, x } + #define STBIR__SIMDI_CONST(var, x) stbir__simdi var = { x, x, x, x } + #define STBIR__CONSTF(var) (var) + #define STBIR__CONSTI(var) (var) + + #ifdef STBIR_FLOORF + #undef STBIR_FLOORF + #endif + #define STBIR_FLOORF stbir_simd_floorf + static stbir__inline float stbir_simd_floorf(float x) + { + return wasm_f32x4_extract_lane( wasm_f32x4_floor( wasm_f32x4_splat(x) ), 0); + } + + #ifdef STBIR_CEILF + #undef STBIR_CEILF + #endif + #define STBIR_CEILF stbir_simd_ceilf + static stbir__inline float stbir_simd_ceilf(float x) + { + return wasm_f32x4_extract_lane( wasm_f32x4_ceil( wasm_f32x4_splat(x) ), 0); + } + + #define STBIR_SIMD + +#endif // SSE2/NEON/WASM + +#endif // NO SIMD + +#ifdef STBIR_SIMD8 + #define stbir__simdfX stbir__simdf8 + #define stbir__simdiX stbir__simdi8 + #define stbir__simdfX_load stbir__simdf8_load + #define stbir__simdiX_load stbir__simdi8_load + #define stbir__simdfX_mult stbir__simdf8_mult + #define stbir__simdfX_add_mem stbir__simdf8_add_mem + #define stbir__simdfX_madd_mem stbir__simdf8_madd_mem + #define stbir__simdfX_store stbir__simdf8_store + #define stbir__simdiX_store stbir__simdi8_store + #define stbir__simdf_frepX stbir__simdf8_frep8 + #define stbir__simdfX_madd stbir__simdf8_madd + #define stbir__simdfX_min stbir__simdf8_min + #define stbir__simdfX_max stbir__simdf8_max + #define stbir__simdfX_aaa1 stbir__simdf8_aaa1 + #define stbir__simdfX_1aaa stbir__simdf8_1aaa + #define stbir__simdfX_a1a1 stbir__simdf8_a1a1 + #define stbir__simdfX_1a1a stbir__simdf8_1a1a + #define stbir__simdfX_convert_float_to_i32 stbir__simdf8_convert_float_to_i32 + #define stbir__simdfX_pack_to_words stbir__simdf8_pack_to_16words + #define stbir__simdfX_zero stbir__simdf8_zero + #define STBIR_onesX STBIR_ones8 + #define STBIR_max_uint8_as_floatX STBIR_max_uint8_as_float8 + #define STBIR_max_uint16_as_floatX STBIR_max_uint16_as_float8 + #define STBIR_simd_point5X STBIR_simd_point58 + #define stbir__simdfX_float_count 8 + #define stbir__simdfX_0123to1230 stbir__simdf8_0123to12301230 + #define stbir__simdfX_0123to2103 stbir__simdf8_0123to21032103 + static const stbir__simdf8 STBIR_max_uint16_as_float_inverted8 = { stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted }; + static const stbir__simdf8 STBIR_max_uint8_as_float_inverted8 = { stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted }; + static const stbir__simdf8 STBIR_ones8 = { 1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 }; + static const stbir__simdf8 STBIR_simd_point58 = { 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5 }; + static const stbir__simdf8 STBIR_max_uint8_as_float8 = { stbir__max_uint8_as_float,stbir__max_uint8_as_float,stbir__max_uint8_as_float,stbir__max_uint8_as_float, stbir__max_uint8_as_float,stbir__max_uint8_as_float,stbir__max_uint8_as_float,stbir__max_uint8_as_float }; + static const stbir__simdf8 STBIR_max_uint16_as_float8 = { stbir__max_uint16_as_float,stbir__max_uint16_as_float,stbir__max_uint16_as_float,stbir__max_uint16_as_float, stbir__max_uint16_as_float,stbir__max_uint16_as_float,stbir__max_uint16_as_float,stbir__max_uint16_as_float }; +#else + #define stbir__simdfX stbir__simdf + #define stbir__simdiX stbir__simdi + #define stbir__simdfX_load stbir__simdf_load + #define stbir__simdiX_load stbir__simdi_load + #define stbir__simdfX_mult stbir__simdf_mult + #define stbir__simdfX_add_mem stbir__simdf_add_mem + #define stbir__simdfX_madd_mem stbir__simdf_madd_mem + #define stbir__simdfX_store stbir__simdf_store + #define stbir__simdiX_store stbir__simdi_store + #define stbir__simdf_frepX stbir__simdf_frep4 + #define stbir__simdfX_madd stbir__simdf_madd + #define stbir__simdfX_min stbir__simdf_min + #define stbir__simdfX_max stbir__simdf_max + #define stbir__simdfX_aaa1 stbir__simdf_aaa1 + #define stbir__simdfX_1aaa stbir__simdf_1aaa + #define stbir__simdfX_a1a1 stbir__simdf_a1a1 + #define stbir__simdfX_1a1a stbir__simdf_1a1a + #define stbir__simdfX_convert_float_to_i32 stbir__simdf_convert_float_to_i32 + #define stbir__simdfX_pack_to_words stbir__simdf_pack_to_8words + #define stbir__simdfX_zero stbir__simdf_zero + #define STBIR_onesX STBIR__CONSTF(STBIR_ones) + #define STBIR_simd_point5X STBIR__CONSTF(STBIR_simd_point5) + #define STBIR_max_uint8_as_floatX STBIR__CONSTF(STBIR_max_uint8_as_float) + #define STBIR_max_uint16_as_floatX STBIR__CONSTF(STBIR_max_uint16_as_float) + #define stbir__simdfX_float_count 4 + #define stbir__if_simdf8_cast_to_simdf4( val ) ( val ) + #define stbir__simdfX_0123to1230 stbir__simdf_0123to1230 + #define stbir__simdfX_0123to2103 stbir__simdf_0123to2103 +#endif + + +#if defined(STBIR_NEON) && !defined(_M_ARM) + + #if defined( _MSC_VER ) && !defined(__clang__) + typedef __int16 stbir__FP16; + #else + typedef float16_t stbir__FP16; + #endif + +#else // no NEON, or 32-bit ARM for MSVC + + typedef union stbir__FP16 + { + unsigned short u; + } stbir__FP16; + +#endif + +#if !defined(STBIR_NEON) && !defined(STBIR_FP16C) || defined(STBIR_NEON) && defined(_M_ARM) + + // Fabian's half float routines, see: https://gist.github.com/rygorous/2156668 + + static stbir__inline float stbir__half_to_float( stbir__FP16 h ) + { + static const stbir__FP32 magic = { (254 - 15) << 23 }; + static const stbir__FP32 was_infnan = { (127 + 16) << 23 }; + stbir__FP32 o; + + o.u = (h.u & 0x7fff) << 13; // exponent/mantissa bits + o.f *= magic.f; // exponent adjust + if (o.f >= was_infnan.f) // make sure Inf/NaN survive + o.u |= 255 << 23; + o.u |= (h.u & 0x8000) << 16; // sign bit + return o.f; + } + + static stbir__inline stbir__FP16 stbir__float_to_half(float val) + { + stbir__FP32 f32infty = { 255 << 23 }; + stbir__FP32 f16max = { (127 + 16) << 23 }; + stbir__FP32 denorm_magic = { ((127 - 15) + (23 - 10) + 1) << 23 }; + unsigned int sign_mask = 0x80000000u; + stbir__FP16 o = { 0 }; + stbir__FP32 f; + unsigned int sign; + + f.f = val; + sign = f.u & sign_mask; + f.u ^= sign; + + if (f.u >= f16max.u) // result is Inf or NaN (all exponent bits set) + o.u = (f.u > f32infty.u) ? 0x7e00 : 0x7c00; // NaN->qNaN and Inf->Inf + else // (De)normalized number or zero + { + if (f.u < (113 << 23)) // resulting FP16 is subnormal or zero + { + // use a magic value to align our 10 mantissa bits at the bottom of + // the float. as long as FP addition is round-to-nearest-even this + // just works. + f.f += denorm_magic.f; + // and one integer subtract of the bias later, we have our final float! + o.u = (unsigned short) ( f.u - denorm_magic.u ); + } + else + { + unsigned int mant_odd = (f.u >> 13) & 1; // resulting mantissa is odd + // update exponent, rounding bias part 1 + f.u = f.u + ((15u - 127) << 23) + 0xfff; + // rounding bias part 2 + f.u += mant_odd; + // take the bits! + o.u = (unsigned short) ( f.u >> 13 ); + } + } + + o.u |= sign >> 16; + return o; + } + +#endif + + +#if defined(STBIR_FP16C) + + #include + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) + { + _mm256_storeu_ps( (float*)output, _mm256_cvtph_ps( _mm_loadu_si128( (__m128i const* )input ) ) ); + } + + static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) + { + _mm_storeu_si128( (__m128i*)output, _mm256_cvtps_ph( _mm256_loadu_ps( input ), 0 ) ); + } + + static stbir__inline float stbir__half_to_float( stbir__FP16 h ) + { + return _mm_cvtss_f32( _mm_cvtph_ps( _mm_cvtsi32_si128( (int)h.u ) ) ); + } + + static stbir__inline stbir__FP16 stbir__float_to_half( float f ) + { + stbir__FP16 h; + h.u = (unsigned short) _mm_cvtsi128_si32( _mm_cvtps_ph( _mm_set_ss( f ), 0 ) ); + return h; + } + +#elif defined(STBIR_SSE2) + + // Fabian's half float routines, see: https://gist.github.com/rygorous/2156668 + stbir__inline static void stbir__half_to_float_SIMD(float * output, void const * input) + { + static const STBIR__SIMDI_CONST(mask_nosign, 0x7fff); + static const STBIR__SIMDI_CONST(smallest_normal, 0x0400); + static const STBIR__SIMDI_CONST(infinity, 0x7c00); + static const STBIR__SIMDI_CONST(expadjust_normal, (127 - 15) << 23); + static const STBIR__SIMDI_CONST(magic_denorm, 113 << 23); + + __m128i i = _mm_loadu_si128 ( (__m128i const*)(input) ); + __m128i h = _mm_unpacklo_epi16 ( i, _mm_setzero_si128() ); + __m128i mnosign = STBIR__CONSTI(mask_nosign); + __m128i eadjust = STBIR__CONSTI(expadjust_normal); + __m128i smallest = STBIR__CONSTI(smallest_normal); + __m128i infty = STBIR__CONSTI(infinity); + __m128i expmant = _mm_and_si128(mnosign, h); + __m128i justsign = _mm_xor_si128(h, expmant); + __m128i b_notinfnan = _mm_cmpgt_epi32(infty, expmant); + __m128i b_isdenorm = _mm_cmpgt_epi32(smallest, expmant); + __m128i shifted = _mm_slli_epi32(expmant, 13); + __m128i adj_infnan = _mm_andnot_si128(b_notinfnan, eadjust); + __m128i adjusted = _mm_add_epi32(eadjust, shifted); + __m128i den1 = _mm_add_epi32(shifted, STBIR__CONSTI(magic_denorm)); + __m128i adjusted2 = _mm_add_epi32(adjusted, adj_infnan); + __m128 den2 = _mm_sub_ps(_mm_castsi128_ps(den1), *(const __m128 *)&magic_denorm); + __m128 adjusted3 = _mm_and_ps(den2, _mm_castsi128_ps(b_isdenorm)); + __m128 adjusted4 = _mm_andnot_ps(_mm_castsi128_ps(b_isdenorm), _mm_castsi128_ps(adjusted2)); + __m128 adjusted5 = _mm_or_ps(adjusted3, adjusted4); + __m128i sign = _mm_slli_epi32(justsign, 16); + __m128 final = _mm_or_ps(adjusted5, _mm_castsi128_ps(sign)); + stbir__simdf_store( output + 0, final ); + + h = _mm_unpackhi_epi16 ( i, _mm_setzero_si128() ); + expmant = _mm_and_si128(mnosign, h); + justsign = _mm_xor_si128(h, expmant); + b_notinfnan = _mm_cmpgt_epi32(infty, expmant); + b_isdenorm = _mm_cmpgt_epi32(smallest, expmant); + shifted = _mm_slli_epi32(expmant, 13); + adj_infnan = _mm_andnot_si128(b_notinfnan, eadjust); + adjusted = _mm_add_epi32(eadjust, shifted); + den1 = _mm_add_epi32(shifted, STBIR__CONSTI(magic_denorm)); + adjusted2 = _mm_add_epi32(adjusted, adj_infnan); + den2 = _mm_sub_ps(_mm_castsi128_ps(den1), *(const __m128 *)&magic_denorm); + adjusted3 = _mm_and_ps(den2, _mm_castsi128_ps(b_isdenorm)); + adjusted4 = _mm_andnot_ps(_mm_castsi128_ps(b_isdenorm), _mm_castsi128_ps(adjusted2)); + adjusted5 = _mm_or_ps(adjusted3, adjusted4); + sign = _mm_slli_epi32(justsign, 16); + final = _mm_or_ps(adjusted5, _mm_castsi128_ps(sign)); + stbir__simdf_store( output + 4, final ); + + // ~38 SSE2 ops for 8 values + } + + // Fabian's round-to-nearest-even float to half + // ~48 SSE2 ops for 8 output + stbir__inline static void stbir__float_to_half_SIMD(void * output, float const * input) + { + static const STBIR__SIMDI_CONST(mask_sign, 0x80000000u); + static const STBIR__SIMDI_CONST(c_f16max, (127 + 16) << 23); // all FP32 values >=this round to +inf + static const STBIR__SIMDI_CONST(c_nanbit, 0x200); + static const STBIR__SIMDI_CONST(c_infty_as_fp16, 0x7c00); + static const STBIR__SIMDI_CONST(c_min_normal, (127 - 14) << 23); // smallest FP32 that yields a normalized FP16 + static const STBIR__SIMDI_CONST(c_subnorm_magic, ((127 - 15) + (23 - 10) + 1) << 23); + static const STBIR__SIMDI_CONST(c_normal_bias, 0xfff - ((127 - 15) << 23)); // adjust exponent and add mantissa rounding + + __m128 f = _mm_loadu_ps(input); + __m128 msign = _mm_castsi128_ps(STBIR__CONSTI(mask_sign)); + __m128 justsign = _mm_and_ps(msign, f); + __m128 absf = _mm_xor_ps(f, justsign); + __m128i absf_int = _mm_castps_si128(absf); // the cast is "free" (extra bypass latency, but no thruput hit) + __m128i f16max = STBIR__CONSTI(c_f16max); + __m128 b_isnan = _mm_cmpunord_ps(absf, absf); // is this a NaN? + __m128i b_isregular = _mm_cmpgt_epi32(f16max, absf_int); // (sub)normalized or special? + __m128i nanbit = _mm_and_si128(_mm_castps_si128(b_isnan), STBIR__CONSTI(c_nanbit)); + __m128i inf_or_nan = _mm_or_si128(nanbit, STBIR__CONSTI(c_infty_as_fp16)); // output for specials + + __m128i min_normal = STBIR__CONSTI(c_min_normal); + __m128i b_issub = _mm_cmpgt_epi32(min_normal, absf_int); + + // "result is subnormal" path + __m128 subnorm1 = _mm_add_ps(absf, _mm_castsi128_ps(STBIR__CONSTI(c_subnorm_magic))); // magic value to round output mantissa + __m128i subnorm2 = _mm_sub_epi32(_mm_castps_si128(subnorm1), STBIR__CONSTI(c_subnorm_magic)); // subtract out bias + + // "result is normal" path + __m128i mantoddbit = _mm_slli_epi32(absf_int, 31 - 13); // shift bit 13 (mantissa LSB) to sign + __m128i mantodd = _mm_srai_epi32(mantoddbit, 31); // -1 if FP16 mantissa odd, else 0 + + __m128i round1 = _mm_add_epi32(absf_int, STBIR__CONSTI(c_normal_bias)); + __m128i round2 = _mm_sub_epi32(round1, mantodd); // if mantissa LSB odd, bias towards rounding up (RTNE) + __m128i normal = _mm_srli_epi32(round2, 13); // rounded result + + // combine the two non-specials + __m128i nonspecial = _mm_or_si128(_mm_and_si128(subnorm2, b_issub), _mm_andnot_si128(b_issub, normal)); + + // merge in specials as well + __m128i joined = _mm_or_si128(_mm_and_si128(nonspecial, b_isregular), _mm_andnot_si128(b_isregular, inf_or_nan)); + + __m128i sign_shift = _mm_srai_epi32(_mm_castps_si128(justsign), 16); + __m128i final2, final= _mm_or_si128(joined, sign_shift); + + f = _mm_loadu_ps(input+4); + justsign = _mm_and_ps(msign, f); + absf = _mm_xor_ps(f, justsign); + absf_int = _mm_castps_si128(absf); // the cast is "free" (extra bypass latency, but no thruput hit) + b_isnan = _mm_cmpunord_ps(absf, absf); // is this a NaN? + b_isregular = _mm_cmpgt_epi32(f16max, absf_int); // (sub)normalized or special? + nanbit = _mm_and_si128(_mm_castps_si128(b_isnan), c_nanbit); + inf_or_nan = _mm_or_si128(nanbit, STBIR__CONSTI(c_infty_as_fp16)); // output for specials + + b_issub = _mm_cmpgt_epi32(min_normal, absf_int); + + // "result is subnormal" path + subnorm1 = _mm_add_ps(absf, _mm_castsi128_ps(STBIR__CONSTI(c_subnorm_magic))); // magic value to round output mantissa + subnorm2 = _mm_sub_epi32(_mm_castps_si128(subnorm1), STBIR__CONSTI(c_subnorm_magic)); // subtract out bias + + // "result is normal" path + mantoddbit = _mm_slli_epi32(absf_int, 31 - 13); // shift bit 13 (mantissa LSB) to sign + mantodd = _mm_srai_epi32(mantoddbit, 31); // -1 if FP16 mantissa odd, else 0 + + round1 = _mm_add_epi32(absf_int, STBIR__CONSTI(c_normal_bias)); + round2 = _mm_sub_epi32(round1, mantodd); // if mantissa LSB odd, bias towards rounding up (RTNE) + normal = _mm_srli_epi32(round2, 13); // rounded result + + // combine the two non-specials + nonspecial = _mm_or_si128(_mm_and_si128(subnorm2, b_issub), _mm_andnot_si128(b_issub, normal)); + + // merge in specials as well + joined = _mm_or_si128(_mm_and_si128(nonspecial, b_isregular), _mm_andnot_si128(b_isregular, inf_or_nan)); + + sign_shift = _mm_srai_epi32(_mm_castps_si128(justsign), 16); + final2 = _mm_or_si128(joined, sign_shift); + final = _mm_packs_epi32(final, final2); + stbir__simdi_store( output,final ); + } + +#elif defined(STBIR_WASM) || (defined(STBIR_NEON) && defined(_MSC_VER) && defined(_M_ARM)) // WASM or 32-bit ARM on MSVC/clang + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) + { + for (int i=0; i<8; i++) + { + output[i] = stbir__half_to_float(input[i]); + } + } + + static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) + { + for (int i=0; i<8; i++) + { + output[i] = stbir__float_to_half(input[i]); + } + } + +#elif defined(STBIR_NEON) && defined(_MSC_VER) && defined(_M_ARM64) && !defined(__clang__) // 64-bit ARM on MSVC (not clang) + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) + { + float16x4_t in0 = vld1_f16(input + 0); + float16x4_t in1 = vld1_f16(input + 4); + vst1q_f32(output + 0, vcvt_f32_f16(in0)); + vst1q_f32(output + 4, vcvt_f32_f16(in1)); + } + + static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) + { + float16x4_t out0 = vcvt_f16_f32(vld1q_f32(input + 0)); + float16x4_t out1 = vcvt_f16_f32(vld1q_f32(input + 4)); + vst1_f16(output+0, out0); + vst1_f16(output+4, out1); + } + + static stbir__inline float stbir__half_to_float( stbir__FP16 h ) + { + return vgetq_lane_f32(vcvt_f32_f16(vld1_dup_f16(&h)), 0); + } + + static stbir__inline stbir__FP16 stbir__float_to_half( float f ) + { + return vget_lane_f16(vcvt_f16_f32(vdupq_n_f32(f)), 0).n16_u16[0]; + } + +#elif defined(STBIR_NEON) // 64-bit ARM + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) + { + float16x8_t in = vld1q_f16(input); + vst1q_f32(output + 0, vcvt_f32_f16(vget_low_f16(in))); + vst1q_f32(output + 4, vcvt_f32_f16(vget_high_f16(in))); + } + + static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) + { + float16x4_t out0 = vcvt_f16_f32(vld1q_f32(input + 0)); + float16x4_t out1 = vcvt_f16_f32(vld1q_f32(input + 4)); + vst1q_f16(output, vcombine_f16(out0, out1)); + } + + static stbir__inline float stbir__half_to_float( stbir__FP16 h ) + { + return vgetq_lane_f32(vcvt_f32_f16(vdup_n_f16(h)), 0); + } + + static stbir__inline stbir__FP16 stbir__float_to_half( float f ) + { + return vget_lane_f16(vcvt_f16_f32(vdupq_n_f32(f)), 0); + } + +#endif + + +#ifdef STBIR_SIMD + +#define stbir__simdf_0123to3333( out, reg ) (out) = stbir__simdf_swiz( reg, 3,3,3,3 ) +#define stbir__simdf_0123to2222( out, reg ) (out) = stbir__simdf_swiz( reg, 2,2,2,2 ) +#define stbir__simdf_0123to1111( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,1,1 ) +#define stbir__simdf_0123to0000( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,0,0 ) +#define stbir__simdf_0123to0003( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,0,3 ) +#define stbir__simdf_0123to0001( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,0,1 ) +#define stbir__simdf_0123to1122( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,2,2 ) +#define stbir__simdf_0123to2333( out, reg ) (out) = stbir__simdf_swiz( reg, 2,3,3,3 ) +#define stbir__simdf_0123to0023( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,2,3 ) +#define stbir__simdf_0123to1230( out, reg ) (out) = stbir__simdf_swiz( reg, 1,2,3,0 ) +#define stbir__simdf_0123to2103( out, reg ) (out) = stbir__simdf_swiz( reg, 2,1,0,3 ) +#define stbir__simdf_0123to3210( out, reg ) (out) = stbir__simdf_swiz( reg, 3,2,1,0 ) +#define stbir__simdf_0123to2301( out, reg ) (out) = stbir__simdf_swiz( reg, 2,3,0,1 ) +#define stbir__simdf_0123to3012( out, reg ) (out) = stbir__simdf_swiz( reg, 3,0,1,2 ) +#define stbir__simdf_0123to0011( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,1,1 ) +#define stbir__simdf_0123to1100( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,0,0 ) +#define stbir__simdf_0123to2233( out, reg ) (out) = stbir__simdf_swiz( reg, 2,2,3,3 ) +#define stbir__simdf_0123to1133( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,3,3 ) +#define stbir__simdf_0123to0022( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,2,2 ) +#define stbir__simdf_0123to1032( out, reg ) (out) = stbir__simdf_swiz( reg, 1,0,3,2 ) + +typedef union stbir__simdi_u32 +{ + stbir_uint32 m128i_u32[4]; + int m128i_i32[4]; + stbir__simdi m128i_i128; +} stbir__simdi_u32; + +static const int STBIR_mask[9] = { 0,0,0,-1,-1,-1,0,0,0 }; + +static const STBIR__SIMDF_CONST(STBIR_max_uint8_as_float, stbir__max_uint8_as_float); +static const STBIR__SIMDF_CONST(STBIR_max_uint16_as_float, stbir__max_uint16_as_float); +static const STBIR__SIMDF_CONST(STBIR_max_uint8_as_float_inverted, stbir__max_uint8_as_float_inverted); +static const STBIR__SIMDF_CONST(STBIR_max_uint16_as_float_inverted, stbir__max_uint16_as_float_inverted); + +static const STBIR__SIMDF_CONST(STBIR_simd_point5, 0.5f); +static const STBIR__SIMDF_CONST(STBIR_ones, 1.0f); +static const STBIR__SIMDI_CONST(STBIR_almost_zero, (127 - 13) << 23); +static const STBIR__SIMDI_CONST(STBIR_almost_one, 0x3f7fffff); +static const STBIR__SIMDI_CONST(STBIR_mastissa_mask, 0xff); +static const STBIR__SIMDI_CONST(STBIR_topscale, 0x02000000); + +// Basically, in simd mode, we unroll the proper amount, and we don't want +// the non-simd remnant loops to be unroll because they only run a few times +// Adding this switch saves about 5K on clang which is Captain Unroll the 3rd. +#define STBIR_SIMD_STREAMOUT_PTR( star ) STBIR_STREAMOUT_PTR( star ) +#define STBIR_SIMD_NO_UNROLL(ptr) STBIR_NO_UNROLL(ptr) + +#ifdef STBIR_MEMCPY +#undef STBIR_MEMCPY +#define STBIR_MEMCPY stbir_simd_memcpy +#endif + +// override normal use of memcpy with much simpler copy (faster and smaller with our sized copies) +static void stbir_simd_memcpy( void * dest, void const * src, size_t bytes ) +{ + char STBIR_SIMD_STREAMOUT_PTR (*) d = (char*) dest; + char STBIR_SIMD_STREAMOUT_PTR( * ) d_end = ((char*) dest) + bytes; + ptrdiff_t ofs_to_src = (char*)src - (char*)dest; + + // check overlaps + STBIR_ASSERT( ( ( d >= ( (char*)src) + bytes ) ) || ( ( d + bytes ) <= (char*)src ) ); + + if ( bytes < (16*stbir__simdfX_float_count) ) + { + if ( bytes < 16 ) + { + if ( bytes ) + { + do + { + STBIR_SIMD_NO_UNROLL(d); + d[ 0 ] = d[ ofs_to_src ]; + ++d; + } while ( d < d_end ); + } + } + else + { + stbir__simdf x; + // do one unaligned to get us aligned for the stream out below + stbir__simdf_load( x, ( d + ofs_to_src ) ); + stbir__simdf_store( d, x ); + d = (char*)( ( ( (ptrdiff_t)d ) + 16 ) & ~15 ); + + for(;;) + { + STBIR_SIMD_NO_UNROLL(d); + + if ( d > ( d_end - 16 ) ) + { + if ( d == d_end ) + return; + d = d_end - 16; + } + + stbir__simdf_load( x, ( d + ofs_to_src ) ); + stbir__simdf_store( d, x ); + d += 16; + } + } + } + else + { + stbir__simdfX x0,x1,x2,x3; + + // do one unaligned to get us aligned for the stream out below + stbir__simdfX_load( x0, ( d + ofs_to_src ) + 0*stbir__simdfX_float_count ); + stbir__simdfX_load( x1, ( d + ofs_to_src ) + 4*stbir__simdfX_float_count ); + stbir__simdfX_load( x2, ( d + ofs_to_src ) + 8*stbir__simdfX_float_count ); + stbir__simdfX_load( x3, ( d + ofs_to_src ) + 12*stbir__simdfX_float_count ); + stbir__simdfX_store( d + 0*stbir__simdfX_float_count, x0 ); + stbir__simdfX_store( d + 4*stbir__simdfX_float_count, x1 ); + stbir__simdfX_store( d + 8*stbir__simdfX_float_count, x2 ); + stbir__simdfX_store( d + 12*stbir__simdfX_float_count, x3 ); + d = (char*)( ( ( (ptrdiff_t)d ) + (16*stbir__simdfX_float_count) ) & ~((16*stbir__simdfX_float_count)-1) ); + + for(;;) + { + STBIR_SIMD_NO_UNROLL(d); + + if ( d > ( d_end - (16*stbir__simdfX_float_count) ) ) + { + if ( d == d_end ) + return; + d = d_end - (16*stbir__simdfX_float_count); + } + + stbir__simdfX_load( x0, ( d + ofs_to_src ) + 0*stbir__simdfX_float_count ); + stbir__simdfX_load( x1, ( d + ofs_to_src ) + 4*stbir__simdfX_float_count ); + stbir__simdfX_load( x2, ( d + ofs_to_src ) + 8*stbir__simdfX_float_count ); + stbir__simdfX_load( x3, ( d + ofs_to_src ) + 12*stbir__simdfX_float_count ); + stbir__simdfX_store( d + 0*stbir__simdfX_float_count, x0 ); + stbir__simdfX_store( d + 4*stbir__simdfX_float_count, x1 ); + stbir__simdfX_store( d + 8*stbir__simdfX_float_count, x2 ); + stbir__simdfX_store( d + 12*stbir__simdfX_float_count, x3 ); + d += (16*stbir__simdfX_float_count); + } + } +} + +// memcpy that is specically intentionally overlapping (src is smaller then dest, so can be +// a normal forward copy, bytes is divisible by 4 and bytes is greater than or equal to +// the diff between dest and src) +static void stbir_overlapping_memcpy( void * dest, void const * src, size_t bytes ) +{ + char STBIR_SIMD_STREAMOUT_PTR (*) sd = (char*) src; + char STBIR_SIMD_STREAMOUT_PTR( * ) s_end = ((char*) src) + bytes; + ptrdiff_t ofs_to_dest = (char*)dest - (char*)src; + + if ( ofs_to_dest >= 16 ) // is the overlap more than 16 away? + { + char STBIR_SIMD_STREAMOUT_PTR( * ) s_end16 = ((char*) src) + (bytes&~15); + do + { + stbir__simdf x; + STBIR_SIMD_NO_UNROLL(sd); + stbir__simdf_load( x, sd ); + stbir__simdf_store( ( sd + ofs_to_dest ), x ); + sd += 16; + } while ( sd < s_end16 ); + + if ( sd == s_end ) + return; + } + + do + { + STBIR_SIMD_NO_UNROLL(sd); + *(int*)( sd + ofs_to_dest ) = *(int*) sd; + sd += 4; + } while ( sd < s_end ); +} + +#else // no SSE2 + +// when in scalar mode, we let unrolling happen, so this macro just does the __restrict +#define STBIR_SIMD_STREAMOUT_PTR( star ) STBIR_STREAMOUT_PTR( star ) +#define STBIR_SIMD_NO_UNROLL(ptr) + +#endif // SSE2 + + +#ifdef STBIR_PROFILE + +#if defined(_x86_64) || defined( __x86_64__ ) || defined( _M_X64 ) || defined(__x86_64) || defined(__SSE2__) || defined(STBIR_SSE) || defined( _M_IX86_FP ) || defined(__i386) || defined( __i386__ ) || defined( _M_IX86 ) || defined( _X86_ ) + +#ifdef _MSC_VER + + STBIRDEF stbir_uint64 __rdtsc(); + #define STBIR_PROFILE_FUNC() __rdtsc() + +#else // non msvc + + static stbir__inline stbir_uint64 STBIR_PROFILE_FUNC() + { + stbir_uint32 lo, hi; + asm volatile ("rdtsc" : "=a" (lo), "=d" (hi) ); + return ( ( (stbir_uint64) hi ) << 32 ) | ( (stbir_uint64) lo ); + } + +#endif // msvc + +#elif defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || defined(__ARM_NEON__) + +#if defined( _MSC_VER ) && !defined(__clang__) + + #define STBIR_PROFILE_FUNC() _ReadStatusReg(ARM64_CNTVCT) + +#else + + static stbir__inline stbir_uint64 STBIR_PROFILE_FUNC() + { + stbir_uint64 tsc; + asm volatile("mrs %0, cntvct_el0" : "=r" (tsc)); + return tsc; + } + +#endif + +#else // x64, arm + +#error Unknown platform for profiling. + +#endif //x64 and + + +#define STBIR_ONLY_PROFILE_GET_SPLIT_INFO ,stbir__per_split_info * split_info +#define STBIR_ONLY_PROFILE_SET_SPLIT_INFO ,split_info + +#define STBIR_ONLY_PROFILE_BUILD_GET_INFO ,stbir__info * profile_info +#define STBIR_ONLY_PROFILE_BUILD_SET_INFO ,profile_info + +// super light-weight micro profiler +#define STBIR_PROFILE_START_ll( info, wh ) { stbir_uint64 wh##thiszonetime = STBIR_PROFILE_FUNC(); stbir_uint64 * wh##save_parent_excluded_ptr = info->current_zone_excluded_ptr; stbir_uint64 wh##current_zone_excluded = 0; info->current_zone_excluded_ptr = &wh##current_zone_excluded; +#define STBIR_PROFILE_END_ll( info, wh ) wh##thiszonetime = STBIR_PROFILE_FUNC() - wh##thiszonetime; info->profile.named.wh += wh##thiszonetime - wh##current_zone_excluded; *wh##save_parent_excluded_ptr += wh##thiszonetime; info->current_zone_excluded_ptr = wh##save_parent_excluded_ptr; } +#define STBIR_PROFILE_FIRST_START_ll( info, wh ) { int i; info->current_zone_excluded_ptr = &info->profile.named.total; for(i=0;iprofile.array);i++) info->profile.array[i]=0; } STBIR_PROFILE_START_ll( info, wh ); +#define STBIR_PROFILE_CLEAR_EXTRAS_ll( info, num ) { int extra; for(extra=1;extra<(num);extra++) { int i; for(i=0;iprofile.array);i++) (info)[extra].profile.array[i]=0; } } + +// for thread data +#define STBIR_PROFILE_START( wh ) STBIR_PROFILE_START_ll( split_info, wh ) +#define STBIR_PROFILE_END( wh ) STBIR_PROFILE_END_ll( split_info, wh ) +#define STBIR_PROFILE_FIRST_START( wh ) STBIR_PROFILE_FIRST_START_ll( split_info, wh ) +#define STBIR_PROFILE_CLEAR_EXTRAS() STBIR_PROFILE_CLEAR_EXTRAS_ll( split_info, split_count ) + +// for build data +#define STBIR_PROFILE_BUILD_START( wh ) STBIR_PROFILE_START_ll( profile_info, wh ) +#define STBIR_PROFILE_BUILD_END( wh ) STBIR_PROFILE_END_ll( profile_info, wh ) +#define STBIR_PROFILE_BUILD_FIRST_START( wh ) STBIR_PROFILE_FIRST_START_ll( profile_info, wh ) +#define STBIR_PROFILE_BUILD_CLEAR( info ) { int i; for(i=0;iprofile.array);i++) info->profile.array[i]=0; } + +#else // no profile + +#define STBIR_ONLY_PROFILE_GET_SPLIT_INFO +#define STBIR_ONLY_PROFILE_SET_SPLIT_INFO + +#define STBIR_ONLY_PROFILE_BUILD_GET_INFO +#define STBIR_ONLY_PROFILE_BUILD_SET_INFO + +#define STBIR_PROFILE_START( wh ) +#define STBIR_PROFILE_END( wh ) +#define STBIR_PROFILE_FIRST_START( wh ) +#define STBIR_PROFILE_CLEAR_EXTRAS( ) + +#define STBIR_PROFILE_BUILD_START( wh ) +#define STBIR_PROFILE_BUILD_END( wh ) +#define STBIR_PROFILE_BUILD_FIRST_START( wh ) +#define STBIR_PROFILE_BUILD_CLEAR( info ) + +#endif // stbir_profile + +#ifndef STBIR_CEILF +#include +#if _MSC_VER <= 1200 // support VC6 for Sean +#define STBIR_CEILF(x) ((float)ceil((float)(x))) +#define STBIR_FLOORF(x) ((float)floor((float)(x))) +#else +#define STBIR_CEILF(x) ceilf(x) +#define STBIR_FLOORF(x) floorf(x) +#endif +#endif + +#ifndef STBIR_MEMCPY +// For memcpy +#include +#define STBIR_MEMCPY( dest, src, len ) memcpy( dest, src, len ) +#endif + +#ifndef STBIR_SIMD + +// memcpy that is specically intentionally overlapping (src is smaller then dest, so can be +// a normal forward copy, bytes is divisible by 4 and bytes is greater than or equal to +// the diff between dest and src) +static void stbir_overlapping_memcpy( void * dest, void const * src, size_t bytes ) +{ + char STBIR_SIMD_STREAMOUT_PTR (*) sd = (char*) src; + char STBIR_SIMD_STREAMOUT_PTR( * ) s_end = ((char*) src) + bytes; + ptrdiff_t ofs_to_dest = (char*)dest - (char*)src; + + if ( ofs_to_dest >= 8 ) // is the overlap more than 8 away? + { + char STBIR_SIMD_STREAMOUT_PTR( * ) s_end8 = ((char*) src) + (bytes&~7); + do + { + STBIR_NO_UNROLL(sd); + *(stbir_uint64*)( sd + ofs_to_dest ) = *(stbir_uint64*) sd; + sd += 8; + } while ( sd < s_end8 ); + + if ( sd == s_end ) + return; + } + + do + { + STBIR_NO_UNROLL(sd); + *(int*)( sd + ofs_to_dest ) = *(int*) sd; + sd += 4; + } while ( sd < s_end ); +} + +#endif + +static float stbir__filter_trapezoid(float x, float scale, void * user_data) +{ + float halfscale = scale / 2; + float t = 0.5f + halfscale; + STBIR_ASSERT(scale <= 1); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x >= t) + return 0.0f; + else + { + float r = 0.5f - halfscale; + if (x <= r) + return 1.0f; + else + return (t - x) / scale; + } +} + +static float stbir__support_trapezoid(float scale, void * user_data) +{ + STBIR__UNUSED(user_data); + return 0.5f + scale / 2.0f; +} + +static float stbir__filter_triangle(float x, float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x <= 1.0f) + return 1.0f - x; + else + return 0.0f; +} + +static float stbir__filter_point(float x, float s, void * user_data) +{ + STBIR__UNUSED(x); + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + return 1.0f; +} + +static float stbir__filter_cubic(float x, float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x < 1.0f) + return (4.0f + x*x*(3.0f*x - 6.0f))/6.0f; + else if (x < 2.0f) + return (8.0f + x*(-12.0f + x*(6.0f - x)))/6.0f; + + return (0.0f); +} + +static float stbir__filter_catmullrom(float x, float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x < 1.0f) + return 1.0f - x*x*(2.5f - 1.5f*x); + else if (x < 2.0f) + return 2.0f - x*(4.0f + x*(0.5f*x - 2.5f)); + + return (0.0f); +} + +static float stbir__filter_mitchell(float x, float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x < 1.0f) + return (16.0f + x*x*(21.0f * x - 36.0f))/18.0f; + else if (x < 2.0f) + return (32.0f + x*(-60.0f + x*(36.0f - 7.0f*x)))/18.0f; + + return (0.0f); +} + +static float stbir__support_zero(float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + return 0; +} + +static float stbir__support_zeropoint5(float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + return 0.5f; +} + +static float stbir__support_one(float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + return 1; +} + +static float stbir__support_two(float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + return 2; +} + +// This is the maximum number of input samples that can affect an output sample +// with the given filter from the output pixel's perspective +static int stbir__get_filter_pixel_width(stbir__support_callback * support, float scale, void * user_data) +{ + STBIR_ASSERT(support != 0); + + if ( scale >= ( 1.0f-stbir__small_float ) ) // upscale + return (int)STBIR_CEILF(support(1.0f/scale,user_data) * 2.0f); + else + return (int)STBIR_CEILF(support(scale,user_data) * 2.0f / scale); +} + +// this is how many coefficents per run of the filter (which is different +// from the filter_pixel_width depending on if we are scattering or gathering) +static int stbir__get_coefficient_width(stbir__sampler * samp, int is_gather, void * user_data) +{ + float scale = samp->scale_info.scale; + stbir__support_callback * support = samp->filter_support; + + switch( is_gather ) + { + case 1: + return (int)STBIR_CEILF(support(1.0f / scale, user_data) * 2.0f); + case 2: + return (int)STBIR_CEILF(support(scale, user_data) * 2.0f / scale); + case 0: + return (int)STBIR_CEILF(support(scale, user_data) * 2.0f); + default: + STBIR_ASSERT( (is_gather >= 0 ) && (is_gather <= 2 ) ); + return 0; + } +} + +static int stbir__get_contributors(stbir__sampler * samp, int is_gather) +{ + if (is_gather) + return samp->scale_info.output_sub_size; + else + return (samp->scale_info.input_full_size + samp->filter_pixel_margin * 2); +} + +static int stbir__edge_zero_full( int n, int max ) +{ + STBIR__UNUSED(n); + STBIR__UNUSED(max); + return 0; // NOTREACHED +} + +static int stbir__edge_clamp_full( int n, int max ) +{ + if (n < 0) + return 0; + + if (n >= max) + return max - 1; + + return n; // NOTREACHED +} + +static int stbir__edge_reflect_full( int n, int max ) +{ + if (n < 0) + { + if (n > -max) + return -n; + else + return max - 1; + } + + if (n >= max) + { + int max2 = max * 2; + if (n >= max2) + return 0; + else + return max2 - n - 1; + } + + return n; // NOTREACHED +} + +static int stbir__edge_wrap_full( int n, int max ) +{ + if (n >= 0) + return (n % max); + else + { + int m = (-n) % max; + + if (m != 0) + m = max - m; + + return (m); + } +} + +typedef int stbir__edge_wrap_func( int n, int max ); +static stbir__edge_wrap_func * stbir__edge_wrap_slow[] = +{ + stbir__edge_clamp_full, // STBIR_EDGE_CLAMP + stbir__edge_reflect_full, // STBIR_EDGE_REFLECT + stbir__edge_wrap_full, // STBIR_EDGE_WRAP + stbir__edge_zero_full, // STBIR_EDGE_ZERO +}; + +stbir__inline static int stbir__edge_wrap(stbir_edge edge, int n, int max) +{ + // avoid per-pixel switch + if (n >= 0 && n < max) + return n; + return stbir__edge_wrap_slow[edge]( n, max ); +} + +#define STBIR__MERGE_RUNS_PIXEL_THRESHOLD 16 + +// get information on the extents of a sampler +static void stbir__get_extents( stbir__sampler * samp, stbir__extents * scanline_extents ) +{ + int j, stop; + int left_margin, right_margin; + int min_n = 0x7fffffff, max_n = -0x7fffffff; + int min_left = 0x7fffffff, max_left = -0x7fffffff; + int min_right = 0x7fffffff, max_right = -0x7fffffff; + stbir_edge edge = samp->edge; + stbir__contributors* contributors = samp->contributors; + int output_sub_size = samp->scale_info.output_sub_size; + int input_full_size = samp->scale_info.input_full_size; + int filter_pixel_margin = samp->filter_pixel_margin; + + STBIR_ASSERT( samp->is_gather ); + + stop = output_sub_size; + for (j = 0; j < stop; j++ ) + { + STBIR_ASSERT( contributors[j].n1 >= contributors[j].n0 ); + if ( contributors[j].n0 < min_n ) + { + min_n = contributors[j].n0; + stop = j + filter_pixel_margin; // if we find a new min, only scan another filter width + if ( stop > output_sub_size ) stop = output_sub_size; + } + } + + stop = 0; + for (j = output_sub_size - 1; j >= stop; j-- ) + { + STBIR_ASSERT( contributors[j].n1 >= contributors[j].n0 ); + if ( contributors[j].n1 > max_n ) + { + max_n = contributors[j].n1; + stop = j - filter_pixel_margin; // if we find a new max, only scan another filter width + if (stop<0) stop = 0; + } + } + + STBIR_ASSERT( scanline_extents->conservative.n0 <= min_n ); + STBIR_ASSERT( scanline_extents->conservative.n1 >= max_n ); + + // now calculate how much into the margins we really read + left_margin = 0; + if ( min_n < 0 ) + { + left_margin = -min_n; + min_n = 0; + } + + right_margin = 0; + if ( max_n >= input_full_size ) + { + right_margin = max_n - input_full_size + 1; + max_n = input_full_size - 1; + } + + // index 1 is margin pixel extents (how many pixels we hang over the edge) + scanline_extents->edge_sizes[0] = left_margin; + scanline_extents->edge_sizes[1] = right_margin; + + // index 2 is pixels read from the input + scanline_extents->spans[0].n0 = min_n; + scanline_extents->spans[0].n1 = max_n; + scanline_extents->spans[0].pixel_offset_for_input = min_n; + + // default to no other input range + scanline_extents->spans[1].n0 = 0; + scanline_extents->spans[1].n1 = -1; + scanline_extents->spans[1].pixel_offset_for_input = 0; + + // don't have to do edge calc for zero clamp + if ( edge == STBIR_EDGE_ZERO ) + return; + + // convert margin pixels to the pixels within the input (min and max) + for( j = -left_margin ; j < 0 ; j++ ) + { + int p = stbir__edge_wrap( edge, j, input_full_size ); + if ( p < min_left ) + min_left = p; + if ( p > max_left ) + max_left = p; + } + + for( j = input_full_size ; j < (input_full_size + right_margin) ; j++ ) + { + int p = stbir__edge_wrap( edge, j, input_full_size ); + if ( p < min_right ) + min_right = p; + if ( p > max_right ) + max_right = p; + } + + // merge the left margin pixel region if it connects within 4 pixels of main pixel region + if ( min_left != 0x7fffffff ) + { + if ( ( ( min_left <= min_n ) && ( ( max_left + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= min_n ) ) || + ( ( min_n <= min_left ) && ( ( max_n + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= max_left ) ) ) + { + scanline_extents->spans[0].n0 = min_n = stbir__min( min_n, min_left ); + scanline_extents->spans[0].n1 = max_n = stbir__max( max_n, max_left ); + scanline_extents->spans[0].pixel_offset_for_input = min_n; + left_margin = 0; + } + } + + // merge the right margin pixel region if it connects within 4 pixels of main pixel region + if ( min_right != 0x7fffffff ) + { + if ( ( ( min_right <= min_n ) && ( ( max_right + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= min_n ) ) || + ( ( min_n <= min_right ) && ( ( max_n + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= max_right ) ) ) + { + scanline_extents->spans[0].n0 = min_n = stbir__min( min_n, min_right ); + scanline_extents->spans[0].n1 = max_n = stbir__max( max_n, max_right ); + scanline_extents->spans[0].pixel_offset_for_input = min_n; + right_margin = 0; + } + } + + STBIR_ASSERT( scanline_extents->conservative.n0 <= min_n ); + STBIR_ASSERT( scanline_extents->conservative.n1 >= max_n ); + + // you get two ranges when you have the WRAP edge mode and you are doing just the a piece of the resize + // so you need to get a second run of pixels from the opposite side of the scanline (which you + // wouldn't need except for WRAP) + + + // if we can't merge the min_left range, add it as a second range + if ( ( left_margin ) && ( min_left != 0x7fffffff ) ) + { + stbir__span * newspan = scanline_extents->spans + 1; + STBIR_ASSERT( right_margin == 0 ); + if ( min_left < scanline_extents->spans[0].n0 ) + { + scanline_extents->spans[1].pixel_offset_for_input = scanline_extents->spans[0].n0; + scanline_extents->spans[1].n0 = scanline_extents->spans[0].n0; + scanline_extents->spans[1].n1 = scanline_extents->spans[0].n1; + --newspan; + } + newspan->pixel_offset_for_input = min_left; + newspan->n0 = -left_margin; + newspan->n1 = ( max_left - min_left ) - left_margin; + scanline_extents->edge_sizes[0] = 0; // don't need to copy the left margin, since we are directly decoding into the margin + return; + } + + // if we can't merge the min_left range, add it as a second range + if ( ( right_margin ) && ( min_right != 0x7fffffff ) ) + { + stbir__span * newspan = scanline_extents->spans + 1; + if ( min_right < scanline_extents->spans[0].n0 ) + { + scanline_extents->spans[1].pixel_offset_for_input = scanline_extents->spans[0].n0; + scanline_extents->spans[1].n0 = scanline_extents->spans[0].n0; + scanline_extents->spans[1].n1 = scanline_extents->spans[0].n1; + --newspan; + } + newspan->pixel_offset_for_input = min_right; + newspan->n0 = scanline_extents->spans[1].n1 + 1; + newspan->n1 = scanline_extents->spans[1].n1 + 1 + ( max_right - min_right ); + scanline_extents->edge_sizes[1] = 0; // don't need to copy the right margin, since we are directly decoding into the margin + return; + } +} + +static void stbir__calculate_in_pixel_range( int * first_pixel, int * last_pixel, float out_pixel_center, float out_filter_radius, float inv_scale, float out_shift, int input_size, stbir_edge edge ) +{ + int first, last; + float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius; + float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius; + + float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) * inv_scale; + float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) * inv_scale; + + first = (int)(STBIR_FLOORF(in_pixel_influence_lowerbound + 0.5f)); + last = (int)(STBIR_FLOORF(in_pixel_influence_upperbound - 0.5f)); + + if ( edge == STBIR_EDGE_WRAP ) + { + if ( first < -input_size ) + first = -input_size; + if ( last >= (input_size*2)) + last = (input_size*2) - 1; + } + + *first_pixel = first; + *last_pixel = last; +} + +static void stbir__calculate_coefficients_for_gather_upsample( float out_filter_radius, stbir__kernel_callback * kernel, stbir__scale_info * scale_info, int num_contributors, stbir__contributors* contributors, float* coefficient_group, int coefficient_width, stbir_edge edge, void * user_data ) +{ + int n, end; + float inv_scale = scale_info->inv_scale; + float out_shift = scale_info->pixel_shift; + int input_size = scale_info->input_full_size; + int numerator = scale_info->scale_numerator; + int polyphase = ( ( scale_info->scale_is_rational ) && ( numerator < num_contributors ) ); + + // Looping through out pixels + end = num_contributors; if ( polyphase ) end = numerator; + for (n = 0; n < end; n++) + { + int i; + int last_non_zero; + float out_pixel_center = (float)n + 0.5f; + float in_center_of_out = (out_pixel_center + out_shift) * inv_scale; + + int in_first_pixel, in_last_pixel; + + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, out_pixel_center, out_filter_radius, inv_scale, out_shift, input_size, edge ); + + last_non_zero = -1; + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + { + float in_pixel_center = (float)(i + in_first_pixel) + 0.5f; + float coeff = kernel(in_center_of_out - in_pixel_center, inv_scale, user_data); + + // kill denormals + if ( ( ( coeff < stbir__small_float ) && ( coeff > -stbir__small_float ) ) ) + { + if ( i == 0 ) // if we're at the front, just eat zero contributors + { + STBIR_ASSERT ( ( in_last_pixel - in_first_pixel ) != 0 ); // there should be at least one contrib + ++in_first_pixel; + i--; + continue; + } + coeff = 0; // make sure is fully zero (should keep denormals away) + } + else + last_non_zero = i; + + coefficient_group[i] = coeff; + } + + in_last_pixel = last_non_zero+in_first_pixel; // kills trailing zeros + contributors->n0 = in_first_pixel; + contributors->n1 = in_last_pixel; + + STBIR_ASSERT(contributors->n1 >= contributors->n0); + + ++contributors; + coefficient_group += coefficient_width; + } +} + +static void stbir__insert_coeff( stbir__contributors * contribs, float * coeffs, int new_pixel, float new_coeff ) +{ + if ( new_pixel <= contribs->n1 ) // before the end + { + if ( new_pixel < contribs->n0 ) // before the front? + { + int j, o = contribs->n0 - new_pixel; + for ( j = contribs->n1 - contribs->n0 ; j <= 0 ; j-- ) + coeffs[ j + o ] = coeffs[ j ]; + for ( j = 1 ; j < o ; j-- ) + coeffs[ j ] = coeffs[ 0 ]; + coeffs[ 0 ] = new_coeff; + contribs->n0 = new_pixel; + } + else + { + coeffs[ new_pixel - contribs->n0 ] += new_coeff; + } + } + else + { + int j, e = new_pixel - contribs->n0; + for( j = ( contribs->n1 - contribs->n0 ) + 1 ; j < e ; j++ ) // clear in-betweens coeffs if there are any + coeffs[j] = 0; + + coeffs[ e ] = new_coeff; + contribs->n1 = new_pixel; + } +} + +static void stbir__calculate_out_pixel_range( int * first_pixel, int * last_pixel, float in_pixel_center, float in_pixels_radius, float scale, float out_shift, int out_size ) +{ + float in_pixel_influence_lowerbound = in_pixel_center - in_pixels_radius; + float in_pixel_influence_upperbound = in_pixel_center + in_pixels_radius; + float out_pixel_influence_lowerbound = in_pixel_influence_lowerbound * scale - out_shift; + float out_pixel_influence_upperbound = in_pixel_influence_upperbound * scale - out_shift; + int out_first_pixel = (int)(STBIR_FLOORF(out_pixel_influence_lowerbound + 0.5f)); + int out_last_pixel = (int)(STBIR_FLOORF(out_pixel_influence_upperbound - 0.5f)); + + if ( out_first_pixel < 0 ) + out_first_pixel = 0; + if ( out_last_pixel >= out_size ) + out_last_pixel = out_size - 1; + *first_pixel = out_first_pixel; + *last_pixel = out_last_pixel; +} + +static void stbir__calculate_coefficients_for_gather_downsample( int start, int end, float in_pixels_radius, stbir__kernel_callback * kernel, stbir__scale_info * scale_info, int coefficient_width, int num_contributors, stbir__contributors * contributors, float * coefficient_group, void * user_data ) +{ + int in_pixel; + int i; + int first_out_inited = -1; + float scale = scale_info->scale; + float out_shift = scale_info->pixel_shift; + int out_size = scale_info->output_sub_size; + int numerator = scale_info->scale_numerator; + int polyphase = ( ( scale_info->scale_is_rational ) && ( numerator < out_size ) ); + + STBIR__UNUSED(num_contributors); + + // Loop through the input pixels + for (in_pixel = start; in_pixel < end; in_pixel++) + { + float in_pixel_center = (float)in_pixel + 0.5f; + float out_center_of_in = in_pixel_center * scale - out_shift; + int out_first_pixel, out_last_pixel; + + stbir__calculate_out_pixel_range( &out_first_pixel, &out_last_pixel, in_pixel_center, in_pixels_radius, scale, out_shift, out_size ); + + if ( out_first_pixel > out_last_pixel ) + continue; + + // clamp or exit if we are using polyphase filtering, and the limit is up + if ( polyphase ) + { + // when polyphase, you only have to do coeffs up to the numerator count + if ( out_first_pixel == numerator ) + break; + + // don't do any extra work, clamp last pixel at numerator too + if ( out_last_pixel >= numerator ) + out_last_pixel = numerator - 1; + } + + for (i = 0; i <= out_last_pixel - out_first_pixel; i++) + { + float out_pixel_center = (float)(i + out_first_pixel) + 0.5f; + float x = out_pixel_center - out_center_of_in; + float coeff = kernel(x, scale, user_data) * scale; + + // kill the coeff if it's too small (avoid denormals) + if ( ( ( coeff < stbir__small_float ) && ( coeff > -stbir__small_float ) ) ) + coeff = 0.0f; + + { + int out = i + out_first_pixel; + float * coeffs = coefficient_group + out * coefficient_width; + stbir__contributors * contribs = contributors + out; + + // is this the first time this output pixel has been seen? Init it. + if ( out > first_out_inited ) + { + STBIR_ASSERT( out == ( first_out_inited + 1 ) ); // ensure we have only advanced one at time + first_out_inited = out; + contribs->n0 = in_pixel; + contribs->n1 = in_pixel; + coeffs[0] = coeff; + } + else + { + // insert on end (always in order) + if ( coeffs[0] == 0.0f ) // if the first coefficent is zero, then zap it for this coeffs + { + STBIR_ASSERT( ( in_pixel - contribs->n0 ) == 1 ); // ensure that when we zap, we're at the 2nd pos + contribs->n0 = in_pixel; + } + contribs->n1 = in_pixel; + STBIR_ASSERT( ( in_pixel - contribs->n0 ) < coefficient_width ); + coeffs[in_pixel - contribs->n0] = coeff; + } + } + } + } +} + +#ifdef STBIR_RENORMALIZE_IN_FLOAT +#define STBIR_RENORM_TYPE float +#else +#define STBIR_RENORM_TYPE double +#endif + +static void stbir__cleanup_gathered_coefficients( stbir_edge edge, stbir__filter_extent_info* filter_info, stbir__scale_info * scale_info, int num_contributors, stbir__contributors* contributors, float * coefficient_group, int coefficient_width ) +{ + int input_size = scale_info->input_full_size; + int input_last_n1 = input_size - 1; + int n, end; + int lowest = 0x7fffffff; + int highest = -0x7fffffff; + int widest = -1; + int numerator = scale_info->scale_numerator; + int denominator = scale_info->scale_denominator; + int polyphase = ( ( scale_info->scale_is_rational ) && ( numerator < num_contributors ) ); + float * coeffs; + stbir__contributors * contribs; + + // weight all the coeffs for each sample + coeffs = coefficient_group; + contribs = contributors; + end = num_contributors; if ( polyphase ) end = numerator; + for (n = 0; n < end; n++) + { + int i; + STBIR_RENORM_TYPE filter_scale, total_filter = 0; + int e; + + // add all contribs + e = contribs->n1 - contribs->n0; + for( i = 0 ; i <= e ; i++ ) + { + total_filter += (STBIR_RENORM_TYPE) coeffs[i]; + STBIR_ASSERT( ( coeffs[i] >= -2.0f ) && ( coeffs[i] <= 2.0f ) ); // check for wonky weights + } + + // rescale + if ( ( total_filter < stbir__small_float ) && ( total_filter > -stbir__small_float ) ) + { + // all coeffs are extremely small, just zero it + contribs->n1 = contribs->n0; + coeffs[0] = 0.0f; + } + else + { + // if the total isn't 1.0, rescale everything + if ( ( total_filter < (1.0f-stbir__small_float) ) || ( total_filter > (1.0f+stbir__small_float) ) ) + { + filter_scale = ((STBIR_RENORM_TYPE)1.0) / total_filter; + + // scale them all + for (i = 0; i <= e; i++) + coeffs[i] = (float) ( coeffs[i] * filter_scale ); + } + } + ++contribs; + coeffs += coefficient_width; + } + + // if we have a rational for the scale, we can exploit the polyphaseness to not calculate + // most of the coefficients, so we copy them here + if ( polyphase ) + { + stbir__contributors * prev_contribs = contributors; + stbir__contributors * cur_contribs = contributors + numerator; + + for( n = numerator ; n < num_contributors ; n++ ) + { + cur_contribs->n0 = prev_contribs->n0 + denominator; + cur_contribs->n1 = prev_contribs->n1 + denominator; + ++cur_contribs; + ++prev_contribs; + } + stbir_overlapping_memcpy( coefficient_group + numerator * coefficient_width, coefficient_group, ( num_contributors - numerator ) * coefficient_width * sizeof( coeffs[ 0 ] ) ); + } + + coeffs = coefficient_group; + contribs = contributors; + for (n = 0; n < num_contributors; n++) + { + int i; + + // in zero edge mode, just remove out of bounds contribs completely (since their weights are accounted for now) + if ( edge == STBIR_EDGE_ZERO ) + { + // shrink the right side if necessary + if ( contribs->n1 > input_last_n1 ) + contribs->n1 = input_last_n1; + + // shrink the left side + if ( contribs->n0 < 0 ) + { + int j, left, skips = 0; + + skips = -contribs->n0; + contribs->n0 = 0; + + // now move down the weights + left = contribs->n1 - contribs->n0 + 1; + if ( left > 0 ) + { + for( j = 0 ; j < left ; j++ ) + coeffs[ j ] = coeffs[ j + skips ]; + } + } + } + else if ( ( edge == STBIR_EDGE_CLAMP ) || ( edge == STBIR_EDGE_REFLECT ) ) + { + // for clamp and reflect, calculate the true inbounds position (based on edge type) and just add that to the existing weight + + // right hand side first + if ( contribs->n1 > input_last_n1 ) + { + int start = contribs->n0; + int endi = contribs->n1; + contribs->n1 = input_last_n1; + for( i = input_size; i <= endi; i++ ) + stbir__insert_coeff( contribs, coeffs, stbir__edge_wrap_slow[edge]( i, input_size ), coeffs[i-start] ); + } + + // now check left hand edge + if ( contribs->n0 < 0 ) + { + int save_n0; + float save_n0_coeff; + float * c = coeffs - ( contribs->n0 + 1 ); + + // reinsert the coeffs with it reflected or clamped (insert accumulates, if the coeffs exist) + for( i = -1 ; i > contribs->n0 ; i-- ) + stbir__insert_coeff( contribs, coeffs, stbir__edge_wrap_slow[edge]( i, input_size ), *c-- ); + save_n0 = contribs->n0; + save_n0_coeff = c[0]; // save it, since we didn't do the final one (i==n0), because there might be too many coeffs to hold (before we resize)! + + // now slide all the coeffs down (since we have accumulated them in the positive contribs) and reset the first contrib + contribs->n0 = 0; + for(i = 0 ; i <= contribs->n1 ; i++ ) + coeffs[i] = coeffs[i-save_n0]; + + // now that we have shrunk down the contribs, we insert the first one safely + stbir__insert_coeff( contribs, coeffs, stbir__edge_wrap_slow[edge]( save_n0, input_size ), save_n0_coeff ); + } + } + + if ( contribs->n0 <= contribs->n1 ) + { + int diff = contribs->n1 - contribs->n0 + 1; + while ( diff && ( coeffs[ diff-1 ] == 0.0f ) ) + --diff; + contribs->n1 = contribs->n0 + diff - 1; + + if ( contribs->n0 <= contribs->n1 ) + { + if ( contribs->n0 < lowest ) + lowest = contribs->n0; + if ( contribs->n1 > highest ) + highest = contribs->n1; + if ( diff > widest ) + widest = diff; + } + + // re-zero out unused coefficients (if any) + for( i = diff ; i < coefficient_width ; i++ ) + coeffs[i] = 0.0f; + } + + ++contribs; + coeffs += coefficient_width; + } + filter_info->lowest = lowest; + filter_info->highest = highest; + filter_info->widest = widest; +} + +#undef STBIR_RENORM_TYPE + +static int stbir__pack_coefficients( int num_contributors, stbir__contributors* contributors, float * coefficents, int coefficient_width, int widest, int row0, int row1 ) +{ + #define STBIR_MOVE_1( dest, src ) { STBIR_NO_UNROLL(dest); ((stbir_uint32*)(dest))[0] = ((stbir_uint32*)(src))[0]; } + #define STBIR_MOVE_2( dest, src ) { STBIR_NO_UNROLL(dest); ((stbir_uint64*)(dest))[0] = ((stbir_uint64*)(src))[0]; } + #ifdef STBIR_SIMD + #define STBIR_MOVE_4( dest, src ) { stbir__simdf t; STBIR_NO_UNROLL(dest); stbir__simdf_load( t, src ); stbir__simdf_store( dest, t ); } + #else + #define STBIR_MOVE_4( dest, src ) { STBIR_NO_UNROLL(dest); ((stbir_uint64*)(dest))[0] = ((stbir_uint64*)(src))[0]; ((stbir_uint64*)(dest))[1] = ((stbir_uint64*)(src))[1]; } + #endif + + int row_end = row1 + 1; + STBIR__UNUSED( row0 ); // only used in an assert + + if ( coefficient_width != widest ) + { + float * pc = coefficents; + float * coeffs = coefficents; + float * pc_end = coefficents + num_contributors * widest; + switch( widest ) + { + case 1: + do { + STBIR_MOVE_1( pc, coeffs ); + ++pc; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 2: + do { + STBIR_MOVE_2( pc, coeffs ); + pc += 2; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 3: + do { + STBIR_MOVE_2( pc, coeffs ); + STBIR_MOVE_1( pc+2, coeffs+2 ); + pc += 3; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 4: + do { + STBIR_MOVE_4( pc, coeffs ); + pc += 4; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 5: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_1( pc+4, coeffs+4 ); + pc += 5; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 6: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_2( pc+4, coeffs+4 ); + pc += 6; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 7: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_2( pc+4, coeffs+4 ); + STBIR_MOVE_1( pc+6, coeffs+6 ); + pc += 7; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 8: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + pc += 8; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 9: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + STBIR_MOVE_1( pc+8, coeffs+8 ); + pc += 9; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 10: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + STBIR_MOVE_2( pc+8, coeffs+8 ); + pc += 10; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 11: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + STBIR_MOVE_2( pc+8, coeffs+8 ); + STBIR_MOVE_1( pc+10, coeffs+10 ); + pc += 11; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 12: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + STBIR_MOVE_4( pc+8, coeffs+8 ); + pc += 12; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + default: + do { + float * copy_end = pc + widest - 4; + float * c = coeffs; + do { + STBIR_NO_UNROLL( pc ); + STBIR_MOVE_4( pc, c ); + pc += 4; + c += 4; + } while ( pc <= copy_end ); + copy_end += 4; + while ( pc < copy_end ) + { + STBIR_MOVE_1( pc, c ); + ++pc; ++c; + } + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + } + } + + // some horizontal routines read one float off the end (which is then masked off), so put in a sentinal so we don't read an snan or denormal + coefficents[ widest * num_contributors ] = 8888.0f; + + // the minimum we might read for unrolled filters widths is 12. So, we need to + // make sure we never read outside the decode buffer, by possibly moving + // the sample area back into the scanline, and putting zeros weights first. + // we start on the right edge and check until we're well past the possible + // clip area (2*widest). + { + stbir__contributors * contribs = contributors + num_contributors - 1; + float * coeffs = coefficents + widest * ( num_contributors - 1 ); + + // go until no chance of clipping (this is usually less than 8 lops) + while ( ( contribs >= contributors ) && ( ( contribs->n0 + widest*2 ) >= row_end ) ) + { + // might we clip?? + if ( ( contribs->n0 + widest ) > row_end ) + { + int stop_range = widest; + + // if range is larger than 12, it will be handled by generic loops that can terminate on the exact length + // of this contrib n1, instead of a fixed widest amount - so calculate this + if ( widest > 12 ) + { + int mod; + + // how far will be read in the n_coeff loop (which depends on the widest count mod4); + mod = widest & 3; + stop_range = ( ( ( contribs->n1 - contribs->n0 + 1 ) - mod + 3 ) & ~3 ) + mod; + + // the n_coeff loops do a minimum amount of coeffs, so factor that in! + if ( stop_range < ( 8 + mod ) ) stop_range = 8 + mod; + } + + // now see if we still clip with the refined range + if ( ( contribs->n0 + stop_range ) > row_end ) + { + int new_n0 = row_end - stop_range; + int num = contribs->n1 - contribs->n0 + 1; + int backup = contribs->n0 - new_n0; + float * from_co = coeffs + num - 1; + float * to_co = from_co + backup; + + STBIR_ASSERT( ( new_n0 >= row0 ) && ( new_n0 < contribs->n0 ) ); + + // move the coeffs over + while( num ) + { + *to_co-- = *from_co--; + --num; + } + // zero new positions + while ( to_co >= coeffs ) + *to_co-- = 0; + // set new start point + contribs->n0 = new_n0; + if ( widest > 12 ) + { + int mod; + + // how far will be read in the n_coeff loop (which depends on the widest count mod4); + mod = widest & 3; + stop_range = ( ( ( contribs->n1 - contribs->n0 + 1 ) - mod + 3 ) & ~3 ) + mod; + + // the n_coeff loops do a minimum amount of coeffs, so factor that in! + if ( stop_range < ( 8 + mod ) ) stop_range = 8 + mod; + } + } + } + --contribs; + coeffs -= widest; + } + } + + return widest; + #undef STBIR_MOVE_1 + #undef STBIR_MOVE_2 + #undef STBIR_MOVE_4 +} + +static void stbir__calculate_filters( stbir__sampler * samp, stbir__sampler * other_axis_for_pivot, void * user_data STBIR_ONLY_PROFILE_BUILD_GET_INFO ) +{ + int n; + float scale = samp->scale_info.scale; + stbir__kernel_callback * kernel = samp->filter_kernel; + stbir__support_callback * support = samp->filter_support; + float inv_scale = samp->scale_info.inv_scale; + int input_full_size = samp->scale_info.input_full_size; + int gather_num_contributors = samp->num_contributors; + stbir__contributors* gather_contributors = samp->contributors; + float * gather_coeffs = samp->coefficients; + int gather_coefficient_width = samp->coefficient_width; + + switch ( samp->is_gather ) + { + case 1: // gather upsample + { + float out_pixels_radius = support(inv_scale,user_data) * scale; + + stbir__calculate_coefficients_for_gather_upsample( out_pixels_radius, kernel, &samp->scale_info, gather_num_contributors, gather_contributors, gather_coeffs, gather_coefficient_width, samp->edge, user_data ); + + STBIR_PROFILE_BUILD_START( cleanup ); + stbir__cleanup_gathered_coefficients( samp->edge, &samp->extent_info, &samp->scale_info, gather_num_contributors, gather_contributors, gather_coeffs, gather_coefficient_width ); + STBIR_PROFILE_BUILD_END( cleanup ); + } + break; + + case 0: // scatter downsample (only on vertical) + case 2: // gather downsample + { + float in_pixels_radius = support(scale,user_data) * inv_scale; + int filter_pixel_margin = samp->filter_pixel_margin; + int input_end = input_full_size + filter_pixel_margin; + + // if this is a scatter, we do a downsample gather to get the coeffs, and then pivot after + if ( !samp->is_gather ) + { + // check if we are using the same gather downsample on the horizontal as this vertical, + // if so, then we don't have to generate them, we can just pivot from the horizontal. + if ( other_axis_for_pivot ) + { + gather_contributors = other_axis_for_pivot->contributors; + gather_coeffs = other_axis_for_pivot->coefficients; + gather_coefficient_width = other_axis_for_pivot->coefficient_width; + gather_num_contributors = other_axis_for_pivot->num_contributors; + samp->extent_info.lowest = other_axis_for_pivot->extent_info.lowest; + samp->extent_info.highest = other_axis_for_pivot->extent_info.highest; + samp->extent_info.widest = other_axis_for_pivot->extent_info.widest; + goto jump_right_to_pivot; + } + + gather_contributors = samp->gather_prescatter_contributors; + gather_coeffs = samp->gather_prescatter_coefficients; + gather_coefficient_width = samp->gather_prescatter_coefficient_width; + gather_num_contributors = samp->gather_prescatter_num_contributors; + } + + stbir__calculate_coefficients_for_gather_downsample( -filter_pixel_margin, input_end, in_pixels_radius, kernel, &samp->scale_info, gather_coefficient_width, gather_num_contributors, gather_contributors, gather_coeffs, user_data ); + + STBIR_PROFILE_BUILD_START( cleanup ); + stbir__cleanup_gathered_coefficients( samp->edge, &samp->extent_info, &samp->scale_info, gather_num_contributors, gather_contributors, gather_coeffs, gather_coefficient_width ); + STBIR_PROFILE_BUILD_END( cleanup ); + + if ( !samp->is_gather ) + { + // if this is a scatter (vertical only), then we need to pivot the coeffs + stbir__contributors * scatter_contributors; + int highest_set; + + jump_right_to_pivot: + + STBIR_PROFILE_BUILD_START( pivot ); + + highest_set = (-filter_pixel_margin) - 1; + for (n = 0; n < gather_num_contributors; n++) + { + int k; + int gn0 = gather_contributors->n0, gn1 = gather_contributors->n1; + int scatter_coefficient_width = samp->coefficient_width; + float * scatter_coeffs = samp->coefficients + ( gn0 + filter_pixel_margin ) * scatter_coefficient_width; + float * g_coeffs = gather_coeffs; + scatter_contributors = samp->contributors + ( gn0 + filter_pixel_margin ); + + for (k = gn0 ; k <= gn1 ; k++ ) + { + float gc = *g_coeffs++; + + // skip zero and denormals - must skip zeros to avoid adding coeffs beyond scatter_coefficient_width + // (which happens when pivoting from horizontal, which might have dummy zeros) + if ( ( ( gc >= stbir__small_float ) || ( gc <= -stbir__small_float ) ) ) + { + if ( ( k > highest_set ) || ( scatter_contributors->n0 > scatter_contributors->n1 ) ) + { + { + // if we are skipping over several contributors, we need to clear the skipped ones + stbir__contributors * clear_contributors = samp->contributors + ( highest_set + filter_pixel_margin + 1); + while ( clear_contributors < scatter_contributors ) + { + clear_contributors->n0 = 0; + clear_contributors->n1 = -1; + ++clear_contributors; + } + } + scatter_contributors->n0 = n; + scatter_contributors->n1 = n; + scatter_coeffs[0] = gc; + highest_set = k; + } + else + { + stbir__insert_coeff( scatter_contributors, scatter_coeffs, n, gc ); + } + STBIR_ASSERT( ( scatter_contributors->n1 - scatter_contributors->n0 + 1 ) <= scatter_coefficient_width ); + } + ++scatter_contributors; + scatter_coeffs += scatter_coefficient_width; + } + + ++gather_contributors; + gather_coeffs += gather_coefficient_width; + } + + // now clear any unset contribs + { + stbir__contributors * clear_contributors = samp->contributors + ( highest_set + filter_pixel_margin + 1); + stbir__contributors * end_contributors = samp->contributors + samp->num_contributors; + while ( clear_contributors < end_contributors ) + { + clear_contributors->n0 = 0; + clear_contributors->n1 = -1; + ++clear_contributors; + } + } + + STBIR_PROFILE_BUILD_END( pivot ); + } + } + break; + } +} + + +//======================================================================================================== +// scanline decoders and encoders + +#define stbir__coder_min_num 1 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + +#define stbir__decode_suffix BGRA +#define stbir__decode_swizzle +#define stbir__decode_order0 2 +#define stbir__decode_order1 1 +#define stbir__decode_order2 0 +#define stbir__decode_order3 3 +#define stbir__encode_order0 2 +#define stbir__encode_order1 1 +#define stbir__encode_order2 0 +#define stbir__encode_order3 3 +#define stbir__coder_min_num 4 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + +#define stbir__decode_suffix ARGB +#define stbir__decode_swizzle +#define stbir__decode_order0 1 +#define stbir__decode_order1 2 +#define stbir__decode_order2 3 +#define stbir__decode_order3 0 +#define stbir__encode_order0 3 +#define stbir__encode_order1 0 +#define stbir__encode_order2 1 +#define stbir__encode_order3 2 +#define stbir__coder_min_num 4 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + +#define stbir__decode_suffix ABGR +#define stbir__decode_swizzle +#define stbir__decode_order0 3 +#define stbir__decode_order1 2 +#define stbir__decode_order2 1 +#define stbir__decode_order3 0 +#define stbir__encode_order0 3 +#define stbir__encode_order1 2 +#define stbir__encode_order2 1 +#define stbir__encode_order3 0 +#define stbir__coder_min_num 4 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + +#define stbir__decode_suffix AR +#define stbir__decode_swizzle +#define stbir__decode_order0 1 +#define stbir__decode_order1 0 +#define stbir__decode_order2 3 +#define stbir__decode_order3 2 +#define stbir__encode_order0 1 +#define stbir__encode_order1 0 +#define stbir__encode_order2 3 +#define stbir__encode_order3 2 +#define stbir__coder_min_num 2 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + + +// fancy alpha means we expand to keep both premultipied and non-premultiplied color channels +static void stbir__fancy_alpha_weight_4ch( float * out_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) out = out_buffer; + float const * end_decode = out_buffer + ( width_times_channels / 4 ) * 7; // decode buffer aligned to end of out_buffer + float STBIR_STREAMOUT_PTR(*) decode = (float*)end_decode - width_times_channels; + + // fancy alpha is stored internally as R G B A Rpm Gpm Bpm + + #ifdef STBIR_SIMD + + #ifdef STBIR_SIMD8 + decode += 16; + while ( decode <= end_decode ) + { + stbir__simdf8 d0,d1,a0,a1,p0,p1; + STBIR_NO_UNROLL(decode); + stbir__simdf8_load( d0, decode-16 ); + stbir__simdf8_load( d1, decode-16+8 ); + stbir__simdf8_0123to33333333( a0, d0 ); + stbir__simdf8_0123to33333333( a1, d1 ); + stbir__simdf8_mult( p0, a0, d0 ); + stbir__simdf8_mult( p1, a1, d1 ); + stbir__simdf8_bot4s( a0, d0, p0 ); + stbir__simdf8_bot4s( a1, d1, p1 ); + stbir__simdf8_top4s( d0, d0, p0 ); + stbir__simdf8_top4s( d1, d1, p1 ); + stbir__simdf8_store ( out, a0 ); + stbir__simdf8_store ( out+7, d0 ); + stbir__simdf8_store ( out+14, a1 ); + stbir__simdf8_store ( out+21, d1 ); + decode += 16; + out += 28; + } + decode -= 16; + #else + decode += 8; + while ( decode <= end_decode ) + { + stbir__simdf d0,a0,d1,a1,p0,p1; + STBIR_NO_UNROLL(decode); + stbir__simdf_load( d0, decode-8 ); + stbir__simdf_load( d1, decode-8+4 ); + stbir__simdf_0123to3333( a0, d0 ); + stbir__simdf_0123to3333( a1, d1 ); + stbir__simdf_mult( p0, a0, d0 ); + stbir__simdf_mult( p1, a1, d1 ); + stbir__simdf_store ( out, d0 ); + stbir__simdf_store ( out+4, p0 ); + stbir__simdf_store ( out+7, d1 ); + stbir__simdf_store ( out+7+4, p1 ); + decode += 8; + out += 14; + } + decode -= 8; + #endif + + // might be one last odd pixel + #ifdef STBIR_SIMD8 + while ( decode < end_decode ) + #else + if ( decode < end_decode ) + #endif + { + stbir__simdf d,a,p; + stbir__simdf_load( d, decode ); + stbir__simdf_0123to3333( a, d ); + stbir__simdf_mult( p, a, d ); + stbir__simdf_store ( out, d ); + stbir__simdf_store ( out+4, p ); + decode += 4; + out += 7; + } + + #else + + while( decode < end_decode ) + { + float r = decode[0], g = decode[1], b = decode[2], alpha = decode[3]; + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = alpha; + out[4] = r * alpha; + out[5] = g * alpha; + out[6] = b * alpha; + out += 7; + decode += 4; + } + + #endif +} + +static void stbir__fancy_alpha_weight_2ch( float * out_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) out = out_buffer; + float const * end_decode = out_buffer + ( width_times_channels / 2 ) * 3; + float STBIR_STREAMOUT_PTR(*) decode = (float*)end_decode - width_times_channels; + + // for fancy alpha, turns into: [X A Xpm][X A Xpm],etc + + #ifdef STBIR_SIMD + + decode += 8; + if ( decode <= end_decode ) + { + do { + #ifdef STBIR_SIMD8 + stbir__simdf8 d0,a0,p0; + STBIR_NO_UNROLL(decode); + stbir__simdf8_load( d0, decode-8 ); + stbir__simdf8_0123to11331133( p0, d0 ); + stbir__simdf8_0123to00220022( a0, d0 ); + stbir__simdf8_mult( p0, p0, a0 ); + + stbir__simdf_store2( out, stbir__if_simdf8_cast_to_simdf4( d0 ) ); + stbir__simdf_store( out+2, stbir__if_simdf8_cast_to_simdf4( p0 ) ); + stbir__simdf_store2h( out+3, stbir__if_simdf8_cast_to_simdf4( d0 ) ); + + stbir__simdf_store2( out+6, stbir__simdf8_gettop4( d0 ) ); + stbir__simdf_store( out+8, stbir__simdf8_gettop4( p0 ) ); + stbir__simdf_store2h( out+9, stbir__simdf8_gettop4( d0 ) ); + #else + stbir__simdf d0,a0,d1,a1,p0,p1; + STBIR_NO_UNROLL(decode); + stbir__simdf_load( d0, decode-8 ); + stbir__simdf_load( d1, decode-8+4 ); + stbir__simdf_0123to1133( p0, d0 ); + stbir__simdf_0123to1133( p1, d1 ); + stbir__simdf_0123to0022( a0, d0 ); + stbir__simdf_0123to0022( a1, d1 ); + stbir__simdf_mult( p0, p0, a0 ); + stbir__simdf_mult( p1, p1, a1 ); + + stbir__simdf_store2( out, d0 ); + stbir__simdf_store( out+2, p0 ); + stbir__simdf_store2h( out+3, d0 ); + + stbir__simdf_store2( out+6, d1 ); + stbir__simdf_store( out+8, p1 ); + stbir__simdf_store2h( out+9, d1 ); + #endif + decode += 8; + out += 12; + } while ( decode <= end_decode ); + } + decode -= 8; + #endif + + while( decode < end_decode ) + { + float x = decode[0], y = decode[1]; + STBIR_SIMD_NO_UNROLL(decode); + out[0] = x; + out[1] = y; + out[2] = x * y; + out += 3; + decode += 2; + } +} + +static void stbir__fancy_alpha_unweight_4ch( float * encode_buffer, int width_times_channels ) +{ + float STBIR_SIMD_STREAMOUT_PTR(*) encode = encode_buffer; + float STBIR_SIMD_STREAMOUT_PTR(*) input = encode_buffer; + float const * end_output = encode_buffer + width_times_channels; + + // fancy RGBA is stored internally as R G B A Rpm Gpm Bpm + + do { + float alpha = input[3]; +#ifdef STBIR_SIMD + stbir__simdf i,ia; + STBIR_SIMD_NO_UNROLL(encode); + if ( alpha < stbir__small_float ) + { + stbir__simdf_load( i, input ); + stbir__simdf_store( encode, i ); + } + else + { + stbir__simdf_load1frep4( ia, 1.0f / alpha ); + stbir__simdf_load( i, input+4 ); + stbir__simdf_mult( i, i, ia ); + stbir__simdf_store( encode, i ); + encode[3] = alpha; + } +#else + if ( alpha < stbir__small_float ) + { + encode[0] = input[0]; + encode[1] = input[1]; + encode[2] = input[2]; + } + else + { + float ialpha = 1.0f / alpha; + encode[0] = input[4] * ialpha; + encode[1] = input[5] * ialpha; + encode[2] = input[6] * ialpha; + } + encode[3] = alpha; +#endif + + input += 7; + encode += 4; + } while ( encode < end_output ); +} + +// format: [X A Xpm][X A Xpm] etc +static void stbir__fancy_alpha_unweight_2ch( float * encode_buffer, int width_times_channels ) +{ + float STBIR_SIMD_STREAMOUT_PTR(*) encode = encode_buffer; + float STBIR_SIMD_STREAMOUT_PTR(*) input = encode_buffer; + float const * end_output = encode_buffer + width_times_channels; + + do { + float alpha = input[1]; + encode[0] = input[0]; + if ( alpha >= stbir__small_float ) + encode[0] = input[2] / alpha; + encode[1] = alpha; + + input += 3; + encode += 2; + } while ( encode < end_output ); +} + +static void stbir__simple_alpha_weight_4ch( float * decode_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) decode = decode_buffer; + float const * end_decode = decode_buffer + width_times_channels; + + #ifdef STBIR_SIMD + { + decode += 2 * stbir__simdfX_float_count; + while ( decode <= end_decode ) + { + stbir__simdfX d0,a0,d1,a1; + STBIR_NO_UNROLL(decode); + stbir__simdfX_load( d0, decode-2*stbir__simdfX_float_count ); + stbir__simdfX_load( d1, decode-2*stbir__simdfX_float_count+stbir__simdfX_float_count ); + stbir__simdfX_aaa1( a0, d0, STBIR_onesX ); + stbir__simdfX_aaa1( a1, d1, STBIR_onesX ); + stbir__simdfX_mult( d0, d0, a0 ); + stbir__simdfX_mult( d1, d1, a1 ); + stbir__simdfX_store ( decode-2*stbir__simdfX_float_count, d0 ); + stbir__simdfX_store ( decode-2*stbir__simdfX_float_count+stbir__simdfX_float_count, d1 ); + decode += 2 * stbir__simdfX_float_count; + } + decode -= 2 * stbir__simdfX_float_count; + + // few last pixels remnants + #ifdef STBIR_SIMD8 + while ( decode < end_decode ) + #else + if ( decode < end_decode ) + #endif + { + stbir__simdf d,a; + stbir__simdf_load( d, decode ); + stbir__simdf_aaa1( a, d, STBIR__CONSTF(STBIR_ones) ); + stbir__simdf_mult( d, d, a ); + stbir__simdf_store ( decode, d ); + decode += 4; + } + } + + #else + + while( decode < end_decode ) + { + float alpha = decode[3]; + decode[0] *= alpha; + decode[1] *= alpha; + decode[2] *= alpha; + decode += 4; + } + + #endif +} + +static void stbir__simple_alpha_weight_2ch( float * decode_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) decode = decode_buffer; + float const * end_decode = decode_buffer + width_times_channels; + + #ifdef STBIR_SIMD + decode += 2 * stbir__simdfX_float_count; + while ( decode <= end_decode ) + { + stbir__simdfX d0,a0,d1,a1; + STBIR_NO_UNROLL(decode); + stbir__simdfX_load( d0, decode-2*stbir__simdfX_float_count ); + stbir__simdfX_load( d1, decode-2*stbir__simdfX_float_count+stbir__simdfX_float_count ); + stbir__simdfX_a1a1( a0, d0, STBIR_onesX ); + stbir__simdfX_a1a1( a1, d1, STBIR_onesX ); + stbir__simdfX_mult( d0, d0, a0 ); + stbir__simdfX_mult( d1, d1, a1 ); + stbir__simdfX_store ( decode-2*stbir__simdfX_float_count, d0 ); + stbir__simdfX_store ( decode-2*stbir__simdfX_float_count+stbir__simdfX_float_count, d1 ); + decode += 2 * stbir__simdfX_float_count; + } + decode -= 2 * stbir__simdfX_float_count; + #endif + + while( decode < end_decode ) + { + float alpha = decode[1]; + STBIR_SIMD_NO_UNROLL(decode); + decode[0] *= alpha; + decode += 2; + } +} + +static void stbir__simple_alpha_unweight_4ch( float * encode_buffer, int width_times_channels ) +{ + float STBIR_SIMD_STREAMOUT_PTR(*) encode = encode_buffer; + float const * end_output = encode_buffer + width_times_channels; + + do { + float alpha = encode[3]; + +#ifdef STBIR_SIMD + stbir__simdf i,ia; + STBIR_SIMD_NO_UNROLL(encode); + if ( alpha >= stbir__small_float ) + { + stbir__simdf_load1frep4( ia, 1.0f / alpha ); + stbir__simdf_load( i, encode ); + stbir__simdf_mult( i, i, ia ); + stbir__simdf_store( encode, i ); + encode[3] = alpha; + } +#else + if ( alpha >= stbir__small_float ) + { + float ialpha = 1.0f / alpha; + encode[0] *= ialpha; + encode[1] *= ialpha; + encode[2] *= ialpha; + } +#endif + encode += 4; + } while ( encode < end_output ); +} + +static void stbir__simple_alpha_unweight_2ch( float * encode_buffer, int width_times_channels ) +{ + float STBIR_SIMD_STREAMOUT_PTR(*) encode = encode_buffer; + float const * end_output = encode_buffer + width_times_channels; + + do { + float alpha = encode[1]; + if ( alpha >= stbir__small_float ) + encode[0] /= alpha; + encode += 2; + } while ( encode < end_output ); +} + + +// only used in RGB->BGR or BGR->RGB +static void stbir__simple_flip_3ch( float * decode_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) decode = decode_buffer; + float const * end_decode = decode_buffer + width_times_channels; + + decode += 12; + while( decode <= end_decode ) + { + float t0,t1,t2,t3; + STBIR_NO_UNROLL(decode); + t0 = decode[0]; t1 = decode[3]; t2 = decode[6]; t3 = decode[9]; + decode[0] = decode[2]; decode[3] = decode[5]; decode[6] = decode[8]; decode[9] = decode[11]; + decode[2] = t0; decode[5] = t1; decode[8] = t2; decode[11] = t3; + decode += 12; + } + decode -= 12; + + while( decode < end_decode ) + { + float t = decode[0]; + STBIR_NO_UNROLL(decode); + decode[0] = decode[2]; + decode[2] = t; + decode += 3; + } +} + + + +static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float * output_buffer STBIR_ONLY_PROFILE_GET_SPLIT_INFO ) +{ + int channels = stbir_info->channels; + int effective_channels = stbir_info->effective_channels; + int input_sample_in_bytes = stbir__type_size[stbir_info->input_type] * channels; + stbir_edge edge_horizontal = stbir_info->horizontal.edge; + stbir_edge edge_vertical = stbir_info->vertical.edge; + int row = stbir__edge_wrap(edge_vertical, n, stbir_info->vertical.scale_info.input_full_size); + const void* input_plane_data = ( (char *) stbir_info->input_data ) + (ptrdiff_t)row * (ptrdiff_t) stbir_info->input_stride_bytes; + stbir__span const * spans = stbir_info->scanline_extents.spans; + float* full_decode_buffer = output_buffer - stbir_info->scanline_extents.conservative.n0 * effective_channels; + + // if we are on edge_zero, and we get in here with an out of bounds n, then the calculate filters has failed + STBIR_ASSERT( !(edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->vertical.scale_info.input_full_size)) ); + + do + { + float * decode_buffer; + void const * input_data; + float * end_decode; + int width_times_channels; + int width; + + if ( spans->n1 < spans->n0 ) + break; + + width = spans->n1 + 1 - spans->n0; + decode_buffer = full_decode_buffer + spans->n0 * effective_channels; + end_decode = full_decode_buffer + ( spans->n1 + 1 ) * effective_channels; + width_times_channels = width * channels; + + // read directly out of input plane by default + input_data = ( (char*)input_plane_data ) + spans->pixel_offset_for_input * input_sample_in_bytes; + + // if we have an input callback, call it to get the input data + if ( stbir_info->in_pixels_cb ) + { + // call the callback with a temp buffer (that they can choose to use or not). the temp is just right aligned memory in the decode_buffer itself + input_data = stbir_info->in_pixels_cb( ( (char*) end_decode ) - ( width * input_sample_in_bytes ), input_plane_data, width, spans->pixel_offset_for_input, row, stbir_info->user_data ); + } + + STBIR_PROFILE_START( decode ); + // convert the pixels info the float decode_buffer, (we index from end_decode, so that when channelsdecode_pixels( (float*)end_decode - width_times_channels, width_times_channels, input_data ); + STBIR_PROFILE_END( decode ); + + if (stbir_info->alpha_weight) + { + STBIR_PROFILE_START( alpha ); + stbir_info->alpha_weight( decode_buffer, width_times_channels ); + STBIR_PROFILE_END( alpha ); + } + + ++spans; + } while ( spans <= ( &stbir_info->scanline_extents.spans[1] ) ); + + // handle the edge_wrap filter (all other types are handled back out at the calculate_filter stage) + // basically the idea here is that if we have the whole scanline in memory, we don't redecode the + // wrapped edge pixels, and instead just memcpy them from the scanline into the edge positions + if ( ( edge_horizontal == STBIR_EDGE_WRAP ) && ( stbir_info->scanline_extents.edge_sizes[0] | stbir_info->scanline_extents.edge_sizes[1] ) ) + { + // this code only runs if we're in edge_wrap, and we're doing the entire scanline + int e, start_x[2]; + int input_full_size = stbir_info->horizontal.scale_info.input_full_size; + + start_x[0] = -stbir_info->scanline_extents.edge_sizes[0]; // left edge start x + start_x[1] = input_full_size; // right edge + + for( e = 0; e < 2 ; e++ ) + { + // do each margin + int margin = stbir_info->scanline_extents.edge_sizes[e]; + if ( margin ) + { + int x = start_x[e]; + float * marg = full_decode_buffer + x * effective_channels; + float const * src = full_decode_buffer + stbir__edge_wrap(edge_horizontal, x, input_full_size) * effective_channels; + STBIR_MEMCPY( marg, src, margin * effective_channels * sizeof(float) ); + } + } + } +} + + +//================= +// Do 1 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc ); \ + stbir__simdf_mult1_mem( tot, c, decode ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot,c,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2z( c, hc ); \ + stbir__simdf_load2( d, decode ); \ + stbir__simdf_mult( tot, c, d ); \ + stbir__simdf_0123to1230( c, tot ); \ + stbir__simdf_add1( tot, tot, c ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot,c,t; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( c, hc ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + stbir__simdf_0123to1230( c, tot ); \ + stbir__simdf_0123to2301( t, tot ); \ + stbir__simdf_add1( tot, tot, c ); \ + stbir__simdf_add1( tot, tot, t ); + +#define stbir__store_output_tiny() \ + stbir__simdf_store1( output, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 1; + +#define stbir__4_coeff_start() \ + stbir__simdf tot,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( c, hc ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( c, hc + (ofs) ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+(ofs) ); + +#define stbir__1_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + stbir__simdf_load1z( c, hc + (ofs) ); \ + stbir__simdf_load1( d, decode + (ofs) ); \ + stbir__simdf_madd( tot, tot, d, c ); } + +#define stbir__2_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + stbir__simdf_load2z( c, hc+(ofs) ); \ + stbir__simdf_load2( d, decode+(ofs) ); \ + stbir__simdf_madd( tot, tot, d, c ); } + +#define stbir__3_coeff_setup() \ + stbir__simdf mask; \ + stbir__simdf_load( mask, STBIR_mask + 3 ); + +#define stbir__3_coeff_remnant( ofs ) \ + stbir__simdf_load( c, hc+(ofs) ); \ + stbir__simdf_and( c, c, mask ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+(ofs) ); + +#define stbir__store_output() \ + stbir__simdf_0123to2301( c, tot ); \ + stbir__simdf_add( tot, tot, c ); \ + stbir__simdf_0123to1230( c, tot ); \ + stbir__simdf_add1( tot, tot, c ); \ + stbir__simdf_store1( output, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 1; + +#else + +#define stbir__1_coeff_only() \ + float tot; \ + tot = decode[0]*hc[0]; + +#define stbir__2_coeff_only() \ + float tot; \ + tot = decode[0] * hc[0]; \ + tot += decode[1] * hc[1]; + +#define stbir__3_coeff_only() \ + float tot; \ + tot = decode[0] * hc[0]; \ + tot += decode[1] * hc[1]; \ + tot += decode[2] * hc[2]; + +#define stbir__store_output_tiny() \ + output[0] = tot; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 1; + +#define stbir__4_coeff_start() \ + float tot0,tot1,tot2,tot3; \ + tot0 = decode[0] * hc[0]; \ + tot1 = decode[1] * hc[1]; \ + tot2 = decode[2] * hc[2]; \ + tot3 = decode[3] * hc[3]; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + tot0 += decode[0+(ofs)] * hc[0+(ofs)]; \ + tot1 += decode[1+(ofs)] * hc[1+(ofs)]; \ + tot2 += decode[2+(ofs)] * hc[2+(ofs)]; \ + tot3 += decode[3+(ofs)] * hc[3+(ofs)]; + +#define stbir__1_coeff_remnant( ofs ) \ + tot0 += decode[0+(ofs)] * hc[0+(ofs)]; + +#define stbir__2_coeff_remnant( ofs ) \ + tot0 += decode[0+(ofs)] * hc[0+(ofs)]; \ + tot1 += decode[1+(ofs)] * hc[1+(ofs)]; \ + +#define stbir__3_coeff_remnant( ofs ) \ + tot0 += decode[0+(ofs)] * hc[0+(ofs)]; \ + tot1 += decode[1+(ofs)] * hc[1+(ofs)]; \ + tot2 += decode[2+(ofs)] * hc[2+(ofs)]; + +#define stbir__store_output() \ + output[0] = (tot0+tot2)+(tot1+tot3); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 1; + +#endif + +#define STBIR__horizontal_channels 1 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + + +//================= +// Do 2 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot,c,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1z( c, hc ); \ + stbir__simdf_0123to0011( c, c ); \ + stbir__simdf_load2( d, decode ); \ + stbir__simdf_mult( tot, d, c ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( c, hc ); \ + stbir__simdf_0123to0011( c, c ); \ + stbir__simdf_mult_mem( tot, c, decode ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot,c,cs,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_load2z( d, decode+4 ); \ + stbir__simdf_madd( tot, tot, d, c ); + +#define stbir__store_output_tiny() \ + stbir__simdf_0123to2301( c, tot ); \ + stbir__simdf_add( tot, tot, c ); \ + stbir__simdf_store2( output, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; + +#ifdef STBIR_SIMD8 + +#define stbir__4_coeff_start() \ + stbir__simdf8 tot0,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc ); \ + stbir__simdf8_0123to00112233( c, cs ); \ + stbir__simdf8_mult_mem( tot0, c, decode ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00112233( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); + +#define stbir__1_coeff_remnant( ofs ) \ + { stbir__simdf t; \ + stbir__simdf_load1z( t, hc + (ofs) ); \ + stbir__simdf_0123to0011( t, t ); \ + stbir__simdf_mult_mem( t, t, decode+(ofs)*2 ); \ + stbir__simdf8_add4( tot0, tot0, t ); } + +#define stbir__2_coeff_remnant( ofs ) \ + { stbir__simdf t; \ + stbir__simdf_load2( t, hc + (ofs) ); \ + stbir__simdf_0123to0011( t, t ); \ + stbir__simdf_mult_mem( t, t, decode+(ofs)*2 ); \ + stbir__simdf8_add4( tot0, tot0, t ); } + +#define stbir__3_coeff_remnant( ofs ) \ + { stbir__simdf8 d; \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00112233( c, cs ); \ + stbir__simdf8_load6z( d, decode+(ofs)*2 ); \ + stbir__simdf8_madd( tot0, tot0, c, d ); } + +#define stbir__store_output() \ + { stbir__simdf t,d; \ + stbir__simdf8_add4halves( t, stbir__if_simdf8_cast_to_simdf4(tot0), tot0 ); \ + stbir__simdf_0123to2301( d, t ); \ + stbir__simdf_add( t, t, d ); \ + stbir__simdf_store2( output, t ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; } + +#else + +#define stbir__4_coeff_start() \ + stbir__simdf tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_0123to2233( c, cs ); \ + stbir__simdf_mult_mem( tot1, c, decode+4 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); \ + stbir__simdf_0123to2233( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*2+4 ); + +#define stbir__1_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + stbir__simdf_load1z( cs, hc + (ofs) ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_load2( d, decode + (ofs) * 2 ); \ + stbir__simdf_madd( tot0, tot0, d, c ); } + +#define stbir__2_coeff_remnant( ofs ) \ + stbir__simdf_load2( cs, hc + (ofs) ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); + +#define stbir__3_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_load2z( d, decode + (ofs) * 2 + 4 ); \ + stbir__simdf_madd( tot1, tot1, d, c ); } + +#define stbir__store_output() \ + stbir__simdf_add( tot0, tot0, tot1 ); \ + stbir__simdf_0123to2301( c, tot0 ); \ + stbir__simdf_add( tot0, tot0, c ); \ + stbir__simdf_store2( output, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; + +#endif + +#else + +#define stbir__1_coeff_only() \ + float tota,totb,c; \ + c = hc[0]; \ + tota = decode[0]*c; \ + totb = decode[1]*c; + +#define stbir__2_coeff_only() \ + float tota,totb,c; \ + c = hc[0]; \ + tota = decode[0]*c; \ + totb = decode[1]*c; \ + c = hc[1]; \ + tota += decode[2]*c; \ + totb += decode[3]*c; + +// this weird order of add matches the simd +#define stbir__3_coeff_only() \ + float tota,totb,c; \ + c = hc[0]; \ + tota = decode[0]*c; \ + totb = decode[1]*c; \ + c = hc[2]; \ + tota += decode[4]*c; \ + totb += decode[5]*c; \ + c = hc[1]; \ + tota += decode[2]*c; \ + totb += decode[3]*c; + +#define stbir__store_output_tiny() \ + output[0] = tota; \ + output[1] = totb; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; + +#define stbir__4_coeff_start() \ + float tota0,tota1,tota2,tota3,totb0,totb1,totb2,totb3,c; \ + c = hc[0]; \ + tota0 = decode[0]*c; \ + totb0 = decode[1]*c; \ + c = hc[1]; \ + tota1 = decode[2]*c; \ + totb1 = decode[3]*c; \ + c = hc[2]; \ + tota2 = decode[4]*c; \ + totb2 = decode[5]*c; \ + c = hc[3]; \ + tota3 = decode[6]*c; \ + totb3 = decode[7]*c; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*2]*c; \ + totb0 += decode[1+(ofs)*2]*c; \ + c = hc[1+(ofs)]; \ + tota1 += decode[2+(ofs)*2]*c; \ + totb1 += decode[3+(ofs)*2]*c; \ + c = hc[2+(ofs)]; \ + tota2 += decode[4+(ofs)*2]*c; \ + totb2 += decode[5+(ofs)*2]*c; \ + c = hc[3+(ofs)]; \ + tota3 += decode[6+(ofs)*2]*c; \ + totb3 += decode[7+(ofs)*2]*c; + +#define stbir__1_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*2] * c; \ + totb0 += decode[1+(ofs)*2] * c; + +#define stbir__2_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*2] * c; \ + totb0 += decode[1+(ofs)*2] * c; \ + c = hc[1+(ofs)]; \ + tota1 += decode[2+(ofs)*2] * c; \ + totb1 += decode[3+(ofs)*2] * c; + +#define stbir__3_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*2] * c; \ + totb0 += decode[1+(ofs)*2] * c; \ + c = hc[1+(ofs)]; \ + tota1 += decode[2+(ofs)*2] * c; \ + totb1 += decode[3+(ofs)*2] * c; \ + c = hc[2+(ofs)]; \ + tota2 += decode[4+(ofs)*2] * c; \ + totb2 += decode[5+(ofs)*2] * c; + +#define stbir__store_output() \ + output[0] = (tota0+tota2)+(tota1+tota3); \ + output[1] = (totb0+totb2)+(totb1+totb3); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; + +#endif + +#define STBIR__horizontal_channels 2 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + + +//================= +// Do 3 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot,c,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1z( c, hc ); \ + stbir__simdf_0123to0001( c, c ); \ + stbir__simdf_load( d, decode ); \ + stbir__simdf_mult( tot, d, c ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot,c,cs,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_load( d, decode ); \ + stbir__simdf_mult( tot, d, c ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_load( d, decode+3 ); \ + stbir__simdf_madd( tot, tot, d, c ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot,c,d,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_load( d, decode ); \ + stbir__simdf_mult( tot, d, c ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_load( d, decode+3 ); \ + stbir__simdf_madd( tot, tot, d, c ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_load( d, decode+6 ); \ + stbir__simdf_madd( tot, tot, d, c ); + +#define stbir__store_output_tiny() \ + stbir__simdf_store2( output, tot ); \ + stbir__simdf_0123to2301( tot, tot ); \ + stbir__simdf_store1( output+2, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; + +#ifdef STBIR_SIMD8 + +// we're loading from the XXXYYY decode by -1 to get the XXXYYY into different halves of the AVX reg fyi +#define stbir__4_coeff_start() \ + stbir__simdf8 tot0,tot1,c,cs; stbir__simdf t; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_mult_mem( tot0, c, decode - 1 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_mult_mem( tot1, c, decode+6 - 1 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*3 - 1 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*3 + 6 - 1 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1rep4( t, hc + (ofs) ); \ + stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*3 - 1 ); + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) - 2 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*3 - 1 ); + + #define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*3 - 1 ); \ + stbir__simdf8_0123to2222( t, cs ); \ + stbir__simdf8_madd_mem4( tot1, tot1, t, decode+(ofs)*3 + 6 - 1 ); + +#define stbir__store_output() \ + stbir__simdf8_add( tot0, tot0, tot1 ); \ + stbir__simdf_0123to1230( t, stbir__if_simdf8_cast_to_simdf4( tot0 ) ); \ + stbir__simdf8_add4halves( t, t, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; \ + if ( output < output_end ) \ + { \ + stbir__simdf_store( output-3, t ); \ + continue; \ + } \ + { stbir__simdf tt; stbir__simdf_0123to2301( tt, t ); \ + stbir__simdf_store2( output-3, t ); \ + stbir__simdf_store1( output+2-3, tt ); } \ + break; + + +#else + +#define stbir__4_coeff_start() \ + stbir__simdf tot0,tot1,tot2,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0001( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_0123to1122( c, cs ); \ + stbir__simdf_mult_mem( tot1, c, decode+4 ); \ + stbir__simdf_0123to2333( c, cs ); \ + stbir__simdf_mult_mem( tot2, c, decode+8 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0001( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); \ + stbir__simdf_0123to1122( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*3+4 ); \ + stbir__simdf_0123to2333( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*3+8 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1z( c, hc + (ofs) ); \ + stbir__simdf_0123to0001( c, c ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); + +#define stbir__2_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2z( cs, hc + (ofs) ); \ + stbir__simdf_0123to0001( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); \ + stbir__simdf_0123to1122( c, cs ); \ + stbir__simdf_load2z( d, decode+(ofs)*3+4 ); \ + stbir__simdf_madd( tot1, tot1, c, d ); } + +#define stbir__3_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0001( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); \ + stbir__simdf_0123to1122( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*3+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_load1z( d, decode+(ofs)*3+8 ); \ + stbir__simdf_madd( tot2, tot2, c, d ); } + +#define stbir__store_output() \ + stbir__simdf_0123ABCDto3ABx( c, tot0, tot1 ); \ + stbir__simdf_0123ABCDto23Ax( cs, tot1, tot2 ); \ + stbir__simdf_0123to1230( tot2, tot2 ); \ + stbir__simdf_add( tot0, tot0, cs ); \ + stbir__simdf_add( c, c, tot2 ); \ + stbir__simdf_add( tot0, tot0, c ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; \ + if ( output < output_end ) \ + { \ + stbir__simdf_store( output-3, tot0 ); \ + continue; \ + } \ + stbir__simdf_0123to2301( tot1, tot0 ); \ + stbir__simdf_store2( output-3, tot0 ); \ + stbir__simdf_store1( output+2-3, tot1 ); \ + break; + +#endif + +#else + +#define stbir__1_coeff_only() \ + float tot0, tot1, tot2, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; + +#define stbir__2_coeff_only() \ + float tot0, tot1, tot2, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + c = hc[1]; \ + tot0 += decode[3]*c; \ + tot1 += decode[4]*c; \ + tot2 += decode[5]*c; + +#define stbir__3_coeff_only() \ + float tot0, tot1, tot2, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + c = hc[1]; \ + tot0 += decode[3]*c; \ + tot1 += decode[4]*c; \ + tot2 += decode[5]*c; \ + c = hc[2]; \ + tot0 += decode[6]*c; \ + tot1 += decode[7]*c; \ + tot2 += decode[8]*c; + +#define stbir__store_output_tiny() \ + output[0] = tot0; \ + output[1] = tot1; \ + output[2] = tot2; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; + +#define stbir__4_coeff_start() \ + float tota0,tota1,tota2,totb0,totb1,totb2,totc0,totc1,totc2,totd0,totd1,totd2,c; \ + c = hc[0]; \ + tota0 = decode[0]*c; \ + tota1 = decode[1]*c; \ + tota2 = decode[2]*c; \ + c = hc[1]; \ + totb0 = decode[3]*c; \ + totb1 = decode[4]*c; \ + totb2 = decode[5]*c; \ + c = hc[2]; \ + totc0 = decode[6]*c; \ + totc1 = decode[7]*c; \ + totc2 = decode[8]*c; \ + c = hc[3]; \ + totd0 = decode[9]*c; \ + totd1 = decode[10]*c; \ + totd2 = decode[11]*c; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*3]*c; \ + tota1 += decode[1+(ofs)*3]*c; \ + tota2 += decode[2+(ofs)*3]*c; \ + c = hc[1+(ofs)]; \ + totb0 += decode[3+(ofs)*3]*c; \ + totb1 += decode[4+(ofs)*3]*c; \ + totb2 += decode[5+(ofs)*3]*c; \ + c = hc[2+(ofs)]; \ + totc0 += decode[6+(ofs)*3]*c; \ + totc1 += decode[7+(ofs)*3]*c; \ + totc2 += decode[8+(ofs)*3]*c; \ + c = hc[3+(ofs)]; \ + totd0 += decode[9+(ofs)*3]*c; \ + totd1 += decode[10+(ofs)*3]*c; \ + totd2 += decode[11+(ofs)*3]*c; + +#define stbir__1_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*3]*c; \ + tota1 += decode[1+(ofs)*3]*c; \ + tota2 += decode[2+(ofs)*3]*c; + +#define stbir__2_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*3]*c; \ + tota1 += decode[1+(ofs)*3]*c; \ + tota2 += decode[2+(ofs)*3]*c; \ + c = hc[1+(ofs)]; \ + totb0 += decode[3+(ofs)*3]*c; \ + totb1 += decode[4+(ofs)*3]*c; \ + totb2 += decode[5+(ofs)*3]*c; \ + +#define stbir__3_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*3]*c; \ + tota1 += decode[1+(ofs)*3]*c; \ + tota2 += decode[2+(ofs)*3]*c; \ + c = hc[1+(ofs)]; \ + totb0 += decode[3+(ofs)*3]*c; \ + totb1 += decode[4+(ofs)*3]*c; \ + totb2 += decode[5+(ofs)*3]*c; \ + c = hc[2+(ofs)]; \ + totc0 += decode[6+(ofs)*3]*c; \ + totc1 += decode[7+(ofs)*3]*c; \ + totc2 += decode[8+(ofs)*3]*c; + +#define stbir__store_output() \ + output[0] = (tota0+totc0)+(totb0+totd0); \ + output[1] = (tota1+totc1)+(totb1+totd1); \ + output[2] = (tota2+totc2)+(totb2+totd2); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; + +#endif + +#define STBIR__horizontal_channels 3 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + +//================= +// Do 4 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc ); \ + stbir__simdf_0123to0000( c, c ); \ + stbir__simdf_mult_mem( tot, c, decode ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+4 ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+8 ); + +#define stbir__store_output_tiny() \ + stbir__simdf_store( output, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#ifdef STBIR_SIMD8 + +#define stbir__4_coeff_start() \ + stbir__simdf8 tot0,c,cs; stbir__simdf t; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_mult_mem( tot0, c, decode ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+8 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4+8 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1rep4( t, hc + (ofs) ); \ + stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*4 ); + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) - 2 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); + + #define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf8_0123to2222( t, cs ); \ + stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*4+8 ); + +#define stbir__store_output() \ + stbir__simdf8_add4halves( t, stbir__if_simdf8_cast_to_simdf4(tot0), tot0 ); \ + stbir__simdf_store( output, t ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#else + +#define stbir__4_coeff_start() \ + stbir__simdf tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_mult_mem( tot1, c, decode+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+8 ); \ + stbir__simdf_0123to3333( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+12 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4+8 ); \ + stbir__simdf_0123to3333( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+12 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, c ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+4 ); + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4+8 ); + +#define stbir__store_output() \ + stbir__simdf_add( tot0, tot0, tot1 ); \ + stbir__simdf_store( output, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#endif + +#else + +#define stbir__1_coeff_only() \ + float p0,p1,p2,p3,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + p0 = decode[0] * c; \ + p1 = decode[1] * c; \ + p2 = decode[2] * c; \ + p3 = decode[3] * c; + +#define stbir__2_coeff_only() \ + float p0,p1,p2,p3,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + p0 = decode[0] * c; \ + p1 = decode[1] * c; \ + p2 = decode[2] * c; \ + p3 = decode[3] * c; \ + c = hc[1]; \ + p0 += decode[4] * c; \ + p1 += decode[5] * c; \ + p2 += decode[6] * c; \ + p3 += decode[7] * c; + +#define stbir__3_coeff_only() \ + float p0,p1,p2,p3,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + p0 = decode[0] * c; \ + p1 = decode[1] * c; \ + p2 = decode[2] * c; \ + p3 = decode[3] * c; \ + c = hc[1]; \ + p0 += decode[4] * c; \ + p1 += decode[5] * c; \ + p2 += decode[6] * c; \ + p3 += decode[7] * c; \ + c = hc[2]; \ + p0 += decode[8] * c; \ + p1 += decode[9] * c; \ + p2 += decode[10] * c; \ + p3 += decode[11] * c; + +#define stbir__store_output_tiny() \ + output[0] = p0; \ + output[1] = p1; \ + output[2] = p2; \ + output[3] = p3; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#define stbir__4_coeff_start() \ + float x0,x1,x2,x3,y0,y1,y2,y3,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + x0 = decode[0] * c; \ + x1 = decode[1] * c; \ + x2 = decode[2] * c; \ + x3 = decode[3] * c; \ + c = hc[1]; \ + y0 = decode[4] * c; \ + y1 = decode[5] * c; \ + y2 = decode[6] * c; \ + y3 = decode[7] * c; \ + c = hc[2]; \ + x0 += decode[8] * c; \ + x1 += decode[9] * c; \ + x2 += decode[10] * c; \ + x3 += decode[11] * c; \ + c = hc[3]; \ + y0 += decode[12] * c; \ + y1 += decode[13] * c; \ + y2 += decode[14] * c; \ + y3 += decode[15] * c; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*4] * c; \ + x1 += decode[1+(ofs)*4] * c; \ + x2 += decode[2+(ofs)*4] * c; \ + x3 += decode[3+(ofs)*4] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[4+(ofs)*4] * c; \ + y1 += decode[5+(ofs)*4] * c; \ + y2 += decode[6+(ofs)*4] * c; \ + y3 += decode[7+(ofs)*4] * c; \ + c = hc[2+(ofs)]; \ + x0 += decode[8+(ofs)*4] * c; \ + x1 += decode[9+(ofs)*4] * c; \ + x2 += decode[10+(ofs)*4] * c; \ + x3 += decode[11+(ofs)*4] * c; \ + c = hc[3+(ofs)]; \ + y0 += decode[12+(ofs)*4] * c; \ + y1 += decode[13+(ofs)*4] * c; \ + y2 += decode[14+(ofs)*4] * c; \ + y3 += decode[15+(ofs)*4] * c; + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*4] * c; \ + x1 += decode[1+(ofs)*4] * c; \ + x2 += decode[2+(ofs)*4] * c; \ + x3 += decode[3+(ofs)*4] * c; + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*4] * c; \ + x1 += decode[1+(ofs)*4] * c; \ + x2 += decode[2+(ofs)*4] * c; \ + x3 += decode[3+(ofs)*4] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[4+(ofs)*4] * c; \ + y1 += decode[5+(ofs)*4] * c; \ + y2 += decode[6+(ofs)*4] * c; \ + y3 += decode[7+(ofs)*4] * c; + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*4] * c; \ + x1 += decode[1+(ofs)*4] * c; \ + x2 += decode[2+(ofs)*4] * c; \ + x3 += decode[3+(ofs)*4] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[4+(ofs)*4] * c; \ + y1 += decode[5+(ofs)*4] * c; \ + y2 += decode[6+(ofs)*4] * c; \ + y3 += decode[7+(ofs)*4] * c; \ + c = hc[2+(ofs)]; \ + x0 += decode[8+(ofs)*4] * c; \ + x1 += decode[9+(ofs)*4] * c; \ + x2 += decode[10+(ofs)*4] * c; \ + x3 += decode[11+(ofs)*4] * c; + +#define stbir__store_output() \ + output[0] = x0 + y0; \ + output[1] = x1 + y1; \ + output[2] = x2 + y2; \ + output[3] = x3 + y3; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#endif + +#define STBIR__horizontal_channels 4 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + + + +//================= +// Do 7 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot0,tot1,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc ); \ + stbir__simdf_0123to0000( c, c ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_mult_mem( tot1, c, decode+3 ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_mult_mem( tot1, c, decode+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c,decode+10 ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_mult_mem( tot1, c, decode+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+10 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+14 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+17 ); + +#define stbir__store_output_tiny() \ + stbir__simdf_store( output+3, tot1 ); \ + stbir__simdf_store( output, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; + +#ifdef STBIR_SIMD8 + +#define stbir__4_coeff_start() \ + stbir__simdf8 tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc ); \ + stbir__simdf8_0123to00000000( c, cs ); \ + stbir__simdf8_mult_mem( tot0, c, decode ); \ + stbir__simdf8_0123to11111111( c, cs ); \ + stbir__simdf8_mult_mem( tot1, c, decode+7 ); \ + stbir__simdf8_0123to22222222( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+14 ); \ + stbir__simdf8_0123to33333333( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+21 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00000000( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf8_0123to11111111( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+7 ); \ + stbir__simdf8_0123to22222222( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); \ + stbir__simdf8_0123to33333333( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+21 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load1b( c, hc + (ofs) ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load1b( c, hc + (ofs) ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf8_load1b( c, hc + (ofs)+1 ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+7 ); + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00000000( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf8_0123to11111111( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+7 ); \ + stbir__simdf8_0123to22222222( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); + +#define stbir__store_output() \ + stbir__simdf8_add( tot0, tot0, tot1 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; \ + if ( output < output_end ) \ + { \ + stbir__simdf8_store( output-7, tot0 ); \ + continue; \ + } \ + stbir__simdf_store( output-7+3, stbir__simdf_swiz(stbir__simdf8_gettop4(tot0),0,0,1,2) ); \ + stbir__simdf_store( output-7, stbir__if_simdf8_cast_to_simdf4(tot0) ); \ + break; + +#else + +#define stbir__4_coeff_start() \ + stbir__simdf tot0,tot1,tot2,tot3,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_mult_mem( tot1, c, decode+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_mult_mem( tot2, c, decode+7 ); \ + stbir__simdf_mult_mem( tot3, c, decode+10 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+14 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+17 ); \ + stbir__simdf_0123to3333( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+21 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+24 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*7+7 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+10 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+17 ); \ + stbir__simdf_0123to3333( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*7+21 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+24 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, c ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+3 ); \ + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*7+7 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+10 ); + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*7+7 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+10 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+17 ); + +#define stbir__store_output() \ + stbir__simdf_add( tot0, tot0, tot2 ); \ + stbir__simdf_add( tot1, tot1, tot3 ); \ + stbir__simdf_store( output+3, tot1 ); \ + stbir__simdf_store( output, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; + +#endif + +#else + +#define stbir__1_coeff_only() \ + float tot0, tot1, tot2, tot3, tot4, tot5, tot6, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + tot3 = decode[3]*c; \ + tot4 = decode[4]*c; \ + tot5 = decode[5]*c; \ + tot6 = decode[6]*c; + +#define stbir__2_coeff_only() \ + float tot0, tot1, tot2, tot3, tot4, tot5, tot6, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + tot3 = decode[3]*c; \ + tot4 = decode[4]*c; \ + tot5 = decode[5]*c; \ + tot6 = decode[6]*c; \ + c = hc[1]; \ + tot0 += decode[7]*c; \ + tot1 += decode[8]*c; \ + tot2 += decode[9]*c; \ + tot3 += decode[10]*c; \ + tot4 += decode[11]*c; \ + tot5 += decode[12]*c; \ + tot6 += decode[13]*c; \ + +#define stbir__3_coeff_only() \ + float tot0, tot1, tot2, tot3, tot4, tot5, tot6, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + tot3 = decode[3]*c; \ + tot4 = decode[4]*c; \ + tot5 = decode[5]*c; \ + tot6 = decode[6]*c; \ + c = hc[1]; \ + tot0 += decode[7]*c; \ + tot1 += decode[8]*c; \ + tot2 += decode[9]*c; \ + tot3 += decode[10]*c; \ + tot4 += decode[11]*c; \ + tot5 += decode[12]*c; \ + tot6 += decode[13]*c; \ + c = hc[2]; \ + tot0 += decode[14]*c; \ + tot1 += decode[15]*c; \ + tot2 += decode[16]*c; \ + tot3 += decode[17]*c; \ + tot4 += decode[18]*c; \ + tot5 += decode[19]*c; \ + tot6 += decode[20]*c; \ + +#define stbir__store_output_tiny() \ + output[0] = tot0; \ + output[1] = tot1; \ + output[2] = tot2; \ + output[3] = tot3; \ + output[4] = tot4; \ + output[5] = tot5; \ + output[6] = tot6; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; + +#define stbir__4_coeff_start() \ + float x0,x1,x2,x3,x4,x5,x6,y0,y1,y2,y3,y4,y5,y6,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + x0 = decode[0] * c; \ + x1 = decode[1] * c; \ + x2 = decode[2] * c; \ + x3 = decode[3] * c; \ + x4 = decode[4] * c; \ + x5 = decode[5] * c; \ + x6 = decode[6] * c; \ + c = hc[1]; \ + y0 = decode[7] * c; \ + y1 = decode[8] * c; \ + y2 = decode[9] * c; \ + y3 = decode[10] * c; \ + y4 = decode[11] * c; \ + y5 = decode[12] * c; \ + y6 = decode[13] * c; \ + c = hc[2]; \ + x0 += decode[14] * c; \ + x1 += decode[15] * c; \ + x2 += decode[16] * c; \ + x3 += decode[17] * c; \ + x4 += decode[18] * c; \ + x5 += decode[19] * c; \ + x6 += decode[20] * c; \ + c = hc[3]; \ + y0 += decode[21] * c; \ + y1 += decode[22] * c; \ + y2 += decode[23] * c; \ + y3 += decode[24] * c; \ + y4 += decode[25] * c; \ + y5 += decode[26] * c; \ + y6 += decode[27] * c; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*7] * c; \ + x1 += decode[1+(ofs)*7] * c; \ + x2 += decode[2+(ofs)*7] * c; \ + x3 += decode[3+(ofs)*7] * c; \ + x4 += decode[4+(ofs)*7] * c; \ + x5 += decode[5+(ofs)*7] * c; \ + x6 += decode[6+(ofs)*7] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[7+(ofs)*7] * c; \ + y1 += decode[8+(ofs)*7] * c; \ + y2 += decode[9+(ofs)*7] * c; \ + y3 += decode[10+(ofs)*7] * c; \ + y4 += decode[11+(ofs)*7] * c; \ + y5 += decode[12+(ofs)*7] * c; \ + y6 += decode[13+(ofs)*7] * c; \ + c = hc[2+(ofs)]; \ + x0 += decode[14+(ofs)*7] * c; \ + x1 += decode[15+(ofs)*7] * c; \ + x2 += decode[16+(ofs)*7] * c; \ + x3 += decode[17+(ofs)*7] * c; \ + x4 += decode[18+(ofs)*7] * c; \ + x5 += decode[19+(ofs)*7] * c; \ + x6 += decode[20+(ofs)*7] * c; \ + c = hc[3+(ofs)]; \ + y0 += decode[21+(ofs)*7] * c; \ + y1 += decode[22+(ofs)*7] * c; \ + y2 += decode[23+(ofs)*7] * c; \ + y3 += decode[24+(ofs)*7] * c; \ + y4 += decode[25+(ofs)*7] * c; \ + y5 += decode[26+(ofs)*7] * c; \ + y6 += decode[27+(ofs)*7] * c; + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*7] * c; \ + x1 += decode[1+(ofs)*7] * c; \ + x2 += decode[2+(ofs)*7] * c; \ + x3 += decode[3+(ofs)*7] * c; \ + x4 += decode[4+(ofs)*7] * c; \ + x5 += decode[5+(ofs)*7] * c; \ + x6 += decode[6+(ofs)*7] * c; \ + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*7] * c; \ + x1 += decode[1+(ofs)*7] * c; \ + x2 += decode[2+(ofs)*7] * c; \ + x3 += decode[3+(ofs)*7] * c; \ + x4 += decode[4+(ofs)*7] * c; \ + x5 += decode[5+(ofs)*7] * c; \ + x6 += decode[6+(ofs)*7] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[7+(ofs)*7] * c; \ + y1 += decode[8+(ofs)*7] * c; \ + y2 += decode[9+(ofs)*7] * c; \ + y3 += decode[10+(ofs)*7] * c; \ + y4 += decode[11+(ofs)*7] * c; \ + y5 += decode[12+(ofs)*7] * c; \ + y6 += decode[13+(ofs)*7] * c; \ + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*7] * c; \ + x1 += decode[1+(ofs)*7] * c; \ + x2 += decode[2+(ofs)*7] * c; \ + x3 += decode[3+(ofs)*7] * c; \ + x4 += decode[4+(ofs)*7] * c; \ + x5 += decode[5+(ofs)*7] * c; \ + x6 += decode[6+(ofs)*7] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[7+(ofs)*7] * c; \ + y1 += decode[8+(ofs)*7] * c; \ + y2 += decode[9+(ofs)*7] * c; \ + y3 += decode[10+(ofs)*7] * c; \ + y4 += decode[11+(ofs)*7] * c; \ + y5 += decode[12+(ofs)*7] * c; \ + y6 += decode[13+(ofs)*7] * c; \ + c = hc[2+(ofs)]; \ + x0 += decode[14+(ofs)*7] * c; \ + x1 += decode[15+(ofs)*7] * c; \ + x2 += decode[16+(ofs)*7] * c; \ + x3 += decode[17+(ofs)*7] * c; \ + x4 += decode[18+(ofs)*7] * c; \ + x5 += decode[19+(ofs)*7] * c; \ + x6 += decode[20+(ofs)*7] * c; \ + +#define stbir__store_output() \ + output[0] = x0 + y0; \ + output[1] = x1 + y1; \ + output[2] = x2 + y2; \ + output[3] = x3 + y3; \ + output[4] = x4 + y4; \ + output[5] = x5 + y5; \ + output[6] = x6 + y6; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; + +#endif + +#define STBIR__horizontal_channels 7 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + + +// include all of the vertical resamplers (both scatter and gather versions) + +#define STBIR__vertical_channels 1 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 1 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 2 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 2 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 3 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 3 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 4 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 4 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 5 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 5 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 6 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 6 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 7 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 7 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 8 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 8 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +typedef void STBIR_VERTICAL_GATHERFUNC( float * output, float const * coeffs, float const ** inputs, float const * input0_end ); + +static STBIR_VERTICAL_GATHERFUNC * stbir__vertical_gathers[ 8 ] = +{ + stbir__vertical_gather_with_1_coeffs,stbir__vertical_gather_with_2_coeffs,stbir__vertical_gather_with_3_coeffs,stbir__vertical_gather_with_4_coeffs,stbir__vertical_gather_with_5_coeffs,stbir__vertical_gather_with_6_coeffs,stbir__vertical_gather_with_7_coeffs,stbir__vertical_gather_with_8_coeffs +}; + +static STBIR_VERTICAL_GATHERFUNC * stbir__vertical_gathers_continues[ 8 ] = +{ + stbir__vertical_gather_with_1_coeffs_cont,stbir__vertical_gather_with_2_coeffs_cont,stbir__vertical_gather_with_3_coeffs_cont,stbir__vertical_gather_with_4_coeffs_cont,stbir__vertical_gather_with_5_coeffs_cont,stbir__vertical_gather_with_6_coeffs_cont,stbir__vertical_gather_with_7_coeffs_cont,stbir__vertical_gather_with_8_coeffs_cont +}; + +typedef void STBIR_VERTICAL_SCATTERFUNC( float ** outputs, float const * coeffs, float const * input, float const * input_end ); + +static STBIR_VERTICAL_SCATTERFUNC * stbir__vertical_scatter_sets[ 8 ] = +{ + stbir__vertical_scatter_with_1_coeffs,stbir__vertical_scatter_with_2_coeffs,stbir__vertical_scatter_with_3_coeffs,stbir__vertical_scatter_with_4_coeffs,stbir__vertical_scatter_with_5_coeffs,stbir__vertical_scatter_with_6_coeffs,stbir__vertical_scatter_with_7_coeffs,stbir__vertical_scatter_with_8_coeffs +}; + +static STBIR_VERTICAL_SCATTERFUNC * stbir__vertical_scatter_blends[ 8 ] = +{ + stbir__vertical_scatter_with_1_coeffs_cont,stbir__vertical_scatter_with_2_coeffs_cont,stbir__vertical_scatter_with_3_coeffs_cont,stbir__vertical_scatter_with_4_coeffs_cont,stbir__vertical_scatter_with_5_coeffs_cont,stbir__vertical_scatter_with_6_coeffs_cont,stbir__vertical_scatter_with_7_coeffs_cont,stbir__vertical_scatter_with_8_coeffs_cont +}; + + +static void stbir__encode_scanline( stbir__info const * stbir_info, void *output_buffer_data, float * encode_buffer, int row STBIR_ONLY_PROFILE_GET_SPLIT_INFO ) +{ + int num_pixels = stbir_info->horizontal.scale_info.output_sub_size; + int channels = stbir_info->channels; + int width_times_channels = num_pixels * channels; + void * output_buffer; + + // un-alpha weight if we need to + if ( stbir_info->alpha_unweight ) + { + STBIR_PROFILE_START( unalpha ); + stbir_info->alpha_unweight( encode_buffer, width_times_channels ); + STBIR_PROFILE_END( unalpha ); + } + + // write directly into output by default + output_buffer = output_buffer_data; + + // if we have an output callback, we first convert the decode buffer in place (and then hand that to the callback) + if ( stbir_info->out_pixels_cb ) + output_buffer = encode_buffer; + + STBIR_PROFILE_START( encode ); + // convert into the output buffer + stbir_info->encode_pixels( output_buffer, width_times_channels, encode_buffer ); + STBIR_PROFILE_END( encode ); + + // if we have an output callback, call it to send the data + if ( stbir_info->out_pixels_cb ) + stbir_info->out_pixels_cb( output_buffer, num_pixels, row, stbir_info->user_data ); +} + + +// Get the ring buffer pointer for an index +static float* stbir__get_ring_buffer_entry(stbir__info const * stbir_info, stbir__per_split_info const * split_info, int index ) +{ + STBIR_ASSERT( index < stbir_info->ring_buffer_num_entries ); + + #ifdef STBIR__SEPARATE_ALLOCATIONS + return split_info->ring_buffers[ index ]; + #else + return (float*) ( ( (char*) split_info->ring_buffer ) + ( index * stbir_info->ring_buffer_length_bytes ) ); + #endif +} + +// Get the specified scan line from the ring buffer +static float* stbir__get_ring_buffer_scanline(stbir__info const * stbir_info, stbir__per_split_info const * split_info, int get_scanline) +{ + int ring_buffer_index = (split_info->ring_buffer_begin_index + (get_scanline - split_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries; + return stbir__get_ring_buffer_entry( stbir_info, split_info, ring_buffer_index ); +} + +static void stbir__resample_horizontal_gather(stbir__info const * stbir_info, float* output_buffer, float const * input_buffer STBIR_ONLY_PROFILE_GET_SPLIT_INFO ) +{ + float const * decode_buffer = input_buffer - ( stbir_info->scanline_extents.conservative.n0 * stbir_info->effective_channels ); + + STBIR_PROFILE_START( horizontal ); + if ( ( stbir_info->horizontal.filter_enum == STBIR_FILTER_POINT_SAMPLE ) && ( stbir_info->horizontal.scale_info.scale == 1.0f ) ) + STBIR_MEMCPY( output_buffer, input_buffer, stbir_info->horizontal.scale_info.output_sub_size * sizeof( float ) * stbir_info->effective_channels ); + else + stbir_info->horizontal_gather_channels( output_buffer, stbir_info->horizontal.scale_info.output_sub_size, decode_buffer, stbir_info->horizontal.contributors, stbir_info->horizontal.coefficients, stbir_info->horizontal.coefficient_width ); + STBIR_PROFILE_END( horizontal ); +} + +static void stbir__resample_vertical_gather(stbir__info const * stbir_info, stbir__per_split_info* split_info, int n, int contrib_n0, int contrib_n1, float const * vertical_coefficients ) +{ + float* encode_buffer = split_info->vertical_buffer; + float* decode_buffer = split_info->decode_buffer; + int vertical_first = stbir_info->vertical_first; + int width = (vertical_first) ? ( stbir_info->scanline_extents.conservative.n1-stbir_info->scanline_extents.conservative.n0+1 ) : stbir_info->horizontal.scale_info.output_sub_size; + int width_times_channels = stbir_info->effective_channels * width; + + STBIR_ASSERT( stbir_info->vertical.is_gather ); + + // loop over the contributing scanlines and scale into the buffer + STBIR_PROFILE_START( vertical ); + { + int k = 0, total = contrib_n1 - contrib_n0 + 1; + STBIR_ASSERT( total > 0 ); + do { + float const * inputs[8]; + int i, cnt = total; if ( cnt > 8 ) cnt = 8; + for( i = 0 ; i < cnt ; i++ ) + inputs[ i ] = stbir__get_ring_buffer_scanline(stbir_info, split_info, k+i+contrib_n0 ); + + // call the N scanlines at a time function (up to 8 scanlines of blending at once) + ((k==0)?stbir__vertical_gathers:stbir__vertical_gathers_continues)[cnt-1]( (vertical_first) ? decode_buffer : encode_buffer, vertical_coefficients + k, inputs, inputs[0] + width_times_channels ); + k += cnt; + total -= cnt; + } while ( total ); + } + STBIR_PROFILE_END( vertical ); + + if ( vertical_first ) + { + // Now resample the gathered vertical data in the horizontal axis into the encode buffer + stbir__resample_horizontal_gather(stbir_info, encode_buffer, decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + } + + stbir__encode_scanline( stbir_info, ( (char *) stbir_info->output_data ) + ((ptrdiff_t)n * (ptrdiff_t)stbir_info->output_stride_bytes), + encode_buffer, n STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); +} + +static void stbir__decode_and_resample_for_vertical_gather_loop(stbir__info const * stbir_info, stbir__per_split_info* split_info, int n) +{ + int ring_buffer_index; + float* ring_buffer; + + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline( stbir_info, n, split_info->decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // update new end scanline + split_info->ring_buffer_last_scanline = n; + + // get ring buffer + ring_buffer_index = (split_info->ring_buffer_begin_index + (split_info->ring_buffer_last_scanline - split_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries; + ring_buffer = stbir__get_ring_buffer_entry(stbir_info, split_info, ring_buffer_index); + + // Now resample it into the ring buffer. + stbir__resample_horizontal_gather( stbir_info, ring_buffer, split_info->decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // Now it's sitting in the ring buffer ready to be used as source for the vertical sampling. +} + +static void stbir__vertical_gather_loop( stbir__info const * stbir_info, stbir__per_split_info* split_info, int split_count ) +{ + int y, start_output_y, end_output_y; + stbir__contributors* vertical_contributors = stbir_info->vertical.contributors; + float const * vertical_coefficients = stbir_info->vertical.coefficients; + + STBIR_ASSERT( stbir_info->vertical.is_gather ); + + start_output_y = split_info->start_output_y; + end_output_y = split_info[split_count-1].end_output_y; + + vertical_contributors += start_output_y; + vertical_coefficients += start_output_y * stbir_info->vertical.coefficient_width; + + // initialize the ring buffer for gathering + split_info->ring_buffer_begin_index = 0; + split_info->ring_buffer_first_scanline = stbir_info->vertical.extent_info.lowest; + split_info->ring_buffer_last_scanline = split_info->ring_buffer_first_scanline - 1; // means "empty" + + for (y = start_output_y; y < end_output_y; y++) + { + int in_first_scanline, in_last_scanline; + + in_first_scanline = vertical_contributors->n0; + in_last_scanline = vertical_contributors->n1; + + // make sure the indexing hasn't broken + STBIR_ASSERT( in_first_scanline >= split_info->ring_buffer_first_scanline ); + + // Load in new scanlines + while (in_last_scanline > split_info->ring_buffer_last_scanline) + { + STBIR_ASSERT( ( split_info->ring_buffer_last_scanline - split_info->ring_buffer_first_scanline + 1 ) <= stbir_info->ring_buffer_num_entries ); + + // make sure there was room in the ring buffer when we add new scanlines + if ( ( split_info->ring_buffer_last_scanline - split_info->ring_buffer_first_scanline + 1 ) == stbir_info->ring_buffer_num_entries ) + { + split_info->ring_buffer_first_scanline++; + split_info->ring_buffer_begin_index++; + } + + if ( stbir_info->vertical_first ) + { + float * ring_buffer = stbir__get_ring_buffer_scanline( stbir_info, split_info, ++split_info->ring_buffer_last_scanline ); + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline( stbir_info, split_info->ring_buffer_last_scanline, ring_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + } + else + { + stbir__decode_and_resample_for_vertical_gather_loop(stbir_info, split_info, split_info->ring_buffer_last_scanline + 1); + } + } + + // Now all buffers should be ready to write a row of vertical sampling, so do it. + stbir__resample_vertical_gather(stbir_info, split_info, y, in_first_scanline, in_last_scanline, vertical_coefficients ); + + ++vertical_contributors; + vertical_coefficients += stbir_info->vertical.coefficient_width; + } +} + +#define STBIR__FLOAT_EMPTY_MARKER 3.0e+38F +#define STBIR__FLOAT_BUFFER_IS_EMPTY(ptr) ((ptr)[0]==STBIR__FLOAT_EMPTY_MARKER) + +static void stbir__encode_first_scanline_from_scatter(stbir__info const * stbir_info, stbir__per_split_info* split_info) +{ + // evict a scanline out into the output buffer + float* ring_buffer_entry = stbir__get_ring_buffer_entry(stbir_info, split_info, split_info->ring_buffer_begin_index ); + + // dump the scanline out + stbir__encode_scanline( stbir_info, ( (char *)stbir_info->output_data ) + ( (ptrdiff_t)split_info->ring_buffer_first_scanline * (ptrdiff_t)stbir_info->output_stride_bytes ), ring_buffer_entry, split_info->ring_buffer_first_scanline STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // mark it as empty + ring_buffer_entry[ 0 ] = STBIR__FLOAT_EMPTY_MARKER; + + // advance the first scanline + split_info->ring_buffer_first_scanline++; + if ( ++split_info->ring_buffer_begin_index == stbir_info->ring_buffer_num_entries ) + split_info->ring_buffer_begin_index = 0; +} + +static void stbir__horizontal_resample_and_encode_first_scanline_from_scatter(stbir__info const * stbir_info, stbir__per_split_info* split_info) +{ + // evict a scanline out into the output buffer + + float* ring_buffer_entry = stbir__get_ring_buffer_entry(stbir_info, split_info, split_info->ring_buffer_begin_index ); + + // Now resample it into the buffer. + stbir__resample_horizontal_gather( stbir_info, split_info->vertical_buffer, ring_buffer_entry STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // dump the scanline out + stbir__encode_scanline( stbir_info, ( (char *)stbir_info->output_data ) + ( (ptrdiff_t)split_info->ring_buffer_first_scanline * (ptrdiff_t)stbir_info->output_stride_bytes ), split_info->vertical_buffer, split_info->ring_buffer_first_scanline STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // mark it as empty + ring_buffer_entry[ 0 ] = STBIR__FLOAT_EMPTY_MARKER; + + // advance the first scanline + split_info->ring_buffer_first_scanline++; + if ( ++split_info->ring_buffer_begin_index == stbir_info->ring_buffer_num_entries ) + split_info->ring_buffer_begin_index = 0; +} + +static void stbir__resample_vertical_scatter(stbir__info const * stbir_info, stbir__per_split_info* split_info, int n0, int n1, float const * vertical_coefficients, float const * vertical_buffer, float const * vertical_buffer_end ) +{ + STBIR_ASSERT( !stbir_info->vertical.is_gather ); + + STBIR_PROFILE_START( vertical ); + { + int k = 0, total = n1 - n0 + 1; + STBIR_ASSERT( total > 0 ); + do { + float * outputs[8]; + int i, n = total; if ( n > 8 ) n = 8; + for( i = 0 ; i < n ; i++ ) + { + outputs[ i ] = stbir__get_ring_buffer_scanline(stbir_info, split_info, k+i+n0 ); + if ( ( i ) && ( STBIR__FLOAT_BUFFER_IS_EMPTY( outputs[i] ) != STBIR__FLOAT_BUFFER_IS_EMPTY( outputs[0] ) ) ) // make sure runs are of the same type + { + n = i; + break; + } + } + // call the scatter to N scanlines at a time function (up to 8 scanlines of scattering at once) + ((STBIR__FLOAT_BUFFER_IS_EMPTY( outputs[0] ))?stbir__vertical_scatter_sets:stbir__vertical_scatter_blends)[n-1]( outputs, vertical_coefficients + k, vertical_buffer, vertical_buffer_end ); + k += n; + total -= n; + } while ( total ); + } + + STBIR_PROFILE_END( vertical ); +} + +typedef void stbir__handle_scanline_for_scatter_func(stbir__info const * stbir_info, stbir__per_split_info* split_info); + +static void stbir__vertical_scatter_loop( stbir__info const * stbir_info, stbir__per_split_info* split_info, int split_count ) +{ + int y, start_output_y, end_output_y, start_input_y, end_input_y; + stbir__contributors* vertical_contributors = stbir_info->vertical.contributors; + float const * vertical_coefficients = stbir_info->vertical.coefficients; + stbir__handle_scanline_for_scatter_func * handle_scanline_for_scatter; + void * scanline_scatter_buffer; + void * scanline_scatter_buffer_end; + int on_first_input_y, last_input_y; + + STBIR_ASSERT( !stbir_info->vertical.is_gather ); + + start_output_y = split_info->start_output_y; + end_output_y = split_info[split_count-1].end_output_y; // may do multiple split counts + + start_input_y = split_info->start_input_y; + end_input_y = split_info[split_count-1].end_input_y; + + // adjust for starting offset start_input_y + y = start_input_y + stbir_info->vertical.filter_pixel_margin; + vertical_contributors += y ; + vertical_coefficients += stbir_info->vertical.coefficient_width * y; + + if ( stbir_info->vertical_first ) + { + handle_scanline_for_scatter = stbir__horizontal_resample_and_encode_first_scanline_from_scatter; + scanline_scatter_buffer = split_info->decode_buffer; + scanline_scatter_buffer_end = ( (char*) scanline_scatter_buffer ) + sizeof( float ) * stbir_info->effective_channels * (stbir_info->scanline_extents.conservative.n1-stbir_info->scanline_extents.conservative.n0+1); + } + else + { + handle_scanline_for_scatter = stbir__encode_first_scanline_from_scatter; + scanline_scatter_buffer = split_info->vertical_buffer; + scanline_scatter_buffer_end = ( (char*) scanline_scatter_buffer ) + sizeof( float ) * stbir_info->effective_channels * stbir_info->horizontal.scale_info.output_sub_size; + } + + // initialize the ring buffer for scattering + split_info->ring_buffer_first_scanline = start_output_y; + split_info->ring_buffer_last_scanline = -1; + split_info->ring_buffer_begin_index = -1; + + // mark all the buffers as empty to start + for( y = 0 ; y < stbir_info->ring_buffer_num_entries ; y++ ) + stbir__get_ring_buffer_entry( stbir_info, split_info, y )[0] = STBIR__FLOAT_EMPTY_MARKER; // only used on scatter + + // do the loop in input space + on_first_input_y = 1; last_input_y = start_input_y; + for (y = start_input_y ; y < end_input_y; y++) + { + int out_first_scanline, out_last_scanline; + + out_first_scanline = vertical_contributors->n0; + out_last_scanline = vertical_contributors->n1; + + STBIR_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if ( ( out_last_scanline >= out_first_scanline ) && ( ( ( out_first_scanline >= start_output_y ) && ( out_first_scanline < end_output_y ) ) || ( ( out_last_scanline >= start_output_y ) && ( out_last_scanline < end_output_y ) ) ) ) + { + float const * vc = vertical_coefficients; + + // keep track of the range actually seen for the next resize + last_input_y = y; + if ( ( on_first_input_y ) && ( y > start_input_y ) ) + split_info->start_input_y = y; + on_first_input_y = 0; + + // clip the region + if ( out_first_scanline < start_output_y ) + { + vc += start_output_y - out_first_scanline; + out_first_scanline = start_output_y; + } + + if ( out_last_scanline >= end_output_y ) + out_last_scanline = end_output_y - 1; + + // if very first scanline, init the index + if (split_info->ring_buffer_begin_index < 0) + split_info->ring_buffer_begin_index = out_first_scanline - start_output_y; + + STBIR_ASSERT( split_info->ring_buffer_begin_index <= out_first_scanline ); + + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline( stbir_info, y, split_info->decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // When horizontal first, we resample horizontally into the vertical buffer before we scatter it out + if ( !stbir_info->vertical_first ) + stbir__resample_horizontal_gather( stbir_info, split_info->vertical_buffer, split_info->decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // Now it's sitting in the buffer ready to be distributed into the ring buffers. + + // evict from the ringbuffer, if we need are full + if ( ( ( split_info->ring_buffer_last_scanline - split_info->ring_buffer_first_scanline + 1 ) == stbir_info->ring_buffer_num_entries ) && + ( out_last_scanline > split_info->ring_buffer_last_scanline ) ) + handle_scanline_for_scatter( stbir_info, split_info ); + + // Now the horizontal buffer is ready to write to all ring buffer rows, so do it. + stbir__resample_vertical_scatter(stbir_info, split_info, out_first_scanline, out_last_scanline, vc, (float*)scanline_scatter_buffer, (float*)scanline_scatter_buffer_end ); + + // update the end of the buffer + if ( out_last_scanline > split_info->ring_buffer_last_scanline ) + split_info->ring_buffer_last_scanline = out_last_scanline; + } + ++vertical_contributors; + vertical_coefficients += stbir_info->vertical.coefficient_width; + } + + // now evict the scanlines that are left over in the ring buffer + while ( split_info->ring_buffer_first_scanline < end_output_y ) + handle_scanline_for_scatter(stbir_info, split_info); + + // update the end_input_y if we do multiple resizes with the same data + ++last_input_y; + for( y = 0 ; y < split_count; y++ ) + if ( split_info[y].end_input_y > last_input_y ) + split_info[y].end_input_y = last_input_y; +} + + +static stbir__kernel_callback * stbir__builtin_kernels[] = { 0, stbir__filter_trapezoid, stbir__filter_triangle, stbir__filter_cubic, stbir__filter_catmullrom, stbir__filter_mitchell, stbir__filter_point }; +static stbir__support_callback * stbir__builtin_supports[] = { 0, stbir__support_trapezoid, stbir__support_one, stbir__support_two, stbir__support_two, stbir__support_two, stbir__support_zeropoint5 }; + +static void stbir__set_sampler(stbir__sampler * samp, stbir_filter filter, stbir__kernel_callback * kernel, stbir__support_callback * support, stbir_edge edge, stbir__scale_info * scale_info, int always_gather, void * user_data ) +{ + // set filter + if (filter == 0) + { + filter = STBIR_DEFAULT_FILTER_DOWNSAMPLE; // default to downsample + if (scale_info->scale >= ( 1.0f - stbir__small_float ) ) + { + if ( (scale_info->scale <= ( 1.0f + stbir__small_float ) ) && ( STBIR_CEILF(scale_info->pixel_shift) == scale_info->pixel_shift ) ) + filter = STBIR_FILTER_POINT_SAMPLE; + else + filter = STBIR_DEFAULT_FILTER_UPSAMPLE; + } + } + samp->filter_enum = filter; + + STBIR_ASSERT(samp->filter_enum != 0); + STBIR_ASSERT((unsigned)samp->filter_enum < STBIR_FILTER_OTHER); + samp->filter_kernel = stbir__builtin_kernels[ filter ]; + samp->filter_support = stbir__builtin_supports[ filter ]; + + if ( kernel && support ) + { + samp->filter_kernel = kernel; + samp->filter_support = support; + samp->filter_enum = STBIR_FILTER_OTHER; + } + + samp->edge = edge; + samp->filter_pixel_width = stbir__get_filter_pixel_width (samp->filter_support, scale_info->scale, user_data ); + // Gather is always better, but in extreme downsamples, you have to most or all of the data in memory + // For horizontal, we always have all the pixels, so we always use gather here (always_gather==1). + // For vertical, we use gather if scaling up (which means we will have samp->filter_pixel_width + // scanlines in memory at once). + samp->is_gather = 0; + if ( scale_info->scale >= ( 1.0f - stbir__small_float ) ) + samp->is_gather = 1; + else if ( ( always_gather ) || ( samp->filter_pixel_width <= STBIR_FORCE_GATHER_FILTER_SCANLINES_AMOUNT ) ) + samp->is_gather = 2; + + // pre calculate stuff based on the above + samp->coefficient_width = stbir__get_coefficient_width(samp, samp->is_gather, user_data); + + // filter_pixel_width is the conservative size in pixels of input that affect an output pixel. + // In rare cases (only with 2 pix to 1 pix with the default filters), it's possible that the + // filter will extend before or after the scanline beyond just one extra entire copy of the + // scanline (we would hit the edge twice). We don't let you do that, so we clamp the total + // width to 3x the total of input pixel (once for the scanline, once for the left side + // overhang, and once for the right side). We only do this for edge mode, since the other + // modes can just re-edge clamp back in again. + if ( edge == STBIR_EDGE_WRAP ) + if ( samp->filter_pixel_width > ( scale_info->input_full_size * 3 ) ) + samp->filter_pixel_width = scale_info->input_full_size * 3; + + // This is how much to expand buffers to account for filters seeking outside + // the image boundaries. + samp->filter_pixel_margin = samp->filter_pixel_width / 2; + + // filter_pixel_margin is the amount that this filter can overhang on just one side of either + // end of the scanline (left or the right). Since we only allow you to overhang 1 scanline's + // worth of pixels, we clamp this one side of overhang to the input scanline size. Again, + // this clamping only happens in rare cases with the default filters (2 pix to 1 pix). + if ( edge == STBIR_EDGE_WRAP ) + if ( samp->filter_pixel_margin > scale_info->input_full_size ) + samp->filter_pixel_margin = scale_info->input_full_size; + + samp->num_contributors = stbir__get_contributors(samp, samp->is_gather); + + samp->contributors_size = samp->num_contributors * sizeof(stbir__contributors); + samp->coefficients_size = samp->num_contributors * samp->coefficient_width * sizeof(float) + sizeof(float); // extra sizeof(float) is padding + + samp->gather_prescatter_contributors = 0; + samp->gather_prescatter_coefficients = 0; + if ( samp->is_gather == 0 ) + { + samp->gather_prescatter_coefficient_width = samp->filter_pixel_width; + samp->gather_prescatter_num_contributors = stbir__get_contributors(samp, 2); + samp->gather_prescatter_contributors_size = samp->gather_prescatter_num_contributors * sizeof(stbir__contributors); + samp->gather_prescatter_coefficients_size = samp->gather_prescatter_num_contributors * samp->gather_prescatter_coefficient_width * sizeof(float); + } +} + +static void stbir__get_conservative_extents( stbir__sampler * samp, stbir__contributors * range, void * user_data ) +{ + float scale = samp->scale_info.scale; + float out_shift = samp->scale_info.pixel_shift; + stbir__support_callback * support = samp->filter_support; + int input_full_size = samp->scale_info.input_full_size; + stbir_edge edge = samp->edge; + float inv_scale = samp->scale_info.inv_scale; + + STBIR_ASSERT( samp->is_gather != 0 ); + + if ( samp->is_gather == 1 ) + { + int in_first_pixel, in_last_pixel; + float out_filter_radius = support(inv_scale, user_data) * scale; + + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, 0.5, out_filter_radius, inv_scale, out_shift, input_full_size, edge ); + range->n0 = in_first_pixel; + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, ( (float)(samp->scale_info.output_sub_size-1) ) + 0.5f, out_filter_radius, inv_scale, out_shift, input_full_size, edge ); + range->n1 = in_last_pixel; + } + else if ( samp->is_gather == 2 ) // downsample gather, refine + { + float in_pixels_radius = support(scale, user_data) * inv_scale; + int filter_pixel_margin = samp->filter_pixel_margin; + int output_sub_size = samp->scale_info.output_sub_size; + int input_end; + int n; + int in_first_pixel, in_last_pixel; + + // get a conservative area of the input range + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, 0, 0, inv_scale, out_shift, input_full_size, edge ); + range->n0 = in_first_pixel; + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, (float)output_sub_size, 0, inv_scale, out_shift, input_full_size, edge ); + range->n1 = in_last_pixel; + + // now go through the margin to the start of area to find bottom + n = range->n0 + 1; + input_end = -filter_pixel_margin; + while( n >= input_end ) + { + int out_first_pixel, out_last_pixel; + stbir__calculate_out_pixel_range( &out_first_pixel, &out_last_pixel, ((float)n)+0.5f, in_pixels_radius, scale, out_shift, output_sub_size ); + if ( out_first_pixel > out_last_pixel ) + break; + + if ( ( out_first_pixel < output_sub_size ) || ( out_last_pixel >= 0 ) ) + range->n0 = n; + --n; + } + + // now go through the end of the area through the margin to find top + n = range->n1 - 1; + input_end = n + 1 + filter_pixel_margin; + while( n <= input_end ) + { + int out_first_pixel, out_last_pixel; + stbir__calculate_out_pixel_range( &out_first_pixel, &out_last_pixel, ((float)n)+0.5f, in_pixels_radius, scale, out_shift, output_sub_size ); + if ( out_first_pixel > out_last_pixel ) + break; + if ( ( out_first_pixel < output_sub_size ) || ( out_last_pixel >= 0 ) ) + range->n1 = n; + ++n; + } + } + + if ( samp->edge == STBIR_EDGE_WRAP ) + { + // if we are wrapping, and we are very close to the image size (so the edges might merge), just use the scanline up to the edge + if ( ( range->n0 > 0 ) && ( range->n1 >= input_full_size ) ) + { + int marg = range->n1 - input_full_size + 1; + if ( ( marg + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= range->n0 ) + range->n0 = 0; + } + if ( ( range->n0 < 0 ) && ( range->n1 < (input_full_size-1) ) ) + { + int marg = -range->n0; + if ( ( input_full_size - marg - STBIR__MERGE_RUNS_PIXEL_THRESHOLD - 1 ) <= range->n1 ) + range->n1 = input_full_size - 1; + } + } + else + { + // for non-edge-wrap modes, we never read over the edge, so clamp + if ( range->n0 < 0 ) + range->n0 = 0; + if ( range->n1 >= input_full_size ) + range->n1 = input_full_size - 1; + } +} + +static void stbir__get_split_info( stbir__per_split_info* split_info, int splits, int output_height, int vertical_pixel_margin, int input_full_height ) +{ + int i, cur; + int left = output_height; + + cur = 0; + for( i = 0 ; i < splits ; i++ ) + { + int each; + split_info[i].start_output_y = cur; + each = left / ( splits - i ); + split_info[i].end_output_y = cur + each; + cur += each; + left -= each; + + // scatter range (updated to minimum as you run it) + split_info[i].start_input_y = -vertical_pixel_margin; + split_info[i].end_input_y = input_full_height + vertical_pixel_margin; + } +} + +static void stbir__free_internal_mem( stbir__info *info ) +{ + #define STBIR__FREE_AND_CLEAR( ptr ) { if ( ptr ) { void * p = (ptr); (ptr) = 0; STBIR_FREE( p, info->user_data); } } + + if ( info ) + { + #ifndef STBIR__SEPARATE_ALLOCATIONS + STBIR__FREE_AND_CLEAR( info->alloced_mem ); + #else + int i,j; + + if ( ( info->vertical.gather_prescatter_contributors ) && ( (void*)info->vertical.gather_prescatter_contributors != (void*)info->split_info[0].decode_buffer ) ) + { + STBIR__FREE_AND_CLEAR( info->vertical.gather_prescatter_coefficients ); + STBIR__FREE_AND_CLEAR( info->vertical.gather_prescatter_contributors ); + } + for( i = 0 ; i < info->splits ; i++ ) + { + for( j = 0 ; j < info->alloc_ring_buffer_num_entries ; j++ ) + { + #ifdef STBIR_SIMD8 + if ( info->effective_channels == 3 ) + --info->split_info[i].ring_buffers[j]; // avx in 3 channel mode needs one float at the start of the buffer + #endif + STBIR__FREE_AND_CLEAR( info->split_info[i].ring_buffers[j] ); + } + + #ifdef STBIR_SIMD8 + if ( info->effective_channels == 3 ) + --info->split_info[i].decode_buffer; // avx in 3 channel mode needs one float at the start of the buffer + #endif + STBIR__FREE_AND_CLEAR( info->split_info[i].decode_buffer ); + STBIR__FREE_AND_CLEAR( info->split_info[i].ring_buffers ); + STBIR__FREE_AND_CLEAR( info->split_info[i].vertical_buffer ); + } + STBIR__FREE_AND_CLEAR( info->split_info ); + if ( info->vertical.coefficients != info->horizontal.coefficients ) + { + STBIR__FREE_AND_CLEAR( info->vertical.coefficients ); + STBIR__FREE_AND_CLEAR( info->vertical.contributors ); + } + STBIR__FREE_AND_CLEAR( info->horizontal.coefficients ); + STBIR__FREE_AND_CLEAR( info->horizontal.contributors ); + STBIR__FREE_AND_CLEAR( info->alloced_mem ); + STBIR__FREE_AND_CLEAR( info ); + #endif + } + + #undef STBIR__FREE_AND_CLEAR +} + +static int stbir__get_max_split( int splits, int height ) +{ + int i; + int max = 0; + + for( i = 0 ; i < splits ; i++ ) + { + int each = height / ( splits - i ); + if ( each > max ) + max = each; + height -= each; + } + return max; +} + +static stbir__horizontal_gather_channels_func ** stbir__horizontal_gather_n_coeffs_funcs[8] = +{ + 0, stbir__horizontal_gather_1_channels_with_n_coeffs_funcs, stbir__horizontal_gather_2_channels_with_n_coeffs_funcs, stbir__horizontal_gather_3_channels_with_n_coeffs_funcs, stbir__horizontal_gather_4_channels_with_n_coeffs_funcs, 0,0, stbir__horizontal_gather_7_channels_with_n_coeffs_funcs +}; + +static stbir__horizontal_gather_channels_func ** stbir__horizontal_gather_channels_funcs[8] = +{ + 0, stbir__horizontal_gather_1_channels_funcs, stbir__horizontal_gather_2_channels_funcs, stbir__horizontal_gather_3_channels_funcs, stbir__horizontal_gather_4_channels_funcs, 0,0, stbir__horizontal_gather_7_channels_funcs +}; + +// there are six resize classifications: 0 == vertical scatter, 1 == vertical gather < 1x scale, 2 == vertical gather 1x-2x scale, 4 == vertical gather < 3x scale, 4 == vertical gather > 3x scale, 5 == <=4 pixel height, 6 == <=4 pixel wide column +#define STBIR_RESIZE_CLASSIFICATIONS 8 + +static float stbir__compute_weights[5][STBIR_RESIZE_CLASSIFICATIONS][4]= // 5 = 0=1chan, 1=2chan, 2=3chan, 3=4chan, 4=7chan +{ + { + { 1.00000f, 1.00000f, 0.31250f, 1.00000f }, + { 0.56250f, 0.59375f, 0.00000f, 0.96875f }, + { 1.00000f, 0.06250f, 0.00000f, 1.00000f }, + { 0.00000f, 0.09375f, 1.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, + { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 0.00000f, 1.00000f, 0.00000f, 0.03125f }, + }, { + { 0.00000f, 0.84375f, 0.00000f, 0.03125f }, + { 0.09375f, 0.93750f, 0.00000f, 0.78125f }, + { 0.87500f, 0.21875f, 0.00000f, 0.96875f }, + { 0.09375f, 0.09375f, 1.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, + { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 0.00000f, 1.00000f, 0.00000f, 0.53125f }, + }, { + { 0.00000f, 0.53125f, 0.00000f, 0.03125f }, + { 0.06250f, 0.96875f, 0.00000f, 0.53125f }, + { 0.87500f, 0.18750f, 0.00000f, 0.93750f }, + { 0.00000f, 0.09375f, 1.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, + { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 0.00000f, 1.00000f, 0.00000f, 0.56250f }, + }, { + { 0.00000f, 0.50000f, 0.00000f, 0.71875f }, + { 0.06250f, 0.84375f, 0.00000f, 0.87500f }, + { 1.00000f, 0.50000f, 0.50000f, 0.96875f }, + { 1.00000f, 0.09375f, 0.31250f, 0.50000f }, + { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 1.00000f, 0.03125f, 0.03125f, 0.53125f }, + { 0.18750f, 0.12500f, 0.00000f, 1.00000f }, + { 0.00000f, 1.00000f, 0.03125f, 0.18750f }, + }, { + { 0.00000f, 0.59375f, 0.00000f, 0.96875f }, + { 0.06250f, 0.81250f, 0.06250f, 0.59375f }, + { 0.75000f, 0.43750f, 0.12500f, 0.96875f }, + { 0.87500f, 0.06250f, 0.18750f, 0.43750f }, + { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.15625f, 0.12500f, 1.00000f, 1.00000f }, + { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 0.00000f, 1.00000f, 0.03125f, 0.34375f }, + } +}; + +// structure that allow us to query and override info for training the costs +typedef struct STBIR__V_FIRST_INFO +{ + double v_cost, h_cost; + int control_v_first; // 0 = no control, 1 = force hori, 2 = force vert + int v_first; + int v_resize_classification; + int is_gather; +} STBIR__V_FIRST_INFO; + +#ifdef STBIR__V_FIRST_INFO_BUFFER +static STBIR__V_FIRST_INFO STBIR__V_FIRST_INFO_BUFFER = {0}; +#define STBIR__V_FIRST_INFO_POINTER &STBIR__V_FIRST_INFO_BUFFER +#else +#define STBIR__V_FIRST_INFO_POINTER 0 +#endif + +// Figure out whether to scale along the horizontal or vertical first. +// This only *super* important when you are scaling by a massively +// different amount in the vertical vs the horizontal (for example, if +// you are scaling by 2x in the width, and 0.5x in the height, then you +// want to do the vertical scale first, because it's around 3x faster +// in that order. +// +// In more normal circumstances, this makes a 20-40% differences, so +// it's good to get right, but not critical. The normal way that you +// decide which direction goes first is just figuring out which +// direction does more multiplies. But with modern CPUs with their +// fancy caches and SIMD and high IPC abilities, so there's just a lot +// more that goes into it. +// +// My handwavy sort of solution is to have an app that does a whole +// bunch of timing for both vertical and horizontal first modes, +// and then another app that can read lots of these timing files +// and try to search for the best weights to use. Dotimings.c +// is the app that does a bunch of timings, and vf_train.c is the +// app that solves for the best weights (and shows how well it +// does currently). + +static int stbir__should_do_vertical_first( float weights_table[STBIR_RESIZE_CLASSIFICATIONS][4], int horizontal_filter_pixel_width, float horizontal_scale, int horizontal_output_size, int vertical_filter_pixel_width, float vertical_scale, int vertical_output_size, int is_gather, STBIR__V_FIRST_INFO * info ) +{ + double v_cost, h_cost; + float * weights; + int vertical_first; + int v_classification; + + // categorize the resize into buckets + if ( ( vertical_output_size <= 4 ) || ( horizontal_output_size <= 4 ) ) + v_classification = ( vertical_output_size < horizontal_output_size ) ? 6 : 7; + else if ( vertical_scale <= 1.0f ) + v_classification = ( is_gather ) ? 1 : 0; + else if ( vertical_scale <= 2.0f) + v_classification = 2; + else if ( vertical_scale <= 3.0f) + v_classification = 3; + else if ( vertical_scale <= 4.0f) + v_classification = 5; + else + v_classification = 6; + + // use the right weights + weights = weights_table[ v_classification ]; + + // this is the costs when you don't take into account modern CPUs with high ipc and simd and caches - wish we had a better estimate + h_cost = (float)horizontal_filter_pixel_width * weights[0] + horizontal_scale * (float)vertical_filter_pixel_width * weights[1]; + v_cost = (float)vertical_filter_pixel_width * weights[2] + vertical_scale * (float)horizontal_filter_pixel_width * weights[3]; + + // use computation estimate to decide vertical first or not + vertical_first = ( v_cost <= h_cost ) ? 1 : 0; + + // save these, if requested + if ( info ) + { + info->h_cost = h_cost; + info->v_cost = v_cost; + info->v_resize_classification = v_classification; + info->v_first = vertical_first; + info->is_gather = is_gather; + } + + // and this allows us to override everything for testing (see dotiming.c) + if ( ( info ) && ( info->control_v_first ) ) + vertical_first = ( info->control_v_first == 2 ) ? 1 : 0; + + return vertical_first; +} + +// layout lookups - must match stbir_internal_pixel_layout +static unsigned char stbir__pixel_channels[] = { + 1,2,3,3,4, // 1ch, 2ch, rgb, bgr, 4ch + 4,4,4,4,2,2, // RGBA,BGRA,ARGB,ABGR,RA,AR + 4,4,4,4,2,2, // RGBA_PM,BGRA_PM,ARGB_PM,ABGR_PM,RA_PM,AR_PM +}; + +// the internal pixel layout enums are in a different order, so we can easily do range comparisons of types +// the public pixel layout is ordered in a way that if you cast num_channels (1-4) to the enum, you get something sensible +static stbir_internal_pixel_layout stbir__pixel_layout_convert_public_to_internal[] = { + STBIRI_BGR, STBIRI_1CHANNEL, STBIRI_2CHANNEL, STBIRI_RGB, STBIRI_RGBA, + STBIRI_4CHANNEL, STBIRI_BGRA, STBIRI_ARGB, STBIRI_ABGR, STBIRI_RA, STBIRI_AR, + STBIRI_RGBA_PM, STBIRI_BGRA_PM, STBIRI_ARGB_PM, STBIRI_ABGR_PM, STBIRI_RA_PM, STBIRI_AR_PM, +}; + +static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sampler * horizontal, stbir__sampler * vertical, stbir__contributors * conservative, stbir_pixel_layout input_pixel_layout_public, stbir_pixel_layout output_pixel_layout_public, int splits, int new_x, int new_y, int fast_alpha, void * user_data STBIR_ONLY_PROFILE_BUILD_GET_INFO ) +{ + static char stbir_channel_count_index[8]={ 9,0,1,2, 3,9,9,4 }; + + stbir__info * info = 0; + void * alloced = 0; + int alloced_total = 0; + int vertical_first; + int decode_buffer_size, ring_buffer_length_bytes, ring_buffer_size, vertical_buffer_size, alloc_ring_buffer_num_entries; + + int alpha_weighting_type = 0; // 0=none, 1=simple, 2=fancy + int conservative_split_output_size = stbir__get_max_split( splits, vertical->scale_info.output_sub_size ); + stbir_internal_pixel_layout input_pixel_layout = stbir__pixel_layout_convert_public_to_internal[ input_pixel_layout_public ]; + stbir_internal_pixel_layout output_pixel_layout = stbir__pixel_layout_convert_public_to_internal[ output_pixel_layout_public ]; + int channels = stbir__pixel_channels[ input_pixel_layout ]; + int effective_channels = channels; + + // first figure out what type of alpha weighting to use (if any) + if ( ( horizontal->filter_enum != STBIR_FILTER_POINT_SAMPLE ) || ( vertical->filter_enum != STBIR_FILTER_POINT_SAMPLE ) ) // no alpha weighting on point sampling + { + if ( ( input_pixel_layout >= STBIRI_RGBA ) && ( input_pixel_layout <= STBIRI_AR ) && ( output_pixel_layout >= STBIRI_RGBA ) && ( output_pixel_layout <= STBIRI_AR ) ) + { + if ( fast_alpha ) + { + alpha_weighting_type = 4; + } + else + { + static int fancy_alpha_effective_cnts[6] = { 7, 7, 7, 7, 3, 3 }; + alpha_weighting_type = 2; + effective_channels = fancy_alpha_effective_cnts[ input_pixel_layout - STBIRI_RGBA ]; + } + } + else if ( ( input_pixel_layout >= STBIRI_RGBA_PM ) && ( input_pixel_layout <= STBIRI_AR_PM ) && ( output_pixel_layout >= STBIRI_RGBA ) && ( output_pixel_layout <= STBIRI_AR ) ) + { + // input premult, output non-premult + alpha_weighting_type = 3; + } + else if ( ( input_pixel_layout >= STBIRI_RGBA ) && ( input_pixel_layout <= STBIRI_AR ) && ( output_pixel_layout >= STBIRI_RGBA_PM ) && ( output_pixel_layout <= STBIRI_AR_PM ) ) + { + // input non-premult, output premult + alpha_weighting_type = 1; + } + } + + // channel in and out count must match currently + if ( channels != stbir__pixel_channels[ output_pixel_layout ] ) + return 0; + + // get vertical first + vertical_first = stbir__should_do_vertical_first( stbir__compute_weights[ (int)stbir_channel_count_index[ effective_channels ] ], horizontal->filter_pixel_width, horizontal->scale_info.scale, horizontal->scale_info.output_sub_size, vertical->filter_pixel_width, vertical->scale_info.scale, vertical->scale_info.output_sub_size, vertical->is_gather, STBIR__V_FIRST_INFO_POINTER ); + + // sometimes read one float off in some of the unrolled loops (with a weight of zero coeff, so it doesn't have an effect) + decode_buffer_size = ( conservative->n1 - conservative->n0 + 1 ) * effective_channels * sizeof(float) + sizeof(float); // extra float for padding + +#if defined( STBIR__SEPARATE_ALLOCATIONS ) && defined(STBIR_SIMD8) + if ( effective_channels == 3 ) + decode_buffer_size += sizeof(float); // avx in 3 channel mode needs one float at the start of the buffer (only with separate allocations) +#endif + + ring_buffer_length_bytes = horizontal->scale_info.output_sub_size * effective_channels * sizeof(float) + sizeof(float); // extra float for padding + + // if we do vertical first, the ring buffer holds a whole decoded line + if ( vertical_first ) + ring_buffer_length_bytes = ( decode_buffer_size + 15 ) & ~15; + + if ( ( ring_buffer_length_bytes & 4095 ) == 0 ) ring_buffer_length_bytes += 64*3; // avoid 4k alias + + // One extra entry because floating point precision problems sometimes cause an extra to be necessary. + alloc_ring_buffer_num_entries = vertical->filter_pixel_width + 1; + + // we never need more ring buffer entries than the scanlines we're outputting when in scatter mode + if ( ( !vertical->is_gather ) && ( alloc_ring_buffer_num_entries > conservative_split_output_size ) ) + alloc_ring_buffer_num_entries = conservative_split_output_size; + + ring_buffer_size = alloc_ring_buffer_num_entries * ring_buffer_length_bytes; + + // The vertical buffer is used differently, depending on whether we are scattering + // the vertical scanlines, or gathering them. + // If scattering, it's used at the temp buffer to accumulate each output. + // If gathering, it's just the output buffer. + vertical_buffer_size = horizontal->scale_info.output_sub_size * effective_channels * sizeof(float) + sizeof(float); // extra float for padding + + // we make two passes through this loop, 1st to add everything up, 2nd to allocate and init + for(;;) + { + int i; + void * advance_mem = alloced; + int copy_horizontal = 0; + stbir__sampler * possibly_use_horizontal_for_pivot = 0; + +#ifdef STBIR__SEPARATE_ALLOCATIONS + #define STBIR__NEXT_PTR( ptr, size, ntype ) if ( alloced ) { void * p = STBIR_MALLOC( size, user_data); if ( p == 0 ) { stbir__free_internal_mem( info ); return 0; } (ptr) = (ntype*)p; } +#else + #define STBIR__NEXT_PTR( ptr, size, ntype ) advance_mem = (void*) ( ( ((size_t)advance_mem) + 15 ) & ~15 ); if ( alloced ) ptr = (ntype*)advance_mem; advance_mem = ((char*)advance_mem) + (size); +#endif + + STBIR__NEXT_PTR( info, sizeof( stbir__info ), stbir__info ); + + STBIR__NEXT_PTR( info->split_info, sizeof( stbir__per_split_info ) * splits, stbir__per_split_info ); + + if ( info ) + { + static stbir__alpha_weight_func * fancy_alpha_weights[6] = { stbir__fancy_alpha_weight_4ch, stbir__fancy_alpha_weight_4ch, stbir__fancy_alpha_weight_4ch, stbir__fancy_alpha_weight_4ch, stbir__fancy_alpha_weight_2ch, stbir__fancy_alpha_weight_2ch }; + static stbir__alpha_unweight_func * fancy_alpha_unweights[6] = { stbir__fancy_alpha_unweight_4ch, stbir__fancy_alpha_unweight_4ch, stbir__fancy_alpha_unweight_4ch, stbir__fancy_alpha_unweight_4ch, stbir__fancy_alpha_unweight_2ch, stbir__fancy_alpha_unweight_2ch }; + static stbir__alpha_weight_func * simple_alpha_weights[6] = { stbir__simple_alpha_weight_4ch, stbir__simple_alpha_weight_4ch, stbir__simple_alpha_weight_4ch, stbir__simple_alpha_weight_4ch, stbir__simple_alpha_weight_2ch, stbir__simple_alpha_weight_2ch }; + static stbir__alpha_unweight_func * simple_alpha_unweights[6] = { stbir__simple_alpha_unweight_4ch, stbir__simple_alpha_unweight_4ch, stbir__simple_alpha_unweight_4ch, stbir__simple_alpha_unweight_4ch, stbir__simple_alpha_unweight_2ch, stbir__simple_alpha_unweight_2ch }; + + // initialize info fields + info->alloced_mem = alloced; + info->alloced_total = alloced_total; + + info->channels = channels; + info->effective_channels = effective_channels; + + info->offset_x = new_x; + info->offset_y = new_y; + info->alloc_ring_buffer_num_entries = alloc_ring_buffer_num_entries; + info->ring_buffer_num_entries = 0; + info->ring_buffer_length_bytes = ring_buffer_length_bytes; + info->splits = splits; + info->vertical_first = vertical_first; + + info->input_pixel_layout_internal = input_pixel_layout; + info->output_pixel_layout_internal = output_pixel_layout; + + // setup alpha weight functions + info->alpha_weight = 0; + info->alpha_unweight = 0; + + // handle alpha weighting functions and overrides + if ( alpha_weighting_type == 2 ) + { + // high quality alpha multiplying on the way in, dividing on the way out + info->alpha_weight = fancy_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; + info->alpha_unweight = fancy_alpha_unweights[ output_pixel_layout - STBIRI_RGBA ]; + } + else if ( alpha_weighting_type == 4 ) + { + // fast alpha multiplying on the way in, dividing on the way out + info->alpha_weight = simple_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; + info->alpha_unweight = simple_alpha_unweights[ output_pixel_layout - STBIRI_RGBA ]; + } + else if ( alpha_weighting_type == 1 ) + { + // fast alpha on the way in, leave in premultiplied form on way out + info->alpha_weight = simple_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; + } + else if ( alpha_weighting_type == 3 ) + { + // incoming is premultiplied, fast alpha dividing on the way out - non-premultiplied output + info->alpha_unweight = simple_alpha_unweights[ output_pixel_layout - STBIRI_RGBA ]; + } + + // handle 3-chan color flipping, using the alpha weight path + if ( ( ( input_pixel_layout == STBIRI_RGB ) && ( output_pixel_layout == STBIRI_BGR ) ) || + ( ( input_pixel_layout == STBIRI_BGR ) && ( output_pixel_layout == STBIRI_RGB ) ) ) + { + // do the flipping on the smaller of the two ends + if ( horizontal->scale_info.scale < 1.0f ) + info->alpha_unweight = stbir__simple_flip_3ch; + else + info->alpha_weight = stbir__simple_flip_3ch; + } + + } + + // get all the per-split buffers + for( i = 0 ; i < splits ; i++ ) + { + STBIR__NEXT_PTR( info->split_info[i].decode_buffer, decode_buffer_size, float ); + +#ifdef STBIR__SEPARATE_ALLOCATIONS + + #ifdef STBIR_SIMD8 + if ( ( info ) && ( effective_channels == 3 ) ) + ++info->split_info[i].decode_buffer; // avx in 3 channel mode needs one float at the start of the buffer + #endif + + STBIR__NEXT_PTR( info->split_info[i].ring_buffers, alloc_ring_buffer_num_entries * sizeof(float*), float* ); + { + int j; + for( j = 0 ; j < alloc_ring_buffer_num_entries ; j++ ) + { + STBIR__NEXT_PTR( info->split_info[i].ring_buffers[j], ring_buffer_length_bytes, float ); + #ifdef STBIR_SIMD8 + if ( ( info ) && ( effective_channels == 3 ) ) + ++info->split_info[i].ring_buffers[j]; // avx in 3 channel mode needs one float at the start of the buffer + #endif + } + } +#else + STBIR__NEXT_PTR( info->split_info[i].ring_buffer, ring_buffer_size, float ); +#endif + STBIR__NEXT_PTR( info->split_info[i].vertical_buffer, vertical_buffer_size, float ); + } + + // alloc memory for to-be-pivoted coeffs (if necessary) + if ( vertical->is_gather == 0 ) + { + int both; + int temp_mem_amt; + + // when in vertical scatter mode, we first build the coefficients in gather mode, and then pivot after, + // that means we need two buffers, so we try to use the decode buffer and ring buffer for this. if that + // is too small, we just allocate extra memory to use as this temp. + + both = vertical->gather_prescatter_contributors_size + vertical->gather_prescatter_coefficients_size; + +#ifdef STBIR__SEPARATE_ALLOCATIONS + temp_mem_amt = decode_buffer_size; +#else + temp_mem_amt = ( decode_buffer_size + ring_buffer_size + vertical_buffer_size ) * splits; +#endif + if ( temp_mem_amt >= both ) + { + if ( info ) + { + vertical->gather_prescatter_contributors = (stbir__contributors*)info->split_info[0].decode_buffer; + vertical->gather_prescatter_coefficients = (float*) ( ( (char*)info->split_info[0].decode_buffer ) + vertical->gather_prescatter_contributors_size ); + } + } + else + { + // ring+decode memory is too small, so allocate temp memory + STBIR__NEXT_PTR( vertical->gather_prescatter_contributors, vertical->gather_prescatter_contributors_size, stbir__contributors ); + STBIR__NEXT_PTR( vertical->gather_prescatter_coefficients, vertical->gather_prescatter_coefficients_size, float ); + } + } + + STBIR__NEXT_PTR( horizontal->contributors, horizontal->contributors_size, stbir__contributors ); + STBIR__NEXT_PTR( horizontal->coefficients, horizontal->coefficients_size, float ); + + // are the two filters identical?? (happens a lot with mipmap generation) + if ( ( horizontal->filter_kernel == vertical->filter_kernel ) && ( horizontal->filter_support == vertical->filter_support ) && ( horizontal->edge == vertical->edge ) && ( horizontal->scale_info.output_sub_size == vertical->scale_info.output_sub_size ) ) + { + float diff_scale = horizontal->scale_info.scale - vertical->scale_info.scale; + float diff_shift = horizontal->scale_info.pixel_shift - vertical->scale_info.pixel_shift; + if ( diff_scale < 0.0f ) diff_scale = -diff_scale; + if ( diff_shift < 0.0f ) diff_shift = -diff_shift; + if ( ( diff_scale <= stbir__small_float ) && ( diff_shift <= stbir__small_float ) ) + { + if ( horizontal->is_gather == vertical->is_gather ) + { + copy_horizontal = 1; + goto no_vert_alloc; + } + // everything matches, but vertical is scatter, horizontal is gather, use horizontal coeffs for vertical pivot coeffs + possibly_use_horizontal_for_pivot = horizontal; + } + } + + STBIR__NEXT_PTR( vertical->contributors, vertical->contributors_size, stbir__contributors ); + STBIR__NEXT_PTR( vertical->coefficients, vertical->coefficients_size, float ); + + no_vert_alloc: + + if ( info ) + { + STBIR_PROFILE_BUILD_START( horizontal ); + + stbir__calculate_filters( horizontal, 0, user_data STBIR_ONLY_PROFILE_BUILD_SET_INFO ); + + // setup the horizontal gather functions + // start with defaulting to the n_coeffs functions (specialized on channels and remnant leftover) + info->horizontal_gather_channels = stbir__horizontal_gather_n_coeffs_funcs[ effective_channels ][ horizontal->extent_info.widest & 3 ]; + // but if the number of coeffs <= 12, use another set of special cases. <=12 coeffs is any enlarging resize, or shrinking resize down to about 1/3 size + if ( horizontal->extent_info.widest <= 12 ) + info->horizontal_gather_channels = stbir__horizontal_gather_channels_funcs[ effective_channels ][ horizontal->extent_info.widest - 1 ]; + + info->scanline_extents.conservative.n0 = conservative->n0; + info->scanline_extents.conservative.n1 = conservative->n1; + + // get exact extents + stbir__get_extents( horizontal, &info->scanline_extents ); + + // pack the horizontal coeffs + horizontal->coefficient_width = stbir__pack_coefficients(horizontal->num_contributors, horizontal->contributors, horizontal->coefficients, horizontal->coefficient_width, horizontal->extent_info.widest, info->scanline_extents.conservative.n0, info->scanline_extents.conservative.n1 ); + + STBIR_MEMCPY( &info->horizontal, horizontal, sizeof( stbir__sampler ) ); + + STBIR_PROFILE_BUILD_END( horizontal ); + + if ( copy_horizontal ) + { + STBIR_MEMCPY( &info->vertical, horizontal, sizeof( stbir__sampler ) ); + } + else + { + STBIR_PROFILE_BUILD_START( vertical ); + + stbir__calculate_filters( vertical, possibly_use_horizontal_for_pivot, user_data STBIR_ONLY_PROFILE_BUILD_SET_INFO ); + STBIR_MEMCPY( &info->vertical, vertical, sizeof( stbir__sampler ) ); + + STBIR_PROFILE_BUILD_END( vertical ); + } + + // setup the vertical split ranges + stbir__get_split_info( info->split_info, info->splits, info->vertical.scale_info.output_sub_size, info->vertical.filter_pixel_margin, info->vertical.scale_info.input_full_size ); + + // now we know precisely how many entries we need + info->ring_buffer_num_entries = info->vertical.extent_info.widest; + + // we never need more ring buffer entries than the scanlines we're outputting + if ( ( !info->vertical.is_gather ) && ( info->ring_buffer_num_entries > conservative_split_output_size ) ) + info->ring_buffer_num_entries = conservative_split_output_size; + STBIR_ASSERT( info->ring_buffer_num_entries <= info->alloc_ring_buffer_num_entries ); + + // a few of the horizontal gather functions read one dword past the end (but mask it out), so put in a normal value so no snans or denormals accidentally sneak in + for( i = 0 ; i < splits ; i++ ) + { + int width, ofs; + + // find the right most span + if ( info->scanline_extents.spans[0].n1 > info->scanline_extents.spans[1].n1 ) + width = info->scanline_extents.spans[0].n1 - info->scanline_extents.spans[0].n0; + else + width = info->scanline_extents.spans[1].n1 - info->scanline_extents.spans[1].n0; + + // this calc finds the exact end of the decoded scanline for all filter modes. + // usually this is just the width * effective channels. But we have to account + // for the area to the left of the scanline for wrap filtering and alignment, this + // is stored as a negative value in info->scanline_extents.conservative.n0. Next, + // we need to skip the exact size of the right hand size filter area (again for + // wrap mode), this is in info->scanline_extents.edge_sizes[1]). + ofs = ( width + 1 - info->scanline_extents.conservative.n0 + info->scanline_extents.edge_sizes[1] ) * effective_channels; + + // place a known, but numerically valid value in the decode buffer + info->split_info[i].decode_buffer[ ofs ] = 9999.0f; + + // if vertical filtering first, place a known, but numerically valid value in the all + // of the ring buffer accumulators + if ( vertical_first ) + { + int j; + for( j = 0; j < info->ring_buffer_num_entries ; j++ ) + { + stbir__get_ring_buffer_entry( info, info->split_info + i, j )[ ofs ] = 9999.0f; + } + } + } + } + + #undef STBIR__NEXT_PTR + + + // is this the first time through loop? + if ( info == 0 ) + { + alloced_total = (int) ( 15 + (size_t)advance_mem ); + alloced = STBIR_MALLOC( alloced_total, user_data ); + if ( alloced == 0 ) + return 0; + } + else + return info; // success + } +} + +static int stbir__perform_resize( stbir__info const * info, int split_start, int split_count ) +{ + stbir__per_split_info * split_info = info->split_info + split_start; + + STBIR_PROFILE_CLEAR_EXTRAS(); + + STBIR_PROFILE_FIRST_START( looping ); + if (info->vertical.is_gather) + stbir__vertical_gather_loop( info, split_info, split_count ); + else + stbir__vertical_scatter_loop( info, split_info, split_count ); + STBIR_PROFILE_END( looping ); + + return 1; +} + +static void stbir__update_info_from_resize( stbir__info * info, STBIR_RESIZE * resize ) +{ + static stbir__decode_pixels_func * decode_simple[STBIR_TYPE_HALF_FLOAT-STBIR_TYPE_UINT8_SRGB+1]= + { + /* 1ch-4ch */ stbir__decode_uint8_srgb, stbir__decode_uint8_srgb, 0, stbir__decode_float_linear, stbir__decode_half_float_linear, + }; + + static stbir__decode_pixels_func * decode_alphas[STBIRI_AR-STBIRI_RGBA+1][STBIR_TYPE_HALF_FLOAT-STBIR_TYPE_UINT8_SRGB+1]= + { + { /* RGBA */ stbir__decode_uint8_srgb4_linearalpha, stbir__decode_uint8_srgb, 0, stbir__decode_float_linear, stbir__decode_half_float_linear }, + { /* BGRA */ stbir__decode_uint8_srgb4_linearalpha_BGRA, stbir__decode_uint8_srgb_BGRA, 0, stbir__decode_float_linear_BGRA, stbir__decode_half_float_linear_BGRA }, + { /* ARGB */ stbir__decode_uint8_srgb4_linearalpha_ARGB, stbir__decode_uint8_srgb_ARGB, 0, stbir__decode_float_linear_ARGB, stbir__decode_half_float_linear_ARGB }, + { /* ABGR */ stbir__decode_uint8_srgb4_linearalpha_ABGR, stbir__decode_uint8_srgb_ABGR, 0, stbir__decode_float_linear_ABGR, stbir__decode_half_float_linear_ABGR }, + { /* RA */ stbir__decode_uint8_srgb2_linearalpha, stbir__decode_uint8_srgb, 0, stbir__decode_float_linear, stbir__decode_half_float_linear }, + { /* AR */ stbir__decode_uint8_srgb2_linearalpha_AR, stbir__decode_uint8_srgb_AR, 0, stbir__decode_float_linear_AR, stbir__decode_half_float_linear_AR }, + }; + + static stbir__decode_pixels_func * decode_simple_scaled_or_not[2][2]= + { + { stbir__decode_uint8_linear_scaled, stbir__decode_uint8_linear }, { stbir__decode_uint16_linear_scaled, stbir__decode_uint16_linear }, + }; + + static stbir__decode_pixels_func * decode_alphas_scaled_or_not[STBIRI_AR-STBIRI_RGBA+1][2][2]= + { + { /* RGBA */ { stbir__decode_uint8_linear_scaled, stbir__decode_uint8_linear }, { stbir__decode_uint16_linear_scaled, stbir__decode_uint16_linear } }, + { /* BGRA */ { stbir__decode_uint8_linear_scaled_BGRA, stbir__decode_uint8_linear_BGRA }, { stbir__decode_uint16_linear_scaled_BGRA, stbir__decode_uint16_linear_BGRA } }, + { /* ARGB */ { stbir__decode_uint8_linear_scaled_ARGB, stbir__decode_uint8_linear_ARGB }, { stbir__decode_uint16_linear_scaled_ARGB, stbir__decode_uint16_linear_ARGB } }, + { /* ABGR */ { stbir__decode_uint8_linear_scaled_ABGR, stbir__decode_uint8_linear_ABGR }, { stbir__decode_uint16_linear_scaled_ABGR, stbir__decode_uint16_linear_ABGR } }, + { /* RA */ { stbir__decode_uint8_linear_scaled, stbir__decode_uint8_linear }, { stbir__decode_uint16_linear_scaled, stbir__decode_uint16_linear } }, + { /* AR */ { stbir__decode_uint8_linear_scaled_AR, stbir__decode_uint8_linear_AR }, { stbir__decode_uint16_linear_scaled_AR, stbir__decode_uint16_linear_AR } } + }; + + static stbir__encode_pixels_func * encode_simple[STBIR_TYPE_HALF_FLOAT-STBIR_TYPE_UINT8_SRGB+1]= + { + /* 1ch-4ch */ stbir__encode_uint8_srgb, stbir__encode_uint8_srgb, 0, stbir__encode_float_linear, stbir__encode_half_float_linear, + }; + + static stbir__encode_pixels_func * encode_alphas[STBIRI_AR-STBIRI_RGBA+1][STBIR_TYPE_HALF_FLOAT-STBIR_TYPE_UINT8_SRGB+1]= + { + { /* RGBA */ stbir__encode_uint8_srgb4_linearalpha, stbir__encode_uint8_srgb, 0, stbir__encode_float_linear, stbir__encode_half_float_linear }, + { /* BGRA */ stbir__encode_uint8_srgb4_linearalpha_BGRA, stbir__encode_uint8_srgb_BGRA, 0, stbir__encode_float_linear_BGRA, stbir__encode_half_float_linear_BGRA }, + { /* ARGB */ stbir__encode_uint8_srgb4_linearalpha_ARGB, stbir__encode_uint8_srgb_ARGB, 0, stbir__encode_float_linear_ARGB, stbir__encode_half_float_linear_ARGB }, + { /* ABGR */ stbir__encode_uint8_srgb4_linearalpha_ABGR, stbir__encode_uint8_srgb_ABGR, 0, stbir__encode_float_linear_ABGR, stbir__encode_half_float_linear_ABGR }, + { /* RA */ stbir__encode_uint8_srgb2_linearalpha, stbir__encode_uint8_srgb, 0, stbir__encode_float_linear, stbir__encode_half_float_linear }, + { /* AR */ stbir__encode_uint8_srgb2_linearalpha_AR, stbir__encode_uint8_srgb_AR, 0, stbir__encode_float_linear_AR, stbir__encode_half_float_linear_AR } + }; + + static stbir__encode_pixels_func * encode_simple_scaled_or_not[2][2]= + { + { stbir__encode_uint8_linear_scaled, stbir__encode_uint8_linear }, { stbir__encode_uint16_linear_scaled, stbir__encode_uint16_linear }, + }; + + static stbir__encode_pixels_func * encode_alphas_scaled_or_not[STBIRI_AR-STBIRI_RGBA+1][2][2]= + { + { /* RGBA */ { stbir__encode_uint8_linear_scaled, stbir__encode_uint8_linear }, { stbir__encode_uint16_linear_scaled, stbir__encode_uint16_linear } }, + { /* BGRA */ { stbir__encode_uint8_linear_scaled_BGRA, stbir__encode_uint8_linear_BGRA }, { stbir__encode_uint16_linear_scaled_BGRA, stbir__encode_uint16_linear_BGRA } }, + { /* ARGB */ { stbir__encode_uint8_linear_scaled_ARGB, stbir__encode_uint8_linear_ARGB }, { stbir__encode_uint16_linear_scaled_ARGB, stbir__encode_uint16_linear_ARGB } }, + { /* ABGR */ { stbir__encode_uint8_linear_scaled_ABGR, stbir__encode_uint8_linear_ABGR }, { stbir__encode_uint16_linear_scaled_ABGR, stbir__encode_uint16_linear_ABGR } }, + { /* RA */ { stbir__encode_uint8_linear_scaled, stbir__encode_uint8_linear }, { stbir__encode_uint16_linear_scaled, stbir__encode_uint16_linear } }, + { /* AR */ { stbir__encode_uint8_linear_scaled_AR, stbir__encode_uint8_linear_AR }, { stbir__encode_uint16_linear_scaled_AR, stbir__encode_uint16_linear_AR } } + }; + + stbir__decode_pixels_func * decode_pixels = 0; + stbir__encode_pixels_func * encode_pixels = 0; + stbir_datatype input_type, output_type; + + input_type = resize->input_data_type; + output_type = resize->output_data_type; + info->input_data = resize->input_pixels; + info->input_stride_bytes = resize->input_stride_in_bytes; + info->output_stride_bytes = resize->output_stride_in_bytes; + + // if we're completely point sampling, then we can turn off SRGB + if ( ( info->horizontal.filter_enum == STBIR_FILTER_POINT_SAMPLE ) && ( info->vertical.filter_enum == STBIR_FILTER_POINT_SAMPLE ) ) + { + if ( ( ( input_type == STBIR_TYPE_UINT8_SRGB ) || ( input_type == STBIR_TYPE_UINT8_SRGB_ALPHA ) ) && + ( ( output_type == STBIR_TYPE_UINT8_SRGB ) || ( output_type == STBIR_TYPE_UINT8_SRGB_ALPHA ) ) ) + { + input_type = STBIR_TYPE_UINT8; + output_type = STBIR_TYPE_UINT8; + } + } + + // recalc the output and input strides + if ( info->input_stride_bytes == 0 ) + info->input_stride_bytes = info->channels * info->horizontal.scale_info.input_full_size * stbir__type_size[input_type]; + + if ( info->output_stride_bytes == 0 ) + info->output_stride_bytes = info->channels * info->horizontal.scale_info.output_sub_size * stbir__type_size[output_type]; + + // calc offset + info->output_data = ( (char*) resize->output_pixels ) + ( (ptrdiff_t) info->offset_y * (ptrdiff_t) resize->output_stride_in_bytes ) + ( info->offset_x * info->channels * stbir__type_size[output_type] ); + + info->in_pixels_cb = resize->input_cb; + info->user_data = resize->user_data; + info->out_pixels_cb = resize->output_cb; + + // setup the input format converters + if ( ( input_type == STBIR_TYPE_UINT8 ) || ( input_type == STBIR_TYPE_UINT16 ) ) + { + int non_scaled = 0; + + // check if we can run unscaled - 0-255.0/0-65535.0 instead of 0-1.0 (which is a tiny bit faster when doing linear 8->8 or 16->16) + if ( ( !info->alpha_weight ) && ( !info->alpha_unweight ) ) // don't short circuit when alpha weighting (get everything to 0-1.0 as usual) + if ( ( ( input_type == STBIR_TYPE_UINT8 ) && ( output_type == STBIR_TYPE_UINT8 ) ) || ( ( input_type == STBIR_TYPE_UINT16 ) && ( output_type == STBIR_TYPE_UINT16 ) ) ) + non_scaled = 1; + + if ( info->input_pixel_layout_internal <= STBIRI_4CHANNEL ) + decode_pixels = decode_simple_scaled_or_not[ input_type == STBIR_TYPE_UINT16 ][ non_scaled ]; + else + decode_pixels = decode_alphas_scaled_or_not[ ( info->input_pixel_layout_internal - STBIRI_RGBA ) % ( STBIRI_AR-STBIRI_RGBA+1 ) ][ input_type == STBIR_TYPE_UINT16 ][ non_scaled ]; + } + else + { + if ( info->input_pixel_layout_internal <= STBIRI_4CHANNEL ) + decode_pixels = decode_simple[ input_type - STBIR_TYPE_UINT8_SRGB ]; + else + decode_pixels = decode_alphas[ ( info->input_pixel_layout_internal - STBIRI_RGBA ) % ( STBIRI_AR-STBIRI_RGBA+1 ) ][ input_type - STBIR_TYPE_UINT8_SRGB ]; + } + + // setup the output format converters + if ( ( output_type == STBIR_TYPE_UINT8 ) || ( output_type == STBIR_TYPE_UINT16 ) ) + { + int non_scaled = 0; + + // check if we can run unscaled - 0-255.0/0-65535.0 instead of 0-1.0 (which is a tiny bit faster when doing linear 8->8 or 16->16) + if ( ( !info->alpha_weight ) && ( !info->alpha_unweight ) ) // don't short circuit when alpha weighting (get everything to 0-1.0 as usual) + if ( ( ( input_type == STBIR_TYPE_UINT8 ) && ( output_type == STBIR_TYPE_UINT8 ) ) || ( ( input_type == STBIR_TYPE_UINT16 ) && ( output_type == STBIR_TYPE_UINT16 ) ) ) + non_scaled = 1; + + if ( info->output_pixel_layout_internal <= STBIRI_4CHANNEL ) + encode_pixels = encode_simple_scaled_or_not[ output_type == STBIR_TYPE_UINT16 ][ non_scaled ]; + else + encode_pixels = encode_alphas_scaled_or_not[ ( info->output_pixel_layout_internal - STBIRI_RGBA ) % ( STBIRI_AR-STBIRI_RGBA+1 ) ][ output_type == STBIR_TYPE_UINT16 ][ non_scaled ]; + } + else + { + if ( info->output_pixel_layout_internal <= STBIRI_4CHANNEL ) + encode_pixels = encode_simple[ output_type - STBIR_TYPE_UINT8_SRGB ]; + else + encode_pixels = encode_alphas[ ( info->output_pixel_layout_internal - STBIRI_RGBA ) % ( STBIRI_AR-STBIRI_RGBA+1 ) ][ output_type - STBIR_TYPE_UINT8_SRGB ]; + } + + info->input_type = input_type; + info->output_type = output_type; + info->decode_pixels = decode_pixels; + info->encode_pixels = encode_pixels; +} + +static void stbir__clip( int * outx, int * outsubw, int outw, double * u0, double * u1 ) +{ + double per, adj; + int over; + + // do left/top edge + if ( *outx < 0 ) + { + per = ( (double)*outx ) / ( (double)*outsubw ); // is negative + adj = per * ( *u1 - *u0 ); + *u0 -= adj; // increases u0 + *outx = 0; + } + + // do right/bot edge + over = outw - ( *outx + *outsubw ); + if ( over < 0 ) + { + per = ( (double)over ) / ( (double)*outsubw ); // is negative + adj = per * ( *u1 - *u0 ); + *u1 += adj; // decrease u1 + *outsubw = outw - *outx; + } +} + +// converts a double to a rational that has less than one float bit of error (returns 0 if unable to do so) +static int stbir__double_to_rational(double f, stbir_uint32 limit, stbir_uint32 *numer, stbir_uint32 *denom, int limit_denom ) // limit_denom (1) or limit numer (0) +{ + double err; + stbir_uint64 top, bot; + stbir_uint64 numer_last = 0; + stbir_uint64 denom_last = 1; + stbir_uint64 numer_estimate = 1; + stbir_uint64 denom_estimate = 0; + + // scale to past float error range + top = (stbir_uint64)( f * (double)(1 << 25) ); + bot = 1 << 25; + + // keep refining, but usually stops in a few loops - usually 5 for bad cases + for(;;) + { + stbir_uint64 est, temp; + + // hit limit, break out and do best full range estimate + if ( ( ( limit_denom ) ? denom_estimate : numer_estimate ) >= limit ) + break; + + // is the current error less than 1 bit of a float? if so, we're done + if ( denom_estimate ) + { + err = ( (double)numer_estimate / (double)denom_estimate ) - f; + if ( err < 0.0 ) err = -err; + if ( err < ( 1.0 / (double)(1<<24) ) ) + { + // yup, found it + *numer = (stbir_uint32) numer_estimate; + *denom = (stbir_uint32) denom_estimate; + return 1; + } + } + + // no more refinement bits left? break out and do full range estimate + if ( bot == 0 ) + break; + + // gcd the estimate bits + est = top / bot; + temp = top % bot; + top = bot; + bot = temp; + + // move remainders + temp = est * denom_estimate + denom_last; + denom_last = denom_estimate; + denom_estimate = temp; + + // move remainders + temp = est * numer_estimate + numer_last; + numer_last = numer_estimate; + numer_estimate = temp; + } + + // we didn't fine anything good enough for float, use a full range estimate + if ( limit_denom ) + { + numer_estimate= (stbir_uint64)( f * (double)limit + 0.5 ); + denom_estimate = limit; + } + else + { + numer_estimate = limit; + denom_estimate = (stbir_uint64)( ( (double)limit / f ) + 0.5 ); + } + + *numer = (stbir_uint32) numer_estimate; + *denom = (stbir_uint32) denom_estimate; + + err = ( denom_estimate ) ? ( ( (double)(stbir_uint32)numer_estimate / (double)(stbir_uint32)denom_estimate ) - f ) : 1.0; + if ( err < 0.0 ) err = -err; + return ( err < ( 1.0 / (double)(1<<24) ) ) ? 1 : 0; +} + +static int stbir__calculate_region_transform( stbir__scale_info * scale_info, int output_full_range, int * output_offset, int output_sub_range, int input_full_range, double input_s0, double input_s1 ) +{ + double output_range, input_range, output_s, input_s, ratio, scale; + + input_s = input_s1 - input_s0; + + // null area + if ( ( output_full_range == 0 ) || ( input_full_range == 0 ) || + ( output_sub_range == 0 ) || ( input_s <= stbir__small_float ) ) + return 0; + + // are either of the ranges completely out of bounds? + if ( ( *output_offset >= output_full_range ) || ( ( *output_offset + output_sub_range ) <= 0 ) || ( input_s0 >= (1.0f-stbir__small_float) ) || ( input_s1 <= stbir__small_float ) ) + return 0; + + output_range = (double)output_full_range; + input_range = (double)input_full_range; + + output_s = ( (double)output_sub_range) / output_range; + + // figure out the scaling to use + ratio = output_s / input_s; + + // save scale before clipping + scale = ( output_range / input_range ) * ratio; + scale_info->scale = (float)scale; + scale_info->inv_scale = (float)( 1.0 / scale ); + + // clip output area to left/right output edges (and adjust input area) + stbir__clip( output_offset, &output_sub_range, output_full_range, &input_s0, &input_s1 ); + + // recalc input area + input_s = input_s1 - input_s0; + + // after clipping do we have zero input area? + if ( input_s <= stbir__small_float ) + return 0; + + // calculate and store the starting source offsets in output pixel space + scale_info->pixel_shift = (float) ( input_s0 * ratio * output_range ); + + scale_info->scale_is_rational = stbir__double_to_rational( scale, ( scale <= 1.0 ) ? output_full_range : input_full_range, &scale_info->scale_numerator, &scale_info->scale_denominator, ( scale >= 1.0 ) ); + + scale_info->input_full_size = input_full_range; + scale_info->output_sub_size = output_sub_range; + + return 1; +} + + +static void stbir__init_and_set_layout( STBIR_RESIZE * resize, stbir_pixel_layout pixel_layout, stbir_datatype data_type ) +{ + resize->input_cb = 0; + resize->output_cb = 0; + resize->user_data = resize; + resize->samplers = 0; + resize->called_alloc = 0; + resize->horizontal_filter = STBIR_FILTER_DEFAULT; + resize->horizontal_filter_kernel = 0; resize->horizontal_filter_support = 0; + resize->vertical_filter = STBIR_FILTER_DEFAULT; + resize->vertical_filter_kernel = 0; resize->vertical_filter_support = 0; + resize->horizontal_edge = STBIR_EDGE_CLAMP; + resize->vertical_edge = STBIR_EDGE_CLAMP; + resize->input_s0 = 0; resize->input_t0 = 0; resize->input_s1 = 1; resize->input_t1 = 1; + resize->output_subx = 0; resize->output_suby = 0; resize->output_subw = resize->output_w; resize->output_subh = resize->output_h; + resize->input_data_type = data_type; + resize->output_data_type = data_type; + resize->input_pixel_layout_public = pixel_layout; + resize->output_pixel_layout_public = pixel_layout; + resize->needs_rebuild = 1; +} + +STBIRDEF void stbir_resize_init( STBIR_RESIZE * resize, + const void *input_pixels, int input_w, int input_h, int input_stride_in_bytes, // stride can be zero + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, // stride can be zero + stbir_pixel_layout pixel_layout, stbir_datatype data_type ) +{ + resize->input_pixels = input_pixels; + resize->input_w = input_w; + resize->input_h = input_h; + resize->input_stride_in_bytes = input_stride_in_bytes; + resize->output_pixels = output_pixels; + resize->output_w = output_w; + resize->output_h = output_h; + resize->output_stride_in_bytes = output_stride_in_bytes; + resize->fast_alpha = 0; + + stbir__init_and_set_layout( resize, pixel_layout, data_type ); +} + +// You can update parameters any time after resize_init +STBIRDEF void stbir_set_datatypes( STBIR_RESIZE * resize, stbir_datatype input_type, stbir_datatype output_type ) // by default, datatype from resize_init +{ + resize->input_data_type = input_type; + resize->output_data_type = output_type; + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + stbir__update_info_from_resize( resize->samplers, resize ); +} + +STBIRDEF void stbir_set_pixel_callbacks( STBIR_RESIZE * resize, stbir_input_callback * input_cb, stbir_output_callback * output_cb ) // no callbacks by default +{ + resize->input_cb = input_cb; + resize->output_cb = output_cb; + + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + { + resize->samplers->in_pixels_cb = input_cb; + resize->samplers->out_pixels_cb = output_cb; + } +} + +STBIRDEF void stbir_set_user_data( STBIR_RESIZE * resize, void * user_data ) // pass back STBIR_RESIZE* by default +{ + resize->user_data = user_data; + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + resize->samplers->user_data = user_data; +} + +STBIRDEF void stbir_set_buffer_ptrs( STBIR_RESIZE * resize, const void * input_pixels, int input_stride_in_bytes, void * output_pixels, int output_stride_in_bytes ) +{ + resize->input_pixels = input_pixels; + resize->input_stride_in_bytes = input_stride_in_bytes; + resize->output_pixels = output_pixels; + resize->output_stride_in_bytes = output_stride_in_bytes; + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + stbir__update_info_from_resize( resize->samplers, resize ); +} + + +STBIRDEF int stbir_set_edgemodes( STBIR_RESIZE * resize, stbir_edge horizontal_edge, stbir_edge vertical_edge ) // CLAMP by default +{ + resize->horizontal_edge = horizontal_edge; + resize->vertical_edge = vertical_edge; + resize->needs_rebuild = 1; + return 1; +} + +STBIRDEF int stbir_set_filters( STBIR_RESIZE * resize, stbir_filter horizontal_filter, stbir_filter vertical_filter ) // STBIR_DEFAULT_FILTER_UPSAMPLE/DOWNSAMPLE by default +{ + resize->horizontal_filter = horizontal_filter; + resize->vertical_filter = vertical_filter; + resize->needs_rebuild = 1; + return 1; +} + +STBIRDEF int stbir_set_filter_callbacks( STBIR_RESIZE * resize, stbir__kernel_callback * horizontal_filter, stbir__support_callback * horizontal_support, stbir__kernel_callback * vertical_filter, stbir__support_callback * vertical_support ) +{ + resize->horizontal_filter_kernel = horizontal_filter; resize->horizontal_filter_support = horizontal_support; + resize->vertical_filter_kernel = vertical_filter; resize->vertical_filter_support = vertical_support; + resize->needs_rebuild = 1; + return 1; +} + +STBIRDEF int stbir_set_pixel_layouts( STBIR_RESIZE * resize, stbir_pixel_layout input_pixel_layout, stbir_pixel_layout output_pixel_layout ) // sets new pixel layouts +{ + resize->input_pixel_layout_public = input_pixel_layout; + resize->output_pixel_layout_public = output_pixel_layout; + resize->needs_rebuild = 1; + return 1; +} + + +STBIRDEF int stbir_set_non_pm_alpha_speed_over_quality( STBIR_RESIZE * resize, int non_pma_alpha_speed_over_quality ) // sets alpha speed +{ + resize->fast_alpha = non_pma_alpha_speed_over_quality; + resize->needs_rebuild = 1; + return 1; +} + +STBIRDEF int stbir_set_input_subrect( STBIR_RESIZE * resize, double s0, double t0, double s1, double t1 ) // sets input region (full region by default) +{ + resize->input_s0 = s0; + resize->input_t0 = t0; + resize->input_s1 = s1; + resize->input_t1 = t1; + resize->needs_rebuild = 1; + + // are we inbounds? + if ( ( s1 < stbir__small_float ) || ( (s1-s0) < stbir__small_float ) || + ( t1 < stbir__small_float ) || ( (t1-t0) < stbir__small_float ) || + ( s0 > (1.0f-stbir__small_float) ) || + ( t0 > (1.0f-stbir__small_float) ) ) + return 0; + + return 1; +} + +STBIRDEF int stbir_set_output_pixel_subrect( STBIR_RESIZE * resize, int subx, int suby, int subw, int subh ) // sets input region (full region by default) +{ + resize->output_subx = subx; + resize->output_suby = suby; + resize->output_subw = subw; + resize->output_subh = subh; + resize->needs_rebuild = 1; + + // are we inbounds? + if ( ( subx >= resize->output_w ) || ( ( subx + subw ) <= 0 ) || ( suby >= resize->output_h ) || ( ( suby + subh ) <= 0 ) || ( subw == 0 ) || ( subh == 0 ) ) + return 0; + + return 1; +} + +STBIRDEF int stbir_set_pixel_subrect( STBIR_RESIZE * resize, int subx, int suby, int subw, int subh ) // sets both regions (full regions by default) +{ + double s0, t0, s1, t1; + + s0 = ( (double)subx ) / ( (double)resize->output_w ); + t0 = ( (double)suby ) / ( (double)resize->output_h ); + s1 = ( (double)(subx+subw) ) / ( (double)resize->output_w ); + t1 = ( (double)(suby+subh) ) / ( (double)resize->output_h ); + + resize->input_s0 = s0; + resize->input_t0 = t0; + resize->input_s1 = s1; + resize->input_t1 = t1; + resize->output_subx = subx; + resize->output_suby = suby; + resize->output_subw = subw; + resize->output_subh = subh; + resize->needs_rebuild = 1; + + // are we inbounds? + if ( ( subx >= resize->output_w ) || ( ( subx + subw ) <= 0 ) || ( suby >= resize->output_h ) || ( ( suby + subh ) <= 0 ) || ( subw == 0 ) || ( subh == 0 ) ) + return 0; + + return 1; +} + +static int stbir__perform_build( STBIR_RESIZE * resize, int splits ) +{ + stbir__contributors conservative = { 0, 0 }; + stbir__sampler horizontal, vertical; + int new_output_subx, new_output_suby; + stbir__info * out_info; + #ifdef STBIR_PROFILE + stbir__info profile_infod; // used to contain building profile info before everything is allocated + stbir__info * profile_info = &profile_infod; + #endif + + // have we already built the samplers? + if ( resize->samplers ) + return 0; + + #define STBIR_RETURN_ERROR_AND_ASSERT( exp ) STBIR_ASSERT( !(exp) ); if (exp) return 0; + STBIR_RETURN_ERROR_AND_ASSERT( (unsigned)resize->horizontal_filter >= STBIR_FILTER_OTHER) + STBIR_RETURN_ERROR_AND_ASSERT( (unsigned)resize->vertical_filter >= STBIR_FILTER_OTHER) + #undef STBIR_RETURN_ERROR_AND_ASSERT + + if ( splits <= 0 ) + return 0; + + STBIR_PROFILE_BUILD_FIRST_START( build ); + + new_output_subx = resize->output_subx; + new_output_suby = resize->output_suby; + + // do horizontal clip and scale calcs + if ( !stbir__calculate_region_transform( &horizontal.scale_info, resize->output_w, &new_output_subx, resize->output_subw, resize->input_w, resize->input_s0, resize->input_s1 ) ) + return 0; + + // do vertical clip and scale calcs + if ( !stbir__calculate_region_transform( &vertical.scale_info, resize->output_h, &new_output_suby, resize->output_subh, resize->input_h, resize->input_t0, resize->input_t1 ) ) + return 0; + + // if nothing to do, just return + if ( ( horizontal.scale_info.output_sub_size == 0 ) || ( vertical.scale_info.output_sub_size == 0 ) ) + return 0; + + stbir__set_sampler(&horizontal, resize->horizontal_filter, resize->horizontal_filter_kernel, resize->horizontal_filter_support, resize->horizontal_edge, &horizontal.scale_info, 1, resize->user_data ); + stbir__get_conservative_extents( &horizontal, &conservative, resize->user_data ); + stbir__set_sampler(&vertical, resize->vertical_filter, resize->horizontal_filter_kernel, resize->vertical_filter_support, resize->vertical_edge, &vertical.scale_info, 0, resize->user_data ); + + if ( ( vertical.scale_info.output_sub_size / splits ) < STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS ) // each split should be a minimum of 4 scanlines (handwavey choice) + { + splits = vertical.scale_info.output_sub_size / STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS; + if ( splits == 0 ) splits = 1; + } + + STBIR_PROFILE_BUILD_START( alloc ); + out_info = stbir__alloc_internal_mem_and_build_samplers( &horizontal, &vertical, &conservative, resize->input_pixel_layout_public, resize->output_pixel_layout_public, splits, new_output_subx, new_output_suby, resize->fast_alpha, resize->user_data STBIR_ONLY_PROFILE_BUILD_SET_INFO ); + STBIR_PROFILE_BUILD_END( alloc ); + STBIR_PROFILE_BUILD_END( build ); + + if ( out_info ) + { + resize->splits = splits; + resize->samplers = out_info; + resize->needs_rebuild = 0; + #ifdef STBIR_PROFILE + STBIR_MEMCPY( &out_info->profile, &profile_infod.profile, sizeof( out_info->profile ) ); + #endif + + // update anything that can be changed without recalcing samplers + stbir__update_info_from_resize( out_info, resize ); + + return splits; + } + + return 0; +} + +void stbir_free_samplers( STBIR_RESIZE * resize ) +{ + if ( resize->samplers ) + { + stbir__free_internal_mem( resize->samplers ); + resize->samplers = 0; + resize->called_alloc = 0; + } +} + +STBIRDEF int stbir_build_samplers_with_splits( STBIR_RESIZE * resize, int splits ) +{ + if ( ( resize->samplers == 0 ) || ( resize->needs_rebuild ) ) + { + if ( resize->samplers ) + stbir_free_samplers( resize ); + + resize->called_alloc = 1; + return stbir__perform_build( resize, splits ); + } + + STBIR_PROFILE_BUILD_CLEAR( resize->samplers ); + + return 1; +} + +STBIRDEF int stbir_build_samplers( STBIR_RESIZE * resize ) +{ + return stbir_build_samplers_with_splits( resize, 1 ); +} + +STBIRDEF int stbir_resize_extended( STBIR_RESIZE * resize ) +{ + int result; + + if ( ( resize->samplers == 0 ) || ( resize->needs_rebuild ) ) + { + int alloc_state = resize->called_alloc; // remember allocated state + + if ( resize->samplers ) + { + stbir__free_internal_mem( resize->samplers ); + resize->samplers = 0; + } + + if ( !stbir_build_samplers( resize ) ) + return 0; + + resize->called_alloc = alloc_state; + + // if build_samplers succeeded (above), but there are no samplers set, then + // the area to stretch into was zero pixels, so don't do anything and return + // success + if ( resize->samplers == 0 ) + return 1; + } + else + { + // didn't build anything - clear it + STBIR_PROFILE_BUILD_CLEAR( resize->samplers ); + } + + // do resize + result = stbir__perform_resize( resize->samplers, 0, resize->splits ); + + // if we alloced, then free + if ( !resize->called_alloc ) + { + stbir_free_samplers( resize ); + resize->samplers = 0; + } + + return result; +} + +STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start, int split_count ) +{ + STBIR_ASSERT( resize->samplers ); + + // if we're just doing the whole thing, call full + if ( ( split_start == -1 ) || ( ( split_start == 0 ) && ( split_count == resize->splits ) ) ) + return stbir_resize_extended( resize ); + + // you **must** build samplers first when using split resize + if ( ( resize->samplers == 0 ) || ( resize->needs_rebuild ) ) + return 0; + + if ( ( split_start >= resize->splits ) || ( split_start < 0 ) || ( ( split_start + split_count ) > resize->splits ) || ( split_count <= 0 ) ) + return 0; + + // do resize + return stbir__perform_resize( resize->samplers, split_start, split_count ); +} + +static int stbir__check_output_stuff( void ** ret_ptr, int * ret_pitch, void * output_pixels, int type_size, int output_w, int output_h, int output_stride_in_bytes, stbir_internal_pixel_layout pixel_layout ) +{ + size_t size; + int pitch; + void * ptr; + + pitch = output_w * type_size * stbir__pixel_channels[ pixel_layout ]; + if ( pitch == 0 ) + return 0; + + if ( output_stride_in_bytes == 0 ) + output_stride_in_bytes = pitch; + + if ( output_stride_in_bytes < pitch ) + return 0; + + size = output_stride_in_bytes * output_h; + if ( size == 0 ) + return 0; + + *ret_ptr = 0; + *ret_pitch = output_stride_in_bytes; + + if ( output_pixels == 0 ) + { + ptr = STBIR_MALLOC( size, 0 ); + if ( ptr == 0 ) + return 0; + + *ret_ptr = ptr; + *ret_pitch = pitch; + } + + return 1; +} + + +STBIRDEF unsigned char * stbir_resize_uint8_linear( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout ) +{ + STBIR_RESIZE resize; + unsigned char * optr; + int opitch; + + if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( unsigned char ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) + return 0; + + stbir_resize_init( &resize, + input_pixels, input_w, input_h, input_stride_in_bytes, + (optr) ? optr : output_pixels, output_w, output_h, opitch, + pixel_layout, STBIR_TYPE_UINT8 ); + + if ( !stbir_resize_extended( &resize ) ) + { + if ( optr ) + STBIR_FREE( optr, 0 ); + return 0; + } + + return (optr) ? optr : output_pixels; +} + +STBIRDEF unsigned char * stbir_resize_uint8_srgb( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout ) +{ + STBIR_RESIZE resize; + unsigned char * optr; + int opitch; + + if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( unsigned char ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) + return 0; + + stbir_resize_init( &resize, + input_pixels, input_w, input_h, input_stride_in_bytes, + (optr) ? optr : output_pixels, output_w, output_h, opitch, + pixel_layout, STBIR_TYPE_UINT8_SRGB ); + + if ( !stbir_resize_extended( &resize ) ) + { + if ( optr ) + STBIR_FREE( optr, 0 ); + return 0; + } + + return (optr) ? optr : output_pixels; +} + + +STBIRDEF float * stbir_resize_float_linear( const float *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout ) +{ + STBIR_RESIZE resize; + float * optr; + int opitch; + + if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( float ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) + return 0; + + stbir_resize_init( &resize, + input_pixels, input_w, input_h, input_stride_in_bytes, + (optr) ? optr : output_pixels, output_w, output_h, opitch, + pixel_layout, STBIR_TYPE_FLOAT ); + + if ( !stbir_resize_extended( &resize ) ) + { + if ( optr ) + STBIR_FREE( optr, 0 ); + return 0; + } + + return (optr) ? optr : output_pixels; +} + + +STBIRDEF void * stbir_resize( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout, stbir_datatype data_type, + stbir_edge edge, stbir_filter filter ) +{ + STBIR_RESIZE resize; + float * optr; + int opitch; + + if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, stbir__type_size[data_type], output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) + return 0; + + stbir_resize_init( &resize, + input_pixels, input_w, input_h, input_stride_in_bytes, + (optr) ? optr : output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, data_type ); + + resize.horizontal_edge = edge; + resize.vertical_edge = edge; + resize.horizontal_filter = filter; + resize.vertical_filter = filter; + + if ( !stbir_resize_extended( &resize ) ) + { + if ( optr ) + STBIR_FREE( optr, 0 ); + return 0; + } + + return (optr) ? optr : output_pixels; +} + +#ifdef STBIR_PROFILE + +STBIRDEF void stbir_resize_build_profile_info( STBIR_PROFILE_INFO * info, STBIR_RESIZE const * resize ) +{ + static char const * bdescriptions[6] = { "Building", "Allocating", "Horizontal sampler", "Vertical sampler", "Coefficient cleanup", "Coefficient piovot" } ; + stbir__info* samp = resize->samplers; + int i; + + typedef int testa[ (STBIR__ARRAY_SIZE( bdescriptions ) == (STBIR__ARRAY_SIZE( samp->profile.array )-1) )?1:-1]; + typedef int testb[ (sizeof( samp->profile.array ) == (sizeof(samp->profile.named)) )?1:-1]; + typedef int testc[ (sizeof( info->clocks ) >= (sizeof(samp->profile.named)) )?1:-1]; + + for( i = 0 ; i < STBIR__ARRAY_SIZE( bdescriptions ) ; i++) + info->clocks[i] = samp->profile.array[i+1]; + + info->total_clocks = samp->profile.named.total; + info->descriptions = bdescriptions; + info->count = STBIR__ARRAY_SIZE( bdescriptions ); +} + +STBIRDEF void stbir_resize_split_profile_info( STBIR_PROFILE_INFO * info, STBIR_RESIZE const * resize, int split_start, int split_count ) +{ + static char const * descriptions[7] = { "Looping", "Vertical sampling", "Horizontal sampling", "Scanline input", "Scanline output", "Alpha weighting", "Alpha unweighting" }; + stbir__per_split_info * split_info; + int s, i; + + typedef int testa[ (STBIR__ARRAY_SIZE( descriptions ) == (STBIR__ARRAY_SIZE( split_info->profile.array )-1) )?1:-1]; + typedef int testb[ (sizeof( split_info->profile.array ) == (sizeof(split_info->profile.named)) )?1:-1]; + typedef int testc[ (sizeof( info->clocks ) >= (sizeof(split_info->profile.named)) )?1:-1]; + + if ( split_start == -1 ) + { + split_start = 0; + split_count = resize->samplers->splits; + } + + if ( ( split_start >= resize->splits ) || ( split_start < 0 ) || ( ( split_start + split_count ) > resize->splits ) || ( split_count <= 0 ) ) + { + info->total_clocks = 0; + info->descriptions = 0; + info->count = 0; + return; + } + + split_info = resize->samplers->split_info + split_start; + + // sum up the profile from all the splits + for( i = 0 ; i < STBIR__ARRAY_SIZE( descriptions ) ; i++ ) + { + stbir_uint64 sum = 0; + for( s = 0 ; s < split_count ; s++ ) + sum += split_info[s].profile.array[i+1]; + info->clocks[i] = sum; + } + + info->total_clocks = split_info->profile.named.total; + info->descriptions = descriptions; + info->count = STBIR__ARRAY_SIZE( descriptions ); +} + +STBIRDEF void stbir_resize_extended_profile_info( STBIR_PROFILE_INFO * info, STBIR_RESIZE const * resize ) +{ + stbir_resize_split_profile_info( info, resize, -1, 0 ); +} + +#endif // STBIR_PROFILE + +#undef STBIR_BGR +#undef STBIR_1CHANNEL +#undef STBIR_2CHANNEL +#undef STBIR_RGB +#undef STBIR_RGBA +#undef STBIR_4CHANNEL +#undef STBIR_BGRA +#undef STBIR_ARGB +#undef STBIR_ABGR +#undef STBIR_RA +#undef STBIR_AR +#undef STBIR_RGBA_PM +#undef STBIR_BGRA_PM +#undef STBIR_ARGB_PM +#undef STBIR_ABGR_PM +#undef STBIR_RA_PM +#undef STBIR_AR_PM + +#endif // STB_IMAGE_RESIZE_IMPLEMENTATION + +#else // STB_IMAGE_RESIZE_HORIZONTALS&STB_IMAGE_RESIZE_DO_VERTICALS + +// we reinclude the header file to define all the horizontal functions +// specializing each function for the number of coeffs is 20-40% faster *OVERALL* + +// by including the header file again this way, we can still debug the functions + +#define STBIR_strs_join2( start, mid, end ) start##mid##end +#define STBIR_strs_join1( start, mid, end ) STBIR_strs_join2( start, mid, end ) + +#define STBIR_strs_join24( start, mid1, mid2, end ) start##mid1##mid2##end +#define STBIR_strs_join14( start, mid1, mid2, end ) STBIR_strs_join24( start, mid1, mid2, end ) + +#ifdef STB_IMAGE_RESIZE_DO_CODERS + +#ifdef stbir__decode_suffix +#define STBIR__CODER_NAME( name ) STBIR_strs_join1( name, _, stbir__decode_suffix ) +#else +#define STBIR__CODER_NAME( name ) name +#endif + +#ifdef stbir__decode_swizzle +#define stbir__decode_simdf8_flip(reg) STBIR_strs_join1( STBIR_strs_join1( STBIR_strs_join1( STBIR_strs_join1( stbir__simdf8_0123to,stbir__decode_order0,stbir__decode_order1),stbir__decode_order2,stbir__decode_order3),stbir__decode_order0,stbir__decode_order1),stbir__decode_order2,stbir__decode_order3)(reg, reg) +#define stbir__decode_simdf4_flip(reg) STBIR_strs_join1( STBIR_strs_join1( stbir__simdf_0123to,stbir__decode_order0,stbir__decode_order1),stbir__decode_order2,stbir__decode_order3)(reg, reg) +#define stbir__encode_simdf8_unflip(reg) STBIR_strs_join1( STBIR_strs_join1( STBIR_strs_join1( STBIR_strs_join1( stbir__simdf8_0123to,stbir__encode_order0,stbir__encode_order1),stbir__encode_order2,stbir__encode_order3),stbir__encode_order0,stbir__encode_order1),stbir__encode_order2,stbir__encode_order3)(reg, reg) +#define stbir__encode_simdf4_unflip(reg) STBIR_strs_join1( STBIR_strs_join1( stbir__simdf_0123to,stbir__encode_order0,stbir__encode_order1),stbir__encode_order2,stbir__encode_order3)(reg, reg) +#else +#define stbir__decode_order0 0 +#define stbir__decode_order1 1 +#define stbir__decode_order2 2 +#define stbir__decode_order3 3 +#define stbir__encode_order0 0 +#define stbir__encode_order1 1 +#define stbir__encode_order2 2 +#define stbir__encode_order3 3 +#define stbir__decode_simdf8_flip(reg) +#define stbir__decode_simdf4_flip(reg) +#define stbir__encode_simdf8_unflip(reg) +#define stbir__encode_simdf4_unflip(reg) +#endif + +#ifdef STBIR_SIMD8 +#define stbir__encode_simdfX_unflip stbir__encode_simdf8_unflip +#else +#define stbir__encode_simdfX_unflip stbir__encode_simdf4_unflip +#endif + +static void STBIR__CODER_NAME( stbir__decode_uint8_linear_scaled )( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const*)inputp; + + #ifdef STBIR_SIMD + unsigned char const * end_input_m16 = input + width_times_channels - 16; + if ( width_times_channels >= 16 ) + { + decode_end -= 16; + for(;;) + { + #ifdef STBIR_SIMD8 + stbir__simdi i; stbir__simdi8 o0,o1; + stbir__simdf8 of0, of1; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi8_expand_u8_to_u32( o0, o1, i ); + stbir__simdi8_convert_i32_to_float( of0, o0 ); + stbir__simdi8_convert_i32_to_float( of1, o1 ); + stbir__simdf8_mult( of0, of0, STBIR_max_uint8_as_float_inverted8); + stbir__simdf8_mult( of1, of1, STBIR_max_uint8_as_float_inverted8); + stbir__decode_simdf8_flip( of0 ); + stbir__decode_simdf8_flip( of1 ); + stbir__simdf8_store( decode + 0, of0 ); + stbir__simdf8_store( decode + 8, of1 ); + #else + stbir__simdi i, o0, o1, o2, o3; + stbir__simdf of0, of1, of2, of3; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi_expand_u8_to_u32( o0,o1,o2,o3,i); + stbir__simdi_convert_i32_to_float( of0, o0 ); + stbir__simdi_convert_i32_to_float( of1, o1 ); + stbir__simdi_convert_i32_to_float( of2, o2 ); + stbir__simdi_convert_i32_to_float( of3, o3 ); + stbir__simdf_mult( of0, of0, STBIR__CONSTF(STBIR_max_uint8_as_float_inverted) ); + stbir__simdf_mult( of1, of1, STBIR__CONSTF(STBIR_max_uint8_as_float_inverted) ); + stbir__simdf_mult( of2, of2, STBIR__CONSTF(STBIR_max_uint8_as_float_inverted) ); + stbir__simdf_mult( of3, of3, STBIR__CONSTF(STBIR_max_uint8_as_float_inverted) ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__decode_simdf4_flip( of2 ); + stbir__decode_simdf4_flip( of3 ); + stbir__simdf_store( decode + 0, of0 ); + stbir__simdf_store( decode + 4, of1 ); + stbir__simdf_store( decode + 8, of2 ); + stbir__simdf_store( decode + 12, of3 ); + #endif + decode += 16; + input += 16; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 16 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m16; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = ((float)(input[stbir__decode_order0])) * stbir__max_uint8_as_float_inverted; + decode[1-4] = ((float)(input[stbir__decode_order1])) * stbir__max_uint8_as_float_inverted; + decode[2-4] = ((float)(input[stbir__decode_order2])) * stbir__max_uint8_as_float_inverted; + decode[3-4] = ((float)(input[stbir__decode_order3])) * stbir__max_uint8_as_float_inverted; + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = ((float)(input[stbir__decode_order0])) * stbir__max_uint8_as_float_inverted; + #if stbir__coder_min_num >= 2 + decode[1] = ((float)(input[stbir__decode_order1])) * stbir__max_uint8_as_float_inverted; + #endif + #if stbir__coder_min_num >= 3 + decode[2] = ((float)(input[stbir__decode_order2])) * stbir__max_uint8_as_float_inverted; + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif +} + +static void STBIR__CODER_NAME( stbir__encode_uint8_linear_scaled )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char *) outputp; + unsigned char * end_output = ( (unsigned char *) output ) + width_times_channels; + + #ifdef STBIR_SIMD + if ( width_times_channels >= stbir__simdfX_float_count*2 ) + { + float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; + end_output -= stbir__simdfX_float_count*2; + for(;;) + { + stbir__simdfX e0, e1; + stbir__simdi i; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_madd_mem( e0, STBIR_simd_point5X, STBIR_max_uint8_as_floatX, encode ); + stbir__simdfX_madd_mem( e1, STBIR_simd_point5X, STBIR_max_uint8_as_floatX, encode+stbir__simdfX_float_count ); + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + #ifdef STBIR_SIMD8 + stbir__simdf8_pack_to_16bytes( i, e0, e1 ); + stbir__simdi_store( output, i ); + #else + stbir__simdf_pack_to_8bytes( i, e0, e1 ); + stbir__simdi_store2( output, i ); + #endif + encode += stbir__simdfX_float_count*2; + output += stbir__simdfX_float_count*2; + if ( output <= end_output ) + continue; + if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + stbir__simdf e0; + stbir__simdi i0; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e0, encode ); + stbir__simdf_madd( e0, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint8_as_float), e0 ); + stbir__encode_simdf4_unflip( e0 ); + stbir__simdf_pack_to_8bytes( i0, e0, e0 ); // only use first 4 + *(int*)(output-4) = stbir__simdi_to_int( i0 ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + stbir__simdf e0; + STBIR_NO_UNROLL(encode); + stbir__simdf_madd1_mem( e0, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint8_as_float), encode+stbir__encode_order0 ); output[0] = stbir__simdf_convert_float_to_uint8( e0 ); + #if stbir__coder_min_num >= 2 + stbir__simdf_madd1_mem( e0, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint8_as_float), encode+stbir__encode_order1 ); output[1] = stbir__simdf_convert_float_to_uint8( e0 ); + #endif + #if stbir__coder_min_num >= 3 + stbir__simdf_madd1_mem( e0, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint8_as_float), encode+stbir__encode_order2 ); output[2] = stbir__simdf_convert_float_to_uint8( e0 ); + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + float f; + f = encode[stbir__encode_order0] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[0-4] = (unsigned char)f; + f = encode[stbir__encode_order1] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[1-4] = (unsigned char)f; + f = encode[stbir__encode_order2] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[2-4] = (unsigned char)f; + f = encode[stbir__encode_order3] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[3-4] = (unsigned char)f; + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + float f; + STBIR_NO_UNROLL(encode); + f = encode[stbir__encode_order0] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[0] = (unsigned char)f; + #if stbir__coder_min_num >= 2 + f = encode[stbir__encode_order1] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[1] = (unsigned char)f; + #endif + #if stbir__coder_min_num >= 3 + f = encode[stbir__encode_order2] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[2] = (unsigned char)f; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + #endif +} + +static void STBIR__CODER_NAME(stbir__decode_uint8_linear)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const*)inputp; + + #ifdef STBIR_SIMD + unsigned char const * end_input_m16 = input + width_times_channels - 16; + if ( width_times_channels >= 16 ) + { + decode_end -= 16; + for(;;) + { + #ifdef STBIR_SIMD8 + stbir__simdi i; stbir__simdi8 o0,o1; + stbir__simdf8 of0, of1; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi8_expand_u8_to_u32( o0, o1, i ); + stbir__simdi8_convert_i32_to_float( of0, o0 ); + stbir__simdi8_convert_i32_to_float( of1, o1 ); + stbir__decode_simdf8_flip( of0 ); + stbir__decode_simdf8_flip( of1 ); + stbir__simdf8_store( decode + 0, of0 ); + stbir__simdf8_store( decode + 8, of1 ); + #else + stbir__simdi i, o0, o1, o2, o3; + stbir__simdf of0, of1, of2, of3; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi_expand_u8_to_u32( o0,o1,o2,o3,i); + stbir__simdi_convert_i32_to_float( of0, o0 ); + stbir__simdi_convert_i32_to_float( of1, o1 ); + stbir__simdi_convert_i32_to_float( of2, o2 ); + stbir__simdi_convert_i32_to_float( of3, o3 ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__decode_simdf4_flip( of2 ); + stbir__decode_simdf4_flip( of3 ); + stbir__simdf_store( decode + 0, of0 ); + stbir__simdf_store( decode + 4, of1 ); + stbir__simdf_store( decode + 8, of2 ); + stbir__simdf_store( decode + 12, of3 ); +#endif + decode += 16; + input += 16; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 16 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m16; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = ((float)(input[stbir__decode_order0])); + decode[1-4] = ((float)(input[stbir__decode_order1])); + decode[2-4] = ((float)(input[stbir__decode_order2])); + decode[3-4] = ((float)(input[stbir__decode_order3])); + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = ((float)(input[stbir__decode_order0])); + #if stbir__coder_min_num >= 2 + decode[1] = ((float)(input[stbir__decode_order1])); + #endif + #if stbir__coder_min_num >= 3 + decode[2] = ((float)(input[stbir__decode_order2])); + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif +} + +static void STBIR__CODER_NAME( stbir__encode_uint8_linear )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char *) outputp; + unsigned char * end_output = ( (unsigned char *) output ) + width_times_channels; + + #ifdef STBIR_SIMD + if ( width_times_channels >= stbir__simdfX_float_count*2 ) + { + float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; + end_output -= stbir__simdfX_float_count*2; + for(;;) + { + stbir__simdfX e0, e1; + stbir__simdi i; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_add_mem( e0, STBIR_simd_point5X, encode ); + stbir__simdfX_add_mem( e1, STBIR_simd_point5X, encode+stbir__simdfX_float_count ); + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + #ifdef STBIR_SIMD8 + stbir__simdf8_pack_to_16bytes( i, e0, e1 ); + stbir__simdi_store( output, i ); + #else + stbir__simdf_pack_to_8bytes( i, e0, e1 ); + stbir__simdi_store2( output, i ); + #endif + encode += stbir__simdfX_float_count*2; + output += stbir__simdfX_float_count*2; + if ( output <= end_output ) + continue; + if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + stbir__simdf e0; + stbir__simdi i0; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e0, encode ); + stbir__simdf_add( e0, STBIR__CONSTF(STBIR_simd_point5), e0 ); + stbir__encode_simdf4_unflip( e0 ); + stbir__simdf_pack_to_8bytes( i0, e0, e0 ); // only use first 4 + *(int*)(output-4) = stbir__simdi_to_int( i0 ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + float f; + f = encode[stbir__encode_order0] + 0.5f; STBIR_CLAMP(f, 0, 255); output[0-4] = (unsigned char)f; + f = encode[stbir__encode_order1] + 0.5f; STBIR_CLAMP(f, 0, 255); output[1-4] = (unsigned char)f; + f = encode[stbir__encode_order2] + 0.5f; STBIR_CLAMP(f, 0, 255); output[2-4] = (unsigned char)f; + f = encode[stbir__encode_order3] + 0.5f; STBIR_CLAMP(f, 0, 255); output[3-4] = (unsigned char)f; + output += 4; + encode += 4; + } + output -= 4; + #endif + + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + float f; + STBIR_NO_UNROLL(encode); + f = encode[stbir__encode_order0] + 0.5f; STBIR_CLAMP(f, 0, 255); output[0] = (unsigned char)f; + #if stbir__coder_min_num >= 2 + f = encode[stbir__encode_order1] + 0.5f; STBIR_CLAMP(f, 0, 255); output[1] = (unsigned char)f; + #endif + #if stbir__coder_min_num >= 3 + f = encode[stbir__encode_order2] + 0.5f; STBIR_CLAMP(f, 0, 255); output[2] = (unsigned char)f; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif +} + +static void STBIR__CODER_NAME(stbir__decode_uint8_srgb)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float const * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const *)inputp; + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + decode[0-4] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order0 ] ]; + decode[1-4] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order1 ] ]; + decode[2-4] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order2 ] ]; + decode[3-4] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order3 ] ]; + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order0 ] ]; + #if stbir__coder_min_num >= 2 + decode[1] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order1 ] ]; + #endif + #if stbir__coder_min_num >= 3 + decode[2] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order2 ] ]; + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif +} + +#define stbir__min_max_shift20( i, f ) \ + stbir__simdf_max( f, f, stbir_simdf_casti(STBIR__CONSTI( STBIR_almost_zero )) ); \ + stbir__simdf_min( f, f, stbir_simdf_casti(STBIR__CONSTI( STBIR_almost_one )) ); \ + stbir__simdi_32shr( i, stbir_simdi_castf( f ), 20 ); + +#define stbir__scale_and_convert( i, f ) \ + stbir__simdf_madd( f, STBIR__CONSTF( STBIR_simd_point5 ), STBIR__CONSTF( STBIR_max_uint8_as_float ), f ); \ + stbir__simdf_max( f, f, stbir__simdf_zeroP() ); \ + stbir__simdf_min( f, f, STBIR__CONSTF( STBIR_max_uint8_as_float ) ); \ + stbir__simdf_convert_float_to_i32( i, f ); + +#define stbir__linear_to_srgb_finish( i, f ) \ +{ \ + stbir__simdi temp; \ + stbir__simdi_32shr( temp, stbir_simdi_castf( f ), 12 ) ; \ + stbir__simdi_and( temp, temp, STBIR__CONSTI(STBIR_mastissa_mask) ); \ + stbir__simdi_or( temp, temp, STBIR__CONSTI(STBIR_topscale) ); \ + stbir__simdi_16madd( i, i, temp ); \ + stbir__simdi_32shr( i, i, 16 ); \ +} + +#define stbir__simdi_table_lookup2( v0,v1, table ) \ +{ \ + stbir__simdi_u32 temp0,temp1; \ + temp0.m128i_i128 = v0; \ + temp1.m128i_i128 = v1; \ + temp0.m128i_u32[0] = table[temp0.m128i_i32[0]]; temp0.m128i_u32[1] = table[temp0.m128i_i32[1]]; temp0.m128i_u32[2] = table[temp0.m128i_i32[2]]; temp0.m128i_u32[3] = table[temp0.m128i_i32[3]]; \ + temp1.m128i_u32[0] = table[temp1.m128i_i32[0]]; temp1.m128i_u32[1] = table[temp1.m128i_i32[1]]; temp1.m128i_u32[2] = table[temp1.m128i_i32[2]]; temp1.m128i_u32[3] = table[temp1.m128i_i32[3]]; \ + v0 = temp0.m128i_i128; \ + v1 = temp1.m128i_i128; \ +} + +#define stbir__simdi_table_lookup3( v0,v1,v2, table ) \ +{ \ + stbir__simdi_u32 temp0,temp1,temp2; \ + temp0.m128i_i128 = v0; \ + temp1.m128i_i128 = v1; \ + temp2.m128i_i128 = v2; \ + temp0.m128i_u32[0] = table[temp0.m128i_i32[0]]; temp0.m128i_u32[1] = table[temp0.m128i_i32[1]]; temp0.m128i_u32[2] = table[temp0.m128i_i32[2]]; temp0.m128i_u32[3] = table[temp0.m128i_i32[3]]; \ + temp1.m128i_u32[0] = table[temp1.m128i_i32[0]]; temp1.m128i_u32[1] = table[temp1.m128i_i32[1]]; temp1.m128i_u32[2] = table[temp1.m128i_i32[2]]; temp1.m128i_u32[3] = table[temp1.m128i_i32[3]]; \ + temp2.m128i_u32[0] = table[temp2.m128i_i32[0]]; temp2.m128i_u32[1] = table[temp2.m128i_i32[1]]; temp2.m128i_u32[2] = table[temp2.m128i_i32[2]]; temp2.m128i_u32[3] = table[temp2.m128i_i32[3]]; \ + v0 = temp0.m128i_i128; \ + v1 = temp1.m128i_i128; \ + v2 = temp2.m128i_i128; \ +} + +#define stbir__simdi_table_lookup4( v0,v1,v2,v3, table ) \ +{ \ + stbir__simdi_u32 temp0,temp1,temp2,temp3; \ + temp0.m128i_i128 = v0; \ + temp1.m128i_i128 = v1; \ + temp2.m128i_i128 = v2; \ + temp3.m128i_i128 = v3; \ + temp0.m128i_u32[0] = table[temp0.m128i_i32[0]]; temp0.m128i_u32[1] = table[temp0.m128i_i32[1]]; temp0.m128i_u32[2] = table[temp0.m128i_i32[2]]; temp0.m128i_u32[3] = table[temp0.m128i_i32[3]]; \ + temp1.m128i_u32[0] = table[temp1.m128i_i32[0]]; temp1.m128i_u32[1] = table[temp1.m128i_i32[1]]; temp1.m128i_u32[2] = table[temp1.m128i_i32[2]]; temp1.m128i_u32[3] = table[temp1.m128i_i32[3]]; \ + temp2.m128i_u32[0] = table[temp2.m128i_i32[0]]; temp2.m128i_u32[1] = table[temp2.m128i_i32[1]]; temp2.m128i_u32[2] = table[temp2.m128i_i32[2]]; temp2.m128i_u32[3] = table[temp2.m128i_i32[3]]; \ + temp3.m128i_u32[0] = table[temp3.m128i_i32[0]]; temp3.m128i_u32[1] = table[temp3.m128i_i32[1]]; temp3.m128i_u32[2] = table[temp3.m128i_i32[2]]; temp3.m128i_u32[3] = table[temp3.m128i_i32[3]]; \ + v0 = temp0.m128i_i128; \ + v1 = temp1.m128i_i128; \ + v2 = temp2.m128i_i128; \ + v3 = temp3.m128i_i128; \ +} + +static void STBIR__CODER_NAME( stbir__encode_uint8_srgb )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char*) outputp; + unsigned char * end_output = ( (unsigned char*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + stbir_uint32 const * to_srgb = fp32_to_srgb8_tab4 - (127-13)*8; + + if ( width_times_channels >= 16 ) + { + float const * end_encode_m16 = encode + width_times_channels - 16; + end_output -= 16; + for(;;) + { + stbir__simdf f0, f1, f2, f3; + stbir__simdi i0, i1, i2, i3; + STBIR_SIMD_NO_UNROLL(encode); + + stbir__simdf_load4_transposed( f0, f1, f2, f3, encode ); + + stbir__min_max_shift20( i0, f0 ); + stbir__min_max_shift20( i1, f1 ); + stbir__min_max_shift20( i2, f2 ); + stbir__min_max_shift20( i3, f3 ); + + stbir__simdi_table_lookup4( i0, i1, i2, i3, to_srgb ); + + stbir__linear_to_srgb_finish( i0, f0 ); + stbir__linear_to_srgb_finish( i1, f1 ); + stbir__linear_to_srgb_finish( i2, f2 ); + stbir__linear_to_srgb_finish( i3, f3 ); + + stbir__interleave_pack_and_store_16_u8( output, STBIR_strs_join1(i, ,stbir__encode_order0), STBIR_strs_join1(i, ,stbir__encode_order1), STBIR_strs_join1(i, ,stbir__encode_order2), STBIR_strs_join1(i, ,stbir__encode_order3) ); + + encode += 16; + output += 16; + if ( output <= end_output ) + continue; + if ( output == ( end_output + 16 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m16; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while ( output <= end_output ) + { + STBIR_SIMD_NO_UNROLL(encode); + + output[0-4] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order0] ); + output[1-4] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order1] ); + output[2-4] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order2] ); + output[3-4] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order3] ); + + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + STBIR_NO_UNROLL(encode); + output[0] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order0] ); + #if stbir__coder_min_num >= 2 + output[1] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order1] ); + #endif + #if stbir__coder_min_num >= 3 + output[2] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order2] ); + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif +} + +#if ( stbir__coder_min_num == 4 ) || ( ( stbir__coder_min_num == 1 ) && ( !defined(stbir__decode_swizzle) ) ) + +static void STBIR__CODER_NAME(stbir__decode_uint8_srgb4_linearalpha)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float const * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const *)inputp; + do { + decode[0] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order0] ]; + decode[1] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order1] ]; + decode[2] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order2] ]; + decode[3] = ( (float) input[stbir__decode_order3] ) * stbir__max_uint8_as_float_inverted; + input += 4; + decode += 4; + } while( decode < decode_end ); +} + + +static void STBIR__CODER_NAME( stbir__encode_uint8_srgb4_linearalpha )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char*) outputp; + unsigned char * end_output = ( (unsigned char*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + stbir_uint32 const * to_srgb = fp32_to_srgb8_tab4 - (127-13)*8; + + if ( width_times_channels >= 16 ) + { + float const * end_encode_m16 = encode + width_times_channels - 16; + end_output -= 16; + for(;;) + { + stbir__simdf f0, f1, f2, f3; + stbir__simdi i0, i1, i2, i3; + + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdf_load4_transposed( f0, f1, f2, f3, encode ); + + stbir__min_max_shift20( i0, f0 ); + stbir__min_max_shift20( i1, f1 ); + stbir__min_max_shift20( i2, f2 ); + stbir__scale_and_convert( i3, f3 ); + + stbir__simdi_table_lookup3( i0, i1, i2, to_srgb ); + + stbir__linear_to_srgb_finish( i0, f0 ); + stbir__linear_to_srgb_finish( i1, f1 ); + stbir__linear_to_srgb_finish( i2, f2 ); + + stbir__interleave_pack_and_store_16_u8( output, STBIR_strs_join1(i, ,stbir__encode_order0), STBIR_strs_join1(i, ,stbir__encode_order1), STBIR_strs_join1(i, ,stbir__encode_order2), STBIR_strs_join1(i, ,stbir__encode_order3) ); + + output += 16; + encode += 16; + + if ( output <= end_output ) + continue; + if ( output == ( end_output + 16 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m16; + } + return; + } + #endif + + do { + float f; + STBIR_SIMD_NO_UNROLL(encode); + + output[stbir__decode_order0] = stbir__linear_to_srgb_uchar( encode[0] ); + output[stbir__decode_order1] = stbir__linear_to_srgb_uchar( encode[1] ); + output[stbir__decode_order2] = stbir__linear_to_srgb_uchar( encode[2] ); + + f = encode[3] * stbir__max_uint8_as_float + 0.5f; + STBIR_CLAMP(f, 0, 255); + output[stbir__decode_order3] = (unsigned char) f; + + output += 4; + encode += 4; + } while( output < end_output ); +} + +#endif + +#if ( stbir__coder_min_num == 2 ) || ( ( stbir__coder_min_num == 1 ) && ( !defined(stbir__decode_swizzle) ) ) + +static void STBIR__CODER_NAME(stbir__decode_uint8_srgb2_linearalpha)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float const * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const *)inputp; + decode += 4; + while( decode <= decode_end ) + { + decode[0-4] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order0] ]; + decode[1-4] = ( (float) input[stbir__decode_order1] ) * stbir__max_uint8_as_float_inverted; + decode[2-4] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order0+2] ]; + decode[3-4] = ( (float) input[stbir__decode_order1+2] ) * stbir__max_uint8_as_float_inverted; + input += 4; + decode += 4; + } + decode -= 4; + if( decode < decode_end ) + { + decode[0] = stbir__srgb_uchar_to_linear_float[ stbir__decode_order0 ]; + decode[1] = ( (float) input[stbir__decode_order1] ) * stbir__max_uint8_as_float_inverted; + } +} + +static void STBIR__CODER_NAME( stbir__encode_uint8_srgb2_linearalpha )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char*) outputp; + unsigned char * end_output = ( (unsigned char*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + stbir_uint32 const * to_srgb = fp32_to_srgb8_tab4 - (127-13)*8; + + if ( width_times_channels >= 16 ) + { + float const * end_encode_m16 = encode + width_times_channels - 16; + end_output -= 16; + for(;;) + { + stbir__simdf f0, f1, f2, f3; + stbir__simdi i0, i1, i2, i3; + + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdf_load4_transposed( f0, f1, f2, f3, encode ); + + stbir__min_max_shift20( i0, f0 ); + stbir__scale_and_convert( i1, f1 ); + stbir__min_max_shift20( i2, f2 ); + stbir__scale_and_convert( i3, f3 ); + + stbir__simdi_table_lookup2( i0, i2, to_srgb ); + + stbir__linear_to_srgb_finish( i0, f0 ); + stbir__linear_to_srgb_finish( i2, f2 ); + + stbir__interleave_pack_and_store_16_u8( output, STBIR_strs_join1(i, ,stbir__encode_order0), STBIR_strs_join1(i, ,stbir__encode_order1), STBIR_strs_join1(i, ,stbir__encode_order2), STBIR_strs_join1(i, ,stbir__encode_order3) ); + + output += 16; + encode += 16; + if ( output <= end_output ) + continue; + if ( output == ( end_output + 16 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m16; + } + return; + } + #endif + + do { + float f; + STBIR_SIMD_NO_UNROLL(encode); + + output[stbir__decode_order0] = stbir__linear_to_srgb_uchar( encode[0] ); + + f = encode[1] * stbir__max_uint8_as_float + 0.5f; + STBIR_CLAMP(f, 0, 255); + output[stbir__decode_order1] = (unsigned char) f; + + output += 2; + encode += 2; + } while( output < end_output ); +} + +#endif + +static void STBIR__CODER_NAME(stbir__decode_uint16_linear_scaled)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned short const * input = (unsigned short const *)inputp; + + #ifdef STBIR_SIMD + unsigned short const * end_input_m8 = input + width_times_channels - 8; + if ( width_times_channels >= 8 ) + { + decode_end -= 8; + for(;;) + { + #ifdef STBIR_SIMD8 + stbir__simdi i; stbir__simdi8 o; + stbir__simdf8 of; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi8_expand_u16_to_u32( o, i ); + stbir__simdi8_convert_i32_to_float( of, o ); + stbir__simdf8_mult( of, of, STBIR_max_uint16_as_float_inverted8); + stbir__decode_simdf8_flip( of ); + stbir__simdf8_store( decode + 0, of ); + #else + stbir__simdi i, o0, o1; + stbir__simdf of0, of1; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi_expand_u16_to_u32( o0,o1,i ); + stbir__simdi_convert_i32_to_float( of0, o0 ); + stbir__simdi_convert_i32_to_float( of1, o1 ); + stbir__simdf_mult( of0, of0, STBIR__CONSTF(STBIR_max_uint16_as_float_inverted) ); + stbir__simdf_mult( of1, of1, STBIR__CONSTF(STBIR_max_uint16_as_float_inverted)); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__simdf_store( decode + 0, of0 ); + stbir__simdf_store( decode + 4, of1 ); + #endif + decode += 8; + input += 8; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 8 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m8; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = ((float)(input[stbir__decode_order0])) * stbir__max_uint16_as_float_inverted; + decode[1-4] = ((float)(input[stbir__decode_order1])) * stbir__max_uint16_as_float_inverted; + decode[2-4] = ((float)(input[stbir__decode_order2])) * stbir__max_uint16_as_float_inverted; + decode[3-4] = ((float)(input[stbir__decode_order3])) * stbir__max_uint16_as_float_inverted; + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = ((float)(input[stbir__decode_order0])) * stbir__max_uint16_as_float_inverted; + #if stbir__coder_min_num >= 2 + decode[1] = ((float)(input[stbir__decode_order1])) * stbir__max_uint16_as_float_inverted; + #endif + #if stbir__coder_min_num >= 3 + decode[2] = ((float)(input[stbir__decode_order2])) * stbir__max_uint16_as_float_inverted; + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif +} + + +static void STBIR__CODER_NAME(stbir__encode_uint16_linear_scaled)( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned short STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned short*) outputp; + unsigned short * end_output = ( (unsigned short*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + { + if ( width_times_channels >= stbir__simdfX_float_count*2 ) + { + float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; + end_output -= stbir__simdfX_float_count*2; + for(;;) + { + stbir__simdfX e0, e1; + stbir__simdiX i; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_madd_mem( e0, STBIR_simd_point5X, STBIR_max_uint16_as_floatX, encode ); + stbir__simdfX_madd_mem( e1, STBIR_simd_point5X, STBIR_max_uint16_as_floatX, encode+stbir__simdfX_float_count ); + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + stbir__simdfX_pack_to_words( i, e0, e1 ); + stbir__simdiX_store( output, i ); + encode += stbir__simdfX_float_count*2; + output += stbir__simdfX_float_count*2; + if ( output <= end_output ) + continue; + if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + stbir__simdf e; + stbir__simdi i; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e, encode ); + stbir__simdf_madd( e, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint16_as_float), e ); + stbir__encode_simdf4_unflip( e ); + stbir__simdf_pack_to_8words( i, e, e ); // only use first 4 + stbir__simdi_store2( output-4, i ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + stbir__simdf e; + STBIR_NO_UNROLL(encode); + stbir__simdf_madd1_mem( e, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint16_as_float), encode+stbir__encode_order0 ); output[0] = stbir__simdf_convert_float_to_short( e ); + #if stbir__coder_min_num >= 2 + stbir__simdf_madd1_mem( e, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint16_as_float), encode+stbir__encode_order1 ); output[1] = stbir__simdf_convert_float_to_short( e ); + #endif + #if stbir__coder_min_num >= 3 + stbir__simdf_madd1_mem( e, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint16_as_float), encode+stbir__encode_order2 ); output[2] = stbir__simdf_convert_float_to_short( e ); + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + float f; + STBIR_SIMD_NO_UNROLL(encode); + f = encode[stbir__encode_order0] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[0-4] = (unsigned short)f; + f = encode[stbir__encode_order1] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[1-4] = (unsigned short)f; + f = encode[stbir__encode_order2] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[2-4] = (unsigned short)f; + f = encode[stbir__encode_order3] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[3-4] = (unsigned short)f; + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + float f; + STBIR_NO_UNROLL(encode); + f = encode[stbir__encode_order0] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[0] = (unsigned short)f; + #if stbir__coder_min_num >= 2 + f = encode[stbir__encode_order1] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[1] = (unsigned short)f; + #endif + #if stbir__coder_min_num >= 3 + f = encode[stbir__encode_order2] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[2] = (unsigned short)f; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + #endif +} + +static void STBIR__CODER_NAME(stbir__decode_uint16_linear)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned short const * input = (unsigned short const *)inputp; + + #ifdef STBIR_SIMD + unsigned short const * end_input_m8 = input + width_times_channels - 8; + if ( width_times_channels >= 8 ) + { + decode_end -= 8; + for(;;) + { + #ifdef STBIR_SIMD8 + stbir__simdi i; stbir__simdi8 o; + stbir__simdf8 of; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi8_expand_u16_to_u32( o, i ); + stbir__simdi8_convert_i32_to_float( of, o ); + stbir__decode_simdf8_flip( of ); + stbir__simdf8_store( decode + 0, of ); + #else + stbir__simdi i, o0, o1; + stbir__simdf of0, of1; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi_expand_u16_to_u32( o0, o1, i ); + stbir__simdi_convert_i32_to_float( of0, o0 ); + stbir__simdi_convert_i32_to_float( of1, o1 ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__simdf_store( decode + 0, of0 ); + stbir__simdf_store( decode + 4, of1 ); + #endif + decode += 8; + input += 8; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 8 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m8; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = ((float)(input[stbir__decode_order0])); + decode[1-4] = ((float)(input[stbir__decode_order1])); + decode[2-4] = ((float)(input[stbir__decode_order2])); + decode[3-4] = ((float)(input[stbir__decode_order3])); + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = ((float)(input[stbir__decode_order0])); + #if stbir__coder_min_num >= 2 + decode[1] = ((float)(input[stbir__decode_order1])); + #endif + #if stbir__coder_min_num >= 3 + decode[2] = ((float)(input[stbir__decode_order2])); + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif +} + +static void STBIR__CODER_NAME(stbir__encode_uint16_linear)( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned short STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned short*) outputp; + unsigned short * end_output = ( (unsigned short*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + { + if ( width_times_channels >= stbir__simdfX_float_count*2 ) + { + float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; + end_output -= stbir__simdfX_float_count*2; + for(;;) + { + stbir__simdfX e0, e1; + stbir__simdiX i; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_add_mem( e0, STBIR_simd_point5X, encode ); + stbir__simdfX_add_mem( e1, STBIR_simd_point5X, encode+stbir__simdfX_float_count ); + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + stbir__simdfX_pack_to_words( i, e0, e1 ); + stbir__simdiX_store( output, i ); + encode += stbir__simdfX_float_count*2; + output += stbir__simdfX_float_count*2; + if ( output <= end_output ) + continue; + if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + stbir__simdf e; + stbir__simdi i; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e, encode ); + stbir__simdf_add( e, STBIR__CONSTF(STBIR_simd_point5), e ); + stbir__encode_simdf4_unflip( e ); + stbir__simdf_pack_to_8words( i, e, e ); // only use first 4 + stbir__simdi_store2( output-4, i ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + float f; + STBIR_SIMD_NO_UNROLL(encode); + f = encode[stbir__encode_order0] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[0-4] = (unsigned short)f; + f = encode[stbir__encode_order1] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[1-4] = (unsigned short)f; + f = encode[stbir__encode_order2] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[2-4] = (unsigned short)f; + f = encode[stbir__encode_order3] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[3-4] = (unsigned short)f; + output += 4; + encode += 4; + } + output -= 4; + #endif + + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + float f; + STBIR_NO_UNROLL(encode); + f = encode[stbir__encode_order0] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[0] = (unsigned short)f; + #if stbir__coder_min_num >= 2 + f = encode[stbir__encode_order1] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[1] = (unsigned short)f; + #endif + #if stbir__coder_min_num >= 3 + f = encode[stbir__encode_order2] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[2] = (unsigned short)f; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif +} + +static void STBIR__CODER_NAME(stbir__decode_half_float_linear)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + stbir__FP16 const * input = (stbir__FP16 const *)inputp; + + #ifdef STBIR_SIMD + if ( width_times_channels >= 8 ) + { + stbir__FP16 const * end_input_m8 = input + width_times_channels - 8; + decode_end -= 8; + for(;;) + { + STBIR_NO_UNROLL(decode); + + stbir__half_to_float_SIMD( decode, input ); + #ifdef stbir__decode_swizzle + #ifdef STBIR_SIMD8 + { + stbir__simdf8 of; + stbir__simdf8_load( of, decode ); + stbir__decode_simdf8_flip( of ); + stbir__simdf8_store( decode, of ); + } + #else + { + stbir__simdf of0,of1; + stbir__simdf_load( of0, decode ); + stbir__simdf_load( of1, decode+4 ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__simdf_store( decode, of0 ); + stbir__simdf_store( decode+4, of1 ); + } + #endif + #endif + decode += 8; + input += 8; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 8 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m8; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = stbir__half_to_float(input[stbir__decode_order0]); + decode[1-4] = stbir__half_to_float(input[stbir__decode_order1]); + decode[2-4] = stbir__half_to_float(input[stbir__decode_order2]); + decode[3-4] = stbir__half_to_float(input[stbir__decode_order3]); + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = stbir__half_to_float(input[stbir__decode_order0]); + #if stbir__coder_min_num >= 2 + decode[1] = stbir__half_to_float(input[stbir__decode_order1]); + #endif + #if stbir__coder_min_num >= 3 + decode[2] = stbir__half_to_float(input[stbir__decode_order2]); + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif +} + +static void STBIR__CODER_NAME( stbir__encode_half_float_linear )( void * outputp, int width_times_channels, float const * encode ) +{ + stbir__FP16 STBIR_SIMD_STREAMOUT_PTR( * ) output = (stbir__FP16*) outputp; + stbir__FP16 * end_output = ( (stbir__FP16*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + if ( width_times_channels >= 8 ) + { + float const * end_encode_m8 = encode + width_times_channels - 8; + end_output -= 8; + for(;;) + { + STBIR_SIMD_NO_UNROLL(encode); + #ifdef stbir__decode_swizzle + #ifdef STBIR_SIMD8 + { + stbir__simdf8 of; + stbir__simdf8_load( of, encode ); + stbir__encode_simdf8_unflip( of ); + stbir__float_to_half_SIMD( output, (float*)&of ); + } + #else + { + stbir__simdf of[2]; + stbir__simdf_load( of[0], encode ); + stbir__simdf_load( of[1], encode+4 ); + stbir__encode_simdf4_unflip( of[0] ); + stbir__encode_simdf4_unflip( of[1] ); + stbir__float_to_half_SIMD( output, (float*)of ); + } + #endif + #else + stbir__float_to_half_SIMD( output, encode ); + #endif + encode += 8; + output += 8; + if ( output <= end_output ) + continue; + if ( output == ( end_output + 8 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + STBIR_SIMD_NO_UNROLL(output); + output[0-4] = stbir__float_to_half(encode[stbir__encode_order0]); + output[1-4] = stbir__float_to_half(encode[stbir__encode_order1]); + output[2-4] = stbir__float_to_half(encode[stbir__encode_order2]); + output[3-4] = stbir__float_to_half(encode[stbir__encode_order3]); + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + STBIR_NO_UNROLL(output); + output[0] = stbir__float_to_half(encode[stbir__encode_order0]); + #if stbir__coder_min_num >= 2 + output[1] = stbir__float_to_half(encode[stbir__encode_order1]); + #endif + #if stbir__coder_min_num >= 3 + output[2] = stbir__float_to_half(encode[stbir__encode_order2]); + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif +} + +static void STBIR__CODER_NAME(stbir__decode_float_linear)( float * decodep, int width_times_channels, void const * inputp ) +{ + #ifdef stbir__decode_swizzle + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + float const * input = (float const *)inputp; + + #ifdef STBIR_SIMD + if ( width_times_channels >= 16 ) + { + float const * end_input_m16 = input + width_times_channels - 16; + decode_end -= 16; + for(;;) + { + STBIR_NO_UNROLL(decode); + #ifdef stbir__decode_swizzle + #ifdef STBIR_SIMD8 + { + stbir__simdf8 of0,of1; + stbir__simdf8_load( of0, input ); + stbir__simdf8_load( of1, input+8 ); + stbir__decode_simdf8_flip( of0 ); + stbir__decode_simdf8_flip( of1 ); + stbir__simdf8_store( decode, of0 ); + stbir__simdf8_store( decode+8, of1 ); + } + #else + { + stbir__simdf of0,of1,of2,of3; + stbir__simdf_load( of0, input ); + stbir__simdf_load( of1, input+4 ); + stbir__simdf_load( of2, input+8 ); + stbir__simdf_load( of3, input+12 ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__decode_simdf4_flip( of2 ); + stbir__decode_simdf4_flip( of3 ); + stbir__simdf_store( decode, of0 ); + stbir__simdf_store( decode+4, of1 ); + stbir__simdf_store( decode+8, of2 ); + stbir__simdf_store( decode+12, of3 ); + } + #endif + #endif + decode += 16; + input += 16; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 16 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m16; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = input[stbir__decode_order0]; + decode[1-4] = input[stbir__decode_order1]; + decode[2-4] = input[stbir__decode_order2]; + decode[3-4] = input[stbir__decode_order3]; + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = input[stbir__decode_order0]; + #if stbir__coder_min_num >= 2 + decode[1] = input[stbir__decode_order1]; + #endif + #if stbir__coder_min_num >= 3 + decode[2] = input[stbir__decode_order2]; + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif + + #else + + if ( (void*)decodep != inputp ) + STBIR_MEMCPY( decodep, inputp, width_times_channels * sizeof( float ) ); + + #endif +} + +static void STBIR__CODER_NAME( stbir__encode_float_linear )( void * outputp, int width_times_channels, float const * encode ) +{ + #if !defined( STBIR_FLOAT_HIGH_CLAMP ) && !defined(STBIR_FLOAT_LO_CLAMP) && !defined(stbir__decode_swizzle) + + if ( (void*)outputp != (void*) encode ) + STBIR_MEMCPY( outputp, encode, width_times_channels * sizeof( float ) ); + + #else + + float STBIR_SIMD_STREAMOUT_PTR( * ) output = (float*) outputp; + float * end_output = ( (float*) output ) + width_times_channels; + + #ifdef STBIR_FLOAT_HIGH_CLAMP + #define stbir_scalar_hi_clamp( v ) if ( v > STBIR_FLOAT_HIGH_CLAMP ) v = STBIR_FLOAT_HIGH_CLAMP; + #else + #define stbir_scalar_hi_clamp( v ) + #endif + #ifdef STBIR_FLOAT_LOW_CLAMP + #define stbir_scalar_lo_clamp( v ) if ( v < STBIR_FLOAT_LOW_CLAMP ) v = STBIR_FLOAT_LOW_CLAMP; + #else + #define stbir_scalar_lo_clamp( v ) + #endif + + #ifdef STBIR_SIMD + + #ifdef STBIR_FLOAT_HIGH_CLAMP + const stbir__simdfX high_clamp = stbir__simdf_frepX(STBIR_FLOAT_HIGH_CLAMP); + #endif + #ifdef STBIR_FLOAT_LOW_CLAMP + const stbir__simdfX low_clamp = stbir__simdf_frepX(STBIR_FLOAT_LOW_CLAMP); + #endif + + if ( width_times_channels >= ( stbir__simdfX_float_count * 2 ) ) + { + float const * end_encode_m8 = encode + width_times_channels - ( stbir__simdfX_float_count * 2 ); + end_output -= ( stbir__simdfX_float_count * 2 ); + for(;;) + { + stbir__simdfX e0, e1; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_load( e0, encode ); + stbir__simdfX_load( e1, encode+stbir__simdfX_float_count ); +#ifdef STBIR_FLOAT_HIGH_CLAMP + stbir__simdfX_min( e0, e0, high_clamp ); + stbir__simdfX_min( e1, e1, high_clamp ); +#endif +#ifdef STBIR_FLOAT_LOW_CLAMP + stbir__simdfX_max( e0, e0, low_clamp ); + stbir__simdfX_max( e1, e1, low_clamp ); +#endif + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + stbir__simdfX_store( output, e0 ); + stbir__simdfX_store( output+stbir__simdfX_float_count, e1 ); + encode += stbir__simdfX_float_count * 2; + output += stbir__simdfX_float_count * 2; + if ( output < end_output ) + continue; + if ( output == ( end_output + ( stbir__simdfX_float_count * 2 ) ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + stbir__simdf e0; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e0, encode ); +#ifdef STBIR_FLOAT_HIGH_CLAMP + stbir__simdf_min( e0, e0, high_clamp ); +#endif +#ifdef STBIR_FLOAT_LOW_CLAMP + stbir__simdf_max( e0, e0, low_clamp ); +#endif + stbir__encode_simdf4_unflip( e0 ); + stbir__simdf_store( output-4, e0 ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + float e; + STBIR_SIMD_NO_UNROLL(encode); + e = encode[ stbir__encode_order0 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[0-4] = e; + e = encode[ stbir__encode_order1 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[1-4] = e; + e = encode[ stbir__encode_order2 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[2-4] = e; + e = encode[ stbir__encode_order3 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[3-4] = e; + output += 4; + encode += 4; + } + output -= 4; + + #endif + + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + float e; + STBIR_NO_UNROLL(encode); + e = encode[ stbir__encode_order0 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[0] = e; + #if stbir__coder_min_num >= 2 + e = encode[ stbir__encode_order1 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[1] = e; + #endif + #if stbir__coder_min_num >= 3 + e = encode[ stbir__encode_order2 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[2] = e; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + + #endif +} + +#undef stbir__decode_suffix +#undef stbir__decode_simdf8_flip +#undef stbir__decode_simdf4_flip +#undef stbir__decode_order0 +#undef stbir__decode_order1 +#undef stbir__decode_order2 +#undef stbir__decode_order3 +#undef stbir__encode_order0 +#undef stbir__encode_order1 +#undef stbir__encode_order2 +#undef stbir__encode_order3 +#undef stbir__encode_simdf8_unflip +#undef stbir__encode_simdf4_unflip +#undef stbir__encode_simdfX_unflip +#undef STBIR__CODER_NAME +#undef stbir__coder_min_num +#undef stbir__decode_swizzle +#undef stbir_scalar_hi_clamp +#undef stbir_scalar_lo_clamp +#undef STB_IMAGE_RESIZE_DO_CODERS + +#elif defined( STB_IMAGE_RESIZE_DO_VERTICALS) + +#ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#define STBIR_chans( start, end ) STBIR_strs_join14(start,STBIR__vertical_channels,end,_cont) +#else +#define STBIR_chans( start, end ) STBIR_strs_join1(start,STBIR__vertical_channels,end) +#endif + +#if STBIR__vertical_channels >= 1 +#define stbIF0( code ) code +#else +#define stbIF0( code ) +#endif +#if STBIR__vertical_channels >= 2 +#define stbIF1( code ) code +#else +#define stbIF1( code ) +#endif +#if STBIR__vertical_channels >= 3 +#define stbIF2( code ) code +#else +#define stbIF2( code ) +#endif +#if STBIR__vertical_channels >= 4 +#define stbIF3( code ) code +#else +#define stbIF3( code ) +#endif +#if STBIR__vertical_channels >= 5 +#define stbIF4( code ) code +#else +#define stbIF4( code ) +#endif +#if STBIR__vertical_channels >= 6 +#define stbIF5( code ) code +#else +#define stbIF5( code ) +#endif +#if STBIR__vertical_channels >= 7 +#define stbIF6( code ) code +#else +#define stbIF6( code ) +#endif +#if STBIR__vertical_channels >= 8 +#define stbIF7( code ) code +#else +#define stbIF7( code ) +#endif + +static void STBIR_chans( stbir__vertical_scatter_with_,_coeffs)( float ** outputs, float const * vertical_coefficients, float const * input, float const * input_end ) +{ + stbIF0( float STBIR_SIMD_STREAMOUT_PTR( * ) output0 = outputs[0]; float c0s = vertical_coefficients[0]; ) + stbIF1( float STBIR_SIMD_STREAMOUT_PTR( * ) output1 = outputs[1]; float c1s = vertical_coefficients[1]; ) + stbIF2( float STBIR_SIMD_STREAMOUT_PTR( * ) output2 = outputs[2]; float c2s = vertical_coefficients[2]; ) + stbIF3( float STBIR_SIMD_STREAMOUT_PTR( * ) output3 = outputs[3]; float c3s = vertical_coefficients[3]; ) + stbIF4( float STBIR_SIMD_STREAMOUT_PTR( * ) output4 = outputs[4]; float c4s = vertical_coefficients[4]; ) + stbIF5( float STBIR_SIMD_STREAMOUT_PTR( * ) output5 = outputs[5]; float c5s = vertical_coefficients[5]; ) + stbIF6( float STBIR_SIMD_STREAMOUT_PTR( * ) output6 = outputs[6]; float c6s = vertical_coefficients[6]; ) + stbIF7( float STBIR_SIMD_STREAMOUT_PTR( * ) output7 = outputs[7]; float c7s = vertical_coefficients[7]; ) + + #ifdef STBIR_SIMD + { + stbIF0(stbir__simdfX c0 = stbir__simdf_frepX( c0s ); ) + stbIF1(stbir__simdfX c1 = stbir__simdf_frepX( c1s ); ) + stbIF2(stbir__simdfX c2 = stbir__simdf_frepX( c2s ); ) + stbIF3(stbir__simdfX c3 = stbir__simdf_frepX( c3s ); ) + stbIF4(stbir__simdfX c4 = stbir__simdf_frepX( c4s ); ) + stbIF5(stbir__simdfX c5 = stbir__simdf_frepX( c5s ); ) + stbIF6(stbir__simdfX c6 = stbir__simdf_frepX( c6s ); ) + stbIF7(stbir__simdfX c7 = stbir__simdf_frepX( c7s ); ) + while ( ( (char*)input_end - (char*) input ) >= (16*stbir__simdfX_float_count) ) + { + stbir__simdfX o0, o1, o2, o3, r0, r1, r2, r3; + STBIR_SIMD_NO_UNROLL(output0); + + stbir__simdfX_load( r0, input ); stbir__simdfX_load( r1, input+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input+(3*stbir__simdfX_float_count) ); + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( stbir__simdfX_load( o0, output0 ); stbir__simdfX_load( o1, output0+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output0+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output0+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c0 ); stbir__simdfX_madd( o1, o1, r1, c0 ); stbir__simdfX_madd( o2, o2, r2, c0 ); stbir__simdfX_madd( o3, o3, r3, c0 ); + stbir__simdfX_store( output0, o0 ); stbir__simdfX_store( output0+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output0+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output0+(3*stbir__simdfX_float_count), o3 ); ) + stbIF1( stbir__simdfX_load( o0, output1 ); stbir__simdfX_load( o1, output1+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output1+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output1+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c1 ); stbir__simdfX_madd( o1, o1, r1, c1 ); stbir__simdfX_madd( o2, o2, r2, c1 ); stbir__simdfX_madd( o3, o3, r3, c1 ); + stbir__simdfX_store( output1, o0 ); stbir__simdfX_store( output1+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output1+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output1+(3*stbir__simdfX_float_count), o3 ); ) + stbIF2( stbir__simdfX_load( o0, output2 ); stbir__simdfX_load( o1, output2+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output2+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output2+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c2 ); stbir__simdfX_madd( o1, o1, r1, c2 ); stbir__simdfX_madd( o2, o2, r2, c2 ); stbir__simdfX_madd( o3, o3, r3, c2 ); + stbir__simdfX_store( output2, o0 ); stbir__simdfX_store( output2+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output2+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output2+(3*stbir__simdfX_float_count), o3 ); ) + stbIF3( stbir__simdfX_load( o0, output3 ); stbir__simdfX_load( o1, output3+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output3+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output3+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c3 ); stbir__simdfX_madd( o1, o1, r1, c3 ); stbir__simdfX_madd( o2, o2, r2, c3 ); stbir__simdfX_madd( o3, o3, r3, c3 ); + stbir__simdfX_store( output3, o0 ); stbir__simdfX_store( output3+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output3+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output3+(3*stbir__simdfX_float_count), o3 ); ) + stbIF4( stbir__simdfX_load( o0, output4 ); stbir__simdfX_load( o1, output4+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output4+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output4+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c4 ); stbir__simdfX_madd( o1, o1, r1, c4 ); stbir__simdfX_madd( o2, o2, r2, c4 ); stbir__simdfX_madd( o3, o3, r3, c4 ); + stbir__simdfX_store( output4, o0 ); stbir__simdfX_store( output4+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output4+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output4+(3*stbir__simdfX_float_count), o3 ); ) + stbIF5( stbir__simdfX_load( o0, output5 ); stbir__simdfX_load( o1, output5+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output5+(2*stbir__simdfX_float_count)); stbir__simdfX_load( o3, output5+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c5 ); stbir__simdfX_madd( o1, o1, r1, c5 ); stbir__simdfX_madd( o2, o2, r2, c5 ); stbir__simdfX_madd( o3, o3, r3, c5 ); + stbir__simdfX_store( output5, o0 ); stbir__simdfX_store( output5+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output5+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output5+(3*stbir__simdfX_float_count), o3 ); ) + stbIF6( stbir__simdfX_load( o0, output6 ); stbir__simdfX_load( o1, output6+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output6+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output6+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c6 ); stbir__simdfX_madd( o1, o1, r1, c6 ); stbir__simdfX_madd( o2, o2, r2, c6 ); stbir__simdfX_madd( o3, o3, r3, c6 ); + stbir__simdfX_store( output6, o0 ); stbir__simdfX_store( output6+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output6+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output6+(3*stbir__simdfX_float_count), o3 ); ) + stbIF7( stbir__simdfX_load( o0, output7 ); stbir__simdfX_load( o1, output7+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output7+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output7+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c7 ); stbir__simdfX_madd( o1, o1, r1, c7 ); stbir__simdfX_madd( o2, o2, r2, c7 ); stbir__simdfX_madd( o3, o3, r3, c7 ); + stbir__simdfX_store( output7, o0 ); stbir__simdfX_store( output7+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output7+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output7+(3*stbir__simdfX_float_count), o3 ); ) + #else + stbIF0( stbir__simdfX_mult( o0, r0, c0 ); stbir__simdfX_mult( o1, r1, c0 ); stbir__simdfX_mult( o2, r2, c0 ); stbir__simdfX_mult( o3, r3, c0 ); + stbir__simdfX_store( output0, o0 ); stbir__simdfX_store( output0+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output0+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output0+(3*stbir__simdfX_float_count), o3 ); ) + stbIF1( stbir__simdfX_mult( o0, r0, c1 ); stbir__simdfX_mult( o1, r1, c1 ); stbir__simdfX_mult( o2, r2, c1 ); stbir__simdfX_mult( o3, r3, c1 ); + stbir__simdfX_store( output1, o0 ); stbir__simdfX_store( output1+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output1+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output1+(3*stbir__simdfX_float_count), o3 ); ) + stbIF2( stbir__simdfX_mult( o0, r0, c2 ); stbir__simdfX_mult( o1, r1, c2 ); stbir__simdfX_mult( o2, r2, c2 ); stbir__simdfX_mult( o3, r3, c2 ); + stbir__simdfX_store( output2, o0 ); stbir__simdfX_store( output2+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output2+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output2+(3*stbir__simdfX_float_count), o3 ); ) + stbIF3( stbir__simdfX_mult( o0, r0, c3 ); stbir__simdfX_mult( o1, r1, c3 ); stbir__simdfX_mult( o2, r2, c3 ); stbir__simdfX_mult( o3, r3, c3 ); + stbir__simdfX_store( output3, o0 ); stbir__simdfX_store( output3+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output3+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output3+(3*stbir__simdfX_float_count), o3 ); ) + stbIF4( stbir__simdfX_mult( o0, r0, c4 ); stbir__simdfX_mult( o1, r1, c4 ); stbir__simdfX_mult( o2, r2, c4 ); stbir__simdfX_mult( o3, r3, c4 ); + stbir__simdfX_store( output4, o0 ); stbir__simdfX_store( output4+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output4+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output4+(3*stbir__simdfX_float_count), o3 ); ) + stbIF5( stbir__simdfX_mult( o0, r0, c5 ); stbir__simdfX_mult( o1, r1, c5 ); stbir__simdfX_mult( o2, r2, c5 ); stbir__simdfX_mult( o3, r3, c5 ); + stbir__simdfX_store( output5, o0 ); stbir__simdfX_store( output5+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output5+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output5+(3*stbir__simdfX_float_count), o3 ); ) + stbIF6( stbir__simdfX_mult( o0, r0, c6 ); stbir__simdfX_mult( o1, r1, c6 ); stbir__simdfX_mult( o2, r2, c6 ); stbir__simdfX_mult( o3, r3, c6 ); + stbir__simdfX_store( output6, o0 ); stbir__simdfX_store( output6+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output6+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output6+(3*stbir__simdfX_float_count), o3 ); ) + stbIF7( stbir__simdfX_mult( o0, r0, c7 ); stbir__simdfX_mult( o1, r1, c7 ); stbir__simdfX_mult( o2, r2, c7 ); stbir__simdfX_mult( o3, r3, c7 ); + stbir__simdfX_store( output7, o0 ); stbir__simdfX_store( output7+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output7+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output7+(3*stbir__simdfX_float_count), o3 ); ) + #endif + + input += (4*stbir__simdfX_float_count); + stbIF0( output0 += (4*stbir__simdfX_float_count); ) stbIF1( output1 += (4*stbir__simdfX_float_count); ) stbIF2( output2 += (4*stbir__simdfX_float_count); ) stbIF3( output3 += (4*stbir__simdfX_float_count); ) stbIF4( output4 += (4*stbir__simdfX_float_count); ) stbIF5( output5 += (4*stbir__simdfX_float_count); ) stbIF6( output6 += (4*stbir__simdfX_float_count); ) stbIF7( output7 += (4*stbir__simdfX_float_count); ) + } + while ( ( (char*)input_end - (char*) input ) >= 16 ) + { + stbir__simdf o0, r0; + STBIR_SIMD_NO_UNROLL(output0); + + stbir__simdf_load( r0, input ); + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( stbir__simdf_load( o0, output0 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c0 ) ); stbir__simdf_store( output0, o0 ); ) + stbIF1( stbir__simdf_load( o0, output1 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c1 ) ); stbir__simdf_store( output1, o0 ); ) + stbIF2( stbir__simdf_load( o0, output2 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c2 ) ); stbir__simdf_store( output2, o0 ); ) + stbIF3( stbir__simdf_load( o0, output3 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c3 ) ); stbir__simdf_store( output3, o0 ); ) + stbIF4( stbir__simdf_load( o0, output4 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c4 ) ); stbir__simdf_store( output4, o0 ); ) + stbIF5( stbir__simdf_load( o0, output5 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c5 ) ); stbir__simdf_store( output5, o0 ); ) + stbIF6( stbir__simdf_load( o0, output6 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c6 ) ); stbir__simdf_store( output6, o0 ); ) + stbIF7( stbir__simdf_load( o0, output7 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c7 ) ); stbir__simdf_store( output7, o0 ); ) + #else + stbIF0( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c0 ) ); stbir__simdf_store( output0, o0 ); ) + stbIF1( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c1 ) ); stbir__simdf_store( output1, o0 ); ) + stbIF2( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c2 ) ); stbir__simdf_store( output2, o0 ); ) + stbIF3( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c3 ) ); stbir__simdf_store( output3, o0 ); ) + stbIF4( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c4 ) ); stbir__simdf_store( output4, o0 ); ) + stbIF5( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c5 ) ); stbir__simdf_store( output5, o0 ); ) + stbIF6( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c6 ) ); stbir__simdf_store( output6, o0 ); ) + stbIF7( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c7 ) ); stbir__simdf_store( output7, o0 ); ) + #endif + + input += 4; + stbIF0( output0 += 4; ) stbIF1( output1 += 4; ) stbIF2( output2 += 4; ) stbIF3( output3 += 4; ) stbIF4( output4 += 4; ) stbIF5( output5 += 4; ) stbIF6( output6 += 4; ) stbIF7( output7 += 4; ) + } + } + #else + while ( ( (char*)input_end - (char*) input ) >= 16 ) + { + float r0, r1, r2, r3; + STBIR_NO_UNROLL(input); + + r0 = input[0], r1 = input[1], r2 = input[2], r3 = input[3]; + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( output0[0] += ( r0 * c0s ); output0[1] += ( r1 * c0s ); output0[2] += ( r2 * c0s ); output0[3] += ( r3 * c0s ); ) + stbIF1( output1[0] += ( r0 * c1s ); output1[1] += ( r1 * c1s ); output1[2] += ( r2 * c1s ); output1[3] += ( r3 * c1s ); ) + stbIF2( output2[0] += ( r0 * c2s ); output2[1] += ( r1 * c2s ); output2[2] += ( r2 * c2s ); output2[3] += ( r3 * c2s ); ) + stbIF3( output3[0] += ( r0 * c3s ); output3[1] += ( r1 * c3s ); output3[2] += ( r2 * c3s ); output3[3] += ( r3 * c3s ); ) + stbIF4( output4[0] += ( r0 * c4s ); output4[1] += ( r1 * c4s ); output4[2] += ( r2 * c4s ); output4[3] += ( r3 * c4s ); ) + stbIF5( output5[0] += ( r0 * c5s ); output5[1] += ( r1 * c5s ); output5[2] += ( r2 * c5s ); output5[3] += ( r3 * c5s ); ) + stbIF6( output6[0] += ( r0 * c6s ); output6[1] += ( r1 * c6s ); output6[2] += ( r2 * c6s ); output6[3] += ( r3 * c6s ); ) + stbIF7( output7[0] += ( r0 * c7s ); output7[1] += ( r1 * c7s ); output7[2] += ( r2 * c7s ); output7[3] += ( r3 * c7s ); ) + #else + stbIF0( output0[0] = ( r0 * c0s ); output0[1] = ( r1 * c0s ); output0[2] = ( r2 * c0s ); output0[3] = ( r3 * c0s ); ) + stbIF1( output1[0] = ( r0 * c1s ); output1[1] = ( r1 * c1s ); output1[2] = ( r2 * c1s ); output1[3] = ( r3 * c1s ); ) + stbIF2( output2[0] = ( r0 * c2s ); output2[1] = ( r1 * c2s ); output2[2] = ( r2 * c2s ); output2[3] = ( r3 * c2s ); ) + stbIF3( output3[0] = ( r0 * c3s ); output3[1] = ( r1 * c3s ); output3[2] = ( r2 * c3s ); output3[3] = ( r3 * c3s ); ) + stbIF4( output4[0] = ( r0 * c4s ); output4[1] = ( r1 * c4s ); output4[2] = ( r2 * c4s ); output4[3] = ( r3 * c4s ); ) + stbIF5( output5[0] = ( r0 * c5s ); output5[1] = ( r1 * c5s ); output5[2] = ( r2 * c5s ); output5[3] = ( r3 * c5s ); ) + stbIF6( output6[0] = ( r0 * c6s ); output6[1] = ( r1 * c6s ); output6[2] = ( r2 * c6s ); output6[3] = ( r3 * c6s ); ) + stbIF7( output7[0] = ( r0 * c7s ); output7[1] = ( r1 * c7s ); output7[2] = ( r2 * c7s ); output7[3] = ( r3 * c7s ); ) + #endif + + input += 4; + stbIF0( output0 += 4; ) stbIF1( output1 += 4; ) stbIF2( output2 += 4; ) stbIF3( output3 += 4; ) stbIF4( output4 += 4; ) stbIF5( output5 += 4; ) stbIF6( output6 += 4; ) stbIF7( output7 += 4; ) + } + #endif + while ( input < input_end ) + { + float r = input[0]; + STBIR_NO_UNROLL(output0); + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( output0[0] += ( r * c0s ); ) + stbIF1( output1[0] += ( r * c1s ); ) + stbIF2( output2[0] += ( r * c2s ); ) + stbIF3( output3[0] += ( r * c3s ); ) + stbIF4( output4[0] += ( r * c4s ); ) + stbIF5( output5[0] += ( r * c5s ); ) + stbIF6( output6[0] += ( r * c6s ); ) + stbIF7( output7[0] += ( r * c7s ); ) + #else + stbIF0( output0[0] = ( r * c0s ); ) + stbIF1( output1[0] = ( r * c1s ); ) + stbIF2( output2[0] = ( r * c2s ); ) + stbIF3( output3[0] = ( r * c3s ); ) + stbIF4( output4[0] = ( r * c4s ); ) + stbIF5( output5[0] = ( r * c5s ); ) + stbIF6( output6[0] = ( r * c6s ); ) + stbIF7( output7[0] = ( r * c7s ); ) + #endif + + ++input; + stbIF0( ++output0; ) stbIF1( ++output1; ) stbIF2( ++output2; ) stbIF3( ++output3; ) stbIF4( ++output4; ) stbIF5( ++output5; ) stbIF6( ++output6; ) stbIF7( ++output7; ) + } +} + +static void STBIR_chans( stbir__vertical_gather_with_,_coeffs)( float * outputp, float const * vertical_coefficients, float const ** inputs, float const * input0_end ) +{ + float STBIR_SIMD_STREAMOUT_PTR( * ) output = outputp; + + stbIF0( float const * input0 = inputs[0]; float c0s = vertical_coefficients[0]; ) + stbIF1( float const * input1 = inputs[1]; float c1s = vertical_coefficients[1]; ) + stbIF2( float const * input2 = inputs[2]; float c2s = vertical_coefficients[2]; ) + stbIF3( float const * input3 = inputs[3]; float c3s = vertical_coefficients[3]; ) + stbIF4( float const * input4 = inputs[4]; float c4s = vertical_coefficients[4]; ) + stbIF5( float const * input5 = inputs[5]; float c5s = vertical_coefficients[5]; ) + stbIF6( float const * input6 = inputs[6]; float c6s = vertical_coefficients[6]; ) + stbIF7( float const * input7 = inputs[7]; float c7s = vertical_coefficients[7]; ) + +#if ( STBIR__vertical_channels == 1 ) && !defined(STB_IMAGE_RESIZE_VERTICAL_CONTINUE) + // check single channel one weight + if ( ( c0s >= (1.0f-0.000001f) ) && ( c0s <= (1.0f+0.000001f) ) ) + { + STBIR_MEMCPY( output, input0, (char*)input0_end - (char*)input0 ); + return; + } +#endif + + #ifdef STBIR_SIMD + { + stbIF0(stbir__simdfX c0 = stbir__simdf_frepX( c0s ); ) + stbIF1(stbir__simdfX c1 = stbir__simdf_frepX( c1s ); ) + stbIF2(stbir__simdfX c2 = stbir__simdf_frepX( c2s ); ) + stbIF3(stbir__simdfX c3 = stbir__simdf_frepX( c3s ); ) + stbIF4(stbir__simdfX c4 = stbir__simdf_frepX( c4s ); ) + stbIF5(stbir__simdfX c5 = stbir__simdf_frepX( c5s ); ) + stbIF6(stbir__simdfX c6 = stbir__simdf_frepX( c6s ); ) + stbIF7(stbir__simdfX c7 = stbir__simdf_frepX( c7s ); ) + + while ( ( (char*)input0_end - (char*) input0 ) >= (16*stbir__simdfX_float_count) ) + { + stbir__simdfX o0, o1, o2, o3, r0, r1, r2, r3; + STBIR_SIMD_NO_UNROLL(output); + + // prefetch four loop iterations ahead (doesn't affect much for small resizes, but helps with big ones) + stbIF0( stbir__prefetch( input0 + (16*stbir__simdfX_float_count) ); ) + stbIF1( stbir__prefetch( input1 + (16*stbir__simdfX_float_count) ); ) + stbIF2( stbir__prefetch( input2 + (16*stbir__simdfX_float_count) ); ) + stbIF3( stbir__prefetch( input3 + (16*stbir__simdfX_float_count) ); ) + stbIF4( stbir__prefetch( input4 + (16*stbir__simdfX_float_count) ); ) + stbIF5( stbir__prefetch( input5 + (16*stbir__simdfX_float_count) ); ) + stbIF6( stbir__prefetch( input6 + (16*stbir__simdfX_float_count) ); ) + stbIF7( stbir__prefetch( input7 + (16*stbir__simdfX_float_count) ); ) + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( stbir__simdfX_load( o0, output ); stbir__simdfX_load( o1, output+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output+(3*stbir__simdfX_float_count) ); + stbir__simdfX_load( r0, input0 ); stbir__simdfX_load( r1, input0+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input0+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input0+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c0 ); stbir__simdfX_madd( o1, o1, r1, c0 ); stbir__simdfX_madd( o2, o2, r2, c0 ); stbir__simdfX_madd( o3, o3, r3, c0 ); ) + #else + stbIF0( stbir__simdfX_load( r0, input0 ); stbir__simdfX_load( r1, input0+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input0+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input0+(3*stbir__simdfX_float_count) ); + stbir__simdfX_mult( o0, r0, c0 ); stbir__simdfX_mult( o1, r1, c0 ); stbir__simdfX_mult( o2, r2, c0 ); stbir__simdfX_mult( o3, r3, c0 ); ) + #endif + + stbIF1( stbir__simdfX_load( r0, input1 ); stbir__simdfX_load( r1, input1+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input1+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input1+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c1 ); stbir__simdfX_madd( o1, o1, r1, c1 ); stbir__simdfX_madd( o2, o2, r2, c1 ); stbir__simdfX_madd( o3, o3, r3, c1 ); ) + stbIF2( stbir__simdfX_load( r0, input2 ); stbir__simdfX_load( r1, input2+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input2+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input2+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c2 ); stbir__simdfX_madd( o1, o1, r1, c2 ); stbir__simdfX_madd( o2, o2, r2, c2 ); stbir__simdfX_madd( o3, o3, r3, c2 ); ) + stbIF3( stbir__simdfX_load( r0, input3 ); stbir__simdfX_load( r1, input3+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input3+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input3+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c3 ); stbir__simdfX_madd( o1, o1, r1, c3 ); stbir__simdfX_madd( o2, o2, r2, c3 ); stbir__simdfX_madd( o3, o3, r3, c3 ); ) + stbIF4( stbir__simdfX_load( r0, input4 ); stbir__simdfX_load( r1, input4+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input4+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input4+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c4 ); stbir__simdfX_madd( o1, o1, r1, c4 ); stbir__simdfX_madd( o2, o2, r2, c4 ); stbir__simdfX_madd( o3, o3, r3, c4 ); ) + stbIF5( stbir__simdfX_load( r0, input5 ); stbir__simdfX_load( r1, input5+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input5+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input5+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c5 ); stbir__simdfX_madd( o1, o1, r1, c5 ); stbir__simdfX_madd( o2, o2, r2, c5 ); stbir__simdfX_madd( o3, o3, r3, c5 ); ) + stbIF6( stbir__simdfX_load( r0, input6 ); stbir__simdfX_load( r1, input6+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input6+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input6+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c6 ); stbir__simdfX_madd( o1, o1, r1, c6 ); stbir__simdfX_madd( o2, o2, r2, c6 ); stbir__simdfX_madd( o3, o3, r3, c6 ); ) + stbIF7( stbir__simdfX_load( r0, input7 ); stbir__simdfX_load( r1, input7+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input7+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input7+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c7 ); stbir__simdfX_madd( o1, o1, r1, c7 ); stbir__simdfX_madd( o2, o2, r2, c7 ); stbir__simdfX_madd( o3, o3, r3, c7 ); ) + + stbir__simdfX_store( output, o0 ); stbir__simdfX_store( output+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output+(3*stbir__simdfX_float_count), o3 ); + output += (4*stbir__simdfX_float_count); + stbIF0( input0 += (4*stbir__simdfX_float_count); ) stbIF1( input1 += (4*stbir__simdfX_float_count); ) stbIF2( input2 += (4*stbir__simdfX_float_count); ) stbIF3( input3 += (4*stbir__simdfX_float_count); ) stbIF4( input4 += (4*stbir__simdfX_float_count); ) stbIF5( input5 += (4*stbir__simdfX_float_count); ) stbIF6( input6 += (4*stbir__simdfX_float_count); ) stbIF7( input7 += (4*stbir__simdfX_float_count); ) + } + + while ( ( (char*)input0_end - (char*) input0 ) >= 16 ) + { + stbir__simdf o0, r0; + STBIR_SIMD_NO_UNROLL(output); + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( stbir__simdf_load( o0, output ); stbir__simdf_load( r0, input0 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c0 ) ); ) + #else + stbIF0( stbir__simdf_load( r0, input0 ); stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c0 ) ); ) + #endif + stbIF1( stbir__simdf_load( r0, input1 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c1 ) ); ) + stbIF2( stbir__simdf_load( r0, input2 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c2 ) ); ) + stbIF3( stbir__simdf_load( r0, input3 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c3 ) ); ) + stbIF4( stbir__simdf_load( r0, input4 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c4 ) ); ) + stbIF5( stbir__simdf_load( r0, input5 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c5 ) ); ) + stbIF6( stbir__simdf_load( r0, input6 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c6 ) ); ) + stbIF7( stbir__simdf_load( r0, input7 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c7 ) ); ) + + stbir__simdf_store( output, o0 ); + output += 4; + stbIF0( input0 += 4; ) stbIF1( input1 += 4; ) stbIF2( input2 += 4; ) stbIF3( input3 += 4; ) stbIF4( input4 += 4; ) stbIF5( input5 += 4; ) stbIF6( input6 += 4; ) stbIF7( input7 += 4; ) + } + } + #else + while ( ( (char*)input0_end - (char*) input0 ) >= 16 ) + { + float o0, o1, o2, o3; + STBIR_NO_UNROLL(output); + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( o0 = output[0] + input0[0] * c0s; o1 = output[1] + input0[1] * c0s; o2 = output[2] + input0[2] * c0s; o3 = output[3] + input0[3] * c0s; ) + #else + stbIF0( o0 = input0[0] * c0s; o1 = input0[1] * c0s; o2 = input0[2] * c0s; o3 = input0[3] * c0s; ) + #endif + stbIF1( o0 += input1[0] * c1s; o1 += input1[1] * c1s; o2 += input1[2] * c1s; o3 += input1[3] * c1s; ) + stbIF2( o0 += input2[0] * c2s; o1 += input2[1] * c2s; o2 += input2[2] * c2s; o3 += input2[3] * c2s; ) + stbIF3( o0 += input3[0] * c3s; o1 += input3[1] * c3s; o2 += input3[2] * c3s; o3 += input3[3] * c3s; ) + stbIF4( o0 += input4[0] * c4s; o1 += input4[1] * c4s; o2 += input4[2] * c4s; o3 += input4[3] * c4s; ) + stbIF5( o0 += input5[0] * c5s; o1 += input5[1] * c5s; o2 += input5[2] * c5s; o3 += input5[3] * c5s; ) + stbIF6( o0 += input6[0] * c6s; o1 += input6[1] * c6s; o2 += input6[2] * c6s; o3 += input6[3] * c6s; ) + stbIF7( o0 += input7[0] * c7s; o1 += input7[1] * c7s; o2 += input7[2] * c7s; o3 += input7[3] * c7s; ) + output[0] = o0; output[1] = o1; output[2] = o2; output[3] = o3; + output += 4; + stbIF0( input0 += 4; ) stbIF1( input1 += 4; ) stbIF2( input2 += 4; ) stbIF3( input3 += 4; ) stbIF4( input4 += 4; ) stbIF5( input5 += 4; ) stbIF6( input6 += 4; ) stbIF7( input7 += 4; ) + } + #endif + while ( input0 < input0_end ) + { + float o0; + STBIR_NO_UNROLL(output); + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( o0 = output[0] + input0[0] * c0s; ) + #else + stbIF0( o0 = input0[0] * c0s; ) + #endif + stbIF1( o0 += input1[0] * c1s; ) + stbIF2( o0 += input2[0] * c2s; ) + stbIF3( o0 += input3[0] * c3s; ) + stbIF4( o0 += input4[0] * c4s; ) + stbIF5( o0 += input5[0] * c5s; ) + stbIF6( o0 += input6[0] * c6s; ) + stbIF7( o0 += input7[0] * c7s; ) + output[0] = o0; + ++output; + stbIF0( ++input0; ) stbIF1( ++input1; ) stbIF2( ++input2; ) stbIF3( ++input3; ) stbIF4( ++input4; ) stbIF5( ++input5; ) stbIF6( ++input6; ) stbIF7( ++input7; ) + } +} + +#undef stbIF0 +#undef stbIF1 +#undef stbIF2 +#undef stbIF3 +#undef stbIF4 +#undef stbIF5 +#undef stbIF6 +#undef stbIF7 +#undef STB_IMAGE_RESIZE_DO_VERTICALS +#undef STBIR__vertical_channels +#undef STB_IMAGE_RESIZE_DO_HORIZONTALS +#undef STBIR_strs_join24 +#undef STBIR_strs_join14 +#undef STBIR_chans +#ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#undef STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#endif + +#else // !STB_IMAGE_RESIZE_DO_VERTICALS + +#define STBIR_chans( start, end ) STBIR_strs_join1(start,STBIR__horizontal_channels,end) + +#ifndef stbir__2_coeff_only +#define stbir__2_coeff_only() \ + stbir__1_coeff_only(); \ + stbir__1_coeff_remnant(1); +#endif + +#ifndef stbir__2_coeff_remnant +#define stbir__2_coeff_remnant( ofs ) \ + stbir__1_coeff_remnant(ofs); \ + stbir__1_coeff_remnant((ofs)+1); +#endif + +#ifndef stbir__3_coeff_only +#define stbir__3_coeff_only() \ + stbir__2_coeff_only(); \ + stbir__1_coeff_remnant(2); +#endif + +#ifndef stbir__3_coeff_remnant +#define stbir__3_coeff_remnant( ofs ) \ + stbir__2_coeff_remnant(ofs); \ + stbir__1_coeff_remnant((ofs)+2); +#endif + +#ifndef stbir__3_coeff_setup +#define stbir__3_coeff_setup() +#endif + +#ifndef stbir__4_coeff_start +#define stbir__4_coeff_start() \ + stbir__2_coeff_only(); \ + stbir__2_coeff_remnant(2); +#endif + +#ifndef stbir__4_coeff_continue_from_4 +#define stbir__4_coeff_continue_from_4( ofs ) \ + stbir__2_coeff_remnant(ofs); \ + stbir__2_coeff_remnant((ofs)+2); +#endif + +#ifndef stbir__store_output_tiny +#define stbir__store_output_tiny stbir__store_output +#endif + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_1_coeff)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__1_coeff_only(); + stbir__store_output_tiny(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_2_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__2_coeff_only(); + stbir__store_output_tiny(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_3_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__3_coeff_only(); + stbir__store_output_tiny(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_4_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_5_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__1_coeff_remnant(4); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_6_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__2_coeff_remnant(4); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_7_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + stbir__3_coeff_setup(); + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + stbir__3_coeff_remnant(4); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_8_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_9_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__1_coeff_remnant(8); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_10_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__2_coeff_remnant(8); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_11_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + stbir__3_coeff_setup(); + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__3_coeff_remnant(8); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_12_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__4_coeff_continue_from_4(8); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod0 )( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 4 + 3 ) >> 2; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + do { + hc += 4; + decode += STBIR__horizontal_channels * 4; + stbir__4_coeff_continue_from_4( 0 ); + --n; + } while ( n > 0 ); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod1 )( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 5 + 3 ) >> 2; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + do { + hc += 4; + decode += STBIR__horizontal_channels * 4; + stbir__4_coeff_continue_from_4( 0 ); + --n; + } while ( n > 0 ); + stbir__1_coeff_remnant( 4 ); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod2 )( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 6 + 3 ) >> 2; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + do { + hc += 4; + decode += STBIR__horizontal_channels * 4; + stbir__4_coeff_continue_from_4( 0 ); + --n; + } while ( n > 0 ); + stbir__2_coeff_remnant( 4 ); + + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod3 )( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + stbir__3_coeff_setup(); + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 7 + 3 ) >> 2; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + do { + hc += 4; + decode += STBIR__horizontal_channels * 4; + stbir__4_coeff_continue_from_4( 0 ); + --n; + } while ( n > 0 ); + stbir__3_coeff_remnant( 4 ); + + stbir__store_output(); + } while ( output < output_end ); +} + +static stbir__horizontal_gather_channels_func * STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_funcs)[4]= +{ + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod0), + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod1), + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod2), + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod3), +}; + +static stbir__horizontal_gather_channels_func * STBIR_chans(stbir__horizontal_gather_,_channels_funcs)[12]= +{ + STBIR_chans(stbir__horizontal_gather_,_channels_with_1_coeff), + STBIR_chans(stbir__horizontal_gather_,_channels_with_2_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_3_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_4_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_5_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_6_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_7_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_8_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_9_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_10_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_11_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_12_coeffs), +}; + +#undef STBIR__horizontal_channels +#undef STB_IMAGE_RESIZE_DO_HORIZONTALS +#undef stbir__1_coeff_only +#undef stbir__1_coeff_remnant +#undef stbir__2_coeff_only +#undef stbir__2_coeff_remnant +#undef stbir__3_coeff_only +#undef stbir__3_coeff_remnant +#undef stbir__3_coeff_setup +#undef stbir__4_coeff_start +#undef stbir__4_coeff_continue_from_4 +#undef stbir__store_output +#undef stbir__store_output_tiny +#undef STBIR_chans + +#endif // HORIZONALS + +#undef STBIR_strs_join2 +#undef STBIR_strs_join1 + +#endif // STB_IMAGE_RESIZE_DO_HORIZONTALS/VERTICALS/CODERS + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_image_resize_test/dotimings.c b/source/engine/thirdparty/stb/stb_image_resize_test/dotimings.c new file mode 100644 index 0000000..515c5d5 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_image_resize_test/dotimings.c @@ -0,0 +1,224 @@ +#include +#include +#include + +#ifdef _MSC_VER + +#define stop() __debugbreak() +#include +#define int64 __int64 +#pragma warning(disable:4127) + +#define get_milliseconds GetTickCount + +#else + +#define stop() __builtin_trap() +#define int64 long long + +typedef unsigned int U32; +typedef unsigned long long U64; + +#include +static int get_milliseconds() +{ + struct timespec ts; + clock_gettime( CLOCK_MONOTONIC, &ts ); + return (U32) ( ( ((U64)(U32)ts.tv_sec) * 1000LL ) + (U64)(((U32)ts.tv_nsec+500000)/1000000) ); +} + +#endif + +#if defined(TIME_SIMD) + // default for most platforms +#elif defined(TIME_SCALAR) + #define STBIR_NO_SIMD +#else + #error You must define TIME_SIMD or TIME_SCALAR when compiling this file. +#endif + +#define STBIR_PROFILE +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#define STBIR__V_FIRST_INFO_BUFFER v_info +#include "stb_image_resize2.h" // new one! + +#if defined(TIME_SIMD) && !defined(STBIR_SIMD) +#error Timing SIMD, but scalar was ON! +#endif + +#if defined(TIME_SCALAR) && defined(STBIR_SIMD) +#error Timing scalar, but SIMD was ON! +#endif + +#define HEADER 32 + + +static int file_write( const char *filename, void * buffer, size_t size ) +{ + FILE * f = fopen( filename, "wb" ); + if ( f == 0 ) return 0; + if ( fwrite( buffer, 1, size, f) != size ) return 0; + fclose(f); + return 1; +} + +int64 nresize( void * o, int ox, int oy, int op, void * i, int ix, int iy, int ip, int buf, int type, int edg, int flt ) +{ + STBIR_RESIZE resize; + int t; + int64 b; + + stbir_resize_init( &resize, i, ix, iy, ip, o, ox, oy, op, buf, type ); + stbir_set_edgemodes( &resize, edg, edg ); + stbir_set_filters( &resize, flt, flt ); + + stbir_build_samplers_with_splits( &resize, 1 ); + + b = 0x7fffffffffffffffULL; + for( t = 0 ; t < 16 ; t++ ) + { + STBIR_PROFILE_INFO profile; + int64 v; + if(!stbir_resize_extended( &resize ) ) + stop(); + stbir_resize_extended_profile_info( &profile, &resize ); + v = profile.clocks[1]+profile.clocks[2]; + if ( v < b ) + { + b = v; + t = 0; + } + } + + stbir_free_samplers( &resize ); + + return b; +} + + +#define INSIZES 5 +#define TYPESCOUNT 5 +#define NUM 64 + +static const int sizes[INSIZES]={63,126,252,520,772}; +static const int types[TYPESCOUNT]={STBIR_1CHANNEL,STBIR_2CHANNEL,STBIR_RGB,STBIR_4CHANNEL,STBIR_RGBA}; +static const int effective[TYPESCOUNT]={1,2,3,4,7}; + +int main( int argc, char ** argv ) +{ + unsigned char * input; + unsigned char * output; + int dimensionx, dimensiony; + int scalex, scaley; + int totalms; + int timing_count; + int ir; + int * file; + int * ts; + int64 totalcycles; + + if ( argc != 6 ) + { + printf("command: dotimings x_samps y_samps x_scale y_scale outfilename\n"); + exit(1); + } + + input = malloc( 4*1200*1200 ); + memset( input, 0x80, 4*1200*1200 ); + output = malloc( 4*10000*10000ULL ); + + dimensionx = atoi( argv[1] ); + dimensiony = atoi( argv[2] ); + scalex = atoi( argv[3] ); + scaley = atoi( argv[4] ); + + timing_count = dimensionx * dimensiony * INSIZES * TYPESCOUNT; + + file = malloc( sizeof(int) * ( 2 * timing_count + HEADER ) ); + ts = file + HEADER; + + totalms = get_milliseconds(); + totalcycles = STBIR_PROFILE_FUNC(); + for( ir = 0 ; ir < INSIZES ; ir++ ) + { + int ix, iy, ty; + ix = iy = sizes[ir]; + + for( ty = 0 ; ty < TYPESCOUNT ; ty++ ) + { + int h, hh; + + h = 1; + for( hh = 0 ; hh < dimensiony; hh++ ) + { + int ww, w = 1; + for( ww = 0 ; ww < dimensionx; ww++ ) + { + int64 VF, HF; + int good; + + v_info.control_v_first = 2; // vertical first + VF = nresize( output, w, h, (w*4*1)&~3, input, ix, iy, ix*4*1, types[ty], STBIR_TYPE_UINT8, STBIR_EDGE_CLAMP, STBIR_FILTER_MITCHELL ); + v_info.control_v_first = 1; // horizonal first + HF = nresize( output, w, h, (w*4*1)&~3, input, ix, iy, ix*4*1, types[ty], STBIR_TYPE_UINT8, STBIR_EDGE_CLAMP, STBIR_FILTER_MITCHELL ); + + good = ( ((HF<=VF) && (!v_info.v_first)) || ((VF<=HF) && (v_info.v_first))); + +// printf("\r%d,%d, %d,%d, %d, %I64d,%I64d, // Good: %c(%c-%d) CompEst: %.1f %.1f\n", ix, iy, w, h, ty, VF, HF, good?'y':'n', v_info.v_first?'v':'h', v_info.v_resize_classification, v_info.v_cost,v_info.h_cost ); + ts[0] = (int)VF; + ts[1] = (int)HF; + + ts += 2; + + w += scalex; + } + printf("."); + h += scaley; + } + } + } + totalms = get_milliseconds() - totalms; + totalcycles = STBIR_PROFILE_FUNC() - totalcycles; + + printf("\n"); + + file[0] = 'VFT1'; + + #if defined(_x86_64) || defined( __x86_64__ ) || defined( _M_X64 ) || defined(__x86_64) || defined(__SSE2__) || defined( _M_IX86_FP ) || defined(__i386) || defined( __i386__ ) || defined( _M_IX86 ) || defined( _X86_ ) + file[1] = 1; // x64 + #elif defined( _M_AMD64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || defined(__ARM_NEON__) || defined(__ARM_NEON) || defined(__arm__) || defined( _M_ARM ) + file[1] = 2; // arm + #else + file[1] = 99; // who knows??? + #endif + + #ifdef STBIR_SIMD8 + file[2] = 2; // simd-8 + #elif defined( STBIR_SIMD ) + file[2] = 1; // simd-4 + #else + file[2] = 0; // nosimd + #endif + + file[3] = dimensionx; // dimx + file[4] = dimensiony; // dimy + file[5] = TYPESCOUNT; // channel types + file[ 6] = types[0]; file[7] = types[1]; file[8] = types[2]; file[9] = types[3]; file[10] = types[4]; // buffer_type + file[11] = effective[0]; file[12] = effective[1]; file[13] = effective[2]; file[14] = effective[3]; file[15] = effective[4]; // effective channels + file[16] = INSIZES; // resizes + file[17] = sizes[0]; file[18] = sizes[0]; // input sizes (w x h) + file[19] = sizes[1]; file[20] = sizes[1]; + file[21] = sizes[2]; file[22] = sizes[2]; + file[23] = sizes[3]; file[24] = sizes[3]; + file[25] = sizes[4]; file[26] = sizes[4]; + file[27] = scalex; file[28] = scaley; // scale the dimx and dimy amount ( for(i=0;i2GB) images + 0.91 (2016-04-02) fix warnings; fix handling of subpixel regions + 0.90 (2014-09-17) first released version + + LICENSE + See end of file for license information. + + TODO + Don't decode all of the image data when only processing a partial tile + Don't use full-width decode buffers when only processing a partial tile + When processing wide images, break processing into tiles so data fits in L1 cache + Installable filters? + Resize that respects alpha test coverage + (Reference code: FloatImage::alphaTestCoverage and FloatImage::scaleAlphaToCoverage: + https://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvimage/FloatImage.cpp ) +*/ + +#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE_H +#define STBIR_INCLUDE_STB_IMAGE_RESIZE_H + +#ifdef _MSC_VER +typedef unsigned char stbir_uint8; +typedef unsigned short stbir_uint16; +typedef unsigned int stbir_uint32; +typedef unsigned __int64 stbir_uint64; +#else +#include +typedef uint8_t stbir_uint8; +typedef uint16_t stbir_uint16; +typedef uint32_t stbir_uint32; +typedef uint64_t stbir_uint64; +#endif + +#ifndef STBIRDEF +#ifdef STB_IMAGE_RESIZE_STATIC +#define STBIRDEF static +#else +#ifdef __cplusplus +#define STBIRDEF extern "C" +#else +#define STBIRDEF extern +#endif +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Easy-to-use API: +// +// * "input pixels" points to an array of image data with 'num_channels' channels (e.g. RGB=3, RGBA=4) +// * input_w is input image width (x-axis), input_h is input image height (y-axis) +// * stride is the offset between successive rows of image data in memory, in bytes. you can +// specify 0 to mean packed continuously in memory +// * alpha channel is treated identically to other channels. +// * colorspace is linear or sRGB as specified by function name +// * returned result is 1 for success or 0 in case of an error. +// #define STBIR_ASSERT() to trigger an assert on parameter validation errors. +// * Memory required grows approximately linearly with input and output size, but with +// discontinuities at input_w == output_w and input_h == output_h. +// * These functions use a "default" resampling filter defined at compile time. To change the filter, +// you can change the compile-time defaults by #defining STBIR_DEFAULT_FILTER_UPSAMPLE +// and STBIR_DEFAULT_FILTER_DOWNSAMPLE, or you can use the medium-complexity API. + +STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels); + +STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels); + + +// The following functions interpret image data as gamma-corrected sRGB. +// Specify STBIR_ALPHA_CHANNEL_NONE if you have no alpha channel, +// or otherwise provide the index of the alpha channel. Flags value +// of 0 will probably do the right thing if you're not sure what +// the flags mean. + +#define STBIR_ALPHA_CHANNEL_NONE -1 + +// Set this flag if your texture has premultiplied alpha. Otherwise, stbir will +// use alpha-weighted resampling (effectively premultiplying, resampling, +// then unpremultiplying). +#define STBIR_FLAG_ALPHA_PREMULTIPLIED (1 << 0) +// The specified alpha channel should be handled as gamma-corrected value even +// when doing sRGB operations. +#define STBIR_FLAG_ALPHA_USES_COLORSPACE (1 << 1) + +#define STBIR_FLAG_ALPHA_OUT_PREMULTIPLIED (1 << 2) + +STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags); + + +typedef enum +{ + STBIR_EDGE_CLAMP = 1, + STBIR_EDGE_REFLECT = 2, + STBIR_EDGE_WRAP = 3, + STBIR_EDGE_ZERO = 4, +} stbir_edge; + +// This function adds the ability to specify how requests to sample off the edge of the image are handled. +STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode); + +////////////////////////////////////////////////////////////////////////////// +// +// Medium-complexity API +// +// This extends the easy-to-use API as follows: +// +// * Alpha-channel can be processed separately +// * If alpha_channel is not STBIR_ALPHA_CHANNEL_NONE +// * Alpha channel will not be gamma corrected (unless flags&STBIR_FLAG_GAMMA_CORRECT) +// * Filters will be weighted by alpha channel (unless flags&STBIR_FLAG_ALPHA_PREMULTIPLIED) +// * Filter can be selected explicitly +// * uint16 image type +// * sRGB colorspace available for all types +// * context parameter for passing to STBIR_MALLOC + +typedef enum +{ + STBIR_FILTER_DEFAULT = 0, // use same filter type that easy-to-use API chooses + STBIR_FILTER_BOX = 1, // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios + STBIR_FILTER_TRIANGLE = 2, // On upsampling, produces same results as bilinear texture filtering + STBIR_FILTER_CUBICBSPLINE = 3, // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque + STBIR_FILTER_CATMULLROM = 4, // An interpolating cubic spline + STBIR_FILTER_MITCHELL = 5, // Mitchell-Netrevalli filter with B=1/3, C=1/3 +} stbir_filter; + +typedef enum +{ + STBIR_COLORSPACE_LINEAR, + STBIR_COLORSPACE_SRGB, + + STBIR_MAX_COLORSPACES, +} stbir_colorspace; + +// The following functions are all identical except for the type of the image data + +STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + + + +////////////////////////////////////////////////////////////////////////////// +// +// Full-complexity API +// +// This extends the medium API as follows: +// +// * uint32 image type +// * not typesafe +// * separate filter types for each axis +// * separate edge modes for each axis +// * can specify scale explicitly for subpixel correctness +// * can specify image source tile using texture coordinates + +typedef enum +{ + STBIR_TYPE_UINT8 , + STBIR_TYPE_UINT16, + STBIR_TYPE_FLOAT , + STBIR_TYPE_UINT32, + + STBIR_MAX_TYPES +} stbir_datatype; + +STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context); + +STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float x_scale, float y_scale, + float x_offset, float y_offset); + +STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float s0, float t0, float s1, float t1); +// (s0, t0) & (s1, t1) are the top-left and bottom right corner (uv addressing style: [0, 1]x[0, 1]) of a region of the input image to use. + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE_H + + + + + +#ifdef STB_IMAGE_RESIZE_IMPLEMENTATION + +#ifndef STBIR_ASSERT +#include +#define STBIR_ASSERT(x) assert(x) +#endif + +// For memset +#include + +#include + +#ifndef STBIR_MALLOC +#include +// use comma operator to evaluate c, to avoid "unused parameter" warnings +#define STBIR_MALLOC(size,c) ((void)(c), malloc(size)) +#define STBIR_FREE(ptr,c) ((void)(c), free(ptr)) +#endif + +#ifndef _MSC_VER +#ifdef __cplusplus +#define stbir__inline inline +#else +#define stbir__inline +#endif +#else +#define stbir__inline __forceinline +#endif + +#ifdef STBIR_PROFILE + +union +{ + struct { stbir_uint64 total, setup, filters, looping, vertical, horizontal, decode, encode, alpha, unalpha; } named; + stbir_uint64 array[10]; +} oldprofile; +stbir_uint64 * current_zone_excluded_ptr; + +#if defined(_x86_64) || defined( __x86_64__ ) || defined( _M_X64 ) || defined(__x86_64) || defined(__SSE2__) || defined(STBIR_SSE) || defined( _M_IX86_FP ) || defined(__i386) || defined( __i386__ ) || defined( _M_IX86 ) || defined( _X86_ ) + +#ifdef _MSC_VER + + STBIRDEF stbir_uint64 __rdtsc(); + #define STBIR_PROFILE_FUNC() __rdtsc() + +#else // non msvc + + static stbir__inline stbir_uint64 STBIR_PROFILE_FUNC() + { + stbir_uint32 lo, hi; + asm volatile ("rdtsc" : "=a" (lo), "=d" (hi) ); + return ( ( (stbir_uint64) hi ) << 32 ) | ( (stbir_uint64) lo ); + } + +#endif // msvc + +#elif defined( _M_AMD64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || defined(__ARM_NEON__) || defined(__ARM_NEON) + +#ifdef _MSC_VER + + #error Not sure what the intrinsic for cntvct_el0 is on MSVC + +#else // no msvc + + static stbir__inline stbir_uint64 STBIR_PROFILE_FUNC() + { + stbir_uint64 tsc; + asm volatile("mrs %0, cntvct_el0" : "=r" (tsc)); + return tsc; + } + +#endif + +#elif // x64, arm + +#error Unknown platform for profiling. + +#endif //x64 and + +#define STBIR_PROFILE_START() { stbir_uint64 thiszonetime = STBIR_PROFILE_FUNC(); stbir_uint64 * save_parent_excluded_ptr = current_zone_excluded_ptr; stbir_uint64 current_zone_excluded = 0; current_zone_excluded_ptr = ¤t_zone_excluded; +#define STBIR_PROFILE_END( wh ) thiszonetime = STBIR_PROFILE_FUNC() - thiszonetime; oldprofile.named.wh += thiszonetime - current_zone_excluded; *save_parent_excluded_ptr += thiszonetime; current_zone_excluded_ptr = save_parent_excluded_ptr; } +#define STBIR_PROFILE_FIRST_START() { int i; current_zone_excluded_ptr = &oldprofile.named.total; for(i=0;i 65536 +#error "Too many channels; STBIR_MAX_CHANNELS must be no more than 65536." +// because we store the indices in 16-bit variables +#endif + +// This value is added to alpha just before premultiplication to avoid +// zeroing out color values. It is equivalent to 2^-80. If you don't want +// that behavior (it may interfere if you have floating point images with +// very small alpha values) then you can define STBIR_NO_ALPHA_EPSILON to +// disable it. +#ifndef STBIR_ALPHA_EPSILON +#define STBIR_ALPHA_EPSILON ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20)) +#endif + + + +#ifdef _MSC_VER +#define STBIR__UNUSED_PARAM(v) (void)(v) +#else +#define STBIR__UNUSED_PARAM(v) (void)sizeof(v) +#endif + +// must match stbir_datatype +static unsigned char stbir__type_size[] = { + 1, // STBIR_TYPE_UINT8 + 2, // STBIR_TYPE_UINT16 + 4, // STBIR_TYPE_UINT32 + 4, // STBIR_TYPE_FLOAT +}; + +// Kernel function centered at 0 +typedef float (stbir__kernel_fn)(float x, float scale); +typedef float (stbir__support_fn)(float scale); + +typedef struct +{ + stbir__kernel_fn* kernel; + stbir__support_fn* support; +} stbir__filter_info; + +// When upsampling, the contributors are which source pixels contribute. +// When downsampling, the contributors are which destination pixels are contributed to. +typedef struct +{ + int n0; // First contributing pixel + int n1; // Last contributing pixel +} stbir__contributors; + +typedef struct +{ + const void* input_data; + int input_w; + int input_h; + int input_stride_bytes; + + void* output_data; + int output_w; + int output_h; + int output_stride_bytes; + + float s0, t0, s1, t1; + + float horizontal_shift; // Units: output pixels + float vertical_shift; // Units: output pixels + float horizontal_scale; + float vertical_scale; + + int channels; + int alpha_channel; + stbir_uint32 flags; + stbir_datatype type; + stbir_filter horizontal_filter; + stbir_filter vertical_filter; + stbir_edge edge_horizontal; + stbir_edge edge_vertical; + stbir_colorspace colorspace; + + stbir__contributors* horizontal_contributors; + float* horizontal_coefficients; + + stbir__contributors* vertical_contributors; + float* vertical_coefficients; + + int decode_buffer_pixels; + float* decode_buffer; + + float* horizontal_buffer; + + // cache these because ceil/floor are inexplicably showing up in profile + int horizontal_coefficient_width; + int vertical_coefficient_width; + int horizontal_filter_pixel_width; + int vertical_filter_pixel_width; + int horizontal_filter_pixel_margin; + int vertical_filter_pixel_margin; + int horizontal_num_contributors; + int vertical_num_contributors; + + int ring_buffer_length_bytes; // The length of an individual entry in the ring buffer. The total number of ring buffers is stbir__get_filter_pixel_width(filter) + int ring_buffer_num_entries; // Total number of entries in the ring buffer. + int ring_buffer_first_scanline; + int ring_buffer_last_scanline; + int ring_buffer_begin_index; // first_scanline is at this index in the ring buffer + float* ring_buffer; + + float* encode_buffer; // A temporary buffer to store floats so we don't lose precision while we do multiply-adds. + + int horizontal_contributors_size; + int horizontal_coefficients_size; + int vertical_contributors_size; + int vertical_coefficients_size; + int decode_buffer_size; + int horizontal_buffer_size; + int ring_buffer_size; + int encode_buffer_size; +} ostbir__info; + + +static const float stbir__max_uint8_as_float = 255.0f; +static const float stbir__max_uint16_as_float = 65535.0f; +static const double stbir__max_uint32_as_float = 4294967295.0; + + +static stbir__inline int stbir__min(int a, int b) +{ + return a < b ? a : b; +} + +static stbir__inline float stbir__saturate(float x) +{ + if (x < 0) + return 0; + + if (x > 1) + return 1; + + return x; +} + +#ifdef STBIR_SATURATE_INT +static stbir__inline stbir_uint8 stbir__saturate8(int x) +{ + if ((unsigned int) x <= 255) + return (stbir_uint8) x; + + if (x < 0) + return 0; + + return 255; +} + +static stbir__inline stbir_uint16 stbir__saturate16(int x) +{ + if ((unsigned int) x <= 65535) + return (stbir_uint16) x; + + if (x < 0) + return 0; + + return 65535; +} +#endif + +static float stbir__srgb_uchar_to_linear_float[256] = { + 0.000000f, 0.000304f, 0.000607f, 0.000911f, 0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f, + 0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f, 0.006049f, 0.006512f, 0.006995f, 0.007499f, + 0.008023f, 0.008568f, 0.009134f, 0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f, 0.014444f, + 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f, 0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f, + 0.025187f, 0.026241f, 0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f, 0.035601f, 0.036889f, + 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f, 0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f, + 0.054480f, 0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f, 0.068478f, 0.070360f, 0.072272f, + 0.074214f, 0.076185f, 0.078187f, 0.080220f, 0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f, + 0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f, 0.114435f, 0.116971f, 0.119538f, 0.122139f, + 0.124772f, 0.127438f, 0.130136f, 0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f, 0.152926f, + 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f, 0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f, + 0.191202f, 0.194618f, 0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f, 0.223228f, 0.226966f, + 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f, 0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f, + 0.274677f, 0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f, 0.309469f, 0.313989f, 0.318547f, + 0.323143f, 0.327778f, 0.332452f, 0.337164f, 0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f, + 0.376262f, 0.381326f, 0.386430f, 0.391573f, 0.396755f, 0.401978f, 0.407240f, 0.412543f, 0.417885f, 0.423268f, 0.428691f, + 0.434154f, 0.439657f, 0.445201f, 0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473532f, 0.479320f, 0.485150f, 0.491021f, + 0.496933f, 0.502887f, 0.508881f, 0.514918f, 0.520996f, 0.527115f, 0.533276f, 0.539480f, 0.545725f, 0.552011f, 0.558340f, + 0.564712f, 0.571125f, 0.577581f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f, 0.623960f, 0.630757f, + 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f, 0.672443f, 0.679543f, 0.686685f, 0.693872f, 0.701102f, 0.708376f, + 0.715694f, 0.723055f, 0.730461f, 0.737911f, 0.745404f, 0.752942f, 0.760525f, 0.768151f, 0.775822f, 0.783538f, 0.791298f, + 0.799103f, 0.806952f, 0.814847f, 0.822786f, 0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f, + 0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f, 0.947307f, 0.955974f, 0.964686f, 0.973445f, + 0.982251f, 0.991102f, 1.0f +}; + +static float stbir__srgb_to_linear(float f) +{ + if (f <= 0.04045f) + return f / 12.92f; + else + return (float)pow((f + 0.055f) / 1.055f, 2.4f); +} + +static float stbir__linear_to_srgb(float f) +{ + if (f <= 0.0031308f) + return f * 12.92f; + else + return 1.055f * (float)pow(f, 1 / 2.4f) - 0.055f; +} + +#ifndef STBIR_NON_IEEE_FLOAT +// From https://gist.github.com/rygorous/2203834 + +typedef union +{ + stbir_uint32 u; + float f; +} stbir__FP32; + +static const stbir_uint32 fp32_to_srgb8_tab4[104] = { + 0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d, + 0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a, + 0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033, + 0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067, + 0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5, + 0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2, + 0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143, + 0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af, + 0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240, + 0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300, + 0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401, + 0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559, + 0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723, +}; + +static stbir_uint8 stbir__linear_to_srgb_uchar(float in) +{ + static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps + static const stbir__FP32 minval = { (127-13) << 23 }; + stbir_uint32 tab,bias,scale,t; + stbir__FP32 f; + + // Clamp to [2^(-13), 1-eps]; these two values map to 0 and 1, respectively. + // The tests are carefully written so that NaNs map to 0, same as in the reference + // implementation. + if (!(in > minval.f)) // written this way to catch NaNs + in = minval.f; + if (in > almostone.f) + in = almostone.f; + + // Do the table lookup and unpack bias, scale + f.f = in; + tab = fp32_to_srgb8_tab4[(f.u - minval.u) >> 20]; + bias = (tab >> 16) << 9; + scale = tab & 0xffff; + + // Grab next-highest mantissa bits and perform linear interpolation + t = (f.u >> 12) & 0xff; + return (unsigned char) ((bias + scale*t) >> 16); +} + +#else +// sRGB transition values, scaled by 1<<28 +static int stbir__srgb_offset_to_linear_scaled[256] = +{ + 0, 40738, 122216, 203693, 285170, 366648, 448125, 529603, + 611080, 692557, 774035, 855852, 942009, 1033024, 1128971, 1229926, + 1335959, 1447142, 1563542, 1685229, 1812268, 1944725, 2082664, 2226148, + 2375238, 2529996, 2690481, 2856753, 3028870, 3206888, 3390865, 3580856, + 3776916, 3979100, 4187460, 4402049, 4622919, 4850123, 5083710, 5323731, + 5570236, 5823273, 6082892, 6349140, 6622065, 6901714, 7188133, 7481369, + 7781466, 8088471, 8402427, 8723380, 9051372, 9386448, 9728650, 10078021, + 10434603, 10798439, 11169569, 11548036, 11933879, 12327139, 12727857, 13136073, + 13551826, 13975156, 14406100, 14844697, 15290987, 15745007, 16206795, 16676389, + 17153826, 17639142, 18132374, 18633560, 19142734, 19659934, 20185196, 20718552, + 21260042, 21809696, 22367554, 22933648, 23508010, 24090680, 24681686, 25281066, + 25888850, 26505076, 27129772, 27762974, 28404716, 29055026, 29713942, 30381490, + 31057708, 31742624, 32436272, 33138682, 33849884, 34569912, 35298800, 36036568, + 36783260, 37538896, 38303512, 39077136, 39859796, 40651528, 41452360, 42262316, + 43081432, 43909732, 44747252, 45594016, 46450052, 47315392, 48190064, 49074096, + 49967516, 50870356, 51782636, 52704392, 53635648, 54576432, 55526772, 56486700, + 57456236, 58435408, 59424248, 60422780, 61431036, 62449032, 63476804, 64514376, + 65561776, 66619028, 67686160, 68763192, 69850160, 70947088, 72053992, 73170912, + 74297864, 75434880, 76581976, 77739184, 78906536, 80084040, 81271736, 82469648, + 83677792, 84896192, 86124888, 87363888, 88613232, 89872928, 91143016, 92423512, + 93714432, 95015816, 96327688, 97650056, 98982952, 100326408, 101680440, 103045072, + 104420320, 105806224, 107202800, 108610064, 110028048, 111456776, 112896264, 114346544, + 115807632, 117279552, 118762328, 120255976, 121760536, 123276016, 124802440, 126339832, + 127888216, 129447616, 131018048, 132599544, 134192112, 135795792, 137410592, 139036528, + 140673648, 142321952, 143981456, 145652208, 147334208, 149027488, 150732064, 152447968, + 154175200, 155913792, 157663776, 159425168, 161197984, 162982240, 164777968, 166585184, + 168403904, 170234160, 172075968, 173929344, 175794320, 177670896, 179559120, 181458992, + 183370528, 185293776, 187228736, 189175424, 191133888, 193104112, 195086128, 197079968, + 199085648, 201103184, 203132592, 205173888, 207227120, 209292272, 211369392, 213458480, + 215559568, 217672656, 219797792, 221934976, 224084240, 226245600, 228419056, 230604656, + 232802400, 235012320, 237234432, 239468736, 241715280, 243974080, 246245120, 248528464, + 250824112, 253132064, 255452368, 257785040, 260130080, 262487520, 264857376, 267239664, +}; + +static stbir_uint8 stbir__linear_to_srgb_uchar(float f) +{ + int x = (int) (f * (1 << 28)); // has headroom so you don't need to clamp + int v = 0; + int i; + + // Refine the guess with a short binary search. + i = v + 128; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 64; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 32; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 16; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 8; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 4; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 2; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 1; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + + return (stbir_uint8) v; +} +#endif + +static float stbir__filter_trapezoid(float x, float scale) +{ + float halfscale = scale / 2; + float t = 0.5f + halfscale; + STBIR_ASSERT(scale <= 1); + + x = (float)fabs(x); + + if (x >= t) + return 0; + else + { + float r = 0.5f - halfscale; + if (x <= r) + return 1; + else + return (t - x) / scale; + } +} + +static float stbir__support_trapezoid(float scale) +{ + STBIR_ASSERT(scale <= 1); + return 0.5f + scale / 2; +} + +static float stbir__filter_triangle(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x <= 1.0f) + return 1 - x; + else + return 0; +} + +static float stbir__filter_cubic(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return (4 + x*x*(3*x - 6))/6; + else if (x < 2.0f) + return (8 + x*(-12 + x*(6 - x)))/6; + + return (0.0f); +} + +static float stbir__filter_catmullrom(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return 1 - x*x*(2.5f - 1.5f*x); + else if (x < 2.0f) + return 2 - x*(4 + x*(0.5f*x - 2.5f)); + + return (0.0f); +} + +static float stbir__filter_mitchell(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return (16 + x*x*(21 * x - 36))/18; + else if (x < 2.0f) + return (32 + x*(-60 + x*(36 - 7*x)))/18; + + return (0.0f); +} + +static float stbir__support_zero(float s) +{ + STBIR__UNUSED_PARAM(s); + return 0; +} + +static float stbir__support_one(float s) +{ + STBIR__UNUSED_PARAM(s); + return 1; +} + +static float stbir__support_two(float s) +{ + STBIR__UNUSED_PARAM(s); + return 2; +} + +static stbir__filter_info stbir__filter_info_table[] = { + { NULL, stbir__support_zero }, + { stbir__filter_trapezoid, stbir__support_trapezoid }, + { stbir__filter_triangle, stbir__support_one }, + { stbir__filter_cubic, stbir__support_two }, + { stbir__filter_catmullrom, stbir__support_two }, + { stbir__filter_mitchell, stbir__support_two }, +}; + +stbir__inline static int stbir__use_upsampling(float ratio) +{ + return ratio > 1; +} + +stbir__inline static int stbir__use_width_upsampling(ostbir__info* stbir_info) +{ + return stbir__use_upsampling(stbir_info->horizontal_scale); +} + +stbir__inline static int stbir__use_height_upsampling(ostbir__info* stbir_info) +{ + return stbir__use_upsampling(stbir_info->vertical_scale); +} + +// This is the maximum number of input samples that can affect an output sample +// with the given filter +static int stbir__get_filter_pixel_width(stbir_filter filter, float scale) +{ + STBIR_ASSERT(filter != 0); + STBIR_ASSERT(filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + + if (stbir__use_upsampling(scale)) + return (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2); + else + return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2 / scale); +} + +// This is how much to expand buffers to account for filters seeking outside +// the image boundaries. +static int stbir__get_filter_pixel_margin(stbir_filter filter, float scale) +{ + return stbir__get_filter_pixel_width(filter, scale) / 2; +} + +static int stbir__get_coefficient_width(stbir_filter filter, float scale) +{ + if (stbir__use_upsampling(scale)) + return (int)ceil(stbir__filter_info_table[filter].support(1 / scale) * 2); + else + return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2); +} + +static int stbir__get_contributors(float scale, stbir_filter filter, int input_size, int output_size) +{ + if (stbir__use_upsampling(scale)) + return output_size; + else + return (input_size + stbir__get_filter_pixel_margin(filter, scale) * 2); +} + +static int stbir__get_total_horizontal_coefficients(ostbir__info* info) +{ + return info->horizontal_num_contributors + * stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale); +} + +static int stbir__get_total_vertical_coefficients(ostbir__info* info) +{ + return info->vertical_num_contributors + * stbir__get_coefficient_width (info->vertical_filter, info->vertical_scale); +} + +static stbir__contributors* stbir__get_contributor(stbir__contributors* contributors, int n) +{ + return &contributors[n]; +} + +// For perf reasons this code is duplicated in stbir__resample_horizontal_upsample/downsample, +// if you change it here change it there too. +static float* stbir__get_coefficient(float* coefficients, stbir_filter filter, float scale, int n, int c) +{ + int width = stbir__get_coefficient_width(filter, scale); + return &coefficients[width*n + c]; +} + +static int stbir__edge_wrap_slow(stbir_edge edge, int n, int max) +{ + switch (edge) + { + case STBIR_EDGE_ZERO: + return 0; // we'll decode the wrong pixel here, and then overwrite with 0s later + + case STBIR_EDGE_CLAMP: + if (n < 0) + return 0; + + if (n >= max) + return max - 1; + + return n; // NOTREACHED + + case STBIR_EDGE_REFLECT: + { + if (n < 0) + { + if (n > -max) + return -n; + else + return max - 1; + } + + if (n >= max) + { + int max2 = max * 2; + if (n >= max2) + return 0; + else + return max2 - n - 1; + } + + return n; // NOTREACHED + } + + case STBIR_EDGE_WRAP: + if (n >= 0) + return (n % max); + else + { + int m = (-n) % max; + + if (m != 0) + m = max - m; + + return (m); + } + // NOTREACHED + + default: + STBIR_ASSERT(!"Unimplemented edge type"); + return 0; + } +} + +stbir__inline static int stbir__edge_wrap(stbir_edge edge, int n, int max) +{ + // avoid per-pixel switch + if (n >= 0 && n < max) + return n; + return stbir__edge_wrap_slow(edge, n, max); +} + +// What input pixels contribute to this output pixel? +static void stbir__calculate_sample_range_upsample(int n, float out_filter_radius, float scale_ratio, float out_shift, int* in_first_pixel, int* in_last_pixel, float* in_center_of_out) +{ + float out_pixel_center = (float)n + 0.5f; + float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius; + float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius; + + float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) / scale_ratio; + float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) / scale_ratio; + + *in_center_of_out = (out_pixel_center + out_shift) / scale_ratio; + *in_first_pixel = (int)(floor(in_pixel_influence_lowerbound + 0.5)); + *in_last_pixel = (int)(floor(in_pixel_influence_upperbound - 0.5)); +} + +// What output pixels does this input pixel contribute to? +static void stbir__calculate_sample_range_downsample(int n, float in_pixels_radius, float scale_ratio, float out_shift, int* out_first_pixel, int* out_last_pixel, float* out_center_of_in) +{ + float in_pixel_center = (float)n + 0.5f; + float in_pixel_influence_lowerbound = in_pixel_center - in_pixels_radius; + float in_pixel_influence_upperbound = in_pixel_center + in_pixels_radius; + + float out_pixel_influence_lowerbound = in_pixel_influence_lowerbound * scale_ratio - out_shift; + float out_pixel_influence_upperbound = in_pixel_influence_upperbound * scale_ratio - out_shift; + + *out_center_of_in = in_pixel_center * scale_ratio - out_shift; + *out_first_pixel = (int)(floor(out_pixel_influence_lowerbound + 0.5)); + *out_last_pixel = (int)(floor(out_pixel_influence_upperbound - 0.5)); +} + +static void stbir__calculate_coefficients_upsample(stbir_filter filter, float scale, int in_first_pixel, int in_last_pixel, float in_center_of_out, stbir__contributors* contributor, float* coefficient_group) +{ + int i; + float total_filter = 0; + float filter_scale; + + STBIR_ASSERT(in_last_pixel - in_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical. + + contributor->n0 = in_first_pixel; + contributor->n1 = in_last_pixel; + + STBIR_ASSERT(contributor->n1 >= contributor->n0); + + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + { + float in_pixel_center = (float)(i + in_first_pixel) + 0.5f; + coefficient_group[i] = stbir__filter_info_table[filter].kernel(in_center_of_out - in_pixel_center, 1 / scale); + + // If the coefficient is zero, skip it. (Don't do the <0 check here, we want the influence of those outside pixels.) + if (i == 0 && !coefficient_group[i]) + { + contributor->n0 = ++in_first_pixel; + i--; + continue; + } + + total_filter += coefficient_group[i]; + } + + STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(in_last_pixel + 1) + 0.5f - in_center_of_out, 1/scale) == 0); + + STBIR_ASSERT(total_filter > 0.9); + STBIR_ASSERT(total_filter < 1.1f); // Make sure it's not way off. + + // Make sure the sum of all coefficients is 1. + filter_scale = 1 / total_filter; + + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + coefficient_group[i] *= filter_scale; + + for (i = in_last_pixel - in_first_pixel; i >= 0; i--) + { + if (coefficient_group[i]) + break; + + // This line has no weight. We can skip it. + contributor->n1 = contributor->n0 + i - 1; + } +} + +static void stbir__calculate_coefficients_downsample(stbir_filter filter, float scale_ratio, int out_first_pixel, int out_last_pixel, float out_center_of_in, stbir__contributors* contributor, float* coefficient_group) +{ + int i; + + STBIR_ASSERT(out_last_pixel - out_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(scale_ratio) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical. + + contributor->n0 = out_first_pixel; + contributor->n1 = out_last_pixel; + + STBIR_ASSERT(contributor->n1 >= contributor->n0); + + for (i = 0; i <= out_last_pixel - out_first_pixel; i++) + { + float out_pixel_center = (float)(i + out_first_pixel) + 0.5f; + float x = out_pixel_center - out_center_of_in; + coefficient_group[i] = stbir__filter_info_table[filter].kernel(x, scale_ratio) * scale_ratio; + } + + STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(out_last_pixel + 1) + 0.5f - out_center_of_in, scale_ratio) == 0); + + for (i = out_last_pixel - out_first_pixel; i >= 0; i--) + { + if (coefficient_group[i]) + break; + + // This line has no weight. We can skip it. + contributor->n1 = contributor->n0 + i - 1; + } +} + +static void stbir__normalize_downsample_coefficients(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, int input_size, int output_size) +{ + int num_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size); + int num_coefficients = stbir__get_coefficient_width(filter, scale_ratio); + int i, j; + int skip; + + for (i = 0; i < output_size; i++) + { + float scale; + float total = 0; + + for (j = 0; j < num_contributors; j++) + { + if (i >= contributors[j].n0 && i <= contributors[j].n1) + { + float coefficient = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0); + total += coefficient; + } + else if (i < contributors[j].n0) + break; + } + + //STBIR_ASSERT(total > 0.9f); + //STBIR_ASSERT(total < 1.5f); + + scale = 1 / total; + + for (j = 0; j < num_contributors; j++) + { + if (i >= contributors[j].n0 && i <= contributors[j].n1) + *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0) *= scale; + else if (i < contributors[j].n0) + break; + } + } + + // Optimize: Skip zero coefficients and contributions outside of image bounds. + // Do this after normalizing because normalization depends on the n0/n1 values. + for (j = 0; j < num_contributors; j++) + { + int range, max, width; + + skip = 0; + while (*stbir__get_coefficient(coefficients, filter, scale_ratio, j, skip) == 0) + skip++; + + contributors[j].n0 += skip; + + while (contributors[j].n0 < 0) + { + contributors[j].n0++; + skip++; + } + + range = contributors[j].n1 - contributors[j].n0 + 1; + max = stbir__min(num_coefficients, range); + + width = stbir__get_coefficient_width(filter, scale_ratio); + for (i = 0; i < max; i++) + { + if (i + skip >= width) + break; + + *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i) = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i + skip); + } + + continue; + } + + // Using min to avoid writing into invalid pixels. + for (i = 0; i < num_contributors; i++) + contributors[i].n1 = stbir__min(contributors[i].n1, output_size - 1); +} + +// Each scan line uses the same kernel values so we should calculate the kernel +// values once and then we can use them for every scan line. +static void stbir__calculate_filters(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, float shift, int input_size, int output_size) +{ + int n; + int total_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size); + + if (stbir__use_upsampling(scale_ratio)) + { + float out_pixels_radius = stbir__filter_info_table[filter].support(1 / scale_ratio) * scale_ratio; + + // Looping through out pixels + for (n = 0; n < total_contributors; n++) + { + float in_center_of_out; // Center of the current out pixel in the in pixel space + int in_first_pixel, in_last_pixel; + + stbir__calculate_sample_range_upsample(n, out_pixels_radius, scale_ratio, shift, &in_first_pixel, &in_last_pixel, &in_center_of_out); + + stbir__calculate_coefficients_upsample(filter, scale_ratio, in_first_pixel, in_last_pixel, in_center_of_out, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0)); + } + } + else + { + float in_pixels_radius = stbir__filter_info_table[filter].support(scale_ratio) / scale_ratio; + + // Looping through in pixels + for (n = 0; n < total_contributors; n++) + { + float out_center_of_in; // Center of the current out pixel in the in pixel space + int out_first_pixel, out_last_pixel; + int n_adjusted = n - stbir__get_filter_pixel_margin(filter, scale_ratio); + + stbir__calculate_sample_range_downsample(n_adjusted, in_pixels_radius, scale_ratio, shift, &out_first_pixel, &out_last_pixel, &out_center_of_in); + + stbir__calculate_coefficients_downsample(filter, scale_ratio, out_first_pixel, out_last_pixel, out_center_of_in, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0)); + } + + stbir__normalize_downsample_coefficients(contributors, coefficients, filter, scale_ratio, input_size, output_size); + } +} + +static float* stbir__get_decode_buffer(ostbir__info* stbir_info) +{ + // The 0 index of the decode buffer starts after the margin. This makes + // it okay to use negative indexes on the decode buffer. + return &stbir_info->decode_buffer[stbir_info->horizontal_filter_pixel_margin * stbir_info->channels]; +} + +#define STBIR__DECODE(type, colorspace) ((type) * (STBIR_MAX_COLORSPACES) + (colorspace)) + +static void stbir__decode_scanline(ostbir__info* stbir_info, int n) +{ + int c; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int input_w = stbir_info->input_w; + size_t input_stride_bytes = stbir_info->input_stride_bytes; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir_edge edge_horizontal = stbir_info->edge_horizontal; + stbir_edge edge_vertical = stbir_info->edge_vertical; + size_t in_buffer_row_offset = stbir__edge_wrap(edge_vertical, n, stbir_info->input_h) * input_stride_bytes; + const void* input_data = (char *) stbir_info->input_data + in_buffer_row_offset; + int max_x = input_w + stbir_info->horizontal_filter_pixel_margin; + int decode = STBIR__DECODE(type, colorspace); + + int x = -stbir_info->horizontal_filter_pixel_margin; + + // special handling for STBIR_EDGE_ZERO because it needs to return an item that doesn't appear in the input, + // and we want to avoid paying overhead on every pixel if not STBIR_EDGE_ZERO + if (edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->input_h)) + { + for (; x < max_x; x++) + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + return; + } + + STBIR_PROFILE_START( ); + switch (decode) + { + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((float)((const unsigned char*)input_data)[input_pixel_index + c]) / stbir__max_uint8_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_uchar_to_linear_float[((const unsigned char*)input_data)[input_pixel_index + c]]; + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned char*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint8_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned short*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint16_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear((float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float)); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((const float*)input_data)[input_pixel_index + c]; + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((const float*)input_data)[input_pixel_index + c]); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((const float*)input_data)[input_pixel_index + alpha_channel]; + } + + break; + + default: + STBIR_ASSERT(!"Unknown type/colorspace/channels combination."); + break; + } + STBIR_PROFILE_END( decode ); + + if (!(stbir_info->flags & STBIR_FLAG_ALPHA_PREMULTIPLIED)) + { + STBIR_PROFILE_START(); + + for (x = -stbir_info->horizontal_filter_pixel_margin; x < max_x; x++) + { + int decode_pixel_index = x * channels; + + // If the alpha value is 0 it will clobber the color values. Make sure it's not. + float alpha = decode_buffer[decode_pixel_index + alpha_channel]; +#ifndef STBIR_NO_ALPHA_EPSILON + if (stbir_info->type != STBIR_TYPE_FLOAT) { + alpha += STBIR_ALPHA_EPSILON; + decode_buffer[decode_pixel_index + alpha_channel] = alpha; + } +#endif + for (c = 0; c < channels; c++) + { + if (c == alpha_channel) + continue; + + decode_buffer[decode_pixel_index + c] *= alpha; + } + } + STBIR_PROFILE_END( alpha ); + } + + if (edge_horizontal == STBIR_EDGE_ZERO) + { + for (x = -stbir_info->horizontal_filter_pixel_margin; x < 0; x++) + { + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + } + for (x = input_w; x < max_x; x++) + { + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + } + } +} + +static float* stbir__get_ring_buffer_entry(float* ring_buffer, int index, int ring_buffer_length) +{ + return &ring_buffer[index * ring_buffer_length]; +} + +static float* stbir__add_empty_ring_buffer_entry(ostbir__info* stbir_info, int n) +{ + int ring_buffer_index; + float* ring_buffer; + + stbir_info->ring_buffer_last_scanline = n; + + if (stbir_info->ring_buffer_begin_index < 0) + { + ring_buffer_index = stbir_info->ring_buffer_begin_index = 0; + stbir_info->ring_buffer_first_scanline = n; + } + else + { + ring_buffer_index = (stbir_info->ring_buffer_begin_index + (stbir_info->ring_buffer_last_scanline - stbir_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries; + STBIR_ASSERT(ring_buffer_index != stbir_info->ring_buffer_begin_index); + } + + ring_buffer = stbir__get_ring_buffer_entry(stbir_info->ring_buffer, ring_buffer_index, stbir_info->ring_buffer_length_bytes / sizeof(float)); + + memset(ring_buffer, 0, stbir_info->ring_buffer_length_bytes); + + return ring_buffer; +} + + +static void stbir__resample_horizontal_upsample(ostbir__info* stbir_info, float* output_buffer) +{ + int x, k; + int output_w = stbir_info->output_w; + int channels = stbir_info->channels; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors; + float* horizontal_coefficients = stbir_info->horizontal_coefficients; + int coefficient_width = stbir_info->horizontal_coefficient_width; + + STBIR_PROFILE_START( ); + for (x = 0; x < output_w; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int out_pixel_index = x * channels; + int coefficient_group = coefficient_width * x; + int coefficient_counter = 0; + + STBIR_ASSERT(n1 >= n0); + STBIR_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n1 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin); + + switch (channels) { + case 1: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 1; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + } + break; + case 2: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 2; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + } + break; + case 3: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 3; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + } + break; + case 4: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 4; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient; + } + break; + default: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * channels; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + int c; + STBIR_ASSERT(coefficient != 0); + for (c = 0; c < channels; c++) + output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient; + } + break; + } + } + STBIR_PROFILE_END( horizontal ); +} + +static void stbir__resample_horizontal_downsample(ostbir__info* stbir_info, float* output_buffer) +{ + int x, k; + int input_w = stbir_info->input_w; + int channels = stbir_info->channels; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors; + float* horizontal_coefficients = stbir_info->horizontal_coefficients; + int coefficient_width = stbir_info->horizontal_coefficient_width; + int filter_pixel_margin = stbir_info->horizontal_filter_pixel_margin; + int max_x = input_w + filter_pixel_margin * 2; + + STBIR_ASSERT(!stbir__use_width_upsampling(stbir_info)); + + STBIR_PROFILE_START( ); + switch (channels) { + case 1: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 1; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 1; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + } + } + break; + + case 2: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 2; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 2; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + } + } + break; + + case 3: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 3; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 3; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + //STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + } + } + break; + + case 4: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 4; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 4; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient; + } + } + break; + + default: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * channels; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int c; + int out_pixel_index = k * channels; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + STBIR_ASSERT(coefficient != 0); + for (c = 0; c < channels; c++) + output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient; + } + } + break; + } + STBIR_PROFILE_END( horizontal ); +} + +static void stbir__decode_and_resample_upsample(ostbir__info* stbir_info, int n) +{ + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline(stbir_info, n); + + // Now resample it into the ring buffer. + if (stbir__use_width_upsampling(stbir_info)) + stbir__resample_horizontal_upsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n)); + else + stbir__resample_horizontal_downsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n)); + + // Now it's sitting in the ring buffer ready to be used as source for the vertical sampling. +} + +static void stbir__decode_and_resample_downsample(ostbir__info* stbir_info, int n) +{ + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline(stbir_info, n); + + memset(stbir_info->horizontal_buffer, 0, stbir_info->output_w * stbir_info->channels * sizeof(float)); + + // Now resample it into the horizontal buffer. + if (stbir__use_width_upsampling(stbir_info)) + stbir__resample_horizontal_upsample(stbir_info, stbir_info->horizontal_buffer); + else + stbir__resample_horizontal_downsample(stbir_info, stbir_info->horizontal_buffer); + + // Now it's sitting in the horizontal buffer ready to be distributed into the ring buffers. +} + +// Get the specified scan line from the ring buffer. +static float* stbir__get_ring_buffer_scanline(int get_scanline, float* ring_buffer, int begin_index, int first_scanline, int ring_buffer_num_entries, int ring_buffer_length) +{ + int ring_buffer_index = (begin_index + (get_scanline - first_scanline)) % ring_buffer_num_entries; + return stbir__get_ring_buffer_entry(ring_buffer, ring_buffer_index, ring_buffer_length); +} + + +static void stbir__encode_scanline(ostbir__info* stbir_info, int num_pixels, void *output_buffer, float *encode_buffer, int channels, int alpha_channel, int decode) +{ + int x; + int n; + int num_nonalpha; + stbir_uint16 nonalpha[STBIR_MAX_CHANNELS]; + + if ((!(stbir_info->flags&STBIR_FLAG_ALPHA_OUT_PREMULTIPLIED))&&(alpha_channel!=-1)) + { + STBIR_PROFILE_START( ); + + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + float alpha = encode_buffer[pixel_index + alpha_channel]; + float reciprocal_alpha = alpha ? 1.0f / alpha : 0; + + // unrolling this produced a 1% slowdown upscaling a large RGBA linear-space image on my machine - stb + for (n = 0; n < channels; n++) + if (n != alpha_channel) + encode_buffer[pixel_index + n] *= reciprocal_alpha; + + // We added in a small epsilon to prevent the color channel from being deleted with zero alpha. + // Because we only add it for integer types, it will automatically be discarded on integer + // conversion, so we don't need to subtract it back out (which would be problematic for + // numeric precision reasons). + } + STBIR_PROFILE_END( unalpha ); + } + + // build a table of all channels that need colorspace correction, so + // we don't perform colorspace correction on channels that don't need it. + for (x = 0, num_nonalpha = 0; x < channels; ++x) + { + if (x != alpha_channel || (stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE)) + { + nonalpha[num_nonalpha++] = (stbir_uint16)x; + } + } + + #define STBIR__ROUND_INT(f) ((int) ((f)+0.5)) + #define STBIR__ROUND_UINT(f) ((stbir_uint32) ((f)+0.5)) + + #ifdef STBIR__SATURATE_INT + #define STBIR__ENCODE_LINEAR8(f) stbir__saturate8 (STBIR__ROUND_INT((f) * stbir__max_uint8_as_float )) + #define STBIR__ENCODE_LINEAR16(f) stbir__saturate16(STBIR__ROUND_INT((f) * stbir__max_uint16_as_float)) + #else + #define STBIR__ENCODE_LINEAR8(f) (unsigned char ) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint8_as_float ) + #define STBIR__ENCODE_LINEAR16(f) (unsigned short) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint16_as_float) + #endif + + STBIR_PROFILE_START( ); + + switch (decode) + { + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned char*)output_buffer)[index] = STBIR__ENCODE_LINEAR8(encode_buffer[index]); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned char*)output_buffer)[index] = stbir__linear_to_srgb_uchar(encode_buffer[index]); + } + + if (!(stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned char *)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR8(encode_buffer[pixel_index+alpha_channel]); + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned short*)output_buffer)[index] = STBIR__ENCODE_LINEAR16(encode_buffer[index]); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned short*)output_buffer)[index] = (unsigned short)STBIR__ROUND_INT(stbir__linear_to_srgb(stbir__saturate(encode_buffer[index])) * stbir__max_uint16_as_float); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned short*)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR16(encode_buffer[pixel_index + alpha_channel]); + } + + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__saturate(encode_buffer[index])) * stbir__max_uint32_as_float); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__linear_to_srgb(stbir__saturate(encode_buffer[index]))) * stbir__max_uint32_as_float); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned int*)output_buffer)[pixel_index + alpha_channel] = (unsigned int)STBIR__ROUND_INT(((double)stbir__saturate(encode_buffer[pixel_index + alpha_channel])) * stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((float*)output_buffer)[index] = encode_buffer[index]; + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + float p = encode_buffer[index]; + if ( p <= 0 ) p = 0; if ( p >= 1.0 ) p = 1.0; + ((float*)output_buffer)[index] = stbir__linear_to_srgb(p); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + { + float p = encode_buffer[pixel_index + alpha_channel]; + if ( p <= 0 ) p = 0; if ( p >= 1.0 ) p = 1.0; + ((float*)output_buffer)[pixel_index + alpha_channel] = p; + } + } + break; + + default: + STBIR_ASSERT(!"Unknown type/colorspace/channels combination."); + break; + } + STBIR_PROFILE_END( encode ); +} + +static void stbir__resample_vertical_upsample(ostbir__info* stbir_info, int n) +{ + int x, k; + int output_w = stbir_info->output_w; + stbir__contributors* vertical_contributors = stbir_info->vertical_contributors; + float* vertical_coefficients = stbir_info->vertical_coefficients; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int ring_buffer_entries = stbir_info->ring_buffer_num_entries; + void* output_data = stbir_info->output_data; + float* encode_buffer = stbir_info->encode_buffer; + int decode = STBIR__DECODE(type, colorspace); + int coefficient_width = stbir_info->vertical_coefficient_width; + int coefficient_counter; + int contributor = n; + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index; + int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + + int n0,n1, output_row_start; + int coefficient_group = coefficient_width * contributor; + + n0 = vertical_contributors[contributor].n0; + n1 = vertical_contributors[contributor].n1; + + output_row_start = n * stbir_info->output_stride_bytes; + + STBIR_ASSERT(stbir__use_height_upsampling(stbir_info)); + + STBIR_PROFILE_START( ); + + memset(encode_buffer, 0, output_w * sizeof(float) * channels); + + // I tried reblocking this for better cache usage of encode_buffer + // (using x_outer, k, x_inner), but it lost speed. -- stb + + coefficient_counter = 0; + switch (channels) { + case 1: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 1; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + } + } + break; + case 2: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 2; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + } + } + break; + case 3: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 3; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient; + } + } + break; + case 4: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 4; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient; + encode_buffer[in_pixel_index + 3] += ring_buffer_entry[in_pixel_index + 3] * coefficient; + } + } + break; + default: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * channels; + int c; + for (c = 0; c < channels; c++) + encode_buffer[in_pixel_index + c] += ring_buffer_entry[in_pixel_index + c] * coefficient; + } + } + break; + } + STBIR_PROFILE_END( vertical ); + stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, encode_buffer, channels, alpha_channel, decode); +} + +static void stbir__resample_vertical_downsample(ostbir__info* stbir_info, int n) +{ + int x, k; + int output_w = stbir_info->output_w; + stbir__contributors* vertical_contributors = stbir_info->vertical_contributors; + float* vertical_coefficients = stbir_info->vertical_coefficients; + int channels = stbir_info->channels; + int ring_buffer_entries = stbir_info->ring_buffer_num_entries; + float* horizontal_buffer = stbir_info->horizontal_buffer; + int coefficient_width = stbir_info->vertical_coefficient_width; + int contributor = n + stbir_info->vertical_filter_pixel_margin; + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index; + int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + int n0,n1; + + n0 = vertical_contributors[contributor].n0; + n1 = vertical_contributors[contributor].n1; + + STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info)); + + STBIR_PROFILE_START( ); + for (k = n0; k <= n1; k++) + { + int coefficient_index = k - n0; + int coefficient_group = coefficient_width * contributor; + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + + switch (channels) { + case 1: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 1; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + } + break; + case 2: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 2; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + } + break; + case 3: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 3; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient; + } + break; + case 4: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 4; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient; + ring_buffer_entry[in_pixel_index + 3] += horizontal_buffer[in_pixel_index + 3] * coefficient; + } + break; + default: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * channels; + + int c; + for (c = 0; c < channels; c++) + ring_buffer_entry[in_pixel_index + c] += horizontal_buffer[in_pixel_index + c] * coefficient; + } + break; + } + } + STBIR_PROFILE_END( vertical ); +} + +static void stbir__buffer_loop_upsample(ostbir__info* stbir_info) +{ + int y; + float scale_ratio = stbir_info->vertical_scale; + float out_scanlines_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(1/scale_ratio) * scale_ratio; + + STBIR_ASSERT(stbir__use_height_upsampling(stbir_info)); + + for (y = 0; y < stbir_info->output_h; y++) + { + float in_center_of_out = 0; // Center of the current out scanline in the in scanline space + int in_first_scanline = 0, in_last_scanline = 0; + + stbir__calculate_sample_range_upsample(y, out_scanlines_radius, scale_ratio, stbir_info->vertical_shift, &in_first_scanline, &in_last_scanline, &in_center_of_out); + + STBIR_ASSERT(in_last_scanline - in_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if (stbir_info->ring_buffer_begin_index >= 0) + { + // Get rid of whatever we don't need anymore. + while (in_first_scanline > stbir_info->ring_buffer_first_scanline) + { + if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline) + { + // We just popped the last scanline off the ring buffer. + // Reset it to the empty state. + stbir_info->ring_buffer_begin_index = -1; + stbir_info->ring_buffer_first_scanline = 0; + stbir_info->ring_buffer_last_scanline = 0; + break; + } + else + { + stbir_info->ring_buffer_first_scanline++; + stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries; + } + } + } + + // Load in new ones. + if (stbir_info->ring_buffer_begin_index < 0) + stbir__decode_and_resample_upsample(stbir_info, in_first_scanline); + + while (in_last_scanline > stbir_info->ring_buffer_last_scanline) + stbir__decode_and_resample_upsample(stbir_info, stbir_info->ring_buffer_last_scanline + 1); + + // Now all buffers should be ready to write a row of vertical sampling. + stbir__resample_vertical_upsample(stbir_info, y); + + STBIR_PROGRESS_REPORT((float)y / stbir_info->output_h); + } +} + +static void stbir__empty_ring_buffer(ostbir__info* stbir_info, int first_necessary_scanline) +{ + int output_stride_bytes = stbir_info->output_stride_bytes; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int output_w = stbir_info->output_w; + void* output_data = stbir_info->output_data; + int decode = STBIR__DECODE(type, colorspace); + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + + if (stbir_info->ring_buffer_begin_index >= 0) + { + // Get rid of whatever we don't need anymore. + while (first_necessary_scanline > stbir_info->ring_buffer_first_scanline) + { + if (stbir_info->ring_buffer_first_scanline >= 0 && stbir_info->ring_buffer_first_scanline < stbir_info->output_h) + { + int output_row_start = stbir_info->ring_buffer_first_scanline * output_stride_bytes; + float* ring_buffer_entry = stbir__get_ring_buffer_entry(ring_buffer, stbir_info->ring_buffer_begin_index, ring_buffer_length); + stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, ring_buffer_entry, channels, alpha_channel, decode); + STBIR_PROGRESS_REPORT((float)stbir_info->ring_buffer_first_scanline / stbir_info->output_h); + } + + if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline) + { + // We just popped the last scanline off the ring buffer. + // Reset it to the empty state. + stbir_info->ring_buffer_begin_index = -1; + stbir_info->ring_buffer_first_scanline = 0; + stbir_info->ring_buffer_last_scanline = 0; + break; + } + else + { + stbir_info->ring_buffer_first_scanline++; + stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries; + } + } + } +} + +static void stbir__buffer_loop_downsample(ostbir__info* stbir_info) +{ + int y; + float scale_ratio = stbir_info->vertical_scale; + int output_h = stbir_info->output_h; + float in_pixels_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(scale_ratio) / scale_ratio; + int pixel_margin = stbir_info->vertical_filter_pixel_margin; + int max_y = stbir_info->input_h + pixel_margin; + + STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info)); + + for (y = -pixel_margin; y < max_y; y++) + { + float out_center_of_in; // Center of the current out scanline in the in scanline space + int out_first_scanline, out_last_scanline; + + stbir__calculate_sample_range_downsample(y, in_pixels_radius, scale_ratio, stbir_info->vertical_shift, &out_first_scanline, &out_last_scanline, &out_center_of_in); + + STBIR_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if (out_last_scanline < 0 || out_first_scanline >= output_h) + continue; + + stbir__empty_ring_buffer(stbir_info, out_first_scanline); + + stbir__decode_and_resample_downsample(stbir_info, y); + + // Load in new ones. + if (stbir_info->ring_buffer_begin_index < 0) + stbir__add_empty_ring_buffer_entry(stbir_info, out_first_scanline); + + while (out_last_scanline > stbir_info->ring_buffer_last_scanline) + stbir__add_empty_ring_buffer_entry(stbir_info, stbir_info->ring_buffer_last_scanline + 1); + + // Now the horizontal buffer is ready to write to all ring buffer rows. + stbir__resample_vertical_downsample(stbir_info, y); + } + + stbir__empty_ring_buffer(stbir_info, stbir_info->output_h); +} + +static void stbir__setup(ostbir__info *info, int input_w, int input_h, int output_w, int output_h, int channels) +{ + info->input_w = input_w; + info->input_h = input_h; + info->output_w = output_w; + info->output_h = output_h; + info->channels = channels; +} + +static void stbir__calculate_transform(ostbir__info *info, float s0, float t0, float s1, float t1, float *transform) +{ + info->s0 = s0; + info->t0 = t0; + info->s1 = s1; + info->t1 = t1; + + if (transform) + { + info->horizontal_scale = transform[0]; + info->vertical_scale = transform[1]; + info->horizontal_shift = transform[2]; + info->vertical_shift = transform[3]; + } + else + { + info->horizontal_scale = ((float)info->output_w / info->input_w) / (s1 - s0); + info->vertical_scale = ((float)info->output_h / info->input_h) / (t1 - t0); + + info->horizontal_shift = s0 * info->output_w / (s1 - s0); + info->vertical_shift = t0 * info->output_h / (t1 - t0); + } +} + +static void stbir__choose_filter(ostbir__info *info, stbir_filter h_filter, stbir_filter v_filter) +{ + if (h_filter == 0) + h_filter = stbir__use_upsampling(info->horizontal_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE; + if (v_filter == 0) + v_filter = stbir__use_upsampling(info->vertical_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE; + info->horizontal_filter = h_filter; + info->vertical_filter = v_filter; +} + +static stbir_uint32 stbir__calculate_memory(ostbir__info *info) +{ + int pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale); + int filter_height = stbir__get_filter_pixel_width(info->vertical_filter, info->vertical_scale); + + info->horizontal_num_contributors = stbir__get_contributors(info->horizontal_scale, info->horizontal_filter, info->input_w, info->output_w); + info->vertical_num_contributors = stbir__get_contributors(info->vertical_scale , info->vertical_filter , info->input_h, info->output_h); + + // One extra entry because floating point precision problems sometimes cause an extra to be necessary. + info->ring_buffer_num_entries = filter_height + 1; + + info->horizontal_contributors_size = info->horizontal_num_contributors * sizeof(stbir__contributors); + info->horizontal_coefficients_size = stbir__get_total_horizontal_coefficients(info) * sizeof(float); + info->vertical_contributors_size = info->vertical_num_contributors * sizeof(stbir__contributors); + info->vertical_coefficients_size = stbir__get_total_vertical_coefficients(info) * sizeof(float); + info->decode_buffer_size = (info->input_w + pixel_margin * 2) * info->channels * sizeof(float); + info->horizontal_buffer_size = info->output_w * info->channels * sizeof(float); + info->ring_buffer_size = info->output_w * info->channels * info->ring_buffer_num_entries * sizeof(float); + info->encode_buffer_size = info->output_w * info->channels * sizeof(float); + + STBIR_ASSERT(info->horizontal_filter != 0); + STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late + STBIR_ASSERT(info->vertical_filter != 0); + STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late + + if (stbir__use_height_upsampling(info)) + // The horizontal buffer is for when we're downsampling the height and we + // can't output the result of sampling the decode buffer directly into the + // ring buffers. + info->horizontal_buffer_size = 0; + else + // The encode buffer is to retain precision in the height upsampling method + // and isn't used when height downsampling. + info->encode_buffer_size = 0; + + return info->horizontal_contributors_size + info->horizontal_coefficients_size + + info->vertical_contributors_size + info->vertical_coefficients_size + + info->decode_buffer_size + info->horizontal_buffer_size + + info->ring_buffer_size + info->encode_buffer_size; +} + +static int stbir__resize_allocated(ostbir__info *info, + const void* input_data, int input_stride_in_bytes, + void* output_data, int output_stride_in_bytes, + int alpha_channel, stbir_uint32 flags, stbir_datatype type, + stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace, + void* tempmem, size_t tempmem_size_in_bytes) +{ + size_t memory_required = stbir__calculate_memory(info); + + int width_stride_input = input_stride_in_bytes ? input_stride_in_bytes : info->channels * info->input_w * stbir__type_size[type]; + int width_stride_output = output_stride_in_bytes ? output_stride_in_bytes : info->channels * info->output_w * stbir__type_size[type]; + +#ifdef STBIR_DEBUG_OVERWRITE_TEST +#define OVERWRITE_ARRAY_SIZE 8 + unsigned char overwrite_output_before_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_tempmem_before_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_output_after_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_tempmem_after_pre[OVERWRITE_ARRAY_SIZE]; + + size_t begin_forbidden = width_stride_output * (info->output_h - 1) + info->output_w * info->channels * stbir__type_size[type]; + memcpy(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE); +#endif + + STBIR_ASSERT(info->channels >= 0); + STBIR_ASSERT(info->channels <= STBIR_MAX_CHANNELS); + + if (info->channels < 0 || info->channels > STBIR_MAX_CHANNELS) + return 0; + + STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + + if (info->horizontal_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table)) + return 0; + if (info->vertical_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table)) + return 0; + + if (alpha_channel < 0) + flags |= STBIR_FLAG_ALPHA_USES_COLORSPACE | STBIR_FLAG_ALPHA_PREMULTIPLIED; + + if (!(flags&STBIR_FLAG_ALPHA_USES_COLORSPACE) || !(flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) { + STBIR_ASSERT(alpha_channel >= 0 && alpha_channel < info->channels); + } + + if (alpha_channel >= info->channels) + return 0; + + STBIR_ASSERT(tempmem); + + if (!tempmem) + return 0; + + STBIR_ASSERT(tempmem_size_in_bytes >= memory_required); + + if (tempmem_size_in_bytes < memory_required) + return 0; + + memset(tempmem, 0, tempmem_size_in_bytes); + + info->input_data = input_data; + info->input_stride_bytes = width_stride_input; + + info->output_data = output_data; + info->output_stride_bytes = width_stride_output; + + info->alpha_channel = alpha_channel; + info->flags = flags; + info->type = type; + info->edge_horizontal = edge_horizontal; + info->edge_vertical = edge_vertical; + info->colorspace = colorspace; + + STBIR_PROFILE_START(); + + info->horizontal_coefficient_width = stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale); + info->vertical_coefficient_width = stbir__get_coefficient_width (info->vertical_filter , info->vertical_scale ); + info->horizontal_filter_pixel_width = stbir__get_filter_pixel_width (info->horizontal_filter, info->horizontal_scale); + info->vertical_filter_pixel_width = stbir__get_filter_pixel_width (info->vertical_filter , info->vertical_scale ); + info->horizontal_filter_pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale); + info->vertical_filter_pixel_margin = stbir__get_filter_pixel_margin(info->vertical_filter , info->vertical_scale ); + + info->ring_buffer_length_bytes = info->output_w * info->channels * sizeof(float); + info->decode_buffer_pixels = info->input_w + info->horizontal_filter_pixel_margin * 2; + +#define STBIR__NEXT_MEMPTR(current, newtype) (newtype*)(((unsigned char*)current) + current##_size) + + info->horizontal_contributors = (stbir__contributors *) tempmem; + info->horizontal_coefficients = STBIR__NEXT_MEMPTR(info->horizontal_contributors, float); + info->vertical_contributors = STBIR__NEXT_MEMPTR(info->horizontal_coefficients, stbir__contributors); + info->vertical_coefficients = STBIR__NEXT_MEMPTR(info->vertical_contributors, float); + info->decode_buffer = STBIR__NEXT_MEMPTR(info->vertical_coefficients, float); + + if (stbir__use_height_upsampling(info)) + { + info->horizontal_buffer = NULL; + info->ring_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float); + info->encode_buffer = STBIR__NEXT_MEMPTR(info->ring_buffer, float); + + STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->encode_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes); + } + else + { + info->horizontal_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float); + info->ring_buffer = STBIR__NEXT_MEMPTR(info->horizontal_buffer, float); + info->encode_buffer = NULL; + + STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->ring_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes); + } + +#undef STBIR__NEXT_MEMPTR + + // This signals that the ring buffer is empty + info->ring_buffer_begin_index = -1; + + stbir__calculate_filters(info->horizontal_contributors, info->horizontal_coefficients, info->horizontal_filter, info->horizontal_scale, info->horizontal_shift, info->input_w, info->output_w); + stbir__calculate_filters(info->vertical_contributors, info->vertical_coefficients, info->vertical_filter, info->vertical_scale, info->vertical_shift, info->input_h, info->output_h); + STBIR_PROFILE_END( filters ); + + STBIR_PROGRESS_REPORT(0); + + STBIR_PROFILE_START(); + if (stbir__use_height_upsampling(info)) + { + stbir__buffer_loop_upsample(info); + } + else + { + stbir__buffer_loop_downsample(info); + } + STBIR_PROFILE_END( looping ); + + + STBIR_PROGRESS_REPORT(1); + +#ifdef STBIR_DEBUG_OVERWRITE_TEST + STBIR_ASSERT(memcmp(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE) == 0); +#endif + + return 1; +} + + +static int stbir__resize_arbitrary( + void *alloc_context, + const void* input_data, int input_w, int input_h, int input_stride_in_bytes, + void* output_data, int output_w, int output_h, int output_stride_in_bytes, + float s0, float t0, float s1, float t1, float *transform, + int channels, int alpha_channel, stbir_uint32 flags, stbir_datatype type, + stbir_filter h_filter, stbir_filter v_filter, + stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace) +{ + ostbir__info info; + int result; + size_t memory_required; + void* extra_memory; + + STBIR_PROFILE_FIRST_START(); + + stbir__setup(&info, input_w, input_h, output_w, output_h, channels); + stbir__calculate_transform(&info, s0,t0,s1,t1,transform); + stbir__choose_filter(&info, h_filter, v_filter); + memory_required = stbir__calculate_memory(&info); + extra_memory = STBIR_MALLOC(memory_required, alloc_context); + + if (!extra_memory) + { + return 0; + } + + result = stbir__resize_allocated(&info, input_data, input_stride_in_bytes, + output_data, output_stride_in_bytes, + alpha_channel, flags, type, + edge_horizontal, edge_vertical, + colorspace, extra_memory, memory_required); + + STBIR_PROFILE_END( setup); + + STBIR_FREE(extra_memory, alloc_context); + + return result; +} + +STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR); +} + +STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_FLOAT, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR); +} + +STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB); +} + +STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + edge_wrap_mode, edge_wrap_mode, STBIR_COLORSPACE_SRGB); +} + +STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + +STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT16, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + + +STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_FLOAT, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + + +STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + + +STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float x_scale, float y_scale, + float x_offset, float y_offset) +{ + float transform[4]; + transform[0] = x_scale; + transform[1] = y_scale; + transform[2] = x_offset; + transform[3] = y_offset; + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,transform,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float s0, float t0, float s1, float t1) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + s0,t0,s1,t1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +#endif // STB_IMAGE_RESIZE_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_image_resize_test/oldir.c b/source/engine/thirdparty/stb/stb_image_resize_test/oldir.c new file mode 100644 index 0000000..e1f3505 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_image_resize_test/oldir.c @@ -0,0 +1,56 @@ +#include +#include + +#ifdef _MSC_VER +#define stop() __debugbreak() +#else +#define stop() __builtin_trap() +#endif + +//#define HEAVYTM +#include "tm.h" + +#define STBIR_SATURATE_INT +#define STB_IMAGE_RESIZE_STATIC +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include "old_image_resize.h" + + +static int types[4] = { STBIR_TYPE_UINT8, STBIR_TYPE_UINT8, STBIR_TYPE_UINT16, STBIR_TYPE_FLOAT }; +static int edges[4] = { STBIR_EDGE_CLAMP, STBIR_EDGE_REFLECT, STBIR_EDGE_ZERO, STBIR_EDGE_WRAP }; +static int flts[5] = { STBIR_FILTER_BOX, STBIR_FILTER_TRIANGLE, STBIR_FILTER_CUBICBSPLINE, STBIR_FILTER_CATMULLROM, STBIR_FILTER_MITCHELL }; +static int channels[20] = { 1, 2, 3, 4, 4,4, 2,2, 4,4, 2,2, 4,4, 2,2, 4,4, 2,2 }; +static int alphapos[20] = { -1, -1, -1, -1, 3,0, 1,0, 3,0, 1,0, 3,0, 1,0, 3,0, 1,0 }; + + +void oresize( void * o, int ox, int oy, int op, void * i, int ix, int iy, int ip, int buf, int type, int edg, int flt ) +{ + int t = types[type]; + int ic = channels[buf]; + int alpha = alphapos[buf]; + int e = edges[edg]; + int f = flts[flt]; + int space = ( type == 1 ) ? STBIR_COLORSPACE_SRGB : 0; + int flags = ( buf >= 16 ) ? STBIR_FLAG_ALPHA_PREMULTIPLIED : ( ( buf >= 12 ) ? STBIR_FLAG_ALPHA_OUT_PREMULTIPLIED : ( ( buf >= 8 ) ? (STBIR_FLAG_ALPHA_PREMULTIPLIED|STBIR_FLAG_ALPHA_OUT_PREMULTIPLIED) : 0 ) ); + stbir_uint64 start; + + ENTER( "Resize (old)" ); + start = tmGetAccumulationStart( tm_mask ); + + if(!stbir_resize( i, ix, iy, ip, o, ox, oy, op, t, ic, alpha, flags, e, e, f, f, space, 0 ) ) + stop(); + + #ifdef STBIR_PROFILE + tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.setup, "Setup (old)" ); + tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.filters, "Filters (old)" ); + tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.looping, "Looping (old)" ); + tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.vertical, "Vertical (old)" ); + tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.horizontal, "Horizontal (old)" ); + tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.decode, "Scanline input (old)" ); + tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.encode, "Scanline output (old)" ); + tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.alpha, "Alpha weighting (old)" ); + tmEmitAccumulationZone( 0, 0, (tm_uint64 *)&start, 0, oldprofile.named.unalpha, "Alpha unweighting (old)" ); + #endif + + LEAVE(); +} diff --git a/source/engine/thirdparty/stb/stb_image_resize_test/stbirtest.c b/source/engine/thirdparty/stb/stb_image_resize_test/stbirtest.c new file mode 100644 index 0000000..22e1b82 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_image_resize_test/stbirtest.c @@ -0,0 +1,992 @@ +#include +#include +#include + +//#define HEAVYTM +#include "tm.h" + +#ifdef RADUSETM3 +tm_api * g_tm_api; +//#define PROFILE_MODE +#endif + +#include + +#ifdef _MSC_VER +#define stop() __debugbreak() +#include +#define int64 __int64 +#define uint64 unsigned __int64 +#else +#define stop() __builtin_trap() +#define int64 long long +#define uint64 unsigned long long +#endif + +#ifdef _MSC_VER +#pragma warning(disable:4127) +#endif + +//#define NOCOMP + + +//#define PROFILE_NEW_ONLY +//#define PROFILE_MODE + + +#if defined(_x86_64) || defined( __x86_64__ ) || defined( _M_X64 ) || defined(__x86_64) || defined(__SSE2__) || defined(STBIR_SSE) || defined( _M_IX86_FP ) || defined(__i386) || defined( __i386__ ) || defined( _M_IX86 ) || defined( _X86_ ) + +#ifdef _MSC_VER + + uint64 __rdtsc(); + #define __cycles() __rdtsc() + +#else // non msvc + + static inline uint64 __cycles() + { + unsigned int lo, hi; + asm volatile ("rdtsc" : "=a" (lo), "=d" (hi) ); + return ( ( (uint64) hi ) << 32 ) | ( (uint64) lo ); + } + +#endif // msvc + +#elif defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || defined(__ARM_NEON__) + +#ifdef _MSC_VER + + #define __cycles() _ReadStatusReg(ARM64_CNTVCT) + +#else + + static inline uint64 __cycles() + { + uint64 tsc; + asm volatile("mrs %0, cntvct_el0" : "=r" (tsc)); + return tsc; + } + +#endif + +#else // x64, arm + +#error Unknown platform for timing. + +#endif //x64 and + + +#ifdef PROFILE_MODE + +#define STBIR_ASSERT(cond) + +#endif + +#ifdef _DEBUG +#undef STBIR_ASSERT +#define STBIR_ASSERT(cond) { if (!(cond)) stop(); } +#endif + + +#define SHRINKBYW 2 +#define ZOOMBYW 2 +#define SHRINKBYH 2 +#define ZOOMBYH 2 + + +int mem_count = 0; + +#ifdef TEST_WITH_VALLOC + +#define STBIR__SEPARATE_ALLOCATIONS + +#if TEST_WITH_LIMIT_AT_FRONT + + void * wmalloc(SIZE_T size) + { + static unsigned int pagesize=0; + void* p; + SIZE_T s; + + // get the page size, if we haven't yet + if (pagesize==0) + { + SYSTEM_INFO si; + GetSystemInfo(&si); + pagesize=si.dwPageSize; + } + + // we need room for the size, 8 bytes to hide the original pointer and a + // validation dword, and enough data to completely fill one page + s=(size+(pagesize-1))&~(pagesize-1); + + // allocate the size plus a page (for the guard) + p=VirtualAlloc(0,(SIZE_T)s,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE); + + return p; + } + + void wfree(void * ptr) + { + if (ptr) + { + if ( ((ptrdiff_t)ptr) & 4095 ) stop(); + if ( VirtualFree(ptr,0,MEM_RELEASE) == 0 ) stop(); + } + } + +#else + + void * wmalloc(SIZE_T size) + { + static unsigned int pagesize=0; + void* p; + SIZE_T s; + + // get the page size, if we haven't yet + if (pagesize==0) + { + SYSTEM_INFO si; + GetSystemInfo(&si); + pagesize=si.dwPageSize; + } + + // we need room for the size, 8 bytes to hide the original pointer and a + // validation dword, and enough data to completely fill one page + s=(size+16+(pagesize-1))&~(pagesize-1); + + // allocate the size plus a page (for the guard) + p=VirtualAlloc(0,(SIZE_T)(s+pagesize+pagesize),MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE); + + if (p) + { + DWORD oldprot; + void* orig=p; + + // protect the first page + VirtualProtect(((char*)p),pagesize,PAGE_NOACCESS,&oldprot); + + // protect the final page + VirtualProtect(((char*)p)+s+pagesize,pagesize,PAGE_NOACCESS,&oldprot); + + // now move the returned pointer so that it bumps right up against the + // the next (protected) page (this may result in unaligned return + // addresses - pre-align the sizes if you always want aligned ptrs) +//#define ERROR_ON_FRONT +#ifdef ERROR_ON_FRONT + p=((char*)p)+pagesize+16; +#else + p=((char*)p)+(s-size)+pagesize; +#endif + + // hide the validation value and the original pointer (which we'll + // need used for freeing) right behind the returned pointer + ((unsigned int*)p)[-1]=0x98765432; + ((void**)p)[-2]=orig; + ++mem_count; +//printf("aloc: %p bytes: %d\n",p,(int)size); + return(p); + } + + return 0; + } + + void wfree(void * ptr) + { + if (ptr) + { + int err=0; + + // is this one of our allocations? + if (((((unsigned int*)ptr)[-1])!=0x98765432) || ((((void**)ptr)[-2])==0)) + { + err=1; + } + + if (err) + { + __debugbreak(); + } + else + { + + // back up to find the original pointer + void* p=((void**)ptr)[-2]; + + // clear the validation value and the original pointer + ((unsigned int*)ptr)[-1]=0; + ((void**)ptr)[-2]=0; + +//printf("free: %p\n",ptr); + + --mem_count; + + // now free the pages + if (p) + VirtualFree(p,0,MEM_RELEASE); + + } + } + } + +#endif + +#define STBIR_MALLOC(size,user_data) ((void)(user_data), wmalloc(size)) +#define STBIR_FREE(ptr,user_data) ((void)(user_data), wfree(ptr)) + +#endif + +#define STBIR_PROFILE +//#define STBIR_NO_SIMD +//#define STBIR_AVX +//#define STBIR_AVX2 +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include "stb_image_resize2.h" // new one! + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +int tsizes[5] = { 1, 1, 2, 4, 2 }; +int ttypes[5] = { STBIR_TYPE_UINT8, STBIR_TYPE_UINT8_SRGB, STBIR_TYPE_UINT16, STBIR_TYPE_FLOAT, STBIR_TYPE_HALF_FLOAT }; + +int cedges[4] = { STBIR_EDGE_CLAMP, STBIR_EDGE_REFLECT, STBIR_EDGE_ZERO, STBIR_EDGE_WRAP }; +int flts[5] = { STBIR_FILTER_BOX, STBIR_FILTER_TRIANGLE, STBIR_FILTER_CUBICBSPLINE, STBIR_FILTER_CATMULLROM, STBIR_FILTER_MITCHELL }; +int buffers[20] = { STBIR_1CHANNEL, STBIR_2CHANNEL, STBIR_RGB, STBIR_4CHANNEL, + STBIR_BGRA, STBIR_ARGB, STBIR_RA, STBIR_AR, + STBIR_RGBA_PM, STBIR_ARGB_PM, STBIR_RA_PM, STBIR_AR_PM, + STBIR_RGBA, STBIR_ARGB, STBIR_RA, STBIR_AR, + STBIR_RGBA_PM, STBIR_ARGB_PM, STBIR_RA_PM, STBIR_AR_PM, + }; +int obuffers[20] = { STBIR_1CHANNEL, STBIR_2CHANNEL, STBIR_RGB, STBIR_4CHANNEL, + STBIR_BGRA, STBIR_ARGB, STBIR_RA, STBIR_AR, + STBIR_RGBA_PM, STBIR_ARGB_PM, STBIR_RA_PM, STBIR_AR_PM, + STBIR_RGBA_PM, STBIR_ARGB_PM, STBIR_RA_PM, STBIR_AR_PM, + STBIR_RGBA, STBIR_ARGB, STBIR_RA, STBIR_AR, + }; + +int bchannels[20] = { 1, 2, 3, 4, 4,4, 2,2, 4,4, 2,2, 4,4, 2,2, 4,4, 2,2 }; +int alphapos[20] = { -1, -1, -1, -1, 3,0, 1,0, 3,0, 1,0, 3,0, 1,0,3,0, 1,0 }; + + +char const * buffstrs[20] = { "1ch", "2ch", "3ch", "4ch", "RGBA", "ARGB", "RA", "AR", "RGBA_both_pre", "ARGB_both_pre", "RA_both_pre", "AR_both_pre", "RGBA_out_pre", "ARGB_out_pre", "RA_out_pre", "AR_out_pre", "RGBA_in_pre", "ARGB_in_pre", "RA_in_pre", "AR_in_pre" }; +char const * typestrs[5] = { "Bytes", "BytesSRGB", "Shorts", "Floats", "Half Floats"}; +char const * edgestrs[4] = { "Clamp", "Reflect", "Zero", "Wrap" }; +char const * fltstrs[5] = { "Box", "Triangle", "Cubic", "Catmullrom", "Mitchell" }; + +#ifdef STBIR_PROFILE + static void do_acc_zones( STBIR_PROFILE_INFO * profile ) + { + stbir_uint32 j; + stbir_uint64 start = tmGetAccumulationStart( tm_mask ); start=start; + + for( j = 0 ; j < profile->count ; j++ ) + { + if ( profile->clocks[j] ) + tmEmitAccumulationZone( 0, 0, (tm_uint64*)&start, 0, profile->clocks[j], profile->descriptions[j] ); + } + } +#else + #define do_acc_zones(...) +#endif + +int64 vert; + +//#define WINTHREADTEST +#ifdef WINTHREADTEST + +static STBIR_RESIZE * thread_resize; +static LONG which; +static int threads_started = 0; +static HANDLE threads[32]; +static HANDLE starts,stops; + +static DWORD resize_shim( LPVOID p ) +{ + for(;;) + { + LONG wh; + + WaitForSingleObject( starts, INFINITE ); + + wh = InterlockedAdd( &which, 1 ) - 1; + + ENTER( "Split %d", wh ); + stbir_resize_split( thread_resize, wh, 1 ); + #ifdef STBIR_PROFILE + { STBIR_PROFILE_INFO profile; stbir_resize_split_profile_info( &profile, thread_resize, wh, 1 ); do_acc_zones( &profile ); vert = profile.clocks[1]; } + #endif + LEAVE(); + + ReleaseSemaphore( stops, 1, 0 ); + } +} + +#endif + +void nresize( void * o, int ox, int oy, int op, void * i, int ix, int iy, int ip, int buf, int type, int edg, int flt ) +{ + STBIR_RESIZE resize; + + stbir_resize_init( &resize, i, ix, iy, ip, o, ox, oy, op, buffers[buf], ttypes[type] ); + stbir_set_pixel_layouts( &resize, buffers[buf], obuffers[buf] ); + stbir_set_edgemodes( &resize, cedges[edg], cedges[edg] ); + stbir_set_filters( &resize, flts[flt], /*STBIR_FILTER_POINT_SAMPLE */ flts[flt] ); + //stbir_set_input_subrect( &resize, 0.55f,0.333f,0.75f,0.50f); + //stbir_set_output_pixel_subrect( &resize, 00, 00, ox/2,oy/2); + //stbir_set_pixel_subrect(&resize, 1430,1361,30,30); + + ENTER( "Resize" ); + + #ifndef WINTHREADTEST + + ENTER( "Filters" ); + stbir_build_samplers_with_splits( &resize, 1 ); + #ifdef STBIR_PROFILE + { STBIR_PROFILE_INFO profile; stbir_resize_build_profile_info( &profile, &resize ); do_acc_zones( &profile ); } + #endif + LEAVE(); + + ENTER( "Resize" ); + if(!stbir_resize_extended( &resize ) ) + stop(); + #ifdef STBIR_PROFILE + { STBIR_PROFILE_INFO profile; stbir_resize_extended_profile_info( &profile, &resize ); do_acc_zones( &profile ); vert = profile.clocks[1]; } + #endif + LEAVE(); + + #else + { + int c, cnt; + + ENTER( "Filters" ); + cnt = stbir_build_samplers_with_splits( &resize, 4 ); + #ifdef STBIR_PROFILE + { STBIR_PROFILE_INFO profile; stbir_resize_build_profile_info( &profile, &resize ); do_acc_zones( &profile ); } + #endif + LEAVE(); + + ENTER( "Thread start" ); + if ( threads_started == 0 ) + { + starts = CreateSemaphore( 0, 0, 32, 0 ); + stops = CreateSemaphore( 0, 0, 32, 0 ); + } + for( c = threads_started ; c < cnt ; c++ ) + threads[ c ] = CreateThread( 0, 2048*1024, resize_shim, 0, 0, 0 ); + + threads_started = cnt; + thread_resize = &resize; + which = 0; + LEAVE(); + + // starts the threads + ReleaseSemaphore( starts, cnt, 0 ); + + ENTER( "Wait" ); + for( c = 0 ; c < cnt; c++ ) + WaitForSingleObject( stops, INFINITE ); + LEAVE(); + } + #endif + + ENTER( "Free" ); + stbir_free_samplers( &resize ); + LEAVE(); + LEAVE(); +} + + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +extern void oresize( void * o, int ox, int oy, int op, void * i, int ix, int iy, int ip, int buf, int type, int edg, int flt ); + + + +#define TYPESTART 0 +#define TYPEEND 4 + +#define LAYOUTSTART 0 +#define LAYOUTEND 19 + +#define SIZEWSTART 0 +#define SIZEWEND 2 + +#define SIZEHSTART 0 +#define SIZEHEND 2 + +#define EDGESTART 0 +#define EDGEEND 3 + +#define FILTERSTART 0 +#define FILTEREND 4 + +#define HEIGHTSTART 0 +#define HEIGHTEND 2 + +#define WIDTHSTART 0 +#define WIDTHEND 2 + + + + +static void * convert8to16( unsigned char * i, int w, int h, int c ) +{ + unsigned short * ret; + int p; + + ret = malloc( w*h*c*sizeof(short) ); + for(p = 0 ; p < (w*h*c) ; p++ ) + { + ret[p]=(short)((((int)i[p])<<8)+i[p]); + } + + return ret; +} + +static void * convert8tof( unsigned char * i, int w, int h, int c ) +{ + float * ret; + int p; + + ret = malloc( w*h*c*sizeof(float) ); + for(p = 0 ; p < (w*h*c) ; p++ ) + { + ret[p]=((float)i[p])*(1.0f/255.0f); + } + + return ret; +} + +static void * convert8tohf( unsigned char * i, int w, int h, int c ) +{ + stbir__FP16 * ret; + int p; + + ret = malloc( w*h*c*sizeof(stbir__FP16) ); + for(p = 0 ; p < (w*h*c) ; p++ ) + { + ret[p]=stbir__float_to_half(((float)i[p])*(1.0f/255.0f)); + } + + return ret; +} + +static void * convert8tohff( unsigned char * i, int w, int h, int c ) +{ + float * ret; + int p; + + ret = malloc( w*h*c*sizeof(float) ); + for(p = 0 ; p < (w*h*c) ; p++ ) + { + ret[p]=stbir__half_to_float(stbir__float_to_half(((float)i[p])*(1.0f/255.0f))); + } + + return ret; +} + +static int isprime( int v ) +{ + int i; + + if ( v <= 3 ) + return ( v > 1 ); + if ( ( v & 1 ) == 0 ) + return 0; + if ( ( v % 3 ) == 0 ) + return 0; + i = 5; + while ( (i*i) <= v ) + { + if ( ( v % i ) == 0 ) + return 0; + if ( ( v % ( i + 2 ) ) == 0 ) + return 0; + i += 6; + } + + return 1; +} + +static int getprime( int v ) +{ + int i; + i = 0; + for(;;) + { + if ( i >= v ) + return v; // can't find any, just return orig + if (isprime(v - i)) + return v - i; + if (isprime(v + i)) + return v + i; + ++i; + } +} + + +int main( int argc, char ** argv ) +{ + int ix, iy, ic; + unsigned char * input[6]; + char * ir1; + char * ir2; + int szhs[3]; + int szws[3]; + int aw, ah, ac; + unsigned char * correctalpha; + int layouts, types, heights, widths, edges, filters; + + if ( argc != 2 ) + { + printf("command: stbirtest [imagefile]\n"); + exit(1); + } + + SetupTM( "127.0.0.1" ); + + correctalpha = stbi_load( "correctalpha.png", &aw, &ah, &ac, 0 ); + + input[0] = stbi_load( argv[1], &ix, &iy, &ic, 0 ); + input[1] = input[0]; + input[2] = convert8to16( input[0], ix, iy, ic ); + input[3] = convert8tof( input[0], ix, iy, ic ); + input[4] = convert8tohf( input[0], ix, iy, ic ); + input[5] = convert8tohff( input[0], ix, iy, ic ); + + printf("Input %dx%d (%d channels)\n",ix,iy,ic); + + ir1 = malloc( 4 * 4 * 3000 * 3000ULL ); + ir2 = malloc( 4 * 4 * 3000 * 3000ULL ); + + szhs[0] = getprime( iy/SHRINKBYH ); + szhs[1] = iy; + szhs[2] = getprime( iy*ZOOMBYH ); + + szws[0] = getprime( ix/SHRINKBYW ); + szws[1] = ix; + szws[2] = getprime( ix*ZOOMBYW ); + + #if 1 + for( types = TYPESTART ; types <= TYPEEND ; types++ ) + #else + for( types = 1 ; types <= 1 ; types++ ) + #endif + { + ENTER( "Test type: %s",typestrs[types]); + #if 1 + for( layouts = LAYOUTSTART ; layouts <= LAYOUTEND ; layouts++ ) + #else + for( layouts = 16; layouts <= 16 ; layouts++ ) + #endif + { + ENTER( "Test layout: %s",buffstrs[layouts]); + + #if 0 + for( heights = HEIGHTSTART ; heights <= HEIGHTEND ; heights++ ) + { + int w, h = szhs[heights]; + #else + for( heights = 0 ; heights <= 11 ; heights++ ) + { + static int szhsz[12]={32, 200, 350, 400, 450, 509, 532, 624, 700, 824, 1023, 2053 }; + int w, h = szhsz[heights]; + #endif + + ENTER( "Test height: %d %s %d",iy,(hiy)?"Up":"Same"),h); + + #if 0 + for( widths = WIDTHSTART ; widths <= WIDTHEND ; widths++ ) + { + w = szws[widths]; + #else + for( widths = 0 ; widths <= 12 ; widths++ ) + { + static int szwsz[13]={2, 32, 200, 350, 400, 450, 509, 532, 624, 700, 824, 1023, 2053 }; + w = szwsz[widths]; + #endif + + ENTER( "Test width: %d %s %d",ix, (wix)?"Up":"Same"), w); + + #if 0 + for( edges = EDGESTART ; edges <= EDGEEND ; edges++ ) + #else + for( edges = 0 ; edges <= 0 ; edges++ ) + #endif + { + ENTER( "Test edge: %s",edgestrs[edges]); + #if 0 + for( filters = FILTERSTART ; filters <= FILTEREND ; filters++ ) + #else + for( filters = 3 ; filters <= 3 ; filters++ ) + #endif + { + int op, opw, np,npw, c, a; + #ifdef COMPARE_SAME + int oldtypes = types; + #else + int oldtypes = (types==4)?3:types; + #endif + + ENTER( "Test filter: %s",fltstrs[filters]); + { + c = bchannels[layouts]; + a = alphapos[layouts]; + + op = w*tsizes[oldtypes]*c + 60; + opw = w*tsizes[oldtypes]*c; + + np = w*tsizes[types]*c + 60; + npw = w*tsizes[types]*c; + + printf( "%s:layout: %s w: %d h: %d edge: %s filt: %s\n", typestrs[types],buffstrs[layouts], w, h, edgestrs[edges], fltstrs[filters] ); + + + // clear pixel area to different, right edge to zero + #ifndef NOCLEAR + ENTER( "Test clear padding" ); + { + int d; + for( d = 0 ; d < h ; d++ ) + { + int oofs = d * op; + int nofs = d * np; + memset( ir1 + oofs, 192, opw ); + memset( ir1 + oofs+opw, 79, op-opw ); + memset( ir2 + nofs, 255, npw ); + memset( ir2 + nofs+npw, 79, np-npw ); + } + } + LEAVE(); + + #endif + + #ifdef COMPARE_SAME + #define TIMINGS 1 + #else + #define TIMINGS 1 + #endif + ENTER( "Test both" ); + { + #ifndef PROFILE_NEW_ONLY + { + int ttt, max = 0x7fffffff; + ENTER( "Test old" ); + for( ttt = 0 ; ttt < TIMINGS ; ttt++ ) + { + int64 m = __cycles(); + + oresize( ir1, w, h, op, + #ifdef COMPARE_SAME + input[types], + #else + input[(types==4)?5:types], + #endif + ix, iy, ix*ic*tsizes[oldtypes], layouts, oldtypes, edges, filters ); + + m = __cycles() - m; + if ( ( (int)m ) < max ) + max = (int) m; + } + LEAVE(); + printf("old: %d\n", max ); + } + #endif + + { + int ttt, max = 0x7fffffff, maxv = 0x7fffffff; + ENTER( "Test new" ); + for( ttt = 0 ; ttt < TIMINGS ; ttt++ ) + { + int64 m = __cycles(); + + nresize( ir2, w, h, np, input[types], ix, iy, ix*ic*tsizes[types], layouts, types, edges, filters ); + + m = __cycles() - m; + if ( ( (int)m ) < max ) + max = (int) m; + if ( ( (int)vert ) < maxv ) + maxv = (int) vert; + } + LEAVE(); // test new + printf("new: %d (v: %d)\n", max, maxv ); + } + } + LEAVE(); // test both + + if ( mem_count!= 0 ) + stop(); + + #ifndef NOCOMP + ENTER( "Test compare" ); + { + int x,y,ch; + int nums = 0; + for( y = 0 ; y < h ; y++ ) + { + for( x = 0 ; x < w ; x++ ) + { + switch(types) + { + case 0: + case 1: //SRGB + { + unsigned char * p1 = (unsigned char *)&ir1[y*op+x*c]; + unsigned char * p2 = (unsigned char *)&ir2[y*np+x*c]; + for( ch = 0 ; ch < c ; ch++ ) + { + float pp1,pp2,d; + float av = (a==-1)?1.0f:((float)p1[a]/255.0f); + + pp1 = p1[ch]; + pp2 = p2[ch]; + + // compare in premult space + #ifndef COMPARE_SAME + if ( ( ( layouts >=4 ) && ( layouts <= 7 ) ) || ( ( layouts >=16 ) && ( layouts <= 19 ) ) ) + { + pp1 *= av; + pp2 *= av; + } + #endif + + d = pp1 - pp2; + if ( d < 0 ) d = -d; + + #ifdef COMPARE_SAME + if ( d > 0 ) + #else + if ( d > 1 ) + #endif + { + printf("Error at %d x %d (chan %d) (d: %g a: %g) [%d %d %d %d] [%d %d %d %d]\n",x,y,ch, d,av, p1[0],p1[1],p1[2],p1[3], p2[0],p2[1],p2[2],p2[3]); + ++nums; + if ( nums > 16 ) goto ex; + //if (d) exit(1); + //goto ex; + } + } + } + break; + + case 2: + { + unsigned short * p1 = (unsigned short *)&ir1[y*op+x*c*sizeof(short)]; + unsigned short * p2 = (unsigned short *)&ir2[y*np+x*c*sizeof(short)]; + for( ch = 0 ; ch < c ; ch++ ) + { + float thres,pp1,pp2,d; + float av = (a==-1)?1.0f:((float)p1[a]/65535.0f); + + pp1 = p1[ch]; + pp2 = p2[ch]; + + // compare in premult space + #ifndef COMPARE_SAME + if ( ( ( layouts >=4 ) && ( layouts <= 7 ) ) || ( ( layouts >= 16 ) && ( layouts <= 19 ) ) ) + { + pp1 *= av; + pp2 *= av; + } + #endif + + d = pp1 - pp2; + if ( d < 0 ) d = -d; + + thres=((float)p1[ch]*0.007f)+2.0f; + if (thres<4) thres = 4; + + #ifdef COMPARE_SAME + if ( d > 0 ) + #else + if ( d > thres) + #endif + { + printf("Error at %d x %d (chan %d) %d %d [df: %g th: %g al: %g] (%d %d %d %d) (%d %d %d %d)\n",x,y,ch, p1[ch],p2[ch],d,thres,av,p1[0],p1[1],p1[2],p1[3],p2[0],p2[1],p2[2],p2[3]); + ++nums; + if ( nums > 16 ) goto ex; + //if (d) exit(1); + //goto ex; + } + } + } + break; + + case 3: + { + float * p1 = (float *)&ir1[y*op+x*c*sizeof(float)]; + float * p2 = (float *)&ir2[y*np+x*c*sizeof(float)]; + for( ch = 0 ; ch < c ; ch++ ) + { + float pp1 = p1[ch], pp2 = p2[ch]; + float av = (a==-1)?1.0f:p1[a]; + float thres, d; + + // clamp + if (pp1<=0.0f) pp1 = 0; + if (pp2<=0.0f) pp2 = 0; + if (av<=0.0f) av = 0; + if (pp1>1.0f) pp1 = 1.0f; + if (pp2>1.0f) pp2 = 1.0f; + if (av>1.0f) av = 1.0f; + + // compare in premult space + #ifndef COMPARE_SAME + if ( ( ( layouts >=4 ) && ( layouts <= 7 ) ) || ( ( layouts >= 16 ) && ( layouts <= 19 ) ) ) + { + pp1 *= av; + pp2 *= av; + } + #endif + + d = pp1 - pp2; + if ( d < 0 ) d = -d; + + thres=(p1[ch]*0.002f)+0.0002f; + if ( thres < 0 ) thres = -thres; + + #ifdef COMPARE_SAME + if ( d != 0.0f ) + #else + if ( d > thres ) + #endif + { + printf("Error at %d x %d (chan %d) %g %g [df: %g th: %g al: %g] (%g %g %g %g) (%g %g %g %g)\n",x,y,ch, p1[ch],p2[ch],d,thres,av,p1[0],p1[1],p1[2],p1[3],p2[0],p2[1],p2[2],p2[3]); + ++nums; + if ( nums > 16 ) goto ex; + //if (d) exit(1); + //goto ex; + } + } + } + break; + + case 4: + { + #ifdef COMPARE_SAME + stbir__FP16 * p1 = (stbir__FP16 *)&ir1[y*op+x*c*sizeof(stbir__FP16)]; + #else + float * p1 = (float *)&ir1[y*op+x*c*sizeof(float)]; + #endif + stbir__FP16 * p2 = (stbir__FP16 *)&ir2[y*np+x*c*sizeof(stbir__FP16)]; + for( ch = 0 ; ch < c ; ch++ ) + { + #ifdef COMPARE_SAME + float pp1 = stbir__half_to_float(p1[ch]); + float av = (a==-1)?1.0f:stbir__half_to_float(p1[a]); + #else + float pp1 = stbir__half_to_float(stbir__float_to_half(p1[ch])); + float av = (a==-1)?1.0f:stbir__half_to_float(stbir__float_to_half(p1[a])); + #endif + float pp2 = stbir__half_to_float(p2[ch]); + float d, thres; + + // clamp + if (pp1<=0.0f) pp1 = 0; + if (pp2<=0.0f) pp2 = 0; + if (av<=0.0f) av = 0; + if (pp1>1.0f) pp1 = 1.0f; + if (pp2>1.0f) pp2 = 1.0f; + if (av>1.0f) av = 1.0f; + + thres=(pp1*0.002f)+0.0002f; + + // compare in premult space + #ifndef COMPARE_SAME + if ( ( ( layouts >=4 ) && ( layouts <= 7 ) ) || ( ( layouts >= 16 ) && ( layouts <= 19 ) ) ) + { + pp1 *= av; + pp2 *= av; + } + #endif + + d = pp1 - pp2; + if ( d < 0 ) d = -d; + + + #ifdef COMPARE_SAME + if ( d != 0.0f ) + #else + if ( d > thres ) + #endif + { + printf("Error at %d x %d (chan %d) %g %g [df: %g th: %g al: %g] (%g %g %g %g) (%g %g %g %g)\n",x,y,ch, + #ifdef COMPARE_SAME + stbir__half_to_float(p1[ch]), + #else + p1[ch], + #endif + stbir__half_to_float(p2[ch]), + d,thres,av, + #ifdef COMPARE_SAME + stbir__half_to_float(p1[0]),stbir__half_to_float(p1[1]),stbir__half_to_float(p1[2]),stbir__half_to_float(p1[3]), + #else + p1[0],p1[1],p1[2],p1[3], + #endif + stbir__half_to_float(p2[0]),stbir__half_to_float(p2[1]),stbir__half_to_float(p2[2]),stbir__half_to_float(p2[3]) ); + ++nums; + if ( nums > 16 ) goto ex; + //if (d) exit(1); + //goto ex; + } + } + } + break; + } + } + + for( x = (w*c)*tsizes[oldtypes]; x < op; x++ ) + { + if ( ir1[y*op+x] != 79 ) + { + printf("Margin error at %d x %d %d (should be 79) OLD!\n",x,y,(unsigned char)ir1[y*op+x]); + goto ex; + } + } + + for( x = (w*c)*tsizes[types]; x < np; x++ ) + { + if ( ir2[y*np+x] != 79 ) + { + printf("Margin error at %d x %d %d (should be 79) NEW\n",x,y,(unsigned char)ir2[y*np+x]); + goto ex; + } + } + } + + ex: + ENTER( "OUTPUT IMAGES" ); + printf(" tot pix: %d, errs: %d\n", w*h*c,nums ); + + if (nums) + { + stbi_write_png("old.png", w, h, c, ir1, op); + stbi_write_png("new.png", w, h, c, ir2, np); + exit(1); + } + + LEAVE(); // output images + } + LEAVE(); //test compare + #endif + + + + } + LEAVE(); // test filter + } + LEAVE(); // test edge + } + LEAVE(); // test width + } + LEAVE(); // test height + } + LEAVE(); // test type + } + LEAVE(); // test layout + } + + CloseTM(); + return 0; +} diff --git a/source/engine/thirdparty/stb/stb_image_resize_test/vf_train.c b/source/engine/thirdparty/stb/stb_image_resize_test/vf_train.c new file mode 100644 index 0000000..0fdbe27 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_image_resize_test/vf_train.c @@ -0,0 +1,999 @@ +#include +#include +#include + +#define stop() __debugbreak() +#include +#define int64 __int64 + +#pragma warning(disable:4127) + +#define STBIR__WEIGHT_TABLES +#define STBIR_PROFILE +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include "stb_image_resize2.h" + +static int * file_read( char const * filename ) +{ + size_t s; + int * m; + FILE * f = fopen( filename, "rb" ); + if ( f == 0 ) return 0; + + fseek( f, 0, SEEK_END); + s = ftell( f ); + fseek( f, 0, SEEK_SET); + m = malloc( s + 4 ); + m[0] = (int)s; + fread( m+1, 1, s, f); + fclose(f); + + return( m ); +} + +typedef struct fileinfo +{ + int * timings; + int timing_count; + int dimensionx, dimensiony; + int numtypes; + int * types; + int * effective; + int cpu; + int simd; + int numinputrects; + int * inputrects; + int outputscalex, outputscaley; + int milliseconds; + int64 cycles; + double scale_time; + int bitmapx, bitmapy; + char const * filename; +} fileinfo; + +int numfileinfo; +fileinfo fi[256]; +unsigned char * bitmap; +int bitmapw, bitmaph, bitmapp; + +static int use_timing_file( char const * filename, int index ) +{ + int * base = file_read( filename ); + int * file = base; + + if ( base == 0 ) return 0; + + ++file; // skip file image size; + if ( *file++ != 'VFT1' ) return 0; + fi[index].cpu = *file++; + fi[index].simd = *file++; + fi[index].dimensionx = *file++; + fi[index].dimensiony = *file++; + fi[index].numtypes = *file++; + fi[index].types = file; file += fi[index].numtypes; + fi[index].effective = file; file += fi[index].numtypes; + fi[index].numinputrects = *file++; + fi[index].inputrects = file; file += fi[index].numinputrects * 2; + fi[index].outputscalex = *file++; + fi[index].outputscaley = *file++; + fi[index].milliseconds = *file++; + fi[index].cycles = ((int64*)file)[0]; file += 2; + fi[index].filename = filename; + + fi[index].timings = file; + fi[index].timing_count = (int) ( ( base[0] - ( ((char*)file - (char*)base - sizeof(int) ) ) ) / (sizeof(int)*2) ); + + fi[index].scale_time = (double)fi[index].milliseconds / (double)fi[index].cycles; + + return 1; +} + +static int vert_first( float weights_table[STBIR_RESIZE_CLASSIFICATIONS][4], int ox, int oy, int ix, int iy, int filter, STBIR__V_FIRST_INFO * v_info ) +{ + float h_scale=(float)ox/(float)(ix); + float v_scale=(float)oy/(float)(iy); + stbir__support_callback * support = stbir__builtin_supports[filter]; + int vertical_filter_width = stbir__get_filter_pixel_width(support,v_scale,0); + int vertical_gather = ( v_scale >= ( 1.0f - stbir__small_float ) ) || ( vertical_filter_width <= STBIR_FORCE_GATHER_FILTER_SCANLINES_AMOUNT ); + + return stbir__should_do_vertical_first( weights_table, stbir__get_filter_pixel_width(support,h_scale,0), h_scale, ox, vertical_filter_width, v_scale, oy, vertical_gather, v_info ); +} + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +static void alloc_bitmap() +{ + int findex; + int x = 0, y = 0; + int w = 0, h = 0; + + for( findex = 0 ; findex < numfileinfo ; findex++ ) + { + int nx, ny; + int thisw, thish; + + thisw = ( fi[findex].dimensionx * fi[findex].numtypes ) + ( fi[findex].numtypes - 1 ); + thish = ( fi[findex].dimensiony * fi[findex].numinputrects ) + ( fi[findex].numinputrects - 1 ); + + for(;;) + { + nx = x + ((x)?4:0) + thisw; + ny = y + ((y)?4:0) + thish; + if ( ( nx <= 3600 ) || ( x == 0 ) ) + { + fi[findex].bitmapx = x + ((x)?4:0); + fi[findex].bitmapy = y + ((y)?4:0); + x = nx; + if ( x > w ) w = x; + if ( ny > h ) h = ny; + break; + } + else + { + x = 0; + y = h; + } + } + } + + w = (w+3) & ~3; + bitmapw = w; + bitmaph = h; + bitmapp = w * 3; // RGB + bitmap = malloc( bitmapp * bitmaph ); + + memset( bitmap, 0, bitmapp * bitmaph ); +} + +static void build_bitmap( float weights[STBIR_RESIZE_CLASSIFICATIONS][4], int do_channel_count_index, int findex ) +{ + static int colors[STBIR_RESIZE_CLASSIFICATIONS]; + STBIR__V_FIRST_INFO v_info = {0}; + + int * ts; + int ir; + unsigned char * bitm = bitmap + ( fi[findex].bitmapx*3 ) + ( fi[findex].bitmapy*bitmapp) ; + + for( ir = 0; ir < STBIR_RESIZE_CLASSIFICATIONS ; ir++ ) colors[ ir ] = 127*ir/STBIR_RESIZE_CLASSIFICATIONS+128; + + ts = fi[findex].timings; + + for( ir = 0 ; ir < fi[findex].numinputrects ; ir++ ) + { + int ix, iy, chanind; + ix = fi[findex].inputrects[ir*2]; + iy = fi[findex].inputrects[ir*2+1]; + + for( chanind = 0 ; chanind < fi[findex].numtypes ; chanind++ ) + { + int ofs, h, hh; + + // just do the type that we're on + if ( chanind != do_channel_count_index ) + { + ts += 2 * fi[findex].dimensionx * fi[findex].dimensiony; + continue; + } + + // bitmap offset + ofs=chanind*(fi[findex].dimensionx+1)*3+ir*(fi[findex].dimensiony+1)*bitmapp; + + h = 1; + for( hh = 0 ; hh < fi[findex].dimensiony; hh++ ) + { + int ww, w = 1; + for( ww = 0 ; ww < fi[findex].dimensionx; ww++ ) + { + int good, v_first, VF, HF; + + VF = ts[0]; + HF = ts[1]; + + v_first = vert_first( weights, w, h, ix, iy, STBIR_FILTER_MITCHELL, &v_info ); + + good = ( ((HF<=VF) && (!v_first)) || ((VF<=HF) && (v_first))); + + if ( good ) + { + bitm[ofs+2] = 0; + bitm[ofs+1] = (unsigned char)colors[v_info.v_resize_classification]; + } + else + { + double r; + + if ( HF < VF ) + r = (double)(VF-HF)/(double)HF; + else + r = (double)(HF-VF)/(double)VF; + + if ( r > 0.4f) r = 0.4; + r *= 1.0f/0.4f; + + bitm[ofs+2] = (char)(255.0f*r); + bitm[ofs+1] = (char)(((float)colors[v_info.v_resize_classification])*(1.0f-r)); + } + bitm[ofs] = 0; + + ofs += 3; + ts += 2; + w += fi[findex].outputscalex; + } + ofs += bitmapp - fi[findex].dimensionx*3; + h += fi[findex].outputscaley; + } + } + } +} + +static void build_comp_bitmap( float weights[STBIR_RESIZE_CLASSIFICATIONS][4], int do_channel_count_index ) +{ + int * ts0; + int * ts1; + int ir; + unsigned char * bitm = bitmap + ( fi[0].bitmapx*3 ) + ( fi[0].bitmapy*bitmapp) ; + + ts0 = fi[0].timings; + ts1 = fi[1].timings; + + for( ir = 0 ; ir < fi[0].numinputrects ; ir++ ) + { + int ix, iy, chanind; + ix = fi[0].inputrects[ir*2]; + iy = fi[0].inputrects[ir*2+1]; + + for( chanind = 0 ; chanind < fi[0].numtypes ; chanind++ ) + { + int ofs, h, hh; + + // just do the type that we're on + if ( chanind != do_channel_count_index ) + { + ts0 += 2 * fi[0].dimensionx * fi[0].dimensiony; + ts1 += 2 * fi[0].dimensionx * fi[0].dimensiony; + continue; + } + + // bitmap offset + ofs=chanind*(fi[0].dimensionx+1)*3+ir*(fi[0].dimensiony+1)*bitmapp; + + h = 1; + for( hh = 0 ; hh < fi[0].dimensiony; hh++ ) + { + int ww, w = 1; + for( ww = 0 ; ww < fi[0].dimensionx; ww++ ) + { + int v_first, time0, time1; + + v_first = vert_first( weights, w, h, ix, iy, STBIR_FILTER_MITCHELL, 0 ); + + time0 = ( v_first ) ? ts0[0] : ts0[1]; + time1 = ( v_first ) ? ts1[0] : ts1[1]; + + if ( time0 < time1 ) + { + double r = (double)(time1-time0)/(double)time0; + if ( r > 0.4f) r = 0.4; + r *= 1.0f/0.4f; + bitm[ofs+2] = 0; + bitm[ofs+1] = (char)(255.0f*r); + bitm[ofs] = (char)(64.0f*(1.0f-r)); + } + else + { + double r = (double)(time0-time1)/(double)time1; + if ( r > 0.4f) r = 0.4; + r *= 1.0f/0.4f; + bitm[ofs+2] = (char)(255.0f*r); + bitm[ofs+1] = 0; + bitm[ofs] = (char)(64.0f*(1.0f-r)); + } + ofs += 3; + ts0 += 2; + ts1 += 2; + w += fi[0].outputscalex; + } + ofs += bitmapp - fi[0].dimensionx*3; + h += fi[0].outputscaley; + } + } + } +} + +static void write_bitmap() +{ + stbi_write_png( "results.png", bitmapp / 3, bitmaph, 3|STB_IMAGE_BGR, bitmap, bitmapp ); +} + + +static void calc_errors( float weights_table[STBIR_RESIZE_CLASSIFICATIONS][4], int * curtot, double * curerr, int do_channel_count_index ) +{ + int th, findex; + STBIR__V_FIRST_INFO v_info = {0}; + + for(th=0;th 200 ) + { + int findex; + + do_bitmap: + lasttick = t; + newbest = 0; + + for( findex = 0 ; findex < numfileinfo ; findex++ ) + build_bitmap( best_output_weights, channel_count_index, findex ); + + lasttick = GetTickCount(); + } + } + } + + windowranges[ channel_count_index ] = range; + + // advance all the weights and loop + --range; + } while( ( range >= 0 ) && ( !windowstatus ) ); + + // if we hit here, then we tried all weights for this opt, so save them +} + +static void print_struct( float weight[5][STBIR_RESIZE_CLASSIFICATIONS][4], char const * name ) +{ + printf("\n\nstatic float %s[5][STBIR_RESIZE_CLASSIFICATIONS][4]=\n{", name ); + { + int i; + for(i=0;i<5;i++) + { + int th; + for(th=0;th 60000) + sprintf( time, "%dm %ds",ms/60000, (ms/1000)%60 ); + else + sprintf( time, "%ds",ms/1000 ); + return time; +} + +static BITMAPINFOHEADER bmiHeader; +static DWORD extrawindoww, extrawindowh; +static HINSTANCE instance; +static int curzoom = 1; + +static LRESULT WINAPI WindowProc( HWND window, + UINT message, + WPARAM wparam, + LPARAM lparam ) +{ + switch( message ) + { + case WM_CHAR: + if ( wparam != 27 ) + break; + // falls through + + case WM_CLOSE: + { + int i; + int max = 0; + + for( i = 0 ; i < fi[0].numtypes ; i++ ) + if( windowranges[i] > max ) max = windowranges[i]; + + if ( ( max == 0 ) || ( MessageBox( window, "Cancel before training is finished?", "Vertical First Training", MB_OKCANCEL|MB_ICONSTOP ) == IDOK ) ) + { + for( i = 0 ; i < fi[0].numtypes ; i++ ) + if( windowranges[i] > max ) max = windowranges[i]; + if ( max ) + windowstatus = 1; + DestroyWindow( window ); + } + } + return 0; + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC dc; + + dc = BeginPaint( window, &ps ); + StretchDIBits( dc, + 0, 0, bitmapw*curzoom, bitmaph*curzoom, + 0, 0, bitmapw, bitmaph, + bitmap, (BITMAPINFO*)&bmiHeader, DIB_RGB_COLORS, SRCCOPY ); + + PatBlt( dc, bitmapw*curzoom, 0, 4096, 4096, WHITENESS ); + PatBlt( dc, 0, bitmaph*curzoom, 4096, 4096, WHITENESS ); + + SetTextColor( dc, RGB(0,0,0) ); + SetBkColor( dc, RGB(255,255,255) ); + SetBkMode( dc, OPAQUE ); + + { + int i, l = 0, max = 0; + char buf[1024]; + RECT rc; + POINT p; + + for( i = 0 ; i < fi[0].numtypes ; i++ ) + { + l += sprintf( buf + l, "channels: %d %s\n", fi[0].effective[i], windowranges[i] ? expand_to_string( windowranges[i] ) : "Done." ); + if ( windowranges[i] > max ) max = windowranges[i]; + } + + rc.left = 32; rc.top = bitmaph*curzoom+10; + rc.right = 512; rc.bottom = rc.top + 512; + DrawText( dc, buf, -1, &rc, DT_TOP ); + + l = 0; + if ( max == 0 ) + { + static DWORD traindone = 0; + if ( traindone == 0 ) traindone = GetTickCount(); + l = sprintf( buf, "Finished in %s.", gettime( traindone - trainstart ) ); + } + else if ( max != MAXRANGE ) + l = sprintf( buf, "Done in %s...", gettime( (int) ( ( ( (int64)max * ( (int64)GetTickCount() - (int64)trainstart ) ) ) / (int64) ( MAXRANGE - max ) ) ) ); + + GetCursorPos( &p ); + ScreenToClient( window, &p ); + + if ( ( p.x >= 0 ) && ( p.y >= 0 ) && ( p.x < (bitmapw*curzoom) ) && ( p.y < (bitmaph*curzoom) ) ) + { + int findex; + int x, y, w, h, sx, sy, ix, iy, ox, oy; + int ir, chanind; + int * ts; + char badstr[64]; + STBIR__V_FIRST_INFO v_info={0}; + + p.x /= curzoom; + p.y /= curzoom; + + for( findex = 0 ; findex < numfileinfo ; findex++ ) + { + x = fi[findex].bitmapx; + y = fi[findex].bitmapy; + w = x + ( fi[findex].dimensionx + 1 ) * fi[findex].numtypes; + h = y + ( fi[findex].dimensiony + 1 ) * fi[findex].numinputrects; + + if ( ( p.x >= x ) && ( p.y >= y ) && ( p.x < w ) && ( p.y < h ) ) + goto found; + } + goto nope; + + found: + + ir = ( p.y - y ) / ( fi[findex].dimensiony + 1 ); + sy = ( p.y - y ) % ( fi[findex].dimensiony + 1 ); + if ( sy >= fi[findex].dimensiony ) goto nope; + + chanind = ( p.x - x ) / ( fi[findex].dimensionx + 1 ); + sx = ( p.x - x ) % ( fi[findex].dimensionx + 1 ); + if ( sx >= fi[findex].dimensionx ) goto nope; + + ix = fi[findex].inputrects[ir*2]; + iy = fi[findex].inputrects[ir*2+1]; + + ts = fi[findex].timings + ( ( fi[findex].dimensionx * fi[findex].dimensiony * fi[findex].numtypes * ir ) + ( fi[findex].dimensionx * fi[findex].dimensiony * chanind ) + ( fi[findex].dimensionx * sy ) + sx ) * 2; + + ox = 1+fi[findex].outputscalex*sx; + oy = 1+fi[findex].outputscaley*sy; + + if ( windowstatus != 2 ) + { + int VF, HF, v_first, good; + VF = ts[0]; + HF = ts[1]; + + v_first = vert_first( retrain_weights[chanind], ox, oy, ix, iy, STBIR_FILTER_MITCHELL, &v_info ); + + good = ( ((HF<=VF) && (!v_first)) || ((VF<=HF) && (v_first))); + + if ( good ) + badstr[0] = 0; + else + { + double r; + + if ( HF < VF ) + r = (double)(VF-HF)/(double)HF; + else + r = (double)(HF-VF)/(double)VF; + sprintf( badstr, " %.1f%% off", r*100 ); + } + sprintf( buf + l, "\n\n%s\nCh: %d Resize: %dx%d to %dx%d\nV: %d H: %d Order: %c (%s%s)\nClass: %d Scale: %.2f %s", fi[findex].filename,fi[findex].effective[chanind], ix,iy,ox,oy, VF, HF, v_first?'V':'H', good?"Good":"Wrong", badstr, v_info.v_resize_classification, (double)oy/(double)iy, v_info.is_gather ? "Gather" : "Scatter" ); + } + else + { + int v_first, time0, time1; + float (* weights)[4] = stbir__compute_weights[chanind]; + int * ts1; + char b0[32], b1[32]; + + ts1 = fi[1].timings + ( ts - fi[0].timings ); + + v_first = vert_first( weights, ox, oy, ix, iy, STBIR_FILTER_MITCHELL, &v_info ); + + time0 = ( v_first ) ? ts[0] : ts[1]; + time1 = ( v_first ) ? ts1[0] : ts1[1]; + + b0[0] = b1[0] = 0; + if ( time0 < time1 ) + sprintf( b0," (%.f%% better)", ((double)time1-(double)time0)*100.0f/(double)time0); + else + sprintf( b1," (%.f%% better)", ((double)time0-(double)time1)*100.0f/(double)time1); + + sprintf( buf + l, "\n\n0: %s\n1: %s\nCh: %d Resize: %dx%d to %dx%d\nClass: %d Scale: %.2f %s\nTime0: %d%s\nTime1: %d%s", fi[0].filename, fi[1].filename, fi[0].effective[chanind], ix,iy,ox,oy, v_info.v_resize_classification, (double)oy/(double)iy, v_info.is_gather ? "Gather" : "Scatter", time0, b0, time1, b1 ); + } + } + nope: + + rc.left = 32+320; rc.right = 512+320; + SetTextColor( dc, RGB(0,0,128) ); + DrawText( dc, buf, -1, &rc, DT_TOP ); + + } + EndPaint( window, &ps ); + return 0; + } + + case WM_TIMER: + InvalidateRect( window, 0, 0 ); + return 0; + + case WM_DESTROY: + PostQuitMessage( 0 ); + return 0; + } + + + return DefWindowProc( window, message, wparam, lparam ); +} + +static void SetHighDPI(void) +{ + typedef HRESULT WINAPI setdpitype(int v); + HMODULE h=LoadLibrary("Shcore.dll"); + if (h) + { + setdpitype * sd = (setdpitype*)GetProcAddress(h,"SetProcessDpiAwareness"); + if (sd ) + sd(1); + } +} + +static void draw_window() +{ + WNDCLASS wc; + HWND w; + MSG msg; + + instance = GetModuleHandle(NULL); + + wc.style = 0; + wc.lpfnWndProc = WindowProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = instance; + wc.hIcon = 0; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = 0; + wc.lpszMenuName = 0; + wc.lpszClassName = "WHTrain"; + + if ( !RegisterClass( &wc ) ) + exit(1); + + SetHighDPI(); + + bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmiHeader.biWidth = bitmapp/3; + bmiHeader.biHeight = -bitmaph; + bmiHeader.biPlanes = 1; + bmiHeader.biBitCount = 24; + bmiHeader.biCompression = BI_RGB; + + w = CreateWindow( "WHTrain", + "Vertical First Training", + WS_CAPTION | WS_POPUP| WS_CLIPCHILDREN | + WS_SYSMENU | WS_MINIMIZEBOX | WS_SIZEBOX, + CW_USEDEFAULT,CW_USEDEFAULT, + CW_USEDEFAULT,CW_USEDEFAULT, + 0, 0, instance, 0 ); + + { + RECT r, c; + GetWindowRect( w, &r ); + GetClientRect( w, &c ); + extrawindoww = ( r.right - r.left ) - ( c.right - c.left ); + extrawindowh = ( r.bottom - r.top ) - ( c.bottom - c.top ); + SetWindowPos( w, 0, 0, 0, bitmapw * curzoom + extrawindoww, bitmaph * curzoom + extrawindowh + 164, SWP_NOMOVE ); + } + + ShowWindow( w, SW_SHOWNORMAL ); + SetTimer( w, 1, 250, 0 ); + + { + BOOL ret; + while( ( ret = GetMessage( &msg, w, 0, 0 ) ) != 0 ) + { + if ( ret == -1 ) + break; + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + } +} + +static void retrain() +{ + HANDLE threads[ 16 ]; + int chanind; + + trainstart = GetTickCount(); + for( chanind = 0 ; chanind < fi[0].numtypes ; chanind++ ) + threads[ chanind ] = CreateThread( 0, 2048*1024, retrain_shim, (LPVOID)(size_t)chanind, 0, 0 ); + + draw_window(); + + for( chanind = 0 ; chanind < fi[0].numtypes ; chanind++ ) + { + WaitForSingleObject( threads[ chanind ], INFINITE ); + CloseHandle( threads[ chanind ] ); + } + + write_bitmap(); + + print_struct( retrain_weights, "retained_weights" ); + if ( windowstatus ) printf( "CANCELLED!\n" ); +} + +static void info() +{ + int findex; + + // display info about each input file + for( findex = 0 ; findex < numfileinfo ; findex++ ) + { + int i, h,m,s; + if ( findex ) printf( "\n" ); + printf( "Timing file: %s\n", fi[findex].filename ); + printf( "CPU type: %d %s\n", fi[findex].cpu, fi[findex].simd?(fi[findex].simd==2?"SIMD8":"SIMD4"):"Scalar" ); + h = fi[findex].milliseconds/3600000; + m = (fi[findex].milliseconds-h*3600000)/60000; + s = (fi[findex].milliseconds-h*3600000-m*60000)/1000; + printf( "Total time in test: %dh %dm %ds Cycles/sec: %.f\n", h,m,s, 1000.0/fi[findex].scale_time ); + printf( "Each tile of samples is %dx%d, and is scaled by %dx%d.\n", fi[findex].dimensionx,fi[findex].dimensiony, fi[findex].outputscalex,fi[findex].outputscaley ); + printf( "So the x coords are: " ); + for( i=0; i < fi[findex].dimensionx ; i++ ) printf( "%d ",1+i*fi[findex].outputscalex ); + printf( "\n" ); + printf( "And the y coords are: " ); + for( i=0; i < fi[findex].dimensiony ; i++ ) printf( "%d ",1+i*fi[findex].outputscaley ); + printf( "\n" ); + printf( "There are %d channel counts and they are: ", fi[findex].numtypes ); + for( i=0; i < fi[findex].numtypes ; i++ ) printf( "%d ",fi[findex].effective[i] ); + printf( "\n" ); + printf( "There are %d input rect sizes and they are: ", fi[findex].numinputrects ); + for( i=0; i < fi[findex].numtypes ; i++ ) printf( "%dx%d ",fi[findex].inputrects[i*2],fi[findex].inputrects[i*2+1] ); + printf( "\n" ); + } +} + +static void current( int do_win, int do_bitmap ) +{ + int i, findex; + + trainstart = GetTickCount(); + + // clear progress + memset( windowranges, 0, sizeof( windowranges ) ); + // copy in appropriate weights + memcpy( retrain_weights, stbir__compute_weights, sizeof( retrain_weights ) ); + + // build and print current errors and build current bitmap + for( i = 0 ; i < fi[0].numtypes ; i++ ) + { + double curerr[STBIR_RESIZE_CLASSIFICATIONS]; + int curtot[STBIR_RESIZE_CLASSIFICATIONS]; + float (* weights)[4] = retrain_weights[i]; + + calc_errors( weights, curtot, curerr, i ); + if ( !do_bitmap ) + print_weights( weights, i, curtot, curerr ); + + for( findex = 0 ; findex < numfileinfo ; findex++ ) + build_bitmap( weights, i, findex ); + } + + if ( do_win ) + draw_window(); + + if ( do_bitmap ) + write_bitmap(); +} + +static void compare() +{ + int i; + + trainstart = GetTickCount(); + windowstatus = 2; // comp mode + + // clear progress + memset( windowranges, 0, sizeof( windowranges ) ); + + if ( ( fi[0].numtypes != fi[1].numtypes ) || ( fi[0].numinputrects != fi[1].numinputrects ) || + ( fi[0].dimensionx != fi[1].dimensionx ) || ( fi[0].dimensiony != fi[1].dimensiony ) || + ( fi[0].outputscalex != fi[1].outputscalex ) || ( fi[0].outputscaley != fi[1].outputscaley ) ) + { + err: + printf( "Timing files don't match.\n" ); + exit(5); + } + + for( i=0; i < fi[0].numtypes ; i++ ) + { + if ( fi[0].effective[i] != fi[1].effective[i] ) goto err; + if ( fi[0].inputrects[i*2] != fi[1].inputrects[i*2] ) goto err; + if ( fi[0].inputrects[i*2+1] != fi[1].inputrects[i*2+1] ) goto err; + } + + alloc_bitmap( 1 ); + + for( i = 0 ; i < fi[0].numtypes ; i++ ) + { + float (* weights)[4] = stbir__compute_weights[i]; + build_comp_bitmap( weights, i ); + } + + draw_window(); +} + +static void load_files( char ** args, int count ) +{ + int i; + + if ( count == 0 ) + { + printf( "No timing files listed!" ); + exit(3); + } + + for ( i = 0 ; i < count ; i++ ) + { + if ( !use_timing_file( args[i], i ) ) + { + printf( "Bad timing file %s\n", args[i] ); + exit(2); + } + } + numfileinfo = count; +} + +int main( int argc, char ** argv ) +{ + int check; + if ( argc < 3 ) + { + err: + printf( "vf_train retrain [timing_filenames....] - recalcs weights for all the files on the command line.\n"); + printf( "vf_train info [timing_filenames....] - shows info about each timing file.\n"); + printf( "vf_train check [timing_filenames...] - show results for the current weights for all files listed.\n"); + printf( "vf_train compare - compare two timing files (must only be two files and same resolution).\n"); + printf( "vf_train bitmap [timing_filenames...] - write out results.png, comparing against the current weights for all files listed.\n"); + exit(1); + } + + check = ( strcmp( argv[1], "check" ) == 0 ); + if ( ( check ) || ( strcmp( argv[1], "bitmap" ) == 0 ) ) + { + load_files( argv + 2, argc - 2 ); + alloc_bitmap( numfileinfo ); + current( check, !check ); + } + else if ( strcmp( argv[1], "info" ) == 0 ) + { + load_files( argv + 2, argc - 2 ); + info(); + } + else if ( strcmp( argv[1], "compare" ) == 0 ) + { + if ( argc != 4 ) + { + printf( "You must specify two files to compare.\n" ); + exit(4); + } + + load_files( argv + 2, argc - 2 ); + compare(); + } + else if ( strcmp( argv[1], "retrain" ) == 0 ) + { + load_files( argv + 2, argc - 2 ); + alloc_bitmap( numfileinfo ); + retrain(); + } + else + { + goto err; + } + + return 0; +} diff --git a/source/engine/thirdparty/stb/stb_image_write.h b/source/engine/thirdparty/stb/stb_image_write.h new file mode 100644 index 0000000..e4b32ed --- /dev/null +++ b/source/engine/thirdparty/stb/stb_image_write.h @@ -0,0 +1,1724 @@ +/* stb_image_write - v1.16 - public domain - http://nothings.org/stb + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + Andrew Kensler + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#include + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +STBIWDEF int stbi_write_tga_with_rle; +STBIWDEF int stbi_write_png_compression_level; +STBIWDEF int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBIW_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +static int stbi__flip_vertically_on_write = 0; + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func *func; + void *context; + unsigned char buffer[64]; + int buf_used; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write_flush(stbi__write_context *s) +{ + if (s->buf_used) { + s->func(s->context, &s->buffer, s->buf_used); + s->buf_used = 0; + } +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write1(stbi__write_context *s, unsigned char a) +{ + if ((size_t)s->buf_used + 1 > sizeof(s->buffer)) + stbiw__write_flush(s); + s->buffer[s->buf_used++] = a; +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + int n; + if ((size_t)s->buf_used + 3 > sizeof(s->buffer)) + stbiw__write_flush(s); + n = s->buf_used; + s->buf_used = n+3; + s->buffer[n+0] = a; + s->buffer[n+1] = b; + s->buffer[n+2] = c; +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + stbiw__write1(s, d[comp - 1]); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + stbiw__write1(s, d[0]); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + stbiw__write1(s, d[comp - 1]); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + if (vdir < 0) { + j_end = -1; j = y-1; + } else { + j_end = y; j = 0; + } + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + stbiw__write_flush(s); + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + if (comp != 4) { + // write RGB bitmap + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header + } else { + // RGBA bitmaps need a v4 header + // use BI_BITFIELDS mode with 32bpp and alpha mask + // (straight BI_RGB with alpha mask doesn't work in most readers) + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0, + "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444", + 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header + 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header + } +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + stbiw__write1(s, header); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + stbiw__write1(s, header); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + stbiw__write_flush(s); + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +#ifndef STBI_WRITE_NO_STDIO + +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + +#ifdef __STDC_LIB_EXT1__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) { best=d; bestloc=hlist[j]; } + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + // store uncompressed instead if compression was worse + if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) { + stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1 + for (j = 0; j < data_len;) { + int blocklen = data_len - j; + if (blocklen > 32767) blocklen = 32767; + stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression + stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN + stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN + stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8)); + memcpy(out+stbiw__sbn(out), data+j, blocklen); + stbiw__sbn(out) += blocklen; + j += blocklen; + } + } + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } + s1 %= 65521; s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int j,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { + est += abs((signed char) line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k, subsample; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + subsample = quality <= 90 ? 1 : 0; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), + 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + const unsigned char *dataR = (const unsigned char *)data; + const unsigned char *dataG = dataR + ofsG; + const unsigned char *dataB = dataR + ofsB; + int x, y, pos; + if(subsample) { + for(y = 0; y < height; y += 16) { + for(x = 0; x < width; x += 16) { + float Y[256], U[256], V[256]; + for(row = y, pos = 0; row < y+16; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+16; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + // subsample U,V + { + float subU[64], subV[64]; + int yy, xx; + for(yy = 0, pos = 0; yy < 8; ++yy) { + for(xx = 0; xx < 8; ++xx, ++pos) { + int j = yy*32+xx*2; + subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; + subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; + } + } + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + } else { + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float Y[64], U[64], V[64]; + for(row = y, pos = 0; row < y+8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+8; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.16 (2021-07-11) + make Deflate code emit uncompressed blocks when it would otherwise expand + support writing BMPs with alpha channel + 1.15 (2020-07-13) unknown + 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels + 1.13 + 1.12 + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_include.h b/source/engine/thirdparty/stb/stb_include.h new file mode 100644 index 0000000..c5db201 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_include.h @@ -0,0 +1,295 @@ +// stb_include.h - v0.02 - parse and process #include directives - public domain +// +// To build this, in one source file that includes this file do +// #define STB_INCLUDE_IMPLEMENTATION +// +// This program parses a string and replaces lines of the form +// #include "foo" +// with the contents of a file named "foo". It also embeds the +// appropriate #line directives. Note that all include files must +// reside in the location specified in the path passed to the API; +// it does not check multiple directories. +// +// If the string contains a line of the form +// #inject +// then it will be replaced with the contents of the string 'inject' passed to the API. +// +// Options: +// +// Define STB_INCLUDE_LINE_GLSL to get GLSL-style #line directives +// which use numbers instead of filenames. +// +// Define STB_INCLUDE_LINE_NONE to disable output of #line directives. +// +// Standard libraries: +// +// stdio.h FILE, fopen, fclose, fseek, ftell +// stdlib.h malloc, realloc, free +// string.h strcpy, strncmp, memcpy +// +// Credits: +// +// Written by Sean Barrett. +// +// Fixes: +// Michal Klos + +#ifndef STB_INCLUDE_STB_INCLUDE_H +#define STB_INCLUDE_STB_INCLUDE_H + +// Do include-processing on the string 'str'. To free the return value, pass it to free() +char *stb_include_string(char *str, char *inject, char *path_to_includes, char *filename_for_line_directive, char error[256]); + +// Concatenate the strings 'strs' and do include-processing on the result. To free the return value, pass it to free() +char *stb_include_strings(char **strs, int count, char *inject, char *path_to_includes, char *filename_for_line_directive, char error[256]); + +// Load the file 'filename' and do include-processing on the string therein. note that +// 'filename' is opened directly; 'path_to_includes' is not used. To free the return value, pass it to free() +char *stb_include_file(char *filename, char *inject, char *path_to_includes, char error[256]); + +#endif + + +#ifdef STB_INCLUDE_IMPLEMENTATION + +#include +#include +#include + +static char *stb_include_load_file(char *filename, size_t *plen) +{ + char *text; + size_t len; + FILE *f = fopen(filename, "rb"); + if (f == 0) return 0; + fseek(f, 0, SEEK_END); + len = (size_t) ftell(f); + if (plen) *plen = len; + text = (char *) malloc(len+1); + if (text == 0) return 0; + fseek(f, 0, SEEK_SET); + fread(text, 1, len, f); + fclose(f); + text[len] = 0; + return text; +} + +typedef struct +{ + int offset; + int end; + char *filename; + int next_line_after; +} include_info; + +static include_info *stb_include_append_include(include_info *array, int len, int offset, int end, char *filename, int next_line) +{ + include_info *z = (include_info *) realloc(array, sizeof(*z) * (len+1)); + z[len].offset = offset; + z[len].end = end; + z[len].filename = filename; + z[len].next_line_after = next_line; + return z; +} + +static void stb_include_free_includes(include_info *array, int len) +{ + int i; + for (i=0; i < len; ++i) + free(array[i].filename); + free(array); +} + +static int stb_include_isspace(int ch) +{ + return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); +} + +// find location of all #include and #inject +static int stb_include_find_includes(char *text, include_info **plist) +{ + int line_count = 1; + int inc_count = 0; + char *s = text, *start; + include_info *list = NULL; + while (*s) { + // parse is always at start of line when we reach here + start = s; + while (*s == ' ' || *s == '\t') + ++s; + if (*s == '#') { + ++s; + while (*s == ' ' || *s == '\t') + ++s; + if (0==strncmp(s, "include", 7) && stb_include_isspace(s[7])) { + s += 7; + while (*s == ' ' || *s == '\t') + ++s; + if (*s == '"') { + char *t = ++s; + while (*t != '"' && *t != '\n' && *t != '\r' && *t != 0) + ++t; + if (*t == '"') { + char *filename = (char *) malloc(t-s+1); + memcpy(filename, s, t-s); + filename[t-s] = 0; + s=t; + while (*s != '\r' && *s != '\n' && *s != 0) + ++s; + // s points to the newline, so s-start is everything except the newline + list = stb_include_append_include(list, inc_count++, start-text, s-text, filename, line_count+1); + } + } + } else if (0==strncmp(s, "inject", 6) && (stb_include_isspace(s[6]) || s[6]==0)) { + while (*s != '\r' && *s != '\n' && *s != 0) + ++s; + list = stb_include_append_include(list, inc_count++, start-text, s-text, NULL, line_count+1); + } + } + while (*s != '\r' && *s != '\n' && *s != 0) + ++s; + if (*s == '\r' || *s == '\n') { + s = s + (s[0] + s[1] == '\r' + '\n' ? 2 : 1); + } + ++line_count; + } + *plist = list; + return inc_count; +} + +// avoid dependency on sprintf() +static void stb_include_itoa(char str[9], int n) +{ + int i; + for (i=0; i < 8; ++i) + str[i] = ' '; + str[i] = 0; + + for (i=1; i < 8; ++i) { + str[7-i] = '0' + (n % 10); + n /= 10; + if (n == 0) + break; + } +} + +static char *stb_include_append(char *str, size_t *curlen, char *addstr, size_t addlen) +{ + str = (char *) realloc(str, *curlen + addlen); + memcpy(str + *curlen, addstr, addlen); + *curlen += addlen; + return str; +} + +char *stb_include_string(char *str, char *inject, char *path_to_includes, char *filename, char error[256]) +{ + char temp[4096]; + include_info *inc_list; + int i, num = stb_include_find_includes(str, &inc_list); + size_t source_len = strlen(str); + char *text=0; + size_t textlen=0, last=0; + for (i=0; i < num; ++i) { + text = stb_include_append(text, &textlen, str+last, inc_list[i].offset - last); + // write out line directive for the include + #ifndef STB_INCLUDE_LINE_NONE + #ifdef STB_INCLUDE_LINE_GLSL + if (textlen != 0) // GLSL #version must appear first, so don't put a #line at the top + #endif + { + strcpy(temp, "#line "); + stb_include_itoa(temp+6, 1); + strcat(temp, " "); + #ifdef STB_INCLUDE_LINE_GLSL + stb_include_itoa(temp+15, i+1); + #else + strcat(temp, "\""); + if (inc_list[i].filename == 0) + strcmp(temp, "INJECT"); + else + strcat(temp, inc_list[i].filename); + strcat(temp, "\""); + #endif + strcat(temp, "\n"); + text = stb_include_append(text, &textlen, temp, strlen(temp)); + } + #endif + if (inc_list[i].filename == 0) { + if (inject != 0) + text = stb_include_append(text, &textlen, inject, strlen(inject)); + } else { + char *inc; + strcpy(temp, path_to_includes); + strcat(temp, "/"); + strcat(temp, inc_list[i].filename); + inc = stb_include_file(temp, inject, path_to_includes, error); + if (inc == NULL) { + stb_include_free_includes(inc_list, num); + return NULL; + } + text = stb_include_append(text, &textlen, inc, strlen(inc)); + free(inc); + } + // write out line directive + #ifndef STB_INCLUDE_LINE_NONE + strcpy(temp, "\n#line "); + stb_include_itoa(temp+6, inc_list[i].next_line_after); + strcat(temp, " "); + #ifdef STB_INCLUDE_LINE_GLSL + stb_include_itoa(temp+15, 0); + #else + strcat(temp, filename != 0 ? filename : "source-file"); + #endif + text = stb_include_append(text, &textlen, temp, strlen(temp)); + // no newlines, because we kept the #include newlines, which will get appended next + #endif + last = inc_list[i].end; + } + text = stb_include_append(text, &textlen, str+last, source_len - last + 1); // append '\0' + stb_include_free_includes(inc_list, num); + return text; +} + +char *stb_include_strings(char **strs, int count, char *inject, char *path_to_includes, char *filename, char error[256]) +{ + char *text; + char *result; + int i; + size_t length=0; + for (i=0; i < count; ++i) + length += strlen(strs[i]); + text = (char *) malloc(length+1); + length = 0; + for (i=0; i < count; ++i) { + strcpy(text + length, strs[i]); + length += strlen(strs[i]); + } + result = stb_include_string(text, inject, path_to_includes, filename, error); + free(text); + return result; +} + +char *stb_include_file(char *filename, char *inject, char *path_to_includes, char error[256]) +{ + size_t len; + char *result; + char *text = stb_include_load_file(filename, &len); + if (text == NULL) { + strcpy(error, "Error: couldn't load '"); + strcat(error, filename); + strcat(error, "'"); + return 0; + } + result = stb_include_string(text, inject, path_to_includes, filename, error); + free(text); + return result; +} + +#if 0 // @TODO, GL_ARB_shader_language_include-style system that doesn't touch filesystem +char *stb_include_preloaded(char *str, char *inject, char *includes[][2], char error[256]) +{ + +} +#endif + +#endif // STB_INCLUDE_IMPLEMENTATION diff --git a/source/engine/thirdparty/stb/stb_leakcheck.h b/source/engine/thirdparty/stb/stb_leakcheck.h new file mode 100644 index 0000000..19ee6e7 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_leakcheck.h @@ -0,0 +1,194 @@ +// stb_leakcheck.h - v0.6 - quick & dirty malloc leak-checking - public domain +// LICENSE +// +// See end of file. + +#ifdef STB_LEAKCHECK_IMPLEMENTATION +#undef STB_LEAKCHECK_IMPLEMENTATION // don't implement more than once + +// if we've already included leakcheck before, undefine the macros +#ifdef malloc +#undef malloc +#undef free +#undef realloc +#endif + +#ifndef STB_LEAKCHECK_OUTPUT_PIPE +#define STB_LEAKCHECK_OUTPUT_PIPE stdout +#endif + +#include +#include +#include +#include +#include +typedef struct malloc_info stb_leakcheck_malloc_info; + +struct malloc_info +{ + const char *file; + int line; + size_t size; + stb_leakcheck_malloc_info *next,*prev; +}; + +static stb_leakcheck_malloc_info *mi_head; + +void *stb_leakcheck_malloc(size_t sz, const char *file, int line) +{ + stb_leakcheck_malloc_info *mi = (stb_leakcheck_malloc_info *) malloc(sz + sizeof(*mi)); + if (mi == NULL) return mi; + mi->file = file; + mi->line = line; + mi->next = mi_head; + if (mi_head) + mi->next->prev = mi; + mi->prev = NULL; + mi->size = (int) sz; + mi_head = mi; + return mi+1; +} + +void stb_leakcheck_free(void *ptr) +{ + if (ptr != NULL) { + stb_leakcheck_malloc_info *mi = (stb_leakcheck_malloc_info *) ptr - 1; + mi->size = ~mi->size; + #ifndef STB_LEAKCHECK_SHOWALL + if (mi->prev == NULL) { + assert(mi_head == mi); + mi_head = mi->next; + } else + mi->prev->next = mi->next; + if (mi->next) + mi->next->prev = mi->prev; + free(mi); + #endif + } +} + +void *stb_leakcheck_realloc(void *ptr, size_t sz, const char *file, int line) +{ + if (ptr == NULL) { + return stb_leakcheck_malloc(sz, file, line); + } else if (sz == 0) { + stb_leakcheck_free(ptr); + return NULL; + } else { + stb_leakcheck_malloc_info *mi = (stb_leakcheck_malloc_info *) ptr - 1; + if (sz <= mi->size) + return ptr; + else { + #ifdef STB_LEAKCHECK_REALLOC_PRESERVE_MALLOC_FILELINE + void *q = stb_leakcheck_malloc(sz, mi->file, mi->line); + #else + void *q = stb_leakcheck_malloc(sz, file, line); + #endif + if (q) { + memcpy(q, ptr, mi->size); + stb_leakcheck_free(ptr); + } + return q; + } + } +} + +static void stblkck_internal_print(const char *reason, stb_leakcheck_malloc_info *mi) +{ +#if defined(_MSC_VER) && _MSC_VER < 1900 // 1900=VS 2015 + // Compilers that use the old MS C runtime library don't have %zd + // and the older ones don't even have %lld either... however, the old compilers + // without "long long" don't support 64-bit targets either, so here's the + // compromise: + #if _MSC_VER < 1400 // before VS 2005 + fprintf(STB_LEAKCHECK_OUTPUT_PIPE, "%s: %s (%4d): %8d bytes at %p\n", reason, mi->file, mi->line, (int)mi->size, (void*)(mi+1)); + #else + fprintf(STB_LEAKCHECK_OUTPUT_PIPE, "%s: %s (%4d): %16lld bytes at %p\n", reason, mi->file, mi->line, (long long)mi->size, (void*)(mi+1)); + #endif +#else + // Assume we have %zd on other targets. + #ifdef __MINGW32__ + __mingw_fprintf(STB_LEAKCHECK_OUTPUT_PIPE, "%s: %s (%4d): %zd bytes at %p\n", reason, mi->file, mi->line, mi->size, (void*)(mi+1)); + #else + fprintf(STB_LEAKCHECK_OUTPUT_PIPE, "%s: %s (%4d): %zd bytes at %p\n", reason, mi->file, mi->line, mi->size, (void*)(mi+1)); + #endif +#endif +} + +void stb_leakcheck_dumpmem(void) +{ + stb_leakcheck_malloc_info *mi = mi_head; + while (mi) { + if ((ptrdiff_t) mi->size >= 0) + stblkck_internal_print("LEAKED", mi); + mi = mi->next; + } + #ifdef STB_LEAKCHECK_SHOWALL + mi = mi_head; + while (mi) { + if ((ptrdiff_t) mi->size < 0) + stblkck_internal_print("FREED ", mi); + mi = mi->next; + } + #endif +} +#endif // STB_LEAKCHECK_IMPLEMENTATION + +#if !defined(INCLUDE_STB_LEAKCHECK_H) || !defined(malloc) +#define INCLUDE_STB_LEAKCHECK_H + +#include // we want to define the macros *after* stdlib to avoid a slew of errors + +#define malloc(sz) stb_leakcheck_malloc(sz, __FILE__, __LINE__) +#define free(p) stb_leakcheck_free(p) +#define realloc(p,sz) stb_leakcheck_realloc(p,sz, __FILE__, __LINE__) + +extern void * stb_leakcheck_malloc(size_t sz, const char *file, int line); +extern void * stb_leakcheck_realloc(void *ptr, size_t sz, const char *file, int line); +extern void stb_leakcheck_free(void *ptr); +extern void stb_leakcheck_dumpmem(void); + +#endif // INCLUDE_STB_LEAKCHECK_H + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_perlin.h b/source/engine/thirdparty/stb/stb_perlin.h new file mode 100644 index 0000000..47cb9a4 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_perlin.h @@ -0,0 +1,428 @@ +// stb_perlin.h - v0.5 - perlin noise +// public domain single-file C implementation by Sean Barrett +// +// LICENSE +// +// See end of file. +// +// +// to create the implementation, +// #define STB_PERLIN_IMPLEMENTATION +// in *one* C/CPP file that includes this file. +// +// +// Documentation: +// +// float stb_perlin_noise3( float x, +// float y, +// float z, +// int x_wrap=0, +// int y_wrap=0, +// int z_wrap=0) +// +// This function computes a random value at the coordinate (x,y,z). +// Adjacent random values are continuous but the noise fluctuates +// its randomness with period 1, i.e. takes on wholly unrelated values +// at integer points. Specifically, this implements Ken Perlin's +// revised noise function from 2002. +// +// The "wrap" parameters can be used to create wraparound noise that +// wraps at powers of two. The numbers MUST be powers of two. Specify +// 0 to mean "don't care". (The noise always wraps every 256 due +// details of the implementation, even if you ask for larger or no +// wrapping.) +// +// float stb_perlin_noise3_seed( float x, +// float y, +// float z, +// int x_wrap=0, +// int y_wrap=0, +// int z_wrap=0, +// int seed) +// +// As above, but 'seed' selects from multiple different variations of the +// noise function. The current implementation only uses the bottom 8 bits +// of 'seed', but possibly in the future more bits will be used. +// +// +// Fractal Noise: +// +// Three common fractal noise functions are included, which produce +// a wide variety of nice effects depending on the parameters +// provided. Note that each function will call stb_perlin_noise3 +// 'octaves' times, so this parameter will affect runtime. +// +// float stb_perlin_ridge_noise3(float x, float y, float z, +// float lacunarity, float gain, float offset, int octaves) +// +// float stb_perlin_fbm_noise3(float x, float y, float z, +// float lacunarity, float gain, int octaves) +// +// float stb_perlin_turbulence_noise3(float x, float y, float z, +// float lacunarity, float gain, int octaves) +// +// Typical values to start playing with: +// octaves = 6 -- number of "octaves" of noise3() to sum +// lacunarity = ~ 2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) +// gain = 0.5 -- relative weighting applied to each successive octave +// offset = 1.0? -- used to invert the ridges, may need to be larger, not sure +// +// +// Contributors: +// Jack Mott - additional noise functions +// Jordan Peck - seeded noise +// + + +#ifdef __cplusplus +extern "C" { +#endif +extern float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap); +extern float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed); +extern float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves); +extern float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves); +extern float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves); +extern float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed); +#ifdef __cplusplus +} +#endif + +#ifdef STB_PERLIN_IMPLEMENTATION + +#include // fabs() + +// not same permutation table as Perlin's reference to avoid copyright issues; +// Perlin's table can be found at http://mrl.nyu.edu/~perlin/noise/ +static unsigned char stb__perlin_randtab[512] = +{ + 23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123, + 152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72, + 175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240, + 8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57, + 225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233, + 94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172, + 165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243, + 65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122, + 26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76, + 250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246, + 132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3, + 91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231, + 38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221, + 131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62, + 27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135, + 61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5, + + // and a second copy so we don't need an extra mask or static initializer + 23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123, + 152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72, + 175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240, + 8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57, + 225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233, + 94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172, + 165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243, + 65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122, + 26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76, + 250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246, + 132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3, + 91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231, + 38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221, + 131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62, + 27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135, + 61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5, +}; + + +// perlin's gradient has 12 cases so some get used 1/16th of the time +// and some 2/16ths. We reduce bias by changing those fractions +// to 5/64ths and 6/64ths + +// this array is designed to match the previous implementation +// of gradient hash: indices[stb__perlin_randtab[i]&63] +static unsigned char stb__perlin_randtab_grad_idx[512] = +{ + 7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7, + 8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8, + 7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8, + 8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5, + 5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1, + 2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4, + 9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11, + 1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6, + 10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0, + 6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2, + 4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3, + 11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11, + 10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1, + 3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10, + 11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7, + 9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5, + + // and a second copy so we don't need an extra mask or static initializer + 7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7, + 8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8, + 7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8, + 8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5, + 5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1, + 2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4, + 9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11, + 1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6, + 10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0, + 6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2, + 4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3, + 11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11, + 10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1, + 3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10, + 11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7, + 9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5, +}; + +static float stb__perlin_lerp(float a, float b, float t) +{ + return a + (b-a) * t; +} + +static int stb__perlin_fastfloor(float a) +{ + int ai = (int) a; + return (a < ai) ? ai-1 : ai; +} + +// different grad function from Perlin's, but easy to modify to match reference +static float stb__perlin_grad(int grad_idx, float x, float y, float z) +{ + static float basis[12][4] = + { + { 1, 1, 0 }, + { -1, 1, 0 }, + { 1,-1, 0 }, + { -1,-1, 0 }, + { 1, 0, 1 }, + { -1, 0, 1 }, + { 1, 0,-1 }, + { -1, 0,-1 }, + { 0, 1, 1 }, + { 0,-1, 1 }, + { 0, 1,-1 }, + { 0,-1,-1 }, + }; + + float *grad = basis[grad_idx]; + return grad[0]*x + grad[1]*y + grad[2]*z; +} + +float stb_perlin_noise3_internal(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed) +{ + float u,v,w; + float n000,n001,n010,n011,n100,n101,n110,n111; + float n00,n01,n10,n11; + float n0,n1; + + unsigned int x_mask = (x_wrap-1) & 255; + unsigned int y_mask = (y_wrap-1) & 255; + unsigned int z_mask = (z_wrap-1) & 255; + int px = stb__perlin_fastfloor(x); + int py = stb__perlin_fastfloor(y); + int pz = stb__perlin_fastfloor(z); + int x0 = px & x_mask, x1 = (px+1) & x_mask; + int y0 = py & y_mask, y1 = (py+1) & y_mask; + int z0 = pz & z_mask, z1 = (pz+1) & z_mask; + int r0,r1, r00,r01,r10,r11; + + #define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a) + + x -= px; u = stb__perlin_ease(x); + y -= py; v = stb__perlin_ease(y); + z -= pz; w = stb__perlin_ease(z); + + r0 = stb__perlin_randtab[x0+seed]; + r1 = stb__perlin_randtab[x1+seed]; + + r00 = stb__perlin_randtab[r0+y0]; + r01 = stb__perlin_randtab[r0+y1]; + r10 = stb__perlin_randtab[r1+y0]; + r11 = stb__perlin_randtab[r1+y1]; + + n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z ); + n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 ); + n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z ); + n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 ); + n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z ); + n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 ); + n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z ); + n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 ); + + n00 = stb__perlin_lerp(n000,n001,w); + n01 = stb__perlin_lerp(n010,n011,w); + n10 = stb__perlin_lerp(n100,n101,w); + n11 = stb__perlin_lerp(n110,n111,w); + + n0 = stb__perlin_lerp(n00,n01,v); + n1 = stb__perlin_lerp(n10,n11,v); + + return stb__perlin_lerp(n0,n1,u); +} + +float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap) +{ + return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap,0); +} + +float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed) +{ + return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap, (unsigned char) seed); +} + +float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves) +{ + int i; + float frequency = 1.0f; + float prev = 1.0f; + float amplitude = 0.5f; + float sum = 0.0f; + + for (i = 0; i < octaves; i++) { + float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i); + r = offset - (float) fabs(r); + r = r*r; + sum += r*amplitude*prev; + prev = r; + frequency *= lacunarity; + amplitude *= gain; + } + return sum; +} + +float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves) +{ + int i; + float frequency = 1.0f; + float amplitude = 1.0f; + float sum = 0.0f; + + for (i = 0; i < octaves; i++) { + sum += stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude; + frequency *= lacunarity; + amplitude *= gain; + } + return sum; +} + +float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves) +{ + int i; + float frequency = 1.0f; + float amplitude = 1.0f; + float sum = 0.0f; + + for (i = 0; i < octaves; i++) { + float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude; + sum += (float) fabs(r); + frequency *= lacunarity; + amplitude *= gain; + } + return sum; +} + +float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed) +{ + float u,v,w; + float n000,n001,n010,n011,n100,n101,n110,n111; + float n00,n01,n10,n11; + float n0,n1; + + int px = stb__perlin_fastfloor(x); + int py = stb__perlin_fastfloor(y); + int pz = stb__perlin_fastfloor(z); + int x_wrap2 = (x_wrap ? x_wrap : 256); + int y_wrap2 = (y_wrap ? y_wrap : 256); + int z_wrap2 = (z_wrap ? z_wrap : 256); + int x0 = px % x_wrap2, x1; + int y0 = py % y_wrap2, y1; + int z0 = pz % z_wrap2, z1; + int r0,r1, r00,r01,r10,r11; + + if (x0 < 0) x0 += x_wrap2; + if (y0 < 0) y0 += y_wrap2; + if (z0 < 0) z0 += z_wrap2; + x1 = (x0+1) % x_wrap2; + y1 = (y0+1) % y_wrap2; + z1 = (z0+1) % z_wrap2; + + #define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a) + + x -= px; u = stb__perlin_ease(x); + y -= py; v = stb__perlin_ease(y); + z -= pz; w = stb__perlin_ease(z); + + r0 = stb__perlin_randtab[x0]; + r0 = stb__perlin_randtab[r0+seed]; + r1 = stb__perlin_randtab[x1]; + r1 = stb__perlin_randtab[r1+seed]; + + r00 = stb__perlin_randtab[r0+y0]; + r01 = stb__perlin_randtab[r0+y1]; + r10 = stb__perlin_randtab[r1+y0]; + r11 = stb__perlin_randtab[r1+y1]; + + n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z ); + n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 ); + n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z ); + n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 ); + n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z ); + n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 ); + n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z ); + n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 ); + + n00 = stb__perlin_lerp(n000,n001,w); + n01 = stb__perlin_lerp(n010,n011,w); + n10 = stb__perlin_lerp(n100,n101,w); + n11 = stb__perlin_lerp(n110,n111,w); + + n0 = stb__perlin_lerp(n00,n01,v); + n1 = stb__perlin_lerp(n10,n11,v); + + return stb__perlin_lerp(n0,n1,u); +} +#endif // STB_PERLIN_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_rect_pack.h b/source/engine/thirdparty/stb/stb_rect_pack.h new file mode 100644 index 0000000..6a633ce --- /dev/null +++ b/source/engine/thirdparty/stb/stb_rect_pack.h @@ -0,0 +1,623 @@ +// stb_rect_pack.h - v1.01 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Before #including, +// +// #define STB_RECT_PACK_IMPLEMENTATION +// +// in the file that you want to have the implementation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// Fabian Giesen +// +// Version history: +// +// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section +// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles +// 0.99 (2019-02-07) warning fixes +// 0.11 (2017-03-03) return packing success/fail result +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +typedef int stbrp_coord; + +#define STBRP__MAXVAL 0x7fffffff +// Mostly for internal use, but this is the maximum supported coordinate value. + +STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#define STBRP__CDECL __cdecl +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#define STBRP__CDECL +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; + context->extra[1].y = (1<<30); + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + // if it can't possibly fit, bail immediately + if (width > c->width || height > c->height) { + fr.prev_link = NULL; + fr.x = fr.y = 0; + return fr; + } + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height <= c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count=0; + cur = context->active_head; + while (cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int STBRP__CDECL rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int STBRP__CDECL rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) + all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_sprintf.h b/source/engine/thirdparty/stb/stb_sprintf.h new file mode 100644 index 0000000..ca432a6 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_sprintf.h @@ -0,0 +1,1906 @@ +// stb_sprintf - v1.10 - public domain snprintf() implementation +// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 +// http://github.com/nothings/stb +// +// allowed types: sc uidBboXx p AaGgEef n +// lengths : hh h ll j z t I64 I32 I +// +// Contributors: +// Fabian "ryg" Giesen (reformatting) +// github:aganm (attribute format) +// +// Contributors (bugfixes): +// github:d26435 +// github:trex78 +// github:account-login +// Jari Komppa (SI suffixes) +// Rohit Nirmal +// Marcin Wojdyr +// Leonard Ritter +// Stefano Zanotti +// Adam Allison +// Arvid Gerstmann +// Markus Kolb +// +// LICENSE: +// +// See end of file for license information. + +#ifndef STB_SPRINTF_H_INCLUDE +#define STB_SPRINTF_H_INCLUDE + +/* +Single file sprintf replacement. + +Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. +Hereby placed in public domain. + +This is a full sprintf replacement that supports everything that +the C runtime sprintfs support, including float/double, 64-bit integers, +hex floats, field parameters (%*.*d stuff), length reads backs, etc. + +Why would you need this if sprintf already exists? Well, first off, +it's *much* faster (see below). It's also much smaller than the CRT +versions code-space-wise. We've also added some simple improvements +that are super handy (commas in thousands, callbacks at buffer full, +for example). Finally, the format strings for MSVC and GCC differ +for 64-bit integers (among other small things), so this lets you use +the same format strings in cross platform code. + +It uses the standard single file trick of being both the header file +and the source itself. If you just include it normally, you just get +the header file function definitions. To get the code, you include +it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. + +It only uses va_args macros from the C runtime to do it's work. It +does cast doubles to S64s and shifts and divides U64s, which does +drag in CRT code on most platforms. + +It compiles to roughly 8K with float support, and 4K without. +As a comparison, when using MSVC static libs, calling sprintf drags +in 16K. + +API: +==== +int stbsp_sprintf( char * buf, char const * fmt, ... ) +int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) + Convert an arg list into a buffer. stbsp_snprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) +int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) + Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) + typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); + Convert into a buffer, calling back every STB_SPRINTF_MIN chars. + Your callback can then copy the chars out, print them or whatever. + This function is actually the workhorse for everything else. + The buffer you pass in must hold at least STB_SPRINTF_MIN characters. + // you return the next buffer to use or 0 to stop converting + +void stbsp_set_separators( char comma, char period ) + Set the comma and period characters to use. + +FLOATS/DOUBLES: +=============== +This code uses a internal float->ascii conversion method that uses +doubles with error correction (double-doubles, for ~105 bits of +precision). This conversion is round-trip perfect - that is, an atof +of the values output here will give you the bit-exact double back. + +One difference is that our insignificant digits will be different than +with MSVC or GCC (but they don't match each other either). We also +don't attempt to find the minimum length matching float (pre-MSVC15 +doesn't either). + +If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT +and you'll save 4K of code space. + +64-BIT INTS: +============ +This library also supports 64-bit integers and you can use MSVC style or +GCC style indicators (%I64d or %lld). It supports the C99 specifiers +for size_t and ptr_diff_t (%jd %zd) as well. + +EXTRAS: +======= +Like some GCCs, for integers and floats, you can use a ' (single quote) +specifier and commas will be inserted on the thousands: "%'d" on 12345 +would print 12,345. + +For integers and floats, you can use a "$" specifier and the number +will be converted to float and then divided to get kilo, mega, giga or +tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is +"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn +2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three +$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the +suffix, add "_" specifier: "%_$d" -> "2.53M". + +In addition to octal and hexadecimal conversions, you can print +integers in binary: "%b" for 256 would print 100. + +PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): +=================================================================== +"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) +"%24d" across all 32-bit ints (4.5x/4.2x faster) +"%x" across all 32-bit ints (4.5x/3.8x faster) +"%08x" across all 32-bit ints (4.3x/3.8x faster) +"%f" across e-10 to e+10 floats (7.3x/6.0x faster) +"%e" across e-10 to e+10 floats (8.1x/6.0x faster) +"%g" across e-10 to e+10 floats (10.0x/7.1x faster) +"%f" for values near e-300 (7.9x/6.5x faster) +"%f" for values near e+300 (10.0x/9.1x faster) +"%e" for values near e-300 (10.1x/7.0x faster) +"%e" for values near e+300 (9.2x/6.0x faster) +"%.320f" for values near e-300 (12.6x/11.2x faster) +"%a" for random values (8.6x/4.3x faster) +"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) +"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) +"%s%s%s" for 64 char strings (7.1x/7.3x faster) +"...512 char string..." ( 35.0x/32.5x faster!) +*/ + +#if defined(__clang__) + #if defined(__has_feature) && defined(__has_attribute) + #if __has_feature(address_sanitizer) + #if __has_attribute(__no_sanitize__) + #define STBSP__ASAN __attribute__((__no_sanitize__("address"))) + #elif __has_attribute(__no_sanitize_address__) + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #elif __has_attribute(__no_address_safety_analysis__) + #define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) + #endif + #endif + #endif +#elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #endif +#endif + +#ifndef STBSP__ASAN +#define STBSP__ASAN +#endif + +#ifdef STB_SPRINTF_STATIC +#define STBSP__PUBLICDEC static +#define STBSP__PUBLICDEF static STBSP__ASAN +#else +#ifdef __cplusplus +#define STBSP__PUBLICDEC extern "C" +#define STBSP__PUBLICDEF extern "C" STBSP__ASAN +#else +#define STBSP__PUBLICDEC extern +#define STBSP__PUBLICDEF STBSP__ASAN +#endif +#endif + +#if defined(__has_attribute) + #if __has_attribute(format) + #define STBSP__ATTRIBUTE_FORMAT(fmt,va) __attribute__((format(printf,fmt,va))) + #endif +#endif + +#ifndef STBSP__ATTRIBUTE_FORMAT +#define STBSP__ATTRIBUTE_FORMAT(fmt,va) +#endif + +#ifdef _MSC_VER +#define STBSP__NOTUSED(v) (void)(v) +#else +#define STBSP__NOTUSED(v) (void)sizeof(v) +#endif + +#include // for va_arg(), va_list() +#include // size_t, ptrdiff_t + +#ifndef STB_SPRINTF_MIN +#define STB_SPRINTF_MIN 512 // how many characters per callback +#endif +typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); + +#ifndef STB_SPRINTF_DECORATE +#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names +#endif + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); + +#endif // STB_SPRINTF_H_INCLUDE + +#ifdef STB_SPRINTF_IMPLEMENTATION + +#define stbsp__uint32 unsigned int +#define stbsp__int32 signed int + +#ifdef _MSC_VER +#define stbsp__uint64 unsigned __int64 +#define stbsp__int64 signed __int64 +#else +#define stbsp__uint64 unsigned long long +#define stbsp__int64 signed long long +#endif +#define stbsp__uint16 unsigned short + +#ifndef stbsp__uintptr +#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) +#define stbsp__uintptr stbsp__uint64 +#else +#define stbsp__uintptr stbsp__uint32 +#endif +#endif + +#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define STB_SPRINTF_MSVC_MODE +#endif +#endif + +#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses +#define STBSP__UNALIGNED(code) +#else +#define STBSP__UNALIGNED(code) code +#endif + +#ifndef STB_SPRINTF_NOFLOAT +// internal float utility functions +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); +#define STBSP__SPECIAL 0x7000 +#endif + +static char stbsp__period = '.'; +static char stbsp__comma = ','; +static struct +{ + short temp; // force next field to be 2-byte aligned + char pair[201]; +} stbsp__digitpair = +{ + 0, + "00010203040506070809101112131415161718192021222324" + "25262728293031323334353637383940414243444546474849" + "50515253545556575859606162636465666768697071727374" + "75767778798081828384858687888990919293949596979899" +}; + +STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) +{ + stbsp__period = pperiod; + stbsp__comma = pcomma; +} + +#define STBSP__LEFTJUST 1 +#define STBSP__LEADINGPLUS 2 +#define STBSP__LEADINGSPACE 4 +#define STBSP__LEADING_0X 8 +#define STBSP__LEADINGZERO 16 +#define STBSP__INTMAX 32 +#define STBSP__TRIPLET_COMMA 64 +#define STBSP__NEGATIVE 128 +#define STBSP__METRIC_SUFFIX 256 +#define STBSP__HALFWIDTH 512 +#define STBSP__METRIC_NOSPACE 1024 +#define STBSP__METRIC_1024 2048 +#define STBSP__METRIC_JEDEC 4096 + +static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) +{ + sign[0] = 0; + if (fl & STBSP__NEGATIVE) { + sign[0] = 1; + sign[1] = '-'; + } else if (fl & STBSP__LEADINGSPACE) { + sign[0] = 1; + sign[1] = ' '; + } else if (fl & STBSP__LEADINGPLUS) { + sign[0] = 1; + sign[1] = '+'; + } +} + +static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit) +{ + char const * sn = s; + + // get up to 4-byte alignment + for (;;) { + if (((stbsp__uintptr)sn & 3) == 0) + break; + + if (!limit || *sn == 0) + return (stbsp__uint32)(sn - s); + + ++sn; + --limit; + } + + // scan over 4 bytes at a time to find terminating 0 + // this will intentionally scan up to 3 bytes past the end of buffers, + // but becase it works 4B aligned, it will never cross page boundaries + // (hence the STBSP__ASAN markup; the over-read here is intentional + // and harmless) + while (limit >= 4) { + stbsp__uint32 v = *(stbsp__uint32 *)sn; + // bit hack to find if there's a 0 byte in there + if ((v - 0x01010101) & (~v) & 0x80808080UL) + break; + + sn += 4; + limit -= 4; + } + + // handle the last few characters to find actual size + while (limit && *sn) { + ++sn; + --limit; + } + + return (stbsp__uint32)(sn - s); +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) +{ + static char hex[] = "0123456789abcdefxp"; + static char hexu[] = "0123456789ABCDEFXP"; + char *bf; + char const *f; + int tlen = 0; + + bf = buf; + f = fmt; + for (;;) { + stbsp__int32 fw, pr, tz; + stbsp__uint32 fl; + + // macros for the callback buffer stuff + #define stbsp__chk_cb_bufL(bytes) \ + { \ + int len = (int)(bf - buf); \ + if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ + tlen += len; \ + if (0 == (bf = buf = callback(buf, user, len))) \ + goto done; \ + } \ + } + #define stbsp__chk_cb_buf(bytes) \ + { \ + if (callback) { \ + stbsp__chk_cb_bufL(bytes); \ + } \ + } + #define stbsp__flush_cb() \ + { \ + stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ + } // flush if there is even one byte in the buffer + #define stbsp__cb_buf_clamp(cl, v) \ + cl = v; \ + if (callback) { \ + int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ + if (cl > lg) \ + cl = lg; \ + } + + // fast copy everything up to the next % (or end of string) + for (;;) { + while (((stbsp__uintptr)f) & 3) { + schk1: + if (f[0] == '%') + goto scandd; + schk2: + if (f[0] == 0) + goto endfmt; + stbsp__chk_cb_buf(1); + *bf++ = f[0]; + ++f; + } + for (;;) { + // Check if the next 4 bytes contain %(0x25) or end of string. + // Using the 'hasless' trick: + // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord + stbsp__uint32 v, c; + v = *(stbsp__uint32 *)f; + c = (~v) & 0x80808080; + if (((v ^ 0x25252525) - 0x01010101) & c) + goto schk1; + if ((v - 0x01010101) & c) + goto schk2; + if (callback) + if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) + goto schk1; + #ifdef STB_SPRINTF_NOUNALIGNED + if(((stbsp__uintptr)bf) & 3) { + bf[0] = f[0]; + bf[1] = f[1]; + bf[2] = f[2]; + bf[3] = f[3]; + } else + #endif + { + *(stbsp__uint32 *)bf = v; + } + bf += 4; + f += 4; + } + } + scandd: + + ++f; + + // ok, we have a percent, read the modifiers first + fw = 0; + pr = -1; + fl = 0; + tz = 0; + + // flags + for (;;) { + switch (f[0]) { + // if we have left justify + case '-': + fl |= STBSP__LEFTJUST; + ++f; + continue; + // if we have leading plus + case '+': + fl |= STBSP__LEADINGPLUS; + ++f; + continue; + // if we have leading space + case ' ': + fl |= STBSP__LEADINGSPACE; + ++f; + continue; + // if we have leading 0x + case '#': + fl |= STBSP__LEADING_0X; + ++f; + continue; + // if we have thousand commas + case '\'': + fl |= STBSP__TRIPLET_COMMA; + ++f; + continue; + // if we have kilo marker (none->kilo->kibi->jedec) + case '$': + if (fl & STBSP__METRIC_SUFFIX) { + if (fl & STBSP__METRIC_1024) { + fl |= STBSP__METRIC_JEDEC; + } else { + fl |= STBSP__METRIC_1024; + } + } else { + fl |= STBSP__METRIC_SUFFIX; + } + ++f; + continue; + // if we don't want space between metric suffix and number + case '_': + fl |= STBSP__METRIC_NOSPACE; + ++f; + continue; + // if we have leading zero + case '0': + fl |= STBSP__LEADINGZERO; + ++f; + goto flags_done; + default: goto flags_done; + } + } + flags_done: + + // get the field width + if (f[0] == '*') { + fw = va_arg(va, stbsp__uint32); + ++f; + } else { + while ((f[0] >= '0') && (f[0] <= '9')) { + fw = fw * 10 + f[0] - '0'; + f++; + } + } + // get the precision + if (f[0] == '.') { + ++f; + if (f[0] == '*') { + pr = va_arg(va, stbsp__uint32); + ++f; + } else { + pr = 0; + while ((f[0] >= '0') && (f[0] <= '9')) { + pr = pr * 10 + f[0] - '0'; + f++; + } + } + } + + // handle integer size overrides + switch (f[0]) { + // are we halfwidth? + case 'h': + fl |= STBSP__HALFWIDTH; + ++f; + if (f[0] == 'h') + ++f; // QUARTERWIDTH + break; + // are we 64-bit (unix style) + case 'l': + fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); + ++f; + if (f[0] == 'l') { + fl |= STBSP__INTMAX; + ++f; + } + break; + // are we 64-bit on intmax? (c99) + case 'j': + fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit on size_t or ptrdiff_t? (c99) + case 'z': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + case 't': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit (msft style) + case 'I': + if ((f[1] == '6') && (f[2] == '4')) { + fl |= STBSP__INTMAX; + f += 3; + } else if ((f[1] == '3') && (f[2] == '2')) { + f += 3; + } else { + fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); + ++f; + } + break; + default: break; + } + + // handle each replacement + switch (f[0]) { + #define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 + char num[STBSP__NUMSZ]; + char lead[8]; + char tail[8]; + char *s; + char const *h; + stbsp__uint32 l, n, cs; + stbsp__uint64 n64; +#ifndef STB_SPRINTF_NOFLOAT + double fv; +#endif + stbsp__int32 dp; + char const *sn; + + case 's': + // get the string + s = va_arg(va, char *); + if (s == 0) + s = (char *)"null"; + // get the length, limited to desired precision + // always limit to ~0u chars since our counts are 32b + l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + // copy the string in + goto scopy; + + case 'c': // char + // get the character + s = num + STBSP__NUMSZ - 1; + *s = (char)va_arg(va, int); + l = 1; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + + case 'n': // weird write-bytes specifier + { + int *d = va_arg(va, int *); + *d = tlen + (int)(bf - buf); + } break; + +#ifdef STB_SPRINTF_NOFLOAT + case 'A': // float + case 'a': // hex float + case 'G': // float + case 'g': // float + case 'E': // float + case 'e': // float + case 'f': // float + va_arg(va, double); // eat it + s = (char *)"No float"; + l = 8; + lead[0] = 0; + tail[0] = 0; + pr = 0; + cs = 0; + STBSP__NOTUSED(dp); + goto scopy; +#else + case 'A': // hex float + case 'a': // hex float + h = (f[0] == 'A') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) + fl |= STBSP__NEGATIVE; + + s = num + 64; + + stbsp__lead_sign(fl, lead); + + if (dp == -1023) + dp = (n64) ? -1022 : 0; + else + n64 |= (((stbsp__uint64)1) << 52); + n64 <<= (64 - 56); + if (pr < 15) + n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); +// add leading chars + +#ifdef STB_SPRINTF_MSVC_MODE + *s++ = '0'; + *s++ = 'x'; +#else + lead[1 + lead[0]] = '0'; + lead[2 + lead[0]] = 'x'; + lead[0] += 2; +#endif + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + if (pr) + *s++ = stbsp__period; + sn = s; + + // print the bits + n = pr; + if (n > 13) + n = 13; + if (pr > (stbsp__int32)n) + tz = pr - n; + pr = 0; + while (n--) { + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + } + + // print the expo + tail[1] = h[17]; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; + n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + + dp = (int)(s - sn); + l = (int)(s - (num + 64)); + s = num + 64; + cs = 1 + (3 << 24); + goto scopy; + + case 'G': // float + case 'g': // float + h = (f[0] == 'G') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; + else if (pr == 0) + pr = 1; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) + fl |= STBSP__NEGATIVE; + + // clamp the precision and delete extra zeros after clamp + n = pr; + if (l > (stbsp__uint32)pr) + l = pr; + while ((l > 1) && (pr) && (sn[l - 1] == '0')) { + --pr; + --l; + } + + // should we use %e + if ((dp <= -4) || (dp > (stbsp__int32)n)) { + if (pr > (stbsp__int32)l) + pr = l - 1; + else if (pr) + --pr; // when using %e, there is one digit before the decimal + goto doexpfromg; + } + // this is the insane action to get the pr to match %g semantics for %f + if (dp > 0) { + pr = (dp < (stbsp__int32)l) ? l - dp : 0; + } else { + pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); + } + goto dofloatfromg; + + case 'E': // float + case 'e': // float + h = (f[0] == 'E') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) + fl |= STBSP__NEGATIVE; + doexpfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + // handle leading chars + *s++ = sn[0]; + + if (pr) + *s++ = stbsp__period; + + // handle after decimal + if ((l - 1) > (stbsp__uint32)pr) + l = pr + 1; + for (n = 1; n < l; n++) + *s++ = sn[n]; + // trailing zeros + tz = pr - (l - 1); + pr = 0; + // dump expo + tail[1] = h[0xe]; + dp -= 1; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; +#ifdef STB_SPRINTF_MSVC_MODE + n = 5; +#else + n = (dp >= 100) ? 5 : 4; +#endif + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + cs = 1 + (3 << 24); // how many tens + goto flt_lead; + + case 'f': // float + fv = va_arg(va, double); + doafloat: + // do kilos + if (fl & STBSP__METRIC_SUFFIX) { + double divisor; + divisor = 1000.0f; + if (fl & STBSP__METRIC_1024) + divisor = 1024.0; + while (fl < 0x4000000) { + if ((fv < divisor) && (fv > -divisor)) + break; + fv /= divisor; + fl += 0x1000000; + } + } + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) + fl |= STBSP__NEGATIVE; + dofloatfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + + // handle the three decimal varieties + if (dp <= 0) { + stbsp__int32 i; + // handle 0.000*000xxxx + *s++ = '0'; + if (pr) + *s++ = stbsp__period; + n = -dp; + if ((stbsp__int32)n > pr) + n = pr; + i = n; + while (i) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + i -= 4; + } + while (i) { + *s++ = '0'; + --i; + } + if ((stbsp__int32)(l + n) > pr) + l = pr - n; + i = l; + while (i) { + *s++ = *sn++; + --i; + } + tz = pr - (n + l); + cs = 1 + (3 << 24); // how many tens did we write (for commas below) + } else { + cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; + if ((stbsp__uint32)dp >= l) { + // handle xxxx000*000.0 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= l) + break; + } + } + if (n < (stbsp__uint32)dp) { + n = dp - n; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (n) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --n; + } + while (n >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + n -= 4; + } + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = '0'; + --n; + } + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) { + *s++ = stbsp__period; + tz = pr; + } + } else { + // handle xxxxx.xxxx000*000 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= (stbsp__uint32)dp) + break; + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) + *s++ = stbsp__period; + if ((l - dp) > (stbsp__uint32)pr) + l = pr + dp; + while (n < l) { + *s++ = sn[n]; + ++n; + } + tz = pr - (l - dp); + } + } + pr = 0; + + // handle k,m,g,t + if (fl & STBSP__METRIC_SUFFIX) { + char idx; + idx = 1; + if (fl & STBSP__METRIC_NOSPACE) + idx = 0; + tail[0] = idx; + tail[1] = ' '; + { + if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. + if (fl & STBSP__METRIC_1024) + tail[idx + 1] = "_KMGT"[fl >> 24]; + else + tail[idx + 1] = "_kMGT"[fl >> 24]; + idx++; + // If printing kibits and not in jedec, add the 'i'. + if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { + tail[idx + 1] = 'i'; + idx++; + } + tail[0] = idx; + } + } + }; + + flt_lead: + // get the length that we copied + l = (stbsp__uint32)(s - (num + 64)); + s = num + 64; + goto scopy; +#endif + + case 'B': // upper binary + case 'b': // lower binary + h = (f[0] == 'B') ? hexu : hex; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[0xb]; + } + l = (8 << 4) | (1 << 8); + goto radixnum; + + case 'o': // octal + h = hexu; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 1; + lead[1] = '0'; + } + l = (3 << 4) | (3 << 8); + goto radixnum; + + case 'p': // pointer + fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; + pr = sizeof(void *) * 2; + fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros + // fall through - to X + + case 'X': // upper hex + case 'x': // lower hex + h = (f[0] == 'X') ? hexu : hex; + l = (4 << 4) | (4 << 8); + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[16]; + } + radixnum: + // get the number + if (fl & STBSP__INTMAX) + n64 = va_arg(va, stbsp__uint64); + else + n64 = va_arg(va, stbsp__uint32); + + s = num + STBSP__NUMSZ; + dp = 0; + // clear tail, and clear leading if value is zero + tail[0] = 0; + if (n64 == 0) { + lead[0] = 0; + if (pr == 0) { + l = 0; + cs = 0; + goto scopy; + } + } + // convert to string + for (;;) { + *--s = h[n64 & ((1 << (l >> 8)) - 1)]; + n64 >>= (l >> 8); + if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) + break; + if (fl & STBSP__TRIPLET_COMMA) { + ++l; + if ((l & 15) == ((l >> 4) & 15)) { + l &= ~15; + *--s = stbsp__comma; + } + } + }; + // get the tens and the comma pos + cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + // copy it + goto scopy; + + case 'u': // unsigned + case 'i': + case 'd': // integer + // get the integer and abs it + if (fl & STBSP__INTMAX) { + stbsp__int64 i64 = va_arg(va, stbsp__int64); + n64 = (stbsp__uint64)i64; + if ((f[0] != 'u') && (i64 < 0)) { + n64 = (stbsp__uint64)-i64; + fl |= STBSP__NEGATIVE; + } + } else { + stbsp__int32 i = va_arg(va, stbsp__int32); + n64 = (stbsp__uint32)i; + if ((f[0] != 'u') && (i < 0)) { + n64 = (stbsp__uint32)-i; + fl |= STBSP__NEGATIVE; + } + } + +#ifndef STB_SPRINTF_NOFLOAT + if (fl & STBSP__METRIC_SUFFIX) { + if (n64 < 1024) + pr = 0; + else if (pr == -1) + pr = 1; + fv = (double)(stbsp__int64)n64; + goto doafloat; + } +#endif + + // convert to string + s = num + STBSP__NUMSZ; + l = 0; + + for (;;) { + // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) + char *o = s - 8; + if (n64 >= 100000000) { + n = (stbsp__uint32)(n64 % 100000000); + n64 /= 100000000; + } else { + n = (stbsp__uint32)n64; + n64 = 0; + } + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + do { + s -= 2; + *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + } while (n); + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = (char)(n % 10) + '0'; + n /= 10; + } + } + if (n64 == 0) { + if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) + ++s; + break; + } + while (s != o) + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = '0'; + } + } + + tail[0] = 0; + stbsp__lead_sign(fl, lead); + + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + if (l == 0) { + *--s = '0'; + l = 1; + } + cs = l + (3 << 24); + if (pr < 0) + pr = 0; + + scopy: + // get fw=leading/trailing space, pr=leading zeros + if (pr < (stbsp__int32)l) + pr = l; + n = pr + lead[0] + tail[0] + tz; + if (fw < (stbsp__int32)n) + fw = n; + fw -= n; + pr -= l; + + // handle right justify and leading zeros + if ((fl & STBSP__LEFTJUST) == 0) { + if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr + { + pr = (fw > pr) ? fw : pr; + fw = 0; + } else { + fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas + } + } + + // copy the spaces and/or zeros + if (fw + pr) { + stbsp__int32 i; + stbsp__uint32 c; + + // copy leading spaces (or when doing %8.4d stuff) + if ((fl & STBSP__LEFTJUST) == 0) + while (fw > 0) { + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = ' '; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leader + sn = lead + 1; + while (lead[0]) { + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leading zeros + c = cs >> 24; + cs &= 0xffffff; + cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; + while (pr > 0) { + stbsp__cb_buf_clamp(i, pr); + pr -= i; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + } + while (i) { + if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { + cs = 0; + *bf++ = stbsp__comma; + } else + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + } + + // copy leader if there is still one + sn = lead + 1; + while (lead[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy the string + n = l; + while (n) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, n); + n -= i; + STBSP__UNALIGNED(while (i >= 4) { + *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; + bf += 4; + s += 4; + i -= 4; + }) + while (i) { + *bf++ = *s++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy trailing zeros + while (tz) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tz); + tz -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy tail if there is one + sn = tail + 1; + while (tail[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tail[0]); + tail[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // handle the left justify + if (fl & STBSP__LEFTJUST) + if (fw > 0) { + while (fw) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i--) + *bf++ = ' '; + stbsp__chk_cb_buf(1); + } + } + break; + + default: // unknown, just copy code + s = num + STBSP__NUMSZ - 1; + *s = f[0]; + l = 1; + fw = fl = 0; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + } + ++f; + } +endfmt: + + if (!callback) + *bf = 0; + else + stbsp__flush_cb(); + +done: + return tlen + (int)(bf - buf); +} + +// cleanup +#undef STBSP__LEFTJUST +#undef STBSP__LEADINGPLUS +#undef STBSP__LEADINGSPACE +#undef STBSP__LEADING_0X +#undef STBSP__LEADINGZERO +#undef STBSP__INTMAX +#undef STBSP__TRIPLET_COMMA +#undef STBSP__NEGATIVE +#undef STBSP__METRIC_SUFFIX +#undef STBSP__NUMSZ +#undef stbsp__chk_cb_bufL +#undef stbsp__chk_cb_buf +#undef stbsp__flush_cb +#undef stbsp__cb_buf_clamp + +// ============================================================================ +// wrapper functions + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); + va_end(va); + return result; +} + +typedef struct stbsp__context { + char *buf; + int count; + int length; + char tmp[STB_SPRINTF_MIN]; +} stbsp__context; + +static char *stbsp__clamp_callback(const char *buf, void *user, int len) +{ + stbsp__context *c = (stbsp__context *)user; + c->length += len; + + if (len > c->count) + len = c->count; + + if (len) { + if (buf != c->buf) { + const char *s, *se; + char *d; + d = c->buf; + s = buf; + se = buf + len; + do { + *d++ = *s++; + } while (s < se); + } + c->buf += len; + c->count -= len; + } + + if (c->count <= 0) + return c->tmp; + return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can +} + +static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) +{ + stbsp__context * c = (stbsp__context*)user; + (void) sizeof(buf); + + c->length += len; + return c->tmp; // go direct into buffer if you can +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) +{ + stbsp__context c; + + if ( (count == 0) && !buf ) + { + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); + } + else + { + int l; + + c.buf = buf; + c.count = count; + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); + + // zero-terminate + l = (int)( c.buf - buf ); + if ( l >= count ) // should never be greater, only equal (or less) than count + l = count - 1; + buf[l] = 0; + } + + return c.length; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + + result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); + va_end(va); + + return result; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) +{ + return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); +} + +// ======================================================================= +// low level float utility functions + +#ifndef STB_SPRINTF_NOFLOAT + +// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) +#define STBSP__COPYFP(dest, src) \ + { \ + int cn; \ + for (cn = 0; cn < 8; cn++) \ + ((char *)&dest)[cn] = ((char *)&src)[cn]; \ + } + +// get float info +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) +{ + double d; + stbsp__int64 b = 0; + + // load value and round at the frac_digits + d = value; + + STBSP__COPYFP(b, d); + + *bits = b & ((((stbsp__uint64)1) << 52) - 1); + *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); + + return (stbsp__int32)((stbsp__uint64) b >> 63); +} + +static double const stbsp__bot[23] = { + 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, + 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 +}; +static double const stbsp__negbot[22] = { + 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, + 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 +}; +static double const stbsp__negboterr[22] = { + -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, + 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, + -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, + 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 +}; +static double const stbsp__top[13] = { + 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 +}; +static double const stbsp__negtop[13] = { + 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 +}; +static double const stbsp__toperr[13] = { + 8388608, + 6.8601809640529717e+028, + -7.253143638152921e+052, + -4.3377296974619174e+075, + -1.5559416129466825e+098, + -3.2841562489204913e+121, + -3.7745893248228135e+144, + -1.7356668416969134e+167, + -3.8893577551088374e+190, + -9.9566444326005119e+213, + 6.3641293062232429e+236, + -5.2069140800249813e+259, + -5.2504760255204387e+282 +}; +static double const stbsp__negtoperr[13] = { + 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, + -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, + 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, + 8.0970921678014997e-317 +}; + +#if defined(_MSC_VER) && (_MSC_VER <= 1200) +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000U +}; +#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) +#else +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000ULL, + 100000000000ULL, + 1000000000000ULL, + 10000000000000ULL, + 100000000000000ULL, + 1000000000000000ULL, + 10000000000000000ULL, + 100000000000000000ULL, + 1000000000000000000ULL, + 10000000000000000000ULL +}; +#define stbsp__tento19th (1000000000000000000ULL) +#endif + +#define stbsp__ddmulthi(oh, ol, xh, yh) \ + { \ + double ahi = 0, alo, bhi = 0, blo; \ + stbsp__int64 bt; \ + oh = xh * yh; \ + STBSP__COPYFP(bt, xh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(ahi, bt); \ + alo = xh - ahi; \ + STBSP__COPYFP(bt, yh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(bhi, bt); \ + blo = yh - bhi; \ + ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ + } + +#define stbsp__ddtoS64(ob, xh, xl) \ + { \ + double ahi = 0, alo, vh, t; \ + ob = (stbsp__int64)xh; \ + vh = (double)ob; \ + ahi = (xh - vh); \ + t = (ahi - xh); \ + alo = (xh - (ahi - t)) - (vh + t); \ + ob += (stbsp__int64)(ahi + alo + xl); \ + } + +#define stbsp__ddrenorm(oh, ol) \ + { \ + double s; \ + s = oh + ol; \ + ol = ol - (s - oh); \ + oh = s; \ + } + +#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); + +#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); + +static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 +{ + double ph, pl; + if ((power >= 0) && (power <= 22)) { + stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); + } else { + stbsp__int32 e, et, eb; + double p2h, p2l; + + e = power; + if (power < 0) + e = -e; + et = (e * 0x2c9) >> 14; /* %23 */ + if (et > 13) + et = 13; + eb = e - (et * 23); + + ph = d; + pl = 0.0; + if (power < 0) { + if (eb) { + --eb; + stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); + stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); + ph = p2h; + pl = p2l; + } + } else { + if (eb) { + e = eb; + if (eb > 22) + eb = 22; + e -= eb; + stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); + if (e) { + stbsp__ddrenorm(ph, pl); + stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); + stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); + ph = p2h; + pl = p2l; + } + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); + ph = p2h; + pl = p2l; + } + } + } + stbsp__ddrenorm(ph, pl); + *ohi = ph; + *olo = pl; +} + +// given a float value, returns the significant bits in bits, and the position of the +// decimal point in decimal_pos. +/-INF and NAN are specified by special values +// returned in the decimal_pos parameter. +// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) +{ + double d; + stbsp__int64 bits = 0; + stbsp__int32 expo, e, ng, tens; + + d = value; + STBSP__COPYFP(bits, d); + expo = (stbsp__int32)((bits >> 52) & 2047); + ng = (stbsp__int32)((stbsp__uint64) bits >> 63); + if (ng) + d = -d; + + if (expo == 2047) // is nan or inf? + { + *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; + *decimal_pos = STBSP__SPECIAL; + *len = 3; + return ng; + } + + if (expo == 0) // is zero or denormal + { + if (((stbsp__uint64) bits << 1) == 0) // do zero + { + *decimal_pos = 1; + *start = out; + out[0] = '0'; + *len = 1; + return ng; + } + // find the right expo for denormals + { + stbsp__int64 v = ((stbsp__uint64)1) << 51; + while ((bits & v) == 0) { + --expo; + v >>= 1; + } + } + } + + // find the decimal exponent as well as the decimal bits of the value + { + double ph, pl; + + // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 + tens = expo - 1023; + tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); + + // move the significant bits into position and stick them into an int + stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); + + // get full as much precision from double-double as possible + stbsp__ddtoS64(bits, ph, pl); + + // check if we undershot + if (((stbsp__uint64)bits) >= stbsp__tento19th) + ++tens; + } + + // now do the rounding in integer land + frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); + if ((frac_digits < 24)) { + stbsp__uint32 dg = 1; + if ((stbsp__uint64)bits >= stbsp__powten[9]) + dg = 10; + while ((stbsp__uint64)bits >= stbsp__powten[dg]) { + ++dg; + if (dg == 20) + goto noround; + } + if (frac_digits < dg) { + stbsp__uint64 r; + // add 0.5 at the right position and round + e = dg - frac_digits; + if ((stbsp__uint32)e >= 24) + goto noround; + r = stbsp__powten[e]; + bits = bits + (r / 2); + if ((stbsp__uint64)bits >= stbsp__powten[dg]) + ++tens; + bits /= r; + } + noround:; + } + + // kill long trailing runs of zeros + if (bits) { + stbsp__uint32 n; + for (;;) { + if (bits <= 0xffffffff) + break; + if (bits % 1000) + goto donez; + bits /= 1000; + } + n = (stbsp__uint32)bits; + while ((n % 1000) == 0) + n /= 1000; + bits = n; + donez:; + } + + // convert to string + out += 64; + e = 0; + for (;;) { + stbsp__uint32 n; + char *o = out - 8; + // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) + if (bits >= 100000000) { + n = (stbsp__uint32)(bits % 100000000); + bits /= 100000000; + } else { + n = (stbsp__uint32)bits; + bits = 0; + } + while (n) { + out -= 2; + *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + e += 2; + } + if (bits == 0) { + if ((e) && (out[0] == '0')) { + ++out; + --e; + } + break; + } + while (out != o) { + *--out = '0'; + ++e; + } + } + + *decimal_pos = tens; + *start = out; + *len = e; + return ng; +} + +#undef stbsp__ddmulthi +#undef stbsp__ddrenorm +#undef stbsp__ddmultlo +#undef stbsp__ddmultlos +#undef STBSP__SPECIAL +#undef STBSP__COPYFP + +#endif // STB_SPRINTF_NOFLOAT + +// clean up +#undef stbsp__uint16 +#undef stbsp__uint32 +#undef stbsp__int32 +#undef stbsp__uint64 +#undef stbsp__int64 +#undef STBSP__UNALIGNED + +#endif // STB_SPRINTF_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_textedit.h b/source/engine/thirdparty/stb/stb_textedit.h new file mode 100644 index 0000000..1442493 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_textedit.h @@ -0,0 +1,1429 @@ +// stb_textedit.h - v1.14 - public domain - Sean Barrett +// Development of this library was sponsored by RAD Game Tools +// +// This C header file implements the guts of a multi-line text-editing +// widget; you implement display, word-wrapping, and low-level string +// insertion/deletion, and stb_textedit will map user inputs into +// insertions & deletions, plus updates to the cursor position, +// selection state, and undo state. +// +// It is intended for use in games and other systems that need to build +// their own custom widgets and which do not have heavy text-editing +// requirements (this library is not recommended for use for editing large +// texts, as its performance does not scale and it has limited undo). +// +// Non-trivial behaviors are modelled after Windows text controls. +// +// +// LICENSE +// +// See end of file for license information. +// +// +// DEPENDENCIES +// +// Uses the C runtime function 'memmove', which you can override +// by defining STB_TEXTEDIT_memmove before the implementation. +// Uses no other functions. Performs no runtime allocations. +// +// +// VERSION HISTORY +// +// 1.14 (2021-07-11) page up/down, various fixes +// 1.13 (2019-02-07) fix bug in undo size management +// 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash +// 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield +// 1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual +// 1.9 (2016-08-27) customizable move-by-word +// 1.8 (2016-04-02) better keyboard handling when mouse button is down +// 1.7 (2015-09-13) change y range handling in case baseline is non-0 +// 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove +// 1.5 (2014-09-10) add support for secondary keys for OS X +// 1.4 (2014-08-17) fix signed/unsigned warnings +// 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary +// 1.2 (2014-05-27) fix some RAD types that had crept into the new code +// 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) +// 1.0 (2012-07-26) improve documentation, initial public release +// 0.3 (2012-02-24) bugfixes, single-line mode; insert mode +// 0.2 (2011-11-28) fixes to undo/redo +// 0.1 (2010-07-08) initial version +// +// ADDITIONAL CONTRIBUTORS +// +// Ulf Winklemann: move-by-word in 1.1 +// Fabian Giesen: secondary key inputs in 1.5 +// Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6 +// Louis Schnellbach: page up/down in 1.14 +// +// Bugfixes: +// Scott Graham +// Daniel Keller +// Omar Cornut +// Dan Thompson +// +// USAGE +// +// This file behaves differently depending on what symbols you define +// before including it. +// +// +// Header-file mode: +// +// If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, +// it will operate in "header file" mode. In this mode, it declares a +// single public symbol, STB_TexteditState, which encapsulates the current +// state of a text widget (except for the string, which you will store +// separately). +// +// To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a +// primitive type that defines a single character (e.g. char, wchar_t, etc). +// +// To save space or increase undo-ability, you can optionally define the +// following things that are used by the undo system: +// +// STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position +// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow +// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer +// +// If you don't define these, they are set to permissive types and +// moderate sizes. The undo system does no memory allocations, so +// it grows STB_TexteditState by the worst-case storage which is (in bytes): +// +// [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATECOUNT +// + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHARCOUNT +// +// +// Implementation mode: +// +// If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it +// will compile the implementation of the text edit widget, depending +// on a large number of symbols which must be defined before the include. +// +// The implementation is defined only as static functions. You will then +// need to provide your own APIs in the same file which will access the +// static functions. +// +// The basic concept is that you provide a "string" object which +// behaves like an array of characters. stb_textedit uses indices to +// refer to positions in the string, implicitly representing positions +// in the displayed textedit. This is true for both plain text and +// rich text; even with rich text stb_truetype interacts with your +// code as if there was an array of all the displayed characters. +// +// Symbols that must be the same in header-file and implementation mode: +// +// STB_TEXTEDIT_CHARTYPE the character type +// STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor position +// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow +// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer +// +// Symbols you must define for implementation mode: +// +// STB_TEXTEDIT_STRING the type of object representing a string being edited, +// typically this is a wrapper object with other data you need +// +// STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) +// STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters +// starting from character #n (see discussion below) +// STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character +// to the xpos of the i+1'th char for a line of characters +// starting at character #n (i.e. accounts for kerning +// with previous char) +// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character +// (return type is int, -1 means not valid to insert) +// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based +// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize +// as manually wordwrapping for end-of-line positioning +// +// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i +// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) +// +// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key +// +// STB_TEXTEDIT_K_LEFT keyboard input to move cursor left +// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right +// STB_TEXTEDIT_K_UP keyboard input to move cursor up +// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down +// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page +// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page +// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME +// STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END +// STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME +// STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END +// STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor +// STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor +// STB_TEXTEDIT_K_UNDO keyboard input to perform undo +// STB_TEXTEDIT_K_REDO keyboard input to perform redo +// +// Optional: +// STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode +// STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), +// required for default WORDLEFT/WORDRIGHT handlers +// STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to +// STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to +// STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT +// STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT +// STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line +// STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line +// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text +// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text +// +// Keyboard input must be encoded as a single integer value; e.g. a character code +// and some bitflags that represent shift states. to simplify the interface, SHIFT must +// be a bitflag, so we can test the shifted state of cursor movements to allow selection, +// i.e. (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. +// +// You can encode other things, such as CONTROL or ALT, in additional bits, and +// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, +// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN +// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit, +// and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the +// API below. The control keys will only match WM_KEYDOWN events because of the +// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN +// bit so it only decodes WM_CHAR events. +// +// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed +// row of characters assuming they start on the i'th character--the width and +// the height and the number of characters consumed. This allows this library +// to traverse the entire layout incrementally. You need to compute word-wrapping +// here. +// +// Each textfield keeps its own insert mode state, which is not how normal +// applications work. To keep an app-wide insert mode, update/copy the +// "insert_mode" field of STB_TexteditState before/after calling API functions. +// +// API +// +// void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) +// +// void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +// void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +// int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +// int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) +// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key) +// +// Each of these functions potentially updates the string and updates the +// state. +// +// initialize_state: +// set the textedit state to a known good default state when initially +// constructing the textedit. +// +// click: +// call this with the mouse x,y on a mouse down; it will update the cursor +// and reset the selection start/end to the cursor point. the x,y must +// be relative to the text widget, with (0,0) being the top left. +// +// drag: +// call this with the mouse x,y on a mouse drag/up; it will update the +// cursor and the selection end point +// +// cut: +// call this to delete the current selection; returns true if there was +// one. you should FIRST copy the current selection to the system paste buffer. +// (To copy, just copy the current selection out of the string yourself.) +// +// paste: +// call this to paste text at the current cursor point or over the current +// selection if there is one. +// +// key: +// call this for keyboard inputs sent to the textfield. you can use it +// for "key down" events or for "translated" key events. if you need to +// do both (as in Win32), or distinguish Unicode characters from control +// inputs, set a high bit to distinguish the two; then you can define the +// various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit +// set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is +// clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to +// anything other type you wante before including. +// +// +// When rendering, you can read the cursor position and selection state from +// the STB_TexteditState. +// +// +// Notes: +// +// This is designed to be usable in IMGUI, so it allows for the possibility of +// running in an IMGUI that has NOT cached the multi-line layout. For this +// reason, it provides an interface that is compatible with computing the +// layout incrementally--we try to make sure we make as few passes through +// as possible. (For example, to locate the mouse pointer in the text, we +// could define functions that return the X and Y positions of characters +// and binary search Y and then X, but if we're doing dynamic layout this +// will run the layout algorithm many times, so instead we manually search +// forward in one pass. Similar logic applies to e.g. up-arrow and +// down-arrow movement.) +// +// If it's run in a widget that *has* cached the layout, then this is less +// efficient, but it's not horrible on modern computers. But you wouldn't +// want to edit million-line files with it. + + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Header-file mode +//// +//// + +#ifndef INCLUDE_STB_TEXTEDIT_H +#define INCLUDE_STB_TEXTEDIT_H + +//////////////////////////////////////////////////////////////////////// +// +// STB_TexteditState +// +// Definition of STB_TexteditState which you should store +// per-textfield; it includes cursor position, selection state, +// and undo state. +// + +#ifndef STB_TEXTEDIT_UNDOSTATECOUNT +#define STB_TEXTEDIT_UNDOSTATECOUNT 99 +#endif +#ifndef STB_TEXTEDIT_UNDOCHARCOUNT +#define STB_TEXTEDIT_UNDOCHARCOUNT 999 +#endif +#ifndef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_CHARTYPE int +#endif +#ifndef STB_TEXTEDIT_POSITIONTYPE +#define STB_TEXTEDIT_POSITIONTYPE int +#endif + +typedef struct +{ + // private data + STB_TEXTEDIT_POSITIONTYPE where; + STB_TEXTEDIT_POSITIONTYPE insert_length; + STB_TEXTEDIT_POSITIONTYPE delete_length; + int char_storage; +} StbUndoRecord; + +typedef struct +{ + // private data + StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT]; + STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; + short undo_point, redo_point; + int undo_char_point, redo_char_point; +} StbUndoState; + +typedef struct +{ + ///////////////////// + // + // public data + // + + int cursor; + // position of the text cursor within the string + + int select_start; // selection start point + int select_end; + // selection start and end point in characters; if equal, no selection. + // note that start may be less than or greater than end (e.g. when + // dragging the mouse, start is where the initial click was, and you + // can drag in either direction) + + unsigned char insert_mode; + // each textfield keeps its own insert mode state. to keep an app-wide + // insert mode, copy this value in/out of the app state + + int row_count_per_page; + // page size in number of row. + // this value MUST be set to >0 for pageup or pagedown in multilines documents. + + ///////////////////// + // + // private data + // + unsigned char cursor_at_end_of_line; // not implemented yet + unsigned char initialized; + unsigned char has_preferred_x; + unsigned char single_line; + unsigned char padding1, padding2, padding3; + float preferred_x; // this determines where the cursor up/down tries to seek to along x + StbUndoState undostate; +} STB_TexteditState; + + +//////////////////////////////////////////////////////////////////////// +// +// StbTexteditRow +// +// Result of layout query, used by stb_textedit to determine where +// the text in each row is. + +// result of layout query +typedef struct +{ + float x0,x1; // starting x location, end x location (allows for align=right, etc) + float baseline_y_delta; // position of baseline relative to previous row's baseline + float ymin,ymax; // height of row above and below baseline + int num_chars; +} StbTexteditRow; +#endif //INCLUDE_STB_TEXTEDIT_H + + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Implementation mode +//// +//// + + +// implementation isn't include-guarded, since it might have indirectly +// included just the "header" portion +#ifdef STB_TEXTEDIT_IMPLEMENTATION + +#ifndef STB_TEXTEDIT_memmove +#include +#define STB_TEXTEDIT_memmove memmove +#endif + + +///////////////////////////////////////////////////////////////////////////// +// +// Mouse input handling +// + +// traverse the layout to locate the nearest character to a display position +static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y) +{ + StbTexteditRow r; + int n = STB_TEXTEDIT_STRINGLEN(str); + float base_y = 0, prev_x; + int i=0, k; + + r.x0 = r.x1 = 0; + r.ymin = r.ymax = 0; + r.num_chars = 0; + + // search rows to find one that straddles 'y' + while (i < n) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (r.num_chars <= 0) + return n; + + if (i==0 && y < base_y + r.ymin) + return 0; + + if (y < base_y + r.ymax) + break; + + i += r.num_chars; + base_y += r.baseline_y_delta; + } + + // below all text, return 'after' last character + if (i >= n) + return n; + + // check if it's before the beginning of the line + if (x < r.x0) + return i; + + // check if it's before the end of the line + if (x < r.x1) { + // search characters in row for one that straddles 'x' + prev_x = r.x0; + for (k=0; k < r.num_chars; ++k) { + float w = STB_TEXTEDIT_GETWIDTH(str, i, k); + if (x < prev_x+w) { + if (x < prev_x+w/2) + return k+i; + else + return k+i+1; + } + prev_x += w; + } + // shouldn't happen, but if it does, fall through to end-of-line case + } + + // if the last character is a newline, return that. otherwise return 'after' the last character + if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) + return i+r.num_chars-1; + else + return i+r.num_chars; +} + +// API click: on mouse down, move the cursor to the clicked location, and reset the selection +static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +{ + // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse + // goes off the top or bottom of the text + if( state->single_line ) + { + StbTexteditRow r; + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + y = r.ymin; + } + + state->cursor = stb_text_locate_coord(str, x, y); + state->select_start = state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; +} + +// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location +static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) +{ + int p = 0; + + // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse + // goes off the top or bottom of the text + if( state->single_line ) + { + StbTexteditRow r; + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + y = r.ymin; + } + + if (state->select_start == state->select_end) + state->select_start = state->cursor; + + p = stb_text_locate_coord(str, x, y); + state->cursor = state->select_end = p; +} + +///////////////////////////////////////////////////////////////////////////// +// +// Keyboard input handling +// + +// forward declarations +static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); +static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); +static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); + +typedef struct +{ + float x,y; // position of n'th character + float height; // height of line + int first_char, length; // first char of row, and length + int prev_first; // first char of previous row +} StbFindState; + +// find the x/y location of a character, and remember info about the previous row in +// case we get a move-up event (for page up, we'll have to rescan) +static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line) +{ + StbTexteditRow r; + int prev_start = 0; + int z = STB_TEXTEDIT_STRINGLEN(str); + int i=0, first; + + if (n == z) { + // if it's at the end, then find the last line -- simpler than trying to + // explicitly handle this case in the regular code + if (single_line) { + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + find->y = 0; + find->first_char = 0; + find->length = z; + find->height = r.ymax - r.ymin; + find->x = r.x1; + } else { + find->y = 0; + find->x = 0; + find->height = 1; + while (i < z) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + prev_start = i; + i += r.num_chars; + } + find->first_char = i; + find->length = 0; + find->prev_first = prev_start; + } + return; + } + + // search rows to find the one that straddles character n + find->y = 0; + + for(;;) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (n < i + r.num_chars) + break; + prev_start = i; + i += r.num_chars; + find->y += r.baseline_y_delta; + } + + find->first_char = first = i; + find->length = r.num_chars; + find->height = r.ymax - r.ymin; + find->prev_first = prev_start; + + // now scan to find xpos + find->x = r.x0; + for (i=0; first+i < n; ++i) + find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); +} + +#define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) + +// make the selection/cursor state valid if client altered the string +static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + int n = STB_TEXTEDIT_STRINGLEN(str); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start > n) state->select_start = n; + if (state->select_end > n) state->select_end = n; + // if clamping forced them to be equal, move the cursor to match + if (state->select_start == state->select_end) + state->cursor = state->select_start; + } + if (state->cursor > n) state->cursor = n; +} + +// delete characters while updating undo +static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) +{ + stb_text_makeundo_delete(str, state, where, len); + STB_TEXTEDIT_DELETECHARS(str, where, len); + state->has_preferred_x = 0; +} + +// delete the section +static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + stb_textedit_clamp(str, state); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start < state->select_end) { + stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); + state->select_end = state->cursor = state->select_start; + } else { + stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); + state->select_start = state->cursor = state->select_end; + } + state->has_preferred_x = 0; + } +} + +// canoncialize the selection so start <= end +static void stb_textedit_sortselection(STB_TexteditState *state) +{ + if (state->select_end < state->select_start) { + int temp = state->select_end; + state->select_end = state->select_start; + state->select_start = temp; + } +} + +// move cursor to first character of selection +static void stb_textedit_move_to_first(STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + state->cursor = state->select_start; + state->select_end = state->select_start; + state->has_preferred_x = 0; + } +} + +// move cursor to last character of selection +static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->select_start = state->select_end; + state->has_preferred_x = 0; + } +} + +#ifdef STB_TEXTEDIT_IS_SPACE +static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx ) +{ + return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; +} + +#ifndef STB_TEXTEDIT_MOVEWORDLEFT +static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c ) +{ + --c; // always move at least one character + while( c >= 0 && !is_word_boundary( str, c ) ) + --c; + + if( c < 0 ) + c = 0; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous +#endif + +#ifndef STB_TEXTEDIT_MOVEWORDRIGHT +static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c ) +{ + const int len = STB_TEXTEDIT_STRINGLEN(str); + ++c; // always move at least one character + while( c < len && !is_word_boundary( str, c ) ) + ++c; + + if( c > len ) + c = len; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next +#endif + +#endif + +// update selection and cursor to match each other +static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) +{ + if (!STB_TEXT_HAS_SELECTION(state)) + state->select_start = state->select_end = state->cursor; + else + state->cursor = state->select_end; +} + +// API cut: delete selection +static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_delete_selection(str,state); // implicitly clamps + state->has_preferred_x = 0; + return 1; + } + return 0; +} + +// API paste: replace existing selection with passed-in text +static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) +{ + // if there's a selection, the paste should delete it + stb_textedit_clamp(str, state); + stb_textedit_delete_selection(str,state); + // try to insert the characters + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { + stb_text_makeundo_insert(state, state->cursor, len); + state->cursor += len; + state->has_preferred_x = 0; + return 1; + } + // note: paste failure will leave deleted selection, may be restored with an undo (see https://github.com/nothings/stb/issues/734 for details) + return 0; +} + +#ifndef STB_TEXTEDIT_KEYTYPE +#define STB_TEXTEDIT_KEYTYPE int +#endif + +// API key: process a keyboard input +static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key) +{ +retry: + switch (key) { + default: { + int c = STB_TEXTEDIT_KEYTOTEXT(key); + if (c > 0) { + STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c; + + // can't add newline in single-line mode + if (c == '\n' && state->single_line) + break; + + if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { + stb_text_makeundo_replace(str, state, state->cursor, 1, 1); + STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + ++state->cursor; + state->has_preferred_x = 0; + } + } else { + stb_textedit_delete_selection(str,state); // implicitly clamps + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + stb_text_makeundo_insert(state, state->cursor, 1); + ++state->cursor; + state->has_preferred_x = 0; + } + } + } + break; + } + +#ifdef STB_TEXTEDIT_K_INSERT + case STB_TEXTEDIT_K_INSERT: + state->insert_mode = !state->insert_mode; + break; +#endif + + case STB_TEXTEDIT_K_UNDO: + stb_text_undo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_REDO: + stb_text_redo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT: + // if currently there's a selection, move cursor to start of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else + if (state->cursor > 0) + --state->cursor; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_RIGHT: + // if currently there's a selection, move cursor to end of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else + ++state->cursor; + stb_textedit_clamp(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + // move selection left + if (state->select_end > 0) + --state->select_end; + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_MOVEWORDLEFT + case STB_TEXTEDIT_K_WORDLEFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + stb_textedit_clamp( str, state ); + } + break; + + case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: + if( !STB_TEXT_HAS_SELECTION( state ) ) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp( str, state ); + break; +#endif + +#ifdef STB_TEXTEDIT_MOVEWORDRIGHT + case STB_TEXTEDIT_K_WORDRIGHT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + stb_textedit_clamp( str, state ); + } + break; + + case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: + if( !STB_TEXT_HAS_SELECTION( state ) ) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp( str, state ); + break; +#endif + + case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + // move selection right + ++state->select_end; + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_DOWN: + case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: + case STB_TEXTEDIT_K_PGDOWN: + case STB_TEXTEDIT_K_PGDOWN | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, j, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGDOWN; + int row_count = is_page ? state->row_count_per_page : 1; + + if (!is_page && state->single_line) { + // on windows, up&down in single-line behave like left&right + key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + for (j = 0; j < row_count; ++j) { + float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; + int start = find.first_char + find.length; + + if (find.length == 0) + break; + + // now find character position down a row + state->cursor = start; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i=0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); + #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) + break; + #endif + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) + state->select_end = state->cursor; + + // go to next line + find.first_char = find.first_char + find.length; + find.length = row.num_chars; + } + break; + } + + case STB_TEXTEDIT_K_UP: + case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: + case STB_TEXTEDIT_K_PGUP: + case STB_TEXTEDIT_K_PGUP | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, j, prev_scan, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGUP; + int row_count = is_page ? state->row_count_per_page : 1; + + if (!is_page && state->single_line) { + // on windows, up&down become left&right + key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + for (j = 0; j < row_count; ++j) { + float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; + + // can only go up if there's a previous row + if (find.prev_first == find.first_char) + break; + + // now find character position up a row + state->cursor = find.prev_first; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i=0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); + #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) + break; + #endif + x += dx; + if (x > goal_x) + break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) + state->select_end = state->cursor; + + // go to previous line + // (we need to scan previous line the hard way. maybe we could expose this as a new API function?) + prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0; + while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE) + --prev_scan; + find.first_char = find.prev_first; + find.prev_first = prev_scan; + } + break; + } + + case STB_TEXTEDIT_K_DELETE: + case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + int n = STB_TEXTEDIT_STRINGLEN(str); + if (state->cursor < n) + stb_textedit_delete(str, state, state->cursor, 1); + } + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_BACKSPACE: + case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + stb_textedit_clamp(str, state); + if (state->cursor > 0) { + stb_textedit_delete(str, state, state->cursor-1, 1); + --state->cursor; + } + } + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2: +#endif + case STB_TEXTEDIT_K_TEXTSTART: + state->cursor = state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2: +#endif + case STB_TEXTEDIT_K_TEXTEND: + state->cursor = STB_TEXTEDIT_STRINGLEN(str); + state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); + state->has_preferred_x = 0; + break; + + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2: +#endif + case STB_TEXTEDIT_K_LINESTART: + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = 0; + else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2: +#endif + case STB_TEXTEDIT_K_LINEEND: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = n; + else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->has_preferred_x = 0; + break; + } + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = 0; + else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = n; + else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Undo processing +// +// @OPTIMIZE: the undo/redo buffer should be circular + +static void stb_textedit_flush_redo(StbUndoState *state) +{ + state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; +} + +// discard the oldest entry in the undo list +static void stb_textedit_discard_undo(StbUndoState *state) +{ + if (state->undo_point > 0) { + // if the 0th undo state has characters, clean those up + if (state->undo_rec[0].char_storage >= 0) { + int n = state->undo_rec[0].insert_length, i; + // delete n characters from all other records + state->undo_char_point -= n; + STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE))); + for (i=0; i < state->undo_point; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it + } + --state->undo_point; + STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0]))); + } +} + +// discard the oldest entry in the redo list--it's bad if this +// ever happens, but because undo & redo have to store the actual +// characters in different cases, the redo character buffer can +// fill up even though the undo buffer didn't +static void stb_textedit_discard_redo(StbUndoState *state) +{ + int k = STB_TEXTEDIT_UNDOSTATECOUNT-1; + + if (state->redo_point <= k) { + // if the k'th undo state has characters, clean those up + if (state->undo_rec[k].char_storage >= 0) { + int n = state->undo_rec[k].insert_length, i; + // move the remaining redo character data to the end of the buffer + state->redo_char_point += n; + STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE))); + // adjust the position of all the other records to account for above memmove + for (i=state->redo_point; i < k; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage += n; + } + // now move all the redo records towards the end of the buffer; the first one is at 'redo_point' + STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, (size_t) ((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point)*sizeof(state->undo_rec[0]))); + // now move redo_point to point to the new one + ++state->redo_point; + } +} + +static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) +{ + // any time we create a new undo record, we discard redo + stb_textedit_flush_redo(state); + + // if we have no free records, we have to make room, by sliding the + // existing records down + if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + stb_textedit_discard_undo(state); + + // if the characters to store won't possibly fit in the buffer, we can't undo + if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) { + state->undo_point = 0; + state->undo_char_point = 0; + return NULL; + } + + // if we don't have enough free characters in the buffer, we have to make room + while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT) + stb_textedit_discard_undo(state); + + return &state->undo_rec[state->undo_point++]; +} + +static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) +{ + StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); + if (r == NULL) + return NULL; + + r->where = pos; + r->insert_length = (STB_TEXTEDIT_POSITIONTYPE) insert_len; + r->delete_length = (STB_TEXTEDIT_POSITIONTYPE) delete_len; + + if (insert_len == 0) { + r->char_storage = -1; + return NULL; + } else { + r->char_storage = state->undo_char_point; + state->undo_char_point += insert_len; + return &state->undo_char[r->char_storage]; + } +} + +static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + StbUndoState *s = &state->undostate; + StbUndoRecord u, *r; + if (s->undo_point == 0) + return; + + // we need to do two things: apply the undo record, and create a redo record + u = s->undo_rec[s->undo_point-1]; + r = &s->undo_rec[s->redo_point-1]; + r->char_storage = -1; + + r->insert_length = u.delete_length; + r->delete_length = u.insert_length; + r->where = u.where; + + if (u.delete_length) { + // if the undo record says to delete characters, then the redo record will + // need to re-insert the characters that get deleted, so we need to store + // them. + + // there are three cases: + // there's enough room to store the characters + // characters stored for *redoing* don't leave room for redo + // characters stored for *undoing* don't leave room for redo + // if the last is true, we have to bail + + if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) { + // the undo records take up too much character space; there's no space to store the redo characters + r->insert_length = 0; + } else { + int i; + + // there's definitely room to store the characters eventually + while (s->undo_char_point + u.delete_length > s->redo_char_point) { + // should never happen: + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + return; + // there's currently not enough room, so discard a redo record + stb_textedit_discard_redo(s); + } + r = &s->undo_rec[s->redo_point-1]; + + r->char_storage = s->redo_char_point - u.delete_length; + s->redo_char_point = s->redo_char_point - u.delete_length; + + // now save the characters + for (i=0; i < u.delete_length; ++i) + s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); + } + + // now we can carry out the deletion + STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); + } + + // check type of recorded action: + if (u.insert_length) { + // easy case: was a deletion, so we need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); + s->undo_char_point -= u.insert_length; + } + + state->cursor = u.where + u.insert_length; + + s->undo_point--; + s->redo_point--; +} + +static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) +{ + StbUndoState *s = &state->undostate; + StbUndoRecord *u, r; + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + return; + + // we need to do two things: apply the redo record, and create an undo record + u = &s->undo_rec[s->undo_point]; + r = s->undo_rec[s->redo_point]; + + // we KNOW there must be room for the undo record, because the redo record + // was derived from an undo record + + u->delete_length = r.insert_length; + u->insert_length = r.delete_length; + u->where = r.where; + u->char_storage = -1; + + if (r.delete_length) { + // the redo record requires us to delete characters, so the undo record + // needs to store the characters + + if (s->undo_char_point + u->insert_length > s->redo_char_point) { + u->insert_length = 0; + u->delete_length = 0; + } else { + int i; + u->char_storage = s->undo_char_point; + s->undo_char_point = s->undo_char_point + u->insert_length; + + // now save the characters + for (i=0; i < u->insert_length; ++i) + s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); + } + + STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); + } + + if (r.insert_length) { + // easy case: need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); + s->redo_char_point += r.insert_length; + } + + state->cursor = r.where + r.insert_length; + + s->undo_point++; + s->redo_point++; +} + +static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) +{ + stb_text_createundo(&state->undostate, where, 0, length); +} + +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) +{ + int i; + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0); + if (p) { + for (i=0; i < length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); + } +} + +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) +{ + int i; + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length); + if (p) { + for (i=0; i < old_length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); + } +} + +// reset the state to default +static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) +{ + state->undostate.undo_point = 0; + state->undostate.undo_char_point = 0; + state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; + state->select_end = state->select_start = 0; + state->cursor = 0; + state->has_preferred_x = 0; + state->preferred_x = 0; + state->cursor_at_end_of_line = 0; + state->initialized = 1; + state->single_line = (unsigned char) is_single_line; + state->insert_mode = 0; + state->row_count_per_page = 0; +} + +// API initialize +static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) +{ + stb_textedit_clear_state(state, is_single_line); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len) +{ + return stb_textedit_paste_internal(str, state, (STB_TEXTEDIT_CHARTYPE *) ctext, len); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif//STB_TEXTEDIT_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_tilemap_editor.h b/source/engine/thirdparty/stb/stb_tilemap_editor.h new file mode 100644 index 0000000..fbd3388 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_tilemap_editor.h @@ -0,0 +1,4187 @@ +// stb_tilemap_editor.h - v0.42 - Sean Barrett - http://nothings.org/stb +// placed in the public domain - not copyrighted - first released 2014-09 +// +// Embeddable tilemap editor for C/C++ +// +// +// TABLE OF CONTENTS +// FAQ +// How to compile/use the library +// Additional configuration macros +// API documentation +// Info on editing multiple levels +// Revision history +// Todo +// Credits +// License +// +// +// FAQ +// +// Q: What counts as a tilemap for this library? +// +// A: An array of rectangles, where each rectangle contains a small +// stack of images. +// +// Q: What are the limitations? +// +// A: Maps are limited to 4096x4096 in dimension. +// Each map square can only contain a stack of at most 32 images. +// A map can only use up to 32768 distinct image tiles. +// +// Q: How do I compile this? +// +// A: You need to #define several symbols before #including it, but only +// in one file. This will cause all the function definitions to be +// generated in that file. See the "HOW TO COMPILE" section. +// +// Q: What advantages does this have over a standalone editor? +// +// A: For one, you can integrate the editor into your game so you can +// flip between editing and testing without even switching windows. +// For another, you don't need an XML parser to get at the map data. +// +// Q: Can I live-edit my game maps? +// +// A: Not really, the editor keeps its own map representation. +// +// Q: How do I save and load maps? +// +// A: You have to do this yourself. The editor provides serialization +// functions (get & set) for reading and writing the map it holds. +// You can choose whatever format you want to store the map to on +// disk; you just need to provide functions to convert. (For example, +// I actually store the editor's map representation to disk basically +// as-is; then I have a single function that converts from the editor +// map representation to the game representation, which is used both +// to go from editor-to-game and from loaded-map-to-game.) +// +// Q: I want to have tiles change appearance based on what's +// adjacent, or other tile-display/substitution trickiness. +// +// A: You can do this when you convert from the editor's map +// representation to the game representation, but there's +// no way to show this live in the editor. +// +// Q: The editor appears to be put map location (0,0) at the top left? +// I want to use a different coordinate system in my game (e.g. y +// increasing upwards, or origin at the center). +// +// A: You can do this when you convert from the editor's map +// representation to the game representation. (Don't forget to +// translate link coordinates as well!) +// +// Q: The editor appears to put pixel (0,0) at the top left? I want +// to use a different coordinate system in my game. +// +// A: The editor defines an "editor pixel coordinate system" with +// (0,0) at the top left and requires you to display things in +// that coordinate system. You can freely remap those coordinates +// to anything you want on screen. +// +// Q: How do I scale the user interface? +// +// A: Since you do all the rendering, you can scale up all the rendering +// calls that the library makes to you. If you do, (a) you need +// to also scale up the mouse coordinates, and (b) you may want +// to scale the map display back down so that you're only scaling +// the UI and not everything. See the next question. +// +// Q: How do I scale the map display? +// +// A: Use stbte_set_spacing() to change the size that the map is displayed +// at. Note that the "callbacks" to draw tiles are used for both drawing +// the map and drawing the tile palette, so that callback may need to +// draw at two different scales. You should choose the scales to match +// You can tell them apart because the +// tile palette gets NULL for the property pointer. +// +// Q: How does object editing work? +// +// A: One way to think of this is that in the editor, you're placing +// spawners, not objects. Each spawner must be tile-aligned, because +// it's only a tile editor. Each tile (stack of layers) gets +// an associated set of properties, and it's up to you to +// determine what properties should appear for a given tile, +// based on e.g. the spawners that are in it. +// +// Q: How are properties themselves handled? +// +// A: All properties, regardless of UI behavior, are internally floats. +// Each tile has an array of floats associated with it, which is +// passed back to you when drawing the tiles so you can draw +// objects appropriately modified by the properties. +// +// Q: What if I want to have two different objects/spawners in +// one tile, both of which have their own properties? +// +// A: Make sure STBTE_MAX_PROPERTIES is large enough for the sum of +// properties in both objects, and then you have to explicitly +// map the property slot #s to the appropriate objects. They'll +// still all appear in a single property panel; there's no way +// to get multiple panels. +// +// Q: Can I do one-to-many linking? +// +// A: The library only supports one link per tile. However, you +// can have multiple tiles all link to a single tile. So, you +// can fake one-to-many linking by linking in the reverse +// direction. +// +// Q: What if I have two objects in the same tile, and they each +// need an independent link? Or I have two kinds of link associated +// with a single object? +// +// A: There is no way to do this. (Unless you can reverse one link.) +// +// Q: How does cut & paste interact with object properties & links? +// +// A: Currently the library has no idea which properties or links +// are associated with which layers of a tile. So currently, the +// library will only copy properties & links if the layer panel +// is set to allow all layers to be copied, OR if you set the +// "props" in the layer panel to "always". Similarly, you can +// set "props" to "none" so it will never copy. +// +// Q: What happens if the library gets a memory allocation failure +// while I'm editing? Will I lose my work? +// +// A: The library allocates all editor memory when you create +// the tilemap. It allocates a maximally-sized map and a +// fixed-size undo buffer (and the fixed-size copy buffer +// is static), and never allocates memory while it's running. +// So it can't fail due to running out of memory. +// +// Q: What happens if the library crashes while I'm editing? Will +// I lose my work? +// +// A: Yes. Save often. +// +// +// HOW TO COMPILE +// +// This header file contains both the header file and the +// implementation file in one. To create the implementation, +// in one source file define a few symbols first and then +// include this header: +// +// #define STB_TILEMAP_EDITOR_IMPLEMENTATION +// // this triggers the implementation +// +// void STBTE_DRAW_RECT(int x0, int y0, int x1, int y1, unsigned int color); +// // this must draw a filled rectangle (exclusive on right/bottom) +// // color = (r<<16)|(g<<8)|(b) +// +// void STBTE_DRAW_TILE(int x0, int y0, +// unsigned short id, int highlight, float *data); +// // this draws the tile image identified by 'id' in one of several +// // highlight modes (see STBTE_drawmode_* in the header section); +// // if 'data' is NULL, it's drawing the tile in the palette; if 'data' +// // is not NULL, it's drawing a tile on the map, and that is the data +// // associated with that map tile +// +// #include "stb_tilemap_editor.h" +// +// Optionally you can define the following functions before the include; +// note these must be macros (but they can just call a function) so +// this library can #ifdef to detect if you've defined them: +// +// #define STBTE_PROP_TYPE(int n, short *tiledata, float *params) ... +// // Returns the type of the n'th property of a given tile, which +// // controls how it is edited. Legal types are: +// // 0 /* no editable property in this slot */ +// // STBTE_PROP_int /* uses a slider to adjust value */ +// // STBTE_PROP_float /* uses a weird multi-axis control */ +// // STBTE_PROP_bool /* uses a checkbox to change value */ +// // And you can bitwise-OR in the following flags: +// // STBTE_PROP_disabled +// // Note that all of these are stored as floats in the param array. +// // The integer slider is limited in precision based on the space +// // available on screen, so for wide-ranged integers you may want +// // to use floats instead. +// // +// // Since the tiledata is passed to you, you can choose which property +// // is bound to that slot based on that data. +// // +// // Changing the type of a parameter does not cause the underlying +// // value to be clamped to the type min/max except when the tile is +// // explicitly selected. +// +// #define STBTE_PROP_NAME(int n, short *tiledata, float *params) ... +// // these return a string with the name for slot #n in the float +// // property list for the tile. +// +// #define STBTE_PROP_MIN(int n, short *tiledata) ...your code here... +// #define STBTE_PROP_MAX(int n, short *tiledata) ...your code here... +// // These return the allowable range for the property values for +// // the specified slot. It is never called for boolean types. +// +// #define STBTE_PROP_FLOAT_SCALE(int n, short *tiledata, float *params) +// // This rescales the float control for a given property; by default +// // left mouse drags add integers, right mouse drags adds fractions, +// // but you can rescale this per-property. +// +// #define STBTE_FLOAT_CONTROL_GRANULARITY ... value ... +// // This returns the number of pixels of mouse motion necessary +// // to advance the object float control. Default is 4 +// +// #define STBTE_ALLOW_LINK(short *src, float *src_data, \ +// short *dest, float *dest_data) ...your code... +// // this returns true or false depending on whether you allow a link +// // to be drawn from a tile 'src' to a tile 'dest'. if you don't +// // define this, linking will not be supported +// +// #define STBTE_LINK_COLOR(short *src, float *src_data, \ +// short *dest, float *dest_data) ...your code... +// // return a color encoded as a 24-bit unsigned integer in the +// // form 0xRRGGBB. If you don't define this, default colors will +// // be used. +// +// +// [[ support for those below is not implemented yet ]] +// +// #define STBTE_HITTEST_TILE(x0,y0,id,mx,my) ...your code here... +// // this returns true or false depending on whether the mouse +// // pointer at mx,my is over (touching) a tile of type 'id' +// // displayed at x0,y0. Normally stb_tilemap_editor just does +// // this hittest based on the tile geometry, but if you have +// // tiles whose images extend out of the tile, you'll need this. +// +// ADDITIONAL CONFIGURATION +// +// The following symbols set static limits which determine how much +// memory will be allocated for the editor. You can override them +// by making similar definitions, but memory usage will increase. +// +// #define STBTE_MAX_TILEMAP_X 200 // max 4096 +// #define STBTE_MAX_TILEMAP_Y 200 // max 4096 +// #define STBTE_MAX_LAYERS 8 // max 32 +// #define STBTE_MAX_CATEGORIES 100 +// #define STBTE_UNDO_BUFFER_BYTES (1 << 24) // 16 MB +// #define STBTE_MAX_COPY 90000 // e.g. 300x300 +// #define STBTE_MAX_PROPERTIES 10 // max properties per tile +// +// API +// +// Further documentation appears in the header-file section below. +// +// EDITING MULTIPLE LEVELS +// +// You can only have one active editor instance. To switch between multiple +// levels, you can either store the levels in your own format and copy them +// in and out of the editor format, or you can create multiple stbte_tilemap +// objects and switch between them. The latter has the advantage that each +// stbte_tilemap keeps its own undo state. (The clipboard is global, so +// either approach allows cut&pasting between levels.) +// +// REVISION HISTORY +// 0.42 fix compilation errors +// 0.41 fix warnings +// 0.40 fix warning +// 0.39 fix warning +// 0.38 fix warning +// 0.37 fix warning +// 0.36 minor compiler support +// 0.35 layername button changes +// - layername buttons grow with the layer panel +// - fix stbte_create_map being declared as stbte_create +// - fix declaration of stbte_create_map +// 0.30 properties release +// - properties panel for editing user-defined "object" properties +// - can link each tile to one other tile +// - keyboard interface +// - fix eraser tool bug (worked in complex cases, failed in simple) +// - undo/redo tools have visible disabled state +// - tiles on higher layers draw on top of adjacent lower-layer tiles +// 0.20 erasable release +// - eraser tool +// - fix bug when pasting into protected layer +// - better color scheme +// - internal-use color picker +// 0.10 initial release +// +// TODO +// +// Separate scroll state for each category +// Implement paint bucket +// Support STBTE_HITTEST_TILE above +// ?Cancel drags by clicking other button? - may be fixed +// Finish support for toolbar at side +// +// CREDITS +// +// +// Main editor & features +// Sean Barrett +// Additional features: +// Josh Huelsman +// Bugfixes: +// Ryan Whitworth +// Eugene Opalev +// Rob Loach +// github:wernsey +// +// LICENSE +// +// See end of file for license information. + + + +/////////////////////////////////////////////////////////////////////// +// +// HEADER SECTION + +#ifndef STB_TILEMAP_INCLUDE_STB_TILEMAP_EDITOR_H +#define STB_TILEMAP_INCLUDE_STB_TILEMAP_EDITOR_H + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #include + #include +#endif + +typedef struct stbte_tilemap stbte_tilemap; + +// these are the drawmodes used in STBTE_DRAW_TILE +enum +{ + STBTE_drawmode_deemphasize = -1, + STBTE_drawmode_normal = 0, + STBTE_drawmode_emphasize = 1, +}; + +// these are the property types +#define STBTE_PROP_none 0 +#define STBTE_PROP_int 1 +#define STBTE_PROP_float 2 +#define STBTE_PROP_bool 3 +#define STBTE_PROP_disabled 4 + +//////// +// +// creation +// + +extern stbte_tilemap *stbte_create_map(int map_x, int map_y, int map_layers, int spacing_x, int spacing_y, int max_tiles); +// create an editable tilemap +// map_x : dimensions of map horizontally (user can change this in editor), <= STBTE_MAX_TILEMAP_X +// map_y : dimensions of map vertically (user can change this in editor) <= STBTE_MAX_TILEMAP_Y +// map_layers : number of layers to use (fixed), <= STBTE_MAX_LAYERS +// spacing_x : initial horizontal distance between left edges of map tiles in stb_tilemap_editor pixels +// spacing_y : initial vertical distance between top edges of map tiles in stb_tilemap_editor pixels +// max_tiles : maximum number of tiles that can defined +// +// If insufficient memory, returns NULL + +extern void stbte_define_tile(stbte_tilemap *tm, unsigned short id, unsigned int layermask, const char * category); +// call this repeatedly for each tile to install the tile definitions into the editable tilemap +// tm : tilemap created by stbte_create_map +// id : unique identifier for each tile, 0 <= id < 32768 +// layermask : bitmask of which layers tile is allowed on: 1 = layer 0, 255 = layers 0..7 +// (note that onscreen, the editor numbers the layers from 1 not 0) +// layer 0 is the furthest back, layer 1 is just in front of layer 0, etc +// category : which category this tile is grouped in + +extern void stbte_set_display(int x0, int y0, int x1, int y1); +// call this once to set the size; if you resize, call it again + + +///////// +// +// every frame +// + +extern void stbte_draw(stbte_tilemap *tm); + +extern void stbte_tick(stbte_tilemap *tm, float time_in_seconds_since_last_frame); + +//////////// +// +// user input +// + +// if you're using SDL, call the next function for SDL_MOUSEMOTION, SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP, SDL_MOUSEWHEEL; +// the transformation lets you scale from SDL mouse coords to stb_tilemap_editor coords +extern void stbte_mouse_sdl(stbte_tilemap *tm, const void *sdl_event, float xscale, float yscale, int xoffset, int yoffset); + +// otherwise, hook these up explicitly: +extern void stbte_mouse_move(stbte_tilemap *tm, int x, int y, int shifted, int scrollkey); +extern void stbte_mouse_button(stbte_tilemap *tm, int x, int y, int right, int down, int shifted, int scrollkey); +extern void stbte_mouse_wheel(stbte_tilemap *tm, int x, int y, int vscroll); + +// note: at the moment, mouse wheel events (SDL_MOUSEWHEEL) are ignored. + +// for keyboard, define your own mapping from keys to the following actions. +// this is totally optional, as all features are accessible with the mouse +enum stbte_action +{ + STBTE_tool_select, + STBTE_tool_brush, + STBTE_tool_erase, + STBTE_tool_rectangle, + STBTE_tool_eyedropper, + STBTE_tool_link, + STBTE_act_toggle_grid, + STBTE_act_toggle_links, + STBTE_act_undo, + STBTE_act_redo, + STBTE_act_cut, + STBTE_act_copy, + STBTE_act_paste, + STBTE_scroll_left, + STBTE_scroll_right, + STBTE_scroll_up, + STBTE_scroll_down, +}; +extern void stbte_action(stbte_tilemap *tm, enum stbte_action act); + +//////////////// +// +// save/load +// +// There is no editor file format. You have to save and load the data yourself +// through the following functions. You can also use these functions to get the +// data to generate game-formatted levels directly. (But make sure you save +// first! You may also want to autosave to a temp file periodically, etc etc.) + +#define STBTE_EMPTY -1 + +extern void stbte_get_dimensions(stbte_tilemap *tm, int *max_x, int *max_y); +// get the dimensions of the level, since the user can change them + +extern short* stbte_get_tile(stbte_tilemap *tm, int x, int y); +// returns an array of shorts that is 'map_layers' in length. each short is +// either one of the tile_id values from define_tile, or STBTE_EMPTY. + +extern float *stbte_get_properties(stbte_tilemap *tm, int x, int y); +// get the property array associated with the tile at x,y. this is an +// array of floats that is STBTE_MAX_PROPERTIES in length; you have to +// interpret the slots according to the semantics you've chosen + +extern void stbte_get_link(stbte_tilemap *tm, int x, int y, int *destx, int *desty); +// gets the link associated with the tile at x,y. + +extern void stbte_set_dimensions(stbte_tilemap *tm, int max_x, int max_y); +// set the dimensions of the level, overrides previous stbte_create_map() +// values or anything the user has changed + +extern void stbte_clear_map(stbte_tilemap *tm); +// clears the map, including the region outside the defined region, so if the +// user expands the map, they won't see garbage there + +extern void stbte_set_tile(stbte_tilemap *tm, int x, int y, int layer, signed short tile); +// tile is your tile_id from define_tile, or STBTE_EMPTY + +extern void stbte_set_property(stbte_tilemap *tm, int x, int y, int n, float val); +// set the value of the n'th slot of the tile at x,y + +extern void stbte_set_link(stbte_tilemap *tm, int x, int y, int destx, int desty); +// set a link going from x,y to destx,desty. to force no link, +// use destx=desty=-1 + +//////// +// +// optional +// + +extern void stbte_set_background_tile(stbte_tilemap *tm, short id); +// selects the tile to fill the bottom layer with and used to clear bottom tiles to; +// should be same ID as + +extern void stbte_set_sidewidths(int left, int right); +// call this once to set the left & right side widths. don't call +// it again since the user can change it + +extern void stbte_set_spacing(stbte_tilemap *tm, int spacing_x, int spacing_y, int palette_spacing_x, int palette_spacing_y); +// call this to set the spacing of map tiles and the spacing of palette tiles. +// if you rescale your display, call it again (e.g. you can implement map zooming yourself) + +extern void stbte_set_layername(stbte_tilemap *tm, int layer, const char *layername); +// sets a string name for your layer that shows in the layer selector. note that this +// makes the layer selector wider. 'layer' is from 0..(map_layers-1) + +#endif + +#ifdef STB_TILEMAP_EDITOR_IMPLEMENTATION + +#ifndef STBTE_ASSERT +#define STBTE_ASSERT assert +#include +#endif + +#ifdef _MSC_VER +#define STBTE__NOTUSED(v) (void)(v) +#else +#define STBTE__NOTUSED(v) (void)sizeof(v) +#endif + +#ifndef STBTE_MAX_TILEMAP_X +#define STBTE_MAX_TILEMAP_X 200 +#endif + +#ifndef STBTE_MAX_TILEMAP_Y +#define STBTE_MAX_TILEMAP_Y 200 +#endif + +#ifndef STBTE_MAX_LAYERS +#define STBTE_MAX_LAYERS 8 +#endif + +#ifndef STBTE_MAX_CATEGORIES +#define STBTE_MAX_CATEGORIES 100 +#endif + +#ifndef STBTE_MAX_COPY +#define STBTE_MAX_COPY 65536 +#endif + +#ifndef STBTE_UNDO_BUFFER_BYTES +#define STBTE_UNDO_BUFFER_BYTES (1 << 24) // 16 MB +#endif + +#ifndef STBTE_PROP_TYPE +#define STBTE__NO_PROPS +#define STBTE_PROP_TYPE(n,td,tp) 0 +#endif + +#ifndef STBTE_PROP_NAME +#define STBTE_PROP_NAME(n,td,tp) "" +#endif + +#ifndef STBTE_MAX_PROPERTIES +#define STBTE_MAX_PROPERTIES 10 +#endif + +#ifndef STBTE_PROP_MIN +#define STBTE_PROP_MIN(n,td,tp) 0 +#endif + +#ifndef STBTE_PROP_MAX +#define STBTE_PROP_MAX(n,td,tp) 100.0 +#endif + +#ifndef STBTE_PROP_FLOAT_SCALE +#define STBTE_PROP_FLOAT_SCALE(n,td,tp) 1 // default scale size +#endif + +#ifndef STBTE_FLOAT_CONTROL_GRANULARITY +#define STBTE_FLOAT_CONTROL_GRANULARITY 4 +#endif + + +#define STBTE__UNDO_BUFFER_COUNT (STBTE_UNDO_BUFFER_BYTES>>1) + +#if STBTE_MAX_TILEMAP_X > 4096 || STBTE_MAX_TILEMAP_Y > 4096 +#error "Maximum editable map size is 4096 x 4096" +#endif +#if STBTE_MAX_LAYERS > 32 +#error "Maximum layers allowed is 32" +#endif +#if STBTE_UNDO_BUFFER_COUNT & (STBTE_UNDO_BUFFER_COUNT-1) +#error "Undo buffer size must be a power of 2" +#endif + +#if STBTE_MAX_PROPERTIES == 0 +#define STBTE__NO_PROPS +#endif + +#ifdef STBTE__NO_PROPS +#undef STBTE_MAX_PROPERTIES +#define STBTE_MAX_PROPERTIES 1 // so we can declare arrays +#endif + +typedef struct +{ + short x,y; +} stbte__link; + +enum +{ + STBTE__base, + STBTE__outline, + STBTE__text, + + STBTE__num_color_aspects, +}; + +enum +{ + STBTE__idle, + STBTE__over, + STBTE__down, + STBTE__over_down, + STBTE__selected, + STBTE__selected_over, + STBTE__disabled, + STBTE__num_color_states, +}; + +enum +{ + STBTE__cexpander, + STBTE__ctoolbar, + STBTE__ctoolbar_button, + STBTE__cpanel, + STBTE__cpanel_sider, + STBTE__cpanel_sizer, + STBTE__cscrollbar, + STBTE__cmapsize, + STBTE__clayer_button, + STBTE__clayer_hide, + STBTE__clayer_lock, + STBTE__clayer_solo, + STBTE__ccategory_button, + + STBTE__num_color_modes, +}; + +#ifdef STBTE__COLORPICKER +static char *stbte__color_names[] = +{ + "expander", "toolbar", "tool button", "panel", + "panel c1", "panel c2", "scollbar", "map button", + "layer", "hide", "lock", "solo", + "category", +}; +#endif // STBTE__COLORPICKER + + // idle, over, down, over&down, selected, sel&over, disabled +static int stbte__color_table[STBTE__num_color_modes][STBTE__num_color_aspects][STBTE__num_color_states] = +{ + { + { 0x000000, 0x84987c, 0xdcdca8, 0xdcdca8, 0x40c040, 0x60d060, 0x505050, }, + { 0xa4b090, 0xe0ec80, 0xffffc0, 0xffffc0, 0x80ff80, 0x80ff80, 0x606060, }, + { 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, }, + }, { + { 0x808890, 0x606060, 0x606060, 0x606060, 0x606060, 0x606060, 0x606060, }, + { 0x605860, 0x606060, 0x606060, 0x606060, 0x606060, 0x606060, 0x606060, }, + { 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, }, + }, { + { 0x3c5068, 0x7088a8, 0x647488, 0x94b4dc, 0x8890c4, 0x9caccc, 0x404040, }, + { 0x889cb8, 0x889cb8, 0x889cb8, 0x889cb8, 0x84c4e8, 0xacc8ff, 0x0c0c08, }, + { 0xbcc4cc, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x707074, }, + }, { + { 0x403848, 0x403010, 0x403010, 0x403010, 0x403010, 0x403010, 0x303024, }, + { 0x68546c, 0xc08040, 0xc08040, 0xc08040, 0xc08040, 0xc08040, 0x605030, }, + { 0xf4e4ff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, }, + }, { + { 0xb4b04c, 0xacac60, 0xc0ffc0, 0xc0ffc0, 0x40c040, 0x60d060, 0x505050, }, + { 0xa0a04c, 0xd0d04c, 0xffff80, 0xffff80, 0x80ff80, 0x80ff80, 0x606060, }, + { 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, }, + }, { + { 0x40c440, 0x60d060, 0xc0ffc0, 0xc0ffc0, 0x40c040, 0x60d060, 0x505050, }, + { 0x40c040, 0x80ff80, 0x80ff80, 0x80ff80, 0x80ff80, 0x80ff80, 0x606060, }, + { 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, }, + }, { + { 0x9090ac, 0xa0a0b8, 0xbcb8cc, 0xbcb8cc, 0x909040, 0x909040, 0x909040, }, + { 0xa0a0b8, 0xb0b4d0, 0xa0a0b8, 0xa0a0b8, 0xa0a050, 0xa0a050, 0xa0a050, }, + { 0x808088, 0x808030, 0x808030, 0x808030, 0x808030, 0x808030, 0x808030, }, + }, { + { 0x704c70, 0x885c8c, 0x9c68a4, 0xb870bc, 0xb490bc, 0xb490bc, 0x302828, }, + { 0x646064, 0xcca8d4, 0xc060c0, 0xa07898, 0xe0b8e0, 0xe0b8e0, 0x403838, }, + { 0xdccce4, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, }, + }, { + { 0x704c70, 0x885c8c, 0x9c68a4, 0xb870bc, 0xb490bc, 0xb490bc, 0x302828, }, + { 0xb09cb4, 0xcca8d4, 0xc060c0, 0xa07898, 0xe0b8e0, 0xe0b8e0, 0x403838, }, + { 0xdccce4, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, }, + }, { + { 0x646494, 0x888cb8, 0xb0b0b0, 0xb0b0cc, 0x9c9cf4, 0x8888b0, 0x50506c, }, + { 0x9090a4, 0xb0b4d4, 0xb0b0dc, 0xb0b0cc, 0xd0d0fc, 0xd0d4f0, 0x606060, }, + { 0xb4b4d4, 0xe4e4ff, 0xffffff, 0xffffff, 0xe0e4ff, 0xececff, 0x909090, }, + }, { + { 0x646444, 0x888c64, 0xb0b0b0, 0xb0b088, 0xaca858, 0x88886c, 0x505050, }, + { 0x88886c, 0xb0b490, 0xb0b0b0, 0xb0b088, 0xd8d898, 0xd0d4b0, 0x606060, }, + { 0xb4b49c, 0xffffd8, 0xffffff, 0xffffd4, 0xffffdc, 0xffffcc, 0x909090, }, + }, { + { 0x906464, 0xb48c8c, 0xd4b0b0, 0xdcb0b0, 0xff9c9c, 0xc88888, 0x505050, }, + { 0xb47c80, 0xd4b4b8, 0xc4a8a8, 0xdcb0b0, 0xffc0c0, 0xfce8ec, 0x606060, }, + { 0xe0b4b4, 0xffdcd8, 0xffd8d4, 0xffe0e4, 0xffece8, 0xffffff, 0x909090, }, + }, { + { 0x403848, 0x403848, 0x403848, 0x886894, 0x7c80c8, 0x7c80c8, 0x302828, }, + { 0x403848, 0x403848, 0x403848, 0x403848, 0x7c80c8, 0x7c80c8, 0x403838, }, + { 0xc8c4c8, 0xffffff, 0xffffff, 0xffffff, 0xe8e8ec, 0xffffff, 0x909090, }, + }, +}; + +#define STBTE_COLOR_TILEMAP_BACKGROUND 0x000000 +#define STBTE_COLOR_TILEMAP_BORDER 0x203060 +#define STBTE_COLOR_TILEMAP_HIGHLIGHT 0xffffff +#define STBTE_COLOR_GRID 0x404040 +#define STBTE_COLOR_SELECTION_OUTLINE1 0xdfdfdf +#define STBTE_COLOR_SELECTION_OUTLINE2 0x303030 +#define STBTE_COLOR_TILEPALETTE_OUTLINE 0xffffff +#define STBTE_COLOR_TILEPALETTE_BACKGROUND 0x000000 + +#ifndef STBTE_LINK_COLOR +#define STBTE_LINK_COLOR(src,sp,dest,dp) 0x5030ff +#endif + +#ifndef STBTE_LINK_COLOR_DRAWING +#define STBTE_LINK_COLOR_DRAWING 0xff40ff +#endif + +#ifndef STBTE_LINK_COLOR_DISALLOWED +#define STBTE_LINK_COLOR_DISALLOWED 0x602060 +#endif + + +// disabled, selected, down, over +static unsigned char stbte__state_to_index[2][2][2][2] = +{ + { + { { STBTE__idle , STBTE__over }, { STBTE__down , STBTE__over_down }, }, + { { STBTE__selected, STBTE__selected_over }, { STBTE__down , STBTE__over_down }, }, + },{ + { { STBTE__disabled, STBTE__disabled }, { STBTE__disabled, STBTE__disabled }, }, + { { STBTE__selected, STBTE__selected_over }, { STBTE__disabled, STBTE__disabled }, }, + } +}; +#define STBTE__INDEX_FOR_STATE(disable,select,down,over) stbte__state_to_index[disable][select][down][over] +#define STBTE__INDEX_FOR_ID(id,disable,select) STBTE__INDEX_FOR_STATE(disable,select,STBTE__IS_ACTIVE(id),STBTE__IS_HOT(id)) + +#define STBTE__FONT_HEIGHT 9 +static short stbte__font_offset[95+16]; +static short stbte__fontdata[769] = +{ + 4,9,6,9,9,9,9,8,9,8,4,9,7,7,7,7,4,2,6,8,6,6,7,3,4,4,8,6,3,6,2,6,6,6,6,6,6, + 6,6,6,6,6,2,3,5,4,5,6,6,6,6,6,6,6,6,6,6,6,6,7,6,7,7,7,6,7,6,6,6,6,7,7,6,6, + 6,4,6,4,7,7,3,6,6,5,6,6,5,6,6,4,5,6,4,7,6,6,6,6,6,6,6,6,6,7,6,6,6,5,2,5,8, + 0,0,0,0,2,253,130,456,156,8,72,184,64,2,125,66,64,160,64,146,511,146,146, + 511,146,146,511,146,511,257,341,297,341,297,341,257,511,16,56,124,16,16,16, + 124,56,16,96,144,270,261,262,136,80,48,224,192,160,80,40,22,14,15,3,448,496, + 496,240,232,20,10,5,2,112,232,452,450,225,113,58,28,63,30,60,200,455,257, + 257,0,0,0,257,257,455,120,204,132,132,159,14,4,4,14,159,132,132,204,120,8, + 24,56,120,56,24,8,32,48,56,60,56,48,32,0,0,0,0,111,111,7,7,0,0,7,7,34,127, + 127,34,34,127,127,34,36,46,107,107,58,18,99,51,24,12,102,99,48,122,79,93, + 55,114,80,4,7,3,62,127,99,65,65,99,127,62,8,42,62,28,28,62,42,8,8,8,62,62, + 8,8,128,224,96,8,8,8,8,8,8,96,96,96,48,24,12,6,3,62,127,89,77,127,62,64,66, + 127,127,64,64,98,115,89,77,71,66,33,97,73,93,119,35,24,28,22,127,127,16,39, + 103,69,69,125,57,62,127,73,73,121,48,1,1,113,121,15,7,54,127,73,73,127,54, + 6,79,73,105,63,30,54,54,128,246,118,8,28,54,99,65,20,20,20,20,65,99,54,28, + 8,2,3,105,109,7,2,30,63,33,45,47,46,124,126,19,19,126,124,127,127,73,73,127, + 54,62,127,65,65,99,34,127,127,65,99,62,28,127,127,73,73,73,65,127,127,9,9, + 9,1,62,127,65,73,121,121,127,127,8,8,127,127,65,65,127,127,65,65,32,96,64, + 64,127,63,127,127,8,28,54,99,65,127,127,64,64,64,64,127,127,6,12,6,127,127, + 127,127,6,12,24,127,127,62,127,65,65,65,127,62,127,127,9,9,15,6,62,127,65, + 81,49,127,94,127,127,9,25,127,102,70,79,73,73,121,49,1,1,127,127,1,1,63,127, + 64,64,127,63,15,31,48,96,48,31,15,127,127,48,24,48,127,127,99,119,28,28,119, + 99,7,15,120,120,15,7,97,113,89,77,71,67,127,127,65,65,3,6,12,24,48,96,65, + 65,127,127,8,12,6,3,6,12,8,64,64,64,64,64,64,64,3,7,4,32,116,84,84,124,120, + 127,127,68,68,124,56,56,124,68,68,68,56,124,68,68,127,127,56,124,84,84,92, + 24,8,124,126,10,10,56,380,324,324,508,252,127,127,4,4,124,120,72,122,122, + 64,256,256,256,506,250,126,126,16,56,104,64,66,126,126,64,124,124,24,56,28, + 124,120,124,124,4,4,124,120,56,124,68,68,124,56,508,508,68,68,124,56,56,124, + 68,68,508,508,124,124,4,4,12,8,72,92,84,84,116,36,4,4,62,126,68,68,60,124, + 64,64,124,124,28,60,96,96,60,28,28,124,112,56,112,124,28,68,108,56,56,108, + 68,284,316,352,320,508,252,68,100,116,92,76,68,8,62,119,65,65,127,127,65, + 65,119,62,8,16,24,12,12,24,24,12,4, +}; + +typedef struct +{ + short id; + unsigned short category_id; + char *category; + unsigned int layermask; +} stbte__tileinfo; + +#define MAX_LAYERMASK (1 << (8*sizeof(unsigned int))) + +typedef short stbte__tiledata; + +#define STBTE__NO_TILE -1 + +enum +{ + STBTE__panel_toolbar, + STBTE__panel_colorpick, + STBTE__panel_info, + STBTE__panel_layers, + STBTE__panel_props, + STBTE__panel_categories, + STBTE__panel_tiles, + + STBTE__num_panel, +}; + +enum +{ + STBTE__side_left, + STBTE__side_right, + STBTE__side_top, + STBTE__side_bottom, +}; + +enum +{ + STBTE__tool_select, + STBTE__tool_brush, + STBTE__tool_erase, + STBTE__tool_rect, + STBTE__tool_eyedrop, + STBTE__tool_fill, + STBTE__tool_link, + + STBTE__tool_showgrid, + STBTE__tool_showlinks, + + STBTE__tool_undo, + STBTE__tool_redo, + // copy/cut/paste aren't included here because they're displayed differently + + STBTE__num_tool, +}; + +// icons are stored in the 0-31 range of ASCII in the font +static int toolchar[] = { 26,24,25,20,23,22,18, 19,17, 29,28, }; + +enum +{ + STBTE__propmode_default, + STBTE__propmode_always, + STBTE__propmode_never, +}; + +enum +{ + STBTE__paint, + + // from here down does hittesting + STBTE__tick, + STBTE__mousemove, + STBTE__mousewheel, + STBTE__leftdown, + STBTE__leftup, + STBTE__rightdown, + STBTE__rightup, +}; + +typedef struct +{ + int expanded, mode; + int delta_height; // number of rows they've requested for this + int side; + int width,height; + int x0,y0; +} stbte__panel; + +typedef struct +{ + int x0,y0,x1,y1,color; +} stbte__colorrect; + +#define STBTE__MAX_DELAYRECT 256 + +typedef struct +{ + int tool, active_event; + int active_id, hot_id, next_hot_id; + int event; + int mx,my, dx,dy; + int ms_time; + int shift, scrollkey; + int initted; + int side_extended[2]; + stbte__colorrect delayrect[STBTE__MAX_DELAYRECT]; + int delaycount; + int show_grid, show_links; + int brush_state; // used to decide which kind of erasing + int eyedrop_x, eyedrop_y, eyedrop_last_layer; + int pasting, paste_x, paste_y; + int scrolling, start_x, start_y; + int last_mouse_x, last_mouse_y; + int accum_x, accum_y; + int linking; + int dragging; + int drag_x, drag_y, drag_w, drag_h; + int drag_offx, drag_offy, drag_dest_x, drag_dest_y; + int undoing; + int has_selection, select_x0, select_y0, select_x1, select_y1; + int sx,sy; + int x0,y0,x1,y1, left_width, right_width; // configurable widths + float alert_timer; + const char *alert_msg; + float dt; + stbte__panel panel[STBTE__num_panel]; + short copybuffer[STBTE_MAX_COPY][STBTE_MAX_LAYERS]; + float copyprops[STBTE_MAX_COPY][STBTE_MAX_PROPERTIES]; +#ifdef STBTE_ALLOW_LINK + stbte__link copylinks[STBTE_MAX_COPY]; +#endif + int copy_src_x, copy_src_y; + stbte_tilemap *copy_src; + int copy_width,copy_height,has_copy,copy_has_props; +} stbte__ui_t; + +// there's only one UI system at a time, so we can globalize this +static stbte__ui_t stbte__ui = { STBTE__tool_brush, 0 }; + +#define STBTE__INACTIVE() (stbte__ui.active_id == 0) +#define STBTE__IS_ACTIVE(id) (stbte__ui.active_id == (id)) +#define STBTE__IS_HOT(id) (stbte__ui.hot_id == (id)) + +#define STBTE__BUTTON_HEIGHT (STBTE__FONT_HEIGHT + 2 * STBTE__BUTTON_INTERNAL_SPACING) +#define STBTE__BUTTON_INTERNAL_SPACING (2 + (STBTE__FONT_HEIGHT>>4)) + +typedef struct +{ + const char *name; + int locked; + int hidden; +} stbte__layer; + +enum +{ + STBTE__unlocked, + STBTE__protected, + STBTE__locked, +}; + +struct stbte_tilemap +{ + stbte__tiledata data[STBTE_MAX_TILEMAP_Y][STBTE_MAX_TILEMAP_X][STBTE_MAX_LAYERS]; + float props[STBTE_MAX_TILEMAP_Y][STBTE_MAX_TILEMAP_X][STBTE_MAX_PROPERTIES]; + #ifdef STBTE_ALLOW_LINK + stbte__link link[STBTE_MAX_TILEMAP_Y][STBTE_MAX_TILEMAP_X]; + int linkcount[STBTE_MAX_TILEMAP_Y][STBTE_MAX_TILEMAP_X]; + #endif + int max_x, max_y, num_layers; + int spacing_x, spacing_y; + int palette_spacing_x, palette_spacing_y; + int scroll_x,scroll_y; + int cur_category, cur_tile, cur_layer; + char *categories[STBTE_MAX_CATEGORIES]; + int num_categories, category_scroll; + stbte__tileinfo *tiles; + int num_tiles, max_tiles, digits; + unsigned char undo_available_valid; + unsigned char undo_available; + unsigned char redo_available; + unsigned char padding; + int cur_palette_count; + int palette_scroll; + int tileinfo_dirty; + stbte__layer layerinfo[STBTE_MAX_LAYERS]; + int has_layer_names; + int layername_width; + int layer_scroll; + int propmode; + int solo_layer; + int undo_pos, undo_len, redo_len; + short background_tile; + unsigned char id_in_use[32768>>3]; + short *undo_buffer; +}; + +static char *default_category = (char*) "[unassigned]"; + +static void stbte__init_gui(void) +{ + int i,n; + stbte__ui.initted = 1; + // init UI state + stbte__ui.show_links = 1; + for (i=0; i < STBTE__num_panel; ++i) { + stbte__ui.panel[i].expanded = 1; // visible if not autohidden + stbte__ui.panel[i].delta_height = 0; + stbte__ui.panel[i].side = STBTE__side_left; + } + stbte__ui.panel[STBTE__panel_toolbar ].side = STBTE__side_top; + stbte__ui.panel[STBTE__panel_colorpick].side = STBTE__side_right; + + if (stbte__ui.left_width == 0) + stbte__ui.left_width = 80; + if (stbte__ui.right_width == 0) + stbte__ui.right_width = 80; + + // init font + n=95+16; + for (i=0; i < 95+16; ++i) { + stbte__font_offset[i] = n; + n += stbte__fontdata[i]; + } +} + +stbte_tilemap *stbte_create_map(int map_x, int map_y, int map_layers, int spacing_x, int spacing_y, int max_tiles) +{ + int i; + stbte_tilemap *tm; + STBTE_ASSERT(map_layers >= 0 && map_layers <= STBTE_MAX_LAYERS); + STBTE_ASSERT(map_x >= 0 && map_x <= STBTE_MAX_TILEMAP_X); + STBTE_ASSERT(map_y >= 0 && map_y <= STBTE_MAX_TILEMAP_Y); + if (map_x < 0 || map_y < 0 || map_layers < 0 || + map_x > STBTE_MAX_TILEMAP_X || map_y > STBTE_MAX_TILEMAP_Y || map_layers > STBTE_MAX_LAYERS) + return NULL; + + if (!stbte__ui.initted) + stbte__init_gui(); + + tm = (stbte_tilemap *) malloc(sizeof(*tm) + sizeof(*tm->tiles) * max_tiles + STBTE_UNDO_BUFFER_BYTES); + if (tm == NULL) + return NULL; + + tm->tiles = (stbte__tileinfo *) (tm+1); + tm->undo_buffer = (short *) (tm->tiles + max_tiles); + tm->num_layers = map_layers; + tm->max_x = map_x; + tm->max_y = map_y; + tm->spacing_x = spacing_x; + tm->spacing_y = spacing_y; + tm->scroll_x = 0; + tm->scroll_y = 0; + tm->palette_scroll = 0; + tm->palette_spacing_x = spacing_x+1; + tm->palette_spacing_y = spacing_y+1; + tm->cur_category = -1; + tm->cur_tile = 0; + tm->solo_layer = -1; + tm->undo_len = 0; + tm->redo_len = 0; + tm->undo_pos = 0; + tm->category_scroll = 0; + tm->layer_scroll = 0; + tm->propmode = 0; + tm->has_layer_names = 0; + tm->layername_width = 0; + tm->undo_available_valid = 0; + + for (i=0; i < tm->num_layers; ++i) { + tm->layerinfo[i].hidden = 0; + tm->layerinfo[i].locked = STBTE__unlocked; + tm->layerinfo[i].name = 0; + } + + tm->background_tile = STBTE__NO_TILE; + stbte_clear_map(tm); + + tm->max_tiles = max_tiles; + tm->num_tiles = 0; + for (i=0; i < 32768/8; ++i) + tm->id_in_use[i] = 0; + tm->tileinfo_dirty = 1; + return tm; +} + +void stbte_set_background_tile(stbte_tilemap *tm, short id) +{ + int i; + STBTE_ASSERT(id >= -1); + // STBTE_ASSERT(id < 32768); + if (id < -1) + return; + for (i=0; i < STBTE_MAX_TILEMAP_X * STBTE_MAX_TILEMAP_Y; ++i) + if (tm->data[0][i][0] == -1) + tm->data[0][i][0] = id; + tm->background_tile = id; +} + +void stbte_set_spacing(stbte_tilemap *tm, int spacing_x, int spacing_y, int palette_spacing_x, int palette_spacing_y) +{ + tm->spacing_x = spacing_x; + tm->spacing_y = spacing_y; + tm->palette_spacing_x = palette_spacing_x; + tm->palette_spacing_y = palette_spacing_y; +} + +void stbte_set_sidewidths(int left, int right) +{ + stbte__ui.left_width = left; + stbte__ui.right_width = right; +} + +void stbte_set_display(int x0, int y0, int x1, int y1) +{ + stbte__ui.x0 = x0; + stbte__ui.y0 = y0; + stbte__ui.x1 = x1; + stbte__ui.y1 = y1; +} + +void stbte_define_tile(stbte_tilemap *tm, unsigned short id, unsigned int layermask, const char * category_c) +{ + char *category = (char *) category_c; + STBTE_ASSERT(id < 32768); + STBTE_ASSERT(tm->num_tiles < tm->max_tiles); + STBTE_ASSERT((tm->id_in_use[id>>3]&(1<<(id&7))) == 0); + if (id >= 32768 || tm->num_tiles >= tm->max_tiles || (tm->id_in_use[id>>3]&(1<<(id&7)))) + return; + + if (category == NULL) + category = (char*) default_category; + tm->id_in_use[id>>3] |= 1 << (id&7); + tm->tiles[tm->num_tiles].category = category; + tm->tiles[tm->num_tiles].id = id; + tm->tiles[tm->num_tiles].layermask = layermask; + ++tm->num_tiles; + tm->tileinfo_dirty = 1; +} + +static int stbte__text_width(const char *str); + +void stbte_set_layername(stbte_tilemap *tm, int layer, const char *layername) +{ + STBTE_ASSERT(layer >= 0 && layer < tm->num_layers); + if (layer >= 0 && layer < tm->num_layers) { + int width; + tm->layerinfo[layer].name = layername; + tm->has_layer_names = 1; + width = stbte__text_width(layername); + tm->layername_width = (width > tm->layername_width ? width : tm->layername_width); + } +} + +void stbte_get_dimensions(stbte_tilemap *tm, int *max_x, int *max_y) +{ + *max_x = tm->max_x; + *max_y = tm->max_y; +} + +short* stbte_get_tile(stbte_tilemap *tm, int x, int y) +{ + STBTE_ASSERT(x >= 0 && x < tm->max_x && y >= 0 && y < tm->max_y); + if (x < 0 || x >= STBTE_MAX_TILEMAP_X || y < 0 || y >= STBTE_MAX_TILEMAP_Y) + return NULL; + return tm->data[y][x]; +} + +float *stbte_get_properties(stbte_tilemap *tm, int x, int y) +{ + STBTE_ASSERT(x >= 0 && x < tm->max_x && y >= 0 && y < tm->max_y); + if (x < 0 || x >= STBTE_MAX_TILEMAP_X || y < 0 || y >= STBTE_MAX_TILEMAP_Y) + return NULL; + return tm->props[y][x]; +} + +void stbte_get_link(stbte_tilemap *tm, int x, int y, int *destx, int *desty) +{ + int gx=-1,gy=-1; + STBTE_ASSERT(x >= 0 && x < tm->max_x && y >= 0 && y < tm->max_y); +#ifdef STBTE_ALLOW_LINK + if (x >= 0 && x < STBTE_MAX_TILEMAP_X && y >= 0 && y < STBTE_MAX_TILEMAP_Y) { + gx = tm->link[y][x].x; + gy = tm->link[y][x].y; + if (gx >= 0) + if (!STBTE_ALLOW_LINK(tm->data[y][x], tm->props[y][x], tm->data[gy][gx], tm->props[gy][gx])) + gx = gy = -1; + } +#endif + *destx = gx; + *desty = gy; +} + +void stbte_set_property(stbte_tilemap *tm, int x, int y, int n, float val) +{ + tm->props[y][x][n] = val; +} + +#ifdef STBTE_ALLOW_LINK +static void stbte__set_link(stbte_tilemap *tm, int src_x, int src_y, int dest_x, int dest_y, int undo_mode); +#endif + +enum +{ + STBTE__undo_none, + STBTE__undo_record, + STBTE__undo_block, +}; + +void stbte_set_link(stbte_tilemap *tm, int x, int y, int destx, int desty) +{ +#ifdef STBTE_ALLOW_LINK + stbte__set_link(tm, x, y, destx, desty, STBTE__undo_none); +#else + STBTE_ASSERT(0); +#endif +} + + +// returns an array of map_layers shorts. each short is either +// one of the tile_id values from define_tile, or STBTE_EMPTY + +void stbte_set_dimensions(stbte_tilemap *tm, int map_x, int map_y) +{ + STBTE_ASSERT(map_x >= 0 && map_x <= STBTE_MAX_TILEMAP_X); + STBTE_ASSERT(map_y >= 0 && map_y <= STBTE_MAX_TILEMAP_Y); + if (map_x < 0 || map_y < 0 || map_x > STBTE_MAX_TILEMAP_X || map_y > STBTE_MAX_TILEMAP_Y) + return; + tm->max_x = map_x; + tm->max_y = map_y; +} + +void stbte_clear_map(stbte_tilemap *tm) +{ + int i,j; + for (i=0; i < STBTE_MAX_TILEMAP_X * STBTE_MAX_TILEMAP_Y; ++i) { + tm->data[0][i][0] = tm->background_tile; + for (j=1; j < tm->num_layers; ++j) + tm->data[0][i][j] = STBTE__NO_TILE; + for (j=0; j < STBTE_MAX_PROPERTIES; ++j) + tm->props[0][i][j] = 0; + #ifdef STBTE_ALLOW_LINK + tm->link[0][i].x = -1; + tm->link[0][i].y = -1; + tm->linkcount[0][i] = 0; + #endif + } +} + +void stbte_set_tile(stbte_tilemap *tm, int x, int y, int layer, signed short tile) +{ + STBTE_ASSERT(x >= 0 && x < tm->max_x && y >= 0 && y < tm->max_y); + STBTE_ASSERT(layer >= 0 && layer < tm->num_layers); + STBTE_ASSERT(tile >= -1); + //STBTE_ASSERT(tile < 32768); + if (x < 0 || x >= STBTE_MAX_TILEMAP_X || y < 0 || y >= STBTE_MAX_TILEMAP_Y) + return; + if (layer < 0 || layer >= tm->num_layers || tile < -1) + return; + tm->data[y][x][layer] = tile; +} + +static void stbte__choose_category(stbte_tilemap *tm, int category) +{ + int i,n=0; + tm->cur_category = category; + for (i=0; i < tm->num_tiles; ++i) + if (tm->tiles[i].category_id == category || category == -1) + ++n; + tm->cur_palette_count = n; + tm->palette_scroll = 0; +} + +static int stbte__strequal(char *p, char *q) +{ + while (*p) + if (*p++ != *q++) return 0; + return *q == 0; +} + +static void stbte__compute_tileinfo(stbte_tilemap *tm) +{ + int i,j; + + tm->num_categories=0; + + for (i=0; i < tm->num_tiles; ++i) { + stbte__tileinfo *t = &tm->tiles[i]; + // find category + for (j=0; j < tm->num_categories; ++j) + if (stbte__strequal(t->category, tm->categories[j])) + goto found; + tm->categories[j] = t->category; + ++tm->num_categories; + found: + t->category_id = (unsigned short) j; + } + + // currently number of categories can never decrease because you + // can't remove tile definitions, but let's get it right anyway + if (tm->cur_category > tm->num_categories) { + tm->cur_category = -1; + } + + stbte__choose_category(tm, tm->cur_category); + + tm->tileinfo_dirty = 0; +} + +static void stbte__prepare_tileinfo(stbte_tilemap *tm) +{ + if (tm->tileinfo_dirty) + stbte__compute_tileinfo(tm); +} + + +/////////////////////// undo system //////////////////////// + +// the undo system works by storing "commands" into a buffer, and +// then playing back those commands. undo and redo have to store +// the commands in different order. +// +// the commands are: +// +// 1) end_of_undo_record +// -1:short +// +// 2) end_of_redo_record +// -2:short +// +// 3) tile update +// tile_id:short (-1..32767) +// x_coord:short +// y_coord:short +// layer:short (0..31) +// +// 4) property update (also used for links) +// value_hi:short +// value_lo:short +// y_coord:short +// x_coord:short +// property:short (256+prop#) +// +// Since we use a circular buffer, we might overwrite the undo storage. +// To detect this, before playing back commands we scan back and see +// if we see an end_of_undo_record before hitting the relevant boundary, +// it's wholly contained. +// +// When we read back through, we see them in reverse order, so +// we'll see the layer number or property number first +// +// To be clearer about the circular buffer, there are two cases: +// 1. a single record is larger than the whole buffer. +// this is caught because the end_of_undo_record will +// get overwritten. +// 2. multiple records written are larger than the whole +// buffer, so some of them have been overwritten by +// the later ones. this is handled by explicitly tracking +// the undo length; we never try to parse the data that +// got overwritten + +// given two points, compute the length between them +#define stbte__wrap(pos) ((pos) & (STBTE__UNDO_BUFFER_COUNT-1)) + +#define STBTE__undo_record -2 +#define STBTE__redo_record -3 +#define STBTE__undo_junk -4 // this is written underneath the undo pointer, never used + +static void stbte__write_undo(stbte_tilemap *tm, short value) +{ + int pos = tm->undo_pos; + tm->undo_buffer[pos] = value; + tm->undo_pos = stbte__wrap(pos+1); + tm->undo_len += (tm->undo_len < STBTE__UNDO_BUFFER_COUNT-2); + tm->redo_len -= (tm->redo_len > 0); + tm->undo_available_valid = 0; +} + +static void stbte__write_redo(stbte_tilemap *tm, short value) +{ + int pos = tm->undo_pos; + tm->undo_buffer[pos] = value; + tm->undo_pos = stbte__wrap(pos-1); + tm->redo_len += (tm->redo_len < STBTE__UNDO_BUFFER_COUNT-2); + tm->undo_len -= (tm->undo_len > 0); + tm->undo_available_valid = 0; +} + +static void stbte__begin_undo(stbte_tilemap *tm) +{ + tm->redo_len = 0; + stbte__write_undo(tm, STBTE__undo_record); + stbte__ui.undoing = 1; + stbte__ui.alert_msg = 0; // clear alert if they start doing something +} + +static void stbte__end_undo(stbte_tilemap *tm) +{ + if (stbte__ui.undoing) { + // check if anything got written + int pos = stbte__wrap(tm->undo_pos-1); + if (tm->undo_buffer[pos] == STBTE__undo_record) { + // empty undo record, move back + tm->undo_pos = pos; + STBTE_ASSERT(tm->undo_len > 0); + tm->undo_len -= 1; + } + tm->undo_buffer[tm->undo_pos] = STBTE__undo_junk; + // otherwise do nothing + + stbte__ui.undoing = 0; + } +} + +static void stbte__undo_record(stbte_tilemap *tm, int x, int y, int i, int v) +{ + STBTE_ASSERT(stbte__ui.undoing); + if (stbte__ui.undoing) { + stbte__write_undo(tm, v); + stbte__write_undo(tm, x); + stbte__write_undo(tm, y); + stbte__write_undo(tm, i); + } +} + +static void stbte__redo_record(stbte_tilemap *tm, int x, int y, int i, int v) +{ + stbte__write_redo(tm, v); + stbte__write_redo(tm, x); + stbte__write_redo(tm, y); + stbte__write_redo(tm, i); +} + +static float stbte__extract_float(short s0, short s1) +{ + union { float f; short s[2]; } converter; + converter.s[0] = s0; + converter.s[1] = s1; + return converter.f; +} + +static short stbte__extract_short(float f, int slot) +{ + union { float f; short s[2]; } converter; + converter.f = f; + return converter.s[slot]; +} + +static void stbte__undo_record_prop(stbte_tilemap *tm, int x, int y, int i, short s0, short s1) +{ + STBTE_ASSERT(stbte__ui.undoing); + if (stbte__ui.undoing) { + stbte__write_undo(tm, s1); + stbte__write_undo(tm, s0); + stbte__write_undo(tm, x); + stbte__write_undo(tm, y); + stbte__write_undo(tm, 256+i); + } +} + +static void stbte__undo_record_prop_float(stbte_tilemap *tm, int x, int y, int i, float f) +{ + stbte__undo_record_prop(tm, x,y,i, stbte__extract_short(f,0), stbte__extract_short(f,1)); +} + +static void stbte__redo_record_prop(stbte_tilemap *tm, int x, int y, int i, short s0, short s1) +{ + stbte__write_redo(tm, s1); + stbte__write_redo(tm, s0); + stbte__write_redo(tm, x); + stbte__write_redo(tm, y); + stbte__write_redo(tm, 256+i); +} + + +static int stbte__undo_find_end(stbte_tilemap *tm) +{ + // first scan through for the end record + int i, pos = stbte__wrap(tm->undo_pos-1); + for (i=0; i < tm->undo_len;) { + STBTE_ASSERT(tm->undo_buffer[pos] != STBTE__undo_junk); + if (tm->undo_buffer[pos] == STBTE__undo_record) + break; + if (tm->undo_buffer[pos] >= 255) + pos = stbte__wrap(pos-5), i += 5; + else + pos = stbte__wrap(pos-4), i += 4; + } + if (i >= tm->undo_len) + return -1; + return pos; +} + +static void stbte__undo(stbte_tilemap *tm) +{ + int i, pos, endpos; + endpos = stbte__undo_find_end(tm); + if (endpos < 0) + return; + + // we found a complete undo record + pos = stbte__wrap(tm->undo_pos-1); + + // start a redo record + stbte__write_redo(tm, STBTE__redo_record); + + // so now go back through undo and apply in reverse + // order, and copy it to redo + for (i=0; endpos != pos; i += 4) { + int x,y,n,v; + // get the undo entry + n = tm->undo_buffer[pos]; + y = tm->undo_buffer[stbte__wrap(pos-1)]; + x = tm->undo_buffer[stbte__wrap(pos-2)]; + v = tm->undo_buffer[stbte__wrap(pos-3)]; + if (n >= 255) { + short s0=0,s1=0; + int v2 = tm->undo_buffer[stbte__wrap(pos-4)]; + pos = stbte__wrap(pos-5); + if (n > 255) { + float vf = stbte__extract_float(v, v2); + s0 = stbte__extract_short(tm->props[y][x][n-256], 0); + s1 = stbte__extract_short(tm->props[y][x][n-256], 1); + tm->props[y][x][n-256] = vf; + } else { +#ifdef STBTE_ALLOW_LINK + s0 = tm->link[y][x].x; + s1 = tm->link[y][x].y; + stbte__set_link(tm, x,y, v, v2, STBTE__undo_none); +#endif + } + // write the redo entry + stbte__redo_record_prop(tm, x, y, n-256, s0,s1); + // apply the undo entry + } else { + pos = stbte__wrap(pos-4); + // write the redo entry + stbte__redo_record(tm, x, y, n, tm->data[y][x][n]); + // apply the undo entry + tm->data[y][x][n] = (short) v; + } + } + // overwrite undo record with junk + tm->undo_buffer[tm->undo_pos] = STBTE__undo_junk; +} + +static int stbte__redo_find_end(stbte_tilemap *tm) +{ + // first scan through for the end record + int i, pos = stbte__wrap(tm->undo_pos+1); + for (i=0; i < tm->redo_len;) { + STBTE_ASSERT(tm->undo_buffer[pos] != STBTE__undo_junk); + if (tm->undo_buffer[pos] == STBTE__redo_record) + break; + if (tm->undo_buffer[pos] >= 255) + pos = stbte__wrap(pos+5), i += 5; + else + pos = stbte__wrap(pos+4), i += 4; + } + if (i >= tm->redo_len) + return -1; // this should only ever happen if redo buffer is empty + return pos; +} + +static void stbte__redo(stbte_tilemap *tm) +{ + // first scan through for the end record + int i, pos, endpos; + endpos = stbte__redo_find_end(tm); + if (endpos < 0) + return; + + // we found a complete redo record + pos = stbte__wrap(tm->undo_pos+1); + + // start an undo record + stbte__write_undo(tm, STBTE__undo_record); + + for (i=0; pos != endpos; i += 4) { + int x,y,n,v; + n = tm->undo_buffer[pos]; + y = tm->undo_buffer[stbte__wrap(pos+1)]; + x = tm->undo_buffer[stbte__wrap(pos+2)]; + v = tm->undo_buffer[stbte__wrap(pos+3)]; + if (n >= 255) { + int v2 = tm->undo_buffer[stbte__wrap(pos+4)]; + short s0=0,s1=0; + pos = stbte__wrap(pos+5); + if (n > 255) { + float vf = stbte__extract_float(v, v2); + s0 = stbte__extract_short(tm->props[y][x][n-256],0); + s1 = stbte__extract_short(tm->props[y][x][n-256],1); + tm->props[y][x][n-256] = vf; + } else { +#ifdef STBTE_ALLOW_LINK + s0 = tm->link[y][x].x; + s1 = tm->link[y][x].y; + stbte__set_link(tm, x,y,v,v2, STBTE__undo_none); +#endif + } + // don't use stbte__undo_record_prop because it's guarded + stbte__write_undo(tm, s1); + stbte__write_undo(tm, s0); + stbte__write_undo(tm, x); + stbte__write_undo(tm, y); + stbte__write_undo(tm, n); + } else { + pos = stbte__wrap(pos+4); + // don't use stbte__undo_record because it's guarded + stbte__write_undo(tm, tm->data[y][x][n]); + stbte__write_undo(tm, x); + stbte__write_undo(tm, y); + stbte__write_undo(tm, n); + tm->data[y][x][n] = (short) v; + } + } + tm->undo_buffer[tm->undo_pos] = STBTE__undo_junk; +} + +// because detecting that undo is available +static void stbte__recompute_undo_available(stbte_tilemap *tm) +{ + tm->undo_available = (stbte__undo_find_end(tm) >= 0); + tm->redo_available = (stbte__redo_find_end(tm) >= 0); +} + +static int stbte__undo_available(stbte_tilemap *tm) +{ + if (!tm->undo_available_valid) + stbte__recompute_undo_available(tm); + return tm->undo_available; +} + +static int stbte__redo_available(stbte_tilemap *tm) +{ + if (!tm->undo_available_valid) + stbte__recompute_undo_available(tm); + return tm->redo_available; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef STBTE_ALLOW_LINK +static void stbte__set_link(stbte_tilemap *tm, int src_x, int src_y, int dest_x, int dest_y, int undo_mode) +{ + stbte__link *a; + STBTE_ASSERT(src_x >= 0 && src_x < STBTE_MAX_TILEMAP_X && src_y >= 0 && src_y < STBTE_MAX_TILEMAP_Y); + a = &tm->link[src_y][src_x]; + // check if it's a do nothing + if (a->x == dest_x && a->y == dest_y) + return; + if (undo_mode != STBTE__undo_none ) { + if (undo_mode == STBTE__undo_block) stbte__begin_undo(tm); + stbte__undo_record_prop(tm, src_x, src_y, -1, a->x, a->y); + if (undo_mode == STBTE__undo_block) stbte__end_undo(tm); + } + // check if there's an existing link + if (a->x >= 0) { + // decrement existing link refcount + STBTE_ASSERT(tm->linkcount[a->y][a->x] > 0); + --tm->linkcount[a->y][a->x]; + } + // increment new dest + if (dest_x >= 0) { + ++tm->linkcount[dest_y][dest_x]; + } + a->x = dest_x; + a->y = dest_y; +} +#endif + + +static void stbte__draw_rect(int x0, int y0, int x1, int y1, unsigned int color) +{ + STBTE_DRAW_RECT(x0,y0,x1,y1, color); +} + +#ifdef STBTE_ALLOW_LINK +static void stbte__draw_line(int x0, int y0, int x1, int y1, unsigned int color) +{ + int temp; + if (x1 < x0) temp=x0,x0=x1,x1=temp; + if (y1 < y0) temp=y0,y0=y1,y1=temp; + stbte__draw_rect(x0,y0,x1+1,y1+1,color); +} + +static void stbte__draw_link(int x0, int y0, int x1, int y1, unsigned int color) +{ + stbte__draw_line(x0,y0,x0,y1, color); + stbte__draw_line(x0,y1,x1,y1, color); +} +#endif + +static void stbte__draw_frame(int x0, int y0, int x1, int y1, unsigned int color) +{ + stbte__draw_rect(x0,y0,x1-1,y0+1,color); + stbte__draw_rect(x1-1,y0,x1,y1-1,color); + stbte__draw_rect(x0+1,y1-1,x1,y1,color); + stbte__draw_rect(x0,y0+1,x0+1,y1,color); +} + +static int stbte__get_char_width(int ch) +{ + return stbte__fontdata[ch-16]; +} + +static short *stbte__get_char_bitmap(int ch) +{ + return stbte__fontdata + stbte__font_offset[ch-16]; +} + +static void stbte__draw_bitmask_as_columns(int x, int y, short bitmask, int color) +{ + int start_i = -1, i=0; + while (bitmask) { + if (bitmask & (1<= 0) { + stbte__draw_rect(x, y+start_i, x+1, y+i, color); + start_i = -1; + bitmask &= ~((1< x_end) + break; + stbte__draw_bitmap(x, y, cw, stbte__get_char_bitmap(c), color); + if (digitspace && c == ' ') + cw = stbte__get_char_width('0'); + x += cw+1; + } +} + +static void stbte__draw_text(int x, int y, const char *str, int w, int color) +{ + stbte__draw_text_core(x,y,str,w,color,0); +} + +static int stbte__text_width(const char *str) +{ + int x = 0; + while (*str) { + int c = *str++; + int cw = stbte__get_char_width(c); + x += cw+1; + } + return x; +} + +static void stbte__draw_frame_delayed(int x0, int y0, int x1, int y1, int color) +{ + if (stbte__ui.delaycount < STBTE__MAX_DELAYRECT) { + stbte__colorrect r = { x0,y0,x1,y1,color }; + stbte__ui.delayrect[stbte__ui.delaycount++] = r; + } +} + +static void stbte__flush_delay(void) +{ + stbte__colorrect *r; + int i; + r = stbte__ui.delayrect; + for (i=0; i < stbte__ui.delaycount; ++i,++r) + stbte__draw_frame(r->x0,r->y0,r->x1,r->y1,r->color); + stbte__ui.delaycount = 0; +} + +static void stbte__activate(int id) +{ + stbte__ui.active_id = id; + stbte__ui.active_event = stbte__ui.event; + stbte__ui.accum_x = 0; + stbte__ui.accum_y = 0; +} + +static int stbte__hittest(int x0, int y0, int x1, int y1, int id) +{ + int over = stbte__ui.mx >= x0 && stbte__ui.my >= y0 + && stbte__ui.mx < x1 && stbte__ui.my < y1; + + if (over && stbte__ui.event >= STBTE__tick) + stbte__ui.next_hot_id = id; + + return over; +} + +static int stbte__button_core(int id) +{ + switch (stbte__ui.event) { + case STBTE__leftdown: + if (stbte__ui.hot_id == id && STBTE__INACTIVE()) + stbte__activate(id); + break; + case STBTE__leftup: + if (stbte__ui.active_id == id && STBTE__IS_HOT(id)) { + stbte__activate(0); + return 1; + } + break; + case STBTE__rightdown: + if (stbte__ui.hot_id == id && STBTE__INACTIVE()) + stbte__activate(id); + break; + case STBTE__rightup: + if (stbte__ui.active_id == id && STBTE__IS_HOT(id)) { + stbte__activate(0); + return -1; + } + break; + } + return 0; +} + +static void stbte__draw_box(int x0, int y0, int x1, int y1, int colormode, int colorindex) +{ + stbte__draw_rect (x0,y0,x1,y1, stbte__color_table[colormode][STBTE__base ][colorindex]); + stbte__draw_frame(x0,y0,x1,y1, stbte__color_table[colormode][STBTE__outline][colorindex]); +} + +static void stbte__draw_textbox(int x0, int y0, int x1, int y1, char *text, int xoff, int yoff, int colormode, int colorindex) +{ + stbte__draw_box(x0,y0,x1,y1,colormode,colorindex); + stbte__draw_text(x0+xoff,y0+yoff, text, x1-x0-xoff-1, stbte__color_table[colormode][STBTE__text][colorindex]); +} + +static int stbte__button(int colormode, const char *label, int x, int y, int textoff, int width, int id, int toggled, int disabled) +{ + int x0=x,y0=y, x1=x+width,y1=y+STBTE__BUTTON_HEIGHT; + int s = STBTE__BUTTON_INTERNAL_SPACING; + + if(!disabled) stbte__hittest(x0,y0,x1,y1,id); + + if (stbte__ui.event == STBTE__paint) + stbte__draw_textbox(x0,y0,x1,y1, (char*) label,s+textoff,s, colormode, STBTE__INDEX_FOR_ID(id,disabled,toggled)); + if (disabled) + return 0; + return (stbte__button_core(id) == 1); +} + +static int stbte__button_icon(int colormode, char ch, int x, int y, int width, int id, int toggled, int disabled) +{ + int x0=x,y0=y, x1=x+width,y1=y+STBTE__BUTTON_HEIGHT; + int s = STBTE__BUTTON_INTERNAL_SPACING; + + stbte__hittest(x0,y0,x1,y1,id); + + if (stbte__ui.event == STBTE__paint) { + char label[2] = { ch, 0 }; + int pad = (9 - stbte__get_char_width(ch))/2; + stbte__draw_textbox(x0,y0,x1,y1, label,s+pad,s, colormode, STBTE__INDEX_FOR_ID(id,disabled,toggled)); + } + if (disabled) + return 0; + return (stbte__button_core(id) == 1); +} + +static int stbte__minibutton(int colormode, int x, int y, int ch, int id) +{ + int x0 = x, y0 = y, x1 = x+8, y1 = y+7; + stbte__hittest(x0,y0,x1,y1,id); + if (stbte__ui.event == STBTE__paint) { + char str[2] = { (char)ch, 0 }; + stbte__draw_textbox(x0,y0,x1,y1, str,1,0,colormode, STBTE__INDEX_FOR_ID(id,0,0)); + } + return stbte__button_core(id); +} + +static int stbte__layerbutton(int x, int y, int ch, int id, int toggled, int disabled, int colormode) +{ + int x0 = x, y0 = y, x1 = x+10, y1 = y+11; + if(!disabled) stbte__hittest(x0,y0,x1,y1,id); + if (stbte__ui.event == STBTE__paint) { + char str[2] = { (char)ch, 0 }; + int off = (9-stbte__get_char_width(ch))/2; + stbte__draw_textbox(x0,y0,x1,y1, str, off+1,2, colormode, STBTE__INDEX_FOR_ID(id,disabled,toggled)); + } + if (disabled) + return 0; + return stbte__button_core(id); +} + +static int stbte__microbutton(int x, int y, int size, int id, int colormode) +{ + int x0 = x, y0 = y, x1 = x+size, y1 = y+size; + stbte__hittest(x0,y0,x1,y1,id); + if (stbte__ui.event == STBTE__paint) { + stbte__draw_box(x0,y0,x1,y1, colormode, STBTE__INDEX_FOR_ID(id,0,0)); + } + return stbte__button_core(id); +} + +static int stbte__microbutton_dragger(int x, int y, int size, int id, int *pos) +{ + int x0 = x, y0 = y, x1 = x+size, y1 = y+size; + stbte__hittest(x0,y0,x1,y1,id); + switch (stbte__ui.event) { + case STBTE__paint: + stbte__draw_box(x0,y0,x1,y1, STBTE__cexpander, STBTE__INDEX_FOR_ID(id,0,0)); + break; + case STBTE__leftdown: + if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) { + stbte__activate(id); + stbte__ui.sx = stbte__ui.mx - *pos; + } + break; + case STBTE__mousemove: + if (STBTE__IS_ACTIVE(id) && stbte__ui.active_event == STBTE__leftdown) { + *pos = stbte__ui.mx - stbte__ui.sx; + } + break; + case STBTE__leftup: + if (STBTE__IS_ACTIVE(id)) + stbte__activate(0); + break; + default: + return stbte__button_core(id); + } + return 0; +} + +static int stbte__category_button(const char *label, int x, int y, int width, int id, int toggled) +{ + int x0=x,y0=y, x1=x+width,y1=y+STBTE__BUTTON_HEIGHT; + int s = STBTE__BUTTON_INTERNAL_SPACING; + + stbte__hittest(x0,y0,x1,y1,id); + + if (stbte__ui.event == STBTE__paint) + stbte__draw_textbox(x0,y0,x1,y1, (char*) label, s,s, STBTE__ccategory_button, STBTE__INDEX_FOR_ID(id,0,toggled)); + + return (stbte__button_core(id) == 1); +} + +enum +{ + STBTE__none, + STBTE__begin, + STBTE__end, + STBTE__change, +}; + +// returns -1 if value changes, 1 at end of drag +static int stbte__slider(int x0, int w, int y, int range, int *value, int id) +{ + int x1 = x0+w; + int pos = *value * w / (range+1); + stbte__hittest(x0,y-2,x1,y+3,id); + int event_mouse_move = STBTE__change; + switch (stbte__ui.event) { + case STBTE__paint: + stbte__draw_rect(x0,y,x1,y+1, 0x808080); + stbte__draw_rect(x0+pos-1,y-1,x0+pos+2,y+2, 0xffffff); + break; + case STBTE__leftdown: + if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) { + stbte__activate(id); + event_mouse_move = STBTE__begin; + } + // fall through + case STBTE__mousemove: + if (STBTE__IS_ACTIVE(id)) { + int v = (stbte__ui.mx-x0)*(range+1)/w; + if (v < 0) v = 0; else if (v > range) v = range; + *value = v; + return event_mouse_move; + } + break; + case STBTE__leftup: + if (STBTE__IS_ACTIVE(id)) { + stbte__activate(0); + return STBTE__end; + } + break; + } + return STBTE__none; +} + +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + #define stbte__sprintf sprintf_s + #define stbte__sizeof(s) , sizeof(s) +#else + #define stbte__sprintf sprintf + #define stbte__sizeof(s) +#endif + +static int stbte__float_control(int x0, int y0, int w, float minv, float maxv, float scale, const char *fmt, float *value, int colormode, int id) +{ + int x1 = x0+w; + int y1 = y0+11; + stbte__hittest(x0,y0,x1,y1,id); + switch (stbte__ui.event) { + case STBTE__paint: { + char text[32]; + stbte__sprintf(text stbte__sizeof(text), fmt ? fmt : "%6.2f", *value); + stbte__draw_textbox(x0,y0,x1,y1, text, 1,2, colormode, STBTE__INDEX_FOR_ID(id,0,0)); + break; + } + case STBTE__leftdown: + case STBTE__rightdown: + if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) + stbte__activate(id); + return STBTE__begin; + break; + case STBTE__leftup: + case STBTE__rightup: + if (STBTE__IS_ACTIVE(id)) { + stbte__activate(0); + return STBTE__end; + } + break; + case STBTE__mousemove: + if (STBTE__IS_ACTIVE(id)) { + float v = *value, delta; + int ax = stbte__ui.accum_x/STBTE_FLOAT_CONTROL_GRANULARITY; + int ay = stbte__ui.accum_y/STBTE_FLOAT_CONTROL_GRANULARITY; + stbte__ui.accum_x -= ax*STBTE_FLOAT_CONTROL_GRANULARITY; + stbte__ui.accum_y -= ay*STBTE_FLOAT_CONTROL_GRANULARITY; + if (stbte__ui.shift) { + if (stbte__ui.active_event == STBTE__leftdown) + delta = ax * 16.0f + ay; + else + delta = ax / 16.0f + ay / 256.0f; + } else { + if (stbte__ui.active_event == STBTE__leftdown) + delta = ax*10.0f + ay; + else + delta = ax * 0.1f + ay * 0.01f; + } + v += delta * scale; + if (v < minv) v = minv; + if (v > maxv) v = maxv; + *value = v; + return STBTE__change; + } + break; + } + return STBTE__none; +} + +static void stbte__scrollbar(int x, int y0, int y1, int *val, int v0, int v1, int num_vis, int id) +{ + int thumbpos; + if (v1 - v0 <= num_vis) + return; + + // generate thumbpos from numvis + thumbpos = y0+2 + (y1-y0-4) * *val / (v1 - v0 - num_vis); + if (thumbpos < y0) thumbpos = y0; + if (thumbpos >= y1) thumbpos = y1; + stbte__hittest(x-1,y0,x+2,y1,id); + switch (stbte__ui.event) { + case STBTE__paint: + stbte__draw_rect(x,y0,x+1,y1, stbte__color_table[STBTE__cscrollbar][STBTE__text][STBTE__idle]); + stbte__draw_box(x-1,thumbpos-3,x+2,thumbpos+4, STBTE__cscrollbar, STBTE__INDEX_FOR_ID(id,0,0)); + break; + case STBTE__leftdown: + if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) { + // check if it's over the thumb + stbte__activate(id); + *val = ((stbte__ui.my-y0) * (v1 - v0 - num_vis) + (y1-y0)/2)/ (y1-y0); + } + break; + case STBTE__mousemove: + if (STBTE__IS_ACTIVE(id) && stbte__ui.mx >= x-15 && stbte__ui.mx <= x+15) + *val = ((stbte__ui.my-y0) * (v1 - v0 - num_vis) + (y1-y0)/2)/ (y1-y0); + break; + case STBTE__leftup: + if (STBTE__IS_ACTIVE(id)) + stbte__activate(0); + break; + + } + + if (*val >= v1-num_vis) + *val = v1-num_vis; + if (*val <= v0) + *val = v0; +} + + +static void stbte__compute_digits(stbte_tilemap *tm) +{ + if (tm->max_x >= 1000 || tm->max_y >= 1000) + tm->digits = 4; + else if (tm->max_x >= 100 || tm->max_y >= 100) + tm->digits = 3; + else + tm->digits = 2; +} + +static int stbte__is_single_selection(void) +{ + return stbte__ui.has_selection + && stbte__ui.select_x0 == stbte__ui.select_x1 + && stbte__ui.select_y0 == stbte__ui.select_y1; +} + +typedef struct +{ + int width, height; + int x,y; + int active; + float retracted; +} stbte__region_t; + +static stbte__region_t stbte__region[4]; + +#define STBTE__TOOLBAR_ICON_SIZE (9+2*2) +#define STBTE__TOOLBAR_PASTE_SIZE (34+2*2) + +// This routine computes where every panel goes onscreen: computes +// a minimum width for each side based on which panels are on that +// side, and accounts for width-dependent layout of certain panels. +static void stbte__compute_panel_locations(stbte_tilemap *tm) +{ + int i, limit, w, k; + int window_width = stbte__ui.x1 - stbte__ui.x0; + int window_height = stbte__ui.y1 - stbte__ui.y0; + int min_width[STBTE__num_panel]={0,0,0,0,0,0,0}; + int height[STBTE__num_panel]={0,0,0,0,0,0,0}; + int panel_active[STBTE__num_panel]={1,0,1,1,1,1,1}; + int vpos[4] = { 0,0,0,0 }; + stbte__panel *p = stbte__ui.panel; + stbte__panel *pt = &p[STBTE__panel_toolbar]; +#ifdef STBTE__NO_PROPS + int props = 0; +#else + int props = 1; +#endif + + for (i=0; i < 4; ++i) { + stbte__region[i].active = 0; + stbte__region[i].width = 0; + stbte__region[i].height = 0; + } + + // compute number of digits needs for info panel + stbte__compute_digits(tm); + + // determine which panels are active + panel_active[STBTE__panel_categories] = tm->num_categories != 0; + panel_active[STBTE__panel_layers ] = tm->num_layers > 1; +#ifdef STBTE__COLORPICKER + panel_active[STBTE__panel_colorpick ] = 1; +#endif + + panel_active[STBTE__panel_props ] = props && stbte__is_single_selection(); + + // compute minimum widths for each panel (assuming they're on sides not top) + min_width[STBTE__panel_info ] = 8 + 11 + 7*tm->digits+17+7; // estimate min width of "w:0000" + min_width[STBTE__panel_colorpick ] = 120; + min_width[STBTE__panel_tiles ] = 4 + tm->palette_spacing_x + 5; // 5 for scrollbar + min_width[STBTE__panel_categories] = 4 + 42 + 5; // 42 is enough to show ~7 chars; 5 for scrollbar + min_width[STBTE__panel_layers ] = 4 + 54 + 30*tm->has_layer_names; // 2 digits plus 3 buttons plus scrollbar + min_width[STBTE__panel_toolbar ] = 4 + STBTE__TOOLBAR_PASTE_SIZE; // wide enough for 'Paste' button + min_width[STBTE__panel_props ] = 80; // narrowest info panel + + // compute minimum widths for left & right panels based on the above + stbte__region[0].width = stbte__ui.left_width; + stbte__region[1].width = stbte__ui.right_width; + + for (i=0; i < STBTE__num_panel; ++i) { + if (panel_active[i]) { + int side = stbte__ui.panel[i].side; + if (min_width[i] > stbte__region[side].width) + stbte__region[side].width = min_width[i]; + stbte__region[side].active = 1; + } + } + + // now compute the heights of each panel + + // if toolbar at top, compute its size & push the left and right start points down + if (stbte__region[STBTE__side_top].active) { + int height = STBTE__TOOLBAR_ICON_SIZE+2; + pt->x0 = stbte__ui.x0; + pt->y0 = stbte__ui.y0; + pt->width = window_width; + pt->height = height; + vpos[STBTE__side_left] = vpos[STBTE__side_right] = height; + } else { + int num_rows = STBTE__num_tool * ((stbte__region[pt->side].width-4)/STBTE__TOOLBAR_ICON_SIZE); + height[STBTE__panel_toolbar] = num_rows*13 + 3*15 + 4; // 3*15 for cut/copy/paste, which are stacked vertically + } + + for (i=0; i < 4; ++i) + stbte__region[i].y = stbte__ui.y0 + vpos[i]; + + for (i=0; i < 2; ++i) { + int anim = (int) (stbte__region[i].width * stbte__region[i].retracted); + stbte__region[i].x = (i == STBTE__side_left) ? stbte__ui.x0 - anim : stbte__ui.x1 - stbte__region[i].width + anim; + } + + // color picker + height[STBTE__panel_colorpick] = 300; + + // info panel + w = stbte__region[p[STBTE__panel_info].side].width; + p[STBTE__panel_info].mode = (w >= 8 + (11+7*tm->digits+17)*2 + 4); + if (p[STBTE__panel_info].mode) + height[STBTE__panel_info] = 5 + 11*2 + 2 + tm->palette_spacing_y; + else + height[STBTE__panel_info] = 5 + 11*4 + 2 + tm->palette_spacing_y; + + // layers + limit = 6 + stbte__ui.panel[STBTE__panel_layers].delta_height; + height[STBTE__panel_layers] = (tm->num_layers > limit ? limit : tm->num_layers)*15 + 7 + (tm->has_layer_names ? 0 : 11) + props*13; + + // categories + limit = 6 + stbte__ui.panel[STBTE__panel_categories].delta_height; + height[STBTE__panel_categories] = (tm->num_categories+1 > limit ? limit : tm->num_categories+1)*11 + 14; + if (stbte__ui.panel[STBTE__panel_categories].side == stbte__ui.panel[STBTE__panel_categories].side) + height[STBTE__panel_categories] -= 4; + + // palette + k = (stbte__region[p[STBTE__panel_tiles].side].width - 8) / tm->palette_spacing_x; + if (k == 0) k = 1; + height[STBTE__panel_tiles] = ((tm->num_tiles+k-1)/k) * tm->palette_spacing_y + 8; + + // properties panel + height[STBTE__panel_props] = 9 + STBTE_MAX_PROPERTIES*14; + + // now compute the locations of all the panels + for (i=0; i < STBTE__num_panel; ++i) { + if (panel_active[i]) { + int side = p[i].side; + if (side == STBTE__side_left || side == STBTE__side_right) { + p[i].width = stbte__region[side].width; + p[i].x0 = stbte__region[side].x; + p[i].y0 = stbte__ui.y0 + vpos[side]; + p[i].height = height[i]; + vpos[side] += height[i]; + if (vpos[side] > window_height) { + vpos[side] = window_height; + p[i].height = stbte__ui.y1 - p[i].y0; + } + } else { + ; // it's at top, it's already been explicitly set up earlier + } + } else { + // inactive panel + p[i].height = 0; + p[i].width = 0; + p[i].x0 = stbte__ui.x1; + p[i].y0 = stbte__ui.y1; + } + } +} + +// unique identifiers for imgui +enum +{ + STBTE__map=1, + STBTE__region, + STBTE__panel, // panel background to hide map, and misc controls + STBTE__info, // info data + STBTE__toolbarA, STBTE__toolbarB, // toolbar buttons: param is tool number + STBTE__palette, // palette selectors: param is tile index + STBTE__categories, // category selectors: param is category index + STBTE__layer, // + STBTE__solo, STBTE__hide, STBTE__lock, // layer controls: param is layer + STBTE__scrollbar, // param is panel ID + STBTE__panel_mover, // p1 is panel ID, p2 is destination side + STBTE__panel_sizer, // param panel ID + STBTE__scrollbar_id, + STBTE__colorpick_id, + STBTE__prop_flag, + STBTE__prop_float, + STBTE__prop_int, +}; + +// id is: [ 24-bit data : 7-bit identifier ] +// map id is: [ 12-bit y : 12 bit x : 7-bit identifier ] + +#define STBTE__ID(n,p) ((n) + ((p)<<7)) +#define STBTE__ID2(n,p,q) STBTE__ID(n, ((p)<<12)+(q) ) +#define STBTE__IDMAP(x,y) STBTE__ID2(STBTE__map, x,y) + +static void stbte__activate_map(int x, int y) +{ + stbte__ui.active_id = STBTE__IDMAP(x,y); + stbte__ui.active_event = stbte__ui.event; + stbte__ui.sx = x; + stbte__ui.sy = y; +} + +static void stbte__alert(const char *msg) +{ + stbte__ui.alert_msg = msg; + stbte__ui.alert_timer = 3; +} + +#define STBTE__BG(tm,layer) ((layer) == 0 ? (tm)->background_tile : STBTE__NO_TILE) + + + +static void stbte__brush_predict(stbte_tilemap *tm, short result[]) +{ + stbte__tileinfo *ti; + int i; + + if (tm->cur_tile < 0) return; + + ti = &tm->tiles[tm->cur_tile]; + + // find lowest legit layer to paint it on, and put it there + for (i=0; i < tm->num_layers; ++i) { + // check if object is allowed on layer + if (!(ti->layermask & (1 << i))) + continue; + + if (i != tm->solo_layer) { + // if there's a selected layer, can only paint on that + if (tm->cur_layer >= 0 && i != tm->cur_layer) + continue; + + // if the layer is hidden, we can't see it + if (tm->layerinfo[i].hidden) + continue; + + // if the layer is locked, we can't write to it + if (tm->layerinfo[i].locked == STBTE__locked) + continue; + + // if the layer is non-empty and protected, can't write to it + if (tm->layerinfo[i].locked == STBTE__protected && result[i] != STBTE__BG(tm,i)) + continue; + } + + result[i] = ti->id; + return; + } +} + +static void stbte__brush(stbte_tilemap *tm, int x, int y) +{ + stbte__tileinfo *ti; + + // find lowest legit layer to paint it on, and put it there + int i; + + if (tm->cur_tile < 0) return; + + ti = &tm->tiles[tm->cur_tile]; + + for (i=0; i < tm->num_layers; ++i) { + // check if object is allowed on layer + if (!(ti->layermask & (1 << i))) + continue; + + if (i != tm->solo_layer) { + // if there's a selected layer, can only paint on that + if (tm->cur_layer >= 0 && i != tm->cur_layer) + continue; + + // if the layer is hidden, we can't see it + if (tm->layerinfo[i].hidden) + continue; + + // if the layer is locked, we can't write to it + if (tm->layerinfo[i].locked == STBTE__locked) + continue; + + // if the layer is non-empty and protected, can't write to it + if (tm->layerinfo[i].locked == STBTE__protected && tm->data[y][x][i] != STBTE__BG(tm,i)) + continue; + } + + stbte__undo_record(tm,x,y,i,tm->data[y][x][i]); + tm->data[y][x][i] = ti->id; + return; + } + + //stbte__alert("Selected tile not valid on active layer(s)"); +} + +enum +{ + STBTE__erase_none = -1, + STBTE__erase_brushonly = 0, + STBTE__erase_any = 1, + STBTE__erase_all = 2, +}; + +static int stbte__erase_predict(stbte_tilemap *tm, short result[], int allow_any) +{ + stbte__tileinfo *ti = tm->cur_tile >= 0 ? &tm->tiles[tm->cur_tile] : NULL; + int i; + + if (allow_any == STBTE__erase_none) + return allow_any; + + // first check if only one layer is legit + i = tm->cur_layer; + if (tm->solo_layer >= 0) + i = tm->solo_layer; + + // if only one layer is legit, directly process that one for clarity + if (i >= 0) { + short bg = (i == 0 ? tm->background_tile : -1); + if (tm->solo_layer < 0) { + // check that we're allowed to write to it + if (tm->layerinfo[i].hidden) return STBTE__erase_none; + if (tm->layerinfo[i].locked) return STBTE__erase_none; + } + if (result[i] == bg) + return STBTE__erase_none; // didn't erase anything + if (ti && result[i] == ti->id && (i != 0 || ti->id != tm->background_tile)) { + result[i] = bg; + return STBTE__erase_brushonly; + } + if (allow_any == STBTE__erase_any) { + result[i] = bg; + return STBTE__erase_any; + } + return STBTE__erase_none; + } + + // if multiple layers are legit, first scan all for brush data + + if (ti && allow_any != STBTE__erase_all) { + for (i=tm->num_layers-1; i >= 0; --i) { + if (result[i] != ti->id) + continue; + if (tm->layerinfo[i].locked || tm->layerinfo[i].hidden) + continue; + if (i == 0 && result[i] == tm->background_tile) + return STBTE__erase_none; + result[i] = STBTE__BG(tm,i); + return STBTE__erase_brushonly; + } + } + + if (allow_any != STBTE__erase_any && allow_any != STBTE__erase_all) + return STBTE__erase_none; + + // apply layer filters, erase from top + for (i=tm->num_layers-1; i >= 0; --i) { + if (result[i] < 0) + continue; + if (tm->layerinfo[i].locked || tm->layerinfo[i].hidden) + continue; + if (i == 0 && result[i] == tm->background_tile) + return STBTE__erase_none; + result[i] = STBTE__BG(tm,i); + if (allow_any != STBTE__erase_all) + return STBTE__erase_any; + } + + if (allow_any == STBTE__erase_all) + return allow_any; + return STBTE__erase_none; +} + +static int stbte__erase(stbte_tilemap *tm, int x, int y, int allow_any) +{ + stbte__tileinfo *ti = tm->cur_tile >= 0 ? &tm->tiles[tm->cur_tile] : NULL; + int i; + + if (allow_any == STBTE__erase_none) + return allow_any; + + // first check if only one layer is legit + i = tm->cur_layer; + if (tm->solo_layer >= 0) + i = tm->solo_layer; + + // if only one layer is legit, directly process that one for clarity + if (i >= 0) { + short bg = (i == 0 ? tm->background_tile : -1); + if (tm->solo_layer < 0) { + // check that we're allowed to write to it + if (tm->layerinfo[i].hidden) return STBTE__erase_none; + if (tm->layerinfo[i].locked) return STBTE__erase_none; + } + if (tm->data[y][x][i] == bg) + return -1; // didn't erase anything + if (ti && tm->data[y][x][i] == ti->id && (i != 0 || ti->id != tm->background_tile)) { + stbte__undo_record(tm,x,y,i,tm->data[y][x][i]); + tm->data[y][x][i] = bg; + return STBTE__erase_brushonly; + } + if (allow_any == STBTE__erase_any) { + stbte__undo_record(tm,x,y,i,tm->data[y][x][i]); + tm->data[y][x][i] = bg; + return STBTE__erase_any; + } + return STBTE__erase_none; + } + + // if multiple layers are legit, first scan all for brush data + + if (ti && allow_any != STBTE__erase_all) { + for (i=tm->num_layers-1; i >= 0; --i) { + if (tm->data[y][x][i] != ti->id) + continue; + if (tm->layerinfo[i].locked || tm->layerinfo[i].hidden) + continue; + if (i == 0 && tm->data[y][x][i] == tm->background_tile) + return STBTE__erase_none; + stbte__undo_record(tm,x,y,i,tm->data[y][x][i]); + tm->data[y][x][i] = STBTE__BG(tm,i); + return STBTE__erase_brushonly; + } + } + + if (allow_any != STBTE__erase_any && allow_any != STBTE__erase_all) + return STBTE__erase_none; + + // apply layer filters, erase from top + for (i=tm->num_layers-1; i >= 0; --i) { + if (tm->data[y][x][i] < 0) + continue; + if (tm->layerinfo[i].locked || tm->layerinfo[i].hidden) + continue; + if (i == 0 && tm->data[y][x][i] == tm->background_tile) + return STBTE__erase_none; + stbte__undo_record(tm,x,y,i,tm->data[y][x][i]); + tm->data[y][x][i] = STBTE__BG(tm,i); + if (allow_any != STBTE__erase_all) + return STBTE__erase_any; + } + if (allow_any == STBTE__erase_all) + return allow_any; + return STBTE__erase_none; +} + +static int stbte__find_tile(stbte_tilemap *tm, int tile_id) +{ + int i; + for (i=0; i < tm->num_tiles; ++i) + if (tm->tiles[i].id == tile_id) + return i; + stbte__alert("Eyedropped tile that isn't in tileset"); + return -1; +} + +static void stbte__eyedrop(stbte_tilemap *tm, int x, int y) +{ + int i,j; + + // flush eyedropper state + if (stbte__ui.eyedrop_x != x || stbte__ui.eyedrop_y != y) { + stbte__ui.eyedrop_x = x; + stbte__ui.eyedrop_y = y; + stbte__ui.eyedrop_last_layer = tm->num_layers; + } + + // if only one layer is active, query that + i = tm->cur_layer; + if (tm->solo_layer >= 0) + i = tm->solo_layer; + if (i >= 0) { + if (tm->data[y][x][i] == STBTE__NO_TILE) + return; + tm->cur_tile = stbte__find_tile(tm, tm->data[y][x][i]); + return; + } + + // if multiple layers, continue from previous + i = stbte__ui.eyedrop_last_layer; + for (j=0; j < tm->num_layers; ++j) { + if (--i < 0) + i = tm->num_layers-1; + if (tm->layerinfo[i].hidden) + continue; + if (tm->data[y][x][i] == STBTE__NO_TILE) + continue; + stbte__ui.eyedrop_last_layer = i; + tm->cur_tile = stbte__find_tile(tm, tm->data[y][x][i]); + return; + } +} + +static int stbte__should_copy_properties(stbte_tilemap *tm) +{ + int i; + if (tm->propmode == STBTE__propmode_always) + return 1; + if (tm->propmode == STBTE__propmode_never) + return 0; + if (tm->solo_layer >= 0 || tm->cur_layer >= 0) + return 0; + for (i=0; i < tm->num_layers; ++i) + if (tm->layerinfo[i].hidden || tm->layerinfo[i].locked) + return 0; + return 1; +} + +// compute the result of pasting into a tile non-destructively so we can preview it +static void stbte__paste_stack(stbte_tilemap *tm, short result[], short dest[], short src[], int dragging) +{ + int i; + + // special case single-layer + i = tm->cur_layer; + if (tm->solo_layer >= 0) + i = tm->solo_layer; + if (i >= 0) { + if (tm->solo_layer < 0) { + // check that we're allowed to write to it + if (tm->layerinfo[i].hidden) return; + if (tm->layerinfo[i].locked == STBTE__locked) return; + // if protected, dest has to be empty + if (tm->layerinfo[i].locked == STBTE__protected && dest[i] != STBTE__BG(tm,i)) return; + // if dragging w/o copy, we will try to erase stuff, which protection disallows + if (dragging && tm->layerinfo[i].locked == STBTE__protected) + return; + } + result[i] = dest[i]; + if (src[i] != STBTE__BG(tm,i)) + result[i] = src[i]; + return; + } + + for (i=0; i < tm->num_layers; ++i) { + result[i] = dest[i]; + if (src[i] != STBTE__NO_TILE) + if (!tm->layerinfo[i].hidden && tm->layerinfo[i].locked != STBTE__locked) + if (tm->layerinfo[i].locked == STBTE__unlocked || (!dragging && dest[i] == STBTE__BG(tm,i))) + result[i] = src[i]; + } +} + +// compute the result of dragging away from a tile +static void stbte__clear_stack(stbte_tilemap *tm, short result[]) +{ + int i; + // special case single-layer + i = tm->cur_layer; + if (tm->solo_layer >= 0) + i = tm->solo_layer; + if (i >= 0) + result[i] = STBTE__BG(tm,i); + else + for (i=0; i < tm->num_layers; ++i) + if (!tm->layerinfo[i].hidden && tm->layerinfo[i].locked == STBTE__unlocked) + result[i] = STBTE__BG(tm,i); +} + +// check if some map square is active +#define STBTE__IS_MAP_ACTIVE() ((stbte__ui.active_id & 127) == STBTE__map) +#define STBTE__IS_MAP_HOT() ((stbte__ui.hot_id & 127) == STBTE__map) + +static void stbte__fillrect(stbte_tilemap *tm, int x0, int y0, int x1, int y1, int fill) +{ + int i,j; + + stbte__begin_undo(tm); + if (x0 > x1) i=x0,x0=x1,x1=i; + if (y0 > y1) j=y0,y0=y1,y1=j; + for (j=y0; j <= y1; ++j) + for (i=x0; i <= x1; ++i) + if (fill) + stbte__brush(tm, i,j); + else + stbte__erase(tm, i,j,STBTE__erase_any); + stbte__end_undo(tm); + // suppress warning from brush + stbte__ui.alert_msg = 0; +} + +static void stbte__select_rect(stbte_tilemap *tm, int x0, int y0, int x1, int y1) +{ + stbte__ui.has_selection = 1; + stbte__ui.select_x0 = (x0 < x1 ? x0 : x1); + stbte__ui.select_x1 = (x0 < x1 ? x1 : x0); + stbte__ui.select_y0 = (y0 < y1 ? y0 : y1); + stbte__ui.select_y1 = (y0 < y1 ? y1 : y0); +} + +static void stbte__copy_properties(float *dest, float *src) +{ + int i; + for (i=0; i < STBTE_MAX_PROPERTIES; ++i) + dest[i] = src[i]; +} + +static void stbte__copy_cut(stbte_tilemap *tm, int cut) +{ + int i,j,n,w,h,p=0; + int copy_props = stbte__should_copy_properties(tm); + if (!stbte__ui.has_selection) + return; + w = stbte__ui.select_x1 - stbte__ui.select_x0 + 1; + h = stbte__ui.select_y1 - stbte__ui.select_y0 + 1; + if (STBTE_MAX_COPY / w < h) { + stbte__alert("Selection too large for copy buffer, increase STBTE_MAX_COPY"); + return; + } + + for (i=0; i < w*h; ++i) + for (n=0; n < tm->num_layers; ++n) + stbte__ui.copybuffer[i][n] = STBTE__NO_TILE; + + if (cut) + stbte__begin_undo(tm); + for (j=stbte__ui.select_y0; j <= stbte__ui.select_y1; ++j) { + for (i=stbte__ui.select_x0; i <= stbte__ui.select_x1; ++i) { + for (n=0; n < tm->num_layers; ++n) { + if (tm->solo_layer >= 0) { + if (tm->solo_layer != n) + continue; + } else { + if (tm->cur_layer >= 0) + if (tm->cur_layer != n) + continue; + if (tm->layerinfo[n].hidden) + continue; + if (cut && tm->layerinfo[n].locked) + continue; + } + stbte__ui.copybuffer[p][n] = tm->data[j][i][n]; + if (cut) { + stbte__undo_record(tm,i,j,n, tm->data[j][i][n]); + tm->data[j][i][n] = (n==0 ? tm->background_tile : -1); + } + } + if (copy_props) { + stbte__copy_properties(stbte__ui.copyprops[p], tm->props[j][i]); +#ifdef STBTE_ALLOW_LINK + stbte__ui.copylinks[p] = tm->link[j][i]; + if (cut) + stbte__set_link(tm, i,j,-1,-1, STBTE__undo_record); +#endif + } + ++p; + } + } + if (cut) + stbte__end_undo(tm); + stbte__ui.copy_width = w; + stbte__ui.copy_height = h; + stbte__ui.has_copy = 1; + //stbte__ui.has_selection = 0; + stbte__ui.copy_has_props = copy_props; + stbte__ui.copy_src = tm; // used to give better semantics when copying links + stbte__ui.copy_src_x = stbte__ui.select_x0; + stbte__ui.copy_src_y = stbte__ui.select_y0; +} + +static int stbte__in_rect(int x, int y, int x0, int y0, int w, int h) +{ + return x >= x0 && x < x0+w && y >= y0 && y < y0+h; +} + +#ifdef STBTE_ALLOW_LINK +static int stbte__in_src_rect(int x, int y) +{ + return stbte__in_rect(x,y, stbte__ui.copy_src_x, stbte__ui.copy_src_y, stbte__ui.copy_width, stbte__ui.copy_height); +} + +static int stbte__in_dest_rect(int x, int y, int destx, int desty) +{ + return stbte__in_rect(x,y, destx, desty, stbte__ui.copy_width, stbte__ui.copy_height); +} +#endif + +static void stbte__paste(stbte_tilemap *tm, int mapx, int mapy) +{ + int w = stbte__ui.copy_width; + int h = stbte__ui.copy_height; + int i,j,k,p; + int x = mapx - (w>>1); + int y = mapy - (h>>1); + int copy_props = stbte__should_copy_properties(tm) && stbte__ui.copy_has_props; + if (stbte__ui.has_copy == 0) + return; + stbte__begin_undo(tm); + p = 0; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + if (y+j >= 0 && y+j < tm->max_y && x+i >= 0 && x+i < tm->max_x) { + // compute the new stack + short tilestack[STBTE_MAX_LAYERS]; + for (k=0; k < tm->num_layers; ++k) + tilestack[k] = tm->data[y+j][x+i][k]; + stbte__paste_stack(tm, tilestack, tilestack, stbte__ui.copybuffer[p], 0); + // update anything that changed + for (k=0; k < tm->num_layers; ++k) { + if (tilestack[k] != tm->data[y+j][x+i][k]) { + stbte__undo_record(tm, x+i,y+j,k, tm->data[y+j][x+i][k]); + tm->data[y+j][x+i][k] = tilestack[k]; + } + } + } + if (copy_props) { +#ifdef STBTE_ALLOW_LINK + // need to decide how to paste a link, so there's a few cases + int destx = -1, desty = -1; + stbte__link *link = &stbte__ui.copylinks[p]; + + // check if link is within-rect + if (stbte__in_src_rect(link->x, link->y)) { + // new link should point to copy (but only if copy is within map) + destx = x + (link->x - stbte__ui.copy_src_x); + desty = y + (link->y - stbte__ui.copy_src_y); + } else if (tm == stbte__ui.copy_src) { + // if same map, then preserve link unless target is overwritten + if (!stbte__in_dest_rect(link->x,link->y,x,y)) { + destx = link->x; + desty = link->y; + } + } + // this is necessary for offset-copy, but also in case max_x/max_y has changed + if (destx < 0 || destx >= tm->max_x || desty < 0 || desty >= tm->max_y) + destx = -1, desty = -1; + stbte__set_link(tm, x+i, y+j, destx, desty, STBTE__undo_record); +#endif + for (k=0; k < STBTE_MAX_PROPERTIES; ++k) { + if (tm->props[y+j][x+i][k] != stbte__ui.copyprops[p][k]) + stbte__undo_record_prop_float(tm, x+i, y+j, k, tm->props[y+j][x+i][k]); + } + stbte__copy_properties(tm->props[y+j][x+i], stbte__ui.copyprops[p]); + } + ++p; + } + } + stbte__end_undo(tm); +} + +static void stbte__drag_update(stbte_tilemap *tm, int mapx, int mapy, int copy_props) +{ + int w = stbte__ui.drag_w, h = stbte__ui.drag_h; + int ox,oy,i,deleted=0,written=0; + short temp[STBTE_MAX_LAYERS]; + short *data = NULL; + + STBTE__NOTUSED(deleted); + STBTE__NOTUSED(written); + + if (!stbte__ui.shift) { + ox = mapx - stbte__ui.drag_x; + oy = mapy - stbte__ui.drag_y; + if (ox >= 0 && ox < w && oy >= 0 && oy < h) { + deleted=1; + for (i=0; i < tm->num_layers; ++i) + temp[i] = tm->data[mapy][mapx][i]; + data = temp; + stbte__clear_stack(tm, data); + } + } + ox = mapx - stbte__ui.drag_dest_x; + oy = mapy - stbte__ui.drag_dest_y; + // if this map square is in the target drag region + if (ox >= 0 && ox < w && oy >= 0 && oy < h) { + // and the src map square is on the map + if (stbte__in_rect(stbte__ui.drag_x+ox, stbte__ui.drag_y+oy, 0, 0, tm->max_x, tm->max_y)) { + written = 1; + if (data == NULL) { + for (i=0; i < tm->num_layers; ++i) + temp[i] = tm->data[mapy][mapx][i]; + data = temp; + } + stbte__paste_stack(tm, data, data, tm->data[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox], !stbte__ui.shift); + if (copy_props) { + for (i=0; i < STBTE_MAX_PROPERTIES; ++i) { + if (tm->props[mapy][mapx][i] != tm->props[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox][i]) { + stbte__undo_record_prop_float(tm, mapx, mapy, i, tm->props[mapy][mapx][i]); + tm->props[mapy][mapx][i] = tm->props[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox][i]; + } + } + } + } + } + if (data) { + for (i=0; i < tm->num_layers; ++i) { + if (tm->data[mapy][mapx][i] != data[i]) { + stbte__undo_record(tm, mapx, mapy, i, tm->data[mapy][mapx][i]); + tm->data[mapy][mapx][i] = data[i]; + } + } + } + #ifdef STBTE_ALLOW_LINK + if (copy_props) { + int overwritten=0, moved=0, copied=0; + // since this function is called on EVERY tile, we can fix up even tiles not + // involved in the move + + stbte__link *k; + // first, determine what src link ends up here + k = &tm->link[mapy][mapx]; // by default, it's the one currently here + if (deleted) // if dragged away, it's erased + k = NULL; + if (written) // if dragged into, it gets that link + k = &tm->link[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox]; + + // now check whether the *target* gets moved or overwritten + if (k && k->x >= 0) { + overwritten = stbte__in_rect(k->x, k->y, stbte__ui.drag_dest_x, stbte__ui.drag_dest_y, w, h); + if (!stbte__ui.shift) + moved = stbte__in_rect(k->x, k->y, stbte__ui.drag_x , stbte__ui.drag_y , w, h); + else + copied = stbte__in_rect(k->x, k->y, stbte__ui.drag_x , stbte__ui.drag_y , w, h); + } + + if (deleted || written || overwritten || moved || copied) { + // choose the final link value based on the above + if (k == NULL || k->x < 0) + stbte__set_link(tm, mapx, mapy, -1, -1, STBTE__undo_record); + else if (moved || (copied && written)) { + // if we move the target, we update to point to the new target; + // or, if we copy the target and the source is part of the copy, then update to new target + int x = k->x + (stbte__ui.drag_dest_x - stbte__ui.drag_x); + int y = k->y + (stbte__ui.drag_dest_y - stbte__ui.drag_y); + if (!(x >= 0 && y >= 0 && x < tm->max_x && y < tm->max_y)) + x = -1, y = -1; + stbte__set_link(tm, mapx, mapy, x, y, STBTE__undo_record); + } else if (overwritten) { + stbte__set_link(tm, mapx, mapy, -1, -1, STBTE__undo_record); + } else + stbte__set_link(tm, mapx, mapy, k->x, k->y, STBTE__undo_record); + } + } + #endif +} + +static void stbte__drag_place(stbte_tilemap *tm, int mapx, int mapy) +{ + int i,j; + int copy_props = stbte__should_copy_properties(tm); + int move_x = (stbte__ui.drag_dest_x - stbte__ui.drag_x); + int move_y = (stbte__ui.drag_dest_y - stbte__ui.drag_y); + if (move_x == 0 && move_y == 0) + return; + + stbte__begin_undo(tm); + // we now need a 2D memmove-style mover that doesn't + // overwrite any data as it goes. this requires being + // direction sensitive in the same way as memmove + if (move_y > 0 || (move_y == 0 && move_x > 0)) { + for (j=tm->max_y-1; j >= 0; --j) + for (i=tm->max_x-1; i >= 0; --i) + stbte__drag_update(tm,i,j,copy_props); + } else { + for (j=0; j < tm->max_y; ++j) + for (i=0; i < tm->max_x; ++i) + stbte__drag_update(tm,i,j,copy_props); + } + stbte__end_undo(tm); + + stbte__ui.has_selection = 1; + stbte__ui.select_x0 = stbte__ui.drag_dest_x; + stbte__ui.select_y0 = stbte__ui.drag_dest_y; + stbte__ui.select_x1 = stbte__ui.select_x0 + stbte__ui.drag_w - 1; + stbte__ui.select_y1 = stbte__ui.select_y0 + stbte__ui.drag_h - 1; +} + +static void stbte__tile_paint(stbte_tilemap *tm, int sx, int sy, int mapx, int mapy, int layer) +{ + int i; + int id = STBTE__IDMAP(mapx,mapy); + int x0=sx, y0=sy; + int x1=sx+tm->spacing_x, y1=sy+tm->spacing_y; + stbte__hittest(x0,y0,x1,y1, id); + short *data = tm->data[mapy][mapx]; + short temp[STBTE_MAX_LAYERS]; + + if (STBTE__IS_MAP_HOT()) { + if (stbte__ui.pasting) { + int ox = mapx - stbte__ui.paste_x; + int oy = mapy - stbte__ui.paste_y; + if (ox >= 0 && ox < stbte__ui.copy_width && oy >= 0 && oy < stbte__ui.copy_height) { + stbte__paste_stack(tm, temp, tm->data[mapy][mapx], stbte__ui.copybuffer[oy*stbte__ui.copy_width+ox], 0); + data = temp; + } + } else if (stbte__ui.dragging) { + int ox,oy; + for (i=0; i < tm->num_layers; ++i) + temp[i] = tm->data[mapy][mapx][i]; + data = temp; + + // if it's in the source area, remove things unless shift-dragging + ox = mapx - stbte__ui.drag_x; + oy = mapy - stbte__ui.drag_y; + if (!stbte__ui.shift && ox >= 0 && ox < stbte__ui.drag_w && oy >= 0 && oy < stbte__ui.drag_h) { + stbte__clear_stack(tm, temp); + } + + ox = mapx - stbte__ui.drag_dest_x; + oy = mapy - stbte__ui.drag_dest_y; + if (ox >= 0 && ox < stbte__ui.drag_w && oy >= 0 && oy < stbte__ui.drag_h) { + stbte__paste_stack(tm, temp, temp, tm->data[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox], !stbte__ui.shift); + } + } else if (STBTE__IS_MAP_ACTIVE()) { + if (stbte__ui.tool == STBTE__tool_rect) { + if ((stbte__ui.ms_time & 511) < 380) { + int ex = ((stbte__ui.hot_id >> 19) & 4095); + int ey = ((stbte__ui.hot_id >> 7) & 4095); + int sx = stbte__ui.sx; + int sy = stbte__ui.sy; + + if ( ((mapx >= sx && mapx < ex+1) || (mapx >= ex && mapx < sx+1)) + && ((mapy >= sy && mapy < ey+1) || (mapy >= ey && mapy < sy+1))) { + int i; + for (i=0; i < tm->num_layers; ++i) + temp[i] = tm->data[mapy][mapx][i]; + data = temp; + if (stbte__ui.active_event == STBTE__leftdown) + stbte__brush_predict(tm, temp); + else + stbte__erase_predict(tm, temp, STBTE__erase_any); + } + } + } + } + } + + if (STBTE__IS_HOT(id) && STBTE__INACTIVE() && !stbte__ui.pasting) { + if (stbte__ui.tool == STBTE__tool_brush) { + if ((stbte__ui.ms_time & 511) < 300) { + data = temp; + for (i=0; i < tm->num_layers; ++i) + temp[i] = tm->data[mapy][mapx][i]; + stbte__brush_predict(tm, temp); + } + } + } + + { + i = layer; + if (i == tm->solo_layer || (!tm->layerinfo[i].hidden && tm->solo_layer < 0)) + if (data[i] >= 0) + STBTE_DRAW_TILE(sx,sy, (unsigned short) data[i], 0, tm->props[mapy][mapx]); + } +} + +static void stbte__tile(stbte_tilemap *tm, int sx, int sy, int mapx, int mapy) +{ + int tool = stbte__ui.tool; + int x0=sx, y0=sy; + int x1=sx+tm->spacing_x, y1=sy+tm->spacing_y; + int id = STBTE__IDMAP(mapx,mapy); + int over = stbte__hittest(x0,y0,x1,y1, id); + switch (stbte__ui.event) { + case STBTE__paint: { + if (stbte__ui.pasting || stbte__ui.dragging || stbte__ui.scrolling) + break; + if (stbte__ui.scrollkey && !STBTE__IS_MAP_ACTIVE()) + break; + if (STBTE__IS_HOT(id) && STBTE__IS_MAP_ACTIVE() && (tool == STBTE__tool_rect || tool == STBTE__tool_select)) { + int rx0,ry0,rx1,ry1,t; + // compute the center of each rect + rx0 = x0 + tm->spacing_x/2; + ry0 = y0 + tm->spacing_y/2; + rx1 = rx0 + (stbte__ui.sx - mapx) * tm->spacing_x; + ry1 = ry0 + (stbte__ui.sy - mapy) * tm->spacing_y; + if (rx0 > rx1) t=rx0,rx0=rx1,rx1=t; + if (ry0 > ry1) t=ry0,ry0=ry1,ry1=t; + rx0 -= tm->spacing_x/2; + ry0 -= tm->spacing_y/2; + rx1 += tm->spacing_x/2; + ry1 += tm->spacing_y/2; + stbte__draw_frame(rx0-1,ry0-1,rx1+1,ry1+1, STBTE_COLOR_TILEMAP_HIGHLIGHT); + break; + } + if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) { + stbte__draw_frame(x0-1,y0-1,x1+1,y1+1, STBTE_COLOR_TILEMAP_HIGHLIGHT); + } +#ifdef STBTE_ALLOW_LINK + if (stbte__ui.show_links && tm->link[mapy][mapx].x >= 0) { + int tx = tm->link[mapy][mapx].x; + int ty = tm->link[mapy][mapx].y; + int lx0,ly0,lx1,ly1; + if (STBTE_ALLOW_LINK(tm->data[mapy][mapx], tm->props[mapy][mapx], + tm->data[ty ][tx ], tm->props[ty ][tx ])) + { + lx0 = x0 + (tm->spacing_x >> 1) - 1; + ly0 = y0 + (tm->spacing_y >> 1) - 1; + lx1 = lx0 + (tx - mapx) * tm->spacing_x + 2; + ly1 = ly0 + (ty - mapy) * tm->spacing_y + 2; + stbte__draw_link(lx0,ly0,lx1,ly1, + STBTE_LINK_COLOR(tm->data[mapy][mapx], tm->props[mapy][mapx], + tm->data[ty ][tx ], tm->props[ty ][tx])); + } + } +#endif + break; + } + } + + if (stbte__ui.pasting) { + switch (stbte__ui.event) { + case STBTE__leftdown: + if (STBTE__IS_HOT(id)) { + stbte__ui.pasting = 0; + stbte__paste(tm, mapx, mapy); + stbte__activate(0); + } + break; + case STBTE__leftup: + // just clear it no matter what, since they might click away to clear it + stbte__activate(0); + break; + case STBTE__rightdown: + if (STBTE__IS_HOT(id)) { + stbte__activate(0); + stbte__ui.pasting = 0; + } + break; + } + return; + } + + if (stbte__ui.scrolling) { + if (stbte__ui.event == STBTE__leftup) { + stbte__activate(0); + stbte__ui.scrolling = 0; + } + if (stbte__ui.event == STBTE__mousemove) { + tm->scroll_x += (stbte__ui.start_x - stbte__ui.mx); + tm->scroll_y += (stbte__ui.start_y - stbte__ui.my); + stbte__ui.start_x = stbte__ui.mx; + stbte__ui.start_y = stbte__ui.my; + } + return; + } + + // regardless of tool, leftdown is a scrolldrag + if (STBTE__IS_HOT(id) && stbte__ui.scrollkey && stbte__ui.event == STBTE__leftdown) { + stbte__ui.scrolling = 1; + stbte__ui.start_x = stbte__ui.mx; + stbte__ui.start_y = stbte__ui.my; + return; + } + + switch (tool) { + case STBTE__tool_brush: + switch (stbte__ui.event) { + case STBTE__mousemove: + if (STBTE__IS_MAP_ACTIVE() && over) { + // don't brush/erase same tile multiple times unless they move away and back @TODO should just be only once, but that needs another data structure + if (!STBTE__IS_ACTIVE(id)) { + if (stbte__ui.active_event == STBTE__leftdown) + stbte__brush(tm, mapx, mapy); + else + stbte__erase(tm, mapx, mapy, stbte__ui.brush_state); + stbte__ui.active_id = id; // switch to this map square so we don't rebrush IT multiple times + } + } + break; + case STBTE__leftdown: + if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) { + stbte__activate(id); + stbte__begin_undo(tm); + stbte__brush(tm, mapx, mapy); + } + break; + case STBTE__rightdown: + if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) { + stbte__activate(id); + stbte__begin_undo(tm); + if (stbte__erase(tm, mapx, mapy, STBTE__erase_any) == STBTE__erase_brushonly) + stbte__ui.brush_state = STBTE__erase_brushonly; + else + stbte__ui.brush_state = STBTE__erase_any; + } + break; + case STBTE__leftup: + case STBTE__rightup: + if (STBTE__IS_MAP_ACTIVE()) { + stbte__end_undo(tm); + stbte__activate(0); + } + break; + } + break; + +#ifdef STBTE_ALLOW_LINK + case STBTE__tool_link: + switch (stbte__ui.event) { + case STBTE__leftdown: + if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) { + stbte__activate(id); + stbte__ui.linking = 1; + stbte__ui.sx = mapx; + stbte__ui.sy = mapy; + // @TODO: undo + } + break; + case STBTE__leftup: + if (STBTE__IS_HOT(id) && STBTE__IS_MAP_ACTIVE()) { + if ((mapx != stbte__ui.sx || mapy != stbte__ui.sy) && + STBTE_ALLOW_LINK(tm->data[stbte__ui.sy][stbte__ui.sx], tm->props[stbte__ui.sy][stbte__ui.sx], + tm->data[mapy][mapx], tm->props[mapy][mapx])) + stbte__set_link(tm, stbte__ui.sx, stbte__ui.sy, mapx, mapy, STBTE__undo_block); + else + stbte__set_link(tm, stbte__ui.sx, stbte__ui.sy, -1,-1, STBTE__undo_block); + stbte__ui.linking = 0; + stbte__activate(0); + } + break; + + case STBTE__rightdown: + if (STBTE__IS_ACTIVE(id)) { + stbte__activate(0); + stbte__ui.linking = 0; + } + break; + } + break; +#endif + + case STBTE__tool_erase: + switch (stbte__ui.event) { + case STBTE__mousemove: + if (STBTE__IS_MAP_ACTIVE() && over) + stbte__erase(tm, mapx, mapy, STBTE__erase_all); + break; + case STBTE__leftdown: + if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) { + stbte__activate(id); + stbte__begin_undo(tm); + stbte__erase(tm, mapx, mapy, STBTE__erase_all); + } + break; + case STBTE__leftup: + if (STBTE__IS_MAP_ACTIVE()) { + stbte__end_undo(tm); + stbte__activate(0); + } + break; + } + break; + + case STBTE__tool_select: + if (STBTE__IS_HOT(id)) { + switch (stbte__ui.event) { + case STBTE__leftdown: + if (STBTE__INACTIVE()) { + // if we're clicking in an existing selection... + if (stbte__ui.has_selection) { + if ( mapx >= stbte__ui.select_x0 && mapx <= stbte__ui.select_x1 + && mapy >= stbte__ui.select_y0 && mapy <= stbte__ui.select_y1) + { + stbte__ui.dragging = 1; + stbte__ui.drag_x = stbte__ui.select_x0; + stbte__ui.drag_y = stbte__ui.select_y0; + stbte__ui.drag_w = stbte__ui.select_x1 - stbte__ui.select_x0 + 1; + stbte__ui.drag_h = stbte__ui.select_y1 - stbte__ui.select_y0 + 1; + stbte__ui.drag_offx = mapx - stbte__ui.select_x0; + stbte__ui.drag_offy = mapy - stbte__ui.select_y0; + } + } + stbte__ui.has_selection = 0; // no selection until it completes + stbte__activate_map(mapx,mapy); + } + break; + case STBTE__leftup: + if (STBTE__IS_MAP_ACTIVE()) { + if (stbte__ui.dragging) { + stbte__drag_place(tm, mapx,mapy); + stbte__ui.dragging = 0; + stbte__activate(0); + } else { + stbte__select_rect(tm, stbte__ui.sx, stbte__ui.sy, mapx, mapy); + stbte__activate(0); + } + } + break; + case STBTE__rightdown: + stbte__ui.has_selection = 0; + break; + } + } + break; + + case STBTE__tool_rect: + if (STBTE__IS_HOT(id)) { + switch (stbte__ui.event) { + case STBTE__leftdown: + if (STBTE__INACTIVE()) + stbte__activate_map(mapx,mapy); + break; + case STBTE__leftup: + if (STBTE__IS_MAP_ACTIVE()) { + stbte__fillrect(tm, stbte__ui.sx, stbte__ui.sy, mapx, mapy, 1); + stbte__activate(0); + } + break; + case STBTE__rightdown: + if (STBTE__INACTIVE()) + stbte__activate_map(mapx,mapy); + break; + case STBTE__rightup: + if (STBTE__IS_MAP_ACTIVE()) { + stbte__fillrect(tm, stbte__ui.sx, stbte__ui.sy, mapx, mapy, 0); + stbte__activate(0); + } + break; + } + } + break; + + + case STBTE__tool_eyedrop: + switch (stbte__ui.event) { + case STBTE__leftdown: + if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) + stbte__eyedrop(tm,mapx,mapy); + break; + } + break; + } +} + +static void stbte__start_paste(stbte_tilemap *tm) +{ + if (stbte__ui.has_copy) { + stbte__ui.pasting = 1; + stbte__activate(STBTE__ID(STBTE__toolbarB,3)); + } +} + +static void stbte__toolbar(stbte_tilemap *tm, int x0, int y0, int w, int h) +{ + int i; + int estimated_width = 13 * STBTE__num_tool + 8+8+ 120+4 - 30; + int x = x0 + w/2 - estimated_width/2; + int y = y0+1; + + for (i=0; i < STBTE__num_tool; ++i) { + int highlight=0, disable=0; + highlight = (stbte__ui.tool == i); + if (i == STBTE__tool_undo || i == STBTE__tool_showgrid) + x += 8; + if (i == STBTE__tool_showgrid && stbte__ui.show_grid) + highlight = 1; + if (i == STBTE__tool_showlinks && stbte__ui.show_links) + highlight = 1; + if (i == STBTE__tool_fill) + continue; + #ifndef STBTE_ALLOW_LINK + if (i == STBTE__tool_link || i == STBTE__tool_showlinks) + disable = 1; + #endif + if (i == STBTE__tool_undo && !stbte__undo_available(tm)) + disable = 1; + if (i == STBTE__tool_redo && !stbte__redo_available(tm)) + disable = 1; + if (stbte__button_icon(STBTE__ctoolbar_button, toolchar[i], x, y, 13, STBTE__ID(STBTE__toolbarA, i), highlight, disable)) { + switch (i) { + case STBTE__tool_eyedrop: + stbte__ui.eyedrop_last_layer = tm->num_layers; // flush eyedropper state + // fallthrough + default: + stbte__ui.tool = i; + stbte__ui.has_selection = 0; + break; + case STBTE__tool_showlinks: + stbte__ui.show_links = !stbte__ui.show_links; + break; + case STBTE__tool_showgrid: + stbte__ui.show_grid = (stbte__ui.show_grid+1)%3; + break; + case STBTE__tool_undo: + stbte__undo(tm); + break; + case STBTE__tool_redo: + stbte__redo(tm); + break; + } + } + x += 13; + } + + x += 8; + if (stbte__button(STBTE__ctoolbar_button, "cut" , x, y,10, 40, STBTE__ID(STBTE__toolbarB,0), 0, !stbte__ui.has_selection)) + stbte__copy_cut(tm, 1); + x += 42; + if (stbte__button(STBTE__ctoolbar_button, "copy" , x, y, 5, 40, STBTE__ID(STBTE__toolbarB,1), 0, !stbte__ui.has_selection)) + stbte__copy_cut(tm, 0); + x += 42; + if (stbte__button(STBTE__ctoolbar_button, "paste", x, y, 0, 40, STBTE__ID(STBTE__toolbarB,2), stbte__ui.pasting, !stbte__ui.has_copy)) + stbte__start_paste(tm); +} + +#define STBTE__TEXTCOLOR(n) stbte__color_table[n][STBTE__text][STBTE__idle] + +static int stbte__info_value(const char *label, int x, int y, int val, int digits, int id) +{ + if (stbte__ui.event == STBTE__paint) { + int off = 9-stbte__get_char_width(label[0]); + char text[16]; + stbte__sprintf(text stbte__sizeof(text), label, digits, val); + stbte__draw_text_core(x+off,y, text, 999, STBTE__TEXTCOLOR(STBTE__cpanel),1); + } + if (id) { + x += 9+7*digits+4; + if (stbte__minibutton(STBTE__cmapsize, x,y, '+', STBTE__ID2(id,1,0))) + val += (stbte__ui.shift ? 10 : 1); + x += 9; + if (stbte__minibutton(STBTE__cmapsize, x,y, '-', STBTE__ID2(id,2,0))) + val -= (stbte__ui.shift ? 10 : 1); + if (val < 1) val = 1; else if (val > 4096) val = 4096; + } + return val; +} + +static void stbte__info(stbte_tilemap *tm, int x0, int y0, int w, int h) +{ + int mode = stbte__ui.panel[STBTE__panel_info].mode; + int s = 11+7*tm->digits+4+15; + int x,y; + int in_region; + + x = x0+2; + y = y0+2; + tm->max_x = stbte__info_value("w:%*d",x,y, tm->max_x, tm->digits, STBTE__ID(STBTE__info,0)); + if (mode) + x += s; + else + y += 11; + tm->max_y = stbte__info_value("h:%*d",x,y, tm->max_y, tm->digits, STBTE__ID(STBTE__info,1)); + x = x0+2; + y += 11; + in_region = (stbte__ui.hot_id & 127) == STBTE__map; + stbte__info_value(in_region ? "x:%*d" : "x:",x,y, (stbte__ui.hot_id>>19)&4095, tm->digits, 0); + if (mode) + x += s; + else + y += 11; + stbte__info_value(in_region ? "y:%*d" : "y:",x,y, (stbte__ui.hot_id>> 7)&4095, tm->digits, 0); + y += 15; + x = x0+2; + stbte__draw_text(x,y,"brush:",40,STBTE__TEXTCOLOR(STBTE__cpanel)); + if (tm->cur_tile >= 0) + STBTE_DRAW_TILE(x+43,y-3,tm->tiles[tm->cur_tile].id,1,0); +} + +static void stbte__layers(stbte_tilemap *tm, int x0, int y0, int w, int h) +{ + static const char *propmodes[3] = { + "default", "always", "never" + }; + int num_rows; + int i, y, n; + int x1 = x0+w; + int y1 = y0+h; + int xoff = 20; + + if (tm->has_layer_names) { + int side = stbte__ui.panel[STBTE__panel_layers].side; + xoff = stbte__region[side].width - 42; + xoff = (xoff < tm->layername_width + 10 ? xoff : tm->layername_width + 10); + } + + x0 += 2; + y0 += 5; + if (!tm->has_layer_names) { + if (stbte__ui.event == STBTE__paint) { + stbte__draw_text(x0,y0, "Layers", w-4, STBTE__TEXTCOLOR(STBTE__cpanel)); + } + y0 += 11; + } + num_rows = (y1-y0)/15; +#ifndef STBTE_NO_PROPS + --num_rows; +#endif + y = y0; + for (i=0; i < tm->num_layers; ++i) { + char text[3], *str = (char *) tm->layerinfo[i].name; + static char lockedchar[3] = { 'U', 'P', 'L' }; + int locked = tm->layerinfo[i].locked; + int disabled = (tm->solo_layer >= 0 && tm->solo_layer != i); + if (i-tm->layer_scroll >= 0 && i-tm->layer_scroll < num_rows) { + if (str == NULL) + stbte__sprintf(str=text stbte__sizeof(text), "%2d", i+1); + if (stbte__button(STBTE__clayer_button, str, x0,y,(i+1<10)*2,xoff-2, STBTE__ID(STBTE__layer,i), tm->cur_layer==i,0)) + tm->cur_layer = (tm->cur_layer == i ? -1 : i); + if (stbte__layerbutton(x0+xoff + 0,y+1,'H',STBTE__ID(STBTE__hide,i), tm->layerinfo[i].hidden,disabled,STBTE__clayer_hide)) + tm->layerinfo[i].hidden = !tm->layerinfo[i].hidden; + if (stbte__layerbutton(x0+xoff + 12,y+1,lockedchar[locked],STBTE__ID(STBTE__lock,i), locked!=0,disabled,STBTE__clayer_lock)) + tm->layerinfo[i].locked = (locked+1)%3; + if (stbte__layerbutton(x0+xoff + 24,y+1,'S',STBTE__ID(STBTE__solo,i), tm->solo_layer==i,0,STBTE__clayer_solo)) + tm->solo_layer = (tm->solo_layer == i ? -1 : i); + y += 15; + } + } + stbte__scrollbar(x1-4, y0,y-2, &tm->layer_scroll, 0, tm->num_layers, num_rows, STBTE__ID(STBTE__scrollbar_id, STBTE__layer)); +#ifndef STBTE_NO_PROPS + n = stbte__text_width("prop:")+2; + stbte__draw_text(x0,y+2, "prop:", w, STBTE__TEXTCOLOR(STBTE__cpanel)); + i = w - n - 4; + if (i > 50) i = 50; + if (stbte__button(STBTE__clayer_button, propmodes[tm->propmode], x0+n,y,0,i, STBTE__ID(STBTE__layer,256), 0,0)) + tm->propmode = (tm->propmode+1)%3; +#endif +} + +static void stbte__categories(stbte_tilemap *tm, int x0, int y0, int w, int h) +{ + int s=11, x,y, i; + int num_rows = h / s; + + w -= 4; + x = x0+2; + y = y0+4; + if (tm->category_scroll == 0) { + if (stbte__category_button("*ALL*", x,y, w, STBTE__ID(STBTE__categories, 65535), tm->cur_category == -1)) { + stbte__choose_category(tm, -1); + } + y += s; + } + + for (i=0; i < tm->num_categories; ++i) { + if (i+1 - tm->category_scroll >= 0 && i+1 - tm->category_scroll < num_rows) { + if (y + 10 > y0+h) + return; + if (stbte__category_button(tm->categories[i], x,y,w, STBTE__ID(STBTE__categories,i), tm->cur_category == i)) + stbte__choose_category(tm, i); + y += s; + } + } + stbte__scrollbar(x0+w, y0+4, y0+h-4, &tm->category_scroll, 0, tm->num_categories+1, num_rows, STBTE__ID(STBTE__scrollbar_id, STBTE__categories)); +} + +static void stbte__tile_in_palette(stbte_tilemap *tm, int x, int y, int slot) +{ + stbte__tileinfo *t = &tm->tiles[slot]; + int x0=x, y0=y, x1 = x+tm->palette_spacing_x - 1, y1 = y+tm->palette_spacing_y; + int id = STBTE__ID(STBTE__palette, slot); + stbte__hittest(x0,y0,x1,y1, id); + switch (stbte__ui.event) { + case STBTE__paint: + stbte__draw_rect(x,y,x+tm->palette_spacing_x-1,y+tm->palette_spacing_x-1, STBTE_COLOR_TILEPALETTE_BACKGROUND); + STBTE_DRAW_TILE(x,y,id, slot == tm->cur_tile,0); + if (slot == tm->cur_tile) + stbte__draw_frame_delayed(x-1,y-1,x+tm->palette_spacing_x,y+tm->palette_spacing_y, STBTE_COLOR_TILEPALETTE_OUTLINE); + break; + default: + if (stbte__button_core(id)) + tm->cur_tile = slot; + break; + } +} + +static void stbte__palette_of_tiles(stbte_tilemap *tm, int x0, int y0, int w, int h) +{ + int i,x,y; + int num_vis_rows = (h-6) / tm->palette_spacing_y; + int num_columns = (w-2-6) / tm->palette_spacing_x; + int num_total_rows; + int column,row; + int x1 = x0+w, y1=y0+h; + x = x0+2; + y = y0+6; + + if (num_columns == 0) + return; + + num_total_rows = (tm->cur_palette_count + num_columns-1) / num_columns; // ceil() + + column = 0; + row = -tm->palette_scroll; + for (i=0; i < tm->num_tiles; ++i) { + stbte__tileinfo *t = &tm->tiles[i]; + + // filter based on category + if (tm->cur_category >= 0 && t->category_id != tm->cur_category) + continue; + + // display it + if (row >= 0 && row < num_vis_rows) { + x = x0 + 2 + tm->palette_spacing_x * column; + y = y0 + 6 + tm->palette_spacing_y * row; + stbte__tile_in_palette(tm,x,y,i); + } + + ++column; + if (column == num_columns) { + column = 0; + ++row; + } + } + stbte__flush_delay(); + stbte__scrollbar(x1-4, y0+6, y1-2, &tm->palette_scroll, 0, num_total_rows, num_vis_rows, STBTE__ID(STBTE__scrollbar_id, STBTE__palette)); +} + +static float stbte__saved; +static void stbte__props_panel(stbte_tilemap *tm, int x0, int y0, int w, int h) +{ + int x1 = x0+w; + int i; + int y = y0 + 5, x = x0+2; + int slider_width = 60; + int mx,my; + float *p; + short *data; + if (!stbte__is_single_selection()) + return; + mx = stbte__ui.select_x0; + my = stbte__ui.select_y0; + p = tm->props[my][mx]; + data = tm->data[my][mx]; + STBTE__NOTUSED(data); + for (i=0; i < STBTE_MAX_PROPERTIES; ++i) { + unsigned int n = STBTE_PROP_TYPE(i, data, p); + if (n) { + char *s = (char*) STBTE_PROP_NAME(i, data, p); + if (s == NULL) s = (char*) ""; + switch (n & 3) { + case STBTE_PROP_bool: { + int flag = (int) p[i]; + if (stbte__layerbutton(x,y, flag ? 'x' : ' ', STBTE__ID(STBTE__prop_flag,i), flag, 0, 2)) { + stbte__begin_undo(tm); + stbte__undo_record_prop_float(tm,mx,my,i,(float) flag); + p[i] = (float) !flag; + stbte__end_undo(tm); + } + stbte__draw_text(x+13,y+1,s,x1-(x+13)-2,STBTE__TEXTCOLOR(STBTE__cpanel)); + y += 13; + break; + } + case STBTE_PROP_int: { + int a = (int) STBTE_PROP_MIN(i,data,p); + int b = (int) STBTE_PROP_MAX(i,data,p); + int v = (int) p[i] - a; + if (a+v != p[i] || v < 0 || v > b-a) { + if (v < 0) v = 0; + if (v > b-a) v = b-a; + p[i] = (float) (a+v); // @TODO undo + } + switch (stbte__slider(x, slider_width, y+7, b-a, &v, STBTE__ID(STBTE__prop_int,i))) + { + case STBTE__begin: + stbte__saved = p[i]; + // fallthrough + case STBTE__change: + p[i] = (float) (a+v); // @TODO undo + break; + case STBTE__end: + if (p[i] != stbte__saved) { + stbte__begin_undo(tm); + stbte__undo_record_prop_float(tm,mx,my,i,stbte__saved); + stbte__end_undo(tm); + } + break; + } + stbte__draw_text(x+slider_width+2,y+2, s, x1-1-(x+slider_width+2), STBTE__TEXTCOLOR(STBTE__cpanel)); + y += 12; + break; + } + case STBTE_PROP_float: { + float a = (float) STBTE_PROP_MIN(i, data,p); + float b = (float) STBTE_PROP_MAX(i, data,p); + float c = STBTE_PROP_FLOAT_SCALE(i, data, p); + float old; + if (p[i] < a || p[i] > b) { + // @TODO undo + if (p[i] < a) p[i] = a; + if (p[i] > b) p[i] = b; + } + old = p[i]; + switch (stbte__float_control(x, y, 50, a, b, c, "%8.4f", &p[i], STBTE__layer,STBTE__ID(STBTE__prop_float,i))) { + case STBTE__begin: + stbte__saved = old; + break; + case STBTE__end: + if (stbte__saved != p[i]) { + stbte__begin_undo(tm); + stbte__undo_record_prop_float(tm,mx,my,i, stbte__saved); + stbte__end_undo(tm); + } + break; + } + stbte__draw_text(x+53,y+1, s, x1-1-(x+53), STBTE__TEXTCOLOR(STBTE__cpanel)); + y += 12; + break; + } + } + } + } +} + +static int stbte__cp_mode, stbte__cp_aspect, stbte__save, stbte__cp_altered; +#ifdef STBTE__COLORPICKER +static int stbte__cp_state, stbte__cp_index, stbte__color_copy; +static void stbte__dump_colorstate(void) +{ + int i,j,k; + printf("static int stbte__color_table[STBTE__num_color_modes][STBTE__num_color_aspects][STBTE__num_color_states] =\n"); + printf("{\n"); + printf(" {\n"); + for (k=0; k < STBTE__num_color_modes; ++k) { + for (j=0; j < STBTE__num_color_aspects; ++j) { + printf(" { "); + for (i=0; i < STBTE__num_color_states; ++i) { + printf("0x%06x, ", stbte__color_table[k][j][i]); + } + printf("},\n"); + } + if (k+1 < STBTE__num_color_modes) + printf(" }, {\n"); + else + printf(" },\n"); + } + printf("};\n"); +} + +static void stbte__colorpicker(int x0, int y0, int w, int h) +{ + int x1 = x0+w, y1 = y0+h, x,y, i; + + x = x0+2; y = y0+6; + + y += 5; + x += 8; + + + { + int color = stbte__color_table[stbte__cp_mode][stbte__cp_aspect][stbte__cp_index]; + int rgb[3]; + if (stbte__cp_altered && stbte__cp_index == STBTE__idle) + color = stbte__save; + + if (stbte__minibutton(STBTE__cmapsize, x1-20,y+ 5, 'C', STBTE__ID2(STBTE__colorpick_id,4,0))) + stbte__color_copy = color; + if (stbte__minibutton(STBTE__cmapsize, x1-20,y+15, 'P', STBTE__ID2(STBTE__colorpick_id,4,1))) + color = stbte__color_copy; + + rgb[0] = color >> 16; rgb[1] = (color>>8)&255; rgb[2] = color & 255; + for (i=0; i < 3; ++i) { + if (stbte__slider(x+8,64, y, 255, rgb+i, STBTE__ID2(STBTE__colorpick_id,3,i)) > 0) + stbte__dump_colorstate(); + y += 15; + } + if (stbte__ui.event != STBTE__paint && stbte__ui.event != STBTE__tick) + stbte__color_table[stbte__cp_mode][stbte__cp_aspect][stbte__cp_index] = (rgb[0]<<16)|(rgb[1]<<8)|(rgb[2]); + } + + y += 5; + + // states + x = x0+2+35; + if (stbte__ui.event == STBTE__paint) { + static char *states[] = { "idle", "over", "down", "down&over", "selected", "selected&over", "disabled" }; + stbte__draw_text(x, y+1, states[stbte__cp_index], x1-x-1, 0xffffff); + } + + x = x0+24; y += 12; + + for (i=3; i >= 0; --i) { + int state = 0 != (stbte__cp_state & (1 << i)); + if (stbte__layerbutton(x,y, "OASD"[i], STBTE__ID2(STBTE__colorpick_id, 0,i), state,0, STBTE__clayer_button)) { + stbte__cp_state ^= (1 << i); + stbte__cp_index = stbte__state_to_index[0][0][0][stbte__cp_state]; + } + x += 16; + } + x = x0+2; y += 18; + + for (i=0; i < 3; ++i) { + static char *labels[] = { "Base", "Edge", "Text" }; + if (stbte__button(STBTE__ctoolbar_button, labels[i], x,y,0,36, STBTE__ID2(STBTE__colorpick_id,1,i), stbte__cp_aspect==i,0)) + stbte__cp_aspect = i; + x += 40; + } + + y += 18; + x = x0+2; + + for (i=0; i < STBTE__num_color_modes; ++i) { + if (stbte__button(STBTE__ctoolbar_button, stbte__color_names[i], x, y, 0,80, STBTE__ID2(STBTE__colorpick_id,2,i), stbte__cp_mode == i,0)) + stbte__cp_mode = i; + y += 12; + } + + // make the currently selected aspect flash, unless we're actively dragging color slider etc + if (stbte__ui.event == STBTE__tick) { + stbte__save = stbte__color_table[stbte__cp_mode][stbte__cp_aspect][STBTE__idle]; + if ((stbte__ui.active_id & 127) != STBTE__colorpick_id) { + if ((stbte__ui.ms_time & 2047) < 200) { + stbte__color_table[stbte__cp_mode][stbte__cp_aspect][STBTE__idle] ^= 0x1f1f1f; + stbte__cp_altered = 1; + } + } + } +} +#endif + +static void stbte__editor_traverse(stbte_tilemap *tm) +{ + int i,j,i0,j0,i1,j1,n; + + if (tm == NULL) + return; + if (stbte__ui.x0 == stbte__ui.x1 || stbte__ui.y0 == stbte__ui.y1) + return; + + stbte__prepare_tileinfo(tm); + + stbte__compute_panel_locations(tm); // @OPTIMIZE: we don't need to recompute this every time + + if (stbte__ui.event == STBTE__paint) { + // fill screen with border + stbte__draw_rect(stbte__ui.x0, stbte__ui.y0, stbte__ui.x1, stbte__ui.y1, STBTE_COLOR_TILEMAP_BORDER); + // fill tilemap with tilemap background + stbte__draw_rect(stbte__ui.x0 - tm->scroll_x, stbte__ui.y0 - tm->scroll_y, + stbte__ui.x0 - tm->scroll_x + tm->spacing_x * tm->max_x, + stbte__ui.y0 - tm->scroll_y + tm->spacing_y * tm->max_y, STBTE_COLOR_TILEMAP_BACKGROUND); + } + + // step 1: traverse all the tilemap data... + + i0 = (tm->scroll_x - tm->spacing_x) / tm->spacing_x; + j0 = (tm->scroll_y - tm->spacing_y) / tm->spacing_y; + i1 = (tm->scroll_x + stbte__ui.x1 - stbte__ui.x0) / tm->spacing_x + 1; + j1 = (tm->scroll_y + stbte__ui.y1 - stbte__ui.y0) / tm->spacing_y + 1; + + if (i0 < 0) i0 = 0; + if (j0 < 0) j0 = 0; + if (i1 > tm->max_x) i1 = tm->max_x; + if (j1 > tm->max_y) j1 = tm->max_y; + + if (stbte__ui.event == STBTE__paint) { + // draw all of layer 0, then all of layer 1, etc, instead of old + // way which drew entire stack of each tile at once + for (n=0; n < tm->num_layers; ++n) { + for (j=j0; j < j1; ++j) { + for (i=i0; i < i1; ++i) { + int x = stbte__ui.x0 + i * tm->spacing_x - tm->scroll_x; + int y = stbte__ui.y0 + j * tm->spacing_y - tm->scroll_y; + stbte__tile_paint(tm, x, y, i, j, n); + } + } + if (n == 0 && stbte__ui.show_grid == 1) { + int x = stbte__ui.x0 + i0 * tm->spacing_x - tm->scroll_x; + int y = stbte__ui.y0 + j0 * tm->spacing_y - tm->scroll_y; + for (i=0; x < stbte__ui.x1 && i <= i1; ++i, x += tm->spacing_x) + stbte__draw_rect(x, stbte__ui.y0, x+1, stbte__ui.y1, STBTE_COLOR_GRID); + for (j=0; y < stbte__ui.y1 && j <= j1; ++j, y += tm->spacing_y) + stbte__draw_rect(stbte__ui.x0, y, stbte__ui.x1, y+1, STBTE_COLOR_GRID); + } + } + } + + if (stbte__ui.event == STBTE__paint) { + // draw grid on top of everything except UI + if (stbte__ui.show_grid == 2) { + int x = stbte__ui.x0 + i0 * tm->spacing_x - tm->scroll_x; + int y = stbte__ui.y0 + j0 * tm->spacing_y - tm->scroll_y; + for (i=0; x < stbte__ui.x1 && i <= i1; ++i, x += tm->spacing_x) + stbte__draw_rect(x, stbte__ui.y0, x+1, stbte__ui.y1, STBTE_COLOR_GRID); + for (j=0; y < stbte__ui.y1 && j <= j1; ++j, y += tm->spacing_y) + stbte__draw_rect(stbte__ui.x0, y, stbte__ui.x1, y+1, STBTE_COLOR_GRID); + } + } + + for (j=j0; j < j1; ++j) { + for (i=i0; i < i1; ++i) { + int x = stbte__ui.x0 + i * tm->spacing_x - tm->scroll_x; + int y = stbte__ui.y0 + j * tm->spacing_y - tm->scroll_y; + stbte__tile(tm, x, y, i, j); + } + } + + if (stbte__ui.event == STBTE__paint) { + // draw the selection border + if (stbte__ui.has_selection) { + int x0,y0,x1,y1; + x0 = stbte__ui.x0 + (stbte__ui.select_x0 ) * tm->spacing_x - tm->scroll_x; + y0 = stbte__ui.y0 + (stbte__ui.select_y0 ) * tm->spacing_y - tm->scroll_y; + x1 = stbte__ui.x0 + (stbte__ui.select_x1 + 1) * tm->spacing_x - tm->scroll_x + 1; + y1 = stbte__ui.y0 + (stbte__ui.select_y1 + 1) * tm->spacing_y - tm->scroll_y + 1; + stbte__draw_frame(x0,y0,x1,y1, (stbte__ui.ms_time & 256 ? STBTE_COLOR_SELECTION_OUTLINE1 : STBTE_COLOR_SELECTION_OUTLINE2)); + } + + stbte__flush_delay(); // draw a dynamic link on top of the queued links + + #ifdef STBTE_ALLOW_LINK + if (stbte__ui.linking && STBTE__IS_MAP_HOT()) { + int x0,y0,x1,y1; + int color; + int ex = ((stbte__ui.hot_id >> 19) & 4095); + int ey = ((stbte__ui.hot_id >> 7) & 4095); + x0 = stbte__ui.x0 + (stbte__ui.sx ) * tm->spacing_x - tm->scroll_x + (tm->spacing_x>>1)+1; + y0 = stbte__ui.y0 + (stbte__ui.sy ) * tm->spacing_y - tm->scroll_y + (tm->spacing_y>>1)+1; + x1 = stbte__ui.x0 + (ex ) * tm->spacing_x - tm->scroll_x + (tm->spacing_x>>1)-1; + y1 = stbte__ui.y0 + (ey ) * tm->spacing_y - tm->scroll_y + (tm->spacing_y>>1)-1; + if (STBTE_ALLOW_LINK(tm->data[stbte__ui.sy][stbte__ui.sx], tm->props[stbte__ui.sy][stbte__ui.sx], tm->data[ey][ex], tm->props[ey][ex])) + color = STBTE_LINK_COLOR_DRAWING; + else + color = STBTE_LINK_COLOR_DISALLOWED; + stbte__draw_link(x0,y0,x1,y1, color); + } + #endif + } + stbte__flush_delay(); + + // step 2: traverse the panels + for (i=0; i < STBTE__num_panel; ++i) { + stbte__panel *p = &stbte__ui.panel[i]; + if (stbte__ui.event == STBTE__paint) { + stbte__draw_box(p->x0,p->y0,p->x0+p->width,p->y0+p->height, STBTE__cpanel, STBTE__idle); + } + // obscure tilemap data underneath panel + stbte__hittest(p->x0,p->y0,p->x0+p->width,p->y0+p->height, STBTE__ID2(STBTE__panel, i, 0)); + switch (i) { + case STBTE__panel_toolbar: + if (stbte__ui.event == STBTE__paint) + stbte__draw_rect(p->x0,p->y0,p->x0+p->width,p->y0+p->height, stbte__color_table[STBTE__ctoolbar][STBTE__base][STBTE__idle]); + stbte__toolbar(tm,p->x0,p->y0,p->width,p->height); + break; + case STBTE__panel_info: + stbte__info(tm,p->x0,p->y0,p->width,p->height); + break; + case STBTE__panel_layers: + stbte__layers(tm,p->x0,p->y0,p->width,p->height); + break; + case STBTE__panel_categories: + stbte__categories(tm,p->x0,p->y0,p->width,p->height); + break; + case STBTE__panel_colorpick: +#ifdef STBTE__COLORPICKER + stbte__colorpicker(p->x0,p->y0,p->width,p->height); +#endif + break; + case STBTE__panel_tiles: + // erase boundary between categories and tiles if they're on same side + if (stbte__ui.event == STBTE__paint && p->side == stbte__ui.panel[STBTE__panel_categories].side) + stbte__draw_rect(p->x0+1,p->y0-1,p->x0+p->width-1,p->y0+1, stbte__color_table[STBTE__cpanel][STBTE__base][STBTE__idle]); + stbte__palette_of_tiles(tm,p->x0,p->y0,p->width,p->height); + break; + case STBTE__panel_props: + stbte__props_panel(tm,p->x0,p->y0,p->width,p->height); + break; + } + // draw the panel side selectors + for (j=0; j < 2; ++j) { + int result; + if (i == STBTE__panel_toolbar) continue; + result = stbte__microbutton(p->x0+p->width - 1 - 2*4 + 4*j,p->y0+2,3, STBTE__ID2(STBTE__panel, i, j+1), STBTE__cpanel_sider+j); + if (result) { + switch (j) { + case 0: p->side = result > 0 ? STBTE__side_left : STBTE__side_right; break; + case 1: p->delta_height += result; break; + } + } + } + } + + if (stbte__ui.panel[STBTE__panel_categories].delta_height < -5) stbte__ui.panel[STBTE__panel_categories].delta_height = -5; + if (stbte__ui.panel[STBTE__panel_layers ].delta_height < -5) stbte__ui.panel[STBTE__panel_layers ].delta_height = -5; + + + // step 3: traverse the regions to place expander controls on them + for (i=0; i < 2; ++i) { + if (stbte__region[i].active) { + int x = stbte__region[i].x; + int width; + if (i == STBTE__side_left) + width = stbte__ui.left_width , x += stbte__region[i].width + 1; + else + width = -stbte__ui.right_width, x -= 6; + if (stbte__microbutton_dragger(x, stbte__region[i].y+2, 5, STBTE__ID(STBTE__region,i), &width)) { + // if non-0, it is expanding, so retract it + if (stbte__region[i].retracted == 0.0) + stbte__region[i].retracted = 0.01f; + else + stbte__region[i].retracted = 0.0; + } + if (i == STBTE__side_left) + stbte__ui.left_width = width; + else + stbte__ui.right_width = -width; + if (stbte__ui.event == STBTE__tick) { + if (stbte__region[i].retracted && stbte__region[i].retracted < 1.0f) { + stbte__region[i].retracted += stbte__ui.dt*4; + if (stbte__region[i].retracted > 1) + stbte__region[i].retracted = 1; + } + } + } + } + + if (stbte__ui.event == STBTE__paint && stbte__ui.alert_msg) { + int w = stbte__text_width(stbte__ui.alert_msg); + int x = (stbte__ui.x0+stbte__ui.x1)/2; + int y = (stbte__ui.y0+stbte__ui.y1)*5/6; + stbte__draw_rect (x-w/2-4,y-8, x+w/2+4,y+8, 0x604020); + stbte__draw_frame(x-w/2-4,y-8, x+w/2+4,y+8, 0x906030); + stbte__draw_text (x-w/2,y-4, stbte__ui.alert_msg, w+1, 0xff8040); + } + +#ifdef STBTE_SHOW_CURSOR + if (stbte__ui.event == STBTE__paint) + stbte__draw_bitmap(stbte__ui.mx, stbte__ui.my, stbte__get_char_width(26), stbte__get_char_bitmap(26), 0xe0e0e0); +#endif + + if (stbte__ui.event == STBTE__tick && stbte__ui.alert_msg) { + stbte__ui.alert_timer -= stbte__ui.dt; + if (stbte__ui.alert_timer < 0) { + stbte__ui.alert_timer = 0; + stbte__ui.alert_msg = 0; + } + } + + if (stbte__ui.event == STBTE__paint) { + stbte__color_table[stbte__cp_mode][stbte__cp_aspect][STBTE__idle] = stbte__save; + stbte__cp_altered = 0; + } +} + +static void stbte__do_event(stbte_tilemap *tm) +{ + stbte__ui.next_hot_id = 0; + stbte__editor_traverse(tm); + stbte__ui.hot_id = stbte__ui.next_hot_id; + + // automatically cancel on mouse-up in case the object that triggered it + // doesn't exist anymore + if (stbte__ui.active_id) { + if (stbte__ui.event == STBTE__leftup || stbte__ui.event == STBTE__rightup) { + if (!stbte__ui.pasting) { + stbte__activate(0); + if (stbte__ui.undoing) + stbte__end_undo(tm); + stbte__ui.scrolling = 0; + stbte__ui.dragging = 0; + stbte__ui.linking = 0; + } + } + } + + // we could do this stuff in the widgets directly, but it would keep recomputing + // the same thing on every tile, which seems dumb. + + if (stbte__ui.pasting) { + if (STBTE__IS_MAP_HOT()) { + // compute pasting location based on last hot + stbte__ui.paste_x = ((stbte__ui.hot_id >> 19) & 4095) - (stbte__ui.copy_width >> 1); + stbte__ui.paste_y = ((stbte__ui.hot_id >> 7) & 4095) - (stbte__ui.copy_height >> 1); + } + } + if (stbte__ui.dragging) { + if (STBTE__IS_MAP_HOT()) { + stbte__ui.drag_dest_x = ((stbte__ui.hot_id >> 19) & 4095) - stbte__ui.drag_offx; + stbte__ui.drag_dest_y = ((stbte__ui.hot_id >> 7) & 4095) - stbte__ui.drag_offy; + } + } +} + +static void stbte__set_event(int event, int x, int y) +{ + stbte__ui.event = event; + stbte__ui.mx = x; + stbte__ui.my = y; + stbte__ui.dx = x - stbte__ui.last_mouse_x; + stbte__ui.dy = y - stbte__ui.last_mouse_y; + stbte__ui.last_mouse_x = x; + stbte__ui.last_mouse_y = y; + stbte__ui.accum_x += stbte__ui.dx; + stbte__ui.accum_y += stbte__ui.dy; +} + +void stbte_draw(stbte_tilemap *tm) +{ + stbte__ui.event = STBTE__paint; + stbte__editor_traverse(tm); +} + +void stbte_mouse_move(stbte_tilemap *tm, int x, int y, int shifted, int scrollkey) +{ + stbte__set_event(STBTE__mousemove, x,y); + stbte__ui.shift = shifted; + stbte__ui.scrollkey = scrollkey; + stbte__do_event(tm); +} + +void stbte_mouse_button(stbte_tilemap *tm, int x, int y, int right, int down, int shifted, int scrollkey) +{ + static int events[2][2] = { { STBTE__leftup , STBTE__leftdown }, + { STBTE__rightup, STBTE__rightdown } }; + stbte__set_event(events[right][down], x,y); + stbte__ui.shift = shifted; + stbte__ui.scrollkey = scrollkey; + + stbte__do_event(tm); +} + +void stbte_mouse_wheel(stbte_tilemap *tm, int x, int y, int vscroll) +{ + // not implemented yet -- need different way of hittesting +} + +void stbte_action(stbte_tilemap *tm, enum stbte_action act) +{ + switch (act) { + case STBTE_tool_select: stbte__ui.tool = STBTE__tool_select; break; + case STBTE_tool_brush: stbte__ui.tool = STBTE__tool_brush; break; + case STBTE_tool_erase: stbte__ui.tool = STBTE__tool_erase; break; + case STBTE_tool_rectangle: stbte__ui.tool = STBTE__tool_rect; break; + case STBTE_tool_eyedropper: stbte__ui.tool = STBTE__tool_eyedrop; break; + case STBTE_tool_link: stbte__ui.tool = STBTE__tool_link; break; + case STBTE_act_toggle_grid: stbte__ui.show_grid = (stbte__ui.show_grid+1) % 3; break; + case STBTE_act_toggle_links: stbte__ui.show_links ^= 1; break; + case STBTE_act_undo: stbte__undo(tm); break; + case STBTE_act_redo: stbte__redo(tm); break; + case STBTE_act_cut: stbte__copy_cut(tm, 1); break; + case STBTE_act_copy: stbte__copy_cut(tm, 0); break; + case STBTE_act_paste: stbte__start_paste(tm); break; + case STBTE_scroll_left: tm->scroll_x -= tm->spacing_x; break; + case STBTE_scroll_right: tm->scroll_x += tm->spacing_x; break; + case STBTE_scroll_up: tm->scroll_y -= tm->spacing_y; break; + case STBTE_scroll_down: tm->scroll_y += tm->spacing_y; break; + } +} + +void stbte_tick(stbte_tilemap *tm, float dt) +{ + stbte__ui.event = STBTE__tick; + stbte__ui.dt = dt; + stbte__do_event(tm); + stbte__ui.ms_time += (int) (dt * 1024) + 1; // make sure if time is superfast it always updates a little +} + +void stbte_mouse_sdl(stbte_tilemap *tm, const void *sdl_event, float xs, float ys, int xo, int yo) +{ +#ifdef _SDL_H + SDL_Event *event = (SDL_Event *) sdl_event; + SDL_Keymod km = SDL_GetModState(); + int shift = (km & KMOD_LCTRL) || (km & KMOD_RCTRL); + int scrollkey = 0 != SDL_GetKeyboardState(NULL)[SDL_SCANCODE_SPACE]; + switch (event->type) { + case SDL_MOUSEMOTION: + stbte_mouse_move(tm, (int) (xs*event->motion.x+xo), (int) (ys*event->motion.y+yo), shift, scrollkey); + break; + case SDL_MOUSEBUTTONUP: + stbte_mouse_button(tm, (int) (xs*event->button.x+xo), (int) (ys*event->button.y+yo), event->button.button != SDL_BUTTON_LEFT, 0, shift, scrollkey); + break; + case SDL_MOUSEBUTTONDOWN: + stbte_mouse_button(tm, (int) (xs*event->button.x+xo), (int) (ys*event->button.y+yo), event->button.button != SDL_BUTTON_LEFT, 1, shift, scrollkey); + break; + case SDL_MOUSEWHEEL: + stbte_mouse_wheel(tm, stbte__ui.mx, stbte__ui.my, event->wheel.y); + break; + } +#else + STBTE__NOTUSED(tm); + STBTE__NOTUSED(sdl_event); + STBTE__NOTUSED(xs); + STBTE__NOTUSED(ys); + STBTE__NOTUSED(xo); + STBTE__NOTUSED(yo); +#endif +} + +#endif // STB_TILEMAP_EDITOR_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_truetype.h b/source/engine/thirdparty/stb/stb_truetype.h new file mode 100644 index 0000000..bbf2284 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_truetype.h @@ -0,0 +1,5077 @@ +// stb_truetype.h - v1.26 - public domain +// authored from 2009-2021 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.26 (2021-08-28) fix broken rasterizer +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start, last; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + last = ttUSHORT(data + endCount + 2*item); + if (unicode_codepoint < start || unicode_codepoint > last) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // FALLTHROUGH + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && b0 < 32) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch (coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + break; + } + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + break; + } + + default: return -1; // unsupported + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch (classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + break; + } + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + break; + } + + default: + return -1; // Unsupported definition type, return an error. + } + + // "All glyphs not assigned to a class fall into class 0". (OpenType spec) + return 0; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i, sti; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i= pairSetCount) return 0; + + needle=glyph2; + r=pairValueCount-1; + l=0; + + // Binary search. + while (l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint8 *pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } else + return 0; + break; + } + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + stbtt_uint8 *class1Records, *class2Records; + stbtt_int16 xAdvance; + + if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed + if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed + + class1Records = table + 16; + class2Records = class1Records + 2 * (glyph1class * class2Count); + xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } else + return 0; + break; + } + + default: + return 0; // Unsupported position format + } + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) +{ + STBTT_assert(top_width >= 0); + STBTT_assert(bottom_width >= 0); + return (top_width + bottom_width) / 2.0f * height; +} + +static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) +{ + return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); +} + +static float stbtt__sized_triangle_area(float height, float width) +{ + return height * width / 2; +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = (sy1 - sy0) * e->direction; + STBTT_assert(x >= 0 && x < len); + scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); + scanline_fill[x] += height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, y_final, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + STBTT_assert(dy >= 0); + STBTT_assert(dx >= 0); + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = y_top + dy * (x1+1 - x0); + + // compute intersection with y axis at x2 + y_final = y_top + dy * (x2 - x0); + + // x1 x_top x2 x_bottom + // y_top +------|-----+------------+------------+--------|---+------------+ + // | | | | | | + // | | | | | | + // sy0 | Txxxxx|............|............|............|............| + // y_crossing | *xxxxx.......|............|............|............| + // | | xxxxx..|............|............|............| + // | | /- xx*xxxx........|............|............| + // | | dy < | xxxxxx..|............|............| + // y_final | | \- | xx*xxx.........|............| + // sy1 | | | | xxxxxB...|............| + // | | | | | | + // | | | | | | + // y_bottom +------------+------------+------------+------------+------------+ + // + // goal is to measure the area covered by '.' in each pixel + + // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 + // @TODO: maybe test against sy1 rather than y_bottom? + if (y_crossing > y_bottom) + y_crossing = y_bottom; + + sign = e->direction; + + // area of the rectangle covered from sy0..y_crossing + area = sign * (y_crossing-sy0); + + // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) + scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); + + // check if final y_crossing is blown up; no test case for this + if (y_final > y_bottom) { + y_final = y_bottom; + dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom + } + + // in second pixel, area covered by line segment found in first pixel + // is always a rectangle 1 wide * the height of that line segment; this + // is exactly what the variable 'area' stores. it also gets a contribution + // from the line segment within it. the THIRD pixel will get the first + // pixel's rectangle contribution, the second pixel's rectangle contribution, + // and its own contribution. the 'own contribution' is the same in every pixel except + // the leftmost and rightmost, a trapezoid that slides down in each pixel. + // the second pixel's contribution to the third pixel will be the + // rectangle 1 wide times the height change in the second pixel, which is dy. + + step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, + // which multiplied by 1-pixel-width is how much pixel area changes for each step in x + // so the area advances by 'step' every time + + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; // area of trapezoid is 1*step/2 + area += step; + } + STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down + STBTT_assert(sy1 > y_final-0.01f); + + // area covered in the last pixel is the rectangle from all the pixels to the left, + // plus the trapezoid filled by the line segment in this pixel all the way to the right edge + scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); + + // the rest of the line is filled based on the total height of the line segment in this pixel + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + // note though that this does happen some of the time because + // x_top and x_bottom can be extrapolated at the top & bottom of + // the shape and actually lie outside the bounding box + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + + orig[0] = x; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + a*x^2 + b*x + c = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3] = {0.f,0.f,0.f}; + float px,py,t,it,dist2; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_vorbis.c b/source/engine/thirdparty/stb/stb_vorbis.c new file mode 100644 index 0000000..3e5c250 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_vorbis.c @@ -0,0 +1,5584 @@ +// Ogg Vorbis audio decoder - v1.22 - public domain +// http://nothings.org/stb_vorbis/ +// +// Original version written by Sean Barrett in 2007. +// +// Originally sponsored by RAD Game Tools. Seeking implementation +// sponsored by Phillip Bennefall, Marc Andersen, Aaron Baker, +// Elias Software, Aras Pranckevicius, and Sean Barrett. +// +// LICENSE +// +// See end of file for license information. +// +// Limitations: +// +// - floor 0 not supported (used in old ogg vorbis files pre-2004) +// - lossless sample-truncation at beginning ignored +// - cannot concatenate multiple vorbis streams +// - sample positions are 32-bit, limiting seekable 192Khz +// files to around 6 hours (Ogg supports 64-bit) +// +// Feature contributors: +// Dougall Johnson (sample-exact seeking) +// +// Bugfix/warning contributors: +// Terje Mathisen Niklas Frykholm Andy Hill +// Casey Muratori John Bolton Gargaj +// Laurent Gomila Marc LeBlanc Ronny Chevalier +// Bernhard Wodo Evan Balster github:alxprd +// Tom Beaumont Ingo Leitgeb Nicolas Guillemot +// Phillip Bennefall Rohit Thiago Goulart +// github:manxorist Saga Musix github:infatum +// Timur Gagiev Maxwell Koo Peter Waller +// github:audinowho Dougall Johnson David Reid +// github:Clownacy Pedro J. Estebanez Remi Verschelde +// AnthoFoxo github:morlat Gabriel Ravier +// +// Partial history: +// 1.22 - 2021-07-11 - various small fixes +// 1.21 - 2021-07-02 - fix bug for files with no comments +// 1.20 - 2020-07-11 - several small fixes +// 1.19 - 2020-02-05 - warnings +// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc. +// 1.17 - 2019-07-08 - fix CVE-2019-13217..CVE-2019-13223 (by ForAllSecure) +// 1.16 - 2019-03-04 - fix warnings +// 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found +// 1.14 - 2018-02-11 - delete bogus dealloca usage +// 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) +// 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files +// 1.11 - 2017-07-23 - fix MinGW compilation +// 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory +// 1.09 - 2016-04-04 - back out 'truncation of last frame' fix from previous version +// 1.08 - 2016-04-02 - warnings; setup memory leaks; truncation of last frame +// 1.07 - 2015-01-16 - fixes for crashes on invalid files; warning fixes; const +// 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) +// some crash fixes when out of memory or with corrupt files +// fix some inappropriately signed shifts +// 1.05 - 2015-04-19 - don't define __forceinline if it's redundant +// 1.04 - 2014-08-27 - fix missing const-correct case in API +// 1.03 - 2014-08-07 - warning fixes +// 1.02 - 2014-07-09 - declare qsort comparison as explicitly _cdecl in Windows +// 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float (interleaved was correct) +// 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in >2-channel; +// (API change) report sample rate for decode-full-file funcs +// +// See end of file for full version history. + + +////////////////////////////////////////////////////////////////////////////// +// +// HEADER BEGINS HERE +// + +#ifndef STB_VORBIS_INCLUDE_STB_VORBIS_H +#define STB_VORBIS_INCLUDE_STB_VORBIS_H + +#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) +#define STB_VORBIS_NO_STDIO 1 +#endif + +#ifndef STB_VORBIS_NO_STDIO +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/////////// THREAD SAFETY + +// Individual stb_vorbis* handles are not thread-safe; you cannot decode from +// them from multiple threads at the same time. However, you can have multiple +// stb_vorbis* handles and decode from them independently in multiple thrads. + + +/////////// MEMORY ALLOCATION + +// normally stb_vorbis uses malloc() to allocate memory at startup, +// and alloca() to allocate temporary memory during a frame on the +// stack. (Memory consumption will depend on the amount of setup +// data in the file and how you set the compile flags for speed +// vs. size. In my test files the maximal-size usage is ~150KB.) +// +// You can modify the wrapper functions in the source (setup_malloc, +// setup_temp_malloc, temp_malloc) to change this behavior, or you +// can use a simpler allocation model: you pass in a buffer from +// which stb_vorbis will allocate _all_ its memory (including the +// temp memory). "open" may fail with a VORBIS_outofmem if you +// do not pass in enough data; there is no way to determine how +// much you do need except to succeed (at which point you can +// query get_info to find the exact amount required. yes I know +// this is lame). +// +// If you pass in a non-NULL buffer of the type below, allocation +// will occur from it as described above. Otherwise just pass NULL +// to use malloc()/alloca() + +typedef struct +{ + char *alloc_buffer; + int alloc_buffer_length_in_bytes; +} stb_vorbis_alloc; + + +/////////// FUNCTIONS USEABLE WITH ALL INPUT MODES + +typedef struct stb_vorbis stb_vorbis; + +typedef struct +{ + unsigned int sample_rate; + int channels; + + unsigned int setup_memory_required; + unsigned int setup_temp_memory_required; + unsigned int temp_memory_required; + + int max_frame_size; +} stb_vorbis_info; + +typedef struct +{ + char *vendor; + + int comment_list_length; + char **comment_list; +} stb_vorbis_comment; + +// get general information about the file +extern stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f); + +// get ogg comments +extern stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f); + +// get the last error detected (clears it, too) +extern int stb_vorbis_get_error(stb_vorbis *f); + +// close an ogg vorbis file and free all memory in use +extern void stb_vorbis_close(stb_vorbis *f); + +// this function returns the offset (in samples) from the beginning of the +// file that will be returned by the next decode, if it is known, or -1 +// otherwise. after a flush_pushdata() call, this may take a while before +// it becomes valid again. +// NOT WORKING YET after a seek with PULLDATA API +extern int stb_vorbis_get_sample_offset(stb_vorbis *f); + +// returns the current seek point within the file, or offset from the beginning +// of the memory buffer. In pushdata mode it returns 0. +extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f); + +/////////// PUSHDATA API + +#ifndef STB_VORBIS_NO_PUSHDATA_API + +// this API allows you to get blocks of data from any source and hand +// them to stb_vorbis. you have to buffer them; stb_vorbis will tell +// you how much it used, and you have to give it the rest next time; +// and stb_vorbis may not have enough data to work with and you will +// need to give it the same data again PLUS more. Note that the Vorbis +// specification does not bound the size of an individual frame. + +extern stb_vorbis *stb_vorbis_open_pushdata( + const unsigned char * datablock, int datablock_length_in_bytes, + int *datablock_memory_consumed_in_bytes, + int *error, + const stb_vorbis_alloc *alloc_buffer); +// create a vorbis decoder by passing in the initial data block containing +// the ogg&vorbis headers (you don't need to do parse them, just provide +// the first N bytes of the file--you're told if it's not enough, see below) +// on success, returns an stb_vorbis *, does not set error, returns the amount of +// data parsed/consumed on this call in *datablock_memory_consumed_in_bytes; +// on failure, returns NULL on error and sets *error, does not change *datablock_memory_consumed +// if returns NULL and *error is VORBIS_need_more_data, then the input block was +// incomplete and you need to pass in a larger block from the start of the file + +extern int stb_vorbis_decode_frame_pushdata( + stb_vorbis *f, + const unsigned char *datablock, int datablock_length_in_bytes, + int *channels, // place to write number of float * buffers + float ***output, // place to write float ** array of float * buffers + int *samples // place to write number of output samples + ); +// decode a frame of audio sample data if possible from the passed-in data block +// +// return value: number of bytes we used from datablock +// +// possible cases: +// 0 bytes used, 0 samples output (need more data) +// N bytes used, 0 samples output (resynching the stream, keep going) +// N bytes used, M samples output (one frame of data) +// note that after opening a file, you will ALWAYS get one N-bytes,0-sample +// frame, because Vorbis always "discards" the first frame. +// +// Note that on resynch, stb_vorbis will rarely consume all of the buffer, +// instead only datablock_length_in_bytes-3 or less. This is because it wants +// to avoid missing parts of a page header if they cross a datablock boundary, +// without writing state-machiney code to record a partial detection. +// +// The number of channels returned are stored in *channels (which can be +// NULL--it is always the same as the number of channels reported by +// get_info). *output will contain an array of float* buffers, one per +// channel. In other words, (*output)[0][0] contains the first sample from +// the first channel, and (*output)[1][0] contains the first sample from +// the second channel. +// +// *output points into stb_vorbis's internal output buffer storage; these +// buffers are owned by stb_vorbis and application code should not free +// them or modify their contents. They are transient and will be overwritten +// once you ask for more data to get decoded, so be sure to grab any data +// you need before then. + +extern void stb_vorbis_flush_pushdata(stb_vorbis *f); +// inform stb_vorbis that your next datablock will not be contiguous with +// previous ones (e.g. you've seeked in the data); future attempts to decode +// frames will cause stb_vorbis to resynchronize (as noted above), and +// once it sees a valid Ogg page (typically 4-8KB, as large as 64KB), it +// will begin decoding the _next_ frame. +// +// if you want to seek using pushdata, you need to seek in your file, then +// call stb_vorbis_flush_pushdata(), then start calling decoding, then once +// decoding is returning you data, call stb_vorbis_get_sample_offset, and +// if you don't like the result, seek your file again and repeat. +#endif + + +////////// PULLING INPUT API + +#ifndef STB_VORBIS_NO_PULLDATA_API +// This API assumes stb_vorbis is allowed to pull data from a source-- +// either a block of memory containing the _entire_ vorbis stream, or a +// FILE * that you or it create, or possibly some other reading mechanism +// if you go modify the source to replace the FILE * case with some kind +// of callback to your code. (But if you don't support seeking, you may +// just want to go ahead and use pushdata.) + +#if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION) +extern int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output); +#endif +#if !defined(STB_VORBIS_NO_INTEGER_CONVERSION) +extern int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output); +#endif +// decode an entire file and output the data interleaved into a malloc()ed +// buffer stored in *output. The return value is the number of samples +// decoded, or -1 if the file could not be opened or was not an ogg vorbis file. +// When you're done with it, just free() the pointer returned in *output. + +extern stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from an ogg vorbis stream in memory (note +// this must be the entire stream!). on failure, returns NULL and sets *error + +#ifndef STB_VORBIS_NO_STDIO +extern stb_vorbis * stb_vorbis_open_filename(const char *filename, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from a filename via fopen(). on failure, +// returns NULL and sets *error (possibly to VORBIS_file_open_failure). + +extern stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from an open FILE *, looking for a stream at +// the _current_ seek point (ftell). on failure, returns NULL and sets *error. +// note that stb_vorbis must "own" this stream; if you seek it in between +// calls to stb_vorbis, it will become confused. Moreover, if you attempt to +// perform stb_vorbis_seek_*() operations on this file, it will assume it +// owns the _entire_ rest of the file after the start point. Use the next +// function, stb_vorbis_open_file_section(), to limit it. + +extern stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close, + int *error, const stb_vorbis_alloc *alloc_buffer, unsigned int len); +// create an ogg vorbis decoder from an open FILE *, looking for a stream at +// the _current_ seek point (ftell); the stream will be of length 'len' bytes. +// on failure, returns NULL and sets *error. note that stb_vorbis must "own" +// this stream; if you seek it in between calls to stb_vorbis, it will become +// confused. +#endif + +extern int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number); +extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number); +// these functions seek in the Vorbis file to (approximately) 'sample_number'. +// after calling seek_frame(), the next call to get_frame_*() will include +// the specified sample. after calling stb_vorbis_seek(), the next call to +// stb_vorbis_get_samples_* will start with the specified sample. If you +// do not need to seek to EXACTLY the target sample when using get_samples_*, +// you can also use seek_frame(). + +extern int stb_vorbis_seek_start(stb_vorbis *f); +// this function is equivalent to stb_vorbis_seek(f,0) + +extern unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f); +extern float stb_vorbis_stream_length_in_seconds(stb_vorbis *f); +// these functions return the total length of the vorbis stream + +extern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output); +// decode the next frame and return the number of samples. the number of +// channels returned are stored in *channels (which can be NULL--it is always +// the same as the number of channels reported by get_info). *output will +// contain an array of float* buffers, one per channel. These outputs will +// be overwritten on the next call to stb_vorbis_get_frame_*. +// +// You generally should not intermix calls to stb_vorbis_get_frame_*() +// and stb_vorbis_get_samples_*(), since the latter calls the former. + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +extern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts); +extern int stb_vorbis_get_frame_short (stb_vorbis *f, int num_c, short **buffer, int num_samples); +#endif +// decode the next frame and return the number of *samples* per channel. +// Note that for interleaved data, you pass in the number of shorts (the +// size of your array), but the return value is the number of samples per +// channel, not the total number of samples. +// +// The data is coerced to the number of channels you request according to the +// channel coercion rules (see below). You must pass in the size of your +// buffer(s) so that stb_vorbis will not overwrite the end of the buffer. +// The maximum buffer size needed can be gotten from get_info(); however, +// the Vorbis I specification implies an absolute maximum of 4096 samples +// per channel. + +// Channel coercion rules: +// Let M be the number of channels requested, and N the number of channels present, +// and Cn be the nth channel; let stereo L be the sum of all L and center channels, +// and stereo R be the sum of all R and center channels (channel assignment from the +// vorbis spec). +// M N output +// 1 k sum(Ck) for all k +// 2 * stereo L, stereo R +// k l k > l, the first l channels, then 0s +// k l k <= l, the first k channels +// Note that this is not _good_ surround etc. mixing at all! It's just so +// you get something useful. + +extern int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats); +extern int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples); +// gets num_samples samples, not necessarily on a frame boundary--this requires +// buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES. +// Returns the number of samples stored per channel; it may be less than requested +// at the end of the file. If there are no more samples in the file, returns 0. + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +extern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts); +extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples); +#endif +// gets num_samples samples, not necessarily on a frame boundary--this requires +// buffering so you have to supply the buffers. Applies the coercion rules above +// to produce 'channels' channels. Returns the number of samples stored per channel; +// it may be less than requested at the end of the file. If there are no more +// samples in the file, returns 0. + +#endif + +//////// ERROR CODES + +enum STBVorbisError +{ + VORBIS__no_error, + + VORBIS_need_more_data=1, // not a real error + + VORBIS_invalid_api_mixing, // can't mix API modes + VORBIS_outofmem, // not enough memory + VORBIS_feature_not_supported, // uses floor 0 + VORBIS_too_many_channels, // STB_VORBIS_MAX_CHANNELS is too small + VORBIS_file_open_failure, // fopen() failed + VORBIS_seek_without_length, // can't seek in unknown-length file + + VORBIS_unexpected_eof=10, // file is truncated? + VORBIS_seek_invalid, // seek past EOF + + // decoding errors (corrupt/invalid stream) -- you probably + // don't care about the exact details of these + + // vorbis errors: + VORBIS_invalid_setup=20, + VORBIS_invalid_stream, + + // ogg errors: + VORBIS_missing_capture_pattern=30, + VORBIS_invalid_stream_structure_version, + VORBIS_continued_packet_flag_invalid, + VORBIS_incorrect_stream_serial_number, + VORBIS_invalid_first_page, + VORBIS_bad_packet_type, + VORBIS_cant_find_last_page, + VORBIS_seek_failed, + VORBIS_ogg_skeleton_not_supported +}; + + +#ifdef __cplusplus +} +#endif + +#endif // STB_VORBIS_INCLUDE_STB_VORBIS_H +// +// HEADER ENDS HERE +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef STB_VORBIS_HEADER_ONLY + +// global configuration settings (e.g. set these in the project/makefile), +// or just set them in this file at the top (although ideally the first few +// should be visible when the header file is compiled too, although it's not +// crucial) + +// STB_VORBIS_NO_PUSHDATA_API +// does not compile the code for the various stb_vorbis_*_pushdata() +// functions +// #define STB_VORBIS_NO_PUSHDATA_API + +// STB_VORBIS_NO_PULLDATA_API +// does not compile the code for the non-pushdata APIs +// #define STB_VORBIS_NO_PULLDATA_API + +// STB_VORBIS_NO_STDIO +// does not compile the code for the APIs that use FILE *s internally +// or externally (implied by STB_VORBIS_NO_PULLDATA_API) +// #define STB_VORBIS_NO_STDIO + +// STB_VORBIS_NO_INTEGER_CONVERSION +// does not compile the code for converting audio sample data from +// float to integer (implied by STB_VORBIS_NO_PULLDATA_API) +// #define STB_VORBIS_NO_INTEGER_CONVERSION + +// STB_VORBIS_NO_FAST_SCALED_FLOAT +// does not use a fast float-to-int trick to accelerate float-to-int on +// most platforms which requires endianness be defined correctly. +//#define STB_VORBIS_NO_FAST_SCALED_FLOAT + + +// STB_VORBIS_MAX_CHANNELS [number] +// globally define this to the maximum number of channels you need. +// The spec does not put a restriction on channels except that +// the count is stored in a byte, so 255 is the hard limit. +// Reducing this saves about 16 bytes per value, so using 16 saves +// (255-16)*16 or around 4KB. Plus anything other memory usage +// I forgot to account for. Can probably go as low as 8 (7.1 audio), +// 6 (5.1 audio), or 2 (stereo only). +#ifndef STB_VORBIS_MAX_CHANNELS +#define STB_VORBIS_MAX_CHANNELS 16 // enough for anyone? +#endif + +// STB_VORBIS_PUSHDATA_CRC_COUNT [number] +// after a flush_pushdata(), stb_vorbis begins scanning for the +// next valid page, without backtracking. when it finds something +// that looks like a page, it streams through it and verifies its +// CRC32. Should that validation fail, it keeps scanning. But it's +// possible that _while_ streaming through to check the CRC32 of +// one candidate page, it sees another candidate page. This #define +// determines how many "overlapping" candidate pages it can search +// at once. Note that "real" pages are typically ~4KB to ~8KB, whereas +// garbage pages could be as big as 64KB, but probably average ~16KB. +// So don't hose ourselves by scanning an apparent 64KB page and +// missing a ton of real ones in the interim; so minimum of 2 +#ifndef STB_VORBIS_PUSHDATA_CRC_COUNT +#define STB_VORBIS_PUSHDATA_CRC_COUNT 4 +#endif + +// STB_VORBIS_FAST_HUFFMAN_LENGTH [number] +// sets the log size of the huffman-acceleration table. Maximum +// supported value is 24. with larger numbers, more decodings are O(1), +// but the table size is larger so worse cache missing, so you'll have +// to probe (and try multiple ogg vorbis files) to find the sweet spot. +#ifndef STB_VORBIS_FAST_HUFFMAN_LENGTH +#define STB_VORBIS_FAST_HUFFMAN_LENGTH 10 +#endif + +// STB_VORBIS_FAST_BINARY_LENGTH [number] +// sets the log size of the binary-search acceleration table. this +// is used in similar fashion to the fast-huffman size to set initial +// parameters for the binary search + +// STB_VORBIS_FAST_HUFFMAN_INT +// The fast huffman tables are much more efficient if they can be +// stored as 16-bit results instead of 32-bit results. This restricts +// the codebooks to having only 65535 possible outcomes, though. +// (At least, accelerated by the huffman table.) +#ifndef STB_VORBIS_FAST_HUFFMAN_INT +#define STB_VORBIS_FAST_HUFFMAN_SHORT +#endif + +// STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH +// If the 'fast huffman' search doesn't succeed, then stb_vorbis falls +// back on binary searching for the correct one. This requires storing +// extra tables with the huffman codes in sorted order. Defining this +// symbol trades off space for speed by forcing a linear search in the +// non-fast case, except for "sparse" codebooks. +// #define STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH + +// STB_VORBIS_DIVIDES_IN_RESIDUE +// stb_vorbis precomputes the result of the scalar residue decoding +// that would otherwise require a divide per chunk. you can trade off +// space for time by defining this symbol. +// #define STB_VORBIS_DIVIDES_IN_RESIDUE + +// STB_VORBIS_DIVIDES_IN_CODEBOOK +// vorbis VQ codebooks can be encoded two ways: with every case explicitly +// stored, or with all elements being chosen from a small range of values, +// and all values possible in all elements. By default, stb_vorbis expands +// this latter kind out to look like the former kind for ease of decoding, +// because otherwise an integer divide-per-vector-element is required to +// unpack the index. If you define STB_VORBIS_DIVIDES_IN_CODEBOOK, you can +// trade off storage for speed. +//#define STB_VORBIS_DIVIDES_IN_CODEBOOK + +#ifdef STB_VORBIS_CODEBOOK_SHORTS +#error "STB_VORBIS_CODEBOOK_SHORTS is no longer supported as it produced incorrect results for some input formats" +#endif + +// STB_VORBIS_DIVIDE_TABLE +// this replaces small integer divides in the floor decode loop with +// table lookups. made less than 1% difference, so disabled by default. + +// STB_VORBIS_NO_INLINE_DECODE +// disables the inlining of the scalar codebook fast-huffman decode. +// might save a little codespace; useful for debugging +// #define STB_VORBIS_NO_INLINE_DECODE + +// STB_VORBIS_NO_DEFER_FLOOR +// Normally we only decode the floor without synthesizing the actual +// full curve. We can instead synthesize the curve immediately. This +// requires more memory and is very likely slower, so I don't think +// you'd ever want to do it except for debugging. +// #define STB_VORBIS_NO_DEFER_FLOOR + + + + +////////////////////////////////////////////////////////////////////////////// + +#ifdef STB_VORBIS_NO_PULLDATA_API + #define STB_VORBIS_NO_INTEGER_CONVERSION + #define STB_VORBIS_NO_STDIO +#endif + +#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) + #define STB_VORBIS_NO_STDIO 1 +#endif + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT + + // only need endianness for fast-float-to-int, which we don't + // use for pushdata + + #ifndef STB_VORBIS_BIG_ENDIAN + #define STB_VORBIS_ENDIAN 0 + #else + #define STB_VORBIS_ENDIAN 1 + #endif + +#endif +#endif + + +#ifndef STB_VORBIS_NO_STDIO +#include +#endif + +#ifndef STB_VORBIS_NO_CRT + #include + #include + #include + #include + + // find definition of alloca if it's not in stdlib.h: + #if defined(_MSC_VER) || defined(__MINGW32__) + #include + #endif + #if defined(__linux__) || defined(__linux) || defined(__sun__) || defined(__EMSCRIPTEN__) || defined(__NEWLIB__) + #include + #endif +#else // STB_VORBIS_NO_CRT + #define NULL 0 + #define malloc(s) 0 + #define free(s) ((void) 0) + #define realloc(s) 0 +#endif // STB_VORBIS_NO_CRT + +#include + +#ifdef __MINGW32__ + // eff you mingw: + // "fixed": + // http://sourceforge.net/p/mingw-w64/mailman/message/32882927/ + // "no that broke the build, reverted, who cares about C": + // http://sourceforge.net/p/mingw-w64/mailman/message/32890381/ + #ifdef __forceinline + #undef __forceinline + #endif + #define __forceinline + #ifndef alloca + #define alloca __builtin_alloca + #endif +#elif !defined(_MSC_VER) + #if __GNUC__ + #define __forceinline inline + #else + #define __forceinline + #endif +#endif + +#if STB_VORBIS_MAX_CHANNELS > 256 +#error "Value of STB_VORBIS_MAX_CHANNELS outside of allowed range" +#endif + +#if STB_VORBIS_FAST_HUFFMAN_LENGTH > 24 +#error "Value of STB_VORBIS_FAST_HUFFMAN_LENGTH outside of allowed range" +#endif + + +#if 0 +#include +#define CHECK(f) _CrtIsValidHeapPointer(f->channel_buffers[1]) +#else +#define CHECK(f) ((void) 0) +#endif + +#define MAX_BLOCKSIZE_LOG 13 // from specification +#define MAX_BLOCKSIZE (1 << MAX_BLOCKSIZE_LOG) + + +typedef unsigned char uint8; +typedef signed char int8; +typedef unsigned short uint16; +typedef signed short int16; +typedef unsigned int uint32; +typedef signed int int32; + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +typedef float codetype; + +#ifdef _MSC_VER +#define STBV_NOTUSED(v) (void)(v) +#else +#define STBV_NOTUSED(v) (void)sizeof(v) +#endif + +// @NOTE +// +// Some arrays below are tagged "//varies", which means it's actually +// a variable-sized piece of data, but rather than malloc I assume it's +// small enough it's better to just allocate it all together with the +// main thing +// +// Most of the variables are specified with the smallest size I could pack +// them into. It might give better performance to make them all full-sized +// integers. It should be safe to freely rearrange the structures or change +// the sizes larger--nothing relies on silently truncating etc., nor the +// order of variables. + +#define FAST_HUFFMAN_TABLE_SIZE (1 << STB_VORBIS_FAST_HUFFMAN_LENGTH) +#define FAST_HUFFMAN_TABLE_MASK (FAST_HUFFMAN_TABLE_SIZE - 1) + +typedef struct +{ + int dimensions, entries; + uint8 *codeword_lengths; + float minimum_value; + float delta_value; + uint8 value_bits; + uint8 lookup_type; + uint8 sequence_p; + uint8 sparse; + uint32 lookup_values; + codetype *multiplicands; + uint32 *codewords; + #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT + int16 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; + #else + int32 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; + #endif + uint32 *sorted_codewords; + int *sorted_values; + int sorted_entries; +} Codebook; + +typedef struct +{ + uint8 order; + uint16 rate; + uint16 bark_map_size; + uint8 amplitude_bits; + uint8 amplitude_offset; + uint8 number_of_books; + uint8 book_list[16]; // varies +} Floor0; + +typedef struct +{ + uint8 partitions; + uint8 partition_class_list[32]; // varies + uint8 class_dimensions[16]; // varies + uint8 class_subclasses[16]; // varies + uint8 class_masterbooks[16]; // varies + int16 subclass_books[16][8]; // varies + uint16 Xlist[31*8+2]; // varies + uint8 sorted_order[31*8+2]; + uint8 neighbors[31*8+2][2]; + uint8 floor1_multiplier; + uint8 rangebits; + int values; +} Floor1; + +typedef union +{ + Floor0 floor0; + Floor1 floor1; +} Floor; + +typedef struct +{ + uint32 begin, end; + uint32 part_size; + uint8 classifications; + uint8 classbook; + uint8 **classdata; + int16 (*residue_books)[8]; +} Residue; + +typedef struct +{ + uint8 magnitude; + uint8 angle; + uint8 mux; +} MappingChannel; + +typedef struct +{ + uint16 coupling_steps; + MappingChannel *chan; + uint8 submaps; + uint8 submap_floor[15]; // varies + uint8 submap_residue[15]; // varies +} Mapping; + +typedef struct +{ + uint8 blockflag; + uint8 mapping; + uint16 windowtype; + uint16 transformtype; +} Mode; + +typedef struct +{ + uint32 goal_crc; // expected crc if match + int bytes_left; // bytes left in packet + uint32 crc_so_far; // running crc + int bytes_done; // bytes processed in _current_ chunk + uint32 sample_loc; // granule pos encoded in page +} CRCscan; + +typedef struct +{ + uint32 page_start, page_end; + uint32 last_decoded_sample; +} ProbedPage; + +struct stb_vorbis +{ + // user-accessible info + unsigned int sample_rate; + int channels; + + unsigned int setup_memory_required; + unsigned int temp_memory_required; + unsigned int setup_temp_memory_required; + + char *vendor; + int comment_list_length; + char **comment_list; + + // input config +#ifndef STB_VORBIS_NO_STDIO + FILE *f; + uint32 f_start; + int close_on_free; +#endif + + uint8 *stream; + uint8 *stream_start; + uint8 *stream_end; + + uint32 stream_len; + + uint8 push_mode; + + // the page to seek to when seeking to start, may be zero + uint32 first_audio_page_offset; + + // p_first is the page on which the first audio packet ends + // (but not necessarily the page on which it starts) + ProbedPage p_first, p_last; + + // memory management + stb_vorbis_alloc alloc; + int setup_offset; + int temp_offset; + + // run-time results + int eof; + enum STBVorbisError error; + + // user-useful data + + // header info + int blocksize[2]; + int blocksize_0, blocksize_1; + int codebook_count; + Codebook *codebooks; + int floor_count; + uint16 floor_types[64]; // varies + Floor *floor_config; + int residue_count; + uint16 residue_types[64]; // varies + Residue *residue_config; + int mapping_count; + Mapping *mapping; + int mode_count; + Mode mode_config[64]; // varies + + uint32 total_samples; + + // decode buffer + float *channel_buffers[STB_VORBIS_MAX_CHANNELS]; + float *outputs [STB_VORBIS_MAX_CHANNELS]; + + float *previous_window[STB_VORBIS_MAX_CHANNELS]; + int previous_length; + + #ifndef STB_VORBIS_NO_DEFER_FLOOR + int16 *finalY[STB_VORBIS_MAX_CHANNELS]; + #else + float *floor_buffers[STB_VORBIS_MAX_CHANNELS]; + #endif + + uint32 current_loc; // sample location of next frame to decode + int current_loc_valid; + + // per-blocksize precomputed data + + // twiddle factors + float *A[2],*B[2],*C[2]; + float *window[2]; + uint16 *bit_reverse[2]; + + // current page/packet/segment streaming info + uint32 serial; // stream serial number for verification + int last_page; + int segment_count; + uint8 segments[255]; + uint8 page_flag; + uint8 bytes_in_seg; + uint8 first_decode; + int next_seg; + int last_seg; // flag that we're on the last segment + int last_seg_which; // what was the segment number of the last seg? + uint32 acc; + int valid_bits; + int packet_bytes; + int end_seg_with_known_loc; + uint32 known_loc_for_packet; + int discard_samples_deferred; + uint32 samples_output; + + // push mode scanning + int page_crc_tests; // only in push_mode: number of tests active; -1 if not searching +#ifndef STB_VORBIS_NO_PUSHDATA_API + CRCscan scan[STB_VORBIS_PUSHDATA_CRC_COUNT]; +#endif + + // sample-access + int channel_buffer_start; + int channel_buffer_end; +}; + +#if defined(STB_VORBIS_NO_PUSHDATA_API) + #define IS_PUSH_MODE(f) FALSE +#elif defined(STB_VORBIS_NO_PULLDATA_API) + #define IS_PUSH_MODE(f) TRUE +#else + #define IS_PUSH_MODE(f) ((f)->push_mode) +#endif + +typedef struct stb_vorbis vorb; + +static int error(vorb *f, enum STBVorbisError e) +{ + f->error = e; + if (!f->eof && e != VORBIS_need_more_data) { + f->error=e; // breakpoint for debugging + } + return 0; +} + + +// these functions are used for allocating temporary memory +// while decoding. if you can afford the stack space, use +// alloca(); otherwise, provide a temp buffer and it will +// allocate out of those. + +#define array_size_required(count,size) (count*(sizeof(void *)+(size))) + +#define temp_alloc(f,size) (f->alloc.alloc_buffer ? setup_temp_malloc(f,size) : alloca(size)) +#define temp_free(f,p) (void)0 +#define temp_alloc_save(f) ((f)->temp_offset) +#define temp_alloc_restore(f,p) ((f)->temp_offset = (p)) + +#define temp_block_array(f,count,size) make_block_array(temp_alloc(f,array_size_required(count,size)), count, size) + +// given a sufficiently large block of memory, make an array of pointers to subblocks of it +static void *make_block_array(void *mem, int count, int size) +{ + int i; + void ** p = (void **) mem; + char *q = (char *) (p + count); + for (i=0; i < count; ++i) { + p[i] = q; + q += size; + } + return p; +} + +static void *setup_malloc(vorb *f, int sz) +{ + sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. + f->setup_memory_required += sz; + if (f->alloc.alloc_buffer) { + void *p = (char *) f->alloc.alloc_buffer + f->setup_offset; + if (f->setup_offset + sz > f->temp_offset) return NULL; + f->setup_offset += sz; + return p; + } + return sz ? malloc(sz) : NULL; +} + +static void setup_free(vorb *f, void *p) +{ + if (f->alloc.alloc_buffer) return; // do nothing; setup mem is a stack + free(p); +} + +static void *setup_temp_malloc(vorb *f, int sz) +{ + sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. + if (f->alloc.alloc_buffer) { + if (f->temp_offset - sz < f->setup_offset) return NULL; + f->temp_offset -= sz; + return (char *) f->alloc.alloc_buffer + f->temp_offset; + } + return malloc(sz); +} + +static void setup_temp_free(vorb *f, void *p, int sz) +{ + if (f->alloc.alloc_buffer) { + f->temp_offset += (sz+7)&~7; + return; + } + free(p); +} + +#define CRC32_POLY 0x04c11db7 // from spec + +static uint32 crc_table[256]; +static void crc32_init(void) +{ + int i,j; + uint32 s; + for(i=0; i < 256; i++) { + for (s=(uint32) i << 24, j=0; j < 8; ++j) + s = (s << 1) ^ (s >= (1U<<31) ? CRC32_POLY : 0); + crc_table[i] = s; + } +} + +static __forceinline uint32 crc32_update(uint32 crc, uint8 byte) +{ + return (crc << 8) ^ crc_table[byte ^ (crc >> 24)]; +} + + +// used in setup, and for huffman that doesn't go fast path +static unsigned int bit_reverse(unsigned int n) +{ + n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1); + n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2); + n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4); + n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8); + return (n >> 16) | (n << 16); +} + +static float square(float x) +{ + return x*x; +} + +// this is a weird definition of log2() for which log2(1) = 1, log2(2) = 2, log2(4) = 3 +// as required by the specification. fast(?) implementation from stb.h +// @OPTIMIZE: called multiple times per-packet with "constants"; move to setup +static int ilog(int32 n) +{ + static signed char log2_4[16] = { 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4 }; + + if (n < 0) return 0; // signed n returns 0 + + // 2 compares if n < 16, 3 compares otherwise (4 if signed or n > 1<<29) + if (n < (1 << 14)) + if (n < (1 << 4)) return 0 + log2_4[n ]; + else if (n < (1 << 9)) return 5 + log2_4[n >> 5]; + else return 10 + log2_4[n >> 10]; + else if (n < (1 << 24)) + if (n < (1 << 19)) return 15 + log2_4[n >> 15]; + else return 20 + log2_4[n >> 20]; + else if (n < (1 << 29)) return 25 + log2_4[n >> 25]; + else return 30 + log2_4[n >> 30]; +} + +#ifndef M_PI + #define M_PI 3.14159265358979323846264f // from CRC +#endif + +// code length assigned to a value with no huffman encoding +#define NO_CODE 255 + +/////////////////////// LEAF SETUP FUNCTIONS ////////////////////////// +// +// these functions are only called at setup, and only a few times +// per file + +static float float32_unpack(uint32 x) +{ + // from the specification + uint32 mantissa = x & 0x1fffff; + uint32 sign = x & 0x80000000; + uint32 exp = (x & 0x7fe00000) >> 21; + double res = sign ? -(double)mantissa : (double)mantissa; + return (float) ldexp((float)res, (int)exp-788); +} + + +// zlib & jpeg huffman tables assume that the output symbols +// can either be arbitrarily arranged, or have monotonically +// increasing frequencies--they rely on the lengths being sorted; +// this makes for a very simple generation algorithm. +// vorbis allows a huffman table with non-sorted lengths. This +// requires a more sophisticated construction, since symbols in +// order do not map to huffman codes "in order". +static void add_entry(Codebook *c, uint32 huff_code, int symbol, int count, int len, uint32 *values) +{ + if (!c->sparse) { + c->codewords [symbol] = huff_code; + } else { + c->codewords [count] = huff_code; + c->codeword_lengths[count] = len; + values [count] = symbol; + } +} + +static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values) +{ + int i,k,m=0; + uint32 available[32]; + + memset(available, 0, sizeof(available)); + // find the first entry + for (k=0; k < n; ++k) if (len[k] < NO_CODE) break; + if (k == n) { assert(c->sorted_entries == 0); return TRUE; } + assert(len[k] < 32); // no error return required, code reading lens checks this + // add to the list + add_entry(c, 0, k, m++, len[k], values); + // add all available leaves + for (i=1; i <= len[k]; ++i) + available[i] = 1U << (32-i); + // note that the above code treats the first case specially, + // but it's really the same as the following code, so they + // could probably be combined (except the initial code is 0, + // and I use 0 in available[] to mean 'empty') + for (i=k+1; i < n; ++i) { + uint32 res; + int z = len[i], y; + if (z == NO_CODE) continue; + assert(z < 32); // no error return required, code reading lens checks this + // find lowest available leaf (should always be earliest, + // which is what the specification calls for) + // note that this property, and the fact we can never have + // more than one free leaf at a given level, isn't totally + // trivial to prove, but it seems true and the assert never + // fires, so! + while (z > 0 && !available[z]) --z; + if (z == 0) { return FALSE; } + res = available[z]; + available[z] = 0; + add_entry(c, bit_reverse(res), i, m++, len[i], values); + // propagate availability up the tree + if (z != len[i]) { + for (y=len[i]; y > z; --y) { + assert(available[y] == 0); + available[y] = res + (1 << (32-y)); + } + } + } + return TRUE; +} + +// accelerated huffman table allows fast O(1) match of all symbols +// of length <= STB_VORBIS_FAST_HUFFMAN_LENGTH +static void compute_accelerated_huffman(Codebook *c) +{ + int i, len; + for (i=0; i < FAST_HUFFMAN_TABLE_SIZE; ++i) + c->fast_huffman[i] = -1; + + len = c->sparse ? c->sorted_entries : c->entries; + #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT + if (len > 32767) len = 32767; // largest possible value we can encode! + #endif + for (i=0; i < len; ++i) { + if (c->codeword_lengths[i] <= STB_VORBIS_FAST_HUFFMAN_LENGTH) { + uint32 z = c->sparse ? bit_reverse(c->sorted_codewords[i]) : c->codewords[i]; + // set table entries for all bit combinations in the higher bits + while (z < FAST_HUFFMAN_TABLE_SIZE) { + c->fast_huffman[z] = i; + z += 1 << c->codeword_lengths[i]; + } + } + } +} + +#ifdef _MSC_VER +#define STBV_CDECL __cdecl +#else +#define STBV_CDECL +#endif + +static int STBV_CDECL uint32_compare(const void *p, const void *q) +{ + uint32 x = * (uint32 *) p; + uint32 y = * (uint32 *) q; + return x < y ? -1 : x > y; +} + +static int include_in_sort(Codebook *c, uint8 len) +{ + if (c->sparse) { assert(len != NO_CODE); return TRUE; } + if (len == NO_CODE) return FALSE; + if (len > STB_VORBIS_FAST_HUFFMAN_LENGTH) return TRUE; + return FALSE; +} + +// if the fast table above doesn't work, we want to binary +// search them... need to reverse the bits +static void compute_sorted_huffman(Codebook *c, uint8 *lengths, uint32 *values) +{ + int i, len; + // build a list of all the entries + // OPTIMIZATION: don't include the short ones, since they'll be caught by FAST_HUFFMAN. + // this is kind of a frivolous optimization--I don't see any performance improvement, + // but it's like 4 extra lines of code, so. + if (!c->sparse) { + int k = 0; + for (i=0; i < c->entries; ++i) + if (include_in_sort(c, lengths[i])) + c->sorted_codewords[k++] = bit_reverse(c->codewords[i]); + assert(k == c->sorted_entries); + } else { + for (i=0; i < c->sorted_entries; ++i) + c->sorted_codewords[i] = bit_reverse(c->codewords[i]); + } + + qsort(c->sorted_codewords, c->sorted_entries, sizeof(c->sorted_codewords[0]), uint32_compare); + c->sorted_codewords[c->sorted_entries] = 0xffffffff; + + len = c->sparse ? c->sorted_entries : c->entries; + // now we need to indicate how they correspond; we could either + // #1: sort a different data structure that says who they correspond to + // #2: for each sorted entry, search the original list to find who corresponds + // #3: for each original entry, find the sorted entry + // #1 requires extra storage, #2 is slow, #3 can use binary search! + for (i=0; i < len; ++i) { + int huff_len = c->sparse ? lengths[values[i]] : lengths[i]; + if (include_in_sort(c,huff_len)) { + uint32 code = bit_reverse(c->codewords[i]); + int x=0, n=c->sorted_entries; + while (n > 1) { + // invariant: sc[x] <= code < sc[x+n] + int m = x + (n >> 1); + if (c->sorted_codewords[m] <= code) { + x = m; + n -= (n>>1); + } else { + n >>= 1; + } + } + assert(c->sorted_codewords[x] == code); + if (c->sparse) { + c->sorted_values[x] = values[i]; + c->codeword_lengths[x] = huff_len; + } else { + c->sorted_values[x] = i; + } + } + } +} + +// only run while parsing the header (3 times) +static int vorbis_validate(uint8 *data) +{ + static uint8 vorbis[6] = { 'v', 'o', 'r', 'b', 'i', 's' }; + return memcmp(data, vorbis, 6) == 0; +} + +// called from setup only, once per code book +// (formula implied by specification) +static int lookup1_values(int entries, int dim) +{ + int r = (int) floor(exp((float) log((float) entries) / dim)); + if ((int) floor(pow((float) r+1, dim)) <= entries) // (int) cast for MinGW warning; + ++r; // floor() to avoid _ftol() when non-CRT + if (pow((float) r+1, dim) <= entries) + return -1; + if ((int) floor(pow((float) r, dim)) > entries) + return -1; + return r; +} + +// called twice per file +static void compute_twiddle_factors(int n, float *A, float *B, float *C) +{ + int n4 = n >> 2, n8 = n >> 3; + int k,k2; + + for (k=k2=0; k < n4; ++k,k2+=2) { + A[k2 ] = (float) cos(4*k*M_PI/n); + A[k2+1] = (float) -sin(4*k*M_PI/n); + B[k2 ] = (float) cos((k2+1)*M_PI/n/2) * 0.5f; + B[k2+1] = (float) sin((k2+1)*M_PI/n/2) * 0.5f; + } + for (k=k2=0; k < n8; ++k,k2+=2) { + C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); + C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); + } +} + +static void compute_window(int n, float *window) +{ + int n2 = n >> 1, i; + for (i=0; i < n2; ++i) + window[i] = (float) sin(0.5 * M_PI * square((float) sin((i - 0 + 0.5) / n2 * 0.5 * M_PI))); +} + +static void compute_bitreverse(int n, uint16 *rev) +{ + int ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + int i, n8 = n >> 3; + for (i=0; i < n8; ++i) + rev[i] = (bit_reverse(i) >> (32-ld+3)) << 2; +} + +static int init_blocksize(vorb *f, int b, int n) +{ + int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3; + f->A[b] = (float *) setup_malloc(f, sizeof(float) * n2); + f->B[b] = (float *) setup_malloc(f, sizeof(float) * n2); + f->C[b] = (float *) setup_malloc(f, sizeof(float) * n4); + if (!f->A[b] || !f->B[b] || !f->C[b]) return error(f, VORBIS_outofmem); + compute_twiddle_factors(n, f->A[b], f->B[b], f->C[b]); + f->window[b] = (float *) setup_malloc(f, sizeof(float) * n2); + if (!f->window[b]) return error(f, VORBIS_outofmem); + compute_window(n, f->window[b]); + f->bit_reverse[b] = (uint16 *) setup_malloc(f, sizeof(uint16) * n8); + if (!f->bit_reverse[b]) return error(f, VORBIS_outofmem); + compute_bitreverse(n, f->bit_reverse[b]); + return TRUE; +} + +static void neighbors(uint16 *x, int n, int *plow, int *phigh) +{ + int low = -1; + int high = 65536; + int i; + for (i=0; i < n; ++i) { + if (x[i] > low && x[i] < x[n]) { *plow = i; low = x[i]; } + if (x[i] < high && x[i] > x[n]) { *phigh = i; high = x[i]; } + } +} + +// this has been repurposed so y is now the original index instead of y +typedef struct +{ + uint16 x,id; +} stbv__floor_ordering; + +static int STBV_CDECL point_compare(const void *p, const void *q) +{ + stbv__floor_ordering *a = (stbv__floor_ordering *) p; + stbv__floor_ordering *b = (stbv__floor_ordering *) q; + return a->x < b->x ? -1 : a->x > b->x; +} + +// +/////////////////////// END LEAF SETUP FUNCTIONS ////////////////////////// + + +#if defined(STB_VORBIS_NO_STDIO) + #define USE_MEMORY(z) TRUE +#else + #define USE_MEMORY(z) ((z)->stream) +#endif + +static uint8 get8(vorb *z) +{ + if (USE_MEMORY(z)) { + if (z->stream >= z->stream_end) { z->eof = TRUE; return 0; } + return *z->stream++; + } + + #ifndef STB_VORBIS_NO_STDIO + { + int c = fgetc(z->f); + if (c == EOF) { z->eof = TRUE; return 0; } + return c; + } + #endif +} + +static uint32 get32(vorb *f) +{ + uint32 x; + x = get8(f); + x += get8(f) << 8; + x += get8(f) << 16; + x += (uint32) get8(f) << 24; + return x; +} + +static int getn(vorb *z, uint8 *data, int n) +{ + if (USE_MEMORY(z)) { + if (z->stream+n > z->stream_end) { z->eof = 1; return 0; } + memcpy(data, z->stream, n); + z->stream += n; + return 1; + } + + #ifndef STB_VORBIS_NO_STDIO + if (fread(data, n, 1, z->f) == 1) + return 1; + else { + z->eof = 1; + return 0; + } + #endif +} + +static void skip(vorb *z, int n) +{ + if (USE_MEMORY(z)) { + z->stream += n; + if (z->stream >= z->stream_end) z->eof = 1; + return; + } + #ifndef STB_VORBIS_NO_STDIO + { + long x = ftell(z->f); + fseek(z->f, x+n, SEEK_SET); + } + #endif +} + +static int set_file_offset(stb_vorbis *f, unsigned int loc) +{ + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (f->push_mode) return 0; + #endif + f->eof = 0; + if (USE_MEMORY(f)) { + if (f->stream_start + loc >= f->stream_end || f->stream_start + loc < f->stream_start) { + f->stream = f->stream_end; + f->eof = 1; + return 0; + } else { + f->stream = f->stream_start + loc; + return 1; + } + } + #ifndef STB_VORBIS_NO_STDIO + if (loc + f->f_start < loc || loc >= 0x80000000) { + loc = 0x7fffffff; + f->eof = 1; + } else { + loc += f->f_start; + } + if (!fseek(f->f, loc, SEEK_SET)) + return 1; + f->eof = 1; + fseek(f->f, f->f_start, SEEK_END); + return 0; + #endif +} + + +static uint8 ogg_page_header[4] = { 0x4f, 0x67, 0x67, 0x53 }; + +static int capture_pattern(vorb *f) +{ + if (0x4f != get8(f)) return FALSE; + if (0x67 != get8(f)) return FALSE; + if (0x67 != get8(f)) return FALSE; + if (0x53 != get8(f)) return FALSE; + return TRUE; +} + +#define PAGEFLAG_continued_packet 1 +#define PAGEFLAG_first_page 2 +#define PAGEFLAG_last_page 4 + +static int start_page_no_capturepattern(vorb *f) +{ + uint32 loc0,loc1,n; + if (f->first_decode && !IS_PUSH_MODE(f)) { + f->p_first.page_start = stb_vorbis_get_file_offset(f) - 4; + } + // stream structure version + if (0 != get8(f)) return error(f, VORBIS_invalid_stream_structure_version); + // header flag + f->page_flag = get8(f); + // absolute granule position + loc0 = get32(f); + loc1 = get32(f); + // @TODO: validate loc0,loc1 as valid positions? + // stream serial number -- vorbis doesn't interleave, so discard + get32(f); + //if (f->serial != get32(f)) return error(f, VORBIS_incorrect_stream_serial_number); + // page sequence number + n = get32(f); + f->last_page = n; + // CRC32 + get32(f); + // page_segments + f->segment_count = get8(f); + if (!getn(f, f->segments, f->segment_count)) + return error(f, VORBIS_unexpected_eof); + // assume we _don't_ know any the sample position of any segments + f->end_seg_with_known_loc = -2; + if (loc0 != ~0U || loc1 != ~0U) { + int i; + // determine which packet is the last one that will complete + for (i=f->segment_count-1; i >= 0; --i) + if (f->segments[i] < 255) + break; + // 'i' is now the index of the _last_ segment of a packet that ends + if (i >= 0) { + f->end_seg_with_known_loc = i; + f->known_loc_for_packet = loc0; + } + } + if (f->first_decode) { + int i,len; + len = 0; + for (i=0; i < f->segment_count; ++i) + len += f->segments[i]; + len += 27 + f->segment_count; + f->p_first.page_end = f->p_first.page_start + len; + f->p_first.last_decoded_sample = loc0; + } + f->next_seg = 0; + return TRUE; +} + +static int start_page(vorb *f) +{ + if (!capture_pattern(f)) return error(f, VORBIS_missing_capture_pattern); + return start_page_no_capturepattern(f); +} + +static int start_packet(vorb *f) +{ + while (f->next_seg == -1) { + if (!start_page(f)) return FALSE; + if (f->page_flag & PAGEFLAG_continued_packet) + return error(f, VORBIS_continued_packet_flag_invalid); + } + f->last_seg = FALSE; + f->valid_bits = 0; + f->packet_bytes = 0; + f->bytes_in_seg = 0; + // f->next_seg is now valid + return TRUE; +} + +static int maybe_start_packet(vorb *f) +{ + if (f->next_seg == -1) { + int x = get8(f); + if (f->eof) return FALSE; // EOF at page boundary is not an error! + if (0x4f != x ) return error(f, VORBIS_missing_capture_pattern); + if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (0x53 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (!start_page_no_capturepattern(f)) return FALSE; + if (f->page_flag & PAGEFLAG_continued_packet) { + // set up enough state that we can read this packet if we want, + // e.g. during recovery + f->last_seg = FALSE; + f->bytes_in_seg = 0; + return error(f, VORBIS_continued_packet_flag_invalid); + } + } + return start_packet(f); +} + +static int next_segment(vorb *f) +{ + int len; + if (f->last_seg) return 0; + if (f->next_seg == -1) { + f->last_seg_which = f->segment_count-1; // in case start_page fails + if (!start_page(f)) { f->last_seg = 1; return 0; } + if (!(f->page_flag & PAGEFLAG_continued_packet)) return error(f, VORBIS_continued_packet_flag_invalid); + } + len = f->segments[f->next_seg++]; + if (len < 255) { + f->last_seg = TRUE; + f->last_seg_which = f->next_seg-1; + } + if (f->next_seg >= f->segment_count) + f->next_seg = -1; + assert(f->bytes_in_seg == 0); + f->bytes_in_seg = len; + return len; +} + +#define EOP (-1) +#define INVALID_BITS (-1) + +static int get8_packet_raw(vorb *f) +{ + if (!f->bytes_in_seg) { // CLANG! + if (f->last_seg) return EOP; + else if (!next_segment(f)) return EOP; + } + assert(f->bytes_in_seg > 0); + --f->bytes_in_seg; + ++f->packet_bytes; + return get8(f); +} + +static int get8_packet(vorb *f) +{ + int x = get8_packet_raw(f); + f->valid_bits = 0; + return x; +} + +static int get32_packet(vorb *f) +{ + uint32 x; + x = get8_packet(f); + x += get8_packet(f) << 8; + x += get8_packet(f) << 16; + x += (uint32) get8_packet(f) << 24; + return x; +} + +static void flush_packet(vorb *f) +{ + while (get8_packet_raw(f) != EOP); +} + +// @OPTIMIZE: this is the secondary bit decoder, so it's probably not as important +// as the huffman decoder? +static uint32 get_bits(vorb *f, int n) +{ + uint32 z; + + if (f->valid_bits < 0) return 0; + if (f->valid_bits < n) { + if (n > 24) { + // the accumulator technique below would not work correctly in this case + z = get_bits(f, 24); + z += get_bits(f, n-24) << 24; + return z; + } + if (f->valid_bits == 0) f->acc = 0; + while (f->valid_bits < n) { + int z = get8_packet_raw(f); + if (z == EOP) { + f->valid_bits = INVALID_BITS; + return 0; + } + f->acc += z << f->valid_bits; + f->valid_bits += 8; + } + } + + assert(f->valid_bits >= n); + z = f->acc & ((1 << n)-1); + f->acc >>= n; + f->valid_bits -= n; + return z; +} + +// @OPTIMIZE: primary accumulator for huffman +// expand the buffer to as many bits as possible without reading off end of packet +// it might be nice to allow f->valid_bits and f->acc to be stored in registers, +// e.g. cache them locally and decode locally +static __forceinline void prep_huffman(vorb *f) +{ + if (f->valid_bits <= 24) { + if (f->valid_bits == 0) f->acc = 0; + do { + int z; + if (f->last_seg && !f->bytes_in_seg) return; + z = get8_packet_raw(f); + if (z == EOP) return; + f->acc += (unsigned) z << f->valid_bits; + f->valid_bits += 8; + } while (f->valid_bits <= 24); + } +} + +enum +{ + VORBIS_packet_id = 1, + VORBIS_packet_comment = 3, + VORBIS_packet_setup = 5 +}; + +static int codebook_decode_scalar_raw(vorb *f, Codebook *c) +{ + int i; + prep_huffman(f); + + if (c->codewords == NULL && c->sorted_codewords == NULL) + return -1; + + // cases to use binary search: sorted_codewords && !c->codewords + // sorted_codewords && c->entries > 8 + if (c->entries > 8 ? c->sorted_codewords!=NULL : !c->codewords) { + // binary search + uint32 code = bit_reverse(f->acc); + int x=0, n=c->sorted_entries, len; + + while (n > 1) { + // invariant: sc[x] <= code < sc[x+n] + int m = x + (n >> 1); + if (c->sorted_codewords[m] <= code) { + x = m; + n -= (n>>1); + } else { + n >>= 1; + } + } + // x is now the sorted index + if (!c->sparse) x = c->sorted_values[x]; + // x is now sorted index if sparse, or symbol otherwise + len = c->codeword_lengths[x]; + if (f->valid_bits >= len) { + f->acc >>= len; + f->valid_bits -= len; + return x; + } + + f->valid_bits = 0; + return -1; + } + + // if small, linear search + assert(!c->sparse); + for (i=0; i < c->entries; ++i) { + if (c->codeword_lengths[i] == NO_CODE) continue; + if (c->codewords[i] == (f->acc & ((1 << c->codeword_lengths[i])-1))) { + if (f->valid_bits >= c->codeword_lengths[i]) { + f->acc >>= c->codeword_lengths[i]; + f->valid_bits -= c->codeword_lengths[i]; + return i; + } + f->valid_bits = 0; + return -1; + } + } + + error(f, VORBIS_invalid_stream); + f->valid_bits = 0; + return -1; +} + +#ifndef STB_VORBIS_NO_INLINE_DECODE + +#define DECODE_RAW(var, f,c) \ + if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) \ + prep_huffman(f); \ + var = f->acc & FAST_HUFFMAN_TABLE_MASK; \ + var = c->fast_huffman[var]; \ + if (var >= 0) { \ + int n = c->codeword_lengths[var]; \ + f->acc >>= n; \ + f->valid_bits -= n; \ + if (f->valid_bits < 0) { f->valid_bits = 0; var = -1; } \ + } else { \ + var = codebook_decode_scalar_raw(f,c); \ + } + +#else + +static int codebook_decode_scalar(vorb *f, Codebook *c) +{ + int i; + if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) + prep_huffman(f); + // fast huffman table lookup + i = f->acc & FAST_HUFFMAN_TABLE_MASK; + i = c->fast_huffman[i]; + if (i >= 0) { + f->acc >>= c->codeword_lengths[i]; + f->valid_bits -= c->codeword_lengths[i]; + if (f->valid_bits < 0) { f->valid_bits = 0; return -1; } + return i; + } + return codebook_decode_scalar_raw(f,c); +} + +#define DECODE_RAW(var,f,c) var = codebook_decode_scalar(f,c); + +#endif + +#define DECODE(var,f,c) \ + DECODE_RAW(var,f,c) \ + if (c->sparse) var = c->sorted_values[var]; + +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + #define DECODE_VQ(var,f,c) DECODE_RAW(var,f,c) +#else + #define DECODE_VQ(var,f,c) DECODE(var,f,c) +#endif + + + + + + +// CODEBOOK_ELEMENT_FAST is an optimization for the CODEBOOK_FLOATS case +// where we avoid one addition +#define CODEBOOK_ELEMENT(c,off) (c->multiplicands[off]) +#define CODEBOOK_ELEMENT_FAST(c,off) (c->multiplicands[off]) +#define CODEBOOK_ELEMENT_BASE(c) (0) + +static int codebook_decode_start(vorb *f, Codebook *c) +{ + int z = -1; + + // type 0 is only legal in a scalar context + if (c->lookup_type == 0) + error(f, VORBIS_invalid_stream); + else { + DECODE_VQ(z,f,c); + if (c->sparse) assert(z < c->sorted_entries); + if (z < 0) { // check for EOP + if (!f->bytes_in_seg) + if (f->last_seg) + return z; + error(f, VORBIS_invalid_stream); + } + } + return z; +} + +static int codebook_decode(vorb *f, Codebook *c, float *output, int len) +{ + int i,z = codebook_decode_start(f,c); + if (z < 0) return FALSE; + if (len > c->dimensions) len = c->dimensions; + +#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + float last = CODEBOOK_ELEMENT_BASE(c); + int div = 1; + for (i=0; i < len; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + output[i] += val; + if (c->sequence_p) last = val + c->minimum_value; + div *= c->lookup_values; + } + return TRUE; + } +#endif + + z *= c->dimensions; + if (c->sequence_p) { + float last = CODEBOOK_ELEMENT_BASE(c); + for (i=0; i < len; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + output[i] += val; + last = val + c->minimum_value; + } + } else { + float last = CODEBOOK_ELEMENT_BASE(c); + for (i=0; i < len; ++i) { + output[i] += CODEBOOK_ELEMENT_FAST(c,z+i) + last; + } + } + + return TRUE; +} + +static int codebook_decode_step(vorb *f, Codebook *c, float *output, int len, int step) +{ + int i,z = codebook_decode_start(f,c); + float last = CODEBOOK_ELEMENT_BASE(c); + if (z < 0) return FALSE; + if (len > c->dimensions) len = c->dimensions; + +#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int div = 1; + for (i=0; i < len; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + output[i*step] += val; + if (c->sequence_p) last = val; + div *= c->lookup_values; + } + return TRUE; + } +#endif + + z *= c->dimensions; + for (i=0; i < len; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + output[i*step] += val; + if (c->sequence_p) last = val; + } + + return TRUE; +} + +static int codebook_decode_deinterleave_repeat(vorb *f, Codebook *c, float **outputs, int ch, int *c_inter_p, int *p_inter_p, int len, int total_decode) +{ + int c_inter = *c_inter_p; + int p_inter = *p_inter_p; + int i,z, effective = c->dimensions; + + // type 0 is only legal in a scalar context + if (c->lookup_type == 0) return error(f, VORBIS_invalid_stream); + + while (total_decode > 0) { + float last = CODEBOOK_ELEMENT_BASE(c); + DECODE_VQ(z,f,c); + #ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + assert(!c->sparse || z < c->sorted_entries); + #endif + if (z < 0) { + if (!f->bytes_in_seg) + if (f->last_seg) return FALSE; + return error(f, VORBIS_invalid_stream); + } + + // if this will take us off the end of the buffers, stop short! + // we check by computing the length of the virtual interleaved + // buffer (len*ch), our current offset within it (p_inter*ch)+(c_inter), + // and the length we'll be using (effective) + if (c_inter + p_inter*ch + effective > len * ch) { + effective = len*ch - (p_inter*ch - c_inter); + } + + #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int div = 1; + for (i=0; i < effective; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + if (c->sequence_p) last = val; + div *= c->lookup_values; + } + } else + #endif + { + z *= c->dimensions; + if (c->sequence_p) { + for (i=0; i < effective; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + last = val; + } + } else { + for (i=0; i < effective; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + } + } + } + + total_decode -= effective; + } + *c_inter_p = c_inter; + *p_inter_p = p_inter; + return TRUE; +} + +static int predict_point(int x, int x0, int x1, int y0, int y1) +{ + int dy = y1 - y0; + int adx = x1 - x0; + // @OPTIMIZE: force int division to round in the right direction... is this necessary on x86? + int err = abs(dy) * (x - x0); + int off = err / adx; + return dy < 0 ? y0 - off : y0 + off; +} + +// the following table is block-copied from the specification +static float inverse_db_table[256] = +{ + 1.0649863e-07f, 1.1341951e-07f, 1.2079015e-07f, 1.2863978e-07f, + 1.3699951e-07f, 1.4590251e-07f, 1.5538408e-07f, 1.6548181e-07f, + 1.7623575e-07f, 1.8768855e-07f, 1.9988561e-07f, 2.1287530e-07f, + 2.2670913e-07f, 2.4144197e-07f, 2.5713223e-07f, 2.7384213e-07f, + 2.9163793e-07f, 3.1059021e-07f, 3.3077411e-07f, 3.5226968e-07f, + 3.7516214e-07f, 3.9954229e-07f, 4.2550680e-07f, 4.5315863e-07f, + 4.8260743e-07f, 5.1396998e-07f, 5.4737065e-07f, 5.8294187e-07f, + 6.2082472e-07f, 6.6116941e-07f, 7.0413592e-07f, 7.4989464e-07f, + 7.9862701e-07f, 8.5052630e-07f, 9.0579828e-07f, 9.6466216e-07f, + 1.0273513e-06f, 1.0941144e-06f, 1.1652161e-06f, 1.2409384e-06f, + 1.3215816e-06f, 1.4074654e-06f, 1.4989305e-06f, 1.5963394e-06f, + 1.7000785e-06f, 1.8105592e-06f, 1.9282195e-06f, 2.0535261e-06f, + 2.1869758e-06f, 2.3290978e-06f, 2.4804557e-06f, 2.6416497e-06f, + 2.8133190e-06f, 2.9961443e-06f, 3.1908506e-06f, 3.3982101e-06f, + 3.6190449e-06f, 3.8542308e-06f, 4.1047004e-06f, 4.3714470e-06f, + 4.6555282e-06f, 4.9580707e-06f, 5.2802740e-06f, 5.6234160e-06f, + 5.9888572e-06f, 6.3780469e-06f, 6.7925283e-06f, 7.2339451e-06f, + 7.7040476e-06f, 8.2047000e-06f, 8.7378876e-06f, 9.3057248e-06f, + 9.9104632e-06f, 1.0554501e-05f, 1.1240392e-05f, 1.1970856e-05f, + 1.2748789e-05f, 1.3577278e-05f, 1.4459606e-05f, 1.5399272e-05f, + 1.6400004e-05f, 1.7465768e-05f, 1.8600792e-05f, 1.9809576e-05f, + 2.1096914e-05f, 2.2467911e-05f, 2.3928002e-05f, 2.5482978e-05f, + 2.7139006e-05f, 2.8902651e-05f, 3.0780908e-05f, 3.2781225e-05f, + 3.4911534e-05f, 3.7180282e-05f, 3.9596466e-05f, 4.2169667e-05f, + 4.4910090e-05f, 4.7828601e-05f, 5.0936773e-05f, 5.4246931e-05f, + 5.7772202e-05f, 6.1526565e-05f, 6.5524908e-05f, 6.9783085e-05f, + 7.4317983e-05f, 7.9147585e-05f, 8.4291040e-05f, 8.9768747e-05f, + 9.5602426e-05f, 0.00010181521f, 0.00010843174f, 0.00011547824f, + 0.00012298267f, 0.00013097477f, 0.00013948625f, 0.00014855085f, + 0.00015820453f, 0.00016848555f, 0.00017943469f, 0.00019109536f, + 0.00020351382f, 0.00021673929f, 0.00023082423f, 0.00024582449f, + 0.00026179955f, 0.00027881276f, 0.00029693158f, 0.00031622787f, + 0.00033677814f, 0.00035866388f, 0.00038197188f, 0.00040679456f, + 0.00043323036f, 0.00046138411f, 0.00049136745f, 0.00052329927f, + 0.00055730621f, 0.00059352311f, 0.00063209358f, 0.00067317058f, + 0.00071691700f, 0.00076350630f, 0.00081312324f, 0.00086596457f, + 0.00092223983f, 0.00098217216f, 0.0010459992f, 0.0011139742f, + 0.0011863665f, 0.0012634633f, 0.0013455702f, 0.0014330129f, + 0.0015261382f, 0.0016253153f, 0.0017309374f, 0.0018434235f, + 0.0019632195f, 0.0020908006f, 0.0022266726f, 0.0023713743f, + 0.0025254795f, 0.0026895994f, 0.0028643847f, 0.0030505286f, + 0.0032487691f, 0.0034598925f, 0.0036847358f, 0.0039241906f, + 0.0041792066f, 0.0044507950f, 0.0047400328f, 0.0050480668f, + 0.0053761186f, 0.0057254891f, 0.0060975636f, 0.0064938176f, + 0.0069158225f, 0.0073652516f, 0.0078438871f, 0.0083536271f, + 0.0088964928f, 0.009474637f, 0.010090352f, 0.010746080f, + 0.011444421f, 0.012188144f, 0.012980198f, 0.013823725f, + 0.014722068f, 0.015678791f, 0.016697687f, 0.017782797f, + 0.018938423f, 0.020169149f, 0.021479854f, 0.022875735f, + 0.024362330f, 0.025945531f, 0.027631618f, 0.029427276f, + 0.031339626f, 0.033376252f, 0.035545228f, 0.037855157f, + 0.040315199f, 0.042935108f, 0.045725273f, 0.048696758f, + 0.051861348f, 0.055231591f, 0.058820850f, 0.062643361f, + 0.066714279f, 0.071049749f, 0.075666962f, 0.080584227f, + 0.085821044f, 0.091398179f, 0.097337747f, 0.10366330f, + 0.11039993f, 0.11757434f, 0.12521498f, 0.13335215f, + 0.14201813f, 0.15124727f, 0.16107617f, 0.17154380f, + 0.18269168f, 0.19456402f, 0.20720788f, 0.22067342f, + 0.23501402f, 0.25028656f, 0.26655159f, 0.28387361f, + 0.30232132f, 0.32196786f, 0.34289114f, 0.36517414f, + 0.38890521f, 0.41417847f, 0.44109412f, 0.46975890f, + 0.50028648f, 0.53279791f, 0.56742212f, 0.60429640f, + 0.64356699f, 0.68538959f, 0.72993007f, 0.77736504f, + 0.82788260f, 0.88168307f, 0.9389798f, 1.0f +}; + + +// @OPTIMIZE: if you want to replace this bresenham line-drawing routine, +// note that you must produce bit-identical output to decode correctly; +// this specific sequence of operations is specified in the spec (it's +// drawing integer-quantized frequency-space lines that the encoder +// expects to be exactly the same) +// ... also, isn't the whole point of Bresenham's algorithm to NOT +// have to divide in the setup? sigh. +#ifndef STB_VORBIS_NO_DEFER_FLOOR +#define LINE_OP(a,b) a *= b +#else +#define LINE_OP(a,b) a = b +#endif + +#ifdef STB_VORBIS_DIVIDE_TABLE +#define DIVTAB_NUMER 32 +#define DIVTAB_DENOM 64 +int8 integer_divide_table[DIVTAB_NUMER][DIVTAB_DENOM]; // 2KB +#endif + +static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y1, int n) +{ + int dy = y1 - y0; + int adx = x1 - x0; + int ady = abs(dy); + int base; + int x=x0,y=y0; + int err = 0; + int sy; + +#ifdef STB_VORBIS_DIVIDE_TABLE + if (adx < DIVTAB_DENOM && ady < DIVTAB_NUMER) { + if (dy < 0) { + base = -integer_divide_table[ady][adx]; + sy = base-1; + } else { + base = integer_divide_table[ady][adx]; + sy = base+1; + } + } else { + base = dy / adx; + if (dy < 0) + sy = base - 1; + else + sy = base+1; + } +#else + base = dy / adx; + if (dy < 0) + sy = base - 1; + else + sy = base+1; +#endif + ady -= abs(base) * adx; + if (x1 > n) x1 = n; + if (x < x1) { + LINE_OP(output[x], inverse_db_table[y&255]); + for (++x; x < x1; ++x) { + err += ady; + if (err >= adx) { + err -= adx; + y += sy; + } else + y += base; + LINE_OP(output[x], inverse_db_table[y&255]); + } + } +} + +static int residue_decode(vorb *f, Codebook *book, float *target, int offset, int n, int rtype) +{ + int k; + if (rtype == 0) { + int step = n / book->dimensions; + for (k=0; k < step; ++k) + if (!codebook_decode_step(f, book, target+offset+k, n-offset-k, step)) + return FALSE; + } else { + for (k=0; k < n; ) { + if (!codebook_decode(f, book, target+offset, n-k)) + return FALSE; + k += book->dimensions; + offset += book->dimensions; + } + } + return TRUE; +} + +// n is 1/2 of the blocksize -- +// specification: "Correct per-vector decode length is [n]/2" +static void decode_residue(vorb *f, float *residue_buffers[], int ch, int n, int rn, uint8 *do_not_decode) +{ + int i,j,pass; + Residue *r = f->residue_config + rn; + int rtype = f->residue_types[rn]; + int c = r->classbook; + int classwords = f->codebooks[c].dimensions; + unsigned int actual_size = rtype == 2 ? n*2 : n; + unsigned int limit_r_begin = (r->begin < actual_size ? r->begin : actual_size); + unsigned int limit_r_end = (r->end < actual_size ? r->end : actual_size); + int n_read = limit_r_end - limit_r_begin; + int part_read = n_read / r->part_size; + int temp_alloc_point = temp_alloc_save(f); + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + uint8 ***part_classdata = (uint8 ***) temp_block_array(f,f->channels, part_read * sizeof(**part_classdata)); + #else + int **classifications = (int **) temp_block_array(f,f->channels, part_read * sizeof(**classifications)); + #endif + + CHECK(f); + + for (i=0; i < ch; ++i) + if (!do_not_decode[i]) + memset(residue_buffers[i], 0, sizeof(float) * n); + + if (rtype == 2 && ch != 1) { + for (j=0; j < ch; ++j) + if (!do_not_decode[j]) + break; + if (j == ch) + goto done; + + for (pass=0; pass < 8; ++pass) { + int pcount = 0, class_set = 0; + if (ch == 2) { + while (pcount < part_read) { + int z = r->begin + pcount*r->part_size; + int c_inter = (z & 1), p_inter = z>>1; + if (pass == 0) { + Codebook *c = f->codebooks+r->classbook; + int q; + DECODE(q,f,c); + if (q == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[0][class_set] = r->classdata[q]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[0][i+pcount] = q % r->classifications; + q /= r->classifications; + } + #endif + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + int z = r->begin + pcount*r->part_size; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[0][class_set][i]; + #else + int c = classifications[0][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + Codebook *book = f->codebooks + b; + #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + #else + // saves 1% + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + #endif + } else { + z += r->part_size; + c_inter = z & 1; + p_inter = z >> 1; + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } else if (ch > 2) { + while (pcount < part_read) { + int z = r->begin + pcount*r->part_size; + int c_inter = z % ch, p_inter = z/ch; + if (pass == 0) { + Codebook *c = f->codebooks+r->classbook; + int q; + DECODE(q,f,c); + if (q == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[0][class_set] = r->classdata[q]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[0][i+pcount] = q % r->classifications; + q /= r->classifications; + } + #endif + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + int z = r->begin + pcount*r->part_size; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[0][class_set][i]; + #else + int c = classifications[0][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + Codebook *book = f->codebooks + b; + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + } else { + z += r->part_size; + c_inter = z % ch; + p_inter = z / ch; + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } + } + goto done; + } + CHECK(f); + + for (pass=0; pass < 8; ++pass) { + int pcount = 0, class_set=0; + while (pcount < part_read) { + if (pass == 0) { + for (j=0; j < ch; ++j) { + if (!do_not_decode[j]) { + Codebook *c = f->codebooks+r->classbook; + int temp; + DECODE(temp,f,c); + if (temp == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[j][class_set] = r->classdata[temp]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[j][i+pcount] = temp % r->classifications; + temp /= r->classifications; + } + #endif + } + } + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + for (j=0; j < ch; ++j) { + if (!do_not_decode[j]) { + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[j][class_set][i]; + #else + int c = classifications[j][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + float *target = residue_buffers[j]; + int offset = r->begin + pcount * r->part_size; + int n = r->part_size; + Codebook *book = f->codebooks + b; + if (!residue_decode(f, book, target, offset, n, rtype)) + goto done; + } + } + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } + done: + CHECK(f); + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + temp_free(f,part_classdata); + #else + temp_free(f,classifications); + #endif + temp_alloc_restore(f,temp_alloc_point); +} + + +#if 0 +// slow way for debugging +void inverse_mdct_slow(float *buffer, int n) +{ + int i,j; + int n2 = n >> 1; + float *x = (float *) malloc(sizeof(*x) * n2); + memcpy(x, buffer, sizeof(*x) * n2); + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n2; ++j) + // formula from paper: + //acc += n/4.0f * x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); + // formula from wikipedia + //acc += 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); + // these are equivalent, except the formula from the paper inverts the multiplier! + // however, what actually works is NO MULTIPLIER!?! + //acc += 64 * 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); + acc += x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); + buffer[i] = acc; + } + free(x); +} +#elif 0 +// same as above, but just barely able to run in real time on modern machines +void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) +{ + float mcos[16384]; + int i,j; + int n2 = n >> 1, nmask = (n << 2) -1; + float *x = (float *) malloc(sizeof(*x) * n2); + memcpy(x, buffer, sizeof(*x) * n2); + for (i=0; i < 4*n; ++i) + mcos[i] = (float) cos(M_PI / 2 * i / n); + + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n2; ++j) + acc += x[j] * mcos[(2 * i + 1 + n2)*(2*j+1) & nmask]; + buffer[i] = acc; + } + free(x); +} +#elif 0 +// transform to use a slow dct-iv; this is STILL basically trivial, +// but only requires half as many ops +void dct_iv_slow(float *buffer, int n) +{ + float mcos[16384]; + float x[2048]; + int i,j; + int n2 = n >> 1, nmask = (n << 3) - 1; + memcpy(x, buffer, sizeof(*x) * n); + for (i=0; i < 8*n; ++i) + mcos[i] = (float) cos(M_PI / 4 * i / n); + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n; ++j) + acc += x[j] * mcos[((2 * i + 1)*(2*j+1)) & nmask]; + buffer[i] = acc; + } +} + +void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) +{ + int i, n4 = n >> 2, n2 = n >> 1, n3_4 = n - n4; + float temp[4096]; + + memcpy(temp, buffer, n2 * sizeof(float)); + dct_iv_slow(temp, n2); // returns -c'-d, a-b' + + for (i=0; i < n4 ; ++i) buffer[i] = temp[i+n4]; // a-b' + for ( ; i < n3_4; ++i) buffer[i] = -temp[n3_4 - i - 1]; // b-a', c+d' + for ( ; i < n ; ++i) buffer[i] = -temp[i - n3_4]; // c'+d +} +#endif + +#ifndef LIBVORBIS_MDCT +#define LIBVORBIS_MDCT 0 +#endif + +#if LIBVORBIS_MDCT +// directly call the vorbis MDCT using an interface documented +// by Jeff Roberts... useful for performance comparison +typedef struct +{ + int n; + int log2n; + + float *trig; + int *bitrev; + + float scale; +} mdct_lookup; + +extern void mdct_init(mdct_lookup *lookup, int n); +extern void mdct_clear(mdct_lookup *l); +extern void mdct_backward(mdct_lookup *init, float *in, float *out); + +mdct_lookup M1,M2; + +void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) +{ + mdct_lookup *M; + if (M1.n == n) M = &M1; + else if (M2.n == n) M = &M2; + else if (M1.n == 0) { mdct_init(&M1, n); M = &M1; } + else { + if (M2.n) __asm int 3; + mdct_init(&M2, n); + M = &M2; + } + + mdct_backward(M, buffer, buffer); +} +#endif + + +// the following were split out into separate functions while optimizing; +// they could be pushed back up but eh. __forceinline showed no change; +// they're probably already being inlined. +static void imdct_step3_iter0_loop(int n, float *e, int i_off, int k_off, float *A) +{ + float *ee0 = e + i_off; + float *ee2 = ee0 + k_off; + int i; + + assert((n & 3) == 0); + for (i=(n>>2); i > 0; --i) { + float k00_20, k01_21; + k00_20 = ee0[ 0] - ee2[ 0]; + k01_21 = ee0[-1] - ee2[-1]; + ee0[ 0] += ee2[ 0];//ee0[ 0] = ee0[ 0] + ee2[ 0]; + ee0[-1] += ee2[-1];//ee0[-1] = ee0[-1] + ee2[-1]; + ee2[ 0] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-1] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-2] - ee2[-2]; + k01_21 = ee0[-3] - ee2[-3]; + ee0[-2] += ee2[-2];//ee0[-2] = ee0[-2] + ee2[-2]; + ee0[-3] += ee2[-3];//ee0[-3] = ee0[-3] + ee2[-3]; + ee2[-2] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-3] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-4] - ee2[-4]; + k01_21 = ee0[-5] - ee2[-5]; + ee0[-4] += ee2[-4];//ee0[-4] = ee0[-4] + ee2[-4]; + ee0[-5] += ee2[-5];//ee0[-5] = ee0[-5] + ee2[-5]; + ee2[-4] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-5] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-6] - ee2[-6]; + k01_21 = ee0[-7] - ee2[-7]; + ee0[-6] += ee2[-6];//ee0[-6] = ee0[-6] + ee2[-6]; + ee0[-7] += ee2[-7];//ee0[-7] = ee0[-7] + ee2[-7]; + ee2[-6] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-7] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + ee0 -= 8; + ee2 -= 8; + } +} + +static void imdct_step3_inner_r_loop(int lim, float *e, int d0, int k_off, float *A, int k1) +{ + int i; + float k00_20, k01_21; + + float *e0 = e + d0; + float *e2 = e0 + k_off; + + for (i=lim >> 2; i > 0; --i) { + k00_20 = e0[-0] - e2[-0]; + k01_21 = e0[-1] - e2[-1]; + e0[-0] += e2[-0];//e0[-0] = e0[-0] + e2[-0]; + e0[-1] += e2[-1];//e0[-1] = e0[-1] + e2[-1]; + e2[-0] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-1] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-2] - e2[-2]; + k01_21 = e0[-3] - e2[-3]; + e0[-2] += e2[-2];//e0[-2] = e0[-2] + e2[-2]; + e0[-3] += e2[-3];//e0[-3] = e0[-3] + e2[-3]; + e2[-2] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-3] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-4] - e2[-4]; + k01_21 = e0[-5] - e2[-5]; + e0[-4] += e2[-4];//e0[-4] = e0[-4] + e2[-4]; + e0[-5] += e2[-5];//e0[-5] = e0[-5] + e2[-5]; + e2[-4] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-5] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-6] - e2[-6]; + k01_21 = e0[-7] - e2[-7]; + e0[-6] += e2[-6];//e0[-6] = e0[-6] + e2[-6]; + e0[-7] += e2[-7];//e0[-7] = e0[-7] + e2[-7]; + e2[-6] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-7] = (k01_21)*A[0] + (k00_20) * A[1]; + + e0 -= 8; + e2 -= 8; + + A += k1; + } +} + +static void imdct_step3_inner_s_loop(int n, float *e, int i_off, int k_off, float *A, int a_off, int k0) +{ + int i; + float A0 = A[0]; + float A1 = A[0+1]; + float A2 = A[0+a_off]; + float A3 = A[0+a_off+1]; + float A4 = A[0+a_off*2+0]; + float A5 = A[0+a_off*2+1]; + float A6 = A[0+a_off*3+0]; + float A7 = A[0+a_off*3+1]; + + float k00,k11; + + float *ee0 = e +i_off; + float *ee2 = ee0+k_off; + + for (i=n; i > 0; --i) { + k00 = ee0[ 0] - ee2[ 0]; + k11 = ee0[-1] - ee2[-1]; + ee0[ 0] = ee0[ 0] + ee2[ 0]; + ee0[-1] = ee0[-1] + ee2[-1]; + ee2[ 0] = (k00) * A0 - (k11) * A1; + ee2[-1] = (k11) * A0 + (k00) * A1; + + k00 = ee0[-2] - ee2[-2]; + k11 = ee0[-3] - ee2[-3]; + ee0[-2] = ee0[-2] + ee2[-2]; + ee0[-3] = ee0[-3] + ee2[-3]; + ee2[-2] = (k00) * A2 - (k11) * A3; + ee2[-3] = (k11) * A2 + (k00) * A3; + + k00 = ee0[-4] - ee2[-4]; + k11 = ee0[-5] - ee2[-5]; + ee0[-4] = ee0[-4] + ee2[-4]; + ee0[-5] = ee0[-5] + ee2[-5]; + ee2[-4] = (k00) * A4 - (k11) * A5; + ee2[-5] = (k11) * A4 + (k00) * A5; + + k00 = ee0[-6] - ee2[-6]; + k11 = ee0[-7] - ee2[-7]; + ee0[-6] = ee0[-6] + ee2[-6]; + ee0[-7] = ee0[-7] + ee2[-7]; + ee2[-6] = (k00) * A6 - (k11) * A7; + ee2[-7] = (k11) * A6 + (k00) * A7; + + ee0 -= k0; + ee2 -= k0; + } +} + +static __forceinline void iter_54(float *z) +{ + float k00,k11,k22,k33; + float y0,y1,y2,y3; + + k00 = z[ 0] - z[-4]; + y0 = z[ 0] + z[-4]; + y2 = z[-2] + z[-6]; + k22 = z[-2] - z[-6]; + + z[-0] = y0 + y2; // z0 + z4 + z2 + z6 + z[-2] = y0 - y2; // z0 + z4 - z2 - z6 + + // done with y0,y2 + + k33 = z[-3] - z[-7]; + + z[-4] = k00 + k33; // z0 - z4 + z3 - z7 + z[-6] = k00 - k33; // z0 - z4 - z3 + z7 + + // done with k33 + + k11 = z[-1] - z[-5]; + y1 = z[-1] + z[-5]; + y3 = z[-3] + z[-7]; + + z[-1] = y1 + y3; // z1 + z5 + z3 + z7 + z[-3] = y1 - y3; // z1 + z5 - z3 - z7 + z[-5] = k11 - k22; // z1 - z5 + z2 - z6 + z[-7] = k11 + k22; // z1 - z5 - z2 + z6 +} + +static void imdct_step3_inner_s_loop_ld654(int n, float *e, int i_off, float *A, int base_n) +{ + int a_off = base_n >> 3; + float A2 = A[0+a_off]; + float *z = e + i_off; + float *base = z - 16 * n; + + while (z > base) { + float k00,k11; + float l00,l11; + + k00 = z[-0] - z[ -8]; + k11 = z[-1] - z[ -9]; + l00 = z[-2] - z[-10]; + l11 = z[-3] - z[-11]; + z[ -0] = z[-0] + z[ -8]; + z[ -1] = z[-1] + z[ -9]; + z[ -2] = z[-2] + z[-10]; + z[ -3] = z[-3] + z[-11]; + z[ -8] = k00; + z[ -9] = k11; + z[-10] = (l00+l11) * A2; + z[-11] = (l11-l00) * A2; + + k00 = z[ -4] - z[-12]; + k11 = z[ -5] - z[-13]; + l00 = z[ -6] - z[-14]; + l11 = z[ -7] - z[-15]; + z[ -4] = z[ -4] + z[-12]; + z[ -5] = z[ -5] + z[-13]; + z[ -6] = z[ -6] + z[-14]; + z[ -7] = z[ -7] + z[-15]; + z[-12] = k11; + z[-13] = -k00; + z[-14] = (l11-l00) * A2; + z[-15] = (l00+l11) * -A2; + + iter_54(z); + iter_54(z-8); + z -= 16; + } +} + +static void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) +{ + int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; + int ld; + // @OPTIMIZE: reduce register pressure by using fewer variables? + int save_point = temp_alloc_save(f); + float *buf2 = (float *) temp_alloc(f, n2 * sizeof(*buf2)); + float *u=NULL,*v=NULL; + // twiddle factors + float *A = f->A[blocktype]; + + // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" + // See notes about bugs in that paper in less-optimal implementation 'inverse_mdct_old' after this function. + + // kernel from paper + + + // merged: + // copy and reflect spectral data + // step 0 + + // note that it turns out that the items added together during + // this step are, in fact, being added to themselves (as reflected + // by step 0). inexplicable inefficiency! this became obvious + // once I combined the passes. + + // so there's a missing 'times 2' here (for adding X to itself). + // this propagates through linearly to the end, where the numbers + // are 1/2 too small, and need to be compensated for. + + { + float *d,*e, *AA, *e_stop; + d = &buf2[n2-2]; + AA = A; + e = &buffer[0]; + e_stop = &buffer[n2]; + while (e != e_stop) { + d[1] = (e[0] * AA[0] - e[2]*AA[1]); + d[0] = (e[0] * AA[1] + e[2]*AA[0]); + d -= 2; + AA += 2; + e += 4; + } + + e = &buffer[n2-3]; + while (d >= buf2) { + d[1] = (-e[2] * AA[0] - -e[0]*AA[1]); + d[0] = (-e[2] * AA[1] + -e[0]*AA[0]); + d -= 2; + AA += 2; + e -= 4; + } + } + + // now we use symbolic names for these, so that we can + // possibly swap their meaning as we change which operations + // are in place + + u = buffer; + v = buf2; + + // step 2 (paper output is w, now u) + // this could be in place, but the data ends up in the wrong + // place... _somebody_'s got to swap it, so this is nominated + { + float *AA = &A[n2-8]; + float *d0,*d1, *e0, *e1; + + e0 = &v[n4]; + e1 = &v[0]; + + d0 = &u[n4]; + d1 = &u[0]; + + while (AA >= A) { + float v40_20, v41_21; + + v41_21 = e0[1] - e1[1]; + v40_20 = e0[0] - e1[0]; + d0[1] = e0[1] + e1[1]; + d0[0] = e0[0] + e1[0]; + d1[1] = v41_21*AA[4] - v40_20*AA[5]; + d1[0] = v40_20*AA[4] + v41_21*AA[5]; + + v41_21 = e0[3] - e1[3]; + v40_20 = e0[2] - e1[2]; + d0[3] = e0[3] + e1[3]; + d0[2] = e0[2] + e1[2]; + d1[3] = v41_21*AA[0] - v40_20*AA[1]; + d1[2] = v40_20*AA[0] + v41_21*AA[1]; + + AA -= 8; + + d0 += 4; + d1 += 4; + e0 += 4; + e1 += 4; + } + } + + // step 3 + ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + + // optimized step 3: + + // the original step3 loop can be nested r inside s or s inside r; + // it's written originally as s inside r, but this is dumb when r + // iterates many times, and s few. So I have two copies of it and + // switch between them halfway. + + // this is iteration 0 of step 3 + imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*0, -(n >> 3), A); + imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*1, -(n >> 3), A); + + // this is iteration 1 of step 3 + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*0, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*1, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*2, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*3, -(n >> 4), A, 16); + + l=2; + for (; l < (ld-3)>>1; ++l) { + int k0 = n >> (l+2), k0_2 = k0>>1; + int lim = 1 << (l+1); + int i; + for (i=0; i < lim; ++i) + imdct_step3_inner_r_loop(n >> (l+4), u, n2-1 - k0*i, -k0_2, A, 1 << (l+3)); + } + + for (; l < ld-6; ++l) { + int k0 = n >> (l+2), k1 = 1 << (l+3), k0_2 = k0>>1; + int rlim = n >> (l+6), r; + int lim = 1 << (l+1); + int i_off; + float *A0 = A; + i_off = n2-1; + for (r=rlim; r > 0; --r) { + imdct_step3_inner_s_loop(lim, u, i_off, -k0_2, A0, k1, k0); + A0 += k1*4; + i_off -= 8; + } + } + + // iterations with count: + // ld-6,-5,-4 all interleaved together + // the big win comes from getting rid of needless flops + // due to the constants on pass 5 & 4 being all 1 and 0; + // combining them to be simultaneous to improve cache made little difference + imdct_step3_inner_s_loop_ld654(n >> 5, u, n2-1, A, n); + + // output is u + + // step 4, 5, and 6 + // cannot be in-place because of step 5 + { + uint16 *bitrev = f->bit_reverse[blocktype]; + // weirdly, I'd have thought reading sequentially and writing + // erratically would have been better than vice-versa, but in + // fact that's not what my testing showed. (That is, with + // j = bitreverse(i), do you read i and write j, or read j and write i.) + + float *d0 = &v[n4-4]; + float *d1 = &v[n2-4]; + while (d0 >= v) { + int k4; + + k4 = bitrev[0]; + d1[3] = u[k4+0]; + d1[2] = u[k4+1]; + d0[3] = u[k4+2]; + d0[2] = u[k4+3]; + + k4 = bitrev[1]; + d1[1] = u[k4+0]; + d1[0] = u[k4+1]; + d0[1] = u[k4+2]; + d0[0] = u[k4+3]; + + d0 -= 4; + d1 -= 4; + bitrev += 2; + } + } + // (paper output is u, now v) + + + // data must be in buf2 + assert(v == buf2); + + // step 7 (paper output is v, now v) + // this is now in place + { + float *C = f->C[blocktype]; + float *d, *e; + + d = v; + e = v + n2 - 4; + + while (d < e) { + float a02,a11,b0,b1,b2,b3; + + a02 = d[0] - e[2]; + a11 = d[1] + e[3]; + + b0 = C[1]*a02 + C[0]*a11; + b1 = C[1]*a11 - C[0]*a02; + + b2 = d[0] + e[ 2]; + b3 = d[1] - e[ 3]; + + d[0] = b2 + b0; + d[1] = b3 + b1; + e[2] = b2 - b0; + e[3] = b1 - b3; + + a02 = d[2] - e[0]; + a11 = d[3] + e[1]; + + b0 = C[3]*a02 + C[2]*a11; + b1 = C[3]*a11 - C[2]*a02; + + b2 = d[2] + e[ 0]; + b3 = d[3] - e[ 1]; + + d[2] = b2 + b0; + d[3] = b3 + b1; + e[0] = b2 - b0; + e[1] = b1 - b3; + + C += 4; + d += 4; + e -= 4; + } + } + + // data must be in buf2 + + + // step 8+decode (paper output is X, now buffer) + // this generates pairs of data a la 8 and pushes them directly through + // the decode kernel (pushing rather than pulling) to avoid having + // to make another pass later + + // this cannot POSSIBLY be in place, so we refer to the buffers directly + + { + float *d0,*d1,*d2,*d3; + + float *B = f->B[blocktype] + n2 - 8; + float *e = buf2 + n2 - 8; + d0 = &buffer[0]; + d1 = &buffer[n2-4]; + d2 = &buffer[n2]; + d3 = &buffer[n-4]; + while (e >= v) { + float p0,p1,p2,p3; + + p3 = e[6]*B[7] - e[7]*B[6]; + p2 = -e[6]*B[6] - e[7]*B[7]; + + d0[0] = p3; + d1[3] = - p3; + d2[0] = p2; + d3[3] = p2; + + p1 = e[4]*B[5] - e[5]*B[4]; + p0 = -e[4]*B[4] - e[5]*B[5]; + + d0[1] = p1; + d1[2] = - p1; + d2[1] = p0; + d3[2] = p0; + + p3 = e[2]*B[3] - e[3]*B[2]; + p2 = -e[2]*B[2] - e[3]*B[3]; + + d0[2] = p3; + d1[1] = - p3; + d2[2] = p2; + d3[1] = p2; + + p1 = e[0]*B[1] - e[1]*B[0]; + p0 = -e[0]*B[0] - e[1]*B[1]; + + d0[3] = p1; + d1[0] = - p1; + d2[3] = p0; + d3[0] = p0; + + B -= 8; + e -= 8; + d0 += 4; + d2 += 4; + d1 -= 4; + d3 -= 4; + } + } + + temp_free(f,buf2); + temp_alloc_restore(f,save_point); +} + +#if 0 +// this is the original version of the above code, if you want to optimize it from scratch +void inverse_mdct_naive(float *buffer, int n) +{ + float s; + float A[1 << 12], B[1 << 12], C[1 << 11]; + int i,k,k2,k4, n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; + int n3_4 = n - n4, ld; + // how can they claim this only uses N words?! + // oh, because they're only used sparsely, whoops + float u[1 << 13], X[1 << 13], v[1 << 13], w[1 << 13]; + // set up twiddle factors + + for (k=k2=0; k < n4; ++k,k2+=2) { + A[k2 ] = (float) cos(4*k*M_PI/n); + A[k2+1] = (float) -sin(4*k*M_PI/n); + B[k2 ] = (float) cos((k2+1)*M_PI/n/2); + B[k2+1] = (float) sin((k2+1)*M_PI/n/2); + } + for (k=k2=0; k < n8; ++k,k2+=2) { + C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); + C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); + } + + // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" + // Note there are bugs in that pseudocode, presumably due to them attempting + // to rename the arrays nicely rather than representing the way their actual + // implementation bounces buffers back and forth. As a result, even in the + // "some formulars corrected" version, a direct implementation fails. These + // are noted below as "paper bug". + + // copy and reflect spectral data + for (k=0; k < n2; ++k) u[k] = buffer[k]; + for ( ; k < n ; ++k) u[k] = -buffer[n - k - 1]; + // kernel from paper + // step 1 + for (k=k2=k4=0; k < n4; k+=1, k2+=2, k4+=4) { + v[n-k4-1] = (u[k4] - u[n-k4-1]) * A[k2] - (u[k4+2] - u[n-k4-3])*A[k2+1]; + v[n-k4-3] = (u[k4] - u[n-k4-1]) * A[k2+1] + (u[k4+2] - u[n-k4-3])*A[k2]; + } + // step 2 + for (k=k4=0; k < n8; k+=1, k4+=4) { + w[n2+3+k4] = v[n2+3+k4] + v[k4+3]; + w[n2+1+k4] = v[n2+1+k4] + v[k4+1]; + w[k4+3] = (v[n2+3+k4] - v[k4+3])*A[n2-4-k4] - (v[n2+1+k4]-v[k4+1])*A[n2-3-k4]; + w[k4+1] = (v[n2+1+k4] - v[k4+1])*A[n2-4-k4] + (v[n2+3+k4]-v[k4+3])*A[n2-3-k4]; + } + // step 3 + ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + for (l=0; l < ld-3; ++l) { + int k0 = n >> (l+2), k1 = 1 << (l+3); + int rlim = n >> (l+4), r4, r; + int s2lim = 1 << (l+2), s2; + for (r=r4=0; r < rlim; r4+=4,++r) { + for (s2=0; s2 < s2lim; s2+=2) { + u[n-1-k0*s2-r4] = w[n-1-k0*s2-r4] + w[n-1-k0*(s2+1)-r4]; + u[n-3-k0*s2-r4] = w[n-3-k0*s2-r4] + w[n-3-k0*(s2+1)-r4]; + u[n-1-k0*(s2+1)-r4] = (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1] + - (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1+1]; + u[n-3-k0*(s2+1)-r4] = (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1] + + (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1+1]; + } + } + if (l+1 < ld-3) { + // paper bug: ping-ponging of u&w here is omitted + memcpy(w, u, sizeof(u)); + } + } + + // step 4 + for (i=0; i < n8; ++i) { + int j = bit_reverse(i) >> (32-ld+3); + assert(j < n8); + if (i == j) { + // paper bug: original code probably swapped in place; if copying, + // need to directly copy in this case + int i8 = i << 3; + v[i8+1] = u[i8+1]; + v[i8+3] = u[i8+3]; + v[i8+5] = u[i8+5]; + v[i8+7] = u[i8+7]; + } else if (i < j) { + int i8 = i << 3, j8 = j << 3; + v[j8+1] = u[i8+1], v[i8+1] = u[j8 + 1]; + v[j8+3] = u[i8+3], v[i8+3] = u[j8 + 3]; + v[j8+5] = u[i8+5], v[i8+5] = u[j8 + 5]; + v[j8+7] = u[i8+7], v[i8+7] = u[j8 + 7]; + } + } + // step 5 + for (k=0; k < n2; ++k) { + w[k] = v[k*2+1]; + } + // step 6 + for (k=k2=k4=0; k < n8; ++k, k2 += 2, k4 += 4) { + u[n-1-k2] = w[k4]; + u[n-2-k2] = w[k4+1]; + u[n3_4 - 1 - k2] = w[k4+2]; + u[n3_4 - 2 - k2] = w[k4+3]; + } + // step 7 + for (k=k2=0; k < n8; ++k, k2 += 2) { + v[n2 + k2 ] = ( u[n2 + k2] + u[n-2-k2] + C[k2+1]*(u[n2+k2]-u[n-2-k2]) + C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; + v[n-2 - k2] = ( u[n2 + k2] + u[n-2-k2] - C[k2+1]*(u[n2+k2]-u[n-2-k2]) - C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; + v[n2+1+ k2] = ( u[n2+1+k2] - u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; + v[n-1 - k2] = (-u[n2+1+k2] + u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; + } + // step 8 + for (k=k2=0; k < n4; ++k,k2 += 2) { + X[k] = v[k2+n2]*B[k2 ] + v[k2+1+n2]*B[k2+1]; + X[n2-1-k] = v[k2+n2]*B[k2+1] - v[k2+1+n2]*B[k2 ]; + } + + // decode kernel to output + // determined the following value experimentally + // (by first figuring out what made inverse_mdct_slow work); then matching that here + // (probably vorbis encoder premultiplies by n or n/2, to save it on the decoder?) + s = 0.5; // theoretically would be n4 + + // [[[ note! the s value of 0.5 is compensated for by the B[] in the current code, + // so it needs to use the "old" B values to behave correctly, or else + // set s to 1.0 ]]] + for (i=0; i < n4 ; ++i) buffer[i] = s * X[i+n4]; + for ( ; i < n3_4; ++i) buffer[i] = -s * X[n3_4 - i - 1]; + for ( ; i < n ; ++i) buffer[i] = -s * X[i - n3_4]; +} +#endif + +static float *get_window(vorb *f, int len) +{ + len <<= 1; + if (len == f->blocksize_0) return f->window[0]; + if (len == f->blocksize_1) return f->window[1]; + return NULL; +} + +#ifndef STB_VORBIS_NO_DEFER_FLOOR +typedef int16 YTYPE; +#else +typedef int YTYPE; +#endif +static int do_floor(vorb *f, Mapping *map, int i, int n, float *target, YTYPE *finalY, uint8 *step2_flag) +{ + int n2 = n >> 1; + int s = map->chan[i].mux, floor; + floor = map->submap_floor[s]; + if (f->floor_types[floor] == 0) { + return error(f, VORBIS_invalid_stream); + } else { + Floor1 *g = &f->floor_config[floor].floor1; + int j,q; + int lx = 0, ly = finalY[0] * g->floor1_multiplier; + for (q=1; q < g->values; ++q) { + j = g->sorted_order[q]; + #ifndef STB_VORBIS_NO_DEFER_FLOOR + STBV_NOTUSED(step2_flag); + if (finalY[j] >= 0) + #else + if (step2_flag[j]) + #endif + { + int hy = finalY[j] * g->floor1_multiplier; + int hx = g->Xlist[j]; + if (lx != hx) + draw_line(target, lx,ly, hx,hy, n2); + CHECK(f); + lx = hx, ly = hy; + } + } + if (lx < n2) { + // optimization of: draw_line(target, lx,ly, n,ly, n2); + for (j=lx; j < n2; ++j) + LINE_OP(target[j], inverse_db_table[ly]); + CHECK(f); + } + } + return TRUE; +} + +// The meaning of "left" and "right" +// +// For a given frame: +// we compute samples from 0..n +// window_center is n/2 +// we'll window and mix the samples from left_start to left_end with data from the previous frame +// all of the samples from left_end to right_start can be output without mixing; however, +// this interval is 0-length except when transitioning between short and long frames +// all of the samples from right_start to right_end need to be mixed with the next frame, +// which we don't have, so those get saved in a buffer +// frame N's right_end-right_start, the number of samples to mix with the next frame, +// has to be the same as frame N+1's left_end-left_start (which they are by +// construction) + +static int vorbis_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) +{ + Mode *m; + int i, n, prev, next, window_center; + f->channel_buffer_start = f->channel_buffer_end = 0; + + retry: + if (f->eof) return FALSE; + if (!maybe_start_packet(f)) + return FALSE; + // check packet type + if (get_bits(f,1) != 0) { + if (IS_PUSH_MODE(f)) + return error(f,VORBIS_bad_packet_type); + while (EOP != get8_packet(f)); + goto retry; + } + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + + i = get_bits(f, ilog(f->mode_count-1)); + if (i == EOP) return FALSE; + if (i >= f->mode_count) return FALSE; + *mode = i; + m = f->mode_config + i; + if (m->blockflag) { + n = f->blocksize_1; + prev = get_bits(f,1); + next = get_bits(f,1); + } else { + prev = next = 0; + n = f->blocksize_0; + } + +// WINDOWING + + window_center = n >> 1; + if (m->blockflag && !prev) { + *p_left_start = (n - f->blocksize_0) >> 2; + *p_left_end = (n + f->blocksize_0) >> 2; + } else { + *p_left_start = 0; + *p_left_end = window_center; + } + if (m->blockflag && !next) { + *p_right_start = (n*3 - f->blocksize_0) >> 2; + *p_right_end = (n*3 + f->blocksize_0) >> 2; + } else { + *p_right_start = window_center; + *p_right_end = n; + } + + return TRUE; +} + +static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start, int left_end, int right_start, int right_end, int *p_left) +{ + Mapping *map; + int i,j,k,n,n2; + int zero_channel[256]; + int really_zero_channel[256]; + +// WINDOWING + + STBV_NOTUSED(left_end); + n = f->blocksize[m->blockflag]; + map = &f->mapping[m->mapping]; + +// FLOORS + n2 = n >> 1; + + CHECK(f); + + for (i=0; i < f->channels; ++i) { + int s = map->chan[i].mux, floor; + zero_channel[i] = FALSE; + floor = map->submap_floor[s]; + if (f->floor_types[floor] == 0) { + return error(f, VORBIS_invalid_stream); + } else { + Floor1 *g = &f->floor_config[floor].floor1; + if (get_bits(f, 1)) { + short *finalY; + uint8 step2_flag[256]; + static int range_list[4] = { 256, 128, 86, 64 }; + int range = range_list[g->floor1_multiplier-1]; + int offset = 2; + finalY = f->finalY[i]; + finalY[0] = get_bits(f, ilog(range)-1); + finalY[1] = get_bits(f, ilog(range)-1); + for (j=0; j < g->partitions; ++j) { + int pclass = g->partition_class_list[j]; + int cdim = g->class_dimensions[pclass]; + int cbits = g->class_subclasses[pclass]; + int csub = (1 << cbits)-1; + int cval = 0; + if (cbits) { + Codebook *c = f->codebooks + g->class_masterbooks[pclass]; + DECODE(cval,f,c); + } + for (k=0; k < cdim; ++k) { + int book = g->subclass_books[pclass][cval & csub]; + cval = cval >> cbits; + if (book >= 0) { + int temp; + Codebook *c = f->codebooks + book; + DECODE(temp,f,c); + finalY[offset++] = temp; + } else + finalY[offset++] = 0; + } + } + if (f->valid_bits == INVALID_BITS) goto error; // behavior according to spec + step2_flag[0] = step2_flag[1] = 1; + for (j=2; j < g->values; ++j) { + int low, high, pred, highroom, lowroom, room, val; + low = g->neighbors[j][0]; + high = g->neighbors[j][1]; + //neighbors(g->Xlist, j, &low, &high); + pred = predict_point(g->Xlist[j], g->Xlist[low], g->Xlist[high], finalY[low], finalY[high]); + val = finalY[j]; + highroom = range - pred; + lowroom = pred; + if (highroom < lowroom) + room = highroom * 2; + else + room = lowroom * 2; + if (val) { + step2_flag[low] = step2_flag[high] = 1; + step2_flag[j] = 1; + if (val >= room) + if (highroom > lowroom) + finalY[j] = val - lowroom + pred; + else + finalY[j] = pred - val + highroom - 1; + else + if (val & 1) + finalY[j] = pred - ((val+1)>>1); + else + finalY[j] = pred + (val>>1); + } else { + step2_flag[j] = 0; + finalY[j] = pred; + } + } + +#ifdef STB_VORBIS_NO_DEFER_FLOOR + do_floor(f, map, i, n, f->floor_buffers[i], finalY, step2_flag); +#else + // defer final floor computation until _after_ residue + for (j=0; j < g->values; ++j) { + if (!step2_flag[j]) + finalY[j] = -1; + } +#endif + } else { + error: + zero_channel[i] = TRUE; + } + // So we just defer everything else to later + + // at this point we've decoded the floor into buffer + } + } + CHECK(f); + // at this point we've decoded all floors + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + + // re-enable coupled channels if necessary + memcpy(really_zero_channel, zero_channel, sizeof(really_zero_channel[0]) * f->channels); + for (i=0; i < map->coupling_steps; ++i) + if (!zero_channel[map->chan[i].magnitude] || !zero_channel[map->chan[i].angle]) { + zero_channel[map->chan[i].magnitude] = zero_channel[map->chan[i].angle] = FALSE; + } + + CHECK(f); +// RESIDUE DECODE + for (i=0; i < map->submaps; ++i) { + float *residue_buffers[STB_VORBIS_MAX_CHANNELS]; + int r; + uint8 do_not_decode[256]; + int ch = 0; + for (j=0; j < f->channels; ++j) { + if (map->chan[j].mux == i) { + if (zero_channel[j]) { + do_not_decode[ch] = TRUE; + residue_buffers[ch] = NULL; + } else { + do_not_decode[ch] = FALSE; + residue_buffers[ch] = f->channel_buffers[j]; + } + ++ch; + } + } + r = map->submap_residue[i]; + decode_residue(f, residue_buffers, ch, n2, r, do_not_decode); + } + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + CHECK(f); + +// INVERSE COUPLING + for (i = map->coupling_steps-1; i >= 0; --i) { + int n2 = n >> 1; + float *m = f->channel_buffers[map->chan[i].magnitude]; + float *a = f->channel_buffers[map->chan[i].angle ]; + for (j=0; j < n2; ++j) { + float a2,m2; + if (m[j] > 0) + if (a[j] > 0) + m2 = m[j], a2 = m[j] - a[j]; + else + a2 = m[j], m2 = m[j] + a[j]; + else + if (a[j] > 0) + m2 = m[j], a2 = m[j] + a[j]; + else + a2 = m[j], m2 = m[j] - a[j]; + m[j] = m2; + a[j] = a2; + } + } + CHECK(f); + + // finish decoding the floors +#ifndef STB_VORBIS_NO_DEFER_FLOOR + for (i=0; i < f->channels; ++i) { + if (really_zero_channel[i]) { + memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); + } else { + do_floor(f, map, i, n, f->channel_buffers[i], f->finalY[i], NULL); + } + } +#else + for (i=0; i < f->channels; ++i) { + if (really_zero_channel[i]) { + memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); + } else { + for (j=0; j < n2; ++j) + f->channel_buffers[i][j] *= f->floor_buffers[i][j]; + } + } +#endif + +// INVERSE MDCT + CHECK(f); + for (i=0; i < f->channels; ++i) + inverse_mdct(f->channel_buffers[i], n, f, m->blockflag); + CHECK(f); + + // this shouldn't be necessary, unless we exited on an error + // and want to flush to get to the next packet + flush_packet(f); + + if (f->first_decode) { + // assume we start so first non-discarded sample is sample 0 + // this isn't to spec, but spec would require us to read ahead + // and decode the size of all current frames--could be done, + // but presumably it's not a commonly used feature + f->current_loc = 0u - n2; // start of first frame is positioned for discard (NB this is an intentional unsigned overflow/wrap-around) + // we might have to discard samples "from" the next frame too, + // if we're lapping a large block then a small at the start? + f->discard_samples_deferred = n - right_end; + f->current_loc_valid = TRUE; + f->first_decode = FALSE; + } else if (f->discard_samples_deferred) { + if (f->discard_samples_deferred >= right_start - left_start) { + f->discard_samples_deferred -= (right_start - left_start); + left_start = right_start; + *p_left = left_start; + } else { + left_start += f->discard_samples_deferred; + *p_left = left_start; + f->discard_samples_deferred = 0; + } + } else if (f->previous_length == 0 && f->current_loc_valid) { + // we're recovering from a seek... that means we're going to discard + // the samples from this packet even though we know our position from + // the last page header, so we need to update the position based on + // the discarded samples here + // but wait, the code below is going to add this in itself even + // on a discard, so we don't need to do it here... + } + + // check if we have ogg information about the sample # for this packet + if (f->last_seg_which == f->end_seg_with_known_loc) { + // if we have a valid current loc, and this is final: + if (f->current_loc_valid && (f->page_flag & PAGEFLAG_last_page)) { + uint32 current_end = f->known_loc_for_packet; + // then let's infer the size of the (probably) short final frame + if (current_end < f->current_loc + (right_end-left_start)) { + if (current_end < f->current_loc) { + // negative truncation, that's impossible! + *len = 0; + } else { + *len = current_end - f->current_loc; + } + *len += left_start; // this doesn't seem right, but has no ill effect on my test files + if (*len > right_end) *len = right_end; // this should never happen + f->current_loc += *len; + return TRUE; + } + } + // otherwise, just set our sample loc + // guess that the ogg granule pos refers to the _middle_ of the + // last frame? + // set f->current_loc to the position of left_start + f->current_loc = f->known_loc_for_packet - (n2-left_start); + f->current_loc_valid = TRUE; + } + if (f->current_loc_valid) + f->current_loc += (right_start - left_start); + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + *len = right_end; // ignore samples after the window goes to 0 + CHECK(f); + + return TRUE; +} + +static int vorbis_decode_packet(vorb *f, int *len, int *p_left, int *p_right) +{ + int mode, left_end, right_end; + if (!vorbis_decode_initial(f, p_left, &left_end, p_right, &right_end, &mode)) return 0; + return vorbis_decode_packet_rest(f, len, f->mode_config + mode, *p_left, left_end, *p_right, right_end, p_left); +} + +static int vorbis_finish_frame(stb_vorbis *f, int len, int left, int right) +{ + int prev,i,j; + // we use right&left (the start of the right- and left-window sin()-regions) + // to determine how much to return, rather than inferring from the rules + // (same result, clearer code); 'left' indicates where our sin() window + // starts, therefore where the previous window's right edge starts, and + // therefore where to start mixing from the previous buffer. 'right' + // indicates where our sin() ending-window starts, therefore that's where + // we start saving, and where our returned-data ends. + + // mixin from previous window + if (f->previous_length) { + int i,j, n = f->previous_length; + float *w = get_window(f, n); + if (w == NULL) return 0; + for (i=0; i < f->channels; ++i) { + for (j=0; j < n; ++j) + f->channel_buffers[i][left+j] = + f->channel_buffers[i][left+j]*w[ j] + + f->previous_window[i][ j]*w[n-1-j]; + } + } + + prev = f->previous_length; + + // last half of this data becomes previous window + f->previous_length = len - right; + + // @OPTIMIZE: could avoid this copy by double-buffering the + // output (flipping previous_window with channel_buffers), but + // then previous_window would have to be 2x as large, and + // channel_buffers couldn't be temp mem (although they're NOT + // currently temp mem, they could be (unless we want to level + // performance by spreading out the computation)) + for (i=0; i < f->channels; ++i) + for (j=0; right+j < len; ++j) + f->previous_window[i][j] = f->channel_buffers[i][right+j]; + + if (!prev) + // there was no previous packet, so this data isn't valid... + // this isn't entirely true, only the would-have-overlapped data + // isn't valid, but this seems to be what the spec requires + return 0; + + // truncate a short frame + if (len < right) right = len; + + f->samples_output += right-left; + + return right - left; +} + +static int vorbis_pump_first_frame(stb_vorbis *f) +{ + int len, right, left, res; + res = vorbis_decode_packet(f, &len, &left, &right); + if (res) + vorbis_finish_frame(f, len, left, right); + return res; +} + +#ifndef STB_VORBIS_NO_PUSHDATA_API +static int is_whole_packet_present(stb_vorbis *f) +{ + // make sure that we have the packet available before continuing... + // this requires a full ogg parse, but we know we can fetch from f->stream + + // instead of coding this out explicitly, we could save the current read state, + // read the next packet with get8() until end-of-packet, check f->eof, then + // reset the state? but that would be slower, esp. since we'd have over 256 bytes + // of state to restore (primarily the page segment table) + + int s = f->next_seg, first = TRUE; + uint8 *p = f->stream; + + if (s != -1) { // if we're not starting the packet with a 'continue on next page' flag + for (; s < f->segment_count; ++s) { + p += f->segments[s]; + if (f->segments[s] < 255) // stop at first short segment + break; + } + // either this continues, or it ends it... + if (s == f->segment_count) + s = -1; // set 'crosses page' flag + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + first = FALSE; + } + for (; s == -1;) { + uint8 *q; + int n; + + // check that we have the page header ready + if (p + 26 >= f->stream_end) return error(f, VORBIS_need_more_data); + // validate the page + if (memcmp(p, ogg_page_header, 4)) return error(f, VORBIS_invalid_stream); + if (p[4] != 0) return error(f, VORBIS_invalid_stream); + if (first) { // the first segment must NOT have 'continued_packet', later ones MUST + if (f->previous_length) + if ((p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); + // if no previous length, we're resynching, so we can come in on a continued-packet, + // which we'll just drop + } else { + if (!(p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); + } + n = p[26]; // segment counts + q = p+27; // q points to segment table + p = q + n; // advance past header + // make sure we've read the segment table + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + for (s=0; s < n; ++s) { + p += q[s]; + if (q[s] < 255) + break; + } + if (s == n) + s = -1; // set 'crosses page' flag + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + first = FALSE; + } + return TRUE; +} +#endif // !STB_VORBIS_NO_PUSHDATA_API + +static int start_decoder(vorb *f) +{ + uint8 header[6], x,y; + int len,i,j,k, max_submaps = 0; + int longest_floorlist=0; + + // first page, first packet + f->first_decode = TRUE; + + if (!start_page(f)) return FALSE; + // validate page flag + if (!(f->page_flag & PAGEFLAG_first_page)) return error(f, VORBIS_invalid_first_page); + if (f->page_flag & PAGEFLAG_last_page) return error(f, VORBIS_invalid_first_page); + if (f->page_flag & PAGEFLAG_continued_packet) return error(f, VORBIS_invalid_first_page); + // check for expected packet length + if (f->segment_count != 1) return error(f, VORBIS_invalid_first_page); + if (f->segments[0] != 30) { + // check for the Ogg skeleton fishead identifying header to refine our error + if (f->segments[0] == 64 && + getn(f, header, 6) && + header[0] == 'f' && + header[1] == 'i' && + header[2] == 's' && + header[3] == 'h' && + header[4] == 'e' && + header[5] == 'a' && + get8(f) == 'd' && + get8(f) == '\0') return error(f, VORBIS_ogg_skeleton_not_supported); + else + return error(f, VORBIS_invalid_first_page); + } + + // read packet + // check packet header + if (get8(f) != VORBIS_packet_id) return error(f, VORBIS_invalid_first_page); + if (!getn(f, header, 6)) return error(f, VORBIS_unexpected_eof); + if (!vorbis_validate(header)) return error(f, VORBIS_invalid_first_page); + // vorbis_version + if (get32(f) != 0) return error(f, VORBIS_invalid_first_page); + f->channels = get8(f); if (!f->channels) return error(f, VORBIS_invalid_first_page); + if (f->channels > STB_VORBIS_MAX_CHANNELS) return error(f, VORBIS_too_many_channels); + f->sample_rate = get32(f); if (!f->sample_rate) return error(f, VORBIS_invalid_first_page); + get32(f); // bitrate_maximum + get32(f); // bitrate_nominal + get32(f); // bitrate_minimum + x = get8(f); + { + int log0,log1; + log0 = x & 15; + log1 = x >> 4; + f->blocksize_0 = 1 << log0; + f->blocksize_1 = 1 << log1; + if (log0 < 6 || log0 > 13) return error(f, VORBIS_invalid_setup); + if (log1 < 6 || log1 > 13) return error(f, VORBIS_invalid_setup); + if (log0 > log1) return error(f, VORBIS_invalid_setup); + } + + // framing_flag + x = get8(f); + if (!(x & 1)) return error(f, VORBIS_invalid_first_page); + + // second packet! + if (!start_page(f)) return FALSE; + + if (!start_packet(f)) return FALSE; + + if (!next_segment(f)) return FALSE; + + if (get8_packet(f) != VORBIS_packet_comment) return error(f, VORBIS_invalid_setup); + for (i=0; i < 6; ++i) header[i] = get8_packet(f); + if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); + //file vendor + len = get32_packet(f); + f->vendor = (char*)setup_malloc(f, sizeof(char) * (len+1)); + if (f->vendor == NULL) return error(f, VORBIS_outofmem); + for(i=0; i < len; ++i) { + f->vendor[i] = get8_packet(f); + } + f->vendor[len] = (char)'\0'; + //user comments + f->comment_list_length = get32_packet(f); + f->comment_list = NULL; + if (f->comment_list_length > 0) + { + f->comment_list = (char**) setup_malloc(f, sizeof(char*) * (f->comment_list_length)); + if (f->comment_list == NULL) return error(f, VORBIS_outofmem); + } + + for(i=0; i < f->comment_list_length; ++i) { + len = get32_packet(f); + f->comment_list[i] = (char*)setup_malloc(f, sizeof(char) * (len+1)); + if (f->comment_list[i] == NULL) return error(f, VORBIS_outofmem); + + for(j=0; j < len; ++j) { + f->comment_list[i][j] = get8_packet(f); + } + f->comment_list[i][len] = (char)'\0'; + } + + // framing_flag + x = get8_packet(f); + if (!(x & 1)) return error(f, VORBIS_invalid_setup); + + + skip(f, f->bytes_in_seg); + f->bytes_in_seg = 0; + + do { + len = next_segment(f); + skip(f, len); + f->bytes_in_seg = 0; + } while (len); + + // third packet! + if (!start_packet(f)) return FALSE; + + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (IS_PUSH_MODE(f)) { + if (!is_whole_packet_present(f)) { + // convert error in ogg header to write type + if (f->error == VORBIS_invalid_stream) + f->error = VORBIS_invalid_setup; + return FALSE; + } + } + #endif + + crc32_init(); // always init it, to avoid multithread race conditions + + if (get8_packet(f) != VORBIS_packet_setup) return error(f, VORBIS_invalid_setup); + for (i=0; i < 6; ++i) header[i] = get8_packet(f); + if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); + + // codebooks + + f->codebook_count = get_bits(f,8) + 1; + f->codebooks = (Codebook *) setup_malloc(f, sizeof(*f->codebooks) * f->codebook_count); + if (f->codebooks == NULL) return error(f, VORBIS_outofmem); + memset(f->codebooks, 0, sizeof(*f->codebooks) * f->codebook_count); + for (i=0; i < f->codebook_count; ++i) { + uint32 *values; + int ordered, sorted_count; + int total=0; + uint8 *lengths; + Codebook *c = f->codebooks+i; + CHECK(f); + x = get_bits(f, 8); if (x != 0x42) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); if (x != 0x43) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); if (x != 0x56) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); + c->dimensions = (get_bits(f, 8)<<8) + x; + x = get_bits(f, 8); + y = get_bits(f, 8); + c->entries = (get_bits(f, 8)<<16) + (y<<8) + x; + ordered = get_bits(f,1); + c->sparse = ordered ? 0 : get_bits(f,1); + + if (c->dimensions == 0 && c->entries != 0) return error(f, VORBIS_invalid_setup); + + if (c->sparse) + lengths = (uint8 *) setup_temp_malloc(f, c->entries); + else + lengths = c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); + + if (!lengths) return error(f, VORBIS_outofmem); + + if (ordered) { + int current_entry = 0; + int current_length = get_bits(f,5) + 1; + while (current_entry < c->entries) { + int limit = c->entries - current_entry; + int n = get_bits(f, ilog(limit)); + if (current_length >= 32) return error(f, VORBIS_invalid_setup); + if (current_entry + n > (int) c->entries) { return error(f, VORBIS_invalid_setup); } + memset(lengths + current_entry, current_length, n); + current_entry += n; + ++current_length; + } + } else { + for (j=0; j < c->entries; ++j) { + int present = c->sparse ? get_bits(f,1) : 1; + if (present) { + lengths[j] = get_bits(f, 5) + 1; + ++total; + if (lengths[j] == 32) + return error(f, VORBIS_invalid_setup); + } else { + lengths[j] = NO_CODE; + } + } + } + + if (c->sparse && total >= c->entries >> 2) { + // convert sparse items to non-sparse! + if (c->entries > (int) f->setup_temp_memory_required) + f->setup_temp_memory_required = c->entries; + + c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); + if (c->codeword_lengths == NULL) return error(f, VORBIS_outofmem); + memcpy(c->codeword_lengths, lengths, c->entries); + setup_temp_free(f, lengths, c->entries); // note this is only safe if there have been no intervening temp mallocs! + lengths = c->codeword_lengths; + c->sparse = 0; + } + + // compute the size of the sorted tables + if (c->sparse) { + sorted_count = total; + } else { + sorted_count = 0; + #ifndef STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH + for (j=0; j < c->entries; ++j) + if (lengths[j] > STB_VORBIS_FAST_HUFFMAN_LENGTH && lengths[j] != NO_CODE) + ++sorted_count; + #endif + } + + c->sorted_entries = sorted_count; + values = NULL; + + CHECK(f); + if (!c->sparse) { + c->codewords = (uint32 *) setup_malloc(f, sizeof(c->codewords[0]) * c->entries); + if (!c->codewords) return error(f, VORBIS_outofmem); + } else { + unsigned int size; + if (c->sorted_entries) { + c->codeword_lengths = (uint8 *) setup_malloc(f, c->sorted_entries); + if (!c->codeword_lengths) return error(f, VORBIS_outofmem); + c->codewords = (uint32 *) setup_temp_malloc(f, sizeof(*c->codewords) * c->sorted_entries); + if (!c->codewords) return error(f, VORBIS_outofmem); + values = (uint32 *) setup_temp_malloc(f, sizeof(*values) * c->sorted_entries); + if (!values) return error(f, VORBIS_outofmem); + } + size = c->entries + (sizeof(*c->codewords) + sizeof(*values)) * c->sorted_entries; + if (size > f->setup_temp_memory_required) + f->setup_temp_memory_required = size; + } + + if (!compute_codewords(c, lengths, c->entries, values)) { + if (c->sparse) setup_temp_free(f, values, 0); + return error(f, VORBIS_invalid_setup); + } + + if (c->sorted_entries) { + // allocate an extra slot for sentinels + c->sorted_codewords = (uint32 *) setup_malloc(f, sizeof(*c->sorted_codewords) * (c->sorted_entries+1)); + if (c->sorted_codewords == NULL) return error(f, VORBIS_outofmem); + // allocate an extra slot at the front so that c->sorted_values[-1] is defined + // so that we can catch that case without an extra if + c->sorted_values = ( int *) setup_malloc(f, sizeof(*c->sorted_values ) * (c->sorted_entries+1)); + if (c->sorted_values == NULL) return error(f, VORBIS_outofmem); + ++c->sorted_values; + c->sorted_values[-1] = -1; + compute_sorted_huffman(c, lengths, values); + } + + if (c->sparse) { + setup_temp_free(f, values, sizeof(*values)*c->sorted_entries); + setup_temp_free(f, c->codewords, sizeof(*c->codewords)*c->sorted_entries); + setup_temp_free(f, lengths, c->entries); + c->codewords = NULL; + } + + compute_accelerated_huffman(c); + + CHECK(f); + c->lookup_type = get_bits(f, 4); + if (c->lookup_type > 2) return error(f, VORBIS_invalid_setup); + if (c->lookup_type > 0) { + uint16 *mults; + c->minimum_value = float32_unpack(get_bits(f, 32)); + c->delta_value = float32_unpack(get_bits(f, 32)); + c->value_bits = get_bits(f, 4)+1; + c->sequence_p = get_bits(f,1); + if (c->lookup_type == 1) { + int values = lookup1_values(c->entries, c->dimensions); + if (values < 0) return error(f, VORBIS_invalid_setup); + c->lookup_values = (uint32) values; + } else { + c->lookup_values = c->entries * c->dimensions; + } + if (c->lookup_values == 0) return error(f, VORBIS_invalid_setup); + mults = (uint16 *) setup_temp_malloc(f, sizeof(mults[0]) * c->lookup_values); + if (mults == NULL) return error(f, VORBIS_outofmem); + for (j=0; j < (int) c->lookup_values; ++j) { + int q = get_bits(f, c->value_bits); + if (q == EOP) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_invalid_setup); } + mults[j] = q; + } + +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int len, sparse = c->sparse; + float last=0; + // pre-expand the lookup1-style multiplicands, to avoid a divide in the inner loop + if (sparse) { + if (c->sorted_entries == 0) goto skip; + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->sorted_entries * c->dimensions); + } else + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->entries * c->dimensions); + if (c->multiplicands == NULL) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } + len = sparse ? c->sorted_entries : c->entries; + for (j=0; j < len; ++j) { + unsigned int z = sparse ? c->sorted_values[j] : j; + unsigned int div=1; + for (k=0; k < c->dimensions; ++k) { + int off = (z / div) % c->lookup_values; + float val = mults[off]*c->delta_value + c->minimum_value + last; + c->multiplicands[j*c->dimensions + k] = val; + if (c->sequence_p) + last = val; + if (k+1 < c->dimensions) { + if (div > UINT_MAX / (unsigned int) c->lookup_values) { + setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); + return error(f, VORBIS_invalid_setup); + } + div *= c->lookup_values; + } + } + } + c->lookup_type = 2; + } + else +#endif + { + float last=0; + CHECK(f); + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->lookup_values); + if (c->multiplicands == NULL) { setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } + for (j=0; j < (int) c->lookup_values; ++j) { + float val = mults[j] * c->delta_value + c->minimum_value + last; + c->multiplicands[j] = val; + if (c->sequence_p) + last = val; + } + } +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + skip:; +#endif + setup_temp_free(f, mults, sizeof(mults[0])*c->lookup_values); + + CHECK(f); + } + CHECK(f); + } + + // time domain transfers (notused) + + x = get_bits(f, 6) + 1; + for (i=0; i < x; ++i) { + uint32 z = get_bits(f, 16); + if (z != 0) return error(f, VORBIS_invalid_setup); + } + + // Floors + f->floor_count = get_bits(f, 6)+1; + f->floor_config = (Floor *) setup_malloc(f, f->floor_count * sizeof(*f->floor_config)); + if (f->floor_config == NULL) return error(f, VORBIS_outofmem); + for (i=0; i < f->floor_count; ++i) { + f->floor_types[i] = get_bits(f, 16); + if (f->floor_types[i] > 1) return error(f, VORBIS_invalid_setup); + if (f->floor_types[i] == 0) { + Floor0 *g = &f->floor_config[i].floor0; + g->order = get_bits(f,8); + g->rate = get_bits(f,16); + g->bark_map_size = get_bits(f,16); + g->amplitude_bits = get_bits(f,6); + g->amplitude_offset = get_bits(f,8); + g->number_of_books = get_bits(f,4) + 1; + for (j=0; j < g->number_of_books; ++j) + g->book_list[j] = get_bits(f,8); + return error(f, VORBIS_feature_not_supported); + } else { + stbv__floor_ordering p[31*8+2]; + Floor1 *g = &f->floor_config[i].floor1; + int max_class = -1; + g->partitions = get_bits(f, 5); + for (j=0; j < g->partitions; ++j) { + g->partition_class_list[j] = get_bits(f, 4); + if (g->partition_class_list[j] > max_class) + max_class = g->partition_class_list[j]; + } + for (j=0; j <= max_class; ++j) { + g->class_dimensions[j] = get_bits(f, 3)+1; + g->class_subclasses[j] = get_bits(f, 2); + if (g->class_subclasses[j]) { + g->class_masterbooks[j] = get_bits(f, 8); + if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } + for (k=0; k < 1 << g->class_subclasses[j]; ++k) { + g->subclass_books[j][k] = (int16)get_bits(f,8)-1; + if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } + } + g->floor1_multiplier = get_bits(f,2)+1; + g->rangebits = get_bits(f,4); + g->Xlist[0] = 0; + g->Xlist[1] = 1 << g->rangebits; + g->values = 2; + for (j=0; j < g->partitions; ++j) { + int c = g->partition_class_list[j]; + for (k=0; k < g->class_dimensions[c]; ++k) { + g->Xlist[g->values] = get_bits(f, g->rangebits); + ++g->values; + } + } + // precompute the sorting + for (j=0; j < g->values; ++j) { + p[j].x = g->Xlist[j]; + p[j].id = j; + } + qsort(p, g->values, sizeof(p[0]), point_compare); + for (j=0; j < g->values-1; ++j) + if (p[j].x == p[j+1].x) + return error(f, VORBIS_invalid_setup); + for (j=0; j < g->values; ++j) + g->sorted_order[j] = (uint8) p[j].id; + // precompute the neighbors + for (j=2; j < g->values; ++j) { + int low = 0,hi = 0; + neighbors(g->Xlist, j, &low,&hi); + g->neighbors[j][0] = low; + g->neighbors[j][1] = hi; + } + + if (g->values > longest_floorlist) + longest_floorlist = g->values; + } + } + + // Residue + f->residue_count = get_bits(f, 6)+1; + f->residue_config = (Residue *) setup_malloc(f, f->residue_count * sizeof(f->residue_config[0])); + if (f->residue_config == NULL) return error(f, VORBIS_outofmem); + memset(f->residue_config, 0, f->residue_count * sizeof(f->residue_config[0])); + for (i=0; i < f->residue_count; ++i) { + uint8 residue_cascade[64]; + Residue *r = f->residue_config+i; + f->residue_types[i] = get_bits(f, 16); + if (f->residue_types[i] > 2) return error(f, VORBIS_invalid_setup); + r->begin = get_bits(f, 24); + r->end = get_bits(f, 24); + if (r->end < r->begin) return error(f, VORBIS_invalid_setup); + r->part_size = get_bits(f,24)+1; + r->classifications = get_bits(f,6)+1; + r->classbook = get_bits(f,8); + if (r->classbook >= f->codebook_count) return error(f, VORBIS_invalid_setup); + for (j=0; j < r->classifications; ++j) { + uint8 high_bits=0; + uint8 low_bits=get_bits(f,3); + if (get_bits(f,1)) + high_bits = get_bits(f,5); + residue_cascade[j] = high_bits*8 + low_bits; + } + r->residue_books = (short (*)[8]) setup_malloc(f, sizeof(r->residue_books[0]) * r->classifications); + if (r->residue_books == NULL) return error(f, VORBIS_outofmem); + for (j=0; j < r->classifications; ++j) { + for (k=0; k < 8; ++k) { + if (residue_cascade[j] & (1 << k)) { + r->residue_books[j][k] = get_bits(f, 8); + if (r->residue_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } else { + r->residue_books[j][k] = -1; + } + } + } + // precompute the classifications[] array to avoid inner-loop mod/divide + // call it 'classdata' since we already have r->classifications + r->classdata = (uint8 **) setup_malloc(f, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); + if (!r->classdata) return error(f, VORBIS_outofmem); + memset(r->classdata, 0, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); + for (j=0; j < f->codebooks[r->classbook].entries; ++j) { + int classwords = f->codebooks[r->classbook].dimensions; + int temp = j; + r->classdata[j] = (uint8 *) setup_malloc(f, sizeof(r->classdata[j][0]) * classwords); + if (r->classdata[j] == NULL) return error(f, VORBIS_outofmem); + for (k=classwords-1; k >= 0; --k) { + r->classdata[j][k] = temp % r->classifications; + temp /= r->classifications; + } + } + } + + f->mapping_count = get_bits(f,6)+1; + f->mapping = (Mapping *) setup_malloc(f, f->mapping_count * sizeof(*f->mapping)); + if (f->mapping == NULL) return error(f, VORBIS_outofmem); + memset(f->mapping, 0, f->mapping_count * sizeof(*f->mapping)); + for (i=0; i < f->mapping_count; ++i) { + Mapping *m = f->mapping + i; + int mapping_type = get_bits(f,16); + if (mapping_type != 0) return error(f, VORBIS_invalid_setup); + m->chan = (MappingChannel *) setup_malloc(f, f->channels * sizeof(*m->chan)); + if (m->chan == NULL) return error(f, VORBIS_outofmem); + if (get_bits(f,1)) + m->submaps = get_bits(f,4)+1; + else + m->submaps = 1; + if (m->submaps > max_submaps) + max_submaps = m->submaps; + if (get_bits(f,1)) { + m->coupling_steps = get_bits(f,8)+1; + if (m->coupling_steps > f->channels) return error(f, VORBIS_invalid_setup); + for (k=0; k < m->coupling_steps; ++k) { + m->chan[k].magnitude = get_bits(f, ilog(f->channels-1)); + m->chan[k].angle = get_bits(f, ilog(f->channels-1)); + if (m->chan[k].magnitude >= f->channels) return error(f, VORBIS_invalid_setup); + if (m->chan[k].angle >= f->channels) return error(f, VORBIS_invalid_setup); + if (m->chan[k].magnitude == m->chan[k].angle) return error(f, VORBIS_invalid_setup); + } + } else + m->coupling_steps = 0; + + // reserved field + if (get_bits(f,2)) return error(f, VORBIS_invalid_setup); + if (m->submaps > 1) { + for (j=0; j < f->channels; ++j) { + m->chan[j].mux = get_bits(f, 4); + if (m->chan[j].mux >= m->submaps) return error(f, VORBIS_invalid_setup); + } + } else + // @SPECIFICATION: this case is missing from the spec + for (j=0; j < f->channels; ++j) + m->chan[j].mux = 0; + + for (j=0; j < m->submaps; ++j) { + get_bits(f,8); // discard + m->submap_floor[j] = get_bits(f,8); + m->submap_residue[j] = get_bits(f,8); + if (m->submap_floor[j] >= f->floor_count) return error(f, VORBIS_invalid_setup); + if (m->submap_residue[j] >= f->residue_count) return error(f, VORBIS_invalid_setup); + } + } + + // Modes + f->mode_count = get_bits(f, 6)+1; + for (i=0; i < f->mode_count; ++i) { + Mode *m = f->mode_config+i; + m->blockflag = get_bits(f,1); + m->windowtype = get_bits(f,16); + m->transformtype = get_bits(f,16); + m->mapping = get_bits(f,8); + if (m->windowtype != 0) return error(f, VORBIS_invalid_setup); + if (m->transformtype != 0) return error(f, VORBIS_invalid_setup); + if (m->mapping >= f->mapping_count) return error(f, VORBIS_invalid_setup); + } + + flush_packet(f); + + f->previous_length = 0; + + for (i=0; i < f->channels; ++i) { + f->channel_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1); + f->previous_window[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); + f->finalY[i] = (int16 *) setup_malloc(f, sizeof(int16) * longest_floorlist); + if (f->channel_buffers[i] == NULL || f->previous_window[i] == NULL || f->finalY[i] == NULL) return error(f, VORBIS_outofmem); + memset(f->channel_buffers[i], 0, sizeof(float) * f->blocksize_1); + #ifdef STB_VORBIS_NO_DEFER_FLOOR + f->floor_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); + if (f->floor_buffers[i] == NULL) return error(f, VORBIS_outofmem); + #endif + } + + if (!init_blocksize(f, 0, f->blocksize_0)) return FALSE; + if (!init_blocksize(f, 1, f->blocksize_1)) return FALSE; + f->blocksize[0] = f->blocksize_0; + f->blocksize[1] = f->blocksize_1; + +#ifdef STB_VORBIS_DIVIDE_TABLE + if (integer_divide_table[1][1]==0) + for (i=0; i < DIVTAB_NUMER; ++i) + for (j=1; j < DIVTAB_DENOM; ++j) + integer_divide_table[i][j] = i / j; +#endif + + // compute how much temporary memory is needed + + // 1. + { + uint32 imdct_mem = (f->blocksize_1 * sizeof(float) >> 1); + uint32 classify_mem; + int i,max_part_read=0; + for (i=0; i < f->residue_count; ++i) { + Residue *r = f->residue_config + i; + unsigned int actual_size = f->blocksize_1 / 2; + unsigned int limit_r_begin = r->begin < actual_size ? r->begin : actual_size; + unsigned int limit_r_end = r->end < actual_size ? r->end : actual_size; + int n_read = limit_r_end - limit_r_begin; + int part_read = n_read / r->part_size; + if (part_read > max_part_read) + max_part_read = part_read; + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(uint8 *)); + #else + classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(int *)); + #endif + + // maximum reasonable partition size is f->blocksize_1 + + f->temp_memory_required = classify_mem; + if (imdct_mem > f->temp_memory_required) + f->temp_memory_required = imdct_mem; + } + + + if (f->alloc.alloc_buffer) { + assert(f->temp_offset == f->alloc.alloc_buffer_length_in_bytes); + // check if there's enough temp memory so we don't error later + if (f->setup_offset + sizeof(*f) + f->temp_memory_required > (unsigned) f->temp_offset) + return error(f, VORBIS_outofmem); + } + + // @TODO: stb_vorbis_seek_start expects first_audio_page_offset to point to a page + // without PAGEFLAG_continued_packet, so this either points to the first page, or + // the page after the end of the headers. It might be cleaner to point to a page + // in the middle of the headers, when that's the page where the first audio packet + // starts, but we'd have to also correctly skip the end of any continued packet in + // stb_vorbis_seek_start. + if (f->next_seg == -1) { + f->first_audio_page_offset = stb_vorbis_get_file_offset(f); + } else { + f->first_audio_page_offset = 0; + } + + return TRUE; +} + +static void vorbis_deinit(stb_vorbis *p) +{ + int i,j; + + setup_free(p, p->vendor); + for (i=0; i < p->comment_list_length; ++i) { + setup_free(p, p->comment_list[i]); + } + setup_free(p, p->comment_list); + + if (p->residue_config) { + for (i=0; i < p->residue_count; ++i) { + Residue *r = p->residue_config+i; + if (r->classdata) { + for (j=0; j < p->codebooks[r->classbook].entries; ++j) + setup_free(p, r->classdata[j]); + setup_free(p, r->classdata); + } + setup_free(p, r->residue_books); + } + } + + if (p->codebooks) { + CHECK(p); + for (i=0; i < p->codebook_count; ++i) { + Codebook *c = p->codebooks + i; + setup_free(p, c->codeword_lengths); + setup_free(p, c->multiplicands); + setup_free(p, c->codewords); + setup_free(p, c->sorted_codewords); + // c->sorted_values[-1] is the first entry in the array + setup_free(p, c->sorted_values ? c->sorted_values-1 : NULL); + } + setup_free(p, p->codebooks); + } + setup_free(p, p->floor_config); + setup_free(p, p->residue_config); + if (p->mapping) { + for (i=0; i < p->mapping_count; ++i) + setup_free(p, p->mapping[i].chan); + setup_free(p, p->mapping); + } + CHECK(p); + for (i=0; i < p->channels && i < STB_VORBIS_MAX_CHANNELS; ++i) { + setup_free(p, p->channel_buffers[i]); + setup_free(p, p->previous_window[i]); + #ifdef STB_VORBIS_NO_DEFER_FLOOR + setup_free(p, p->floor_buffers[i]); + #endif + setup_free(p, p->finalY[i]); + } + for (i=0; i < 2; ++i) { + setup_free(p, p->A[i]); + setup_free(p, p->B[i]); + setup_free(p, p->C[i]); + setup_free(p, p->window[i]); + setup_free(p, p->bit_reverse[i]); + } + #ifndef STB_VORBIS_NO_STDIO + if (p->close_on_free) fclose(p->f); + #endif +} + +void stb_vorbis_close(stb_vorbis *p) +{ + if (p == NULL) return; + vorbis_deinit(p); + setup_free(p,p); +} + +static void vorbis_init(stb_vorbis *p, const stb_vorbis_alloc *z) +{ + memset(p, 0, sizeof(*p)); // NULL out all malloc'd pointers to start + if (z) { + p->alloc = *z; + p->alloc.alloc_buffer_length_in_bytes &= ~7; + p->temp_offset = p->alloc.alloc_buffer_length_in_bytes; + } + p->eof = 0; + p->error = VORBIS__no_error; + p->stream = NULL; + p->codebooks = NULL; + p->page_crc_tests = -1; + #ifndef STB_VORBIS_NO_STDIO + p->close_on_free = FALSE; + p->f = NULL; + #endif +} + +int stb_vorbis_get_sample_offset(stb_vorbis *f) +{ + if (f->current_loc_valid) + return f->current_loc; + else + return -1; +} + +stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f) +{ + stb_vorbis_info d; + d.channels = f->channels; + d.sample_rate = f->sample_rate; + d.setup_memory_required = f->setup_memory_required; + d.setup_temp_memory_required = f->setup_temp_memory_required; + d.temp_memory_required = f->temp_memory_required; + d.max_frame_size = f->blocksize_1 >> 1; + return d; +} + +stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f) +{ + stb_vorbis_comment d; + d.vendor = f->vendor; + d.comment_list_length = f->comment_list_length; + d.comment_list = f->comment_list; + return d; +} + +int stb_vorbis_get_error(stb_vorbis *f) +{ + int e = f->error; + f->error = VORBIS__no_error; + return e; +} + +static stb_vorbis * vorbis_alloc(stb_vorbis *f) +{ + stb_vorbis *p = (stb_vorbis *) setup_malloc(f, sizeof(*p)); + return p; +} + +#ifndef STB_VORBIS_NO_PUSHDATA_API + +void stb_vorbis_flush_pushdata(stb_vorbis *f) +{ + f->previous_length = 0; + f->page_crc_tests = 0; + f->discard_samples_deferred = 0; + f->current_loc_valid = FALSE; + f->first_decode = FALSE; + f->samples_output = 0; + f->channel_buffer_start = 0; + f->channel_buffer_end = 0; +} + +static int vorbis_search_for_page_pushdata(vorb *f, uint8 *data, int data_len) +{ + int i,n; + for (i=0; i < f->page_crc_tests; ++i) + f->scan[i].bytes_done = 0; + + // if we have room for more scans, search for them first, because + // they may cause us to stop early if their header is incomplete + if (f->page_crc_tests < STB_VORBIS_PUSHDATA_CRC_COUNT) { + if (data_len < 4) return 0; + data_len -= 3; // need to look for 4-byte sequence, so don't miss + // one that straddles a boundary + for (i=0; i < data_len; ++i) { + if (data[i] == 0x4f) { + if (0==memcmp(data+i, ogg_page_header, 4)) { + int j,len; + uint32 crc; + // make sure we have the whole page header + if (i+26 >= data_len || i+27+data[i+26] >= data_len) { + // only read up to this page start, so hopefully we'll + // have the whole page header start next time + data_len = i; + break; + } + // ok, we have it all; compute the length of the page + len = 27 + data[i+26]; + for (j=0; j < data[i+26]; ++j) + len += data[i+27+j]; + // scan everything up to the embedded crc (which we must 0) + crc = 0; + for (j=0; j < 22; ++j) + crc = crc32_update(crc, data[i+j]); + // now process 4 0-bytes + for ( ; j < 26; ++j) + crc = crc32_update(crc, 0); + // len is the total number of bytes we need to scan + n = f->page_crc_tests++; + f->scan[n].bytes_left = len-j; + f->scan[n].crc_so_far = crc; + f->scan[n].goal_crc = data[i+22] + (data[i+23] << 8) + (data[i+24]<<16) + (data[i+25]<<24); + // if the last frame on a page is continued to the next, then + // we can't recover the sample_loc immediately + if (data[i+27+data[i+26]-1] == 255) + f->scan[n].sample_loc = ~0; + else + f->scan[n].sample_loc = data[i+6] + (data[i+7] << 8) + (data[i+ 8]<<16) + (data[i+ 9]<<24); + f->scan[n].bytes_done = i+j; + if (f->page_crc_tests == STB_VORBIS_PUSHDATA_CRC_COUNT) + break; + // keep going if we still have room for more + } + } + } + } + + for (i=0; i < f->page_crc_tests;) { + uint32 crc; + int j; + int n = f->scan[i].bytes_done; + int m = f->scan[i].bytes_left; + if (m > data_len - n) m = data_len - n; + // m is the bytes to scan in the current chunk + crc = f->scan[i].crc_so_far; + for (j=0; j < m; ++j) + crc = crc32_update(crc, data[n+j]); + f->scan[i].bytes_left -= m; + f->scan[i].crc_so_far = crc; + if (f->scan[i].bytes_left == 0) { + // does it match? + if (f->scan[i].crc_so_far == f->scan[i].goal_crc) { + // Houston, we have page + data_len = n+m; // consumption amount is wherever that scan ended + f->page_crc_tests = -1; // drop out of page scan mode + f->previous_length = 0; // decode-but-don't-output one frame + f->next_seg = -1; // start a new page + f->current_loc = f->scan[i].sample_loc; // set the current sample location + // to the amount we'd have decoded had we decoded this page + f->current_loc_valid = f->current_loc != ~0U; + return data_len; + } + // delete entry + f->scan[i] = f->scan[--f->page_crc_tests]; + } else { + ++i; + } + } + + return data_len; +} + +// return value: number of bytes we used +int stb_vorbis_decode_frame_pushdata( + stb_vorbis *f, // the file we're decoding + const uint8 *data, int data_len, // the memory available for decoding + int *channels, // place to write number of float * buffers + float ***output, // place to write float ** array of float * buffers + int *samples // place to write number of output samples + ) +{ + int i; + int len,right,left; + + if (!IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + if (f->page_crc_tests >= 0) { + *samples = 0; + return vorbis_search_for_page_pushdata(f, (uint8 *) data, data_len); + } + + f->stream = (uint8 *) data; + f->stream_end = (uint8 *) data + data_len; + f->error = VORBIS__no_error; + + // check that we have the entire packet in memory + if (!is_whole_packet_present(f)) { + *samples = 0; + return 0; + } + + if (!vorbis_decode_packet(f, &len, &left, &right)) { + // save the actual error we encountered + enum STBVorbisError error = f->error; + if (error == VORBIS_bad_packet_type) { + // flush and resynch + f->error = VORBIS__no_error; + while (get8_packet(f) != EOP) + if (f->eof) break; + *samples = 0; + return (int) (f->stream - data); + } + if (error == VORBIS_continued_packet_flag_invalid) { + if (f->previous_length == 0) { + // we may be resynching, in which case it's ok to hit one + // of these; just discard the packet + f->error = VORBIS__no_error; + while (get8_packet(f) != EOP) + if (f->eof) break; + *samples = 0; + return (int) (f->stream - data); + } + } + // if we get an error while parsing, what to do? + // well, it DEFINITELY won't work to continue from where we are! + stb_vorbis_flush_pushdata(f); + // restore the error that actually made us bail + f->error = error; + *samples = 0; + return 1; + } + + // success! + len = vorbis_finish_frame(f, len, left, right); + for (i=0; i < f->channels; ++i) + f->outputs[i] = f->channel_buffers[i] + left; + + if (channels) *channels = f->channels; + *samples = len; + *output = f->outputs; + return (int) (f->stream - data); +} + +stb_vorbis *stb_vorbis_open_pushdata( + const unsigned char *data, int data_len, // the memory available for decoding + int *data_used, // only defined if result is not NULL + int *error, const stb_vorbis_alloc *alloc) +{ + stb_vorbis *f, p; + vorbis_init(&p, alloc); + p.stream = (uint8 *) data; + p.stream_end = (uint8 *) data + data_len; + p.push_mode = TRUE; + if (!start_decoder(&p)) { + if (p.eof) + *error = VORBIS_need_more_data; + else + *error = p.error; + vorbis_deinit(&p); + return NULL; + } + f = vorbis_alloc(&p); + if (f) { + *f = p; + *data_used = (int) (f->stream - data); + *error = 0; + return f; + } else { + vorbis_deinit(&p); + return NULL; + } +} +#endif // STB_VORBIS_NO_PUSHDATA_API + +unsigned int stb_vorbis_get_file_offset(stb_vorbis *f) +{ + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (f->push_mode) return 0; + #endif + if (USE_MEMORY(f)) return (unsigned int) (f->stream - f->stream_start); + #ifndef STB_VORBIS_NO_STDIO + return (unsigned int) (ftell(f->f) - f->f_start); + #endif +} + +#ifndef STB_VORBIS_NO_PULLDATA_API +// +// DATA-PULLING API +// + +static uint32 vorbis_find_page(stb_vorbis *f, uint32 *end, uint32 *last) +{ + for(;;) { + int n; + if (f->eof) return 0; + n = get8(f); + if (n == 0x4f) { // page header candidate + unsigned int retry_loc = stb_vorbis_get_file_offset(f); + int i; + // check if we're off the end of a file_section stream + if (retry_loc - 25 > f->stream_len) + return 0; + // check the rest of the header + for (i=1; i < 4; ++i) + if (get8(f) != ogg_page_header[i]) + break; + if (f->eof) return 0; + if (i == 4) { + uint8 header[27]; + uint32 i, crc, goal, len; + for (i=0; i < 4; ++i) + header[i] = ogg_page_header[i]; + for (; i < 27; ++i) + header[i] = get8(f); + if (f->eof) return 0; + if (header[4] != 0) goto invalid; + goal = header[22] + (header[23] << 8) + (header[24]<<16) + ((uint32)header[25]<<24); + for (i=22; i < 26; ++i) + header[i] = 0; + crc = 0; + for (i=0; i < 27; ++i) + crc = crc32_update(crc, header[i]); + len = 0; + for (i=0; i < header[26]; ++i) { + int s = get8(f); + crc = crc32_update(crc, s); + len += s; + } + if (len && f->eof) return 0; + for (i=0; i < len; ++i) + crc = crc32_update(crc, get8(f)); + // finished parsing probable page + if (crc == goal) { + // we could now check that it's either got the last + // page flag set, OR it's followed by the capture + // pattern, but I guess TECHNICALLY you could have + // a file with garbage between each ogg page and recover + // from it automatically? So even though that paranoia + // might decrease the chance of an invalid decode by + // another 2^32, not worth it since it would hose those + // invalid-but-useful files? + if (end) + *end = stb_vorbis_get_file_offset(f); + if (last) { + if (header[5] & 0x04) + *last = 1; + else + *last = 0; + } + set_file_offset(f, retry_loc-1); + return 1; + } + } + invalid: + // not a valid page, so rewind and look for next one + set_file_offset(f, retry_loc); + } + } +} + + +#define SAMPLE_unknown 0xffffffff + +// seeking is implemented with a binary search, which narrows down the range to +// 64K, before using a linear search (because finding the synchronization +// pattern can be expensive, and the chance we'd find the end page again is +// relatively high for small ranges) +// +// two initial interpolation-style probes are used at the start of the search +// to try to bound either side of the binary search sensibly, while still +// working in O(log n) time if they fail. + +static int get_seek_page_info(stb_vorbis *f, ProbedPage *z) +{ + uint8 header[27], lacing[255]; + int i,len; + + // record where the page starts + z->page_start = stb_vorbis_get_file_offset(f); + + // parse the header + getn(f, header, 27); + if (header[0] != 'O' || header[1] != 'g' || header[2] != 'g' || header[3] != 'S') + return 0; + getn(f, lacing, header[26]); + + // determine the length of the payload + len = 0; + for (i=0; i < header[26]; ++i) + len += lacing[i]; + + // this implies where the page ends + z->page_end = z->page_start + 27 + header[26] + len; + + // read the last-decoded sample out of the data + z->last_decoded_sample = header[6] + (header[7] << 8) + (header[8] << 16) + (header[9] << 24); + + // restore file state to where we were + set_file_offset(f, z->page_start); + return 1; +} + +// rarely used function to seek back to the preceding page while finding the +// start of a packet +static int go_to_page_before(stb_vorbis *f, unsigned int limit_offset) +{ + unsigned int previous_safe, end; + + // now we want to seek back 64K from the limit + if (limit_offset >= 65536 && limit_offset-65536 >= f->first_audio_page_offset) + previous_safe = limit_offset - 65536; + else + previous_safe = f->first_audio_page_offset; + + set_file_offset(f, previous_safe); + + while (vorbis_find_page(f, &end, NULL)) { + if (end >= limit_offset && stb_vorbis_get_file_offset(f) < limit_offset) + return 1; + set_file_offset(f, end); + } + + return 0; +} + +// implements the search logic for finding a page and starting decoding. if +// the function succeeds, current_loc_valid will be true and current_loc will +// be less than or equal to the provided sample number (the closer the +// better). +static int seek_to_sample_coarse(stb_vorbis *f, uint32 sample_number) +{ + ProbedPage left, right, mid; + int i, start_seg_with_known_loc, end_pos, page_start; + uint32 delta, stream_length, padding, last_sample_limit; + double offset = 0.0, bytes_per_sample = 0.0; + int probe = 0; + + // find the last page and validate the target sample + stream_length = stb_vorbis_stream_length_in_samples(f); + if (stream_length == 0) return error(f, VORBIS_seek_without_length); + if (sample_number > stream_length) return error(f, VORBIS_seek_invalid); + + // this is the maximum difference between the window-center (which is the + // actual granule position value), and the right-start (which the spec + // indicates should be the granule position (give or take one)). + padding = ((f->blocksize_1 - f->blocksize_0) >> 2); + if (sample_number < padding) + last_sample_limit = 0; + else + last_sample_limit = sample_number - padding; + + left = f->p_first; + while (left.last_decoded_sample == ~0U) { + // (untested) the first page does not have a 'last_decoded_sample' + set_file_offset(f, left.page_end); + if (!get_seek_page_info(f, &left)) goto error; + } + + right = f->p_last; + assert(right.last_decoded_sample != ~0U); + + // starting from the start is handled differently + if (last_sample_limit <= left.last_decoded_sample) { + if (stb_vorbis_seek_start(f)) { + if (f->current_loc > sample_number) + return error(f, VORBIS_seek_failed); + return 1; + } + return 0; + } + + while (left.page_end != right.page_start) { + assert(left.page_end < right.page_start); + // search range in bytes + delta = right.page_start - left.page_end; + if (delta <= 65536) { + // there's only 64K left to search - handle it linearly + set_file_offset(f, left.page_end); + } else { + if (probe < 2) { + if (probe == 0) { + // first probe (interpolate) + double data_bytes = right.page_end - left.page_start; + bytes_per_sample = data_bytes / right.last_decoded_sample; + offset = left.page_start + bytes_per_sample * (last_sample_limit - left.last_decoded_sample); + } else { + // second probe (try to bound the other side) + double error = ((double) last_sample_limit - mid.last_decoded_sample) * bytes_per_sample; + if (error >= 0 && error < 8000) error = 8000; + if (error < 0 && error > -8000) error = -8000; + offset += error * 2; + } + + // ensure the offset is valid + if (offset < left.page_end) + offset = left.page_end; + if (offset > right.page_start - 65536) + offset = right.page_start - 65536; + + set_file_offset(f, (unsigned int) offset); + } else { + // binary search for large ranges (offset by 32K to ensure + // we don't hit the right page) + set_file_offset(f, left.page_end + (delta / 2) - 32768); + } + + if (!vorbis_find_page(f, NULL, NULL)) goto error; + } + + for (;;) { + if (!get_seek_page_info(f, &mid)) goto error; + if (mid.last_decoded_sample != ~0U) break; + // (untested) no frames end on this page + set_file_offset(f, mid.page_end); + assert(mid.page_start < right.page_start); + } + + // if we've just found the last page again then we're in a tricky file, + // and we're close enough (if it wasn't an interpolation probe). + if (mid.page_start == right.page_start) { + if (probe >= 2 || delta <= 65536) + break; + } else { + if (last_sample_limit < mid.last_decoded_sample) + right = mid; + else + left = mid; + } + + ++probe; + } + + // seek back to start of the last packet + page_start = left.page_start; + set_file_offset(f, page_start); + if (!start_page(f)) return error(f, VORBIS_seek_failed); + end_pos = f->end_seg_with_known_loc; + assert(end_pos >= 0); + + for (;;) { + for (i = end_pos; i > 0; --i) + if (f->segments[i-1] != 255) + break; + + start_seg_with_known_loc = i; + + if (start_seg_with_known_loc > 0 || !(f->page_flag & PAGEFLAG_continued_packet)) + break; + + // (untested) the final packet begins on an earlier page + if (!go_to_page_before(f, page_start)) + goto error; + + page_start = stb_vorbis_get_file_offset(f); + if (!start_page(f)) goto error; + end_pos = f->segment_count - 1; + } + + // prepare to start decoding + f->current_loc_valid = FALSE; + f->last_seg = FALSE; + f->valid_bits = 0; + f->packet_bytes = 0; + f->bytes_in_seg = 0; + f->previous_length = 0; + f->next_seg = start_seg_with_known_loc; + + for (i = 0; i < start_seg_with_known_loc; i++) + skip(f, f->segments[i]); + + // start decoding (optimizable - this frame is generally discarded) + if (!vorbis_pump_first_frame(f)) + return 0; + if (f->current_loc > sample_number) + return error(f, VORBIS_seek_failed); + return 1; + +error: + // try to restore the file to a valid state + stb_vorbis_seek_start(f); + return error(f, VORBIS_seek_failed); +} + +// the same as vorbis_decode_initial, but without advancing +static int peek_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) +{ + int bits_read, bytes_read; + + if (!vorbis_decode_initial(f, p_left_start, p_left_end, p_right_start, p_right_end, mode)) + return 0; + + // either 1 or 2 bytes were read, figure out which so we can rewind + bits_read = 1 + ilog(f->mode_count-1); + if (f->mode_config[*mode].blockflag) + bits_read += 2; + bytes_read = (bits_read + 7) / 8; + + f->bytes_in_seg += bytes_read; + f->packet_bytes -= bytes_read; + skip(f, -bytes_read); + if (f->next_seg == -1) + f->next_seg = f->segment_count - 1; + else + f->next_seg--; + f->valid_bits = 0; + + return 1; +} + +int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number) +{ + uint32 max_frame_samples; + + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + // fast page-level search + if (!seek_to_sample_coarse(f, sample_number)) + return 0; + + assert(f->current_loc_valid); + assert(f->current_loc <= sample_number); + + // linear search for the relevant packet + max_frame_samples = (f->blocksize_1*3 - f->blocksize_0) >> 2; + while (f->current_loc < sample_number) { + int left_start, left_end, right_start, right_end, mode, frame_samples; + if (!peek_decode_initial(f, &left_start, &left_end, &right_start, &right_end, &mode)) + return error(f, VORBIS_seek_failed); + // calculate the number of samples returned by the next frame + frame_samples = right_start - left_start; + if (f->current_loc + frame_samples > sample_number) { + return 1; // the next frame will contain the sample + } else if (f->current_loc + frame_samples + max_frame_samples > sample_number) { + // there's a chance the frame after this could contain the sample + vorbis_pump_first_frame(f); + } else { + // this frame is too early to be relevant + f->current_loc += frame_samples; + f->previous_length = 0; + maybe_start_packet(f); + flush_packet(f); + } + } + // the next frame should start with the sample + if (f->current_loc != sample_number) return error(f, VORBIS_seek_failed); + return 1; +} + +int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number) +{ + if (!stb_vorbis_seek_frame(f, sample_number)) + return 0; + + if (sample_number != f->current_loc) { + int n; + uint32 frame_start = f->current_loc; + stb_vorbis_get_frame_float(f, &n, NULL); + assert(sample_number > frame_start); + assert(f->channel_buffer_start + (int) (sample_number-frame_start) <= f->channel_buffer_end); + f->channel_buffer_start += (sample_number - frame_start); + } + + return 1; +} + +int stb_vorbis_seek_start(stb_vorbis *f) +{ + if (IS_PUSH_MODE(f)) { return error(f, VORBIS_invalid_api_mixing); } + set_file_offset(f, f->first_audio_page_offset); + f->previous_length = 0; + f->first_decode = TRUE; + f->next_seg = -1; + return vorbis_pump_first_frame(f); +} + +unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f) +{ + unsigned int restore_offset, previous_safe; + unsigned int end, last_page_loc; + + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + if (!f->total_samples) { + unsigned int last; + uint32 lo,hi; + char header[6]; + + // first, store the current decode position so we can restore it + restore_offset = stb_vorbis_get_file_offset(f); + + // now we want to seek back 64K from the end (the last page must + // be at most a little less than 64K, but let's allow a little slop) + if (f->stream_len >= 65536 && f->stream_len-65536 >= f->first_audio_page_offset) + previous_safe = f->stream_len - 65536; + else + previous_safe = f->first_audio_page_offset; + + set_file_offset(f, previous_safe); + // previous_safe is now our candidate 'earliest known place that seeking + // to will lead to the final page' + + if (!vorbis_find_page(f, &end, &last)) { + // if we can't find a page, we're hosed! + f->error = VORBIS_cant_find_last_page; + f->total_samples = 0xffffffff; + goto done; + } + + // check if there are more pages + last_page_loc = stb_vorbis_get_file_offset(f); + + // stop when the last_page flag is set, not when we reach eof; + // this allows us to stop short of a 'file_section' end without + // explicitly checking the length of the section + while (!last) { + set_file_offset(f, end); + if (!vorbis_find_page(f, &end, &last)) { + // the last page we found didn't have the 'last page' flag + // set. whoops! + break; + } + //previous_safe = last_page_loc+1; // NOTE: not used after this point, but note for debugging + last_page_loc = stb_vorbis_get_file_offset(f); + } + + set_file_offset(f, last_page_loc); + + // parse the header + getn(f, (unsigned char *)header, 6); + // extract the absolute granule position + lo = get32(f); + hi = get32(f); + if (lo == 0xffffffff && hi == 0xffffffff) { + f->error = VORBIS_cant_find_last_page; + f->total_samples = SAMPLE_unknown; + goto done; + } + if (hi) + lo = 0xfffffffe; // saturate + f->total_samples = lo; + + f->p_last.page_start = last_page_loc; + f->p_last.page_end = end; + f->p_last.last_decoded_sample = lo; + + done: + set_file_offset(f, restore_offset); + } + return f->total_samples == SAMPLE_unknown ? 0 : f->total_samples; +} + +float stb_vorbis_stream_length_in_seconds(stb_vorbis *f) +{ + return stb_vorbis_stream_length_in_samples(f) / (float) f->sample_rate; +} + + + +int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output) +{ + int len, right,left,i; + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + if (!vorbis_decode_packet(f, &len, &left, &right)) { + f->channel_buffer_start = f->channel_buffer_end = 0; + return 0; + } + + len = vorbis_finish_frame(f, len, left, right); + for (i=0; i < f->channels; ++i) + f->outputs[i] = f->channel_buffers[i] + left; + + f->channel_buffer_start = left; + f->channel_buffer_end = left+len; + + if (channels) *channels = f->channels; + if (output) *output = f->outputs; + return len; +} + +#ifndef STB_VORBIS_NO_STDIO + +stb_vorbis * stb_vorbis_open_file_section(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc, unsigned int length) +{ + stb_vorbis *f, p; + vorbis_init(&p, alloc); + p.f = file; + p.f_start = (uint32) ftell(file); + p.stream_len = length; + p.close_on_free = close_on_free; + if (start_decoder(&p)) { + f = vorbis_alloc(&p); + if (f) { + *f = p; + vorbis_pump_first_frame(f); + return f; + } + } + if (error) *error = p.error; + vorbis_deinit(&p); + return NULL; +} + +stb_vorbis * stb_vorbis_open_file(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc) +{ + unsigned int len, start; + start = (unsigned int) ftell(file); + fseek(file, 0, SEEK_END); + len = (unsigned int) (ftell(file) - start); + fseek(file, start, SEEK_SET); + return stb_vorbis_open_file_section(file, close_on_free, error, alloc, len); +} + +stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const stb_vorbis_alloc *alloc) +{ + FILE *f; +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + if (0 != fopen_s(&f, filename, "rb")) + f = NULL; +#else + f = fopen(filename, "rb"); +#endif + if (f) + return stb_vorbis_open_file(f, TRUE, error, alloc); + if (error) *error = VORBIS_file_open_failure; + return NULL; +} +#endif // STB_VORBIS_NO_STDIO + +stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc) +{ + stb_vorbis *f, p; + if (!data) { + if (error) *error = VORBIS_unexpected_eof; + return NULL; + } + vorbis_init(&p, alloc); + p.stream = (uint8 *) data; + p.stream_end = (uint8 *) data + len; + p.stream_start = (uint8 *) p.stream; + p.stream_len = len; + p.push_mode = FALSE; + if (start_decoder(&p)) { + f = vorbis_alloc(&p); + if (f) { + *f = p; + vorbis_pump_first_frame(f); + if (error) *error = VORBIS__no_error; + return f; + } + } + if (error) *error = p.error; + vorbis_deinit(&p); + return NULL; +} + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +#define PLAYBACK_MONO 1 +#define PLAYBACK_LEFT 2 +#define PLAYBACK_RIGHT 4 + +#define L (PLAYBACK_LEFT | PLAYBACK_MONO) +#define C (PLAYBACK_LEFT | PLAYBACK_RIGHT | PLAYBACK_MONO) +#define R (PLAYBACK_RIGHT | PLAYBACK_MONO) + +static int8 channel_position[7][6] = +{ + { 0 }, + { C }, + { L, R }, + { L, C, R }, + { L, R, L, R }, + { L, C, R, L, R }, + { L, C, R, L, R, C }, +}; + + +#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT + typedef union { + float f; + int i; + } float_conv; + typedef char stb_vorbis_float_size_test[sizeof(float)==4 && sizeof(int) == 4]; + #define FASTDEF(x) float_conv x + // add (1<<23) to convert to int, then divide by 2^SHIFT, then add 0.5/2^SHIFT to round + #define MAGIC(SHIFT) (1.5f * (1 << (23-SHIFT)) + 0.5f/(1 << SHIFT)) + #define ADDEND(SHIFT) (((150-SHIFT) << 23) + (1 << 22)) + #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) (temp.f = (x) + MAGIC(s), temp.i - ADDEND(s)) + #define check_endianness() +#else + #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) ((int) ((x) * (1 << (s)))) + #define check_endianness() + #define FASTDEF(x) +#endif + +static void copy_samples(short *dest, float *src, int len) +{ + int i; + check_endianness(); + for (i=0; i < len; ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp, src[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + dest[i] = v; + } +} + +static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len) +{ + #define STB_BUFFER_SIZE 32 + float buffer[STB_BUFFER_SIZE]; + int i,j,o,n = STB_BUFFER_SIZE; + check_endianness(); + for (o = 0; o < len; o += STB_BUFFER_SIZE) { + memset(buffer, 0, sizeof(buffer)); + if (o + n > len) n = len - o; + for (j=0; j < num_c; ++j) { + if (channel_position[num_c][j] & mask) { + for (i=0; i < n; ++i) + buffer[i] += data[j][d_offset+o+i]; + } + } + for (i=0; i < n; ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + output[o+i] = v; + } + } + #undef STB_BUFFER_SIZE +} + +static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len) +{ + #define STB_BUFFER_SIZE 32 + float buffer[STB_BUFFER_SIZE]; + int i,j,o,n = STB_BUFFER_SIZE >> 1; + // o is the offset in the source data + check_endianness(); + for (o = 0; o < len; o += STB_BUFFER_SIZE >> 1) { + // o2 is the offset in the output data + int o2 = o << 1; + memset(buffer, 0, sizeof(buffer)); + if (o + n > len) n = len - o; + for (j=0; j < num_c; ++j) { + int m = channel_position[num_c][j] & (PLAYBACK_LEFT | PLAYBACK_RIGHT); + if (m == (PLAYBACK_LEFT | PLAYBACK_RIGHT)) { + for (i=0; i < n; ++i) { + buffer[i*2+0] += data[j][d_offset+o+i]; + buffer[i*2+1] += data[j][d_offset+o+i]; + } + } else if (m == PLAYBACK_LEFT) { + for (i=0; i < n; ++i) { + buffer[i*2+0] += data[j][d_offset+o+i]; + } + } else if (m == PLAYBACK_RIGHT) { + for (i=0; i < n; ++i) { + buffer[i*2+1] += data[j][d_offset+o+i]; + } + } + } + for (i=0; i < (n<<1); ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + output[o2+i] = v; + } + } + #undef STB_BUFFER_SIZE +} + +static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples) +{ + int i; + if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { + static int channel_selector[3][2] = { {0}, {PLAYBACK_MONO}, {PLAYBACK_LEFT, PLAYBACK_RIGHT} }; + for (i=0; i < buf_c; ++i) + compute_samples(channel_selector[buf_c][i], buffer[i]+b_offset, data_c, data, d_offset, samples); + } else { + int limit = buf_c < data_c ? buf_c : data_c; + for (i=0; i < limit; ++i) + copy_samples(buffer[i]+b_offset, data[i]+d_offset, samples); + for ( ; i < buf_c; ++i) + memset(buffer[i]+b_offset, 0, sizeof(short) * samples); + } +} + +int stb_vorbis_get_frame_short(stb_vorbis *f, int num_c, short **buffer, int num_samples) +{ + float **output = NULL; + int len = stb_vorbis_get_frame_float(f, NULL, &output); + if (len > num_samples) len = num_samples; + if (len) + convert_samples_short(num_c, buffer, 0, f->channels, output, 0, len); + return len; +} + +static void convert_channels_short_interleaved(int buf_c, short *buffer, int data_c, float **data, int d_offset, int len) +{ + int i; + check_endianness(); + if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { + assert(buf_c == 2); + for (i=0; i < buf_c; ++i) + compute_stereo_samples(buffer, data_c, data, d_offset, len); + } else { + int limit = buf_c < data_c ? buf_c : data_c; + int j; + for (j=0; j < len; ++j) { + for (i=0; i < limit; ++i) { + FASTDEF(temp); + float f = data[i][d_offset+j]; + int v = FAST_SCALED_FLOAT_TO_INT(temp, f,15);//data[i][d_offset+j],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + *buffer++ = v; + } + for ( ; i < buf_c; ++i) + *buffer++ = 0; + } + } +} + +int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts) +{ + float **output; + int len; + if (num_c == 1) return stb_vorbis_get_frame_short(f,num_c,&buffer, num_shorts); + len = stb_vorbis_get_frame_float(f, NULL, &output); + if (len) { + if (len*num_c > num_shorts) len = num_shorts / num_c; + convert_channels_short_interleaved(num_c, buffer, f->channels, output, 0, len); + } + return len; +} + +int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts) +{ + float **outputs; + int len = num_shorts / channels; + int n=0; + while (n < len) { + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + if (k) + convert_channels_short_interleaved(channels, buffer, f->channels, f->channel_buffers, f->channel_buffer_start, k); + buffer += k*channels; + n += k; + f->channel_buffer_start += k; + if (n == len) break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; + } + return n; +} + +int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int len) +{ + float **outputs; + int n=0; + while (n < len) { + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + if (k) + convert_samples_short(channels, buffer, n, f->channels, f->channel_buffers, f->channel_buffer_start, k); + n += k; + f->channel_buffer_start += k; + if (n == len) break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; + } + return n; +} + +#ifndef STB_VORBIS_NO_STDIO +int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output) +{ + int data_len, offset, total, limit, error; + short *data; + stb_vorbis *v = stb_vorbis_open_filename(filename, &error, NULL); + if (v == NULL) return -1; + limit = v->channels * 4096; + *channels = v->channels; + if (sample_rate) + *sample_rate = v->sample_rate; + offset = data_len = 0; + total = limit; + data = (short *) malloc(total * sizeof(*data)); + if (data == NULL) { + stb_vorbis_close(v); + return -2; + } + for (;;) { + int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); + if (n == 0) break; + data_len += n; + offset += n * v->channels; + if (offset + limit > total) { + short *data2; + total *= 2; + data2 = (short *) realloc(data, total * sizeof(*data)); + if (data2 == NULL) { + free(data); + stb_vorbis_close(v); + return -2; + } + data = data2; + } + } + *output = data; + stb_vorbis_close(v); + return data_len; +} +#endif // NO_STDIO + +int stb_vorbis_decode_memory(const uint8 *mem, int len, int *channels, int *sample_rate, short **output) +{ + int data_len, offset, total, limit, error; + short *data; + stb_vorbis *v = stb_vorbis_open_memory(mem, len, &error, NULL); + if (v == NULL) return -1; + limit = v->channels * 4096; + *channels = v->channels; + if (sample_rate) + *sample_rate = v->sample_rate; + offset = data_len = 0; + total = limit; + data = (short *) malloc(total * sizeof(*data)); + if (data == NULL) { + stb_vorbis_close(v); + return -2; + } + for (;;) { + int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); + if (n == 0) break; + data_len += n; + offset += n * v->channels; + if (offset + limit > total) { + short *data2; + total *= 2; + data2 = (short *) realloc(data, total * sizeof(*data)); + if (data2 == NULL) { + free(data); + stb_vorbis_close(v); + return -2; + } + data = data2; + } + } + *output = data; + stb_vorbis_close(v); + return data_len; +} +#endif // STB_VORBIS_NO_INTEGER_CONVERSION + +int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats) +{ + float **outputs; + int len = num_floats / channels; + int n=0; + int z = f->channels; + if (z > channels) z = channels; + while (n < len) { + int i,j; + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + for (j=0; j < k; ++j) { + for (i=0; i < z; ++i) + *buffer++ = f->channel_buffers[i][f->channel_buffer_start+j]; + for ( ; i < channels; ++i) + *buffer++ = 0; + } + n += k; + f->channel_buffer_start += k; + if (n == len) + break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) + break; + } + return n; +} + +int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples) +{ + float **outputs; + int n=0; + int z = f->channels; + if (z > channels) z = channels; + while (n < num_samples) { + int i; + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= num_samples) k = num_samples - n; + if (k) { + for (i=0; i < z; ++i) + memcpy(buffer[i]+n, f->channel_buffers[i]+f->channel_buffer_start, sizeof(float)*k); + for ( ; i < channels; ++i) + memset(buffer[i]+n, 0, sizeof(float) * k); + } + n += k; + f->channel_buffer_start += k; + if (n == num_samples) + break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) + break; + } + return n; +} +#endif // STB_VORBIS_NO_PULLDATA_API + +/* Version history + 1.17 - 2019-07-08 - fix CVE-2019-13217, -13218, -13219, -13220, -13221, -13222, -13223 + found with Mayhem by ForAllSecure + 1.16 - 2019-03-04 - fix warnings + 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found + 1.14 - 2018-02-11 - delete bogus dealloca usage + 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) + 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files + 1.11 - 2017-07-23 - fix MinGW compilation + 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory + 1.09 - 2016-04-04 - back out 'avoid discarding last frame' fix from previous version + 1.08 - 2016-04-02 - fixed multiple warnings; fix setup memory leaks; + avoid discarding last frame of audio data + 1.07 - 2015-01-16 - fixed some warnings, fix mingw, const-correct API + some more crash fixes when out of memory or with corrupt files + 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) + some crash fixes when out of memory or with corrupt files + 1.05 - 2015-04-19 - don't define __forceinline if it's redundant + 1.04 - 2014-08-27 - fix missing const-correct case in API + 1.03 - 2014-08-07 - Warning fixes + 1.02 - 2014-07-09 - Declare qsort compare function _cdecl on windows + 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float + 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in multichannel + (API change) report sample rate for decode-full-file funcs + 0.99996 - bracket #include for macintosh compilation by Laurent Gomila + 0.99995 - use union instead of pointer-cast for fast-float-to-int to avoid alias-optimization problem + 0.99994 - change fast-float-to-int to work in single-precision FPU mode, remove endian-dependence + 0.99993 - remove assert that fired on legal files with empty tables + 0.99992 - rewind-to-start + 0.99991 - bugfix to stb_vorbis_get_samples_short by Bernhard Wodo + 0.9999 - (should have been 0.99990) fix no-CRT support, compiling as C++ + 0.9998 - add a full-decode function with a memory source + 0.9997 - fix a bug in the read-from-FILE case in 0.9996 addition + 0.9996 - query length of vorbis stream in samples/seconds + 0.9995 - bugfix to another optimization that only happened in certain files + 0.9994 - bugfix to one of the optimizations that caused significant (but inaudible?) errors + 0.9993 - performance improvements; runs in 99% to 104% of time of reference implementation + 0.9992 - performance improvement of IMDCT; now performs close to reference implementation + 0.9991 - performance improvement of IMDCT + 0.999 - (should have been 0.9990) performance improvement of IMDCT + 0.998 - no-CRT support from Casey Muratori + 0.997 - bugfixes for bugs found by Terje Mathisen + 0.996 - bugfix: fast-huffman decode initialized incorrectly for sparse codebooks; fixing gives 10% speedup - found by Terje Mathisen + 0.995 - bugfix: fix to 'effective' overrun detection - found by Terje Mathisen + 0.994 - bugfix: garbage decode on final VQ symbol of a non-multiple - found by Terje Mathisen + 0.993 - bugfix: pushdata API required 1 extra byte for empty page (failed to consume final page if empty) - found by Terje Mathisen + 0.992 - fixes for MinGW warning + 0.991 - turn fast-float-conversion on by default + 0.990 - fix push-mode seek recovery if you seek into the headers + 0.98b - fix to bad release of 0.98 + 0.98 - fix push-mode seek recovery; robustify float-to-int and support non-fast mode + 0.97 - builds under c++ (typecasting, don't use 'class' keyword) + 0.96 - somehow MY 0.95 was right, but the web one was wrong, so here's my 0.95 rereleased as 0.96, fixes a typo in the clamping code + 0.95 - clamping code for 16-bit functions + 0.94 - not publically released + 0.93 - fixed all-zero-floor case (was decoding garbage) + 0.92 - fixed a memory leak + 0.91 - conditional compiles to omit parts of the API and the infrastructure to support them: STB_VORBIS_NO_PULLDATA_API, STB_VORBIS_NO_PUSHDATA_API, STB_VORBIS_NO_STDIO, STB_VORBIS_NO_INTEGER_CONVERSION + 0.90 - first public release +*/ + +#endif // STB_VORBIS_HEADER_ONLY + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/stb_voxel_render.h b/source/engine/thirdparty/stb/stb_voxel_render.h new file mode 100644 index 0000000..2e7a372 --- /dev/null +++ b/source/engine/thirdparty/stb/stb_voxel_render.h @@ -0,0 +1,3807 @@ +// stb_voxel_render.h - v0.89 - Sean Barrett, 2015 - public domain +// +// This library helps render large-scale "voxel" worlds for games, +// in this case, one with blocks that can have textures and that +// can also be a few shapes other than cubes. +// +// Video introduction: +// http://www.youtube.com/watch?v=2vnTtiLrV1w +// +// Minecraft-viewer sample app (not very simple though): +// http://github.com/nothings/stb/tree/master/tests/caveview +// +// It works by creating triangle meshes. The library includes +// +// - converter from dense 3D arrays of block info to vertex mesh +// - vertex & fragment shaders for the vertex mesh +// - assistance in setting up shader state +// +// For portability, none of the library code actually accesses +// the 3D graphics API. (At the moment, it's not actually portable +// since the shaders are GLSL only, but patches are welcome.) +// +// You have to do all the caching and tracking of vertex buffers +// yourself. However, you could also try making a game with +// a small enough world that it's fully loaded rather than +// streaming. Currently the preferred vertex format is 20 bytes +// per quad. There are designs to allow much more compact formats +// with a slight reduction in shader features, but no roadmap +// for actually implementing them. +// +// +// USAGE +// +// #define the symbol STB_VOXEL_RENDER_IMPLEMENTATION in *one* +// C/C++ file before the #include of this file; the implementation +// will be generated in that file. +// +// If you define the symbols STB_VOXEL_RENDER_STATIC, then the +// implementation will be private to that file. +// +// +// FEATURES +// +// - you can choose textured blocks with the features below, +// or colored voxels with 2^24 colors and no textures. +// +// - voxels are mostly just cubes, but there's support for +// half-height cubes and diagonal slopes, half-height +// diagonals, and even odder shapes especially for doing +// more-continuous "ground". +// +// - texture coordinates are projections along one of the major +// axes, with the per-texture scaling. +// +// - a number of aspects of the shader and the vertex format +// are configurable; the library generally takes care of +// coordinating the vertex format with the mesh for you. +// +// +// FEATURES (SHADER PERSPECTIVE) +// +// - vertices aligned on integer lattice, z on multiples of 0.5 +// - per-vertex "lighting" or "ambient occlusion" value (6 bits) +// - per-vertex texture crossfade (3 bits) +// +// - per-face texture #1 id (8-bit index into array texture) +// - per-face texture #2 id (8-bit index into second array texture) +// - per-face color (6-bit palette index, 2 bits of per-texture boolean enable) +// - per-face 5-bit normal for lighting calculations & texture coord computation +// - per-face 2-bit texture matrix rotation to rotate faces +// +// - indexed-by-texture-id scale factor (separate for texture #1 and texture #2) +// - indexed-by-texture-#2-id blend mode (alpha composite or modulate/multiply); +// the first is good for decals, the second for detail textures, "light maps", +// etc; both modes are controlled by texture #2's alpha, scaled by the +// per-vertex texture crossfade and the per-face color (if enabled on texture #2); +// modulate/multiply multiplies by an extra factor of 2.0 so that if you +// make detail maps whose average brightness is 0.5 everything works nicely. +// +// - ambient lighting: half-lambert directional plus constant, all scaled by vertex ao +// - face can be fullbright (emissive), controlled by per-face color +// - installable lighting, with default single-point-light +// - installable fog, with default hacked smoothstep +// +// Note that all the variations of lighting selection and texture +// blending are run-time conditions in the shader, so they can be +// intermixed in a single mesh. +// +// +// INTEGRATION ARC +// +// The way to get this library to work from scratch is to do the following: +// +// Step 1. define STBVOX_CONFIG_MODE to 0 +// +// This mode uses only vertex attributes and uniforms, and is easiest +// to get working. It requires 32 bytes per quad and limits the +// size of some tables to avoid hitting uniform limits. +// +// Step 2. define STBVOX_CONFIG_MODE to 1 +// +// This requires using a texture buffer to store the quad data, +// reducing the size to 20 bytes per quad. +// +// Step 3: define STBVOX_CONFIG_PREFER_TEXBUFFER +// +// This causes some uniforms to be stored as texture buffers +// instead. This increases the size of some of those tables, +// and avoids a potential slow path (gathering non-uniform +// data from uniforms) on some hardware. +// +// In the future I might add additional modes that have significantly +// smaller meshes but reduce features, down as small as 6 bytes per quad. +// See elsewhere in this file for a table of candidate modes. Switching +// to a mode will require changing some of your mesh creation code, but +// everything else should be seamless. (And I'd like to change the API +// so that mesh creation is data-driven the way the uniforms are, and +// then you wouldn't even have to change anything but the mode number.) +// +// +// IMPROVEMENTS FOR SHIP-WORTHY PROGRAMS USING THIS LIBRARY +// +// I currently tolerate a certain level of "bugginess" in this library. +// +// I'm referring to things which look a little wrong (as long as they +// don't cause holes or cracks in the output meshes), or things which +// do not produce as optimal a mesh as possible. Notable examples: +// +// - incorrect lighting on slopes +// - inefficient meshes for vheight blocks +// +// I am willing to do the work to improve these things if someone is +// going to ship a substantial program that would be improved by them. +// (It need not be commercial, nor need it be a game.) I just didn't +// want to do the work up front if it might never be leveraged. So just +// submit a bug report as usual (github is preferred), but add a note +// that this is for a thing that is really going to ship. (That means +// you need to be far enough into the project that it's clear you're +// committed to it; not during early exploratory development.) +// +// +// VOXEL MESH API +// +// Context +// +// To understand the API, make sure you first understand the feature set +// listed above. +// +// Because the vertices are compact, they have very limited spatial +// precision. Thus a single mesh can only contain the data for a limited +// area. To make very large voxel maps, you'll need to build multiple +// vertex buffers. (But you want this anyway for frustum culling.) +// +// Each generated mesh has three components: +// - vertex data (vertex buffer) +// - face data (optional, stored in texture buffer) +// - mesh transform (uniforms) +// +// Once you've generated the mesh with this library, it's up to you +// to upload it to the GPU, to keep track of the state, and to render +// it. +// +// Concept +// +// The basic design is that you pass in one or more 3D arrays; each array +// is (typically) one-byte-per-voxel and contains information about one +// or more properties of some particular voxel property. +// +// Because there is so much per-vertex and per-face data possible +// in the output, and each voxel can have 6 faces and 8 vertices, it +// would require an very large data structure to describe all +// of the possibilities, and this would cause the mesh-creation +// process to be slow. Instead, the API provides multiple ways +// to express each property, some more compact, others less so; +// each such way has some limitations on what it can express. +// +// Note that there are so many paths and combinations, not all of them +// have been tested. Just report bugs and I'll fix 'em. +// +// Details +// +// See the API documentation in the header-file section. +// +// +// CONTRIBUTORS +// +// Features Porting Bugfixes & Warnings +// Sean Barrett github:r-leyh Jesus Fernandez +// Miguel Lechon github:Arbeiterunfallversicherungsgesetz +// Thomas Frase James Hofmann +// Stephen Olsen github:guitarfreak +// +// VERSION HISTORY +// +// 0.89 (2020-02-02) bugfix in sample code +// 0.88 (2019-03-04) fix warnings +// 0.87 (2019-02-25) fix warning +// 0.86 (2019-02-07) fix typos in comments +// 0.85 (2017-03-03) add block_selector (by guitarfreak) +// 0.84 (2016-04-02) fix GLSL syntax error on glModelView path +// 0.83 (2015-09-13) remove non-constant struct initializers to support more compilers +// 0.82 (2015-08-01) added input.packed_compact to store rot, vheight & texlerp efficiently +// fix broken tex_overlay2 +// 0.81 (2015-05-28) fix broken STBVOX_CONFIG_OPTIMIZED_VHEIGHT +// 0.80 (2015-04-11) fix broken STBVOX_CONFIG_ROTATION_IN_LIGHTING refactoring +// change STBVOX_MAKE_LIGHTING to STBVOX_MAKE_LIGHTING_EXT so +// that header defs don't need to see config vars +// add STBVOX_CONFIG_VHEIGHT_IN_LIGHTING and other vheight fixes +// added documentation for vheight ("weird slopes") +// 0.79 (2015-04-01) fix the missing types from 0.78; fix string constants being const +// 0.78 (2015-04-02) bad "#else", compile as C++ +// 0.77 (2015-04-01) documentation tweaks, rename config var to STB_VOXEL_RENDER_STATIC +// 0.76 (2015-04-01) typos, signed/unsigned shader issue, more documentation +// 0.75 (2015-04-01) initial release +// +// +// HISTORICAL FOUNDATION +// +// stb_voxel_render 20-byte quads 2015/01 +// zmc engine 32-byte quads 2013/12 +// zmc engine 96-byte quads 2011/10 +// +// +// LICENSE +// +// See end of file for license information. + +#ifndef INCLUDE_STB_VOXEL_RENDER_H +#define INCLUDE_STB_VOXEL_RENDER_H + +#include + +typedef struct stbvox_mesh_maker stbvox_mesh_maker; +typedef struct stbvox_input_description stbvox_input_description; + +#ifdef STB_VOXEL_RENDER_STATIC +#define STBVXDEC static +#else +#define STBVXDEC extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// CONFIGURATION MACROS +// +// #define STBVOX_CONFIG_MODE // REQUIRED +// Configures the overall behavior of stb_voxel_render. This +// can affect the shaders, the uniform info, and other things. +// (If you need more than one mode in the same app, you can +// use STB_VOXEL_RENDER_STATIC to create multiple versions +// in separate files, and then wrap them.) +// +// Mode value Meaning +// 0 Textured blocks, 32-byte quads +// 1 Textured blocks, 20-byte quads +// 20 Untextured blocks, 32-byte quads +// 21 Untextured blocks, 20-byte quads +// +// +// #define STBVOX_CONFIG_PRECISION_Z // OPTIONAL +// Defines the number of bits of fractional position for Z. +// Only 0 or 1 are valid. 1 is the default. If 0, then a +// single mesh has twice the legal Z range; e.g. in +// modes 0,1,20,21, Z in the mesh can extend to 511 instead +// of 255. However, half-height blocks cannot be used. +// +// All of the following are just #ifdef tested so need no values, and are optional. +// +// STBVOX_CONFIG_BLOCKTYPE_SHORT +// use unsigned 16-bit values for 'blocktype' in the input instead of 8-bit values +// +// STBVOX_CONFIG_OPENGL_MODELVIEW +// use the gl_ModelView matrix rather than the explicit uniform +// +// STBVOX_CONFIG_HLSL +// NOT IMPLEMENTED! Define HLSL shaders instead of GLSL shaders +// +// STBVOX_CONFIG_PREFER_TEXBUFFER +// Stores many of the uniform arrays in texture buffers instead, +// so they can be larger and may be more efficient on some hardware. +// +// STBVOX_CONFIG_LIGHTING_SIMPLE +// Creates a simple lighting engine with a single point light source +// in addition to the default half-lambert ambient light. +// +// STBVOX_CONFIG_LIGHTING +// Declares a lighting function hook; you must append a lighting function +// to the shader before compiling it: +// vec3 compute_lighting(vec3 pos, vec3 norm, vec3 albedo, vec3 ambient); +// 'ambient' is the half-lambert ambient light with vertex ambient-occlusion applied +// +// STBVOX_CONFIG_FOG_SMOOTHSTEP +// Defines a simple unrealistic fog system designed to maximize +// unobscured view distance while not looking too weird when things +// emerge from the fog. Configured using an extra array element +// in the STBVOX_UNIFORM_ambient uniform. +// +// STBVOX_CONFIG_FOG +// Defines a fog function hook; you must append a fog function to +// the shader before compiling it: +// vec3 compute_fog(vec3 color, vec3 relative_pos, float fragment_alpha); +// "color" is the incoming pre-fogged color, fragment_alpha is the alpha value, +// and relative_pos is the vector from the point to the camera in worldspace +// +// STBVOX_CONFIG_DISABLE_TEX2 +// This disables all processing of texture 2 in the shader in case +// you don't use it. Eventually this could be replaced with a mode +// that omits the unused data entirely. +// +// STBVOX_CONFIG_TEX1_EDGE_CLAMP +// STBVOX_CONFIG_TEX2_EDGE_CLAMP +// If you want to edge clamp the textures, instead of letting them wrap, +// set this flag. By default stb_voxel_render relies on texture wrapping +// to simplify texture coordinate generation. This flag forces it to do +// it correctly, although there can still be minor artifacts. +// +// STBVOX_CONFIG_ROTATION_IN_LIGHTING +// Changes the meaning of the 'lighting' mesher input variable to also +// store the rotation; see later discussion. +// +// STBVOX_CONFIG_VHEIGHT_IN_LIGHTING +// Changes the meaning of the 'lighting' mesher input variable to also +// store the vheight; see later discussion. Cannot use both this and +// the previous variable. +// +// STBVOX_CONFIG_PREMULTIPLIED_ALPHA +// Adjusts the shader calculations on the assumption that tex1.rgba, +// tex2.rgba, and color.rgba all use premultiplied values, and that +// the output of the fragment shader should be premultiplied. +// +// STBVOX_CONFIG_UNPREMULTIPLY +// Only meaningful if STBVOX_CONFIG_PREMULTIPLIED_ALPHA is defined. +// Changes the behavior described above so that the inputs are +// still premultiplied alpha, but the output of the fragment +// shader is not premultiplied alpha. This is needed when allowing +// non-unit alpha values but not doing alpha-blending (for example +// when alpha testing). +// + +////////////////////////////////////////////////////////////////////////////// +// +// MESHING +// +// A mesh represents a (typically) small chunk of a larger world. +// Meshes encode coordinates using small integers, so those +// coordinates must be relative to some base location. +// All of the coordinates in the functions below use +// these relative coordinates unless explicitly stated +// otherwise. +// +// Input to the meshing step is documented further down + +STBVXDEC void stbvox_init_mesh_maker(stbvox_mesh_maker *mm); +// Call this function to initialize a mesh-maker context structure +// used to build meshes. You should have one context per thread +// that's building meshes. + +STBVXDEC void stbvox_set_buffer(stbvox_mesh_maker *mm, int mesh, int slot, void *buffer, size_t len); +// Call this to set the buffer into which stbvox will write the mesh +// it creates. It can build more than one mesh in parallel (distinguished +// by the 'mesh' parameter), and each mesh can be made up of more than +// one buffer (distinguished by the 'slot' parameter). +// +// Multiple meshes are under your control; use the 'selector' input +// variable to choose which mesh each voxel's vertices are written to. +// For example, you can use this to generate separate meshes for opaque +// and transparent data. +// +// You can query the number of slots by calling stbvox_get_buffer_count +// described below. The meaning of the buffer for each slot depends +// on STBVOX_CONFIG_MODE. +// +// In mode 0 & mode 20, there is only one slot. The mesh data for that +// slot is two interleaved vertex attributes: attr_vertex, a single +// 32-bit uint, and attr_face, a single 32-bit uint. +// +// In mode 1 & mode 21, there are two slots. The first buffer should +// be four times as large as the second buffer. The first buffer +// contains a single vertex attribute: 'attr_vertex', a single 32-bit uint. +// The second buffer contains texture buffer data (an array of 32-bit uints) +// that will be accessed through the sampler identified by STBVOX_UNIFORM_face_data. + +STBVXDEC int stbvox_get_buffer_count(stbvox_mesh_maker *mm); +// Returns the number of buffers needed per mesh as described above. + +STBVXDEC int stbvox_get_buffer_size_per_quad(stbvox_mesh_maker *mm, int slot); +// Returns how much of a given buffer will get used per quad. This +// allows you to choose correct relative sizes for each buffer, although +// the values are fixed based on the configuration you've selected at +// compile time, and the details are described in stbvox_set_buffer. + +STBVXDEC void stbvox_set_default_mesh(stbvox_mesh_maker *mm, int mesh); +// Selects which mesh the mesher will output to (see previous function) +// if the input doesn't specify a per-voxel selector. (I doubt this is +// useful, but it's here just in case.) + +STBVXDEC stbvox_input_description *stbvox_get_input_description(stbvox_mesh_maker *mm); +// This function call returns a pointer to the stbvox_input_description part +// of stbvox_mesh_maker (which you should otherwise treat as opaque). You +// zero this structure, then fill out the relevant pointers to the data +// describing your voxel object/world. +// +// See further documentation at the description of stbvox_input_description below. + +STBVXDEC void stbvox_set_input_stride(stbvox_mesh_maker *mm, int x_stride_in_elements, int y_stride_in_elements); +// This sets the stride between successive elements of the 3D arrays +// in the stbvox_input_description. Z values are always stored consecutively. +// (The preferred coordinate system for stbvox is X right, Y forwards, Z up.) + +STBVXDEC void stbvox_set_input_range(stbvox_mesh_maker *mm, int x0, int y0, int z0, int x1, int y1, int z1); +// This sets the range of values in the 3D array for the voxels that +// the mesh generator will convert. The lower values are inclusive, +// the higher values are exclusive, so (0,0,0) to (16,16,16) generates +// mesh data associated with voxels up to (15,15,15) but no higher. +// +// The mesh generate generates faces at the boundary between open space +// and solid space but associates them with the solid space, so if (15,0,0) +// is open and (16,0,0) is solid, then the mesh will contain the boundary +// between them if x0 <= 16 and x1 > 16. +// +// Note that the mesh generator will access array elements 1 beyond the +// limits set in these parameters. For example, if you set the limits +// to be (0,0,0) and (16,16,16), then the generator will access all of +// the voxels between (-1,-1,-1) and (16,16,16), including (16,16,16). +// You may have to do pointer arithmetic to make it work. +// +// For example, caveview processes mesh chunks that are 32x32x16, but it +// does this using input buffers that are 34x34x18. +// +// The lower limits are x0 >= 0, y0 >= 0, and z0 >= 0. +// +// The upper limits are mode dependent, but all the current methods are +// limited to x1 < 127, y1 < 127, z1 < 255. Note that these are not +// powers of two; if you want to use power-of-two chunks (to make +// it efficient to decide which chunk a coordinate falls in), you're +// limited to at most x1=64, y1=64, z1=128. For classic Minecraft-style +// worlds with limited vertical extent, I recommend using a single +// chunk for the entire height, which limits the height to 255 blocks +// (one less than Minecraft), and only chunk the map in X & Y. + +STBVXDEC int stbvox_make_mesh(stbvox_mesh_maker *mm); +// Call this function to create mesh data for the currently configured +// set of input data. This appends to the currently configured mesh output +// buffer. Returns 1 on success. If there is not enough room in the buffer, +// it outputs as much as it can, and returns 0; you need to switch output +// buffers (either by calling stbvox_set_buffer to set new buffers, or +// by copying the data out and calling stbvox_reset_buffers), and then +// call this function again without changing any of the input parameters. +// +// Note that this function appends; you can call it multiple times to +// build a single mesh. For example, caveview uses chunks that are +// 32x32x255, but builds the mesh for it by processing 32x32x16 at atime +// (this is faster as it is reuses the same 34x34x18 input buffers rather +// than needing 34x34x257 input buffers). + +// Once you're done creating a mesh into a given buffer, +// consider the following functions: + +STBVXDEC int stbvox_get_quad_count(stbvox_mesh_maker *mm, int mesh); +// Returns the number of quads in the mesh currently generated by mm. +// This is the sum of all consecutive stbvox_make_mesh runs appending +// to the same buffer. 'mesh' distinguishes between the multiple user +// meshes available via 'selector' or stbvox_set_default_mesh. +// +// Typically you use this function when you're done building the mesh +// and want to record how to draw it. +// +// Note that there are no index buffers; the data stored in the buffers +// should be drawn as quads (e.g. with GL_QUAD); if your API does not +// support quads, you can create a single index buffer large enough to +// draw your largest vertex buffer, and reuse it for every rendering. +// (Note that if you use 32-bit indices, you'll use 24 bytes of bandwidth +// per quad, more than the 20 bytes for the vertex/face mesh data.) + +STBVXDEC void stbvox_set_mesh_coordinates(stbvox_mesh_maker *mm, int x, int y, int z); +// Sets the global coordinates for this chunk, such that (0,0,0) relative +// coordinates will be at (x,y,z) in global coordinates. + +STBVXDEC void stbvox_get_bounds(stbvox_mesh_maker *mm, float bounds[2][3]); +// Returns the bounds for the mesh in global coordinates. Use this +// for e.g. frustum culling the mesh. @BUG: this just uses the +// values from stbvox_set_input_range(), so if you build by +// appending multiple values, this will be wrong, and you need to +// set stbvox_set_input_range() to the full size. Someday this +// will switch to tracking the actual bounds of the *mesh*, though. + +STBVXDEC void stbvox_get_transform(stbvox_mesh_maker *mm, float transform[3][3]); +// Returns the 'transform' data for the shader uniforms. It is your +// job to set this to the shader before drawing the mesh. It is the +// only uniform that needs to change per-mesh. Note that it is not +// a 3x3 matrix, but rather a scale to decode fixed point numbers as +// floats, a translate from relative to global space, and a special +// translation for texture coordinate generation that avoids +// floating-point precision issues. @TODO: currently we add the +// global translation to the vertex, than multiply by modelview, +// but this means if camera location and vertex are far from the +// origin, we lose precision. Need to make a special modelview with +// the translation (or some of it) factored out to avoid this. + +STBVXDEC void stbvox_reset_buffers(stbvox_mesh_maker *mm); +// Call this function if you're done with the current output buffer +// but want to reuse it (e.g. you're done appending with +// stbvox_make_mesh and you've copied the data out to your graphics API +// so can reuse the buffer). + +////////////////////////////////////////////////////////////////////////////// +// +// RENDERING +// + +STBVXDEC char *stbvox_get_vertex_shader(void); +// Returns the (currently GLSL-only) vertex shader. + +STBVXDEC char *stbvox_get_fragment_shader(void); +// Returns the (currently GLSL-only) fragment shader. +// You can override the lighting and fogging calculations +// by appending data to the end of these; see the #define +// documentation for more information. + +STBVXDEC char *stbvox_get_fragment_shader_alpha_only(void); +// Returns a slightly cheaper fragment shader that computes +// alpha but not color. This is useful for e.g. a depth-only +// pass when using alpha test. + +typedef struct stbvox_uniform_info stbvox_uniform_info; + +STBVXDEC int stbvox_get_uniform_info(stbvox_uniform_info *info, int uniform); +// Gets the information about a uniform necessary for you to +// set up each uniform with a minimal amount of explicit code. +// See the sample code after the structure definition for stbvox_uniform_info, +// further down in this header section. +// +// "uniform" is from the list immediately following. For many +// of these, default values are provided which you can set. +// Most values are shared for most draw calls; e.g. for stateful +// APIs you can set most of the state only once. Only +// STBVOX_UNIFORM_transform needs to change per draw call. +// +// STBVOX_UNIFORM_texscale +// 64- or 128-long vec4 array. (128 only if STBVOX_CONFIG_PREFER_TEXBUFFER) +// x: scale factor to apply to texture #1. must be a power of two. 1.0 means 'face-sized' +// y: scale factor to apply to texture #2. must be a power of two. 1.0 means 'face-sized' +// z: blend mode indexed by texture #2. 0.0 is alpha compositing; 1.0 is multiplication. +// w: unused currently. @TODO use to support texture animation? +// +// Texscale is indexed by the bottom 6 or 7 bits of the texture id; thus for +// example the texture at index 0 in the array and the texture in index 128 of +// the array must be scaled the same. This means that if you only have 64 or 128 +// unique textures, they all get distinct values anyway; otherwise you have +// to group them in pairs or sets of four. +// +// STBVOX_UNIFORM_ambient +// 4-long vec4 array: +// ambient[0].xyz - negative of direction of a directional light for half-lambert +// ambient[1].rgb - color of light scaled by NdotL (can be negative) +// ambient[2].rgb - constant light added to above calculation; +// effectively light ranges from ambient[2]-ambient[1] to ambient[2]+ambient[1] +// ambient[3].rgb - fog color for STBVOX_CONFIG_FOG_SMOOTHSTEP +// ambient[3].a - reciprocal of squared distance of farthest fog point (viewing distance) + + + // +----- has a default value + // | +-- you should always use the default value +enum // V V +{ // ------------------------------------------------ + STBVOX_UNIFORM_face_data, // n the sampler with the face texture buffer + STBVOX_UNIFORM_transform, // n the transform data from stbvox_get_transform + STBVOX_UNIFORM_tex_array, // n an array of two texture samplers containing the two texture arrays + STBVOX_UNIFORM_texscale, // Y a table of texture properties, see above + STBVOX_UNIFORM_color_table, // Y 64 vec4 RGBA values; a default palette is provided; if A > 1.0, fullbright + STBVOX_UNIFORM_normals, // Y Y table of normals, internal-only + STBVOX_UNIFORM_texgen, // Y Y table of texgen vectors, internal-only + STBVOX_UNIFORM_ambient, // n lighting & fog info, see above + STBVOX_UNIFORM_camera_pos, // Y camera position in global voxel space (for lighting & fog) + + STBVOX_UNIFORM_count, +}; + +enum +{ + STBVOX_UNIFORM_TYPE_none, + STBVOX_UNIFORM_TYPE_sampler, + STBVOX_UNIFORM_TYPE_vec2, + STBVOX_UNIFORM_TYPE_vec3, + STBVOX_UNIFORM_TYPE_vec4, +}; + +struct stbvox_uniform_info +{ + int type; // which type of uniform + int bytes_per_element; // the size of each uniform array element (e.g. vec3 = 12 bytes) + int array_length; // length of the uniform array + char *name; // name in the shader @TODO use numeric binding + float *default_value; // if not NULL, you can use this as the uniform pointer + int use_tex_buffer; // if true, then the uniform is a sampler but the data can come from default_value +}; + +////////////////////////////////////////////////////////////////////////////// +// +// Uniform sample code +// + +#if 0 +// Run this once per frame before drawing all the meshes. +// You still need to separately set the 'transform' uniform for every mesh. +void setup_uniforms(GLuint shader, float camera_pos[4], GLuint tex1, GLuint tex2) +{ + int i; + glUseProgram(shader); // so uniform binding works + for (i=0; i < STBVOX_UNIFORM_count; ++i) { + stbvox_uniform_info sui; + if (stbvox_get_uniform_info(&sui, i)) { + GLint loc = glGetUniformLocation(shader, sui.name); + if (loc != -1) { + switch (i) { + case STBVOX_UNIFORM_camera_pos: // only needed for fog + glUniform4fv(loc, sui.array_length, camera_pos); + break; + + case STBVOX_UNIFORM_tex_array: { + GLuint tex_unit[2] = { 0, 1 }; // your choice of samplers + glUniform1iv(loc, 2, tex_unit); + + glActiveTexture(GL_TEXTURE0 + tex_unit[0]); glBindTexture(GL_TEXTURE_2D_ARRAY, tex1); + glActiveTexture(GL_TEXTURE0 + tex_unit[1]); glBindTexture(GL_TEXTURE_2D_ARRAY, tex2); + glActiveTexture(GL_TEXTURE0); // reset to default + break; + } + + case STBVOX_UNIFORM_face_data: + glUniform1i(loc, SAMPLER_YOU_WILL_BIND_PER_MESH_FACE_DATA_TO); + break; + + case STBVOX_UNIFORM_ambient: // you definitely want to override this + case STBVOX_UNIFORM_color_table: // you might want to override this + case STBVOX_UNIFORM_texscale: // you may want to override this + glUniform4fv(loc, sui.array_length, sui.default_value); + break; + + case STBVOX_UNIFORM_normals: // you never want to override this + case STBVOX_UNIFORM_texgen: // you never want to override this + glUniform3fv(loc, sui.array_length, sui.default_value); + break; + } + } + } + } +} +#endif + +#ifdef __cplusplus +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// INPUT TO MESHING +// + +// Shapes of blocks that aren't always cubes +enum +{ + STBVOX_GEOM_empty, + STBVOX_GEOM_knockout, // creates a hole in the mesh + STBVOX_GEOM_solid, + STBVOX_GEOM_transp, // solid geometry, but transparent contents so neighbors generate normally, unless same blocktype + + // following 4 can be represented by vheight as well + STBVOX_GEOM_slab_upper, + STBVOX_GEOM_slab_lower, + STBVOX_GEOM_floor_slope_north_is_top, + STBVOX_GEOM_ceil_slope_north_is_bottom, + + STBVOX_GEOM_floor_slope_north_is_top_as_wall_UNIMPLEMENTED, // same as floor_slope above, but uses wall's texture & texture projection + STBVOX_GEOM_ceil_slope_north_is_bottom_as_wall_UNIMPLEMENTED, + STBVOX_GEOM_crossed_pair, // corner-to-corner pairs, with normal vector bumped upwards + STBVOX_GEOM_force, // like GEOM_transp, but faces visible even if neighbor is same type, e.g. minecraft fancy leaves + + // these access vheight input + STBVOX_GEOM_floor_vheight_03 = 12, // diagonal is SW-NE + STBVOX_GEOM_floor_vheight_12, // diagonal is SE-NW + STBVOX_GEOM_ceil_vheight_03, + STBVOX_GEOM_ceil_vheight_12, + + STBVOX_GEOM_count, // number of geom cases +}; + +enum +{ + STBVOX_FACE_east, + STBVOX_FACE_north, + STBVOX_FACE_west, + STBVOX_FACE_south, + STBVOX_FACE_up, + STBVOX_FACE_down, + + STBVOX_FACE_count, +}; + +#ifdef STBVOX_CONFIG_BLOCKTYPE_SHORT +typedef unsigned short stbvox_block_type; +#else +typedef unsigned char stbvox_block_type; +#endif + +// 24-bit color +typedef struct +{ + unsigned char r,g,b; +} stbvox_rgb; + +#define STBVOX_COLOR_TEX1_ENABLE 64 +#define STBVOX_COLOR_TEX2_ENABLE 128 + +// This is the data structure you fill out. Most of the arrays can be +// NULL, except when one is required to get the value to index another. +// +// The compass system used in the following descriptions is: +// east means increasing x +// north means increasing y +// up means increasing z +struct stbvox_input_description +{ + unsigned char lighting_at_vertices; + // The default is lighting values (i.e. ambient occlusion) are at block + // center, and the vertex light is gathered from those adjacent block + // centers that the vertex is facing. This makes smooth lighting + // consistent across adjacent faces with the same orientation. + // + // Setting this flag to non-zero gives you explicit control + // of light at each vertex, but now the lighting/ao will be + // shared by all vertices at the same point, even if they + // have different normals. + + // these are mostly 3D maps you use to define your voxel world, using x_stride and y_stride + // note that for cache efficiency, you want to use the block_foo palettes as much as possible instead + + stbvox_rgb *rgb; + // Indexed by 3D coordinate. + // 24-bit voxel color for STBVOX_CONFIG_MODE = 20 or 21 only + + unsigned char *lighting; + // Indexed by 3D coordinate. The lighting value / ambient occlusion + // value that is used to define the vertex lighting values. + // The raw lighting values are defined at the center of blocks + // (or at vertex if 'lighting_at_vertices' is true). + // + // If the macro STBVOX_CONFIG_ROTATION_IN_LIGHTING is defined, + // then an additional 2-bit block rotation value is stored + // in this field as well. + // + // Encode with STBVOX_MAKE_LIGHTING_EXT(lighting,rot)--here + // 'lighting' should still be 8 bits, as the macro will + // discard the bottom bits automatically. Similarly, if + // using STBVOX_CONFIG_VHEIGHT_IN_LIGHTING, encode with + // STBVOX_MAKE_LIGHTING_EXT(lighting,vheight). + // + // (Rationale: rotation needs to be independent of blocktype, + // but is only 2 bits so doesn't want to be its own array. + // Lighting is the one thing that was likely to already be + // in use and that I could easily steal 2 bits from.) + + stbvox_block_type *blocktype; + // Indexed by 3D coordinate. This is a core "block type" value, which is used + // to index into other arrays; essentially a "palette". This is much more + // memory-efficient and performance-friendly than storing the values explicitly, + // but only makes sense if the values are always synchronized. + // + // If a voxel's blocktype is 0, it is assumed to be empty (STBVOX_GEOM_empty), + // and no other blocktypes should be STBVOX_GEOM_empty. (Only if you do not + // have blocktypes should STBVOX_GEOM_empty ever used.) + // + // Normally it is an unsigned byte, but you can override it to be + // a short if you have too many blocktypes. + + unsigned char *geometry; + // Indexed by 3D coordinate. Contains the geometry type for the block. + // Also contains a 2-bit rotation for how the whole block is rotated. + // Also includes a 2-bit vheight value when using shared vheight values. + // See the separate vheight documentation. + // Encode with STBVOX_MAKE_GEOMETRY(geom, rot, vheight) + + unsigned char *block_geometry; + // Array indexed by blocktype containing the geometry for this block, plus + // a 2-bit "simple rotation". Note rotation has limited use since it's not + // independent of blocktype. + // + // Encode with STBVOX_MAKE_GEOMETRY(geom,simple_rot,0) + + unsigned char *block_tex1; + // Array indexed by blocktype containing the texture id for texture #1. + + unsigned char (*block_tex1_face)[6]; + // Array indexed by blocktype and face containing the texture id for texture #1. + // The N/E/S/W face choices can be rotated by one of the rotation selectors; + // The top & bottom face textures will rotate to match. + // Note that it only makes sense to use one of block_tex1 or block_tex1_face; + // this pattern repeats throughout and this notice is not repeated. + + unsigned char *tex2; + // Indexed by 3D coordinate. Contains the texture id for texture #2 + // to use on all faces of the block. + + unsigned char *block_tex2; + // Array indexed by blocktype containing the texture id for texture #2. + + unsigned char (*block_tex2_face)[6]; + // Array indexed by blocktype and face containing the texture id for texture #2. + // The N/E/S/W face choices can be rotated by one of the rotation selectors; + // The top & bottom face textures will rotate to match. + + unsigned char *color; + // Indexed by 3D coordinate. Contains the color for all faces of the block. + // The core color value is 0..63. + // Encode with STBVOX_MAKE_COLOR(color_number, tex1_enable, tex2_enable) + + unsigned char *block_color; + // Array indexed by blocktype containing the color value to apply to the faces. + // The core color value is 0..63. + // Encode with STBVOX_MAKE_COLOR(color_number, tex1_enable, tex2_enable) + + unsigned char (*block_color_face)[6]; + // Array indexed by blocktype and face containing the color value to apply to that face. + // The core color value is 0..63. + // Encode with STBVOX_MAKE_COLOR(color_number, tex1_enable, tex2_enable) + + unsigned char *block_texlerp; + // Array indexed by blocktype containing 3-bit scalar for texture #2 alpha + // (known throughout as 'texlerp'). This is constant over every face even + // though the property is potentially per-vertex. + + unsigned char (*block_texlerp_face)[6]; + // Array indexed by blocktype and face containing 3-bit scalar for texture #2 alpha. + // This is constant over the face even though the property is potentially per-vertex. + + unsigned char *block_vheight; + // Array indexed by blocktype containing the vheight values for the + // top or bottom face of this block. These will rotate properly if the + // block is rotated. See discussion of vheight. + // Encode with STBVOX_MAKE_VHEIGHT(sw_height, se_height, nw_height, ne_height) + + unsigned char *selector; + // Array indexed by 3D coordinates indicating which output mesh to select. + + unsigned char *block_selector; + // Array indexed by blocktype indicating which output mesh to select. + + unsigned char *side_texrot; + // Array indexed by 3D coordinates encoding 2-bit texture rotations for the + // faces on the E/N/W/S sides of the block. + // Encode with STBVOX_MAKE_SIDE_TEXROT(rot_e, rot_n, rot_w, rot_s) + + unsigned char *block_side_texrot; + // Array indexed by blocktype encoding 2-bit texture rotations for the faces + // on the E/N/W/S sides of the block. + // Encode with STBVOX_MAKE_SIDE_TEXROT(rot_e, rot_n, rot_w, rot_s) + + unsigned char *overlay; // index into palettes listed below + // Indexed by 3D coordinate. If 0, there is no overlay. If non-zero, + // it indexes into to the below arrays and overrides the values + // defined by the blocktype. + + unsigned char (*overlay_tex1)[6]; + // Array indexed by overlay value and face, containing an override value + // for the texture id for texture #1. If 0, the value defined by blocktype + // is used. + + unsigned char (*overlay_tex2)[6]; + // Array indexed by overlay value and face, containing an override value + // for the texture id for texture #2. If 0, the value defined by blocktype + // is used. + + unsigned char (*overlay_color)[6]; + // Array indexed by overlay value and face, containing an override value + // for the face color. If 0, the value defined by blocktype is used. + + unsigned char *overlay_side_texrot; + // Array indexed by overlay value, encoding 2-bit texture rotations for the faces + // on the E/N/W/S sides of the block. + // Encode with STBVOX_MAKE_SIDE_TEXROT(rot_e, rot_n, rot_w, rot_s) + + unsigned char *rotate; + // Indexed by 3D coordinate. Allows independent rotation of several + // parts of the voxel, where by rotation I mean swapping textures + // and colors between E/N/S/W faces. + // Block: rotates anything indexed by blocktype + // Overlay: rotates anything indexed by overlay + // EColor: rotates faces defined in ecolor_facemask + // Encode with STBVOX_MAKE_MATROT(block,overlay,ecolor) + + unsigned char *tex2_for_tex1; + // Array indexed by tex1 containing the texture id for texture #2. + // You can use this if the two are always/almost-always strictly + // correlated (e.g. if tex2 is a detail texture for tex1), as it + // will be more efficient (touching fewer cache lines) than using + // e.g. block_tex2_face. + + unsigned char *tex2_replace; + // Indexed by 3D coordinate. Specifies the texture id for texture #2 + // to use on a single face of the voxel, which must be E/N/W/S (not U/D). + // The texture id is limited to 6 bits unless tex2_facemask is also + // defined (see below). + // Encode with STBVOX_MAKE_TEX2_REPLACE(tex2, face) + + unsigned char *tex2_facemask; + // Indexed by 3D coordinate. Specifies which of the six faces should + // have their tex2 replaced by the value of tex2_replace. In this + // case, all 8 bits of tex2_replace are used as the texture id. + // Encode with STBVOX_MAKE_FACE_MASK(east,north,west,south,up,down) + + unsigned char *extended_color; + // Indexed by 3D coordinate. Specifies a value that indexes into + // the ecolor arrays below (both of which must be defined). + + unsigned char *ecolor_color; + // Indexed by extended_color value, specifies an optional override + // for the color value on some faces. + // Encode with STBVOX_MAKE_COLOR(color_number, tex1_enable, tex2_enable) + + unsigned char *ecolor_facemask; + // Indexed by extended_color value, this specifies which faces the + // color in ecolor_color should be applied to. The faces can be + // independently rotated by the ecolor value of 'rotate', if it exists. + // Encode with STBVOX_MAKE_FACE_MASK(e,n,w,s,u,d) + + unsigned char *color2; + // Indexed by 3D coordinates, specifies an alternative color to apply + // to some of the faces of the block. + // Encode with STBVOX_MAKE_COLOR(color_number, tex1_enable, tex2_enable) + + unsigned char *color2_facemask; + // Indexed by 3D coordinates, specifies which faces should use the + // color defined in color2. No rotation value is applied. + // Encode with STBVOX_MAKE_FACE_MASK(e,n,w,s,u,d) + + unsigned char *color3; + // Indexed by 3D coordinates, specifies an alternative color to apply + // to some of the faces of the block. + // Encode with STBVOX_MAKE_COLOR(color_number, tex1_enable, tex2_enable) + + unsigned char *color3_facemask; + // Indexed by 3D coordinates, specifies which faces should use the + // color defined in color3. No rotation value is applied. + // Encode with STBVOX_MAKE_FACE_MASK(e,n,w,s,u,d) + + unsigned char *texlerp_simple; + // Indexed by 3D coordinates, this is the smallest texlerp encoding + // that can do useful work. It consits of three values: baselerp, + // vertlerp, and face_vertlerp. Baselerp defines the value + // to use on all of the faces but one, from the STBVOX_TEXLERP_BASE + // values. face_vertlerp is one of the 6 face values (or STBVOX_FACE_NONE) + // which specifies the face should use the vertlerp values. + // Vertlerp defines a lerp value at every vertex of the mesh. + // Thus, one face can have per-vertex texlerp values, and those + // values are encoded in the space so that they will be shared + // by adjacent faces that also use vertlerp, allowing continuity + // (this is used for the "texture crossfade" bit of the release video). + // Encode with STBVOX_MAKE_TEXLERP_SIMPLE(baselerp, vertlerp, face_vertlerp) + + // The following texlerp encodings are experimental and maybe not + // that useful. + + unsigned char *texlerp; + // Indexed by 3D coordinates, this defines four values: + // vertlerp is a lerp value at every vertex of the mesh (using STBVOX_TEXLERP_BASE values). + // ud is the value to use on up and down faces, from STBVOX_TEXLERP_FACE values + // ew is the value to use on east and west faces, from STBVOX_TEXLERP_FACE values + // ns is the value to use on north and south faces, from STBVOX_TEXLERP_FACE values + // If any of ud, ew, or ns is STBVOX_TEXLERP_FACE_use_vert, then the + // vertlerp values for the vertices are gathered and used for those faces. + // Encode with STBVOX_MAKE_TEXLERP(vertlerp,ud,ew,sw) + + unsigned short *texlerp_vert3; + // Indexed by 3D coordinates, this works with texlerp and + // provides a unique texlerp value for every direction at + // every vertex. The same rules of whether faces share values + // applies. The STBVOX_TEXLERP_FACE vertlerp value defined in + // texlerp is only used for the down direction. The values at + // each vertex in other directions are defined in this array, + // and each uses the STBVOX_TEXLERP3 values (i.e. full precision + // 3-bit texlerp values). + // Encode with STBVOX_MAKE_VERT3(vertlerp_e,vertlerp_n,vertlerp_w,vertlerp_s,vertlerp_u) + + unsigned short *texlerp_face3; // e:3,n:3,w:3,s:3,u:2,d:2 + // Indexed by 3D coordinates, this provides a compact way to + // fully specify the texlerp value indepenendly for every face, + // but doesn't allow per-vertex variation. E/N/W/S values are + // encoded using STBVOX_TEXLERP3 values, whereas up and down + // use STBVOX_TEXLERP_SIMPLE values. + // Encode with STBVOX_MAKE_FACE3(face_e,face_n,face_w,face_s,face_u,face_d) + + unsigned char *vheight; // STBVOX_MAKE_VHEIGHT -- sw:2, se:2, nw:2, ne:2, doesn't rotate + // Indexed by 3D coordinates, this defines the four + // vheight values to use if the geometry is STBVOX_GEOM_vheight*. + // See the vheight discussion. + + unsigned char *packed_compact; + // Stores block rotation, vheight, and texlerp values: + // block rotation: 2 bits + // vertex vheight: 2 bits + // use_texlerp : 1 bit + // vertex texlerp: 3 bits + // If STBVOX_CONFIG_UP_TEXLERP_PACKED is defined, then 'vertex texlerp' is + // used for up faces if use_texlerp is 1. If STBVOX_CONFIG_DOWN_TEXLERP_PACKED + // is defined, then 'vertex texlerp' is used for down faces if use_texlerp is 1. + // Note if those symbols are defined but packed_compact is NULL, the normal + // texlerp default will be used. + // Encode with STBVOX_MAKE_PACKED_COMPACT(rot, vheight, texlerp, use_texlerp) +}; +// @OPTIMIZE allow specializing; build a single struct with all of the +// 3D-indexed arrays combined so it's AoS instead of SoA for better +// cache efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// VHEIGHT DOCUMENTATION +// +// "vheight" is the internal name for the special block types +// with sloped tops or bottoms. "vheight" stands for "vertex height". +// +// Note that these blocks are very flexible (there are 256 of them, +// although at least 17 of them should never be used), but they +// also have a disadvantage that they generate extra invisible +// faces; the generator does not currently detect whether adjacent +// vheight blocks hide each others sides, so those side faces are +// always generated. For a continuous ground terrain, this means +// that you may generate 5x as many quads as needed. See notes +// on "improvements for shipping products" in the introduction. + +enum +{ + STBVOX_VERTEX_HEIGHT_0, + STBVOX_VERTEX_HEIGHT_half, + STBVOX_VERTEX_HEIGHT_1, + STBVOX_VERTEX_HEIGHT_one_and_a_half, +}; +// These are the "vheight" values. Vheight stands for "vertex height". +// The idea is that for a "floor vheight" block, you take a cube and +// reposition the top-most vertices at various heights as specified by +// the vheight values. Similarly, a "ceiling vheight" block takes a +// cube and repositions the bottom-most vertices. +// +// A floor block only adjusts the top four vertices; the bottom four vertices +// remain at the bottom of the block. The height values are 2 bits, +// measured in halves of a block; so you can specify heights of 0/2, +// 1/2, 2/2, or 3/2. 0 is the bottom of the block, 1 is halfway +// up the block, 2 is the top of the block, and 3 is halfway up the +// next block (and actually outside of the block). The value 3 is +// actually legal for floor vheight (but not ceiling), and allows you to: +// +// (A) have smoother terrain by having slopes that cross blocks, +// e.g. (1,1,3,3) is a regular-seeming slope halfway between blocks +// (B) make slopes steeper than 45-degrees, e.g. (0,0,3,3) +// +// (Because only z coordinates have half-block precision, and x&y are +// limited to block corner precision, it's not possible to make these +// things "properly" out of blocks, e.g. a half-slope block on its side +// or a sloped block halfway between blocks that's made out of two blocks.) +// +// If you define STBVOX_CONFIG_OPTIMIZED_VHEIGHT, then the top face +// (or bottom face for a ceiling vheight block) will be drawn as a +// single quad even if the four vertex heights aren't planar, and a +// single normal will be used over the entire quad. If you +// don't define it, then if the top face is non-planar, it will be +// split into two triangles, each with their own normal/lighting. +// (Note that since all output from stb_voxel_render is quad meshes, +// triangles are actually rendered as degenerate quads.) In this case, +// the distinction between STBVOX_GEOM_floor_vheight_03 and +// STBVOX_GEOM_floor_vheight_12 comes into play; the former introduces +// an edge from the SW to NE corner (i.e. from <0,0,?> to <1,1,?>), +// while the latter introduces an edge from the NW to SE corner +// (i.e. from <0,1,?> to <1,0,?>.) For a "lazy mesh" look, use +// exclusively _03 or _12. For a "classic mesh" look, alternate +// _03 and _12 in a checkerboard pattern. For a "smoothest surface" +// look, choose the edge based on actual vertex heights. +// +// The four vertex heights can come from several places. The simplest +// encoding is to just use the 'vheight' parameter which stores four +// explicit vertex heights for every block. This allows total independence, +// but at the cost of the largest memory usage, 1 byte per 3D block. +// Encode this with STBVOX_MAKE_VHEIGHT(vh_sw, vh_se, vh_nw, vh_ne). +// These coordinates are absolute, not affected by block rotations. +// +// An alternative if you just want to encode some very specific block +// types, not all the possibilities--say you just want half-height slopes, +// so you want (0,0,1,1) and (1,1,2,2)--then you can use block_vheight +// to specify them. The geometry rotation will cause block_vheight values +// to be rotated (because it's as if you're just defining a type of +// block). This value is also encoded with STBVOX_MAKE_VHEIGHT. +// +// If you want to save memory and you're creating a "continuous ground" +// sort of effect, you can make each vertex of the lattice share the +// vheight value; that is, two adjacent blocks that share a vertex will +// always get the same vheight value for that vertex. Then you need to +// store two bits of vheight for every block, which you do by storing it +// as part another data structure. Store the south-west vertex's vheight +// with the block. You can either use the "geometry" mesh variable (it's +// a parameter to STBVOX_MAKE_GEOMETRY) or you can store it in the +// "lighting" mesh variable if you defined STBVOX_CONFIG_VHEIGHT_IN_LIGHTING, +// using STBVOX_MAKE_LIGHTING_EXT(lighting,vheight). +// +// Note that if you start with a 2D height map and generate vheight data from +// it, you don't necessarily store only one value per (x,y) coordinate, +// as the same value may need to be set up at multiple z heights. For +// example, if height(8,8) = 13.5, then you want the block at (8,8,13) +// to store STBVOX_VERTEX_HEIGHT_half, and this will be used by blocks +// at (7,7,13), (8,7,13), (7,8,13), and (8,8,13). However, if you're +// allowing steep slopes, it might be the case that you have a block +// at (7,7,12) which is supposed to stick up to 13.5; that means +// you also need to store STBVOX_VERTEX_HEIGHT_one_and_a_half at (8,8,12). + +enum +{ + STBVOX_TEXLERP_FACE_0, + STBVOX_TEXLERP_FACE_half, + STBVOX_TEXLERP_FACE_1, + STBVOX_TEXLERP_FACE_use_vert, +}; + +enum +{ + STBVOX_TEXLERP_BASE_0, // 0.0 + STBVOX_TEXLERP_BASE_2_7, // 2/7 + STBVOX_TEXLERP_BASE_5_7, // 4/7 + STBVOX_TEXLERP_BASE_1 // 1.0 +}; + +enum +{ + STBVOX_TEXLERP3_0_8, + STBVOX_TEXLERP3_1_8, + STBVOX_TEXLERP3_2_8, + STBVOX_TEXLERP3_3_8, + STBVOX_TEXLERP3_4_8, + STBVOX_TEXLERP3_5_8, + STBVOX_TEXLERP3_6_8, + STBVOX_TEXLERP3_7_8, +}; + +#define STBVOX_FACE_NONE 7 + +#define STBVOX_BLOCKTYPE_EMPTY 0 + +#ifdef STBVOX_BLOCKTYPE_SHORT +#define STBVOX_BLOCKTYPE_HOLE 65535 +#else +#define STBVOX_BLOCKTYPE_HOLE 255 +#endif + +#define STBVOX_MAKE_GEOMETRY(geom, rotate, vheight) ((geom) + (rotate)*16 + (vheight)*64) +#define STBVOX_MAKE_VHEIGHT(v_sw, v_se, v_nw, v_ne) ((v_sw) + (v_se)*4 + (v_nw)*16 + (v_ne)*64) +#define STBVOX_MAKE_MATROT(block, overlay, color) ((block) + (overlay)*4 + (color)*64) +#define STBVOX_MAKE_TEX2_REPLACE(tex2, tex2_replace_face) ((tex2) + ((tex2_replace_face) & 3)*64) +#define STBVOX_MAKE_TEXLERP(ns2, ew2, ud2, vert) ((ew2) + (ns2)*4 + (ud2)*16 + (vert)*64) +#define STBVOX_MAKE_TEXLERP_SIMPLE(baselerp,vert,face) ((vert)*32 + (face)*4 + (baselerp)) +#define STBVOX_MAKE_TEXLERP1(vert,e2,n2,w2,s2,u4,d2) STBVOX_MAKE_TEXLERP(s2, w2, d2, vert) +#define STBVOX_MAKE_TEXLERP2(vert,e2,n2,w2,s2,u4,d2) ((u2)*16 + (n2)*4 + (s2)) +#define STBVOX_MAKE_FACE_MASK(e,n,w,s,u,d) ((e)+(n)*2+(w)*4+(s)*8+(u)*16+(d)*32) +#define STBVOX_MAKE_SIDE_TEXROT(e,n,w,s) ((e)+(n)*4+(w)*16+(s)*64) +#define STBVOX_MAKE_COLOR(color,t1,t2) ((color)+(t1)*64+(t2)*128) +#define STBVOX_MAKE_TEXLERP_VERT3(e,n,w,s,u) ((e)+(n)*8+(w)*64+(s)*512+(u)*4096) +#define STBVOX_MAKE_TEXLERP_FACE3(e,n,w,s,u,d) ((e)+(n)*8+(w)*64+(s)*512+(u)*4096+(d)*16384) +#define STBVOX_MAKE_PACKED_COMPACT(rot, vheight, texlerp, def) ((rot)+4*(vheight)+16*(use)+32*(texlerp)) + +#define STBVOX_MAKE_LIGHTING_EXT(lighting, rot) (((lighting)&~3)+(rot)) +#define STBVOX_MAKE_LIGHTING(lighting) (lighting) + +#ifndef STBVOX_MAX_MESHES +#define STBVOX_MAX_MESHES 2 // opaque & transparent +#endif + +#define STBVOX_MAX_MESH_SLOTS 3 // one vertex & two faces, or two vertex and one face + + +// don't mess with this directly, it's just here so you can +// declare stbvox_mesh_maker on the stack or as a global +struct stbvox_mesh_maker +{ + stbvox_input_description input; + int cur_x, cur_y, cur_z; // last unprocessed voxel if it splits into multiple buffers + int x0,y0,z0,x1,y1,z1; + int x_stride_in_bytes; + int y_stride_in_bytes; + int config_dirty; + int default_mesh; + unsigned int tags; + + int cube_vertex_offset[6][4]; // this allows access per-vertex data stored block-centered (like texlerp, ambient) + int vertex_gather_offset[6][4]; + + int pos_x,pos_y,pos_z; + int full; + + // computed from user input + char *output_cur [STBVOX_MAX_MESHES][STBVOX_MAX_MESH_SLOTS]; + char *output_end [STBVOX_MAX_MESHES][STBVOX_MAX_MESH_SLOTS]; + char *output_buffer[STBVOX_MAX_MESHES][STBVOX_MAX_MESH_SLOTS]; + int output_len [STBVOX_MAX_MESHES][STBVOX_MAX_MESH_SLOTS]; + + // computed from config + int output_size [STBVOX_MAX_MESHES][STBVOX_MAX_MESH_SLOTS]; // per quad + int output_step [STBVOX_MAX_MESHES][STBVOX_MAX_MESH_SLOTS]; // per vertex or per face, depending + int num_mesh_slots; + + float default_tex_scale[128][2]; +}; + +#endif // INCLUDE_STB_VOXEL_RENDER_H + + +#ifdef STB_VOXEL_RENDER_IMPLEMENTATION + +#include +#include +#include // memset + +// have to use our own names to avoid the _MSC_VER path having conflicting type names +#ifndef _MSC_VER + #include + typedef uint16_t stbvox_uint16; + typedef uint32_t stbvox_uint32; +#else + typedef unsigned short stbvox_uint16; + typedef unsigned int stbvox_uint32; +#endif + +#ifdef _MSC_VER + #define STBVOX_NOTUSED(v) (void)(v) +#else + #define STBVOX_NOTUSED(v) (void)sizeof(v) +#endif + + + +#ifndef STBVOX_CONFIG_MODE +#error "Must defined STBVOX_CONFIG_MODE to select the mode" +#endif + +#if defined(STBVOX_CONFIG_ROTATION_IN_LIGHTING) && defined(STBVOX_CONFIG_VHEIGHT_IN_LIGHTING) +#error "Can't store both rotation and vheight in lighting" +#endif + + +// The following are candidate voxel modes. Only modes 0, 1, and 20, and 21 are +// currently implemented. Reducing the storage-per-quad further +// shouldn't improve performance, although obviously it allow you +// to create larger worlds without streaming. +// +// +// ----------- Two textures ----------- -- One texture -- ---- Color only ---- +// Mode: 0 1 2 3 4 5 6 10 11 12 20 21 22 23 24 +// ============================================================================================================ +// uses Tex Buffer n Y Y Y Y Y Y Y Y Y n Y Y Y Y +// bytes per quad 32 20 14 12 10 6 6 8 8 4 32 20 10 6 4 +// non-blocks all all some some some slabs stairs some some none all all slabs slabs none +// tex1 256 256 256 256 256 256 256 256 256 256 n n n n n +// tex2 256 256 256 256 256 256 128 n n n n n n n n +// colors 64 64 64 64 64 64 64 8 n n 2^24 2^24 2^24 2^24 256 +// vertex ao Y Y Y Y Y n n Y Y n Y Y Y n n +// vertex texlerp Y Y Y n n n n - - - - - - - - +// x&y extents 127 127 128 64 64 128 64 64 128 128 127 127 128 128 128 +// z extents 255 255 128 64? 64? 64 64 32 64 128 255 255 128 64 128 + +// not sure why I only wrote down the above "result data" and didn't preserve +// the vertex formats, but here I've tried to reconstruct the designs... +// mode # 3 is wrong, one byte too large, but they may have been an error originally + +// Mode: 0 1 2 3 4 5 6 10 11 12 20 21 22 23 24 +// ============================================================================================================= +// bytes per quad 32 20 14 12 10 6 6 8 8 4 20 10 6 4 +// +// vertex x bits 7 7 0 6 0 0 0 0 0 0 7 0 0 0 +// vertex y bits 7 7 0 0 0 0 0 0 0 0 7 0 0 0 +// vertex z bits 9 9 7 4 2 0 0 2 2 0 9 2 0 0 +// vertex ao bits 6 6 6 6 6 0 0 6 6 0 6 6 0 0 +// vertex txl bits 3 3 3 0 0 0 0 0 0 0 (3) 0 0 0 +// +// face tex1 bits (8) 8 8 8 8 8 8 8 8 8 +// face tex2 bits (8) 8 8 8 8 8 7 - - - +// face color bits (8) 8 8 8 8 8 8 3 0 0 24 24 24 8 +// face normal bits (8) 8 8 8 6 4 7 4 4 3 8 3 4 3 +// face x bits 7 0 6 7 6 6 7 7 0 7 7 7 +// face y bits 7 6 6 7 6 6 7 7 0 7 7 7 +// face z bits 2 2 6 6 6 5 6 7 0 7 6 7 + + +#if STBVOX_CONFIG_MODE==0 || STBVOX_CONFIG_MODE==1 + + #define STBVOX_ICONFIG_VERTEX_32 + #define STBVOX_ICONFIG_FACE1_1 + +#elif STBVOX_CONFIG_MODE==20 || STBVOX_CONFIG_MODE==21 + + #define STBVOX_ICONFIG_VERTEX_32 + #define STBVOX_ICONFIG_FACE1_1 + #define STBVOX_ICONFIG_UNTEXTURED + +#else +#error "Selected value of STBVOX_CONFIG_MODE is not supported" +#endif + +#if STBVOX_CONFIG_MODE==0 || STBVOX_CONFIG_MODE==20 +#define STBVOX_ICONFIG_FACE_ATTRIBUTE +#endif + +#ifndef STBVOX_CONFIG_HLSL +// the fallback if all others are exhausted is GLSL +#define STBVOX_ICONFIG_GLSL +#endif + +#ifdef STBVOX_CONFIG_OPENGL_MODELVIEW +#define STBVOX_ICONFIG_OPENGL_3_1_COMPATIBILITY +#endif + +#if defined(STBVOX_ICONFIG_VERTEX_32) + typedef stbvox_uint32 stbvox_mesh_vertex; + #define stbvox_vertex_encode(x,y,z,ao,texlerp) \ + ((stbvox_uint32) ((x)+((y)<<7)+((z)<<14)+((ao)<<23)+((texlerp)<<29))) +#elif defined(STBVOX_ICONFIG_VERTEX_16_1) // mode=2 + typedef stbvox_uint16 stbvox_mesh_vertex; + #define stbvox_vertex_encode(x,y,z,ao,texlerp) \ + ((stbvox_uint16) ((z)+((ao)<<7)+((texlerp)<<13) +#elif defined(STBVOX_ICONFIG_VERTEX_16_2) // mode=3 + typedef stbvox_uint16 stbvox_mesh_vertex; + #define stbvox_vertex_encode(x,y,z,ao,texlerp) \ + ((stbvox_uint16) ((x)+((z)<<6))+((ao)<<10)) +#elif defined(STBVOX_ICONFIG_VERTEX_8) + typedef stbvox_uint8 stbvox_mesh_vertex; + #define stbvox_vertex_encode(x,y,z,ao,texlerp) \ + ((stbvox_uint8) ((z)+((ao)<<6)) +#else + #error "internal error, no vertex type" +#endif + +#ifdef STBVOX_ICONFIG_FACE1_1 + typedef struct + { + unsigned char tex1,tex2,color,face_info; + } stbvox_mesh_face; +#else + #error "internal error, no face type" +#endif + + +// 20-byte quad format: +// +// per vertex: +// +// x:7 +// y:7 +// z:9 +// ao:6 +// tex_lerp:3 +// +// per face: +// +// tex1:8 +// tex2:8 +// face:8 +// color:8 + + +// Faces: +// +// Faces use the bottom 3 bits to choose the texgen +// mode, and all the bits to choose the normal. +// Thus the bottom 3 bits have to be: +// e, n, w, s, u, d, u, d +// +// These use compact names so tables are readable + +enum +{ + STBVF_e, + STBVF_n, + STBVF_w, + STBVF_s, + STBVF_u, + STBVF_d, + STBVF_eu, + STBVF_ed, + + STBVF_eu_wall, + STBVF_nu_wall, + STBVF_wu_wall, + STBVF_su_wall, + STBVF_ne_u, + STBVF_ne_d, + STBVF_nu, + STBVF_nd, + + STBVF_ed_wall, + STBVF_nd_wall, + STBVF_wd_wall, + STBVF_sd_wall, + STBVF_nw_u, + STBVF_nw_d, + STBVF_wu, + STBVF_wd, + + STBVF_ne_u_cross, + STBVF_nw_u_cross, + STBVF_sw_u_cross, + STBVF_se_u_cross, + STBVF_sw_u, + STBVF_sw_d, + STBVF_su, + STBVF_sd, + + // @TODO we need more than 5 bits to encode the normal to fit the following + // so for now we use the right projection but the wrong normal + STBVF_se_u = STBVF_su, + STBVF_se_d = STBVF_sd, + + STBVF_count, +}; + +///////////////////////////////////////////////////////////////////////////// +// +// tables -- i'd prefer if these were at the end of the file, but: C++ +// + +static float stbvox_default_texgen[2][32][3] = +{ + { { 0, 1,0 }, { 0, 0, 1 }, { 0,-1,0 }, { 0, 0,-1 }, + { -1, 0,0 }, { 0, 0, 1 }, { 1, 0,0 }, { 0, 0,-1 }, + { 0,-1,0 }, { 0, 0, 1 }, { 0, 1,0 }, { 0, 0,-1 }, + { 1, 0,0 }, { 0, 0, 1 }, { -1, 0,0 }, { 0, 0,-1 }, + + { 1, 0,0 }, { 0, 1, 0 }, { -1, 0,0 }, { 0,-1, 0 }, + { -1, 0,0 }, { 0,-1, 0 }, { 1, 0,0 }, { 0, 1, 0 }, + { 1, 0,0 }, { 0, 1, 0 }, { -1, 0,0 }, { 0,-1, 0 }, + { -1, 0,0 }, { 0,-1, 0 }, { 1, 0,0 }, { 0, 1, 0 }, + }, + { { 0, 0,-1 }, { 0, 1,0 }, { 0, 0, 1 }, { 0,-1,0 }, + { 0, 0,-1 }, { -1, 0,0 }, { 0, 0, 1 }, { 1, 0,0 }, + { 0, 0,-1 }, { 0,-1,0 }, { 0, 0, 1 }, { 0, 1,0 }, + { 0, 0,-1 }, { 1, 0,0 }, { 0, 0, 1 }, { -1, 0,0 }, + + { 0,-1, 0 }, { 1, 0,0 }, { 0, 1, 0 }, { -1, 0,0 }, + { 0, 1, 0 }, { -1, 0,0 }, { 0,-1, 0 }, { 1, 0,0 }, + { 0,-1, 0 }, { 1, 0,0 }, { 0, 1, 0 }, { -1, 0,0 }, + { 0, 1, 0 }, { -1, 0,0 }, { 0,-1, 0 }, { 1, 0,0 }, + }, +}; + +#define STBVOX_RSQRT2 0.7071067811865f +#define STBVOX_RSQRT3 0.5773502691896f + +static float stbvox_default_normals[32][3] = +{ + { 1,0,0 }, // east + { 0,1,0 }, // north + { -1,0,0 }, // west + { 0,-1,0 }, // south + { 0,0,1 }, // up + { 0,0,-1 }, // down + { STBVOX_RSQRT2,0, STBVOX_RSQRT2 }, // east & up + { STBVOX_RSQRT2,0, -STBVOX_RSQRT2 }, // east & down + + { STBVOX_RSQRT2,0, STBVOX_RSQRT2 }, // east & up + { 0, STBVOX_RSQRT2, STBVOX_RSQRT2 }, // north & up + { -STBVOX_RSQRT2,0, STBVOX_RSQRT2 }, // west & up + { 0,-STBVOX_RSQRT2, STBVOX_RSQRT2 }, // south & up + { STBVOX_RSQRT3, STBVOX_RSQRT3, STBVOX_RSQRT3 }, // ne & up + { STBVOX_RSQRT3, STBVOX_RSQRT3,-STBVOX_RSQRT3 }, // ne & down + { 0, STBVOX_RSQRT2, STBVOX_RSQRT2 }, // north & up + { 0, STBVOX_RSQRT2, -STBVOX_RSQRT2 }, // north & down + + { STBVOX_RSQRT2,0, -STBVOX_RSQRT2 }, // east & down + { 0, STBVOX_RSQRT2, -STBVOX_RSQRT2 }, // north & down + { -STBVOX_RSQRT2,0, -STBVOX_RSQRT2 }, // west & down + { 0,-STBVOX_RSQRT2, -STBVOX_RSQRT2 }, // south & down + { -STBVOX_RSQRT3, STBVOX_RSQRT3, STBVOX_RSQRT3 }, // NW & up + { -STBVOX_RSQRT3, STBVOX_RSQRT3,-STBVOX_RSQRT3 }, // NW & down + { -STBVOX_RSQRT2,0, STBVOX_RSQRT2 }, // west & up + { -STBVOX_RSQRT2,0, -STBVOX_RSQRT2 }, // west & down + + { STBVOX_RSQRT3, STBVOX_RSQRT3,STBVOX_RSQRT3 }, // NE & up crossed + { -STBVOX_RSQRT3, STBVOX_RSQRT3,STBVOX_RSQRT3 }, // NW & up crossed + { -STBVOX_RSQRT3,-STBVOX_RSQRT3,STBVOX_RSQRT3 }, // SW & up crossed + { STBVOX_RSQRT3,-STBVOX_RSQRT3,STBVOX_RSQRT3 }, // SE & up crossed + { -STBVOX_RSQRT3,-STBVOX_RSQRT3, STBVOX_RSQRT3 }, // SW & up + { -STBVOX_RSQRT3,-STBVOX_RSQRT3,-STBVOX_RSQRT3 }, // SW & up + { 0,-STBVOX_RSQRT2, STBVOX_RSQRT2 }, // south & up + { 0,-STBVOX_RSQRT2, -STBVOX_RSQRT2 }, // south & down +}; + +static float stbvox_default_texscale[128][4] = +{ + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, + {1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0},{1,1,0,0}, +}; + +static unsigned char stbvox_default_palette_compact[64][3] = +{ + { 255,255,255 }, { 238,238,238 }, { 221,221,221 }, { 204,204,204 }, + { 187,187,187 }, { 170,170,170 }, { 153,153,153 }, { 136,136,136 }, + { 119,119,119 }, { 102,102,102 }, { 85, 85, 85 }, { 68, 68, 68 }, + { 51, 51, 51 }, { 34, 34, 34 }, { 17, 17, 17 }, { 0, 0, 0 }, + { 255,240,240 }, { 255,220,220 }, { 255,160,160 }, { 255, 32, 32 }, + { 200,120,160 }, { 200, 60,150 }, { 220,100,130 }, { 255, 0,128 }, + { 240,240,255 }, { 220,220,255 }, { 160,160,255 }, { 32, 32,255 }, + { 120,160,200 }, { 60,150,200 }, { 100,130,220 }, { 0,128,255 }, + { 240,255,240 }, { 220,255,220 }, { 160,255,160 }, { 32,255, 32 }, + { 160,200,120 }, { 150,200, 60 }, { 130,220,100 }, { 128,255, 0 }, + { 255,255,240 }, { 255,255,220 }, { 220,220,180 }, { 255,255, 32 }, + { 200,160,120 }, { 200,150, 60 }, { 220,130,100 }, { 255,128, 0 }, + { 255,240,255 }, { 255,220,255 }, { 220,180,220 }, { 255, 32,255 }, + { 160,120,200 }, { 150, 60,200 }, { 130,100,220 }, { 128, 0,255 }, + { 240,255,255 }, { 220,255,255 }, { 180,220,220 }, { 32,255,255 }, + { 120,200,160 }, { 60,200,150 }, { 100,220,130 }, { 0,255,128 }, +}; + +static float stbvox_default_ambient[4][4] = +{ + { 0,0,1 ,0 }, // reversed lighting direction + { 0.5,0.5,0.5,0 }, // directional color + { 0.5,0.5,0.5,0 }, // constant color + { 0.5,0.5,0.5,1.0f/1000.0f/1000.0f }, // fog data for simple_fog +}; + +static float stbvox_default_palette[64][4]; + +static void stbvox_build_default_palette(void) +{ + int i; + for (i=0; i < 64; ++i) { + stbvox_default_palette[i][0] = stbvox_default_palette_compact[i][0] / 255.0f; + stbvox_default_palette[i][1] = stbvox_default_palette_compact[i][1] / 255.0f; + stbvox_default_palette[i][2] = stbvox_default_palette_compact[i][2] / 255.0f; + stbvox_default_palette[i][3] = 1.0f; + } +} + +////////////////////////////////////////////////////////////////////////////// +// +// Shaders +// + +#if defined(STBVOX_ICONFIG_OPENGL_3_1_COMPATIBILITY) + #define STBVOX_SHADER_VERSION "#version 150 compatibility\n" +#elif defined(STBVOX_ICONFIG_OPENGL_3_0) + #define STBVOX_SHADER_VERSION "#version 130\n" +#elif defined(STBVOX_ICONFIG_GLSL) + #define STBVOX_SHADER_VERSION "#version 150\n" +#else + #define STBVOX_SHADER_VERSION "" +#endif + +static const char *stbvox_vertex_program = +{ + STBVOX_SHADER_VERSION + + #ifdef STBVOX_ICONFIG_FACE_ATTRIBUTE // NOT TAG_face_sampled + "in uvec4 attr_face;\n" + #else + "uniform usamplerBuffer facearray;\n" + #endif + + #ifdef STBVOX_ICONFIG_FACE_ARRAY_2 + "uniform usamplerBuffer facearray2;\n" + #endif + + // vertex input data + "in uint attr_vertex;\n" + + // per-buffer data + "uniform vec3 transform[3];\n" + + // per-frame data + "uniform vec4 camera_pos;\n" // 4th value is used for arbitrary hacking + + // to simplify things, we avoid using more than 256 uniform vectors + // in fragment shader to avoid possible 1024 component limit, so + // we access this table in the fragment shader. + "uniform vec3 normal_table[32];\n" + + #ifndef STBVOX_CONFIG_OPENGL_MODELVIEW + "uniform mat4x4 model_view;\n" + #endif + + // fragment output data + "flat out uvec4 facedata;\n" + " out vec3 voxelspace_pos;\n" + " out vec3 vnormal;\n" + " out float texlerp;\n" + " out float amb_occ;\n" + + // @TODO handle the HLSL way to do this + "void main()\n" + "{\n" + #ifdef STBVOX_ICONFIG_FACE_ATTRIBUTE + " facedata = attr_face;\n" + #else + " int faceID = gl_VertexID >> 2;\n" + " facedata = texelFetch(facearray, faceID);\n" + #endif + + // extract data for vertex + " vec3 offset;\n" + " offset.x = float( (attr_vertex ) & 127u );\n" // a[0..6] + " offset.y = float( (attr_vertex >> 7u) & 127u );\n" // a[7..13] + " offset.z = float( (attr_vertex >> 14u) & 511u );\n" // a[14..22] + " amb_occ = float( (attr_vertex >> 23u) & 63u ) / 63.0;\n" // a[23..28] + " texlerp = float( (attr_vertex >> 29u) ) / 7.0;\n" // a[29..31] + + " vnormal = normal_table[(facedata.w>>2u) & 31u];\n" + " voxelspace_pos = offset * transform[0];\n" // mesh-to-object scale + " vec3 position = voxelspace_pos + transform[1];\n" // mesh-to-object translate + + #ifdef STBVOX_DEBUG_TEST_NORMALS + " if ((facedata.w & 28u) == 16u || (facedata.w & 28u) == 24u)\n" + " position += vnormal.xyz * camera_pos.w;\n" + #endif + + #ifndef STBVOX_CONFIG_OPENGL_MODELVIEW + " gl_Position = model_view * vec4(position,1.0);\n" + #else + " gl_Position = gl_ModelViewProjectionMatrix * vec4(position,1.0);\n" + #endif + + "}\n" +}; + + +static const char *stbvox_fragment_program = +{ + STBVOX_SHADER_VERSION + + // rlerp is lerp but with t on the left, like god intended + #if defined(STBVOX_ICONFIG_GLSL) + "#define rlerp(t,x,y) mix(x,y,t)\n" + #elif defined(STBVOX_CONFIG_HLSL) + "#define rlerp(t,x,y) lerp(x,y,t)\n" + #else + #error "need definition of rlerp()" + #endif + + + // vertex-shader output data + "flat in uvec4 facedata;\n" + " in vec3 voxelspace_pos;\n" + " in vec3 vnormal;\n" + " in float texlerp;\n" + " in float amb_occ;\n" + + // per-buffer data + "uniform vec3 transform[3];\n" + + // per-frame data + "uniform vec4 camera_pos;\n" // 4th value is used for arbitrary hacking + + // probably constant data + "uniform vec4 ambient[4];\n" + + #ifndef STBVOX_ICONFIG_UNTEXTURED + // generally constant data + "uniform sampler2DArray tex_array[2];\n" + + #ifdef STBVOX_CONFIG_PREFER_TEXBUFFER + "uniform samplerBuffer color_table;\n" + "uniform samplerBuffer texscale;\n" + "uniform samplerBuffer texgen;\n" + #else + "uniform vec4 color_table[64];\n" + "uniform vec4 texscale[64];\n" // instead of 128, to avoid running out of uniforms + "uniform vec3 texgen[64];\n" + #endif + #endif + + "out vec4 outcolor;\n" + + #if defined(STBVOX_CONFIG_LIGHTING) || defined(STBVOX_CONFIG_LIGHTING_SIMPLE) + "vec3 compute_lighting(vec3 pos, vec3 norm, vec3 albedo, vec3 ambient);\n" + #endif + #if defined(STBVOX_CONFIG_FOG) || defined(STBVOX_CONFIG_FOG_SMOOTHSTEP) + "vec3 compute_fog(vec3 color, vec3 relative_pos, float fragment_alpha);\n" + #endif + + "void main()\n" + "{\n" + " vec3 albedo;\n" + " float fragment_alpha;\n" + + #ifndef STBVOX_ICONFIG_UNTEXTURED + // unpack the values + " uint tex1_id = facedata.x;\n" + " uint tex2_id = facedata.y;\n" + " uint texprojid = facedata.w & 31u;\n" + " uint color_id = facedata.z;\n" + + #ifndef STBVOX_CONFIG_PREFER_TEXBUFFER + // load from uniforms / texture buffers + " vec3 texgen_s = texgen[texprojid];\n" + " vec3 texgen_t = texgen[texprojid+32u];\n" + " float tex1_scale = texscale[tex1_id & 63u].x;\n" + " vec4 color = color_table[color_id & 63u];\n" + #ifndef STBVOX_CONFIG_DISABLE_TEX2 + " vec4 tex2_props = texscale[tex2_id & 63u];\n" + #endif + #else + " vec3 texgen_s = texelFetch(texgen, int(texprojid)).xyz;\n" + " vec3 texgen_t = texelFetch(texgen, int(texprojid+32u)).xyz;\n" + " float tex1_scale = texelFetch(texscale, int(tex1_id & 127u)).x;\n" + " vec4 color = texelFetch(color_table, int(color_id & 63u));\n" + #ifndef STBVOX_CONFIG_DISABLE_TEX2 + " vec4 tex2_props = texelFetch(texscale, int(tex1_id & 127u));\n" + #endif + #endif + + #ifndef STBVOX_CONFIG_DISABLE_TEX2 + " float tex2_scale = tex2_props.y;\n" + " bool texblend_mode = tex2_props.z != 0.0;\n" + #endif + " vec2 texcoord;\n" + " vec3 texturespace_pos = voxelspace_pos + transform[2].xyz;\n" + " texcoord.s = dot(texturespace_pos, texgen_s);\n" + " texcoord.t = dot(texturespace_pos, texgen_t);\n" + + " vec2 texcoord_1 = tex1_scale * texcoord;\n" + #ifndef STBVOX_CONFIG_DISABLE_TEX2 + " vec2 texcoord_2 = tex2_scale * texcoord;\n" + #endif + + #ifdef STBVOX_CONFIG_TEX1_EDGE_CLAMP + " texcoord_1 = texcoord_1 - floor(texcoord_1);\n" + " vec4 tex1 = textureGrad(tex_array[0], vec3(texcoord_1, float(tex1_id)), dFdx(tex1_scale*texcoord), dFdy(tex1_scale*texcoord));\n" + #else + " vec4 tex1 = texture(tex_array[0], vec3(texcoord_1, float(tex1_id)));\n" + #endif + + #ifndef STBVOX_CONFIG_DISABLE_TEX2 + #ifdef STBVOX_CONFIG_TEX2_EDGE_CLAMP + " texcoord_2 = texcoord_2 - floor(texcoord_2);\n" + " vec4 tex2 = textureGrad(tex_array[0], vec3(texcoord_2, float(tex2_id)), dFdx(tex2_scale*texcoord), dFdy(tex2_scale*texcoord));\n" + #else + " vec4 tex2 = texture(tex_array[1], vec3(texcoord_2, float(tex2_id)));\n" + #endif + #endif + + " bool emissive = (color.a > 1.0);\n" + " color.a = min(color.a, 1.0);\n" + + // recolor textures + " if ((color_id & 64u) != 0u) tex1.rgba *= color.rgba;\n" + " fragment_alpha = tex1.a;\n" + #ifndef STBVOX_CONFIG_DISABLE_TEX2 + " if ((color_id & 128u) != 0u) tex2.rgba *= color.rgba;\n" + + #ifdef STBVOX_CONFIG_PREMULTIPLIED_ALPHA + " tex2.rgba *= texlerp;\n" + #else + " tex2.a *= texlerp;\n" + #endif + + " if (texblend_mode)\n" + " albedo = tex1.xyz * rlerp(tex2.a, vec3(1.0,1.0,1.0), 2.0*tex2.xyz);\n" + " else {\n" + #ifdef STBVOX_CONFIG_PREMULTIPLIED_ALPHA + " albedo = (1.0-tex2.a)*tex1.xyz + tex2.xyz;\n" + #else + " albedo = rlerp(tex2.a, tex1.xyz, tex2.xyz);\n" + #endif + " fragment_alpha = tex1.a*(1-tex2.a)+tex2.a;\n" + " }\n" + #else + " albedo = tex1.xyz;\n" + #endif + + #else // UNTEXTURED + " vec4 color;" + " color.xyz = vec3(facedata.xyz) / 255.0;\n" + " bool emissive = false;\n" + " albedo = color.xyz;\n" + " fragment_alpha = 1.0;\n" + #endif + + #ifdef STBVOX_ICONFIG_VARYING_VERTEX_NORMALS + // currently, there are no modes that trigger this path; idea is that there + // could be a couple of bits per vertex to perturb the normal to e.g. get curved look + " vec3 normal = normalize(vnormal);\n" + #else + " vec3 normal = vnormal;\n" + #endif + + " vec3 ambient_color = dot(normal, ambient[0].xyz) * ambient[1].xyz + ambient[2].xyz;\n" + + " ambient_color = clamp(ambient_color, 0.0, 1.0);" + " ambient_color *= amb_occ;\n" + + " vec3 lit_color;\n" + " if (!emissive)\n" + #if defined(STBVOX_ICONFIG_LIGHTING) || defined(STBVOX_CONFIG_LIGHTING_SIMPLE) + " lit_color = compute_lighting(voxelspace_pos + transform[1], normal, albedo, ambient_color);\n" + #else + " lit_color = albedo * ambient_color ;\n" + #endif + " else\n" + " lit_color = albedo;\n" + + #if defined(STBVOX_ICONFIG_FOG) || defined(STBVOX_CONFIG_FOG_SMOOTHSTEP) + " vec3 dist = voxelspace_pos + (transform[1] - camera_pos.xyz);\n" + " lit_color = compute_fog(lit_color, dist, fragment_alpha);\n" + #endif + + #ifdef STBVOX_CONFIG_UNPREMULTIPLY + " vec4 final_color = vec4(lit_color/fragment_alpha, fragment_alpha);\n" + #else + " vec4 final_color = vec4(lit_color, fragment_alpha);\n" + #endif + " outcolor = final_color;\n" + "}\n" + + #ifdef STBVOX_CONFIG_LIGHTING_SIMPLE + "\n" + "uniform vec3 light_source[2];\n" + "vec3 compute_lighting(vec3 pos, vec3 norm, vec3 albedo, vec3 ambient)\n" + "{\n" + " vec3 light_dir = light_source[0] - pos;\n" + " float lambert = dot(light_dir, norm) / dot(light_dir, light_dir);\n" + " vec3 diffuse = clamp(light_source[1] * clamp(lambert, 0.0, 1.0), 0.0, 1.0);\n" + " return (diffuse + ambient) * albedo;\n" + "}\n" + #endif + + #ifdef STBVOX_CONFIG_FOG_SMOOTHSTEP + "\n" + "vec3 compute_fog(vec3 color, vec3 relative_pos, float fragment_alpha)\n" + "{\n" + " float f = dot(relative_pos,relative_pos)*ambient[3].w;\n" + //" f = rlerp(f, -2,1);\n" + " f = clamp(f, 0.0, 1.0);\n" + " f = 3.0*f*f - 2.0*f*f*f;\n" // smoothstep + //" f = f*f;\n" // fade in more smoothly + #ifdef STBVOX_CONFIG_PREMULTIPLIED_ALPHA + " return rlerp(f, color.xyz, ambient[3].xyz*fragment_alpha);\n" + #else + " return rlerp(f, color.xyz, ambient[3].xyz);\n" + #endif + "}\n" + #endif +}; + + +// still requires full alpha lookups, including tex2 if texblend is enabled +static const char *stbvox_fragment_program_alpha_only = +{ + STBVOX_SHADER_VERSION + + // vertex-shader output data + "flat in uvec4 facedata;\n" + " in vec3 voxelspace_pos;\n" + " in float texlerp;\n" + + // per-buffer data + "uniform vec3 transform[3];\n" + + #ifndef STBVOX_ICONFIG_UNTEXTURED + // generally constant data + "uniform sampler2DArray tex_array[2];\n" + + #ifdef STBVOX_CONFIG_PREFER_TEXBUFFER + "uniform samplerBuffer texscale;\n" + "uniform samplerBuffer texgen;\n" + #else + "uniform vec4 texscale[64];\n" // instead of 128, to avoid running out of uniforms + "uniform vec3 texgen[64];\n" + #endif + #endif + + "out vec4 outcolor;\n" + + "void main()\n" + "{\n" + " vec3 albedo;\n" + " float fragment_alpha;\n" + + #ifndef STBVOX_ICONFIG_UNTEXTURED + // unpack the values + " uint tex1_id = facedata.x;\n" + " uint tex2_id = facedata.y;\n" + " uint texprojid = facedata.w & 31u;\n" + " uint color_id = facedata.z;\n" + + #ifndef STBVOX_CONFIG_PREFER_TEXBUFFER + // load from uniforms / texture buffers + " vec3 texgen_s = texgen[texprojid];\n" + " vec3 texgen_t = texgen[texprojid+32u];\n" + " float tex1_scale = texscale[tex1_id & 63u].x;\n" + " vec4 color = color_table[color_id & 63u];\n" + " vec4 tex2_props = texscale[tex2_id & 63u];\n" + #else + " vec3 texgen_s = texelFetch(texgen, int(texprojid)).xyz;\n" + " vec3 texgen_t = texelFetch(texgen, int(texprojid+32u)).xyz;\n" + " float tex1_scale = texelFetch(texscale, int(tex1_id & 127u)).x;\n" + " vec4 color = texelFetch(color_table, int(color_id & 63u));\n" + " vec4 tex2_props = texelFetch(texscale, int(tex2_id & 127u));\n" + #endif + + #ifndef STBVOX_CONFIG_DISABLE_TEX2 + " float tex2_scale = tex2_props.y;\n" + " bool texblend_mode = tex2_props.z &((facedata.w & 128u) != 0u);\n" + #endif + + " color.a = min(color.a, 1.0);\n" + + " vec2 texcoord;\n" + " vec3 texturespace_pos = voxelspace_pos + transform[2].xyz;\n" + " texcoord.s = dot(texturespace_pos, texgen_s);\n" + " texcoord.t = dot(texturespace_pos, texgen_t);\n" + + " vec2 texcoord_1 = tex1_scale * texcoord;\n" + " vec2 texcoord_2 = tex2_scale * texcoord;\n" + + #ifdef STBVOX_CONFIG_TEX1_EDGE_CLAMP + " texcoord_1 = texcoord_1 - floor(texcoord_1);\n" + " vec4 tex1 = textureGrad(tex_array[0], vec3(texcoord_1, float(tex1_id)), dFdx(tex1_scale*texcoord), dFdy(tex1_scale*texcoord));\n" + #else + " vec4 tex1 = texture(tex_array[0], vec3(texcoord_1, float(tex1_id)));\n" + #endif + + " if ((color_id & 64u) != 0u) tex1.a *= color.a;\n" + " fragment_alpha = tex1.a;\n" + + #ifndef STBVOX_CONFIG_DISABLE_TEX2 + " if (!texblend_mode) {\n" + #ifdef STBVOX_CONFIG_TEX2_EDGE_CLAMP + " texcoord_2 = texcoord_2 - floor(texcoord_2);\n" + " vec4 tex2 = textureGrad(tex_array[0], vec3(texcoord_2, float(tex2_id)), dFdx(tex2_scale*texcoord), dFdy(tex2_scale*texcoord));\n" + #else + " vec4 tex2 = texture(tex_array[1], vec3(texcoord_2, float(tex2_id)));\n" + #endif + + " tex2.a *= texlerp;\n" + " if ((color_id & 128u) != 0u) tex2.rgba *= color.a;\n" + " fragment_alpha = tex1.a*(1-tex2.a)+tex2.a;\n" + "}\n" + "\n" + #endif + + #else // UNTEXTURED + " fragment_alpha = 1.0;\n" + #endif + + " outcolor = vec4(0.0, 0.0, 0.0, fragment_alpha);\n" + "}\n" +}; + + +STBVXDEC char *stbvox_get_vertex_shader(void) +{ + return (char *) stbvox_vertex_program; +} + +STBVXDEC char *stbvox_get_fragment_shader(void) +{ + return (char *) stbvox_fragment_program; +} + +STBVXDEC char *stbvox_get_fragment_shader_alpha_only(void) +{ + return (char *) stbvox_fragment_program_alpha_only; +} + +static float stbvox_dummy_transform[3][3]; + +#ifdef STBVOX_CONFIG_PREFER_TEXBUFFER +#define STBVOX_TEXBUF 1 +#else +#define STBVOX_TEXBUF 0 +#endif + +static stbvox_uniform_info stbvox_uniforms[] = +{ + { STBVOX_UNIFORM_TYPE_sampler , 4, 1, (char*) "facearray" , 0 }, + { STBVOX_UNIFORM_TYPE_vec3 , 12, 3, (char*) "transform" , stbvox_dummy_transform[0] }, + { STBVOX_UNIFORM_TYPE_sampler , 4, 2, (char*) "tex_array" , 0 }, + { STBVOX_UNIFORM_TYPE_vec4 , 16, 128, (char*) "texscale" , stbvox_default_texscale[0] , STBVOX_TEXBUF }, + { STBVOX_UNIFORM_TYPE_vec4 , 16, 64, (char*) "color_table" , stbvox_default_palette[0] , STBVOX_TEXBUF }, + { STBVOX_UNIFORM_TYPE_vec3 , 12, 32, (char*) "normal_table" , stbvox_default_normals[0] }, + { STBVOX_UNIFORM_TYPE_vec3 , 12, 64, (char*) "texgen" , stbvox_default_texgen[0][0], STBVOX_TEXBUF }, + { STBVOX_UNIFORM_TYPE_vec4 , 16, 4, (char*) "ambient" , stbvox_default_ambient[0] }, + { STBVOX_UNIFORM_TYPE_vec4 , 16, 1, (char*) "camera_pos" , stbvox_dummy_transform[0] }, +}; + +STBVXDEC int stbvox_get_uniform_info(stbvox_uniform_info *info, int uniform) +{ + if (uniform < 0 || uniform >= STBVOX_UNIFORM_count) + return 0; + + *info = stbvox_uniforms[uniform]; + return 1; +} + +#define STBVOX_GET_GEO(geom_data) ((geom_data) & 15) + +typedef struct +{ + unsigned char block:2; + unsigned char overlay:2; + unsigned char facerot:2; + unsigned char ecolor:2; +} stbvox_rotate; + +typedef struct +{ + unsigned char x,y,z; +} stbvox_pos; + +static unsigned char stbvox_rotate_face[6][4] = +{ + { 0,1,2,3 }, + { 1,2,3,0 }, + { 2,3,0,1 }, + { 3,0,1,2 }, + { 4,4,4,4 }, + { 5,5,5,5 }, +}; + +#define STBVOX_ROTATE(x,r) stbvox_rotate_face[x][r] // (((x)+(r))&3) + +stbvox_mesh_face stbvox_compute_mesh_face_value(stbvox_mesh_maker *mm, stbvox_rotate rot, int face, int v_off, int normal) +{ + stbvox_mesh_face face_data = { 0 }; + stbvox_block_type bt = mm->input.blocktype[v_off]; + unsigned char bt_face = STBVOX_ROTATE(face, rot.block); + int facerot = rot.facerot; + + #ifdef STBVOX_ICONFIG_UNTEXTURED + if (mm->input.rgb) { + face_data.tex1 = mm->input.rgb[v_off].r; + face_data.tex2 = mm->input.rgb[v_off].g; + face_data.color = mm->input.rgb[v_off].b; + face_data.face_info = (normal<<2); + return face_data; + } + #else + unsigned char color_face; + + if (mm->input.color) + face_data.color = mm->input.color[v_off]; + + if (mm->input.block_tex1) + face_data.tex1 = mm->input.block_tex1[bt]; + else if (mm->input.block_tex1_face) + face_data.tex1 = mm->input.block_tex1_face[bt][bt_face]; + else + face_data.tex1 = bt; + + if (mm->input.block_tex2) + face_data.tex2 = mm->input.block_tex2[bt]; + else if (mm->input.block_tex2_face) + face_data.tex2 = mm->input.block_tex2_face[bt][bt_face]; + + if (mm->input.block_color) { + unsigned char mcol = mm->input.block_color[bt]; + if (mcol) + face_data.color = mcol; + } else if (mm->input.block_color_face) { + unsigned char mcol = mm->input.block_color_face[bt][bt_face]; + if (mcol) + face_data.color = mcol; + } + + if (face <= STBVOX_FACE_south) { + if (mm->input.side_texrot) + facerot = mm->input.side_texrot[v_off] >> (2 * face); + else if (mm->input.block_side_texrot) + facerot = mm->input.block_side_texrot[v_off] >> (2 * bt_face); + } + + if (mm->input.overlay) { + int over_face = STBVOX_ROTATE(face, rot.overlay); + unsigned char over = mm->input.overlay[v_off]; + if (over) { + if (mm->input.overlay_tex1) { + unsigned char rep1 = mm->input.overlay_tex1[over][over_face]; + if (rep1) + face_data.tex1 = rep1; + } + if (mm->input.overlay_tex2) { + unsigned char rep2 = mm->input.overlay_tex2[over][over_face]; + if (rep2) + face_data.tex2 = rep2; + } + if (mm->input.overlay_color) { + unsigned char rep3 = mm->input.overlay_color[over][over_face]; + if (rep3) + face_data.color = rep3; + } + + if (mm->input.overlay_side_texrot && face <= STBVOX_FACE_south) + facerot = mm->input.overlay_side_texrot[over] >> (2*over_face); + } + } + + if (mm->input.tex2_for_tex1) + face_data.tex2 = mm->input.tex2_for_tex1[face_data.tex1]; + if (mm->input.tex2) + face_data.tex2 = mm->input.tex2[v_off]; + if (mm->input.tex2_replace) { + if (mm->input.tex2_facemask[v_off] & (1 << face)) + face_data.tex2 = mm->input.tex2_replace[v_off]; + } + + color_face = STBVOX_ROTATE(face, rot.ecolor); + if (mm->input.extended_color) { + unsigned char ec = mm->input.extended_color[v_off]; + if (mm->input.ecolor_facemask[ec] & (1 << color_face)) + face_data.color = mm->input.ecolor_color[ec]; + } + + if (mm->input.color2) { + if (mm->input.color2_facemask[v_off] & (1 << color_face)) + face_data.color = mm->input.color2[v_off]; + if (mm->input.color3 && (mm->input.color3_facemask[v_off] & (1 << color_face))) + face_data.color = mm->input.color3[v_off]; + } + #endif + + face_data.face_info = (normal<<2) + facerot; + return face_data; +} + +// these are the types of faces each block can have +enum +{ + STBVOX_FT_none , + STBVOX_FT_upper , + STBVOX_FT_lower , + STBVOX_FT_solid , + STBVOX_FT_diag_012, + STBVOX_FT_diag_023, + STBVOX_FT_diag_013, + STBVOX_FT_diag_123, + STBVOX_FT_force , // can't be covered up, used for internal faces, also hides nothing + STBVOX_FT_partial , // only covered by solid, never covers anything else + + STBVOX_FT_count +}; + +static unsigned char stbvox_face_lerp[6] = { 0,2,0,2,4,4 }; +static unsigned char stbvox_vert3_lerp[5] = { 0,3,6,9,12 }; +static unsigned char stbvox_vert_lerp_for_face_lerp[4] = { 0, 4, 7, 7 }; +static unsigned char stbvox_face3_lerp[6] = { 0,3,6,9,12,14 }; +static unsigned char stbvox_vert_lerp_for_simple[4] = { 0,2,5,7 }; +static unsigned char stbvox_face3_updown[8] = { 0,2,5,7,0,2,5,7 }; // ignore top bit + +// vertex offsets for face vertices +static unsigned char stbvox_vertex_vector[6][4][3] = +{ + { { 1,0,1 }, { 1,1,1 }, { 1,1,0 }, { 1,0,0 } }, // east + { { 1,1,1 }, { 0,1,1 }, { 0,1,0 }, { 1,1,0 } }, // north + { { 0,1,1 }, { 0,0,1 }, { 0,0,0 }, { 0,1,0 } }, // west + { { 0,0,1 }, { 1,0,1 }, { 1,0,0 }, { 0,0,0 } }, // south + { { 0,1,1 }, { 1,1,1 }, { 1,0,1 }, { 0,0,1 } }, // up + { { 0,0,0 }, { 1,0,0 }, { 1,1,0 }, { 0,1,0 } }, // down +}; + +// stbvox_vertex_vector, but read coordinates as binary numbers, zyx +static unsigned char stbvox_vertex_selector[6][4] = +{ + { 5,7,3,1 }, + { 7,6,2,3 }, + { 6,4,0,2 }, + { 4,5,1,0 }, + { 6,7,5,4 }, + { 0,1,3,2 }, +}; + +static stbvox_mesh_vertex stbvox_vmesh_delta_normal[6][4] = +{ + { stbvox_vertex_encode(1,0,1,0,0) , + stbvox_vertex_encode(1,1,1,0,0) , + stbvox_vertex_encode(1,1,0,0,0) , + stbvox_vertex_encode(1,0,0,0,0) }, + { stbvox_vertex_encode(1,1,1,0,0) , + stbvox_vertex_encode(0,1,1,0,0) , + stbvox_vertex_encode(0,1,0,0,0) , + stbvox_vertex_encode(1,1,0,0,0) }, + { stbvox_vertex_encode(0,1,1,0,0) , + stbvox_vertex_encode(0,0,1,0,0) , + stbvox_vertex_encode(0,0,0,0,0) , + stbvox_vertex_encode(0,1,0,0,0) }, + { stbvox_vertex_encode(0,0,1,0,0) , + stbvox_vertex_encode(1,0,1,0,0) , + stbvox_vertex_encode(1,0,0,0,0) , + stbvox_vertex_encode(0,0,0,0,0) }, + { stbvox_vertex_encode(0,1,1,0,0) , + stbvox_vertex_encode(1,1,1,0,0) , + stbvox_vertex_encode(1,0,1,0,0) , + stbvox_vertex_encode(0,0,1,0,0) }, + { stbvox_vertex_encode(0,0,0,0,0) , + stbvox_vertex_encode(1,0,0,0,0) , + stbvox_vertex_encode(1,1,0,0,0) , + stbvox_vertex_encode(0,1,0,0,0) } +}; + +static stbvox_mesh_vertex stbvox_vmesh_pre_vheight[6][4] = +{ + { stbvox_vertex_encode(1,0,0,0,0) , + stbvox_vertex_encode(1,1,0,0,0) , + stbvox_vertex_encode(1,1,0,0,0) , + stbvox_vertex_encode(1,0,0,0,0) }, + { stbvox_vertex_encode(1,1,0,0,0) , + stbvox_vertex_encode(0,1,0,0,0) , + stbvox_vertex_encode(0,1,0,0,0) , + stbvox_vertex_encode(1,1,0,0,0) }, + { stbvox_vertex_encode(0,1,0,0,0) , + stbvox_vertex_encode(0,0,0,0,0) , + stbvox_vertex_encode(0,0,0,0,0) , + stbvox_vertex_encode(0,1,0,0,0) }, + { stbvox_vertex_encode(0,0,0,0,0) , + stbvox_vertex_encode(1,0,0,0,0) , + stbvox_vertex_encode(1,0,0,0,0) , + stbvox_vertex_encode(0,0,0,0,0) }, + { stbvox_vertex_encode(0,1,0,0,0) , + stbvox_vertex_encode(1,1,0,0,0) , + stbvox_vertex_encode(1,0,0,0,0) , + stbvox_vertex_encode(0,0,0,0,0) }, + { stbvox_vertex_encode(0,0,0,0,0) , + stbvox_vertex_encode(1,0,0,0,0) , + stbvox_vertex_encode(1,1,0,0,0) , + stbvox_vertex_encode(0,1,0,0,0) } +}; + +static stbvox_mesh_vertex stbvox_vmesh_delta_half_z[6][4] = +{ + { stbvox_vertex_encode(1,0,2,0,0) , + stbvox_vertex_encode(1,1,2,0,0) , + stbvox_vertex_encode(1,1,0,0,0) , + stbvox_vertex_encode(1,0,0,0,0) }, + { stbvox_vertex_encode(1,1,2,0,0) , + stbvox_vertex_encode(0,1,2,0,0) , + stbvox_vertex_encode(0,1,0,0,0) , + stbvox_vertex_encode(1,1,0,0,0) }, + { stbvox_vertex_encode(0,1,2,0,0) , + stbvox_vertex_encode(0,0,2,0,0) , + stbvox_vertex_encode(0,0,0,0,0) , + stbvox_vertex_encode(0,1,0,0,0) }, + { stbvox_vertex_encode(0,0,2,0,0) , + stbvox_vertex_encode(1,0,2,0,0) , + stbvox_vertex_encode(1,0,0,0,0) , + stbvox_vertex_encode(0,0,0,0,0) }, + { stbvox_vertex_encode(0,1,2,0,0) , + stbvox_vertex_encode(1,1,2,0,0) , + stbvox_vertex_encode(1,0,2,0,0) , + stbvox_vertex_encode(0,0,2,0,0) }, + { stbvox_vertex_encode(0,0,0,0,0) , + stbvox_vertex_encode(1,0,0,0,0) , + stbvox_vertex_encode(1,1,0,0,0) , + stbvox_vertex_encode(0,1,0,0,0) } +}; + +static stbvox_mesh_vertex stbvox_vmesh_crossed_pair[6][4] = +{ + { stbvox_vertex_encode(1,0,2,0,0) , + stbvox_vertex_encode(0,1,2,0,0) , + stbvox_vertex_encode(0,1,0,0,0) , + stbvox_vertex_encode(1,0,0,0,0) }, + { stbvox_vertex_encode(1,1,2,0,0) , + stbvox_vertex_encode(0,0,2,0,0) , + stbvox_vertex_encode(0,0,0,0,0) , + stbvox_vertex_encode(1,1,0,0,0) }, + { stbvox_vertex_encode(0,1,2,0,0) , + stbvox_vertex_encode(1,0,2,0,0) , + stbvox_vertex_encode(1,0,0,0,0) , + stbvox_vertex_encode(0,1,0,0,0) }, + { stbvox_vertex_encode(0,0,2,0,0) , + stbvox_vertex_encode(1,1,2,0,0) , + stbvox_vertex_encode(1,1,0,0,0) , + stbvox_vertex_encode(0,0,0,0,0) }, + // not used, so we leave it non-degenerate to make sure it doesn't get gen'd accidentally + { stbvox_vertex_encode(0,1,2,0,0) , + stbvox_vertex_encode(1,1,2,0,0) , + stbvox_vertex_encode(1,0,2,0,0) , + stbvox_vertex_encode(0,0,2,0,0) }, + { stbvox_vertex_encode(0,0,0,0,0) , + stbvox_vertex_encode(1,0,0,0,0) , + stbvox_vertex_encode(1,1,0,0,0) , + stbvox_vertex_encode(0,1,0,0,0) } +}; + +#define STBVOX_MAX_GEOM 16 +#define STBVOX_NUM_ROTATION 4 + +// this is used to determine if a face is ever generated at all +static unsigned char stbvox_hasface[STBVOX_MAX_GEOM][STBVOX_NUM_ROTATION] = +{ + { 0,0,0,0 }, // empty + { 0,0,0,0 }, // knockout + { 63,63,63,63 }, // solid + { 63,63,63,63 }, // transp + { 63,63,63,63 }, // slab + { 63,63,63,63 }, // slab + { 1|2|4|48, 8|1|2|48, 4|8|1|48, 2|4|8|48, }, // floor slopes + { 1|2|4|48, 8|1|2|48, 4|8|1|48, 2|4|8|48, }, // ceil slopes + { 47,47,47,47 }, // wall-projected diagonal with down face + { 31,31,31,31 }, // wall-projected diagonal with up face + { 63,63,63,63 }, // crossed-pair has special handling, but avoid early-out + { 63,63,63,63 }, // force + { 63,63,63,63 }, // vheight + { 63,63,63,63 }, // vheight + { 63,63,63,63 }, // vheight + { 63,63,63,63 }, // vheight +}; + +// this determines which face type above is visible on each side of the geometry +static unsigned char stbvox_facetype[STBVOX_GEOM_count][6] = +{ + { 0, }, // STBVOX_GEOM_empty + { STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid }, // knockout + { STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid, STBVOX_FT_solid }, // solid + { STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force }, // transp + + { STBVOX_FT_upper, STBVOX_FT_upper, STBVOX_FT_upper, STBVOX_FT_upper, STBVOX_FT_solid, STBVOX_FT_force }, + { STBVOX_FT_lower, STBVOX_FT_lower, STBVOX_FT_lower, STBVOX_FT_lower, STBVOX_FT_force, STBVOX_FT_solid }, + { STBVOX_FT_diag_123, STBVOX_FT_solid, STBVOX_FT_diag_023, STBVOX_FT_none, STBVOX_FT_force, STBVOX_FT_solid }, + { STBVOX_FT_diag_012, STBVOX_FT_solid, STBVOX_FT_diag_013, STBVOX_FT_none, STBVOX_FT_solid, STBVOX_FT_force }, + + { STBVOX_FT_diag_123, STBVOX_FT_solid, STBVOX_FT_diag_023, STBVOX_FT_force, STBVOX_FT_none, STBVOX_FT_solid }, + { STBVOX_FT_diag_012, STBVOX_FT_solid, STBVOX_FT_diag_013, STBVOX_FT_force, STBVOX_FT_solid, STBVOX_FT_none }, + { STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, 0,0 }, // crossed pair + { STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force, STBVOX_FT_force }, // GEOM_force + + { STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial, STBVOX_FT_force, STBVOX_FT_solid }, // floor vheight, all neighbors forced + { STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial, STBVOX_FT_force, STBVOX_FT_solid }, // floor vheight, all neighbors forced + { STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial, STBVOX_FT_solid, STBVOX_FT_force }, // ceil vheight, all neighbors forced + { STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial,STBVOX_FT_partial, STBVOX_FT_solid, STBVOX_FT_force }, // ceil vheight, all neighbors forced +}; + +// This table indicates what normal to use for the "up" face of a sloped geom +// @TODO this could be done with math given the current arrangement of the enum, but let's not require it +static unsigned char stbvox_floor_slope_for_rot[4] = +{ + STBVF_su, + STBVF_wu, // @TODO: why is this reversed from what it should be? this is a north-is-up face, so slope should be south&up + STBVF_nu, + STBVF_eu, +}; + +static unsigned char stbvox_ceil_slope_for_rot[4] = +{ + STBVF_sd, + STBVF_ed, + STBVF_nd, + STBVF_wd, +}; + +// this table indicates whether, for each pair of types above, a face is visible. +// each value indicates whether a given type is visible for all neighbor types +static unsigned short stbvox_face_visible[STBVOX_FT_count] = +{ + // we encode the table by listing which cases cause *obscuration*, and bitwise inverting that + // table is pre-shifted by 5 to save a shift when it's accessed + (unsigned short) ((~0x07ffu )<<5), // none is completely obscured by everything + (unsigned short) ((~((1u<output_cur[mesh][0]; + int step = mm->output_step[mesh][0]; + + // allocate a new quad from the mesh + vertices[0] = (stbvox_mesh_vertex *) p; p += step; + vertices[1] = (stbvox_mesh_vertex *) p; p += step; + vertices[2] = (stbvox_mesh_vertex *) p; p += step; + vertices[3] = (stbvox_mesh_vertex *) p; p += step; + mm->output_cur[mesh][0] = p; + + // output the face + #ifdef STBVOX_ICONFIG_FACE_ATTRIBUTE + // write face as interleaved vertex data + *(stbvox_mesh_face *) (vertices[0]+1) = face; + *(stbvox_mesh_face *) (vertices[1]+1) = face; + *(stbvox_mesh_face *) (vertices[2]+1) = face; + *(stbvox_mesh_face *) (vertices[3]+1) = face; + #else + *(stbvox_mesh_face *) mm->output_cur[mesh][1] = face; + mm->output_cur[mesh][1] += 4; + #endif +} + +void stbvox_make_mesh_for_face(stbvox_mesh_maker *mm, stbvox_rotate rot, int face, int v_off, stbvox_pos pos, stbvox_mesh_vertex vertbase, stbvox_mesh_vertex *face_coord, unsigned char mesh, int normal) +{ + stbvox_mesh_face face_data = stbvox_compute_mesh_face_value(mm,rot,face,v_off, normal); + + // still need to compute ao & texlerp for each vertex + + // first compute texlerp into p1 + stbvox_mesh_vertex p1[4] = { 0 }; + + #if defined(STBVOX_CONFIG_DOWN_TEXLERP_PACKED) && defined(STBVOX_CONFIG_UP_TEXLERP_PACKED) + #define STBVOX_USE_PACKED(f) ((f) == STBVOX_FACE_up || (f) == STBVOX_FACE_down) + #elif defined(STBVOX_CONFIG_UP_TEXLERP_PACKED) + #define STBVOX_USE_PACKED(f) ((f) == STBVOX_FACE_up ) + #elif defined(STBVOX_CONFIG_DOWN_TEXLERP_PACKED) + #define STBVOX_USE_PACKED(f) ( (f) == STBVOX_FACE_down) + #endif + + #if defined(STBVOX_CONFIG_DOWN_TEXLERP_PACKED) || defined(STBVOX_CONFIG_UP_TEXLERP_PACKED) + if (STBVOX_USE_PACKED(face)) { + if (!mm->input.packed_compact || 0==(mm->input.packed_compact[v_off]&16)) + goto set_default; + p1[0] = (mm->input.packed_compact[v_off + mm->cube_vertex_offset[face][0]] >> 5); + p1[1] = (mm->input.packed_compact[v_off + mm->cube_vertex_offset[face][1]] >> 5); + p1[2] = (mm->input.packed_compact[v_off + mm->cube_vertex_offset[face][2]] >> 5); + p1[3] = (mm->input.packed_compact[v_off + mm->cube_vertex_offset[face][3]] >> 5); + p1[0] = stbvox_vertex_encode(0,0,0,0,p1[0]); + p1[1] = stbvox_vertex_encode(0,0,0,0,p1[1]); + p1[2] = stbvox_vertex_encode(0,0,0,0,p1[2]); + p1[3] = stbvox_vertex_encode(0,0,0,0,p1[3]); + goto skip; + } + #endif + + if (mm->input.block_texlerp) { + stbvox_block_type bt = mm->input.blocktype[v_off]; + unsigned char val = mm->input.block_texlerp[bt]; + p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,val); + } else if (mm->input.block_texlerp_face) { + stbvox_block_type bt = mm->input.blocktype[v_off]; + unsigned char bt_face = STBVOX_ROTATE(face, rot.block); + unsigned char val = mm->input.block_texlerp_face[bt][bt_face]; + p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,val); + } else if (mm->input.texlerp_face3) { + unsigned char val = (mm->input.texlerp_face3[v_off] >> stbvox_face3_lerp[face]) & 7; + if (face >= STBVOX_FACE_up) + val = stbvox_face3_updown[val]; + p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,val); + } else if (mm->input.texlerp_simple) { + unsigned char val = mm->input.texlerp_simple[v_off]; + unsigned char lerp_face = (val >> 2) & 7; + if (lerp_face == face) { + p1[0] = (mm->input.texlerp_simple[v_off + mm->cube_vertex_offset[face][0]] >> 5) & 7; + p1[1] = (mm->input.texlerp_simple[v_off + mm->cube_vertex_offset[face][1]] >> 5) & 7; + p1[2] = (mm->input.texlerp_simple[v_off + mm->cube_vertex_offset[face][2]] >> 5) & 7; + p1[3] = (mm->input.texlerp_simple[v_off + mm->cube_vertex_offset[face][3]] >> 5) & 7; + p1[0] = stbvox_vertex_encode(0,0,0,0,p1[0]); + p1[1] = stbvox_vertex_encode(0,0,0,0,p1[1]); + p1[2] = stbvox_vertex_encode(0,0,0,0,p1[2]); + p1[3] = stbvox_vertex_encode(0,0,0,0,p1[3]); + } else { + unsigned char base = stbvox_vert_lerp_for_simple[val&3]; + p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,base); + } + } else if (mm->input.texlerp) { + unsigned char facelerp = (mm->input.texlerp[v_off] >> stbvox_face_lerp[face]) & 3; + if (facelerp == STBVOX_TEXLERP_FACE_use_vert) { + if (mm->input.texlerp_vert3 && face != STBVOX_FACE_down) { + unsigned char shift = stbvox_vert3_lerp[face]; + p1[0] = (mm->input.texlerp_vert3[mm->cube_vertex_offset[face][0]] >> shift) & 7; + p1[1] = (mm->input.texlerp_vert3[mm->cube_vertex_offset[face][1]] >> shift) & 7; + p1[2] = (mm->input.texlerp_vert3[mm->cube_vertex_offset[face][2]] >> shift) & 7; + p1[3] = (mm->input.texlerp_vert3[mm->cube_vertex_offset[face][3]] >> shift) & 7; + } else { + p1[0] = stbvox_vert_lerp_for_simple[mm->input.texlerp[mm->cube_vertex_offset[face][0]]>>6]; + p1[1] = stbvox_vert_lerp_for_simple[mm->input.texlerp[mm->cube_vertex_offset[face][1]]>>6]; + p1[2] = stbvox_vert_lerp_for_simple[mm->input.texlerp[mm->cube_vertex_offset[face][2]]>>6]; + p1[3] = stbvox_vert_lerp_for_simple[mm->input.texlerp[mm->cube_vertex_offset[face][3]]>>6]; + } + p1[0] = stbvox_vertex_encode(0,0,0,0,p1[0]); + p1[1] = stbvox_vertex_encode(0,0,0,0,p1[1]); + p1[2] = stbvox_vertex_encode(0,0,0,0,p1[2]); + p1[3] = stbvox_vertex_encode(0,0,0,0,p1[3]); + } else { + p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,stbvox_vert_lerp_for_face_lerp[facelerp]); + } + } else { + #if defined(STBVOX_CONFIG_UP_TEXLERP_PACKED) || defined(STBVOX_CONFIG_DOWN_TEXLERP_PACKED) + set_default: + #endif + p1[0] = p1[1] = p1[2] = p1[3] = stbvox_vertex_encode(0,0,0,0,7); // @TODO make this configurable + } + + #if defined(STBVOX_CONFIG_UP_TEXLERP_PACKED) || defined(STBVOX_CONFIG_DOWN_TEXLERP_PACKED) + skip: + #endif + + // now compute lighting and store to vertices + { + stbvox_mesh_vertex *mv[4]; + stbvox_get_quad_vertex_pointer(mm, mesh, mv, face_data); + + if (mm->input.lighting) { + // @TODO: lighting at block centers, but not gathered, instead constant-per-face + if (mm->input.lighting_at_vertices) { + int i; + for (i=0; i < 4; ++i) { + *mv[i] = vertbase + face_coord[i] + + stbvox_vertex_encode(0,0,0,mm->input.lighting[v_off + mm->cube_vertex_offset[face][i]] & 63,0) + + p1[i]; + } + } else { + unsigned char *amb = &mm->input.lighting[v_off]; + int i,j; + #if defined(STBVOX_CONFIG_ROTATION_IN_LIGHTING) || defined(STBVOX_CONFIG_VHEIGHT_IN_LIGHTING) + #define STBVOX_GET_LIGHTING(light) ((light) & ~3) + #define STBVOX_LIGHTING_ROUNDOFF 8 + #else + #define STBVOX_GET_LIGHTING(light) (light) + #define STBVOX_LIGHTING_ROUNDOFF 2 + #endif + + for (i=0; i < 4; ++i) { + // for each vertex, gather from the four neighbor blocks it's facing + unsigned char *vamb = &amb[mm->cube_vertex_offset[face][i]]; + int total=0; + for (j=0; j < 4; ++j) + total += STBVOX_GET_LIGHTING(vamb[mm->vertex_gather_offset[face][j]]); + *mv[i] = vertbase + face_coord[i] + + stbvox_vertex_encode(0,0,0,(total+STBVOX_LIGHTING_ROUNDOFF)>>4,0) + + p1[i]; + // >> 4 is because: + // >> 2 to divide by 4 to get average over 4 samples + // >> 2 because input is 8 bits, output is 6 bits + } + + // @TODO: note that gathering baked *lighting* + // is different from gathering baked ao; baked ao can count + // solid blocks as 0 ao, but baked lighting wants average + // of non-blocked--not take average & treat blocked as 0. And + // we can't bake the right value into the solid blocks + // because they can have different lighting values on + // different sides. So we need to actually gather and + // then divide by 0..4 (which we can do with a table-driven + // multiply, or have an 'if' for the 3 case) + + } + } else { + vertbase += stbvox_vertex_encode(0,0,0,63,0); + *mv[0] = vertbase + face_coord[0] + p1[0]; + *mv[1] = vertbase + face_coord[1] + p1[1]; + *mv[2] = vertbase + face_coord[2] + p1[2]; + *mv[3] = vertbase + face_coord[3] + p1[3]; + } + } +} + +// get opposite-facing normal & texgen for opposite face, used to map up-facing vheight data to down-facing data +static unsigned char stbvox_reverse_face[STBVF_count] = +{ + STBVF_w, STBVF_s, STBVF_e, STBVF_n, STBVF_d , STBVF_u , STBVF_wd, STBVF_wu, + 0, 0, 0, 0, STBVF_sw_d, STBVF_sw_u, STBVF_sd, STBVF_su, + 0, 0, 0, 0, STBVF_se_d, STBVF_se_u, STBVF_ed, STBVF_eu, + 0, 0, 0, 0, STBVF_ne_d, STBVF_ne_d, STBVF_nd, STBVF_nu +}; + +#ifndef STBVOX_CONFIG_OPTIMIZED_VHEIGHT +// render non-planar quads by splitting into two triangles, rendering each as a degenerate quad +static void stbvox_make_12_split_mesh_for_face(stbvox_mesh_maker *mm, stbvox_rotate rot, int face, int v_off, stbvox_pos pos, stbvox_mesh_vertex vertbase, stbvox_mesh_vertex *face_coord, unsigned char mesh, unsigned char *ht) +{ + stbvox_mesh_vertex v[4]; + + unsigned char normal1 = stbvox_face_up_normal_012[ht[2]][ht[1]][ht[0]]; + unsigned char normal2 = stbvox_face_up_normal_123[ht[3]][ht[2]][ht[1]]; + + if (face == STBVOX_FACE_down) { + normal1 = stbvox_reverse_face[normal1]; + normal2 = stbvox_reverse_face[normal2]; + } + + // the floor side face_coord is stored in order NW,NE,SE,SW, but ht[] is stored SW,SE,NW,NE + v[0] = face_coord[2]; + v[1] = face_coord[3]; + v[2] = face_coord[0]; + v[3] = face_coord[2]; + stbvox_make_mesh_for_face(mm, rot, face, v_off, pos, vertbase, v, mesh, normal1); + v[1] = face_coord[0]; + v[2] = face_coord[1]; + stbvox_make_mesh_for_face(mm, rot, face, v_off, pos, vertbase, v, mesh, normal2); +} + +static void stbvox_make_03_split_mesh_for_face(stbvox_mesh_maker *mm, stbvox_rotate rot, int face, int v_off, stbvox_pos pos, stbvox_mesh_vertex vertbase, stbvox_mesh_vertex *face_coord, unsigned char mesh, unsigned char *ht) +{ + stbvox_mesh_vertex v[4]; + + unsigned char normal1 = stbvox_face_up_normal_013[ht[3]][ht[1]][ht[0]]; + unsigned char normal2 = stbvox_face_up_normal_023[ht[3]][ht[2]][ht[0]]; + + if (face == STBVOX_FACE_down) { + normal1 = stbvox_reverse_face[normal1]; + normal2 = stbvox_reverse_face[normal2]; + } + + v[0] = face_coord[1]; + v[1] = face_coord[2]; + v[2] = face_coord[3]; + v[3] = face_coord[1]; + stbvox_make_mesh_for_face(mm, rot, face, v_off, pos, vertbase, v, mesh, normal1); + v[1] = face_coord[3]; + v[2] = face_coord[0]; + stbvox_make_mesh_for_face(mm, rot, face, v_off, pos, vertbase, v, mesh, normal2); // this one is correct! +} +#endif + +#ifndef STBVOX_CONFIG_PRECISION_Z +#define STBVOX_CONFIG_PRECISION_Z 1 +#endif + +// simple case for mesh generation: we have only solid and empty blocks +static void stbvox_make_mesh_for_block(stbvox_mesh_maker *mm, stbvox_pos pos, int v_off, stbvox_mesh_vertex *vmesh) +{ + int ns_off = mm->y_stride_in_bytes; + int ew_off = mm->x_stride_in_bytes; + + unsigned char *blockptr = &mm->input.blocktype[v_off]; + stbvox_mesh_vertex basevert = stbvox_vertex_encode(pos.x, pos.y, pos.z << STBVOX_CONFIG_PRECISION_Z , 0,0); + + stbvox_rotate rot = { 0,0,0,0 }; + unsigned char simple_rot = 0; + + unsigned char mesh = mm->default_mesh; + + if (mm->input.selector) + mesh = mm->input.selector[v_off]; + else if (mm->input.block_selector) + mesh = mm->input.block_selector[mm->input.blocktype[v_off]]; + + // check if we're going off the end + if (mm->output_cur[mesh][0] + mm->output_size[mesh][0]*6 > mm->output_end[mesh][0]) { + mm->full = 1; + return; + } + + #ifdef STBVOX_CONFIG_ROTATION_IN_LIGHTING + simple_rot = mm->input.lighting[v_off] & 3; + #endif + + if (mm->input.packed_compact) + simple_rot = mm->input.packed_compact[v_off] & 3; + + if (blockptr[ 1]==0) { + rot.facerot = simple_rot; + stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_up , v_off, pos, basevert, vmesh+4*STBVOX_FACE_up, mesh, STBVOX_FACE_up); + } + if (blockptr[-1]==0) { + rot.facerot = (-simple_rot) & 3; + stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_down, v_off, pos, basevert, vmesh+4*STBVOX_FACE_down, mesh, STBVOX_FACE_down); + } + + if (mm->input.rotate) { + unsigned char val = mm->input.rotate[v_off]; + rot.block = (val >> 0) & 3; + rot.overlay = (val >> 2) & 3; + //rot.tex2 = (val >> 4) & 3; + rot.ecolor = (val >> 6) & 3; + } else { + rot.block = rot.overlay = rot.ecolor = simple_rot; + } + rot.facerot = 0; + + if (blockptr[ ns_off]==0) + stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_north, v_off, pos, basevert, vmesh+4*STBVOX_FACE_north, mesh, STBVOX_FACE_north); + if (blockptr[-ns_off]==0) + stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_south, v_off, pos, basevert, vmesh+4*STBVOX_FACE_south, mesh, STBVOX_FACE_south); + if (blockptr[ ew_off]==0) + stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_east , v_off, pos, basevert, vmesh+4*STBVOX_FACE_east, mesh, STBVOX_FACE_east); + if (blockptr[-ew_off]==0) + stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_west , v_off, pos, basevert, vmesh+4*STBVOX_FACE_west, mesh, STBVOX_FACE_west); +} + +// complex case for mesh generation: we have lots of different +// block types, and we don't want to generate faces of blocks +// if they're hidden by neighbors. +// +// we use lots of tables to determine this: we have a table +// which tells us what face type is generated for each type of +// geometry, and then a table that tells us whether that type +// is hidden by a neighbor. +static void stbvox_make_mesh_for_block_with_geo(stbvox_mesh_maker *mm, stbvox_pos pos, int v_off) +{ + int ns_off = mm->y_stride_in_bytes; + int ew_off = mm->x_stride_in_bytes; + int visible_faces, visible_base; + unsigned char mesh; + + // first gather the geometry info for this block and all neighbors + + unsigned char bt, nbt[6]; + unsigned char geo, ngeo[6]; + unsigned char rot, nrot[6]; + + bt = mm->input.blocktype[v_off]; + nbt[0] = mm->input.blocktype[v_off + ew_off]; + nbt[1] = mm->input.blocktype[v_off + ns_off]; + nbt[2] = mm->input.blocktype[v_off - ew_off]; + nbt[3] = mm->input.blocktype[v_off - ns_off]; + nbt[4] = mm->input.blocktype[v_off + 1]; + nbt[5] = mm->input.blocktype[v_off - 1]; + if (mm->input.geometry) { + int i; + geo = mm->input.geometry[v_off]; + ngeo[0] = mm->input.geometry[v_off + ew_off]; + ngeo[1] = mm->input.geometry[v_off + ns_off]; + ngeo[2] = mm->input.geometry[v_off - ew_off]; + ngeo[3] = mm->input.geometry[v_off - ns_off]; + ngeo[4] = mm->input.geometry[v_off + 1]; + ngeo[5] = mm->input.geometry[v_off - 1]; + + rot = (geo >> 4) & 3; + geo &= 15; + for (i=0; i < 6; ++i) { + nrot[i] = (ngeo[i] >> 4) & 3; + ngeo[i] &= 15; + } + } else { + int i; + assert(mm->input.block_geometry); + geo = mm->input.block_geometry[bt]; + for (i=0; i < 6; ++i) + ngeo[i] = mm->input.block_geometry[nbt[i]]; + if (mm->input.selector) { + #ifndef STBVOX_CONFIG_ROTATION_IN_LIGHTING + if (mm->input.packed_compact == NULL) { + rot = (mm->input.selector[v_off ] >> 4) & 3; + nrot[0] = (mm->input.selector[v_off + ew_off] >> 4) & 3; + nrot[1] = (mm->input.selector[v_off + ns_off] >> 4) & 3; + nrot[2] = (mm->input.selector[v_off - ew_off] >> 4) & 3; + nrot[3] = (mm->input.selector[v_off - ns_off] >> 4) & 3; + nrot[4] = (mm->input.selector[v_off + 1] >> 4) & 3; + nrot[5] = (mm->input.selector[v_off - 1] >> 4) & 3; + } + #endif + } else { + #ifndef STBVOX_CONFIG_ROTATION_IN_LIGHTING + if (mm->input.packed_compact == NULL) { + rot = (geo>>4)&3; + geo &= 15; + for (i=0; i < 6; ++i) { + nrot[i] = (ngeo[i]>>4)&3; + ngeo[i] &= 15; + } + } + #endif + } + } + + #ifndef STBVOX_CONFIG_ROTATION_IN_LIGHTING + if (mm->input.packed_compact) { + rot = mm->input.packed_compact[rot] & 3; + nrot[0] = mm->input.packed_compact[v_off + ew_off] & 3; + nrot[1] = mm->input.packed_compact[v_off + ns_off] & 3; + nrot[2] = mm->input.packed_compact[v_off - ew_off] & 3; + nrot[3] = mm->input.packed_compact[v_off - ns_off] & 3; + nrot[4] = mm->input.packed_compact[v_off + 1] & 3; + nrot[5] = mm->input.packed_compact[v_off - 1] & 3; + } + #else + rot = mm->input.lighting[v_off] & 3; + nrot[0] = (mm->input.lighting[v_off + ew_off]) & 3; + nrot[1] = (mm->input.lighting[v_off + ns_off]) & 3; + nrot[2] = (mm->input.lighting[v_off - ew_off]) & 3; + nrot[3] = (mm->input.lighting[v_off - ns_off]) & 3; + nrot[4] = (mm->input.lighting[v_off + 1]) & 3; + nrot[5] = (mm->input.lighting[v_off - 1]) & 3; + #endif + + if (geo == STBVOX_GEOM_transp) { + // transparency has a special rule: if the blocktype is the same, + // and the faces are compatible, then can hide them; otherwise, + // force them on + // Note that this means we don't support any transparentshapes other + // than solid blocks, since detecting them is too complicated. If + // you wanted to do something like minecraft water, you probably + // should just do that with a separate renderer anyway. (We don't + // support transparency sorting so you need to use alpha test + // anyway) + int i; + for (i=0; i < 6; ++i) + if (nbt[i] != bt) { + nbt[i] = 0; + ngeo[i] = STBVOX_GEOM_empty; + } else + ngeo[i] = STBVOX_GEOM_solid; + geo = STBVOX_GEOM_solid; + } + + // now compute the face visibility + visible_base = stbvox_hasface[geo][rot]; + // @TODO: assert(visible_base != 0); // we should have early-outted earlier in this case + visible_faces = 0; + + // now, for every face that might be visible, check if neighbor hides it + if (visible_base & (1 << STBVOX_FACE_east)) { + int type = stbvox_facetype[ geo ][(STBVOX_FACE_east+ rot )&3]; + int ntype = stbvox_facetype[ngeo[0]][(STBVOX_FACE_west+nrot[0])&3]; + visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_east)) & (1 << STBVOX_FACE_east); + } + if (visible_base & (1 << STBVOX_FACE_north)) { + int type = stbvox_facetype[ geo ][(STBVOX_FACE_north+ rot )&3]; + int ntype = stbvox_facetype[ngeo[1]][(STBVOX_FACE_south+nrot[1])&3]; + visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_north)) & (1 << STBVOX_FACE_north); + } + if (visible_base & (1 << STBVOX_FACE_west)) { + int type = stbvox_facetype[ geo ][(STBVOX_FACE_west+ rot )&3]; + int ntype = stbvox_facetype[ngeo[2]][(STBVOX_FACE_east+nrot[2])&3]; + visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_west)) & (1 << STBVOX_FACE_west); + } + if (visible_base & (1 << STBVOX_FACE_south)) { + int type = stbvox_facetype[ geo ][(STBVOX_FACE_south+ rot )&3]; + int ntype = stbvox_facetype[ngeo[3]][(STBVOX_FACE_north+nrot[3])&3]; + visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_south)) & (1 << STBVOX_FACE_south); + } + if (visible_base & (1 << STBVOX_FACE_up)) { + int type = stbvox_facetype[ geo ][STBVOX_FACE_up]; + int ntype = stbvox_facetype[ngeo[4]][STBVOX_FACE_down]; + visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_up)) & (1 << STBVOX_FACE_up); + } + if (visible_base & (1 << STBVOX_FACE_down)) { + int type = stbvox_facetype[ geo ][STBVOX_FACE_down]; + int ntype = stbvox_facetype[ngeo[5]][STBVOX_FACE_up]; + visible_faces |= ((stbvox_face_visible[type]) >> (ntype + 5 - STBVOX_FACE_down)) & (1 << STBVOX_FACE_down); + } + + if (geo == STBVOX_GEOM_force) + geo = STBVOX_GEOM_solid; + + assert((geo == STBVOX_GEOM_crossed_pair) ? (visible_faces == 15) : 1); + + // now we finally know for sure which faces are getting generated + if (visible_faces == 0) + return; + + mesh = mm->default_mesh; + if (mm->input.selector) + mesh = mm->input.selector[v_off]; + else if (mm->input.block_selector) + mesh = mm->input.block_selector[bt]; + + if (geo <= STBVOX_GEOM_ceil_slope_north_is_bottom) { + // this is the simple case, we can just use regular block gen with special vmesh calculated with vheight + stbvox_mesh_vertex basevert; + stbvox_mesh_vertex vmesh[6][4]; + stbvox_rotate rotate = { 0,0,0,0 }; + unsigned char simple_rot = rot; + int i; + // we only need to do this for the displayed faces, but it's easier + // to just do it up front; @OPTIMIZE check if it's faster to do it + // for visible faces only + for (i=0; i < 6*4; ++i) { + int vert = stbvox_vertex_selector[0][i]; + vert = stbvox_rotate_vertex[vert][rot]; + vmesh[0][i] = stbvox_vmesh_pre_vheight[0][i] + + stbvox_geometry_vheight[geo][vert]; + } + + basevert = stbvox_vertex_encode(pos.x, pos.y, pos.z << STBVOX_CONFIG_PRECISION_Z, 0,0); + if (mm->input.selector) { + mesh = mm->input.selector[v_off]; + } else if (mm->input.block_selector) + mesh = mm->input.block_selector[bt]; + + + // check if we're going off the end + if (mm->output_cur[mesh][0] + mm->output_size[mesh][0]*6 > mm->output_end[mesh][0]) { + mm->full = 1; + return; + } + + if (geo >= STBVOX_GEOM_floor_slope_north_is_top) { + if (visible_faces & (1 << STBVOX_FACE_up)) { + int normal = geo == STBVOX_GEOM_floor_slope_north_is_top ? stbvox_floor_slope_for_rot[simple_rot] : STBVOX_FACE_up; + rotate.facerot = simple_rot; + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, normal); + } + if (visible_faces & (1 << STBVOX_FACE_down)) { + int normal = geo == STBVOX_GEOM_ceil_slope_north_is_bottom ? stbvox_ceil_slope_for_rot[simple_rot] : STBVOX_FACE_down; + rotate.facerot = (-rotate.facerot) & 3; + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, normal); + } + } else { + if (visible_faces & (1 << STBVOX_FACE_up)) { + rotate.facerot = simple_rot; + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, STBVOX_FACE_up); + } + if (visible_faces & (1 << STBVOX_FACE_down)) { + rotate.facerot = (-rotate.facerot) & 3; + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, STBVOX_FACE_down); + } + } + + if (mm->input.rotate) { + unsigned char val = mm->input.rotate[v_off]; + rotate.block = (val >> 0) & 3; + rotate.overlay = (val >> 2) & 3; + //rotate.tex2 = (val >> 4) & 3; + rotate.ecolor = (val >> 6) & 3; + } else { + rotate.block = rotate.overlay = rotate.ecolor = simple_rot; + } + + rotate.facerot = 0; + + if (visible_faces & (1 << STBVOX_FACE_north)) + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_north, v_off, pos, basevert, vmesh[STBVOX_FACE_north], mesh, STBVOX_FACE_north); + if (visible_faces & (1 << STBVOX_FACE_south)) + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_south, v_off, pos, basevert, vmesh[STBVOX_FACE_south], mesh, STBVOX_FACE_south); + if (visible_faces & (1 << STBVOX_FACE_east)) + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_east , v_off, pos, basevert, vmesh[STBVOX_FACE_east ], mesh, STBVOX_FACE_east); + if (visible_faces & (1 << STBVOX_FACE_west)) + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_west , v_off, pos, basevert, vmesh[STBVOX_FACE_west ], mesh, STBVOX_FACE_west); + } + if (geo >= STBVOX_GEOM_floor_vheight_03) { + // this case can also be generated with regular block gen with special vmesh, + // except: + // if we want to generate middle diagonal for 'weird' blocks + // it's more complicated to detect neighbor matchups + stbvox_mesh_vertex vmesh[6][4]; + stbvox_mesh_vertex cube[8]; + stbvox_mesh_vertex basevert; + stbvox_rotate rotate = { 0,0,0,0 }; + unsigned char simple_rot = rot; + unsigned char ht[4]; + int extreme; + + // extract the heights + #ifdef STBVOX_CONFIG_VHEIGHT_IN_LIGHTING + ht[0] = mm->input.lighting[v_off ] & 3; + ht[1] = mm->input.lighting[v_off+ew_off ] & 3; + ht[2] = mm->input.lighting[v_off +ns_off] & 3; + ht[3] = mm->input.lighting[v_off+ew_off+ns_off] & 3; + #else + if (mm->input.vheight) { + unsigned char v = mm->input.vheight[v_off]; + ht[0] = (v >> 0) & 3; + ht[1] = (v >> 2) & 3; + ht[2] = (v >> 4) & 3; + ht[3] = (v >> 6) & 3; + } else if (mm->input.block_vheight) { + unsigned char v = mm->input.block_vheight[bt]; + unsigned char raw[4]; + int i; + + raw[0] = (v >> 0) & 3; + raw[1] = (v >> 2) & 3; + raw[2] = (v >> 4) & 3; + raw[3] = (v >> 6) & 3; + + for (i=0; i < 4; ++i) + ht[i] = raw[stbvox_rotate_vertex[i][rot]]; + } else if (mm->input.packed_compact) { + ht[0] = (mm->input.packed_compact[v_off ] >> 2) & 3; + ht[1] = (mm->input.packed_compact[v_off+ew_off ] >> 2) & 3; + ht[2] = (mm->input.packed_compact[v_off +ns_off] >> 2) & 3; + ht[3] = (mm->input.packed_compact[v_off+ew_off+ns_off] >> 2) & 3; + } else if (mm->input.geometry) { + ht[0] = mm->input.geometry[v_off ] >> 6; + ht[1] = mm->input.geometry[v_off+ew_off ] >> 6; + ht[2] = mm->input.geometry[v_off +ns_off] >> 6; + ht[3] = mm->input.geometry[v_off+ew_off+ns_off] >> 6; + } else { + assert(0); + } + #endif + + // flag whether any sides go off the top of the block, which means + // our visible_faces test was wrong + extreme = (ht[0] == 3 || ht[1] == 3 || ht[2] == 3 || ht[3] == 3); + + if (geo >= STBVOX_GEOM_ceil_vheight_03) { + cube[0] = stbvox_vertex_encode(0,0,ht[0],0,0); + cube[1] = stbvox_vertex_encode(0,0,ht[1],0,0); + cube[2] = stbvox_vertex_encode(0,0,ht[2],0,0); + cube[3] = stbvox_vertex_encode(0,0,ht[3],0,0); + cube[4] = stbvox_vertex_encode(0,0,2,0,0); + cube[5] = stbvox_vertex_encode(0,0,2,0,0); + cube[6] = stbvox_vertex_encode(0,0,2,0,0); + cube[7] = stbvox_vertex_encode(0,0,2,0,0); + } else { + cube[0] = stbvox_vertex_encode(0,0,0,0,0); + cube[1] = stbvox_vertex_encode(0,0,0,0,0); + cube[2] = stbvox_vertex_encode(0,0,0,0,0); + cube[3] = stbvox_vertex_encode(0,0,0,0,0); + cube[4] = stbvox_vertex_encode(0,0,ht[0],0,0); + cube[5] = stbvox_vertex_encode(0,0,ht[1],0,0); + cube[6] = stbvox_vertex_encode(0,0,ht[2],0,0); + cube[7] = stbvox_vertex_encode(0,0,ht[3],0,0); + } + if (!mm->input.vheight && mm->input.block_vheight) { + // @TODO: support block vheight here, I've forgotten what needs to be done specially + } + + // build vertex mesh + { + int i; + for (i=0; i < 6*4; ++i) { + int vert = stbvox_vertex_selector[0][i]; + vmesh[0][i] = stbvox_vmesh_pre_vheight[0][i] + + cube[vert]; + } + } + + basevert = stbvox_vertex_encode(pos.x, pos.y, pos.z << STBVOX_CONFIG_PRECISION_Z, 0,0); + // check if we're going off the end + if (mm->output_cur[mesh][0] + mm->output_size[mesh][0]*6 > mm->output_end[mesh][0]) { + mm->full = 1; + return; + } + + // @TODO generate split faces + if (visible_faces & (1 << STBVOX_FACE_up)) { + if (geo >= STBVOX_GEOM_ceil_vheight_03) + // flat + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, STBVOX_FACE_up); + else { + #ifndef STBVOX_CONFIG_OPTIMIZED_VHEIGHT + // check if it's non-planar + if (cube[5] + cube[6] != cube[4] + cube[7]) { + // not planar, split along diagonal and make degenerate quads + if (geo == STBVOX_GEOM_floor_vheight_03) + stbvox_make_03_split_mesh_for_face(mm, rotate, STBVOX_FACE_up, v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, ht); + else + stbvox_make_12_split_mesh_for_face(mm, rotate, STBVOX_FACE_up, v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, ht); + } else + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, stbvox_planar_face_up_normal[ht[2]][ht[1]][ht[0]]); + #else + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_up , v_off, pos, basevert, vmesh[STBVOX_FACE_up], mesh, stbvox_optimized_face_up_normal[ht[3]][ht[2]][ht[1]][ht[0]]); + #endif + } + } + if (visible_faces & (1 << STBVOX_FACE_down)) { + if (geo < STBVOX_GEOM_ceil_vheight_03) + // flat + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, STBVOX_FACE_down); + else { + #ifndef STBVOX_CONFIG_OPTIMIZED_VHEIGHT + // check if it's non-planar + if (cube[1] + cube[2] != cube[0] + cube[3]) { + // not planar, split along diagonal and make degenerate quads + if (geo == STBVOX_GEOM_ceil_vheight_03) + stbvox_make_03_split_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, ht); + else + stbvox_make_12_split_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, ht); + } else + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, stbvox_reverse_face[stbvox_planar_face_up_normal[ht[2]][ht[1]][ht[0]]]); + #else + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_down, v_off, pos, basevert, vmesh[STBVOX_FACE_down], mesh, stbvox_reverse_face[stbvox_optimized_face_up_normal[ht[3]][ht[2]][ht[1]][ht[0]]]); + #endif + } + } + + if (mm->input.rotate) { + unsigned char val = mm->input.rotate[v_off]; + rotate.block = (val >> 0) & 3; + rotate.overlay = (val >> 2) & 3; + //rotate.tex2 = (val >> 4) & 3; + rotate.ecolor = (val >> 6) & 3; + } else if (mm->input.selector) { + rotate.block = rotate.overlay = rotate.ecolor = simple_rot; + } + + if ((visible_faces & (1 << STBVOX_FACE_north)) || (extreme && (ht[2] == 3 || ht[3] == 3))) + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_north, v_off, pos, basevert, vmesh[STBVOX_FACE_north], mesh, STBVOX_FACE_north); + if ((visible_faces & (1 << STBVOX_FACE_south)) || (extreme && (ht[0] == 3 || ht[1] == 3))) + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_south, v_off, pos, basevert, vmesh[STBVOX_FACE_south], mesh, STBVOX_FACE_south); + if ((visible_faces & (1 << STBVOX_FACE_east)) || (extreme && (ht[1] == 3 || ht[3] == 3))) + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_east , v_off, pos, basevert, vmesh[STBVOX_FACE_east ], mesh, STBVOX_FACE_east); + if ((visible_faces & (1 << STBVOX_FACE_west)) || (extreme && (ht[0] == 3 || ht[2] == 3))) + stbvox_make_mesh_for_face(mm, rotate, STBVOX_FACE_west , v_off, pos, basevert, vmesh[STBVOX_FACE_west ], mesh, STBVOX_FACE_west); + } + + if (geo == STBVOX_GEOM_crossed_pair) { + // this can be generated with a special vmesh + stbvox_mesh_vertex basevert = stbvox_vertex_encode(pos.x, pos.y, pos.z << STBVOX_CONFIG_PRECISION_Z , 0,0); + unsigned char simple_rot=0; + stbvox_rotate rot = { 0,0,0,0 }; + unsigned char mesh = mm->default_mesh; + if (mm->input.selector) { + mesh = mm->input.selector[v_off]; + simple_rot = mesh >> 4; + mesh &= 15; + } + if (mm->input.block_selector) { + mesh = mm->input.block_selector[bt]; + } + + // check if we're going off the end + if (mm->output_cur[mesh][0] + mm->output_size[mesh][0]*4 > mm->output_end[mesh][0]) { + mm->full = 1; + return; + } + + if (mm->input.rotate) { + unsigned char val = mm->input.rotate[v_off]; + rot.block = (val >> 0) & 3; + rot.overlay = (val >> 2) & 3; + //rot.tex2 = (val >> 4) & 3; + rot.ecolor = (val >> 6) & 3; + } else if (mm->input.selector) { + rot.block = rot.overlay = rot.ecolor = simple_rot; + } + rot.facerot = 0; + + stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_north, v_off, pos, basevert, stbvox_vmesh_crossed_pair[STBVOX_FACE_north], mesh, STBVF_ne_u_cross); + stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_south, v_off, pos, basevert, stbvox_vmesh_crossed_pair[STBVOX_FACE_south], mesh, STBVF_sw_u_cross); + stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_east , v_off, pos, basevert, stbvox_vmesh_crossed_pair[STBVOX_FACE_east ], mesh, STBVF_se_u_cross); + stbvox_make_mesh_for_face(mm, rot, STBVOX_FACE_west , v_off, pos, basevert, stbvox_vmesh_crossed_pair[STBVOX_FACE_west ], mesh, STBVF_nw_u_cross); + } + + + // @TODO + // STBVOX_GEOM_floor_slope_north_is_top_as_wall, + // STBVOX_GEOM_ceil_slope_north_is_bottom_as_wall, +} + +static void stbvox_make_mesh_for_column(stbvox_mesh_maker *mm, int x, int y, int z0) +{ + stbvox_pos pos; + int v_off = x * mm->x_stride_in_bytes + y * mm->y_stride_in_bytes; + int ns_off = mm->y_stride_in_bytes; + int ew_off = mm->x_stride_in_bytes; + pos.x = x; + pos.y = y; + pos.z = 0; + if (mm->input.geometry) { + unsigned char *bt = mm->input.blocktype + v_off; + unsigned char *geo = mm->input.geometry + v_off; + int z; + for (z=z0; z < mm->z1; ++z) { + if (bt[z] && ( !bt[z+ns_off] || !STBVOX_GET_GEO(geo[z+ns_off]) || !bt[z-ns_off] || !STBVOX_GET_GEO(geo[z-ns_off]) + || !bt[z+ew_off] || !STBVOX_GET_GEO(geo[z+ew_off]) || !bt[z-ew_off] || !STBVOX_GET_GEO(geo[z-ew_off]) + || !bt[z-1] || !STBVOX_GET_GEO(geo[z-1]) || !bt[z+1] || !STBVOX_GET_GEO(geo[z+1]))) + { // TODO check up and down + pos.z = z; + stbvox_make_mesh_for_block_with_geo(mm, pos, v_off+z); + if (mm->full) { + mm->cur_z = z; + return; + } + } + } + } else if (mm->input.block_geometry) { + int z; + unsigned char *bt = mm->input.blocktype + v_off; + unsigned char *geo = mm->input.block_geometry; + for (z=z0; z < mm->z1; ++z) { + if (bt[z] && ( geo[bt[z+ns_off]] != STBVOX_GEOM_solid + || geo[bt[z-ns_off]] != STBVOX_GEOM_solid + || geo[bt[z+ew_off]] != STBVOX_GEOM_solid + || geo[bt[z-ew_off]] != STBVOX_GEOM_solid + || geo[bt[z-1]] != STBVOX_GEOM_solid + || geo[bt[z+1]] != STBVOX_GEOM_solid)) + { + pos.z = z; + stbvox_make_mesh_for_block_with_geo(mm, pos, v_off+z); + if (mm->full) { + mm->cur_z = z; + return; + } + } + } + } else { + unsigned char *bt = mm->input.blocktype + v_off; + int z; + #if STBVOX_CONFIG_PRECISION_Z == 1 + stbvox_mesh_vertex *vmesh = stbvox_vmesh_delta_half_z[0]; + #else + stbvox_mesh_vertex *vmesh = stbvox_vmesh_delta_normal[0]; + #endif + for (z=z0; z < mm->z1; ++z) { + // if it's solid and at least one neighbor isn't solid + if (bt[z] && (!bt[z+ns_off] || !bt[z-ns_off] || !bt[z+ew_off] || !bt[z-ew_off] || !bt[z-1] || !bt[z+1])) { + pos.z = z; + stbvox_make_mesh_for_block(mm, pos, v_off+z, vmesh); + if (mm->full) { + mm->cur_z = z; + return; + } + } + } + } +} + +static void stbvox_bring_up_to_date(stbvox_mesh_maker *mm) +{ + if (mm->config_dirty) { + int i; + #ifdef STBVOX_ICONFIG_FACE_ATTRIBUTE + mm->num_mesh_slots = 1; + for (i=0; i < STBVOX_MAX_MESHES; ++i) { + mm->output_size[i][0] = 32; + mm->output_step[i][0] = 8; + } + #else + mm->num_mesh_slots = 2; + for (i=0; i < STBVOX_MAX_MESHES; ++i) { + mm->output_size[i][0] = 16; + mm->output_step[i][0] = 4; + mm->output_size[i][1] = 4; + mm->output_step[i][1] = 4; + } + #endif + + mm->config_dirty = 0; + } +} + +int stbvox_make_mesh(stbvox_mesh_maker *mm) +{ + int x,y; + stbvox_bring_up_to_date(mm); + mm->full = 0; + if (mm->cur_x > mm->x0 || mm->cur_y > mm->y0 || mm->cur_z > mm->z0) { + stbvox_make_mesh_for_column(mm, mm->cur_x, mm->cur_y, mm->cur_z); + if (mm->full) + return 0; + ++mm->cur_y; + while (mm->cur_y < mm->y1 && !mm->full) { + stbvox_make_mesh_for_column(mm, mm->cur_x, mm->cur_y, mm->z0); + if (mm->full) + return 0; + ++mm->cur_y; + } + ++mm->cur_x; + } + for (x=mm->cur_x; x < mm->x1; ++x) { + for (y=mm->y0; y < mm->y1; ++y) { + stbvox_make_mesh_for_column(mm, x, y, mm->z0); + if (mm->full) { + mm->cur_x = x; + mm->cur_y = y; + return 0; + } + } + } + return 1; +} + +void stbvox_init_mesh_maker(stbvox_mesh_maker *mm) +{ + memset(mm, 0, sizeof(*mm)); + stbvox_build_default_palette(); + + mm->config_dirty = 1; + mm->default_mesh = 0; +} + +int stbvox_get_buffer_count(stbvox_mesh_maker *mm) +{ + stbvox_bring_up_to_date(mm); + return mm->num_mesh_slots; +} + +int stbvox_get_buffer_size_per_quad(stbvox_mesh_maker *mm, int n) +{ + return mm->output_size[0][n]; +} + +void stbvox_reset_buffers(stbvox_mesh_maker *mm) +{ + int i; + for (i=0; i < STBVOX_MAX_MESHES*STBVOX_MAX_MESH_SLOTS; ++i) { + mm->output_cur[0][i] = 0; + mm->output_buffer[0][i] = 0; + } +} + +void stbvox_set_buffer(stbvox_mesh_maker *mm, int mesh, int slot, void *buffer, size_t len) +{ + int i; + stbvox_bring_up_to_date(mm); + mm->output_buffer[mesh][slot] = (char *) buffer; + mm->output_cur [mesh][slot] = (char *) buffer; + mm->output_len [mesh][slot] = (int) len; + mm->output_end [mesh][slot] = (char *) buffer + len; + for (i=0; i < STBVOX_MAX_MESH_SLOTS; ++i) { + if (mm->output_buffer[mesh][i]) { + assert(mm->output_len[mesh][i] / mm->output_size[mesh][i] == mm->output_len[mesh][slot] / mm->output_size[mesh][slot]); + } + } +} + +void stbvox_set_default_mesh(stbvox_mesh_maker *mm, int mesh) +{ + mm->default_mesh = mesh; +} + +int stbvox_get_quad_count(stbvox_mesh_maker *mm, int mesh) +{ + return (int) ((mm->output_cur[mesh][0] - mm->output_buffer[mesh][0]) / mm->output_size[mesh][0]); +} + +stbvox_input_description *stbvox_get_input_description(stbvox_mesh_maker *mm) +{ + return &mm->input; +} + +void stbvox_set_input_range(stbvox_mesh_maker *mm, int x0, int y0, int z0, int x1, int y1, int z1) +{ + mm->x0 = x0; + mm->y0 = y0; + mm->z0 = z0; + + mm->x1 = x1; + mm->y1 = y1; + mm->z1 = z1; + + mm->cur_x = x0; + mm->cur_y = y0; + mm->cur_z = z0; + + // @TODO validate that this range is representable in this mode +} + +void stbvox_get_transform(stbvox_mesh_maker *mm, float transform[3][3]) +{ + // scale + transform[0][0] = 1.0; + transform[0][1] = 1.0; + #if STBVOX_CONFIG_PRECISION_Z==1 + transform[0][2] = 0.5f; + #else + transform[0][2] = 1.0f; + #endif + // translation + transform[1][0] = (float) (mm->pos_x); + transform[1][1] = (float) (mm->pos_y); + transform[1][2] = (float) (mm->pos_z); + // texture coordinate projection translation + transform[2][0] = (float) (mm->pos_x & 255); // @TODO depends on max texture scale + transform[2][1] = (float) (mm->pos_y & 255); + transform[2][2] = (float) (mm->pos_z & 255); +} + +void stbvox_get_bounds(stbvox_mesh_maker *mm, float bounds[2][3]) +{ + bounds[0][0] = (float) (mm->pos_x + mm->x0); + bounds[0][1] = (float) (mm->pos_y + mm->y0); + bounds[0][2] = (float) (mm->pos_z + mm->z0); + bounds[1][0] = (float) (mm->pos_x + mm->x1); + bounds[1][1] = (float) (mm->pos_y + mm->y1); + bounds[1][2] = (float) (mm->pos_z + mm->z1); +} + +void stbvox_set_mesh_coordinates(stbvox_mesh_maker *mm, int x, int y, int z) +{ + mm->pos_x = x; + mm->pos_y = y; + mm->pos_z = z; +} + +void stbvox_set_input_stride(stbvox_mesh_maker *mm, int x_stride_in_bytes, int y_stride_in_bytes) +{ + int f,v; + mm->x_stride_in_bytes = x_stride_in_bytes; + mm->y_stride_in_bytes = y_stride_in_bytes; + for (f=0; f < 6; ++f) { + for (v=0; v < 4; ++v) { + mm->cube_vertex_offset[f][v] = stbvox_vertex_vector[f][v][0] * mm->x_stride_in_bytes + + stbvox_vertex_vector[f][v][1] * mm->y_stride_in_bytes + + stbvox_vertex_vector[f][v][2] ; + mm->vertex_gather_offset[f][v] = (stbvox_vertex_vector[f][v][0]-1) * mm->x_stride_in_bytes + + (stbvox_vertex_vector[f][v][1]-1) * mm->y_stride_in_bytes + + (stbvox_vertex_vector[f][v][2]-1) ; + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// offline computation of tables +// + +#if 0 +// compute optimized vheight table +static char *normal_names[32] = +{ + 0,0,0,0,"u ",0, "eu ",0, + 0,0,0,0,"ne_u",0, "nu ",0, + 0,0,0,0,"nw_u",0, "wu ",0, + 0,0,0,0,"sw_u",0, "su ",0, +}; + +static char *find_best_normal(float x, float y, float z) +{ + int best_slot = 4; + float best_dot = 0; + int i; + for (i=0; i < 32; ++i) { + if (normal_names[i]) { + float dot = x * stbvox_default_normals[i][0] + y * stbvox_default_normals[i][1] + z * stbvox_default_normals[i][2]; + if (dot > best_dot) { + best_dot = dot; + best_slot = i; + } + } + } + return normal_names[best_slot]; +} + +int main(int argc, char **argv) +{ + int sw,se,nw,ne; + for (ne=0; ne < 4; ++ne) { + for (nw=0; nw < 4; ++nw) { + for (se=0; se < 4; ++se) { + printf(" { "); + for (sw=0; sw < 4; ++sw) { + float x = (float) (nw + sw - ne - se); + float y = (float) (sw + se - nw - ne); + float z = 2; + printf("STBVF_%s, ", find_best_normal(x,y,z)); + } + printf("},\n"); + } + } + } + return 0; +} +#endif + +// @TODO +// +// - test API for texture rotation on side faces +// - API for texture rotation on top & bottom +// - better culling of vheight faces with vheight neighbors +// - better culling of non-vheight faces with vheight neighbors +// - gather vertex lighting from slopes correctly +// - better support texture edge_clamp: currently if you fall +// exactly on 1.0 you get wrapped incorrectly; this is rare, but +// can avoid: compute texcoords in vertex shader, offset towards +// center before modding, need 2 bits per vertex to know offset direction) +// - other mesh modes (10,6,4-byte quads) +// +// +// With TexBuffer for the fixed vertex data, we can actually do +// minecrafty non-blocks like stairs -- we still probably only +// want 256 or so, so we can't do the equivalent of all the vheight +// combos, but that's ok. The 256 includes baked rotations, but only +// some of them need it, and lots of block types share some faces. +// +// mode 5 (6 bytes): mode 6 (6 bytes) +// x:7 x:6 +// y:7 y:6 +// z:6 z:6 +// tex1:8 tex1:8 +// tex2:8 tex2:7 +// color:8 color:8 +// face:4 face:7 +// +// +// side faces (all x4) top&bottom faces (2x) internal faces (1x) +// 1 regular 1 regular +// 2 slabs 2 +// 8 stairs 4 stairs 16 +// 4 diag side 8 +// 4 upper diag side 8 +// 4 lower diag side 8 +// 4 crossed pairs +// +// 23*4 + 5*4 + 46 +// == 92 + 20 + 46 = 158 +// +// Must drop 30 of them to fit in 7 bits: +// ceiling half diagonals: 16+8 = 24 +// Need to get rid of 6 more. +// ceiling diagonals: 8+4 = 12 +// This brings it to 122, so can add a crossed-pair variant. +// (diagonal and non-diagonal, or randomly offset) +// Or carpet, which would be 5 more. +// +// +// Mode 4 (10 bytes): +// v: z:2,light:6 +// f: x:6,y:6,z:7, t1:8,t2:8,c:8,f:5 +// +// Mode ? (10 bytes) +// v: xyz:5 (27 values), light:3 +// f: x:7,y:7,z:6, t1:8,t2:8,c:8,f:4 +// (v: x:2,y:2,z:2,light:2) + +#endif // STB_VOXEL_RENDER_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/tests/Makefile b/source/engine/thirdparty/stb/tests/Makefile new file mode 100644 index 0000000..1782ea6 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/Makefile @@ -0,0 +1,12 @@ +INCLUDES = -I.. +CFLAGS = -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast -DSTB_DIVIDE_TEST +CPPFLAGS = -Wno-write-strings -DSTB_DIVIDE_TEST + +# Uncomment this line for reproducing OSS-Fuzz bugs with image_fuzzer +#CFLAGS += -O -fsanitize=address + +all: + $(CC) $(INCLUDES) $(CFLAGS) ../stb_vorbis.c test_c_compilation.c test_c_lexer.c test_dxt.c test_easyfont.c test_image.c test_image_write.c test_perlin.c test_sprintf.c test_truetype.c test_voxel.c -lm + $(CC) $(INCLUDES) $(CPPFLAGS) -std=c++0x test_cpp_compilation.cpp -lm -lstdc++ + $(CC) $(INCLUDES) $(CFLAGS) -DIWT_TEST image_write_test.c -lm -o image_write_test + $(CC) $(INCLUDES) $(CFLAGS) fuzz_main.c stbi_read_fuzzer.c -lm -o image_fuzzer diff --git a/source/engine/thirdparty/stb/tests/c_lexer_test.c b/source/engine/thirdparty/stb/tests/c_lexer_test.c new file mode 100644 index 0000000..a919b6c --- /dev/null +++ b/source/engine/thirdparty/stb/tests/c_lexer_test.c @@ -0,0 +1,50 @@ +#define STB_C_LEX_C_DECIMAL_INTS Y // "0|[1-9][0-9]*" CLEX_intlit +#define STB_C_LEX_C_HEX_INTS Y // "0x[0-9a-fA-F]+" CLEX_intlit +#define STB_C_LEX_C_OCTAL_INTS Y // "[0-7]+" CLEX_intlit +#define STB_C_LEX_C_DECIMAL_FLOATS Y // "[0-9]*(.[0-9]*([eE][-+]?[0-9]+)?) CLEX_floatlit +#define STB_C_LEX_C99_HEX_FLOATS Y // "0x{hex}+(.{hex}*)?[pP][-+]?{hex}+ CLEX_floatlit +#define STB_C_LEX_C_IDENTIFIERS Y // "[_a-zA-Z][_a-zA-Z0-9]*" CLEX_id +#define STB_C_LEX_C_DQ_STRINGS Y // double-quote-delimited strings with escapes CLEX_dqstring +#define STB_C_LEX_C_SQ_STRINGS Y // single-quote-delimited strings with escapes CLEX_ssstring +#define STB_C_LEX_C_CHARS Y // single-quote-delimited character with escape CLEX_charlits +#define STB_C_LEX_C_COMMENTS Y // "/* comment */" +#define STB_C_LEX_CPP_COMMENTS Y // "// comment to end of line\n" +#define STB_C_LEX_C_COMPARISONS Y // "==" CLEX_eq "!=" CLEX_noteq "<=" CLEX_lesseq ">=" CLEX_greatereq +#define STB_C_LEX_C_LOGICAL Y // "&&" CLEX_andand "||" CLEX_oror +#define STB_C_LEX_C_SHIFTS Y // "<<" CLEX_shl ">>" CLEX_shr +#define STB_C_LEX_C_INCREMENTS Y // "++" CLEX_plusplus "--" CLEX_minusminus +#define STB_C_LEX_C_ARROW Y // "->" CLEX_arrow +#define STB_C_LEX_EQUAL_ARROW Y // "=>" CLEX_eqarrow +#define STB_C_LEX_C_BITWISEEQ Y // "&=" CLEX_andeq "|=" CLEX_oreq "^=" CLEX_xoreq +#define STB_C_LEX_C_ARITHEQ Y // "+=" CLEX_pluseq "-=" CLEX_minuseq + // "*=" CLEX_muleq "/=" CLEX_diveq "%=" CLEX_modeq + // if both STB_C_LEX_SHIFTS & STB_C_LEX_ARITHEQ: + // "<<=" CLEX_shleq ">>=" CLEX_shreq + +#define STB_C_LEX_PARSE_SUFFIXES Y // letters after numbers are parsed as part of those numbers, and must be in suffix list below +#define STB_C_LEX_DECIMAL_SUFFIXES "uUlL" // decimal integer suffixes e.g. "uUlL" -- these are returned as-is in string storage +#define STB_C_LEX_HEX_SUFFIXES "lL" // e.g. "uUlL" +#define STB_C_LEX_OCTAL_SUFFIXES "lL" // e.g. "uUlL" +#define STB_C_LEX_FLOAT_SUFFIXES "uulL" // + +#define STB_C_LEX_0_IS_EOF N // if Y, ends parsing at '\0'; if N, returns '\0' as token +#define STB_C_LEX_INTEGERS_AS_DOUBLES N // parses integers as doubles so they can be larger than 'int', but only if STB_C_LEX_STDLIB==N +#define STB_C_LEX_MULTILINE_DSTRINGS Y // allow newlines in double-quoted strings +#define STB_C_LEX_MULTILINE_SSTRINGS Y // allow newlines in single-quoted strings +#define STB_C_LEX_USE_STDLIB N // use strtod,strtol for parsing #s; otherwise inaccurate hack +#define STB_C_LEX_DOLLAR_IDENTIFIER Y // allow $ as an identifier character +#define STB_C_LEX_FLOAT_NO_DECIMAL Y // allow floats that have no decimal point if they have an exponent + +#define STB_C_LEX_DEFINE_ALL_TOKEN_NAMES Y // if Y, all CLEX_ token names are defined, even if never returned + // leaving it as N should help you catch config bugs + +#define STB_C_LEX_DISCARD_PREPROCESSOR Y // discard C-preprocessor directives (e.g. after prepocess + // still have #line, #pragma, etc) + +#define STB_C_LEXER_DEFINITIONS // This line prevents the header file from replacing your definitions + + + +#define STB_C_LEXER_IMPLEMENTATION +#define STB_C_LEXER_SELF_TEST +#include "../stb_c_lexer.h" diff --git a/source/engine/thirdparty/stb/tests/c_lexer_test.dsp b/source/engine/thirdparty/stb/tests/c_lexer_test.dsp new file mode 100644 index 0000000..13f8758 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/c_lexer_test.dsp @@ -0,0 +1,89 @@ +# Microsoft Developer Studio Project File - Name="c_lexer_test" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=c_lexer_test - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "c_lexer_test.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "c_lexer_test.mak" CFG="c_lexer_test - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "c_lexer_test - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "c_lexer_test - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "c_lexer_test - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "c_lexer_test - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug\c_lexer_test" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "c_lexer_test - Win32 Release" +# Name "c_lexer_test - Win32 Debug" +# Begin Source File + +SOURCE=.\c_lexer_test.c +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/stb/tests/caveview/README.md b/source/engine/thirdparty/stb/tests/caveview/README.md new file mode 100644 index 0000000..10da838 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/caveview/README.md @@ -0,0 +1,85 @@ +# FAQ + +### How to run it? + +There's no GUI. Find a directory with Minecraft Anvil files (.mca). +Copy a Minecraft "terrain.png" into that directory (do a google +image search). Run from that directory. + +### How accurate is this as a Minecraft viewer? + +Not very. Many Minecraft blocks are not handled correctly: + +* No redstone, rails, or other "flat" blocks +* No signs, doors, fences, carpets, or other complicated geometry +* Stairs are turned into ramps +* Upper slabs turn into lower slabs +* Wood types only for blocks, not stairs, slabs, etc +* Colored glass becomes regular glass +* Glass panes become glass blocks +* Water is opaque +* Water level is incorrect +* No biome coloration +* Cactus is not shrunk, shows holes +* Chests are not shrunk +* Double-chests draw as two chests +* Pumpkins etc. are not rotated properly +* Torches are drawn hackily, do not attach to walls +* Incorrect textures for blocks that postdate terrain.png +* Transparent textures have black fringes due to non-premultiplied-alpha +* Skylight and block light are combined in a single value +* Only blocks at y=1..255 are shown (not y=0) +* If a 32x32x256 "quad-chunk" needs more than 800K quads, isn't handled (very unlikely) + +Some of these are due to engine limitations, and some of +these are because I didn't make the effort since my +goal was to make a demo for stb_voxel_render.h, not +to make a proper Minecraft viewer. + + +### Could this be turned into a proper Minecraft viewer? + +Yes and no. Yes, you could do it, but no, it wouldn't +really resemble this code that much anymore. + +You could certainly use this engine to +render the parts of Minecraft it works for, but many +of the things it doesn't handle it can't handle at all +(stairs, water, fences, carpets, etc) because it uses +low-precision coordinates to store voxel data. + +You would have to render all of the stuff it doesn't +handle through another rendering path. In a game (not +a viewer) you would need such a path for movable entities +like doors and carts anyway, so possibly handling other +things that way wouldn't be so bad. + +Rails, ladders, and redstone lines could be implemented by +using tex2 to overlay those effects, but you can't rotate +tex1 and tex2 independently, so there may be cases where +the underlying texture needs a different rotation from the +overlaid texture, which would require separate rendering. +Handling redstone's brightness being different from underlying +block's brightness would require separate rendering. + +You can use the face-color effect to do biome coloration, +but the change won't be smooth the way it is in Minecraft. + + +### Why isn't building the mesh data faster? + +Partly because converting from minecraft data is expensive. + +Here is the approximate breakdown of an older version +of this executable and lib that did the building single-threaded. + +* 25% loading & parsing minecraft files (4/5ths of this is my crappy zlib) +* 18% converting from minecraft blockids & lighting to stb blockids & lighting +* 10% reordering from data[z][y]\[x] (minecraft-style) to data[y][x]\[z] (stb-style) +* 40% building mesh data +* 7% uploading mesh data to OpenGL + +I did do significant optimizations after the above, so the +final breakdown is different, but it should give you some +sense of the costs. + diff --git a/source/engine/thirdparty/stb/tests/caveview/cave_main.c b/source/engine/thirdparty/stb/tests/caveview/cave_main.c new file mode 100644 index 0000000..d345cf1 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/caveview/cave_main.c @@ -0,0 +1,598 @@ +#define _WIN32_WINNT 0x400 + +#include +#include + +// stb.h +#define STB_DEFINE +#include "stb.h" + +// stb_gl.h +#define STB_GL_IMPLEMENTATION +#define STB_GLEXT_DEFINE "glext_list.h" +#include "stb_gl.h" + +// SDL +#include "sdl.h" +#include "SDL_opengl.h" + +// stb_glprog.h +#define STB_GLPROG_IMPLEMENTATION +#define STB_GLPROG_ARB_DEFINE_EXTENSIONS +#include "stb_glprog.h" + +// stb_image.h +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +// stb_easy_font.h +#include "stb_easy_font.h" // doesn't require an IMPLEMENTATION + +#include "caveview.h" + +char *game_name = "caveview"; + + +#define REVERSE_DEPTH + + + +static void print_string(float x, float y, char *text, float r, float g, float b) +{ + static char buffer[99999]; + int num_quads; + + num_quads = stb_easy_font_print(x, y, text, NULL, buffer, sizeof(buffer)); + + glColor3f(r,g,b); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(2, GL_FLOAT, 16, buffer); + glDrawArrays(GL_QUADS, 0, num_quads*4); + glDisableClientState(GL_VERTEX_ARRAY); +} + +static float text_color[3]; +static float pos_x = 10; +static float pos_y = 10; + +static void print(char *text, ...) +{ + char buffer[999]; + va_list va; + va_start(va, text); + vsprintf(buffer, text, va); + va_end(va); + print_string(pos_x, pos_y, buffer, text_color[0], text_color[1], text_color[2]); + pos_y += 10; +} + +float camang[3], camloc[3] = { 60,22,77 }; +float player_zoom = 1.0; +float rotate_view = 0.0; + + +void camera_to_worldspace(float world[3], float cam_x, float cam_y, float cam_z) +{ + float vec[3] = { cam_x, cam_y, cam_z }; + float t[3]; + float s,c; + s = (float) sin(camang[0]*3.141592/180); + c = (float) cos(camang[0]*3.141592/180); + + t[0] = vec[0]; + t[1] = c*vec[1] - s*vec[2]; + t[2] = s*vec[1] + c*vec[2]; + + s = (float) sin(camang[2]*3.141592/180); + c = (float) cos(camang[2]*3.141592/180); + world[0] = c*t[0] - s*t[1]; + world[1] = s*t[0] + c*t[1]; + world[2] = t[2]; +} + +// camera worldspace velocity +float cam_vel[3]; + +int controls; + +#define MAX_VEL 150.0f // blocks per second +#define ACCEL 6.0f +#define DECEL 3.0f + +#define STATIC_FRICTION DECEL +#define EFFECTIVE_ACCEL (ACCEL+DECEL) + +// dynamic friction: +// +// if going at MAX_VEL, ACCEL and friction must cancel +// EFFECTIVE_ACCEL = DECEL + DYNAMIC_FRIC*MAX_VEL +#define DYNAMIC_FRICTION (ACCEL/(float)MAX_VEL) + +float view_x_vel = 0; +float view_z_vel = 0; +float pending_view_x; +float pending_view_z; +float pending_view_x; +float pending_view_z; + +void process_tick_raw(float dt) +{ + int i; + float thrust[3] = { 0,0,0 }; + float world_thrust[3]; + + // choose direction to apply thrust + + thrust[0] = (controls & 3)== 1 ? EFFECTIVE_ACCEL : (controls & 3)== 2 ? -EFFECTIVE_ACCEL : 0; + thrust[1] = (controls & 12)== 4 ? EFFECTIVE_ACCEL : (controls & 12)== 8 ? -EFFECTIVE_ACCEL : 0; + thrust[2] = (controls & 48)==16 ? EFFECTIVE_ACCEL : (controls & 48)==32 ? -EFFECTIVE_ACCEL : 0; + + // @TODO clamp thrust[0] & thrust[1] vector length to EFFECTIVE_ACCEL + + camera_to_worldspace(world_thrust, thrust[0], thrust[1], 0); + world_thrust[2] += thrust[2]; + + for (i=0; i < 3; ++i) { + float acc = world_thrust[i]; + cam_vel[i] += acc*dt; + } + + if (cam_vel[0] || cam_vel[1] || cam_vel[2]) + { + float vel = (float) sqrt(cam_vel[0]*cam_vel[0] + cam_vel[1]*cam_vel[1] + cam_vel[2]*cam_vel[2]); + float newvel = vel; + float dec = STATIC_FRICTION + DYNAMIC_FRICTION*vel; + newvel = vel - dec*dt; + if (newvel < 0) + newvel = 0; + cam_vel[0] *= newvel/vel; + cam_vel[1] *= newvel/vel; + cam_vel[2] *= newvel/vel; + } + + camloc[0] += cam_vel[0] * dt; + camloc[1] += cam_vel[1] * dt; + camloc[2] += cam_vel[2] * dt; + + view_x_vel *= (float) pow(0.75, dt); + view_z_vel *= (float) pow(0.75, dt); + + view_x_vel += (pending_view_x - view_x_vel)*dt*60; + view_z_vel += (pending_view_z - view_z_vel)*dt*60; + + pending_view_x -= view_x_vel * dt; + pending_view_z -= view_z_vel * dt; + camang[0] += view_x_vel * dt; + camang[2] += view_z_vel * dt; + camang[0] = stb_clamp(camang[0], -90, 90); + camang[2] = (float) fmod(camang[2], 360); +} + +void process_tick(float dt) +{ + while (dt > 1.0f/60) { + process_tick_raw(1.0f/60); + dt -= 1.0f/60; + } + process_tick_raw(dt); +} + +void update_view(float dx, float dy) +{ + // hard-coded mouse sensitivity, not resolution independent? + pending_view_z -= dx*300; + pending_view_x -= dy*700; +} + +extern int screen_x, screen_y; +extern int is_synchronous_debug; +float render_time; + +extern int chunk_locations, chunks_considered, chunks_in_frustum; +extern int quads_considered, quads_rendered; +extern int chunk_storage_rendered, chunk_storage_considered, chunk_storage_total; +extern int view_dist_in_chunks; +extern int num_threads_active, num_meshes_started, num_meshes_uploaded; +extern float chunk_server_activity; + +static Uint64 start_time, end_time; // render time + +float chunk_server_status[32]; +int chunk_server_pos; + +void draw_stats(void) +{ + int i; + + static Uint64 last_frame_time; + Uint64 cur_time = SDL_GetPerformanceCounter(); + float chunk_server=0; + float frame_time = (cur_time - last_frame_time) / (float) SDL_GetPerformanceFrequency(); + last_frame_time = cur_time; + + chunk_server_status[chunk_server_pos] = chunk_server_activity; + chunk_server_pos = (chunk_server_pos+1) %32; + + for (i=0; i < 32; ++i) + chunk_server += chunk_server_status[i] / 32.0f; + + stb_easy_font_spacing(-0.75); + pos_y = 10; + text_color[0] = text_color[1] = text_color[2] = 1.0f; + print("Frame time: %6.2fms, CPU frame render time: %5.2fms", frame_time*1000, render_time*1000); + print("Tris: %4.1fM drawn of %4.1fM in range", 2*quads_rendered/1000000.0f, 2*quads_considered/1000000.0f); + print("Vbuf storage: %dMB in frustum of %dMB in range of %dMB in cache", chunk_storage_rendered>>20, chunk_storage_considered>>20, chunk_storage_total>>20); + print("Num mesh builds started this frame: %d; num uploaded this frame: %d\n", num_meshes_started, num_meshes_uploaded); + print("QChunks: %3d in frustum of %3d valid of %3d in range", chunks_in_frustum, chunks_considered, chunk_locations); + print("Mesh worker threads active: %d", num_threads_active); + print("View distance: %d blocks", view_dist_in_chunks*16); + print("%s", glGetString(GL_RENDERER)); + + if (is_synchronous_debug) { + text_color[0] = 1.0; + text_color[1] = 0.5; + text_color[2] = 0.5; + print("SLOWNESS: Synchronous debug output is enabled!"); + } +} + +void draw_main(void) +{ + glEnable(GL_CULL_FACE); + glDisable(GL_TEXTURE_2D); + glDisable(GL_LIGHTING); + glEnable(GL_DEPTH_TEST); + #ifdef REVERSE_DEPTH + glDepthFunc(GL_GREATER); + glClearDepth(0); + #else + glDepthFunc(GL_LESS); + glClearDepth(1); + #endif + glDepthMask(GL_TRUE); + glDisable(GL_SCISSOR_TEST); + glClearColor(0.6f,0.7f,0.9f,0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor3f(1,1,1); + glFrontFace(GL_CW); + glEnable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + #ifdef REVERSE_DEPTH + stbgl_Perspective(player_zoom, 90, 70, 3000, 1.0/16); + #else + stbgl_Perspective(player_zoom, 90, 70, 1.0/16, 3000); + #endif + + // now compute where the camera should be + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + stbgl_initCamera_zup_facing_y(); + + glRotatef(-camang[0],1,0,0); + glRotatef(-camang[2],0,0,1); + glTranslatef(-camloc[0], -camloc[1], -camloc[2]); + + start_time = SDL_GetPerformanceCounter(); + render_caves(camloc); + end_time = SDL_GetPerformanceCounter(); + + render_time = (end_time - start_time) / (float) SDL_GetPerformanceFrequency(); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluOrtho2D(0,screen_x/2,screen_y/2,0); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); + draw_stats(); +} + + + +#pragma warning(disable:4244; disable:4305; disable:4018) + +#define SCALE 2 + +void error(char *s) +{ + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", s, NULL); + exit(0); +} + +void ods(char *fmt, ...) +{ + char buffer[1000]; + va_list va; + va_start(va, fmt); + vsprintf(buffer, fmt, va); + va_end(va); + SDL_Log("%s", buffer); +} + +#define TICKS_PER_SECOND 60 + +static SDL_Window *window; + +extern void draw_main(void); +extern void process_tick(float dt); +extern void editor_init(void); + +void draw(void) +{ + draw_main(); + SDL_GL_SwapWindow(window); +} + + +static int initialized=0; +static float last_dt; + +int screen_x,screen_y; + +float carried_dt = 0; +#define TICKRATE 60 + +float tex2_alpha = 1.0; + +int raw_level_time; + +float global_timer; +int global_hack; + +int loopmode(float dt, int real, int in_client) +{ + if (!initialized) return 0; + + if (!real) + return 0; + + // don't allow more than 6 frames to update at a time + if (dt > 0.075) dt = 0.075; + + global_timer += dt; + + carried_dt += dt; + while (carried_dt > 1.0/TICKRATE) { + if (global_hack) { + tex2_alpha += global_hack / 60.0f; + if (tex2_alpha < 0) tex2_alpha = 0; + if (tex2_alpha > 1) tex2_alpha = 1; + } + //update_input(); + // if the player is dead, stop the sim + carried_dt -= 1.0/TICKRATE; + } + + process_tick(dt); + draw(); + + return 0; +} + +static int quit; + +extern int controls; + +void active_control_set(int key) +{ + controls |= 1 << key; +} + +void active_control_clear(int key) +{ + controls &= ~(1 << key); +} + +extern void update_view(float dx, float dy); + +void process_sdl_mouse(SDL_Event *e) +{ + update_view((float) e->motion.xrel / screen_x, (float) e->motion.yrel / screen_y); +} + +void process_event(SDL_Event *e) +{ + switch (e->type) { + case SDL_MOUSEMOTION: + process_sdl_mouse(e); + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + break; + + case SDL_QUIT: + quit = 1; + break; + + case SDL_WINDOWEVENT: + switch (e->window.event) { + case SDL_WINDOWEVENT_SIZE_CHANGED: + screen_x = e->window.data1; + screen_y = e->window.data2; + loopmode(0,1,0); + break; + } + break; + + case SDL_KEYDOWN: { + int k = e->key.keysym.sym; + int s = e->key.keysym.scancode; + SDL_Keymod mod; + mod = SDL_GetModState(); + if (k == SDLK_ESCAPE) + quit = 1; + + if (s == SDL_SCANCODE_D) active_control_set(0); + if (s == SDL_SCANCODE_A) active_control_set(1); + if (s == SDL_SCANCODE_W) active_control_set(2); + if (s == SDL_SCANCODE_S) active_control_set(3); + if (k == SDLK_SPACE) active_control_set(4); + if (s == SDL_SCANCODE_LCTRL) active_control_set(5); + if (s == SDL_SCANCODE_S) active_control_set(6); + if (s == SDL_SCANCODE_D) active_control_set(7); + if (k == '1') global_hack = !global_hack; + if (k == '2') global_hack = -1; + + #if 0 + if (game_mode == GAME_editor) { + switch (k) { + case SDLK_RIGHT: editor_key(STBTE_scroll_right); break; + case SDLK_LEFT : editor_key(STBTE_scroll_left ); break; + case SDLK_UP : editor_key(STBTE_scroll_up ); break; + case SDLK_DOWN : editor_key(STBTE_scroll_down ); break; + } + switch (s) { + case SDL_SCANCODE_S: editor_key(STBTE_tool_select); break; + case SDL_SCANCODE_B: editor_key(STBTE_tool_brush ); break; + case SDL_SCANCODE_E: editor_key(STBTE_tool_erase ); break; + case SDL_SCANCODE_R: editor_key(STBTE_tool_rectangle ); break; + case SDL_SCANCODE_I: editor_key(STBTE_tool_eyedropper); break; + case SDL_SCANCODE_L: editor_key(STBTE_tool_link); break; + case SDL_SCANCODE_G: editor_key(STBTE_act_toggle_grid); break; + } + if ((e->key.keysym.mod & KMOD_CTRL) && !(e->key.keysym.mod & ~KMOD_CTRL)) { + switch (s) { + case SDL_SCANCODE_X: editor_key(STBTE_act_cut ); break; + case SDL_SCANCODE_C: editor_key(STBTE_act_copy ); break; + case SDL_SCANCODE_V: editor_key(STBTE_act_paste); break; + case SDL_SCANCODE_Z: editor_key(STBTE_act_undo ); break; + case SDL_SCANCODE_Y: editor_key(STBTE_act_redo ); break; + } + } + } + #endif + break; + } + case SDL_KEYUP: { + int k = e->key.keysym.sym; + int s = e->key.keysym.scancode; + if (s == SDL_SCANCODE_D) active_control_clear(0); + if (s == SDL_SCANCODE_A) active_control_clear(1); + if (s == SDL_SCANCODE_W) active_control_clear(2); + if (s == SDL_SCANCODE_S) active_control_clear(3); + if (k == SDLK_SPACE) active_control_clear(4); + if (s == SDL_SCANCODE_LCTRL) active_control_clear(5); + if (s == SDL_SCANCODE_S) active_control_clear(6); + if (s == SDL_SCANCODE_D) active_control_clear(7); + break; + } + } +} + +static SDL_GLContext *context; + +static float getTimestep(float minimum_time) +{ + float elapsedTime; + double thisTime; + static double lastTime = -1; + + if (lastTime == -1) + lastTime = SDL_GetTicks() / 1000.0 - minimum_time; + + for(;;) { + thisTime = SDL_GetTicks() / 1000.0; + elapsedTime = (float) (thisTime - lastTime); + if (elapsedTime >= minimum_time) { + lastTime = thisTime; + return elapsedTime; + } + // @TODO: compute correct delay + SDL_Delay(1); + } +} + +void APIENTRY gl_debug(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *param) +{ + ods("%s\n", message); +} + +int is_synchronous_debug; +void enable_synchronous(void) +{ + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB); + is_synchronous_debug = 1; +} + +void prepare_threads(void); + +//void stbwingraph_main(void) +int SDL_main(int argc, char **argv) +{ + SDL_Init(SDL_INIT_VIDEO); + + prepare_threads(); + + SDL_GL_SetAttribute(SDL_GL_RED_SIZE , 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE , 8); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + + #ifdef GL_DEBUG + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); + #endif + + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); + + screen_x = 1920; + screen_y = 1080; + + window = SDL_CreateWindow("caveview", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + screen_x, screen_y, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE + ); + if (!window) error("Couldn't create window"); + + context = SDL_GL_CreateContext(window); + if (!context) error("Couldn't create context"); + + SDL_GL_MakeCurrent(window, context); // is this true by default? + + SDL_SetRelativeMouseMode(SDL_TRUE); + #if defined(_MSC_VER) && _MSC_VER < 1300 + // work around broken behavior in VC6 debugging + if (IsDebuggerPresent()) + SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1"); + #endif + + stbgl_initExtensions(); + + #ifdef GL_DEBUG + if (glDebugMessageCallbackARB) { + glDebugMessageCallbackARB(gl_debug, NULL); + + enable_synchronous(); + } + #endif + + SDL_GL_SetSwapInterval(1); + + render_init(); + mesh_init(); + world_init(); + + initialized = 1; + + while (!quit) { + SDL_Event e; + while (SDL_PollEvent(&e)) + process_event(&e); + + loopmode(getTimestep(0.0166f/8), 1, 1); + } + + return 0; +} diff --git a/source/engine/thirdparty/stb/tests/caveview/cave_mesher.c b/source/engine/thirdparty/stb/tests/caveview/cave_mesher.c new file mode 100644 index 0000000..1f76c89 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/caveview/cave_mesher.c @@ -0,0 +1,933 @@ +// This file takes minecraft chunks (decoded by cave_parse) and +// uses stb_voxel_render to turn them into vertex buffers. + +#define STB_GLEXT_DECLARE "glext_list.h" +#include "stb_gl.h" +#include "stb_image.h" +#include "stb_glprog.h" + +#include "caveview.h" +#include "cave_parse.h" +#include "stb.h" +#include "sdl.h" +#include "sdl_thread.h" +#include + +//#define VHEIGHT_TEST +//#define STBVOX_OPTIMIZED_VHEIGHT + +#define STBVOX_CONFIG_MODE 1 +#define STBVOX_CONFIG_OPENGL_MODELVIEW +#define STBVOX_CONFIG_PREFER_TEXBUFFER +//#define STBVOX_CONFIG_LIGHTING_SIMPLE +#define STBVOX_CONFIG_FOG_SMOOTHSTEP +//#define STBVOX_CONFIG_PREMULTIPLIED_ALPHA // this doesn't work properly alpha test without next #define +//#define STBVOX_CONFIG_UNPREMULTIPLY // slower, fixes alpha test makes windows & fancy leaves look better +//#define STBVOX_CONFIG_TEX1_EDGE_CLAMP +#define STBVOX_CONFIG_DISABLE_TEX2 +//#define STBVOX_CONFIG_DOWN_TEXLERP_PACKED +#define STBVOX_CONFIG_ROTATION_IN_LIGHTING + +#define STB_VOXEL_RENDER_IMPLEMENTATION +#include "stb_voxel_render.h" + +extern void ods(char *fmt, ...); + +//#define FANCY_LEAVES // nearly 2x the triangles when enabled (if underground is filled) +#define FAST_CHUNK +#define IN_PLACE + +#define SKIP_TERRAIN 0 +//#define SKIP_TERRAIN 48 // use to avoid building underground stuff + // allows you to see what perf would be like if underground was efficiently culled, + // or if you were making a game without underground + +enum +{ + C_empty, + C_solid, + C_trans, + C_cross, + C_water, + C_slab, + C_stair, + C_force, +}; + +unsigned char geom_map[] = +{ + STBVOX_GEOM_empty, + STBVOX_GEOM_solid, + STBVOX_GEOM_transp, + STBVOX_GEOM_crossed_pair, + STBVOX_GEOM_solid, + STBVOX_GEOM_slab_lower, + STBVOX_GEOM_floor_slope_north_is_top, + STBVOX_GEOM_force, +}; + +unsigned char minecraft_info[256][7] = +{ + { C_empty, 0,0,0,0,0,0 }, + { C_solid, 1,1,1,1,1,1 }, + { C_solid, 3,3,3,3,40,2 }, + { C_solid, 2,2,2,2,2,2 }, + { C_solid, 16,16,16,16,16,16 }, + { C_solid, 4,4,4,4,4,4 }, + { C_cross, 15,15,15,15 }, + { C_solid, 17,17,17,17,17,17 }, + + // 8 + { C_water, 223,223,223,223,223,223 }, + { C_water, 223,223,223,223,223,223 }, + { C_solid, 255,255,255,255,255,255 }, + { C_solid, 255,255,255,255,255,255 }, + { C_solid, 18,18,18,18,18,18 }, + { C_solid, 19,19,19,19,19,19 }, + { C_solid, 32,32,32,32,32,32 }, + { C_solid, 33,33,33,33,33,33 }, + + // 16 + { C_solid, 34,34,34,34,34,34 }, + { C_solid, 20,20,20,20,21,21 }, +#ifdef FANCY_LEAVES + { C_force, 52,52,52,52,52,52 }, // leaves +#else + { C_solid, 53,53,53,53,53,53 }, // leaves +#endif + { C_solid, 24,24,24,24,24,24 }, + { C_trans, 49,49,49,49,49,49 }, // glass + { C_solid, 160,160,160,160,160,160 }, + { C_solid, 144,144,144,144,144,144 }, + { C_solid, 46,45,45,45,62,62 }, + + // 24 + { C_solid, 192,192,192,192, 176,176 }, + { C_solid, 74,74,74,74,74,74 }, + { C_empty }, // bed + { C_empty }, // powered rail + { C_empty }, // detector rail + { C_solid, 106,108,109,108,108,108 }, + { C_empty }, // cobweb=11 + { C_cross, 39,39,39,39 }, + + // 32 + { C_cross, 55,55,55,55,0,0 }, + { C_solid, 107,108,109,108,108,108 }, + { C_empty }, // piston head + { C_solid, 64,64,64,64,64,64 }, // various colors + { C_empty }, // unused + { C_cross, 13,13,13,13,0,0 }, + { C_cross, 12,12,12,12,0,0 }, + { C_cross, 29,29,29,29,0,0 }, + + // 40 + { C_cross, 28,28,28,28,0,0 }, + { C_solid, 23,23,23,23,23,23 }, + { C_solid, 22,22,22,22,22,22 }, + { C_solid, 5,5,5,5,6,6, }, + { C_slab , 5,5,5,5,6,6, }, + { C_solid, 7,7,7,7,7,7, }, + { C_solid, 8,8,8,8,9,10 }, + { C_solid, 35,35,35,35,4,4, }, + + // 48 + //{ C_solid, 36,36,36,36,36,36 }, + { C_force, 36,36,36,36,36,36 }, + { C_solid, 37,37,37,37,37,37 }, + { C_cross, 80,80,80,80,80,80 }, // torch + { C_empty }, // fire + { C_trans, 65,65,65,65,65,65 }, + { C_stair, 4,4,4,4,4,4 }, + { C_solid, 26,26,26,27,25,25 }, + { C_empty }, // redstone + + // 56 + { C_solid, 50,50,50,50,50,50 }, + //{ C_force, 50,50,50,50,50,50 }, + { C_solid, 26,26,26,26,26,26 }, + { C_solid, 60,59,59,59,43,43 }, + { C_cross, 95,95,95,95 }, + { C_solid, 2,2,2,2,86,2 }, + { C_solid, 44,45,45,45,62,62 }, + { C_solid, 61,45,45,45,62,62 }, + { C_empty }, // sign + + // 64 + { C_empty }, // door + { C_empty }, // ladder + { C_empty }, // rail + { C_stair, 16,16,16,16,16,16 }, // cobblestone stairs + { C_empty }, // sign + { C_empty }, // lever + { C_empty }, // stone pressure plate + { C_empty }, // iron door + + // 72 + { C_empty }, // wooden pressure + { C_solid, 51,51,51,51,51,51 }, + { C_solid, 51,51,51,51,51,51 }, + { C_empty }, + { C_empty }, + { C_empty }, + { C_empty }, // snow on block below, do as half slab? + { C_solid, 67,67,67,67,67,67 }, + + // 80 + { C_solid, 66,66,66,66,66,66 }, + { C_solid, 70,70,70,70,69,71 }, + { C_solid, 72,72,72,72,72,72 }, + { C_cross, 73,73,73,73,73,73 }, + { C_solid, 74,74,74,74,75,74 }, + { C_empty }, // fence + { C_solid,119,118,118,118,102,102 }, + { C_solid,103,103,103,103,103,103 }, + + // 88 + { C_solid, 104,104,104,104,104,104 }, + { C_solid, 105,105,105,105,105,105 }, + { C_solid, 167,167,167,167,167,167 }, + { C_solid, 120,118,118,118,102,102 }, + { C_empty }, // cake + { C_empty }, // repeater + { C_empty }, // repeater + { C_solid, 49,49,49,49,49,49 }, // colored glass + + // 96 + { C_empty }, + { C_empty }, + { C_solid, 54,54,54,54,54,54 }, + { C_solid, 125,125,125,125,125,125 }, + { C_solid, 126,126,126,126,126,126 }, + { C_empty }, // bars + { C_trans, 49,49,49,49,49,49 }, // glass pane + { C_solid, 136,136,136,136,137,137 }, // melon + + // 104 + { C_empty }, // pumpkin stem + { C_empty }, // melon stem + { C_empty }, // vines + { C_empty }, // gate + { C_stair, 7,7,7,7,7,7, }, // brick stairs + { C_stair, 54,54,54,54,54,54 }, // stone brick stairs + { C_empty }, // mycelium + { C_empty }, // lily pad + + // 112 + { C_solid, 224,224,224,224,224,224 }, + { C_empty }, // nether brick fence + { C_stair, 224,224,224,224,224,224 }, // nether brick stairs + { C_empty }, // nether wart + { C_solid, 182,182,182,182,166,183 }, + { C_empty }, // brewing stand + { C_empty }, // cauldron + { C_empty }, // end portal + + // 120 + { C_solid, 159,159,159,159,158,158 }, + { C_solid, 175,175,175,175,175,175 }, + { C_empty }, // dragon egg + { C_solid, 211,211,211,211,211,211 }, + { C_solid, 212,212,212,212,212,212 }, + { C_solid, 4,4,4,4,4,4, }, // wood double-slab + { C_slab , 4,4,4,4,4,4, }, // wood slab + { C_empty }, // cocoa + + // 128 + { C_solid, 192,192,192,192,176,176 }, // sandstone stairs + { C_solid, 32,32,32,32,32,32 }, // emerald ore + { C_solid, 26,26,26,27,25,25 }, // ender chest + { C_empty }, + { C_empty }, + { C_solid, 23,23,23,23,23,23 }, // emerald block + { C_solid, 198,198,198,198,198,198 }, // spruce stairs + { C_solid, 214,214,214,214,214,214 }, // birch stairs + + // 136 + { C_stair, 199,199,199,199,199,199 }, // jungle stairs + { C_empty }, // command block + { C_empty }, // beacon + { C_slab, 16,16,16,16,16,16 }, // cobblestone wall + { C_empty }, // flower pot + { C_empty }, // carrot + { C_empty }, // potatoes + { C_empty }, // wooden button + + // 144 + { C_empty }, // mob head + { C_empty }, // anvil + { C_solid, 26,26,26,27,25,25 }, // trapped chest + { C_empty }, // weighted pressure plate light + { C_empty }, // weighted pressure plat eheavy + { C_empty }, // comparator inactive + { C_empty }, // comparator active + { C_empty }, // daylight sensor + + // 152 + { C_solid, 135,135,135,135,135,135 }, // redstone block + { C_solid, 0,0,0,0,0,0, }, // nether quartz ore + { C_empty }, // hopper + { C_solid, 22,22,22,22,22,22 }, // quartz block + { C_stair, 22,22,22,22,22,22 }, // quartz stairs + { C_empty }, // activator rail + { C_solid, 46,45,45,45,62,62 }, // dropper + { C_solid, 72,72,72,72,72,72 }, // stained clay + + // 160 + { C_trans, 49,49,49,49,49,49 }, // stained glass pane + #ifdef FANCY_LEAVES + { C_force, 52,52,52,52,52,52 }, // leaves + #else + { C_solid, 53,53,53,53,53,53 }, // acacia leaves + #endif + { C_solid, 20,20,20,20,21,21 }, // acacia tree + { C_solid, 199,199,199,199,199,199 }, // acacia wood stairs + { C_solid, 198,198,198,198,198,198 }, // dark oak stairs + { C_solid, 146,146,146,146,146,146 }, // slime block + + { C_solid, 176,176,176,176,176,176 }, // red sandstone + { C_solid, 176,176,176,176,176,176 }, // red sandstone + + // 168 + { C_empty }, + { C_empty }, + { C_empty }, + { C_empty }, + { C_solid, 72,72,72,72,72,72 }, // hardened clay + { C_empty }, + { C_empty }, + { C_empty }, + + // 176 + { C_empty }, + { C_empty }, + { C_solid, 176,176,176,176,176,176 }, // red sandstone +}; + +unsigned char minecraft_tex1_for_blocktype[256][6]; +unsigned char effective_blocktype[256]; +unsigned char minecraft_color_for_blocktype[256][6]; +unsigned char minecraft_geom_for_blocktype[256]; + +uint8 build_buffer[BUILD_BUFFER_SIZE]; +uint8 face_buffer[FACE_BUFFER_SIZE]; + +//GLuint vbuf, fbuf, fbuf_tex; + +//unsigned char tex1_for_blocktype[256][6]; + +//unsigned char blocktype[34][34][257]; +//unsigned char lighting[34][34][257]; + +// a superchunk is 64x64x256, with the border blocks computed as well, +// which means we need 4x4 chunks plus 16 border chunks plus 4 corner chunks + +#define SUPERCHUNK_X 4 +#define SUPERCHUNK_Y 4 + +unsigned char remap_data[16][16]; +unsigned char remap[256]; +unsigned char rotate_data[4] = { 1,3,2,0 }; + +void convert_fastchunk_inplace(fast_chunk *fc) +{ + int i; + int num_blocks=0, step=0; + unsigned char rot[4096]; + #ifndef IN_PLACE + unsigned char *storage; + #endif + + memset(rot, 0, 4096); + + for (i=0; i < 16; ++i) + num_blocks += fc->blockdata[i] != NULL; + + #ifndef IN_PLACE + storage = malloc(16*16*16*2 * num_blocks); + #endif + + for (i=0; i < 16; ++i) { + if (fc->blockdata[i]) { + int o=0; + unsigned char *bd,*dd,*lt,*sky; + unsigned char *out, *outb; + + // this ordering allows us to determine which data we can safely overwrite for in-place processing + bd = fc->blockdata[i]; + dd = fc->data[i]; + lt = fc->light[i]; + sky = fc->skylight[i]; + + #ifdef IN_PLACE + out = bd; + #else + out = storage + 16*16*16*2*step; + #endif + + // bd is written in place, but also reads from dd + for (o=0; o < 16*16*16/2; o += 1) { + unsigned char v1,v2; + unsigned char d = dd[o]; + v1 = bd[o*2+0]; + v2 = bd[o*2+1]; + + if (remap[v1]) + { + //unsigned char d = bd[o] & 15; + v1 = remap_data[remap[v1]][d&15]; + rot[o*2+0] = rotate_data[d&3]; + } else + v1 = effective_blocktype[v1]; + + if (remap[v2]) + { + //unsigned char d = bd[o] >> 4; + v2 = remap_data[remap[v2]][d>>4]; + rot[o*2+1] = rotate_data[(d>>4)&3]; + } else + v2 = effective_blocktype[v2]; + + out[o*2+0] = v1; + out[o*2+1] = v2; + } + + // this reads from lt & sky + #ifndef IN_PLACE + outb = out + 16*16*16; + ++step; + #endif + + // MC used to write in this order and it makes it possible to compute in-place + if (dd < sky && sky < lt) { + // @TODO go this path always if !IN_PLACE + #ifdef IN_PLACE + outb = dd; + #endif + + for (o=0; o < 16*16*16/2; ++o) { + int bright; + bright = (lt[o]&15)*12 + 15 + (sky[o]&15)*16; + if (bright > 255) bright = 255; + if (bright < 32) bright = 32; + outb[o*2+0] = STBVOX_MAKE_LIGHTING_EXT((unsigned char) bright, (rot[o*2+0]&3)); + + bright = (lt[o]>>4)*12 + 15 + (sky[o]>>4)*16; + if (bright > 255) bright = 255; + if (bright < 32) bright = 32; + outb[o*2+1] = STBVOX_MAKE_LIGHTING_EXT((unsigned char) bright, (rot[o*2+1]&3)); + } + } else { + // @TODO: if blocktype is in between others, this breaks; need to find which side has two pointers, and use that + // overwrite rot[] array, then copy out + #ifdef IN_PLACE + outb = (dd < sky) ? dd : sky; + if (lt < outb) lt = outb; + #endif + + for (o=0; o < 16*16*16/2; ++o) { + int bright; + bright = (lt[o]&15)*12 + 15 + (sky[o]&15)*16; + if (bright > 255) bright = 255; + if (bright < 32) bright = 32; + rot[o*2+0] = STBVOX_MAKE_LIGHTING_EXT((unsigned char) bright, (rot[o*2+0]&3)); + + bright = (lt[o]>>4)*12 + 15 + (sky[o]>>4)*16; + if (bright > 255) bright = 255; + if (bright < 32) bright = 32; + rot[o*2+1] = STBVOX_MAKE_LIGHTING_EXT((unsigned char) bright, (rot[o*2+1]&3)); + } + + memcpy(outb, rot, 4096); + fc->data[i] = outb; + } + + #ifndef IN_PLACE + fc->blockdata[i] = out; + fc->data[i] = outb; + #endif + } + } + + #ifndef IN_PLACE + free(fc->pointer_to_free); + fc->pointer_to_free = storage; + #endif +} + +void make_converted_fastchunk(fast_chunk *fc, int x, int y, int segment, uint8 *sv_blocktype, uint8 *sv_lighting) +{ + int z; + assert(fc == NULL || (fc->refcount > 0 && fc->refcount < 64)); + if (fc == NULL || fc->blockdata[segment] == NULL) { + for (z=0; z < 16; ++z) { + sv_blocktype[z] = C_empty; + sv_lighting[z] = 255; + } + } else { + unsigned char *block = fc->blockdata[segment]; + unsigned char *data = fc->data[segment]; + y = 15-y; + for (z=0; z < 16; ++z) { + sv_blocktype[z] = block[z*256 + y*16 + x]; + sv_lighting [z] = data [z*256 + y*16 + x]; + } + } +} + + +#define CHUNK_CACHE 64 +typedef struct +{ + int valid; + int chunk_x, chunk_y; + fast_chunk *fc; +} cached_converted_chunk; + +cached_converted_chunk chunk_cache[CHUNK_CACHE][CHUNK_CACHE]; +int cache_size = CHUNK_CACHE; + +void reset_cache_size(int size) +{ + int i,j; + for (j=size; j < cache_size; ++j) { + for (i=size; i < cache_size; ++i) { + cached_converted_chunk *ccc = &chunk_cache[j][i]; + if (ccc->valid) { + if (ccc->fc) { + free(ccc->fc->pointer_to_free); + free(ccc->fc); + ccc->fc = NULL; + } + ccc->valid = 0; + } + } + } + cache_size = size; +} + +// this must be called inside mutex +void deref_fastchunk(fast_chunk *fc) +{ + if (fc) { + assert(fc->refcount > 0); + --fc->refcount; + if (fc->refcount == 0) { + free(fc->pointer_to_free); + free(fc); + } + } +} + +SDL_mutex * chunk_cache_mutex; +SDL_mutex * chunk_get_mutex; + +void lock_chunk_get_mutex(void) +{ + SDL_LockMutex(chunk_get_mutex); +} +void unlock_chunk_get_mutex(void) +{ + SDL_UnlockMutex(chunk_get_mutex); +} + +fast_chunk *get_converted_fastchunk(int chunk_x, int chunk_y) +{ + int slot_x = (chunk_x & (cache_size-1)); + int slot_y = (chunk_y & (cache_size-1)); + fast_chunk *fc; + cached_converted_chunk *ccc; + SDL_LockMutex(chunk_cache_mutex); + ccc = &chunk_cache[slot_y][slot_x]; + if (ccc->valid) { + if (ccc->chunk_x == chunk_x && ccc->chunk_y == chunk_y) { + fast_chunk *fc = ccc->fc; + if (fc) + ++fc->refcount; + SDL_UnlockMutex(chunk_cache_mutex); + return fc; + } + if (ccc->fc) { + deref_fastchunk(ccc->fc); + ccc->fc = NULL; + ccc->valid = 0; + } + } + SDL_UnlockMutex(chunk_cache_mutex); + + fc = get_decoded_fastchunk_uncached(chunk_x, -chunk_y); + if (fc) + convert_fastchunk_inplace(fc); + + SDL_LockMutex(chunk_cache_mutex); + // another thread might have updated it, so before we overwrite it... + if (ccc->fc) { + deref_fastchunk(ccc->fc); + ccc->fc = NULL; + } + + if (fc) + fc->refcount = 1; // 1 in the cache + + ccc->chunk_x = chunk_x; + ccc->chunk_y = chunk_y; + ccc->valid = 1; + if (fc) + ++fc->refcount; + ccc->fc = fc; + SDL_UnlockMutex(chunk_cache_mutex); + return fc; +} + +void make_map_segment_for_superchunk_preconvert(int chunk_x, int chunk_y, int segment, fast_chunk *fc_table[4][4], uint8 sv_blocktype[34][34][18], uint8 sv_lighting[34][34][18]) +{ + int a,b; + assert((chunk_x & 1) == 0); + assert((chunk_y & 1) == 0); + for (b=-1; b < 3; ++b) { + for (a=-1; a < 3; ++a) { + int xo = a*16+1; + int yo = b*16+1; + int x,y; + fast_chunk *fc = fc_table[b+1][a+1]; + for (y=0; y < 16; ++y) + for (x=0; x < 16; ++x) + if (xo+x >= 0 && xo+x < 34 && yo+y >= 0 && yo+y < 34) + make_converted_fastchunk(fc,x,y, segment, sv_blocktype[xo+x][yo+y], sv_lighting[xo+x][yo+y]); + } + } +} + +// build 1 mesh covering 2x2 chunks +void build_chunk(int chunk_x, int chunk_y, fast_chunk *fc_table[4][4], raw_mesh *rm) +{ + int a,b,z; + stbvox_input_description *map; + + #ifdef VHEIGHT_TEST + unsigned char vheight[34][34][18]; + #endif + + #ifndef STBVOX_CONFIG_DISABLE_TEX2 + unsigned char tex2_choice[34][34][18]; + #endif + + assert((chunk_x & 1) == 0); + assert((chunk_y & 1) == 0); + + rm->cx = chunk_x; + rm->cy = chunk_y; + + stbvox_set_input_stride(&rm->mm, 34*18, 18); + + assert(rm->mm.input.geometry == NULL); + + map = stbvox_get_input_description(&rm->mm); + map->block_tex1_face = minecraft_tex1_for_blocktype; + map->block_color_face = minecraft_color_for_blocktype; + map->block_geometry = minecraft_geom_for_blocktype; + + stbvox_reset_buffers(&rm->mm); + stbvox_set_buffer(&rm->mm, 0, 0, rm->build_buffer, BUILD_BUFFER_SIZE); + stbvox_set_buffer(&rm->mm, 0, 1, rm->face_buffer , FACE_BUFFER_SIZE); + + map->blocktype = &rm->sv_blocktype[1][1][1]; // this is (0,0,0), but we need to be able to query off the edges + map->lighting = &rm->sv_lighting[1][1][1]; + + // fill in the top two rows of the buffer + for (a=0; a < 34; ++a) { + for (b=0; b < 34; ++b) { + rm->sv_blocktype[a][b][16] = 0; + rm->sv_lighting [a][b][16] = 255; + rm->sv_blocktype[a][b][17] = 0; + rm->sv_lighting [a][b][17] = 255; + } + } + + #ifndef STBVOX_CONFIG_DISABLE_TEX2 + for (a=0; a < 34; ++a) { + for (b=0; b < 34; ++b) { + int px = chunk_x*16 + a - 1; + int py = chunk_y*16 + b - 1; + float dist = (float) sqrt(px*px + py*py); + float s1 = (float) sin(dist / 16), s2, s3; + dist = (float) sqrt((px-80)*(px-80) + (py-50)*(py-50)); + s2 = (float) sin(dist / 11); + for (z=0; z < 18; ++z) { + s3 = (float) sin(z * 3.141592 / 8); + + s3 = s1*s2*s3; + tex2_choice[a][b][z] = 63 & (int) stb_linear_remap(s3,-1,1, -20,83); + } + } + } + #endif + + for (z=256-16; z >= SKIP_TERRAIN; z -= 16) + { + int z0 = z; + int z1 = z+16; + if (z1 == 256) z1 = 255; + + make_map_segment_for_superchunk_preconvert(chunk_x, chunk_y, z >> 4, fc_table, rm->sv_blocktype, rm->sv_lighting); + + map->blocktype = &rm->sv_blocktype[1][1][1-z]; // specify location of 0,0,0 so that accessing z0..z1 gets right data + map->lighting = &rm->sv_lighting[1][1][1-z]; + #ifndef STBVOX_CONFIG_DISABLE_TEX2 + map->tex2 = &tex2_choice[1][1][1-z]; + #endif + + #ifdef VHEIGHT_TEST + // hacky test of vheight + for (a=0; a < 34; ++a) { + for (b=0; b < 34; ++b) { + int c; + for (c=0; c < 17; ++c) { + if (rm->sv_blocktype[a][b][c] != 0 && rm->sv_blocktype[a][b][c+1] == 0) { + // topmost block + vheight[a][b][c] = rand() & 255; + rm->sv_blocktype[a][b][c] = 168; + } else if (c > 0 && rm->sv_blocktype[a][b][c] != 0 && rm->sv_blocktype[a][b][c-1] == 0) { + // bottommost block + vheight[a][b][c] = ((rand() % 3) << 6) + ((rand() % 3) << 4) + ((rand() % 3) << 2) + (rand() % 3); + rm->sv_blocktype[a][b][c] = 169; + } + } + vheight[a][b][c] = STBVOX_MAKE_VHEIGHT(2,2,2,2); // flat top + } + } + map->vheight = &vheight[1][1][1-z]; + #endif + + { + stbvox_set_input_range(&rm->mm, 0,0,z0, 32,32,z1); + stbvox_set_default_mesh(&rm->mm, 0); + stbvox_make_mesh(&rm->mm); + } + + // copy the bottom two rows of data up to the top + for (a=0; a < 34; ++a) { + for (b=0; b < 34; ++b) { + rm->sv_blocktype[a][b][16] = rm->sv_blocktype[a][b][0]; + rm->sv_blocktype[a][b][17] = rm->sv_blocktype[a][b][1]; + rm->sv_lighting [a][b][16] = rm->sv_lighting [a][b][0]; + rm->sv_lighting [a][b][17] = rm->sv_lighting [a][b][1]; + } + } + } + + stbvox_set_mesh_coordinates(&rm->mm, chunk_x*16, chunk_y*16, 0); + stbvox_get_transform(&rm->mm, rm->transform); + + stbvox_set_input_range(&rm->mm, 0,0,0, 32,32,255); + stbvox_get_bounds(&rm->mm, rm->bounds); + + rm->num_quads = stbvox_get_quad_count(&rm->mm, 0); +} + +int next_blocktype = 255; + +unsigned char mc_rot[4] = { 1,3,2,0 }; + +// create blocktypes with rotation baked into type... +// @TODO we no longer need this now that we store rotations +// in lighting +void build_stair_rotations(int blocktype, unsigned char *map) +{ + int i; + + // use the existing block type for floor stairs; allocate a new type for ceil stairs + for (i=0; i < 6; ++i) { + minecraft_color_for_blocktype[next_blocktype][i] = minecraft_color_for_blocktype[blocktype][i]; + minecraft_tex1_for_blocktype [next_blocktype][i] = minecraft_tex1_for_blocktype [blocktype][i]; + } + minecraft_geom_for_blocktype[next_blocktype] = (unsigned char) STBVOX_MAKE_GEOMETRY(STBVOX_GEOM_ceil_slope_north_is_bottom, 0, 0); + minecraft_geom_for_blocktype[ blocktype] = (unsigned char) STBVOX_MAKE_GEOMETRY(STBVOX_GEOM_floor_slope_north_is_top, 0, 0); + + for (i=0; i < 4; ++i) { + map[0+i+8] = map[0+i] = blocktype; + map[4+i+8] = map[4+i] = next_blocktype; + } + --next_blocktype; +} + +void build_wool_variations(int bt, unsigned char *map) +{ + int i,k; + unsigned char tex[16] = { 64, 210, 194, 178, 162, 146, 130, 114, 225, 209, 193, 177, 161, 145, 129, 113 }; + for (i=0; i < 16; ++i) { + if (i == 0) + map[i] = bt; + else { + map[i] = next_blocktype; + for (k=0; k < 6; ++k) { + minecraft_tex1_for_blocktype[next_blocktype][k] = tex[i]; + } + minecraft_geom_for_blocktype[next_blocktype] = minecraft_geom_for_blocktype[bt]; + --next_blocktype; + } + } +} + +void build_wood_variations(int bt, unsigned char *map) +{ + int i,k; + unsigned char tex[4] = { 5, 198, 214, 199 }; + for (i=0; i < 4; ++i) { + if (i == 0) + map[i] = bt; + else { + map[i] = next_blocktype; + for (k=0; k < 6; ++k) { + minecraft_tex1_for_blocktype[next_blocktype][k] = tex[i]; + } + minecraft_geom_for_blocktype[next_blocktype] = minecraft_geom_for_blocktype[bt]; + --next_blocktype; + } + } + map[i] = map[i-1]; + ++i; + for (; i < 16; ++i) + map[i] = bt; +} + +void remap_in_place(int bt, int rm) +{ + int i; + remap[bt] = rm; + for (i=0; i < 16; ++i) + remap_data[rm][i] = bt; +} + + +void mesh_init(void) +{ + int i; + + chunk_cache_mutex = SDL_CreateMutex(); + chunk_get_mutex = SDL_CreateMutex(); + + for (i=0; i < 256; ++i) { + memcpy(minecraft_tex1_for_blocktype[i], minecraft_info[i]+1, 6); + effective_blocktype[i] = (minecraft_info[i][0] == C_empty ? 0 : i); + minecraft_geom_for_blocktype[i] = geom_map[minecraft_info[i][0]]; + } + //effective_blocktype[50] = 0; // delete torches + + for (i=0; i < 6*256; ++i) { + if (minecraft_tex1_for_blocktype[0][i] == 40) + minecraft_color_for_blocktype[0][i] = 38 | 64; // apply to tex1 + if (minecraft_tex1_for_blocktype[0][i] == 39) + minecraft_color_for_blocktype[0][i] = 39 | 64; // apply to tex1 + if (minecraft_tex1_for_blocktype[0][i] == 105) + minecraft_color_for_blocktype[0][i] = 63; // emissive + if (minecraft_tex1_for_blocktype[0][i] == 212) + minecraft_color_for_blocktype[0][i] = 63; // emissive + if (minecraft_tex1_for_blocktype[0][i] == 80) + minecraft_color_for_blocktype[0][i] = 63; // emissive + } + + for (i=0; i < 6; ++i) { + minecraft_color_for_blocktype[172][i] = 47 | 64; // apply to tex1 + minecraft_color_for_blocktype[178][i] = 47 | 64; // apply to tex1 + minecraft_color_for_blocktype[18][i] = 39 | 64; // green + minecraft_color_for_blocktype[161][i] = 37 | 64; // green + minecraft_color_for_blocktype[10][i] = 63; // emissive lava + minecraft_color_for_blocktype[11][i] = 63; // emissive + //minecraft_color_for_blocktype[56][i] = 63; // emissive diamond + minecraft_color_for_blocktype[48][i] = 63; // emissive dungeon + } + + #ifdef VHEIGHT_TEST + effective_blocktype[168] = 168; + minecraft_tex1_for_blocktype[168][0] = 1; + minecraft_tex1_for_blocktype[168][1] = 1; + minecraft_tex1_for_blocktype[168][2] = 1; + minecraft_tex1_for_blocktype[168][3] = 1; + minecraft_tex1_for_blocktype[168][4] = 1; + minecraft_tex1_for_blocktype[168][5] = 1; + minecraft_geom_for_blocktype[168] = STBVOX_GEOM_floor_vheight_12; + effective_blocktype[169] = 169; + minecraft_tex1_for_blocktype[169][0] = 1; + minecraft_tex1_for_blocktype[169][1] = 1; + minecraft_tex1_for_blocktype[169][2] = 1; + minecraft_tex1_for_blocktype[169][3] = 1; + minecraft_tex1_for_blocktype[169][4] = 1; + minecraft_tex1_for_blocktype[169][5] = 1; + minecraft_geom_for_blocktype[169] = STBVOX_GEOM_ceil_vheight_03; + #endif + + remap[53] = 1; + remap[67] = 2; + remap[108] = 3; + remap[109] = 4; + remap[114] = 5; + remap[136] = 6; + remap[156] = 7; + for (i=0; i < 256; ++i) + if (remap[i]) + build_stair_rotations(i, remap_data[remap[i]]); + remap[35] = 8; + build_wool_variations(35, remap_data[remap[35]]); + remap[5] = 11; + build_wood_variations(5, remap_data[remap[5]]); + + // set the remap flags for these so they write the rotation values + remap_in_place(54, 9); + remap_in_place(146, 10); +} + +// Timing stats while optimizing the single-threaded builder + +// 32..-32, 32..-32, SKIP_TERRAIN=0, !FANCY_LEAVES on 'mcrealm' data set + +// 6.27s - reblocked to do 16 z at a time instead of 256 (still using 66x66x258), 4 meshes in parallel +// 5.96s - reblocked to use FAST_CHUNK (no intermediate data structure) +// 5.45s - unknown change, or previous measurement was wrong + +// 6.12s - use preconverted data, not in-place +// 5.91s - use preconverted, in-place +// 5.34s - preconvert, in-place, avoid dependency chain (suggested by ryg) +// 5.34s - preconvert, in-place, avoid dependency chain, use bit-table instead of byte-table +// 5.50s - preconvert, in-place, branchless + +// 6.42s - non-preconvert, avoid dependency chain (not an error) +// 5.40s - non-preconvert, w/dependency chain (same as earlier) + +// 5.50s - non-FAST_CHUNK, reblocked outer loop for better cache reuse +// 4.73s - FAST_CHUNK non-preconvert, reblocked outer loop +// 4.25s - preconvert, in-place, reblocked outer loop +// 4.18s - preconvert, in-place, unrolled again +// 4.10s - 34x34 1 mesh instead of 66x66 and 4 meshes (will make it easier to do multiple threads) + +// 4.83s - building bitmasks but not using them (2 bits per block, one if empty, one if solid) + +// 5.16s - using empty bitmasks to early out +// 5.01s - using solid & empty bitmasks to early out - "foo" +// 4.64s - empty bitmask only, test 8 at a time, then test geom +// 4.72s - empty bitmask only, 8 at a time, then test bits +// 4.46s - split bitmask building into three loops (each byte is separate) +// 4.42s - further optimize computing bitmask + +// 4.58s - using solid & empty bitmasks to early out, same as "foo" but faster bitmask building +// 4.12s - using solid & empty bitmasks to efficiently test neighbors +// 4.04s - using 16-bit fetches (not endian-independent) +// - note this is first place that beats previous best '4.10s - 34x34 1 mesh' + +// 4.30s - current time with bitmasks disabled again (note was 4.10s earlier) +// 3.95s - bitmasks enabled again, no other changes +// 4.00s - current time with bitmasks disabled again, no other changes -- wide variation that is time dependent? +// (note that most of the numbers listed here are median of 3 values already) +// 3.98s - bitmasks enabled + +// Bitmasks removed from the code as not worth the complexity increase + + + +// Raw data for Q&A: +// +// 26% parsing & loading minecraft files (4/5ths of which is zlib decode) +// 39% building mesh from stb input format +// 18% converting from minecraft blocks to stb blocks +// 9% reordering from minecraft axis order to stb axis order +// 7% uploading vertex buffer to OpenGL diff --git a/source/engine/thirdparty/stb/tests/caveview/cave_parse.c b/source/engine/thirdparty/stb/tests/caveview/cave_parse.c new file mode 100644 index 0000000..e8ae02b --- /dev/null +++ b/source/engine/thirdparty/stb/tests/caveview/cave_parse.c @@ -0,0 +1,632 @@ +#include +#include +#include +#include + +#define FAST_CHUNK // disabling this enables the old, slower path that deblocks into a regular form + +#include "cave_parse.h" + +#include "stb_image.h" +#include "stb.h" + +#define NUM_CHUNKS_PER_REGION 32 // only on one axis +#define NUM_CHUNKS_PER_REGION_LOG2 5 + +#define NUM_COLUMNS_PER_CHUNK 16 +#define NUM_COLUMNS_PER_CHUNK_LOG2 4 + +uint32 read_uint32_be(FILE *f) +{ + unsigned char data[4]; + fread(data, 1, 4, f); + return (data[0]<<24) + (data[1]<<16) + (data[2]<<8) + data[3]; +} + +typedef struct +{ + uint8 *data; + size_t len; + int x,z; // chunk index + int refcount; // for multi-threading +} compressed_chunk; + +typedef struct +{ + int x,z; + uint32 sector_data[NUM_CHUNKS_PER_REGION][NUM_CHUNKS_PER_REGION]; +} region; + +size_t cached_compressed=0; + +FILE *last_region; +int last_region_x; +int last_region_z; +int opened=0; + +static void open_file(int reg_x, int reg_z) +{ + if (!opened || last_region_x != reg_x || last_region_z != reg_z) { + char filename[256]; + if (last_region != NULL) + fclose(last_region); + sprintf(filename, "r.%d.%d.mca", reg_x, reg_z); + last_region = fopen(filename, "rb"); + last_region_x = reg_x; + last_region_z = reg_z; + opened = 1; + } +} + +static region *load_region(int reg_x, int reg_z) +{ + region *r; + int x,z; + + open_file(reg_x, reg_z); + + r = malloc(sizeof(*r)); + + if (last_region == NULL) { + memset(r, 0, sizeof(*r)); + } else { + fseek(last_region, 0, SEEK_SET); + for (z=0; z < NUM_CHUNKS_PER_REGION; ++z) + for (x=0; x < NUM_CHUNKS_PER_REGION; ++x) + r->sector_data[z][x] = read_uint32_be(last_region); + } + r->x = reg_x; + r->z = reg_z; + + return r; +} + +void free_region(region *r) +{ + free(r); +} + +#define MAX_MAP_REGIONS 64 // in one axis: 64 regions * 32 chunk/region * 16 columns/chunk = 16384 columns +region *regions[MAX_MAP_REGIONS][MAX_MAP_REGIONS]; + +static region *get_region(int reg_x, int reg_z) +{ + int slot_x = reg_x & (MAX_MAP_REGIONS-1); + int slot_z = reg_z & (MAX_MAP_REGIONS-1); + region *r; + + r = regions[slot_z][slot_x]; + + if (r) { + if (r->x == reg_x && r->z == reg_z) + return r; + free_region(r); + } + + r = load_region(reg_x, reg_z); + regions[slot_z][slot_x] = r; + + return r; +} + +// about one region, so size should be ok +#define NUM_CACHED_X 64 +#define NUM_CACHED_Z 64 + +// @TODO: is it really worth caching these? we probably can just +// pull them from the disk cache nearly as efficiently. +// Can test that by setting to 1x1? +compressed_chunk *cached_chunk[NUM_CACHED_Z][NUM_CACHED_X]; + +static void deref_compressed_chunk(compressed_chunk *cc) +{ + assert(cc->refcount > 0); + --cc->refcount; + if (cc->refcount == 0) { + if (cc->data) + free(cc->data); + free(cc); + } +} + +static compressed_chunk *get_compressed_chunk(int chunk_x, int chunk_z) +{ + int slot_x = chunk_x & (NUM_CACHED_X-1); + int slot_z = chunk_z & (NUM_CACHED_Z-1); + compressed_chunk *cc = cached_chunk[slot_z][slot_x]; + + if (cc && cc->x == chunk_x && cc->z == chunk_z) + return cc; + else { + int reg_x = chunk_x >> NUM_CHUNKS_PER_REGION_LOG2; + int reg_z = chunk_z >> NUM_CHUNKS_PER_REGION_LOG2; + region *r = get_region(reg_x, reg_z); + if (cc) { + deref_compressed_chunk(cc); + cached_chunk[slot_z][slot_x] = NULL; + } + cc = malloc(sizeof(*cc)); + cc->x = chunk_x; + cc->z = chunk_z; + { + int subchunk_x = chunk_x & (NUM_CHUNKS_PER_REGION-1); + int subchunk_z = chunk_z & (NUM_CHUNKS_PER_REGION-1); + uint32 code = r->sector_data[subchunk_z][subchunk_x]; + + if (code & 255) { + open_file(reg_x, reg_z); + fseek(last_region, (code>>8)*4096, SEEK_SET); + cc->len = (code&255)*4096; + cc->data = malloc(cc->len); + fread(cc->data, 1, cc->len, last_region); + } else { + cc->len = 0; + cc->data = 0; + } + } + cc->refcount = 1; + cached_chunk[slot_z][slot_x] = cc; + return cc; + } +} + + +// NBT parser -- can automatically parse stuff we don't +// have definitions for, but want to explicitly parse +// stuff we do have definitions for. +// +// option 1: auto-parse everything into data structures, +// then read those +// +// option 2: have a "parse next object" which +// doesn't resolve whether it expands its children +// yet, and then the user either says "expand" or +// "skip" after looking at the name. Anything with +// "children" without names can't go through this +// interface. +// +// Let's try option 2. + + +typedef struct +{ + unsigned char *buffer_start; + unsigned char *buffer_end; + unsigned char *cur; + int nesting; + char temp_buffer[256]; +} nbt; + +enum { TAG_End=0, TAG_Byte=1, TAG_Short=2, TAG_Int=3, TAG_Long=4, + TAG_Float=5, TAG_Double=6, TAG_Byte_Array=7, TAG_String=8, + TAG_List=9, TAG_Compound=10, TAG_Int_Array=11 }; + +static void nbt_get_string_data(unsigned char *data, char *buffer, size_t bufsize) +{ + int len = data[0]*256 + data[1]; + int i; + for (i=0; i < len && i+1 < (int) bufsize; ++i) + buffer[i] = (char) data[i+2]; + buffer[i] = 0; +} + +static char *nbt_peek(nbt *n) +{ + unsigned char type = *n->cur; + if (type == TAG_End) + return NULL; + nbt_get_string_data(n->cur+1, n->temp_buffer, sizeof(n->temp_buffer)); + return n->temp_buffer; +} + +static uint32 nbt_parse_uint32(unsigned char *buffer) +{ + return (buffer[0] << 24) + (buffer[1]<<16) + (buffer[2]<<8) + buffer[3]; +} + +static void nbt_skip(nbt *n); + +// skip an item that doesn't have an id or name prefix (usable in lists) +static void nbt_skip_raw(nbt *n, unsigned char type) +{ + switch (type) { + case TAG_Byte : n->cur += 1; break; + case TAG_Short : n->cur += 2; break; + case TAG_Int : n->cur += 4; break; + case TAG_Long : n->cur += 8; break; + case TAG_Float : n->cur += 4; break; + case TAG_Double: n->cur += 8; break; + case TAG_Byte_Array: n->cur += 4 + 1*nbt_parse_uint32(n->cur); break; + case TAG_Int_Array : n->cur += 4 + 4*nbt_parse_uint32(n->cur); break; + case TAG_String : n->cur += 2 + (n->cur[0]*256 + n->cur[1]); break; + case TAG_List : { + unsigned char list_type = *n->cur++; + unsigned int list_len = nbt_parse_uint32(n->cur); + unsigned int i; + n->cur += 4; // list_len + for (i=0; i < list_len; ++i) + nbt_skip_raw(n, list_type); + break; + } + case TAG_Compound : { + while (*n->cur != TAG_End) + nbt_skip(n); + nbt_skip(n); // skip the TAG_end + break; + } + } + assert(n->cur <= n->buffer_end); +} + +static void nbt_skip(nbt *n) +{ + unsigned char type = *n->cur++; + if (type == TAG_End) + return; + // skip name + n->cur += (n->cur[0]*256 + n->cur[1]) + 2; + nbt_skip_raw(n, type); +} + +// byteswap +static void nbt_swap(unsigned char *ptr, int len) +{ + int i; + for (i=0; i < (len>>1); ++i) { + unsigned char t = ptr[i]; + ptr[i] = ptr[len-1-i]; + ptr[len-1-i] = t; + } +} + +// pass in the expected type, fail if doesn't match +// returns a pointer to the data, byteswapped if appropriate +static void *nbt_get_fromlist(nbt *n, unsigned char type, int *len) +{ + unsigned char *ptr; + assert(type != TAG_Compound); + assert(type != TAG_List); // we could support getting lists of primitives as if they were arrays, but eh + if (len) *len = 1; + ptr = n->cur; + switch (type) { + case TAG_Byte : break; + + case TAG_Short : nbt_swap(ptr, 2); break; + case TAG_Int : nbt_swap(ptr, 4); break; + case TAG_Long : nbt_swap(ptr, 8); break; + case TAG_Float : nbt_swap(ptr, 4); break; + case TAG_Double: nbt_swap(ptr, 8); break; + + case TAG_Byte_Array: + *len = nbt_parse_uint32(ptr); + ptr += 4; + break; + case TAG_Int_Array: { + int i; + *len = nbt_parse_uint32(ptr); + ptr += 4; + for (i=0; i < *len; ++i) + nbt_swap(ptr + 4*i, 4); + break; + } + + default: assert(0); // unhandled case + } + nbt_skip_raw(n, type); + return ptr; +} + +static void *nbt_get(nbt *n, unsigned char type, int *len) +{ + assert(n->cur[0] == type); + n->cur += 3 + (n->cur[1]*256+n->cur[2]); + return nbt_get_fromlist(n, type, len); +} + +static void nbt_begin_compound(nbt *n) // start a compound +{ + assert(*n->cur == TAG_Compound); + // skip header + n->cur += 3 + (n->cur[1]*256 + n->cur[2]); + ++n->nesting; +} + +static void nbt_begin_compound_in_list(nbt *n) // start a compound +{ + ++n->nesting; +} + +static void nbt_end_compound(nbt *n) // end a compound +{ + assert(*n->cur == TAG_End); + assert(n->nesting != 0); + ++n->cur; + --n->nesting; +} + +// @TODO no interface to get lists from lists +static int nbt_begin_list(nbt *n, unsigned char type) +{ + uint32 len; + unsigned char *ptr; + + ptr = n->cur + 3 + (n->cur[1]*256 + n->cur[2]); + if (ptr[0] != type) + return -1; + n->cur = ptr; + len = nbt_parse_uint32(n->cur+1); + assert(n->cur[0] == type); + // @TODO keep a stack with the count to make sure they do it right + ++n->nesting; + n->cur += 5; + return (int) len; +} + +static void nbt_end_list(nbt *n) +{ + --n->nesting; +} + +// raw_block chunk is 16x256x16x4 = 2^(4+8+4+2) = 256KB +// +// if we want to process 64x64x256 at a time, that will be: +// 4*4*256KB => 4MB per area in raw_block +// +// (plus we maybe need to decode adjacent regions) + + +#ifdef FAST_CHUNK +typedef fast_chunk parse_chunk; +#else +typedef chunk parse_chunk; +#endif + +static parse_chunk *minecraft_chunk_parse(unsigned char *data, size_t len) +{ + char *s; + parse_chunk *c = NULL; + + nbt n_store, *n = &n_store; + n->buffer_start = data; + n->buffer_end = data + len; + n->cur = n->buffer_start; + n->nesting = 0; + + nbt_begin_compound(n); + while ((s = nbt_peek(n)) != NULL) { + if (!strcmp(s, "Level")) { + int *height; + c = malloc(sizeof(*c)); + #ifdef FAST_CHUNK + memset(c, 0, sizeof(*c)); + c->pointer_to_free = data; + #else + c->rb[15][15][255].block = 0; + #endif + c->max_y = 0; + + nbt_begin_compound(n); + while ((s = nbt_peek(n)) != NULL) { + if (!strcmp(s, "xPos")) + c->xpos = *(int *) nbt_get(n, TAG_Int, 0); + else if (!strcmp(s, "zPos")) + c->zpos = *(int *) nbt_get(n, TAG_Int, 0); + else if (!strcmp(s, "Sections")) { + int count = nbt_begin_list(n, TAG_Compound), i; + if (count == -1) { + // this not-a-list case happens in The End and I'm not sure + // what it means... possibly one of those silly encodings + // where it's not encoded as a list if there's only one? + // not worth figuring out + nbt_skip(n); + count = -1; + } + for (i=0; i < count; ++i) { + int yi, len; + uint8 *light = NULL, *blocks = NULL, *data = NULL, *skylight = NULL; + nbt_begin_compound_in_list(n); + while ((s = nbt_peek(n)) != NULL) { + if (!strcmp(s, "Y")) + yi = * (uint8 *) nbt_get(n, TAG_Byte, 0); + else if (!strcmp(s, "BlockLight")) { + light = nbt_get(n, TAG_Byte_Array, &len); + assert(len == 2048); + } else if (!strcmp(s, "Blocks")) { + blocks = nbt_get(n, TAG_Byte_Array, &len); + assert(len == 4096); + } else if (!strcmp(s, "Data")) { + data = nbt_get(n, TAG_Byte_Array, &len); + assert(len == 2048); + } else if (!strcmp(s, "SkyLight")) { + skylight = nbt_get(n, TAG_Byte_Array, &len); + assert(len == 2048); + } + } + nbt_end_compound(n); + + assert(yi < 16); + + #ifndef FAST_CHUNK + + // clear data below current max_y + { + int x,z; + while (c->max_y < yi*16) { + for (x=0; x < 16; ++x) + for (z=0; z < 16; ++z) + c->rb[z][x][c->max_y].block = 0; + ++c->max_y; + } + } + + // now assemble the data + { + int x,y,z, o2=0,o4=0; + for (y=0; y < 16; ++y) { + for (z=0; z < 16; ++z) { + for (x=0; x < 16; x += 2) { + raw_block *rb = &c->rb[15-z][x][y + yi*16]; // 15-z because switching to z-up will require flipping an axis + rb[0].block = blocks[o4]; + rb[0].light = light[o2] & 15; + rb[0].data = data[o2] & 15; + rb[0].skylight = skylight[o2] & 15; + + rb[256].block = blocks[o4+1]; + rb[256].light = light[o2] >> 4; + rb[256].data = data[o2] >> 4; + rb[256].skylight = skylight[o2] >> 4; + + o2 += 1; + o4 += 2; + } + } + } + c->max_y += 16; + } + #else + c->blockdata[yi] = blocks; + c->data [yi] = data; + c->light [yi] = light; + c->skylight [yi] = skylight; + #endif + } + //nbt_end_list(n); + } else if (!strcmp(s, "HeightMap")) { + height = nbt_get(n, TAG_Int_Array, &len); + assert(len == 256); + } else + nbt_skip(n); + } + nbt_end_compound(n); + + } else + nbt_skip(n); + } + nbt_end_compound(n); + assert(n->cur == n->buffer_end); + return c; +} + +#define MAX_DECODED_CHUNK_X 64 +#define MAX_DECODED_CHUNK_Z 64 + +typedef struct +{ + int cx,cz; + fast_chunk *fc; + int valid; +} decoded_buffer; + +static decoded_buffer decoded_buffers[MAX_DECODED_CHUNK_Z][MAX_DECODED_CHUNK_X]; +void lock_chunk_get_mutex(void); +void unlock_chunk_get_mutex(void); + +#ifdef FAST_CHUNK +fast_chunk *get_decoded_fastchunk_uncached(int chunk_x, int chunk_z) +{ + unsigned char *decoded; + compressed_chunk *cc; + int inlen; + int len; + fast_chunk *fc; + + lock_chunk_get_mutex(); + cc = get_compressed_chunk(chunk_x, chunk_z); + if (cc->len != 0) + ++cc->refcount; + unlock_chunk_get_mutex(); + + if (cc->len == 0) + return NULL; + + assert(cc != NULL); + + assert(cc->data[4] == 2); + + inlen = nbt_parse_uint32(cc->data); + decoded = stbi_zlib_decode_malloc_guesssize(cc->data+5, inlen, inlen*3, &len); + assert(decoded != NULL); + assert(len != 0); + + lock_chunk_get_mutex(); + deref_compressed_chunk(cc); + unlock_chunk_get_mutex(); + + #ifdef FAST_CHUNK + fc = minecraft_chunk_parse(decoded, len); + #else + fc = NULL; + #endif + if (fc == NULL) + free(decoded); + return fc; +} + + +decoded_buffer *get_decoded_buffer(int chunk_x, int chunk_z) +{ + decoded_buffer *db = &decoded_buffers[chunk_z&(MAX_DECODED_CHUNK_Z-1)][chunk_x&(MAX_DECODED_CHUNK_X-1)]; + if (db->valid) { + if (db->cx == chunk_x && db->cz == chunk_z) + return db; + if (db->fc) { + free(db->fc->pointer_to_free); + free(db->fc); + } + } + + db->cx = chunk_x; + db->cz = chunk_z; + db->valid = 1; + db->fc = 0; + + { + db->fc = get_decoded_fastchunk_uncached(chunk_x, chunk_z); + return db; + } +} + +fast_chunk *get_decoded_fastchunk(int chunk_x, int chunk_z) +{ + decoded_buffer *db = get_decoded_buffer(chunk_x, chunk_z); + return db->fc; +} +#endif + +#ifndef FAST_CHUNK +chunk *get_decoded_chunk_raw(int chunk_x, int chunk_z) +{ + unsigned char *decoded; + compressed_chunk *cc = get_compressed_chunk(chunk_x, chunk_z); + assert(cc != NULL); + if (cc->len == 0) + return NULL; + else { + chunk *ch; + int inlen = nbt_parse_uint32(cc->data); + int len; + assert(cc->data[4] == 2); + decoded = stbi_zlib_decode_malloc_guesssize(cc->data+5, inlen, inlen*3, &len); + assert(decoded != NULL); + #ifdef FAST_CHUNK + ch = NULL; + #else + ch = minecraft_chunk_parse(decoded, len); + #endif + free(decoded); + return ch; + } +} + +static chunk *decoded_chunks[MAX_DECODED_CHUNK_Z][MAX_DECODED_CHUNK_X]; +chunk *get_decoded_chunk(int chunk_x, int chunk_z) +{ + chunk *c = decoded_chunks[chunk_z&(MAX_DECODED_CHUNK_Z-1)][chunk_x&(MAX_DECODED_CHUNK_X-1)]; + if (c && c->xpos == chunk_x && c->zpos == chunk_z) + return c; + if (c) free(c); + c = get_decoded_chunk_raw(chunk_x, chunk_z); + decoded_chunks[chunk_z&(MAX_DECODED_CHUNK_Z-1)][chunk_x&(MAX_DECODED_CHUNK_X-1)] = c; + return c; +} +#endif diff --git a/source/engine/thirdparty/stb/tests/caveview/cave_parse.h b/source/engine/thirdparty/stb/tests/caveview/cave_parse.h new file mode 100644 index 0000000..4cdfe2a --- /dev/null +++ b/source/engine/thirdparty/stb/tests/caveview/cave_parse.h @@ -0,0 +1,41 @@ +#ifndef INCLUDE_CAVE_PARSE_H +#define INCLUDE_CAVE_PARSE_H + +typedef struct +{ + unsigned char block; + unsigned char data; + unsigned char light:4; + unsigned char skylight:4; +} raw_block; + +// this is the old fully-decoded chunk +typedef struct +{ + int xpos, zpos, max_y; + int height[16][16]; + raw_block rb[16][16][256]; // [z][x][y] which becomes [y][x][z] in stb +} chunk; + +chunk *get_decoded_chunk(int chunk_x, int chunk_z); + +#define NUM_SEGMENTS 16 +typedef struct +{ + int max_y, xpos, zpos; + + unsigned char *blockdata[NUM_SEGMENTS]; + unsigned char *data[NUM_SEGMENTS]; + unsigned char *skylight[NUM_SEGMENTS]; + unsigned char *light[NUM_SEGMENTS]; + + void *pointer_to_free; + + int refcount; // this allows multi-threaded building without wrapping in ANOTHER struct +} fast_chunk; + +fast_chunk *get_decoded_fastchunk(int chunk_x, int chunk_z); // cache, never call free() + +fast_chunk *get_decoded_fastchunk_uncached(int chunk_x, int chunk_z); + +#endif diff --git a/source/engine/thirdparty/stb/tests/caveview/cave_render.c b/source/engine/thirdparty/stb/tests/caveview/cave_render.c new file mode 100644 index 0000000..3ed4628 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/caveview/cave_render.c @@ -0,0 +1,951 @@ +// This file renders vertex buffers, converts raw meshes +// to GL meshes, and manages threads that do the raw-mesh +// building (found in cave_mesher.c) + + +#include "stb_voxel_render.h" + +#define STB_GLEXT_DECLARE "glext_list.h" +#include "stb_gl.h" +#include "stb_image.h" +#include "stb_glprog.h" + +#include "caveview.h" +#include "cave_parse.h" +#include "stb.h" +#include "sdl.h" +#include "sdl_thread.h" +#include +#include + +//#define STBVOX_CONFIG_TEX1_EDGE_CLAMP + + +// currently no dynamic way to set mesh cache size or view distance +//#define SHORTVIEW + + +stbvox_mesh_maker g_mesh_maker; + +GLuint main_prog; +GLint uniform_locations[64]; + +//#define MAX_QUADS_PER_DRAW (65536 / 4) // assuming 16-bit indices, 4 verts per quad +//#define FIXED_INDEX_BUFFER_SIZE (MAX_QUADS_PER_DRAW * 6 * 2) // 16*1024 * 12 == ~192KB + +// while uploading texture data, this holds our each texture +#define TEX_SIZE 64 +uint32 texture[TEX_SIZE][TEX_SIZE]; + +GLuint voxel_tex[2]; + +// chunk state +enum +{ + STATE_invalid, + STATE_needed, + STATE_requested, + STATE_abandoned, + STATE_valid, +}; + +// mesh is 32x32x255 ... this is hardcoded in that +// a mesh covers 2x2 minecraft chunks, no #defines for it +typedef struct +{ + int state; + int chunk_x, chunk_y; + int num_quads; + float priority; + int vbuf_size, fbuf_size; + + float transform[3][3]; + float bounds[2][3]; + + GLuint vbuf;// vbuf_tex; + GLuint fbuf, fbuf_tex; + +} chunk_mesh; + +void scale_texture(unsigned char *src, int x, int y, int w, int h) +{ + int i,j,k; + assert(w == 256 && h == 256); + for (j=0; j < TEX_SIZE; ++j) { + for (i=0; i < TEX_SIZE; ++i) { + uint32 val=0; + for (k=0; k < 4; ++k) { + val >>= 8; + val += src[ 4*(x+(i>>2)) + 4*w*(y+(j>>2)) + k]<<24; + } + texture[j][i] = val; + } + } +} + +void build_base_texture(int n) +{ + int x,y; + uint32 color = stb_rand() | 0x808080; + for (y=0; ystate == STATE_valid) { + glDeleteTextures(1, &cm->fbuf_tex); + glDeleteBuffersARB(1, &cm->vbuf); + glDeleteBuffersARB(1, &cm->fbuf); + cached_chunk_mesh[slot_y][slot_x].state = STATE_invalid; + } +} + +void upload_mesh(chunk_mesh *cm, uint8 *build_buffer, uint8 *face_buffer) +{ + glGenBuffersARB(1, &cm->vbuf); + glBindBufferARB(GL_ARRAY_BUFFER_ARB, cm->vbuf); + glBufferDataARB(GL_ARRAY_BUFFER_ARB, cm->num_quads*4*sizeof(uint32), build_buffer, GL_STATIC_DRAW_ARB); + glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + + glGenBuffersARB(1, &cm->fbuf); + glBindBufferARB(GL_TEXTURE_BUFFER_ARB, cm->fbuf); + glBufferDataARB(GL_TEXTURE_BUFFER_ARB, cm->num_quads*sizeof(uint32), face_buffer , GL_STATIC_DRAW_ARB); + glBindBufferARB(GL_TEXTURE_BUFFER_ARB, 0); + + glGenTextures(1, &cm->fbuf_tex); + glBindTexture(GL_TEXTURE_BUFFER_ARB, cm->fbuf_tex); + glTexBufferARB(GL_TEXTURE_BUFFER_ARB, GL_RGBA8UI, cm->fbuf); + glBindTexture(GL_TEXTURE_BUFFER_ARB, 0); +} + +static void upload_mesh_data(raw_mesh *rm) +{ + int cx = rm->cx; + int cy = rm->cy; + int slot_x = (cx >> 1) & (CACHED_MESH_NUM_X-1); + int slot_y = (cy >> 1) & (CACHED_MESH_NUM_Y-1); + chunk_mesh *cm; + + free_chunk(slot_x, slot_y); + + cm = &cached_chunk_mesh[slot_y][slot_x]; + cm->num_quads = rm->num_quads; + + upload_mesh(cm, rm->build_buffer, rm->face_buffer); + cm->vbuf_size = rm->num_quads*4*sizeof(uint32); + cm->fbuf_size = rm->num_quads*sizeof(uint32); + cm->priority = 100000; + cm->chunk_x = cx; + cm->chunk_y = cy; + + memcpy(cm->bounds, rm->bounds, sizeof(cm->bounds)); + memcpy(cm->transform, rm->transform, sizeof(cm->transform)); + + // write barrier here + cm->state = STATE_valid; +} + +GLint uniform_loc[16]; +float table3[128][3]; +float table4[64][4]; +GLint tablei[2]; + +float step=0; + +#ifdef SHORTVIEW +int view_dist_in_chunks = 50; +#else +int view_dist_in_chunks = 80; +#endif + +void setup_uniforms(float pos[3]) +{ + int i,j; + step += 1.0f/60.0f; + for (i=0; i < STBVOX_UNIFORM_count; ++i) { + stbvox_uniform_info raw, *ui=&raw; + stbvox_get_uniform_info(&raw, i); + uniform_loc[i] = -1; + + if (i == STBVOX_UNIFORM_texscale || i == STBVOX_UNIFORM_texgen || i == STBVOX_UNIFORM_color_table) + continue; + + if (ui) { + void *data = ui->default_value; + uniform_loc[i] = stbgl_find_uniform(main_prog, ui->name); + switch (i) { + case STBVOX_UNIFORM_face_data: + tablei[0] = 2; + data = tablei; + break; + + case STBVOX_UNIFORM_tex_array: + glActiveTextureARB(GL_TEXTURE0_ARB); + glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, voxel_tex[0]); + glActiveTextureARB(GL_TEXTURE1_ARB); + glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, voxel_tex[1]); + glActiveTextureARB(GL_TEXTURE0_ARB); + tablei[0] = 0; + tablei[1] = 1; + data = tablei; + break; + + case STBVOX_UNIFORM_color_table: + data = ui->default_value; + ((float *)data)[63*4+3] = 2.0f; // emissive + break; + + case STBVOX_UNIFORM_camera_pos: + data = table3[0]; + table3[0][0] = pos[0]; + table3[0][1] = pos[1]; + table3[0][2] = pos[2]; + table3[0][3] = stb_max(0,(float)sin(step*2)*0.125f); + break; + + case STBVOX_UNIFORM_ambient: { + float bright = 1.0; + //float bright = 0.75; + float amb[3][3]; + + // ambient direction is sky-colored upwards + // "ambient" lighting is from above + table4[0][0] = 0.3f; + table4[0][1] = -0.5f; + table4[0][2] = 0.9f; + + amb[1][0] = 0.3f; amb[1][1] = 0.3f; amb[1][2] = 0.3f; // dark-grey + amb[2][0] = 1.0; amb[2][1] = 1.0; amb[2][2] = 1.0; // white + + // convert so (table[1]*dot+table[2]) gives + // above interpolation + // lerp((dot+1)/2, amb[1], amb[2]) + // amb[1] + (amb[2] - amb[1]) * (dot+1)/2 + // amb[1] + (amb[2] - amb[1]) * dot/2 + (amb[2]-amb[1])/2 + + for (j=0; j < 3; ++j) { + table4[1][j] = (amb[2][j] - amb[1][j])/2 * bright; + table4[2][j] = (amb[1][j] + amb[2][j])/2 * bright; + } + + // fog color + table4[3][0] = 0.6f, table4[3][1] = 0.7f, table4[3][2] = 0.9f; + table4[3][3] = 1.0f / (view_dist_in_chunks * 16); + table4[3][3] *= table4[3][3]; + + data = table4; + break; + } + } + + switch (ui->type) { + case STBVOX_UNIFORM_TYPE_sampler: stbglUniform1iv(uniform_loc[i], ui->array_length, data); break; + case STBVOX_UNIFORM_TYPE_vec2: stbglUniform2fv(uniform_loc[i], ui->array_length, data); break; + case STBVOX_UNIFORM_TYPE_vec3: stbglUniform3fv(uniform_loc[i], ui->array_length, data); break; + case STBVOX_UNIFORM_TYPE_vec4: stbglUniform4fv(uniform_loc[i], ui->array_length, data); break; + } + } + } +} + +GLuint unitex[64], unibuf[64]; +void make_texture_buffer_for_uniform(int uniform, int slot) +{ + GLenum type; + stbvox_uniform_info raw, *ui=&raw; + GLint uloc; + + stbvox_get_uniform_info(ui, uniform); + uloc = stbgl_find_uniform(main_prog, ui->name); + + if (uniform == STBVOX_UNIFORM_color_table) + ((float *)ui->default_value)[63*4+3] = 2.0f; // emissive + + glGenBuffersARB(1, &unibuf[uniform]); + glBindBufferARB(GL_ARRAY_BUFFER_ARB, unibuf[uniform]); + glBufferDataARB(GL_ARRAY_BUFFER_ARB, ui->array_length * ui->bytes_per_element, ui->default_value, GL_STATIC_DRAW_ARB); + glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + + glGenTextures(1, &unitex[uniform]); + glBindTexture(GL_TEXTURE_BUFFER_ARB, unitex[uniform]); + switch (ui->type) { + case STBVOX_UNIFORM_TYPE_vec2: type = GL_RG32F; break; + case STBVOX_UNIFORM_TYPE_vec3: type = GL_RGB32F; break; + case STBVOX_UNIFORM_TYPE_vec4: type = GL_RGBA32F; break; + default: assert(0); + } + glTexBufferARB(GL_TEXTURE_BUFFER_ARB, type, unibuf[uniform]); + glBindTexture(GL_TEXTURE_BUFFER_ARB, 0); + + glActiveTextureARB(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_BUFFER_ARB, unitex[uniform]); + glActiveTextureARB(GL_TEXTURE0); + + stbglUseProgram(main_prog); + stbglUniform1i(uloc, slot); +} + +#define MAX_MESH_WORKERS 8 +#define MAX_CHUNK_LOAD_WORKERS 2 + +int num_mesh_workers; +int num_chunk_load_workers; + +typedef struct +{ + int state; + int request_cx; + int request_cy; + int padding[13]; + + SDL_sem * request_received; + + SDL_sem * chunk_server_done_processing; + int chunk_action; + int chunk_request_x; + int chunk_request_y; + fast_chunk *chunks[4][4]; + + int padding2[16]; + raw_mesh rm; + int padding3[16]; + + uint8 *build_buffer; + uint8 *face_buffer ; +} mesh_worker; + +enum +{ + WSTATE_idle, + WSTATE_requested, + WSTATE_running, + WSTATE_mesh_ready, +}; + +mesh_worker mesh_data[MAX_MESH_WORKERS]; +int num_meshes_started; // stats + +int request_chunk(int chunk_x, int chunk_y); +void update_meshes_from_render_thread(void); + +unsigned char tex2_data[64][4]; + +void init_tex2_gradient(void) +{ + int i; + for (i=0; i < 16; ++i) { + tex2_data[i+ 0][0] = 64 + 12*i; + tex2_data[i+ 0][1] = 32; + tex2_data[i+ 0][2] = 64; + + tex2_data[i+16][0] = 255; + tex2_data[i+16][1] = 32 + 8*i; + tex2_data[i+16][2] = 64; + + tex2_data[i+32][0] = 255; + tex2_data[i+32][1] = 160; + tex2_data[i+32][2] = 64 + 12*i; + + tex2_data[i+48][0] = 255; + tex2_data[i+48][1] = 160 + 6*i; + tex2_data[i+48][2] = 255; + } +} + +void set_tex2_alpha(float fa) +{ + int i; + int a = (int) stb_lerp(fa, 0, 255); + if (a < 0) a = 0; else if (a > 255) a = 255; + glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, voxel_tex[1]); + for (i=0; i < 64; ++i) { + tex2_data[i][3] = a; + glTexSubImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT, 0, 0,0,i, 1,1,1, GL_RGBA, GL_UNSIGNED_BYTE, tex2_data[i]); + } +} + +void render_init(void) +{ + int i; + char *binds[] = { "attr_vertex", "attr_face", NULL }; + char *vertex; + char *fragment; + int w=0,h=0; + + unsigned char *texdata = stbi_load("terrain.png", &w, &h, NULL, 4); + + stbvox_init_mesh_maker(&g_mesh_maker); + for (i=0; i < num_mesh_workers; ++i) { + stbvox_init_mesh_maker(&mesh_data[i].rm.mm); + } + + vertex = stbvox_get_vertex_shader(); + fragment = stbvox_get_fragment_shader(); + + { + char error_buffer[1024]; + char *main_vertex[] = { vertex, NULL }; + char *main_fragment[] = { fragment, NULL }; + main_prog = stbgl_create_program(main_vertex, main_fragment, binds, error_buffer, sizeof(error_buffer)); + if (main_prog == 0) { + ods("Compile error for main shader: %s\n", error_buffer); + assert(0); + exit(1); + } + } + //init_index_buffer(); + + make_texture_buffer_for_uniform(STBVOX_UNIFORM_texscale , 3); + make_texture_buffer_for_uniform(STBVOX_UNIFORM_texgen , 4); + make_texture_buffer_for_uniform(STBVOX_UNIFORM_color_table , 5); + + glGenTextures(2, voxel_tex); + + glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, voxel_tex[0]); + glTexImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_RGBA, + TEX_SIZE,TEX_SIZE,256, + 0,GL_RGBA,GL_UNSIGNED_BYTE,NULL); + for (i=0; i < 256; ++i) { + if (texdata) + scale_texture(texdata, (i&15)*w/16, (h/16)*(i>>4), w,h); + else + build_base_texture(i); + glTexSubImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT, 0, 0,0,i, TEX_SIZE,TEX_SIZE,1, GL_RGBA, GL_UNSIGNED_BYTE, texture[0]); + } + glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16); + #ifdef STBVOX_CONFIG_TEX1_EDGE_CLAMP + glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + #endif + + glGenerateMipmapEXT(GL_TEXTURE_2D_ARRAY_EXT); + + glBindTexture(GL_TEXTURE_2D_ARRAY_EXT, voxel_tex[1]); + glTexImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_RGBA, + 1,1,64, + 0,GL_RGBA,GL_UNSIGNED_BYTE,NULL); + init_tex2_gradient(); + set_tex2_alpha(0.0); + #if 0 + for (i=0; i < 128; ++i) { + //build_overlay_texture(i); + glTexSubImage3DEXT(GL_TEXTURE_2D_ARRAY_EXT, 0, 0,0,i, TEX_SIZE,TEX_SIZE,1, GL_RGBA, GL_UNSIGNED_BYTE, texture[0]); + } + #endif + glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glGenerateMipmapEXT(GL_TEXTURE_2D_ARRAY_EXT); +} + +void world_init(void) +{ + int a,b,x,y; + + Uint64 start_time, end_time; + #ifdef NDEBUG + int range = 32; + #else + int range = 12; + #endif + + start_time = SDL_GetPerformanceCounter(); + + // iterate in 8x8 clusters of qchunks at a time to get better converted-chunk-cache reuse + // than a purely row-by-row ordering is (single-threaded this is a bigger win than + // any of the above optimizations were, since it halves zlib/mc-conversion costs) + for (x=-range; x <= range; x += 16) + for (y=-range; y <= range; y += 16) + for (b=y; b < y+16 && b <= range; b += 2) + for (a=x; a < x+16 && a <= range; a += 2) + while (!request_chunk(a, b)) { // if request fails, all threads are busy + update_meshes_from_render_thread(); + SDL_Delay(1); + } + + // wait until all the workers are done, + // (this is only needed if we want to time + // when the build finishes, or when we want to reset the + // cache size; otherwise we could just go ahead and + // start rendering whatever we've got) + for(;;) { + int i; + update_meshes_from_render_thread(); + for (i=0; i < num_mesh_workers; ++i) + if (mesh_data[i].state != WSTATE_idle) + break; + if (i == num_mesh_workers) + break; + SDL_Delay(3); + } + + end_time = SDL_GetPerformanceCounter(); + ods("Build time: %7.2fs\n", (end_time - start_time) / (float) SDL_GetPerformanceFrequency()); + + // don't waste lots of storage on chunk caches once it's finished starting-up; + // this was only needed to be this large because we worked in large blocks + // to maximize sharing + reset_cache_size(32); +} + +extern SDL_mutex * chunk_cache_mutex; + +int mesh_worker_handler(void *data) +{ + mesh_worker *mw = data; + mw->face_buffer = malloc(FACE_BUFFER_SIZE); + mw->build_buffer = malloc(BUILD_BUFFER_SIZE); + + // this loop only works because the compiler can't + // tell that the SDL_calls don't access mw->state; + // really we should barrier that stuff + for(;;) { + int i,j; + int cx,cy; + + // wait for a chunk request + SDL_SemWait(mw->request_received); + + // analyze the chunk request + assert(mw->state == WSTATE_requested); + cx = mw->request_cx; + cy = mw->request_cy; + + // this is inaccurate as it can block while another thread has the cache locked + mw->state = WSTATE_running; + + // get the chunks we need (this takes a lock and caches them) + for (j=0; j < 4; ++j) + for (i=0; i < 4; ++i) + mw->chunks[j][i] = get_converted_fastchunk(cx-1 + i, cy-1 + j); + + // build the mesh based on the chunks + mw->rm.build_buffer = mw->build_buffer; + mw->rm.face_buffer = mw->face_buffer; + build_chunk(cx, cy, mw->chunks, &mw->rm); + mw->state = WSTATE_mesh_ready; + // don't need to notify of this, because it gets polled + + // when done, free the chunks + + // for efficiency we just take the mutex once around the whole thing, + // though this spreads the mutex logic over two files + SDL_LockMutex(chunk_cache_mutex); + for (j=0; j < 4; ++j) + for (i=0; i < 4; ++i) { + deref_fastchunk(mw->chunks[j][i]); + mw->chunks[j][i] = NULL; + } + SDL_UnlockMutex(chunk_cache_mutex); + } + return 0; +} + +int request_chunk(int chunk_x, int chunk_y) +{ + int i; + for (i=0; i < num_mesh_workers; ++i) { + mesh_worker *mw = &mesh_data[i]; + if (mw->state == WSTATE_idle) { + mw->request_cx = chunk_x; + mw->request_cy = chunk_y; + mw->state = WSTATE_requested; + SDL_SemPost(mw->request_received); + ++num_meshes_started; + return 1; + } + } + return 0; +} + +void prepare_threads(void) +{ + int i; + int num_proc = SDL_GetCPUCount(); + + if (num_proc > 6) + num_mesh_workers = num_proc/2; + else if (num_proc > 4) + num_mesh_workers = 4; + else + num_mesh_workers = num_proc-1; + +// @TODO +// Thread usage is probably pretty terrible; need to make a +// separate queue of needed chunks, instead of just generating +// one request per thread per frame, and a separate queue of +// results. (E.g. If it takes 1.5 frames to build mesh, thread +// is idle for 0.5 frames.) To fake this for now, I've just +// doubled the number of threads to let those serve as a 'queue', +// but that's dumb. + + num_mesh_workers *= 2; // try to get better thread usage + + if (num_mesh_workers > MAX_MESH_WORKERS) + num_mesh_workers = MAX_MESH_WORKERS; + + for (i=0; i < num_mesh_workers; ++i) { + mesh_worker *data = &mesh_data[i]; + data->request_received = SDL_CreateSemaphore(0); + data->chunk_server_done_processing = SDL_CreateSemaphore(0); + SDL_CreateThread(mesh_worker_handler, "mesh worker", data); + } +} + + +// "better" buffer uploading +#if 0 + if (glBufferStorage) { + glDeleteBuffersARB(1, &vb->vbuf); + glGenBuffersARB(1, &vb->vbuf); + + glBindBufferARB(GL_ARRAY_BUFFER_ARB, vb->vbuf); + glBufferStorage(GL_ARRAY_BUFFER_ARB, sizeof(build_buffer), build_buffer, 0); + glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + } else { + glBindBufferARB(GL_ARRAY_BUFFER_ARB, vb->vbuf); + glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(build_buffer), build_buffer, GL_STATIC_DRAW_ARB); + glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + } +#endif + + +typedef struct +{ + float x,y,z,w; +} plane; + +static plane frustum[6]; + +static void matd_mul(double out[4][4], double src1[4][4], double src2[4][4]) +{ + int i,j,k; + for (j=0; j < 4; ++j) { + for (i=0; i < 4; ++i) { + double t=0; + for (k=0; k < 4; ++k) + t += src1[k][i] * src2[j][k]; + out[i][j] = t; + } + } +} + +// https://fgiesen.wordpress.com/2012/08/31/frustum-planes-from-the-projection-matrix/ +static void compute_frustum(void) +{ + int i; + GLdouble mv[4][4],proj[4][4], mvproj[4][4]; + glGetDoublev(GL_MODELVIEW_MATRIX , mv[0]); + glGetDoublev(GL_PROJECTION_MATRIX, proj[0]); + matd_mul(mvproj, proj, mv); + for (i=0; i < 4; ++i) { + (&frustum[0].x)[i] = (float) (mvproj[3][i] + mvproj[0][i]); + (&frustum[1].x)[i] = (float) (mvproj[3][i] - mvproj[0][i]); + (&frustum[2].x)[i] = (float) (mvproj[3][i] + mvproj[1][i]); + (&frustum[3].x)[i] = (float) (mvproj[3][i] - mvproj[1][i]); + (&frustum[4].x)[i] = (float) (mvproj[3][i] + mvproj[2][i]); + (&frustum[5].x)[i] = (float) (mvproj[3][i] - mvproj[2][i]); + } +} + +static int test_plane(plane *p, float x0, float y0, float z0, float x1, float y1, float z1) +{ + // return false if the box is entirely behind the plane + float d=0; + assert(x0 <= x1 && y0 <= y1 && z0 <= z1); + if (p->x > 0) d += x1*p->x; else d += x0*p->x; + if (p->y > 0) d += y1*p->y; else d += y0*p->y; + if (p->z > 0) d += z1*p->z; else d += z0*p->z; + return d + p->w >= 0; +} + +static int is_box_in_frustum(float *bmin, float *bmax) +{ + int i; + for (i=0; i < 6; ++i) + if (!test_plane(&frustum[i], bmin[0], bmin[1], bmin[2], bmax[0], bmax[1], bmax[2])) + return 0; + return 1; +} + +float compute_priority(int cx, int cy, float x, float y) +{ + float distx, disty, dist2; + distx = (cx*16+8) - x; + disty = (cy*16+8) - y; + dist2 = distx*distx + disty*disty; + return view_dist_in_chunks*view_dist_in_chunks * 16 * 16 - dist2; +} + +int chunk_locations, chunks_considered, chunks_in_frustum; +int quads_considered, quads_rendered; +int chunk_storage_rendered, chunk_storage_considered, chunk_storage_total; +int update_frustum = 1; + +#ifdef SHORTVIEW +int max_chunk_storage = 450 << 20; +int min_chunk_storage = 350 << 20; +#else +int max_chunk_storage = 900 << 20; +int min_chunk_storage = 800 << 20; +#endif + +float min_priority = -500; // this really wants to be in unit space, not squared space + +int num_meshes_uploaded; + +void update_meshes_from_render_thread(void) +{ + int i; + for (i=0; i < num_mesh_workers; ++i) { + mesh_worker *mw = &mesh_data[i]; + if (mw->state == WSTATE_mesh_ready) { + upload_mesh_data(&mw->rm); + ++num_meshes_uploaded; + mw->state = WSTATE_idle; + } + } +} + +extern float tex2_alpha; +extern int global_hack; +int num_threads_active; +float chunk_server_activity; + +void render_caves(float campos[3]) +{ + float x = campos[0], y = campos[1]; + int qchunk_x, qchunk_y; + int cam_x, cam_y; + int i,j, rad; + + compute_frustum(); + + chunk_locations = chunks_considered = chunks_in_frustum = 0; + quads_considered = quads_rendered = 0; + chunk_storage_total = chunk_storage_considered = chunk_storage_rendered = 0; + + cam_x = (int) floor(x+0.5); + cam_y = (int) floor(y+0.5); + + qchunk_x = (((int) floor(x)+16) >> 5) << 1; + qchunk_y = (((int) floor(y)+16) >> 5) << 1; + + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.5); + + stbglUseProgram(main_prog); + setup_uniforms(campos); // set uniforms to default values inefficiently + glActiveTextureARB(GL_TEXTURE2_ARB); + stbglEnableVertexAttribArray(0); + + { + float lighting[2][3] = { { campos[0],campos[1],campos[2] }, { 0.75,0.75,0.65f } }; + float bright = 8; + lighting[1][0] *= bright; + lighting[1][1] *= bright; + lighting[1][2] *= bright; + stbglUniform3fv(stbgl_find_uniform(main_prog, "light_source"), 2, lighting[0]); + } + + if (global_hack) + set_tex2_alpha(tex2_alpha); + + num_meshes_uploaded = 0; + update_meshes_from_render_thread(); + + // traverse all in-range chunks and analyze them + for (j=-view_dist_in_chunks; j <= view_dist_in_chunks; j += 2) { + for (i=-view_dist_in_chunks; i <= view_dist_in_chunks; i += 2) { + float priority; + int cx = qchunk_x + i; + int cy = qchunk_y + j; + + priority = compute_priority(cx, cy, x, y); + if (priority >= min_priority) { + int slot_x = (cx>>1) & (CACHED_MESH_NUM_X-1); + int slot_y = (cy>>1) & (CACHED_MESH_NUM_Y-1); + chunk_mesh *cm = &cached_chunk_mesh[slot_y][slot_x]; + ++chunk_locations; + if (cm->state == STATE_valid && priority >= 0) { + // check if chunk pos actually matches + if (cm->chunk_x != cx || cm->chunk_y != cy) { + // we have a stale chunk we need to recreate + free_chunk(slot_x, slot_y); // it probably will have already gotten freed, but just in case + } + } + if (cm->state == STATE_invalid) { + cm->chunk_x = cx; + cm->chunk_y = cy; + cm->state = STATE_needed; + } + cm->priority = priority; + } + } + } + + // draw front-to-back + for (rad = 0; rad <= view_dist_in_chunks; rad += 2) { + for (j=-rad; j <= rad; j += 2) { + // if j is +- rad, then iterate i through all values + // if j isn't +-rad, then i should be only -rad & rad + int step = 2; + if (abs(j) != rad) + step = 2*rad; + for (i=-rad; i <= rad; i += step) { + int cx = qchunk_x + i; + int cy = qchunk_y + j; + int slot_x = (cx>>1) & (CACHED_MESH_NUM_X-1); + int slot_y = (cy>>1) & (CACHED_MESH_NUM_Y-1); + chunk_mesh *cm = &cached_chunk_mesh[slot_y][slot_x]; + if (cm->state == STATE_valid && cm->priority >= 0) { + ++chunks_considered; + quads_considered += cm->num_quads; + if (is_box_in_frustum(cm->bounds[0], cm->bounds[1])) { + ++chunks_in_frustum; + + // @TODO if in range + stbglUniform3fv(uniform_loc[STBVOX_UNIFORM_transform], 3, cm->transform[0]); + glBindBufferARB(GL_ARRAY_BUFFER_ARB, cm->vbuf); + glVertexAttribIPointer(0, 1, GL_UNSIGNED_INT, 4, (void*) 0); + glBindTexture(GL_TEXTURE_BUFFER_ARB, cm->fbuf_tex); + glDrawArrays(GL_QUADS, 0, cm->num_quads*4); + quads_rendered += cm->num_quads; + + chunk_storage_rendered += cm->vbuf_size + cm->fbuf_size; + } + chunk_storage_considered += cm->vbuf_size + cm->fbuf_size; + } + } + } + } + + stbglDisableVertexAttribArray(0); + glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + glActiveTextureARB(GL_TEXTURE0_ARB); + + stbglUseProgram(0); + num_meshes_started = 0; + + { + #define MAX_QUEUE 8 + float highest_priority[MAX_QUEUE]; + int highest_i[MAX_QUEUE], highest_j[MAX_QUEUE]; + float lowest_priority = view_dist_in_chunks * view_dist_in_chunks * 16 * 16.0f; + int lowest_i = -1, lowest_j = -1; + + for (i=0; i < MAX_QUEUE; ++i) { + highest_priority[i] = min_priority; + highest_i[i] = -1; + highest_j[i] = -1; + } + + for (j=0; j < CACHED_MESH_NUM_Y; ++j) { + for (i=0; i < CACHED_MESH_NUM_X; ++i) { + chunk_mesh *cm = &cached_chunk_mesh[j][i]; + if (cm->state == STATE_valid) { + cm->priority = compute_priority(cm->chunk_x, cm->chunk_y, x, y); + chunk_storage_total += cm->vbuf_size + cm->fbuf_size; + if (cm->priority < lowest_priority) { + lowest_priority = cm->priority; + lowest_i = i; + lowest_j = j; + } + } + if (cm->state == STATE_needed) { + cm->priority = compute_priority(cm->chunk_x, cm->chunk_y, x, y); + if (cm->priority < min_priority) + cm->state = STATE_invalid; + else if (cm->priority > highest_priority[0]) { + int k; + highest_priority[0] = cm->priority; + highest_i[0] = i; + highest_j[0] = j; + // bubble this up to right place + for (k=0; k < MAX_QUEUE-1; ++k) { + if (highest_priority[k] > highest_priority[k+1]) { + highest_priority[k] = highest_priority[k+1]; + highest_priority[k+1] = cm->priority; + highest_i[k] = highest_i[k+1]; + highest_i[k+1] = i; + highest_j[k] = highest_j[k+1]; + highest_j[k+1] = j; + } else { + break; + } + } + } + } + } + } + + + // I couldn't find any straightforward logic that avoids + // the hysteresis problem of continually creating & freeing + // a block on the margin, so I just don't free a block until + // it's out of range, but this doesn't actually correctly + // handle when the cache is too small for the given range + if (chunk_storage_total >= min_chunk_storage && lowest_i >= 0) { + if (cached_chunk_mesh[lowest_j][lowest_i].priority < -1200) // -1000? 0? + free_chunk(lowest_i, lowest_j); + } + + if (chunk_storage_total < max_chunk_storage && highest_i[0] >= 0) { + for (j=MAX_QUEUE-1; j >= 0; --j) { + if (highest_j[0] >= 0) { + chunk_mesh *cm = &cached_chunk_mesh[highest_j[j]][highest_i[j]]; + if (request_chunk(cm->chunk_x, cm->chunk_y)) { + cm->state = STATE_requested; + } else { + // if we couldn't queue this one, skip the remainder + break; + } + } + } + } + } + + update_meshes_from_render_thread(); + + num_threads_active = 0; + for (i=0; i < num_mesh_workers; ++i) { + num_threads_active += (mesh_data[i].state == WSTATE_running); + } +} diff --git a/source/engine/thirdparty/stb/tests/caveview/caveview.dsp b/source/engine/thirdparty/stb/tests/caveview/caveview.dsp new file mode 100644 index 0000000..2a462d3 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/caveview/caveview.dsp @@ -0,0 +1,157 @@ +# Microsoft Developer Studio Project File - Name="caveview" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=caveview - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "caveview.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "caveview.mak" CFG="caveview - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "caveview - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "caveview - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "caveview - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /WX /GX /Zd /O2 /I "../.." /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib sdl2.lib opengl32.lib glu32.lib winmm.lib sdl2_mixer.lib advapi32.lib /nologo /subsystem:windows /debug /machine:I386 +# SUBTRACT LINK32 /map + +!ELSEIF "$(CFG)" == "caveview - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /WX /Gm /GX /Zi /Od /I "../.." /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib advapi32.lib winspool.lib comdlg32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib sdl2.lib opengl32.lib glu32.lib winmm.lib sdl2_mixer.lib /nologo /subsystem:windows /incremental:no /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "caveview - Win32 Release" +# Name "caveview - Win32 Debug" +# Begin Source File + +SOURCE=.\cave_main.c +# End Source File +# Begin Source File + +SOURCE=.\cave_mesher.c +# End Source File +# Begin Source File + +SOURCE=.\cave_parse.c +# End Source File +# Begin Source File + +SOURCE=.\cave_parse.h +# End Source File +# Begin Source File + +SOURCE=.\cave_render.c +# End Source File +# Begin Source File + +SOURCE=.\caveview.h +# End Source File +# Begin Source File + +SOURCE=.\glext.h +# End Source File +# Begin Source File + +SOURCE=.\glext_list.h +# End Source File +# Begin Source File + +SOURCE=.\README.md +# End Source File +# Begin Source File + +SOURCE=.\win32\SDL_windows_main.c +# End Source File +# Begin Source File + +SOURCE=..\..\stb.h +# End Source File +# Begin Source File + +SOURCE=..\..\stb_easy_font.h +# End Source File +# Begin Source File + +SOURCE=.\stb_gl.h +# End Source File +# Begin Source File + +SOURCE=.\stb_glprog.h +# End Source File +# Begin Source File + +SOURCE=..\..\stb_image.h +# End Source File +# Begin Source File + +SOURCE=..\..\stb_voxel_render.h +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/stb/tests/caveview/caveview.dsw b/source/engine/thirdparty/stb/tests/caveview/caveview.dsw new file mode 100644 index 0000000..ddc9387 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/caveview/caveview.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "caveview"=.\caveview.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/source/engine/thirdparty/stb/tests/caveview/caveview.h b/source/engine/thirdparty/stb/tests/caveview/caveview.h new file mode 100644 index 0000000..73a71da --- /dev/null +++ b/source/engine/thirdparty/stb/tests/caveview/caveview.h @@ -0,0 +1,50 @@ +#ifndef INCLUDE_CAVEVIEW_H +#define INCLUDE_CAVEVIEW_H + +#include "stb.h" + +#include "stb_voxel_render.h" + +typedef struct +{ + int cx,cy; + + stbvox_mesh_maker mm; + + uint8 *build_buffer; + uint8 *face_buffer; + + int num_quads; + float transform[3][3]; + float bounds[2][3]; + + uint8 sv_blocktype[34][34][18]; + uint8 sv_lighting [34][34][18]; +} raw_mesh; + +// a 3D checkerboard of empty,solid would be: 32x32x255x6/2 ~= 800000 +// an all-leaf qchunk would be: 32 x 32 x 255 x 6 ~= 1,600,000 + +#define BUILD_QUAD_MAX 400000 +#define BUILD_BUFFER_SIZE (4*4*BUILD_QUAD_MAX) // 4 bytes per vertex, 4 vertices per quad +#define FACE_BUFFER_SIZE ( 4*BUILD_QUAD_MAX) // 4 bytes per quad + + +extern void mesh_init(void); +extern void render_init(void); +extern void world_init(void); +extern void ods(char *fmt, ...); // output debug string +extern void reset_cache_size(int size); + + +extern void render_caves(float pos[3]); + + +#include "cave_parse.h" // fast_chunk + +extern fast_chunk *get_converted_fastchunk(int chunk_x, int chunk_y); +extern void build_chunk(int chunk_x, int chunk_y, fast_chunk *fc_table[4][4], raw_mesh *rm); +extern void reset_cache_size(int size); +extern void deref_fastchunk(fast_chunk *fc); + +#endif \ No newline at end of file diff --git a/source/engine/thirdparty/stb/tests/caveview/glext.h b/source/engine/thirdparty/stb/tests/caveview/glext.h new file mode 100644 index 0000000..c6a233a --- /dev/null +++ b/source/engine/thirdparty/stb/tests/caveview/glext.h @@ -0,0 +1,11124 @@ +#ifndef __glext_h_ +#define __glext_h_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright (c) 2013 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are 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 Materials. +** +** THE MATERIALS ARE 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 +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ +/* +** This header is generated from the Khronos OpenGL / OpenGL ES XML +** API Registry. The current version of the Registry, generator scripts +** used to make the header, and the header can be found at +** http://www.opengl.org/registry/ +** +** Khronos $Revision: 24756 $ on $Date: 2014-01-14 03:42:29 -0800 (Tue, 14 Jan 2014) $ +*/ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI +#define GLAPI extern +#endif + +#define GL_GLEXT_VERSION 20140114 + +/* Generated C header for: + * API: gl + * Profile: compatibility + * Versions considered: .* + * Versions emitted: 1\.[2-9]|[234]\.[0-9] + * Default extensions included: gl + * Additional extensions included: _nomatch_^ + * Extensions removed: _nomatch_^ + */ + +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#define GL_RESCALE_NORMAL 0x803A +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); +typedef void (APIENTRYP PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawRangeElements (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); +GLAPI void APIENTRY glTexImage3D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif +#endif /* GL_VERSION_1_2 */ + +#ifndef GL_VERSION_1_3 +#define GL_VERSION_1_3 1 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 +#define GL_MULTISAMPLE_BIT 0x20000000 +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_SUBTRACT 0x84E7 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF +typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEPROC) (GLfloat value, GLboolean invert); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint level, void *img); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDPROC) (const GLdouble *m); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTexture (GLenum texture); +GLAPI void APIENTRY glSampleCoverage (GLfloat value, GLboolean invert); +GLAPI void APIENTRY glCompressedTexImage3D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexImage2D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexImage1D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glGetCompressedTexImage (GLenum target, GLint level, void *img); +GLAPI void APIENTRY glClientActiveTexture (GLenum texture); +GLAPI void APIENTRY glMultiTexCoord1d (GLenum target, GLdouble s); +GLAPI void APIENTRY glMultiTexCoord1dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord1f (GLenum target, GLfloat s); +GLAPI void APIENTRY glMultiTexCoord1fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord1i (GLenum target, GLint s); +GLAPI void APIENTRY glMultiTexCoord1iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord1s (GLenum target, GLshort s); +GLAPI void APIENTRY glMultiTexCoord1sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord2d (GLenum target, GLdouble s, GLdouble t); +GLAPI void APIENTRY glMultiTexCoord2dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord2f (GLenum target, GLfloat s, GLfloat t); +GLAPI void APIENTRY glMultiTexCoord2fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord2i (GLenum target, GLint s, GLint t); +GLAPI void APIENTRY glMultiTexCoord2iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord2s (GLenum target, GLshort s, GLshort t); +GLAPI void APIENTRY glMultiTexCoord2sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord3d (GLenum target, GLdouble s, GLdouble t, GLdouble r); +GLAPI void APIENTRY glMultiTexCoord3dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord3f (GLenum target, GLfloat s, GLfloat t, GLfloat r); +GLAPI void APIENTRY glMultiTexCoord3fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord3i (GLenum target, GLint s, GLint t, GLint r); +GLAPI void APIENTRY glMultiTexCoord3iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord3s (GLenum target, GLshort s, GLshort t, GLshort r); +GLAPI void APIENTRY glMultiTexCoord3sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord4d (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI void APIENTRY glMultiTexCoord4dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord4f (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI void APIENTRY glMultiTexCoord4fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord4i (GLenum target, GLint s, GLint t, GLint r, GLint q); +GLAPI void APIENTRY glMultiTexCoord4iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord4s (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI void APIENTRY glMultiTexCoord4sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glLoadTransposeMatrixf (const GLfloat *m); +GLAPI void APIENTRY glLoadTransposeMatrixd (const GLdouble *m); +GLAPI void APIENTRY glMultTransposeMatrixf (const GLfloat *m); +GLAPI void APIENTRY glMultTransposeMatrixd (const GLdouble *m); +#endif +#endif /* GL_VERSION_1_3 */ + +#ifndef GL_VERSION_1_4 +#define GL_VERSION_1_4 1 +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_POINT_SIZE_MIN 0x8126 +#define GL_POINT_SIZE_MAX 0x8127 +#define GL_POINT_DISTANCE_ATTENUATION 0x8129 +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_FOG_COORDINATE_SOURCE 0x8450 +#define GL_FOG_COORDINATE 0x8451 +#define GL_FRAGMENT_DEPTH 0x8452 +#define GL_CURRENT_FOG_COORDINATE 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 +#define GL_FOG_COORDINATE_ARRAY 0x8457 +#define GL_COLOR_SUM 0x8458 +#define GL_CURRENT_SECONDARY_COLOR 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D +#define GL_SECONDARY_COLOR_ARRAY 0x845E +#define GL_TEXTURE_FILTER_CONTROL 0x8500 +#define GL_DEPTH_TEXTURE_MODE 0x884B +#define GL_COMPARE_R_TO_TEXTURE 0x884E +#define GL_FUNC_ADD 0x8006 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFOGCOORDFPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERPROC) (GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLWINDOWPOS2DPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLBLENDCOLORPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparate (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +GLAPI void APIENTRY glMultiDrawArrays (GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount); +GLAPI void APIENTRY glMultiDrawElements (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount); +GLAPI void APIENTRY glPointParameterf (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfv (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glPointParameteri (GLenum pname, GLint param); +GLAPI void APIENTRY glPointParameteriv (GLenum pname, const GLint *params); +GLAPI void APIENTRY glFogCoordf (GLfloat coord); +GLAPI void APIENTRY glFogCoordfv (const GLfloat *coord); +GLAPI void APIENTRY glFogCoordd (GLdouble coord); +GLAPI void APIENTRY glFogCoorddv (const GLdouble *coord); +GLAPI void APIENTRY glFogCoordPointer (GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glSecondaryColor3b (GLbyte red, GLbyte green, GLbyte blue); +GLAPI void APIENTRY glSecondaryColor3bv (const GLbyte *v); +GLAPI void APIENTRY glSecondaryColor3d (GLdouble red, GLdouble green, GLdouble blue); +GLAPI void APIENTRY glSecondaryColor3dv (const GLdouble *v); +GLAPI void APIENTRY glSecondaryColor3f (GLfloat red, GLfloat green, GLfloat blue); +GLAPI void APIENTRY glSecondaryColor3fv (const GLfloat *v); +GLAPI void APIENTRY glSecondaryColor3i (GLint red, GLint green, GLint blue); +GLAPI void APIENTRY glSecondaryColor3iv (const GLint *v); +GLAPI void APIENTRY glSecondaryColor3s (GLshort red, GLshort green, GLshort blue); +GLAPI void APIENTRY glSecondaryColor3sv (const GLshort *v); +GLAPI void APIENTRY glSecondaryColor3ub (GLubyte red, GLubyte green, GLubyte blue); +GLAPI void APIENTRY glSecondaryColor3ubv (const GLubyte *v); +GLAPI void APIENTRY glSecondaryColor3ui (GLuint red, GLuint green, GLuint blue); +GLAPI void APIENTRY glSecondaryColor3uiv (const GLuint *v); +GLAPI void APIENTRY glSecondaryColor3us (GLushort red, GLushort green, GLushort blue); +GLAPI void APIENTRY glSecondaryColor3usv (const GLushort *v); +GLAPI void APIENTRY glSecondaryColorPointer (GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glWindowPos2d (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dv (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2f (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fv (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2i (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2iv (const GLint *v); +GLAPI void APIENTRY glWindowPos2s (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2sv (const GLshort *v); +GLAPI void APIENTRY glWindowPos3d (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dv (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3f (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fv (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3i (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3iv (const GLint *v); +GLAPI void APIENTRY glWindowPos3s (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3sv (const GLshort *v); +GLAPI void APIENTRY glBlendColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI void APIENTRY glBlendEquation (GLenum mode); +#endif +#endif /* GL_VERSION_1_4 */ + +#ifndef GL_VERSION_1_5 +#define GL_VERSION_1_5 1 +#include +typedef ptrdiff_t GLsizeiptr; +typedef ptrdiff_t GLintptr; +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 +#define GL_SRC1_ALPHA 0x8589 +#define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E +#define GL_FOG_COORD_SRC 0x8450 +#define GL_FOG_COORD 0x8451 +#define GL_CURRENT_FOG_COORD 0x8453 +#define GL_FOG_COORD_ARRAY_TYPE 0x8454 +#define GL_FOG_COORD_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORD_ARRAY_POINTER 0x8456 +#define GL_FOG_COORD_ARRAY 0x8457 +#define GL_FOG_COORD_ARRAY_BUFFER_BINDING 0x889D +#define GL_SRC0_RGB 0x8580 +#define GL_SRC1_RGB 0x8581 +#define GL_SRC2_RGB 0x8582 +#define GL_SRC0_ALPHA 0x8588 +#define GL_SRC2_ALPHA 0x858A +typedef void (APIENTRYP PFNGLGENQUERIESPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVPROC) (GLuint id, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const void *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, void *data); +typedef void *(APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVPROC) (GLenum target, GLenum pname, void **params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueries (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteQueries (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsQuery (GLuint id); +GLAPI void APIENTRY glBeginQuery (GLenum target, GLuint id); +GLAPI void APIENTRY glEndQuery (GLenum target); +GLAPI void APIENTRY glGetQueryiv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectiv (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectuiv (GLuint id, GLenum pname, GLuint *params); +GLAPI void APIENTRY glBindBuffer (GLenum target, GLuint buffer); +GLAPI void APIENTRY glDeleteBuffers (GLsizei n, const GLuint *buffers); +GLAPI void APIENTRY glGenBuffers (GLsizei n, GLuint *buffers); +GLAPI GLboolean APIENTRY glIsBuffer (GLuint buffer); +GLAPI void APIENTRY glBufferData (GLenum target, GLsizeiptr size, const void *data, GLenum usage); +GLAPI void APIENTRY glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +GLAPI void APIENTRY glGetBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, void *data); +GLAPI void *APIENTRY glMapBuffer (GLenum target, GLenum access); +GLAPI GLboolean APIENTRY glUnmapBuffer (GLenum target); +GLAPI void APIENTRY glGetBufferParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetBufferPointerv (GLenum target, GLenum pname, void **params); +#endif +#endif /* GL_VERSION_1_5 */ + +#ifndef GL_VERSION_2_0 +#define GL_VERSION_2_0 1 +typedef char GLchar; +#define GL_BLEND_EQUATION_RGB 0x8009 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#define GL_MAX_DRAW_BUFFERS 0x8824 +#define GL_DRAW_BUFFER0 0x8825 +#define GL_DRAW_BUFFER1 0x8826 +#define GL_DRAW_BUFFER2 0x8827 +#define GL_DRAW_BUFFER3 0x8828 +#define GL_DRAW_BUFFER4 0x8829 +#define GL_DRAW_BUFFER5 0x882A +#define GL_DRAW_BUFFER6 0x882B +#define GL_DRAW_BUFFER7 0x882C +#define GL_DRAW_BUFFER8 0x882D +#define GL_DRAW_BUFFER9 0x882E +#define GL_DRAW_BUFFER10 0x882F +#define GL_DRAW_BUFFER11 0x8830 +#define GL_DRAW_BUFFER12 0x8831 +#define GL_DRAW_BUFFER13 0x8832 +#define GL_DRAW_BUFFER14 0x8833 +#define GL_DRAW_BUFFER15 0x8834 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GL_MAX_VARYING_FLOATS 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_SHADER_TYPE 0x8B4F +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT_VEC2 0x8B53 +#define GL_INT_VEC3 0x8B54 +#define GL_INT_VEC4 0x8B55 +#define GL_BOOL 0x8B56 +#define GL_BOOL_VEC2 0x8B57 +#define GL_BOOL_VEC3 0x8B58 +#define GL_BOOL_VEC4 0x8B59 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#define GL_SAMPLER_1D 0x8B5D +#define GL_SAMPLER_2D 0x8B5E +#define GL_SAMPLER_3D 0x8B5F +#define GL_SAMPLER_CUBE 0x8B60 +#define GL_SAMPLER_1D_SHADOW 0x8B61 +#define GL_SAMPLER_2D_SHADOW 0x8B62 +#define GL_DELETE_STATUS 0x8B80 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_VALIDATE_STATUS 0x8B83 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 +#define GL_LOWER_LEFT 0x8CA1 +#define GL_UPPER_LEFT 0x8CA2 +#define GL_STENCIL_BACK_REF 0x8CA3 +#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 +#define GL_STENCIL_BACK_WRITEMASK 0x8CA5 +#define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 +#define GL_POINT_SPRITE 0x8861 +#define GL_COORD_REPLACE 0x8862 +#define GL_MAX_TEXTURE_COORDS 0x8871 +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLDRAWBUFFERSPROC) (GLsizei n, const GLenum *bufs); +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEPROC) (GLenum face, GLenum func, GLint ref, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILMASKSEPARATEPROC) (GLenum face, GLuint mask); +typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONPROC) (GLuint program, GLuint index, const GLchar *name); +typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETATTACHEDSHADERSPROC) (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *shaders); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVPROC) (GLuint program, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVPROC) (GLuint program, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint index, GLenum pname, void **pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC) (GLuint program); +typedef GLboolean (APIENTRYP PFNGLISSHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparate (GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glDrawBuffers (GLsizei n, const GLenum *bufs); +GLAPI void APIENTRY glStencilOpSeparate (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +GLAPI void APIENTRY glStencilFuncSeparate (GLenum face, GLenum func, GLint ref, GLuint mask); +GLAPI void APIENTRY glStencilMaskSeparate (GLenum face, GLuint mask); +GLAPI void APIENTRY glAttachShader (GLuint program, GLuint shader); +GLAPI void APIENTRY glBindAttribLocation (GLuint program, GLuint index, const GLchar *name); +GLAPI void APIENTRY glCompileShader (GLuint shader); +GLAPI GLuint APIENTRY glCreateProgram (void); +GLAPI GLuint APIENTRY glCreateShader (GLenum type); +GLAPI void APIENTRY glDeleteProgram (GLuint program); +GLAPI void APIENTRY glDeleteShader (GLuint shader); +GLAPI void APIENTRY glDetachShader (GLuint program, GLuint shader); +GLAPI void APIENTRY glDisableVertexAttribArray (GLuint index); +GLAPI void APIENTRY glEnableVertexAttribArray (GLuint index); +GLAPI void APIENTRY glGetActiveAttrib (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetActiveUniform (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetAttachedShaders (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *shaders); +GLAPI GLint APIENTRY glGetAttribLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetProgramiv (GLuint program, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramInfoLog (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glGetShaderiv (GLuint shader, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glGetShaderSource (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +GLAPI GLint APIENTRY glGetUniformLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetUniformfv (GLuint program, GLint location, GLfloat *params); +GLAPI void APIENTRY glGetUniformiv (GLuint program, GLint location, GLint *params); +GLAPI void APIENTRY glGetVertexAttribdv (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfv (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribiv (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointerv (GLuint index, GLenum pname, void **pointer); +GLAPI GLboolean APIENTRY glIsProgram (GLuint program); +GLAPI GLboolean APIENTRY glIsShader (GLuint shader); +GLAPI void APIENTRY glLinkProgram (GLuint program); +GLAPI void APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +GLAPI void APIENTRY glUseProgram (GLuint program); +GLAPI void APIENTRY glUniform1f (GLint location, GLfloat v0); +GLAPI void APIENTRY glUniform2f (GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glUniform3f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glUniform4f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glUniform1i (GLint location, GLint v0); +GLAPI void APIENTRY glUniform2i (GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glUniform3i (GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glUniform4i (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glUniform1fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform2fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform3fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform4fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform1iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform2iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform3iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform4iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniformMatrix2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glValidateProgram (GLuint program); +GLAPI void APIENTRY glVertexAttrib1d (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1f (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1s (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2d (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2f (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2s (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3d (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3f (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3s (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4Nbv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4Niv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4Nsv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4Nub (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4Nubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4Nuiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4Nusv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttrib4bv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4d (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4f (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4s (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4usv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +#endif +#endif /* GL_VERSION_2_0 */ + +#ifndef GL_VERSION_2_1 +#define GL_VERSION_2_1 1 +#define GL_PIXEL_PACK_BUFFER 0x88EB +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF +#define GL_FLOAT_MAT2x3 0x8B65 +#define GL_FLOAT_MAT2x4 0x8B66 +#define GL_FLOAT_MAT3x2 0x8B67 +#define GL_FLOAT_MAT3x4 0x8B68 +#define GL_FLOAT_MAT4x2 0x8B69 +#define GL_FLOAT_MAT4x3 0x8B6A +#define GL_SRGB 0x8C40 +#define GL_SRGB8 0x8C41 +#define GL_SRGB_ALPHA 0x8C42 +#define GL_SRGB8_ALPHA8 0x8C43 +#define GL_COMPRESSED_SRGB 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA 0x8C49 +#define GL_CURRENT_RASTER_SECONDARY_COLOR 0x845F +#define GL_SLUMINANCE_ALPHA 0x8C44 +#define GL_SLUMINANCE8_ALPHA8 0x8C45 +#define GL_SLUMINANCE 0x8C46 +#define GL_SLUMINANCE8 0x8C47 +#define GL_COMPRESSED_SLUMINANCE 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA 0x8C4B +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniformMatrix2x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix2x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +#endif +#endif /* GL_VERSION_2_1 */ + +#ifndef GL_VERSION_3_0 +#define GL_VERSION_3_0 1 +typedef unsigned short GLhalf; +#define GL_COMPARE_REF_TO_TEXTURE 0x884E +#define GL_CLIP_DISTANCE0 0x3000 +#define GL_CLIP_DISTANCE1 0x3001 +#define GL_CLIP_DISTANCE2 0x3002 +#define GL_CLIP_DISTANCE3 0x3003 +#define GL_CLIP_DISTANCE4 0x3004 +#define GL_CLIP_DISTANCE5 0x3005 +#define GL_CLIP_DISTANCE6 0x3006 +#define GL_CLIP_DISTANCE7 0x3007 +#define GL_MAX_CLIP_DISTANCES 0x0D32 +#define GL_MAJOR_VERSION 0x821B +#define GL_MINOR_VERSION 0x821C +#define GL_NUM_EXTENSIONS 0x821D +#define GL_CONTEXT_FLAGS 0x821E +#define GL_COMPRESSED_RED 0x8225 +#define GL_COMPRESSED_RG 0x8226 +#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x00000001 +#define GL_RGBA32F 0x8814 +#define GL_RGB32F 0x8815 +#define GL_RGBA16F 0x881A +#define GL_RGB16F 0x881B +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER 0x88FD +#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF +#define GL_MIN_PROGRAM_TEXEL_OFFSET 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET 0x8905 +#define GL_CLAMP_READ_COLOR 0x891C +#define GL_FIXED_ONLY 0x891D +#define GL_MAX_VARYING_COMPONENTS 0x8B4B +#define GL_TEXTURE_1D_ARRAY 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY 0x8C19 +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY 0x8C1D +#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_TEXTURE_SHARED_SIZE 0x8C3F +#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 0x8C76 +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 0x8C80 +#define GL_TRANSFORM_FEEDBACK_VARYINGS 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE 0x8C85 +#define GL_PRIMITIVES_GENERATED 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88 +#define GL_RASTERIZER_DISCARD 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS 0x8C8B +#define GL_INTERLEAVED_ATTRIBS 0x8C8C +#define GL_SEPARATE_ATTRIBS 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING 0x8C8F +#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_GREEN_INTEGER 0x8D95 +#define GL_BLUE_INTEGER 0x8D96 +#define GL_RGB_INTEGER 0x8D98 +#define GL_RGBA_INTEGER 0x8D99 +#define GL_BGR_INTEGER 0x8D9A +#define GL_BGRA_INTEGER 0x8D9B +#define GL_SAMPLER_1D_ARRAY 0x8DC0 +#define GL_SAMPLER_2D_ARRAY 0x8DC1 +#define GL_SAMPLER_1D_ARRAY_SHADOW 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW 0x8DC5 +#define GL_UNSIGNED_INT_VEC2 0x8DC6 +#define GL_UNSIGNED_INT_VEC3 0x8DC7 +#define GL_UNSIGNED_INT_VEC4 0x8DC8 +#define GL_INT_SAMPLER_1D 0x8DC9 +#define GL_INT_SAMPLER_2D 0x8DCA +#define GL_INT_SAMPLER_3D 0x8DCB +#define GL_INT_SAMPLER_CUBE 0x8DCC +#define GL_INT_SAMPLER_1D_ARRAY 0x8DCE +#define GL_INT_SAMPLER_2D_ARRAY 0x8DCF +#define GL_UNSIGNED_INT_SAMPLER_1D 0x8DD1 +#define GL_UNSIGNED_INT_SAMPLER_2D 0x8DD2 +#define GL_UNSIGNED_INT_SAMPLER_3D 0x8DD3 +#define GL_UNSIGNED_INT_SAMPLER_CUBE 0x8DD4 +#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY 0x8DD6 +#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY 0x8DD7 +#define GL_QUERY_WAIT 0x8E13 +#define GL_QUERY_NO_WAIT 0x8E14 +#define GL_QUERY_BY_REGION_WAIT 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT 0x8E16 +#define GL_BUFFER_ACCESS_FLAGS 0x911F +#define GL_BUFFER_MAP_LENGTH 0x9120 +#define GL_BUFFER_MAP_OFFSET 0x9121 +#define GL_DEPTH_COMPONENT32F 0x8CAC +#define GL_DEPTH32F_STENCIL8 0x8CAD +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD +#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 +#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 +#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 +#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 +#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 +#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 +#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 +#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 +#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 +#define GL_FRAMEBUFFER_DEFAULT 0x8218 +#define GL_FRAMEBUFFER_UNDEFINED 0x8219 +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#define GL_MAX_RENDERBUFFER_SIZE 0x84E8 +#define GL_DEPTH_STENCIL 0x84F9 +#define GL_UNSIGNED_INT_24_8 0x84FA +#define GL_DEPTH24_STENCIL8 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE 0x88F1 +#define GL_TEXTURE_RED_TYPE 0x8C10 +#define GL_TEXTURE_GREEN_TYPE 0x8C11 +#define GL_TEXTURE_BLUE_TYPE 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE 0x8C13 +#define GL_TEXTURE_DEPTH_TYPE 0x8C16 +#define GL_UNSIGNED_NORMALIZED 0x8C17 +#define GL_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_RENDERBUFFER_BINDING 0x8CA7 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA +#define GL_RENDERBUFFER_SAMPLES 0x8CAB +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_COLOR_ATTACHMENT1 0x8CE1 +#define GL_COLOR_ATTACHMENT2 0x8CE2 +#define GL_COLOR_ATTACHMENT3 0x8CE3 +#define GL_COLOR_ATTACHMENT4 0x8CE4 +#define GL_COLOR_ATTACHMENT5 0x8CE5 +#define GL_COLOR_ATTACHMENT6 0x8CE6 +#define GL_COLOR_ATTACHMENT7 0x8CE7 +#define GL_COLOR_ATTACHMENT8 0x8CE8 +#define GL_COLOR_ATTACHMENT9 0x8CE9 +#define GL_COLOR_ATTACHMENT10 0x8CEA +#define GL_COLOR_ATTACHMENT11 0x8CEB +#define GL_COLOR_ATTACHMENT12 0x8CEC +#define GL_COLOR_ATTACHMENT13 0x8CED +#define GL_COLOR_ATTACHMENT14 0x8CEE +#define GL_COLOR_ATTACHMENT15 0x8CEF +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_RENDERBUFFER 0x8D41 +#define GL_RENDERBUFFER_WIDTH 0x8D42 +#define GL_RENDERBUFFER_HEIGHT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 +#define GL_STENCIL_INDEX1 0x8D46 +#define GL_STENCIL_INDEX4 0x8D47 +#define GL_STENCIL_INDEX8 0x8D48 +#define GL_STENCIL_INDEX16 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 +#define GL_MAX_SAMPLES 0x8D57 +#define GL_INDEX 0x8222 +#define GL_TEXTURE_LUMINANCE_TYPE 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE 0x8C15 +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#define GL_HALF_FLOAT 0x140B +#define GL_MAP_READ_BIT 0x0001 +#define GL_MAP_WRITE_BIT 0x0002 +#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 +#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 +#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 +#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 +#define GL_COMPRESSED_RED_RGTC1 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC +#define GL_COMPRESSED_RG_RGTC2 0x8DBD +#define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE +#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_VERTEX_ARRAY_BINDING 0x85B5 +#define GL_CLAMP_VERTEX_COLOR 0x891A +#define GL_CLAMP_FRAGMENT_COLOR 0x891B +#define GL_ALPHA_INTEGER 0x8D97 +typedef void (APIENTRYP PFNGLCOLORMASKIPROC) (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +typedef void (APIENTRYP PFNGLGETBOOLEANI_VPROC) (GLenum target, GLuint index, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINTEGERI_VPROC) (GLenum target, GLuint index, GLint *data); +typedef void (APIENTRYP PFNGLENABLEIPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLDISABLEIPROC) (GLenum target, GLuint index); +typedef GLboolean (APIENTRYP PFNGLISENABLEDIPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGEPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFERBASEPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSPROC) (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLCLAMPCOLORPROC) (GLenum target, GLenum clamp); +typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERPROC) (GLuint id, GLenum mode); +typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERPROC) (void); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIPOINTERPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIUIVPROC) (GLuint index, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IPROC) (GLuint index, GLint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IPROC) (GLuint index, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IPROC) (GLuint index, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IPROC) (GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIPROC) (GLuint index, GLuint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIPROC) (GLuint index, GLuint x, GLuint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIPROC) (GLuint index, GLuint x, GLuint y, GLuint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIPROC) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4BVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4USVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLGETUNIFORMUIVPROC) (GLuint program, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONPROC) (GLuint program, GLuint color, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATALOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORM1UIPROC) (GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLUNIFORM2UIPROC) (GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLUNIFORM3UIPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLUNIFORM4UIPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLUNIFORM1UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM2UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM3UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM4UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLTEXPARAMETERIIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXPARAMETERIUIVPROC) (GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIUIVPROC) (GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLCLEARBUFFERIVPROC) (GLenum buffer, GLint drawbuffer, const GLint *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERUIVPROC) (GLenum buffer, GLint drawbuffer, const GLuint *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERFVPROC) (GLenum buffer, GLint drawbuffer, const GLfloat *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERFIPROC) (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGIPROC) (GLenum name, GLuint index); +typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFERPROC) (GLuint renderbuffer); +typedef void (APIENTRYP PFNGLBINDRENDERBUFFERPROC) (GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSPROC) (GLsizei n, const GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLGENRENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFERPROC) (GLuint framebuffer); +typedef void (APIENTRYP PFNGLBINDFRAMEBUFFERPROC) (GLenum target, GLuint framebuffer); +typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSPROC) (GLsizei n, const GLuint *framebuffers); +typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers); +typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSPROC) (GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFERPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATEMIPMAPPROC) (GLenum target); +typedef void (APIENTRYP PFNGLBLITFRAMEBUFFERPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void *(APIENTRYP PFNGLMAPBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length); +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays); +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYPROC) (GLuint array); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorMaski (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +GLAPI void APIENTRY glGetBooleani_v (GLenum target, GLuint index, GLboolean *data); +GLAPI void APIENTRY glGetIntegeri_v (GLenum target, GLuint index, GLint *data); +GLAPI void APIENTRY glEnablei (GLenum target, GLuint index); +GLAPI void APIENTRY glDisablei (GLenum target, GLuint index); +GLAPI GLboolean APIENTRY glIsEnabledi (GLenum target, GLuint index); +GLAPI void APIENTRY glBeginTransformFeedback (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedback (void); +GLAPI void APIENTRY glBindBufferRange (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferBase (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryings (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); +GLAPI void APIENTRY glGetTransformFeedbackVarying (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glClampColor (GLenum target, GLenum clamp); +GLAPI void APIENTRY glBeginConditionalRender (GLuint id, GLenum mode); +GLAPI void APIENTRY glEndConditionalRender (void); +GLAPI void APIENTRY glVertexAttribIPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glGetVertexAttribIiv (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribIuiv (GLuint index, GLenum pname, GLuint *params); +GLAPI void APIENTRY glVertexAttribI1i (GLuint index, GLint x); +GLAPI void APIENTRY glVertexAttribI2i (GLuint index, GLint x, GLint y); +GLAPI void APIENTRY glVertexAttribI3i (GLuint index, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexAttribI4i (GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexAttribI1ui (GLuint index, GLuint x); +GLAPI void APIENTRY glVertexAttribI2ui (GLuint index, GLuint x, GLuint y); +GLAPI void APIENTRY glVertexAttribI3ui (GLuint index, GLuint x, GLuint y, GLuint z); +GLAPI void APIENTRY glVertexAttribI4ui (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glVertexAttribI1iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI2iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI3iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI4iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI1uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI2uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI3uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4bv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttribI4sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttribI4ubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribI4usv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glGetUniformuiv (GLuint program, GLint location, GLuint *params); +GLAPI void APIENTRY glBindFragDataLocation (GLuint program, GLuint color, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glUniform1ui (GLint location, GLuint v0); +GLAPI void APIENTRY glUniform2ui (GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glUniform3ui (GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glUniform4ui (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glUniform1uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform2uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform3uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform4uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glTexParameterIiv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTexParameterIuiv (GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTexParameterIiv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTexParameterIuiv (GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glClearBufferiv (GLenum buffer, GLint drawbuffer, const GLint *value); +GLAPI void APIENTRY glClearBufferuiv (GLenum buffer, GLint drawbuffer, const GLuint *value); +GLAPI void APIENTRY glClearBufferfv (GLenum buffer, GLint drawbuffer, const GLfloat *value); +GLAPI void APIENTRY glClearBufferfi (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +GLAPI const GLubyte *APIENTRY glGetStringi (GLenum name, GLuint index); +GLAPI GLboolean APIENTRY glIsRenderbuffer (GLuint renderbuffer); +GLAPI void APIENTRY glBindRenderbuffer (GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glDeleteRenderbuffers (GLsizei n, const GLuint *renderbuffers); +GLAPI void APIENTRY glGenRenderbuffers (GLsizei n, GLuint *renderbuffers); +GLAPI void APIENTRY glRenderbufferStorage (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetRenderbufferParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI GLboolean APIENTRY glIsFramebuffer (GLuint framebuffer); +GLAPI void APIENTRY glBindFramebuffer (GLenum target, GLuint framebuffer); +GLAPI void APIENTRY glDeleteFramebuffers (GLsizei n, const GLuint *framebuffers); +GLAPI void APIENTRY glGenFramebuffers (GLsizei n, GLuint *framebuffers); +GLAPI GLenum APIENTRY glCheckFramebufferStatus (GLenum target); +GLAPI void APIENTRY glFramebufferTexture1D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture3D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetFramebufferAttachmentParameteriv (GLenum target, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateMipmap (GLenum target); +GLAPI void APIENTRY glBlitFramebuffer (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +GLAPI void APIENTRY glRenderbufferStorageMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glFramebufferTextureLayer (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void *APIENTRY glMapBufferRange (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +GLAPI void APIENTRY glFlushMappedBufferRange (GLenum target, GLintptr offset, GLsizeiptr length); +GLAPI void APIENTRY glBindVertexArray (GLuint array); +GLAPI void APIENTRY glDeleteVertexArrays (GLsizei n, const GLuint *arrays); +GLAPI void APIENTRY glGenVertexArrays (GLsizei n, GLuint *arrays); +GLAPI GLboolean APIENTRY glIsVertexArray (GLuint array); +#endif +#endif /* GL_VERSION_3_0 */ + +#ifndef GL_VERSION_3_1 +#define GL_VERSION_3_1 1 +#define GL_SAMPLER_2D_RECT 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW 0x8B64 +#define GL_SAMPLER_BUFFER 0x8DC2 +#define GL_INT_SAMPLER_2D_RECT 0x8DCD +#define GL_INT_SAMPLER_BUFFER 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_2D_RECT 0x8DD5 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER 0x8DD8 +#define GL_TEXTURE_BUFFER 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING 0x8C2D +#define GL_TEXTURE_RECTANGLE 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE 0x84F8 +#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_SIGNED_NORMALIZED 0x8F9C +#define GL_PRIMITIVE_RESTART 0x8F9D +#define GL_PRIMITIVE_RESTART_INDEX 0x8F9E +#define GL_COPY_READ_BUFFER 0x8F36 +#define GL_COPY_WRITE_BUFFER 0x8F37 +#define GL_UNIFORM_BUFFER 0x8A11 +#define GL_UNIFORM_BUFFER_BINDING 0x8A28 +#define GL_UNIFORM_BUFFER_START 0x8A29 +#define GL_UNIFORM_BUFFER_SIZE 0x8A2A +#define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B +#define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D +#define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E +#define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F +#define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30 +#define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31 +#define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33 +#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 +#define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35 +#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 +#define GL_UNIFORM_TYPE 0x8A37 +#define GL_UNIFORM_SIZE 0x8A38 +#define GL_UNIFORM_NAME_LENGTH 0x8A39 +#define GL_UNIFORM_BLOCK_INDEX 0x8A3A +#define GL_UNIFORM_OFFSET 0x8A3B +#define GL_UNIFORM_ARRAY_STRIDE 0x8A3C +#define GL_UNIFORM_MATRIX_STRIDE 0x8A3D +#define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E +#define GL_UNIFORM_BLOCK_BINDING 0x8A3F +#define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40 +#define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46 +#define GL_INVALID_INDEX 0xFFFFFFFFu +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDPROC) (GLenum mode, GLint first, GLsizei count, GLsizei instancecount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount); +typedef void (APIENTRYP PFNGLTEXBUFFERPROC) (GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXPROC) (GLuint index); +typedef void (APIENTRYP PFNGLCOPYBUFFERSUBDATAPROC) (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLGETUNIFORMINDICESPROC) (GLuint program, GLsizei uniformCount, const GLchar *const*uniformNames, GLuint *uniformIndices); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMSIVPROC) (GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMNAMEPROC) (GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformName); +typedef GLuint (APIENTRYP PFNGLGETUNIFORMBLOCKINDEXPROC) (GLuint program, const GLchar *uniformBlockName); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKIVPROC) (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC) (GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName); +typedef void (APIENTRYP PFNGLUNIFORMBLOCKBINDINGPROC) (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstanced (GLenum mode, GLint first, GLsizei count, GLsizei instancecount); +GLAPI void APIENTRY glDrawElementsInstanced (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount); +GLAPI void APIENTRY glTexBuffer (GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glPrimitiveRestartIndex (GLuint index); +GLAPI void APIENTRY glCopyBufferSubData (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +GLAPI void APIENTRY glGetUniformIndices (GLuint program, GLsizei uniformCount, const GLchar *const*uniformNames, GLuint *uniformIndices); +GLAPI void APIENTRY glGetActiveUniformsiv (GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetActiveUniformName (GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformName); +GLAPI GLuint APIENTRY glGetUniformBlockIndex (GLuint program, const GLchar *uniformBlockName); +GLAPI void APIENTRY glGetActiveUniformBlockiv (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetActiveUniformBlockName (GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName); +GLAPI void APIENTRY glUniformBlockBinding (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); +#endif +#endif /* GL_VERSION_3_1 */ + +#ifndef GL_VERSION_3_2 +#define GL_VERSION_3_2 1 +typedef struct __GLsync *GLsync; +#ifndef GLEXT_64_TYPES_DEFINED +/* This code block is duplicated in glxext.h, so must be protected */ +#define GLEXT_64_TYPES_DEFINED +/* Define int32_t, int64_t, and uint64_t types for UST/MSC */ +/* (as used in the GL_EXT_timer_query extension). */ +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#include +#elif defined(__sun__) || defined(__digital__) +#include +#if defined(__STDC__) +#if defined(__arch64__) || defined(_LP64) +typedef long int int64_t; +typedef unsigned long int uint64_t; +#else +typedef long long int int64_t; +typedef unsigned long long int uint64_t; +#endif /* __arch64__ */ +#endif /* __STDC__ */ +#elif defined( __VMS ) || defined(__sgi) +#include +#elif defined(__SCO__) || defined(__USLC__) +#include +#elif defined(__UNIXOS2__) || defined(__SOL64__) +typedef long int int32_t; +typedef long long int int64_t; +typedef unsigned long long int uint64_t; +#elif defined(_WIN32) && defined(__GNUC__) +#include +#elif defined(_WIN32) +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +/* Fallback if nothing above works */ +#include +#endif +#endif +typedef uint64_t GLuint64; +typedef int64_t GLint64; +#define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001 +#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 +#define GL_LINES_ADJACENCY 0x000A +#define GL_LINE_STRIP_ADJACENCY 0x000B +#define GL_TRIANGLES_ADJACENCY 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY 0x000D +#define GL_PROGRAM_POINT_SIZE 0x8642 +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS 0x8DA8 +#define GL_GEOMETRY_SHADER 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT 0x8916 +#define GL_GEOMETRY_INPUT_TYPE 0x8917 +#define GL_GEOMETRY_OUTPUT_TYPE 0x8918 +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS 0x8DE1 +#define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122 +#define GL_MAX_GEOMETRY_INPUT_COMPONENTS 0x9123 +#define GL_MAX_GEOMETRY_OUTPUT_COMPONENTS 0x9124 +#define GL_MAX_FRAGMENT_INPUT_COMPONENTS 0x9125 +#define GL_CONTEXT_PROFILE_MASK 0x9126 +#define GL_DEPTH_CLAMP 0x864F +#define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION 0x8E4C +#define GL_FIRST_VERTEX_CONVENTION 0x8E4D +#define GL_LAST_VERTEX_CONVENTION 0x8E4E +#define GL_PROVOKING_VERTEX 0x8E4F +#define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F +#define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111 +#define GL_OBJECT_TYPE 0x9112 +#define GL_SYNC_CONDITION 0x9113 +#define GL_SYNC_STATUS 0x9114 +#define GL_SYNC_FLAGS 0x9115 +#define GL_SYNC_FENCE 0x9116 +#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117 +#define GL_UNSIGNALED 0x9118 +#define GL_SIGNALED 0x9119 +#define GL_ALREADY_SIGNALED 0x911A +#define GL_TIMEOUT_EXPIRED 0x911B +#define GL_CONDITION_SATISFIED 0x911C +#define GL_WAIT_FAILED 0x911D +#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull +#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001 +#define GL_SAMPLE_POSITION 0x8E50 +#define GL_SAMPLE_MASK 0x8E51 +#define GL_SAMPLE_MASK_VALUE 0x8E52 +#define GL_MAX_SAMPLE_MASK_WORDS 0x8E59 +#define GL_TEXTURE_2D_MULTISAMPLE 0x9100 +#define GL_PROXY_TEXTURE_2D_MULTISAMPLE 0x9101 +#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 +#define GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9103 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE 0x9104 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY 0x9105 +#define GL_TEXTURE_SAMPLES 0x9106 +#define GL_TEXTURE_FIXED_SAMPLE_LOCATIONS 0x9107 +#define GL_SAMPLER_2D_MULTISAMPLE 0x9108 +#define GL_INT_SAMPLER_2D_MULTISAMPLE 0x9109 +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE 0x910A +#define GL_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910B +#define GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910C +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910D +#define GL_MAX_COLOR_TEXTURE_SAMPLES 0x910E +#define GL_MAX_DEPTH_TEXTURE_SAMPLES 0x910F +#define GL_MAX_INTEGER_SAMPLES 0x9110 +typedef void (APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount, const GLint *basevertex); +typedef void (APIENTRYP PFNGLPROVOKINGVERTEXPROC) (GLenum mode); +typedef GLsync (APIENTRYP PFNGLFENCESYNCPROC) (GLenum condition, GLbitfield flags); +typedef GLboolean (APIENTRYP PFNGLISSYNCPROC) (GLsync sync); +typedef void (APIENTRYP PFNGLDELETESYNCPROC) (GLsync sync); +typedef GLenum (APIENTRYP PFNGLCLIENTWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout); +typedef void (APIENTRYP PFNGLWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout); +typedef void (APIENTRYP PFNGLGETINTEGER64VPROC) (GLenum pname, GLint64 *data); +typedef void (APIENTRYP PFNGLGETSYNCIVPROC) (GLsync sync, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values); +typedef void (APIENTRYP PFNGLGETINTEGER64I_VPROC) (GLenum target, GLuint index, GLint64 *data); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERI64VPROC) (GLenum target, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLTEXIMAGE2DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXIMAGE3DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLGETMULTISAMPLEFVPROC) (GLenum pname, GLuint index, GLfloat *val); +typedef void (APIENTRYP PFNGLSAMPLEMASKIPROC) (GLuint maskNumber, GLbitfield mask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawElementsBaseVertex (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +GLAPI void APIENTRY glDrawRangeElementsBaseVertex (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex); +GLAPI void APIENTRY glDrawElementsInstancedBaseVertex (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex); +GLAPI void APIENTRY glMultiDrawElementsBaseVertex (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount, const GLint *basevertex); +GLAPI void APIENTRY glProvokingVertex (GLenum mode); +GLAPI GLsync APIENTRY glFenceSync (GLenum condition, GLbitfield flags); +GLAPI GLboolean APIENTRY glIsSync (GLsync sync); +GLAPI void APIENTRY glDeleteSync (GLsync sync); +GLAPI GLenum APIENTRY glClientWaitSync (GLsync sync, GLbitfield flags, GLuint64 timeout); +GLAPI void APIENTRY glWaitSync (GLsync sync, GLbitfield flags, GLuint64 timeout); +GLAPI void APIENTRY glGetInteger64v (GLenum pname, GLint64 *data); +GLAPI void APIENTRY glGetSynciv (GLsync sync, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values); +GLAPI void APIENTRY glGetInteger64i_v (GLenum target, GLuint index, GLint64 *data); +GLAPI void APIENTRY glGetBufferParameteri64v (GLenum target, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glFramebufferTexture (GLenum target, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glTexImage2DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTexImage3DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glGetMultisamplefv (GLenum pname, GLuint index, GLfloat *val); +GLAPI void APIENTRY glSampleMaski (GLuint maskNumber, GLbitfield mask); +#endif +#endif /* GL_VERSION_3_2 */ + +#ifndef GL_VERSION_3_3 +#define GL_VERSION_3_3 1 +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR 0x88FE +#define GL_SRC1_COLOR 0x88F9 +#define GL_ONE_MINUS_SRC1_COLOR 0x88FA +#define GL_ONE_MINUS_SRC1_ALPHA 0x88FB +#define GL_MAX_DUAL_SOURCE_DRAW_BUFFERS 0x88FC +#define GL_ANY_SAMPLES_PASSED 0x8C2F +#define GL_SAMPLER_BINDING 0x8919 +#define GL_RGB10_A2UI 0x906F +#define GL_TEXTURE_SWIZZLE_R 0x8E42 +#define GL_TEXTURE_SWIZZLE_G 0x8E43 +#define GL_TEXTURE_SWIZZLE_B 0x8E44 +#define GL_TEXTURE_SWIZZLE_A 0x8E45 +#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46 +#define GL_TIME_ELAPSED 0x88BF +#define GL_TIMESTAMP 0x8E28 +#define GL_INT_2_10_10_10_REV 0x8D9F +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONINDEXEDPROC) (GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATAINDEXPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGENSAMPLERSPROC) (GLsizei count, GLuint *samplers); +typedef void (APIENTRYP PFNGLDELETESAMPLERSPROC) (GLsizei count, const GLuint *samplers); +typedef GLboolean (APIENTRYP PFNGLISSAMPLERPROC) (GLuint sampler); +typedef void (APIENTRYP PFNGLBINDSAMPLERPROC) (GLuint unit, GLuint sampler); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIPROC) (GLuint sampler, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIVPROC) (GLuint sampler, GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFPROC) (GLuint sampler, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFVPROC) (GLuint sampler, GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIIVPROC) (GLuint sampler, GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIUIVPROC) (GLuint sampler, GLenum pname, const GLuint *param); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIVPROC) (GLuint sampler, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIIVPROC) (GLuint sampler, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERFVPROC) (GLuint sampler, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIUIVPROC) (GLuint sampler, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLQUERYCOUNTERPROC) (GLuint id, GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTI64VPROC) (GLuint id, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUI64VPROC) (GLuint id, GLenum pname, GLuint64 *params); +typedef void (APIENTRYP PFNGLVERTEXATTRIBDIVISORPROC) (GLuint index, GLuint divisor); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXP2UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP2UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXP3UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP3UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXP4UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP4UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLTEXCOORDP1UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP1UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP2UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP2UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP3UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP3UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP4UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP4UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLNORMALP3UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLNORMALP3UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLCOLORP3UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLCOLORP3UIVPROC) (GLenum type, const GLuint *color); +typedef void (APIENTRYP PFNGLCOLORP4UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLCOLORP4UIVPROC) (GLenum type, const GLuint *color); +typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIVPROC) (GLenum type, const GLuint *color); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindFragDataLocationIndexed (GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataIndex (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGenSamplers (GLsizei count, GLuint *samplers); +GLAPI void APIENTRY glDeleteSamplers (GLsizei count, const GLuint *samplers); +GLAPI GLboolean APIENTRY glIsSampler (GLuint sampler); +GLAPI void APIENTRY glBindSampler (GLuint unit, GLuint sampler); +GLAPI void APIENTRY glSamplerParameteri (GLuint sampler, GLenum pname, GLint param); +GLAPI void APIENTRY glSamplerParameteriv (GLuint sampler, GLenum pname, const GLint *param); +GLAPI void APIENTRY glSamplerParameterf (GLuint sampler, GLenum pname, GLfloat param); +GLAPI void APIENTRY glSamplerParameterfv (GLuint sampler, GLenum pname, const GLfloat *param); +GLAPI void APIENTRY glSamplerParameterIiv (GLuint sampler, GLenum pname, const GLint *param); +GLAPI void APIENTRY glSamplerParameterIuiv (GLuint sampler, GLenum pname, const GLuint *param); +GLAPI void APIENTRY glGetSamplerParameteriv (GLuint sampler, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSamplerParameterIiv (GLuint sampler, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSamplerParameterfv (GLuint sampler, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetSamplerParameterIuiv (GLuint sampler, GLenum pname, GLuint *params); +GLAPI void APIENTRY glQueryCounter (GLuint id, GLenum target); +GLAPI void APIENTRY glGetQueryObjecti64v (GLuint id, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glGetQueryObjectui64v (GLuint id, GLenum pname, GLuint64 *params); +GLAPI void APIENTRY glVertexAttribDivisor (GLuint index, GLuint divisor); +GLAPI void APIENTRY glVertexAttribP1ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP1uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP2ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP2uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP3ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP3uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP4ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP4uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexP2ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP2uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glVertexP3ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP3uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glVertexP4ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP4uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glTexCoordP1ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP1uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP2ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP2uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP3ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP3uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP4ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP4uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP1ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP1uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP2ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP2uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP3ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP3uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP4ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP4uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glNormalP3ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glNormalP3uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glColorP3ui (GLenum type, GLuint color); +GLAPI void APIENTRY glColorP3uiv (GLenum type, const GLuint *color); +GLAPI void APIENTRY glColorP4ui (GLenum type, GLuint color); +GLAPI void APIENTRY glColorP4uiv (GLenum type, const GLuint *color); +GLAPI void APIENTRY glSecondaryColorP3ui (GLenum type, GLuint color); +GLAPI void APIENTRY glSecondaryColorP3uiv (GLenum type, const GLuint *color); +#endif +#endif /* GL_VERSION_3_3 */ + +#ifndef GL_VERSION_4_0 +#define GL_VERSION_4_0 1 +#define GL_SAMPLE_SHADING 0x8C36 +#define GL_MIN_SAMPLE_SHADING_VALUE 0x8C37 +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5F +#define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY 0x900A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARRAY 0x900B +#define GL_SAMPLER_CUBE_MAP_ARRAY 0x900C +#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW 0x900D +#define GL_INT_SAMPLER_CUBE_MAP_ARRAY 0x900E +#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY 0x900F +#define GL_DRAW_INDIRECT_BUFFER 0x8F3F +#define GL_DRAW_INDIRECT_BUFFER_BINDING 0x8F43 +#define GL_GEOMETRY_SHADER_INVOCATIONS 0x887F +#define GL_MAX_GEOMETRY_SHADER_INVOCATIONS 0x8E5A +#define GL_MIN_FRAGMENT_INTERPOLATION_OFFSET 0x8E5B +#define GL_MAX_FRAGMENT_INTERPOLATION_OFFSET 0x8E5C +#define GL_FRAGMENT_INTERPOLATION_OFFSET_BITS 0x8E5D +#define GL_MAX_VERTEX_STREAMS 0x8E71 +#define GL_DOUBLE_VEC2 0x8FFC +#define GL_DOUBLE_VEC3 0x8FFD +#define GL_DOUBLE_VEC4 0x8FFE +#define GL_DOUBLE_MAT2 0x8F46 +#define GL_DOUBLE_MAT3 0x8F47 +#define GL_DOUBLE_MAT4 0x8F48 +#define GL_DOUBLE_MAT2x3 0x8F49 +#define GL_DOUBLE_MAT2x4 0x8F4A +#define GL_DOUBLE_MAT3x2 0x8F4B +#define GL_DOUBLE_MAT3x4 0x8F4C +#define GL_DOUBLE_MAT4x2 0x8F4D +#define GL_DOUBLE_MAT4x3 0x8F4E +#define GL_ACTIVE_SUBROUTINES 0x8DE5 +#define GL_ACTIVE_SUBROUTINE_UNIFORMS 0x8DE6 +#define GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS 0x8E47 +#define GL_ACTIVE_SUBROUTINE_MAX_LENGTH 0x8E48 +#define GL_ACTIVE_SUBROUTINE_UNIFORM_MAX_LENGTH 0x8E49 +#define GL_MAX_SUBROUTINES 0x8DE7 +#define GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS 0x8DE8 +#define GL_NUM_COMPATIBLE_SUBROUTINES 0x8E4A +#define GL_COMPATIBLE_SUBROUTINES 0x8E4B +#define GL_PATCHES 0x000E +#define GL_PATCH_VERTICES 0x8E72 +#define GL_PATCH_DEFAULT_INNER_LEVEL 0x8E73 +#define GL_PATCH_DEFAULT_OUTER_LEVEL 0x8E74 +#define GL_TESS_CONTROL_OUTPUT_VERTICES 0x8E75 +#define GL_TESS_GEN_MODE 0x8E76 +#define GL_TESS_GEN_SPACING 0x8E77 +#define GL_TESS_GEN_VERTEX_ORDER 0x8E78 +#define GL_TESS_GEN_POINT_MODE 0x8E79 +#define GL_ISOLINES 0x8E7A +#define GL_FRACTIONAL_ODD 0x8E7B +#define GL_FRACTIONAL_EVEN 0x8E7C +#define GL_MAX_PATCH_VERTICES 0x8E7D +#define GL_MAX_TESS_GEN_LEVEL 0x8E7E +#define GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS 0x8E7F +#define GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS 0x8E80 +#define GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS 0x8E81 +#define GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS 0x8E82 +#define GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS 0x8E83 +#define GL_MAX_TESS_PATCH_COMPONENTS 0x8E84 +#define GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS 0x8E85 +#define GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS 0x8E86 +#define GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS 0x8E89 +#define GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS 0x8E8A +#define GL_MAX_TESS_CONTROL_INPUT_COMPONENTS 0x886C +#define GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS 0x886D +#define GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS 0x8E1E +#define GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS 0x8E1F +#define GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_CONTROL_SHADER 0x84F0 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_EVALUATION_SHADER 0x84F1 +#define GL_TESS_EVALUATION_SHADER 0x8E87 +#define GL_TESS_CONTROL_SHADER 0x8E88 +#define GL_TRANSFORM_FEEDBACK 0x8E22 +#define GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED 0x8E23 +#define GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE 0x8E24 +#define GL_TRANSFORM_FEEDBACK_BINDING 0x8E25 +#define GL_MAX_TRANSFORM_FEEDBACK_BUFFERS 0x8E70 +typedef void (APIENTRYP PFNGLMINSAMPLESHADINGPROC) (GLfloat value); +typedef void (APIENTRYP PFNGLBLENDEQUATIONIPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEIPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLBLENDFUNCIPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEIPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +typedef void (APIENTRYP PFNGLDRAWARRAYSINDIRECTPROC) (GLenum mode, const void *indirect); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINDIRECTPROC) (GLenum mode, GLenum type, const void *indirect); +typedef void (APIENTRYP PFNGLUNIFORM1DPROC) (GLint location, GLdouble x); +typedef void (APIENTRYP PFNGLUNIFORM2DPROC) (GLint location, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLUNIFORM3DPROC) (GLint location, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLUNIFORM4DPROC) (GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLUNIFORM1DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM2DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM3DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM4DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLGETUNIFORMDVPROC) (GLuint program, GLint location, GLdouble *params); +typedef GLint (APIENTRYP PFNGLGETSUBROUTINEUNIFORMLOCATIONPROC) (GLuint program, GLenum shadertype, const GLchar *name); +typedef GLuint (APIENTRYP PFNGLGETSUBROUTINEINDEXPROC) (GLuint program, GLenum shadertype, const GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINEUNIFORMIVPROC) (GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint *values); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINEUNIFORMNAMEPROC) (GLuint program, GLenum shadertype, GLuint index, GLsizei bufsize, GLsizei *length, GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINENAMEPROC) (GLuint program, GLenum shadertype, GLuint index, GLsizei bufsize, GLsizei *length, GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORMSUBROUTINESUIVPROC) (GLenum shadertype, GLsizei count, const GLuint *indices); +typedef void (APIENTRYP PFNGLGETUNIFORMSUBROUTINEUIVPROC) (GLenum shadertype, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTAGEIVPROC) (GLuint program, GLenum shadertype, GLenum pname, GLint *values); +typedef void (APIENTRYP PFNGLPATCHPARAMETERIPROC) (GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLPATCHPARAMETERFVPROC) (GLenum pname, const GLfloat *values); +typedef void (APIENTRYP PFNGLBINDTRANSFORMFEEDBACKPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETETRANSFORMFEEDBACKSPROC) (GLsizei n, const GLuint *ids); +typedef void (APIENTRYP PFNGLGENTRANSFORMFEEDBACKSPROC) (GLsizei n, GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISTRANSFORMFEEDBACKPROC) (GLuint id); +typedef void (APIENTRYP PFNGLPAUSETRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLRESUMETRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKPROC) (GLenum mode, GLuint id); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKSTREAMPROC) (GLenum mode, GLuint id, GLuint stream); +typedef void (APIENTRYP PFNGLBEGINQUERYINDEXEDPROC) (GLenum target, GLuint index, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYINDEXEDPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLGETQUERYINDEXEDIVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMinSampleShading (GLfloat value); +GLAPI void APIENTRY glBlendEquationi (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparatei (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glBlendFunci (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparatei (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +GLAPI void APIENTRY glDrawArraysIndirect (GLenum mode, const void *indirect); +GLAPI void APIENTRY glDrawElementsIndirect (GLenum mode, GLenum type, const void *indirect); +GLAPI void APIENTRY glUniform1d (GLint location, GLdouble x); +GLAPI void APIENTRY glUniform2d (GLint location, GLdouble x, GLdouble y); +GLAPI void APIENTRY glUniform3d (GLint location, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glUniform4d (GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glUniform1dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform2dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform3dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform4dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2x3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2x4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3x2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3x4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4x2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4x3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glGetUniformdv (GLuint program, GLint location, GLdouble *params); +GLAPI GLint APIENTRY glGetSubroutineUniformLocation (GLuint program, GLenum shadertype, const GLchar *name); +GLAPI GLuint APIENTRY glGetSubroutineIndex (GLuint program, GLenum shadertype, const GLchar *name); +GLAPI void APIENTRY glGetActiveSubroutineUniformiv (GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint *values); +GLAPI void APIENTRY glGetActiveSubroutineUniformName (GLuint program, GLenum shadertype, GLuint index, GLsizei bufsize, GLsizei *length, GLchar *name); +GLAPI void APIENTRY glGetActiveSubroutineName (GLuint program, GLenum shadertype, GLuint index, GLsizei bufsize, GLsizei *length, GLchar *name); +GLAPI void APIENTRY glUniformSubroutinesuiv (GLenum shadertype, GLsizei count, const GLuint *indices); +GLAPI void APIENTRY glGetUniformSubroutineuiv (GLenum shadertype, GLint location, GLuint *params); +GLAPI void APIENTRY glGetProgramStageiv (GLuint program, GLenum shadertype, GLenum pname, GLint *values); +GLAPI void APIENTRY glPatchParameteri (GLenum pname, GLint value); +GLAPI void APIENTRY glPatchParameterfv (GLenum pname, const GLfloat *values); +GLAPI void APIENTRY glBindTransformFeedback (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteTransformFeedbacks (GLsizei n, const GLuint *ids); +GLAPI void APIENTRY glGenTransformFeedbacks (GLsizei n, GLuint *ids); +GLAPI GLboolean APIENTRY glIsTransformFeedback (GLuint id); +GLAPI void APIENTRY glPauseTransformFeedback (void); +GLAPI void APIENTRY glResumeTransformFeedback (void); +GLAPI void APIENTRY glDrawTransformFeedback (GLenum mode, GLuint id); +GLAPI void APIENTRY glDrawTransformFeedbackStream (GLenum mode, GLuint id, GLuint stream); +GLAPI void APIENTRY glBeginQueryIndexed (GLenum target, GLuint index, GLuint id); +GLAPI void APIENTRY glEndQueryIndexed (GLenum target, GLuint index); +GLAPI void APIENTRY glGetQueryIndexediv (GLenum target, GLuint index, GLenum pname, GLint *params); +#endif +#endif /* GL_VERSION_4_0 */ + +#ifndef GL_VERSION_4_1 +#define GL_VERSION_4_1 1 +#define GL_FIXED 0x140C +#define GL_IMPLEMENTATION_COLOR_READ_TYPE 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B +#define GL_LOW_FLOAT 0x8DF0 +#define GL_MEDIUM_FLOAT 0x8DF1 +#define GL_HIGH_FLOAT 0x8DF2 +#define GL_LOW_INT 0x8DF3 +#define GL_MEDIUM_INT 0x8DF4 +#define GL_HIGH_INT 0x8DF5 +#define GL_SHADER_COMPILER 0x8DFA +#define GL_SHADER_BINARY_FORMATS 0x8DF8 +#define GL_NUM_SHADER_BINARY_FORMATS 0x8DF9 +#define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB +#define GL_MAX_VARYING_VECTORS 0x8DFC +#define GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD +#define GL_RGB565 0x8D62 +#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257 +#define GL_PROGRAM_BINARY_LENGTH 0x8741 +#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE +#define GL_PROGRAM_BINARY_FORMATS 0x87FF +#define GL_VERTEX_SHADER_BIT 0x00000001 +#define GL_FRAGMENT_SHADER_BIT 0x00000002 +#define GL_GEOMETRY_SHADER_BIT 0x00000004 +#define GL_TESS_CONTROL_SHADER_BIT 0x00000008 +#define GL_TESS_EVALUATION_SHADER_BIT 0x00000010 +#define GL_ALL_SHADER_BITS 0xFFFFFFFF +#define GL_PROGRAM_SEPARABLE 0x8258 +#define GL_ACTIVE_PROGRAM 0x8259 +#define GL_PROGRAM_PIPELINE_BINDING 0x825A +#define GL_MAX_VIEWPORTS 0x825B +#define GL_VIEWPORT_SUBPIXEL_BITS 0x825C +#define GL_VIEWPORT_BOUNDS_RANGE 0x825D +#define GL_LAYER_PROVOKING_VERTEX 0x825E +#define GL_VIEWPORT_INDEX_PROVOKING_VERTEX 0x825F +#define GL_UNDEFINED_VERTEX 0x8260 +typedef void (APIENTRYP PFNGLRELEASESHADERCOMPILERPROC) (void); +typedef void (APIENTRYP PFNGLSHADERBINARYPROC) (GLsizei count, const GLuint *shaders, GLenum binaryformat, const void *binary, GLsizei length); +typedef void (APIENTRYP PFNGLGETSHADERPRECISIONFORMATPROC) (GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); +typedef void (APIENTRYP PFNGLDEPTHRANGEFPROC) (GLfloat n, GLfloat f); +typedef void (APIENTRYP PFNGLCLEARDEPTHFPROC) (GLfloat d); +typedef void (APIENTRYP PFNGLGETPROGRAMBINARYPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary); +typedef void (APIENTRYP PFNGLPROGRAMBINARYPROC) (GLuint program, GLenum binaryFormat, const void *binary, GLsizei length); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIPROC) (GLuint program, GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLUSEPROGRAMSTAGESPROC) (GLuint pipeline, GLbitfield stages, GLuint program); +typedef void (APIENTRYP PFNGLACTIVESHADERPROGRAMPROC) (GLuint pipeline, GLuint program); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROGRAMVPROC) (GLenum type, GLsizei count, const GLchar *const*strings); +typedef void (APIENTRYP PFNGLBINDPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPIPELINESPROC) (GLsizei n, const GLuint *pipelines); +typedef void (APIENTRYP PFNGLGENPROGRAMPIPELINESPROC) (GLsizei n, GLuint *pipelines); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLGETPROGRAMPIPELINEIVPROC) (GLuint pipeline, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IPROC) (GLuint program, GLint location, GLint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FPROC) (GLuint program, GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DPROC) (GLuint program, GLint location, GLdouble v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIPROC) (GLuint program, GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IPROC) (GLuint program, GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLGETPROGRAMPIPELINEINFOLOGPROC) (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLPOINTERPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLDVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLVIEWPORTARRAYVPROC) (GLuint first, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVIEWPORTINDEXEDFPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); +typedef void (APIENTRYP PFNGLVIEWPORTINDEXEDFVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLSCISSORARRAYVPROC) (GLuint first, GLsizei count, const GLint *v); +typedef void (APIENTRYP PFNGLSCISSORINDEXEDPROC) (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLSCISSORINDEXEDVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLDEPTHRANGEARRAYVPROC) (GLuint first, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLDEPTHRANGEINDEXEDPROC) (GLuint index, GLdouble n, GLdouble f); +typedef void (APIENTRYP PFNGLGETFLOATI_VPROC) (GLenum target, GLuint index, GLfloat *data); +typedef void (APIENTRYP PFNGLGETDOUBLEI_VPROC) (GLenum target, GLuint index, GLdouble *data); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReleaseShaderCompiler (void); +GLAPI void APIENTRY glShaderBinary (GLsizei count, const GLuint *shaders, GLenum binaryformat, const void *binary, GLsizei length); +GLAPI void APIENTRY glGetShaderPrecisionFormat (GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); +GLAPI void APIENTRY glDepthRangef (GLfloat n, GLfloat f); +GLAPI void APIENTRY glClearDepthf (GLfloat d); +GLAPI void APIENTRY glGetProgramBinary (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary); +GLAPI void APIENTRY glProgramBinary (GLuint program, GLenum binaryFormat, const void *binary, GLsizei length); +GLAPI void APIENTRY glProgramParameteri (GLuint program, GLenum pname, GLint value); +GLAPI void APIENTRY glUseProgramStages (GLuint pipeline, GLbitfield stages, GLuint program); +GLAPI void APIENTRY glActiveShaderProgram (GLuint pipeline, GLuint program); +GLAPI GLuint APIENTRY glCreateShaderProgramv (GLenum type, GLsizei count, const GLchar *const*strings); +GLAPI void APIENTRY glBindProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glDeleteProgramPipelines (GLsizei n, const GLuint *pipelines); +GLAPI void APIENTRY glGenProgramPipelines (GLsizei n, GLuint *pipelines); +GLAPI GLboolean APIENTRY glIsProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glGetProgramPipelineiv (GLuint pipeline, GLenum pname, GLint *params); +GLAPI void APIENTRY glProgramUniform1i (GLuint program, GLint location, GLint v0); +GLAPI void APIENTRY glProgramUniform1iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform1f (GLuint program, GLint location, GLfloat v0); +GLAPI void APIENTRY glProgramUniform1fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform1d (GLuint program, GLint location, GLdouble v0); +GLAPI void APIENTRY glProgramUniform1dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform1ui (GLuint program, GLint location, GLuint v0); +GLAPI void APIENTRY glProgramUniform1uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform2i (GLuint program, GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glProgramUniform2iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform2f (GLuint program, GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glProgramUniform2fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform2d (GLuint program, GLint location, GLdouble v0, GLdouble v1); +GLAPI void APIENTRY glProgramUniform2dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform2ui (GLuint program, GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glProgramUniform2uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform3i (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glProgramUniform3iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform3f (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glProgramUniform3fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform3d (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2); +GLAPI void APIENTRY glProgramUniform3dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform3ui (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glProgramUniform3uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform4i (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glProgramUniform4iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform4f (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glProgramUniform4fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform4d (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); +GLAPI void APIENTRY glProgramUniform4dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform4ui (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glProgramUniform4uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniformMatrix2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glValidateProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glGetProgramPipelineInfoLog (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glVertexAttribL1d (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttribL2d (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttribL3d (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttribL4d (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttribL1dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL2dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL3dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL4dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribLPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glGetVertexAttribLdv (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glViewportArrayv (GLuint first, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glViewportIndexedf (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); +GLAPI void APIENTRY glViewportIndexedfv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glScissorArrayv (GLuint first, GLsizei count, const GLint *v); +GLAPI void APIENTRY glScissorIndexed (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); +GLAPI void APIENTRY glScissorIndexedv (GLuint index, const GLint *v); +GLAPI void APIENTRY glDepthRangeArrayv (GLuint first, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glDepthRangeIndexed (GLuint index, GLdouble n, GLdouble f); +GLAPI void APIENTRY glGetFloati_v (GLenum target, GLuint index, GLfloat *data); +GLAPI void APIENTRY glGetDoublei_v (GLenum target, GLuint index, GLdouble *data); +#endif +#endif /* GL_VERSION_4_1 */ + +#ifndef GL_VERSION_4_2 +#define GL_VERSION_4_2 1 +#define GL_UNPACK_COMPRESSED_BLOCK_WIDTH 0x9127 +#define GL_UNPACK_COMPRESSED_BLOCK_HEIGHT 0x9128 +#define GL_UNPACK_COMPRESSED_BLOCK_DEPTH 0x9129 +#define GL_UNPACK_COMPRESSED_BLOCK_SIZE 0x912A +#define GL_PACK_COMPRESSED_BLOCK_WIDTH 0x912B +#define GL_PACK_COMPRESSED_BLOCK_HEIGHT 0x912C +#define GL_PACK_COMPRESSED_BLOCK_DEPTH 0x912D +#define GL_PACK_COMPRESSED_BLOCK_SIZE 0x912E +#define GL_NUM_SAMPLE_COUNTS 0x9380 +#define GL_MIN_MAP_BUFFER_ALIGNMENT 0x90BC +#define GL_ATOMIC_COUNTER_BUFFER 0x92C0 +#define GL_ATOMIC_COUNTER_BUFFER_BINDING 0x92C1 +#define GL_ATOMIC_COUNTER_BUFFER_START 0x92C2 +#define GL_ATOMIC_COUNTER_BUFFER_SIZE 0x92C3 +#define GL_ATOMIC_COUNTER_BUFFER_DATA_SIZE 0x92C4 +#define GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTERS 0x92C5 +#define GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTER_INDICES 0x92C6 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_VERTEX_SHADER 0x92C7 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_CONTROL_SHADER 0x92C8 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_EVALUATION_SHADER 0x92C9 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_GEOMETRY_SHADER 0x92CA +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_FRAGMENT_SHADER 0x92CB +#define GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS 0x92CC +#define GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS 0x92CD +#define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS 0x92CE +#define GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS 0x92CF +#define GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS 0x92D0 +#define GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS 0x92D1 +#define GL_MAX_VERTEX_ATOMIC_COUNTERS 0x92D2 +#define GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS 0x92D3 +#define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS 0x92D4 +#define GL_MAX_GEOMETRY_ATOMIC_COUNTERS 0x92D5 +#define GL_MAX_FRAGMENT_ATOMIC_COUNTERS 0x92D6 +#define GL_MAX_COMBINED_ATOMIC_COUNTERS 0x92D7 +#define GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE 0x92D8 +#define GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS 0x92DC +#define GL_ACTIVE_ATOMIC_COUNTER_BUFFERS 0x92D9 +#define GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX 0x92DA +#define GL_UNSIGNED_INT_ATOMIC_COUNTER 0x92DB +#define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT 0x00000001 +#define GL_ELEMENT_ARRAY_BARRIER_BIT 0x00000002 +#define GL_UNIFORM_BARRIER_BIT 0x00000004 +#define GL_TEXTURE_FETCH_BARRIER_BIT 0x00000008 +#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020 +#define GL_COMMAND_BARRIER_BIT 0x00000040 +#define GL_PIXEL_BUFFER_BARRIER_BIT 0x00000080 +#define GL_TEXTURE_UPDATE_BARRIER_BIT 0x00000100 +#define GL_BUFFER_UPDATE_BARRIER_BIT 0x00000200 +#define GL_FRAMEBUFFER_BARRIER_BIT 0x00000400 +#define GL_TRANSFORM_FEEDBACK_BARRIER_BIT 0x00000800 +#define GL_ATOMIC_COUNTER_BARRIER_BIT 0x00001000 +#define GL_ALL_BARRIER_BITS 0xFFFFFFFF +#define GL_MAX_IMAGE_UNITS 0x8F38 +#define GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS 0x8F39 +#define GL_IMAGE_BINDING_NAME 0x8F3A +#define GL_IMAGE_BINDING_LEVEL 0x8F3B +#define GL_IMAGE_BINDING_LAYERED 0x8F3C +#define GL_IMAGE_BINDING_LAYER 0x8F3D +#define GL_IMAGE_BINDING_ACCESS 0x8F3E +#define GL_IMAGE_1D 0x904C +#define GL_IMAGE_2D 0x904D +#define GL_IMAGE_3D 0x904E +#define GL_IMAGE_2D_RECT 0x904F +#define GL_IMAGE_CUBE 0x9050 +#define GL_IMAGE_BUFFER 0x9051 +#define GL_IMAGE_1D_ARRAY 0x9052 +#define GL_IMAGE_2D_ARRAY 0x9053 +#define GL_IMAGE_CUBE_MAP_ARRAY 0x9054 +#define GL_IMAGE_2D_MULTISAMPLE 0x9055 +#define GL_IMAGE_2D_MULTISAMPLE_ARRAY 0x9056 +#define GL_INT_IMAGE_1D 0x9057 +#define GL_INT_IMAGE_2D 0x9058 +#define GL_INT_IMAGE_3D 0x9059 +#define GL_INT_IMAGE_2D_RECT 0x905A +#define GL_INT_IMAGE_CUBE 0x905B +#define GL_INT_IMAGE_BUFFER 0x905C +#define GL_INT_IMAGE_1D_ARRAY 0x905D +#define GL_INT_IMAGE_2D_ARRAY 0x905E +#define GL_INT_IMAGE_CUBE_MAP_ARRAY 0x905F +#define GL_INT_IMAGE_2D_MULTISAMPLE 0x9060 +#define GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY 0x9061 +#define GL_UNSIGNED_INT_IMAGE_1D 0x9062 +#define GL_UNSIGNED_INT_IMAGE_2D 0x9063 +#define GL_UNSIGNED_INT_IMAGE_3D 0x9064 +#define GL_UNSIGNED_INT_IMAGE_2D_RECT 0x9065 +#define GL_UNSIGNED_INT_IMAGE_CUBE 0x9066 +#define GL_UNSIGNED_INT_IMAGE_BUFFER 0x9067 +#define GL_UNSIGNED_INT_IMAGE_1D_ARRAY 0x9068 +#define GL_UNSIGNED_INT_IMAGE_2D_ARRAY 0x9069 +#define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY 0x906A +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE 0x906B +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY 0x906C +#define GL_MAX_IMAGE_SAMPLES 0x906D +#define GL_IMAGE_BINDING_FORMAT 0x906E +#define GL_IMAGE_FORMAT_COMPATIBILITY_TYPE 0x90C7 +#define GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE 0x90C8 +#define GL_IMAGE_FORMAT_COMPATIBILITY_BY_CLASS 0x90C9 +#define GL_MAX_VERTEX_IMAGE_UNIFORMS 0x90CA +#define GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS 0x90CB +#define GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS 0x90CC +#define GL_MAX_GEOMETRY_IMAGE_UNIFORMS 0x90CD +#define GL_MAX_FRAGMENT_IMAGE_UNIFORMS 0x90CE +#define GL_MAX_COMBINED_IMAGE_UNIFORMS 0x90CF +#define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM 0x8E8D +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT 0x8E8F +#define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDBASEINSTANCEPROC) (GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEINSTANCEPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLuint baseinstance); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance); +typedef void (APIENTRYP PFNGLGETINTERNALFORMATIVPROC) (GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint *params); +typedef void (APIENTRYP PFNGLGETACTIVEATOMICCOUNTERBUFFERIVPROC) (GLuint program, GLuint bufferIndex, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREPROC) (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format); +typedef void (APIENTRYP PFNGLMEMORYBARRIERPROC) (GLbitfield barriers); +typedef void (APIENTRYP PFNGLTEXSTORAGE1DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +typedef void (APIENTRYP PFNGLTEXSTORAGE2DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXSTORAGE3DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKINSTANCEDPROC) (GLenum mode, GLuint id, GLsizei instancecount); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKSTREAMINSTANCEDPROC) (GLenum mode, GLuint id, GLuint stream, GLsizei instancecount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstancedBaseInstance (GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance); +GLAPI void APIENTRY glDrawElementsInstancedBaseInstance (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLuint baseinstance); +GLAPI void APIENTRY glDrawElementsInstancedBaseVertexBaseInstance (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance); +GLAPI void APIENTRY glGetInternalformativ (GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint *params); +GLAPI void APIENTRY glGetActiveAtomicCounterBufferiv (GLuint program, GLuint bufferIndex, GLenum pname, GLint *params); +GLAPI void APIENTRY glBindImageTexture (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format); +GLAPI void APIENTRY glMemoryBarrier (GLbitfield barriers); +GLAPI void APIENTRY glTexStorage1D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +GLAPI void APIENTRY glTexStorage2D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTexStorage3D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glDrawTransformFeedbackInstanced (GLenum mode, GLuint id, GLsizei instancecount); +GLAPI void APIENTRY glDrawTransformFeedbackStreamInstanced (GLenum mode, GLuint id, GLuint stream, GLsizei instancecount); +#endif +#endif /* GL_VERSION_4_2 */ + +#ifndef GL_VERSION_4_3 +#define GL_VERSION_4_3 1 +typedef void (APIENTRY *GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +#define GL_NUM_SHADING_LANGUAGE_VERSIONS 0x82E9 +#define GL_VERTEX_ATTRIB_ARRAY_LONG 0x874E +#define GL_COMPRESSED_RGB8_ETC2 0x9274 +#define GL_COMPRESSED_SRGB8_ETC2 0x9275 +#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 +#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9277 +#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279 +#define GL_COMPRESSED_R11_EAC 0x9270 +#define GL_COMPRESSED_SIGNED_R11_EAC 0x9271 +#define GL_COMPRESSED_RG11_EAC 0x9272 +#define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 +#define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69 +#define GL_ANY_SAMPLES_PASSED_CONSERVATIVE 0x8D6A +#define GL_MAX_ELEMENT_INDEX 0x8D6B +#define GL_COMPUTE_SHADER 0x91B9 +#define GL_MAX_COMPUTE_UNIFORM_BLOCKS 0x91BB +#define GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS 0x91BC +#define GL_MAX_COMPUTE_IMAGE_UNIFORMS 0x91BD +#define GL_MAX_COMPUTE_SHARED_MEMORY_SIZE 0x8262 +#define GL_MAX_COMPUTE_UNIFORM_COMPONENTS 0x8263 +#define GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS 0x8264 +#define GL_MAX_COMPUTE_ATOMIC_COUNTERS 0x8265 +#define GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS 0x8266 +#define GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS 0x90EB +#define GL_MAX_COMPUTE_WORK_GROUP_COUNT 0x91BE +#define GL_MAX_COMPUTE_WORK_GROUP_SIZE 0x91BF +#define GL_COMPUTE_WORK_GROUP_SIZE 0x8267 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_COMPUTE_SHADER 0x90EC +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_COMPUTE_SHADER 0x90ED +#define GL_DISPATCH_INDIRECT_BUFFER 0x90EE +#define GL_DISPATCH_INDIRECT_BUFFER_BINDING 0x90EF +#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 +#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH 0x8243 +#define GL_DEBUG_CALLBACK_FUNCTION 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM 0x8245 +#define GL_DEBUG_SOURCE_API 0x8246 +#define GL_DEBUG_SOURCE_WINDOW_SYSTEM 0x8247 +#define GL_DEBUG_SOURCE_SHADER_COMPILER 0x8248 +#define GL_DEBUG_SOURCE_THIRD_PARTY 0x8249 +#define GL_DEBUG_SOURCE_APPLICATION 0x824A +#define GL_DEBUG_SOURCE_OTHER 0x824B +#define GL_DEBUG_TYPE_ERROR 0x824C +#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR 0x824D +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E +#define GL_DEBUG_TYPE_PORTABILITY 0x824F +#define GL_DEBUG_TYPE_PERFORMANCE 0x8250 +#define GL_DEBUG_TYPE_OTHER 0x8251 +#define GL_MAX_DEBUG_MESSAGE_LENGTH 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES 0x9145 +#define GL_DEBUG_SEVERITY_HIGH 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM 0x9147 +#define GL_DEBUG_SEVERITY_LOW 0x9148 +#define GL_DEBUG_TYPE_MARKER 0x8268 +#define GL_DEBUG_TYPE_PUSH_GROUP 0x8269 +#define GL_DEBUG_TYPE_POP_GROUP 0x826A +#define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B +#define GL_MAX_DEBUG_GROUP_STACK_DEPTH 0x826C +#define GL_DEBUG_GROUP_STACK_DEPTH 0x826D +#define GL_BUFFER 0x82E0 +#define GL_SHADER 0x82E1 +#define GL_PROGRAM 0x82E2 +#define GL_QUERY 0x82E3 +#define GL_PROGRAM_PIPELINE 0x82E4 +#define GL_SAMPLER 0x82E6 +#define GL_MAX_LABEL_LENGTH 0x82E8 +#define GL_DEBUG_OUTPUT 0x92E0 +#define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002 +#define GL_MAX_UNIFORM_LOCATIONS 0x826E +#define GL_FRAMEBUFFER_DEFAULT_WIDTH 0x9310 +#define GL_FRAMEBUFFER_DEFAULT_HEIGHT 0x9311 +#define GL_FRAMEBUFFER_DEFAULT_LAYERS 0x9312 +#define GL_FRAMEBUFFER_DEFAULT_SAMPLES 0x9313 +#define GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS 0x9314 +#define GL_MAX_FRAMEBUFFER_WIDTH 0x9315 +#define GL_MAX_FRAMEBUFFER_HEIGHT 0x9316 +#define GL_MAX_FRAMEBUFFER_LAYERS 0x9317 +#define GL_MAX_FRAMEBUFFER_SAMPLES 0x9318 +#define GL_INTERNALFORMAT_SUPPORTED 0x826F +#define GL_INTERNALFORMAT_PREFERRED 0x8270 +#define GL_INTERNALFORMAT_RED_SIZE 0x8271 +#define GL_INTERNALFORMAT_GREEN_SIZE 0x8272 +#define GL_INTERNALFORMAT_BLUE_SIZE 0x8273 +#define GL_INTERNALFORMAT_ALPHA_SIZE 0x8274 +#define GL_INTERNALFORMAT_DEPTH_SIZE 0x8275 +#define GL_INTERNALFORMAT_STENCIL_SIZE 0x8276 +#define GL_INTERNALFORMAT_SHARED_SIZE 0x8277 +#define GL_INTERNALFORMAT_RED_TYPE 0x8278 +#define GL_INTERNALFORMAT_GREEN_TYPE 0x8279 +#define GL_INTERNALFORMAT_BLUE_TYPE 0x827A +#define GL_INTERNALFORMAT_ALPHA_TYPE 0x827B +#define GL_INTERNALFORMAT_DEPTH_TYPE 0x827C +#define GL_INTERNALFORMAT_STENCIL_TYPE 0x827D +#define GL_MAX_WIDTH 0x827E +#define GL_MAX_HEIGHT 0x827F +#define GL_MAX_DEPTH 0x8280 +#define GL_MAX_LAYERS 0x8281 +#define GL_MAX_COMBINED_DIMENSIONS 0x8282 +#define GL_COLOR_COMPONENTS 0x8283 +#define GL_DEPTH_COMPONENTS 0x8284 +#define GL_STENCIL_COMPONENTS 0x8285 +#define GL_COLOR_RENDERABLE 0x8286 +#define GL_DEPTH_RENDERABLE 0x8287 +#define GL_STENCIL_RENDERABLE 0x8288 +#define GL_FRAMEBUFFER_RENDERABLE 0x8289 +#define GL_FRAMEBUFFER_RENDERABLE_LAYERED 0x828A +#define GL_FRAMEBUFFER_BLEND 0x828B +#define GL_READ_PIXELS 0x828C +#define GL_READ_PIXELS_FORMAT 0x828D +#define GL_READ_PIXELS_TYPE 0x828E +#define GL_TEXTURE_IMAGE_FORMAT 0x828F +#define GL_TEXTURE_IMAGE_TYPE 0x8290 +#define GL_GET_TEXTURE_IMAGE_FORMAT 0x8291 +#define GL_GET_TEXTURE_IMAGE_TYPE 0x8292 +#define GL_MIPMAP 0x8293 +#define GL_MANUAL_GENERATE_MIPMAP 0x8294 +#define GL_AUTO_GENERATE_MIPMAP 0x8295 +#define GL_COLOR_ENCODING 0x8296 +#define GL_SRGB_READ 0x8297 +#define GL_SRGB_WRITE 0x8298 +#define GL_FILTER 0x829A +#define GL_VERTEX_TEXTURE 0x829B +#define GL_TESS_CONTROL_TEXTURE 0x829C +#define GL_TESS_EVALUATION_TEXTURE 0x829D +#define GL_GEOMETRY_TEXTURE 0x829E +#define GL_FRAGMENT_TEXTURE 0x829F +#define GL_COMPUTE_TEXTURE 0x82A0 +#define GL_TEXTURE_SHADOW 0x82A1 +#define GL_TEXTURE_GATHER 0x82A2 +#define GL_TEXTURE_GATHER_SHADOW 0x82A3 +#define GL_SHADER_IMAGE_LOAD 0x82A4 +#define GL_SHADER_IMAGE_STORE 0x82A5 +#define GL_SHADER_IMAGE_ATOMIC 0x82A6 +#define GL_IMAGE_TEXEL_SIZE 0x82A7 +#define GL_IMAGE_COMPATIBILITY_CLASS 0x82A8 +#define GL_IMAGE_PIXEL_FORMAT 0x82A9 +#define GL_IMAGE_PIXEL_TYPE 0x82AA +#define GL_SIMULTANEOUS_TEXTURE_AND_DEPTH_TEST 0x82AC +#define GL_SIMULTANEOUS_TEXTURE_AND_STENCIL_TEST 0x82AD +#define GL_SIMULTANEOUS_TEXTURE_AND_DEPTH_WRITE 0x82AE +#define GL_SIMULTANEOUS_TEXTURE_AND_STENCIL_WRITE 0x82AF +#define GL_TEXTURE_COMPRESSED_BLOCK_WIDTH 0x82B1 +#define GL_TEXTURE_COMPRESSED_BLOCK_HEIGHT 0x82B2 +#define GL_TEXTURE_COMPRESSED_BLOCK_SIZE 0x82B3 +#define GL_CLEAR_BUFFER 0x82B4 +#define GL_TEXTURE_VIEW 0x82B5 +#define GL_VIEW_COMPATIBILITY_CLASS 0x82B6 +#define GL_FULL_SUPPORT 0x82B7 +#define GL_CAVEAT_SUPPORT 0x82B8 +#define GL_IMAGE_CLASS_4_X_32 0x82B9 +#define GL_IMAGE_CLASS_2_X_32 0x82BA +#define GL_IMAGE_CLASS_1_X_32 0x82BB +#define GL_IMAGE_CLASS_4_X_16 0x82BC +#define GL_IMAGE_CLASS_2_X_16 0x82BD +#define GL_IMAGE_CLASS_1_X_16 0x82BE +#define GL_IMAGE_CLASS_4_X_8 0x82BF +#define GL_IMAGE_CLASS_2_X_8 0x82C0 +#define GL_IMAGE_CLASS_1_X_8 0x82C1 +#define GL_IMAGE_CLASS_11_11_10 0x82C2 +#define GL_IMAGE_CLASS_10_10_10_2 0x82C3 +#define GL_VIEW_CLASS_128_BITS 0x82C4 +#define GL_VIEW_CLASS_96_BITS 0x82C5 +#define GL_VIEW_CLASS_64_BITS 0x82C6 +#define GL_VIEW_CLASS_48_BITS 0x82C7 +#define GL_VIEW_CLASS_32_BITS 0x82C8 +#define GL_VIEW_CLASS_24_BITS 0x82C9 +#define GL_VIEW_CLASS_16_BITS 0x82CA +#define GL_VIEW_CLASS_8_BITS 0x82CB +#define GL_VIEW_CLASS_S3TC_DXT1_RGB 0x82CC +#define GL_VIEW_CLASS_S3TC_DXT1_RGBA 0x82CD +#define GL_VIEW_CLASS_S3TC_DXT3_RGBA 0x82CE +#define GL_VIEW_CLASS_S3TC_DXT5_RGBA 0x82CF +#define GL_VIEW_CLASS_RGTC1_RED 0x82D0 +#define GL_VIEW_CLASS_RGTC2_RG 0x82D1 +#define GL_VIEW_CLASS_BPTC_UNORM 0x82D2 +#define GL_VIEW_CLASS_BPTC_FLOAT 0x82D3 +#define GL_UNIFORM 0x92E1 +#define GL_UNIFORM_BLOCK 0x92E2 +#define GL_PROGRAM_INPUT 0x92E3 +#define GL_PROGRAM_OUTPUT 0x92E4 +#define GL_BUFFER_VARIABLE 0x92E5 +#define GL_SHADER_STORAGE_BLOCK 0x92E6 +#define GL_VERTEX_SUBROUTINE 0x92E8 +#define GL_TESS_CONTROL_SUBROUTINE 0x92E9 +#define GL_TESS_EVALUATION_SUBROUTINE 0x92EA +#define GL_GEOMETRY_SUBROUTINE 0x92EB +#define GL_FRAGMENT_SUBROUTINE 0x92EC +#define GL_COMPUTE_SUBROUTINE 0x92ED +#define GL_VERTEX_SUBROUTINE_UNIFORM 0x92EE +#define GL_TESS_CONTROL_SUBROUTINE_UNIFORM 0x92EF +#define GL_TESS_EVALUATION_SUBROUTINE_UNIFORM 0x92F0 +#define GL_GEOMETRY_SUBROUTINE_UNIFORM 0x92F1 +#define GL_FRAGMENT_SUBROUTINE_UNIFORM 0x92F2 +#define GL_COMPUTE_SUBROUTINE_UNIFORM 0x92F3 +#define GL_TRANSFORM_FEEDBACK_VARYING 0x92F4 +#define GL_ACTIVE_RESOURCES 0x92F5 +#define GL_MAX_NAME_LENGTH 0x92F6 +#define GL_MAX_NUM_ACTIVE_VARIABLES 0x92F7 +#define GL_MAX_NUM_COMPATIBLE_SUBROUTINES 0x92F8 +#define GL_NAME_LENGTH 0x92F9 +#define GL_TYPE 0x92FA +#define GL_ARRAY_SIZE 0x92FB +#define GL_OFFSET 0x92FC +#define GL_BLOCK_INDEX 0x92FD +#define GL_ARRAY_STRIDE 0x92FE +#define GL_MATRIX_STRIDE 0x92FF +#define GL_IS_ROW_MAJOR 0x9300 +#define GL_ATOMIC_COUNTER_BUFFER_INDEX 0x9301 +#define GL_BUFFER_BINDING 0x9302 +#define GL_BUFFER_DATA_SIZE 0x9303 +#define GL_NUM_ACTIVE_VARIABLES 0x9304 +#define GL_ACTIVE_VARIABLES 0x9305 +#define GL_REFERENCED_BY_VERTEX_SHADER 0x9306 +#define GL_REFERENCED_BY_TESS_CONTROL_SHADER 0x9307 +#define GL_REFERENCED_BY_TESS_EVALUATION_SHADER 0x9308 +#define GL_REFERENCED_BY_GEOMETRY_SHADER 0x9309 +#define GL_REFERENCED_BY_FRAGMENT_SHADER 0x930A +#define GL_REFERENCED_BY_COMPUTE_SHADER 0x930B +#define GL_TOP_LEVEL_ARRAY_SIZE 0x930C +#define GL_TOP_LEVEL_ARRAY_STRIDE 0x930D +#define GL_LOCATION 0x930E +#define GL_LOCATION_INDEX 0x930F +#define GL_IS_PER_PATCH 0x92E7 +#define GL_SHADER_STORAGE_BUFFER 0x90D2 +#define GL_SHADER_STORAGE_BUFFER_BINDING 0x90D3 +#define GL_SHADER_STORAGE_BUFFER_START 0x90D4 +#define GL_SHADER_STORAGE_BUFFER_SIZE 0x90D5 +#define GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS 0x90D6 +#define GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS 0x90D7 +#define GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS 0x90D8 +#define GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS 0x90D9 +#define GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS 0x90DA +#define GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS 0x90DB +#define GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS 0x90DC +#define GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS 0x90DD +#define GL_MAX_SHADER_STORAGE_BLOCK_SIZE 0x90DE +#define GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT 0x90DF +#define GL_SHADER_STORAGE_BARRIER_BIT 0x00002000 +#define GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES 0x8F39 +#define GL_DEPTH_STENCIL_TEXTURE_MODE 0x90EA +#define GL_TEXTURE_BUFFER_OFFSET 0x919D +#define GL_TEXTURE_BUFFER_SIZE 0x919E +#define GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT 0x919F +#define GL_TEXTURE_VIEW_MIN_LEVEL 0x82DB +#define GL_TEXTURE_VIEW_NUM_LEVELS 0x82DC +#define GL_TEXTURE_VIEW_MIN_LAYER 0x82DD +#define GL_TEXTURE_VIEW_NUM_LAYERS 0x82DE +#define GL_TEXTURE_IMMUTABLE_LEVELS 0x82DF +#define GL_VERTEX_ATTRIB_BINDING 0x82D4 +#define GL_VERTEX_ATTRIB_RELATIVE_OFFSET 0x82D5 +#define GL_VERTEX_BINDING_DIVISOR 0x82D6 +#define GL_VERTEX_BINDING_OFFSET 0x82D7 +#define GL_VERTEX_BINDING_STRIDE 0x82D8 +#define GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET 0x82D9 +#define GL_MAX_VERTEX_ATTRIB_BINDINGS 0x82DA +#define GL_DISPLAY_LIST 0x82E7 +typedef void (APIENTRYP PFNGLCLEARBUFFERDATAPROC) (GLenum target, GLenum internalformat, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCLEARBUFFERSUBDATAPROC) (GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLDISPATCHCOMPUTEPROC) (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z); +typedef void (APIENTRYP PFNGLDISPATCHCOMPUTEINDIRECTPROC) (GLintptr indirect); +typedef void (APIENTRYP PFNGLCOPYIMAGESUBDATAPROC) (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +typedef void (APIENTRYP PFNGLFRAMEBUFFERPARAMETERIPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETINTERNALFORMATI64VPROC) (GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint64 *params); +typedef void (APIENTRYP PFNGLINVALIDATETEXSUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLINVALIDATETEXIMAGEPROC) (GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLINVALIDATEBUFFERSUBDATAPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length); +typedef void (APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLINVALIDATEFRAMEBUFFERPROC) (GLenum target, GLsizei numAttachments, const GLenum *attachments); +typedef void (APIENTRYP PFNGLINVALIDATESUBFRAMEBUFFERPROC) (GLenum target, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTPROC) (GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLGETPROGRAMINTERFACEIVPROC) (GLuint program, GLenum programInterface, GLenum pname, GLint *params); +typedef GLuint (APIENTRYP PFNGLGETPROGRAMRESOURCEINDEXPROC) (GLuint program, GLenum programInterface, const GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMRESOURCENAMEPROC) (GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMRESOURCEIVPROC) (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei bufSize, GLsizei *length, GLint *params); +typedef GLint (APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONPROC) (GLuint program, GLenum programInterface, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONINDEXPROC) (GLuint program, GLenum programInterface, const GLchar *name); +typedef void (APIENTRYP PFNGLSHADERSTORAGEBLOCKBINDINGPROC) (GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding); +typedef void (APIENTRYP PFNGLTEXBUFFERRANGEPROC) (GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLTEXSTORAGE2DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXSTORAGE3DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXTUREVIEWPROC) (GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers); +typedef void (APIENTRYP PFNGLBINDVERTEXBUFFERPROC) (GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXATTRIBFORMATPROC) (GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIFORMATPROC) (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLFORMATPROC) (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXATTRIBBINDINGPROC) (GLuint attribindex, GLuint bindingindex); +typedef void (APIENTRYP PFNGLVERTEXBINDINGDIVISORPROC) (GLuint bindingindex, GLuint divisor); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECONTROLPROC) (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTPROC) (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKPROC) (GLDEBUGPROC callback, const void *userParam); +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGPROC) (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +typedef void (APIENTRYP PFNGLPUSHDEBUGGROUPPROC) (GLenum source, GLuint id, GLsizei length, const GLchar *message); +typedef void (APIENTRYP PFNGLPOPDEBUGGROUPPROC) (void); +typedef void (APIENTRYP PFNGLOBJECTLABELPROC) (GLenum identifier, GLuint name, GLsizei length, const GLchar *label); +typedef void (APIENTRYP PFNGLGETOBJECTLABELPROC) (GLenum identifier, GLuint name, GLsizei bufSize, GLsizei *length, GLchar *label); +typedef void (APIENTRYP PFNGLOBJECTPTRLABELPROC) (const void *ptr, GLsizei length, const GLchar *label); +typedef void (APIENTRYP PFNGLGETOBJECTPTRLABELPROC) (const void *ptr, GLsizei bufSize, GLsizei *length, GLchar *label); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClearBufferData (GLenum target, GLenum internalformat, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glClearBufferSubData (GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glDispatchCompute (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z); +GLAPI void APIENTRY glDispatchComputeIndirect (GLintptr indirect); +GLAPI void APIENTRY glCopyImageSubData (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +GLAPI void APIENTRY glFramebufferParameteri (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glGetFramebufferParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetInternalformati64v (GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint64 *params); +GLAPI void APIENTRY glInvalidateTexSubImage (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glInvalidateTexImage (GLuint texture, GLint level); +GLAPI void APIENTRY glInvalidateBufferSubData (GLuint buffer, GLintptr offset, GLsizeiptr length); +GLAPI void APIENTRY glInvalidateBufferData (GLuint buffer); +GLAPI void APIENTRY glInvalidateFramebuffer (GLenum target, GLsizei numAttachments, const GLenum *attachments); +GLAPI void APIENTRY glInvalidateSubFramebuffer (GLenum target, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glMultiDrawArraysIndirect (GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawElementsIndirect (GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride); +GLAPI void APIENTRY glGetProgramInterfaceiv (GLuint program, GLenum programInterface, GLenum pname, GLint *params); +GLAPI GLuint APIENTRY glGetProgramResourceIndex (GLuint program, GLenum programInterface, const GLchar *name); +GLAPI void APIENTRY glGetProgramResourceName (GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +GLAPI void APIENTRY glGetProgramResourceiv (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei bufSize, GLsizei *length, GLint *params); +GLAPI GLint APIENTRY glGetProgramResourceLocation (GLuint program, GLenum programInterface, const GLchar *name); +GLAPI GLint APIENTRY glGetProgramResourceLocationIndex (GLuint program, GLenum programInterface, const GLchar *name); +GLAPI void APIENTRY glShaderStorageBlockBinding (GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding); +GLAPI void APIENTRY glTexBufferRange (GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glTexStorage2DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTexStorage3DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTextureView (GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers); +GLAPI void APIENTRY glBindVertexBuffer (GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +GLAPI void APIENTRY glVertexAttribFormat (GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +GLAPI void APIENTRY glVertexAttribIFormat (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexAttribLFormat (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexAttribBinding (GLuint attribindex, GLuint bindingindex); +GLAPI void APIENTRY glVertexBindingDivisor (GLuint bindingindex, GLuint divisor); +GLAPI void APIENTRY glDebugMessageControl (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI void APIENTRY glDebugMessageInsert (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +GLAPI void APIENTRY glDebugMessageCallback (GLDEBUGPROC callback, const void *userParam); +GLAPI GLuint APIENTRY glGetDebugMessageLog (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +GLAPI void APIENTRY glPushDebugGroup (GLenum source, GLuint id, GLsizei length, const GLchar *message); +GLAPI void APIENTRY glPopDebugGroup (void); +GLAPI void APIENTRY glObjectLabel (GLenum identifier, GLuint name, GLsizei length, const GLchar *label); +GLAPI void APIENTRY glGetObjectLabel (GLenum identifier, GLuint name, GLsizei bufSize, GLsizei *length, GLchar *label); +GLAPI void APIENTRY glObjectPtrLabel (const void *ptr, GLsizei length, const GLchar *label); +GLAPI void APIENTRY glGetObjectPtrLabel (const void *ptr, GLsizei bufSize, GLsizei *length, GLchar *label); +#endif +#endif /* GL_VERSION_4_3 */ + +#ifndef GL_VERSION_4_4 +#define GL_VERSION_4_4 1 +#define GL_MAX_VERTEX_ATTRIB_STRIDE 0x82E5 +#define GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED 0x8221 +#define GL_TEXTURE_BUFFER_BINDING 0x8C2A +#define GL_MAP_PERSISTENT_BIT 0x0040 +#define GL_MAP_COHERENT_BIT 0x0080 +#define GL_DYNAMIC_STORAGE_BIT 0x0100 +#define GL_CLIENT_STORAGE_BIT 0x0200 +#define GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT 0x00004000 +#define GL_BUFFER_IMMUTABLE_STORAGE 0x821F +#define GL_BUFFER_STORAGE_FLAGS 0x8220 +#define GL_CLEAR_TEXTURE 0x9365 +#define GL_LOCATION_COMPONENT 0x934A +#define GL_TRANSFORM_FEEDBACK_BUFFER_INDEX 0x934B +#define GL_TRANSFORM_FEEDBACK_BUFFER_STRIDE 0x934C +#define GL_QUERY_BUFFER 0x9192 +#define GL_QUERY_BUFFER_BARRIER_BIT 0x00008000 +#define GL_QUERY_BUFFER_BINDING 0x9193 +#define GL_QUERY_RESULT_NO_WAIT 0x9194 +#define GL_MIRROR_CLAMP_TO_EDGE 0x8743 +typedef void (APIENTRYP PFNGLBUFFERSTORAGEPROC) (GLenum target, GLsizeiptr size, const void *data, GLbitfield flags); +typedef void (APIENTRYP PFNGLCLEARTEXIMAGEPROC) (GLuint texture, GLint level, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCLEARTEXSUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLBINDBUFFERSBASEPROC) (GLenum target, GLuint first, GLsizei count, const GLuint *buffers); +typedef void (APIENTRYP PFNGLBINDBUFFERSRANGEPROC) (GLenum target, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizeiptr *sizes); +typedef void (APIENTRYP PFNGLBINDTEXTURESPROC) (GLuint first, GLsizei count, const GLuint *textures); +typedef void (APIENTRYP PFNGLBINDSAMPLERSPROC) (GLuint first, GLsizei count, const GLuint *samplers); +typedef void (APIENTRYP PFNGLBINDIMAGETEXTURESPROC) (GLuint first, GLsizei count, const GLuint *textures); +typedef void (APIENTRYP PFNGLBINDVERTEXBUFFERSPROC) (GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferStorage (GLenum target, GLsizeiptr size, const void *data, GLbitfield flags); +GLAPI void APIENTRY glClearTexImage (GLuint texture, GLint level, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glClearTexSubImage (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glBindBuffersBase (GLenum target, GLuint first, GLsizei count, const GLuint *buffers); +GLAPI void APIENTRY glBindBuffersRange (GLenum target, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizeiptr *sizes); +GLAPI void APIENTRY glBindTextures (GLuint first, GLsizei count, const GLuint *textures); +GLAPI void APIENTRY glBindSamplers (GLuint first, GLsizei count, const GLuint *samplers); +GLAPI void APIENTRY glBindImageTextures (GLuint first, GLsizei count, const GLuint *textures); +GLAPI void APIENTRY glBindVertexBuffers (GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides); +#endif +#endif /* GL_VERSION_4_4 */ + +#ifndef GL_ARB_ES2_compatibility +#define GL_ARB_ES2_compatibility 1 +#endif /* GL_ARB_ES2_compatibility */ + +#ifndef GL_ARB_ES3_compatibility +#define GL_ARB_ES3_compatibility 1 +#endif /* GL_ARB_ES3_compatibility */ + +#ifndef GL_ARB_arrays_of_arrays +#define GL_ARB_arrays_of_arrays 1 +#endif /* GL_ARB_arrays_of_arrays */ + +#ifndef GL_ARB_base_instance +#define GL_ARB_base_instance 1 +#endif /* GL_ARB_base_instance */ + +#ifndef GL_ARB_bindless_texture +#define GL_ARB_bindless_texture 1 +typedef uint64_t GLuint64EXT; +#define GL_UNSIGNED_INT64_ARB 0x140F +typedef GLuint64 (APIENTRYP PFNGLGETTEXTUREHANDLEARBPROC) (GLuint texture); +typedef GLuint64 (APIENTRYP PFNGLGETTEXTURESAMPLERHANDLEARBPROC) (GLuint texture, GLuint sampler); +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLERESIDENTARBPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLENONRESIDENTARBPROC) (GLuint64 handle); +typedef GLuint64 (APIENTRYP PFNGLGETIMAGEHANDLEARBPROC) (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLERESIDENTARBPROC) (GLuint64 handle, GLenum access); +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLENONRESIDENTARBPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64ARBPROC) (GLint location, GLuint64 value); +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64ARBPROC) (GLuint program, GLint location, GLuint64 value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREHANDLERESIDENTARBPROC) (GLuint64 handle); +typedef GLboolean (APIENTRYP PFNGLISIMAGEHANDLERESIDENTARBPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64ARBPROC) (GLuint index, GLuint64EXT x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64VARBPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLUI64VARBPROC) (GLuint index, GLenum pname, GLuint64EXT *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint64 APIENTRY glGetTextureHandleARB (GLuint texture); +GLAPI GLuint64 APIENTRY glGetTextureSamplerHandleARB (GLuint texture, GLuint sampler); +GLAPI void APIENTRY glMakeTextureHandleResidentARB (GLuint64 handle); +GLAPI void APIENTRY glMakeTextureHandleNonResidentARB (GLuint64 handle); +GLAPI GLuint64 APIENTRY glGetImageHandleARB (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +GLAPI void APIENTRY glMakeImageHandleResidentARB (GLuint64 handle, GLenum access); +GLAPI void APIENTRY glMakeImageHandleNonResidentARB (GLuint64 handle); +GLAPI void APIENTRY glUniformHandleui64ARB (GLint location, GLuint64 value); +GLAPI void APIENTRY glUniformHandleui64vARB (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glProgramUniformHandleui64ARB (GLuint program, GLint location, GLuint64 value); +GLAPI void APIENTRY glProgramUniformHandleui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +GLAPI GLboolean APIENTRY glIsTextureHandleResidentARB (GLuint64 handle); +GLAPI GLboolean APIENTRY glIsImageHandleResidentARB (GLuint64 handle); +GLAPI void APIENTRY glVertexAttribL1ui64ARB (GLuint index, GLuint64EXT x); +GLAPI void APIENTRY glVertexAttribL1ui64vARB (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glGetVertexAttribLui64vARB (GLuint index, GLenum pname, GLuint64EXT *params); +#endif +#endif /* GL_ARB_bindless_texture */ + +#ifndef GL_ARB_blend_func_extended +#define GL_ARB_blend_func_extended 1 +#endif /* GL_ARB_blend_func_extended */ + +#ifndef GL_ARB_buffer_storage +#define GL_ARB_buffer_storage 1 +#endif /* GL_ARB_buffer_storage */ + +#ifndef GL_ARB_cl_event +#define GL_ARB_cl_event 1 +struct _cl_context; +struct _cl_event; +#define GL_SYNC_CL_EVENT_ARB 0x8240 +#define GL_SYNC_CL_EVENT_COMPLETE_ARB 0x8241 +typedef GLsync (APIENTRYP PFNGLCREATESYNCFROMCLEVENTARBPROC) (struct _cl_context *context, struct _cl_event *event, GLbitfield flags); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLsync APIENTRY glCreateSyncFromCLeventARB (struct _cl_context *context, struct _cl_event *event, GLbitfield flags); +#endif +#endif /* GL_ARB_cl_event */ + +#ifndef GL_ARB_clear_buffer_object +#define GL_ARB_clear_buffer_object 1 +#endif /* GL_ARB_clear_buffer_object */ + +#ifndef GL_ARB_clear_texture +#define GL_ARB_clear_texture 1 +#endif /* GL_ARB_clear_texture */ + +#ifndef GL_ARB_color_buffer_float +#define GL_ARB_color_buffer_float 1 +#define GL_RGBA_FLOAT_MODE_ARB 0x8820 +#define GL_CLAMP_VERTEX_COLOR_ARB 0x891A +#define GL_CLAMP_FRAGMENT_COLOR_ARB 0x891B +#define GL_CLAMP_READ_COLOR_ARB 0x891C +#define GL_FIXED_ONLY_ARB 0x891D +typedef void (APIENTRYP PFNGLCLAMPCOLORARBPROC) (GLenum target, GLenum clamp); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClampColorARB (GLenum target, GLenum clamp); +#endif +#endif /* GL_ARB_color_buffer_float */ + +#ifndef GL_ARB_compatibility +#define GL_ARB_compatibility 1 +#endif /* GL_ARB_compatibility */ + +#ifndef GL_ARB_compressed_texture_pixel_storage +#define GL_ARB_compressed_texture_pixel_storage 1 +#endif /* GL_ARB_compressed_texture_pixel_storage */ + +#ifndef GL_ARB_compute_shader +#define GL_ARB_compute_shader 1 +#define GL_COMPUTE_SHADER_BIT 0x00000020 +#endif /* GL_ARB_compute_shader */ + +#ifndef GL_ARB_compute_variable_group_size +#define GL_ARB_compute_variable_group_size 1 +#define GL_MAX_COMPUTE_VARIABLE_GROUP_INVOCATIONS_ARB 0x9344 +#define GL_MAX_COMPUTE_FIXED_GROUP_INVOCATIONS_ARB 0x90EB +#define GL_MAX_COMPUTE_VARIABLE_GROUP_SIZE_ARB 0x9345 +#define GL_MAX_COMPUTE_FIXED_GROUP_SIZE_ARB 0x91BF +typedef void (APIENTRYP PFNGLDISPATCHCOMPUTEGROUPSIZEARBPROC) (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z, GLuint group_size_x, GLuint group_size_y, GLuint group_size_z); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDispatchComputeGroupSizeARB (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z, GLuint group_size_x, GLuint group_size_y, GLuint group_size_z); +#endif +#endif /* GL_ARB_compute_variable_group_size */ + +#ifndef GL_ARB_conservative_depth +#define GL_ARB_conservative_depth 1 +#endif /* GL_ARB_conservative_depth */ + +#ifndef GL_ARB_copy_buffer +#define GL_ARB_copy_buffer 1 +#define GL_COPY_READ_BUFFER_BINDING 0x8F36 +#define GL_COPY_WRITE_BUFFER_BINDING 0x8F37 +#endif /* GL_ARB_copy_buffer */ + +#ifndef GL_ARB_copy_image +#define GL_ARB_copy_image 1 +#endif /* GL_ARB_copy_image */ + +#ifndef GL_ARB_debug_output +#define GL_ARB_debug_output 1 +typedef void (APIENTRY *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +#define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242 +#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243 +#define GL_DEBUG_CALLBACK_FUNCTION_ARB 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM_ARB 0x8245 +#define GL_DEBUG_SOURCE_API_ARB 0x8246 +#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB 0x8247 +#define GL_DEBUG_SOURCE_SHADER_COMPILER_ARB 0x8248 +#define GL_DEBUG_SOURCE_THIRD_PARTY_ARB 0x8249 +#define GL_DEBUG_SOURCE_APPLICATION_ARB 0x824A +#define GL_DEBUG_SOURCE_OTHER_ARB 0x824B +#define GL_DEBUG_TYPE_ERROR_ARB 0x824C +#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB 0x824D +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB 0x824E +#define GL_DEBUG_TYPE_PORTABILITY_ARB 0x824F +#define GL_DEBUG_TYPE_PERFORMANCE_ARB 0x8250 +#define GL_DEBUG_TYPE_OTHER_ARB 0x8251 +#define GL_MAX_DEBUG_MESSAGE_LENGTH_ARB 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES_ARB 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_ARB 0x9145 +#define GL_DEBUG_SEVERITY_HIGH_ARB 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_ARB 0x9147 +#define GL_DEBUG_SEVERITY_LOW_ARB 0x9148 +typedef void (APIENTRYP PFNGLDEBUGMESSAGECONTROLARBPROC) (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTARBPROC) (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKARBPROC) (GLDEBUGPROCARB callback, const void *userParam); +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGARBPROC) (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDebugMessageControlARB (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI void APIENTRY glDebugMessageInsertARB (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +GLAPI void APIENTRY glDebugMessageCallbackARB (GLDEBUGPROCARB callback, const void *userParam); +GLAPI GLuint APIENTRY glGetDebugMessageLogARB (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +#endif +#endif /* GL_ARB_debug_output */ + +#ifndef GL_ARB_depth_buffer_float +#define GL_ARB_depth_buffer_float 1 +#endif /* GL_ARB_depth_buffer_float */ + +#ifndef GL_ARB_depth_clamp +#define GL_ARB_depth_clamp 1 +#endif /* GL_ARB_depth_clamp */ + +#ifndef GL_ARB_depth_texture +#define GL_ARB_depth_texture 1 +#define GL_DEPTH_COMPONENT16_ARB 0x81A5 +#define GL_DEPTH_COMPONENT24_ARB 0x81A6 +#define GL_DEPTH_COMPONENT32_ARB 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#endif /* GL_ARB_depth_texture */ + +#ifndef GL_ARB_draw_buffers +#define GL_ARB_draw_buffers 1 +#define GL_MAX_DRAW_BUFFERS_ARB 0x8824 +#define GL_DRAW_BUFFER0_ARB 0x8825 +#define GL_DRAW_BUFFER1_ARB 0x8826 +#define GL_DRAW_BUFFER2_ARB 0x8827 +#define GL_DRAW_BUFFER3_ARB 0x8828 +#define GL_DRAW_BUFFER4_ARB 0x8829 +#define GL_DRAW_BUFFER5_ARB 0x882A +#define GL_DRAW_BUFFER6_ARB 0x882B +#define GL_DRAW_BUFFER7_ARB 0x882C +#define GL_DRAW_BUFFER8_ARB 0x882D +#define GL_DRAW_BUFFER9_ARB 0x882E +#define GL_DRAW_BUFFER10_ARB 0x882F +#define GL_DRAW_BUFFER11_ARB 0x8830 +#define GL_DRAW_BUFFER12_ARB 0x8831 +#define GL_DRAW_BUFFER13_ARB 0x8832 +#define GL_DRAW_BUFFER14_ARB 0x8833 +#define GL_DRAW_BUFFER15_ARB 0x8834 +typedef void (APIENTRYP PFNGLDRAWBUFFERSARBPROC) (GLsizei n, const GLenum *bufs); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersARB (GLsizei n, const GLenum *bufs); +#endif +#endif /* GL_ARB_draw_buffers */ + +#ifndef GL_ARB_draw_buffers_blend +#define GL_ARB_draw_buffers_blend 1 +typedef void (APIENTRYP PFNGLBLENDEQUATIONIARBPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEIARBPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLBLENDFUNCIARBPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEIARBPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationiARB (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparateiARB (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glBlendFunciARB (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparateiARB (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +#endif +#endif /* GL_ARB_draw_buffers_blend */ + +#ifndef GL_ARB_draw_elements_base_vertex +#define GL_ARB_draw_elements_base_vertex 1 +#endif /* GL_ARB_draw_elements_base_vertex */ + +#ifndef GL_ARB_draw_indirect +#define GL_ARB_draw_indirect 1 +#endif /* GL_ARB_draw_indirect */ + +#ifndef GL_ARB_draw_instanced +#define GL_ARB_draw_instanced 1 +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDARBPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDARBPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstancedARB (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +GLAPI void APIENTRY glDrawElementsInstancedARB (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#endif +#endif /* GL_ARB_draw_instanced */ + +#ifndef GL_ARB_enhanced_layouts +#define GL_ARB_enhanced_layouts 1 +#endif /* GL_ARB_enhanced_layouts */ + +#ifndef GL_ARB_explicit_attrib_location +#define GL_ARB_explicit_attrib_location 1 +#endif /* GL_ARB_explicit_attrib_location */ + +#ifndef GL_ARB_explicit_uniform_location +#define GL_ARB_explicit_uniform_location 1 +#endif /* GL_ARB_explicit_uniform_location */ + +#ifndef GL_ARB_fragment_coord_conventions +#define GL_ARB_fragment_coord_conventions 1 +#endif /* GL_ARB_fragment_coord_conventions */ + +#ifndef GL_ARB_fragment_layer_viewport +#define GL_ARB_fragment_layer_viewport 1 +#endif /* GL_ARB_fragment_layer_viewport */ + +#ifndef GL_ARB_fragment_program +#define GL_ARB_fragment_program 1 +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF +typedef void (APIENTRYP PFNGLPROGRAMSTRINGARBPROC) (GLenum target, GLenum format, GLsizei len, const void *string); +typedef void (APIENTRYP PFNGLBINDPROGRAMARBPROC) (GLenum target, GLuint program); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSARBPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLGENPROGRAMSARBPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGARBPROC) (GLenum target, GLenum pname, void *string); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMARBPROC) (GLuint program); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramStringARB (GLenum target, GLenum format, GLsizei len, const void *string); +GLAPI void APIENTRY glBindProgramARB (GLenum target, GLuint program); +GLAPI void APIENTRY glDeleteProgramsARB (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glGenProgramsARB (GLsizei n, GLuint *programs); +GLAPI void APIENTRY glProgramEnvParameter4dARB (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramEnvParameter4dvARB (GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glProgramEnvParameter4fARB (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramEnvParameter4fvARB (GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glProgramLocalParameter4dARB (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramLocalParameter4dvARB (GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glProgramLocalParameter4fARB (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramLocalParameter4fvARB (GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glGetProgramEnvParameterdvARB (GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetProgramEnvParameterfvARB (GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetProgramLocalParameterdvARB (GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetProgramLocalParameterfvARB (GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetProgramivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramStringARB (GLenum target, GLenum pname, void *string); +GLAPI GLboolean APIENTRY glIsProgramARB (GLuint program); +#endif +#endif /* GL_ARB_fragment_program */ + +#ifndef GL_ARB_fragment_program_shadow +#define GL_ARB_fragment_program_shadow 1 +#endif /* GL_ARB_fragment_program_shadow */ + +#ifndef GL_ARB_fragment_shader +#define GL_ARB_fragment_shader 1 +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB 0x8B8B +#endif /* GL_ARB_fragment_shader */ + +#ifndef GL_ARB_framebuffer_no_attachments +#define GL_ARB_framebuffer_no_attachments 1 +#endif /* GL_ARB_framebuffer_no_attachments */ + +#ifndef GL_ARB_framebuffer_object +#define GL_ARB_framebuffer_object 1 +#endif /* GL_ARB_framebuffer_object */ + +#ifndef GL_ARB_framebuffer_sRGB +#define GL_ARB_framebuffer_sRGB 1 +#endif /* GL_ARB_framebuffer_sRGB */ + +#ifndef GL_ARB_geometry_shader4 +#define GL_ARB_geometry_shader4 1 +#define GL_LINES_ADJACENCY_ARB 0x000A +#define GL_LINE_STRIP_ADJACENCY_ARB 0x000B +#define GL_TRIANGLES_ADJACENCY_ARB 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY_ARB 0x000D +#define GL_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_ARB 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_ARB 0x8DA8 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_ARB 0x8DA9 +#define GL_GEOMETRY_SHADER_ARB 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT_ARB 0x8DDA +#define GL_GEOMETRY_INPUT_TYPE_ARB 0x8DDB +#define GL_GEOMETRY_OUTPUT_TYPE_ARB 0x8DDC +#define GL_MAX_GEOMETRY_VARYING_COMPONENTS_ARB 0x8DDD +#define GL_MAX_VERTEX_VARYING_COMPONENTS_ARB 0x8DDE +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_ARB 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB 0x8DE1 +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIARBPROC) (GLuint program, GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREFACEARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramParameteriARB (GLuint program, GLenum pname, GLint value); +GLAPI void APIENTRY glFramebufferTextureARB (GLenum target, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTextureLayerARB (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glFramebufferTextureFaceARB (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#endif +#endif /* GL_ARB_geometry_shader4 */ + +#ifndef GL_ARB_get_program_binary +#define GL_ARB_get_program_binary 1 +#endif /* GL_ARB_get_program_binary */ + +#ifndef GL_ARB_gpu_shader5 +#define GL_ARB_gpu_shader5 1 +#endif /* GL_ARB_gpu_shader5 */ + +#ifndef GL_ARB_gpu_shader_fp64 +#define GL_ARB_gpu_shader_fp64 1 +#endif /* GL_ARB_gpu_shader_fp64 */ + +#ifndef GL_ARB_half_float_pixel +#define GL_ARB_half_float_pixel 1 +typedef unsigned short GLhalfARB; +#define GL_HALF_FLOAT_ARB 0x140B +#endif /* GL_ARB_half_float_pixel */ + +#ifndef GL_ARB_half_float_vertex +#define GL_ARB_half_float_vertex 1 +#endif /* GL_ARB_half_float_vertex */ + +#ifndef GL_ARB_imaging +#define GL_ARB_imaging 1 +#define GL_BLEND_COLOR 0x8005 +#define GL_BLEND_EQUATION 0x8009 +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS 0x80BB +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_CONSTANT_BORDER 0x8151 +#define GL_REPLICATE_BORDER 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR 0x8154 +typedef void (APIENTRYP PFNGLCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, void *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, void *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTable (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); +GLAPI void APIENTRY glColorTableParameterfv (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glColorTableParameteriv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyColorTable (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glGetColorTable (GLenum target, GLenum format, GLenum type, void *table); +GLAPI void APIENTRY glGetColorTableParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetColorTableParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glColorSubTable (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glCopyColorSubTable (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glConvolutionFilter1D (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); +GLAPI void APIENTRY glConvolutionFilter2D (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); +GLAPI void APIENTRY glConvolutionParameterf (GLenum target, GLenum pname, GLfloat params); +GLAPI void APIENTRY glConvolutionParameterfv (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glConvolutionParameteri (GLenum target, GLenum pname, GLint params); +GLAPI void APIENTRY glConvolutionParameteriv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyConvolutionFilter1D (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyConvolutionFilter2D (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetConvolutionFilter (GLenum target, GLenum format, GLenum type, void *image); +GLAPI void APIENTRY glGetConvolutionParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetConvolutionParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSeparableFilter (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); +GLAPI void APIENTRY glSeparableFilter2D (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); +GLAPI void APIENTRY glGetHistogram (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +GLAPI void APIENTRY glGetHistogramParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetHistogramParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMinmax (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +GLAPI void APIENTRY glGetMinmaxParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMinmaxParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glHistogram (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glMinmax (GLenum target, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glResetHistogram (GLenum target); +GLAPI void APIENTRY glResetMinmax (GLenum target); +#endif +#endif /* GL_ARB_imaging */ + +#ifndef GL_ARB_indirect_parameters +#define GL_ARB_indirect_parameters 1 +#define GL_PARAMETER_BUFFER_ARB 0x80EE +#define GL_PARAMETER_BUFFER_BINDING_ARB 0x80EF +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTCOUNTARBPROC) (GLenum mode, GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTCOUNTARBPROC) (GLenum mode, GLenum type, GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysIndirectCountARB (GLenum mode, GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawElementsIndirectCountARB (GLenum mode, GLenum type, GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +#endif +#endif /* GL_ARB_indirect_parameters */ + +#ifndef GL_ARB_instanced_arrays +#define GL_ARB_instanced_arrays 1 +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ARB 0x88FE +typedef void (APIENTRYP PFNGLVERTEXATTRIBDIVISORARBPROC) (GLuint index, GLuint divisor); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribDivisorARB (GLuint index, GLuint divisor); +#endif +#endif /* GL_ARB_instanced_arrays */ + +#ifndef GL_ARB_internalformat_query +#define GL_ARB_internalformat_query 1 +#endif /* GL_ARB_internalformat_query */ + +#ifndef GL_ARB_internalformat_query2 +#define GL_ARB_internalformat_query2 1 +#define GL_SRGB_DECODE_ARB 0x8299 +#endif /* GL_ARB_internalformat_query2 */ + +#ifndef GL_ARB_invalidate_subdata +#define GL_ARB_invalidate_subdata 1 +#endif /* GL_ARB_invalidate_subdata */ + +#ifndef GL_ARB_map_buffer_alignment +#define GL_ARB_map_buffer_alignment 1 +#endif /* GL_ARB_map_buffer_alignment */ + +#ifndef GL_ARB_map_buffer_range +#define GL_ARB_map_buffer_range 1 +#endif /* GL_ARB_map_buffer_range */ + +#ifndef GL_ARB_matrix_palette +#define GL_ARB_matrix_palette 1 +#define GL_MATRIX_PALETTE_ARB 0x8840 +#define GL_MAX_MATRIX_PALETTE_STACK_DEPTH_ARB 0x8841 +#define GL_MAX_PALETTE_MATRICES_ARB 0x8842 +#define GL_CURRENT_PALETTE_MATRIX_ARB 0x8843 +#define GL_MATRIX_INDEX_ARRAY_ARB 0x8844 +#define GL_CURRENT_MATRIX_INDEX_ARB 0x8845 +#define GL_MATRIX_INDEX_ARRAY_SIZE_ARB 0x8846 +#define GL_MATRIX_INDEX_ARRAY_TYPE_ARB 0x8847 +#define GL_MATRIX_INDEX_ARRAY_STRIDE_ARB 0x8848 +#define GL_MATRIX_INDEX_ARRAY_POINTER_ARB 0x8849 +typedef void (APIENTRYP PFNGLCURRENTPALETTEMATRIXARBPROC) (GLint index); +typedef void (APIENTRYP PFNGLMATRIXINDEXUBVARBPROC) (GLint size, const GLubyte *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUSVARBPROC) (GLint size, const GLushort *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUIVARBPROC) (GLint size, const GLuint *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCurrentPaletteMatrixARB (GLint index); +GLAPI void APIENTRY glMatrixIndexubvARB (GLint size, const GLubyte *indices); +GLAPI void APIENTRY glMatrixIndexusvARB (GLint size, const GLushort *indices); +GLAPI void APIENTRY glMatrixIndexuivARB (GLint size, const GLuint *indices); +GLAPI void APIENTRY glMatrixIndexPointerARB (GLint size, GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_ARB_matrix_palette */ + +#ifndef GL_ARB_multi_bind +#define GL_ARB_multi_bind 1 +#endif /* GL_ARB_multi_bind */ + +#ifndef GL_ARB_multi_draw_indirect +#define GL_ARB_multi_draw_indirect 1 +#endif /* GL_ARB_multi_draw_indirect */ + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEARBPROC) (GLfloat value, GLboolean invert); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleCoverageARB (GLfloat value, GLboolean invert); +#endif +#endif /* GL_ARB_multisample */ + +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +typedef void (APIENTRYP PFNGLACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTextureARB (GLenum texture); +GLAPI void APIENTRY glClientActiveTextureARB (GLenum texture); +GLAPI void APIENTRY glMultiTexCoord1dARB (GLenum target, GLdouble s); +GLAPI void APIENTRY glMultiTexCoord1dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord1fARB (GLenum target, GLfloat s); +GLAPI void APIENTRY glMultiTexCoord1fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord1iARB (GLenum target, GLint s); +GLAPI void APIENTRY glMultiTexCoord1ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord1sARB (GLenum target, GLshort s); +GLAPI void APIENTRY glMultiTexCoord1svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord2dARB (GLenum target, GLdouble s, GLdouble t); +GLAPI void APIENTRY glMultiTexCoord2dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord2fARB (GLenum target, GLfloat s, GLfloat t); +GLAPI void APIENTRY glMultiTexCoord2fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord2iARB (GLenum target, GLint s, GLint t); +GLAPI void APIENTRY glMultiTexCoord2ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord2sARB (GLenum target, GLshort s, GLshort t); +GLAPI void APIENTRY glMultiTexCoord2svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord3dARB (GLenum target, GLdouble s, GLdouble t, GLdouble r); +GLAPI void APIENTRY glMultiTexCoord3dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord3fARB (GLenum target, GLfloat s, GLfloat t, GLfloat r); +GLAPI void APIENTRY glMultiTexCoord3fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord3iARB (GLenum target, GLint s, GLint t, GLint r); +GLAPI void APIENTRY glMultiTexCoord3ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord3sARB (GLenum target, GLshort s, GLshort t, GLshort r); +GLAPI void APIENTRY glMultiTexCoord3svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord4dARB (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI void APIENTRY glMultiTexCoord4dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord4fARB (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI void APIENTRY glMultiTexCoord4fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord4iARB (GLenum target, GLint s, GLint t, GLint r, GLint q); +GLAPI void APIENTRY glMultiTexCoord4ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord4sARB (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI void APIENTRY glMultiTexCoord4svARB (GLenum target, const GLshort *v); +#endif +#endif /* GL_ARB_multitexture */ + +#ifndef GL_ARB_occlusion_query +#define GL_ARB_occlusion_query 1 +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#define GL_SAMPLES_PASSED_ARB 0x8914 +typedef void (APIENTRYP PFNGLGENQUERIESARBPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESARBPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYARBPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYARBPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVARBPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVARBPROC) (GLuint id, GLenum pname, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueriesARB (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteQueriesARB (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsQueryARB (GLuint id); +GLAPI void APIENTRY glBeginQueryARB (GLenum target, GLuint id); +GLAPI void APIENTRY glEndQueryARB (GLenum target); +GLAPI void APIENTRY glGetQueryivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectivARB (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectuivARB (GLuint id, GLenum pname, GLuint *params); +#endif +#endif /* GL_ARB_occlusion_query */ + +#ifndef GL_ARB_occlusion_query2 +#define GL_ARB_occlusion_query2 1 +#endif /* GL_ARB_occlusion_query2 */ + +#ifndef GL_ARB_pixel_buffer_object +#define GL_ARB_pixel_buffer_object 1 +#define GL_PIXEL_PACK_BUFFER_ARB 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_ARB 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_ARB 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_ARB 0x88EF +#endif /* GL_ARB_pixel_buffer_object */ + +#ifndef GL_ARB_point_parameters +#define GL_ARB_point_parameters 1 +#define GL_POINT_SIZE_MIN_ARB 0x8126 +#define GL_POINT_SIZE_MAX_ARB 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_ARB 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION_ARB 0x8129 +typedef void (APIENTRYP PFNGLPOINTPARAMETERFARBPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVARBPROC) (GLenum pname, const GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfARB (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvARB (GLenum pname, const GLfloat *params); +#endif +#endif /* GL_ARB_point_parameters */ + +#ifndef GL_ARB_point_sprite +#define GL_ARB_point_sprite 1 +#define GL_POINT_SPRITE_ARB 0x8861 +#define GL_COORD_REPLACE_ARB 0x8862 +#endif /* GL_ARB_point_sprite */ + +#ifndef GL_ARB_program_interface_query +#define GL_ARB_program_interface_query 1 +#endif /* GL_ARB_program_interface_query */ + +#ifndef GL_ARB_provoking_vertex +#define GL_ARB_provoking_vertex 1 +#endif /* GL_ARB_provoking_vertex */ + +#ifndef GL_ARB_query_buffer_object +#define GL_ARB_query_buffer_object 1 +#endif /* GL_ARB_query_buffer_object */ + +#ifndef GL_ARB_robust_buffer_access_behavior +#define GL_ARB_robust_buffer_access_behavior 1 +#endif /* GL_ARB_robust_buffer_access_behavior */ + +#ifndef GL_ARB_robustness +#define GL_ARB_robustness 1 +#define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT_ARB 0x00000004 +#define GL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 +#define GL_GUILTY_CONTEXT_RESET_ARB 0x8253 +#define GL_INNOCENT_CONTEXT_RESET_ARB 0x8254 +#define GL_UNKNOWN_CONTEXT_RESET_ARB 0x8255 +#define GL_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 +#define GL_NO_RESET_NOTIFICATION_ARB 0x8261 +typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSARBPROC) (void); +typedef void (APIENTRYP PFNGLGETNTEXIMAGEARBPROC) (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *img); +typedef void (APIENTRYP PFNGLREADNPIXELSARBPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); +typedef void (APIENTRYP PFNGLGETNCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint lod, GLsizei bufSize, void *img); +typedef void (APIENTRYP PFNGLGETNUNIFORMFVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMIVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLint *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMUIVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMDVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); +typedef void (APIENTRYP PFNGLGETNMAPDVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); +typedef void (APIENTRYP PFNGLGETNMAPFVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); +typedef void (APIENTRYP PFNGLGETNMAPIVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLint *v); +typedef void (APIENTRYP PFNGLGETNPIXELMAPFVARBPROC) (GLenum map, GLsizei bufSize, GLfloat *values); +typedef void (APIENTRYP PFNGLGETNPIXELMAPUIVARBPROC) (GLenum map, GLsizei bufSize, GLuint *values); +typedef void (APIENTRYP PFNGLGETNPIXELMAPUSVARBPROC) (GLenum map, GLsizei bufSize, GLushort *values); +typedef void (APIENTRYP PFNGLGETNPOLYGONSTIPPLEARBPROC) (GLsizei bufSize, GLubyte *pattern); +typedef void (APIENTRYP PFNGLGETNCOLORTABLEARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table); +typedef void (APIENTRYP PFNGLGETNCONVOLUTIONFILTERARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image); +typedef void (APIENTRYP PFNGLGETNSEPARABLEFILTERARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span); +typedef void (APIENTRYP PFNGLGETNHISTOGRAMARBPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +typedef void (APIENTRYP PFNGLGETNMINMAXARBPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLenum APIENTRY glGetGraphicsResetStatusARB (void); +GLAPI void APIENTRY glGetnTexImageARB (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *img); +GLAPI void APIENTRY glReadnPixelsARB (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); +GLAPI void APIENTRY glGetnCompressedTexImageARB (GLenum target, GLint lod, GLsizei bufSize, void *img); +GLAPI void APIENTRY glGetnUniformfvARB (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +GLAPI void APIENTRY glGetnUniformivARB (GLuint program, GLint location, GLsizei bufSize, GLint *params); +GLAPI void APIENTRY glGetnUniformuivARB (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +GLAPI void APIENTRY glGetnUniformdvARB (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); +GLAPI void APIENTRY glGetnMapdvARB (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); +GLAPI void APIENTRY glGetnMapfvARB (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); +GLAPI void APIENTRY glGetnMapivARB (GLenum target, GLenum query, GLsizei bufSize, GLint *v); +GLAPI void APIENTRY glGetnPixelMapfvARB (GLenum map, GLsizei bufSize, GLfloat *values); +GLAPI void APIENTRY glGetnPixelMapuivARB (GLenum map, GLsizei bufSize, GLuint *values); +GLAPI void APIENTRY glGetnPixelMapusvARB (GLenum map, GLsizei bufSize, GLushort *values); +GLAPI void APIENTRY glGetnPolygonStippleARB (GLsizei bufSize, GLubyte *pattern); +GLAPI void APIENTRY glGetnColorTableARB (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table); +GLAPI void APIENTRY glGetnConvolutionFilterARB (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image); +GLAPI void APIENTRY glGetnSeparableFilterARB (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span); +GLAPI void APIENTRY glGetnHistogramARB (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +GLAPI void APIENTRY glGetnMinmaxARB (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +#endif +#endif /* GL_ARB_robustness */ + +#ifndef GL_ARB_robustness_isolation +#define GL_ARB_robustness_isolation 1 +#endif /* GL_ARB_robustness_isolation */ + +#ifndef GL_ARB_sample_shading +#define GL_ARB_sample_shading 1 +#define GL_SAMPLE_SHADING_ARB 0x8C36 +#define GL_MIN_SAMPLE_SHADING_VALUE_ARB 0x8C37 +typedef void (APIENTRYP PFNGLMINSAMPLESHADINGARBPROC) (GLfloat value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMinSampleShadingARB (GLfloat value); +#endif +#endif /* GL_ARB_sample_shading */ + +#ifndef GL_ARB_sampler_objects +#define GL_ARB_sampler_objects 1 +#endif /* GL_ARB_sampler_objects */ + +#ifndef GL_ARB_seamless_cube_map +#define GL_ARB_seamless_cube_map 1 +#endif /* GL_ARB_seamless_cube_map */ + +#ifndef GL_ARB_seamless_cubemap_per_texture +#define GL_ARB_seamless_cubemap_per_texture 1 +#endif /* GL_ARB_seamless_cubemap_per_texture */ + +#ifndef GL_ARB_separate_shader_objects +#define GL_ARB_separate_shader_objects 1 +#endif /* GL_ARB_separate_shader_objects */ + +#ifndef GL_ARB_shader_atomic_counters +#define GL_ARB_shader_atomic_counters 1 +#endif /* GL_ARB_shader_atomic_counters */ + +#ifndef GL_ARB_shader_bit_encoding +#define GL_ARB_shader_bit_encoding 1 +#endif /* GL_ARB_shader_bit_encoding */ + +#ifndef GL_ARB_shader_draw_parameters +#define GL_ARB_shader_draw_parameters 1 +#endif /* GL_ARB_shader_draw_parameters */ + +#ifndef GL_ARB_shader_group_vote +#define GL_ARB_shader_group_vote 1 +#endif /* GL_ARB_shader_group_vote */ + +#ifndef GL_ARB_shader_image_load_store +#define GL_ARB_shader_image_load_store 1 +#endif /* GL_ARB_shader_image_load_store */ + +#ifndef GL_ARB_shader_image_size +#define GL_ARB_shader_image_size 1 +#endif /* GL_ARB_shader_image_size */ + +#ifndef GL_ARB_shader_objects +#define GL_ARB_shader_objects 1 +#ifdef __APPLE__ +typedef void *GLhandleARB; +#else +typedef unsigned int GLhandleARB; +#endif +typedef char GLcharARB; +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_SAMPLER_1D_ARB 0x8B5D +#define GL_SAMPLER_2D_ARB 0x8B5E +#define GL_SAMPLER_3D_ARB 0x8B5F +#define GL_SAMPLER_CUBE_ARB 0x8B60 +#define GL_SAMPLER_1D_SHADOW_ARB 0x8B61 +#define GL_SAMPLER_2D_SHADOW_ARB 0x8B62 +#define GL_SAMPLER_2D_RECT_ARB 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 +typedef void (APIENTRYP PFNGLDELETEOBJECTARBPROC) (GLhandleARB obj); +typedef GLhandleARB (APIENTRYP PFNGLGETHANDLEARBPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLDETACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB attachedObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATESHADEROBJECTARBPROC) (GLenum shaderType); +typedef void (APIENTRYP PFNGLSHADERSOURCEARBPROC) (GLhandleARB shaderObj, GLsizei count, const GLcharARB **string, const GLint *length); +typedef void (APIENTRYP PFNGLCOMPILESHADERARBPROC) (GLhandleARB shaderObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATEPROGRAMOBJECTARBPROC) (void); +typedef void (APIENTRYP PFNGLATTACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB obj); +typedef void (APIENTRYP PFNGLLINKPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUSEPROGRAMOBJECTARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUNIFORM1FARBPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FARBPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IARBPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IARBPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERFVARBPROC) (GLhandleARB obj, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVARBPROC) (GLhandleARB obj, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETINFOLOGARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +typedef void (APIENTRYP PFNGLGETATTACHEDOBJECTSARBPROC) (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVARBPROC) (GLhandleARB programObj, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVARBPROC) (GLhandleARB programObj, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteObjectARB (GLhandleARB obj); +GLAPI GLhandleARB APIENTRY glGetHandleARB (GLenum pname); +GLAPI void APIENTRY glDetachObjectARB (GLhandleARB containerObj, GLhandleARB attachedObj); +GLAPI GLhandleARB APIENTRY glCreateShaderObjectARB (GLenum shaderType); +GLAPI void APIENTRY glShaderSourceARB (GLhandleARB shaderObj, GLsizei count, const GLcharARB **string, const GLint *length); +GLAPI void APIENTRY glCompileShaderARB (GLhandleARB shaderObj); +GLAPI GLhandleARB APIENTRY glCreateProgramObjectARB (void); +GLAPI void APIENTRY glAttachObjectARB (GLhandleARB containerObj, GLhandleARB obj); +GLAPI void APIENTRY glLinkProgramARB (GLhandleARB programObj); +GLAPI void APIENTRY glUseProgramObjectARB (GLhandleARB programObj); +GLAPI void APIENTRY glValidateProgramARB (GLhandleARB programObj); +GLAPI void APIENTRY glUniform1fARB (GLint location, GLfloat v0); +GLAPI void APIENTRY glUniform2fARB (GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glUniform3fARB (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glUniform4fARB (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glUniform1iARB (GLint location, GLint v0); +GLAPI void APIENTRY glUniform2iARB (GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glUniform3iARB (GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glUniform4iARB (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glUniform1fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform2fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform3fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform4fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform1ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform2ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform3ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform4ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniformMatrix2fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glGetObjectParameterfvARB (GLhandleARB obj, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetObjectParameterivARB (GLhandleARB obj, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetInfoLogARB (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +GLAPI void APIENTRY glGetAttachedObjectsARB (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +GLAPI GLint APIENTRY glGetUniformLocationARB (GLhandleARB programObj, const GLcharARB *name); +GLAPI void APIENTRY glGetActiveUniformARB (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +GLAPI void APIENTRY glGetUniformfvARB (GLhandleARB programObj, GLint location, GLfloat *params); +GLAPI void APIENTRY glGetUniformivARB (GLhandleARB programObj, GLint location, GLint *params); +GLAPI void APIENTRY glGetShaderSourceARB (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +#endif +#endif /* GL_ARB_shader_objects */ + +#ifndef GL_ARB_shader_precision +#define GL_ARB_shader_precision 1 +#endif /* GL_ARB_shader_precision */ + +#ifndef GL_ARB_shader_stencil_export +#define GL_ARB_shader_stencil_export 1 +#endif /* GL_ARB_shader_stencil_export */ + +#ifndef GL_ARB_shader_storage_buffer_object +#define GL_ARB_shader_storage_buffer_object 1 +#endif /* GL_ARB_shader_storage_buffer_object */ + +#ifndef GL_ARB_shader_subroutine +#define GL_ARB_shader_subroutine 1 +#endif /* GL_ARB_shader_subroutine */ + +#ifndef GL_ARB_shader_texture_lod +#define GL_ARB_shader_texture_lod 1 +#endif /* GL_ARB_shader_texture_lod */ + +#ifndef GL_ARB_shading_language_100 +#define GL_ARB_shading_language_100 1 +#define GL_SHADING_LANGUAGE_VERSION_ARB 0x8B8C +#endif /* GL_ARB_shading_language_100 */ + +#ifndef GL_ARB_shading_language_420pack +#define GL_ARB_shading_language_420pack 1 +#endif /* GL_ARB_shading_language_420pack */ + +#ifndef GL_ARB_shading_language_include +#define GL_ARB_shading_language_include 1 +#define GL_SHADER_INCLUDE_ARB 0x8DAE +#define GL_NAMED_STRING_LENGTH_ARB 0x8DE9 +#define GL_NAMED_STRING_TYPE_ARB 0x8DEA +typedef void (APIENTRYP PFNGLNAMEDSTRINGARBPROC) (GLenum type, GLint namelen, const GLchar *name, GLint stringlen, const GLchar *string); +typedef void (APIENTRYP PFNGLDELETENAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name); +typedef void (APIENTRYP PFNGLCOMPILESHADERINCLUDEARBPROC) (GLuint shader, GLsizei count, const GLchar *const*path, const GLint *length); +typedef GLboolean (APIENTRYP PFNGLISNAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name); +typedef void (APIENTRYP PFNGLGETNAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name, GLsizei bufSize, GLint *stringlen, GLchar *string); +typedef void (APIENTRYP PFNGLGETNAMEDSTRINGIVARBPROC) (GLint namelen, const GLchar *name, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glNamedStringARB (GLenum type, GLint namelen, const GLchar *name, GLint stringlen, const GLchar *string); +GLAPI void APIENTRY glDeleteNamedStringARB (GLint namelen, const GLchar *name); +GLAPI void APIENTRY glCompileShaderIncludeARB (GLuint shader, GLsizei count, const GLchar *const*path, const GLint *length); +GLAPI GLboolean APIENTRY glIsNamedStringARB (GLint namelen, const GLchar *name); +GLAPI void APIENTRY glGetNamedStringARB (GLint namelen, const GLchar *name, GLsizei bufSize, GLint *stringlen, GLchar *string); +GLAPI void APIENTRY glGetNamedStringivARB (GLint namelen, const GLchar *name, GLenum pname, GLint *params); +#endif +#endif /* GL_ARB_shading_language_include */ + +#ifndef GL_ARB_shading_language_packing +#define GL_ARB_shading_language_packing 1 +#endif /* GL_ARB_shading_language_packing */ + +#ifndef GL_ARB_shadow +#define GL_ARB_shadow 1 +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#endif /* GL_ARB_shadow */ + +#ifndef GL_ARB_shadow_ambient +#define GL_ARB_shadow_ambient 1 +#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF +#endif /* GL_ARB_shadow_ambient */ + +#ifndef GL_ARB_sparse_texture +#define GL_ARB_sparse_texture 1 +#define GL_TEXTURE_SPARSE_ARB 0x91A6 +#define GL_VIRTUAL_PAGE_SIZE_INDEX_ARB 0x91A7 +#define GL_MIN_SPARSE_LEVEL_ARB 0x919B +#define GL_NUM_VIRTUAL_PAGE_SIZES_ARB 0x91A8 +#define GL_VIRTUAL_PAGE_SIZE_X_ARB 0x9195 +#define GL_VIRTUAL_PAGE_SIZE_Y_ARB 0x9196 +#define GL_VIRTUAL_PAGE_SIZE_Z_ARB 0x9197 +#define GL_MAX_SPARSE_TEXTURE_SIZE_ARB 0x9198 +#define GL_MAX_SPARSE_3D_TEXTURE_SIZE_ARB 0x9199 +#define GL_MAX_SPARSE_ARRAY_TEXTURE_LAYERS_ARB 0x919A +#define GL_SPARSE_TEXTURE_FULL_ARRAY_CUBE_MIPMAPS_ARB 0x91A9 +typedef void (APIENTRYP PFNGLTEXPAGECOMMITMENTARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean resident); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexPageCommitmentARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean resident); +#endif +#endif /* GL_ARB_sparse_texture */ + +#ifndef GL_ARB_stencil_texturing +#define GL_ARB_stencil_texturing 1 +#endif /* GL_ARB_stencil_texturing */ + +#ifndef GL_ARB_sync +#define GL_ARB_sync 1 +#endif /* GL_ARB_sync */ + +#ifndef GL_ARB_tessellation_shader +#define GL_ARB_tessellation_shader 1 +#endif /* GL_ARB_tessellation_shader */ + +#ifndef GL_ARB_texture_border_clamp +#define GL_ARB_texture_border_clamp 1 +#define GL_CLAMP_TO_BORDER_ARB 0x812D +#endif /* GL_ARB_texture_border_clamp */ + +#ifndef GL_ARB_texture_buffer_object +#define GL_ARB_texture_buffer_object 1 +#define GL_TEXTURE_BUFFER_ARB 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE_ARB 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER_ARB 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_ARB 0x8C2D +#define GL_TEXTURE_BUFFER_FORMAT_ARB 0x8C2E +typedef void (APIENTRYP PFNGLTEXBUFFERARBPROC) (GLenum target, GLenum internalformat, GLuint buffer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBufferARB (GLenum target, GLenum internalformat, GLuint buffer); +#endif +#endif /* GL_ARB_texture_buffer_object */ + +#ifndef GL_ARB_texture_buffer_object_rgb32 +#define GL_ARB_texture_buffer_object_rgb32 1 +#endif /* GL_ARB_texture_buffer_object_rgb32 */ + +#ifndef GL_ARB_texture_buffer_range +#define GL_ARB_texture_buffer_range 1 +#endif /* GL_ARB_texture_buffer_range */ + +#ifndef GL_ARB_texture_compression +#define GL_ARB_texture_compression 1 +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint level, void *img); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCompressedTexImage3DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexImage2DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexImage1DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage3DARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage2DARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage1DARB (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glGetCompressedTexImageARB (GLenum target, GLint level, void *img); +#endif +#endif /* GL_ARB_texture_compression */ + +#ifndef GL_ARB_texture_compression_bptc +#define GL_ARB_texture_compression_bptc 1 +#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F +#endif /* GL_ARB_texture_compression_bptc */ + +#ifndef GL_ARB_texture_compression_rgtc +#define GL_ARB_texture_compression_rgtc 1 +#endif /* GL_ARB_texture_compression_rgtc */ + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C +#endif /* GL_ARB_texture_cube_map */ + +#ifndef GL_ARB_texture_cube_map_array +#define GL_ARB_texture_cube_map_array 1 +#define GL_TEXTURE_CUBE_MAP_ARRAY_ARB 0x9009 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY_ARB 0x900A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARRAY_ARB 0x900B +#define GL_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900C +#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW_ARB 0x900D +#define GL_INT_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900E +#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900F +#endif /* GL_ARB_texture_cube_map_array */ + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 +#endif /* GL_ARB_texture_env_add */ + +#ifndef GL_ARB_texture_env_combine +#define GL_ARB_texture_env_combine 1 +#define GL_COMBINE_ARB 0x8570 +#define GL_COMBINE_RGB_ARB 0x8571 +#define GL_COMBINE_ALPHA_ARB 0x8572 +#define GL_SOURCE0_RGB_ARB 0x8580 +#define GL_SOURCE1_RGB_ARB 0x8581 +#define GL_SOURCE2_RGB_ARB 0x8582 +#define GL_SOURCE0_ALPHA_ARB 0x8588 +#define GL_SOURCE1_ALPHA_ARB 0x8589 +#define GL_SOURCE2_ALPHA_ARB 0x858A +#define GL_OPERAND0_RGB_ARB 0x8590 +#define GL_OPERAND1_RGB_ARB 0x8591 +#define GL_OPERAND2_RGB_ARB 0x8592 +#define GL_OPERAND0_ALPHA_ARB 0x8598 +#define GL_OPERAND1_ALPHA_ARB 0x8599 +#define GL_OPERAND2_ALPHA_ARB 0x859A +#define GL_RGB_SCALE_ARB 0x8573 +#define GL_ADD_SIGNED_ARB 0x8574 +#define GL_INTERPOLATE_ARB 0x8575 +#define GL_SUBTRACT_ARB 0x84E7 +#define GL_CONSTANT_ARB 0x8576 +#define GL_PRIMARY_COLOR_ARB 0x8577 +#define GL_PREVIOUS_ARB 0x8578 +#endif /* GL_ARB_texture_env_combine */ + +#ifndef GL_ARB_texture_env_crossbar +#define GL_ARB_texture_env_crossbar 1 +#endif /* GL_ARB_texture_env_crossbar */ + +#ifndef GL_ARB_texture_env_dot3 +#define GL_ARB_texture_env_dot3 1 +#define GL_DOT3_RGB_ARB 0x86AE +#define GL_DOT3_RGBA_ARB 0x86AF +#endif /* GL_ARB_texture_env_dot3 */ + +#ifndef GL_ARB_texture_float +#define GL_ARB_texture_float 1 +#define GL_TEXTURE_RED_TYPE_ARB 0x8C10 +#define GL_TEXTURE_GREEN_TYPE_ARB 0x8C11 +#define GL_TEXTURE_BLUE_TYPE_ARB 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE_ARB 0x8C13 +#define GL_TEXTURE_LUMINANCE_TYPE_ARB 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE_ARB 0x8C15 +#define GL_TEXTURE_DEPTH_TYPE_ARB 0x8C16 +#define GL_UNSIGNED_NORMALIZED_ARB 0x8C17 +#define GL_RGBA32F_ARB 0x8814 +#define GL_RGB32F_ARB 0x8815 +#define GL_ALPHA32F_ARB 0x8816 +#define GL_INTENSITY32F_ARB 0x8817 +#define GL_LUMINANCE32F_ARB 0x8818 +#define GL_LUMINANCE_ALPHA32F_ARB 0x8819 +#define GL_RGBA16F_ARB 0x881A +#define GL_RGB16F_ARB 0x881B +#define GL_ALPHA16F_ARB 0x881C +#define GL_INTENSITY16F_ARB 0x881D +#define GL_LUMINANCE16F_ARB 0x881E +#define GL_LUMINANCE_ALPHA16F_ARB 0x881F +#endif /* GL_ARB_texture_float */ + +#ifndef GL_ARB_texture_gather +#define GL_ARB_texture_gather 1 +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET_ARB 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET_ARB 0x8E5F +#define GL_MAX_PROGRAM_TEXTURE_GATHER_COMPONENTS_ARB 0x8F9F +#endif /* GL_ARB_texture_gather */ + +#ifndef GL_ARB_texture_mirror_clamp_to_edge +#define GL_ARB_texture_mirror_clamp_to_edge 1 +#endif /* GL_ARB_texture_mirror_clamp_to_edge */ + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_ARB_texture_mirrored_repeat 1 +#define GL_MIRRORED_REPEAT_ARB 0x8370 +#endif /* GL_ARB_texture_mirrored_repeat */ + +#ifndef GL_ARB_texture_multisample +#define GL_ARB_texture_multisample 1 +#endif /* GL_ARB_texture_multisample */ + +#ifndef GL_ARB_texture_non_power_of_two +#define GL_ARB_texture_non_power_of_two 1 +#endif /* GL_ARB_texture_non_power_of_two */ + +#ifndef GL_ARB_texture_query_levels +#define GL_ARB_texture_query_levels 1 +#endif /* GL_ARB_texture_query_levels */ + +#ifndef GL_ARB_texture_query_lod +#define GL_ARB_texture_query_lod 1 +#endif /* GL_ARB_texture_query_lod */ + +#ifndef GL_ARB_texture_rectangle +#define GL_ARB_texture_rectangle 1 +#define GL_TEXTURE_RECTANGLE_ARB 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_ARB 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_ARB 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 +#endif /* GL_ARB_texture_rectangle */ + +#ifndef GL_ARB_texture_rg +#define GL_ARB_texture_rg 1 +#endif /* GL_ARB_texture_rg */ + +#ifndef GL_ARB_texture_rgb10_a2ui +#define GL_ARB_texture_rgb10_a2ui 1 +#endif /* GL_ARB_texture_rgb10_a2ui */ + +#ifndef GL_ARB_texture_stencil8 +#define GL_ARB_texture_stencil8 1 +#endif /* GL_ARB_texture_stencil8 */ + +#ifndef GL_ARB_texture_storage +#define GL_ARB_texture_storage 1 +#endif /* GL_ARB_texture_storage */ + +#ifndef GL_ARB_texture_storage_multisample +#define GL_ARB_texture_storage_multisample 1 +#endif /* GL_ARB_texture_storage_multisample */ + +#ifndef GL_ARB_texture_swizzle +#define GL_ARB_texture_swizzle 1 +#endif /* GL_ARB_texture_swizzle */ + +#ifndef GL_ARB_texture_view +#define GL_ARB_texture_view 1 +#endif /* GL_ARB_texture_view */ + +#ifndef GL_ARB_timer_query +#define GL_ARB_timer_query 1 +#endif /* GL_ARB_timer_query */ + +#ifndef GL_ARB_transform_feedback2 +#define GL_ARB_transform_feedback2 1 +#define GL_TRANSFORM_FEEDBACK_PAUSED 0x8E23 +#define GL_TRANSFORM_FEEDBACK_ACTIVE 0x8E24 +#endif /* GL_ARB_transform_feedback2 */ + +#ifndef GL_ARB_transform_feedback3 +#define GL_ARB_transform_feedback3 1 +#endif /* GL_ARB_transform_feedback3 */ + +#ifndef GL_ARB_transform_feedback_instanced +#define GL_ARB_transform_feedback_instanced 1 +#endif /* GL_ARB_transform_feedback_instanced */ + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 +#define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLoadTransposeMatrixfARB (const GLfloat *m); +GLAPI void APIENTRY glLoadTransposeMatrixdARB (const GLdouble *m); +GLAPI void APIENTRY glMultTransposeMatrixfARB (const GLfloat *m); +GLAPI void APIENTRY glMultTransposeMatrixdARB (const GLdouble *m); +#endif +#endif /* GL_ARB_transpose_matrix */ + +#ifndef GL_ARB_uniform_buffer_object +#define GL_ARB_uniform_buffer_object 1 +#define GL_MAX_GEOMETRY_UNIFORM_BLOCKS 0x8A2C +#define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS 0x8A32 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER 0x8A45 +#endif /* GL_ARB_uniform_buffer_object */ + +#ifndef GL_ARB_vertex_array_bgra +#define GL_ARB_vertex_array_bgra 1 +#endif /* GL_ARB_vertex_array_bgra */ + +#ifndef GL_ARB_vertex_array_object +#define GL_ARB_vertex_array_object 1 +#endif /* GL_ARB_vertex_array_object */ + +#ifndef GL_ARB_vertex_attrib_64bit +#define GL_ARB_vertex_attrib_64bit 1 +#endif /* GL_ARB_vertex_attrib_64bit */ + +#ifndef GL_ARB_vertex_attrib_binding +#define GL_ARB_vertex_attrib_binding 1 +#endif /* GL_ARB_vertex_attrib_binding */ + +#ifndef GL_ARB_vertex_blend +#define GL_ARB_vertex_blend 1 +#define GL_MAX_VERTEX_UNITS_ARB 0x86A4 +#define GL_ACTIVE_VERTEX_UNITS_ARB 0x86A5 +#define GL_WEIGHT_SUM_UNITY_ARB 0x86A6 +#define GL_VERTEX_BLEND_ARB 0x86A7 +#define GL_CURRENT_WEIGHT_ARB 0x86A8 +#define GL_WEIGHT_ARRAY_TYPE_ARB 0x86A9 +#define GL_WEIGHT_ARRAY_STRIDE_ARB 0x86AA +#define GL_WEIGHT_ARRAY_SIZE_ARB 0x86AB +#define GL_WEIGHT_ARRAY_POINTER_ARB 0x86AC +#define GL_WEIGHT_ARRAY_ARB 0x86AD +#define GL_MODELVIEW0_ARB 0x1700 +#define GL_MODELVIEW1_ARB 0x850A +#define GL_MODELVIEW2_ARB 0x8722 +#define GL_MODELVIEW3_ARB 0x8723 +#define GL_MODELVIEW4_ARB 0x8724 +#define GL_MODELVIEW5_ARB 0x8725 +#define GL_MODELVIEW6_ARB 0x8726 +#define GL_MODELVIEW7_ARB 0x8727 +#define GL_MODELVIEW8_ARB 0x8728 +#define GL_MODELVIEW9_ARB 0x8729 +#define GL_MODELVIEW10_ARB 0x872A +#define GL_MODELVIEW11_ARB 0x872B +#define GL_MODELVIEW12_ARB 0x872C +#define GL_MODELVIEW13_ARB 0x872D +#define GL_MODELVIEW14_ARB 0x872E +#define GL_MODELVIEW15_ARB 0x872F +#define GL_MODELVIEW16_ARB 0x8730 +#define GL_MODELVIEW17_ARB 0x8731 +#define GL_MODELVIEW18_ARB 0x8732 +#define GL_MODELVIEW19_ARB 0x8733 +#define GL_MODELVIEW20_ARB 0x8734 +#define GL_MODELVIEW21_ARB 0x8735 +#define GL_MODELVIEW22_ARB 0x8736 +#define GL_MODELVIEW23_ARB 0x8737 +#define GL_MODELVIEW24_ARB 0x8738 +#define GL_MODELVIEW25_ARB 0x8739 +#define GL_MODELVIEW26_ARB 0x873A +#define GL_MODELVIEW27_ARB 0x873B +#define GL_MODELVIEW28_ARB 0x873C +#define GL_MODELVIEW29_ARB 0x873D +#define GL_MODELVIEW30_ARB 0x873E +#define GL_MODELVIEW31_ARB 0x873F +typedef void (APIENTRYP PFNGLWEIGHTBVARBPROC) (GLint size, const GLbyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTSVARBPROC) (GLint size, const GLshort *weights); +typedef void (APIENTRYP PFNGLWEIGHTIVARBPROC) (GLint size, const GLint *weights); +typedef void (APIENTRYP PFNGLWEIGHTFVARBPROC) (GLint size, const GLfloat *weights); +typedef void (APIENTRYP PFNGLWEIGHTDVARBPROC) (GLint size, const GLdouble *weights); +typedef void (APIENTRYP PFNGLWEIGHTUBVARBPROC) (GLint size, const GLubyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTUSVARBPROC) (GLint size, const GLushort *weights); +typedef void (APIENTRYP PFNGLWEIGHTUIVARBPROC) (GLint size, const GLuint *weights); +typedef void (APIENTRYP PFNGLWEIGHTPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLVERTEXBLENDARBPROC) (GLint count); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWeightbvARB (GLint size, const GLbyte *weights); +GLAPI void APIENTRY glWeightsvARB (GLint size, const GLshort *weights); +GLAPI void APIENTRY glWeightivARB (GLint size, const GLint *weights); +GLAPI void APIENTRY glWeightfvARB (GLint size, const GLfloat *weights); +GLAPI void APIENTRY glWeightdvARB (GLint size, const GLdouble *weights); +GLAPI void APIENTRY glWeightubvARB (GLint size, const GLubyte *weights); +GLAPI void APIENTRY glWeightusvARB (GLint size, const GLushort *weights); +GLAPI void APIENTRY glWeightuivARB (GLint size, const GLuint *weights); +GLAPI void APIENTRY glWeightPointerARB (GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glVertexBlendARB (GLint count); +#endif +#endif /* GL_ARB_vertex_blend */ + +#ifndef GL_ARB_vertex_buffer_object +#define GL_ARB_vertex_buffer_object 1 +typedef ptrdiff_t GLsizeiptrARB; +typedef ptrdiff_t GLintptrARB; +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_ARRAY_BUFFER_ARB 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 +#define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#define GL_STREAM_DRAW_ARB 0x88E0 +#define GL_STREAM_READ_ARB 0x88E1 +#define GL_STREAM_COPY_ARB 0x88E2 +#define GL_STATIC_DRAW_ARB 0x88E4 +#define GL_STATIC_READ_ARB 0x88E5 +#define GL_STATIC_COPY_ARB 0x88E6 +#define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DYNAMIC_READ_ARB 0x88E9 +#define GL_DYNAMIC_COPY_ARB 0x88EA +typedef void (APIENTRYP PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERARBPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAARBPROC) (GLenum target, GLsizeiptrARB size, const void *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const void *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, void *data); +typedef void *(APIENTRYP PFNGLMAPBUFFERARBPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVARBPROC) (GLenum target, GLenum pname, void **params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindBufferARB (GLenum target, GLuint buffer); +GLAPI void APIENTRY glDeleteBuffersARB (GLsizei n, const GLuint *buffers); +GLAPI void APIENTRY glGenBuffersARB (GLsizei n, GLuint *buffers); +GLAPI GLboolean APIENTRY glIsBufferARB (GLuint buffer); +GLAPI void APIENTRY glBufferDataARB (GLenum target, GLsizeiptrARB size, const void *data, GLenum usage); +GLAPI void APIENTRY glBufferSubDataARB (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const void *data); +GLAPI void APIENTRY glGetBufferSubDataARB (GLenum target, GLintptrARB offset, GLsizeiptrARB size, void *data); +GLAPI void *APIENTRY glMapBufferARB (GLenum target, GLenum access); +GLAPI GLboolean APIENTRY glUnmapBufferARB (GLenum target); +GLAPI void APIENTRY glGetBufferParameterivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetBufferPointervARB (GLenum target, GLenum pname, void **params); +#endif +#endif /* GL_ARB_vertex_buffer_object */ + +#ifndef GL_ARB_vertex_program +#define GL_ARB_vertex_program 1 +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DARBPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FARBPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SARBPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DARBPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FARBPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SARBPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBARBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERARBPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVARBPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVARBPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVARBPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVARBPROC) (GLuint index, GLenum pname, void **pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttrib1dARB (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1fARB (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1sARB (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2dARB (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2fARB (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2sARB (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3dARB (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3fARB (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3sARB (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4NbvARB (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4NivARB (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4NsvARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4NubARB (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4NubvARB (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4NuivARB (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4NusvARB (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttrib4bvARB (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4dARB (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4fARB (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4ivARB (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4sARB (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubvARB (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4uivARB (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4usvARB (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribPointerARB (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glEnableVertexAttribArrayARB (GLuint index); +GLAPI void APIENTRY glDisableVertexAttribArrayARB (GLuint index); +GLAPI void APIENTRY glGetVertexAttribdvARB (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfvARB (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribivARB (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointervARB (GLuint index, GLenum pname, void **pointer); +#endif +#endif /* GL_ARB_vertex_program */ + +#ifndef GL_ARB_vertex_shader +#define GL_ARB_vertex_shader 1 +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONARBPROC) (GLhandleARB programObj, GLuint index, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindAttribLocationARB (GLhandleARB programObj, GLuint index, const GLcharARB *name); +GLAPI void APIENTRY glGetActiveAttribARB (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +GLAPI GLint APIENTRY glGetAttribLocationARB (GLhandleARB programObj, const GLcharARB *name); +#endif +#endif /* GL_ARB_vertex_shader */ + +#ifndef GL_ARB_vertex_type_10f_11f_11f_rev +#define GL_ARB_vertex_type_10f_11f_11f_rev 1 +#endif /* GL_ARB_vertex_type_10f_11f_11f_rev */ + +#ifndef GL_ARB_vertex_type_2_10_10_10_rev +#define GL_ARB_vertex_type_2_10_10_10_rev 1 +#endif /* GL_ARB_vertex_type_2_10_10_10_rev */ + +#ifndef GL_ARB_viewport_array +#define GL_ARB_viewport_array 1 +#endif /* GL_ARB_viewport_array */ + +#ifndef GL_ARB_window_pos +#define GL_ARB_window_pos 1 +typedef void (APIENTRYP PFNGLWINDOWPOS2DARBPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FARBPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IARBPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SARBPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVARBPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DARBPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FARBPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IARBPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SARBPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVARBPROC) (const GLshort *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dARB (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dvARB (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2fARB (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fvARB (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2iARB (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2ivARB (const GLint *v); +GLAPI void APIENTRY glWindowPos2sARB (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2svARB (const GLshort *v); +GLAPI void APIENTRY glWindowPos3dARB (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dvARB (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3fARB (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fvARB (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3iARB (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3ivARB (const GLint *v); +GLAPI void APIENTRY glWindowPos3sARB (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3svARB (const GLshort *v); +#endif +#endif /* GL_ARB_window_pos */ + +#ifndef GL_KHR_debug +#define GL_KHR_debug 1 +#endif /* GL_KHR_debug */ + +#ifndef GL_KHR_texture_compression_astc_hdr +#define GL_KHR_texture_compression_astc_hdr 1 +#define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93B0 +#define GL_COMPRESSED_RGBA_ASTC_5x4_KHR 0x93B1 +#define GL_COMPRESSED_RGBA_ASTC_5x5_KHR 0x93B2 +#define GL_COMPRESSED_RGBA_ASTC_6x5_KHR 0x93B3 +#define GL_COMPRESSED_RGBA_ASTC_6x6_KHR 0x93B4 +#define GL_COMPRESSED_RGBA_ASTC_8x5_KHR 0x93B5 +#define GL_COMPRESSED_RGBA_ASTC_8x6_KHR 0x93B6 +#define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93B7 +#define GL_COMPRESSED_RGBA_ASTC_10x5_KHR 0x93B8 +#define GL_COMPRESSED_RGBA_ASTC_10x6_KHR 0x93B9 +#define GL_COMPRESSED_RGBA_ASTC_10x8_KHR 0x93BA +#define GL_COMPRESSED_RGBA_ASTC_10x10_KHR 0x93BB +#define GL_COMPRESSED_RGBA_ASTC_12x10_KHR 0x93BC +#define GL_COMPRESSED_RGBA_ASTC_12x12_KHR 0x93BD +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR 0x93D0 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR 0x93D1 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR 0x93D2 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR 0x93D3 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR 0x93D4 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR 0x93D5 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR 0x93D6 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR 0x93D7 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR 0x93D8 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR 0x93D9 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR 0x93DA +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR 0x93DB +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR 0x93DC +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR 0x93DD +#endif /* GL_KHR_texture_compression_astc_hdr */ + +#ifndef GL_KHR_texture_compression_astc_ldr +#define GL_KHR_texture_compression_astc_ldr 1 +#endif /* GL_KHR_texture_compression_astc_ldr */ + +#ifndef GL_OES_byte_coordinates +#define GL_OES_byte_coordinates 1 +typedef void (APIENTRYP PFNGLMULTITEXCOORD1BOESPROC) (GLenum texture, GLbyte s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1BVOESPROC) (GLenum texture, const GLbyte *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2BOESPROC) (GLenum texture, GLbyte s, GLbyte t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2BVOESPROC) (GLenum texture, const GLbyte *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3BOESPROC) (GLenum texture, GLbyte s, GLbyte t, GLbyte r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3BVOESPROC) (GLenum texture, const GLbyte *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4BOESPROC) (GLenum texture, GLbyte s, GLbyte t, GLbyte r, GLbyte q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4BVOESPROC) (GLenum texture, const GLbyte *coords); +typedef void (APIENTRYP PFNGLTEXCOORD1BOESPROC) (GLbyte s); +typedef void (APIENTRYP PFNGLTEXCOORD1BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLTEXCOORD2BOESPROC) (GLbyte s, GLbyte t); +typedef void (APIENTRYP PFNGLTEXCOORD2BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLTEXCOORD3BOESPROC) (GLbyte s, GLbyte t, GLbyte r); +typedef void (APIENTRYP PFNGLTEXCOORD3BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLTEXCOORD4BOESPROC) (GLbyte s, GLbyte t, GLbyte r, GLbyte q); +typedef void (APIENTRYP PFNGLTEXCOORD4BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLVERTEX2BOESPROC) (GLbyte x); +typedef void (APIENTRYP PFNGLVERTEX2BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLVERTEX3BOESPROC) (GLbyte x, GLbyte y); +typedef void (APIENTRYP PFNGLVERTEX3BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLVERTEX4BOESPROC) (GLbyte x, GLbyte y, GLbyte z); +typedef void (APIENTRYP PFNGLVERTEX4BVOESPROC) (const GLbyte *coords); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiTexCoord1bOES (GLenum texture, GLbyte s); +GLAPI void APIENTRY glMultiTexCoord1bvOES (GLenum texture, const GLbyte *coords); +GLAPI void APIENTRY glMultiTexCoord2bOES (GLenum texture, GLbyte s, GLbyte t); +GLAPI void APIENTRY glMultiTexCoord2bvOES (GLenum texture, const GLbyte *coords); +GLAPI void APIENTRY glMultiTexCoord3bOES (GLenum texture, GLbyte s, GLbyte t, GLbyte r); +GLAPI void APIENTRY glMultiTexCoord3bvOES (GLenum texture, const GLbyte *coords); +GLAPI void APIENTRY glMultiTexCoord4bOES (GLenum texture, GLbyte s, GLbyte t, GLbyte r, GLbyte q); +GLAPI void APIENTRY glMultiTexCoord4bvOES (GLenum texture, const GLbyte *coords); +GLAPI void APIENTRY glTexCoord1bOES (GLbyte s); +GLAPI void APIENTRY glTexCoord1bvOES (const GLbyte *coords); +GLAPI void APIENTRY glTexCoord2bOES (GLbyte s, GLbyte t); +GLAPI void APIENTRY glTexCoord2bvOES (const GLbyte *coords); +GLAPI void APIENTRY glTexCoord3bOES (GLbyte s, GLbyte t, GLbyte r); +GLAPI void APIENTRY glTexCoord3bvOES (const GLbyte *coords); +GLAPI void APIENTRY glTexCoord4bOES (GLbyte s, GLbyte t, GLbyte r, GLbyte q); +GLAPI void APIENTRY glTexCoord4bvOES (const GLbyte *coords); +GLAPI void APIENTRY glVertex2bOES (GLbyte x); +GLAPI void APIENTRY glVertex2bvOES (const GLbyte *coords); +GLAPI void APIENTRY glVertex3bOES (GLbyte x, GLbyte y); +GLAPI void APIENTRY glVertex3bvOES (const GLbyte *coords); +GLAPI void APIENTRY glVertex4bOES (GLbyte x, GLbyte y, GLbyte z); +GLAPI void APIENTRY glVertex4bvOES (const GLbyte *coords); +#endif +#endif /* GL_OES_byte_coordinates */ + +#ifndef GL_OES_compressed_paletted_texture +#define GL_OES_compressed_paletted_texture 1 +#define GL_PALETTE4_RGB8_OES 0x8B90 +#define GL_PALETTE4_RGBA8_OES 0x8B91 +#define GL_PALETTE4_R5_G6_B5_OES 0x8B92 +#define GL_PALETTE4_RGBA4_OES 0x8B93 +#define GL_PALETTE4_RGB5_A1_OES 0x8B94 +#define GL_PALETTE8_RGB8_OES 0x8B95 +#define GL_PALETTE8_RGBA8_OES 0x8B96 +#define GL_PALETTE8_R5_G6_B5_OES 0x8B97 +#define GL_PALETTE8_RGBA4_OES 0x8B98 +#define GL_PALETTE8_RGB5_A1_OES 0x8B99 +#endif /* GL_OES_compressed_paletted_texture */ + +#ifndef GL_OES_fixed_point +#define GL_OES_fixed_point 1 +typedef GLint GLfixed; +#define GL_FIXED_OES 0x140C +typedef void (APIENTRYP PFNGLALPHAFUNCXOESPROC) (GLenum func, GLfixed ref); +typedef void (APIENTRYP PFNGLCLEARCOLORXOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +typedef void (APIENTRYP PFNGLCLEARDEPTHXOESPROC) (GLfixed depth); +typedef void (APIENTRYP PFNGLCLIPPLANEXOESPROC) (GLenum plane, const GLfixed *equation); +typedef void (APIENTRYP PFNGLCOLOR4XOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +typedef void (APIENTRYP PFNGLDEPTHRANGEXOESPROC) (GLfixed n, GLfixed f); +typedef void (APIENTRYP PFNGLFOGXOESPROC) (GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLFOGXVOESPROC) (GLenum pname, const GLfixed *param); +typedef void (APIENTRYP PFNGLFRUSTUMXOESPROC) (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); +typedef void (APIENTRYP PFNGLGETCLIPPLANEXOESPROC) (GLenum plane, GLfixed *equation); +typedef void (APIENTRYP PFNGLGETFIXEDVOESPROC) (GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETTEXENVXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLLIGHTMODELXOESPROC) (GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLLIGHTMODELXVOESPROC) (GLenum pname, const GLfixed *param); +typedef void (APIENTRYP PFNGLLIGHTXOESPROC) (GLenum light, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLLIGHTXVOESPROC) (GLenum light, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLLINEWIDTHXOESPROC) (GLfixed width); +typedef void (APIENTRYP PFNGLLOADMATRIXXOESPROC) (const GLfixed *m); +typedef void (APIENTRYP PFNGLMATERIALXOESPROC) (GLenum face, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLMATERIALXVOESPROC) (GLenum face, GLenum pname, const GLfixed *param); +typedef void (APIENTRYP PFNGLMULTMATRIXXOESPROC) (const GLfixed *m); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4XOESPROC) (GLenum texture, GLfixed s, GLfixed t, GLfixed r, GLfixed q); +typedef void (APIENTRYP PFNGLNORMAL3XOESPROC) (GLfixed nx, GLfixed ny, GLfixed nz); +typedef void (APIENTRYP PFNGLORTHOXOESPROC) (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); +typedef void (APIENTRYP PFNGLPOINTPARAMETERXVOESPROC) (GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLPOINTSIZEXOESPROC) (GLfixed size); +typedef void (APIENTRYP PFNGLPOLYGONOFFSETXOESPROC) (GLfixed factor, GLfixed units); +typedef void (APIENTRYP PFNGLROTATEXOESPROC) (GLfixed angle, GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEOESPROC) (GLfixed value, GLboolean invert); +typedef void (APIENTRYP PFNGLSCALEXOESPROC) (GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLTEXENVXOESPROC) (GLenum target, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLTEXENVXVOESPROC) (GLenum target, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLTEXPARAMETERXOESPROC) (GLenum target, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLTEXPARAMETERXVOESPROC) (GLenum target, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLTRANSLATEXOESPROC) (GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLACCUMXOESPROC) (GLenum op, GLfixed value); +typedef void (APIENTRYP PFNGLBITMAPXOESPROC) (GLsizei width, GLsizei height, GLfixed xorig, GLfixed yorig, GLfixed xmove, GLfixed ymove, const GLubyte *bitmap); +typedef void (APIENTRYP PFNGLBLENDCOLORXOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +typedef void (APIENTRYP PFNGLCLEARACCUMXOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +typedef void (APIENTRYP PFNGLCOLOR3XOESPROC) (GLfixed red, GLfixed green, GLfixed blue); +typedef void (APIENTRYP PFNGLCOLOR3XVOESPROC) (const GLfixed *components); +typedef void (APIENTRYP PFNGLCOLOR4XVOESPROC) (const GLfixed *components); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERXOESPROC) (GLenum target, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERXVOESPROC) (GLenum target, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLEVALCOORD1XOESPROC) (GLfixed u); +typedef void (APIENTRYP PFNGLEVALCOORD1XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLEVALCOORD2XOESPROC) (GLfixed u, GLfixed v); +typedef void (APIENTRYP PFNGLEVALCOORD2XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLFEEDBACKBUFFERXOESPROC) (GLsizei n, GLenum type, const GLfixed *buffer); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETLIGHTXOESPROC) (GLenum light, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETMAPXVOESPROC) (GLenum target, GLenum query, GLfixed *v); +typedef void (APIENTRYP PFNGLGETMATERIALXOESPROC) (GLenum face, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLGETPIXELMAPXVPROC) (GLenum map, GLint size, GLfixed *values); +typedef void (APIENTRYP PFNGLGETTEXGENXVOESPROC) (GLenum coord, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETTEXLEVELPARAMETERXVOESPROC) (GLenum target, GLint level, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLINDEXXOESPROC) (GLfixed component); +typedef void (APIENTRYP PFNGLINDEXXVOESPROC) (const GLfixed *component); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXXOESPROC) (const GLfixed *m); +typedef void (APIENTRYP PFNGLMAP1XOESPROC) (GLenum target, GLfixed u1, GLfixed u2, GLint stride, GLint order, GLfixed points); +typedef void (APIENTRYP PFNGLMAP2XOESPROC) (GLenum target, GLfixed u1, GLfixed u2, GLint ustride, GLint uorder, GLfixed v1, GLfixed v2, GLint vstride, GLint vorder, GLfixed points); +typedef void (APIENTRYP PFNGLMAPGRID1XOESPROC) (GLint n, GLfixed u1, GLfixed u2); +typedef void (APIENTRYP PFNGLMAPGRID2XOESPROC) (GLint n, GLfixed u1, GLfixed u2, GLfixed v1, GLfixed v2); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXXOESPROC) (const GLfixed *m); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1XOESPROC) (GLenum texture, GLfixed s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1XVOESPROC) (GLenum texture, const GLfixed *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2XOESPROC) (GLenum texture, GLfixed s, GLfixed t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2XVOESPROC) (GLenum texture, const GLfixed *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3XOESPROC) (GLenum texture, GLfixed s, GLfixed t, GLfixed r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3XVOESPROC) (GLenum texture, const GLfixed *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4XVOESPROC) (GLenum texture, const GLfixed *coords); +typedef void (APIENTRYP PFNGLNORMAL3XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLPASSTHROUGHXOESPROC) (GLfixed token); +typedef void (APIENTRYP PFNGLPIXELMAPXPROC) (GLenum map, GLint size, const GLfixed *values); +typedef void (APIENTRYP PFNGLPIXELSTOREXPROC) (GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLPIXELTRANSFERXOESPROC) (GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLPIXELZOOMXOESPROC) (GLfixed xfactor, GLfixed yfactor); +typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESXOESPROC) (GLsizei n, const GLuint *textures, const GLfixed *priorities); +typedef void (APIENTRYP PFNGLRASTERPOS2XOESPROC) (GLfixed x, GLfixed y); +typedef void (APIENTRYP PFNGLRASTERPOS2XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLRASTERPOS3XOESPROC) (GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLRASTERPOS3XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLRASTERPOS4XOESPROC) (GLfixed x, GLfixed y, GLfixed z, GLfixed w); +typedef void (APIENTRYP PFNGLRASTERPOS4XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLRECTXOESPROC) (GLfixed x1, GLfixed y1, GLfixed x2, GLfixed y2); +typedef void (APIENTRYP PFNGLRECTXVOESPROC) (const GLfixed *v1, const GLfixed *v2); +typedef void (APIENTRYP PFNGLTEXCOORD1XOESPROC) (GLfixed s); +typedef void (APIENTRYP PFNGLTEXCOORD1XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLTEXCOORD2XOESPROC) (GLfixed s, GLfixed t); +typedef void (APIENTRYP PFNGLTEXCOORD2XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLTEXCOORD3XOESPROC) (GLfixed s, GLfixed t, GLfixed r); +typedef void (APIENTRYP PFNGLTEXCOORD3XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLTEXCOORD4XOESPROC) (GLfixed s, GLfixed t, GLfixed r, GLfixed q); +typedef void (APIENTRYP PFNGLTEXCOORD4XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLTEXGENXOESPROC) (GLenum coord, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLTEXGENXVOESPROC) (GLenum coord, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLVERTEX2XOESPROC) (GLfixed x); +typedef void (APIENTRYP PFNGLVERTEX2XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLVERTEX3XOESPROC) (GLfixed x, GLfixed y); +typedef void (APIENTRYP PFNGLVERTEX3XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLVERTEX4XOESPROC) (GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLVERTEX4XVOESPROC) (const GLfixed *coords); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glAlphaFuncxOES (GLenum func, GLfixed ref); +GLAPI void APIENTRY glClearColorxOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +GLAPI void APIENTRY glClearDepthxOES (GLfixed depth); +GLAPI void APIENTRY glClipPlanexOES (GLenum plane, const GLfixed *equation); +GLAPI void APIENTRY glColor4xOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +GLAPI void APIENTRY glDepthRangexOES (GLfixed n, GLfixed f); +GLAPI void APIENTRY glFogxOES (GLenum pname, GLfixed param); +GLAPI void APIENTRY glFogxvOES (GLenum pname, const GLfixed *param); +GLAPI void APIENTRY glFrustumxOES (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); +GLAPI void APIENTRY glGetClipPlanexOES (GLenum plane, GLfixed *equation); +GLAPI void APIENTRY glGetFixedvOES (GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetTexEnvxvOES (GLenum target, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetTexParameterxvOES (GLenum target, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glLightModelxOES (GLenum pname, GLfixed param); +GLAPI void APIENTRY glLightModelxvOES (GLenum pname, const GLfixed *param); +GLAPI void APIENTRY glLightxOES (GLenum light, GLenum pname, GLfixed param); +GLAPI void APIENTRY glLightxvOES (GLenum light, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glLineWidthxOES (GLfixed width); +GLAPI void APIENTRY glLoadMatrixxOES (const GLfixed *m); +GLAPI void APIENTRY glMaterialxOES (GLenum face, GLenum pname, GLfixed param); +GLAPI void APIENTRY glMaterialxvOES (GLenum face, GLenum pname, const GLfixed *param); +GLAPI void APIENTRY glMultMatrixxOES (const GLfixed *m); +GLAPI void APIENTRY glMultiTexCoord4xOES (GLenum texture, GLfixed s, GLfixed t, GLfixed r, GLfixed q); +GLAPI void APIENTRY glNormal3xOES (GLfixed nx, GLfixed ny, GLfixed nz); +GLAPI void APIENTRY glOrthoxOES (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); +GLAPI void APIENTRY glPointParameterxvOES (GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glPointSizexOES (GLfixed size); +GLAPI void APIENTRY glPolygonOffsetxOES (GLfixed factor, GLfixed units); +GLAPI void APIENTRY glRotatexOES (GLfixed angle, GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glSampleCoverageOES (GLfixed value, GLboolean invert); +GLAPI void APIENTRY glScalexOES (GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glTexEnvxOES (GLenum target, GLenum pname, GLfixed param); +GLAPI void APIENTRY glTexEnvxvOES (GLenum target, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glTexParameterxOES (GLenum target, GLenum pname, GLfixed param); +GLAPI void APIENTRY glTexParameterxvOES (GLenum target, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glTranslatexOES (GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glAccumxOES (GLenum op, GLfixed value); +GLAPI void APIENTRY glBitmapxOES (GLsizei width, GLsizei height, GLfixed xorig, GLfixed yorig, GLfixed xmove, GLfixed ymove, const GLubyte *bitmap); +GLAPI void APIENTRY glBlendColorxOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +GLAPI void APIENTRY glClearAccumxOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +GLAPI void APIENTRY glColor3xOES (GLfixed red, GLfixed green, GLfixed blue); +GLAPI void APIENTRY glColor3xvOES (const GLfixed *components); +GLAPI void APIENTRY glColor4xvOES (const GLfixed *components); +GLAPI void APIENTRY glConvolutionParameterxOES (GLenum target, GLenum pname, GLfixed param); +GLAPI void APIENTRY glConvolutionParameterxvOES (GLenum target, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glEvalCoord1xOES (GLfixed u); +GLAPI void APIENTRY glEvalCoord1xvOES (const GLfixed *coords); +GLAPI void APIENTRY glEvalCoord2xOES (GLfixed u, GLfixed v); +GLAPI void APIENTRY glEvalCoord2xvOES (const GLfixed *coords); +GLAPI void APIENTRY glFeedbackBufferxOES (GLsizei n, GLenum type, const GLfixed *buffer); +GLAPI void APIENTRY glGetConvolutionParameterxvOES (GLenum target, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetHistogramParameterxvOES (GLenum target, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetLightxOES (GLenum light, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetMapxvOES (GLenum target, GLenum query, GLfixed *v); +GLAPI void APIENTRY glGetMaterialxOES (GLenum face, GLenum pname, GLfixed param); +GLAPI void APIENTRY glGetPixelMapxv (GLenum map, GLint size, GLfixed *values); +GLAPI void APIENTRY glGetTexGenxvOES (GLenum coord, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetTexLevelParameterxvOES (GLenum target, GLint level, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glIndexxOES (GLfixed component); +GLAPI void APIENTRY glIndexxvOES (const GLfixed *component); +GLAPI void APIENTRY glLoadTransposeMatrixxOES (const GLfixed *m); +GLAPI void APIENTRY glMap1xOES (GLenum target, GLfixed u1, GLfixed u2, GLint stride, GLint order, GLfixed points); +GLAPI void APIENTRY glMap2xOES (GLenum target, GLfixed u1, GLfixed u2, GLint ustride, GLint uorder, GLfixed v1, GLfixed v2, GLint vstride, GLint vorder, GLfixed points); +GLAPI void APIENTRY glMapGrid1xOES (GLint n, GLfixed u1, GLfixed u2); +GLAPI void APIENTRY glMapGrid2xOES (GLint n, GLfixed u1, GLfixed u2, GLfixed v1, GLfixed v2); +GLAPI void APIENTRY glMultTransposeMatrixxOES (const GLfixed *m); +GLAPI void APIENTRY glMultiTexCoord1xOES (GLenum texture, GLfixed s); +GLAPI void APIENTRY glMultiTexCoord1xvOES (GLenum texture, const GLfixed *coords); +GLAPI void APIENTRY glMultiTexCoord2xOES (GLenum texture, GLfixed s, GLfixed t); +GLAPI void APIENTRY glMultiTexCoord2xvOES (GLenum texture, const GLfixed *coords); +GLAPI void APIENTRY glMultiTexCoord3xOES (GLenum texture, GLfixed s, GLfixed t, GLfixed r); +GLAPI void APIENTRY glMultiTexCoord3xvOES (GLenum texture, const GLfixed *coords); +GLAPI void APIENTRY glMultiTexCoord4xvOES (GLenum texture, const GLfixed *coords); +GLAPI void APIENTRY glNormal3xvOES (const GLfixed *coords); +GLAPI void APIENTRY glPassThroughxOES (GLfixed token); +GLAPI void APIENTRY glPixelMapx (GLenum map, GLint size, const GLfixed *values); +GLAPI void APIENTRY glPixelStorex (GLenum pname, GLfixed param); +GLAPI void APIENTRY glPixelTransferxOES (GLenum pname, GLfixed param); +GLAPI void APIENTRY glPixelZoomxOES (GLfixed xfactor, GLfixed yfactor); +GLAPI void APIENTRY glPrioritizeTexturesxOES (GLsizei n, const GLuint *textures, const GLfixed *priorities); +GLAPI void APIENTRY glRasterPos2xOES (GLfixed x, GLfixed y); +GLAPI void APIENTRY glRasterPos2xvOES (const GLfixed *coords); +GLAPI void APIENTRY glRasterPos3xOES (GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glRasterPos3xvOES (const GLfixed *coords); +GLAPI void APIENTRY glRasterPos4xOES (GLfixed x, GLfixed y, GLfixed z, GLfixed w); +GLAPI void APIENTRY glRasterPos4xvOES (const GLfixed *coords); +GLAPI void APIENTRY glRectxOES (GLfixed x1, GLfixed y1, GLfixed x2, GLfixed y2); +GLAPI void APIENTRY glRectxvOES (const GLfixed *v1, const GLfixed *v2); +GLAPI void APIENTRY glTexCoord1xOES (GLfixed s); +GLAPI void APIENTRY glTexCoord1xvOES (const GLfixed *coords); +GLAPI void APIENTRY glTexCoord2xOES (GLfixed s, GLfixed t); +GLAPI void APIENTRY glTexCoord2xvOES (const GLfixed *coords); +GLAPI void APIENTRY glTexCoord3xOES (GLfixed s, GLfixed t, GLfixed r); +GLAPI void APIENTRY glTexCoord3xvOES (const GLfixed *coords); +GLAPI void APIENTRY glTexCoord4xOES (GLfixed s, GLfixed t, GLfixed r, GLfixed q); +GLAPI void APIENTRY glTexCoord4xvOES (const GLfixed *coords); +GLAPI void APIENTRY glTexGenxOES (GLenum coord, GLenum pname, GLfixed param); +GLAPI void APIENTRY glTexGenxvOES (GLenum coord, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glVertex2xOES (GLfixed x); +GLAPI void APIENTRY glVertex2xvOES (const GLfixed *coords); +GLAPI void APIENTRY glVertex3xOES (GLfixed x, GLfixed y); +GLAPI void APIENTRY glVertex3xvOES (const GLfixed *coords); +GLAPI void APIENTRY glVertex4xOES (GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glVertex4xvOES (const GLfixed *coords); +#endif +#endif /* GL_OES_fixed_point */ + +#ifndef GL_OES_query_matrix +#define GL_OES_query_matrix 1 +typedef GLbitfield (APIENTRYP PFNGLQUERYMATRIXXOESPROC) (GLfixed *mantissa, GLint *exponent); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLbitfield APIENTRY glQueryMatrixxOES (GLfixed *mantissa, GLint *exponent); +#endif +#endif /* GL_OES_query_matrix */ + +#ifndef GL_OES_read_format +#define GL_OES_read_format 1 +#define GL_IMPLEMENTATION_COLOR_READ_TYPE_OES 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES 0x8B9B +#endif /* GL_OES_read_format */ + +#ifndef GL_OES_single_precision +#define GL_OES_single_precision 1 +typedef void (APIENTRYP PFNGLCLEARDEPTHFOESPROC) (GLclampf depth); +typedef void (APIENTRYP PFNGLCLIPPLANEFOESPROC) (GLenum plane, const GLfloat *equation); +typedef void (APIENTRYP PFNGLDEPTHRANGEFOESPROC) (GLclampf n, GLclampf f); +typedef void (APIENTRYP PFNGLFRUSTUMFOESPROC) (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +typedef void (APIENTRYP PFNGLGETCLIPPLANEFOESPROC) (GLenum plane, GLfloat *equation); +typedef void (APIENTRYP PFNGLORTHOFOESPROC) (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClearDepthfOES (GLclampf depth); +GLAPI void APIENTRY glClipPlanefOES (GLenum plane, const GLfloat *equation); +GLAPI void APIENTRY glDepthRangefOES (GLclampf n, GLclampf f); +GLAPI void APIENTRY glFrustumfOES (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +GLAPI void APIENTRY glGetClipPlanefOES (GLenum plane, GLfloat *equation); +GLAPI void APIENTRY glOrthofOES (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +#endif +#endif /* GL_OES_single_precision */ + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 +#define GL_MULTISAMPLE_3DFX 0x86B2 +#define GL_SAMPLE_BUFFERS_3DFX 0x86B3 +#define GL_SAMPLES_3DFX 0x86B4 +#define GL_MULTISAMPLE_BIT_3DFX 0x20000000 +#endif /* GL_3DFX_multisample */ + +#ifndef GL_3DFX_tbuffer +#define GL_3DFX_tbuffer 1 +typedef void (APIENTRYP PFNGLTBUFFERMASK3DFXPROC) (GLuint mask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTbufferMask3DFX (GLuint mask); +#endif +#endif /* GL_3DFX_tbuffer */ + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif /* GL_3DFX_texture_compression_FXT1 */ + +#ifndef GL_AMD_blend_minmax_factor +#define GL_AMD_blend_minmax_factor 1 +#define GL_FACTOR_MIN_AMD 0x901C +#define GL_FACTOR_MAX_AMD 0x901D +#endif /* GL_AMD_blend_minmax_factor */ + +#ifndef GL_AMD_conservative_depth +#define GL_AMD_conservative_depth 1 +#endif /* GL_AMD_conservative_depth */ + +#ifndef GL_AMD_debug_output +#define GL_AMD_debug_output 1 +typedef void (APIENTRY *GLDEBUGPROCAMD)(GLuint id,GLenum category,GLenum severity,GLsizei length,const GLchar *message,void *userParam); +#define GL_MAX_DEBUG_MESSAGE_LENGTH_AMD 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES_AMD 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_AMD 0x9145 +#define GL_DEBUG_SEVERITY_HIGH_AMD 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_AMD 0x9147 +#define GL_DEBUG_SEVERITY_LOW_AMD 0x9148 +#define GL_DEBUG_CATEGORY_API_ERROR_AMD 0x9149 +#define GL_DEBUG_CATEGORY_WINDOW_SYSTEM_AMD 0x914A +#define GL_DEBUG_CATEGORY_DEPRECATION_AMD 0x914B +#define GL_DEBUG_CATEGORY_UNDEFINED_BEHAVIOR_AMD 0x914C +#define GL_DEBUG_CATEGORY_PERFORMANCE_AMD 0x914D +#define GL_DEBUG_CATEGORY_SHADER_COMPILER_AMD 0x914E +#define GL_DEBUG_CATEGORY_APPLICATION_AMD 0x914F +#define GL_DEBUG_CATEGORY_OTHER_AMD 0x9150 +typedef void (APIENTRYP PFNGLDEBUGMESSAGEENABLEAMDPROC) (GLenum category, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTAMDPROC) (GLenum category, GLenum severity, GLuint id, GLsizei length, const GLchar *buf); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKAMDPROC) (GLDEBUGPROCAMD callback, void *userParam); +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGAMDPROC) (GLuint count, GLsizei bufsize, GLenum *categories, GLuint *severities, GLuint *ids, GLsizei *lengths, GLchar *message); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDebugMessageEnableAMD (GLenum category, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI void APIENTRY glDebugMessageInsertAMD (GLenum category, GLenum severity, GLuint id, GLsizei length, const GLchar *buf); +GLAPI void APIENTRY glDebugMessageCallbackAMD (GLDEBUGPROCAMD callback, void *userParam); +GLAPI GLuint APIENTRY glGetDebugMessageLogAMD (GLuint count, GLsizei bufsize, GLenum *categories, GLuint *severities, GLuint *ids, GLsizei *lengths, GLchar *message); +#endif +#endif /* GL_AMD_debug_output */ + +#ifndef GL_AMD_depth_clamp_separate +#define GL_AMD_depth_clamp_separate 1 +#define GL_DEPTH_CLAMP_NEAR_AMD 0x901E +#define GL_DEPTH_CLAMP_FAR_AMD 0x901F +#endif /* GL_AMD_depth_clamp_separate */ + +#ifndef GL_AMD_draw_buffers_blend +#define GL_AMD_draw_buffers_blend 1 +typedef void (APIENTRYP PFNGLBLENDFUNCINDEXEDAMDPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINDEXEDAMDPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONINDEXEDAMDPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEINDEXEDAMDPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncIndexedAMD (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparateIndexedAMD (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +GLAPI void APIENTRY glBlendEquationIndexedAMD (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparateIndexedAMD (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +#endif +#endif /* GL_AMD_draw_buffers_blend */ + +#ifndef GL_AMD_interleaved_elements +#define GL_AMD_interleaved_elements 1 +#define GL_VERTEX_ELEMENT_SWIZZLE_AMD 0x91A4 +#define GL_VERTEX_ID_SWIZZLE_AMD 0x91A5 +typedef void (APIENTRYP PFNGLVERTEXATTRIBPARAMETERIAMDPROC) (GLuint index, GLenum pname, GLint param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribParameteriAMD (GLuint index, GLenum pname, GLint param); +#endif +#endif /* GL_AMD_interleaved_elements */ + +#ifndef GL_AMD_multi_draw_indirect +#define GL_AMD_multi_draw_indirect 1 +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTAMDPROC) (GLenum mode, const void *indirect, GLsizei primcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTAMDPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei primcount, GLsizei stride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysIndirectAMD (GLenum mode, const void *indirect, GLsizei primcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawElementsIndirectAMD (GLenum mode, GLenum type, const void *indirect, GLsizei primcount, GLsizei stride); +#endif +#endif /* GL_AMD_multi_draw_indirect */ + +#ifndef GL_AMD_name_gen_delete +#define GL_AMD_name_gen_delete 1 +#define GL_DATA_BUFFER_AMD 0x9151 +#define GL_PERFORMANCE_MONITOR_AMD 0x9152 +#define GL_QUERY_OBJECT_AMD 0x9153 +#define GL_VERTEX_ARRAY_OBJECT_AMD 0x9154 +#define GL_SAMPLER_OBJECT_AMD 0x9155 +typedef void (APIENTRYP PFNGLGENNAMESAMDPROC) (GLenum identifier, GLuint num, GLuint *names); +typedef void (APIENTRYP PFNGLDELETENAMESAMDPROC) (GLenum identifier, GLuint num, const GLuint *names); +typedef GLboolean (APIENTRYP PFNGLISNAMEAMDPROC) (GLenum identifier, GLuint name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenNamesAMD (GLenum identifier, GLuint num, GLuint *names); +GLAPI void APIENTRY glDeleteNamesAMD (GLenum identifier, GLuint num, const GLuint *names); +GLAPI GLboolean APIENTRY glIsNameAMD (GLenum identifier, GLuint name); +#endif +#endif /* GL_AMD_name_gen_delete */ + +#ifndef GL_AMD_occlusion_query_event +#define GL_AMD_occlusion_query_event 1 +#define GL_OCCLUSION_QUERY_EVENT_MASK_AMD 0x874F +#define GL_QUERY_DEPTH_PASS_EVENT_BIT_AMD 0x00000001 +#define GL_QUERY_DEPTH_FAIL_EVENT_BIT_AMD 0x00000002 +#define GL_QUERY_STENCIL_FAIL_EVENT_BIT_AMD 0x00000004 +#define GL_QUERY_DEPTH_BOUNDS_FAIL_EVENT_BIT_AMD 0x00000008 +#define GL_QUERY_ALL_EVENT_BITS_AMD 0xFFFFFFFF +typedef void (APIENTRYP PFNGLQUERYOBJECTPARAMETERUIAMDPROC) (GLenum target, GLuint id, GLenum pname, GLuint param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glQueryObjectParameteruiAMD (GLenum target, GLuint id, GLenum pname, GLuint param); +#endif +#endif /* GL_AMD_occlusion_query_event */ + +#ifndef GL_AMD_performance_monitor +#define GL_AMD_performance_monitor 1 +#define GL_COUNTER_TYPE_AMD 0x8BC0 +#define GL_COUNTER_RANGE_AMD 0x8BC1 +#define GL_UNSIGNED_INT64_AMD 0x8BC2 +#define GL_PERCENTAGE_AMD 0x8BC3 +#define GL_PERFMON_RESULT_AVAILABLE_AMD 0x8BC4 +#define GL_PERFMON_RESULT_SIZE_AMD 0x8BC5 +#define GL_PERFMON_RESULT_AMD 0x8BC6 +typedef void (APIENTRYP PFNGLGETPERFMONITORGROUPSAMDPROC) (GLint *numGroups, GLsizei groupsSize, GLuint *groups); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERSAMDPROC) (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters); +typedef void (APIENTRYP PFNGLGETPERFMONITORGROUPSTRINGAMDPROC) (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERSTRINGAMDPROC) (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERINFOAMDPROC) (GLuint group, GLuint counter, GLenum pname, void *data); +typedef void (APIENTRYP PFNGLGENPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors); +typedef void (APIENTRYP PFNGLDELETEPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors); +typedef void (APIENTRYP PFNGLSELECTPERFMONITORCOUNTERSAMDPROC) (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *counterList); +typedef void (APIENTRYP PFNGLBEGINPERFMONITORAMDPROC) (GLuint monitor); +typedef void (APIENTRYP PFNGLENDPERFMONITORAMDPROC) (GLuint monitor); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERDATAAMDPROC) (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetPerfMonitorGroupsAMD (GLint *numGroups, GLsizei groupsSize, GLuint *groups); +GLAPI void APIENTRY glGetPerfMonitorCountersAMD (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters); +GLAPI void APIENTRY glGetPerfMonitorGroupStringAMD (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString); +GLAPI void APIENTRY glGetPerfMonitorCounterStringAMD (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString); +GLAPI void APIENTRY glGetPerfMonitorCounterInfoAMD (GLuint group, GLuint counter, GLenum pname, void *data); +GLAPI void APIENTRY glGenPerfMonitorsAMD (GLsizei n, GLuint *monitors); +GLAPI void APIENTRY glDeletePerfMonitorsAMD (GLsizei n, GLuint *monitors); +GLAPI void APIENTRY glSelectPerfMonitorCountersAMD (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *counterList); +GLAPI void APIENTRY glBeginPerfMonitorAMD (GLuint monitor); +GLAPI void APIENTRY glEndPerfMonitorAMD (GLuint monitor); +GLAPI void APIENTRY glGetPerfMonitorCounterDataAMD (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten); +#endif +#endif /* GL_AMD_performance_monitor */ + +#ifndef GL_AMD_pinned_memory +#define GL_AMD_pinned_memory 1 +#define GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD 0x9160 +#endif /* GL_AMD_pinned_memory */ + +#ifndef GL_AMD_query_buffer_object +#define GL_AMD_query_buffer_object 1 +#define GL_QUERY_BUFFER_AMD 0x9192 +#define GL_QUERY_BUFFER_BINDING_AMD 0x9193 +#define GL_QUERY_RESULT_NO_WAIT_AMD 0x9194 +#endif /* GL_AMD_query_buffer_object */ + +#ifndef GL_AMD_sample_positions +#define GL_AMD_sample_positions 1 +#define GL_SUBSAMPLE_DISTANCE_AMD 0x883F +typedef void (APIENTRYP PFNGLSETMULTISAMPLEFVAMDPROC) (GLenum pname, GLuint index, const GLfloat *val); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSetMultisamplefvAMD (GLenum pname, GLuint index, const GLfloat *val); +#endif +#endif /* GL_AMD_sample_positions */ + +#ifndef GL_AMD_seamless_cubemap_per_texture +#define GL_AMD_seamless_cubemap_per_texture 1 +#endif /* GL_AMD_seamless_cubemap_per_texture */ + +#ifndef GL_AMD_shader_atomic_counter_ops +#define GL_AMD_shader_atomic_counter_ops 1 +#endif /* GL_AMD_shader_atomic_counter_ops */ + +#ifndef GL_AMD_shader_stencil_export +#define GL_AMD_shader_stencil_export 1 +#endif /* GL_AMD_shader_stencil_export */ + +#ifndef GL_AMD_shader_trinary_minmax +#define GL_AMD_shader_trinary_minmax 1 +#endif /* GL_AMD_shader_trinary_minmax */ + +#ifndef GL_AMD_sparse_texture +#define GL_AMD_sparse_texture 1 +#define GL_VIRTUAL_PAGE_SIZE_X_AMD 0x9195 +#define GL_VIRTUAL_PAGE_SIZE_Y_AMD 0x9196 +#define GL_VIRTUAL_PAGE_SIZE_Z_AMD 0x9197 +#define GL_MAX_SPARSE_TEXTURE_SIZE_AMD 0x9198 +#define GL_MAX_SPARSE_3D_TEXTURE_SIZE_AMD 0x9199 +#define GL_MAX_SPARSE_ARRAY_TEXTURE_LAYERS 0x919A +#define GL_MIN_SPARSE_LEVEL_AMD 0x919B +#define GL_MIN_LOD_WARNING_AMD 0x919C +#define GL_TEXTURE_STORAGE_SPARSE_BIT_AMD 0x00000001 +typedef void (APIENTRYP PFNGLTEXSTORAGESPARSEAMDPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); +typedef void (APIENTRYP PFNGLTEXTURESTORAGESPARSEAMDPROC) (GLuint texture, GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexStorageSparseAMD (GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); +GLAPI void APIENTRY glTextureStorageSparseAMD (GLuint texture, GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); +#endif +#endif /* GL_AMD_sparse_texture */ + +#ifndef GL_AMD_stencil_operation_extended +#define GL_AMD_stencil_operation_extended 1 +#define GL_SET_AMD 0x874A +#define GL_REPLACE_VALUE_AMD 0x874B +#define GL_STENCIL_OP_VALUE_AMD 0x874C +#define GL_STENCIL_BACK_OP_VALUE_AMD 0x874D +typedef void (APIENTRYP PFNGLSTENCILOPVALUEAMDPROC) (GLenum face, GLuint value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilOpValueAMD (GLenum face, GLuint value); +#endif +#endif /* GL_AMD_stencil_operation_extended */ + +#ifndef GL_AMD_texture_texture4 +#define GL_AMD_texture_texture4 1 +#endif /* GL_AMD_texture_texture4 */ + +#ifndef GL_AMD_transform_feedback3_lines_triangles +#define GL_AMD_transform_feedback3_lines_triangles 1 +#endif /* GL_AMD_transform_feedback3_lines_triangles */ + +#ifndef GL_AMD_vertex_shader_layer +#define GL_AMD_vertex_shader_layer 1 +#endif /* GL_AMD_vertex_shader_layer */ + +#ifndef GL_AMD_vertex_shader_tessellator +#define GL_AMD_vertex_shader_tessellator 1 +#define GL_SAMPLER_BUFFER_AMD 0x9001 +#define GL_INT_SAMPLER_BUFFER_AMD 0x9002 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER_AMD 0x9003 +#define GL_TESSELLATION_MODE_AMD 0x9004 +#define GL_TESSELLATION_FACTOR_AMD 0x9005 +#define GL_DISCRETE_AMD 0x9006 +#define GL_CONTINUOUS_AMD 0x9007 +typedef void (APIENTRYP PFNGLTESSELLATIONFACTORAMDPROC) (GLfloat factor); +typedef void (APIENTRYP PFNGLTESSELLATIONMODEAMDPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTessellationFactorAMD (GLfloat factor); +GLAPI void APIENTRY glTessellationModeAMD (GLenum mode); +#endif +#endif /* GL_AMD_vertex_shader_tessellator */ + +#ifndef GL_AMD_vertex_shader_viewport_index +#define GL_AMD_vertex_shader_viewport_index 1 +#endif /* GL_AMD_vertex_shader_viewport_index */ + +#ifndef GL_APPLE_aux_depth_stencil +#define GL_APPLE_aux_depth_stencil 1 +#define GL_AUX_DEPTH_STENCIL_APPLE 0x8A14 +#endif /* GL_APPLE_aux_depth_stencil */ + +#ifndef GL_APPLE_client_storage +#define GL_APPLE_client_storage 1 +#define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2 +#endif /* GL_APPLE_client_storage */ + +#ifndef GL_APPLE_element_array +#define GL_APPLE_element_array 1 +#define GL_ELEMENT_ARRAY_APPLE 0x8A0C +#define GL_ELEMENT_ARRAY_TYPE_APPLE 0x8A0D +#define GL_ELEMENT_ARRAY_POINTER_APPLE 0x8A0E +typedef void (APIENTRYP PFNGLELEMENTPOINTERAPPLEPROC) (GLenum type, const void *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerAPPLE (GLenum type, const void *pointer); +GLAPI void APIENTRY glDrawElementArrayAPPLE (GLenum mode, GLint first, GLsizei count); +GLAPI void APIENTRY glDrawRangeElementArrayAPPLE (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +GLAPI void APIENTRY glMultiDrawElementArrayAPPLE (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +GLAPI void APIENTRY glMultiDrawRangeElementArrayAPPLE (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); +#endif +#endif /* GL_APPLE_element_array */ + +#ifndef GL_APPLE_fence +#define GL_APPLE_fence 1 +#define GL_DRAW_PIXELS_APPLE 0x8A0A +#define GL_FENCE_APPLE 0x8A0B +typedef void (APIENTRYP PFNGLGENFENCESAPPLEPROC) (GLsizei n, GLuint *fences); +typedef void (APIENTRYP PFNGLDELETEFENCESAPPLEPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLSETFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLISFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCEAPPLEPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLFINISHFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTOBJECTAPPLEPROC) (GLenum object, GLuint name); +typedef void (APIENTRYP PFNGLFINISHOBJECTAPPLEPROC) (GLenum object, GLint name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenFencesAPPLE (GLsizei n, GLuint *fences); +GLAPI void APIENTRY glDeleteFencesAPPLE (GLsizei n, const GLuint *fences); +GLAPI void APIENTRY glSetFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glIsFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glTestFenceAPPLE (GLuint fence); +GLAPI void APIENTRY glFinishFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glTestObjectAPPLE (GLenum object, GLuint name); +GLAPI void APIENTRY glFinishObjectAPPLE (GLenum object, GLint name); +#endif +#endif /* GL_APPLE_fence */ + +#ifndef GL_APPLE_float_pixels +#define GL_APPLE_float_pixels 1 +#define GL_HALF_APPLE 0x140B +#define GL_RGBA_FLOAT32_APPLE 0x8814 +#define GL_RGB_FLOAT32_APPLE 0x8815 +#define GL_ALPHA_FLOAT32_APPLE 0x8816 +#define GL_INTENSITY_FLOAT32_APPLE 0x8817 +#define GL_LUMINANCE_FLOAT32_APPLE 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_APPLE 0x8819 +#define GL_RGBA_FLOAT16_APPLE 0x881A +#define GL_RGB_FLOAT16_APPLE 0x881B +#define GL_ALPHA_FLOAT16_APPLE 0x881C +#define GL_INTENSITY_FLOAT16_APPLE 0x881D +#define GL_LUMINANCE_FLOAT16_APPLE 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_APPLE 0x881F +#define GL_COLOR_FLOAT_APPLE 0x8A0F +#endif /* GL_APPLE_float_pixels */ + +#ifndef GL_APPLE_flush_buffer_range +#define GL_APPLE_flush_buffer_range 1 +#define GL_BUFFER_SERIALIZED_MODIFY_APPLE 0x8A12 +#define GL_BUFFER_FLUSHING_UNMAP_APPLE 0x8A13 +typedef void (APIENTRYP PFNGLBUFFERPARAMETERIAPPLEPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEAPPLEPROC) (GLenum target, GLintptr offset, GLsizeiptr size); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferParameteriAPPLE (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glFlushMappedBufferRangeAPPLE (GLenum target, GLintptr offset, GLsizeiptr size); +#endif +#endif /* GL_APPLE_flush_buffer_range */ + +#ifndef GL_APPLE_object_purgeable +#define GL_APPLE_object_purgeable 1 +#define GL_BUFFER_OBJECT_APPLE 0x85B3 +#define GL_RELEASED_APPLE 0x8A19 +#define GL_VOLATILE_APPLE 0x8A1A +#define GL_RETAINED_APPLE 0x8A1B +#define GL_UNDEFINED_APPLE 0x8A1C +#define GL_PURGEABLE_APPLE 0x8A1D +typedef GLenum (APIENTRYP PFNGLOBJECTPURGEABLEAPPLEPROC) (GLenum objectType, GLuint name, GLenum option); +typedef GLenum (APIENTRYP PFNGLOBJECTUNPURGEABLEAPPLEPROC) (GLenum objectType, GLuint name, GLenum option); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVAPPLEPROC) (GLenum objectType, GLuint name, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLenum APIENTRY glObjectPurgeableAPPLE (GLenum objectType, GLuint name, GLenum option); +GLAPI GLenum APIENTRY glObjectUnpurgeableAPPLE (GLenum objectType, GLuint name, GLenum option); +GLAPI void APIENTRY glGetObjectParameterivAPPLE (GLenum objectType, GLuint name, GLenum pname, GLint *params); +#endif +#endif /* GL_APPLE_object_purgeable */ + +#ifndef GL_APPLE_rgb_422 +#define GL_APPLE_rgb_422 1 +#define GL_RGB_422_APPLE 0x8A1F +#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB +#define GL_RGB_RAW_422_APPLE 0x8A51 +#endif /* GL_APPLE_rgb_422 */ + +#ifndef GL_APPLE_row_bytes +#define GL_APPLE_row_bytes 1 +#define GL_PACK_ROW_BYTES_APPLE 0x8A15 +#define GL_UNPACK_ROW_BYTES_APPLE 0x8A16 +#endif /* GL_APPLE_row_bytes */ + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif /* GL_APPLE_specular_vector */ + +#ifndef GL_APPLE_texture_range +#define GL_APPLE_texture_range 1 +#define GL_TEXTURE_RANGE_LENGTH_APPLE 0x85B7 +#define GL_TEXTURE_RANGE_POINTER_APPLE 0x85B8 +#define GL_TEXTURE_STORAGE_HINT_APPLE 0x85BC +#define GL_STORAGE_PRIVATE_APPLE 0x85BD +#define GL_STORAGE_CACHED_APPLE 0x85BE +#define GL_STORAGE_SHARED_APPLE 0x85BF +typedef void (APIENTRYP PFNGLTEXTURERANGEAPPLEPROC) (GLenum target, GLsizei length, const void *pointer); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERPOINTERVAPPLEPROC) (GLenum target, GLenum pname, void **params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureRangeAPPLE (GLenum target, GLsizei length, const void *pointer); +GLAPI void APIENTRY glGetTexParameterPointervAPPLE (GLenum target, GLenum pname, void **params); +#endif +#endif /* GL_APPLE_texture_range */ + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif /* GL_APPLE_transform_hint */ + +#ifndef GL_APPLE_vertex_array_object +#define GL_APPLE_vertex_array_object 1 +#define GL_VERTEX_ARRAY_BINDING_APPLE 0x85B5 +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYAPPLEPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSAPPLEPROC) (GLsizei n, GLuint *arrays); +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYAPPLEPROC) (GLuint array); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindVertexArrayAPPLE (GLuint array); +GLAPI void APIENTRY glDeleteVertexArraysAPPLE (GLsizei n, const GLuint *arrays); +GLAPI void APIENTRY glGenVertexArraysAPPLE (GLsizei n, GLuint *arrays); +GLAPI GLboolean APIENTRY glIsVertexArrayAPPLE (GLuint array); +#endif +#endif /* GL_APPLE_vertex_array_object */ + +#ifndef GL_APPLE_vertex_array_range +#define GL_APPLE_vertex_array_range 1 +#define GL_VERTEX_ARRAY_RANGE_APPLE 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_APPLE 0x851E +#define GL_VERTEX_ARRAY_STORAGE_HINT_APPLE 0x851F +#define GL_VERTEX_ARRAY_RANGE_POINTER_APPLE 0x8521 +#define GL_STORAGE_CLIENT_APPLE 0x85B4 +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, void *pointer); +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, void *pointer); +typedef void (APIENTRYP PFNGLVERTEXARRAYPARAMETERIAPPLEPROC) (GLenum pname, GLint param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexArrayRangeAPPLE (GLsizei length, void *pointer); +GLAPI void APIENTRY glFlushVertexArrayRangeAPPLE (GLsizei length, void *pointer); +GLAPI void APIENTRY glVertexArrayParameteriAPPLE (GLenum pname, GLint param); +#endif +#endif /* GL_APPLE_vertex_array_range */ + +#ifndef GL_APPLE_vertex_program_evaluators +#define GL_APPLE_vertex_program_evaluators 1 +#define GL_VERTEX_ATTRIB_MAP1_APPLE 0x8A00 +#define GL_VERTEX_ATTRIB_MAP2_APPLE 0x8A01 +#define GL_VERTEX_ATTRIB_MAP1_SIZE_APPLE 0x8A02 +#define GL_VERTEX_ATTRIB_MAP1_COEFF_APPLE 0x8A03 +#define GL_VERTEX_ATTRIB_MAP1_ORDER_APPLE 0x8A04 +#define GL_VERTEX_ATTRIB_MAP1_DOMAIN_APPLE 0x8A05 +#define GL_VERTEX_ATTRIB_MAP2_SIZE_APPLE 0x8A06 +#define GL_VERTEX_ATTRIB_MAP2_COEFF_APPLE 0x8A07 +#define GL_VERTEX_ATTRIB_MAP2_ORDER_APPLE 0x8A08 +#define GL_VERTEX_ATTRIB_MAP2_DOMAIN_APPLE 0x8A09 +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBAPPLEPROC) (GLuint index, GLenum pname); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBAPPLEPROC) (GLuint index, GLenum pname); +typedef GLboolean (APIENTRYP PFNGLISVERTEXATTRIBENABLEDAPPLEPROC) (GLuint index, GLenum pname); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB1DAPPLEPROC) (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB1FAPPLEPROC) (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB2DAPPLEPROC) (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB2FAPPLEPROC) (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glEnableVertexAttribAPPLE (GLuint index, GLenum pname); +GLAPI void APIENTRY glDisableVertexAttribAPPLE (GLuint index, GLenum pname); +GLAPI GLboolean APIENTRY glIsVertexAttribEnabledAPPLE (GLuint index, GLenum pname); +GLAPI void APIENTRY glMapVertexAttrib1dAPPLE (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +GLAPI void APIENTRY glMapVertexAttrib1fAPPLE (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +GLAPI void APIENTRY glMapVertexAttrib2dAPPLE (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +GLAPI void APIENTRY glMapVertexAttrib2fAPPLE (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +#endif +#endif /* GL_APPLE_vertex_program_evaluators */ + +#ifndef GL_APPLE_ycbcr_422 +#define GL_APPLE_ycbcr_422 1 +#define GL_YCBCR_422_APPLE 0x85B9 +#endif /* GL_APPLE_ycbcr_422 */ + +#ifndef GL_ATI_draw_buffers +#define GL_ATI_draw_buffers 1 +#define GL_MAX_DRAW_BUFFERS_ATI 0x8824 +#define GL_DRAW_BUFFER0_ATI 0x8825 +#define GL_DRAW_BUFFER1_ATI 0x8826 +#define GL_DRAW_BUFFER2_ATI 0x8827 +#define GL_DRAW_BUFFER3_ATI 0x8828 +#define GL_DRAW_BUFFER4_ATI 0x8829 +#define GL_DRAW_BUFFER5_ATI 0x882A +#define GL_DRAW_BUFFER6_ATI 0x882B +#define GL_DRAW_BUFFER7_ATI 0x882C +#define GL_DRAW_BUFFER8_ATI 0x882D +#define GL_DRAW_BUFFER9_ATI 0x882E +#define GL_DRAW_BUFFER10_ATI 0x882F +#define GL_DRAW_BUFFER11_ATI 0x8830 +#define GL_DRAW_BUFFER12_ATI 0x8831 +#define GL_DRAW_BUFFER13_ATI 0x8832 +#define GL_DRAW_BUFFER14_ATI 0x8833 +#define GL_DRAW_BUFFER15_ATI 0x8834 +typedef void (APIENTRYP PFNGLDRAWBUFFERSATIPROC) (GLsizei n, const GLenum *bufs); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersATI (GLsizei n, const GLenum *bufs); +#endif +#endif /* GL_ATI_draw_buffers */ + +#ifndef GL_ATI_element_array +#define GL_ATI_element_array 1 +#define GL_ELEMENT_ARRAY_ATI 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_ATI 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_ATI 0x876A +typedef void (APIENTRYP PFNGLELEMENTPOINTERATIPROC) (GLenum type, const void *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYATIPROC) (GLenum mode, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYATIPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerATI (GLenum type, const void *pointer); +GLAPI void APIENTRY glDrawElementArrayATI (GLenum mode, GLsizei count); +GLAPI void APIENTRY glDrawRangeElementArrayATI (GLenum mode, GLuint start, GLuint end, GLsizei count); +#endif +#endif /* GL_ATI_element_array */ + +#ifndef GL_ATI_envmap_bumpmap +#define GL_ATI_envmap_bumpmap 1 +#define GL_BUMP_ROT_MATRIX_ATI 0x8775 +#define GL_BUMP_ROT_MATRIX_SIZE_ATI 0x8776 +#define GL_BUMP_NUM_TEX_UNITS_ATI 0x8777 +#define GL_BUMP_TEX_UNITS_ATI 0x8778 +#define GL_DUDV_ATI 0x8779 +#define GL_DU8DV8_ATI 0x877A +#define GL_BUMP_ENVMAP_ATI 0x877B +#define GL_BUMP_TARGET_ATI 0x877C +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERIVATIPROC) (GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERFVATIPROC) (GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERIVATIPROC) (GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERFVATIPROC) (GLenum pname, GLfloat *param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBumpParameterivATI (GLenum pname, const GLint *param); +GLAPI void APIENTRY glTexBumpParameterfvATI (GLenum pname, const GLfloat *param); +GLAPI void APIENTRY glGetTexBumpParameterivATI (GLenum pname, GLint *param); +GLAPI void APIENTRY glGetTexBumpParameterfvATI (GLenum pname, GLfloat *param); +#endif +#endif /* GL_ATI_envmap_bumpmap */ + +#ifndef GL_ATI_fragment_shader +#define GL_ATI_fragment_shader 1 +#define GL_FRAGMENT_SHADER_ATI 0x8920 +#define GL_REG_0_ATI 0x8921 +#define GL_REG_1_ATI 0x8922 +#define GL_REG_2_ATI 0x8923 +#define GL_REG_3_ATI 0x8924 +#define GL_REG_4_ATI 0x8925 +#define GL_REG_5_ATI 0x8926 +#define GL_REG_6_ATI 0x8927 +#define GL_REG_7_ATI 0x8928 +#define GL_REG_8_ATI 0x8929 +#define GL_REG_9_ATI 0x892A +#define GL_REG_10_ATI 0x892B +#define GL_REG_11_ATI 0x892C +#define GL_REG_12_ATI 0x892D +#define GL_REG_13_ATI 0x892E +#define GL_REG_14_ATI 0x892F +#define GL_REG_15_ATI 0x8930 +#define GL_REG_16_ATI 0x8931 +#define GL_REG_17_ATI 0x8932 +#define GL_REG_18_ATI 0x8933 +#define GL_REG_19_ATI 0x8934 +#define GL_REG_20_ATI 0x8935 +#define GL_REG_21_ATI 0x8936 +#define GL_REG_22_ATI 0x8937 +#define GL_REG_23_ATI 0x8938 +#define GL_REG_24_ATI 0x8939 +#define GL_REG_25_ATI 0x893A +#define GL_REG_26_ATI 0x893B +#define GL_REG_27_ATI 0x893C +#define GL_REG_28_ATI 0x893D +#define GL_REG_29_ATI 0x893E +#define GL_REG_30_ATI 0x893F +#define GL_REG_31_ATI 0x8940 +#define GL_CON_0_ATI 0x8941 +#define GL_CON_1_ATI 0x8942 +#define GL_CON_2_ATI 0x8943 +#define GL_CON_3_ATI 0x8944 +#define GL_CON_4_ATI 0x8945 +#define GL_CON_5_ATI 0x8946 +#define GL_CON_6_ATI 0x8947 +#define GL_CON_7_ATI 0x8948 +#define GL_CON_8_ATI 0x8949 +#define GL_CON_9_ATI 0x894A +#define GL_CON_10_ATI 0x894B +#define GL_CON_11_ATI 0x894C +#define GL_CON_12_ATI 0x894D +#define GL_CON_13_ATI 0x894E +#define GL_CON_14_ATI 0x894F +#define GL_CON_15_ATI 0x8950 +#define GL_CON_16_ATI 0x8951 +#define GL_CON_17_ATI 0x8952 +#define GL_CON_18_ATI 0x8953 +#define GL_CON_19_ATI 0x8954 +#define GL_CON_20_ATI 0x8955 +#define GL_CON_21_ATI 0x8956 +#define GL_CON_22_ATI 0x8957 +#define GL_CON_23_ATI 0x8958 +#define GL_CON_24_ATI 0x8959 +#define GL_CON_25_ATI 0x895A +#define GL_CON_26_ATI 0x895B +#define GL_CON_27_ATI 0x895C +#define GL_CON_28_ATI 0x895D +#define GL_CON_29_ATI 0x895E +#define GL_CON_30_ATI 0x895F +#define GL_CON_31_ATI 0x8960 +#define GL_MOV_ATI 0x8961 +#define GL_ADD_ATI 0x8963 +#define GL_MUL_ATI 0x8964 +#define GL_SUB_ATI 0x8965 +#define GL_DOT3_ATI 0x8966 +#define GL_DOT4_ATI 0x8967 +#define GL_MAD_ATI 0x8968 +#define GL_LERP_ATI 0x8969 +#define GL_CND_ATI 0x896A +#define GL_CND0_ATI 0x896B +#define GL_DOT2_ADD_ATI 0x896C +#define GL_SECONDARY_INTERPOLATOR_ATI 0x896D +#define GL_NUM_FRAGMENT_REGISTERS_ATI 0x896E +#define GL_NUM_FRAGMENT_CONSTANTS_ATI 0x896F +#define GL_NUM_PASSES_ATI 0x8970 +#define GL_NUM_INSTRUCTIONS_PER_PASS_ATI 0x8971 +#define GL_NUM_INSTRUCTIONS_TOTAL_ATI 0x8972 +#define GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI 0x8973 +#define GL_NUM_LOOPBACK_COMPONENTS_ATI 0x8974 +#define GL_COLOR_ALPHA_PAIRING_ATI 0x8975 +#define GL_SWIZZLE_STR_ATI 0x8976 +#define GL_SWIZZLE_STQ_ATI 0x8977 +#define GL_SWIZZLE_STR_DR_ATI 0x8978 +#define GL_SWIZZLE_STQ_DQ_ATI 0x8979 +#define GL_SWIZZLE_STRQ_ATI 0x897A +#define GL_SWIZZLE_STRQ_DQ_ATI 0x897B +#define GL_RED_BIT_ATI 0x00000001 +#define GL_GREEN_BIT_ATI 0x00000002 +#define GL_BLUE_BIT_ATI 0x00000004 +#define GL_2X_BIT_ATI 0x00000001 +#define GL_4X_BIT_ATI 0x00000002 +#define GL_8X_BIT_ATI 0x00000004 +#define GL_HALF_BIT_ATI 0x00000008 +#define GL_QUARTER_BIT_ATI 0x00000010 +#define GL_EIGHTH_BIT_ATI 0x00000020 +#define GL_SATURATE_BIT_ATI 0x00000040 +#define GL_COMP_BIT_ATI 0x00000002 +#define GL_NEGATE_BIT_ATI 0x00000004 +#define GL_BIAS_BIT_ATI 0x00000008 +typedef GLuint (APIENTRYP PFNGLGENFRAGMENTSHADERSATIPROC) (GLuint range); +typedef void (APIENTRYP PFNGLBINDFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDELETEFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLENDFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLPASSTEXCOORDATIPROC) (GLuint dst, GLuint coord, GLenum swizzle); +typedef void (APIENTRYP PFNGLSAMPLEMAPATIPROC) (GLuint dst, GLuint interp, GLenum swizzle); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLSETFRAGMENTSHADERCONSTANTATIPROC) (GLuint dst, const GLfloat *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glGenFragmentShadersATI (GLuint range); +GLAPI void APIENTRY glBindFragmentShaderATI (GLuint id); +GLAPI void APIENTRY glDeleteFragmentShaderATI (GLuint id); +GLAPI void APIENTRY glBeginFragmentShaderATI (void); +GLAPI void APIENTRY glEndFragmentShaderATI (void); +GLAPI void APIENTRY glPassTexCoordATI (GLuint dst, GLuint coord, GLenum swizzle); +GLAPI void APIENTRY glSampleMapATI (GLuint dst, GLuint interp, GLenum swizzle); +GLAPI void APIENTRY glColorFragmentOp1ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +GLAPI void APIENTRY glColorFragmentOp2ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +GLAPI void APIENTRY glColorFragmentOp3ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +GLAPI void APIENTRY glAlphaFragmentOp1ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +GLAPI void APIENTRY glAlphaFragmentOp2ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +GLAPI void APIENTRY glAlphaFragmentOp3ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +GLAPI void APIENTRY glSetFragmentShaderConstantATI (GLuint dst, const GLfloat *value); +#endif +#endif /* GL_ATI_fragment_shader */ + +#ifndef GL_ATI_map_object_buffer +#define GL_ATI_map_object_buffer 1 +typedef void *(APIENTRYP PFNGLMAPOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUNMAPOBJECTBUFFERATIPROC) (GLuint buffer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void *APIENTRY glMapObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glUnmapObjectBufferATI (GLuint buffer); +#endif +#endif /* GL_ATI_map_object_buffer */ + +#ifndef GL_ATI_meminfo +#define GL_ATI_meminfo 1 +#define GL_VBO_FREE_MEMORY_ATI 0x87FB +#define GL_TEXTURE_FREE_MEMORY_ATI 0x87FC +#define GL_RENDERBUFFER_FREE_MEMORY_ATI 0x87FD +#endif /* GL_ATI_meminfo */ + +#ifndef GL_ATI_pixel_format_float +#define GL_ATI_pixel_format_float 1 +#define GL_RGBA_FLOAT_MODE_ATI 0x8820 +#define GL_COLOR_CLEAR_UNCLAMPED_VALUE_ATI 0x8835 +#endif /* GL_ATI_pixel_format_float */ + +#ifndef GL_ATI_pn_triangles +#define GL_ATI_pn_triangles 1 +#define GL_PN_TRIANGLES_ATI 0x87F0 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 +typedef void (APIENTRYP PFNGLPNTRIANGLESIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPNTRIANGLESFATIPROC) (GLenum pname, GLfloat param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPNTrianglesiATI (GLenum pname, GLint param); +GLAPI void APIENTRY glPNTrianglesfATI (GLenum pname, GLfloat param); +#endif +#endif /* GL_ATI_pn_triangles */ + +#ifndef GL_ATI_separate_stencil +#define GL_ATI_separate_stencil 1 +#define GL_STENCIL_BACK_FUNC_ATI 0x8800 +#define GL_STENCIL_BACK_FAIL_ATI 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL_ATI 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS_ATI 0x8803 +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEATIPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEATIPROC) (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilOpSeparateATI (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +GLAPI void APIENTRY glStencilFuncSeparateATI (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +#endif +#endif /* GL_ATI_separate_stencil */ + +#ifndef GL_ATI_text_fragment_shader +#define GL_ATI_text_fragment_shader 1 +#define GL_TEXT_FRAGMENT_SHADER_ATI 0x8200 +#endif /* GL_ATI_text_fragment_shader */ + +#ifndef GL_ATI_texture_env_combine3 +#define GL_ATI_texture_env_combine3 1 +#define GL_MODULATE_ADD_ATI 0x8744 +#define GL_MODULATE_SIGNED_ADD_ATI 0x8745 +#define GL_MODULATE_SUBTRACT_ATI 0x8746 +#endif /* GL_ATI_texture_env_combine3 */ + +#ifndef GL_ATI_texture_float +#define GL_ATI_texture_float 1 +#define GL_RGBA_FLOAT32_ATI 0x8814 +#define GL_RGB_FLOAT32_ATI 0x8815 +#define GL_ALPHA_FLOAT32_ATI 0x8816 +#define GL_INTENSITY_FLOAT32_ATI 0x8817 +#define GL_LUMINANCE_FLOAT32_ATI 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_ATI 0x8819 +#define GL_RGBA_FLOAT16_ATI 0x881A +#define GL_RGB_FLOAT16_ATI 0x881B +#define GL_ALPHA_FLOAT16_ATI 0x881C +#define GL_INTENSITY_FLOAT16_ATI 0x881D +#define GL_LUMINANCE_FLOAT16_ATI 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_ATI 0x881F +#endif /* GL_ATI_texture_float */ + +#ifndef GL_ATI_texture_mirror_once +#define GL_ATI_texture_mirror_once 1 +#define GL_MIRROR_CLAMP_ATI 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_ATI 0x8743 +#endif /* GL_ATI_texture_mirror_once */ + +#ifndef GL_ATI_vertex_array_object +#define GL_ATI_vertex_array_object 1 +#define GL_STATIC_ATI 0x8760 +#define GL_DYNAMIC_ATI 0x8761 +#define GL_PRESERVE_ATI 0x8762 +#define GL_DISCARD_ATI 0x8763 +#define GL_OBJECT_BUFFER_SIZE_ATI 0x8764 +#define GL_OBJECT_BUFFER_USAGE_ATI 0x8765 +#define GL_ARRAY_OBJECT_BUFFER_ATI 0x8766 +#define GL_ARRAY_OBJECT_OFFSET_ATI 0x8767 +typedef GLuint (APIENTRYP PFNGLNEWOBJECTBUFFERATIPROC) (GLsizei size, const void *pointer, GLenum usage); +typedef GLboolean (APIENTRYP PFNGLISOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUPDATEOBJECTBUFFERATIPROC) (GLuint buffer, GLuint offset, GLsizei size, const void *pointer, GLenum preserve); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERFVATIPROC) (GLuint buffer, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERIVATIPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFREEOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLARRAYOBJECTATIPROC) (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTFVATIPROC) (GLenum array, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTIVATIPROC) (GLenum array, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLVARIANTARRAYOBJECTATIPROC) (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTFVATIPROC) (GLuint id, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTIVATIPROC) (GLuint id, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glNewObjectBufferATI (GLsizei size, const void *pointer, GLenum usage); +GLAPI GLboolean APIENTRY glIsObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glUpdateObjectBufferATI (GLuint buffer, GLuint offset, GLsizei size, const void *pointer, GLenum preserve); +GLAPI void APIENTRY glGetObjectBufferfvATI (GLuint buffer, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetObjectBufferivATI (GLuint buffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glFreeObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glArrayObjectATI (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetArrayObjectfvATI (GLenum array, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetArrayObjectivATI (GLenum array, GLenum pname, GLint *params); +GLAPI void APIENTRY glVariantArrayObjectATI (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetVariantArrayObjectfvATI (GLuint id, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVariantArrayObjectivATI (GLuint id, GLenum pname, GLint *params); +#endif +#endif /* GL_ATI_vertex_array_object */ + +#ifndef GL_ATI_vertex_attrib_array_object +#define GL_ATI_vertex_attrib_array_object 1 +typedef void (APIENTRYP PFNGLVERTEXATTRIBARRAYOBJECTATIPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC) (GLuint index, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribArrayObjectATI (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetVertexAttribArrayObjectfvATI (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribArrayObjectivATI (GLuint index, GLenum pname, GLint *params); +#endif +#endif /* GL_ATI_vertex_attrib_array_object */ + +#ifndef GL_ATI_vertex_streams +#define GL_ATI_vertex_streams 1 +#define GL_MAX_VERTEX_STREAMS_ATI 0x876B +#define GL_VERTEX_STREAM0_ATI 0x876C +#define GL_VERTEX_STREAM1_ATI 0x876D +#define GL_VERTEX_STREAM2_ATI 0x876E +#define GL_VERTEX_STREAM3_ATI 0x876F +#define GL_VERTEX_STREAM4_ATI 0x8770 +#define GL_VERTEX_STREAM5_ATI 0x8771 +#define GL_VERTEX_STREAM6_ATI 0x8772 +#define GL_VERTEX_STREAM7_ATI 0x8773 +#define GL_VERTEX_SOURCE_ATI 0x8774 +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SATIPROC) (GLenum stream, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IATIPROC) (GLenum stream, GLint x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FATIPROC) (GLenum stream, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DATIPROC) (GLenum stream, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SATIPROC) (GLenum stream, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IATIPROC) (GLenum stream, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FATIPROC) (GLenum stream, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DATIPROC) (GLenum stream, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IATIPROC) (GLenum stream, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IATIPROC) (GLenum stream, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BATIPROC) (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BVATIPROC) (GLenum stream, const GLbyte *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SATIPROC) (GLenum stream, GLshort nx, GLshort ny, GLshort nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IATIPROC) (GLenum stream, GLint nx, GLint ny, GLint nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FATIPROC) (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DATIPROC) (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC) (GLenum stream); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVFATIPROC) (GLenum pname, GLfloat param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexStream1sATI (GLenum stream, GLshort x); +GLAPI void APIENTRY glVertexStream1svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream1iATI (GLenum stream, GLint x); +GLAPI void APIENTRY glVertexStream1ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream1fATI (GLenum stream, GLfloat x); +GLAPI void APIENTRY glVertexStream1fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream1dATI (GLenum stream, GLdouble x); +GLAPI void APIENTRY glVertexStream1dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream2sATI (GLenum stream, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexStream2svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream2iATI (GLenum stream, GLint x, GLint y); +GLAPI void APIENTRY glVertexStream2ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream2fATI (GLenum stream, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexStream2fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream2dATI (GLenum stream, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexStream2dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream3sATI (GLenum stream, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexStream3svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream3iATI (GLenum stream, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexStream3ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream3fATI (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexStream3fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream3dATI (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexStream3dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream4sATI (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexStream4svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream4iATI (GLenum stream, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexStream4ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream4fATI (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexStream4fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream4dATI (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexStream4dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glNormalStream3bATI (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); +GLAPI void APIENTRY glNormalStream3bvATI (GLenum stream, const GLbyte *coords); +GLAPI void APIENTRY glNormalStream3sATI (GLenum stream, GLshort nx, GLshort ny, GLshort nz); +GLAPI void APIENTRY glNormalStream3svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glNormalStream3iATI (GLenum stream, GLint nx, GLint ny, GLint nz); +GLAPI void APIENTRY glNormalStream3ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glNormalStream3fATI (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); +GLAPI void APIENTRY glNormalStream3fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glNormalStream3dATI (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); +GLAPI void APIENTRY glNormalStream3dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glClientActiveVertexStreamATI (GLenum stream); +GLAPI void APIENTRY glVertexBlendEnviATI (GLenum pname, GLint param); +GLAPI void APIENTRY glVertexBlendEnvfATI (GLenum pname, GLfloat param); +#endif +#endif /* GL_ATI_vertex_streams */ + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 +#define GL_422_EXT 0x80CC +#define GL_422_REV_EXT 0x80CD +#define GL_422_AVERAGE_EXT 0x80CE +#define GL_422_REV_AVERAGE_EXT 0x80CF +#endif /* GL_EXT_422_pixels */ + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#define GL_ABGR_EXT 0x8000 +#endif /* GL_EXT_abgr */ + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif /* GL_EXT_bgra */ + +#ifndef GL_EXT_bindable_uniform +#define GL_EXT_bindable_uniform 1 +#define GL_MAX_VERTEX_BINDABLE_UNIFORMS_EXT 0x8DE2 +#define GL_MAX_FRAGMENT_BINDABLE_UNIFORMS_EXT 0x8DE3 +#define GL_MAX_GEOMETRY_BINDABLE_UNIFORMS_EXT 0x8DE4 +#define GL_MAX_BINDABLE_UNIFORM_SIZE_EXT 0x8DED +#define GL_UNIFORM_BUFFER_EXT 0x8DEE +#define GL_UNIFORM_BUFFER_BINDING_EXT 0x8DEF +typedef void (APIENTRYP PFNGLUNIFORMBUFFEREXTPROC) (GLuint program, GLint location, GLuint buffer); +typedef GLint (APIENTRYP PFNGLGETUNIFORMBUFFERSIZEEXTPROC) (GLuint program, GLint location); +typedef GLintptr (APIENTRYP PFNGLGETUNIFORMOFFSETEXTPROC) (GLuint program, GLint location); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniformBufferEXT (GLuint program, GLint location, GLuint buffer); +GLAPI GLint APIENTRY glGetUniformBufferSizeEXT (GLuint program, GLint location); +GLAPI GLintptr APIENTRY glGetUniformOffsetEXT (GLuint program, GLint location); +#endif +#endif /* GL_EXT_bindable_uniform */ + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 +#define GL_CONSTANT_COLOR_EXT 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 +#define GL_CONSTANT_ALPHA_EXT 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 +#define GL_BLEND_COLOR_EXT 0x8005 +typedef void (APIENTRYP PFNGLBLENDCOLOREXTPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColorEXT (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +#endif +#endif /* GL_EXT_blend_color */ + +#ifndef GL_EXT_blend_equation_separate +#define GL_EXT_blend_equation_separate 1 +#define GL_BLEND_EQUATION_RGB_EXT 0x8009 +#define GL_BLEND_EQUATION_ALPHA_EXT 0x883D +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEEXTPROC) (GLenum modeRGB, GLenum modeAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparateEXT (GLenum modeRGB, GLenum modeAlpha); +#endif +#endif /* GL_EXT_blend_equation_separate */ + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 +#define GL_BLEND_DST_RGB_EXT 0x80C8 +#define GL_BLEND_SRC_RGB_EXT 0x80C9 +#define GL_BLEND_DST_ALPHA_EXT 0x80CA +#define GL_BLEND_SRC_ALPHA_EXT 0x80CB +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEEXTPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateEXT (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif +#endif /* GL_EXT_blend_func_separate */ + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 +#endif /* GL_EXT_blend_logic_op */ + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_BLEND_EQUATION_EXT 0x8009 +typedef void (APIENTRYP PFNGLBLENDEQUATIONEXTPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationEXT (GLenum mode); +#endif +#endif /* GL_EXT_blend_minmax */ + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif /* GL_EXT_blend_subtract */ + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif /* GL_EXT_clip_volume_hint */ + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 +#define GL_CMYK_EXT 0x800C +#define GL_CMYKA_EXT 0x800D +#define GL_PACK_CMYK_HINT_EXT 0x800E +#define GL_UNPACK_CMYK_HINT_EXT 0x800F +#endif /* GL_EXT_cmyka */ + +#ifndef GL_EXT_color_subtable +#define GL_EXT_color_subtable 1 +typedef void (APIENTRYP PFNGLCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorSubTableEXT (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glCopyColorSubTableEXT (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#endif +#endif /* GL_EXT_color_subtable */ + +#ifndef GL_EXT_compiled_vertex_array +#define GL_EXT_compiled_vertex_array 1 +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 +typedef void (APIENTRYP PFNGLLOCKARRAYSEXTPROC) (GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLUNLOCKARRAYSEXTPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLockArraysEXT (GLint first, GLsizei count); +GLAPI void APIENTRY glUnlockArraysEXT (void); +#endif +#endif /* GL_EXT_compiled_vertex_array */ + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 +#define GL_CONVOLUTION_1D_EXT 0x8010 +#define GL_CONVOLUTION_2D_EXT 0x8011 +#define GL_SEPARABLE_2D_EXT 0x8012 +#define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 +#define GL_REDUCE_EXT 0x8016 +#define GL_CONVOLUTION_FORMAT_EXT 0x8017 +#define GL_CONVOLUTION_WIDTH_EXT 0x8018 +#define GL_CONVOLUTION_HEIGHT_EXT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, void *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glConvolutionFilter1DEXT (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); +GLAPI void APIENTRY glConvolutionFilter2DEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); +GLAPI void APIENTRY glConvolutionParameterfEXT (GLenum target, GLenum pname, GLfloat params); +GLAPI void APIENTRY glConvolutionParameterfvEXT (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glConvolutionParameteriEXT (GLenum target, GLenum pname, GLint params); +GLAPI void APIENTRY glConvolutionParameterivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyConvolutionFilter1DEXT (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyConvolutionFilter2DEXT (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetConvolutionFilterEXT (GLenum target, GLenum format, GLenum type, void *image); +GLAPI void APIENTRY glGetConvolutionParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetConvolutionParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSeparableFilterEXT (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); +GLAPI void APIENTRY glSeparableFilter2DEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); +#endif +#endif /* GL_EXT_convolution */ + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 +#define GL_TANGENT_ARRAY_EXT 0x8439 +#define GL_BINORMAL_ARRAY_EXT 0x843A +#define GL_CURRENT_TANGENT_EXT 0x843B +#define GL_CURRENT_BINORMAL_EXT 0x843C +#define GL_TANGENT_ARRAY_TYPE_EXT 0x843E +#define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F +#define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 +#define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 +#define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 +#define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 +#define GL_MAP1_TANGENT_EXT 0x8444 +#define GL_MAP2_TANGENT_EXT 0x8445 +#define GL_MAP1_BINORMAL_EXT 0x8446 +#define GL_MAP2_BINORMAL_EXT 0x8447 +typedef void (APIENTRYP PFNGLTANGENT3BEXTPROC) (GLbyte tx, GLbyte ty, GLbyte tz); +typedef void (APIENTRYP PFNGLTANGENT3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLTANGENT3DEXTPROC) (GLdouble tx, GLdouble ty, GLdouble tz); +typedef void (APIENTRYP PFNGLTANGENT3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLTANGENT3FEXTPROC) (GLfloat tx, GLfloat ty, GLfloat tz); +typedef void (APIENTRYP PFNGLTANGENT3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLTANGENT3IEXTPROC) (GLint tx, GLint ty, GLint tz); +typedef void (APIENTRYP PFNGLTANGENT3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLTANGENT3SEXTPROC) (GLshort tx, GLshort ty, GLshort tz); +typedef void (APIENTRYP PFNGLTANGENT3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLBINORMAL3BEXTPROC) (GLbyte bx, GLbyte by, GLbyte bz); +typedef void (APIENTRYP PFNGLBINORMAL3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLBINORMAL3DEXTPROC) (GLdouble bx, GLdouble by, GLdouble bz); +typedef void (APIENTRYP PFNGLBINORMAL3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLBINORMAL3FEXTPROC) (GLfloat bx, GLfloat by, GLfloat bz); +typedef void (APIENTRYP PFNGLBINORMAL3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLBINORMAL3IEXTPROC) (GLint bx, GLint by, GLint bz); +typedef void (APIENTRYP PFNGLBINORMAL3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLBINORMAL3SEXTPROC) (GLshort bx, GLshort by, GLshort bz); +typedef void (APIENTRYP PFNGLBINORMAL3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLTANGENTPOINTEREXTPROC) (GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLBINORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTangent3bEXT (GLbyte tx, GLbyte ty, GLbyte tz); +GLAPI void APIENTRY glTangent3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glTangent3dEXT (GLdouble tx, GLdouble ty, GLdouble tz); +GLAPI void APIENTRY glTangent3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glTangent3fEXT (GLfloat tx, GLfloat ty, GLfloat tz); +GLAPI void APIENTRY glTangent3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glTangent3iEXT (GLint tx, GLint ty, GLint tz); +GLAPI void APIENTRY glTangent3ivEXT (const GLint *v); +GLAPI void APIENTRY glTangent3sEXT (GLshort tx, GLshort ty, GLshort tz); +GLAPI void APIENTRY glTangent3svEXT (const GLshort *v); +GLAPI void APIENTRY glBinormal3bEXT (GLbyte bx, GLbyte by, GLbyte bz); +GLAPI void APIENTRY glBinormal3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glBinormal3dEXT (GLdouble bx, GLdouble by, GLdouble bz); +GLAPI void APIENTRY glBinormal3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glBinormal3fEXT (GLfloat bx, GLfloat by, GLfloat bz); +GLAPI void APIENTRY glBinormal3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glBinormal3iEXT (GLint bx, GLint by, GLint bz); +GLAPI void APIENTRY glBinormal3ivEXT (const GLint *v); +GLAPI void APIENTRY glBinormal3sEXT (GLshort bx, GLshort by, GLshort bz); +GLAPI void APIENTRY glBinormal3svEXT (const GLshort *v); +GLAPI void APIENTRY glTangentPointerEXT (GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glBinormalPointerEXT (GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_EXT_coordinate_frame */ + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE1DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE2DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyTexImage1DEXT (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyTexImage2DEXT (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyTexSubImage1DEXT (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyTexSubImage2DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glCopyTexSubImage3DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif +#endif /* GL_EXT_copy_texture */ + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 +#define GL_CULL_VERTEX_EXT 0x81AA +#define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB +#define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC +typedef void (APIENTRYP PFNGLCULLPARAMETERDVEXTPROC) (GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLCULLPARAMETERFVEXTPROC) (GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCullParameterdvEXT (GLenum pname, GLdouble *params); +GLAPI void APIENTRY glCullParameterfvEXT (GLenum pname, GLfloat *params); +#endif +#endif /* GL_EXT_cull_vertex */ + +#ifndef GL_EXT_debug_label +#define GL_EXT_debug_label 1 +#define GL_PROGRAM_PIPELINE_OBJECT_EXT 0x8A4F +#define GL_PROGRAM_OBJECT_EXT 0x8B40 +#define GL_SHADER_OBJECT_EXT 0x8B48 +#define GL_BUFFER_OBJECT_EXT 0x9151 +#define GL_QUERY_OBJECT_EXT 0x9153 +#define GL_VERTEX_ARRAY_OBJECT_EXT 0x9154 +typedef void (APIENTRYP PFNGLLABELOBJECTEXTPROC) (GLenum type, GLuint object, GLsizei length, const GLchar *label); +typedef void (APIENTRYP PFNGLGETOBJECTLABELEXTPROC) (GLenum type, GLuint object, GLsizei bufSize, GLsizei *length, GLchar *label); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLabelObjectEXT (GLenum type, GLuint object, GLsizei length, const GLchar *label); +GLAPI void APIENTRY glGetObjectLabelEXT (GLenum type, GLuint object, GLsizei bufSize, GLsizei *length, GLchar *label); +#endif +#endif /* GL_EXT_debug_label */ + +#ifndef GL_EXT_debug_marker +#define GL_EXT_debug_marker 1 +typedef void (APIENTRYP PFNGLINSERTEVENTMARKEREXTPROC) (GLsizei length, const GLchar *marker); +typedef void (APIENTRYP PFNGLPUSHGROUPMARKEREXTPROC) (GLsizei length, const GLchar *marker); +typedef void (APIENTRYP PFNGLPOPGROUPMARKEREXTPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glInsertEventMarkerEXT (GLsizei length, const GLchar *marker); +GLAPI void APIENTRY glPushGroupMarkerEXT (GLsizei length, const GLchar *marker); +GLAPI void APIENTRY glPopGroupMarkerEXT (void); +#endif +#endif /* GL_EXT_debug_marker */ + +#ifndef GL_EXT_depth_bounds_test +#define GL_EXT_depth_bounds_test 1 +#define GL_DEPTH_BOUNDS_TEST_EXT 0x8890 +#define GL_DEPTH_BOUNDS_EXT 0x8891 +typedef void (APIENTRYP PFNGLDEPTHBOUNDSEXTPROC) (GLclampd zmin, GLclampd zmax); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthBoundsEXT (GLclampd zmin, GLclampd zmax); +#endif +#endif /* GL_EXT_depth_bounds_test */ + +#ifndef GL_EXT_direct_state_access +#define GL_EXT_direct_state_access 1 +#define GL_PROGRAM_MATRIX_EXT 0x8E2D +#define GL_TRANSPOSE_PROGRAM_MATRIX_EXT 0x8E2E +#define GL_PROGRAM_MATRIX_STACK_DEPTH_EXT 0x8E2F +typedef void (APIENTRYP PFNGLMATRIXLOADFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXLOADDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXMULTFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULTDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXLOADIDENTITYEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLMATRIXROTATEFEXTPROC) (GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXROTATEDEXTPROC) (GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXSCALEFEXTPROC) (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXSCALEDEXTPROC) (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXTRANSLATEFEXTPROC) (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXTRANSLATEDEXTPROC) (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXFRUSTUMEXTPROC) (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLMATRIXORTHOEXTPROC) (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLMATRIXPOPEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLMATRIXPUSHEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLCLIENTATTRIBDEFAULTEXTPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLPUSHCLIENTATTRIBDEFAULTEXTPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETTEXTUREIMAGEEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLBINDMULTITEXTUREEXTPROC) (GLenum texunit, GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORDPOINTEREXTPROC) (GLenum texunit, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLMULTITEXENVFEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXENVFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXENVIEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXENVIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXGENDEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLdouble param); +typedef void (APIENTRYP PFNGLMULTITEXGENDVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLdouble *params); +typedef void (APIENTRYP PFNGLMULTITEXGENFEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXGENFVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXGENIEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXGENIVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXENVFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXENVIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENDVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENFVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENIVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERFEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETMULTITEXIMAGEEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXLEVELPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXLEVELPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLENABLECLIENTSTATEINDEXEDEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLDISABLECLIENTSTATEINDEXEDEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLGETFLOATINDEXEDVEXTPROC) (GLenum target, GLuint index, GLfloat *data); +typedef void (APIENTRYP PFNGLGETDOUBLEINDEXEDVEXTPROC) (GLenum target, GLuint index, GLdouble *data); +typedef void (APIENTRYP PFNGLGETPOINTERINDEXEDVEXTPROC) (GLenum target, GLuint index, void **data); +typedef void (APIENTRYP PFNGLENABLEINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLDISABLEINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef GLboolean (APIENTRYP PFNGLISENABLEDINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLGETINTEGERINDEXEDVEXTPROC) (GLenum target, GLuint index, GLint *data); +typedef void (APIENTRYP PFNGLGETBOOLEANINDEXEDVEXTPROC) (GLenum target, GLuint index, GLboolean *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTUREIMAGEEXTPROC) (GLuint texture, GLenum target, GLint lod, void *img); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDMULTITEXIMAGEEXTPROC) (GLenum texunit, GLenum target, GLint lod, void *img); +typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSEFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSEDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSEFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSEDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLNAMEDBUFFERDATAEXTPROC) (GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +typedef void *(APIENTRYP PFNGLMAPNAMEDBUFFEREXTPROC) (GLuint buffer, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPNAMEDBUFFEREXTPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERIVEXTPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPOINTERVEXTPROC) (GLuint buffer, GLenum pname, void **params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, void *data); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FEXTPROC) (GLuint program, GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IEXTPROC) (GLuint program, GLint location, GLint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLTEXTUREBUFFEREXTPROC) (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLMULTITEXBUFFEREXTPROC) (GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIUIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIUIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIUIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIUIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIEXTPROC) (GLuint program, GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERS4FVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4IEXTPROC) (GLuint program, GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4IVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERSI4IVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIEXTPROC) (GLuint program, GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERSI4UIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERIIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERIUIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLuint *params); +typedef void (APIENTRYP PFNGLENABLECLIENTSTATEIEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLDISABLECLIENTSTATEIEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLGETFLOATI_VEXTPROC) (GLenum pname, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETDOUBLEI_VEXTPROC) (GLenum pname, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPOINTERI_VEXTPROC) (GLenum pname, GLuint index, void **params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMSTRINGEXTPROC) (GLuint program, GLenum target, GLenum format, GLsizei len, const void *string); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4DEXTPROC) (GLuint program, GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4DVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4FEXTPROC) (GLuint program, GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4FVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERDVEXTPROC) (GLuint program, GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERFVEXTPROC) (GLuint program, GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMIVEXTPROC) (GLuint program, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMSTRINGEXTPROC) (GLuint program, GLenum target, GLenum pname, void *string); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEEXTPROC) (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETNAMEDRENDERBUFFERPARAMETERIVEXTPROC) (GLuint renderbuffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLECOVERAGEEXTPROC) (GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +typedef GLenum (APIENTRYP PFNGLCHECKNAMEDFRAMEBUFFERSTATUSEXTPROC) (GLuint framebuffer, GLenum target); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE1DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE3DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERRENDERBUFFEREXTPROC) (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATETEXTUREMIPMAPEXTPROC) (GLuint texture, GLenum target); +typedef void (APIENTRYP PFNGLGENERATEMULTITEXMIPMAPEXTPROC) (GLenum texunit, GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERDRAWBUFFEREXTPROC) (GLuint framebuffer, GLenum mode); +typedef void (APIENTRYP PFNGLFRAMEBUFFERDRAWBUFFERSEXTPROC) (GLuint framebuffer, GLsizei n, const GLenum *bufs); +typedef void (APIENTRYP PFNGLFRAMEBUFFERREADBUFFEREXTPROC) (GLuint framebuffer, GLenum mode); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLNAMEDCOPYBUFFERSUBDATAEXTPROC) (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREEXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURELAYEREXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREFACEEXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face); +typedef void (APIENTRYP PFNGLTEXTURERENDERBUFFEREXTPROC) (GLuint texture, GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLMULTITEXRENDERBUFFEREXTPROC) (GLenum texunit, GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYCOLOROFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYEDGEFLAGOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYINDEXOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYNORMALOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYTEXCOORDOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYMULTITEXCOORDOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum texunit, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYFOGCOORDOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYSECONDARYCOLOROFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBIOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLENABLEVERTEXARRAYEXTPROC) (GLuint vaobj, GLenum array); +typedef void (APIENTRYP PFNGLDISABLEVERTEXARRAYEXTPROC) (GLuint vaobj, GLenum array); +typedef void (APIENTRYP PFNGLENABLEVERTEXARRAYATTRIBEXTPROC) (GLuint vaobj, GLuint index); +typedef void (APIENTRYP PFNGLDISABLEVERTEXARRAYATTRIBEXTPROC) (GLuint vaobj, GLuint index); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYINTEGERVEXTPROC) (GLuint vaobj, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYPOINTERVEXTPROC) (GLuint vaobj, GLenum pname, void **param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYINTEGERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYPOINTERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, void **param); +typedef void *(APIENTRYP PFNGLMAPNAMEDBUFFERRANGEEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSTORAGEEXTPROC) (GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); +typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERDATAEXTPROC) (GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLenum internalformat, GLsizeiptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERPARAMETERIEXTPROC) (GLuint framebuffer, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DEXTPROC) (GLuint program, GLint location, GLdouble x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLTEXTUREBUFFERRANGEEXTPROC) (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE1DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DMULTISAMPLEEXTPROC) (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DMULTISAMPLEEXTPROC) (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLVERTEXARRAYBINDVERTEXBUFFEREXTPROC) (GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBFORMATEXTPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBIFORMATEXTPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBLFORMATEXTPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBBINDINGEXTPROC) (GLuint vaobj, GLuint attribindex, GLuint bindingindex); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXBINDINGDIVISOREXTPROC) (GLuint vaobj, GLuint bindingindex, GLuint divisor); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBLOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLTEXTUREPAGECOMMITMENTEXTPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean resident); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBDIVISOREXTPROC) (GLuint vaobj, GLuint index, GLuint divisor); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMatrixLoadfEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixLoaddEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixMultfEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMultdEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixLoadIdentityEXT (GLenum mode); +GLAPI void APIENTRY glMatrixRotatefEXT (GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixRotatedEXT (GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixScalefEXT (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixScaledEXT (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixTranslatefEXT (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixTranslatedEXT (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixFrustumEXT (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glMatrixOrthoEXT (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glMatrixPopEXT (GLenum mode); +GLAPI void APIENTRY glMatrixPushEXT (GLenum mode); +GLAPI void APIENTRY glClientAttribDefaultEXT (GLbitfield mask); +GLAPI void APIENTRY glPushClientAttribDefaultEXT (GLbitfield mask); +GLAPI void APIENTRY glTextureParameterfEXT (GLuint texture, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glTextureParameterfvEXT (GLuint texture, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glTextureParameteriEXT (GLuint texture, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glTextureParameterivEXT (GLuint texture, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetTextureImageEXT (GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +GLAPI void APIENTRY glGetTextureParameterfvEXT (GLuint texture, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTextureParameterivEXT (GLuint texture, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTextureLevelParameterfvEXT (GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTextureLevelParameterivEXT (GLuint texture, GLenum target, GLint level, GLenum pname, GLint *params); +GLAPI void APIENTRY glTextureImage3DEXT (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glBindMultiTextureEXT (GLenum texunit, GLenum target, GLuint texture); +GLAPI void APIENTRY glMultiTexCoordPointerEXT (GLenum texunit, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glMultiTexEnvfEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexEnvfvEXT (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexEnviEXT (GLenum texunit, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexEnvivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexGendEXT (GLenum texunit, GLenum coord, GLenum pname, GLdouble param); +GLAPI void APIENTRY glMultiTexGendvEXT (GLenum texunit, GLenum coord, GLenum pname, const GLdouble *params); +GLAPI void APIENTRY glMultiTexGenfEXT (GLenum texunit, GLenum coord, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexGenfvEXT (GLenum texunit, GLenum coord, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexGeniEXT (GLenum texunit, GLenum coord, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexGenivEXT (GLenum texunit, GLenum coord, GLenum pname, const GLint *params); +GLAPI void APIENTRY glGetMultiTexEnvfvEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexEnvivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexGendvEXT (GLenum texunit, GLenum coord, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetMultiTexGenfvEXT (GLenum texunit, GLenum coord, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexGenivEXT (GLenum texunit, GLenum coord, GLenum pname, GLint *params); +GLAPI void APIENTRY glMultiTexParameteriEXT (GLenum texunit, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexParameterivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexParameterfEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexParameterfvEXT (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetMultiTexImageEXT (GLenum texunit, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +GLAPI void APIENTRY glGetMultiTexParameterfvEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexParameterivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexLevelParameterfvEXT (GLenum texunit, GLenum target, GLint level, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexLevelParameterivEXT (GLenum texunit, GLenum target, GLint level, GLenum pname, GLint *params); +GLAPI void APIENTRY glMultiTexImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glEnableClientStateIndexedEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glDisableClientStateIndexedEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glGetFloatIndexedvEXT (GLenum target, GLuint index, GLfloat *data); +GLAPI void APIENTRY glGetDoubleIndexedvEXT (GLenum target, GLuint index, GLdouble *data); +GLAPI void APIENTRY glGetPointerIndexedvEXT (GLenum target, GLuint index, void **data); +GLAPI void APIENTRY glEnableIndexedEXT (GLenum target, GLuint index); +GLAPI void APIENTRY glDisableIndexedEXT (GLenum target, GLuint index); +GLAPI GLboolean APIENTRY glIsEnabledIndexedEXT (GLenum target, GLuint index); +GLAPI void APIENTRY glGetIntegerIndexedvEXT (GLenum target, GLuint index, GLint *data); +GLAPI void APIENTRY glGetBooleanIndexedvEXT (GLenum target, GLuint index, GLboolean *data); +GLAPI void APIENTRY glCompressedTextureImage3DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glGetCompressedTextureImageEXT (GLuint texture, GLenum target, GLint lod, void *img); +GLAPI void APIENTRY glCompressedMultiTexImage3DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glGetCompressedMultiTexImageEXT (GLenum texunit, GLenum target, GLint lod, void *img); +GLAPI void APIENTRY glMatrixLoadTransposefEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixLoadTransposedEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixMultTransposefEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMultTransposedEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glNamedBufferDataEXT (GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); +GLAPI void APIENTRY glNamedBufferSubDataEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +GLAPI void *APIENTRY glMapNamedBufferEXT (GLuint buffer, GLenum access); +GLAPI GLboolean APIENTRY glUnmapNamedBufferEXT (GLuint buffer); +GLAPI void APIENTRY glGetNamedBufferParameterivEXT (GLuint buffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetNamedBufferPointervEXT (GLuint buffer, GLenum pname, void **params); +GLAPI void APIENTRY glGetNamedBufferSubDataEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, void *data); +GLAPI void APIENTRY glProgramUniform1fEXT (GLuint program, GLint location, GLfloat v0); +GLAPI void APIENTRY glProgramUniform2fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glProgramUniform3fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glProgramUniform4fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glProgramUniform1iEXT (GLuint program, GLint location, GLint v0); +GLAPI void APIENTRY glProgramUniform2iEXT (GLuint program, GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glProgramUniform3iEXT (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glProgramUniform4iEXT (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glProgramUniform1fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform2fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform3fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform4fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform1ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform2ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform3ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform4ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniformMatrix2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glTextureBufferEXT (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glMultiTexBufferEXT (GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glTextureParameterIivEXT (GLuint texture, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTextureParameterIuivEXT (GLuint texture, GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTextureParameterIivEXT (GLuint texture, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTextureParameterIuivEXT (GLuint texture, GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glMultiTexParameterIivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexParameterIuivEXT (GLenum texunit, GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetMultiTexParameterIivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexParameterIuivEXT (GLenum texunit, GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glProgramUniform1uiEXT (GLuint program, GLint location, GLuint v0); +GLAPI void APIENTRY glProgramUniform2uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glProgramUniform3uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glProgramUniform4uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glProgramUniform1uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform2uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform3uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform4uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glNamedProgramLocalParameters4fvEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glNamedProgramLocalParameterI4iEXT (GLuint program, GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glNamedProgramLocalParameterI4ivEXT (GLuint program, GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glNamedProgramLocalParametersI4ivEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glNamedProgramLocalParameterI4uiEXT (GLuint program, GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glNamedProgramLocalParameterI4uivEXT (GLuint program, GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glNamedProgramLocalParametersI4uivEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterIivEXT (GLuint program, GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterIuivEXT (GLuint program, GLenum target, GLuint index, GLuint *params); +GLAPI void APIENTRY glEnableClientStateiEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glDisableClientStateiEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glGetFloati_vEXT (GLenum pname, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetDoublei_vEXT (GLenum pname, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetPointeri_vEXT (GLenum pname, GLuint index, void **params); +GLAPI void APIENTRY glNamedProgramStringEXT (GLuint program, GLenum target, GLenum format, GLsizei len, const void *string); +GLAPI void APIENTRY glNamedProgramLocalParameter4dEXT (GLuint program, GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glNamedProgramLocalParameter4dvEXT (GLuint program, GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glNamedProgramLocalParameter4fEXT (GLuint program, GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glNamedProgramLocalParameter4fvEXT (GLuint program, GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterdvEXT (GLuint program, GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterfvEXT (GLuint program, GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetNamedProgramivEXT (GLuint program, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetNamedProgramStringEXT (GLuint program, GLenum target, GLenum pname, void *string); +GLAPI void APIENTRY glNamedRenderbufferStorageEXT (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetNamedRenderbufferParameterivEXT (GLuint renderbuffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleEXT (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleCoverageEXT (GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI GLenum APIENTRY glCheckNamedFramebufferStatusEXT (GLuint framebuffer, GLenum target); +GLAPI void APIENTRY glNamedFramebufferTexture1DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTexture2DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTexture3DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glNamedFramebufferRenderbufferEXT (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetNamedFramebufferAttachmentParameterivEXT (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateTextureMipmapEXT (GLuint texture, GLenum target); +GLAPI void APIENTRY glGenerateMultiTexMipmapEXT (GLenum texunit, GLenum target); +GLAPI void APIENTRY glFramebufferDrawBufferEXT (GLuint framebuffer, GLenum mode); +GLAPI void APIENTRY glFramebufferDrawBuffersEXT (GLuint framebuffer, GLsizei n, const GLenum *bufs); +GLAPI void APIENTRY glFramebufferReadBufferEXT (GLuint framebuffer, GLenum mode); +GLAPI void APIENTRY glGetFramebufferParameterivEXT (GLuint framebuffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glNamedCopyBufferSubDataEXT (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +GLAPI void APIENTRY glNamedFramebufferTextureEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTextureLayerEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glNamedFramebufferTextureFaceEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face); +GLAPI void APIENTRY glTextureRenderbufferEXT (GLuint texture, GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glMultiTexRenderbufferEXT (GLenum texunit, GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glVertexArrayVertexOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayColorOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayEdgeFlagOffsetEXT (GLuint vaobj, GLuint buffer, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayIndexOffsetEXT (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayNormalOffsetEXT (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayTexCoordOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayMultiTexCoordOffsetEXT (GLuint vaobj, GLuint buffer, GLenum texunit, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayFogCoordOffsetEXT (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArraySecondaryColorOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayVertexAttribOffsetEXT (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayVertexAttribIOffsetEXT (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glEnableVertexArrayEXT (GLuint vaobj, GLenum array); +GLAPI void APIENTRY glDisableVertexArrayEXT (GLuint vaobj, GLenum array); +GLAPI void APIENTRY glEnableVertexArrayAttribEXT (GLuint vaobj, GLuint index); +GLAPI void APIENTRY glDisableVertexArrayAttribEXT (GLuint vaobj, GLuint index); +GLAPI void APIENTRY glGetVertexArrayIntegervEXT (GLuint vaobj, GLenum pname, GLint *param); +GLAPI void APIENTRY glGetVertexArrayPointervEXT (GLuint vaobj, GLenum pname, void **param); +GLAPI void APIENTRY glGetVertexArrayIntegeri_vEXT (GLuint vaobj, GLuint index, GLenum pname, GLint *param); +GLAPI void APIENTRY glGetVertexArrayPointeri_vEXT (GLuint vaobj, GLuint index, GLenum pname, void **param); +GLAPI void *APIENTRY glMapNamedBufferRangeEXT (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); +GLAPI void APIENTRY glFlushMappedNamedBufferRangeEXT (GLuint buffer, GLintptr offset, GLsizeiptr length); +GLAPI void APIENTRY glNamedBufferStorageEXT (GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); +GLAPI void APIENTRY glClearNamedBufferDataEXT (GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glClearNamedBufferSubDataEXT (GLuint buffer, GLenum internalformat, GLsizeiptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glNamedFramebufferParameteriEXT (GLuint framebuffer, GLenum pname, GLint param); +GLAPI void APIENTRY glGetNamedFramebufferParameterivEXT (GLuint framebuffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glProgramUniform1dEXT (GLuint program, GLint location, GLdouble x); +GLAPI void APIENTRY glProgramUniform2dEXT (GLuint program, GLint location, GLdouble x, GLdouble y); +GLAPI void APIENTRY glProgramUniform3dEXT (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glProgramUniform4dEXT (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramUniform1dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform2dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform3dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform4dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glTextureBufferRangeEXT (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glTextureStorage1DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +GLAPI void APIENTRY glTextureStorage2DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTextureStorage3DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glTextureStorage2DMultisampleEXT (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTextureStorage3DMultisampleEXT (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glVertexArrayBindVertexBufferEXT (GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +GLAPI void APIENTRY glVertexArrayVertexAttribFormatEXT (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayVertexAttribIFormatEXT (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayVertexAttribLFormatEXT (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayVertexAttribBindingEXT (GLuint vaobj, GLuint attribindex, GLuint bindingindex); +GLAPI void APIENTRY glVertexArrayVertexBindingDivisorEXT (GLuint vaobj, GLuint bindingindex, GLuint divisor); +GLAPI void APIENTRY glVertexArrayVertexAttribLOffsetEXT (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glTexturePageCommitmentEXT (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean resident); +GLAPI void APIENTRY glVertexArrayVertexAttribDivisorEXT (GLuint vaobj, GLuint index, GLuint divisor); +#endif +#endif /* GL_EXT_direct_state_access */ + +#ifndef GL_EXT_draw_buffers2 +#define GL_EXT_draw_buffers2 1 +typedef void (APIENTRYP PFNGLCOLORMASKINDEXEDEXTPROC) (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorMaskIndexedEXT (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +#endif +#endif /* GL_EXT_draw_buffers2 */ + +#ifndef GL_EXT_draw_instanced +#define GL_EXT_draw_instanced 1 +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstancedEXT (GLenum mode, GLint start, GLsizei count, GLsizei primcount); +GLAPI void APIENTRY glDrawElementsInstancedEXT (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#endif +#endif /* GL_EXT_draw_instanced */ + +#ifndef GL_EXT_draw_range_elements +#define GL_EXT_draw_range_elements 1 +#define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 +#define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSEXTPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawRangeElementsEXT (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); +#endif +#endif /* GL_EXT_draw_range_elements */ + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 +typedef void (APIENTRYP PFNGLFOGCOORDFEXTPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVEXTPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDEXTPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVEXTPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTEREXTPROC) (GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogCoordfEXT (GLfloat coord); +GLAPI void APIENTRY glFogCoordfvEXT (const GLfloat *coord); +GLAPI void APIENTRY glFogCoorddEXT (GLdouble coord); +GLAPI void APIENTRY glFogCoorddvEXT (const GLdouble *coord); +GLAPI void APIENTRY glFogCoordPointerEXT (GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_EXT_fog_coord */ + +#ifndef GL_EXT_framebuffer_blit +#define GL_EXT_framebuffer_blit 1 +#define GL_READ_FRAMEBUFFER_EXT 0x8CA8 +#define GL_DRAW_FRAMEBUFFER_EXT 0x8CA9 +#define GL_DRAW_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_READ_FRAMEBUFFER_BINDING_EXT 0x8CAA +typedef void (APIENTRYP PFNGLBLITFRAMEBUFFEREXTPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlitFramebufferEXT (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#endif +#endif /* GL_EXT_framebuffer_blit */ + +#ifndef GL_EXT_framebuffer_multisample +#define GL_EXT_framebuffer_multisample 1 +#define GL_RENDERBUFFER_SAMPLES_EXT 0x8CAB +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT 0x8D56 +#define GL_MAX_SAMPLES_EXT 0x8D57 +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRenderbufferStorageMultisampleEXT (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +#endif +#endif /* GL_EXT_framebuffer_multisample */ + +#ifndef GL_EXT_framebuffer_multisample_blit_scaled +#define GL_EXT_framebuffer_multisample_blit_scaled 1 +#define GL_SCALED_RESOLVE_FASTEST_EXT 0x90BA +#define GL_SCALED_RESOLVE_NICEST_EXT 0x90BB +#endif /* GL_EXT_framebuffer_multisample_blit_scaled */ + +#ifndef GL_EXT_framebuffer_object +#define GL_EXT_framebuffer_object 1 +#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 +#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 +#define GL_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT 0x8CD9 +#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT 0x8CDA +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED_EXT 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF +#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 +#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 +#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 +#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 +#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 +#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 +#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 +#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 +#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 +#define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 +#define GL_COLOR_ATTACHMENT10_EXT 0x8CEA +#define GL_COLOR_ATTACHMENT11_EXT 0x8CEB +#define GL_COLOR_ATTACHMENT12_EXT 0x8CEC +#define GL_COLOR_ATTACHMENT13_EXT 0x8CED +#define GL_COLOR_ATTACHMENT14_EXT 0x8CEE +#define GL_COLOR_ATTACHMENT15_EXT 0x8CEF +#define GL_DEPTH_ATTACHMENT_EXT 0x8D00 +#define GL_STENCIL_ATTACHMENT_EXT 0x8D20 +#define GL_FRAMEBUFFER_EXT 0x8D40 +#define GL_RENDERBUFFER_EXT 0x8D41 +#define GL_RENDERBUFFER_WIDTH_EXT 0x8D42 +#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 +#define GL_STENCIL_INDEX1_EXT 0x8D46 +#define GL_STENCIL_INDEX4_EXT 0x8D47 +#define GL_STENCIL_INDEX8_EXT 0x8D48 +#define GL_STENCIL_INDEX16_EXT 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE_EXT 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE_EXT 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE_EXT 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE_EXT 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55 +typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFEREXTPROC) (GLuint renderbuffer); +typedef void (APIENTRYP PFNGLBINDRENDERBUFFEREXTPROC) (GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSEXTPROC) (GLsizei n, const GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLGENRENDERBUFFERSEXTPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFEREXTPROC) (GLuint framebuffer); +typedef void (APIENTRYP PFNGLBINDFRAMEBUFFEREXTPROC) (GLenum target, GLuint framebuffer); +typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSEXTPROC) (GLsizei n, const GLuint *framebuffers); +typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSEXTPROC) (GLsizei n, GLuint *framebuffers); +typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATEMIPMAPEXTPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glIsRenderbufferEXT (GLuint renderbuffer); +GLAPI void APIENTRY glBindRenderbufferEXT (GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glDeleteRenderbuffersEXT (GLsizei n, const GLuint *renderbuffers); +GLAPI void APIENTRY glGenRenderbuffersEXT (GLsizei n, GLuint *renderbuffers); +GLAPI void APIENTRY glRenderbufferStorageEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetRenderbufferParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI GLboolean APIENTRY glIsFramebufferEXT (GLuint framebuffer); +GLAPI void APIENTRY glBindFramebufferEXT (GLenum target, GLuint framebuffer); +GLAPI void APIENTRY glDeleteFramebuffersEXT (GLsizei n, const GLuint *framebuffers); +GLAPI void APIENTRY glGenFramebuffersEXT (GLsizei n, GLuint *framebuffers); +GLAPI GLenum APIENTRY glCheckFramebufferStatusEXT (GLenum target); +GLAPI void APIENTRY glFramebufferTexture1DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture2DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture3DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glFramebufferRenderbufferEXT (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetFramebufferAttachmentParameterivEXT (GLenum target, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateMipmapEXT (GLenum target); +#endif +#endif /* GL_EXT_framebuffer_object */ + +#ifndef GL_EXT_framebuffer_sRGB +#define GL_EXT_framebuffer_sRGB 1 +#define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9 +#define GL_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x8DBA +#endif /* GL_EXT_framebuffer_sRGB */ + +#ifndef GL_EXT_geometry_shader4 +#define GL_EXT_geometry_shader4 1 +#define GL_GEOMETRY_SHADER_EXT 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT_EXT 0x8DDA +#define GL_GEOMETRY_INPUT_TYPE_EXT 0x8DDB +#define GL_GEOMETRY_OUTPUT_TYPE_EXT 0x8DDC +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_EXT 0x8C29 +#define GL_MAX_GEOMETRY_VARYING_COMPONENTS_EXT 0x8DDD +#define GL_MAX_VERTEX_VARYING_COMPONENTS_EXT 0x8DDE +#define GL_MAX_VARYING_COMPONENTS_EXT 0x8B4B +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_EXT 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_EXT 0x8DE1 +#define GL_LINES_ADJACENCY_EXT 0x000A +#define GL_LINE_STRIP_ADJACENCY_EXT 0x000B +#define GL_TRIANGLES_ADJACENCY_EXT 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY_EXT 0x000D +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT 0x8DA8 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_EXT 0x8DA9 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_EXT 0x8DA7 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER_EXT 0x8CD4 +#define GL_PROGRAM_POINT_SIZE_EXT 0x8642 +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIEXTPROC) (GLuint program, GLenum pname, GLint value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramParameteriEXT (GLuint program, GLenum pname, GLint value); +#endif +#endif /* GL_EXT_geometry_shader4 */ + +#ifndef GL_EXT_gpu_program_parameters +#define GL_EXT_gpu_program_parameters 1 +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERS4FVEXTPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERS4FVEXTPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramEnvParameters4fvEXT (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glProgramLocalParameters4fvEXT (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +#endif +#endif /* GL_EXT_gpu_program_parameters */ + +#ifndef GL_EXT_gpu_shader4 +#define GL_EXT_gpu_shader4 1 +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER_EXT 0x88FD +#define GL_SAMPLER_1D_ARRAY_EXT 0x8DC0 +#define GL_SAMPLER_2D_ARRAY_EXT 0x8DC1 +#define GL_SAMPLER_BUFFER_EXT 0x8DC2 +#define GL_SAMPLER_1D_ARRAY_SHADOW_EXT 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW_EXT 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW_EXT 0x8DC5 +#define GL_UNSIGNED_INT_VEC2_EXT 0x8DC6 +#define GL_UNSIGNED_INT_VEC3_EXT 0x8DC7 +#define GL_UNSIGNED_INT_VEC4_EXT 0x8DC8 +#define GL_INT_SAMPLER_1D_EXT 0x8DC9 +#define GL_INT_SAMPLER_2D_EXT 0x8DCA +#define GL_INT_SAMPLER_3D_EXT 0x8DCB +#define GL_INT_SAMPLER_CUBE_EXT 0x8DCC +#define GL_INT_SAMPLER_2D_RECT_EXT 0x8DCD +#define GL_INT_SAMPLER_1D_ARRAY_EXT 0x8DCE +#define GL_INT_SAMPLER_2D_ARRAY_EXT 0x8DCF +#define GL_INT_SAMPLER_BUFFER_EXT 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_1D_EXT 0x8DD1 +#define GL_UNSIGNED_INT_SAMPLER_2D_EXT 0x8DD2 +#define GL_UNSIGNED_INT_SAMPLER_3D_EXT 0x8DD3 +#define GL_UNSIGNED_INT_SAMPLER_CUBE_EXT 0x8DD4 +#define GL_UNSIGNED_INT_SAMPLER_2D_RECT_EXT 0x8DD5 +#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY_EXT 0x8DD6 +#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY_EXT 0x8DD7 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER_EXT 0x8DD8 +#define GL_MIN_PROGRAM_TEXEL_OFFSET_EXT 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET_EXT 0x8905 +typedef void (APIENTRYP PFNGLGETUNIFORMUIVEXTPROC) (GLuint program, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONEXTPROC) (GLuint program, GLuint color, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATALOCATIONEXTPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORM1UIEXTPROC) (GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLUNIFORM2UIEXTPROC) (GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLUNIFORM3UIEXTPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLUNIFORM4UIEXTPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLUNIFORM1UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM2UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM3UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM4UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetUniformuivEXT (GLuint program, GLint location, GLuint *params); +GLAPI void APIENTRY glBindFragDataLocationEXT (GLuint program, GLuint color, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataLocationEXT (GLuint program, const GLchar *name); +GLAPI void APIENTRY glUniform1uiEXT (GLint location, GLuint v0); +GLAPI void APIENTRY glUniform2uiEXT (GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glUniform3uiEXT (GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glUniform4uiEXT (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glUniform1uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform2uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform3uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform4uivEXT (GLint location, GLsizei count, const GLuint *value); +#endif +#endif /* GL_EXT_gpu_shader4 */ + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 +#define GL_HISTOGRAM_EXT 0x8024 +#define GL_PROXY_HISTOGRAM_EXT 0x8025 +#define GL_HISTOGRAM_WIDTH_EXT 0x8026 +#define GL_HISTOGRAM_FORMAT_EXT 0x8027 +#define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C +#define GL_HISTOGRAM_SINK_EXT 0x802D +#define GL_MINMAX_EXT 0x802E +#define GL_MINMAX_FORMAT_EXT 0x802F +#define GL_MINMAX_SINK_EXT 0x8030 +#define GL_TABLE_TOO_LARGE_EXT 0x8031 +typedef void (APIENTRYP PFNGLGETHISTOGRAMEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMEXTPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXEXTPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXEXTPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetHistogramEXT (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +GLAPI void APIENTRY glGetHistogramParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetHistogramParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMinmaxEXT (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +GLAPI void APIENTRY glGetMinmaxParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMinmaxParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glHistogramEXT (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glMinmaxEXT (GLenum target, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glResetHistogramEXT (GLenum target); +GLAPI void APIENTRY glResetMinmaxEXT (GLenum target); +#endif +#endif /* GL_EXT_histogram */ + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 +#define GL_IUI_V2F_EXT 0x81AD +#define GL_IUI_V3F_EXT 0x81AE +#define GL_IUI_N3F_V2F_EXT 0x81AF +#define GL_IUI_N3F_V3F_EXT 0x81B0 +#define GL_T2F_IUI_V2F_EXT 0x81B1 +#define GL_T2F_IUI_V3F_EXT 0x81B2 +#define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 +#define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 +#endif /* GL_EXT_index_array_formats */ + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 +#define GL_INDEX_TEST_EXT 0x81B5 +#define GL_INDEX_TEST_FUNC_EXT 0x81B6 +#define GL_INDEX_TEST_REF_EXT 0x81B7 +typedef void (APIENTRYP PFNGLINDEXFUNCEXTPROC) (GLenum func, GLclampf ref); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexFuncEXT (GLenum func, GLclampf ref); +#endif +#endif /* GL_EXT_index_func */ + +#ifndef GL_EXT_index_material +#define GL_EXT_index_material 1 +#define GL_INDEX_MATERIAL_EXT 0x81B8 +#define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 +#define GL_INDEX_MATERIAL_FACE_EXT 0x81BA +typedef void (APIENTRYP PFNGLINDEXMATERIALEXTPROC) (GLenum face, GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexMaterialEXT (GLenum face, GLenum mode); +#endif +#endif /* GL_EXT_index_material */ + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 +#endif /* GL_EXT_index_texture */ + +#ifndef GL_EXT_light_texture +#define GL_EXT_light_texture 1 +#define GL_FRAGMENT_MATERIAL_EXT 0x8349 +#define GL_FRAGMENT_NORMAL_EXT 0x834A +#define GL_FRAGMENT_COLOR_EXT 0x834C +#define GL_ATTENUATION_EXT 0x834D +#define GL_SHADOW_ATTENUATION_EXT 0x834E +#define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F +#define GL_TEXTURE_LIGHT_EXT 0x8350 +#define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 +#define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 +typedef void (APIENTRYP PFNGLAPPLYTEXTUREEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLTEXTURELIGHTEXTPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLTEXTUREMATERIALEXTPROC) (GLenum face, GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glApplyTextureEXT (GLenum mode); +GLAPI void APIENTRY glTextureLightEXT (GLenum pname); +GLAPI void APIENTRY glTextureMaterialEXT (GLenum face, GLenum mode); +#endif +#endif /* GL_EXT_light_texture */ + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 +#endif /* GL_EXT_misc_attribute */ + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysEXT (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +GLAPI void APIENTRY glMultiDrawElementsEXT (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount); +#endif +#endif /* GL_EXT_multi_draw_arrays */ + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#define GL_SAMPLE_MASK_EXT 0x80A0 +#define GL_1PASS_EXT 0x80A1 +#define GL_2PASS_0_EXT 0x80A2 +#define GL_2PASS_1_EXT 0x80A3 +#define GL_4PASS_0_EXT 0x80A4 +#define GL_4PASS_1_EXT 0x80A5 +#define GL_4PASS_2_EXT 0x80A6 +#define GL_4PASS_3_EXT 0x80A7 +#define GL_SAMPLE_BUFFERS_EXT 0x80A8 +#define GL_SAMPLES_EXT 0x80A9 +#define GL_SAMPLE_MASK_VALUE_EXT 0x80AA +#define GL_SAMPLE_MASK_INVERT_EXT 0x80AB +#define GL_SAMPLE_PATTERN_EXT 0x80AC +#define GL_MULTISAMPLE_BIT_EXT 0x20000000 +typedef void (APIENTRYP PFNGLSAMPLEMASKEXTPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNEXTPROC) (GLenum pattern); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskEXT (GLclampf value, GLboolean invert); +GLAPI void APIENTRY glSamplePatternEXT (GLenum pattern); +#endif +#endif /* GL_EXT_multisample */ + +#ifndef GL_EXT_packed_depth_stencil +#define GL_EXT_packed_depth_stencil 1 +#define GL_DEPTH_STENCIL_EXT 0x84F9 +#define GL_UNSIGNED_INT_24_8_EXT 0x84FA +#define GL_DEPTH24_STENCIL8_EXT 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE_EXT 0x88F1 +#endif /* GL_EXT_packed_depth_stencil */ + +#ifndef GL_EXT_packed_float +#define GL_EXT_packed_float 1 +#define GL_R11F_G11F_B10F_EXT 0x8C3A +#define GL_UNSIGNED_INT_10F_11F_11F_REV_EXT 0x8C3B +#define GL_RGBA_SIGNED_COMPONENTS_EXT 0x8C3C +#endif /* GL_EXT_packed_float */ + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 +#endif /* GL_EXT_packed_pixels */ + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +typedef void (APIENTRYP PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEEXTPROC) (GLenum target, GLenum format, GLenum type, void *data); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableEXT (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void *table); +GLAPI void APIENTRY glGetColorTableEXT (GLenum target, GLenum format, GLenum type, void *data); +GLAPI void APIENTRY glGetColorTableParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetColorTableParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +#endif +#endif /* GL_EXT_paletted_texture */ + +#ifndef GL_EXT_pixel_buffer_object +#define GL_EXT_pixel_buffer_object 1 +#define GL_PIXEL_PACK_BUFFER_EXT 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_EXT 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_EXT 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_EXT 0x88EF +#endif /* GL_EXT_pixel_buffer_object */ + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 +#define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 +#define GL_PIXEL_MAG_FILTER_EXT 0x8331 +#define GL_PIXEL_MIN_FILTER_EXT 0x8332 +#define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 +#define GL_CUBIC_EXT 0x8334 +#define GL_AVERAGE_EXT 0x8335 +#define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 +#define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 +#define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTransformParameteriEXT (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glPixelTransformParameterfEXT (GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glPixelTransformParameterivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glPixelTransformParameterfvEXT (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetPixelTransformParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetPixelTransformParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +#endif +#endif /* GL_EXT_pixel_transform */ + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 +#endif /* GL_EXT_pixel_transform_color_table */ + +#ifndef GL_EXT_point_parameters +#define GL_EXT_point_parameters 1 +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +typedef void (APIENTRYP PFNGLPOINTPARAMETERFEXTPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVEXTPROC) (GLenum pname, const GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfEXT (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvEXT (GLenum pname, const GLfloat *params); +#endif +#endif /* GL_EXT_point_parameters */ + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +typedef void (APIENTRYP PFNGLPOLYGONOFFSETEXTPROC) (GLfloat factor, GLfloat bias); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPolygonOffsetEXT (GLfloat factor, GLfloat bias); +#endif +#endif /* GL_EXT_polygon_offset */ + +#ifndef GL_EXT_provoking_vertex +#define GL_EXT_provoking_vertex 1 +#define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION_EXT 0x8E4C +#define GL_FIRST_VERTEX_CONVENTION_EXT 0x8E4D +#define GL_LAST_VERTEX_CONVENTION_EXT 0x8E4E +#define GL_PROVOKING_VERTEX_EXT 0x8E4F +typedef void (APIENTRYP PFNGLPROVOKINGVERTEXEXTPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProvokingVertexEXT (GLenum mode); +#endif +#endif /* GL_EXT_provoking_vertex */ + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#define GL_RESCALE_NORMAL_EXT 0x803A +#endif /* GL_EXT_rescale_normal */ + +#ifndef GL_EXT_secondary_color +#define GL_EXT_secondary_color 1 +#define GL_COLOR_SUM_EXT 0x8458 +#define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D +#define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BEXTPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DEXTPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FEXTPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IEXTPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SEXTPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBEXTPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVEXTPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIEXTPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVEXTPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USEXTPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVEXTPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSecondaryColor3bEXT (GLbyte red, GLbyte green, GLbyte blue); +GLAPI void APIENTRY glSecondaryColor3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glSecondaryColor3dEXT (GLdouble red, GLdouble green, GLdouble blue); +GLAPI void APIENTRY glSecondaryColor3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glSecondaryColor3fEXT (GLfloat red, GLfloat green, GLfloat blue); +GLAPI void APIENTRY glSecondaryColor3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glSecondaryColor3iEXT (GLint red, GLint green, GLint blue); +GLAPI void APIENTRY glSecondaryColor3ivEXT (const GLint *v); +GLAPI void APIENTRY glSecondaryColor3sEXT (GLshort red, GLshort green, GLshort blue); +GLAPI void APIENTRY glSecondaryColor3svEXT (const GLshort *v); +GLAPI void APIENTRY glSecondaryColor3ubEXT (GLubyte red, GLubyte green, GLubyte blue); +GLAPI void APIENTRY glSecondaryColor3ubvEXT (const GLubyte *v); +GLAPI void APIENTRY glSecondaryColor3uiEXT (GLuint red, GLuint green, GLuint blue); +GLAPI void APIENTRY glSecondaryColor3uivEXT (const GLuint *v); +GLAPI void APIENTRY glSecondaryColor3usEXT (GLushort red, GLushort green, GLushort blue); +GLAPI void APIENTRY glSecondaryColor3usvEXT (const GLushort *v); +GLAPI void APIENTRY glSecondaryColorPointerEXT (GLint size, GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_EXT_secondary_color */ + +#ifndef GL_EXT_separate_shader_objects +#define GL_EXT_separate_shader_objects 1 +#define GL_ACTIVE_PROGRAM_EXT 0x8B8D +typedef void (APIENTRYP PFNGLUSESHADERPROGRAMEXTPROC) (GLenum type, GLuint program); +typedef void (APIENTRYP PFNGLACTIVEPROGRAMEXTPROC) (GLuint program); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROGRAMEXTPROC) (GLenum type, const GLchar *string); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUseShaderProgramEXT (GLenum type, GLuint program); +GLAPI void APIENTRY glActiveProgramEXT (GLuint program); +GLAPI GLuint APIENTRY glCreateShaderProgramEXT (GLenum type, const GLchar *string); +#endif +#endif /* GL_EXT_separate_shader_objects */ + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif /* GL_EXT_separate_specular_color */ + +#ifndef GL_EXT_shader_image_load_store +#define GL_EXT_shader_image_load_store 1 +#define GL_MAX_IMAGE_UNITS_EXT 0x8F38 +#define GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS_EXT 0x8F39 +#define GL_IMAGE_BINDING_NAME_EXT 0x8F3A +#define GL_IMAGE_BINDING_LEVEL_EXT 0x8F3B +#define GL_IMAGE_BINDING_LAYERED_EXT 0x8F3C +#define GL_IMAGE_BINDING_LAYER_EXT 0x8F3D +#define GL_IMAGE_BINDING_ACCESS_EXT 0x8F3E +#define GL_IMAGE_1D_EXT 0x904C +#define GL_IMAGE_2D_EXT 0x904D +#define GL_IMAGE_3D_EXT 0x904E +#define GL_IMAGE_2D_RECT_EXT 0x904F +#define GL_IMAGE_CUBE_EXT 0x9050 +#define GL_IMAGE_BUFFER_EXT 0x9051 +#define GL_IMAGE_1D_ARRAY_EXT 0x9052 +#define GL_IMAGE_2D_ARRAY_EXT 0x9053 +#define GL_IMAGE_CUBE_MAP_ARRAY_EXT 0x9054 +#define GL_IMAGE_2D_MULTISAMPLE_EXT 0x9055 +#define GL_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x9056 +#define GL_INT_IMAGE_1D_EXT 0x9057 +#define GL_INT_IMAGE_2D_EXT 0x9058 +#define GL_INT_IMAGE_3D_EXT 0x9059 +#define GL_INT_IMAGE_2D_RECT_EXT 0x905A +#define GL_INT_IMAGE_CUBE_EXT 0x905B +#define GL_INT_IMAGE_BUFFER_EXT 0x905C +#define GL_INT_IMAGE_1D_ARRAY_EXT 0x905D +#define GL_INT_IMAGE_2D_ARRAY_EXT 0x905E +#define GL_INT_IMAGE_CUBE_MAP_ARRAY_EXT 0x905F +#define GL_INT_IMAGE_2D_MULTISAMPLE_EXT 0x9060 +#define GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x9061 +#define GL_UNSIGNED_INT_IMAGE_1D_EXT 0x9062 +#define GL_UNSIGNED_INT_IMAGE_2D_EXT 0x9063 +#define GL_UNSIGNED_INT_IMAGE_3D_EXT 0x9064 +#define GL_UNSIGNED_INT_IMAGE_2D_RECT_EXT 0x9065 +#define GL_UNSIGNED_INT_IMAGE_CUBE_EXT 0x9066 +#define GL_UNSIGNED_INT_IMAGE_BUFFER_EXT 0x9067 +#define GL_UNSIGNED_INT_IMAGE_1D_ARRAY_EXT 0x9068 +#define GL_UNSIGNED_INT_IMAGE_2D_ARRAY_EXT 0x9069 +#define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY_EXT 0x906A +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_EXT 0x906B +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x906C +#define GL_MAX_IMAGE_SAMPLES_EXT 0x906D +#define GL_IMAGE_BINDING_FORMAT_EXT 0x906E +#define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT_EXT 0x00000001 +#define GL_ELEMENT_ARRAY_BARRIER_BIT_EXT 0x00000002 +#define GL_UNIFORM_BARRIER_BIT_EXT 0x00000004 +#define GL_TEXTURE_FETCH_BARRIER_BIT_EXT 0x00000008 +#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT_EXT 0x00000020 +#define GL_COMMAND_BARRIER_BIT_EXT 0x00000040 +#define GL_PIXEL_BUFFER_BARRIER_BIT_EXT 0x00000080 +#define GL_TEXTURE_UPDATE_BARRIER_BIT_EXT 0x00000100 +#define GL_BUFFER_UPDATE_BARRIER_BIT_EXT 0x00000200 +#define GL_FRAMEBUFFER_BARRIER_BIT_EXT 0x00000400 +#define GL_TRANSFORM_FEEDBACK_BARRIER_BIT_EXT 0x00000800 +#define GL_ATOMIC_COUNTER_BARRIER_BIT_EXT 0x00001000 +#define GL_ALL_BARRIER_BITS_EXT 0xFFFFFFFF +typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREEXTPROC) (GLuint index, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLint format); +typedef void (APIENTRYP PFNGLMEMORYBARRIEREXTPROC) (GLbitfield barriers); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindImageTextureEXT (GLuint index, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLint format); +GLAPI void APIENTRY glMemoryBarrierEXT (GLbitfield barriers); +#endif +#endif /* GL_EXT_shader_image_load_store */ + +#ifndef GL_EXT_shader_integer_mix +#define GL_EXT_shader_integer_mix 1 +#endif /* GL_EXT_shader_integer_mix */ + +#ifndef GL_EXT_shadow_funcs +#define GL_EXT_shadow_funcs 1 +#endif /* GL_EXT_shadow_funcs */ + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif /* GL_EXT_shared_texture_palette */ + +#ifndef GL_EXT_stencil_clear_tag +#define GL_EXT_stencil_clear_tag 1 +#define GL_STENCIL_TAG_BITS_EXT 0x88F2 +#define GL_STENCIL_CLEAR_TAG_VALUE_EXT 0x88F3 +typedef void (APIENTRYP PFNGLSTENCILCLEARTAGEXTPROC) (GLsizei stencilTagBits, GLuint stencilClearTag); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilClearTagEXT (GLsizei stencilTagBits, GLuint stencilClearTag); +#endif +#endif /* GL_EXT_stencil_clear_tag */ + +#ifndef GL_EXT_stencil_two_side +#define GL_EXT_stencil_two_side 1 +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +typedef void (APIENTRYP PFNGLACTIVESTENCILFACEEXTPROC) (GLenum face); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveStencilFaceEXT (GLenum face); +#endif +#endif /* GL_EXT_stencil_two_side */ + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif /* GL_EXT_stencil_wrap */ + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 +typedef void (APIENTRYP PFNGLTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexSubImage1DEXT (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTexSubImage2DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +#endif +#endif /* GL_EXT_subtexture */ + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#define GL_ALPHA4_EXT 0x803B +#define GL_ALPHA8_EXT 0x803C +#define GL_ALPHA12_EXT 0x803D +#define GL_ALPHA16_EXT 0x803E +#define GL_LUMINANCE4_EXT 0x803F +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE12_EXT 0x8041 +#define GL_LUMINANCE16_EXT 0x8042 +#define GL_LUMINANCE4_ALPHA4_EXT 0x8043 +#define GL_LUMINANCE6_ALPHA2_EXT 0x8044 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_LUMINANCE12_ALPHA4_EXT 0x8046 +#define GL_LUMINANCE12_ALPHA12_EXT 0x8047 +#define GL_LUMINANCE16_ALPHA16_EXT 0x8048 +#define GL_INTENSITY_EXT 0x8049 +#define GL_INTENSITY4_EXT 0x804A +#define GL_INTENSITY8_EXT 0x804B +#define GL_INTENSITY12_EXT 0x804C +#define GL_INTENSITY16_EXT 0x804D +#define GL_RGB2_EXT 0x804E +#define GL_RGB4_EXT 0x804F +#define GL_RGB5_EXT 0x8050 +#define GL_RGB8_EXT 0x8051 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB12_EXT 0x8053 +#define GL_RGB16_EXT 0x8054 +#define GL_RGBA2_EXT 0x8055 +#define GL_RGBA4_EXT 0x8056 +#define GL_RGB5_A1_EXT 0x8057 +#define GL_RGBA8_EXT 0x8058 +#define GL_RGB10_A2_EXT 0x8059 +#define GL_RGBA12_EXT 0x805A +#define GL_RGBA16_EXT 0x805B +#define GL_TEXTURE_RED_SIZE_EXT 0x805C +#define GL_TEXTURE_GREEN_SIZE_EXT 0x805D +#define GL_TEXTURE_BLUE_SIZE_EXT 0x805E +#define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 +#define GL_REPLACE_EXT 0x8062 +#define GL_PROXY_TEXTURE_1D_EXT 0x8063 +#define GL_PROXY_TEXTURE_2D_EXT 0x8064 +#define GL_TEXTURE_TOO_LARGE_EXT 0x8065 +#endif /* GL_EXT_texture */ + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +typedef void (APIENTRYP PFNGLTEXIMAGE3DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage3DEXT (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTexSubImage3DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +#endif +#endif /* GL_EXT_texture3D */ + +#ifndef GL_EXT_texture_array +#define GL_EXT_texture_array 1 +#define GL_TEXTURE_1D_ARRAY_EXT 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY_EXT 0x8C19 +#define GL_TEXTURE_2D_ARRAY_EXT 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY_EXT 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY_EXT 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY_EXT 0x8C1D +#define GL_MAX_ARRAY_TEXTURE_LAYERS_EXT 0x88FF +#define GL_COMPARE_REF_DEPTH_TO_TEXTURE_EXT 0x884E +#endif /* GL_EXT_texture_array */ + +#ifndef GL_EXT_texture_buffer_object +#define GL_EXT_texture_buffer_object 1 +#define GL_TEXTURE_BUFFER_EXT 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE_EXT 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER_EXT 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_EXT 0x8C2D +#define GL_TEXTURE_BUFFER_FORMAT_EXT 0x8C2E +typedef void (APIENTRYP PFNGLTEXBUFFEREXTPROC) (GLenum target, GLenum internalformat, GLuint buffer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBufferEXT (GLenum target, GLenum internalformat, GLuint buffer); +#endif +#endif /* GL_EXT_texture_buffer_object */ + +#ifndef GL_EXT_texture_compression_latc +#define GL_EXT_texture_compression_latc 1 +#define GL_COMPRESSED_LUMINANCE_LATC1_EXT 0x8C70 +#define GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT 0x8C71 +#define GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT 0x8C72 +#define GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT 0x8C73 +#endif /* GL_EXT_texture_compression_latc */ + +#ifndef GL_EXT_texture_compression_rgtc +#define GL_EXT_texture_compression_rgtc 1 +#define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC +#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD +#define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE +#endif /* GL_EXT_texture_compression_rgtc */ + +#ifndef GL_EXT_texture_compression_s3tc +#define GL_EXT_texture_compression_s3tc 1 +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif /* GL_EXT_texture_compression_s3tc */ + +#ifndef GL_EXT_texture_cube_map +#define GL_EXT_texture_cube_map 1 +#define GL_NORMAL_MAP_EXT 0x8511 +#define GL_REFLECTION_MAP_EXT 0x8512 +#define GL_TEXTURE_CUBE_MAP_EXT 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C +#endif /* GL_EXT_texture_cube_map */ + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 +#endif /* GL_EXT_texture_env_add */ + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#endif /* GL_EXT_texture_env_combine */ + +#ifndef GL_EXT_texture_env_dot3 +#define GL_EXT_texture_env_dot3 1 +#define GL_DOT3_RGB_EXT 0x8740 +#define GL_DOT3_RGBA_EXT 0x8741 +#endif /* GL_EXT_texture_env_dot3 */ + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif /* GL_EXT_texture_filter_anisotropic */ + +#ifndef GL_EXT_texture_integer +#define GL_EXT_texture_integer 1 +#define GL_RGBA32UI_EXT 0x8D70 +#define GL_RGB32UI_EXT 0x8D71 +#define GL_ALPHA32UI_EXT 0x8D72 +#define GL_INTENSITY32UI_EXT 0x8D73 +#define GL_LUMINANCE32UI_EXT 0x8D74 +#define GL_LUMINANCE_ALPHA32UI_EXT 0x8D75 +#define GL_RGBA16UI_EXT 0x8D76 +#define GL_RGB16UI_EXT 0x8D77 +#define GL_ALPHA16UI_EXT 0x8D78 +#define GL_INTENSITY16UI_EXT 0x8D79 +#define GL_LUMINANCE16UI_EXT 0x8D7A +#define GL_LUMINANCE_ALPHA16UI_EXT 0x8D7B +#define GL_RGBA8UI_EXT 0x8D7C +#define GL_RGB8UI_EXT 0x8D7D +#define GL_ALPHA8UI_EXT 0x8D7E +#define GL_INTENSITY8UI_EXT 0x8D7F +#define GL_LUMINANCE8UI_EXT 0x8D80 +#define GL_LUMINANCE_ALPHA8UI_EXT 0x8D81 +#define GL_RGBA32I_EXT 0x8D82 +#define GL_RGB32I_EXT 0x8D83 +#define GL_ALPHA32I_EXT 0x8D84 +#define GL_INTENSITY32I_EXT 0x8D85 +#define GL_LUMINANCE32I_EXT 0x8D86 +#define GL_LUMINANCE_ALPHA32I_EXT 0x8D87 +#define GL_RGBA16I_EXT 0x8D88 +#define GL_RGB16I_EXT 0x8D89 +#define GL_ALPHA16I_EXT 0x8D8A +#define GL_INTENSITY16I_EXT 0x8D8B +#define GL_LUMINANCE16I_EXT 0x8D8C +#define GL_LUMINANCE_ALPHA16I_EXT 0x8D8D +#define GL_RGBA8I_EXT 0x8D8E +#define GL_RGB8I_EXT 0x8D8F +#define GL_ALPHA8I_EXT 0x8D90 +#define GL_INTENSITY8I_EXT 0x8D91 +#define GL_LUMINANCE8I_EXT 0x8D92 +#define GL_LUMINANCE_ALPHA8I_EXT 0x8D93 +#define GL_RED_INTEGER_EXT 0x8D94 +#define GL_GREEN_INTEGER_EXT 0x8D95 +#define GL_BLUE_INTEGER_EXT 0x8D96 +#define GL_ALPHA_INTEGER_EXT 0x8D97 +#define GL_RGB_INTEGER_EXT 0x8D98 +#define GL_RGBA_INTEGER_EXT 0x8D99 +#define GL_BGR_INTEGER_EXT 0x8D9A +#define GL_BGRA_INTEGER_EXT 0x8D9B +#define GL_LUMINANCE_INTEGER_EXT 0x8D9C +#define GL_LUMINANCE_ALPHA_INTEGER_EXT 0x8D9D +#define GL_RGBA_INTEGER_MODE_EXT 0x8D9E +typedef void (APIENTRYP PFNGLTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLCLEARCOLORIIEXTPROC) (GLint red, GLint green, GLint blue, GLint alpha); +typedef void (APIENTRYP PFNGLCLEARCOLORIUIEXTPROC) (GLuint red, GLuint green, GLuint blue, GLuint alpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexParameterIivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTexParameterIuivEXT (GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTexParameterIivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTexParameterIuivEXT (GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glClearColorIiEXT (GLint red, GLint green, GLint blue, GLint alpha); +GLAPI void APIENTRY glClearColorIuiEXT (GLuint red, GLuint green, GLuint blue, GLuint alpha); +#endif +#endif /* GL_EXT_texture_integer */ + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 +#endif /* GL_EXT_texture_lod_bias */ + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_EXT_texture_mirror_clamp 1 +#define GL_MIRROR_CLAMP_EXT 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_EXT 0x8743 +#define GL_MIRROR_CLAMP_TO_BORDER_EXT 0x8912 +#endif /* GL_EXT_texture_mirror_clamp */ + +#ifndef GL_EXT_texture_object +#define GL_EXT_texture_object 1 +#define GL_TEXTURE_PRIORITY_EXT 0x8066 +#define GL_TEXTURE_RESIDENT_EXT 0x8067 +#define GL_TEXTURE_1D_BINDING_EXT 0x8068 +#define GL_TEXTURE_2D_BINDING_EXT 0x8069 +#define GL_TEXTURE_3D_BINDING_EXT 0x806A +typedef GLboolean (APIENTRYP PFNGLARETEXTURESRESIDENTEXTPROC) (GLsizei n, const GLuint *textures, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDTEXTUREEXTPROC) (GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLDELETETEXTURESEXTPROC) (GLsizei n, const GLuint *textures); +typedef void (APIENTRYP PFNGLGENTEXTURESEXTPROC) (GLsizei n, GLuint *textures); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREEXTPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESEXTPROC) (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreTexturesResidentEXT (GLsizei n, const GLuint *textures, GLboolean *residences); +GLAPI void APIENTRY glBindTextureEXT (GLenum target, GLuint texture); +GLAPI void APIENTRY glDeleteTexturesEXT (GLsizei n, const GLuint *textures); +GLAPI void APIENTRY glGenTexturesEXT (GLsizei n, GLuint *textures); +GLAPI GLboolean APIENTRY glIsTextureEXT (GLuint texture); +GLAPI void APIENTRY glPrioritizeTexturesEXT (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#endif +#endif /* GL_EXT_texture_object */ + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +typedef void (APIENTRYP PFNGLTEXTURENORMALEXTPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureNormalEXT (GLenum mode); +#endif +#endif /* GL_EXT_texture_perturb_normal */ + +#ifndef GL_EXT_texture_sRGB +#define GL_EXT_texture_sRGB 1 +#define GL_SRGB_EXT 0x8C40 +#define GL_SRGB8_EXT 0x8C41 +#define GL_SRGB_ALPHA_EXT 0x8C42 +#define GL_SRGB8_ALPHA8_EXT 0x8C43 +#define GL_SLUMINANCE_ALPHA_EXT 0x8C44 +#define GL_SLUMINANCE8_ALPHA8_EXT 0x8C45 +#define GL_SLUMINANCE_EXT 0x8C46 +#define GL_SLUMINANCE8_EXT 0x8C47 +#define GL_COMPRESSED_SRGB_EXT 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA_EXT 0x8C49 +#define GL_COMPRESSED_SLUMINANCE_EXT 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA_EXT 0x8C4B +#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F +#endif /* GL_EXT_texture_sRGB */ + +#ifndef GL_EXT_texture_sRGB_decode +#define GL_EXT_texture_sRGB_decode 1 +#define GL_TEXTURE_SRGB_DECODE_EXT 0x8A48 +#define GL_DECODE_EXT 0x8A49 +#define GL_SKIP_DECODE_EXT 0x8A4A +#endif /* GL_EXT_texture_sRGB_decode */ + +#ifndef GL_EXT_texture_shared_exponent +#define GL_EXT_texture_shared_exponent 1 +#define GL_RGB9_E5_EXT 0x8C3D +#define GL_UNSIGNED_INT_5_9_9_9_REV_EXT 0x8C3E +#define GL_TEXTURE_SHARED_SIZE_EXT 0x8C3F +#endif /* GL_EXT_texture_shared_exponent */ + +#ifndef GL_EXT_texture_snorm +#define GL_EXT_texture_snorm 1 +#define GL_ALPHA_SNORM 0x9010 +#define GL_LUMINANCE_SNORM 0x9011 +#define GL_LUMINANCE_ALPHA_SNORM 0x9012 +#define GL_INTENSITY_SNORM 0x9013 +#define GL_ALPHA8_SNORM 0x9014 +#define GL_LUMINANCE8_SNORM 0x9015 +#define GL_LUMINANCE8_ALPHA8_SNORM 0x9016 +#define GL_INTENSITY8_SNORM 0x9017 +#define GL_ALPHA16_SNORM 0x9018 +#define GL_LUMINANCE16_SNORM 0x9019 +#define GL_LUMINANCE16_ALPHA16_SNORM 0x901A +#define GL_INTENSITY16_SNORM 0x901B +#define GL_RED_SNORM 0x8F90 +#define GL_RG_SNORM 0x8F91 +#define GL_RGB_SNORM 0x8F92 +#define GL_RGBA_SNORM 0x8F93 +#endif /* GL_EXT_texture_snorm */ + +#ifndef GL_EXT_texture_swizzle +#define GL_EXT_texture_swizzle 1 +#define GL_TEXTURE_SWIZZLE_R_EXT 0x8E42 +#define GL_TEXTURE_SWIZZLE_G_EXT 0x8E43 +#define GL_TEXTURE_SWIZZLE_B_EXT 0x8E44 +#define GL_TEXTURE_SWIZZLE_A_EXT 0x8E45 +#define GL_TEXTURE_SWIZZLE_RGBA_EXT 0x8E46 +#endif /* GL_EXT_texture_swizzle */ + +#ifndef GL_EXT_timer_query +#define GL_EXT_timer_query 1 +#define GL_TIME_ELAPSED_EXT 0x88BF +typedef void (APIENTRYP PFNGLGETQUERYOBJECTI64VEXTPROC) (GLuint id, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUI64VEXTPROC) (GLuint id, GLenum pname, GLuint64 *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetQueryObjecti64vEXT (GLuint id, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glGetQueryObjectui64vEXT (GLuint id, GLenum pname, GLuint64 *params); +#endif +#endif /* GL_EXT_timer_query */ + +#ifndef GL_EXT_transform_feedback +#define GL_EXT_transform_feedback 1 +#define GL_TRANSFORM_FEEDBACK_BUFFER_EXT 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_START_EXT 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE_EXT 0x8C85 +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_EXT 0x8C8F +#define GL_INTERLEAVED_ATTRIBS_EXT 0x8C8C +#define GL_SEPARATE_ATTRIBS_EXT 0x8C8D +#define GL_PRIMITIVES_GENERATED_EXT 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_EXT 0x8C88 +#define GL_RASTERIZER_DISCARD_EXT 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT 0x8C8B +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT 0x8C80 +#define GL_TRANSFORM_FEEDBACK_VARYINGS_EXT 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE_EXT 0x8C7F +#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH_EXT 0x8C76 +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKEXTPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKEXTPROC) (void); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGEEXTPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFEROFFSETEXTPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +typedef void (APIENTRYP PFNGLBINDBUFFERBASEEXTPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSEXTPROC) (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGEXTPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginTransformFeedbackEXT (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedbackEXT (void); +GLAPI void APIENTRY glBindBufferRangeEXT (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferOffsetEXT (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +GLAPI void APIENTRY glBindBufferBaseEXT (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryingsEXT (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); +GLAPI void APIENTRY glGetTransformFeedbackVaryingEXT (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +#endif +#endif /* GL_EXT_transform_feedback */ + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +typedef void (APIENTRYP PFNGLARRAYELEMENTEXTPROC) (GLint i); +typedef void (APIENTRYP PFNGLCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +typedef void (APIENTRYP PFNGLDRAWARRAYSEXTPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTEREXTPROC) (GLsizei stride, GLsizei count, const GLboolean *pointer); +typedef void (APIENTRYP PFNGLGETPOINTERVEXTPROC) (GLenum pname, void **params); +typedef void (APIENTRYP PFNGLINDEXPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const void *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const void *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +typedef void (APIENTRYP PFNGLVERTEXPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glArrayElementEXT (GLint i); +GLAPI void APIENTRY glColorPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +GLAPI void APIENTRY glDrawArraysEXT (GLenum mode, GLint first, GLsizei count); +GLAPI void APIENTRY glEdgeFlagPointerEXT (GLsizei stride, GLsizei count, const GLboolean *pointer); +GLAPI void APIENTRY glGetPointervEXT (GLenum pname, void **params); +GLAPI void APIENTRY glIndexPointerEXT (GLenum type, GLsizei stride, GLsizei count, const void *pointer); +GLAPI void APIENTRY glNormalPointerEXT (GLenum type, GLsizei stride, GLsizei count, const void *pointer); +GLAPI void APIENTRY glTexCoordPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +GLAPI void APIENTRY glVertexPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +#endif +#endif /* GL_EXT_vertex_array */ + +#ifndef GL_EXT_vertex_array_bgra +#define GL_EXT_vertex_array_bgra 1 +#endif /* GL_EXT_vertex_array_bgra */ + +#ifndef GL_EXT_vertex_attrib_64bit +#define GL_EXT_vertex_attrib_64bit 1 +#define GL_DOUBLE_VEC2_EXT 0x8FFC +#define GL_DOUBLE_VEC3_EXT 0x8FFD +#define GL_DOUBLE_VEC4_EXT 0x8FFE +#define GL_DOUBLE_MAT2_EXT 0x8F46 +#define GL_DOUBLE_MAT3_EXT 0x8F47 +#define GL_DOUBLE_MAT4_EXT 0x8F48 +#define GL_DOUBLE_MAT2x3_EXT 0x8F49 +#define GL_DOUBLE_MAT2x4_EXT 0x8F4A +#define GL_DOUBLE_MAT3x2_EXT 0x8F4B +#define GL_DOUBLE_MAT3x4_EXT 0x8F4C +#define GL_DOUBLE_MAT4x2_EXT 0x8F4D +#define GL_DOUBLE_MAT4x3_EXT 0x8F4E +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DEXTPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DEXTPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DEXTPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DEXTPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLPOINTEREXTPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLDVEXTPROC) (GLuint index, GLenum pname, GLdouble *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribL1dEXT (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttribL2dEXT (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttribL3dEXT (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttribL4dEXT (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttribL1dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL2dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL3dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL4dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribLPointerEXT (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glGetVertexAttribLdvEXT (GLuint index, GLenum pname, GLdouble *params); +#endif +#endif /* GL_EXT_vertex_attrib_64bit */ + +#ifndef GL_EXT_vertex_shader +#define GL_EXT_vertex_shader 1 +#define GL_VERTEX_SHADER_EXT 0x8780 +#define GL_VERTEX_SHADER_BINDING_EXT 0x8781 +#define GL_OP_INDEX_EXT 0x8782 +#define GL_OP_NEGATE_EXT 0x8783 +#define GL_OP_DOT3_EXT 0x8784 +#define GL_OP_DOT4_EXT 0x8785 +#define GL_OP_MUL_EXT 0x8786 +#define GL_OP_ADD_EXT 0x8787 +#define GL_OP_MADD_EXT 0x8788 +#define GL_OP_FRAC_EXT 0x8789 +#define GL_OP_MAX_EXT 0x878A +#define GL_OP_MIN_EXT 0x878B +#define GL_OP_SET_GE_EXT 0x878C +#define GL_OP_SET_LT_EXT 0x878D +#define GL_OP_CLAMP_EXT 0x878E +#define GL_OP_FLOOR_EXT 0x878F +#define GL_OP_ROUND_EXT 0x8790 +#define GL_OP_EXP_BASE_2_EXT 0x8791 +#define GL_OP_LOG_BASE_2_EXT 0x8792 +#define GL_OP_POWER_EXT 0x8793 +#define GL_OP_RECIP_EXT 0x8794 +#define GL_OP_RECIP_SQRT_EXT 0x8795 +#define GL_OP_SUB_EXT 0x8796 +#define GL_OP_CROSS_PRODUCT_EXT 0x8797 +#define GL_OP_MULTIPLY_MATRIX_EXT 0x8798 +#define GL_OP_MOV_EXT 0x8799 +#define GL_OUTPUT_VERTEX_EXT 0x879A +#define GL_OUTPUT_COLOR0_EXT 0x879B +#define GL_OUTPUT_COLOR1_EXT 0x879C +#define GL_OUTPUT_TEXTURE_COORD0_EXT 0x879D +#define GL_OUTPUT_TEXTURE_COORD1_EXT 0x879E +#define GL_OUTPUT_TEXTURE_COORD2_EXT 0x879F +#define GL_OUTPUT_TEXTURE_COORD3_EXT 0x87A0 +#define GL_OUTPUT_TEXTURE_COORD4_EXT 0x87A1 +#define GL_OUTPUT_TEXTURE_COORD5_EXT 0x87A2 +#define GL_OUTPUT_TEXTURE_COORD6_EXT 0x87A3 +#define GL_OUTPUT_TEXTURE_COORD7_EXT 0x87A4 +#define GL_OUTPUT_TEXTURE_COORD8_EXT 0x87A5 +#define GL_OUTPUT_TEXTURE_COORD9_EXT 0x87A6 +#define GL_OUTPUT_TEXTURE_COORD10_EXT 0x87A7 +#define GL_OUTPUT_TEXTURE_COORD11_EXT 0x87A8 +#define GL_OUTPUT_TEXTURE_COORD12_EXT 0x87A9 +#define GL_OUTPUT_TEXTURE_COORD13_EXT 0x87AA +#define GL_OUTPUT_TEXTURE_COORD14_EXT 0x87AB +#define GL_OUTPUT_TEXTURE_COORD15_EXT 0x87AC +#define GL_OUTPUT_TEXTURE_COORD16_EXT 0x87AD +#define GL_OUTPUT_TEXTURE_COORD17_EXT 0x87AE +#define GL_OUTPUT_TEXTURE_COORD18_EXT 0x87AF +#define GL_OUTPUT_TEXTURE_COORD19_EXT 0x87B0 +#define GL_OUTPUT_TEXTURE_COORD20_EXT 0x87B1 +#define GL_OUTPUT_TEXTURE_COORD21_EXT 0x87B2 +#define GL_OUTPUT_TEXTURE_COORD22_EXT 0x87B3 +#define GL_OUTPUT_TEXTURE_COORD23_EXT 0x87B4 +#define GL_OUTPUT_TEXTURE_COORD24_EXT 0x87B5 +#define GL_OUTPUT_TEXTURE_COORD25_EXT 0x87B6 +#define GL_OUTPUT_TEXTURE_COORD26_EXT 0x87B7 +#define GL_OUTPUT_TEXTURE_COORD27_EXT 0x87B8 +#define GL_OUTPUT_TEXTURE_COORD28_EXT 0x87B9 +#define GL_OUTPUT_TEXTURE_COORD29_EXT 0x87BA +#define GL_OUTPUT_TEXTURE_COORD30_EXT 0x87BB +#define GL_OUTPUT_TEXTURE_COORD31_EXT 0x87BC +#define GL_OUTPUT_FOG_EXT 0x87BD +#define GL_SCALAR_EXT 0x87BE +#define GL_VECTOR_EXT 0x87BF +#define GL_MATRIX_EXT 0x87C0 +#define GL_VARIANT_EXT 0x87C1 +#define GL_INVARIANT_EXT 0x87C2 +#define GL_LOCAL_CONSTANT_EXT 0x87C3 +#define GL_LOCAL_EXT 0x87C4 +#define GL_MAX_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87C5 +#define GL_MAX_VERTEX_SHADER_VARIANTS_EXT 0x87C6 +#define GL_MAX_VERTEX_SHADER_INVARIANTS_EXT 0x87C7 +#define GL_MAX_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87C8 +#define GL_MAX_VERTEX_SHADER_LOCALS_EXT 0x87C9 +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CA +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_VARIANTS_EXT 0x87CB +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87CC +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INVARIANTS_EXT 0x87CD +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCALS_EXT 0x87CE +#define GL_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CF +#define GL_VERTEX_SHADER_VARIANTS_EXT 0x87D0 +#define GL_VERTEX_SHADER_INVARIANTS_EXT 0x87D1 +#define GL_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87D2 +#define GL_VERTEX_SHADER_LOCALS_EXT 0x87D3 +#define GL_VERTEX_SHADER_OPTIMIZED_EXT 0x87D4 +#define GL_X_EXT 0x87D5 +#define GL_Y_EXT 0x87D6 +#define GL_Z_EXT 0x87D7 +#define GL_W_EXT 0x87D8 +#define GL_NEGATIVE_X_EXT 0x87D9 +#define GL_NEGATIVE_Y_EXT 0x87DA +#define GL_NEGATIVE_Z_EXT 0x87DB +#define GL_NEGATIVE_W_EXT 0x87DC +#define GL_ZERO_EXT 0x87DD +#define GL_ONE_EXT 0x87DE +#define GL_NEGATIVE_ONE_EXT 0x87DF +#define GL_NORMALIZED_RANGE_EXT 0x87E0 +#define GL_FULL_RANGE_EXT 0x87E1 +#define GL_CURRENT_VERTEX_EXT 0x87E2 +#define GL_MVP_MATRIX_EXT 0x87E3 +#define GL_VARIANT_VALUE_EXT 0x87E4 +#define GL_VARIANT_DATATYPE_EXT 0x87E5 +#define GL_VARIANT_ARRAY_STRIDE_EXT 0x87E6 +#define GL_VARIANT_ARRAY_TYPE_EXT 0x87E7 +#define GL_VARIANT_ARRAY_EXT 0x87E8 +#define GL_VARIANT_ARRAY_POINTER_EXT 0x87E9 +#define GL_INVARIANT_VALUE_EXT 0x87EA +#define GL_INVARIANT_DATATYPE_EXT 0x87EB +#define GL_LOCAL_CONSTANT_VALUE_EXT 0x87EC +#define GL_LOCAL_CONSTANT_DATATYPE_EXT 0x87ED +typedef void (APIENTRYP PFNGLBEGINVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLENDVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLBINDVERTEXSHADEREXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLGENVERTEXSHADERSEXTPROC) (GLuint range); +typedef void (APIENTRYP PFNGLDELETEVERTEXSHADEREXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLSHADEROP1EXTPROC) (GLenum op, GLuint res, GLuint arg1); +typedef void (APIENTRYP PFNGLSHADEROP2EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +typedef void (APIENTRYP PFNGLSHADEROP3EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +typedef void (APIENTRYP PFNGLSWIZZLEEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLWRITEMASKEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLINSERTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef void (APIENTRYP PFNGLEXTRACTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef GLuint (APIENTRYP PFNGLGENSYMBOLSEXTPROC) (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); +typedef void (APIENTRYP PFNGLSETINVARIANTEXTPROC) (GLuint id, GLenum type, const void *addr); +typedef void (APIENTRYP PFNGLSETLOCALCONSTANTEXTPROC) (GLuint id, GLenum type, const void *addr); +typedef void (APIENTRYP PFNGLVARIANTBVEXTPROC) (GLuint id, const GLbyte *addr); +typedef void (APIENTRYP PFNGLVARIANTSVEXTPROC) (GLuint id, const GLshort *addr); +typedef void (APIENTRYP PFNGLVARIANTIVEXTPROC) (GLuint id, const GLint *addr); +typedef void (APIENTRYP PFNGLVARIANTFVEXTPROC) (GLuint id, const GLfloat *addr); +typedef void (APIENTRYP PFNGLVARIANTDVEXTPROC) (GLuint id, const GLdouble *addr); +typedef void (APIENTRYP PFNGLVARIANTUBVEXTPROC) (GLuint id, const GLubyte *addr); +typedef void (APIENTRYP PFNGLVARIANTUSVEXTPROC) (GLuint id, const GLushort *addr); +typedef void (APIENTRYP PFNGLVARIANTUIVEXTPROC) (GLuint id, const GLuint *addr); +typedef void (APIENTRYP PFNGLVARIANTPOINTEREXTPROC) (GLuint id, GLenum type, GLuint stride, const void *addr); +typedef void (APIENTRYP PFNGLENABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLBINDLIGHTPARAMETEREXTPROC) (GLenum light, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDMATERIALPARAMETEREXTPROC) (GLenum face, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXGENPARAMETEREXTPROC) (GLenum unit, GLenum coord, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXTUREUNITPARAMETEREXTPROC) (GLenum unit, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDPARAMETEREXTPROC) (GLenum value); +typedef GLboolean (APIENTRYP PFNGLISVARIANTENABLEDEXTPROC) (GLuint id, GLenum cap); +typedef void (APIENTRYP PFNGLGETVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETVARIANTPOINTERVEXTPROC) (GLuint id, GLenum value, void **data); +typedef void (APIENTRYP PFNGLGETINVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETINVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginVertexShaderEXT (void); +GLAPI void APIENTRY glEndVertexShaderEXT (void); +GLAPI void APIENTRY glBindVertexShaderEXT (GLuint id); +GLAPI GLuint APIENTRY glGenVertexShadersEXT (GLuint range); +GLAPI void APIENTRY glDeleteVertexShaderEXT (GLuint id); +GLAPI void APIENTRY glShaderOp1EXT (GLenum op, GLuint res, GLuint arg1); +GLAPI void APIENTRY glShaderOp2EXT (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +GLAPI void APIENTRY glShaderOp3EXT (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +GLAPI void APIENTRY glSwizzleEXT (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +GLAPI void APIENTRY glWriteMaskEXT (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +GLAPI void APIENTRY glInsertComponentEXT (GLuint res, GLuint src, GLuint num); +GLAPI void APIENTRY glExtractComponentEXT (GLuint res, GLuint src, GLuint num); +GLAPI GLuint APIENTRY glGenSymbolsEXT (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); +GLAPI void APIENTRY glSetInvariantEXT (GLuint id, GLenum type, const void *addr); +GLAPI void APIENTRY glSetLocalConstantEXT (GLuint id, GLenum type, const void *addr); +GLAPI void APIENTRY glVariantbvEXT (GLuint id, const GLbyte *addr); +GLAPI void APIENTRY glVariantsvEXT (GLuint id, const GLshort *addr); +GLAPI void APIENTRY glVariantivEXT (GLuint id, const GLint *addr); +GLAPI void APIENTRY glVariantfvEXT (GLuint id, const GLfloat *addr); +GLAPI void APIENTRY glVariantdvEXT (GLuint id, const GLdouble *addr); +GLAPI void APIENTRY glVariantubvEXT (GLuint id, const GLubyte *addr); +GLAPI void APIENTRY glVariantusvEXT (GLuint id, const GLushort *addr); +GLAPI void APIENTRY glVariantuivEXT (GLuint id, const GLuint *addr); +GLAPI void APIENTRY glVariantPointerEXT (GLuint id, GLenum type, GLuint stride, const void *addr); +GLAPI void APIENTRY glEnableVariantClientStateEXT (GLuint id); +GLAPI void APIENTRY glDisableVariantClientStateEXT (GLuint id); +GLAPI GLuint APIENTRY glBindLightParameterEXT (GLenum light, GLenum value); +GLAPI GLuint APIENTRY glBindMaterialParameterEXT (GLenum face, GLenum value); +GLAPI GLuint APIENTRY glBindTexGenParameterEXT (GLenum unit, GLenum coord, GLenum value); +GLAPI GLuint APIENTRY glBindTextureUnitParameterEXT (GLenum unit, GLenum value); +GLAPI GLuint APIENTRY glBindParameterEXT (GLenum value); +GLAPI GLboolean APIENTRY glIsVariantEnabledEXT (GLuint id, GLenum cap); +GLAPI void APIENTRY glGetVariantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetVariantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetVariantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +GLAPI void APIENTRY glGetVariantPointervEXT (GLuint id, GLenum value, void **data); +GLAPI void APIENTRY glGetInvariantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetInvariantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetInvariantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +GLAPI void APIENTRY glGetLocalConstantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetLocalConstantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetLocalConstantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +#endif +#endif /* GL_EXT_vertex_shader */ + +#ifndef GL_EXT_vertex_weighting +#define GL_EXT_vertex_weighting 1 +#define GL_MODELVIEW0_STACK_DEPTH_EXT 0x0BA3 +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW0_MATRIX_EXT 0x0BA6 +#define GL_MODELVIEW1_MATRIX_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW0_EXT 0x1700 +#define GL_MODELVIEW1_EXT 0x850A +#define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B +#define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C +#define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D +#define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E +#define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F +#define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFEXTPROC) (GLfloat weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFVEXTPROC) (const GLfloat *weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexWeightfEXT (GLfloat weight); +GLAPI void APIENTRY glVertexWeightfvEXT (const GLfloat *weight); +GLAPI void APIENTRY glVertexWeightPointerEXT (GLint size, GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_EXT_vertex_weighting */ + +#ifndef GL_EXT_x11_sync_object +#define GL_EXT_x11_sync_object 1 +#define GL_SYNC_X11_FENCE_EXT 0x90E1 +typedef GLsync (APIENTRYP PFNGLIMPORTSYNCEXTPROC) (GLenum external_sync_type, GLintptr external_sync, GLbitfield flags); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLsync APIENTRY glImportSyncEXT (GLenum external_sync_type, GLintptr external_sync, GLbitfield flags); +#endif +#endif /* GL_EXT_x11_sync_object */ + +#ifndef GL_GREMEDY_frame_terminator +#define GL_GREMEDY_frame_terminator 1 +typedef void (APIENTRYP PFNGLFRAMETERMINATORGREMEDYPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFrameTerminatorGREMEDY (void); +#endif +#endif /* GL_GREMEDY_frame_terminator */ + +#ifndef GL_GREMEDY_string_marker +#define GL_GREMEDY_string_marker 1 +typedef void (APIENTRYP PFNGLSTRINGMARKERGREMEDYPROC) (GLsizei len, const void *string); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStringMarkerGREMEDY (GLsizei len, const void *string); +#endif +#endif /* GL_GREMEDY_string_marker */ + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 +#define GL_IGNORE_BORDER_HP 0x8150 +#define GL_CONSTANT_BORDER_HP 0x8151 +#define GL_REPLICATE_BORDER_HP 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 +#endif /* GL_HP_convolution_border_modes */ + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 +#define GL_IMAGE_SCALE_X_HP 0x8155 +#define GL_IMAGE_SCALE_Y_HP 0x8156 +#define GL_IMAGE_TRANSLATE_X_HP 0x8157 +#define GL_IMAGE_TRANSLATE_Y_HP 0x8158 +#define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 +#define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A +#define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B +#define GL_IMAGE_MAG_FILTER_HP 0x815C +#define GL_IMAGE_MIN_FILTER_HP 0x815D +#define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E +#define GL_CUBIC_HP 0x815F +#define GL_AVERAGE_HP 0x8160 +#define GL_IMAGE_TRANSFORM_2D_HP 0x8161 +#define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 +#define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIHPPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFHPPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImageTransformParameteriHP (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glImageTransformParameterfHP (GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glImageTransformParameterivHP (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glImageTransformParameterfvHP (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetImageTransformParameterivHP (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetImageTransformParameterfvHP (GLenum target, GLenum pname, GLfloat *params); +#endif +#endif /* GL_HP_image_transform */ + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#endif /* GL_HP_occlusion_test */ + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 +#define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 +#define GL_TEXTURE_POST_SPECULAR_HP 0x8168 +#define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 +#endif /* GL_HP_texture_lighting */ + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 +#define GL_CULL_VERTEX_IBM 103050 +#endif /* GL_IBM_cull_vertex */ + +#ifndef GL_IBM_multimode_draw_arrays +#define GL_IBM_multimode_draw_arrays 1 +typedef void (APIENTRYP PFNGLMULTIMODEDRAWARRAYSIBMPROC) (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +typedef void (APIENTRYP PFNGLMULTIMODEDRAWELEMENTSIBMPROC) (const GLenum *mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, GLint modestride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiModeDrawArraysIBM (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +GLAPI void APIENTRY glMultiModeDrawElementsIBM (const GLenum *mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, GLint modestride); +#endif +#endif /* GL_IBM_multimode_draw_arrays */ + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 +#define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 +#endif /* GL_IBM_rasterpos_clip */ + +#ifndef GL_IBM_static_data +#define GL_IBM_static_data 1 +#define GL_ALL_STATIC_DATA_IBM 103060 +#define GL_STATIC_VERTEX_ARRAY_IBM 103061 +typedef void (APIENTRYP PFNGLFLUSHSTATICDATAIBMPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushStaticDataIBM (GLenum target); +#endif +#endif /* GL_IBM_static_data */ + +#ifndef GL_IBM_texture_mirrored_repeat +#define GL_IBM_texture_mirrored_repeat 1 +#define GL_MIRRORED_REPEAT_IBM 0x8370 +#endif /* GL_IBM_texture_mirrored_repeat */ + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 +#define GL_VERTEX_ARRAY_LIST_IBM 103070 +#define GL_NORMAL_ARRAY_LIST_IBM 103071 +#define GL_COLOR_ARRAY_LIST_IBM 103072 +#define GL_INDEX_ARRAY_LIST_IBM 103073 +#define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 +#define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 +#define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 +#define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 +#define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 +#define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 +#define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 +#define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 +#define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 +#define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 +#define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 +#define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 +typedef void (APIENTRYP PFNGLCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTERLISTIBMPROC) (GLint stride, const GLboolean **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERLISTIBMPROC) (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLINDEXPOINTERLISTIBMPROC) (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLNORMALPOINTERLISTIBMPROC) (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLVERTEXPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glSecondaryColorPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glEdgeFlagPointerListIBM (GLint stride, const GLboolean **pointer, GLint ptrstride); +GLAPI void APIENTRY glFogCoordPointerListIBM (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glIndexPointerListIBM (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glNormalPointerListIBM (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glTexCoordPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glVertexPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +#endif +#endif /* GL_IBM_vertex_array_lists */ + +#ifndef GL_INGR_blend_func_separate +#define GL_INGR_blend_func_separate 1 +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINGRPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateINGR (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif +#endif /* GL_INGR_blend_func_separate */ + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 +#define GL_RED_MIN_CLAMP_INGR 0x8560 +#define GL_GREEN_MIN_CLAMP_INGR 0x8561 +#define GL_BLUE_MIN_CLAMP_INGR 0x8562 +#define GL_ALPHA_MIN_CLAMP_INGR 0x8563 +#define GL_RED_MAX_CLAMP_INGR 0x8564 +#define GL_GREEN_MAX_CLAMP_INGR 0x8565 +#define GL_BLUE_MAX_CLAMP_INGR 0x8566 +#define GL_ALPHA_MAX_CLAMP_INGR 0x8567 +#endif /* GL_INGR_color_clamp */ + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 +#define GL_INTERLACE_READ_INGR 0x8568 +#endif /* GL_INGR_interlace_read */ + +#ifndef GL_INTEL_fragment_shader_ordering +#define GL_INTEL_fragment_shader_ordering 1 +#endif /* GL_INTEL_fragment_shader_ordering */ + +#ifndef GL_INTEL_map_texture +#define GL_INTEL_map_texture 1 +#define GL_TEXTURE_MEMORY_LAYOUT_INTEL 0x83FF +#define GL_LAYOUT_DEFAULT_INTEL 0 +#define GL_LAYOUT_LINEAR_INTEL 1 +#define GL_LAYOUT_LINEAR_CPU_CACHED_INTEL 2 +typedef void (APIENTRYP PFNGLSYNCTEXTUREINTELPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLUNMAPTEXTURE2DINTELPROC) (GLuint texture, GLint level); +typedef void *(APIENTRYP PFNGLMAPTEXTURE2DINTELPROC) (GLuint texture, GLint level, GLbitfield access, GLint *stride, GLenum *layout); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSyncTextureINTEL (GLuint texture); +GLAPI void APIENTRY glUnmapTexture2DINTEL (GLuint texture, GLint level); +GLAPI void *APIENTRY glMapTexture2DINTEL (GLuint texture, GLint level, GLbitfield access, GLint *stride, GLenum *layout); +#endif +#endif /* GL_INTEL_map_texture */ + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 +#define GL_PARALLEL_ARRAYS_INTEL 0x83F4 +#define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 +#define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 +#define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 +#define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 +typedef void (APIENTRYP PFNGLVERTEXPOINTERVINTELPROC) (GLint size, GLenum type, const void **pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTERVINTELPROC) (GLenum type, const void **pointer); +typedef void (APIENTRYP PFNGLCOLORPOINTERVINTELPROC) (GLint size, GLenum type, const void **pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERVINTELPROC) (GLint size, GLenum type, const void **pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexPointervINTEL (GLint size, GLenum type, const void **pointer); +GLAPI void APIENTRY glNormalPointervINTEL (GLenum type, const void **pointer); +GLAPI void APIENTRY glColorPointervINTEL (GLint size, GLenum type, const void **pointer); +GLAPI void APIENTRY glTexCoordPointervINTEL (GLint size, GLenum type, const void **pointer); +#endif +#endif /* GL_INTEL_parallel_arrays */ + +#ifndef GL_INTEL_performance_query +#define GL_INTEL_performance_query 1 +#define GL_PERFQUERY_SINGLE_CONTEXT_INTEL 0x00000000 +#define GL_PERFQUERY_GLOBAL_CONTEXT_INTEL 0x00000001 +#define GL_PERFQUERY_WAIT_INTEL 0x83FB +#define GL_PERFQUERY_FLUSH_INTEL 0x83FA +#define GL_PERFQUERY_DONOT_FLUSH_INTEL 0x83F9 +#define GL_PERFQUERY_COUNTER_EVENT_INTEL 0x94F0 +#define GL_PERFQUERY_COUNTER_DURATION_NORM_INTEL 0x94F1 +#define GL_PERFQUERY_COUNTER_DURATION_RAW_INTEL 0x94F2 +#define GL_PERFQUERY_COUNTER_THROUGHPUT_INTEL 0x94F3 +#define GL_PERFQUERY_COUNTER_RAW_INTEL 0x94F4 +#define GL_PERFQUERY_COUNTER_TIMESTAMP_INTEL 0x94F5 +#define GL_PERFQUERY_COUNTER_DATA_UINT32_INTEL 0x94F8 +#define GL_PERFQUERY_COUNTER_DATA_UINT64_INTEL 0x94F9 +#define GL_PERFQUERY_COUNTER_DATA_FLOAT_INTEL 0x94FA +#define GL_PERFQUERY_COUNTER_DATA_DOUBLE_INTEL 0x94FB +#define GL_PERFQUERY_COUNTER_DATA_BOOL32_INTEL 0x94FC +#define GL_PERFQUERY_QUERY_NAME_LENGTH_MAX_INTEL 0x94FD +#define GL_PERFQUERY_COUNTER_NAME_LENGTH_MAX_INTEL 0x94FE +#define GL_PERFQUERY_COUNTER_DESC_LENGTH_MAX_INTEL 0x94FF +#define GL_PERFQUERY_GPA_EXTENDED_COUNTERS_INTEL 0x9500 +typedef void (APIENTRYP PFNGLBEGINPERFQUERYINTELPROC) (GLuint queryHandle); +typedef void (APIENTRYP PFNGLCREATEPERFQUERYINTELPROC) (GLuint queryId, GLuint *queryHandle); +typedef void (APIENTRYP PFNGLDELETEPERFQUERYINTELPROC) (GLuint queryHandle); +typedef void (APIENTRYP PFNGLENDPERFQUERYINTELPROC) (GLuint queryHandle); +typedef void (APIENTRYP PFNGLGETFIRSTPERFQUERYIDINTELPROC) (GLuint *queryId); +typedef void (APIENTRYP PFNGLGETNEXTPERFQUERYIDINTELPROC) (GLuint queryId, GLuint *nextQueryId); +typedef void (APIENTRYP PFNGLGETPERFCOUNTERINFOINTELPROC) (GLuint queryId, GLuint counterId, GLuint counterNameLength, GLchar *counterName, GLuint counterDescLength, GLchar *counterDesc, GLuint *counterOffset, GLuint *counterDataSize, GLuint *counterTypeEnum, GLuint *counterDataTypeEnum, GLuint64 *rawCounterMaxValue); +typedef void (APIENTRYP PFNGLGETPERFQUERYDATAINTELPROC) (GLuint queryHandle, GLuint flags, GLsizei dataSize, GLvoid *data, GLuint *bytesWritten); +typedef void (APIENTRYP PFNGLGETPERFQUERYIDBYNAMEINTELPROC) (GLchar *queryName, GLuint *queryId); +typedef void (APIENTRYP PFNGLGETPERFQUERYINFOINTELPROC) (GLuint queryId, GLuint queryNameLength, GLchar *queryName, GLuint *dataSize, GLuint *noCounters, GLuint *noInstances, GLuint *capsMask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginPerfQueryINTEL (GLuint queryHandle); +GLAPI void APIENTRY glCreatePerfQueryINTEL (GLuint queryId, GLuint *queryHandle); +GLAPI void APIENTRY glDeletePerfQueryINTEL (GLuint queryHandle); +GLAPI void APIENTRY glEndPerfQueryINTEL (GLuint queryHandle); +GLAPI void APIENTRY glGetFirstPerfQueryIdINTEL (GLuint *queryId); +GLAPI void APIENTRY glGetNextPerfQueryIdINTEL (GLuint queryId, GLuint *nextQueryId); +GLAPI void APIENTRY glGetPerfCounterInfoINTEL (GLuint queryId, GLuint counterId, GLuint counterNameLength, GLchar *counterName, GLuint counterDescLength, GLchar *counterDesc, GLuint *counterOffset, GLuint *counterDataSize, GLuint *counterTypeEnum, GLuint *counterDataTypeEnum, GLuint64 *rawCounterMaxValue); +GLAPI void APIENTRY glGetPerfQueryDataINTEL (GLuint queryHandle, GLuint flags, GLsizei dataSize, GLvoid *data, GLuint *bytesWritten); +GLAPI void APIENTRY glGetPerfQueryIdByNameINTEL (GLchar *queryName, GLuint *queryId); +GLAPI void APIENTRY glGetPerfQueryInfoINTEL (GLuint queryId, GLuint queryNameLength, GLchar *queryName, GLuint *dataSize, GLuint *noCounters, GLuint *noInstances, GLuint *capsMask); +#endif +#endif /* GL_INTEL_performance_query */ + +#ifndef GL_MESAX_texture_stack +#define GL_MESAX_texture_stack 1 +#define GL_TEXTURE_1D_STACK_MESAX 0x8759 +#define GL_TEXTURE_2D_STACK_MESAX 0x875A +#define GL_PROXY_TEXTURE_1D_STACK_MESAX 0x875B +#define GL_PROXY_TEXTURE_2D_STACK_MESAX 0x875C +#define GL_TEXTURE_1D_STACK_BINDING_MESAX 0x875D +#define GL_TEXTURE_2D_STACK_BINDING_MESAX 0x875E +#endif /* GL_MESAX_texture_stack */ + +#ifndef GL_MESA_pack_invert +#define GL_MESA_pack_invert 1 +#define GL_PACK_INVERT_MESA 0x8758 +#endif /* GL_MESA_pack_invert */ + +#ifndef GL_MESA_resize_buffers +#define GL_MESA_resize_buffers 1 +typedef void (APIENTRYP PFNGLRESIZEBUFFERSMESAPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glResizeBuffersMESA (void); +#endif +#endif /* GL_MESA_resize_buffers */ + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 +typedef void (APIENTRYP PFNGLWINDOWPOS2DMESAPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FMESAPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IMESAPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SMESAPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DMESAPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FMESAPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IMESAPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SMESAPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4DMESAPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLWINDOWPOS4DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4FMESAPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLWINDOWPOS4FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4IMESAPROC) (GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLWINDOWPOS4IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4SMESAPROC) (GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLWINDOWPOS4SVMESAPROC) (const GLshort *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dMESA (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2fMESA (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2iMESA (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos2sMESA (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2svMESA (const GLshort *v); +GLAPI void APIENTRY glWindowPos3dMESA (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3fMESA (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3iMESA (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos3sMESA (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3svMESA (const GLshort *v); +GLAPI void APIENTRY glWindowPos4dMESA (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glWindowPos4dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos4fMESA (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glWindowPos4fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos4iMESA (GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glWindowPos4ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos4sMESA (GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glWindowPos4svMESA (const GLshort *v); +#endif +#endif /* GL_MESA_window_pos */ + +#ifndef GL_MESA_ycbcr_texture +#define GL_MESA_ycbcr_texture 1 +#define GL_UNSIGNED_SHORT_8_8_MESA 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_MESA 0x85BB +#define GL_YCBCR_MESA 0x8757 +#endif /* GL_MESA_ycbcr_texture */ + +#ifndef GL_NVX_conditional_render +#define GL_NVX_conditional_render 1 +typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERNVXPROC) (GLuint id); +typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERNVXPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginConditionalRenderNVX (GLuint id); +GLAPI void APIENTRY glEndConditionalRenderNVX (void); +#endif +#endif /* GL_NVX_conditional_render */ + +#ifndef GL_NV_bindless_multi_draw_indirect +#define GL_NV_bindless_multi_draw_indirect 1 +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTBINDLESSNVPROC) (GLenum mode, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTBINDLESSNVPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysIndirectBindlessNV (GLenum mode, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); +GLAPI void APIENTRY glMultiDrawElementsIndirectBindlessNV (GLenum mode, GLenum type, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); +#endif +#endif /* GL_NV_bindless_multi_draw_indirect */ + +#ifndef GL_NV_bindless_texture +#define GL_NV_bindless_texture 1 +typedef GLuint64 (APIENTRYP PFNGLGETTEXTUREHANDLENVPROC) (GLuint texture); +typedef GLuint64 (APIENTRYP PFNGLGETTEXTURESAMPLERHANDLENVPROC) (GLuint texture, GLuint sampler); +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLERESIDENTNVPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLENONRESIDENTNVPROC) (GLuint64 handle); +typedef GLuint64 (APIENTRYP PFNGLGETIMAGEHANDLENVPROC) (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLERESIDENTNVPROC) (GLuint64 handle, GLenum access); +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLENONRESIDENTNVPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64NVPROC) (GLint location, GLuint64 value); +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64VNVPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64NVPROC) (GLuint program, GLint location, GLuint64 value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREHANDLERESIDENTNVPROC) (GLuint64 handle); +typedef GLboolean (APIENTRYP PFNGLISIMAGEHANDLERESIDENTNVPROC) (GLuint64 handle); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint64 APIENTRY glGetTextureHandleNV (GLuint texture); +GLAPI GLuint64 APIENTRY glGetTextureSamplerHandleNV (GLuint texture, GLuint sampler); +GLAPI void APIENTRY glMakeTextureHandleResidentNV (GLuint64 handle); +GLAPI void APIENTRY glMakeTextureHandleNonResidentNV (GLuint64 handle); +GLAPI GLuint64 APIENTRY glGetImageHandleNV (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +GLAPI void APIENTRY glMakeImageHandleResidentNV (GLuint64 handle, GLenum access); +GLAPI void APIENTRY glMakeImageHandleNonResidentNV (GLuint64 handle); +GLAPI void APIENTRY glUniformHandleui64NV (GLint location, GLuint64 value); +GLAPI void APIENTRY glUniformHandleui64vNV (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glProgramUniformHandleui64NV (GLuint program, GLint location, GLuint64 value); +GLAPI void APIENTRY glProgramUniformHandleui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +GLAPI GLboolean APIENTRY glIsTextureHandleResidentNV (GLuint64 handle); +GLAPI GLboolean APIENTRY glIsImageHandleResidentNV (GLuint64 handle); +#endif +#endif /* GL_NV_bindless_texture */ + +#ifndef GL_NV_blend_equation_advanced +#define GL_NV_blend_equation_advanced 1 +#define GL_BLEND_OVERLAP_NV 0x9281 +#define GL_BLEND_PREMULTIPLIED_SRC_NV 0x9280 +#define GL_BLUE_NV 0x1905 +#define GL_COLORBURN_NV 0x929A +#define GL_COLORDODGE_NV 0x9299 +#define GL_CONJOINT_NV 0x9284 +#define GL_CONTRAST_NV 0x92A1 +#define GL_DARKEN_NV 0x9297 +#define GL_DIFFERENCE_NV 0x929E +#define GL_DISJOINT_NV 0x9283 +#define GL_DST_ATOP_NV 0x928F +#define GL_DST_IN_NV 0x928B +#define GL_DST_NV 0x9287 +#define GL_DST_OUT_NV 0x928D +#define GL_DST_OVER_NV 0x9289 +#define GL_EXCLUSION_NV 0x92A0 +#define GL_GREEN_NV 0x1904 +#define GL_HARDLIGHT_NV 0x929B +#define GL_HARDMIX_NV 0x92A9 +#define GL_HSL_COLOR_NV 0x92AF +#define GL_HSL_HUE_NV 0x92AD +#define GL_HSL_LUMINOSITY_NV 0x92B0 +#define GL_HSL_SATURATION_NV 0x92AE +#define GL_INVERT_OVG_NV 0x92B4 +#define GL_INVERT_RGB_NV 0x92A3 +#define GL_LIGHTEN_NV 0x9298 +#define GL_LINEARBURN_NV 0x92A5 +#define GL_LINEARDODGE_NV 0x92A4 +#define GL_LINEARLIGHT_NV 0x92A7 +#define GL_MINUS_CLAMPED_NV 0x92B3 +#define GL_MINUS_NV 0x929F +#define GL_MULTIPLY_NV 0x9294 +#define GL_OVERLAY_NV 0x9296 +#define GL_PINLIGHT_NV 0x92A8 +#define GL_PLUS_CLAMPED_ALPHA_NV 0x92B2 +#define GL_PLUS_CLAMPED_NV 0x92B1 +#define GL_PLUS_DARKER_NV 0x9292 +#define GL_PLUS_NV 0x9291 +#define GL_RED_NV 0x1903 +#define GL_SCREEN_NV 0x9295 +#define GL_SOFTLIGHT_NV 0x929C +#define GL_SRC_ATOP_NV 0x928E +#define GL_SRC_IN_NV 0x928A +#define GL_SRC_NV 0x9286 +#define GL_SRC_OUT_NV 0x928C +#define GL_SRC_OVER_NV 0x9288 +#define GL_UNCORRELATED_NV 0x9282 +#define GL_VIVIDLIGHT_NV 0x92A6 +#define GL_XOR_NV 0x1506 +typedef void (APIENTRYP PFNGLBLENDPARAMETERINVPROC) (GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLBLENDBARRIERNVPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendParameteriNV (GLenum pname, GLint value); +GLAPI void APIENTRY glBlendBarrierNV (void); +#endif +#endif /* GL_NV_blend_equation_advanced */ + +#ifndef GL_NV_blend_equation_advanced_coherent +#define GL_NV_blend_equation_advanced_coherent 1 +#define GL_BLEND_ADVANCED_COHERENT_NV 0x9285 +#endif /* GL_NV_blend_equation_advanced_coherent */ + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 +#endif /* GL_NV_blend_square */ + +#ifndef GL_NV_compute_program5 +#define GL_NV_compute_program5 1 +#define GL_COMPUTE_PROGRAM_NV 0x90FB +#define GL_COMPUTE_PROGRAM_PARAMETER_BUFFER_NV 0x90FC +#endif /* GL_NV_compute_program5 */ + +#ifndef GL_NV_conditional_render +#define GL_NV_conditional_render 1 +#define GL_QUERY_WAIT_NV 0x8E13 +#define GL_QUERY_NO_WAIT_NV 0x8E14 +#define GL_QUERY_BY_REGION_WAIT_NV 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT_NV 0x8E16 +typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERNVPROC) (GLuint id, GLenum mode); +typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERNVPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginConditionalRenderNV (GLuint id, GLenum mode); +GLAPI void APIENTRY glEndConditionalRenderNV (void); +#endif +#endif /* GL_NV_conditional_render */ + +#ifndef GL_NV_copy_depth_to_color +#define GL_NV_copy_depth_to_color 1 +#define GL_DEPTH_STENCIL_TO_RGBA_NV 0x886E +#define GL_DEPTH_STENCIL_TO_BGRA_NV 0x886F +#endif /* GL_NV_copy_depth_to_color */ + +#ifndef GL_NV_copy_image +#define GL_NV_copy_image 1 +typedef void (APIENTRYP PFNGLCOPYIMAGESUBDATANVPROC) (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyImageSubDataNV (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#endif +#endif /* GL_NV_copy_image */ + +#ifndef GL_NV_deep_texture3D +#define GL_NV_deep_texture3D 1 +#define GL_MAX_DEEP_3D_TEXTURE_WIDTH_HEIGHT_NV 0x90D0 +#define GL_MAX_DEEP_3D_TEXTURE_DEPTH_NV 0x90D1 +#endif /* GL_NV_deep_texture3D */ + +#ifndef GL_NV_depth_buffer_float +#define GL_NV_depth_buffer_float 1 +#define GL_DEPTH_COMPONENT32F_NV 0x8DAB +#define GL_DEPTH32F_STENCIL8_NV 0x8DAC +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV_NV 0x8DAD +#define GL_DEPTH_BUFFER_FLOAT_MODE_NV 0x8DAF +typedef void (APIENTRYP PFNGLDEPTHRANGEDNVPROC) (GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLCLEARDEPTHDNVPROC) (GLdouble depth); +typedef void (APIENTRYP PFNGLDEPTHBOUNDSDNVPROC) (GLdouble zmin, GLdouble zmax); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthRangedNV (GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glClearDepthdNV (GLdouble depth); +GLAPI void APIENTRY glDepthBoundsdNV (GLdouble zmin, GLdouble zmax); +#endif +#endif /* GL_NV_depth_buffer_float */ + +#ifndef GL_NV_depth_clamp +#define GL_NV_depth_clamp 1 +#define GL_DEPTH_CLAMP_NV 0x864F +#endif /* GL_NV_depth_clamp */ + +#ifndef GL_NV_draw_texture +#define GL_NV_draw_texture 1 +typedef void (APIENTRYP PFNGLDRAWTEXTURENVPROC) (GLuint texture, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawTextureNV (GLuint texture, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); +#endif +#endif /* GL_NV_draw_texture */ + +#ifndef GL_NV_evaluators +#define GL_NV_evaluators 1 +#define GL_EVAL_2D_NV 0x86C0 +#define GL_EVAL_TRIANGULAR_2D_NV 0x86C1 +#define GL_MAP_TESSELLATION_NV 0x86C2 +#define GL_MAP_ATTRIB_U_ORDER_NV 0x86C3 +#define GL_MAP_ATTRIB_V_ORDER_NV 0x86C4 +#define GL_EVAL_FRACTIONAL_TESSELLATION_NV 0x86C5 +#define GL_EVAL_VERTEX_ATTRIB0_NV 0x86C6 +#define GL_EVAL_VERTEX_ATTRIB1_NV 0x86C7 +#define GL_EVAL_VERTEX_ATTRIB2_NV 0x86C8 +#define GL_EVAL_VERTEX_ATTRIB3_NV 0x86C9 +#define GL_EVAL_VERTEX_ATTRIB4_NV 0x86CA +#define GL_EVAL_VERTEX_ATTRIB5_NV 0x86CB +#define GL_EVAL_VERTEX_ATTRIB6_NV 0x86CC +#define GL_EVAL_VERTEX_ATTRIB7_NV 0x86CD +#define GL_EVAL_VERTEX_ATTRIB8_NV 0x86CE +#define GL_EVAL_VERTEX_ATTRIB9_NV 0x86CF +#define GL_EVAL_VERTEX_ATTRIB10_NV 0x86D0 +#define GL_EVAL_VERTEX_ATTRIB11_NV 0x86D1 +#define GL_EVAL_VERTEX_ATTRIB12_NV 0x86D2 +#define GL_EVAL_VERTEX_ATTRIB13_NV 0x86D3 +#define GL_EVAL_VERTEX_ATTRIB14_NV 0x86D4 +#define GL_EVAL_VERTEX_ATTRIB15_NV 0x86D5 +#define GL_MAX_MAP_TESSELLATION_NV 0x86D6 +#define GL_MAX_RATIONAL_EVAL_ORDER_NV 0x86D7 +typedef void (APIENTRYP PFNGLMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const void *points); +typedef void (APIENTRYP PFNGLMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, void *points); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERIVNVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLEVALMAPSNVPROC) (GLenum target, GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMapControlPointsNV (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const void *points); +GLAPI void APIENTRY glMapParameterivNV (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMapParameterfvNV (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetMapControlPointsNV (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, void *points); +GLAPI void APIENTRY glGetMapParameterivNV (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMapParameterfvNV (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMapAttribParameterivNV (GLenum target, GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMapAttribParameterfvNV (GLenum target, GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glEvalMapsNV (GLenum target, GLenum mode); +#endif +#endif /* GL_NV_evaluators */ + +#ifndef GL_NV_explicit_multisample +#define GL_NV_explicit_multisample 1 +#define GL_SAMPLE_POSITION_NV 0x8E50 +#define GL_SAMPLE_MASK_NV 0x8E51 +#define GL_SAMPLE_MASK_VALUE_NV 0x8E52 +#define GL_TEXTURE_BINDING_RENDERBUFFER_NV 0x8E53 +#define GL_TEXTURE_RENDERBUFFER_DATA_STORE_BINDING_NV 0x8E54 +#define GL_TEXTURE_RENDERBUFFER_NV 0x8E55 +#define GL_SAMPLER_RENDERBUFFER_NV 0x8E56 +#define GL_INT_SAMPLER_RENDERBUFFER_NV 0x8E57 +#define GL_UNSIGNED_INT_SAMPLER_RENDERBUFFER_NV 0x8E58 +#define GL_MAX_SAMPLE_MASK_WORDS_NV 0x8E59 +typedef void (APIENTRYP PFNGLGETMULTISAMPLEFVNVPROC) (GLenum pname, GLuint index, GLfloat *val); +typedef void (APIENTRYP PFNGLSAMPLEMASKINDEXEDNVPROC) (GLuint index, GLbitfield mask); +typedef void (APIENTRYP PFNGLTEXRENDERBUFFERNVPROC) (GLenum target, GLuint renderbuffer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetMultisamplefvNV (GLenum pname, GLuint index, GLfloat *val); +GLAPI void APIENTRY glSampleMaskIndexedNV (GLuint index, GLbitfield mask); +GLAPI void APIENTRY glTexRenderbufferNV (GLenum target, GLuint renderbuffer); +#endif +#endif /* GL_NV_explicit_multisample */ + +#ifndef GL_NV_fence +#define GL_NV_fence 1 +#define GL_ALL_COMPLETED_NV 0x84F2 +#define GL_FENCE_STATUS_NV 0x84F3 +#define GL_FENCE_CONDITION_NV 0x84F4 +typedef void (APIENTRYP PFNGLDELETEFENCESNVPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLGENFENCESNVPROC) (GLsizei n, GLuint *fences); +typedef GLboolean (APIENTRYP PFNGLISFENCENVPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLGETFENCEIVNVPROC) (GLuint fence, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFINISHFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLSETFENCENVPROC) (GLuint fence, GLenum condition); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteFencesNV (GLsizei n, const GLuint *fences); +GLAPI void APIENTRY glGenFencesNV (GLsizei n, GLuint *fences); +GLAPI GLboolean APIENTRY glIsFenceNV (GLuint fence); +GLAPI GLboolean APIENTRY glTestFenceNV (GLuint fence); +GLAPI void APIENTRY glGetFenceivNV (GLuint fence, GLenum pname, GLint *params); +GLAPI void APIENTRY glFinishFenceNV (GLuint fence); +GLAPI void APIENTRY glSetFenceNV (GLuint fence, GLenum condition); +#endif +#endif /* GL_NV_fence */ + +#ifndef GL_NV_float_buffer +#define GL_NV_float_buffer 1 +#define GL_FLOAT_R_NV 0x8880 +#define GL_FLOAT_RG_NV 0x8881 +#define GL_FLOAT_RGB_NV 0x8882 +#define GL_FLOAT_RGBA_NV 0x8883 +#define GL_FLOAT_R16_NV 0x8884 +#define GL_FLOAT_R32_NV 0x8885 +#define GL_FLOAT_RG16_NV 0x8886 +#define GL_FLOAT_RG32_NV 0x8887 +#define GL_FLOAT_RGB16_NV 0x8888 +#define GL_FLOAT_RGB32_NV 0x8889 +#define GL_FLOAT_RGBA16_NV 0x888A +#define GL_FLOAT_RGBA32_NV 0x888B +#define GL_TEXTURE_FLOAT_COMPONENTS_NV 0x888C +#define GL_FLOAT_CLEAR_COLOR_VALUE_NV 0x888D +#define GL_FLOAT_RGBA_MODE_NV 0x888E +#endif /* GL_NV_float_buffer */ + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +#endif /* GL_NV_fog_distance */ + +#ifndef GL_NV_fragment_program +#define GL_NV_fragment_program 1 +#define GL_MAX_FRAGMENT_PROGRAM_LOCAL_PARAMETERS_NV 0x8868 +#define GL_FRAGMENT_PROGRAM_NV 0x8870 +#define GL_MAX_TEXTURE_COORDS_NV 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_NV 0x8872 +#define GL_FRAGMENT_PROGRAM_BINDING_NV 0x8873 +#define GL_PROGRAM_ERROR_STRING_NV 0x8874 +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramNamedParameter4fNV (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramNamedParameter4fvNV (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); +GLAPI void APIENTRY glProgramNamedParameter4dNV (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramNamedParameter4dvNV (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); +GLAPI void APIENTRY glGetProgramNamedParameterfvNV (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); +GLAPI void APIENTRY glGetProgramNamedParameterdvNV (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); +#endif +#endif /* GL_NV_fragment_program */ + +#ifndef GL_NV_fragment_program2 +#define GL_NV_fragment_program2 1 +#define GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV 0x88F4 +#define GL_MAX_PROGRAM_CALL_DEPTH_NV 0x88F5 +#define GL_MAX_PROGRAM_IF_DEPTH_NV 0x88F6 +#define GL_MAX_PROGRAM_LOOP_DEPTH_NV 0x88F7 +#define GL_MAX_PROGRAM_LOOP_COUNT_NV 0x88F8 +#endif /* GL_NV_fragment_program2 */ + +#ifndef GL_NV_fragment_program4 +#define GL_NV_fragment_program4 1 +#endif /* GL_NV_fragment_program4 */ + +#ifndef GL_NV_fragment_program_option +#define GL_NV_fragment_program_option 1 +#endif /* GL_NV_fragment_program_option */ + +#ifndef GL_NV_framebuffer_multisample_coverage +#define GL_NV_framebuffer_multisample_coverage 1 +#define GL_RENDERBUFFER_COVERAGE_SAMPLES_NV 0x8CAB +#define GL_RENDERBUFFER_COLOR_SAMPLES_NV 0x8E10 +#define GL_MAX_MULTISAMPLE_COVERAGE_MODES_NV 0x8E11 +#define GL_MULTISAMPLE_COVERAGE_MODES_NV 0x8E12 +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRenderbufferStorageMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +#endif +#endif /* GL_NV_framebuffer_multisample_coverage */ + +#ifndef GL_NV_geometry_program4 +#define GL_NV_geometry_program4 1 +#define GL_GEOMETRY_PROGRAM_NV 0x8C26 +#define GL_MAX_PROGRAM_OUTPUT_VERTICES_NV 0x8C27 +#define GL_MAX_PROGRAM_TOTAL_OUTPUT_COMPONENTS_NV 0x8C28 +typedef void (APIENTRYP PFNGLPROGRAMVERTEXLIMITNVPROC) (GLenum target, GLint limit); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYEREXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREFACEEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramVertexLimitNV (GLenum target, GLint limit); +GLAPI void APIENTRY glFramebufferTextureEXT (GLenum target, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTextureLayerEXT (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glFramebufferTextureFaceEXT (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#endif +#endif /* GL_NV_geometry_program4 */ + +#ifndef GL_NV_geometry_shader4 +#define GL_NV_geometry_shader4 1 +#endif /* GL_NV_geometry_shader4 */ + +#ifndef GL_NV_gpu_program4 +#define GL_NV_gpu_program4 1 +#define GL_MIN_PROGRAM_TEXEL_OFFSET_NV 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET_NV 0x8905 +#define GL_PROGRAM_ATTRIB_COMPONENTS_NV 0x8906 +#define GL_PROGRAM_RESULT_COMPONENTS_NV 0x8907 +#define GL_MAX_PROGRAM_ATTRIB_COMPONENTS_NV 0x8908 +#define GL_MAX_PROGRAM_RESULT_COMPONENTS_NV 0x8909 +#define GL_MAX_PROGRAM_GENERIC_ATTRIBS_NV 0x8DA5 +#define GL_MAX_PROGRAM_GENERIC_RESULTS_NV 0x8DA6 +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4INVPROC) (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4IVNVPROC) (GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERSI4IVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4UINVPROC) (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4UIVNVPROC) (GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERSI4UIVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4INVPROC) (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4IVNVPROC) (GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERSI4IVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4UINVPROC) (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4UIVNVPROC) (GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERSI4UIVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERIIVNVPROC) (GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERIUIVNVPROC) (GLenum target, GLuint index, GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERIIVNVPROC) (GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERIUIVNVPROC) (GLenum target, GLuint index, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramLocalParameterI4iNV (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glProgramLocalParameterI4ivNV (GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glProgramLocalParametersI4ivNV (GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramLocalParameterI4uiNV (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glProgramLocalParameterI4uivNV (GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glProgramLocalParametersI4uivNV (GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glProgramEnvParameterI4iNV (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glProgramEnvParameterI4ivNV (GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glProgramEnvParametersI4ivNV (GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramEnvParameterI4uiNV (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glProgramEnvParameterI4uivNV (GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glProgramEnvParametersI4uivNV (GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetProgramLocalParameterIivNV (GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetProgramLocalParameterIuivNV (GLenum target, GLuint index, GLuint *params); +GLAPI void APIENTRY glGetProgramEnvParameterIivNV (GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetProgramEnvParameterIuivNV (GLenum target, GLuint index, GLuint *params); +#endif +#endif /* GL_NV_gpu_program4 */ + +#ifndef GL_NV_gpu_program5 +#define GL_NV_gpu_program5 1 +#define GL_MAX_GEOMETRY_PROGRAM_INVOCATIONS_NV 0x8E5A +#define GL_MIN_FRAGMENT_INTERPOLATION_OFFSET_NV 0x8E5B +#define GL_MAX_FRAGMENT_INTERPOLATION_OFFSET_NV 0x8E5C +#define GL_FRAGMENT_PROGRAM_INTERPOLATION_OFFSET_BITS_NV 0x8E5D +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET_NV 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET_NV 0x8E5F +#define GL_MAX_PROGRAM_SUBROUTINE_PARAMETERS_NV 0x8F44 +#define GL_MAX_PROGRAM_SUBROUTINE_NUM_NV 0x8F45 +typedef void (APIENTRYP PFNGLPROGRAMSUBROUTINEPARAMETERSUIVNVPROC) (GLenum target, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSUBROUTINEPARAMETERUIVNVPROC) (GLenum target, GLuint index, GLuint *param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramSubroutineParametersuivNV (GLenum target, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetProgramSubroutineParameteruivNV (GLenum target, GLuint index, GLuint *param); +#endif +#endif /* GL_NV_gpu_program5 */ + +#ifndef GL_NV_gpu_program5_mem_extended +#define GL_NV_gpu_program5_mem_extended 1 +#endif /* GL_NV_gpu_program5_mem_extended */ + +#ifndef GL_NV_gpu_shader5 +#define GL_NV_gpu_shader5 1 +typedef int64_t GLint64EXT; +#define GL_INT64_NV 0x140E +#define GL_UNSIGNED_INT64_NV 0x140F +#define GL_INT8_NV 0x8FE0 +#define GL_INT8_VEC2_NV 0x8FE1 +#define GL_INT8_VEC3_NV 0x8FE2 +#define GL_INT8_VEC4_NV 0x8FE3 +#define GL_INT16_NV 0x8FE4 +#define GL_INT16_VEC2_NV 0x8FE5 +#define GL_INT16_VEC3_NV 0x8FE6 +#define GL_INT16_VEC4_NV 0x8FE7 +#define GL_INT64_VEC2_NV 0x8FE9 +#define GL_INT64_VEC3_NV 0x8FEA +#define GL_INT64_VEC4_NV 0x8FEB +#define GL_UNSIGNED_INT8_NV 0x8FEC +#define GL_UNSIGNED_INT8_VEC2_NV 0x8FED +#define GL_UNSIGNED_INT8_VEC3_NV 0x8FEE +#define GL_UNSIGNED_INT8_VEC4_NV 0x8FEF +#define GL_UNSIGNED_INT16_NV 0x8FF0 +#define GL_UNSIGNED_INT16_VEC2_NV 0x8FF1 +#define GL_UNSIGNED_INT16_VEC3_NV 0x8FF2 +#define GL_UNSIGNED_INT16_VEC4_NV 0x8FF3 +#define GL_UNSIGNED_INT64_VEC2_NV 0x8FF5 +#define GL_UNSIGNED_INT64_VEC3_NV 0x8FF6 +#define GL_UNSIGNED_INT64_VEC4_NV 0x8FF7 +#define GL_FLOAT16_NV 0x8FF8 +#define GL_FLOAT16_VEC2_NV 0x8FF9 +#define GL_FLOAT16_VEC3_NV 0x8FFA +#define GL_FLOAT16_VEC4_NV 0x8FFB +typedef void (APIENTRYP PFNGLUNIFORM1I64NVPROC) (GLint location, GLint64EXT x); +typedef void (APIENTRYP PFNGLUNIFORM2I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLUNIFORM3I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLUNIFORM4I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLUNIFORM1I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM2I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM3I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM4I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM1UI64NVPROC) (GLint location, GLuint64EXT x); +typedef void (APIENTRYP PFNGLUNIFORM2UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLUNIFORM3UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLUNIFORM4UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLUNIFORM1UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM2UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM3UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM4UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLGETUNIFORMI64VNVPROC) (GLuint program, GLint location, GLint64EXT *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64NVPROC) (GLuint program, GLint location, GLint64EXT x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniform1i64NV (GLint location, GLint64EXT x); +GLAPI void APIENTRY glUniform2i64NV (GLint location, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glUniform3i64NV (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glUniform4i64NV (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glUniform1i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform2i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform3i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform4i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform1ui64NV (GLint location, GLuint64EXT x); +GLAPI void APIENTRY glUniform2ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glUniform3ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glUniform4ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glUniform1ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform2ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform3ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform4ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glGetUniformi64vNV (GLuint program, GLint location, GLint64EXT *params); +GLAPI void APIENTRY glProgramUniform1i64NV (GLuint program, GLint location, GLint64EXT x); +GLAPI void APIENTRY glProgramUniform2i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glProgramUniform3i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glProgramUniform4i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glProgramUniform1i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform2i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform3i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform4i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform1ui64NV (GLuint program, GLint location, GLuint64EXT x); +GLAPI void APIENTRY glProgramUniform2ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glProgramUniform3ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glProgramUniform4ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glProgramUniform1ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform2ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform3ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform4ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif +#endif /* GL_NV_gpu_shader5 */ + +#ifndef GL_NV_half_float +#define GL_NV_half_float 1 +typedef unsigned short GLhalfNV; +#define GL_HALF_FLOAT_NV 0x140B +typedef void (APIENTRYP PFNGLVERTEX2HNVPROC) (GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEX2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX3HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEX3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX4HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEX4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLNORMAL3HNVPROC) (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); +typedef void (APIENTRYP PFNGLNORMAL3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR4HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); +typedef void (APIENTRYP PFNGLCOLOR4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD1HNVPROC) (GLhalfNV s); +typedef void (APIENTRYP PFNGLTEXCOORD1HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD2HNVPROC) (GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLTEXCOORD2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD3HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLTEXCOORD3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD4HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLTEXCOORD4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HNVPROC) (GLenum target, GLhalfNV s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLFOGCOORDHNVPROC) (GLhalfNV fog); +typedef void (APIENTRYP PFNGLFOGCOORDHVNVPROC) (const GLhalfNV *fog); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHNVPROC) (GLhalfNV weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHVNVPROC) (const GLhalfNV *weight); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HNVPROC) (GLuint index, GLhalfNV x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertex2hNV (GLhalfNV x, GLhalfNV y); +GLAPI void APIENTRY glVertex2hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertex3hNV (GLhalfNV x, GLhalfNV y, GLhalfNV z); +GLAPI void APIENTRY glVertex3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertex4hNV (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +GLAPI void APIENTRY glVertex4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glNormal3hNV (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); +GLAPI void APIENTRY glNormal3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glColor3hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +GLAPI void APIENTRY glColor3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glColor4hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); +GLAPI void APIENTRY glColor4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord1hNV (GLhalfNV s); +GLAPI void APIENTRY glTexCoord1hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord2hNV (GLhalfNV s, GLhalfNV t); +GLAPI void APIENTRY glTexCoord2hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord3hNV (GLhalfNV s, GLhalfNV t, GLhalfNV r); +GLAPI void APIENTRY glTexCoord3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord4hNV (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +GLAPI void APIENTRY glTexCoord4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord1hNV (GLenum target, GLhalfNV s); +GLAPI void APIENTRY glMultiTexCoord1hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord2hNV (GLenum target, GLhalfNV s, GLhalfNV t); +GLAPI void APIENTRY glMultiTexCoord2hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord3hNV (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); +GLAPI void APIENTRY glMultiTexCoord3hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord4hNV (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +GLAPI void APIENTRY glMultiTexCoord4hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glFogCoordhNV (GLhalfNV fog); +GLAPI void APIENTRY glFogCoordhvNV (const GLhalfNV *fog); +GLAPI void APIENTRY glSecondaryColor3hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +GLAPI void APIENTRY glSecondaryColor3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertexWeighthNV (GLhalfNV weight); +GLAPI void APIENTRY glVertexWeighthvNV (const GLhalfNV *weight); +GLAPI void APIENTRY glVertexAttrib1hNV (GLuint index, GLhalfNV x); +GLAPI void APIENTRY glVertexAttrib1hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib2hNV (GLuint index, GLhalfNV x, GLhalfNV y); +GLAPI void APIENTRY glVertexAttrib2hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib3hNV (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); +GLAPI void APIENTRY glVertexAttrib3hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib4hNV (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +GLAPI void APIENTRY glVertexAttrib4hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs1hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs2hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs3hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs4hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +#endif +#endif /* GL_NV_half_float */ + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif /* GL_NV_light_max_exponent */ + +#ifndef GL_NV_multisample_coverage +#define GL_NV_multisample_coverage 1 +#define GL_COLOR_SAMPLES_NV 0x8E20 +#endif /* GL_NV_multisample_coverage */ + +#ifndef GL_NV_multisample_filter_hint +#define GL_NV_multisample_filter_hint 1 +#define GL_MULTISAMPLE_FILTER_HINT_NV 0x8534 +#endif /* GL_NV_multisample_filter_hint */ + +#ifndef GL_NV_occlusion_query +#define GL_NV_occlusion_query 1 +#define GL_PIXEL_COUNTER_BITS_NV 0x8864 +#define GL_CURRENT_OCCLUSION_QUERY_ID_NV 0x8865 +#define GL_PIXEL_COUNT_NV 0x8866 +#define GL_PIXEL_COUNT_AVAILABLE_NV 0x8867 +typedef void (APIENTRYP PFNGLGENOCCLUSIONQUERIESNVPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEOCCLUSIONQUERIESNVPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLENDOCCLUSIONQUERYNVPROC) (void); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYUIVNVPROC) (GLuint id, GLenum pname, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenOcclusionQueriesNV (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteOcclusionQueriesNV (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsOcclusionQueryNV (GLuint id); +GLAPI void APIENTRY glBeginOcclusionQueryNV (GLuint id); +GLAPI void APIENTRY glEndOcclusionQueryNV (void); +GLAPI void APIENTRY glGetOcclusionQueryivNV (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetOcclusionQueryuivNV (GLuint id, GLenum pname, GLuint *params); +#endif +#endif /* GL_NV_occlusion_query */ + +#ifndef GL_NV_packed_depth_stencil +#define GL_NV_packed_depth_stencil 1 +#define GL_DEPTH_STENCIL_NV 0x84F9 +#define GL_UNSIGNED_INT_24_8_NV 0x84FA +#endif /* GL_NV_packed_depth_stencil */ + +#ifndef GL_NV_parameter_buffer_object +#define GL_NV_parameter_buffer_object 1 +#define GL_MAX_PROGRAM_PARAMETER_BUFFER_BINDINGS_NV 0x8DA0 +#define GL_MAX_PROGRAM_PARAMETER_BUFFER_SIZE_NV 0x8DA1 +#define GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV 0x8DA2 +#define GL_GEOMETRY_PROGRAM_PARAMETER_BUFFER_NV 0x8DA3 +#define GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV 0x8DA4 +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSFVNVPROC) (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSIIVNVPROC) (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSIUIVNVPROC) (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramBufferParametersfvNV (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glProgramBufferParametersIivNV (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramBufferParametersIuivNV (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLuint *params); +#endif +#endif /* GL_NV_parameter_buffer_object */ + +#ifndef GL_NV_parameter_buffer_object2 +#define GL_NV_parameter_buffer_object2 1 +#endif /* GL_NV_parameter_buffer_object2 */ + +#ifndef GL_NV_path_rendering +#define GL_NV_path_rendering 1 +#define GL_PATH_FORMAT_SVG_NV 0x9070 +#define GL_PATH_FORMAT_PS_NV 0x9071 +#define GL_STANDARD_FONT_NAME_NV 0x9072 +#define GL_SYSTEM_FONT_NAME_NV 0x9073 +#define GL_FILE_NAME_NV 0x9074 +#define GL_PATH_STROKE_WIDTH_NV 0x9075 +#define GL_PATH_END_CAPS_NV 0x9076 +#define GL_PATH_INITIAL_END_CAP_NV 0x9077 +#define GL_PATH_TERMINAL_END_CAP_NV 0x9078 +#define GL_PATH_JOIN_STYLE_NV 0x9079 +#define GL_PATH_MITER_LIMIT_NV 0x907A +#define GL_PATH_DASH_CAPS_NV 0x907B +#define GL_PATH_INITIAL_DASH_CAP_NV 0x907C +#define GL_PATH_TERMINAL_DASH_CAP_NV 0x907D +#define GL_PATH_DASH_OFFSET_NV 0x907E +#define GL_PATH_CLIENT_LENGTH_NV 0x907F +#define GL_PATH_FILL_MODE_NV 0x9080 +#define GL_PATH_FILL_MASK_NV 0x9081 +#define GL_PATH_FILL_COVER_MODE_NV 0x9082 +#define GL_PATH_STROKE_COVER_MODE_NV 0x9083 +#define GL_PATH_STROKE_MASK_NV 0x9084 +#define GL_COUNT_UP_NV 0x9088 +#define GL_COUNT_DOWN_NV 0x9089 +#define GL_PATH_OBJECT_BOUNDING_BOX_NV 0x908A +#define GL_CONVEX_HULL_NV 0x908B +#define GL_BOUNDING_BOX_NV 0x908D +#define GL_TRANSLATE_X_NV 0x908E +#define GL_TRANSLATE_Y_NV 0x908F +#define GL_TRANSLATE_2D_NV 0x9090 +#define GL_TRANSLATE_3D_NV 0x9091 +#define GL_AFFINE_2D_NV 0x9092 +#define GL_AFFINE_3D_NV 0x9094 +#define GL_TRANSPOSE_AFFINE_2D_NV 0x9096 +#define GL_TRANSPOSE_AFFINE_3D_NV 0x9098 +#define GL_UTF8_NV 0x909A +#define GL_UTF16_NV 0x909B +#define GL_BOUNDING_BOX_OF_BOUNDING_BOXES_NV 0x909C +#define GL_PATH_COMMAND_COUNT_NV 0x909D +#define GL_PATH_COORD_COUNT_NV 0x909E +#define GL_PATH_DASH_ARRAY_COUNT_NV 0x909F +#define GL_PATH_COMPUTED_LENGTH_NV 0x90A0 +#define GL_PATH_FILL_BOUNDING_BOX_NV 0x90A1 +#define GL_PATH_STROKE_BOUNDING_BOX_NV 0x90A2 +#define GL_SQUARE_NV 0x90A3 +#define GL_ROUND_NV 0x90A4 +#define GL_TRIANGULAR_NV 0x90A5 +#define GL_BEVEL_NV 0x90A6 +#define GL_MITER_REVERT_NV 0x90A7 +#define GL_MITER_TRUNCATE_NV 0x90A8 +#define GL_SKIP_MISSING_GLYPH_NV 0x90A9 +#define GL_USE_MISSING_GLYPH_NV 0x90AA +#define GL_PATH_ERROR_POSITION_NV 0x90AB +#define GL_PATH_FOG_GEN_MODE_NV 0x90AC +#define GL_ACCUM_ADJACENT_PAIRS_NV 0x90AD +#define GL_ADJACENT_PAIRS_NV 0x90AE +#define GL_FIRST_TO_REST_NV 0x90AF +#define GL_PATH_GEN_MODE_NV 0x90B0 +#define GL_PATH_GEN_COEFF_NV 0x90B1 +#define GL_PATH_GEN_COLOR_FORMAT_NV 0x90B2 +#define GL_PATH_GEN_COMPONENTS_NV 0x90B3 +#define GL_PATH_STENCIL_FUNC_NV 0x90B7 +#define GL_PATH_STENCIL_REF_NV 0x90B8 +#define GL_PATH_STENCIL_VALUE_MASK_NV 0x90B9 +#define GL_PATH_STENCIL_DEPTH_OFFSET_FACTOR_NV 0x90BD +#define GL_PATH_STENCIL_DEPTH_OFFSET_UNITS_NV 0x90BE +#define GL_PATH_COVER_DEPTH_FUNC_NV 0x90BF +#define GL_PATH_DASH_OFFSET_RESET_NV 0x90B4 +#define GL_MOVE_TO_RESETS_NV 0x90B5 +#define GL_MOVE_TO_CONTINUES_NV 0x90B6 +#define GL_CLOSE_PATH_NV 0x00 +#define GL_MOVE_TO_NV 0x02 +#define GL_RELATIVE_MOVE_TO_NV 0x03 +#define GL_LINE_TO_NV 0x04 +#define GL_RELATIVE_LINE_TO_NV 0x05 +#define GL_HORIZONTAL_LINE_TO_NV 0x06 +#define GL_RELATIVE_HORIZONTAL_LINE_TO_NV 0x07 +#define GL_VERTICAL_LINE_TO_NV 0x08 +#define GL_RELATIVE_VERTICAL_LINE_TO_NV 0x09 +#define GL_QUADRATIC_CURVE_TO_NV 0x0A +#define GL_RELATIVE_QUADRATIC_CURVE_TO_NV 0x0B +#define GL_CUBIC_CURVE_TO_NV 0x0C +#define GL_RELATIVE_CUBIC_CURVE_TO_NV 0x0D +#define GL_SMOOTH_QUADRATIC_CURVE_TO_NV 0x0E +#define GL_RELATIVE_SMOOTH_QUADRATIC_CURVE_TO_NV 0x0F +#define GL_SMOOTH_CUBIC_CURVE_TO_NV 0x10 +#define GL_RELATIVE_SMOOTH_CUBIC_CURVE_TO_NV 0x11 +#define GL_SMALL_CCW_ARC_TO_NV 0x12 +#define GL_RELATIVE_SMALL_CCW_ARC_TO_NV 0x13 +#define GL_SMALL_CW_ARC_TO_NV 0x14 +#define GL_RELATIVE_SMALL_CW_ARC_TO_NV 0x15 +#define GL_LARGE_CCW_ARC_TO_NV 0x16 +#define GL_RELATIVE_LARGE_CCW_ARC_TO_NV 0x17 +#define GL_LARGE_CW_ARC_TO_NV 0x18 +#define GL_RELATIVE_LARGE_CW_ARC_TO_NV 0x19 +#define GL_RESTART_PATH_NV 0xF0 +#define GL_DUP_FIRST_CUBIC_CURVE_TO_NV 0xF2 +#define GL_DUP_LAST_CUBIC_CURVE_TO_NV 0xF4 +#define GL_RECT_NV 0xF6 +#define GL_CIRCULAR_CCW_ARC_TO_NV 0xF8 +#define GL_CIRCULAR_CW_ARC_TO_NV 0xFA +#define GL_CIRCULAR_TANGENT_ARC_TO_NV 0xFC +#define GL_ARC_TO_NV 0xFE +#define GL_RELATIVE_ARC_TO_NV 0xFF +#define GL_BOLD_BIT_NV 0x01 +#define GL_ITALIC_BIT_NV 0x02 +#define GL_GLYPH_WIDTH_BIT_NV 0x01 +#define GL_GLYPH_HEIGHT_BIT_NV 0x02 +#define GL_GLYPH_HORIZONTAL_BEARING_X_BIT_NV 0x04 +#define GL_GLYPH_HORIZONTAL_BEARING_Y_BIT_NV 0x08 +#define GL_GLYPH_HORIZONTAL_BEARING_ADVANCE_BIT_NV 0x10 +#define GL_GLYPH_VERTICAL_BEARING_X_BIT_NV 0x20 +#define GL_GLYPH_VERTICAL_BEARING_Y_BIT_NV 0x40 +#define GL_GLYPH_VERTICAL_BEARING_ADVANCE_BIT_NV 0x80 +#define GL_GLYPH_HAS_KERNING_BIT_NV 0x100 +#define GL_FONT_X_MIN_BOUNDS_BIT_NV 0x00010000 +#define GL_FONT_Y_MIN_BOUNDS_BIT_NV 0x00020000 +#define GL_FONT_X_MAX_BOUNDS_BIT_NV 0x00040000 +#define GL_FONT_Y_MAX_BOUNDS_BIT_NV 0x00080000 +#define GL_FONT_UNITS_PER_EM_BIT_NV 0x00100000 +#define GL_FONT_ASCENDER_BIT_NV 0x00200000 +#define GL_FONT_DESCENDER_BIT_NV 0x00400000 +#define GL_FONT_HEIGHT_BIT_NV 0x00800000 +#define GL_FONT_MAX_ADVANCE_WIDTH_BIT_NV 0x01000000 +#define GL_FONT_MAX_ADVANCE_HEIGHT_BIT_NV 0x02000000 +#define GL_FONT_UNDERLINE_POSITION_BIT_NV 0x04000000 +#define GL_FONT_UNDERLINE_THICKNESS_BIT_NV 0x08000000 +#define GL_FONT_HAS_KERNING_BIT_NV 0x10000000 +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +typedef GLuint (APIENTRYP PFNGLGENPATHSNVPROC) (GLsizei range); +typedef void (APIENTRYP PFNGLDELETEPATHSNVPROC) (GLuint path, GLsizei range); +typedef GLboolean (APIENTRYP PFNGLISPATHNVPROC) (GLuint path); +typedef void (APIENTRYP PFNGLPATHCOMMANDSNVPROC) (GLuint path, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (APIENTRYP PFNGLPATHCOORDSNVPROC) (GLuint path, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (APIENTRYP PFNGLPATHSUBCOMMANDSNVPROC) (GLuint path, GLsizei commandStart, GLsizei commandsToDelete, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (APIENTRYP PFNGLPATHSUBCOORDSNVPROC) (GLuint path, GLsizei coordStart, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (APIENTRYP PFNGLPATHSTRINGNVPROC) (GLuint path, GLenum format, GLsizei length, const void *pathString); +typedef void (APIENTRYP PFNGLPATHGLYPHSNVPROC) (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLsizei numGlyphs, GLenum type, const void *charcodes, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef void (APIENTRYP PFNGLPATHGLYPHRANGENVPROC) (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyph, GLsizei numGlyphs, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef void (APIENTRYP PFNGLWEIGHTPATHSNVPROC) (GLuint resultPath, GLsizei numPaths, const GLuint *paths, const GLfloat *weights); +typedef void (APIENTRYP PFNGLCOPYPATHNVPROC) (GLuint resultPath, GLuint srcPath); +typedef void (APIENTRYP PFNGLINTERPOLATEPATHSNVPROC) (GLuint resultPath, GLuint pathA, GLuint pathB, GLfloat weight); +typedef void (APIENTRYP PFNGLTRANSFORMPATHNVPROC) (GLuint resultPath, GLuint srcPath, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLPATHPARAMETERIVNVPROC) (GLuint path, GLenum pname, const GLint *value); +typedef void (APIENTRYP PFNGLPATHPARAMETERINVPROC) (GLuint path, GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLPATHPARAMETERFVNVPROC) (GLuint path, GLenum pname, const GLfloat *value); +typedef void (APIENTRYP PFNGLPATHPARAMETERFNVPROC) (GLuint path, GLenum pname, GLfloat value); +typedef void (APIENTRYP PFNGLPATHDASHARRAYNVPROC) (GLuint path, GLsizei dashCount, const GLfloat *dashArray); +typedef void (APIENTRYP PFNGLPATHSTENCILFUNCNVPROC) (GLenum func, GLint ref, GLuint mask); +typedef void (APIENTRYP PFNGLPATHSTENCILDEPTHOFFSETNVPROC) (GLfloat factor, GLfloat units); +typedef void (APIENTRYP PFNGLSTENCILFILLPATHNVPROC) (GLuint path, GLenum fillMode, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILSTROKEPATHNVPROC) (GLuint path, GLint reference, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLSTENCILSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLPATHCOVERDEPTHFUNCNVPROC) (GLenum func); +typedef void (APIENTRYP PFNGLPATHCOLORGENNVPROC) (GLenum color, GLenum genMode, GLenum colorFormat, const GLfloat *coeffs); +typedef void (APIENTRYP PFNGLPATHTEXGENNVPROC) (GLenum texCoordSet, GLenum genMode, GLint components, const GLfloat *coeffs); +typedef void (APIENTRYP PFNGLPATHFOGGENNVPROC) (GLenum genMode); +typedef void (APIENTRYP PFNGLCOVERFILLPATHNVPROC) (GLuint path, GLenum coverMode); +typedef void (APIENTRYP PFNGLCOVERSTROKEPATHNVPROC) (GLuint path, GLenum coverMode); +typedef void (APIENTRYP PFNGLCOVERFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLCOVERSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLGETPATHPARAMETERIVNVPROC) (GLuint path, GLenum pname, GLint *value); +typedef void (APIENTRYP PFNGLGETPATHPARAMETERFVNVPROC) (GLuint path, GLenum pname, GLfloat *value); +typedef void (APIENTRYP PFNGLGETPATHCOMMANDSNVPROC) (GLuint path, GLubyte *commands); +typedef void (APIENTRYP PFNGLGETPATHCOORDSNVPROC) (GLuint path, GLfloat *coords); +typedef void (APIENTRYP PFNGLGETPATHDASHARRAYNVPROC) (GLuint path, GLfloat *dashArray); +typedef void (APIENTRYP PFNGLGETPATHMETRICSNVPROC) (GLbitfield metricQueryMask, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLsizei stride, GLfloat *metrics); +typedef void (APIENTRYP PFNGLGETPATHMETRICRANGENVPROC) (GLbitfield metricQueryMask, GLuint firstPathName, GLsizei numPaths, GLsizei stride, GLfloat *metrics); +typedef void (APIENTRYP PFNGLGETPATHSPACINGNVPROC) (GLenum pathListMode, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLfloat advanceScale, GLfloat kerningScale, GLenum transformType, GLfloat *returnedSpacing); +typedef void (APIENTRYP PFNGLGETPATHCOLORGENIVNVPROC) (GLenum color, GLenum pname, GLint *value); +typedef void (APIENTRYP PFNGLGETPATHCOLORGENFVNVPROC) (GLenum color, GLenum pname, GLfloat *value); +typedef void (APIENTRYP PFNGLGETPATHTEXGENIVNVPROC) (GLenum texCoordSet, GLenum pname, GLint *value); +typedef void (APIENTRYP PFNGLGETPATHTEXGENFVNVPROC) (GLenum texCoordSet, GLenum pname, GLfloat *value); +typedef GLboolean (APIENTRYP PFNGLISPOINTINFILLPATHNVPROC) (GLuint path, GLuint mask, GLfloat x, GLfloat y); +typedef GLboolean (APIENTRYP PFNGLISPOINTINSTROKEPATHNVPROC) (GLuint path, GLfloat x, GLfloat y); +typedef GLfloat (APIENTRYP PFNGLGETPATHLENGTHNVPROC) (GLuint path, GLsizei startSegment, GLsizei numSegments); +typedef GLboolean (APIENTRYP PFNGLPOINTALONGPATHNVPROC) (GLuint path, GLsizei startSegment, GLsizei numSegments, GLfloat distance, GLfloat *x, GLfloat *y, GLfloat *tangentX, GLfloat *tangentY); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glGenPathsNV (GLsizei range); +GLAPI void APIENTRY glDeletePathsNV (GLuint path, GLsizei range); +GLAPI GLboolean APIENTRY glIsPathNV (GLuint path); +GLAPI void APIENTRY glPathCommandsNV (GLuint path, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +GLAPI void APIENTRY glPathCoordsNV (GLuint path, GLsizei numCoords, GLenum coordType, const void *coords); +GLAPI void APIENTRY glPathSubCommandsNV (GLuint path, GLsizei commandStart, GLsizei commandsToDelete, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +GLAPI void APIENTRY glPathSubCoordsNV (GLuint path, GLsizei coordStart, GLsizei numCoords, GLenum coordType, const void *coords); +GLAPI void APIENTRY glPathStringNV (GLuint path, GLenum format, GLsizei length, const void *pathString); +GLAPI void APIENTRY glPathGlyphsNV (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLsizei numGlyphs, GLenum type, const void *charcodes, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GLAPI void APIENTRY glPathGlyphRangeNV (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyph, GLsizei numGlyphs, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GLAPI void APIENTRY glWeightPathsNV (GLuint resultPath, GLsizei numPaths, const GLuint *paths, const GLfloat *weights); +GLAPI void APIENTRY glCopyPathNV (GLuint resultPath, GLuint srcPath); +GLAPI void APIENTRY glInterpolatePathsNV (GLuint resultPath, GLuint pathA, GLuint pathB, GLfloat weight); +GLAPI void APIENTRY glTransformPathNV (GLuint resultPath, GLuint srcPath, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glPathParameterivNV (GLuint path, GLenum pname, const GLint *value); +GLAPI void APIENTRY glPathParameteriNV (GLuint path, GLenum pname, GLint value); +GLAPI void APIENTRY glPathParameterfvNV (GLuint path, GLenum pname, const GLfloat *value); +GLAPI void APIENTRY glPathParameterfNV (GLuint path, GLenum pname, GLfloat value); +GLAPI void APIENTRY glPathDashArrayNV (GLuint path, GLsizei dashCount, const GLfloat *dashArray); +GLAPI void APIENTRY glPathStencilFuncNV (GLenum func, GLint ref, GLuint mask); +GLAPI void APIENTRY glPathStencilDepthOffsetNV (GLfloat factor, GLfloat units); +GLAPI void APIENTRY glStencilFillPathNV (GLuint path, GLenum fillMode, GLuint mask); +GLAPI void APIENTRY glStencilStrokePathNV (GLuint path, GLint reference, GLuint mask); +GLAPI void APIENTRY glStencilFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glStencilStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glPathCoverDepthFuncNV (GLenum func); +GLAPI void APIENTRY glPathColorGenNV (GLenum color, GLenum genMode, GLenum colorFormat, const GLfloat *coeffs); +GLAPI void APIENTRY glPathTexGenNV (GLenum texCoordSet, GLenum genMode, GLint components, const GLfloat *coeffs); +GLAPI void APIENTRY glPathFogGenNV (GLenum genMode); +GLAPI void APIENTRY glCoverFillPathNV (GLuint path, GLenum coverMode); +GLAPI void APIENTRY glCoverStrokePathNV (GLuint path, GLenum coverMode); +GLAPI void APIENTRY glCoverFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glCoverStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glGetPathParameterivNV (GLuint path, GLenum pname, GLint *value); +GLAPI void APIENTRY glGetPathParameterfvNV (GLuint path, GLenum pname, GLfloat *value); +GLAPI void APIENTRY glGetPathCommandsNV (GLuint path, GLubyte *commands); +GLAPI void APIENTRY glGetPathCoordsNV (GLuint path, GLfloat *coords); +GLAPI void APIENTRY glGetPathDashArrayNV (GLuint path, GLfloat *dashArray); +GLAPI void APIENTRY glGetPathMetricsNV (GLbitfield metricQueryMask, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLsizei stride, GLfloat *metrics); +GLAPI void APIENTRY glGetPathMetricRangeNV (GLbitfield metricQueryMask, GLuint firstPathName, GLsizei numPaths, GLsizei stride, GLfloat *metrics); +GLAPI void APIENTRY glGetPathSpacingNV (GLenum pathListMode, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLfloat advanceScale, GLfloat kerningScale, GLenum transformType, GLfloat *returnedSpacing); +GLAPI void APIENTRY glGetPathColorGenivNV (GLenum color, GLenum pname, GLint *value); +GLAPI void APIENTRY glGetPathColorGenfvNV (GLenum color, GLenum pname, GLfloat *value); +GLAPI void APIENTRY glGetPathTexGenivNV (GLenum texCoordSet, GLenum pname, GLint *value); +GLAPI void APIENTRY glGetPathTexGenfvNV (GLenum texCoordSet, GLenum pname, GLfloat *value); +GLAPI GLboolean APIENTRY glIsPointInFillPathNV (GLuint path, GLuint mask, GLfloat x, GLfloat y); +GLAPI GLboolean APIENTRY glIsPointInStrokePathNV (GLuint path, GLfloat x, GLfloat y); +GLAPI GLfloat APIENTRY glGetPathLengthNV (GLuint path, GLsizei startSegment, GLsizei numSegments); +GLAPI GLboolean APIENTRY glPointAlongPathNV (GLuint path, GLsizei startSegment, GLsizei numSegments, GLfloat distance, GLfloat *x, GLfloat *y, GLfloat *tangentX, GLfloat *tangentY); +#endif +#endif /* GL_NV_path_rendering */ + +#ifndef GL_NV_pixel_data_range +#define GL_NV_pixel_data_range 1 +#define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878 +#define GL_READ_PIXEL_DATA_RANGE_NV 0x8879 +#define GL_WRITE_PIXEL_DATA_RANGE_LENGTH_NV 0x887A +#define GL_READ_PIXEL_DATA_RANGE_LENGTH_NV 0x887B +#define GL_WRITE_PIXEL_DATA_RANGE_POINTER_NV 0x887C +#define GL_READ_PIXEL_DATA_RANGE_POINTER_NV 0x887D +typedef void (APIENTRYP PFNGLPIXELDATARANGENVPROC) (GLenum target, GLsizei length, const void *pointer); +typedef void (APIENTRYP PFNGLFLUSHPIXELDATARANGENVPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelDataRangeNV (GLenum target, GLsizei length, const void *pointer); +GLAPI void APIENTRY glFlushPixelDataRangeNV (GLenum target); +#endif +#endif /* GL_NV_pixel_data_range */ + +#ifndef GL_NV_point_sprite +#define GL_NV_point_sprite 1 +#define GL_POINT_SPRITE_NV 0x8861 +#define GL_COORD_REPLACE_NV 0x8862 +#define GL_POINT_SPRITE_R_MODE_NV 0x8863 +typedef void (APIENTRYP PFNGLPOINTPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameteriNV (GLenum pname, GLint param); +GLAPI void APIENTRY glPointParameterivNV (GLenum pname, const GLint *params); +#endif +#endif /* GL_NV_point_sprite */ + +#ifndef GL_NV_present_video +#define GL_NV_present_video 1 +#define GL_FRAME_NV 0x8E26 +#define GL_FIELDS_NV 0x8E27 +#define GL_CURRENT_TIME_NV 0x8E28 +#define GL_NUM_FILL_STREAMS_NV 0x8E29 +#define GL_PRESENT_TIME_NV 0x8E2A +#define GL_PRESENT_DURATION_NV 0x8E2B +typedef void (APIENTRYP PFNGLPRESENTFRAMEKEYEDNVPROC) (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLuint key0, GLenum target1, GLuint fill1, GLuint key1); +typedef void (APIENTRYP PFNGLPRESENTFRAMEDUALFILLNVPROC) (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLenum target1, GLuint fill1, GLenum target2, GLuint fill2, GLenum target3, GLuint fill3); +typedef void (APIENTRYP PFNGLGETVIDEOIVNVPROC) (GLuint video_slot, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOUIVNVPROC) (GLuint video_slot, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLGETVIDEOI64VNVPROC) (GLuint video_slot, GLenum pname, GLint64EXT *params); +typedef void (APIENTRYP PFNGLGETVIDEOUI64VNVPROC) (GLuint video_slot, GLenum pname, GLuint64EXT *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPresentFrameKeyedNV (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLuint key0, GLenum target1, GLuint fill1, GLuint key1); +GLAPI void APIENTRY glPresentFrameDualFillNV (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLenum target1, GLuint fill1, GLenum target2, GLuint fill2, GLenum target3, GLuint fill3); +GLAPI void APIENTRY glGetVideoivNV (GLuint video_slot, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideouivNV (GLuint video_slot, GLenum pname, GLuint *params); +GLAPI void APIENTRY glGetVideoi64vNV (GLuint video_slot, GLenum pname, GLint64EXT *params); +GLAPI void APIENTRY glGetVideoui64vNV (GLuint video_slot, GLenum pname, GLuint64EXT *params); +#endif +#endif /* GL_NV_present_video */ + +#ifndef GL_NV_primitive_restart +#define GL_NV_primitive_restart 1 +#define GL_PRIMITIVE_RESTART_NV 0x8558 +#define GL_PRIMITIVE_RESTART_INDEX_NV 0x8559 +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTNVPROC) (void); +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXNVPROC) (GLuint index); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPrimitiveRestartNV (void); +GLAPI void APIENTRY glPrimitiveRestartIndexNV (GLuint index); +#endif +#endif /* GL_NV_primitive_restart */ + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFVNVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFNVPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLCOMBINERINPUTNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLCOMBINEROUTPUTNVPROC) (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +typedef void (APIENTRYP PFNGLFINALCOMBINERINPUTNVPROC) (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC) (GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC) (GLenum variable, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerParameterfvNV (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glCombinerParameterfNV (GLenum pname, GLfloat param); +GLAPI void APIENTRY glCombinerParameterivNV (GLenum pname, const GLint *params); +GLAPI void APIENTRY glCombinerParameteriNV (GLenum pname, GLint param); +GLAPI void APIENTRY glCombinerInputNV (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +GLAPI void APIENTRY glCombinerOutputNV (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +GLAPI void APIENTRY glFinalCombinerInputNV (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +GLAPI void APIENTRY glGetCombinerInputParameterfvNV (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetCombinerInputParameterivNV (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetCombinerOutputParameterfvNV (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetCombinerOutputParameterivNV (GLenum stage, GLenum portion, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetFinalCombinerInputParameterfvNV (GLenum variable, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFinalCombinerInputParameterivNV (GLenum variable, GLenum pname, GLint *params); +#endif +#endif /* GL_NV_register_combiners */ + +#ifndef GL_NV_register_combiners2 +#define GL_NV_register_combiners2 1 +#define GL_PER_STAGE_CONSTANTS_NV 0x8535 +typedef void (APIENTRYP PFNGLCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerStageParameterfvNV (GLenum stage, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetCombinerStageParameterfvNV (GLenum stage, GLenum pname, GLfloat *params); +#endif +#endif /* GL_NV_register_combiners2 */ + +#ifndef GL_NV_shader_atomic_counters +#define GL_NV_shader_atomic_counters 1 +#endif /* GL_NV_shader_atomic_counters */ + +#ifndef GL_NV_shader_atomic_float +#define GL_NV_shader_atomic_float 1 +#endif /* GL_NV_shader_atomic_float */ + +#ifndef GL_NV_shader_buffer_load +#define GL_NV_shader_buffer_load 1 +#define GL_BUFFER_GPU_ADDRESS_NV 0x8F1D +#define GL_GPU_ADDRESS_NV 0x8F34 +#define GL_MAX_SHADER_BUFFER_ADDRESS_NV 0x8F35 +typedef void (APIENTRYP PFNGLMAKEBUFFERRESIDENTNVPROC) (GLenum target, GLenum access); +typedef void (APIENTRYP PFNGLMAKEBUFFERNONRESIDENTNVPROC) (GLenum target); +typedef GLboolean (APIENTRYP PFNGLISBUFFERRESIDENTNVPROC) (GLenum target); +typedef void (APIENTRYP PFNGLMAKENAMEDBUFFERRESIDENTNVPROC) (GLuint buffer, GLenum access); +typedef void (APIENTRYP PFNGLMAKENAMEDBUFFERNONRESIDENTNVPROC) (GLuint buffer); +typedef GLboolean (APIENTRYP PFNGLISNAMEDBUFFERRESIDENTNVPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERUI64VNVPROC) (GLenum target, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERUI64VNVPROC) (GLuint buffer, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLGETINTEGERUI64VNVPROC) (GLenum value, GLuint64EXT *result); +typedef void (APIENTRYP PFNGLUNIFORMUI64NVPROC) (GLint location, GLuint64EXT value); +typedef void (APIENTRYP PFNGLUNIFORMUI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLGETUNIFORMUI64VNVPROC) (GLuint program, GLint location, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMUI64NVPROC) (GLuint program, GLint location, GLuint64EXT value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMUI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMakeBufferResidentNV (GLenum target, GLenum access); +GLAPI void APIENTRY glMakeBufferNonResidentNV (GLenum target); +GLAPI GLboolean APIENTRY glIsBufferResidentNV (GLenum target); +GLAPI void APIENTRY glMakeNamedBufferResidentNV (GLuint buffer, GLenum access); +GLAPI void APIENTRY glMakeNamedBufferNonResidentNV (GLuint buffer); +GLAPI GLboolean APIENTRY glIsNamedBufferResidentNV (GLuint buffer); +GLAPI void APIENTRY glGetBufferParameterui64vNV (GLenum target, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glGetNamedBufferParameterui64vNV (GLuint buffer, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glGetIntegerui64vNV (GLenum value, GLuint64EXT *result); +GLAPI void APIENTRY glUniformui64NV (GLint location, GLuint64EXT value); +GLAPI void APIENTRY glUniformui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glGetUniformui64vNV (GLuint program, GLint location, GLuint64EXT *params); +GLAPI void APIENTRY glProgramUniformui64NV (GLuint program, GLint location, GLuint64EXT value); +GLAPI void APIENTRY glProgramUniformui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif +#endif /* GL_NV_shader_buffer_load */ + +#ifndef GL_NV_shader_buffer_store +#define GL_NV_shader_buffer_store 1 +#define GL_SHADER_GLOBAL_ACCESS_BARRIER_BIT_NV 0x00000010 +#endif /* GL_NV_shader_buffer_store */ + +#ifndef GL_NV_shader_storage_buffer_object +#define GL_NV_shader_storage_buffer_object 1 +#endif /* GL_NV_shader_storage_buffer_object */ + +#ifndef GL_NV_tessellation_program5 +#define GL_NV_tessellation_program5 1 +#define GL_MAX_PROGRAM_PATCH_ATTRIBS_NV 0x86D8 +#define GL_TESS_CONTROL_PROGRAM_NV 0x891E +#define GL_TESS_EVALUATION_PROGRAM_NV 0x891F +#define GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV 0x8C74 +#define GL_TESS_EVALUATION_PROGRAM_PARAMETER_BUFFER_NV 0x8C75 +#endif /* GL_NV_tessellation_program5 */ + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif /* GL_NV_texgen_emboss */ + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif /* GL_NV_texgen_reflection */ + +#ifndef GL_NV_texture_barrier +#define GL_NV_texture_barrier 1 +typedef void (APIENTRYP PFNGLTEXTUREBARRIERNVPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureBarrierNV (void); +#endif +#endif /* GL_NV_texture_barrier */ + +#ifndef GL_NV_texture_compression_vtc +#define GL_NV_texture_compression_vtc 1 +#endif /* GL_NV_texture_compression_vtc */ + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 +#define GL_COMBINE4_NV 0x8503 +#define GL_SOURCE3_RGB_NV 0x8583 +#define GL_SOURCE3_ALPHA_NV 0x858B +#define GL_OPERAND3_RGB_NV 0x8593 +#define GL_OPERAND3_ALPHA_NV 0x859B +#endif /* GL_NV_texture_env_combine4 */ + +#ifndef GL_NV_texture_expand_normal +#define GL_NV_texture_expand_normal 1 +#define GL_TEXTURE_UNSIGNED_REMAP_MODE_NV 0x888F +#endif /* GL_NV_texture_expand_normal */ + +#ifndef GL_NV_texture_multisample +#define GL_NV_texture_multisample 1 +#define GL_TEXTURE_COVERAGE_SAMPLES_NV 0x9045 +#define GL_TEXTURE_COLOR_SAMPLES_NV 0x9046 +typedef void (APIENTRYP PFNGLTEXIMAGE2DMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXIMAGE3DMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DMULTISAMPLENVPROC) (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DMULTISAMPLENVPROC) (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DMULTISAMPLECOVERAGENVPROC) (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DMULTISAMPLECOVERAGENVPROC) (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage2DMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTexImage3DMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage2DMultisampleNV (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage3DMultisampleNV (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage2DMultisampleCoverageNV (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage3DMultisampleCoverageNV (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +#endif +#endif /* GL_NV_texture_multisample */ + +#ifndef GL_NV_texture_rectangle +#define GL_NV_texture_rectangle 1 +#define GL_TEXTURE_RECTANGLE_NV 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 +#endif /* GL_NV_texture_rectangle */ + +#ifndef GL_NV_texture_shader +#define GL_NV_texture_shader 1 +#define GL_OFFSET_TEXTURE_RECTANGLE_NV 0x864C +#define GL_OFFSET_TEXTURE_RECTANGLE_SCALE_NV 0x864D +#define GL_DOT_PRODUCT_TEXTURE_RECTANGLE_NV 0x864E +#define GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV 0x86D9 +#define GL_UNSIGNED_INT_S8_S8_8_8_NV 0x86DA +#define GL_UNSIGNED_INT_8_8_S8_S8_REV_NV 0x86DB +#define GL_DSDT_MAG_INTENSITY_NV 0x86DC +#define GL_SHADER_CONSISTENT_NV 0x86DD +#define GL_TEXTURE_SHADER_NV 0x86DE +#define GL_SHADER_OPERATION_NV 0x86DF +#define GL_CULL_MODES_NV 0x86E0 +#define GL_OFFSET_TEXTURE_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_BIAS_NV 0x86E3 +#define GL_OFFSET_TEXTURE_2D_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_2D_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_2D_BIAS_NV 0x86E3 +#define GL_PREVIOUS_TEXTURE_INPUT_NV 0x86E4 +#define GL_CONST_EYE_NV 0x86E5 +#define GL_PASS_THROUGH_NV 0x86E6 +#define GL_CULL_FRAGMENT_NV 0x86E7 +#define GL_OFFSET_TEXTURE_2D_NV 0x86E8 +#define GL_DEPENDENT_AR_TEXTURE_2D_NV 0x86E9 +#define GL_DEPENDENT_GB_TEXTURE_2D_NV 0x86EA +#define GL_DOT_PRODUCT_NV 0x86EC +#define GL_DOT_PRODUCT_DEPTH_REPLACE_NV 0x86ED +#define GL_DOT_PRODUCT_TEXTURE_2D_NV 0x86EE +#define GL_DOT_PRODUCT_TEXTURE_CUBE_MAP_NV 0x86F0 +#define GL_DOT_PRODUCT_DIFFUSE_CUBE_MAP_NV 0x86F1 +#define GL_DOT_PRODUCT_REFLECT_CUBE_MAP_NV 0x86F2 +#define GL_DOT_PRODUCT_CONST_EYE_REFLECT_CUBE_MAP_NV 0x86F3 +#define GL_HILO_NV 0x86F4 +#define GL_DSDT_NV 0x86F5 +#define GL_DSDT_MAG_NV 0x86F6 +#define GL_DSDT_MAG_VIB_NV 0x86F7 +#define GL_HILO16_NV 0x86F8 +#define GL_SIGNED_HILO_NV 0x86F9 +#define GL_SIGNED_HILO16_NV 0x86FA +#define GL_SIGNED_RGBA_NV 0x86FB +#define GL_SIGNED_RGBA8_NV 0x86FC +#define GL_SIGNED_RGB_NV 0x86FE +#define GL_SIGNED_RGB8_NV 0x86FF +#define GL_SIGNED_LUMINANCE_NV 0x8701 +#define GL_SIGNED_LUMINANCE8_NV 0x8702 +#define GL_SIGNED_LUMINANCE_ALPHA_NV 0x8703 +#define GL_SIGNED_LUMINANCE8_ALPHA8_NV 0x8704 +#define GL_SIGNED_ALPHA_NV 0x8705 +#define GL_SIGNED_ALPHA8_NV 0x8706 +#define GL_SIGNED_INTENSITY_NV 0x8707 +#define GL_SIGNED_INTENSITY8_NV 0x8708 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT8_MAG8_NV 0x870A +#define GL_DSDT8_MAG8_INTENSITY8_NV 0x870B +#define GL_SIGNED_RGB_UNSIGNED_ALPHA_NV 0x870C +#define GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV 0x870D +#define GL_HI_SCALE_NV 0x870E +#define GL_LO_SCALE_NV 0x870F +#define GL_DS_SCALE_NV 0x8710 +#define GL_DT_SCALE_NV 0x8711 +#define GL_MAGNITUDE_SCALE_NV 0x8712 +#define GL_VIBRANCE_SCALE_NV 0x8713 +#define GL_HI_BIAS_NV 0x8714 +#define GL_LO_BIAS_NV 0x8715 +#define GL_DS_BIAS_NV 0x8716 +#define GL_DT_BIAS_NV 0x8717 +#define GL_MAGNITUDE_BIAS_NV 0x8718 +#define GL_VIBRANCE_BIAS_NV 0x8719 +#define GL_TEXTURE_BORDER_VALUES_NV 0x871A +#define GL_TEXTURE_HI_SIZE_NV 0x871B +#define GL_TEXTURE_LO_SIZE_NV 0x871C +#define GL_TEXTURE_DS_SIZE_NV 0x871D +#define GL_TEXTURE_DT_SIZE_NV 0x871E +#define GL_TEXTURE_MAG_SIZE_NV 0x871F +#endif /* GL_NV_texture_shader */ + +#ifndef GL_NV_texture_shader2 +#define GL_NV_texture_shader2 1 +#define GL_DOT_PRODUCT_TEXTURE_3D_NV 0x86EF +#endif /* GL_NV_texture_shader2 */ + +#ifndef GL_NV_texture_shader3 +#define GL_NV_texture_shader3 1 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_NV 0x8850 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_SCALE_NV 0x8851 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8852 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_SCALE_NV 0x8853 +#define GL_OFFSET_HILO_TEXTURE_2D_NV 0x8854 +#define GL_OFFSET_HILO_TEXTURE_RECTANGLE_NV 0x8855 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_2D_NV 0x8856 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8857 +#define GL_DEPENDENT_HILO_TEXTURE_2D_NV 0x8858 +#define GL_DEPENDENT_RGB_TEXTURE_3D_NV 0x8859 +#define GL_DEPENDENT_RGB_TEXTURE_CUBE_MAP_NV 0x885A +#define GL_DOT_PRODUCT_PASS_THROUGH_NV 0x885B +#define GL_DOT_PRODUCT_TEXTURE_1D_NV 0x885C +#define GL_DOT_PRODUCT_AFFINE_DEPTH_REPLACE_NV 0x885D +#define GL_HILO8_NV 0x885E +#define GL_SIGNED_HILO8_NV 0x885F +#define GL_FORCE_BLUE_TO_ONE_NV 0x8860 +#endif /* GL_NV_texture_shader3 */ + +#ifndef GL_NV_transform_feedback +#define GL_NV_transform_feedback 1 +#define GL_BACK_PRIMARY_COLOR_NV 0x8C77 +#define GL_BACK_SECONDARY_COLOR_NV 0x8C78 +#define GL_TEXTURE_COORD_NV 0x8C79 +#define GL_CLIP_DISTANCE_NV 0x8C7A +#define GL_VERTEX_ID_NV 0x8C7B +#define GL_PRIMITIVE_ID_NV 0x8C7C +#define GL_GENERIC_ATTRIB_NV 0x8C7D +#define GL_TRANSFORM_FEEDBACK_ATTRIBS_NV 0x8C7E +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE_NV 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_NV 0x8C80 +#define GL_ACTIVE_VARYINGS_NV 0x8C81 +#define GL_ACTIVE_VARYING_MAX_LENGTH_NV 0x8C82 +#define GL_TRANSFORM_FEEDBACK_VARYINGS_NV 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START_NV 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE_NV 0x8C85 +#define GL_TRANSFORM_FEEDBACK_RECORD_NV 0x8C86 +#define GL_PRIMITIVES_GENERATED_NV 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV 0x8C88 +#define GL_RASTERIZER_DISCARD_NV 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_NV 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_NV 0x8C8B +#define GL_INTERLEAVED_ATTRIBS_NV 0x8C8C +#define GL_SEPARATE_ATTRIBS_NV 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER_NV 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_NV 0x8C8F +#define GL_LAYER_NV 0x8DAA +#define GL_NEXT_BUFFER_NV -2 +#define GL_SKIP_COMPONENTS4_NV -3 +#define GL_SKIP_COMPONENTS3_NV -4 +#define GL_SKIP_COMPONENTS2_NV -5 +#define GL_SKIP_COMPONENTS1_NV -6 +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKNVPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKATTRIBSNVPROC) (GLuint count, const GLint *attribs, GLenum bufferMode); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGENVPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFEROFFSETNVPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +typedef void (APIENTRYP PFNGLBINDBUFFERBASENVPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSNVPROC) (GLuint program, GLsizei count, const GLint *locations, GLenum bufferMode); +typedef void (APIENTRYP PFNGLACTIVEVARYINGNVPROC) (GLuint program, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETVARYINGLOCATIONNVPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVEVARYINGNVPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGNVPROC) (GLuint program, GLuint index, GLint *location); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKSTREAMATTRIBSNVPROC) (GLsizei count, const GLint *attribs, GLsizei nbuffers, const GLint *bufstreams, GLenum bufferMode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginTransformFeedbackNV (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedbackNV (void); +GLAPI void APIENTRY glTransformFeedbackAttribsNV (GLuint count, const GLint *attribs, GLenum bufferMode); +GLAPI void APIENTRY glBindBufferRangeNV (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferOffsetNV (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +GLAPI void APIENTRY glBindBufferBaseNV (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryingsNV (GLuint program, GLsizei count, const GLint *locations, GLenum bufferMode); +GLAPI void APIENTRY glActiveVaryingNV (GLuint program, const GLchar *name); +GLAPI GLint APIENTRY glGetVaryingLocationNV (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetActiveVaryingNV (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetTransformFeedbackVaryingNV (GLuint program, GLuint index, GLint *location); +GLAPI void APIENTRY glTransformFeedbackStreamAttribsNV (GLsizei count, const GLint *attribs, GLsizei nbuffers, const GLint *bufstreams, GLenum bufferMode); +#endif +#endif /* GL_NV_transform_feedback */ + +#ifndef GL_NV_transform_feedback2 +#define GL_NV_transform_feedback2 1 +#define GL_TRANSFORM_FEEDBACK_NV 0x8E22 +#define GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED_NV 0x8E23 +#define GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE_NV 0x8E24 +#define GL_TRANSFORM_FEEDBACK_BINDING_NV 0x8E25 +typedef void (APIENTRYP PFNGLBINDTRANSFORMFEEDBACKNVPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETETRANSFORMFEEDBACKSNVPROC) (GLsizei n, const GLuint *ids); +typedef void (APIENTRYP PFNGLGENTRANSFORMFEEDBACKSNVPROC) (GLsizei n, GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISTRANSFORMFEEDBACKNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLPAUSETRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLRESUMETRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKNVPROC) (GLenum mode, GLuint id); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindTransformFeedbackNV (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteTransformFeedbacksNV (GLsizei n, const GLuint *ids); +GLAPI void APIENTRY glGenTransformFeedbacksNV (GLsizei n, GLuint *ids); +GLAPI GLboolean APIENTRY glIsTransformFeedbackNV (GLuint id); +GLAPI void APIENTRY glPauseTransformFeedbackNV (void); +GLAPI void APIENTRY glResumeTransformFeedbackNV (void); +GLAPI void APIENTRY glDrawTransformFeedbackNV (GLenum mode, GLuint id); +#endif +#endif /* GL_NV_transform_feedback2 */ + +#ifndef GL_NV_vdpau_interop +#define GL_NV_vdpau_interop 1 +typedef GLintptr GLvdpauSurfaceNV; +#define GL_SURFACE_STATE_NV 0x86EB +#define GL_SURFACE_REGISTERED_NV 0x86FD +#define GL_SURFACE_MAPPED_NV 0x8700 +#define GL_WRITE_DISCARD_NV 0x88BE +typedef void (APIENTRYP PFNGLVDPAUINITNVPROC) (const void *vdpDevice, const void *getProcAddress); +typedef void (APIENTRYP PFNGLVDPAUFININVPROC) (void); +typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTERVIDEOSURFACENVPROC) (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTEROUTPUTSURFACENVPROC) (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +typedef GLboolean (APIENTRYP PFNGLVDPAUISSURFACENVPROC) (GLvdpauSurfaceNV surface); +typedef void (APIENTRYP PFNGLVDPAUUNREGISTERSURFACENVPROC) (GLvdpauSurfaceNV surface); +typedef void (APIENTRYP PFNGLVDPAUGETSURFACEIVNVPROC) (GLvdpauSurfaceNV surface, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values); +typedef void (APIENTRYP PFNGLVDPAUSURFACEACCESSNVPROC) (GLvdpauSurfaceNV surface, GLenum access); +typedef void (APIENTRYP PFNGLVDPAUMAPSURFACESNVPROC) (GLsizei numSurfaces, const GLvdpauSurfaceNV *surfaces); +typedef void (APIENTRYP PFNGLVDPAUUNMAPSURFACESNVPROC) (GLsizei numSurface, const GLvdpauSurfaceNV *surfaces); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVDPAUInitNV (const void *vdpDevice, const void *getProcAddress); +GLAPI void APIENTRY glVDPAUFiniNV (void); +GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterVideoSurfaceNV (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterOutputSurfaceNV (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +GLAPI GLboolean APIENTRY glVDPAUIsSurfaceNV (GLvdpauSurfaceNV surface); +GLAPI void APIENTRY glVDPAUUnregisterSurfaceNV (GLvdpauSurfaceNV surface); +GLAPI void APIENTRY glVDPAUGetSurfaceivNV (GLvdpauSurfaceNV surface, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values); +GLAPI void APIENTRY glVDPAUSurfaceAccessNV (GLvdpauSurfaceNV surface, GLenum access); +GLAPI void APIENTRY glVDPAUMapSurfacesNV (GLsizei numSurfaces, const GLvdpauSurfaceNV *surfaces); +GLAPI void APIENTRY glVDPAUUnmapSurfacesNV (GLsizei numSurface, const GLvdpauSurfaceNV *surfaces); +#endif +#endif /* GL_NV_vdpau_interop */ + +#ifndef GL_NV_vertex_array_range +#define GL_NV_vertex_array_range 1 +#define GL_VERTEX_ARRAY_RANGE_NV 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E +#define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGENVPROC) (void); +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGENVPROC) (GLsizei length, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushVertexArrayRangeNV (void); +GLAPI void APIENTRY glVertexArrayRangeNV (GLsizei length, const void *pointer); +#endif +#endif /* GL_NV_vertex_array_range */ + +#ifndef GL_NV_vertex_array_range2 +#define GL_NV_vertex_array_range2 1 +#define GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV 0x8533 +#endif /* GL_NV_vertex_array_range2 */ + +#ifndef GL_NV_vertex_attrib_integer_64bit +#define GL_NV_vertex_attrib_integer_64bit 1 +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1I64NVPROC) (GLuint index, GLint64EXT x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64NVPROC) (GLuint index, GLuint64EXT x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLI64VNVPROC) (GLuint index, GLenum pname, GLint64EXT *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLUI64VNVPROC) (GLuint index, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLsizei stride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribL1i64NV (GLuint index, GLint64EXT x); +GLAPI void APIENTRY glVertexAttribL2i64NV (GLuint index, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glVertexAttribL3i64NV (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glVertexAttribL4i64NV (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glVertexAttribL1i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL2i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL3i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL4i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL1ui64NV (GLuint index, GLuint64EXT x); +GLAPI void APIENTRY glVertexAttribL2ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glVertexAttribL3ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glVertexAttribL4ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glVertexAttribL1ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL2ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL3ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL4ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glGetVertexAttribLi64vNV (GLuint index, GLenum pname, GLint64EXT *params); +GLAPI void APIENTRY glGetVertexAttribLui64vNV (GLuint index, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glVertexAttribLFormatNV (GLuint index, GLint size, GLenum type, GLsizei stride); +#endif +#endif /* GL_NV_vertex_attrib_integer_64bit */ + +#ifndef GL_NV_vertex_buffer_unified_memory +#define GL_NV_vertex_buffer_unified_memory 1 +#define GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV 0x8F1E +#define GL_ELEMENT_ARRAY_UNIFIED_NV 0x8F1F +#define GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV 0x8F20 +#define GL_VERTEX_ARRAY_ADDRESS_NV 0x8F21 +#define GL_NORMAL_ARRAY_ADDRESS_NV 0x8F22 +#define GL_COLOR_ARRAY_ADDRESS_NV 0x8F23 +#define GL_INDEX_ARRAY_ADDRESS_NV 0x8F24 +#define GL_TEXTURE_COORD_ARRAY_ADDRESS_NV 0x8F25 +#define GL_EDGE_FLAG_ARRAY_ADDRESS_NV 0x8F26 +#define GL_SECONDARY_COLOR_ARRAY_ADDRESS_NV 0x8F27 +#define GL_FOG_COORD_ARRAY_ADDRESS_NV 0x8F28 +#define GL_ELEMENT_ARRAY_ADDRESS_NV 0x8F29 +#define GL_VERTEX_ATTRIB_ARRAY_LENGTH_NV 0x8F2A +#define GL_VERTEX_ARRAY_LENGTH_NV 0x8F2B +#define GL_NORMAL_ARRAY_LENGTH_NV 0x8F2C +#define GL_COLOR_ARRAY_LENGTH_NV 0x8F2D +#define GL_INDEX_ARRAY_LENGTH_NV 0x8F2E +#define GL_TEXTURE_COORD_ARRAY_LENGTH_NV 0x8F2F +#define GL_EDGE_FLAG_ARRAY_LENGTH_NV 0x8F30 +#define GL_SECONDARY_COLOR_ARRAY_LENGTH_NV 0x8F31 +#define GL_FOG_COORD_ARRAY_LENGTH_NV 0x8F32 +#define GL_ELEMENT_ARRAY_LENGTH_NV 0x8F33 +#define GL_DRAW_INDIRECT_UNIFIED_NV 0x8F40 +#define GL_DRAW_INDIRECT_ADDRESS_NV 0x8F41 +#define GL_DRAW_INDIRECT_LENGTH_NV 0x8F42 +typedef void (APIENTRYP PFNGLBUFFERADDRESSRANGENVPROC) (GLenum pname, GLuint index, GLuint64EXT address, GLsizeiptr length); +typedef void (APIENTRYP PFNGLVERTEXFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLNORMALFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLCOLORFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLINDEXFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLTEXCOORDFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLEDGEFLAGFORMATNVPROC) (GLsizei stride); +typedef void (APIENTRYP PFNGLSECONDARYCOLORFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLFOGCOORDFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXATTRIBFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLGETINTEGERUI64I_VNVPROC) (GLenum value, GLuint index, GLuint64EXT *result); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferAddressRangeNV (GLenum pname, GLuint index, GLuint64EXT address, GLsizeiptr length); +GLAPI void APIENTRY glVertexFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glNormalFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glColorFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glIndexFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glTexCoordFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glEdgeFlagFormatNV (GLsizei stride); +GLAPI void APIENTRY glSecondaryColorFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glFogCoordFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glVertexAttribFormatNV (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride); +GLAPI void APIENTRY glVertexAttribIFormatNV (GLuint index, GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glGetIntegerui64i_vNV (GLenum value, GLuint index, GLuint64EXT *result); +#endif +#endif /* GL_NV_vertex_buffer_unified_memory */ + +#ifndef GL_NV_vertex_program +#define GL_NV_vertex_program 1 +#define GL_VERTEX_PROGRAM_NV 0x8620 +#define GL_VERTEX_STATE_PROGRAM_NV 0x8621 +#define GL_ATTRIB_ARRAY_SIZE_NV 0x8623 +#define GL_ATTRIB_ARRAY_STRIDE_NV 0x8624 +#define GL_ATTRIB_ARRAY_TYPE_NV 0x8625 +#define GL_CURRENT_ATTRIB_NV 0x8626 +#define GL_PROGRAM_LENGTH_NV 0x8627 +#define GL_PROGRAM_STRING_NV 0x8628 +#define GL_MODELVIEW_PROJECTION_NV 0x8629 +#define GL_IDENTITY_NV 0x862A +#define GL_INVERSE_NV 0x862B +#define GL_TRANSPOSE_NV 0x862C +#define GL_INVERSE_TRANSPOSE_NV 0x862D +#define GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV 0x862E +#define GL_MAX_TRACK_MATRICES_NV 0x862F +#define GL_MATRIX0_NV 0x8630 +#define GL_MATRIX1_NV 0x8631 +#define GL_MATRIX2_NV 0x8632 +#define GL_MATRIX3_NV 0x8633 +#define GL_MATRIX4_NV 0x8634 +#define GL_MATRIX5_NV 0x8635 +#define GL_MATRIX6_NV 0x8636 +#define GL_MATRIX7_NV 0x8637 +#define GL_CURRENT_MATRIX_STACK_DEPTH_NV 0x8640 +#define GL_CURRENT_MATRIX_NV 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_NV 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_NV 0x8643 +#define GL_PROGRAM_PARAMETER_NV 0x8644 +#define GL_ATTRIB_ARRAY_POINTER_NV 0x8645 +#define GL_PROGRAM_TARGET_NV 0x8646 +#define GL_PROGRAM_RESIDENT_NV 0x8647 +#define GL_TRACK_MATRIX_NV 0x8648 +#define GL_TRACK_MATRIX_TRANSFORM_NV 0x8649 +#define GL_VERTEX_PROGRAM_BINDING_NV 0x864A +#define GL_PROGRAM_ERROR_POSITION_NV 0x864B +#define GL_VERTEX_ATTRIB_ARRAY0_NV 0x8650 +#define GL_VERTEX_ATTRIB_ARRAY1_NV 0x8651 +#define GL_VERTEX_ATTRIB_ARRAY2_NV 0x8652 +#define GL_VERTEX_ATTRIB_ARRAY3_NV 0x8653 +#define GL_VERTEX_ATTRIB_ARRAY4_NV 0x8654 +#define GL_VERTEX_ATTRIB_ARRAY5_NV 0x8655 +#define GL_VERTEX_ATTRIB_ARRAY6_NV 0x8656 +#define GL_VERTEX_ATTRIB_ARRAY7_NV 0x8657 +#define GL_VERTEX_ATTRIB_ARRAY8_NV 0x8658 +#define GL_VERTEX_ATTRIB_ARRAY9_NV 0x8659 +#define GL_VERTEX_ATTRIB_ARRAY10_NV 0x865A +#define GL_VERTEX_ATTRIB_ARRAY11_NV 0x865B +#define GL_VERTEX_ATTRIB_ARRAY12_NV 0x865C +#define GL_VERTEX_ATTRIB_ARRAY13_NV 0x865D +#define GL_VERTEX_ATTRIB_ARRAY14_NV 0x865E +#define GL_VERTEX_ATTRIB_ARRAY15_NV 0x865F +#define GL_MAP1_VERTEX_ATTRIB0_4_NV 0x8660 +#define GL_MAP1_VERTEX_ATTRIB1_4_NV 0x8661 +#define GL_MAP1_VERTEX_ATTRIB2_4_NV 0x8662 +#define GL_MAP1_VERTEX_ATTRIB3_4_NV 0x8663 +#define GL_MAP1_VERTEX_ATTRIB4_4_NV 0x8664 +#define GL_MAP1_VERTEX_ATTRIB5_4_NV 0x8665 +#define GL_MAP1_VERTEX_ATTRIB6_4_NV 0x8666 +#define GL_MAP1_VERTEX_ATTRIB7_4_NV 0x8667 +#define GL_MAP1_VERTEX_ATTRIB8_4_NV 0x8668 +#define GL_MAP1_VERTEX_ATTRIB9_4_NV 0x8669 +#define GL_MAP1_VERTEX_ATTRIB10_4_NV 0x866A +#define GL_MAP1_VERTEX_ATTRIB11_4_NV 0x866B +#define GL_MAP1_VERTEX_ATTRIB12_4_NV 0x866C +#define GL_MAP1_VERTEX_ATTRIB13_4_NV 0x866D +#define GL_MAP1_VERTEX_ATTRIB14_4_NV 0x866E +#define GL_MAP1_VERTEX_ATTRIB15_4_NV 0x866F +#define GL_MAP2_VERTEX_ATTRIB0_4_NV 0x8670 +#define GL_MAP2_VERTEX_ATTRIB1_4_NV 0x8671 +#define GL_MAP2_VERTEX_ATTRIB2_4_NV 0x8672 +#define GL_MAP2_VERTEX_ATTRIB3_4_NV 0x8673 +#define GL_MAP2_VERTEX_ATTRIB4_4_NV 0x8674 +#define GL_MAP2_VERTEX_ATTRIB5_4_NV 0x8675 +#define GL_MAP2_VERTEX_ATTRIB6_4_NV 0x8676 +#define GL_MAP2_VERTEX_ATTRIB7_4_NV 0x8677 +#define GL_MAP2_VERTEX_ATTRIB8_4_NV 0x8678 +#define GL_MAP2_VERTEX_ATTRIB9_4_NV 0x8679 +#define GL_MAP2_VERTEX_ATTRIB10_4_NV 0x867A +#define GL_MAP2_VERTEX_ATTRIB11_4_NV 0x867B +#define GL_MAP2_VERTEX_ATTRIB12_4_NV 0x867C +#define GL_MAP2_VERTEX_ATTRIB13_4_NV 0x867D +#define GL_MAP2_VERTEX_ATTRIB14_4_NV 0x867E +#define GL_MAP2_VERTEX_ATTRIB15_4_NV 0x867F +typedef GLboolean (APIENTRYP PFNGLAREPROGRAMSRESIDENTNVPROC) (GLsizei n, const GLuint *programs, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDPROGRAMNVPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLEXECUTEPROGRAMNVPROC) (GLenum target, GLuint id, const GLfloat *params); +typedef void (APIENTRYP PFNGLGENPROGRAMSNVPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERDVNVPROC) (GLenum target, GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGNVPROC) (GLuint id, GLenum pname, GLubyte *program); +typedef void (APIENTRYP PFNGLGETTRACKMATRIXIVNVPROC) (GLenum target, GLuint address, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVNVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVNVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVNVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVNVPROC) (GLuint index, GLenum pname, void **pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLLOADPROGRAMNVPROC) (GLenum target, GLuint id, GLsizei len, const GLubyte *program); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DNVPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DVNVPROC) (GLenum target, GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FNVPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FVNVPROC) (GLenum target, GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4DVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4FVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLREQUESTRESIDENTPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLTRACKMATRIXNVPROC) (GLenum target, GLuint address, GLenum matrix, GLenum transform); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERNVPROC) (GLuint index, GLint fsize, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DNVPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FNVPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SNVPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DNVPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FNVPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SNVPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBNVPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVNVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4UBVNVPROC) (GLuint index, GLsizei count, const GLubyte *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreProgramsResidentNV (GLsizei n, const GLuint *programs, GLboolean *residences); +GLAPI void APIENTRY glBindProgramNV (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteProgramsNV (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glExecuteProgramNV (GLenum target, GLuint id, const GLfloat *params); +GLAPI void APIENTRY glGenProgramsNV (GLsizei n, GLuint *programs); +GLAPI void APIENTRY glGetProgramParameterdvNV (GLenum target, GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetProgramParameterfvNV (GLenum target, GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetProgramivNV (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramStringNV (GLuint id, GLenum pname, GLubyte *program); +GLAPI void APIENTRY glGetTrackMatrixivNV (GLenum target, GLuint address, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribdvNV (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfvNV (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribivNV (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointervNV (GLuint index, GLenum pname, void **pointer); +GLAPI GLboolean APIENTRY glIsProgramNV (GLuint id); +GLAPI void APIENTRY glLoadProgramNV (GLenum target, GLuint id, GLsizei len, const GLubyte *program); +GLAPI void APIENTRY glProgramParameter4dNV (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramParameter4dvNV (GLenum target, GLuint index, const GLdouble *v); +GLAPI void APIENTRY glProgramParameter4fNV (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramParameter4fvNV (GLenum target, GLuint index, const GLfloat *v); +GLAPI void APIENTRY glProgramParameters4dvNV (GLenum target, GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glProgramParameters4fvNV (GLenum target, GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glRequestResidentProgramsNV (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glTrackMatrixNV (GLenum target, GLuint address, GLenum matrix, GLenum transform); +GLAPI void APIENTRY glVertexAttribPointerNV (GLuint index, GLint fsize, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glVertexAttrib1dNV (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1fNV (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1sNV (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2dNV (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2fNV (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2sNV (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3dNV (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3fNV (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3sNV (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4dNV (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4fNV (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4sNV (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubNV (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4ubvNV (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribs1dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs1fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs1svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs2dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs2fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs2svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs3dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs3fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs3svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs4dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs4fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs4svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs4ubvNV (GLuint index, GLsizei count, const GLubyte *v); +#endif +#endif /* GL_NV_vertex_program */ + +#ifndef GL_NV_vertex_program1_1 +#define GL_NV_vertex_program1_1 1 +#endif /* GL_NV_vertex_program1_1 */ + +#ifndef GL_NV_vertex_program2 +#define GL_NV_vertex_program2 1 +#endif /* GL_NV_vertex_program2 */ + +#ifndef GL_NV_vertex_program2_option +#define GL_NV_vertex_program2_option 1 +#endif /* GL_NV_vertex_program2_option */ + +#ifndef GL_NV_vertex_program3 +#define GL_NV_vertex_program3 1 +#endif /* GL_NV_vertex_program3 */ + +#ifndef GL_NV_vertex_program4 +#define GL_NV_vertex_program4 1 +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER_NV 0x88FD +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IEXTPROC) (GLuint index, GLint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IEXTPROC) (GLuint index, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IEXTPROC) (GLuint index, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IEXTPROC) (GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIEXTPROC) (GLuint index, GLuint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIEXTPROC) (GLuint index, GLuint x, GLuint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIEXTPROC) (GLuint index, GLuint x, GLuint y, GLuint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIEXTPROC) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4BVEXTPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4SVEXTPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UBVEXTPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4USVEXTPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIPOINTEREXTPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIIVEXTPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIUIVEXTPROC) (GLuint index, GLenum pname, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribI1iEXT (GLuint index, GLint x); +GLAPI void APIENTRY glVertexAttribI2iEXT (GLuint index, GLint x, GLint y); +GLAPI void APIENTRY glVertexAttribI3iEXT (GLuint index, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexAttribI4iEXT (GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexAttribI1uiEXT (GLuint index, GLuint x); +GLAPI void APIENTRY glVertexAttribI2uiEXT (GLuint index, GLuint x, GLuint y); +GLAPI void APIENTRY glVertexAttribI3uiEXT (GLuint index, GLuint x, GLuint y, GLuint z); +GLAPI void APIENTRY glVertexAttribI4uiEXT (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glVertexAttribI1ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI2ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI3ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI4ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI1uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI2uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI3uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4bvEXT (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttribI4svEXT (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttribI4ubvEXT (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribI4usvEXT (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribIPointerEXT (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glGetVertexAttribIivEXT (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribIuivEXT (GLuint index, GLenum pname, GLuint *params); +#endif +#endif /* GL_NV_vertex_program4 */ + +#ifndef GL_NV_video_capture +#define GL_NV_video_capture 1 +#define GL_VIDEO_BUFFER_NV 0x9020 +#define GL_VIDEO_BUFFER_BINDING_NV 0x9021 +#define GL_FIELD_UPPER_NV 0x9022 +#define GL_FIELD_LOWER_NV 0x9023 +#define GL_NUM_VIDEO_CAPTURE_STREAMS_NV 0x9024 +#define GL_NEXT_VIDEO_CAPTURE_BUFFER_STATUS_NV 0x9025 +#define GL_VIDEO_CAPTURE_TO_422_SUPPORTED_NV 0x9026 +#define GL_LAST_VIDEO_CAPTURE_STATUS_NV 0x9027 +#define GL_VIDEO_BUFFER_PITCH_NV 0x9028 +#define GL_VIDEO_COLOR_CONVERSION_MATRIX_NV 0x9029 +#define GL_VIDEO_COLOR_CONVERSION_MAX_NV 0x902A +#define GL_VIDEO_COLOR_CONVERSION_MIN_NV 0x902B +#define GL_VIDEO_COLOR_CONVERSION_OFFSET_NV 0x902C +#define GL_VIDEO_BUFFER_INTERNAL_FORMAT_NV 0x902D +#define GL_PARTIAL_SUCCESS_NV 0x902E +#define GL_SUCCESS_NV 0x902F +#define GL_FAILURE_NV 0x9030 +#define GL_YCBYCR8_422_NV 0x9031 +#define GL_YCBAYCR8A_4224_NV 0x9032 +#define GL_Z6Y10Z6CB10Z6Y10Z6CR10_422_NV 0x9033 +#define GL_Z6Y10Z6CB10Z6A10Z6Y10Z6CR10Z6A10_4224_NV 0x9034 +#define GL_Z4Y12Z4CB12Z4Y12Z4CR12_422_NV 0x9035 +#define GL_Z4Y12Z4CB12Z4A12Z4Y12Z4CR12Z4A12_4224_NV 0x9036 +#define GL_Z4Y12Z4CB12Z4CR12_444_NV 0x9037 +#define GL_VIDEO_CAPTURE_FRAME_WIDTH_NV 0x9038 +#define GL_VIDEO_CAPTURE_FRAME_HEIGHT_NV 0x9039 +#define GL_VIDEO_CAPTURE_FIELD_UPPER_HEIGHT_NV 0x903A +#define GL_VIDEO_CAPTURE_FIELD_LOWER_HEIGHT_NV 0x903B +#define GL_VIDEO_CAPTURE_SURFACE_ORIGIN_NV 0x903C +typedef void (APIENTRYP PFNGLBEGINVIDEOCAPTURENVPROC) (GLuint video_capture_slot); +typedef void (APIENTRYP PFNGLBINDVIDEOCAPTURESTREAMBUFFERNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLintptrARB offset); +typedef void (APIENTRYP PFNGLBINDVIDEOCAPTURESTREAMTEXTURENVPROC) (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLENDVIDEOCAPTURENVPROC) (GLuint video_capture_slot); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTUREIVNVPROC) (GLuint video_capture_slot, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMIVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMFVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMDVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLdouble *params); +typedef GLenum (APIENTRYP PFNGLVIDEOCAPTURENVPROC) (GLuint video_capture_slot, GLuint *sequence_num, GLuint64EXT *capture_time); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERIVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERFVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERDVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLdouble *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginVideoCaptureNV (GLuint video_capture_slot); +GLAPI void APIENTRY glBindVideoCaptureStreamBufferNV (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLintptrARB offset); +GLAPI void APIENTRY glBindVideoCaptureStreamTextureNV (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLenum target, GLuint texture); +GLAPI void APIENTRY glEndVideoCaptureNV (GLuint video_capture_slot); +GLAPI void APIENTRY glGetVideoCaptureivNV (GLuint video_capture_slot, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideoCaptureStreamivNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideoCaptureStreamfvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVideoCaptureStreamdvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLdouble *params); +GLAPI GLenum APIENTRY glVideoCaptureNV (GLuint video_capture_slot, GLuint *sequence_num, GLuint64EXT *capture_time); +GLAPI void APIENTRY glVideoCaptureStreamParameterivNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLint *params); +GLAPI void APIENTRY glVideoCaptureStreamParameterfvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glVideoCaptureStreamParameterdvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLdouble *params); +#endif +#endif /* GL_NV_video_capture */ + +#ifndef GL_OML_interlace +#define GL_OML_interlace 1 +#define GL_INTERLACE_OML 0x8980 +#define GL_INTERLACE_READ_OML 0x8981 +#endif /* GL_OML_interlace */ + +#ifndef GL_OML_resample +#define GL_OML_resample 1 +#define GL_PACK_RESAMPLE_OML 0x8984 +#define GL_UNPACK_RESAMPLE_OML 0x8985 +#define GL_RESAMPLE_REPLICATE_OML 0x8986 +#define GL_RESAMPLE_ZERO_FILL_OML 0x8987 +#define GL_RESAMPLE_AVERAGE_OML 0x8988 +#define GL_RESAMPLE_DECIMATE_OML 0x8989 +#endif /* GL_OML_resample */ + +#ifndef GL_OML_subsample +#define GL_OML_subsample 1 +#define GL_FORMAT_SUBSAMPLE_24_24_OML 0x8982 +#define GL_FORMAT_SUBSAMPLE_244_244_OML 0x8983 +#endif /* GL_OML_subsample */ + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 +#define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD +#define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 +#define GL_ALWAYS_FAST_HINT_PGI 0x1A20C +#define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E +#define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F +#define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 +#define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 +#define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 +#define GL_FULL_STIPPLE_HINT_PGI 0x1A219 +#define GL_CLIP_NEAR_HINT_PGI 0x1A220 +#define GL_CLIP_FAR_HINT_PGI 0x1A221 +#define GL_WIDE_LINE_HINT_PGI 0x1A222 +#define GL_BACK_NORMALS_HINT_PGI 0x1A223 +typedef void (APIENTRYP PFNGLHINTPGIPROC) (GLenum target, GLint mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glHintPGI (GLenum target, GLint mode); +#endif +#endif /* GL_PGI_misc_hints */ + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#define GL_VERTEX_DATA_HINT_PGI 0x1A22A +#define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B +#define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C +#define GL_MAX_VERTEX_HINT_PGI 0x1A22D +#define GL_COLOR3_BIT_PGI 0x00010000 +#define GL_COLOR4_BIT_PGI 0x00020000 +#define GL_EDGEFLAG_BIT_PGI 0x00040000 +#define GL_INDEX_BIT_PGI 0x00080000 +#define GL_MAT_AMBIENT_BIT_PGI 0x00100000 +#define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 +#define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 +#define GL_MAT_EMISSION_BIT_PGI 0x00800000 +#define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 +#define GL_MAT_SHININESS_BIT_PGI 0x02000000 +#define GL_MAT_SPECULAR_BIT_PGI 0x04000000 +#define GL_NORMAL_BIT_PGI 0x08000000 +#define GL_TEXCOORD1_BIT_PGI 0x10000000 +#define GL_TEXCOORD2_BIT_PGI 0x20000000 +#define GL_TEXCOORD3_BIT_PGI 0x40000000 +#define GL_TEXCOORD4_BIT_PGI 0x80000000 +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#endif /* GL_PGI_vertex_hints */ + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif /* GL_REND_screen_coordinates */ + +#ifndef GL_S3_s3tc +#define GL_S3_s3tc 1 +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 +#define GL_RGBA_S3TC 0x83A2 +#define GL_RGBA4_S3TC 0x83A3 +#define GL_RGBA_DXT5_S3TC 0x83A4 +#define GL_RGBA4_DXT5_S3TC 0x83A5 +#endif /* GL_S3_s3tc */ + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 +#define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 +#define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 +#define GL_LINEAR_DETAIL_SGIS 0x8097 +#define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 +#define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 +#define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A +#define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B +#define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C +typedef void (APIENTRYP PFNGLDETAILTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETDETAILTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDetailTexFuncSGIS (GLenum target, GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetDetailTexFuncSGIS (GLenum target, GLfloat *points); +#endif +#endif /* GL_SGIS_detail_texture */ + +#ifndef GL_SGIS_fog_function +#define GL_SGIS_fog_function 1 +#define GL_FOG_FUNC_SGIS 0x812A +#define GL_FOG_FUNC_POINTS_SGIS 0x812B +#define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C +typedef void (APIENTRYP PFNGLFOGFUNCSGISPROC) (GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETFOGFUNCSGISPROC) (GLfloat *points); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogFuncSGIS (GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetFogFuncSGIS (GLfloat *points); +#endif +#endif /* GL_SGIS_fog_function */ + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif /* GL_SGIS_generate_mipmap */ + +#ifndef GL_SGIS_multisample +#define GL_SGIS_multisample 1 +#define GL_MULTISAMPLE_SGIS 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F +#define GL_SAMPLE_MASK_SGIS 0x80A0 +#define GL_1PASS_SGIS 0x80A1 +#define GL_2PASS_0_SGIS 0x80A2 +#define GL_2PASS_1_SGIS 0x80A3 +#define GL_4PASS_0_SGIS 0x80A4 +#define GL_4PASS_1_SGIS 0x80A5 +#define GL_4PASS_2_SGIS 0x80A6 +#define GL_4PASS_3_SGIS 0x80A7 +#define GL_SAMPLE_BUFFERS_SGIS 0x80A8 +#define GL_SAMPLES_SGIS 0x80A9 +#define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA +#define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB +#define GL_SAMPLE_PATTERN_SGIS 0x80AC +typedef void (APIENTRYP PFNGLSAMPLEMASKSGISPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNSGISPROC) (GLenum pattern); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskSGIS (GLclampf value, GLboolean invert); +GLAPI void APIENTRY glSamplePatternSGIS (GLenum pattern); +#endif +#endif /* GL_SGIS_multisample */ + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 +#define GL_PIXEL_TEXTURE_SGIS 0x8353 +#define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 +#define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 +#define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERISGISPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenParameteriSGIS (GLenum pname, GLint param); +GLAPI void APIENTRY glPixelTexGenParameterivSGIS (GLenum pname, const GLint *params); +GLAPI void APIENTRY glPixelTexGenParameterfSGIS (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPixelTexGenParameterfvSGIS (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetPixelTexGenParameterivSGIS (GLenum pname, GLint *params); +GLAPI void APIENTRY glGetPixelTexGenParameterfvSGIS (GLenum pname, GLfloat *params); +#endif +#endif /* GL_SGIS_pixel_texture */ + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 +#endif /* GL_SGIS_point_line_texgen */ + +#ifndef GL_SGIS_point_parameters +#define GL_SGIS_point_parameters 1 +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#define GL_DISTANCE_ATTENUATION_SGIS 0x8129 +typedef void (APIENTRYP PFNGLPOINTPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfSGIS (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvSGIS (GLenum pname, const GLfloat *params); +#endif +#endif /* GL_SGIS_point_parameters */ + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 +#define GL_LINEAR_SHARPEN_SGIS 0x80AD +#define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE +#define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF +#define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 +typedef void (APIENTRYP PFNGLSHARPENTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETSHARPENTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSharpenTexFuncSGIS (GLenum target, GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetSharpenTexFuncSGIS (GLenum target, GLfloat *points); +#endif +#endif /* GL_SGIS_sharpen_texture */ + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 +#define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 +#define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 +#define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 +#define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 +#define GL_TEXTURE_4D_SGIS 0x8134 +#define GL_PROXY_TEXTURE_4D_SGIS 0x8135 +#define GL_TEXTURE_4DSIZE_SGIS 0x8136 +#define GL_TEXTURE_WRAP_Q_SGIS 0x8137 +#define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 +#define GL_TEXTURE_4D_BINDING_SGIS 0x814F +typedef void (APIENTRYP PFNGLTEXIMAGE4DSGISPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE4DSGISPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const void *pixels); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage4DSGIS (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTexSubImage4DSGIS (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const void *pixels); +#endif +#endif /* GL_SGIS_texture4D */ + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#endif /* GL_SGIS_texture_border_clamp */ + +#ifndef GL_SGIS_texture_color_mask +#define GL_SGIS_texture_color_mask 1 +#define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF +typedef void (APIENTRYP PFNGLTEXTURECOLORMASKSGISPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureColorMaskSGIS (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#endif +#endif /* GL_SGIS_texture_color_mask */ + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif /* GL_SGIS_texture_edge_clamp */ + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 +#define GL_FILTER4_SGIS 0x8146 +#define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 +typedef void (APIENTRYP PFNGLGETTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLfloat *weights); +typedef void (APIENTRYP PFNGLTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetTexFilterFuncSGIS (GLenum target, GLenum filter, GLfloat *weights); +GLAPI void APIENTRY glTexFilterFuncSGIS (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#endif +#endif /* GL_SGIS_texture_filter4 */ + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 +#define GL_TEXTURE_MIN_LOD_SGIS 0x813A +#define GL_TEXTURE_MAX_LOD_SGIS 0x813B +#define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C +#define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D +#endif /* GL_SGIS_texture_lod */ + +#ifndef GL_SGIS_texture_select +#define GL_SGIS_texture_select 1 +#define GL_DUAL_ALPHA4_SGIS 0x8110 +#define GL_DUAL_ALPHA8_SGIS 0x8111 +#define GL_DUAL_ALPHA12_SGIS 0x8112 +#define GL_DUAL_ALPHA16_SGIS 0x8113 +#define GL_DUAL_LUMINANCE4_SGIS 0x8114 +#define GL_DUAL_LUMINANCE8_SGIS 0x8115 +#define GL_DUAL_LUMINANCE12_SGIS 0x8116 +#define GL_DUAL_LUMINANCE16_SGIS 0x8117 +#define GL_DUAL_INTENSITY4_SGIS 0x8118 +#define GL_DUAL_INTENSITY8_SGIS 0x8119 +#define GL_DUAL_INTENSITY12_SGIS 0x811A +#define GL_DUAL_INTENSITY16_SGIS 0x811B +#define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C +#define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D +#define GL_QUAD_ALPHA4_SGIS 0x811E +#define GL_QUAD_ALPHA8_SGIS 0x811F +#define GL_QUAD_LUMINANCE4_SGIS 0x8120 +#define GL_QUAD_LUMINANCE8_SGIS 0x8121 +#define GL_QUAD_INTENSITY4_SGIS 0x8122 +#define GL_QUAD_INTENSITY8_SGIS 0x8123 +#define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 +#define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 +#endif /* GL_SGIS_texture_select */ + +#ifndef GL_SGIX_async +#define GL_SGIX_async 1 +#define GL_ASYNC_MARKER_SGIX 0x8329 +typedef void (APIENTRYP PFNGLASYNCMARKERSGIXPROC) (GLuint marker); +typedef GLint (APIENTRYP PFNGLFINISHASYNCSGIXPROC) (GLuint *markerp); +typedef GLint (APIENTRYP PFNGLPOLLASYNCSGIXPROC) (GLuint *markerp); +typedef GLuint (APIENTRYP PFNGLGENASYNCMARKERSSGIXPROC) (GLsizei range); +typedef void (APIENTRYP PFNGLDELETEASYNCMARKERSSGIXPROC) (GLuint marker, GLsizei range); +typedef GLboolean (APIENTRYP PFNGLISASYNCMARKERSGIXPROC) (GLuint marker); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glAsyncMarkerSGIX (GLuint marker); +GLAPI GLint APIENTRY glFinishAsyncSGIX (GLuint *markerp); +GLAPI GLint APIENTRY glPollAsyncSGIX (GLuint *markerp); +GLAPI GLuint APIENTRY glGenAsyncMarkersSGIX (GLsizei range); +GLAPI void APIENTRY glDeleteAsyncMarkersSGIX (GLuint marker, GLsizei range); +GLAPI GLboolean APIENTRY glIsAsyncMarkerSGIX (GLuint marker); +#endif +#endif /* GL_SGIX_async */ + +#ifndef GL_SGIX_async_histogram +#define GL_SGIX_async_histogram 1 +#define GL_ASYNC_HISTOGRAM_SGIX 0x832C +#define GL_MAX_ASYNC_HISTOGRAM_SGIX 0x832D +#endif /* GL_SGIX_async_histogram */ + +#ifndef GL_SGIX_async_pixel +#define GL_SGIX_async_pixel 1 +#define GL_ASYNC_TEX_IMAGE_SGIX 0x835C +#define GL_ASYNC_DRAW_PIXELS_SGIX 0x835D +#define GL_ASYNC_READ_PIXELS_SGIX 0x835E +#define GL_MAX_ASYNC_TEX_IMAGE_SGIX 0x835F +#define GL_MAX_ASYNC_DRAW_PIXELS_SGIX 0x8360 +#define GL_MAX_ASYNC_READ_PIXELS_SGIX 0x8361 +#endif /* GL_SGIX_async_pixel */ + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif /* GL_SGIX_blend_alpha_minmax */ + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_SGIX_calligraphic_fragment 1 +#define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 +#endif /* GL_SGIX_calligraphic_fragment */ + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 +#define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 +#define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 +#define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 +#define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 +#define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 +#define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 +#define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 +#define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 +#define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 +#define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D +#define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E +#define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F +#endif /* GL_SGIX_clipmap */ + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 +#define GL_CONVOLUTION_HINT_SGIX 0x8316 +#endif /* GL_SGIX_convolution_accuracy */ + +#ifndef GL_SGIX_depth_pass_instrument +#define GL_SGIX_depth_pass_instrument 1 +#endif /* GL_SGIX_depth_pass_instrument */ + +#ifndef GL_SGIX_depth_texture +#define GL_SGIX_depth_texture 1 +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif /* GL_SGIX_depth_texture */ + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 +typedef void (APIENTRYP PFNGLFLUSHRASTERSGIXPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushRasterSGIX (void); +#endif +#endif /* GL_SGIX_flush_raster */ + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif /* GL_SGIX_fog_offset */ + +#ifndef GL_SGIX_fragment_lighting +#define GL_SGIX_fragment_lighting 1 +#define GL_FRAGMENT_LIGHTING_SGIX 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 +#define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 +#define GL_LIGHT_ENV_MODE_SGIX 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B +#define GL_FRAGMENT_LIGHT0_SGIX 0x840C +#define GL_FRAGMENT_LIGHT1_SGIX 0x840D +#define GL_FRAGMENT_LIGHT2_SGIX 0x840E +#define GL_FRAGMENT_LIGHT3_SGIX 0x840F +#define GL_FRAGMENT_LIGHT4_SGIX 0x8410 +#define GL_FRAGMENT_LIGHT5_SGIX 0x8411 +#define GL_FRAGMENT_LIGHT6_SGIX 0x8412 +#define GL_FRAGMENT_LIGHT7_SGIX 0x8413 +typedef void (APIENTRYP PFNGLFRAGMENTCOLORMATERIALSGIXPROC) (GLenum face, GLenum mode); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFSGIXPROC) (GLenum light, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTISGIXPROC) (GLenum light, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELIVSGIXPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFSGIXPROC) (GLenum face, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALISGIXPROC) (GLenum face, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLIGHTENVISGIXPROC) (GLenum pname, GLint param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFragmentColorMaterialSGIX (GLenum face, GLenum mode); +GLAPI void APIENTRY glFragmentLightfSGIX (GLenum light, GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentLightfvSGIX (GLenum light, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentLightiSGIX (GLenum light, GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentLightivSGIX (GLenum light, GLenum pname, const GLint *params); +GLAPI void APIENTRY glFragmentLightModelfSGIX (GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentLightModelfvSGIX (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentLightModeliSGIX (GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentLightModelivSGIX (GLenum pname, const GLint *params); +GLAPI void APIENTRY glFragmentMaterialfSGIX (GLenum face, GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentMaterialfvSGIX (GLenum face, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentMaterialiSGIX (GLenum face, GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentMaterialivSGIX (GLenum face, GLenum pname, const GLint *params); +GLAPI void APIENTRY glGetFragmentLightfvSGIX (GLenum light, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFragmentLightivSGIX (GLenum light, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetFragmentMaterialfvSGIX (GLenum face, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFragmentMaterialivSGIX (GLenum face, GLenum pname, GLint *params); +GLAPI void APIENTRY glLightEnviSGIX (GLenum pname, GLint param); +#endif +#endif /* GL_SGIX_fragment_lighting */ + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 +#define GL_FRAMEZOOM_SGIX 0x818B +#define GL_FRAMEZOOM_FACTOR_SGIX 0x818C +#define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D +typedef void (APIENTRYP PFNGLFRAMEZOOMSGIXPROC) (GLint factor); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFrameZoomSGIX (GLint factor); +#endif +#endif /* GL_SGIX_framezoom */ + +#ifndef GL_SGIX_igloo_interface +#define GL_SGIX_igloo_interface 1 +typedef void (APIENTRYP PFNGLIGLOOINTERFACESGIXPROC) (GLenum pname, const void *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIglooInterfaceSGIX (GLenum pname, const void *params); +#endif +#endif /* GL_SGIX_igloo_interface */ + +#ifndef GL_SGIX_instruments +#define GL_SGIX_instruments 1 +#define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 +#define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 +typedef GLint (APIENTRYP PFNGLGETINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLINSTRUMENTSBUFFERSGIXPROC) (GLsizei size, GLint *buffer); +typedef GLint (APIENTRYP PFNGLPOLLINSTRUMENTSSGIXPROC) (GLint *marker_p); +typedef void (APIENTRYP PFNGLREADINSTRUMENTSSGIXPROC) (GLint marker); +typedef void (APIENTRYP PFNGLSTARTINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLSTOPINSTRUMENTSSGIXPROC) (GLint marker); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLint APIENTRY glGetInstrumentsSGIX (void); +GLAPI void APIENTRY glInstrumentsBufferSGIX (GLsizei size, GLint *buffer); +GLAPI GLint APIENTRY glPollInstrumentsSGIX (GLint *marker_p); +GLAPI void APIENTRY glReadInstrumentsSGIX (GLint marker); +GLAPI void APIENTRY glStartInstrumentsSGIX (void); +GLAPI void APIENTRY glStopInstrumentsSGIX (GLint marker); +#endif +#endif /* GL_SGIX_instruments */ + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 +#define GL_INTERLACE_SGIX 0x8094 +#endif /* GL_SGIX_interlace */ + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 +#define GL_IR_INSTRUMENT1_SGIX 0x817F +#endif /* GL_SGIX_ir_instrument1 */ + +#ifndef GL_SGIX_list_priority +#define GL_SGIX_list_priority 1 +#define GL_LIST_PRIORITY_SGIX 0x8182 +typedef void (APIENTRYP PFNGLGETLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERFSGIXPROC) (GLuint list, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERISGIXPROC) (GLuint list, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, const GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetListParameterfvSGIX (GLuint list, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetListParameterivSGIX (GLuint list, GLenum pname, GLint *params); +GLAPI void APIENTRY glListParameterfSGIX (GLuint list, GLenum pname, GLfloat param); +GLAPI void APIENTRY glListParameterfvSGIX (GLuint list, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glListParameteriSGIX (GLuint list, GLenum pname, GLint param); +GLAPI void APIENTRY glListParameterivSGIX (GLuint list, GLenum pname, const GLint *params); +#endif +#endif /* GL_SGIX_list_priority */ + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +#define GL_PIXEL_TEX_GEN_SGIX 0x8139 +#define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B +typedef void (APIENTRYP PFNGLPIXELTEXGENSGIXPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenSGIX (GLenum mode); +#endif +#endif /* GL_SGIX_pixel_texture */ + +#ifndef GL_SGIX_pixel_tiles +#define GL_SGIX_pixel_tiles 1 +#define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E +#define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F +#define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 +#define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 +#define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 +#define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 +#define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 +#define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 +#endif /* GL_SGIX_pixel_tiles */ + +#ifndef GL_SGIX_polynomial_ffd +#define GL_SGIX_polynomial_ffd 1 +#define GL_TEXTURE_DEFORMATION_BIT_SGIX 0x00000001 +#define GL_GEOMETRY_DEFORMATION_BIT_SGIX 0x00000002 +#define GL_GEOMETRY_DEFORMATION_SGIX 0x8194 +#define GL_TEXTURE_DEFORMATION_SGIX 0x8195 +#define GL_DEFORMATIONS_MASK_SGIX 0x8196 +#define GL_MAX_DEFORMATION_ORDER_SGIX 0x8197 +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3DSGIXPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3FSGIXPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +typedef void (APIENTRYP PFNGLDEFORMSGIXPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLLOADIDENTITYDEFORMATIONMAPSGIXPROC) (GLbitfield mask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeformationMap3dSGIX (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +GLAPI void APIENTRY glDeformationMap3fSGIX (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +GLAPI void APIENTRY glDeformSGIX (GLbitfield mask); +GLAPI void APIENTRY glLoadIdentityDeformationMapSGIX (GLbitfield mask); +#endif +#endif /* GL_SGIX_polynomial_ffd */ + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 +#define GL_REFERENCE_PLANE_SGIX 0x817D +#define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E +typedef void (APIENTRYP PFNGLREFERENCEPLANESGIXPROC) (const GLdouble *equation); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReferencePlaneSGIX (const GLdouble *equation); +#endif +#endif /* GL_SGIX_reference_plane */ + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 +#define GL_PACK_RESAMPLE_SGIX 0x842C +#define GL_UNPACK_RESAMPLE_SGIX 0x842D +#define GL_RESAMPLE_REPLICATE_SGIX 0x842E +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x842F +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#endif /* GL_SGIX_resample */ + +#ifndef GL_SGIX_scalebias_hint +#define GL_SGIX_scalebias_hint 1 +#define GL_SCALEBIAS_HINT_SGIX 0x8322 +#endif /* GL_SGIX_scalebias_hint */ + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 +#define GL_TEXTURE_COMPARE_SGIX 0x819A +#define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B +#define GL_TEXTURE_LEQUAL_R_SGIX 0x819C +#define GL_TEXTURE_GEQUAL_R_SGIX 0x819D +#endif /* GL_SGIX_shadow */ + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif /* GL_SGIX_shadow_ambient */ + +#ifndef GL_SGIX_sprite +#define GL_SGIX_sprite 1 +#define GL_SPRITE_SGIX 0x8148 +#define GL_SPRITE_MODE_SGIX 0x8149 +#define GL_SPRITE_AXIS_SGIX 0x814A +#define GL_SPRITE_TRANSLATION_SGIX 0x814B +#define GL_SPRITE_AXIAL_SGIX 0x814C +#define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D +#define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERIVSGIXPROC) (GLenum pname, const GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSpriteParameterfSGIX (GLenum pname, GLfloat param); +GLAPI void APIENTRY glSpriteParameterfvSGIX (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glSpriteParameteriSGIX (GLenum pname, GLint param); +GLAPI void APIENTRY glSpriteParameterivSGIX (GLenum pname, const GLint *params); +#endif +#endif /* GL_SGIX_sprite */ + +#ifndef GL_SGIX_subsample +#define GL_SGIX_subsample 1 +#define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 +#define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 +#define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 +#define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 +#define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 +#endif /* GL_SGIX_subsample */ + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 +typedef void (APIENTRYP PFNGLTAGSAMPLEBUFFERSGIXPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTagSampleBufferSGIX (void); +#endif +#endif /* GL_SGIX_tag_sample_buffer */ + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 +#define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE +#endif /* GL_SGIX_texture_add_env */ + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_SGIX_texture_coordinate_clamp 1 +#define GL_TEXTURE_MAX_CLAMP_S_SGIX 0x8369 +#define GL_TEXTURE_MAX_CLAMP_T_SGIX 0x836A +#define GL_TEXTURE_MAX_CLAMP_R_SGIX 0x836B +#endif /* GL_SGIX_texture_coordinate_clamp */ + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 +#define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E +#define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F +#define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 +#endif /* GL_SGIX_texture_lod_bias */ + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif /* GL_SGIX_texture_multi_buffer */ + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 +#define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A +#define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B +#define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C +#endif /* GL_SGIX_texture_scale_bias */ + +#ifndef GL_SGIX_vertex_preclip +#define GL_SGIX_vertex_preclip 1 +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif /* GL_SGIX_vertex_preclip */ + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#define GL_YCRCB_422_SGIX 0x81BB +#define GL_YCRCB_444_SGIX 0x81BC +#endif /* GL_SGIX_ycrcb */ + +#ifndef GL_SGIX_ycrcb_subsample +#define GL_SGIX_ycrcb_subsample 1 +#endif /* GL_SGIX_ycrcb_subsample */ + +#ifndef GL_SGIX_ycrcba +#define GL_SGIX_ycrcba 1 +#define GL_YCRCB_SGIX 0x8318 +#define GL_YCRCBA_SGIX 0x8319 +#endif /* GL_SGIX_ycrcba */ + +#ifndef GL_SGI_color_matrix +#define GL_SGI_color_matrix 1 +#define GL_COLOR_MATRIX_SGI 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB +#endif /* GL_SGI_color_matrix */ + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 +#define GL_COLOR_TABLE_SGI 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 +#define GL_PROXY_COLOR_TABLE_SGI 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 +#define GL_COLOR_TABLE_SCALE_SGI 0x80D6 +#define GL_COLOR_TABLE_BIAS_SGI 0x80D7 +#define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 +#define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF +typedef void (APIENTRYP PFNGLCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLESGIPROC) (GLenum target, GLenum format, GLenum type, void *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableSGI (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); +GLAPI void APIENTRY glColorTableParameterfvSGI (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glColorTableParameterivSGI (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyColorTableSGI (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glGetColorTableSGI (GLenum target, GLenum format, GLenum type, void *table); +GLAPI void APIENTRY glGetColorTableParameterfvSGI (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetColorTableParameterivSGI (GLenum target, GLenum pname, GLint *params); +#endif +#endif /* GL_SGI_color_table */ + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif /* GL_SGI_texture_color_table */ + +#ifndef GL_SUNX_constant_data +#define GL_SUNX_constant_data 1 +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +typedef void (APIENTRYP PFNGLFINISHTEXTURESUNXPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFinishTextureSUNX (void); +#endif +#endif /* GL_SUNX_constant_data */ + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif /* GL_SUN_convolution_border_modes */ + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORBSUNPROC) (GLbyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORSSUNPROC) (GLshort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORISUNPROC) (GLint factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORFSUNPROC) (GLfloat factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORDSUNPROC) (GLdouble factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUBSUNPROC) (GLubyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUSSUNPROC) (GLushort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUISUNPROC) (GLuint factor); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGlobalAlphaFactorbSUN (GLbyte factor); +GLAPI void APIENTRY glGlobalAlphaFactorsSUN (GLshort factor); +GLAPI void APIENTRY glGlobalAlphaFactoriSUN (GLint factor); +GLAPI void APIENTRY glGlobalAlphaFactorfSUN (GLfloat factor); +GLAPI void APIENTRY glGlobalAlphaFactordSUN (GLdouble factor); +GLAPI void APIENTRY glGlobalAlphaFactorubSUN (GLubyte factor); +GLAPI void APIENTRY glGlobalAlphaFactorusSUN (GLushort factor); +GLAPI void APIENTRY glGlobalAlphaFactoruiSUN (GLuint factor); +#endif +#endif /* GL_SUN_global_alpha */ + +#ifndef GL_SUN_mesh_array +#define GL_SUN_mesh_array 1 +#define GL_QUAD_MESH_SUN 0x8614 +#define GL_TRIANGLE_MESH_SUN 0x8615 +typedef void (APIENTRYP PFNGLDRAWMESHARRAYSSUNPROC) (GLenum mode, GLint first, GLsizei count, GLsizei width); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawMeshArraysSUN (GLenum mode, GLint first, GLsizei count, GLsizei width); +#endif +#endif /* GL_SUN_mesh_array */ + +#ifndef GL_SUN_slice_accum +#define GL_SUN_slice_accum 1 +#define GL_SLICE_ACCUM_SUN 0x85CC +#endif /* GL_SUN_slice_accum */ + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 +#define GL_RESTART_SUN 0x0001 +#define GL_REPLACE_MIDDLE_SUN 0x0002 +#define GL_REPLACE_OLDEST_SUN 0x0003 +#define GL_TRIANGLE_LIST_SUN 0x81D7 +#define GL_REPLACEMENT_CODE_SUN 0x81D8 +#define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 +#define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 +#define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 +#define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 +#define GL_R1UI_V3F_SUN 0x85C4 +#define GL_R1UI_C4UB_V3F_SUN 0x85C5 +#define GL_R1UI_C3F_V3F_SUN 0x85C6 +#define GL_R1UI_N3F_V3F_SUN 0x85C7 +#define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 +#define GL_R1UI_T2F_V3F_SUN 0x85C9 +#define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA +#define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUISUNPROC) (GLuint code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSSUNPROC) (GLushort code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBSUNPROC) (GLubyte code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVSUNPROC) (const GLuint *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSVSUNPROC) (const GLushort *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBVSUNPROC) (const GLubyte *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEPOINTERSUNPROC) (GLenum type, GLsizei stride, const void **pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReplacementCodeuiSUN (GLuint code); +GLAPI void APIENTRY glReplacementCodeusSUN (GLushort code); +GLAPI void APIENTRY glReplacementCodeubSUN (GLubyte code); +GLAPI void APIENTRY glReplacementCodeuivSUN (const GLuint *code); +GLAPI void APIENTRY glReplacementCodeusvSUN (const GLushort *code); +GLAPI void APIENTRY glReplacementCodeubvSUN (const GLubyte *code); +GLAPI void APIENTRY glReplacementCodePointerSUN (GLenum type, GLsizei stride, const void **pointer); +#endif +#endif /* GL_SUN_triangle_list */ + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FSUNPROC) (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC) (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC) (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC) (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC) (const GLuint *rc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColor4ubVertex2fSUN (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +GLAPI void APIENTRY glColor4ubVertex2fvSUN (const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glColor4ubVertex3fSUN (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor4ubVertex3fvSUN (const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glColor3fVertex3fSUN (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor3fVertex3fvSUN (const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glNormal3fVertex3fSUN (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glNormal3fVertex3fvSUN (const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glColor4fNormal3fVertex3fSUN (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor4fNormal3fVertex3fvSUN (const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fVertex3fSUN (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fVertex3fvSUN (const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glTexCoord4fVertex4fSUN (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glTexCoord4fVertex4fvSUN (const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fSUN (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fvSUN (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fvSUN (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fSUN (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiVertex3fSUN (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiVertex3fvSUN (const GLuint *rc, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fSUN (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fvSUN (const GLuint *rc, const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fSUN (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fvSUN (const GLuint *rc, const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fSUN (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fSUN (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#endif +#endif /* GL_SUN_vertex */ + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif /* GL_WIN_phong_shading */ + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif /* GL_WIN_specular_fog */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/engine/thirdparty/stb/tests/caveview/glext_list.h b/source/engine/thirdparty/stb/tests/caveview/glext_list.h new file mode 100644 index 0000000..5cdbca5 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/caveview/glext_list.h @@ -0,0 +1,34 @@ +GLARB(ActiveTexture,ACTIVETEXTURE) +GLARB(ClientActiveTexture,CLIENTACTIVETEXTURE) +GLARB(MultiTexCoord2f,MULTITEXCOORD2F) +GLEXT(TexImage3D,TEXIMAGE3D) +GLEXT(TexSubImage3D,TEXSUBIMAGE3D) +GLEXT(GenerateMipmap,GENERATEMIPMAP) +GLARB(DebugMessageCallback,DEBUGMESSAGECALLBACK) + +GLCORE(VertexAttribIPointer,VERTEXATTRIBIPOINTER) + +GLEXT(BindFramebuffer,BINDFRAMEBUFFER) +GLEXT(DeleteFramebuffers,DELETEFRAMEBUFFERS) +GLEXT(GenFramebuffers,GENFRAMEBUFFERS) +GLEXT(CheckFramebufferStatus,CHECKFRAMEBUFFERSTATUS) +GLEXT(FramebufferTexture2D,FRAMEBUFFERTEXTURE2D) +GLEXT(BindRenderBuffer,BINDRENDERBUFFER) +GLEXT(RenderbufferStorage,RENDERBUFFERSTORAGE) +GLEXT(GenRenderbuffers,GENRENDERBUFFERS) +GLEXT(BindRenderbuffer,BINDRENDERBUFFER) +GLEXT(FramebufferRenderbuffer,FRAMEBUFFERRENDERBUFFER) +GLEXT(GenerateMipmap,GENERATEMIPMAP) + +GLARB(BindBuffer ,BINDBUFFER,) +GLARB(GenBuffers ,GENBUFFERS ) +GLARB(DeleteBuffers,DELETEBUFFERS) +GLARB(BufferData ,BUFFERDATA ) +GLARB(BufferSubData,BUFFERSUBDATA) +GLARB(MapBuffer ,MAPBUFFER ) +GLARB(UnmapBuffer ,UNMAPBUFFER ) +GLARB(TexBuffer ,TEXBUFFER ) + +GLEXT(NamedBufferStorage,NAMEDBUFFERSTORAGE) +GLE(BufferStorage,BUFFERSTORAGE) +GLE(GetStringi,GETSTRINGI) \ No newline at end of file diff --git a/source/engine/thirdparty/stb/tests/caveview/main.c b/source/engine/thirdparty/stb/tests/caveview/main.c new file mode 100644 index 0000000..e69de29 diff --git a/source/engine/thirdparty/stb/tests/caveview/stb_gl.h b/source/engine/thirdparty/stb/tests/caveview/stb_gl.h new file mode 100644 index 0000000..6498e28 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/caveview/stb_gl.h @@ -0,0 +1,1103 @@ +// stbgl - v0.04 - Sean Barrett 2008 - public domain +// +// Note that the gl extensions support requires glext.h. In fact, it works +// if you just concatenate glext.h onto the end of this file. In that case, +// this file is covered by the SGI FreeB license, and is not public domain. +// +// Extension usage: +// +// 1. Make a file called something like "extlist.txt" which contains stuff like: +// GLE(ShaderSourceARB,SHADERSOURCEARB) +// GLE(Uniform1iARB,UNIFORM1IARB) +// GLARB(ActiveTexture,ACTIVETEXTURE) // same as GLE(ActiveTextureARB,ACTIVETEXTUREARB) +// GLARB(ClientActiveTexture,CLIENTACTIVETEXTURE) +// GLE(MultiTexCoord2f,MULTITEXCOORD2F) +// +// 2. To declare functions (to make a header file), do this: +// #define STB_GLEXT_DECLARE "extlist.txt" +// #include "stb_gl.h" +// +// A good way to do this is to define STB_GLEXT_DECLARE project-wide. +// +// 3. To define functions (implement), do this in some C file: +// #define STB_GLEXT_DEFINE "extlist.txt" +// #include "stb_gl.h" +// +// If you've already defined STB_GLEXT_DECLARE, you can just do: +// #define STB_GLEXT_DEFINE_DECLARE +// #include "stb_gl.h" +// +// 4. Now you need to initialize: +// +// stbgl_initExtensions(); + + +#ifndef INCLUDE_STB_GL_H +#define INCLUDE_STB_GL_H + +#define STB_GL + +#ifdef _WIN32 +#ifndef WINGDIAPI +#define CALLBACK __stdcall +#define WINGDIAPI __declspec(dllimport) +#define APIENTRY __stdcall +#endif +#endif //_WIN32 + +#include + +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846f +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// like gluPerspective, but: +// fov is chosen to satisfy both hfov <= max_hfov & vfov <= max_vfov; +// set one to 179 or 0 to ignore it +// zoom is applied separately, so you can do linear zoom without +// mucking with trig with fov; 1 -> use exact fov +// 'aspect' is inferred from the current viewport, and ignores the +// possibility of non-square pixels +extern void stbgl_Perspective(float zoom, float max_hfov, float max_vfov, float znear, float zfar); +extern void stbgl_PerspectiveViewport(int x, int y, int w, int h, float zoom, float max_hfov, float max_vfov, float znear, float zfar); +extern void stbgl_initCamera_zup_facing_x(void); +extern void stbgl_initCamera_zup_facing_y(void); +extern void stbgl_positionCameraWithEulerAngles(float *loc, float *ang); +extern void stbgl_drawRect(float x0, float y0, float x1, float y1); +extern void stbgl_drawRectTC(float x0, float y0, float x1, float y1, float s0, float t0, float s1, float t1); +extern void stbgl_drawBox(float x, float y, float z, float sx, float sy, float sz, int cw); + +extern int stbgl_hasExtension(char *ext); +extern void stbgl_SimpleLight(int index, float bright, float x, float y, float z); +extern void stbgl_GlobalAmbient(float r, float g, float b); + +extern int stbgl_LoadTexture(char *filename, char *props); // only if stb_image is available + +extern int stbgl_TestTexture(int w); +extern int stbgl_TestTextureEx(int w, char *scale_table, int checks_log2, int r1,int g1,int b1, int r2, int b2, int g2); +extern unsigned int stbgl_rand(void); // internal, but exposed just in case; LCG, so use middle bits + +extern int stbgl_TexImage2D(int texid, int w, int h, void *data, char *props); +extern int stbgl_TexImage2D_Extra(int texid, int w, int h, void *data, int chan, char *props, int preserve_data); +// "props" is a series of characters (and blocks of characters), a la fopen()'s mode, +// e.g.: +// GLuint texid = stbgl_LoadTexture("myfile.jpg", "mbc") +// means: load the image "myfile.jpg", and do the following: +// generate mipmaps +// use bilinear filtering (not trilinear) +// use clamp-to-edge on both channels +// +// input descriptor: AT MOST ONE +// TEXT MEANING +// 1 1 channel of input (intensity/alpha) +// 2 2 channels of input (luminance, alpha) +// 3 3 channels of input (RGB) +// 4 4 channels of input (RGBA) +// l 1 channel of input (luminance) +// a 1 channel of input (alpha) +// la 2 channels of input (lum/alpha) +// rgb 3 channels of input (RGB) +// ycocg 3 channels of input (YCoCg - forces YCoCg output) +// ycocgj 4 channels of input (YCoCgJunk - forces YCoCg output) +// rgba 4 channels of input (RGBA) +// +// output descriptor: AT MOST ONE +// TEXT MEANING +// A 1 channel of output (alpha) +// I 1 channel of output (intensity) +// LA 2 channels of output (lum/alpha) +// RGB 3 channels of output (RGB) +// RGBA 4 channels of output (RGBA) +// DXT1 encode as a DXT1 texture (RGB unless input has RGBA) +// DXT3 encode as a DXT3 texture +// DXT5 encode as a DXT5 texture +// YCoCg encode as a DXT5 texture with Y in alpha, CoCg in RG +// D GL_DEPTH_COMPONENT +// NONE no input/output, don't call TexImage2D at all +// +// when reading from a file or using another interface with an explicit +// channel count, the input descriptor is ignored and instead the channel +// count is used as the input descriptor. if the file read is a DXT DDS, +// then it is passed directly to OpenGL in the file format. +// +// if an input descriptor is supplied but no output descriptor, the output +// is assumed to be the same as the input. if an output descriptor is supplied +// but no input descriptor, the input is assumed to be the same as the +// output. if neither is supplied, the input is assumed to be 4-channel. +// If DXT1 or YCoCG output is requested with no input, the input is assumed +// to be 4-channel but the alpha channel is ignored. +// +// filtering descriptor (default is no mipmaps) +// TEXT MEANING +// m generate mipmaps +// M mipmaps are provided, concatenated at end of data (from largest to smallest) +// t use trilinear filtering (default if mipmapped) +// b use bilinear filtering (default if not-mipmapped) +// n use nearest-neighbor sampling +// +// wrapping descriptor +// TEXT MEANING +// w wrap (default) +// c clamp-to-edge +// C GL_CLAMP (uses border color) +// +// If only one wrapping descriptor is supplied, it is applied to both channels. +// +// special: +// TEXT MEANING +// f input data is floats (default unsigned bytes) +// F input&output data is floats (default unsigned bytes) +// p explicitly pre-multiply the alpha +// P pad to power-of-two (default stretches) +// NP2 non-power-of-two +// + can overwrite the texture data with temp data +// ! free the texture data with "free" +// +// the properties string can also include spaces + +#ifdef __cplusplus +} +#endif + + +#ifdef STB_GL_IMPLEMENTATION +#include +#include +#include +#include + +int stbgl_hasExtension(char *ext) +{ + const char *s = glGetString(GL_EXTENSIONS); + for(;;) { + char *e = ext; + for (;;) { + if (*e == 0) { + if (*s == 0 || *s == ' ') return 1; + break; + } + if (*s != *e) + break; + ++s, ++e; + } + while (*s && *s != ' ') ++s; + if (!*s) return 0; + ++s; // skip space + } +} + +void stbgl_drawRect(float x0, float y0, float x1, float y1) +{ + glBegin(GL_POLYGON); + glTexCoord2f(0,0); glVertex2f(x0,y0); + glTexCoord2f(1,0); glVertex2f(x1,y0); + glTexCoord2f(1,1); glVertex2f(x1,y1); + glTexCoord2f(0,1); glVertex2f(x0,y1); + glEnd(); +} + +void stbgl_drawRectTC(float x0, float y0, float x1, float y1, float s0, float t0, float s1, float t1) +{ + glBegin(GL_POLYGON); + glTexCoord2f(s0,t0); glVertex2f(x0,y0); + glTexCoord2f(s1,t0); glVertex2f(x1,y0); + glTexCoord2f(s1,t1); glVertex2f(x1,y1); + glTexCoord2f(s0,t1); glVertex2f(x0,y1); + glEnd(); +} + +void stbgl_drawBox(float x, float y, float z, float sx, float sy, float sz, int cw) +{ + float x0,y0,z0,x1,y1,z1; + sx /=2, sy/=2, sz/=2; + x0 = x-sx; y0 = y-sy; z0 = z-sz; + x1 = x+sx; y1 = y+sy; z1 = z+sz; + + glBegin(GL_QUADS); + if (cw) { + glNormal3f(0,0,-1); + glTexCoord2f(0,0); glVertex3f(x0,y0,z0); + glTexCoord2f(1,0); glVertex3f(x1,y0,z0); + glTexCoord2f(1,1); glVertex3f(x1,y1,z0); + glTexCoord2f(0,1); glVertex3f(x0,y1,z0); + + glNormal3f(0,0,1); + glTexCoord2f(0,0); glVertex3f(x1,y0,z1); + glTexCoord2f(1,0); glVertex3f(x0,y0,z1); + glTexCoord2f(1,1); glVertex3f(x0,y1,z1); + glTexCoord2f(0,1); glVertex3f(x1,y1,z1); + + glNormal3f(-1,0,0); + glTexCoord2f(0,0); glVertex3f(x0,y1,z1); + glTexCoord2f(1,0); glVertex3f(x0,y0,z1); + glTexCoord2f(1,1); glVertex3f(x0,y0,z0); + glTexCoord2f(0,1); glVertex3f(x0,y1,z0); + + glNormal3f(1,0,0); + glTexCoord2f(0,0); glVertex3f(x1,y0,z1); + glTexCoord2f(1,0); glVertex3f(x1,y1,z1); + glTexCoord2f(1,1); glVertex3f(x1,y1,z0); + glTexCoord2f(0,1); glVertex3f(x1,y0,z0); + + glNormal3f(0,-1,0); + glTexCoord2f(0,0); glVertex3f(x0,y0,z1); + glTexCoord2f(1,0); glVertex3f(x1,y0,z1); + glTexCoord2f(1,1); glVertex3f(x1,y0,z0); + glTexCoord2f(0,1); glVertex3f(x0,y0,z0); + + glNormal3f(0,1,0); + glTexCoord2f(0,0); glVertex3f(x1,y1,z1); + glTexCoord2f(1,0); glVertex3f(x0,y1,z1); + glTexCoord2f(1,1); glVertex3f(x0,y1,z0); + glTexCoord2f(0,1); glVertex3f(x1,y1,z0); + } else { + glNormal3f(0,0,-1); + glTexCoord2f(0,0); glVertex3f(x0,y0,z0); + glTexCoord2f(0,1); glVertex3f(x0,y1,z0); + glTexCoord2f(1,1); glVertex3f(x1,y1,z0); + glTexCoord2f(1,0); glVertex3f(x1,y0,z0); + + glNormal3f(0,0,1); + glTexCoord2f(0,0); glVertex3f(x1,y0,z1); + glTexCoord2f(0,1); glVertex3f(x1,y1,z1); + glTexCoord2f(1,1); glVertex3f(x0,y1,z1); + glTexCoord2f(1,0); glVertex3f(x0,y0,z1); + + glNormal3f(-1,0,0); + glTexCoord2f(0,0); glVertex3f(x0,y1,z1); + glTexCoord2f(0,1); glVertex3f(x0,y1,z0); + glTexCoord2f(1,1); glVertex3f(x0,y0,z0); + glTexCoord2f(1,0); glVertex3f(x0,y0,z1); + + glNormal3f(1,0,0); + glTexCoord2f(0,0); glVertex3f(x1,y0,z1); + glTexCoord2f(0,1); glVertex3f(x1,y0,z0); + glTexCoord2f(1,1); glVertex3f(x1,y1,z0); + glTexCoord2f(1,0); glVertex3f(x1,y1,z1); + + glNormal3f(0,-1,0); + glTexCoord2f(0,0); glVertex3f(x0,y0,z1); + glTexCoord2f(0,1); glVertex3f(x0,y0,z0); + glTexCoord2f(1,1); glVertex3f(x1,y0,z0); + glTexCoord2f(1,0); glVertex3f(x1,y0,z1); + + glNormal3f(0,1,0); + glTexCoord2f(0,0); glVertex3f(x1,y1,z1); + glTexCoord2f(0,1); glVertex3f(x1,y1,z0); + glTexCoord2f(1,1); glVertex3f(x0,y1,z0); + glTexCoord2f(1,0); glVertex3f(x0,y1,z1); + } + glEnd(); +} + +void stbgl_SimpleLight(int index, float bright, float x, float y, float z) +{ + float d = (float) (1.0f/sqrt(x*x+y*y+z*z)); + float dir[4] = { x*d,y*d,z*d,0 }, zero[4] = { 0,0,0,0 }; + float c[4] = { bright,bright,bright,0 }; + GLuint light = GL_LIGHT0 + index; + glLightfv(light, GL_POSITION, dir); + glLightfv(light, GL_DIFFUSE, c); + glLightfv(light, GL_AMBIENT, zero); + glLightfv(light, GL_SPECULAR, zero); + glEnable(light); + glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); + glEnable(GL_COLOR_MATERIAL); +} + +void stbgl_GlobalAmbient(float r, float g, float b) +{ + float v[4] = { r,g,b,0 }; + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, v); +} + + +#define stbgl_rad2deg(r) ((r)*180.0f / M_PI) +#define stbgl_deg2rad(r) ((r)/180.0f * M_PI) + +void stbgl_Perspective(float zoom, float max_hfov, float max_vfov, float znear, float zfar) +{ + float unit_width, unit_height, aspect, vfov; + int data[4],w,h; + glGetIntegerv(GL_VIEWPORT, data); + w = data[2]; + h = data[3]; + aspect = (float) w / h; + + if (max_hfov <= 0) max_hfov = 179; + if (max_vfov <= 0) max_vfov = 179; + + // convert max_hfov, max_vfov to worldspace width at depth=1 + unit_width = (float) tan(stbgl_deg2rad(max_hfov/2)) * 2; + unit_height = (float) tan(stbgl_deg2rad(max_vfov/2)) * 2; + // check if hfov = max_hfov is enough to satisfy it + if (unit_width <= aspect * unit_height) { + float height = unit_width / aspect; + vfov = (float) atan(( height/2) / zoom); + } else { + vfov = (float) atan((unit_height/2) / zoom); + } + vfov = (float) stbgl_rad2deg(vfov * 2); + gluPerspective(vfov, aspect, znear, zfar); +} + +void stbgl_PerspectiveViewport(int x, int y, int w, int h, float zoom, float min_hfov, float min_vfov, float znear, float zfar) +{ + if (znear <= 0.0001f) znear = 0.0001f; + glViewport(x,y,w,h); + glScissor(x,y,w,h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + stbgl_Perspective(zoom, min_hfov, min_vfov, znear, zfar); + glMatrixMode(GL_MODELVIEW); +} + +// point the camera along the positive X axis, Z-up +void stbgl_initCamera_zup_facing_x(void) +{ + glRotatef(-90, 1,0,0); + glRotatef( 90, 0,0,1); +} + +// point the camera along the positive Y axis, Z-up +void stbgl_initCamera_zup_facing_y(void) +{ + glRotatef(-90, 1,0,0); +} + +// setup a camera using Euler angles +void stbgl_positionCameraWithEulerAngles(float *loc, float *ang) +{ + glRotatef(-ang[1], 0,1,0); + glRotatef(-ang[0], 1,0,0); + glRotatef(-ang[2], 0,0,1); + glTranslatef(-loc[0], -loc[1], -loc[2]); +} + +static int stbgl_m(char *a, char *b) +{ + // skip first character + do { ++a,++b; } while (*b && *a == *b); + return *b == 0; +} + +#ifdef STBI_VERSION +#ifndef STBI_NO_STDIO +int stbgl_LoadTexture(char *filename, char *props) +{ + // @TODO: handle DDS files directly + int res; + void *data; + int w,h,c; + #ifndef STBI_NO_HDR + if (stbi_is_hdr(filename)) { + data = stbi_loadf(filename, &w, &h, &c, 0); + if (!data) return 0; + res = stbgl_TexImage2D_Extra(0, w,h,data, -c, props, 0); + free(data); + return res; + } + #endif + + data = stbi_load(filename, &w, &h, &c, 0); + if (!data) return 0; + res = stbgl_TexImage2D_Extra(0, w,h,data, c, props, 0); + free(data); + return res; +} +#endif +#endif // STBI_VERSION + +int stbgl_TexImage2D(int texid, int w, int h, void *data, char *props) +{ + return stbgl_TexImage2D_Extra(texid, w, h, data, 0, props,1); +} + +int stbgl_TestTexture(int w) +{ + char scale_table[] = { 10,20,30,30,35,40,5,18,25,13,7,5,3,3,2,2,2,2,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0 }; + return stbgl_TestTextureEx(w, scale_table, 2, 140,130,200, 180,200,170); +} + +unsigned int stbgl_rand(void) +{ + static unsigned int stbgl__rand_seed = 3248980923; // random typing + return stbgl__rand_seed = stbgl__rand_seed * 2147001325 + 715136305; // BCPL generator +} + +// wish this could be smaller, since it's so frivolous +int stbgl_TestTextureEx(int w, char *scale_table, int checks_log2, int r1,int g1,int b1, int r2, int b2, int g2) +{ + int rt[2] = {r1,r2}, gt[2] = {g1,g2}, bt[2] = {b1,b2}; + signed char modded[256]; + int i,j, m = w-1, s,k,scale; + unsigned char *data = (unsigned char *) malloc(w*w*3); + assert((m & w) == 0); + data[0] = 128; + for (s=0; s < 16; ++s) if ((1 << s) == w) break; + assert(w == (1 << s)); + // plasma fractal noise + for (k=s-1; k >= 0; --k) { + int step = 1 << k; + // interpolate from "parents" + for (j=0; j < w; j += step*2) { + for (i=0; i < w; i += step*2) { + int i1 = i+step, j1=j+step; + int i2 = (i+step*2)&m, j2 = (j+step*2)&m; + int p00 = data[(j*w+i )*3], p01 = data[(j2*w+i )*3]; + int p10 = data[(j*w+i2)*3], p11 = data[(j2*w+i2)*3]; + data[(j*w+i1)*3] = (p00+p10)>>1; + data[(j1*w+i)*3] = (p00+p01)>>1; + data[(j1*w+i1)*3]= (p00+p01+p10+p11)>>2; + } + } + scale = scale_table[s-k+1]; + if (!scale) continue; // just interpolate down the remaining data + for (j=0,i=0; i < 256; i += 2, j == scale ? j=0 : ++j) + modded[i] = j, modded[i+1] = -j; // precompute i%scale (plus sign) + for (j=0; j < w; j += step) + for (i=0; i < w; i += step) { + int x = data[(j*w+i)*3] + modded[(stbgl_rand() >> 12) & 255]; + data[(j*w+i)*3] = x < 0 ? 0 : x > 255 ? 255 : x; + } + } + for (j=0; j < w; ++j) + for (i=0; i < w; ++i) { + int check = ((i^j) & (1 << (s-checks_log2))) == 0; + int v = data[(j*w+i)*3] >> 2; + data[(j*w+i)*3+0] = rt[check]-v; + data[(j*w+i)*3+1] = gt[check]-v; + data[(j*w+i)*3+2] = bt[check]-v; + } + return stbgl_TexImage2D(0, w, w, data, "3m!"); // 3 channels, mipmap, free +} + +#ifdef _WIN32 +#ifndef WINGDIAPI +typedef int (__stdcall *stbgl__voidfunc)(void); +__declspec(dllimport) stbgl__voidfunc wglGetProcAddress(char *); +#endif +#define STB__HAS_WGLPROC +static void (__stdcall *stbgl__CompressedTexImage2DARB)(int target, int level, + int internalformat, int width, + int height, int border, + int imageSize, void *data); +static void stbgl__initCompTex(void) +{ + *((void **) &stbgl__CompressedTexImage2DARB) = (void *) wglGetProcAddress("glCompressedTexImage2DARB"); +} +#else +static void (*stbgl__CompressedTexImage2DARB)(int target, int level, + int internalformat, int width, + int height, int border, + int imageSize, void *data); +static void stbgl__initCompTex(void) +{ +} +#endif // _WIN32 + +#define STBGL_COMPRESSED_RGB_S3TC_DXT1 0x83F0 +#define STBGL_COMPRESSED_RGBA_S3TC_DXT1 0x83F1 +#define STBGL_COMPRESSED_RGBA_S3TC_DXT3 0x83F2 +#define STBGL_COMPRESSED_RGBA_S3TC_DXT5 0x83F3 + +#ifdef STB_COMPRESS_DXT_BLOCK +static void stbgl__convert(uint8 *p, uint8 *q, int n, int input_desc, uint8 *end) +{ + int i; + switch (input_desc) { + case GL_RED: + case GL_LUMINANCE: for (i=0; i < n; ++i,p+=4) p[0] = p[1] = p[2] = q[0], p[3]=255, q+=1; break; + case GL_ALPHA: for (i=0; i < n; ++i,p+=4) p[0] = p[1] = p[2] = 0, p[3] = q[0], q+=1; break; + case GL_LUMINANCE_ALPHA: for (i=0; i < n; ++i,p+=4) p[0] = p[1] = p[2] = q[0], p[3]=q[1], q+=2; break; + case GL_RGB: for (i=0; i < n; ++i,p+=4) p[0]=q[0],p[1]=q[1],p[2]=q[2],p[3]=255,q+=3; break; + case GL_RGBA: memcpy(p, q, n*4); break; + case GL_INTENSITY: for (i=0; i < n; ++i,p+=4) p[0] = p[1] = p[2] = p[3] = q[0], q+=1; break; + } + assert(p <= end); +} + +static void stbgl__compress(uint8 *p, uint8 *rgba, int w, int h, int output_desc, uint8 *end) +{ + int i,j,y,y2; + int alpha = (output_desc == STBGL_COMPRESSED_RGBA_S3TC_DXT5); + for (j=0; j < w; j += 4) { + int x=4; + for (i=0; i < h; i += 4) { + uint8 block[16*4]; + if (i+3 >= w) x = w-i; + for (y=0; y < 4; ++y) { + if (j+y >= h) break; + memcpy(block+y*16, rgba + w*4*(j+y) + i*4, x*4); + } + if (x < 4) { + switch (x) { + case 0: assert(0); + case 1: + for (y2=0; y2 < y; ++y2) { + memcpy(block+y2*16+1*4, block+y2*16+0*4, 4); + memcpy(block+y2*16+2*4, block+y2*16+0*4, 8); + } + break; + case 2: + for (y2=0; y2 < y; ++y2) + memcpy(block+y2*16+2*4, block+y2*16+0*4, 8); + break; + case 3: + for (y2=0; y2 < y; ++y2) + memcpy(block+y2*16+3*4, block+y2*16+1*4, 4); + break; + } + } + y2 = 0; + for(; y<4; ++y,++y2) + memcpy(block+y*16, block+y2*16, 4*4); + stb_compress_dxt_block(p, block, alpha, 10); + p += alpha ? 16 : 8; + } + } + assert(p <= end); +} +#endif // STB_COMPRESS_DXT_BLOCK + +// use the reserved temporary-use enumerant range, since no +// OpenGL enumerants should fall in that range +enum +{ + STBGL_UNDEFINED = 0x6000, + STBGL_YCOCG, + STBGL_YCOCGJ, + STBGL_GEN_MIPMAPS, + STBGL_MIPMAPS, + STBGL_NO_DOWNLOAD, +}; + +#define STBGL_CLAMP_TO_EDGE 0x812F +#define STBGL_CLAMP_TO_BORDER 0x812D + +#define STBGL_DEPTH_COMPONENT16 0x81A5 +#define STBGL_DEPTH_COMPONENT24 0x81A6 +#define STBGL_DEPTH_COMPONENT32 0x81A7 + +int stbgl_TexImage2D_Extra(int texid, int w, int h, void *data, int chan, char *props, int preserve_data) +{ + static int has_s3tc = -1; // haven't checked yet + int free_data = 0, is_compressed = 0; + int pad_to_power_of_two = 0, non_power_of_two = 0; + int premultiply_alpha = 0; // @TODO + int float_tex = 0; // @TODO + int input_type = GL_UNSIGNED_BYTE; + int input_desc = STBGL_UNDEFINED; + int output_desc = STBGL_UNDEFINED; + int mipmaps = STBGL_UNDEFINED; + int filter = STBGL_UNDEFINED, mag_filter; + int wrap_s = STBGL_UNDEFINED, wrap_t = STBGL_UNDEFINED; + + // parse out the properties + if (props == NULL) props = ""; + while (*props) { + switch (*props) { + case '1' : input_desc = GL_LUMINANCE; break; + case '2' : input_desc = GL_LUMINANCE_ALPHA; break; + case '3' : input_desc = GL_RGB; break; + case '4' : input_desc = GL_RGBA; break; + case 'l' : if (props[1] == 'a') { input_desc = GL_LUMINANCE_ALPHA; ++props; } + else input_desc = GL_LUMINANCE; + break; + case 'a' : input_desc = GL_ALPHA; break; + case 'r' : if (stbgl_m(props, "rgba")) { input_desc = GL_RGBA; props += 3; break; } + if (stbgl_m(props, "rgb")) { input_desc = GL_RGB; props += 2; break; } + input_desc = GL_RED; + break; + case 'y' : if (stbgl_m(props, "ycocg")) { + if (props[5] == 'j') { props += 5; input_desc = STBGL_YCOCGJ; } + else { props += 4; input_desc = STBGL_YCOCG; } + break; + } + return 0; + case 'L' : if (props[1] == 'A') { output_desc = GL_LUMINANCE_ALPHA; ++props; } + else output_desc = GL_LUMINANCE; + break; + case 'I' : output_desc = GL_INTENSITY; break; + case 'A' : output_desc = GL_ALPHA; break; + case 'R' : if (stbgl_m(props, "RGBA")) { output_desc = GL_RGBA; props += 3; break; } + if (stbgl_m(props, "RGB")) { output_desc = GL_RGB; props += 2; break; } + output_desc = GL_RED; + break; + case 'Y' : if (stbgl_m(props, "YCoCg") || stbgl_m(props, "YCOCG")) { + props += 4; + output_desc = STBGL_YCOCG; + break; + } + return 0; + case 'D' : if (stbgl_m(props, "DXT")) { + switch (props[3]) { + case '1': output_desc = STBGL_COMPRESSED_RGB_S3TC_DXT1; break; + case '3': output_desc = STBGL_COMPRESSED_RGBA_S3TC_DXT3; break; + case '5': output_desc = STBGL_COMPRESSED_RGBA_S3TC_DXT5; break; + default: return 0; + } + props += 3; + } else if (stbgl_m(props, "D16")) { + output_desc = STBGL_DEPTH_COMPONENT16; + input_desc = GL_DEPTH_COMPONENT; + props += 2; + } else if (stbgl_m(props, "D24")) { + output_desc = STBGL_DEPTH_COMPONENT24; + input_desc = GL_DEPTH_COMPONENT; + props += 2; + } else if (stbgl_m(props, "D32")) { + output_desc = STBGL_DEPTH_COMPONENT32; + input_desc = GL_DEPTH_COMPONENT; + props += 2; + } else { + output_desc = GL_DEPTH_COMPONENT; + input_desc = GL_DEPTH_COMPONENT; + } + break; + case 'N' : if (stbgl_m(props, "NONE")) { + props += 3; + input_desc = STBGL_NO_DOWNLOAD; + output_desc = STBGL_NO_DOWNLOAD; + break; + } + if (stbgl_m(props, "NP2")) { + non_power_of_two = 1; + props += 2; + break; + } + return 0; + case 'm' : mipmaps = STBGL_GEN_MIPMAPS; break; + case 'M' : mipmaps = STBGL_MIPMAPS; break; + case 't' : filter = GL_LINEAR_MIPMAP_LINEAR; break; + case 'b' : filter = GL_LINEAR; break; + case 'n' : filter = GL_NEAREST; break; + case 'w' : if (wrap_s == STBGL_UNDEFINED) wrap_s = GL_REPEAT; else wrap_t = GL_REPEAT; break; + case 'C' : if (wrap_s == STBGL_UNDEFINED) wrap_s = STBGL_CLAMP_TO_BORDER; else wrap_t = STBGL_CLAMP_TO_BORDER; break; + case 'c' : if (wrap_s == STBGL_UNDEFINED) wrap_s = STBGL_CLAMP_TO_EDGE; else wrap_t = STBGL_CLAMP_TO_EDGE; break; + case 'f' : input_type = GL_FLOAT; break; + case 'F' : input_type = GL_FLOAT; float_tex = 1; break; + case 'p' : premultiply_alpha = 1; break; + case 'P' : pad_to_power_of_two = 1; break; + case '+' : preserve_data = 0; break; + case '!' : preserve_data = 0; free_data = 1; break; + case ' ' : break; + case '-' : break; + default : if (free_data) free(data); + return 0; + } + ++props; + } + + // override input_desc based on channel count + if (output_desc != STBGL_NO_DOWNLOAD) { + switch (abs(chan)) { + case 1: input_desc = GL_LUMINANCE; break; + case 2: input_desc = GL_LUMINANCE_ALPHA; break; + case 3: input_desc = GL_RGB; break; + case 4: input_desc = GL_RGBA; break; + case 0: break; + default: return 0; + } + } + + // override input_desc based on channel info + if (chan > 0) { input_type = GL_UNSIGNED_BYTE; } + if (chan < 0) { input_type = GL_FLOAT; } + + if (output_desc == GL_ALPHA) { + if (input_desc == GL_LUMINANCE) + input_desc = GL_ALPHA; + if (input_desc == GL_RGB) { + // force a presumably-mono image to alpha + // @TODO handle 'preserve_data' case? + if (data && !preserve_data && input_type == GL_UNSIGNED_BYTE) { + int i; + unsigned char *p = (unsigned char *) data, *q = p; + for (i=0; i < w*h; ++i) { + *q = (p[0] + 2*p[1] + p[2]) >> 2; + p += 3; + q += 1; + } + input_desc = GL_ALPHA; + } + } + } + + // set undefined input/output based on the other + if (input_desc == STBGL_UNDEFINED && output_desc == STBGL_UNDEFINED) { + input_desc = output_desc = GL_RGBA; + } else if (output_desc == STBGL_UNDEFINED) { + switch (input_desc) { + case GL_LUMINANCE: + case GL_ALPHA: + case GL_LUMINANCE_ALPHA: + case GL_RGB: + case GL_RGBA: + output_desc = input_desc; + break; + case GL_RED: + output_desc = GL_INTENSITY; + break; + case STBGL_YCOCG: + case STBGL_YCOCGJ: + output_desc = STBGL_YCOCG; + break; + default: assert(0); return 0; + } + } else if (input_desc == STBGL_UNDEFINED) { + switch (output_desc) { + case GL_LUMINANCE: + case GL_ALPHA: + case GL_LUMINANCE_ALPHA: + case GL_RGB: + case GL_RGBA: + input_desc = output_desc; + break; + case GL_INTENSITY: + input_desc = GL_RED; + break; + case STBGL_YCOCG: + case STBGL_COMPRESSED_RGB_S3TC_DXT1: + case STBGL_COMPRESSED_RGBA_S3TC_DXT3: + case STBGL_COMPRESSED_RGBA_S3TC_DXT5: + input_desc = GL_RGBA; + break; + } + } else { + if (output_desc == STBGL_COMPRESSED_RGB_S3TC_DXT1) { + // if input has alpha, force output alpha + switch (input_desc) { + case GL_ALPHA: + case GL_LUMINANCE_ALPHA: + case GL_RGBA: + output_desc = STBGL_COMPRESSED_RGBA_S3TC_DXT5; + break; + } + } + } + + switch(input_desc) { + case GL_LUMINANCE: + case GL_RED: + case GL_ALPHA: + chan = 1; + break; + case GL_LUMINANCE_ALPHA: + chan = 2; + break; + case GL_RGB: + chan = 3; + break; + case GL_RGBA: + chan = 4; + break; + } + + if (pad_to_power_of_two && ((w & (w-1)) || (h & (h-1)))) { + if (output_desc != STBGL_NO_DOWNLOAD && input_type == GL_UNSIGNED_BYTE && chan > 0) { + unsigned char *new_data; + int w2 = w, h2 = h, j; + while (w & (w-1)) + w = (w | (w>>1))+1; + while (h & (h-1)) + h = (h | (h>>1))+1; + new_data = malloc(w * h * chan); + for (j=0; j < h2; ++j) { + memcpy(new_data + j * w * chan, (char *) data+j*w2*chan, w2*chan); + memset(new_data + (j * w+w2) * chan, 0, (w-w2)*chan); + } + for (; j < h; ++j) + memset(new_data + j*w*chan, 0, w*chan); + if (free_data) + free(data); + data = new_data; + free_data = 1; + } + } + + switch (output_desc) { + case STBGL_COMPRESSED_RGB_S3TC_DXT1: + case STBGL_COMPRESSED_RGBA_S3TC_DXT1: + case STBGL_COMPRESSED_RGBA_S3TC_DXT3: + case STBGL_COMPRESSED_RGBA_S3TC_DXT5: + is_compressed = 1; + if (has_s3tc == -1) { + has_s3tc = stbgl_hasExtension("GL_EXT_texture_compression_s3tc"); + if (has_s3tc) stbgl__initCompTex(); + } + if (!has_s3tc) { + is_compressed = 0; + if (output_desc == STBGL_COMPRESSED_RGB_S3TC_DXT1) + output_desc = GL_RGB; + else + output_desc = GL_RGBA; + } + } + + if (output_desc == STBGL_YCOCG) { + assert(0); + output_desc = GL_RGB; // @TODO! + if (free_data) free(data); + return 0; + } + + mag_filter = 0; + if (mipmaps != STBGL_UNDEFINED) { + switch (filter) { + case STBGL_UNDEFINED: filter = GL_LINEAR_MIPMAP_LINEAR; break; + case GL_NEAREST : mag_filter = GL_NEAREST; filter = GL_LINEAR_MIPMAP_LINEAR; break; + case GL_LINEAR : filter = GL_LINEAR_MIPMAP_NEAREST; break; + } + } else { + if (filter == STBGL_UNDEFINED) + filter = GL_LINEAR; + } + + // update filtering + if (!mag_filter) { + if (filter == GL_NEAREST) + mag_filter = GL_NEAREST; + else + mag_filter = GL_LINEAR; + } + + // update wrap/clamp + if (wrap_s == STBGL_UNDEFINED) wrap_s = GL_REPEAT; + if (wrap_t == STBGL_UNDEFINED) wrap_t = wrap_s; + + // if no texture id, generate one + if (texid == 0) { + GLuint tex; + glGenTextures(1, &tex); + if (tex == 0) { if (free_data) free(data); return 0; } + texid = tex; + } + + if (data == NULL && mipmaps == STBGL_GEN_MIPMAPS) + mipmaps = STBGL_MIPMAPS; + + if (output_desc == STBGL_NO_DOWNLOAD) + mipmaps = STBGL_NO_DOWNLOAD; + + glBindTexture(GL_TEXTURE_2D, texid); + +#ifdef STB_COMPRESS_DXT_BLOCK + if (!is_compressed || !stbgl__CompressedTexImage2DARB || output_desc == STBGL_COMPRESSED_RGBA_S3TC_DXT3 || data == NULL) +#endif + { + switch (mipmaps) { + case STBGL_NO_DOWNLOAD: + break; + + case STBGL_UNDEFINED: + // check if actually power-of-two + if (non_power_of_two || ((w & (w-1)) == 0 && (h & (h-1)) == 0)) + glTexImage2D(GL_TEXTURE_2D, 0, output_desc, w, h, 0, input_desc, input_type, data); + else + gluBuild2DMipmaps(GL_TEXTURE_2D, output_desc, w, h, input_desc, input_type, data); + // not power of two, so use glu to resize (generates mipmaps needlessly) + break; + + case STBGL_MIPMAPS: { + int level = 0; + int size = input_type == GL_FLOAT ? sizeof(float) : 1; + if (data == NULL) size = 0; // reuse same block of memory for all mipmaps + assert((w & (w-1)) == 0 && (h & (h-1)) == 0); // verify power-of-two + while (w > 1 && h > 1) { + glTexImage2D(GL_TEXTURE_2D, level, output_desc, w, h, 0, input_desc, input_type, data); + data = (void *) ((char *) data + w * h * size * chan); + if (w > 1) w >>= 1; + if (h > 1) h >>= 1; + ++level; + } + break; + } + case STBGL_GEN_MIPMAPS: + gluBuild2DMipmaps(GL_TEXTURE_2D, output_desc, w, h, input_desc, input_type, data); + break; + + default: + assert(0); + if (free_data) free(data); + return 0; + } +#ifdef STB_COMPRESS_DXT_BLOCK + } else { + uint8 *out, *rgba=0, *end_out, *end_rgba; + int level = 0, alpha = (output_desc != STBGL_COMPRESSED_RGB_S3TC_DXT1); + int size = input_type == GL_FLOAT ? sizeof(float) : 1; + int osize = alpha ? 16 : 8; + if (!free_data && mipmaps == STBGL_GEN_MIPMAPS) { + uint8 *temp = malloc(w*h*chan); + if (!temp) { if (free_data) free(data); return 0; } + memcpy(temp, data, w*h*chan); + if (free_data) free(data); + free_data = 1; + data = temp; + } + if (chan != 4 || size != 1) { + rgba = malloc(w*h*4); + if (!rgba) return 0; + end_rgba = rgba+w*h*4; + } + out = malloc((w+3)*(h+3)/16*osize); // enough storage for the s3tc data + if (!out) return 0; + end_out = out + ((w+3)*(h+3))/16*osize; + + for(;;) { + if (chan != 4) + stbgl__convert(rgba, data, w*h, input_desc, end_rgba); + stbgl__compress(out, rgba ? rgba : data, w, h, output_desc, end_out); + stbgl__CompressedTexImage2DARB(GL_TEXTURE_2D, level, output_desc, w, h, 0, ((w+3)&~3)*((h+3)&~3)/16*osize, out); + //glTexImage2D(GL_TEXTURE_2D, level, alpha?GL_RGBA:GL_RGB, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba ? rgba : data); + + if (mipmaps == STBGL_UNDEFINED) break; + if (w <= 1 && h <= 1) break; + if (mipmaps == STBGL_MIPMAPS) data = (void *) ((char *) data + w * h * size * chan); + if (mipmaps == STBGL_GEN_MIPMAPS) { + int w2 = w>>1, h2=h>>1, i,j,k, s=w*chan; + uint8 *p = data, *q=data; + if (w == 1) { + for (j=0; j < h2; ++j) { + for (k=0; k < chan; ++k) + *p++ = (q[k] + q[s+k] + 1) >> 1; + q += s*2; + } + } else if (h == 1) { + for (i=0; i < w2; ++i) { + for (k=0; k < chan; ++k) + *p++ = (q[k] + q[k+chan] + 1) >> 1; + q += chan*2; + } + } else { + for (j=0; j < h2; ++j) { + for (i=0; i < w2; ++i) { + for (k=0; k < chan; ++k) + *p++ = (q[k] + q[k+chan] + q[s+k] + q[s+k+chan] + 2) >> 2; + q += chan*2; + } + q += s; + } + } + } + if (w > 1) w >>= 1; + if (h > 1) h >>= 1; + ++level; + } + if (out) free(out); + if (rgba) free(rgba); +#endif // STB_COMPRESS_DXT_BLOCK + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + + if (free_data) free(data); + return texid; +} + +#endif // STB_DEFINE +#undef STB_EXTERN + +#endif //INCLUDE_STB_GL_H + +// Extension handling... must be outside the INCLUDE_ brackets + +#if defined(STB_GLEXT_DEFINE) || defined(STB_GLEXT_DECLARE) + +#ifndef STB_GLEXT_SKIP_DURING_RECURSION + +#ifndef GL_GLEXT_VERSION + + // First check if glext.h is concatenated on the end of this file + // (if it's concatenated on the beginning, we'll have GL_GLEXT_VERSION) + + #define STB_GLEXT_SKIP_DURING_RECURSION + #include __FILE__ + #undef STB_GLEXT_SKIP_DURING_RECURSION + + // now check if it's still undefined; if so, try going for it by name; + // if this errors, that's fine, since we can't compile without it + + #ifndef GL_GLEXT_VERSION + #include "glext.h" + #endif +#endif + +#define GLARB(a,b) GLE(a##ARB,b##ARB) +#define GLEXT(a,b) GLE(a##EXT,b##EXT) +#define GLNV(a,b) GLE(a##NV ,b##NV) +#define GLATI(a,b) GLE(a##ATI,b##ATI) +#define GLCORE(a,b) GLE(a,b) + +#ifdef STB_GLEXT_DEFINE_DECLARE +#define STB_GLEXT_DEFINE STB_GLEXT_DECLARE +#endif + +#if defined(STB_GLEXT_DECLARE) && defined(STB_GLEXT_DEFINE) +#undef STB_GLEXT_DECLARE +#endif + +#if defined(STB_GLEXT_DECLARE) && !defined(STB_GLEXT_DEFINE) + #define GLE(a,b) extern PFNGL##b##PROC gl##a; + + #ifdef __cplusplus + extern "C" { + #endif + + extern void stbgl_initExtensions(void); + + #include STB_GLEXT_DECLARE + + #ifdef __cplusplus + }; + #endif + +#else + + #ifndef STB_GLEXT_DEFINE + #error "Header file is screwed up somehow" + #endif + + #ifdef _WIN32 + #ifndef WINGDIAPI + #ifndef STB__HAS_WGLPROC + typedef int (__stdcall *stbgl__voidfunc)(void); + __declspec(dllimport) stbgl__voidfunc wglGetProcAddress(char *); + #endif + #endif + #define STBGL__GET_FUNC(x) wglGetProcAddress(x) + #endif + + #ifdef GLE + #undef GLE + #endif + + #define GLE(a,b) PFNGL##b##PROC gl##a; + #include STB_GLEXT_DEFINE + + #undef GLE + #define GLE(a,b) gl##a = (PFNGL##b##PROC) STBGL__GET_FUNC("gl" #a ); + + void stbgl_initExtensions(void) + { + #include STB_GLEXT_DEFINE + } + + #undef GLE + +#endif // STB_GLEXT_DECLARE + +#endif // STB_GLEXT_SKIP + +#endif // STB_GLEXT_DEFINE || STB_GLEXT_DECLARE diff --git a/source/engine/thirdparty/stb/tests/caveview/stb_glprog.h b/source/engine/thirdparty/stb/tests/caveview/stb_glprog.h new file mode 100644 index 0000000..8883a3e --- /dev/null +++ b/source/engine/thirdparty/stb/tests/caveview/stb_glprog.h @@ -0,0 +1,504 @@ +// stb_glprog v0.02 public domain functions to reduce GLSL boilerplate +// http://nothings.org/stb/stb_glprog.h especially with GL1 + ARB extensions +// +// Following defines *before* including have following effects: +// +// STB_GLPROG_IMPLEMENTATION +// creates the implementation +// +// STB_GLPROG_STATIC +// forces the implementation to be static (private to file that creates it) +// +// STB_GLPROG_ARB +// uses ARB extension names for GLSL functions and enumerants instead of core names +// +// STB_GLPROG_ARB_DEFINE_EXTENSIONS +// instantiates function pointers needed, static to implementing file +// to avoid collisions (but will collide if implementing file also +// defines any; best to isolate this to its own file in this case). +// This will try to automatically #include glext.h, but if it's not +// in the default include directories you'll need to include it +// yourself and define the next macro. +// +// STB_GLPROG_SUPPRESS_GLEXT_INCLUDE +// disables the automatic #include of glext.h which is normally +// forced by STB_GLPROG_ARB_DEFINE_EXTENSIONS +// +// So, e.g., sample usage on an old Windows compiler: +// +// #define STB_GLPROG_IMPLEMENTATION +// #define STB_GLPROG_ARB_DEFINE_EXTENSIONS +// #include +// #include "gl/gl.h" +// #include "stb_glprog.h" +// +// Note though that the header-file version of this (when you don't define +// STB_GLPROG_IMPLEMENTATION) still uses GLint and such, so you basically +// can only include it in places where you're already including GL, especially +// on Windows where including "gl.h" requires (some of) "windows.h". +// +// See following comment blocks for function documentation. +// +// Version history: +// 2013-12-08 v0.02 slightly simplified API and reduced GL resource usage (@rygorous) +// 2013-12-08 v0.01 initial release + + +// header file section starts here +#if !defined(INCLUDE_STB_GLPROG_H) +#define INCLUDE_STB_GLPROG_H + +#ifndef STB_GLPROG_STATIC +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////// + +///////////// SHADER CREATION + + +/// EASY API + +extern GLuint stbgl_create_program(char const **vertex_source, char const **frag_source, char const **binds, char *error, int error_buflen); +// This function returns a compiled program or 0 if there's an error. +// To free the created program, call stbgl_delete_program. +// +// stbgl_create_program( +// char **vertex_source, // NULL or one or more strings with the vertex shader source, with a final NULL +// char **frag_source, // NULL or one or more strings with the fragment shader source, with a final NULL +// char **binds, // NULL or zero or more strings with attribute bind names, with a final NULL +// char *error, // output location where compile error message is placed +// int error_buflen) // length of error output buffer +// +// Returns a GLuint with the GL program object handle. +// +// If an individual bind string is "", no name is bound to that slot (this +// allows you to create binds that aren't continuous integers starting at 0). +// +// If the vertex shader is NULL, then fixed-function vertex pipeline +// is used, if that's legal in your version of GL. +// +// If the fragment shader is NULL, then fixed-function fragment pipeline +// is used, if that's legal in your version of GL. + +extern void stgbl_delete_program(GLuint program); +// deletes a program created by stbgl_create_program or stbgl_link_program + + +/// FLEXIBLE API + +extern GLuint stbgl_compile_shader(GLenum type, char const **sources, int num_sources, char *error, int error_buflen); +// compiles a shader. returns the shader on success or 0 on failure. +// +// type either: GL_VERTEX_SHADER or GL_FRAGMENT_SHADER +// or GL_VERTEX_SHADER_ARB or GL_FRAGMENT_SHADER_ARB +// or STBGL_VERTEX_SHADER or STBGL_FRAGMENT_SHADER +// sources array of strings containing the shader source +// num_sources number of string in sources, or -1 meaning sources is NULL-terminated +// error string to output compiler error to +// error_buflen length of error buffer in chars + +extern GLuint stbgl_link_program(GLuint vertex_shader, GLuint fragment_shader, char const **binds, int num_binds, char *error, int error_buflen); +// links a shader. returns the linked program on success or 0 on failure. +// +// vertex_shader a compiled vertex shader from stbgl_compile_shader, or 0 for fixed-function (if legal) +// fragment_shader a compiled fragment shader from stbgl_compile_shader, or 0 for fixed-function (if legal) +// + +extern void stbgl_delete_shader(GLuint shader); +// deletes a shader created by stbgl_compile_shader + + +///////////// RENDERING WITH SHADERS + +extern GLint stbgl_find_uniform(GLuint prog, char *uniform); + +extern void stbgl_find_uniforms(GLuint prog, GLint *locations, char const **uniforms, int num_uniforms); +// Given the locations array that is num_uniforms long, fills out +// the locations of each of those uniforms for the specified program. +// If num_uniforms is -1, then uniforms[] must be NULL-terminated + +// the following functions just wrap the difference in naming between GL2+ and ARB, +// so you don't need them unless you're using both ARB and GL2+ in the same codebase, +// or you're relying on this lib to provide the extensions +extern void stbglUseProgram(GLuint program); +extern void stbglVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer); +extern void stbglEnableVertexAttribArray(GLuint index); +extern void stbglDisableVertexAttribArray(GLuint index); +extern void stbglUniform1fv(GLint loc, GLsizei count, const GLfloat *v); +extern void stbglUniform2fv(GLint loc, GLsizei count, const GLfloat *v); +extern void stbglUniform3fv(GLint loc, GLsizei count, const GLfloat *v); +extern void stbglUniform4fv(GLint loc, GLsizei count, const GLfloat *v); +extern void stbglUniform1iv(GLint loc, GLsizei count, const GLint *v); +extern void stbglUniform2iv(GLint loc, GLsizei count, const GLint *v); +extern void stbglUniform3iv(GLint loc, GLsizei count, const GLint *v); +extern void stbglUniform4iv(GLint loc, GLsizei count, const GLint *v); +extern void stbglUniform1f(GLint loc, float v0); +extern void stbglUniform2f(GLint loc, float v0, float v1); +extern void stbglUniform3f(GLint loc, float v0, float v1, float v2); +extern void stbglUniform4f(GLint loc, float v0, float v1, float v2, float v3); +extern void stbglUniform1i(GLint loc, GLint v0); +extern void stbglUniform2i(GLint loc, GLint v0, GLint v1); +extern void stbglUniform3i(GLint loc, GLint v0, GLint v1, GLint v2); +extern void stbglUniform4i(GLint loc, GLint v0, GLint v1, GLint v2, GLint v3); + + +////////////// END OF FUNCTIONS + +////////////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif +#endif // STB_GLPROG_STATIC + +#ifdef STB_GLPROG_ARB +#define STBGL_VERTEX_SHADER GL_VERTEX_SHADER_ARB +#define STBGL_FRAGMENT_SHADER GL_FRAGMENT_SHADER_ARB +#else +#define STBGL_VERTEX_SHADER GL_VERTEX_SHADER +#define STBGL_FRAGMENT_SHADER GL_FRAGMENT_SHADER +#endif + +#endif // INCLUDE_STB_GLPROG_H + + +///////// header file section ends here + + +#ifdef STB_GLPROG_IMPLEMENTATION +#include // strncpy + +#ifdef STB_GLPROG_STATIC +#define STB_GLPROG_DECLARE static +#else +#define STB_GLPROG_DECLARE extern +#endif + +// check if user wants this file to define the GL extensions itself +#ifdef STB_GLPROG_ARB_DEFINE_EXTENSIONS +#define STB_GLPROG_ARB // make sure later code uses the extensions + +#ifndef STB_GLPROG_SUPPRESS_GLEXT_INCLUDE +#include "glext.h" +#endif + +#define STB_GLPROG_EXTENSIONS \ + STB_GLPROG_FUNC(ATTACHOBJECT , AttachObject ) \ + STB_GLPROG_FUNC(BINDATTRIBLOCATION , BindAttribLocation ) \ + STB_GLPROG_FUNC(COMPILESHADER , CompileShader ) \ + STB_GLPROG_FUNC(CREATEPROGRAMOBJECT , CreateProgramObject ) \ + STB_GLPROG_FUNC(CREATESHADEROBJECT , CreateShaderObject ) \ + STB_GLPROG_FUNC(DELETEOBJECT , DeleteObject ) \ + STB_GLPROG_FUNC(DETACHOBJECT , DetachObject ) \ + STB_GLPROG_FUNC(DISABLEVERTEXATTRIBARRAY, DisableVertexAttribArray) \ + STB_GLPROG_FUNC(ENABLEVERTEXATTRIBARRAY, EnableVertexAttribArray ) \ + STB_GLPROG_FUNC(GETATTACHEDOBJECTS , GetAttachedObjects ) \ + STB_GLPROG_FUNC(GETOBJECTPARAMETERIV, GetObjectParameteriv) \ + STB_GLPROG_FUNC(GETINFOLOG , GetInfoLog ) \ + STB_GLPROG_FUNC(GETUNIFORMLOCATION , GetUniformLocation ) \ + STB_GLPROG_FUNC(LINKPROGRAM , LinkProgram ) \ + STB_GLPROG_FUNC(SHADERSOURCE , ShaderSource ) \ + STB_GLPROG_FUNC(UNIFORM1F , Uniform1f ) \ + STB_GLPROG_FUNC(UNIFORM2F , Uniform2f ) \ + STB_GLPROG_FUNC(UNIFORM3F , Uniform3f ) \ + STB_GLPROG_FUNC(UNIFORM4F , Uniform4f ) \ + STB_GLPROG_FUNC(UNIFORM1I , Uniform1i ) \ + STB_GLPROG_FUNC(UNIFORM2I , Uniform2i ) \ + STB_GLPROG_FUNC(UNIFORM3I , Uniform3i ) \ + STB_GLPROG_FUNC(UNIFORM4I , Uniform4i ) \ + STB_GLPROG_FUNC(UNIFORM1FV , Uniform1fv ) \ + STB_GLPROG_FUNC(UNIFORM2FV , Uniform2fv ) \ + STB_GLPROG_FUNC(UNIFORM3FV , Uniform3fv ) \ + STB_GLPROG_FUNC(UNIFORM4FV , Uniform4fv ) \ + STB_GLPROG_FUNC(UNIFORM1IV , Uniform1iv ) \ + STB_GLPROG_FUNC(UNIFORM2IV , Uniform2iv ) \ + STB_GLPROG_FUNC(UNIFORM3IV , Uniform3iv ) \ + STB_GLPROG_FUNC(UNIFORM4IV , Uniform4iv ) \ + STB_GLPROG_FUNC(USEPROGRAMOBJECT , UseProgramObject ) \ + STB_GLPROG_FUNC(VERTEXATTRIBPOINTER , VertexAttribPointer ) + +// define the static function pointers + +#define STB_GLPROG_FUNC(x,y) static PFNGL##x##ARBPROC gl##y##ARB; +STB_GLPROG_EXTENSIONS +#undef STB_GLPROG_FUNC + +// define the GetProcAddress + +#ifdef _WIN32 +#ifndef WINGDIAPI +#ifndef STB__HAS_WGLPROC +typedef int (__stdcall *stbgl__voidfunc)(void); +static __declspec(dllimport) stbgl__voidfunc wglGetProcAddress(char *); +#endif +#endif +#define STBGL__GET_FUNC(x) wglGetProcAddress(x) +#else +#error "need to define how this platform gets extensions" +#endif + +// create a function that fills out the function pointers + +static void stb_glprog_init(void) +{ + static int initialized = 0; // not thread safe! + if (initialized) return; + #define STB_GLPROG_FUNC(x,y) gl##y##ARB = (PFNGL##x##ARBPROC) STBGL__GET_FUNC("gl" #y "ARB"); + STB_GLPROG_EXTENSIONS + #undef STB_GLPROG_FUNC +} +#undef STB_GLPROG_EXTENSIONS + +#else +static void stb_glprog_init(void) +{ +} +#endif + + +// define generic names for many of the gl functions or extensions for later use; +// note that in some cases there are two functions in core and one function in ARB +#ifdef STB_GLPROG_ARB +#define stbglCreateShader glCreateShaderObjectARB +#define stbglDeleteShader glDeleteObjectARB +#define stbglAttachShader glAttachObjectARB +#define stbglDetachShader glDetachObjectARB +#define stbglShaderSource glShaderSourceARB +#define stbglCompileShader glCompileShaderARB +#define stbglGetShaderStatus(a,b) glGetObjectParameterivARB(a, GL_OBJECT_COMPILE_STATUS_ARB, b) +#define stbglGetShaderInfoLog glGetInfoLogARB +#define stbglCreateProgram glCreateProgramObjectARB +#define stbglDeleteProgram glDeleteObjectARB +#define stbglLinkProgram glLinkProgramARB +#define stbglGetProgramStatus(a,b) glGetObjectParameterivARB(a, GL_OBJECT_LINK_STATUS_ARB, b) +#define stbglGetProgramInfoLog glGetInfoLogARB +#define stbglGetAttachedShaders glGetAttachedObjectsARB +#define stbglBindAttribLocation glBindAttribLocationARB +#define stbglGetUniformLocation glGetUniformLocationARB +#define stbgl_UseProgram glUseProgramObjectARB +#else +#define stbglCreateShader glCreateShader +#define stbglDeleteShader glDeleteShader +#define stbglAttachShader glAttachShader +#define stbglDetachShader glDetachShader +#define stbglShaderSource glShaderSource +#define stbglCompileShader glCompileShader +#define stbglGetShaderStatus(a,b) glGetShaderiv(a, GL_COMPILE_STATUS, b) +#define stbglGetShaderInfoLog glGetShaderInfoLog +#define stbglCreateProgram glCreateProgram +#define stbglDeleteProgram glDeleteProgram +#define stbglLinkProgram glLinkProgram +#define stbglGetProgramStatus(a,b) glGetProgramiv(a, GL_LINK_STATUS, b) +#define stbglGetProgramInfoLog glGetProgramInfoLog +#define stbglGetAttachedShaders glGetAttachedShaders +#define stbglBindAttribLocation glBindAttribLocation +#define stbglGetUniformLocation glGetUniformLocation +#define stbgl_UseProgram glUseProgram +#endif + + +// perform a safe strcat of 3 strings, given that we can't rely on portable snprintf +// if you need to break on error, this is the best place to place a breakpoint +static void stb_glprog_error(char *error, int error_buflen, char *str1, char *str2, char *str3) +{ + int n = strlen(str1); + strncpy(error, str1, error_buflen); + if (n < error_buflen && str2) { + strncpy(error+n, str2, error_buflen - n); + n += strlen(str2); + if (n < error_buflen && str3) { + strncpy(error+n, str3, error_buflen - n); + } + } + error[error_buflen-1] = 0; +} + +STB_GLPROG_DECLARE GLuint stbgl_compile_shader(GLenum type, char const **sources, int num_sources, char *error, int error_buflen) +{ + char *typename = (type == STBGL_VERTEX_SHADER ? "vertex" : "fragment"); + int len; + GLint result; + GLuint shader; + + // initialize the extensions if we haven't already + stb_glprog_init(); + + // allocate + + shader = stbglCreateShader(type); + if (!shader) { + stb_glprog_error(error, error_buflen, "Couldn't allocate shader object in stbgl_compile_shader for ", typename, NULL); + return 0; + } + + // compile + + // if num_sources is negative, assume source is NULL-terminated and count the non-NULL ones + if (num_sources < 0) + for (num_sources = 0; sources[num_sources] != NULL; ++num_sources) + ; + stbglShaderSource(shader, num_sources, sources, NULL); + stbglCompileShader(shader); + stbglGetShaderStatus(shader, &result); + if (result) + return shader; + + // errors + + stb_glprog_error(error, error_buflen, "Compile error for ", typename, " shader: "); + len = strlen(error); + if (len < error_buflen) + stbglGetShaderInfoLog(shader, error_buflen-len, NULL, error+len); + + stbglDeleteShader(shader); + return 0; +} + +STB_GLPROG_DECLARE GLuint stbgl_link_program(GLuint vertex_shader, GLuint fragment_shader, char const **binds, int num_binds, char *error, int error_buflen) +{ + int len; + GLint result; + + // allocate + + GLuint prog = stbglCreateProgram(); + if (!prog) { + stb_glprog_error(error, error_buflen, "Couldn't allocate program object in stbgl_link_program", NULL, NULL); + return 0; + } + + // attach + + if (vertex_shader) + stbglAttachShader(prog, vertex_shader); + if (fragment_shader) + stbglAttachShader(prog, fragment_shader); + + // attribute binds + + if (binds) { + int i; + // if num_binds is negative, then it is NULL terminated + if (num_binds < 0) + for (num_binds=0; binds[num_binds]; ++num_binds) + ; + for (i=0; i < num_binds; ++i) + if (binds[i] && binds[i][0]) // empty binds can be NULL or "" + stbglBindAttribLocation(prog, i, binds[i]); + } + + // link + + stbglLinkProgram(prog); + + // detach + + if (vertex_shader) + stbglDetachShader(prog, vertex_shader); + if (fragment_shader) + stbglDetachShader(prog, fragment_shader); + + // errors + + stbglGetProgramStatus(prog, &result); + if (result) + return prog; + + stb_glprog_error(error, error_buflen, "Link error: ", NULL, NULL); + len = strlen(error); + if (len < error_buflen) + stbglGetProgramInfoLog(prog, error_buflen-len, NULL, error+len); + + stbglDeleteProgram(prog); + return 0; +} + +STB_GLPROG_DECLARE GLuint stbgl_create_program(char const **vertex_source, char const **frag_source, char const **binds, char *error, int error_buflen) +{ + GLuint vertex, fragment, prog=0; + vertex = stbgl_compile_shader(STBGL_VERTEX_SHADER, vertex_source, -1, error, error_buflen); + if (vertex) { + fragment = stbgl_compile_shader(STBGL_FRAGMENT_SHADER, frag_source, -1, error, error_buflen); + if (fragment) + prog = stbgl_link_program(vertex, fragment, binds, -1, error, error_buflen); + if (fragment) + stbglDeleteShader(fragment); + stbglDeleteShader(vertex); + } + return prog; +} + +STB_GLPROG_DECLARE void stbgl_delete_shader(GLuint shader) +{ + stbglDeleteShader(shader); +} + +STB_GLPROG_DECLARE void stgbl_delete_program(GLuint program) +{ + stbglDeleteProgram(program); +} + +GLint stbgl_find_uniform(GLuint prog, char *uniform) +{ + return stbglGetUniformLocation(prog, uniform); +} + +STB_GLPROG_DECLARE void stbgl_find_uniforms(GLuint prog, GLint *locations, char const **uniforms, int num_uniforms) +{ + int i; + if (num_uniforms < 0) + num_uniforms = 999999; + for (i=0; i < num_uniforms && uniforms[i]; ++i) + locations[i] = stbglGetUniformLocation(prog, uniforms[i]); +} + +STB_GLPROG_DECLARE void stbglUseProgram(GLuint program) +{ + stbgl_UseProgram(program); +} + +#ifdef STB_GLPROG_ARB +#define STBGL_ARBIFY(name) name##ARB +#else +#define STBGL_ARBIFY(name) name +#endif + +STB_GLPROG_DECLARE void stbglVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer) +{ + STBGL_ARBIFY(glVertexAttribPointer)(index, size, type, normalized, stride, pointer); +} + +STB_GLPROG_DECLARE void stbglEnableVertexAttribArray (GLuint index) { STBGL_ARBIFY(glEnableVertexAttribArray )(index); } +STB_GLPROG_DECLARE void stbglDisableVertexAttribArray(GLuint index) { STBGL_ARBIFY(glDisableVertexAttribArray)(index); } + +STB_GLPROG_DECLARE void stbglUniform1fv(GLint loc, GLsizei count, const GLfloat *v) { STBGL_ARBIFY(glUniform1fv)(loc,count,v); } +STB_GLPROG_DECLARE void stbglUniform2fv(GLint loc, GLsizei count, const GLfloat *v) { STBGL_ARBIFY(glUniform2fv)(loc,count,v); } +STB_GLPROG_DECLARE void stbglUniform3fv(GLint loc, GLsizei count, const GLfloat *v) { STBGL_ARBIFY(glUniform3fv)(loc,count,v); } +STB_GLPROG_DECLARE void stbglUniform4fv(GLint loc, GLsizei count, const GLfloat *v) { STBGL_ARBIFY(glUniform4fv)(loc,count,v); } + +STB_GLPROG_DECLARE void stbglUniform1iv(GLint loc, GLsizei count, const GLint *v) { STBGL_ARBIFY(glUniform1iv)(loc,count,v); } +STB_GLPROG_DECLARE void stbglUniform2iv(GLint loc, GLsizei count, const GLint *v) { STBGL_ARBIFY(glUniform2iv)(loc,count,v); } +STB_GLPROG_DECLARE void stbglUniform3iv(GLint loc, GLsizei count, const GLint *v) { STBGL_ARBIFY(glUniform3iv)(loc,count,v); } +STB_GLPROG_DECLARE void stbglUniform4iv(GLint loc, GLsizei count, const GLint *v) { STBGL_ARBIFY(glUniform4iv)(loc,count,v); } + +STB_GLPROG_DECLARE void stbglUniform1f(GLint loc, float v0) + { STBGL_ARBIFY(glUniform1f)(loc,v0); } +STB_GLPROG_DECLARE void stbglUniform2f(GLint loc, float v0, float v1) + { STBGL_ARBIFY(glUniform2f)(loc,v0,v1); } +STB_GLPROG_DECLARE void stbglUniform3f(GLint loc, float v0, float v1, float v2) + { STBGL_ARBIFY(glUniform3f)(loc,v0,v1,v2); } +STB_GLPROG_DECLARE void stbglUniform4f(GLint loc, float v0, float v1, float v2, float v3) + { STBGL_ARBIFY(glUniform4f)(loc,v0,v1,v2,v3); } + +STB_GLPROG_DECLARE void stbglUniform1i(GLint loc, GLint v0) + { STBGL_ARBIFY(glUniform1i)(loc,v0); } +STB_GLPROG_DECLARE void stbglUniform2i(GLint loc, GLint v0, GLint v1) + { STBGL_ARBIFY(glUniform2i)(loc,v0,v1); } +STB_GLPROG_DECLARE void stbglUniform3i(GLint loc, GLint v0, GLint v1, GLint v2) + { STBGL_ARBIFY(glUniform3i)(loc,v0,v1,v2); } +STB_GLPROG_DECLARE void stbglUniform4i(GLint loc, GLint v0, GLint v1, GLint v2, GLint v3) + { STBGL_ARBIFY(glUniform4i)(loc,v0,v1,v2,v3); } + +#endif diff --git a/source/engine/thirdparty/stb/tests/caveview/win32/SDL_windows_main.c b/source/engine/thirdparty/stb/tests/caveview/win32/SDL_windows_main.c new file mode 100644 index 0000000..32e316b --- /dev/null +++ b/source/engine/thirdparty/stb/tests/caveview/win32/SDL_windows_main.c @@ -0,0 +1,224 @@ +/* + SDL_windows_main.c, placed in the public domain by Sam Lantinga 4/13/98 + + The WinMain function -- calls your program's main() function +*/ +#include "SDL_config.h" + +#ifdef __WIN32__ + +//#include "../../core/windows/SDL_windows.h" + +/* Include this so we define UNICODE properly */ +#if defined(__WIN32__) +#define WIN32_LEAN_AND_MEAN +#define STRICT +#ifndef UNICODE +#define UNICODE 1 +#endif +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x501 /* Need 0x410 for AlphaBlend() and 0x500 for EnumDisplayDevices(), 0x501 for raw input */ +#endif + +#include + +/* Routines to convert from UTF8 to native Windows text */ +#if UNICODE +#define WIN_StringToUTF8(S) SDL_iconv_string("UTF-8", "UTF-16LE", (char *)(S), (SDL_wcslen(S)+1)*sizeof(WCHAR)) +#define WIN_UTF8ToString(S) (WCHAR *)SDL_iconv_string("UTF-16LE", "UTF-8", (char *)(S), SDL_strlen(S)+1) +#else +/* !!! FIXME: UTF8ToString() can just be a SDL_strdup() here. */ +#define WIN_StringToUTF8(S) SDL_iconv_string("UTF-8", "ASCII", (char *)(S), (SDL_strlen(S)+1)) +#define WIN_UTF8ToString(S) SDL_iconv_string("ASCII", "UTF-8", (char *)(S), SDL_strlen(S)+1) +#endif + +/* Sets an error message based on a given HRESULT */ +extern int WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr); + +/* Sets an error message based on GetLastError(). Always return -1. */ +extern int WIN_SetError(const char *prefix); + +/* Wrap up the oddities of CoInitialize() into a common function. */ +extern HRESULT WIN_CoInitialize(void); +extern void WIN_CoUninitialize(void); + +/* Returns SDL_TRUE if we're running on Windows Vista and newer */ +extern BOOL WIN_IsWindowsVistaOrGreater(); + +#include +#include + +/* Include the SDL main definition header */ +#include "SDL.h" +#include "SDL_main.h" + +#ifdef main +# undef main +#endif /* main */ + +static void +UnEscapeQuotes(char *arg) +{ + char *last = NULL; + + while (*arg) { + if (*arg == '"' && (last != NULL && *last == '\\')) { + char *c_curr = arg; + char *c_last = last; + + while (*c_curr) { + *c_last = *c_curr; + c_last = c_curr; + c_curr++; + } + *c_last = '\0'; + } + last = arg; + arg++; + } +} + +/* Parse a command line buffer into arguments */ +static int +ParseCommandLine(char *cmdline, char **argv) +{ + char *bufp; + char *lastp = NULL; + int argc, last_argc; + + argc = last_argc = 0; + for (bufp = cmdline; *bufp;) { + /* Skip leading whitespace */ + while (SDL_isspace(*bufp)) { + ++bufp; + } + /* Skip over argument */ + if (*bufp == '"') { + ++bufp; + if (*bufp) { + if (argv) { + argv[argc] = bufp; + } + ++argc; + } + /* Skip over word */ + lastp = bufp; + while (*bufp && (*bufp != '"' || *lastp == '\\')) { + lastp = bufp; + ++bufp; + } + } else { + if (*bufp) { + if (argv) { + argv[argc] = bufp; + } + ++argc; + } + /* Skip over word */ + while (*bufp && !SDL_isspace(*bufp)) { + ++bufp; + } + } + if (*bufp) { + if (argv) { + *bufp = '\0'; + } + ++bufp; + } + + /* Strip out \ from \" sequences */ + if (argv && last_argc != argc) { + UnEscapeQuotes(argv[last_argc]); + } + last_argc = argc; + } + if (argv) { + argv[argc] = NULL; + } + return (argc); +} + +/* Show an error message */ +static void +ShowError(const char *title, const char *message) +{ +/* If USE_MESSAGEBOX is defined, you need to link with user32.lib */ +#ifdef USE_MESSAGEBOX + MessageBox(NULL, message, title, MB_ICONEXCLAMATION | MB_OK); +#else + fprintf(stderr, "%s: %s\n", title, message); +#endif +} + +/* Pop up an out of memory message, returns to Windows */ +static BOOL +OutOfMemory(void) +{ + ShowError("Fatal Error", "Out of memory - aborting"); + return FALSE; +} + +#if defined(_MSC_VER) +/* The VC++ compiler needs main defined */ +#define console_main main +#endif + +/* This is where execution begins [console apps] */ +int +console_main(int argc, char *argv[]) +{ + int status; + + SDL_SetMainReady(); + + /* Run the application main() code */ + status = SDL_main(argc, argv); + + /* Exit cleanly, calling atexit() functions */ + exit(status); + + /* Hush little compiler, don't you cry... */ + return 0; +} + +/* This is where execution begins [windowed apps] */ +int WINAPI +WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw) +{ + char **argv; + int argc; + char *cmdline; + + /* Grab the command line */ + TCHAR *text = GetCommandLine(); +#if UNICODE + cmdline = SDL_iconv_string("UTF-8", "UCS-2-INTERNAL", (char *)(text), (SDL_wcslen(text)+1)*sizeof(WCHAR)); +#else + cmdline = SDL_strdup(text); +#endif + if (cmdline == NULL) { + return OutOfMemory(); + } + + /* Parse it into argv and argc */ + argc = ParseCommandLine(cmdline, NULL); + argv = SDL_stack_alloc(char *, argc + 1); + if (argv == NULL) { + return OutOfMemory(); + } + ParseCommandLine(cmdline, argv); + + /* Run the main program */ + console_main(argc, argv); + + SDL_stack_free(argv); + + SDL_free(cmdline); + + /* Hush little compiler, don't you cry... */ + return 0; +} + +#endif /* __WIN32__ */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/source/engine/thirdparty/stb/tests/fuzz_main.c b/source/engine/thirdparty/stb/tests/fuzz_main.c new file mode 100644 index 0000000..40c0cc8 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/fuzz_main.c @@ -0,0 +1,54 @@ +#include +#include +#include + +/* fuzz target entry point, works without libFuzzer */ + +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); + +int main(int argc, char **argv) +{ + FILE *f; + char *buf = NULL; + long siz_buf; + + if(argc < 2) + { + fprintf(stderr, "no input file\n"); + goto err; + } + + f = fopen(argv[1], "rb"); + if(f == NULL) + { + fprintf(stderr, "error opening input file %s\n", argv[1]); + goto err; + } + + fseek(f, 0, SEEK_END); + + siz_buf = ftell(f); + rewind(f); + + if(siz_buf < 1) goto err; + + buf = (char*)malloc((size_t)siz_buf); + if(buf == NULL) + { + fprintf(stderr, "malloc() failed\n"); + goto err; + } + + if(fread(buf, (size_t)siz_buf, 1, f) != 1) + { + fprintf(stderr, "fread() failed\n"); + goto err; + } + + (void)LLVMFuzzerTestOneInput((uint8_t*)buf, (size_t)siz_buf); + +err: + free(buf); + + return 0; +} diff --git a/source/engine/thirdparty/stb/tests/grid_reachability.c b/source/engine/thirdparty/stb/tests/grid_reachability.c new file mode 100644 index 0000000..905f2c2 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/grid_reachability.c @@ -0,0 +1,363 @@ +#define STB_CONNECTED_COMPONENTS_IMPLEMENTATION +#define STBCC_GRID_COUNT_X_LOG2 10 +#define STBCC_GRID_COUNT_Y_LOG2 10 +#include "stb_connected_components.h" + +#ifdef GRID_TEST + +#include +#include +#include + +//#define STB_DEFINE +#include "stb.h" + +//#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +//#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +typedef struct +{ + uint16 x,y; +} point; + +point leader[1024][1024]; +uint32 color[1024][1024]; + +point find(int x, int y) +{ + point p,q; + p = leader[y][x]; + if (p.x == x && p.y == y) + return p; + q = find(p.x, p.y); + leader[y][x] = q; + return q; +} + +void onion(int x1, int y1, int x2, int y2) +{ + point p = find(x1,y1); + point q = find(x2,y2); + + if (p.x == q.x && p.y == q.y) + return; + + leader[p.y][p.x] = q; +} + +void reference(uint8 *map, int w, int h) +{ + int i,j; + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + leader[j][i].x = i; + leader[j][i].y = j; + } + } + + for (j=1; j < h-1; ++j) { + for (i=1; i < w-1; ++i) { + if (map[j*w+i] == 255) { + if (map[(j+1)*w+i] == 255) onion(i,j, i,j+1); + if (map[(j)*w+i+1] == 255) onion(i,j, i+1,j); + } + } + } + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + uint32 c = 0xff000000; + if (leader[j][i].x == i && leader[j][i].y == j) { + if (map[j*w+i] == 255) + c = stb_randLCG() | 0xff404040; + } + color[j][i] = c; + } + } + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + if (leader[j][i].x != i || leader[j][i].y != j) { + point p = find(i,j); + color[j][i] = color[p.y][p.x]; + } + } + } +} + +void write_map(stbcc_grid *g, int w, int h, char *filename) +{ + int i,j; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + unsigned int c; + c = stbcc_get_unique_id(g,i,j); + c = stb_rehash_improved(c)&0xffffff; + if (c == STBCC_NULL_UNIQUE_ID) + c = 0xff000000; + else + c = (~c)^0x555555; + if (i % 32 == 0 || j %32 == 0) { + int r = (c >> 16) & 255; + int g = (c >> 8) & 255; + int b = c & 255; + r = (r+130)/2; + g = (g+130)/2; + b = (b+130)/2; + c = 0xff000000 + (r<<16) + (g<<8) + b; + } + color[j][i] = c; + } + } + stbi_write_png(filename, w, h, 4, color, 4*w); +} + +void test_connected(stbcc_grid *g) +{ + int n = stbcc_query_grid_node_connection(g, 512, 90, 512, 871); + //printf("%d ", n); +} + +static char *message; +LARGE_INTEGER start; + +void start_timer(char *s) +{ + message = s; + QueryPerformanceCounter(&start); +} + +void end_timer(void) +{ + LARGE_INTEGER end, freq; + double tm; + + QueryPerformanceCounter(&end); + QueryPerformanceFrequency(&freq); + + tm = (end.QuadPart - start.QuadPart) / (double) freq.QuadPart; + printf("%6.4lf ms: %s\n", tm * 1000, message); +} + +extern void quicktest(void); + +int loc[5000][2]; +int main(int argc, char **argv) +{ + stbcc_grid *g; + + int w,h, i,j,k=0, count=0, r; + uint8 *map = stbi_load("data/map_03.png", &w, &h, 0, 1); + + assert(map); + quicktest(); + + for (j=0; j < h; ++j) + for (i=0; i < w; ++i) + map[j*w+i] = ~map[j*w+i]; + + for (i=0; i < w; ++i) + for (j=0; j < h; ++j) + //map[j*w+i] = (((i+1) ^ (j+1)) >> 1) & 1 ? 255 : 0; + map[j*w+i] = stb_max(abs(i-w/2),abs(j-h/2)) & 1 ? 255 : 0; + //map[j*w+i] = (((i ^ j) >> 5) ^ (i ^ j)) & 1 ? 255 : 0; + //map[j*w+i] = stb_rand() & 1 ? 255 : 0; + + #if 1 + for (i=0; i < 100000; ++i) + map[(stb_rand()%h)*w + stb_rand()%w] ^= 255; + #endif + + _mkdir("tests/output/stbcc"); + + stbi_write_png("tests/output/stbcc/reference.png", w, h, 1, map, 0); + + //reference(map, w, h); + + g = malloc(stbcc_grid_sizeof()); + printf("Size: %d\n", stbcc_grid_sizeof()); + +#if 0 + memset(map, 0, w*h); + stbcc_init_grid(g, map, w, h); + { + int n; + char **s = stb_stringfile("c:/x/clockwork_update.txt", &n); + write_map(g, w, h, "tests/output/stbcc/base.png"); + for (i=1; i < n; i += 1) { + int x,y,t; + sscanf(s[i], "%d %d %d", &x, &y, &t); + if (i == 571678) + write_map(g, w, h, stb_sprintf("tests/output/stbcc/clockwork_good.png", i)); + stbcc_update_grid(g, x, y, t); + if (i == 571678) + write_map(g, w, h, stb_sprintf("tests/output/stbcc/clockwork_bad.png", i)); + //if (i > 571648 && i <= 571712) + //write_map(g, w, h, stb_sprintf("tests/output/stbcc/clockwork_%06d.png", i)); + } + write_map(g, w, h, stb_sprintf("tests/output/stbcc/clockwork_%06d.png", i-1)); + } + return 0; +#endif + + + start_timer("init"); + stbcc_init_grid(g, map, w, h); + end_timer(); + //g = stb_file("c:/x/clockwork_path.bin", 0); + write_map(g, w, h, "tests/output/stbcc/base.png"); + + for (i=0; i < 5000;) { + loc[i][0] = stb_rand() % w; + loc[i][1] = stb_rand() % h; + if (stbcc_query_grid_open(g, loc[i][0], loc[i][1])) + ++i; + } + + r = 0; + start_timer("reachable"); + for (i=0; i < 2000; ++i) { + for (j=0; j < 2000; ++j) { + int x1 = loc[i][0], y1 = loc[i][1]; + int x2 = loc[2000+j][0], y2 = loc[2000+j][1]; + r += stbcc_query_grid_node_connection(g, x1,y1, x2,y2); + } + } + end_timer(); + printf("%d reachable\n", r); + + printf("Cluster size: %d,%d\n", STBCC__CLUSTER_SIZE_X, STBCC__CLUSTER_SIZE_Y); + + #if 1 + for (j=0; j < 10; ++j) { + for (i=0; i < 5000; ++i) { + loc[i][0] = stb_rand() % w; + loc[i][1] = stb_rand() % h; + } + start_timer("updating 2500"); + for (i=0; i < 2500; ++i) { + if (stbcc_query_grid_open(g, loc[i][0], loc[i][1])) + stbcc_update_grid(g, loc[i][0], loc[i][1], 1); + else + stbcc_update_grid(g, loc[i][0], loc[i][1], 0); + } + end_timer(); + write_map(g, w, h, stb_sprintf("tests/output/stbcc/update_random_%d.png", j*i)); + } + #endif + + #if 0 + start_timer("removing"); + count = 0; + for (i=0; i < 1800; ++i) { + int x,y,a,b; + x = stb_rand() % (w-32); + y = stb_rand() % (h-32); + + if (i & 1) { + for (a=0; a < 32; ++a) + for (b=0; b < 1; ++b) + if (stbcc_query_grid_open(g, x+a, y+b)) { + stbcc_update_grid(g, x+a, y+b, 1); + ++count; + } + } else { + for (a=0; a < 1; ++a) + for (b=0; b < 32; ++b) + if (stbcc_query_grid_open(g, x+a, y+b)) { + stbcc_update_grid(g, x+a, y+b, 1); + ++count; + } + } + + //if (i % 100 == 0) write_map(g, w, h, stb_sprintf("tests/output/stbcc/open_random_%d.png", i+1)); + } + end_timer(); + printf("Removed %d grid spaces\n", count); + write_map(g, w, h, stb_sprintf("tests/output/stbcc/open_random_%d.png", i)); + + + r = 0; + start_timer("reachable"); + for (i=0; i < 1000; ++i) { + for (j=0; j < 1000; ++j) { + int x1 = loc[i][0], y1 = loc[i][1]; + int x2 = loc[j][0], y2 = loc[j][1]; + r += stbcc_query_grid_node_connection(g, x1,y1, x2,y2); + } + } + end_timer(); + printf("%d reachable\n", r); + + start_timer("adding"); + count = 0; + for (i=0; i < 1800; ++i) { + int x,y,a,b; + x = stb_rand() % (w-32); + y = stb_rand() % (h-32); + + if (i & 1) { + for (a=0; a < 32; ++a) + for (b=0; b < 1; ++b) + if (!stbcc_query_grid_open(g, x+a, y+b)) { + stbcc_update_grid(g, x+a, y+b, 0); + ++count; + } + } else { + for (a=0; a < 1; ++a) + for (b=0; b < 32; ++b) + if (!stbcc_query_grid_open(g, x+a, y+b)) { + stbcc_update_grid(g, x+a, y+b, 0); + ++count; + } + } + + //if (i % 100 == 0) write_map(g, w, h, stb_sprintf("tests/output/stbcc/close_random_%d.png", i+1)); + } + end_timer(); + write_map(g, w, h, stb_sprintf("tests/output/stbcc/close_random_%d.png", i)); + printf("Added %d grid spaces\n", count); + #endif + + + #if 0 // for map_02.png + start_timer("process"); + for (k=0; k < 20; ++k) { + for (j=0; j < h; ++j) { + int any=0; + for (i=0; i < w; ++i) { + if (map[j*w+i] > 10 && map[j*w+i] < 250) { + //start_timer(stb_sprintf("open %d,%d", i,j)); + stbcc_update_grid(g, i, j, 0); + test_connected(g); + //end_timer(); + any = 1; + } + } + if (any) write_map(g, w, h, stb_sprintf("tests/output/stbcc/open_row_%04d.png", j)); + } + + for (j=0; j < h; ++j) { + int any=0; + for (i=0; i < w; ++i) { + if (map[j*w+i] > 10 && map[j*w+i] < 250) { + //start_timer(stb_sprintf("close %d,%d", i,j)); + stbcc_update_grid(g, i, j, 1); + test_connected(g); + //end_timer(); + any = 1; + } + } + if (any) write_map(g, w, h, stb_sprintf("tests/output/stbcc/close_row_%04d.png", j)); + } + } + end_timer(); + #endif + + return 0; +} +#endif diff --git a/source/engine/thirdparty/stb/tests/herringbone.dsp b/source/engine/thirdparty/stb/tests/herringbone.dsp new file mode 100644 index 0000000..b82fee4 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/herringbone.dsp @@ -0,0 +1,95 @@ +# Microsoft Developer Studio Project File - Name="herringbone" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=herringbone - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "herringbone.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "herringbone.mak" CFG="herringbone - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "herringbone - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "herringbone - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "herringbone - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I ".." /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "herringbone - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "herringbone___Win32_Debug" +# PROP BASE Intermediate_Dir "herringbone___Win32_Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "herringbone - Win32 Release" +# Name "herringbone - Win32 Debug" +# Begin Source File + +SOURCE=.\herringbone_generator.c +# End Source File +# Begin Source File + +SOURCE=..\stb_herringbone_wang_tile.h +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/stb/tests/herringbone_generator.c b/source/engine/thirdparty/stb/tests/herringbone_generator.c new file mode 100644 index 0000000..cf2a99e --- /dev/null +++ b/source/engine/thirdparty/stb/tests/herringbone_generator.c @@ -0,0 +1,87 @@ +#define STB_HERRINGBONE_WANG_TILE_IMPLEMENTATION +#include "stb_herringbone_wang_tile.h" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +// e 12 1 1 1 1 1 1 4 4 + +int main(int argc, char **argv) +{ + stbhw_config c = { 0 }; + int w,h, num_colors,i; + unsigned char *data; + + if (argc == 1) goto usage; + if (argc < 3) goto error; + + switch (argv[2][0]) { + case 'c': + if (argc < 8 || argc > 10) + goto error; + num_colors = 4; + c.is_corner = 1; + break; + + case 'e': + if (argc < 10 || argc > 12) + goto error; + num_colors = 6; + c.is_corner = 0; + break; + + default: + goto error; + } + + c.short_side_len = atoi(argv[3]); + for (i=0; i < num_colors; ++i) + c.num_color[i] = atoi(argv[4+i]); + + c.num_vary_x = 1; + c.num_vary_y = 1; + + if (argc > 4+i) + c.num_vary_x = atoi(argv[4+i]); + if (argc > 5+i) + c.num_vary_y = atoi(argv[5+i]); + + stbhw_get_template_size(&c, &w, &h); + + data = (unsigned char *) malloc(w*h*3); + + if (stbhw_make_template(&c, data, w, h, w*3)) + stbi_write_png(argv[1], w, h, 3, data, w*3); + else + fprintf(stderr, "Error: %s\n", stbhw_get_last_error()); + return 0; + + error: + fputs("Invalid command-line arguments\n\n", stderr); + usage: + fputs("Usage (see source for corner & edge type definitions):\n\n", stderr); + fputs("herringbone_generator {outfile} c {sidelen} {c0} {c1} {c2} {c3} [{vx} {vy}]\n" + " {outfile} -- filename that template will be written to as PNG\n" + " {sidelen} -- length of short side of rectangle in pixels\n" + " {c0} -- number of colors for corner type 0\n" + " {c1} -- number of colors for corner type 1\n" + " {c2} -- number of colors for corner type 2\n" + " {c3} -- number of colors for corner type 3\n" + " {vx} -- number of color-duplicating variations horizontally in template\n" + " {vy} -- number of color-duplicating variations vertically in template\n" + "\n" + , stderr); + fputs("herringbone_generator {outfile} e {sidelen} {e0} {e1} {e2} {e3} {e4} {e5} [{vx} {vy}]\n" + " {outfile} -- filename that template will be written to as PNG\n" + " {sidelen} -- length of short side of rectangle in pixels\n" + " {e0} -- number of colors for edge type 0\n" + " {e1} -- number of colors for edge type 1\n" + " {e2} -- number of colors for edge type 2\n" + " {e3} -- number of colors for edge type 3\n" + " {e4} -- number of colors for edge type 4\n" + " {e5} -- number of colors for edge type 5\n" + " {vx} -- number of color-duplicating variations horizontally in template\n" + " {vy} -- number of color-duplicating variations vertically in template\n" + , stderr); + return 1; +} diff --git a/source/engine/thirdparty/stb/tests/herringbone_map.c b/source/engine/thirdparty/stb/tests/herringbone_map.c new file mode 100644 index 0000000..22cc013 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/herringbone_map.c @@ -0,0 +1,83 @@ +#include + +#define STB_HBWANG_MAX_X 500 +#define STB_HBWANG_MAX_Y 500 + +#define STB_HERRINGBONE_WANG_TILE_IMPLEMENTATION +#include "stb_herringbone_wang_tile.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +int main(int argc, char **argv) +{ + if (argc < 5) { + fprintf(stderr, "Usage: herringbone_map {inputfile} {output-width} {output-height} {outputfile}\n"); + return 1; + } else { + char *filename = argv[1]; + int out_w = atoi(argv[2]); + int out_h = atoi(argv[3]); + char *outfile = argv[4]; + + unsigned char *pixels, *out_pixels; + stbhw_tileset ts; + int w,h; + + pixels = stbi_load(filename, &w, &h, 0, 3); + if (pixels == 0) { + fprintf(stderr, "Couldn't open input file '%s'\n", filename); + exit(1); + } + + if (!stbhw_build_tileset_from_image(&ts, pixels, w*3, w, h)) { + fprintf(stderr, "Error: %s\n", stbhw_get_last_error()); + return 1; + } + + free(pixels); + + #ifdef DEBUG_OUTPUT + { + int i,j,k; + // add blue borders to top-left edges of the tiles + int hstride = (ts.short_side_len*2)*3; + int vstride = (ts.short_side_len )*3; + for (i=0; i < ts.num_h_tiles; ++i) { + unsigned char *pix = ts.h_tiles[i]->pixels; + for (j=0; j < ts.short_side_len*2; ++j) + for (k=0; k < 3; ++k) + pix[j*3+k] = (pix[j*3+k]*0.5+100+k*75)/1.5; + for (j=1; j < ts.short_side_len; ++j) + for (k=0; k < 3; ++k) + pix[j*hstride+k] = (pix[j*hstride+k]*0.5+100+k*75)/1.5; + } + for (i=0; i < ts.num_v_tiles; ++i) { + unsigned char *pix = ts.v_tiles[i]->pixels; + for (j=0; j < ts.short_side_len; ++j) + for (k=0; k < 3; ++k) + pix[j*3+k] = (pix[j*3+k]*0.5+100+k*75)/1.5; + for (j=1; j < ts.short_side_len*2; ++j) + for (k=0; k < 3; ++k) + pix[j*vstride+k] = (pix[j*vstride+k]*0.5+100+k*75)/1.5; + } + } + #endif + + out_pixels = malloc(out_w * out_h * 3); + + if (!stbhw_generate_image(&ts, NULL, out_pixels, out_w*3, out_w, out_h)) { + fprintf(stderr, "Error: %s\n", stbhw_get_last_error()); + return 1; + } + + stbi_write_png(argv[4], out_w, out_h, 3, out_pixels, out_w*3); + free(out_pixels); + + stbhw_free_tileset(&ts); + return 0; + } +} \ No newline at end of file diff --git a/source/engine/thirdparty/stb/tests/herringbone_map.dsp b/source/engine/thirdparty/stb/tests/herringbone_map.dsp new file mode 100644 index 0000000..3e26d6d --- /dev/null +++ b/source/engine/thirdparty/stb/tests/herringbone_map.dsp @@ -0,0 +1,94 @@ +# Microsoft Developer Studio Project File - Name="herringbone_map" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=herringbone_map - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "herringbone_map.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "herringbone_map.mak" CFG="herringbone_map - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "herringbone_map - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "herringbone_map - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "herringbone_map - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I ".." /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "herringbone_map - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "herringbone_map___Win32_Debug" +# PROP BASE Intermediate_Dir "herringbone_map___Win32_Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "herringbone_map - Win32 Release" +# Name "herringbone_map - Win32 Debug" +# Begin Source File + +SOURCE=.\herringbone_map.c +# End Source File +# Begin Source File + +SOURCE=..\stb_herringbone_wang_tile.h +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/stb/tests/image_test.c b/source/engine/thirdparty/stb/tests/image_test.c new file mode 100644 index 0000000..9c216cf --- /dev/null +++ b/source/engine/thirdparty/stb/tests/image_test.c @@ -0,0 +1,173 @@ +#define STBI_WINDOWS_UTF8 + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#define STB_DEFINE +#include "stb.h" + +//#define PNGSUITE_PRIMARY + +#if 0 +void test_ycbcr(void) +{ + STBI_SIMD_ALIGN(unsigned char, y[256]); + STBI_SIMD_ALIGN(unsigned char, cb[256]); + STBI_SIMD_ALIGN(unsigned char, cr[256]); + STBI_SIMD_ALIGN(unsigned char, out1[256][4]); + STBI_SIMD_ALIGN(unsigned char, out2[256][4]); + + int i,j,k; + int count = 0, bigcount=0, total=0; + + for (i=0; i < 256; ++i) { + for (j=0; j < 256; ++j) { + for (k=0; k < 256; ++k) { + y [k] = k; + cb[k] = j; + cr[k] = i; + } + stbi__YCbCr_to_RGB_row(out1[0], y, cb, cr, 256, 4); + stbi__YCbCr_to_RGB_sse2(out2[0], y, cb, cr, 256, 4); + for (k=0; k < 256; ++k) { + // inaccurate proxy for values outside of RGB cube + if (out1[k][0] == 0 || out1[k][1] == 0 || out1[k][2] == 0 || out1[k][0] == 255 || out1[k][1] == 255 || out1[k][2] == 255) + continue; + ++total; + if (out1[k][0] != out2[k][0] || out1[k][1] != out2[k][1] || out1[k][2] != out2[k][2]) { + int dist1 = abs(out1[k][0] - out2[k][0]); + int dist2 = abs(out1[k][1] - out2[k][1]); + int dist3 = abs(out1[k][2] - out2[k][2]); + ++count; + if (out1[k][1] > out2[k][1]) + ++bigcount; + } + } + } + printf("So far: %d (%d big) of %d\n", count, bigcount, total); + } + printf("Final: %d (%d big) of %d\n", count, bigcount, total); +} +#endif + +float hdr_data[200][200][3]; + +void dummy_write(void *context, void *data, int len) +{ + static char dummy[1024]; + if (len > 1024) len = 1024; + memcpy(dummy, data, len); +} + +extern void image_write_test(void); + +int main(int argc, char **argv) +{ + int w,h; + //test_ycbcr(); + + image_write_test(); + + #if 0 + // test hdr asserts + for (h=0; h < 100; h += 2) + for (w=0; w < 200; ++w) + hdr_data[h][w][0] = (float) rand(), + hdr_data[h][w][1] = (float) rand(), + hdr_data[h][w][2] = (float) rand(); + + stbi_write_hdr("output/test.hdr", 200,200,3,hdr_data[0][0]); + #endif + + if (argc > 1) { + int i, n; + + for (i=1; i < argc; ++i) { + int res; + int w2,h2,n2; + unsigned char *data; + printf("%s\n", argv[i]); + res = stbi_info(argv[i], &w2, &h2, &n2); + data = stbi_load(argv[i], &w, &h, &n, 0); if (data) free(data); else printf("Failed &n\n"); + data = stbi_load(argv[i], &w, &h, &n, 4); if (data) free(data); else printf("Failed &n\n"); + data = stbi_load(argv[i], &w, &h, 0, 1); if (data) free(data); else printf("Failed 1\n"); + data = stbi_load(argv[i], &w, &h, 0, 2); if (data) free(data); else printf("Failed 2\n"); + data = stbi_load(argv[i], &w, &h, 0, 3); if (data) free(data); else printf("Failed 3\n"); + data = stbi_load(argv[i], &w, &h, &n, 4); + assert(data); + assert(w == w2 && h == h2 && n == n2); + assert(res); + if (data) { + char fname[512]; + stb_splitpath(fname, argv[i], STB_FILE); + stbi_write_png(stb_sprintf("output/%s.png", fname), w, h, 4, data, w*4); + stbi_write_bmp(stb_sprintf("output/%s.bmp", fname), w, h, 4, data); + stbi_write_tga(stb_sprintf("output/%s.tga", fname), w, h, 4, data); + stbi_write_png_to_func(dummy_write,0, w, h, 4, data, w*4); + stbi_write_bmp_to_func(dummy_write,0, w, h, 4, data); + stbi_write_tga_to_func(dummy_write,0, w, h, 4, data); + free(data); + } else + printf("FAILED 4\n"); + } + } else { + int i; + #ifdef PNGSUITE_PRIMARY + char **files = stb_readdir_files("pngsuite/primary"); + #else + char **files = stb_readdir_files("images"); + #endif + for (i=0; i < stb_arr_len(files); ++i) { + int n; + char **failed = NULL; + unsigned char *data; + printf("."); + //printf("%s\n", files[i]); + data = stbi_load(files[i], &w, &h, &n, 0); if (data) free(data); else stb_arr_push(failed, "&n"); + data = stbi_load(files[i], &w, &h, 0, 1); if (data) free(data); else stb_arr_push(failed, "1"); + data = stbi_load(files[i], &w, &h, 0, 2); if (data) free(data); else stb_arr_push(failed, "2"); + data = stbi_load(files[i], &w, &h, 0, 3); if (data) free(data); else stb_arr_push(failed, "3"); + data = stbi_load(files[i], &w, &h, 0, 4); if (data) ; else stb_arr_push(failed, "4"); + if (data) { + char fname[512]; + + #ifdef PNGSUITE_PRIMARY + int w2,h2; + unsigned char *data2; + stb_splitpath(fname, files[i], STB_FILE_EXT); + data2 = stbi_load(stb_sprintf("pngsuite/primary_check/%s", fname), &w2, &h2, 0, 4); + if (!data2) + printf("FAILED: couldn't load 'pngsuite/primary_check/%s\n", fname); + else { + if (w != w2 || h != w2 || 0 != memcmp(data, data2, w*h*4)) { + int x,y,c; + if (w == w2 && h == h2) + for (y=0; y < h; ++y) + for (x=0; x < w; ++x) + for (c=0; c < 4; ++c) + assert(data[y*w*4+x*4+c] == data2[y*w*4+x*4+c]); + printf("FAILED: %s loaded but didn't match PRIMARY_check 32-bit version\n", files[i]); + } + free(data2); + } + #else + stb_splitpath(fname, files[i], STB_FILE); + stbi_write_png(stb_sprintf("output/%s.png", fname), w, h, 4, data, w*4); + #endif + free(data); + } + if (failed) { + int j; + printf("FAILED: "); + for (j=0; j < stb_arr_len(failed); ++j) + printf("%s ", failed[j]); + printf(" -- %s\n", files[i]); + } + } + printf("Tested %d files.\n", i); + } + return 0; +} diff --git a/source/engine/thirdparty/stb/tests/image_test.dsp b/source/engine/thirdparty/stb/tests/image_test.dsp new file mode 100644 index 0000000..65f0aaf --- /dev/null +++ b/source/engine/thirdparty/stb/tests/image_test.dsp @@ -0,0 +1,103 @@ +# Microsoft Developer Studio Project File - Name="image_test" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=image_test - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "image_test.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "image_test.mak" CFG="image_test - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "image_test - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "image_test - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "image_test - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /Zi /O2 /I ".." /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 + +!ELSEIF "$(CFG)" == "image_test - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug\image_test" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "image_test - Win32 Release" +# Name "image_test - Win32 Debug" +# Begin Source File + +SOURCE=.\image_test.c +# End Source File +# Begin Source File + +SOURCE=.\image_write_test.c +# End Source File +# Begin Source File + +SOURCE=..\stb_image.h +# End Source File +# Begin Source File + +SOURCE=..\stb_image_write.h +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/stb/tests/image_write_test.c b/source/engine/thirdparty/stb/tests/image_write_test.c new file mode 100644 index 0000000..4e4a7e8 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/image_write_test.c @@ -0,0 +1,60 @@ +#ifdef __clang__ +#define STBIWDEF static inline +#endif +#define STB_IMAGE_WRITE_STATIC +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +// using an 'F' since it has no rotational symmetries, and 6x5 +// because it's a small, atypical size likely to trigger edge cases. +// +// conveniently, it's also small enough to fully fit inside a typical +// directory listing thumbnail, which simplifies checking at a glance. +static const char img6x5_template[] = + ".****." + ".*...." + ".***.." + ".*...." + ".*...."; + +void image_write_test(void) +{ + // make a RGB version of the template image + // use red on blue to detect R<->B swaps + unsigned char img6x5_rgb[6*5*3]; + float img6x5_rgbf[6*5*3]; + int i; + + for (i = 0; i < 6*5; i++) { + int on = img6x5_template[i] == '*'; + img6x5_rgb[i*3 + 0] = on ? 255 : 0; + img6x5_rgb[i*3 + 1] = 0; + img6x5_rgb[i*3 + 2] = on ? 0 : 255; + + img6x5_rgbf[i*3 + 0] = on ? 1.0f : 0.0f; + img6x5_rgbf[i*3 + 1] = 0.0f; + img6x5_rgbf[i*3 + 2] = on ? 0.0f : 1.0f; + } + + stbi_write_png("output/wr6x5_regular.png", 6, 5, 3, img6x5_rgb, 6*3); + stbi_write_bmp("output/wr6x5_regular.bmp", 6, 5, 3, img6x5_rgb); + stbi_write_tga("output/wr6x5_regular.tga", 6, 5, 3, img6x5_rgb); + stbi_write_jpg("output/wr6x5_regular.jpg", 6, 5, 3, img6x5_rgb, 95); + stbi_write_hdr("output/wr6x5_regular.hdr", 6, 5, 3, img6x5_rgbf); + + stbi_flip_vertically_on_write(1); + + stbi_write_png("output/wr6x5_flip.png", 6, 5, 3, img6x5_rgb, 6*3); + stbi_write_bmp("output/wr6x5_flip.bmp", 6, 5, 3, img6x5_rgb); + stbi_write_tga("output/wr6x5_flip.tga", 6, 5, 3, img6x5_rgb); + stbi_write_jpg("output/wr6x5_flip.jpg", 6, 5, 3, img6x5_rgb, 95); + stbi_write_hdr("output/wr6x5_flip.hdr", 6, 5, 3, img6x5_rgbf); +} + +#ifdef IWT_TEST +int main(int argc, char **argv) +{ + image_write_test(); + return 0; +} +#endif diff --git a/source/engine/thirdparty/stb/tests/ossfuzz.sh b/source/engine/thirdparty/stb/tests/ossfuzz.sh new file mode 100755 index 0000000..2db2060 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/ossfuzz.sh @@ -0,0 +1,29 @@ +#!/bin/bash -eu +# This script is meant to be run by +# https://github.com/google/oss-fuzz/blob/master/projects/stb/Dockerfile + +$CXX $CXXFLAGS -std=c++11 -I. -DSTBI_ONLY_PNG \ + $SRC/stb/tests/stbi_read_fuzzer.c \ + -o $OUT/stb_png_read_fuzzer $LIB_FUZZING_ENGINE + +$CXX $CXXFLAGS -std=c++11 -I. \ + $SRC/stb/tests/stbi_read_fuzzer.c \ + -o $OUT/stbi_read_fuzzer $LIB_FUZZING_ENGINE + +find $SRC/stb/tests/pngsuite -name "*.png" | \ + xargs zip $OUT/stb_png_read_fuzzer_seed_corpus.zip + +cp $SRC/stb/tests/stb_png.dict $OUT/stb_png_read_fuzzer.dict + +tar xvzf $SRC/stbi/jpg.tar.gz --directory $SRC/stb/tests +tar xvzf $SRC/stbi/gif.tar.gz --directory $SRC/stb/tests +unzip $SRC/stbi/bmp.zip -d $SRC/stb/tests +unzip $SRC/stbi/tga.zip -d $SRC/stb/tests + +find $SRC/stb/tests -name "*.png" -o -name "*.jpg" -o -name "*.gif" \ + -o -name "*.bmp" -o -name "*.tga" -o -name "*.TGA" \ + -o -name "*.ppm" -o -name "*.pgm" \ + | xargs zip $OUT/stbi_read_fuzzer_seed_corpus.zip + +echo "" >> $SRC/stbi/gif.dict +cat $SRC/stbi/gif.dict $SRC/stb/tests/stb_png.dict > $OUT/stbi_read_fuzzer.dict diff --git a/source/engine/thirdparty/stb/tests/oversample/README.md b/source/engine/thirdparty/stb/tests/oversample/README.md new file mode 100644 index 0000000..cdfdfff --- /dev/null +++ b/source/engine/thirdparty/stb/tests/oversample/README.md @@ -0,0 +1,94 @@ +# Font character oversampling for rendering from atlas textures + +TL,DR: Run oversample.exe on a windows machine to see the +benefits of oversampling. It will try to use arial.ttf from the +Windows font directory unless you type the name of a .ttf file as +a command-line argument. + +## Benefits of oversampling + +Oversampling is a mechanism for improving subpixel rendering of characters. + +Improving subpixel has a few benefits: + +* With horizontal-oversampling, text can remain sharper while still being sub-pixel positioned for better kerning +* Horizontally-oversampled text significantly reduces aliasing when text animates horizontally +* Vertically-oversampled text significantly reduces aliasing when text animates vertically +* Text oversampled in both directions significantly reduces aliasing when text rotates + +## What text oversampling is + +A common strategy for rendering text is to cache character bitmaps +and reuse them. For hinted characters, every instance of a given +character is always identical, so this works fine. However, stb_truetype +doesn't do hinting. + +For anti-aliased characters, you can actually position the characters +with subpixel precision, and get different bitmaps based on that positioning +if you re-render the vector data. + +However, if you simply cache a single version of the bitmap and +draw it at different subpixel positions with a GPU, you will get +either the exact same result (if you use point-sampling on the +texture) or linear filtering. Linear filtering will cause a sub-pixel +positioned bitmap to blur further, causing a visible de-sharpening +of the character. (And, since the character wasn't hinted, it was +already blurrier than a hinted one would be, and now it gets even +more blurry.) + +You can avoid this by caching multiple variants of a character which +were rendered independently from the vector data. For example, you +might cache 3 versions of a char, at 0, 1/3, and 2/3rds of a pixel +horizontal offset, and always require characters to fall on integer +positions vertically. + +When creating a texture atlas for use on GPUs, which support bilinear +filtering, there is a better approach than caching several independent +positions, which is to allow lerping between the versions to allow +finer subpixel positioning. You can achieve these by interleaving +each of the cached bitmaps, but this turns out to be mathematically +equivalent to a simpler operation: oversampling and prefiltering the +characters. + +So, setting oversampling of 2x2 in stb_truetype is equivalent to caching +each character in 4 different variations, 1 for each subpixel position +in a 2x2 set. + +An advantage of this formulation is that no changes are required to +the rendering code; the exact same quad-rendering code works, it just +uses different texture coordinates. (Note this does potentially increase +texture bandwidth for text rendering since we end up minifying the texture +without using mipmapping, but you probably are not going to be fill-bound +by your text rendering.) + +## What about gamma? + +Gamma-correction for fonts just doesn't work. This doesn't seem to make +much sense -- it's physically correct, it simulates what we'd see if you +shrunk a font down really far, right? + +But you can play with it in the oversample.exe app. If you turn it on, +white-on-black fonts become too thick (i.e. they become too bright), and +black-on-white fonts become too thin (i.e. they are insufficiently dark). There is +no way to adjust the font's inherent thickness (i.e. by switching to +bold) to fix this for both; making the font thicker will make white +text worse, and making the font thinner will make black text worse. +Obviously you could use different fonts for light and dark cases, but +this doesn't seem like a very good way for fonts to work. + +Multiple people who have experimented with this independently (me, +Fabian Giesen,and Maxim Shemanarev of Anti-Grain Geometry) have all +concluded that correct gamma-correction does not produce the best +results for fonts. Font rendering just generally looks better without +gamma correction (or possibly with some arbitrary power stuck in +there, but it's not really correcting for gamma at that point). Maybe +this is in part a product of how we're used to fonts being on screens +which has changed how we expect them to look (e.g. perhaps hinting +oversharpens them and prevents the real-world thinning you'd see in +a black-on-white text). + +(AGG link on text rendering, including mention of gamma: + http://www.antigrain.com/research/font_rasterization/ ) + +Nevertheless, even if you turn on gamma-correction, you will find that +oversampling still helps in many cases for small fonts. diff --git a/source/engine/thirdparty/stb/tests/oversample/main.c b/source/engine/thirdparty/stb/tests/oversample/main.c new file mode 100644 index 0000000..bc6bd0f --- /dev/null +++ b/source/engine/thirdparty/stb/tests/oversample/main.c @@ -0,0 +1,332 @@ +#pragma warning(disable:4244; disable:4305; disable:4018) +#include +#include + +#define STB_WINMAIN +#include "stb_wingraph.h" + +#define STB_TRUETYPE_IMPLEMENTATION +#define STB_RECT_PACK_IMPLEMENTATION +#include "stb_rect_pack.h" +#include "stb_truetype.h" + +#ifndef WINGDIAPI +#define CALLBACK __stdcall +#define WINGDIAPI __declspec(dllimport) +#define APIENTRY __stdcall +#endif + +#include +#include + +#define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9 + +#define SIZE_X 1024 +#define SIZE_Y 768 + +stbtt_packedchar chardata[6][128]; + +int sx=SIZE_X, sy=SIZE_Y; + +#define BITMAP_W 512 +#define BITMAP_H 512 +unsigned char temp_bitmap[BITMAP_W][BITMAP_H]; +unsigned char ttf_buffer[1 << 25]; +GLuint font_tex; + +float scale[2] = { 24.0f, 14.0f }; + +int sf[6] = { 0,1,2, 0,1,2 }; + +void load_fonts(void) +{ + stbtt_pack_context pc; + int i; + FILE *f; + char filename[256]; + char *win = getenv("windir"); + if (win == NULL) win = getenv("SystemRoot"); + + f = fopen(stb_wingraph_commandline, "rb"); + if (!f) { + if (win == NULL) + sprintf(filename, "arial.ttf", win); + else + sprintf(filename, "%s/fonts/arial.ttf", win); + f = fopen(filename, "rb"); + if (!f) exit(0); + } + + fread(ttf_buffer, 1, 1<<25, f); + + stbtt_PackBegin(&pc, temp_bitmap[0], BITMAP_W, BITMAP_H, 0, 1, NULL); + for (i=0; i < 2; ++i) { + stbtt_PackSetOversampling(&pc, 1, 1); + stbtt_PackFontRange(&pc, ttf_buffer, 0, scale[i], 32, 95, chardata[i*3+0]+32); + stbtt_PackSetOversampling(&pc, 2, 2); + stbtt_PackFontRange(&pc, ttf_buffer, 0, scale[i], 32, 95, chardata[i*3+1]+32); + stbtt_PackSetOversampling(&pc, 3, 1); + stbtt_PackFontRange(&pc, ttf_buffer, 0, scale[i], 32, 95, chardata[i*3+2]+32); + } + stbtt_PackEnd(&pc); + + glGenTextures(1, &font_tex); + glBindTexture(GL_TEXTURE_2D, font_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, BITMAP_W, BITMAP_H, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +} + +int black_on_white; + +void draw_init(void) +{ + glDisable(GL_CULL_FACE); + glDisable(GL_TEXTURE_2D); + glDisable(GL_LIGHTING); + glDisable(GL_DEPTH_TEST); + + glViewport(0,0,sx,sy); + if (black_on_white) + glClearColor(255,255,255,0); + else + glClearColor(0,0,0,0); + glClear(GL_COLOR_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluOrtho2D(0,sx,sy,0); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + + +void drawBoxTC(float x0, float y0, float x1, float y1, float s0, float t0, float s1, float t1) +{ + glTexCoord2f(s0,t0); glVertex2f(x0,y0); + glTexCoord2f(s1,t0); glVertex2f(x1,y0); + glTexCoord2f(s1,t1); glVertex2f(x1,y1); + glTexCoord2f(s0,t1); glVertex2f(x0,y1); +} + +int integer_align; + +void print(float x, float y, int font, char *text) +{ + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, font_tex); + glBegin(GL_QUADS); + while (*text) { + stbtt_aligned_quad q; + stbtt_GetPackedQuad(chardata[font], BITMAP_W, BITMAP_H, *text++, &x, &y, &q, font ? 0 : integer_align); + drawBoxTC(q.x0,q.y0,q.x1,q.y1, q.s0,q.t0,q.s1,q.t1); + } + glEnd(); +} + +int font=3; +int translating; +int rotating=0; +int srgb=0; +float rotate_t, translate_t; +int show_tex; + +void draw_world(void) +{ + int sfont = sf[font]; + float x = 20; + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if (black_on_white) + glColor3f(0,0,0); + else + glColor3f(1,1,1); + + + print(80, 30, sfont, "Controls:"); + print(100, 60, sfont, "S: toggle font size"); + print(100, 85, sfont, "O: toggle oversampling"); + print(100,110, sfont, "T: toggle translation"); + print(100,135, sfont, "R: toggle rotation"); + print(100,160, sfont, "P: toggle pixel-snap (only non-oversampled)"); + print(100,185, sfont, "G: toggle srgb gamma-correction"); + if (black_on_white) + print(100,210, sfont, "B: toggle to white-on-black"); + else + print(100,210, sfont, "B: toggle to black-on-white"); + print(100,235, sfont, "V: view font texture"); + + print(80, 300, sfont, "Current font:"); + + if (!show_tex) { + if (font < 3) + print(100, 350, sfont, "Font height: 24 pixels"); + else + print(100, 350, sfont, "Font height: 14 pixels"); + } + + if (font%3==1) + print(100, 325, sfont, "2x2 oversampled text at 1:1"); + else if (font%3 == 2) + print(100, 325, sfont, "3x1 oversampled text at 1:1"); + else if (integer_align) + print(100, 325, sfont, "1:1 text, one texel = one pixel, snapped to integer coordinates"); + else + print(100, 325, sfont, "1:1 text, one texel = one pixel"); + + if (show_tex) { + glBegin(GL_QUADS); + drawBoxTC(200,400, 200+BITMAP_W,300+BITMAP_H, 0,0,1,1); + glEnd(); + } else { + glMatrixMode(GL_MODELVIEW); + glTranslatef(200,350,0); + + if (translating) + x += fmod(translate_t*8,30); + + if (rotating) { + glTranslatef(100,150,0); + glRotatef(rotate_t*2,0,0,1); + glTranslatef(-100,-150,0); + } + print(x,100, font, "This is a test"); + print(x,130, font, "Now is the time for all good men to come to the aid of their country."); + print(x,160, font, "The quick brown fox jumps over the lazy dog."); + print(x,190, font, "0123456789"); + } +} + +void draw(void) +{ + draw_init(); + draw_world(); + stbwingraph_SwapBuffers(NULL); +} + +static int initialized=0; +static float last_dt; + +int move[4]; +int raw_mouse_x, raw_mouse_y; + +int loopmode(float dt, int real, int in_client) +{ + float actual_dt = dt; + + if (!initialized) return 0; + + rotate_t += dt; + translate_t += dt; + +// music_sim(); + if (!real) + return 0; + + if (dt > 0.25) dt = 0.25; + if (dt < 0.01) dt = 0.01; + + draw(); + + return 0; +} + +int winproc(void *data, stbwingraph_event *e) +{ + switch (e->type) { + case STBWGE_create: + break; + + case STBWGE_char: + switch(e->key) { + case 27: + stbwingraph_ShowCursor(NULL,1); + return STBWINGRAPH_winproc_exit; + break; + case 'o': case 'O': + font = (font+1) % 3 + (font/3)*3; + break; + case 's': case 'S': + font = (font+3) % 6; + break; + case 't': case 'T': + translating = !translating; + translate_t = 0; + break; + case 'r': case 'R': + rotating = !rotating; + rotate_t = 0; + break; + case 'p': case 'P': + integer_align = !integer_align; + break; + case 'g': case 'G': + srgb = !srgb; + if (srgb) + glEnable(GL_FRAMEBUFFER_SRGB_EXT); + else + glDisable(GL_FRAMEBUFFER_SRGB_EXT); + break; + case 'v': case 'V': + show_tex = !show_tex; + break; + case 'b': case 'B': + black_on_white = !black_on_white; + break; + } + break; + + case STBWGE_mousemove: + raw_mouse_x = e->mx; + raw_mouse_y = e->my; + break; + +#if 0 + case STBWGE_mousewheel: do_mouse(e,0,0); break; + case STBWGE_leftdown: do_mouse(e, 1,0); break; + case STBWGE_leftup: do_mouse(e,-1,0); break; + case STBWGE_rightdown: do_mouse(e,0, 1); break; + case STBWGE_rightup: do_mouse(e,0,-1); break; +#endif + + case STBWGE_keydown: + if (e->key == VK_RIGHT) move[0] = 1; + if (e->key == VK_LEFT) move[1] = 1; + if (e->key == VK_UP) move[2] = 1; + if (e->key == VK_DOWN) move[3] = 1; + break; + case STBWGE_keyup: + if (e->key == VK_RIGHT) move[0] = 0; + if (e->key == VK_LEFT) move[1] = 0; + if (e->key == VK_UP) move[2] = 0; + if (e->key == VK_DOWN) move[3] = 0; + break; + + case STBWGE_size: + sx = e->width; + sy = e->height; + loopmode(0,1,0); + break; + + case STBWGE_draw: + if (initialized) + loopmode(0,1,0); + break; + + default: + return STBWINGRAPH_unprocessed; + } + return 0; +} + +void stbwingraph_main(void) +{ + stbwingraph_Priority(2); + stbwingraph_CreateWindow(1, winproc, NULL, "tt", SIZE_X,SIZE_Y, 0, 1, 0, 0); + stbwingraph_ShowCursor(NULL, 0); + load_fonts(); + initialized = 1; + stbwingraph_MainLoop(loopmode, 0.016f); // 30 fps = 0.33 +} + diff --git a/source/engine/thirdparty/stb/tests/oversample/oversample.dsp b/source/engine/thirdparty/stb/tests/oversample/oversample.dsp new file mode 100644 index 0000000..cc1edc3 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/oversample/oversample.dsp @@ -0,0 +1,97 @@ +# Microsoft Developer Studio Project File - Name="oversample" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=oversample - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "oversample.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "oversample.mak" CFG="oversample - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "oversample - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "oversample - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "oversample - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /WX /GX /O2 /I "c:\sean\prj\stb" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# SUBTRACT LINK32 /map /debug + +!ELSEIF "$(CFG)" == "oversample - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /WX /Gm /GX /Zi /Od /I "c:\sean\prj\stb" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib advapi32.lib winspool.lib comdlg32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /incremental:no /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "oversample - Win32 Release" +# Name "oversample - Win32 Debug" +# Begin Source File + +SOURCE=.\main.c +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/stb/tests/oversample/oversample.dsw b/source/engine/thirdparty/stb/tests/oversample/oversample.dsw new file mode 100644 index 0000000..0f5aa7f --- /dev/null +++ b/source/engine/thirdparty/stb/tests/oversample/oversample.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "oversample"=.\oversample.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/source/engine/thirdparty/stb/tests/oversample/oversample.exe b/source/engine/thirdparty/stb/tests/oversample/oversample.exe new file mode 100644 index 0000000000000000000000000000000000000000..004069318671fd3a7d13d393cd70a9c7b77a2207 GIT binary patch literal 54272 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 fYY$1 +#endif +#include +#include +#include +#include +#include +#endif + +typedef void * stbwingraph_hwnd; +typedef void * stbwingraph_hinstance; + +enum +{ + STBWINGRAPH_unprocessed = -(1 << 24), + STBWINGRAPH_do_not_show, + STBWINGRAPH_winproc_exit, + STBWINGRAPH_winproc_update, + STBWINGRAPH_update_exit, + STBWINGRAPH_update_pause, +}; + +typedef enum +{ + STBWGE__none=0, + + STBWGE_create, + STBWGE_create_postshow, + STBWGE_draw, + STBWGE_destroy, + STBWGE_char, + STBWGE_keydown, + STBWGE_syskeydown, + STBWGE_keyup, + STBWGE_syskeyup, + STBWGE_deactivate, + STBWGE_activate, + STBWGE_size, + + STBWGE_mousemove , + STBWGE_leftdown , STBWGE_leftup , + STBWGE_middledown, STBWGE_middleup, + STBWGE_rightdown , STBWGE_rightup , + STBWGE_mousewheel, +} stbwingraph_event_type; + +typedef struct +{ + stbwingraph_event_type type; + + // for input events (mouse, keyboard) + int mx,my; // mouse x & y + int dx,dy; + int shift, ctrl, alt; + + // for keyboard events + int key; + + // for STBWGE_size: + int width, height; + + // for STBWGE_crate + int did_share_lists; // if true, wglShareLists succeeded + + void *handle; + +} stbwingraph_event; + +typedef int (*stbwingraph_window_proc)(void *data, stbwingraph_event *event); + +extern stbwingraph_hinstance stbwingraph_app; +extern stbwingraph_hwnd stbwingraph_primary_window; +extern int stbwingraph_request_fullscreen; +extern int stbwingraph_request_windowed; + +STB_EXTERN void stbwingraph_ods(char *str, ...); +STB_EXTERN int stbwingraph_MessageBox(stbwingraph_hwnd win, unsigned int type, + char *caption, char *text, ...); +STB_EXTERN int stbwingraph_ChangeResolution(unsigned int w, unsigned int h, + unsigned int bits, int use_message_box); +STB_EXTERN int stbwingraph_SetPixelFormat(stbwingraph_hwnd win, int color_bits, + int alpha_bits, int depth_bits, int stencil_bits, int accum_bits); +STB_EXTERN int stbwingraph_DefineClass(void *hinstance, char *iconname); +STB_EXTERN void stbwingraph_SwapBuffers(void *win); +STB_EXTERN void stbwingraph_Priority(int n); + +STB_EXTERN void stbwingraph_MakeFonts(void *window, int font_base); +STB_EXTERN void stbwingraph_ShowWindow(void *window); +STB_EXTERN void *stbwingraph_CreateWindow(int primary, stbwingraph_window_proc func, void *data, char *text, int width, int height, int fullscreen, int resizeable, int dest_alpha, int stencil); +STB_EXTERN void *stbwingraph_CreateWindowSimple(stbwingraph_window_proc func, int width, int height); +STB_EXTERN void *stbwingraph_CreateWindowSimpleFull(stbwingraph_window_proc func, int fullscreen, int ww, int wh, int fw, int fh); +STB_EXTERN void stbwingraph_DestroyWindow(void *window); +STB_EXTERN void stbwingraph_ShowCursor(void *window, int visible); +STB_EXTERN float stbwingraph_GetTimestep(float minimum_time); +STB_EXTERN void stbwingraph_SetGLWindow(void *win); +typedef int (*stbwingraph_update)(float timestep, int real, int in_client); +STB_EXTERN int stbwingraph_MainLoop(stbwingraph_update func, float mintime); + +#ifdef STB_DEFINE +stbwingraph_hinstance stbwingraph_app; +stbwingraph_hwnd stbwingraph_primary_window; +int stbwingraph_request_fullscreen; +int stbwingraph_request_windowed; + +void stbwingraph_ods(char *str, ...) +{ + char buffer[1024]; + va_list v; + va_start(v,str); + vsprintf(buffer, str, v); + va_end(v); + OutputDebugString(buffer); +} + +int stbwingraph_MessageBox(stbwingraph_hwnd win, unsigned int type, char *caption, char *text, ...) +{ + va_list v; + char buffer[1024]; + va_start(v, text); + vsprintf(buffer, text, v); + va_end(v); + return MessageBox(win, buffer, caption, type); +} + +void stbwingraph_Priority(int n) +{ + int p; + switch (n) { + case -1: p = THREAD_PRIORITY_BELOW_NORMAL; break; + case 0: p = THREAD_PRIORITY_NORMAL; break; + case 1: p = THREAD_PRIORITY_ABOVE_NORMAL; break; + default: + if (n < 0) p = THREAD_PRIORITY_LOWEST; + else p = THREAD_PRIORITY_HIGHEST; + } + SetThreadPriority(GetCurrentThread(), p); +} + +static void stbwingraph_ResetResolution(void) +{ + ChangeDisplaySettings(NULL, 0); +} + +static void stbwingraph_RegisterResetResolution(void) +{ + static int done=0; + if (!done) { + done = 1; + atexit(stbwingraph_ResetResolution); + } +} + +int stbwingraph_ChangeResolution(unsigned int w, unsigned int h, unsigned int bits, int use_message_box) +{ + DEVMODE mode; + int res; + + int i, tries=0; + for (i=0; ; ++i) { + int success = EnumDisplaySettings(NULL, i, &mode); + if (!success) break; + if (mode.dmBitsPerPel == bits && mode.dmPelsWidth == w && mode.dmPelsHeight == h) { + ++tries; + success = ChangeDisplaySettings(&mode, CDS_FULLSCREEN); + if (success == DISP_CHANGE_SUCCESSFUL) { + stbwingraph_RegisterResetResolution(); + return TRUE; + } + break; + } + } + + if (!tries) { + if (use_message_box) + stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "The resolution %d x %d x %d-bits is not supported.", w, h, bits); + return FALSE; + } + + // we tried but failed, so try explicitly doing it without specifying refresh rate + + // Win95 support logic + mode.dmBitsPerPel = bits; + mode.dmPelsWidth = w; + mode.dmPelsHeight = h; + mode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; + + res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN); + + switch (res) { + case DISP_CHANGE_SUCCESSFUL: + stbwingraph_RegisterResetResolution(); + return TRUE; + + case DISP_CHANGE_RESTART: + if (use_message_box) + stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "Please set your desktop to %d-bit color and then try again."); + return FALSE; + + case DISP_CHANGE_FAILED: + if (use_message_box) + stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "The hardware failed to change modes."); + return FALSE; + + case DISP_CHANGE_BADMODE: + if (use_message_box) + stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "The resolution %d x %d x %d-bits is not supported.", w, h, bits); + return FALSE; + + default: + if (use_message_box) + stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "An unknown error prevented a change to a %d x %d x %d-bit display.", w, h, bits); + return FALSE; + } +} + +int stbwingraph_SetPixelFormat(stbwingraph_hwnd win, int color_bits, int alpha_bits, int depth_bits, int stencil_bits, int accum_bits) +{ + HDC dc = GetDC(win); + PIXELFORMATDESCRIPTOR pfd = { sizeof(pfd) }; + int pixel_format; + + pfd.nVersion = 1; + pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER; + pfd.dwLayerMask = PFD_MAIN_PLANE; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = color_bits; + pfd.cAlphaBits = alpha_bits; + pfd.cDepthBits = depth_bits; + pfd.cStencilBits = stencil_bits; + pfd.cAccumBits = accum_bits; + + pixel_format = ChoosePixelFormat(dc, &pfd); + if (!pixel_format) return FALSE; + + if (!DescribePixelFormat(dc, pixel_format, sizeof(PIXELFORMATDESCRIPTOR), &pfd)) + return FALSE; + SetPixelFormat(dc, pixel_format, &pfd); + + return TRUE; +} + +typedef struct +{ + // app data + stbwingraph_window_proc func; + void *data; + // creation parameters + int color, alpha, depth, stencil, accum; + HWND share_window; + HWND window; + // internal data + HGLRC rc; + HDC dc; + int hide_mouse; + int in_client; + int active; + int did_share_lists; + int mx,my; // last mouse positions +} stbwingraph__window; + +static void stbwingraph__inclient(stbwingraph__window *win, int state) +{ + if (state != win->in_client) { + win->in_client = state; + if (win->hide_mouse) + ShowCursor(!state); + } +} + +static void stbwingraph__key(stbwingraph_event *e, int type, int key, stbwingraph__window *z) +{ + e->type = type; + e->key = key; + e->shift = (GetKeyState(VK_SHIFT) < 0); + e->ctrl = (GetKeyState(VK_CONTROL) < 0); + e->alt = (GetKeyState(VK_MENU) < 0); + if (z) { + e->mx = z->mx; + e->my = z->my; + } else { + e->mx = e->my = 0; + } + e->dx = e->dy = 0; +} + +static void stbwingraph__mouse(stbwingraph_event *e, int type, WPARAM wparam, LPARAM lparam, int capture, void *wnd, stbwingraph__window *z) +{ + static int captured = 0; + e->type = type; + e->mx = (short) LOWORD(lparam); + e->my = (short) HIWORD(lparam); + if (!z || z->mx == -(1 << 30)) { + e->dx = e->dy = 0; + } else { + e->dx = e->mx - z->mx; + e->dy = e->my - z->my; + } + e->shift = (wparam & MK_SHIFT) != 0; + e->ctrl = (wparam & MK_CONTROL) != 0; + e->alt = (wparam & MK_ALT) != 0; + if (z) { + z->mx = e->mx; + z->my = e->my; + } + if (capture) { + if (!captured && capture == 1) + SetCapture(wnd); + captured += capture; + if (!captured && capture == -1) + ReleaseCapture(); + if (captured < 0) captured = 0; + } +} + +static void stbwingraph__mousewheel(stbwingraph_event *e, int type, WPARAM wparam, LPARAM lparam, int capture, void *wnd, stbwingraph__window *z) +{ + // lparam seems bogus! + static int captured = 0; + e->type = type; + if (z) { + e->mx = z->mx; + e->my = z->my; + } + e->dx = e->dy = 0; + e->shift = (wparam & MK_SHIFT) != 0; + e->ctrl = (wparam & MK_CONTROL) != 0; + e->alt = (GetKeyState(VK_MENU) < 0); + e->key = ((int) wparam >> 16); +} + +int stbwingraph_force_update; +static int WINAPI stbwingraph_WinProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + int allow_default = TRUE; + stbwingraph_event e = { STBWGE__none }; + // the following line is wrong for 64-bit windows, but VC6 doesn't have GetWindowLongPtr + stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(wnd, GWL_USERDATA); + + switch (msg) { + + case WM_CREATE: + { + LPCREATESTRUCT lpcs = (LPCREATESTRUCT) lparam; + assert(z == NULL); + z = (stbwingraph__window *) lpcs->lpCreateParams; + SetWindowLong(wnd, GWL_USERDATA, (LONG) z); + z->dc = GetDC(wnd); + if (stbwingraph_SetPixelFormat(wnd, z->color, z->alpha, z->depth, z->stencil, z->accum)) { + z->rc = wglCreateContext(z->dc); + if (z->rc) { + e.type = STBWGE_create; + z->did_share_lists = FALSE; + if (z->share_window) { + stbwingraph__window *y = (stbwingraph__window *) GetWindowLong(z->share_window, GWL_USERDATA); + if (wglShareLists(z->rc, y->rc)) + z->did_share_lists = TRUE; + } + wglMakeCurrent(z->dc, z->rc); + return 0; + } + } + return -1; + } + + case WM_PAINT: { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(wnd, &ps); + SelectObject(hdc, GetStockObject(NULL_BRUSH)); + e.type = STBWGE_draw; + e.handle = wnd; + z->func(z->data, &e); + EndPaint(wnd, &ps); + return 0; + } + + case WM_DESTROY: + e.type = STBWGE_destroy; + e.handle = wnd; + if (z && z->func) + z->func(z->data, &e); + wglMakeCurrent(NULL, NULL) ; + if (z) { + if (z->rc) wglDeleteContext(z->rc); + z->dc = 0; + z->rc = 0; + } + if (wnd == stbwingraph_primary_window) + PostQuitMessage (0); + return 0; + + case WM_CHAR: stbwingraph__key(&e, STBWGE_char , wparam, z); break; + case WM_KEYDOWN: stbwingraph__key(&e, STBWGE_keydown, wparam, z); break; + case WM_KEYUP: stbwingraph__key(&e, STBWGE_keyup , wparam, z); break; + + case WM_NCMOUSEMOVE: stbwingraph__inclient(z,0); break; + case WM_MOUSEMOVE: stbwingraph__inclient(z,1); stbwingraph__mouse(&e, STBWGE_mousemove, wparam, lparam,0,wnd, z); break; + case WM_LBUTTONDOWN: stbwingraph__mouse(&e, STBWGE_leftdown, wparam, lparam,1,wnd, z); break; + case WM_MBUTTONDOWN: stbwingraph__mouse(&e, STBWGE_middledown, wparam, lparam,1,wnd, z); break; + case WM_RBUTTONDOWN: stbwingraph__mouse(&e, STBWGE_rightdown, wparam, lparam,1,wnd, z); break; + case WM_LBUTTONUP: stbwingraph__mouse(&e, STBWGE_leftup, wparam, lparam,-1,wnd, z); break; + case WM_MBUTTONUP: stbwingraph__mouse(&e, STBWGE_middleup, wparam, lparam,-1,wnd, z); break; + case WM_RBUTTONUP: stbwingraph__mouse(&e, STBWGE_rightup, wparam, lparam,-1,wnd, z); break; + case WM_MOUSEWHEEL: stbwingraph__mousewheel(&e, STBWGE_mousewheel, wparam, lparam,0,wnd, z); break; + + case WM_ACTIVATE: + allow_default = FALSE; + if (LOWORD(wparam)==WA_INACTIVE ) { + wglMakeCurrent(z->dc, NULL); + e.type = STBWGE_deactivate; + z->active = FALSE; + } else { + wglMakeCurrent(z->dc, z->rc); + e.type = STBWGE_activate; + z->active = TRUE; + } + e.handle = wnd; + z->func(z->data, &e); + return 0; + + case WM_SIZE: { + RECT rect; + allow_default = FALSE; + GetClientRect(wnd, &rect); + e.type = STBWGE_size; + e.width = rect.right; + e.height = rect.bottom; + e.handle = wnd; + z->func(z->data, &e); + return 0; + } + + default: + return DefWindowProc (wnd, msg, wparam, lparam); + } + + if (e.type != STBWGE__none) { + int n; + e.handle = wnd; + n = z->func(z->data, &e); + if (n == STBWINGRAPH_winproc_exit) { + PostQuitMessage(0); + n = 0; + } + if (n == STBWINGRAPH_winproc_update) { + stbwingraph_force_update = TRUE; + return 1; + } + if (n != STBWINGRAPH_unprocessed) + return n; + } + return DefWindowProc (wnd, msg, wparam, lparam); +} + +int stbwingraph_DefineClass(HINSTANCE hInstance, char *iconname) +{ + WNDCLASSEX wndclass; + + stbwingraph_app = hInstance; + + wndclass.cbSize = sizeof(wndclass); + wndclass.style = CS_OWNDC; + wndclass.lpfnWndProc = (WNDPROC) stbwingraph_WinProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = hInstance; + wndclass.hIcon = LoadIcon(hInstance, iconname); + wndclass.hCursor = LoadCursor(NULL,IDC_ARROW); + wndclass.hbrBackground = GetStockObject(NULL_BRUSH); + wndclass.lpszMenuName = "zwingraph"; + wndclass.lpszClassName = "zwingraph"; + wndclass.hIconSm = NULL; + + if (!RegisterClassEx(&wndclass)) + return FALSE; + return TRUE; +} + +void stbwingraph_ShowWindow(void *window) +{ + stbwingraph_event e = { STBWGE_create_postshow }; + stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(window, GWL_USERDATA); + ShowWindow(window, SW_SHOWNORMAL); + InvalidateRect(window, NULL, TRUE); + UpdateWindow(window); + e.handle = window; + z->func(z->data, &e); +} + +void *stbwingraph_CreateWindow(int primary, stbwingraph_window_proc func, void *data, char *text, + int width, int height, int fullscreen, int resizeable, int dest_alpha, int stencil) +{ + HWND win; + DWORD dwstyle; + stbwingraph__window *z = (stbwingraph__window *) malloc(sizeof(*z)); + + if (z == NULL) return NULL; + memset(z, 0, sizeof(*z)); + z->color = 24; + z->depth = 24; + z->alpha = dest_alpha; + z->stencil = stencil; + z->func = func; + z->data = data; + z->mx = -(1 << 30); + z->my = 0; + + if (primary) { + if (stbwingraph_request_windowed) + fullscreen = FALSE; + else if (stbwingraph_request_fullscreen) + fullscreen = TRUE; + } + + if (fullscreen) { + #ifdef STB_SIMPLE + stbwingraph_ChangeResolution(width, height, 32, 1); + #else + if (!stbwingraph_ChangeResolution(width, height, 32, 0)) + return NULL; + #endif + dwstyle = WS_POPUP | WS_CLIPSIBLINGS; + } else { + RECT rect; + dwstyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; + if (resizeable) + dwstyle |= WS_SIZEBOX | WS_MAXIMIZEBOX; + rect.top = 0; + rect.left = 0; + rect.right = width; + rect.bottom = height; + AdjustWindowRect(&rect, dwstyle, FALSE); + width = rect.right - rect.left; + height = rect.bottom - rect.top; + } + + win = CreateWindow("zwingraph", text ? text : "sample", dwstyle, + CW_USEDEFAULT,0, width, height, + NULL, NULL, stbwingraph_app, z); + + if (win == NULL) return win; + + if (primary) { + if (stbwingraph_primary_window) + stbwingraph_DestroyWindow(stbwingraph_primary_window); + stbwingraph_primary_window = win; + } + + { + stbwingraph_event e = { STBWGE_create }; + stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(win, GWL_USERDATA); + z->window = win; + e.did_share_lists = z->did_share_lists; + e.handle = win; + if (z->func(z->data, &e) != STBWINGRAPH_do_not_show) + stbwingraph_ShowWindow(win); + } + + return win; +} + +void *stbwingraph_CreateWindowSimple(stbwingraph_window_proc func, int width, int height) +{ + int fullscreen = 0; + #ifndef _DEBUG + if (width == 640 && height == 480) fullscreen = 1; + if (width == 800 && height == 600) fullscreen = 1; + if (width == 1024 && height == 768) fullscreen = 1; + if (width == 1280 && height == 1024) fullscreen = 1; + if (width == 1600 && height == 1200) fullscreen = 1; + //@TODO: widescreen widths + #endif + return stbwingraph_CreateWindow(1, func, NULL, NULL, width, height, fullscreen, 1, 0, 0); +} + +void *stbwingraph_CreateWindowSimpleFull(stbwingraph_window_proc func, int fullscreen, int ww, int wh, int fw, int fh) +{ + if (fullscreen == -1) { + #ifdef _DEBUG + fullscreen = 0; + #else + fullscreen = 1; + #endif + } + + if (fullscreen) { + if (fw) ww = fw; + if (fh) wh = fh; + } + return stbwingraph_CreateWindow(1, func, NULL, NULL, ww, wh, fullscreen, 1, 0, 0); +} + +void stbwingraph_DestroyWindow(void *window) +{ + stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(window, GWL_USERDATA); + DestroyWindow(window); + free(z); + if (stbwingraph_primary_window == window) + stbwingraph_primary_window = NULL; +} + +void stbwingraph_ShowCursor(void *window, int visible) +{ + int hide; + stbwingraph__window *win; + if (!window) + window = stbwingraph_primary_window; + win = (stbwingraph__window *) GetWindowLong((HWND) window, GWL_USERDATA); + hide = !visible; + if (hide != win->hide_mouse) { + win->hide_mouse = hide; + if (!hide) + ShowCursor(TRUE); + else if (win->in_client) + ShowCursor(FALSE); + } +} + +float stbwingraph_GetTimestep(float minimum_time) +{ + float elapsedTime; + double thisTime; + static double lastTime = -1; + + if (lastTime == -1) + lastTime = timeGetTime() / 1000.0 - minimum_time; + + for(;;) { + thisTime = timeGetTime() / 1000.0; + elapsedTime = (float) (thisTime - lastTime); + if (elapsedTime >= minimum_time) { + lastTime = thisTime; + return elapsedTime; + } + #if 1 + Sleep(2); + #endif + } +} + +void stbwingraph_SetGLWindow(void *win) +{ + stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(win, GWL_USERDATA); + if (z) + wglMakeCurrent(z->dc, z->rc); +} + +void stbwingraph_MakeFonts(void *window, int font_base) +{ + wglUseFontBitmaps(GetDC(window ? window : stbwingraph_primary_window), 0, 256, font_base); +} + +// returns 1 if WM_QUIT, 0 if 'func' returned 0 +int stbwingraph_MainLoop(stbwingraph_update func, float mintime) +{ + int needs_drawing = FALSE; + MSG msg; + + int is_animating = TRUE; + if (mintime <= 0) mintime = 0.01f; + + for(;;) { + int n; + + is_animating = TRUE; + // wait for a message if: (a) we're animating and there's already a message + // or (b) we're not animating + if (!is_animating || PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { + stbwingraph_force_update = FALSE; + if (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } else { + return 1; // WM_QUIT + } + + // only force a draw for certain messages... + // if I don't do this, we peg at 50% for some reason... must + // be a bug somewhere, because we peg at 100% when rendering... + // very weird... looks like NVIDIA is pumping some messages + // through our pipeline? well, ok, I guess if we can get + // non-user-generated messages we have to do this + if (!stbwingraph_force_update) { + switch (msg.message) { + case WM_MOUSEMOVE: + case WM_NCMOUSEMOVE: + break; + case WM_CHAR: + case WM_KEYDOWN: + case WM_KEYUP: + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_TIMER: + case WM_SIZE: + case WM_ACTIVATE: + needs_drawing = TRUE; + break; + } + } else + needs_drawing = TRUE; + } + + // if another message, process that first + // @TODO: i don't think this is working, because I can't key ahead + // in the SVT demo app + if (PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE)) + continue; + + // and now call update + if (needs_drawing || is_animating) { + int real=1, in_client=1; + if (stbwingraph_primary_window) { + stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(stbwingraph_primary_window, GWL_USERDATA); + if (z && !z->active) { + real = 0; + } + if (z) + in_client = z->in_client; + } + + if (stbwingraph_primary_window) + stbwingraph_SetGLWindow(stbwingraph_primary_window); + n = func(stbwingraph_GetTimestep(mintime), real, in_client); + if (n == STBWINGRAPH_update_exit) + return 0; // update_quit + + is_animating = (n != STBWINGRAPH_update_pause); + + needs_drawing = FALSE; + } + } +} + +void stbwingraph_SwapBuffers(void *win) +{ + stbwingraph__window *z; + if (win == NULL) win = stbwingraph_primary_window; + z = (stbwingraph__window *) GetWindowLong(win, GWL_USERDATA); + if (z && z->dc) + SwapBuffers(z->dc); +} +#endif + +#ifdef STB_WINMAIN +void stbwingraph_main(void); + +char *stb_wingraph_commandline; + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + { + char buffer[1024]; + // add spaces to either side of the string + buffer[0] = ' '; + strcpy(buffer+1, lpCmdLine); + strcat(buffer, " "); + if (strstr(buffer, " -reset ")) { + ChangeDisplaySettings(NULL, 0); + exit(0); + } + if (strstr(buffer, " -window ") || strstr(buffer, " -windowed ")) + stbwingraph_request_windowed = TRUE; + else if (strstr(buffer, " -full ") || strstr(buffer, " -fullscreen ")) + stbwingraph_request_fullscreen = TRUE; + } + stb_wingraph_commandline = lpCmdLine; + + stbwingraph_DefineClass(hInstance, "appicon"); + stbwingraph_main(); + + return 0; +} +#endif + +#undef STB_EXTERN +#ifdef STB_WINGRAPH_DISABLE_DEFINE_AT_END +#undef STB_DEFINE +#endif + +#endif // INCLUDE_STB_WINGRAPH_H diff --git a/source/engine/thirdparty/stb/tests/pbm/basi0g16.pgm b/source/engine/thirdparty/stb/tests/pbm/basi0g16.pgm new file mode 100644 index 0000000000000000000000000000000000000000..72412431a9513fbced9f9bac62f912d2edf89aea GIT binary patch literal 2064 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 P(p6*|{G1ovCTHLW)Hrd7 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pbm/cdun2c08.ppm b/source/engine/thirdparty/stb/tests/pbm/cdun2c08.ppm new file mode 100644 index 0000000000000000000000000000000000000000..2d7202b89dafb02c9a57f18997a48916bec26dcd GIT binary patch literal 3086 zcmb7_+p(iC3_$&zKRVc+TKerUZ2WdiTL+V0$EW((@>eIz*a;>yJq-#O-HD|wzVOV&L&3Ew& zWA>cG6;9@NzaM|&2xs2MGQI;c=yy6fBZeK?Y>QYPLrNQ&Qh2BEOrhV(D}`ZuQZ7zf z35P0(Ibq!LAs5(Uirz8T0v{JBt-T6M=)%WrEG%ee)725;&7?Z5B z|Ht*YWKv*pj)L;d1${>zn>j+>o#8je=iwZ`ue^lH{y-Rr2VIGc7$~o(Ut5u{)RFPU zo4kI!Kfw18X@4W+i~a1OBCCKoj#hJ$WaH1>BIiqn7GZ2Rly7edt1_u8=`&p=(dJ7` zD&KAP{W&+@w3rJE9~B>eUY?5e7v}co#=94UA>&)cTe~yV{>$T#w(nwmdsdG10ZO&e ztmpMQi7Wn(i)B_MaLY)cYvm)6M{sER_hvWbN literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pbm/comment.pgm b/source/engine/thirdparty/stb/tests/pbm/comment.pgm new file mode 100644 index 0000000000000000000000000000000000000000..aa9dc71887f79ac4bd39c67cf3af312b332d3cea GIT binary patch literal 1083 zcmWGAl$}0GrtzKmf9z*76`b(ESavdnOQo z@q00Mk=Q%fE>zG41e1q3kjX(^A-Y>@r5mWQQtWPk4lf-^t>vY*!Spge|d LH^^QXptU>zF1GZB literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pbm/ctfn0g04.pgm b/source/engine/thirdparty/stb/tests/pbm/ctfn0g04.pgm new file mode 100644 index 0000000000000000000000000000000000000000..284f8708977d076a30ee1ac63577121f46eac71a GIT binary patch literal 1038 zcmWGA<>E3nQZP2+GBP#g`i}&Rfxra_LV+L+2+Dx~N%}vn hf_width-1) x = (float) hf_width-1; + if (y < 0) y = 0; + if (y > hf_height-1) y = (float) hf_height-1; + ix = (int) x; x -= ix; + iy = (int) y; y -= iy; + h00 = hf[(iy+0)*hf_height+(ix+0)]; + h10 = hf[(iy+0)*hf_height+(ix+1)]; + h01 = hf[(iy+1)*hf_height+(ix+0)]; + h11 = hf[(iy+1)*hf_height+(ix+1)]; + h0 = stb_lerp(y, h00, h01); + h1 = stb_lerp(y, h10, h11); + return stb_lerp(x, h0, h1); +} + +void stbpg_tick(float dt) +{ + int i=0,j=0; + int step = 1; + + glUseProgram(0); + + glClearColor(0.6f,0.7f,1.0f,1.0f); + glClearDepth(1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glDepthFunc(GL_LESS); + glEnable(GL_DEPTH_TEST); +#if 1 + glEnable(GL_CULL_FACE); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(60.0, 1920/1080.0f, 0.02f, 8000.0f); + //glOrtho(-8,8,-6,6, -100, 100); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glRotatef(-90, 1,0,0); // z-up + + { + float x,y; + stbpg_get_mouselook(&x,&y); + glRotatef(-y, 1,0,0); + glRotatef(-x, 0,0,1); + } + + { + static float cam_x = 1000; + static float cam_y = 1000; + static float cam_z = 700; + float x=0,y=0; + stbpg_get_keymove(&x,&y); + cam_x += x*dt*5.0f; + cam_y += y*dt*5.0f; + glTranslatef(-cam_x, -cam_y, -cam_z); + if (cam_x >= 0 && cam_x < hf_width && cam_y >= 0 && cam_y < hf_height) + cam_z = get_height(cam_x, cam_y) + 1.65f; // average eye height in meters + } + + for (j=501; j+1 < 1500+0*hf_height; j += step) { + glBegin(GL_QUAD_STRIP); + for (i=501; i < 1500+0*hf_width; i += step) { + static int flip=0; + if (flip) + glColor3f(0.5,0.5,0.5); + else + glColor3f(0.4f,0.4f,0.4f); + flip = !flip; + glVertex3f((float) i, (float) j+step,hf[(j+step)*hf_width+i]); + glVertex3f((float) i, (float) j ,hf[ j *hf_width+i]); + } + glEnd(); + } + + glBegin(GL_LINES); + glColor3f(1,0,0); glVertex3f(10,0,0); glVertex3f(0,0,0); + glColor3f(0,1,0); glVertex3f(0,10,0); glVertex3f(0,0,0); + glColor3f(0,0,1); glVertex3f(0,0,10); glVertex3f(0,0,0); + glEnd(); +#endif +} + +void stbpg_main(int argc, char **argv) +{ + int i,j; + + #if 0 + int w,h,c; + unsigned short *data = stbi_load_16("c:/x/ned_1m/test2.png", &w, &h, &c, 1); + stb_filewrite("c:/x/ned_1m/x73_y428_10012_10012.bin", data, w*h*2); + #else + unsigned short *data = stb_file("c:/x/ned_1m/x73_y428_10012_10012.bin", NULL); + int w=10012, h = 10012; + #endif + + hf = malloc(hf_width * hf_height * 4); + for (j=0; j < hf_height; ++j) + for (i=0; i < hf_width; ++i) + hf[j*hf_width+i] = data[j*w+i] / 32.0f; + + stbpg_gl_compat_version(1,1); + stbpg_windowed("terrain_edit", 1920, 1080); + stbpg_run(); + + return; +} diff --git a/source/engine/thirdparty/stb/tests/pngsuite/16bit/basi0g16.png b/source/engine/thirdparty/stb/tests/pngsuite/16bit/basi0g16.png new file mode 100644 index 0000000000000000000000000000000000000000..a9f28165efd4a6eaab890f3f46eaab0812614046 GIT binary patch literal 299 zcmV+`0o4A9P)kMw|y20kXn4P1fk&um!5HR4i(o|AwZ6~LzrfePT$2C@a*GO>pzD`W+JMY+o{~OMQc&lm=3iqj(vZoAg56v)&@pF xpiv;U26q|-VrxJe1@ZNXf_(7)M8WX7;00Eg?uiut|NZ~~002ovPDHLkV1i{YdSUv}5QRU_7&y#fp@D&gr9M`8f1y92FoiBVSa`IsurPtS ze{e7d3sdH>SD>hoE4z_nx$z0??7UgMea|aJNhAr@Zvg8Z*gUmKePu;z>y8jWkvlsq z1W-(qZPlsA;>PQ-xbb>e=+HyjR`72RpJL@9duyYiw{L8ondoV1Dstk)`yNe2E|Tn) zQn&|5Z4$O-o8S$zjgb_v!ImC3RAHrGc4#Vv!)L8;XfO3ToGLXjk#F*79m4!RxgIe_tfnTuJcqEWy)4g2yKb%DDu0za_YxN$~Sgf+CmT>X!ubOoF_W z03=B5#`sg62L3e6{2RJyFI>13c>MtF{pl)#xkJ@t(o7&`wRIOmYhI*_#{U5rtXn@HNfb7oz6Bu5!i$}Tx-z(a!?=gYPiFWah6BvhR7Y~@g_(Z#S%LK+P h+Qpp-{6C^y{2ine-m~8mME3vy002ovPDHLkV1lO|5~BbB literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/16bit/basi4a16.png b/source/engine/thirdparty/stb/tests/pngsuite/16bit/basi4a16.png new file mode 100644 index 0000000000000000000000000000000000000000..51192e731106e77cec52a3acda3d0c3cd54a16d5 GIT binary patch literal 2855 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 FV1na*GhqM# literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/16bit/basi6a16.png b/source/engine/thirdparty/stb/tests/pngsuite/16bit/basi6a16.png new file mode 100644 index 0000000000000000000000000000000000000000..4181533ad81b739fb7b4f3b024f632cd7baa85c5 GIT binary patch literal 4180 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 N1)i>cF6*2UngAzTID!BG literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/16bit/basn2c16.png b/source/engine/thirdparty/stb/tests/pngsuite/16bit/basn2c16.png new file mode 100644 index 0000000000000000000000000000000000000000..50c1cb91a0171e9f34991b079fe932f5f0bb16d6 GIT binary patch literal 302 zcmV+}0nz@6P)NTxus~-YE?|ew94KIo9tHTv?hhRR zwrA%J^h9UxCeRmyPjW#d?oxNFL9(uFDZ1gBle+D$rIj`J+5;}Xa zfF63WfGT3xy1iYa$zve>zUI)9x>;M1&07*qoM6N<$g8PGj A5dZ)H literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/16bit/basn4a16.png b/source/engine/thirdparty/stb/tests/pngsuite/16bit/basn4a16.png new file mode 100644 index 0000000000000000000000000000000000000000..8243644d0743ebd1fdacb7923069fefbb01dab0e GIT binary patch literal 2206 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 AY5)KL literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/16bit/bgai4a16.png b/source/engine/thirdparty/stb/tests/pngsuite/16bit/bgai4a16.png new file mode 100644 index 0000000000000000000000000000000000000000..51192e731106e77cec52a3acda3d0c3cd54a16d5 GIT binary patch literal 2855 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 FV1na*GhqM# literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/16bit/bgan6a16.png b/source/engine/thirdparty/stb/tests/pngsuite/16bit/bgan6a16.png new file mode 100644 index 0000000000000000000000000000000000000000..984a99525f5246cbc5d7083dd79006c7efa0ab0b GIT binary patch literal 3435 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 AY5)KL literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/16bit/bggn4a16.png b/source/engine/thirdparty/stb/tests/pngsuite/16bit/bggn4a16.png new file mode 100644 index 0000000000000000000000000000000000000000..13fd85ba193369dbd84a0dc07f73d3340485fa6a GIT binary patch literal 2220 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 TFaLS`e*&EE@`;w61 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/16bit/oi1n0g16.png b/source/engine/thirdparty/stb/tests/pngsuite/16bit/oi1n0g16.png new file mode 100644 index 0000000000000000000000000000000000000000..e7c82f78eb954be5be51c35bd287e00018c69d82 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^3Lq>1BpBEle`W(ImUKs7M+U~W1%@xC#RK_qo-U3d z6?3j$GUPg7z~dZf8U4Qg*qsHF4`@^yUDaY)e6!!)qG9=taAD!(1y)T7vK%uR?lygM zIK}cp><*ign#1-5wiApPcd>47oWOZOH-mqPPlA0u`y2l+k{0Ld8N_aQ--utQJ^wn$ N1)i>cF6*2UngAzTID!BG literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/16bit/oi1n2c16.png b/source/engine/thirdparty/stb/tests/pngsuite/16bit/oi1n2c16.png new file mode 100644 index 0000000000000000000000000000000000000000..50c1cb91a0171e9f34991b079fe932f5f0bb16d6 GIT binary patch literal 302 zcmV+}0nz@6P)NTxus~-YE?|ew94KIo9tHTv?hhRR zwrA%J^h9UxCeRmyPjW#d?oxNFL9(uFDZ1gBle+D$rIj`J+5;}Xa zfF63WfGT3xy1iYa$zve>zUI)9x>;M1&07*qoM6N<$g8PGj A5dZ)H literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/16bit/oi2n0g16.png b/source/engine/thirdparty/stb/tests/pngsuite/16bit/oi2n0g16.png new file mode 100644 index 0000000000000000000000000000000000000000..14d64c583db347908d4e107b49bdaf88e3857b8d GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^3Lq>1BpBEle`W(ImUKs7M+U~W1%@xC#RK^co-U3d z6?3j$GUPg7z~dZf8U4Qg*qsHF4`@^yUDaY)e6!!)qG9=taAD!(1y)T7vK%uR?lygM zIK}cp><*ign#1-5wiAr3HXJYpnk5G^i*Xn0_QnaE7j!fDm-r;u=d-`@{~~E|zMets XhWCy5O;z!=K$Q%hu6{1-oD!MJY5_^ zD&}0Bc95&tK)_}B1IPN)b=&{0{WeXGjd@a=TH53@RT6EkPi(I(=ofHeu+x7q>wQX5 zah-&%x^euuN1vn0w==yySE5m{$awnax1S1PFES>4-qaZX;>{9*dzc{lpeF5*>&z{m79Ma`yb}T3cx+N9l7QKY_b21tUy>hHS zj=$rVW!RSfocD`M`g^qt@_T0=(DrKPRwV_Ejec_{h{I9JdPiZ{w#YI8t;GR^e@i0d|ndJLIzJ) KKbLh*2~7a%CxW>E literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/16bit/oi4n0g16.png b/source/engine/thirdparty/stb/tests/pngsuite/16bit/oi4n0g16.png new file mode 100644 index 0000000000000000000000000000000000000000..69e73ede311c4a846a8f818330708658a1e0fe77 GIT binary patch literal 203 zcmeAS@N?(olHy`uVBq!ia0vp^3Lq>1BpBEle`W(ImUKs7M+U~W1%@xC#RK{Bo-U3d z6?3j$GUPg7z~dZf8U4Qg*qsHF4`@^yUDaY)e6!!)qCI!=eVEdQwQX5 zah-&%x^euuN1vn0w==yySE5m{$awnax1S1PFES>4-qaZX;>{Y_l!4PL3!@ULbQoiNtWY z=b;I!`Z?mk+pi~%T4uHFkQ%qs0PKuo=U#gjgXq@dQ#YX;uR zyDEzTqN3i8Ri+t9;=Io`4iKjuJ*jdoASoRATB86`e`o#L4-kt!*XOIIkPy|$Z!G18 zk=$_R1cm^jDHwGE*;OPH+pecw0=8F|cWD7)9r{V(>YZh7s}#xX@Y-V@a8$R# zz7Nzll=T^a-D91a7GNOU#!3RDRppIxfH+g4`7VUA7;JpD{z%Gl^hz`rR!C-7WjBz2w=IKp0oL zbO>k?lt*0#RAuh<9{?e*sV()uX|32M9JssnY{osHpuWUu5cv8Oo7WFKnf5e90$&!U zD|&#%lN#4PfLz=wKl=n%AbF;bH~a_iR2$1}rLn7htMc}f7XqbjYTgrI+dp1QF98{q zx~>x7qmaPfO~8-eOc`>46CZ0QegN!tCr*3|kka8h&MF@x8JDN?qySsUVf_vKy0476 z9%wM?M@--UJ8OATkeUmSDcRFHm6nLa%*FSV76{aF1);#sVqI_>Kql)G-?4S=NapNh zVg_*djmhC=Ag9J7dkM%L^-MbokZB}6AQ$H>b5|DQ7>`=5gAqs{oMFj;fyD8u{}=gK zMu71K5`$&sNDOfBa_Q}rTV!=k^EBM^c>q~R=6AduZXuc8qS|W*kl#6Wnxp&)iHoCb zXd@uZXIuUPD6CbzZUDK2+Pw*)4M@m|&Mp?KwVGjB$JzxnI1su>k|SW z;K-2xP8~qD0I!0IuDeLqZiu^T&h0G>BMn)Ym=lSRkOKQt;)MxF$jp%`={{CrJA+}Q z0NGudCGHuG7?!!Lm%ady&A}>J%1A4M0{%q_gjthe+%dX`L1TnYm#8ctAc9 z@}pa>>=HU6*)gvdIs;@`N_gX0SxCrj<`CfL)P#i0DJdZ9W{Ka!FtV*beIQ<%iDapc zzwQpuzunh$8PLB|-d+oAxGoVK2OOgUZCC)=Ex%&=XT>2Qmra%!>Ggb-Hfk2fkY^() zKsHtKl(~0px+MSwNPBxOXxkqksf(TP*$R+5i!_m(4oOIRA3ry`IR({7L&)Y`5g+dy zjA3LNm(5d>Pa+|cbrc|HP1XT<>~@^s)UP}#S04qW zJ7f!3yE~lF1UCm1Ag?RrHNta!r8Eu0$jtfWu@_WGT8_ru{1AB9l%}=?3U3!0mB7Z| zi!2)ef4yhpK|nM&HhOB*ztD(3v5YRNcL0EJE literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/16bit/tbbn2c16.png b/source/engine/thirdparty/stb/tests/pngsuite/16bit/tbbn2c16.png new file mode 100644 index 0000000000000000000000000000000000000000..dd3168e5c864a81f3284856f2b00fd1e52eb312e GIT binary patch literal 2041 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xc9n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xc9n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..b11c2a7b4049475d967a8cc76b93ef1039684a3a GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE2_&^8vlamc;6;z7cmE{-7; zb9B#amdK II;Vst0B-g)L;wH) literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xd0n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xd0n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..2f001610a85a662d8baa0c1f02c06adc02cfa20f GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^3Si6xB+q0lO9E0X>5jgR42*3H3|~x(2l72UT^vIy z=DfXnkdwiHhsmM!&BVXki-fIRw7)F;aqgk^7G4(X1}>2wG7H{5sr9LFXk_B|YL`FM q!Y-omVL^$7@=Axs-38@~4=^@u{29mZaNZkeCWEJ|pUXO@geCyFeJ}z5 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xd3n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xd3n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..9e4a3ff7accf4453ddcd58318f7048b326b44ad2 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnP1SGpp+}Q-ASkfJR9T^zg78t&m77yfmc)B=- zRLpsM^&lsM0S}Wy>zj#xw-*UpyJ&w|_~YC|?Jc}4)(u=DKV%lXeNyXF;n2v$@6|4U rsD)ibzj#xw-*UpyJ&w|_~YC|?Jc}4)(u=DKV%lXeNyXF;n2v$@6|4U rsD)ibc;6;z8n`u6{1- HoD!M<9fb>g literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xhdn0g08.png b/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xhdn0g08.png new file mode 100644 index 0000000000000000000000000000000000000000..fcb8737fa2505b43955e995d95c3d6972b85fe32 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk@Bp957Lw$i1OS+@4BLidG0>c;6;(>fePZ!6K ziaE(C2`UUC949vOu(I~>b7=nfE^nVuX&@tFa7u7Oddi~87#=p(p8gM~{{27yqy1T- hp#)Io!PL|g7KVVQ{|ZeX-G~HP;_2$=vd$@?2>>eWDsTV* literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xlfn0g04.png b/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xlfn0g04.png new file mode 100644 index 0000000000000000000000000000000000000000..d9ec53ed94b34f50eeabf3029465e1106725ea2a GIT binary patch literal 145 zcmeAS@N?(llHy`uVBqrfa0vp^3Lwk^Bp4c;6;z7cmE{-7; zb9B#azopr048WOXaE2J literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xs2n0g01.png b/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xs2n0g01.png new file mode 100644 index 0000000000000000000000000000000000000000..b8147f2a84b861b559fd52fc0ded6971debdc6f1 GIT binary patch literal 164 zcmeAS^mFIslHy`uVBq!ia0vp^3Lwk~Bp9L@-6Me%OS+@4BLidG0>c;6;z7cmE{-7; zb9B#azopr0C@{E=>Px# literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xs4n0g01.png b/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xs4n0g01.png new file mode 100644 index 0000000000000000000000000000000000000000..45237a1d294f7432c06ae45769727d9745250221 GIT binary patch literal 164 zcmeAS@Jr|AlHy`uVBq!ia0vp^3Lwk~Bp9L@-6Me%OS+@4BLidG0>c;6;z7cmE{-7; zb9B#azopr0E~z=2mk;8 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xs7n0g01.png b/source/engine/thirdparty/stb/tests/pngsuite/corrupt/xs7n0g01.png new file mode 100644 index 0000000000000000000000000000000000000000..3f307f14ea5ed37b9f74b896a98ee4a5d2c9e111 GIT binary patch literal 164 zcmeAS@N?(oQs81>VBq!ia0vp^3Lwk~Bp9L@-6Me%OS+@4BLidG0>c;6;z7cmE{-7; zb9B#azopr0DK%Y?f?J) literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_basi0g01.png b/source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_basi0g01.png new file mode 100644 index 0000000000000000000000000000000000000000..33db08c5b3e519239a589541910760cb3081189c GIT binary patch literal 384 zcmeAS@N?(olHy`uU|?}hck&EiP+-&9QJx0m^Ll!?1OaIU5awWGU|?ja^-2PASkfJR z9T^xl_H+M9WCijWi-X*q7}lMWc?nXP>=ES4z)+>ez|hdb!0-zw)bN6Vq11qZ;Z*_y zgVhWM2JwP9y8>;167M`+97B$VF!Hq+a6JA$|60bPnLHYcXXxInEM7AuO08Y0=zoXI z`QUqVvp?>s5KlfLpy}D;f8>YL!?T`FV#17*x6cdf@{}$&b~3O$pyhMu-m@c)^Z&2} zSS?B2V3$%MAezZI#aknLLek{3GnhJ7v@s<1X0*L(bY%45$xu#ZjN$!qcgve2lX`XK zE1q6iZ+eV<%0wk!g@)_g*XnE5t}}4BD`Di2)XKA>OpU+zJEQS7!3M61vvcnHtDh5c zcwO{pKWBhi%*&NUNhyb}p4z_k;kN1qzZlgE@A}_$?X3`Bu3fmB>1w{0_JLWe);P>w cef{~iBj0TpdBQeq1p1u8)78&qol`;+0Lqq-JOBUy literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_basi0g02.png b/source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_basi0g02.png new file mode 100644 index 0000000000000000000000000000000000000000..484f46d248d08f3ed1f67e41dfc08af88e6cc83f GIT binary patch literal 302 zcmeAS@N?(olHy`uU|?}hck&EiP+-&9QJx0m^Ll!?1OaIU5awWGU|?ja^-2PASkfJR z9T^xl_H+M9WCijWi-X*q7}lMWc?nXP>=ES4z)+>ez|hdb!0-zw)bN6Vq11qZ;Z*_y zgVhWM2JwP9y8>;15_3FV97EoQFmfGG;Mn8;?WEmJtu&qEIUXg_CK5@{U#3bjERjgg z+E%>jsD)C?gj4Eb;mdDK7gSAjEe?A7m{XwMiH&>BzYQ&{*K+^4HS3&M#Insu?48;? zi#;v}AF6Wgt2S87{zvVTe`9^mZ@D+s24Ce~FvzYDzBlXT^U16NWq1CUFs|m#vWq|P us%h4FaR+(lT@1e@F0o!XK0z;`^x*lu%y-rLu%tWs zIx;Y9?C1WI$O_~$76-XIF|0c$^Ae;o*(1o8fuTx`fuW&=f#DZWsNn?zL#Y7+!>a@a z2CEqi4B`cIb_Lo1CFXj%IEK6pzR7n$f#dQ2`dP6S?lZP;)+^+f>T_bBI-{fWyMJDl z)b+V%ENmv7Fn6#tHi|fRxsr8?EKkMmtxh6mBXu9K)E_yRzI(x!^yfUsHO&nr^xtMY z+InTL#C4uq=>~@d9!58;FMOHsa=~i8d;ffy&;AQt7-cih;eFH5ADkRD){j)={1Z03 s(2?_H_{Z~*MXjQ(fT`IvJvIYGeoSDJetfxE1n5u(Pgg&ebxsLQ05`{JsQ>@~ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_bgwn6a08.png b/source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_bgwn6a08.png new file mode 100644 index 0000000000000000000000000000000000000000..7d0ac50be96a9cd46906abf146d02288ebd44cd8 GIT binary patch literal 2573 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_bgyn6a16.png b/source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_bgyn6a16.png new file mode 100644 index 0000000000000000000000000000000000000000..9046336a89ee08db49c7204c0279f74d239b86b9 GIT binary patch literal 2267 zcmV<12qgE3P)Px#AY({UO#lFTCIA3{ga82g z0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBU)Nl8RORLz&qN^)%%hnJfF+Ud@=WoouD zD=kg4L@KOc7ZOGPtf+`0u%e(zISC?YAk?IpG-%eqNsT=P51~`&5T5CG`6(X{`#XU5 zRjy_a&*gou^;_!}`B!{;dwZo<1#bsWhn@~fhi-6paCLBYaCERkR|i`MYX?h*&i8*G z6@h3pDp4JVAp%1Xf*=e+00zJh8uWn=RPcfadO!g;xWEYx=mHy9!2*2MzXiMq=m=!9 zS;=N#3X(7ZaTtRbL_voLgdqq4=m!mapn?Y!aDx-;V&KuXqSF>F(uMTE=nm<5IqNFq=`U<-kAxh&;V zID$Rcf;CuyC76dCq+tR^L5C3dL4|IxgOqJsQnD8~i=EFrLkz!?Jf2)rUtuh*si4lnQk*KiCQP=FLfzzb4W zUZv){m#Y7a3)zygwj?Frv>2Wk%^5HYbR#g(3L*s32rMA5hQJ{LHwZi-@Qy&E(U8U` zyukxpzz!534jM?q`!4l?C#i*Qq#8byN@z{WfdzUaEe5fbRR$z#@BUlBjes8k9f3&% z@(8RTu#3Ps0(S_!An=Jmv)Pp97ren8?86*{L7Lj9G$JojAGwoS>|CnRU8(3RQV!>7 z&`A`Hw3;A`1$#fpKo}E1DFK)34cj5 z{w9sogVd+4q?S68YGPd~qf0dCjFf^2Vk$yTYGwf!0(}UC5g0=tjlettD+p{OaDu=! z0uKnhA@GgB@9(ete!wQk@8q}q%)Lpo@W6#!OMUJ{YPoHxW>%y!F)!sV)Tb4 z>+NGoxXc1R1cC^RAdo~Lhd=>=A_Ch893yanz#Rh52z(&$=g%MEyZIr%2hZ}ee<#hI z3u$Z|OMP`)YKujw<_l6u=cGKIBr8V<>mbv@XBKcGpdt`JKu2I4fiwbn1QrokMPLJg z9R&6fI6z<%f&UuFpN~!XeLj$%yM1Y1>`3EyL+abBQY$V>wUC!eE-mHcxRge8CPsjf zr7|m>W&tk({Rj*p5JO-BfeZpU1m+MZAW%SH4uMGoxPwiO;iGNx`#mW?Z*$UoC`jYF zAoY_uQX(hSm5fy8CkX7Al*Wd{2=`02&ubQNAmBkjLm-HNj=(4a2?SCIOd*g$AdWy7 z0jhA4LO9?qo;i;1w$aa5nBIufped-U1k9r0!{?n2q*|B2y`Rh zK!ADdVkG*QN+AYAjBHL3k2w-*k&r0Tr`xp35!HH5!Q60X_Z-_}+q`<~kjhcFl#2?x zlp;pl%_iw&eYKed90)iOa3SDCz>WYLiG!uU!(8?=8pBMdC<7uvMrVl0JV{j`ELP~( zby{YZsy(D|&bYfvj_a zOjMQwKl3@nh}4-Z>sNO9KXpcKQ;uufczofinlTyJh zM$#_Xp)Qtqt69K?fDHjF0&Jx=7DFeynZkOcvY}`!2?6GGm{F-Stzry}aWXqeY^F)B z8Nwq^|1Qu%OH^^Ikc!;pI>))y{zau_s~A%@mU^36z=D7k0Sf}`JKL#?1yNx~ z^Rg!S*iK9&ZJx!up`3^2Wd43IDx z9wA0`lB^XbG5UFomWor=6BJgGyPe{AGwolP)h%WL9*4r)%);}?$_vHDBcY2u-NCBt zWLtHyFuK{<6xOC5HWv@egO~ZOGD3Y!u|5WgMwa`D(*Y7KK&TAT*Fjn!tK=J?p002ovPDHLkV1i7(uGjzo literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_tbyn3p08.png b/source/engine/thirdparty/stb/tests/pngsuite/iphone/iphone_tbyn3p08.png new file mode 100644 index 0000000000000000000000000000000000000000..3c224d092e41a36a81ae79b27f6b3a92bc4d283c GIT binary patch literal 1558 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/basi0g02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/basi0g02.png new file mode 100644 index 0000000000000000000000000000000000000000..ce09821ef101b65b2aa653931416c72923ccab09 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk`Bp9=o@yY{fmUKs7M+U~W1%@xC#RK_)o-U3d z6?2jUl3pY*JS=M4oiJze;et03Q>IK~Gc?O{N#DFKJ@&l7SH)M%%o7ftVn}g$DCX>; zkRs^i5p-N&*DHI8!;%tAJO+*|%*(hAE|_G=Fej+Nw{_diTA<+!p00i_>zopr0NF1v AeE`KLv!pxvIx;Y}EiimBEgr~U=jq}Y zQZXmBFHrE1fq;v+j0h02$h25#lnT63xZ-%g@gNW$aGWTh62d&wbH=%|b$8yp-97vE z;Z29Oxt6;AKCTefnOkLOe zceV31xk&buuP3aOvK_8v?mNu>)5yI*ti8x;zv2JpMH=2qx2(%fUX!Y@wwPzh+iJE; se9X+5d(#!(S{O|*V`01GC)_Bn-uQauWnXqNpvxINUHx3vIVCg!0H8fu|I&j2dyvG3;`TGSdQ=U^x!+b$IgQ- z%948O0A52IVpo?-TFZBK`N@L-n zZIKuYb8U;nSX9Mrl^A36r{K12&k3mj^Z%lhjX;s*Jg960iexztW-CxinU-MA*#;OZ zngOW30HtIErtRT^u_8M_CF3kj<3j{vMRtJ6I<2P3n{$R*t1rNmk`0g=AU|E-;g=04 zMQ4L1&-<_M{L=>-dBi{f3 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/basi3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/basi3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..bb16b44b30907b832b678a3b441278df340edb9f GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH%)r1{DZJ<-ki(Mh=EaktF(C3l8oA>Me*wxTz-(}y$c;d$?hLjJF#hg79QUtv`g1+0Ah)O6+%y3%0 kK!=4{>!bX^1(PfpwnWvwlrMbmdKI;Vst08#Hgh5!Hn literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/basi3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/basi3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..b4e888e2477d4fbaa3c8ed16a8007d40e58cd52c GIT binary patch literal 327 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF%)r3-EjT<7$YDu$^mSxlY+GRXVp=?q&s^-} z8N$NCQvPG>Vjx#Hz$e62iQzxP|Nk8f|CRnTFwFe_KaJro!~b%i7*N6gnRh{q-T!wp zFt`9&E|)fV?gff(^K@|xshE@eVr8<$zX=QdragS9+NR;%b~IQ*M^<9l2E!vd+h?C( zII@q&;mi?+BR6^0F7JwWQfc53?9iD|$he5*L_qV3*BrK+SKaw>*N06*_XNYzScwH+ zV%a3lGAx}cv7khXdCI+B1NO@fK9ddC9@xTtImqX>;oQaofA@?RGmTe=O`R?+y}#PL zSZx`DR(OCwFvHZ>kD4?aPVKE04`%2+-2h~U1|1S{c)Dz9BG-bV^7qFC7#KE}y~^Mz Rxc~|Y22WQ%mvv4FO#pr5f6M>? literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/basi3p08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/basi3p08.png new file mode 100644 index 0000000000000000000000000000000000000000..50a6d1cac7a111d53cd3aec0d35dc4d09311baab GIT binary patch literal 1527 zcmWkt4Nw$y6n}dd;vwQ8DFIG$3@GI2c^hLQV*{=V2t>jrDk_FvDg&7$%b-@G;h|=k zpdN>Gek8Jdh#y$4REHQNu9O-n$@*~yld0Q~CgKPCdUtQ`_P_uC|GnSGdsmv8GRH?B zs0RR_gm`WqJ;%AfULF7pdq_&9mmwd-CdUF(;+S%8&r$#nNKHzgW#r%qssai!qCpSQ z0vL_7LIcnOu#UGNg#{Tj`hkLY32hvQ!1G1`Sw@Zn9(h>?HW2Wkq9XR7jEce`M@cLI zX9QKH0)U4(L{U}UX&T}n(htKT%SQskpw)^XiVOoBfYl1vBTI|Gh(?PU5N)D}JkUAF z*+A4fQ52$qu$3IJkUnIraqtk890gF3fMr1iHJ=i#wnVy*0swAiP?EIuK(HVK+H9oK zO8az3*a`*`R}fi5uu;&5F%xGAi8P7`R6^54fJ7UmSdgp%+jcbYbn0eLh5#4{O2a|z zFlncK3j#}*c@!wwN`=4x0@Wv9Pz46?iR#Xv1EQkRnl2%Lup5n_C@hN_Xp)3BL=u!O zgCs#6RU@v*uGicWd7h||*~k+*SQG^)HX|blsFAzW2%aZ!O(VrJ31D;t3x3pAg5Wqi zve-$Qc$9#ED~5p_E`A&DTySs_gXBPgvEi4GHiIsCXqG@a(*{? zqG+gzY2=omW89DcEUHMQNrOtO^VViET%@j9XuK5ipN55EJB^!6bHJ02)DH{^ z28HH;T8~zS28YEnJAZp0KzA^Ki%l=ATyc3-rZ)t5_Dwa^e3e*sHMuT3A|j#p=*X*F z#y0Vd=-McX6_VY&k>*>Z%e|vX1^&KXtnjHm_kp z{Oz$(O(8S=UuD^|XKQO8JeWScy}iAy&FQ@UT~n0V9QLiaO{Ytot3K50Bj4!g*tp@* zcGj*hPH>pOmLs;xabA)>UnI5F|W2|vHO`X8G8{qNq5UvQ>+yva2AorH*qN%N=1 zRO-F+*Y4O692_0}_3qrW zV_8*GU40<7^H$aV#hJ=gqrL3swI3}rKihD;tJ~gc@LGOoZ}}hHIjh6Ni;61tbzVDC zEf)+M(cfo^N={x^khe0+l%Bp^o)#S#*w$83a{u1Ivl?+Wcj0ty-sn>0W|&`1nH z58GA$jXh^Y`JN;7H@hZGKGv`#E%e2gv6o*jI5)Y=Gw4px%OQatp)Q!Xsl()5;fH&w40HKUbx zj4}D7oEu&UXI&wcy60YxT`|F*cQ@)MIhQzY_v!Om7VgvKXAK=bq{#ES%PVuszm5L= z&bJ*WO#!E!Apy*TI{`1@hW^84hM29NG>%Pa9pm_Xjc<61X<)<)m$})aVkqnKe7Wgq z-l@|1T-)pTTjSfo?;6Ga8{R(IDkmelNXRj)ZH9oA|PM{kg$m&gMT07S3iqx$gt>#1~b*D~%?ISJ;?`E{!%Qxx!#mpQpGpa(A8H z_iZ|MVh@5=q!mUuvSsYMxvKp95>weV2Kff8ej5&Q`t30L(azh))$_*RmQyw|G#2PE N22WQ%mvv4FO#oeUP~QLm literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/basi6a08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/basi6a08.png new file mode 100644 index 0000000000000000000000000000000000000000..aecb32e0d9e347ccdcab5d7fdad2dde7aef9da8a GIT binary patch literal 361 zcmV-v0ha!WP)!N7@Xl>n0JWRT0P1ZZ0~F7IAF8qd>UAgssMo0sP&xyjf=W~LX-;!O00000NkvXX Hu0mjfr8|%> literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/basn0g01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/basn0g01.png new file mode 100644 index 0000000000000000000000000000000000000000..1d722423aa5157fe2b029af4e2bb07f478ebe8bb GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk~Bp9L@-6Me%OS+@4BLidG0>c;6;z7cmE{-7; zb9B#azopr0C;FL=l}o! literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/basn0g02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/basn0g02.png new file mode 100644 index 0000000000000000000000000000000000000000..508332418fa86637d39e95268ec1d3658d120cae GIT binary patch literal 104 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk`Bp75C+I9jdmUKs7M+U~W1%@xC#RK{Bo-U3d z6?2jkIAXub_k1+y2sp$L+}6)%%qgEdVJADoUeEf*ZqCk?AR|0o{an^LB{Ts5miHSU literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/basn0g04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/basn0g04.png new file mode 100644 index 0000000000000000000000000000000000000000..0bf3687863d8a1f53bef0aa24b841b21b0e04d9e GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk^Bp4c;6;(>fePZ!6K ziaE(C2`UUC949vOu(I~>b7=nfE^nVuX&@tFa7u7Oddi~87#=p(p8gM~{{27yqy1T- hp#)Io!PL|g7KVVQ{|ZeX-G~HP;_2$=vd$@?2>|oDDeM3M literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/basn2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/basn2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..db5ad15865f56e48e4bae5b43661d2dbc4e847e3 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WSkfJR9T^zg78t&m77yfmc)B=- zRLpsM^&lsM0S}Wy>zj#xw-*UpyJ&w|_~YC|?Jc}4)(u=DKV%lXeNyXF;n2v$@6|4U rsD)ibtrx; literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/basn3p01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/basn3p01.png new file mode 100644 index 0000000000000000000000000000000000000000..b145c2b8eff1f4298e540bfae5c1351d015a3592 GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnL3?x0byx0z;SkfJR9T^zg78t&m77ygJ1^9%x zzWcAFl=eSI>XI5zMAXy8F{ENn@&k4zHj_up0tD0@h%3W?AY}Lt#0>va?X`CSX(dk=$B>FS$v@Vz>816FsF9(VN75txh7sS~8e>Vez z3y|e3}y1F=z>r=LEEca@LnZf*~BA0ROZ5~$d#4)lIa>n+tGAx#s~NCh;!h=Q?&7t5~~Lk>mL*|1x+L`Ivp6mn?kJ^kK$c6a~%|NrOteLPR)ru^*4 zh?oc>ipx)wHw!xP<||VqG2mh2yNQ1IZKr30Xr(I7PBXU z(o_;ftk^?X5O*qmM)+A^aUR*s!>r3PlcI=3mc_D63M-aHQVj83+hHDKi)~wG8A%eb zMRFVzAa0kL4aW(lxy(-(A3@r7{KRdCcI^9^ zBwSWlMY4uMp(p@%T`0C7K$rnonuoRmLY9xP#5bTx(RKJi zCYfG^*s^*-{Ro^e4Kgw{Dlmv)JpjQ5bKwF@CLI|%59=nhA>e#HJh5GNjRLr(&Jco- zL=roU($L^w70~)&xPh+uFV&-K!K0w3W-9Hy@g@HWXL3ML4|%5ULen8 zlT@|D2@;VI*iada4446_ptp_#Sul*Cx7Y6>PlSiq8TyN-q=y}cXZX&@20)p=ihIP{CfA$Z*4W@ zEr$zzJ<~t_(0G1&!T9U{_Iz>DPFy@Dgq4(i*+1}U*ZiG1m)32#<4sRFJ5!AP>yZ}L zzdq~5lB&|C6*=qgy?nj!is#SN$*Gwo6M?qJgKJtd1_tvb_xS3qJuRh&eH)f1f0J@< z)4uq(Z_li{H~+s=+3L-xjU7X)ZvEUC-CLA+t^7d7$jI)tsK~;c&XdnBOC5Txp}1)@ zusm-vWl2G`{dnN}K>pDE=;yi{rVB@Y3B-HrtGOePxNz#}j}FD`z3|nlwikX+ZZ56= zAY&lKTUG9w&2^VN89D}o5=^>%~&6ID5>`0fk%7K&zZcY&F#znJTW|E zB5&cFwM~ly35&;HdZq5*>W9rM<&V4Wr%mNt{B`@CxYpR8I;^7!?Tc=wu6t&Du2Ec< zKIiz-xXN3dttqGahAW20Qo6MzgSk7e_{!(?CKyw`*?j|NY=7wG%F`JaWZ(VOm8CUr z$$no_)1uMK%W6)nN!IrL6EB>puUj#i+1xg}<&qUPnY6#j4&NW^MpZNn{t+Cq+^l@L JEAySt{s-!kV`~5a literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/basn4a08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/basn4a08.png new file mode 100644 index 0000000000000000000000000000000000000000..3e13052201c9f8b90172ea2491b18d6d121c2786 GIT binary patch literal 126 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtq=#3k+XOiwE*eJzX3_ zD&{2rIe*{)W9ys@6GNprI`<_tn04&i_AxF_G>~EoF-$NKa6jCj#n!|2C_^IPFayJV XErr@6uUk(54Pfwe^>bP0l+XkK+-D@M literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/basn6a08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/basn6a08.png new file mode 100644 index 0000000000000000000000000000000000000000..e6087387639b9cefaaafb696e29a8bd910ee0680 GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK{Io-U3d z6?5KRGvsP8;BkAXo|56e@Vk8|OTXfgyGLCsWP@G=X#A1))wtQkXb@LjH|PH@<7U}X zR(Ix8Y%6~9Ml4|WGnG@SZHQ?Kc`Uubi9hC!9z&%<$E5#{7BHGEsCHnVVk5wElR+}T f%%Pr9`U69dg47%_{vAF*s~J39{an^LB{Ts5$SFGl literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/bgai4a08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/bgai4a08.png new file mode 100644 index 0000000000000000000000000000000000000000..398132be5faadf83e159ac59c212213bcd43a894 GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE!oa||uB7QDki(Mh=l@|1T-)pTTjSfo?;6Ga8{R(IDkmelNXRj)ZH9oA|PM{kg$m&gMT07S3iqx$gt>#1~b*D~%?ISJ;?`E{!%Qxx!#mpQpGpa(A8H z_iZ|MVh@5=q!mUuvSsYMxvKp95>weV2Kff8ej5&Q`t30L(azh))$_*RmQyw|G#2PE N22WQ%mvv4FO#oeUP~QLm literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/bgan6a08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/bgan6a08.png new file mode 100644 index 0000000000000000000000000000000000000000..e6087387639b9cefaaafb696e29a8bd910ee0680 GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK{Io-U3d z6?5KRGvsP8;BkAXo|56e@Vk8|OTXfgyGLCsWP@G=X#A1))wtQkXb@LjH|PH@<7U}X zR(Ix8Y%6~9Ml4|WGnG@SZHQ?Kc`Uubi9hC!9z&%<$E5#{7BHGEsCHnVVk5wElR+}T f%%Pr9`U69dg47%_{vAF*s~J39{an^LB{Ts5$SFGl literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/bgbn4a08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/bgbn4a08.png new file mode 100644 index 0000000000000000000000000000000000000000..7cbefc3bff08a9d91666d6b0f8b5cb1c896b7987 GIT binary patch literal 140 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtq=#3k+XOiwE+VlDyqr z7#LRdDjNZLrk*a2Ar*6y|C~Q?fU$K>hKZrl9G&};8q7NOZTlD(CmKkxg%~E72)G|^ k&|>Rhdz2v&aF~JNzLrAmk=Ly!fCeyly85}Sb4q9e08Xzba{vGU literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/bgwn6a08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/bgwn6a08.png new file mode 100644 index 0000000000000000000000000000000000000000..a67ff205bba91cc8f391a0b59110ae6c038539ac GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg<^yE{-7;bKYJvrKjw}eL#0B;r2mf= uFq$o>c3_`kBfxT#K{CM1p`KCt14EF4)EqJX9X>#-89ZJ6T-G@yGywoV#zE8o literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s01i3p01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s01i3p01.png new file mode 100644 index 0000000000000000000000000000000000000000..6c0fad1fc982e54aea994e12efd3fe3584cabdbc GIT binary patch literal 113 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?zc?q{RR^Ea{HEjtq=#3k+XOiwE+Vi=8|} zSXfxfe{5Y0;s*GHxH2&O@2a>I4&-uqx;TbZ%t=lFvY8kdJ=QNN1hN=BUHx3vIVCg! E01FBk(f|Me literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s01n3p01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s01n3p01.png new file mode 100644 index 0000000000000000000000000000000000000000..cb2c8c78261e509e7ef2c352306618963954a84a GIT binary patch literal 113 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)gaEa{HEjtq=#3k+XOiwE+Vi=8|} zSXfxfe{5Y0;s*GHxH2&O@2a>I4&-uqx;TbZ%t=lFvY8kdJ=QNN1hN=BUHx3vIVCg! E02xdg0RR91 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s02i3p01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s02i3p01.png new file mode 100644 index 0000000000000000000000000000000000000000..2defaed911a29507f745bd7183a9819b29cc53de GIT binary patch literal 114 zcmeAS@N?(olHy`uVBq!ia0vp^Od!n2%)r2CU%&n%ki(Mh=)Kx2KC^NX4Aw1O`S11~vx9e?93ZfWi!(u6{1- HoD!M4t literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s02n3p01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s02n3p01.png new file mode 100644 index 0000000000000000000000000000000000000000..2b1b669643540f182c425fb67869b7f97fe75f10 GIT binary patch literal 115 zcmeAS@N?(olHy`uVBq!ia0vp^Od!n23?w}&=BEQGmUKs7M+U~W1%@xC#RK`w#ZI0f zEG#VLKejFgaRYopTp9lVmyFR@1#)>jT^vIy<|HR1Bmj9V42<8zEkc1T22WQ%mvv4F FO#mnG8JGY7 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s03i3p01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s03i3p01.png new file mode 100644 index 0000000000000000000000000000000000000000..c23fdc463170faf97e53fccb4799386700b21a15 GIT binary patch literal 118 zcmeAS@N?(olHy`uVBq!ia0vp^%plANBpIb2ie~{iEa{HEjtq=#3k+XOiw8*-J9&n% zu&|W>*t!_VWee~Lab@_=@V}g49;WoqQ$(6ld^s L^>bP0l+XkKxV;@Z literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s03n3p01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s03n3p01.png new file mode 100644 index 0000000000000000000000000000000000000000..6d96ee4f873baf1df3652a8d70994eeea799c30b GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^%plANB6FUp{{d1g>5jgR42*3H3|~x(2T2w?d4{mC zu$2GUx){i13-AeXW%$qVzno#?)1~u)B7B}Mjv*Cuk`odN5)y#?V+@X#hCJRt7K5j& KpUXO@geCw&86D36 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s04i3p01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s04i3p01.png new file mode 100644 index 0000000000000000000000000000000000000000..0e710c2c397e371e4feab66add6a9f9763ce0c27 GIT binary patch literal 126 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFt%)r3-#KLAZh?DN<>&U>^w!rYkw0Iz&x!B1w zgoTBr{KwYCKrUN=Pl)S(hVuXa8D@QV-U$>D@pN$vshE?Tk-%_}nTL-@Si+8hL2?}n Uzopr0P-Lp3;+NC literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s04n3p01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s04n3p01.png new file mode 100644 index 0000000000000000000000000000000000000000..956396c45b5103d3c38dd8906be14002e5bee48f GIT binary patch literal 121 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFt3?wJp^Voto>5jgR42*3H3|~x(2lAPVojgNW zSXjz`Y+VfGvIY2rxc+A-|Noz1)_3QfKoNdV7srr_Imtf`7%+0!GcfRQFf%r7-v+XT N!PC{xWt~$(695jV9kT!c literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s05i3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s05i3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..d14cbd351ac11022eefcfa3bb2af528c3aadae41 GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$9%)r3d&i3yCki(Mh=b^8f!C{x5dr%K!>0dAc};RLn`vNXST-!El6+N0&!i n!bU<#VwswOk4HkpRtAR1>$j<%)r2S&bt2^ki(Mh==jv*Cuk}VDtG(2QH%-+V! svtII_@t;Hw#zXcz;mHztXBa0j9DK~v7~1r+6R3f~)78&qol`;+01$C3h5!Hn literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s07n3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s07n3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..6a582593d654c8d43aa8c8dfa8f6516e4f24c8c4 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>>$j<3?z5j>~{cCEa{HEjtq=#3k+XOiwE+Vi=8|} zSXfxfe{5Y08<>}%WQZXm_$N%<+Sv-f=TJo4b eYw%F`{a>D;u##)1Tx>4LCI(MeKbLh*2~7YjwJ1CQ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s08i3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s08i3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..acf74f3fc4132609443b0555d56e5b314644bf23 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^93afZ%)r2SE-kGO$YDu$^mSxlY+GRXVp=?q&s^-} z8N$NCQvPG>Vj!0%z$e6&;s5_~hX3XN8U8actlAYR0~A;Fba4!+n3J5qz{IFfcOc>T u0@fwp4bCJ+99*!q$#Hv=FS$q5Mw7orX>_}}cf hovEdPM`3y+BZF-itL;x80b!sr22WQ%mvv4FO#lIoCTIWv literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s09i3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s09i3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..0bfae8e45678282b23bed2760c0dbbd736be9df8 GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4^%)r3->c>4%AcrO0(btiIv2B6ji)rydK69~? zX9x=mOZktji-BC80G|+7hW`u<<^TUP{QqCh@IQEaktF()~}fr+~zg1bjT rBF;F5J&BF&_kSLqX%_`}QkWS^|FgS^^-Kp^#=zj|>gTe~DWM4fSkxz) literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s09n3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s09n3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..711ab8245189b4d5118b4dcd49ef9771bf924fb8 GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4^3?%3Nf7cA8SkfJR9T^zg78t&m77yez7dv@| zu&}U{|Jb@1$mI#}32|lk&%jXr|3Ab3|K$w-!x&P4;_{v@jv*Cuk`ox1e*fp;30o~G lp|iL#LdvQ6heDwO14Cvi>)|ZtdS#$e22WQ%mvv4FO#lN*C*c49 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s32i3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s32i3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..0841910b72779aa7571cce45e56447eeb3de4520 GIT binary patch literal 355 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF%)r3-EjT<7$YDu$^mSxlY+GRXVp=?q&s^-} z8N$NCQvPG>Vjx#Nz$e5NNdIRjXZZggLNS#8F9$J!e4s=*NVput`nsRZ8c5&pba4!+ zm{U7pFJF^^h?~7@-J{?g64%>Ss_=bq+ht{1xSCJrO-tjMmhSi` nkfZu#wXNcYdz+%H|96_V1YK&G^7e=_(2oqBu6{1-oD!M>$PW;WtqGsLq6}$jpDV7@>y0l)ibBXvN}dz6L`X{!W|QK qt4`9uP~=9WliB7OneY3z7cem=9`<-$rKSgT2ZN`ppUXO@geCyfYFs4% literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s33i3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s33i3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..ab0dc14aba444d3f59f0bf77808ee7ee78ab5a48 GIT binary patch literal 385 zcmeAS@N?(olHy`uVBq!ia0vp^iXhCw%)r2S>FoPcKn_c~qpu?aW7`757t`W_eCA>& z&kz9yJIm7?|5Q?Gve>sQ=2z#A&*U>UfA#bB(&kXT4uKE5vkQo zUQ-!gbN(`3qRGqhce6z7%EakmS0Y8FPq}6+_@ir6xO2`=y*VFuJPs^Aejwdi=Zl4) zY)`yQC*Lu~AJ4vKi$Bd@7qys2yXwnUHmtVq{n7=4!I#bZtC8xk9FB%)(-P6#}_whaYKv zG;#N1*N-P`3zt?rkunQiXB;>sd*S~OJ*$Vjx#Nz$e5NNdIRjXZZggLNS#8F9$J!e4s=*NVput`nsRZ8c1LAba4!+ zm{Z%gm#f)8#BG1p-0Bz*xXo g-$=Io|APMvAEo}ki@M*T3G^LmdKI;Vst0JTexR{#J2 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s34n3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s34n3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..9cbc68b3b9d5f263eb64bca9ad8bdfeae8205f63 GIT binary patch literal 248 zcmeAS@N?(olHy`uVBq!ia0vp^N+8U_3?xrvihTr9Ea{HEjtq=#3k+XOiwE+Vi=8|} zSXfxfe{5Y00D12$B>FS zQ_pYYZBP(ky%4X^8GYlzZKG+@7dEqqPBxHOE~fV1{h+qq6V*Svna^1j^)9?Ct99+6 zN12I`jtXnzk)}u!%RD37KjJGY{QS1{OXvRBqt2SFsin;wQV|yPQK!t*<5f${SKY|E by&mjqj=t@%(q8BXw3xxu)z4*}Q$iB}CDT{I literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s35i3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s35i3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..e2a5e0a6595f100edc1f79a3e0864ad6ea0d0121 GIT binary patch literal 399 zcmeAS@N?(olHy`uVBq!ia0vp^${@_b%)r1n`@_6HKn_c~qpu?aW7`757t`W_eCA>& z&kz9yJIm7?|5Q?Gve>sQ= z+doX6Y5h};;jD1&3&kR*!|w$e_FSCK!TZ;uv#nAtd0y%gzGN#yUNMtr9o7cw{d`v} znPPXp_t6M&TexoXtroF))1FRQrM1j3tI<%mZHpV5tekks+WAwqdfa)ug=wLE+m7oy zMQ7hT>LK{?oy%sWdO^0lW9vk3*7N!L?zPr!EpIN@4l($9_RGP?h0kp-%f1)C*BQ6t zSkfJR9T^zg78t&m77yez7dv@| zu&}U{|Jb@1$W;&U32_C|{~5{|{{M$i4CVjJK};YYC{Yd)E(fu`?q{2n^e{*@3Z79)POXW#oDtF1yr`=+itVv=OktFdG6g{K)mRoAX+EY7>z zmhXBt<&Eu!6r*o!cV_nNo&Ccp*=?%nxjo?-PXbeTw3r{SnJ?StNM`T3!o6nKZi}v&Xt(4rKl7t^ V?&lUg^#FQ_!PC{xWt~$(696v@lAZtn literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s36i3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s36i3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..eb61b6f9a325db7d967bd796d3a65494bf6b7754 GIT binary patch literal 356 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|l%)r2yqPf);$YDu$^mSxlY+GRXVp=?q&s^-} z8N$NCQvPG>Vjx#Nz$e5NNdIRjXZZggLNS#8F9$J!e4s=*NVput`nsRZ8c5&tba4!+ znA1CPFITgHh@1WGUzfMPP>Mb3d2r#rz};nBJ2KhD%L@z2iugh<9M4MfUF6B#_T+KJ zyS#u&i*AX_`$J4FwOGR3+lYu(xyz9^Us?I$Nzg8@_uT%B&fwPAv<_h$1 zKV4X~uHW!2nO zw^mgdcYZzlI&jNId!5cjyapMo+SY3yv3S|MG5=1ghQ&wM_et*;#P6@Vu`=r)^ENJ> nM6Fzb-Rz~M-&QV*`e$h`)grF|l3?zm1T2})pmUKs7M+U~W1%@xC#RK`w#ZI0f zEG#VLKejFga@7NTLR^9Le}-~~|NkKrL;3%55EIA;N|b|y%R#KK``N64bh)RCV@SoE zeX02TU7fN^(b&f@AC0IF4)lp>A zamtZ6X3lW8^&N+fpX;N5a1N0dE1b9+oO)I`@yuDpE^(oyK}Bq#(;+7|2{WUFjT0Lq lq)!ST`XV5f-^|R;z~ISp;_}2b`+!bh@O1TaS?83{1OP(LROkQz literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s37i3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s37i3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..6e2b1e9b79ba8ded32f713506b543f95c8720615 GIT binary patch literal 393 zcmeAS@N?(olHy`uVBq!ia0vp^svyk5%)r3tu$^}qki(Mh=sg+HIV-2>Eakt zF(-9`wNR6RK-+&o*^hxQcxC(!_~<1(WXyQ8cE!t#;Gj#lmM+`0{Ul?NhPm`7gP#(U z&TKlpT{>aX1vlpoG2ywrj9K?;d1~@l8+XmqNYD|GnW?j3_LDi&_Gk5ZpVL(C5Y*hb z;_)GkI~OeSB#!?*!4ts2={s4)Kw+AwsIZx0;sW~{6Jr*4M-?k-J?sdRs5Q&mv0%AZ zytIqJyc-jCPuY6yD{r}_O<9KFm9UNB@vD7vZe722&ATSp?!|BMy_))a()awI`Eg15 zcb}IZ6e>9H6slHm#xw|b7U^#ds^C5mec`FsvsuL_-L{JOW$W&>5V^;FS zsTXzmnjHk%9_laj{iXhZjaU6f3%5cQ1CLzmy47pf9C;MBe%0{;2EMB|HJ5HS=D#U8 zTcE<&;mf3XHLadi$}J7S6KsC%TdZ8VLMtWoP@;^%GPWsSI*kflz0dEPDy4JU==ewT z$8HrlSvDN~XJuI*Zalg3>X&(%0bi?r*mu~p# z*kbkGd%RM6vU;~}yPmpD-2FE9?b`>wofE(7z53qT-=ZIGukYz~KPva4hT&JMP+%DE SUKyZ=7(8A5T-G@yGywpP!HFRN literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s38i3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s38i3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..a0a8a140ad7ec7f78f5b8cb398f54233e790fe7c GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$Q%)r2?D{_Pz$YDu$^mSxlY+GRXVp=?q&s^-} z8N$NCQvPG>Vjx#Nz$e5NNdIRjXZZggLNS#8F9$J!e4s=*NVput`nsRZ8c5&rba4!+ zn6q@EJ$I9XOxyf_5svYTT%(q*Y;ODGw$)5_M`m;PZW9x8Q*O>*8?9X5^PP9_c=!F& zj}P}&avWz``7H13GiA$f!58}0_y?X%x||YHuGu}U@%Fz$#r)47t&6p-nF}}c@lTBs z^1PZ{v*FFH8(9sEQqM(?B}LEJyE$O3_FS z$q5XNhu)=5Tjidh!fa-!aP~lxV%mWM7U!NuQRf`SX00}f3tT>iIb{)A35DHSJCk^B z9JyieHiKao|6$!5cY4l9TNo}lec>oiL(-j$qysxN6CA{uC#7^J8uHKH+k8^@!3HLV YPnp`y=hnD91lr2r>FVdQ&MBb@0IdL5LI3~& literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s39i3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s39i3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..04fee93eae400e745534756b48f5420cbe4a1d91 GIT binary patch literal 420 zcmeAS@N?(olHy`uVBq!ia0vp^>LAR*%)r3N*;O+E$YDu$^mSxlY+GRXVp=?q&s^-} z8N$NCQvPG>VvyPZpAc6d{hy(n;s1XK#Zdmg9K;0jffD5);c^h`>wY$Cpuv)!E{-7; zb8;sb7Bw3PxXwR5nPsxbIbI3B13h{Zl-LU%&pWl%I?8Km-rd09oy#9GScQZM32{6% zJn&)b?*IA=s?&@YhcsVH;1O9g(`9z%6oU2EhY-&tz!*HGHjC7g8ZRKA?r5euu=`tFBX7AyHo&^hw&tWyi;3*RQj<5NC7 zD$9QAmr-d~@vfp^>g%_&3Lb_R^iP%F$ND|O^6>H9g zXp(yF?QYZiJMI0h)x3XK|Fr(u@2&iL4zKdoR{mw=nP&Qp=ij3Tz`$bgboFyt=akR{ E0PqU5-v9sr literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s39n3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s39n3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..c750100d55fbd07d216bcc5af538a83b9f7772a3 GIT binary patch literal 352 zcmeAS@N?(olHy`uVBq!ia0vp^>LAR*3?%D%eb|5$OS+@4BLidG0>c;6;(>hTVkgfK z78aKBA6plL)CTy3xB}__4CM^}|3fH-^8e)^CXf%5Crf5MCykT z!yOH&Kayq_xt_}isYhMd#%sHX&9!mIgr_sq&#dB<+^ar$Z{X$9)vls_k8f1|*}8bi zpB=vzEdR6P)rUoYc6jZ*eQSQo)o-o)F5h}-kYN6lt?pKC%J$H%zv=qU-|SMRztaeP zGf~`5;@h^18+60tYao5g)5S5Q zV$RyWja;A|lo{eZ(@L`p} zS{LWU6$LrdObg}*USsKY346=g=`j6{lFBTGU2~>go4o6%r;zgB(0Rug*KVDo#;EA% pAydNHT#*-Kd$uF{lI;G>I4-d)zk<)z6+mAyc)I$ztaD0e0ssYck!}D0 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/s40n3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/s40n3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..864b6b9673b3b331f2956ad2299b7854210cdb41 GIT binary patch literal 256 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NV3?%C=ER6$FEa{HEjtq=#3k+XOiwE+Vi=8|} zSXfxfe{5Y0FS zXD@8zZ7|?*x#--bqd09rVn>dvna6z96-Ml=&4Mcg{<*&Jc=+gKnBVfCtuiaG{Fsq+ zN5I`d(XC(dB13ART()`KdT#Ey^-ixof4?!Sz`}fsxz-Y$_FSPy%NW9)eC5gyx=V3r k20fUweGf1AoYPn=%bN?oV&q3&xZP#{- zd#wGeV8okcyH@o)cboHb1rrf^jB^?sG2T2?@G1n)CJc@hyhCf)&E8%^0~1oF;v>7S z;6sBJiji@r0Yq=mmR;)Ojr>$W46R-Sau7HO7xZFtI1{|4z%-nLbW=B z3o0EAC)h$;8WIR1rohQ#w444G0#1m$Dj66xet|L#R>4C36e7xMM0T)(aSAa2XfV)D zcEU#my$(kCaRx~QL^LgQ_;=)3gHxFOXBZJd{maEJLqY3H<>rDuET^1Yth7z0>+1gi X*waT#qxC!3? zz50+^bb0-!+_LIFVlkOn3uCR$oXN=hfy)oYZM=LTO5_V)JX0F)aUb#`{Px3~8K z45Cm8G#cZ?2?d2q03|3C!N$f8gK>%=kzAFOs;a7HXJsOhsJOUTC=>!T78DfJ0p#W7 zO#|fS<`x6+csvb_-PzgMH*VaxejVY*&B(||Pfw37i%v;TNli^nPEKZQ-I|z~7@ruA z92Sce8ykD3^-OehbYx^?czAeNSXdrFNJvO(?(>_pT_EMw{BQ#m>$Sn!qg3OkVyw9#4k*05u5X^#Wxw8I%DBhvS=>0kTjoKsJnz zkHa;PN~LfWvNvv&lMD4H5CQz1ooNFD1Ml8_0AO}^ckkGN(bYw&Ct|UTmX?<3ckj?> zcNB^Yu-39J8}w6t>_9>;(#;tUCqEAdt`EKrw%G5dUucga(q-6VG498*{K%rij4wv2 zoEfW%jzHYO{1#TLQQ`9W=k@mzoI;b9QinWz_A0mp3_C3m`>c%tC$KR#Xus#^maPqG>$Ip&r<7PP}+lo3xrqrK0bX_;b9>)$CG zYiDOPWRFqn6UcGDpA~Oj9{Dz;6-NwxejzAm$n^KEA< zw~yjS7UP676`?(gNA_%Ti&?JTRiQ)ukN>niNvk-4K4Y-(eYokz+n%p00U^e=@{X9+ z)rI4_;#GQ*EM)A{+^B9WciOLZ)vuJAK75`z`)0L0X_*pJxWq7Myp(uq&jr<7|o zLnIyhWR@l$6GSYpXJsSmv@J23K`FjDRPrN<`4!1UJg1FhP}`#*W-iAZNy`g6qaeJ622^AeZriDs|F)fz44lCrgvCaK9(i)p7!%%LO z6LCs$SZ>MfJV}wPPOIFlR1QvYe%|MK-}ia{df)f+{c@b0>@`sOC;-qn<$!ZVI(j`f zDj^kjGph?}%2e!GEGVRlO(*lc0TkamJGz}*TU$#c5*r&IR@vH?l$6w=(b>AXMMXv1 zx04DB3jtEEU%$?+^=WBow*aI7Nl8fw2?+qjY&IL9hsk8d#>O5wLZs8_(b3V7k&zD74p)(r z6#W5wi5%j-eJnyYfy3casnmdg0P-cW&PSbeL&Go(2FJts`1l+vI)>xmtn;h^Zo*f9 zFF+!ZJUl$y-Q5WUf_a*`i;K%{;ch%0Z)Jtm(t1?CX%ln~ww zvI4CLn;>X`*DqdZX)#srsxB=p0rJcP5Q$0`7Z=q_)!{xs9cb~i;5IBQEI>6tCCtyy z&(6+*5T>W60ceneP(Tm>WPquush*y(ii(Z4cCD)F{LssxzP`RZfO2EwuCA_*j*dQn zArvZ!Mq?a5uB3Dcpag{?+Sxf^v1cR6WET~cs;a8FIk{LYE-o$>i9`TRH*Vah2Ph~g zm;uPo&o2hx^Z7bDJ92Y#uU)%#^(w-Tmz9;3nVA_=7L%5lmY$xTnwrYkv?)0`IX*ca z2^NbL7Z-QB?Q~2`OjJ}7l7z-JS_iG7g0sVN0Uz}3~&+1VKez@Wfj%a%*<6dWBL zfByUl>%J=rqtT`{H60urpc%{q%@q~D69^Qz2T+SJUKdaPBUi= zZ?YsBS+ans&A`7&dFaENu<+a*|IhOD!Fy5FU2($py#tmf$d>!3__F$7Rx`n0Xd`(x zXNPrB#Hf{`Do)y8Y}67x>Fw{qlr}T!YF_4_IUZux-oC*~jH=jeIQPaX!`r{y1WzBl z(m6wK7D&e$Zg(V>@2C;Wf~LRR?X8$9#=q?zc-|P5aetE9NbV#QTKGtM8suGqBBx#3 zxo@Q}hx+TEERS}!iYl~OZDE->d_uXdy?`@|M5G4?j`=QYAGR)#`a zvNJkHwy@!`Y;;2I`Sq)+{L9pEnB}|hM@{;rXnr^QKIxlagyn~gH+uDas6JD-A_hPD*sh|5-pZZezZNI zTBjQ-?c5``GX0PwW_dm>8`Y+1#_9&A`Q}k652Y3f(u)LcJ9%$iuabnh9D6vUApEqF nBzr1Qcj<%PHtn77gx$)ZQM<+~5{$JW7KKwMop80bl;r;b+-;m} literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/tbrn2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/tbrn2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..5cca0d621047cacf1637cf7d77e997d51cc6b15a GIT binary patch literal 1633 zcmV-n2A=teP))6_}Akuqmh*5X4X^rIbM;BH-v?$kU01p_ErCl=u4~ zL7fbz&H-~9!(}3aFu`RZW&unzb50QkgK2!RYsr=o3t~2tT^d3tzx&+X@9uj)%)Vj6 z{|IpB&Yj-g-pR?y{}^!Z-o3WAwzRagh=_<-fB!T`$I~7jJzDL9k&%A|7#|<+>gvkP z&5eqRQiO&cb#=`%Gt&SnAO*+-su`xq!J$j392p#Z_UzeG0aH^`SFc_zD=UkSkAGjG zNZGY3d;R({hG_tbfDeIqE;r1MUWVZftDkNW!h zs;jG$lamt?5{`w2W;;5Ta5(kAS>OWD3k(34fhLB@x3Eyz+ozkGX8`3uA)t^*Vq;_J zARVu%si6?50P_I-{r&0b>GXAy-pZYvJ_kMn+JSRiZt1pd#bWVg8=D~(>oza}`~v)> zM}7e)8HRFA`iDhFM^mix0K>z>DwQfHCnqyAGc`3;6%f#2ZhqFvO5^8uBq=H7FUwFU zT6XLhvbG*uwdz+cchJo2l$~8_P|)B15EmC05)$I?@88+kIS+9E{(W+tk&&UwQ56&v z(A`o(AH0rH_Tk~-0RaJGvDnww_wL=h^8j=?>6z)a6}IeCY}aO2b*Z|fq@=L0@WhD| zsSDdty^(jSiA*M&bM5WzEtks;bWvVj9&?GoHDC(zc6o7eG4)bKMMY_8X?Aw@u?6h# z;lshf`|a#hY<3Bkd&JHz$kWs3`0?Wg0OXPTF&CY{G^|Igi;9ZKH36!ss;DvOr&_J1 zx~1t&sZ>(M4;(lkk?c1!`x@w{b_L!AqU`OZt*xyF0OXE$lBa8(+d4*aQ!-UaQ=##! z_w+pDSx{e4UteEaTT3@Y0IErFaIj1kyl&n1z;8e!i?yF&L_nC8m3(Mu$N-?Zxp_n7 z2DAX*=>Wn$A?4)L>VvOzv*1+iR99Em(9l2wMRh0dROro{PXe>RH6x>UEG*vQa@TOV zQlU`t^yyOrfcExwL7@Om)cL>za2|4U%W^%=c<5%V3NB?X6h0+EjKsu5@-7xf85_?~ z_(n!?E-s$Cckkx$Tm=FtNq~V#ChO_xan5qaMSv9N2jF-3*7yqRh3Ek)Au1KoO;8fV zprIvzKyXeU{%KoVxrc`zkCz5CFib&h?McIcn>TN|Dcy8{RDkAq5^L3Kg|$LmUsXU< zBBJ4(W03sGWO5UeaSET!R&3c4%;9JOl9gkorlAz5VF1mVSE84$J}Q`0nqU|>1Di@4 ze5+F&_hR?T%1UBTp~Aw#cJ55qhku#R59RT^^+74-!fSSE~<7VgEJh?uUa0qm; z8Ez47t52=QkANEJ0t(=x^_iQy^73*TT0XxG7zGZwyYCSQ1YB+maEUU=$|_zAke{D# zTyIS3>>TO5U9}y3y6WrNG#}mt-ZU1(ApHmo3|zUggX&{s6zS#V<>Yh_y5_4hF$wDK z?p_S=`P`~WeyGw#>B+X26iZPz^D41kPE3=O0g4z6qf*N)=*PsGE4-+(Dp2{ zw2Zxeeeh*hh5=+_V`EuaSwV3@IIpj54kWn}jYdNZ8e`JrO`E(~ENUAnPWZ-+iMMY3 zY|zhwfLVQ0wY0S8#Df-Q7G&8n5<6nh7)vA)kx0bn+nAbqczMM?d^oX?lf?jYc4O#< zON|S4tujMN`;Hh?Jz7|PetuiGZY4pbYg^pItKUTv6B8tGw0Wp##Go?l literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/tbwn3p08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/tbwn3p08.png new file mode 100644 index 0000000000000000000000000000000000000000..eacab7a144cb54be28f7a5952a2fc63fd883766e GIT binary patch literal 1496 zcmWlXeK^w#6vvOZ$|54&sN1DSM9q^>(d99%ke6YZ7R$UYR><4NHs#$&Yi#Ca7>ZQf zh+9gRl` zNh~TV0!S$+DBx6a1Ofq{&(F=xH8LV_xmFEIVsi~0f+%jL$9u` zLJPv?JUoFnFJG#wGn8wTmzS3TdFBBKg=I@iODbh5a37!!)Vb<#2No9>p&FnH=I7^U zXJ%cRLDjsAP4}`!PL}LTibZ!Mq3SyT4m+=!B>O5y}h{r6~@M$ot^FN?Y#hl zC{!YqO1H65P`C(Cib4@io^-%qT%t%M_l+BG-MTe5CliT8B_$<7p%9>{u&}TmAU{8U z1|TmluLOX{<7sN{%E`&Oe*OBjYY0DXW@ctaMn-ITY-&boT3T94N(x;~Eh#A}At?a~ zCX*Q-AAhR#RBUW)baZrNWMo7{L_R=RSXgL8Xnb&RXlQ5(VwZBPQ=XGwpr60LKYWFw z`A2N+eLT=&d$z1 ze*A!+zAFx=Qm40Uad2>eW-t#nmzV#B$CKe6KrO=fr+_k<49bCn!|~0{0XZlSAO|KU zCg3VarBb*8IU6>}$%O|Jhya1EuGE2nfw8f70F3VL?(N$#dU}ZTL@bux($X?BHikx@ zMxn?6>n$vn+TI5>$O^e=4_7?cei{z`Irux-V7&^JWYHn|XJb{) ztaW8aFz!G>3$xX@Xyx4VhMGi|@Z{yRAy3~u3T{Dzq05Alkb8H9K1P4F-hT3g)m9Nh z8VDfQoBpwyH`740DZKl)ulN4y*5D>vW}_t6!m7uTdPs~i<_W0O@toO~X~vAfo$~kX z?99fT_mqZ2GV8Z9;>{}~Uxu_<#PH|mLqdkkUKd+?dv$CItbLO(X?+IM&%D`Wc7tc( zCSF9>rsLiwKlI~HSa@$u_-AGM;JxVT&UitaVZY^ZlI8v>p0qxM*^Cbq*ovReoy54y zqc@kKDo@y7XjB(I?dj`Amo?MtZokTNvI#Y7Yg4ikp(^za=H6PR`vz8+;AjI^J7#Fj ze936To%SmgyKal5=cgBHdMc+%aN}M5FB+rM?@v-1Ngeni3qNspgRGNZ?7C+sXI%1X zu&@5<%1FnPY{|sA&399jf*Ki-lC51XeTCyzc{m1!Hy?E|SK;2qE86{)*9AoayT6^+}UtG~L-LQ!_uNscFG7wOc+;n55 zi%O5BBNH<3FJF{p3sXbkmhU1SHR+V;^14{}iC_7{%m7S+(d!q3rCSucFC8q?ub!ax zF755m+JV|^+;sBNR8-&ZOS{mH!F^^e!qemf2F)+O1xWD<6~CxV>WZY3AMK7S*J*`G zI`+z}Og|)wnBLFIM>MEg;`+lGIzxVxozicm0XHC>L6aZ+tx?p_}HCXEn zD#*m%$mv8xm4flWfKsN!VyxgCfbvH#cVDN~)zxGIp`qbnm7`-xNlC4N0Y^`-xVU)x zc4ARc5kUI2YuC7yT%k}X5D4<~^G!_&JRT3Ah|A@21RQho%G})CWB?9_(+I$3vo$mZ z=y7pbSy=#e85tQj0b~FvDJhpOT>=oZSS)~U27{51kZ|Y_fkvan#l^+M#JE(uTtQA! z4gv@uunBwiF!3Bbo6V(AD8a$Oqzfe7&$^k$#*t_=mWTEC_dim61k1+S<=X+=fH{C~ zKqL}Ro;-Qt#0fkeZLZKPnzIvst%}~3mwzRYa$TJT>Br088T+}GlfcpTopv}{U+b}yj3)KLXFf%hV zH8ll7n3$LVph7M}0YLze1r`<-`ub+;*E{Iw)TpV=^uO-!?d{D6C^s|f?CflBZ|?=@ zN1;-vRJy&ripm9m5)_JX?3fD%;}Jt5d9PnzRaG@Tt&m70VzF2x5&<+`zkaOg!FtJU%`?Ha0doIyx#Ust_O|A|gC0JTW9BJUl!diAx#Qt-#Gc$Uh(;0OsLv z;o&o9&YV7d8onR~ksRrc1Ofrzt2y7?In>$0f((P;Ljpax;QW}rf$fHL6XZ~`kUKn^MZ$b-?* zQMe3pxg3%q@0VYclp=!&M1UYqPikLZ-^j=Z07iFr_m(XfLqo)RA{I+;X=#}p89}3c zQ7AINY72|Cx(f1<1BfS}6zk)O2Zt|%A!|#3n=^L7>E-reBy>5hSd8!C+(5GA0R5|( zCU?rNq9X*i?^+A9)vRdw?6dm2DISq&OPK>F&h1k13hobIB8bE8-4UHJ{j2p>b2F>0 zJdV^CNUpQ^W2IoSo@jsl&fn+!_ExusG&(RFWck*%JvP(>Qj{5ANTt5dn`)V0Od8)V z``FITX~_FXsZSxZemf;qUmltp&}R`NpM{2n4OqS@w(~pLu_37DZPJ+CDa;Gz%|^=` zd}}Z1EV?EO_b%I<>>cOJ>%y2#_#mN!^x5<= zjJGmcy%bg9Z>un%{403Uo~AEOp-q|0FAO&Q_payLN^pd^v$8v; z_2>K%L+MXinj+%k=b2%{MBZdz?a#ncO7`Gc#`N2t?P<&Y2}MhElSclGW+d9ySV&Fv zGKiPYu6ryW8ddns&8aEAjSocHe299~xUJNH-^IF5oEHo-12IXaZ(jD7Y*g*OxWCk> zdX(C`xVuAdD@xt0@z}-jnCHJQ?m)YSJhyBS`I7e;H@#X2l;c&(f7KW>kjTeAJ04Z5 z)r*jI>{i%Xd`gip{hpQ$=}bQ!O lGajP1^htlK&dv|QE>+O1S>+WAhFg({f~%7!w#Jd1`acy)otOXs literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/tm3n3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/tm3n3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..fb3ef1d0c5aa4658cf1c1383dc6e74fcf09116d3 GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@o&cW^R|bavaI|&4^Ese6b4ie2 zFhl4n?w#tEK(2_Vi(^PdTyhFSQ-Fe;PETlaHzR|#xM-r7v7RGPp25@A&t;ucLK6T+ CZ5hr0 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/tp0n0g08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/tp0n0g08.png new file mode 100644 index 0000000000000000000000000000000000000000..333465fcdc9f10f7f25b2d999b558f5421983ecc GIT binary patch literal 719 zcmV;=0xe{x-64Dw5ISbDJfGPNXYF1fsnx~?7~M3eMD*m6rOMo0;2$rUW>-c(^**S*Ycd0zuX+u!1|S9WB+#F78QX<>lo? ziQz$h-hRnIWoQ!H=elq>2fX`s$g4Md}>$Ugu1e*ygBpcmqZ3DNb_5HNEsBtg8Fz$MsRgsRcUEt!R)#7 z@*=Hdg|%Ijgaqfp6x>M8@W|(*DezA>78ATBV zGiJ?6^pq7`pj8TmNNXtGQ7M(fr#kNOGpUKtO+a# zE}lAlTDXoRkAt?j2v0jgK~H>oU`AO)UQprWsr_L}qI~YALPA1E5ej}SPEJnGOv?!@ zojAEOR7Ob9QdC4?8%mP9keiy8ksVOlKd~`Xk)KCEK=mA2ntaoimYx|<+}mFjqR+?8 zZTJ8q(f(PLp6*}V-CGiDD=qBv7F)8vQWR0&(VZLQZ(jZjd%k$Tpt-FhJuqOwUu^jT z1pXapZA%Q=jx2U BP!RwC literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/tp0n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/tp0n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..fc6e42cb420fd08ecd78340256157fa5f8a22934 GIT binary patch literal 1594 zcmV-A2F3Y_P)u542F>)wJ;-zU<0Ta5X4X^rIbM; zBH-v?$kU01p_ErCl=u4~L7fbz&H-~9!(}3aFu`RZW&unzb50QkgK2zirzKlPEQpyV zJ88Z&UwhB*+;h+QpNot)Z1^7mZr{G$*Vi{SHT9YSckkY9Z*NaePmhd@j0*@zcXT}E z>DjB%+#enNPk@PuiSF+1yu7^V=xBLZ*b!IPd^0mOpafEZETD#AngxPxg<^DQ=;_m^ zO9f0%PhYumrM$d6AtB*?xjc2}&YX4Y${D5+CG4W_vSdOD(DVN&-oB_@QeZU}a320`R0!vG! zgF}Y7c_vT+6ajLvI4&-Z7Si(C+FA;c5-<-iFfc$*`Z-x=6;4i{1D^pMz*&|p+q$(x zB)Vj4J8WQZ3z!6c0e;dUzW@{rLwimBm&L@yP^|L+BO@b9r7|}+H!CYEEiFwM7}#lU ze#Y8b?eBj$IXU%j%aqGow{IV|u^C^v@>iA}GBZ1AZ=V(%{LdfavLmWItxn1d>CixU>w z(z&H&q%@~cmeiGMuLdu#(_V!Qg$)f2b#--gLIj|kgoK1hr6FtAeh>TxG#MD|V;DCe z+}c_;JUpxi(9+VfzG^*Mf$y{cVZV^}t ze*#wx4d1b}e2ZmQv#dlY6hC?LL=T{&qk~_>M>ADEa39m2fL{qvo7rko-xdG82;t3ZKJ~Z{8fjKg!o$ON?8wlCe~HHnv#{{d1tq7Cl~vHp z%#3~jNv`NUkyhKvAxIVA8=wbFDWm}$)k06v92)E2}b{Rj#QTCt*&@?&Ti zD zl4Z+2B9B~dgtK!fhf@n&;PLjfw||-Og6SEVLqkKcv9UCP1w{gUs+$RUkfbMoF`VR)x@ASCQaVB z(Z|4m%7&5?v0+2f&6_{#^|K%#LswL-t*u(|pn;hUX^xb{ju_O&VzJoG&5g&iH8u70 z_D*>4U~(ZRivi~B`tWs^S{JHXMW%x09Wf|-G_d^r{kLq{LV`-iwz!9v--{+ECrRLF z@=(%OV07*qoM6N<$f`h#14gdfE literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/tp0n3p08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/tp0n3p08.png new file mode 100644 index 0000000000000000000000000000000000000000..69a69e5872234d96cab4cbb017a0933fbfedba81 GIT binary patch literal 1476 zcmWlXc~H^`6vv-Om}J3dn5OOMOgg5aIm=3#R_~R8*9gm+#ueDlIJqsFg@0;tH{|b8}HqQ5Jw$Ebasl zi9{wQqx{s={QP`?_T1du%K+m5+1c6W&Yc6O&&;#KYi za0xyG{0*U@p&=n5OeT{~rxS7s!NI{ht9Awk26}jqu-LA432IQH$P*->1%a<=E7fhf7KmpiNEL?|$g$1|@ z&=;ZU0fm$KWEP7BFz+mN_CD$D>KXyBfJUPQ1qHz$U@Noz`t>V3 z0Y5*#_4Rf5=AAr|%l))v3zbTRZXmD;`udA>dIa18XhRr(3s9%iK@&&<11kswKn|(| zCKfuzi4TRu*jp4GWDgcp#{666_U|?<$;@lL6fyrT=v@~MF43;U zZ$6b_)kkb?TQRlFx@L1DFHuvCdiGRV@qSb5o3SyX=dqWIo~SD}lgtU8-0hinIc|l^ zwZpyIy{+Kp3qku~jyuQS{wOJTffqk}ylg!rGf@@8WYp~qRTEe@i_6M#f}U2WR@4MfM)1HJRjPCMx~ z7uM(-p3*y>&A)fhi1A3;cS(L+J1;&pqWL&it>CX0-+eBqO{=g-lKzNGvG(`sNqtRF zeg3%mDzvL|F3Eho_0DyETn8H%dc!?dA!#0>Q=Vr9#{-Ga&|1#6CAW+Q0sfY$`N-&; z^0c=#@%4)8jy6X3lL6u3JAW=?c#tm>K2a$#(Q?|Uz}T^+pWADD@MB|q8>&Xr948aj zsY1|Z7&ad@u4wr4W%TE{WS7U&Rh^yI#(OsCtq$vQ3|A*M_uLm%-A~M)I@BiVUyS#YR$&g!`CNx58tIE z|3D@=NLJY$Q%;J12ZU?1w#&^2c5EzDN05DZLoa2szJ*SEwwg0|Mem<$Yn1cWcz^cq z-}lxrB6iz%k1R&3=>`|KS%};k)l(YskH!jI!g#-(&O>3hx)5(GX8X3fe_&q16?&Zr z)72bEymR&OySDOI3mZ0C3tSEiO=8X3u^BG}uBukDWrMyZ$LQ%Y4sr7@il=hRi%!IK N0G~qvls0mN@PBi2fm#3n literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/tp1n3p08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/tp1n3p08.png new file mode 100644 index 0000000000000000000000000000000000000000..a6c9f35a86271c056e955f637a8769381eb03827 GIT binary patch literal 1483 zcmWlXdpOez7{_0ijw~Xj6ZLfH5mEEBP|@KrEtkTuS}m>SI;>1?8{6D(v$V!$$}kk^ z;zXQM92O%H=ShmnbXw(ZQ8_r3^Xq+{_x=9)zVGMz<@$KL>7tBL0HEvPj`Ks*k)Uu?Q@xn6z>Z3lMM-Q#6tfUZ$jg9xOySS8oYSdsZ?58S}Ku90Gh5`xl#{MR8%wt zP*_-43Lq2;*RS7_pPzr}(xrd%E)}>Vg+~H{0|EmB;S209 z+JEZQsgoy9!bij)vJ2CNNF)M$vKCsqg}Grc6c_?OKR+KIALs+qBGU~U&cS2w^z{7x z{X6{3T?w5=n_Ror-Q69U!8X)ZUHvP8K!G~|wFu*%2C7slr~p15FS4}-9)W zgA1TkDj@~(fB8jCjUG%S0R(${)B5}SM@HTPu)4duH*Un5nj+Sda5!dbYwOg=2pWA9 zg`xngv~oCl+wa{*R;b1Lc@x0-<4`CPvRn@w_UQh+Ac}Mk^OKb>f7-E19*W;}xs}~! zRlIciNyF__FM9f7*5I)-+cbPa1|k-TrQvsONlsb*)pnz$h0|UcNA3@z)MNfwE}Uv0 zIbFH+_Zk14HEp3y&g@1-ft^E-J#CK+WhE5TXruYlt&^-N^P3g#I=H!w`R}L=sT9s{ zCuD1uhCdG)aY*zhVd3F}HZMyY{g2C62iLwz8h1Q_?PFhSvbid>^O4P?YqRlh((eZd z#_aspCH=EBx%*CRO=qIG-J;L_AlZKBgiu)@&Tb|Ii=AapW?Zqp>gYA)sH#J5XB+h; zk9wYVqsyC_bvIrV9&w7WX>VWUAVpP~na{j&$UYNXX^m&}Uyx5RnnjA&4L3VdD!1H_ zD#Iq{ZueA8l;TIb`kpn$X5SsBHj?FpV!HrYcY~@^RN}pDGk;X^V&G~0qorYaOP*rv z^qN~4t3n!C(Ta6lUVkRto3S5$_!4xZm;=Uxwl-UlXk<^TD! zwrXx-kZ%7r>OqrnxrwlgbC>i*G{g?VCRx6GHc+-!v-|w+aMq(Z)3<$!sgDrNgW zt{66;txYft&kQV}Qtm74k`-qO{C2WMU5|#0wUn?oyC~|ghKx56YPk5`Xp_O#x8g2M X(5+n&l!#upArS?SL*BSr7fRay;V71Q literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/z00n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/z00n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..7669eb8385172325c399f3229cfe834f886fecb2 GIT binary patch literal 3172 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj{$|z@zQy;uunKi&25+{%6Mj{~3NT zfY5g^iOI$zg{%daG8T~8Gz1(pwSa?x0cZixpELm=(-{~zY2*W_>0lpFKMoM4)4~E2 z)2SW@=%!OQ4zQR`#UR3NI<+joX*yLbz-Kz;kxamJ3L}}Y>68Q!QKpk^0kNi&7Y8Jm zPDUJ%W;&@sM6&55SwN2I#K!@drV|+lP)Gd0N=r*M+Cfdcn4n{+d)GIuBfe}0=haRfYbdY4|HeAW8JeNk!}u< z>b4FPx}gJ|u3eE@R|Qyfc?U*a*nwS_tjMJg1H3vP0MMC|F?z3LnBH6wQvU~l^-l+Q iz3w1HzgHBc=YRuMlTL2m+d~-u0000811+tMcYXzcm?aHZ#?gdL8ij-oNLTu@go{<42!=C2J(%l`2yyuQm;^LM9XM_s7MQs$ow Yp_Bdc41*5p0G-U>>FVdQ&MBb@0O=}Ly8r+H literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary/z09n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary/z09n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..5f191a78ee5601a45f1add2a3ad7a77b7b1ae0f1 GIT binary patch literal 224 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WmV3H5hE&|zdeNJ&K|z4|!mJGk ze#?uM?J%qpNVJm;SDAGELf+biOrjbV{C6uqc^By}RQhy!A*198PSpVRZwtidR0X(h zmlOPO>811+tMcYXzcm?aHZ#?gdL8ij-oNLTu@go{<42!=C2J(%l`2yyuQm;^LM9XM_s7MQs$ow Yp_Bdct_kk@3Uo4qr>mdKI;Vst043R2BLDyZ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi0g01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi0g01.png new file mode 100644 index 0000000000000000000000000000000000000000..96ed62dbed7614edfd0d3cb58b1713c0a067eb9c GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg-0oT^vIq4j-NF$k*b)!7}^ozyGJ-oIDabg|X_u>_Q(?)khkuR!La7 zu*~chsLPP|}S^f!skB7DWF?ZskqvJ`CRNW>pLl^tak&^VnrV)#A>)9kel<=2?=1C*pefxc2Lag8WRNi0dVN-jzTQVd20h9bP0l+XkKm%fiA literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi0g02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi0g02.png new file mode 100644 index 0000000000000000000000000000000000000000..bb5309885175369e7a44a82b3d71c503ee0e6230 GIT binary patch literal 283 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg*99E{-7)hu==~<~m@&;gasP?#18!r0uz8iWB6Y>=YJ}>w6Iq^h#K8 z;k-L#;k9$^Z^>}_zukc`I~jKX*zo!^M()1+nFD*GaTS& zXpm=kzseGVKswJ)wB`Jv|saDBFsX&Us$iUD<*U(7U&^W}%*vi1t3dlCF zure?xzopr04@Jou>b%7 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi0g04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi0g04.png new file mode 100644 index 0000000000000000000000000000000000000000..2efd4876b56cb502102b73a24c678199e64f646d GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg<*vE{-7)hu>b>$jhL>!|d1`q41lZTUVH+t9bgmC4W3N6|Y@&sq(yy z+<{mBcCzhb&R}q0W{_gIz|g?PV8T$qppwOVi}ypuVxUo~C9V-ADTyViR>?)FK#IZ0 zz|ch3&`8(NIK;@<%D~bJ$TqOBGB7CQxvq+$AvZrIGp&-r(9+UE*T6*Az%<0j$jZRT a$^^(Y2Wqf8*4_!!z~JfX=d#Wzp$P!N$w>GB literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi0g08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi0g08.png new file mode 100644 index 0000000000000000000000000000000000000000..23952137c9a6eff813346fa4db89480e77c87880 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg%~6E{-7)hu>b?$jhL}!|Wh!$maCMUNossom zTJ?)FK#IZ0z|ch3&`8(NIK;@< z%D~bJ$TqOBGB7CQxvq+$AvZrIGp&-r(9+UE*T6*Az%<0j$jZRT$^^(Y2Wqf8*4_!! Oz~JfX=d#Wzp$P!Y-&#@t literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..64ef3f844312534c6c9dc0643ef8df95f07fb676 GIT binary patch literal 274 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg+)vE{-7)hu>b=$a}zmhuKl;g514&?c7hAlVoQ+;=X1hFoDm(DQad) z_ERPn4v!Oz8sE}SF>?qgJow9e^q)^910xelO+CY x-29Zxv`Pj;OG^t~0~1{X(-0#gD+41dQy|+MsKNBi+hm{y22WQ%mvv4FO#tY{R$u@C literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi3p01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi3p01.png new file mode 100644 index 0000000000000000000000000000000000000000..a8599e9a5504cf33f6de68849961c0eb59e77883 GIT binary patch literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg&NEE{-7)hm&i*$G@~^7BcYM-+d<4!Z>(l literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..c911ea9cc2d8793dd5be36477cf563ef6f592650 GIT binary patch literal 286 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg*{XE{-7)hu=K|~gNTDg(5*9HR@U8VWIC~tIe-6W>%TjgStl4D?f?JpO22gg!&d%3atmq~ zFuY}E&5+kPz}f!9M*YHa>7_u+R7+eVN>UO_QmvAUQh^kMk%6I!uAz~xp>c?jv6X?P z6_9OUVP#-Y$a7s4MMG|WN@iLmgQ2CRg|2~#u7PQYk&%^wk(DWsYYx=#DW_c=sDZ)L L)z4*}Q$iB}ib!A; literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..750ef69dc5a1f1433a83616453aa1144a11f8ea5 GIT binary patch literal 331 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg*jLE{-7)hu=;=$lGAR!z_En)5<$Mfwe7()vY1)h1NswKM$_AwSVru z5~Xh46;*uT<2v4r*KT|h`#rZ${z~~4|AVY2RU3?~i{&7%6Eugk`*|>Tz=5t zT+U{T-$0kCmbgZgq$HN4S|t~y0x1R~149#CLnB>7;}9cbD+5a_Altyg%D|wI=ejD2 yhTQy=%(O}dLrY5wT>}$c1Je*ABP#Y~u z*B$5i8g-Kbl@=^GmZjsDF@@uXVnashqQHO7tU?NmX6z^Yeq=M;;pBB_u%jNt$P literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi4a08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi4a08.png new file mode 100644 index 0000000000000000000000000000000000000000..1b7b3a5821bc99816cb5508e544986596a951e51 GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg)a>E{-7)hu>aW$i-m5;d;^B;LQL35xMKuui=T?+2m+2r{MY-k4yW^ zp4a+XIofu^dKxJHzuB$lLFB^RXv zDF!10Lla#?BV9w|5F=wN14}C)+rYxgz@U)lx+;o>-29Zxv`Pj;OG^t~0~1{X(-0#g cD+8b&Aln?MVbZ$}OP~e@Pgg&ebxsLQ07Pz2rT_o{ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi6a08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basi6a08.png new file mode 100644 index 0000000000000000000000000000000000000000..c12484fc58fd2ea0ff4709c6c5093af224580faa GIT binary patch literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg-t{E{-7)hu>Z^MG{133*2~v=nBdG1=3ba_Y#5JNMC9x#cD!C{XNHG{07@FuB8tEDu zhZq@K8CY5Y*#;I?1_p&Z*Huw8-vH$=8 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn0g01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn0g01.png new file mode 100644 index 0000000000000000000000000000000000000000..20f6404a201c49da0a7118c9484403cbb606132b GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg-0oT^vIq4j-NF$k*b)!7}^ozyGJ-oIDabg|X_u>_Q(?)khkuR!La7 zu*~chsLPP|}S^f!skB7DWF?ZskqvJ`CRNW>pLl^tak&^VnrV)#A>)9kel<=2?=1C*pefxc2Lag8WRNi0dVN-jzTQVd20h9=YJ}>w6Iq^h#K8 z;k-L#;k9$^Z^>}_zukc`I~jKX*zo!^M()1+nFD*GaTS& zXpm=kzseGVKswJ)wB`Jv|saDBFsX&Us$iUD<*U(7U&^W}%*vi1t3dlCF zure?xgTe~ HDWM4f9Rpd5 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn0g04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn0g04.png new file mode 100644 index 0000000000000000000000000000000000000000..166e7db2193ad12dc53cc431d58a9cf93b036ebd GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg<*vE{-7)hu>b>$jhL>!|d1`q41lZTUVH+t9bgmC4W3N6|Y@&sq(yy z+<{mBcCzhb&R}q0W{_gIz|g?PV8T$qppwOVi}ypuVxUo~C9V-ADTyViR>?)FK#IZ0 zz|ch3&`8(NIK;@<%D~bJ$TqOBGB7CQxvq+$AvZrIGp&-r(9+UE*T6*Az%<0j$jZRT a$`Hsk2Wlu&>wF8;z~JfX=d#Wzp$P!M(Mab?$jhL}!|Wh!$maCMUNossom zTJ?)FK#IZ0z|ch3&`8(NIK;@< z%D~bJ$TqOBGB7CQxvq+$AvZrIGp&-r(9+UE*T6*Az%<0j$jZRT%E&<1z#ORIwA+D9 Ppaup{S3j3^P6b=$a}zmhuKl;g514&?c7hAlVoQ+;=X1hFoDm(DQad) z_ERPn4v!Oz8sE}SF>?qgJow9e^q)^910xelO+CY z-29Zxv`Pj;OG^t~0~1{X(-0#gD+41dBQsqCbD)M5)4m1+H86O(`njxgN@xNA>`_*f literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn3p01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn3p01.png new file mode 100644 index 0000000000000000000000000000000000000000..77c580b00ae6c173933e5f6f8ae3fd9d50c7cc26 GIT binary patch literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg&NEE{-7)hm&i*$G@~^7BcYM-+d<4!Z>(lK|~gNTDg(5*9HR@U8VWIC~tIe-6W>%TjgStl4D?f?JpO22gg!&d%3atmq~ zFuY}E&5+kPz}f!9M*YHa>7_u+R7+eVN>UO_QmvAUQh^kMk%6I!uAz~xp>c?jv6X?P z6_9OUVP#-Y$a7s4MMG|WN@iLmgQ2CRg|2~#u7PQYk&%^wk(IH5u7NpF1N)l;-+>w! NJYD@<);T3K0RWJ$V7UMQ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..f08c6e99d4b477e8ec7defd7f7cbb58cb815d2b9 GIT binary patch literal 331 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg*jLE{-7)hu=;=$lGAR!z_En)5<$Mfwe7()vY1)h1NswKM$_AwSVru z5~Xh46;*uT<2v4r*KT|h`#rZ${z~~4|AVY2RU3?~i{&7%6Eugk`*|>Tz=5t zT+U{T-$0kCmbgZgq$HN4S|t~y0x1R~149#CLnB>7;}9cbD+5a_Altyg%D|wI=ejD2 zhTQy=%(O}dLrY5wT>}$c1Je*ABP#Y~u z*B$5i8g-Kbl@=^GmZjsDF@@uXVnashqQHO7tU?NmX6z^Yeq=M;;pBBE{-7)hu>aW$i-m5;d;^B;LQL35xMKuui=T?+2m+2r{MY-k4yW^ zp4a+XIofu^dKxJHzuB$lLFB^RXv zDF!10Lla#?BV9w|5F=wN14}C)+rYxgz@U)lx+;o>-29Zxv`Pj;OG^t~0~1{X(-0#g fD+41dV>4X?bD)M4o3Ceq8W=oX{an^LB{Ts5MD|b1 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn6a08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/basn6a08.png new file mode 100644 index 0000000000000000000000000000000000000000..1f54e565df630fc54cf973677e9b7344d639cf25 GIT binary patch literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg-t{E{-7)hu>Z^MG{133*2~v=nBdG1=3ba_Y#5JNMC9x#cD!C{XNHG{07@FuB8tEDu zhZq@K8CY5Y*#;I?1_p&Z*Huw8E{-7)hu>aW$i-m5;d;^B;LQL35xMKuui=T?+2m+2r{MY-k4yW^ zp4a+XIofu^dKxJHzuB$lLFB^RXv zDF!10Lla#?BV9w|5F=wN14}C)+rYxgz@U)lx+;o>-29Zxv`Pj;OG^t~0~1{X(-0#g dD+6OI6Cl?dsNo{Zo9#di44$rjF6*2UngB;aPvrmr literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/bgan6a08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/bgan6a08.png new file mode 100644 index 0000000000000000000000000000000000000000..6cb76f2b43ccd41aeca1eb05eb9863ba4b4129db GIT binary patch literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg-t{E{-7)hu>Z^MG{133*2~v=nBdG1=3ba_Y#5JNMC9x#cD!C{XNHG{07@FuB8tEDu zhZq@K8CY5Y*#;I?1_p&Z*Huw8)WG2B>gTe~DWM4f4eVQZ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/bgbn4a08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/bgbn4a08.png new file mode 100644 index 0000000000000000000000000000000000000000..1086ccc09b46ecd656fecd5d459c657c15d5ae05 GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* zKpF^sI`6IrQeK`ejv*0;-(FhC#bChUdePkA%>Vxpx$D-i;fdSX%rmB{>MwFx^mZVxG7o`Fz z1|tJQ6J0|iT|?s#BV#KAODiDTz{1MFppfUfDvE~O{FKbJN(MtqOAB2C6I}z-5F;Zi c17j-_AlDqI;UdeM?LZ9-p00i_>zopr0M9&3#{d8T literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/bgwn6a08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/bgwn6a08.png new file mode 100644 index 0000000000000000000000000000000000000000..03a0a303d69431d20dfc9d6c7e4ed346303eb62e GIT binary patch literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg-t{E{-7)hu>Z^MG{133*2~v=nBdG1=3ba_Y#5JNMC9x#cD!C{XNHG{07@FuB8tEDu zhZq@K8CY5Y*#;I?1_p&Z*Huw8|fFnGH9xvXzm0Xkxq!^40 z3{7+mjdTr-LyU~A3@ojHYy%4`1A{`I>#8Uka`RI%(<&JZEiEl{4NPzm0Xkxq!^40 z3{7+mjdTr-LyU~A3@ojHYy%4`1A{`I>#8Uka`RI%(<&JZEiEl{4NP6%z+wC-k#Y9)WG2B>gTe~DWM4fysR}V literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s02i3p01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s02i3p01.png new file mode 100644 index 0000000000000000000000000000000000000000..d84f40613e6b2aed5b1c3c0a638ea8599b57327c GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^Od!m`1|*BN@u~nRmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg+-wE{-7_*OOCz{Qqyy3p*F|> literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s03i3p01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s03i3p01.png new file mode 100644 index 0000000000000000000000000000000000000000..51367f7f8e0bf32bbf096160b69349bf1ddf2c2c GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~Ea{HEjtq=#3k+XOiwE-AlDyqr z82*Fcg1yTp14X1gT^vI=t|zDbIR9ZjBi9TGAov&VV9|5Jh@q;EwPMO`W;>uV)e_f; zl9a@fRIB8oR3OD*WMF8bYiOivXdGf>Y-M0+1!NmoSQ!`;@?2L%(U6;;l9^V?U}$M+ op=)5GYhW5;WMpMvY-MDiYhVu4pwhBB9H@c8)78&qol`;+0Pxj18vpuV)e_f; zl9a@fRIB8oR3OD*WMF8bYiOivXdGf>Y-M0+1!NmoSQ!`;@?2L%(U6;;l9^V?U}$M+ op=)5GYhW5;WMpMvY-MPsYhVu4aPs!dKA;8$Pgg&ebxsLQ00NFXK>z>% literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s04i3p01.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s04i3p01.png new file mode 100644 index 0000000000000000000000000000000000000000..ae326c1c4bbdb9c7e51e1054ac3d79df26bbeb00 GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2|pq&xaLGBCC+FnlpB9>`})@^*J& z_z!{$_AZ|c6jAVWaSY+Op8V(k{D=0;LJ3I;AfVHpu!BJ;!R0y=gT!IBkCHlBQ-Nw# zOI#yLQW8s2t&)pUffR$0fuV`6p^>hkafp$zm4T%dkZoXLWnfUqb6pihLvDUbW?ChK rp{1pTu7QcJfoX`5k(Gh5m63t2fjLlvO3UhSpaup{S3j3^P6`})@^*J& z_z!{$_AZ|c6jAVWaSY+Op8V(k{D=0;LJ3I;AfVHpu!BJ;!R0y=gT!IBkCHlBQ-Nw# zOI#yLQW8s2t&)pUffR$0fuV`6p^>hkafp$zm4T%dkZoXLWnfUqb6pihLvDUbW?ChK sp{1pTu7QcJfoX`5k(Gh5m7$rgfjLma$=fsgfEpM)UHx3vIVCg!0P%4>xBvhE literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s05i3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s05i3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..fd41d1d8115a06d935284c91f40b932178007608 GIT binary patch literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=mUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQfg;+TE{-7_*OUL8Kk#8bBku+SAP7^=`%DCCQ!R0gC`m~yNwrEYN(E93Mh1o^x`sx&hQ=XA##RQFRzS9ag_VIp zA^=`%DCCQ!R0gC`m~yNwrEYN(E93Mh1o^x`sx&hQ=XA##RQFRzS9ag_VIp zA;r0G@O1TaS?83{ F1OP&2LP-Ds literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s06i3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s06i3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..73a7b0c64ffebe6b62296460854bb97e53e878f0 GIT binary patch literal 239 zcmeAS@N?(olHy`uVBq!ia0vp^Y#_`5A|IT2?*XJ((j9#r85r9Z7`~Vm50Xssc6VX; z4}uH!E}sk(G4gbA4B@z*{OA0K|Mtvc2M!$of`~q?ANwaaHgcYOozRk?k&twNG2+1* zu8`Fdg^_#=dDplmi8BXw0d=dExJHzuB$lLFB^RXvDF!10Lla#?BV9w|5F=wN14}C) z+rYxgz@U)lx+;o>-29Zxv`Pj;OG^t~0~1{X(-0#gD+6OIBLiInbD##5met`v4Gf;H KelF{r5}E)-K1fFZ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s06n3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s06n3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..e85eac8e8f8618ac6f320da65e51072f6de12003 GIT binary patch literal 239 zcmeAS@N?(olHy`uVBq!ia0vp^Y#_`5A|IT2?*XJ((j9#r85r9Z7`~Vm50Xssc6VX; z4}uH!E}sk(G4gbA4B@z*{OA0K|Mtvc2M!$of`~q?ANwaaHgcYOozRk?k&twNG2+1* zu8`Fdg^_#=dDplmi8BXw0d=dExJHzuB$lLFB^RXvDF!10Lla#?BV9w|5F=wN14}C) z+rYxgz@U)lx+;o>-29Zxv`Pj;OG^t~0~1{X(-0#gD+6OILm<~2sA1-lN0)&b7(8A5 KT-G@yGywoy97)yy literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s07i3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s07i3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..08f61804f4ba73b862d9bf31e5aada044dae1ee9 GIT binary patch literal 249 zcmeAS@N?(olHy`uVBq!ia0vp^>>$j+1|*LJg>$j+1|*LJgbP0l+XkKiN;L> literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s08i3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s08i3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..23d16c71e25c8df5e1f17bad4a7832e89bd4b8df GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEa{HEjtq=#3k+XOiwE-AlDyqr z82*Fcg1yTp14W!XT^vIsF1Pl3@-ZlIFtb0N|CICBtEvkUrh0oBCts49RH4D7@}@#( z1tUXc@}A#qshtJa`WyQn**tNc#`t4t_us6rWy|>f*Z%rB4QQHbiEBhjN@7W>RdP`( zkYX@0Ff`FMG}1LR4ly#eGO)A)vJEV(3=9f+uB)PG$jwj5Osixtw6wI)H89aNFby#> evNABXG6HHa2Wr?Xy{itWfx*+&&t;ucLK6TtHBZ<8 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s08n3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s08n3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..4a46016665abd286a25d958ae4b1c5b9a470b46d GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEa{HEjtq=#3k+XOiwE-AlDyqr z82*Fcg1yTp14W!XT^vIsF1Pl3@-ZlIFtb0N|CICBtEvkUrh0oBCts49RH4D7@}@#( z1tUXc@}A#qshtJa`WyQn**tNc#`t4t_us6rWy|>f*Z%rB4QQHbiEBhjN@7W>RdP`( zkYX@0Ff`FMG}1LR4ly#eGO)A)vJEV(3=9f+uB)PG$jwj5Osixtw6wI)H89aNFby#> evNABXG6ZtXff{B$d2|`5fx*+&&t;ucLK6T;Pf-Z~ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s09i3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s09i3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..ea14f9be0e000d9079a2305b26259234b6f9e810 GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{VEa{HEjtq=#3k+XOiwE-AlDyqr z82*Fcg1yTp14X<%T^vIsF85AwdcG!ScDmhme{?4J9NNta?@PFXlDefiG+N2H84PR{%Hg&|=ZpZJGjrlmkrRZCnW zN>UO_QmvAUQh^kMk%6I!uAz~xp>c?jv6X?P6_9OUVP#-Y$a7s4MMG|WN@iLmgQ2CR ng|2~#u7PQYk&%^wv6T@}gE>&cX6apZKn)C@u6{1-oD!M<7}iv9 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s09n3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s09n3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..7a822537f73bf0cd78f93f717d98e3a658f5ae2b GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{VEa{HEjtq=#3k+XOiwE-AlDyqr z82*Fcg1yTp14X<%T^vIsF85AwdcG!ScDmhme{?4J9NNta?@PFXlDefiG+N2H84PR{%Hg&|=ZpZJGjrlmkrRZCnW zN>UO_QmvAUQh^kMk%6I!uAz~xp>c?jv6X?P6_9OUVP#-Y$a7s4MMG|WN@iLmgQ2CR ng|2~#u7PQYk&%^wv6Uf^YYx;f^U0&jKn)C@u6{1-oD!Mb3K)h=a~;X zoA_yLw#%*AR)m*4g=~I*oznCEXTim0e7<*qydRxMEjY%hjgc*~fP7 zxX%5s)HvAOMsvUYgbq88b3K)h=a~;X zoA_yLw#%*AR)m*4g=~I*oznCEXTim0e7<*qydRxMEjY%hjgc*~fP7 zxX%5s)HvAOMsvUYgbq88zopr0B;tb)Bpeg literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s33i3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s33i3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..0faaa74108eebe50057ae9499393d39730e86096 GIT binary patch literal 470 zcmeAS@N?(olHy`uVBq!ia0vp^iXhCv1|-9u9Lfh$Ea{HEjtq=#3k+XOiwE-AlDyqr z82*Fcg1yTpGcYhpdb&7xXE?#OpofoHSozcc#|tlsRr@bsYtYqFa9vsHSMAN4S> za7SqE>f91DQ`YmugRUh(2ZPxd&Ro)t6={6q!@$7d?Ic)W+T`>r%dA`JK}J(t=dHqA zCfdz>$^6g6=$b; z9(?oL*-OK0*~hA!idjuc6E1dSaGdTlQrM(!ZKm+kZ1C7HKHUXu_V$ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s33n3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s33n3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..599171c7b1a3569a121ed3c7b751c66c92325751 GIT binary patch literal 470 zcmeAS@N?(olHy`uVBq!ia0vp^iXhCv1|-9u9Lfh$Ea{HEjtq=#3k+XOiwE-AlDyqr z82*Fcg1yTpGcYhpdb&7xXE?#OpofoHSozcc#|tlsRr@bsYtYqFa9vsHSMAN4S> za7SqE>f91DQ`YmugRUh(2ZPxd&Ro)t6={6q!@$7d?Ic)W+T`>r%dA`JK}J(t=dHqA zCfdz>$^6g6=$b; z9(?oL*-OK0*~hA!idjuc6E1dSaGdTlQrM(!ZKm+kZ1C7HKHUXu_Vh&(&(E z@>ArT>|ms#D119{?Ys5I^lenc7>y3iT^k82~$if4~t!7e0Iw{E%(~Yk1`V+9>#BW_||>lR{ury z6x?a$u{rKaRh&(&(E z@>ArT>|ms#D119{?Ys5I^lenc7>y3iT^k82~$if4~t!7e0Iw{E%(~Yk1`V+9>#BW_||>lR{ury z6x?a$u{rKaRxXlEs(F-fX7*OW%;{x7lNzp-*Rl&HuD9~zgcceXTF)( z&Zxk|DZ6Wih}}%q)EhffH@`I&O=e7(`MiF?4yDKt76zWzL)bPY+ zrdLb*5qDOehL-COXR~=SANOJF>&=hl4v?DcI3>!%aY7+e>s6x=A2#)js?K@44J1P( zEG!Im?K6@$mpr%n=z3XJ6Stb>-}9XH=hUm%p<`>G6Fdt}Vh}qbIGI$vbQB`?`BVP)t{QGbebpY&X%(esuzB9 z*w5JbH-q{5vYsMf=&6>tMwFx^mZVxG7o`Fz1|tJQ6J0|iT|?s#BV#KAODiDTz{1MF zppfUfDvE~O{FKbJN(MtqOAB2C6I}z-5F;Zi17j;=GhG97poZ>$^F9MLFnGH9xvXxXlEs(F-fX7*OW%;{x7lNzp-*Rl&HuD9~zgcceXTF)( z&Zxk|DZ6Wih}}%q)EhffH@`I&O=e7(`MiF?4yDKt76zWzL)bPY+ zrdLb*5qDOehL-COXR~=SANOJF>&=hl4v?DcI3>!%aY7+e>s6x=A2#)js?K@44J1P( zEG!Im?K6@$mpr%n=z3XJ6Stb>-}9XH=hUm%p<`>G6Fdt}Vh}qbIGI$vbQB`?`BVP)t{QGbebpY&X%(esuzB9 z*w5JbH-q{5vYsMf=&6>tMwFx^mZVxG7o`Fz1|tJQ6J0|iT|?s#BV#KAODiDTz{1MF zppfUfDvE~O{FKbJN(MtqOAB2C6I}z-5F;Zi17j;AAlDqIK|K4Z0Z;>jr>mdKI;Vst E00wZd8UO$Q literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s36i3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s36i3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..d61491fac8816dce998f63673cc1983e193534a6 GIT binary patch literal 448 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBSkfJR9T^zg78t&m77ygJC3(BM zF#HF>1$&oIW?*1s_H=O!i8%ardMsbF0Z;SXPV479wp+hDbUR%xedC@fXW*p8dQiPX zmWyG}CSjF@Dh^s6O*2O++{-vy2@$}Q#e_SOy&iI8)by!i!w^r{J&nb457exi80n#T{ zx43Y%@kp%TPZy6%4Ykdu;sRl!3(D69(sS985lsSC9V-ADTyViR>?)FK#IZ0 zz|ch3&`8(NIK;@<%D~bJ$TqOBGB7CQxvq+$AvZrIGp&-r(9+UE*T6*Az%<0j$jZRj a${5Hs2Wq%|@aa0B1_n=8KbLh*2~7aHMW@gJ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s36n3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s36n3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..1f50479e48ebcbf5c106e1baa714a47fcbee2448 GIT binary patch literal 448 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBSkfJR9T^zg78t&m77ygJC3(BM zF#HF>1$&oIW?*1s_H=O!i8%ardMsbF0Z;SXPV479wp+hDbUR%xedC@fXW*p8dQiPX zmWyG}CSjF@Dh^s6O*2O++{-vy2@$}Q#e_SOy&iI8)by!i!w^r{J&nb457exi80n#T{ zx43Y%@kp%TPZy6%4Ykdu;sRl!3(D69(sS985lsSC9V-ADTyViR>?)FK#IZ0 zz|ch3&`8(NIK;@<%D~bJ$TqOBGB7CQxvq+$AvZrIGp&-r(9+UE*T6*Az%<0j$jZRj a$_U6c2Wk+{erf>Jz~JfX=d#Wzp$Pz@eWo`6 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s37i3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s37i3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..2906fa311661d670be012c178ea9b6c57798af70 GIT binary patch literal 478 zcmeAS@N?(olHy`uVBq!ia0vp^svyk41|*NpQ(y*CEa{HEjtq=#3k+XOiwE-AlDyqr z82*Fcg1yTpGcYhJdb&7xXlJ(ll~0Z((TX8FB!7lPZTPirmv>)5lT&PgCM>|5e? zmIVzn?A8|H9T0ePg6j2gGc!Wi#S)F1S2)~iJJ`l53LZ+nBlwf`LYWRS1-T1^IpkF z$4Wxt??3*%yZbX1pFB2AJtU;{o6;(cHNS(7-;lezz43^7~~k zT7LiRLfw78b$lW>-VHw-Qgf{A{$Jh$TYs;~;lCyqv-|zygU!F6h=1Rw&U>>y=J%fo z?`GdsjW7O`s=!^WrvnPx64!{5l*E!$tK_0oAjM#0U}&OiXryas9Aac_WngIqWE)sm z85k7uTn9%6TtjYtN@iLmgQ2CRg|2~#u7PQYk&%^wv6YE|u7NpF!=u-WE&y#{@O1Ta JS?83{1OQL@!BYSL literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s37n3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s37n3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..8931b859daf0a13f2c648a566023d0cb1ecafd61 GIT binary patch literal 478 zcmeAS@N?(olHy`uVBq!ia0vp^svyk41|*NpQ(y*CEa{HEjtq=#3k+XOiwE-AlDyqr z82*Fcg1yTpGcYhJdb&7xXlJ(ll~0Z((TX8FB!7lPZTPirmv>)5lT&PgCM>|5e? zmIVzn?A8|H9T0ePg6j2gGc!Wi#S)F1S2)~iJJ`l53LZ+nBlwf`LYWRS1-T1^IpkF z$4Wxt??3*%yZbX1pFB2AJtU;{o6;(cHNS(7-;lezz43^7~~k zT7LiRLfw78b$lW>-VHw-Qgf{A{$Jh$TYs;~;lCyqv-|zygU!F6h=1Rw&U>>y=J%fo z?`GdsjW7O`s=!^WrvnPx64!{5l*E!$tK_0oAjM#0U}&OiXryas9Aac_WngIqWE)sm z85k7uTn9%6TtjYtN@iLmgQ2CRg|2~#u7PQYk&%^wv6Zobu7NpF!wbFDCxJFFc)I$z JtaD0e0suu(z?}d9 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s38i3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s38i3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..becf5a1df6935d3524bed7d0b279df4e338b1b8a GIT binary patch literal 439 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^SSkfJR9T^zg78t&m77ygJC3(BM zF#HF>1$&oI28#Uhba4!cIQ(|{MZRVQ9#{YAe~b4wuHRSf__jqi&+Rnx^$7|BML}NP zj3rArJ$JB7Qs&&bV*dAK{%h+ER>(0uQD|KM?c;XWzIW`e`=U7QzdL_DsPn~^eHEj& zpjTx8!}+Dj+h#|yyIGz(_VewBwCN3Nw;S<=Ol8^ezA#7RxxDA&Cdu6uRvls8k>~e& zpX+UzrG4K*OqlJ`mYAKVZ(6Ie-*{Gfk!738hKnW#)oxs^ns@(^f2&1gR1ipoT}U S7hM2qVDNPHb6Mw<&;$Vcd#phK literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s38n3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s38n3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..43f8c983f111c2e7d7812f99aedfc787b68680f8 GIT binary patch literal 439 zcmeAS@N?(olHy`uVBq!ia0vp^Y9P$P1|(P5zFY^SSkfJR9T^zg78t&m77ygJC3(BM zF#HF>1$&oI28#Uhba4!cIQ(|{MZRVQ9#{YAe~b4wuHRSf__jqi&+Rnx^$7|BML}NP zj3rArJ$JB7Qs&&bV*dAK{%h+ER>(0uQD|KM?c;XWzIW`e`=U7QzdL_DsPn~^eHEj& zpjTx8!}+Dj+h#|yyIGz(_VewBwCN3Nw;S<=Ol8^ezA#7RxxDA&Cdu6uRvls8k>~e& zpX+UzrG4K*OqlJ`mYAKVZ(6Ie-*{Gfk!738hKnW#)oxs^ns@(^f2&1gR1ipoSNE St4{(oFnGH9xvXLAR)1|)kH2buyYmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQ85kJNJY5_^A`YLO5xwrPf`D^wr|o6k4O#c))s}iz#AvSl7=BdY>a2)j z1||WqEn8kDE`2)pW`f_`X%pVxFWSP(uz6kl8k1w2PD(Hcq!;%ZdS8%Q+HbJY4;?wdEGtk}LMy>|1O$GL5pd9-b^4bpV=o?Z# zS5tx_Zdg?{W@d+&HSX_ueM)Vc9Gkj-Q~jpJg_F#W@{|C>RJFu4q9i4;B-JXpC>2OC z7#SFv=o%X78XAWf8Cw}xS^?Px7FGrZg*?|)Q8eV{r(~v8G8kG~TId>>=o*-Y7#Ud^ c7+aYDHJAf6l$LD%3Dm&g>FVdQ&MBb@0C};)`v3p{ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s39n3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s39n3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..d37d66d44129066477311007b621745cee75fd73 GIT binary patch literal 499 zcmeAS@N?(olHy`uVBq!ia0vp^>LAR)1|)kH2buyYmUKs7M+U~W1%@xC#RK_lN#5=* z4F5rJ!QSPQ85kJNJY5_^A`YLO5xwrPf`D^wr|o6k4O#c))s}iz#AvSl7=BdY>a2)j z1||WqEn8kDE`2)pW`f_`X%pVxFWSP(uz6kl8k1w2PD(Hcq!;%ZdS8%Q+HbJY4;?wdEGtk}LMy>|1O$GL5pd9-b^4bpV=o?Z# zS5tx_Zdg?{W@d+&HSX_ueM)Vc9Gkj-Q~jpJg_F#W@{|C>RJFu4q9i4;B-JXpC>2OC z7#SFv=o%X78XAWf8Cw}xS^?Px7FGrZg*?|)Q8eV{r(~v8G8kG~TId>>=o*-Y7#Ud^ c7+VzuQ<002!1)diDrSIp2&+xDP9Voi?+@_5alfNvva4X2} z+=@;G2P=;zKYJvJs->WRWJaZS5oNMZu=I%|~=4`WNbdjGPwtIhkafp$zm4T%dkZoXLWnfUqb6pihLvDUbW?ChK qp{1pTu7QcJfoX`5k(Gh5l?hOTIZ#7s$>yIx4Gf;HelF{r5}E*qORR7J literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s40n3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/s40n3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..6f8596ca14510022eb45b8a6e05fca280b354e32 GIT binary patch literal 463 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEEa{HEjtq=#3k+XOiwE-AlDyqr z82*Fcg1yTpGcYg;dAc};L>zuQ<002!1)diDrSIp2&+xDP9Voi?+@_5alfNvva4X2} z+=@;G2P=;zKYJvJs->WRWJaZS5oNMZu=I%|~=4`WNbdjGPwtIhkafp$zm4T%dkZoXLWnfUqb6pihLvDUbW?ChK rp{1pTu7QcJfoX`5k(Gh5l`&9*IZ#9W#$6A98W=oX{an^LB{Ts5ii51A literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/tbbn0g04.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/tbbn0g04.png new file mode 100644 index 0000000000000000000000000000000000000000..8d9f7d52b0446496c9db04c1ab90dd967dbdbf71 GIT binary patch literal 762 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/tbgn3p08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/tbgn3p08.png new file mode 100644 index 0000000000000000000000000000000000000000..fa5cdbc95a44cf76abbf44527dbbd7e12d72c268 GIT binary patch literal 1911 zcmV--2Z;EIP)%G*ochBz3{Q9u#ja*|cB~p&`lXidl&7RNso^yV4RuV$+bDI3KdVdjs zbUH0&W@bdWTz(-5ZO?N!lgWs0zfCedeVOIuWkj(;1j2vy1FWo$s{u~Gb}DH;;Ge8@82ghKaY~jQ3H&2Bw=9N$Oye}y+zO4Zxb0FMo|>$ zxdF=Mvbb^M2FYZSyLazm>N>8QH}PdMc<u>Q!_=1)6?852H~UJv~H{Ne(P6K^qF74E(1J_Ag-dgAcH~-5VmVR;wfu z2_`2er42J=v!Do1Oj{?kFzo~gpgZ}lNlc7dZofI=I7aW zvK}x z6yy3hN7s%Lc84(xgKD)(ypF&EU>F90K!EP2qgXpqb0 zC>AX)UVIl#^8;7Vv;vwI1Yl@rXlnpjmQgAS*ntuh`~@Bb9?@6qqheLKu_5C~8zmC!VeLZN`BX_%(q!i7Ijsq_GUL6)zgst?ii2&Iz3zF+R!R3{w^hr{fs z?|?PnL2GZWe-0r87sD6%YS&kM$(MXu_!J=oilPt<1~Ci+P16uUFhBnWmC6Wk8(IDb zJ9p0F@uYCOEA;l7#D-#919-jL@fu#J13$D>_~KVzpes6tZh!|^1-Gr$G3%?*Xdr~3 zS`9Kc_XbczQU1umgK6UNeiS9j?%gmx-n;2)-T)8?_^=o4;0NvjnbwI*(k1H6I<#+X zwW)3b09ac3HJ0TD&ePwY$KmjB<;p18?7Pg*k25k7e{8G%m;#>1yjwb zc8}445JF&CR_E@pELd2WXh%QI_U#V7`Q`|kHVC9}yRR`a65Rx_p$NBkb+ta2fCD@k z50&K#T9?L(w$j>f16Y=YEXy4Q3WeR&>jB_zba&@@^eBU2^Z;60U7bWC@ni*p!62%p zLKE=Az;^?%0`x`aLuIps8ab&p${F`8~iK46^%PUBd4M;FL8pP#t zNlye&6h#_z4z>V%u&cNW&yoj1fZj?6%^wx8EDOUhFin$m+JR*?fi&T;PItG1V9zO&u|@HhNeb_<}>g0frzR&hEF za=C79-W&sb^z{6Rk3W8oLx&DMb~4(efU2s}hrjz!`sBzbc-^fxeRMh68VIb`QY@vT zz}nhcJ0-KgGHYu#u3g)UVKfMb|IYdIr#NzCV#~2?%c(e#NJytopJrfSfcWirD|(~d zB(k|71ps}0w^5W8EDKS=+{B;I011*UEQJ~Ri zU|AMkZ-K6^i#Qx9UVXKTsi}{MM0P)QLVoI0yl!*zb7JzJlT`0jd2`#Fxb1EfMd9JY zhosYKOw;7Sg9jWwe3&z5&Y-I5GmYoZ3;>{MnmBv*Ea`L_S(cGynS1x{k3v)jE;^zxAPYQ0I1jNqEIN%+uQqpIe%e*pThnN3zbN{>Y)K>0000bbVXQnWMOn= zI%9HWVRU5xGB7bTEip1JF*8&$GdeIiIx#XWFgQ9eFmVjmB>(^bC3HntbYx+4Wjbwd xWNBu305UK!F)c7OEio`uF)}(bF*-0bEigAaFffPyJZAs^002ovPDHLkV1hJDb{GHv literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/tbrn2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/tbrn2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..bbe748fd32d611cece365935600b1cbc400ae247 GIT binary patch literal 1901 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/tbyn3p08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/tbyn3p08.png new file mode 100644 index 0000000000000000000000000000000000000000..4fbdb36a33d7f3c3c82addd028dffed7a0d3f0ed GIT binary patch literal 1911 zcmV--2Z;EIP)%G*ochBz3{Q9u#ja*|cB~p&`lXidl&7RNso^yV4RuV$+bDI3KdVdjs zbUH0&W@bdWTz(-5ZO?N!lgWs0zfCedeVOIuWkj(;1j2vy1FWo$s{u~Gb}DH;;Ge8@82ghKaY~jQ3H&2Bw=9N$Oye}y+zO4Zxb0FMo|>$ zxdF=Mvbb^M2FYZSyLazm>N>8QH}PdMc<u>Q!_=1)6?852H~UJv~H{Ne(P6K^qF74E(1J_Ag-dgAcH~-5VmVR;wfu z2_`2er42J=v!Do1Oj{?kFzo~gpgZ}lNlc7dZofI=I7aW zvK}x z6yy3hN7s%Lc84(xgKD)(ypF&EU>F90K!EP2qgXpqb0 zC>AX)UVIl#^8;7Vv;vwI1Yl@rXlnpjmQgAS*ntuh`~@Bb9?@6qqheLKu_5C~8zmC!VeLZN`BX_%(q!i7Ijsq_GUL6)zgst?ii2&Iz3zF+R!R3{w^hr{fs z?|?PnL2GZWe-0r87sD6%YS&kM$(MXu_!J=oilPt<1~Ci+P16uUFhBnWmC6Wk8(IDb zJ9p0F@uYCOEA;l7#D-#919-jL@fu#J13$D>_~KVzpes6tZh!|^1-Gr$G3%?*Xdr~3 zS`9Kc_XbczQU1umgK6UNeiS9j?%gmx-n;2)-T)8?_^=o4;0NvjnbwI*(k1H6I<#+X zwW)3b09ac3HJ0TD&ePwY$KmjB<;p18?7Pg*k25k7e{8G%m;#>1yjwb zc8}445JF&CR_E@pELd2WXh%QI_U#V7`Q`|kHVC9}yRR`a65Rx_p$NBkb+ta2fCD@k z50&K#T9?L(w$j>f16Y=YEXy4Q3WeR&>jB_zba&@@^eBU2^Z;60U7bWC@ni*p!62%p zLKE=Az;^?%0`x`aLuIps8ab&p${F`8~iK46^%PUBd4M;FL8pP#t zNlye&6h#_z4z>V%u&cNW&yoj1fZj?6%^wx8EDOUhFin$m+JR*?fi&T;PItG1V9zO&u|@HhNeb_<}>g0frzR&hEF za=C79-W&sb^z{6Rk3W8oLx&DMb~4(efU2s}hrjz!`sBzbc-^fxeRMh68VIb`QY@vT zz}nhcJ0-KgGHYu#u3g)UVKfMb|IYdIr#NzCV#~2?%c(e#NJytopJrfSfcWirD|(~d zB(k|71ps}0w^5W8EDKS=+{B;I011*UEQJ~Ri zU|AMkZ-K6^i#Qx9UVXKTsi}{MM0P)QLVoI0yl!*zb7JzJlT`0jd2`#Fxb1EfMd9JY zhosYKOw;7Sg9jWwe3&z5&Y-I5GmYoZ3;>{MnmBv*Ea`L_S(cGynS1x{k3v)jE;^zxAPYQ0I1jNqEIN%+uQqpIe%e*pThnN3zbN{>Y)K>0000bbVXQnWMOn= zI%9HWVRU5xGB7bTEip1JF*8&$GdeIiIx#XWFgQ9eFmVjmB>(^bC3HntbYx+4Wjbwd xWNBu305UK!F)c7OEio`uF)}(bF*-0bEigAaFffPyJZAs^002ovPDHLkV1myucDn!o literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/tm3n3p02.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/tm3n3p02.png new file mode 100644 index 0000000000000000000000000000000000000000..babdebefb5f83c6d969414485fe31d9a3b8486b3 GIT binary patch literal 306 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/tp0n3p08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/tp0n3p08.png new file mode 100644 index 0000000000000000000000000000000000000000..e5a29d6d627bee342ee10dfc9140169e96a05163 GIT binary patch literal 1959 zcmV;Y2Uz%tP)r| z0M6{t%uG}ytJi7LDy?d@s!}gnX#%Q=N?Z&D2@M1nfD%x|1rn59pn`;i)=pE46e^T9 zTA3oDsDwdNldOhdAT%F1{%9Q%+d0R_=iP;kS(XqQF4jjnvR}*Zd4A{re4OVs96EGJ z@DrN!Q#uDQKJUgIJ9aQJF+ngG+qNYG7@m~_ZEbBtA`z}#yT-kH_ps-3m>xW!CK^RfCdmO>SxPUktf`6BZ@UF>BHX=um!hIjH8_Yf9>*~}%rYPgq=8wNi+p`O-wh8} z_HR}coMtoe_uixN<(CPCLIi_BIy*b(0aS8e|Ni|%A`v2y2uF?_p;#nQ3Oe{+hwTL1bCR@Anf725D+) z;^fJba{-2ihUo6@CZEqEgg}xc)<&aj7#>CeOhCcqV$|>F?y6O+i$rK18Nm-Yl1WG= znFhWugM9@oTkgSZu>c^1V0d_#fq?-wZQ3+n0k_+W*=(k&stQfh$mjDU*R8{M=MKW{ z=2j@gWMd;jD&=iF5a64c8GaUxvhnIwRv3-gfvl#HSi2Tpd=abNUU8%-3aM0zTrP*- z?`M2`d@g{)VM7Rkq9_#@x`okUt%h_tjUm|zl6EEY+n zQcO=zQz#VBG>t$YKyPpFTmYxjiK?r7T>CLGM~tu%=B?Sc@Y=j+nnpI8C7;hfv#f*= zmCvcEDbndQilU%t8k(lzcDp%!`tJu(FFOYNQsHv&J=krlr zU0o{4behr8QL@=QJw1OWli31%izHpgVz~vVJbEOv@#h<#4&e2AS)N}Gv%q)d@~j_1 z2*G9VWxibbC13Cb=hEj8LLkdB9*+l2)5v5p2qB0@-(+T{3Al|U{gV|dZeX{^u-Rr< zy}C%SG5B-&}(gDGQz0^ov%ZyR6|sE1e~ z20EaDQ+8II>$;AjC?pagBq;~{3y-IOEKedylNby}U=y1+dpLIN*x~>k9UZK?3xl0hCfWC_GU>*L5^aqgX65HfBNB3&0p&uR>j&1&^l+A$|*dLpZ#Fg>2qL zwBEPA5_H1qQLCzY&j%1fQQV^8W|Ya zfTpQ0;;N_>pBicn%dgSSS&GKeYKXp zzCD~je~xF_Gsp6djt-*HD4+fMGg{-VG-sL#C;^JaVg*15!Sdx!>gp`)-rYiXcRL?^ zaN?QHVF?G5PyhZYpYQ&hix)34Ha3P{(z;Rw27`gNwl=nI-Aa3V`;Wct(hepkPMl!- z_U(kj;Yt%yRTYE5!29p-(^bC3HntbYx+4WjbwdWNBu3 t05UK!F)c7OEio`uF)}(bF*-0bEigAaFffPyJZAs^002ovPDHLkV1m3UeQ^K) literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/tp1n3p08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/tp1n3p08.png new file mode 100644 index 0000000000000000000000000000000000000000..9ecd40470c4d39cdedd3d6dcb0960f955a731b5c GIT binary patch literal 1911 zcmV--2Z;EIP)%G*ochBz3{Q9u#ja*|cB~p&`lXidl&7RNso^yV4RuV$+bDI3KdVdjs zbUH0&W@bdWTz(-5ZO?N!lgWs0zfCedeVOIuWkj(;1j2vy1FWo$s{u~Gb}DH;;Ge8@82ghKaY~jQ3H&2Bw=9N$Oye}y+zO4Zxb0FMo|>$ zxdF=Mvbb^M2FYZSyLazm>N>8QH}PdMc<u>Q!_=1)6?852H~UJv~H{Ne(P6K^qF74E(1J_Ag-dgAcH~-5VmVR;wfu z2_`2er42J=v!Do1Oj{?kFzo~gpgZ}lNlc7dZofI=I7aW zvK}x z6yy3hN7s%Lc84(xgKD)(ypF&EU>F90K!EP2qgXpqb0 zC>AX)UVIl#^8;7Vv;vwI1Yl@rXlnpjmQgAS*ntuh`~@Bb9?@6qqheLKu_5C~8zmC!VeLZN`BX_%(q!i7Ijsq_GUL6)zgst?ii2&Iz3zF+R!R3{w^hr{fs z?|?PnL2GZWe-0r87sD6%YS&kM$(MXu_!J=oilPt<1~Ci+P16uUFhBnWmC6Wk8(IDb zJ9p0F@uYCOEA;l7#D-#919-jL@fu#J13$D>_~KVzpes6tZh!|^1-Gr$G3%?*Xdr~3 zS`9Kc_XbczQU1umgK6UNeiS9j?%gmx-n;2)-T)8?_^=o4;0NvjnbwI*(k1H6I<#+X zwW)3b09ac3HJ0TD&ePwY$KmjB<;p18?7Pg*k25k7e{8G%m;#>1yjwb zc8}445JF&CR_E@pELd2WXh%QI_U#V7`Q`|kHVC9}yRR`a65Rx_p$NBkb+ta2fCD@k z50&K#T9?L(w$j>f16Y=YEXy4Q3WeR&>jB_zba&@@^eBU2^Z;60U7bWC@ni*p!62%p zLKE=Az;^?%0`x`aLuIps8ab&p${F`8~iK46^%PUBd4M;FL8pP#t zNlye&6h#_z4z>V%u&cNW&yoj1fZj?6%^wx8EDOUhFin$m+JR*?fi&T;PItG1V9zO&u|@HhNeb_<}>g0frzR&hEF za=C79-W&sb^z{6Rk3W8oLx&DMb~4(efU2s}hrjz!`sBzbc-^fxeRMh68VIb`QY@vT zz}nhcJ0-KgGHYu#u3g)UVKfMb|IYdIr#NzCV#~2?%c(e#NJytopJrfSfcWirD|(~d zB(k|71ps}0w^5W8EDKS=+{B;I011*UEQJ~Ri zU|AMkZ-K6^i#Qx9UVXKTsi}{MM0P)QLVoI0yl!*zb7JzJlT`0jd2`#Fxb1EfMd9JY zhosYKOw;7Sg9jWwe3&z5&Y-I5GmYoZ3;>{MnmBv*Ea`L_S(cGynS1x{k3v)jE;^zxAPYQ0I1jNqEIN%+uQqpIe%e*pThnN3zbN{>Y)K>0000bbVXQnWMOn= zI%9HWVRU5xGB7bTEip1JF*8&$GdeIiIx#XWFgQ9eFmVjmB>(^bC3HntbYx+4Wjbwd xWNBu305UK!F)c7OEio`uF)}(bF*-0bEigAaFffPyJZAs^002ovPDHLkV1jhUc%J|O literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/z00n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/z00n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..ecaa0d8bab72b7e37df0e032d9d28b2479f59ce1 GIT binary patch literal 422 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UV>C6dj$D1FjT2AFf_CjA5L~c#`DCET22U5qkch)aFYV-NFc4t5AgJ@`fBl-5Nx~maZQOnHuo_33xy9zG zO_LZ?7}9O4-%rWkQC^n9Vy9{3u!`}Vg^Y#LzXQGOAs>#-W&TrmoOes%a^5e6?Oexp z6gF7qNi?)c_fF`O?wQc89e&`rDD#PerMxE!TY1+!>=s?~n47Vr;33!629nO4bQ qXlZGoYha>lU>ag%WMyD#WeU__4%D#az+nZT1_n=8KbLh*2~7Z8tc<<@ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/z03n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/z03n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..ecaa0d8bab72b7e37df0e032d9d28b2479f59ce1 GIT binary patch literal 422 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UV>C6dj$D1FjT2AFf_CjA5L~c#`DCET22U5qkch)aFYV-NFc4t5AgJ@`fBl-5Nx~maZQOnHuo_33xy9zG zO_LZ?7}9O4-%rWkQC^n9Vy9{3u!`}Vg^Y#LzXQGOAs>#-W&TrmoOes%a^5e6?Oexp z6gF7qNi?)c_fF`O?wQc89e&`rDD#PerMxE!TY1+!>=s?~n47Vr;33!629nO4bQ qXlZGoYha>lU>ag%WMyD#WeU__4%D#az+nZT1_n=8KbLh*2~7Z8tc<<@ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/z06n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/z06n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..ecaa0d8bab72b7e37df0e032d9d28b2479f59ce1 GIT binary patch literal 422 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UV>C6dj$D1FjT2AFf_CjA5L~c#`DCET22U5qkch)aFYV-NFc4t5AgJ@`fBl-5Nx~maZQOnHuo_33xy9zG zO_LZ?7}9O4-%rWkQC^n9Vy9{3u!`}Vg^Y#LzXQGOAs>#-W&TrmoOes%a^5e6?Oexp z6gF7qNi?)c_fF`O?wQc89e&`rDD#PerMxE!TY1+!>=s?~n47Vr;33!629nO4bQ qXlZGoYha>lU>ag%WMyD#WeU__4%D#az+nZT1_n=8KbLh*2~7Z8tc<<@ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/primary_check/z09n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/primary_check/z09n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..d869f992f80e61f073ea0f55a05aef941bfb6842 GIT binary patch literal 422 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UV>C6dj$D1FjT2AFf_CjA5L~c#`DCET22U5qkch)aFYV-NFc4t5AgJ@`fBl-5Nx~maZQOnHuo_33xy9zG zO_LZ?7}9O4-%rWkQC^n9Vy9{3u!`}Vg^Y#LzXQGOAs>#-W&TrmoOes%a^5e6?Oexp z6gF7qNi?)c_fF`O?wQc89e&`rDD#PerMxE!TY1+!>=s?~n47Vr;33!629nO4bQ qXlZGoYha>lU>ag%WMyD#WeQ}Q12tS1_$CF^z~JfX=d#Wzp$Pz2HjH2Z literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/ref_results.csv b/source/engine/thirdparty/stb/tests/pngsuite/ref_results.csv new file mode 100644 index 0000000..74dc2e6 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/pngsuite/ref_results.csv @@ -0,0 +1,259 @@ +filename,width,height,ncomp,error,hash +pngsuite/16bit/basi0g16.png,32,32,1,,0xfc8f2f99 +pngsuite/16bit/basi2c16.png,32,32,3,,0x65567ed5 +pngsuite/16bit/basi4a16.png,32,32,2,,0x198cf245 +pngsuite/16bit/basi6a16.png,32,32,4,,0x3016e9b5 +pngsuite/16bit/basn0g16.png,32,32,1,,0xfc8f2f99 +pngsuite/16bit/basn2c16.png,32,32,3,,0x65567ed5 +pngsuite/16bit/basn4a16.png,32,32,2,,0x198cf245 +pngsuite/16bit/basn6a16.png,32,32,4,,0x3016e9b5 +pngsuite/16bit/bgai4a16.png,32,32,2,,0x198cf245 +pngsuite/16bit/bgan6a16.png,32,32,4,,0x3016e9b5 +pngsuite/16bit/bggn4a16.png,32,32,2,,0x198cf245 +pngsuite/16bit/bgyn6a16.png,32,32,4,,0x3016e9b5 +pngsuite/16bit/oi1n0g16.png,32,32,1,,0xfc8f2f99 +pngsuite/16bit/oi1n2c16.png,32,32,3,,0x65567ed5 +pngsuite/16bit/oi2n0g16.png,32,32,1,,0xfc8f2f99 +pngsuite/16bit/oi2n2c16.png,32,32,3,,0x65567ed5 +pngsuite/16bit/oi4n0g16.png,32,32,1,,0xfc8f2f99 +pngsuite/16bit/oi4n2c16.png,32,32,3,,0x65567ed5 +pngsuite/16bit/oi9n0g16.png,32,32,1,,0xfc8f2f99 +pngsuite/16bit/oi9n2c16.png,32,32,3,,0x65567ed5 +pngsuite/16bit/tbbn2c16.png,32,32,4,,0xaa9bfe44 +pngsuite/16bit/tbgn2c16.png,32,32,4,,0xaa9bfe44 +pngsuite/16bit/tbwn0g16.png,32,32,2,,0x075e519a +pngsuite/corrupt/xc1n0g08.png,32,32,2,bad ctype,0x00000000 +pngsuite/corrupt/xc9n2c08.png,32,32,2,bad ctype,0x00000000 +pngsuite/corrupt/xcrn0g04.png,32,32,2,unknown image type,0x00000000 +pngsuite/corrupt/xcsn0g01.png,32,32,1,,0x43b9891f +pngsuite/corrupt/xd0n2c08.png,32,32,1,1/2/4/8/16-bit only,0x00000000 +pngsuite/corrupt/xd3n2c08.png,32,32,1,1/2/4/8/16-bit only,0x00000000 +pngsuite/corrupt/xd9n2c08.png,32,32,1,1/2/4/8/16-bit only,0x00000000 +pngsuite/corrupt/xdtn0g01.png,32,32,1,no IDAT,0x00000000 +pngsuite/corrupt/xhdn0g08.png,32,32,1,,0x414f1ca9 +pngsuite/corrupt/xlfn0g04.png,32,32,1,unknown image type,0x00000000 +pngsuite/corrupt/xs1n0g01.png,32,32,1,unknown image type,0x00000000 +pngsuite/corrupt/xs2n0g01.png,32,32,1,unknown image type,0x00000000 +pngsuite/corrupt/xs4n0g01.png,32,32,1,unknown image type,0x00000000 +pngsuite/corrupt/xs7n0g01.png,32,32,1,unknown image type,0x00000000 +pngsuite/iphone/iphone_basi0g01.png,32,32,4,,0x5fb33cfd +pngsuite/iphone/iphone_basi0g02.png,32,32,4,,0x5bbe95c5 +pngsuite/iphone/iphone_basi3p02.png,32,32,4,,0x50ba29c5 +pngsuite/iphone/iphone_bgwn6a08.png,32,32,4,,0x45d8548a +pngsuite/iphone/iphone_bgyn6a16.png,32,32,4,,0x4b2b7545 +pngsuite/iphone/iphone_tbyn3p08.png,32,32,4,,0x8ea9aaaf +pngsuite/iphone/iphone_z06n2c08.png,32,32,4,,0xb5dd034b +pngsuite/primary/basi0g01.png,32,32,1,,0x43b9891f +pngsuite/primary/basi0g02.png,32,32,1,,0xaf0bb3c5 +pngsuite/primary/basi0g04.png,32,32,1,,0x6fbaeb45 +pngsuite/primary/basi0g08.png,32,32,1,,0x414f1ca9 +pngsuite/primary/basi2c08.png,32,32,3,,0x522345c5 +pngsuite/primary/basi3p01.png,32,32,3,,0x9c5b75c5 +pngsuite/primary/basi3p02.png,32,32,3,,0x46f26ec5 +pngsuite/primary/basi3p04.png,32,32,3,,0x35b2e4a5 +pngsuite/primary/basi3p08.png,32,32,3,,0xfe066865 +pngsuite/primary/basi4a08.png,32,32,2,,0x77cbbfa5 +pngsuite/primary/basi6a08.png,32,32,4,,0xb472197d +pngsuite/primary/basn0g01.png,32,32,1,,0x43b9891f +pngsuite/primary/basn0g02.png,32,32,1,,0xaf0bb3c5 +pngsuite/primary/basn0g04.png,32,32,1,,0x6fbaeb45 +pngsuite/primary/basn0g08.png,32,32,1,,0x414f1ca9 +pngsuite/primary/basn2c08.png,32,32,3,,0x522345c5 +pngsuite/primary/basn3p01.png,32,32,3,,0x9c5b75c5 +pngsuite/primary/basn3p02.png,32,32,3,,0x46f26ec5 +pngsuite/primary/basn3p04.png,32,32,3,,0x35b2e4a5 +pngsuite/primary/basn3p08.png,32,32,3,,0xfe066865 +pngsuite/primary/basn4a08.png,32,32,2,,0x77cbbfa5 +pngsuite/primary/basn6a08.png,32,32,4,,0xb472197d +pngsuite/primary/bgai4a08.png,32,32,2,,0x77cbbfa5 +pngsuite/primary/bgan6a08.png,32,32,4,,0xb472197d +pngsuite/primary/bgbn4a08.png,32,32,2,,0x77cbbfa5 +pngsuite/primary/bgwn6a08.png,32,32,4,,0xb472197d +pngsuite/primary/s01i3p01.png,1,1,3,,0xafb003b6 +pngsuite/primary/s01n3p01.png,1,1,3,,0xafb003b6 +pngsuite/primary/s02i3p01.png,2,2,3,,0x96f3dd85 +pngsuite/primary/s02n3p01.png,2,2,3,,0x96f3dd85 +pngsuite/primary/s03i3p01.png,3,3,3,,0xb0cf1241 +pngsuite/primary/s03n3p01.png,3,3,3,,0xb0cf1241 +pngsuite/primary/s04i3p01.png,4,4,3,,0xbfcedd75 +pngsuite/primary/s04n3p01.png,4,4,3,,0xbfcedd75 +pngsuite/primary/s05i3p02.png,5,5,3,,0xc322cedd +pngsuite/primary/s05n3p02.png,5,5,3,,0xc322cedd +pngsuite/primary/s06i3p02.png,6,6,3,,0x46916799 +pngsuite/primary/s06n3p02.png,6,6,3,,0x46916799 +pngsuite/primary/s07i3p02.png,7,7,3,,0xfdabc297 +pngsuite/primary/s07n3p02.png,7,7,3,,0xfdabc297 +pngsuite/primary/s08i3p02.png,8,8,3,,0x8f036d09 +pngsuite/primary/s08n3p02.png,8,8,3,,0x8f036d09 +pngsuite/primary/s09i3p02.png,9,9,3,,0x16a46830 +pngsuite/primary/s09n3p02.png,9,9,3,,0x16a46830 +pngsuite/primary/s32i3p04.png,32,32,3,,0x4bd4fbd3 +pngsuite/primary/s32n3p04.png,32,32,3,,0x4bd4fbd3 +pngsuite/primary/s33i3p04.png,33,33,3,,0x51aa005e +pngsuite/primary/s33n3p04.png,33,33,3,,0x51aa005e +pngsuite/primary/s34i3p04.png,34,34,3,,0x84818775 +pngsuite/primary/s34n3p04.png,34,34,3,,0x84818775 +pngsuite/primary/s35i3p04.png,35,35,3,,0x6359ec75 +pngsuite/primary/s35n3p04.png,35,35,3,,0x6359ec75 +pngsuite/primary/s36i3p04.png,36,36,3,,0xe4878065 +pngsuite/primary/s36n3p04.png,36,36,3,,0xe4878065 +pngsuite/primary/s37i3p04.png,37,37,3,,0x3cefc423 +pngsuite/primary/s37n3p04.png,37,37,3,,0x3cefc423 +pngsuite/primary/s38i3p04.png,38,38,3,,0xffc55a2b +pngsuite/primary/s38n3p04.png,38,38,3,,0xffc55a2b +pngsuite/primary/s39i3p04.png,39,39,3,,0x0c790240 +pngsuite/primary/s39n3p04.png,39,39,3,,0x0c790240 +pngsuite/primary/s40i3p04.png,40,40,3,,0x951a316d +pngsuite/primary/s40n3p04.png,40,40,3,,0x951a316d +pngsuite/primary/tbbn0g04.png,32,32,2,,0x9c8410ea +pngsuite/primary/tbbn3p08.png,32,32,4,,0x82bf9a57 +pngsuite/primary/tbgn3p08.png,32,32,4,,0x82bf9a57 +pngsuite/primary/tbrn2c08.png,32,32,4,,0xaa9bfe44 +pngsuite/primary/tbwn3p08.png,32,32,4,,0x82bf9a57 +pngsuite/primary/tbyn3p08.png,32,32,4,,0x82bf9a57 +pngsuite/primary/tm3n3p02.png,32,32,4,,0xf59745c5 +pngsuite/primary/tp0n0g08.png,32,32,1,,0xbac0864c +pngsuite/primary/tp0n2c08.png,32,32,3,,0x82687c37 +pngsuite/primary/tp0n3p08.png,32,32,3,,0x61f54e37 +pngsuite/primary/tp1n3p08.png,32,32,4,,0x82bf9a57 +pngsuite/primary/z00n2c08.png,32,32,3,,0x65b4a72f +pngsuite/primary/z03n2c08.png,32,32,3,,0x65b4a72f +pngsuite/primary/z06n2c08.png,32,32,3,,0x65b4a72f +pngsuite/primary/z09n2c08.png,32,32,3,,0x65b4a72f +pngsuite/primary_check/basi0g01.png,32,32,4,,0x5fb33cfd +pngsuite/primary_check/basi0g02.png,32,32,4,,0x5bbe95c5 +pngsuite/primary_check/basi0g04.png,32,32,4,,0x3468b9c5 +pngsuite/primary_check/basi0g08.png,32,32,4,,0x262ef46d +pngsuite/primary_check/basi2c08.png,32,32,4,,0x1fc92bc5 +pngsuite/primary_check/basi3p01.png,32,32,4,,0x28a3e1c5 +pngsuite/primary_check/basi3p02.png,32,32,4,,0x803be5c5 +pngsuite/primary_check/basi3p04.png,32,32,4,,0xf3fc60e5 +pngsuite/primary_check/basi3p08.png,32,32,4,,0x30ef4f45 +pngsuite/primary_check/basi4a08.png,32,32,4,,0x23c8536d +pngsuite/primary_check/basi6a08.png,32,32,4,,0xb472197d +pngsuite/primary_check/basn0g01.png,32,32,4,,0x5fb33cfd +pngsuite/primary_check/basn0g02.png,32,32,4,,0x5bbe95c5 +pngsuite/primary_check/basn0g04.png,32,32,4,,0x3468b9c5 +pngsuite/primary_check/basn0g08.png,32,32,4,,0x262ef46d +pngsuite/primary_check/basn2c08.png,32,32,4,,0x1fc92bc5 +pngsuite/primary_check/basn3p01.png,32,32,4,,0x28a3e1c5 +pngsuite/primary_check/basn3p02.png,32,32,4,,0x803be5c5 +pngsuite/primary_check/basn3p04.png,32,32,4,,0xf3fc60e5 +pngsuite/primary_check/basn3p08.png,32,32,4,,0x30ef4f45 +pngsuite/primary_check/basn4a08.png,32,32,4,,0x23c8536d +pngsuite/primary_check/basn6a08.png,32,32,4,,0xb472197d +pngsuite/primary_check/bgai4a08.png,32,32,4,,0x23c8536d +pngsuite/primary_check/bgan6a08.png,32,32,4,,0xb472197d +pngsuite/primary_check/bgbn4a08.png,32,32,4,,0x23c8536d +pngsuite/primary_check/bgwn6a08.png,32,32,4,,0xb472197d +pngsuite/primary_check/s01i3p01.png,1,1,4,,0xdb152beb +pngsuite/primary_check/s01n3p01.png,1,1,4,,0xdb152beb +pngsuite/primary_check/s02i3p01.png,2,2,4,,0xa344a3a5 +pngsuite/primary_check/s02n3p01.png,2,2,4,,0xa344a3a5 +pngsuite/primary_check/s03i3p01.png,3,3,4,,0x594d3bfa +pngsuite/primary_check/s03n3p01.png,3,3,4,,0x594d3bfa +pngsuite/primary_check/s04i3p01.png,4,4,4,,0xd59d4605 +pngsuite/primary_check/s04n3p01.png,4,4,4,,0xd59d4605 +pngsuite/primary_check/s05i3p02.png,5,5,4,,0x41e58366 +pngsuite/primary_check/s05n3p02.png,5,5,4,,0x41e58366 +pngsuite/primary_check/s06i3p02.png,6,6,4,,0xcad1a885 +pngsuite/primary_check/s06n3p02.png,6,6,4,,0xcad1a885 +pngsuite/primary_check/s07i3p02.png,7,7,4,,0x09184108 +pngsuite/primary_check/s07n3p02.png,7,7,4,,0x09184108 +pngsuite/primary_check/s08i3p02.png,8,8,4,,0x4fd11cad +pngsuite/primary_check/s08n3p02.png,8,8,4,,0x4fd11cad +pngsuite/primary_check/s09i3p02.png,9,9,4,,0xc50dbecd +pngsuite/primary_check/s09n3p02.png,9,9,4,,0xc50dbecd +pngsuite/primary_check/s32i3p04.png,32,32,4,,0x95cbb1d3 +pngsuite/primary_check/s32n3p04.png,32,32,4,,0x95cbb1d3 +pngsuite/primary_check/s33i3p04.png,33,33,4,,0x6649fc5b +pngsuite/primary_check/s33n3p04.png,33,33,4,,0x6649fc5b +pngsuite/primary_check/s34i3p04.png,34,34,4,,0x35b98e15 +pngsuite/primary_check/s34n3p04.png,34,34,4,,0x35b98e15 +pngsuite/primary_check/s35i3p04.png,35,35,4,,0xc9ddf938 +pngsuite/primary_check/s35n3p04.png,35,35,4,,0xc9ddf938 +pngsuite/primary_check/s36i3p04.png,36,36,4,,0x7bb4e1cd +pngsuite/primary_check/s36n3p04.png,36,36,4,,0x7bb4e1cd +pngsuite/primary_check/s37i3p04.png,37,37,4,,0xee50001c +pngsuite/primary_check/s37n3p04.png,37,37,4,,0xee50001c +pngsuite/primary_check/s38i3p04.png,38,38,4,,0x51b76813 +pngsuite/primary_check/s38n3p04.png,38,38,4,,0x51b76813 +pngsuite/primary_check/s39i3p04.png,39,39,4,,0x42f23327 +pngsuite/primary_check/s39n3p04.png,39,39,4,,0x42f23327 +pngsuite/primary_check/s40i3p04.png,40,40,4,,0xf91b6a7d +pngsuite/primary_check/s40n3p04.png,40,40,4,,0xf91b6a7d +pngsuite/primary_check/tbbn0g04.png,32,32,4,,0x8a0117a4 +pngsuite/primary_check/tbbn3p08.png,32,32,4,,0x82bf9a57 +pngsuite/primary_check/tbgn3p08.png,32,32,4,,0x82bf9a57 +pngsuite/primary_check/tbrn2c08.png,32,32,4,,0xaa9bfe44 +pngsuite/primary_check/tbwn3p08.png,32,32,4,,0x82bf9a57 +pngsuite/primary_check/tbyn3p08.png,32,32,4,,0x82bf9a57 +pngsuite/primary_check/tm3n3p02.png,32,32,4,,0xf59745c5 +pngsuite/primary_check/tp0n0g08.png,32,32,4,,0xd405ad2e +pngsuite/primary_check/tp0n2c08.png,32,32,4,,0x5a66ca09 +pngsuite/primary_check/tp0n3p08.png,32,32,4,,0x06e81adf +pngsuite/primary_check/tp1n3p08.png,32,32,4,,0x82bf9a57 +pngsuite/primary_check/z00n2c08.png,32,32,4,,0xaa698493 +pngsuite/primary_check/z03n2c08.png,32,32,4,,0xaa698493 +pngsuite/primary_check/z06n2c08.png,32,32,4,,0xaa698493 +pngsuite/primary_check/z09n2c08.png,32,32,4,,0xaa698493 +pngsuite/unused/ccwn2c08.png,32,32,3,,0xbb576418 +pngsuite/unused/ccwn3p08.png,32,32,3,,0x5c4df060 +pngsuite/unused/cdfn2c08.png,8,32,3,,0xe30ed48f +pngsuite/unused/cdhn2c08.png,32,8,3,,0x999321f5 +pngsuite/unused/cdsn2c08.png,8,8,3,,0x7f63fa01 +pngsuite/unused/cdun2c08.png,32,32,3,,0xbd325d71 +pngsuite/unused/ch1n3p04.png,32,32,3,,0x35b2e4a5 +pngsuite/unused/ch2n3p08.png,32,32,3,,0xfe066865 +pngsuite/unused/cm0n0g04.png,32,32,1,,0xe9f53e6c +pngsuite/unused/cm7n0g04.png,32,32,1,,0xe9f53e6c +pngsuite/unused/cm9n0g04.png,32,32,1,,0xe9f53e6c +pngsuite/unused/cs3n2c16.png,32,32,3,,0x7f0fa2c5 +pngsuite/unused/cs3n3p08.png,32,32,3,,0x5533bac5 +pngsuite/unused/cs5n2c08.png,32,32,3,,0x8a80f8c5 +pngsuite/unused/cs5n3p08.png,32,32,3,,0x8a80f8c5 +pngsuite/unused/cs8n2c08.png,32,32,3,,0x7f0fa2c5 +pngsuite/unused/cs8n3p08.png,32,32,3,,0x7f0fa2c5 +pngsuite/unused/ct0n0g04.png,32,32,1,,0xe9f53e6c +pngsuite/unused/ct1n0g04.png,32,32,1,,0xe9f53e6c +pngsuite/unused/cten0g04.png,32,32,1,,0x1c073b45 +pngsuite/unused/ctfn0g04.png,32,32,1,,0xfa9fd205 +pngsuite/unused/ctgn0g04.png,32,32,1,,0xf28c8085 +pngsuite/unused/cthn0g04.png,32,32,1,,0x7c039595 +pngsuite/unused/ctjn0g04.png,32,32,1,,0xc520f455 +pngsuite/unused/ctzn0g04.png,32,32,1,,0xe9f53e6c +pngsuite/unused/f00n0g08.png,32,32,1,,0x21db411b +pngsuite/unused/f00n2c08.png,32,32,3,,0x1f25ded0 +pngsuite/unused/f01n0g08.png,32,32,1,,0x7437b32a +pngsuite/unused/f01n2c08.png,32,32,3,,0x0d4507ae +pngsuite/unused/f02n0g08.png,32,32,1,,0x6b633c7c +pngsuite/unused/f02n2c08.png,32,32,3,,0x4b278986 +pngsuite/unused/f03n0g08.png,32,32,1,,0x2f31c08e +pngsuite/unused/f03n2c08.png,32,32,3,,0x843ecc7e +pngsuite/unused/f04n0g08.png,32,32,1,,0xfd3a0b73 +pngsuite/unused/f04n2c08.png,32,32,3,,0x557174bc +pngsuite/unused/f99n0g04.png,32,32,1,,0xb79aa6e1 +pngsuite/unused/g03n0g16.png,32,32,1,,0xecd13817 +pngsuite/unused/g03n2c08.png,32,32,3,,0x242407a8 +pngsuite/unused/g03n3p04.png,32,32,3,,0xe801ecc8 +pngsuite/unused/g04n0g16.png,32,32,1,,0xc11bc972 +pngsuite/unused/g04n2c08.png,32,32,3,,0xdf843cc4 +pngsuite/unused/g04n3p04.png,32,32,3,,0x60e41f3b +pngsuite/unused/g05n0g16.png,32,32,1,,0xbe6615a5 +pngsuite/unused/g05n2c08.png,32,32,3,,0x5c312116 +pngsuite/unused/g05n3p04.png,32,32,3,,0x2e0fbf86 +pngsuite/unused/g07n0g16.png,32,32,1,,0x2b54a398 +pngsuite/unused/g07n2c08.png,32,32,3,,0xf765fb10 +pngsuite/unused/g07n3p04.png,32,32,3,,0x9a8c3338 +pngsuite/unused/g10n0g16.png,32,32,1,,0xb08a92e1 +pngsuite/unused/g10n2c08.png,32,32,3,,0xa43f2291 +pngsuite/unused/g10n3p04.png,32,32,3,,0xb733194c +pngsuite/unused/g25n0g16.png,32,32,1,,0xa6b1f5dd +pngsuite/unused/g25n2c08.png,32,32,3,,0x767aee0c +pngsuite/unused/g25n3p04.png,32,32,3,,0x4cf349a8 +pngsuite/unused/pp0n2c16.png,32,32,3,,0x65567ed5 +pngsuite/unused/pp0n6a08.png,32,32,4,,0x3188c645 +pngsuite/unused/ps1n0g08.png,32,32,1,,0x414f1ca9 +pngsuite/unused/ps1n2c16.png,32,32,3,,0x65567ed5 +pngsuite/unused/ps2n0g08.png,32,32,1,,0x414f1ca9 +pngsuite/unused/ps2n2c16.png,32,32,3,,0x65567ed5 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/ccwn2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/ccwn2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..47c24817b79c1e2df8eb3f4fe43798f249a063d6 GIT binary patch literal 1514 zcmV004R>004l5008;`004mK004C`008P>0026e000+ooVrmw000Gb zNkl1!`#jIX%gn1pggd$P9Zd(E8@>1o|NOaq`y>7F!|ZsTJzwK126G#Ly6%XM z@b-^5>QWhU%dqD1!_57RBTumVe+8~Hq9eTb8gGRJAwh?*OV}a2z_~^C|CZMU`+s$C zf=f^GMjIPqJ*EzYFsfNGT)Lap?PPm7_!7VJ=-g%pukx;sAJU$c-=j0zF+ofa5q!)a ze#6rhquT+FvGf6bpLReH((V#=343&VbR#+uVMHe)7;@qr4%Ccq1-RxEz0QX|?KUbv zg-D3?a5PfW@#w_lF{X>|p$bm?jc2~d`wI6~f#a;6WITuOqXKjXI#{2ULDi%_otUzR z>7gTt5je5J^Sd~Dle2ye@C#0R_#V2AZlgm~2OH2hsG43tH)a~4d#D*O2R?ta&G)tW z>8Ar@roZFTCSclIO*Ag1ArI+AG!Z7+1hzr17QOyU)Mwc7+NS`==$DuP*T?vqO>|IQ ztcPnUI`m>BLib<}*@i@om~~+l){c| zVM7=SBVi=G`3mjN^Uzb*0scX@ir!A!MZ4%0+DG}A03D)xh@t5bN9Y)dtvG|t!e{A2 zVJr-VH(rqM{a!t^`;)+*NnHXL-9oj{tt~?bm>$Z7h&)1VrgJrYXc=0@ma%1G8KuoD z&+_AUoChDj1~`crIu6P~xu_ZAq5Z4pRfm`!Wn^(jTpi2MGMYsT(bw1_m z-{DWcW?RoW2Sx1%q6`?p5#3d|Bp`vxK|FMT+rjJ7PN`Fx6q_I^ zHsSO@o_kdt`-!tWMURz18H)xjXp0u5EYvg!dKD%@(vVc71xZelk*1^>Sw>ormt<42 zhFmB#g+t{FiXi4}-?><#$4U#@DlI4r4PvoVssT2+X;jvbP04Gr zio7B#DN2fhGApO!Ash15FNBNxItO+uXX|?4s1n(L1}xIhR7?x<%j`IhKSXD!3`rZZ zhRl*T6jO>R>MU|4RaMv=y0F$9} zR4$TPn>VItC~Z@fNxj~;XU>`>Z{Pj*Eq?Q{1ADY{!yAorm%^@LYwChBA?vgK9NhuV zEWJQVYm2(9vPpCPqMTgf?}Pc|ffx6F>Cg}2g?nz4y9M0qW3N!>6l2^z@iOrMH$#`G zQl@pW(kx$*<@%1VVyS645OM(au{s2+Lb!Cm*v-(Kz~fjhVU4fw3PM64Z& QC;$Ke07*qoM6N<$g6Hha`2YX_ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/ccwn3p08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/ccwn3p08.png new file mode 100644 index 0000000000000000000000000000000000000000..8bb2c10981594a83b7a8981436225d0b2241d27b GIT binary patch literal 1554 zcmWkudo+{@9Q~wiYbxpxyQH*PvQk+jg_3M0kMUetMjk0^ibC6FT8+ng>|mu<;>-?$ zm5gBE76za z`V2rL-V2)_s6XvdI|05qg3CyUFdEKU;xM`LGPZ>qA>#tN;_w)^vQv<0j}{EP(no$i z`mo0!AQSgK>Bh#O1Uo1c%9qgC08SDbT*Rc~7NA^cjuaHHr)sr@p!{AFnbKlF+h0Dke2lZ=BJFa3_1Pd-&6R(7q2>7?e`@` z2+zSi67nJu@aa}f)U3dTH8`j}NbZC|_wRhnwqpDkdIZnq^Mw#AAXh1rk|hC1a$`e# zV-YlQO+>2x#2+~ArqX?p(+hMM6oVzgAuTW+d5|lVcJnG(%P3BD)?#ZSHf%!Zby(CP zbrt$P!C~!psuiF|p!y)PNs5DXbQ*5lJ!*k+RpmZhEtn@Gs0a|;pZ zu~mOaE5qA45~jygEACV%-f(amkFH*arp)TM<_{!(td=Z&Rf=QC&qd6qVjPAfD<3nC zh_J^_V+ezxPwstYijw1t$B&EU&4{COWt zVR8e2Z{dXU?a)tyb#R1Iirg-^pTWKhiHJp<1{^AoqaTColG2eP?A-^hB^piMsr0bp zJVwAA0PXX=m^&{c!SpeNZ7$J;AsYN{&O*xN0eV#q7~bv=)=g+*8hLJE5TG$3=Ore@ zlDBF%NJ@Yi>YMI%3XnaBS#bVnhFOQ!p^C+C=TZ+i`hL#|-?g9Ev8Jly(eTN>(I6qk zs-@Z6yPEsqewkrTVN==Wzva^UFWypuE-|9M5ejGMwBbk&+nr}rsz)C=6Mg;7!p`W{ z*3~{qezB>k(KS802c-H%MJtOML$`!vXDrv4dA&B^?EA>Laq)wJ`AA9-`)?bXpnYtZ z**8#nC9r6}lYe*V7X^NmwdatS!q&aTv2GK}t$pbuI?lXOqlte1o8O&f1YZsfs{L&1 zZoz%=oa51tY^i+zB#M=_j$sXzMB?(7N*Xj(?)<=@b}om6+jNTRdt1f!@iLHfQk z`Im$Q_8Q769>r)~P>^hRDA_iy_5h*$Kk5ptj>x+)kf| zUCti|wcqQxc@1b(iexLF45xdH9&YI{Rtz*|(b+mwZY$GfOU5Nji;Hn)EvInIyw&H6VI7pV{6m$_g05>{kkUx-_69w3hFaYQorBj zDX_=;D|R(DV{iZR3fwcX5XUT6UNQ*_n^{<5*q_TfRA5^dnUj}yFa2X1o3rN@lnb*< z!|vRg*cA5kc1HTa*M$`o>FG@i|HPJ@sFVg?HagxLeoW@3zx4@sz5b)(>a_fdH;*5O z)i?OfjnGg^KQwW7n2!*3?6#5{%QaA_=g+OQ^R1n-BcAPeo8I7%7X;;=;1%j1i zZ)bCl8v&97rVft|P_iC!Ccs>)Nfzav-)4vs=ljCt>^(xPQBFB!f~}2 z33c`L-70SCvR3s|4U=$oHH9TFzjO1%!~RfY+0z-VM(^3 zbZpsM^d4*^09(kX9^w*ZmI5XJmB^)Ql!H<7e6ZY) zc`+QeUEqwtD6lI*MoSDvI5{}WC480&PtuOH*-2AVaV?E<`JCD_tpv-j#9PIaBFEN! ya5NvsJwe(+v{{WEX}38N*-?K$b^NQk+{YgmGj*nOAn>;U0000d_tM6?Z=$-SxY}-ICRK&4y|1 zVwc#f!dHA1xV88oBj*}@g$RWy_hMZ-KIm-D*-?;qSET3(i^4(8Km2Jkn4dfq@Se&3 zTKj@moXkY~bFu#0PPnZ(e1e0$_)enlnWhXC=IqbUUkI-WEMadnm^fjTNZTC0CDs-P zO1_Hd{`ll}k%^K0x`I<0XS2iy+u$b!SFTK5^fGI1n`4Un+K*`hy-NEv><+L^T&{2S kcJ9aK_IoSqvM<+joQ+wPvG4a1ptl)3UHx3vIVCg!05sc)VE_OC literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/cdsn2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/cdsn2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..076c32cc082066dc870e1282b97c15ec22192407 GIT binary patch literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1SGw4HSYi^mUKs7M+U~W1%@xC#RK`w#ZI0f zEG#VLKejFgaybh;B8!1EBN+Ru9=Qf&RC~HOhE&XPJr~G($U(&AVsKMqdZO+XCv8UV z2nJ4tMy~^h!kDxdYA>9+p~j^3=S&}?ojYTl4JO=Jai&15=FGw*d!IX*;+1!`qwm#r zn`WQ%3^IuS{KD+pZL<&+b@wcnUV-Cw0nJZSYySkqa&Hnkl_)Oke>c9oU4*NKQEP+# V1@X9R>wwN+@O1TaS?83{1OP3BOWptg literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/cdun2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/cdun2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..846033be6b4fdec08cc010aa0abb35428b6659d0 GIT binary patch literal 724 zcmV;_0xSKAP)9p$>iIc zM9BG}R?-GyZPG$D`^%XGf`^qAr06IYS+9p{fK&k*ZoATR1wD8|WCM=~607$j`vY7L zV8zwkB3t?J3uaPidN3!<*KK9Z0BA!^-Kg`Xnb*QIyQ{+A355#ayA z0cpp|nK|8N%npZU=nH)H(bhrG@~;!&M3?XV2(`}^e@baTp03|x0oH!)@bHo=j4KQ0 zuY|$dbUuo5Y|+T(H30UBc@<4A(dF}I3{b5$!14M1g8c&!K-bPLY`YQw0000Vz>816FsF9(VN75txh7sS~8e>Vez z3y|esg8)MVP)qc0!95k9*}y}5sLX|@B3D-GMbkBlwnNjonHwIy z9EcJJF%{78L==oIJhA+`7;;cFolQHd7RYGxEQK6edY*pw@2LCip7ZW~-{<*#JkP!5 zYYTEB!lT29C?YRceiiRU!SD1?JmuDki+F_~O=B_)K_Hn((}+hbb3KVH`hCQ#8wM+c zbFwxPsNG;gO(S62+sIEOu%KnpUADK`#I{M2L=HJ6NYwOv+-H@D_X=#9?oI+MS;V>y zl^XVO2yC09!Icb&nREm`f)AWc25B~!P{MT$qI?}Geug!qSF%UJX^=e(A@Tsq&y_e(IV5N zc2ombmUg;9iHZVhFq;*ili#)pv6lr^Wf$(E5sCtE*M(vW0)!C2(rmO95VCxjCB6Yo zimt;CGRgER#Fo_!>WATkX^@enQK3N$>j4Nhm?Bc~Cc@4FTut;EC7;Rd?q$9UQBAH#yE#JE|Q0|h>!DU_)gvGGGRzl1C&lfZjR|WWg|k-fq8#JmDTaBI#^STUG9+W$DsWo438O zx%khhimTsDdcDtYX^8l8`@k~2aals#tw5h$TvR^EzC~aJtyXxM{*Nd)reoq~rm{KwpXnEYf zqB*0lzd&-2CvWIFQhLa@dP&k(Dd*Pij(dA}O2xf7|D4KEZ$_?Z8(4n($A+lx;)H8u z)fq!W+gl8UebBY<#K*_xetq?5tDJr_srucF zRWSz=YaUiV+I4>B_%&@-PtL)FX`y5J^WLm!j19!cj=uCt?f&G4P0QquI_{@U+1wXV|N8?-P52vntW^|UZqc(l!@kKG^x7(XjPW24#8yrdL)E4&VZN1_vo8296 zO!#K>^qsN&VVBEKXIzkd_mj&@tKO3RzQo4)w=OTPI2+x=IZaHg(y>8-4$mKjGb gSyRUo_cYqm_AGEC_tp3R7W~7>%Px>RvflahUvG13wg3PC literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/cm0n0g04.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/cm0n0g04.png new file mode 100644 index 0000000000000000000000000000000000000000..9fba5db3b82ca7725816efca47adfb44d61292f0 GIT binary patch literal 292 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk^Bp4a_!xt_BxxKPq}K;y)F)+dv$%Po8B#%=u~U{}NXebp5O&tsV_+x6KC z`n6Ov-e|q7d~>yV!S#P~w_ZMf;qcvgkAe_q@|8lddEI`uLzP=J4Gp*J^)`wY2|W3b o#w`BCU_;}R^bC!qjU|5>nikk@kH_`o5%P%0-tcHxz2242t zZVAjc7P#3?WtxA)Gw_j&*7KvKZ>J=PsTO88@_NksIICmj$1M%HOyBFza{Sr);QoBh zi@mlwB0JLF_+GF!ImvlsMh zsc5{>dRh7AYV(5Y|Kx7HeE!1WyYn6eA`4jdmiWEU!L;8G5Gbb{58QQCkh zC%`R%`Nje_+o??Rk9Y_%RXc^_wWto*p8A(!cU{aKDbTOZt? z&v~)eR!3w<+8cjwuIKCvE|l~g&^Ym)^~t2`a?9Sjaa+F#*wwIpUv)*n^H^rfc767O zek~P^H(D<%-&}28aQ&a$t(VVVIDB{BqaehYe5FuqUbo-vP~{d)L&NQQy^W$p0#81q oF^fMj*wFYSJwsz@W656zxtoVvfBf&v2KtG?)78&qol`;+0Fu^nQ~&?~ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/cs3n2c16.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/cs3n2c16.png new file mode 100644 index 0000000000000000000000000000000000000000..bf5fd20a20445fb7c9cf5d38a6ac4cd0fb28de29 GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^3Lq@N1SHpV7(M_}Ea{HEjtq=#3k+XOiwE+Vi=8|} zczJm*nD-t8a_c-@978JRyq$iK_lN@rGdt(YQy#z8YiT}?Tf!kXZS7nxn;(ocNRJS_ct9{9$oSsu1^4G$9qOxF^ITqWf!_v|1&zuJmL90#Z}{$h z;X$oW3rB}@4oBt#ju#pq{&7g{v2S8~pRDBMFgMZFh1uQVB)dVdT4qdrSvt@q44$rj JF6*2UngG#&N@f56 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/cs3n3p08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/cs3n3p08.png new file mode 100644 index 0000000000000000000000000000000000000000..f4a66237bfb3a113b7874d569367932762cd0ba8 GIT binary patch literal 259 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtq=#3k+XOiwE+Vi=8|} zn3vvNsN)kh^hn zQh3OQxBHhXtGq91lkmGBUgcb!!F~1uvkcz_zD4pSt$G)@WaeGq$X&o%(w=ugNaorF hmc0uYt(@e4Fe^D2KP>;UP7P=dgQu&X%Q~loCIF6>U!?#5 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/cs5n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/cs5n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..40f947c33e477af3d4c13e3a56efcf32e6871be2 GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WSkfJR9T^zg78t&m77yez7dv@| zu(GmBsNHi0a+5q=978JRyq&sHu)%@n@Dyu(NA}wKUA0-b10Ep zFS_6slkGtcsjSnkCm!EZ->w_6&B5QGsqfG`#!m_?Kf_rn1^#&$H05(u3N+bpIQ|n& cya`qx_w<}dzJS2a44`ccp00i_>zopr06uR!@c;k- literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/cs5n3p08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/cs5n3p08.png new file mode 100644 index 0000000000000000000000000000000000000000..dfd6e6e6ecfcf1d0be69730fb34292c8b6561376 GIT binary patch literal 271 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtq=#3k+XOiwE+Vi=8|} zSXo&m)b2S0xd{P2A+G-=8Jzwz9R1J0@V}bj|6Ydw{~3U6Ab;uq|M!3d14Huv|8p1^ z6#xH^0;;k4Zv_-!cm`I*@IOisq;U=dQ0cS(K&=cd|AA%zO#~X&0+Nz+*Khp_w9DJm z#WAE}PU}Hd#sdrt4F>Ptz55$&$0R-3;cHXa2ac?Rsv0cq2_h{`K_9r-92Fv1Rz46e wX^Ir!40F`E!P1!^p3)Rkz@>FiH{%HZu|hk>x7R-I1{%xY>FVdQ&MBb@0QH?@PXGV_ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/cs8n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/cs8n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..8e01d3294ff71ea83918aa7f99589c7f88da81c4 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WSkfJR9T^zg78t&m77yh6c)B=- zRLpsM)sdIMfX69t)`R5F>$8F{b!Pm_R9NudWu=&O_QeBy0tqv+c5v)_Q~Q&J!@#%m wUE>4C4G-c4A22Uq;csH)Zz^SCk$A@V`i@Ax+rlTSfaWrIy85}Sb4q9e0Oq;wP*-)CT0@c;jQ z2B6XdAldyud4`1l|LYkT9R35fGAR598n(G{b288_Z%-G;kcv622U!^p0CgC=d-v{d zv>lW5Rt15YCienPzJp3LSeh3I<}^i2;MQ`~516SSqSE9Pz!h~+uY+af0^y#f$PHY6 d2X!ls@@qb_WBltft&4Aruj!a10Ts~ zJwICdc1nVnYGHOGugAQPvpQCO+|rQC^u7Kp$Dgea?$77E*lVjJvLo${zc<%&_5~M8 z`VMHEc+dJ|(sj9IZ{4`9Uj*!GSii5jqTqQfvt_$JdqKaJipCqQmz8g>HZQpTPwv*s z=Pw+-JMU2t;!M6$C^oO#?{=tii>9IBcD>$4(ISB-AJUk`pBQXte3G7_v9z(|FN568 VL#{vmcV>h9>gnp|vd$@?2>`oyZOi}w literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/ct1n0g04.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/ct1n0g04.png new file mode 100644 index 0000000000000000000000000000000000000000..3ba110aa764ff4e5ee642b7c0788229a4924d383 GIT binary patch literal 792 zcmYL_OH30%7{`}afYlm95ECCT0X3y5D?Mmv3<;G|C`zfeL@oq|?R0nSc4yh0QW_6Z zB`Pss;_F}x)Pp8ol!HcNj8URV4;m%W;Nzf@CWQndoG|e@TZkTZXTJH*|M$)JANBiM zE&Mi~<2Z}A&3%-u!2Al?kGu3WUCWk2$<^WF^3J3j^H+OWy@|GT(;%h_;{2)(`~)QFCN!|B{t=iEQyFKl3pA11?%3{l4##YE*?fOXKY0?i7L zFo3{7)ZE}Gl#l^L9YKmhfD%BFK>?K!KL~Y9VoW0n(d%eJWA~VYNr1%#!bFckDgdu4 zDzOmrLLKq_3KS!I;xY@vzGk!o4JwE-xE;hXl>t;V83H!9sv>3WjHfzO4UGX*F|iId z1mIE>(2r?d2x(L{m{B2dvyUheJ;X-m&DXNm7#b4I?m*L#fmAAVlv*Mz2B#TAfP`tB ziCpvgh%5jIe5gBU(6u;n^MRPbh@e_iqm*c>R4p7Y3ndt&JeMUlL>pcHB)>|a4ucs$lG3@ulPNE1@BXy#w+=g z&%HGh>bR@`vHY&O>~* zIQ52(KeeX66=g2YR^D3jY&bCQe>8me$%!E?+f(2?ySG@oVB2L~v@IkS-pGsyV*?Kcce{q!`rdz)M R88w6Bye&TWor8U+{sI?(Ab literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/cten0g04.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/cten0g04.png new file mode 100644 index 0000000000000000000000000000000000000000..a6a56faf2b3e8655f88c028070ac174823ccefd1 GIT binary patch literal 742 zcmZXRO=uHA6vrnJKT;a(wL;~!h$N6MrYd;wkTqLtKw~wL_E6C2W|AE`J2UJ|4DsMW zRM3MWg?cGs!J~Lk1VIoBT0AKzdTK$zg5c4M2SMLvLo0aLnfLj7^Zt8tx;m8|*gHT7 z$yQE!=kSL3OXEQnzrM}mm2SDGU6Q)-p!j|10{X+eK5OgT3Wg4oBr&IDtJdb0eR}j} zAI6WyMQ+;$DoU#Q_6!$-g>Z>VsQCfq3y|ydLM7949bbj{$r*TvU2e=ME8UHFE3gS> z+ugF@KV|{SRMG~8iG#)BhaZPl2}upcFpn8O;@Wa06WqT67jU-bg2ri3O`u?yjSU{s z7Bk>Sj9S(JtH3g2!3GQ$iMVQGP6-u_+pfks?1)n+Ei_dWQtLoP`l8*yUK%l&7t~)k z&zCi*cOQE4;+q~Gm>jRUR_FwgCHgJ~NWY!s>! z&+&Z~HMnf`UW>0Wn)$uyI%0-N>s&dK^-zSVSeKe=+76NvQMSBte_@n^LY2+djZMVq zZ3}G^^T)Idtu?rFVds1`b3(7;*|Nq{$ee}L~xhLtN m^^F(rmq*W#)$5mU+Z5S&`t$7h&xh|JmsBRJ-s9sJul@x+i~KYI literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/ctfn0g04.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/ctfn0g04.png new file mode 100644 index 0000000000000000000000000000000000000000..353873ebbd0ca66c57ec7f6488e8f18a1c3517ac GIT binary patch literal 716 zcmZXRL1+^}6ow}dghq?tNtEi?QzFS~JO~9rW1}X-)@p**gXox@CNDF)v+m4n$W41t z#Dj>S9;8}`R}Y>7A|4{OAV@F0t5^`c)r&$!-zHWNVP}W8^WOLG|Ih82=~JVrT`5Ay zXt`88hdYf|5?|#0$K?!e$wpzSKoU2f=D$3+jP-7)&6*k*$+4INa>i&7LXjE2QMF+B zx4p|{Jei2w3f44LIP9x&0j++QTmUJ#M6jjrPF;DKsTYq{ajXKnB>NAH%plenk*N$M+~b-foIWHJh@4$U`PV!RWLc-i8>vZ zYmJf~ieQMENeRYSMs>jJvF4}ickq$@uJ-#~=k4KZ7t!O<*mhOTnKlbIZ?4%wub_=G zpi=KsN5hy&~?ec@1Ie>$uaZM$zGZ32aTXT=*4DPUBk9b(-q_gZk&Rz>KfHfjULdK5 zH;yEd?bZG3$(5cvLVGXUPbMZ8$oxB!=x!zCN4Q?tvGV2>GD-Qwbn*G|EAzhrgC78w literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/ctgn0g04.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/ctgn0g04.png new file mode 100644 index 0000000000000000000000000000000000000000..453f2b0a49ee544fcc6b297a59dd685a06285005 GIT binary patch literal 1182 zcma)5T}V@57(R*-XciSoc=e?OF6PLfpo_retRxc2LEc1;b=(f_kL{>MM01;(Fe5Xf zEW;>*KsS#z?6{55l|t}-Nl;fsffq#=K@ibZJ>R!Yx(T}2`Tn23_uY6`=gHdY`f5c{ zYCBFj&SGTZSA~x<^=Zw5p~_=FV^=ChXInnsxgZWTXOHgGbf1ca>Qm?mrFnv~RNyhC zE8yuK&~%lJwVF6SA@14-bZ;Su$l0PA*pU0OeD z8j@SB5xKOvgMQ1~14sIRF)k3>f}zV{&EwTeptF>x41jZhOOQz`G{@+It7 zq5r*E79uniV^~U#1TBk;1#FwRoMJp7F=Q|ZpC|cn5p+yWa&RSYG-H^6B1xDi66P=rqhKk9 zjX zV6q@FSka1Dl36}0OPELY;915{wj$|8Qs^d#OUh)SAWLbaQD~L|kr3iUf+qY8(>!LG zl3p2k4w1mJU>90&RA7;i>J~Lfi+y;)8$&u0PT|J(^txqlxhuPaefp3qT>e|!m(?|S z9B{IR3f=xk*}xVT_Dc=084&AKdz=y7C3;Hc2G?;}%>tAlWaKC_M-~mCfL{-W0-gq2 z+oe}uA*xMwr517fT$*1k{{bh(OZhr2(Uu{LkBj1n6h&sDDgTmfvp!7w0C&E_VRtr7 z&X9elvTs}4y7y&e-9mJWwf9loLRU>sYfatao0<1tj;;RQeotYipJg&1R`2evblkc& zt=)|G^=q%|e_c&vDuCong?_VK6m>q(xv*Kj1}5zZ0E5m^qKHu!ynhmu$Vm7IxEZTxmWRJtOe)8nzKoFYPwIw!K*RB7e%yF z@Pvro5`0PUQPvpnINCH_b^NYfLeNRNVQtgB!EokEQbcBf)_^0b>7mDF0+E zS1ONHR48xVEehSw6nHuI1 zi1Qsu5>)3!^d{h7A_y#l*kM_gy_ooqopK`X8Q03EeQC7humk5Lwq3+zxN zH!h@?2)YxXDI?9PD2VK17?dF4c6$^4GEKO z3p|9D^*}JNT7@ZQc;-ZOK=8YA0%9oy zs*K(bI-FqXKGv&E&WP@!2~|o$0lyv$1w6`;Y_sJHNzVYy6H1~P${jFi_z33btHz1 zF3grRu&4GtmL4{+t8(L6@usQu?YouYqOb1{)iP$O`Sxb_^IwmVi`h@t+vciUyZ!(g CG05To literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/ctjn0g04.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/ctjn0g04.png new file mode 100644 index 0000000000000000000000000000000000000000..a77b8d2fee4c377c20b8e751317d094cc2be3cf4 GIT binary patch literal 941 zcmZWn$xB;77$27w5yewMq=%qTvTX0TTOEITtVlMs?! zQ(g4}zdG@xg~SC8ec3O_n1t!KbW{qL<7NeP=JUpscJ>oaWV0xVW3II$>gcJ z_fgZ}RzA!so>ibTT7l3RJ(vP#dLKsmEMuq_C|DnIPUEw8uhw1t1EWw?)+7$Vz;TLs-nJd z+}PZFnUj8UQe5owNs^*uwPa*u-Yxo*$bn9ItiZd}V*Z_YZ^iJn%Q4lWAD-c literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/ctzn0g04.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/ctzn0g04.png new file mode 100644 index 0000000000000000000000000000000000000000..b4401c9cfca842537838cec131e416ce9d7b0c26 GIT binary patch literal 753 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk^Bp4&$Z`J6 zNzb#P8s2AuIsDFfoH=tXkmKALZ-1Q=`d*<1Teg^7-D<+j(4EM?Bq(Z%5YVvQAj4cz zi<65o3raHc^FYSw_HE=lWWeL{z3!;5&(i+~S|SAYC&V}_-LdfE3H>h@z9Dj7b$L0{ z?V!z*(zQ3RbHvuze{%AF`9ST7(P3V_yAM6v__yY~zu>c0ws!SHZY7mF>(Xu3=spqF zYyE1=?(N&QTWCLD_9?x`{4#-_N0n=i%(89VqQ<1I<@)8j^q#<%rc7M<{~y>rT+&tj z?Xk|)VE%yIyVWz+p51BFUYon-WoSjdd+w#BKfbWae+pyylvLFL^o9e-8^QT$CFO}l zso)^d^YYd4^bI_5KG-AVgLf~7rkwsp-f`cTpI5{UVGdHyeY?`*0udct2*HvZ)!BFOeE9Eakt zF(>xoL0({Ru?EO%IdFX7kX@*}flE2$(Fs;VMri}4oB+23<{Jy#Y^O5KKjInqNJi`V z(bBh562w#svm1Fm=6#&ivGU`VhFqra^=CQ$Y<+NlKIg?=TOE-dX>a_!xt_BxxKPq} zK;y)F)+dv$%Po8B#%=u~U{}NXebp5O&tsV_+x6KC`n6Ov-e|q7d~>yV!S#P~w_ZMf z;qcvgkAe_q@|8lddEI`uLzP=J4Gp*J^)`wY2|W3b#w`BCU_;}R^bC!qjU|5>N8=ISR z4CLwk<95#Vg>&ou?4IBK-OKf4I=h+Q-Yu4^`-jJ;=a<*F_w~o;*Jiu>-k0SMV?!`9 z1Vck`We6?}!N3q)7=l6z!b$|W5>ORnBHWgrk_!4FAm#v-gL4rOlDZRAQhFl5C-ouJ zT{shgf4PuS-IlHh@Hud2s6Ob305=7HTh$nC5#jVOMRm%l2)`fuP_1(!!tE!01i`Tg zNcxC^mI$IC_VFh;N<~=u^=Vc`@;_)KBFl?^KZrv^P&WiMLvUaSEP*Ak#4p5s%2-gQ R&b9ym002ovPDHLkV1h+qhR^^2 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/f00n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/f00n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..d6a1ffff62eb0f1f1dcdb4ba2b1d30473d3adacd GIT binary patch literal 2475 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/f01n0g08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/f01n0g08.png new file mode 100644 index 0000000000000000000000000000000000000000..4a1107b4637642335b8ec7b8e1856dc0ba7dab70 GIT binary patch literal 321 zcmV-H0lxl;P)}T4b;N0E z3!a!0=lJ%5$Eg%oz?c7nM_Pz6fDfO-!!(E~0KEA*c%UmW2WDfu_({31E3v>N-{Hyc z!o4_%Y@;b2{kGgyCmt~K_ux)+Vvc)%F>XgE7N{D;Epg%*RfD)GPQ0RO5I3R|@2DEY z_29%Oss?dQ2JwUcSEewBLHsU3nZgtbQQmYZ7(-i7*cJ?I3zBU?bOg~6L`VDql~F8% T6Mw4;00000NkvXXu0mjfmy?8r literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/f01n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/f01n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..26fee958ce7e62a59843205ad34f86a525718595 GIT binary patch literal 1180 zcmV;N1Y`S&P)Qj4`pI zFjkU)u{F?Akfo*B!a}174J9nsV(#3X+5PQ?pp&O~&o{|^n{V!Obi%NV*_mQj=dbpv zm_B(tFHYo4wlii|=dbiix4xT4UZ{eGTtw$J**>tSy6N}n|vqif78L&$byRK9f1$SzrLzUf{!*^r3k$774rXm_7!8f1Q2+ z=C=8j8BA;b>93ttmB!R4Gqr2#)Il4ngL#p|9QHl^o9+4V^!;`CWzciKaJiBz*2s-A z%Q~e~hNeLo)I~S+8N27*(|0^}KLTZc)b*DYt70WpQX@6;tlY_+JY<94KKc3jyKkHx zc~$=bes_90V0+^67$}#lCtg&T@Kv!ADxnb@aaQcaP8xdm`scunQ;gUj^(pYH(^C;! zNn5y!L|KC#`k`FGPq->y@fF|j4L>V%d?yTrfgi+0@98t($EU{=wvxAS)ubd$_PkV3 z!A+>5imOt?H8kTouFDPFK!bmL?KbeO)596d+rNC?VU*+1#52gCK!FJ=R8*lsgBd#N zFko=|Nq}~GkP6E2D88M@l8GXUm_!xTsfy-kVs>rq&*Z3{>ZqRPX`b1u2fF{_*^dDC$unBQ(Ew)&lzW^YzR^hz^JI^CidP9to*K@y zNDItSJ$~`R-vM{)Cyb~i-{XO3q;i~yCk82yJm$&k5=WK56seJ#sF9XvnK_+adK+*z zf5tFI)>7;!5IcJyUb1-f+PNSFERrJ@DN!XcC2FQ7s?jpdPCtJOaPPm$D2$0wv{X9^ zWaZ)t@yIozCq+^qMJ$rnCC-%0l&Qw=-{Go9xBkqiT4p;6#CFmU*E%r}BWWdJiJY-a zF^Vx6Z~f)!f}4M1bjGY@*ij%CA&7a#dtxL;QaWQsUcZ|{DNGZ!QmbmEH8z2;34}4U zEce==fUtrOcu$DLNKC}F#?F@|D_EgSluD^gm7PG?1j0Nt7TXI1ibI5i_qeUYM`9wZ zHAc+m{muoO$dyvD%1$6`0-+Ag%UwbcF5n#U_<-~Hh>wJXPsDV-gPixfkS9{*C=fP* zFb&F3UG{zuavL5Oa2^-&5ueTk!)M~U--S34D@TE_2?QJDp|V`eAXY9N+By{R372qb zf6pNl!i29J1;Qo}(jX4E_qp%Fz4^!GobzySIy|o?)B5V>_Wt4V>G|dL?fv8P>$@@5`4A$18fipC zj4{?)>#TR)dmloGF-Am6L9i(ZNIDP1wql`V1IXeb3GF@N^}7L$OLT?eW035 z>s-(z0SrY%YCz?nO#+xF1yoYnBznj5E`aJnr^KQQpxV+di7ue_plgdhP^rb}TH<8U z-_)#002ovPDHLkV1l$} BmFfTh literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/f02n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/f02n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..e590f1234816946731c677d0b353e2db5a08d71b GIT binary patch literal 1729 zcmV;y20r-x*}n2M`}#}vjc?j3 z-?s05&wlVj`|(fh7r(S$|JMHaC;Q7^?O*?~d&{=IVSD>_5?Nc!ER3!g3n}x;oHDCS zE7M9tnNlW|No7L0po}Y{%BV7;)RZA*NEuWHlu$uIF{MyCN=M0+Oi>NpMWV~bQp%#T zpv)_?%8W9jOe<4Ly-ylf#ww|%3|Gvc5-LF-)9o>#n>V*ev}9aNS<tNOUgyIH` zG-q7aJ|<;Bs%p}n(NLyR>cV6>o0KtOR2fOBDK%kO8B8li!+3&t(p()kWp0LO#<-$= zOvf4WkAB#DDgP)1#^?QNplUNhH+K4K#)MH}M8lng?S#!1>#?oI+;=QHd&_Y* zvQ2ag-A04p0Z)8E@cI#Yj2@#AdWt&Kp)E8)6V#)Eh6mnu6Sfn!T5QC&9<$>3pR;Ah zn~`mxSA%AGv*B#jvEq6SSw}a}hv?(JmFIitK6;2Y(MP>%q^zct!qYa9w`0$dr1uwhW0Zhu#4`YC3A2lof%qhmB?1mex?MvA0uiC*F<|9wjtewjbMGY&*`j zoNYQ=i;4%x3bKm)ELbUlhe-cy&Q~LkkWkfBOvHTK5^pVU&KE8zqNF%!6-RN=jJt=f zyYIStuDcy|)=2IKN9FDBuxB>$Tlj7K4t|&RE`A?>fIq}HX*X$yq{F5)WCR(dJ%%60 zUobyOQYUH9nkJsLcn-IKTf$xH1ii7(+IyZFAaXuHLUf39$aL7GW-`)eOjt5$$&|Gk z7SC8bXKudx>~4AcfL#|Bg#}?=m=$J(8L0w9n3UeL9uLU|&_U86bQr0b)M$_5M@xoe zf}~Ds%Hl?G{lDey;l^hyT6)`NFITQS5g;L1KsJC5kPe`O$dJh}?HYch$0Xw<6ZZU{ zd$-?t0XK_VuxLS_2oUXUsK?Ay2?S&T8dk=j$q?;fe2=lO{-L*jZof!e$2D-%xH*gF z^n8Gvsp5t2h@l@X8zXow7$3|f20{E)r!yT=R8p8tOF$?p``zg4{drQ+FJ#q^ho zXy)BBQMAcvVzRW z3^h_v5Ric9p5w+1ZrERCgI8Y-UVAOLeLHyLjo{85+yt&}QC+$AHbz)d7DMt5 zd6zUtb0njk;WJuFoZ^&7I1!H8G(892_Pp&Rww>5!%hqFCjqSd(WoK`>;%;>AP46PR z$UY&RE}af}mn)!IyrepUKyIwR{8>j(~{{02>UU6F*}Ye$EIT~(g*w> XLp~IzS7kjt00000NkvXXu0mjfY;#J> literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/f03n0g08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/f03n0g08.png new file mode 100644 index 0000000000000000000000000000000000000000..ed01e2923c92fd8b8786524c6d6841365a44b9de GIT binary patch literal 389 zcmV;00eb$4P)4Zd45D^U_MS@TiGz2aVLCXUNp&Fi;&<&!6t`aRYNwm-e(L$HR7llUs_2v2Lp?04i2#w>5 zCjFHgoS}>OqWhKs;-;4%0XoyCYd;H{ChLTAkL z`Nnmc1cXl84@#vy84@~$6^JKJlZ=Ubk`d9uF>y~YB0kwMamU0Lt#&uLFSbqGG0ztr z+6kZrX%qLj_#)n+{rCF=MU%cN0ReDq=R?P^VtFOGwjP)u6|4v*CxG_LFwq4OLVH6N jD*7N)_#jLSK#2GUV`p4Ky#5P000000NkvXXu0mjfx;dtp literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/f03n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/f03n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..758115059d78260f7485521b83a864d4a930be84 GIT binary patch literal 1291 zcmV+m1@!ufP)=&S9{TIAi*MDSax9$V&z4Gt?ul6?wozPIOm-?GqbzHVVL0@-rxKGKhOL8k5O_q zSFpv3t*o}I*V_9Zvg_8{M;@~qp0pb`*{3(#=eF8yFWQ%0wy%ua*LT{9H|_38yXP&t z_Z_?MUHjhq_P_`B;30eDsQu(qd+fM9al)Sb+AZ}0v>o^z(58vARqwd=u@6n@Pmke1XD_3x{N{!lS^S@Mz)_} zdJOa7^M*h)z+aJKlpUap>FUU*babUGt;6i!o}sK%&_Ml_0}Q1o&Mu`O$?Cq66Q@JT z3RxIYM!*w1@Du|XA%l7Q)eI$}3>DD;e|4H6l$4TCgo02|@B=KrQ z@Wkm5Gr226kv_(nB^X7=$b&B_9m1$G>KKWRgkdJ$$Rs9|g_2{8Ez5|#T1H2TVa9h$ z(j;+7%SwO->aXp@3<>@U&;J{qzZIUl8J_zyJbOJn`+IoiYIx>K`1Q0sy`|S$qy4Ql z0v=}L_DlonP=h(73bQba+*O)>Gz;H^EtdX#W}j_9D|OAoJhWjRA~5T>Ndu`v9p<2x zV$3LYKl;cP4%*<5UG=dYJ8U03Vjuo4ycMs0P50HI0S#zE(=pl(GY{sz&C+xa)e;>z z1~aNPzjM$1t-?9LR-*64P#mG-1*D8@fZ(*yqnYop#)_J&`RW&@^-8p-T}(?iQxc|RDc*( z<`4s3eE1|ejyabvT}B)cWe8L&9Y4_UCg9tx=>U_B7``SQ&NPmDBN;&XQlhP3n_*jascABYwWa1geh;>x-+i4(U_Y8$pQBrmp@_v#) zgF2F8aQOzn)E_eOLYJ|y`CQzx3-$nmLD=V&A(sP5g05dja_!i}_ zgIDirr|I{mZhX~}7YtBA6@@6ph=h6QI6&Kt{O(YUr$lPFM|Gd{aIa#L7cd2c1x1(E z(Q=Gs10<^TF-)&&>U0cv)zz`6SBo;LJ%(}H?`NdF>L`))0fxOAVsvH`hUsg)nABw~ zucMi=tL~(v8Fzi|4FCoh!(J`gSItz-2XxH%{{y9tm%W$Hmi_<$002ovPDHLkV1gMt BbC>`C literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/f04n0g08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/f04n0g08.png new file mode 100644 index 0000000000000000000000000000000000000000..663fdae3e7d906df7379d92621a7a951313705cc GIT binary patch literal 269 zcmV+o0rLKdP){e2x4DAd1)!+NJ)f5B{T9psOdMM) z%F+|8FY)0+3~^>3vs1_&)hG|SqQp{zQhWvt^p*^yfsjOBNTM$!(HFu%2m|o|7A8yw TA(>!-00000NkvXXu0mjf-K%M) literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/f04n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/f04n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..3c8b5116e72a63a3de8e0c1892c147071d0269e7 GIT binary patch literal 985 zcmV;~119{5P)hvyJ!8utT1VEc>UN*1Tj!qJPN}IY>N=sRACLdrt)_N=Jy&nmRP0foi1T3q z1vs!^NS8tnTiC(|3Cm|k`D(`N4+4W;;{3xH5HR3=UuuoS4K!$rOa`(R z?R*&X`GvfR1n&u#ACUWVNrjF@+CUA>kh2DD{mP;k>FRtL8*-Oa0-8az@Zi?_D?6u0xgjeiedR*>(yJr`MN%ucekr3 zWi?2)HMNKXi#p_|&$f}^7s7eRdN|{jt0Vw}4uOnAlX>OyYx{;(h32S_hezhCE(Fab znDm|v`WGp9zt{od@3wLWe5y}twl%v9eNEpS4jt=K5jm_s#Yobzz%^iBf5O6mQ4IVB z1gSS#SEq}Q@Y^iry7D|7u$1$9vV7%>V~{k^6C{`$4zP=FjwZm`$rGS zvDv1V?}l%U4QyZyYY4D{53qy=jUGER5T*mg4DDFWy$X8(2FG?szK8L%=^pMOKo~6M z$j~Pc+wCNgX-nw*aZjf2VMtF87IxrAsgQ{`|D+q<6)@}&$53fBJv+1hHpj`E}Y00000NkvXX Hu0mjft5eU! literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/f99n0g04.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/f99n0g04.png new file mode 100644 index 0000000000000000000000000000000000000000..0b521c1d56ac6ba14d82547eea5c87dae391cfba GIT binary patch literal 426 zcmV;b0agBqP)R*H$WiHScT#vd?>UqJiKVd^cv0p+pyQ zGH|gKM+__s5&6}tHkZ;`DS5Y9+rZhQ zwP@N_D-FoTR{3vHO$x#=5Cz~@tXf4-D8*gDQ#65;*9uNSx9chJUE%)%R{#^#^^XN{toLo}DWI?N<>C;~ z04ksXP5}*|#RS)@U%=Ob*$h}N)l>-ed1V5+W2XZEdA?ey6A(g5buxi@h4uQqlhPtM zpG^V)m`*nvwSYxHS5VI&O?NvOiV039lVCCd(iGV5g^==n1tvHiO#xM<`sZb@9~=`L r4yJ(5#RO&P7r+GDZJ=PW2opR2brp<;D@5J500000NkvXXu0mjfFu#U? literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/g03n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/g03n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..a9354dbee6f36cf88f7b6277d3fd43b6cabfd84a GIT binary patch literal 370 zcmV-&0ge8NP)yO^U)m5Jsyp{zL>36n8GX#CQmg>mm9SE(8}cf*52*XGY_~ zh{+_6px=OtfYFFnf&gIz^!p-! z4E~_M*AoG%LANUc)ER`K2v7|=9TA|;pxqV$s(}np4O%S`KnAq|RD)(y1iYU?5QqRW z_=SEnxIkSs2+80F>Z$=A5dp}6`d>Z+8Ssq3bn47(xy-IHGcz-PeHq)$ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/g03n3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/g03n3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..60396c95af19fc5f14aff9f83d391331a32b3dab GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvmUKs7M+SzD9ea;@ZUFM-0(?ST zfi%Pa|Nj~OGn_cFb}hr*yC5EjfA|0Ywf_xwDRcl8Bzw9zhE&W+)?jB+RWM*uY zOI4Q~40T|+ae#?Kl#?gtjDxO+)}>1i-Z_XV1hX;(T`t)BOm^#Pkb^v3{an^LB{Ts5 DR8dIo literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/g04n0g16.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/g04n0g16.png new file mode 100644 index 0000000000000000000000000000000000000000..32395b76c9c0e1cb962743e8a9c8c41f319a26bd GIT binary patch literal 363 zcmV-x0hIoUP)k zMP!AUF&SCLg{ApYo}a%I2W<;z1p%J5#`A86=diAEu-(Goq6aWQ91jDiarl1YV72lz zSS}q6C}6R0GJYI*6ZWZ>;Ot> zZFbS1yTa-8J$*mz%z(DscaWy%bDwwTTMVko4oH&Ag@0}_C`%l49mFv}k^p3xQd%q3 z*T@Wt!e(&2nptb1gFLri;n!jYw;K*dqhSCu$TClZ{oV~g4L&I}i72HGV}Jkv002ov JPDHLkV1oB;ijV*R literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/g04n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/g04n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..a652b0ce87ed2824e2320154aeeb92915dab7be4 GIT binary patch literal 377 zcmV-<0fzpGP)=$qmFdR}P2oOfV zU|<4VgMQxxxCU`-0$hV$&jh#zHh>I1V1Ku30$c+d;Lf1aF#%*iSJZBs0Cxue-@pdA z2CbF}AcGU^M}sfOy9O~C5WqFSD>^`)3~T@e862Ae{Ka543z8O#a*afilwMz5gDA=u z(`i^Pv*IGJo|YG6Kr_1mR;%nWd2jp{WI%v#Gnh=mdi~I3QXrAR9su`drSUjyHa}Ju z8`J_uqi|tJ+HSMFZgOR)ZU*mlfZeWqG%Y-fK?ZMifZqlgyfy_OgO{cNWI%t*O&Od4 Xkxf^INQ0u+00000NkvXXu0mjffu*e-u`Y1b?z zzWXd(t64Wrb6MRl-MPm3%bnSi&5lTH;%2!xfw$vO?LWcx>5tgDx7P8NE*G2%a+Rm6 KpUXO@geCxuo>m_K literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/g05n0g16.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/g05n0g16.png new file mode 100644 index 0000000000000000000000000000000000000000..70b37f01e2b9e13a30a6e9d3c51a114a2f0953ca GIT binary patch literal 339 zcmV-Z0j&OsP)?t@NnF8DcSAbJ+I4}jD4==ze*zcJFZU7PBBkXpb0$0HQ1>69qAc{N%IA9o<0{k+y ztwZpB+XXm)2wVYpI0a#74`2wgEYEeVl>WRDLD#JdZnwEs3P4|pU@M>~u2)?HSOmDs z%O>jDD!>8DCayJ<2$}{h$mxDQ3z4SJr&0jRBM<=&(7Tr9be|rZe{e)l*V{)}8$|GW l@dAjTs?dUD3LXzX!4K_Ugk}Okv)}*#002ovPDHLkV1mh&gfRdB literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/g05n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/g05n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..932c1365364fead1166e4772f29dcb7657f73f39 GIT binary patch literal 350 zcmV-k0iphhP)yIS#@w5JksQ*g`}>M?;BdsB#iGXl_D}xBv~JKVfNIcci2&6=2B-$jrU)Q|6XY8W5%7NoGC(z`*F^vs1lyuu23P2l z!4dKa0rt=jWC9bvghS52BVSt=-A}B^lR{o z!EBZfhpyn1c^lB7D8J-F$KT*fK)J|Z=K<*ToSF6eZn4N8k6hj>3?mmdm%hv(n&R wWU%o7coi9}As&7nsP&a50$ri)KG u9IC*rureVboiWQXC@_w%oP86+Of7~h92M4HZx@7u9OCKf=d#Wzp$PyhYDC`v literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/g07n0g16.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/g07n0g16.png new file mode 100644 index 0000000000000000000000000000000000000000..d6a47c2d5746fc93d252900c0b2286878b14a71b GIT binary patch literal 321 zcmV-H0lxl;P)ic&7(nC-rHzraP|afnxc^RYsd`d`5u7poSz$l82kp{Tq3Z6JE6!C z<~cx~GsZc95JJcub)b0oLy=wE_B`cklP8(MI6l9^UC0cE!3;nQ`rgu@>+B3Z0>z=! T&@6K-00000NkvXXu0mjfZ=Zkq literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/g07n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/g07n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..597346460f1999fb225a46c941751a063ce744f4 GIT binary patch literal 340 zcmV-a0jvIrP)Ec1Z7JAAD^)rl zGc;g3%p!~-&*xWAO~jWXaK$Npe&EOP4FJlLpC7?~0$d0v3LyXpLqMLRlR!xgvP=k& z8i)Z>gESQaqy}ODGPs5U>}G%fNg@Ow1N4Z*fS)rkMhK9;MgMOA0e3K$8Wd6hG8n;r zXmA1Z-7-K1S_=WdAR39-HfGgLqn?XbwVVOhUIuloww+K0g9o7Nrm9jK0F(g&e1^=# z-V(Wa{fz-XY1a&35Di3ZYyEC(6%kphw4Tp(^SMg{u4DAwEbq7P7ygd;HOjyT;8iGt mb0`2ZIE4Zr1N1W+%HRPOfLawl;h_ou0000jY(XgVYP$Ug2i0{3Ntua3PevZO<6g^Ah?s$m6bVyVK1BpBEle`W(ImUKs7M+U~W1%@xC#RK_!JzX3_ zD(1YMYRK23AmC~*&8=hG7IXO0C1yqTPaJWFFL_6JBpnoMIXcsY>1ImurhiOn%Vt#m z5fC`n=)o~7+xK(flFC$FU60NsKN=pf&Dn9QJZi=3cwrm4I|rNBb9(vyap&(_JYhlR zCEhOE8{3?ktnW4_&R8ZOEj;gf4*xH^W5+UNujMpVzT34~{@9PX!h3Wk|Ka$Mox;Fy ze2R(Tv5(acSr+AHDoQLDVbJLg`uAs{=eP4));+&@o#p&$o;{4OIA-jB`e#}P$b+7) KelF{r5}E)9`D>*B literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/g10n2c08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/g10n2c08.png new file mode 100644 index 0000000000000000000000000000000000000000..b3039970c10e786659f9752913c41b8a26b1179a GIT binary patch literal 285 zcmV+&0pk9NP)@();|)eY}Flu=NxRyTwAba19zW zNcaI2oGKYjCc{&aErrDdt_X+yXV}i(ARy1f{s-JoAU8t`q60wT=m4lx4WLj1#Goi5 z0}g9&GjO^Y?CP)v$sk{c!58?k4IU`q1^i?TP{4Hwg$wQL5H j&i)?k>vRoL{Z;$`)U8ca-rtX&00000NkvXXu0mjffk|^a literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/g10n3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/g10n3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..1b6a6be2ca571956ce45bae5470c03cd2266fd89 GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvmUKs7M+U~W1%@xC#RK_r0X`wF zKzij$28RFt|F8NV62kBw2K)ELwKW(gS)7nLjn&=f#?aQDJu;O{JayK!wx3QIX10fSyM0LA|{qo zr@2pEmRO+mfXPAELrdbu&4xCml^Xi(yO|>!d_)=C9<`pFX(h-9w2r~k)z4*}Q$iB} Dmq$l3 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/g25n0g16.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/g25n0g16.png new file mode 100644 index 0000000000000000000000000000000000000000..a9f6787c7ab50968e37514529f53a6249762ca17 GIT binary patch literal 383 zcmV-_0f7FAP)$PJx&n)kH3|svhZZ(r0ivJ>sO?q;pvG#q^Pdnj*zaWkW*`MH zgTvuZ0}6;^p~3Np8;yq0Knh?6r_-MX=QC+2p~2;X8_lNB;Cht-m;nW}T0(={4L90t zp~3y`2b5*NfC2^sT+`Rg3?2{M=yd!5X7GIa0W_Ea1$4W905hON>h)}1tpH{-dv8qr zYh?yig&X}o0Kj6ArtTUrpa891&a%3ygBrvY39Yr(8X(K&^KXDL&H#scy)e;z2g~Jm zWsI8yn*n_$yCu)3Q-D0T+mfUnB%1+k42L$a*EYNPyBsudfL{?b7>)b@WOU@^z{4wh)7YG#lngB zpO4to4PwXAUtl<^EZyItpI$A4c0ho^z$dGfn@-I=c}&df(}j0HTdM#fvR(&W;o}K& zG9bWk2p1G&K>eeU5^p%h%I3sGP!q+00000NkvXXu0mjf+0UW; literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/g25n3p04.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/g25n3p04.png new file mode 100644 index 0000000000000000000000000000000000000000..4f943c6175f31c3609bf4458fff9d3c591f112f7 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvmUKs7M+W8#6Mlqr_yhTJ0X`wF zK$<~Um*M|^0Re{pF+l47e;^$L6aWH-pup=-feKPQT^vIy=IlMUk+;Es$0ab@YvMKS z4NTlSPH?R`z_n#cTw`u=ph~f+$IqufMW?aPJ#nF}g2zjg8cRnT4C8a-ev#F_0RlCh$*z86iP| zaVyJO7(suuAQvVX1O;6NThywBgyle%p;kiF?Y!^7%*UPg-nqYX?l%`38+JvCR~8dd zB)L1WAJ5hPQxeAagYN1HJPOnCJ#h+k^;hS6n~B2RjrC1+_??J}9FY(yk@yB>bT*DpgTKuCNOVY>KHEazxSy09-hZpy3u#A`f8V zHsSyi48tL)AqHsN1Qs~Z7}}6FKO%n`+o-V&6GM>jhqkEH%nvXazo!T$>gO2@$wLLX z`|$=*dd46fKkdM#M|Gt4gY=kt2bIyaHXr#UDa%9SKVaIhyKeYwX|*h#PykDCr-AN)~w;?+^4yN zr-s8{bD3D{UA{K=ZMOCByHINGNoR9@dNzC}GcY)|Ju@)5BDQntP)ot4{Pc$K&wPET zb0n|#)BTaYxn$Ykj&#Mk)P>xtSgI$wXyNturRDT2Jr$Wxq-QF}D@JdO#pYwF-{qy3 za({X@R~7V)9!N#APsdv3V~y=a8^^ai{@Af>>)D}>Xm+S$ZFc#a+o9u`9GCs*$lY49 f5Z_z){0_hPQv2`7;*nR={=b*3Ye+n*O`ZD>%pJzM literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/pp0n6a08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/pp0n6a08.png new file mode 100644 index 0000000000000000000000000000000000000000..4ed7a30e4d16e0527c2695bf0493ec1ba156d8c4 GIT binary patch literal 818 zcmW-fy=zoa5XDDVMV1W~5)ehSNEfm%xBU z2&4*#Vrk)5qd16#e?&m+j8-!K=3aRF?(EE*Ip@rKwzzO@s(ZRCB2&ZbgJs_5>p6am z-v^V)Z(c|4^l$d%(39=C{jFOfho3IqSefU45fw2J7YUK9NLAECT{J|qlBQxN=3*h1 zRqHBl;w~QIS*@WGCgBnxkswK`q)EDDND_bzG*~oKoi(S9sk&;YW{o8>Q**UY%VI&& zP2JT)J&P2zVH&Ox8VN^QHBHksLzAhn1`N`$gcdmlgAFuTG-nB_nB!()mZgueMDFHc zo+TF+B*QJjB2f=1YAxL|ERlyF(4-qX&<_a|!WuA0!;(^XFc@s0!OC*3(!`u_&oUBy zj3tWjNPI;uEJ%8WCqCjd2EdDQj7BY1;s-S8#tt|k7=^F~4AOK$3myyx8|bVRRW)g1 zPO>qKL?2^`BC{6Q)?X~zMr)sKNkyvpUh8dE7RTmJZMz9B%1uKpztJiMZ4%IY4r~eY z(8Q?Quih^4C60%K{>u7`U*G4t?=SSOE=^ya`TK7B-cIk^kKIxK>$!`kdXxRvvtNF0 z{=1(CZ$@{A&yUj9`FybVvBPCeK0F$)bCK1Jv!jQXxa@3n&V2eaJ~@4&3S>CHFnD=o G?eTv$Q+DJ4 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/ps1n0g08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/ps1n0g08.png new file mode 100644 index 0000000000000000000000000000000000000000..99625fa4ba1c6964446797075c47ee05dcae22f5 GIT binary patch literal 1456 zcmXxkK}(cj5Ww*#aAOfA1P>uWA_;XUl%aEMY}7)@A`%EeORi%bMFPEcC<37_1p^(# z58$OQAu`alqXdS>Zh3N=`9rY#yvw`)eV+Lp_Wd|qc|1KiH`!X79z5x<=5@Z_-CWx9 znZ?_Anb_(*>$NNI50=lrzHF^Cw=;aYw)1B9?#A0!o2|;IT3D6USe-R$6&j%t8le#y zp-~#8Q5vOD8l^EBqcIwzF&d+B8mDm@r*Rsm37Vh@nxF}qs77!JE@{yjc z!KJtqm*P@fc~mxr%WxSk!)3T~kZN!lF2iNG3|Ed>4KBlFxD1!!N<=ld442_DT!t$@ zR)foM87{+RxH7B;m*Fy8hRbl}HrY5X$K|*jm*dJqs=?*B9GByATscBDxEz<`a$Jrp z2doB{<8oY%%W>uS)!=emj>~a5u3W0Y<+vP|<8oY?Wb^-3|G~f&xB^#hQ1@{KuD}(z z0#|Nd4X(fyxB^$;%EPO{6}SRd;0jzhS~a)=SKtaFjV?4UmX1mAVr#hYeYu!5+e{Vh*?JeDyT{@Zhyfr@Ddogvjv;XPO$z}Yw k`0#ss`FehH`ek!G(S~Q2+xLI{T(5`g;L%F|=;7$yKLBk?PXGV_ literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/ps1n2c16.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/ps1n2c16.png new file mode 100644 index 0000000000000000000000000000000000000000..0c7a6b380e9a2e60c887a260d43a41553260fc74 GIT binary patch literal 1620 zcmXxlUue~37{Kx0Hvcggjqqa5wnZ0q2=d=kL4nm-M{-~`!ka?f!Rk2%2GSHdS8n`@?ayH~ad{hwEnjwy0Kqsa!OTO|EN93>3xU&cXi8TL<6T z_xS63cfMIH^CJ6I2&=LtR%6Yv3XRYRjnD{<&?t@4D2>u6jnX8VM3ZO|O`=IOMq@Na zV>Cu%G?^yTWSUHqX>v4zOK?dMjTF&H5sial#U;1|m*5gy{x}+3f=h4-F2Us=iUyb9 z5?q2yaQT+e;1XPdOK=G;KQJ0xf=h4-F2UvFc%!%!m*P@fipz(M2AASeT#8F^IVBog zic4`RF2&^!qQRxO6qn*sT%JdROK~YK#ih7>vuJQBF2$v|6qg_6O~RFMC0q$t!sUZR zgDc@mxDu{}%g2lcSHhKWC0q%YBcj2Ta3x#`SHk6QqrsJMC0q$t!sTH!xDu{}E8$AG zd>d~Jm*Fy8hRbmIA<^J6T!zbV87?0o8eE3Ua2YPc%WxSkPrUj672m;(E91(ze1mw7E91(zGOmovw~q!_#+7kpTp5=i9u2OH zE91(zGAr)-96iXX{yejIXKXmyW6~f=;W#C=Y~#RUr~DD=C<7}Pd4V( zH~-n#+;niNVPDrK+m6lmtvvO7t!H)hgZe|I>Uj6!dzWuN&{h0UyVC!@}`gd2y$Mt{4pIX~;?82+n?%}i3yBA9R2bMfB^Yr&uM<0CVz0;%J!>30d8(#j? tiKe%Q>ay~W(faXa_sTCXI{$_Ic%%2f9ZRP!&BY%;-^MMK?|Q3;?gI^1pmYEL literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/ps2n0g08.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/ps2n0g08.png new file mode 100644 index 0000000000000000000000000000000000000000..90b2979685772423e875e3504fcfee1d406d6a37 GIT binary patch literal 2320 zcmYk4K}%Fo7=}-vF^E!vi-@2W1+@s2p><45)If=Z1VNCbj)cC8aMZEGd0S>5Cs( zJyVm_Gd0qcmznjVR@Ql^~~0? zdZrdvOdOUJmK0Z{u%x&m$?BPHW%W!=R?o4*q*$@9zh`Rp)iX8wW@^~XkYryy^VYt4 zP9=Ty%sW{>) zq|8hTOUle7Sv}KLR?pOA^_=N)gPxgFR?pOA^_+de>Y2@C^-N7x&oK~I&$y7)Gc{Q~ z$Jzf|XMb5e<3v`^vD~r;U-oueZ@odkx4iv!b#3Khnd^M|I6XPJf2wok=+F7zj<`#D literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/pngsuite/unused/ps2n2c16.png b/source/engine/thirdparty/stb/tests/pngsuite/unused/ps2n2c16.png new file mode 100644 index 0000000000000000000000000000000000000000..a4a181e4ecfc378abcba1a2af79c0aa96eae57e1 GIT binary patch literal 2484 zcmYk4Z)ntY9LInAqm9Avh#oBGwjSvrRL)-t1*>k|sDbH-Jt?dmsdoql;x5t)bSqee zNKkNb>TLSJ5KOlMePH5(prGSe59(15hG-tNTI`k%!mfMs?)AmNe&6?fe)sx*KA#u& z(eTa}JKEQ`M^V%<^iukj#`&236U~k1#Si-y8b@m`^>Qj|nw;*d7DuC~tuQvcV`Oaq z;m7y9{pP+X(fEe`>OP`4q-;o~kjf#||4(sXlMo>hazvubuF(laZWRr1VAW|cg%5-gt86&BB` z!Q$D|`35{|PFOsv28(C!V;0Za3>MF-!Q$Bl%;H%tVDYRPES~M`!L7YNES}{A7SFca zV2ks|CmY^w52aHhhfn`p{I>o5Es69O3#ns29=&@1%e%#%wJo1aZp?mKE_6-~-s=Bl z`s2hv^z4%hCztjf8avwAzbPKA+^oI!&ei7MEBWNXbMa@jKb8-^{!>$?_SM8_b#b}* zNdElH+;jQ!w;oNtaA)^{mTlF=t<8T|cQj30tClb1raPu;LtQh^=X#&Wd{Eht%$(?1 zv-$;;WxjP*z>Dy^=4^c9w}F4(c<9=9i;ab& Np~0Q$O9Pqr{sYFSp`QQ% literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/prerelease/stb_lib.h b/source/engine/thirdparty/stb/tests/prerelease/stb_lib.h new file mode 100644 index 0000000..0a6eead --- /dev/null +++ b/source/engine/thirdparty/stb/tests/prerelease/stb_lib.h @@ -0,0 +1,3305 @@ +/* stb_lib.h - v1.00 - http://nothings.org/stb + no warranty is offered or implied; use this code at your own risk + + ============================================================================ + You MUST + + #define STB_LIB_IMPLEMENTATION + + in EXACTLY _one_ C or C++ file that includes this header, BEFORE the + include, like this: + + #define STB_LIB_IMPLEMENTATION + #include "stblib_files.h" + + All other files should just #include "stblib_files.h" without the #define. + ============================================================================ + +LICENSE + + See end of file for license information. + +CREDITS + + Written by Sean Barrett. + + Fixes: + Philipp Wiesemann Robert Nix + r-lyeh blackpawn + github:Mojofreem Ryan Whitworth + Vincent Isambart Mike Sartain + Eugene Opalev Tim Sjostrand + github:infatum Dave Butler +*/ + +#ifndef STB_INCLUDE_STB_LIB_H + +#include + +#if defined(_WIN32) && !defined(__MINGW32__) + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif + #ifndef _CRT_NON_CONFORMING_SWPRINTFS + #define _CRT_NON_CONFORMING_SWPRINTFS + #endif + #if !defined(_MSC_VER) || _MSC_VER > 1700 + #include // _BitScanReverse + #endif +#endif + +#include // stdlib could have min/max +#include // need FILE +#include // stb_define_hash needs memcpy/memset +#include // stb_dirtree + +typedef unsigned char stb_uchar; +typedef unsigned char stb_uint8; +typedef unsigned int stb_uint; +typedef unsigned short stb_uint16; +typedef short stb_int16; +typedef signed char stb_int8; +#if defined(STB_USE_LONG_FOR_32_BIT_INT) || defined(STB_LONG32) + typedef unsigned long stb_uint32; + typedef long stb_int32; +#else + typedef unsigned int stb_uint32; + typedef int stb_int32; +#endif +typedef char stb__testsize2_16[sizeof(stb_uint16)==2 ? 1 : -1]; +typedef char stb__testsize2_32[sizeof(stb_uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER + typedef unsigned __int64 stb_uint64; + typedef __int64 stb_int64; + #define STB_IMM_UINT64(literalui64) (literalui64##ui64) +#else + // ?? + typedef unsigned long long stb_uint64; + typedef long long stb_int64; + #define STB_IMM_UINT64(literalui64) (literalui64##ULL) +#endif +typedef char stb__testsize2_64[sizeof(stb_uint64)==8 ? 1 : -1]; + +#ifdef __cplusplus + #define STB_EXTERN extern "C" +#else + #define STB_EXTERN extern +#endif + +// check for well-known debug defines +#if defined(DEBUG) || defined(_DEBUG) || defined(DBG) + #ifndef NDEBUG + #define STB_DEBUG + #endif +#endif + +#ifdef STB_DEBUG + #include +#endif +#endif // STB_INCLUDE_STB_LIB_H + +#ifdef STB_LIB_IMPLEMENTATION + #include + #include + #include + #include + #include + #ifndef _WIN32 + #include + #else + #include // _mktemp + #include // _rmdir + #endif + #include // stat()/_stat() + #include // stat()/_stat() +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Miscellany +// + +#ifdef _WIN32 + #define stb_stricmp(a,b) stricmp(a,b) + #define stb_strnicmp(a,b,n) strnicmp(a,b,n) +#else + #define stb_stricmp(a,b) strcasecmp(a,b) + #define stb_strnicmp(a,b,n) strncasecmp(a,b,n) +#endif + +#ifndef STB_INCLUDE_STB_LIB_H +STB_EXTERN void stb_fatal(char *fmt, ...); +STB_EXTERN void stb_swap(void *p, void *q, size_t sz); +STB_EXTERN double stb_linear_remap(double x, double x_min, double x_max, + double out_min, double out_max); + +#define stb_arrcount(x) (sizeof(x)/sizeof((x)[0])) +#define stb_lerp(t,a,b) ( (a) + (t) * (float) ((b)-(a)) ) +#define stb_unlerp(t,a,b) ( ((t) - (a)) / (float) ((b) - (a)) ) + +#endif + + +#ifdef STB_LIB_IMPLEMENTATION +void stb_fatal(char *s, ...) +{ + va_list a; + va_start(a,s); + fputs("Fatal error: ", stderr); + vfprintf(stderr, s, a); + va_end(a); + fputs("\n", stderr); + #ifdef STB_DEBUG + #ifdef _MSC_VER + #ifndef _WIN64 + __asm int 3; // trap to debugger! + #else + __debugbreak(); + #endif + #else + __builtin_trap(); + #endif + #endif + exit(1); +} + +typedef struct { char d[4]; } stb__4; +typedef struct { char d[8]; } stb__8; + +// optimize the small cases, though you shouldn't be calling this for those! +void stb_swap(void *p, void *q, size_t sz) +{ + char buffer[256]; + if (p == q) return; + if (sz == 4) { + stb__4 temp = * ( stb__4 *) p; + * (stb__4 *) p = * ( stb__4 *) q; + * (stb__4 *) q = temp; + return; + } else if (sz == 8) { + stb__8 temp = * ( stb__8 *) p; + * (stb__8 *) p = * ( stb__8 *) q; + * (stb__8 *) q = temp; + return; + } + + while (sz > sizeof(buffer)) { + stb_swap(p, q, sizeof(buffer)); + p = (char *) p + sizeof(buffer); + q = (char *) q + sizeof(buffer); + sz -= sizeof(buffer); + } + + memcpy(buffer, p , sz); + memcpy(p , q , sz); + memcpy(q , buffer, sz); +} + +#ifdef stb_linear_remap +#undef stb_linear_remap +#endif + +double stb_linear_remap(double x, double x_min, double x_max, + double out_min, double out_max) +{ + return stb_lerp(stb_unlerp(x,x_min,x_max),out_min,out_max); +} + +#define stb_linear_remap(t,a,b,c,d) stb_lerp(stb_unlerp(t,a,b),c,d) +#endif // STB_LIB_IMPLEMENTATION + +#ifndef STB_INCLUDE_STB_LIB_H +// avoid unnecessary function call, but define function so its address can be taken +#ifndef stb_linear_remap +#define stb_linear_remap(t,a,b,c,d) stb_lerp(stb_unlerp(t,a,b),c,d) +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// cross-platform snprintf because they keep changing that, +// and with old compilers without vararg macros we can't write +// a macro wrapper to fix it up + +#ifndef STB_INCLUDE_STB_LIB_H +STB_EXTERN int stb_snprintf(char *s, size_t n, const char *fmt, ...); +STB_EXTERN int stb_vsnprintf(char *s, size_t n, const char *fmt, va_list v); +STB_EXTERN char *stb_sprintf(const char *fmt, ...); +#endif + +#ifdef STB_LIB_IMPLEMENTATION + +int stb_vsnprintf(char *s, size_t n, const char *fmt, va_list v) +{ + int res; + #ifdef _WIN32 + // Could use "_vsnprintf_s(s, n, _TRUNCATE, fmt, v)" ? + res = _vsnprintf(s,n,fmt,v); + #else + res = vsnprintf(s,n,fmt,v); + #endif + if (n) s[n-1] = 0; + // Unix returns length output would require, Windows returns negative when truncated. + return (res >= (int) n || res < 0) ? -1 : res; +} + +int stb_snprintf(char *s, size_t n, const char *fmt, ...) +{ + int res; + va_list v; + va_start(v,fmt); + res = stb_vsnprintf(s, n, fmt, v); + va_end(v); + return res; +} + +char *stb_sprintf(const char *fmt, ...) +{ + static char buffer[1024]; + va_list v; + va_start(v,fmt); + stb_vsnprintf(buffer,1024,fmt,v); + va_end(v); + return buffer; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Windows UTF8 filename handling +// +// Windows stupidly treats 8-bit filenames as some dopey code page, +// rather than utf-8. If we want to use utf8 filenames, we have to +// convert them to WCHAR explicitly and call WCHAR versions of the +// file functions. So, ok, we do. + + +#ifndef STB_INCLUDE_STB_LIB_H +#ifdef _WIN32 + #define stb__fopen(x,y) _wfopen((const wchar_t *)stb__from_utf8(x), (const wchar_t *)stb__from_utf8_alt(y)) + #define stb__windows(x,y) x +#else + #define stb__fopen(x,y) fopen(x,y) + #define stb__windows(x,y) y +#endif + + +typedef unsigned short stb__wchar; + +STB_EXTERN stb__wchar * stb_from_utf8(stb__wchar *buffer, char *str, int n); +STB_EXTERN char * stb_to_utf8 (char *buffer, stb__wchar *str, int n); + +STB_EXTERN stb__wchar *stb__from_utf8(char *str); +STB_EXTERN stb__wchar *stb__from_utf8_alt(char *str); +STB_EXTERN char *stb__to_utf8(stb__wchar *str); +#endif + +#ifdef STB_LIB_IMPLEMENTATION +stb__wchar * stb_from_utf8(stb__wchar *buffer, char *ostr, int n) +{ + unsigned char *str = (unsigned char *) ostr; + stb_uint32 c; + int i=0; + --n; + while (*str) { + if (i >= n) + return NULL; + if (!(*str & 0x80)) + buffer[i++] = *str++; + else if ((*str & 0xe0) == 0xc0) { + if (*str < 0xc2) return NULL; + c = (*str++ & 0x1f) << 6; + if ((*str & 0xc0) != 0x80) return NULL; + buffer[i++] = c + (*str++ & 0x3f); + } else if ((*str & 0xf0) == 0xe0) { + if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return NULL; + if (*str == 0xed && str[1] > 0x9f) return NULL; // str[1] < 0x80 is checked below + c = (*str++ & 0x0f) << 12; + if ((*str & 0xc0) != 0x80) return NULL; + c += (*str++ & 0x3f) << 6; + if ((*str & 0xc0) != 0x80) return NULL; + buffer[i++] = c + (*str++ & 0x3f); + } else if ((*str & 0xf8) == 0xf0) { + if (*str > 0xf4) return NULL; + if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return NULL; + if (*str == 0xf4 && str[1] > 0x8f) return NULL; // str[1] < 0x80 is checked below + c = (*str++ & 0x07) << 18; + if ((*str & 0xc0) != 0x80) return NULL; + c += (*str++ & 0x3f) << 12; + if ((*str & 0xc0) != 0x80) return NULL; + c += (*str++ & 0x3f) << 6; + if ((*str & 0xc0) != 0x80) return NULL; + c += (*str++ & 0x3f); + // utf-8 encodings of values used in surrogate pairs are invalid + if ((c & 0xFFFFF800) == 0xD800) return NULL; + if (c >= 0x10000) { + c -= 0x10000; + if (i + 2 > n) return NULL; + buffer[i++] = 0xD800 | (0x3ff & (c >> 10)); + buffer[i++] = 0xDC00 | (0x3ff & (c )); + } + } else + return NULL; + } + buffer[i] = 0; + return buffer; +} + +char * stb_to_utf8(char *buffer, stb__wchar *str, int n) +{ + int i=0; + --n; + while (*str) { + if (*str < 0x80) { + if (i+1 > n) return NULL; + buffer[i++] = (char) *str++; + } else if (*str < 0x800) { + if (i+2 > n) return NULL; + buffer[i++] = 0xc0 + (*str >> 6); + buffer[i++] = 0x80 + (*str & 0x3f); + str += 1; + } else if (*str >= 0xd800 && *str < 0xdc00) { + stb_uint32 c; + if (i+4 > n) return NULL; + c = ((str[0] - 0xd800) << 10) + ((str[1]) - 0xdc00) + 0x10000; + buffer[i++] = 0xf0 + (c >> 18); + buffer[i++] = 0x80 + ((c >> 12) & 0x3f); + buffer[i++] = 0x80 + ((c >> 6) & 0x3f); + buffer[i++] = 0x80 + ((c ) & 0x3f); + str += 2; + } else if (*str >= 0xdc00 && *str < 0xe000) { + return NULL; + } else { + if (i+3 > n) return NULL; + buffer[i++] = 0xe0 + (*str >> 12); + buffer[i++] = 0x80 + ((*str >> 6) & 0x3f); + buffer[i++] = 0x80 + ((*str ) & 0x3f); + str += 1; + } + } + buffer[i] = 0; + return buffer; +} + +stb__wchar *stb__from_utf8(char *str) +{ + static stb__wchar buffer[4096]; + return stb_from_utf8(buffer, str, 4096); +} + +stb__wchar *stb__from_utf8_alt(char *str) +{ + static stb__wchar buffer[4096]; + return stb_from_utf8(buffer, str, 4096); +} + +char *stb__to_utf8(stb__wchar *str) +{ + static char buffer[4096]; + return stb_to_utf8(buffer, str, 4096); +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// qsort Compare Routines +// NOT THREAD SAFE + +#ifndef STB_INCLUDE_STB_LIB_H +STB_EXTERN int (*stb_intcmp(int offset))(const void *a, const void *b); +STB_EXTERN int (*stb_qsort_strcmp(int offset))(const void *a, const void *b); +STB_EXTERN int (*stb_qsort_stricmp(int offset))(const void *a, const void *b); +STB_EXTERN int (*stb_floatcmp(int offset))(const void *a, const void *b); +STB_EXTERN int (*stb_doublecmp(int offset))(const void *a, const void *b); +STB_EXTERN int (*stb_ucharcmp(int offset))(const void *a, const void *b); +STB_EXTERN int (*stb_charcmp(int offset))(const void *a, const void *b); +#endif + +#ifdef STB_LIB_IMPLEMENTATION +static int stb__intcmpoffset, stb__ucharcmpoffset, stb__strcmpoffset; +static int stb__floatcmpoffset, stb__doublecmpoffset, stb__charcmpoffset; + +int stb__intcmp(const void *a, const void *b) +{ + const int p = *(const int *) ((const char *) a + stb__intcmpoffset); + const int q = *(const int *) ((const char *) b + stb__intcmpoffset); + return p < q ? -1 : p > q; +} + +int stb__ucharcmp(const void *a, const void *b) +{ + const int p = *(const unsigned char *) ((const char *) a + stb__ucharcmpoffset); + const int q = *(const unsigned char *) ((const char *) b + stb__ucharcmpoffset); + return p < q ? -1 : p > q; +} + +int stb__charcmp(const void *a, const void *b) +{ + const int p = *(const char *) ((const char *) a + stb__ucharcmpoffset); + const int q = *(const char *) ((const char *) b + stb__ucharcmpoffset); + return p < q ? -1 : p > q; +} + +int stb__floatcmp(const void *a, const void *b) +{ + const float p = *(const float *) ((const char *) a + stb__floatcmpoffset); + const float q = *(const float *) ((const char *) b + stb__floatcmpoffset); + return p < q ? -1 : p > q; +} + +int stb__doublecmp(const void *a, const void *b) +{ + const double p = *(const double *) ((const char *) a + stb__doublecmpoffset); + const double q = *(const double *) ((const char *) b + stb__doublecmpoffset); + return p < q ? -1 : p > q; +} + +int stb__qsort_strcmp(const void *a, const void *b) +{ + const char *p = *(const char **) ((const char *) a + stb__strcmpoffset); + const char *q = *(const char **) ((const char *) b + stb__strcmpoffset); + return strcmp(p,q); +} + +int stb__qsort_stricmp(const void *a, const void *b) +{ + const char *p = *(const char **) ((const char *) a + stb__strcmpoffset); + const char *q = *(const char **) ((const char *) b + stb__strcmpoffset); + return stb_stricmp(p,q); +} + +int (*stb_intcmp(int offset))(const void *, const void *) +{ + stb__intcmpoffset = offset; + return &stb__intcmp; +} + +int (*stb_ucharcmp(int offset))(const void *, const void *) +{ + stb__ucharcmpoffset = offset; + return &stb__ucharcmp; +} + +int (*stb_charcmp(int offset))(const void *, const void *) +{ + stb__charcmpoffset = offset; + return &stb__ucharcmp; +} + +int (*stb_qsort_strcmp(int offset))(const void *, const void *) +{ + stb__strcmpoffset = offset; + return &stb__qsort_strcmp; +} + +int (*stb_qsort_stricmp(int offset))(const void *, const void *) +{ + stb__strcmpoffset = offset; + return &stb__qsort_stricmp; +} + +int (*stb_floatcmp(int offset))(const void *, const void *) +{ + stb__floatcmpoffset = offset; + return &stb__floatcmp; +} + +int (*stb_doublecmp(int offset))(const void *, const void *) +{ + stb__doublecmpoffset = offset; + return &stb__doublecmp; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// String Processing +// + +#ifndef STB_INCLUDE_STB_LIB_H +#define stb_prefixi(s,t) (0==stb_strnicmp((s),(t),strlen(t))) + +enum stb_splitpath_flag +{ + STB_PATH = 1, + STB_FILE = 2, + STB_EXT = 4, + STB_PATH_FILE = STB_PATH + STB_FILE, + STB_FILE_EXT = STB_FILE + STB_EXT, + STB_EXT_NO_PERIOD = 8, +}; + +STB_EXTERN char * stb_skipwhite(char *s); +STB_EXTERN char * stb_trimwhite(char *s); +STB_EXTERN char * stb_skipnewline(char *s); +STB_EXTERN char * stb_strncpy(char *s, char *t, int n); +STB_EXTERN char * stb_substr(char *t, int n); +STB_EXTERN char * stb_duplower(char *s); +STB_EXTERN void stb_tolower (char *s); +STB_EXTERN char * stb_strchr2 (char *s, char p1, char p2); +STB_EXTERN char * stb_strrchr2(char *s, char p1, char p2); +STB_EXTERN char * stb_strtok(char *output, char *src, char *delimit); +STB_EXTERN char * stb_strtok_keep(char *output, char *src, char *delimit); +STB_EXTERN char * stb_strtok_invert(char *output, char *src, char *allowed); +STB_EXTERN char * stb_dupreplace(char *s, char *find, char *replace); +STB_EXTERN void stb_replaceinplace(char *s, char *find, char *replace); +STB_EXTERN char * stb_splitpath(char *output, char *src, int flag); +STB_EXTERN char * stb_splitpathdup(char *src, int flag); +STB_EXTERN char * stb_replacedir(char *output, char *src, char *dir); +STB_EXTERN char * stb_replaceext(char *output, char *src, char *ext); +STB_EXTERN void stb_fixpath(char *path); +STB_EXTERN char * stb_shorten_path_readable(char *path, int max_len); +STB_EXTERN int stb_suffix (char *s, char *t); +STB_EXTERN int stb_suffixi(char *s, char *t); +STB_EXTERN int stb_prefix (char *s, char *t); +STB_EXTERN char * stb_strichr(char *s, char t); +STB_EXTERN char * stb_stristr(char *s, char *t); +STB_EXTERN int stb_prefix_count(char *s, char *t); +STB_EXTERN const char * stb_plural(int n); // "s" or "" +STB_EXTERN size_t stb_strscpy(char *d, const char *s, size_t n); + +STB_EXTERN char **stb_tokens(char *src, char *delimit, int *count); +STB_EXTERN char **stb_tokens_nested(char *src, char *delimit, int *count, char *nest_in, char *nest_out); +STB_EXTERN char **stb_tokens_nested_empty(char *src, char *delimit, int *count, char *nest_in, char *nest_out); +STB_EXTERN char **stb_tokens_allowempty(char *src, char *delimit, int *count); +STB_EXTERN char **stb_tokens_stripwhite(char *src, char *delimit, int *count); +STB_EXTERN char **stb_tokens_withdelim(char *src, char *delimit, int *count); +STB_EXTERN char **stb_tokens_quoted(char *src, char *delimit, int *count); +// with 'quoted', allow delimiters to appear inside quotation marks, and don't +// strip whitespace inside them (and we delete the quotation marks unless they +// appear back to back, in which case they're considered escaped) +#endif // STB_INCLUDE_STB_LIB_H + +#ifdef STB_LIB_IMPLEMENTATION +#include + +size_t stb_strscpy(char *d, const char *s, size_t n) +{ + size_t len = strlen(s); + if (len >= n) { + if (n) d[0] = 0; + return 0; + } + strcpy(d,s); + return len + 1; +} + +const char *stb_plural(int n) +{ + return n == 1 ? "" : "s"; +} + +int stb_prefix(char *s, char *t) +{ + while (*t) + if (*s++ != *t++) + return 0; + return 1; +} + +int stb_prefix_count(char *s, char *t) +{ + int c=0; + while (*t) { + if (*s++ != *t++) + break; + ++c; + } + return c; +} + +int stb_suffix(char *s, char *t) +{ + size_t n = strlen(s); + size_t m = strlen(t); + if (m <= n) + return 0 == strcmp(s+n-m, t); + else + return 0; +} + +int stb_suffixi(char *s, char *t) +{ + size_t n = strlen(s); + size_t m = strlen(t); + if (m <= n) + return 0 == stb_stricmp(s+n-m, t); + else + return 0; +} + +// originally I was using this table so that I could create known sentinel +// values--e.g. change whitetable[0] to be true if I was scanning for whitespace, +// and false if I was scanning for nonwhite. I don't appear to be using that +// functionality anymore (I do for tokentable, though), so just replace it +// with isspace() +char *stb_skipwhite(char *s) +{ + while (isspace((unsigned char) *s)) ++s; + return s; +} + +char *stb_skipnewline(char *s) +{ + if (s[0] == '\r' || s[0] == '\n') { + if (s[0]+s[1] == '\r' + '\n') ++s; + ++s; + } + return s; +} + +char *stb_trimwhite(char *s) +{ + int i,n; + s = stb_skipwhite(s); + n = (int) strlen(s); + for (i=n-1; i >= 0; --i) + if (!isspace(s[i])) + break; + s[i+1] = 0; + return s; +} + +char *stb_strncpy(char *s, char *t, int n) +{ + strncpy(s,t,n); + s[n-1] = 0; + return s; +} + +char *stb_substr(char *t, int n) +{ + char *a; + int z = (int) strlen(t); + if (z < n) n = z; + a = (char *) malloc(n+1); + strncpy(a,t,n); + a[n] = 0; + return a; +} + +char *stb_duplower(char *s) +{ + char *p = strdup(s), *q = p; + while (*q) { + *q = tolower(*q); + ++q; + } + return p; +} + +void stb_tolower(char *s) +{ + while (*s) { + *s = tolower(*s); + ++s; + } +} + +char *stb_strchr2(char *s, char x, char y) +{ + for(; *s; ++s) + if (*s == x || *s == y) + return s; + return NULL; +} + +char *stb_strrchr2(char *s, char x, char y) +{ + char *r = NULL; + for(; *s; ++s) + if (*s == x || *s == y) + r = s; + return r; +} + +char *stb_strichr(char *s, char t) +{ + if (tolower(t) == toupper(t)) + return strchr(s,t); + return stb_strchr2(s, (char) tolower(t), (char) toupper(t)); +} + +char *stb_stristr(char *s, char *t) +{ + size_t n = strlen(t); + char *z; + if (n==0) return s; + while ((z = stb_strichr(s, *t)) != NULL) { + if (0==stb_strnicmp(z, t, n)) + return z; + s = z+1; + } + return NULL; +} + +static char *stb_strtok_raw(char *output, char *src, char *delimit, int keep, int invert) +{ + if (invert) { + while (*src && strchr(delimit, *src) != NULL) { + *output++ = *src++; + } + } else { + while (*src && strchr(delimit, *src) == NULL) { + *output++ = *src++; + } + } + *output = 0; + if (keep) + return src; + else + return *src ? src+1 : src; +} + +char *stb_strtok(char *output, char *src, char *delimit) +{ + return stb_strtok_raw(output, src, delimit, 0, 0); +} + +char *stb_strtok_keep(char *output, char *src, char *delimit) +{ + return stb_strtok_raw(output, src, delimit, 1, 0); +} + +char *stb_strtok_invert(char *output, char *src, char *delimit) +{ + return stb_strtok_raw(output, src, delimit, 1,1); +} + +static char **stb_tokens_raw(char *src_, char *delimit, int *count, + int stripwhite, int allow_empty, char *start, char *end) +{ + int nested = 0; + unsigned char *src = (unsigned char *) src_; + static char stb_tokentable[256]; // rely on static initializion to 0 + static char stable[256],etable[256]; + char *out; + char **result; + int num=0; + unsigned char *s; + + s = (unsigned char *) delimit; while (*s) stb_tokentable[*s++] = 1; + if (start) { + s = (unsigned char *) start; while (*s) stable[*s++] = 1; + s = (unsigned char *) end; if (s) while (*s) stable[*s++] = 1; + s = (unsigned char *) end; if (s) while (*s) etable[*s++] = 1; + } + stable[0] = 1; + + // two passes through: the first time, counting how many + s = (unsigned char *) src; + while (*s) { + // state: just found delimiter + // skip further delimiters + if (!allow_empty) { + stb_tokentable[0] = 0; + while (stb_tokentable[*s]) + ++s; + if (!*s) break; + } + ++num; + // skip further non-delimiters + stb_tokentable[0] = 1; + if (stripwhite == 2) { // quoted strings + while (!stb_tokentable[*s]) { + if (*s != '"') + ++s; + else { + ++s; + if (*s == '"') + ++s; // "" -> ", not start a string + else { + // begin a string + while (*s) { + if (s[0] == '"') { + if (s[1] == '"') s += 2; // "" -> " + else { ++s; break; } // terminating " + } else + ++s; + } + } + } + } + } else + while (nested || !stb_tokentable[*s]) { + if (stable[*s]) { + if (!*s) break; + if (end ? etable[*s] : nested) + --nested; + else + ++nested; + } + ++s; + } + if (allow_empty) { + if (*s) ++s; + } + } + // now num has the actual count... malloc our output structure + // need space for all the strings: strings won't be any longer than + // original input, since for every '\0' there's at least one delimiter + result = (char **) malloc(sizeof(*result) * (num+1) + (s-src+1)); + if (result == NULL) return result; + out = (char *) (result + (num+1)); + // second pass: copy out the data + s = (unsigned char *) src; + num = 0; + nested = 0; + while (*s) { + char *last_nonwhite; + // state: just found delimiter + // skip further delimiters + if (!allow_empty) { + stb_tokentable[0] = 0; + if (stripwhite) + while (stb_tokentable[*s] || isspace(*s)) + ++s; + else + while (stb_tokentable[*s]) + ++s; + } else if (stripwhite) { + while (isspace(*s)) ++s; + } + if (!*s) break; + // we're past any leading delimiters and whitespace + result[num] = out; + ++num; + // copy non-delimiters + stb_tokentable[0] = 1; + last_nonwhite = out-1; + if (stripwhite == 2) { + while (!stb_tokentable[*s]) { + if (*s != '"') { + if (!isspace(*s)) last_nonwhite = out; + *out++ = *s++; + } else { + ++s; + if (*s == '"') { + if (!isspace(*s)) last_nonwhite = out; + *out++ = *s++; // "" -> ", not start string + } else { + // begin a quoted string + while (*s) { + if (s[0] == '"') { + if (s[1] == '"') { *out++ = *s; s += 2; } + else { ++s; break; } // terminating " + } else + *out++ = *s++; + } + last_nonwhite = out-1; // all in quotes counts as non-white + } + } + } + } else { + while (nested || !stb_tokentable[*s]) { + if (!isspace(*s)) last_nonwhite = out; + if (stable[*s]) { + if (!*s) break; + if (end ? etable[*s] : nested) + --nested; + else + ++nested; + } + *out++ = *s++; + } + } + + if (stripwhite) // rewind to last non-whitespace char + out = last_nonwhite+1; + *out++ = '\0'; + + if (*s) ++s; // skip delimiter + } + s = (unsigned char *) delimit; while (*s) stb_tokentable[*s++] = 0; + if (start) { + s = (unsigned char *) start; while (*s) stable[*s++] = 1; + s = (unsigned char *) end; if (s) while (*s) stable[*s++] = 1; + s = (unsigned char *) end; if (s) while (*s) etable[*s++] = 1; + } + if (count != NULL) *count = num; + result[num] = 0; + return result; +} + +char **stb_tokens(char *src, char *delimit, int *count) +{ + return stb_tokens_raw(src,delimit,count,0,0,0,0); +} + +char **stb_tokens_nested(char *src, char *delimit, int *count, char *nest_in, char *nest_out) +{ + return stb_tokens_raw(src,delimit,count,0,0,nest_in,nest_out); +} + +char **stb_tokens_nested_empty(char *src, char *delimit, int *count, char *nest_in, char *nest_out) +{ + return stb_tokens_raw(src,delimit,count,0,1,nest_in,nest_out); +} + +char **stb_tokens_allowempty(char *src, char *delimit, int *count) +{ + return stb_tokens_raw(src,delimit,count,0,1,0,0); +} + +char **stb_tokens_stripwhite(char *src, char *delimit, int *count) +{ + return stb_tokens_raw(src,delimit,count,1,1,0,0); +} + +char **stb_tokens_quoted(char *src, char *delimit, int *count) +{ + return stb_tokens_raw(src,delimit,count,2,1,0,0); +} + +char *stb_dupreplace(char *src, char *find, char *replace) +{ + size_t len_find = strlen(find); + size_t len_replace = strlen(replace); + int count = 0; + + char *s,*p,*q; + + s = strstr(src, find); + if (s == NULL) return strdup(src); + do { + ++count; + s = strstr(s + len_find, find); + } while (s != NULL); + + p = (char *) malloc(strlen(src) + count * (len_replace - len_find) + 1); + if (p == NULL) return p; + q = p; + s = src; + for (;;) { + char *t = strstr(s, find); + if (t == NULL) { + strcpy(q,s); + assert(strlen(p) == strlen(src) + count*(len_replace-len_find)); + return p; + } + memcpy(q, s, t-s); + q += t-s; + memcpy(q, replace, len_replace); + q += len_replace; + s = t + len_find; + } +} + +void stb_replaceinplace(char *src, char *find, char *replace) +{ + size_t len_find = strlen(find); + size_t len_replace = strlen(replace); + int delta; + + char *s,*p,*q; + + delta = len_replace - len_find; + assert(delta <= 0); + if (delta > 0) return; + + p = strstr(src, find); + if (p == NULL) return; + + s = q = p; + while (*s) { + memcpy(q, replace, len_replace); + p += len_find; + q += len_replace; + s = strstr(p, find); + if (s == NULL) s = p + strlen(p); + memmove(q, p, s-p); + q += s-p; + p = s; + } + *q = 0; +} + +void stb_fixpath(char *path) +{ + for(; *path; ++path) + if (*path == '\\') + *path = '/'; +} + +void stb__add_section(char *buffer, char *data, int curlen, int newlen) +{ + if (newlen < curlen) { + int z1 = newlen >> 1, z2 = newlen-z1; + memcpy(buffer, data, z1-1); + buffer[z1-1] = '.'; + buffer[z1-0] = '.'; + memcpy(buffer+z1+1, data+curlen-z2+1, z2-1); + } else + memcpy(buffer, data, curlen); +} + +char * stb_shorten_path_readable(char *path, int len) +{ + static char buffer[1024]; + int n = strlen(path),n1,n2,r1,r2; + char *s; + if (n <= len) return path; + if (len > 1024) return path; + s = stb_strrchr2(path, '/', '\\'); + if (s) { + n1 = s - path + 1; + n2 = n - n1; + ++s; + } else { + n1 = 0; + n2 = n; + s = path; + } + // now we need to reduce r1 and r2 so that they fit in len + if (n1 < len>>1) { + r1 = n1; + r2 = len - r1; + } else if (n2 < len >> 1) { + r2 = n2; + r1 = len - r2; + } else { + r1 = n1 * len / n; + r2 = n2 * len / n; + if (r1 < len>>2) r1 = len>>2, r2 = len-r1; + if (r2 < len>>2) r2 = len>>2, r1 = len-r2; + } + assert(r1 <= n1 && r2 <= n2); + if (n1) + stb__add_section(buffer, path, n1, r1); + stb__add_section(buffer+r1, s, n2, r2); + buffer[len] = 0; + return buffer; +} + +static char *stb__splitpath_raw(char *buffer, char *path, int flag) +{ + int len=0,x,y, n = (int) strlen(path), f1,f2; + char *s = stb_strrchr2(path, '/', '\\'); + char *t = strrchr(path, '.'); + if (s && t && t < s) t = NULL; + if (s) ++s; + + if (flag == STB_EXT_NO_PERIOD) + flag |= STB_EXT; + + if (!(flag & (STB_PATH | STB_FILE | STB_EXT))) return NULL; + + f1 = s == NULL ? 0 : s-path; // start of filename + f2 = t == NULL ? n : t-path; // just past end of filename + + if (flag & STB_PATH) { + x = 0; if (f1 == 0 && flag == STB_PATH) len=2; + } else if (flag & STB_FILE) { + x = f1; + } else { + x = f2; + if (flag & STB_EXT_NO_PERIOD) + if (buffer[x] == '.') + ++x; + } + + if (flag & STB_EXT) + y = n; + else if (flag & STB_FILE) + y = f2; + else + y = f1; + + if (buffer == NULL) { + buffer = (char *) malloc(y-x + len + 1); + if (!buffer) return NULL; + } + + if (len) { strcpy(buffer, "./"); return buffer; } + strncpy(buffer, path+x, y-x); + buffer[y-x] = 0; + return buffer; +} + +char *stb_splitpath(char *output, char *src, int flag) +{ + return stb__splitpath_raw(output, src, flag); +} + +char *stb_splitpathdup(char *src, int flag) +{ + return stb__splitpath_raw(NULL, src, flag); +} + +char *stb_replacedir(char *output, char *src, char *dir) +{ + char buffer[4096]; + stb_splitpath(buffer, src, STB_FILE | STB_EXT); + if (dir) + sprintf(output, "%s/%s", dir, buffer); + else + strcpy(output, buffer); + return output; +} + +char *stb_replaceext(char *output, char *src, char *ext) +{ + char buffer[4096]; + stb_splitpath(buffer, src, STB_PATH | STB_FILE); + if (ext) + sprintf(output, "%s.%s", buffer, ext[0] == '.' ? ext+1 : ext); + else + strcpy(output, buffer); + return output; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// stb_arr +// +// An stb_arr is directly useable as a pointer (use the actual type in your +// definition), but when it resizes, it returns a new pointer and you can't +// use the old one, so you have to be careful to copy-in-out as necessary. +// +// Use a NULL pointer as a 0-length array. +// +// float *my_array = NULL, *temp; +// +// // add elements on the end one at a time +// stb_arr_push(my_array, 0.0f); +// stb_arr_push(my_array, 1.0f); +// stb_arr_push(my_array, 2.0f); +// +// assert(my_array[1] == 2.0f); +// +// // add an uninitialized element at the end, then assign it +// *stb_arr_add(my_array) = 3.0f; +// +// // add three uninitialized elements at the end +// temp = stb_arr_addn(my_array,3); +// temp[0] = 4.0f; +// temp[1] = 5.0f; +// temp[2] = 6.0f; +// +// assert(my_array[5] == 5.0f); +// +// // remove the last one +// stb_arr_pop(my_array); +// +// assert(stb_arr_len(my_array) == 6); + + +#ifndef STB_INCLUDE_STB_LIB_H + +// simple functions written on top of other functions +#define stb_arr_empty(a) ( stb_arr_len(a) == 0 ) +#define stb_arr_add(a) ( stb_arr_addn((a),1) ) +#define stb_arr_push(a,v) ( *stb_arr_add(a)=(v) ) + +typedef struct +{ + int len, limit; + unsigned int signature; + unsigned int padding; // make it a multiple of 16 so preserve alignment mod 16 +} stb__arr; + +#define stb_arr_signature 0x51bada7b // ends with 0123 in decimal + +// access the header block stored before the data +#define stb_arrhead(a) /*lint --e(826)*/ (((stb__arr *) (a)) - 1) +#define stb_arrhead2(a) /*lint --e(826)*/ (((stb__arr *) (a)) - 1) + +#ifdef STB_DEBUG +#define stb_arr_check(a) assert(!a || stb_arrhead(a)->signature == stb_arr_signature) +#define stb_arr_check2(a) assert(!a || stb_arrhead2(a)->signature == stb_arr_signature) +#else +#define stb_arr_check(a) ((void) 0) +#define stb_arr_check2(a) ((void) 0) +#endif + +// ARRAY LENGTH + +// get the array length; special case if pointer is NULL +#define stb_arr_len(a) (a ? stb_arrhead(a)->len : 0) +#define stb_arr_len2(a) ((stb__arr *) (a) ? stb_arrhead2(a)->len : 0) +#define stb_arr_lastn(a) (stb_arr_len(a)-1) + +// check whether a given index is valid -- tests 0 <= i < stb_arr_len(a) +#define stb_arr_valid(a,i) (a ? (int) (i) < stb_arrhead(a)->len : 0) + +// change the array length so is is exactly N entries long, creating +// uninitialized entries as needed +#define stb_arr_setlen(a,n) \ + (stb__arr_setlen((void **) &(a), sizeof(a[0]), (n))) + +// change the array length so that N is a valid index (that is, so +// it is at least N entries long), creating uninitialized entries as needed +#define stb_arr_makevalid(a,n) \ + (stb_arr_len(a) < (n)+1 ? stb_arr_setlen(a,(n)+1),(a) : (a)) + +// remove the last element of the array, returning it +#define stb_arr_pop(a) ((stb_arr_check(a), (a))[--stb_arrhead(a)->len]) + +// access the last element in the array +#define stb_arr_last(a) ((stb_arr_check(a), (a))[stb_arr_len(a)-1]) + +// is iterator at end of list? +#define stb_arr_end(a,i) ((i) >= &(a)[stb_arr_len(a)]) + +// (internal) change the allocated length of the array +#define stb_arr__grow(a,n) (stb_arr_check(a), stb_arrhead(a)->len += (n)) + +// add N new uninitialized elements to the end of the array +#define stb_arr__addn(a,n) /*lint --e(826)*/ \ + ((stb_arr_len(a)+(n) > stb_arrcurmax(a)) \ + ? (stb__arr_addlen((void **) &(a),sizeof(*a),(n)),0) \ + : ((stb_arr__grow(a,n), 0))) + +// add N new uninitialized elements to the end of the array, and return +// a pointer to the first new one +#define stb_arr_addn(a,n) (stb_arr__addn((a),n),(a)+stb_arr_len(a)-(n)) + +// add N new uninitialized elements starting at index 'i' +#define stb_arr_insertn(a,i,n) (stb__arr_insertn((void **) &(a), sizeof(*a), i, n)) + +// insert an element at i +#define stb_arr_insert(a,i,v) (stb__arr_insertn((void **) &(a), sizeof(*a), i, 1), ((a)[i] = v)) + +// delete N elements from the middle starting at index 'i' +#define stb_arr_deleten(a,i,n) (stb__arr_deleten((void **) &(a), sizeof(*a), i, n)) + +// delete the i'th element +#define stb_arr_delete(a,i) stb_arr_deleten(a,i,1) + +// delete the i'th element, swapping down from the end +#define stb_arr_fastdelete(a,i) \ + (stb_swap(&a[i], &a[stb_arrhead(a)->len-1], sizeof(*a)), stb_arr_pop(a)) + + +// ARRAY STORAGE + +// get the array maximum storage; special case if NULL +#define stb_arrcurmax(a) (a ? stb_arrhead(a)->limit : 0) +#define stb_arrcurmax2(a) (a ? stb_arrhead2(a)->limit : 0) + +// set the maxlength of the array to n in anticipation of further growth +#define stb_arr_setsize(a,n) (stb_arr_check(a), stb__arr_setsize((void **) &(a),sizeof((a)[0]),n)) + +// make sure maxlength is large enough for at least N new allocations +#define stb_arr_atleast(a,n) (stb_arr_len(a)+(n) > stb_arrcurmax(a) \ + ? stb_arr_setsize((a), (n)) : 0) + +// make a copy of a given array (copies contents via 'memcpy'!) +#define stb_arr_copy(a) stb__arr_copy(a, sizeof((a)[0])) + +// compute the storage needed to store all the elements of the array +#define stb_arr_storage(a) (stb_arr_len(a) * sizeof((a)[0])) + +#define stb_arr_for(v,arr) for((v)=(arr); (v) < (arr)+stb_arr_len(arr); ++(v)) + +// IMPLEMENTATION + +STB_EXTERN void stb_arr_free_(void **p); +STB_EXTERN void *stb__arr_copy_(void *p, int elem_size); +STB_EXTERN void stb__arr_setsize_(void **p, int size, int limit); +STB_EXTERN void stb__arr_setlen_(void **p, int size, int newlen); +STB_EXTERN void stb__arr_addlen_(void **p, int size, int addlen); +STB_EXTERN void stb__arr_deleten_(void **p, int size, int loc, int n); +STB_EXTERN void stb__arr_insertn_(void **p, int size, int loc, int n); + +#define stb_arr_free(p) stb_arr_free_((void **) &(p)) + +#ifndef STBLIB_MALLOC_WRAPPER // @Todo + #define stb__arr_setsize stb__arr_setsize_ + #define stb__arr_setlen stb__arr_setlen_ + #define stb__arr_addlen stb__arr_addlen_ + #define stb__arr_deleten stb__arr_deleten_ + #define stb__arr_insertn stb__arr_insertn_ + #define stb__arr_copy stb__arr_copy_ +#else + #define stb__arr_addlen(p,s,n) stb__arr_addlen_(p,s,n,__FILE__,__LINE__) + #define stb__arr_setlen(p,s,n) stb__arr_setlen_(p,s,n,__FILE__,__LINE__) + #define stb__arr_setsize(p,s,n) stb__arr_setsize_(p,s,n,__FILE__,__LINE__) + #define stb__arr_deleten(p,s,i,n) stb__arr_deleten_(p,s,i,n,__FILE__,__LINE__) + #define stb__arr_insertn(p,s,i,n) stb__arr_insertn_(p,s,i,n,__FILE__,__LINE__) + #define stb__arr_copy(p,s) stb__arr_copy_(p,s,__FILE__,__LINE__) +#endif +#endif // STB_INCLUDE_STB_LIB_H + +#ifdef STB_LIB_IMPLEMENTATION +void stb_arr_malloc(void **target, void *context) +{ + stb__arr *q = (stb__arr *) malloc(sizeof(*q)); + q->len = q->limit = 0; + q->signature = stb_arr_signature; + *target = (void *) (q+1); +} + +static void * stb__arr_malloc(int size) +{ + return malloc(size); +} + +void * stb__arr_copy_(void *p, int elem_size) +{ + stb__arr *q; + if (p == NULL) return p; + q = (stb__arr *) malloc(sizeof(*q) + elem_size * stb_arrhead2(p)->limit); + stb_arr_check2(p); + memcpy(q, stb_arrhead2(p), sizeof(*q) + elem_size * stb_arrhead2(p)->len); + return q+1; +} + +void stb_arr_free_(void **pp) +{ + void *p = *pp; + stb_arr_check2(p); + if (p) { + stb__arr *q = stb_arrhead2(p); + free(q); + } + *pp = NULL; +} + +static void stb__arrsize_(void **pp, int size, int limit, int len) +{ + void *p = *pp; + stb__arr *a; + stb_arr_check2(p); + if (p == NULL) { + if (len == 0 && size == 0) return; + a = (stb__arr *) stb__arr_malloc(sizeof(*a) + size*limit); + a->limit = limit; + a->len = len; + a->signature = stb_arr_signature; + } else { + a = stb_arrhead2(p); + a->len = len; + if (a->limit < limit) { + void *p; + if (a->limit >= 4 && limit < a->limit * 2) + limit = a->limit * 2; + p = realloc(a, sizeof(*a) + limit*size); + if (p) { + a = (stb__arr *) p; + a->limit = limit; + } else { + // throw an error! + } + } + } + a->len = a->len < a->limit ? a->len : a->limit; + *pp = a+1; +} + +void stb__arr_setsize_(void **pp, int size, int limit) +{ + void *p = *pp; + stb_arr_check2(p); + stb__arrsize_(pp, size, limit, stb_arr_len2(p)); +} + +void stb__arr_setlen_(void **pp, int size, int newlen) +{ + void *p = *pp; + stb_arr_check2(p); + if (stb_arrcurmax2(p) < newlen || p == NULL) { + stb__arrsize_(pp, size, newlen, newlen); + } else { + stb_arrhead2(p)->len = newlen; + } +} + +void stb__arr_addlen_(void **p, int size, int addlen) +{ + stb__arr_setlen_(p, size, stb_arr_len2(*p) + addlen); +} + +void stb__arr_insertn_(void **pp, int size, int i, int n) +{ + void *p = *pp; + if (n) { + int z; + + if (p == NULL) { + stb__arr_addlen_(pp, size, n); + return; + } + + z = stb_arr_len2(p); + stb__arr_addlen_(&p, size, n); + memmove((char *) p + (i+n)*size, (char *) p + i*size, size * (z-i)); + } + *pp = p; +} + +void stb__arr_deleten_(void **pp, int size, int i, int n) +{ + void *p = *pp; + if (n) { + memmove((char *) p + i*size, (char *) p + (i+n)*size, size * (stb_arr_len2(p)-(i+n))); + stb_arrhead2(p)->len -= n; + } + *pp = p; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Hashing +// +// typical use for this is to make a power-of-two hash table. +// +// let N = size of table (2^n) +// let H = stb_hash(str) +// let S = stb_rehash(H) | 1 +// +// then hash probe sequence P(i) for i=0..N-1 +// P(i) = (H + S*i) & (N-1) +// +// the idea is that H has 32 bits of hash information, but the +// table has only, say, 2^20 entries so only uses 20 of the bits. +// then by rehashing the original H we get 2^12 different probe +// sequences for a given initial probe location. (So it's optimal +// for 64K tables and its optimality decreases past that.) +// +// ok, so I've added something that generates _two separate_ +// 32-bit hashes simultaneously which should scale better to +// very large tables. + +#ifndef STB_INCLUDE_STB_LIB_H +STB_EXTERN unsigned int stb_hash(char *str); +STB_EXTERN unsigned int stb_hashptr(void *p); +STB_EXTERN unsigned int stb_hashlen(char *str, int len); +STB_EXTERN unsigned int stb_rehash_improved(unsigned int v); +STB_EXTERN unsigned int stb_hash_fast(void *p, int len); +STB_EXTERN unsigned int stb_hash2(char *str, unsigned int *hash2_ptr); +STB_EXTERN unsigned int stb_hash_number(unsigned int hash); + +#define stb_rehash(x) ((x) + ((x) >> 6) + ((x) >> 19)) +#endif // STB_INCLUDE_STB_LIB_H + +#ifdef STB_LIB_IMPLEMENTATION +unsigned int stb_hash(char *str) +{ + unsigned int hash = 0; + while (*str) + hash = (hash << 7) + (hash >> 25) + *str++; + return hash + (hash >> 16); +} + +unsigned int stb_hashlen(char *str, int len) +{ + unsigned int hash = 0; + while (len-- > 0 && *str) + hash = (hash << 7) + (hash >> 25) + *str++; + return hash + (hash >> 16); +} + +unsigned int stb_hashptr(void *p) +{ + unsigned int x = (unsigned int)(size_t) p; + + // typically lacking in low bits and high bits + x = stb_rehash(x); + x += x << 16; + + // pearson's shuffle + x ^= x << 3; + x += x >> 5; + x ^= x << 2; + x += x >> 15; + x ^= x << 10; + return stb_rehash(x); +} + +unsigned int stb_rehash_improved(unsigned int v) +{ + return stb_hashptr((void *)(size_t) v); +} + +unsigned int stb_hash2(char *str, unsigned int *hash2_ptr) +{ + unsigned int hash1 = 0x3141592c; + unsigned int hash2 = 0x77f044ed; + while (*str) { + hash1 = (hash1 << 7) + (hash1 >> 25) + *str; + hash2 = (hash2 << 11) + (hash2 >> 21) + *str; + ++str; + } + *hash2_ptr = hash2 + (hash1 >> 16); + return hash1 + (hash2 >> 16); +} + +// Paul Hsieh hash +#define stb__get16_slow(p) ((p)[0] + ((p)[1] << 8)) +#if defined(_MSC_VER) + #define stb__get16(p) (*((unsigned short *) (p))) +#else + #define stb__get16(p) stb__get16_slow(p) +#endif + +unsigned int stb_hash_fast(void *p, int len) +{ + unsigned char *q = (unsigned char *) p; + unsigned int hash = len; + + if (len <= 0 || q == NULL) return 0; + + /* Main loop */ + if (((int)(size_t) q & 1) == 0) { + for (;len > 3; len -= 4) { + unsigned int val; + hash += stb__get16(q); + val = (stb__get16(q+2) << 11); + hash = (hash << 16) ^ hash ^ val; + q += 4; + hash += hash >> 11; + } + } else { + for (;len > 3; len -= 4) { + unsigned int val; + hash += stb__get16_slow(q); + val = (stb__get16_slow(q+2) << 11); + hash = (hash << 16) ^ hash ^ val; + q += 4; + hash += hash >> 11; + } + } + + /* Handle end cases */ + switch (len) { + case 3: hash += stb__get16_slow(q); + hash ^= hash << 16; + hash ^= q[2] << 18; + hash += hash >> 11; + break; + case 2: hash += stb__get16_slow(q); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: hash += q[0]; + hash ^= hash << 10; + hash += hash >> 1; + break; + case 0: break; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; +} + +unsigned int stb_hash_number(unsigned int hash) +{ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Instantiated data structures +// +// This is an attempt to implement a templated data structure. +// +// Hash table: call stb_define_hash(TYPE,N,KEY,K1,K2,HASH,VALUE) +// TYPE -- will define a structure type containing the hash table +// N -- the name, will prefix functions named: +// N create +// N destroy +// N get +// N set, N add, N update, +// N remove +// KEY -- the type of the key. 'x == y' must be valid +// K1,K2 -- keys never used by the app, used as flags in the hashtable +// HASH -- a piece of code ending with 'return' that hashes key 'k' +// VALUE -- the type of the value. 'x = y' must be valid +// +// Note that stb_define_hash_base can be used to define more sophisticated +// hash tables, e.g. those that make copies of the key or use special +// comparisons (e.g. strcmp). + +#define STB_(prefix,name) stb__##prefix##name +#define STB__(prefix,name) prefix##name +#define STB__use(x) x +#define STB__skip(x) + +#define stb_declare_hash(PREFIX,TYPE,N,KEY,VALUE) \ + typedef struct stb__st_##TYPE TYPE;\ + PREFIX int STB__(N, init)(TYPE *h, int count);\ + PREFIX int STB__(N, memory_usage)(TYPE *h);\ + PREFIX TYPE * STB__(N, create)(void);\ + PREFIX TYPE * STB__(N, copy)(TYPE *h);\ + PREFIX void STB__(N, destroy)(TYPE *h);\ + PREFIX int STB__(N,get_flag)(TYPE *a, KEY k, VALUE *v);\ + PREFIX VALUE STB__(N,get)(TYPE *a, KEY k);\ + PREFIX int STB__(N, set)(TYPE *a, KEY k, VALUE v);\ + PREFIX int STB__(N, add)(TYPE *a, KEY k, VALUE v);\ + PREFIX int STB__(N, update)(TYPE*a,KEY k,VALUE v);\ + PREFIX int STB__(N, remove)(TYPE *a, KEY k, VALUE *v); + +#define STB_nocopy(x) (x) +#define STB_nodelete(x) 0 +#define STB_nofields +#define STB_nonullvalue(x) +#define STB_nullvalue(x) x +#define STB_safecompare(x) x +#define STB_nosafe(x) +#define STB_noprefix + +#ifdef __GNUC__ +#define STB__nogcc(x) +#else +#define STB__nogcc(x) x +#endif + +#define stb_define_hash_base(PREFIX,TYPE,FIELDS,N,NC,LOAD_FACTOR, \ + KEY,EMPTY,DEL,COPY,DISPOSE,SAFE, \ + VCOMPARE,CCOMPARE,HASH, \ + VALUE,HASVNULL,VNULL) \ + \ +typedef struct \ +{ \ + KEY k; \ + VALUE v; \ +} STB_(N,_hashpair); \ + \ +STB__nogcc( typedef struct stb__st_##TYPE TYPE; ) \ +struct stb__st_##TYPE { \ + FIELDS \ + STB_(N,_hashpair) *table; \ + unsigned int mask; \ + int count, limit; \ + int deleted; \ + \ + int delete_threshhold; \ + int grow_threshhold; \ + int shrink_threshhold; \ + unsigned char alloced, has_empty, has_del; \ + VALUE ev; VALUE dv; \ +}; \ + \ +static unsigned int STB_(N, hash)(KEY k) \ +{ \ + HASH \ +} \ + \ +PREFIX int STB__(N, init)(TYPE *h, int count) \ +{ \ + int i; \ + if (count < 4) count = 4; \ + h->limit = count; \ + h->count = 0; \ + h->mask = count-1; \ + h->deleted = 0; \ + h->grow_threshhold = (int) (count * LOAD_FACTOR); \ + h->has_empty = h->has_del = 0; \ + h->alloced = 0; \ + if (count <= 64) \ + h->shrink_threshhold = 0; \ + else \ + h->shrink_threshhold = (int) (count * (LOAD_FACTOR/2.25)); \ + h->delete_threshhold = (int) (count * (1-LOAD_FACTOR)/2); \ + h->table = (STB_(N,_hashpair)*) malloc(sizeof(h->table[0]) * count); \ + if (h->table == NULL) return 0; \ + /* ideally this gets turned into a memset32 automatically */ \ + for (i=0; i < count; ++i) \ + h->table[i].k = EMPTY; \ + return 1; \ +} \ + \ +PREFIX int STB__(N, memory_usage)(TYPE *h) \ +{ \ + return sizeof(*h) + h->limit * sizeof(h->table[0]); \ +} \ + \ +PREFIX TYPE * STB__(N, create)(void) \ +{ \ + TYPE *h = (TYPE *) malloc(sizeof(*h)); \ + if (h) { \ + if (STB__(N, init)(h, 16)) \ + h->alloced = 1; \ + else { free(h); h=NULL; } \ + } \ + return h; \ +} \ + \ +PREFIX void STB__(N, destroy)(TYPE *a) \ +{ \ + int i; \ + for (i=0; i < a->limit; ++i) \ + if (!CCOMPARE(a->table[i].k,EMPTY) && !CCOMPARE(a->table[i].k, DEL)) \ + DISPOSE(a->table[i].k); \ + free(a->table); \ + if (a->alloced) \ + free(a); \ +} \ + \ +static void STB_(N, rehash)(TYPE *a, int count); \ + \ +PREFIX int STB__(N,get_flag)(TYPE *a, KEY k, VALUE *v) \ +{ \ + unsigned int h = STB_(N, hash)(k); \ + unsigned int n = h & a->mask, s; \ + if (CCOMPARE(k,EMPTY)){ if (a->has_empty) *v = a->ev; return a->has_empty;}\ + if (CCOMPARE(k,DEL)) { if (a->has_del ) *v = a->dv; return a->has_del; }\ + if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \ + SAFE(if (!CCOMPARE(a->table[n].k,DEL))) \ + if (VCOMPARE(a->table[n].k,k)) { *v = a->table[n].v; return 1; } \ + s = stb_rehash(h) | 1; \ + for(;;) { \ + n = (n + s) & a->mask; \ + if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \ + SAFE(if (CCOMPARE(a->table[n].k,DEL)) continue;) \ + if (VCOMPARE(a->table[n].k,k)) \ + { *v = a->table[n].v; return 1; } \ + } \ +} \ + \ +HASVNULL( \ + PREFIX VALUE STB__(N,get)(TYPE *a, KEY k) \ + { \ + VALUE v; \ + if (STB__(N,get_flag)(a,k,&v)) return v; \ + else return VNULL; \ + } \ +) \ + \ +PREFIX int STB__(N,getkey)(TYPE *a, KEY k, KEY *kout) \ +{ \ + unsigned int h = STB_(N, hash)(k); \ + unsigned int n = h & a->mask, s; \ + if (CCOMPARE(k,EMPTY)||CCOMPARE(k,DEL)) return 0; \ + if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \ + SAFE(if (!CCOMPARE(a->table[n].k,DEL))) \ + if (VCOMPARE(a->table[n].k,k)) { *kout = a->table[n].k; return 1; } \ + s = stb_rehash(h) | 1; \ + for(;;) { \ + n = (n + s) & a->mask; \ + if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \ + SAFE(if (CCOMPARE(a->table[n].k,DEL)) continue;) \ + if (VCOMPARE(a->table[n].k,k)) \ + { *kout = a->table[n].k; return 1; } \ + } \ +} \ + \ +static int STB_(N,addset)(TYPE *a, KEY k, VALUE v, \ + int allow_new, int allow_old, int copy) \ +{ \ + unsigned int h = STB_(N, hash)(k); \ + unsigned int n = h & a->mask; \ + int b = -1; \ + if (CCOMPARE(k,EMPTY)) { \ + if (a->has_empty ? allow_old : allow_new) { \ + n=a->has_empty; a->ev = v; a->has_empty = 1; return !n; \ + } else return 0; \ + } \ + if (CCOMPARE(k,DEL)) { \ + if (a->has_del ? allow_old : allow_new) { \ + n=a->has_del; a->dv = v; a->has_del = 1; return !n; \ + } else return 0; \ + } \ + if (!CCOMPARE(a->table[n].k, EMPTY)) { \ + unsigned int s; \ + if (CCOMPARE(a->table[n].k, DEL)) \ + b = n; \ + else if (VCOMPARE(a->table[n].k,k)) { \ + if (allow_old) \ + a->table[n].v = v; \ + return !allow_new; \ + } \ + s = stb_rehash(h) | 1; \ + for(;;) { \ + n = (n + s) & a->mask; \ + if (CCOMPARE(a->table[n].k, EMPTY)) break; \ + if (CCOMPARE(a->table[n].k, DEL)) { \ + if (b < 0) b = n; \ + } else if (VCOMPARE(a->table[n].k,k)) { \ + if (allow_old) \ + a->table[n].v = v; \ + return !allow_new; \ + } \ + } \ + } \ + if (!allow_new) return 0; \ + if (b < 0) b = n; else --a->deleted; \ + a->table[b].k = copy ? COPY(k) : k; \ + a->table[b].v = v; \ + ++a->count; \ + if (a->count > a->grow_threshhold) \ + STB_(N,rehash)(a, a->limit*2); \ + return 1; \ +} \ + \ +PREFIX int STB__(N, set)(TYPE *a, KEY k, VALUE v){return STB_(N,addset)(a,k,v,1,1,1);}\ +PREFIX int STB__(N, add)(TYPE *a, KEY k, VALUE v){return STB_(N,addset)(a,k,v,1,0,1);}\ +PREFIX int STB__(N, update)(TYPE*a,KEY k,VALUE v){return STB_(N,addset)(a,k,v,0,1,1);}\ + \ +PREFIX int STB__(N, remove)(TYPE *a, KEY k, VALUE *v) \ +{ \ + unsigned int h = STB_(N, hash)(k); \ + unsigned int n = h & a->mask, s; \ + if (CCOMPARE(k,EMPTY)) { if (a->has_empty) { if(v)*v = a->ev; a->has_empty=0; return 1; } return 0; } \ + if (CCOMPARE(k,DEL)) { if (a->has_del ) { if(v)*v = a->dv; a->has_del =0; return 1; } return 0; } \ + if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \ + if (SAFE(CCOMPARE(a->table[n].k,DEL) || ) !VCOMPARE(a->table[n].k,k)) { \ + s = stb_rehash(h) | 1; \ + for(;;) { \ + n = (n + s) & a->mask; \ + if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \ + SAFE(if (CCOMPARE(a->table[n].k, DEL)) continue;) \ + if (VCOMPARE(a->table[n].k,k)) break; \ + } \ + } \ + DISPOSE(a->table[n].k); \ + a->table[n].k = DEL; \ + --a->count; \ + ++a->deleted; \ + if (v != NULL) \ + *v = a->table[n].v; \ + if (a->count < a->shrink_threshhold) \ + STB_(N, rehash)(a, a->limit >> 1); \ + else if (a->deleted > a->delete_threshhold) \ + STB_(N, rehash)(a, a->limit); \ + return 1; \ +} \ + \ +PREFIX TYPE * STB__(NC, copy)(TYPE *a) \ +{ \ + int i; \ + TYPE *h = (TYPE *) malloc(sizeof(*h)); \ + if (!h) return NULL; \ + if (!STB__(N, init)(h, a->limit)) { free(h); return NULL; } \ + h->count = a->count; \ + h->deleted = a->deleted; \ + h->alloced = 1; \ + h->ev = a->ev; h->dv = a->dv; \ + h->has_empty = a->has_empty; h->has_del = a->has_del; \ + memcpy(h->table, a->table, h->limit * sizeof(h->table[0])); \ + for (i=0; i < a->limit; ++i) \ + if (!CCOMPARE(h->table[i].k,EMPTY) && !CCOMPARE(h->table[i].k,DEL)) \ + h->table[i].k = COPY(h->table[i].k); \ + return h; \ +} \ + \ +static void STB_(N, rehash)(TYPE *a, int count) \ +{ \ + int i; \ + TYPE b; \ + STB__(N, init)(&b, count); \ + for (i=0; i < a->limit; ++i) \ + if (!CCOMPARE(a->table[i].k,EMPTY) && !CCOMPARE(a->table[i].k,DEL)) \ + STB_(N,addset)(&b, a->table[i].k, a->table[i].v,1,1,0); \ + free(a->table); \ + a->table = b.table; \ + a->mask = b.mask; \ + a->count = b.count; \ + a->limit = b.limit; \ + a->deleted = b.deleted; \ + a->delete_threshhold = b.delete_threshhold; \ + a->grow_threshhold = b.grow_threshhold; \ + a->shrink_threshhold = b.shrink_threshhold; \ +} + +#define STB_equal(a,b) ((a) == (b)) + +#define stb_define_hash(TYPE,N,KEY,EMPTY,DEL,HASH,VALUE) \ + stb_define_hash_base(STB_noprefix, TYPE,STB_nofields,N,NC,0.85f, \ + KEY,EMPTY,DEL,STB_nocopy,STB_nodelete,STB_nosafe, \ + STB_equal,STB_equal,HASH, \ + VALUE,STB_nonullvalue,0) + +#define stb_define_hash_vnull(TYPE,N,KEY,EMPTY,DEL,HASH,VALUE,VNULL) \ + stb_define_hash_base(STB_noprefix, TYPE,STB_nofields,N,NC,0.85f, \ + KEY,EMPTY,DEL,STB_nocopy,STB_nodelete,STB_nosafe, \ + STB_equal,STB_equal,HASH, \ + VALUE,STB_nullvalue,VNULL) + +////////////////////////////////////////////////////////////////////////////// +// +// stb_ptrmap +// +// An stb_ptrmap data structure is an O(1) hash table between pointers. One +// application is to let you store "extra" data associated with pointers, +// which is why it was originally called stb_extra. + +#ifndef STB_INCLUDE_STB_LIB_H +stb_declare_hash(STB_EXTERN, stb_ptrmap, stb_ptrmap_, void *, void *) +stb_declare_hash(STB_EXTERN, stb_idict, stb_idict_, stb_int32, stb_int32) + +STB_EXTERN void stb_ptrmap_delete(stb_ptrmap *e, void (*free_func)(void *)); +STB_EXTERN stb_ptrmap *stb_ptrmap_new(void); + +STB_EXTERN stb_idict * stb_idict_new_size(unsigned int size); +STB_EXTERN void stb_idict_remove_all(stb_idict *e); +#endif // STB_INCLUDE_STB_LIB_H + +#ifdef STB_LIB_IMPLEMENTATION + +#define STB_EMPTY ((void *) 2) +#define STB_EDEL ((void *) 6) + +stb_define_hash_base(STB_noprefix,stb_ptrmap, STB_nofields, stb_ptrmap_,stb_ptrmap_,0.85f, + void *,STB_EMPTY,STB_EDEL,STB_nocopy,STB_nodelete,STB_nosafe, + STB_equal,STB_equal,return stb_hashptr(k);, + void *,STB_nullvalue,NULL) + +stb_ptrmap *stb_ptrmap_new(void) +{ + return stb_ptrmap_create(); +} + +void stb_ptrmap_delete(stb_ptrmap *e, void (*free_func)(void *)) +{ + int i; + if (free_func) + for (i=0; i < e->limit; ++i) + if (e->table[i].k != STB_EMPTY && e->table[i].k != STB_EDEL) { + if (free_func == free) + free(e->table[i].v); // allow STB_MALLOC_WRAPPER to operate + else + free_func(e->table[i].v); + } + stb_ptrmap_destroy(e); +} + +// extra fields needed for stua_dict +#define STB_IEMPTY ((int) 1) +#define STB_IDEL ((int) 3) +stb_define_hash_base(STB_noprefix, stb_idict, STB_nofields, stb_idict_,stb_idict_,0.85f, + stb_int32,STB_IEMPTY,STB_IDEL,STB_nocopy,STB_nodelete,STB_nosafe, + STB_equal,STB_equal, + return stb_rehash_improved(k);,stb_int32,STB_nonullvalue,0) + +stb_idict * stb_idict_new_size(unsigned int size) +{ + stb_idict *e = (stb_idict *) malloc(sizeof(*e)); + if (e) { + // round up to power of 2 + while ((size & (size-1)) != 0) // while more than 1 bit is set + size += (size & ~(size-1)); // add the lowest set bit + stb_idict_init(e, size); + e->alloced = 1; + } + return e; +} + +void stb_idict_remove_all(stb_idict *e) +{ + int n; + for (n=0; n < e->limit; ++n) + e->table[n].k = STB_IEMPTY; + e->has_empty = e->has_del = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// SDICT: Hash Table for Strings (symbol table) +// +// if "use_arena=1", then strings will be copied +// into blocks and never freed until the sdict is freed; +// otherwise they're malloc()ed and free()d on the fly. +// (specify use_arena=1 if you never stb_sdict_remove) + +#ifndef STB_INCLUDE_STB_LIB_H +stb_declare_hash(STB_EXTERN, stb_sdict, stb_sdict_, char *, void *) + +STB_EXTERN stb_sdict * stb_sdict_new(void); +STB_EXTERN stb_sdict * stb_sdict_copy(stb_sdict*); +STB_EXTERN void stb_sdict_delete(stb_sdict *); +STB_EXTERN void * stb_sdict_change(stb_sdict *, char *str, void *p); +STB_EXTERN int stb_sdict_count(stb_sdict *d); + +STB_EXTERN int stb_sdict_internal_limit(stb_sdict *d); +STB_EXTERN char * stb_sdict_internal_key(stb_sdict *d, int n); +STB_EXTERN void * stb_sdict_internal_value(stb_sdict *d, int n); + +#define stb_sdict_for(d,i,q,z) \ + for(i=0; i < stb_sdict_internal_limit(d) ? (q=stb_sdict_internal_key(d,i),z=stb_sdict_internal_value(d,i),1) : 0; ++i) \ + if (q==NULL||q==(void *) 1);else // reversed makes macro friendly +#endif // STB_INCLUDE_STB_LIB_H + +#ifdef STB_LIB_IMPLEMENTATION + +// if in same translation unit, for speed, don't call accessors +#undef stb_sdict_for +#define stb_sdict_for(d,i,q,z) \ + for(i=0; i < (d)->limit ? (q=(d)->table[i].k,z=(d)->table[i].v,1) : 0; ++i) \ + if (q==NULL||q==(void *) 1);else // reversed makes macro friendly + +//#define STB_DEL ((void *) 1) +#define STB_SDEL ((char *) 1) + +stb_define_hash_base(STB_noprefix, stb_sdict, STB_nofields, stb_sdict_,stb_sdictinternal_, 0.85f, + char *, NULL, STB_SDEL, strdup, free, + STB_safecompare, !strcmp, STB_equal, return stb_hash(k);, + void *, STB_nullvalue, NULL) + +int stb_sdict_count(stb_sdict *a) +{ + return a->count; +} + +int stb_sdict_internal_limit(stb_sdict *a) +{ + return a->limit; +} +char* stb_sdict_internal_key(stb_sdict *a, int n) +{ + return a->table[n].k; +} +void* stb_sdict_internal_value(stb_sdict *a, int n) +{ + return a->table[n].v; +} + +stb_sdict * stb_sdict_new(void) +{ + stb_sdict *d = stb_sdict_create(); + if (d == NULL) return NULL; + return d; +} + +stb_sdict* stb_sdict_copy(stb_sdict *old) +{ + return stb_sdictinternal_copy(old); +} + +void stb_sdict_delete(stb_sdict *d) +{ + stb_sdict_destroy(d); +} + +void * stb_sdict_change(stb_sdict *d, char *str, void *p) +{ + void *q = stb_sdict_get(d, str); + stb_sdict_set(d, str, p); + return q; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// File Processing +// + +#ifndef STB_INCLUDE_STB_LIB_H +#ifdef _MSC_VER + #define stb_rename(x,y) _wrename((const wchar_t *)stb__from_utf8(x), (const wchar_t *)stb__from_utf8_alt(y)) + #define stb_mktemp _mktemp +#else + #define stb_mktemp mktemp + #define stb_rename rename +#endif + +#define stb_filec (char *) stb_file +#define stb_fileu (unsigned char *) stb_file +STB_EXTERN void * stb_file(char *filename, size_t *length); +STB_EXTERN size_t stb_filelen(FILE *f); +STB_EXTERN int stb_filewrite(char *filename, void *data, size_t length); +STB_EXTERN int stb_filewritestr(char *filename, char *data); +STB_EXTERN char ** stb_stringfile(char *filename, int *len); +STB_EXTERN char * stb_fgets(char *buffer, int buflen, FILE *f); +STB_EXTERN char * stb_fgets_malloc(FILE *f); +STB_EXTERN int stb_fexists(char *filename); +STB_EXTERN int stb_fcmp(char *s1, char *s2); +STB_EXTERN int stb_feq(char *s1, char *s2); +STB_EXTERN time_t stb_ftimestamp(char *filename); +STB_EXTERN int stb_fullpath(char *abs, int abs_size, char *rel); + +STB_EXTERN int stb_copyfile(char *src, char *dest); +STB_EXTERN int stb_fread(void *data, size_t len, size_t count, void *f); +STB_EXTERN int stb_fwrite(void *data, size_t len, size_t count, void *f); +#endif // STB_INCLUDE_STB_LIB_H + +#ifdef STB_LIB_IMPLEMENTATION +#if defined(_MSC_VER) || defined(__MINGW32__) + #define stb__stat _stat +#else + #define stb__stat stat +#endif + +int stb_fexists(char *filename) +{ + struct stb__stat buf; + return stb__windows( + _wstat((const wchar_t *)stb__from_utf8(filename), &buf), + stat(filename,&buf) + ) == 0; +} + +time_t stb_ftimestamp(char *filename) +{ + struct stb__stat buf; + if (stb__windows( + _wstat((const wchar_t *)stb__from_utf8(filename), &buf), + stat(filename,&buf) + ) == 0) + { + return buf.st_mtime; + } else { + return 0; + } +} + +size_t stb_filelen(FILE *f) +{ + size_t len, pos; + pos = ftell(f); + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, pos, SEEK_SET); + return len; +} + +void *stb_file(char *filename, size_t *length) +{ + FILE *f = stb__fopen(filename, "rb"); + char *buffer; + size_t len, len2; + if (!f) return NULL; + len = stb_filelen(f); + buffer = (char *) malloc(len+2); // nul + extra + len2 = fread(buffer, 1, len, f); + if (len2 == len) { + if (length) *length = len; + buffer[len] = 0; + } else { + free(buffer); + buffer = NULL; + } + fclose(f); + return buffer; +} + +int stb_filewrite(char *filename, void *data, size_t length) +{ + FILE *f = stb__fopen(filename, "wb"); + if (f) { + unsigned char *data_ptr = (unsigned char *) data; + size_t remaining = length; + while (remaining > 0) { + size_t len2 = remaining > 65536 ? 65536 : remaining; + size_t len3 = fwrite(data_ptr, 1, len2, f); + if (len2 != len3) { + fprintf(stderr, "Failed while writing %s\n", filename); + break; + } + remaining -= len2; + data_ptr += len2; + } + fclose(f); + } + return f != NULL; +} + +int stb_filewritestr(char *filename, char *data) +{ + return stb_filewrite(filename, data, strlen(data)); +} + +char ** stb_stringfile(char *filename, int *plen) +{ + FILE *f = stb__fopen(filename, "rb"); + char *buffer, **list=NULL, *s; + size_t len, count, i; + + if (!f) return NULL; + len = stb_filelen(f); + buffer = (char *) malloc(len+1); + len = fread(buffer, 1, len, f); + buffer[len] = 0; + fclose(f); + + // two passes through: first time count lines, second time set them + for (i=0; i < 2; ++i) { + s = buffer; + if (i == 1) + list[0] = s; + count = 1; + while (*s) { + if (*s == '\n' || *s == '\r') { + // detect if both cr & lf are together + int crlf = (s[0] + s[1]) == ('\n' + '\r'); + if (i == 1) *s = 0; + if (crlf) ++s; + if (s[1]) { // it's not over yet + if (i == 1) list[count] = s+1; + ++count; + } + } + ++s; + } + if (i == 0) { + list = (char **) malloc(sizeof(*list) * (count+1) + len+1); + if (!list) return NULL; + list[count] = 0; + // recopy the file so there's just a single allocation to free + memcpy(&list[count+1], buffer, len+1); + free(buffer); + buffer = (char *) &list[count+1]; + if (plen) *plen = count; + } + } + return list; +} + +char * stb_fgets(char *buffer, int buflen, FILE *f) +{ + char *p; + buffer[0] = 0; + p = fgets(buffer, buflen, f); + if (p) { + int n = strlen(p)-1; + if (n >= 0) + if (p[n] == '\n') + p[n] = 0; + } + return p; +} + +char * stb_fgets_malloc(FILE *f) +{ + // avoid reallocing for small strings + char quick_buffer[800]; + quick_buffer[sizeof(quick_buffer)-2] = 0; + if (!fgets(quick_buffer, sizeof(quick_buffer), f)) + return NULL; + + if (quick_buffer[sizeof(quick_buffer)-2] == 0) { + int n = strlen(quick_buffer); + if (n > 0 && quick_buffer[n-1] == '\n') + quick_buffer[n-1] = 0; + return strdup(quick_buffer); + } else { + char *p; + char *a = strdup(quick_buffer); + int len = sizeof(quick_buffer)-1; + + while (!feof(f)) { + if (a[len-1] == '\n') break; + a = (char *) realloc(a, len*2); + p = &a[len]; + p[len-2] = 0; + if (!fgets(p, len, f)) + break; + if (p[len-2] == 0) { + len += strlen(p); + break; + } + len = len + (len-1); + } + if (a[len-1] == '\n') + a[len-1] = 0; + return a; + } +} + +int stb_fullpath(char *abs, int abs_size, char *rel) +{ + #ifdef _MSC_VER + return _fullpath(abs, rel, abs_size) != NULL; + #else + if (rel[0] == '/' || rel[0] == '~') { + if ((int) strlen(rel) >= abs_size) + return 0; + strcpy(abs,rel); + return 1; + } else { + int n; + getcwd(abs, abs_size); + n = strlen(abs); + if (n+(int) strlen(rel)+2 <= abs_size) { + abs[n] = '/'; + strcpy(abs+n+1, rel); + return 1; + } else { + return 0; + } + } + #endif +} + +static int stb_fcmp_core(FILE *f, FILE *g) +{ + char buf1[1024],buf2[1024]; + int n1,n2, res=0; + + while (1) { + n1 = fread(buf1, 1, sizeof(buf1), f); + n2 = fread(buf2, 1, sizeof(buf2), g); + res = memcmp(buf1,buf2,n1 < n2 ? n1 : n2); + if (res) + break; + if (n1 != n2) { + res = n1 < n2 ? -1 : 1; + break; + } + if (n1 == 0) + break; + } + + fclose(f); + fclose(g); + return res; +} + +int stb_fcmp(char *s1, char *s2) +{ + FILE *f = stb__fopen(s1, "rb"); + FILE *g = stb__fopen(s2, "rb"); + + if (f == NULL || g == NULL) { + if (f) fclose(f); + if (g) { + fclose(g); + return 1; + } + return f != NULL; + } + + return stb_fcmp_core(f,g); +} + +int stb_feq(char *s1, char *s2) +{ + FILE *f = stb__fopen(s1, "rb"); + FILE *g = stb__fopen(s2, "rb"); + + if (f == NULL || g == NULL) { + if (f) fclose(f); + if (g) fclose(g); + return f == g; + } + + // feq is faster because it shortcuts if they're different length + if (stb_filelen(f) != stb_filelen(g)) { + fclose(f); + fclose(g); + return 0; + } + + return !stb_fcmp_core(f,g); +} + +int stb_copyfile(char *src, char *dest) +{ + char raw_buffer[1024]; + char *buffer; + int buf_size = 65536; + + FILE *f, *g; + + // if file already exists at destination, do nothing + if (stb_feq(src, dest)) return 1; + + // open file + f = stb__fopen(src, "rb"); + if (f == NULL) return 0; + + // open file for writing + g = stb__fopen(dest, "wb"); + if (g == NULL) { + fclose(f); + return 0; + } + + buffer = (char *) malloc(buf_size); + if (buffer == NULL) { + buffer = raw_buffer; + buf_size = sizeof(raw_buffer); + } + + while (!feof(f)) { + int n = fread(buffer, 1, buf_size, f); + if (n != 0) + fwrite(buffer, 1, n, g); + } + + fclose(f); + if (buffer != raw_buffer) + free(buffer); + + fclose(g); + return 1; +} + +#define stb_fgetc(f) ((unsigned char) fgetc(f)) + +#if 0 +// strip the trailing '/' or '\\' from a directory so we can refer to it +// as a file for _stat() +char *stb_strip_final_slash(char *t) +{ + if (t[0]) { + char *z = t + strlen(t) - 1; + // *z is the last character + if (*z == '\\' || *z == '/') + if (z != t+2 || t[1] != ':') // but don't strip it if it's e.g. "c:/" + *z = 0; + if (*z == '\\') + *z = '/'; // canonicalize to make sure it matches db + } + return t; +} + +char *stb_strip_final_slash_regardless(char *t) +{ + if (t[0]) { + char *z = t + strlen(t) - 1; + // *z is the last character + if (*z == '\\' || *z == '/') + *z = 0; + if (*z == '\\') + *z = '/'; // canonicalize to make sure it matches db + } + return t; +} +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Portable directory reading +// + +#ifndef STB_INCLUDE_STB_LIB_H +STB_EXTERN char **stb_readdir_files (char *dir); +STB_EXTERN char **stb_readdir_files_mask(char *dir, char *wild); +STB_EXTERN char **stb_readdir_subdirs(char *dir); +STB_EXTERN char **stb_readdir_subdirs_mask(char *dir, char *wild); +STB_EXTERN void stb_readdir_free (char **files); +STB_EXTERN char **stb_readdir_recursive(char *dir, char *filespec); +STB_EXTERN void stb_delete_directory_recursive(char *dir); + +// forward declare for implementation +STB_EXTERN int stb_wildmatchi(char *expr, char *candidate); +#endif // STB_INCLUDE_STB_LIB_H + + +#ifdef STB_LIB_IMPLEMENTATION + +#ifdef _MSC_VER +#include +#else +#include +#include +#endif + +void stb_readdir_free(char **files) +{ + char **f2 = files; + int i; + for (i=0; i < stb_arr_len(f2); ++i) + free(f2[i]); + stb_arr_free(f2); +} + +static int isdotdirname(char *name) +{ + if (name[0] == '.') + return (name[1] == '.') ? !name[2] : !name[1]; + return 0; +} + +static char **readdir_raw(char *dir, int return_subdirs, char *mask) +{ + char **results = NULL; + char buffer[4096], with_slash[4096]; + size_t n; + + #ifdef _MSC_VER + stb__wchar *ws; + struct _wfinddata_t data; + #ifdef _WIN64 + const intptr_t none = -1; + intptr_t z; + #else + const long none = -1; + long z; + #endif + #else // !_MSC_VER + const DIR *none = NULL; + DIR *z; + #endif + + n = stb_strscpy(buffer,dir,sizeof(buffer)); + if (!n || n >= sizeof(buffer)) + return NULL; + stb_fixpath(buffer); + n--; + + if (n > 0 && (buffer[n-1] != '/')) { + buffer[n++] = '/'; + } + buffer[n] = 0; + if (!stb_strscpy(with_slash,buffer,sizeof(with_slash))) + return NULL; + + #ifdef _MSC_VER + if (!stb_strscpy(buffer+n,"*.*",sizeof(buffer)-n)) + return NULL; + ws = stb__from_utf8(buffer); + z = _wfindfirst((const wchar_t *)ws, &data); + #else + z = opendir(dir); + #endif + + if (z != none) { + int nonempty = 1; + #ifndef _MSC_VER + struct dirent *data = readdir(z); + nonempty = (data != NULL); + #endif + + if (nonempty) { + + do { + int is_subdir; + #ifdef _MSC_VER + char *name = stb__to_utf8((stb__wchar *)data.name); + if (name == NULL) { + fprintf(stderr, "%s to convert '%S' to %s!\n", "Unable", data.name, "utf8"); + continue; + } + is_subdir = !!(data.attrib & _A_SUBDIR); + #else + char *name = data->d_name; + if (!stb_strscpy(buffer+n,name,sizeof(buffer)-n)) + break; + // Could follow DT_LNK, but would need to check for recursive links. + is_subdir = !!(data->d_type & DT_DIR); + #endif + + if (is_subdir == return_subdirs) { + if (!is_subdir || !isdotdirname(name)) { + if (!mask || stb_wildmatchi(mask, name)) { + char buffer[4096],*p=buffer; + if ( stb_snprintf(buffer, sizeof(buffer), "%s%s", with_slash, name) < 0 ) + break; + if (buffer[0] == '.' && buffer[1] == '/') + p = buffer+2; + stb_arr_push(results, strdup(p)); + } + } + } + } + #ifdef _MSC_VER + while (0 == _wfindnext(z, &data)); + #else + while ((data = readdir(z)) != NULL); + #endif + } + #ifdef _MSC_VER + _findclose(z); + #else + closedir(z); + #endif + } + return results; +} + +char **stb_readdir_files (char *dir) { return readdir_raw(dir, 0, NULL); } +char **stb_readdir_subdirs(char *dir) { return readdir_raw(dir, 1, NULL); } +char **stb_readdir_files_mask(char *dir, char *wild) { return readdir_raw(dir, 0, wild); } +char **stb_readdir_subdirs_mask(char *dir, char *wild) { return readdir_raw(dir, 1, wild); } + +int stb__rec_max=0x7fffffff; +static char **stb_readdir_rec(char **sofar, char *dir, char *filespec) +{ + char **files; + char ** dirs; + char **p; + + if (stb_arr_len(sofar) >= stb__rec_max) return sofar; + + files = stb_readdir_files_mask(dir, filespec); + stb_arr_for(p, files) { + stb_arr_push(sofar, strdup(*p)); + if (stb_arr_len(sofar) >= stb__rec_max) break; + } + stb_readdir_free(files); + if (stb_arr_len(sofar) >= stb__rec_max) return sofar; + + dirs = stb_readdir_subdirs(dir); + stb_arr_for(p, dirs) + sofar = stb_readdir_rec(sofar, *p, filespec); + stb_readdir_free(dirs); + return sofar; +} + +char **stb_readdir_recursive(char *dir, char *filespec) +{ + return stb_readdir_rec(NULL, dir, filespec); +} + +void stb_delete_directory_recursive(char *dir) +{ + char **list = stb_readdir_subdirs(dir); + int i; + for (i=0; i < stb_arr_len(list); ++i) + stb_delete_directory_recursive(list[i]); + stb_arr_free(list); + list = stb_readdir_files(dir); + for (i=0; i < stb_arr_len(list); ++i) + if (!remove(list[i])) { + // on windows, try again after making it writeable; don't ALWAYS + // do this first since that would be slow in the normal case + #ifdef _MSC_VER + _chmod(list[i], _S_IWRITE); + remove(list[i]); + #endif + } + stb_arr_free(list); + stb__windows(_rmdir,rmdir)(dir); +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Checksums: CRC-32, ADLER32, SHA-1 +// +// CRC-32 and ADLER32 allow streaming blocks +// SHA-1 requires either a complete buffer, max size 2^32 - 73 +// or it can checksum directly from a file, max 2^61 + +#ifndef STB_INCLUDE_STB_LIB_H +#define STB_ADLER32_SEED 1 +#define STB_CRC32_SEED 0 // note that we logical NOT this in the code + +STB_EXTERN stb_uint stb_adler32 (stb_uint adler32, stb_uchar *buffer, stb_uint buflen); +STB_EXTERN stb_uint stb_crc32_block(stb_uint crc32 , stb_uchar *buffer, stb_uint buflen); +STB_EXTERN stb_uint stb_crc32 ( stb_uchar *buffer, stb_uint buflen); + +STB_EXTERN void stb_sha1( unsigned char output[20], stb_uchar *buffer, unsigned int len); +STB_EXTERN int stb_sha1_file(unsigned char output[20], char *file); +#endif // STB_INCLUDE_STB_LIB_H + +#ifdef STB_LIB_IMPLEMENTATION +stb_uint stb_crc32_block(stb_uint crc, unsigned char *buffer, stb_uint len) +{ + static stb_uint crc_table[256]; + stb_uint i,j,s; + crc = ~crc; + + if (crc_table[1] == 0) + for(i=0; i < 256; i++) { + for (s=i, j=0; j < 8; ++j) + s = (s >> 1) ^ (s & 1 ? 0xedb88320 : 0); + crc_table[i] = s; + } + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +} + +stb_uint stb_crc32(unsigned char *buffer, stb_uint len) +{ + return stb_crc32_block(0, buffer, len); +} + +stb_uint stb_adler32(stb_uint adler32, stb_uchar *buffer, stb_uint buflen) +{ + const unsigned long ADLER_MOD = 65521; + unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16; + unsigned long blocklen, i; + + blocklen = buflen % 5552; + while (buflen) { + for (i=0; i + 7 < blocklen; i += 8) { + s1 += buffer[0], s2 += s1; + s1 += buffer[1], s2 += s1; + s1 += buffer[2], s2 += s1; + s1 += buffer[3], s2 += s1; + s1 += buffer[4], s2 += s1; + s1 += buffer[5], s2 += s1; + s1 += buffer[6], s2 += s1; + s1 += buffer[7], s2 += s1; + + buffer += 8; + } + + for (; i < blocklen; ++i) + s1 += *buffer++, s2 += s1; + + s1 %= ADLER_MOD, s2 %= ADLER_MOD; + buflen -= blocklen; + blocklen = 5552; + } + return (s2 << 16) + s1; +} + +#define stb__big32(c) (((c)[0]<<24) + (c)[1]*65536 + (c)[2]*256 + (c)[3]) +static void stb__sha1(stb_uchar *chunk, stb_uint h[5]) +{ + int i; + stb_uint a,b,c,d,e; + stb_uint w[80]; + + for (i=0; i < 16; ++i) + w[i] = stb__big32(&chunk[i*4]); + for (i=16; i < 80; ++i) { + stb_uint t; + t = w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]; + w[i] = (t + t) | (t >> 31); + } + + a = h[0]; + b = h[1]; + c = h[2]; + d = h[3]; + e = h[4]; + + #define STB__SHA1(k,f) \ + { \ + stb_uint temp = (a << 5) + (a >> 27) + (f) + e + (k) + w[i]; \ + e = d; \ + d = c; \ + c = (b << 30) + (b >> 2); \ + b = a; \ + a = temp; \ + } + + i=0; + for (; i < 20; ++i) STB__SHA1(0x5a827999, d ^ (b & (c ^ d)) ); + for (; i < 40; ++i) STB__SHA1(0x6ed9eba1, b ^ c ^ d ); + for (; i < 60; ++i) STB__SHA1(0x8f1bbcdc, (b & c) + (d & (b ^ c)) ); + for (; i < 80; ++i) STB__SHA1(0xca62c1d6, b ^ c ^ d ); + + #undef STB__SHA1 + + h[0] += a; + h[1] += b; + h[2] += c; + h[3] += d; + h[4] += e; +} + +void stb_sha1(stb_uchar output[20], stb_uchar *buffer, stb_uint len) +{ + unsigned char final_block[128]; + stb_uint end_start, final_len, j; + int i; + + stb_uint h[5]; + + h[0] = 0x67452301; + h[1] = 0xefcdab89; + h[2] = 0x98badcfe; + h[3] = 0x10325476; + h[4] = 0xc3d2e1f0; + + // we need to write padding to the last one or two + // blocks, so build those first into 'final_block' + + // we have to write one special byte, plus the 8-byte length + + // compute the block where the data runs out + end_start = len & ~63; + + // compute the earliest we can encode the length + if (((len+9) & ~63) == end_start) { + // it all fits in one block, so fill a second-to-last block + end_start -= 64; + } + + final_len = end_start + 128; + + // now we need to copy the data in + assert(end_start + 128 >= len+9); + assert(end_start < len || len < 64-9); + + j = 0; + if (end_start > len) + j = (stb_uint) - (int) end_start; + + for (; end_start + j < len; ++j) + final_block[j] = buffer[end_start + j]; + final_block[j++] = 0x80; + while (j < 128-5) // 5 byte length, so write 4 extra padding bytes + final_block[j++] = 0; + // big-endian size + final_block[j++] = len >> 29; + final_block[j++] = len >> 21; + final_block[j++] = len >> 13; + final_block[j++] = len >> 5; + final_block[j++] = len << 3; + assert(j == 128 && end_start + j == final_len); + + for (j=0; j < final_len; j += 64) { // 512-bit chunks + if (j+64 >= end_start+64) + stb__sha1(&final_block[j - end_start], h); + else + stb__sha1(&buffer[j], h); + } + + for (i=0; i < 5; ++i) { + output[i*4 + 0] = h[i] >> 24; + output[i*4 + 1] = h[i] >> 16; + output[i*4 + 2] = h[i] >> 8; + output[i*4 + 3] = h[i] >> 0; + } +} + +int stb_sha1_file(stb_uchar output[20], char *file) +{ + int i; + stb_uint64 length=0; + unsigned char buffer[128]; + + FILE *f = stb__fopen(file, "rb"); + stb_uint h[5]; + + if (f == NULL) return 0; // file not found + + h[0] = 0x67452301; + h[1] = 0xefcdab89; + h[2] = 0x98badcfe; + h[3] = 0x10325476; + h[4] = 0xc3d2e1f0; + + for(;;) { + int n = fread(buffer, 1, 64, f); + if (n == 64) { + stb__sha1(buffer, h); + length += n; + } else { + int block = 64; + + length += n; + + buffer[n++] = 0x80; + + // if there isn't enough room for the length, double the block + if (n + 8 > 64) + block = 128; + + // pad to end + memset(buffer+n, 0, block-8-n); + + i = block - 8; + buffer[i++] = (stb_uchar) (length >> 53); + buffer[i++] = (stb_uchar) (length >> 45); + buffer[i++] = (stb_uchar) (length >> 37); + buffer[i++] = (stb_uchar) (length >> 29); + buffer[i++] = (stb_uchar) (length >> 21); + buffer[i++] = (stb_uchar) (length >> 13); + buffer[i++] = (stb_uchar) (length >> 5); + buffer[i++] = (stb_uchar) (length << 3); + assert(i == block); + stb__sha1(buffer, h); + if (block == 128) + stb__sha1(buffer+64, h); + else + assert(block == 64); + break; + } + } + fclose(f); + + for (i=0; i < 5; ++i) { + output[i*4 + 0] = h[i] >> 24; + output[i*4 + 1] = h[i] >> 16; + output[i*4 + 2] = h[i] >> 8; + output[i*4 + 3] = h[i] >> 0; + } + + return 1; +} +#endif // STB_LIB_IMPLEMENTATION + +////////////////////////////////////////////////////////////////////////////// +// +// Random Numbers via Meresenne Twister or LCG +// + +#ifndef STB_INCLUDE_STB_LIB_H +STB_EXTERN unsigned long stb_srandLCG(unsigned long seed); +STB_EXTERN unsigned long stb_randLCG(void); +STB_EXTERN double stb_frandLCG(void); + +STB_EXTERN void stb_srand(unsigned long seed); +STB_EXTERN unsigned long stb_rand(void); +STB_EXTERN double stb_frand(void); +STB_EXTERN void stb_shuffle(void *p, size_t n, size_t sz, + unsigned long seed); +STB_EXTERN void stb_reverse(void *p, size_t n, size_t sz); + +STB_EXTERN unsigned long stb_randLCG_explicit(unsigned long seed); +#endif // STB_INCLUDE_STB_LIB_H + +#ifdef STB_LIB_IMPLEMENTATION +unsigned long stb_randLCG_explicit(unsigned long seed) +{ + return seed * 2147001325 + 715136305; +} + +static unsigned long stb__rand_seed=0; + +unsigned long stb_srandLCG(unsigned long seed) +{ + unsigned long previous = stb__rand_seed; + stb__rand_seed = seed; + return previous; +} + +unsigned long stb_randLCG(void) +{ + stb__rand_seed = stb__rand_seed * 2147001325 + 715136305; // BCPL generator + // shuffle non-random bits to the middle, and xor to decorrelate with seed + return 0x31415926 ^ ((stb__rand_seed >> 16) + (stb__rand_seed << 16)); +} + +double stb_frandLCG(void) +{ + return stb_randLCG() / ((double) (1 << 16) * (1 << 16)); +} + +void stb_shuffle(void *p, size_t n, size_t sz, unsigned long seed) +{ + char *a; + unsigned long old_seed; + int i; + if (seed) + old_seed = stb_srandLCG(seed); + a = (char *) p + (n-1) * sz; + + for (i=n; i > 1; --i) { + int j = stb_randLCG() % i; + stb_swap(a, (char *) p + j * sz, sz); + a -= sz; + } + if (seed) + stb_srandLCG(old_seed); +} + +void stb_reverse(void *p, size_t n, size_t sz) +{ + int i,j = n-1; + for (i=0; i < j; ++i,--j) { + stb_swap((char *) p + i * sz, (char *) p + j * sz, sz); + } +} + +// public domain Mersenne Twister by Michael Brundage +#define STB__MT_LEN 624 + +int stb__mt_index = STB__MT_LEN*sizeof(unsigned long)+1; +unsigned long stb__mt_buffer[STB__MT_LEN]; + +void stb_srand(unsigned long seed) +{ + int i; + unsigned long old = stb_srandLCG(seed); + for (i = 0; i < STB__MT_LEN; i++) + stb__mt_buffer[i] = stb_randLCG(); + stb_srandLCG(old); + stb__mt_index = STB__MT_LEN*sizeof(unsigned long); +} + +#define STB__MT_IA 397 +#define STB__MT_IB (STB__MT_LEN - STB__MT_IA) +#define STB__UPPER_MASK 0x80000000 +#define STB__LOWER_MASK 0x7FFFFFFF +#define STB__MATRIX_A 0x9908B0DF +#define STB__TWIST(b,i,j) ((b)[i] & STB__UPPER_MASK) | ((b)[j] & STB__LOWER_MASK) +#define STB__MAGIC(s) (((s)&1)*STB__MATRIX_A) + +unsigned long stb_rand() +{ + unsigned long * b = stb__mt_buffer; + int idx = stb__mt_index; + unsigned long s,r; + int i; + + if (idx >= STB__MT_LEN*sizeof(unsigned long)) { + if (idx > STB__MT_LEN*sizeof(unsigned long)) + stb_srand(0); + idx = 0; + i = 0; + for (; i < STB__MT_IB; i++) { + s = STB__TWIST(b, i, i+1); + b[i] = b[i + STB__MT_IA] ^ (s >> 1) ^ STB__MAGIC(s); + } + for (; i < STB__MT_LEN-1; i++) { + s = STB__TWIST(b, i, i+1); + b[i] = b[i - STB__MT_IB] ^ (s >> 1) ^ STB__MAGIC(s); + } + + s = STB__TWIST(b, STB__MT_LEN-1, 0); + b[STB__MT_LEN-1] = b[STB__MT_IA-1] ^ (s >> 1) ^ STB__MAGIC(s); + } + stb__mt_index = idx + sizeof(unsigned long); + + r = *(unsigned long *)((unsigned char *)b + idx); + + r ^= (r >> 11); + r ^= (r << 7) & 0x9D2C5680; + r ^= (r << 15) & 0xEFC60000; + r ^= (r >> 18); + + return r; +} + +double stb_frand(void) +{ + return stb_rand() / ((double) (1 << 16) * (1 << 16)); +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// wildcards and regexping +// + +#ifndef STB_INCLUDE_STB_LIB_H +STB_EXTERN int stb_wildmatch (char *expr, char *candidate); +STB_EXTERN int stb_wildmatchi(char *expr, char *candidate); +STB_EXTERN int stb_wildfind (char *expr, char *candidate); +STB_EXTERN int stb_wildfindi (char *expr, char *candidate); +#endif // STB_INCLUDE_STB_LIB_H + +#ifdef STB_LIB_IMPLEMENTATION +static int stb__match_qstring(char *candidate, char *qstring, int qlen, int insensitive) +{ + int i; + if (insensitive) { + for (i=0; i < qlen; ++i) + if (qstring[i] == '?') { + if (!candidate[i]) return 0; + } else + if (tolower(qstring[i]) != tolower(candidate[i])) + return 0; + } else { + for (i=0; i < qlen; ++i) + if (qstring[i] == '?') { + if (!candidate[i]) return 0; + } else + if (qstring[i] != candidate[i]) + return 0; + } + return 1; +} + +static int stb__find_qstring(char *candidate, char *qstring, int qlen, int insensitive) +{ + char c; + + int offset=0; + while (*qstring == '?') { + ++qstring; + --qlen; + ++candidate; + if (qlen == 0) return 0; + if (*candidate == 0) return -1; + } + + c = *qstring++; + --qlen; + if (insensitive) c = tolower(c); + + while (candidate[offset]) { + if (c == (insensitive ? tolower(candidate[offset]) : candidate[offset])) + if (stb__match_qstring(candidate+offset+1, qstring, qlen, insensitive)) + return offset; + ++offset; + } + + return -1; +} + +int stb__wildmatch_raw2(char *expr, char *candidate, int search, int insensitive) +{ + int where=0; + int start = -1; + + if (!search) { + // parse to first '*' + if (*expr != '*') + start = 0; + while (*expr != '*') { + if (!*expr) + return *candidate == 0 ? 0 : -1; + if (*expr == '?') { + if (!*candidate) return -1; + } else { + if (insensitive) { + if (tolower(*candidate) != tolower(*expr)) + return -1; + } else + if (*candidate != *expr) + return -1; + } + ++candidate, ++expr, ++where; + } + } else { + // 0-length search string + if (!*expr) + return 0; + } + + assert(search || *expr == '*'); + if (!search) + ++expr; + + // implicit '*' at this point + + while (*expr) { + int o=0; + // combine redundant * characters + while (expr[0] == '*') ++expr; + + // ok, at this point, expr[-1] == '*', + // and expr[0] != '*' + + if (!expr[0]) return start >= 0 ? start : 0; + + // now find next '*' + o = 0; + while (expr[o] != '*') { + if (expr[o] == 0) + break; + ++o; + } + // if no '*', scan to end, then match at end + if (expr[o] == 0 && !search) { + int z; + for (z=0; z < o; ++z) + if (candidate[z] == 0) + return -1; + while (candidate[z]) + ++z; + // ok, now check if they match + if (stb__match_qstring(candidate+z-o, expr, o, insensitive)) + return start >= 0 ? start : 0; + return -1; + } else { + // if yes '*', then do stb__find_qmatch on the intervening chars + int n = stb__find_qstring(candidate, expr, o, insensitive); + if (n < 0) + return -1; + if (start < 0) + start = where + n; + expr += o; + candidate += n+o; + } + + if (*expr == 0) { + assert(search); + return start; + } + + assert(*expr == '*'); + ++expr; + } + + return start >= 0 ? start : 0; +} + +int stb__wildmatch_raw(char *expr, char *candidate, int search, int insensitive) +{ + char buffer[256]; + // handle multiple search strings + char *s = strchr(expr, ';'); + char *last = expr; + while (s) { + int z; + // need to allow for non-writeable strings... assume they're small + if (s - last < 256) { + stb_strncpy(buffer, last, s-last+1); + z = stb__wildmatch_raw2(buffer, candidate, search, insensitive); + } else { + *s = 0; + z = stb__wildmatch_raw2(last, candidate, search, insensitive); + *s = ';'; + } + if (z >= 0) return z; + last = s+1; + s = strchr(last, ';'); + } + return stb__wildmatch_raw2(last, candidate, search, insensitive); +} + +int stb_wildmatch(char *expr, char *candidate) +{ + return stb__wildmatch_raw(expr, candidate, 0,0) >= 0; +} + +int stb_wildmatchi(char *expr, char *candidate) +{ + return stb__wildmatch_raw(expr, candidate, 0,1) >= 0; +} + +int stb_wildfind(char *expr, char *candidate) +{ + return stb__wildmatch_raw(expr, candidate, 1,0); +} + +int stb_wildfindi(char *expr, char *candidate) +{ + return stb__wildmatch_raw(expr, candidate, 1,1); +} + +#undef STB_LIB_IMPLEMENTATION +#endif // STB_LIB_IMPLEMENTATION + +#ifndef STB_INCLUDE_STB_LIB_H +#define STB_INCLUDE_STB_LIB_H +#undef STB_EXTERN +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/source/engine/thirdparty/stb/tests/resample_test.cpp b/source/engine/thirdparty/stb/tests/resample_test.cpp new file mode 100644 index 0000000..6595e37 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/resample_test.cpp @@ -0,0 +1,1127 @@ +#define _CRT_SECURE_NO_WARNINGS +#include +#include + +#if defined(_WIN32) && _MSC_VER > 1200 +#define STBIR_ASSERT(x) \ + if (!(x)) { \ + __debugbreak(); \ + } else +#else +#include +#define STBIR_ASSERT(x) assert(x) +#endif + +#define STBIR_MALLOC stbir_malloc +#define STBIR_FREE stbir_free + +class stbir_context { +public: + stbir_context() + { + size = 1000000; + memory = malloc(size); + } + + ~stbir_context() + { + free(memory); + } + + size_t size; + void* memory; +} g_context; + +void* stbir_malloc(size_t size, void* context) +{ + if (!context) + return malloc(size); + + stbir_context* real_context = (stbir_context*)context; + if (size > real_context->size) + return 0; + + return real_context->memory; +} + +void stbir_free(void* memory, void* context) +{ + if (!context) + free(memory); +} + +//#include +void stbir_progress(float p) +{ + //printf("%f\n", p); + STBIR_ASSERT(p >= 0 && p <= 1); +} + +#ifdef __clang__ +#define STBIRDEF static inline +#endif + +#define STBIR_PROGRESS_REPORT stbir_progress +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#define STB_IMAGE_RESIZE_STATIC +#include "stb_image_resize2.h" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#ifdef _WIN32 +#include +#include +#define mkdir(a, b) _mkdir(a) +#else +#include +#endif + +#define MT_SIZE 624 +static size_t g_aiMT[MT_SIZE]; +static size_t g_iMTI = 0; + +// Mersenne Twister implementation from Wikipedia. +// Avoiding use of the system rand() to be sure that our tests generate the same test data on any system. +void mtsrand(size_t iSeed) +{ + g_aiMT[0] = iSeed; + for (size_t i = 1; i < MT_SIZE; i++) + { + size_t inner1 = g_aiMT[i - 1]; + size_t inner2 = (g_aiMT[i - 1] >> 30); + size_t inner = inner1 ^ inner2; + g_aiMT[i] = (0x6c078965 * inner) + i; + } + + g_iMTI = 0; +} + +size_t mtrand() +{ + if (g_iMTI == 0) + { + for (size_t i = 0; i < MT_SIZE; i++) + { + size_t y = (0x80000000 & (g_aiMT[i])) + (0x7fffffff & (g_aiMT[(i + 1) % MT_SIZE])); + g_aiMT[i] = g_aiMT[(i + 397) % MT_SIZE] ^ (y >> 1); + if ((y % 2) == 1) + g_aiMT[i] = g_aiMT[i] ^ 0x9908b0df; + } + } + + size_t y = g_aiMT[g_iMTI]; + y = y ^ (y >> 11); + y = y ^ ((y << 7) & (0x9d2c5680)); + y = y ^ ((y << 15) & (0xefc60000)); + y = y ^ (y >> 18); + + g_iMTI = (g_iMTI + 1) % MT_SIZE; + + return y; +} + + +inline float mtfrand() +{ + const int ninenine = 999999; + return (float)(mtrand() % ninenine)/ninenine; +} + +void resizer(int argc, char **argv) +{ + unsigned char* input_pixels; + unsigned char* output_pixels; + int w, h; + int n; + int out_w, out_h; + input_pixels = stbi_load(argv[1], &w, &h, &n, 0); + out_w = w*3; + out_h = h*3; + output_pixels = (unsigned char*) malloc(out_w*out_h*n); + //stbir_resize_uint8_srgb(input_pixels, w, h, 0, output_pixels, out_w, out_h, 0, n, -1,0); + stbir_resize_uint8_linear(input_pixels, w, h, 0, output_pixels, out_w, out_h, 0, (stbir_pixel_layout) n); + stbi_write_png("output.png", out_w, out_h, n, output_pixels, 0); + exit(0); +} + +void performance(int argc, char **argv) +{ + unsigned char* input_pixels; + unsigned char* output_pixels; + int w, h, count; + int n, i; + int out_w, out_h, srgb=1; + input_pixels = stbi_load(argv[1], &w, &h, &n, 0); + #if 0 + out_w = w/4; out_h = h/4; count=100; // 1 + #elif 0 + out_w = w*2; out_h = h/4; count=20; // 2 // note this is structured pessimily, would be much faster to downsample vertically first + #elif 0 + out_w = w/4; out_h = h*2; count=50; // 3 + #elif 0 + out_w = w*3; out_h = h*3; count=2; srgb=0; // 4 + #else + out_w = w*3; out_h = h*3; count=2; // 5 // this is dominated by linear->sRGB conversion + #endif + + output_pixels = (unsigned char*) malloc(out_w*out_h*n); + for (i=0; i < count; ++i) + if (srgb) + stbir_resize_uint8_srgb(input_pixels, w, h, 0, output_pixels, out_w, out_h, 0, (stbir_pixel_layout) n); + else + stbir_resize_uint8_linear(input_pixels, w, h, 0, output_pixels, out_w, out_h, 0, (stbir_pixel_layout) n); + exit(0); +} + +void test_suite(int argc, char **argv); + +int main(int argc, char** argv) +{ + //resizer(argc, argv); + //performance(argc, argv); + + test_suite(argc, argv); + return 0; +} + +#if 0 +void resize_image(const char* filename, float width_percent, float height_percent, stbir_filter filter, stbir_edge edge, stbir_colorspace colorspace, const char* output_filename) +{ + int w, h, n; + + unsigned char* input_data = stbi_load(filename, &w, &h, &n, 0); + if (!input_data) + { + printf("Input image could not be loaded\n"); + return; + } + + int out_w = (int)(w * width_percent); + int out_h = (int)(h * height_percent); + + unsigned char* output_data = (unsigned char*)malloc(out_w * out_h * n); + + stbir_resize(input_data, w, h, 0, output_data, out_w, out_h, 0, STBIR_TYPE_UINT8, n, STBIR_ALPHA_CHANNEL_NONE, 0, edge, edge, filter, filter, colorspace, &g_context); + + stbi_image_free(input_data); + + stbi_write_png(output_filename, out_w, out_h, n, output_data, 0); + + free(output_data); +} + +template +void convert_image(const F* input, T* output, int length) +{ + double f = (pow(2.0, 8.0 * sizeof(T)) - 1) / (pow(2.0, 8.0 * sizeof(F)) - 1); + for (int i = 0; i < length; i++) + output[i] = (T)(((double)input[i]) * f); +} + +template +void test_format(const char* file, float width_percent, float height_percent, stbir_datatype type, stbir_colorspace colorspace) +{ + int w, h, n; + unsigned char* input_data = stbi_load(file, &w, &h, &n, 0); + + if (input_data == NULL) + return; + + + int new_w = (int)(w * width_percent); + int new_h = (int)(h * height_percent); + + T* T_data = (T*)malloc(w * h * n * sizeof(T)); + memset(T_data, 0, w*h*n*sizeof(T)); + convert_image(input_data, T_data, w * h * n); + + T* output_data = (T*)malloc(new_w * new_h * n * sizeof(T)); + + stbir_resize(T_data, w, h, 0, output_data, new_w, new_h, 0, type, n, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_CATMULLROM, STBIR_FILTER_CATMULLROM, colorspace, &g_context); + + free(T_data); + stbi_image_free(input_data); + + unsigned char* char_data = (unsigned char*)malloc(new_w * new_h * n * sizeof(char)); + convert_image(output_data, char_data, new_w * new_h * n); + + char output[200]; + sprintf(output, "test-output/type-%d-%d-%d-%d-%s", type, colorspace, new_w, new_h, file); + stbi_write_png(output, new_w, new_h, n, char_data, 0); + + free(char_data); + free(output_data); +} + +void convert_image_float(const unsigned char* input, float* output, int length) +{ + for (int i = 0; i < length; i++) + output[i] = ((float)input[i])/255; +} + +void convert_image_float(const float* input, unsigned char* output, int length) +{ + for (int i = 0; i < length; i++) + output[i] = (unsigned char)(stbir__saturate(input[i]) * 255); +} + +void test_float(const char* file, float width_percent, float height_percent, stbir_datatype type, stbir_colorspace colorspace) +{ + int w, h, n; + unsigned char* input_data = stbi_load(file, &w, &h, &n, 0); + + if (input_data == NULL) + return; + + int new_w = (int)(w * width_percent); + int new_h = (int)(h * height_percent); + + float* T_data = (float*)malloc(w * h * n * sizeof(float)); + convert_image_float(input_data, T_data, w * h * n); + + float* output_data = (float*)malloc(new_w * new_h * n * sizeof(float)); + + stbir_resize_float_generic(T_data, w, h, 0, output_data, new_w, new_h, 0, n, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_FILTER_CATMULLROM, colorspace, &g_context); + + free(T_data); + stbi_image_free(input_data); + + unsigned char* char_data = (unsigned char*)malloc(new_w * new_h * n * sizeof(char)); + convert_image_float(output_data, char_data, new_w * new_h * n); + + char output[200]; + sprintf(output, "test-output/type-%d-%d-%d-%d-%s", type, colorspace, new_w, new_h, file); + stbi_write_png(output, new_w, new_h, n, char_data, 0); + + free(char_data); + free(output_data); +} + +void test_channels(const char* file, float width_percent, float height_percent, int channels) +{ + int w, h, n; + unsigned char* input_data = stbi_load(file, &w, &h, &n, 0); + + if (input_data == NULL) + return; + + int new_w = (int)(w * width_percent); + int new_h = (int)(h * height_percent); + + unsigned char* channels_data = (unsigned char*)malloc(w * h * channels * sizeof(unsigned char)); + + for (int i = 0; i < w * h; i++) + { + int input_position = i * n; + int output_position = i * channels; + + for (int c = 0; c < channels; c++) + channels_data[output_position + c] = input_data[input_position + stbir__min(c, n)]; + } + + unsigned char* output_data = (unsigned char*)malloc(new_w * new_h * channels * sizeof(unsigned char)); + + stbir_resize_uint8_srgb(channels_data, w, h, 0, output_data, new_w, new_h, 0, channels, STBIR_ALPHA_CHANNEL_NONE, 0); + + free(channels_data); + stbi_image_free(input_data); + + char output[200]; + sprintf(output, "test-output/channels-%d-%d-%d-%s", channels, new_w, new_h, file); + stbi_write_png(output, new_w, new_h, channels, output_data, 0); + + free(output_data); +} + +void test_subpixel(const char* file, float width_percent, float height_percent, float s1, float t1) +{ + int w, h, n; + unsigned char* input_data = stbi_load(file, &w, &h, &n, 0); + + if (input_data == NULL) + return; + + s1 = ((float)w - 1 + s1)/w; + t1 = ((float)h - 1 + t1)/h; + + int new_w = (int)(w * width_percent); + int new_h = (int)(h * height_percent); + + unsigned char* output_data = (unsigned char*)malloc(new_w * new_h * n * sizeof(unsigned char)); + + stbir_resize_region(input_data, w, h, 0, output_data, new_w, new_h, 0, STBIR_TYPE_UINT8, n, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_BOX, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_SRGB, &g_context, 0, 0, s1, t1); + + stbi_image_free(input_data); + + char output[200]; + sprintf(output, "test-output/subpixel-%d-%d-%f-%f-%s", new_w, new_h, s1, t1, file); + stbi_write_png(output, new_w, new_h, n, output_data, 0); + + free(output_data); +} + +void test_subpixel_region(const char* file, float width_percent, float height_percent, float s0, float t0, float s1, float t1) +{ + int w, h, n; + unsigned char* input_data = stbi_load(file, &w, &h, &n, 0); + + if (input_data == NULL) + return; + + int new_w = (int)(w * width_percent); + int new_h = (int)(h * height_percent); + + unsigned char* output_data = (unsigned char*)malloc(new_w * new_h * n * sizeof(unsigned char)); + + stbir_resize_region(input_data, w, h, 0, output_data, new_w, new_h, 0, STBIR_TYPE_UINT8, n, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_BOX, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_SRGB, &g_context, s0, t0, s1, t1); + + stbi_image_free(input_data); + + char output[200]; + sprintf(output, "test-output/subpixel-region-%d-%d-%f-%f-%f-%f-%s", new_w, new_h, s0, t0, s1, t1, file); + stbi_write_png(output, new_w, new_h, n, output_data, 0); + + free(output_data); +} + +void test_subpixel_command(const char* file, float width_percent, float height_percent, float x_scale, float y_scale, float x_offset, float y_offset) +{ + int w, h, n; + unsigned char* input_data = stbi_load(file, &w, &h, &n, 0); + + if (input_data == NULL) + return; + + int new_w = (int)(w * width_percent); + int new_h = (int)(h * height_percent); + + unsigned char* output_data = (unsigned char*)malloc(new_w * new_h * n * sizeof(unsigned char)); + + stbir_resize_subpixel(input_data, w, h, 0, output_data, new_w, new_h, 0, STBIR_TYPE_UINT8, n, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_BOX, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_SRGB, &g_context, x_scale, y_scale, x_offset, y_offset); + + stbi_image_free(input_data); + + char output[200]; + sprintf(output, "test-output/subpixel-command-%d-%d-%f-%f-%f-%f-%s", new_w, new_h, x_scale, y_scale, x_offset, y_offset, file); + stbi_write_png(output, new_w, new_h, n, output_data, 0); + + free(output_data); +} + +unsigned int* pixel(unsigned int* buffer, int x, int y, int c, int w, int n) +{ + return &buffer[y*w*n + x*n + c]; +} + +void test_premul() +{ + unsigned int input[2 * 2 * 4]; + unsigned int output[1 * 1 * 4]; + unsigned int output2[2 * 2 * 4]; + + memset(input, 0, sizeof(input)); + + // First a test to make sure premul is working properly. + + // Top left - solid red + *pixel(input, 0, 0, 0, 2, 4) = 255; + *pixel(input, 0, 0, 3, 2, 4) = 255; + + // Bottom left - solid red + *pixel(input, 0, 1, 0, 2, 4) = 255; + *pixel(input, 0, 1, 3, 2, 4) = 255; + + // Top right - transparent green + *pixel(input, 1, 0, 1, 2, 4) = 255; + *pixel(input, 1, 0, 3, 2, 4) = 25; + + // Bottom right - transparent green + *pixel(input, 1, 1, 1, 2, 4) = 255; + *pixel(input, 1, 1, 3, 2, 4) = 25; + + stbir_resize(input, 2, 2, 0, output, 1, 1, 0, STBIR_TYPE_UINT32, 4, 3, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_BOX, STBIR_FILTER_BOX, STBIR_COLORSPACE_LINEAR, &g_context); + + float r = (float)255 / 4294967296; + float g = (float)255 / 4294967296; + float ra = (float)255 / 4294967296; + float ga = (float)25 / 4294967296; + float a = (ra + ga) / 2; + + STBIR_ASSERT(output[0] == (unsigned int)(r * ra / 2 / a * 4294967296 + 0.5f)); // 232 + STBIR_ASSERT(output[1] == (unsigned int)(g * ga / 2 / a * 4294967296 + 0.5f)); // 23 + STBIR_ASSERT(output[2] == 0); + STBIR_ASSERT(output[3] == (unsigned int)(a * 4294967296 + 0.5f)); // 140 + + // Now a test to make sure it doesn't clobber existing values. + + // Top right - completely transparent green + *pixel(input, 1, 0, 1, 2, 4) = 255; + *pixel(input, 1, 0, 3, 2, 4) = 0; + + // Bottom right - completely transparent green + *pixel(input, 1, 1, 1, 2, 4) = 255; + *pixel(input, 1, 1, 3, 2, 4) = 0; + + stbir_resize(input, 2, 2, 0, output2, 2, 2, 0, STBIR_TYPE_UINT32, 4, 3, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_BOX, STBIR_FILTER_BOX, STBIR_COLORSPACE_LINEAR, &g_context); + + STBIR_ASSERT(*pixel(output2, 0, 0, 0, 2, 4) == 255); + STBIR_ASSERT(*pixel(output2, 0, 0, 1, 2, 4) == 0); + STBIR_ASSERT(*pixel(output2, 0, 0, 2, 2, 4) == 0); + STBIR_ASSERT(*pixel(output2, 0, 0, 3, 2, 4) == 255); + + STBIR_ASSERT(*pixel(output2, 0, 1, 0, 2, 4) == 255); + STBIR_ASSERT(*pixel(output2, 0, 1, 1, 2, 4) == 0); + STBIR_ASSERT(*pixel(output2, 0, 1, 2, 2, 4) == 0); + STBIR_ASSERT(*pixel(output2, 0, 1, 3, 2, 4) == 255); + + STBIR_ASSERT(*pixel(output2, 1, 0, 0, 2, 4) == 0); + STBIR_ASSERT(*pixel(output2, 1, 0, 1, 2, 4) == 255); + STBIR_ASSERT(*pixel(output2, 1, 0, 2, 2, 4) == 0); + STBIR_ASSERT(*pixel(output2, 1, 0, 3, 2, 4) == 0); + + STBIR_ASSERT(*pixel(output2, 1, 1, 0, 2, 4) == 0); + STBIR_ASSERT(*pixel(output2, 1, 1, 1, 2, 4) == 255); + STBIR_ASSERT(*pixel(output2, 1, 1, 2, 2, 4) == 0); + STBIR_ASSERT(*pixel(output2, 1, 1, 3, 2, 4) == 0); +} + +// test that splitting a pow-2 image into tiles produces identical results +void test_subpixel_1() +{ + unsigned char image[8 * 8]; + + mtsrand(0); + + for (int i = 0; i < sizeof(image); i++) + image[i] = mtrand() & 255; + + unsigned char output_data[16 * 16]; + + stbir_resize_region(image, 8, 8, 0, output_data, 16, 16, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_CATMULLROM, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_SRGB, &g_context, 0, 0, 1, 1); + + unsigned char output_left[8 * 16]; + unsigned char output_right[8 * 16]; + + stbir_resize_region(image, 8, 8, 0, output_left, 8, 16, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_CATMULLROM, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_SRGB, &g_context, 0, 0, 0.5f, 1); + stbir_resize_region(image, 8, 8, 0, output_right, 8, 16, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_CATMULLROM, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_SRGB, &g_context, 0.5f, 0, 1, 1); + + for (int x = 0; x < 8; x++) + { + for (int y = 0; y < 16; y++) + { + STBIR_ASSERT(output_data[y * 16 + x] == output_left[y * 8 + x]); + STBIR_ASSERT(output_data[y * 16 + x + 8] == output_right[y * 8 + x]); + } + } + + stbir_resize_subpixel(image, 8, 8, 0, output_left, 8, 16, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_CATMULLROM, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_SRGB, &g_context, 2, 2, 0, 0); + stbir_resize_subpixel(image, 8, 8, 0, output_right, 8, 16, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_CATMULLROM, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_SRGB, &g_context, 2, 2, 8, 0); + + {for (int x = 0; x < 8; x++) + { + for (int y = 0; y < 16; y++) + { + STBIR_ASSERT(output_data[y * 16 + x] == output_left[y * 8 + x]); + STBIR_ASSERT(output_data[y * 16 + x + 8] == output_right[y * 8 + x]); + } + }} +} + +// test that replicating an image and using a subtile of it produces same results as wraparound +void test_subpixel_2() +{ + unsigned char image[8 * 8]; + + mtsrand(0); + + for (int i = 0; i < sizeof(image); i++) + image[i] = mtrand() & 255; + + unsigned char large_image[32 * 32]; + + for (int x = 0; x < 8; x++) + { + for (int y = 0; y < 8; y++) + { + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + large_image[j*4*8*8 + i*8 + y*4*8 + x] = image[y*8 + x]; + } + } + } + + unsigned char output_data_1[16 * 16]; + unsigned char output_data_2[16 * 16]; + + stbir_resize(image, 8, 8, 0, output_data_1, 16, 16, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_WRAP, STBIR_EDGE_WRAP, STBIR_FILTER_CATMULLROM, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_SRGB, &g_context); + stbir_resize_region(large_image, 32, 32, 0, output_data_2, 16, 16, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_WRAP, STBIR_EDGE_WRAP, STBIR_FILTER_CATMULLROM, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_SRGB, &g_context, 0.25f, 0.25f, 0.5f, 0.5f); + + {for (int x = 0; x < 16; x++) + { + for (int y = 0; y < 16; y++) + STBIR_ASSERT(output_data_1[y * 16 + x] == output_data_2[y * 16 + x]); + }} + + stbir_resize_subpixel(large_image, 32, 32, 0, output_data_2, 16, 16, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_WRAP, STBIR_EDGE_WRAP, STBIR_FILTER_CATMULLROM, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_SRGB, &g_context, 2, 2, 16, 16); + + {for (int x = 0; x < 16; x++) + { + for (int y = 0; y < 16; y++) + STBIR_ASSERT(output_data_1[y * 16 + x] == output_data_2[y * 16 + x]); + }} +} + +// test that 0,0,1,1 subpixel produces same result as no-rect +void test_subpixel_3() +{ + unsigned char image[8 * 8]; + + mtsrand(0); + + for (int i = 0; i < sizeof(image); i++) + image[i] = mtrand() & 255; + + unsigned char output_data_1[32 * 32]; + unsigned char output_data_2[32 * 32]; + + stbir_resize_region(image, 8, 8, 0, output_data_1, 32, 32, 0, STBIR_TYPE_UINT8, 1, 0, STBIR_ALPHA_CHANNEL_NONE, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_CATMULLROM, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_LINEAR, NULL, 0, 0, 1, 1); + stbir_resize_uint8(image, 8, 8, 0, output_data_2, 32, 32, 0, 1); + + for (int x = 0; x < 32; x++) + { + for (int y = 0; y < 32; y++) + STBIR_ASSERT(output_data_1[y * 32 + x] == output_data_2[y * 32 + x]); + } + + stbir_resize_subpixel(image, 8, 8, 0, output_data_1, 32, 32, 0, STBIR_TYPE_UINT8, 1, 0, STBIR_ALPHA_CHANNEL_NONE, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_CATMULLROM, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_LINEAR, NULL, 4, 4, 0, 0); + + {for (int x = 0; x < 32; x++) + { + for (int y = 0; y < 32; y++) + STBIR_ASSERT(output_data_1[y * 32 + x] == output_data_2[y * 32 + x]); + }} +} + +// test that 1:1 resample using s,t=0,0,1,1 with bilinear produces original image +void test_subpixel_4() +{ + unsigned char image[8 * 8]; + + mtsrand(0); + + for (int i = 0; i < sizeof(image); i++) + image[i] = mtrand() & 255; + + unsigned char output[8 * 8]; + + stbir_resize_region(image, 8, 8, 0, output, 8, 8, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_TRIANGLE, STBIR_FILTER_TRIANGLE, STBIR_COLORSPACE_LINEAR, &g_context, 0, 0, 1, 1); + STBIR_ASSERT(memcmp(image, output, 8 * 8) == 0); + + stbir_resize_subpixel(image, 8, 8, 0, output, 8, 8, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_TRIANGLE, STBIR_FILTER_TRIANGLE, STBIR_COLORSPACE_LINEAR, &g_context, 1, 1, 0, 0); + STBIR_ASSERT(memcmp(image, output, 8 * 8) == 0); +} + +static unsigned int image88_int[8][8]; +static unsigned char image88 [8][8]; +static unsigned char output88[8][8]; +static unsigned char output44[4][4]; +static unsigned char output22[2][2]; +static unsigned char output11[1][1]; + +void resample_88(stbir_filter filter) +{ + stbir_resize_uint8_generic(image88[0],8,8,0, output88[0],8,8,0, 1,-1,0, STBIR_EDGE_CLAMP, filter, STBIR_COLORSPACE_LINEAR, NULL); + stbir_resize_uint8_generic(image88[0],8,8,0, output44[0],4,4,0, 1,-1,0, STBIR_EDGE_CLAMP, filter, STBIR_COLORSPACE_LINEAR, NULL); + stbir_resize_uint8_generic(image88[0],8,8,0, output22[0],2,2,0, 1,-1,0, STBIR_EDGE_CLAMP, filter, STBIR_COLORSPACE_LINEAR, NULL); + stbir_resize_uint8_generic(image88[0],8,8,0, output11[0],1,1,0, 1,-1,0, STBIR_EDGE_CLAMP, filter, STBIR_COLORSPACE_LINEAR, NULL); +} + +void verify_box(void) +{ + int i,j,t; + + resample_88(STBIR_FILTER_BOX); + + for (i=0; i < sizeof(image88); ++i) + STBIR_ASSERT(image88[0][i] == output88[0][i]); + + t = 0; + for (j=0; j < 4; ++j) + for (i=0; i < 4; ++i) { + int n = image88[j*2+0][i*2+0] + + image88[j*2+0][i*2+1] + + image88[j*2+1][i*2+0] + + image88[j*2+1][i*2+1]; + STBIR_ASSERT(output44[j][i] == ((n+2)>>2) || output44[j][i] == ((n+1)>>2)); // can't guarantee exact rounding due to numerical precision + t += n; + } + STBIR_ASSERT(output11[0][0] == ((t+32)>>6) || output11[0][0] == ((t+31)>>6)); // can't guarantee exact rounding due to numerical precision +} + +void verify_filter_normalized(stbir_filter filter, int output_size, unsigned int value) +{ + int i, j; + unsigned int output[64]; + + stbir_resize(image88_int[0], 8, 8, 0, output, output_size, output_size, 0, STBIR_TYPE_UINT32, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, filter, filter, STBIR_COLORSPACE_LINEAR, NULL); + + for (j = 0; j < output_size; ++j) + for (i = 0; i < output_size; ++i) + STBIR_ASSERT(value == output[j*output_size + i]); +} + +float round2(float f) +{ + return (float) floor(f+0.5f); // round() isn't C standard pre-C99 +} + +void test_filters(void) +{ + int i,j; + + mtsrand(0); + + for (i=0; i < sizeof(image88); ++i) + image88[0][i] = mtrand() & 255; + verify_box(); + + for (i=0; i < sizeof(image88); ++i) + image88[0][i] = 0; + image88[4][4] = 255; + verify_box(); + + for (j=0; j < 8; ++j) + for (i=0; i < 8; ++i) + image88[j][i] = (j^i)&1 ? 255 : 0; + verify_box(); + + for (j=0; j < 8; ++j) + for (i=0; i < 8; ++i) + image88[j][i] = i&2 ? 255 : 0; + verify_box(); + + int value = 64; + + for (j = 0; j < 8; ++j) + for (i = 0; i < 8; ++i) + image88_int[j][i] = value; + + verify_filter_normalized(STBIR_FILTER_BOX, 8, value); + verify_filter_normalized(STBIR_FILTER_TRIANGLE, 8, value); + verify_filter_normalized(STBIR_FILTER_CUBICBSPLINE, 8, value); + verify_filter_normalized(STBIR_FILTER_CATMULLROM, 8, value); + verify_filter_normalized(STBIR_FILTER_MITCHELL, 8, value); + + verify_filter_normalized(STBIR_FILTER_BOX, 4, value); + verify_filter_normalized(STBIR_FILTER_TRIANGLE, 4, value); + verify_filter_normalized(STBIR_FILTER_CUBICBSPLINE, 4, value); + verify_filter_normalized(STBIR_FILTER_CATMULLROM, 4, value); + verify_filter_normalized(STBIR_FILTER_MITCHELL, 4, value); + + verify_filter_normalized(STBIR_FILTER_BOX, 2, value); + verify_filter_normalized(STBIR_FILTER_TRIANGLE, 2, value); + verify_filter_normalized(STBIR_FILTER_CUBICBSPLINE, 2, value); + verify_filter_normalized(STBIR_FILTER_CATMULLROM, 2, value); + verify_filter_normalized(STBIR_FILTER_MITCHELL, 2, value); + + verify_filter_normalized(STBIR_FILTER_BOX, 1, value); + verify_filter_normalized(STBIR_FILTER_TRIANGLE, 1, value); + verify_filter_normalized(STBIR_FILTER_CUBICBSPLINE, 1, value); + verify_filter_normalized(STBIR_FILTER_CATMULLROM, 1, value); + verify_filter_normalized(STBIR_FILTER_MITCHELL, 1, value); + + { + // This test is designed to produce coefficients that are very badly denormalized. + unsigned int v = 556; + + unsigned int input[100 * 100]; + unsigned int output[11 * 11]; + + for (j = 0; j < 100 * 100; ++j) + input[j] = v; + + stbir_resize(input, 100, 100, 0, output, 11, 11, 0, STBIR_TYPE_UINT32, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_TRIANGLE, STBIR_FILTER_TRIANGLE, STBIR_COLORSPACE_LINEAR, NULL); + + for (j = 0; j < 11 * 11; ++j) + STBIR_ASSERT(v == output[j]); + } + + { + // Now test the trapezoid filter for downsampling. + unsigned int input[3 * 1]; + unsigned int output[2 * 1]; + + input[0] = 0; + input[1] = 255; + input[2] = 127; + + stbir_resize(input, 3, 1, 0, output, 2, 1, 0, STBIR_TYPE_UINT32, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_BOX, STBIR_FILTER_BOX, STBIR_COLORSPACE_LINEAR, NULL); + + STBIR_ASSERT(output[0] == (unsigned int)round2((float)(input[0] * 2 + input[1]) / 3)); + STBIR_ASSERT(output[1] == (unsigned int)round2((float)(input[2] * 2 + input[1]) / 3)); + + stbir_resize(input, 1, 3, 0, output, 1, 2, 0, STBIR_TYPE_UINT32, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_BOX, STBIR_FILTER_BOX, STBIR_COLORSPACE_LINEAR, NULL); + + STBIR_ASSERT(output[0] == (unsigned int)round2((float)(input[0] * 2 + input[1]) / 3)); + STBIR_ASSERT(output[1] == (unsigned int)round2((float)(input[2] * 2 + input[1]) / 3)); + } + + { + // Now test the trapezoid filter for upsampling. + unsigned int input[2 * 1]; + unsigned int output[3 * 1]; + + input[0] = 0; + input[1] = 255; + + stbir_resize(input, 2, 1, 0, output, 3, 1, 0, STBIR_TYPE_UINT32, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_BOX, STBIR_FILTER_BOX, STBIR_COLORSPACE_LINEAR, NULL); + + STBIR_ASSERT(output[0] == input[0]); + STBIR_ASSERT(output[1] == (input[0] + input[1]) / 2); + STBIR_ASSERT(output[2] == input[1]); + + stbir_resize(input, 1, 2, 0, output, 1, 3, 0, STBIR_TYPE_UINT32, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_BOX, STBIR_FILTER_BOX, STBIR_COLORSPACE_LINEAR, NULL); + + STBIR_ASSERT(output[0] == input[0]); + STBIR_ASSERT(output[1] == (input[0] + input[1]) / 2); + STBIR_ASSERT(output[2] == input[1]); + } + + // checkerboard + { + unsigned char input[64][64]; + unsigned char output[16][16]; + int i,j; + for (j=0; j < 64; ++j) + for (i=0; i < 64; ++i) + input[j][i] = (i^j)&1 ? 255 : 0; + stbir_resize_uint8_generic(input[0], 64, 64, 0, output[0],16,16,0, 1,-1,0,STBIR_EDGE_WRAP,STBIR_FILTER_DEFAULT,STBIR_COLORSPACE_LINEAR,0); + for (j=0; j < 16; ++j) + for (i=0; i < 16; ++i) + STBIR_ASSERT(output[j][i] == 128); + stbir_resize_uint8_srgb_edgemode(input[0], 64, 64, 0, output[0],16,16,0, 1,-1,0,STBIR_EDGE_WRAP); + for (j=0; j < 16; ++j) + for (i=0; i < 16; ++i) + STBIR_ASSERT(output[j][i] == 188); + + + } + + { + // Test trapezoid box filter + unsigned char input[2 * 1]; + unsigned char output[127 * 1]; + + input[0] = 0; + input[1] = 255; + + stbir_resize(input, 2, 1, 0, output, 127, 1, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_BOX, STBIR_FILTER_BOX, STBIR_COLORSPACE_LINEAR, NULL); + STBIR_ASSERT(output[0] == 0); + STBIR_ASSERT(output[127 / 2 - 1] == 0); + STBIR_ASSERT(output[127 / 2] == 128); + STBIR_ASSERT(output[127 / 2 + 1] == 255); + STBIR_ASSERT(output[126] == 255); + stbi_write_png("test-output/trapezoid-upsample-horizontal.png", 127, 1, 1, output, 0); + + stbir_resize(input, 1, 2, 0, output, 1, 127, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_BOX, STBIR_FILTER_BOX, STBIR_COLORSPACE_LINEAR, NULL); + STBIR_ASSERT(output[0] == 0); + STBIR_ASSERT(output[127 / 2 - 1] == 0); + STBIR_ASSERT(output[127 / 2] == 128); + STBIR_ASSERT(output[127 / 2 + 1] == 255); + STBIR_ASSERT(output[126] == 255); + stbi_write_png("test-output/trapezoid-upsample-vertical.png", 1, 127, 1, output, 0); + } +} + +#define UMAX32 4294967295U + +static void write32(const char *filename, stbir_uint32 *output, int w, int h) +{ + stbir_uint8 *data = (stbir_uint8*) malloc(w*h*3); + for (int i=0; i < w*h*3; ++i) + data[i] = output[i]>>24; + stbi_write_png(filename, w, h, 3, data, 0); + free(data); +} + +static void test_32(void) +{ + int w=100,h=120,x,y, out_w,out_h; + stbir_uint32 *input = (stbir_uint32*) malloc(4 * 3 * w * h); + stbir_uint32 *output = (stbir_uint32*) malloc(4 * 3 * 3*w * 3*h); + for (y=0; y < h; ++y) { + for (x=0; x < w; ++x) { + input[y*3*w + x*3 + 0] = x * ( UMAX32/w ); + input[y*3*w + x*3 + 1] = y * ( UMAX32/h ); + input[y*3*w + x*3 + 2] = UMAX32/2; + } + } + out_w = w*33/16; + out_h = h*33/16; + stbir_resize(input,w,h,0,output,out_w,out_h,0,STBIR_TYPE_UINT32,3,-1,0,STBIR_EDGE_CLAMP,STBIR_EDGE_CLAMP,STBIR_FILTER_DEFAULT,STBIR_FILTER_DEFAULT,STBIR_COLORSPACE_LINEAR,NULL); + write32("test-output/seantest_1.png", output,out_w,out_h); + + out_w = w*16/33; + out_h = h*16/33; + stbir_resize(input,w,h,0,output,out_w,out_h,0,STBIR_TYPE_UINT32,3,-1,0,STBIR_EDGE_CLAMP,STBIR_EDGE_CLAMP,STBIR_FILTER_DEFAULT,STBIR_FILTER_DEFAULT,STBIR_COLORSPACE_LINEAR,NULL); + write32("test-output/seantest_2.png", output,out_w,out_h); +} + + +void test_suite(int argc, char **argv) +{ + int i; + const char *barbara; + + mkdir("test-output", 777); + + if (argc > 1) + barbara = argv[1]; + else + barbara = "barbara.png"; + + // check what cases we need normalization for +#if 1 + { + float x, y; + for (x = -1; x < 1; x += 0.05f) { + float sums[5] = { 0 }; + float o; + for (o = -5; o <= 5; ++o) { + sums[0] += stbir__filter_mitchell(x + o, 1); + sums[1] += stbir__filter_catmullrom(x + o, 1); + sums[2] += stbir__filter_cubic(x + o, 1); + sums[3] += stbir__filter_triangle(x + o, 1); + sums[4] += stbir__filter_trapezoid(x + o, 0.5f); + } + for (i = 0; i < 5; ++i) + STBIR_ASSERT(sums[i] >= 1.0 - 0.001 && sums[i] <= 1.0 + 0.001); + } + +#if 1 + for (y = 0.11f; y < 1; y += 0.01f) { // Step + for (x = -1; x < 1; x += 0.05f) { // Phase + float sums[5] = { 0 }; + float o; + for (o = -5; o <= 5; o += y) { + sums[0] += y * stbir__filter_mitchell(x + o, 1); + sums[1] += y * stbir__filter_catmullrom(x + o, 1); + sums[2] += y * stbir__filter_cubic(x + o, 1); + sums[4] += y * stbir__filter_trapezoid(x + o, 0.5f); + sums[3] += y * stbir__filter_triangle(x + o, 1); + } + for (i = 0; i < 3; ++i) + STBIR_ASSERT(sums[i] >= 1.0 - 0.0170 && sums[i] <= 1.0 + 0.0170); + } + } +#endif + } +#endif + +#if 0 // linear_to_srgb_uchar table + for (i=0; i < 256; ++i) { + float f = stbir__srgb_to_linear((i-0.5f)/255.0f); + printf("%9d, ", (int) ((f) * (1<<28))); + if ((i & 7) == 7) + printf("\n"); + } +#endif + + // old tests that hacky fix worked on - test that + // every uint8 maps to itself + for (i = 0; i < 256; i++) { + float f = stbir__srgb_to_linear(float(i) / 255); + int n = stbir__linear_to_srgb_uchar(f); + STBIR_ASSERT(n == i); + } + + // new tests that hacky fix failed for - test that + // values adjacent to uint8 round to nearest uint8 + for (i = 0; i < 256; i++) { + for (float y = -0.42f; y <= 0.42f; y += 0.01f) { + float f = stbir__srgb_to_linear((i+y) / 255.0f); + int n = stbir__linear_to_srgb_uchar(f); + STBIR_ASSERT(n == i); + } + } + + test_filters(); + + test_subpixel_1(); + test_subpixel_2(); + test_subpixel_3(); + test_subpixel_4(); + + test_premul(); + + test_32(); + + // Some tests to make sure errors don't pop up with strange filter/dimension combinations. + stbir_resize(image88, 8, 8, 0, output88, 4, 16, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_BOX, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_SRGB, &g_context); + stbir_resize(image88, 8, 8, 0, output88, 4, 16, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_CATMULLROM, STBIR_FILTER_BOX, STBIR_COLORSPACE_SRGB, &g_context); + stbir_resize(image88, 8, 8, 0, output88, 16, 4, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_BOX, STBIR_FILTER_CATMULLROM, STBIR_COLORSPACE_SRGB, &g_context); + stbir_resize(image88, 8, 8, 0, output88, 16, 4, 0, STBIR_TYPE_UINT8, 1, STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_FILTER_CATMULLROM, STBIR_FILTER_BOX, STBIR_COLORSPACE_SRGB, &g_context); + + int barbara_width, barbara_height, barbara_channels; + stbi_image_free(stbi_load(barbara, &barbara_width, &barbara_height, &barbara_channels, 0)); + + int res = 10; + // Downscaling + {for (int i = 0; i <= res; i++) + { + float t = (float)i/res; + float scale = 0.5; + float out_scale = 2.0f/3; + float x_shift = (barbara_width*out_scale - barbara_width*scale) * t; + float y_shift = (barbara_height*out_scale - barbara_height*scale) * t; + + test_subpixel_command(barbara, scale, scale, out_scale, out_scale, x_shift, y_shift); + }} + + // Upscaling + {for (int i = 0; i <= res; i++) + { + float t = (float)i/res; + float scale = 2; + float out_scale = 3; + float x_shift = (barbara_width*out_scale - barbara_width*scale) * t; + float y_shift = (barbara_height*out_scale - barbara_height*scale) * t; + + test_subpixel_command(barbara, scale, scale, out_scale, out_scale, x_shift, y_shift); + }} + + // Downscaling + {for (int i = 0; i <= res; i++) + { + float t = (float)i/res / 2; + test_subpixel_region(barbara, 0.25f, 0.25f, t, t, t+0.5f, t+0.5f); + }} + + // No scaling + {for (int i = 0; i <= res; i++) + { + float t = (float)i/res / 2; + test_subpixel_region(barbara, 0.5f, 0.5f, t, t, t+0.5f, t+0.5f); + }} + + // Upscaling + {for (int i = 0; i <= res; i++) + { + float t = (float)i/res / 2; + test_subpixel_region(barbara, 1, 1, t, t, t+0.5f, t+0.5f); + }} + + {for (i = 0; i < 10; i++) + test_subpixel(barbara, 0.5f, 0.5f, (float)i / 10, 1); + } + + {for (i = 0; i < 10; i++) + test_subpixel(barbara, 0.5f, 0.5f, 1, (float)i / 10); + } + + {for (i = 0; i < 10; i++) + test_subpixel(barbara, 2, 2, (float)i / 10, 1); + } + + {for (i = 0; i < 10; i++) + test_subpixel(barbara, 2, 2, 1, (float)i / 10); + } + + // Channels test + test_channels(barbara, 0.5f, 0.5f, 1); + test_channels(barbara, 0.5f, 0.5f, 2); + test_channels(barbara, 0.5f, 0.5f, 3); + test_channels(barbara, 0.5f, 0.5f, 4); + + test_channels(barbara, 2, 2, 1); + test_channels(barbara, 2, 2, 2); + test_channels(barbara, 2, 2, 3); + test_channels(barbara, 2, 2, 4); + + // filter tests + resize_image(barbara, 2, 2, STBIR_FILTER_BOX , STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, "test-output/barbara-upsample-nearest.png"); + resize_image(barbara, 2, 2, STBIR_FILTER_TRIANGLE , STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, "test-output/barbara-upsample-bilinear.png"); + resize_image(barbara, 2, 2, STBIR_FILTER_CUBICBSPLINE, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, "test-output/barbara-upsample-bicubic.png"); + resize_image(barbara, 2, 2, STBIR_FILTER_CATMULLROM , STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, "test-output/barbara-upsample-catmullrom.png"); + resize_image(barbara, 2, 2, STBIR_FILTER_MITCHELL , STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, "test-output/barbara-upsample-mitchell.png"); + + resize_image(barbara, 0.5f, 0.5f, STBIR_FILTER_BOX , STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, "test-output/barbara-downsample-nearest.png"); + resize_image(barbara, 0.5f, 0.5f, STBIR_FILTER_TRIANGLE , STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, "test-output/barbara-downsample-bilinear.png"); + resize_image(barbara, 0.5f, 0.5f, STBIR_FILTER_CUBICBSPLINE, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, "test-output/barbara-downsample-bicubic.png"); + resize_image(barbara, 0.5f, 0.5f, STBIR_FILTER_CATMULLROM , STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, "test-output/barbara-downsample-catmullrom.png"); + resize_image(barbara, 0.5f, 0.5f, STBIR_FILTER_MITCHELL , STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, "test-output/barbara-downsample-mitchell.png"); + + {for (i = 10; i < 100; i++) + { + char outname[200]; + sprintf(outname, "test-output/barbara-width-%d.jpg", i); + resize_image(barbara, (float)i / 100, 1, STBIR_FILTER_CATMULLROM, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, outname); + }} + + {for (i = 110; i < 500; i += 10) + { + char outname[200]; + sprintf(outname, "test-output/barbara-width-%d.jpg", i); + resize_image(barbara, (float)i / 100, 1, STBIR_FILTER_CATMULLROM, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, outname); + }} + + {for (i = 10; i < 100; i++) + { + char outname[200]; + sprintf(outname, "test-output/barbara-height-%d.jpg", i); + resize_image(barbara, 1, (float)i / 100, STBIR_FILTER_CATMULLROM, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, outname); + }} + + {for (i = 110; i < 500; i += 10) + { + char outname[200]; + sprintf(outname, "test-output/barbara-height-%d.jpg", i); + resize_image(barbara, 1, (float)i / 100, STBIR_FILTER_CATMULLROM, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, outname); + }} + + {for (i = 50; i < 200; i += 10) + { + char outname[200]; + sprintf(outname, "test-output/barbara-width-height-%d.jpg", i); + resize_image(barbara, 100 / (float)i, (float)i / 100, STBIR_FILTER_CATMULLROM, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB, outname); + }} + + test_format(barbara, 0.5, 2.0, STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB); + test_format(barbara, 0.5, 2.0, STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR); + test_format(barbara, 2.0, 0.5, STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB); + test_format(barbara, 2.0, 0.5, STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR); + + test_format(barbara, 0.5, 2.0, STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB); + test_format(barbara, 0.5, 2.0, STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR); + test_format(barbara, 2.0, 0.5, STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB); + test_format(barbara, 2.0, 0.5, STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR); + + test_float(barbara, 0.5, 2.0, STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB); + test_float(barbara, 0.5, 2.0, STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR); + test_float(barbara, 2.0, 0.5, STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB); + test_float(barbara, 2.0, 0.5, STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR); + + // Edge behavior tests + resize_image("hgradient.png", 2, 2, STBIR_FILTER_CATMULLROM, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR, "test-output/hgradient-clamp.png"); + resize_image("hgradient.png", 2, 2, STBIR_FILTER_CATMULLROM, STBIR_EDGE_WRAP, STBIR_COLORSPACE_LINEAR, "test-output/hgradient-wrap.png"); + + resize_image("vgradient.png", 2, 2, STBIR_FILTER_CATMULLROM, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR, "test-output/vgradient-clamp.png"); + resize_image("vgradient.png", 2, 2, STBIR_FILTER_CATMULLROM, STBIR_EDGE_WRAP, STBIR_COLORSPACE_LINEAR, "test-output/vgradient-wrap.png"); + + resize_image("1px-border.png", 2, 2, STBIR_FILTER_CATMULLROM, STBIR_EDGE_REFLECT, STBIR_COLORSPACE_LINEAR, "test-output/1px-border-reflect.png"); + resize_image("1px-border.png", 2, 2, STBIR_FILTER_CATMULLROM, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR, "test-output/1px-border-clamp.png"); + + // sRGB tests + resize_image("gamma_colors.jpg", .5f, .5f, STBIR_FILTER_CATMULLROM, STBIR_EDGE_REFLECT, STBIR_COLORSPACE_SRGB, "test-output/gamma_colors.jpg"); + resize_image("gamma_2.2.jpg", .5f, .5f, STBIR_FILTER_CATMULLROM, STBIR_EDGE_REFLECT, STBIR_COLORSPACE_SRGB, "test-output/gamma_2.2.jpg"); + resize_image("gamma_dalai_lama_gray.jpg", .5f, .5f, STBIR_FILTER_CATMULLROM, STBIR_EDGE_REFLECT, STBIR_COLORSPACE_SRGB, "test-output/gamma_dalai_lama_gray.jpg"); +} +#endif +void test_suite(int argc, char **argv) +{ +} diff --git a/source/engine/thirdparty/stb/tests/resample_test_c.c b/source/engine/thirdparty/stb/tests/resample_test_c.c new file mode 100644 index 0000000..e7e3531 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/resample_test_c.c @@ -0,0 +1,8 @@ +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#define STB_IMAGE_RESIZE_STATIC +#include "stb_image_resize.h" + +// Just to make sure it will build properly with a c compiler + +int main() { +} diff --git a/source/engine/thirdparty/stb/tests/resize.dsp b/source/engine/thirdparty/stb/tests/resize.dsp new file mode 100644 index 0000000..cfb9608 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/resize.dsp @@ -0,0 +1,94 @@ +# Microsoft Developer Studio Project File - Name="resize" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=resize - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "resize.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "resize.mak" CFG="resize - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "resize - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "resize - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "resize - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /G6 /W3 /GX /Z7 /O2 /I ".." /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 + +!ELSEIF "$(CFG)" == "resize - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /WX /Gm /GX /ZI /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "resize - Win32 Release" +# Name "resize - Win32 Debug" +# Begin Source File + +SOURCE=.\resample_test.cpp +# End Source File +# Begin Source File + +SOURCE=..\stb_image_resize2.h +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/stb/tests/sdf/sdf_test.c b/source/engine/thirdparty/stb/tests/sdf/sdf_test.c new file mode 100644 index 0000000..d5b0ca0 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/sdf/sdf_test.c @@ -0,0 +1,152 @@ +#define STB_DEFINE +#include "stb.h" + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +// used both to compute SDF and in 'shader' +float sdf_size = 32.0; // the larger this is, the better large font sizes look +float pixel_dist_scale = 64.0; // trades off precision w/ ability to handle *smaller* sizes +int onedge_value = 128; +int padding = 3; // not used in shader + +typedef struct +{ + float advance; + signed char xoff; + signed char yoff; + unsigned char w,h; + unsigned char *data; +} fontchar; + +fontchar fdata[128]; + +#define BITMAP_W 1200 +#define BITMAP_H 800 +unsigned char bitmap[BITMAP_H][BITMAP_W][3]; + +char *sample = "This is goofy text, size %d!"; +char *small_sample = "This is goofy text, size %d! Really needs in-shader supersampling to look good."; + +void blend_pixel(int x, int y, int color, float alpha) +{ + int i; + for (i=0; i < 3; ++i) + bitmap[y][x][i] = (unsigned char) (stb_lerp(alpha, bitmap[y][x][i], color)+0.5); // round +} + +void draw_char(float px, float py, char c, float relative_scale) +{ + int x,y; + fontchar *fc = &fdata[c]; + float fx0 = px + fc->xoff*relative_scale; + float fy0 = py + fc->yoff*relative_scale; + float fx1 = fx0 + fc->w*relative_scale; + float fy1 = fy0 + fc->h*relative_scale; + int ix0 = (int) floor(fx0); + int iy0 = (int) floor(fy0); + int ix1 = (int) ceil(fx1); + int iy1 = (int) ceil(fy1); + // clamp to viewport + if (ix0 < 0) ix0 = 0; + if (iy0 < 0) iy0 = 0; + if (ix1 > BITMAP_W) ix1 = BITMAP_W; + if (iy1 > BITMAP_H) iy1 = BITMAP_H; + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float sdf_dist, pix_dist; + float bmx = stb_linear_remap(x, fx0, fx1, 0, fc->w); + float bmy = stb_linear_remap(y, fy0, fy1, 0, fc->h); + int v00,v01,v10,v11; + float v0,v1,v; + int sx0 = (int) bmx; + int sx1 = sx0+1; + int sy0 = (int) bmy; + int sy1 = sy0+1; + // compute lerp weights + bmx = bmx - sx0; + bmy = bmy - sy0; + // clamp to edge + sx0 = stb_clamp(sx0, 0, fc->w-1); + sx1 = stb_clamp(sx1, 0, fc->w-1); + sy0 = stb_clamp(sy0, 0, fc->h-1); + sy1 = stb_clamp(sy1, 0, fc->h-1); + // bilinear texture sample + v00 = fc->data[sy0*fc->w+sx0]; + v01 = fc->data[sy0*fc->w+sx1]; + v10 = fc->data[sy1*fc->w+sx0]; + v11 = fc->data[sy1*fc->w+sx1]; + v0 = stb_lerp(bmx,v00,v01); + v1 = stb_lerp(bmx,v10,v11); + v = stb_lerp(bmy,v0 ,v1 ); + #if 0 + // non-anti-aliased + if (v > onedge_value) + blend_pixel(x,y,0,1.0); + #else + // Following math can be greatly simplified + + // convert distance in SDF value to distance in SDF bitmap + sdf_dist = stb_linear_remap(v, onedge_value, onedge_value+pixel_dist_scale, 0, 1); + // convert distance in SDF bitmap to distance in output bitmap + pix_dist = sdf_dist * relative_scale; + // anti-alias by mapping 1/2 pixel around contour from 0..1 alpha + v = stb_linear_remap(pix_dist, -0.5f, 0.5f, 0, 1); + if (v > 1) v = 1; + if (v > 0) + blend_pixel(x,y,0,v); + #endif + } + } +} + + +void print_text(float x, float y, char *text, float scale) +{ + int i; + for (i=0; text[i]; ++i) { + if (fdata[text[i]].data) + draw_char(x,y,text[i],scale); + x += fdata[text[i]].advance * scale; + } +} + +int main(int argc, char **argv) +{ + int ch; + float scale, ypos; + stbtt_fontinfo font; + void *data = stb_file("c:/windows/fonts/times.ttf", NULL); + stbtt_InitFont(&font, data, 0); + + scale = stbtt_ScaleForPixelHeight(&font, sdf_size); + + for (ch=32; ch < 127; ++ch) { + fontchar fc; + int xoff,yoff,w,h, advance; + fc.data = stbtt_GetCodepointSDF(&font, scale, ch, padding, onedge_value, pixel_dist_scale, &w, &h, &xoff, &yoff); + fc.xoff = xoff; + fc.yoff = yoff; + fc.w = w; + fc.h = h; + stbtt_GetCodepointHMetrics(&font, ch, &advance, NULL); + fc.advance = advance * scale; + fdata[ch] = fc; + } + + ypos = 60; + memset(bitmap, 255, sizeof(bitmap)); + print_text(400, ypos+30, stb_sprintf("sdf bitmap height %d", (int) sdf_size), 30/sdf_size); + ypos += 80; + for (scale = 8.0; scale < 120.0; scale *= 1.33f) { + print_text(80, ypos+scale, stb_sprintf(scale == 8.0 ? small_sample : sample, (int) scale), scale / sdf_size); + ypos += scale*1.05f + 20; + } + + stbi_write_png("sdf_test.png", BITMAP_W, BITMAP_H, 3, bitmap, 0); + return 0; +} diff --git a/source/engine/thirdparty/stb/tests/sdf/sdf_test_arial_16.png b/source/engine/thirdparty/stb/tests/sdf/sdf_test_arial_16.png new file mode 100644 index 0000000000000000000000000000000000000000..3d2bc1e487517ebd6b7544c29076ff0305b9e139 GIT binary patch literal 121269 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/sdf/sdf_test_times_16.png b/source/engine/thirdparty/stb/tests/sdf/sdf_test_times_16.png new file mode 100644 index 0000000000000000000000000000000000000000..c76e7b96c472f224702555dafdf7cd042fca7ff5 GIT binary patch literal 108371 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/sdf/sdf_test_times_50.png b/source/engine/thirdparty/stb/tests/sdf/sdf_test_times_50.png new file mode 100644 index 0000000000000000000000000000000000000000..bf4974f009fefb6ab03639c7cf7f18390312a055 GIT binary patch literal 104962 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 literal 0 HcmV?d00001 diff --git a/source/engine/thirdparty/stb/tests/stb.c b/source/engine/thirdparty/stb/tests/stb.c new file mode 100644 index 0000000..ee6941f --- /dev/null +++ b/source/engine/thirdparty/stb/tests/stb.c @@ -0,0 +1,3330 @@ +/* + * Unit tests for "stb.h" + */ + +#define _CRT_SECURE_NO_WARNINGS +//#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +//#define STB_FASTMALLOC +#ifdef _DEBUG +#define STB_MALLOC_WRAPPER_DEBUG +#endif +#ifndef _M_AMD64 +#define STB_NPTR +#endif +#define STB_DEFINE +#include "stb.h" + +//#include "stb_file.h" +//#include "stb_pixel32.h" + +//#define DEBUG_BLOCK +#ifdef DEBUG_BLOCK +#include +#endif + +#ifdef STB_FASTMALLOC +#error "can't use FASTMALLOC with threads" +#endif + +int count; +void c(int truth, char *error) +{ + if (!truth) { + fprintf(stderr, "Test failed: %s\n", error); + ++count; + } +} + + +#if 0 +void show(void) +{ + #ifdef _WIN32 + SYSTEM_INFO x; + GetSystemInfo(&x); + printf("%d\n", x.dwPageSize); + #endif +} +#endif + +void test_classes(void) +{ + unsigned char size_base[32], size_shift[32]; + int class_to_pages[256]; + int class_to_size[256], cl; + int lg, size, wasted_pages; + int kAlignShift = 3; + int kAlignment = 1 << kAlignShift; + int kMaxSize = 8 * 4096; + int kPageShift = 12; + int kPageSize = (1 << kPageShift); + int next_class = 1; + int alignshift = kAlignShift; + int last_lg = -1; + + for (lg = 0; lg < kAlignShift; lg++) { + size_base[lg] = 1; + size_shift[lg] = kAlignShift; + } + + for (size = kAlignment; size <= kMaxSize; size += (1 << alignshift)) { + int lg = stb_log2_floor(size); + if (lg > last_lg) { + // Increase alignment every so often. + // + // Since we double the alignment every time size doubles and + // size >= 128, this means that space wasted due to alignment is + // at most 16/128 i.e., 12.5%. Plus we cap the alignment at 256 + // bytes, so the space wasted as a percentage starts falling for + // sizes > 2K. + if ((lg >= 7) && (alignshift < 8)) { + alignshift++; + } + size_base[lg] = next_class - ((size-1) >> alignshift); + size_shift[lg] = alignshift; + } + + class_to_size[next_class] = size; + last_lg = lg; + + next_class++; + } + + // Initialize the number of pages we should allocate to split into + // small objects for a given class. + wasted_pages = 0; + for (cl = 1; cl < next_class; cl++) { + // Allocate enough pages so leftover is less than 1/8 of total. + // This bounds wasted space to at most 12.5%. + size_t psize = kPageSize; + const size_t s = class_to_size[cl]; + while ((psize % s) > (psize >> 3)) { + psize += kPageSize; + } + class_to_pages[cl] = (int) (psize >> kPageShift); + wasted_pages += (int) psize; + } + + printf("TCMalloc can waste as much as %d memory on one-shot allocations\n", wasted_pages); + + + return; +} + +#ifdef STB_STUA +void test_script(void) +{ + stua_run_script( + "var g = (2+3)*5 + 3*(2+1) + ((7)); \n" + "func sprint(x) _print(x) _print(' ') x end;\n" + "func foo(y) var q = func(x) sprint(x) end; q end;\n " + "var z=foo(5); z(77);\n" + "func counter(z) func(x) z=z+1 end end\n" + "var q=counter(0), p=counter(5);\n" + "sprint(q()) sprint(p()) sprint(q()) sprint(p()) sprint(q()) sprint(p())\n" + "var x=2222;\n" + "if 1 == 2 then 3333 else 4444 end; => x; sprint(x);\n" + "var x1 = sprint(1.5e3); \n" + "var x2 = sprint(.5); \n" + "var x3 = sprint(1.); \n" + "var x4 = sprint(1.e3); \n" + "var x5 = sprint(1e3); \n" + "var x6 = sprint(0.5e3); \n" + "var x7 = sprint(.5e3); \n" + " func sum(x,y) x+y end \n" + " func sumfunc(a) sum+{x=a} end \n" + " var q = sumfunc(3) \n" + " var p = sumfunc(20) \n" + " var d = sprint(q(5)) - sprint(q(8)) \n" + " var e = sprint(p(5)) - sprint(p(8)) \n" + " func test3(x) \n" + " sprint(x) \n" + " x = x+3 \n" + " sprint(x) \n" + " x+5 \n" + " end \n" + " var y = test3(4); \n" + " func fib(x) \n" + " if x < 3 then \n" + " 1 \n" + " else \n" + " fib(x-1) + fib(x-2); \n" + " end \n" + " end \n" + " \n" + " func fib2(x) \n" + " var a=1 \n" + " var b=1 \n" + " sprint(a) \n" + " sprint(b) \n" + " while x > 2 do \n" + " var c=a+b \n" + " a=b \n" + " b=c \n" + " sprint(b) \n" + " x=x-1 \n" + " end \n" + " b \n" + " end \n" + " \n" + " func assign(z) \n" + " var y = { 'this', 'is', 'a', 'lame', 'day', 'to', 'die'} \n" + " y[3] = z \n" + " var i = 0 \n" + " while y[i] != nil do \n" + " sprint(y[i]) \n" + " i = i+1 \n" + " end \n" + " end \n" + " \n" + " sprint(fib(12)); \n" + " assign(\"good\"); \n" + " fib2(20); \n" + " sprint('ok'); \n" + " sprint(-5); \n" + " // final comment with no newline" + ); +} +#endif + +#ifdef STB_THREADS +extern void __stdcall Sleep(unsigned long); + +void * thread_1(void *x) +{ + Sleep(80); + printf("thread 1\n"); fflush(stdout); + return (void *) 2; +} + +void * thread_2(void *y) +{ + stb_work(thread_1, NULL, y); + Sleep(50); + printf("thread 2\n"); fflush(stdout); + return (void *) 3; +} + +stb_semaphore stest; +stb_mutex mutex; +volatile int tc1, tc2; + +void *thread_3(void *p) +{ + stb_mutex_begin(mutex); + ++tc1; + stb_mutex_end(mutex); + stb_sem_waitfor(stest); + stb_mutex_begin(mutex); + ++tc2; + stb_mutex_end(mutex); + return NULL; +} + +void test_threads(void) +{ + volatile int a=0,b=0; + //stb_work_numthreads(2); + stb_work(thread_2, (void *) &a, (void *) &b); + while (a==0 || b==0) { + Sleep(10); + //printf("a=%d b=%d\n", a, b); + } + c(a==2 && b == 3, "stb_thread"); + stb_work_numthreads(4); + stest = stb_sem_new(8); + mutex = stb_mutex_new(); + stb_work(thread_3, NULL, NULL); + stb_work(thread_3, NULL, NULL); + stb_work(thread_3, NULL, NULL); + stb_work(thread_3, NULL, NULL); + stb_work(thread_3, NULL, NULL); + stb_work(thread_3, NULL, NULL); + stb_work(thread_3, NULL, NULL); + stb_work(thread_3, NULL, NULL); + while (tc1 < 4) + Sleep(10); + c(tc1 == 4, "stb_work 1"); + stb_sem_release(stest); + stb_sem_release(stest); + stb_sem_release(stest); + stb_sem_release(stest); + stb_sem_release(stest); + stb_sem_release(stest); + stb_sem_release(stest); + stb_sem_release(stest); + Sleep(40); + while (tc1 != 8 || tc2 != 8) + Sleep(10); + c(tc1 == 8 && tc2 == 8, "stb_work 2"); + stb_work_numthreads(2); + stb_work(thread_3, NULL, NULL); + stb_work(thread_3, NULL, NULL); + stb_work(thread_3, NULL, NULL); + stb_work(thread_3, NULL, NULL); + while (tc1 < 10) + Sleep(10); + c(tc1 == 10, "stb_work 1"); + stb_sem_release(stest); + stb_sem_release(stest); + stb_sem_release(stest); + stb_sem_release(stest); + + Sleep(100); + stb_sem_delete(stest); + stb_mutex_delete(mutex); +} +#else +void test_threads(void) +{ +} +#endif + +void *thread4(void *p) +{ + return NULL; +} + +#ifdef STB_THREADS +stb_threadqueue *tq; +stb_sync synch; +stb_mutex msum; + +volatile int thread_sum; + +void *consume1(void *p) +{ + volatile int *q = (volatile int *) p; + for(;;) { + int z; + stb_threadq_get_block(tq, &z); + stb_mutex_begin(msum); + thread_sum += z; + *q += z; + stb_mutex_end(msum); + stb_sync_reach(synch); + } +} + +void test_threads2(void) +{ + int array[256],i,n=0; + volatile int which[4]; + synch = stb_sync_new(); + stb_sync_set_target(synch,2); + stb_work_reach(thread4, NULL, NULL, synch); + stb_sync_reach_and_wait(synch); + printf("ok\n"); + + tq = stb_threadq_new(4, 1, TRUE,TRUE); + msum = stb_mutex_new(); + thread_sum = 0; + stb_sync_set_target(synch, 65); + for (i=0; i < 4; ++i) { + which[i] = 0; + stb_create_thread(consume1, (int *) &which[i]); + } + for (i=1; i <= 64; ++i) { + array[i] = i; + n += i; + stb_threadq_add_block(tq, &array[i]); + } + stb_sync_reach_and_wait(synch); + stb_barrier(); + c(thread_sum == n, "stb_threadq 1"); + c(which[0] + which[1] + which[2] + which[3] == n, "stb_threadq 2"); + printf("(Distribution: %d %d %d %d)\n", which[0], which[1], which[2], which[3]); + + stb_sync_delete(synch); + stb_threadq_delete(tq); + stb_mutex_delete(msum); +} +#else +void test_threads2(void) +{ +} +#endif + +char tc[] = "testing compression test quick test voila woohoo what the hell"; + +unsigned char storage1[1 << 23]; +int test_compression(unsigned char *buffer, int length) +{ + unsigned char *storage2; + int c_len = stb_compress(storage1, buffer, length); + int dc_len; + printf("Compressed %d to %d\n", length, c_len); + dc_len = stb_decompress_length(storage1); + storage2 = malloc(dc_len); + dc_len = stb_decompress(storage2, storage1, c_len); + if (dc_len != length) { free(storage2); return -1; } + if (memcmp(buffer, storage2, length) != 0) { free(storage2); return -1; } + free(storage2); + return c_len; +} + +#if 0 +int test_en_compression(char *buffer, int length) +{ + int c_len = stb_en_compress(storage1, buffer, length); + int dc_len; + printf("Encompressed %d to %d\n", length, c_len); + dc_len = stb_en_decompress(storage2, storage1, c_len); + if (dc_len != length) return -1; + if (memcmp(buffer, storage2, length) != 0) return -1; + return c_len; +} +#endif + +#define STR_x "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#define STR_y "yyyyyyyyyyyyyyyyyy" + +#define STR_xy STR_x STR_y +#define STR_xyyxy STR_xy STR_y STR_xy + +#define STR_1 "testing" +#define STR_2 STR_xyyxy STR_xy STR_xyyxy STR_xyyxy STR_xy STR_xyyxy +#define STR_3 "buh" + +char buffer[] = STR_1 "\r\n" STR_2 STR_2 STR_2 "\n" STR_3; +char str1[] = STR_1; +char str2[] = STR_2 STR_2 STR_2; +char str3[] = STR_3; + +int sum(short *s) +{ + int i,total=0; + for (i=0; i < stb_arr_len(s); ++i) + total += s[i]; + return total; +} + +stb_uint stb_adler32_old(stb_uint adler32, stb_uchar *buffer, stb_uint buflen) +{ + const stb_uint ADLER_MOD = 65521; + stb_uint s1 = adler32 & 0xffff; + stb_uint s2 = adler32 >> 16; + + while (buflen-- > 0) { // NOTE: much faster implementations are possible! + s1 += *buffer++; if (s1 > ADLER_MOD) s1 -= ADLER_MOD; + s2 += s1 ; if (s2 > ADLER_MOD) s2 -= ADLER_MOD; + } + return (s2 << 16) + s1; +} + +int sample_test[3][5] = +{ + { 1,2,3,4,5 }, + { 6,7,8,9,10, }, + { 11,12,13,14,15 }, +}; + +typedef struct { unsigned short x,y,z; } struct1; +typedef struct { double a; int x,y,z; } struct2; + +char *args_raw[] = { "foo", "-dxrf", "bar", "-ts" }; +char *args[8]; + +void do_compressor(int,char**); +void test_sha1(void); + +int alloc_num, alloc_size; +void dumpfunc(void *ptr, int sz, char *file, int line) +{ + printf("%p (%6d) -- %3d:%s\n", ptr, sz, line, file); + alloc_size += sz; + alloc_num += 1; +} + +char *expects(stb_matcher *m, char *s, int result, int len, char *str) +{ + int res2,len2=0; + res2 = stb_lex(m, s, &len2); + c(result == res2 && len == len2, str); + return s + len; +} + +void test_lex(void) +{ + stb_matcher *m = stb_lex_matcher(); + // tok_en5 .3 20.1 20. .20 .1 + char *s = "tok_en5.3 20.1 20. .20.1"; + + stb_lex_item(m, "[a-zA-Z_][a-zA-Z0-9_]*", 1 ); + stb_lex_item(m, "[0-9]*\\.?[0-9]*" , 2 ); + stb_lex_item(m, "[\r\n\t ]+" , 3 ); + stb_lex_item(m, "." , -99 ); + s=expects(m,s,1,7, "stb_lex 1"); + s=expects(m,s,2,2, "stb_lex 2"); + s=expects(m,s,3,1, "stb_lex 3"); + s=expects(m,s,2,4, "stb_lex 4"); + s=expects(m,s,3,1, "stb_lex 5"); + s=expects(m,s,2,3, "stb_lex 6"); + s=expects(m,s,3,1, "stb_lex 7"); + s=expects(m,s,2,3, "stb_lex 8"); + s=expects(m,s,2,2, "stb_lex 9"); + s=expects(m,s,0,0, "stb_lex 10"); + stb_matcher_free(m); +} + +typedef struct Btest +{ + struct Btest stb_bst_fields(btest_); + int v; +} Btest; + +stb_bst(Btest, btest_, BT2,bt2,v, int, a - b) + +void bst_test(void) +{ + Btest *root = NULL, *t; + int items[500], sorted[500]; + int i,j,z; + for (z=0; z < 10; ++z) { + for (i=0; i < 500; ++i) + items[i] = stb_rand() & 0xfffffff; + + // check for collisions, and retrry if so + memcpy(sorted, items, sizeof(sorted)); + qsort(sorted, 500, sizeof(sorted[0]), stb_intcmp(0)); + for (i=1; i < 500; ++i) + if (sorted[i-1] == sorted[i]) + break; + if (i != 500) { --z; break; } + + for (i=0; i < 500; ++i) { + t = malloc(sizeof(*t)); + t->v = items[i]; + root = btest_insert(root, t); + #ifdef STB_DEBUG + btest__validate(root,1); + #endif + for (j=0; j <= i; ++j) + c(btest_find(root, items[j]) != NULL, "stb_bst 1"); + for ( ; j < 500; ++j) + c(btest_find(root, items[j]) == NULL, "stb_bst 2"); + } + + t = btest_first(root); + for (i=0; i < 500; ++i) + t = btest_next(root,t); + c(t == NULL, "stb_bst 5"); + t = btest_last(root); + for (i=0; i < 500; ++i) + t = btest_prev(root,t); + c(t == NULL, "stb_bst 6"); + + memcpy(sorted, items, sizeof(sorted)); + qsort(sorted, 500, sizeof(sorted[0]), stb_intcmp(0)); + t = btest_first(root); + for (i=0; i < 500; ++i) { + assert(t->v == sorted[i]); + t = btest_next(root, t); + } + assert(t == NULL); + + if (z==1) + stb_reverse(items, 500, sizeof(items[0])); + else if (z) + stb_shuffle(items, 500, sizeof(items[0]), stb_rand()); + + for (i=0; i < 500; ++i) { + t = btest_find(root, items[i]); + assert(t != NULL); + root = btest_remove(root, t); + c(btest_find(root, items[i]) == NULL, "stb_bst 5"); + #ifdef STB_DEBUG + btest__validate(root, 1); + #endif + for (j=0; j <= i; ++j) + c(btest_find(root, items[j]) == NULL, "stb_bst 3"); + for ( ; j < 500; ++j) + c(btest_find(root, items[j]) != NULL, "stb_bst 4"); + free(t); + } + } +} + +extern void stu_uninit(void); + +stb_define_sort(sort_int, int, *a < *b) + +stb_rand_define(prime_rand, 1) +void test_packed_floats(void); +void test_parser_generator(void); + +void rec_print(stb_dirtree2 *d, int depth) +{ + int i; + for (i=0; i < depth; ++i) printf(" "); + printf("%s (%d)\n", d->relpath, stb_arr_len(d->files)); + for (i=0; i < stb_arr_len(d->subdirs); ++i) + rec_print(d->subdirs[i], depth+1); + d->weight = (float) stb_arr_len(d->files); +} + +#ifdef MAIN_TEST +int main(int argc, char **argv) +{ + char *z; + stb__wchar buffer7[1024],buffer9[1024]; + char buffer8[4096]; + FILE *f; + char *p1 = "foo/bar\\baz/test.xyz"; + char *p2 = "foo/.bar"; + char *p3 = "foo.bar"; + char *p4 = "foo/bar"; + char *wildcards[] = { "*foo*", "*bar", "baz", "*1*2*3*", "*/CVS/repository", "*oof*" }; + char **s; + char buf[256], *p; + int n,len2,*q,i; + stb_matcher *mt=NULL; + + if (argc > 1) { + do_compressor(argc,argv); + return 0; + } + test_classes(); + //show(); + + //stb_malloc_check_counter(2,2); + //_CrtSetBreakAlloc(10398); + + stbprint("Checking {!if} the {$fancy} print function {#works}? - should\n"); + stbprint(" - align\n"); + stbprint("But {#3this}} {one}} - shouldn't\n"); + + #if 0 + { + int i; + char **s = stb_readdir_recursive("/sean", NULL); + stb_dirtree *d = stb_dirtree_from_files_relative("", s, stb_arr_len(s)); + stb_dirtree **e; + rec_print(d, 0); + e = stb_summarize_tree(d,12,4); + for (i=0; i < stb_arr_len(e); ++i) { + printf("%s\n", e[i]->fullpath); + } + stb_arr_free(e); + + stb_fatal("foo"); + } + #endif + + stb_("Started stb.c"); + test_threads2(); + test_threads(); + + for (i=0; i < 1023 && 5+77*i < 0xd800; ++i) + buffer7[i] = 5+77*i; + buffer7[i++] = 0xd801; + buffer7[i++] = 0xdc02; + buffer7[i++] = 0xdbff; + buffer7[i++] = 0xdfff; + buffer7[i] = 0; + p = stb_to_utf8(buffer8, buffer7, sizeof(buffer8)); + c(p != NULL, "stb_to_utf8"); + if (p != NULL) { + stb_from_utf8(buffer9, buffer8, sizeof(buffer9)/2); + c(!memcmp(buffer7, buffer9, i*2), "stb_from_utf8"); + } + + z = "foo.*[bd]ak?r"; + c( stb_regex(z, "muggle man food is barfy") == 1, "stb_regex 1"); + c( stb_regex("foo.*bar", "muggle man food is farfy") == 0, "stb_regex 2"); + c( stb_regex("[^a-zA-Z]foo[^a-zA-Z]", "dfoobar xfood") == 0, "stb_regex 3"); + c( stb_regex(z, "muman foob is bakrfy") == 1, "stb_regex 4"); + z = "foo.*[bd]bk?r"; + c( stb_regex(z, "muman foob is bakrfy") == 0, "stb_regex 5"); + c( stb_regex(z, "muman foob is bbkrfy") == 1, "stb_regex 6"); + + stb_regex(NULL,NULL); + + #if 0 + test_parser_generator(); + stb_wrapper_listall(dumpfunc); + if (alloc_num) + printf("Memory still in use: %d allocations of %d bytes.\n", alloc_num, alloc_size); + #endif + + test_script(); + p = stb_file("sieve.stua", NULL); + if (p) { + stua_run_script(p); + free(p); + } + stua_uninit(); + + //stb_wrapper_listall(dumpfunc); + printf("Memory still in use: %d allocations of %d bytes.\n", alloc_num, alloc_size); + + c(stb_alloc_count_alloc == stb_alloc_count_free, "stb_alloc 0"); + + bst_test(); + + c(stb_alloc_count_alloc == stb_alloc_count_free, "stb_alloc 0"); + +#if 0 + // stb_block + { + int inuse=0, freespace=0; + int *x = malloc(10000*sizeof(*x)); + stb_block *b = stb_block_new(1, 10000); + #define BLOCK_COUNT 1000 + int *p = malloc(sizeof(*p) * BLOCK_COUNT); + int *l = malloc(sizeof(*l) * BLOCK_COUNT); + int i, n, k = 0; + + memset(x, 0, 10000 * sizeof(*x)); + + n = 0; + while (n < BLOCK_COUNT && k < 1000) { + l[n] = 16 + (rand() & 31); + p[n] = stb_block_alloc(b, l[n], 0); + if (p[n] == 0) + break; + inuse += l[n]; + + freespace = 0; + for (i=0; i < b->len; ++i) + freespace += b->freelist[i].len; + assert(freespace + inuse == 9999); + + for (i=0; i < l[n]; ++i) + x[ p[n]+i ] = p[n]; + ++n; + + if (k > 20) { + int sz; + i = (stb_rand() % n); + sz = l[i]; + stb_block_free(b, p[i], sz); + inuse -= sz; + p[i] = p[n-1]; + l[i] = l[n-1]; + --n; + + freespace = 0; + for (i=0; i < b->len; ++i) + freespace += b->freelist[i].len; + assert(freespace + inuse == 9999); + } + + + ++k; + + // validate + if ((k % 50) == 0) { + int j; + for (j=0; j < n; ++j) { + for (i=0; i < l[j]; ++i) + assert(x[ p[j]+i ] == p[j]); + } + } + + if ((k % 200) == 0) { + stb_block_compact_freelist(b); + } + } + + for (i=0; i < n; ++i) + stb_block_free(b, p[i], l[i]); + + stb_block_destroy(b); + free(p); + free(l); + free(x); + } + + blockfile_test(); +#endif + + mt = stb_lex_matcher(); + for (i=0; i < 5; ++i) + stb_lex_item_wild(mt, wildcards[i], i+1); + + c(1==stb_lex(mt, "this is a foo in the middle",NULL), "stb_matcher_match 1"); + c(0==stb_lex(mt, "this is a bar in the middle",NULL), "stb_matcher_match 2"); + c(0==stb_lex(mt, "this is a baz in the middle",NULL), "stb_matcher_match 3"); + c(2==stb_lex(mt, "this is a bar",NULL), "stb_matcher_match 4"); + c(0==stb_lex(mt, "this is a baz",NULL), "stb_matcher_match 5"); + c(3==stb_lex(mt, "baz",NULL), "stb_matcher_match 6"); + c(4==stb_lex(mt, "1_2_3_4",NULL), "stb_matcher_match 7"); + c(0==stb_lex(mt, "1 3 3 3 3 2 ",NULL), "stb_matcher_match 8"); + c(4==stb_lex(mt, "1 3 3 3 2 3 ",NULL), "stb_matcher_match 9"); + c(5==stb_lex(mt, "C:/sean/prj/old/gdmag/mipmap/hqp/adol-c/CVS/Repository",NULL), "stb_matcher_match 10"); + stb_matcher_free(mt); + + { + #define SSIZE 500000 + static int arr[SSIZE],arr2[SSIZE]; + int i,good; + for (i=0; i < SSIZE; ++i) + arr2[i] = stb_rand(); + memcpy(arr,arr2,sizeof(arr)); + printf("stb_define_sort:\n"); + sort_int(arr, SSIZE); + good = 1; + for (i=0; i+1 < SSIZE; ++i) + if (arr[i] > arr[i+1]) + good = 0; + c(good, "stb_define_sort"); + printf("qsort:\n"); + qsort(arr2, SSIZE, sizeof(arr2[0]), stb_intcmp(0)); + printf("done\n"); + // check for bugs + memset(arr, 0, sizeof(arr[0]) * 1000); + sort_int(arr, 1000); + } + + + c(stb_alloc_count_alloc == stb_alloc_count_free, "stb_alloc -2"); + + c( stb_is_prime( 2), "stb_is_prime 1"); + c( stb_is_prime( 3), "stb_is_prime 2"); + c( stb_is_prime( 5), "stb_is_prime 3"); + c( stb_is_prime( 7), "stb_is_prime 4"); + c(!stb_is_prime( 9), "stb_is_prime 5"); + c( stb_is_prime(11), "stb_is_prime 6"); + c(!stb_is_prime(25), "stb_is_prime 7"); + c(!stb_is_prime(27), "stb_is_prime 8"); + c( stb_is_prime(29), "stb_is_prime 9"); + c( stb_is_prime(31), "stb_is_prime a"); + c(!stb_is_prime(33), "stb_is_prime b"); + c(!stb_is_prime(35), "stb_is_prime c"); + c(!stb_is_prime(36), "stb_is_prime d"); + + for (n=7; n < 64; n += 3) { + int i; + stb_perfect s; + unsigned int *p = malloc(n * sizeof(*p)); + for (i=0; i < n; ++i) + p[i] = i*i; + c(stb_perfect_create(&s, p, n), "stb_perfect_hash 1"); + stb_perfect_destroy(&s); + for (i=0; i < n; ++i) + p[i] = stb_rand(); + c(stb_perfect_create(&s, p, n), "stb_perfect_hash 2"); + stb_perfect_destroy(&s); + for (i=0; i < n; ++i) + p[i] = (0x80000000 >> stb_log2_ceil(n>>1)) * i; + c(stb_perfect_create(&s, p, n), "stb_perfect_hash 2"); + stb_perfect_destroy(&s); + for (i=0; i < n; ++i) + p[i] = (int) malloc(1024); + c(stb_perfect_create(&s, p, n), "stb_perfect_hash 3"); + stb_perfect_destroy(&s); + for (i=0; i < n; ++i) + free((void *) p[i]); + free(p); + } + printf("Maximum attempts required to find perfect hash: %d\n", + stb_perfect_hash_max_failures); + + p = "abcdefghijklmnopqrstuvwxyz"; + c(stb_ischar('c', p), "stb_ischar 1"); + c(stb_ischar('x', p), "stb_ischar 2"); + c(!stb_ischar('#', p), "stb_ischar 3"); + c(!stb_ischar('X', p), "stb_ischar 4"); + p = "0123456789"; + c(!stb_ischar('c', p), "stb_ischar 5"); + c(!stb_ischar('x', p), "stb_ischar 6"); + c(!stb_ischar('#', p), "stb_ischar 7"); + c(!stb_ischar('X', p), "stb_ischar 8"); + p = "#####"; + c(!stb_ischar('c', p), "stb_ischar a"); + c(!stb_ischar('x', p), "stb_ischar b"); + c(stb_ischar('#', p), "stb_ischar c"); + c(!stb_ischar('X', p), "stb_ischar d"); + p = "xXyY"; + c(!stb_ischar('c', p), "stb_ischar e"); + c(stb_ischar('x', p), "stb_ischar f"); + c(!stb_ischar('#', p), "stb_ischar g"); + c(stb_ischar('X', p), "stb_ischar h"); + + c(stb_alloc_count_alloc == stb_alloc_count_free, "stb_alloc 1"); + + q = stb_wordwrapalloc(15, "How now brown cow. Testinglishously. Okey dokey"); + // How now brown + // cow. Testinglis + // hously. Okey + // dokey + c(stb_arr_len(q) == 8, "stb_wordwrap 8"); + c(q[2] == 14 && q[3] == 15, "stb_wordwrap 9"); + c(q[4] == 29 && q[5] == 12, "stb_wordwrap 10"); + stb_arr_free(q); + + q = stb_wordwrapalloc(20, "How now brown cow. Testinglishously. Okey dokey"); + // How now brown cow. + // Testinglishously. + // Okey dokey + c(stb_arr_len(q) == 6, "stb_wordwrap 1"); + c(q[0] == 0 && q[1] == 18, "stb_wordwrap 2"); + c(q[2] == 19 && q[3] == 17, "stb_wordwrap 3"); + c(q[4] == 37 && q[5] == 10, "stb_wordwrap 4"); + stb_arr_free(q); + + q = stb_wordwrapalloc(12, "How now brown cow. Testinglishously. Okey dokey"); + // How now + // brown cow. + // Testinglisho + // usly. Okey + // dokey + c(stb_arr_len(q) == 10, "stb_wordwrap 5"); + c(q[4] == 19 && q[5] == 12, "stb_wordwrap 6"); + c(q[6] == 31 && q[3] == 10, "stb_wordwrap 7"); + stb_arr_free(q); + + //test_script(); + + //test_packed_floats(); + + c(stb_alloc_count_alloc == stb_alloc_count_free, "stb_alloc 0"); + if (stb_alloc_count_alloc != stb_alloc_count_free) { + printf("%d allocs, %d frees\n", stb_alloc_count_alloc, stb_alloc_count_free); + } + test_lex(); + + mt = stb_regex_matcher(".*foo.*bar.*"); + c(stb_matcher_match(mt, "foobarx") == 1, "stb_matcher_match 1"); + c(stb_matcher_match(mt, "foobar") == 1, "stb_matcher_match 2"); + c(stb_matcher_match(mt, "foo bar") == 1, "stb_matcher_match 3"); + c(stb_matcher_match(mt, "fo foo ba ba bar ba") == 1, "stb_matcher_match 4"); + c(stb_matcher_match(mt, "fo oo oo ba ba bar foo") == 0, "stb_matcher_match 5"); + stb_free(mt); + + mt = stb_regex_matcher(".*foo.?bar.*"); + c(stb_matcher_match(mt, "abfoobarx") == 1, "stb_matcher_match 6"); + c(stb_matcher_match(mt, "abfoobar") == 1, "stb_matcher_match 7"); + c(stb_matcher_match(mt, "abfoo bar") == 1, "stb_matcher_match 8"); + c(stb_matcher_match(mt, "abfoo bar") == 0, "stb_matcher_match 9"); + c(stb_matcher_match(mt, "abfo foo ba ba bar ba") == 0, "stb_matcher_match 10"); + c(stb_matcher_match(mt, "abfo oo oo ba ba bar foo") == 0, "stb_matcher_match 11"); + stb_free(mt); + + mt = stb_regex_matcher(".*m((foo|bar)*baz)m.*"); + c(stb_matcher_match(mt, "abfoobarx") == 0, "stb_matcher_match 12"); + c(stb_matcher_match(mt, "a mfoofoofoobazm d") == 1, "stb_matcher_match 13"); + c(stb_matcher_match(mt, "a mfoobarbazfoom d") == 0, "stb_matcher_match 14"); + c(stb_matcher_match(mt, "a mbarbarfoobarbazm d") == 1, "stb_matcher_match 15"); + c(stb_matcher_match(mt, "a mfoobarfoo bazm d") == 0, "stb_matcher_match 16"); + c(stb_matcher_match(mt, "a mm foobarfoobarfoobar ") == 0, "stb_matcher_match 17"); + stb_free(mt); + + mt = stb_regex_matcher("f*|z"); + c(stb_matcher_match(mt, "fz") == 0, "stb_matcher_match 0a"); + c(stb_matcher_match(mt, "ff") == 1, "stb_matcher_match 0b"); + c(stb_matcher_match(mt, "z") == 1, "stb_matcher_match 0c"); + stb_free(mt); + + mt = stb_regex_matcher("m(f|z*)n"); + c(stb_matcher_match(mt, "mfzn") == 0, "stb_matcher_match 0d"); + c(stb_matcher_match(mt, "mffn") == 0, "stb_matcher_match 0e"); + c(stb_matcher_match(mt, "mzn") == 1, "stb_matcher_match 0f"); + c(stb_matcher_match(mt, "mn") == 1, "stb_matcher_match 0g"); + c(stb_matcher_match(mt, "mzfn") == 0, "stb_matcher_match 0f"); + + c(stb_matcher_find(mt, "manmanmannnnnnnmmmmmmmmm ") == 0, "stb_matcher_find 1"); + c(stb_matcher_find(mt, "manmanmannnnnnnmmmmmmmmm ") == 0, "stb_matcher_find 2"); + c(stb_matcher_find(mt, "manmanmannnnnnnmmmmmmmmmffzzz ") == 0, "stb_matcher_find 3"); + c(stb_matcher_find(mt, "manmanmannnnnnnmmmmmmmmmnfzzz ") == 1, "stb_matcher_find 4"); + c(stb_matcher_find(mt, "mmmfn aanmannnnnnnmmmmmm fzzz ") == 1, "stb_matcher_find 5"); + c(stb_matcher_find(mt, "mmmzzn anmannnnnnnmmmmmm fzzz ") == 1, "stb_matcher_find 6"); + c(stb_matcher_find(mt, "mm anmannnnnnnmmmmmm fzmzznzz ") == 1, "stb_matcher_find 7"); + c(stb_matcher_find(mt, "mm anmannnnnnnmmmmmm fzmzzfnzz ") == 0, "stb_matcher_find 8"); + c(stb_matcher_find(mt, "manmfnmannnnnnnmmmmmmmmmffzzz ") == 1, "stb_matcher_find 9"); + stb_free(mt); + + mt = stb_regex_matcher(".*m((foo|bar)*|baz)m.*"); + c(stb_matcher_match(mt, "abfoobarx") == 0, "stb_matcher_match 18"); + c(stb_matcher_match(mt, "a mfoofoofoobazm d") == 0, "stb_matcher_match 19"); + c(stb_matcher_match(mt, "a mfoobarbazfoom d") == 0, "stb_matcher_match 20"); + c(stb_matcher_match(mt, "a mbazm d") == 1, "stb_matcher_match 21"); + c(stb_matcher_match(mt, "a mfoobarfoom d") == 1, "stb_matcher_match 22"); + c(stb_matcher_match(mt, "a mm foobarfoobarfoobar ") == 1, "stb_matcher_match 23"); + stb_free(mt); + + mt = stb_regex_matcher("[a-fA-F]..[^]a-zA-Z]"); + c(stb_matcher_match(mt, "Axx1") == 1, "stb_matcher_match 24"); + c(stb_matcher_match(mt, "Fxx1") == 1, "stb_matcher_match 25"); + c(stb_matcher_match(mt, "Bxx]") == 0, "stb_matcher_match 26"); + c(stb_matcher_match(mt, "Cxxz") == 0, "stb_matcher_match 27"); + c(stb_matcher_match(mt, "gxx[") == 0, "stb_matcher_match 28"); + c(stb_matcher_match(mt, "-xx0") == 0, "stb_matcher_match 29"); + stb_free(mt); + + c(stb_wildmatch("foo*bar", "foobarx") == 0, "stb_wildmatch 0a"); + c(stb_wildmatch("foo*bar", "foobar") == 1, "stb_wildmatch 1a"); + c(stb_wildmatch("foo*bar", "foo bar") == 1, "stb_wildmatch 2a"); + c(stb_wildmatch("foo*bar", "fo foo ba ba bar ba") == 0, "stb_wildmatch 3a"); + c(stb_wildmatch("foo*bar", "fo oo oo ba ba ar foo") == 0, "stb_wildmatch 4a"); + + c(stb_wildmatch("*foo*bar*", "foobar") == 1, "stb_wildmatch 1b"); + c(stb_wildmatch("*foo*bar*", "foo bar") == 1, "stb_wildmatch 2b"); + c(stb_wildmatch("*foo*bar*", "fo foo ba ba bar ba") == 1, "stb_wildmatch 3b"); + c(stb_wildmatch("*foo*bar*", "fo oo oo ba ba ar foo") == 0, "stb_wildmatch 4b"); + + c(stb_wildmatch("foo*bar*", "foobarx") == 1, "stb_wildmatch 1c"); + c(stb_wildmatch("foo*bar*", "foobabar") == 1, "stb_wildmatch 2c"); + c(stb_wildmatch("foo*bar*", "fo foo ba ba bar ba") == 0, "stb_wildmatch 3c"); + c(stb_wildmatch("foo*bar*", "fo oo oo ba ba ar foo") == 0, "stb_wildmatch 4c"); + + c(stb_wildmatch("*foo*bar", "foobar") == 1, "stb_wildmatch 1d"); + c(stb_wildmatch("*foo*bar", "foo bar") == 1, "stb_wildmatch 2d"); + c(stb_wildmatch("*foo*bar", "fo foo ba ba bar ba") == 0, "stb_wildmatch 3d"); + c(stb_wildmatch("*foo*bar", "fo oo oo ba ba ar foo") == 0, "stb_wildmatch 4d"); + + c(stb_wildfind("foo*bar", "xyfoobarx") == 2, "stb_wildfind 0a"); + c(stb_wildfind("foo*bar", "aaafoobar") == 3, "stb_wildfind 1a"); + c(stb_wildfind("foo*bar", "foo bar") == 0, "stb_wildfind 2a"); + c(stb_wildfind("foo*bar", "fo foo ba ba bar ba") == 3, "stb_wildfind 3a"); + c(stb_wildfind("foo*bar", "fo oo oo ba ba ar foo") == -1, "stb_wildfind 4a"); + + c(stb_wildmatch("*foo*;*bar*", "foobar") == 1, "stb_wildmatch 1e"); + c(stb_wildmatch("*foo*;*bar*", "afooa") == 1, "stb_wildmatch 2e"); + c(stb_wildmatch("*foo*;*bar*", "abara") == 1, "stb_wildmatch 3e"); + c(stb_wildmatch("*foo*;*bar*", "abaza") == 0, "stb_wildmatch 4e"); + c(stb_wildmatch("*foo*;*bar*", "foboar") == 0, "stb_wildmatch 5e"); + + test_sha1(); + + n = sizeof(args_raw)/sizeof(args_raw[0]); + memcpy(args, args_raw, sizeof(args_raw)); + s = stb_getopt(&n, args); + c(n >= 1 && !strcmp(args[1], "bar" ), "stb_getopt 1"); + c(stb_arr_len(s) >= 2 && !strcmp(s[2] , "r" ), "stb_getopt 2"); + stb_getopt_free(s); + + n = sizeof(args_raw)/sizeof(args_raw[0]); + memcpy(args, args_raw, sizeof(args_raw)); + s = stb_getopt_param(&n, args, "f"); + c(stb_arr_len(s) >= 3 && !strcmp(s[3] , "fbar"), "stb_getopt 3"); + stb_getopt_free(s); + + n = sizeof(args_raw)/sizeof(args_raw[0]); + memcpy(args, args_raw, sizeof(args_raw)); + s = stb_getopt_param(&n, args, "x"); + c(stb_arr_len(s) >= 2 && !strcmp(s[1] , "xrf" ), "stb_getopt 4"); + stb_getopt_free(s); + + n = sizeof(args_raw)/sizeof(args_raw[0]); + memcpy(args, args_raw, sizeof(args_raw)); + s = stb_getopt_param(&n, args, "s"); + c(s == NULL && n == 0 , "stb_getopt 5"); + stb_getopt_free(s); + +#if 0 + c(*stb_csample_int(sample_test[0], 1, 5, 5, 3, -1, -1) == 1, "stb_csample_int 1"); + c(*stb_csample_int(sample_test[0], 1, 5, 5, 3, 1, -3) == 2, "stb_csample_int 2"); + c(*stb_csample_int(sample_test[0], 1, 5, 5, 3, 12, -2) == 5, "stb_csample_int 3"); + c(*stb_csample_int(sample_test[0], 1, 5, 5, 3, 15, 1) == 10, "stb_csample_int 4"); + c(*stb_csample_int(sample_test[0], 1, 5, 5, 3, 5, 4) == 15, "stb_csample_int 5"); + c(*stb_csample_int(sample_test[0], 1, 5, 5, 3, 3, 3) == 14, "stb_csample_int 6"); + c(*stb_csample_int(sample_test[0], 1, 5, 5, 3, -2, 5) == 11, "stb_csample_int 7"); + c(*stb_csample_int(sample_test[0], 1, 5, 5, 3, -7, 0) == 1, "stb_csample_int 8"); + c(*stb_csample_int(sample_test[0], 1, 5, 5, 3, 2, 1) == 8, "stb_csample_int 9"); +#endif + + c(!strcmp(stb_splitpath(buf, p1, STB_PATH ), "foo/bar\\baz/"), "stb_splitpath 1"); + c(!strcmp(stb_splitpath(buf, p1, STB_FILE ), "test"), "stb_splitpath 2"); + c(!strcmp(stb_splitpath(buf, p1, STB_EXT ), ".xyz"), "stb_splitpath 3"); + c(!strcmp(stb_splitpath(buf, p1, STB_PATH_FILE ), "foo/bar\\baz/test"), "stb_splitpath 4"); + c(!strcmp(stb_splitpath(buf, p1, STB_FILE_EXT ), "test.xyz"), "stb_splitpath 5"); + + c(!strcmp(stb_splitpath(buf, p2, STB_PATH ), "foo/"), "stb_splitpath 6"); + c(!strcmp(stb_splitpath(buf, p2, STB_FILE ), ""), "stb_splitpath 7"); + c(!strcmp(stb_splitpath(buf, p2, STB_EXT ), ".bar"), "stb_splitpath 8"); + c(!strcmp(stb_splitpath(buf, p2, STB_PATH_FILE ), "foo/"), "stb_splitpath 9"); + c(!strcmp(stb_splitpath(buf, p2, STB_FILE_EXT ), ".bar"), "stb_splitpath 10"); + + c(!strcmp(stb_splitpath(buf, p3, STB_PATH ), "./"), "stb_splitpath 11"); + c(!strcmp(stb_splitpath(buf, p3, STB_FILE ), "foo"), "stb_splitpath 12"); + c(!strcmp(stb_splitpath(buf, p3, STB_EXT ), ".bar"), "stb_splitpath 13"); + c(!strcmp(stb_splitpath(buf, p3, STB_PATH_FILE ), "foo"), "stb_splitpath 14"); + + c(!strcmp(stb_splitpath(buf, p4, STB_PATH ), "foo/"), "stb_splitpath 16"); + c(!strcmp(stb_splitpath(buf, p4, STB_FILE ), "bar"), "stb_splitpath 17"); + c(!strcmp(stb_splitpath(buf, p4, STB_EXT ), ""), "stb_splitpath 18"); + c(!strcmp(stb_splitpath(buf, p4, STB_PATH_FILE ), "foo/bar"), "stb_splitpath 19"); + c(!strcmp(stb_splitpath(buf, p4, STB_FILE_EXT ), "bar"), "stb_splitpath 20"); + + c(!strcmp(p=stb_dupreplace("testfootffooo foo fox", "foo", "brap"), "testbraptfbrapo brap fox"), "stb_dupreplace 1"); free(p); + c(!strcmp(p=stb_dupreplace("testfootffooo foo fox", "foo", "" ), "testtfo fox" ), "stb_dupreplace 2"); free(p); + c(!strcmp(p=stb_dupreplace("abacab", "a", "aba"), "abababacabab" ), "stb_dupreplace 3"); free(p); + + +#if 0 + m = stb_mml_parse("xy<&f>"); + c(m != NULL, "stb_mml_parse 1"); + if (m) { + c(!strcmp(m->child[0]->child[0]->child[1]->tag, "d"), "stb_mml_parse 2"); + c(!strcmp(m->child[0]->child[1]->leaf_data, "<&f>"), "stb_mml_parse 3"); + } + if (m) + stb_mml_free(m); + c(stb_alloc_count_alloc == stb_alloc_count_free, "stb_alloc 1"); + if (stb_alloc_count_alloc != stb_alloc_count_free) { + printf("%d allocs, %d frees\n", stb_alloc_count_alloc, stb_alloc_count_free); + } +#endif + + c(stb_linear_remap(3.0f,0,8,1,2) == 1.375, "stb_linear_remap()"); + + c(stb_bitreverse(0x1248fec8) == 0x137f1248, "stb_bitreverse() 1"); + c(stb_bitreverse8(0x4e) == 0x72, "stb_bitreverse8() 1"); + c(stb_bitreverse8(0x31) == 0x8c, "stb_bitreverse8() 2"); + for (n=1; n < 255; ++n) { + unsigned int m = stb_bitreverse8((uint8) n); + c(stb_bitreverse8((uint8) m) == (unsigned int) n, "stb_bitreverse8() 3"); + } + + for (n=2; n <= 31; ++n) { + c(stb_is_pow2 ((1 << n) ) == 1 , "stb_is_pow2() 1"); + c(stb_is_pow2 ((1 << n)+1) == 0 , "stb_is_pow2() 2"); + c(stb_is_pow2 ((1 << n)-1) == 0 , "stb_is_pow2() 3"); + + c(stb_log2_floor((1 << n) ) == n , "stb_log2_floor() 1"); + c(stb_log2_floor((1 << n)+1) == n , "stb_log2_floor() 2"); + c(stb_log2_floor((1 << n)-1) == n-1, "stb_log2_floor() 3"); + + c(stb_log2_ceil ((1 << n) ) == n , "stb_log2_ceil() 1"); + c(stb_log2_ceil ((1 << n)+1) == n+1, "stb_log2_ceil() 2"); + c(stb_log2_ceil ((1 << n)-1) == n , "stb_log2_ceil() 3"); + + c(stb_bitreverse(1 << n) == 1U << (31-n), "stb_bitreverse() 2"); + } + + c(stb_log2_floor(0) == -1, "stb_log2_floor() 4"); + c(stb_log2_ceil (0) == -1, "stb_log2_ceil () 4"); + + c(stb_log2_floor(-1) == 31, "stb_log2_floor() 5"); + c(stb_log2_ceil (-1) == 32, "stb_log2_ceil () 5"); + + c(stb_bitcount(0xffffffff) == 32, "stb_bitcount() 1"); + c(stb_bitcount(0xaaaaaaaa) == 16, "stb_bitcount() 2"); + c(stb_bitcount(0x55555555) == 16, "stb_bitcount() 3"); + c(stb_bitcount(0x00000000) == 0, "stb_bitcount() 4"); + + c(stb_lowbit8(0xf0) == 4, "stb_lowbit8 1"); + c(stb_lowbit8(0x10) == 4, "stb_lowbit8 2"); + c(stb_lowbit8(0xf3) == 0, "stb_lowbit8 3"); + c(stb_lowbit8(0xf8) == 3, "stb_lowbit8 4"); + c(stb_lowbit8(0x60) == 5, "stb_lowbit8 5"); + + for (n=0; n < sizeof(buf); ++n) + buf[n] = 0; + + for (n = 0; n < 200000; ++n) { + unsigned int k = stb_rand(); + int i,z=0; + for (i=0; i < 32; ++i) + if (k & (1 << i)) ++z; + c(stb_bitcount(k) == z, "stb_bitcount() 5"); + + buf[k >> 24] = 1; + + if (k != 0) { + if (stb_is_pow2(k)) { + c(stb_log2_floor(k) == stb_log2_ceil(k), "stb_is_pow2() 1"); + c(k == 1U << stb_log2_floor(k), "stb_is_pow2() 2"); + } else { + c(stb_log2_floor(k) == stb_log2_ceil(k)-1, "stb_is_pow2() 3"); + } + } + + c(stb_bitreverse(stb_bitreverse(n)) == (uint32) n, "stb_bitreverse() 3"); + } + + // make sure reasonable coverage from stb_rand() + for (n=0; n < sizeof(buf); ++n) + c(buf[n] != 0, "stb_rand()"); + + for (n=0; n < sizeof(buf); ++n) + buf[n] = 0; + + for (n=0; n < 60000; ++n) { + float z = (float) stb_frand(); + int n = (int) (z * sizeof(buf)); + c(z >= 0 && z < 1, "stb_frand() 1"); + c(n >= 0 && n < sizeof(buf), "stb_frand() 2"); + buf[n] = 1; + } + + // make sure reasonable coverage from stb_frand(), + // e.g. that the range remap isn't incorrect + for (n=0; n < sizeof(buf); ++n) + c(buf[n] != 0, "stb_frand()"); + + + // stb_arr + { + short *s = NULL; + + c(sum(s) == 0, "stb_arr 1"); + + stb_arr_add(s); s[0] = 3; + stb_arr_push(s,7); + + c( stb_arr_valid(s,1), "stb_arr 2"); + c(!stb_arr_valid(s,2), "stb_arr 3"); + + // force a realloc + stb_arr_push(s,0); + stb_arr_push(s,0); + stb_arr_push(s,0); + stb_arr_push(s,0); + + c(sum(s) == 10, "stb_arr 4"); + stb_arr_push(s,0); + s[0] = 1; s[1] = 5; s[2] = 20; + c(sum(s) == 26, "stb_arr 5"); + stb_arr_setlen(s,2); + c(sum(s) == 6, "stb_arr 6"); + stb_arr_setlen(s,1); + c(sum(s) == 1, "stb_arr 7"); + stb_arr_setlen(s,0); + c(sum(s) == 0, "stb_arr 8"); + + stb_arr_push(s,3); + stb_arr_push(s,4); + stb_arr_push(s,5); + stb_arr_push(s,6); + stb_arr_push(s,7); + stb_arr_deleten(s,1,3); + c(stb_arr_len(s)==2 && sum(s) == 10, "stb_arr_9"); + + stb_arr_push(s,2); + // 3 7 2 + stb_arr_insertn(s,2,2); + // 3 7 x x 2 + s[2] = 5; + s[3] = 6; + c(s[0]==3 && s[1] == 7 && s[2] == 5 && s[3] == 6 && s[4] == 2, "stb_arr 10"); + stb_arr_free(s); + } + + #if 1 + f= stb_fopen("data/stb.test", "wb"); + fwrite(buffer, 1, sizeof(buffer)-1, f); + stb_fclose(f, stb_keep_yes); + #ifndef WIN32 + sleep(1); // andLinux has some synchronization problem here + #endif + #else + f= fopen("data/stb.test", "wb"); + fwrite(buffer, 1, sizeof(buffer)-1, f); + fclose(f); + #endif + if (!stb_fexists("data/stb.test")) { + fprintf(stderr, "Error: couldn't open file just written, or stb_fexists() is broken.\n"); + } + + f = fopen("data/stb.test", "rb"); + // f = NULL; // test stb_fatal() + if (!f) { stb_fatal("Error: couldn't open file just written\n"); } + else { + char temp[4]; + int len1 = stb_filelen(f), len2; + int n1,n2; + if (fread(temp,1,4,f) == 0) { + int n = ferror(f); + if (n) { stb_fatal("Error reading from stream: %d", n); } + if (feof(f)) stb_fatal("Weird, read 0 bytes and hit eof"); + stb_fatal("Read 0, but neither feof nor ferror is true"); + } + fclose(f); + p = stb_file("data/stb.test", &len2); + if (p == NULL) stb_fatal("Error: stb_file() failed"); + c(len1 == sizeof(buffer)-1, "stb_filelen()"); + c(len2 == sizeof(buffer)-1, "stb_file():n"); + c(memcmp(p, buffer, sizeof(buffer)-1) == 0, "stb_file()"); + c(strcmp(p, buffer)==0, "stb_file() terminated"); + free(p); + + s = stb_stringfile("data/stb.test", &n1); + c(n1 == 3, "stb_stringfile():n"); + n2 = 0; + while (s[n2]) ++n2; + c(n1 == n2, "stb_stringfile():n length matches the non-NULL strings"); + if (n2 == 3) { + c(strcmp(s[0],str1)==0, "stb_stringfile()[0]"); + c(strcmp(s[1],str2)==0, "stb_stringfile()[1]"); + c(strcmp(s[2],str3)==0, "stb_stringfile()[2] (no terminating newlines)"); + } + free(s); + + f = fopen("data/stb.test", "rb"); + stb_fgets(buf, sizeof(buf), f); + //c(strcmp(buf, str1)==0, "stb_fgets()"); + p = stb_fgets_malloc(f); + n1 = strlen(p); + n2 = strlen(str2); + c(strcmp(p, str2)==0, "stb_fgets_malloc()"); + free(p); + stb_fgets(buf, sizeof(buf), f); + c(strcmp(buf, str3)==0, "stb_fgets()3"); + } + + c( stb_prefix("foobar", "foo"), "stb_prefix() 1"); + c(!stb_prefix("foo", "foobar"), "stb_prefix() 2"); + c( stb_prefix("foob", "foob" ), "stb_prefix() 3"); + + stb_strncpy(buf, "foobar", 6); c(strcmp(buf,"fooba" )==0, "stb_strncpy() 1"); + stb_strncpy(buf, "foobar", 8); c(strcmp(buf,"foobar")==0, "stb_strncpy() 2"); + + c(!strcmp(p=stb_duplower("FooBar"), "foobar"), "stb_duplower()"); free(p); + strcpy(buf, "FooBar"); + stb_tolower(buf); + c(!strcmp(buf, "foobar"), "stb_tolower()"); + + p = stb_strtok(buf, "foo=ba*r", "#=*"); + c(!strcmp(buf, "foo" ), "stb_strtok() 1"); + c(!strcmp(p , "ba*r"), "stb_strtok() 2"); + p = stb_strtok(buf, "foobar", "#=*"); + c(*p == 0, "stb_strtok() 3"); + + c(!strcmp(stb_skipwhite(" \t\n foo"), "foo"), "stb_skipwhite()"); + + s = stb_tokens("foo == ba*r", "#=*", NULL); + c(!strcmp(s[0], "foo "), "stb_tokens() 1"); + c(!strcmp(s[1], " ba"), "stb_tokens() 2"); + c(!strcmp(s[2], "r"), "stb_tokens() 3"); + c(s[3] == 0, "stb_tokens() 4"); + free(s); + + s = stb_tokens_allowempty("foo == ba*r", "#=*", NULL); + c(!strcmp(s[0], "foo "), "stb_tokens_allowempty() 1"); + c(!strcmp(s[1], "" ), "stb_tokens_allowempty() 2"); + c(!strcmp(s[2], " ba"), "stb_tokens_allowempty() 3"); + c(!strcmp(s[3], "r"), "stb_tokens_allowempty() 4"); + c(s[4] == 0, "stb_tokens_allowempty() 5"); + free(s); + + s = stb_tokens_stripwhite("foo == ba*r", "#=*", NULL); + c(!strcmp(s[0], "foo"), "stb_tokens_stripwhite() 1"); + c(!strcmp(s[1], "" ), "stb_tokens_stripwhite() 2"); + c(!strcmp(s[2], "ba"), "stb_tokens_stripwhite() 3"); + c(!strcmp(s[3], "r"), "stb_tokens_stripwhite() 4"); + c(s[4] == 0, "stb_tokens_stripwhite() 5"); + free(s); + + s = stb_tokens_quoted("foo =\"=\" ba*\"\"r \" foo\" bah ", "#=*", NULL); + c(!strcmp(s[0], "foo"), "stb_tokens_quoted() 1"); + c(!strcmp(s[1], "= ba"), "stb_tokens_quoted() 2"); + c(!strcmp(s[2], "\"r foo bah"), "stb_tokens_quoted() 3"); + c(s[3] == 0, "stb_tokens_quoted() 4"); + free(s); + + + p = stb_file("stb.h", &len2); + if (p) { + uint32 z = stb_adler32_old(1, p, len2); + uint32 x = stb_adler32 (1, p, len2); + c(z == x, "stb_adler32() 1"); + memset(p,0xff,len2); + z = stb_adler32_old((65520<<16) + 65520, p, len2); + x = stb_adler32 ((65520<<16) + 65520, p, len2); + c(z == x, "stb_adler32() 2"); + free(p); + } + + // stb_hheap + { + #define HHEAP_COUNT 100000 + void **p = malloc(sizeof(*p) * HHEAP_COUNT); + int i, j; + #if 0 + stb_hheap *h2, *h = stb_newhheap(sizeof(struct1),0); + + for (i=0; i < HHEAP_COUNT; ++i) + p[i] = stb_halloc(h); + stb_shuffle(p, HHEAP_COUNT, sizeof(*p), stb_rand()); + for (i=0; i < HHEAP_COUNT; ++i) + stb_hfree(p[i]); + + c(h->num_alloc == 0, "stb_hheap 1"); + stb_delhheap(h); + + h = stb_newhheap(sizeof(struct1),0); + h2 = stb_newhheap(sizeof(struct2),8); + + for (i=0; i < HHEAP_COUNT; ++i) { + if (i & 1) + p[i] = stb_halloc(h); + else { + p[i] = stb_halloc(h2); + c((((int) p[i]) & 4) == 0, "stb_hheap 2"); + } + } + + stb_shuffle(p, HHEAP_COUNT, sizeof(*p), stb_rand()); + for (i=0; i < HHEAP_COUNT; ++i) + stb_hfree(p[i]); + + c(h->num_alloc == 0, "stb_hheap 3"); + c(h2->num_alloc == 0, "stb_hheap 4"); + + stb_delhheap(h); + stb_delhheap(h2); + #else + for (i=0; i < HHEAP_COUNT; ++i) + p[i] = malloc(32); + stb_shuffle(p, HHEAP_COUNT, sizeof(*p), stb_rand()); + for (i=0; i < HHEAP_COUNT; ++i) + free(p[i]); + #endif + + // now use the same array of pointers to do pointer set operations + for (j=100; j < HHEAP_COUNT; j += 25000) { + stb_ps *ps = NULL; + for (i=0; i < j; ++i) + ps = stb_ps_add(ps, p[i]); + + for (i=0; i < HHEAP_COUNT; ++i) + c(stb_ps_find(ps, p[i]) == (i < j), "stb_ps 1"); + c(stb_ps_count(ps) == j, "stb_ps 1b"); + + for (i=j; i < HHEAP_COUNT; ++i) + ps = stb_ps_add(ps, p[i]); + + for (i=0; i < j; ++i) + ps = stb_ps_remove(ps, p[i]); + + for (i=0; i < HHEAP_COUNT; ++i) + c(stb_ps_find(ps, p[i]) == !(i < j), "stb_ps 2"); + + stb_ps_delete(ps); + } + + #define HHEAP_COUNT2 100 + // now use the same array of pointers to do pointer set operations + for (j=1; j < 40; ++j) { + stb_ps *ps = NULL; + for (i=0; i < j; ++i) + ps = stb_ps_add(ps, p[i]); + + for (i=0; i < HHEAP_COUNT2; ++i) + c(stb_ps_find(ps, p[i]) == (i < j), "stb_ps 3"); + c(stb_ps_count(ps) == j, "stb_ps 3b"); + + for (i=j; i < HHEAP_COUNT2; ++i) + ps = stb_ps_add(ps, p[i]); + + for (i=0; i < j; ++i) + ps = stb_ps_remove(ps, p[i]); + + for (i=0; i < HHEAP_COUNT2; ++i) + c(stb_ps_find(ps, p[i]) == !(i < j), "stb_ps 4"); + + stb_ps_delete(ps); + } + + free(p); + } + + + n = test_compression(tc, sizeof(tc)); + c(n >= 0, "stb_compress()/stb_decompress() 1"); + + p = stb_file("stb.h", &len2); + if (p) { + FILE *f = fopen("data/stb_h.z", "wb"); + if (stb_compress_stream_start(f)) { + int i; + void *q; + int len3; + + for (i=0; i < len2; ) { + int n = stb_rand() % 10; + if (n <= 6) n = 1 + stb_rand()%16; + else if (n <= 8) n = 20 + stb_rand() % 1000; + else n = 15000; + if (i + n > len2) n = len2 - i; + stb_write(p + i, n); + i += n; + } + stb_compress_stream_end(1); + + q = stb_decompress_fromfile("data/stb_h.z", &len3); + c(len3 == len2, "stb_compress_stream 2"); + if (len2 == len3) + c(!memcmp(p,q,len2), "stb_compress_stream 3"); + if (q) free(q); + } else { + c(0, "stb_compress_stream 1"); + } + + free(p); + stb_compress_window(65536*4); + } + + p = stb_file("stb.h", &len2); + if (p) { + n = test_compression(p, len2); + c(n >= 0, "stb_compress()/stb_decompress() 2"); + #if 0 + n = test_en_compression(p, len2); + c(n >= 0, "stb_en_compress()/stb_en_decompress() 2"); + #endif + free(p); + } else { + fprintf(stderr, "No stb.h to compression test.\n"); + } + + p = stb_file("data/test.bmp", &len2); + if (p) { + n = test_compression(p, len2); + c(n == 106141, "stb_compress()/stb_decompress() 4"); + #if 0 + n = test_en_compression(p, len2); + c(n >= 0, "stb_en_compress()/stb_en_decompress() 4"); + #endif + free(p); + } + + // the hardcoded compressed lengths being verified _could_ + // change if you changed the compressor parameters; but pure + // performance optimizations shouldn't change them + p = stb_file("data/cantrbry.zip", &len2); + if (p) { + n = test_compression(p, len2); + c(n == 642787, "stb_compress()/stb_decompress() 3"); + #if 0 + n = test_en_compression(p, len2); + c(n >= 0, "stb_en_compress()/stb_en_decompress() 3"); + #endif + free(p); + } + + p = stb_file("data/bible.txt", &len2); + if (p) { + n = test_compression(p, len2); + c(n == 2022520, "stb_compress()/stb_decompress() 4"); + #if 0 + n = test_en_compression(p, len2); + c(n >= 0, "stb_en_compress()/stb_en_decompress() 4"); + #endif + free(p); + } + + { + int len = 1 << 25, o=0; // 32MB + char *buffer = malloc(len); + int i; + for (i=0; i < 8192; ++i) + buffer[o++] = (char) stb_rand(); + for (i=0; i < (1 << 15); ++i) + buffer[o++] = 1; + for (i=0; i < 64; ++i) + buffer[o++] = buffer[i]; + for (i=0; i < (1 << 21); ++i) + buffer[o++] = 2; + for (i=0; i < 64; ++i) + buffer[o++] = buffer[i]; + for (i=0; i < (1 << 21); ++i) + buffer[o++] = 3; + for (i=0; i < 8192; ++i) + buffer[o++] = buffer[i]; + for (i=0; i < (1 << 21); ++i) + buffer[o++] = 4; + assert(o < len); + stb_compress_window(1 << 24); + i = test_compression(buffer, len); + c(n >= 0, "stb_compress() 6"); + free(buffer); + } + + #ifdef STB_THREADS + stb_thread_cleanup(); + #endif + stb_ischar(0,NULL); + stb_wrapper_listall(dumpfunc); + printf("Memory still in use: %d allocations of %d bytes.\n", alloc_num, alloc_size); + + // force some memory checking + for (n=1; n < 20; ++n) + malloc(1 << n); + + printf("Finished stb.c with %d errors.\n", count); + + #ifdef _MSC_VER + if (count) + __asm int 3; + #endif + + return 0; +} + +#endif + + + + + +// NIST test vectors + +struct +{ + int length; + char *message; + char *digest; +} sha1_tests[] = +{ +{ 24, +"616263", +"a9993e364706816aba3e25717850c26c9cd0d89d", +},{ + 1304, +"ec29561244ede706b6eb30a1c371d74450a105c3f9735f7fa9fe38cf67f304a5736a106e" +"92e17139a6813b1c81a4f3d3fb9546ab4296fa9f722826c066869edacd73b25480351858" +"13e22634a9da44000d95a281ff9f264ecce0a931222162d021cca28db5f3c2aa24945ab1" +"e31cb413ae29810fd794cad5dfaf29ec43cb38d198fe4ae1da2359780221405bd6712a53" +"05da4b1b737fce7cd21c0eb7728d08235a9011", +"970111c4e77bcc88cc20459c02b69b4aa8f58217", +},{ + 2096, +"5fc2c3f6a7e79dc94be526e5166a238899d54927ce470018fbfd668fd9dd97cbf64e2c91" +"584d01da63be3cc9fdff8adfefc3ac728e1e335b9cdc87f069172e323d094b47fa1e652a" +"fe4d6aa147a9f46fda33cacb65f3aa12234746b9007a8c85fe982afed7815221e43dba55" +"3d8fe8a022cdac1b99eeeea359e5a9d2e72e382dffa6d19f359f4f27dc3434cd27daeeda" +"8e38594873398678065fbb23665aba9309d946135da0e4a4afdadff14db18e85e71dd93c" +"3bf9faf7f25c8194c4269b1ee3d9934097ab990025d9c3aaf63d5109f52335dd3959d38a" +"e485050e4bbb6235574fc0102be8f7a306d6e8de6ba6becf80f37415b57f9898a5824e77" +"414197422be3d36a6080", + "0423dc76a8791107d14e13f5265b343f24cc0f19", +},{ + 2888, +"0f865f46a8f3aed2da18482aa09a8f390dc9da07d51d1bd10fe0bf5f3928d5927d08733d" +"32075535a6d1c8ac1b2dc6ba0f2f633dc1af68e3f0fa3d85e6c60cb7b56c239dc1519a00" +"7ea536a07b518ecca02a6c31b46b76f021620ef3fc6976804018380e5ab9c558ebfc5cb1" +"c9ed2d974722bf8ab6398f1f2b82fa5083f85c16a5767a3a07271d67743f00850ce8ec42" +"8c7f22f1cf01f99895c0c844845b06a06cecb0c6cf83eb55a1d4ebc44c2c13f6f7aa5e0e" +"08abfd84e7864279057abc471ee4a45dbbb5774afa24e51791a0eada11093b88681fe30b" +"aa3b2e94113dc63342c51ca5d1a6096d0897b626e42cb91761058008f746f35465465540" +"ad8c6b8b60f7e1461b3ce9e6529625984cb8c7d46f07f735be067588a0117f23e34ff578" +"00e2bbe9a1605fde6087fb15d22c5d3ac47566b8c448b0cee40373e5ba6eaa21abee7136" +"6afbb27dbbd300477d70c371e7b8963812f5ed4fb784fb2f3bd1d3afe883cdd47ef32bea" +"ea", + "6692a71d73e00f27df976bc56df4970650d90e45", +},{ + 3680, +"4893f1c763625f2c6ce53aacf28026f14b3cd8687e1a1d3b60a81e80fcd1e2b038f9145a" +"b64a0718f948f7c3c9ac92e3d86fb669a5257da1a18c776291653688338210a3242120f1" +"01788e8acc9110db9258b1554bf3d26602516ea93606a25a7f566c0c758fb39ecd9d876b" +"c5d8abc1c3205095382c2474cb1f8bbdb45c2c0e659cb0fc703ec607a5de6bcc7a28687d" +"b1ee1c8f34797bb2441d5706d210df8c2d7d65dbded36414d063c117b52a51f7a4eb9cac" +"0782e008b47459ed5acac0bc1f20121087f992ad985511b33c866d18e63f585478ee5a5e" +"654b19d81231d98683ae3f0533565aba43dce408d7e3c4c6be11d8f05165f29c9dcb2030" +"c4ee31d3a04e7421aa92c3231a1fc07e50e95fea7389a5e65891afaba51cf55e36a9d089" +"bf293accb356d5d06547307d6e41456d4ed146a056179971c56521c83109bf922866186e" +"184a99a96c7bb96df8937e35970e438412a2b8d744cf2ad87cb605d4232e976f9f151697" +"76e4e5b6b786132c966b25fc56d815c56c819af5e159aa39f8a93d38115f5580cda93bc0" +"73c30b39920e726fe861b72483a3f886269ab7a8eefe952f35d25c4eb7f443f4f3f26e43" +"d51fb54591e6a6dad25fcdf5142033084e5624bdd51435e77dea86b8", + "dc5859dd5163c4354d5d577b855fa98e37f04384", +},{ + 4472, +"cf494c18a4e17bf03910631471bca5ba7edea8b9a63381e3463517961749848eb03abefd" +"4ce676dece3740860255f57c261a558aa9c7f11432f549a9e4ce31d8e17c79450ce2ccfc" +"148ad904aedfb138219d7052088520495355dadd90f72e6f69f9c6176d3d45f113f275b7" +"fbc2a295784d41384cd7d629b23d1459a22e45fd5097ec9bf65fa965d3555ec77367903c" +"32141065fc24da5c56963d46a2da3c279e4035fb2fb1c0025d9dda5b9e3443d457d92401" +"a0d3f58b48469ecb1862dc975cdbe75ca099526db8b0329b03928206f084c633c04eef5e" +"8e377f118d30edf592504be9d2802651ec78aeb02aea167a03fc3e23e5fc907c324f283f" +"89ab37e84687a9c74ccf055402db95c29ba2c8d79b2bd4fa96459f8e3b78e07e923b8119" +"8267492196ecb71e01c331f8df245ec5bdf8d0e05c91e63bb299f0f6324895304dda721d" +"39410458f117c87b7dd6a0ee734b79fcbe482b2c9e9aa0cef03a39d4b0c86de3bc34b4aa" +"dabfa373fd2258f7c40c187744d237080762382f547a36adb117839ca72f8ebbc5a20a07" +"e86f4c8bb923f5787698d278f6db0040e76e54645bb0f97083995b34b9aa445fc4244550" +"58795828dd00c32471ec402a307f5aa1b37b1a86d6dae3bcbfbe9ba41cab0beeabf489af" +"0073d4b3837d3f14b815120bc3602d072b5aeefcdec655fe756b660eba7dcf34675acbce" +"317746270599424b9248791a0780449c1eabbb9459cc1e588bfd74df9b1b711c85c09d8a" +"a171b309281947e8f4b6ac438753158f4f36fa", + "4c17926feb6e87f5bca7890d8a5cde744f231dab", +},{ + 5264, +"8236153781bd2f1b81ffe0def1beb46f5a70191142926651503f1b3bb1016acdb9e7f7ac" +"ced8dd168226f118ff664a01a8800116fd023587bfba52a2558393476f5fc69ce9c65001" +"f23e70476d2cc81c97ea19caeb194e224339bcb23f77a83feac5096f9b3090c51a6ee6d2" +"04b735aa71d7e996d380b80822e4dfd43683af9c7442498cacbea64842dfda238cb09992" +"7c6efae07fdf7b23a4e4456e0152b24853fe0d5de4179974b2b9d4a1cdbefcbc01d8d311" +"b5dda059136176ea698ab82acf20dd490be47130b1235cb48f8a6710473cfc923e222d94" +"b582f9ae36d4ca2a32d141b8e8cc36638845fbc499bce17698c3fecae2572dbbd4705524" +"30d7ef30c238c2124478f1f780483839b4fb73d63a9460206824a5b6b65315b21e3c2f24" +"c97ee7c0e78faad3df549c7ca8ef241876d9aafe9a309f6da352bec2caaa92ee8dca3928" +"99ba67dfed90aef33d41fc2494b765cb3e2422c8e595dabbfaca217757453fb322a13203" +"f425f6073a9903e2dc5818ee1da737afc345f0057744e3a56e1681c949eb12273a3bfc20" +"699e423b96e44bd1ff62e50a848a890809bfe1611c6787d3d741103308f849a790f9c015" +"098286dbacfc34c1718b2c2b77e32194a75dda37954a320fa68764027852855a7e5b5274" +"eb1e2cbcd27161d98b59ad245822015f48af82a45c0ed59be94f9af03d9736048570d6e3" +"ef63b1770bc98dfb77de84b1bb1708d872b625d9ab9b06c18e5dbbf34399391f0f8aa26e" +"c0dac7ff4cb8ec97b52bcb942fa6db2385dcd1b3b9d567aaeb425d567b0ebe267235651a" +"1ed9bf78fd93d3c1dd077fe340bb04b00529c58f45124b717c168d07e9826e33376988bc" +"5cf62845c2009980a4dfa69fbc7e5a0b1bb20a5958ca967aec68eb31dd8fccca9afcd30a" +"26bab26279f1bf6724ff", + "11863b483809ef88413ca9b0084ac4a5390640af", +},{ + 6056, +"31ec3c3636618c7141441294fde7e72366a407fa7ec6a64a41a7c8dfda150ca417fac868" +"1b3c5be253e3bff3ab7a5e2c01b72790d95ee09b5362be835b4d33bd20e307c3c702aa15" +"60cdc97d190a1f98b1c78e9230446e31d60d25155167f73e33ed20cea27b2010514b57ba" +"b05ed16f601e6388ea41f714b0f0241d2429022e37623c11156f66dd0fa59131d8401dba" +"f502cffb6f1d234dcb53e4243b5cf9951688821586a524848123a06afa76ab8058bcfa72" +"27a09ce30d7e8cb100c8877bb7a81b615ee6010b8e0daced7cc922c971940b757a9107de" +"60b8454dda3452e902092e7e06faa57c20aadc43c8012b9d28d12a8cd0ba0f47ab4b377f" +"316902e6dff5e4f2e4a9b9de1e4359f344e66d0565bd814091e15a25d67d89cf6e30407b" +"36b2654762bbe53a6f204b855a3f9108109e351825cf9080c89764c5f74fb4afef89d804" +"e7f7d097fd89d98171d63eaf11bd719df44c5a606be0efea358e058af2c265b2da2623fd" +"afc62b70f0711d0150625b55672060cea6a253c590b7db1427a536d8a51085756d1e6ada" +"41d9d506b5d51bcae41249d16123b7df7190e056777a70feaf7d9f051fdbbe45cbd60fc6" +"295dda84d4ebbd7284ad44be3ee3ba57c8883ead603519b8ad434e3bf630734a9243c00a" +"a07366b8f88621ec6176111f0418c66b20ff9a93009f43432aaea899dad0f4e3ae72e9ab" +"a3f678f140118eb7117230c357a5caa0fe36c4e6cf1957bbe7499f9a68b0f1536e476e53" +"457ed826d9dea53a6ded52e69052faaa4d3927b9a3f9e8b435f424b941bf2d9cd6849874" +"42a44d5acaa0da6d9f390d1a0dd6c19af427f8bb7c082ae405a8dd535dea76aa360b4faa" +"d786093e113424bb75b8cc66c41af637a7b2acdca048a501417919cf9c5cd3b2fa668860" +"d08b6717eea6f125fa1b0bae1dbb52aafce8ae2deaf92aeb5be003fb9c09fedbc286ffb5" +"e16ad8e07e725faa46ebc35500cf205fc03250075ddc050c263814b8d16d141db4ca289f" +"386719b28a09a8e5934722202beb3429899b016dfeb972fee487cdd8d18f8a681042624f" +"51", + "f43937922444421042f76756fbed0338b354516f", +},{ + 6848, +"21b9a9686ec200456c414f2e6963e2d59e8b57e654eced3d4b57fe565b51c9045c697566" +"44c953178f0a64a6e44d1b46f58763c6a71ce4c373b0821c0b3927a64159c32125ec916b" +"6edd9bf41c3d80725b9675d6a97c8a7e3b662fac9dbcf6379a319a805b5341a8d360fe00" +"5a5c9ac1976094fea43566d66d220aee5901bd1f2d98036b2d27eb36843e94b2e5d1f09c" +"738ec826de6e0034cf8b1dca873104c5c33704cae290177d491d65f307c50a69b5c81936" +"a050e1fe2b4a6f296e73549323b6a885c3b54ee5eca67aa90660719126b590163203909e" +"470608f157f033f017bcf48518bf17d63380dabe2bc9ac7d8efe34aedcae957aeb68f10c" +"8ad02c4465f1f2b029d5fbb8e8538d18be294394b54b0ee6e67a79fce11731604f3ac4f8" +"d6ffa9ef3d2081f3d1c99ca107a7bf3d624324a7978ec38af0bcd0d7ee568572328b212b" +"9dc831efb7880e3f4d6ca7e25f8e80d73913fb8edfffd758ae4df61b4140634a92f49314" +"6138ebdcdaa083ea72d52a601230aa6f77874dcad9479f5bcac3763662cc30cb99823c5f" +"f469dcbd64c028286b0e579580fd3a17b56b099b97bf62d555798f7a250e08b0e4f238c3" +"fcf684198bd48a68c208a6268be2bb416eda3011b523388bce8357b7f26122640420461a" +"bcabcb5004519adfa2d43db718bce7d0c8f1b4645c89315c65df1f0842e5741244bba3b5" +"10801d2a446818635d0e8ffcd80c8a6f97ca9f878793b91780ee18eb6c2b99ffac3c38ef" +"b7c6d3af0478317c2b9c421247eba8209ea677f984e2398c7c243696a12df2164417f602" +"d7a1d33809c865b73397550ff33fe116166ae0ddbccd00e2b6fc538733830ac39c328018" +"bcb87ac52474ad3cce8780d6002e14c6734f814cb551632bcc31965c1cd23d048b9509a4" +"e22ab88f76a6dba209d5dd2febd1413a64d32be8574a22341f2a14e4bd879abb35627ef1" +"35c37be0f80843006a7cc7158d2bb2a71bf536b36de20ca09bb5b674e5c408485106e6fa" +"966e4f2139779b46f6010051615b5b41cda12d206d48e436b9f75d7e1398a656abb0087a" +"a0eb453368fc1ecc71a31846080f804d7b67ad6a7aa48579c3a1435eff7577f4e6004d46" +"aac4130293f6f62ae6d50c0d0c3b9876f0728923a94843785966a27555dd3ce68602e7d9" +"0f7c7c552f9bda4969ec2dc3e30a70620db6300e822a93e633ab9a7a", + "5d4d18b24b877092188a44be4f2e80ab1d41e795", +},{ + 7640, +"1c87f48f4409c3682e2cf34c63286dd52701b6c14e08669851a6dc8fa15530ad3bef692c" +"7d2bf02238644561069df19bdec3bccae5311fce877afc58c7628d08d32d9bd2dc1df0a6" +"24360e505944219d211f33bff62e9ff2342ac86070240a420ccaf14908e6a93c1b27b6e2" +"0324e522199e83692805cc4c7f3ea66f45a490a50d4dd558aa8e052c45c1a5dfad452674" +"edc7149024c09024913f004ceee90577ff3eaec96a1eebbdc98b440ffeb0cad9c6224efc" +"9267d2c192b53dc012fb53010926e362ef9d4238d00df9399f6cbb9acc389a7418007a6c" +"a926c59359e3608b548bdeece213f4e581d02d273781dffe26905ec161956f6dfe1c008d" +"6da8165d08f8062eea88e80c055b499f6ff8204ffdb303ab132d9b0cba1e5675f3525bbe" +"4cf2c3f2b00506f58336b36aefd865d37827f2fad7d1e59105b52f1596ea19f848037dfe" +"dc9136e824ead5505e2995d4c0769276548835430667f333fc77375125b29c1b1535602c" +"10fe161864f49a98fc274ae7335a736be6bf0a98cd019d120b87881103f86c0a6efadd8c" +"aa405b6855c384141b4f8751cc42dc0cb2913382210baaa84fe242ca66679472d815c08b" +"f3d1a7c6b5705a3de17ad157522de1eb90c568a8a1fbcbb422cca293967bb14bfdd91bc5" +"a9c4d2774dee524057e08f937f3e2bd8a04ced0fc7b16fb78a7b16ee9c6447d99e53d846" +"3726c59066af25c317fc5c01f5dc9125809e63a55f1cd7bdf7f995ca3c2655f4c7ab940f" +"2aa48bc3808961eb48b3a03c731ce627bb67dd0037206c5f2c442fc72704258548c6a9db" +"e16da45e40da009dc8b2600347620eff8361346116b550087cb9e2ba6b1d6753622e8b22" +"85589b90a8e93902aa17530104455699a1829efef153327639b2ae722d5680fec035575c" +"3b48d9ec8c8e9550e15338cc76b203f3ab597c805a8c6482676deabc997a1e4ba857a889" +"97ceba32431443c53d4d662dd5532aa177b373c93bf93122b72ed7a3189e0fa171dfabf0" +"520edf4b9d5caef595c9a3a13830c190cf84bcf9c3596aadb2a674fbc2c951d135cb7525" +"3ee6c59313444f48440a381e4b95f5086403beb19ff640603394931f15d36b1cc9f3924f" +"794c965d4449bfbdd8b543194335bdf70616dc986b49582019ac2bf8e68cfd71ec67e0aa" +"dff63db39e6a0ef207f74ec6108fae6b13f08a1e6ae01b813cb7ee40961f95f5be189c49" +"c43fbf5c594f5968e4e820a1d38f105f2ff7a57e747e4d059ffb1d0788b7c3c772b9bc1f" +"e147c723aca999015230d22c917730b935e902092f83e0a8e6db9a75d2626e0346e67e40" +"8d5b815439dab8ccb8ea23f828fff6916c4047", + "32e0f5d40ceec1fbe45ddd151c76c0b3fef1c938", +},{ + 8432, +"084f04f8d44b333dca539ad2f45f1d94065fbb1d86d2ccf32f9486fe98f7c64011160ec0" +"cd66c9c7478ed74fde7945b9c2a95cbe14cedea849978cc2d0c8eb0df48d4834030dfac2" +"b043e793b6094a88be76b37f836a4f833467693f1aa331b97a5bbc3dbd694d96ce19d385" +"c439b26bc16fc64919d0a5eab7ad255fbdb01fac6b2872c142a24aac69b9a20c4f2f07c9" +"923c9f0220256b479c11c90903193d4e8f9e70a9dbdf796a49ca5c12a113d00afa844694" +"de942601a93a5c2532031308ad63c0ded048633935f50a7e000e9695c1efc1e59c426080" +"a7d1e69a93982a408f1f6a4769078f82f6e2b238b548e0d4af271adfa15aa02c5d7d7052" +"6e00095ffb7b74cbee4185ab54385f2707e8362e8bd1596937026f6d95e700340b6338ce" +"ba1ee854a621ce1e17a016354016200b1f98846aa46254ab15b7a128b1e840f494b2cdc9" +"daccf14107c1e149a7fc27d33121a5cc31a4d74ea6945816a9b7a83850dc2c11d26d767e" +"ec44c74b83bfd2ef8a17c37626ed80be10262fe63cf9f804b8460c16d62ae63c8dd0d124" +"1d8aaac5f220e750cb68d8631b162d80afd6b9bf929875bf2e2bc8e2b30e05babd8336be" +"31e41842673a66a68f0c5acd4d7572d0a77970f42199a4da26a56df6aad2fe420e0d5e34" +"448eb2ed33afbfb35dffaba1bf92039df89c038bae3e11c02ea08aba5240c10ea88a45a1" +"d0a8631b269bec99a28b39a3fc5b6b5d1381f7018f15638cc5274ab8dc56a62b2e9e4fee" +"f172be20170b17ec72ff67b81c15299f165810222f6a001a281b5df1153a891206aca89e" +"e7baa761a5af7c0493a3af840b9219e358b1ec1dd301f35d4d241b71ad70337bda42f0ea" +"dc9434a93ed28f96b6ea073608a314a7272fefd69d030cf22ee6e520b848fa705ed6160f" +"e54bd3bf5e89608506e882a16aced9c3cf80657cd03749f34977ced9749caa9f52b683e6" +"4d96af371b293ef4e5053a8ea9422df9dd8be45d5574730f660e79bf4cbaa5f3c93a79b4" +"0f0e4e86e0fd999ef4f26c509b0940c7a3eaf1f87c560ad89aff43cd1b9d4863aa3ebc41" +"a3dd7e5b77372b6953dae497fc7f517efe99e553052e645e8be6a3aeb362900c75ce712d" +"fcba712c4c25583728db9a883302939655ef118d603e13fcf421d0cea0f8fb7c49224681" +"d013250defa7d4fd64b69b0b52e95142e4cc1fb6332486716a82a3b02818b25025ccd283" +"198b07c7d9e08519c3c52c655db94f423912b9dc1c95f2315e44be819477e7ff6d2e3ccd" +"daa6da27722aaadf142c2b09ce9472f7fd586f68b64d71fc653decebb4397bf7af30219f" +"25c1d496514e3c73b952b8aa57f4a2bbf7dcd4a9e0456aaeb653ca2d9fa7e2e8a532b173" +"5c4609e9c4f393dd70901393e898ed704db8e9b03b253357f333a66aba24495e7c3d1ad1" +"b5200b7892554b59532ac63af3bdef590b57bd5df4fbf38d2b3fa540fa5bf89455802963" +"036bd173fe3967ed1b7d", + "ee976e4ad3cad933b283649eff9ffdb41fcccb18", +},{ + 9224, +"bd8320703d0cac96a96aeefa3abf0f757456bf42b3e56f62070fc03e412d3b8f4e4e427b" +"c47c4600bb423b96de6b4910c20bc5c476c45feb5b429d4b35088813836fa5060ceb26db" +"bb9162e4acd683ef879a7e6a0d6549caf0f0482de8e7083d03ed2f583de1b3ef505f4b2c" +"cd8a23d86c09d47ba05093c56f21a82c815223d777d0cabb7ee4550423b5deb6690f9394" +"1862ae41590ea7a580dda79229d141a786215d75f77e74e1db9a03c9a7eb39eb35adf302" +"5e26eb31ca2d2ca507edca77d9e7cfcfd136784f2117a2afafa87fa468f08d07d720c933" +"f61820af442d260d172a0a113494ca169d33a3aeaacdcc895b356398ed85a871aba769f6" +"071abd31e9f2f5834721d0fef6f6ee0fc0e38760b6835dfcc7dbefb592e1f0c3793af7ad" +"f748786d3364f3cfd5686b1a18711af220e3637d8fad08c553ce9d5dc1183d48e8337b16" +"1fe69b50e1920316dbffec07425b5d616a805a699576590e0939f5c965bce6c7342d314a" +"c37b9c4d30166567c4f633f182de4d6b00e20a1c762789f915eaa1c89ac31b85222b1f05" +"403dedd94db9ce75ff4e49923d1999d032695fa0a1c595617830c3c9a7ab758732fcec26" +"85ae14350959b6a5f423ef726587e186b055a8daf6fa8fdefa02841b2fdbca1616dcee78" +"c685fc6dcc09f24a36097572eba3c37a3eabe98bc23836085f63ef71a54b4488615d83b2" +"6ed28c9fce78852df9b6cf8a75ca3899a7567298e91bc4ffdd04ffab0066b43b8286a4bb" +"555c78808496b252c6e0e4d153631f11f68baf88630e052acc2af5d2af2e22e4f23bb630" +"314c561a577455f86b6727bcad3c19d3e271404dec30af3d9dd0ed63cd9fa708aadfa12a" +"500ef2d99a6b71e137b56ba90036975b88004b45f577ef800f0fb3cf97577dc9da37253b" +"8675e5c8bb7e0bd26564f19eca232fb25f280f82e014424c9fbdd1411d7556e5d7906bb8" +"62206316ba03385cd820c54c82ed35b36735bc486b1885d84053eba036c1ebfb5422d93d" +"a71c53deda7f74db07cd4959cdfa898ba37080d76b564d344b124dd7b80cd70ed3b52a6c" +"f9c9a32695d134bd39eb11ddeecdac86c808e469bd8a7995b667c452e7d9a54d5c85bcf6" +"d5ffdc27d491bc06f438f02c7cf018073431587c78ba08d18a8daccb2d3b26136f612ade" +"c673f3cd5eb83412b29652d55a10d0d6238d0b5365db272c917349450aff062c36191cfc" +"d45660819083f89cd42ecae9e26934a020cafeb9b2b68d544edf59574c0ca159fd195dbf" +"3e3e74244d942fffdbd4ed7f626219bab88b5a07e50b09a832d3e8ad82091114e54f2c35" +"6b48e55e36589ebad3ac6077cb7b1827748b00670df65bbf0a2e65caad3f8a97d654d64e" +"1c7dad171cafbc37110d2f7ca66524dc08fe60593e914128bd95f41137bfe819b5ca835f" +"e5741344b5c907ce20a35f4f48726141c6398e753ed9d46d3692050628c78859d5014fe4" +"dd3708e58d4d9807f8dac540492e32fa579491717ad4145c9efc24cf95605660b2e09b89" +"9369b74d3ebff41e707917ff314d93e6ac8dfd643ef2c087cd9912005b4b2681da01a369" +"42a756a3e22123cbf38c429373c6a8663130c24b24b2690b000013960b1c46a32d1d5397" +"47", + "2df09b10933afedfcd3f2532dfd29e7cb6213859", +},{ + 10016, +"7a94978bec7f5034b12c96b86498068db28cd2726b676f54d81d8d7350804cc106bead8a" +"252b465a1f413b1c41e5697f8cece49ec0dea4dfb9fa7b1bfe7a4a00981875b420d094bb" +"1ce86c1b8c2e1dbebf819c176b926409fdec69042e324e71d7a8d75006f5a11f512811fe" +"6af88a12f450e327950b18994dfc3f740631beda6c78bca5fe23d54e6509120e05cd1842" +"d3639f1466cf26585030e5b4aefe0404fe900afc31e1980f0193579085342f1803c1ba27" +"0568f80eaf92440c4f2186b736f6ab9dc7b7522ccdcfc8cf12b6375a2d721aa89b5ef482" +"112a42c31123aebabcb485d0e72d6b6b70c44e12d2da98d1f87fa9df4f37847e1ffec823" +"1b8be3d737d282ddb9cc4b95937acfa0f028ba450def4d134a7d0fc88119bf7296e18cd4" +"4f56890b661b5b72ddfa34c29228067e13caf08eb3b7fd29de800df9a9ae137aad4a81a4" +"16a301c9f74b66c0e163e243b3187996b36eb569de3d9c007d78df91f9b554eef0eaa663" +"88754ce20460b75d95e2d0747229a1502a5652cf39ca58e1daa0e9321d7ab3093981cd70" +"23a7ee956030dd70177028a66ad619ad0629e631f91228b7c5db8e81b276d3b168c1edb1" +"bc0888d1cbcbb23245c2d8e40c1ff14bfe13f9c70e93a1939a5c45eef9351e795374b9e1" +"b5c3a7bd642477ba7233e1f590ab44a8232c53099a3c0a6ffe8be8b7ca7b58e6fedf700f" +"6f03dd7861ee1ef857e3f1a32a2e0baa591d0c7ca04cb231cc254d29cda873f00d68f465" +"00d6101cfdc2e8004c1f333d8007325d06ffe6b0ff7b80f24ba51928e65aa3cb78752028" +"27511207b089328bb60264595a2cebfc0b84d9899f5eca7ea3e1d2f0f053b4e67f975500" +"7ff3705ca4178ab9c15b29dd99494135f35befbcec05691d91f6361cad9c9a32e0e65577" +"f14d8dc66515081b51d09e3f6c25eea868cf519a83e80c935968cae6fce949a646ad53c5" +"6ee1f07dda23daef3443310bc04670afedb1a0132a04cb64fa84b4af4b3dc501044849cd" +"dd4adb8d733d1eac9c73afa4f7d75864c87787f4033ffe5ba707cbc14dd17bd1014b8b61" +"509c1f55a25cf6c0cbe49e4ddcc9e4de3fa38f7203134e4c7404ee52ef30d0b3f4e69bcc" +"7d0b2e4d8e60d9970e02cc69d537cfbc066734eb9f690a174e0194ca87a6fadad3883d91" +"6bd1700a052b26deee832701590d67e6f78938eac7c4beef3061a3474dd90dd588c1cd6e" +"6a4cda85b110fd08a30dcd85a3ebde910283366a17a100db920885600db7578be46bcfa6" +"4765ba9a8d6d5010cb1766d5a645e48365ed785e4b1d8c7c233c76291c92ef89d70bc77f" +"bf37d7ce9996367e5b13b08242ce73971f1e0c6ff2d7920fb9c821768a888a7fe0734908" +"33efb854cbf482aed5cb594fb715ec82a110130664164db488666d6198279006c1aa521f" +"9cf04250476c934eba0914fd586f62d6c5825b8cf82cd7ef915d93106c506ea6760fd8b0" +"bf39875cd1036b28417de54783173446026330ef701c3a6e5b6873b2025a2c1666bb9e41" +"a40adb4a81c1052047dabe2ad092df2ae06d6d67b87ac90be7d826ca647940c4da264cad" +"43c32a2bb8d5e27f87414e6887561444a80ed879ce91af13e0fbd6af1b5fa497ad0cbd2e" +"7f0f898f52f9e4710de2174e55ad07c45f8ead3b02cac6c811becc51e72324f2439099a0" +"5740090c1b165ecae7dec0b341d60a88f46d7ad8624aac231a90c93fad61fcfbbea12503" +"59fcd203862a6b0f1d71ac43db6c58a6b60c2c546edc12dd658998e8", + "f32e70862a16e3e8b199e9d81a9949d66f812cad", +},{ + 10808, +"88dd7f273acbe799219c23184782ac0b07bade2bc46b4f8adbd25ed3d59c0fd3e2931638" +"837d31998641bbb7374c7f03d533ca60439ac4290054ff7659cc519bdda3dff2129a7bdb" +"66b3300068931ade382b7b813c970c8e15469187d25cb04b635403dc50ea6c65ab38a97c" +"431f28a41ae81c16192bd0c103f03b8fa815d6ea5bf0aa7fa534ad413b194eb12eb74f5d" +"62b3d3a7411eb8c8b09a261542bf6880acbdfb617a42e577009e482992253712f8d4c8bd" +"1c386bad068c7aa10a22111640041f0c35dabd0de00ebf6cd82f89cbc49325df12419278" +"ec0d5ebb670577b2fe0c3e0840c5dd0dc5b3da00669eed8ead380f968b00d42f4967faec" +"c131425fce1f7edb01cbec7e96d3c26fa6390a659e0ab069ef3edadc07e077bb816f1b22" +"98830a0fe2b393693bb79f41feca89577c5230e0a6c34b860dc1fdb10d85aa054481082c" +"494779d59ba798fcd817116c3059b7831857d0364352b354ce3b960fbb61a1b8a04d47ca" +"a0ead52a9bea4bada2646cdbaec211f391dac22f2c5b8748e36bfc3d4e8ea45131ca7f52" +"af09df21babe776fcecbb5c5dfa352c790ab27b9a5e74242bbd23970368dbefd7c3c74d1" +"61ae01c7e13c65b415f38aa660f51b69ea1c9a504fe1ad31987cb9b26a4db2c37d7b326c" +"50dbc8c91b13925306ff0e6098532dee7282a99c3ddf99f9e1024301f76e31e58271870b" +"d94b9356e892a6a798d422a48c7fd5b80efe855a4925cc93b8cf27badec5498338e2b538" +"70758b45d3e7a2fa059ed88df320a65e0a7cf87fa7e63b74cea1b7371e221f8004726642" +"30d4d57945a85b23d58f248c8cd06ccfabfa969ab8cb78317451fab60e4fdfa796e2e2a8" +"b46405839a91266d37e8d38bae545fb4060c357923b86d62f5d59d7bef5af20fbb9c7fb4" +"2c6fd487748ed3b9973dbf4b1f2c9615129fa10d21cc49c622842c37c01670be71715765" +"a98814634efbdee66bf3420f284dbd3efafc8a9117a8b9a72d9b81aa53ded78c409f3f90" +"bad6e30d5229e26f4f0cea7ee82c09e3b60ec0e768f35a7fb9007b869f9bfc49c518f648" +"3c951d3b6e22505453266ec4e7fe6a80dbe6a2458a1d6cd93044f2955607412091009c7d" +"6cd81648a3b0603c92bfdff9ec3c0104b07ed2105962ca7c56ede91cb932073c337665e2" +"409387549f9a46da05bc21c5126bd4b084bc2c06ab1019c51df30581aa4464ab92978c13" +"f6d7c7ac8d30a78f982b9a43181bbe3c3eb9f7a1230b3e53b98a3c2a028317827fbe8cf6" +"ec5e3e6b2a084d517d472b25f72fab3a34415bba488f14e7f621cfa72396ba40890e8c60" +"b04815601a0819c9bebc5e18b95e04be3f9c156bd7375d8cc8a97c13ce0a3976123419fa" +"592631317ca638c1182be06886f9663d0e8e6839573df8f52219eeb5381482a6a1681a64" +"173660bfbb6d98bf06ee31e601ee99b4b99b5671ed0253260b3077ed5b977c6a79b4ff9a" +"08efd3cba5c39bec1a1e9807d40bbf0c988e0fd071cf2155ed7b014c88683cd869783a95" +"4cbfced9c0e80c3a92d45b508985cbbc533ba868c0dc4f112e99400345cf7524e42bf234" +"5a129e53da4051c429af2ef09aba33ae3c820ec1529132a203bd2b81534f2e865265f55c" +"9395caf0e0d3e1762c95eaaec935e765dc963b3e0d0a04b28373ab560fa9ba5ca71ced5d" +"17bb8b56f314f6f0d0bc8104b3f1835eca7eaac15adf912cf9a6945cfd1de392342dd596" +"d67e7ffcb7e086a6c1ea318aa2e0c2b5c2da079078232c637de0d317a1f26640bc1dac5b" +"e8699b53edc86e4bfdfaf797a2ae350bf4ea29790face675c4d2e85b8f37a694c91f6a14" +"1fd561274392ee6ee1a14424d5c134a69bcb4333079400f03615952fc4c99bf03f5733a8" +"dc71524269fc5c648371f5f3098314d9d10258", + "08632c75676571a5db5971f5d99cb8de6bf1792a", +},{ + 11600, +"85d43615942fcaa449329fd1fe9efb17545eb252cac752228f1e9d90955a3cf4e72cb116" +"3c3d8e93ccb7e4826206ff58b3e05009ee82ab70943db3f18a32925d6d5aed1525c91673" +"bd33846571af815b09bb236466807d935b5816a8be8e9becbe65d05d765bcc0bc3ae66c2" +"5320ebe9fff712aa5b4931548b76b0fd58f6be6b83554435587b1725873172e130e1a3ca" +"3d9d0425f4632d79cca0683780f266a0633230e4f3b25f87b0c390092f7b13c66ab5e31b" +"5a58dbcac8dd26a0600bf85507057bb36e870dfae76da8847875a1a52e4596d5b4b0a211" +"2435d27e1dc8dd5016d60feaf2838746d436a2983457b72e3357059b2bf1e9148bb0551a" +"e2b27d5a39abd3d1a62c36331e26668e8baabc2a1ef218b5e7a51a9ca35795bcd54f403a" +"188eafafb30d82896e45ddaea4f418629a1fb76a0f539c7114317bac1e2a8fba5a868bce" +"40abd40f6b9ced3fa8c0329b4de5ca03cc84d75b8746ef31e6c8d0a0a79b4f747690928e" +"be327f8bbe9374a0df4c39c845bf3322a49fda9455b36db5a9d6e4ea7d4326cf0e0f7cd8" +"0ff74538f95cec01a38c188d1243221e9272ccc1053e30787c4cf697043cca6fc3730d2a" +"431ecbf60d73ee667a3ab114c68d578c66dc1c659b346cb148c053980190353f6499bfef" +"acfd1d73838d6dc1188c74dd72b690fb0481eee481a3fd9af1d4233f05d5ae33a7b10d7d" +"d643406cb1f88d7dd1d77580dcbee6f757eeb2bfbcc940f2cddb820b2718264b1a64115c" +"b85909352c44b13d4e70bbb374a8594d8af7f41f65b221bf54b8d1a7f8f9c7da563550cb" +"2b062e7a7f21d5e07dd9da8d82e5a89074627597551c745718094c2eb316ca077526d27f" +"9a589c461d891dc7cd1bc20ba3f464da53c97924219c87a0f683dfb3b3ac8793c59e78ac" +"fac109439221ac599a6fd8d2754946d6bcba60784805f7958c9e34ff287ad1dbbc888848" +"fa80cc4200dbb8c5e4224535906cbffdd0237a77a906c10ced740f9c0ce7821f2dbf8c8d" +"7d41ecfcc7dfdc0846b98c78b765d01fb1eb15ff39149ab592e5dd1152665304bba85bbf" +"4705751985aaaf31245361554d561a2337e3daeef58a826492fd886d5f18ef568c1e772e" +"f6461170407695e3254eb7bf0c683811ddde5960140d959114998f08bdb24a104095987d" +"3255d590e0dbd41ae32b1ae4f4ea4a4f011de1388034231e034756870c9f2d9f23788723" +"27055a7de2b5e931dfb53e7780b6d4294bf094e08567025b026db9203b681565a1d52f30" +"318d0ebe49471b22ba5fd62e1ed6c8966c99b853c9062246a1ace51ef7523c7bf93bef53" +"d8a9cb96d6a04f0da1eca888df66e0380a72525a7ecc6115d08569a66248f6ba34e2341b" +"fd01a78f7b3c1cfe0754e0d26cba2fa3f951ef14d5749ff8933b8aba06fa40fb570b467c" +"54ce0d3f0bed21e998e5a36b3bc2f9e1ae29c4bab59c121af6fad67c0b45959cd6a86194" +"14b90b4535fb95f86ca7e64502acc135eff4f8a3abe9dde84238fab7a7d402454a3f07ad" +"ec05ec94b2891e0879037fae6acaa31dcecf3f85236ade946f5ad69ad4077beb65099285" +"38ee09f2bc38e5704da67b5006b5e39cd765aafcd740c7dadb99d0c547126e1324610fcb" +"7353dac2c110e803fca2b17485b1c4b78690bc4f867e6f043b2568889f67985a465a48eb" +"ee915200589e915756d4968d26529c3ffe3dbe70e84c682ad08a0c68db571634fbb0210d" +"c1b16b8b725886465c8c51f36a5e27d0f78e5643e051d3bddd512ce511f6bdf3dfe42759" +"00c5fea9d248c2b3f36911ed0ff41a19f6445521f251724657ea8f795b3ead0928a1657f" +"308dd7c7c1e7e490d9849df43becfa5cc25ed09ef614fd69ddc7e5e3147623901d647876" +"fb60077ffc48c51ed7d02b35f6802e3715fc708a0c88b82fe9cba0a442d38d09ca5ae483" +"21487bdef1794e7636bf7457dd2b51a391880c34d229438347e5fec8555fe263f08ba87b" +"b16dcde529248a477628067d13d0cb3bf51776f4d39fb3fbc5f669e91019323e40360e4b" +"78b6584f077bf9e03b66", + "ab7213f6becb980d40dc89fbda0ca39f225a2d33", +},{ + 12392, +"7ae3ca60b3a96be914d24980fb5652eb68451fed5fa47abe8e771db8301fbd5331e64753" +"93d96a4010d6551701e5c23f7ecb33bec7dd7bade21381e9865d410c383a139cb4863082" +"8e9372bd197c5b5788b6599853e8487bddfd395e537772fdd706b6a1de59c695d63427da" +"0dc3261bce2e1ae3cd6de90ec45ecd7e5f14580f5672b6ccd8f9336330dffcd6a3612a74" +"975afc08fb136450e25dc6b071ddfc28fca89d846c107fd2e4bd7a19a4ff6f482d62896d" +"a583c3277e23ab5e537a653112cdf2306043b3cc39f5280bd744fe81d66f497b95650e7d" +"dfd704efcb929b13e00c3e3a7d3cd53878af8f1506d9de05dba9c39a92604b394ea25acb" +"a2cda7b4ae8b08098ba3f0fdea15359df76517be84377f33631c844313ac335aa0d590fe" +"c472d805521f0905d44ca40d7391b292184105acd142c083761c1a038c4f5ed869ea3696" +"99592a37817f64cb4205b66be1f1de6fa47a08e1bf1a94312fe61a29e71bab242af95a7b" +"38d4fb412c682b30256d91e2a46b634535d02b495240cbdb842cbe17cba6a2b94073f3d5" +"f9621ac92ddda66f98bed997216466b4bb0579d58945f8d7450808d9e285d4f1709d8a1d" +"416aa57d4a1a72bfcbfecdda33de2cff3e90e0cc60c897c4663224fc5bbe8316a83c1773" +"802837a57bc7e9238173ed41ea32fe5fe38e546014a16d5e80700d9bac7a84bb03902f31" +"79e641f86f6bc383d656daf69801499633fb367ea7593195934c72bc9bf9624c0c845ebf" +"c36eb7ad4b22fdfb45ca7d4f0d6708c69a21f6eaa6db6bde0f0bd9dc7ec9c6e24626d0a7" +"8fbeeed4b391f871e80e6a9d207165832d4ff689296f9bca15dc03c7c0381659ea5335eb" +"aafdc3e50d18e46b00f1844870d09c25afcdb0ff1ae69dd8f94f91aca6095ba6f2b6e594" +"c4acfe9903485d21b684e31a6acc2162d40e1a7bb8114a860a07e76f5265666555f2418d" +"f11ef8f7499656d12215f5da8d7d041ac72648d15d7661ad93b24f3f071334b0921d5bb0" +"6f2c7ab09f5034518b5ae21cec379373e87d51c77d44a70c2337606aadeb9036716fd920" +"a824e7ae18ce3de9f0ec3456f3454027d8c476b3f1854b240c309f6f9786fa8a073915d9" +"7a019ce99aec3260c5f6b6346cd9c41cb9267f4475958e45289965548238c6b9f91a8784" +"b4e0957ba8b73956012c9a2fc3428434b1c1679f6ed2a3e8e2c90238df428622046f668e" +"e2b053f55e64ffd45600e05a885e3264af573bacee93d23d72a0222b5442ac80bc0a8b79" +"4c2afcf3bc881d20c111f57e3450b50a703f3db1fc5de2076a006f3b7eed694b93269874" +"3b03c2ed2684bad445e69a692e744c7ac3a04f1e0e52b7a6708076d1fbffdb3f1c995828" +"7d5f884e29407030f2db06811092efd80ae08da9daec39744c5ecd3ca771663b8f4968d4" +"2a88c2c9821c73ae2a5a4d9e2551f82c03583b9c4dea775423b4748d24eb604e8ee3159b" +"a6de9bea5b22eed6264e011734ed02b2c74ce06dda890b8604ed7ba49e7bf30e28c9871b" +"e90f5cead67eaf52b5d3181c822b10701219b28ef6f6bebfa278e38acf863e2a1d4b1e40" +"fd8a0ac6ce31054446301046148bf10dc3ae3385e2026e7762bdc8003ffebc4263191a59" +"c72f4f90db03e7d52808506b33bfe1dfa53f1a3daa152e83974fbe56cfd4e8f4e7f7806a" +"084b9d0795b858100eced0b5355a72446f37779d6c67ade60a627b8077ae1f3996b03bc3" +"a5c290651c8609f0d879fbf578cbab35086e1159dd6ddbe3bf7fb5654edcc8f09e4f80d0" +"258c9376d7c53fb68f78d333b18b70170d9a11070790c956f5744c78c986b1baf08b7631" +"7a65c5f07ae6f57eb0e65488659324d29709e3735623d0426e90aa8c4629bb080881150c" +"02be1c004da84414ac001c2eb6138c26388f5a36d594f3acef0e69e2cb43b870efa84da0" +"cff9c923a9880202aed64ad76260f53c45bb1584b3e388a909d13586094b924680006a1d" +"25d4dd36c579a8ec9d3fa63c082d977a5a5021440b5314b51850f2daa6e6af6ae88cb5b1" +"44242bceb1d4771e641101f8abfc3a9b19f2de64e35e76458ad22072ba57925d73015de5" +"66c66fcaa28fdc656f90de967ad51afd331e246d74ed469d63dd7d219935c59984bd9629" +"09d1af296eb3121d782650e7d038063bab5fa854aac77de5ffebeb53d263f521e3fc02ac" +"70", + "b0e15d39025f5263e3efa255c1868d4a37041382", +},{ + 13184, +"fa922061131282d91217a9ff07463843ae34ff7f8c28b6d93b23f1ea031d5020aa92f660" +"8c3d3df0ee24a8958fd41af880ee454e36e26438defb2de8f09c018607c967d2f0e8b80a" +"00c91c0eabe5b4c253e319b45e6106ff8bf0516f866020e5ba3f59fd669c5aeff310ebb3" +"85007069d01c64f72d2b02f4ec0b45c5ecf313056afcb52b17e08b666d01fecc42adb5b4" +"9ea00c60cacac2e0a953f1324bdd44aec00964a22a3cb33916a33da10d74ec6c6577fb37" +"5dc6ac8a6ad13e00cba419a8636d4daac8383a2e98fe90790cde7b59cfaa17c410a52abc" +"d68b127593d2fcbafd30578d195d890e981ae09e6772cb4382404a4e09f1a33c958b57db" +"ccee54ae335b6c91443206a0c100135647b844f226417a1f70317fd350d9f3789d81894a" +"aff4730072401aaeb8b713ead4394e2e64b6917d6eee2549af7bd0952f12035719065320" +"ca0d2dfe2847c6a2357c52bee4a676b12bafff66597bd479aa29299c1896f63a7523a85a" +"b7b916c5930ab66b4d191103cefc74f2f7e0e96e354f65e355ae43959a0af1880d14ea9d" +"1569e4fd47174aba7f5decb430b3f6baf80a1ef27855227b62487250d3602970e423423c" +"7ca90920685bcf75adfbe2a61ce5bd9228947b32f567927cb1a5bd8727c03aef91d6367b" +"ae7d86fd15c0977ac965a88b0d7236037aefb8d24eec8d2a07c633e031a7b9147c4c7714" +"110bfc7e261448a5d0f73c3619664e1c533c81a0acbf95d502227a33f84f0b8249e3f9fa" +"5c7905a8192b7313fc56bb20679e81333d32c797ac5162204a0eaa0e64507635921c485b" +"8f17c4e2484667a733197529e2a833eed83c57229b11bd820b5a5b78f1867787dbc217ea" +"28bfba785fb545cbc5a840a12eea428213e1aaa4e50a900ba13efcf4a5345574c2481c5d" +"927ada610bba567a55630c89d905db3d9b67fe36c9cc3d6a947664c83e69f51c74711a33" +"df66dd3ff6af9b7c1605b614d4798b4192b9a4b1508f2e2ec5aaad7eaea1ee8867353db9" +"b8d7d9a6f16aa5f339492073238c979082879aee7f94ac4be8133eaacbaedfb044e2ad4e" +"93ba0fa071dea615a5cd80d1d2678f4f93ae5a4bc9cdf3df345a29ec41d8febb23805ce4" +"2541036f3f05c63ae736f79a29802045fad9f370cabf843458c1b636ca41f387fd7821c9" +"1abbd1946afcb9186b936403233f28a5b467595131a6bc07b0873e51a08de66b5d7709a6" +"02c1bd0e7f6e8f4beb0579c51bda0e0c738ef876fcd9a40ab7873c9c31c1d63a588eebc7" +"8d9a0ae6fa35cd1a269e0d2bc68252cbd7c08d79e96f0aa6be22a016136a2b8abe9d3c9c" +"f9d60eeafe3dbc76d489b24d68c36167df4c38cf2b21cf03dc5e659e39018c3490f1237e" +"ca3f85b742ab0045d86a899c4126ad60a147cbc95b71814c274d6478668df41eb32acfb4" +"bbf024fb4e3d6be0b60653a0471afc3037ab67dcb00a2b2e24b26911e1880136e56106b7" +"f3c570fbe6f311d94624cb001914ff96fbbf481f71686aa17be0850568058fc1ee8900b4" +"7af5cf51c5ed9e00a8b532c131f42513f6b8df14a9bbc2e9ede5a560681184d41a147552" +"edfbdef98d95e6a7793229d25ba9b0b395a020aa1c0731de89e662246d59ec22e5d8f4b4" +"6fbc048efcffbc234744c5c66417070f9c751c81788f04691ccb1a09d60c46f6f73375bf" +"e2e646cf6290069541a8dfe216374c925e94d06ece72e851e81d3e8acd011f82526c2f9f" +"55955c6752dc10e93153ab58627e30fa2c573e4042954337982eec1f741be058c85bad86" +"bf3a02ed96d3201dadd48bd4de8105200dfcbcc400c3c3dd717abfc562ebe338b14b1eb5" +"ecbe9227661e49c58bf8233770d813faafc78b05711135adcc4ce4c65095ca0bdc1debc0" +"b6e5d195dbc582ce94b3afa14a422edf9b69abd7ae869a78c3a26fb50ef7122ec5af8d0c" +"78ef082ca114f8817c3d93b31809870caea2eb9533fa767c2954efb9ba07e4f1077e9f9b" +"be845661eabea2c91079321477a7c167c7234528d63d6aabbe723e0e337b2e61138a310a" +"3fd04368aa4215b7af9d0334a8a74681bcb86b4af87a0329a1ed9dc7c9aef14521785eda" +"0eeb97bdff8c9945fd0ee04e84d0dae091a69c0bfcdcd4150878fed839c0db6565fc1fed" +"0e7d6ae2efde7a59d58a9fb3b07e6f7cea51ba93f771c18b2eafa252d7fe171085776052" +"a6a17e6858f0a20b7e8be54413523989bf20a028a84d9ce98b78e6ee0b8362df49de5344" +"b409cc322354672a21ea383e870d047551a3af71aaf2f44f49a859cf001e61b592dd036f" +"c6625bf7b91ea0fb78c1563cceb8c4345bf4a9fbe6ee2b6bf5e81083", + "8b6d59106f04300cb57c7c961945cd77f3536b0a", +},{ + 13976, +"162cca41155de90f6e4b76a34261be6666ef65bdb92b5831b47604ce42e0c6c8d2eda265" +"ab9a3716809bf2e745e7831a41768d0f6349a268d9ac6e6adfb832a5d51b75d7951cf60e" +"03d9e40de6d351f1f6ade5143531cf32839401ca6dfb9dc7473daa607aeb0c3d1e8eb3db" +"cc2f1231ad1dd394d7eac9d8dab726b895b1ee774fdcabc8031063ecfa41c71a9f03ad23" +"904cc056f17c76a1059c43faffe30dfd157fdfd7d792e162bf7a889109550a0fc4c41523" +"2af0c0d72dcbc2595299e1a1c2aeae549f7970e994c15e0ab02f113d740d38c32a4d8ec0" +"79cd099d37d954ab7ef2800902cdf7c7a19fb14b3c98aaf4c6ad93fe9a9bc7a61229828e" +"55ad4d6270d1bdbca9975d450f9be91e5699bd7ee22e8c9c22e355cf1f6793f3551cb510" +"c1d5cd363bdf8cab063e6e49a6383221f1188d64692c1f84c910a696de2e72fb9886193f" +"61ab6b41ad0ea894d37ff1261bf1fd1f187e0d0c38ab223d99ec6d6b1e6b079fc305e24e" +"2d9500c98676e2d587434495d6e107b193c06fb12d5d8eaa7b0c001d08f4d91cae5bdcae" +"6624ee755e95ca8e4c5ef5b903d7f5ba438abeffd6f16d82d88138f157e7a50d1c91fb50" +"c770f6d222fcbf6daf791b1f8379e3b157a3b496ddb2e71650c1c4ac4fc5f2aceb5b3228" +"ffc44e15c02d4baa9434e60928a93f21bc91cfd3c2719f53a8c9bcd2f2dee65a8bbc88f9" +"5d7ced211fc3b04f6e8b74eb2026d66fd57fa0cccea43f0a0381782e6fee5660afed674d" +"cb2c28cb54d2bdbbaf78e534b0742ede6b5e659b46cd516d5362a194dd0822f6417935c4" +"ff05815b118fe5687cd8b050240015cfe449d9dfde1f4fdb105586e429b2c1849aac2791" +"ef73bc54603190eba39037ec057e784bb92d497e705dfcde2addb3514b4f1926f12d5440" +"850935779019b23bd0f2977a8c9478c424a7eaaeec04f3743a77bee2bec3937412e707bc" +"92a070046e2f9c35fe5cc3f755bbb91a182e683591ab7e8cff40633730546e81522f588f" +"07bdf142b78e115d2a22d2eb5664fcdb7574c1ee5ba9abd307d7d29078cd5223c222fc69" +"60324c40cc639be84dad96b01059efce7b08538ebef89bafab834609c7e82774a14e5be6" +"62067edba6111efa8ae270f5066442b17e3f31a793581c8a3f96d92921ec26981594e28a" +"08987d020b97ad2ba5c662836e35fd3fd954bcec52b579528913959d0d942fbf1c4b9910" +"ba010c3700359a4eb7616541257f0f7727cc71b580cc903f718ecc408a315b6bbfa7f6e3" +"beb9d258804bd2731ee2fb75e763281baf1effc4690a23d5f952ab5d4311d4f5885af2eb" +"f27cad9f6d84692cb903064bbd11ca751f919b4811b7722c6ec80c360521e34d357b5c8b" +"ba6d42e5c632730f53add99ab8aa9c607b6796216753086ede158bc670d04900aca66ce8" +"357bd72d19fb147b5fde8ee4df6a0184573a2e65ba3fd3a0cb04dac5eb36d17d2f639a6e" +"b602645f3ab4da9de4c9999d6506e8e242a5a3216f9e79a4202558ecdc74249ad3caaf90" +"71b4e653338b48b3ba3e9daf1e51e49384268d63f37ce87c6335de79175cdf542d661bcd" +"74b8f5107d6ab492f54b7c3c31257ecb0b426b77ed2e2ed22bbfdaf49653e1d54e5988fa" +"d71397546f9955659f22b3a4117fc823a1e87d6fb6fb8ab7d302a1316975e8baf0c0adbd" +"35455655f6a596b6ac3be7c9a8ea34166119d5e70dfbc1aa6e14ff98eff95e94ef576656" +"5d368ec8857fb0b029bcb990d420a5ca6bc7ab08053eb4dbfc4612a345d56faefc5e03a4" +"43520b224de776a5b618e1aa16edc513d5fcefcd413031b0ddc958a6fca45d108fbde065" +"3cf2d11cb00a71cd35f57993875598b4e33e2384623a0986859105d511c717c21d6534bf" +"69fd3d7cf1682e4fc25298d90df951e77a316996beac61bb7078988118c906548af92cfe" +"72cd4b102ffad584e5e721a0cdb5621ed07dda8955d84bea57a5afa4ba06289ddfac3a9e" +"765538fd9392fc7904cedb65e38cd90967f01845ff819777a22d199f608e62c13e6ba98b" +"40824b38c784bdb41d62c4014fc7e8d93be52695e975e54d1ff92b412f451177143d74a6" +"bde0ee53a986043ab465a1ef315ac4c538e775ef4178fde5f2ea560a364de18b8fe9578a" +"ad80027c3fd32dcf0967d9d03789b1cdf19040762f626289cf3af8afe5a8e0a152d9258e" +"981872c1ec95cd7f8d65812e55cb5cbd8db61b3f068a23d9652372dfbf18d43a663c5a0d" +"026b0898e383ce5c95b0ba7fb5ed6b7304c7c9d3ba64f38d1dc579465148ccfa7271f2e3" +"e0e97e9ddac7c0874f0f396cf07851638a734df393687b7b0343afd1652ff32a2da17b3a" +"4c99d79c02256c73f32625527e5666594a8a42a12135eddb022e743371b3ab7b12ad6785" +"7635eed03558ac673d17280769b2368056276d5d72f5dbc75525f8a7558bd90b544aa6cb" +"dd964e6c70be79441969bfdf471f17a2dc0c92", + "6144c4786145852e2a01b20604c369d1b9721019", +},{ + 14768, +"c9bed88d93806b89c2d028866842e6542ab88c895228c96c1f9f05125f8697c7402538b0" +"6465b7ae33daef847500f73d20c598c86e4804e633e1c4466e61f3ed1e9baadc5723bbed" +"9455a2ff4f99b852cfe6aa3442852ade0b18e4995ddab4250928165a9441de108d4a293d" +"1d95935de022aa17f366a31d4f4c4c54557a4235a9d56473444787ddc5c06c87087aef24" +"fa8280b7ac74d76ba685e4be7dc705e5a8a97c6c8fbd201ee5bf522438d23371c60c155d" +"93352f8fb8cc9421fe4b66ffabad46909c2c1099944fc55ed424c90aecca4f50d0331153" +"2e2844c3ff8ecb495de7ab26941cbf177b79ad7b05f918b713c417da8cf6e67db0a2dcee" +"a9179d8d636191759e13955f4244f0c4f2d88842e3015641ef0417d6e54144e8246e4591" +"6823e2c6e39bfa3b90b97781c44981710689f2ce20e70a26760d65f9971b291e12338461" +"8b3b56710dde2afaa2d46b0e2164d5c9482729350a0e256b2aa6b3fb099b618ebd7c11ca" +"62bdf176b502aedfdf9be57a8e4adbca4a4d6d8407984af2f6635f95a1e4930e375eb53f" +"245ab2ade5340c281bda87afded1268e537955c9819168bd60fd440533c75c9b1865e03f" +"de3a301d165f97aa6da236cf39cf3e49512f6350224f8d76ff02d0d3b9a99e5f70b23b9f" +"a85f72849fc98790df246c3a0f4437940e60d42b4317f72e2eb055d343a614f7f9648005" +"1e4dff186dff476462d9ced24dbb82eaa60cbbf6a0026e64001da36d30f529f48f3688b1" +"0ce9378ef3f50f5106e5007cd0eb037136254fda4f20d048769bd51a9d8d09a1e469a482" +"6aa0e25b6267b5a96abcb6e919a362fdd7b683d2f2dcec40ee5969311c07f6066ee22f36" +"89ca08381c85bea470040e9541e7a451cd43d62c2aa292a9dc4b95e3a7c4de2ba29663f3" +"8d5002eb64ceba6934bb1b0e2e55fba7fa706b514ebeeae1be4dd882d6512da066246a05" +"1d8bd042593bd0513e9cc47806ccdc7097e75bc75b8603834c85cd084e0ade3cc2c2b7e8" +"586eac62249f9769f5bdcd50e24e515f257548762db9adf3ee0846d67cfcd723d85d9588" +"09e6dd406f4c2637557c356fc52490a2a0763429ee298a1c72c098bb810e740c15faffc6" +"1e80cf6e18f86dc0e29bc150ce43ca71f5729356cd966277fd8b32366f6263c3a761b13d" +"544a631a25e1c4c8dea8d794abed47ccb4069d20f1dcb54e40a673ffb5f7b2eb31fb7d44" +"36fd8252f92dc35bb9a18fc55099b17e0807e79caf4f9641ee4bbbc2d6922508bcfae236" +"475bf78bc796548bc8d60659e816af68e5e43352fa64b5086c97c22c60ddcbbbefb9d9ef" +"7cd57c64454604793910f4f90aedb4fb824a86061a93bb79c9b0272a1ad0d24e8165f099" +"ef6f14a6a4fea09845f280022e061804090d7ab79f7bddcbef264b6f7d4e9971eddb9ca7" +"d0e79a8dbe7cff2fa59f514a608d66ae8c44d5e69745aa1b19995e366812064567d3ca20" +"9e12994c901d1b1f489be7253615f7c339b5581afd4d262e879ab8480ecb18990d3db61f" +"96895dcde9c065e645f52baafefcbe34d072dba373fd1c786fd56c3f3284be7260eaff9a" +"6a8348b762ed59e20ea443313b1164db53c3989c32fcae5b366f190b9548e8cff46df961" +"350369b490354ed8e530a91f5072967eff45c63540862fb2deab02b3ae05deac65414368" +"ac3549f277da92b692947de47cba9c1579526931e31c3490c1d3605f9bafcf468c2e9b47" +"981407ea40b0b59754621943095a2d4f4ba266ac545fe7447e54f69555a7ac9ff1e8f001" +"834fa65f2d4523061726e4d3bf4680519032dc21b7389e9f3229e4c2295d354482f8b803" +"b06ca3a8cb3ff786e60f6bc59dd3a5bfed63b0aa493bab78e97bbefb6633534d84de826f" +"4e2ccc3069050d50a2caace6c9de15ffc2656988d94b736e5688df0351a3a6a4c875cd99" +"ef304f3cc7a0585df2b0b3e6c62f86bba0d43de47b80c4eec1c4f98e60a36188219919cf" +"36dc10ee11e174a67d226ad9e71f02a7fca26ad67a4862773f3defc6a747545314063e5f" +"ce7a3f890ec57daa5532acfd027739832437c8a58dcbe11c2842e60e8ca64979d081fbd5" +"a1a028f59317212fb5869abc689a156171d69e4f4c93b949c3459904c00192d3603cd184" +"48d64b843c57f34aee7830f313e58e2abc41b44be46a96c845ffebcb7120e21d1d751046" +"c072adf65dd901a39c8019742054be5e159ea88d0885ee05fcd4c189bafe5abb68603186" +"5dc570b9342fa7f41fd5c1c87e68371ab19a83c82ae1d890c678102d5da8e6c29845657c" +"027ba07362cba4d24950ab38e747925e22ce8df9eaec1ae2c6d23374b360c8352feb6cb9" +"913e4fc49bde6caf5293030d0d234a8ecd616023cc668262591f812de208738e5336a9e6" +"9f9be2479b86be1e1369761518dfc93797ed3a55308878a944581eba50bc9c7f7a0e75c7" +"6a28acd95b277857726f3f684eefc215e0a696f47d65d30431d710d957c08ef96682b385" +"0ee5ba1c8417aafc1af2846a127ec155b4b7fb369e90eb3a5c3793a3389bbc6b532ca32b" +"f5e1f03c2280e71c6e1ae21312d4ff163eee16ebb1fdee8e887bb0d453829b4e6ed5fa70" +"8f2053f29b81e277be46", + "a757ead499a6ec3d8ab9814f839117354ae563c8" +} +}; + +void test_sha1(void) +{ + unsigned char buffer[4000]; + int i; + for (i=0; i < sizeof(sha1_tests) / sizeof(sha1_tests[0]); ++i) { + stb_uint len = sha1_tests[i].length / 8; + unsigned char digest[20], fdig[20]; + unsigned int h; + assert(len <= sizeof(buffer)); + assert(strlen(sha1_tests[i].message) == len*2); + assert(strlen(sha1_tests[i].digest) == 20 * 2); + for (h=0; h < len; ++h) { + char v[3]; + v[0] = sha1_tests[i].message[h*2]; + v[1] = sha1_tests[i].message[h*2+1]; + v[2] = 0; + buffer[h] = (unsigned char) strtol(v, NULL, 16); + } + stb_sha1(digest, buffer, len); + for (h=0; h < 20; ++h) { + char v[3]; + int res; + v[0] = sha1_tests[i].digest[h*2]; + v[1] = sha1_tests[i].digest[h*2+1]; + v[2] = 0; + res = digest[h] == strtol(v, NULL, 16); + c(res, sha1_tests[i].digest); + if (!res) + break; + } + { + int z; + FILE *f = fopen("data/test.bin", "wb"); + if (!f) stb_fatal("Couldn't write to test.bin"); + fwrite(buffer, len, 1, f); + fclose(f); + #ifdef _WIN32 + z = stb_sha1_file(fdig, "data/test.bin"); + if (!z) stb_fatal("Couldn't digest test.bin"); + c(memcmp(digest, fdig, 20)==0, "stb_sh1_file"); + #endif + } + } +} + + +#if 0 + +stb__obj zero, one; + +void test_packed_floats(void) +{ + stb__obj *p; + float x,y,*q; + clock_t a,b,c; + int i; + stb_float_init(); + for (i=-10; i < 10; ++i) { + float f = (float) pow(10,i); + float g = f * 10; + float delta = (g - f) / 10000; + while (f < g) { + stb__obj z = stb_float(f); + float k = stb_getfloat(z); + float p = stb_getfloat_table(z); + assert((z & 1) == 1); + assert(f == k); + assert(k == p); + f += delta; + } + } + + zero = stb_float(0); + one = stb_float(1); + + p = malloc(8192 * 4); + for (i=0; i < 8192; ++i) + p[i] = stb_rand(); + for (i=0; i < 8192; ++i) + if ((stb_rand() & 31) < 28) + p[i] = zero; + + q = malloc(4 * 1024); + + a = clock(); + + x = y = 0; + for (i=0; i < 200000000; ++i) + q[i&1023] = stb_getfloat_table(p[i&8191]); + b = clock(); + for (i=0; i < 200000000; ++i) + q[i&1023] = stb_getfloat_table2(p[i&8191]); + c = clock(); + free(p); + + free(q); + + printf("Table: %d\nIFs: %d\n", b-a, c-b); +} +#endif + + +void do_compressor(int argc,char**argv) +{ + char *p; + size_t slen; + unsigned int len; + + int window; + if (argc == 2) { + p = stb_file(argv[1], &slen); + len = (unsigned int) slen; + if (p) { + unsigned int dlen, clen = stb_compress_tofile("data/dummy.bin", p, len); + char *q = stb_decompress_fromfile("data/dummy.bin", &dlen); + + if (len != dlen) { + printf("FAILED %d -> %d\n", len, clen); + } else { + int z = memcmp(q,p,dlen); + if (z != 0) + printf("FAILED %d -> %d\n", len, clen); + else + printf("%d -> %d\n", len, clen); + } + } + return; + } + + window = atoi(argv[1]); + if (window && argc == 4) { + p = stb_file(argv[3], &slen); + len = (int) slen; + if (p) { + stb_compress_hashsize(window); + stb_compress_tofile(argv[2], p, len); + } + } else if (argc == 3) { + p = stb_decompress_fromfile(argv[2], &len); + if (p) { + FILE *f = fopen(argv[1], "wb"); + fwrite(p,1,len,f); + fclose(f); + } else { + fprintf(stderr, "FAILED.\n"); + } + } else { + fprintf(stderr, "Usage: stb \n" + " or stb \n"); + } +} + +#if 0 +// naive backtracking implementation +int wildmatch(char *expr, char *candidate) +{ + while(*expr) { + if (*expr == '?') { + if (!*candidate) return 0; + ++candidate; + ++expr; + } else if (*expr == '*') { + ++expr; + while (*expr == '*' || *expr =='?') ++expr; + // '*' at end of expression matches anything + if (!*expr) return 1; + // now scan candidate 'til first match + while (*candidate) { + if (*candidate == *expr) { + // check this candidate + if (stb_wildmatch(expr+1, candidate+1)) + return 1; + // if not, then backtrack + } + ++candidate; + } + } else { + if (*expr != *candidate) + return 0; + ++expr, ++candidate; + } + } + return *candidate != 0; +} + +int stb_matcher_find_slow(stb_matcher *m, char *str) +{ + int result = 1; + int i,j,y,z; + uint16 *previous = NULL; + uint16 *current = NULL; + uint16 *temp; + + stb_arr_setsize(previous, 4); + stb_arr_setsize(current, 4); + + previous = stb__add_if_inactive(m, previous, m->start_node); + previous = stb__eps_closure(m,previous); + if (stb__clear_goalcheck(m, previous)) + goto done; + + while (*str) { + y = stb_arr_len(previous); + for (i=0; i < y; ++i) { + stb_nfa_node *n = &m->nodes[previous[i]]; + z = stb_arr_len(n->out); + for (j=0; j < z; ++j) { + if (n->out[j].match == *str) + current = stb__add_if_inactive(m, current, n->out[j].node); + else if (n->out[j].match == -1) { + if (*str != '\n') + current = stb__add_if_inactive(m, current, n->out[j].node); + } else if (n->out[j].match < -1) { + int z = -n->out[j].match - 2; + if (m->charset[(uint8) *str] & (1 << z)) + current = stb__add_if_inactive(m, current, n->out[j].node); + } + } + } + ++str; + stb_arr_setlen(previous, 0); + + temp = previous; + previous = current; + current = temp; + + if (!m->match_start) + previous = stb__add_if_inactive(m, previous, m->start_node); + previous = stb__eps_closure(m,previous); + if (stb__clear_goalcheck(m, previous)) + goto done; + } + + result=0; + +done: + stb_arr_free(previous); + stb_arr_free(current); + + return result; +} +#endif + + + + + +#if 0 // parser generator +////////////////////////////////////////////////////////////////////////// +// +// stb_parser +// +// Generates an LR(1) parser from a grammar, and can parse with it + + + +// Symbol representations +// +// Client: Internal: +// - c=0 e aka epsilon +// - c=1 $ aka end of string +// > 0 2<=c= 0 ? encode_term(x) : encode_nonterm(x)) + +stb_bitset **compute_first(short ** productions) +{ + int i, changed; + stb_bitset **first = malloc(sizeof(*first) * num_symbols); + + assert(symset); + for (i=0; i < num_symbols; ++i) + first[i] = stb_bitset_new(0, symset); + + for (i=END; i < first_nonterm; ++i) + stb_bitset_setbit(first[i], i); + + for (i=0; i < stb_arr_len(productions); ++i) { + if (productions[i][2] == 0) { + int nt = encode_nonterm(productions[i][0]); + stb_bitset_setbit(first[nt], EPS); + } + } + + do { + changed = 0; + for (i=0; i < stb_arr_len(productions); ++i) { + int j, nt = encode_nonterm(productions[i][0]); + for (j=2; productions[i][j]; ++j) { + int z = encode_symbol(productions[i][j]); + changed |= stb_bitset_unioneq_changed(first[nt], first[z], symset); + if (!stb_bitset_testbit(first[z], EPS)) + break; + } + if (!productions[i][j] && !stb_bitset_testbit(first[nt], EPS)) { + stb_bitset_setbit(first[nt], EPS); + changed = 1; + } + } + } while (changed); + return first; +} + +stb_bitset **compute_follow(short ** productions, stb_bitset **first, int start) +{ + int i,j,changed; + stb_bitset **follow = malloc(sizeof(*follow) * num_symbols); + + assert(symset); + for (i=0; i < num_symbols; ++i) + follow[i] = (i >= first_nonterm ? stb_bitset_new(0, symset) : NULL); + + stb_bitset_setbit(follow[start], END); + do { + changed = 0; + for (i=0; i < stb_arr_len(productions); ++i) { + int nt = encode_nonterm(productions[i][0]); + for (j=2; productions[i][j]; ++j) { + if (productions[i][j] < 0) { + int k,z = encode_nonterm(productions[i][j]); + for (k=j+1; productions[i][k]; ++k) { + int q = encode_symbol(productions[i][k]); + changed |= stb_bitset_unioneq_changed(follow[z], first[q], symset); + if (!stb_bitset_testbit(first[q], EPS)) + break; + } + if (!productions[i][k] == 0) + changed |= stb_bitset_unioneq_changed(follow[z], follow[nt], symset); + } + } + } + } while (changed); + + for (i=first_nonterm; i < num_symbols; ++i) + stb_bitset_clearbit(follow[i], EPS); + + return follow; +} + +void first_for_prod_plus_sym(stb_bitset **first, stb_bitset *out, short *prod, int symbol) +{ + stb_bitset_clearall(out, symset); + for(;*prod;++prod) { + int z = encode_symbol(*prod); + stb_bitset_unioneq_changed(out, first[z], symset); + if (!stb_bitset_testbit(first[z], EPS)) + return; + } + stb_bitset_unioneq_changed(out, first[symbol], symset); +} + +#define Item(p,c,t) ((void *) (size_t) (((t) << 18) + ((c) << 12) + ((p) << 2))) +#define ItemProd(i) ((((uint32) (size_t) (i)) >> 2) & 1023) +#define ItemCursor(i) ((((uint32) (size_t) (i)) >> 12) & 63) +#define ItemLookahead(i) (((uint32) (size_t) (i)) >> 18) + +static void pc(stb_ps *p) +{ +} + +typedef struct +{ + short *prod; + int prod_num; +} ProdRef; + +typedef struct +{ + stb_bitset **first; + stb_bitset **follow; + short ** prod; + ProdRef ** prod_by_nt; +} Grammar; + +stb_ps *itemset_closure(Grammar g, stb_ps *set) +{ + stb_bitset *lookahead; + int changed,i,j,k, list_len; + if (set == NULL) return set; + lookahead = stb_bitset_new(0, symset); + do { + void **list = stb_ps_getlist(set, &list_len); + changed = 0; + for (i=0; i < list_len; ++i) { + ProdRef *prod; + int nt, *looklist; + int p = ItemProd(list[i]), c = ItemCursor(list[i]), t = ItemLookahead(list[i]); + if (g.prod[p][c] >= 0) continue; + nt = encode_nonterm(g.prod[p][c]); + first_for_prod_plus_sym(g.first, lookahead, g.prod[p]+c+1, t); + looklist = stb_bitset_getlist(lookahead, 1, first_nonterm); + + prod = g.prod_by_nt[nt]; + for (j=0; j < stb_arr_len(prod); ++j) { + assert(prod[j].prod[0] == g.prod[p][c]); + // matched production; now iterate terminals + for (k=0; k < stb_arr_len(looklist); ++k) { + void *item = Item(prod[j].prod_num,2,looklist[k]); + if (!stb_ps_find(set, item)) { + changed = 1; + set = stb_ps_add(set, item); + pc(set); + } + } + } + stb_arr_free(looklist); + } + free(list); + } while (changed); + free(lookahead); + return set; +} + +stb_ps *itemset_goto(Grammar g, stb_ps *set, int sym) +{ + int i, listlen; + void **list = stb_ps_fastlist(set, &listlen); + stb_ps *out = NULL; + for (i=0; i < listlen; ++i) { + int p,c; + if (!stb_ps_fastlist_valid(list[i])) continue; + p = ItemProd(list[i]), c = ItemCursor(list[i]); + if (encode_symbol(g.prod[p][c]) == sym) { + void *z = Item(p,c+1,ItemLookahead(list[i])); + if (!stb_ps_find(out, z)) + out = stb_ps_add(out, z); + pc(out); + } + } + return itemset_closure(g, out); +} + +void itemset_all_nextsym(Grammar g, stb_bitset *out, stb_ps *set) +{ + int i, listlen; + void **list = stb_ps_fastlist(set, &listlen); + stb_bitset_clearall(out, symset); + pc(set); + for (i=0; i < listlen; ++i) { + if (stb_ps_fastlist_valid(list[i])) { + int p = ItemProd(list[i]); + int c = ItemCursor(list[i]); + if (g.prod[p][c]) + stb_bitset_setbit(out, encode_symbol(g.prod[p][c])); + } + } +} + +stb_ps ** generate_items(Grammar g, int start_prod) +{ + stb_ps ** all=NULL; + int i,j,k; + stb_bitset *try = stb_bitset_new(0,symset); + stb_ps *set = NULL; + void *item = Item(start_prod, 2, END); + set = stb_ps_add(set, item); + pc(set); + set = itemset_closure(g, set); + pc(set); + stb_arr_push(all, set); + for (i = 0; i < stb_arr_len(all); ++i) { + // only try symbols that appear in all[i]... there's a smarter way to do this, + // which is to take all[i], and divide it up by symbol + pc(all[i]); + itemset_all_nextsym(g, try, all[i]); + for (j = 1; j < num_symbols; ++j) { + if (stb_bitset_testbit(try, j)) { + stb_ps *out; + if (stb_arr_len(all) > 4) pc(all[4]); + if (i == 1 && j == 29) { + if (stb_arr_len(all) > 4) pc(all[4]); + out = itemset_goto(g, all[i], j); + if (stb_arr_len(all) > 4) pc(all[4]); + } else + out = itemset_goto(g, all[i], j); + pc(out); + if (stb_arr_len(all) > 4) pc(all[4]); + if (out != NULL) { + // add it to the array if it's not already there + for (k=0; k < stb_arr_len(all); ++k) + if (stb_ps_eq(all[k], out)) + break; + if (k == stb_arr_len(all)) { + stb_arr_push(all, out); + pc(out); + if (stb_arr_len(all) > 4) pc(all[4]); + } else + stb_ps_delete(out); + } + } + } + } + free(try); + return all; +} + +typedef struct +{ + int num_stack; + int function; +} Reduction; + +typedef struct +{ + short *encode_term; + Reduction *reductions; + short **action_goto; // terminals are action, nonterminals are goto + int start; + int end_term; +} Parser; + +enum +{ + A_error, A_accept, A_shift, A_reduce, A_conflict +}; + +typedef struct +{ + uint8 type; + uint8 cursor; + short prod; + short value; +} Action; + +Parser *parser_create(short **productions, int num_prod, int start_nt, int end_term) +{ + short *mini_rule = malloc(4 * sizeof(mini_rule[0])); + Action *actions; + Grammar g; + stb_ps ** sets; + Parser *p = malloc(sizeof(*p)); + int i,j,n; + stb_bitset *mapped; + int min_s=0, max_s=0, termset, ntset, num_states, num_reductions, init_prod; + + int synth_start; + + // remap sparse terminals and nonterminals + + for (i=0; i < num_prod; ++i) { + for (j=2; productions[i][j]; ++j) { + if (productions[i][j] < min_s) min_s = productions[i][j]; + if (productions[i][j] > max_s) max_s = productions[i][j]; + } + } + synth_start = --min_s; + + termset = (max_s + 32) >> 5; + ntset = (~min_s + 32) >> 5; + memset(encode_term, 0, sizeof(encode_term)); + memset(encode_nonterm, 0, sizeof(encode_nonterm)); + + mapped = stb_bitset_new(0, termset); + n = 2; + for (i=0; i < num_prod; ++i) + for (j=2; productions[i][j]; ++j) + if (productions[i][j] > 0) + if (!stb_bitset_testbit(mapped, productions[i][j])) { + stb_bitset_setbit(mapped, productions[i][j]); + encode_term[productions[i][j]] = n++; + } + free(mapped); + + first_nonterm = n; + + mapped = stb_bitset_new(0, ntset); + for (i=0; i < num_prod; ++i) + for (j=2; productions[i][j]; ++j) + if (productions[i][j] < 0) + if (!stb_bitset_testbit(mapped, ~productions[i][j])) { + stb_bitset_setbit(mapped, ~productions[i][j]); + encode_nonterm[~productions[i][j]] = n++; + } + free(mapped); + + // add a special start state for internal processing + p->start = n++; + encode_nonterm[synth_start] = p->start; + mini_rule[0] = synth_start; + mini_rule[1] = -32768; + mini_rule[2] = start_nt; + mini_rule[3] = 0; + + p->end_term = end_term; + + num_symbols = n; + + // create tables + g.prod = NULL; + g.prod_by_nt = malloc(num_symbols * sizeof(g.prod_by_nt[0])); + for (i=0; i < num_symbols; ++i) + g.prod_by_nt[i] = NULL; + + for (i=0; i < num_prod; ++i) { + stb_arr_push(g.prod, productions[i]); + } + init_prod = stb_arr_len(g.prod); + stb_arr_push(g.prod, mini_rule); + + num_reductions = stb_arr_len(g.prod); + p->reductions = malloc(num_reductions * sizeof(*p->reductions)); + + symset = (num_symbols + 31) >> 5; + g.first = compute_first(g.prod); + g.follow = compute_follow(g.prod, g.first, p->start); + + for (i=0; i < stb_arr_len(g.prod); ++i) { + ProdRef pr = { g.prod[i], i }; + stb_arr_push(g.prod_by_nt[encode_nonterm(g.prod[i][0])], pr); + } + + sets = generate_items(g, init_prod); + + num_states = stb_arr_len(sets); + // now generate tables + + actions = malloc(sizeof(*actions) * first_nonterm); + p->action_goto = (short **) stb_array_block_alloc(num_states, sizeof(short) * num_symbols); + for (i=0; i < num_states; ++i) { + int j,n; + void **list = stb_ps_getlist(sets[i], &n); + memset(actions, 0, sizeof(*actions) * first_nonterm); + for (j=0; j < n; ++j) { + int p = ItemProd(list[j]), c = ItemCursor(list[j]), t = ItemLookahead(list[j]); + if (g.prod[p][c] == 0) { + if (p == init_prod) { + // @TODO: check for conflicts + assert(actions[t].type == A_error || actions[t].type == A_accept); + actions[t].type = A_accept; + } else { + // reduce production p + if (actions[t].type == A_reduce) { + // is it the same reduction we already have? + if (actions[t].prod != p) { + // no, it's a reduce-reduce conflict! + printf("Reduce-reduce conflict for rule %d and %d, lookahead %d\n", p, actions[t].prod, t); + // @TODO: use precedence + actions[t].type = A_conflict; + } + } else if (actions[t].type == A_shift) { + printf("Shift-reduce conflict for rule %d and %d, lookahead %d\n", actions[t].prod, p, t); + actions[t].type = A_conflict; + } else if (actions[t].type == A_accept) { + assert(0); + } else if (actions[t].type == A_error) { + actions[t].type = A_reduce; + actions[t].prod = p; + } + } + } else if (g.prod[p][c] > 0) { + int a = encode_symbol(g.prod[p][c]), k; + stb_ps *out = itemset_goto(g, sets[i], a); + for (k=0; k < stb_arr_len(sets); ++k) + if (stb_ps_eq(sets[k], out)) + break; + assert(k < stb_arr_len(sets)); + // shift k + if (actions[a].type == A_shift) { + if (actions[a].value != k) { + printf("Shift-shift conflict! Rule %d and %d with lookahead %d/%d\n", actions[a].prod, p, a,t); + actions[a].type = A_conflict; + } + } else if (actions[a].type == A_reduce) { + printf("Shift-reduce conflict for rule %d and %d, lookahead %d/%d\n", p, actions[a].prod, a,t); + actions[a].type = A_conflict; + } else if (actions[a].type == A_accept) { + assert(0); + } else if (actions[a].type == A_error) { + actions[a].type = A_shift; + actions[a].prod = p; + actions[a].cursor = c; + actions[a].value = k; + } + } + } + // @TODO: recompile actions into p->action_goto + } + + free(mini_rule); + stb_pointer_array_free(g.first , num_symbols); free(g.first ); + stb_pointer_array_free(g.follow, num_symbols); free(g.follow); + stb_arr_free(g.prod); + for (i=0; i < num_symbols; ++i) + stb_arr_free(g.prod_by_nt[i]); + free(g.prod_by_nt); + for (i=0; i < stb_arr_len(sets); ++i) + stb_ps_delete(sets[i]); + stb_arr_free(sets); + + return p; +} + +void parser_destroy(Parser *p) +{ + free(p); +} + +#if 0 +enum nonterm +{ + N_globals = -50, + N_global, N_vardef, N_varinitlist, N_varinit, N_funcdef, N_optid, N_optparamlist, + N_paramlist, N_param, N_optinit, N_optcomma, N_statements, N_statement, + N_optexpr, N_assign, N_if, N_ifcore, N_else, N_dictdef, N_dictdef2, + N_dictdefitem, N_expr, + N__last +}; + +short grammar[][10] = +{ + { N_globals , 0, N_globals, N_global }, + { N_globals , 0 }, + { N_global , 0, N_vardef }, + { N_global , 0, N_funcdef }, + { N_vardef , 0, ST_var, N_varinitlist, }, + { N_varinitlist, 0, N_varinitlist, ',', N_varinit }, + { N_varinitlist, 0, N_varinit, }, + { N_varinit , 0, ST_id, N_optinit, }, + { N_funcdef , 0, ST_func, N_optid, '(', N_optparamlist, ')', N_statements, ST_end }, + { N_optid , 0, ST_id }, + { N_optid , 0, }, + { N_optparamlist, 0, }, + { N_optparamlist, 0, N_paramlist, N_optcomma }, + { N_paramlist , 0, N_paramlist, ',', N_param }, + { N_paramlist , 0, N_param }, + { N_param , 0, ST_id, N_optinit }, + { N_optinit , 0, '=', N_expr }, + { N_optinit , 0, }, + { N_optcomma , 0, ',' }, + { N_optcomma , 0, }, + { N_statements , 0, N_statements, N_statement }, + { N_statement , 0, N_statement, ';' }, + { N_statement , 0, N_varinit }, + { N_statement , 0, ST_return, N_expr }, + { N_statement , 0, ST_break , N_optexpr }, + { N_optexpr , 0, N_expr }, + { N_optexpr , 0, }, + { N_statement , 0, ST_continue }, + { N_statement , 0, N_assign }, + { N_assign , 0, N_expr, '=', N_assign }, + //{ N_assign , 0, N_expr }, + { N_statement , 0, ST_while, N_expr, N_statements, ST_end }, + { N_statement , 0, ST_if, N_if, }, + { N_if , 0, N_ifcore, ST_end, }, + { N_ifcore , 0, N_expr, ST_then, N_statements, N_else, ST_end }, + { N_else , 0, ST_elseif, N_ifcore }, + { N_else , 0, ST_else, N_statements }, + { N_else , 0, }, + { N_dictdef , 0, N_dictdef2, N_optcomma }, + { N_dictdef2 , 0, N_dictdef2, ',', N_dictdefitem }, + { N_dictdef2 , 0, N_dictdefitem }, + { N_dictdefitem, 0, ST_id, '=', N_expr }, + { N_dictdefitem, 0, N_expr }, + { N_expr , 0, ST_number }, + { N_expr , 0, ST_string }, + { N_expr , 0, ST_id }, + { N_expr , 0, N_funcdef }, + { N_expr , 0, '-', N_expr }, + { N_expr , 0, '{', N_dictdef, '}' }, + { N_expr , 0, '(', N_expr, ')' }, + { N_expr , 0, N_expr, '.', ST_id }, + { N_expr , 0, N_expr, '[', N_expr, ']' }, + { N_expr , 0, N_expr, '(', N_dictdef, ')' }, +#if 0 +#define BINOP(op) { N_expr, 0, N_expr, op, N_expr } + BINOP(ST_and), BINOP(ST_or), BINOP(ST_eq), BINOP(ST_ne), + BINOP(ST_le), BINOP(ST_ge), BINOP('>') , BINOP('<' ), + BINOP('&'), BINOP('|'), BINOP('^'), BINOP('+'), BINOP('-'), + BINOP('*'), BINOP('/'), BINOP('%'), +#undef BINOP +#endif +}; + +short *grammar_list[stb_arrcount(grammar)]; + +void test_parser_generator(void) +{ + Parser *p; + int i; + assert(N__last <= 0); + for (i=0; i < stb_arrcount(grammar); ++i) + grammar_list[i] = grammar[i]; + p = parser_create(grammar_list, stb_arrcount(grammar), N_globals, 0); + parser_destroy(p); +} +#endif +#endif // parser generator + +#if 0 +// stb_threadtest.c + + +#include +#define STB_DEFINE +//#define STB_THREAD_TEST +#include "../stb.h" + +#define NUM_WORK 100 + +void *work_consumer(void *p) +{ + stb__thread_sleep(20); + return NULL; +} + +int pass; +stb_threadqueue *tq1, *tq2, *tq3, *tq4; +volatile float t1,t2; + +// with windows.h +// Worked correctly with 100,000,000 enqueue/dequeue WAITLESS +// (770 passes, 170000 per pass) +// Worked correctly with 2,500,000 enqueue/dequeue !WAITLESS +// (15 passes, 170000 per pass) +// Worked correctly with 1,500,000 enqueue/dequeue WAITLESS && STB_THREAD_TEST +// (9 passes, 170000 per pass) +// without windows.h +// Worked correctly with 1,000,000 enqueue/dequeue WAITLESS && STB_THREAD_TEST +// (6 passes, 170000 per pass) +// Worked correctly with 500,000 enqueue/dequeue !WAITLESS && STB_THREAD_TEST +// (3 passes, 170000 per pass) +// Worked correctly with 1,000,000 enqueue/dequeue WAITLESS +// (15 passes, 170000 per pass) +#define WAITLESS + +volatile int table[1000*1000*10]; + +void wait(int n) +{ +#ifndef WAITLESS + int j; + float y; + for (j=0; j < n; ++j) + y += 1 / (t1+j); + t2 = y; +#endif +} + +void *tq1_consumer(void *p) +{ + for(;;) { + int z; + float y = 0; + stb_threadq_get_block(tq1, &z); + wait(5000); + table[z] = pass; + } +} + +void *tq2_consumer(void *p) +{ + for(;;) { + int z; + if (stb_threadq_get(tq2, &z)) + table[z] = pass; + wait(1000); + } +} + +void *tq3_consumer(void *p) +{ + for(;;) { + int z; + stb_threadq_get_block(tq3, &z); + table[z] = pass; + wait(500); + } +} + +void *tq4_consumer(void *p) +{ + for (;;) { + int z; + stb_threadq_get_block(tq4, &z); + table[z] = pass; + wait(500); + } +} + +typedef struct +{ + int start, end; + stb_threadqueue *tq; + int delay; +} write_data; + +void *writer(void *q) +{ + int i; + write_data *p = (write_data *) q; + for (i=p->start; i < p->end; ++i) { + stb_threadq_add_block(p->tq, &i); + #ifndef WAITLESS + if (p->delay) stb__thread_sleep(p->delay); + else { + int j; + float z = 0; + for (j=0; j <= 20; ++j) + z += 1 / (t1+j); + t2 = z; + } + #endif + } + return NULL; +} + +write_data info[256]; +int pos; + +void start_writer(int z, int count, stb_threadqueue *tq, int delay) +{ + info[z].start = pos; + info[z].end = pos+count; + info[z].tq = tq; + info[z].delay = delay; + stb_create_thread(writer, &info[z]); + pos += count; +} + +int main(int argc, char **argv) +{ + int i; + stb_sync s = stb_sync_new(); + stb_sync_set_target(s, NUM_WORK+1); + stb_work_numthreads(2); + for (i=0; i < NUM_WORK; ++i) { + stb_work_reach(work_consumer, NULL, NULL, s); + } + printf("Started stb_work test.\n"); + + t1 = 1; + + // create the queues + tq1 = stb_threadq_new(4, 4, TRUE , TRUE); + tq2 = stb_threadq_new(4, 4, TRUE , FALSE); + tq3 = stb_threadq_new(4, 4, FALSE, TRUE); + tq4 = stb_threadq_new(4, 4, FALSE, FALSE); + + // start the consumers + stb_create_thread(tq1_consumer, NULL); + stb_create_thread(tq1_consumer, NULL); + stb_create_thread(tq1_consumer, NULL); + + stb_create_thread(tq2_consumer, NULL); + + stb_create_thread(tq3_consumer, NULL); + stb_create_thread(tq3_consumer, NULL); + stb_create_thread(tq3_consumer, NULL); + stb_create_thread(tq3_consumer, NULL); + stb_create_thread(tq3_consumer, NULL); + stb_create_thread(tq3_consumer, NULL); + stb_create_thread(tq3_consumer, NULL); + + stb_create_thread(tq4_consumer, NULL); + + for (pass=1; pass <= 5000; ++pass) { + int z = 0; + int last_n = -1; + int identical = 0; + pos = 0; + start_writer(z++, 50000, tq1, 0); + start_writer(z++, 50000, tq1, 0); + start_writer(z++, 50000, tq1, 0); + + start_writer(z++, 5000, tq2, 1); + start_writer(z++, 3000, tq2, 3); + start_writer(z++, 2000, tq2, 5); + + start_writer(z++, 5000, tq3, 3); + + start_writer(z++, 5000, tq4, 3); + #ifndef WAITLESS + stb__thread_sleep(8000); + #endif + for(;;) { + int n =0; + for (i=0; i < pos; ++i) { + if (table[i] == pass) + ++n; + } + if (n == pos) break; + if (n == last_n) { + ++identical; + if (identical == 3) { + printf("Problem slots:\n"); + for (i=0; i < pos; ++i) { + if (table[i] != pass) printf("%d ", i); + } + printf("\n"); + } else { + if (identical < 3) + printf("Processed %d of %d\n", n, pos); + else + printf("."); + } + } else { + identical = 0; + printf("Processed %d of %d\n", n, pos); + } + last_n = n; + #ifdef WAITLESS + stb__thread_sleep(750); + #else + stb__thread_sleep(3000); + #endif + } + printf("Finished pass %d\n", pass); + } + + stb_sync_reach_and_wait(s); + printf("stb_work test completed ok.\n"); + return 0; +} +#endif + + +#if 0 +////////////////////////////////////////////////////////////////////////////// +// +// collapse tree leaves up to parents until we only have N nodes +// useful for cmirror summaries + +typedef struct stb_summary_tree +{ + struct stb_summary_tree **children; + int num_children; + float weight; +} stb_summary_tree; + +STB_EXTERN void *stb_summarize_tree(void *tree, int limit, float reweight); + +#ifdef STB_DEFINE + +typedef struct stb_summary_tree2 +{ + STB__ARR(struct stb_summary_tree2 *) children; + int num_children; + float weight; + float weight_with_all_children; + float makes_target_weight; + float weight_at_target; + stb_summary_tree *original; + struct stb_summary_tree2 *target; + STB__ARR(struct stb_summary_tree2 *) targeters; +} stb_summary_tree2; + +static stb_summary_tree2 *stb__summarize_clone(stb_summary_tree *t) +{ + int i; + stb_summary_tree2 *s; + s = (stb_summary_tree2 *) malloc(sizeof(*s)); + s->original = t; + s->weight = t->weight; + s->weight_with_all_children = 0; + s->weight_at_target = 0; + s->target = NULL; + s->targeters = NULL; + s->num_children = t->num_children; + s->children = NULL; + for (i=0; i < s->num_children; ++i) + stb_arr_push(s->children, stb__summarize_clone(t->children[i])); + return s; +} + +static float stb__summarize_compute_targets(stb_summary_tree2 *parent, stb_summary_tree2 *node, float reweight, float weight) +{ + float total = 0; + if (node->weight == 0 && node->num_children == 1 && parent) { + node->target = parent; + return stb__summarize_compute_targets(parent, node->children[0], reweight, weight*reweight); + } else { + float total=0; + int i; + for (i=0; i < node->num_children; ++i) + total += stb__summarize_compute_targets(node, node->children[i], reweight, reweight); + node->weight_with_all_children = total + node->weight; + if (parent && node->weight_with_all_children) { + node->target = parent; + node->weight_at_target = node->weight_with_all_children * weight; + node->makes_target_weight = node->weight_at_target + parent->weight; + stb_arr_push(parent->targeters, node); + } else { + node->target = NULL; + node->weight_at_target = node->weight; + node->makes_target_weight = 0; + } + return node->weight_with_all_children * weight; + } +} + +static stb_summary_tree2 ** stb__summarize_make_array(STB__ARR(stb_summary_tree2 *) all, stb_summary_tree2 *tree) +{ + int i; + stb_arr_push(all, tree); + for (i=0; i < tree->num_children; ++i) + all = stb__summarize_make_array(all, tree->children[i]); + return all; +} + +typedef stb_summary_tree2 * stb__stree2; +stb_define_sort(stb__summarysort, stb__stree2, (*a)->makes_target_weight < (*b)->makes_target_weight) + +void *stb_summarize_tree(void *tree, int limit, float reweight) +{ + int i,j,k; + STB__ARR(stb_summary_tree *) ret=NULL; + STB__ARR(stb_summary_tree2 *) all=NULL; + + // first clone the tree so we can manipulate it + stb_summary_tree2 *t = stb__summarize_clone((stb_summary_tree *) tree); + if (reweight < 1) reweight = 1; + + // now compute how far up the tree each node would get pushed + // there's no value in pushing a node up to an empty node with + // only one child, so we keep pushing it up + stb__summarize_compute_targets(NULL, t, reweight, 1); + + all = stb__summarize_make_array(all, t); + + // now we want to iteratively find the smallest 'makes_target_weight', + // update that, and then fix all the others (which will be all descendents) + // to do this efficiently, we need a heap or a sorted binary tree + // what we have is an array. maybe we can insertion sort the array? + stb__summarysort(all, stb_arr_len(all)); + + for (i=0; i < stb_arr_len(all) - limit; ++i) { + stb_summary_tree2 *src, *dest; + src = all[i]; + dest = all[i]->target; + if (src->makes_target_weight == 0) continue; + assert(dest != NULL); + + for (k=0; k < stb_arr_len(all); ++k) + if (all[k] == dest) + break; + assert(k != stb_arr_len(all)); + assert(i < k); + + // move weight from all[i] to target + src->weight = dest->makes_target_weight; + src->weight = 0; + src->makes_target_weight = 0; + // recompute effect of other descendents + for (j=0; j < stb_arr_len(dest->targeters); ++j) { + if (dest->targeters[j]->weight) { + dest->targeters[j]->makes_target_weight = dest->weight + dest->targeters[j]->weight_at_target; + assert(dest->targeters[j]->makes_target_weight <= dest->weight_with_all_children); + } + } + STB_(stb__summarysort,_ins_sort)(all+i, stb_arr_len(all)-i); + } + // now the elements in [ i..stb_arr_len(all) ) are the relevant ones + for (; i < stb_arr_len(all); ++i) + stb_arr_push(ret, all[i]->original); + + // now free all our temp data + for (i=0; i < stb_arr_len(all); ++i) { + stb_arr_free(all[i]->children); + free(all[i]); + } + stb_arr_free(all); + return ret; +} +#endif + +#endif diff --git a/source/engine/thirdparty/stb/tests/stb.dsp b/source/engine/thirdparty/stb/tests/stb.dsp new file mode 100644 index 0000000..849b95b --- /dev/null +++ b/source/engine/thirdparty/stb/tests/stb.dsp @@ -0,0 +1,240 @@ +# Microsoft Developer Studio Project File - Name="stb" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=stb - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "stb.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "stb.mak" CFG="stb - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "stb - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "stb - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "stb - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /G6 /MT /W3 /GX /Z7 /O2 /Ob2 /I ".." /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "VORBIS_TEST" /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 + +!ELSEIF "$(CFG)" == "stb - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug\stb" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /GX /Zi /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D "TT_TEST" /FR /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /incremental:no /debug /machine:I386 /pdbtype:sept +# SUBTRACT LINK32 /force + +!ENDIF + +# Begin Target + +# Name "stb - Win32 Release" +# Name "stb - Win32 Debug" +# Begin Source File + +SOURCE=.\grid_reachability.c +# End Source File +# Begin Source File + +SOURCE=.\stb.c +# End Source File +# Begin Source File + +SOURCE=..\stb.h +# End Source File +# Begin Source File + +SOURCE=..\stb_c_lexer.h +# End Source File +# Begin Source File + +SOURCE=..\stb_connected_components.h +# End Source File +# Begin Source File + +SOURCE=..\stb_divide.h +# End Source File +# Begin Source File + +SOURCE=..\stb_ds.h +# End Source File +# Begin Source File + +SOURCE=..\stb_dxt.h +# End Source File +# Begin Source File + +SOURCE=..\stb_easy_font.h +# End Source File +# Begin Source File + +SOURCE=..\stb_herringbone_wang_tile.h +# End Source File +# Begin Source File + +SOURCE=..\stb_image.h +# End Source File +# Begin Source File + +SOURCE=..\stb_image_write.h +# End Source File +# Begin Source File + +SOURCE=..\stb_include.h +# End Source File +# Begin Source File + +SOURCE=..\stb_leakcheck.h +# End Source File +# Begin Source File + +SOURCE=..\stb_malloc.h +# End Source File +# Begin Source File + +SOURCE=..\stb_perlin.h +# End Source File +# Begin Source File + +SOURCE=..\stb_rect_pack.h +# End Source File +# Begin Source File + +SOURCE=..\stb_sprintf.h +# End Source File +# Begin Source File + +SOURCE=.\stb_static.c +# End Source File +# Begin Source File + +SOURCE=..\stb_textedit.h +# End Source File +# Begin Source File + +SOURCE=..\stb_tilemap_editor.h +# End Source File +# Begin Source File + +SOURCE=..\stb_truetype.h +# End Source File +# Begin Source File + +SOURCE=..\stb_vorbis.c +# End Source File +# Begin Source File + +SOURCE=..\stb_voxel_render.h +# End Source File +# Begin Source File + +SOURCE=.\test_c_compilation.c +# End Source File +# Begin Source File + +SOURCE=.\test_c_lexer.c +# End Source File +# Begin Source File + +SOURCE=.\test_ds.c +# End Source File +# Begin Source File + +SOURCE=.\test_dxt.c +# End Source File +# Begin Source File + +SOURCE=.\test_easyfont.c +# End Source File +# Begin Source File + +SOURCE=.\test_image.c +# End Source File +# Begin Source File + +SOURCE=.\test_image_write.c +# End Source File +# Begin Source File + +SOURCE=.\test_packer.c +# End Source File +# Begin Source File + +SOURCE=.\test_perlin.c +# End Source File +# Begin Source File + +SOURCE=.\test_sprintf.c +# End Source File +# Begin Source File + +SOURCE=.\test_truetype.c +# End Source File +# Begin Source File + +SOURCE=.\test_vorbis.c +# End Source File +# Begin Source File + +SOURCE=.\test_voxel.c +# End Source File +# Begin Source File + +SOURCE=.\textedit_sample.c +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/stb/tests/stb.dsw b/source/engine/thirdparty/stb/tests/stb.dsw new file mode 100644 index 0000000..eae18c8 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/stb.dsw @@ -0,0 +1,158 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "c_lexer_test"=.\c_lexer_test.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "herringbone"=.\herringbone.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "herringbone_map"=.\herringbone_map.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "image_test"=.\image_test.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "make_readme"=..\tools\make_readme.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "resize"=.\resize.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "stb"=.\stb.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name stb_cpp + End Project Dependency + Begin Project Dependency + Project_Dep_Name image_test + End Project Dependency + Begin Project Dependency + Project_Dep_Name c_lexer_test + End Project Dependency +}}} + +############################################################################### + +Project: "stb_cpp"=.\stb_cpp.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "stblib"=.\stblib.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "unicode"=..\tools\unicode\unicode.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "vorbseek"=.\vorbseek\vorbseek.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/source/engine/thirdparty/stb/tests/stb_c_lexer_fuzzer.cpp b/source/engine/thirdparty/stb/tests/stb_c_lexer_fuzzer.cpp new file mode 100644 index 0000000..f6dd04a --- /dev/null +++ b/source/engine/thirdparty/stb/tests/stb_c_lexer_fuzzer.cpp @@ -0,0 +1,74 @@ +#define STB_C_LEX_C_DECIMAL_INTS Y +#define STB_C_LEX_C_HEX_INTS Y +#define STB_C_LEX_C_OCTAL_INTS Y +#define STB_C_LEX_C_DECIMAL_FLOATS Y +#define STB_C_LEX_C99_HEX_FLOATS Y +#define STB_C_LEX_C_IDENTIFIERS Y +#define STB_C_LEX_C_DQ_STRINGS Y +#define STB_C_LEX_C_SQ_STRINGS Y +#define STB_C_LEX_C_CHARS Y +#define STB_C_LEX_C_COMMENTS Y +#define STB_C_LEX_CPP_COMMENTS Y +#define STB_C_LEX_C_COMPARISONS Y +#define STB_C_LEX_C_LOGICAL Y +#define STB_C_LEX_C_SHIFTS Y +#define STB_C_LEX_C_INCREMENTS Y +#define STB_C_LEX_C_ARROW Y +#define STB_C_LEX_EQUAL_ARROW Y +#define STB_C_LEX_C_BITWISEEQ Y +#define STB_C_LEX_C_ARITHEQ Y + +#define STB_C_LEX_PARSE_SUFFIXES Y +#define STB_C_LEX_DECIMAL_SUFFIXES "uUlL" +#define STB_C_LEX_HEX_SUFFIXES "lL" +#define STB_C_LEX_OCTAL_SUFFIXES "lL" +#define STB_C_LEX_FLOAT_SUFFIXES "uulL" + +#define STB_C_LEX_0_IS_EOF N +#define STB_C_LEX_INTEGERS_AS_DOUBLES N +#define STB_C_LEX_MULTILINE_DSTRINGS Y +#define STB_C_LEX_MULTILINE_SSTRINGS Y +#define STB_C_LEX_USE_STDLIB N +#define STB_C_LEX_DOLLAR_IDENTIFIER Y +#define STB_C_LEX_FLOAT_NO_DECIMAL Y + +#define STB_C_LEX_DEFINE_ALL_TOKEN_NAMES Y +#define STB_C_LEX_DISCARD_PREPROCESSOR Y +#define STB_C_LEXER_DEFINITIONS + +#define STB_C_LEXER_IMPLEMENTATION +#define STB_C_LEXER_SELF_TEST +#include "../stb_c_lexer.h" +#include +#include +#include +#include + + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + if(size<3){ + return 0; + } + char *input_stream = (char *)malloc(size); + if (input_stream == NULL){ + return 0; + } + memcpy(input_stream, data, size); + + stb_lexer lex; + char *input_end = input_stream+size-1; + char *store = (char *)malloc(0x10000); + int len = 0x10000; + + stb_c_lexer_init(&lex, input_stream, input_end, store, len); + while (stb_c_lexer_get_token(&lex)) { + if (lex.token == CLEX_parse_error) { + break; + } + } + + free(input_stream); + free(store); + return 0; +} diff --git a/source/engine/thirdparty/stb/tests/stb_cpp.cpp b/source/engine/thirdparty/stb/tests/stb_cpp.cpp new file mode 100644 index 0000000..65373a9 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/stb_cpp.cpp @@ -0,0 +1,85 @@ +#define WIN32_MEAN_AND_LEAN +#define WIN32_LEAN_AND_MEAN +//#include +#include +#define STB_DEFINE +#ifndef _M_AMD64 +#define STB_NPTR +#endif +#define STB_ONLY +#include "stb.h" +//#include "stb_file.h" + +int count; +void c(int truth, const char *error) +{ + if (!truth) { + fprintf(stderr, "Test failed: %s\n", error); + ++count; + } +} + +char *expects(stb_matcher *m, char *s, int result, int len, const char *str) +{ + int res2,len2=0; + res2 = stb_lex(m, s, &len2); + c(result == res2 && len == len2, str); + return s + len; +} + +void test_lex(void) +{ + stb_matcher *m = stb_lex_matcher(); + // tok_en5 .3 20.1 20. .20 .1 + char *s = (char*) "tok_en5.3 20.1 20. .20.1"; + + stb_lex_item(m, "[a-zA-Z_][a-zA-Z0-9_]*", 1 ); + stb_lex_item(m, "[0-9]*\\.?[0-9]*" , 2 ); + stb_lex_item(m, "[\r\n\t ]+" , 3 ); + stb_lex_item(m, "." , -99 ); + s=expects(m,s,1,7, "stb_lex 1"); + s=expects(m,s,2,2, "stb_lex 2"); + s=expects(m,s,3,1, "stb_lex 3"); + s=expects(m,s,2,4, "stb_lex 4"); + s=expects(m,s,3,1, "stb_lex 5"); + s=expects(m,s,2,3, "stb_lex 6"); + s=expects(m,s,3,1, "stb_lex 7"); + s=expects(m,s,2,3, "stb_lex 8"); + s=expects(m,s,2,2, "stb_lex 9"); + s=expects(m,s,0,0, "stb_lex 10"); + stb_matcher_free(m); +} + +int main(int argc, char **argv) +{ +#if 0 + char *p; + p = (char*) "abcdefghijklmnopqrstuvwxyz"; + c(stb_ischar('c', p), "stb_ischar 1"); + c(stb_ischar('x', p), "stb_ischar 2"); + c(!stb_ischar('#', p), "stb_ischar 3"); + c(!stb_ischar('X', p), "stb_ischar 4"); + p = (char*) "0123456789"; + c(!stb_ischar('c', p), "stb_ischar 5"); + c(!stb_ischar('x', p), "stb_ischar 6"); + c(!stb_ischar('#', p), "stb_ischar 7"); + c(!stb_ischar('X', p), "stb_ischar 8"); + p = (char*) "#####"; + c(!stb_ischar('c', p), "stb_ischar a"); + c(!stb_ischar('x', p), "stb_ischar b"); + c(stb_ischar('#', p), "stb_ischar c"); + c(!stb_ischar('X', p), "stb_ischar d"); + p = (char*) "xXyY"; + c(!stb_ischar('c', p), "stb_ischar e"); + c(stb_ischar('x', p), "stb_ischar f"); + c(!stb_ischar('#', p), "stb_ischar g"); + c(stb_ischar('X', p), "stb_ischar h"); +#endif + + test_lex(); + + if (count) { + _getch(); + } + return 0; +} diff --git a/source/engine/thirdparty/stb/tests/stb_cpp.dsp b/source/engine/thirdparty/stb/tests/stb_cpp.dsp new file mode 100644 index 0000000..8bf9975 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/stb_cpp.dsp @@ -0,0 +1,98 @@ +# Microsoft Developer Studio Project File - Name="stb_cpp" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=stb_cpp - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "stb_cpp.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "stb_cpp.mak" CFG="stb_cpp - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "stb_cpp - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "stb_cpp - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "stb_cpp - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I ".." /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "stb_cpp - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug\stb_cpp" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /GX /Zd /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "stb_cpp - Win32 Release" +# Name "stb_cpp - Win32 Debug" +# Begin Source File + +SOURCE=.\stb_cpp.cpp +# End Source File +# Begin Source File + +SOURCE=..\stb_vorbis.c +# End Source File +# Begin Source File + +SOURCE=.\test_cpp_compilation.cpp +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/stb/tests/stb_png.dict b/source/engine/thirdparty/stb/tests/stb_png.dict new file mode 100644 index 0000000..2a27994 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/stb_png.dict @@ -0,0 +1,8 @@ +header_png="\x89PNG\x0d\x0a\x1a\x0a" + +section_idat="IDAT" +section_iend="IEND" +section_ihdr="IHDR" +section_plte="PLTE" +section_trns="tRNS" +section_cgbi="CgBI" diff --git a/source/engine/thirdparty/stb/tests/stb_static.c b/source/engine/thirdparty/stb/tests/stb_static.c new file mode 100644 index 0000000..07ce1f9 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/stb_static.c @@ -0,0 +1,12 @@ +#define STBI_WINDOWS_UTF8 +#define STB_IMAGE_STATIC +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#define STB_IMAGE_WRITE_STATIC +#define STB_IMAGE_WRITE_IMPLEMENTATION +//#include "stb_image_write.h" + +#define STBTT_STATIC +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" diff --git a/source/engine/thirdparty/stb/tests/stbi_read_fuzzer.c b/source/engine/thirdparty/stb/tests/stbi_read_fuzzer.c new file mode 100644 index 0000000..c25398e --- /dev/null +++ b/source/engine/thirdparty/stb/tests/stbi_read_fuzzer.c @@ -0,0 +1,28 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#define STB_IMAGE_IMPLEMENTATION + +#include "../stb_image.h" + + +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + int x, y, channels; + + if(!stbi_info_from_memory(data, size, &x, &y, &channels)) return 0; + + /* exit if the image is larger than ~80MB */ + if(y && x > (80000000 / 4) / y) return 0; + + unsigned char *img = stbi_load_from_memory(data, size, &x, &y, &channels, 4); + + free(img); + + return 0; +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/engine/thirdparty/stb/tests/stblib.dsp b/source/engine/thirdparty/stb/tests/stblib.dsp new file mode 100644 index 0000000..c43513d --- /dev/null +++ b/source/engine/thirdparty/stb/tests/stblib.dsp @@ -0,0 +1,102 @@ +# Microsoft Developer Studio Project File - Name="stblib" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=stblib - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "stblib.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "stblib.mak" CFG="stblib - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "stblib - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "stblib - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "stblib - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I ".." /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "stblib - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "stblib - Win32 Release" +# Name "stblib - Win32 Debug" +# Begin Source File + +SOURCE=.\prerelease\stb_lib.h +# End Source File +# Begin Source File + +SOURCE=..\stb_regex.h +# End Source File +# Begin Source File + +SOURCE=.\stblib_test.c +# End Source File +# Begin Source File + +SOURCE=.\stblib_test_companion.c +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/stb/tests/stblib_test.c b/source/engine/thirdparty/stb/tests/stblib_test.c new file mode 100644 index 0000000..9f2e176 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/stblib_test.c @@ -0,0 +1,11 @@ +#include "prerelease/stb_lib.h" +#define STB_LIB_IMPLEMENTATION +#include "prerelease/stb_lib.h" + +//#define STB_REGEX_IMPLEMENTATION +//#include "stb_regex.h" + +int main(int argc, char **argv) +{ + +} \ No newline at end of file diff --git a/source/engine/thirdparty/stb/tests/stblib_test_companion.c b/source/engine/thirdparty/stb/tests/stblib_test_companion.c new file mode 100644 index 0000000..88f233c --- /dev/null +++ b/source/engine/thirdparty/stb/tests/stblib_test_companion.c @@ -0,0 +1,4 @@ +//#include "stb_regex.h" +//#include "stb_regex.h" +#include "prerelease/stb_lib.h" +#include "prerelease/stb_lib.h" diff --git a/source/engine/thirdparty/stb/tests/stretch_test.dsp b/source/engine/thirdparty/stb/tests/stretch_test.dsp new file mode 100644 index 0000000..dd0442c --- /dev/null +++ b/source/engine/thirdparty/stb/tests/stretch_test.dsp @@ -0,0 +1,89 @@ +# Microsoft Developer Studio Project File - Name="stretch_test" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=stretch_test - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "stretch_test.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "stretch_test.mak" CFG="stretch_test - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "stretch_test - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "stretch_test - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "stretch_test - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\.." /I ".." /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "TT_TEST" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "stretch_test - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "stretch_test___Win32_Debug" +# PROP BASE Intermediate_Dir "stretch_test___Win32_Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug\stretch_test" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "stretch_test - Win32 Release" +# Name "stretch_test - Win32 Debug" +# Begin Source File + +SOURCE=.\stretch_test.c +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/stb/tests/test.sbm b/source/engine/thirdparty/stb/tests/test.sbm new file mode 100644 index 0000000..9465605 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test.sbm @@ -0,0 +1,60 @@ +[link] +-Xlinker advapi32.lib + +[args] +-I .. -Wall -D_DEBUG + +[compilers] +#clang for x64,vcvars_2015_x64,clang --analyze +clang for x64,vcvars_2015_x64,clang +clang for x86,vcvars_2015_x86,clang --target=i386-pc-windows-msvc + +##### STATIC ANALYSIS +# +#[link] +# +#[args] +#-I .. -Wall -D_DEBUG +# +#[compilers] +#clang for x64,vcvars_2015_x64,clang --analyze +# +##### + +[link] +advapi32.lib + +[args] +/nologo -I .. -W3 -WX -D_DEBUG + +[compilers] +VS2015 for x64, vcvars_2015_x64 +VC6 , vcvars_vc6 +VS2008 for x86, vcvars_2008_x86 +VS2013 for x86, vcvars_2013_x86 +VS2015 for x86, vcvars_2015_x86 +clang-cl for x64, vcvars_2015_x64, clang-cl +clang-cl for x86, vcvars_2015_x86, clang-cl --target=i386-pc-windows-msvc +#these batch files don't path a cl executable on my machine?!? +#VS2008 for x64, vcvars_2008_x64 +#VS2013 for x64, vcvars_2013_x64 + +[projects] +c_lexer_test.c +image_test.c image_write_test.c +test_cpp_compilation.cpp stb_cpp.cpp ../stb_vorbis.c +resample_test.cpp +-DTT_TEST test_c_compilation.c test_truetype.c +main.c stb.c +main.c stretchy_buffer_test.c +main.c test_c_compilation.c +main.c test_c_lexer.c +main.c test_dxt.c +main.c test_easyfont.c +main.c test_image.c +main.c test_image_write.c +main.c test_perlin.c +main.c test_sprintf.c +main.c test_vorbis.c ../stb_vorbis.c +main.c test_voxel.c +main.c textedit_sample.c diff --git a/source/engine/thirdparty/stb/tests/test_c_compilation.c b/source/engine/thirdparty/stb/tests/test_c_compilation.c new file mode 100644 index 0000000..8141d2b --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_c_compilation.c @@ -0,0 +1,50 @@ +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include "stb_image_resize2.h" + +#define STB_SPRINTF_IMPLEMENTATION +#include "stb_sprintf.h" + +#define STB_PERLIN_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION +#define STB_C_LEXER_IMPLEMENTATIOn +#define STB_DIVIDE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#define STB_HERRINGBONE_WANG_TILE_IMEPLEMENTATIOn +#define STB_RECT_PACK_IMPLEMENTATION +#define STB_VOXEL_RENDER_IMPLEMENTATION +#define STB_EASY_FONT_IMPLEMENTATION +#define STB_DXT_IMPLEMENTATION +#define STB_INCLUDE_IMPLEMENTATION + +#include "stb_herringbone_wang_tile.h" +#include "stb_image.h" +#include "stb_image_write.h" +#include "stb_perlin.h" +#include "stb_c_lexer.h" +#include "stb_divide.h" +#include "stb_rect_pack.h" +#include "stb_dxt.h" +#include "stb_include.h" + +#include "stb_ds.h" + +#define STBVOX_CONFIG_MODE 1 +#include "stb_voxel_render.h" + +void STBTE_DRAW_RECT(int x0, int y0, int x1, int y1, unsigned int color) +{ +} + +void STBTE_DRAW_TILE(int x0, int y0, unsigned short id, int highlight, float *data) +{ +} + +#define STB_TILEMAP_EDITOR_IMPLEMENTATION +//#include "stb_tilemap_editor.h" // @TODO: it's broken + +int quicktest(void) +{ + char buffer[999]; + stbsp_sprintf(buffer, "test%%test"); + return 0; +} \ No newline at end of file diff --git a/source/engine/thirdparty/stb/tests/test_c_lexer.c b/source/engine/thirdparty/stb/tests/test_c_lexer.c new file mode 100644 index 0000000..7921b1c --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_c_lexer.c @@ -0,0 +1,50 @@ +#include "stb_c_lexer.h" + +#define STB_C_LEX_C_DECIMAL_INTS Y // "0|[1-9][0-9]*" CLEX_intlit +#define STB_C_LEX_C_HEX_INTS Y // "0x[0-9a-fA-F]+" CLEX_intlit +#define STB_C_LEX_C_OCTAL_INTS Y // "[0-7]+" CLEX_intlit +#define STB_C_LEX_C_DECIMAL_FLOATS Y // "[0-9]*(.[0-9]*([eE][-+]?[0-9]+)?) CLEX_floatlit +#define STB_C_LEX_C99_HEX_FLOATS N // "0x{hex}+(.{hex}*)?[pP][-+]?{hex}+ CLEX_floatlit +#define STB_C_LEX_C_IDENTIFIERS Y // "[_a-zA-Z][_a-zA-Z0-9]*" CLEX_id +#define STB_C_LEX_C_DQ_STRINGS Y // double-quote-delimited strings with escapes CLEX_dqstring +#define STB_C_LEX_C_SQ_STRINGS N // single-quote-delimited strings with escapes CLEX_ssstring +#define STB_C_LEX_C_CHARS Y // single-quote-delimited character with escape CLEX_charlits +#define STB_C_LEX_C_COMMENTS Y // "/* comment */" +#define STB_C_LEX_CPP_COMMENTS Y // "// comment to end of line\n" +#define STB_C_LEX_C_COMPARISONS Y // "==" CLEX_eq "!=" CLEX_noteq "<=" CLEX_lesseq ">=" CLEX_greatereq +#define STB_C_LEX_C_LOGICAL Y // "&&" CLEX_andand "||" CLEX_oror +#define STB_C_LEX_C_SHIFTS Y // "<<" CLEX_shl ">>" CLEX_shr +#define STB_C_LEX_C_INCREMENTS Y // "++" CLEX_plusplus "--" CLEX_minusminus +#define STB_C_LEX_C_ARROW Y // "->" CLEX_arrow +#define STB_C_LEX_EQUAL_ARROW N // "=>" CLEX_eqarrow +#define STB_C_LEX_C_BITWISEEQ Y // "&=" CLEX_andeq "|=" CLEX_oreq "^=" CLEX_xoreq +#define STB_C_LEX_C_ARITHEQ Y // "+=" CLEX_pluseq "-=" CLEX_minuseq + // "*=" CLEX_muleq "/=" CLEX_diveq "%=" CLEX_modeq + // if both STB_C_LEX_SHIFTS & STB_C_LEX_ARITHEQ: + // "<<=" CLEX_shleq ">>=" CLEX_shreq + +#define STB_C_LEX_PARSE_SUFFIXES N // letters after numbers are parsed as part of those numbers, and must be in suffix list below +#define STB_C_LEX_DECIMAL_SUFFIXES "" // decimal integer suffixes e.g. "uUlL" -- these are returned as-is in string storage +#define STB_C_LEX_HEX_SUFFIXES "" // e.g. "uUlL" +#define STB_C_LEX_OCTAL_SUFFIXES "" // e.g. "uUlL" +#define STB_C_LEX_FLOAT_SUFFIXES "" // + +#define STB_C_LEX_0_IS_EOF Y // if Y, ends parsing at '\0'; if N, returns '\0' as token +#define STB_C_LEX_INTEGERS_AS_DOUBLES N // parses integers as doubles so they can be larger than 'int', but only if STB_C_LEX_STDLIB==N +#define STB_C_LEX_MULTILINE_DSTRINGS N // allow newlines in double-quoted strings +#define STB_C_LEX_MULTILINE_SSTRINGS N // allow newlines in single-quoted strings +#define STB_C_LEX_USE_STDLIB Y // use strtod,strtol for parsing #s; otherwise inaccurate hack +#define STB_C_LEX_DOLLAR_IDENTIFIER Y // allow $ as an identifier character +#define STB_C_LEX_FLOAT_NO_DECIMAL Y // allow floats that have no decimal point if they have an exponent + +#define STB_C_LEX_DEFINE_ALL_TOKEN_NAMES N // if Y, all CLEX_ token names are defined, even if never returned + // leaving it as N should help you catch config bugs + +#define STB_C_LEX_DISCARD_PREPROCESSOR Y // discard C-preprocessor directives (e.g. after prepocess + // still have #line, #pragma, etc) + +//#define STB_C_LEX_ISWHITE(str) ... // return length in bytes of whitespace characters if first char is whitespace + +#define STB_C_LEXER_DEFINITIONS // This line prevents the header file from replacing your definitions + +#include "stb_c_lexer.h" diff --git a/source/engine/thirdparty/stb/tests/test_cpp_compilation.cpp b/source/engine/thirdparty/stb/tests/test_cpp_compilation.cpp new file mode 100644 index 0000000..d1d10b8 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_cpp_compilation.cpp @@ -0,0 +1,186 @@ +#define STB_IMAGE_WRITE_STATIC +#define STBIWDEF static inline + +#include "stb_image.h" +#include "stb_rect_pack.h" +#include "stb_truetype.h" +#include "stb_image_write.h" +#include "stb_c_lexer.h" +#include "stb_perlin.h" +#include "stb_dxt.h" +#include "stb_divide.h" +#include "stb_herringbone_wang_tile.h" +#include "stb_ds.h" +#include "stb_hexwave.h" + +#include "stb_sprintf.h" +#define STB_SPRINTF_IMPLEMENTATION +#include "stb_sprintf.h" + + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#define STB_TRUETYPE_IMPLEMENTATION +#define STB_PERLIN_IMPLEMENTATION +#define STB_DXT_IMPLEMENATION +#define STB_C_LEXER_IMPLEMENTATIOn +#define STB_DIVIDE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#define STB_HERRINGBONE_WANG_TILE_IMPLEMENTATION +#define STB_RECT_PACK_IMPLEMENTATION +#define STB_VOXEL_RENDER_IMPLEMENTATION +#define STB_CONNECTED_COMPONENTS_IMPLEMENTATION +#define STB_HEXWAVE_IMPLEMENTATION +#define STB_DS_IMPLEMENTATION +#define STBDS_UNIT_TESTS + +#define STBI_MALLOC my_malloc +#define STBI_FREE my_free +#define STBI_REALLOC my_realloc + +void *my_malloc(size_t) { return 0; } +void *my_realloc(void *, size_t) { return 0; } +void my_free(void *) { } + +#include "stb_image.h" +#include "stb_rect_pack.h" +#include "stb_truetype.h" +#include "stb_image_write.h" +#include "stb_perlin.h" +#include "stb_dxt.h" +#include "stb_divide.h" +#include "stb_herringbone_wang_tile.h" +#include "stb_ds.h" +#include "stb_hexwave.h" + +#define STBCC_GRID_COUNT_X_LOG2 10 +#define STBCC_GRID_COUNT_Y_LOG2 10 +#include "stb_connected_components.h" + +#define STBVOX_CONFIG_MODE 1 +#include "stb_voxel_render.h" + +#define STBTE_DRAW_RECT(x0,y0,x1,y1,color) do ; while(0) +#define STBTE_DRAW_TILE(x,y,id,highlight,data) do ; while(0) +#define STB_TILEMAP_EDITOR_IMPLEMENTATION +#include "stb_tilemap_editor.h" + +#include "stb_easy_font.h" + +#define STB_LEAKCHECK_IMPLEMENTATION +#include "stb_leakcheck.h" + +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include "stb_image_resize2.h" + +//#include "stretchy_buffer.h" // deprecating + + +// avoid unused-function complaints +void dummy2(void) +{ + stb_easy_font_spacing(1.0); + stb_easy_font_print(0,0,NULL,NULL,NULL,0); + stb_easy_font_width(NULL); + stb_easy_font_height(NULL); +} + + +//////////////////////////////////////////////////////////// +// +// text edit + +#include +#include // memmove +#include // isspace + +#define STB_TEXTEDIT_CHARTYPE char +#define STB_TEXTEDIT_STRING text_control + +// get the base type +#include "stb_textedit.h" + +// define our editor structure +typedef struct +{ + char *string; + int stringlen; + STB_TexteditState state; +} text_control; + +// define the functions we need +void layout_func(StbTexteditRow *row, STB_TEXTEDIT_STRING *str, int start_i) +{ + int remaining_chars = str->stringlen - start_i; + row->num_chars = remaining_chars > 20 ? 20 : remaining_chars; // should do real word wrap here + row->x0 = 0; + row->x1 = 20; // need to account for actual size of characters + row->baseline_y_delta = 1.25; + row->ymin = -1; + row->ymax = 0; +} + +int delete_chars(STB_TEXTEDIT_STRING *str, int pos, int num) +{ + memmove(&str->string[pos], &str->string[pos+num], str->stringlen - (pos+num)); + str->stringlen -= num; + return 1; // always succeeds +} + +int insert_chars(STB_TEXTEDIT_STRING *str, int pos, STB_TEXTEDIT_CHARTYPE *newtext, int num) +{ + str->string = (char *) realloc(str->string, str->stringlen + num); + memmove(&str->string[pos+num], &str->string[pos], str->stringlen - pos); + memcpy(&str->string[pos], newtext, num); + str->stringlen += num; + return 1; // always succeeds +} + +// define all the #defines needed + +#define KEYDOWN_BIT 0x40000000 + +#define STB_TEXTEDIT_STRINGLEN(tc) ((tc)->stringlen) +#define STB_TEXTEDIT_LAYOUTROW layout_func +#define STB_TEXTEDIT_GETWIDTH(tc,n,i) (1) // quick hack for monospaced +#define STB_TEXTEDIT_KEYTOTEXT(key) (((key) & KEYDOWN_BIT) ? 0 : (key)) +#define STB_TEXTEDIT_GETCHAR(tc,i) ((tc)->string[i]) +#define STB_TEXTEDIT_NEWLINE '\n' +#define STB_TEXTEDIT_IS_SPACE(ch) isspace(ch) +#define STB_TEXTEDIT_DELETECHARS delete_chars +#define STB_TEXTEDIT_INSERTCHARS insert_chars + +#define STB_TEXTEDIT_K_SHIFT 0x20000000 +#define STB_TEXTEDIT_K_CONTROL 0x10000000 +#define STB_TEXTEDIT_K_LEFT (KEYDOWN_BIT | 1) // actually use VK_LEFT, SDLK_LEFT, etc +#define STB_TEXTEDIT_K_RIGHT (KEYDOWN_BIT | 2) // VK_RIGHT +#define STB_TEXTEDIT_K_UP (KEYDOWN_BIT | 3) // VK_UP +#define STB_TEXTEDIT_K_DOWN (KEYDOWN_BIT | 4) // VK_DOWN +#define STB_TEXTEDIT_K_LINESTART (KEYDOWN_BIT | 5) // VK_HOME +#define STB_TEXTEDIT_K_LINEEND (KEYDOWN_BIT | 6) // VK_END +#define STB_TEXTEDIT_K_TEXTSTART (STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_CONTROL) +#define STB_TEXTEDIT_K_TEXTEND (STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_CONTROL) +#define STB_TEXTEDIT_K_DELETE (KEYDOWN_BIT | 7) // VK_DELETE +#define STB_TEXTEDIT_K_BACKSPACE (KEYDOWN_BIT | 8) // VK_BACKSPACE +#define STB_TEXTEDIT_K_UNDO (KEYDOWN_BIT | STB_TEXTEDIT_K_CONTROL | 'z') +#define STB_TEXTEDIT_K_REDO (KEYDOWN_BIT | STB_TEXTEDIT_K_CONTROL | 'y') +#define STB_TEXTEDIT_K_INSERT (KEYDOWN_BIT | 9) // VK_INSERT +#define STB_TEXTEDIT_K_WORDLEFT (STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_CONTROL) +#define STB_TEXTEDIT_K_WORDRIGHT (STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_CONTROL) +#define STB_TEXTEDIT_K_PGUP (KEYDOWN_BIT | 10) // VK_PGUP -- not implemented +#define STB_TEXTEDIT_K_PGDOWN (KEYDOWN_BIT | 11) // VK_PGDOWN -- not implemented + +#define STB_TEXTEDIT_IMPLEMENTATION +#include "stb_textedit.h" + + +void dummy3(void) +{ + stb_textedit_click(0,0,0,0); + stb_textedit_drag(0,0,0,0); + stb_textedit_cut(0,0); + stb_textedit_key(0,0,0); + stb_textedit_initialize_state(0,0); + stb_textedit_paste(0,0,0,0); +} + +#include "stb_c_lexer.h" diff --git a/source/engine/thirdparty/stb/tests/test_ds.c b/source/engine/thirdparty/stb/tests/test_ds.c new file mode 100644 index 0000000..fffff8d --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_ds.c @@ -0,0 +1,1034 @@ +#ifdef DS_PERF +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NON_CONFORMING_SWPRINTFS + + +//#define STBDS_INTERNAL_SMALL_BUCKET // make 64-bit bucket fit both keys and hash bits +//#define STBDS_SIPHASH_2_4 // performance test 1_3 against 2_4 +//#define STBDS_INTERNAL_BUCKET_START // don't bother offseting differently within bucket for different hash values +//#define STBDS_FLUSH_CACHE (1u<<20) // do this much memory traffic to flush the cache between some benchmarking measurements + +#include + +#define WIN32_LEAN_AND_MEAN +#include +#define STB_DEFINE +#define STB_NO_REGISTRY +#include "../stb.h" +#endif + +#ifdef DS_TEST +#define STBDS_UNIT_TESTS +#define STBDS_SMALL_BUCKET +#endif + +#ifdef DS_STATS +#define STBDS_STATISTICS +#endif + +#ifndef DS_PERF +#define STBDS_ASSERT assert +#include +#endif + +#define STB_DS_IMPLEMENTATION +#include "../stb_ds.h" + +size_t churn_inserts, churn_deletes; + +void churn(int a, int b, int count) +{ + struct { int key,value; } *map=NULL; + int i,j,n,k; + for (i=0; i < a; ++i) + hmput(map,i,i+1); + for (n=0; n < count; ++n) { + for (j=a; j < b; ++j,++i) { + hmput(map,i,i+1); + } + assert(hmlen(map) == b); + for (j=a; j < b; ++j) { + k=i-j-1; + k = hmdel(map,k); + assert(k != 0); + } + assert(hmlen(map) == a); + } + hmfree(map); + churn_inserts = i; + churn_deletes = (b-a) * n; +} + +#ifdef DS_TEST +#include +int main(int argc, char **argv) +{ + char *temp=NULL; + stbds_unit_tests(); + arrins(temp, 0, 'a'); + arrins(temp, arrlen(temp), 'b'); + churn(0,100,1); + churn(3,7,50000); + churn(3,15,50000); + churn(16, 48, 25000); + churn(10, 15, 25000); + churn(200,500, 5000); + churn(2000,5000, 500); + churn(20000,50000, 50); + printf("Ok!"); + return 0; +} +#endif + +#ifdef DS_STATS +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +size_t max_hit_probes, max_miss_probes, total_put_probes, total_miss_probes, churn_misses; +void churn_stats(int a, int b, int count) +{ + struct { int key,value; } *map=NULL; + int i,j,n,k; + churn_misses = 0; + for (i=0; i < a; ++i) { + hmput(map,i,i+1); + max_hit_probes = MAX(max_hit_probes, stbds_hash_probes); + total_put_probes += stbds_hash_probes; + stbds_hash_probes = 0; + } + + for (n=0; n < count; ++n) { + for (j=a; j < b; ++j,++i) { + hmput(map,i,i+1); + max_hit_probes = MAX(max_hit_probes, stbds_hash_probes); + total_put_probes += stbds_hash_probes; + stbds_hash_probes = 0; + } + for (j=0; j < (b-a)*10; ++j) { + k=i+j; + (void) hmgeti(map,k); // miss + max_miss_probes = MAX(max_miss_probes, stbds_hash_probes); + total_miss_probes += stbds_hash_probes; + stbds_hash_probes = 0; + ++churn_misses; + } + assert(hmlen(map) == b); + for (j=a; j < b; ++j) { + k=i-j-1; + k = hmdel(map,k); + stbds_hash_probes = 0; + assert(k); + } + assert(hmlen(map) == a); + } + hmfree(map); + churn_inserts = i; + churn_deletes = (b-a) * n; +} + +void reset_stats(void) +{ + stbds_array_grow=0, + stbds_hash_grow=0; + stbds_hash_shrink=0; + stbds_hash_rebuild=0; + stbds_hash_probes=0; + stbds_hash_alloc=0; + stbds_rehash_probes=0; + stbds_rehash_items=0; + max_hit_probes = 0; + max_miss_probes = 0; + total_put_probes = 0; + total_miss_probes = 0; +} + +void print_churn_probe_stats(char *str) +{ + printf("Probes: %3d max hit, %3d max miss, %4.2f avg hit, %4.2f avg miss: %s\n", + (int) max_hit_probes, (int) max_miss_probes, (float) total_put_probes / churn_inserts, (float) total_miss_probes / churn_misses, str); + reset_stats(); +} + +int main(int arg, char **argv) +{ + churn_stats(0,500000,1); print_churn_probe_stats("Inserting 500000 items"); + churn_stats(0,500000,1); print_churn_probe_stats("Inserting 500000 items"); + churn_stats(0,500000,1); print_churn_probe_stats("Inserting 500000 items"); + churn_stats(0,500000,1); print_churn_probe_stats("Inserting 500000 items"); + churn_stats(49000,50000,500); print_churn_probe_stats("Deleting/Inserting 500000 items"); + churn_stats(49000,50000,500); print_churn_probe_stats("Deleting/Inserting 500000 items"); + churn_stats(49000,50000,500); print_churn_probe_stats("Deleting/Inserting 500000 items"); + churn_stats(49000,50000,500); print_churn_probe_stats("Deleting/Inserting 500000 items"); + return 0; +} +#endif + + +#ifdef DS_PERF +//char *strdup(const char *foo) { return 0; } +//int stricmp(const char *a, const char *b) { return 0; } +//int strnicmp(const char *a, const char *b, size_t n) { return 0; } + +unsigned __int64 t0, xsum, mn,mx,count; +void begin(void) +{ + LARGE_INTEGER m; + QueryPerformanceCounter(&m); + t0 = m.QuadPart; + xsum = 0; + count = 0; + mx = 0; + mn = ~(unsigned __int64) 0; +} + +void measure(void) +{ + unsigned __int64 t1, t; + LARGE_INTEGER m; + QueryPerformanceCounter(&m); + t1 = m.QuadPart; + t = t1-t0; + if (t1 < t0) + printf("ALERT: QueryPerformanceCounter was unordered!\n"); + if (t < mn) mn = t; + if (t > mx) mx = t; + xsum += t; + ++count; + t0 = t1; +} + +void dont_measure(void) +{ + LARGE_INTEGER m; + QueryPerformanceCounter(&m); + t0 = m.QuadPart; +} + +double timer; +double end(void) +{ + LARGE_INTEGER m; + QueryPerformanceFrequency(&m); + + if (count > 3) { + // discard the highest and lowest + xsum -= mn; + xsum -= mx; + count -= 2; + } + timer = (double) (xsum) / count / m.QuadPart * 1000; + return timer; +} + +void build(int a, int b, int count, int step) +{ + struct { int key,value; } *map=NULL; + int i,n; + for (i=0; i < a; ++i) { + n = i*step; + hmput(map,n,i+1); + } + measure(); + churn_inserts = i; + hmfree(map); + dont_measure(); +} + +#ifdef STB__INCLUDE_STB_H +void build_stb(int a, int b, int count, int step) +{ + stb_idict *d = stb_idict_new_size(8); + int i; + for (i=0; i < a; ++i) + stb_idict_add(d, i*step, i+1); + measure(); + churn_inserts = i; + stb_idict_destroy(d); + dont_measure(); +} + +void multibuild_stb(int a, int b, int count, int step, int tables) +{ + stb_idict *d[50000]; + int i,q; + for (q=0; q < tables; ++q) + d[q] = stb_idict_new_size(8); + dont_measure(); + for (i=0; i < a; ++i) + for (q=0; q < tables; ++q) + stb_idict_add(d[q], i*step+q*771, i+1); + measure(); + churn_inserts = i; + for (q=0; q < tables; ++q) + stb_idict_destroy(d[q]); + dont_measure(); +} + +int multisearch_stb(int a, int start, int end, int step, int tables) +{ + stb_idict *d[50000]; + int i,q,total=0,v; + for (q=0; q < tables; ++q) + d[q] = stb_idict_new_size(8); + for (q=0; q < tables; ++q) + for (i=0; i < a; ++i) + stb_idict_add(d[q], i*step+q*771, i+1); + dont_measure(); + for (i=start; i < end; ++i) + for (q=0; q < tables; ++q) + if (stb_idict_get_flag(d[q], i*step+q*771, &v)) + total += v; + measure(); + churn_inserts = i; + for (q=0; q < tables; ++q) + stb_idict_destroy(d[q]); + dont_measure(); + return total; +} +#endif + +int multisearch(int a, int start, int end, int step, int tables) +{ + struct { int key,value; } *hash[50000]; + int i,q,total=0; + for (q=0; q < tables; ++q) + hash[q] = NULL; + for (q=0; q < tables; ++q) + for (i=0; i < a; ++i) + hmput(hash[q], i*step+q*771, i+1); + dont_measure(); + for (i=start; i < end; ++i) + for (q=0; q < tables; ++q) + total += hmget(hash[q], i*step+q*771); + measure(); + churn_inserts = i; + for (q=0; q < tables; ++q) + hmfree(hash[q]); + dont_measure(); + return total; +} + +void churn_skip(unsigned int a, unsigned int b, int count) +{ + struct { unsigned int key,value; } *map=NULL; + unsigned int i,j,n,k; + for (i=0; i < a; ++i) + hmput(map,i,i+1); + dont_measure(); + for (n=0; n < count; ++n) { + for (j=a; j < b; ++j,++i) { + hmput(map,i,i+1); + } + assert(hmlen(map) == b); + for (j=a; j < b; ++j) { + k=i-j-1; + k = hmdel(map,k); + assert(k != 0); + } + assert(hmlen(map) == a); + } + measure(); + churn_inserts = i; + churn_deletes = (b-a) * n; + hmfree(map); + dont_measure(); +} + +typedef struct { int n[8]; } str32; +void churn32(int a, int b, int count, int include_startup) +{ + struct { str32 key; int value; } *map=NULL; + int i,j,n; + str32 key = { 0 }; + for (i=0; i < a; ++i) { + key.n[0] = i; + hmput(map,key,i+1); + } + if (!include_startup) + dont_measure(); + for (n=0; n < count; ++n) { + for (j=a; j < b; ++j,++i) { + key.n[0] = i; + hmput(map,key,i+1); + } + assert(hmlen(map) == b); + for (j=a; j < b; ++j) { + key.n[0] = i-j-1; + hmdel(map,key); + } + assert(hmlen(map) == a); + } + measure(); + hmfree(map); + churn_inserts = i; + churn_deletes = (b-a) * n; + dont_measure(); +} + +typedef struct { int n[32]; } str256; +void churn256(int a, int b, int count, int include_startup) +{ + struct { str256 key; int value; } *map=NULL; + int i,j,n; + str256 key = { 0 }; + for (i=0; i < a; ++i) { + key.n[0] = i; + hmput(map,key,i+1); + } + if (!include_startup) + dont_measure(); + for (n=0; n < count; ++n) { + for (j=a; j < b; ++j,++i) { + key.n[0] = i; + hmput(map,key,i+1); + } + assert(hmlen(map) == b); + for (j=a; j < b; ++j) { + key.n[0] = i-j-1; + hmdel(map,key); + } + assert(hmlen(map) == a); + } + measure(); + hmfree(map); + churn_inserts = i; + churn_deletes = (b-a) * n; + dont_measure(); +} + +void churn8(int a, int b, int count, int include_startup) +{ + struct { size_t key,value; } *map=NULL; + int i,j,n,k; + for (i=0; i < a; ++i) + hmput(map,i,i+1); + if (!include_startup) + dont_measure(); + for (n=0; n < count; ++n) { + for (j=a; j < b; ++j,++i) { + hmput(map,i,i+1); + } + assert(hmlen(map) == b); + for (j=a; j < b; ++j) { + k=i-j-1; + k = hmdel(map,k); + assert(k != 0); + } + assert(hmlen(map) == a); + } + measure(); + hmfree(map); + churn_inserts = i; + churn_deletes = (b-a) * n; + dont_measure(); +} + +void multichurn4(int a, int b, int count, int include_startup, int tables) +{ + struct { int key,value; } *map[50000]; + int i,j,n,k,q; + for (q=0; q < tables; ++q) + map[q] = NULL; + dont_measure(); + + for (i=0; i < a; ++i) + for (q=0; q < tables; ++q) + hmput(map[q],i,i+1); + if (!include_startup) + dont_measure(); + for (n=0; n < count; ++n) { + for (j=a; j < b; ++j,++i) { + for (q=0; q < tables; ++q) + hmput(map[q],i,i+1); + } + assert(hmlen(map[0]) == b); + for (j=a; j < b; ++j) { + k=i-j-1; + for (q=0; q < tables; ++q) + k = hmdel(map[q],k); + assert(k != 0); + } + assert(hmlen(map[0]) == a); + } + measure(); + for (q=0; q < tables; ++q) + hmfree(map[q]); + churn_inserts = i * tables; + churn_deletes = (b-a) * n * tables; + dont_measure(); +} + +struct { + unsigned __int64 start; + unsigned __int64 end; + int table_size; +} mstats[32][4000]; + +const int first_step = 64; +const int last_step = 384-48; // 32M + +void measure_build4(int step_log2) +{ + double length; + int i,j,k=0; + int step = 1 << step_log2; + unsigned __int64 t0,t1; + struct { int key,value; } *map=NULL; + double rdtsc_scale; + begin(); + t0 = __rdtsc(); + + mstats[0][0].start = __rdtsc(); + for (i=0; i < 256; ++i) { + hmput(map,k,k+1); + k += step; + } + mstats[0][first_step-1].end = __rdtsc(); + mstats[0][first_step-1].table_size = k >> step_log2; + for (j=first_step; j < last_step; ++j) { + for (i=0; i < (1<<(j>>4)); ++i) { + hmput(map, k,k+1); + k += step; + } + mstats[0][j].end = __rdtsc(); + mstats[0][j].table_size = k >> step_log2; + } + t1 = __rdtsc(); + measure(); + hmfree(map); + length = end(); + rdtsc_scale = length / (t1-t0) * 1000; + + for (j=1; j < last_step; ++j) + mstats[0][j].start = mstats[0][0].start; + for (j=first_step-1; j < last_step; ++j) { + printf("%12.4f,%12d,%12d,0,0,0\n", (mstats[0][j].end - mstats[0][j].start) * rdtsc_scale, mstats[0][j].table_size, mstats[0][j].table_size); + } +} + +#ifdef STBDS_FLUSH_CACHE +static int cache_index; +char dummy[8][STBDS_FLUSH_CACHE]; + +int flush_cache(void) +{ + memmove(dummy[cache_index],dummy[cache_index]+1, sizeof(dummy[cache_index])-1); + cache_index = (cache_index+1)%8; + return dummy[cache_index][0]; +} +#else +int flush_cache(void) { return 0; } +#endif + +int measure_average_lookup4(int step_log2) +{ + int total; + double length; + int i,j,k=0,q; + int step = 1 << step_log2; + unsigned __int64 t0,t1; + struct { int key,value; } *map=NULL; + double rdtsc_scale; + begin(); + t0 = __rdtsc(); + + for (i=0; i < 128; ++i) { + hmput(map,k,k+1); + k += step; + } + for (j=first_step; j <= last_step; ++j) { + total += flush_cache(); + mstats[0][j].start = __rdtsc(); + for (q=i=0; i < 50000; ++i) { + total += hmget(map, q); // hit + if (++q == k) q = 0; + } + mstats[0][j].end = __rdtsc(); + mstats[0][j].table_size = k; + total += flush_cache(); + mstats[1][j].start = __rdtsc(); + for (i=0; i < 50000; ++i) { + total += hmget(map, i+k); // miss + } + mstats[1][j].end = __rdtsc(); + mstats[1][j].table_size = k; + + // expand table + for (i=0; i < (1<<(j>>4)); ++i) { + hmput(map, k,k+1); + k += step; + } + } + + t1 = __rdtsc(); + measure(); + hmfree(map); + length = end(); + rdtsc_scale = length / (t1-t0) * 1000; + + for (j=first_step; j <= last_step; ++j) { + // time,table_size,numins,numhit,nummiss,numperflush + printf("%12.4f,%12d,0,50000,0,0\n", (mstats[0][j].end - mstats[0][j].start) * rdtsc_scale, mstats[0][j].table_size); + } + for (j=first_step; j <= last_step; ++j) { + printf("%12.4f,%12d,0,0,50000,0\n", (mstats[1][j].end - mstats[1][j].start) * rdtsc_scale, mstats[1][j].table_size); + } + return total; +} + +int measure_worst_lookup4_a(int step_log2) +{ + int total; + double length; + int i,j,k=0,q,worst_q,n,z,attempts; + int step = 1 << step_log2; + unsigned __int64 t0,t1; + unsigned __int64 m0,m1,worst; + struct { int key,value; } *map=NULL; + double rdtsc_scale; + begin(); + t0 = __rdtsc(); + + memset(mstats, 0, sizeof(mstats)); + for (j=first_step; j <= last_step; ++j) + mstats[0][j].end = mstats[1][j].end = ~(unsigned __int64) 0; + + for(attempts=0; attempts < 2; ++attempts) { + k = 0; + stbds_rand_seed(0); // force us to get the same table every time + for (i=0; i < 128; ++i) { + hmput(map,k,k+1); + k += step; + } + for (j=first_step; j <= last_step; ++j) { + unsigned __int64 times[32]; + + // find the worst hit time + for (z=0; z < 2; ++z) { // try the bisectioning measurement 4 times + worst = 0; + for (n=0; n < 10; ++n) { // test 400 keys total + // find the worst time to hit 20 keys + q=0; + worst_q = 0; + total += flush_cache(); + m0 = __rdtsc(); + for (i=0; i < 20; ++i) { + total += hmget(map, q); // hit + if (++q == k) q = 0; + } + m1 = __rdtsc(); + // for each n, check if this is the worst lookup we've seen + if (m1 - m0 > worst) { + worst = m1-m0; + worst_q = q - i; + if (worst_q < 0) q += k; + } + } + // after 400 keys, take the worst 20 keys, and try each one + worst = 0; + q = worst_q; + for (i=0; i < 20; ++i) { + total += flush_cache(); + m0 = __rdtsc(); + total += hmget(map, q); // hit + m1 = __rdtsc(); + if (m1 - m0 > worst) + worst = m1-m0; + if (++q == k) q = 0; + } + times[z] = worst; + } + // find the worst time in the bunch + worst = 0; + for (i=0; i < z; ++i) + if (times[i] > worst) + worst = times[i]; + // take the best of 'attempts', to discard outliers + if (worst < mstats[0][j].end) + mstats[0][j].end = worst; + mstats[0][j].start = 0; + mstats[0][j].table_size = k >> step_log2; + + // find the worst miss time + for (z=0; z < 8; ++z) { // try the bisectioning measurement 8 times + worst = 0; + for (n=0; n < 20; ++n) { // test 400 keys total + // find the worst time to hit 20 keys + q=k; + worst_q = 0; + total += flush_cache(); + m0 = __rdtsc(); + for (i=0; i < 20; ++i) { + total += hmget(map, q); // hit + } + m1 = __rdtsc(); + // for each n, check if this is the worst lookup we've seen + if (m1 - m0 > worst) { + worst = m1-m0; + worst_q = q - i; + } + } + // after 400 keys, take the worst 20 keys, and try each one + worst = 0; + q = worst_q; + for (i=0; i < 20; ++i) { + total += flush_cache(); + m0 = __rdtsc(); + total += hmget(map, q); // hit + m1 = __rdtsc(); + if (m1 - m0 > worst) + worst = m1-m0; + } + times[z] = worst; + } + // find the worst time in the bunch + worst = 0; + for (i=0; i < z; ++i) + if (times[i] > worst) + worst = times[i]; + if (worst < mstats[1][j].end) + mstats[1][j].end = worst; + mstats[1][j].start = 0; + mstats[1][j].table_size = k >> step_log2; + + // expand table + for (i=0; i < (1<<(j>>4)); ++i) { + hmput(map, k,k+1); + k += step; + } + } + hmfree(map); + } + + t1 = __rdtsc(); + measure(); + length = end(); + rdtsc_scale = length / (t1-t0) * 1000; + + for (j=first_step; j <= last_step; ++j) { + printf("%12.4f,%12d,0,1,0,1\n", (mstats[0][j].end - mstats[0][j].start) * rdtsc_scale, mstats[0][j].table_size); + } + for (j=first_step; j <= last_step; ++j) { + printf("%12.4f,%12d,0,0,1,1\n", (mstats[1][j].end - mstats[1][j].start) * rdtsc_scale, mstats[1][j].table_size); + } + return total; +} + +int measure_worst_lookup4_b(int step_log2) +{ + int total; + double length; + int i,j,k=0,q,worst_q,n,z,attempts; + int step = 1 << step_log2; + unsigned __int64 t0,t1; + unsigned __int64 m0,m1,worst; + struct { int key,value; } *map=NULL; + double rdtsc_scale; + begin(); + t0 = __rdtsc(); + + memset(mstats, 0, sizeof(mstats)); + for (j=first_step; j <= last_step; ++j) + mstats[0][j].end = mstats[1][j].end = ~(unsigned __int64) 0; + + k = 0; + stbds_rand_seed(0); // force us to get the same table every time + for (i=0; i < 128; ++i) { + hmput(map,k,k+1); + k += step; + } + for (j=first_step; j <= last_step; ++j) { + unsigned __int64 times[32]; + + // find the worst hit time + for (z=0; z < 8; ++z) { // try this 8 times + worst = 0; + q=0; + for (i=0; i < 5000; ++i) { + total += hmget(map, q); + m0 = __rdtsc(); + total += hmget(map, q); + m1 = __rdtsc(); + if (m1 - m0 > worst) { + worst = m1-m0; + worst_q = q - i; + } + if (++q == k) q = 0; + } + // now retry with the worst one, but find the shortest time for it + worst = ~(unsigned __int64) 0; + for (i=0; i < 4; ++i) { + total += flush_cache(); + m0 = __rdtsc(); + total += hmget(map,worst_q); + m1 = __rdtsc(); + if (m1-m0 < worst) + worst = m1-m0; + } + times[z] = worst; + } + + // find the worst of those + worst = 0; + for (i=0; i < z; ++i) + if (times[i] > worst) + worst = times[i]; + mstats[0][j].start = 0; + mstats[0][j].end = worst; + mstats[0][j].table_size = k; + + // find the worst miss time + for (z=0; z < 8; ++z) { // try this 8 times + worst = 0; + q=k; + for (i=0; i < 5000; ++i) { + total += hmget(map, q); + m0 = __rdtsc(); + total += hmget(map, q); + m1 = __rdtsc(); + if (m1 - m0 > worst) { + worst = m1-m0; + worst_q = q - i; + } + //printf("%6llu ", m1-m0); + } + // now retry with the worst one, but find the shortest time for it + worst = ~(unsigned __int64) 0; + for (i=0; i < 4; ++i) { + total += flush_cache(); + m0 = __rdtsc(); + total += hmget(map,worst_q); + m1 = __rdtsc(); + if (m1-m0 < worst) + worst = m1-m0; + } + times[z] = worst; + } + + // find the worst of those + worst = 0; + for (i=0; i < z; ++i) + if (times[i] > worst) + worst = times[i]; + mstats[1][j].start = 0; + mstats[1][j].end = worst; + mstats[1][j].table_size = k; + + // expand table + for (i=0; i < (1<<(j>>4)); ++i) { + hmput(map, k,k+1); + k += step; + } + } + hmfree(map); + + t1 = __rdtsc(); + measure(); + length = end(); + rdtsc_scale = length / (t1-t0) * 1000; + + for (j=first_step+1; j <= last_step; ++j) { + printf("%12.4f,%12d,0,1,0,1\n", (mstats[0][j].end - mstats[0][j].start) * rdtsc_scale, mstats[0][j].table_size); + } + for (j=first_step+1; j <= last_step; ++j) { + printf("%12.4f,%12d,0,0,1,1\n", (mstats[1][j].end - mstats[1][j].start) * rdtsc_scale, mstats[1][j].table_size); + } + return total; +} + +int measure_uncached_lookup4(int step_log2) +{ + int total; + double length; + int i,j,k=0,q; + int step = 1 << step_log2; + unsigned __int64 t0,t1; + struct { int key,value; } *map=NULL; + double rdtsc_scale; + begin(); + t0 = __rdtsc(); + + map = NULL; + for (i=0; i < 128; ++i) { + hmput(map,k,k+1); + k += step; + } + for (j=first_step; j <= last_step; ++j) { + mstats[0][j].start = __rdtsc(); + mstats[0][j].end = 0; + for (q=i=0; i < 512; ++i) { + if ((i & 3) == 0) { + mstats[0][j].end += __rdtsc(); + total += flush_cache(); + mstats[0][j].start += __rdtsc(); + } + total += hmget(map, q); // hit + if (++q == k) q = 0; + } + mstats[0][j].end += __rdtsc(); + mstats[0][j].table_size = k; + total += flush_cache(); + mstats[1][j].end = 0; + mstats[1][j].start = __rdtsc(); + for (i=0; i < 512; ++i) { + if ((i & 3) == 0) { + mstats[1][j].end += __rdtsc(); + total += flush_cache(); + mstats[1][j].start += __rdtsc(); + } + total += hmget(map, i+k); // miss + } + mstats[1][j].end += __rdtsc(); + mstats[1][j].table_size = k; + + // expand table + for (i=0; i < (1<<(j>>4)); ++i) { + hmput(map, k,k+1); + k += step; + } + } + hmfree(map); + + t1 = __rdtsc(); + measure(); + length = end(); + rdtsc_scale = length / (t1-t0) * 1000; + + for (j=first_step; j <= last_step; ++j) { + printf("%12.4f,%12d,0,512,0,4\n", (mstats[0][j].end - mstats[0][j].start) * rdtsc_scale, mstats[0][j].table_size); + } + for (j=first_step; j <= last_step; ++j) { + printf("%12.4f,%12d,0,0,512,4\n", (mstats[1][j].end - mstats[1][j].start) * rdtsc_scale, mstats[1][j].table_size); + } + return total; +} + + + + +int main(int arg, char **argv) +{ + int n,s,w; + double worst = 0; + + printf("# size_t=%d,", (int) sizeof(size_t)); + +// number of cache-lines +#ifdef STBDS_SMALL_BUCKET + printf("cacheline=%d,", 1); +#else + printf("cacheline=%d,", sizeof(size_t)==8 ? 2 : 1); +#endif +#ifdef STBDS_FLUSH_CACHE + printf("%d,", (int) stbds_log2(STBDS_FLUSH_CACHE)); +#else + printf("0,"); +#endif +#ifdef STBDS_BUCKET_START // don't bother offseting differently within bucket for different hash values + printf("STBDS_BUCKET_START,"); +#else + printf(","); +#endif +#ifdef STBDS_SIPHASH_2_4 + printf("STBDS_SIPHASH_2_4,"); +#else + printf(","); +#endif + printf("\n"); + + measure_worst_lookup4_b(0); + //measure_worst_lookup4_a(0); + measure_average_lookup4(0); + measure_uncached_lookup4(0); + measure_build4(0); + return 0; + +#if 0 + begin(); for (n=0; n < 2000; ++n) { build_stb(2000,0,0,1); } end(); printf(" // %7.2fms : 2,000 inserts creating 2K table\n", timer); + begin(); for (n=0; n < 500; ++n) { build_stb(20000,0,0,1); } end(); printf(" // %7.2fms : 20,000 inserts creating 20K table\n", timer); + begin(); for (n=0; n < 100; ++n) { build_stb(200000,0,0,1); } end(); printf(" // %7.2fms : 200,000 inserts creating 200K table\n", timer); + begin(); for (n=0; n < 10; ++n) { build_stb(2000000,0,0,1); } end(); printf(" // %7.2fms : 2,000,000 inserts creating 2M table\n", timer); + begin(); for (n=0; n < 5; ++n) { build_stb(20000000,0,0,1); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 20M table\n", timer); +#endif + +#if 0 + begin(); for (n=0; n < 2000; ++n) { churn32(2000,0,0,1); } end(); printf(" // %7.2fms : 2,000 inserts creating 2K table w/ 32-byte key\n", timer); + begin(); for (n=0; n < 500; ++n) { churn32(20000,0,0,1); } end(); printf(" // %7.2fms : 20,000 inserts creating 20K table w/ 32-byte key\n", timer); + begin(); for (n=0; n < 100; ++n) { churn32(200000,0,0,1); } end(); printf(" // %7.2fms : 200,000 inserts creating 200K table w/ 32-byte key\n", timer); + begin(); for (n=0; n < 10; ++n) { churn32(2000000,0,0,1); } end(); printf(" // %7.2fms : 2,000,000 inserts creating 2M table w/ 32-byte key\n", timer); + begin(); for (n=0; n < 5; ++n) { churn32(20000000,0,0,1); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 20M table w/ 32-byte key\n", timer); + + begin(); for (n=0; n < 2000; ++n) { churn256(2000,0,0,1); } end(); printf(" // %7.2fms : 2,000 inserts creating 2K table w/ 256-byte key\n", timer); + begin(); for (n=0; n < 500; ++n) { churn256(20000,0,0,1); } end(); printf(" // %7.2fms : 20,000 inserts creating 20K table w/ 256-byte key\n", timer); + begin(); for (n=0; n < 100; ++n) { churn256(200000,0,0,1); } end(); printf(" // %7.2fms : 200,000 inserts creating 200K table w/ 256-byte key\n", timer); + begin(); for (n=0; n < 10; ++n) { churn256(2000000,0,0,1); } end(); printf(" // %7.2fms : 2,000,000 inserts creating 2M table w/ 256-byte key\n", timer); + begin(); for (n=0; n < 5; ++n) { churn256(20000000,0,0,1); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 20M table w/ 256-byte key\n", timer); +#endif + + begin(); for (n=0; n < 20; ++n) { multisearch_stb(2000,0,2000,1,1000); } end(); printf(" // %7.2fms : 2,000,000 hits on 1,000 2K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 10; ++n) { multisearch_stb(20000,0,2000,1,1000); } end(); printf(" // %7.2fms : 2,000,000 hits on 1,000 20K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 6; ++n) { multisearch_stb(200000,0,2000,1,1000); } end(); printf(" // %7.2fms : 2,000,000 hits on 1,000 200K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 2; ++n) { multisearch_stb(2000000,0,20000,1,100); } end(); printf(" // %7.2fms : 2,000,000 hits on 100 2M table w/ 4-byte key\n", timer); + + begin(); for (n=0; n < 20; ++n) { multisearch (2000,0,2000,1,1000); } end(); printf(" // %7.2fms : 2,000,000 hits on 1,000 2K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 10; ++n) { multisearch (20000,0,2000,1,1000); } end(); printf(" // %7.2fms : 2,000,000 hits on 1,000 20K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 6; ++n) { multisearch (200000,0,2000,1,1000); } end(); printf(" // %7.2fms : 2,000,000 hits on 1,000 200K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 2; ++n) { multisearch (2000000,0,20000,1,100); } end(); printf(" // %7.2fms : 2,000,000 hits on 100 2M table w/ 4-byte key\n", timer); + + +#if 1 + begin(); for (n=0; n < 2; ++n) { multibuild_stb(2000,0,0,1,10000); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 10,000 2K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 2; ++n) { multibuild_stb(20000,0,0,1,1000); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 1,000 20K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 2; ++n) { multibuild_stb(200000,0,0,1,100); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 100 200K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 2; ++n) { multibuild_stb(2000000,0,0,1,10); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 10 2M table w/ 4-byte key\n", timer); + + begin(); for (n=0; n < 2; ++n) { multichurn4(2000,0,0,1,10000); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 10,000 2K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 2; ++n) { multichurn4(20000,0,0,1,1000); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 1,000 20K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 2; ++n) { multichurn4(200000,0,0,1,100); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 100 200K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 2; ++n) { multichurn4(2000000,0,0,1,10); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 10 2M table w/ 4-byte key\n", timer); +#endif + + begin(); for (n=0; n < 2000; ++n) { build(2000,0,0,1); } end(); printf(" // %7.2fms : 2,000 inserts creating 2K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 500; ++n) { build(20000,0,0,1); } end(); printf(" // %7.2fms : 20,000 inserts creating 20K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 100; ++n) { build(200000,0,0,1); } end(); printf(" // %7.2fms : 200,000 inserts creating 200K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 10; ++n) { build(2000000,0,0,1); } end(); printf(" // %7.2fms : 2,000,000 inserts creating 2M table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 5; ++n) { build(20000000,0,0,1); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 20M table w/ 4-byte key\n", timer); + + begin(); for (n=0; n < 2000; ++n) { churn8(2000,0,0,1); } end(); printf(" // %7.2fms : 2,000 inserts creating 2K table w/ 8-byte key\n", timer); + begin(); for (n=0; n < 500; ++n) { churn8(20000,0,0,1); } end(); printf(" // %7.2fms : 20,000 inserts creating 20K table w/ 8-byte key\n", timer); + begin(); for (n=0; n < 100; ++n) { churn8(200000,0,0,1); } end(); printf(" // %7.2fms : 200,000 inserts creating 200K table w/ 8-byte key\n", timer); + begin(); for (n=0; n < 10; ++n) { churn8(2000000,0,0,1); } end(); printf(" // %7.2fms : 2,000,000 inserts creating 2M table w/ 8-byte key\n", timer); + begin(); for (n=0; n < 5; ++n) { churn8(20000000,0,0,1); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 20M table w/ 8-byte key\n", timer); + + begin(); for (n=0; n < 60; ++n) { churn_skip(2000,2100,5000); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 2K table\n", timer); + begin(); for (n=0; n < 30; ++n) { churn_skip(20000,21000,500); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 20K table\n", timer); + begin(); for (n=0; n < 15; ++n) { churn_skip(200000,201000,500); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 200K table\n", timer); + begin(); for (n=0; n < 8; ++n) { churn_skip(2000000,2001000,500); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 2M table\n", timer); + begin(); for (n=0; n < 5; ++n) { churn_skip(20000000,20001000,500); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 20M table\n", timer); + begin(); for (n=0; n < 1; ++n) { churn_skip(200000000u,200001000u,500); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 200M table\n", timer); + // even though the above measures a roughly fixed amount of work, we still have to build the table n times, hence the fewer measurements each time + + begin(); for (n=0; n < 60; ++n) { churn_skip(1000,3000,250); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 2K table\n", timer); + begin(); for (n=0; n < 15; ++n) { churn_skip(10000,30000,25); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 20K table\n", timer); + begin(); for (n=0; n < 7; ++n) { churn_skip(100000,300000,10); } end(); printf(" // %7.2fms : 2,000,000 inserts & deletes in 200K table\n", timer); + begin(); for (n=0; n < 2; ++n) { churn_skip(1000000,3000000,10); } end(); printf(" // %7.2fms : 20,000,000 inserts & deletes in 2M table\n", timer); + + // search for bad intervals.. in practice this just seems to measure execution variance + for (s = 2; s < 64; ++s) { + begin(); for (n=0; n < 50; ++n) { build(200000,0,0,s); } end(); + if (timer > worst) { + worst = timer; + w = s; + } + } + for (; s <= 1024; s *= 2) { + begin(); for (n=0; n < 50; ++n) { build(200000,0,0,s); } end(); + if (timer > worst) { + worst = timer; + w = s; + } + } + printf(" // %7.2fms(%d) : Worst time from inserting 200,000 items with spacing %d.\n", worst, w, w); + + return 0; +} +#endif diff --git a/source/engine/thirdparty/stb/tests/test_ds_cpp.cpp b/source/engine/thirdparty/stb/tests/test_ds_cpp.cpp new file mode 100644 index 0000000..fce99df --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_ds_cpp.cpp @@ -0,0 +1,418 @@ +#include + +#ifdef DS_TEST +#define STBDS_UNIT_TESTS +#endif + +#ifdef DS_STATS +#define STBDS_STATISTICS +#endif + +#ifndef DS_PERF +#define STBDS_ASSERT assert +#include +#endif + +//#define STBDS_SIPHASH_2_4 +#define STB_DS_IMPLEMENTATION +#include "../stb_ds.h" + +size_t churn_inserts, churn_deletes; + +void churn(int a, int b, int count) +{ + struct { int key,value; } *map=NULL; + int i,j,n,k; + for (i=0; i < a; ++i) + hmput(map,i,i+1); + for (n=0; n < count; ++n) { + for (j=a; j < b; ++j,++i) { + hmput(map,i,i+1); + } + assert(hmlen(map) == b); + for (j=a; j < b; ++j) { + k=i-j-1; + k = hmdel(map,k); + assert(k != 0); + } + assert(hmlen(map) == a); + } + hmfree(map); + churn_inserts = i; + churn_deletes = (b-a) * n; +} + +#ifdef DS_TEST +#include +int main(int argc, char **argv) +{ + stbds_unit_tests(); + churn(0,100,1); + churn(3,7,50000); + churn(3,15,50000); + churn(16, 48, 25000); + churn(10, 15, 25000); + churn(200,500, 5000); + churn(2000,5000, 500); + churn(20000,50000, 50); + printf("Ok!"); + return 0; +} +#endif + +#ifdef DS_STATS +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +size_t max_hit_probes, max_miss_probes, total_put_probes, total_miss_probes, churn_misses; +void churn_stats(int a, int b, int count) +{ + struct { int key,value; } *map=NULL; + int i,j,n,k; + churn_misses = 0; + for (i=0; i < a; ++i) { + hmput(map,i,i+1); + max_hit_probes = MAX(max_hit_probes, stbds_hash_probes); + total_put_probes += stbds_hash_probes; + stbds_hash_probes = 0; + } + + for (n=0; n < count; ++n) { + for (j=a; j < b; ++j,++i) { + hmput(map,i,i+1); + max_hit_probes = MAX(max_hit_probes, stbds_hash_probes); + total_put_probes += stbds_hash_probes; + stbds_hash_probes = 0; + } + for (j=0; j < (b-a)*10; ++j) { + k=i+j; + (void) hmgeti(map,k); // miss + max_miss_probes = MAX(max_miss_probes, stbds_hash_probes); + total_miss_probes += stbds_hash_probes; + stbds_hash_probes = 0; + ++churn_misses; + } + assert(hmlen(map) == b); + for (j=a; j < b; ++j) { + k=i-j-1; + k = hmdel(map,k); + stbds_hash_probes = 0; + assert(k); + } + assert(hmlen(map) == a); + } + hmfree(map); + churn_inserts = i; + churn_deletes = (b-a) * n; +} + +void reset_stats(void) +{ + stbds_array_grow=0, + stbds_hash_grow=0; + stbds_hash_shrink=0; + stbds_hash_rebuild=0; + stbds_hash_probes=0; + stbds_hash_alloc=0; + stbds_rehash_probes=0; + stbds_rehash_items=0; + max_hit_probes = 0; + max_miss_probes = 0; + total_put_probes = 0; + total_miss_probes = 0; +} + +void print_churn_probe_stats(char *str) +{ + printf("Probes: %3d max hit, %3d max miss, %4.2f avg hit, %4.2f avg miss: %s\n", + (int) max_hit_probes, (int) max_miss_probes, (float) total_put_probes / churn_inserts, (float) total_miss_probes / churn_misses, str); + reset_stats(); +} + +int main(int arg, char **argv) +{ + churn_stats(0,500000,1); print_churn_probe_stats("Inserting 500000 items"); + churn_stats(0,500000,1); print_churn_probe_stats("Inserting 500000 items"); + churn_stats(0,500000,1); print_churn_probe_stats("Inserting 500000 items"); + churn_stats(0,500000,1); print_churn_probe_stats("Inserting 500000 items"); + churn_stats(49000,50000,500); print_churn_probe_stats("Deleting/Inserting 500000 items"); + churn_stats(49000,50000,500); print_churn_probe_stats("Deleting/Inserting 500000 items"); + churn_stats(49000,50000,500); print_churn_probe_stats("Deleting/Inserting 500000 items"); + churn_stats(49000,50000,500); print_churn_probe_stats("Deleting/Inserting 500000 items"); + return 0; +} +#endif + +#ifdef DS_PERF +#define WIN32_LEAN_AND_MEAN +#include +#define STB_DEFINE +#define STB_NO_REGISTRY +//#include "../stb.h" + + +size_t t0, sum, mn,mx,count; +void begin(void) +{ + size_t t0; + LARGE_INTEGER m; + QueryPerformanceCounter(&m); + t0 = m.QuadPart; + sum = 0; + count = 0; + mx = 0; + mn = ~(size_t) 0; +} + +void measure(void) +{ + size_t t1, t; + LARGE_INTEGER m; + QueryPerformanceCounter(&m); + t1 = m.QuadPart; + t = t1-t0; + if (t1 < t0) + printf("ALERT: QueryPerformanceCounter was unordered!\n"); + if (t < mn) mn = t; + if (t > mx) mx = t; + sum += t; + ++count; + t0 = t1; +} + +void dont_measure(void) +{ + size_t t1, t; + LARGE_INTEGER m; + QueryPerformanceCounter(&m); + t0 = m.QuadPart; +} + +double timer; +void end(void) +{ + LARGE_INTEGER m; + QueryPerformanceFrequency(&m); + + if (count > 3) { + // discard the highest and lowest + sum -= mn; + sum -= mx; + count -= 2; + } + timer = (double) (sum) / count / m.QuadPart * 1000; +} + +void build(int a, int b, int count, int step) +{ + struct { int key,value; } *map=NULL; + int i,j,n,k; + for (i=0; i < a; ++i) + hmput(map,i*step,i+1); + measure(); + churn_inserts = i; + hmfree(map); + dont_measure(); +} + +#ifdef STB__INCLUDE_STB_H +void build_stb(int a, int b, int count, int step) +{ + stb_idict *d = stb_idict_new_size(8); + struct { int key,value; } *map=NULL; + int i,j,n,k; + for (i=0; i < a; ++i) + stb_idict_add(d, i*step, i+1); + measure(); + churn_inserts = i; + stb_idict_destroy(d); + dont_measure(); +} +#endif + +void churn_skip(unsigned int a, unsigned int b, int count) +{ + struct { unsigned int key,value; } *map=NULL; + unsigned int i,j,n,k; + for (i=0; i < a; ++i) + hmput(map,i,i+1); + dont_measure(); + for (n=0; n < count; ++n) { + for (j=a; j < b; ++j,++i) { + hmput(map,i,i+1); + } + assert(hmlen(map) == b); + for (j=a; j < b; ++j) { + k=i-j-1; + k = hmdel(map,k); + assert(k != 0); + } + assert(hmlen(map) == a); + } + measure(); + churn_inserts = i; + churn_deletes = (b-a) * n; + hmfree(map); + dont_measure(); +} + +typedef struct { int n[8]; } str32; +void churn32(int a, int b, int count, int include_startup) +{ + struct { str32 key; int value; } *map=NULL; + int i,j,n; + str32 key = { 0 }; + for (i=0; i < a; ++i) { + key.n[0] = i; + hmput(map,key,i+1); + } + if (!include_startup) + dont_measure(); + for (n=0; n < count; ++n) { + for (j=a; j < b; ++j,++i) { + key.n[0] = i; + hmput(map,key,i+1); + } + assert(hmlen(map) == b); + for (j=a; j < b; ++j) { + key.n[0] = i-j-1; + hmdel(map,key); + } + assert(hmlen(map) == a); + } + measure(); + hmfree(map); + churn_inserts = i; + churn_deletes = (b-a) * n; + dont_measure(); +} + +typedef struct { int n[32]; } str256; +void churn256(int a, int b, int count, int include_startup) +{ + struct { str256 key; int value; } *map=NULL; + int i,j,n; + str256 key = { 0 }; + for (i=0; i < a; ++i) { + key.n[0] = i; + hmput(map,key,i+1); + } + if (!include_startup) + dont_measure(); + for (n=0; n < count; ++n) { + for (j=a; j < b; ++j,++i) { + key.n[0] = i; + hmput(map,key,i+1); + } + assert(hmlen(map) == b); + for (j=a; j < b; ++j) { + key.n[0] = i-j-1; + hmdel(map,key); + } + assert(hmlen(map) == a); + } + measure(); + hmfree(map); + churn_inserts = i; + churn_deletes = (b-a) * n; + dont_measure(); +} + +void churn8(int a, int b, int count, int include_startup) +{ + struct { size_t key,value; } *map=NULL; + int i,j,n,k; + for (i=0; i < a; ++i) + hmput(map,i,i+1); + if (!include_startup) + dont_measure(); + for (n=0; n < count; ++n) { + for (j=a; j < b; ++j,++i) { + hmput(map,i,i+1); + } + assert(hmlen(map) == b); + for (j=a; j < b; ++j) { + k=i-j-1; + k = hmdel(map,k); + assert(k != 0); + } + assert(hmlen(map) == a); + } + measure(); + hmfree(map); + churn_inserts = i; + churn_deletes = (b-a) * n; + dont_measure(); +} + + +int main(int arg, char **argv) +{ + int n,s,w; + double worst = 0; + +#if 0 + begin(); for (n=0; n < 2000; ++n) { build_stb(2000,0,0,1); } end(); printf(" // %7.2fms : 2,000 inserts creating 2K table\n", timer); + begin(); for (n=0; n < 500; ++n) { build_stb(20000,0,0,1); } end(); printf(" // %7.2fms : 20,000 inserts creating 20K table\n", timer); + begin(); for (n=0; n < 100; ++n) { build_stb(200000,0,0,1); } end(); printf(" // %7.2fms : 200,000 inserts creating 200K table\n", timer); + begin(); for (n=0; n < 10; ++n) { build_stb(2000000,0,0,1); } end(); printf(" // %7.2fms : 2,000,000 inserts creating 2M table\n", timer); + begin(); for (n=0; n < 5; ++n) { build_stb(20000000,0,0,1); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 20M table\n", timer); +#endif + + begin(); for (n=0; n < 2000; ++n) { churn8(2000,0,0,1); } end(); printf(" // %7.2fms : 2,000 inserts creating 2K table w/ 8-byte key\n", timer); + begin(); for (n=0; n < 500; ++n) { churn8(20000,0,0,1); } end(); printf(" // %7.2fms : 20,000 inserts creating 20K table w/ 8-byte key\n", timer); + begin(); for (n=0; n < 100; ++n) { churn8(200000,0,0,1); } end(); printf(" // %7.2fms : 200,000 inserts creating 200K table w/ 8-byte key\n", timer); + begin(); for (n=0; n < 10; ++n) { churn8(2000000,0,0,1); } end(); printf(" // %7.2fms : 2,000,000 inserts creating 2M table w/ 8-byte key\n", timer); + begin(); for (n=0; n < 5; ++n) { churn8(20000000,0,0,1); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 20M table w/ 8-byte key\n", timer); + +#if 0 + begin(); for (n=0; n < 2000; ++n) { churn32(2000,0,0,1); } end(); printf(" // %7.2fms : 2,000 inserts creating 2K table w/ 32-byte key\n", timer); + begin(); for (n=0; n < 500; ++n) { churn32(20000,0,0,1); } end(); printf(" // %7.2fms : 20,000 inserts creating 20K table w/ 32-byte key\n", timer); + begin(); for (n=0; n < 100; ++n) { churn32(200000,0,0,1); } end(); printf(" // %7.2fms : 200,000 inserts creating 200K table w/ 32-byte key\n", timer); + begin(); for (n=0; n < 10; ++n) { churn32(2000000,0,0,1); } end(); printf(" // %7.2fms : 2,000,000 inserts creating 2M table w/ 32-byte key\n", timer); + begin(); for (n=0; n < 5; ++n) { churn32(20000000,0,0,1); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 20M table w/ 32-byte key\n", timer); + + begin(); for (n=0; n < 2000; ++n) { churn256(2000,0,0,1); } end(); printf(" // %7.2fms : 2,000 inserts creating 2K table w/ 256-byte key\n", timer); + begin(); for (n=0; n < 500; ++n) { churn256(20000,0,0,1); } end(); printf(" // %7.2fms : 20,000 inserts creating 20K table w/ 256-byte key\n", timer); + begin(); for (n=0; n < 100; ++n) { churn256(200000,0,0,1); } end(); printf(" // %7.2fms : 200,000 inserts creating 200K table w/ 256-byte key\n", timer); + begin(); for (n=0; n < 10; ++n) { churn256(2000000,0,0,1); } end(); printf(" // %7.2fms : 2,000,000 inserts creating 2M table w/ 256-byte key\n", timer); + begin(); for (n=0; n < 5; ++n) { churn256(20000000,0,0,1); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 20M table w/ 256-byte key\n", timer); +#endif + + begin(); for (n=0; n < 2000; ++n) { build(2000,0,0,1); } end(); printf(" // %7.2fms : 2,000 inserts creating 2K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 500; ++n) { build(20000,0,0,1); } end(); printf(" // %7.2fms : 20,000 inserts creating 20K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 100; ++n) { build(200000,0,0,1); } end(); printf(" // %7.2fms : 200,000 inserts creating 200K table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 10; ++n) { build(2000000,0,0,1); } end(); printf(" // %7.2fms : 2,000,000 inserts creating 2M table w/ 4-byte key\n", timer); + begin(); for (n=0; n < 5; ++n) { build(20000000,0,0,1); } end(); printf(" // %7.2fms : 20,000,000 inserts creating 20M table w/ 4-byte key\n", timer); + + begin(); for (n=0; n < 60; ++n) { churn_skip(2000,2100,5000); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 2K table\n", timer); + begin(); for (n=0; n < 30; ++n) { churn_skip(20000,21000,500); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 20K table\n", timer); + begin(); for (n=0; n < 15; ++n) { churn_skip(200000,201000,500); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 200K table\n", timer); + begin(); for (n=0; n < 8; ++n) { churn_skip(2000000,2001000,500); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 2M table\n", timer); + begin(); for (n=0; n < 5; ++n) { churn_skip(20000000,20001000,500); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 20M table\n", timer); + begin(); for (n=0; n < 1; ++n) { churn_skip(200000000u,200001000u,500); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 200M table\n", timer); + // even though the above measures a roughly fixed amount of work, we still have to build the table n times, hence the fewer measurements each time + + begin(); for (n=0; n < 60; ++n) { churn_skip(1000,3000,250); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 2K table\n", timer); + begin(); for (n=0; n < 15; ++n) { churn_skip(10000,30000,25); } end(); printf(" // %7.2fms : 500,000 inserts & deletes in 20K table\n", timer); + begin(); for (n=0; n < 7; ++n) { churn_skip(100000,300000,10); } end(); printf(" // %7.2fms : 2,000,000 inserts & deletes in 200K table\n", timer); + begin(); for (n=0; n < 2; ++n) { churn_skip(1000000,3000000,10); } end(); printf(" // %7.2fms : 20,000,000 inserts & deletes in 2M table\n", timer); + + // search for bad intervals.. in practice this just seems to measure execution variance + for (s = 2; s < 64; ++s) { + begin(); for (n=0; n < 50; ++n) { build(200000,0,0,s); } end(); + if (timer > worst) { + worst = timer; + w = s; + } + } + for (; s <= 1024; s *= 2) { + begin(); for (n=0; n < 50; ++n) { build(200000,0,0,s); } end(); + if (timer > worst) { + worst = timer; + w = s; + } + } + printf(" // %7.2fms(%d) : Worst time from inserting 200,000 items with spacing %d.\n", worst, w, w); + + return 0; +} +#endif diff --git a/source/engine/thirdparty/stb/tests/test_dxt.c b/source/engine/thirdparty/stb/tests/test_dxt.c new file mode 100644 index 0000000..a1ef29d --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_dxt.c @@ -0,0 +1 @@ +#include "stb_dxt.h" diff --git a/source/engine/thirdparty/stb/tests/test_easyfont.c b/source/engine/thirdparty/stb/tests/test_easyfont.c new file mode 100644 index 0000000..25d54ff --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_easyfont.c @@ -0,0 +1,10 @@ +#include "stb_easy_font.h" + +void ef_dummy(void) +{ + // suppress unsused-function warning + stb_easy_font_spacing(0); + stb_easy_font_print(0,0,0,0,0,0); + stb_easy_font_width(0); + stb_easy_font_height(0); +} \ No newline at end of file diff --git a/source/engine/thirdparty/stb/tests/test_image.c b/source/engine/thirdparty/stb/tests/test_image.c new file mode 100644 index 0000000..de2c8b0 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_image.c @@ -0,0 +1,7 @@ +#ifdef __clang__ +#define STBIDEF static inline +#endif + +#define STB_IMAGE_STATIC +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" \ No newline at end of file diff --git a/source/engine/thirdparty/stb/tests/test_image_write.c b/source/engine/thirdparty/stb/tests/test_image_write.c new file mode 100644 index 0000000..8c4c35c --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_image_write.c @@ -0,0 +1,7 @@ +#ifdef __clang__ +#define STBIWDEF static inline +#endif + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#define STB_IMAGE_WRITE_STATIC +#include "stb_image_write.h" \ No newline at end of file diff --git a/source/engine/thirdparty/stb/tests/test_perlin.c b/source/engine/thirdparty/stb/tests/test_perlin.c new file mode 100644 index 0000000..de702c9 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_perlin.c @@ -0,0 +1 @@ +#include "stb_perlin.h" \ No newline at end of file diff --git a/source/engine/thirdparty/stb/tests/test_png_paeth.c b/source/engine/thirdparty/stb/tests/test_png_paeth.c new file mode 100644 index 0000000..69ba37f --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_png_paeth.c @@ -0,0 +1,47 @@ +#include +#include + +// Reference Paeth filter as per PNG spec +static int ref_paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +// Optimized Paeth filter +static int opt_paeth(int a, int b, int c) +{ + int thresh = c*3 - (a + b); + int lo = a < b ? a : b; + int hi = a < b ? b : a; + int t0 = (hi <= thresh) ? lo : c; + int t1 = (thresh <= lo) ? hi : t0; + return t1; +} + +int main() +{ + // Exhaustively test the functions match for all byte inputs a, b,c in [0,255] + for (int i = 0; i < (1 << 24); ++i) { + int a = i & 0xff; + int b = (i >> 8) & 0xff; + int c = (i >> 16) & 0xff; + + int ref = ref_paeth(a, b, c); + int opt = opt_paeth(a, b, c); + if (ref != opt) { + fprintf(stderr, "mismatch at a=%3d b=%3d c=%3d: ref=%3d opt=%3d\n", a, b, c, ref, opt); + return 1; + } + } + + printf("all ok!\n"); + return 0; +} + +// vim:sw=3:sts=3:et diff --git a/source/engine/thirdparty/stb/tests/test_png_regress.c b/source/engine/thirdparty/stb/tests/test_png_regress.c new file mode 100644 index 0000000..5ba6796 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_png_regress.c @@ -0,0 +1,75 @@ +#include +#include + +#define STBI_WINDOWS_UTF8 + +#ifdef _WIN32 +#define WIN32 // what stb.h checks +#pragma comment(lib, "advapi32.lib") +#endif + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#define STB_DEFINE +#include "deprecated/stb.h" + +static unsigned int fnv1a_hash32(const stbi_uc *bytes, size_t len) +{ + unsigned int hash = 0x811c9dc5; + unsigned int mul = 0x01000193; + size_t i; + + for (i = 0; i < len; ++i) + hash = (hash ^ bytes[i]) * mul; + + return hash; +} + +// The idea for this test is to leave pngsuite/ref_results.csv checked in, +// and then you can run this test after making PNG loader changes. If the +// ref results change (as per git diff), confirm that the change was +// intentional. If so, commit them as well; if not, undo. +int main() +{ + char **files; + FILE *csv_file; + int i; + + files = stb_readdir_recursive("pngsuite", "*.png"); + if (!files) { + fprintf(stderr, "pngsuite files not found!\n"); + return 1; + } + + // sort files by name + qsort(files, stb_arr_len(files), sizeof(char*), stb_qsort_strcmp(0)); + + csv_file = fopen("pngsuite/ref_results.csv", "w"); + if (!csv_file) { + fprintf(stderr, "error opening ref results for writing!\n"); + stb_readdir_free(files); + return 1; + } + + fprintf(csv_file, "filename,width,height,ncomp,error,hash\n"); + for (i = 0; i < stb_arr_len(files); ++i) { + char *filename = files[i]; + int width, height, ncomp; + stbi_uc *pixels = stbi_load(filename, &width, &height, &ncomp, 0); + const char *error = ""; + unsigned int hash = 0; + + if (!pixels) + error = stbi_failure_reason(); + else { + hash = fnv1a_hash32(pixels, width * height * ncomp); + stbi_image_free(pixels); + } + + fprintf(csv_file, "%s,%d,%d,%d,%s,0x%08x\n", filename, width, height, ncomp, error, hash); + } + + fclose(csv_file); + stb_readdir_free(files); +} diff --git a/source/engine/thirdparty/stb/tests/test_siphash.c b/source/engine/thirdparty/stb/tests/test_siphash.c new file mode 100644 index 0000000..643f549 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_siphash.c @@ -0,0 +1,21 @@ +#include + +#define STB_DS_IMPLEMENTATION +#define STBDS_SIPHASH_2_4 +#define STBDS_TEST_SIPHASH_2_4 +#include "../stb_ds.h" + +int main(int argc, char **argv) +{ + unsigned char mem[64]; + int i,j; + for (i=0; i < 64; ++i) mem[i] = i; + for (i=0; i < 64; ++i) { + size_t hash = stbds_hash_bytes(mem, i, 0); + printf(" { "); + for (j=0; j < 8; ++j) + printf("0x%02x, ", (unsigned char) ((hash >> (j*8)) & 255)); + printf(" },\n"); + } + return 0; +} \ No newline at end of file diff --git a/source/engine/thirdparty/stb/tests/test_sprintf.c b/source/engine/thirdparty/stb/tests/test_sprintf.c new file mode 100644 index 0000000..60ef40b --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_sprintf.c @@ -0,0 +1,202 @@ + +#define USE_STB 1 + +#if USE_STB +# include "stb_sprintf.h" +# define SPRINTF stbsp_sprintf +# define SNPRINTF stbsp_snprintf +#else +# include +# define SPRINTF sprintf +# define SNPRINTF snprintf +#endif + +#include +#include // for INFINITY, NAN +#include // for ptrdiff_t +#include // for printf +#include // for strcmp, strncmp, strlen + +#if _MSC_VER && _MSC_VER <= 1600 +typedef int intmax_t; +typedef ptrdiff_t ssize_t; +#else +#include // for intmax_t, ssize_t +#endif + +// stbsp_sprintf +#define CHECK_END(str) \ + if (strcmp(buf, str) != 0 || (unsigned) ret != strlen(str)) { \ + printf("< '%s'\n> '%s'\n", str, buf); \ + assert(!"Fail"); \ + } + +#define CHECK9(str, v1, v2, v3, v4, v5, v6, v7, v8, v9) { int ret = SPRINTF(buf, v1, v2, v3, v4, v5, v6, v7, v8, v9); CHECK_END(str); } +#define CHECK8(str, v1, v2, v3, v4, v5, v6, v7, v8 ) { int ret = SPRINTF(buf, v1, v2, v3, v4, v5, v6, v7, v8 ); CHECK_END(str); } +#define CHECK7(str, v1, v2, v3, v4, v5, v6, v7 ) { int ret = SPRINTF(buf, v1, v2, v3, v4, v5, v6, v7 ); CHECK_END(str); } +#define CHECK6(str, v1, v2, v3, v4, v5, v6 ) { int ret = SPRINTF(buf, v1, v2, v3, v4, v5, v6 ); CHECK_END(str); } +#define CHECK5(str, v1, v2, v3, v4, v5 ) { int ret = SPRINTF(buf, v1, v2, v3, v4, v5 ); CHECK_END(str); } +#define CHECK4(str, v1, v2, v3, v4 ) { int ret = SPRINTF(buf, v1, v2, v3, v4 ); CHECK_END(str); } +#define CHECK3(str, v1, v2, v3 ) { int ret = SPRINTF(buf, v1, v2, v3 ); CHECK_END(str); } +#define CHECK2(str, v1, v2 ) { int ret = SPRINTF(buf, v1, v2 ); CHECK_END(str); } +#define CHECK1(str, v1 ) { int ret = SPRINTF(buf, v1 ); CHECK_END(str); } + +#ifdef TEST_SPRINTF +int main() +{ + char buf[1024]; + int n = 0; + const double pow_2_75 = 37778931862957161709568.0; + const double pow_2_85 = 38685626227668133590597632.0; + + // integers + CHECK4("a b 1", "%c %s %d", 'a', "b", 1); + CHECK2("abc ", "%-8.3s", "abcdefgh"); + CHECK2("+5", "%+2d", 5); + CHECK2(" 6", "% 3i", 6); + CHECK2("-7 ", "%-4d", -7); + CHECK2("+0", "%+d", 0); + CHECK3(" 00003: 00004", "%10.5d:%10.5d", 3, 4); + CHECK2("-100006789", "%d", -100006789); + CHECK3("20 0020", "%u %04u", 20u, 20u); + CHECK4("12 1e 3C", "%o %x %X", 10u, 30u, 60u); + CHECK4(" 12 1e 3C ", "%3o %2x %-3X", 10u, 30u, 60u); + CHECK4("012 0x1e 0X3C", "%#o %#x %#X", 10u, 30u, 60u); + CHECK2("", "%.0x", 0); +#if USE_STB + CHECK2("0", "%.0d", 0); // stb_sprintf gives "0" +#else + CHECK2("", "%.0d", 0); // glibc gives "" as specified by C99(?) +#endif + CHECK3("33 555", "%hi %ld", (short)33, 555l); +#if !defined(_MSC_VER) || _MSC_VER >= 1600 + CHECK2("9888777666", "%llu", 9888777666llu); +#endif + CHECK4("-1 2 -3", "%ji %zi %ti", (intmax_t)-1, (ssize_t)2, (ptrdiff_t)-3); + + // floating-point numbers + CHECK2("-3.000000", "%f", -3.0); + CHECK2("-8.8888888800", "%.10f", -8.88888888); + CHECK2("880.0888888800", "%.10f", 880.08888888); + CHECK2("4.1", "%.1f", 4.1); + CHECK2(" 0", "% .0f", 0.1); + CHECK2("0.00", "%.2f", 1e-4); + CHECK2("-5.20", "%+4.2f", -5.2); + CHECK2("0.0 ", "%-10.1f", 0.); + CHECK2("-0.000000", "%f", -0.); + CHECK2("0.000001", "%f", 9.09834e-07); +#if USE_STB // rounding differences + CHECK2("38685626227668133600000000.0", "%.1f", pow_2_85); + CHECK2("0.000000499999999999999978", "%.24f", 5e-7); +#else + CHECK2("38685626227668133590597632.0", "%.1f", pow_2_85); // exact + CHECK2("0.000000499999999999999977", "%.24f", 5e-7); +#endif + CHECK2("0.000000000000000020000000", "%.24f", 2e-17); + CHECK3("0.0000000100 100000000", "%.10f %.0f", 1e-8, 1e+8); + CHECK2("100056789.0", "%.1f", 100056789.0); + CHECK4(" 1.23 %", "%*.*f %%", 5, 2, 1.23); + CHECK2("-3.000000e+00", "%e", -3.0); + CHECK2("4.1E+00", "%.1E", 4.1); + CHECK2("-5.20e+00", "%+4.2e", -5.2); + CHECK3("+0.3 -3", "%+g %+g", 0.3, -3.0); + CHECK2("4", "%.1G", 4.1); + CHECK2("-5.2", "%+4.2g", -5.2); + CHECK2("3e-300", "%g", 3e-300); + CHECK2("1", "%.0g", 1.2); + CHECK3(" 3.7 3.71", "% .3g %.3g", 3.704, 3.706); + CHECK3("2e-315:1e+308", "%g:%g", 2e-315, 1e+308); + +#if __STDC_VERSION__ >= 199901L +#if USE_STB + CHECK4("Inf Inf NaN", "%g %G %f", INFINITY, INFINITY, NAN); + CHECK2("N", "%.1g", NAN); +#else + CHECK4("inf INF nan", "%g %G %f", INFINITY, INFINITY, NAN); + CHECK2("nan", "%.1g", NAN); +#endif +#endif + + // %n + CHECK3("aaa ", "%.3s %n", "aaaaaaaaaaaaa", &n); + assert(n == 4); + +#if __STDC_VERSION__ >= 199901L + // hex floats + CHECK2("0x1.fedcbap+98", "%a", 0x1.fedcbap+98); + CHECK2("0x1.999999999999a0p-4", "%.14a", 0.1); + CHECK2("0x1.0p-1022", "%.1a", 0x1.ffp-1023); +#if USE_STB // difference in default precision and x vs X for %A + CHECK2("0x1.009117p-1022", "%a", 2.23e-308); + CHECK2("-0x1.AB0P-5", "%.3A", -0x1.abp-5); +#else + CHECK2("0x1.0091177587f83p-1022", "%a", 2.23e-308); + CHECK2("-0X1.AB0P-5", "%.3A", -0X1.abp-5); +#endif +#endif + + // %p +#if USE_STB + CHECK2("0000000000000000", "%p", (void*) NULL); +#else + CHECK2("(nil)", "%p", (void*) NULL); +#endif + + // snprintf + assert(SNPRINTF(buf, 100, " %s %d", "b", 123) == 10); + assert(strcmp(buf, " b 123") == 0); + assert(SNPRINTF(buf, 100, "%f", pow_2_75) == 30); + assert(strncmp(buf, "37778931862957161709568.000000", 17) == 0); + n = SNPRINTF(buf, 10, "number %f", 123.456789); + assert(strcmp(buf, "number 12") == 0); + assert(n == 17); // written vs would-be written bytes + n = SNPRINTF(buf, 0, "7 chars"); + assert(n == 7); + // stb_sprintf uses internal buffer of 512 chars - test longer string + assert(SPRINTF(buf, "%d %600s", 3, "abc") == 603); + assert(strlen(buf) == 603); + SNPRINTF(buf, 550, "%d %600s", 3, "abc"); + assert(strlen(buf) == 549); + assert(SNPRINTF(buf, 600, "%510s %c", "a", 'b') == 516); + + // length check + assert(SNPRINTF(NULL, 0, " %s %d", "b", 123) == 10); + + // ' modifier. Non-standard, but supported by glibc. +#if !USE_STB + setlocale(LC_NUMERIC, ""); // C locale does not group digits +#endif + CHECK2("1,200,000", "%'d", 1200000); + CHECK2("-100,006,789", "%'d", -100006789); +#if !defined(_MSC_VER) || _MSC_VER >= 1600 + CHECK2("9,888,777,666", "%'lld", 9888777666ll); +#endif + CHECK2("200,000,000.000000", "%'18f", 2e8); + CHECK2("100,056,789", "%'.0f", 100056789.0); + CHECK2("100,056,789.0", "%'.1f", 100056789.0); +#if USE_STB // difference in leading zeros + CHECK2("000,001,200,000", "%'015d", 1200000); +#else + CHECK2("0000001,200,000", "%'015d", 1200000); +#endif + + // things not supported by glibc +#if USE_STB + CHECK2("null", "%s", (char*) NULL); + CHECK2("123,4abc:", "%'x:", 0x1234ABC); + CHECK2("100000000", "%b", 256); + CHECK3("0b10 0B11", "%#b %#B", 2, 3); +#if !defined(_MSC_VER) || _MSC_VER >= 1600 + CHECK4("2 3 4", "%I64d %I32d %Id", 2ll, 3, 4ll); +#endif + CHECK3("1k 2.54 M", "%$_d %$.2d", 1000, 2536000); + CHECK3("2.42 Mi 2.4 M", "%$$.2d %$$$d", 2536000, 2536000); + + // different separators + stbsp_set_separators(' ', ','); + CHECK2("12 345,678900", "%'f", 12345.6789); +#endif + + return 0; +} +#endif diff --git a/source/engine/thirdparty/stb/tests/test_truetype.c b/source/engine/thirdparty/stb/tests/test_truetype.c new file mode 100644 index 0000000..cb9b35f --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_truetype.c @@ -0,0 +1,154 @@ +#ifndef _CRT_SECURE_NO_WARNINGS +// Fixes Compile Errors for Visual Studio 2005 or newer + #define _CRT_SECURE_NO_WARNINGS +#endif + +#include + +// this isn't meant to compile standalone; link with test_c_compilation.c as well +#include "stb_rect_pack.h" +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" +#include "stb_image_write.h" + +#ifdef TT_TEST + +#include + +unsigned char ttf_buffer[1 << 25]; +unsigned char output[512*100]; + +void debug(void) +{ + stbtt_fontinfo font; + fread(ttf_buffer, 1, 1<<25, fopen("c:/x/lm/LiberationMono-Regular.ttf", "rb")); + stbtt_InitFont(&font, ttf_buffer, 0); + + stbtt_MakeGlyphBitmap(&font, output, 6, 9, 512, 5.172414E-03f, 5.172414E-03f, 54); +} + +#define BITMAP_W 256 +#define BITMAP_H 512 +unsigned char temp_bitmap[BITMAP_H][BITMAP_W]; +stbtt_bakedchar cdata[256*2]; // ASCII 32..126 is 95 glyphs +stbtt_packedchar pdata[256*2]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : '@'), s = (argc > 2 ? atoi(argv[2]) : 32); + + //debug(); + + // @TODO: why is minglui.ttc failing? + //fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/mingliu.ttc", "rb")); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/DejaVuSans.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + +#if 0 + { + stbtt__bitmap b; + stbtt__point p[2]; + int wcount[2] = { 2,0 }; + p[0].x = 0.2f; + p[0].y = 0.3f; + p[1].x = 3.8f; + p[1].y = 0.8f; + b.w = 16; + b.h = 2; + b.stride = 16; + b.pixels = malloc(b.w*b.h); + stbtt__rasterize(&b, p, wcount, 1, 1, 1, 0, 0, 0, 0, 0, NULL); + for (i=0; i < 8; ++i) + printf("%f\n", b.pixels[i]/255.0); + } +#endif + +#if 1 + { + static stbtt_pack_context pc; + static stbtt_packedchar cd[256]; + static unsigned char atlas[1024*1024]; + + stbtt_PackBegin(&pc, atlas, 1024,1024,1024,1,NULL); + stbtt_PackFontRange(&pc, ttf_buffer, 0, 32.0, 0, 256, cd); + stbtt_PackEnd(&pc); + } +#endif + + +#if 1 + { + static stbtt_pack_context pc; + static stbtt_packedchar cd[256]; + static unsigned char atlas[1024*1024]; + unsigned char *data; + + data = stbtt_GetCodepointSDF(&font, stbtt_ScaleForPixelHeight(&font,32.0), 'u', 4, 128, 128/4, &w,&h,&i,&j); + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + putchar(" .:ioVM@"[data[j*w+i]>>5]); + } + putchar('\n'); + } + } +#endif + +#if 0 + stbtt_BakeFontBitmap(ttf_buffer,stbtt_GetFontOffsetForIndex(ttf_buffer,0), 40.0, temp_bitmap[0],BITMAP_W,BITMAP_H, 32,96, cdata); // no guarantee this fits! + stbi_write_png("fonttest1.png", BITMAP_W, BITMAP_H, 1, temp_bitmap, 0); + + { + stbtt_pack_context pc; + stbtt_PackBegin(&pc, temp_bitmap[0], BITMAP_W, BITMAP_H, 0, 1, NULL); + stbtt_PackFontRange(&pc, ttf_buffer, 0, 20.0, 32, 95, pdata); + stbtt_PackFontRange(&pc, ttf_buffer, 0, 20.0, 0xa0, 0x100-0xa0, pdata); + stbtt_PackEnd(&pc); + stbi_write_png("fonttest2.png", BITMAP_W, BITMAP_H, 1, temp_bitmap, 0); + } + + { + stbtt_pack_context pc; + stbtt_pack_range pr[2]; + stbtt_PackBegin(&pc, temp_bitmap[0], BITMAP_W, BITMAP_H, 0, 1, NULL); + + pr[0].chardata_for_range = pdata; + pr[0].array_of_unicode_codepoints = NULL; + pr[0].first_unicode_codepoint_in_range = 32; + pr[0].num_chars = 95; + pr[0].font_size = 20.0f; + pr[1].chardata_for_range = pdata+256; + pr[1].array_of_unicode_codepoints = NULL; + pr[1].first_unicode_codepoint_in_range = 0xa0; + pr[1].num_chars = 0x100 - 0xa0; + pr[1].font_size = 20.0f; + + stbtt_PackSetOversampling(&pc, 2, 2); + stbtt_PackFontRanges(&pc, ttf_buffer, 0, pr, 2); + stbtt_PackEnd(&pc); + stbi_write_png("fonttest3.png", BITMAP_W, BITMAP_H, 1, temp_bitmap, 0); + } + return 0; +#endif + + (void)stbtt_GetCodepointBitmapSubpixel(&font, + 0.4972374737262726f, + 0.4986416995525360f, + 0.2391788959503174f, + 0.1752119064331055f, + 'd', + &w, &h, + 0,0); + + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, (float)s), c, &w, &h, 0,0); + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif diff --git a/source/engine/thirdparty/stb/tests/test_vorbis.c b/source/engine/thirdparty/stb/tests/test_vorbis.c new file mode 100644 index 0000000..d54ed23 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_vorbis.c @@ -0,0 +1,18 @@ +#define STB_VORBIS_HEADER_ONLY +#include "stb_vorbis.c" +#include "stb.h" + +extern void stb_vorbis_dumpmem(void); + +#ifdef VORBIS_TEST +int main(int argc, char **argv) +{ + size_t memlen; + unsigned char *mem = stb_fileu("c:/x/sketch008.ogg", &memlen); + int chan, samplerate; + short *output; + int samples = stb_vorbis_decode_memory(mem, memlen, &chan, &samplerate, &output); + stb_filewrite("c:/x/sketch008.raw", output, samples*4); + return 0; +} +#endif diff --git a/source/engine/thirdparty/stb/tests/test_voxel.c b/source/engine/thirdparty/stb/tests/test_voxel.c new file mode 100644 index 0000000..e177b6f --- /dev/null +++ b/source/engine/thirdparty/stb/tests/test_voxel.c @@ -0,0 +1 @@ +#include "stb_voxel_render.h" \ No newline at end of file diff --git a/source/engine/thirdparty/stb/tests/textedit_sample.c b/source/engine/thirdparty/stb/tests/textedit_sample.c new file mode 100644 index 0000000..57250f3 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/textedit_sample.c @@ -0,0 +1,94 @@ +// I haven't actually tested this yet, this is just to make sure it compiles + +#include +#include // memmove +#include // isspace + +#define STB_TEXTEDIT_CHARTYPE char +#define STB_TEXTEDIT_STRING text_control + +// get the base type +#include "stb_textedit.h" + +// define our editor structure +typedef struct +{ + char *string; + int stringlen; + STB_TexteditState state; +} text_control; + +// define the functions we need +void layout_func(StbTexteditRow *row, STB_TEXTEDIT_STRING *str, int start_i) +{ + int remaining_chars = str->stringlen - start_i; + row->num_chars = remaining_chars > 20 ? 20 : remaining_chars; // should do real word wrap here + row->x0 = 0; + row->x1 = 20; // need to account for actual size of characters + row->baseline_y_delta = 1.25; + row->ymin = -1; + row->ymax = 0; +} + +int delete_chars(STB_TEXTEDIT_STRING *str, int pos, int num) +{ + memmove(&str->string[pos], &str->string[pos+num], str->stringlen - (pos+num)); + str->stringlen -= num; + return 1; // always succeeds +} + +int insert_chars(STB_TEXTEDIT_STRING *str, int pos, STB_TEXTEDIT_CHARTYPE *newtext, int num) +{ + str->string = realloc(str->string, str->stringlen + num); + memmove(&str->string[pos+num], &str->string[pos], str->stringlen - pos); + memcpy(&str->string[pos], newtext, num); + str->stringlen += num; + return 1; // always succeeds +} + +// define all the #defines needed + +#define KEYDOWN_BIT 0x80000000 + +#define STB_TEXTEDIT_STRINGLEN(tc) ((tc)->stringlen) +#define STB_TEXTEDIT_LAYOUTROW layout_func +#define STB_TEXTEDIT_GETWIDTH(tc,n,i) (1) // quick hack for monospaced +#define STB_TEXTEDIT_KEYTOTEXT(key) (((key) & KEYDOWN_BIT) ? 0 : (key)) +#define STB_TEXTEDIT_GETCHAR(tc,i) ((tc)->string[i]) +#define STB_TEXTEDIT_NEWLINE '\n' +#define STB_TEXTEDIT_IS_SPACE(ch) isspace(ch) +#define STB_TEXTEDIT_DELETECHARS delete_chars +#define STB_TEXTEDIT_INSERTCHARS insert_chars + +#define STB_TEXTEDIT_K_SHIFT 0x40000000 +#define STB_TEXTEDIT_K_CONTROL 0x20000000 +#define STB_TEXTEDIT_K_LEFT (KEYDOWN_BIT | 1) // actually use VK_LEFT, SDLK_LEFT, etc +#define STB_TEXTEDIT_K_RIGHT (KEYDOWN_BIT | 2) // VK_RIGHT +#define STB_TEXTEDIT_K_UP (KEYDOWN_BIT | 3) // VK_UP +#define STB_TEXTEDIT_K_DOWN (KEYDOWN_BIT | 4) // VK_DOWN +#define STB_TEXTEDIT_K_LINESTART (KEYDOWN_BIT | 5) // VK_HOME +#define STB_TEXTEDIT_K_LINEEND (KEYDOWN_BIT | 6) // VK_END +#define STB_TEXTEDIT_K_TEXTSTART (STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_CONTROL) +#define STB_TEXTEDIT_K_TEXTEND (STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_CONTROL) +#define STB_TEXTEDIT_K_DELETE (KEYDOWN_BIT | 7) // VK_DELETE +#define STB_TEXTEDIT_K_BACKSPACE (KEYDOWN_BIT | 8) // VK_BACKSPACE +#define STB_TEXTEDIT_K_UNDO (KEYDOWN_BIT | STB_TEXTEDIT_K_CONTROL | 'z') +#define STB_TEXTEDIT_K_REDO (KEYDOWN_BIT | STB_TEXTEDIT_K_CONTROL | 'y') +#define STB_TEXTEDIT_K_INSERT (KEYDOWN_BIT | 9) // VK_INSERT +#define STB_TEXTEDIT_K_WORDLEFT (STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_CONTROL) +#define STB_TEXTEDIT_K_WORDRIGHT (STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_CONTROL) +#define STB_TEXTEDIT_K_PGUP (KEYDOWN_BIT | 10) // VK_PGUP -- not implemented +#define STB_TEXTEDIT_K_PGDOWN (KEYDOWN_BIT | 11) // VK_PGDOWN -- not implemented + +#define STB_TEXTEDIT_IMPLEMENTATION +#include "stb_textedit.h" + +void dummy3(void) +{ + stb_textedit_click(0,0,0,0); + stb_textedit_drag(0,0,0,0); + stb_textedit_cut(0,0); + stb_textedit_key(0,0,0); + stb_textedit_initialize_state(0,0); + stb_textedit_paste(0,0,0,0); +} diff --git a/source/engine/thirdparty/stb/tests/tilemap_editor_integration_example.c b/source/engine/thirdparty/stb/tests/tilemap_editor_integration_example.c new file mode 100644 index 0000000..ea5dee7 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/tilemap_editor_integration_example.c @@ -0,0 +1,193 @@ +// This isn't compilable as-is, as it was extracted from a working +// integration-in-a-game and makes reference to symbols from that game. + +#include +#include +#include "game.h" +#include "SDL.h" +#include "stb_tilemap_editor.h" + +extern void editor_draw_tile(int x, int y, unsigned short tile, int mode, float *props); +extern void editor_draw_rect(int x0, int y0, int x1, int y1, unsigned char r, unsigned char g, unsigned char b); + +static int is_platform(short *tiles); +static unsigned int prop_type(int n, short *tiles); +static char *prop_name(int n, short *tiles); +static float prop_range(int n, short *tiles, int is_max); +static int allow_link(short *src, short *dest); + +#define STBTE_MAX_PROPERTIES 8 + +#define STBTE_PROP_TYPE(n, tiledata, p) prop_type(n,tiledata) +#define STBTE_PROP_NAME(n, tiledata, p) prop_name(n,tiledata) +#define STBTE_PROP_MIN(n, tiledata, p) prop_range(n,tiledata,0) +#define STBTE_PROP_MAX(n, tiledata, p) prop_range(n,tiledata,1) +#define STBTE_PROP_FLOAT_SCALE(n,td,p) (0.1) + +#define STBTE_ALLOW_LINK(srctile, srcprop, desttile, destprop) \ + allow_link(srctile, desttile) + +#define STBTE_LINK_COLOR(srctile, srcprop, desttile, destprop) \ + (is_platform(srctile) ? 0xff80ff : 0x808040) + +#define STBTE_DRAW_RECT(x0,y0,x1,y1,c) \ + editor_draw_rect(x0,y0,x1,y1,(c)>>16,((c)>>8)&255,(c)&255) + +#define STBTE_DRAW_TILE(x,y,id,highlight,props) \ + editor_draw_tile(x,y,id,highlight,props) + + + +#define STB_TILEMAP_EDITOR_IMPLEMENTATION +#include "stb_tilemap_editor.h" + +stbte_tilemap *edit_map; + +void editor_key(enum stbte_action act) +{ + stbte_action(edit_map, act); +} + +void editor_process_sdl_event(SDL_Event *e) +{ + switch (e->type) { + case SDL_MOUSEMOTION: + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + case SDL_MOUSEWHEEL: + stbte_mouse_sdl(edit_map, e, 1.0f/editor_scale,1.0f/editor_scale,0,0); + break; + + case SDL_KEYDOWN: + if (in_editor) { + switch (e->key.keysym.sym) { + case SDLK_RIGHT: editor_key(STBTE_scroll_right); break; + case SDLK_LEFT : editor_key(STBTE_scroll_left ); break; + case SDLK_UP : editor_key(STBTE_scroll_up ); break; + case SDLK_DOWN : editor_key(STBTE_scroll_down ); break; + } + switch (e->key.keysym.scancode) { + case SDL_SCANCODE_S: editor_key(STBTE_tool_select); break; + case SDL_SCANCODE_B: editor_key(STBTE_tool_brush ); break; + case SDL_SCANCODE_E: editor_key(STBTE_tool_erase ); break; + case SDL_SCANCODE_R: editor_key(STBTE_tool_rectangle ); break; + case SDL_SCANCODE_I: editor_key(STBTE_tool_eyedropper); break; + case SDL_SCANCODE_L: editor_key(STBTE_tool_link); break; + case SDL_SCANCODE_G: editor_key(STBTE_act_toggle_grid); break; + } + if ((e->key.keysym.mod & KMOD_CTRL) && !(e->key.keysym.mod & ~KMOD_CTRL)) { + switch (e->key.keysym.scancode) { + case SDL_SCANCODE_X: editor_key(STBTE_act_cut ); break; + case SDL_SCANCODE_C: editor_key(STBTE_act_copy ); break; + case SDL_SCANCODE_V: editor_key(STBTE_act_paste); break; + case SDL_SCANCODE_Z: editor_key(STBTE_act_undo ); break; + case SDL_SCANCODE_Y: editor_key(STBTE_act_redo ); break; + } + } + } + break; + } +} + +void editor_init(void) +{ + int i; + edit_map = stbte_create_map(20,14, 8, 16,16, 100); + + stbte_set_background_tile(edit_map, T_empty); + + for (i=0; i < T__num_types; ++i) { + if (i != T_reserved1 && i != T_entry && i != T_doorframe) + stbte_define_tile(edit_map, 0+i, 1, "Background"); + } + stbte_define_tile(edit_map, 256+O_player , 8, "Char"); + stbte_define_tile(edit_map, 256+O_robot , 8, "Char"); + for (i=O_lockeddoor; i < O__num_types-2; ++i) + if (i == O_platform || i == O_vplatform) + stbte_define_tile(edit_map, 256+i, 4, "Object"); + else + stbte_define_tile(edit_map, 256+i, 2, "Object"); + + //stbte_set_layername(edit_map, 0, "background"); + //stbte_set_layername(edit_map, 1, "objects"); + //stbte_set_layername(edit_map, 2, "platforms"); + //stbte_set_layername(edit_map, 3, "characters"); +} + +static int is_platform(short *tiles) +{ + // platforms are only on layer #2 + return tiles[2] == 256 + O_platform || tiles[2] == 256 + O_vplatform; +} + +static int is_object(short *tiles) +{ + return (tiles[1] >= 256 || tiles[2] >= 256 || tiles[3] >= 256); +} + +static unsigned int prop_type(int n, short *tiles) +{ + if (is_platform(tiles)) { + static unsigned int platform_types[STBTE_MAX_PROPERTIES] = { + STBTE_PROP_bool, // phantom + STBTE_PROP_int, // x_adjust + STBTE_PROP_int, // y_adjust + STBTE_PROP_float, // width + STBTE_PROP_float, // lspeed + STBTE_PROP_float, // rspeed + STBTE_PROP_bool, // autoreturn + STBTE_PROP_bool, // one-shot + // remainder get 0, means 'no property in this slot' + }; + return platform_types[n]; + } else if (is_object(tiles)) { + if (n == 0) + return STBTE_PROP_bool; + } + return 0; +} + +static char *prop_name(int n, short *tiles) +{ + if (is_platform(tiles)) { + static char *platform_vars[STBTE_MAX_PROPERTIES] = { + "phantom", + "x_adjust", + "y_adjust", + "width", + "lspeed", + "rspeed", + "autoreturn", + "one-shot", + }; + return platform_vars[n]; + } + return "phantom"; +} + +static float prop_range(int n, short *tiles, int is_max) +{ + if (is_platform(tiles)) { + static float ranges[8][2] = { + { 0, 1 }, // phantom-flag, range is ignored + { -15, 15 }, // x_adjust + { -15, 15 }, // y_adjust + { 0, 6 }, // width + { 0, 10 }, // lspeed + { 0, 10 }, // rspeed + { 0, 1 }, // autoreturn, range is ignored + { 0, 1 }, // one-shot, range is ignored + }; + return ranges[n][is_max]; + } + return 0; +} + +static int allow_link(short *src, short *dest) +{ + if (is_platform(src)) + return dest[1] == 256+O_lever; + if (src[1] == 256+O_endpoint) + return is_platform(dest); + return 0; +} diff --git a/source/engine/thirdparty/stb/tests/truetype_test_win32.c b/source/engine/thirdparty/stb/tests/truetype_test_win32.c new file mode 100644 index 0000000..c9aed99 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/truetype_test_win32.c @@ -0,0 +1,184 @@ +// tested in VC6 (1998) and VS 2019 +#define _CRT_SECURE_NO_WARNINGS +#define WIN32_MEAN_AND_LEAN +#include + +#include +#include + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" + +#include +#include + +int screen_x=1024, screen_y=768; +GLuint tex; + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[1024*1024]; +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs + +void init(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 64.0, temp_bitmap,1024,1024, 32,96, cdata); + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 1024,1024,0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glBindTexture(GL_TEXTURE_2D, tex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 1024,1024, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} + +void draw(void) +{ + glViewport(0,0,screen_x,screen_y); + glClearColor(0.45f,0.45f,0.75f,0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0,screen_x,screen_y,0,-1,1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor3f(1,1,1); + + print(100,150, "This is a simple test!"); + + // show font bitmap + glBegin(GL_QUADS); + glTexCoord2f(0,0); glVertex2f(256,200+0); + glTexCoord2f(1,0); glVertex2f(768,200+0); + glTexCoord2f(1,1); glVertex2f(768,200+512); + glTexCoord2f(0,1); glVertex2f(256,200+512); + glEnd(); +} + +/////////////////////////////////////////////////////////////////////// +/// +/// +/// Windows OpenGL setup +/// +/// + +HINSTANCE app; +HWND window; +HGLRC rc; +HDC dc; + +#pragma comment(lib, "opengl32.lib") +#pragma comment(lib, "glu32.lib") +#pragma comment(lib, "winmm.lib") + +int mySetPixelFormat(HWND win) +{ + PIXELFORMATDESCRIPTOR pfd = { sizeof(pfd), 1, PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA }; + int pixel_format; + pfd.dwLayerMask = PFD_MAIN_PLANE; + pfd.cColorBits = 24; + pfd.cAlphaBits = 8; + pfd.cDepthBits = 24; + pfd.cStencilBits = 8; + pixel_format = ChoosePixelFormat(dc, &pfd); + if (!pixel_format) return FALSE; + if (!DescribePixelFormat(dc, pixel_format, sizeof(PIXELFORMATDESCRIPTOR), &pfd)) + return FALSE; + SetPixelFormat(dc, pixel_format, &pfd); + return TRUE; +} + +static int WINAPI WinProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + switch (msg) { + case WM_CREATE: { + LPCREATESTRUCT lpcs = (LPCREATESTRUCT) lparam; + dc = GetDC(wnd); + if (mySetPixelFormat(wnd)) { + rc = wglCreateContext(dc); + if (rc) { + wglMakeCurrent(dc, rc); + return 0; + } + } + return -1; + } + + case WM_DESTROY: + wglMakeCurrent(NULL, NULL); + if (rc) wglDeleteContext(rc); + PostQuitMessage (0); + return 0; + + default: + return DefWindowProc (wnd, msg, wparam, lparam); + } + + return DefWindowProc (wnd, msg, wparam, lparam); +} + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + DWORD dwstyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; + WNDCLASSEX wndclass; + wndclass.cbSize = sizeof(wndclass); + wndclass.style = CS_OWNDC; + wndclass.lpfnWndProc = (WNDPROC) WinProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = hInstance; + wndclass.hIcon = LoadIcon(hInstance, _T("appicon")); + wndclass.hCursor = LoadCursor(NULL,IDC_ARROW); + wndclass.hbrBackground = GetStockObject(NULL_BRUSH); + wndclass.lpszMenuName = _T("truetype-test"); + wndclass.lpszClassName = _T("truetype-test"); + wndclass.hIconSm = NULL; + app = hInstance; + + if (!RegisterClassEx(&wndclass)) + return 0; + + window = CreateWindow(_T("truetype-test"), _T("truetype test"), dwstyle, + CW_USEDEFAULT,0, screen_x, screen_y, + NULL, NULL, app, NULL); + ShowWindow(window, SW_SHOWNORMAL); + init(); + + for(;;) { + MSG msg; + if (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } else { + return 1; // WM_QUIT + } + wglMakeCurrent(dc, rc); + draw(); + SwapBuffers(dc); + } + return 0; +} diff --git a/source/engine/thirdparty/stb/tests/vorbseek/vorbseek.c b/source/engine/thirdparty/stb/tests/vorbseek/vorbseek.c new file mode 100644 index 0000000..f3460ad --- /dev/null +++ b/source/engine/thirdparty/stb/tests/vorbseek/vorbseek.c @@ -0,0 +1,125 @@ +#include +#include +#include +#include + +#define STB_VORBIS_HEADER_ONLY +#include "stb_vorbis.c" + +#define SAMPLES_TO_TEST 3000 + +int test_count [5] = { 5000, 3000, 2000, 50000, 50000 }; +int test_spacing[5] = { 1, 111, 3337, 7779, 72717 }; + +int try_seeking(stb_vorbis *v, unsigned int pos, short *output, unsigned int num_samples) +{ + int count; + short samples[SAMPLES_TO_TEST*2]; + assert(pos <= num_samples); + + if (!stb_vorbis_seek(v, pos)) { + fprintf(stderr, "Seek to %u returned error from stb_vorbis\n", pos); + return 0; + } + + count = stb_vorbis_get_samples_short_interleaved(v, 2, samples, SAMPLES_TO_TEST*2); + + if (count > (int) (num_samples - pos)) { + fprintf(stderr, "Seek to %u allowed decoding %d samples when only %d should have been valid.\n", + pos, count, (int) (num_samples - pos)); + return 0; + } + + if (count < SAMPLES_TO_TEST && count < (int) (num_samples - pos)) { + fprintf(stderr, "Seek to %u only decoded %d samples of %d attempted when at least %d should have been valid.\n", + pos, count, SAMPLES_TO_TEST, num_samples - pos); + return 0; + } + + if (0 != memcmp(samples, output + pos*2, count*2)) { + int k; + for (k=0; k < SAMPLES_TO_TEST*2; ++k) { + if (samples[k] != output[k]) { + fprintf(stderr, "Seek to %u produced incorrect samples starting at sample %u (short #%d in buffer).\n", + pos, pos + (k/2), k); + break; + } + } + assert(k != SAMPLES_TO_TEST*2); + return 0; + } + + return 1; +} + +int main(int argc, char **argv) +{ + int num_chan, samprate; + int i, j, test, phase; + short *output; + + if (argc == 1) { + fprintf(stderr, "Usage: vorbseek {vorbisfile} [{vorbisfile]*]\n"); + fprintf(stderr, "Tests various seek offsets to make sure they're sample exact.\n"); + return 0; + } + + #if 0 + { + // check that outofmem occurs correctly + stb_vorbis_alloc va; + va.alloc_buffer = malloc(1024*1024); + for (i=0; i < 1024*1024; i += 10) { + int error=0; + stb_vorbis *v; + va.alloc_buffer_length_in_bytes = i; + v = stb_vorbis_open_filename(argv[1], &error, &va); + if (v != NULL) + break; + printf("Error %d at %d\n", error, i); + } + } + #endif + + for (j=1; j < argc; ++j) { + unsigned int successes=0, attempts = 0; + unsigned int num_samples = stb_vorbis_decode_filename(argv[j], &num_chan, &samprate, &output); + + break; + + if (num_samples == 0xffffffff) { + fprintf(stderr, "Error: couldn't open file or not vorbis file: %s\n", argv[j]); + goto fail; + } + + if (num_chan != 2) { + fprintf(stderr, "vorbseek testing only works with files with 2 channels, %s has %d\n", argv[j], num_chan); + goto fail; + } + + for (test=0; test < 5; ++test) { + int error; + stb_vorbis *v = stb_vorbis_open_filename(argv[j], &error, NULL); + if (v == NULL) { + fprintf(stderr, "Couldn't re-open %s for test #%d\n", argv[j], test); + goto fail; + } + for (phase=0; phase < 3; ++phase) { + unsigned int base = phase == 0 ? 0 : phase == 1 ? num_samples - test_count[test]*test_spacing[test] : num_samples/3; + for (i=0; i < test_count[test]; ++i) { + unsigned int pos = base + i*test_spacing[test]; + if (pos > num_samples) // this also catches underflows + continue; + successes += try_seeking(v, pos, output, num_samples); + attempts += 1; + } + } + stb_vorbis_close(v); + } + printf("%d of %d seeks failed in %s (%d samples)\n", attempts-successes, attempts, argv[j], num_samples); + free(output); + } + return 0; + fail: + return 1; +} \ No newline at end of file diff --git a/source/engine/thirdparty/stb/tests/vorbseek/vorbseek.dsp b/source/engine/thirdparty/stb/tests/vorbseek/vorbseek.dsp new file mode 100644 index 0000000..5eaf579 --- /dev/null +++ b/source/engine/thirdparty/stb/tests/vorbseek/vorbseek.dsp @@ -0,0 +1,96 @@ +# Microsoft Developer Studio Project File - Name="vorbseek" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=vorbseek - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "vorbseek.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "vorbseek.mak" CFG="vorbseek - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "vorbseek - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "vorbseek - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "vorbseek - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /Zd /O2 /I "..\.." /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 + +!ELSEIF "$(CFG)" == "vorbseek - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\.." /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "vorbseek - Win32 Release" +# Name "vorbseek - Win32 Debug" +# Begin Source File + +SOURCE=..\..\stb_vorbis.c +# End Source File +# Begin Source File + +SOURCE=.\vorbseek.c +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/stb/tools/README.footer.md b/source/engine/thirdparty/stb/tools/README.footer.md new file mode 100644 index 0000000..3eb8dc0 --- /dev/null +++ b/source/engine/thirdparty/stb/tools/README.footer.md @@ -0,0 +1,135 @@ + +FAQ +--- + +#### What's the license? + +These libraries are in the public domain. You can do anything you +want with them. You have no legal obligation +to do anything else, although I appreciate attribution. + +They are also licensed under the MIT open source license, if you have lawyers +who are unhappy with public domain. Every source file includes an explicit +dual-license for you to choose from. + +#### How do I use these libraries? + +The idea behind single-header file libraries is that they're easy to distribute and deploy +because all the code is contained in a single file. By default, the .h files in here act as +their own header files, i.e. they declare the functions contained in the file but don't +actually result in any code getting compiled. + +So in addition, you should select _exactly one_ C/C++ source file that actually instantiates +the code, preferably a file you're not editing frequently. This file should define a +specific macro (this is documented per-library) to actually enable the function definitions. +For example, to use stb_image, you should have exactly one C/C++ file that doesn't +include stb_image.h regularly, but instead does + + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + +The right macro to define is pointed out right at the top of each of these libraries. + +#### Are there other single-file public-domain/open source libraries with minimal dependencies out there? + +[Yes.](https://github.com/nothings/single_file_libs) + +#### If I wrap an stb library in a new library, does the new library have to be public domain/MIT? + +No, because it's public domain you can freely relicense it to whatever license your new +library wants to be. + +#### What's the deal with SSE support in GCC-based compilers? + +stb_image will either use SSE2 (if you compile with -msse2) or +will not use any SIMD at all, rather than trying to detect the +processor at runtime and handle it correctly. As I understand it, +the approved path in GCC for runtime-detection require +you to use multiple source files, one for each CPU configuration. +Because stb_image is a header-file library that compiles in only +one source file, there's no approved way to build both an +SSE-enabled and a non-SSE-enabled variation. + +While we've tried to work around it, we've had multiple issues over +the years due to specific versions of gcc breaking what we're doing, +so we've given up on it. See https://github.com/nothings/stb/issues/280 +and https://github.com/nothings/stb/issues/410 for examples. + +#### Some of these libraries seem redundant to existing open source libraries. Are they better somehow? + +Generally they're only better in that they're easier to integrate, +easier to use, and easier to release (single file; good API; no +attribution requirement). They may be less featureful, slower, +and/or use more memory. If you're already using an equivalent +library, there's probably no good reason to switch. + +#### Can I link directly to the table of stb libraries? + +You can use [this URL](https://github.com/nothings/stb#stb_libs) to link directly to that list. + +#### Why do you list "lines of code"? It's a terrible metric. + +Just to give you some idea of the internal complexity of the library, +to help you manage your expectations, or to let you know what you're +getting into. While not all the libraries are written in the same +style, they're certainly similar styles, and so comparisons between +the libraries are probably still meaningful. + +Note though that the lines do include both the implementation, the +part that corresponds to a header file, and the documentation. + +#### Why single-file headers? + +Windows doesn't have standard directories where libraries +live. That makes deploying libraries in Windows a lot more +painful than open source developers on Unix-derivates generally +realize. (It also makes library dependencies a lot worse in Windows.) + +There's also a common problem in Windows where a library was built +against a different version of the runtime library, which causes +link conflicts and confusion. Shipping the libs as headers means +you normally just compile them straight into your project without +making libraries, thus sidestepping that problem. + +Making them a single file makes it very easy to just +drop them into a project that needs them. (Of course you can +still put them in a proper shared library tree if you want.) + +Why not two files, one a header and one an implementation? +The difference between 10 files and 9 files is not a big deal, +but the difference between 2 files and 1 file is a big deal. +You don't need to zip or tar the files up, you don't have to +remember to attach *two* files, etc. + +#### Why "stb"? Is this something to do with Set-Top Boxes? + +No, they are just the initials for my name, Sean T. Barrett. +This was not chosen out of egomania, but as a moderately sane +way of namespacing the filenames and source function names. + +#### Will you add more image types to stb_image.h? + +No. As stb_image use has grown, it has become more important +for us to focus on security of the codebase. Adding new image +formats increases the amount of code we need to secure, so it +is no longer worth adding new formats. + +#### Do you have any advice on how to create my own single-file library? + +Yes. https://github.com/nothings/stb/blob/master/docs/stb_howto.txt + +#### Why public domain? + +I prefer it over GPL, LGPL, BSD, zlib, etc. for many reasons. +Some of them are listed here: +https://github.com/nothings/stb/blob/master/docs/why_public_domain.md + +#### Why C? + +Primarily, because I use C, not C++. But it does also make it easier +for other people to use them from other languages. + +#### Why not C99? stdint.h, declare-anywhere, etc. + +I still use MSVC 6 (1998) as my IDE because it has better human factors +for me than later versions of MSVC. diff --git a/source/engine/thirdparty/stb/tools/README.header.md b/source/engine/thirdparty/stb/tools/README.header.md new file mode 100644 index 0000000..65e0408 --- /dev/null +++ b/source/engine/thirdparty/stb/tools/README.header.md @@ -0,0 +1,22 @@ +stb +=== + +single-file public domain (or MIT licensed) libraries for C/C++ + +# This project discusses security-relevant bugs in public in Github Issues and Pull Requests, and it may take significant time for security fixes to be implemented or merged. If this poses an unreasonable risk to your project, do not use stb libraries. + +Noteworthy: + +* image loader: [stb_image.h](stb_image.h) +* image writer: [stb_image_write.h](stb_image_write.h) +* image resizer: [stb_image_resize2.h](stb_image_resize2.h) +* font text rasterizer: [stb_truetype.h](stb_truetype.h) +* typesafe containers: [stb_ds.h](stb_ds.h) + +Most libraries by stb, except: stb_dxt by Fabian "ryg" Giesen, original stb_image_resize +by Jorge L. "VinoBS" Rodriguez, and stb_image_resize2 and stb_sprintf by Jeff Roberts. + + + +library | lastest version | category | LoC | description +--------------------- | ---- | -------- | --- | -------------------------------- diff --git a/source/engine/thirdparty/stb/tools/README.list b/source/engine/thirdparty/stb/tools/README.list new file mode 100644 index 0000000..e858783 --- /dev/null +++ b/source/engine/thirdparty/stb/tools/README.list @@ -0,0 +1,21 @@ +stb_vorbis.c | audio | decode ogg vorbis files from file/memory to float/16-bit signed output +stb_hexwave.h | audio | audio waveform synthesizer +stb_image.h | graphics | image loading/decoding from file/memory: JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC +stb_truetype.h | graphics | parse, decode, and rasterize characters from truetype fonts +stb_image_write.h | graphics | image writing to disk: PNG, TGA, BMP +stb_image_resize2.h | graphics | resize images larger/smaller with good quality +stb_rect_pack.h | graphics | simple 2D rectangle packer with decent quality +stb_perlin.h | graphics | perlin's revised simplex noise w/ different seeds +stb_ds.h | utility | typesafe dynamic array and hash tables for C, will compile in C++ +stb_sprintf.h | utility | fast sprintf, snprintf for C/C++ +stb_textedit.h | user interface | guts of a text editor for games etc implementing them from scratch +stb_voxel_render.h | 3D graphics | Minecraft-esque voxel rendering "engine" with many more features +stb_dxt.h | 3D graphics | Fabian "ryg" Giesen's real-time DXT compressor +stb_easy_font.h | 3D graphics | quick-and-dirty easy-to-deploy bitmap font for printing frame rate, etc +stb_tilemap_editor.h | game dev | embeddable tilemap editor +stb_herringbone_wang_tile.h | game dev | herringbone Wang tile map generator +stb_c_lexer.h | parsing | simplify writing parsers for C-like languages +stb_divide.h | math | more useful 32-bit modulus e.g. "euclidean divide" +stb_connected_components.h | misc | incrementally compute reachability on grids +stb_leakcheck.h | misc | quick-and-dirty malloc/free leak-checking +stb_include.h | misc | implement recursive #include support, particularly for GLSL diff --git a/source/engine/thirdparty/stb/tools/build_matrix.c b/source/engine/thirdparty/stb/tools/build_matrix.c new file mode 100644 index 0000000..4051020 --- /dev/null +++ b/source/engine/thirdparty/stb/tools/build_matrix.c @@ -0,0 +1,137 @@ +#define STB_DEFINE +#include "stb.h" + +// true if no error +int run_command(char *batch_file, char *command) +{ + char buffer[4096]; + if (batch_file[0]) { + sprintf(buffer, "%s && %s", batch_file, command); + return system(buffer) == 0; + } else { + return system(command) == 0; + } +} + +typedef struct +{ + char *compiler_name; + char *batchfile; + char *objdir; + char *compiler; + char *args; + char *link; +} compiler_info; + +compiler_info *compilers; +char *shared_args; +char *shared_link; + +typedef struct +{ + char *filelist; +} project_info; + +project_info *projects; + +enum { NONE, IN_COMPILERS, IN_ARGS, IN_PROJECTS, IN_LINK }; + +int main(int argc, char **argv) +{ + int state = NONE; + int i,j,n; + char **line; + if (argc != 2) stb_fatal("Usage: stb_build_matrix {build-file}\n"); + line = stb_stringfile(argv[1], &n); + if (line == 0) stb_fatal("Couldn't open file '%s'\n", argv[1]); + + for (i=0; i < n; ++i) { + char *p = stb_trimwhite(line[i]); + if (p[0] == 0) continue; + else if (p[0] == '#') continue; + else if (0 == stricmp(p, "[compilers]")) { state = IN_COMPILERS; } + else if (0 == stricmp(p, "[args]" )) { state = IN_ARGS ; shared_args = NULL; } + else if (0 == stricmp(p, "[projects]" )) { state = IN_PROJECTS ; } + else if (0 == stricmp(p, "[link]" )) { state = IN_LINK ; shared_link = NULL; } + else { + switch (state) { + case NONE: stb_fatal("Invalid text outside section at line %d.", i+1); + case IN_COMPILERS: { + char buffer[4096]; + int count; + compiler_info ci; + char **tokens = stb_tokens_stripwhite(p, ",", &count), *batch; + if (count > 3) stb_fatal("Expecting name and batch file name at line %d.", i+1); + batch = (count==1 ? tokens[0] : tokens[1]); + if (strlen(batch)) + sprintf(buffer, "c:\\%s.bat", batch); + else + strcpy(buffer, ""); + ci.compiler_name = strdup(tokens[0]); + ci.batchfile = strdup(buffer); + ci.compiler = count==3 ? strdup(tokens[2]) : "cl"; + if (0==strnicmp(batch, "vcvars_", 7)) + ci.objdir = strdup(stb_sprintf("vs_%s_%d", batch+7, i)); + else + ci.objdir = strdup(stb_sprintf("%s_%d", batch, i)); + ci.args = shared_args; + ci.link = shared_link; + stb_arr_push(compilers, ci); + break; + } + case IN_ARGS: { + stb_arr_push(shared_args, ' '); + for (j=0; p[j] != 0; ++j) + stb_arr_push(shared_args, p[j]); + break; + } + case IN_LINK: { + stb_arr_push(shared_link, ' '); + for (j=0; p[j] != 0; ++j) + stb_arr_push(shared_link, p[j]); + break; + } + case IN_PROJECTS: { + project_info pi; + pi.filelist = strdup(p); + stb_arr_push(projects, pi); + break; + } + } + } + } + + _mkdir("obj"); + for (j=0; j < stb_arr_len(compilers); ++j) { + char command[4096]; + for (i=0; i < stb_arr_len(projects); ++i) { + int r; + _mkdir(stb_sprintf("obj/%s", compilers[j].objdir)); + if (stb_suffix(compilers[j].compiler, "cl")) + sprintf(command, "%s %.*s %s /link %.*s", + compilers[j].compiler, + stb_arr_len(compilers[j].args), compilers[j].args, + projects[i].filelist, + stb_arr_len(compilers[j].link), compilers[j].link); + else + sprintf(command, "%s %.*s %s %.*s", + compilers[j].compiler, + stb_arr_len(compilers[j].args), compilers[j].args, + projects[i].filelist, + stb_arr_len(compilers[j].link), compilers[j].link); + r = run_command(compilers[j].batchfile, command); + stbprint("{%c== Compiler %s == Building %s}\n", r ? '$' : '!', compilers[j].compiler_name, projects[i].filelist); + stb_copyfile("a.exe", stb_sprintf("obj/%s/a.exe", compilers[j].objdir)); + //printf("Copy: %s to %s\n", "a.exe", stb_sprintf("obj/%s/a.exe", compilers[j].objdir)); + stb_copyfile("temp.exe", stb_sprintf("obj/%s/temp.exe", compilers[j].objdir)); + system("if EXIST a.exe del /q a.exe"); + system("if EXIST temp.exe del /q temp.exe"); + system("if EXIST *.obj del /q *.obj"); + system("if EXIST *.o del /q *.o"); + if (!r) + return 1; + } + } + + return 0; +} diff --git a/source/engine/thirdparty/stb/tools/easy_font_maker.c b/source/engine/thirdparty/stb/tools/easy_font_maker.c new file mode 100644 index 0000000..f1b4836 --- /dev/null +++ b/source/engine/thirdparty/stb/tools/easy_font_maker.c @@ -0,0 +1,211 @@ +// This program was used to encode the data for stb_simple_font.h + +#define STB_DEFINE +#include "stb.h" +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +int w,h; +uint8 *data; + +int last_x[2], last_y[2]; +int num_seg[2], non_empty; +#if 0 +typedef struct +{ + unsigned short first_segment; + unsigned char advance; +} chardata; + +typedef struct +{ + unsigned char x:4; + unsigned char y:4; + unsigned char len:3; + unsigned char dir:1; +} segment; + +segment *segments; + +void add_seg(int x, int y, int len, int horizontal) +{ + segment s; + s.x = x; + s.y = y; + s.len = len; + s.dir = horizontal; + assert(s.x == x); + assert(s.y == y); + assert(s.len == len); + stb_arr_push(segments, s); +} +#else +typedef struct +{ + unsigned char first_segment:8; + unsigned char first_v_segment:8; + unsigned char advance:5; + unsigned char voff:1; +} chardata; + +#define X_LIMIT 1 +#define LEN_LIMIT 7 + +typedef struct +{ + unsigned char dx:1; + unsigned char y:4; + unsigned char len:3; +} segment; + +segment *segments; +segment *vsegments; + +void add_seg(int x, int y, int len, int horizontal) +{ + segment s; + + while (x - last_x[horizontal] > X_LIMIT) { + add_seg(last_x[horizontal] + X_LIMIT, 0, 0, horizontal); + } + while (len > LEN_LIMIT) { + add_seg(x, y, LEN_LIMIT, horizontal); + len -= LEN_LIMIT; + x += LEN_LIMIT*horizontal; + y += LEN_LIMIT*!horizontal; + } + + s.dx = x - last_x[horizontal]; + s.y = y; + s.len = len; + non_empty += len != 0; + //assert(s.x == x); + assert(s.y == y); + assert(s.len == len); + ++num_seg[horizontal]; + if (horizontal) + stb_arr_push(segments, s); + else + stb_arr_push(vsegments, s); + last_x[horizontal] = x; +} + +void print_segments(segment *s) +{ + int i, hpos; + printf(" "); + hpos = 4; + for (i=0; i < stb_arr_len(s); ++i) { + // repack for portability + unsigned char seg = s[i].len + s[i].dx*8 + s[i].y*16; + hpos += printf("%d,", seg); + if (hpos > 72 && i+1 < stb_arr_len(s)) { + hpos = 4; + printf("\n "); + } + } + printf("\n"); +} + +#endif + +chardata charinfo[128]; + +int parse_char(int x, chardata *c, int offset) +{ + int start_x = x, end_x, top_y = 0, y; + + c->first_segment = stb_arr_len(segments); + c->first_v_segment = stb_arr_len(vsegments) - offset; + assert(c->first_segment == stb_arr_len(segments)); + assert(c->first_v_segment + offset == stb_arr_len(vsegments)); + + // find advance distance + end_x = x+1; + while (data[end_x*3] == 255) + ++end_x; + c->advance = end_x - start_x + 1; + + last_x[0] = last_x[1] = 0; + last_y[0] = last_y[1] = 0; + + for (y=2; y < h; ++y) { + for (x=start_x; x < end_x; ++x) { + if (data[y*3*w+x*3+1] < 255) { + top_y = y; + break; + } + } + if (top_y) + break; + } + c->voff = top_y > 2; + if (top_y > 2) + top_y = 3; + + for (x=start_x; x < end_x; ++x) { + int y; + for (y=2; y < h; ++y) { + if (data[y*3*w+x*3+1] < 255) { + if (data[y*3*w+x*3+0] == 255) { // red + int len=0; + while (y+len < h && data[(y+len)*3*w+x*3+0] == 255 && data[(y+len)*3*w+x*3+1] == 0) { + data[(y+len)*3*w+x*3+0] = 0; + ++len; + } + add_seg(x-start_x,y-top_y,len,0); + } + if (data[y*3*w+x*3+2] == 255) { // blue + int len=0; + while (x+len < end_x && data[y*3*w+(x+len)*3+2] == 255 && data[y*3*w+(x+len)*3+1] == 0) { + data[y*3*w+(x+len)*3+2] = 0; + ++len; + } + add_seg(x-start_x,y-top_y,len,1); + } + } + } + } + return end_x; +} + + +int main(int argc, char **argv) +{ + int c, x=0; + data = stbi_load("easy_font_raw.png", &w, &h, 0, 3); + for (c=32; c < 127; ++c) { + x = parse_char(x, &charinfo[c], 0); + printf("%3d -- %3d %3d\n", c, charinfo[c].first_segment, charinfo[c].first_v_segment); + } + printf("===\n"); + printf("%d %d %d\n", num_seg[0], num_seg[1], non_empty); + printf("%d\n", sizeof(segments[0]) * stb_arr_len(segments)); + printf("%d\n", sizeof(segments[0]) * stb_arr_len(segments) + sizeof(segments[0]) * stb_arr_len(vsegments) + sizeof(charinfo[32])*95); + + printf("struct {\n" + " unsigned char advance;\n" + " unsigned char h_seg;\n" + " unsigned char v_seg;\n" + "} stb_easy_font_charinfo[96] = {\n"); + charinfo[c].first_segment = stb_arr_len(segments); + charinfo[c].first_v_segment = stb_arr_len(vsegments); + for (c=32; c < 128; ++c) { + if ((c & 3) == 0) printf(" "); + printf("{ %2d,%3d,%3d },", + charinfo[c].advance + 16*charinfo[c].voff, + charinfo[c].first_segment, + charinfo[c].first_v_segment); + if ((c & 3) == 3) printf("\n"); else printf(" "); + } + printf("};\n\n"); + + printf("unsigned char stb_easy_font_hseg[%d] = {\n", stb_arr_len(segments)); + print_segments(segments); + printf("};\n\n"); + + printf("unsigned char stb_easy_font_vseg[%d] = {\n", stb_arr_len(vsegments)); + print_segments(vsegments); + printf("};\n"); + return 0; +} diff --git a/source/engine/thirdparty/stb/tools/make_readme.c b/source/engine/thirdparty/stb/tools/make_readme.c new file mode 100644 index 0000000..bca9d2b --- /dev/null +++ b/source/engine/thirdparty/stb/tools/make_readme.c @@ -0,0 +1,65 @@ +#define STB_DEFINE +#include "../stb.h" + +int main(int argc, char **argv) +{ + int i; + int hlen, flen, listlen, total_lines = 0; + char *header = stb_file("README.header.md", &hlen); // stb_file - read file into malloc()ed buffer + char *footer = stb_file("README.footer.md", &flen); // stb_file - read file into malloc()ed buffer + char **list = stb_stringfile("README.list", &listlen); // stb_stringfile - read file lines into malloced array of strings + + FILE *f = fopen("../README.md", "wb"); + + fprintf(f, "\r\n\r\n"); + fwrite(header, 1, hlen, f); + + for (i=0; i < listlen; ++i) { + int num,j; + char **tokens = stb_tokens_stripwhite(list[i], "|", &num); // stb_tokens -- tokenize string into malloced array of strings + int num_lines; + char **lines = stb_stringfile(stb_sprintf("../%s", tokens[0]), &num_lines); + char *s1, *s2,*s3; + if (lines == NULL) stb_fatal("Couldn't open '%s'", tokens[0]); + s1 = strchr(lines[0], '-'); + if (!s1) stb_fatal("Couldn't find '-' before version number in %s", tokens[0]); // stb_fatal -- print error message & exit + s2 = strchr(s1+2, '-'); + if (!s2) stb_fatal("Couldn't find '-' after version number in %s", tokens[0]); // stb_fatal -- print error message & exit + *s2 = 0; + s1 += 1; + s1 = stb_trimwhite(s1); // stb_trimwhite -- advance pointer to after whitespace & delete trailing whitespace + if (*s1 == 'v') ++s1; + s3 = tokens[0]; + stb_trimwhite(s3); + fprintf(f, "**["); + if (strlen(s3) < 21) { + fprintf(f, "%s", tokens[0]); + } else { + char buffer[256]; + strncpy(buffer, s3, 18); + buffer[18] = 0; + strcat(buffer, "..."); + fprintf(f, "%s", buffer); + } + fprintf(f, "](%s)**", tokens[0]); + fprintf(f, " | %s", s1); + s1 = stb_trimwhite(tokens[1]); // stb_trimwhite -- advance pointer to after whitespace & delete trailing whitespace + s2 = stb_dupreplace(s1, " ", " "); // stb_dupreplace -- search & replace string and malloc result + fprintf(f, " | %s", s2); + free(s2); + fprintf(f, " | %d", num_lines); + total_lines += num_lines; + for (j=2; j < num; ++j) + fprintf(f, " | %s", tokens[j]); + fprintf(f, "\r\n"); + } + + fprintf(f, "\r\n"); + fprintf(f, "Total libraries: %d\r\n", listlen); + fprintf(f, "Total lines of C code: %d\r\n\r\n", total_lines); + + fwrite(footer, 1, flen, f); + fclose(f); + + return 0; +} diff --git a/source/engine/thirdparty/stb/tools/make_readme.dsp b/source/engine/thirdparty/stb/tools/make_readme.dsp new file mode 100644 index 0000000..232dd86 --- /dev/null +++ b/source/engine/thirdparty/stb/tools/make_readme.dsp @@ -0,0 +1,97 @@ +# Microsoft Developer Studio Project File - Name="make_readme" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=make_readme - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "make_readme.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "make_readme.mak" CFG="make_readme - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "make_readme - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "make_readme - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "make_readme - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "make_readme - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug\make_readme" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "make_readme - Win32 Release" +# Name "make_readme - Win32 Debug" +# Begin Source File + +SOURCE=.\make_readme.c +# End Source File +# Begin Source File + +SOURCE=.\README.header.md +# End Source File +# Begin Source File + +SOURCE=.\README.list +# End Source File +# End Target +# End Project diff --git a/source/engine/thirdparty/stb/tools/mr.bat b/source/engine/thirdparty/stb/tools/mr.bat new file mode 100644 index 0000000..475bc4f --- /dev/null +++ b/source/engine/thirdparty/stb/tools/mr.bat @@ -0,0 +1 @@ +debug\make_readme diff --git a/source/engine/thirdparty/stb/tools/trailing_whitespace.c b/source/engine/thirdparty/stb/tools/trailing_whitespace.c new file mode 100644 index 0000000..55afc87 --- /dev/null +++ b/source/engine/thirdparty/stb/tools/trailing_whitespace.c @@ -0,0 +1,32 @@ +#define STB_DEFINE +#include "stb.h" + +int main(int argc, char **argv) +{ + int i; + for (i=1; i < argc; ++i) { + int len; + FILE *f; + char *s = stb_file(argv[i], &len); + char *end, *src, *dest; + if (s == NULL) { + printf("Couldn't read file '%s'.\n", argv[i]); + continue; + } + end = s + len; + src = dest = s; + while (src < end) { + char *start=0; + while (src < end && *src != '\n' && *src != '\r') + *dest++ = *src++; + while (dest-1 > s && (dest[-1] == ' ' || dest[-1] == '\t')) + --dest; + while (src < end && (*src == '\n' || *src == '\r')) + *dest++ = *src++; + } + f = fopen(argv[i], "wb"); + fwrite(s, 1, dest-s, f); + fclose(f); + } + return 0; +} diff --git a/source/engine/thirdparty/stb/tools/unicode.c b/source/engine/thirdparty/stb/tools/unicode.c new file mode 100644 index 0000000..9536d5d --- /dev/null +++ b/source/engine/thirdparty/stb/tools/unicode.c @@ -0,0 +1,749 @@ +#define STB_DEFINE +#include "../stb.h" + +// create unicode mappings +// +// Two kinds of mappings: +// map to a number +// map to a bit +// +// For mapping to a number, we use the following strategy: +// +// User supplies: +// 1. a table of numbers (for now we use uint16, so full Unicode table is 4MB) +// 2. a "don't care" value +// 3. define a 'fallback' value (typically 0) +// 4. define a fast-path range (typically 0..255 or 0..1023) [@TODO: automate detecting this] +// +// Code: +// 1. Determine range of *end* of unicode codepoints (U+10FFFF and down) which +// all have the same value (or don't care). If large enough, emit this as a +// special case in the code. +// 2. Repeat above, limited to at most U+FFFF. +// 3. Cluster the data into intervals of 8,16,32,64,128,256 numeric values. +// 3a. If all the values in an interval are fallback/dont-care, no further processing +// 3b. Find the "trimmed range" outside which all the values are the fallback or don't care +// 3c. Find the "special trimmed range" outside which all the values are some constant or don't care +// 4. Pack the clusters into continuous memory, and find previous instances of +// the cluster. Repeat for trimmed & special-trimmed. In the first case, find +// previous instances of the cluster (allow don't-care to match in either +// direction), both aligned and mis-aligned; in the latter, starting where +// things start or mis-aligned. Build an index table specifying the +// location of each cluster (and its length). Allow an extra indirection here; +// the full-sized index can index a smaller table which has the actual offset +// (and lengths). +// 5. Associate with each packed continuous memory above the amount of memory +// required to store the data w/ smallest datatype (of uint8, uint16, uint32). +// Discard the continuous memory. Recurse on each index table, but avoid the +// smaller packing. +// +// For mapping to a bit, we pack the results for 8 characters into a byte, and then apply +// the above strategy. Note that there may be more optimal approaches with e.g. packing +// 8 different bits into a single structure, though, which we should explore eventually. + + +// currently we limit *indices* to being 2^16, and we pack them as +// index + end_trim*2^16 + start_trim*2^24; specials have to go in a separate table +typedef uint32 uval; +#define UVAL_DONT_CARE_DEFAULT 0xffffffff + +typedef struct +{ + uval *input; + uint32 dont_care; + uint32 fallback; + int fastpath; + int length; + int depth; + int has_sign; + int splittable; + int replace_fallback_with_codepoint; + size_t input_size; + size_t inherited_storage; +} table; + +typedef struct +{ + int split_log2; + table result; // index into not-returned table + int storage; +} output; + +typedef struct +{ + table t; + char **output_name; +} info; + +typedef struct +{ + size_t path; + size_t size; +} result; + +typedef struct +{ + uint8 trim_end; + uint8 trim_start; + uint8 special; + uint8 aligned; + uint8 indirect; + + uint16 overhead; // add some forced overhead for each mode to avoid getting complex encoding when it doesn't save much + +} mode_info; + +mode_info modes[] = +{ + { 0,0,0,0,0, 32, }, + { 0,0,0,0,1, 100, }, + { 0,0,0,1,0, 32, }, + { 0,0,0,1,1, 100, }, + { 0,0,1,0,1, 100, }, + { 0,0,1,1,0, 32, }, + { 0,0,1,1,1, 200, }, + { 1,0,0,0,0, 100, }, + { 1,0,0,0,1, 120, }, + { 1,1,0,0,0, 100, }, + { 1,1,0,0,1, 130, }, + { 1,0,1,0,0, 130, }, + { 1,0,1,0,1, 180, }, + { 1,1,1,0,0, 180, }, + { 1,1,1,0,1, 200, }, +}; + +#define MODECOUNT (sizeof(modes)/sizeof(modes[0])) +#define CLUSTERSIZECOUNT 6 // 8,16, 32,64, 128,256 + +size_t size_for_max_number(uint32 number) +{ + if (number == 0) return 0; + if (number < 256) return 1; + if (number < 256*256) return 2; + if (number < 256*256*256) return 3; + return 4; +} + +size_t size_for_max_number_aligned(uint32 number) +{ + size_t n = size_for_max_number(number); + return n == 3 ? 4 : n; +} + +uval get_data(uval *data, int offset, uval *end) +{ + if (data + offset >= end) + return 0; + else + return data[offset]; +} + +int safe_len(uval *data, int len, uval *end) +{ + if (len > end - data) + return end - data; + return len; +} + +uval tempdata[256]; +int dirty=0; + +size_t find_packed(uval **packed, uval *data, int len, int aligned, int fastpath, uval *end, int offset, int replace) +{ + int packlen = stb_arr_len(*packed); + int i,p; + + if (data+len > end || replace) { + int safelen = safe_len(data, len, end); + memset(tempdata, 0, dirty*sizeof(tempdata[0])); + memcpy(tempdata, data, safelen * sizeof(data[0])); + data = tempdata; + dirty = len; + } + if (replace) { + int i; + int safelen = safe_len(data, len, end); + for (i=0; i < safelen; ++i) + if (data[i] == 0) + data[i] = offset+i; + } + + if (len <= 0) + return 0; + if (!fastpath) { + if (aligned) { + for (i=0; i < packlen; i += len) + if ((*packed)[i] == data[0] && 0==memcmp(&(*packed)[i], data, len * sizeof(uval))) + return i / len; + } else { + for (i=0; i < packlen-len+1; i += 1 ) + if ((*packed)[i] == data[0] && 0==memcmp(&(*packed)[i], data, len * sizeof(uval))) + return i; + } + } + p = stb_arr_len(*packed); + for (i=0; i < len; ++i) + stb_arr_push(*packed, data[i]); + return p; +} + +void output_table(char *name1, char *name2, uval *data, int length, int sign, char **names) +{ + char temp[20]; + uval maxv = 0; + int bytes, numlen, at_newline; + int linelen = 79; // @TODO: make table more readable by choosing a length that's a multiple? + int i,pos, do_split=0; + for (i=0; i < length; ++i) + if (sign) + maxv = stb_max(maxv, (uval)abs((int)data[i])); + else + maxv = stb_max(maxv, data[i]); + bytes = size_for_max_number_aligned(maxv); + sprintf(temp, "%d", maxv); + numlen=strlen(temp); + if (sign) + ++numlen; + + if (bytes == 0) + return; + + printf("uint%d %s%s[%d] = {\n", bytes*8, name1, name2, length); + at_newline = 1; + for (i=0; i < length; ++i) { + if (pos + numlen + 2 > linelen) { + printf("\n"); + at_newline = 1; + pos = 0; + } + if (at_newline) { + printf(" "); + pos = 2; + at_newline = 0; + } else { + printf(" "); + ++pos; + } + printf("%*d,", numlen, data[i]); + pos += numlen+1; + } + if (!at_newline) printf("\n"); + printf("};\n"); +} + +void output_table_with_trims(char *name1, char *name2, uval *data, int length) +{ + uval maxt=0, maxp=0; + int i,d,s,e, count; + // split the table into two pieces + uval *trims = NULL; + + if (length == 0) + return; + + for (i=0; i < stb_arr_len(data); ++i) { + stb_arr_push(trims, data[i] >> 16); + data[i] &= 0xffff; + maxt = stb_max(maxt, trims[i]); + maxp = stb_max(maxp, data[i]); + } + + d=s=e=1; + if (maxt >= 256) { + // need to output start & end values + if (maxp >= 256) { + // can pack into a single table + printf("struct { uint16 val; uint8 start, end; } %s%s[%d] = {\n", name1, name2, length); + } else { + output_table(name1, name2, data, length, 0, 0); + d=0; + printf("struct { uint8 start, end; } %s%s_trim[%d] = {\n", name1, name2, length); + } + } else if (maxt > 0) { + if (maxp >= 256) { + output_table(name1, name2, data, length, 0, 0); + output_table(name1, stb_sprintf("%s_end", name2), trims, length, 0, 0); + return; + } else { + printf("struct { uint8 val, end; } %s%s[%d] = {\n", name1, name2, length); + s=0; + } + } else { + output_table(name1, name2, data, length, 0, 0); + return; + } + // d or s can be zero (but not both), e is always present and last + count = d + s + e; + assert(count >= 2 && count <= 3); + + { + char temp[60]; + uval maxv = 0; + int numlen, at_newline, len; + int linelen = 79; // @TODO: make table more readable by choosing a length that's a multiple? + int i,pos, do_split=0; + numlen = 0; + for (i=0; i < length; ++i) { + if (count == 2) + sprintf(temp, "{%d,%d}", d ? data[i] : (trims[i]>>8), trims[i]&255); + else + sprintf(temp, "{%d,%d,%d}", data[i], trims[i]>>8, trims[i]&255); + len = strlen(temp); + numlen = stb_max(len, numlen); + } + + at_newline = 1; + for (i=0; i < length; ++i) { + if (pos + numlen + 2 > linelen) { + printf("\n"); + at_newline = 1; + pos = 0; + } + if (at_newline) { + printf(" "); + pos = 2; + at_newline = 0; + } else { + printf(" "); + ++pos; + } + if (count == 2) + sprintf(temp, "{%d,%d}", d ? data[i] : (trims[i]>>8), trims[i]&255); + else + sprintf(temp, "{%d,%d,%d}", data[i], trims[i]>>8, trims[i]&255); + printf("%*s,", numlen, temp); + pos += numlen+1; + } + if (!at_newline) printf("\n"); + printf("};\n"); + } +} + +int weight=1; + +table pack_for_mode(table *t, int mode, char *table_name) +{ + size_t extra_size; + int i; + uval maxv; + mode_info mi = modes[mode % MODECOUNT]; + int size = 8 << (mode / MODECOUNT); + table newtab; + uval *packed = NULL; + uval *index = NULL; + uval *indirect = NULL; + uval *specials = NULL; + newtab.dont_care = UVAL_DONT_CARE_DEFAULT; + if (table_name) + printf("// clusters of %d\n", size); + for (i=0; i < t->length; i += size) { + uval newval; + int fastpath = (i < t->fastpath); + if (mi.special) { + int end_trim = size-1; + int start_trim = 0; + uval special; + // @TODO: pick special from start or end instead of only end depending on which is longer + for(;;) { + special = t->input[i + end_trim]; + if (special != t->dont_care || end_trim == 0) + break; + --end_trim; + } + // at this point, special==inp[end_trim], and end_trim >= 0 + if (special == t->dont_care && !fastpath) { + // entire block is don't care, so OUTPUT don't care + stb_arr_push(index, newtab.dont_care); + continue; + } else { + uval pos, trim; + if (mi.trim_end && !fastpath) { + while (end_trim >= 0) { + if (t->input[i + end_trim] == special || t->input[i + end_trim] == t->dont_care) + --end_trim; + else + break; + } + } + + if (mi.trim_start && !fastpath) { + while (start_trim < end_trim) { + if (t->input[i + start_trim] == special || t->input[i + start_trim] == t->dont_care) + ++start_trim; + else + break; + } + } + + // end_trim points to the last character we have to output + + // find the first match, or add it + pos = find_packed(&packed, &t->input[i+start_trim], end_trim-start_trim+1, mi.aligned, fastpath, &t->input[t->length], i+start_trim, t->replace_fallback_with_codepoint); + + // encode as a uval + if (!mi.trim_end) { + if (end_trim == 0) + pos = special; + else + pos = pos | 0x80000000; + } else { + assert(end_trim < size && end_trim >= -1); + if (!fastpath) assert(end_trim < size-1); // special always matches last one + assert(end_trim < size && end_trim+1 >= 0); + if (!fastpath) assert(end_trim+1 < size); + + if (mi.trim_start) + trim = start_trim*256 + (end_trim+1); + else + trim = end_trim+1; + + assert(pos < 65536); // @TODO: if this triggers, just bail on this search path + pos = pos + (trim << 16); + } + + newval = pos; + + stb_arr_push(specials, special); + } + } else if (mi.trim_end) { + int end_trim = size-1; + int start_trim = 0; + uval pos, trim; + + while (end_trim >= 0 && !fastpath) + if (t->input[i + end_trim] == t->fallback || t->input[i + end_trim] == t->dont_care) + --end_trim; + else + break; + + if (mi.trim_start && !fastpath) { + while (start_trim < end_trim) { + if (t->input[i + start_trim] == t->fallback || t->input[i + start_trim] == t->dont_care) + ++start_trim; + else + break; + } + } + + // end_trim points to the last character we have to output, and can be -1 + ++end_trim; // make exclusive at end + + if (end_trim == 0 && size == 256) + start_trim = end_trim = 1; // we can't make encode a length from 0..256 in 8 bits, so restrict end_trim to 1..256 + + // find the first match, or add it + pos = find_packed(&packed, &t->input[i+start_trim], end_trim - start_trim, mi.aligned, fastpath, &t->input[t->length], i+start_trim, t->replace_fallback_with_codepoint); + + assert(end_trim <= size && end_trim >= 0); + if (size == 256) + assert(end_trim-1 < 256 && end_trim-1 >= 0); + else + assert(end_trim < 256 && end_trim >= 0); + if (size == 256) + --end_trim; + + if (mi.trim_start) + trim = start_trim*256 + end_trim; + else + trim = end_trim; + + assert(pos < 65536); // @TODO: if this triggers, just bail on this search path + pos = pos + (trim << 16); + + newval = pos; + } else { + newval = find_packed(&packed, &t->input[i], size, mi.aligned, fastpath, &t->input[t->length], i, t->replace_fallback_with_codepoint); + } + + if (mi.indirect) { + int j; + for (j=0; j < stb_arr_len(indirect); ++j) + if (indirect[j] == newval) + break; + if (j == stb_arr_len(indirect)) + stb_arr_push(indirect, newval); + stb_arr_push(index, j); + } else { + stb_arr_push(index, newval); + } + } + + // total up the new size for everything but the index table + extra_size = mi.overhead * weight; // not the actual overhead cost; a penalty to avoid excessive complexity + extra_size += 150; // per indirection + if (table_name) + extra_size = 0; + + if (t->has_sign) { + // 'packed' contains two values, which should be packed positive & negative for size + uval maxv2; + for (i=0; i < stb_arr_len(packed); ++i) + if (packed[i] & 0x80000000) + maxv2 = stb_max(maxv2, packed[i]); + else + maxv = stb_max(maxv, packed[i]); + maxv = stb_max(maxv, maxv2) << 1; + } else { + maxv = 0; + for (i=0; i < stb_arr_len(packed); ++i) + if (packed[i] > maxv && packed[i] != t->dont_care) + maxv = packed[i]; + } + extra_size += stb_arr_len(packed) * (t->splittable ? size_for_max_number(maxv) : size_for_max_number_aligned(maxv)); + if (table_name) { + if (t->splittable) + output_table_with_trims(table_name, "", packed, stb_arr_len(packed)); + else + output_table(table_name, "", packed, stb_arr_len(packed), t->has_sign, NULL); + } + + maxv = 0; + for (i=0; i < stb_arr_len(specials); ++i) + if (specials[i] > maxv) + maxv = specials[i]; + extra_size += stb_arr_len(specials) * size_for_max_number_aligned(maxv); + if (table_name) + output_table(table_name, "_default", specials, stb_arr_len(specials), 0, NULL); + + maxv = 0; + for (i=0; i < stb_arr_len(indirect); ++i) + if (indirect[i] > maxv) + maxv = indirect[i]; + extra_size += stb_arr_len(indirect) * size_for_max_number(maxv); + + if (table_name && stb_arr_len(indirect)) { + if (mi.trim_end) + output_table_with_trims(table_name, "_index", indirect, stb_arr_len(indirect)); + else { + assert(0); // this case should only trigger in very extreme circumstances + output_table(table_name, "_index", indirect, stb_arr_len(indirect), 0, NULL); + } + mi.trim_end = mi.special = 0; + } + + if (table_name) + printf("// above tables should be %d bytes\n", extra_size); + + maxv = 0; + for (i=0; i < stb_arr_len(index); ++i) + if (index[i] > maxv && index[i] != t->dont_care) + maxv = index[i]; + newtab.splittable = mi.trim_end; + newtab.input_size = newtab.splittable ? size_for_max_number(maxv) : size_for_max_number_aligned(maxv); + newtab.input = index; + newtab.length = stb_arr_len(index); + newtab.inherited_storage = t->inherited_storage + extra_size; + newtab.fastpath = 0; + newtab.depth = t->depth+1; + stb_arr_free(indirect); + stb_arr_free(packed); + stb_arr_free(specials); + + return newtab; +} + +result pack_table(table *t, size_t path, int min_storage) +{ + int i; + result best; + best.size = t->inherited_storage + t->input_size * t->length; + best.path = path; + + if ((int) t->inherited_storage > min_storage) { + best.size = stb_max(best.size, t->inherited_storage); + return best; + } + + if (t->length <= 256 || t->depth >= 4) { + //printf("%08x: %7d\n", best.path, best.size); + return best; + } + + path <<= 7; + for (i=0; i < MODECOUNT * CLUSTERSIZECOUNT; ++i) { + table newtab; + result r; + newtab = pack_for_mode(t, i, 0); + r = pack_table(&newtab, path+i+1, min_storage); + if (r.size < best.size) + best = r; + stb_arr_free(newtab.input); + //printf("Size: %6d + %6d\n", newtab.inherited_storage, newtab.input_size * newtab.length); + } + return best; +} + +int pack_table_by_modes(table *t, int *modes) +{ + table s = *t; + while (*modes > -1) { + table newtab; + newtab = pack_for_mode(&s, *modes, 0); + if (s.input != t->input) + stb_arr_free(s.input); + s = newtab; + ++modes; + } + return s.inherited_storage + s.input_size * s.length; +} + +int strip_table(table *t, int exceptions) +{ + uval terminal_value; + int p = t->length-1; + while (t->input[p] == t->dont_care) + --p; + terminal_value = t->input[p]; + + while (p >= 0x10000) { + if (t->input[p] != terminal_value && t->input[p] != t->dont_care) { + if (exceptions) + --exceptions; + else + break; + } + --p; + } + return p+1; // p is a character we must output +} + +void optimize_table(table *t, char *table_name) +{ + int modelist[3] = { 85, -1 }; + int modes[8]; + int num_modes = 0; + int decent_size; + result r; + size_t path; + table s; + + // strip tail end of table + int orig_length = t->length; + int threshhold = 0xffff; + int p = strip_table(t, 2); + int len_saved = t->length - p; + if (len_saved >= threshhold) { + t->length = p; + while (p > 0x10000) { + p = strip_table(t, 0); + len_saved = t->length - p; + if (len_saved < 0x10000) + break; + len_saved = orig_length - p; + if (len_saved < threshhold) + break; + threshhold *= 2; + } + } + + t->depth = 1; + + + // find size of table if we use path 86 + decent_size = pack_table_by_modes(t, modelist); + + + #if 1 + // find best packing of remainder of table by exploring tree of packings + r = pack_table(t, 0, decent_size); + // use the computed 'path' to evaluate and output tree + path = r.path; + #else + path = 86;//90;//132097; + #endif + + while (path) { + modes[num_modes++] = (path & 127) - 1; + path >>= 7; + } + + printf("// modes: %d\n", r.path); + s = *t; + while (num_modes > 0) { + char name[256]; + sprintf(name, "%s_%d", table_name, num_modes+1); + --num_modes; + s = pack_for_mode(&s, modes[num_modes], name); + } + // output the final table as-is + if (s.splittable) + output_table_with_trims(table_name, "_1", s.input, s.length); + else + output_table(table_name, "_1", s.input, s.length, 0, NULL); +} + +uval unicode_table[0x110000]; + +typedef struct +{ + uval lo,hi; +} char_range; + +char_range get_range(char *str) +{ + char_range cr; + char *p; + cr.lo = strtol(str, &p, 16); + p = stb_skipwhite(p); + if (*p == '.') + cr.hi = strtol(p+2, NULL, 16); + else + cr.hi = cr.lo; + return cr; +} + +char *skip_semi(char *s, int count) +{ + while (count) { + s = strchr(s, ';'); + assert(s != NULL); + ++s; + --count; + } + return s; +} + +int main(int argc, char **argv) +{ + table t; + uval maxv=0; + int i,n=0; + char **s = stb_stringfile("../../data/UnicodeData.txt", &n); + assert(s); + for (i=0; i < n; ++i) { + if (s[i][0] == '#' || s[i][0] == '\n' || s[i][0] == 0) + ; + else { + char_range cr = get_range(s[i]); + char *t = skip_semi(s[i], 13); + uval j, v; + if (*t == ';' || *t == '\n' || *t == 0) + v = 0; + else { + v = strtol(t, NULL, 16); + if (v < 65536) { + maxv = stb_max(v, maxv); + for (j=cr.lo; j <= cr.hi; ++j) { + unicode_table[j] = v; + //printf("%06x => %06x\n", j, v); + } + } + } + } + } + + t.depth = 0; + t.dont_care = UVAL_DONT_CARE_DEFAULT; + t.fallback = 0; + t.fastpath = 256; + t.inherited_storage = 0; + t.has_sign = 0; + t.splittable = 0; + t.input = unicode_table; + t.input_size = size_for_max_number(maxv); + t.length = 0x110000; + t.replace_fallback_with_codepoint = 1; + + optimize_table(&t, "stbu_upppercase"); + return 0; +} diff --git a/source/engine/thirdparty/stb/tools/unicode/unicode.dsp b/source/engine/thirdparty/stb/tools/unicode/unicode.dsp new file mode 100644 index 0000000..78e6a5b --- /dev/null +++ b/source/engine/thirdparty/stb/tools/unicode/unicode.dsp @@ -0,0 +1,88 @@ +# Microsoft Developer Studio Project File - Name="unicode" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=unicode - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "unicode.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "unicode.mak" CFG="unicode - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "unicode - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "unicode - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "unicode - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "unicode - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "unicode - Win32 Release" +# Name "unicode - Win32 Debug" +# Begin Source File + +SOURCE=..\unicode.c +# End Source File +# End Target +# End Project

    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%0A&n{AVJ>EZqD< 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≥%f{5h8Hgq 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